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.
- package/README.md +104 -6
- package/README.zh-CN.md +45 -5
- package/dist/{batch-45WLJUVP.js → batch-DE4RXKZD.js} +2 -2
- package/dist/{chunk-EN63JA7N.js → chunk-6NS6643Y.js} +1 -1
- package/dist/{chunk-D6M4E7SH.js → chunk-BE6ERF7M.js} +1 -1
- package/dist/{chunk-ATNYI6JL.js → chunk-C3OU2OPF.js} +374 -22
- package/dist/{chunk-UPMBIS4T.js → chunk-E5XCM4A6.js} +5 -3
- package/dist/{chunk-STIEOOOL.js → chunk-JBWA73GK.js} +1 -1
- package/dist/{chunk-DUZRQXIP.js → chunk-UOROWTGG.js} +45 -6
- package/dist/{chunk-JVLRMIHA.js → chunk-ZOPYREL5.js} +312 -38
- package/dist/{chunk-EYID2AIR.js → chunk-ZY2N2N6T.js} +5 -3
- package/dist/{ci-PUE3MQ7J.js → ci-XMUEX526.js} +2 -2
- package/dist/{constants-VTZLKDAG.js → constants-AWTIQIWG.js} +3 -1
- package/dist/{doctor-cli-IU6FSVLF.js → doctor-cli-SSI6ETFT.js} +4 -4
- package/dist/electron-server.js +1006 -471
- package/dist/{hub-4ETK4R2J.js → hub-INUJND2G.js} +1 -1
- package/dist/index.js +137 -367
- package/dist/{run-tests-NDS2IEMK.js → run-tests-3MHWUF43.js} +2 -2
- package/dist/{run-tests-UAS5NJBM.js → run-tests-PACN4UYX.js} +1 -1
- package/dist/{server-72SAPLPS.js → server-7USZJJAH.js} +4 -4
- package/dist/{server-4URLNC7P.js → server-BTSKOPQI.js} +139 -311
- package/dist/{task-orchestrator-6PZTB35T.js → task-orchestrator-OV4O25MX.js} +4 -4
- package/dist/{usage-ZNCKGF6U.js → usage-T2P6FTE7.js} +2 -2
- package/dist/web/client/app.js +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ToolRegistry
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZOPYREL5.js";
|
|
5
5
|
import "./chunk-T2NL5ZIA.js";
|
|
6
6
|
import "./chunk-BXP6YZ2P.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-6NS6643Y.js";
|
|
8
8
|
import {
|
|
9
9
|
runTool
|
|
10
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
52
|
+
} from "./chunk-ZOPYREL5.js";
|
|
43
53
|
import "./chunk-T2NL5ZIA.js";
|
|
44
54
|
import "./chunk-BXP6YZ2P.js";
|
|
45
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
|
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.
|
|
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]
|
|
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 =
|
|
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 =
|
|
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-
|
|
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 =
|
|
2486
|
-
const prompt =
|
|
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
|
|
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
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
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 +=
|
|
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
|
|
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
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
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
|
|