jinzd-ai-cli 0.4.209 → 0.4.211

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.
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-EN63JA7N.js";
6
- import "./chunk-UPMBIS4T.js";
5
+ } from "./chunk-6NS6643Y.js";
6
+ import "./chunk-E5XCM4A6.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-EYID2AIR.js";
4
+ } from "./chunk-ZY2N2N6T.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-JVLRMIHA.js";
4
+ } from "./chunk-ZOPYREL5.js";
5
5
  import "./chunk-T2NL5ZIA.js";
6
6
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-EN63JA7N.js";
7
+ import "./chunk-6NS6643Y.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-STIEOOOL.js";
10
+ } from "./chunk-JBWA73GK.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-UPMBIS4T.js";
18
+ } from "./chunk-E5XCM4A6.js";
19
19
  import "./chunk-4BKXL7SM.js";
20
20
  import "./chunk-TB4W4Y4T.js";
21
21
  import "./chunk-KHYD3WXE.js";
@@ -16,18 +16,28 @@ import {
16
16
  McpManager,
17
17
  SkillManager,
18
18
  autoTrimSessionIfNeeded,
19
+ buildInitPrompt,
20
+ loadContextFiles,
19
21
  loadDevState,
20
22
  persistToolRound,
21
23
  readGitDiff,
24
+ scanDirTree,
25
+ scanProject,
22
26
  setupProxy
23
- } from "./chunk-ATNYI6JL.js";
27
+ } from "./chunk-C3OU2OPF.js";
28
+ import {
29
+ buildReviewPrompt,
30
+ buildSecurityReviewPrompt
31
+ } from "./chunk-HLWUDRBO.js";
24
32
  import {
25
33
  ToolExecutor,
26
34
  ToolRegistry,
27
35
  askUserContext,
28
- checkPermission,
36
+ checkNetworkPolicy,
37
+ checkPermissionWithProfile,
29
38
  cleanupRejectedTeeFile,
30
39
  estimateTokens,
40
+ formatPermissionProfileWarning,
31
41
  googleSearchContext,
32
42
  groupCallsByPhase,
33
43
  renderDiff,
@@ -39,10 +49,10 @@ import {
39
49
  spawnAgentContext,
40
50
  truncateOutput,
41
51
  undoStack
42
- } from "./chunk-JVLRMIHA.js";
52
+ } from "./chunk-ZOPYREL5.js";
43
53
  import "./chunk-T2NL5ZIA.js";
44
54
  import "./chunk-BXP6YZ2P.js";
45
- import "./chunk-EN63JA7N.js";
55
+ import "./chunk-6NS6643Y.js";
46
56
  import {
47
57
  SessionManager,
48
58
  getContentText
@@ -56,7 +66,7 @@ import {
56
66
  } from "./chunk-QMXC327F.js";
57
67
  import {
58
68
  runTool
59
- } from "./chunk-STIEOOOL.js";
69
+ } from "./chunk-JBWA73GK.js";
60
70
  import {
61
71
  CONTENT_ONLY_STREAM_REMINDER,
62
72
  TEE_FINAL_USER_NUDGE,
@@ -75,13 +85,12 @@ import {
75
85
  } from "./chunk-XPBEJB27.js";
76
86
  import {
77
87
  ConfigManager
78
- } from "./chunk-DUZRQXIP.js";
88
+ } from "./chunk-UOROWTGG.js";
79
89
  import "./chunk-TZQHYZKT.js";
