shark-ai 0.4.15 → 0.4.17
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/bin/shark.js +767 -3105
- package/dist/bin/shark.js.map +1 -1
- package/dist/{chunk-H6S7DYRG.js → chunk-CUUMQTCW.js} +117 -335
- package/dist/chunk-CUUMQTCW.js.map +1 -0
- package/dist/chunk-JLE7NOEG.js +292 -0
- package/dist/chunk-JLE7NOEG.js.map +1 -0
- package/dist/chunk-T3DPAQAV.js +3759 -0
- package/dist/chunk-T3DPAQAV.js.map +1 -0
- package/dist/developer-agent-CEDTDILQ.js +9 -0
- package/dist/developer-agent-CEDTDILQ.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/skills/brainstorming/SKILL.md +159 -0
- package/skills/brainstorming/scripts/frame-template.html +213 -0
- package/skills/brainstorming/scripts/helper.js +167 -0
- package/skills/brainstorming/scripts/server.cjs +723 -0
- package/skills/brainstorming/scripts/start-server.sh +209 -0
- package/skills/brainstorming/scripts/stop-server.sh +120 -0
- package/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/skills/brainstorming/visual-companion.md +298 -0
- package/skills/dispatching-parallel-agents/SKILL.md +185 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +241 -0
- package/skills/receiving-code-review/SKILL.md +213 -0
- package/skills/requesting-code-review/SKILL.md +103 -0
- package/skills/requesting-code-review/code-reviewer.md +172 -0
- package/skills/subagent-driven-development/SKILL.md +416 -0
- package/skills/subagent-driven-development/implementer-prompt.md +139 -0
- package/skills/subagent-driven-development/scripts/review-package +47 -0
- package/skills/subagent-driven-development/scripts/task-brief +42 -0
- package/skills/subagent-driven-development/task-reviewer-prompt.md +188 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +371 -0
- package/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/skills/using-git-worktrees/SKILL.md +202 -0
- package/skills/using-superpowers/SKILL.md +121 -0
- package/skills/using-superpowers/references/antigravity-tools.md +96 -0
- package/skills/using-superpowers/references/claude-code-tools.md +50 -0
- package/skills/using-superpowers/references/codex-tools.md +72 -0
- package/skills/using-superpowers/references/copilot-tools.md +49 -0
- package/skills/using-superpowers/references/gemini-tools.md +63 -0
- package/skills/using-superpowers/references/pi-tools.md +28 -0
- package/skills/verification-before-completion/SKILL.md +139 -0
- package/skills/writing-plans/SKILL.md +188 -0
- package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/writing-skills/SKILL.md +689 -0
- package/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/writing-skills/persuasion-principles.md +187 -0
- package/skills/writing-skills/render-graphs.js +168 -0
- package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/dist/chunk-H6S7DYRG.js.map +0 -1
package/dist/bin/shark.js
CHANGED
|
@@ -1,15 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configCommand,
|
|
4
|
+
loginCommand
|
|
5
|
+
} from "../chunk-JLE7NOEG.js";
|
|
6
|
+
import {
|
|
7
|
+
AGENT_RESPONSE_JSON_SCHEMA,
|
|
8
|
+
ProviderResolver,
|
|
9
|
+
TechStackEnum,
|
|
10
|
+
astAddClass,
|
|
11
|
+
astAddDecorator,
|
|
12
|
+
astAddFunction,
|
|
13
|
+
astAddImport,
|
|
14
|
+
astAddInterface,
|
|
15
|
+
astAddMethod,
|
|
16
|
+
astAddProperty,
|
|
17
|
+
astAddTypeAlias,
|
|
18
|
+
astGetMethod,
|
|
19
|
+
astGetProperty,
|
|
20
|
+
astGrepRewrite,
|
|
21
|
+
astGrepSearch,
|
|
22
|
+
astListStructure,
|
|
23
|
+
astModifyMethod,
|
|
24
|
+
astModifyProperty,
|
|
25
|
+
astOrganizeImports,
|
|
26
|
+
astRemoveFunction,
|
|
27
|
+
astRemoveImport,
|
|
28
|
+
astRemoveMethod,
|
|
29
|
+
astRemoveProperty,
|
|
30
|
+
conversationManager,
|
|
31
|
+
handleListFiles,
|
|
32
|
+
handleReadFile,
|
|
33
|
+
handleRunCommand,
|
|
34
|
+
handleSearchCode,
|
|
35
|
+
handleSearchFile,
|
|
36
|
+
interactiveDeveloperAgent,
|
|
37
|
+
replaceLineRange,
|
|
38
|
+
startSmartReplace,
|
|
39
|
+
workflowManager
|
|
40
|
+
} from "../chunk-T3DPAQAV.js";
|
|
2
41
|
import {
|
|
3
42
|
ConfigManager,
|
|
4
43
|
FileLogger,
|
|
5
|
-
authenticate,
|
|
6
44
|
colors,
|
|
7
|
-
configCommand,
|
|
8
|
-
loginCommand,
|
|
9
|
-
t,
|
|
10
|
-
tokenStorage,
|
|
11
45
|
tui
|
|
12
|
-
} from "../chunk-
|
|
46
|
+
} from "../chunk-CUUMQTCW.js";
|
|
13
47
|
|
|
14
48
|
// src/core/error/crash-handler.ts
|
|
15
49
|
import fs from "fs";
|
|
@@ -68,123 +102,10 @@ Node: ${process.version}
|
|
|
68
102
|
var crashHandler = CrashHandler.getInstance();
|
|
69
103
|
|
|
70
104
|
// src/bin/shark.ts
|
|
71
|
-
import { Command as
|
|
105
|
+
import { Command as Command6 } from "commander";
|
|
72
106
|
|
|
73
107
|
// src/commands/init.ts
|
|
74
108
|
import { Command } from "commander";
|
|
75
|
-
|
|
76
|
-
// src/core/workflow/workflow-manager.ts
|
|
77
|
-
import fs2 from "fs/promises";
|
|
78
|
-
import path2 from "path";
|
|
79
|
-
|
|
80
|
-
// src/core/workflow/shark-workflow.schema.ts
|
|
81
|
-
import { z } from "zod";
|
|
82
|
-
var TechStackEnum = z.enum([
|
|
83
|
-
"react",
|
|
84
|
-
"nextjs",
|
|
85
|
-
"angular",
|
|
86
|
-
"vue",
|
|
87
|
-
"node-ts",
|
|
88
|
-
"python",
|
|
89
|
-
"dotnet",
|
|
90
|
-
"java",
|
|
91
|
-
"unknown"
|
|
92
|
-
]);
|
|
93
|
-
var WorkflowStageEnum = z.enum([
|
|
94
|
-
"business_analysis",
|
|
95
|
-
"specification",
|
|
96
|
-
"architecture",
|
|
97
|
-
"development",
|
|
98
|
-
"verification",
|
|
99
|
-
"deployment"
|
|
100
|
-
]);
|
|
101
|
-
var StageStatusEnum = z.enum([
|
|
102
|
-
"pending",
|
|
103
|
-
"in_progress",
|
|
104
|
-
"completed",
|
|
105
|
-
"failed",
|
|
106
|
-
"skipped"
|
|
107
|
-
]);
|
|
108
|
-
var WorkflowSchema = z.object({
|
|
109
|
-
projectId: z.string().uuid(),
|
|
110
|
-
projectName: z.string().min(1),
|
|
111
|
-
techStack: TechStackEnum.default("unknown"),
|
|
112
|
-
currentStage: WorkflowStageEnum.default("business_analysis"),
|
|
113
|
-
stageStatus: StageStatusEnum.default("pending"),
|
|
114
|
-
lastUpdated: z.string().datetime(),
|
|
115
|
-
// ISO 8601
|
|
116
|
-
conversationId: z.string().optional(),
|
|
117
|
-
conversations: z.record(z.string(), z.string()).optional(),
|
|
118
|
-
// agentType -> conversationId
|
|
119
|
-
artifacts: z.array(z.string()).default([]),
|
|
120
|
-
// Extensible metadata bag
|
|
121
|
-
metadata: z.record(z.unknown()).optional()
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// src/core/workflow/workflow-manager.ts
|
|
125
|
-
var WorkflowManager = class _WorkflowManager {
|
|
126
|
-
static instance;
|
|
127
|
-
filename = "shark-workflow.json";
|
|
128
|
-
tmpFilename = ".shark-workflow.tmp";
|
|
129
|
-
constructor() {
|
|
130
|
-
}
|
|
131
|
-
static getInstance() {
|
|
132
|
-
if (!_WorkflowManager.instance) {
|
|
133
|
-
_WorkflowManager.instance = new _WorkflowManager();
|
|
134
|
-
}
|
|
135
|
-
return _WorkflowManager.instance;
|
|
136
|
-
}
|
|
137
|
-
getFilePath() {
|
|
138
|
-
return path2.join(process.cwd(), this.filename);
|
|
139
|
-
}
|
|
140
|
-
getTmpFilePath() {
|
|
141
|
-
return path2.join(process.cwd(), this.tmpFilename);
|
|
142
|
-
}
|
|
143
|
-
async save(state) {
|
|
144
|
-
const parsed = WorkflowSchema.safeParse(state);
|
|
145
|
-
if (!parsed.success) {
|
|
146
|
-
throw new Error(`Invalid workflow state: ${parsed.error.message}`);
|
|
147
|
-
}
|
|
148
|
-
const filePath = this.getFilePath();
|
|
149
|
-
const tmpPath = this.getTmpFilePath();
|
|
150
|
-
const data = JSON.stringify(parsed.data, null, 2);
|
|
151
|
-
try {
|
|
152
|
-
await fs2.writeFile(tmpPath, data, "utf-8");
|
|
153
|
-
await fs2.rename(tmpPath, filePath);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
throw new Error(`Failed to save workflow state atomically: ${error.message}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async load() {
|
|
159
|
-
const filePath = this.getFilePath();
|
|
160
|
-
try {
|
|
161
|
-
try {
|
|
162
|
-
await fs2.access(filePath);
|
|
163
|
-
} catch {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
const content = await fs2.readFile(filePath, "utf-8");
|
|
167
|
-
const json = JSON.parse(content);
|
|
168
|
-
const parsed = WorkflowSchema.safeParse(json);
|
|
169
|
-
if (parsed.success) {
|
|
170
|
-
return parsed.data;
|
|
171
|
-
} else {
|
|
172
|
-
console.warn(colors.warning(`\u26A0\uFE0F Corrupted workflow file detected: ${parsed.error.message}`));
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
} catch (error) {
|
|
176
|
-
console.warn(colors.warning(`\u26A0\uFE0F Failed to load workflow state: ${error.message}`));
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
// Helper to get current state or default if none exists
|
|
181
|
-
async getOrInitState() {
|
|
182
|
-
return await this.load();
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
var workflowManager = WorkflowManager.getInstance();
|
|
186
|
-
|
|
187
|
-
// src/commands/init.ts
|
|
188
109
|
import { randomUUID } from "crypto";
|
|
189
110
|
var initAction = async () => {
|
|
190
111
|
tui.intro("Shark Project Initialization");
|
|
@@ -267,1707 +188,148 @@ var initAction = async () => {
|
|
|
267
188
|
};
|
|
268
189
|
var initCommand = new Command("init").description("Initialize a new Shark project").action(initAction);
|
|
269
190
|
|
|
270
|
-
// src/
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
};
|
|
277
|
-
var STACKSPOT_AGENT_API_BASE = "https://genai-inference-app.stackspot.com";
|
|
278
|
-
async function ensureValidToken(realm) {
|
|
279
|
-
let creds = await tokenStorage.getCredentials(realm);
|
|
280
|
-
if (!creds?.accessToken) {
|
|
281
|
-
throw new AuthError(`Authentication required for realm '${realm}'.
|
|
282
|
-
Please run 'shark login' to authenticate.`);
|
|
283
|
-
}
|
|
284
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
285
|
-
const buffer = 300;
|
|
286
|
-
if (creds.expiresAt && creds.clientId && creds.clientKey) {
|
|
287
|
-
if (now > creds.expiresAt - buffer) {
|
|
288
|
-
try {
|
|
289
|
-
const newTokens = await authenticate(realm, creds.clientId, creds.clientKey);
|
|
290
|
-
await tokenStorage.saveToken(
|
|
291
|
-
realm,
|
|
292
|
-
newTokens.access_token,
|
|
293
|
-
creds.clientId,
|
|
294
|
-
creds.clientKey,
|
|
295
|
-
newTokens.expires_in
|
|
296
|
-
);
|
|
297
|
-
return newTokens.access_token;
|
|
298
|
-
} catch (error) {
|
|
299
|
-
console.warn(colors.warning(`\u26A0\uFE0F Failed to auto-refresh token: ${error.message}`));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
191
|
+
// src/commands/dev.ts
|
|
192
|
+
import { Command as Command2 } from "commander";
|
|
193
|
+
var devCommand = new Command2("dev").description("Starts the Shark Developer Agent (Shark Dev Orchestration V2)").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").option("-y, --yes", "Automatically approve all actions without prompting").option("--auto", "Automatically approve all actions without prompting").option("--export-schema", "Export the agent response JSON schema").action(async (options) => {
|
|
194
|
+
if (options.exportSchema) {
|
|
195
|
+
console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
|
|
196
|
+
return;
|
|
302
197
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Streams agent response using Server-Sent Events.
|
|
310
|
-
*
|
|
311
|
-
* @param url - The SSE endpoint URL
|
|
312
|
-
* @param requestPayload - The request payload to POST
|
|
313
|
-
* @param headers - Request headers (including Authorization)
|
|
314
|
-
* @param callbacks - Event callbacks for chunks, completion, and errors
|
|
315
|
-
*/
|
|
316
|
-
async streamAgentResponse(url, requestPayload, headers, callbacks = {}) {
|
|
317
|
-
const { onChunk, onComplete, onError } = callbacks;
|
|
318
|
-
FileLogger.log("SSE", `Starting Request to ${url}`, {
|
|
319
|
-
headers,
|
|
320
|
-
payload: requestPayload
|
|
198
|
+
try {
|
|
199
|
+
const result = await interactiveDeveloperAgent({
|
|
200
|
+
taskInstruction: options.task,
|
|
201
|
+
context: options.context,
|
|
202
|
+
auto: options.yes || options.auto
|
|
321
203
|
});
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
headers: {
|
|
326
|
-
...headers,
|
|
327
|
-
"Content-Type": "application/json"
|
|
328
|
-
},
|
|
329
|
-
body: JSON.stringify(requestPayload)
|
|
330
|
-
});
|
|
331
|
-
FileLogger.log("SSE", `Response Status: ${response.status} ${response.statusText}`);
|
|
332
|
-
const responseHeaders = {};
|
|
333
|
-
response.headers.forEach((value, key) => {
|
|
334
|
-
responseHeaders[key] = value;
|
|
335
|
-
});
|
|
336
|
-
FileLogger.log("SSE", "Response Headers", responseHeaders);
|
|
337
|
-
if (!response.ok) {
|
|
338
|
-
const errorText = await response.text();
|
|
339
|
-
FileLogger.log("SSE", "Response Error Body", errorText);
|
|
340
|
-
throw new Error(`SSE request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
341
|
-
}
|
|
342
|
-
if (!response.body) {
|
|
343
|
-
throw new Error("Response body is null");
|
|
344
|
-
}
|
|
345
|
-
const contentType = response.headers.get("content-type") || "";
|
|
346
|
-
const isJson = contentType.includes("application/json");
|
|
347
|
-
if (isJson) {
|
|
348
|
-
const jsonBody = await response.json();
|
|
349
|
-
FileLogger.log("SSE", "Received Non-Streaming JSON Response", { length: JSON.stringify(jsonBody).length });
|
|
350
|
-
let content = "";
|
|
351
|
-
if (typeof jsonBody === "string") content = jsonBody;
|
|
352
|
-
else if (jsonBody.message) content = jsonBody.message;
|
|
353
|
-
else if (jsonBody.choices?.[0]?.message?.content) content = jsonBody.choices[0].message.content;
|
|
354
|
-
else content = JSON.stringify(jsonBody);
|
|
355
|
-
if (onChunk) onChunk(content);
|
|
356
|
-
if (onComplete) onComplete(content, jsonBody);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
const reader = response.body.getReader();
|
|
360
|
-
const decoder = new TextDecoder();
|
|
361
|
-
let buffer = "";
|
|
362
|
-
let fullMessage = "";
|
|
363
|
-
let metadata = {};
|
|
364
|
-
while (true) {
|
|
365
|
-
const { done, value } = await reader.read();
|
|
366
|
-
if (done) {
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
buffer += decoder.decode(value, { stream: true });
|
|
370
|
-
const lines = buffer.split("\n");
|
|
371
|
-
buffer = lines.pop() || "";
|
|
372
|
-
for (const line of lines) {
|
|
373
|
-
if (line.startsWith("data:")) {
|
|
374
|
-
const data = line.slice(5).trim();
|
|
375
|
-
if (data === "[DONE]") {
|
|
376
|
-
FileLogger.log("SSE", "Stream Complete [DONE]", { fullMessage, metadata });
|
|
377
|
-
if (onComplete) {
|
|
378
|
-
onComplete(fullMessage, metadata);
|
|
379
|
-
}
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
try {
|
|
383
|
-
const parsed = JSON.parse(data);
|
|
384
|
-
const chunk = parsed.message || parsed.content || data;
|
|
385
|
-
fullMessage += chunk;
|
|
386
|
-
if (parsed.conversation_id) {
|
|
387
|
-
metadata.conversation_id = parsed.conversation_id;
|
|
388
|
-
}
|
|
389
|
-
if (onChunk) {
|
|
390
|
-
onChunk(chunk);
|
|
391
|
-
}
|
|
392
|
-
} catch (parseError) {
|
|
393
|
-
fullMessage += data;
|
|
394
|
-
if (onChunk) {
|
|
395
|
-
onChunk(data);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
FileLogger.log("SSE", "Stream Ended Naturally", { fullMessage, metadata });
|
|
402
|
-
if (onComplete) {
|
|
403
|
-
onComplete(fullMessage, metadata);
|
|
404
|
-
}
|
|
405
|
-
} catch (error) {
|
|
406
|
-
FileLogger.log("SSE", "Stream Error", error);
|
|
407
|
-
if (onError) {
|
|
408
|
-
onError(error instanceof Error ? error : new Error(String(error)));
|
|
409
|
-
} else {
|
|
410
|
-
throw error;
|
|
411
|
-
}
|
|
204
|
+
if (!result.success) {
|
|
205
|
+
console.error("Task execution failed:", result.summary);
|
|
206
|
+
process.exit(1);
|
|
412
207
|
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error("Error during development agent execution:", error.message);
|
|
210
|
+
process.exit(1);
|
|
413
211
|
}
|
|
414
|
-
};
|
|
415
|
-
var sseClient = new SSEClient();
|
|
416
|
-
|
|
417
|
-
// src/core/agents/agent-response-parser.ts
|
|
418
|
-
import { z as z2 } from "zod";
|
|
419
|
-
var AgentActionSchema = z2.object({
|
|
420
|
-
type: z2.enum([
|
|
421
|
-
"create_file",
|
|
422
|
-
"modify_file",
|
|
423
|
-
"list_files",
|
|
424
|
-
"search_file",
|
|
425
|
-
"search_code",
|
|
426
|
-
"read_file",
|
|
427
|
-
"delete_file",
|
|
428
|
-
"list_structure",
|
|
429
|
-
"modify_ast",
|
|
430
|
-
"search_ast",
|
|
431
|
-
"run_command",
|
|
432
|
-
"talk_with_user",
|
|
433
|
-
"use_mcp_tool",
|
|
434
|
-
"ast_list_structure",
|
|
435
|
-
"ast_get_method",
|
|
436
|
-
"ast_add_method",
|
|
437
|
-
"ast_modify_method",
|
|
438
|
-
"ast_remove_method",
|
|
439
|
-
"ast_add_class",
|
|
440
|
-
"ast_get_property",
|
|
441
|
-
"ast_add_property",
|
|
442
|
-
"ast_modify_property",
|
|
443
|
-
"ast_remove_property",
|
|
444
|
-
"ast_add_decorator",
|
|
445
|
-
"ast_add_interface",
|
|
446
|
-
"ast_add_type_alias",
|
|
447
|
-
"ast_add_function",
|
|
448
|
-
"ast_remove_function",
|
|
449
|
-
"ast_add_import",
|
|
450
|
-
"ast_remove_import",
|
|
451
|
-
"ast_organize_imports"
|
|
452
|
-
]),
|
|
453
|
-
path: z2.string().nullable().optional(),
|
|
454
|
-
// Nullable for strict mode combatibility
|
|
455
|
-
content: z2.string().nullable().optional(),
|
|
456
|
-
line_range: z2.array(z2.number()).nullable().optional(),
|
|
457
|
-
target_content: z2.string().nullable().optional(),
|
|
458
|
-
command: z2.string().nullable().optional(),
|
|
459
|
-
tool_name: z2.string().nullable().optional(),
|
|
460
|
-
tool_args: z2.string().nullable().optional(),
|
|
461
|
-
// JSON string argument
|
|
462
|
-
// search_code fields
|
|
463
|
-
query: z2.string().nullable().optional(),
|
|
464
|
-
is_regex: z2.boolean().nullable().optional(),
|
|
465
|
-
// AST-Grep fields
|
|
466
|
-
pattern: z2.string().nullable().optional(),
|
|
467
|
-
fix: z2.string().nullable().optional(),
|
|
468
|
-
language: z2.string().nullable().optional(),
|
|
469
|
-
file_path: z2.string().nullable().optional(),
|
|
470
|
-
// Alias for path in ast-grep actions
|
|
471
|
-
// New AST Tool Specific Fields
|
|
472
|
-
class_name: z2.string().nullable().optional(),
|
|
473
|
-
method_name: z2.string().nullable().optional(),
|
|
474
|
-
method_code: z2.string().nullable().optional(),
|
|
475
|
-
property_name: z2.string().nullable().optional(),
|
|
476
|
-
property_code: z2.string().nullable().optional(),
|
|
477
|
-
extends_class: z2.string().nullable().optional(),
|
|
478
|
-
implements_interfaces: z2.array(z2.string()).nullable().optional(),
|
|
479
|
-
decorator_code: z2.string().nullable().optional(),
|
|
480
|
-
interface_code: z2.string().nullable().optional(),
|
|
481
|
-
type_code: z2.string().nullable().optional(),
|
|
482
|
-
function_name: z2.string().nullable().optional(),
|
|
483
|
-
function_code: z2.string().nullable().optional(),
|
|
484
|
-
import_statement: z2.string().nullable().optional(),
|
|
485
|
-
module_path: z2.string().nullable().optional(),
|
|
486
|
-
new_body: z2.string().nullable().optional(),
|
|
487
|
-
// Preview confirmation
|
|
488
|
-
confirmed: z2.boolean().nullable().optional()
|
|
489
|
-
});
|
|
490
|
-
var AgentCommandSchema = z2.object({
|
|
491
|
-
command: z2.string(),
|
|
492
|
-
description: z2.string(),
|
|
493
|
-
critical: z2.boolean()
|
|
494
212
|
});
|
|
495
|
-
var AgentResponseSchema = z2.object({
|
|
496
|
-
actions: z2.array(AgentActionSchema),
|
|
497
|
-
commands: z2.array(AgentCommandSchema).optional(),
|
|
498
|
-
summary: z2.string().optional(),
|
|
499
|
-
// Legacy fields handling for smooth transition/fallback
|
|
500
|
-
message: z2.string().optional(),
|
|
501
|
-
conversation_id: z2.string().optional()
|
|
502
|
-
});
|
|
503
|
-
function parseAgentResponse(rawResponse) {
|
|
504
|
-
FileLogger.log("PARSER", "Parsing Agent Response", { rawType: typeof rawResponse });
|
|
505
|
-
let parsedObj = {};
|
|
506
|
-
let conversation_id;
|
|
507
|
-
if (typeof rawResponse === "string") {
|
|
508
|
-
FileLogger.log("PARSER", "Type String", { length: rawResponse.length });
|
|
509
|
-
try {
|
|
510
|
-
parsedObj = extractFirstJson(rawResponse);
|
|
511
|
-
} catch (e) {
|
|
512
|
-
FileLogger.log("PARSER", "String Parse Failed", { error: e.message });
|
|
513
|
-
return {
|
|
514
|
-
actions: [{
|
|
515
|
-
type: "talk_with_user",
|
|
516
|
-
content: rawResponse,
|
|
517
|
-
path: ""
|
|
518
|
-
}],
|
|
519
|
-
message: rawResponse
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
} else if (typeof rawResponse === "object" && rawResponse !== null) {
|
|
523
|
-
const anyResp = rawResponse;
|
|
524
|
-
conversation_id = anyResp.conversation_id;
|
|
525
|
-
FileLogger.log("PARSER", "Type Object", {
|
|
526
|
-
hasContent: !!anyResp.content,
|
|
527
|
-
hasMessage: !!anyResp.message,
|
|
528
|
-
messageType: typeof anyResp.message
|
|
529
|
-
});
|
|
530
|
-
const stringContent = anyResp.content || anyResp.message;
|
|
531
|
-
if (stringContent && typeof stringContent === "string") {
|
|
532
|
-
try {
|
|
533
|
-
const parsedInside = extractFirstJson(stringContent);
|
|
534
|
-
if (typeof parsedInside === "object" && parsedInside !== null) {
|
|
535
|
-
parsedObj = parsedInside;
|
|
536
|
-
FileLogger.log("PARSER", "Inner JSON Parsed", { keys: Object.keys(parsedObj) });
|
|
537
|
-
} else {
|
|
538
|
-
parsedObj = rawResponse;
|
|
539
|
-
FileLogger.log("PARSER", "Inner JSON was primitive");
|
|
540
|
-
}
|
|
541
|
-
} catch (e) {
|
|
542
|
-
parsedObj = rawResponse;
|
|
543
|
-
FileLogger.log("PARSER", "Inner JSON Parse Error - treating as raw", { error: e.message });
|
|
544
|
-
}
|
|
545
|
-
} else {
|
|
546
|
-
parsedObj = rawResponse;
|
|
547
|
-
}
|
|
548
|
-
if (!parsedObj.actions) {
|
|
549
|
-
parsedObj = rawResponse;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
if (!parsedObj.actions) {
|
|
553
|
-
FileLogger.log("PARSER", "No Actions Found - Constructing Default");
|
|
554
|
-
return {
|
|
555
|
-
conversation_id,
|
|
556
|
-
actions: [{
|
|
557
|
-
type: "talk_with_user",
|
|
558
|
-
content: parsedObj.message || JSON.stringify(parsedObj),
|
|
559
|
-
path: ""
|
|
560
|
-
}],
|
|
561
|
-
message: parsedObj.message
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
const result = {
|
|
565
|
-
actions: parsedObj.actions,
|
|
566
|
-
commands: parsedObj.commands || [],
|
|
567
|
-
summary: parsedObj.summary || "",
|
|
568
|
-
conversation_id,
|
|
569
|
-
message: parsedObj.summary || "Agent Action"
|
|
570
|
-
// Backward compatibility
|
|
571
|
-
};
|
|
572
|
-
FileLogger.log("PARSER", "Final Result Constructed", { actionCount: result.actions.length });
|
|
573
|
-
try {
|
|
574
|
-
return AgentResponseSchema.parse(result);
|
|
575
|
-
} catch (e) {
|
|
576
|
-
FileLogger.log("PARSER", "Schema Validation Failed", { error: e.message });
|
|
577
|
-
throw e;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
function extractFirstJson(str) {
|
|
581
|
-
try {
|
|
582
|
-
return JSON.parse(str);
|
|
583
|
-
} catch (e) {
|
|
584
|
-
const firstOpen = str.indexOf("{");
|
|
585
|
-
if (firstOpen === -1) throw e;
|
|
586
|
-
let balance = 0;
|
|
587
|
-
let inString = false;
|
|
588
|
-
let escape = false;
|
|
589
|
-
for (let i = firstOpen; i < str.length; i++) {
|
|
590
|
-
const char = str[i];
|
|
591
|
-
if (escape) {
|
|
592
|
-
escape = false;
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
if (char === "\\") {
|
|
596
|
-
escape = true;
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
if (char === '"') {
|
|
600
|
-
inString = !inString;
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (!inString) {
|
|
604
|
-
if (char === "{") balance++;
|
|
605
|
-
else if (char === "}") {
|
|
606
|
-
balance--;
|
|
607
|
-
if (balance === 0) {
|
|
608
|
-
const potentialJson = str.substring(firstOpen, i + 1);
|
|
609
|
-
try {
|
|
610
|
-
return JSON.parse(potentialJson);
|
|
611
|
-
} catch (innerE) {
|
|
612
|
-
throw e;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
throw e;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
213
|
|
|
622
|
-
// src/
|
|
623
|
-
|
|
624
|
-
static instance;
|
|
625
|
-
constructor() {
|
|
626
|
-
}
|
|
627
|
-
static getInstance() {
|
|
628
|
-
if (!_ConversationManager.instance) {
|
|
629
|
-
_ConversationManager.instance = new _ConversationManager();
|
|
630
|
-
}
|
|
631
|
-
return _ConversationManager.instance;
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* Saves a conversation ID for a specific agent type.
|
|
635
|
-
*
|
|
636
|
-
* @param agentType - The type of agent (e.g., 'business_analyst', 'architect')
|
|
637
|
-
* @param conversationId - The conversation ID from the agent response
|
|
638
|
-
*/
|
|
639
|
-
async saveConversationId(agentType, conversationId) {
|
|
640
|
-
const state = await workflowManager.load();
|
|
641
|
-
if (!state) {
|
|
642
|
-
throw new Error('No workflow state found. Please run "shark init" first.');
|
|
643
|
-
}
|
|
644
|
-
if (!state.conversations) {
|
|
645
|
-
state.conversations = {};
|
|
646
|
-
}
|
|
647
|
-
state.conversations[agentType] = conversationId;
|
|
648
|
-
await workflowManager.save(state);
|
|
649
|
-
}
|
|
650
|
-
/**
|
|
651
|
-
* Retrieves the conversation ID for a specific agent type.
|
|
652
|
-
*
|
|
653
|
-
* @param agentType - The type of agent
|
|
654
|
-
* @returns The conversation ID, or undefined if none exists
|
|
655
|
-
*/
|
|
656
|
-
async getConversationId(agentType) {
|
|
657
|
-
const state = await workflowManager.load();
|
|
658
|
-
if (!state || !state.conversations) {
|
|
659
|
-
return void 0;
|
|
660
|
-
}
|
|
661
|
-
return state.conversations[agentType];
|
|
662
|
-
}
|
|
663
|
-
/**
|
|
664
|
-
* Clears the conversation ID for a specific agent type.
|
|
665
|
-
*
|
|
666
|
-
* @param agentType - The type of agent
|
|
667
|
-
*/
|
|
668
|
-
async clearConversationId(agentType) {
|
|
669
|
-
const state = await workflowManager.load();
|
|
670
|
-
if (!state || !state.conversations) {
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
delete state.conversations[agentType];
|
|
674
|
-
await workflowManager.save(state);
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Clears all conversation IDs.
|
|
678
|
-
* Useful when transitioning to a new project or resetting state.
|
|
679
|
-
*/
|
|
680
|
-
async clearAllConversations() {
|
|
681
|
-
const state = await workflowManager.load();
|
|
682
|
-
if (!state) {
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
state.conversations = {};
|
|
686
|
-
await workflowManager.save(state);
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
var conversationManager = ConversationManager.getInstance();
|
|
214
|
+
// src/commands/legacy.ts
|
|
215
|
+
import { Command as Command3 } from "commander";
|
|
690
216
|
|
|
691
|
-
// src/core/
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
217
|
+
// src/core/agents/legacy-developer-agent.ts
|
|
218
|
+
import fs2 from "fs";
|
|
219
|
+
import path2 from "path";
|
|
220
|
+
var AGENT_TYPE = "developer_agent";
|
|
221
|
+
async function validateTypeScript(filePath) {
|
|
222
|
+
try {
|
|
223
|
+
const result = await handleRunCommand(`npx tsc --noEmit --skipLibCheck ${filePath}`);
|
|
224
|
+
if (result.trim() === "" || !result.includes("error TS")) {
|
|
225
|
+
return { valid: true };
|
|
226
|
+
}
|
|
227
|
+
return { valid: false, error: result };
|
|
228
|
+
} catch (e) {
|
|
229
|
+
return { valid: false, error: e.message || "TypeScript validation failed" };
|
|
700
230
|
}
|
|
701
|
-
return realm;
|
|
702
231
|
}
|
|
703
|
-
|
|
704
|
-
// src/core/agents/business-analyst-agent.ts
|
|
705
|
-
var AGENT_TYPE = "business_analyst";
|
|
706
232
|
function getAgentId(overrideId) {
|
|
707
233
|
if (overrideId) return overrideId;
|
|
708
234
|
const config = ConfigManager.getInstance().getConfig();
|
|
709
|
-
if (config.agents?.
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
function getAgentVersion(overrideVersion) {
|
|
713
|
-
if (overrideVersion) return overrideVersion;
|
|
714
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
715
|
-
if (config.agentVersions?.ba) return config.agentVersions.ba;
|
|
716
|
-
return process.env.STACKSPOT_BA_AGENT_VERSION;
|
|
235
|
+
if (config.agents?.dev) return config.agents.dev;
|
|
236
|
+
if (process.env.STACKSPOT_DEV_AGENT_ID) return process.env.STACKSPOT_DEV_AGENT_ID;
|
|
237
|
+
return "01KEQCGJ65YENRA4QBXVN1YFFX";
|
|
717
238
|
}
|
|
718
|
-
async function
|
|
719
|
-
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
const existingConversationId = await conversationManager.getConversationId(AGENT_TYPE);
|
|
726
|
-
const requestPayload = {
|
|
727
|
-
user_prompt: prompt,
|
|
728
|
-
streaming: true,
|
|
729
|
-
stackspot_knowledge: false,
|
|
730
|
-
// Use agent's configured KS instead
|
|
731
|
-
return_ks_in_response: true,
|
|
732
|
-
deep_search_ks: false,
|
|
733
|
-
conversation_id: existingConversationId
|
|
734
|
-
};
|
|
735
|
-
const agentVersion = getAgentVersion();
|
|
736
|
-
if (agentVersion) {
|
|
737
|
-
requestPayload.agent_version_number = agentVersion;
|
|
239
|
+
async function interactiveDeveloperAgent2(options = {}) {
|
|
240
|
+
FileLogger.init();
|
|
241
|
+
const agentId = getAgentId();
|
|
242
|
+
if (agentId === "PENDING_CONFIGURATION") {
|
|
243
|
+
tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
|
|
244
|
+
return { success: false, summary: "Missing configuration." };
|
|
738
245
|
}
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
agentUrl,
|
|
749
|
-
requestPayload,
|
|
750
|
-
headers,
|
|
751
|
-
{
|
|
752
|
-
onChunk: (chunk) => {
|
|
753
|
-
fullMessage += chunk;
|
|
754
|
-
if (onChunk) {
|
|
755
|
-
onChunk(chunk);
|
|
756
|
-
}
|
|
757
|
-
},
|
|
758
|
-
onComplete: async (message) => {
|
|
759
|
-
rawResponse = {
|
|
760
|
-
message: message || fullMessage,
|
|
761
|
-
conversation_id: existingConversationId
|
|
762
|
-
// Will be updated if new one provided
|
|
763
|
-
};
|
|
764
|
-
},
|
|
765
|
-
onError: (error) => {
|
|
766
|
-
throw error;
|
|
767
|
-
}
|
|
246
|
+
const projectRoot = process.cwd();
|
|
247
|
+
let contextContent = "";
|
|
248
|
+
const defaultContextPath = path2.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
249
|
+
const specificContextPath = options.context ? path2.resolve(projectRoot, options.context) : defaultContextPath;
|
|
250
|
+
if (fs2.existsSync(specificContextPath)) {
|
|
251
|
+
try {
|
|
252
|
+
contextContent = fs2.readFileSync(specificContextPath, "utf-8");
|
|
253
|
+
} catch (e) {
|
|
254
|
+
tui.log.warning(`Failed to read context file: ${e}`);
|
|
768
255
|
}
|
|
769
|
-
);
|
|
770
|
-
const parsedResponse = parseAgentResponse(rawResponse);
|
|
771
|
-
if (parsedResponse.conversation_id) {
|
|
772
|
-
await conversationManager.saveConversationId(AGENT_TYPE, parsedResponse.conversation_id);
|
|
773
256
|
}
|
|
774
|
-
|
|
775
|
-
|
|
257
|
+
const currentTask = options.taskInstruction || "Analyze the project and fix pending issues.";
|
|
258
|
+
let basePrompt = ``;
|
|
259
|
+
if (contextContent) {
|
|
260
|
+
basePrompt += `
|
|
261
|
+
|
|
262
|
+
--- PROJECT CONTEXT ---
|
|
263
|
+
${contextContent}
|
|
264
|
+
-----------------------
|
|
265
|
+
`;
|
|
776
266
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
validate: (value) => {
|
|
785
|
-
if (!value || value.length < 10) return "Please provide a detailed description (at least 10 characters)";
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
if (tui.isCancel(prompt)) {
|
|
789
|
-
tui.outro("Cancelled");
|
|
790
|
-
return;
|
|
267
|
+
if (options.history) {
|
|
268
|
+
basePrompt += `
|
|
269
|
+
|
|
270
|
+
--- PREVIOUS EXECUTION SUMMARY ---
|
|
271
|
+
${options.history}
|
|
272
|
+
----------------------------------
|
|
273
|
+
`;
|
|
791
274
|
}
|
|
275
|
+
basePrompt += `
|
|
276
|
+
|
|
277
|
+
\u{1F7E2} EXECUTION MODE
|
|
278
|
+
|
|
279
|
+
You are a highly skilled Developer Agent.
|
|
280
|
+
\u{1F449} **CURRENT TASK**: "${currentTask}"
|
|
281
|
+
|
|
282
|
+
Your goal is to COMPLETE this specific task and then STOP.
|
|
283
|
+
1. Implement the necessary changes.
|
|
284
|
+
2. Verify (compile/test).
|
|
285
|
+
3. **MANDATORY**: When you are confident the task is done, output a final message starting with "TASK_COMPLETED:" followed by a brief technical summary of what you did.
|
|
286
|
+
`;
|
|
287
|
+
let nextPrompt = basePrompt;
|
|
288
|
+
let keepGoing = true;
|
|
792
289
|
const spinner = tui.spinner();
|
|
793
|
-
|
|
794
|
-
let
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
290
|
+
let finalSummary = "";
|
|
291
|
+
let isTaskCompleted = false;
|
|
292
|
+
const conversationKey = options.taskId ? `dev_agent_${options.taskId}` : `dev_agent_${Date.now()}`;
|
|
293
|
+
let autoApprovals = {
|
|
294
|
+
files: false,
|
|
295
|
+
commands: false
|
|
296
|
+
};
|
|
297
|
+
while (keepGoing) {
|
|
298
|
+
try {
|
|
299
|
+
spinner.start("\u{1F988} Shark Dev working...");
|
|
300
|
+
const lastResponse = await callDevAgentApi(nextPrompt, (chunk) => {
|
|
301
|
+
}, conversationKey);
|
|
302
|
+
spinner.stop("Response received");
|
|
303
|
+
if (lastResponse) {
|
|
304
|
+
const response = lastResponse;
|
|
305
|
+
const actions = response.actions || [];
|
|
306
|
+
if (response.message && response.message.includes("TASK_COMPLETED:")) {
|
|
307
|
+
isTaskCompleted = true;
|
|
308
|
+
finalSummary = response.message.split("TASK_COMPLETED:")[1].trim();
|
|
309
|
+
keepGoing = false;
|
|
806
310
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
tui.log.info(colors.italic(response.summary));
|
|
311
|
+
if (response.message && response.message.includes("TASK_FAILED:")) {
|
|
312
|
+
const failureReason = response.message.split("TASK_FAILED:")[1].trim();
|
|
313
|
+
tui.log.error(`\u274C Agent reported task failure: ${failureReason}`);
|
|
314
|
+
return { success: false, summary: failureReason };
|
|
812
315
|
}
|
|
813
|
-
if (
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
\u{1F916} Agent wants to ${action.type}: ${colors.bold(action.path || "unknown")}`);
|
|
821
|
-
if (action.content) {
|
|
822
|
-
console.log(colors.dim("--- Content Preview ---"));
|
|
823
|
-
console.log(action.content.substring(0, 300) + (action.content.length > 300 ? "..." : ""));
|
|
824
|
-
console.log(colors.dim("-----------------------"));
|
|
825
|
-
}
|
|
826
|
-
const confirm = await tui.confirm({
|
|
827
|
-
message: `Allow agent to ${action.type} '${action.path}'?`,
|
|
828
|
-
active: "Yes",
|
|
829
|
-
inactive: "No"
|
|
830
|
-
});
|
|
831
|
-
if (confirm) {
|
|
832
|
-
tui.log.success(`\u2705 Action executed: ${action.path} created.`);
|
|
833
|
-
} else {
|
|
834
|
-
tui.log.error("\u274C Action denied.");
|
|
835
|
-
}
|
|
836
|
-
}
|
|
316
|
+
if (actions.length === 0 && response.message && !isTaskCompleted) {
|
|
317
|
+
tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
318
|
+
console.log(response.message);
|
|
319
|
+
const userReply = await tui.text({ message: "Your answer:" });
|
|
320
|
+
if (tui.isCancel(userReply)) {
|
|
321
|
+
keepGoing = false;
|
|
322
|
+
break;
|
|
837
323
|
}
|
|
324
|
+
nextPrompt = userReply;
|
|
838
325
|
}
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
tui.outro("Session complete");
|
|
842
|
-
} catch (error) {
|
|
843
|
-
spinner.stop("\u274C Error", 1);
|
|
844
|
-
tui.log.error(error.message);
|
|
845
|
-
throw error;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// src/core/agents/specification-agent.ts
|
|
850
|
-
import fs5 from "fs";
|
|
851
|
-
import path6 from "path";
|
|
852
|
-
|
|
853
|
-
// src/core/agents/agent-tools.ts
|
|
854
|
-
import fs4 from "fs";
|
|
855
|
-
import path5 from "path";
|
|
856
|
-
import fg from "fast-glob";
|
|
857
|
-
import { exec } from "child_process";
|
|
858
|
-
import { promisify } from "util";
|
|
859
|
-
import { fileURLToPath } from "url";
|
|
860
|
-
|
|
861
|
-
// src/core/ast-editing/editors/code-editor-factory.ts
|
|
862
|
-
import * as path4 from "path";
|
|
863
|
-
|
|
864
|
-
// src/core/ast-editing/editors/typescript-editor.ts
|
|
865
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
866
|
-
import * as path3 from "path";
|
|
867
|
-
import * as fs3 from "fs";
|
|
868
|
-
var TypeScriptEditor = class {
|
|
869
|
-
project;
|
|
870
|
-
constructor() {
|
|
871
|
-
this.project = new Project({
|
|
872
|
-
skipAddingFilesFromTsConfig: true,
|
|
873
|
-
compilerOptions: {
|
|
874
|
-
target: 99,
|
|
875
|
-
// ESNext
|
|
876
|
-
module: 99
|
|
877
|
-
// ESNext
|
|
878
|
-
}
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* Get or add source file to project
|
|
883
|
-
*/
|
|
884
|
-
getSourceFile(filePath) {
|
|
885
|
-
const absolutePath = path3.resolve(filePath);
|
|
886
|
-
let sourceFile = this.project.getSourceFile(absolutePath);
|
|
887
|
-
if (!sourceFile) {
|
|
888
|
-
if (!fs3.existsSync(absolutePath)) {
|
|
889
|
-
throw new Error(`File not found: ${absolutePath}`);
|
|
890
|
-
}
|
|
891
|
-
sourceFile = this.project.addSourceFileAtPath(absolutePath);
|
|
892
|
-
}
|
|
893
|
-
return sourceFile;
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Get class declaration by name
|
|
897
|
-
*/
|
|
898
|
-
getClass(sourceFile, className) {
|
|
899
|
-
const classDecl = sourceFile.getClass(className);
|
|
900
|
-
if (!classDecl) {
|
|
901
|
-
throw new Error(`Class "${className}" not found in ${sourceFile.getFilePath()}`);
|
|
902
|
-
}
|
|
903
|
-
return classDecl;
|
|
904
|
-
}
|
|
905
|
-
// ═══════════════════════════════════════════════════════
|
|
906
|
-
// IMPLEMENTATION: listStructure
|
|
907
|
-
// ═══════════════════════════════════════════════════════
|
|
908
|
-
async listStructure(filePath) {
|
|
909
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
910
|
-
const classes = sourceFile.getClasses().map((cls) => ({
|
|
911
|
-
name: cls.getName() || "<anonymous>",
|
|
912
|
-
methods: cls.getMethods().map((m) => this.extractMethodInfo(m)),
|
|
913
|
-
properties: cls.getProperties().map((p) => this.extractPropertyInfo(p)),
|
|
914
|
-
decorators: cls.getDecorators().map((d) => d.getText()),
|
|
915
|
-
extendsClass: cls.getExtends()?.getText(),
|
|
916
|
-
implementsInterfaces: cls.getImplements().map((i) => i.getText())
|
|
917
|
-
}));
|
|
918
|
-
const interfaces = sourceFile.getInterfaces().map((iface) => ({
|
|
919
|
-
name: iface.getName(),
|
|
920
|
-
properties: iface.getProperties().map((p) => this.extractPropertyInfo(p)),
|
|
921
|
-
extends: iface.getExtends().map((e) => e.getText())
|
|
922
|
-
}));
|
|
923
|
-
const functions = sourceFile.getFunctions().map((fn) => ({
|
|
924
|
-
name: fn.getName() || "<anonymous>",
|
|
925
|
-
parameters: fn.getParameters().map((p) => ({
|
|
926
|
-
name: p.getName(),
|
|
927
|
-
type: p.getType().getText(),
|
|
928
|
-
isOptional: p.isOptional()
|
|
929
|
-
})),
|
|
930
|
-
returnType: fn.getReturnType().getText(),
|
|
931
|
-
isAsync: fn.isAsync(),
|
|
932
|
-
isExported: fn.isExported()
|
|
933
|
-
}));
|
|
934
|
-
const imports = sourceFile.getImportDeclarations().map((imp) => ({
|
|
935
|
-
modulePath: imp.getModuleSpecifierValue(),
|
|
936
|
-
isDefault: !!imp.getDefaultImport(),
|
|
937
|
-
namedImports: imp.getNamedImports().map((n) => n.getName()),
|
|
938
|
-
namespaceImport: imp.getNamespaceImport()?.getText()
|
|
939
|
-
}));
|
|
940
|
-
const exports = sourceFile.getExportedDeclarations();
|
|
941
|
-
const exportInfo = Array.from(exports.entries()).flatMap(
|
|
942
|
-
([name, declarations]) => declarations.map((decl) => ({
|
|
943
|
-
name,
|
|
944
|
-
type: this.getDeclarationType(decl),
|
|
945
|
-
isDefault: sourceFile.getDefaultExportSymbol()?.getName() === name
|
|
946
|
-
}))
|
|
947
|
-
);
|
|
948
|
-
return {
|
|
949
|
-
classes,
|
|
950
|
-
interfaces,
|
|
951
|
-
functions,
|
|
952
|
-
imports,
|
|
953
|
-
exports: exportInfo
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
extractMethodInfo(method) {
|
|
957
|
-
return {
|
|
958
|
-
name: method.getName(),
|
|
959
|
-
parameters: method.getParameters().map((p) => ({
|
|
960
|
-
name: p.getName(),
|
|
961
|
-
type: p.getType().getText(),
|
|
962
|
-
isOptional: p.isOptional()
|
|
963
|
-
})),
|
|
964
|
-
returnType: method.getReturnType().getText(),
|
|
965
|
-
isAsync: method.isAsync(),
|
|
966
|
-
isStatic: method.isStatic(),
|
|
967
|
-
visibility: this.getVisibility(method),
|
|
968
|
-
decorators: method.getDecorators().map((d) => d.getText())
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
extractPropertyInfo(property) {
|
|
972
|
-
return {
|
|
973
|
-
name: property.getName(),
|
|
974
|
-
type: property.getType()?.getText(),
|
|
975
|
-
visibility: this.getVisibility(property),
|
|
976
|
-
isReadonly: property.isReadonly(),
|
|
977
|
-
initializer: property.getInitializer()?.getText()
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
getVisibility(node) {
|
|
981
|
-
if (node.hasModifier?.(SyntaxKind.PrivateKeyword)) return "private";
|
|
982
|
-
if (node.hasModifier?.(SyntaxKind.ProtectedKeyword)) return "protected";
|
|
983
|
-
return "public";
|
|
984
|
-
}
|
|
985
|
-
getDeclarationType(decl) {
|
|
986
|
-
if (decl.getKind() === SyntaxKind.ClassDeclaration) return "class";
|
|
987
|
-
if (decl.getKind() === SyntaxKind.FunctionDeclaration) return "function";
|
|
988
|
-
if (decl.getKind() === SyntaxKind.InterfaceDeclaration) return "interface";
|
|
989
|
-
if (decl.getKind() === SyntaxKind.TypeAliasDeclaration) return "type";
|
|
990
|
-
return "const";
|
|
991
|
-
}
|
|
992
|
-
// ═══════════════════════════════════════════════════════
|
|
993
|
-
// IMPLEMENTATION: Class Operations
|
|
994
|
-
// ═══════════════════════════════════════════════════════
|
|
995
|
-
async addClass(filePath, className, options) {
|
|
996
|
-
try {
|
|
997
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
998
|
-
sourceFile.addClass({
|
|
999
|
-
name: className,
|
|
1000
|
-
extends: options?.extendsClass,
|
|
1001
|
-
implements: options?.implementsInterfaces,
|
|
1002
|
-
isExported: true
|
|
1003
|
-
});
|
|
1004
|
-
await sourceFile.save();
|
|
1005
|
-
return true;
|
|
1006
|
-
} catch (error) {
|
|
1007
|
-
console.error(`Failed to add class "${className}":`, error);
|
|
1008
|
-
return false;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
async addProperty(filePath, className, propertyCode) {
|
|
1012
|
-
try {
|
|
1013
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1014
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1015
|
-
const closeBrace = classDecl.getEnd() - 1;
|
|
1016
|
-
classDecl.insertText(closeBrace, `
|
|
1017
|
-
${propertyCode}`);
|
|
1018
|
-
classDecl.formatText();
|
|
1019
|
-
await sourceFile.save();
|
|
1020
|
-
return true;
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
console.error(`Failed to add property to "${className}":`, error);
|
|
1023
|
-
return false;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
async getProperty(filePath, className, propertyName) {
|
|
1027
|
-
try {
|
|
1028
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1029
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1030
|
-
const property = classDecl.getProperty(propertyName);
|
|
1031
|
-
if (!property) {
|
|
1032
|
-
return void 0;
|
|
1033
|
-
}
|
|
1034
|
-
return property.getText();
|
|
1035
|
-
} catch (error) {
|
|
1036
|
-
console.error(`Failed to get property "${propertyName}":`, error);
|
|
1037
|
-
return void 0;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
async modifyProperty(filePath, className, propertyName, newCode) {
|
|
1041
|
-
try {
|
|
1042
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1043
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1044
|
-
const property = classDecl.getProperty(propertyName);
|
|
1045
|
-
if (!property) {
|
|
1046
|
-
throw new Error(`Property "${propertyName}" not found in class "${className}"`);
|
|
1047
|
-
}
|
|
1048
|
-
property.replaceWithText(newCode);
|
|
1049
|
-
classDecl.formatText();
|
|
1050
|
-
await sourceFile.save();
|
|
1051
|
-
return true;
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
console.error(`Failed to modify property "${propertyName}":`, error);
|
|
1054
|
-
return false;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
async removeProperty(filePath, className, propertyName) {
|
|
1058
|
-
try {
|
|
1059
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1060
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1061
|
-
const property = classDecl.getProperty(propertyName);
|
|
1062
|
-
if (!property) {
|
|
1063
|
-
throw new Error(`Property "${propertyName}" not found in class "${className}"`);
|
|
1064
|
-
}
|
|
1065
|
-
property.remove();
|
|
1066
|
-
await sourceFile.save();
|
|
1067
|
-
return true;
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
console.error(`Failed to remove property "${propertyName}":`, error);
|
|
1070
|
-
return false;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
async addMethod(filePath, className, methodCode) {
|
|
1074
|
-
try {
|
|
1075
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1076
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1077
|
-
const closeBrace = classDecl.getEnd() - 1;
|
|
1078
|
-
classDecl.insertText(closeBrace, `
|
|
1079
|
-
${methodCode}
|
|
1080
|
-
`);
|
|
1081
|
-
classDecl.formatText();
|
|
1082
|
-
await sourceFile.save();
|
|
1083
|
-
return true;
|
|
1084
|
-
} catch (error) {
|
|
1085
|
-
console.error(`Failed to add method to "${className}":`, error);
|
|
1086
|
-
return false;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
async modifyMethod(filePath, className, methodName, newBody) {
|
|
1090
|
-
try {
|
|
1091
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1092
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1093
|
-
const method = classDecl.getMethod(methodName);
|
|
1094
|
-
if (!method) {
|
|
1095
|
-
throw new Error(`Method "${methodName}" not found in class "${className}"`);
|
|
1096
|
-
}
|
|
1097
|
-
method.setBodyText(newBody);
|
|
1098
|
-
method.formatText();
|
|
1099
|
-
await sourceFile.save();
|
|
1100
|
-
return true;
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
console.error(`Failed to modify method "${methodName}":`, error);
|
|
1103
|
-
return false;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
async removeMethod(filePath, className, methodName) {
|
|
1107
|
-
try {
|
|
1108
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1109
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1110
|
-
const method = classDecl.getMethod(methodName);
|
|
1111
|
-
if (!method) {
|
|
1112
|
-
throw new Error(`Method "${methodName}" not found in class "${className}"`);
|
|
1113
|
-
}
|
|
1114
|
-
method.remove();
|
|
1115
|
-
await sourceFile.save();
|
|
1116
|
-
return true;
|
|
1117
|
-
} catch (error) {
|
|
1118
|
-
console.error(`Failed to remove method "${methodName}":`, error);
|
|
1119
|
-
return false;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
async addDecorator(filePath, className, decoratorCode) {
|
|
1123
|
-
try {
|
|
1124
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1125
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1126
|
-
const start = classDecl.getStart();
|
|
1127
|
-
sourceFile.insertText(start, `${decoratorCode}
|
|
1128
|
-
`);
|
|
1129
|
-
sourceFile.formatText();
|
|
1130
|
-
await sourceFile.save();
|
|
1131
|
-
return true;
|
|
1132
|
-
} catch (error) {
|
|
1133
|
-
console.error(`Failed to add decorator to "${className}":`, error);
|
|
1134
|
-
return false;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
async getMethod(filePath, className, methodName) {
|
|
1138
|
-
try {
|
|
1139
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1140
|
-
const classDecl = this.getClass(sourceFile, className);
|
|
1141
|
-
const method = classDecl.getMethod(methodName);
|
|
1142
|
-
if (!method) {
|
|
1143
|
-
return void 0;
|
|
1144
|
-
}
|
|
1145
|
-
return method.getText();
|
|
1146
|
-
} catch (error) {
|
|
1147
|
-
console.error(`Failed to get method "${methodName}":`, error);
|
|
1148
|
-
return void 0;
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
// ═══════════════════════════════════════════════════════
|
|
1152
|
-
// IMPLEMENTATION: Interface/Type Operations
|
|
1153
|
-
// ═══════════════════════════════════════════════════════
|
|
1154
|
-
async addInterface(filePath, interfaceCode) {
|
|
1155
|
-
try {
|
|
1156
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1157
|
-
sourceFile.insertText(sourceFile.getEnd(), `
|
|
1158
|
-
|
|
1159
|
-
${interfaceCode}`);
|
|
1160
|
-
sourceFile.formatText();
|
|
1161
|
-
await sourceFile.save();
|
|
1162
|
-
return true;
|
|
1163
|
-
} catch (error) {
|
|
1164
|
-
console.error(`Failed to add interface:`, error);
|
|
1165
|
-
return false;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
async addTypeAlias(filePath, typeCode) {
|
|
1169
|
-
try {
|
|
1170
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1171
|
-
sourceFile.insertText(sourceFile.getEnd(), `
|
|
1172
|
-
|
|
1173
|
-
${typeCode}`);
|
|
1174
|
-
sourceFile.formatText();
|
|
1175
|
-
await sourceFile.save();
|
|
1176
|
-
return true;
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
console.error(`Failed to add type alias:`, error);
|
|
1179
|
-
return false;
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
// ═══════════════════════════════════════════════════════
|
|
1183
|
-
// IMPLEMENTATION: Function Operations
|
|
1184
|
-
// ═══════════════════════════════════════════════════════
|
|
1185
|
-
async addFunction(filePath, functionCode) {
|
|
1186
|
-
try {
|
|
1187
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1188
|
-
sourceFile.insertText(sourceFile.getEnd(), `
|
|
1189
|
-
|
|
1190
|
-
${functionCode}`);
|
|
1191
|
-
sourceFile.formatText();
|
|
1192
|
-
await sourceFile.save();
|
|
1193
|
-
return true;
|
|
1194
|
-
} catch (error) {
|
|
1195
|
-
console.error(`Failed to add function:`, error);
|
|
1196
|
-
return false;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
async removeFunction(filePath, functionName) {
|
|
1200
|
-
try {
|
|
1201
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1202
|
-
const func = sourceFile.getFunction(functionName);
|
|
1203
|
-
if (!func) {
|
|
1204
|
-
throw new Error(`Function "${functionName}" not found`);
|
|
1205
|
-
}
|
|
1206
|
-
func.remove();
|
|
1207
|
-
await sourceFile.save();
|
|
1208
|
-
return true;
|
|
1209
|
-
} catch (error) {
|
|
1210
|
-
console.error(`Failed to remove function "${functionName}":`, error);
|
|
1211
|
-
return false;
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
// ═══════════════════════════════════════════════════════
|
|
1215
|
-
// IMPLEMENTATION: Import/Export Operations
|
|
1216
|
-
// ═══════════════════════════════════════════════════════
|
|
1217
|
-
async addImport(filePath, importStatement) {
|
|
1218
|
-
try {
|
|
1219
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1220
|
-
const lastImport = sourceFile.getImportDeclarations().pop();
|
|
1221
|
-
const pos = lastImport ? lastImport.getEnd() : 0;
|
|
1222
|
-
sourceFile.insertText(pos, `
|
|
1223
|
-
${importStatement}`);
|
|
1224
|
-
this.organizeImports(filePath);
|
|
1225
|
-
await sourceFile.save();
|
|
1226
|
-
return true;
|
|
1227
|
-
} catch (error) {
|
|
1228
|
-
console.error(`Failed to add import:`, error);
|
|
1229
|
-
return false;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
async removeImport(filePath, modulePath) {
|
|
1233
|
-
try {
|
|
1234
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1235
|
-
const importDecls = sourceFile.getImportDeclarations().filter((imp) => imp.getModuleSpecifierValue() === modulePath);
|
|
1236
|
-
if (importDecls.length === 0) {
|
|
1237
|
-
throw new Error(`Import from "${modulePath}" not found`);
|
|
1238
|
-
}
|
|
1239
|
-
importDecls.forEach((d) => d.remove());
|
|
1240
|
-
await sourceFile.save();
|
|
1241
|
-
return true;
|
|
1242
|
-
} catch (error) {
|
|
1243
|
-
console.error(`Failed to remove import from "${modulePath}":`, error);
|
|
1244
|
-
return false;
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
async organizeImports(filePath) {
|
|
1248
|
-
try {
|
|
1249
|
-
const sourceFile = this.getSourceFile(filePath);
|
|
1250
|
-
sourceFile.organizeImports();
|
|
1251
|
-
const imports = sourceFile.getImportDeclarations();
|
|
1252
|
-
const importStructure = imports.map((i) => i.getStructure());
|
|
1253
|
-
importStructure.sort((a, b) => {
|
|
1254
|
-
return a.moduleSpecifier.localeCompare(b.moduleSpecifier);
|
|
1255
|
-
});
|
|
1256
|
-
imports.forEach((i) => i.remove());
|
|
1257
|
-
sourceFile.addImportDeclarations(importStructure);
|
|
1258
|
-
await sourceFile.save();
|
|
1259
|
-
return true;
|
|
1260
|
-
} catch (error) {
|
|
1261
|
-
console.error(`Failed to organize imports:`, error);
|
|
1262
|
-
return false;
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
};
|
|
1266
|
-
|
|
1267
|
-
// src/core/ast-editing/editors/code-editor-factory.ts
|
|
1268
|
-
var CodeEditorFactory = class {
|
|
1269
|
-
static tsEditor = null;
|
|
1270
|
-
/**
|
|
1271
|
-
* Returns appropriate editor for the file, or null if AST editing not supported
|
|
1272
|
-
*/
|
|
1273
|
-
static getEditor(filePath) {
|
|
1274
|
-
const ext = path4.extname(filePath).toLowerCase();
|
|
1275
|
-
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
1276
|
-
if (!this.tsEditor) {
|
|
1277
|
-
this.tsEditor = new TypeScriptEditor();
|
|
1278
|
-
}
|
|
1279
|
-
return this.tsEditor;
|
|
1280
|
-
}
|
|
1281
|
-
return null;
|
|
1282
|
-
}
|
|
1283
|
-
/**
|
|
1284
|
-
* Clear cached editors (useful for testing)
|
|
1285
|
-
*/
|
|
1286
|
-
static clearCache() {
|
|
1287
|
-
this.tsEditor = null;
|
|
1288
|
-
}
|
|
1289
|
-
};
|
|
1290
|
-
|
|
1291
|
-
// src/core/agents/agent-tools.ts
|
|
1292
|
-
var execAsync = promisify(exec);
|
|
1293
|
-
function detectLineEnding(content) {
|
|
1294
|
-
const crlf = content.split("\r\n").length - 1;
|
|
1295
|
-
const lf = content.split("\n").length - 1 - crlf;
|
|
1296
|
-
return crlf > lf ? "\r\n" : "\n";
|
|
1297
|
-
}
|
|
1298
|
-
function handleListFiles(dirPath) {
|
|
1299
|
-
try {
|
|
1300
|
-
const fullPath = path5.resolve(process.cwd(), dirPath);
|
|
1301
|
-
if (!fs4.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
|
|
1302
|
-
const items = fs4.readdirSync(fullPath, { withFileTypes: true });
|
|
1303
|
-
return items.map((item) => {
|
|
1304
|
-
return `${item.isDirectory() ? "[DIR]" : "[FILE]"} ${item.name}`;
|
|
1305
|
-
}).join("\n");
|
|
1306
|
-
} catch (e) {
|
|
1307
|
-
return `Error listing files: ${e.message}`;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
function handleReadFile(filePath, showLineNumbers = true) {
|
|
1311
|
-
try {
|
|
1312
|
-
const fullPath = path5.resolve(process.cwd(), filePath);
|
|
1313
|
-
if (!fs4.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
|
|
1314
|
-
const stats = fs4.statSync(fullPath);
|
|
1315
|
-
if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
|
|
1316
|
-
const content = fs4.readFileSync(fullPath, "utf-8");
|
|
1317
|
-
if (showLineNumbers) {
|
|
1318
|
-
const lines = content.split("\n");
|
|
1319
|
-
return lines.map((line, idx) => `${idx + 1}: ${line}`).join("\n");
|
|
1320
|
-
}
|
|
1321
|
-
return content;
|
|
1322
|
-
} catch (e) {
|
|
1323
|
-
return `Error reading file: ${e.message}`;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
|
|
1327
|
-
try {
|
|
1328
|
-
if (!fs4.existsSync(filePath)) {
|
|
1329
|
-
tui2.log.error(`\u274C File not found for modification: ${filePath}`);
|
|
1330
|
-
return false;
|
|
1331
|
-
}
|
|
1332
|
-
const currentFileContent = fs4.readFileSync(filePath, "utf-8");
|
|
1333
|
-
const lineEnding = detectLineEnding(currentFileContent);
|
|
1334
|
-
const lines = currentFileContent.split(lineEnding);
|
|
1335
|
-
if (startLine < 1 || startLine > lines.length) {
|
|
1336
|
-
tui2.log.error(`\u274C Invalid start line: ${startLine}. File has ${lines.length} lines.`);
|
|
1337
|
-
return false;
|
|
1338
|
-
}
|
|
1339
|
-
if (endLine < startLine || endLine > lines.length) {
|
|
1340
|
-
tui2.log.error(`\u274C Invalid end line: ${endLine}. Must be >= startLine and <= file length.`);
|
|
1341
|
-
return false;
|
|
1342
|
-
}
|
|
1343
|
-
const before = lines.slice(0, startLine - 1);
|
|
1344
|
-
const after = lines.slice(endLine);
|
|
1345
|
-
const newLines = newContent.split(lineEnding);
|
|
1346
|
-
const normalizedNewLines = newContent.replace(/\r\n/g, "\n").split("\n");
|
|
1347
|
-
const result = [...before, ...normalizedNewLines, ...after].join(lineEnding);
|
|
1348
|
-
const BOM = "\uFEFF";
|
|
1349
|
-
const finalContent = result.startsWith(BOM) ? result : BOM + result;
|
|
1350
|
-
fs4.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
|
|
1351
|
-
tui2.log.success(`\u2705 Replaced lines ${startLine}-${endLine} in ${filePath}`);
|
|
1352
|
-
return true;
|
|
1353
|
-
} catch (e) {
|
|
1354
|
-
tui2.log.error(`\u274C Error replacing line range: ${e.message}`);
|
|
1355
|
-
return false;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
function handleSearchFile(pattern) {
|
|
1359
|
-
try {
|
|
1360
|
-
const entries = fg.sync(pattern, { dot: true });
|
|
1361
|
-
if (entries.length === 0) return "No files found matching pattern.";
|
|
1362
|
-
return entries.slice(0, 50).join("\n");
|
|
1363
|
-
} catch (e) {
|
|
1364
|
-
return `Error searching files: ${e.message}`;
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
function handleSearchCode(globPattern, query, isRegex = false) {
|
|
1368
|
-
const MAX_MATCHES = 50;
|
|
1369
|
-
const MAX_FILE_SIZE_BYTES = 500 * 1024;
|
|
1370
|
-
try {
|
|
1371
|
-
const files = fg.sync(globPattern, { dot: true, absolute: false });
|
|
1372
|
-
if (files.length === 0) return `No files found matching pattern: "${globPattern}"`;
|
|
1373
|
-
let searchRegex;
|
|
1374
|
-
try {
|
|
1375
|
-
searchRegex = isRegex ? new RegExp(query, "gi") : new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
|
|
1376
|
-
} catch {
|
|
1377
|
-
return `Error: Invalid regex pattern: "${query}"`;
|
|
1378
|
-
}
|
|
1379
|
-
const results = [];
|
|
1380
|
-
let totalMatches = 0;
|
|
1381
|
-
for (const filePath of files) {
|
|
1382
|
-
if (totalMatches >= MAX_MATCHES) break;
|
|
1383
|
-
try {
|
|
1384
|
-
const fullPath = path5.resolve(process.cwd(), filePath);
|
|
1385
|
-
const stats = fs4.statSync(fullPath);
|
|
1386
|
-
if (stats.size > MAX_FILE_SIZE_BYTES) continue;
|
|
1387
|
-
const content = fs4.readFileSync(fullPath, "utf-8");
|
|
1388
|
-
const lines = content.split("\n");
|
|
1389
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1390
|
-
if (totalMatches >= MAX_MATCHES) break;
|
|
1391
|
-
searchRegex.lastIndex = 0;
|
|
1392
|
-
if (searchRegex.test(lines[i])) {
|
|
1393
|
-
results.push(`${filePath}:${i + 1}: ${lines[i].trim()}`);
|
|
1394
|
-
totalMatches++;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
} catch {
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
if (results.length === 0) {
|
|
1401
|
-
return `No matches found for "${query}" in files matching "${globPattern}"`;
|
|
1402
|
-
}
|
|
1403
|
-
const limited = totalMatches >= MAX_MATCHES ? ` (limited to ${MAX_MATCHES})` : "";
|
|
1404
|
-
return `Found ${totalMatches} match(es) for "${query}" in "${globPattern}"${limited}:
|
|
1405
|
-
${results.join("\n")}`;
|
|
1406
|
-
} catch (e) {
|
|
1407
|
-
return `Error searching code: ${e.message}`;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
function startSmartReplace(filePath, newContent, targetContent, tui2) {
|
|
1411
|
-
if (!fs4.existsSync(filePath)) {
|
|
1412
|
-
tui2.log.error(`\u274C File not found for modification: ${filePath}`);
|
|
1413
|
-
return false;
|
|
1414
|
-
}
|
|
1415
|
-
const currentFileContent = fs4.readFileSync(filePath, "utf-8");
|
|
1416
|
-
const normalizedTarget = targetContent.replace(/\r\n/g, "\n");
|
|
1417
|
-
const normalizedContent = currentFileContent.replace(/\r\n/g, "\n");
|
|
1418
|
-
if (!normalizedContent.includes(normalizedTarget)) {
|
|
1419
|
-
tui2.log.error(`\u274C Target content not found in ${filePath} (checked with normalized line endings). Modification aborted.`);
|
|
1420
|
-
console.log(colors.dim("--- Target Content Expected ---"));
|
|
1421
|
-
console.log(targetContent.substring(0, 200) + "...");
|
|
1422
|
-
return false;
|
|
1423
|
-
}
|
|
1424
|
-
const occurrences = currentFileContent.split(targetContent).length - 1;
|
|
1425
|
-
if (occurrences > 1) {
|
|
1426
|
-
tui2.log.error(`\u274C Ambiguous target: Found ${occurrences} occurrences in ${filePath}. Modification aborted.`);
|
|
1427
|
-
return false;
|
|
1428
|
-
}
|
|
1429
|
-
const BOM = "\uFEFF";
|
|
1430
|
-
const updatedContent = currentFileContent.replace(targetContent, newContent);
|
|
1431
|
-
const finalContent = updatedContent.startsWith(BOM) ? updatedContent : BOM + updatedContent;
|
|
1432
|
-
fs4.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
|
|
1433
|
-
tui2.log.success(`\u2705 Smart Replace Applied: ${filePath}`);
|
|
1434
|
-
return true;
|
|
1435
|
-
}
|
|
1436
|
-
async function handleRunCommand(command) {
|
|
1437
|
-
const { spawn } = await import("child_process");
|
|
1438
|
-
try {
|
|
1439
|
-
tui.log.info(`\u{1F4BB} Executing: ${colors.dim(command)}`);
|
|
1440
|
-
return new Promise((resolve2) => {
|
|
1441
|
-
const child = spawn(command, {
|
|
1442
|
-
shell: true,
|
|
1443
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1444
|
-
cwd: process.cwd()
|
|
1445
|
-
});
|
|
1446
|
-
let stdout = "";
|
|
1447
|
-
let stderr = "";
|
|
1448
|
-
const timer = setTimeout(() => {
|
|
1449
|
-
child.kill();
|
|
1450
|
-
resolve2(`Error: Command timed out after 5 minutes.
|
|
1451
|
-
Output so far:
|
|
1452
|
-
${stdout}
|
|
1453
|
-
${stderr}`);
|
|
1454
|
-
}, 5 * 60 * 1e3);
|
|
1455
|
-
child.stdout.on("data", (data) => {
|
|
1456
|
-
const chunk = data.toString();
|
|
1457
|
-
stdout += chunk;
|
|
1458
|
-
});
|
|
1459
|
-
child.stderr.on("data", (data) => {
|
|
1460
|
-
stderr += data.toString();
|
|
1461
|
-
});
|
|
1462
|
-
child.on("close", (code) => {
|
|
1463
|
-
clearTimeout(timer);
|
|
1464
|
-
if (code === 0) {
|
|
1465
|
-
resolve2(stdout.trim() || "Command executed successfully (no output).");
|
|
1466
|
-
} else {
|
|
1467
|
-
resolve2(`Command failed with exit code ${code}.
|
|
1468
|
-
STDERR:
|
|
1469
|
-
${stderr}
|
|
1470
|
-
STDOUT:
|
|
1471
|
-
${stdout}`);
|
|
1472
|
-
}
|
|
1473
|
-
});
|
|
1474
|
-
child.on("error", (err) => {
|
|
1475
|
-
clearTimeout(timer);
|
|
1476
|
-
resolve2(`Error executing command: ${err.message}`);
|
|
1477
|
-
});
|
|
1478
|
-
});
|
|
1479
|
-
} catch (e) {
|
|
1480
|
-
return `Error launching command: ${e.message}`;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
function resolveAstGrepCommand() {
|
|
1484
|
-
const isWin = process.platform === "win32";
|
|
1485
|
-
const binName = isWin ? "sg.cmd" : "sg";
|
|
1486
|
-
try {
|
|
1487
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
1488
|
-
let dir = path5.dirname(currentFile);
|
|
1489
|
-
for (let i = 0; i < 5; i++) {
|
|
1490
|
-
const candidate = path5.join(dir, "node_modules", ".bin", binName);
|
|
1491
|
-
if (fs4.existsSync(candidate)) {
|
|
1492
|
-
return `"${candidate}"`;
|
|
1493
|
-
}
|
|
1494
|
-
const parent = path5.dirname(dir);
|
|
1495
|
-
if (parent === dir) break;
|
|
1496
|
-
dir = parent;
|
|
1497
|
-
}
|
|
1498
|
-
} catch (e) {
|
|
1499
|
-
}
|
|
1500
|
-
const cwdBin = path5.resolve(process.cwd(), "node_modules", ".bin", binName);
|
|
1501
|
-
if (fs4.existsSync(cwdBin)) {
|
|
1502
|
-
return `"${cwdBin}"`;
|
|
1503
|
-
}
|
|
1504
|
-
return "npx sg";
|
|
1505
|
-
}
|
|
1506
|
-
async function astGrepSearch(pattern, filePath, language, tui2) {
|
|
1507
|
-
const { spawn } = await import("child_process");
|
|
1508
|
-
try {
|
|
1509
|
-
if (!fs4.existsSync(filePath)) {
|
|
1510
|
-
return `\u274C File not found: ${filePath}`;
|
|
1511
|
-
}
|
|
1512
|
-
const sgCmd = resolveAstGrepCommand();
|
|
1513
|
-
const cmd = `${sgCmd} run -p "${pattern}" -l ${language} --json ${filePath}`;
|
|
1514
|
-
tui2.log.info(`\u{1F50D} [AST-GREP] Searching: ${cmd}`);
|
|
1515
|
-
return new Promise((resolve2) => {
|
|
1516
|
-
const child = spawn(cmd, {
|
|
1517
|
-
shell: true,
|
|
1518
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1519
|
-
cwd: process.cwd(),
|
|
1520
|
-
env: { ...process.env, NO_COLOR: "true" }
|
|
1521
|
-
// Avoid ANSI codes in JSON output
|
|
1522
|
-
});
|
|
1523
|
-
let stdout = "";
|
|
1524
|
-
let stderr = "";
|
|
1525
|
-
child.stdout.on("data", (data) => stdout += data.toString());
|
|
1526
|
-
child.stderr.on("data", (data) => stderr += data.toString());
|
|
1527
|
-
child.on("close", (code) => {
|
|
1528
|
-
if (code === 0 && stdout) {
|
|
1529
|
-
resolve2(stdout);
|
|
1530
|
-
} else if (code === 1 && !stderr) {
|
|
1531
|
-
resolve2("No structural matches found.");
|
|
1532
|
-
} else {
|
|
1533
|
-
if (!stdout && !stderr) resolve2("No structural matches found.");
|
|
1534
|
-
else {
|
|
1535
|
-
tui2.log.error(`\u274C ast-grep search error (code ${code}): ${stderr}`);
|
|
1536
|
-
resolve2(`Error executing ast-grep search: ${stderr || stdout}`);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
});
|
|
1540
|
-
child.on("error", (err) => {
|
|
1541
|
-
resolve2(`Error executing ast-grep search: ${err.message}`);
|
|
1542
|
-
});
|
|
1543
|
-
});
|
|
1544
|
-
} catch (e) {
|
|
1545
|
-
tui2.log.error(`\u274C ast-grep search exception: ${e.message}`);
|
|
1546
|
-
return `Error executing ast-grep search: ${e.message}`;
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
async function astGrepRewrite(pattern, fix, filePath, language, tui2) {
|
|
1550
|
-
const { spawn } = await import("child_process");
|
|
1551
|
-
try {
|
|
1552
|
-
if (!fs4.existsSync(filePath)) {
|
|
1553
|
-
tui2.log.error(`\u274C File not found for AST modification: ${filePath}`);
|
|
1554
|
-
return false;
|
|
1555
|
-
}
|
|
1556
|
-
const sgCmd = resolveAstGrepCommand();
|
|
1557
|
-
const cmd = `${sgCmd} run -p "${pattern}" -r "${fix}" -l ${language} ${filePath} --update-all`;
|
|
1558
|
-
tui2.log.info(`\u270F\uFE0F [AST-GREP] Rewriting: pattern="${pattern}" fix="${fix.substring(0, 50)}..."`);
|
|
1559
|
-
return new Promise((resolve2) => {
|
|
1560
|
-
const child = spawn(cmd, {
|
|
1561
|
-
shell: true,
|
|
1562
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1563
|
-
cwd: process.cwd()
|
|
1564
|
-
});
|
|
1565
|
-
let stderr = "";
|
|
1566
|
-
child.stderr.on("data", (data) => stderr += data.toString());
|
|
1567
|
-
child.on("close", (code) => {
|
|
1568
|
-
if (code === 0) {
|
|
1569
|
-
tui2.log.success(`\u2705 AST Rewrite applied to ${filePath}`);
|
|
1570
|
-
resolve2(true);
|
|
1571
|
-
} else {
|
|
1572
|
-
tui2.log.error(`\u274C AST Rewrite failed (code ${code}): ${stderr}`);
|
|
1573
|
-
resolve2(false);
|
|
1574
|
-
}
|
|
1575
|
-
});
|
|
1576
|
-
child.on("error", (err) => {
|
|
1577
|
-
tui2.log.error(`\u274C AST Rewrite spawn error: ${err.message}`);
|
|
1578
|
-
resolve2(false);
|
|
1579
|
-
});
|
|
1580
|
-
});
|
|
1581
|
-
} catch (e) {
|
|
1582
|
-
tui2.log.error(`\u274C Unexpected error in astGrepRewrite: ${e.message}`);
|
|
1583
|
-
return false;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
async function astListStructure(filePath) {
|
|
1587
|
-
try {
|
|
1588
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1589
|
-
if (!editor) {
|
|
1590
|
-
return `[AST Error] File type not supported: ${filePath}. Use read_file instead.`;
|
|
1591
|
-
}
|
|
1592
|
-
const structure = await editor.listStructure(filePath);
|
|
1593
|
-
let output = `[AST Structure of ${filePath}]
|
|
1594
|
-
|
|
1595
|
-
`;
|
|
1596
|
-
if (structure.classes.length > 0) {
|
|
1597
|
-
output += `CLASSES:
|
|
1598
|
-
`;
|
|
1599
|
-
structure.classes.forEach((cls) => {
|
|
1600
|
-
output += ` - ${cls.name}`;
|
|
1601
|
-
if (cls.extendsClass) output += ` extends ${cls.extendsClass}`;
|
|
1602
|
-
if (cls.implementsInterfaces.length > 0) {
|
|
1603
|
-
output += ` implements ${cls.implementsInterfaces.join(", ")}`;
|
|
1604
|
-
}
|
|
1605
|
-
output += `
|
|
1606
|
-
`;
|
|
1607
|
-
if (cls.decorators.length > 0) {
|
|
1608
|
-
output += ` Decorators: ${cls.decorators.join(", ")}
|
|
1609
|
-
`;
|
|
1610
|
-
}
|
|
1611
|
-
if (cls.properties.length > 0) {
|
|
1612
|
-
output += ` Properties:
|
|
1613
|
-
`;
|
|
1614
|
-
cls.properties.forEach((prop) => {
|
|
1615
|
-
output += ` - ${prop.visibility} ${prop.name}: ${prop.type}
|
|
1616
|
-
`;
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
|
-
if (cls.methods.length > 0) {
|
|
1620
|
-
output += ` Methods:
|
|
1621
|
-
`;
|
|
1622
|
-
cls.methods.forEach((method) => {
|
|
1623
|
-
const params = method.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
1624
|
-
output += ` - ${method.visibility} ${method.name}(${params}): ${method.returnType}
|
|
1625
|
-
`;
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
output += `
|
|
1629
|
-
`;
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
if (structure.interfaces.length > 0) {
|
|
1633
|
-
output += `INTERFACES:
|
|
1634
|
-
`;
|
|
1635
|
-
structure.interfaces.forEach((iface) => {
|
|
1636
|
-
output += ` - ${iface.name}
|
|
1637
|
-
`;
|
|
1638
|
-
iface.properties.forEach((prop) => {
|
|
1639
|
-
output += ` ${prop.name}: ${prop.type}
|
|
1640
|
-
`;
|
|
1641
|
-
});
|
|
1642
|
-
});
|
|
1643
|
-
output += `
|
|
1644
|
-
`;
|
|
1645
|
-
}
|
|
1646
|
-
if (structure.functions.length > 0) {
|
|
1647
|
-
output += `FUNCTIONS:
|
|
1648
|
-
`;
|
|
1649
|
-
structure.functions.forEach((fn) => {
|
|
1650
|
-
const params = fn.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
1651
|
-
output += ` - ${fn.name}(${params}): ${fn.returnType}
|
|
1652
|
-
`;
|
|
1653
|
-
});
|
|
1654
|
-
output += `
|
|
1655
|
-
`;
|
|
1656
|
-
}
|
|
1657
|
-
if (structure.imports.length > 0) {
|
|
1658
|
-
output += `IMPORTS:
|
|
1659
|
-
`;
|
|
1660
|
-
structure.imports.forEach((imp) => {
|
|
1661
|
-
output += ` - from "${imp.modulePath}": ${imp.namedImports.join(", ")}
|
|
1662
|
-
`;
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1665
|
-
return output;
|
|
1666
|
-
} catch (error) {
|
|
1667
|
-
return `[AST Error] ${error.message}`;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
async function astAddMethod(filePath, className, methodCode) {
|
|
1671
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1672
|
-
if (!editor) {
|
|
1673
|
-
throw new Error(`No AST editor available for ${filePath}`);
|
|
1674
|
-
}
|
|
1675
|
-
return await editor.addMethod(filePath, className, methodCode);
|
|
1676
|
-
}
|
|
1677
|
-
async function astGetMethod(filePath, className, methodName) {
|
|
1678
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1679
|
-
if (!editor) {
|
|
1680
|
-
throw new Error(`No AST editor available for ${filePath}`);
|
|
1681
|
-
}
|
|
1682
|
-
const methodContent = await editor.getMethod(filePath, className, methodName);
|
|
1683
|
-
if (!methodContent) {
|
|
1684
|
-
return `Method "${methodName}" not found in class "${className}"`;
|
|
1685
|
-
}
|
|
1686
|
-
return methodContent;
|
|
1687
|
-
}
|
|
1688
|
-
async function astAddClass(filePath, className, extendsClass, implementsInterfaces) {
|
|
1689
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1690
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1691
|
-
return await editor.addClass(filePath, className, { extendsClass, implementsInterfaces });
|
|
1692
|
-
}
|
|
1693
|
-
async function astAddProperty(filePath, className, propertyCode) {
|
|
1694
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1695
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1696
|
-
return await editor.addProperty(filePath, className, propertyCode);
|
|
1697
|
-
}
|
|
1698
|
-
async function astGetProperty(filePath, className, propertyName) {
|
|
1699
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1700
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1701
|
-
const propContent = await editor.getProperty(filePath, className, propertyName);
|
|
1702
|
-
if (!propContent) {
|
|
1703
|
-
return `Property "${propertyName}" not found in class "${className}"`;
|
|
1704
|
-
}
|
|
1705
|
-
return propContent;
|
|
1706
|
-
}
|
|
1707
|
-
async function astModifyProperty(filePath, className, propertyName, propertyCode) {
|
|
1708
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1709
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1710
|
-
return await editor.modifyProperty(filePath, className, propertyName, propertyCode);
|
|
1711
|
-
}
|
|
1712
|
-
async function astRemoveProperty(filePath, className, propertyName) {
|
|
1713
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1714
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1715
|
-
return await editor.removeProperty(filePath, className, propertyName);
|
|
1716
|
-
}
|
|
1717
|
-
async function astModifyMethod(filePath, className, methodName, newBody) {
|
|
1718
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1719
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1720
|
-
return await editor.modifyMethod(filePath, className, methodName, newBody);
|
|
1721
|
-
}
|
|
1722
|
-
async function astRemoveMethod(filePath, className, methodName) {
|
|
1723
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1724
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1725
|
-
return await editor.removeMethod(filePath, className, methodName);
|
|
1726
|
-
}
|
|
1727
|
-
async function astAddDecorator(filePath, className, decoratorCode) {
|
|
1728
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1729
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1730
|
-
return await editor.addDecorator(filePath, className, decoratorCode);
|
|
1731
|
-
}
|
|
1732
|
-
async function astAddInterface(filePath, interfaceCode) {
|
|
1733
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1734
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1735
|
-
return await editor.addInterface(filePath, interfaceCode);
|
|
1736
|
-
}
|
|
1737
|
-
async function astAddTypeAlias(filePath, typeCode) {
|
|
1738
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1739
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1740
|
-
return await editor.addTypeAlias(filePath, typeCode);
|
|
1741
|
-
}
|
|
1742
|
-
async function astAddFunction(filePath, functionCode) {
|
|
1743
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1744
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1745
|
-
return await editor.addFunction(filePath, functionCode);
|
|
1746
|
-
}
|
|
1747
|
-
async function astRemoveFunction(filePath, functionName) {
|
|
1748
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1749
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1750
|
-
return await editor.removeFunction(filePath, functionName);
|
|
1751
|
-
}
|
|
1752
|
-
async function astAddImport(filePath, importStatement) {
|
|
1753
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1754
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1755
|
-
return await editor.addImport(filePath, importStatement);
|
|
1756
|
-
}
|
|
1757
|
-
async function astRemoveImport(filePath, modulePath) {
|
|
1758
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1759
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1760
|
-
return await editor.removeImport(filePath, modulePath);
|
|
1761
|
-
}
|
|
1762
|
-
async function astOrganizeImports(filePath) {
|
|
1763
|
-
const editor = CodeEditorFactory.getEditor(filePath);
|
|
1764
|
-
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
1765
|
-
return await editor.organizeImports(filePath);
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
// src/core/agents/specification-agent.ts
|
|
1769
|
-
var AGENT_TYPE2 = "specification_agent";
|
|
1770
|
-
function getAgentId2(overrideId) {
|
|
1771
|
-
if (overrideId) return overrideId;
|
|
1772
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
1773
|
-
if (config.agents?.spec) return config.agents.spec;
|
|
1774
|
-
return process.env.STACKSPOT_SPEC_AGENT_ID || "01KEPXTX37FTB4N672TZST4SGP";
|
|
1775
|
-
}
|
|
1776
|
-
function getAgentVersion2(overrideVersion) {
|
|
1777
|
-
if (overrideVersion) return overrideVersion;
|
|
1778
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
1779
|
-
if (config.agentVersions?.spec) return config.agentVersions.spec;
|
|
1780
|
-
return process.env.STACKSPOT_SPEC_AGENT_VERSION;
|
|
1781
|
-
}
|
|
1782
|
-
async function interactiveSpecificationAgent(options = {}) {
|
|
1783
|
-
FileLogger.init();
|
|
1784
|
-
tui.intro("\u{1F3D7}\uFE0F Specification Agent (Template-Based)");
|
|
1785
|
-
const projectRoot = process.cwd();
|
|
1786
|
-
const sharkRcDir = path6.resolve(projectRoot, "_sharkrc");
|
|
1787
|
-
if (!fs5.existsSync(sharkRcDir)) fs5.mkdirSync(sharkRcDir, { recursive: true });
|
|
1788
|
-
const outputFile = path6.resolve(sharkRcDir, "tech-spec.md");
|
|
1789
|
-
if (!fs5.existsSync(outputFile)) {
|
|
1790
|
-
let initialContent = `# Technical Specification: {{PROJECT_NAME}}
|
|
1791
|
-
|
|
1792
|
-
## 1. Technology Stack
|
|
1793
|
-
[TO BE ANALYZED]
|
|
1794
|
-
- Language: [e.g. TypeScript]
|
|
1795
|
-
- Framework: [e.g. Node.js / React]
|
|
1796
|
-
- Database: [e.g. SQLite / PostgreSQL]
|
|
1797
|
-
- Key Libraries: [Top 5 dependencies]
|
|
1798
|
-
|
|
1799
|
-
## 2. Architecture Overview
|
|
1800
|
-
[TO BE ANALYZED]
|
|
1801
|
-
[Brief description of architectural pattern]
|
|
1802
|
-
|
|
1803
|
-
## 3. Data Model
|
|
1804
|
-
[TO BE ANALYZED]
|
|
1805
|
-
[Schema/ERD definitions]
|
|
1806
|
-
|
|
1807
|
-
## 4. API / Interface Contracts
|
|
1808
|
-
[TO BE ANALYZED]
|
|
1809
|
-
[Main endpoints or CLI commands]
|
|
1810
|
-
|
|
1811
|
-
## 5. Implementation Steps
|
|
1812
|
-
[TO BE FILLED - MUST BE CHECKBOXES]
|
|
1813
|
-
`;
|
|
1814
|
-
const projectName = path6.basename(projectRoot);
|
|
1815
|
-
initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
|
|
1816
|
-
const BOM = "\uFEFF";
|
|
1817
|
-
fs5.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
|
|
1818
|
-
tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
1819
|
-
} else {
|
|
1820
|
-
tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
1821
|
-
}
|
|
1822
|
-
let contextContent = "";
|
|
1823
|
-
const contextPath = path6.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
1824
|
-
if (fs5.existsSync(contextPath)) {
|
|
1825
|
-
contextContent = fs5.readFileSync(contextPath, "utf-8");
|
|
1826
|
-
tui.log.info(`\u{1F4D8} Context loaded.`);
|
|
1827
|
-
}
|
|
1828
|
-
let briefingContent = "";
|
|
1829
|
-
if (options.briefingPath && fs5.existsSync(options.briefingPath)) {
|
|
1830
|
-
briefingContent = fs5.readFileSync(options.briefingPath, "utf-8");
|
|
1831
|
-
tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
|
|
1832
|
-
} else {
|
|
1833
|
-
const standardBriefing = path6.resolve(projectRoot, "_sharkrc", "briefing.md");
|
|
1834
|
-
if (fs5.existsSync(standardBriefing)) {
|
|
1835
|
-
briefingContent = fs5.readFileSync(standardBriefing, "utf-8");
|
|
1836
|
-
tui.log.info(`\u{1F4C4} Briefing loaded.`);
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
let initialPrompt = `
|
|
1840
|
-
Voc\xEA \xE9 o **Shark Spec**, um Arquiteto de Software S\xEAnior e Tech Lead.
|
|
1841
|
-
Seu objetivo final \xE9 produzir uma especifica\xE7\xE3o t\xE9cnica precisa para a tarefa no arquivo \`_sharkrc/tech-spec.md\`.
|
|
1842
|
-
|
|
1843
|
-
\u26A0\uFE0F O SEU WORKFLOW \xC9 GUIADO POR FASES.
|
|
1844
|
-
N\xC3O TENTE ADIANTAR O TRABALHO (ex: investigar c\xF3digo ou preencher template agora).
|
|
1845
|
-
|
|
1846
|
-
**VOC\xCA EST\xC1 NA FASE 1: ENTENDIMENTO DA TAREFA**
|
|
1847
|
-
- Use \`talk_with_user\` para perguntar ao usu\xE1rio qual tarefa espec\xEDfica, funcionalidade ou bug ele precisa especificar.
|
|
1848
|
-
- Confirme o escopo e os limites com o usu\xE1rio.
|
|
1849
|
-
- Se o escopo estiver perfeitamente claro e confirmado (com o usu\xE1rio), emita "PHASE_COMPLETED" no campo "summary" do JSON para avan\xE7ar.
|
|
1850
|
-
|
|
1851
|
-
IMPORTANTE: Toda a sua comunica\xE7\xE3o DEVE ser em Portugu\xEAs.
|
|
1852
|
-
`;
|
|
1853
|
-
if (briefingContent) {
|
|
1854
|
-
initialPrompt += `
|
|
1855
|
-
\u2139\uFE0F Um documento de briefing foi encontrado. Ele define parcialmente a tarefa para a Fase 1.
|
|
1856
|
-
Confirme seu entendimento com o usu\xE1rio via \`talk_with_user\` antes de prosseguir para a Fase 2.
|
|
1857
|
-
|
|
1858
|
-
--- BRIEFING ---
|
|
1859
|
-
${briefingContent}
|
|
1860
|
-
----------------
|
|
1861
|
-
`;
|
|
1862
|
-
} else {
|
|
1863
|
-
initialPrompt += `
|
|
1864
|
-
\u2139\uFE0F Nenhum documento de briefing foi encontrado. Inicie a Fase 1 imediatamente: use \`talk_with_user\` para perguntar ao usu\xE1rio o que precisa ser especificado.
|
|
1865
|
-
`;
|
|
1866
|
-
}
|
|
1867
|
-
if (options.initialContext) {
|
|
1868
|
-
initialPrompt += `
|
|
1869
|
-
--- CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER/FEEDBACK) ---
|
|
1870
|
-
${options.initialContext}
|
|
1871
|
-
-----------------------------------------------------
|
|
1872
|
-
`;
|
|
1873
|
-
}
|
|
1874
|
-
if (contextContent) {
|
|
1875
|
-
initialPrompt += `
|
|
1876
|
-
\u2139\uFE0F O contexto do projeto est\xE1 dispon\xEDvel para refer\xEAncia. Use-o na Fase 2 para se alinhar com os padr\xF5es de arquitetura existentes, mas N\xC3O o use para preencher as se\xE7\xF5es de forma gen\xE9rica.
|
|
1877
|
-
|
|
1878
|
-
--- PROJECT CONTEXT ---
|
|
1879
|
-
${contextContent}
|
|
1880
|
-
-----------------------
|
|
1881
|
-
`;
|
|
1882
|
-
}
|
|
1883
|
-
await runSpecLoop(initialPrompt.trim(), outputFile, options.agentId);
|
|
1884
|
-
}
|
|
1885
|
-
async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
|
|
1886
|
-
let nextPrompt = initialMessage;
|
|
1887
|
-
let keepGoing = true;
|
|
1888
|
-
let stepCount = 0;
|
|
1889
|
-
let currentPhase = 1;
|
|
1890
|
-
const MAX_STEPS = 30;
|
|
1891
|
-
while (keepGoing && stepCount < MAX_STEPS) {
|
|
1892
|
-
stepCount++;
|
|
1893
|
-
const spinner = tui.spinner();
|
|
1894
|
-
spinner.start(`\u{1F3D7}\uFE0F Spec Agent working (Step ${stepCount}/${MAX_STEPS})...`);
|
|
1895
|
-
let pendingSections = [];
|
|
1896
|
-
if (fs5.existsSync(targetPath)) {
|
|
1897
|
-
const content = fs5.readFileSync(targetPath, "utf-8");
|
|
1898
|
-
if (content.includes("[TO BE ANALYZED]")) pendingSections.push("Analysis Sections (Stack, Arch, Data, API)");
|
|
1899
|
-
if (content.includes("[TO BE FILLED")) pendingSections.push("Implementation Steps");
|
|
1900
|
-
}
|
|
1901
|
-
if (pendingSections.length === 0 && stepCount > 1) {
|
|
1902
|
-
}
|
|
1903
|
-
let responseText = "";
|
|
1904
|
-
let lastResponse = null;
|
|
1905
|
-
try {
|
|
1906
|
-
lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
|
|
1907
|
-
responseText += chunk;
|
|
1908
|
-
}, overrideAgentId);
|
|
1909
|
-
spinner.stop("Response received");
|
|
1910
|
-
if (lastResponse && lastResponse.actions) {
|
|
1911
326
|
let executionResults = "";
|
|
1912
327
|
let waitingForUser = false;
|
|
1913
|
-
|
|
1914
|
-
if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
|
|
1915
|
-
if (currentPhase === 1) {
|
|
1916
|
-
currentPhase = 2;
|
|
1917
|
-
tui.log.success(`\u2705 Fase 1 Conclu\xEDda. Iniciando Fase 2 (Investiga\xE7\xE3o).`);
|
|
1918
|
-
nextPrompt = `[System Message]
|
|
1919
|
-
Voc\xEA completou a FASE 1 com sucesso.
|
|
1920
|
-
|
|
1921
|
-
**VOC\xCA AGORA EST\xC1 NA FASE 2: INVESTIGA\xC7\xC3O**
|
|
1922
|
-
- Use \`search_code\` e \`list_files\` para explorar os arquivos relevantes \xE0 tarefa.
|
|
1923
|
-
- Prefira \`search_code\` em vez de \`read_file\` para buscar c\xF3digo sem inflar o contexto.
|
|
1924
|
-
- N\xC3O leia o projeto inteiro de forma gen\xE9rica.
|
|
1925
|
-
- REGRA DE OURO (READ-FIRST): Voc\xEA N\xC3O PODE referenciar um arquivo na especifica\xE7\xE3o t\xE9cnica que n\xE3o tenha investigado nesta fase.
|
|
1926
|
-
- Quando achar que possui toda a clareza t\xE9cnica sobre onde e o que deve ser feito no c\xF3digo, emita "PHASE_COMPLETED" no summary.`;
|
|
1927
|
-
continue;
|
|
1928
|
-
} else if (currentPhase === 2) {
|
|
1929
|
-
currentPhase = 3;
|
|
1930
|
-
tui.log.success(`\u2705 Fase 2 Conclu\xEDda. Iniciando Fase 3 (Preenchimento).`);
|
|
1931
|
-
nextPrompt = `[System Message]
|
|
1932
|
-
Voc\xEA completou a FASE 2 com sucesso.
|
|
1933
|
-
|
|
1934
|
-
**VOC\xCA AGORA EST\xC1 NA FASE 3: PREENCHIMENTO DO TEMPLATE**
|
|
1935
|
-
- Use \`modify_file\` no arquivo \`${targetPath}\` para substituir os placeholders pelo conte\xFAdo real levantado na fase de investiga\xE7\xE3o.
|
|
1936
|
-
- As se\xE7\xF5es 1-4 devem descrever o contexto da TAREFA, e n\xE3o o projeto como um todo.
|
|
1937
|
-
- Passos de Implementa\xE7\xE3o (Implementation Steps): APENAS checkboxes markdown: \`- [ ] [Verbo de A\xE7\xE3o] [O Que] em [Caminho Relativo]\`.
|
|
1938
|
-
- Quando TODOS os placeholders ([TO BE ANALYZED] ou [TO BE FILLED]) forem substitu\xEDdos e o trabalho conclu\xEDdo, emita "SPEC_UPDATED: Complete" no summary para finalizar.`;
|
|
1939
|
-
continue;
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
if (lastResponse.message && lastResponse.message.includes("SPEC_UPDATED:")) {
|
|
1943
|
-
if (currentPhase < 3) {
|
|
1944
|
-
tui.log.warning(`O agente tentou finalizar prematuramente. For\xE7ando retorno para a fase atual...`);
|
|
1945
|
-
nextPrompt = `[System Error]: Voc\xEA tentou finalizar a especifica\xE7\xE3o prematuramente emitindo SPEC_UPDATED, mas ainda est\xE1 na Fase ${currentPhase}. Voc\xEA s\xF3 pode finalizar quando estiver na Fase 3.
|
|
1946
|
-
|
|
1947
|
-
Continue seu trabalho na Fase ${currentPhase} ou emita "PHASE_COMPLETED" se terminou esta etapa atual.`;
|
|
1948
|
-
continue;
|
|
1949
|
-
}
|
|
1950
|
-
const content = fs5.existsSync(targetPath) ? fs5.readFileSync(targetPath, "utf-8") : "";
|
|
1951
|
-
if (content.includes("[TO BE")) {
|
|
1952
|
-
const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
|
|
1953
|
-
let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "algumas se\xE7\xF5es";
|
|
1954
|
-
tui.log.warning(`O agente tentou concluir prematuramente, mas h\xE1 placeholders pendentes. For\xE7ando retorno...`);
|
|
1955
|
-
nextPrompt = `[System Error]: A valida\xE7\xE3o falhou e o bloqueio autom\xE1tico foi acionado.
|
|
1956
|
-
Voc\xEA tentou concluir a tarefa, mas o arquivo AINDA possui placeholders '[TO BE ANALYZED]' ou '[TO BE FILLED]'.
|
|
1957
|
-
As seguintes se\xE7\xF5es ainda cont\xEAm estes placeholders: ${missing}.
|
|
1958
|
-
Voc\xEA \xE9 OBRIGADO a usar a action \`modify_file\` para preencher o conte\xFAdo de cada uma dessas se\xE7\xF5es. Use o placeholder exato no campo \`target_content\`. N\xC3O repita a conclus\xE3o da tarefa at\xE9 corrigir todas as pend\xEAncias.`;
|
|
1959
|
-
continue;
|
|
1960
|
-
} else {
|
|
1961
|
-
const updateSummary = lastResponse.message.split("SPEC_UPDATED:")[1].trim();
|
|
1962
|
-
tui.log.success(`\u2705 Spec Finalized: ${updateSummary}`);
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
for (const action of lastResponse.actions) {
|
|
328
|
+
for (const action of actions) {
|
|
1967
329
|
if (action.type === "talk_with_user") {
|
|
1968
|
-
tui.log.info(colors.primary("\u{1F916}
|
|
330
|
+
tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
1969
331
|
console.log(action.content);
|
|
1970
|
-
waitingForUser = true;
|
|
332
|
+
if (!isTaskCompleted) waitingForUser = true;
|
|
1971
333
|
} else if (action.type === "list_files") {
|
|
1972
334
|
tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
|
|
1973
335
|
const result = handleListFiles(action.path || ".");
|
|
@@ -1982,811 +344,493 @@ ${result}
|
|
|
1982
344
|
${result}
|
|
1983
345
|
|
|
1984
346
|
`;
|
|
1985
|
-
} else if (action.type === "search_file") {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
347
|
+
} else if (action.type === "search_file") {
|
|
348
|
+
const result = handleSearchFile(action.path || "");
|
|
349
|
+
executionResults += `[Action search_file(${action.path}) Result]:
|
|
350
|
+
${result}
|
|
351
|
+
|
|
352
|
+
`;
|
|
353
|
+
} else if (action.type === "run_command") {
|
|
354
|
+
const cmd = action.command || "";
|
|
355
|
+
tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
|
|
356
|
+
let approved = autoApprovals.commands;
|
|
357
|
+
if (!approved) {
|
|
358
|
+
const choice = await tui.select({
|
|
359
|
+
message: `Execute: ${cmd}?`,
|
|
360
|
+
options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
|
|
361
|
+
});
|
|
362
|
+
if (choice === "always") {
|
|
363
|
+
autoApprovals.commands = true;
|
|
364
|
+
approved = true;
|
|
365
|
+
} else if (choice === "yes") approved = true;
|
|
366
|
+
}
|
|
367
|
+
if (approved) {
|
|
368
|
+
const result = await handleRunCommand(cmd);
|
|
369
|
+
executionResults += `[Action run_command(${cmd}) Result]:
|
|
370
|
+
${result}
|
|
371
|
+
|
|
372
|
+
`;
|
|
373
|
+
} else {
|
|
374
|
+
executionResults += `[Action run_command]: User blocked execution.
|
|
375
|
+
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
} else if (["create_file", "modify_file"].includes(action.type)) {
|
|
379
|
+
const filePath = action.path || "";
|
|
380
|
+
tui.log.warning(`\u{1F4DD} ${action.type === "create_file" ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
|
|
381
|
+
let approved = autoApprovals.files;
|
|
382
|
+
if (!approved) {
|
|
383
|
+
const choice = await tui.select({
|
|
384
|
+
message: `Approve changes to ${filePath}?`,
|
|
385
|
+
options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
|
|
386
|
+
});
|
|
387
|
+
if (choice === "always") {
|
|
388
|
+
autoApprovals.files = true;
|
|
389
|
+
approved = true;
|
|
390
|
+
} else if (choice === "yes") approved = true;
|
|
391
|
+
}
|
|
392
|
+
if (approved) {
|
|
393
|
+
if (action.type === "create_file") {
|
|
394
|
+
const dir = path2.dirname(path2.resolve(projectRoot, filePath));
|
|
395
|
+
if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
|
|
396
|
+
fs2.writeFileSync(path2.resolve(projectRoot, filePath), action.content || "", "utf-8");
|
|
397
|
+
executionResults += `[Action create_file]: Success
|
|
398
|
+
|
|
399
|
+
`;
|
|
400
|
+
} else {
|
|
401
|
+
let success = false;
|
|
402
|
+
if (action.line_range) success = replaceLineRange(filePath, action.line_range[0], action.line_range[1], action.content || "", tui);
|
|
403
|
+
else if (action.target_content) success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
|
|
404
|
+
executionResults += success ? `[Action modify_file]: Success
|
|
405
|
+
|
|
406
|
+
` : `[Action modify_file]: Failed
|
|
407
|
+
|
|
408
|
+
`;
|
|
409
|
+
}
|
|
410
|
+
const val = await validateTypeScript(path2.resolve(projectRoot, filePath));
|
|
411
|
+
if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
|
|
412
|
+
|
|
413
|
+
`;
|
|
414
|
+
} else {
|
|
415
|
+
executionResults += `[Action ${action.type}]: User Denied.
|
|
416
|
+
|
|
417
|
+
`;
|
|
418
|
+
}
|
|
419
|
+
} else if (action.type.startsWith("ast_")) {
|
|
420
|
+
try {
|
|
421
|
+
let result = "";
|
|
422
|
+
if (action.type === "ast_list_structure") {
|
|
423
|
+
result = await astListStructure(action.path || "");
|
|
424
|
+
} else if (action.type === "ast_get_method") {
|
|
425
|
+
result = await astGetMethod(action.path || "", action.class_name || "", action.method_name || "");
|
|
426
|
+
} else if (action.type === "ast_add_method") {
|
|
427
|
+
const success = await astAddMethod(action.path || "", action.class_name || "", action.method_code || "");
|
|
428
|
+
result = success ? "Method added successfully." : "Failed to add method.";
|
|
429
|
+
} else if (action.type === "ast_modify_method") {
|
|
430
|
+
const success = await astModifyMethod(action.path || "", action.class_name || "", action.method_name || "", action.new_body || "");
|
|
431
|
+
result = success ? "Method modified successfully." : "Failed to modify method.";
|
|
432
|
+
} else if (action.type === "ast_remove_method") {
|
|
433
|
+
const success = await astRemoveMethod(action.path || "", action.class_name || "", action.method_name || "");
|
|
434
|
+
result = success ? "Method removed successfully." : "Failed to remove method.";
|
|
435
|
+
} else if (action.type === "ast_add_class") {
|
|
436
|
+
const success = await astAddClass(action.path || "", action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
|
|
437
|
+
result = success ? "Class added successfully." : "Failed to add class.";
|
|
438
|
+
} else if (action.type === "ast_get_property") {
|
|
439
|
+
tui.log.info(`\u{1F50D} Reading property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
|
|
440
|
+
const propContent = await astGetProperty(action.path || "", action.class_name || "", action.property_name || "");
|
|
441
|
+
result = `[AST Get Property] Content:
|
|
442
|
+
${propContent}`;
|
|
443
|
+
} else if (action.type === "ast_add_property") {
|
|
444
|
+
const success = await astAddProperty(action.path || "", action.class_name || "", action.property_code || "");
|
|
445
|
+
result = success ? "Property added successfully." : "Failed to add property.";
|
|
446
|
+
} else if (action.type === "ast_modify_property") {
|
|
447
|
+
tui.log.info(`\u270F\uFE0F Modifying property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
|
|
448
|
+
const success = await astModifyProperty(action.path || "", action.class_name || "", action.property_name || "", action.property_code || "");
|
|
449
|
+
result = success ? "Property modified successfully." : "Failed to modify property.";
|
|
450
|
+
} else if (action.type === "ast_remove_property") {
|
|
451
|
+
const success = await astRemoveProperty(action.path || "", action.class_name || "", action.property_name || "");
|
|
452
|
+
result = success ? "Property removed successfully." : "Failed to remove property.";
|
|
453
|
+
} else if (action.type === "ast_add_decorator") {
|
|
454
|
+
const success = await astAddDecorator(action.path || "", action.class_name || "", action.decorator_code || "");
|
|
455
|
+
result = success ? "Decorator added successfully." : "Failed to add decorator.";
|
|
456
|
+
} else if (action.type === "ast_add_interface") {
|
|
457
|
+
const success = await astAddInterface(action.path || "", action.interface_code || "");
|
|
458
|
+
result = success ? "Interface added successfully." : "Failed to add interface.";
|
|
459
|
+
} else if (action.type === "ast_add_type_alias") {
|
|
460
|
+
const success = await astAddTypeAlias(action.path || "", action.type_code || "");
|
|
461
|
+
result = success ? "Type alias added successfully." : "Failed to add type alias.";
|
|
462
|
+
} else if (action.type === "ast_add_function") {
|
|
463
|
+
const success = await astAddFunction(action.path || "", action.function_code || "");
|
|
464
|
+
result = success ? "Function added successfully." : "Failed to add function.";
|
|
465
|
+
} else if (action.type === "ast_remove_function") {
|
|
466
|
+
const success = await astRemoveFunction(action.path || "", action.function_name || "");
|
|
467
|
+
result = success ? "Function removed successfully." : "Failed to remove function.";
|
|
468
|
+
} else if (action.type === "ast_add_import") {
|
|
469
|
+
const success = await astAddImport(action.path || "", action.import_statement || "");
|
|
470
|
+
result = success ? "Import added successfully." : "Failed to add import.";
|
|
471
|
+
} else if (action.type === "ast_remove_import") {
|
|
472
|
+
const success = await astRemoveImport(action.path || "", action.module_path || "");
|
|
473
|
+
result = success ? "Import removed successfully." : "Failed to remove import.";
|
|
474
|
+
} else if (action.type === "ast_organize_imports") {
|
|
475
|
+
const success = await astOrganizeImports(action.path || "");
|
|
476
|
+
result = success ? "Imports organized successfully." : "Failed to organize imports.";
|
|
477
|
+
} else {
|
|
478
|
+
result = `Unknown AST action: ${action.type}`;
|
|
479
|
+
}
|
|
480
|
+
executionResults += `[Action ${action.type} Result]:
|
|
481
|
+
${result}
|
|
482
|
+
|
|
483
|
+
`;
|
|
484
|
+
tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
executionResults += `[Action ${action.type} Failed]: ${e.message}
|
|
1990
487
|
|
|
1991
488
|
`;
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
const
|
|
1996
|
-
|
|
1997
|
-
const result = handleSearchCode(glob, query, isRegex);
|
|
1998
|
-
executionResults += `[Action search_code("${query}" in "${glob}") Result]:
|
|
489
|
+
tui.log.error(`\u274C AST Action Error: ${e.message}`);
|
|
490
|
+
}
|
|
491
|
+
} else if (action.type === "search_ast") {
|
|
492
|
+
const result = await astGrepSearch(action.pattern || "", action.path || "", action.language || "typescript", tui);
|
|
493
|
+
executionResults += `[Action search_ast Result]:
|
|
1999
494
|
${result}
|
|
2000
495
|
|
|
2001
496
|
`;
|
|
2002
|
-
} else if (
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
action.path = targetPath;
|
|
2009
|
-
actionPath = resolvedTargetPath;
|
|
2010
|
-
isTarget = true;
|
|
2011
|
-
}
|
|
2012
|
-
if (!isTarget && action.type === "create_file") {
|
|
2013
|
-
const confirm = await tui.confirm({ message: `Agent wants to create ${action.path}. Allow?` });
|
|
2014
|
-
if (!confirm) {
|
|
2015
|
-
executionResults += `[Action create_file]: User denied.
|
|
2016
|
-
`;
|
|
2017
|
-
continue;
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
try {
|
|
2021
|
-
if (action.type === "create_file") {
|
|
2022
|
-
const BOM = "\uFEFF";
|
|
2023
|
-
fs5.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
|
|
2024
|
-
tui.log.success(`\u2705 Created: ${action.path}`);
|
|
2025
|
-
executionResults += `[Action create_file]: Success.
|
|
2026
|
-
`;
|
|
2027
|
-
} else if (action.type === "modify_file") {
|
|
2028
|
-
if (action.target_content) {
|
|
2029
|
-
const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
|
|
2030
|
-
if (success) {
|
|
2031
|
-
executionResults += `[Action modify_file]: Success.
|
|
2032
|
-
`;
|
|
2033
|
-
specUpdated = true;
|
|
2034
|
-
} else {
|
|
2035
|
-
executionResults += `[Action modify_file]: Failed. Target content not found.
|
|
2036
|
-
`;
|
|
2037
|
-
}
|
|
2038
|
-
} else {
|
|
2039
|
-
executionResults += `[Action modify_file]: Failed. 'target_content' is required.
|
|
2040
|
-
`;
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
} catch (e) {
|
|
2044
|
-
executionResults += `[Action ${action.type}]: Error: ${e.message}
|
|
497
|
+
} else if (action.type === "modify_ast") {
|
|
498
|
+
const success = await astGrepRewrite(action.pattern || "", action.fix || "", action.path || "", action.language || "typescript", tui);
|
|
499
|
+
executionResults += success ? `[Action modify_ast]: Success
|
|
500
|
+
|
|
501
|
+
` : `[Action modify_ast]: Failed
|
|
502
|
+
|
|
2045
503
|
`;
|
|
2046
|
-
}
|
|
2047
504
|
}
|
|
2048
505
|
}
|
|
2049
|
-
if (
|
|
2050
|
-
|
|
2051
|
-
if (tui.isCancel(userReply)) {
|
|
2052
|
-
keepGoing = false;
|
|
2053
|
-
return;
|
|
2054
|
-
}
|
|
2055
|
-
nextPrompt = `${executionResults}
|
|
2056
|
-
|
|
2057
|
-
User Reply: ${userReply}`;
|
|
2058
|
-
} else if (executionResults) {
|
|
2059
|
-
const content = fs5.existsSync(targetPath) ? fs5.readFileSync(targetPath, "utf-8") : "";
|
|
2060
|
-
let systemMsg = "Execu\xE7\xE3o da ferramenta conclu\xEDda.";
|
|
2061
|
-
if (specUpdated) {
|
|
2062
|
-
if (content.includes("[TO BE")) {
|
|
2063
|
-
const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
|
|
2064
|
-
let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "v\xE1rias se\xE7\xF5es";
|
|
2065
|
-
systemMsg += `
|
|
2066
|
-
[System]: Se\xE7\xE3o atualizada com sucesso. A valida\xE7\xE3o detectou que AINDA H\xC1 placeholders pendentes ('[TO BE...]') nas seguintes se\xE7\xF5es: ${missing}.
|
|
2067
|
-
Por favor, envie uma nova action \`modify_file\` focada em uma destas se\xE7\xF5es obrigatoriamente. USE o respectivo placeholder no campo \`target_content\` para que o replace funcione.`;
|
|
2068
|
-
} else {
|
|
2069
|
-
systemMsg += "\n[System]: O arquivo parece completo! Se estiver satisfeito e possuir TODAS as implementa\xE7\xF5es descritas, retorne 'SPEC_UPDATED: Complete'.";
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2072
|
-
nextPrompt = `${executionResults}
|
|
2073
|
-
|
|
2074
|
-
${systemMsg}`;
|
|
2075
|
-
} else {
|
|
2076
|
-
if (lastResponse.message) {
|
|
2077
|
-
tui.log.info(colors.primary("\u{1F916} Architect (Message only):"));
|
|
2078
|
-
console.log(lastResponse.message);
|
|
506
|
+
if (executionResults) {
|
|
507
|
+
if (waitingForUser) {
|
|
2079
508
|
const userReply = await tui.text({ message: "Your answer:" });
|
|
2080
509
|
if (tui.isCancel(userReply)) {
|
|
2081
510
|
keepGoing = false;
|
|
2082
511
|
break;
|
|
2083
512
|
}
|
|
2084
|
-
nextPrompt =
|
|
513
|
+
nextPrompt = `${executionResults}
|
|
514
|
+
User Reply: ${userReply}`;
|
|
2085
515
|
} else {
|
|
2086
|
-
|
|
516
|
+
nextPrompt = `${executionResults}
|
|
517
|
+
[System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
|
|
518
|
+
tui.log.info(colors.dim("Processing results..."));
|
|
2087
519
|
}
|
|
520
|
+
} else if (!keepGoing) {
|
|
521
|
+
} else if (waitingForUser) {
|
|
522
|
+
} else {
|
|
523
|
+
if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
|
|
2088
524
|
}
|
|
2089
525
|
} else {
|
|
2090
|
-
tui.log.warning("No
|
|
2091
|
-
keepGoing = false;
|
|
526
|
+
tui.log.warning("No response received from agent.");
|
|
2092
527
|
}
|
|
2093
|
-
} catch (
|
|
2094
|
-
|
|
2095
|
-
tui.log.error(error.message);
|
|
528
|
+
} catch (e) {
|
|
529
|
+
tui.log.error(e.message);
|
|
2096
530
|
keepGoing = false;
|
|
531
|
+
return { success: false, summary: `Error: ${e.message}` };
|
|
2097
532
|
}
|
|
2098
533
|
}
|
|
534
|
+
tui.log.success("\u2705 Task Scope Completed");
|
|
535
|
+
return { success: true, summary: finalSummary || "Task completed without summary." };
|
|
2099
536
|
}
|
|
2100
|
-
async function
|
|
2101
|
-
const
|
|
2102
|
-
const
|
|
2103
|
-
const
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
stackspot_knowledge: false,
|
|
2108
|
-
return_ks_in_response: true,
|
|
2109
|
-
use_conversation: true,
|
|
2110
|
-
conversation_id: conversationId
|
|
2111
|
-
};
|
|
2112
|
-
const agentVersion = getAgentVersion2();
|
|
2113
|
-
if (agentVersion) {
|
|
2114
|
-
payload.agent_version_number = agentVersion;
|
|
2115
|
-
}
|
|
2116
|
-
const effectiveAgentId = getAgentId2(agentId);
|
|
2117
|
-
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${effectiveAgentId}/chat`;
|
|
2118
|
-
let fullMsg = "";
|
|
2119
|
-
let raw = {};
|
|
2120
|
-
FileLogger.log("AGENT", "Calling Agent API", { agentId: effectiveAgentId, conversationId });
|
|
2121
|
-
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
2122
|
-
onChunk: (c) => {
|
|
2123
|
-
fullMsg += c;
|
|
2124
|
-
onChunk(c);
|
|
2125
|
-
},
|
|
2126
|
-
onComplete: (msg, metadata) => {
|
|
2127
|
-
const returnedId = metadata?.conversation_id;
|
|
2128
|
-
raw = { message: msg || fullMsg, conversation_id: returnedId || conversationId };
|
|
2129
|
-
},
|
|
2130
|
-
onError: (e) => {
|
|
2131
|
-
throw e;
|
|
2132
|
-
}
|
|
537
|
+
async function callDevAgentApi(prompt, onChunk, conversationKey = AGENT_TYPE) {
|
|
538
|
+
const existingConversationId = await conversationManager.getConversationId(conversationKey);
|
|
539
|
+
const provider = ProviderResolver.getProvider("developer_agent");
|
|
540
|
+
const parsedResponse = await provider.streamChat(prompt, {
|
|
541
|
+
conversationId: existingConversationId,
|
|
542
|
+
agentType: "developer_agent",
|
|
543
|
+
onChunk
|
|
2133
544
|
});
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
|
|
545
|
+
if (parsedResponse.conversation_id) {
|
|
546
|
+
await conversationManager.saveConversationId(conversationKey, parsedResponse.conversation_id);
|
|
2137
547
|
}
|
|
2138
|
-
return
|
|
548
|
+
return parsedResponse;
|
|
2139
549
|
}
|
|
2140
550
|
|
|
2141
|
-
// src/
|
|
2142
|
-
import
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
if (
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
551
|
+
// src/core/workflow/task-manager.ts
|
|
552
|
+
import fs3 from "fs";
|
|
553
|
+
import path3 from "path";
|
|
554
|
+
var TaskManager = class {
|
|
555
|
+
projectRoot;
|
|
556
|
+
specPath;
|
|
557
|
+
constructor(projectRoot = process.cwd()) {
|
|
558
|
+
this.projectRoot = projectRoot;
|
|
559
|
+
this.specPath = path3.resolve(this.projectRoot, "_sharkrc", "tech-spec.md");
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Reads the tech-spec.md file and analyzes its current state.
|
|
563
|
+
*/
|
|
564
|
+
analyzeSpecState() {
|
|
565
|
+
if (!fs3.existsSync(this.specPath)) {
|
|
566
|
+
return { status: "MISSING", allTasks: [] };
|
|
567
|
+
}
|
|
568
|
+
const content = fs3.readFileSync(this.specPath, "utf-8");
|
|
569
|
+
const lines = content.split("\n");
|
|
570
|
+
const tasks = [];
|
|
571
|
+
let taskIndex = 1;
|
|
572
|
+
for (let i = 0; i < lines.length; i++) {
|
|
573
|
+
const line = lines[i];
|
|
574
|
+
const trimmed = line.trim();
|
|
575
|
+
const pendingMatch = trimmed.match(/^- \[ \] (.*)/);
|
|
576
|
+
const completedMatch = trimmed.match(/^- \[x\] (.*)/i);
|
|
577
|
+
const progressMatch = trimmed.match(/^- \[\/\] (.*)/);
|
|
578
|
+
let currentTask = null;
|
|
579
|
+
let status2 = null;
|
|
580
|
+
let description = "";
|
|
581
|
+
if (pendingMatch) {
|
|
582
|
+
description = pendingMatch[1].trim();
|
|
583
|
+
status2 = "PENDING";
|
|
584
|
+
} else if (completedMatch) {
|
|
585
|
+
description = completedMatch[1].trim();
|
|
586
|
+
status2 = "COMPLETED";
|
|
587
|
+
} else if (progressMatch) {
|
|
588
|
+
description = progressMatch[1].trim();
|
|
589
|
+
status2 = "IN_PROGRESS";
|
|
2179
590
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
591
|
+
if (status2 && description) {
|
|
592
|
+
let j = i + 1;
|
|
593
|
+
while (j < lines.length) {
|
|
594
|
+
const nextLine = lines[j];
|
|
595
|
+
const nextTrimmed = nextLine.trim();
|
|
596
|
+
if (!nextTrimmed || nextTrimmed.match(/^- \[[ x\/]\]/) || nextTrimmed.startsWith("#")) {
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
description += "\n" + nextTrimmed;
|
|
600
|
+
j++;
|
|
601
|
+
}
|
|
602
|
+
currentTask = {
|
|
603
|
+
id: `task-${taskIndex++}`,
|
|
604
|
+
description,
|
|
605
|
+
status: status2,
|
|
606
|
+
line_number: i
|
|
607
|
+
};
|
|
608
|
+
tasks.push(currentTask);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
let nextTask = tasks.find((t) => t.status === "IN_PROGRESS");
|
|
612
|
+
if (!nextTask) {
|
|
613
|
+
nextTask = tasks.find((t) => t.status === "PENDING");
|
|
2183
614
|
}
|
|
615
|
+
const status = !nextTask && tasks.length > 0 && tasks.every((t) => t.status === "COMPLETED") ? "COMPLETED" : "PENDING";
|
|
616
|
+
return {
|
|
617
|
+
status: tasks.length === 0 ? "MISSING" : status,
|
|
618
|
+
// Empty file is effectively "pending creation" but we treat as missing content logic elsewhere
|
|
619
|
+
nextTask,
|
|
620
|
+
allTasks: tasks
|
|
621
|
+
};
|
|
2184
622
|
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
623
|
+
/**
|
|
624
|
+
* Marks a specific task as COMPLETED in the file.
|
|
625
|
+
* Uses line-based replacement to be safe.
|
|
626
|
+
*/
|
|
627
|
+
markTaskAsDone(taskId) {
|
|
628
|
+
const state = this.analyzeSpecState();
|
|
629
|
+
const task = state.allTasks.find((t) => t.id === taskId);
|
|
630
|
+
if (!task) {
|
|
631
|
+
console.error(`Task ${taskId} not found.`);
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
const content = fs3.readFileSync(this.specPath, "utf-8");
|
|
635
|
+
const lines = content.split("\n");
|
|
636
|
+
const targetLine = lines[task.line_number];
|
|
637
|
+
if (!targetLine.includes(task.description)) {
|
|
638
|
+
console.error(`Concurrency Error: Task line content mistmatch. Expected "${task.description}" at line ${task.line_number}.`);
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
|
|
642
|
+
lines[task.line_number] = newLine;
|
|
643
|
+
fs3.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Marks a task as IN_PROGRESS.
|
|
648
|
+
*/
|
|
649
|
+
markTaskInProgress(taskId) {
|
|
650
|
+
const state = this.analyzeSpecState();
|
|
651
|
+
const task = state.allTasks.find((t) => t.id === taskId);
|
|
652
|
+
if (!task) return false;
|
|
653
|
+
const content = fs3.readFileSync(this.specPath, "utf-8");
|
|
654
|
+
const lines = content.split("\n");
|
|
655
|
+
let targetLine = lines[task.line_number];
|
|
656
|
+
targetLine = targetLine.replace("- [ ]", "- [/]");
|
|
657
|
+
lines[task.line_number] = targetLine;
|
|
658
|
+
fs3.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Completely updates the spec file content (used by Spec Agent).
|
|
663
|
+
*/
|
|
664
|
+
updateSpecContent(newContent) {
|
|
665
|
+
fs3.writeFileSync(this.specPath, newContent, "utf-8");
|
|
666
|
+
}
|
|
667
|
+
getSpecPath() {
|
|
668
|
+
return this.specPath;
|
|
669
|
+
}
|
|
670
|
+
};
|
|
2202
671
|
|
|
2203
|
-
|
|
2204
|
-
|
|
672
|
+
// src/core/agents/specification-agent.ts
|
|
673
|
+
import fs4 from "fs";
|
|
674
|
+
import path4 from "path";
|
|
675
|
+
var AGENT_TYPE2 = "specification_agent";
|
|
676
|
+
async function interactiveSpecificationAgent(options = {}) {
|
|
677
|
+
FileLogger.init();
|
|
678
|
+
tui.intro("\u{1F3D7}\uFE0F Specification Agent (Template-Based)");
|
|
679
|
+
const projectRoot = process.cwd();
|
|
680
|
+
const sharkRcDir = path4.resolve(projectRoot, "_sharkrc");
|
|
681
|
+
if (!fs4.existsSync(sharkRcDir)) fs4.mkdirSync(sharkRcDir, { recursive: true });
|
|
682
|
+
const outputFile = path4.resolve(sharkRcDir, "tech-spec.md");
|
|
683
|
+
if (!fs4.existsSync(outputFile)) {
|
|
684
|
+
let initialContent = `# Technical Specification: {{PROJECT_NAME}}
|
|
2205
685
|
|
|
2206
|
-
##
|
|
2207
|
-
[TO BE ANALYZED]
|
|
686
|
+
## 1. Technology Stack
|
|
687
|
+
[TO BE ANALYZED - STACK]
|
|
688
|
+
- Language: [e.g. TypeScript]
|
|
689
|
+
- Framework: [e.g. Node.js / React]
|
|
690
|
+
- Database: [e.g. SQLite / PostgreSQL]
|
|
691
|
+
- Key Libraries: [Top 5 dependencies]
|
|
2208
692
|
|
|
2209
|
-
##
|
|
2210
|
-
[TO BE ANALYZED]
|
|
693
|
+
## 2. Architecture Overview
|
|
694
|
+
[TO BE ANALYZED - ARCHITECTURE]
|
|
695
|
+
[Brief description of architectural pattern]
|
|
2211
696
|
|
|
2212
|
-
##
|
|
2213
|
-
[TO BE ANALYZED]
|
|
697
|
+
## 3. Data Model
|
|
698
|
+
[TO BE ANALYZED - DATA MODEL]
|
|
699
|
+
[Schema/ERD definitions]
|
|
2214
700
|
|
|
2215
|
-
##
|
|
2216
|
-
[TO BE ANALYZED]
|
|
701
|
+
## 4. API / Interface Contracts
|
|
702
|
+
[TO BE ANALYZED - API]
|
|
703
|
+
[Main endpoints or CLI commands]
|
|
2217
704
|
|
|
2218
|
-
##
|
|
2219
|
-
[TO BE
|
|
705
|
+
## 5. Implementation Steps
|
|
706
|
+
[TO BE FILLED - MUST BE CHECKBOXES]
|
|
2220
707
|
`;
|
|
2221
|
-
|
|
708
|
+
const projectName = path4.basename(projectRoot);
|
|
709
|
+
initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
|
|
2222
710
|
const BOM = "\uFEFF";
|
|
2223
|
-
|
|
2224
|
-
tui.log.success(
|
|
711
|
+
fs4.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
|
|
712
|
+
tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
2225
713
|
} else {
|
|
2226
|
-
tui.log.info(
|
|
714
|
+
tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
2227
715
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
**
|
|
2247
|
-
|
|
2248
|
-
- \`read_file\` package.json (or pom.xml, go.mod, requirements.txt, etc.)
|
|
2249
|
-
- \`modify_file\` to replace:
|
|
2250
|
-
- \`target_content\`: "## Tech Stack\\n[TO BE ANALYZED]"
|
|
2251
|
-
- \`content\`: Detailed tech stack with versions, dependencies, and their purposes
|
|
2252
|
-
|
|
2253
|
-
**Step 2 - Analyze Directory Structure:**
|
|
2254
|
-
- \`list_files\` on ALL key directories (src, tests, config, docs, etc.)
|
|
2255
|
-
- Map the complete directory tree
|
|
2256
|
-
- \`modify_file\` to replace:
|
|
2257
|
-
- \`target_content\`: "## Directory Structure\\n[TO BE ANALYZED]"
|
|
2258
|
-
- \`content\`: Visual directory tree with purpose of each folder
|
|
2259
|
-
|
|
2260
|
-
**Step 3 - Analyze Architecture:**
|
|
2261
|
-
- \`read_file\` entry points (main.ts, index.js, app.py, etc.)
|
|
2262
|
-
- \`read_file\` 5-10 source files to understand code patterns and organization
|
|
2263
|
-
- Identify architectural pattern (Clean Arch, MVC, Microservices, etc.)
|
|
2264
|
-
- \`modify_file\` to replace:
|
|
2265
|
-
- \`target_content\`: "## Architecture\\n[TO BE ANALYZED]"
|
|
2266
|
-
- \`content\`: Comprehensive architectural description with patterns and module organization
|
|
2267
|
-
|
|
2268
|
-
**Step 4 - Document Components:**
|
|
2269
|
-
- Identify ALL major modules/components by reading source files
|
|
2270
|
-
- For each component: location, purpose, key files
|
|
2271
|
-
- \`modify_file\` to replace:
|
|
2272
|
-
- \`target_content\`: "## Key Components\\n[TO BE ANALYZED]"
|
|
2273
|
-
- \`content\`: Detailed list of components with their purposes
|
|
2274
|
-
|
|
2275
|
-
**Step 5 - Document APIs (if applicable):**
|
|
2276
|
-
- Search for route definitions, controllers, API endpoints
|
|
2277
|
-
- Document base URL, main endpoints, request/response patterns
|
|
2278
|
-
- \`modify_file\` to replace:
|
|
2279
|
-
- \`target_content\`: "## API / Interfaces\\n[TO BE ANALYZED]"
|
|
2280
|
-
- \`content\`: API documentation (or "Not applicable" if no API)
|
|
2281
|
-
|
|
2282
|
-
**Step 6 - Document Data Layer (if applicable):**
|
|
2283
|
-
- Search for database configs, ORM setup, model definitions
|
|
2284
|
-
- Document database type, ORM tool, key entities/tables
|
|
2285
|
-
- \`modify_file\` to replace:
|
|
2286
|
-
- \`target_content\`: "## Data Layer\\n[TO BE ANALYZED]"
|
|
2287
|
-
- \`content\`: Data layer details (or "Not applicable" if no database)
|
|
2288
|
-
|
|
2289
|
-
**Step 7 - Configuration & Environment:**
|
|
2290
|
-
- Read config files (.env.example, config/, etc.)
|
|
2291
|
-
- Identify environment variables and their purposes
|
|
2292
|
-
- \`modify_file\` to replace:
|
|
2293
|
-
- \`target_content\`: "## Configuration & Environment\\n[TO BE ANALYZED]"
|
|
2294
|
-
- \`content\`: List of env vars and config files
|
|
2295
|
-
|
|
2296
|
-
**Step 8 - Build & Development:**
|
|
2297
|
-
- Read package.json scripts, Makefile, build configs
|
|
2298
|
-
- Document dev, build, test, lint commands
|
|
2299
|
-
- \`modify_file\` to replace:
|
|
2300
|
-
- \`target_content\`: "## Build & Development\\n[TO BE ANALYZED]"
|
|
2301
|
-
- \`content\`: Commands for development workflow
|
|
2302
|
-
|
|
2303
|
-
**Step 9 - Patterns & Conventions:**
|
|
2304
|
-
- Based on files read, document naming conventions, code organization
|
|
2305
|
-
- Note error handling, logging strategies
|
|
2306
|
-
- \`modify_file\` to replace:
|
|
2307
|
-
- \`target_content\`: "## Key Patterns & Conventions\\n[TO BE ANALYZED]"
|
|
2308
|
-
- \`content\`: Observed patterns and conventions
|
|
2309
|
-
|
|
2310
|
-
**Step 10 - Overview (LAST):**
|
|
2311
|
-
- Synthesize all findings into a comprehensive overview
|
|
2312
|
-
- \`modify_file\` to replace:
|
|
2313
|
-
- \`target_content\`: "## Overview\\n[TO BE ANALYZED]"
|
|
2314
|
-
- \`content\`: Detailed project description with purpose and main functionality
|
|
2315
|
-
|
|
2316
|
-
**HOW TO USE modify_file:**
|
|
2317
|
-
\`\`\`json
|
|
2318
|
-
{
|
|
2319
|
-
"actions": [{
|
|
2320
|
-
"type": "modify_file",
|
|
2321
|
-
"path": "${configFileRelative}",
|
|
2322
|
-
"target_content": "## Tech Stack\\n[TO BE ANALYZED]",
|
|
2323
|
-
"content": "## Tech Stack\\n- **Language**: TypeScript 5.3\\n- **Runtime**: Node.js 20.x\\n..."
|
|
2324
|
-
}]
|
|
2325
|
-
}
|
|
2326
|
-
\`\`\`
|
|
2327
|
-
|
|
2328
|
-
\`\`\`markdown
|
|
2329
|
-
# Project Context
|
|
2330
|
-
|
|
2331
|
-
## Overview
|
|
2332
|
-
[TO BE ANALYZED]
|
|
2333
|
-
|
|
2334
|
-
## Tech Stack
|
|
2335
|
-
[TO BE ANALYZED]
|
|
2336
|
-
|
|
2337
|
-
## Architecture
|
|
2338
|
-
[TO BE ANALYZED]
|
|
2339
|
-
|
|
2340
|
-
## Directory Structure
|
|
2341
|
-
[TO BE ANALYZED]
|
|
2342
|
-
|
|
2343
|
-
## Key Components
|
|
2344
|
-
[TO BE ANALYZED]
|
|
2345
|
-
|
|
2346
|
-
## API / Interfaces
|
|
2347
|
-
[TO BE ANALYZED]
|
|
2348
|
-
|
|
2349
|
-
## Data Layer
|
|
2350
|
-
[TO BE ANALYZED]
|
|
2351
|
-
|
|
2352
|
-
## Configuration & Environment
|
|
2353
|
-
[TO BE ANALYZED]
|
|
2354
|
-
|
|
2355
|
-
## Build & Development
|
|
2356
|
-
[TO BE ANALYZED]
|
|
2357
|
-
|
|
2358
|
-
## Key Patterns & Conventions
|
|
2359
|
-
[TO BE ANALYZED]
|
|
2360
|
-
\`\`\`
|
|
2361
|
-
|
|
2362
|
-
**Step 2 - Explore and Update Incrementally:**
|
|
2363
|
-
After creating the template, perform these analyses and UPDATE each section:
|
|
2364
|
-
|
|
2365
|
-
**2.1 - Analyze Tech Stack:**
|
|
2366
|
-
- \`list_files\` root directory
|
|
2367
|
-
- \`read_file\` package.json (or equivalent manifest)
|
|
2368
|
-
- \`modify_file\` to replace "## Tech Stack\\n[TO BE ANALYZED]" with detailed findings
|
|
2369
|
-
|
|
2370
|
-
**2.2 - Analyze Directory Structure:**
|
|
2371
|
-
- \`list_files\` on key directories (src, tests, config, etc.)
|
|
2372
|
-
- \`modify_file\` to replace "## Directory Structure\\n[TO BE ANALYZED]" with complete structure
|
|
2373
|
-
|
|
2374
|
-
**2.3 - Analyze Architecture:**
|
|
2375
|
-
- \`read_file\` entry points (main.ts, index.js, etc.)
|
|
2376
|
-
- \`read_file\` 5-10 source files to understand patterns
|
|
2377
|
-
- \`modify_file\` to replace "## Architecture\\n[TO BE ANALYZED]" with architectural insights
|
|
2378
|
-
|
|
2379
|
-
**2.4 - Document Components:**
|
|
2380
|
-
- Identify major modules/components
|
|
2381
|
-
- \`modify_file\` to replace "## Key Components\\n[TO BE ANALYZED]" with component details
|
|
2382
|
-
|
|
2383
|
-
**2.5 - Document APIs (if applicable):**
|
|
2384
|
-
- Search for route definitions, controllers, API endpoints
|
|
2385
|
-
- \`modify_file\` to replace "## API / Interfaces\\n[TO BE ANALYZED]"
|
|
2386
|
-
|
|
2387
|
-
**2.6 - Document Data Layer (if applicable):**
|
|
2388
|
-
- Search for database configs, ORM, models
|
|
2389
|
-
- \`modify_file\` to replace "## Data Layer\\n[TO BE ANALYZED]"
|
|
2390
|
-
|
|
2391
|
-
**2.7 - Final Touches:**
|
|
2392
|
-
- Update remaining sections (Config, Build, Patterns)
|
|
2393
|
-
- Ensure Overview is comprehensive
|
|
2394
|
-
|
|
2395
|
-
**HOW TO USE modify_file:**
|
|
2396
|
-
\`\`\`json
|
|
2397
|
-
{
|
|
2398
|
-
"actions": [{
|
|
2399
|
-
"type": "modify_file",
|
|
2400
|
-
"path": "${configFileRelative}",
|
|
2401
|
-
"target_content": "## Tech Stack\\n[TO BE ANALYZED]",
|
|
2402
|
-
"content": "## Tech Stack\\n- **Language**: TypeScript 5.3\\n- **Runtime**: Node.js 20.x\\n- **Framework**: Express 4.18\\n..."
|
|
2403
|
-
}]
|
|
2404
|
-
}
|
|
2405
|
-
\`\`\`
|
|
2406
|
-
|
|
2407
|
-
**SECTION TEMPLATES (For your updates):**
|
|
2408
|
-
|
|
2409
|
-
**Tech Stack:**
|
|
2410
|
-
\`\`\`markdown
|
|
2411
|
-
## Tech Stack
|
|
2412
|
-
- **Language**: [name + version]
|
|
2413
|
-
- **Runtime**: [name + version]
|
|
2414
|
-
- **Framework**: [name + version]
|
|
2415
|
-
- **Build Tool**: [name]
|
|
2416
|
-
- **Testing**: [framework]
|
|
2417
|
-
- **Key Dependencies**:
|
|
2418
|
-
- [dep-name]: [purpose]
|
|
2419
|
-
- [dep-name]: [purpose]
|
|
2420
|
-
\`\`\`
|
|
2421
|
-
|
|
2422
|
-
**Architecture:**
|
|
2423
|
-
\`\`\`markdown
|
|
2424
|
-
## Architecture
|
|
2425
|
-
[Comprehensive description]
|
|
2426
|
-
- **Pattern**: [Clean Arch, MVC, etc.]
|
|
2427
|
-
- **Module Organization**: [how modules are structured]
|
|
2428
|
-
- **Layer Separation**: [controllers, services, repos]
|
|
2429
|
-
- **Configuration**: [how config is managed]
|
|
2430
|
-
\`\`\`
|
|
2431
|
-
|
|
2432
|
-
**Directory Structure:**
|
|
2433
|
-
\`\`\`markdown
|
|
2434
|
-
## Directory Structure
|
|
2435
|
-
\\\`\\\`\\\`
|
|
2436
|
-
/src
|
|
2437
|
-
/core - [Purpose]
|
|
2438
|
-
/commands - [Purpose]
|
|
2439
|
-
/ui - [Purpose]
|
|
2440
|
-
/tests - [Purpose]
|
|
2441
|
-
/docs - [Purpose]
|
|
2442
|
-
\\\`\\\`\\\`
|
|
2443
|
-
\`\`\`
|
|
2444
|
-
|
|
2445
|
-
**Key Components:**
|
|
2446
|
-
\`\`\`markdown
|
|
2447
|
-
## Key Components
|
|
2448
|
-
|
|
2449
|
-
### [Component Name 1]
|
|
2450
|
-
- **Location**: \\\`path/to/component\\\`
|
|
2451
|
-
- **Purpose**: [What it does]
|
|
2452
|
-
- **Key Files**: [Important files]
|
|
2453
|
-
|
|
2454
|
-
### [Component Name 2]
|
|
2455
|
-
- **Location**: \\\`path/to/component\\\`
|
|
2456
|
-
- **Purpose**: [What it does]
|
|
2457
|
-
- **Key Files**: [Important files]
|
|
2458
|
-
\`\`\`
|
|
2459
|
-
|
|
2460
|
-
**DEPTH REQUIREMENT**:
|
|
2461
|
-
- Read MULTIPLE files (5-10 minimum) to understand patterns
|
|
2462
|
-
- Identify ALL major modules/components
|
|
2463
|
-
- Document API routes, database schemas if applicable
|
|
2464
|
-
- Note design patterns and architectural decisions
|
|
2465
|
-
- List important dependencies with their purposes
|
|
716
|
+
let contextContent = "";
|
|
717
|
+
const contextPath = path4.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
718
|
+
if (fs4.existsSync(contextPath)) {
|
|
719
|
+
contextContent = fs4.readFileSync(contextPath, "utf-8");
|
|
720
|
+
tui.log.info(`\u{1F4D8} Context loaded.`);
|
|
721
|
+
}
|
|
722
|
+
let briefingContent = "";
|
|
723
|
+
if (options.briefingPath && fs4.existsSync(options.briefingPath)) {
|
|
724
|
+
briefingContent = fs4.readFileSync(options.briefingPath, "utf-8");
|
|
725
|
+
tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
|
|
726
|
+
} else {
|
|
727
|
+
const standardBriefing = path4.resolve(projectRoot, "_sharkrc", "briefing.md");
|
|
728
|
+
if (fs4.existsSync(standardBriefing)) {
|
|
729
|
+
briefingContent = fs4.readFileSync(standardBriefing, "utf-8");
|
|
730
|
+
tui.log.info(`\u{1F4C4} Briefing loaded.`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
let initialPrompt = `
|
|
734
|
+
Voc\xEA \xE9 o **Shark Spec**, um Arquiteto de Software S\xEAnior e Tech Lead.
|
|
735
|
+
Seu objetivo final \xE9 produzir uma especifica\xE7\xE3o t\xE9cnica precisa para a tarefa no arquivo \`_sharkrc/tech-spec.md\`.
|
|
2466
736
|
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
- Update sections INCREMENTALLY (steps 2-7)
|
|
2470
|
-
- Do NOT wait to write everything at the end
|
|
2471
|
-
- Use \`modify_file\` to replace "[TO BE ANALYZED]" sections
|
|
2472
|
-
- Be DETAILED and COMPREHENSIVE
|
|
2473
|
-
- Take your time - you have up to 30 steps
|
|
2474
|
-
- Verify facts with \`read_file\` or \`search_file\` before documenting
|
|
2475
|
-
`.trim();
|
|
2476
|
-
await runScanLoop(superPrompt, outputFile);
|
|
2477
|
-
}
|
|
2478
|
-
async function runScanLoop(initialPrompt, targetPath) {
|
|
2479
|
-
let nextPrompt = initialPrompt;
|
|
2480
|
-
let keepGoing = true;
|
|
2481
|
-
let stepCount = 0;
|
|
2482
|
-
const MAX_STEPS = 60;
|
|
2483
|
-
while (keepGoing && stepCount < MAX_STEPS) {
|
|
2484
|
-
stepCount++;
|
|
2485
|
-
const spinner = tui.spinner();
|
|
2486
|
-
const msg = t("commands.scan.analyzing").replace("{step}", stepCount.toString());
|
|
2487
|
-
spinner.start(msg);
|
|
2488
|
-
let responseText = "";
|
|
2489
|
-
let lastResponse = null;
|
|
2490
|
-
try {
|
|
2491
|
-
lastResponse = await callScanAgentApi(nextPrompt, (chunk) => {
|
|
2492
|
-
responseText += chunk;
|
|
2493
|
-
});
|
|
2494
|
-
spinner.stop(t("commands.scan.stepComplete"));
|
|
2495
|
-
if (lastResponse && lastResponse.actions && lastResponse.actions.length > 0) {
|
|
2496
|
-
let executionResults = "";
|
|
2497
|
-
let fileCreated = false;
|
|
2498
|
-
for (const action of lastResponse.actions) {
|
|
2499
|
-
if (action.type === "list_files") {
|
|
2500
|
-
tui.log.info(t("commands.scan.scanningDir").replace("{0}", colors.bold(action.path || ".")));
|
|
2501
|
-
const result = handleListFiles(action.path || ".");
|
|
2502
|
-
executionResults += `[Action list_files(${action.path}) Result]:
|
|
2503
|
-
${result}
|
|
737
|
+
\u26A0\uFE0F O SEU WORKFLOW \xC9 GUIADO POR FASES.
|
|
738
|
+
N\xC3O TENTE ADIANTAR O TRABALHO (ex: investigar c\xF3digo ou preencher template agora).
|
|
2504
739
|
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
executionResults += `[Action read_file(${action.path}) Result]:
|
|
2510
|
-
${result}
|
|
740
|
+
**VOC\xCA EST\xC1 NA FASE 1: ENTENDIMENTO DA TAREFA**
|
|
741
|
+
- Use \`talk_with_user\` para perguntar ao usu\xE1rio qual tarefa espec\xEDfica, funcionalidade ou bug ele precisa especificar.
|
|
742
|
+
- Confirme o escopo e os limites com o usu\xE1rio.
|
|
743
|
+
- Se o escopo estiver perfeitamente claro e confirmado (com o usu\xE1rio), emita "PHASE_COMPLETED" no campo "summary" do JSON para avan\xE7ar.
|
|
2511
744
|
|
|
745
|
+
IMPORTANTE: Toda a sua comunica\xE7\xE3o DEVE ser em Portugu\xEAs.
|
|
2512
746
|
`;
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
${result}
|
|
747
|
+
if (briefingContent) {
|
|
748
|
+
initialPrompt += `
|
|
749
|
+
\u2139\uFE0F Um documento de briefing foi encontrado. Ele define parcialmente a tarefa para a Fase 1.
|
|
750
|
+
Confirme seu entendimento com o usu\xE1rio via \`talk_with_user\` antes de prosseguir para a Fase 2.
|
|
2518
751
|
|
|
752
|
+
--- BRIEFING ---
|
|
753
|
+
${briefingContent}
|
|
754
|
+
----------------
|
|
2519
755
|
`;
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
let isTarget = resolvedActionPath === resolvedTargetPath;
|
|
2524
|
-
if (!isTarget && path7.basename(action.path || "") === "project-context.md") {
|
|
2525
|
-
tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path7.relative(process.cwd(), targetPath)));
|
|
2526
|
-
isTarget = true;
|
|
2527
|
-
action.path = targetPath;
|
|
2528
|
-
}
|
|
2529
|
-
if (isTarget) {
|
|
2530
|
-
const finalPath = targetPath;
|
|
2531
|
-
const BOM = "\uFEFF";
|
|
2532
|
-
if (action.type === "create_file") {
|
|
2533
|
-
const contentToWrite = action.content || "";
|
|
2534
|
-
const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
|
|
2535
|
-
fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
|
|
2536
|
-
tui.log.success(t("commands.scan.generated").replace("{0}", finalPath));
|
|
2537
|
-
fileCreated = true;
|
|
2538
|
-
} else {
|
|
2539
|
-
if (fs6.existsSync(finalPath)) {
|
|
2540
|
-
const currentContent = fs6.readFileSync(finalPath, "utf-8");
|
|
2541
|
-
if (action.target_content && currentContent.includes(action.target_content)) {
|
|
2542
|
-
const newContent = currentContent.replace(action.target_content, action.content || "");
|
|
2543
|
-
const finalContent = newContent.startsWith(BOM) ? newContent : BOM + newContent;
|
|
2544
|
-
fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
|
|
2545
|
-
tui.log.success(t("commands.scan.updated").replace("{0}", finalPath));
|
|
2546
|
-
fileCreated = true;
|
|
2547
|
-
} else {
|
|
2548
|
-
tui.log.warning(t("commands.scan.error") + ": " + t("commands.scan.contentNotFound"));
|
|
2549
|
-
executionResults += `[Action ${action.type}]: Failed. Target content not found in file.
|
|
2550
|
-
`;
|
|
2551
|
-
fileCreated = false;
|
|
2552
|
-
}
|
|
2553
|
-
} else {
|
|
2554
|
-
tui.log.warning(t("commands.scan.error") + ": " + t("commands.scan.notFound"));
|
|
2555
|
-
}
|
|
2556
|
-
}
|
|
2557
|
-
executionResults += `[Action ${action.type}]: Success. Task Completed.
|
|
2558
|
-
`;
|
|
2559
|
-
} else {
|
|
2560
|
-
tui.log.warning(t("commands.scan.error"));
|
|
2561
|
-
executionResults += `[Action ${action.type}]: ${t("commands.scan.skipped")}
|
|
2562
|
-
`;
|
|
2563
|
-
}
|
|
2564
|
-
} else if (action.type === "talk_with_user") {
|
|
2565
|
-
tui.log.info(colors.primary(t("commands.scan.agentAsks")));
|
|
2566
|
-
console.log(action.content);
|
|
2567
|
-
const reply = await tui.text({ message: t("commands.scan.agentInput"), placeholder: t("commands.scan.replyPlaceholder") });
|
|
2568
|
-
executionResults += `[User Reply]: ${reply}
|
|
756
|
+
} else {
|
|
757
|
+
initialPrompt += `
|
|
758
|
+
\u2139\uFE0F Nenhum documento de briefing foi encontrado. Inicie a Fase 1 imediatamente: use \`talk_with_user\` para perguntar ao usu\xE1rio o que precisa ser especificado.
|
|
2569
759
|
`;
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
if (fileCreated) {
|
|
2573
|
-
const currentContent = fs6.readFileSync(targetPath, "utf-8");
|
|
2574
|
-
const pendingSections = [];
|
|
2575
|
-
const lines = currentContent.split("\n");
|
|
2576
|
-
let currentSection = "";
|
|
2577
|
-
for (const line of lines) {
|
|
2578
|
-
if (line.startsWith("## ")) {
|
|
2579
|
-
currentSection = line.substring(3).trim();
|
|
2580
|
-
}
|
|
2581
|
-
if (line.includes("[TO BE ANALYZED]")) {
|
|
2582
|
-
if (currentSection && !pendingSections.includes(currentSection)) {
|
|
2583
|
-
pendingSections.push(currentSection);
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
const pendingMsg = pendingSections.length > 0 ? `
|
|
2588
|
-
|
|
2589
|
-
[System Helper]: ${t("commands.scan.pendingSections").replace("{0}", pendingSections.join(", "))}` : `
|
|
2590
|
-
|
|
2591
|
-
[System Helper]: ${t("commands.scan.allPopulated")}`;
|
|
2592
|
-
nextPrompt = `${executionResults}
|
|
2593
|
-
|
|
2594
|
-
[System]: File updated successfully.${pendingMsg} Please continue with the next step of the analysis and focus on the pending sections.`;
|
|
2595
|
-
FileLogger.log("SCAN", "Section updated, continuing loop", { step: stepCount, pending: pendingSections.length });
|
|
2596
|
-
} else {
|
|
2597
|
-
nextPrompt = executionResults;
|
|
2598
|
-
FileLogger.log("SCAN", "Auto-replying with results", { length: executionResults.length });
|
|
2599
|
-
}
|
|
2600
|
-
} else {
|
|
2601
|
-
tui.log.success(t("commands.scan.completed"));
|
|
2602
|
-
keepGoing = false;
|
|
2603
|
-
}
|
|
2604
|
-
} catch (error) {
|
|
2605
|
-
spinner.stop(t("common.error"));
|
|
2606
|
-
tui.log.error(error.message);
|
|
2607
|
-
keepGoing = false;
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
async function callScanAgentApi(prompt, onChunk) {
|
|
2612
|
-
const realm = await getActiveRealm();
|
|
2613
|
-
const token = await ensureValidToken(realm);
|
|
2614
|
-
let conversationId = await conversationManager.getConversationId(AGENT_TYPE3);
|
|
2615
|
-
const payload = {
|
|
2616
|
-
user_prompt: prompt,
|
|
2617
|
-
streaming: true,
|
|
2618
|
-
stackspot_knowledge: false,
|
|
2619
|
-
return_ks_in_response: true,
|
|
2620
|
-
use_conversation: true,
|
|
2621
|
-
conversation_id: conversationId
|
|
2622
|
-
};
|
|
2623
|
-
const agentVersion = getAgentVersion3();
|
|
2624
|
-
if (agentVersion) {
|
|
2625
|
-
payload.agent_version_number = agentVersion;
|
|
2626
|
-
}
|
|
2627
|
-
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${getAgentId3()}/chat`;
|
|
2628
|
-
let fullMsg = "";
|
|
2629
|
-
let raw = {};
|
|
2630
|
-
FileLogger.log("SCAN", "Calling API", { promptLength: prompt.length });
|
|
2631
|
-
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
2632
|
-
onChunk: (c) => {
|
|
2633
|
-
fullMsg += c;
|
|
2634
|
-
onChunk(c);
|
|
2635
|
-
},
|
|
2636
|
-
onComplete: (msg, metadata) => {
|
|
2637
|
-
const returnedId = metadata?.conversation_id;
|
|
2638
|
-
raw = {
|
|
2639
|
-
message: msg || fullMsg,
|
|
2640
|
-
conversation_id: returnedId || conversationId
|
|
2641
|
-
};
|
|
2642
|
-
},
|
|
2643
|
-
onError: (e) => {
|
|
2644
|
-
throw e;
|
|
2645
|
-
}
|
|
2646
|
-
});
|
|
2647
|
-
const parsed = parseAgentResponse(raw);
|
|
2648
|
-
if (parsed.conversation_id) {
|
|
2649
|
-
await conversationManager.saveConversationId(AGENT_TYPE3, parsed.conversation_id);
|
|
2650
|
-
}
|
|
2651
|
-
return parsed;
|
|
2652
|
-
}
|
|
2653
|
-
|
|
2654
|
-
// src/commands/scan.ts
|
|
2655
|
-
var scanCommand = new Command2("scan").description("Analyze the project and generate context documentation").option("-o, --output <path>", "Output file path (default: _bmad/project-context/project-context.md)").option("--depth <level>", "Scan depth (quick, deep, exhaustive)", "quick").action(async (options) => {
|
|
2656
|
-
try {
|
|
2657
|
-
await interactiveScanAgent(options);
|
|
2658
|
-
} catch (error) {
|
|
2659
|
-
console.error("Error during scan:", error.message);
|
|
2660
|
-
process.exit(1);
|
|
2661
|
-
}
|
|
2662
|
-
});
|
|
2663
|
-
|
|
2664
|
-
// src/commands/dev.ts
|
|
2665
|
-
import { Command as Command3 } from "commander";
|
|
2666
|
-
|
|
2667
|
-
// src/core/agents/developer-agent.ts
|
|
2668
|
-
import fs7 from "fs";
|
|
2669
|
-
import path8 from "path";
|
|
2670
|
-
var AGENT_TYPE4 = "developer_agent";
|
|
2671
|
-
async function validateTypeScript(filePath) {
|
|
2672
|
-
try {
|
|
2673
|
-
const result = await handleRunCommand(`npx tsc --noEmit --skipLibCheck ${filePath}`);
|
|
2674
|
-
if (result.trim() === "" || !result.includes("error TS")) {
|
|
2675
|
-
return { valid: true };
|
|
2676
|
-
}
|
|
2677
|
-
return { valid: false, error: result };
|
|
2678
|
-
} catch (e) {
|
|
2679
|
-
return { valid: false, error: e.message || "TypeScript validation failed" };
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
function getAgentId4(overrideId) {
|
|
2683
|
-
if (overrideId) return overrideId;
|
|
2684
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
2685
|
-
if (config.agents?.dev) return config.agents.dev;
|
|
2686
|
-
if (process.env.STACKSPOT_DEV_AGENT_ID) return process.env.STACKSPOT_DEV_AGENT_ID;
|
|
2687
|
-
return "01KEQCGJ65YENRA4QBXVN1YFFX";
|
|
2688
|
-
}
|
|
2689
|
-
function getAgentVersion4(overrideVersion) {
|
|
2690
|
-
if (overrideVersion) return overrideVersion;
|
|
2691
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
2692
|
-
if (config.agentVersions?.dev) return config.agentVersions.dev;
|
|
2693
|
-
if (process.env.STACKSPOT_DEV_AGENT_VERSION) return process.env.STACKSPOT_DEV_AGENT_VERSION;
|
|
2694
|
-
return void 0;
|
|
2695
|
-
}
|
|
2696
|
-
async function interactiveDeveloperAgent(options = {}) {
|
|
2697
|
-
FileLogger.init();
|
|
2698
|
-
const agentId = getAgentId4();
|
|
2699
|
-
if (agentId === "PENDING_CONFIGURATION") {
|
|
2700
|
-
tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
|
|
2701
|
-
return { success: false, summary: "Missing configuration." };
|
|
2702
760
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
contextContent = fs7.readFileSync(specificContextPath, "utf-8");
|
|
2710
|
-
} catch (e) {
|
|
2711
|
-
tui.log.warning(`Failed to read context file: ${e}`);
|
|
2712
|
-
}
|
|
761
|
+
if (options.initialContext) {
|
|
762
|
+
initialPrompt += `
|
|
763
|
+
--- CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER/FEEDBACK) ---
|
|
764
|
+
${options.initialContext}
|
|
765
|
+
-----------------------------------------------------
|
|
766
|
+
`;
|
|
2713
767
|
}
|
|
2714
|
-
const currentTask = options.taskInstruction || "Analyze the project and fix pending issues.";
|
|
2715
|
-
let basePrompt = ``;
|
|
2716
768
|
if (contextContent) {
|
|
2717
|
-
|
|
769
|
+
initialPrompt += `
|
|
770
|
+
\u2139\uFE0F O contexto do projeto est\xE1 dispon\xEDvel para refer\xEAncia. Use-o na Fase 2 para se alinhar com os padr\xF5es de arquitetura existentes, mas N\xC3O o use para preencher as se\xE7\xF5es de forma gen\xE9rica.
|
|
2718
771
|
|
|
2719
772
|
--- PROJECT CONTEXT ---
|
|
2720
773
|
${contextContent}
|
|
2721
774
|
-----------------------
|
|
2722
775
|
`;
|
|
2723
776
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
${options.history}
|
|
2729
|
-
----------------------------------
|
|
2730
|
-
`;
|
|
2731
|
-
}
|
|
2732
|
-
basePrompt += `
|
|
2733
|
-
|
|
2734
|
-
\u{1F7E2} EXECUTION MODE
|
|
2735
|
-
|
|
2736
|
-
You are a highly skilled Developer Agent.
|
|
2737
|
-
\u{1F449} **CURRENT TASK**: "${currentTask}"
|
|
2738
|
-
|
|
2739
|
-
Your goal is to COMPLETE this specific task and then STOP.
|
|
2740
|
-
1. Implement the necessary changes.
|
|
2741
|
-
2. Verify (compile/test).
|
|
2742
|
-
3. **MANDATORY**: When you are confident the task is done, output a final message starting with "TASK_COMPLETED:" followed by a brief technical summary of what you did.
|
|
2743
|
-
`;
|
|
2744
|
-
let nextPrompt = basePrompt;
|
|
777
|
+
await runSpecLoop(initialPrompt.trim(), outputFile, options.agentId);
|
|
778
|
+
}
|
|
779
|
+
async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
|
|
780
|
+
let nextPrompt = initialMessage;
|
|
2745
781
|
let keepGoing = true;
|
|
2746
|
-
|
|
2747
|
-
let
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
782
|
+
let stepCount = 0;
|
|
783
|
+
let currentPhase = 1;
|
|
784
|
+
const MAX_STEPS = 30;
|
|
785
|
+
while (keepGoing && stepCount < MAX_STEPS) {
|
|
786
|
+
stepCount++;
|
|
787
|
+
const spinner = tui.spinner();
|
|
788
|
+
spinner.start(`\u{1F3D7}\uFE0F Spec Agent working (Step ${stepCount}/${MAX_STEPS})...`);
|
|
789
|
+
let pendingSections = [];
|
|
790
|
+
if (fs4.existsSync(targetPath)) {
|
|
791
|
+
const content = fs4.readFileSync(targetPath, "utf-8");
|
|
792
|
+
if (content.includes("[TO BE ANALYZED]")) pendingSections.push("Analysis Sections (Stack, Arch, Data, API)");
|
|
793
|
+
if (content.includes("[TO BE FILLED")) pendingSections.push("Implementation Steps");
|
|
794
|
+
}
|
|
795
|
+
if (pendingSections.length === 0 && stepCount > 1) {
|
|
796
|
+
}
|
|
797
|
+
let responseText = "";
|
|
798
|
+
let lastResponse = null;
|
|
2755
799
|
try {
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
},
|
|
800
|
+
lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
|
|
801
|
+
responseText += chunk;
|
|
802
|
+
}, overrideAgentId);
|
|
2759
803
|
spinner.stop("Response received");
|
|
2760
|
-
if (lastResponse) {
|
|
2761
|
-
const response = lastResponse;
|
|
2762
|
-
const actions = response.actions || [];
|
|
2763
|
-
if (response.message && response.message.includes("TASK_COMPLETED:")) {
|
|
2764
|
-
isTaskCompleted = true;
|
|
2765
|
-
finalSummary = response.message.split("TASK_COMPLETED:")[1].trim();
|
|
2766
|
-
keepGoing = false;
|
|
2767
|
-
}
|
|
2768
|
-
if (response.message && response.message.includes("TASK_FAILED:")) {
|
|
2769
|
-
const failureReason = response.message.split("TASK_FAILED:")[1].trim();
|
|
2770
|
-
tui.log.error(`\u274C Agent reported task failure: ${failureReason}`);
|
|
2771
|
-
return { success: false, summary: failureReason };
|
|
2772
|
-
}
|
|
2773
|
-
if (actions.length === 0 && response.message && !isTaskCompleted) {
|
|
2774
|
-
tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
2775
|
-
console.log(response.message);
|
|
2776
|
-
const userReply = await tui.text({ message: "Your answer:" });
|
|
2777
|
-
if (tui.isCancel(userReply)) {
|
|
2778
|
-
keepGoing = false;
|
|
2779
|
-
break;
|
|
2780
|
-
}
|
|
2781
|
-
nextPrompt = userReply;
|
|
2782
|
-
}
|
|
804
|
+
if (lastResponse && lastResponse.actions) {
|
|
2783
805
|
let executionResults = "";
|
|
2784
806
|
let waitingForUser = false;
|
|
2785
|
-
|
|
807
|
+
let specUpdated = false;
|
|
808
|
+
let hasSystemError = false;
|
|
809
|
+
let systemErrorContent = "";
|
|
810
|
+
for (const action of lastResponse.actions) {
|
|
2786
811
|
if (action.type === "talk_with_user") {
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
812
|
+
const isSystemError = action.content?.startsWith("[SYSTEM ERROR]");
|
|
813
|
+
if (isSystemError) {
|
|
814
|
+
tui.log.error(`\u26A0\uFE0F Detectado erro na resposta do Agente (truncado ou inv\xE1lido).`);
|
|
815
|
+
tui.log.info(colors.dim(action.content || ""));
|
|
816
|
+
const approved = await tui.confirm({ message: `Enviar notifica\xE7\xE3o de erro para o agente tentar se recuperar automaticamente?` });
|
|
817
|
+
if (approved) {
|
|
818
|
+
hasSystemError = true;
|
|
819
|
+
systemErrorContent = action.content || "";
|
|
820
|
+
} else {
|
|
821
|
+
const userReply = await tui.text({ message: "Seu prompt alternativo para o agente:" });
|
|
822
|
+
if (tui.isCancel(userReply)) {
|
|
823
|
+
keepGoing = false;
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
hasSystemError = true;
|
|
827
|
+
systemErrorContent = userReply;
|
|
828
|
+
}
|
|
829
|
+
} else {
|
|
830
|
+
tui.log.info(colors.primary("\u{1F916} Architect:"));
|
|
831
|
+
console.log(action.content);
|
|
832
|
+
waitingForUser = true;
|
|
833
|
+
}
|
|
2790
834
|
} else if (action.type === "list_files") {
|
|
2791
835
|
tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
|
|
2792
836
|
const result = handleListFiles(action.path || ".");
|
|
@@ -2802,359 +846,205 @@ ${result}
|
|
|
2802
846
|
|
|
2803
847
|
`;
|
|
2804
848
|
} else if (action.type === "search_file") {
|
|
849
|
+
tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
|
|
2805
850
|
const result = handleSearchFile(action.path || "");
|
|
2806
851
|
executionResults += `[Action search_file(${action.path}) Result]:
|
|
2807
852
|
${result}
|
|
2808
853
|
|
|
2809
854
|
`;
|
|
2810
|
-
} else if (action.type === "
|
|
2811
|
-
const
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
|
|
2818
|
-
});
|
|
2819
|
-
if (choice === "always") {
|
|
2820
|
-
autoApprovals.commands = true;
|
|
2821
|
-
approved = true;
|
|
2822
|
-
} else if (choice === "yes") approved = true;
|
|
2823
|
-
}
|
|
2824
|
-
if (approved) {
|
|
2825
|
-
const result = await handleRunCommand(cmd);
|
|
2826
|
-
executionResults += `[Action run_command(${cmd}) Result]:
|
|
855
|
+
} else if (action.type === "search_code") {
|
|
856
|
+
const glob = action.path || "src/**/*";
|
|
857
|
+
const query = action.query || "";
|
|
858
|
+
const isRegex = action.is_regex === true;
|
|
859
|
+
tui.log.info(`\u{1F50E} Search code: ${colors.dim(`"${query}" in ${glob}`)}`);
|
|
860
|
+
const result = handleSearchCode(glob, query, isRegex);
|
|
861
|
+
executionResults += `[Action search_code("${query}" in "${glob}") Result]:
|
|
2827
862
|
${result}
|
|
2828
863
|
|
|
2829
864
|
`;
|
|
2830
|
-
} else {
|
|
2831
|
-
executionResults += `[Action run_command]: User blocked execution.
|
|
2832
|
-
|
|
2833
|
-
`;
|
|
2834
|
-
}
|
|
2835
865
|
} else if (["create_file", "modify_file"].includes(action.type)) {
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
let
|
|
2839
|
-
if (!
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
if (choice === "always") {
|
|
2845
|
-
autoApprovals.files = true;
|
|
2846
|
-
approved = true;
|
|
2847
|
-
} else if (choice === "yes") approved = true;
|
|
866
|
+
let actionPath = path4.resolve(action.path || "");
|
|
867
|
+
const resolvedTargetPath = path4.resolve(targetPath);
|
|
868
|
+
let isTarget = actionPath === resolvedTargetPath;
|
|
869
|
+
if (!isTarget && path4.basename(actionPath) === "tech-spec.md") {
|
|
870
|
+
tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${path4.relative(process.cwd(), targetPath)}`);
|
|
871
|
+
action.path = targetPath;
|
|
872
|
+
actionPath = resolvedTargetPath;
|
|
873
|
+
isTarget = true;
|
|
2848
874
|
}
|
|
2849
|
-
if (
|
|
875
|
+
if (!isTarget && action.type === "create_file") {
|
|
876
|
+
const confirm = await tui.confirm({ message: `Agent wants to create ${action.path}. Allow?` });
|
|
877
|
+
if (!confirm) {
|
|
878
|
+
executionResults += `[Action create_file]: User denied.
|
|
879
|
+
`;
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
2850
884
|
if (action.type === "create_file") {
|
|
2851
|
-
const
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
executionResults += `[Action create_file]: Success
|
|
2855
|
-
|
|
885
|
+
const BOM = "\uFEFF";
|
|
886
|
+
fs4.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
|
|
887
|
+
tui.log.success(`\u2705 Created: ${action.path}`);
|
|
888
|
+
executionResults += `[Action create_file]: Success.
|
|
2856
889
|
`;
|
|
2857
|
-
} else {
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
` : `[Action modify_file]: Failed
|
|
2864
|
-
|
|
890
|
+
} else if (action.type === "modify_file") {
|
|
891
|
+
if (action.target_content) {
|
|
892
|
+
const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
|
|
893
|
+
if (success) {
|
|
894
|
+
executionResults += `[Action modify_file]: Success.
|
|
2865
895
|
`;
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
896
|
+
specUpdated = true;
|
|
897
|
+
} else {
|
|
898
|
+
executionResults += `[Action modify_file]: Failed. Target content not found or ambiguous.
|
|
2870
899
|
`;
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
executionResults += `[Action modify_file]: Failed. 'target_content' is required.
|
|
2874
903
|
`;
|
|
2875
|
-
|
|
2876
|
-
} else if (action.type.startsWith("ast_")) {
|
|
2877
|
-
try {
|
|
2878
|
-
let result = "";
|
|
2879
|
-
if (action.type === "ast_list_structure") {
|
|
2880
|
-
result = await astListStructure(action.path || "");
|
|
2881
|
-
} else if (action.type === "ast_get_method") {
|
|
2882
|
-
result = await astGetMethod(action.path || "", action.class_name || "", action.method_name || "");
|
|
2883
|
-
} else if (action.type === "ast_add_method") {
|
|
2884
|
-
const success = await astAddMethod(action.path || "", action.class_name || "", action.method_code || "");
|
|
2885
|
-
result = success ? "Method added successfully." : "Failed to add method.";
|
|
2886
|
-
} else if (action.type === "ast_modify_method") {
|
|
2887
|
-
const success = await astModifyMethod(action.path || "", action.class_name || "", action.method_name || "", action.new_body || "");
|
|
2888
|
-
result = success ? "Method modified successfully." : "Failed to modify method.";
|
|
2889
|
-
} else if (action.type === "ast_remove_method") {
|
|
2890
|
-
const success = await astRemoveMethod(action.path || "", action.class_name || "", action.method_name || "");
|
|
2891
|
-
result = success ? "Method removed successfully." : "Failed to remove method.";
|
|
2892
|
-
} else if (action.type === "ast_add_class") {
|
|
2893
|
-
const success = await astAddClass(action.path || "", action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
|
|
2894
|
-
result = success ? "Class added successfully." : "Failed to add class.";
|
|
2895
|
-
} else if (action.type === "ast_get_property") {
|
|
2896
|
-
tui.log.info(`\u{1F50D} Reading property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
|
|
2897
|
-
const propContent = await astGetProperty(action.path || "", action.class_name || "", action.property_name || "");
|
|
2898
|
-
result = `[AST Get Property] Content:
|
|
2899
|
-
${propContent}`;
|
|
2900
|
-
} else if (action.type === "ast_add_property") {
|
|
2901
|
-
const success = await astAddProperty(action.path || "", action.class_name || "", action.property_code || "");
|
|
2902
|
-
result = success ? "Property added successfully." : "Failed to add property.";
|
|
2903
|
-
} else if (action.type === "ast_modify_property") {
|
|
2904
|
-
tui.log.info(`\u270F\uFE0F Modifying property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
|
|
2905
|
-
const success = await astModifyProperty(action.path || "", action.class_name || "", action.property_name || "", action.property_code || "");
|
|
2906
|
-
result = success ? "Property modified successfully." : "Failed to modify property.";
|
|
2907
|
-
} else if (action.type === "ast_remove_property") {
|
|
2908
|
-
const success = await astRemoveProperty(action.path || "", action.class_name || "", action.property_name || "");
|
|
2909
|
-
result = success ? "Property removed successfully." : "Failed to remove property.";
|
|
2910
|
-
} else if (action.type === "ast_add_decorator") {
|
|
2911
|
-
const success = await astAddDecorator(action.path || "", action.class_name || "", action.decorator_code || "");
|
|
2912
|
-
result = success ? "Decorator added successfully." : "Failed to add decorator.";
|
|
2913
|
-
} else if (action.type === "ast_add_interface") {
|
|
2914
|
-
const success = await astAddInterface(action.path || "", action.interface_code || "");
|
|
2915
|
-
result = success ? "Interface added successfully." : "Failed to add interface.";
|
|
2916
|
-
} else if (action.type === "ast_add_type_alias") {
|
|
2917
|
-
const success = await astAddTypeAlias(action.path || "", action.type_code || "");
|
|
2918
|
-
result = success ? "Type alias added successfully." : "Failed to add type alias.";
|
|
2919
|
-
} else if (action.type === "ast_add_function") {
|
|
2920
|
-
const success = await astAddFunction(action.path || "", action.function_code || "");
|
|
2921
|
-
result = success ? "Function added successfully." : "Failed to add function.";
|
|
2922
|
-
} else if (action.type === "ast_remove_function") {
|
|
2923
|
-
const success = await astRemoveFunction(action.path || "", action.function_name || "");
|
|
2924
|
-
result = success ? "Function removed successfully." : "Failed to remove function.";
|
|
2925
|
-
} else if (action.type === "ast_add_import") {
|
|
2926
|
-
const success = await astAddImport(action.path || "", action.import_statement || "");
|
|
2927
|
-
result = success ? "Import added successfully." : "Failed to add import.";
|
|
2928
|
-
} else if (action.type === "ast_remove_import") {
|
|
2929
|
-
const success = await astRemoveImport(action.path || "", action.module_path || "");
|
|
2930
|
-
result = success ? "Import removed successfully." : "Failed to remove import.";
|
|
2931
|
-
} else if (action.type === "ast_organize_imports") {
|
|
2932
|
-
const success = await astOrganizeImports(action.path || "");
|
|
2933
|
-
result = success ? "Imports organized successfully." : "Failed to organize imports.";
|
|
2934
|
-
} else {
|
|
2935
|
-
result = `Unknown AST action: ${action.type}`;
|
|
904
|
+
}
|
|
2936
905
|
}
|
|
2937
|
-
executionResults += `[Action ${action.type} Result]:
|
|
2938
|
-
${result}
|
|
2939
|
-
|
|
2940
|
-
`;
|
|
2941
|
-
tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
|
|
2942
906
|
} catch (e) {
|
|
2943
|
-
executionResults += `[Action ${action.type}
|
|
2944
|
-
|
|
907
|
+
executionResults += `[Action ${action.type}]: Error: ${e.message}
|
|
2945
908
|
`;
|
|
2946
|
-
tui.log.error(`\u274C AST Action Error: ${e.message}`);
|
|
2947
909
|
}
|
|
2948
|
-
}
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
|
|
913
|
+
const extraContext = executionResults ? `
|
|
2952
914
|
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
915
|
+
Resultados das \xFAltimas a\xE7\xF5es executadas antes da conclus\xE3o:
|
|
916
|
+
${executionResults}` : "";
|
|
917
|
+
if (currentPhase === 1) {
|
|
918
|
+
currentPhase = 2;
|
|
919
|
+
tui.log.success(`\u2705 Fase 1 Conclu\xEDda. Iniciando Fase 2 (Investiga\xE7\xE3o).`);
|
|
920
|
+
nextPrompt = `[System Message]
|
|
921
|
+
Voc\xEA completou a FASE 1 com sucesso.
|
|
2957
922
|
|
|
2958
|
-
|
|
923
|
+
**VOC\xCA AGORA EST\xC1 NA FASE 2: INVESTIGA\xC7\xC3O**
|
|
924
|
+
- Use \`search_code\` e \`list_files\` para explorar os arquivos relevantes \xE0 tarefa.
|
|
925
|
+
- Prefira \`search_code\` em vez de \`read_file\` para buscar c\xF3digo sem inflar o contexto.
|
|
926
|
+
- N\xC3O leia o projeto inteiro de forma gen\xE9rica.
|
|
927
|
+
- REGRA DE OURO (READ-FIRST): Voc\xEA N\xC3O PODE referenciar um arquivo na especifica\xE7\xE3o t\xE9cnica que n\xE3o tenha investigado nesta fase.
|
|
928
|
+
- Quando achar que possui toda a clareza t\xE9cnica sobre onde e o que deve ser feito no c\xF3digo, emita "PHASE_COMPLETED" no summary.${extraContext}`;
|
|
929
|
+
continue;
|
|
930
|
+
} else if (currentPhase === 2) {
|
|
931
|
+
currentPhase = 3;
|
|
932
|
+
tui.log.success(`\u2705 Fase 2 Conclu\xEDda. Iniciando Fase 3 (Preenchimento).`);
|
|
933
|
+
nextPrompt = `[System Message]
|
|
934
|
+
Voc\xEA completou a FASE 2 com sucesso.
|
|
2959
935
|
|
|
2960
|
-
|
|
936
|
+
**VOC\xCA AGORA EST\xC1 NA FASE 3: PREENCHIMENTO DO TEMPLATE**
|
|
937
|
+
- Use \`modify_file\` no arquivo \`${targetPath}\` para substituir os placeholders pelo conte\xFAdo real levantado na fase de investiga\xE7\xE3o.
|
|
938
|
+
- As se\xE7\xF5es 1-4 devem descrever o contexto da TAREFA, e n\xE3o o projeto como um todo.
|
|
939
|
+
- Passos de Implementa\xE7\xE3o (Implementation Steps): APENAS checkboxes markdown: \`- [ ] [Verbo de A\xE7\xE3o] [O Que] em [Caminho Relativo]\`.
|
|
940
|
+
- Quando TODOS os placeholders ([TO BE ANALYZED...] ou [TO BE FILLED]) forem substitu\xEDdos e o trabalho conclu\xEDdo, emita "SPEC_UPDATED: Complete" no summary para finalizar.${extraContext}`;
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (lastResponse.message && lastResponse.message.includes("SPEC_UPDATED:")) {
|
|
945
|
+
if (currentPhase < 3) {
|
|
946
|
+
tui.log.warning(`O agente tentou finalizar prematuramente. For\xE7ando retorno para a fase atual...`);
|
|
947
|
+
nextPrompt = `[System Error]: Voc\xEA tentou finalizar a especifica\xE7\xE3o prematuramente emitindo SPEC_UPDATED, mas ainda est\xE1 na Fase ${currentPhase}. Voc\xEA s\xF3 pode finalizar quando estiver na Fase 3.
|
|
948
|
+
|
|
949
|
+
Continue seu trabalho na Fase ${currentPhase} ou emita "PHASE_COMPLETED" se terminou esta etapa atual.`;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const content = fs4.existsSync(targetPath) ? fs4.readFileSync(targetPath, "utf-8") : "";
|
|
953
|
+
if (content.includes("[TO BE")) {
|
|
954
|
+
const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
|
|
955
|
+
let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "algumas se\xE7\xF5es";
|
|
956
|
+
tui.log.warning(`O agente tentou concluir prematuramente, mas h\xE1 placeholders pendentes. For\xE7ando retorno...`);
|
|
957
|
+
nextPrompt = `[System Error]: A valida\xE7\xE3o falhou e o bloqueio autom\xE1tico foi acionado.
|
|
958
|
+
Voc\xEA tentou concluir a tarefa, mas o arquivo AINDA possui placeholders '[TO BE ANALYZED...]' ou '[TO BE FILLED]'.
|
|
959
|
+
As seguintes se\xE7\xF5es ainda cont\xEAm estes placeholders: ${missing}.
|
|
960
|
+
Voc\xEA \xE9 OBRIGADO a usar a action \`modify_file\` para preencher o conte\xFAdo de cada uma dessas se\xE7\xF5es. Use o placeholder exato no campo \`target_content\`. N\xC3O repita a conclus\xE3o da tarefa at\xE9 corrigir todas as pend\xEAncias.`;
|
|
961
|
+
continue;
|
|
962
|
+
} else {
|
|
963
|
+
const updateSummary = lastResponse.message.split("SPEC_UPDATED:")[1].trim();
|
|
964
|
+
tui.log.success(`\u2705 Spec Finalized: ${updateSummary}`);
|
|
965
|
+
return;
|
|
2961
966
|
}
|
|
2962
967
|
}
|
|
2963
968
|
if (executionResults) {
|
|
2964
|
-
|
|
969
|
+
FileLogger.log("TOOL_EXECUTION", "Specification Agent Actions Output", { executionResults });
|
|
970
|
+
}
|
|
971
|
+
if (hasSystemError) {
|
|
972
|
+
nextPrompt = systemErrorContent;
|
|
973
|
+
} else if (waitingForUser) {
|
|
974
|
+
const userReply = await tui.text({ message: "Your answer", placeholder: "Type your answer..." });
|
|
975
|
+
if (tui.isCancel(userReply)) {
|
|
976
|
+
keepGoing = false;
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
FileLogger.log("USER_INPUT", "User response to specification agent", { userReply });
|
|
980
|
+
nextPrompt = `${executionResults}
|
|
981
|
+
|
|
982
|
+
User Reply: ${userReply}`;
|
|
983
|
+
} else if (executionResults) {
|
|
984
|
+
const content = fs4.existsSync(targetPath) ? fs4.readFileSync(targetPath, "utf-8") : "";
|
|
985
|
+
let systemMsg = "Execu\xE7\xE3o da ferramenta conclu\xEDda.";
|
|
986
|
+
if (specUpdated) {
|
|
987
|
+
if (content.includes("[TO BE")) {
|
|
988
|
+
const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
|
|
989
|
+
let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "v\xE1rias se\xE7\xF5es";
|
|
990
|
+
systemMsg += `
|
|
991
|
+
[System]: Se\xE7\xE3o atualizada com sucesso. A valida\xE7\xE3o detectou que AINDA H\xC1 placeholders pendentes ('[TO BE...]') nas seguintes se\xE7\xF5es: ${missing}.
|
|
992
|
+
Por favor, envie uma nova action \`modify_file\` focada em uma destas se\xE7\xF5es obrigatoriamente. USE o respectivo placeholder no campo \`target_content\` para que o replace funcione.`;
|
|
993
|
+
} else {
|
|
994
|
+
systemMsg += "\n[System]: O arquivo parece completo! Se estiver satisfeito e possuir TODAS as implementa\xE7\xF5es descritas, retorne 'SPEC_UPDATED: Complete'.";
|
|
995
|
+
}
|
|
996
|
+
} else {
|
|
997
|
+
systemMsg += "\n[System]: A modifica\xE7\xE3o do arquivo falhou. Verifique se o `target_content` que voc\xEA usou existe EXATAMENTE como no arquivo e se ele \xE9 \xDANICO na hora de usar a action `modify_file`.";
|
|
998
|
+
}
|
|
999
|
+
nextPrompt = `${executionResults}
|
|
1000
|
+
|
|
1001
|
+
${systemMsg}`;
|
|
1002
|
+
} else {
|
|
1003
|
+
if (lastResponse.message) {
|
|
1004
|
+
tui.log.info(colors.primary("\u{1F916} Architect (Message only):"));
|
|
1005
|
+
console.log(lastResponse.message);
|
|
2965
1006
|
const userReply = await tui.text({ message: "Your answer:" });
|
|
2966
1007
|
if (tui.isCancel(userReply)) {
|
|
2967
1008
|
keepGoing = false;
|
|
2968
1009
|
break;
|
|
2969
1010
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
1011
|
+
FileLogger.log("USER_INPUT", "User response to specification agent (Message only)", { userReply });
|
|
1012
|
+
nextPrompt = userReply;
|
|
2972
1013
|
} else {
|
|
2973
|
-
|
|
2974
|
-
[System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
|
|
2975
|
-
tui.log.info(colors.dim("Processing results..."));
|
|
1014
|
+
keepGoing = false;
|
|
2976
1015
|
}
|
|
2977
|
-
} else if (!keepGoing) {
|
|
2978
|
-
} else if (waitingForUser) {
|
|
2979
|
-
} else {
|
|
2980
|
-
if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
|
|
2981
1016
|
}
|
|
2982
1017
|
} else {
|
|
2983
|
-
tui.log.warning("No
|
|
1018
|
+
tui.log.warning("No actions received.");
|
|
1019
|
+
keepGoing = false;
|
|
2984
1020
|
}
|
|
2985
|
-
} catch (
|
|
2986
|
-
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
spinner.stop("Error");
|
|
1023
|
+
tui.log.error(error.message);
|
|
2987
1024
|
keepGoing = false;
|
|
2988
|
-
return { success: false, summary: `Error: ${e.message}` };
|
|
2989
1025
|
}
|
|
2990
1026
|
}
|
|
2991
|
-
tui.log.success("\u2705 Task Scope Completed");
|
|
2992
|
-
return { success: true, summary: finalSummary || "Task completed without summary." };
|
|
2993
1027
|
}
|
|
2994
|
-
async function
|
|
2995
|
-
const
|
|
2996
|
-
|
|
2997
|
-
const
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
const agentVersion = getAgentVersion4();
|
|
3006
|
-
if (agentVersion) {
|
|
3007
|
-
payload.agent_version_number = agentVersion;
|
|
3008
|
-
}
|
|
3009
|
-
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${getAgentId4()}/chat`;
|
|
3010
|
-
let fullMsg = "";
|
|
3011
|
-
let raw = {};
|
|
3012
|
-
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
3013
|
-
onChunk: (c) => {
|
|
3014
|
-
fullMsg += c;
|
|
3015
|
-
onChunk(c);
|
|
3016
|
-
},
|
|
3017
|
-
onComplete: (msg, metadata) => {
|
|
3018
|
-
const returnedId = metadata?.conversation_id;
|
|
3019
|
-
raw = {
|
|
3020
|
-
message: msg || fullMsg,
|
|
3021
|
-
conversation_id: returnedId || conversationId
|
|
3022
|
-
};
|
|
3023
|
-
},
|
|
3024
|
-
onError: (e) => {
|
|
3025
|
-
throw e;
|
|
3026
|
-
}
|
|
1028
|
+
async function callSpecAgentApi(prompt, onChunk, agentId) {
|
|
1029
|
+
const conversationId = await conversationManager.getConversationId(AGENT_TYPE2);
|
|
1030
|
+
FileLogger.log("AGENT", "Calling Agent API", { agentId, conversationId });
|
|
1031
|
+
const provider = ProviderResolver.getProvider("specification_agent");
|
|
1032
|
+
if (agentId && "agentId" in provider) {
|
|
1033
|
+
provider.agentId = agentId;
|
|
1034
|
+
}
|
|
1035
|
+
const parsed = await provider.streamChat(prompt, {
|
|
1036
|
+
conversationId,
|
|
1037
|
+
agentType: "specification_agent",
|
|
1038
|
+
onChunk
|
|
3027
1039
|
});
|
|
3028
|
-
const parsed = parseAgentResponse(raw);
|
|
3029
1040
|
if (parsed.conversation_id) {
|
|
3030
|
-
await conversationManager.saveConversationId(
|
|
1041
|
+
await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
|
|
3031
1042
|
}
|
|
3032
1043
|
return parsed;
|
|
3033
1044
|
}
|
|
3034
1045
|
|
|
3035
|
-
// src/
|
|
3036
|
-
|
|
3037
|
-
import path9 from "path";
|
|
3038
|
-
var TaskManager = class {
|
|
3039
|
-
projectRoot;
|
|
3040
|
-
specPath;
|
|
3041
|
-
constructor(projectRoot = process.cwd()) {
|
|
3042
|
-
this.projectRoot = projectRoot;
|
|
3043
|
-
this.specPath = path9.resolve(this.projectRoot, "_sharkrc", "tech-spec.md");
|
|
3044
|
-
}
|
|
3045
|
-
/**
|
|
3046
|
-
* Reads the tech-spec.md file and analyzes its current state.
|
|
3047
|
-
*/
|
|
3048
|
-
analyzeSpecState() {
|
|
3049
|
-
if (!fs8.existsSync(this.specPath)) {
|
|
3050
|
-
return { status: "MISSING", allTasks: [] };
|
|
3051
|
-
}
|
|
3052
|
-
const content = fs8.readFileSync(this.specPath, "utf-8");
|
|
3053
|
-
const lines = content.split("\n");
|
|
3054
|
-
const tasks = [];
|
|
3055
|
-
let taskIndex = 1;
|
|
3056
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3057
|
-
const line = lines[i];
|
|
3058
|
-
const trimmed = line.trim();
|
|
3059
|
-
const pendingMatch = trimmed.match(/^- \[ \] (.*)/);
|
|
3060
|
-
const completedMatch = trimmed.match(/^- \[x\] (.*)/i);
|
|
3061
|
-
const progressMatch = trimmed.match(/^- \[\/\] (.*)/);
|
|
3062
|
-
let currentTask = null;
|
|
3063
|
-
let status2 = null;
|
|
3064
|
-
let description = "";
|
|
3065
|
-
if (pendingMatch) {
|
|
3066
|
-
description = pendingMatch[1].trim();
|
|
3067
|
-
status2 = "PENDING";
|
|
3068
|
-
} else if (completedMatch) {
|
|
3069
|
-
description = completedMatch[1].trim();
|
|
3070
|
-
status2 = "COMPLETED";
|
|
3071
|
-
} else if (progressMatch) {
|
|
3072
|
-
description = progressMatch[1].trim();
|
|
3073
|
-
status2 = "IN_PROGRESS";
|
|
3074
|
-
}
|
|
3075
|
-
if (status2 && description) {
|
|
3076
|
-
let j = i + 1;
|
|
3077
|
-
while (j < lines.length) {
|
|
3078
|
-
const nextLine = lines[j];
|
|
3079
|
-
const nextTrimmed = nextLine.trim();
|
|
3080
|
-
if (!nextTrimmed || nextTrimmed.match(/^- \[[ x\/]\]/) || nextTrimmed.startsWith("#")) {
|
|
3081
|
-
break;
|
|
3082
|
-
}
|
|
3083
|
-
description += "\n" + nextTrimmed;
|
|
3084
|
-
j++;
|
|
3085
|
-
}
|
|
3086
|
-
currentTask = {
|
|
3087
|
-
id: `task-${taskIndex++}`,
|
|
3088
|
-
description,
|
|
3089
|
-
status: status2,
|
|
3090
|
-
line_number: i
|
|
3091
|
-
};
|
|
3092
|
-
tasks.push(currentTask);
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
let nextTask = tasks.find((t2) => t2.status === "IN_PROGRESS");
|
|
3096
|
-
if (!nextTask) {
|
|
3097
|
-
nextTask = tasks.find((t2) => t2.status === "PENDING");
|
|
3098
|
-
}
|
|
3099
|
-
const status = !nextTask && tasks.length > 0 && tasks.every((t2) => t2.status === "COMPLETED") ? "COMPLETED" : "PENDING";
|
|
3100
|
-
return {
|
|
3101
|
-
status: tasks.length === 0 ? "MISSING" : status,
|
|
3102
|
-
// Empty file is effectively "pending creation" but we treat as missing content logic elsewhere
|
|
3103
|
-
nextTask,
|
|
3104
|
-
allTasks: tasks
|
|
3105
|
-
};
|
|
3106
|
-
}
|
|
3107
|
-
/**
|
|
3108
|
-
* Marks a specific task as COMPLETED in the file.
|
|
3109
|
-
* Uses line-based replacement to be safe.
|
|
3110
|
-
*/
|
|
3111
|
-
markTaskAsDone(taskId) {
|
|
3112
|
-
const state = this.analyzeSpecState();
|
|
3113
|
-
const task = state.allTasks.find((t2) => t2.id === taskId);
|
|
3114
|
-
if (!task) {
|
|
3115
|
-
console.error(`Task ${taskId} not found.`);
|
|
3116
|
-
return false;
|
|
3117
|
-
}
|
|
3118
|
-
const content = fs8.readFileSync(this.specPath, "utf-8");
|
|
3119
|
-
const lines = content.split("\n");
|
|
3120
|
-
const targetLine = lines[task.line_number];
|
|
3121
|
-
if (!targetLine.includes(task.description)) {
|
|
3122
|
-
console.error(`Concurrency Error: Task line content mistmatch. Expected "${task.description}" at line ${task.line_number}.`);
|
|
3123
|
-
return false;
|
|
3124
|
-
}
|
|
3125
|
-
const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
|
|
3126
|
-
lines[task.line_number] = newLine;
|
|
3127
|
-
fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
3128
|
-
return true;
|
|
3129
|
-
}
|
|
3130
|
-
/**
|
|
3131
|
-
* Marks a task as IN_PROGRESS.
|
|
3132
|
-
*/
|
|
3133
|
-
markTaskInProgress(taskId) {
|
|
3134
|
-
const state = this.analyzeSpecState();
|
|
3135
|
-
const task = state.allTasks.find((t2) => t2.id === taskId);
|
|
3136
|
-
if (!task) return false;
|
|
3137
|
-
const content = fs8.readFileSync(this.specPath, "utf-8");
|
|
3138
|
-
const lines = content.split("\n");
|
|
3139
|
-
let targetLine = lines[task.line_number];
|
|
3140
|
-
targetLine = targetLine.replace("- [ ]", "- [/]");
|
|
3141
|
-
lines[task.line_number] = targetLine;
|
|
3142
|
-
fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
3143
|
-
return true;
|
|
3144
|
-
}
|
|
3145
|
-
/**
|
|
3146
|
-
* Completely updates the spec file content (used by Spec Agent).
|
|
3147
|
-
*/
|
|
3148
|
-
updateSpecContent(newContent) {
|
|
3149
|
-
fs8.writeFileSync(this.specPath, newContent, "utf-8");
|
|
3150
|
-
}
|
|
3151
|
-
getSpecPath() {
|
|
3152
|
-
return this.specPath;
|
|
3153
|
-
}
|
|
3154
|
-
};
|
|
3155
|
-
|
|
3156
|
-
// src/commands/dev.ts
|
|
3157
|
-
var devCommand = new Command3("dev").description("Starts the Shark Developer Agent (Shark Dev Orchestration V2)").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").action(async (options) => {
|
|
1046
|
+
// src/commands/legacy.ts
|
|
1047
|
+
var legacyCommand = new Command3("legacy").description("Starts the Legacy Developer Agent task orchestration loop").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").action(async (options) => {
|
|
3158
1048
|
const taskManager = new TaskManager(process.cwd());
|
|
3159
1049
|
let state = taskManager.analyzeSpecState();
|
|
3160
1050
|
if (state.status === "MISSING") {
|
|
@@ -3163,7 +1053,6 @@ var devCommand = new Command3("dev").description("Starts the Shark Developer Age
|
|
|
3163
1053
|
if (confirm) {
|
|
3164
1054
|
await interactiveSpecificationAgent({
|
|
3165
1055
|
briefingPath: options.task ? void 0 : void 0
|
|
3166
|
-
// If task provided, maybe write a temp briefing? For now standard flow.
|
|
3167
1056
|
});
|
|
3168
1057
|
state = taskManager.analyzeSpecState();
|
|
3169
1058
|
if (state.status === "MISSING") {
|
|
@@ -3177,7 +1066,7 @@ var devCommand = new Command3("dev").description("Starts the Shark Developer Age
|
|
|
3177
1066
|
let keepOrchestrating = true;
|
|
3178
1067
|
let burnMode = false;
|
|
3179
1068
|
let contextHistory = "";
|
|
3180
|
-
tui.intro("\u{1F988} Shark Orchestrator
|
|
1069
|
+
tui.intro("\u{1F988} Shark Legacy Orchestrator");
|
|
3181
1070
|
while (keepOrchestrating) {
|
|
3182
1071
|
state = taskManager.analyzeSpecState();
|
|
3183
1072
|
if (state.status === "COMPLETED") {
|
|
@@ -3236,7 +1125,7 @@ ${contextHistory}`
|
|
|
3236
1125
|
}
|
|
3237
1126
|
taskManager.markTaskInProgress(currentTask.id);
|
|
3238
1127
|
tui.log.info(`\u26A1 Starting Micro-Context for Task: "${currentTask.description}"`);
|
|
3239
|
-
const result = await
|
|
1128
|
+
const result = await interactiveDeveloperAgent2({
|
|
3240
1129
|
taskId: currentTask.id,
|
|
3241
1130
|
taskInstruction: currentTask.description,
|
|
3242
1131
|
history: contextHistory,
|
|
@@ -3271,279 +1160,52 @@ ${contextHistory}`
|
|
|
3271
1160
|
}
|
|
3272
1161
|
}
|
|
3273
1162
|
}
|
|
3274
|
-
tui.outro("\u{1F988} Orchestration Finished.");
|
|
1163
|
+
tui.outro("\u{1F988} Legacy Orchestration Finished.");
|
|
3275
1164
|
});
|
|
3276
1165
|
|
|
3277
|
-
// src/commands/
|
|
1166
|
+
// src/commands/export-schema.ts
|
|
3278
1167
|
import { Command as Command4 } from "commander";
|
|
1168
|
+
var exportSchemaCommand = new Command4("export-schema").description("Outputs the agent response JSON Schema").action(() => {
|
|
1169
|
+
console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
|
|
1170
|
+
});
|
|
3279
1171
|
|
|
3280
|
-
// src/
|
|
3281
|
-
import
|
|
3282
|
-
import
|
|
3283
|
-
import
|
|
3284
|
-
import
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
const
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
3293
|
-
if (config.agentVersions?.qa) return config.agentVersions.qa;
|
|
3294
|
-
return process.env.STACKSPOT_QA_AGENT_VERSION;
|
|
3295
|
-
}
|
|
3296
|
-
var ChromeDevToolsClient = class {
|
|
3297
|
-
client = null;
|
|
3298
|
-
transport = null;
|
|
3299
|
-
async connect() {
|
|
3300
|
-
if (this.client) return;
|
|
3301
|
-
try {
|
|
3302
|
-
this.transport = new StdioClientTransport({
|
|
3303
|
-
command: "npx",
|
|
3304
|
-
args: ["-y", "chrome-devtools-mcp@latest"]
|
|
3305
|
-
});
|
|
3306
|
-
this.client = new Client({
|
|
3307
|
-
name: "shark-qa-client",
|
|
3308
|
-
version: "1.0.0"
|
|
3309
|
-
}, {
|
|
3310
|
-
capabilities: {}
|
|
3311
|
-
});
|
|
3312
|
-
await this.client.connect(this.transport);
|
|
3313
|
-
tui.log.success("\u{1F50C} Connected to Chrome DevTools MCP");
|
|
3314
|
-
} catch (e) {
|
|
3315
|
-
tui.log.error(`Failed to connect to Chrome MCP: ${e.message}`);
|
|
3316
|
-
throw e;
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
async callTool(name, args) {
|
|
3320
|
-
if (!this.client) await this.connect();
|
|
3321
|
-
try {
|
|
3322
|
-
const result = await this.client.callTool({
|
|
3323
|
-
name,
|
|
3324
|
-
arguments: args
|
|
3325
|
-
});
|
|
3326
|
-
return result;
|
|
3327
|
-
} catch (e) {
|
|
3328
|
-
return { isError: true, content: [{ type: "text", text: `MCP Error: ${e.message}` }] };
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
async close() {
|
|
3332
|
-
if (this.transport) {
|
|
3333
|
-
await this.transport.close();
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
};
|
|
3337
|
-
var mcpClient = new ChromeDevToolsClient();
|
|
3338
|
-
async function runQAAgent(options) {
|
|
3339
|
-
const agentId = getAgentId5();
|
|
3340
|
-
if (!agentId) {
|
|
3341
|
-
tui.log.error("\u274C STACKSPOT_QA_AGENT_ID not configured.");
|
|
3342
|
-
tui.log.info("Please run: set STACKSPOT_QA_AGENT_ID=<your-id>");
|
|
3343
|
-
return;
|
|
3344
|
-
}
|
|
3345
|
-
await mcpClient.connect();
|
|
3346
|
-
tui.intro("\u{1F988} Shark QA Agent");
|
|
3347
|
-
tui.log.info("Connecting to Chrome DevTools...");
|
|
3348
|
-
const realm = await getActiveRealm();
|
|
3349
|
-
const token = await tokenStorage.getToken(realm);
|
|
3350
|
-
if (!token) {
|
|
3351
|
-
tui.log.error('Authentication required. Run "shark login".');
|
|
3352
|
-
return;
|
|
3353
|
-
}
|
|
3354
|
-
let projectContext = "";
|
|
1172
|
+
// src/commands/super.ts
|
|
1173
|
+
import { Command as Command5 } from "commander";
|
|
1174
|
+
import { fileURLToPath } from "url";
|
|
1175
|
+
import path5 from "path";
|
|
1176
|
+
import os2 from "os";
|
|
1177
|
+
import fs5 from "fs/promises";
|
|
1178
|
+
var superCommandAction = async (options = {}) => {
|
|
1179
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
1180
|
+
const __dirname2 = path5.dirname(__filename2);
|
|
1181
|
+
const packageRoot = path5.resolve(__dirname2, "../../");
|
|
1182
|
+
const internalSkillsPath = path5.join(packageRoot, "skills");
|
|
1183
|
+
const targetPath = options.local ? path5.join(process.cwd(), ".agents", "skills") : path5.join(os2.homedir(), ".shark", "skills");
|
|
3355
1184
|
try {
|
|
3356
|
-
const contextPath = path10.join(process.cwd(), "_sharkrc", "project-context.md");
|
|
3357
|
-
if (fs9.existsSync(contextPath)) {
|
|
3358
|
-
projectContext = fs9.readFileSync(contextPath, "utf-8");
|
|
3359
|
-
tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
|
|
3360
|
-
}
|
|
3361
|
-
} catch (e) {
|
|
3362
|
-
}
|
|
3363
|
-
let userMessage = `CONTEXTO DO PROJETO:
|
|
3364
|
-
${projectContext}
|
|
3365
|
-
|
|
3366
|
-
`;
|
|
3367
|
-
if (options.initialUrl) {
|
|
3368
|
-
userMessage += `URL ALVO: ${options.initialUrl}
|
|
3369
|
-
`;
|
|
3370
|
-
}
|
|
3371
|
-
if (options.scenario) {
|
|
3372
|
-
userMessage += `CEN\xC1RIO DE TESTE: ${options.scenario}
|
|
3373
|
-
`;
|
|
3374
|
-
} else {
|
|
3375
|
-
userMessage += `Por favor, aguarde instru\xE7\xF5es do usu\xE1rio.`;
|
|
3376
|
-
}
|
|
3377
|
-
let keepRunning = true;
|
|
3378
|
-
while (keepRunning) {
|
|
3379
|
-
const spinner = tui.spinner();
|
|
3380
|
-
spinner.start("\u{1F916} Shark QA is thinking...");
|
|
3381
|
-
let agentResponseText = "";
|
|
3382
|
-
let agentResponse = null;
|
|
3383
1185
|
try {
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
`https://genai-inference-app.stackspot.com/v1/agent/${getAgentId5()}/chat`,
|
|
3387
|
-
{
|
|
3388
|
-
user_prompt: userMessage,
|
|
3389
|
-
streaming: true,
|
|
3390
|
-
use_conversation: true,
|
|
3391
|
-
conversation_id: existingConversationId,
|
|
3392
|
-
...getAgentVersion5() ? { agent_version_number: getAgentVersion5() } : {}
|
|
3393
|
-
},
|
|
3394
|
-
{
|
|
3395
|
-
"Authorization": `Bearer ${token}`
|
|
3396
|
-
},
|
|
3397
|
-
{
|
|
3398
|
-
onChunk: (chunk) => {
|
|
3399
|
-
agentResponseText += chunk;
|
|
3400
|
-
if (agentResponseText.length > 10 && agentResponseText.trim().startsWith("{")) {
|
|
3401
|
-
spinner.message("Receiving structured plan...");
|
|
3402
|
-
}
|
|
3403
|
-
},
|
|
3404
|
-
onComplete: (fullText, metadata) => {
|
|
3405
|
-
try {
|
|
3406
|
-
if (metadata?.conversation_id) {
|
|
3407
|
-
conversationManager.saveConversationId(AGENT_TYPE5, metadata.conversation_id);
|
|
3408
|
-
}
|
|
3409
|
-
agentResponse = parseAgentResponse(fullText || agentResponseText);
|
|
3410
|
-
} catch (e) {
|
|
3411
|
-
tui.log.error(`Parse Error: ${e.message}`);
|
|
3412
|
-
agentResponse = { actions: [], summary: "Error parsing response", message: fullText };
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
);
|
|
3417
|
-
spinner.stop("Response Received");
|
|
3418
|
-
} catch (error) {
|
|
3419
|
-
spinner.stop("Communication Error", 1);
|
|
3420
|
-
tui.log.error(error.message);
|
|
3421
|
-
keepRunning = false;
|
|
3422
|
-
break;
|
|
3423
|
-
}
|
|
3424
|
-
const currentResponse = agentResponse;
|
|
3425
|
-
if (!currentResponse) continue;
|
|
3426
|
-
if (currentResponse.summary) {
|
|
3427
|
-
tui.log.info(colors.primary(`\u{1F4CB} Plan: ${currentResponse.summary}`));
|
|
3428
|
-
}
|
|
3429
|
-
if (currentResponse.actions.length === 0) {
|
|
3430
|
-
const reply = await tui.text({
|
|
3431
|
-
message: "\u{1F916} Shark QA:",
|
|
3432
|
-
placeholder: "Your reply..."
|
|
3433
|
-
});
|
|
3434
|
-
if (tui.isCancel(reply)) {
|
|
3435
|
-
keepRunning = false;
|
|
3436
|
-
} else {
|
|
3437
|
-
userMessage = reply;
|
|
3438
|
-
}
|
|
3439
|
-
continue;
|
|
3440
|
-
}
|
|
3441
|
-
for (const action of currentResponse.actions) {
|
|
3442
|
-
tui.log.info(colors.dim(`Executing: ${action.type}`));
|
|
3443
|
-
let result = "";
|
|
3444
|
-
try {
|
|
3445
|
-
switch (action.type) {
|
|
3446
|
-
case "talk_with_user":
|
|
3447
|
-
const reply = await tui.text({
|
|
3448
|
-
message: `\u{1F916} ${action.content}`
|
|
3449
|
-
});
|
|
3450
|
-
if (tui.isCancel(reply)) keepRunning = false;
|
|
3451
|
-
else result = reply;
|
|
3452
|
-
break;
|
|
3453
|
-
case "use_mcp_tool":
|
|
3454
|
-
if (action.tool_name) {
|
|
3455
|
-
tui.log.info(`\u{1F527} MCP Tool: ${colors.bold(action.tool_name)}`);
|
|
3456
|
-
let args = {};
|
|
3457
|
-
try {
|
|
3458
|
-
args = typeof action.tool_args === "string" ? JSON.parse(action.tool_args) : action.tool_args || {};
|
|
3459
|
-
} catch (e) {
|
|
3460
|
-
tui.log.warning("Failed to parse tool_args, using empty object");
|
|
3461
|
-
}
|
|
3462
|
-
const mcpResult = await mcpClient.callTool(action.tool_name, args);
|
|
3463
|
-
result = JSON.stringify(mcpResult);
|
|
3464
|
-
tui.log.success(`Result: ${result.substring(0, 100)}...`);
|
|
3465
|
-
}
|
|
3466
|
-
break;
|
|
3467
|
-
case "create_file":
|
|
3468
|
-
if (action.path && action.content) {
|
|
3469
|
-
const fullPath = path10.resolve(process.cwd(), action.path);
|
|
3470
|
-
const BOM = "\uFEFF";
|
|
3471
|
-
const contentToWrite = action.content;
|
|
3472
|
-
const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
|
|
3473
|
-
fs9.writeFileSync(fullPath, finalContent, { encoding: "utf-8" });
|
|
3474
|
-
tui.log.success(`File created: ${action.path}`);
|
|
3475
|
-
result = "File created successfully.";
|
|
3476
|
-
}
|
|
3477
|
-
break;
|
|
3478
|
-
case "read_file":
|
|
3479
|
-
result = handleReadFile(action.path || "");
|
|
3480
|
-
break;
|
|
3481
|
-
case "run_command":
|
|
3482
|
-
const confirm = await tui.confirm({ message: `Run command: ${action.command}?` });
|
|
3483
|
-
if (confirm && action.command) {
|
|
3484
|
-
result = await handleRunCommand(action.command);
|
|
3485
|
-
} else {
|
|
3486
|
-
result = "Command execution denied by user.";
|
|
3487
|
-
}
|
|
3488
|
-
break;
|
|
3489
|
-
default:
|
|
3490
|
-
result = `Action ${action.type} not fully implemented in local client.`;
|
|
3491
|
-
}
|
|
3492
|
-
} catch (e) {
|
|
3493
|
-
result = `Error executing ${action.type}: ${e.message}`;
|
|
3494
|
-
tui.log.error(result);
|
|
3495
|
-
}
|
|
3496
|
-
userMessage = `[Action ${action.type} Result]:
|
|
3497
|
-
${result}
|
|
3498
|
-
|
|
3499
|
-
`;
|
|
1186
|
+
await fs5.rm(targetPath, { recursive: true, force: true });
|
|
1187
|
+
} catch {
|
|
3500
1188
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
}
|
|
3505
|
-
|
|
3506
|
-
// src/commands/qa.ts
|
|
3507
|
-
var qaCommand = new Command4("qa").description("Start the Shark QA Agent to test web applications").option("--url <url>", "Initial URL to test").option("--scenario <scenario>", "Scenario description or test case to execute").action(async (options) => {
|
|
3508
|
-
try {
|
|
3509
|
-
await runQAAgent({
|
|
3510
|
-
initialUrl: options.url,
|
|
3511
|
-
scenario: options.scenario
|
|
3512
|
-
});
|
|
1189
|
+
await fs5.mkdir(targetPath, { recursive: true });
|
|
1190
|
+
await fs5.cp(internalSkillsPath, targetPath, { recursive: true, force: true });
|
|
1191
|
+
console.log(`\u{1F680} Superpowers skills installed successfully to ${targetPath}`);
|
|
3513
1192
|
} catch (error) {
|
|
3514
|
-
console.error(
|
|
1193
|
+
console.error(`\u274C Failed to install superpowers skills: ${error.message}`);
|
|
3515
1194
|
process.exit(1);
|
|
3516
1195
|
}
|
|
3517
|
-
}
|
|
1196
|
+
};
|
|
1197
|
+
var superCommand = new Command5("super").description("Install Superpowers skills globally or locally").option("-l, --local", "Install skills locally in the current project under .agents/skills").action(superCommandAction);
|
|
3518
1198
|
|
|
3519
1199
|
// src/bin/shark.ts
|
|
3520
1200
|
crashHandler.init();
|
|
3521
|
-
var program = new
|
|
1201
|
+
var program = new Command6();
|
|
3522
1202
|
program.name("shark").description("Shark CLI: AI-Native Collaborative Development Tool").version("0.0.1");
|
|
3523
1203
|
program.addCommand(loginCommand);
|
|
3524
1204
|
program.addCommand(initCommand);
|
|
3525
|
-
program.addCommand(scanCommand);
|
|
3526
1205
|
program.addCommand(devCommand);
|
|
3527
|
-
program.addCommand(
|
|
3528
|
-
program.
|
|
3529
|
-
|
|
3530
|
-
await interactiveBusinessAnalyst();
|
|
3531
|
-
} catch (error) {
|
|
3532
|
-
console.error("Error:", error.message);
|
|
3533
|
-
process.exit(1);
|
|
3534
|
-
}
|
|
3535
|
-
});
|
|
3536
|
-
program.command("spec").description("Start Specification Agent interactive session").option("--id <agent_id>", "Override Agent ID").option("--briefing <path>", "Path to briefing file").action(async (options) => {
|
|
3537
|
-
try {
|
|
3538
|
-
await interactiveSpecificationAgent({
|
|
3539
|
-
agentId: options.id,
|
|
3540
|
-
briefingPath: options.briefing
|
|
3541
|
-
});
|
|
3542
|
-
} catch (error) {
|
|
3543
|
-
console.error("Error:", error.message);
|
|
3544
|
-
process.exit(1);
|
|
3545
|
-
}
|
|
3546
|
-
});
|
|
1206
|
+
program.addCommand(legacyCommand);
|
|
1207
|
+
program.addCommand(exportSchemaCommand);
|
|
1208
|
+
program.addCommand(superCommand);
|
|
3547
1209
|
program.command("config").description("Manage global configuration").action(configCommand.action);
|
|
3548
1210
|
process.on("unhandledRejection", (err) => {
|
|
3549
1211
|
console.error(colors.error("\u274C Unhandled Error:"), err);
|