jinzd-ai-cli 0.4.208 → 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 +10 -8
- package/README.zh-CN.md +5 -5
- package/dist/{batch-3T44NFLG.js → batch-MFPYHOIL.js} +4 -4
- package/dist/{chunk-TD7JEHCR.js → chunk-7JWT3KXO.js} +390 -23
- package/dist/{chunk-SQB66GP6.js → chunk-BNERFXVO.js} +12 -9
- package/dist/{chunk-J22B3OSQ.js → chunk-DJ35G5VO.js} +1 -1
- package/dist/{chunk-VWYUB22Y.js → chunk-E2ZWWE6Q.js} +1 -1
- package/dist/{chunk-KV4GYNEA.js → chunk-GRJNQJA5.js} +5 -3
- package/dist/{chunk-QDY72ABW.js → chunk-HZQVX7VF.js} +5 -3
- package/dist/{chunk-O6UFCEUZ.js → chunk-K3CF65QH.js} +12 -9
- package/dist/{chunk-2OVMMSGQ.js → chunk-NEPFADHX.js} +10 -9
- package/dist/{chunk-P7JY3SWA.js → chunk-NWP7C6CC.js} +1 -1
- package/dist/{chunk-TM4MS63K.js → chunk-P6MTFCXB.js} +14 -6
- package/dist/{ci-6QWBVRJX.js → ci-QLJUDLMY.js} +3 -2
- package/dist/{constants-SZTQNN7K.js → constants-47OR72MV.js} +3 -1
- package/dist/{doctor-cli-I2Y2YR4V.js → doctor-cli-32COMA5K.js} +4 -4
- package/dist/electron-server.js +652 -457
- package/dist/{hub-XVPFBODB.js → hub-3NWJUCLK.js} +1 -1
- package/dist/index.js +128 -376
- package/dist/{indexer-AKWMYNJI.js → indexer-2AG4G6B5.js} +1 -1
- package/dist/{indexer-BMYUUDLH.js → indexer-4WWS3VIL.js} +1 -1
- package/dist/{run-tests-HZ2CRCTZ.js → run-tests-NT2UIUVB.js} +1 -1
- package/dist/{run-tests-D5SNUBYX.js → run-tests-UT7RZ7Y3.js} +2 -2
- package/dist/{server-PVKLPRUP.js → server-AIHVMLNU.js} +5 -5
- package/dist/{server-4EFTRFTN.js → server-E5D54DIZ.js} +87 -306
- package/dist/{task-orchestrator-2A5VCFLS.js → task-orchestrator-BFDSTSOH.js} +5 -5
- package/dist/{usage-Q24E5636.js → usage-P4B2RZKL.js} +2 -2
- package/package.json +2 -1
package/dist/electron-server.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-SKET65WZ.js";
|
|
4
4
|
import {
|
|
5
5
|
indexProject
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BNERFXVO.js";
|
|
7
7
|
import {
|
|
8
8
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
9
9
|
APP_NAME,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
CONFIG_DIR_NAME,
|
|
13
13
|
CONFIG_FILE_NAME,
|
|
14
14
|
CONTEXT_FILE_CANDIDATES,
|
|
15
|
+
CONTEXT_FILE_MAX_BYTES,
|
|
15
16
|
CUSTOM_COMMANDS_DIR_NAME,
|
|
16
17
|
DEFAULT_MAX_TOKENS,
|
|
17
18
|
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
VERSION,
|
|
37
38
|
buildUserIdentityPrompt,
|
|
38
39
|
runTestsTool
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-HZQVX7VF.js";
|
|
40
41
|
import {
|
|
41
42
|
hasSemanticIndex,
|
|
42
43
|
semanticSearch
|
|
@@ -59,12 +60,12 @@ import {
|
|
|
59
60
|
import express from "express";
|
|
60
61
|
import { createServer } from "http";
|
|
61
62
|
import { WebSocketServer } from "ws";
|
|
62
|
-
import { join as
|
|
63
|
-
import { existsSync as
|
|
63
|
+
import { join as join19, dirname as dirname6, resolve as resolve7, relative as relative4, sep as sep3 } from "path";
|
|
64
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
|
|
64
65
|
import { networkInterfaces } from "os";
|
|
65
66
|
|
|
66
67
|
// src/config/config-manager.ts
|
|
67
|
-
import { readFileSync,
|
|
68
|
+
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
68
69
|
import { join } from "path";
|
|
69
70
|
import { homedir } from "os";
|
|
70
71
|
|
|
@@ -172,10 +173,15 @@ var ConfigSchema = z.object({
|
|
|
172
173
|
systemPrompt: z.string().optional()
|
|
173
174
|
}).default({}),
|
|
174
175
|
// 项目上下文文件配置
|
|
175
|
-
// 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md 机制
|
|
176
|
-
// 默认按顺序查找:AICLI.md →
|
|
177
|
-
// 设为 false
|
|
176
|
+
// 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md / Codex AGENTS.md 机制
|
|
177
|
+
// 默认按顺序查找:AICLI.override.md → AGENTS.override.md → AICLI.md → CLAUDE.md → AGENTS.md
|
|
178
|
+
// 设为 false 可禁用此功能;指定字符串时仅加载 cwd 下的该文件
|
|
178
179
|
contextFile: z.union([z.string(), z.literal(false)]).default("auto"),
|
|
180
|
+
context: z.object({
|
|
181
|
+
projectDocFallbackFilenames: z.array(z.string()).default([]),
|
|
182
|
+
projectDocMaxBytes: z.number().int().positive().default(32 * 1024),
|
|
183
|
+
showLoadedContextSources: z.boolean().default(false)
|
|
184
|
+
}).default({}),
|
|
179
185
|
// Google Custom Search API 的 Search Engine ID (cx 参数)
|
|
180
186
|
// API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
|
|
181
187
|
// CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
|
|
@@ -503,7 +509,7 @@ ${err}`
|
|
|
503
509
|
}
|
|
504
510
|
save() {
|
|
505
511
|
mkdirSync(this.configDir, { recursive: true });
|
|
506
|
-
|
|
512
|
+
atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
507
513
|
}
|
|
508
514
|
getApiKey(providerId) {
|
|
509
515
|
const envKey = EnvLoader.getApiKey(providerId);
|
|
@@ -4736,7 +4742,7 @@ import { platform as platform2 } from "os";
|
|
|
4736
4742
|
import { resolve } from "path";
|
|
4737
4743
|
|
|
4738
4744
|
// src/tools/undo-stack.ts
|
|
4739
|
-
import { readFileSync as readFileSync3, writeFileSync
|
|
4745
|
+
import { readFileSync as readFileSync3, writeFileSync, unlinkSync as unlinkSync2, rmdirSync, existsSync as existsSync3 } from "fs";
|
|
4740
4746
|
var MAX_UNDO_DEPTH = 20;
|
|
4741
4747
|
var UndoStack = class {
|
|
4742
4748
|
stack = [];
|
|
@@ -4819,7 +4825,7 @@ var UndoStack = class {
|
|
|
4819
4825
|
return { entry, result: `Deleted newly created file: ${entry.filePath}` };
|
|
4820
4826
|
}
|
|
4821
4827
|
} else {
|
|
4822
|
-
|
|
4828
|
+
writeFileSync(entry.filePath, entry.previousContent, "utf-8");
|
|
4823
4829
|
const lines = entry.previousContent.split("\n").length;
|
|
4824
4830
|
return {
|
|
4825
4831
|
entry,
|
|
@@ -6489,7 +6495,7 @@ var ToolExecutor = class {
|
|
|
6489
6495
|
rl.resume();
|
|
6490
6496
|
process.stdout.write(prompt);
|
|
6491
6497
|
this.confirming = true;
|
|
6492
|
-
return new Promise((
|
|
6498
|
+
return new Promise((resolve8) => {
|
|
6493
6499
|
let completed = false;
|
|
6494
6500
|
const cleanup = (result) => {
|
|
6495
6501
|
if (completed) return;
|
|
@@ -6499,7 +6505,7 @@ var ToolExecutor = class {
|
|
|
6499
6505
|
rl.pause();
|
|
6500
6506
|
rlAny.output = savedOutput;
|
|
6501
6507
|
this.confirming = false;
|
|
6502
|
-
|
|
6508
|
+
resolve8(result);
|
|
6503
6509
|
};
|
|
6504
6510
|
const onLine = (line) => {
|
|
6505
6511
|
const trimmed = line.trim();
|
|
@@ -6673,7 +6679,7 @@ var ToolExecutor = class {
|
|
|
6673
6679
|
rl.resume();
|
|
6674
6680
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
6675
6681
|
this.confirming = true;
|
|
6676
|
-
return new Promise((
|
|
6682
|
+
return new Promise((resolve8) => {
|
|
6677
6683
|
let completed = false;
|
|
6678
6684
|
const cleanup = (answer) => {
|
|
6679
6685
|
if (completed) return;
|
|
@@ -6683,7 +6689,7 @@ var ToolExecutor = class {
|
|
|
6683
6689
|
rl.pause();
|
|
6684
6690
|
rlAny.output = savedOutput;
|
|
6685
6691
|
this.confirming = false;
|
|
6686
|
-
|
|
6692
|
+
resolve8(answer === "y");
|
|
6687
6693
|
};
|
|
6688
6694
|
const onLine = (line) => {
|
|
6689
6695
|
const trimmed = line.trim();
|
|
@@ -6864,7 +6870,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
6864
6870
|
const mode = appendMode ? "appended" : "written";
|
|
6865
6871
|
void (async () => {
|
|
6866
6872
|
try {
|
|
6867
|
-
const { updateFile } = await import("./indexer-
|
|
6873
|
+
const { updateFile } = await import("./indexer-4WWS3VIL.js");
|
|
6868
6874
|
await updateFile(process.cwd(), filePath);
|
|
6869
6875
|
} catch {
|
|
6870
6876
|
}
|
|
@@ -7666,7 +7672,7 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
7666
7672
|
walk(rootPath);
|
|
7667
7673
|
return paths;
|
|
7668
7674
|
}
|
|
7669
|
-
async function searchInFileAsync(fullPath,
|
|
7675
|
+
async function searchInFileAsync(fullPath, displayPath2, regex, contextLines, maxResults) {
|
|
7670
7676
|
let content;
|
|
7671
7677
|
try {
|
|
7672
7678
|
content = await readFile(fullPath, "utf-8");
|
|
@@ -7680,7 +7686,7 @@ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, max
|
|
|
7680
7686
|
regex.lastIndex = 0;
|
|
7681
7687
|
if (regex.test(lines[i])) {
|
|
7682
7688
|
const match = {
|
|
7683
|
-
file:
|
|
7689
|
+
file: displayPath2.replace(/\\/g, "/"),
|
|
7684
7690
|
lineNumber: i + 1,
|
|
7685
7691
|
lineText: lines[i]
|
|
7686
7692
|
};
|
|
@@ -7699,7 +7705,7 @@ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, max
|
|
|
7699
7705
|
}
|
|
7700
7706
|
return results;
|
|
7701
7707
|
}
|
|
7702
|
-
function searchInFile(fullPath,
|
|
7708
|
+
function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, results) {
|
|
7703
7709
|
try {
|
|
7704
7710
|
const stat = statSync4(fullPath);
|
|
7705
7711
|
if (stat.size > 1e6) return;
|
|
@@ -7719,7 +7725,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
7719
7725
|
regex.lastIndex = 0;
|
|
7720
7726
|
if (regex.test(lines[i])) {
|
|
7721
7727
|
const result = {
|
|
7722
|
-
file:
|
|
7728
|
+
file: displayPath2,
|
|
7723
7729
|
lineNumber: i + 1,
|
|
7724
7730
|
lineText: lines[i].trimEnd()
|
|
7725
7731
|
};
|
|
@@ -7932,7 +7938,7 @@ var runInteractiveTool = {
|
|
|
7932
7938
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
7933
7939
|
};
|
|
7934
7940
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
7935
|
-
return new Promise((
|
|
7941
|
+
return new Promise((resolve8) => {
|
|
7936
7942
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
7937
7943
|
cwd: process.cwd(),
|
|
7938
7944
|
env,
|
|
@@ -7965,22 +7971,22 @@ var runInteractiveTool = {
|
|
|
7965
7971
|
setTimeout(writeNextLine, 400);
|
|
7966
7972
|
const timer = setTimeout(() => {
|
|
7967
7973
|
child.kill();
|
|
7968
|
-
|
|
7974
|
+
resolve8(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
7969
7975
|
${buildOutput(stdout, stderr)}`);
|
|
7970
7976
|
}, timeout);
|
|
7971
7977
|
child.on("close", (code) => {
|
|
7972
7978
|
clearTimeout(timer);
|
|
7973
7979
|
const output = buildOutput(stdout, stderr);
|
|
7974
7980
|
if (code !== 0 && code !== null) {
|
|
7975
|
-
|
|
7981
|
+
resolve8(`${prefixWarnings}Exit code ${code}:
|
|
7976
7982
|
${output}`);
|
|
7977
7983
|
} else {
|
|
7978
|
-
|
|
7984
|
+
resolve8(`${prefixWarnings}${output || "(no output)"}`);
|
|
7979
7985
|
}
|
|
7980
7986
|
});
|
|
7981
7987
|
child.on("error", (err) => {
|
|
7982
7988
|
clearTimeout(timer);
|
|
7983
|
-
|
|
7989
|
+
resolve8(
|
|
7984
7990
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
7985
7991
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
7986
7992
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -8648,7 +8654,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
8648
8654
|
};
|
|
8649
8655
|
|
|
8650
8656
|
// src/tools/builtin/save-memory.ts
|
|
8651
|
-
import { existsSync as existsSync12,
|
|
8657
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9, statSync as statSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
8652
8658
|
import { join as join7 } from "path";
|
|
8653
8659
|
import { homedir as homedir5 } from "os";
|
|
8654
8660
|
function getMemoryFilePath() {
|
|
@@ -8685,7 +8691,8 @@ var saveMemoryTool = {
|
|
|
8685
8691
|
## ${timestamp}
|
|
8686
8692
|
${content}
|
|
8687
8693
|
`;
|
|
8688
|
-
|
|
8694
|
+
const previous = existsSync12(memoryPath) ? readFileSync9(memoryPath, "utf-8") : "";
|
|
8695
|
+
atomicWriteFileSync(memoryPath, previous + entry);
|
|
8689
8696
|
const byteSize = statSync6(memoryPath).size;
|
|
8690
8697
|
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
8691
8698
|
}
|
|
@@ -8731,7 +8738,7 @@ function promptUser(rl, question) {
|
|
|
8731
8738
|
console.log();
|
|
8732
8739
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
8733
8740
|
process.stdout.write(chalk4.cyan("> "));
|
|
8734
|
-
return new Promise((
|
|
8741
|
+
return new Promise((resolve8) => {
|
|
8735
8742
|
let completed = false;
|
|
8736
8743
|
const cleanup = (answer) => {
|
|
8737
8744
|
if (completed) return;
|
|
@@ -8741,7 +8748,7 @@ function promptUser(rl, question) {
|
|
|
8741
8748
|
rl.pause();
|
|
8742
8749
|
rlAny.output = savedOutput;
|
|
8743
8750
|
askUserContext.prompting = false;
|
|
8744
|
-
|
|
8751
|
+
resolve8(answer);
|
|
8745
8752
|
};
|
|
8746
8753
|
const onLine = (line) => {
|
|
8747
8754
|
cleanup(line);
|
|
@@ -9750,7 +9757,7 @@ ${commitOutput.trim()}`;
|
|
|
9750
9757
|
};
|
|
9751
9758
|
|
|
9752
9759
|
// src/tools/builtin/notebook-edit.ts
|
|
9753
|
-
import { readFileSync as
|
|
9760
|
+
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
|
|
9754
9761
|
import { writeFile } from "fs/promises";
|
|
9755
9762
|
import { resolve as resolve4, extname as extname2 } from "path";
|
|
9756
9763
|
var notebookEditTool = {
|
|
@@ -9808,7 +9815,7 @@ var notebookEditTool = {
|
|
|
9808
9815
|
if (!existsSync14(absPath)) {
|
|
9809
9816
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
9810
9817
|
}
|
|
9811
|
-
const raw =
|
|
9818
|
+
const raw = readFileSync10(absPath, "utf-8");
|
|
9812
9819
|
const nb = parseNotebook(raw);
|
|
9813
9820
|
const cellIdx0 = cellIndexRaw - 1;
|
|
9814
9821
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -10580,7 +10587,7 @@ var McpClient = class {
|
|
|
10580
10587
|
// 内部方法:JSON-RPC 通信
|
|
10581
10588
|
// ══════════════════════════════════════════════════════════════════
|
|
10582
10589
|
sendRequest(method, params) {
|
|
10583
|
-
return new Promise((
|
|
10590
|
+
return new Promise((resolve8, reject) => {
|
|
10584
10591
|
if (!this.process?.stdin?.writable) {
|
|
10585
10592
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
10586
10593
|
}
|
|
@@ -10604,7 +10611,7 @@ var McpClient = class {
|
|
|
10604
10611
|
this.pendingRequests.set(id, {
|
|
10605
10612
|
resolve: (result) => {
|
|
10606
10613
|
cleanup();
|
|
10607
|
-
|
|
10614
|
+
resolve8(result);
|
|
10608
10615
|
},
|
|
10609
10616
|
reject: (error) => {
|
|
10610
10617
|
cleanup();
|
|
@@ -10681,13 +10688,13 @@ var McpClient = class {
|
|
|
10681
10688
|
}
|
|
10682
10689
|
/** Promise 超时包装 */
|
|
10683
10690
|
withTimeout(promise, ms, label) {
|
|
10684
|
-
return new Promise((
|
|
10691
|
+
return new Promise((resolve8, reject) => {
|
|
10685
10692
|
const timer = setTimeout(() => {
|
|
10686
10693
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
10687
10694
|
}, ms);
|
|
10688
10695
|
promise.then((val) => {
|
|
10689
10696
|
clearTimeout(timer);
|
|
10690
|
-
|
|
10697
|
+
resolve8(val);
|
|
10691
10698
|
}).catch((err) => {
|
|
10692
10699
|
clearTimeout(timer);
|
|
10693
10700
|
reject(err);
|
|
@@ -10968,7 +10975,7 @@ import { existsSync as existsSync16, readdirSync as readdirSync8, mkdirSync as m
|
|
|
10968
10975
|
import { join as join10 } from "path";
|
|
10969
10976
|
|
|
10970
10977
|
// src/skills/types.ts
|
|
10971
|
-
import { readFileSync as
|
|
10978
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
10972
10979
|
import { basename as basename5 } from "path";
|
|
10973
10980
|
function parseSimpleYaml(yaml) {
|
|
10974
10981
|
const result = {};
|
|
@@ -10990,7 +10997,7 @@ function parseYamlArray(value) {
|
|
|
10990
10997
|
function parseSkillFile(filePath) {
|
|
10991
10998
|
let raw;
|
|
10992
10999
|
try {
|
|
10993
|
-
raw =
|
|
11000
|
+
raw = readFileSync11(filePath, "utf-8");
|
|
10994
11001
|
} catch {
|
|
10995
11002
|
return null;
|
|
10996
11003
|
}
|
|
@@ -11119,7 +11126,7 @@ var SkillManager = class {
|
|
|
11119
11126
|
|
|
11120
11127
|
// src/web/tool-executor-web.ts
|
|
11121
11128
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11122
|
-
import { existsSync as existsSync17, readFileSync as
|
|
11129
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
|
|
11123
11130
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
11124
11131
|
constructor(registry, ws) {
|
|
11125
11132
|
this.registry = registry;
|
|
@@ -11175,33 +11182,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11175
11182
|
}
|
|
11176
11183
|
/** Resolve a pending confirm from client response */
|
|
11177
11184
|
resolveConfirm(requestId, approved) {
|
|
11178
|
-
const
|
|
11179
|
-
if (
|
|
11185
|
+
const resolve8 = this.pendingConfirms.get(requestId);
|
|
11186
|
+
if (resolve8) {
|
|
11180
11187
|
this.clearPendingTimer(requestId);
|
|
11181
11188
|
this.pendingConfirms.delete(requestId);
|
|
11182
11189
|
this.confirming = false;
|
|
11183
|
-
|
|
11190
|
+
resolve8(approved);
|
|
11184
11191
|
}
|
|
11185
11192
|
}
|
|
11186
11193
|
/** Resolve a pending batch confirm from client response */
|
|
11187
11194
|
resolveBatchConfirm(requestId, decision) {
|
|
11188
|
-
const
|
|
11189
|
-
if (
|
|
11195
|
+
const resolve8 = this.pendingBatchConfirms.get(requestId);
|
|
11196
|
+
if (resolve8) {
|
|
11190
11197
|
this.clearPendingTimer(requestId);
|
|
11191
11198
|
this.pendingBatchConfirms.delete(requestId);
|
|
11192
11199
|
this.confirming = false;
|
|
11193
11200
|
if (decision === "all" || decision === "none") {
|
|
11194
|
-
|
|
11201
|
+
resolve8(decision);
|
|
11195
11202
|
} else {
|
|
11196
|
-
|
|
11203
|
+
resolve8(new Set(decision));
|
|
11197
11204
|
}
|
|
11198
11205
|
}
|
|
11199
11206
|
}
|
|
11200
11207
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
11201
11208
|
cancelAll() {
|
|
11202
|
-
for (const
|
|
11209
|
+
for (const resolve8 of this.pendingConfirms.values()) resolve8(false);
|
|
11203
11210
|
this.pendingConfirms.clear();
|
|
11204
|
-
for (const
|
|
11211
|
+
for (const resolve8 of this.pendingBatchConfirms.values()) resolve8("none");
|
|
11205
11212
|
this.pendingBatchConfirms.clear();
|
|
11206
11213
|
this.confirming = false;
|
|
11207
11214
|
}
|
|
@@ -11246,7 +11253,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11246
11253
|
const newContent = String(call.arguments["content"] ?? "");
|
|
11247
11254
|
if (filePath && existsSync17(filePath)) {
|
|
11248
11255
|
try {
|
|
11249
|
-
const old =
|
|
11256
|
+
const old = readFileSync12(filePath, "utf-8");
|
|
11250
11257
|
return renderDiff(old, newContent, { filePath });
|
|
11251
11258
|
} catch {
|
|
11252
11259
|
}
|
|
@@ -11277,8 +11284,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11277
11284
|
diff: this.getDiffPreview(call)
|
|
11278
11285
|
};
|
|
11279
11286
|
this.send(msg);
|
|
11280
|
-
return new Promise((
|
|
11281
|
-
this.pendingConfirms.set(requestId,
|
|
11287
|
+
return new Promise((resolve8) => {
|
|
11288
|
+
this.pendingConfirms.set(requestId, resolve8);
|
|
11282
11289
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11283
11290
|
if (this.pendingConfirms.has(requestId)) {
|
|
11284
11291
|
this.resolveConfirm(requestId, false);
|
|
@@ -11302,8 +11309,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11302
11309
|
files
|
|
11303
11310
|
};
|
|
11304
11311
|
this.send(msg);
|
|
11305
|
-
return new Promise((
|
|
11306
|
-
this.pendingBatchConfirms.set(requestId,
|
|
11312
|
+
return new Promise((resolve8) => {
|
|
11313
|
+
this.pendingBatchConfirms.set(requestId, resolve8);
|
|
11307
11314
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11308
11315
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
11309
11316
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -11447,11 +11454,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11447
11454
|
};
|
|
11448
11455
|
|
|
11449
11456
|
// src/core/system-prompt-builder.ts
|
|
11450
|
-
import { existsSync as existsSync19, readFileSync as
|
|
11457
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
|
|
11451
11458
|
import { join as join12 } from "path";
|
|
11452
11459
|
|
|
11453
11460
|
// src/repl/dev-state.ts
|
|
11454
|
-
import { existsSync as existsSync18, readFileSync as
|
|
11461
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13, unlinkSync as unlinkSync4, mkdirSync as mkdirSync9 } from "fs";
|
|
11455
11462
|
import { join as join11 } from "path";
|
|
11456
11463
|
import { homedir as homedir6 } from "os";
|
|
11457
11464
|
function getDevStatePath() {
|
|
@@ -11460,7 +11467,7 @@ function getDevStatePath() {
|
|
|
11460
11467
|
function loadDevState() {
|
|
11461
11468
|
const path3 = getDevStatePath();
|
|
11462
11469
|
if (!existsSync18(path3)) return null;
|
|
11463
|
-
const content =
|
|
11470
|
+
const content = readFileSync13(path3, "utf-8").trim();
|
|
11464
11471
|
return content || null;
|
|
11465
11472
|
}
|
|
11466
11473
|
|
|
@@ -11520,7 +11527,7 @@ ${ctx.activeSkill.content}`);
|
|
|
11520
11527
|
function loadMemoryContent(configDir) {
|
|
11521
11528
|
const memoryPath = join12(configDir, MEMORY_FILE_NAME);
|
|
11522
11529
|
if (!existsSync19(memoryPath)) return null;
|
|
11523
|
-
let content =
|
|
11530
|
+
let content = readFileSync14(memoryPath, "utf-8").trim();
|
|
11524
11531
|
if (!content) return null;
|
|
11525
11532
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
11526
11533
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -11728,15 +11735,171 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
11728
11735
|
return committedSize < originalSize;
|
|
11729
11736
|
}
|
|
11730
11737
|
|
|
11738
|
+
// src/core/context-files.ts
|
|
11739
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
11740
|
+
import { join as join13, relative as relative3, resolve as resolve5 } from "path";
|
|
11741
|
+
function uniqueNonEmpty(values) {
|
|
11742
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11743
|
+
const out = [];
|
|
11744
|
+
for (const value of values) {
|
|
11745
|
+
const trimmed = value.trim();
|
|
11746
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
11747
|
+
seen.add(trimmed);
|
|
11748
|
+
out.push(trimmed);
|
|
11749
|
+
}
|
|
11750
|
+
return out;
|
|
11751
|
+
}
|
|
11752
|
+
function buildContextFileCandidates(fallbackFilenames = []) {
|
|
11753
|
+
return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
|
|
11754
|
+
}
|
|
11755
|
+
function displayPath(filePath, cwd) {
|
|
11756
|
+
const rel = relative3(cwd, filePath);
|
|
11757
|
+
if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
|
|
11758
|
+
return rel;
|
|
11759
|
+
}
|
|
11760
|
+
return filePath;
|
|
11761
|
+
}
|
|
11762
|
+
function isInsideOrEqual(parent, child) {
|
|
11763
|
+
const rel = relative3(resolve5(parent), resolve5(child));
|
|
11764
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
11765
|
+
}
|
|
11766
|
+
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
11767
|
+
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
11768
|
+
const shown = displayPath(filePath, cwd);
|
|
11769
|
+
try {
|
|
11770
|
+
const raw = readFileSync15(filePath);
|
|
11771
|
+
const byteCount = raw.byteLength;
|
|
11772
|
+
if (byteCount === 0) {
|
|
11773
|
+
return {
|
|
11774
|
+
layer: null,
|
|
11775
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
11776
|
+
};
|
|
11777
|
+
}
|
|
11778
|
+
const truncated = byteCount > maxBytes;
|
|
11779
|
+
const bytes = truncated ? raw.subarray(0, maxBytes) : raw;
|
|
11780
|
+
const content = bytes.toString("utf-8").trim();
|
|
11781
|
+
if (!content) {
|
|
11782
|
+
return {
|
|
11783
|
+
layer: null,
|
|
11784
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
11785
|
+
};
|
|
11786
|
+
}
|
|
11787
|
+
return {
|
|
11788
|
+
layer: {
|
|
11789
|
+
level: level === "single" ? "project" : level,
|
|
11790
|
+
filePath,
|
|
11791
|
+
displayPath: shown,
|
|
11792
|
+
fileName: name,
|
|
11793
|
+
content,
|
|
11794
|
+
charCount: content.length,
|
|
11795
|
+
byteCount,
|
|
11796
|
+
truncated
|
|
11797
|
+
},
|
|
11798
|
+
skipped: truncated ? { level, filePath, displayPath: shown, fileName: name, reason: "too-large", message: `truncated to ${maxBytes} bytes` } : null
|
|
11799
|
+
};
|
|
11800
|
+
} catch (err) {
|
|
11801
|
+
return {
|
|
11802
|
+
layer: null,
|
|
11803
|
+
skipped: {
|
|
11804
|
+
level,
|
|
11805
|
+
filePath,
|
|
11806
|
+
displayPath: shown,
|
|
11807
|
+
fileName: name,
|
|
11808
|
+
reason: "read-error",
|
|
11809
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11810
|
+
}
|
|
11811
|
+
};
|
|
11812
|
+
}
|
|
11813
|
+
}
|
|
11814
|
+
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
11815
|
+
const skipped = [];
|
|
11816
|
+
for (const candidate of candidates) {
|
|
11817
|
+
const filePath = join13(dir, candidate);
|
|
11818
|
+
if (!existsSync20(filePath)) continue;
|
|
11819
|
+
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
11820
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
11821
|
+
if (result.layer) return { layer: result.layer, skipped };
|
|
11822
|
+
}
|
|
11823
|
+
return { layer: null, skipped };
|
|
11824
|
+
}
|
|
11825
|
+
function loadContextFiles(options) {
|
|
11826
|
+
const cwd = resolve5(options.cwd);
|
|
11827
|
+
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
11828
|
+
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
11829
|
+
const skipped = [];
|
|
11830
|
+
if (options.setting === false) {
|
|
11831
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
11832
|
+
}
|
|
11833
|
+
if (options.setting && options.setting !== "auto") {
|
|
11834
|
+
const filePath = resolve5(cwd, String(options.setting));
|
|
11835
|
+
if (!isInsideOrEqual(cwd, filePath)) {
|
|
11836
|
+
skipped.push({
|
|
11837
|
+
level: "single",
|
|
11838
|
+
filePath,
|
|
11839
|
+
displayPath: filePath,
|
|
11840
|
+
fileName: String(options.setting),
|
|
11841
|
+
reason: "outside-cwd"
|
|
11842
|
+
});
|
|
11843
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
11844
|
+
}
|
|
11845
|
+
if (!existsSync20(filePath)) {
|
|
11846
|
+
skipped.push({
|
|
11847
|
+
level: "single",
|
|
11848
|
+
filePath,
|
|
11849
|
+
displayPath: displayPath(filePath, cwd),
|
|
11850
|
+
fileName: String(options.setting),
|
|
11851
|
+
reason: "missing"
|
|
11852
|
+
});
|
|
11853
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
11854
|
+
}
|
|
11855
|
+
const result = readContextFile("single", filePath, cwd, maxBytes);
|
|
11856
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
11857
|
+
const layers2 = result.layer ? [result.layer] : [];
|
|
11858
|
+
const mergedContent2 = layers2.map((l) => l.content).join("\n\n---\n\n");
|
|
11859
|
+
return {
|
|
11860
|
+
layers: layers2,
|
|
11861
|
+
mergedContent: mergedContent2,
|
|
11862
|
+
skipped,
|
|
11863
|
+
totalChars: layers2.reduce((sum, l) => sum + l.charCount, 0),
|
|
11864
|
+
totalBytes: layers2.reduce((sum, l) => sum + l.byteCount, 0),
|
|
11865
|
+
maxBytes,
|
|
11866
|
+
candidates
|
|
11867
|
+
};
|
|
11868
|
+
}
|
|
11869
|
+
const layers = [];
|
|
11870
|
+
for (const [level, dir] of [
|
|
11871
|
+
["global", options.configDir],
|
|
11872
|
+
["project", options.projectRoot]
|
|
11873
|
+
]) {
|
|
11874
|
+
const result = findFirstContextFile(level, dir, cwd, candidates, maxBytes);
|
|
11875
|
+
skipped.push(...result.skipped);
|
|
11876
|
+
if (result.layer) layers.push(result.layer);
|
|
11877
|
+
}
|
|
11878
|
+
if (resolve5(options.cwd) !== resolve5(options.projectRoot)) {
|
|
11879
|
+
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
11880
|
+
skipped.push(...result.skipped);
|
|
11881
|
+
if (result.layer) layers.push(result.layer);
|
|
11882
|
+
}
|
|
11883
|
+
const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
|
|
11884
|
+
return {
|
|
11885
|
+
layers,
|
|
11886
|
+
mergedContent,
|
|
11887
|
+
skipped,
|
|
11888
|
+
totalChars: layers.reduce((sum, l) => sum + l.charCount, 0),
|
|
11889
|
+
totalBytes: layers.reduce((sum, l) => sum + l.byteCount, 0),
|
|
11890
|
+
maxBytes,
|
|
11891
|
+
candidates
|
|
11892
|
+
};
|
|
11893
|
+
}
|
|
11894
|
+
|
|
11731
11895
|
// src/web/session-handler.ts
|
|
11732
|
-
import { existsSync as
|
|
11733
|
-
import { join as
|
|
11734
|
-
import { execSync as execSync3 } from "child_process";
|
|
11896
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
|
|
11897
|
+
import { join as join17, resolve as resolve6, dirname as dirname5 } from "path";
|
|
11735
11898
|
|
|
11736
11899
|
// src/tools/git-context.ts
|
|
11737
11900
|
import { execSync as execSync2 } from "child_process";
|
|
11738
|
-
import { existsSync as
|
|
11739
|
-
import { join as
|
|
11901
|
+
import { existsSync as existsSync21 } from "fs";
|
|
11902
|
+
import { join as join14 } from "path";
|
|
11740
11903
|
function runGit2(cmd, cwd) {
|
|
11741
11904
|
try {
|
|
11742
11905
|
return execSync2(`git ${cmd}`, {
|
|
@@ -11753,7 +11916,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
11753
11916
|
return runGit2("rev-parse --show-toplevel", cwd);
|
|
11754
11917
|
}
|
|
11755
11918
|
function getGitContext(cwd = process.cwd()) {
|
|
11756
|
-
if (!
|
|
11919
|
+
if (!existsSync21(join14(cwd, ".git"))) {
|
|
11757
11920
|
const result = runGit2("rev-parse --git-dir", cwd);
|
|
11758
11921
|
if (!result) return null;
|
|
11759
11922
|
}
|
|
@@ -11823,6 +11986,264 @@ function formatGitContextForPrompt(ctx) {
|
|
|
11823
11986
|
return lines.join("\n");
|
|
11824
11987
|
}
|
|
11825
11988
|
|
|
11989
|
+
// src/cli/review-prompts.ts
|
|
11990
|
+
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
11991
|
+
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.";
|
|
11992
|
+
return `# Code Review Request
|
|
11993
|
+
|
|
11994
|
+
${level}
|
|
11995
|
+
|
|
11996
|
+
## Git Status
|
|
11997
|
+
${gitContextStr}
|
|
11998
|
+
|
|
11999
|
+
## Code Changes (diff)
|
|
12000
|
+
\`\`\`diff
|
|
12001
|
+
${diff}
|
|
12002
|
+
\`\`\`
|
|
12003
|
+
|
|
12004
|
+
## Output Format
|
|
12005
|
+
Please structure your review as follows:
|
|
12006
|
+
1. **Overall Assessment**: One-sentence summary of the change quality
|
|
12007
|
+
2. **Issues** (if any): Each issue with [Severity] file:line \u2014 description + suggested fix
|
|
12008
|
+
3. **Improvement Suggestions** (if any): Non-critical but recommended optimizations
|
|
12009
|
+
4. **Highlights** (if any): Good practices worth acknowledging
|
|
12010
|
+
|
|
12011
|
+
Severity levels: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
|
|
12012
|
+
}
|
|
12013
|
+
function buildSecurityReviewPrompt(diff, gitContextStr) {
|
|
12014
|
+
return `# Security Vulnerability Review
|
|
12015
|
+
|
|
12016
|
+
Analyze the following code changes **exclusively for security vulnerabilities**.
|
|
12017
|
+
|
|
12018
|
+
## Categories to check:
|
|
12019
|
+
1. **Injection** \u2014 SQL, command, path traversal, XSS, template injection
|
|
12020
|
+
2. **Authentication & Authorization** \u2014 hardcoded credentials, missing auth checks, privilege escalation
|
|
12021
|
+
3. **Secrets & Sensitive Data** \u2014 API keys, tokens, passwords in code, logging sensitive data
|
|
12022
|
+
4. **Input Validation** \u2014 missing validation, unsafe deserialization, buffer issues
|
|
12023
|
+
5. **Cryptography** \u2014 weak algorithms, improper random, hardcoded IVs/salts
|
|
12024
|
+
6. **Dependencies** \u2014 known vulnerable packages, unsafe dynamic imports
|
|
12025
|
+
7. **File System** \u2014 path traversal, unsafe file permissions, symlink attacks
|
|
12026
|
+
8. **Network** \u2014 SSRF, insecure protocols, missing TLS validation
|
|
12027
|
+
|
|
12028
|
+
## Git Status
|
|
12029
|
+
${gitContextStr}
|
|
12030
|
+
|
|
12031
|
+
## Code Changes (diff)
|
|
12032
|
+
\`\`\`diff
|
|
12033
|
+
${diff}
|
|
12034
|
+
\`\`\`
|
|
12035
|
+
|
|
12036
|
+
## Output Format
|
|
12037
|
+
For each finding:
|
|
12038
|
+
- **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW / \u2139\uFE0F INFO
|
|
12039
|
+
- **Category**: (from list above)
|
|
12040
|
+
- **File & location**: file:line
|
|
12041
|
+
- **Description**: what the vulnerability is and how it could be exploited
|
|
12042
|
+
- **Recommended fix**: specific code change to resolve
|
|
12043
|
+
|
|
12044
|
+
If no security issues found, state "\u2705 No security vulnerabilities detected" with a brief explanation of what was checked.`;
|
|
12045
|
+
}
|
|
12046
|
+
|
|
12047
|
+
// src/repl/commands/project-init.ts
|
|
12048
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
12049
|
+
import { join as join15 } from "path";
|
|
12050
|
+
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
12051
|
+
"node_modules",
|
|
12052
|
+
".git",
|
|
12053
|
+
"dist",
|
|
12054
|
+
"build",
|
|
12055
|
+
"out",
|
|
12056
|
+
"target",
|
|
12057
|
+
".next",
|
|
12058
|
+
".nuxt",
|
|
12059
|
+
"__pycache__",
|
|
12060
|
+
".venv",
|
|
12061
|
+
"venv",
|
|
12062
|
+
".tox",
|
|
12063
|
+
".mypy_cache",
|
|
12064
|
+
".pytest_cache",
|
|
12065
|
+
".gradle",
|
|
12066
|
+
".idea",
|
|
12067
|
+
".vscode",
|
|
12068
|
+
".vs",
|
|
12069
|
+
"coverage",
|
|
12070
|
+
".cache",
|
|
12071
|
+
".parcel-cache",
|
|
12072
|
+
"dist-cjs",
|
|
12073
|
+
"release",
|
|
12074
|
+
".output",
|
|
12075
|
+
".turbo",
|
|
12076
|
+
"vendor"
|
|
12077
|
+
]);
|
|
12078
|
+
function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
12079
|
+
const lines = [];
|
|
12080
|
+
let count = 0;
|
|
12081
|
+
const walk = (d, prefix, depth) => {
|
|
12082
|
+
if (depth > maxDepth || count >= maxEntries) return;
|
|
12083
|
+
let entries;
|
|
12084
|
+
try {
|
|
12085
|
+
entries = readdirSync9(d);
|
|
12086
|
+
} catch {
|
|
12087
|
+
return;
|
|
12088
|
+
}
|
|
12089
|
+
const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
|
|
12090
|
+
const sorted = filtered.sort((a, b) => {
|
|
12091
|
+
let aIsDir = false, bIsDir = false;
|
|
12092
|
+
try {
|
|
12093
|
+
aIsDir = statSync8(join15(d, a)).isDirectory();
|
|
12094
|
+
} catch {
|
|
12095
|
+
}
|
|
12096
|
+
try {
|
|
12097
|
+
bIsDir = statSync8(join15(d, b)).isDirectory();
|
|
12098
|
+
} catch {
|
|
12099
|
+
}
|
|
12100
|
+
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
12101
|
+
return a.localeCompare(b);
|
|
12102
|
+
});
|
|
12103
|
+
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
12104
|
+
const name = sorted[i];
|
|
12105
|
+
const fullPath = join15(d, name);
|
|
12106
|
+
const isLast = i === sorted.length - 1;
|
|
12107
|
+
const connector = isLast ? "+-- " : "|-- ";
|
|
12108
|
+
let isDir;
|
|
12109
|
+
try {
|
|
12110
|
+
isDir = statSync8(fullPath).isDirectory();
|
|
12111
|
+
} catch {
|
|
12112
|
+
continue;
|
|
12113
|
+
}
|
|
12114
|
+
lines.push(prefix + connector + name + (isDir ? "/" : ""));
|
|
12115
|
+
count++;
|
|
12116
|
+
if (isDir) {
|
|
12117
|
+
walk(fullPath, prefix + (isLast ? " " : "| "), depth + 1);
|
|
12118
|
+
}
|
|
12119
|
+
}
|
|
12120
|
+
};
|
|
12121
|
+
walk(dir, "", 0);
|
|
12122
|
+
if (count >= maxEntries) lines.push("... (truncated)");
|
|
12123
|
+
return lines.join("\n");
|
|
12124
|
+
}
|
|
12125
|
+
function scanProject(cwd) {
|
|
12126
|
+
const info = {
|
|
12127
|
+
type: "unknown",
|
|
12128
|
+
language: "unknown",
|
|
12129
|
+
configFiles: [],
|
|
12130
|
+
directoryStructure: ""
|
|
12131
|
+
};
|
|
12132
|
+
const check = (file) => existsSync22(join15(cwd, file));
|
|
12133
|
+
const configCandidates = [
|
|
12134
|
+
"package.json",
|
|
12135
|
+
"tsconfig.json",
|
|
12136
|
+
"Cargo.toml",
|
|
12137
|
+
"pyproject.toml",
|
|
12138
|
+
"setup.py",
|
|
12139
|
+
"requirements.txt",
|
|
12140
|
+
"go.mod",
|
|
12141
|
+
"pom.xml",
|
|
12142
|
+
"build.gradle",
|
|
12143
|
+
"build.gradle.kts",
|
|
12144
|
+
"CMakeLists.txt",
|
|
12145
|
+
"Makefile",
|
|
12146
|
+
".csproj",
|
|
12147
|
+
".sln",
|
|
12148
|
+
"composer.json",
|
|
12149
|
+
"Gemfile",
|
|
12150
|
+
"mix.exs",
|
|
12151
|
+
"deno.json",
|
|
12152
|
+
"bun.lockb"
|
|
12153
|
+
];
|
|
12154
|
+
info.configFiles = configCandidates.filter(check);
|
|
12155
|
+
if (check("package.json")) {
|
|
12156
|
+
info.type = "node";
|
|
12157
|
+
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
12158
|
+
try {
|
|
12159
|
+
const pkg = JSON.parse(readFileSync16(join15(cwd, "package.json"), "utf-8"));
|
|
12160
|
+
const scripts = pkg.scripts ?? {};
|
|
12161
|
+
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
12162
|
+
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
12163
|
+
info.devCommand = scripts.dev ? "npm run dev" : void 0;
|
|
12164
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12165
|
+
if (allDeps["react"]) info.framework = "React";
|
|
12166
|
+
else if (allDeps["vue"]) info.framework = "Vue";
|
|
12167
|
+
else if (allDeps["@angular/core"]) info.framework = "Angular";
|
|
12168
|
+
else if (allDeps["next"]) info.framework = "Next.js";
|
|
12169
|
+
else if (allDeps["nuxt"]) info.framework = "Nuxt";
|
|
12170
|
+
else if (allDeps["express"]) info.framework = "Express";
|
|
12171
|
+
else if (allDeps["fastify"]) info.framework = "Fastify";
|
|
12172
|
+
else if (allDeps["svelte"]) info.framework = "Svelte";
|
|
12173
|
+
} catch {
|
|
12174
|
+
}
|
|
12175
|
+
} else if (check("Cargo.toml")) {
|
|
12176
|
+
info.type = "rust";
|
|
12177
|
+
info.language = "Rust";
|
|
12178
|
+
info.buildCommand = "cargo build";
|
|
12179
|
+
info.testCommand = "cargo test";
|
|
12180
|
+
} else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
|
|
12181
|
+
info.type = "python";
|
|
12182
|
+
info.language = "Python";
|
|
12183
|
+
info.testCommand = check("pytest.ini") || check("pyproject.toml") ? "pytest" : "python -m unittest";
|
|
12184
|
+
} else if (check("go.mod")) {
|
|
12185
|
+
info.type = "go";
|
|
12186
|
+
info.language = "Go";
|
|
12187
|
+
info.buildCommand = "go build ./...";
|
|
12188
|
+
info.testCommand = "go test ./...";
|
|
12189
|
+
} else if (check("pom.xml")) {
|
|
12190
|
+
info.type = "java";
|
|
12191
|
+
info.language = "Java";
|
|
12192
|
+
info.buildCommand = "mvn package";
|
|
12193
|
+
info.testCommand = "mvn test";
|
|
12194
|
+
} else if (check("build.gradle") || check("build.gradle.kts")) {
|
|
12195
|
+
info.type = "java";
|
|
12196
|
+
info.language = "Java/Kotlin";
|
|
12197
|
+
info.buildCommand = "./gradlew build";
|
|
12198
|
+
info.testCommand = "./gradlew test";
|
|
12199
|
+
}
|
|
12200
|
+
info.directoryStructure = scanDirTree(cwd);
|
|
12201
|
+
return info;
|
|
12202
|
+
}
|
|
12203
|
+
function buildInitPrompt(info, cwd) {
|
|
12204
|
+
const parts = [
|
|
12205
|
+
"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.",
|
|
12206
|
+
"\n## Project Info\n",
|
|
12207
|
+
`- Working directory: ${cwd}`,
|
|
12208
|
+
`- Type: ${info.type}`,
|
|
12209
|
+
`- Language: ${info.language}`
|
|
12210
|
+
];
|
|
12211
|
+
if (info.framework) parts.push(`- Framework: ${info.framework}`);
|
|
12212
|
+
if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
|
|
12213
|
+
if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
|
|
12214
|
+
if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
|
|
12215
|
+
parts.push(`
|
|
12216
|
+
## Detected Config Files
|
|
12217
|
+
${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
|
|
12218
|
+
parts.push(`
|
|
12219
|
+
## Directory Structure
|
|
12220
|
+
\`\`\`
|
|
12221
|
+
${info.directoryStructure}
|
|
12222
|
+
\`\`\``);
|
|
12223
|
+
parts.push(`
|
|
12224
|
+
## Requirements
|
|
12225
|
+
Please generate a structured Markdown file containing:
|
|
12226
|
+
1. Project overview (one-sentence summary)
|
|
12227
|
+
2. Tech stack
|
|
12228
|
+
3. Project structure description (based on the directory structure above)
|
|
12229
|
+
4. Common commands (build, test, dev, etc.)
|
|
12230
|
+
5. Code style and conventions (inferred from config files)
|
|
12231
|
+
|
|
12232
|
+
Output the Markdown content directly, do not wrap the entire file in a code block. Keep it concise, within 200 lines.`);
|
|
12233
|
+
return parts.join("\n");
|
|
12234
|
+
}
|
|
12235
|
+
|
|
12236
|
+
// src/core/git-diff.ts
|
|
12237
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
12238
|
+
function readGitDiff(options = {}) {
|
|
12239
|
+
const args = ["diff"];
|
|
12240
|
+
if (options.staged) args.push("--staged");
|
|
12241
|
+
return execFileSync3("git", args, {
|
|
12242
|
+
encoding: "utf-8",
|
|
12243
|
+
timeout: options.timeoutMs ?? 1e4
|
|
12244
|
+
}).trim();
|
|
12245
|
+
}
|
|
12246
|
+
|
|
11826
12247
|
// src/hub/convergence.ts
|
|
11827
12248
|
function convergenceThreshold(total) {
|
|
11828
12249
|
if (total <= 0) return Infinity;
|
|
@@ -12395,7 +12816,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
12395
12816
|
}
|
|
12396
12817
|
|
|
12397
12818
|
// src/hub/persist.ts
|
|
12398
|
-
import { join as
|
|
12819
|
+
import { join as join16 } from "path";
|
|
12399
12820
|
function discussionToMessages(state2) {
|
|
12400
12821
|
const out = [];
|
|
12401
12822
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -12433,7 +12854,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
12433
12854
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
12434
12855
|
session.titleAiGenerated = true;
|
|
12435
12856
|
await sm.save();
|
|
12436
|
-
return { id: session.id, path:
|
|
12857
|
+
return { id: session.id, path: join16(config.getHistoryDir(), `${session.id}.json`) };
|
|
12437
12858
|
}
|
|
12438
12859
|
|
|
12439
12860
|
// src/web/session-handler.ts
|
|
@@ -12451,7 +12872,7 @@ function lastAssistantText(messages) {
|
|
|
12451
12872
|
}
|
|
12452
12873
|
return void 0;
|
|
12453
12874
|
}
|
|
12454
|
-
var SessionHandler = class
|
|
12875
|
+
var SessionHandler = class {
|
|
12455
12876
|
ws;
|
|
12456
12877
|
config;
|
|
12457
12878
|
providers;
|
|
@@ -12488,6 +12909,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
12488
12909
|
pendingAutoPause = /* @__PURE__ */ new Map();
|
|
12489
12910
|
/** Active system prompt from context files */
|
|
12490
12911
|
activeSystemPrompt;
|
|
12912
|
+
contextLoadResult = null;
|
|
12491
12913
|
/** Directories added via /add-dir */
|
|
12492
12914
|
addedDirs = /* @__PURE__ */ new Set();
|
|
12493
12915
|
/** Track MCP tool names used across this session (for MCP budget prioritization) */
|
|
@@ -12594,10 +13016,10 @@ var SessionHandler = class _SessionHandler {
|
|
|
12594
13016
|
return;
|
|
12595
13017
|
}
|
|
12596
13018
|
case "ask_user_response": {
|
|
12597
|
-
const
|
|
12598
|
-
if (
|
|
13019
|
+
const resolve8 = this.pendingAskUser.get(msg.requestId);
|
|
13020
|
+
if (resolve8) {
|
|
12599
13021
|
this.pendingAskUser.delete(msg.requestId);
|
|
12600
|
-
|
|
13022
|
+
resolve8(msg.answer);
|
|
12601
13023
|
}
|
|
12602
13024
|
return;
|
|
12603
13025
|
}
|
|
@@ -12608,10 +13030,10 @@ var SessionHandler = class _SessionHandler {
|
|
|
12608
13030
|
case "memory_rebuild":
|
|
12609
13031
|
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
12610
13032
|
case "auto_pause_response": {
|
|
12611
|
-
const
|
|
12612
|
-
if (
|
|
13033
|
+
const resolve8 = this.pendingAutoPause.get(msg.requestId);
|
|
13034
|
+
if (resolve8) {
|
|
12613
13035
|
this.pendingAutoPause.delete(msg.requestId);
|
|
12614
|
-
|
|
13036
|
+
resolve8({ action: msg.action, message: msg.message });
|
|
12615
13037
|
}
|
|
12616
13038
|
return;
|
|
12617
13039
|
}
|
|
@@ -12626,10 +13048,10 @@ var SessionHandler = class _SessionHandler {
|
|
|
12626
13048
|
this.hubOrchestrator?.abort();
|
|
12627
13049
|
return;
|
|
12628
13050
|
case "hub_steer": {
|
|
12629
|
-
const
|
|
12630
|
-
if (
|
|
13051
|
+
const resolve8 = this.pendingHubReview.get(msg.requestId);
|
|
13052
|
+
if (resolve8) {
|
|
12631
13053
|
this.pendingHubReview.delete(msg.requestId);
|
|
12632
|
-
|
|
13054
|
+
resolve8({ action: msg.action, message: msg.message });
|
|
12633
13055
|
}
|
|
12634
13056
|
return;
|
|
12635
13057
|
}
|
|
@@ -12641,9 +13063,9 @@ var SessionHandler = class _SessionHandler {
|
|
|
12641
13063
|
onDisconnect() {
|
|
12642
13064
|
this.toolExecutor.cancelAll();
|
|
12643
13065
|
if (this.abortController) this.abortController.abort();
|
|
12644
|
-
for (const
|
|
13066
|
+
for (const resolve8 of this.pendingAskUser.values()) resolve8(null);
|
|
12645
13067
|
this.pendingAskUser.clear();
|
|
12646
|
-
for (const
|
|
13068
|
+
for (const resolve8 of this.pendingAutoPause.values()) resolve8({ action: "stop" });
|
|
12647
13069
|
this.pendingAutoPause.clear();
|
|
12648
13070
|
this.saveIfNeeded();
|
|
12649
13071
|
}
|
|
@@ -12777,9 +13199,9 @@ var SessionHandler = class _SessionHandler {
|
|
|
12777
13199
|
this.hubOrchestrator = orchestrator;
|
|
12778
13200
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
12779
13201
|
if (config.humanSteer) {
|
|
12780
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
13202
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve8) => {
|
|
12781
13203
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
12782
|
-
this.pendingHubReview.set(requestId,
|
|
13204
|
+
this.pendingHubReview.set(requestId, resolve8);
|
|
12783
13205
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
12784
13206
|
});
|
|
12785
13207
|
}
|
|
@@ -13116,8 +13538,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
13116
13538
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
13117
13539
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
13118
13540
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13119
|
-
const pauseResp = await new Promise((
|
|
13120
|
-
this.pendingAutoPause.set(requestId,
|
|
13541
|
+
const pauseResp = await new Promise((resolve8) => {
|
|
13542
|
+
this.pendingAutoPause.set(requestId, resolve8);
|
|
13121
13543
|
this.send({
|
|
13122
13544
|
type: "auto_pause_request",
|
|
13123
13545
|
requestId,
|
|
@@ -13296,8 +13718,8 @@ ${summaryContent}`,
|
|
|
13296
13718
|
}
|
|
13297
13719
|
if (chunk.done) break;
|
|
13298
13720
|
}
|
|
13299
|
-
await new Promise((
|
|
13300
|
-
fileStream.end((err) => err ? reject(err) :
|
|
13721
|
+
await new Promise((resolve8, reject) => {
|
|
13722
|
+
fileStream.end((err) => err ? reject(err) : resolve8());
|
|
13301
13723
|
});
|
|
13302
13724
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
13303
13725
|
if (verdict.kind === "reject") {
|
|
@@ -13323,7 +13745,7 @@ ${summaryContent}`,
|
|
|
13323
13745
|
} catch (err) {
|
|
13324
13746
|
if (fileStream) {
|
|
13325
13747
|
try {
|
|
13326
|
-
await new Promise((
|
|
13748
|
+
await new Promise((resolve8) => fileStream.end(() => resolve8()));
|
|
13327
13749
|
} catch {
|
|
13328
13750
|
}
|
|
13329
13751
|
}
|
|
@@ -13612,7 +14034,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
13612
14034
|
" /plan [enter|exit] \u2014 Toggle read-only planning mode",
|
|
13613
14035
|
" /session new|list|load|delete <id> \u2014 Session management",
|
|
13614
14036
|
" /system [prompt|clear] \u2014 Set/view/reset system prompt",
|
|
13615
|
-
" /context [reload]
|
|
14037
|
+
" /context [status|reload] \u2014 Show/reload context layers",
|
|
13616
14038
|
" /status \u2014 Show session info & token usage",
|
|
13617
14039
|
" /cost \u2014 Show cumulative token usage",
|
|
13618
14040
|
" /config [show|get|set] \u2014 View/modify configuration",
|
|
@@ -13853,9 +14275,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
13853
14275
|
let modifiedFiles = 0;
|
|
13854
14276
|
const diffLines2 = [];
|
|
13855
14277
|
for (const [filePath, { earliest }] of fileMap) {
|
|
13856
|
-
const currentContent =
|
|
14278
|
+
const currentContent = existsSync23(filePath) ? (() => {
|
|
13857
14279
|
try {
|
|
13858
|
-
return
|
|
14280
|
+
return readFileSync17(filePath, "utf-8");
|
|
13859
14281
|
} catch {
|
|
13860
14282
|
return null;
|
|
13861
14283
|
}
|
|
@@ -13956,7 +14378,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
13956
14378
|
break;
|
|
13957
14379
|
}
|
|
13958
14380
|
const sub = args[0]?.toLowerCase();
|
|
13959
|
-
const
|
|
14381
|
+
const resolve8 = (ref) => {
|
|
13960
14382
|
const r = session.resolveBranchRef(ref);
|
|
13961
14383
|
if (r.ok) return r.id;
|
|
13962
14384
|
if (r.reason === "ambiguous") {
|
|
@@ -14010,7 +14432,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14010
14432
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
14011
14433
|
break;
|
|
14012
14434
|
}
|
|
14013
|
-
const id =
|
|
14435
|
+
const id = resolve8(ref);
|
|
14014
14436
|
if (!id) break;
|
|
14015
14437
|
const ok = session.switchBranch(id);
|
|
14016
14438
|
if (ok) {
|
|
@@ -14030,7 +14452,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14030
14452
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
14031
14453
|
break;
|
|
14032
14454
|
}
|
|
14033
|
-
const id =
|
|
14455
|
+
const id = resolve8(ref);
|
|
14034
14456
|
if (!id) break;
|
|
14035
14457
|
const ok = session.deleteBranch(id);
|
|
14036
14458
|
if (ok) {
|
|
@@ -14049,7 +14471,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14049
14471
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
14050
14472
|
break;
|
|
14051
14473
|
}
|
|
14052
|
-
const id =
|
|
14474
|
+
const id = resolve8(ref);
|
|
14053
14475
|
if (!id) break;
|
|
14054
14476
|
const ok = session.renameBranch(id, title);
|
|
14055
14477
|
if (ok) {
|
|
@@ -14067,7 +14489,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14067
14489
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
14068
14490
|
break;
|
|
14069
14491
|
}
|
|
14070
|
-
const id =
|
|
14492
|
+
const id = resolve8(ref);
|
|
14071
14493
|
if (!id) break;
|
|
14072
14494
|
const d = session.diffBranches(id);
|
|
14073
14495
|
if (!d) {
|
|
@@ -14109,7 +14531,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14109
14531
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
14110
14532
|
break;
|
|
14111
14533
|
}
|
|
14112
|
-
const id =
|
|
14534
|
+
const id = resolve8(ref);
|
|
14113
14535
|
if (!id) break;
|
|
14114
14536
|
const idx = parseInt(idxStr, 10);
|
|
14115
14537
|
if (Number.isNaN(idx)) {
|
|
@@ -14136,7 +14558,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14136
14558
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
14137
14559
|
const root = process.cwd();
|
|
14138
14560
|
const { loadIndex: loadIndex2, clearIndex } = await import("./store-VO37H6LS.js");
|
|
14139
|
-
const { indexProject: indexProject2 } = await import("./indexer-
|
|
14561
|
+
const { indexProject: indexProject2 } = await import("./indexer-4WWS3VIL.js");
|
|
14140
14562
|
const { loadVectorStore, clearVectorStore } = await import("./vector-store-JBAE6PS4.js");
|
|
14141
14563
|
if (sub === "status") {
|
|
14142
14564
|
const idx = loadIndex2(root);
|
|
@@ -14255,8 +14677,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14255
14677
|
const detailed = args.includes("--detailed");
|
|
14256
14678
|
let diff;
|
|
14257
14679
|
try {
|
|
14258
|
-
|
|
14259
|
-
diff = execSync3(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
14680
|
+
diff = readGitDiff({ staged });
|
|
14260
14681
|
} catch {
|
|
14261
14682
|
this.send({ type: "error", message: "Failed to run git diff." });
|
|
14262
14683
|
break;
|
|
@@ -14273,7 +14694,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14273
14694
|
diff = head + "\n\n... [diff truncated, " + diff.length + " chars total] ...\n\n" + tail;
|
|
14274
14695
|
truncated = true;
|
|
14275
14696
|
}
|
|
14276
|
-
const reviewPrompt =
|
|
14697
|
+
const reviewPrompt = buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
|
|
14277
14698
|
this.send({ type: "info", message: "\u{1F50D} Analyzing changes..." });
|
|
14278
14699
|
try {
|
|
14279
14700
|
const review = await this.chatOnce(reviewPrompt, { temperature: 0.3, maxTokens: 8192 });
|
|
@@ -14294,8 +14715,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14294
14715
|
const secStaged = args.includes("--staged");
|
|
14295
14716
|
let secDiff;
|
|
14296
14717
|
try {
|
|
14297
|
-
|
|
14298
|
-
secDiff = execSync3(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
14718
|
+
secDiff = readGitDiff({ staged: secStaged });
|
|
14299
14719
|
} catch {
|
|
14300
14720
|
this.send({ type: "error", message: "Failed to run git diff." });
|
|
14301
14721
|
break;
|
|
@@ -14312,7 +14732,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14312
14732
|
secDiff = head + "\n\n... [diff truncated, " + secDiff.length + " chars total] ...\n\n" + tail;
|
|
14313
14733
|
secTruncated = true;
|
|
14314
14734
|
}
|
|
14315
|
-
const secPrompt =
|
|
14735
|
+
const secPrompt = buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
|
|
14316
14736
|
this.send({ type: "info", message: "\u{1F512} Scanning for security vulnerabilities..." });
|
|
14317
14737
|
try {
|
|
14318
14738
|
const secReview = await this.chatOnce(secPrompt, { temperature: 0.2, maxTokens: 8192 });
|
|
@@ -14373,7 +14793,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14373
14793
|
case "test": {
|
|
14374
14794
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
14375
14795
|
try {
|
|
14376
|
-
const { executeTests } = await import("./run-tests-
|
|
14796
|
+
const { executeTests } = await import("./run-tests-NT2UIUVB.js");
|
|
14377
14797
|
const argStr = args.join(" ").trim();
|
|
14378
14798
|
let testArgs = {};
|
|
14379
14799
|
if (argStr) {
|
|
@@ -14390,19 +14810,19 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14390
14810
|
// ── /init ───────────────────────────────────────────────────────
|
|
14391
14811
|
case "init": {
|
|
14392
14812
|
const cwd = process.cwd();
|
|
14393
|
-
const targetPath =
|
|
14813
|
+
const targetPath = join17(cwd, "AICLI.md");
|
|
14394
14814
|
const force = args.includes("--force");
|
|
14395
|
-
if (
|
|
14815
|
+
if (existsSync23(targetPath) && !force) {
|
|
14396
14816
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
14397
14817
|
Use /init --force to overwrite.` });
|
|
14398
14818
|
break;
|
|
14399
14819
|
}
|
|
14400
14820
|
this.send({ type: "info", message: "\u{1F4DD} Scanning project structure..." });
|
|
14401
14821
|
try {
|
|
14402
|
-
const projectInfo =
|
|
14403
|
-
const prompt =
|
|
14822
|
+
const projectInfo = scanProject(cwd);
|
|
14823
|
+
const prompt = buildInitPrompt(projectInfo, cwd);
|
|
14404
14824
|
const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
|
|
14405
|
-
|
|
14825
|
+
writeFileSync2(targetPath, content, "utf-8");
|
|
14406
14826
|
this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
|
|
14407
14827
|
Use /context reload to load it.` });
|
|
14408
14828
|
} catch (err) {
|
|
@@ -14425,19 +14845,19 @@ Use /context reload to load it.` });
|
|
|
14425
14845
|
lines.push("**Config Files:**");
|
|
14426
14846
|
lines.push(` Dir: ${configDir}`);
|
|
14427
14847
|
const checkFile = (label, filePath) => {
|
|
14428
|
-
const exists =
|
|
14848
|
+
const exists = existsSync23(filePath);
|
|
14429
14849
|
let extra = "";
|
|
14430
14850
|
if (exists) {
|
|
14431
14851
|
try {
|
|
14432
|
-
extra = ` (${
|
|
14852
|
+
extra = ` (${statSync9(filePath).size} bytes)`;
|
|
14433
14853
|
} catch {
|
|
14434
14854
|
}
|
|
14435
14855
|
}
|
|
14436
14856
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
14437
14857
|
};
|
|
14438
|
-
checkFile("config.json",
|
|
14439
|
-
checkFile("memory.md",
|
|
14440
|
-
checkFile("dev-state.md",
|
|
14858
|
+
checkFile("config.json", join17(configDir, "config.json"));
|
|
14859
|
+
checkFile("memory.md", join17(configDir, MEMORY_FILE_NAME));
|
|
14860
|
+
checkFile("dev-state.md", join17(configDir, "dev-state.md"));
|
|
14441
14861
|
lines.push("");
|
|
14442
14862
|
if (this.mcpManager) {
|
|
14443
14863
|
lines.push("**MCP Servers:**");
|
|
@@ -14537,29 +14957,24 @@ ${this.config.toFormattedJSON()}
|
|
|
14537
14957
|
this.send({ type: "info", message: this.activeSystemPrompt ? `\u2713 Context reloaded (${this.activeSystemPrompt.length} chars).` : "\u2713 No context files found." });
|
|
14538
14958
|
break;
|
|
14539
14959
|
}
|
|
14540
|
-
const
|
|
14541
|
-
const cwd = process.cwd();
|
|
14542
|
-
const gitRoot = getGitRoot(cwd);
|
|
14543
|
-
const projectRoot = gitRoot ?? cwd;
|
|
14960
|
+
const result = this.contextLoadResult;
|
|
14544
14961
|
const layers = ["\u{1F4DA} **Context Layers:**", ""];
|
|
14545
|
-
|
|
14546
|
-
|
|
14547
|
-
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14962
|
+
if (result && result.layers.length > 0) {
|
|
14963
|
+
const labels = { global: "Global ", project: "Project", local: "Local " };
|
|
14964
|
+
for (const layer of result.layers) {
|
|
14965
|
+
layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
|
|
14966
|
+
}
|
|
14967
|
+
layers.push(` Total: ${result.totalChars} chars (${result.layers.length} layer${result.layers.length > 1 ? "s" : ""})`);
|
|
14968
|
+
} else {
|
|
14969
|
+
layers.push(" No context files loaded.");
|
|
14970
|
+
layers.push(" Search order per layer: AICLI.override.md \u2192 AGENTS.override.md \u2192 AICLI.md \u2192 CLAUDE.md \u2192 AGENTS.md");
|
|
14971
|
+
}
|
|
14972
|
+
if (result && result.skipped.length > 0) {
|
|
14973
|
+
layers.push("");
|
|
14974
|
+
layers.push("Skipped:");
|
|
14975
|
+
for (const skipped of result.skipped.slice(0, 5)) {
|
|
14976
|
+
layers.push(` - ${skipped.displayPath}: ${skipped.reason}${skipped.message ? ` (${skipped.message})` : ""}`);
|
|
14556
14977
|
}
|
|
14557
|
-
layers.push(` \u2013 ${label}: (none)`);
|
|
14558
|
-
};
|
|
14559
|
-
checkLayer("Global", configDir);
|
|
14560
|
-
checkLayer("Project", projectRoot);
|
|
14561
|
-
if (resolve5(cwd) !== resolve5(projectRoot)) {
|
|
14562
|
-
checkLayer("Subdir", cwd);
|
|
14563
14978
|
}
|
|
14564
14979
|
layers.push("");
|
|
14565
14980
|
layers.push(`Total prompt length: ${this.activeSystemPrompt?.length ?? 0} chars`);
|
|
@@ -14629,12 +15044,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
14629
15044
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
14630
15045
|
break;
|
|
14631
15046
|
}
|
|
14632
|
-
const dirPath =
|
|
14633
|
-
if (!
|
|
15047
|
+
const dirPath = resolve6(sub);
|
|
15048
|
+
if (!existsSync23(dirPath)) {
|
|
14634
15049
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
14635
15050
|
break;
|
|
14636
15051
|
}
|
|
14637
|
-
if (!
|
|
15052
|
+
if (!statSync9(dirPath).isDirectory()) {
|
|
14638
15053
|
this.send({ type: "error", message: `Not a directory: ${dirPath}` });
|
|
14639
15054
|
break;
|
|
14640
15055
|
}
|
|
@@ -14646,14 +15061,14 @@ It will be included in AI context for subsequent messages.` });
|
|
|
14646
15061
|
// ── /commands ───────────────────────────────────────────────────
|
|
14647
15062
|
case "commands": {
|
|
14648
15063
|
const configDir = this.config.getConfigDir();
|
|
14649
|
-
const commandsDir =
|
|
14650
|
-
if (!
|
|
15064
|
+
const commandsDir = join17(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
15065
|
+
if (!existsSync23(commandsDir)) {
|
|
14651
15066
|
this.send({ type: "info", message: `No custom commands directory.
|
|
14652
15067
|
Create: ${commandsDir}/ with .md files.` });
|
|
14653
15068
|
break;
|
|
14654
15069
|
}
|
|
14655
15070
|
try {
|
|
14656
|
-
const files =
|
|
15071
|
+
const files = readdirSync10(commandsDir).filter((f) => f.endsWith(".md"));
|
|
14657
15072
|
if (files.length === 0) {
|
|
14658
15073
|
this.send({ type: "info", message: `No custom commands found in ${commandsDir}
|
|
14659
15074
|
Add .md files to create commands.` });
|
|
@@ -14673,7 +15088,7 @@ Add .md files to create commands.` });
|
|
|
14673
15088
|
// ── /plugins ────────────────────────────────────────────────────
|
|
14674
15089
|
case "plugins": {
|
|
14675
15090
|
const configDir = this.config.getConfigDir();
|
|
14676
|
-
const pluginsDir =
|
|
15091
|
+
const pluginsDir = join17(configDir, PLUGINS_DIR_NAME);
|
|
14677
15092
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
14678
15093
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
14679
15094
|
if (pluginTools.length === 0) {
|
|
@@ -14847,11 +15262,11 @@ Add .md files to create commands.` });
|
|
|
14847
15262
|
}
|
|
14848
15263
|
memoryShow() {
|
|
14849
15264
|
const configDir = this.config.getConfigDir();
|
|
14850
|
-
const memPath =
|
|
15265
|
+
const memPath = join17(configDir, MEMORY_FILE_NAME);
|
|
14851
15266
|
let content = "";
|
|
14852
15267
|
try {
|
|
14853
|
-
if (
|
|
14854
|
-
content =
|
|
15268
|
+
if (existsSync23(memPath)) {
|
|
15269
|
+
content = readFileSync17(memPath, "utf-8");
|
|
14855
15270
|
}
|
|
14856
15271
|
} catch (err) {
|
|
14857
15272
|
process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
|
|
@@ -14865,13 +15280,15 @@ Add .md files to create commands.` });
|
|
|
14865
15280
|
}
|
|
14866
15281
|
memoryAdd(text) {
|
|
14867
15282
|
const configDir = this.config.getConfigDir();
|
|
14868
|
-
const memPath =
|
|
15283
|
+
const memPath = join17(configDir, MEMORY_FILE_NAME);
|
|
14869
15284
|
try {
|
|
14870
15285
|
mkdirSync10(configDir, { recursive: true });
|
|
14871
15286
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
14872
|
-
|
|
15287
|
+
const previous = existsSync23(memPath) ? readFileSync17(memPath, "utf-8") : "";
|
|
15288
|
+
const entry = `
|
|
14873
15289
|
- [${timestamp}] ${text}
|
|
14874
|
-
|
|
15290
|
+
`;
|
|
15291
|
+
atomicWriteFileSync(memPath, previous + entry);
|
|
14875
15292
|
this.send({ type: "info", message: `\u{1F4DD} Memory entry added: "${text}"` });
|
|
14876
15293
|
} catch (err) {
|
|
14877
15294
|
this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
|
|
@@ -14879,9 +15296,9 @@ Add .md files to create commands.` });
|
|
|
14879
15296
|
}
|
|
14880
15297
|
memoryClear() {
|
|
14881
15298
|
const configDir = this.config.getConfigDir();
|
|
14882
|
-
const memPath =
|
|
15299
|
+
const memPath = join17(configDir, MEMORY_FILE_NAME);
|
|
14883
15300
|
try {
|
|
14884
|
-
|
|
15301
|
+
atomicWriteFileSync(memPath, "");
|
|
14885
15302
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
14886
15303
|
} catch (err) {
|
|
14887
15304
|
this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
|
|
@@ -15044,7 +15461,7 @@ Add .md files to create commands.` });
|
|
|
15044
15461
|
dirContext += `
|
|
15045
15462
|
[Directory: ${dir}]
|
|
15046
15463
|
`;
|
|
15047
|
-
dirContext +=
|
|
15464
|
+
dirContext += scanDirTree(dir, 2, 40) + "\n";
|
|
15048
15465
|
totalLen = dirContext.length;
|
|
15049
15466
|
}
|
|
15050
15467
|
stable += dirContext;
|
|
@@ -15083,265 +15500,21 @@ Add .md files to create commands.` });
|
|
|
15083
15500
|
}
|
|
15084
15501
|
return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
|
|
15085
15502
|
}
|
|
15086
|
-
/**
|
|
15087
|
-
* Find first matching context file in a directory.
|
|
15088
|
-
*/
|
|
15089
|
-
findContextFile(dir) {
|
|
15090
|
-
for (const name of CONTEXT_FILE_CANDIDATES) {
|
|
15091
|
-
const fullPath = join15(dir, name);
|
|
15092
|
-
try {
|
|
15093
|
-
if (existsSync21(fullPath)) {
|
|
15094
|
-
const content = readFileSync14(fullPath, "utf-8").trim();
|
|
15095
|
-
if (content) return content;
|
|
15096
|
-
}
|
|
15097
|
-
} catch {
|
|
15098
|
-
}
|
|
15099
|
-
}
|
|
15100
|
-
return null;
|
|
15101
|
-
}
|
|
15102
|
-
/**
|
|
15103
|
-
* Load hierarchical context files (same as CLI):
|
|
15104
|
-
* 1. Global: ~/.aicli/AICLI.md or CLAUDE.md
|
|
15105
|
-
* 2. Project: <git-root>/AICLI.md or CLAUDE.md
|
|
15106
|
-
* 3. Subdir: <cwd>/AICLI.md or CLAUDE.md (only if cwd ≠ project root)
|
|
15107
|
-
*/
|
|
15108
|
-
// ── /init helpers ─────────────────────────────────────────────────
|
|
15109
|
-
static SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
15110
|
-
"node_modules",
|
|
15111
|
-
".git",
|
|
15112
|
-
"dist",
|
|
15113
|
-
"build",
|
|
15114
|
-
"out",
|
|
15115
|
-
"target",
|
|
15116
|
-
".next",
|
|
15117
|
-
".nuxt",
|
|
15118
|
-
"__pycache__",
|
|
15119
|
-
".venv",
|
|
15120
|
-
"venv",
|
|
15121
|
-
".tox",
|
|
15122
|
-
".mypy_cache",
|
|
15123
|
-
".pytest_cache",
|
|
15124
|
-
".gradle",
|
|
15125
|
-
".idea",
|
|
15126
|
-
".vscode",
|
|
15127
|
-
".vs",
|
|
15128
|
-
"coverage",
|
|
15129
|
-
".cache",
|
|
15130
|
-
".parcel-cache",
|
|
15131
|
-
"dist-cjs",
|
|
15132
|
-
"release",
|
|
15133
|
-
".output",
|
|
15134
|
-
".turbo",
|
|
15135
|
-
"vendor"
|
|
15136
|
-
]);
|
|
15137
|
-
scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
15138
|
-
const lines = [];
|
|
15139
|
-
let count = 0;
|
|
15140
|
-
const walk = (d, prefix, depth) => {
|
|
15141
|
-
if (depth > maxDepth || count >= maxEntries) return;
|
|
15142
|
-
let entries;
|
|
15143
|
-
try {
|
|
15144
|
-
entries = readdirSync9(d);
|
|
15145
|
-
} catch {
|
|
15146
|
-
return;
|
|
15147
|
-
}
|
|
15148
|
-
const filtered = entries.filter((e) => !e.startsWith(".") && !_SessionHandler.SCAN_SKIP_DIRS.has(e));
|
|
15149
|
-
const sorted = filtered.sort((a, b) => {
|
|
15150
|
-
let aIsDir = false, bIsDir = false;
|
|
15151
|
-
try {
|
|
15152
|
-
aIsDir = statSync8(join15(d, a)).isDirectory();
|
|
15153
|
-
} catch {
|
|
15154
|
-
}
|
|
15155
|
-
try {
|
|
15156
|
-
bIsDir = statSync8(join15(d, b)).isDirectory();
|
|
15157
|
-
} catch {
|
|
15158
|
-
}
|
|
15159
|
-
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
15160
|
-
return a.localeCompare(b);
|
|
15161
|
-
});
|
|
15162
|
-
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
15163
|
-
const name = sorted[i];
|
|
15164
|
-
const fullPath = join15(d, name);
|
|
15165
|
-
const isLast = i === sorted.length - 1;
|
|
15166
|
-
let isDir;
|
|
15167
|
-
try {
|
|
15168
|
-
isDir = statSync8(fullPath).isDirectory();
|
|
15169
|
-
} catch {
|
|
15170
|
-
continue;
|
|
15171
|
-
}
|
|
15172
|
-
lines.push(prefix + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ") + name + (isDir ? "/" : ""));
|
|
15173
|
-
count++;
|
|
15174
|
-
if (isDir) walk(fullPath, prefix + (isLast ? " " : "\u2502 "), depth + 1);
|
|
15175
|
-
}
|
|
15176
|
-
};
|
|
15177
|
-
walk(dir, "", 0);
|
|
15178
|
-
if (count >= maxEntries) lines.push("... (truncated)");
|
|
15179
|
-
return lines.join("\n");
|
|
15180
|
-
}
|
|
15181
|
-
scanProject(cwd) {
|
|
15182
|
-
const info = { type: "unknown", language: "unknown", configFiles: [], directoryStructure: "" };
|
|
15183
|
-
const check = (file) => existsSync21(join15(cwd, file));
|
|
15184
|
-
const configCandidates = [
|
|
15185
|
-
"package.json",
|
|
15186
|
-
"tsconfig.json",
|
|
15187
|
-
"Cargo.toml",
|
|
15188
|
-
"pyproject.toml",
|
|
15189
|
-
"setup.py",
|
|
15190
|
-
"requirements.txt",
|
|
15191
|
-
"go.mod",
|
|
15192
|
-
"pom.xml",
|
|
15193
|
-
"build.gradle",
|
|
15194
|
-
"build.gradle.kts",
|
|
15195
|
-
"CMakeLists.txt",
|
|
15196
|
-
"Makefile",
|
|
15197
|
-
".csproj",
|
|
15198
|
-
".sln",
|
|
15199
|
-
"composer.json",
|
|
15200
|
-
"Gemfile",
|
|
15201
|
-
"mix.exs",
|
|
15202
|
-
"deno.json",
|
|
15203
|
-
"bun.lockb"
|
|
15204
|
-
];
|
|
15205
|
-
info.configFiles = configCandidates.filter(check);
|
|
15206
|
-
if (check("package.json")) {
|
|
15207
|
-
info.type = "node";
|
|
15208
|
-
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
15209
|
-
try {
|
|
15210
|
-
const pkg = JSON.parse(readFileSync14(join15(cwd, "package.json"), "utf-8"));
|
|
15211
|
-
const scripts = pkg.scripts ?? {};
|
|
15212
|
-
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
15213
|
-
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
15214
|
-
info.devCommand = scripts.dev ? "npm run dev" : void 0;
|
|
15215
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
15216
|
-
if (allDeps["react"]) info.framework = "React";
|
|
15217
|
-
else if (allDeps["vue"]) info.framework = "Vue";
|
|
15218
|
-
else if (allDeps["@angular/core"]) info.framework = "Angular";
|
|
15219
|
-
else if (allDeps["next"]) info.framework = "Next.js";
|
|
15220
|
-
else if (allDeps["express"]) info.framework = "Express";
|
|
15221
|
-
} catch {
|
|
15222
|
-
}
|
|
15223
|
-
} else if (check("Cargo.toml")) {
|
|
15224
|
-
info.type = "rust";
|
|
15225
|
-
info.language = "Rust";
|
|
15226
|
-
info.buildCommand = "cargo build";
|
|
15227
|
-
info.testCommand = "cargo test";
|
|
15228
|
-
} else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
|
|
15229
|
-
info.type = "python";
|
|
15230
|
-
info.language = "Python";
|
|
15231
|
-
info.testCommand = "pytest";
|
|
15232
|
-
} else if (check("go.mod")) {
|
|
15233
|
-
info.type = "go";
|
|
15234
|
-
info.language = "Go";
|
|
15235
|
-
info.buildCommand = "go build ./...";
|
|
15236
|
-
info.testCommand = "go test ./...";
|
|
15237
|
-
} else if (check("pom.xml")) {
|
|
15238
|
-
info.type = "java";
|
|
15239
|
-
info.language = "Java";
|
|
15240
|
-
info.buildCommand = "mvn package";
|
|
15241
|
-
info.testCommand = "mvn test";
|
|
15242
|
-
} else if (check("build.gradle") || check("build.gradle.kts")) {
|
|
15243
|
-
info.type = "java";
|
|
15244
|
-
info.language = "Java/Kotlin";
|
|
15245
|
-
info.buildCommand = "./gradlew build";
|
|
15246
|
-
info.testCommand = "./gradlew test";
|
|
15247
|
-
}
|
|
15248
|
-
info.directoryStructure = this.scanDirTree(cwd);
|
|
15249
|
-
return info;
|
|
15250
|
-
}
|
|
15251
|
-
buildInitPrompt(info, cwd) {
|
|
15252
|
-
const parts = [
|
|
15253
|
-
"Please generate an AICLI.md context file (Markdown format) for the following project.",
|
|
15254
|
-
"\n## Project Info\n",
|
|
15255
|
-
`- Working directory: ${cwd}`,
|
|
15256
|
-
`- Type: ${info.type}`,
|
|
15257
|
-
`- Language: ${info.language}`
|
|
15258
|
-
];
|
|
15259
|
-
if (info.framework) parts.push(`- Framework: ${info.framework}`);
|
|
15260
|
-
if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
|
|
15261
|
-
if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
|
|
15262
|
-
if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
|
|
15263
|
-
parts.push(`
|
|
15264
|
-
## Detected Config Files
|
|
15265
|
-
${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
|
|
15266
|
-
parts.push(`
|
|
15267
|
-
## Directory Structure
|
|
15268
|
-
\`\`\`
|
|
15269
|
-
${info.directoryStructure}
|
|
15270
|
-
\`\`\``);
|
|
15271
|
-
parts.push(`
|
|
15272
|
-
## Requirements
|
|
15273
|
-
Generate a structured Markdown file with: project overview, tech stack, project structure, common commands, code style. Keep it concise, within 200 lines.`);
|
|
15274
|
-
return parts.join("\n");
|
|
15275
|
-
}
|
|
15276
|
-
// ── /review helper ──────────────────────────────────────────────────
|
|
15277
|
-
buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
15278
|
-
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.";
|
|
15279
|
-
return `# Code Review Request
|
|
15280
|
-
|
|
15281
|
-
${level}
|
|
15282
|
-
|
|
15283
|
-
## Git Status
|
|
15284
|
-
${gitContextStr}
|
|
15285
|
-
|
|
15286
|
-
## Code Changes (diff)
|
|
15287
|
-
\`\`\`diff
|
|
15288
|
-
${diff}
|
|
15289
|
-
\`\`\`
|
|
15290
|
-
|
|
15291
|
-
## Output Format
|
|
15292
|
-
1. **Overall Assessment**: One-sentence summary
|
|
15293
|
-
2. **Issues** (if any): [Severity] file:line \u2014 description + fix
|
|
15294
|
-
3. **Improvement Suggestions** (if any)
|
|
15295
|
-
4. **Highlights** (if any)
|
|
15296
|
-
|
|
15297
|
-
Severity: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
|
|
15298
|
-
}
|
|
15299
|
-
buildSecurityReviewPrompt(diff, gitContextStr) {
|
|
15300
|
-
return `# Security Vulnerability Review
|
|
15301
|
-
|
|
15302
|
-
Analyze the following code changes **exclusively for security vulnerabilities**.
|
|
15303
|
-
|
|
15304
|
-
## Categories
|
|
15305
|
-
1. Injection (SQL, command, path traversal, XSS)
|
|
15306
|
-
2. Auth & Authorization (hardcoded creds, missing checks)
|
|
15307
|
-
3. Secrets & Sensitive Data (API keys, tokens in code)
|
|
15308
|
-
4. Input Validation (missing validation, unsafe deserialization)
|
|
15309
|
-
5. Cryptography (weak algorithms, hardcoded IVs)
|
|
15310
|
-
6. File System (path traversal, symlink attacks)
|
|
15311
|
-
7. Network (SSRF, insecure protocols)
|
|
15312
|
-
|
|
15313
|
-
## Git Status
|
|
15314
|
-
${gitContextStr}
|
|
15315
|
-
|
|
15316
|
-
## Code Changes
|
|
15317
|
-
\`\`\`diff
|
|
15318
|
-
${diff}
|
|
15319
|
-
\`\`\`
|
|
15320
|
-
|
|
15321
|
-
## Output Format
|
|
15322
|
-
For each finding:
|
|
15323
|
-
- **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW
|
|
15324
|
-
- **Category** + **File:line**
|
|
15325
|
-
- **Description** + exploitation scenario
|
|
15326
|
-
- **Recommended fix**
|
|
15327
|
-
|
|
15328
|
-
If none found: "\u2705 No security vulnerabilities detected"`;
|
|
15329
|
-
}
|
|
15330
15503
|
loadContextFiles() {
|
|
15331
|
-
const
|
|
15504
|
+
const contextConfig = this.config.get("context");
|
|
15332
15505
|
const cwd = process.cwd();
|
|
15333
|
-
const configDir = this.config.getConfigDir();
|
|
15334
|
-
const globalCtx = this.findContextFile(configDir);
|
|
15335
|
-
if (globalCtx) parts.push(globalCtx);
|
|
15336
15506
|
const gitRoot = getGitRoot(cwd);
|
|
15337
15507
|
const projectRoot = gitRoot ?? cwd;
|
|
15338
|
-
const
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15508
|
+
const result = loadContextFiles({
|
|
15509
|
+
cwd,
|
|
15510
|
+
configDir: this.config.getConfigDir(),
|
|
15511
|
+
projectRoot,
|
|
15512
|
+
setting: this.config.get("contextFile"),
|
|
15513
|
+
fallbackFilenames: contextConfig.projectDocFallbackFilenames,
|
|
15514
|
+
maxBytes: contextConfig.projectDocMaxBytes
|
|
15515
|
+
});
|
|
15516
|
+
this.contextLoadResult = result;
|
|
15517
|
+
return result.mergedContent || void 0;
|
|
15345
15518
|
}
|
|
15346
15519
|
};
|
|
15347
15520
|
|
|
@@ -15357,8 +15530,8 @@ async function setupProxy(configProxy) {
|
|
|
15357
15530
|
}
|
|
15358
15531
|
|
|
15359
15532
|
// src/web/auth.ts
|
|
15360
|
-
import { existsSync as
|
|
15361
|
-
import { join as
|
|
15533
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18, writeFileSync as writeFileSync3, mkdirSync as mkdirSync11, readdirSync as readdirSync11, copyFileSync } from "fs";
|
|
15534
|
+
import { join as join18 } from "path";
|
|
15362
15535
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
15363
15536
|
var USERS_FILE = "users.json";
|
|
15364
15537
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -15373,7 +15546,7 @@ var AuthManager = class {
|
|
|
15373
15546
|
db;
|
|
15374
15547
|
constructor(baseDir) {
|
|
15375
15548
|
this.baseDir = baseDir;
|
|
15376
|
-
this.usersFile =
|
|
15549
|
+
this.usersFile = join18(baseDir, USERS_FILE);
|
|
15377
15550
|
this.db = this.loadOrCreate();
|
|
15378
15551
|
}
|
|
15379
15552
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -15402,9 +15575,9 @@ var AuthManager = class {
|
|
|
15402
15575
|
}
|
|
15403
15576
|
const salt = randomBytes(16).toString("hex");
|
|
15404
15577
|
const passwordHash = this.hashPassword(password, salt);
|
|
15405
|
-
const dataDir =
|
|
15406
|
-
const fullDataDir =
|
|
15407
|
-
mkdirSync11(
|
|
15578
|
+
const dataDir = join18(USERS_DIR, username);
|
|
15579
|
+
const fullDataDir = join18(this.baseDir, dataDir);
|
|
15580
|
+
mkdirSync11(join18(fullDataDir, "history"), { recursive: true });
|
|
15408
15581
|
const user = {
|
|
15409
15582
|
username,
|
|
15410
15583
|
passwordHash,
|
|
@@ -15519,7 +15692,7 @@ var AuthManager = class {
|
|
|
15519
15692
|
getUserDataDir(username) {
|
|
15520
15693
|
const user = this.db.users.find((u) => u.username === username);
|
|
15521
15694
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
15522
|
-
return
|
|
15695
|
+
return join18(this.baseDir, user.dataDir);
|
|
15523
15696
|
}
|
|
15524
15697
|
/** List all usernames */
|
|
15525
15698
|
listUsers() {
|
|
@@ -15555,30 +15728,30 @@ var AuthManager = class {
|
|
|
15555
15728
|
const err = this.register(username, password);
|
|
15556
15729
|
if (err) return err;
|
|
15557
15730
|
const userDir = this.getUserDataDir(username);
|
|
15558
|
-
const globalConfig =
|
|
15559
|
-
if (
|
|
15731
|
+
const globalConfig = join18(this.baseDir, "config.json");
|
|
15732
|
+
if (existsSync24(globalConfig)) {
|
|
15560
15733
|
try {
|
|
15561
|
-
const content =
|
|
15562
|
-
|
|
15734
|
+
const content = readFileSync18(globalConfig, "utf-8");
|
|
15735
|
+
writeFileSync3(join18(userDir, "config.json"), content, "utf-8");
|
|
15563
15736
|
} catch {
|
|
15564
15737
|
}
|
|
15565
15738
|
}
|
|
15566
|
-
const globalMemory =
|
|
15567
|
-
if (
|
|
15739
|
+
const globalMemory = join18(this.baseDir, "memory.md");
|
|
15740
|
+
if (existsSync24(globalMemory)) {
|
|
15568
15741
|
try {
|
|
15569
|
-
const content =
|
|
15570
|
-
|
|
15742
|
+
const content = readFileSync18(globalMemory, "utf-8");
|
|
15743
|
+
writeFileSync3(join18(userDir, "memory.md"), content, "utf-8");
|
|
15571
15744
|
} catch {
|
|
15572
15745
|
}
|
|
15573
15746
|
}
|
|
15574
|
-
const globalHistory =
|
|
15575
|
-
if (
|
|
15747
|
+
const globalHistory = join18(this.baseDir, "history");
|
|
15748
|
+
if (existsSync24(globalHistory)) {
|
|
15576
15749
|
try {
|
|
15577
|
-
const files =
|
|
15578
|
-
const userHistory =
|
|
15750
|
+
const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
|
|
15751
|
+
const userHistory = join18(userDir, "history");
|
|
15579
15752
|
for (const f of files) {
|
|
15580
15753
|
try {
|
|
15581
|
-
copyFileSync(
|
|
15754
|
+
copyFileSync(join18(globalHistory, f), join18(userHistory, f));
|
|
15582
15755
|
} catch {
|
|
15583
15756
|
}
|
|
15584
15757
|
}
|
|
@@ -15589,9 +15762,9 @@ var AuthManager = class {
|
|
|
15589
15762
|
}
|
|
15590
15763
|
// ── Private methods ────────────────────────────────────────────
|
|
15591
15764
|
loadOrCreate() {
|
|
15592
|
-
if (
|
|
15765
|
+
if (existsSync24(this.usersFile)) {
|
|
15593
15766
|
try {
|
|
15594
|
-
return JSON.parse(
|
|
15767
|
+
return JSON.parse(readFileSync18(this.usersFile, "utf-8"));
|
|
15595
15768
|
} catch {
|
|
15596
15769
|
}
|
|
15597
15770
|
}
|
|
@@ -15643,7 +15816,7 @@ function getModuleDir() {
|
|
|
15643
15816
|
}
|
|
15644
15817
|
} catch {
|
|
15645
15818
|
}
|
|
15646
|
-
return
|
|
15819
|
+
return resolve7(".");
|
|
15647
15820
|
}
|
|
15648
15821
|
async function startWebServer(options = {}) {
|
|
15649
15822
|
const port = options.port ?? 3e3;
|
|
@@ -15708,8 +15881,8 @@ async function startWebServer(options = {}) {
|
|
|
15708
15881
|
}
|
|
15709
15882
|
}
|
|
15710
15883
|
let skillManager = null;
|
|
15711
|
-
const skillsDir =
|
|
15712
|
-
if (
|
|
15884
|
+
const skillsDir = join19(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
15885
|
+
if (existsSync25(skillsDir)) {
|
|
15713
15886
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
15714
15887
|
skillManager.loadSkills();
|
|
15715
15888
|
const count = skillManager.listSkills().length;
|
|
@@ -15780,17 +15953,18 @@ async function startWebServer(options = {}) {
|
|
|
15780
15953
|
next();
|
|
15781
15954
|
};
|
|
15782
15955
|
const moduleDir = getModuleDir();
|
|
15783
|
-
let clientDir =
|
|
15784
|
-
if (!
|
|
15785
|
-
clientDir =
|
|
15956
|
+
let clientDir = join19(moduleDir, "web", "client");
|
|
15957
|
+
if (!existsSync25(clientDir)) {
|
|
15958
|
+
clientDir = join19(moduleDir, "client");
|
|
15786
15959
|
}
|
|
15787
|
-
if (!
|
|
15788
|
-
clientDir =
|
|
15960
|
+
if (!existsSync25(clientDir)) {
|
|
15961
|
+
clientDir = join19(moduleDir, "..", "..", "src", "web", "client");
|
|
15789
15962
|
}
|
|
15790
|
-
if (!
|
|
15791
|
-
clientDir =
|
|
15963
|
+
if (!existsSync25(clientDir)) {
|
|
15964
|
+
clientDir = join19(process.cwd(), "src", "web", "client");
|
|
15792
15965
|
}
|
|
15793
15966
|
console.log(` Static files: ${clientDir}`);
|
|
15967
|
+
app.use("/vendor", express.static(join19(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
15794
15968
|
app.use(express.static(clientDir));
|
|
15795
15969
|
app.get("/api/status", (_req, res) => {
|
|
15796
15970
|
res.json({
|
|
@@ -15833,7 +16007,7 @@ async function startWebServer(options = {}) {
|
|
|
15833
16007
|
}
|
|
15834
16008
|
const token = authManager.login(username, password);
|
|
15835
16009
|
console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
|
|
15836
|
-
res.cookie("aicli_token", token,
|
|
16010
|
+
res.cookie("aicli_token", token, authCookieOptions(req));
|
|
15837
16011
|
res.json({ success: true, username });
|
|
15838
16012
|
});
|
|
15839
16013
|
app.post("/api/auth/login", (req, res) => {
|
|
@@ -15847,20 +16021,20 @@ async function startWebServer(options = {}) {
|
|
|
15847
16021
|
res.status(401).json({ error: "Invalid username or password" });
|
|
15848
16022
|
return;
|
|
15849
16023
|
}
|
|
15850
|
-
res.cookie("aicli_token", token,
|
|
16024
|
+
res.cookie("aicli_token", token, authCookieOptions(req));
|
|
15851
16025
|
res.json({ success: true, username });
|
|
15852
16026
|
});
|
|
15853
|
-
app.post("/api/auth/logout", (
|
|
15854
|
-
res.clearCookie("aicli_token",
|
|
16027
|
+
app.post("/api/auth/logout", (req, res) => {
|
|
16028
|
+
res.clearCookie("aicli_token", clearAuthCookieOptions(req));
|
|
15855
16029
|
res.json({ success: true });
|
|
15856
16030
|
});
|
|
15857
16031
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
15858
16032
|
const cwd = process.cwd();
|
|
15859
16033
|
const prefix = req.query.prefix || "";
|
|
15860
|
-
const targetDir =
|
|
16034
|
+
const targetDir = join19(cwd, prefix);
|
|
15861
16035
|
try {
|
|
15862
|
-
const canonicalTarget = realpathSync(
|
|
15863
|
-
const canonicalCwd = realpathSync(
|
|
16036
|
+
const canonicalTarget = realpathSync(resolve7(targetDir));
|
|
16037
|
+
const canonicalCwd = realpathSync(resolve7(cwd));
|
|
15864
16038
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
15865
16039
|
res.json({ files: [] });
|
|
15866
16040
|
return;
|
|
@@ -15871,10 +16045,10 @@ async function startWebServer(options = {}) {
|
|
|
15871
16045
|
}
|
|
15872
16046
|
try {
|
|
15873
16047
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
|
|
15874
|
-
const entries =
|
|
16048
|
+
const entries = readdirSync12(targetDir, { withFileTypes: true });
|
|
15875
16049
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
15876
16050
|
name: e.name,
|
|
15877
|
-
path:
|
|
16051
|
+
path: relative4(cwd, join19(targetDir, e.name)).replace(/\\/g, "/"),
|
|
15878
16052
|
isDir: e.isDirectory()
|
|
15879
16053
|
}));
|
|
15880
16054
|
res.json({ files });
|
|
@@ -15910,8 +16084,8 @@ async function startWebServer(options = {}) {
|
|
|
15910
16084
|
try {
|
|
15911
16085
|
const authUser = req._authUser;
|
|
15912
16086
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
15913
|
-
const filePath =
|
|
15914
|
-
if (!
|
|
16087
|
+
const filePath = join19(histDir, `${id}.json`);
|
|
16088
|
+
if (!existsSync25(filePath)) {
|
|
15915
16089
|
res.status(404).json({ error: "Session not found" });
|
|
15916
16090
|
return;
|
|
15917
16091
|
}
|
|
@@ -15926,7 +16100,7 @@ async function startWebServer(options = {}) {
|
|
|
15926
16100
|
res.status(404).json({ error: "Session not found" });
|
|
15927
16101
|
return;
|
|
15928
16102
|
}
|
|
15929
|
-
const data = JSON.parse(
|
|
16103
|
+
const data = JSON.parse(readFileSync19(filePath, "utf-8"));
|
|
15930
16104
|
res.json({ session: data });
|
|
15931
16105
|
} catch (err) {
|
|
15932
16106
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -15939,10 +16113,10 @@ async function startWebServer(options = {}) {
|
|
|
15939
16113
|
return;
|
|
15940
16114
|
}
|
|
15941
16115
|
const cwd = process.cwd();
|
|
15942
|
-
const fullPath =
|
|
16116
|
+
const fullPath = resolve7(join19(cwd, filePath));
|
|
15943
16117
|
try {
|
|
15944
16118
|
const canonicalFull = realpathSync(fullPath);
|
|
15945
|
-
const canonicalCwd = realpathSync(
|
|
16119
|
+
const canonicalCwd = realpathSync(resolve7(cwd));
|
|
15946
16120
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
15947
16121
|
res.json({ error: "Access denied" });
|
|
15948
16122
|
return;
|
|
@@ -15952,12 +16126,12 @@ async function startWebServer(options = {}) {
|
|
|
15952
16126
|
return;
|
|
15953
16127
|
}
|
|
15954
16128
|
try {
|
|
15955
|
-
const stat =
|
|
16129
|
+
const stat = statSync10(fullPath);
|
|
15956
16130
|
if (stat.size > 512 * 1024) {
|
|
15957
16131
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
15958
16132
|
return;
|
|
15959
16133
|
}
|
|
15960
|
-
const content =
|
|
16134
|
+
const content = readFileSync19(fullPath, "utf-8");
|
|
15961
16135
|
res.json({ content, size: stat.size });
|
|
15962
16136
|
} catch {
|
|
15963
16137
|
res.json({ error: "Cannot read file" });
|
|
@@ -16161,7 +16335,7 @@ async function startWebServer(options = {}) {
|
|
|
16161
16335
|
});
|
|
16162
16336
|
const MAX_PORT_ATTEMPTS = 10;
|
|
16163
16337
|
let actualPort = port;
|
|
16164
|
-
const result = await new Promise((
|
|
16338
|
+
const result = await new Promise((resolve8, reject) => {
|
|
16165
16339
|
const tryListen = (attempt) => {
|
|
16166
16340
|
server.once("error", (err) => {
|
|
16167
16341
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -16192,7 +16366,7 @@ async function startWebServer(options = {}) {
|
|
|
16192
16366
|
}
|
|
16193
16367
|
console.log(` Press Ctrl+C to stop
|
|
16194
16368
|
`);
|
|
16195
|
-
|
|
16369
|
+
resolve8({ port: actualPort, host, url });
|
|
16196
16370
|
});
|
|
16197
16371
|
};
|
|
16198
16372
|
tryListen(1);
|
|
@@ -16212,22 +16386,43 @@ function resolveProjectMcpPath() {
|
|
|
16212
16386
|
const cwd = process.cwd();
|
|
16213
16387
|
const gitRoot = getGitRoot(cwd);
|
|
16214
16388
|
const projectRoot = gitRoot ?? cwd;
|
|
16215
|
-
const configPath =
|
|
16216
|
-
return
|
|
16389
|
+
const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
16390
|
+
return existsSync25(configPath) ? configPath : null;
|
|
16217
16391
|
}
|
|
16218
16392
|
function loadProjectMcpConfig() {
|
|
16219
16393
|
const cwd = process.cwd();
|
|
16220
16394
|
const gitRoot = getGitRoot(cwd);
|
|
16221
16395
|
const projectRoot = gitRoot ?? cwd;
|
|
16222
|
-
const configPath =
|
|
16223
|
-
if (!
|
|
16396
|
+
const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
16397
|
+
if (!existsSync25(configPath)) return null;
|
|
16224
16398
|
try {
|
|
16225
|
-
const raw = JSON.parse(
|
|
16399
|
+
const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
16226
16400
|
return raw.mcpServers ?? raw;
|
|
16227
16401
|
} catch {
|
|
16228
16402
|
return null;
|
|
16229
16403
|
}
|
|
16230
16404
|
}
|
|
16405
|
+
function isHttpsRequest(req) {
|
|
16406
|
+
if (req.secure) return true;
|
|
16407
|
+
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
16408
|
+
const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
|
|
16409
|
+
return proto?.split(",")[0]?.trim().toLowerCase() === "https";
|
|
16410
|
+
}
|
|
16411
|
+
function authCookieOptions(req) {
|
|
16412
|
+
return {
|
|
16413
|
+
httpOnly: true,
|
|
16414
|
+
sameSite: "strict",
|
|
16415
|
+
maxAge: TOKEN_EXPIRY_MS,
|
|
16416
|
+
secure: isHttpsRequest(req)
|
|
16417
|
+
};
|
|
16418
|
+
}
|
|
16419
|
+
function clearAuthCookieOptions(req) {
|
|
16420
|
+
return {
|
|
16421
|
+
httpOnly: true,
|
|
16422
|
+
sameSite: "strict",
|
|
16423
|
+
secure: isHttpsRequest(req)
|
|
16424
|
+
};
|
|
16425
|
+
}
|
|
16231
16426
|
function parseCookie(cookie) {
|
|
16232
16427
|
const result = {};
|
|
16233
16428
|
for (const part of cookie.split(";")) {
|