shark-ai 0.4.16 → 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 +753 -3097
- 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,1655 +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 - STACK]
|
|
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 - ARCHITECTURE]
|
|
1801
|
-
[Brief description of architectural pattern]
|
|
1802
|
-
|
|
1803
|
-
## 3. Data Model
|
|
1804
|
-
[TO BE ANALYZED - DATA MODEL]
|
|
1805
|
-
[Schema/ERD definitions]
|
|
1806
|
-
|
|
1807
|
-
## 4. API / Interface Contracts
|
|
1808
|
-
[TO BE ANALYZED - API]
|
|
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
|
-
for (const action of lastResponse.actions) {
|
|
328
|
+
for (const action of actions) {
|
|
1915
329
|
if (action.type === "talk_with_user") {
|
|
1916
|
-
tui.log.info(colors.primary("\u{1F916}
|
|
330
|
+
tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
1917
331
|
console.log(action.content);
|
|
1918
|
-
waitingForUser = true;
|
|
332
|
+
if (!isTaskCompleted) waitingForUser = true;
|
|
1919
333
|
} else if (action.type === "list_files") {
|
|
1920
334
|
tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
|
|
1921
335
|
const result = handleListFiles(action.path || ".");
|
|
@@ -1931,868 +345,492 @@ ${result}
|
|
|
1931
345
|
|
|
1932
346
|
`;
|
|
1933
347
|
} else if (action.type === "search_file") {
|
|
1934
|
-
tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
|
|
1935
348
|
const result = handleSearchFile(action.path || "");
|
|
1936
349
|
executionResults += `[Action search_file(${action.path}) Result]:
|
|
1937
350
|
${result}
|
|
1938
351
|
|
|
1939
352
|
`;
|
|
1940
|
-
} else if (action.type === "
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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]:
|
|
1947
370
|
${result}
|
|
1948
371
|
|
|
1949
372
|
`;
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
let isTarget = actionPath === resolvedTargetPath;
|
|
1954
|
-
if (!isTarget && path6.basename(actionPath) === "tech-spec.md") {
|
|
1955
|
-
tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${path6.relative(process.cwd(), targetPath)}`);
|
|
1956
|
-
action.path = targetPath;
|
|
1957
|
-
actionPath = resolvedTargetPath;
|
|
1958
|
-
isTarget = true;
|
|
1959
|
-
}
|
|
1960
|
-
if (!isTarget && action.type === "create_file") {
|
|
1961
|
-
const confirm = await tui.confirm({ message: `Agent wants to create ${action.path}. Allow?` });
|
|
1962
|
-
if (!confirm) {
|
|
1963
|
-
executionResults += `[Action create_file]: User denied.
|
|
373
|
+
} else {
|
|
374
|
+
executionResults += `[Action run_command]: User blocked execution.
|
|
375
|
+
|
|
1964
376
|
`;
|
|
1965
|
-
continue;
|
|
1966
|
-
}
|
|
1967
377
|
}
|
|
1968
|
-
|
|
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) {
|
|
1969
393
|
if (action.type === "create_file") {
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
executionResults += `[Action create_file]: Success
|
|
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
|
+
|
|
1974
399
|
`;
|
|
1975
|
-
} else
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
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
|
+
|
|
1980
408
|
`;
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
409
|
+
}
|
|
410
|
+
const val = await validateTypeScript(path2.resolve(projectRoot, filePath));
|
|
411
|
+
if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
|
|
412
|
+
|
|
1984
413
|
`;
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
414
|
+
} else {
|
|
415
|
+
executionResults += `[Action ${action.type}]: User Denied.
|
|
416
|
+
|
|
1988
417
|
`;
|
|
1989
|
-
|
|
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}`;
|
|
1990
479
|
}
|
|
480
|
+
executionResults += `[Action ${action.type} Result]:
|
|
481
|
+
${result}
|
|
482
|
+
|
|
483
|
+
`;
|
|
484
|
+
tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
|
|
1991
485
|
} catch (e) {
|
|
1992
|
-
executionResults += `[Action ${action.type}]:
|
|
486
|
+
executionResults += `[Action ${action.type} Failed]: ${e.message}
|
|
487
|
+
|
|
1993
488
|
`;
|
|
489
|
+
tui.log.error(`\u274C AST Action Error: ${e.message}`);
|
|
1994
490
|
}
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
Resultados das \xFAltimas a\xE7\xF5es executadas antes da conclus\xE3o:
|
|
2001
|
-
${executionResults}` : "";
|
|
2002
|
-
if (currentPhase === 1) {
|
|
2003
|
-
currentPhase = 2;
|
|
2004
|
-
tui.log.success(`\u2705 Fase 1 Conclu\xEDda. Iniciando Fase 2 (Investiga\xE7\xE3o).`);
|
|
2005
|
-
nextPrompt = `[System Message]
|
|
2006
|
-
Voc\xEA completou a FASE 1 com sucesso.
|
|
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]:
|
|
494
|
+
${result}
|
|
2007
495
|
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
- 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.
|
|
2013
|
-
- 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}`;
|
|
2014
|
-
continue;
|
|
2015
|
-
} else if (currentPhase === 2) {
|
|
2016
|
-
currentPhase = 3;
|
|
2017
|
-
tui.log.success(`\u2705 Fase 2 Conclu\xEDda. Iniciando Fase 3 (Preenchimento).`);
|
|
2018
|
-
nextPrompt = `[System Message]
|
|
2019
|
-
Voc\xEA completou a FASE 2 com sucesso.
|
|
496
|
+
`;
|
|
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
|
|
2020
500
|
|
|
2021
|
-
|
|
2022
|
-
- Use \`modify_file\` no arquivo \`${targetPath}\` para substituir os placeholders pelo conte\xFAdo real levantado na fase de investiga\xE7\xE3o.
|
|
2023
|
-
- As se\xE7\xF5es 1-4 devem descrever o contexto da TAREFA, e n\xE3o o projeto como um todo.
|
|
2024
|
-
- Passos de Implementa\xE7\xE3o (Implementation Steps): APENAS checkboxes markdown: \`- [ ] [Verbo de A\xE7\xE3o] [O Que] em [Caminho Relativo]\`.
|
|
2025
|
-
- 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}`;
|
|
2026
|
-
continue;
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
if (lastResponse.message && lastResponse.message.includes("SPEC_UPDATED:")) {
|
|
2030
|
-
if (currentPhase < 3) {
|
|
2031
|
-
tui.log.warning(`O agente tentou finalizar prematuramente. For\xE7ando retorno para a fase atual...`);
|
|
2032
|
-
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.
|
|
501
|
+
` : `[Action modify_ast]: Failed
|
|
2033
502
|
|
|
2034
|
-
|
|
2035
|
-
continue;
|
|
2036
|
-
}
|
|
2037
|
-
const content = fs5.existsSync(targetPath) ? fs5.readFileSync(targetPath, "utf-8") : "";
|
|
2038
|
-
if (content.includes("[TO BE")) {
|
|
2039
|
-
const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
|
|
2040
|
-
let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "algumas se\xE7\xF5es";
|
|
2041
|
-
tui.log.warning(`O agente tentou concluir prematuramente, mas h\xE1 placeholders pendentes. For\xE7ando retorno...`);
|
|
2042
|
-
nextPrompt = `[System Error]: A valida\xE7\xE3o falhou e o bloqueio autom\xE1tico foi acionado.
|
|
2043
|
-
Voc\xEA tentou concluir a tarefa, mas o arquivo AINDA possui placeholders '[TO BE ANALYZED...]' ou '[TO BE FILLED]'.
|
|
2044
|
-
As seguintes se\xE7\xF5es ainda cont\xEAm estes placeholders: ${missing}.
|
|
2045
|
-
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.`;
|
|
2046
|
-
continue;
|
|
2047
|
-
} else {
|
|
2048
|
-
const updateSummary = lastResponse.message.split("SPEC_UPDATED:")[1].trim();
|
|
2049
|
-
tui.log.success(`\u2705 Spec Finalized: ${updateSummary}`);
|
|
2050
|
-
return;
|
|
503
|
+
`;
|
|
2051
504
|
}
|
|
2052
505
|
}
|
|
2053
|
-
if (
|
|
2054
|
-
|
|
2055
|
-
if (tui.isCancel(userReply)) {
|
|
2056
|
-
keepGoing = false;
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
nextPrompt = `${executionResults}
|
|
2060
|
-
|
|
2061
|
-
User Reply: ${userReply}`;
|
|
2062
|
-
} else if (executionResults) {
|
|
2063
|
-
const content = fs5.existsSync(targetPath) ? fs5.readFileSync(targetPath, "utf-8") : "";
|
|
2064
|
-
let systemMsg = "Execu\xE7\xE3o da ferramenta conclu\xEDda.";
|
|
2065
|
-
if (specUpdated) {
|
|
2066
|
-
if (content.includes("[TO BE")) {
|
|
2067
|
-
const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
|
|
2068
|
-
let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "v\xE1rias se\xE7\xF5es";
|
|
2069
|
-
systemMsg += `
|
|
2070
|
-
[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}.
|
|
2071
|
-
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.`;
|
|
2072
|
-
} else {
|
|
2073
|
-
systemMsg += "\n[System]: O arquivo parece completo! Se estiver satisfeito e possuir TODAS as implementa\xE7\xF5es descritas, retorne 'SPEC_UPDATED: Complete'.";
|
|
2074
|
-
}
|
|
2075
|
-
} else {
|
|
2076
|
-
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`.";
|
|
2077
|
-
}
|
|
2078
|
-
nextPrompt = `${executionResults}
|
|
2079
|
-
|
|
2080
|
-
${systemMsg}`;
|
|
2081
|
-
} else {
|
|
2082
|
-
if (lastResponse.message) {
|
|
2083
|
-
tui.log.info(colors.primary("\u{1F916} Architect (Message only):"));
|
|
2084
|
-
console.log(lastResponse.message);
|
|
506
|
+
if (executionResults) {
|
|
507
|
+
if (waitingForUser) {
|
|
2085
508
|
const userReply = await tui.text({ message: "Your answer:" });
|
|
2086
509
|
if (tui.isCancel(userReply)) {
|
|
2087
510
|
keepGoing = false;
|
|
2088
511
|
break;
|
|
2089
512
|
}
|
|
2090
|
-
nextPrompt =
|
|
513
|
+
nextPrompt = `${executionResults}
|
|
514
|
+
User Reply: ${userReply}`;
|
|
2091
515
|
} else {
|
|
2092
|
-
|
|
516
|
+
nextPrompt = `${executionResults}
|
|
517
|
+
[System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
|
|
518
|
+
tui.log.info(colors.dim("Processing results..."));
|
|
2093
519
|
}
|
|
520
|
+
} else if (!keepGoing) {
|
|
521
|
+
} else if (waitingForUser) {
|
|
522
|
+
} else {
|
|
523
|
+
if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
|
|
2094
524
|
}
|
|
2095
525
|
} else {
|
|
2096
|
-
tui.log.warning("No
|
|
2097
|
-
keepGoing = false;
|
|
526
|
+
tui.log.warning("No response received from agent.");
|
|
2098
527
|
}
|
|
2099
|
-
} catch (
|
|
2100
|
-
|
|
2101
|
-
tui.log.error(error.message);
|
|
528
|
+
} catch (e) {
|
|
529
|
+
tui.log.error(e.message);
|
|
2102
530
|
keepGoing = false;
|
|
531
|
+
return { success: false, summary: `Error: ${e.message}` };
|
|
2103
532
|
}
|
|
2104
533
|
}
|
|
534
|
+
tui.log.success("\u2705 Task Scope Completed");
|
|
535
|
+
return { success: true, summary: finalSummary || "Task completed without summary." };
|
|
2105
536
|
}
|
|
2106
|
-
async function
|
|
2107
|
-
const
|
|
2108
|
-
const
|
|
2109
|
-
const
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
stackspot_knowledge: false,
|
|
2114
|
-
return_ks_in_response: true,
|
|
2115
|
-
use_conversation: true,
|
|
2116
|
-
conversation_id: conversationId
|
|
2117
|
-
};
|
|
2118
|
-
const agentVersion = getAgentVersion2();
|
|
2119
|
-
if (agentVersion) {
|
|
2120
|
-
payload.agent_version_number = agentVersion;
|
|
2121
|
-
}
|
|
2122
|
-
const effectiveAgentId = getAgentId2(agentId);
|
|
2123
|
-
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${effectiveAgentId}/chat`;
|
|
2124
|
-
let fullMsg = "";
|
|
2125
|
-
let raw = {};
|
|
2126
|
-
FileLogger.log("AGENT", "Calling Agent API", { agentId: effectiveAgentId, conversationId });
|
|
2127
|
-
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
2128
|
-
onChunk: (c) => {
|
|
2129
|
-
fullMsg += c;
|
|
2130
|
-
onChunk(c);
|
|
2131
|
-
},
|
|
2132
|
-
onComplete: (msg, metadata) => {
|
|
2133
|
-
const returnedId = metadata?.conversation_id;
|
|
2134
|
-
raw = { message: msg || fullMsg, conversation_id: returnedId || conversationId };
|
|
2135
|
-
},
|
|
2136
|
-
onError: (e) => {
|
|
2137
|
-
throw e;
|
|
2138
|
-
}
|
|
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
|
|
2139
544
|
});
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
|
|
545
|
+
if (parsedResponse.conversation_id) {
|
|
546
|
+
await conversationManager.saveConversationId(conversationKey, parsedResponse.conversation_id);
|
|
2143
547
|
}
|
|
2144
|
-
return
|
|
548
|
+
return parsedResponse;
|
|
2145
549
|
}
|
|
2146
550
|
|
|
2147
|
-
// src/
|
|
2148
|
-
import
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
if (
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
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";
|
|
590
|
+
}
|
|
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);
|
|
2185
609
|
}
|
|
2186
|
-
} else {
|
|
2187
|
-
fs6.mkdirSync(outputDir, { recursive: true });
|
|
2188
|
-
outputFile = path7.join(outputDir, "project-context.md");
|
|
2189
610
|
}
|
|
611
|
+
let nextTask = tasks.find((t) => t.status === "IN_PROGRESS");
|
|
612
|
+
if (!nextTask) {
|
|
613
|
+
nextTask = tasks.find((t) => t.status === "PENDING");
|
|
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
|
+
};
|
|
2190
622
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
[
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
[
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
**LANGUAGE INSTRUCTION**:
|
|
2241
|
-
You MUST write the content in **${language}**.
|
|
2242
|
-
|
|
2243
|
-
**CRITICAL STRATEGY - INCREMENTAL UPDATES**:
|
|
2244
|
-
DO NOT try to rewrite the entire file at once! Instead:
|
|
2245
|
-
|
|
2246
|
-
1. **Explore** the project using \`list_files\`, \`read_file\`, \`search_file\`
|
|
2247
|
-
2. **Update** specific sections incrementally using \`modify_file\`
|
|
2248
|
-
3. **Benefit**: Build a MUCH MORE DETAILED document without context size limitations
|
|
2249
|
-
|
|
2250
|
-
**WORKFLOW - FILL EACH SECTION:**
|
|
2251
|
-
|
|
2252
|
-
**Step 1 - Analyze Tech Stack:**
|
|
2253
|
-
- \`list_files\` root directory to see project structure
|
|
2254
|
-
- \`read_file\` package.json (or pom.xml, go.mod, requirements.txt, etc.)
|
|
2255
|
-
- \`modify_file\` to replace:
|
|
2256
|
-
- \`target_content\`: "## Tech Stack\\n[TO BE ANALYZED]"
|
|
2257
|
-
- \`content\`: Detailed tech stack with versions, dependencies, and their purposes
|
|
2258
|
-
|
|
2259
|
-
**Step 2 - Analyze Directory Structure:**
|
|
2260
|
-
- \`list_files\` on ALL key directories (src, tests, config, docs, etc.)
|
|
2261
|
-
- Map the complete directory tree
|
|
2262
|
-
- \`modify_file\` to replace:
|
|
2263
|
-
- \`target_content\`: "## Directory Structure\\n[TO BE ANALYZED]"
|
|
2264
|
-
- \`content\`: Visual directory tree with purpose of each folder
|
|
2265
|
-
|
|
2266
|
-
**Step 3 - Analyze Architecture:**
|
|
2267
|
-
- \`read_file\` entry points (main.ts, index.js, app.py, etc.)
|
|
2268
|
-
- \`read_file\` 5-10 source files to understand code patterns and organization
|
|
2269
|
-
- Identify architectural pattern (Clean Arch, MVC, Microservices, etc.)
|
|
2270
|
-
- \`modify_file\` to replace:
|
|
2271
|
-
- \`target_content\`: "## Architecture\\n[TO BE ANALYZED]"
|
|
2272
|
-
- \`content\`: Comprehensive architectural description with patterns and module organization
|
|
2273
|
-
|
|
2274
|
-
**Step 4 - Document Components:**
|
|
2275
|
-
- Identify ALL major modules/components by reading source files
|
|
2276
|
-
- For each component: location, purpose, key files
|
|
2277
|
-
- \`modify_file\` to replace:
|
|
2278
|
-
- \`target_content\`: "## Key Components\\n[TO BE ANALYZED]"
|
|
2279
|
-
- \`content\`: Detailed list of components with their purposes
|
|
2280
|
-
|
|
2281
|
-
**Step 5 - Document APIs (if applicable):**
|
|
2282
|
-
- Search for route definitions, controllers, API endpoints
|
|
2283
|
-
- Document base URL, main endpoints, request/response patterns
|
|
2284
|
-
- \`modify_file\` to replace:
|
|
2285
|
-
- \`target_content\`: "## API / Interfaces\\n[TO BE ANALYZED]"
|
|
2286
|
-
- \`content\`: API documentation (or "Not applicable" if no API)
|
|
2287
|
-
|
|
2288
|
-
**Step 6 - Document Data Layer (if applicable):**
|
|
2289
|
-
- Search for database configs, ORM setup, model definitions
|
|
2290
|
-
- Document database type, ORM tool, key entities/tables
|
|
2291
|
-
- \`modify_file\` to replace:
|
|
2292
|
-
- \`target_content\`: "## Data Layer\\n[TO BE ANALYZED]"
|
|
2293
|
-
- \`content\`: Data layer details (or "Not applicable" if no database)
|
|
2294
|
-
|
|
2295
|
-
**Step 7 - Configuration & Environment:**
|
|
2296
|
-
- Read config files (.env.example, config/, etc.)
|
|
2297
|
-
- Identify environment variables and their purposes
|
|
2298
|
-
- \`modify_file\` to replace:
|
|
2299
|
-
- \`target_content\`: "## Configuration & Environment\\n[TO BE ANALYZED]"
|
|
2300
|
-
- \`content\`: List of env vars and config files
|
|
2301
|
-
|
|
2302
|
-
**Step 8 - Build & Development:**
|
|
2303
|
-
- Read package.json scripts, Makefile, build configs
|
|
2304
|
-
- Document dev, build, test, lint commands
|
|
2305
|
-
- \`modify_file\` to replace:
|
|
2306
|
-
- \`target_content\`: "## Build & Development\\n[TO BE ANALYZED]"
|
|
2307
|
-
- \`content\`: Commands for development workflow
|
|
2308
|
-
|
|
2309
|
-
**Step 9 - Patterns & Conventions:**
|
|
2310
|
-
- Based on files read, document naming conventions, code organization
|
|
2311
|
-
- Note error handling, logging strategies
|
|
2312
|
-
- \`modify_file\` to replace:
|
|
2313
|
-
- \`target_content\`: "## Key Patterns & Conventions\\n[TO BE ANALYZED]"
|
|
2314
|
-
- \`content\`: Observed patterns and conventions
|
|
2315
|
-
|
|
2316
|
-
**Step 10 - Overview (LAST):**
|
|
2317
|
-
- Synthesize all findings into a comprehensive overview
|
|
2318
|
-
- \`modify_file\` to replace:
|
|
2319
|
-
- \`target_content\`: "## Overview\\n[TO BE ANALYZED]"
|
|
2320
|
-
- \`content\`: Detailed project description with purpose and main functionality
|
|
2321
|
-
|
|
2322
|
-
**HOW TO USE modify_file:**
|
|
2323
|
-
\`\`\`json
|
|
2324
|
-
{
|
|
2325
|
-
"actions": [{
|
|
2326
|
-
"type": "modify_file",
|
|
2327
|
-
"path": "${configFileRelative}",
|
|
2328
|
-
"target_content": "## Tech Stack\\n[TO BE ANALYZED]",
|
|
2329
|
-
"content": "## Tech Stack\\n- **Language**: TypeScript 5.3\\n- **Runtime**: Node.js 20.x\\n..."
|
|
2330
|
-
}]
|
|
2331
|
-
}
|
|
2332
|
-
\`\`\`
|
|
2333
|
-
|
|
2334
|
-
\`\`\`markdown
|
|
2335
|
-
# Project Context
|
|
2336
|
-
|
|
2337
|
-
## Overview
|
|
2338
|
-
[TO BE ANALYZED]
|
|
2339
|
-
|
|
2340
|
-
## Tech Stack
|
|
2341
|
-
[TO BE ANALYZED]
|
|
2342
|
-
|
|
2343
|
-
## Architecture
|
|
2344
|
-
[TO BE ANALYZED]
|
|
2345
|
-
|
|
2346
|
-
## Directory Structure
|
|
2347
|
-
[TO BE ANALYZED]
|
|
2348
|
-
|
|
2349
|
-
## Key Components
|
|
2350
|
-
[TO BE ANALYZED]
|
|
2351
|
-
|
|
2352
|
-
## API / Interfaces
|
|
2353
|
-
[TO BE ANALYZED]
|
|
2354
|
-
|
|
2355
|
-
## Data Layer
|
|
2356
|
-
[TO BE ANALYZED]
|
|
2357
|
-
|
|
2358
|
-
## Configuration & Environment
|
|
2359
|
-
[TO BE ANALYZED]
|
|
2360
|
-
|
|
2361
|
-
## Build & Development
|
|
2362
|
-
[TO BE ANALYZED]
|
|
2363
|
-
|
|
2364
|
-
## Key Patterns & Conventions
|
|
2365
|
-
[TO BE ANALYZED]
|
|
2366
|
-
\`\`\`
|
|
2367
|
-
|
|
2368
|
-
**Step 2 - Explore and Update Incrementally:**
|
|
2369
|
-
After creating the template, perform these analyses and UPDATE each section:
|
|
2370
|
-
|
|
2371
|
-
**2.1 - Analyze Tech Stack:**
|
|
2372
|
-
- \`list_files\` root directory
|
|
2373
|
-
- \`read_file\` package.json (or equivalent manifest)
|
|
2374
|
-
- \`modify_file\` to replace "## Tech Stack\\n[TO BE ANALYZED]" with detailed findings
|
|
2375
|
-
|
|
2376
|
-
**2.2 - Analyze Directory Structure:**
|
|
2377
|
-
- \`list_files\` on key directories (src, tests, config, etc.)
|
|
2378
|
-
- \`modify_file\` to replace "## Directory Structure\\n[TO BE ANALYZED]" with complete structure
|
|
2379
|
-
|
|
2380
|
-
**2.3 - Analyze Architecture:**
|
|
2381
|
-
- \`read_file\` entry points (main.ts, index.js, etc.)
|
|
2382
|
-
- \`read_file\` 5-10 source files to understand patterns
|
|
2383
|
-
- \`modify_file\` to replace "## Architecture\\n[TO BE ANALYZED]" with architectural insights
|
|
2384
|
-
|
|
2385
|
-
**2.4 - Document Components:**
|
|
2386
|
-
- Identify major modules/components
|
|
2387
|
-
- \`modify_file\` to replace "## Key Components\\n[TO BE ANALYZED]" with component details
|
|
2388
|
-
|
|
2389
|
-
**2.5 - Document APIs (if applicable):**
|
|
2390
|
-
- Search for route definitions, controllers, API endpoints
|
|
2391
|
-
- \`modify_file\` to replace "## API / Interfaces\\n[TO BE ANALYZED]"
|
|
2392
|
-
|
|
2393
|
-
**2.6 - Document Data Layer (if applicable):**
|
|
2394
|
-
- Search for database configs, ORM, models
|
|
2395
|
-
- \`modify_file\` to replace "## Data Layer\\n[TO BE ANALYZED]"
|
|
2396
|
-
|
|
2397
|
-
**2.7 - Final Touches:**
|
|
2398
|
-
- Update remaining sections (Config, Build, Patterns)
|
|
2399
|
-
- Ensure Overview is comprehensive
|
|
2400
|
-
|
|
2401
|
-
**HOW TO USE modify_file:**
|
|
2402
|
-
\`\`\`json
|
|
2403
|
-
{
|
|
2404
|
-
"actions": [{
|
|
2405
|
-
"type": "modify_file",
|
|
2406
|
-
"path": "${configFileRelative}",
|
|
2407
|
-
"target_content": "## Tech Stack\\n[TO BE ANALYZED]",
|
|
2408
|
-
"content": "## Tech Stack\\n- **Language**: TypeScript 5.3\\n- **Runtime**: Node.js 20.x\\n- **Framework**: Express 4.18\\n..."
|
|
2409
|
-
}]
|
|
2410
|
-
}
|
|
2411
|
-
\`\`\`
|
|
2412
|
-
|
|
2413
|
-
**SECTION TEMPLATES (For your updates):**
|
|
2414
|
-
|
|
2415
|
-
**Tech Stack:**
|
|
2416
|
-
\`\`\`markdown
|
|
2417
|
-
## Tech Stack
|
|
2418
|
-
- **Language**: [name + version]
|
|
2419
|
-
- **Runtime**: [name + version]
|
|
2420
|
-
- **Framework**: [name + version]
|
|
2421
|
-
- **Build Tool**: [name]
|
|
2422
|
-
- **Testing**: [framework]
|
|
2423
|
-
- **Key Dependencies**:
|
|
2424
|
-
- [dep-name]: [purpose]
|
|
2425
|
-
- [dep-name]: [purpose]
|
|
2426
|
-
\`\`\`
|
|
2427
|
-
|
|
2428
|
-
**Architecture:**
|
|
2429
|
-
\`\`\`markdown
|
|
2430
|
-
## Architecture
|
|
2431
|
-
[Comprehensive description]
|
|
2432
|
-
- **Pattern**: [Clean Arch, MVC, etc.]
|
|
2433
|
-
- **Module Organization**: [how modules are structured]
|
|
2434
|
-
- **Layer Separation**: [controllers, services, repos]
|
|
2435
|
-
- **Configuration**: [how config is managed]
|
|
2436
|
-
\`\`\`
|
|
2437
|
-
|
|
2438
|
-
**Directory Structure:**
|
|
2439
|
-
\`\`\`markdown
|
|
2440
|
-
## Directory Structure
|
|
2441
|
-
\\\`\\\`\\\`
|
|
2442
|
-
/src
|
|
2443
|
-
/core - [Purpose]
|
|
2444
|
-
/commands - [Purpose]
|
|
2445
|
-
/ui - [Purpose]
|
|
2446
|
-
/tests - [Purpose]
|
|
2447
|
-
/docs - [Purpose]
|
|
2448
|
-
\\\`\\\`\\\`
|
|
2449
|
-
\`\`\`
|
|
2450
|
-
|
|
2451
|
-
**Key Components:**
|
|
2452
|
-
\`\`\`markdown
|
|
2453
|
-
## Key Components
|
|
2454
|
-
|
|
2455
|
-
### [Component Name 1]
|
|
2456
|
-
- **Location**: \\\`path/to/component\\\`
|
|
2457
|
-
- **Purpose**: [What it does]
|
|
2458
|
-
- **Key Files**: [Important files]
|
|
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
|
+
};
|
|
2459
671
|
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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}}
|
|
2465
685
|
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
-
|
|
2469
|
-
-
|
|
2470
|
-
-
|
|
2471
|
-
-
|
|
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]
|
|
2472
692
|
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
- Do NOT wait to write everything at the end
|
|
2477
|
-
- Use \`modify_file\` to replace "[TO BE ANALYZED]" sections
|
|
2478
|
-
- Be DETAILED and COMPREHENSIVE
|
|
2479
|
-
- Take your time - you have up to 30 steps
|
|
2480
|
-
- Verify facts with \`read_file\` or \`search_file\` before documenting
|
|
2481
|
-
`.trim();
|
|
2482
|
-
await runScanLoop(superPrompt, outputFile);
|
|
2483
|
-
}
|
|
2484
|
-
async function runScanLoop(initialPrompt, targetPath) {
|
|
2485
|
-
let nextPrompt = initialPrompt;
|
|
2486
|
-
let keepGoing = true;
|
|
2487
|
-
let stepCount = 0;
|
|
2488
|
-
const MAX_STEPS = 60;
|
|
2489
|
-
while (keepGoing && stepCount < MAX_STEPS) {
|
|
2490
|
-
stepCount++;
|
|
2491
|
-
const spinner = tui.spinner();
|
|
2492
|
-
const msg = t("commands.scan.analyzing").replace("{step}", stepCount.toString());
|
|
2493
|
-
spinner.start(msg);
|
|
2494
|
-
let responseText = "";
|
|
2495
|
-
let lastResponse = null;
|
|
2496
|
-
try {
|
|
2497
|
-
lastResponse = await callScanAgentApi(nextPrompt, (chunk) => {
|
|
2498
|
-
responseText += chunk;
|
|
2499
|
-
});
|
|
2500
|
-
spinner.stop(t("commands.scan.stepComplete"));
|
|
2501
|
-
if (lastResponse && lastResponse.actions && lastResponse.actions.length > 0) {
|
|
2502
|
-
let executionResults = "";
|
|
2503
|
-
let fileCreated = false;
|
|
2504
|
-
for (const action of lastResponse.actions) {
|
|
2505
|
-
if (action.type === "list_files") {
|
|
2506
|
-
tui.log.info(t("commands.scan.scanningDir").replace("{0}", colors.bold(action.path || ".")));
|
|
2507
|
-
const result = handleListFiles(action.path || ".");
|
|
2508
|
-
executionResults += `[Action list_files(${action.path}) Result]:
|
|
2509
|
-
${result}
|
|
693
|
+
## 2. Architecture Overview
|
|
694
|
+
[TO BE ANALYZED - ARCHITECTURE]
|
|
695
|
+
[Brief description of architectural pattern]
|
|
2510
696
|
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
const result = handleReadFile(action.path || "");
|
|
2515
|
-
executionResults += `[Action read_file(${action.path}) Result]:
|
|
2516
|
-
${result}
|
|
697
|
+
## 3. Data Model
|
|
698
|
+
[TO BE ANALYZED - DATA MODEL]
|
|
699
|
+
[Schema/ERD definitions]
|
|
2517
700
|
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
const result = handleSearchFile(action.path || "");
|
|
2522
|
-
executionResults += `[Action search_file(${action.path}) Result]:
|
|
2523
|
-
${result}
|
|
701
|
+
## 4. API / Interface Contracts
|
|
702
|
+
[TO BE ANALYZED - API]
|
|
703
|
+
[Main endpoints or CLI commands]
|
|
2524
704
|
|
|
705
|
+
## 5. Implementation Steps
|
|
706
|
+
[TO BE FILLED - MUST BE CHECKBOXES]
|
|
2525
707
|
`;
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
action.path = targetPath;
|
|
2534
|
-
}
|
|
2535
|
-
if (isTarget) {
|
|
2536
|
-
const finalPath = targetPath;
|
|
2537
|
-
const BOM = "\uFEFF";
|
|
2538
|
-
if (action.type === "create_file") {
|
|
2539
|
-
const contentToWrite = action.content || "";
|
|
2540
|
-
const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
|
|
2541
|
-
fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
|
|
2542
|
-
tui.log.success(t("commands.scan.generated").replace("{0}", finalPath));
|
|
2543
|
-
fileCreated = true;
|
|
2544
|
-
} else {
|
|
2545
|
-
if (fs6.existsSync(finalPath)) {
|
|
2546
|
-
const currentContent = fs6.readFileSync(finalPath, "utf-8");
|
|
2547
|
-
if (action.target_content && currentContent.includes(action.target_content)) {
|
|
2548
|
-
const newContent = currentContent.replace(action.target_content, action.content || "");
|
|
2549
|
-
const finalContent = newContent.startsWith(BOM) ? newContent : BOM + newContent;
|
|
2550
|
-
fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
|
|
2551
|
-
tui.log.success(t("commands.scan.updated").replace("{0}", finalPath));
|
|
2552
|
-
fileCreated = true;
|
|
2553
|
-
} else {
|
|
2554
|
-
tui.log.warning(t("commands.scan.error") + ": " + t("commands.scan.contentNotFound"));
|
|
2555
|
-
executionResults += `[Action ${action.type}]: Failed. Target content not found in file.
|
|
2556
|
-
`;
|
|
2557
|
-
fileCreated = false;
|
|
2558
|
-
}
|
|
2559
|
-
} else {
|
|
2560
|
-
tui.log.warning(t("commands.scan.error") + ": " + t("commands.scan.notFound"));
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
executionResults += `[Action ${action.type}]: Success. Task Completed.
|
|
2564
|
-
`;
|
|
2565
|
-
} else {
|
|
2566
|
-
tui.log.warning(t("commands.scan.error"));
|
|
2567
|
-
executionResults += `[Action ${action.type}]: ${t("commands.scan.skipped")}
|
|
2568
|
-
`;
|
|
2569
|
-
}
|
|
2570
|
-
} else if (action.type === "talk_with_user") {
|
|
2571
|
-
tui.log.info(colors.primary(t("commands.scan.agentAsks")));
|
|
2572
|
-
console.log(action.content);
|
|
2573
|
-
const reply = await tui.text({ message: t("commands.scan.agentInput"), placeholder: t("commands.scan.replyPlaceholder") });
|
|
2574
|
-
executionResults += `[User Reply]: ${reply}
|
|
2575
|
-
`;
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
if (fileCreated) {
|
|
2579
|
-
const currentContent = fs6.readFileSync(targetPath, "utf-8");
|
|
2580
|
-
const pendingSections = [];
|
|
2581
|
-
const lines = currentContent.split("\n");
|
|
2582
|
-
let currentSection = "";
|
|
2583
|
-
for (const line of lines) {
|
|
2584
|
-
if (line.startsWith("## ")) {
|
|
2585
|
-
currentSection = line.substring(3).trim();
|
|
2586
|
-
}
|
|
2587
|
-
if (line.includes("[TO BE ANALYZED]")) {
|
|
2588
|
-
if (currentSection && !pendingSections.includes(currentSection)) {
|
|
2589
|
-
pendingSections.push(currentSection);
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
const pendingMsg = pendingSections.length > 0 ? `
|
|
2594
|
-
|
|
2595
|
-
[System Helper]: ${t("commands.scan.pendingSections").replace("{0}", pendingSections.join(", "))}` : `
|
|
2596
|
-
|
|
2597
|
-
[System Helper]: ${t("commands.scan.allPopulated")}`;
|
|
2598
|
-
nextPrompt = `${executionResults}
|
|
2599
|
-
|
|
2600
|
-
[System]: File updated successfully.${pendingMsg} Please continue with the next step of the analysis and focus on the pending sections.`;
|
|
2601
|
-
FileLogger.log("SCAN", "Section updated, continuing loop", { step: stepCount, pending: pendingSections.length });
|
|
2602
|
-
} else {
|
|
2603
|
-
nextPrompt = executionResults;
|
|
2604
|
-
FileLogger.log("SCAN", "Auto-replying with results", { length: executionResults.length });
|
|
2605
|
-
}
|
|
2606
|
-
} else {
|
|
2607
|
-
tui.log.success(t("commands.scan.completed"));
|
|
2608
|
-
keepGoing = false;
|
|
2609
|
-
}
|
|
2610
|
-
} catch (error) {
|
|
2611
|
-
spinner.stop(t("common.error"));
|
|
2612
|
-
tui.log.error(error.message);
|
|
2613
|
-
keepGoing = false;
|
|
2614
|
-
}
|
|
708
|
+
const projectName = path4.basename(projectRoot);
|
|
709
|
+
initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
|
|
710
|
+
const BOM = "\uFEFF";
|
|
711
|
+
fs4.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
|
|
712
|
+
tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
713
|
+
} else {
|
|
714
|
+
tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
2615
715
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
const payload = {
|
|
2622
|
-
user_prompt: prompt,
|
|
2623
|
-
streaming: true,
|
|
2624
|
-
stackspot_knowledge: false,
|
|
2625
|
-
return_ks_in_response: true,
|
|
2626
|
-
use_conversation: true,
|
|
2627
|
-
conversation_id: conversationId
|
|
2628
|
-
};
|
|
2629
|
-
const agentVersion = getAgentVersion3();
|
|
2630
|
-
if (agentVersion) {
|
|
2631
|
-
payload.agent_version_number = agentVersion;
|
|
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.`);
|
|
2632
721
|
}
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
onComplete: (msg, metadata) => {
|
|
2643
|
-
const returnedId = metadata?.conversation_id;
|
|
2644
|
-
raw = {
|
|
2645
|
-
message: msg || fullMsg,
|
|
2646
|
-
conversation_id: returnedId || conversationId
|
|
2647
|
-
};
|
|
2648
|
-
},
|
|
2649
|
-
onError: (e) => {
|
|
2650
|
-
throw e;
|
|
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.`);
|
|
2651
731
|
}
|
|
2652
|
-
});
|
|
2653
|
-
const parsed = parseAgentResponse(raw);
|
|
2654
|
-
if (parsed.conversation_id) {
|
|
2655
|
-
await conversationManager.saveConversationId(AGENT_TYPE3, parsed.conversation_id);
|
|
2656
732
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
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\`.
|
|
2659
736
|
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
try {
|
|
2663
|
-
await interactiveScanAgent(options);
|
|
2664
|
-
} catch (error) {
|
|
2665
|
-
console.error("Error during scan:", error.message);
|
|
2666
|
-
process.exit(1);
|
|
2667
|
-
}
|
|
2668
|
-
});
|
|
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).
|
|
2669
739
|
|
|
2670
|
-
|
|
2671
|
-
|
|
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.
|
|
2672
744
|
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
function getAgentId4(overrideId) {
|
|
2689
|
-
if (overrideId) return overrideId;
|
|
2690
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
2691
|
-
if (config.agents?.dev) return config.agents.dev;
|
|
2692
|
-
if (process.env.STACKSPOT_DEV_AGENT_ID) return process.env.STACKSPOT_DEV_AGENT_ID;
|
|
2693
|
-
return "01KEQCGJ65YENRA4QBXVN1YFFX";
|
|
2694
|
-
}
|
|
2695
|
-
function getAgentVersion4(overrideVersion) {
|
|
2696
|
-
if (overrideVersion) return overrideVersion;
|
|
2697
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
2698
|
-
if (config.agentVersions?.dev) return config.agentVersions.dev;
|
|
2699
|
-
if (process.env.STACKSPOT_DEV_AGENT_VERSION) return process.env.STACKSPOT_DEV_AGENT_VERSION;
|
|
2700
|
-
return void 0;
|
|
2701
|
-
}
|
|
2702
|
-
async function interactiveDeveloperAgent(options = {}) {
|
|
2703
|
-
FileLogger.init();
|
|
2704
|
-
const agentId = getAgentId4();
|
|
2705
|
-
if (agentId === "PENDING_CONFIGURATION") {
|
|
2706
|
-
tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
|
|
2707
|
-
return { success: false, summary: "Missing configuration." };
|
|
745
|
+
IMPORTANTE: Toda a sua comunica\xE7\xE3o DEVE ser em Portugu\xEAs.
|
|
746
|
+
`;
|
|
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.
|
|
751
|
+
|
|
752
|
+
--- BRIEFING ---
|
|
753
|
+
${briefingContent}
|
|
754
|
+
----------------
|
|
755
|
+
`;
|
|
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.
|
|
759
|
+
`;
|
|
2708
760
|
}
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
contextContent = fs7.readFileSync(specificContextPath, "utf-8");
|
|
2716
|
-
} catch (e) {
|
|
2717
|
-
tui.log.warning(`Failed to read context file: ${e}`);
|
|
2718
|
-
}
|
|
761
|
+
if (options.initialContext) {
|
|
762
|
+
initialPrompt += `
|
|
763
|
+
--- CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER/FEEDBACK) ---
|
|
764
|
+
${options.initialContext}
|
|
765
|
+
-----------------------------------------------------
|
|
766
|
+
`;
|
|
2719
767
|
}
|
|
2720
|
-
const currentTask = options.taskInstruction || "Analyze the project and fix pending issues.";
|
|
2721
|
-
let basePrompt = ``;
|
|
2722
768
|
if (contextContent) {
|
|
2723
|
-
|
|
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.
|
|
2724
771
|
|
|
2725
772
|
--- PROJECT CONTEXT ---
|
|
2726
773
|
${contextContent}
|
|
2727
774
|
-----------------------
|
|
2728
775
|
`;
|
|
2729
776
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
${options.history}
|
|
2735
|
-
----------------------------------
|
|
2736
|
-
`;
|
|
2737
|
-
}
|
|
2738
|
-
basePrompt += `
|
|
2739
|
-
|
|
2740
|
-
\u{1F7E2} EXECUTION MODE
|
|
2741
|
-
|
|
2742
|
-
You are a highly skilled Developer Agent.
|
|
2743
|
-
\u{1F449} **CURRENT TASK**: "${currentTask}"
|
|
2744
|
-
|
|
2745
|
-
Your goal is to COMPLETE this specific task and then STOP.
|
|
2746
|
-
1. Implement the necessary changes.
|
|
2747
|
-
2. Verify (compile/test).
|
|
2748
|
-
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.
|
|
2749
|
-
`;
|
|
2750
|
-
let nextPrompt = basePrompt;
|
|
777
|
+
await runSpecLoop(initialPrompt.trim(), outputFile, options.agentId);
|
|
778
|
+
}
|
|
779
|
+
async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
|
|
780
|
+
let nextPrompt = initialMessage;
|
|
2751
781
|
let keepGoing = true;
|
|
2752
|
-
|
|
2753
|
-
let
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
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;
|
|
2761
799
|
try {
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
},
|
|
800
|
+
lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
|
|
801
|
+
responseText += chunk;
|
|
802
|
+
}, overrideAgentId);
|
|
2765
803
|
spinner.stop("Response received");
|
|
2766
|
-
if (lastResponse) {
|
|
2767
|
-
const response = lastResponse;
|
|
2768
|
-
const actions = response.actions || [];
|
|
2769
|
-
if (response.message && response.message.includes("TASK_COMPLETED:")) {
|
|
2770
|
-
isTaskCompleted = true;
|
|
2771
|
-
finalSummary = response.message.split("TASK_COMPLETED:")[1].trim();
|
|
2772
|
-
keepGoing = false;
|
|
2773
|
-
}
|
|
2774
|
-
if (response.message && response.message.includes("TASK_FAILED:")) {
|
|
2775
|
-
const failureReason = response.message.split("TASK_FAILED:")[1].trim();
|
|
2776
|
-
tui.log.error(`\u274C Agent reported task failure: ${failureReason}`);
|
|
2777
|
-
return { success: false, summary: failureReason };
|
|
2778
|
-
}
|
|
2779
|
-
if (actions.length === 0 && response.message && !isTaskCompleted) {
|
|
2780
|
-
tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
2781
|
-
console.log(response.message);
|
|
2782
|
-
const userReply = await tui.text({ message: "Your answer:" });
|
|
2783
|
-
if (tui.isCancel(userReply)) {
|
|
2784
|
-
keepGoing = false;
|
|
2785
|
-
break;
|
|
2786
|
-
}
|
|
2787
|
-
nextPrompt = userReply;
|
|
2788
|
-
}
|
|
804
|
+
if (lastResponse && lastResponse.actions) {
|
|
2789
805
|
let executionResults = "";
|
|
2790
806
|
let waitingForUser = false;
|
|
2791
|
-
|
|
807
|
+
let specUpdated = false;
|
|
808
|
+
let hasSystemError = false;
|
|
809
|
+
let systemErrorContent = "";
|
|
810
|
+
for (const action of lastResponse.actions) {
|
|
2792
811
|
if (action.type === "talk_with_user") {
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
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
|
+
}
|
|
2796
834
|
} else if (action.type === "list_files") {
|
|
2797
835
|
tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
|
|
2798
836
|
const result = handleListFiles(action.path || ".");
|
|
@@ -2808,359 +846,205 @@ ${result}
|
|
|
2808
846
|
|
|
2809
847
|
`;
|
|
2810
848
|
} else if (action.type === "search_file") {
|
|
849
|
+
tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
|
|
2811
850
|
const result = handleSearchFile(action.path || "");
|
|
2812
851
|
executionResults += `[Action search_file(${action.path}) Result]:
|
|
2813
852
|
${result}
|
|
2814
853
|
|
|
2815
854
|
`;
|
|
2816
|
-
} else if (action.type === "
|
|
2817
|
-
const
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
|
|
2824
|
-
});
|
|
2825
|
-
if (choice === "always") {
|
|
2826
|
-
autoApprovals.commands = true;
|
|
2827
|
-
approved = true;
|
|
2828
|
-
} else if (choice === "yes") approved = true;
|
|
2829
|
-
}
|
|
2830
|
-
if (approved) {
|
|
2831
|
-
const result = await handleRunCommand(cmd);
|
|
2832
|
-
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]:
|
|
2833
862
|
${result}
|
|
2834
863
|
|
|
2835
864
|
`;
|
|
2836
|
-
} else {
|
|
2837
|
-
executionResults += `[Action run_command]: User blocked execution.
|
|
2838
|
-
|
|
2839
|
-
`;
|
|
2840
|
-
}
|
|
2841
865
|
} else if (["create_file", "modify_file"].includes(action.type)) {
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
let
|
|
2845
|
-
if (!
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
if (choice === "always") {
|
|
2851
|
-
autoApprovals.files = true;
|
|
2852
|
-
approved = true;
|
|
2853
|
-
} 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;
|
|
2854
874
|
}
|
|
2855
|
-
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 {
|
|
2856
884
|
if (action.type === "create_file") {
|
|
2857
|
-
const
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
executionResults += `[Action create_file]: Success
|
|
2861
|
-
|
|
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.
|
|
2862
889
|
`;
|
|
2863
|
-
} else {
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
` : `[Action modify_file]: Failed
|
|
2870
|
-
|
|
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.
|
|
2871
895
|
`;
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
896
|
+
specUpdated = true;
|
|
897
|
+
} else {
|
|
898
|
+
executionResults += `[Action modify_file]: Failed. Target content not found or ambiguous.
|
|
2876
899
|
`;
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
executionResults += `[Action modify_file]: Failed. 'target_content' is required.
|
|
2880
903
|
`;
|
|
2881
|
-
|
|
2882
|
-
} else if (action.type.startsWith("ast_")) {
|
|
2883
|
-
try {
|
|
2884
|
-
let result = "";
|
|
2885
|
-
if (action.type === "ast_list_structure") {
|
|
2886
|
-
result = await astListStructure(action.path || "");
|
|
2887
|
-
} else if (action.type === "ast_get_method") {
|
|
2888
|
-
result = await astGetMethod(action.path || "", action.class_name || "", action.method_name || "");
|
|
2889
|
-
} else if (action.type === "ast_add_method") {
|
|
2890
|
-
const success = await astAddMethod(action.path || "", action.class_name || "", action.method_code || "");
|
|
2891
|
-
result = success ? "Method added successfully." : "Failed to add method.";
|
|
2892
|
-
} else if (action.type === "ast_modify_method") {
|
|
2893
|
-
const success = await astModifyMethod(action.path || "", action.class_name || "", action.method_name || "", action.new_body || "");
|
|
2894
|
-
result = success ? "Method modified successfully." : "Failed to modify method.";
|
|
2895
|
-
} else if (action.type === "ast_remove_method") {
|
|
2896
|
-
const success = await astRemoveMethod(action.path || "", action.class_name || "", action.method_name || "");
|
|
2897
|
-
result = success ? "Method removed successfully." : "Failed to remove method.";
|
|
2898
|
-
} else if (action.type === "ast_add_class") {
|
|
2899
|
-
const success = await astAddClass(action.path || "", action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
|
|
2900
|
-
result = success ? "Class added successfully." : "Failed to add class.";
|
|
2901
|
-
} else if (action.type === "ast_get_property") {
|
|
2902
|
-
tui.log.info(`\u{1F50D} Reading property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
|
|
2903
|
-
const propContent = await astGetProperty(action.path || "", action.class_name || "", action.property_name || "");
|
|
2904
|
-
result = `[AST Get Property] Content:
|
|
2905
|
-
${propContent}`;
|
|
2906
|
-
} else if (action.type === "ast_add_property") {
|
|
2907
|
-
const success = await astAddProperty(action.path || "", action.class_name || "", action.property_code || "");
|
|
2908
|
-
result = success ? "Property added successfully." : "Failed to add property.";
|
|
2909
|
-
} else if (action.type === "ast_modify_property") {
|
|
2910
|
-
tui.log.info(`\u270F\uFE0F Modifying property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
|
|
2911
|
-
const success = await astModifyProperty(action.path || "", action.class_name || "", action.property_name || "", action.property_code || "");
|
|
2912
|
-
result = success ? "Property modified successfully." : "Failed to modify property.";
|
|
2913
|
-
} else if (action.type === "ast_remove_property") {
|
|
2914
|
-
const success = await astRemoveProperty(action.path || "", action.class_name || "", action.property_name || "");
|
|
2915
|
-
result = success ? "Property removed successfully." : "Failed to remove property.";
|
|
2916
|
-
} else if (action.type === "ast_add_decorator") {
|
|
2917
|
-
const success = await astAddDecorator(action.path || "", action.class_name || "", action.decorator_code || "");
|
|
2918
|
-
result = success ? "Decorator added successfully." : "Failed to add decorator.";
|
|
2919
|
-
} else if (action.type === "ast_add_interface") {
|
|
2920
|
-
const success = await astAddInterface(action.path || "", action.interface_code || "");
|
|
2921
|
-
result = success ? "Interface added successfully." : "Failed to add interface.";
|
|
2922
|
-
} else if (action.type === "ast_add_type_alias") {
|
|
2923
|
-
const success = await astAddTypeAlias(action.path || "", action.type_code || "");
|
|
2924
|
-
result = success ? "Type alias added successfully." : "Failed to add type alias.";
|
|
2925
|
-
} else if (action.type === "ast_add_function") {
|
|
2926
|
-
const success = await astAddFunction(action.path || "", action.function_code || "");
|
|
2927
|
-
result = success ? "Function added successfully." : "Failed to add function.";
|
|
2928
|
-
} else if (action.type === "ast_remove_function") {
|
|
2929
|
-
const success = await astRemoveFunction(action.path || "", action.function_name || "");
|
|
2930
|
-
result = success ? "Function removed successfully." : "Failed to remove function.";
|
|
2931
|
-
} else if (action.type === "ast_add_import") {
|
|
2932
|
-
const success = await astAddImport(action.path || "", action.import_statement || "");
|
|
2933
|
-
result = success ? "Import added successfully." : "Failed to add import.";
|
|
2934
|
-
} else if (action.type === "ast_remove_import") {
|
|
2935
|
-
const success = await astRemoveImport(action.path || "", action.module_path || "");
|
|
2936
|
-
result = success ? "Import removed successfully." : "Failed to remove import.";
|
|
2937
|
-
} else if (action.type === "ast_organize_imports") {
|
|
2938
|
-
const success = await astOrganizeImports(action.path || "");
|
|
2939
|
-
result = success ? "Imports organized successfully." : "Failed to organize imports.";
|
|
2940
|
-
} else {
|
|
2941
|
-
result = `Unknown AST action: ${action.type}`;
|
|
904
|
+
}
|
|
2942
905
|
}
|
|
2943
|
-
executionResults += `[Action ${action.type} Result]:
|
|
2944
|
-
${result}
|
|
2945
|
-
|
|
2946
|
-
`;
|
|
2947
|
-
tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
|
|
2948
906
|
} catch (e) {
|
|
2949
|
-
executionResults += `[Action ${action.type}
|
|
2950
|
-
|
|
907
|
+
executionResults += `[Action ${action.type}]: Error: ${e.message}
|
|
2951
908
|
`;
|
|
2952
|
-
tui.log.error(`\u274C AST Action Error: ${e.message}`);
|
|
2953
909
|
}
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
|
|
913
|
+
const extraContext = executionResults ? `
|
|
2958
914
|
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
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.
|
|
2963
922
|
|
|
2964
|
-
|
|
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.
|
|
2965
935
|
|
|
2966
|
-
|
|
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;
|
|
2967
966
|
}
|
|
2968
967
|
}
|
|
2969
968
|
if (executionResults) {
|
|
2970
|
-
|
|
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);
|
|
2971
1006
|
const userReply = await tui.text({ message: "Your answer:" });
|
|
2972
1007
|
if (tui.isCancel(userReply)) {
|
|
2973
1008
|
keepGoing = false;
|
|
2974
1009
|
break;
|
|
2975
1010
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
1011
|
+
FileLogger.log("USER_INPUT", "User response to specification agent (Message only)", { userReply });
|
|
1012
|
+
nextPrompt = userReply;
|
|
2978
1013
|
} else {
|
|
2979
|
-
|
|
2980
|
-
[System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
|
|
2981
|
-
tui.log.info(colors.dim("Processing results..."));
|
|
1014
|
+
keepGoing = false;
|
|
2982
1015
|
}
|
|
2983
|
-
} else if (!keepGoing) {
|
|
2984
|
-
} else if (waitingForUser) {
|
|
2985
|
-
} else {
|
|
2986
|
-
if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
|
|
2987
1016
|
}
|
|
2988
1017
|
} else {
|
|
2989
|
-
tui.log.warning("No
|
|
1018
|
+
tui.log.warning("No actions received.");
|
|
1019
|
+
keepGoing = false;
|
|
2990
1020
|
}
|
|
2991
|
-
} catch (
|
|
2992
|
-
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
spinner.stop("Error");
|
|
1023
|
+
tui.log.error(error.message);
|
|
2993
1024
|
keepGoing = false;
|
|
2994
|
-
return { success: false, summary: `Error: ${e.message}` };
|
|
2995
1025
|
}
|
|
2996
1026
|
}
|
|
2997
|
-
tui.log.success("\u2705 Task Scope Completed");
|
|
2998
|
-
return { success: true, summary: finalSummary || "Task completed without summary." };
|
|
2999
1027
|
}
|
|
3000
|
-
async function
|
|
3001
|
-
const
|
|
3002
|
-
|
|
3003
|
-
const
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
const agentVersion = getAgentVersion4();
|
|
3012
|
-
if (agentVersion) {
|
|
3013
|
-
payload.agent_version_number = agentVersion;
|
|
3014
|
-
}
|
|
3015
|
-
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${getAgentId4()}/chat`;
|
|
3016
|
-
let fullMsg = "";
|
|
3017
|
-
let raw = {};
|
|
3018
|
-
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
3019
|
-
onChunk: (c) => {
|
|
3020
|
-
fullMsg += c;
|
|
3021
|
-
onChunk(c);
|
|
3022
|
-
},
|
|
3023
|
-
onComplete: (msg, metadata) => {
|
|
3024
|
-
const returnedId = metadata?.conversation_id;
|
|
3025
|
-
raw = {
|
|
3026
|
-
message: msg || fullMsg,
|
|
3027
|
-
conversation_id: returnedId || conversationId
|
|
3028
|
-
};
|
|
3029
|
-
},
|
|
3030
|
-
onError: (e) => {
|
|
3031
|
-
throw e;
|
|
3032
|
-
}
|
|
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
|
|
3033
1039
|
});
|
|
3034
|
-
const parsed = parseAgentResponse(raw);
|
|
3035
1040
|
if (parsed.conversation_id) {
|
|
3036
|
-
await conversationManager.saveConversationId(
|
|
1041
|
+
await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
|
|
3037
1042
|
}
|
|
3038
1043
|
return parsed;
|
|
3039
1044
|
}
|
|
3040
1045
|
|
|
3041
|
-
// src/
|
|
3042
|
-
|
|
3043
|
-
import path9 from "path";
|
|
3044
|
-
var TaskManager = class {
|
|
3045
|
-
projectRoot;
|
|
3046
|
-
specPath;
|
|
3047
|
-
constructor(projectRoot = process.cwd()) {
|
|
3048
|
-
this.projectRoot = projectRoot;
|
|
3049
|
-
this.specPath = path9.resolve(this.projectRoot, "_sharkrc", "tech-spec.md");
|
|
3050
|
-
}
|
|
3051
|
-
/**
|
|
3052
|
-
* Reads the tech-spec.md file and analyzes its current state.
|
|
3053
|
-
*/
|
|
3054
|
-
analyzeSpecState() {
|
|
3055
|
-
if (!fs8.existsSync(this.specPath)) {
|
|
3056
|
-
return { status: "MISSING", allTasks: [] };
|
|
3057
|
-
}
|
|
3058
|
-
const content = fs8.readFileSync(this.specPath, "utf-8");
|
|
3059
|
-
const lines = content.split("\n");
|
|
3060
|
-
const tasks = [];
|
|
3061
|
-
let taskIndex = 1;
|
|
3062
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3063
|
-
const line = lines[i];
|
|
3064
|
-
const trimmed = line.trim();
|
|
3065
|
-
const pendingMatch = trimmed.match(/^- \[ \] (.*)/);
|
|
3066
|
-
const completedMatch = trimmed.match(/^- \[x\] (.*)/i);
|
|
3067
|
-
const progressMatch = trimmed.match(/^- \[\/\] (.*)/);
|
|
3068
|
-
let currentTask = null;
|
|
3069
|
-
let status2 = null;
|
|
3070
|
-
let description = "";
|
|
3071
|
-
if (pendingMatch) {
|
|
3072
|
-
description = pendingMatch[1].trim();
|
|
3073
|
-
status2 = "PENDING";
|
|
3074
|
-
} else if (completedMatch) {
|
|
3075
|
-
description = completedMatch[1].trim();
|
|
3076
|
-
status2 = "COMPLETED";
|
|
3077
|
-
} else if (progressMatch) {
|
|
3078
|
-
description = progressMatch[1].trim();
|
|
3079
|
-
status2 = "IN_PROGRESS";
|
|
3080
|
-
}
|
|
3081
|
-
if (status2 && description) {
|
|
3082
|
-
let j = i + 1;
|
|
3083
|
-
while (j < lines.length) {
|
|
3084
|
-
const nextLine = lines[j];
|
|
3085
|
-
const nextTrimmed = nextLine.trim();
|
|
3086
|
-
if (!nextTrimmed || nextTrimmed.match(/^- \[[ x\/]\]/) || nextTrimmed.startsWith("#")) {
|
|
3087
|
-
break;
|
|
3088
|
-
}
|
|
3089
|
-
description += "\n" + nextTrimmed;
|
|
3090
|
-
j++;
|
|
3091
|
-
}
|
|
3092
|
-
currentTask = {
|
|
3093
|
-
id: `task-${taskIndex++}`,
|
|
3094
|
-
description,
|
|
3095
|
-
status: status2,
|
|
3096
|
-
line_number: i
|
|
3097
|
-
};
|
|
3098
|
-
tasks.push(currentTask);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
let nextTask = tasks.find((t2) => t2.status === "IN_PROGRESS");
|
|
3102
|
-
if (!nextTask) {
|
|
3103
|
-
nextTask = tasks.find((t2) => t2.status === "PENDING");
|
|
3104
|
-
}
|
|
3105
|
-
const status = !nextTask && tasks.length > 0 && tasks.every((t2) => t2.status === "COMPLETED") ? "COMPLETED" : "PENDING";
|
|
3106
|
-
return {
|
|
3107
|
-
status: tasks.length === 0 ? "MISSING" : status,
|
|
3108
|
-
// Empty file is effectively "pending creation" but we treat as missing content logic elsewhere
|
|
3109
|
-
nextTask,
|
|
3110
|
-
allTasks: tasks
|
|
3111
|
-
};
|
|
3112
|
-
}
|
|
3113
|
-
/**
|
|
3114
|
-
* Marks a specific task as COMPLETED in the file.
|
|
3115
|
-
* Uses line-based replacement to be safe.
|
|
3116
|
-
*/
|
|
3117
|
-
markTaskAsDone(taskId) {
|
|
3118
|
-
const state = this.analyzeSpecState();
|
|
3119
|
-
const task = state.allTasks.find((t2) => t2.id === taskId);
|
|
3120
|
-
if (!task) {
|
|
3121
|
-
console.error(`Task ${taskId} not found.`);
|
|
3122
|
-
return false;
|
|
3123
|
-
}
|
|
3124
|
-
const content = fs8.readFileSync(this.specPath, "utf-8");
|
|
3125
|
-
const lines = content.split("\n");
|
|
3126
|
-
const targetLine = lines[task.line_number];
|
|
3127
|
-
if (!targetLine.includes(task.description)) {
|
|
3128
|
-
console.error(`Concurrency Error: Task line content mistmatch. Expected "${task.description}" at line ${task.line_number}.`);
|
|
3129
|
-
return false;
|
|
3130
|
-
}
|
|
3131
|
-
const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
|
|
3132
|
-
lines[task.line_number] = newLine;
|
|
3133
|
-
fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
3134
|
-
return true;
|
|
3135
|
-
}
|
|
3136
|
-
/**
|
|
3137
|
-
* Marks a task as IN_PROGRESS.
|
|
3138
|
-
*/
|
|
3139
|
-
markTaskInProgress(taskId) {
|
|
3140
|
-
const state = this.analyzeSpecState();
|
|
3141
|
-
const task = state.allTasks.find((t2) => t2.id === taskId);
|
|
3142
|
-
if (!task) return false;
|
|
3143
|
-
const content = fs8.readFileSync(this.specPath, "utf-8");
|
|
3144
|
-
const lines = content.split("\n");
|
|
3145
|
-
let targetLine = lines[task.line_number];
|
|
3146
|
-
targetLine = targetLine.replace("- [ ]", "- [/]");
|
|
3147
|
-
lines[task.line_number] = targetLine;
|
|
3148
|
-
fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
3149
|
-
return true;
|
|
3150
|
-
}
|
|
3151
|
-
/**
|
|
3152
|
-
* Completely updates the spec file content (used by Spec Agent).
|
|
3153
|
-
*/
|
|
3154
|
-
updateSpecContent(newContent) {
|
|
3155
|
-
fs8.writeFileSync(this.specPath, newContent, "utf-8");
|
|
3156
|
-
}
|
|
3157
|
-
getSpecPath() {
|
|
3158
|
-
return this.specPath;
|
|
3159
|
-
}
|
|
3160
|
-
};
|
|
3161
|
-
|
|
3162
|
-
// src/commands/dev.ts
|
|
3163
|
-
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) => {
|
|
3164
1048
|
const taskManager = new TaskManager(process.cwd());
|
|
3165
1049
|
let state = taskManager.analyzeSpecState();
|
|
3166
1050
|
if (state.status === "MISSING") {
|
|
@@ -3169,7 +1053,6 @@ var devCommand = new Command3("dev").description("Starts the Shark Developer Age
|
|
|
3169
1053
|
if (confirm) {
|
|
3170
1054
|
await interactiveSpecificationAgent({
|
|
3171
1055
|
briefingPath: options.task ? void 0 : void 0
|
|
3172
|
-
// If task provided, maybe write a temp briefing? For now standard flow.
|
|
3173
1056
|
});
|
|
3174
1057
|
state = taskManager.analyzeSpecState();
|
|
3175
1058
|
if (state.status === "MISSING") {
|
|
@@ -3183,7 +1066,7 @@ var devCommand = new Command3("dev").description("Starts the Shark Developer Age
|
|
|
3183
1066
|
let keepOrchestrating = true;
|
|
3184
1067
|
let burnMode = false;
|
|
3185
1068
|
let contextHistory = "";
|
|
3186
|
-
tui.intro("\u{1F988} Shark Orchestrator
|
|
1069
|
+
tui.intro("\u{1F988} Shark Legacy Orchestrator");
|
|
3187
1070
|
while (keepOrchestrating) {
|
|
3188
1071
|
state = taskManager.analyzeSpecState();
|
|
3189
1072
|
if (state.status === "COMPLETED") {
|
|
@@ -3242,7 +1125,7 @@ ${contextHistory}`
|
|
|
3242
1125
|
}
|
|
3243
1126
|
taskManager.markTaskInProgress(currentTask.id);
|
|
3244
1127
|
tui.log.info(`\u26A1 Starting Micro-Context for Task: "${currentTask.description}"`);
|
|
3245
|
-
const result = await
|
|
1128
|
+
const result = await interactiveDeveloperAgent2({
|
|
3246
1129
|
taskId: currentTask.id,
|
|
3247
1130
|
taskInstruction: currentTask.description,
|
|
3248
1131
|
history: contextHistory,
|
|
@@ -3277,279 +1160,52 @@ ${contextHistory}`
|
|
|
3277
1160
|
}
|
|
3278
1161
|
}
|
|
3279
1162
|
}
|
|
3280
|
-
tui.outro("\u{1F988} Orchestration Finished.");
|
|
1163
|
+
tui.outro("\u{1F988} Legacy Orchestration Finished.");
|
|
3281
1164
|
});
|
|
3282
1165
|
|
|
3283
|
-
// src/commands/
|
|
1166
|
+
// src/commands/export-schema.ts
|
|
3284
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
|
+
});
|
|
3285
1171
|
|
|
3286
|
-
// src/
|
|
3287
|
-
import
|
|
3288
|
-
import
|
|
3289
|
-
import
|
|
3290
|
-
import
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
const
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
const config = ConfigManager.getInstance().getConfig();
|
|
3299
|
-
if (config.agentVersions?.qa) return config.agentVersions.qa;
|
|
3300
|
-
return process.env.STACKSPOT_QA_AGENT_VERSION;
|
|
3301
|
-
}
|
|
3302
|
-
var ChromeDevToolsClient = class {
|
|
3303
|
-
client = null;
|
|
3304
|
-
transport = null;
|
|
3305
|
-
async connect() {
|
|
3306
|
-
if (this.client) return;
|
|
3307
|
-
try {
|
|
3308
|
-
this.transport = new StdioClientTransport({
|
|
3309
|
-
command: "npx",
|
|
3310
|
-
args: ["-y", "chrome-devtools-mcp@latest"]
|
|
3311
|
-
});
|
|
3312
|
-
this.client = new Client({
|
|
3313
|
-
name: "shark-qa-client",
|
|
3314
|
-
version: "1.0.0"
|
|
3315
|
-
}, {
|
|
3316
|
-
capabilities: {}
|
|
3317
|
-
});
|
|
3318
|
-
await this.client.connect(this.transport);
|
|
3319
|
-
tui.log.success("\u{1F50C} Connected to Chrome DevTools MCP");
|
|
3320
|
-
} catch (e) {
|
|
3321
|
-
tui.log.error(`Failed to connect to Chrome MCP: ${e.message}`);
|
|
3322
|
-
throw e;
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3325
|
-
async callTool(name, args) {
|
|
3326
|
-
if (!this.client) await this.connect();
|
|
3327
|
-
try {
|
|
3328
|
-
const result = await this.client.callTool({
|
|
3329
|
-
name,
|
|
3330
|
-
arguments: args
|
|
3331
|
-
});
|
|
3332
|
-
return result;
|
|
3333
|
-
} catch (e) {
|
|
3334
|
-
return { isError: true, content: [{ type: "text", text: `MCP Error: ${e.message}` }] };
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
async close() {
|
|
3338
|
-
if (this.transport) {
|
|
3339
|
-
await this.transport.close();
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
};
|
|
3343
|
-
var mcpClient = new ChromeDevToolsClient();
|
|
3344
|
-
async function runQAAgent(options) {
|
|
3345
|
-
const agentId = getAgentId5();
|
|
3346
|
-
if (!agentId) {
|
|
3347
|
-
tui.log.error("\u274C STACKSPOT_QA_AGENT_ID not configured.");
|
|
3348
|
-
tui.log.info("Please run: set STACKSPOT_QA_AGENT_ID=<your-id>");
|
|
3349
|
-
return;
|
|
3350
|
-
}
|
|
3351
|
-
await mcpClient.connect();
|
|
3352
|
-
tui.intro("\u{1F988} Shark QA Agent");
|
|
3353
|
-
tui.log.info("Connecting to Chrome DevTools...");
|
|
3354
|
-
const realm = await getActiveRealm();
|
|
3355
|
-
const token = await tokenStorage.getToken(realm);
|
|
3356
|
-
if (!token) {
|
|
3357
|
-
tui.log.error('Authentication required. Run "shark login".');
|
|
3358
|
-
return;
|
|
3359
|
-
}
|
|
3360
|
-
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");
|
|
3361
1184
|
try {
|
|
3362
|
-
const contextPath = path10.join(process.cwd(), "_sharkrc", "project-context.md");
|
|
3363
|
-
if (fs9.existsSync(contextPath)) {
|
|
3364
|
-
projectContext = fs9.readFileSync(contextPath, "utf-8");
|
|
3365
|
-
tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
|
|
3366
|
-
}
|
|
3367
|
-
} catch (e) {
|
|
3368
|
-
}
|
|
3369
|
-
let userMessage = `CONTEXTO DO PROJETO:
|
|
3370
|
-
${projectContext}
|
|
3371
|
-
|
|
3372
|
-
`;
|
|
3373
|
-
if (options.initialUrl) {
|
|
3374
|
-
userMessage += `URL ALVO: ${options.initialUrl}
|
|
3375
|
-
`;
|
|
3376
|
-
}
|
|
3377
|
-
if (options.scenario) {
|
|
3378
|
-
userMessage += `CEN\xC1RIO DE TESTE: ${options.scenario}
|
|
3379
|
-
`;
|
|
3380
|
-
} else {
|
|
3381
|
-
userMessage += `Por favor, aguarde instru\xE7\xF5es do usu\xE1rio.`;
|
|
3382
|
-
}
|
|
3383
|
-
let keepRunning = true;
|
|
3384
|
-
while (keepRunning) {
|
|
3385
|
-
const spinner = tui.spinner();
|
|
3386
|
-
spinner.start("\u{1F916} Shark QA is thinking...");
|
|
3387
|
-
let agentResponseText = "";
|
|
3388
|
-
let agentResponse = null;
|
|
3389
1185
|
try {
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
`https://genai-inference-app.stackspot.com/v1/agent/${getAgentId5()}/chat`,
|
|
3393
|
-
{
|
|
3394
|
-
user_prompt: userMessage,
|
|
3395
|
-
streaming: true,
|
|
3396
|
-
use_conversation: true,
|
|
3397
|
-
conversation_id: existingConversationId,
|
|
3398
|
-
...getAgentVersion5() ? { agent_version_number: getAgentVersion5() } : {}
|
|
3399
|
-
},
|
|
3400
|
-
{
|
|
3401
|
-
"Authorization": `Bearer ${token}`
|
|
3402
|
-
},
|
|
3403
|
-
{
|
|
3404
|
-
onChunk: (chunk) => {
|
|
3405
|
-
agentResponseText += chunk;
|
|
3406
|
-
if (agentResponseText.length > 10 && agentResponseText.trim().startsWith("{")) {
|
|
3407
|
-
spinner.message("Receiving structured plan...");
|
|
3408
|
-
}
|
|
3409
|
-
},
|
|
3410
|
-
onComplete: (fullText, metadata) => {
|
|
3411
|
-
try {
|
|
3412
|
-
if (metadata?.conversation_id) {
|
|
3413
|
-
conversationManager.saveConversationId(AGENT_TYPE5, metadata.conversation_id);
|
|
3414
|
-
}
|
|
3415
|
-
agentResponse = parseAgentResponse(fullText || agentResponseText);
|
|
3416
|
-
} catch (e) {
|
|
3417
|
-
tui.log.error(`Parse Error: ${e.message}`);
|
|
3418
|
-
agentResponse = { actions: [], summary: "Error parsing response", message: fullText };
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
);
|
|
3423
|
-
spinner.stop("Response Received");
|
|
3424
|
-
} catch (error) {
|
|
3425
|
-
spinner.stop("Communication Error", 1);
|
|
3426
|
-
tui.log.error(error.message);
|
|
3427
|
-
keepRunning = false;
|
|
3428
|
-
break;
|
|
3429
|
-
}
|
|
3430
|
-
const currentResponse = agentResponse;
|
|
3431
|
-
if (!currentResponse) continue;
|
|
3432
|
-
if (currentResponse.summary) {
|
|
3433
|
-
tui.log.info(colors.primary(`\u{1F4CB} Plan: ${currentResponse.summary}`));
|
|
3434
|
-
}
|
|
3435
|
-
if (currentResponse.actions.length === 0) {
|
|
3436
|
-
const reply = await tui.text({
|
|
3437
|
-
message: "\u{1F916} Shark QA:",
|
|
3438
|
-
placeholder: "Your reply..."
|
|
3439
|
-
});
|
|
3440
|
-
if (tui.isCancel(reply)) {
|
|
3441
|
-
keepRunning = false;
|
|
3442
|
-
} else {
|
|
3443
|
-
userMessage = reply;
|
|
3444
|
-
}
|
|
3445
|
-
continue;
|
|
3446
|
-
}
|
|
3447
|
-
for (const action of currentResponse.actions) {
|
|
3448
|
-
tui.log.info(colors.dim(`Executing: ${action.type}`));
|
|
3449
|
-
let result = "";
|
|
3450
|
-
try {
|
|
3451
|
-
switch (action.type) {
|
|
3452
|
-
case "talk_with_user":
|
|
3453
|
-
const reply = await tui.text({
|
|
3454
|
-
message: `\u{1F916} ${action.content}`
|
|
3455
|
-
});
|
|
3456
|
-
if (tui.isCancel(reply)) keepRunning = false;
|
|
3457
|
-
else result = reply;
|
|
3458
|
-
break;
|
|
3459
|
-
case "use_mcp_tool":
|
|
3460
|
-
if (action.tool_name) {
|
|
3461
|
-
tui.log.info(`\u{1F527} MCP Tool: ${colors.bold(action.tool_name)}`);
|
|
3462
|
-
let args = {};
|
|
3463
|
-
try {
|
|
3464
|
-
args = typeof action.tool_args === "string" ? JSON.parse(action.tool_args) : action.tool_args || {};
|
|
3465
|
-
} catch (e) {
|
|
3466
|
-
tui.log.warning("Failed to parse tool_args, using empty object");
|
|
3467
|
-
}
|
|
3468
|
-
const mcpResult = await mcpClient.callTool(action.tool_name, args);
|
|
3469
|
-
result = JSON.stringify(mcpResult);
|
|
3470
|
-
tui.log.success(`Result: ${result.substring(0, 100)}...`);
|
|
3471
|
-
}
|
|
3472
|
-
break;
|
|
3473
|
-
case "create_file":
|
|
3474
|
-
if (action.path && action.content) {
|
|
3475
|
-
const fullPath = path10.resolve(process.cwd(), action.path);
|
|
3476
|
-
const BOM = "\uFEFF";
|
|
3477
|
-
const contentToWrite = action.content;
|
|
3478
|
-
const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
|
|
3479
|
-
fs9.writeFileSync(fullPath, finalContent, { encoding: "utf-8" });
|
|
3480
|
-
tui.log.success(`File created: ${action.path}`);
|
|
3481
|
-
result = "File created successfully.";
|
|
3482
|
-
}
|
|
3483
|
-
break;
|
|
3484
|
-
case "read_file":
|
|
3485
|
-
result = handleReadFile(action.path || "");
|
|
3486
|
-
break;
|
|
3487
|
-
case "run_command":
|
|
3488
|
-
const confirm = await tui.confirm({ message: `Run command: ${action.command}?` });
|
|
3489
|
-
if (confirm && action.command) {
|
|
3490
|
-
result = await handleRunCommand(action.command);
|
|
3491
|
-
} else {
|
|
3492
|
-
result = "Command execution denied by user.";
|
|
3493
|
-
}
|
|
3494
|
-
break;
|
|
3495
|
-
default:
|
|
3496
|
-
result = `Action ${action.type} not fully implemented in local client.`;
|
|
3497
|
-
}
|
|
3498
|
-
} catch (e) {
|
|
3499
|
-
result = `Error executing ${action.type}: ${e.message}`;
|
|
3500
|
-
tui.log.error(result);
|
|
3501
|
-
}
|
|
3502
|
-
userMessage = `[Action ${action.type} Result]:
|
|
3503
|
-
${result}
|
|
3504
|
-
|
|
3505
|
-
`;
|
|
1186
|
+
await fs5.rm(targetPath, { recursive: true, force: true });
|
|
1187
|
+
} catch {
|
|
3506
1188
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
}
|
|
3511
|
-
|
|
3512
|
-
// src/commands/qa.ts
|
|
3513
|
-
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) => {
|
|
3514
|
-
try {
|
|
3515
|
-
await runQAAgent({
|
|
3516
|
-
initialUrl: options.url,
|
|
3517
|
-
scenario: options.scenario
|
|
3518
|
-
});
|
|
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}`);
|
|
3519
1192
|
} catch (error) {
|
|
3520
|
-
console.error(
|
|
1193
|
+
console.error(`\u274C Failed to install superpowers skills: ${error.message}`);
|
|
3521
1194
|
process.exit(1);
|
|
3522
1195
|
}
|
|
3523
|
-
}
|
|
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);
|
|
3524
1198
|
|
|
3525
1199
|
// src/bin/shark.ts
|
|
3526
1200
|
crashHandler.init();
|
|
3527
|
-
var program = new
|
|
1201
|
+
var program = new Command6();
|
|
3528
1202
|
program.name("shark").description("Shark CLI: AI-Native Collaborative Development Tool").version("0.0.1");
|
|
3529
1203
|
program.addCommand(loginCommand);
|
|
3530
1204
|
program.addCommand(initCommand);
|
|
3531
|
-
program.addCommand(scanCommand);
|
|
3532
1205
|
program.addCommand(devCommand);
|
|
3533
|
-
program.addCommand(
|
|
3534
|
-
program.
|
|
3535
|
-
|
|
3536
|
-
await interactiveBusinessAnalyst();
|
|
3537
|
-
} catch (error) {
|
|
3538
|
-
console.error("Error:", error.message);
|
|
3539
|
-
process.exit(1);
|
|
3540
|
-
}
|
|
3541
|
-
});
|
|
3542
|
-
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) => {
|
|
3543
|
-
try {
|
|
3544
|
-
await interactiveSpecificationAgent({
|
|
3545
|
-
agentId: options.id,
|
|
3546
|
-
briefingPath: options.briefing
|
|
3547
|
-
});
|
|
3548
|
-
} catch (error) {
|
|
3549
|
-
console.error("Error:", error.message);
|
|
3550
|
-
process.exit(1);
|
|
3551
|
-
}
|
|
3552
|
-
});
|
|
1206
|
+
program.addCommand(legacyCommand);
|
|
1207
|
+
program.addCommand(exportSchemaCommand);
|
|
1208
|
+
program.addCommand(superCommand);
|
|
3553
1209
|
program.command("config").description("Manage global configuration").action(configCommand.action);
|
|
3554
1210
|
process.on("unhandledRejection", (err) => {
|
|
3555
1211
|
console.error(colors.error("\u274C Unhandled Error:"), err);
|