80
90
  import {
81
91
  AGENTIC_BEHAVIOR_GUIDELINE,
82
92
  AUTHOR,
83
93
  AUTHOR_EMAIL,
84
- CONTEXT_FILE_CANDIDATES,
85
94
  CUSTOM_COMMANDS_DIR_NAME,
86
95
  DEFAULT_MAX_TOKENS,
87
96
  DEFAULT_MAX_TOOL_ROUNDS,
@@ -95,7 +104,7 @@ import {
95
104
  SKILLS_DIR_NAME,
96
105
  VERSION,
97
106
  buildUserIdentityPrompt
98
- } from "./chunk-UPMBIS4T.js";
107
+ } from "./chunk-E5XCM4A6.js";
99
108
  import {
100
109
  formatGitContextForPrompt,
101
110
  getGitContext,
@@ -121,6 +130,7 @@ import { networkInterfaces } from "os";
121
130
 
122
131
  // src/web/tool-executor-web.ts
123
132
  import { randomUUID } from "crypto";
133
+ import { tmpdir } from "os";
124
134
  import { existsSync, readFileSync } from "fs";
125
135
  var ToolExecutorWeb = class _ToolExecutorWeb {
126
136
  constructor(registry, ws) {
@@ -132,6 +142,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
132
142
  hookConfig;
133
143
  permissionRules = [];
134
144
  defaultPermission = "confirm";
145
+ permissionProfileName = "legacy";
146
+ permissionProfile;
147
+ allowedPermissionProfiles = [];
148
+ workspaceRoot = process.cwd();
149
+ networkPolicy;
135
150
  /** Pending confirm promises keyed by requestId */
136
151
  pendingConfirms = /* @__PURE__ */ new Map();
137
152
  /** Pending batch confirm promises */
@@ -166,6 +181,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
166
181
  this.hookConfig = opts.hookConfig;
167
182
  if (opts.permissionRules) this.permissionRules = opts.permissionRules;
168
183
  if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
184
+ if (opts.permissionProfileName) this.permissionProfileName = opts.permissionProfileName;
185
+ if (opts.permissionProfile) this.permissionProfile = opts.permissionProfile;
186
+ if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
187
+ if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
188
+ if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
169
189
  }
170
190
  /** Clear M7 timeout timer for a requestId */
171
191
  clearPendingTimer(requestId) {
@@ -214,6 +234,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
214
234
  }
215
235
  sendToolCallStart(call) {
216
236
  const dangerLevel = getDangerLevel(call.name, call.arguments);
237
+ let toolCallStarted = false;
217
238
  const startTime = Date.now();
218
239
  this.toolStartTimes.set(call.id, startTime);
219
240
  const msg = {
@@ -322,30 +343,66 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
322
343
  return { callId: call.id, content: `Unknown tool: ${call.name}`, isError: true };
323
344
  }
324
345
  const dangerLevel = getDangerLevel(call.name, call.arguments);
346
+ let toolCallStarted = false;
325
347
  runHook(this.hookConfig?.preToolExecution, {
326
348
  tool: call.name,
327
349
  dangerLevel,
328
350
  args: JSON.stringify(call.arguments).slice(0, 200)
329
351
  });
330
- if (this.permissionRules.length > 0) {
331
- const action = checkPermission(call.name, call.arguments, dangerLevel, this.permissionRules, this.defaultPermission);
332
- if (action === "deny") {
333
- return { callId: call.id, content: `[Permission denied] Tool ${call.name} is blocked by permission rules. Do not retry.`, isError: true };
352
+ const permission = checkPermissionWithProfile(
353
+ call.name,
354
+ call.arguments,
355
+ dangerLevel,
356
+ this.permissionRules,
357
+ this.defaultPermission,
358
+ {
359
+ profileName: this.permissionProfileName,
360
+ profile: this.permissionProfile,
361
+ allowedProfiles: this.allowedPermissionProfiles,
362
+ workspaceRoot: this.workspaceRoot,
363
+ tempDirs: [tmpdir()]
334
364
  }
335
- if (action === "auto-approve") {
336
- this.sendToolCallStart(call);
337
- try {
338
- const rawContent = await runTool(tool, call.arguments, call.name);
339
- const content = truncateOutput(rawContent, call.name);
340
- this.sendToolCallResult(call, rawContent, false);
341
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
342
- return { callId: call.id, content, isError: false };
343
- } catch (err) {
344
- const message = err instanceof Error ? err.message : String(err);
345
- this.sendToolCallResult(call, message, true);
346
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
347
- return { callId: call.id, content: message, isError: true };
348
- }
365
+ );
366
+ const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
367
+ if (networkPermission?.action === "deny") {
368
+ const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
369
+ return {
370
+ callId: call.id,
371
+ content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
372
+ isError: true
373
+ };
374
+ }
375
+ if (permission.action === "deny") {
376
+ const reason = permission.reason ? ` (${permission.reason})` : "";
377
+ return {
378
+ callId: call.id,
379
+ content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
380
+ isError: true
381
+ };
382
+ }
383
+ if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
384
+ this.sendToolCallStart(call);
385
+ try {
386
+ const rawContent = await runTool(tool, call.arguments, call.name);
387
+ const content = truncateOutput(rawContent, call.name);
388
+ this.sendToolCallResult(call, rawContent, false);
389
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
390
+ return { callId: call.id, content, isError: false };
391
+ } catch (err) {
392
+ const message = err instanceof Error ? err.message : String(err);
393
+ this.sendToolCallResult(call, message, true);
394
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
395
+ return { callId: call.id, content: message, isError: true };
396
+ }
397
+ }
398
+ if (networkPermission?.action === "confirm" && dangerLevel === "safe") {
399
+ this.sendToolCallStart(call);
400
+ toolCallStarted = true;
401
+ const confirmed = await this.confirm(call, "write");
402
+ if (!confirmed) {
403
+ const rejectionMsg = `[User cancelled] The user declined network access for ${call.name}. Do not retry without asking.`;
404
+ this.sendToolCallResult(call, rejectionMsg, true);
405
+ return { callId: call.id, content: rejectionMsg, isError: true };
349
406
  }
350
407
  }
351
408
  this.sendToolCallStart(call);
@@ -404,6 +461,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
404
461
  return results;
405
462
  }
406
463
  async executeBatchFileWrites(items) {
464
+ if (this.permissionProfileName !== "legacy" || this.permissionRules.length > 0) {
465
+ return Promise.all(items.map(({ call }) => this.execute(call)));
466
+ }
407
467
  const calls = items.map((i) => i.call);
408
468
  const decision = this.sessionAutoApprove ? "all" : await this.batchConfirm(calls);
409
469
  const results = new Array(calls.length);
@@ -536,7 +596,7 @@ function lastAssistantText(messages) {
536
596
  }
537
597
  return void 0;
538
598
  }
539
- var SessionHandler = class _SessionHandler {
599
+ var SessionHandler = class {
540
600
  ws;
541
601
  config;
542
602
  providers;
@@ -573,6 +633,7 @@ var SessionHandler = class _SessionHandler {
573
633
  pendingAutoPause = /* @__PURE__ */ new Map();
574
634
  /** Active system prompt from context files */
575
635
  activeSystemPrompt;
636
+ contextLoadResult = null;
576
637
  /** Directories added via /add-dir */
577
638
  addedDirs = /* @__PURE__ */ new Set();
578
639
  /** Track MCP tool names used across this session (for MCP budget prioritization) */
@@ -616,7 +677,22 @@ var SessionHandler = class _SessionHandler {
616
677
  const hooks = this.config.get("hooks");
617
678
  const permissionRules = this.config.get("permissionRules");
618
679
  const defaultPermission = this.config.get("defaultPermission");
619
- this.toolExecutor.setConfig({ hookConfig: hooks, permissionRules, defaultPermission });
680
+ const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
681
+ const permissionProfiles = this.config.get("permissionProfiles") ?? {};
682
+ this.toolExecutor.setConfig({
683
+ hookConfig: hooks,
684
+ permissionRules,
685
+ defaultPermission,
686
+ permissionProfileName,
687
+ permissionProfile: permissionProfiles[permissionProfileName],
688
+ allowedPermissionProfiles: this.config.get("allowedPermissionProfiles") ?? [],
689
+ workspaceRoot: process.cwd(),
690
+ networkPolicy: this.config.get("networkPolicy")
691
+ });
692
+ const permissionProfileWarning = formatPermissionProfileWarning(permissionProfileName);
693
+ if (permissionProfileWarning) {
694
+ this.send({ type: "info", message: permissionProfileWarning });
695
+ }
620
696
  this.sendStatus();
621
697
  askUserContext.rl = null;
622
698
  askUserContext.prompting = false;
@@ -651,6 +727,7 @@ var SessionHandler = class _SessionHandler {
651
727
  messageCount: this.sessions.current?.messages.length ?? 0,
652
728
  planMode: this.planMode,
653
729
  thinkingMode: this.runtimeThinking ?? false,
730
+ permissionProfile: this.config.get("defaultPermissionProfile") ?? "legacy",
654
731
  tokenUsage: { ...this.sessionTokenUsage },
655
732
  costUsd,
656
733
  providers: providerList,
@@ -1697,7 +1774,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
1697
1774
  " /plan [enter|exit] \u2014 Toggle read-only planning mode",
1698
1775
  " /session new|list|load|delete <id> \u2014 Session management",
1699
1776
  " /system [prompt|clear] \u2014 Set/view/reset system prompt",
1700
- " /context [reload] \u2014 Show/reload context layers",
1777
+ " /context [status|reload] \u2014 Show/reload context layers",
1701
1778
  " /status \u2014 Show session info & token usage",
1702
1779
  " /cost \u2014 Show cumulative token usage",
1703
1780
  " /config [show|get|set] \u2014 View/modify configuration",
@@ -2357,7 +2434,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2357
2434
  diff = head + "\n\n... [diff truncated, " + diff.length + " chars total] ...\n\n" + tail;
2358
2435
  truncated = true;
2359
2436
  }
2360
- const reviewPrompt = this.buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
2437
+ const reviewPrompt = buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
2361
2438
  this.send({ type: "info", message: "\u{1F50D} Analyzing changes..." });
2362
2439
  try {
2363
2440
  const review = await this.chatOnce(reviewPrompt, { temperature: 0.3, maxTokens: 8192 });
@@ -2395,7 +2472,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2395
2472
  secDiff = head + "\n\n... [diff truncated, " + secDiff.length + " chars total] ...\n\n" + tail;
2396
2473
  secTruncated = true;
2397
2474
  }
2398
- const secPrompt = this.buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
2475
+ const secPrompt = buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
2399
2476
  this.send({ type: "info", message: "\u{1F512} Scanning for security vulnerabilities..." });
2400
2477
  try {
2401
2478
  const secReview = await this.chatOnce(secPrompt, { temperature: 0.2, maxTokens: 8192 });
@@ -2456,7 +2533,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2456
2533
  case "test": {
2457
2534
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2458
2535
  try {
2459
- const { executeTests } = await import("./run-tests-NDS2IEMK.js");
2536
+ const { executeTests } = await import("./run-tests-3MHWUF43.js");
2460
2537
  const argStr = args.join(" ").trim();
2461
2538
  let testArgs = {};
2462
2539
  if (argStr) {
@@ -2482,8 +2559,8 @@ Use /init --force to overwrite.` });
2482
2559
  }
2483
2560
  this.send({ type: "info", message: "\u{1F4DD} Scanning project structure..." });
2484
2561
  try {
2485
- const projectInfo = this.scanProject(cwd);
2486
- const prompt = this.buildInitPrompt(projectInfo, cwd);
2562
+ const projectInfo = scanProject(cwd);
2563
+ const prompt = buildInitPrompt(projectInfo, cwd);
2487
2564
  const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
2488
2565
  writeFileSync(targetPath, content, "utf-8");
2489
2566
  this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
@@ -2620,29 +2697,24 @@ ${this.config.toFormattedJSON()}
2620
2697
  this.send({ type: "info", message: this.activeSystemPrompt ? `\u2713 Context reloaded (${this.activeSystemPrompt.length} chars).` : "\u2713 No context files found." });
2621
2698
  break;
2622
2699
  }
2623
- const configDir = this.config.getConfigDir();
2624
- const cwd = process.cwd();
2625
- const gitRoot = getGitRoot(cwd);
2626
- const projectRoot = gitRoot ?? cwd;
2700
+ const result = this.contextLoadResult;
2627
2701
  const layers = ["\u{1F4DA} **Context Layers:**", ""];
2628
- const checkLayer = (label, dir) => {
2629
- for (const name2 of CONTEXT_FILE_CANDIDATES) {
2630
- const fullPath = join2(dir, name2);
2631
- if (existsSync3(fullPath)) {
2632
- try {
2633
- const size = statSync(fullPath).size;
2634
- layers.push(` \u2713 ${label}: ${fullPath} (${size} bytes)`);
2635
- return;
2636
- } catch {
2637
- }
2638
- }
2702
+ if (result && result.layers.length > 0) {
2703
+ const labels = { global: "Global ", project: "Project", local: "Local " };
2704
+ for (const layer of result.layers) {
2705
+ layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
2706
+ }
2707
+ layers.push(` Total: ${result.totalChars} chars (${result.layers.length} layer${result.layers.length > 1 ? "s" : ""})`);
2708
+ } else {
2709
+ layers.push(" No context files loaded.");
2710
+ layers.push(" Search order per layer: AICLI.override.md \u2192 AGENTS.override.md \u2192 AICLI.md \u2192 CLAUDE.md \u2192 AGENTS.md");
2711
+ }
2712
+ if (result && result.skipped.length > 0) {
2713
+ layers.push("");
2714
+ layers.push("Skipped:");
2715
+ for (const skipped of result.skipped.slice(0, 5)) {
2716
+ layers.push(` - ${skipped.displayPath}: ${skipped.reason}${skipped.message ? ` (${skipped.message})` : ""}`);
2639
2717
  }
2640
- layers.push(` \u2013 ${label}: (none)`);
2641
- };
2642
- checkLayer("Global", configDir);
2643
- checkLayer("Project", projectRoot);
2644
- if (resolve(cwd) !== resolve(projectRoot)) {
2645
- checkLayer("Subdir", cwd);
2646
2718
  }
2647
2719
  layers.push("");
2648
2720
  layers.push(`Total prompt length: ${this.activeSystemPrompt?.length ?? 0} chars`);
@@ -3129,7 +3201,7 @@ Add .md files to create commands.` });
3129
3201
  dirContext += `
3130
3202
  [Directory: ${dir}]
3131
3203
  `;
3132
- dirContext += this.scanDirTree(dir, 2, 40) + "\n";
3204
+ dirContext += scanDirTree(dir, 2, 40) + "\n";
3133
3205
  totalLen = dirContext.length;
3134
3206
  }
3135
3207
  stable += dirContext;
@@ -3168,265 +3240,21 @@ Add .md files to create commands.` });
3168
3240
  }
3169
3241
  return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
3170
3242
  }
3171
- /**
3172
- * Find first matching context file in a directory.
3173
- */
3174
- findContextFile(dir) {
3175
- for (const name of CONTEXT_FILE_CANDIDATES) {
3176
- const fullPath = join2(dir, name);
3177
- try {
3178
- if (existsSync3(fullPath)) {
3179
- const content = readFileSync3(fullPath, "utf-8").trim();
3180
- if (content) return content;
3181
- }
3182
- } catch {
3183
- }
3184
- }
3185
- return null;
3186
- }
3187
- /**
3188
- * Load hierarchical context files (same as CLI):
3189
- * 1. Global: ~/.aicli/AICLI.md or CLAUDE.md
3190
- * 2. Project: <git-root>/AICLI.md or CLAUDE.md
3191
- * 3. Subdir: <cwd>/AICLI.md or CLAUDE.md (only if cwd ≠ project root)
3192
- */
3193
- // ── /init helpers ─────────────────────────────────────────────────
3194
- static SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
3195
- "node_modules",
3196
- ".git",
3197
- "dist",
3198
- "build",
3199
- "out",
3200
- "target",
3201
- ".next",
3202
- ".nuxt",
3203
- "__pycache__",
3204
- ".venv",
3205
- "venv",
3206
- ".tox",
3207
- ".mypy_cache",
3208
- ".pytest_cache",
3209
- ".gradle",
3210
- ".idea",
3211
- ".vscode",
3212
- ".vs",
3213
- "coverage",
3214
- ".cache",
3215
- ".parcel-cache",
3216
- "dist-cjs",
3217
- "release",
3218
- ".output",
3219
- ".turbo",
3220
- "vendor"
3221
- ]);
3222
- scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
3223
- const lines = [];
3224
- let count = 0;
3225
- const walk = (d, prefix, depth) => {
3226
- if (depth > maxDepth || count >= maxEntries) return;
3227
- let entries;
3228
- try {
3229
- entries = readdirSync(d);
3230
- } catch {
3231
- return;
3232
- }
3233
- const filtered = entries.filter((e) => !e.startsWith(".") && !_SessionHandler.SCAN_SKIP_DIRS.has(e));
3234
- const sorted = filtered.sort((a, b) => {
3235
- let aIsDir = false, bIsDir = false;
3236
- try {
3237
- aIsDir = statSync(join2(d, a)).isDirectory();
3238
- } catch {
3239
- }
3240
- try {
3241
- bIsDir = statSync(join2(d, b)).isDirectory();
3242
- } catch {
3243
- }
3244
- if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
3245
- return a.localeCompare(b);
3246
- });
3247
- for (let i = 0; i < sorted.length && count < maxEntries; i++) {
3248
- const name = sorted[i];
3249
- const fullPath = join2(d, name);
3250
- const isLast = i === sorted.length - 1;
3251
- let isDir;
3252
- try {
3253
- isDir = statSync(fullPath).isDirectory();
3254
- } catch {
3255
- continue;
3256
- }
3257
- lines.push(prefix + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ") + name + (isDir ? "/" : ""));
3258
- count++;
3259
- if (isDir) walk(fullPath, prefix + (isLast ? " " : "\u2502 "), depth + 1);
3260
- }
3261
- };
3262
- walk(dir, "", 0);
3263
- if (count >= maxEntries) lines.push("... (truncated)");
3264
- return lines.join("\n");
3265
- }
3266
- scanProject(cwd) {
3267
- const info = { type: "unknown", language: "unknown", configFiles: [], directoryStructure: "" };
3268
- const check = (file) => existsSync3(join2(cwd, file));
3269
- const configCandidates = [
3270
- "package.json",
3271
- "tsconfig.json",
3272
- "Cargo.toml",
3273
- "pyproject.toml",
3274
- "setup.py",
3275
- "requirements.txt",
3276
- "go.mod",
3277
- "pom.xml",
3278
- "build.gradle",
3279
- "build.gradle.kts",
3280
- "CMakeLists.txt",
3281
- "Makefile",
3282
- ".csproj",
3283
- ".sln",
3284
- "composer.json",
3285
- "Gemfile",
3286
- "mix.exs",
3287
- "deno.json",
3288
- "bun.lockb"
3289
- ];
3290
- info.configFiles = configCandidates.filter(check);
3291
- if (check("package.json")) {
3292
- info.type = "node";
3293
- info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
3294
- try {
3295
- const pkg = JSON.parse(readFileSync3(join2(cwd, "package.json"), "utf-8"));
3296
- const scripts = pkg.scripts ?? {};
3297
- info.buildCommand = scripts.build ? "npm run build" : void 0;
3298
- info.testCommand = scripts.test ? "npm test" : void 0;
3299
- info.devCommand = scripts.dev ? "npm run dev" : void 0;
3300
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3301
- if (allDeps["react"]) info.framework = "React";
3302
- else if (allDeps["vue"]) info.framework = "Vue";
3303
- else if (allDeps["@angular/core"]) info.framework = "Angular";
3304
- else if (allDeps["next"]) info.framework = "Next.js";
3305
- else if (allDeps["express"]) info.framework = "Express";
3306
- } catch {
3307
- }
3308
- } else if (check("Cargo.toml")) {
3309
- info.type = "rust";
3310
- info.language = "Rust";
3311
- info.buildCommand = "cargo build";
3312
- info.testCommand = "cargo test";
3313
- } else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
3314
- info.type = "python";
3315
- info.language = "Python";
3316
- info.testCommand = "pytest";
3317
- } else if (check("go.mod")) {
3318
- info.type = "go";
3319
- info.language = "Go";
3320
- info.buildCommand = "go build ./...";
3321
- info.testCommand = "go test ./...";
3322
- } else if (check("pom.xml")) {
3323
- info.type = "java";
3324
- info.language = "Java";
3325
- info.buildCommand = "mvn package";
3326
- info.testCommand = "mvn test";
3327
- } else if (check("build.gradle") || check("build.gradle.kts")) {
3328
- info.type = "java";
3329
- info.language = "Java/Kotlin";
3330
- info.buildCommand = "./gradlew build";
3331
- info.testCommand = "./gradlew test";
3332
- }
3333
- info.directoryStructure = this.scanDirTree(cwd);
3334
- return info;
3335
- }
3336
- buildInitPrompt(info, cwd) {
3337
- const parts = [
3338
- "Please generate an AICLI.md context file (Markdown format) for the following project.",
3339
- "\n## Project Info\n",
3340
- `- Working directory: ${cwd}`,
3341
- `- Type: ${info.type}`,
3342
- `- Language: ${info.language}`
3343
- ];
3344
- if (info.framework) parts.push(`- Framework: ${info.framework}`);
3345
- if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
3346
- if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
3347
- if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
3348
- parts.push(`
3349
- ## Detected Config Files
3350
- ${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
3351
- parts.push(`
3352
- ## Directory Structure
3353
- \`\`\`
3354
- ${info.directoryStructure}
3355
- \`\`\``);
3356
- parts.push(`
3357
- ## Requirements
3358
- Generate a structured Markdown file with: project overview, tech stack, project structure, common commands, code style. Keep it concise, within 200 lines.`);
3359
- return parts.join("\n");
3360
- }
3361
- // ── /review helper ──────────────────────────────────────────────────
3362
- buildReviewPrompt(diff, gitContextStr, detailed) {
3363
- const level = detailed ? "Please perform a detailed in-depth review covering: security, performance, maintainability, error handling, naming conventions, and code duplication." : "Please perform a concise code review focusing on bugs, security issues, and key improvement suggestions.";
3364
- return `# Code Review Request
3365
-
3366
- ${level}
3367
-
3368
- ## Git Status
3369
- ${gitContextStr}
3370
-
3371
- ## Code Changes (diff)
3372
- \`\`\`diff
3373
- ${diff}
3374
- \`\`\`
3375
-
3376
- ## Output Format
3377
- 1. **Overall Assessment**: One-sentence summary
3378
- 2. **Issues** (if any): [Severity] file:line \u2014 description + fix
3379
- 3. **Improvement Suggestions** (if any)
3380
- 4. **Highlights** (if any)
3381
-
3382
- Severity: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
3383
- }
3384
- buildSecurityReviewPrompt(diff, gitContextStr) {
3385
- return `# Security Vulnerability Review
3386
-
3387
- Analyze the following code changes **exclusively for security vulnerabilities**.
3388
-
3389
- ## Categories
3390
- 1. Injection (SQL, command, path traversal, XSS)
3391
- 2. Auth & Authorization (hardcoded creds, missing checks)
3392
- 3. Secrets & Sensitive Data (API keys, tokens in code)
3393
- 4. Input Validation (missing validation, unsafe deserialization)
3394
- 5. Cryptography (weak algorithms, hardcoded IVs)
3395
- 6. File System (path traversal, symlink attacks)
3396
- 7. Network (SSRF, insecure protocols)
3397
-
3398
- ## Git Status
3399
- ${gitContextStr}
3400
-
3401
- ## Code Changes
3402
- \`\`\`diff
3403
- ${diff}
3404
- \`\`\`
3405
-
3406
- ## Output Format
3407
- For each finding:
3408
- - **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW
3409
- - **Category** + **File:line**
3410
- - **Description** + exploitation scenario
3411
- - **Recommended fix**
3412
-
3413
- If none found: "\u2705 No security vulnerabilities detected"`;
3414
- }
3415
3243
  loadContextFiles() {
3416
- const parts = [];
3244
+ const contextConfig = this.config.get("context");
3417
3245
  const cwd = process.cwd();
3418
- const configDir = this.config.getConfigDir();
3419
- const globalCtx = this.findContextFile(configDir);
3420
- if (globalCtx) parts.push(globalCtx);
3421
3246
  const gitRoot = getGitRoot(cwd);
3422
3247
  const projectRoot = gitRoot ?? cwd;
3423
- const projectCtx = this.findContextFile(projectRoot);
3424
- if (projectCtx) parts.push(projectCtx);
3425
- if (resolve(cwd) !== resolve(projectRoot)) {
3426
- const localCtx = this.findContextFile(cwd);
3427
- if (localCtx) parts.push(localCtx);
3428
- }
3429
- return parts.length > 0 ? parts.join("\n\n---\n\n") : void 0;
3248
+ const result = loadContextFiles({
3249
+ cwd,
3250
+ configDir: this.config.getConfigDir(),
3251
+ projectRoot,
3252
+ setting: this.config.get("contextFile"),
3253
+ fallbackFilenames: contextConfig.projectDocFallbackFilenames,
3254
+ maxBytes: contextConfig.projectDocMaxBytes
3255
+ });
3256
+ this.contextLoadResult = result;
3257
+ return result.mergedContent || void 0;
3430
3258
  }
3431
3259
  };
3432
3260