@lasso-ai/cli 1.0.13 → 1.0.15
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/dist/cli/agent.js +68 -16
- package/dist/cli/bridge.js +19 -3
- package/package.json +1 -1
package/dist/cli/agent.js
CHANGED
|
@@ -49,17 +49,26 @@ async function sourceFiles(directory) {
|
|
|
49
49
|
return files;
|
|
50
50
|
}
|
|
51
51
|
async function contextFor(cwd, element) {
|
|
52
|
-
const needle = element.
|
|
52
|
+
const needle = element.label.replace(/^[^.#]+[.#]?/, "");
|
|
53
53
|
const hintedPath = element.sourceHint?.split(":")[0];
|
|
54
54
|
const sourceFile = hintedPath ? node_path_1.default.basename(hintedPath) : "";
|
|
55
55
|
if (hintedPath) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
const candidates = [
|
|
57
|
+
node_path_1.default.isAbsolute(hintedPath) ? hintedPath : node_path_1.default.resolve(cwd, hintedPath),
|
|
58
|
+
];
|
|
59
|
+
const normalizedHint = hintedPath.replace(/\\/g, "/");
|
|
60
|
+
const srcMarker = "/src/";
|
|
61
|
+
const srcIndex = normalizedHint.lastIndexOf(srcMarker);
|
|
62
|
+
if (srcIndex >= 0)
|
|
63
|
+
candidates.push(node_path_1.default.join(cwd, normalizedHint.slice(srcIndex + 1)));
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
try {
|
|
66
|
+
const content = await promises_1.default.readFile(candidate, "utf8");
|
|
67
|
+
return `FILE: ${node_path_1.default.relative(cwd, candidate)}\n${content.slice(0, 16000)}`;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// The runtime source hint can point to a different checkout.
|
|
71
|
+
}
|
|
63
72
|
}
|
|
64
73
|
}
|
|
65
74
|
const files = await sourceFiles(cwd);
|
|
@@ -82,17 +91,60 @@ async function contextFor(cwd, element) {
|
|
|
82
91
|
}
|
|
83
92
|
return snippets.join("\n\n---\n\n");
|
|
84
93
|
}
|
|
94
|
+
function jsonObjectCandidates(text) {
|
|
95
|
+
const candidates = [];
|
|
96
|
+
for (let start = 0; start < text.length; start += 1) {
|
|
97
|
+
if (text[start] !== "{")
|
|
98
|
+
continue;
|
|
99
|
+
let depth = 0;
|
|
100
|
+
let inString = false;
|
|
101
|
+
let escaped = false;
|
|
102
|
+
for (let index = start; index < text.length; index += 1) {
|
|
103
|
+
const character = text[index];
|
|
104
|
+
if (inString) {
|
|
105
|
+
if (escaped)
|
|
106
|
+
escaped = false;
|
|
107
|
+
else if (character === "\\")
|
|
108
|
+
escaped = true;
|
|
109
|
+
else if (character === '"')
|
|
110
|
+
inString = false;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (character === '"') {
|
|
114
|
+
inString = true;
|
|
115
|
+
}
|
|
116
|
+
else if (character === "{") {
|
|
117
|
+
depth += 1;
|
|
118
|
+
}
|
|
119
|
+
else if (character === "}" && --depth === 0) {
|
|
120
|
+
candidates.push(text.slice(start, index + 1));
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return candidates;
|
|
126
|
+
}
|
|
85
127
|
function jsonFrom(text) {
|
|
86
|
-
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
128
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
|
|
129
|
+
const candidates = [...(fenced ? [fenced] : []), ...jsonObjectCandidates(text)];
|
|
130
|
+
for (const candidate of candidates) {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(candidate.trim());
|
|
133
|
+
if (!Array.isArray(parsed.changes))
|
|
134
|
+
continue;
|
|
135
|
+
for (const change of parsed.changes) {
|
|
136
|
+
if (!change.filePath || typeof change.oldString !== "string" || typeof change.newString !== "string") {
|
|
137
|
+
throw new Error("The agent returned an invalid file change.");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { summary: parsed.summary || "The proposed source changes are ready for review.", changes: parsed.changes };
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error instanceof Error && error.message === "The agent returned an invalid file change.")
|
|
144
|
+
throw error;
|
|
93
145
|
}
|
|
94
146
|
}
|
|
95
|
-
|
|
147
|
+
throw new Error("The agent returned no valid reviewable changes. Progress output may have been mixed with the final JSON.");
|
|
96
148
|
}
|
|
97
149
|
function extractLocalAgentText(raw, provider) {
|
|
98
150
|
if (provider === "claude-code") {
|
package/dist/cli/bridge.js
CHANGED
|
@@ -52,6 +52,24 @@ function fileLineEnding(content) {
|
|
|
52
52
|
function withLineEnding(value, lineEnding) {
|
|
53
53
|
return value.replace(/\r\n?|\n/g, lineEnding);
|
|
54
54
|
}
|
|
55
|
+
function resolveProposedFile(cwd, proposedPath) {
|
|
56
|
+
const root = node_path_1.default.resolve(cwd);
|
|
57
|
+
const candidate = node_path_1.default.resolve(root, proposedPath);
|
|
58
|
+
if (candidate.startsWith(`${root}${node_path_1.default.sep}`) && node_fs_1.default.existsSync(candidate))
|
|
59
|
+
return candidate;
|
|
60
|
+
// Some React/Next source maps contain an absolute path from an older
|
|
61
|
+
// checkout. Rebase only the recognizable src/... suffix into this project.
|
|
62
|
+
const normalized = proposedPath.replace(/\\/g, "/");
|
|
63
|
+
const srcIndex = normalized.lastIndexOf("/src/");
|
|
64
|
+
if (srcIndex >= 0) {
|
|
65
|
+
const rebased = node_path_1.default.join(root, normalized.slice(srcIndex + 1));
|
|
66
|
+
if (rebased.startsWith(`${root}${node_path_1.default.sep}`) && node_fs_1.default.existsSync(rebased))
|
|
67
|
+
return rebased;
|
|
68
|
+
}
|
|
69
|
+
if (!candidate.startsWith(`${root}${node_path_1.default.sep}`))
|
|
70
|
+
throw new Error(`The proposed file is outside the current project: ${proposedPath}`);
|
|
71
|
+
throw new Error(`Could not find the proposed file in the current project: ${proposedPath}`);
|
|
72
|
+
}
|
|
55
73
|
function occurrenceCount(content, needle) {
|
|
56
74
|
if (!needle)
|
|
57
75
|
return 0;
|
|
@@ -419,9 +437,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
419
437
|
lastSnapshot = [];
|
|
420
438
|
const planned = new Map();
|
|
421
439
|
for (const change of msg.changes) {
|
|
422
|
-
const filePath =
|
|
423
|
-
if (!filePath.startsWith(`${node_path_1.default.resolve(cwd)}${node_path_1.default.sep}`))
|
|
424
|
-
throw new Error("A proposed file was outside the project.");
|
|
440
|
+
const filePath = resolveProposedFile(cwd, change.filePath);
|
|
425
441
|
const content = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
426
442
|
const prepared = prepareChange(content, change);
|
|
427
443
|
const fileChanges = planned.get(filePath) || [];
|
package/package.json
CHANGED