scai 0.1.127 → 0.1.128
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 +32 -4
- package/dist/agents/MainAgent.js +55 -42
- package/dist/agents/contextReviewStep.js +29 -68
- package/dist/agents/planTargetFilesStep.js +22 -8
- package/dist/agents/transformPlanGenStep.js +5 -7
- package/dist/index.js +107 -33
- package/dist/pipeline/modules/codeTransformModule.js +87 -106
- package/dist/utils/splitCodeIntoChunk.js +6 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,14 +12,42 @@ SCAI is your AI coding companion in the terminal. Stay focused on coding while S
|
|
|
12
12
|
> ⚠️ **Alpha Version Notice**
|
|
13
13
|
> If you have previously installed SCAI, please run:
|
|
14
14
|
>
|
|
15
|
-
>
|
|
15
|
+
> bash
|
|
16
16
|
> scai db reset && scai index start
|
|
17
|
-
>
|
|
17
|
+
>
|
|
18
18
|
>
|
|
19
19
|
> before using this version.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
## 🧠 Why SCAI?
|
|
24
|
+
|
|
25
|
+
SCAI is not just another AI tool — it's a **developer-first**, **privacy-focused**, and **local-first** coding companion. Here's why SCAI stands out:
|
|
26
|
+
|
|
27
|
+
### 🔐 **100% Local & Private by Design**
|
|
28
|
+
|
|
29
|
+
Unlike cloud-based AI tools, SCAI runs entirely on your machine. No data leaves your environment — making it ideal for **sensitive codebases** and **GDPR-compliant workflows**.
|
|
30
|
+
|
|
31
|
+
### 🧠 **Deep Code Understanding**
|
|
32
|
+
|
|
33
|
+
SCAI doesn't just parse code — it **understands** it. With background indexing, static analysis, and language-aware parsing, SCAI helps you explore, refactor, and debug with confidence.
|
|
34
|
+
|
|
35
|
+
### 📦 **No Token Costs or API Keys**
|
|
36
|
+
|
|
37
|
+
SCAI works offline, with **zero token usage**. You don’t pay for API calls or subscribe to cloud services. Just install and go.
|
|
38
|
+
|
|
39
|
+
### 🛠️ **Developer-Focused Toolset**
|
|
40
|
+
|
|
41
|
+
From commit message generation to architecture summaries, SCAI integrates directly into your workflow. It's built for developers, by developers.
|
|
42
|
+
|
|
43
|
+
### 🇩🇰 **Built in Denmark/EU**
|
|
44
|
+
|
|
45
|
+
SCAI is developed in the European Union, ensuring compliance with data protection laws and a focus on privacy-first development.
|
|
46
|
+
|
|
47
|
+
> ✅ SCAI is your **AI coding assistant that respects your privacy**, enhances your productivity, and works **entirely offline**.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
23
51
|
## 🗣️ Language Support (Important)
|
|
24
52
|
|
|
25
53
|
SCAI is currently **tested and validated only on the following languages**:
|
|
@@ -36,11 +64,11 @@ Other languages may work partially, but analysis quality, indexing accuracy, and
|
|
|
36
64
|
|
|
37
65
|
### 1️⃣ Install & Initialize
|
|
38
66
|
|
|
39
|
-
|
|
67
|
+
bash
|
|
40
68
|
npm install -g scai
|
|
41
69
|
scai init
|
|
42
70
|
scai index start
|
|
43
|
-
|
|
71
|
+
|
|
44
72
|
|
|
45
73
|
This initializes local models (recommended: `qwen3-coder:30b`) and starts indexing your code repository.
|
|
46
74
|
|
package/dist/agents/MainAgent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { builtInModules } from "../pipeline/registry/moduleRegistry.js";
|
|
2
2
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
3
|
+
import { planResolverStep } from "./planResolverStep.js";
|
|
3
4
|
import { infoPlanGen } from "./infoPlanGenStep.js";
|
|
4
5
|
import { understandIntentStep } from "./understandIntentStep.js";
|
|
5
6
|
import { structuralAnalysisStep } from "./structuralAnalysisStep.js";
|
|
@@ -19,17 +20,14 @@ function startTimer() {
|
|
|
19
20
|
return () => Date.now() - start;
|
|
20
21
|
}
|
|
21
22
|
function logLine(phase, step, ms, desc) {
|
|
22
|
-
// Clear current line (removes leftover spinner)
|
|
23
23
|
process.stdout.write('\r\x1b[K');
|
|
24
24
|
const suffix = desc ? ` — ${desc}` : "";
|
|
25
25
|
const timing = typeof ms === "number" ? ` (${ms}ms)` : "";
|
|
26
26
|
console.log(`[AGENT] ${phase} :: ${step}${suffix}${timing}`);
|
|
27
27
|
}
|
|
28
|
-
/** Helper to display messages to the user from the agent */
|
|
29
28
|
function userOutput(message) {
|
|
30
29
|
console.log(`[USER OUTPUT] ${message}`);
|
|
31
30
|
}
|
|
32
|
-
/** Helper to display messages with phase context */
|
|
33
31
|
function userPhaseOutput(phase, message) {
|
|
34
32
|
console.log(`[USER OUTPUT] [${phase}] ${message}`);
|
|
35
33
|
}
|
|
@@ -41,7 +39,7 @@ function resolveModuleForAction(action) {
|
|
|
41
39
|
/* ───────────────────────── agent ───────────────────────── */
|
|
42
40
|
export class MainAgent {
|
|
43
41
|
constructor(context) {
|
|
44
|
-
this.spinner = new Spinner();
|
|
42
|
+
this.spinner = new Spinner();
|
|
45
43
|
this.runCount = 0;
|
|
46
44
|
this.maxRuns = 2;
|
|
47
45
|
this.context = context;
|
|
@@ -50,26 +48,22 @@ export class MainAgent {
|
|
|
50
48
|
/* ───────────── step executor ───────────── */
|
|
51
49
|
async executeStep(step, input) {
|
|
52
50
|
const stop = startTimer();
|
|
53
|
-
|
|
54
|
-
input.context.currentStep = step;
|
|
55
|
-
}
|
|
51
|
+
this.context.currentStep = step;
|
|
56
52
|
const mod = resolveModuleForAction(step.action);
|
|
57
53
|
if (!mod) {
|
|
58
54
|
logLine("EXECUTE", step.action, stop(), "skipped (missing module)");
|
|
59
55
|
return {
|
|
60
56
|
query: input.query,
|
|
61
57
|
content: input.content,
|
|
62
|
-
data: { skipped: true }
|
|
63
|
-
context: input.context
|
|
58
|
+
data: { skipped: true }
|
|
64
59
|
};
|
|
65
60
|
}
|
|
66
61
|
try {
|
|
67
|
-
// Update spinner text for user
|
|
68
62
|
this.spinner.update(`Running step: ${step.action}`);
|
|
69
63
|
const output = await mod.run({
|
|
70
64
|
query: step.description ?? input.query,
|
|
71
65
|
content: input.data ?? input.content,
|
|
72
|
-
context:
|
|
66
|
+
context: this.context
|
|
73
67
|
});
|
|
74
68
|
if (!output) {
|
|
75
69
|
throw new Error(`Module "${mod.name}" returned empty output`);
|
|
@@ -77,8 +71,7 @@ export class MainAgent {
|
|
|
77
71
|
logLine("EXECUTE", step.action, stop());
|
|
78
72
|
return {
|
|
79
73
|
query: step.description ?? input.query,
|
|
80
|
-
data: output.data
|
|
81
|
-
context: input.context
|
|
74
|
+
data: output.data
|
|
82
75
|
};
|
|
83
76
|
}
|
|
84
77
|
catch (err) {
|
|
@@ -92,14 +85,12 @@ export class MainAgent {
|
|
|
92
85
|
const stopRun = startTimer();
|
|
93
86
|
logLine("RUN", `start #${this.runCount}`);
|
|
94
87
|
logInputOutput("GlobalContext (structured)", "input", this.context);
|
|
95
|
-
this.spinner.start();
|
|
96
|
-
/* BOOT */
|
|
88
|
+
this.spinner.start();
|
|
89
|
+
/* ================= BOOT ================= */
|
|
97
90
|
{
|
|
98
91
|
const t = startTimer();
|
|
99
92
|
await understandIntentStep.run({ context: this.context });
|
|
100
93
|
logLine("BOOT", "understandIntent", t());
|
|
101
|
-
// >>> TASK PERSISTENCE ADDITION <<<
|
|
102
|
-
// create a task immediately after boot
|
|
103
94
|
const db = getDbForRepo();
|
|
104
95
|
const now = new Date().toISOString();
|
|
105
96
|
const userQuery = this.context.initContext?.userQuery ?? "unknown query";
|
|
@@ -109,33 +100,35 @@ export class MainAgent {
|
|
|
109
100
|
`).run(userQuery, now, now);
|
|
110
101
|
this.taskId = result.lastInsertRowid;
|
|
111
102
|
logLine("TASK", `created task id=${this.taskId}"`);
|
|
103
|
+
logFolderCapsulesSummary(this.context);
|
|
112
104
|
}
|
|
113
|
-
|
|
114
|
-
// INFORMATION ACQUISITION PHASE
|
|
115
|
-
// Purpose: gather raw information, no interpretation
|
|
116
|
-
// =====================================================
|
|
105
|
+
/* ================= FAST-PATH CHECK ================= */
|
|
117
106
|
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
await planResolverStep.run(this.context);
|
|
108
|
+
const routing = this.context.analysis?.routingDecision;
|
|
109
|
+
if (routing?.decision === "final-answer" && routing.answer) {
|
|
110
|
+
logLine("ROUTING", "fastPathHit", undefined, "returning final answer early");
|
|
111
|
+
return {
|
|
112
|
+
query: this.query,
|
|
113
|
+
data: { finalAnswer: routing.answer, source: "planResolver" }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
121
116
|
}
|
|
117
|
+
/* ================= INFORMATION ACQUISITION ================= */
|
|
122
118
|
{
|
|
123
|
-
|
|
119
|
+
let t = startTimer();
|
|
120
|
+
await preFileSearchCheckStep(this.context);
|
|
121
|
+
logLine("PRECHECK", "preFileSearch", t());
|
|
122
|
+
t = startTimer();
|
|
124
123
|
await infoPlanGen.run(this.context);
|
|
125
124
|
logLine("PLAN", "infoPlanGen", t());
|
|
126
125
|
}
|
|
127
126
|
const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
128
|
-
let stepIO = {
|
|
129
|
-
query: this.query,
|
|
130
|
-
context: this.context
|
|
131
|
-
};
|
|
127
|
+
let stepIO = { query: this.query };
|
|
132
128
|
for (const step of infoPlan.steps.filter(s => s.groups?.includes("info"))) {
|
|
133
129
|
stepIO = await this.executeStep(step, stepIO);
|
|
134
130
|
}
|
|
135
|
-
|
|
136
|
-
// ANALYSIS PHASE
|
|
137
|
-
// Purpose: understand what we have and what is being asked
|
|
138
|
-
// =====================================================
|
|
131
|
+
/* ================= ANALYSIS PHASE ================= */
|
|
139
132
|
{
|
|
140
133
|
let t = startTimer();
|
|
141
134
|
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
@@ -158,10 +151,7 @@ export class MainAgent {
|
|
|
158
151
|
this.resetInitContextForLoop();
|
|
159
152
|
return this.run();
|
|
160
153
|
}
|
|
161
|
-
|
|
162
|
-
// TRANSFORM PHASE
|
|
163
|
-
// Purpose: produce concrete changes or artifacts
|
|
164
|
-
// =====================================================
|
|
154
|
+
/* ================= TRANSFORM PHASE ================= */
|
|
165
155
|
{
|
|
166
156
|
const t = startTimer();
|
|
167
157
|
await transformPlanGenStep.run(this.context);
|
|
@@ -171,10 +161,12 @@ export class MainAgent {
|
|
|
171
161
|
for (const step of transformPlan.steps.filter(s => s.groups?.includes("transform"))) {
|
|
172
162
|
stepIO = await this.executeStep(step, stepIO);
|
|
173
163
|
}
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
164
|
+
/* // ── OPTIONAL DEBUGGING ──
|
|
165
|
+
debugContext(this.context, {
|
|
166
|
+
step: "After transform plan generation",
|
|
167
|
+
note: "Does plan exist?"
|
|
168
|
+
}); */
|
|
169
|
+
/* ================= FINALIZE PHASE ================= */
|
|
178
170
|
{
|
|
179
171
|
const t = startTimer();
|
|
180
172
|
await finalPlanGenStep.run(this.context);
|
|
@@ -184,9 +176,12 @@ export class MainAgent {
|
|
|
184
176
|
for (const step of finalPlan.steps.filter(s => s.groups?.includes("finalize"))) {
|
|
185
177
|
stepIO = await this.executeStep(step, stepIO);
|
|
186
178
|
}
|
|
187
|
-
this.spinner.stop();
|
|
179
|
+
this.spinner.stop();
|
|
188
180
|
userOutput("All input/output logs can be found at ~/.scai/input_output.log");
|
|
189
181
|
logLine("RUN", "complete", stopRun());
|
|
182
|
+
// ───────────── LOG FINAL CONTEXT ─────────────
|
|
183
|
+
console.log("\n[DEBUG] Final MainAgent context:");
|
|
184
|
+
console.dir(this.context, { depth: null, colors: true });
|
|
190
185
|
return stepIO;
|
|
191
186
|
}
|
|
192
187
|
/* ───────────── helpers ───────────── */
|
|
@@ -196,3 +191,21 @@ export class MainAgent {
|
|
|
196
191
|
}
|
|
197
192
|
}
|
|
198
193
|
}
|
|
194
|
+
/* ───────────── FOLDER CAPSULES SUMMARY HELPER ───────────── */
|
|
195
|
+
function logFolderCapsulesSummary(context) {
|
|
196
|
+
if (!context.initContext?.folderCapsules?.length)
|
|
197
|
+
return;
|
|
198
|
+
const capsulesSummary = context.initContext.folderCapsules.map(fc => ({
|
|
199
|
+
path: fc.path,
|
|
200
|
+
fileCount: fc.stats?.fileCount ?? 0,
|
|
201
|
+
depth: fc.depth ?? 0,
|
|
202
|
+
confidence: fc.confidence ?? 0,
|
|
203
|
+
roles: fc.roles ?? [],
|
|
204
|
+
concerns: fc.concerns ?? []
|
|
205
|
+
}));
|
|
206
|
+
context.analysis ?? (context.analysis = {});
|
|
207
|
+
context.analysis.folderCapsulesSummary = capsulesSummary;
|
|
208
|
+
const humanReadable = capsulesSummary.map(fc => `- ${fc.path}: ${fc.fileCount} files, depth ${fc.depth}, confidence ${fc.confidence}`).join("\n");
|
|
209
|
+
context.analysis.folderCapsulesHuman = humanReadable;
|
|
210
|
+
logLine("BOOT", "folderCapsulesSummary", undefined, `📂 ${capsulesSummary.length} capsules summarized`);
|
|
211
|
+
}
|
|
@@ -2,92 +2,53 @@
|
|
|
2
2
|
import { generate } from "../lib/generate.js";
|
|
3
3
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
4
4
|
export async function contextReviewStep(context) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
.filter(f => context.plan.targetFiles.includes(f.path))
|
|
13
|
-
.map((f) => ({
|
|
14
|
-
path: f.path,
|
|
15
|
-
hasCode: Boolean(f.code),
|
|
16
|
-
code: f.code ?? null,
|
|
17
|
-
summary: f.summary ?? null,
|
|
18
|
-
}));
|
|
19
|
-
// ------------------------------
|
|
20
|
-
// Structural verification: only target files
|
|
21
|
-
// ------------------------------
|
|
22
|
-
const structuralProof = context.analysis?.structure
|
|
23
|
-
? {
|
|
24
|
-
fileCount: context.analysis.structure.files.filter(f => context.plan.targetFiles.includes(f.path)).length,
|
|
25
|
-
files: context.analysis.structure.files
|
|
26
|
-
.filter(f => context.plan.targetFiles.includes(f.path))
|
|
27
|
-
.map(f => ({
|
|
28
|
-
path: f.path,
|
|
29
|
-
hasCode: Boolean(authoritativeFiles.find(af => af.path === f.path)?.code),
|
|
30
|
-
})),
|
|
31
|
-
}
|
|
32
|
-
: null;
|
|
33
|
-
// ------------------------------
|
|
34
|
-
// Include combined architecture analysis
|
|
35
|
-
// ------------------------------
|
|
36
|
-
const combinedAnalysis = context.analysis?.combinedAnalysis ?? {};
|
|
37
|
-
const architectureSummary = combinedAnalysis.architectureSummary ?? "";
|
|
38
|
-
const hotspots = combinedAnalysis.hotspots ?? [];
|
|
5
|
+
const analysis = context.analysis;
|
|
6
|
+
if (!analysis)
|
|
7
|
+
throw new Error("[contextReviewStep] No analysis state available.");
|
|
8
|
+
const intent = analysis.intent ?? { intent: "", intentCategory: "", normalizedQuery: "", confidence: 0 };
|
|
9
|
+
const focus = analysis.focus ?? { relevantFiles: [], missingFiles: [], rationale: "" };
|
|
10
|
+
const fileAnalysis = analysis.fileAnalysis ?? {};
|
|
11
|
+
const planDecision = analysis.planSuggestion?.plan ? "planExists" : "needsPlan";
|
|
39
12
|
// ------------------------------
|
|
40
|
-
// Build prompt
|
|
13
|
+
// Build prompt using only conclusions from analysis
|
|
41
14
|
// ------------------------------
|
|
42
15
|
const prompt = `
|
|
43
|
-
You are a meta-reasoning agent
|
|
44
|
-
has sufficient information
|
|
16
|
+
You are a meta-reasoning agent.
|
|
17
|
+
Your job is to determine whether the agent has sufficient information
|
|
18
|
+
to fulfill the user's intent, based on the analysis that has already been performed.
|
|
45
19
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
complete and authoritative source code for that file.
|
|
49
|
-
- Do NOT request file contents again if "code" is present.
|
|
20
|
+
User Intent:
|
|
21
|
+
${JSON.stringify(intent, null, 2)}
|
|
50
22
|
|
|
51
|
-
|
|
52
|
-
${JSON.stringify(
|
|
23
|
+
Relevant Files:
|
|
24
|
+
${JSON.stringify(focus.relevantFiles, null, 2)}
|
|
25
|
+
Missing Files:
|
|
26
|
+
${JSON.stringify(focus.missingFiles, null, 2)}
|
|
27
|
+
Rationale:
|
|
28
|
+
${focus.rationale ?? "No rationale provided."}
|
|
53
29
|
|
|
54
|
-
|
|
55
|
-
${JSON.stringify(
|
|
30
|
+
File-level Semantic Analysis:
|
|
31
|
+
${JSON.stringify(fileAnalysis, null, 2)}
|
|
56
32
|
|
|
57
|
-
|
|
58
|
-
${JSON.stringify(structuralProof, null, 2)}
|
|
33
|
+
Plan Suggestion Status: ${planDecision}
|
|
59
34
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
Your task:
|
|
66
|
-
1. Determine whether the available information is sufficient to fulfill the user intent.
|
|
67
|
-
2. If NOT sufficient, explicitly list what is missing.
|
|
68
|
-
3. Output STRICT JSON with the following shape:
|
|
35
|
+
Question:
|
|
36
|
+
Based on the above, can the agent proceed with the user's request?
|
|
37
|
+
Output STRICT JSON with shape:
|
|
69
38
|
|
|
70
39
|
{
|
|
71
40
|
"decision": "loopAgain" | "stop",
|
|
72
|
-
"reason": "string",
|
|
73
|
-
"missing": string[]
|
|
41
|
+
"reason": "string explaining why or why not",
|
|
42
|
+
"missing": string[] // any additional analysis or files needed
|
|
74
43
|
}
|
|
75
|
-
|
|
76
|
-
Rules:
|
|
77
|
-
- If the intent involves modifying, commenting, refactoring, or analyzing code,
|
|
78
|
-
and at least one relevant file includes full source code, the context SHOULD
|
|
79
|
-
be considered sufficient.
|
|
80
|
-
- Do NOT request information that is already present in the authoritative files.
|
|
81
44
|
`.trim();
|
|
82
|
-
|
|
83
|
-
// Call LLM
|
|
84
|
-
// ------------------------------
|
|
45
|
+
logInputOutput("contextReviewStep", "output", prompt);
|
|
85
46
|
const ai = await generate({
|
|
86
47
|
query: context.initContext?.userQuery ?? '',
|
|
87
48
|
content: prompt,
|
|
88
49
|
});
|
|
89
50
|
const text = typeof ai.data === "string" ? ai.data : JSON.stringify(ai.data);
|
|
90
|
-
logInputOutput("
|
|
51
|
+
logInputOutput("contextReviewStep", "output", text);
|
|
91
52
|
// ------------------------------
|
|
92
53
|
// Parse JSON or fallback
|
|
93
54
|
// ------------------------------
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
2
2
|
export const planTargetFilesStep = {
|
|
3
3
|
name: "planTargetFilesStep",
|
|
4
|
-
description: "Sync files from analysis.focus into plan.targetFiles. Ensures only workingFiles are moved and does not redo intent or risks.",
|
|
4
|
+
description: "Sync relevant files from analysis.focus into plan.targetFiles, skipping irrelevant files. Ensures only workingFiles are moved and does not redo intent or risks.",
|
|
5
5
|
groups: ["analysis"],
|
|
6
6
|
run: async (input) => {
|
|
7
|
+
var _a;
|
|
7
8
|
const context = input.context;
|
|
8
9
|
const query = input.query ?? "";
|
|
9
10
|
if (!context) {
|
|
@@ -14,8 +15,9 @@ export const planTargetFilesStep = {
|
|
|
14
15
|
logInputOutput("planTargetFilesStep", "output", output.data);
|
|
15
16
|
return output;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
const focusFiles =
|
|
18
|
+
const analysis = context.analysis;
|
|
19
|
+
const focusFiles = analysis?.focus?.relevantFiles ?? [];
|
|
20
|
+
const fileAnalysisMap = analysis?.fileAnalysis ?? {};
|
|
19
21
|
if (!focusFiles.length) {
|
|
20
22
|
const output = {
|
|
21
23
|
query,
|
|
@@ -26,12 +28,23 @@ export const planTargetFilesStep = {
|
|
|
26
28
|
}
|
|
27
29
|
// Ensure plan exists
|
|
28
30
|
context.plan || (context.plan = {});
|
|
29
|
-
// Move relevant files to plan.targetFiles, only if they exist in workingFiles
|
|
30
31
|
const workingFilePaths = new Set(context.workingFiles?.map(f => f.path) ?? []);
|
|
31
32
|
const targetFiles = new Set(context.plan.targetFiles ?? []);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const skippedFiles = [];
|
|
34
|
+
// Ensure discardedFiles array exists
|
|
35
|
+
if (context.analysis?.focus) {
|
|
36
|
+
(_a = context.analysis.focus).discardedFiles || (_a.discardedFiles = []);
|
|
37
|
+
}
|
|
38
|
+
// Add only non-irrelevant files
|
|
39
|
+
focusFiles.forEach(filePath => {
|
|
40
|
+
const analysisEntry = fileAnalysisMap[filePath];
|
|
41
|
+
if (analysisEntry?.intent === "irrelevant") {
|
|
42
|
+
skippedFiles.push(filePath);
|
|
43
|
+
context.analysis.focus.discardedFiles.push(filePath); // add to discardedFiles
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (workingFilePaths.has(filePath)) {
|
|
47
|
+
targetFiles.add(filePath);
|
|
35
48
|
}
|
|
36
49
|
});
|
|
37
50
|
context.plan.targetFiles = Array.from(targetFiles);
|
|
@@ -39,7 +52,8 @@ export const planTargetFilesStep = {
|
|
|
39
52
|
query,
|
|
40
53
|
data: {
|
|
41
54
|
movedFiles: Array.from(targetFiles),
|
|
42
|
-
|
|
55
|
+
skippedFiles,
|
|
56
|
+
notes: `Focus files synced to plan.targetFiles. ${skippedFiles.length} irrelevant file(s) skipped and added to discardedFiles.`,
|
|
43
57
|
},
|
|
44
58
|
};
|
|
45
59
|
logInputOutput("planTargetFilesStep", "output", output.data);
|
|
@@ -31,22 +31,19 @@ or modifications in the system to achieve the intended task.
|
|
|
31
31
|
Intent / task description:
|
|
32
32
|
${intentText}
|
|
33
33
|
|
|
34
|
-
If the intent indicates that this is NOT a coding, refactoring, or inline commenting task,
|
|
35
|
-
then return an empty plan object with an empty "steps" array:
|
|
36
|
-
{ "steps": [] }
|
|
37
|
-
|
|
38
34
|
Allowed actions (transformation only):
|
|
39
35
|
${actionsJson}
|
|
40
36
|
|
|
41
37
|
Task category:
|
|
42
38
|
${intentCategory}
|
|
43
39
|
|
|
44
|
-
Folder structure:
|
|
45
|
-
${context.analysis.folderCapsulesHuman ?? ''}
|
|
46
|
-
|
|
47
40
|
Existing relevant files:
|
|
48
41
|
${JSON.stringify(context.analysis.focus?.relevantFiles ?? {}, null, 2)}
|
|
49
42
|
|
|
43
|
+
If the intent indicates that this is NOT a coding, refactoring, or inline commenting task,
|
|
44
|
+
then return an empty plan object with an empty "steps" array:
|
|
45
|
+
{ "steps": [] }
|
|
46
|
+
|
|
50
47
|
Only perform transformations that are safe based on the existing analysis.
|
|
51
48
|
|
|
52
49
|
⚡ Phase guidance:
|
|
@@ -74,6 +71,7 @@ Return a strictly valid JSON plan:
|
|
|
74
71
|
let plan = null;
|
|
75
72
|
// --- Parse strategy ---
|
|
76
73
|
if (cleaned.data && typeof cleaned.data === 'object') {
|
|
74
|
+
process.stdout.write("\r\x1b[K");
|
|
77
75
|
console.log('[transformPlanGen][debug] Using parsed JSON from cleanupModule');
|
|
78
76
|
plan = cleaned.data;
|
|
79
77
|
}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ const shellQuote = require('shell-quote');
|
|
|
7
7
|
import { createProgram as cmdFactory, withContext } from './commands/factory.js';
|
|
8
8
|
import { runAskCommand } from './commands/AskCmd.js';
|
|
9
9
|
import { setRl } from './commands/ReadlineSingleton.js';
|
|
10
|
-
const program = cmdFactory();
|
|
10
|
+
const program = cmdFactory();
|
|
11
11
|
let inShell = false;
|
|
12
12
|
const customCommands = {};
|
|
13
13
|
// =====================================================
|
|
@@ -64,10 +64,15 @@ customCommands.quit = customCommands.exit;
|
|
|
64
64
|
export function registerCommand(name, fn) {
|
|
65
65
|
customCommands[name] = fn;
|
|
66
66
|
}
|
|
67
|
+
// =====================================================
|
|
68
|
+
// SHELL
|
|
69
|
+
// =====================================================
|
|
67
70
|
async function startShell() {
|
|
68
71
|
if (inShell)
|
|
69
72
|
return;
|
|
70
73
|
inShell = true;
|
|
74
|
+
// Ensure cursor is visible & blinking
|
|
75
|
+
process.stdout.write('\x1b[?25h');
|
|
71
76
|
const rl = readline.createInterface({
|
|
72
77
|
input: process.stdin,
|
|
73
78
|
output: process.stdout,
|
|
@@ -75,70 +80,139 @@ async function startShell() {
|
|
|
75
80
|
historySize: 200,
|
|
76
81
|
});
|
|
77
82
|
setRl(rl);
|
|
83
|
+
// Explicit """ multiline
|
|
84
|
+
let multilineBuffer = null;
|
|
85
|
+
// Normal buffered input (enter-to-execute)
|
|
86
|
+
let inputBuffer = [];
|
|
87
|
+
// --- Buffered paste flush to fix "double Enter" issue
|
|
88
|
+
let flushHandle = null;
|
|
89
|
+
const flushInputBuffer = () => {
|
|
90
|
+
if (flushHandle)
|
|
91
|
+
return; // already scheduled
|
|
92
|
+
flushHandle = setImmediate(async () => {
|
|
93
|
+
flushHandle = null;
|
|
94
|
+
const fullQuery = inputBuffer.join('\n').trim();
|
|
95
|
+
inputBuffer = [];
|
|
96
|
+
if (fullQuery) {
|
|
97
|
+
await runQuery(fullQuery);
|
|
98
|
+
}
|
|
99
|
+
rl.prompt();
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const showCursor = () => {
|
|
103
|
+
process.stdout.write('\x1b[?25h');
|
|
104
|
+
};
|
|
78
105
|
rl.prompt();
|
|
106
|
+
showCursor();
|
|
79
107
|
rl.on('line', async (line) => {
|
|
80
108
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
109
|
+
showCursor();
|
|
110
|
+
// =====================================================
|
|
111
|
+
// Explicit multiline (""" … """)
|
|
112
|
+
// =====================================================
|
|
113
|
+
if (multilineBuffer !== null) {
|
|
114
|
+
if (line.trim() === '"""') {
|
|
115
|
+
const fullQuery = multilineBuffer.join('\n');
|
|
116
|
+
multilineBuffer = null;
|
|
117
|
+
await runQuery(fullQuery);
|
|
118
|
+
rl.prompt();
|
|
119
|
+
showCursor();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
multilineBuffer.push(line);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (line.trim() === '"""') {
|
|
126
|
+
multilineBuffer = [];
|
|
127
|
+
console.log('(multiline input — end with """)');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// =====================================================
|
|
131
|
+
// Empty line = EXECUTE buffered input
|
|
132
|
+
// =====================================================
|
|
133
|
+
if (line.trim() === '') {
|
|
134
|
+
const fullQuery = inputBuffer.join('\n').trim();
|
|
135
|
+
inputBuffer = [];
|
|
136
|
+
if (fullQuery) {
|
|
137
|
+
await runQuery(fullQuery);
|
|
138
|
+
}
|
|
83
139
|
rl.prompt();
|
|
140
|
+
showCursor();
|
|
84
141
|
return;
|
|
85
142
|
}
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
child
|
|
143
|
+
// =====================================================
|
|
144
|
+
// Shell command
|
|
145
|
+
// =====================================================
|
|
146
|
+
if (line.trim().startsWith('!')) {
|
|
147
|
+
const child = spawn(line.trim().slice(1), {
|
|
148
|
+
shell: true,
|
|
149
|
+
stdio: 'inherit',
|
|
150
|
+
});
|
|
151
|
+
child.on('exit', () => {
|
|
152
|
+
rl.prompt();
|
|
153
|
+
showCursor();
|
|
154
|
+
});
|
|
91
155
|
return;
|
|
92
156
|
}
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
157
|
+
// =====================================================
|
|
158
|
+
// Slash commands
|
|
159
|
+
// =====================================================
|
|
160
|
+
if (line.trim().startsWith('/')) {
|
|
161
|
+
const argvParts = shellQuote
|
|
162
|
+
.parse(line.trim().slice(1))
|
|
163
|
+
.map((tok) => typeof tok === 'object'
|
|
164
|
+
? tok.op ?? tok.pattern ?? ''
|
|
165
|
+
: String(tok))
|
|
97
166
|
.filter(Boolean);
|
|
98
167
|
const cmdName = argvParts[0];
|
|
99
168
|
if (customCommands[cmdName]) {
|
|
100
169
|
await customCommands[cmdName]();
|
|
101
170
|
}
|
|
102
171
|
else {
|
|
103
|
-
|
|
104
|
-
await program.parseAsync(argvParts, { from: 'user' });
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
console.error('Command error:', err instanceof Error ? err.message : err);
|
|
108
|
-
}
|
|
172
|
+
await program.parseAsync(argvParts, { from: 'user' });
|
|
109
173
|
}
|
|
110
174
|
rl.prompt();
|
|
175
|
+
showCursor();
|
|
111
176
|
return;
|
|
112
177
|
}
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
178
|
+
// =====================================================
|
|
179
|
+
// Otherwise → accumulate input
|
|
180
|
+
// =====================================================
|
|
181
|
+
inputBuffer.push(line);
|
|
182
|
+
flushInputBuffer(); // schedule auto-flush after paste ends
|
|
116
183
|
}
|
|
117
184
|
catch (err) {
|
|
118
185
|
console.error('REPL error:', err instanceof Error ? err.stack : err);
|
|
119
186
|
rl.prompt();
|
|
187
|
+
showCursor();
|
|
120
188
|
}
|
|
121
189
|
});
|
|
122
|
-
rl.on('close', () => {
|
|
123
|
-
|
|
190
|
+
rl.on('close', () => {
|
|
191
|
+
showCursor();
|
|
192
|
+
console.log('Bye!');
|
|
193
|
+
process.exit(0);
|
|
194
|
+
});
|
|
195
|
+
process.on('SIGINT', () => {
|
|
196
|
+
console.log('\nExiting REPL...');
|
|
197
|
+
showCursor();
|
|
198
|
+
rl.close();
|
|
199
|
+
});
|
|
200
|
+
process.on('exit', showCursor);
|
|
124
201
|
}
|
|
125
202
|
// ---------------- Main -----------------
|
|
126
203
|
async function main() {
|
|
127
204
|
process.on('unhandledRejection', (reason) => console.error('Unhandled Rejection:', reason));
|
|
128
|
-
process.on('uncaughtException', (err) => {
|
|
205
|
+
process.on('uncaughtException', (err) => {
|
|
206
|
+
console.error('Uncaught Exception:', err.stack ?? err);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
129
209
|
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
130
210
|
const args = process.argv.slice(2);
|
|
131
|
-
|
|
132
|
-
|
|
211
|
+
if (isInteractive &&
|
|
212
|
+
(args.length === 0 || (args.length === 1 && args[0] === 'shell'))) {
|
|
133
213
|
await startShell();
|
|
134
214
|
return;
|
|
135
215
|
}
|
|
136
|
-
|
|
137
|
-
await program.parseAsync(process.argv);
|
|
138
|
-
}
|
|
139
|
-
catch (err) {
|
|
140
|
-
console.error('CLI Error:', err instanceof Error ? err.message : err);
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
216
|
+
await program.parseAsync(process.argv);
|
|
143
217
|
}
|
|
144
218
|
main();
|
|
@@ -2,53 +2,36 @@ import { generate } from "../../lib/generate.js";
|
|
|
2
2
|
import { cleanupModule } from "./cleanupModule.js";
|
|
3
3
|
import { logInputOutput } from "../../utils/promptLogHelper.js";
|
|
4
4
|
import { splitCodeIntoChunks, countTokens } from "../../utils/splitCodeIntoChunk.js";
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
* Remove leading/trailing markdown code fences.
|
|
9
|
-
*/
|
|
5
|
+
// ───────────── Token limits ─────────────
|
|
6
|
+
const SINGLE_SHOT_TOKEN_LIMIT = 1500; // keep large enough for small files
|
|
7
|
+
const CHUNK_TOKEN_LIMIT = 800; // chunked transformation limit
|
|
10
8
|
function stripCodeFences(text) {
|
|
11
9
|
const lines = text.split("\n");
|
|
12
|
-
while (lines.length && /^```/.test(lines[0].trim()))
|
|
10
|
+
while (lines.length && /^```/.test(lines[0].trim()))
|
|
13
11
|
lines.shift();
|
|
14
|
-
|
|
15
|
-
while (lines.length && /^```/.test(lines[lines.length - 1].trim())) {
|
|
12
|
+
while (lines.length && /^```/.test(lines[lines.length - 1].trim()))
|
|
16
13
|
lines.pop();
|
|
17
|
-
}
|
|
18
14
|
return lines.join("\n");
|
|
19
15
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Heuristic: decide whether chunk output looks unsafe / non-code.
|
|
22
|
-
*/
|
|
23
16
|
function isSuspiciousChunkOutput(text, originalChunk) {
|
|
24
17
|
const trimmed = text.trim();
|
|
25
18
|
if (!trimmed)
|
|
26
19
|
return true;
|
|
27
|
-
|
|
28
|
-
if (/here is|transformed|updated code|explanation/i.test(trimmed)) {
|
|
20
|
+
if (/here is|transformed|updated code|explanation/i.test(trimmed))
|
|
29
21
|
return true;
|
|
30
|
-
}
|
|
31
|
-
// JSON-ish output (we do NOT want JSON in chunk mode)
|
|
32
|
-
if (trimmed.startsWith("{") &&
|
|
33
|
-
trimmed.endsWith("}")) {
|
|
22
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}"))
|
|
34
23
|
return true;
|
|
35
|
-
|
|
36
|
-
// Extremely short compared to original
|
|
37
|
-
if (trimmed.length < originalChunk.trim().length * 0.3) {
|
|
24
|
+
if (trimmed.length < originalChunk.trim().length * 0.3)
|
|
38
25
|
return true;
|
|
39
|
-
}
|
|
40
26
|
return false;
|
|
41
27
|
}
|
|
42
28
|
export const codeTransformModule = {
|
|
43
29
|
name: "codeTransform",
|
|
44
|
-
description: "Transforms a single file specified in the current plan step based on user instruction.
|
|
45
|
-
"Outputs full rewritten file content (materialized state).",
|
|
30
|
+
description: "Transforms a single file specified in the current plan step based on user instruction.",
|
|
46
31
|
groups: ["transform"],
|
|
47
32
|
run: async (input) => {
|
|
48
|
-
var _a, _b;
|
|
49
|
-
const query = typeof input.query === "string"
|
|
50
|
-
? input.query
|
|
51
|
-
: String(input.query ?? "");
|
|
33
|
+
var _a, _b, _c, _d;
|
|
34
|
+
const query = typeof input.query === "string" ? input.query : String(input.query ?? "");
|
|
52
35
|
const context = input.context;
|
|
53
36
|
if (!context) {
|
|
54
37
|
return { query, data: { files: [], errors: ["No context provided"] } };
|
|
@@ -57,26 +40,23 @@ export const codeTransformModule = {
|
|
|
57
40
|
const step = context.currentStep;
|
|
58
41
|
const targetFile = step?.targetFile;
|
|
59
42
|
if (!targetFile) {
|
|
60
|
-
return {
|
|
61
|
-
query,
|
|
62
|
-
data: { files: [], errors: ["No targetFile specified in current plan step"] },
|
|
63
|
-
};
|
|
43
|
+
return { query, data: { files: [], errors: ["No targetFile specified in current plan step"] } };
|
|
64
44
|
}
|
|
65
45
|
const file = workingFiles.find(f => f.path === targetFile);
|
|
66
46
|
if (!file || typeof file.code !== "string") {
|
|
67
|
-
return {
|
|
68
|
-
query,
|
|
69
|
-
data: { files: [], errors: [`Target file not found or missing code: ${targetFile}`] },
|
|
70
|
-
};
|
|
47
|
+
return { query, data: { files: [], errors: [`Target file not found or missing code: ${targetFile}`] } };
|
|
71
48
|
}
|
|
72
49
|
const normalizedQuery = context.analysis?.intent?.normalizedQuery ?? query;
|
|
73
50
|
const outputs = [];
|
|
74
51
|
const perFileErrors = [];
|
|
75
52
|
const tokenCount = countTokens(file.code);
|
|
76
|
-
//
|
|
77
|
-
// 🔹 PATH 1 — SMALL FILE (JSON, strict)
|
|
78
|
-
// =========================================================================
|
|
53
|
+
// ───────────── SMALL FILE ─────────────
|
|
79
54
|
if (tokenCount <= SINGLE_SHOT_TOKEN_LIMIT) {
|
|
55
|
+
logInputOutput("codeTransform", "output", {
|
|
56
|
+
file: file.path,
|
|
57
|
+
tokenCount,
|
|
58
|
+
message: "Starting small file transformation",
|
|
59
|
+
});
|
|
80
60
|
const prompt = `
|
|
81
61
|
You are a precise code transformation assistant.
|
|
82
62
|
|
|
@@ -104,105 +84,106 @@ JSON schema:
|
|
|
104
84
|
}
|
|
105
85
|
`.trim();
|
|
106
86
|
try {
|
|
87
|
+
logInputOutput("codeTransform", "output", { file: file.path, message: "Sending prompt to LLM" });
|
|
107
88
|
const llmResponse = await generate({ content: prompt, query });
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
89
|
+
logInputOutput("codeTransform", "output", {
|
|
90
|
+
file: file.path,
|
|
91
|
+
message: "Received LLM response",
|
|
92
|
+
responseSnippet: String(llmResponse.data ?? "").slice(0, 200),
|
|
111
93
|
});
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const out = Array.isArray(structured.files)
|
|
94
|
+
const cleaned = await cleanupModule.run({ query, content: llmResponse.data });
|
|
95
|
+
const structured = cleaned.data;
|
|
96
|
+
const out = Array.isArray(structured?.files)
|
|
116
97
|
? structured.files.find((f) => f.filePath === file.path)
|
|
117
98
|
: null;
|
|
118
|
-
if (!out || typeof out.content !== "string"
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
outputs.push({
|
|
123
|
-
filePath: file.path,
|
|
124
|
-
content: out.content,
|
|
125
|
-
notes: out.notes,
|
|
126
|
-
});
|
|
99
|
+
if (!out || typeof out.content !== "string") {
|
|
100
|
+
return { query, data: { files: [], errors: [`Model did not return valid content for ${file.path}`] } };
|
|
127
101
|
}
|
|
102
|
+
const finalContent = out.content.trim() ? out.content : file.code;
|
|
103
|
+
outputs.push({ filePath: file.path, content: finalContent, notes: out.notes });
|
|
128
104
|
perFileErrors.push(...(structured.errors ?? []));
|
|
105
|
+
context.execution || (context.execution = {});
|
|
106
|
+
(_a = context.execution).codeTransformArtifacts || (_a.codeTransformArtifacts = { files: [] });
|
|
107
|
+
context.execution.codeTransformArtifacts.files = context.execution.codeTransformArtifacts.files.filter(f => f.filePath !== file.path);
|
|
108
|
+
context.execution.codeTransformArtifacts.files.push({
|
|
109
|
+
filePath: file.path,
|
|
110
|
+
content: finalContent,
|
|
111
|
+
notes: perFileErrors.length ? perFileErrors.join("; ") : undefined,
|
|
112
|
+
});
|
|
113
|
+
context.plan || (context.plan = {});
|
|
114
|
+
(_b = context.plan).touchedFiles || (_b.touchedFiles = []);
|
|
115
|
+
if (!context.plan.touchedFiles.includes(file.path))
|
|
116
|
+
context.plan.touchedFiles.push(file.path);
|
|
117
|
+
const output = { query, data: { files: outputs, errors: perFileErrors } };
|
|
118
|
+
logInputOutput("codeTransform", "output", context.execution.codeTransformArtifacts.files);
|
|
119
|
+
return output;
|
|
129
120
|
}
|
|
130
121
|
catch (err) {
|
|
131
|
-
return {
|
|
132
|
-
query,
|
|
133
|
-
data: { files: [], errors: [`LLM call or parsing failed: ${err.message}`] },
|
|
134
|
-
};
|
|
122
|
+
return { query, data: { files: [], errors: [`LLM call or cleanup failed: ${err.message}`] } };
|
|
135
123
|
}
|
|
136
124
|
}
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
125
|
+
// ───────────── LARGE FILE (chunked) ─────────────
|
|
126
|
+
const chunks = splitCodeIntoChunks(file.code, CHUNK_TOKEN_LIMIT);
|
|
127
|
+
const transformedChunks = [];
|
|
128
|
+
logInputOutput("codeTransform", "output", { file: file.path, chunkCount: chunks.length, message: "Starting chunked transformation" });
|
|
129
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
130
|
+
const chunk = chunks[i];
|
|
131
|
+
// ✅ Add user-facing console log here:
|
|
132
|
+
process.stdout.write("\r\x1b[K");
|
|
133
|
+
console.log(` - Processing chunk ${i + 1} of ${chunks.length} for ${file.path}...`);
|
|
134
|
+
logInputOutput("codeTransform", "output", {
|
|
135
|
+
file: file.path,
|
|
136
|
+
chunkIndex: i + 1,
|
|
137
|
+
chunkLength: chunk.length,
|
|
138
|
+
message: "Processing chunk",
|
|
139
|
+
});
|
|
140
|
+
const prompt = `
|
|
146
141
|
You are a precise code transformation assistant.
|
|
147
142
|
|
|
148
143
|
User instruction (normalized):
|
|
149
144
|
${normalizedQuery}
|
|
150
145
|
|
|
151
|
-
You are given ONE CHUNK of a larger file.
|
|
152
|
-
|
|
153
146
|
Rules:
|
|
154
147
|
- Apply the instruction ONLY if relevant to this chunk.
|
|
155
148
|
- If no change is needed, return the chunk UNCHANGED.
|
|
156
|
-
-
|
|
157
|
-
- Do NOT reference other chunks.
|
|
158
|
-
- Return ONLY code. No JSON. No explanations. No markdown fences.
|
|
149
|
+
- Return ONLY code.
|
|
159
150
|
|
|
160
151
|
FILE: ${file.path}
|
|
161
152
|
CHUNK ${i + 1} / ${chunks.length}
|
|
162
153
|
---
|
|
163
154
|
${chunk}
|
|
164
155
|
`.trim();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
: String(llmResponse.data ?? "");
|
|
171
|
-
const stripped = stripCodeFences(raw);
|
|
172
|
-
if (isSuspiciousChunkOutput(stripped, chunk)) {
|
|
173
|
-
transformedChunks.push(chunk);
|
|
174
|
-
perFileErrors.push(`Chunk ${i + 1} suspicious output; original preserved.`);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
transformedChunks.push(stripped);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
156
|
+
try {
|
|
157
|
+
const llmResponse = await generate({ content: prompt, query });
|
|
158
|
+
const raw = String(llmResponse.data ?? "");
|
|
159
|
+
const stripped = stripCodeFences(raw);
|
|
160
|
+
if (isSuspiciousChunkOutput(stripped, chunk)) {
|
|
181
161
|
transformedChunks.push(chunk);
|
|
182
|
-
perFileErrors.push(`Chunk ${i + 1}
|
|
162
|
+
perFileErrors.push(`Chunk ${i + 1} suspicious; original preserved.`);
|
|
163
|
+
logInputOutput("codeTransform", "output", { file: file.path, chunkIndex: i + 1, message: "Suspicious output, original chunk preserved" });
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
transformedChunks.push(stripped);
|
|
167
|
+
logInputOutput("codeTransform", "output", { file: file.path, chunkIndex: i + 1, message: "Chunk transformed successfully" });
|
|
183
168
|
}
|
|
184
169
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
170
|
+
catch (err) {
|
|
171
|
+
transformedChunks.push(chunk);
|
|
172
|
+
perFileErrors.push(`Chunk ${i + 1} failed; original preserved. Error: ${err.message}`);
|
|
173
|
+
logInputOutput("codeTransform", "output", { file: file.path, chunkIndex: i + 1, error: err.message, message: "Chunk transformation failed, original preserved" });
|
|
174
|
+
}
|
|
189
175
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// =========================================================================
|
|
176
|
+
logInputOutput("codeTransform", "output", { file: file.path, message: "Finished all chunks", totalChunks: chunks.length });
|
|
177
|
+
outputs.push({ filePath: file.path, content: transformedChunks.join("\n") });
|
|
193
178
|
context.execution || (context.execution = {});
|
|
194
|
-
(
|
|
179
|
+
(_c = context.execution).codeTransformArtifacts || (_c.codeTransformArtifacts = { files: [] });
|
|
195
180
|
context.execution.codeTransformArtifacts.files.push(...outputs);
|
|
196
181
|
context.plan || (context.plan = {});
|
|
197
|
-
(
|
|
198
|
-
if (!context.plan.touchedFiles.includes(file.path))
|
|
182
|
+
(_d = context.plan).touchedFiles || (_d.touchedFiles = []);
|
|
183
|
+
if (!context.plan.touchedFiles.includes(file.path))
|
|
199
184
|
context.plan.touchedFiles.push(file.path);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
query,
|
|
203
|
-
data: { files: outputs, errors: perFileErrors },
|
|
204
|
-
};
|
|
205
|
-
logInputOutput("codeTransform", "output", output.data);
|
|
185
|
+
const output = { query, data: { files: outputs, errors: perFileErrors } };
|
|
186
|
+
logInputOutput("codeTransform", "output", context.execution.codeTransformArtifacts.files);
|
|
206
187
|
return output;
|
|
207
188
|
},
|
|
208
189
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { encode } from 'gpt-3-encoder';
|
|
2
|
-
export function splitCodeIntoChunks(text, softLimit =
|
|
3
|
-
|
|
2
|
+
export function splitCodeIntoChunks(text, softLimit = 800, // reduced from 1500
|
|
3
|
+
hardLimitMultiplier = 1.5 // reduced from 2, so hard limit ~1200
|
|
4
|
+
) {
|
|
5
|
+
const hardLimit = Math.floor(softLimit * hardLimitMultiplier);
|
|
4
6
|
const lines = text.split('\n');
|
|
5
7
|
const chunks = [];
|
|
6
8
|
let currentChunkLines = [];
|
|
@@ -59,15 +61,13 @@ export function splitCodeIntoChunks(text, softLimit = 1500, hardLimitMultiplier
|
|
|
59
61
|
globalBraceDepth = Math.max(0, globalBraceDepth - 1);
|
|
60
62
|
if (inFunction) {
|
|
61
63
|
functionBraceDepth = Math.max(0, functionBraceDepth - 1);
|
|
62
|
-
if (functionBraceDepth === 0)
|
|
64
|
+
if (functionBraceDepth === 0)
|
|
63
65
|
justClosedFunction = true;
|
|
64
|
-
}
|
|
65
66
|
}
|
|
66
67
|
if (inTryChain) {
|
|
67
68
|
tryBraceDepth = Math.max(0, tryBraceDepth - 1);
|
|
68
|
-
if (tryBraceDepth === 0)
|
|
69
|
+
if (tryBraceDepth === 0)
|
|
69
70
|
justClosedTryBlock = true;
|
|
70
|
-
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
else if (char === '(') {
|
|
@@ -87,7 +87,6 @@ export function splitCodeIntoChunks(text, softLimit = 1500, hardLimitMultiplier
|
|
|
87
87
|
inFunction = false;
|
|
88
88
|
if (justClosedTryBlock) {
|
|
89
89
|
inTryBlock = false;
|
|
90
|
-
// Only close the chain if we've already seen a handler
|
|
91
90
|
if (tryChainHasHandler) {
|
|
92
91
|
inTryChain = false;
|
|
93
92
|
tryChainHasHandler = false;
|