flowseeker 0.1.7
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/CHANGELOG.md +111 -0
- package/COMPATIBILITY.md +281 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/dist/adapters/frameworkEdges.js +586 -0
- package/dist/agent/approvalPolicy.js +81 -0
- package/dist/agent/commandRunner.js +166 -0
- package/dist/agent/flowCommandRunner.js +124 -0
- package/dist/agent/mcpToolRunner.js +167 -0
- package/dist/auth/githubAuth.js +71 -0
- package/dist/auth/modelList.js +127 -0
- package/dist/auth/oauthHandler.js +377 -0
- package/dist/chat/nativeChatParticipant.js +616 -0
- package/dist/cli/mcpServer.js +383 -0
- package/dist/cli/runEvaluation.js +789 -0
- package/dist/cli/runReplay.js +481 -0
- package/dist/commands/checkHostCompatibility.js +149 -0
- package/dist/commands/copyAgentPrompt.js +52 -0
- package/dist/commands/copyContext.js +54 -0
- package/dist/commands/explainSelectionRelevance.js +57 -0
- package/dist/commands/findRelevantContext.js +127 -0
- package/dist/commands/openEvidence.js +49 -0
- package/dist/commands/openMcpConfig.js +81 -0
- package/dist/commands/openRelatedTests.js +54 -0
- package/dist/commands/rebuildIndex.js +45 -0
- package/dist/commands/runEvaluationSuite.js +323 -0
- package/dist/commands/runReplaySuite.js +228 -0
- package/dist/config/defaultConfig.js +72 -0
- package/dist/config/loadConfig.js +84 -0
- package/dist/config/loadConfigFromPath.js +60 -0
- package/dist/extension.js +513 -0
- package/dist/gateway/agentPrompts.js +176 -0
- package/dist/gateway/aiGateway.js +1255 -0
- package/dist/gateway/aiProviders.js +901 -0
- package/dist/gateway/contextExpansion.js +331 -0
- package/dist/gateway/editProposalStore.js +238 -0
- package/dist/gateway/planProposalStore.js +28 -0
- package/dist/index/cacheStore.js +51 -0
- package/dist/index/chunker.js +45 -0
- package/dist/index/dependencyExtractor.js +107 -0
- package/dist/index/fileDiscovery.js +177 -0
- package/dist/index/structuredExtractor.js +256 -0
- package/dist/index/workspaceIndex.js +518 -0
- package/dist/mcp/mcpConfig.js +154 -0
- package/dist/mcp/mcpProvider.js +109 -0
- package/dist/mcp/mcpTools.js +215 -0
- package/dist/pipeline/agentPrompt.js +79 -0
- package/dist/pipeline/agentPromptHeadless.js +85 -0
- package/dist/pipeline/contextBlueprint.js +346 -0
- package/dist/pipeline/contextPack.js +80 -0
- package/dist/pipeline/diffPreview.js +79 -0
- package/dist/pipeline/evaluationMetrics.js +154 -0
- package/dist/pipeline/evidenceGraph.js +389 -0
- package/dist/pipeline/feedback.js +215 -0
- package/dist/pipeline/fileGroups.js +84 -0
- package/dist/pipeline/fileScanner.js +866 -0
- package/dist/pipeline/nodeScan.js +219 -0
- package/dist/pipeline/ranker.js +563 -0
- package/dist/pipeline/responseLanguage.js +39 -0
- package/dist/pipeline/retrievalPlan.js +163 -0
- package/dist/pipeline/runHeadless.js +54 -0
- package/dist/pipeline/runPipeline.js +114 -0
- package/dist/pipeline/solvePacket.js +382 -0
- package/dist/pipeline/subsystem.js +257 -0
- package/dist/pipeline/taskUnderstanding.js +453 -0
- package/dist/pipeline/tokenSavings.js +146 -0
- package/dist/pipeline/universalScan.js +216 -0
- package/dist/pipeline/verifyAfterApply.js +233 -0
- package/dist/runtime/capabilities.js +71 -0
- package/dist/runtime/hostDetect.js +98 -0
- package/dist/runtime/hostTier.js +68 -0
- package/dist/runtime/statusBar.js +80 -0
- package/dist/skills/skillRegistry.js +208 -0
- package/dist/types.js +3 -0
- package/dist/ui/chatViewProvider.js +3899 -0
- package/dist/ui/resultTreeProvider.js +174 -0
- package/dist/usage/quotaTracker.js +358 -0
- package/dist/utils/async.js +30 -0
- package/dist/utils/logger.js +64 -0
- package/dist/utils/text.js +364 -0
- package/dist/utils/updateChecker.js +140 -0
- package/package.json +561 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.truncateForAgent = truncateForAgent;
|
|
37
|
+
exports.isMissingContextRequest = isMissingContextRequest;
|
|
38
|
+
exports.isReviewablePlan = isReviewablePlan;
|
|
39
|
+
exports.createExpandedPlanInstruction = createExpandedPlanInstruction;
|
|
40
|
+
exports.createExpandedApprovedEditInstruction = createExpandedApprovedEditInstruction;
|
|
41
|
+
exports.selectContextExpansionPaths = selectContextExpansionPaths;
|
|
42
|
+
exports.readWorkspaceContextFiles = readWorkspaceContextFiles;
|
|
43
|
+
const vscode = __importStar(require("vscode"));
|
|
44
|
+
const fileGroups_1 = require("../pipeline/fileGroups");
|
|
45
|
+
const editProposalStore_1 = require("./editProposalStore");
|
|
46
|
+
const agentPrompts_1 = require("./agentPrompts");
|
|
47
|
+
const MAX_CONTEXT_FILES = 6;
|
|
48
|
+
const MAX_TOTAL_CONTEXT_CHARS = 50000;
|
|
49
|
+
const MAX_CONTEXT_FILE_CHARS = 12000;
|
|
50
|
+
const MAX_CONTEXT_FILE_BYTES = 250000;
|
|
51
|
+
function truncateForAgent(value, maxLength) {
|
|
52
|
+
const clean = value.trim();
|
|
53
|
+
if (clean.length <= maxLength) {
|
|
54
|
+
return clean;
|
|
55
|
+
}
|
|
56
|
+
return `${clean.slice(0, Math.max(0, maxLength - 16)).trimEnd()}\n...[truncated]`;
|
|
57
|
+
}
|
|
58
|
+
function isMissingContextRequest(text) {
|
|
59
|
+
const normalized = normalizeHumanText(text);
|
|
60
|
+
if (looksLikeReviewableImplementationPlan(normalized)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
"can doc",
|
|
65
|
+
"can doc day du",
|
|
66
|
+
"can kiem tra",
|
|
67
|
+
"can xem",
|
|
68
|
+
"can xem them",
|
|
69
|
+
"can xem toan bo",
|
|
70
|
+
"can mo rong",
|
|
71
|
+
"can them context",
|
|
72
|
+
"cho phep toi doc",
|
|
73
|
+
"toi se doc",
|
|
74
|
+
"toi can doc",
|
|
75
|
+
"toi can xem",
|
|
76
|
+
"toi can kiem tra",
|
|
77
|
+
"truoc khi lap ke hoach",
|
|
78
|
+
"truoc khi de xuat sua",
|
|
79
|
+
"truoc khi generate edits",
|
|
80
|
+
"noi dung bi cat",
|
|
81
|
+
"noi dung file bi cat",
|
|
82
|
+
"bi cat ngan",
|
|
83
|
+
"mo rong context",
|
|
84
|
+
"mo rong packet",
|
|
85
|
+
"bo sung context",
|
|
86
|
+
"khong du context",
|
|
87
|
+
"khong du ngu canh",
|
|
88
|
+
"chua du context",
|
|
89
|
+
"chua du ngu canh",
|
|
90
|
+
"truncated",
|
|
91
|
+
"need to read",
|
|
92
|
+
"need to inspect",
|
|
93
|
+
"need more context",
|
|
94
|
+
"need additional context",
|
|
95
|
+
"not enough context",
|
|
96
|
+
"insufficient context",
|
|
97
|
+
"full file contents",
|
|
98
|
+
"read the full",
|
|
99
|
+
"expand context",
|
|
100
|
+
"expand the packet",
|
|
101
|
+
"before i can",
|
|
102
|
+
"cannot safely"
|
|
103
|
+
].some((phrase) => normalized.includes(phrase));
|
|
104
|
+
}
|
|
105
|
+
function isReviewablePlan(text) {
|
|
106
|
+
const normalized = normalizeHumanText(text);
|
|
107
|
+
if (looksLikeReviewableImplementationPlan(normalized)) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function looksLikeReviewableImplementationPlan(normalized) {
|
|
113
|
+
const structureMarkers = [
|
|
114
|
+
"awaiting user approval",
|
|
115
|
+
"ke hoach",
|
|
116
|
+
"ke hoach trien khai",
|
|
117
|
+
"muc tieu",
|
|
118
|
+
"edit candidates",
|
|
119
|
+
"files likely",
|
|
120
|
+
"files to inspect",
|
|
121
|
+
"cac file co kha nang",
|
|
122
|
+
"inspect only",
|
|
123
|
+
"chi can doc",
|
|
124
|
+
"buoc 1",
|
|
125
|
+
"buoc 2",
|
|
126
|
+
"step 1",
|
|
127
|
+
"step 2",
|
|
128
|
+
"verification",
|
|
129
|
+
"kiem thu",
|
|
130
|
+
"kiem tra / xac minh",
|
|
131
|
+
"test/verification",
|
|
132
|
+
"rui ro",
|
|
133
|
+
"risks",
|
|
134
|
+
"unknowns"
|
|
135
|
+
];
|
|
136
|
+
const changeMarkers = [
|
|
137
|
+
"sua",
|
|
138
|
+
"cap nhat",
|
|
139
|
+
"them",
|
|
140
|
+
"xoa",
|
|
141
|
+
"bo",
|
|
142
|
+
"tao migration",
|
|
143
|
+
"migration",
|
|
144
|
+
"controller",
|
|
145
|
+
"model",
|
|
146
|
+
"view",
|
|
147
|
+
"template",
|
|
148
|
+
"mail",
|
|
149
|
+
"route",
|
|
150
|
+
"schema",
|
|
151
|
+
"constructor",
|
|
152
|
+
"update",
|
|
153
|
+
"add",
|
|
154
|
+
"remove",
|
|
155
|
+
"edit",
|
|
156
|
+
"change"
|
|
157
|
+
];
|
|
158
|
+
const cautionMarkers = [
|
|
159
|
+
"rui ro",
|
|
160
|
+
"an so",
|
|
161
|
+
"unknown",
|
|
162
|
+
"risk",
|
|
163
|
+
"regression",
|
|
164
|
+
"tuong thich",
|
|
165
|
+
"anh huong"
|
|
166
|
+
];
|
|
167
|
+
const structureScore = countPhraseMatches(normalized, structureMarkers);
|
|
168
|
+
const changeScore = countPhraseMatches(normalized, changeMarkers);
|
|
169
|
+
const hasVerification = /\b(verification|verify|test|kiem thu|kiem tra|xac minh)\b/.test(normalized);
|
|
170
|
+
const hasNumberedSteps = /\b(buoc|step)\s*\d+\b/.test(normalized);
|
|
171
|
+
const hasFileOrCodeReference = /\b(file|controller|model|migration|route|view|template|mail|blade|php|ts|js|py|java|go)\b/.test(normalized);
|
|
172
|
+
const hasApprovalMarker = normalized.includes("awaiting user approval") || normalized.includes("truoc khi tien hanh") || normalized.includes("cho approval");
|
|
173
|
+
const hasCaution = countPhraseMatches(normalized, cautionMarkers) > 0;
|
|
174
|
+
return (structureScore >= 3 &&
|
|
175
|
+
changeScore >= 2 &&
|
|
176
|
+
hasFileOrCodeReference &&
|
|
177
|
+
(hasVerification || hasNumberedSteps) &&
|
|
178
|
+
(hasApprovalMarker || hasCaution || structureScore >= 5));
|
|
179
|
+
}
|
|
180
|
+
function countPhraseMatches(value, phrases) {
|
|
181
|
+
return phrases.filter((phrase) => value.includes(phrase)).length;
|
|
182
|
+
}
|
|
183
|
+
function createExpandedPlanInstruction(previousResponse, expandedContext) {
|
|
184
|
+
return [
|
|
185
|
+
"Your previous response asked to read more context instead of returning a reviewable plan.",
|
|
186
|
+
"FlowSeeker has now provided a bounded extra context set below.",
|
|
187
|
+
"Use only the original Solve Packet plus this additional context.",
|
|
188
|
+
"Do not ask to read these same files again.",
|
|
189
|
+
"If this is still insufficient, return a concise explanation of exactly which missing file/route/model blocks the fix and do not invent edits.",
|
|
190
|
+
"",
|
|
191
|
+
(0, agentPrompts_1.createPlanInstruction)(),
|
|
192
|
+
"",
|
|
193
|
+
"Previous AI context request:",
|
|
194
|
+
truncateForAgent(previousResponse, 2000),
|
|
195
|
+
"",
|
|
196
|
+
"Additional bounded file context:",
|
|
197
|
+
expandedContext
|
|
198
|
+
].join("\n");
|
|
199
|
+
}
|
|
200
|
+
function createExpandedApprovedEditInstruction(approvedPlan, previousResponse, expandedContext) {
|
|
201
|
+
return [
|
|
202
|
+
"Your previous response did not include structured edits and asked for more context, or explained that context was insufficient.",
|
|
203
|
+
"FlowSeeker has now provided a bounded extra context set below.",
|
|
204
|
+
"Use only the original Solve Packet plus this additional context.",
|
|
205
|
+
"Do not ask to read these same files again.",
|
|
206
|
+
"Return structured edits only if the change is directly supported by the approved plan and context.",
|
|
207
|
+
"",
|
|
208
|
+
(0, agentPrompts_1.createApprovedEditInstruction)(approvedPlan),
|
|
209
|
+
"",
|
|
210
|
+
"Previous AI response:",
|
|
211
|
+
truncateForAgent(previousResponse, 2000),
|
|
212
|
+
"",
|
|
213
|
+
"Additional bounded file context:",
|
|
214
|
+
expandedContext
|
|
215
|
+
].join("\n");
|
|
216
|
+
}
|
|
217
|
+
function selectContextExpansionPaths(message, result) {
|
|
218
|
+
const explicit = extractPathMentions(message);
|
|
219
|
+
const groups = (0, fileGroups_1.aggregateEvidenceFiles)(result.units).map((group) => group.relativePath);
|
|
220
|
+
const primary = result.solvePacket?.primaryEditCandidates.map((candidate) => candidate.relativePath) ?? [];
|
|
221
|
+
const candidates = uniqueStrings([...explicit, ...primary, ...groups]);
|
|
222
|
+
const selected = [];
|
|
223
|
+
for (const file of explicit) {
|
|
224
|
+
pushUnique(selected, file);
|
|
225
|
+
}
|
|
226
|
+
const normalizedMessage = normalizePathText(message).toLowerCase();
|
|
227
|
+
const basenameCounts = new Map();
|
|
228
|
+
for (const file of candidates) {
|
|
229
|
+
const base = basename(file).toLowerCase();
|
|
230
|
+
basenameCounts.set(base, (basenameCounts.get(base) ?? 0) + 1);
|
|
231
|
+
}
|
|
232
|
+
for (const file of candidates) {
|
|
233
|
+
if (selected.length >= MAX_CONTEXT_FILES) {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
const normalizedFile = normalizePathText(file).toLowerCase();
|
|
237
|
+
const base = basename(file).toLowerCase();
|
|
238
|
+
if (normalizedMessage.includes(normalizedFile) || (basenameCounts.get(base) === 1 && normalizedMessage.includes(base))) {
|
|
239
|
+
pushUnique(selected, file);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (selected.length === 0) {
|
|
243
|
+
for (const file of uniqueStrings([...primary, ...groups]).slice(0, MAX_CONTEXT_FILES)) {
|
|
244
|
+
pushUnique(selected, file);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return selected.slice(0, MAX_CONTEXT_FILES);
|
|
248
|
+
}
|
|
249
|
+
async function readWorkspaceContextFiles(paths) {
|
|
250
|
+
const root = vscode.workspace.workspaceFolders?.[0]?.uri;
|
|
251
|
+
if (!root) {
|
|
252
|
+
return "";
|
|
253
|
+
}
|
|
254
|
+
const chunks = [];
|
|
255
|
+
let totalChars = 0;
|
|
256
|
+
for (const file of paths) {
|
|
257
|
+
if (chunks.length >= MAX_CONTEXT_FILES || totalChars >= MAX_TOTAL_CONTEXT_CHARS) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const uri = (0, editProposalStore_1.resolveWorkspaceFile)(root, file);
|
|
262
|
+
const bytes = await vscode.workspace.fs.readFile(uri);
|
|
263
|
+
if (bytes.byteLength > MAX_CONTEXT_FILE_BYTES) {
|
|
264
|
+
chunks.push(`### ${file}\n[Skipped: file is too large for bounded context expansion]\n`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const raw = Buffer.from(bytes).toString("utf8");
|
|
268
|
+
if (raw.includes("\0")) {
|
|
269
|
+
chunks.push(`### ${file}\n[Skipped: file appears to be binary]\n`);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const remaining = Math.max(0, MAX_TOTAL_CONTEXT_CHARS - totalChars);
|
|
273
|
+
const maxChars = Math.min(MAX_CONTEXT_FILE_CHARS, remaining);
|
|
274
|
+
const clipped = raw.length > maxChars
|
|
275
|
+
? `${raw.slice(0, maxChars).trimEnd()}\n... [FlowSeeker clipped this file for bounded context expansion]`
|
|
276
|
+
: raw;
|
|
277
|
+
const rendered = `### ${file}\n\nLine-numbered context. Do not include line prefixes in replacement text.\n\n\`\`\`\n${withLineNumbers(clipped)}\n\`\`\`\n`;
|
|
278
|
+
chunks.push(rendered);
|
|
279
|
+
totalChars += rendered.length;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
chunks.push(`### ${file}\n[Skipped: FlowSeeker could not read this path safely]\n`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return chunks.join("\n");
|
|
286
|
+
}
|
|
287
|
+
function extractPathMentions(text) {
|
|
288
|
+
const normalized = text.replace(/\\/g, "/");
|
|
289
|
+
const paths = [];
|
|
290
|
+
const pattern = /(?:^|[\s`'"])([A-Za-z0-9_@./+[\]-]+\.[A-Za-z0-9]{1,8})(?=[\s`'",).:;]|$)/g;
|
|
291
|
+
let match;
|
|
292
|
+
while ((match = pattern.exec(normalized))) {
|
|
293
|
+
const path = match[1].replace(/^\/+/, "");
|
|
294
|
+
if (path.includes("/") && !path.includes("..")) {
|
|
295
|
+
paths.push(path);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return uniqueStrings(paths);
|
|
299
|
+
}
|
|
300
|
+
function withLineNumbers(text) {
|
|
301
|
+
return text.split(/\r?\n/).map((line, index) => `${index + 1}: ${line}`).join("\n");
|
|
302
|
+
}
|
|
303
|
+
function normalizeHumanText(value) {
|
|
304
|
+
return value
|
|
305
|
+
.normalize("NFD")
|
|
306
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
307
|
+
.replace(/\u0111/g, "d")
|
|
308
|
+
.replace(/\u0110/g, "D")
|
|
309
|
+
.toLowerCase();
|
|
310
|
+
}
|
|
311
|
+
function normalizePathText(value) {
|
|
312
|
+
return value.replace(/\\/g, "/");
|
|
313
|
+
}
|
|
314
|
+
function basename(file) {
|
|
315
|
+
return normalizePathText(file).split("/").filter(Boolean).pop() ?? file;
|
|
316
|
+
}
|
|
317
|
+
function uniqueStrings(values) {
|
|
318
|
+
const output = [];
|
|
319
|
+
for (const value of values) {
|
|
320
|
+
pushUnique(output, value);
|
|
321
|
+
}
|
|
322
|
+
return output;
|
|
323
|
+
}
|
|
324
|
+
function pushUnique(values, value) {
|
|
325
|
+
const clean = normalizePathText(value).trim();
|
|
326
|
+
if (!clean || values.some((item) => item.toLowerCase() === clean.toLowerCase())) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
values.push(clean);
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=contextExpansion.js.map
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EditProposalStore = void 0;
|
|
37
|
+
exports.resolveWorkspaceFile = resolveWorkspaceFile;
|
|
38
|
+
const vscode = __importStar(require("vscode"));
|
|
39
|
+
class EditProposalStore {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.proposals = new Map();
|
|
42
|
+
}
|
|
43
|
+
store(edits, scope) {
|
|
44
|
+
const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
45
|
+
this.proposals.set(id, { edits, scope });
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
get(proposalId) {
|
|
49
|
+
return this.proposals.get(proposalId)?.edits;
|
|
50
|
+
}
|
|
51
|
+
getScope(proposalId) {
|
|
52
|
+
return this.proposals.get(proposalId)?.scope;
|
|
53
|
+
}
|
|
54
|
+
listFiles(proposalId) {
|
|
55
|
+
const proposal = this.proposals.get(proposalId);
|
|
56
|
+
if (!proposal?.edits.length) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
return [...new Set(proposal.edits.map((edit) => edit.file))];
|
|
60
|
+
}
|
|
61
|
+
validateScope(proposalId) {
|
|
62
|
+
const proposal = this.proposals.get(proposalId);
|
|
63
|
+
if (!proposal?.edits.length) {
|
|
64
|
+
return { valid: false, outsideScope: [] };
|
|
65
|
+
}
|
|
66
|
+
if (!proposal.scope || proposal.scope.length === 0) {
|
|
67
|
+
return { valid: true };
|
|
68
|
+
}
|
|
69
|
+
const normalizedScope = proposal.scope.map(normalizePath);
|
|
70
|
+
const outsideScope = proposal.edits
|
|
71
|
+
.map((edit) => edit.file)
|
|
72
|
+
.filter((file) => !normalizedScope.some((allowed) => pathMatches(file, allowed)));
|
|
73
|
+
if (outsideScope.length > 0) {
|
|
74
|
+
return { valid: false, outsideScope: [...new Set(outsideScope)] };
|
|
75
|
+
}
|
|
76
|
+
return { valid: true };
|
|
77
|
+
}
|
|
78
|
+
async apply(proposalId) {
|
|
79
|
+
const proposal = this.proposals.get(proposalId);
|
|
80
|
+
if (!proposal?.edits.length) {
|
|
81
|
+
throw new Error("Edit proposal is no longer available.");
|
|
82
|
+
}
|
|
83
|
+
const scopeCheck = this.validateScope(proposalId);
|
|
84
|
+
if (!scopeCheck.valid) {
|
|
85
|
+
throw new Error(`Scope lock: these files are outside the approved plan scope: ${scopeCheck.outsideScope.join(", ")}. Approve a broader plan or ask for scope expansion.`);
|
|
86
|
+
}
|
|
87
|
+
const edits = proposal.edits;
|
|
88
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
89
|
+
if (!folders?.[0]) {
|
|
90
|
+
throw new Error("Open a workspace folder before applying edits.");
|
|
91
|
+
}
|
|
92
|
+
const files = [...new Set(edits.map((edit) => edit.file))];
|
|
93
|
+
const appliedFiles = [];
|
|
94
|
+
const failedFiles = [];
|
|
95
|
+
for (const file of files) {
|
|
96
|
+
try {
|
|
97
|
+
const fileEdits = edits.filter((edit) => edit.file === file);
|
|
98
|
+
const workspaceEdit = new vscode.WorkspaceEdit();
|
|
99
|
+
const uri = resolveWorkspaceFile(folders[0].uri, file);
|
|
100
|
+
const document = await vscode.workspace.openTextDocument(uri);
|
|
101
|
+
for (const edit of fileEdits) {
|
|
102
|
+
const range = toVscodeRange(document, edit);
|
|
103
|
+
workspaceEdit.replace(uri, range, edit.text);
|
|
104
|
+
}
|
|
105
|
+
const applied = await vscode.workspace.applyEdit(workspaceEdit);
|
|
106
|
+
if (!applied) {
|
|
107
|
+
failedFiles.push({ file, reason: `VS Code rejected the edit for ${file}. The file may have been modified or the edit range is no longer valid.` });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
await document.save();
|
|
111
|
+
appliedFiles.push(file);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
failedFiles.push({ file, reason: message });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (appliedFiles.length === 0) {
|
|
119
|
+
const reasons = failedFiles.map((f) => `${f.file}: ${f.reason}`).join("; ");
|
|
120
|
+
throw new Error(`No edits were applied. ${reasons}`);
|
|
121
|
+
}
|
|
122
|
+
this.proposals.delete(proposalId);
|
|
123
|
+
return {
|
|
124
|
+
editCount: edits.length,
|
|
125
|
+
fileCount: files.length,
|
|
126
|
+
files: appliedFiles,
|
|
127
|
+
failedFiles
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async previewFile(root, proposalId, file) {
|
|
131
|
+
const proposal = this.proposals.get(proposalId);
|
|
132
|
+
if (!proposal?.edits.length) {
|
|
133
|
+
throw new Error("Edit proposal is no longer available.");
|
|
134
|
+
}
|
|
135
|
+
const edits = proposal.edits.filter((edit) => pathMatches(edit.file, file));
|
|
136
|
+
if (edits.length === 0) {
|
|
137
|
+
throw new Error(`No proposed edits were found for ${file}.`);
|
|
138
|
+
}
|
|
139
|
+
const originalUri = resolveWorkspaceFile(root, edits[0].file);
|
|
140
|
+
const document = await vscode.workspace.openTextDocument(originalUri);
|
|
141
|
+
return {
|
|
142
|
+
file: edits[0].file,
|
|
143
|
+
originalUri,
|
|
144
|
+
languageId: document.languageId,
|
|
145
|
+
text: applyPreviewEdits(document, edits)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.EditProposalStore = EditProposalStore;
|
|
150
|
+
function resolveWorkspaceFile(root, relativePath) {
|
|
151
|
+
const normalized = relativePath.trim().replace(/\\/g, "/");
|
|
152
|
+
if (!normalized ||
|
|
153
|
+
normalized.includes("\0") ||
|
|
154
|
+
normalized.startsWith("/") ||
|
|
155
|
+
/^[a-zA-Z]:\//.test(normalized)) {
|
|
156
|
+
throw new Error(`Unsafe workspace path: ${relativePath}`);
|
|
157
|
+
}
|
|
158
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
159
|
+
if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) {
|
|
160
|
+
throw new Error(`Unsafe workspace path: ${relativePath}`);
|
|
161
|
+
}
|
|
162
|
+
return vscode.Uri.joinPath(root, ...segments);
|
|
163
|
+
}
|
|
164
|
+
function toVscodeRange(document, edit) {
|
|
165
|
+
if (edit.replaceWholeFile) {
|
|
166
|
+
const lastLine = document.lineAt(document.lineCount - 1);
|
|
167
|
+
return new vscode.Range(new vscode.Position(0, 0), lastLine.range.end);
|
|
168
|
+
}
|
|
169
|
+
if (!edit.range) {
|
|
170
|
+
throw new Error(`Gateway edit for ${edit.file} must include a range or replaceWholeFile=true.`);
|
|
171
|
+
}
|
|
172
|
+
const start = toVscodePosition(document, edit.file, edit.range.startLine, edit.range.startColumn ?? 1);
|
|
173
|
+
const end = toVscodePosition(document, edit.file, edit.range.endLine, edit.range.endColumn ?? 1);
|
|
174
|
+
if (end.isBefore(start)) {
|
|
175
|
+
throw new Error(`Gateway edit for ${edit.file} has an invalid range.`);
|
|
176
|
+
}
|
|
177
|
+
return new vscode.Range(start, end);
|
|
178
|
+
}
|
|
179
|
+
function applyPreviewEdits(document, edits) {
|
|
180
|
+
const wholeFileEdits = edits.filter((edit) => edit.replaceWholeFile);
|
|
181
|
+
if (wholeFileEdits.length > 0) {
|
|
182
|
+
if (edits.length > 1) {
|
|
183
|
+
throw new Error(`Cannot preview mixed whole-file and ranged edits for ${wholeFileEdits[0].file}.`);
|
|
184
|
+
}
|
|
185
|
+
return wholeFileEdits[0].text;
|
|
186
|
+
}
|
|
187
|
+
const replacements = edits
|
|
188
|
+
.map((edit) => {
|
|
189
|
+
const range = toVscodeRange(document, edit);
|
|
190
|
+
return {
|
|
191
|
+
start: document.offsetAt(range.start),
|
|
192
|
+
end: document.offsetAt(range.end),
|
|
193
|
+
text: edit.text,
|
|
194
|
+
file: edit.file
|
|
195
|
+
};
|
|
196
|
+
})
|
|
197
|
+
.sort((left, right) => right.start - left.start || right.end - left.end);
|
|
198
|
+
let text = document.getText();
|
|
199
|
+
let previousStart = Number.POSITIVE_INFINITY;
|
|
200
|
+
for (const replacement of replacements) {
|
|
201
|
+
if (replacement.end > previousStart) {
|
|
202
|
+
throw new Error(`Cannot preview overlapping edits for ${replacement.file}.`);
|
|
203
|
+
}
|
|
204
|
+
text = text.slice(0, replacement.start) + replacement.text + text.slice(replacement.end);
|
|
205
|
+
previousStart = replacement.start;
|
|
206
|
+
}
|
|
207
|
+
return text;
|
|
208
|
+
}
|
|
209
|
+
function toVscodePosition(document, file, line, column) {
|
|
210
|
+
const zeroLine = line - 1;
|
|
211
|
+
const zeroColumn = column - 1;
|
|
212
|
+
if (zeroLine < 0 || zeroLine >= document.lineCount) {
|
|
213
|
+
throw new Error(`Gateway edit for ${file} references line ${line}, but the file has ${document.lineCount} line(s).`);
|
|
214
|
+
}
|
|
215
|
+
if (zeroColumn < 0) {
|
|
216
|
+
throw new Error(`Gateway edit for ${file} has an invalid column ${column}.`);
|
|
217
|
+
}
|
|
218
|
+
const textLine = document.lineAt(zeroLine);
|
|
219
|
+
if (zeroColumn > textLine.text.length) {
|
|
220
|
+
throw new Error(`Gateway edit for ${file} references column ${column}, but line ${line} has ${textLine.text.length + 1} column position(s).`);
|
|
221
|
+
}
|
|
222
|
+
return new vscode.Position(zeroLine, zeroColumn);
|
|
223
|
+
}
|
|
224
|
+
function normalizePath(value) {
|
|
225
|
+
return value.replace(/\\/g, "/").toLowerCase();
|
|
226
|
+
}
|
|
227
|
+
function pathMatches(actualPath, allowedPath) {
|
|
228
|
+
const actual = normalizePath(actualPath);
|
|
229
|
+
const allowed = normalizePath(allowedPath);
|
|
230
|
+
if (actual === allowed) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (actual.endsWith(`/${allowed}`)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
return allowed.endsWith(`/${actual}`);
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=editProposalStore.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PlanProposalStore = void 0;
|
|
4
|
+
class PlanProposalStore {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.proposals = new Map();
|
|
7
|
+
}
|
|
8
|
+
store(result, plan) {
|
|
9
|
+
const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
10
|
+
const allowedFiles = result.solvePacket?.primaryEditCandidates.map((candidate) => candidate.relativePath) ?? [];
|
|
11
|
+
this.proposals.set(id, {
|
|
12
|
+
id,
|
|
13
|
+
result,
|
|
14
|
+
plan,
|
|
15
|
+
allowedFiles,
|
|
16
|
+
createdAt: Date.now()
|
|
17
|
+
});
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
get(id) {
|
|
21
|
+
return this.proposals.get(id);
|
|
22
|
+
}
|
|
23
|
+
getAllowedFiles(id) {
|
|
24
|
+
return this.proposals.get(id)?.allowedFiles ?? [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.PlanProposalStore = PlanProposalStore;
|
|
28
|
+
//# sourceMappingURL=planProposalStore.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.clearFlowSeekerCache = clearFlowSeekerCache;
|
|
37
|
+
const vscode = __importStar(require("vscode"));
|
|
38
|
+
async function clearFlowSeekerCache() {
|
|
39
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
40
|
+
if (!folders || folders.length === 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const cacheUri = vscode.Uri.joinPath(folders[0].uri, ".flowseeker", "cache");
|
|
44
|
+
try {
|
|
45
|
+
await vscode.workspace.fs.delete(cacheUri, { recursive: true, useTrash: false });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Cache may not exist yet.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cacheStore.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chunkFile = chunkFile;
|
|
4
|
+
exports.chunkAroundLines = chunkAroundLines;
|
|
5
|
+
const text_1 = require("../utils/text");
|
|
6
|
+
function chunkFile(content, chunkSizeLines, overlapLines) {
|
|
7
|
+
const lines = content.split(/\r?\n/);
|
|
8
|
+
const chunks = [];
|
|
9
|
+
const size = Math.max(20, chunkSizeLines);
|
|
10
|
+
const overlap = Math.max(0, Math.min(overlapLines, size - 1));
|
|
11
|
+
for (let index = 0; index < lines.length; index += size - overlap) {
|
|
12
|
+
const startLine = index + 1;
|
|
13
|
+
const endLine = Math.min(lines.length, index + size);
|
|
14
|
+
chunks.push({
|
|
15
|
+
range: { startLine, endLine },
|
|
16
|
+
text: (0, text_1.snippetFromLines)(lines, startLine, endLine)
|
|
17
|
+
});
|
|
18
|
+
if (endLine >= lines.length) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return chunks;
|
|
23
|
+
}
|
|
24
|
+
function chunkAroundLines(content, lineNumbers, contextRadius, maxChunkLines = 120) {
|
|
25
|
+
const lines = content.split(/\r?\n/);
|
|
26
|
+
const sorted = [...new Set(lineNumbers)].sort((a, b) => a - b);
|
|
27
|
+
const ranges = [];
|
|
28
|
+
const maxLines = Math.max(contextRadius * 2 + 1, maxChunkLines);
|
|
29
|
+
for (const lineNumber of sorted) {
|
|
30
|
+
const startLine = Math.max(1, lineNumber - contextRadius);
|
|
31
|
+
const endLine = Math.min(lines.length, lineNumber + contextRadius);
|
|
32
|
+
const previous = ranges.at(-1);
|
|
33
|
+
if (previous && startLine <= previous.endLine + 1 && Math.max(previous.endLine, endLine) - previous.startLine + 1 <= maxLines) {
|
|
34
|
+
previous.endLine = Math.max(previous.endLine, endLine);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
ranges.push({ startLine, endLine });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return ranges.map((range) => ({
|
|
41
|
+
range,
|
|
42
|
+
text: (0, text_1.snippetFromLines)(lines, range.startLine, range.endLine)
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=chunker.js.map
|