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.
Files changed (64) hide show
  1. package/dist/bin/shark.js +753 -3097
  2. package/dist/bin/shark.js.map +1 -1
  3. package/dist/{chunk-H6S7DYRG.js → chunk-CUUMQTCW.js} +117 -335
  4. package/dist/chunk-CUUMQTCW.js.map +1 -0
  5. package/dist/chunk-JLE7NOEG.js +292 -0
  6. package/dist/chunk-JLE7NOEG.js.map +1 -0
  7. package/dist/chunk-T3DPAQAV.js +3759 -0
  8. package/dist/chunk-T3DPAQAV.js.map +1 -0
  9. package/dist/developer-agent-CEDTDILQ.js +9 -0
  10. package/dist/developer-agent-CEDTDILQ.js.map +1 -0
  11. package/dist/index.js +2 -1
  12. package/dist/index.js.map +1 -1
  13. package/package.json +2 -1
  14. package/skills/brainstorming/SKILL.md +159 -0
  15. package/skills/brainstorming/scripts/frame-template.html +213 -0
  16. package/skills/brainstorming/scripts/helper.js +167 -0
  17. package/skills/brainstorming/scripts/server.cjs +723 -0
  18. package/skills/brainstorming/scripts/start-server.sh +209 -0
  19. package/skills/brainstorming/scripts/stop-server.sh +120 -0
  20. package/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  21. package/skills/brainstorming/visual-companion.md +298 -0
  22. package/skills/dispatching-parallel-agents/SKILL.md +185 -0
  23. package/skills/executing-plans/SKILL.md +70 -0
  24. package/skills/finishing-a-development-branch/SKILL.md +241 -0
  25. package/skills/receiving-code-review/SKILL.md +213 -0
  26. package/skills/requesting-code-review/SKILL.md +103 -0
  27. package/skills/requesting-code-review/code-reviewer.md +172 -0
  28. package/skills/subagent-driven-development/SKILL.md +416 -0
  29. package/skills/subagent-driven-development/implementer-prompt.md +139 -0
  30. package/skills/subagent-driven-development/scripts/review-package +47 -0
  31. package/skills/subagent-driven-development/scripts/task-brief +42 -0
  32. package/skills/subagent-driven-development/task-reviewer-prompt.md +188 -0
  33. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  34. package/skills/systematic-debugging/SKILL.md +296 -0
  35. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  36. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  37. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  38. package/skills/systematic-debugging/find-polluter.sh +63 -0
  39. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  40. package/skills/systematic-debugging/test-academic.md +14 -0
  41. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  42. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  43. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  44. package/skills/test-driven-development/SKILL.md +371 -0
  45. package/skills/test-driven-development/testing-anti-patterns.md +299 -0
  46. package/skills/using-git-worktrees/SKILL.md +202 -0
  47. package/skills/using-superpowers/SKILL.md +121 -0
  48. package/skills/using-superpowers/references/antigravity-tools.md +96 -0
  49. package/skills/using-superpowers/references/claude-code-tools.md +50 -0
  50. package/skills/using-superpowers/references/codex-tools.md +72 -0
  51. package/skills/using-superpowers/references/copilot-tools.md +49 -0
  52. package/skills/using-superpowers/references/gemini-tools.md +63 -0
  53. package/skills/using-superpowers/references/pi-tools.md +28 -0
  54. package/skills/verification-before-completion/SKILL.md +139 -0
  55. package/skills/writing-plans/SKILL.md +188 -0
  56. package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  57. package/skills/writing-skills/SKILL.md +689 -0
  58. package/skills/writing-skills/anthropic-best-practices.md +1150 -0
  59. package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  60. package/skills/writing-skills/graphviz-conventions.dot +172 -0
  61. package/skills/writing-skills/persuasion-principles.md +187 -0
  62. package/skills/writing-skills/render-graphs.js +168 -0
  63. package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  64. 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-H6S7DYRG.js";
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 Command5 } from "commander";
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/core/api/stackspot-client.ts
271
- var AuthError = class extends Error {
272
- constructor(message) {
273
- super(message);
274
- this.name = "AuthError";
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
- return creds.accessToken;
304
- }
305
-
306
- // src/core/api/sse-client.ts
307
- var SSEClient = class {
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
- try {
323
- const response = await fetch(url, {
324
- method: "POST",
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/core/workflow/conversation-manager.ts
623
- var ConversationManager = class _ConversationManager {
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/auth/get-active-realm.ts
692
- async function getActiveRealm() {
693
- const configManager = ConfigManager.getInstance();
694
- const config = configManager.getConfig();
695
- const realm = config.activeRealm;
696
- if (!realm) {
697
- throw new Error(
698
- 'No active authentication found.\nPlease run "shark login" first to authenticate.'
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?.ba) return config.agents.ba;
710
- return process.env.STACKSPOT_BA_AGENT_ID || "01KEJ95G304TNNAKGH5XNEEBVD";
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 runBusinessAnalystAgent(prompt, options = {}) {
719
- const { agentId, onChunk, onComplete } = options;
720
- const realm = await getActiveRealm();
721
- const token = await tokenStorage.getToken(realm);
722
- if (!token) {
723
- throw new Error(`No authentication token found for realm '${realm}'. Please run 'shark login'.`);
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 effectiveAgentId = getAgentId(options.agentId);
740
- const agentUrl = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${effectiveAgentId}/chat`;
741
- const headers = {
742
- "Authorization": `Bearer ${token}`,
743
- "Content-Type": "application/json"
744
- };
745
- let fullMessage = "";
746
- let rawResponse = {};
747
- await sseClient.streamAgentResponse(
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
- if (onComplete) {
775
- onComplete(parsedResponse);
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
- return parsedResponse;
778
- }
779
- async function interactiveBusinessAnalyst() {
780
- tui.intro("\u{1F3AF} Business Analyst Agent");
781
- const prompt = await tui.text({
782
- message: "Describe your project idea",
783
- placeholder: "E.g., I want to build a task management app for teams...",
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
- spinner.start("\u{1F4AC} Business Analyst is thinking...");
794
- let responseText = "";
795
- try {
796
- await runBusinessAnalystAgent(prompt, {
797
- onChunk: (chunk) => {
798
- responseText += chunk;
799
- try {
800
- if (responseText.trim().startsWith("{")) {
801
- spinner.message(colors.dim("Receiving structured data..."));
802
- } else {
803
- spinner.message(colors.dim("Thinking..."));
804
- }
805
- } catch (e) {
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
- onComplete: async (response) => {
809
- spinner.stop("Response received");
810
- if (response.summary) {
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 (response.actions && response.actions.length > 0) {
814
- for (const action of response.actions) {
815
- if (action.type === "talk_with_user") {
816
- tui.log.info(colors.success("\u{1F916} BA Agent:"));
817
- console.log(action.content);
818
- } else {
819
- tui.log.warning(`
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
- let specUpdated = false;
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} Architect:"));
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 === "search_code") {
1941
- const glob = action.path || "src/**/*";
1942
- const query = action.query || "";
1943
- const isRegex = action.is_regex === true;
1944
- tui.log.info(`\u{1F50E} Search code: ${colors.dim(`"${query}" in ${glob}`)}`);
1945
- const result = handleSearchCode(glob, query, isRegex);
1946
- executionResults += `[Action search_code("${query}" in "${glob}") Result]:
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
- } else if (["create_file", "modify_file"].includes(action.type)) {
1951
- let actionPath = path6.resolve(action.path || "");
1952
- const resolvedTargetPath = path6.resolve(targetPath);
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
- try {
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 BOM = "\uFEFF";
1971
- fs5.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
1972
- tui.log.success(`\u2705 Created: ${action.path}`);
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 if (action.type === "modify_file") {
1976
- if (action.target_content) {
1977
- const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
1978
- if (success) {
1979
- executionResults += `[Action modify_file]: Success.
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
- specUpdated = true;
1982
- } else {
1983
- executionResults += `[Action modify_file]: Failed. Target content not found or ambiguous.
409
+ }
410
+ const val = await validateTypeScript(path2.resolve(projectRoot, filePath));
411
+ if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
412
+
1984
413
  `;
1985
- }
1986
- } else {
1987
- executionResults += `[Action modify_file]: Failed. 'target_content' is required.
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}]: Error: ${e.message}
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
- if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
1998
- const extraContext = executionResults ? `
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
- **VOC\xCA AGORA EST\xC1 NA FASE 2: INVESTIGA\xC7\xC3O**
2009
- - Use \`search_code\` e \`list_files\` para explorar os arquivos relevantes \xE0 tarefa.
2010
- - Prefira \`search_code\` em vez de \`read_file\` para buscar c\xF3digo sem inflar o contexto.
2011
- - N\xC3O leia o projeto inteiro de forma gen\xE9rica.
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
- **VOC\xCA AGORA EST\xC1 NA FASE 3: PREENCHIMENTO DO TEMPLATE**
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
- Continue seu trabalho na Fase ${currentPhase} ou emita "PHASE_COMPLETED" se terminou esta etapa atual.`;
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 (waitingForUser) {
2054
- const userReply = await tui.text({ message: "Your answer", placeholder: "Type your answer..." });
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 = userReply;
513
+ nextPrompt = `${executionResults}
514
+ User Reply: ${userReply}`;
2091
515
  } else {
2092
- keepGoing = false;
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 actions received.");
2097
- keepGoing = false;
526
+ tui.log.warning("No response received from agent.");
2098
527
  }
2099
- } catch (error) {
2100
- spinner.stop("Error");
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 callSpecAgentApi(prompt, onChunk, agentId) {
2107
- const realm = await getActiveRealm();
2108
- const token = await ensureValidToken(realm);
2109
- const conversationId = await conversationManager.getConversationId(AGENT_TYPE2);
2110
- const payload = {
2111
- user_prompt: prompt,
2112
- streaming: true,
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
- const parsed = parseAgentResponse(raw);
2141
- if (parsed.conversation_id) {
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 parsed;
548
+ return parsedResponse;
2145
549
  }
2146
550
 
2147
- // src/commands/scan.ts
2148
- import { Command as Command2 } from "commander";
2149
-
2150
- // src/core/agents/scan-agent.ts
2151
- import fs6 from "fs";
2152
- import path7 from "path";
2153
- var AGENT_TYPE3 = "scan_agent";
2154
- function getAgentId3() {
2155
- const config = ConfigManager.getInstance().getConfig();
2156
- if (config.agents?.scan) return config.agents.scan;
2157
- return process.env.STACKSPOT_SCAN_AGENT_ID || "01KEQ9AHWB550J2244YBH3QATN";
2158
- }
2159
- function getAgentVersion3() {
2160
- const config = ConfigManager.getInstance().getConfig();
2161
- if (config.agentVersions?.scan) return config.agentVersions.scan;
2162
- return process.env.STACKSPOT_SCAN_AGENT_VERSION;
2163
- }
2164
- async function interactiveScanAgent(options = {}) {
2165
- FileLogger.init();
2166
- const config = ConfigManager.getInstance().getConfig();
2167
- const language = config.language || "English";
2168
- tui.intro(t("commands.scan.intro"));
2169
- const projectRoot = process.cwd();
2170
- let outputFile;
2171
- if (options.output) {
2172
- outputFile = path7.resolve(process.cwd(), options.output);
2173
- } else {
2174
- const outputDir = path7.resolve(projectRoot, "_sharkrc");
2175
- if (!fs6.existsSync(outputDir)) {
2176
- const stat = fs6.existsSync(outputDir) ? fs6.statSync(outputDir) : null;
2177
- if (stat && stat.isFile()) {
2178
- tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
2179
- const fallbackDir = path7.resolve(projectRoot, "_bmad/project-context");
2180
- if (!fs6.existsSync(fallbackDir)) fs6.mkdirSync(fallbackDir, { recursive: true });
2181
- outputFile = path7.join(fallbackDir, "project-context.md");
2182
- } else {
2183
- fs6.mkdirSync(outputDir, { recursive: true });
2184
- outputFile = path7.join(outputDir, "project-context.md");
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
- tui.log.info(`${t("commands.scan.scanningProject")} ${colors.bold(projectRoot)}`);
2192
- tui.log.info(`${t("commands.scan.outputTarget")} ${colors.bold(outputFile)}`);
2193
- tui.log.info(`${t("commands.scan.language")} ${colors.bold(language)}`);
2194
- const configFileRelative = path7.relative(projectRoot, outputFile);
2195
- const initialTemplate = `# Project Context
2196
-
2197
- ## Overview
2198
- [TO BE ANALYZED]
2199
-
2200
- ## Tech Stack
2201
- [TO BE ANALYZED]
2202
-
2203
- ## Architecture
2204
- [TO BE ANALYZED]
2205
-
2206
- ## Directory Structure
2207
- [TO BE ANALYZED]
2208
-
2209
- ## Key Components
2210
- [TO BE ANALYZED]
2211
-
2212
- ## API / Interfaces
2213
- [TO BE ANALYZED]
2214
-
2215
- ## Data Layer
2216
- [TO BE ANALYZED]
2217
-
2218
- ## Configuration & Environment
2219
- [TO BE ANALYZED]
2220
-
2221
- ## Build & Development
2222
- [TO BE ANALYZED]
2223
-
2224
- ## Key Patterns & Conventions
2225
- [TO BE ANALYZED]
2226
- `;
2227
- if (!fs6.existsSync(outputFile)) {
2228
- const BOM = "\uFEFF";
2229
- fs6.writeFileSync(outputFile, BOM + initialTemplate, { encoding: "utf-8" });
2230
- tui.log.success(`${t("commands.scan.templateCreated")} ${outputFile}`);
2231
- } else {
2232
- tui.log.info(t("commands.scan.fileExists"));
2233
- }
2234
- const superPrompt = `
2235
- You are the **Scan Agent**, an expert software architect and analyst.
2236
- Your mission is to explore this project's codebase THOROUGHLY and generate a COMPREHENSIVE context file that will be used by other AI agents (specifically a Developer Agent) to understand how to work on this project.
2237
-
2238
- **IMPORTANT**: The file \`${configFileRelative}\` has already been created with a template structure. Your job is to FILL IN each section by analyzing the project.
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
- ### [Component Name 2]
2461
- - **Location**: \\\`path/to/component\\\`
2462
- - **Purpose**: [What it does]
2463
- - **Key Files**: [Important files]
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
- **DEPTH REQUIREMENT**:
2467
- - Read MULTIPLE files (5-10 minimum) to understand patterns
2468
- - Identify ALL major modules/components
2469
- - Document API routes, database schemas if applicable
2470
- - Note design patterns and architectural decisions
2471
- - List important dependencies with their purposes
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
- **RULES**:
2474
- - Create template FIRST (step 1)
2475
- - Update sections INCREMENTALLY (steps 2-7)
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
- } else if (action.type === "read_file") {
2513
- tui.log.info(t("commands.scan.readingFile").replace("{0}", colors.bold(action.path || "")));
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
- } else if (action.type === "search_file") {
2520
- tui.log.info(t("commands.scan.searching").replace("{0}", colors.bold(action.path || "")));
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
- } else if (action.type === "create_file" || action.type === "modify_file") {
2527
- const resolvedActionPath = path7.resolve(action.path || "");
2528
- const resolvedTargetPath = path7.resolve(targetPath);
2529
- let isTarget = resolvedActionPath === resolvedTargetPath;
2530
- if (!isTarget && path7.basename(action.path || "") === "project-context.md") {
2531
- tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path7.relative(process.cwd(), targetPath)));
2532
- isTarget = true;
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
- async function callScanAgentApi(prompt, onChunk) {
2618
- const realm = await getActiveRealm();
2619
- const token = await ensureValidToken(realm);
2620
- let conversationId = await conversationManager.getConversationId(AGENT_TYPE3);
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
- const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${getAgentId3()}/chat`;
2634
- let fullMsg = "";
2635
- let raw = {};
2636
- FileLogger.log("SCAN", "Calling API", { promptLength: prompt.length });
2637
- await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
2638
- onChunk: (c) => {
2639
- fullMsg += c;
2640
- onChunk(c);
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
- return parsed;
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
- // src/commands/scan.ts
2661
- var scanCommand = new Command2("scan").description("Analyze the project and generate context documentation").option("-o, --output <path>", "Output file path (default: _bmad/project-context/project-context.md)").option("--depth <level>", "Scan depth (quick, deep, exhaustive)", "quick").action(async (options) => {
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
- // src/commands/dev.ts
2671
- import { Command as Command3 } from "commander";
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
- // src/core/agents/developer-agent.ts
2674
- import fs7 from "fs";
2675
- import path8 from "path";
2676
- var AGENT_TYPE4 = "developer_agent";
2677
- async function validateTypeScript(filePath) {
2678
- try {
2679
- const result = await handleRunCommand(`npx tsc --noEmit --skipLibCheck ${filePath}`);
2680
- if (result.trim() === "" || !result.includes("error TS")) {
2681
- return { valid: true };
2682
- }
2683
- return { valid: false, error: result };
2684
- } catch (e) {
2685
- return { valid: false, error: e.message || "TypeScript validation failed" };
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
- const projectRoot = process.cwd();
2710
- let contextContent = "";
2711
- const defaultContextPath = path8.resolve(projectRoot, "_sharkrc", "project-context.md");
2712
- const specificContextPath = options.context ? path8.resolve(projectRoot, options.context) : defaultContextPath;
2713
- if (fs7.existsSync(specificContextPath)) {
2714
- try {
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
- basePrompt += `
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
- if (options.history) {
2731
- basePrompt += `
2732
-
2733
- --- PREVIOUS EXECUTION SUMMARY ---
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
- const spinner = tui.spinner();
2753
- let finalSummary = "";
2754
- let isTaskCompleted = false;
2755
- const conversationKey = options.taskId ? `dev_agent_${options.taskId}` : `dev_agent_${Date.now()}`;
2756
- let autoApprovals = {
2757
- files: false,
2758
- commands: false
2759
- };
2760
- while (keepGoing) {
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
- spinner.start("\u{1F988} Shark Dev working...");
2763
- const lastResponse = await callDevAgentApi(nextPrompt, (chunk) => {
2764
- }, conversationKey);
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
- for (const action of actions) {
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
- tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
2794
- console.log(action.content);
2795
- if (!isTaskCompleted) waitingForUser = true;
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 === "run_command") {
2817
- const cmd = action.command || "";
2818
- tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
2819
- let approved = autoApprovals.commands;
2820
- if (!approved) {
2821
- const choice = await tui.select({
2822
- message: `Execute: ${cmd}?`,
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
- const filePath = action.path || "";
2843
- tui.log.warning(`\u{1F4DD} ${action.type === "create_file" ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
2844
- let approved = autoApprovals.files;
2845
- if (!approved) {
2846
- const choice = await tui.select({
2847
- message: `Approve changes to ${filePath}?`,
2848
- options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
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 (approved) {
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 dir = path8.dirname(path8.resolve(projectRoot, filePath));
2858
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2859
- fs7.writeFileSync(path8.resolve(projectRoot, filePath), action.content || "", "utf-8");
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
- let success = false;
2865
- if (action.line_range) success = replaceLineRange(filePath, action.line_range[0], action.line_range[1], action.content || "", tui);
2866
- else if (action.target_content) success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
2867
- executionResults += success ? `[Action modify_file]: Success
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
- const val = await validateTypeScript(path8.resolve(projectRoot, filePath));
2874
- if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
2875
-
896
+ specUpdated = true;
897
+ } else {
898
+ executionResults += `[Action modify_file]: Failed. Target content not found or ambiguous.
2876
899
  `;
2877
- } else {
2878
- executionResults += `[Action ${action.type}]: User Denied.
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} Failed]: ${e.message}
2950
-
907
+ executionResults += `[Action ${action.type}]: Error: ${e.message}
2951
908
  `;
2952
- tui.log.error(`\u274C AST Action Error: ${e.message}`);
2953
909
  }
2954
- } else if (action.type === "search_ast") {
2955
- const result = await astGrepSearch(action.pattern || "", action.path || "", action.language || "typescript", tui);
2956
- executionResults += `[Action search_ast Result]:
2957
- ${result}
910
+ }
911
+ }
912
+ if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
913
+ const extraContext = executionResults ? `
2958
914
 
2959
- `;
2960
- } else if (action.type === "modify_ast") {
2961
- const success = await astGrepRewrite(action.pattern || "", action.fix || "", action.path || "", action.language || "typescript", tui);
2962
- executionResults += success ? `[Action modify_ast]: Success
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
- ` : `[Action modify_ast]: Failed
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
- if (waitingForUser) {
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
- nextPrompt = `${executionResults}
2977
- User Reply: ${userReply}`;
1011
+ FileLogger.log("USER_INPUT", "User response to specification agent (Message only)", { userReply });
1012
+ nextPrompt = userReply;
2978
1013
  } else {
2979
- nextPrompt = `${executionResults}
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 response received from agent.");
1018
+ tui.log.warning("No actions received.");
1019
+ keepGoing = false;
2990
1020
  }
2991
- } catch (e) {
2992
- tui.log.error(e.message);
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 callDevAgentApi(prompt, onChunk, conversationKey = AGENT_TYPE4) {
3001
- const realm = await getActiveRealm();
3002
- const token = await ensureValidToken(realm);
3003
- const conversationId = await conversationManager.getConversationId(conversationKey);
3004
- const payload = {
3005
- user_prompt: prompt,
3006
- streaming: true,
3007
- use_conversation: true,
3008
- conversation_id: conversationId,
3009
- stackspot_knowledge: false
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(conversationKey, parsed.conversation_id);
1041
+ await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
3037
1042
  }
3038
1043
  return parsed;
3039
1044
  }
3040
1045
 
3041
- // src/core/workflow/task-manager.ts
3042
- import fs8 from "fs";
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 V2");
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 interactiveDeveloperAgent({
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/qa.ts
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/core/agents/qa-agent.ts
3287
- import fs9 from "fs";
3288
- import path10 from "path";
3289
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3290
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3291
- var AGENT_TYPE5 = "qa_agent";
3292
- function getAgentId5() {
3293
- const config = ConfigManager.getInstance().getConfig();
3294
- if (config.agents?.qa) return config.agents.qa;
3295
- return process.env.STACKSPOT_QA_AGENT_ID || "01KEQFJZ3Q3JER11NH22HEZX9X";
3296
- }
3297
- function getAgentVersion5() {
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
- const existingConversationId = await conversationManager.getConversationId(AGENT_TYPE5);
3391
- await sseClient.streamAgentResponse(
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
- await mcpClient.close();
3509
- tui.outro("\u{1F988} Shark QA Session Ended");
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("Failed to run QA Agent:", error.message);
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 Command5();
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(qaCommand);
3534
- program.command("ba").description("Start Business Analyst Agent interactive session").option("--id <agent_id>", "Override Agent ID").action(async (options) => {
3535
- try {
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);