ai-spec-dev 0.41.0 → 0.46.0
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/.ai-spec-workspace.json +17 -0
- package/.ai-spec.json +7 -0
- package/README.md +33 -17
- package/cli/commands/create.ts +232 -11
- package/cli/commands/init.ts +310 -107
- package/cli/commands/model.ts +7 -11
- package/cli/index.ts +1 -1
- package/cli/pipeline/single-repo.ts +19 -10
- package/cli/utils.ts +72 -4
- package/core/cli-ui.ts +136 -0
- package/core/code-generator.ts +4 -2
- package/core/config-defaults.ts +44 -0
- package/core/constitution-generator.ts +2 -1
- package/core/dsl-extractor.ts +2 -1
- package/core/error-feedback.ts +7 -4
- package/core/openapi-exporter.ts +3 -2
- package/core/provider-utils.ts +8 -7
- package/core/repo-store.ts +95 -0
- package/core/reviewer.ts +14 -13
- package/core/run-logger.ts +3 -4
- package/core/run-snapshot.ts +2 -3
- package/core/run-trend.ts +3 -4
- package/core/spec-generator.ts +27 -42
- package/core/token-budget.ts +3 -8
- package/core/vcr.ts +3 -1
- package/dist/cli/index.js +1042 -533
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1042 -533
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +123 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +123 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/RELEASE_LOG.md +0 -2962
- package/purpose.md +0 -1434
package/dist/index.d.mts
CHANGED
|
@@ -81,6 +81,8 @@ interface ProviderMeta {
|
|
|
81
81
|
models: string[];
|
|
82
82
|
/** Environment variable name for the API key */
|
|
83
83
|
envKey: string;
|
|
84
|
+
/** Fallback env var names checked if envKey is not set */
|
|
85
|
+
fallbackEnvKeys?: string[];
|
|
84
86
|
/**
|
|
85
87
|
* Base URL for OpenAI-compatible providers.
|
|
86
88
|
* Undefined means the provider has its own SDK (Gemini / Claude).
|
|
@@ -126,10 +128,9 @@ declare class OpenAICompatibleProvider implements AIProvider {
|
|
|
126
128
|
generate(prompt: string, systemInstruction?: string): Promise<string>;
|
|
127
129
|
}
|
|
128
130
|
declare class MiMoProvider implements AIProvider {
|
|
131
|
+
private client;
|
|
129
132
|
readonly providerName = "mimo";
|
|
130
133
|
readonly modelName: string;
|
|
131
|
-
private apiKey;
|
|
132
|
-
private readonly baseUrl;
|
|
133
134
|
constructor(apiKey: string, modelName?: string);
|
|
134
135
|
generate(prompt: string, systemInstruction?: string): Promise<string>;
|
|
135
136
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ interface ProviderMeta {
|
|
|
81
81
|
models: string[];
|
|
82
82
|
/** Environment variable name for the API key */
|
|
83
83
|
envKey: string;
|
|
84
|
+
/** Fallback env var names checked if envKey is not set */
|
|
85
|
+
fallbackEnvKeys?: string[];
|
|
84
86
|
/**
|
|
85
87
|
* Base URL for OpenAI-compatible providers.
|
|
86
88
|
* Undefined means the provider has its own SDK (Gemini / Claude).
|
|
@@ -126,10 +128,9 @@ declare class OpenAICompatibleProvider implements AIProvider {
|
|
|
126
128
|
generate(prompt: string, systemInstruction?: string): Promise<string>;
|
|
127
129
|
}
|
|
128
130
|
declare class MiMoProvider implements AIProvider {
|
|
131
|
+
private client;
|
|
129
132
|
readonly providerName = "mimo";
|
|
130
133
|
readonly modelName: string;
|
|
131
|
-
private apiKey;
|
|
132
|
-
private readonly baseUrl;
|
|
133
134
|
constructor(apiKey: string, modelName?: string);
|
|
134
135
|
generate(prompt: string, systemInstruction?: string): Promise<string>;
|
|
135
136
|
}
|
package/dist/index.js
CHANGED
|
@@ -68,7 +68,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
68
68
|
var import_generative_ai = require("@google/generative-ai");
|
|
69
69
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
70
70
|
var import_openai = __toESM(require("openai"));
|
|
71
|
-
var import_axios = __toESM(require("axios"));
|
|
72
71
|
var import_undici = require("undici");
|
|
73
72
|
|
|
74
73
|
// prompts/spec.prompt.ts
|
|
@@ -182,9 +181,77 @@ CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uF
|
|
|
182
181
|
3. \u5BF9\u4E8E\u6BCF\u6761\u76F4\u63A5\u76F8\u5173\u7684\u6559\u8BAD\uFF0C\u5728 \xA78 \u5B9E\u65BD\u8981\u70B9\u672B\u5C3E\u8FFD\u52A0\u4E00\u884C\uFF1A\u300C\u26A0 \u57FA\u4E8E\u5386\u53F2\u6559\u8BAD\uFF1A[\u7B80\u8FF0\u672C\u6B21 spec \u5982\u4F55\u89C4\u907F\u8BE5\u95EE\u9898]\u300D
|
|
183
182
|
4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
|
|
184
183
|
|
|
185
|
-
// core/
|
|
184
|
+
// core/cli-ui.ts
|
|
186
185
|
var import_chalk = __toESM(require("chalk"));
|
|
187
|
-
var
|
|
186
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
187
|
+
function startSpinner(text) {
|
|
188
|
+
const isTTY = process.stderr.isTTY;
|
|
189
|
+
let frame = 0;
|
|
190
|
+
let currentText = text;
|
|
191
|
+
let stopped = false;
|
|
192
|
+
function render() {
|
|
193
|
+
if (stopped) return;
|
|
194
|
+
const symbol = import_chalk.default.cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
|
|
195
|
+
if (isTTY) {
|
|
196
|
+
process.stderr.write(`\r ${symbol} ${currentText}${" ".repeat(10)}`);
|
|
197
|
+
}
|
|
198
|
+
frame++;
|
|
199
|
+
}
|
|
200
|
+
if (!isTTY) {
|
|
201
|
+
process.stderr.write(` \u2026 ${currentText}
|
|
202
|
+
`);
|
|
203
|
+
}
|
|
204
|
+
const timer = setInterval(render, 80);
|
|
205
|
+
render();
|
|
206
|
+
return {
|
|
207
|
+
update(newText) {
|
|
208
|
+
currentText = newText;
|
|
209
|
+
},
|
|
210
|
+
stop(finalText) {
|
|
211
|
+
if (stopped) return;
|
|
212
|
+
stopped = true;
|
|
213
|
+
clearInterval(timer);
|
|
214
|
+
if (isTTY) {
|
|
215
|
+
process.stderr.write(`\r${" ".repeat(currentText.length + 20)}\r`);
|
|
216
|
+
}
|
|
217
|
+
if (finalText) {
|
|
218
|
+
process.stderr.write(` ${finalText}
|
|
219
|
+
`);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
succeed(successText) {
|
|
223
|
+
this.stop(import_chalk.default.green(`\u2714 ${successText}`));
|
|
224
|
+
},
|
|
225
|
+
fail(failText) {
|
|
226
|
+
this.stop(import_chalk.default.red(`\u2718 ${failText}`));
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async function retryCountdown(opts) {
|
|
231
|
+
const { attempt, maxAttempts, waitMs, errorMessage, label } = opts;
|
|
232
|
+
const isTTY = process.stderr.isTTY;
|
|
233
|
+
const shortErr = errorMessage.length > 120 ? errorMessage.slice(0, 117) + "..." : errorMessage;
|
|
234
|
+
process.stderr.write("\n");
|
|
235
|
+
process.stderr.write(import_chalk.default.yellow(` \u250C\u2500 Retry ${attempt}/${maxAttempts} `) + import_chalk.default.gray(`[${label}]`) + import_chalk.default.yellow(` ${"\u2500".repeat(Math.max(1, 40 - label.length))}
|
|
236
|
+
`));
|
|
237
|
+
process.stderr.write(import_chalk.default.yellow(` \u2502 `) + import_chalk.default.white(shortErr) + "\n");
|
|
238
|
+
process.stderr.write(import_chalk.default.yellow(` \u2502 `) + import_chalk.default.gray(`Waiting before retry...`) + "\n");
|
|
239
|
+
const totalSeconds = Math.ceil(waitMs / 1e3);
|
|
240
|
+
for (let s = totalSeconds; s > 0; s--) {
|
|
241
|
+
const bar = import_chalk.default.green("\u2588".repeat(totalSeconds - s)) + import_chalk.default.gray("\u2591".repeat(s));
|
|
242
|
+
const line = import_chalk.default.yellow(` \u2502 `) + `${bar} ${import_chalk.default.bold.white(`${s}s`)}`;
|
|
243
|
+
if (isTTY) {
|
|
244
|
+
process.stderr.write(`\r${line}${" ".repeat(10)}`);
|
|
245
|
+
}
|
|
246
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
247
|
+
}
|
|
248
|
+
if (isTTY) {
|
|
249
|
+
process.stderr.write(`\r${" ".repeat(70)}\r`);
|
|
250
|
+
}
|
|
251
|
+
process.stderr.write(import_chalk.default.yellow(` \u2514\u2500 `) + import_chalk.default.cyan(`Retrying now...`) + "\n\n");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// core/provider-utils.ts
|
|
188
255
|
var ProviderError = class extends Error {
|
|
189
256
|
constructor(message, kind, originalError) {
|
|
190
257
|
super(message);
|
|
@@ -268,11 +335,14 @@ async function withReliability(fn, opts) {
|
|
|
268
335
|
throw classifyError(err, label);
|
|
269
336
|
}
|
|
270
337
|
const waitMs = attempt === 0 ? 2e3 : 6e3;
|
|
271
|
-
console.warn(
|
|
272
|
-
import_chalk.default.yellow(` \u26A0 ${label} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${waitMs / 1e3}s`) + import_chalk.default.gray(` \u2014 ${err.message}`)
|
|
273
|
-
);
|
|
274
338
|
onRetry?.(attempt + 1, err);
|
|
275
|
-
await
|
|
339
|
+
await retryCountdown({
|
|
340
|
+
attempt: attempt + 1,
|
|
341
|
+
maxAttempts: retries + 1,
|
|
342
|
+
waitMs,
|
|
343
|
+
errorMessage: err.message ?? String(err),
|
|
344
|
+
label
|
|
345
|
+
});
|
|
276
346
|
}
|
|
277
347
|
}
|
|
278
348
|
throw new Error("unreachable");
|
|
@@ -290,7 +360,9 @@ var PROVIDER_CATALOG = {
|
|
|
290
360
|
displayName: "MiMo (Xiaomi)",
|
|
291
361
|
description: "\u5C0F\u7C73 MiMo \u2014 mimo-v2-pro (Anthropic-compatible API)",
|
|
292
362
|
models: ["mimo-v2-pro"],
|
|
293
|
-
envKey: "MIMO_API_KEY"
|
|
363
|
+
envKey: "MIMO_API_KEY",
|
|
364
|
+
// Fallback env var — MiMo's token plan uses ANTHROPIC_AUTH_TOKEN
|
|
365
|
+
fallbackEnvKeys: ["ANTHROPIC_AUTH_TOKEN"]
|
|
294
366
|
// baseURL not used — MiMo has a dedicated provider class
|
|
295
367
|
},
|
|
296
368
|
gemini: {
|
|
@@ -459,8 +531,8 @@ var ClaudeProvider = class {
|
|
|
459
531
|
...systemInstruction ? { system: systemInstruction } : {},
|
|
460
532
|
messages: [{ role: "user", content: prompt }]
|
|
461
533
|
});
|
|
462
|
-
const
|
|
463
|
-
if (
|
|
534
|
+
const textBlock = message.content.find((b) => b.type === "text");
|
|
535
|
+
if (textBlock) return textBlock.text;
|
|
464
536
|
throw new Error("Unexpected response type from Claude API");
|
|
465
537
|
},
|
|
466
538
|
{ label: `${this.providerName}/${this.modelName}` }
|
|
@@ -505,43 +577,29 @@ var OpenAICompatibleProvider = class {
|
|
|
505
577
|
}
|
|
506
578
|
};
|
|
507
579
|
var MiMoProvider = class {
|
|
580
|
+
client;
|
|
508
581
|
providerName = "mimo";
|
|
509
582
|
modelName;
|
|
510
|
-
apiKey;
|
|
511
|
-
baseUrl = "https://api.xiaomimimo.com/anthropic/v1/messages";
|
|
512
583
|
constructor(apiKey, modelName = PROVIDER_CATALOG.mimo.models[0]) {
|
|
513
|
-
|
|
584
|
+
const baseURL = process.env["MIMO_BASE_URL"] || process.env["ANTHROPIC_BASE_URL"] || "https://token-plan-cn.xiaomimimo.com/anthropic";
|
|
585
|
+
this.client = new import_sdk.default({ apiKey, baseURL });
|
|
514
586
|
this.modelName = modelName;
|
|
515
587
|
}
|
|
516
588
|
async generate(prompt, systemInstruction) {
|
|
517
589
|
return withReliability(
|
|
518
590
|
async () => {
|
|
519
|
-
const
|
|
591
|
+
const stream = this.client.messages.stream({
|
|
520
592
|
model: this.modelName,
|
|
521
|
-
max_tokens:
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
stream: false,
|
|
525
|
-
temperature: 1,
|
|
526
|
-
stop_sequences: null
|
|
527
|
-
};
|
|
528
|
-
if (systemInstruction) {
|
|
529
|
-
body.system = systemInstruction;
|
|
530
|
-
}
|
|
531
|
-
const response = await import_axios.default.post(this.baseUrl, body, {
|
|
532
|
-
headers: {
|
|
533
|
-
"api-key": this.apiKey,
|
|
534
|
-
"Content-Type": "application/json"
|
|
535
|
-
}
|
|
593
|
+
max_tokens: 65536,
|
|
594
|
+
...systemInstruction ? { system: systemInstruction } : {},
|
|
595
|
+
messages: [{ role: "user", content: prompt }]
|
|
536
596
|
});
|
|
537
|
-
const
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
throw new Error(`Unexpected MiMo response: ${JSON.stringify(response.data).slice(0, 200)}`);
|
|
597
|
+
const message = await stream.finalMessage();
|
|
598
|
+
const textBlock = message.content.find((b) => b.type === "text");
|
|
599
|
+
if (textBlock) return textBlock.text;
|
|
600
|
+
const thinkBlock = message.content.find((b) => b.type === "thinking");
|
|
601
|
+
if (thinkBlock) return thinkBlock.thinking;
|
|
602
|
+
return message.content.map((b) => b.text ?? "").join("");
|
|
545
603
|
},
|
|
546
604
|
{ label: `${this.providerName}/${this.modelName}` }
|
|
547
605
|
);
|
|
@@ -5146,13 +5204,11 @@ function typeLabel(v2) {
|
|
|
5146
5204
|
|
|
5147
5205
|
// core/token-budget.ts
|
|
5148
5206
|
var import_chalk6 = __toESM(require("chalk"));
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
return Math.ceil(cjkCount + nonCjkLength / 4);
|
|
5155
|
-
}
|
|
5207
|
+
|
|
5208
|
+
// core/config-defaults.ts
|
|
5209
|
+
var DEFAULT_REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
|
|
5210
|
+
var DEFAULT_MAX_CONSTITUTION_CHARS = 4e3;
|
|
5211
|
+
var DEFAULT_MAX_REVIEW_FILE_CHARS = 3e3;
|
|
5156
5212
|
var DEFAULT_TOKEN_BUDGETS = {
|
|
5157
5213
|
gemini: 9e5,
|
|
5158
5214
|
claude: 18e4,
|
|
@@ -5160,8 +5216,18 @@ var DEFAULT_TOKEN_BUDGETS = {
|
|
|
5160
5216
|
deepseek: 6e4,
|
|
5161
5217
|
default: 1e5
|
|
5162
5218
|
};
|
|
5219
|
+
|
|
5220
|
+
// core/token-budget.ts
|
|
5221
|
+
var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
|
|
5222
|
+
function estimateTokens(text) {
|
|
5223
|
+
if (!text) return 0;
|
|
5224
|
+
const cjkCount = (text.match(CJK_RANGE) ?? []).length;
|
|
5225
|
+
const nonCjkLength = text.length - cjkCount;
|
|
5226
|
+
return Math.ceil(cjkCount + nonCjkLength / 4);
|
|
5227
|
+
}
|
|
5228
|
+
var DEFAULT_TOKEN_BUDGETS2 = DEFAULT_TOKEN_BUDGETS;
|
|
5163
5229
|
function getDefaultBudget(providerName) {
|
|
5164
|
-
return
|
|
5230
|
+
return DEFAULT_TOKEN_BUDGETS2[providerName] ?? DEFAULT_TOKEN_BUDGETS2.default;
|
|
5165
5231
|
}
|
|
5166
5232
|
|
|
5167
5233
|
// core/dsl-extractor.ts
|
|
@@ -6463,6 +6529,7 @@ ${spec}
|
|
|
6463
6529
|
${constitutionSection}
|
|
6464
6530
|
=== ${existingContent ? "Existing content (modify and return the complete file)" : "Create this file from scratch"} ===
|
|
6465
6531
|
${existingContent || "Output only the complete file content."}`;
|
|
6532
|
+
const fileSpinner = startSpinner(`${prefix}Generating ${import_chalk10.default.bold(item.file)}...`);
|
|
6466
6533
|
try {
|
|
6467
6534
|
const raw = await this.provider.generate(codePrompt, systemPrompt);
|
|
6468
6535
|
const fileContent = stripCodeFences(raw);
|
|
@@ -6470,11 +6537,11 @@ ${existingContent || "Output only the complete file content."}`;
|
|
|
6470
6537
|
await fs10.ensureDir(path6.dirname(fullPath));
|
|
6471
6538
|
await fs10.writeFile(fullPath, fileContent, "utf-8");
|
|
6472
6539
|
getActiveLogger()?.fileWritten(item.file);
|
|
6473
|
-
|
|
6540
|
+
fileSpinner.succeed(`${existingContent ? import_chalk10.default.yellow("~") : import_chalk10.default.green("+")} ${import_chalk10.default.bold(item.file)}`);
|
|
6474
6541
|
successCount++;
|
|
6475
6542
|
writtenFiles.push(item.file);
|
|
6476
6543
|
} catch (err) {
|
|
6477
|
-
|
|
6544
|
+
fileSpinner.fail(`${import_chalk10.default.bold(item.file)} \u2014 ${err.message}`);
|
|
6478
6545
|
}
|
|
6479
6546
|
}
|
|
6480
6547
|
if (!taskLabel) {
|
|
@@ -6621,7 +6688,7 @@ ${context.routeSummary}
|
|
|
6621
6688
|
}
|
|
6622
6689
|
if (context.schema) {
|
|
6623
6690
|
parts.push(`=== Prisma Schema ===
|
|
6624
|
-
${context.schema.slice(0,
|
|
6691
|
+
${context.schema.slice(0, DEFAULT_MAX_CONSTITUTION_CHARS)}
|
|
6625
6692
|
`);
|
|
6626
6693
|
}
|
|
6627
6694
|
if (context.errorPatterns) {
|
|
@@ -6685,7 +6752,7 @@ async function loadAccumulatedLessons(projectRoot) {
|
|
|
6685
6752
|
const nextSection = section.slice(marker.length).match(/\n## \d/);
|
|
6686
6753
|
return nextSection ? section.slice(0, marker.length + nextSection.index) : section;
|
|
6687
6754
|
}
|
|
6688
|
-
var REVIEW_HISTORY_FILE =
|
|
6755
|
+
var REVIEW_HISTORY_FILE = DEFAULT_REVIEW_HISTORY_FILE;
|
|
6689
6756
|
async function loadReviewHistory(projectRoot) {
|
|
6690
6757
|
const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6691
6758
|
try {
|
|
@@ -6816,15 +6883,13 @@ ${specContent || "(No spec \u2014 review for general code quality)"}
|
|
|
6816
6883
|
${codeContext}`;
|
|
6817
6884
|
const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
|
|
6818
6885
|
console.log(import_chalk12.default.gray(" Pass 2/3: Implementation review..."));
|
|
6886
|
+
const specDigest = specContent && specContent.length > 600 ? specContent.slice(0, 600) + "\n... [spec truncated \u2014 see Pass 0/1 for full text]" : specContent || "(No spec)";
|
|
6819
6887
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6820
6888
|
const historyContext = buildHistoryContext(history);
|
|
6821
6889
|
const implPrompt = `Review the implementation details of this change.
|
|
6822
6890
|
|
|
6823
|
-
=== Feature Spec ===
|
|
6824
|
-
${
|
|
6825
|
-
|
|
6826
|
-
=== Code ===
|
|
6827
|
-
${codeContext}
|
|
6891
|
+
=== Feature Spec (digest \u2014 full spec was provided in Pass 0/1) ===
|
|
6892
|
+
${specDigest}
|
|
6828
6893
|
|
|
6829
6894
|
=== Architecture Review (Pass 1 \u2014 do NOT repeat these findings) ===
|
|
6830
6895
|
${archReview}
|
|
@@ -6833,11 +6898,8 @@ ${historyContext}`;
|
|
|
6833
6898
|
console.log(import_chalk12.default.gray(" Pass 3/3: Impact & complexity assessment..."));
|
|
6834
6899
|
const impactPrompt = `Assess the impact and complexity of this change.
|
|
6835
6900
|
|
|
6836
|
-
=== Feature Spec ===
|
|
6837
|
-
${
|
|
6838
|
-
|
|
6839
|
-
=== Code ===
|
|
6840
|
-
${codeContext}
|
|
6901
|
+
=== Feature Spec (digest) ===
|
|
6902
|
+
${specDigest}
|
|
6841
6903
|
|
|
6842
6904
|
=== Architecture Review (Pass 1 \u2014 do NOT repeat) ===
|
|
6843
6905
|
${archReview}
|
|
@@ -6911,8 +6973,8 @@ ${sep}
|
|
|
6911
6973
|
filesSection += `
|
|
6912
6974
|
|
|
6913
6975
|
=== ${filePath} ===
|
|
6914
|
-
${content.slice(0,
|
|
6915
|
-
if (content.length >
|
|
6976
|
+
${content.slice(0, DEFAULT_MAX_REVIEW_FILE_CHARS)}`;
|
|
6977
|
+
if (content.length > DEFAULT_MAX_REVIEW_FILE_CHARS) filesSection += `
|
|
6916
6978
|
... (truncated, ${content.length} chars total)`;
|
|
6917
6979
|
} catch {
|
|
6918
6980
|
filesSection += `
|