shark-ai 0.4.15 → 0.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/bin/shark.js +767 -3105
  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,1707 +188,148 @@ var initAction = async () => {
267
188
  };
268
189
  var initCommand = new Command("init").description("Initialize a new Shark project").action(initAction);
269
190
 
270
- // src/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]
1794
- - Language: [e.g. TypeScript]
1795
- - Framework: [e.g. Node.js / React]
1796
- - Database: [e.g. SQLite / PostgreSQL]
1797
- - Key Libraries: [Top 5 dependencies]
1798
-
1799
- ## 2. Architecture Overview
1800
- [TO BE ANALYZED]
1801
- [Brief description of architectural pattern]
1802
-
1803
- ## 3. Data Model
1804
- [TO BE ANALYZED]
1805
- [Schema/ERD definitions]
1806
-
1807
- ## 4. API / Interface Contracts
1808
- [TO BE ANALYZED]
1809
- [Main endpoints or CLI commands]
1810
-
1811
- ## 5. Implementation Steps
1812
- [TO BE FILLED - MUST BE CHECKBOXES]
1813
- `;
1814
- const projectName = path6.basename(projectRoot);
1815
- initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
1816
- const BOM = "\uFEFF";
1817
- fs5.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
1818
- tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
1819
- } else {
1820
- tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
1821
- }
1822
- let contextContent = "";
1823
- const contextPath = path6.resolve(projectRoot, "_sharkrc", "project-context.md");
1824
- if (fs5.existsSync(contextPath)) {
1825
- contextContent = fs5.readFileSync(contextPath, "utf-8");
1826
- tui.log.info(`\u{1F4D8} Context loaded.`);
1827
- }
1828
- let briefingContent = "";
1829
- if (options.briefingPath && fs5.existsSync(options.briefingPath)) {
1830
- briefingContent = fs5.readFileSync(options.briefingPath, "utf-8");
1831
- tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
1832
- } else {
1833
- const standardBriefing = path6.resolve(projectRoot, "_sharkrc", "briefing.md");
1834
- if (fs5.existsSync(standardBriefing)) {
1835
- briefingContent = fs5.readFileSync(standardBriefing, "utf-8");
1836
- tui.log.info(`\u{1F4C4} Briefing loaded.`);
1837
- }
1838
- }
1839
- let initialPrompt = `
1840
- Voc\xEA \xE9 o **Shark Spec**, um Arquiteto de Software S\xEAnior e Tech Lead.
1841
- Seu objetivo final \xE9 produzir uma especifica\xE7\xE3o t\xE9cnica precisa para a tarefa no arquivo \`_sharkrc/tech-spec.md\`.
1842
-
1843
- \u26A0\uFE0F O SEU WORKFLOW \xC9 GUIADO POR FASES.
1844
- N\xC3O TENTE ADIANTAR O TRABALHO (ex: investigar c\xF3digo ou preencher template agora).
1845
-
1846
- **VOC\xCA EST\xC1 NA FASE 1: ENTENDIMENTO DA TAREFA**
1847
- - Use \`talk_with_user\` para perguntar ao usu\xE1rio qual tarefa espec\xEDfica, funcionalidade ou bug ele precisa especificar.
1848
- - Confirme o escopo e os limites com o usu\xE1rio.
1849
- - Se o escopo estiver perfeitamente claro e confirmado (com o usu\xE1rio), emita "PHASE_COMPLETED" no campo "summary" do JSON para avan\xE7ar.
1850
-
1851
- IMPORTANTE: Toda a sua comunica\xE7\xE3o DEVE ser em Portugu\xEAs.
1852
- `;
1853
- if (briefingContent) {
1854
- initialPrompt += `
1855
- \u2139\uFE0F Um documento de briefing foi encontrado. Ele define parcialmente a tarefa para a Fase 1.
1856
- Confirme seu entendimento com o usu\xE1rio via \`talk_with_user\` antes de prosseguir para a Fase 2.
1857
-
1858
- --- BRIEFING ---
1859
- ${briefingContent}
1860
- ----------------
1861
- `;
1862
- } else {
1863
- initialPrompt += `
1864
- \u2139\uFE0F Nenhum documento de briefing foi encontrado. Inicie a Fase 1 imediatamente: use \`talk_with_user\` para perguntar ao usu\xE1rio o que precisa ser especificado.
1865
- `;
1866
- }
1867
- if (options.initialContext) {
1868
- initialPrompt += `
1869
- --- CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER/FEEDBACK) ---
1870
- ${options.initialContext}
1871
- -----------------------------------------------------
1872
- `;
1873
- }
1874
- if (contextContent) {
1875
- initialPrompt += `
1876
- \u2139\uFE0F O contexto do projeto est\xE1 dispon\xEDvel para refer\xEAncia. Use-o na Fase 2 para se alinhar com os padr\xF5es de arquitetura existentes, mas N\xC3O o use para preencher as se\xE7\xF5es de forma gen\xE9rica.
1877
-
1878
- --- PROJECT CONTEXT ---
1879
- ${contextContent}
1880
- -----------------------
1881
- `;
1882
- }
1883
- await runSpecLoop(initialPrompt.trim(), outputFile, options.agentId);
1884
- }
1885
- async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
1886
- let nextPrompt = initialMessage;
1887
- let keepGoing = true;
1888
- let stepCount = 0;
1889
- let currentPhase = 1;
1890
- const MAX_STEPS = 30;
1891
- while (keepGoing && stepCount < MAX_STEPS) {
1892
- stepCount++;
1893
- const spinner = tui.spinner();
1894
- spinner.start(`\u{1F3D7}\uFE0F Spec Agent working (Step ${stepCount}/${MAX_STEPS})...`);
1895
- let pendingSections = [];
1896
- if (fs5.existsSync(targetPath)) {
1897
- const content = fs5.readFileSync(targetPath, "utf-8");
1898
- if (content.includes("[TO BE ANALYZED]")) pendingSections.push("Analysis Sections (Stack, Arch, Data, API)");
1899
- if (content.includes("[TO BE FILLED")) pendingSections.push("Implementation Steps");
1900
- }
1901
- if (pendingSections.length === 0 && stepCount > 1) {
1902
- }
1903
- let responseText = "";
1904
- let lastResponse = null;
1905
- try {
1906
- lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
1907
- responseText += chunk;
1908
- }, overrideAgentId);
1909
- spinner.stop("Response received");
1910
- if (lastResponse && lastResponse.actions) {
1911
326
  let executionResults = "";
1912
327
  let waitingForUser = false;
1913
- let specUpdated = false;
1914
- if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
1915
- if (currentPhase === 1) {
1916
- currentPhase = 2;
1917
- tui.log.success(`\u2705 Fase 1 Conclu\xEDda. Iniciando Fase 2 (Investiga\xE7\xE3o).`);
1918
- nextPrompt = `[System Message]
1919
- Voc\xEA completou a FASE 1 com sucesso.
1920
-
1921
- **VOC\xCA AGORA EST\xC1 NA FASE 2: INVESTIGA\xC7\xC3O**
1922
- - Use \`search_code\` e \`list_files\` para explorar os arquivos relevantes \xE0 tarefa.
1923
- - Prefira \`search_code\` em vez de \`read_file\` para buscar c\xF3digo sem inflar o contexto.
1924
- - N\xC3O leia o projeto inteiro de forma gen\xE9rica.
1925
- - REGRA DE OURO (READ-FIRST): Voc\xEA N\xC3O PODE referenciar um arquivo na especifica\xE7\xE3o t\xE9cnica que n\xE3o tenha investigado nesta fase.
1926
- - Quando achar que possui toda a clareza t\xE9cnica sobre onde e o que deve ser feito no c\xF3digo, emita "PHASE_COMPLETED" no summary.`;
1927
- continue;
1928
- } else if (currentPhase === 2) {
1929
- currentPhase = 3;
1930
- tui.log.success(`\u2705 Fase 2 Conclu\xEDda. Iniciando Fase 3 (Preenchimento).`);
1931
- nextPrompt = `[System Message]
1932
- Voc\xEA completou a FASE 2 com sucesso.
1933
-
1934
- **VOC\xCA AGORA EST\xC1 NA FASE 3: PREENCHIMENTO DO TEMPLATE**
1935
- - Use \`modify_file\` no arquivo \`${targetPath}\` para substituir os placeholders pelo conte\xFAdo real levantado na fase de investiga\xE7\xE3o.
1936
- - As se\xE7\xF5es 1-4 devem descrever o contexto da TAREFA, e n\xE3o o projeto como um todo.
1937
- - Passos de Implementa\xE7\xE3o (Implementation Steps): APENAS checkboxes markdown: \`- [ ] [Verbo de A\xE7\xE3o] [O Que] em [Caminho Relativo]\`.
1938
- - Quando TODOS os placeholders ([TO BE ANALYZED] ou [TO BE FILLED]) forem substitu\xEDdos e o trabalho conclu\xEDdo, emita "SPEC_UPDATED: Complete" no summary para finalizar.`;
1939
- continue;
1940
- }
1941
- }
1942
- if (lastResponse.message && lastResponse.message.includes("SPEC_UPDATED:")) {
1943
- if (currentPhase < 3) {
1944
- tui.log.warning(`O agente tentou finalizar prematuramente. For\xE7ando retorno para a fase atual...`);
1945
- nextPrompt = `[System Error]: Voc\xEA tentou finalizar a especifica\xE7\xE3o prematuramente emitindo SPEC_UPDATED, mas ainda est\xE1 na Fase ${currentPhase}. Voc\xEA s\xF3 pode finalizar quando estiver na Fase 3.
1946
-
1947
- Continue seu trabalho na Fase ${currentPhase} ou emita "PHASE_COMPLETED" se terminou esta etapa atual.`;
1948
- continue;
1949
- }
1950
- const content = fs5.existsSync(targetPath) ? fs5.readFileSync(targetPath, "utf-8") : "";
1951
- if (content.includes("[TO BE")) {
1952
- const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
1953
- let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "algumas se\xE7\xF5es";
1954
- tui.log.warning(`O agente tentou concluir prematuramente, mas h\xE1 placeholders pendentes. For\xE7ando retorno...`);
1955
- nextPrompt = `[System Error]: A valida\xE7\xE3o falhou e o bloqueio autom\xE1tico foi acionado.
1956
- Voc\xEA tentou concluir a tarefa, mas o arquivo AINDA possui placeholders '[TO BE ANALYZED]' ou '[TO BE FILLED]'.
1957
- As seguintes se\xE7\xF5es ainda cont\xEAm estes placeholders: ${missing}.
1958
- Voc\xEA \xE9 OBRIGADO a usar a action \`modify_file\` para preencher o conte\xFAdo de cada uma dessas se\xE7\xF5es. Use o placeholder exato no campo \`target_content\`. N\xC3O repita a conclus\xE3o da tarefa at\xE9 corrigir todas as pend\xEAncias.`;
1959
- continue;
1960
- } else {
1961
- const updateSummary = lastResponse.message.split("SPEC_UPDATED:")[1].trim();
1962
- tui.log.success(`\u2705 Spec Finalized: ${updateSummary}`);
1963
- return;
1964
- }
1965
- }
1966
- for (const action of lastResponse.actions) {
328
+ for (const action of actions) {
1967
329
  if (action.type === "talk_with_user") {
1968
- tui.log.info(colors.primary("\u{1F916} Architect:"));
330
+ tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
1969
331
  console.log(action.content);
1970
- waitingForUser = true;
332
+ if (!isTaskCompleted) waitingForUser = true;
1971
333
  } else if (action.type === "list_files") {
1972
334
  tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
1973
335
  const result = handleListFiles(action.path || ".");
@@ -1982,811 +344,493 @@ ${result}
1982
344
  ${result}
1983
345
 
1984
346
  `;
1985
- } else if (action.type === "search_file") {
1986
- tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
1987
- const result = handleSearchFile(action.path || "");
1988
- executionResults += `[Action search_file(${action.path}) Result]:
1989
- ${result}
347
+ } else if (action.type === "search_file") {
348
+ const result = handleSearchFile(action.path || "");
349
+ executionResults += `[Action search_file(${action.path}) Result]:
350
+ ${result}
351
+
352
+ `;
353
+ } else if (action.type === "run_command") {
354
+ const cmd = action.command || "";
355
+ tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
356
+ let approved = autoApprovals.commands;
357
+ if (!approved) {
358
+ const choice = await tui.select({
359
+ message: `Execute: ${cmd}?`,
360
+ options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
361
+ });
362
+ if (choice === "always") {
363
+ autoApprovals.commands = true;
364
+ approved = true;
365
+ } else if (choice === "yes") approved = true;
366
+ }
367
+ if (approved) {
368
+ const result = await handleRunCommand(cmd);
369
+ executionResults += `[Action run_command(${cmd}) Result]:
370
+ ${result}
371
+
372
+ `;
373
+ } else {
374
+ executionResults += `[Action run_command]: User blocked execution.
375
+
376
+ `;
377
+ }
378
+ } else if (["create_file", "modify_file"].includes(action.type)) {
379
+ const filePath = action.path || "";
380
+ tui.log.warning(`\u{1F4DD} ${action.type === "create_file" ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
381
+ let approved = autoApprovals.files;
382
+ if (!approved) {
383
+ const choice = await tui.select({
384
+ message: `Approve changes to ${filePath}?`,
385
+ options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
386
+ });
387
+ if (choice === "always") {
388
+ autoApprovals.files = true;
389
+ approved = true;
390
+ } else if (choice === "yes") approved = true;
391
+ }
392
+ if (approved) {
393
+ if (action.type === "create_file") {
394
+ const dir = path2.dirname(path2.resolve(projectRoot, filePath));
395
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
396
+ fs2.writeFileSync(path2.resolve(projectRoot, filePath), action.content || "", "utf-8");
397
+ executionResults += `[Action create_file]: Success
398
+
399
+ `;
400
+ } else {
401
+ let success = false;
402
+ if (action.line_range) success = replaceLineRange(filePath, action.line_range[0], action.line_range[1], action.content || "", tui);
403
+ else if (action.target_content) success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
404
+ executionResults += success ? `[Action modify_file]: Success
405
+
406
+ ` : `[Action modify_file]: Failed
407
+
408
+ `;
409
+ }
410
+ const val = await validateTypeScript(path2.resolve(projectRoot, filePath));
411
+ if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
412
+
413
+ `;
414
+ } else {
415
+ executionResults += `[Action ${action.type}]: User Denied.
416
+
417
+ `;
418
+ }
419
+ } else if (action.type.startsWith("ast_")) {
420
+ try {
421
+ let result = "";
422
+ if (action.type === "ast_list_structure") {
423
+ result = await astListStructure(action.path || "");
424
+ } else if (action.type === "ast_get_method") {
425
+ result = await astGetMethod(action.path || "", action.class_name || "", action.method_name || "");
426
+ } else if (action.type === "ast_add_method") {
427
+ const success = await astAddMethod(action.path || "", action.class_name || "", action.method_code || "");
428
+ result = success ? "Method added successfully." : "Failed to add method.";
429
+ } else if (action.type === "ast_modify_method") {
430
+ const success = await astModifyMethod(action.path || "", action.class_name || "", action.method_name || "", action.new_body || "");
431
+ result = success ? "Method modified successfully." : "Failed to modify method.";
432
+ } else if (action.type === "ast_remove_method") {
433
+ const success = await astRemoveMethod(action.path || "", action.class_name || "", action.method_name || "");
434
+ result = success ? "Method removed successfully." : "Failed to remove method.";
435
+ } else if (action.type === "ast_add_class") {
436
+ const success = await astAddClass(action.path || "", action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
437
+ result = success ? "Class added successfully." : "Failed to add class.";
438
+ } else if (action.type === "ast_get_property") {
439
+ tui.log.info(`\u{1F50D} Reading property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
440
+ const propContent = await astGetProperty(action.path || "", action.class_name || "", action.property_name || "");
441
+ result = `[AST Get Property] Content:
442
+ ${propContent}`;
443
+ } else if (action.type === "ast_add_property") {
444
+ const success = await astAddProperty(action.path || "", action.class_name || "", action.property_code || "");
445
+ result = success ? "Property added successfully." : "Failed to add property.";
446
+ } else if (action.type === "ast_modify_property") {
447
+ tui.log.info(`\u270F\uFE0F Modifying property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
448
+ const success = await astModifyProperty(action.path || "", action.class_name || "", action.property_name || "", action.property_code || "");
449
+ result = success ? "Property modified successfully." : "Failed to modify property.";
450
+ } else if (action.type === "ast_remove_property") {
451
+ const success = await astRemoveProperty(action.path || "", action.class_name || "", action.property_name || "");
452
+ result = success ? "Property removed successfully." : "Failed to remove property.";
453
+ } else if (action.type === "ast_add_decorator") {
454
+ const success = await astAddDecorator(action.path || "", action.class_name || "", action.decorator_code || "");
455
+ result = success ? "Decorator added successfully." : "Failed to add decorator.";
456
+ } else if (action.type === "ast_add_interface") {
457
+ const success = await astAddInterface(action.path || "", action.interface_code || "");
458
+ result = success ? "Interface added successfully." : "Failed to add interface.";
459
+ } else if (action.type === "ast_add_type_alias") {
460
+ const success = await astAddTypeAlias(action.path || "", action.type_code || "");
461
+ result = success ? "Type alias added successfully." : "Failed to add type alias.";
462
+ } else if (action.type === "ast_add_function") {
463
+ const success = await astAddFunction(action.path || "", action.function_code || "");
464
+ result = success ? "Function added successfully." : "Failed to add function.";
465
+ } else if (action.type === "ast_remove_function") {
466
+ const success = await astRemoveFunction(action.path || "", action.function_name || "");
467
+ result = success ? "Function removed successfully." : "Failed to remove function.";
468
+ } else if (action.type === "ast_add_import") {
469
+ const success = await astAddImport(action.path || "", action.import_statement || "");
470
+ result = success ? "Import added successfully." : "Failed to add import.";
471
+ } else if (action.type === "ast_remove_import") {
472
+ const success = await astRemoveImport(action.path || "", action.module_path || "");
473
+ result = success ? "Import removed successfully." : "Failed to remove import.";
474
+ } else if (action.type === "ast_organize_imports") {
475
+ const success = await astOrganizeImports(action.path || "");
476
+ result = success ? "Imports organized successfully." : "Failed to organize imports.";
477
+ } else {
478
+ result = `Unknown AST action: ${action.type}`;
479
+ }
480
+ executionResults += `[Action ${action.type} Result]:
481
+ ${result}
482
+
483
+ `;
484
+ tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
485
+ } catch (e) {
486
+ executionResults += `[Action ${action.type} Failed]: ${e.message}
1990
487
 
1991
488
  `;
1992
- } else if (action.type === "search_code") {
1993
- const glob = action.path || "src/**/*";
1994
- const query = action.query || "";
1995
- const isRegex = action.is_regex === true;
1996
- tui.log.info(`\u{1F50E} Search code: ${colors.dim(`"${query}" in ${glob}`)}`);
1997
- const result = handleSearchCode(glob, query, isRegex);
1998
- executionResults += `[Action search_code("${query}" in "${glob}") Result]:
489
+ tui.log.error(`\u274C AST Action Error: ${e.message}`);
490
+ }
491
+ } else if (action.type === "search_ast") {
492
+ const result = await astGrepSearch(action.pattern || "", action.path || "", action.language || "typescript", tui);
493
+ executionResults += `[Action search_ast Result]:
1999
494
  ${result}
2000
495
 
2001
496
  `;
2002
- } else if (["create_file", "modify_file"].includes(action.type)) {
2003
- let actionPath = path6.resolve(action.path || "");
2004
- const resolvedTargetPath = path6.resolve(targetPath);
2005
- let isTarget = actionPath === resolvedTargetPath;
2006
- if (!isTarget && path6.basename(actionPath) === "tech-spec.md") {
2007
- tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${path6.relative(process.cwd(), targetPath)}`);
2008
- action.path = targetPath;
2009
- actionPath = resolvedTargetPath;
2010
- isTarget = true;
2011
- }
2012
- if (!isTarget && action.type === "create_file") {
2013
- const confirm = await tui.confirm({ message: `Agent wants to create ${action.path}. Allow?` });
2014
- if (!confirm) {
2015
- executionResults += `[Action create_file]: User denied.
2016
- `;
2017
- continue;
2018
- }
2019
- }
2020
- try {
2021
- if (action.type === "create_file") {
2022
- const BOM = "\uFEFF";
2023
- fs5.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
2024
- tui.log.success(`\u2705 Created: ${action.path}`);
2025
- executionResults += `[Action create_file]: Success.
2026
- `;
2027
- } else if (action.type === "modify_file") {
2028
- if (action.target_content) {
2029
- const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
2030
- if (success) {
2031
- executionResults += `[Action modify_file]: Success.
2032
- `;
2033
- specUpdated = true;
2034
- } else {
2035
- executionResults += `[Action modify_file]: Failed. Target content not found.
2036
- `;
2037
- }
2038
- } else {
2039
- executionResults += `[Action modify_file]: Failed. 'target_content' is required.
2040
- `;
2041
- }
2042
- }
2043
- } catch (e) {
2044
- executionResults += `[Action ${action.type}]: Error: ${e.message}
497
+ } else if (action.type === "modify_ast") {
498
+ const success = await astGrepRewrite(action.pattern || "", action.fix || "", action.path || "", action.language || "typescript", tui);
499
+ executionResults += success ? `[Action modify_ast]: Success
500
+
501
+ ` : `[Action modify_ast]: Failed
502
+
2045
503
  `;
2046
- }
2047
504
  }
2048
505
  }
2049
- if (waitingForUser) {
2050
- const userReply = await tui.text({ message: "Your answer", placeholder: "Type your answer..." });
2051
- if (tui.isCancel(userReply)) {
2052
- keepGoing = false;
2053
- return;
2054
- }
2055
- nextPrompt = `${executionResults}
2056
-
2057
- User Reply: ${userReply}`;
2058
- } else if (executionResults) {
2059
- const content = fs5.existsSync(targetPath) ? fs5.readFileSync(targetPath, "utf-8") : "";
2060
- let systemMsg = "Execu\xE7\xE3o da ferramenta conclu\xEDda.";
2061
- if (specUpdated) {
2062
- if (content.includes("[TO BE")) {
2063
- const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
2064
- let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "v\xE1rias se\xE7\xF5es";
2065
- systemMsg += `
2066
- [System]: Se\xE7\xE3o atualizada com sucesso. A valida\xE7\xE3o detectou que AINDA H\xC1 placeholders pendentes ('[TO BE...]') nas seguintes se\xE7\xF5es: ${missing}.
2067
- Por favor, envie uma nova action \`modify_file\` focada em uma destas se\xE7\xF5es obrigatoriamente. USE o respectivo placeholder no campo \`target_content\` para que o replace funcione.`;
2068
- } else {
2069
- systemMsg += "\n[System]: O arquivo parece completo! Se estiver satisfeito e possuir TODAS as implementa\xE7\xF5es descritas, retorne 'SPEC_UPDATED: Complete'.";
2070
- }
2071
- }
2072
- nextPrompt = `${executionResults}
2073
-
2074
- ${systemMsg}`;
2075
- } else {
2076
- if (lastResponse.message) {
2077
- tui.log.info(colors.primary("\u{1F916} Architect (Message only):"));
2078
- console.log(lastResponse.message);
506
+ if (executionResults) {
507
+ if (waitingForUser) {
2079
508
  const userReply = await tui.text({ message: "Your answer:" });
2080
509
  if (tui.isCancel(userReply)) {
2081
510
  keepGoing = false;
2082
511
  break;
2083
512
  }
2084
- nextPrompt = userReply;
513
+ nextPrompt = `${executionResults}
514
+ User Reply: ${userReply}`;
2085
515
  } else {
2086
- keepGoing = false;
516
+ nextPrompt = `${executionResults}
517
+ [System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
518
+ tui.log.info(colors.dim("Processing results..."));
2087
519
  }
520
+ } else if (!keepGoing) {
521
+ } else if (waitingForUser) {
522
+ } else {
523
+ if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
2088
524
  }
2089
525
  } else {
2090
- tui.log.warning("No actions received.");
2091
- keepGoing = false;
526
+ tui.log.warning("No response received from agent.");
2092
527
  }
2093
- } catch (error) {
2094
- spinner.stop("Error");
2095
- tui.log.error(error.message);
528
+ } catch (e) {
529
+ tui.log.error(e.message);
2096
530
  keepGoing = false;
531
+ return { success: false, summary: `Error: ${e.message}` };
2097
532
  }
2098
533
  }
534
+ tui.log.success("\u2705 Task Scope Completed");
535
+ return { success: true, summary: finalSummary || "Task completed without summary." };
2099
536
  }
2100
- async function callSpecAgentApi(prompt, onChunk, agentId) {
2101
- const realm = await getActiveRealm();
2102
- const token = await ensureValidToken(realm);
2103
- const conversationId = await conversationManager.getConversationId(AGENT_TYPE2);
2104
- const payload = {
2105
- user_prompt: prompt,
2106
- streaming: true,
2107
- stackspot_knowledge: false,
2108
- return_ks_in_response: true,
2109
- use_conversation: true,
2110
- conversation_id: conversationId
2111
- };
2112
- const agentVersion = getAgentVersion2();
2113
- if (agentVersion) {
2114
- payload.agent_version_number = agentVersion;
2115
- }
2116
- const effectiveAgentId = getAgentId2(agentId);
2117
- const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${effectiveAgentId}/chat`;
2118
- let fullMsg = "";
2119
- let raw = {};
2120
- FileLogger.log("AGENT", "Calling Agent API", { agentId: effectiveAgentId, conversationId });
2121
- await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
2122
- onChunk: (c) => {
2123
- fullMsg += c;
2124
- onChunk(c);
2125
- },
2126
- onComplete: (msg, metadata) => {
2127
- const returnedId = metadata?.conversation_id;
2128
- raw = { message: msg || fullMsg, conversation_id: returnedId || conversationId };
2129
- },
2130
- onError: (e) => {
2131
- throw e;
2132
- }
537
+ async function callDevAgentApi(prompt, onChunk, conversationKey = AGENT_TYPE) {
538
+ const existingConversationId = await conversationManager.getConversationId(conversationKey);
539
+ const provider = ProviderResolver.getProvider("developer_agent");
540
+ const parsedResponse = await provider.streamChat(prompt, {
541
+ conversationId: existingConversationId,
542
+ agentType: "developer_agent",
543
+ onChunk
2133
544
  });
2134
- const parsed = parseAgentResponse(raw);
2135
- if (parsed.conversation_id) {
2136
- await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
545
+ if (parsedResponse.conversation_id) {
546
+ await conversationManager.saveConversationId(conversationKey, parsedResponse.conversation_id);
2137
547
  }
2138
- return parsed;
548
+ return parsedResponse;
2139
549
  }
2140
550
 
2141
- // src/commands/scan.ts
2142
- import { Command as Command2 } from "commander";
2143
-
2144
- // src/core/agents/scan-agent.ts
2145
- import fs6 from "fs";
2146
- import path7 from "path";
2147
- var AGENT_TYPE3 = "scan_agent";
2148
- function getAgentId3() {
2149
- const config = ConfigManager.getInstance().getConfig();
2150
- if (config.agents?.scan) return config.agents.scan;
2151
- return process.env.STACKSPOT_SCAN_AGENT_ID || "01KEQ9AHWB550J2244YBH3QATN";
2152
- }
2153
- function getAgentVersion3() {
2154
- const config = ConfigManager.getInstance().getConfig();
2155
- if (config.agentVersions?.scan) return config.agentVersions.scan;
2156
- return process.env.STACKSPOT_SCAN_AGENT_VERSION;
2157
- }
2158
- async function interactiveScanAgent(options = {}) {
2159
- FileLogger.init();
2160
- const config = ConfigManager.getInstance().getConfig();
2161
- const language = config.language || "English";
2162
- tui.intro(t("commands.scan.intro"));
2163
- const projectRoot = process.cwd();
2164
- let outputFile;
2165
- if (options.output) {
2166
- outputFile = path7.resolve(process.cwd(), options.output);
2167
- } else {
2168
- const outputDir = path7.resolve(projectRoot, "_sharkrc");
2169
- if (!fs6.existsSync(outputDir)) {
2170
- const stat = fs6.existsSync(outputDir) ? fs6.statSync(outputDir) : null;
2171
- if (stat && stat.isFile()) {
2172
- tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
2173
- const fallbackDir = path7.resolve(projectRoot, "_bmad/project-context");
2174
- if (!fs6.existsSync(fallbackDir)) fs6.mkdirSync(fallbackDir, { recursive: true });
2175
- outputFile = path7.join(fallbackDir, "project-context.md");
2176
- } else {
2177
- fs6.mkdirSync(outputDir, { recursive: true });
2178
- 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";
2179
590
  }
2180
- } else {
2181
- fs6.mkdirSync(outputDir, { recursive: true });
2182
- outputFile = path7.join(outputDir, "project-context.md");
591
+ if (status2 && description) {
592
+ let j = i + 1;
593
+ while (j < lines.length) {
594
+ const nextLine = lines[j];
595
+ const nextTrimmed = nextLine.trim();
596
+ if (!nextTrimmed || nextTrimmed.match(/^- \[[ x\/]\]/) || nextTrimmed.startsWith("#")) {
597
+ break;
598
+ }
599
+ description += "\n" + nextTrimmed;
600
+ j++;
601
+ }
602
+ currentTask = {
603
+ id: `task-${taskIndex++}`,
604
+ description,
605
+ status: status2,
606
+ line_number: i
607
+ };
608
+ tasks.push(currentTask);
609
+ }
610
+ }
611
+ let nextTask = tasks.find((t) => t.status === "IN_PROGRESS");
612
+ if (!nextTask) {
613
+ nextTask = tasks.find((t) => t.status === "PENDING");
2183
614
  }
615
+ const status = !nextTask && tasks.length > 0 && tasks.every((t) => t.status === "COMPLETED") ? "COMPLETED" : "PENDING";
616
+ return {
617
+ status: tasks.length === 0 ? "MISSING" : status,
618
+ // Empty file is effectively "pending creation" but we treat as missing content logic elsewhere
619
+ nextTask,
620
+ allTasks: tasks
621
+ };
2184
622
  }
2185
- tui.log.info(`${t("commands.scan.scanningProject")} ${colors.bold(projectRoot)}`);
2186
- tui.log.info(`${t("commands.scan.outputTarget")} ${colors.bold(outputFile)}`);
2187
- tui.log.info(`${t("commands.scan.language")} ${colors.bold(language)}`);
2188
- const configFileRelative = path7.relative(projectRoot, outputFile);
2189
- const initialTemplate = `# Project Context
2190
-
2191
- ## Overview
2192
- [TO BE ANALYZED]
2193
-
2194
- ## Tech Stack
2195
- [TO BE ANALYZED]
2196
-
2197
- ## Architecture
2198
- [TO BE ANALYZED]
2199
-
2200
- ## Directory Structure
2201
- [TO BE ANALYZED]
623
+ /**
624
+ * Marks a specific task as COMPLETED in the file.
625
+ * Uses line-based replacement to be safe.
626
+ */
627
+ markTaskAsDone(taskId) {
628
+ const state = this.analyzeSpecState();
629
+ const task = state.allTasks.find((t) => t.id === taskId);
630
+ if (!task) {
631
+ console.error(`Task ${taskId} not found.`);
632
+ return false;
633
+ }
634
+ const content = fs3.readFileSync(this.specPath, "utf-8");
635
+ const lines = content.split("\n");
636
+ const targetLine = lines[task.line_number];
637
+ if (!targetLine.includes(task.description)) {
638
+ console.error(`Concurrency Error: Task line content mistmatch. Expected "${task.description}" at line ${task.line_number}.`);
639
+ return false;
640
+ }
641
+ const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
642
+ lines[task.line_number] = newLine;
643
+ fs3.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
644
+ return true;
645
+ }
646
+ /**
647
+ * Marks a task as IN_PROGRESS.
648
+ */
649
+ markTaskInProgress(taskId) {
650
+ const state = this.analyzeSpecState();
651
+ const task = state.allTasks.find((t) => t.id === taskId);
652
+ if (!task) return false;
653
+ const content = fs3.readFileSync(this.specPath, "utf-8");
654
+ const lines = content.split("\n");
655
+ let targetLine = lines[task.line_number];
656
+ targetLine = targetLine.replace("- [ ]", "- [/]");
657
+ lines[task.line_number] = targetLine;
658
+ fs3.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
659
+ return true;
660
+ }
661
+ /**
662
+ * Completely updates the spec file content (used by Spec Agent).
663
+ */
664
+ updateSpecContent(newContent) {
665
+ fs3.writeFileSync(this.specPath, newContent, "utf-8");
666
+ }
667
+ getSpecPath() {
668
+ return this.specPath;
669
+ }
670
+ };
2202
671
 
2203
- ## Key Components
2204
- [TO BE ANALYZED]
672
+ // src/core/agents/specification-agent.ts
673
+ import fs4 from "fs";
674
+ import path4 from "path";
675
+ var AGENT_TYPE2 = "specification_agent";
676
+ async function interactiveSpecificationAgent(options = {}) {
677
+ FileLogger.init();
678
+ tui.intro("\u{1F3D7}\uFE0F Specification Agent (Template-Based)");
679
+ const projectRoot = process.cwd();
680
+ const sharkRcDir = path4.resolve(projectRoot, "_sharkrc");
681
+ if (!fs4.existsSync(sharkRcDir)) fs4.mkdirSync(sharkRcDir, { recursive: true });
682
+ const outputFile = path4.resolve(sharkRcDir, "tech-spec.md");
683
+ if (!fs4.existsSync(outputFile)) {
684
+ let initialContent = `# Technical Specification: {{PROJECT_NAME}}
2205
685
 
2206
- ## API / Interfaces
2207
- [TO BE ANALYZED]
686
+ ## 1. Technology Stack
687
+ [TO BE ANALYZED - STACK]
688
+ - Language: [e.g. TypeScript]
689
+ - Framework: [e.g. Node.js / React]
690
+ - Database: [e.g. SQLite / PostgreSQL]
691
+ - Key Libraries: [Top 5 dependencies]
2208
692
 
2209
- ## Data Layer
2210
- [TO BE ANALYZED]
693
+ ## 2. Architecture Overview
694
+ [TO BE ANALYZED - ARCHITECTURE]
695
+ [Brief description of architectural pattern]
2211
696
 
2212
- ## Configuration & Environment
2213
- [TO BE ANALYZED]
697
+ ## 3. Data Model
698
+ [TO BE ANALYZED - DATA MODEL]
699
+ [Schema/ERD definitions]
2214
700
 
2215
- ## Build & Development
2216
- [TO BE ANALYZED]
701
+ ## 4. API / Interface Contracts
702
+ [TO BE ANALYZED - API]
703
+ [Main endpoints or CLI commands]
2217
704
 
2218
- ## Key Patterns & Conventions
2219
- [TO BE ANALYZED]
705
+ ## 5. Implementation Steps
706
+ [TO BE FILLED - MUST BE CHECKBOXES]
2220
707
  `;
2221
- if (!fs6.existsSync(outputFile)) {
708
+ const projectName = path4.basename(projectRoot);
709
+ initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
2222
710
  const BOM = "\uFEFF";
2223
- fs6.writeFileSync(outputFile, BOM + initialTemplate, { encoding: "utf-8" });
2224
- tui.log.success(`${t("commands.scan.templateCreated")} ${outputFile}`);
711
+ fs4.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
712
+ tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
2225
713
  } else {
2226
- tui.log.info(t("commands.scan.fileExists"));
714
+ tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
2227
715
  }
2228
- const superPrompt = `
2229
- You are the **Scan Agent**, an expert software architect and analyst.
2230
- 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.
2231
-
2232
- **IMPORTANT**: The file \`${configFileRelative}\` has already been created with a template structure. Your job is to FILL IN each section by analyzing the project.
2233
-
2234
- **LANGUAGE INSTRUCTION**:
2235
- You MUST write the content in **${language}**.
2236
-
2237
- **CRITICAL STRATEGY - INCREMENTAL UPDATES**:
2238
- DO NOT try to rewrite the entire file at once! Instead:
2239
-
2240
- 1. **Explore** the project using \`list_files\`, \`read_file\`, \`search_file\`
2241
- 2. **Update** specific sections incrementally using \`modify_file\`
2242
- 3. **Benefit**: Build a MUCH MORE DETAILED document without context size limitations
2243
-
2244
- **WORKFLOW - FILL EACH SECTION:**
2245
-
2246
- **Step 1 - Analyze Tech Stack:**
2247
- - \`list_files\` root directory to see project structure
2248
- - \`read_file\` package.json (or pom.xml, go.mod, requirements.txt, etc.)
2249
- - \`modify_file\` to replace:
2250
- - \`target_content\`: "## Tech Stack\\n[TO BE ANALYZED]"
2251
- - \`content\`: Detailed tech stack with versions, dependencies, and their purposes
2252
-
2253
- **Step 2 - Analyze Directory Structure:**
2254
- - \`list_files\` on ALL key directories (src, tests, config, docs, etc.)
2255
- - Map the complete directory tree
2256
- - \`modify_file\` to replace:
2257
- - \`target_content\`: "## Directory Structure\\n[TO BE ANALYZED]"
2258
- - \`content\`: Visual directory tree with purpose of each folder
2259
-
2260
- **Step 3 - Analyze Architecture:**
2261
- - \`read_file\` entry points (main.ts, index.js, app.py, etc.)
2262
- - \`read_file\` 5-10 source files to understand code patterns and organization
2263
- - Identify architectural pattern (Clean Arch, MVC, Microservices, etc.)
2264
- - \`modify_file\` to replace:
2265
- - \`target_content\`: "## Architecture\\n[TO BE ANALYZED]"
2266
- - \`content\`: Comprehensive architectural description with patterns and module organization
2267
-
2268
- **Step 4 - Document Components:**
2269
- - Identify ALL major modules/components by reading source files
2270
- - For each component: location, purpose, key files
2271
- - \`modify_file\` to replace:
2272
- - \`target_content\`: "## Key Components\\n[TO BE ANALYZED]"
2273
- - \`content\`: Detailed list of components with their purposes
2274
-
2275
- **Step 5 - Document APIs (if applicable):**
2276
- - Search for route definitions, controllers, API endpoints
2277
- - Document base URL, main endpoints, request/response patterns
2278
- - \`modify_file\` to replace:
2279
- - \`target_content\`: "## API / Interfaces\\n[TO BE ANALYZED]"
2280
- - \`content\`: API documentation (or "Not applicable" if no API)
2281
-
2282
- **Step 6 - Document Data Layer (if applicable):**
2283
- - Search for database configs, ORM setup, model definitions
2284
- - Document database type, ORM tool, key entities/tables
2285
- - \`modify_file\` to replace:
2286
- - \`target_content\`: "## Data Layer\\n[TO BE ANALYZED]"
2287
- - \`content\`: Data layer details (or "Not applicable" if no database)
2288
-
2289
- **Step 7 - Configuration & Environment:**
2290
- - Read config files (.env.example, config/, etc.)
2291
- - Identify environment variables and their purposes
2292
- - \`modify_file\` to replace:
2293
- - \`target_content\`: "## Configuration & Environment\\n[TO BE ANALYZED]"
2294
- - \`content\`: List of env vars and config files
2295
-
2296
- **Step 8 - Build & Development:**
2297
- - Read package.json scripts, Makefile, build configs
2298
- - Document dev, build, test, lint commands
2299
- - \`modify_file\` to replace:
2300
- - \`target_content\`: "## Build & Development\\n[TO BE ANALYZED]"
2301
- - \`content\`: Commands for development workflow
2302
-
2303
- **Step 9 - Patterns & Conventions:**
2304
- - Based on files read, document naming conventions, code organization
2305
- - Note error handling, logging strategies
2306
- - \`modify_file\` to replace:
2307
- - \`target_content\`: "## Key Patterns & Conventions\\n[TO BE ANALYZED]"
2308
- - \`content\`: Observed patterns and conventions
2309
-
2310
- **Step 10 - Overview (LAST):**
2311
- - Synthesize all findings into a comprehensive overview
2312
- - \`modify_file\` to replace:
2313
- - \`target_content\`: "## Overview\\n[TO BE ANALYZED]"
2314
- - \`content\`: Detailed project description with purpose and main functionality
2315
-
2316
- **HOW TO USE modify_file:**
2317
- \`\`\`json
2318
- {
2319
- "actions": [{
2320
- "type": "modify_file",
2321
- "path": "${configFileRelative}",
2322
- "target_content": "## Tech Stack\\n[TO BE ANALYZED]",
2323
- "content": "## Tech Stack\\n- **Language**: TypeScript 5.3\\n- **Runtime**: Node.js 20.x\\n..."
2324
- }]
2325
- }
2326
- \`\`\`
2327
-
2328
- \`\`\`markdown
2329
- # Project Context
2330
-
2331
- ## Overview
2332
- [TO BE ANALYZED]
2333
-
2334
- ## Tech Stack
2335
- [TO BE ANALYZED]
2336
-
2337
- ## Architecture
2338
- [TO BE ANALYZED]
2339
-
2340
- ## Directory Structure
2341
- [TO BE ANALYZED]
2342
-
2343
- ## Key Components
2344
- [TO BE ANALYZED]
2345
-
2346
- ## API / Interfaces
2347
- [TO BE ANALYZED]
2348
-
2349
- ## Data Layer
2350
- [TO BE ANALYZED]
2351
-
2352
- ## Configuration & Environment
2353
- [TO BE ANALYZED]
2354
-
2355
- ## Build & Development
2356
- [TO BE ANALYZED]
2357
-
2358
- ## Key Patterns & Conventions
2359
- [TO BE ANALYZED]
2360
- \`\`\`
2361
-
2362
- **Step 2 - Explore and Update Incrementally:**
2363
- After creating the template, perform these analyses and UPDATE each section:
2364
-
2365
- **2.1 - Analyze Tech Stack:**
2366
- - \`list_files\` root directory
2367
- - \`read_file\` package.json (or equivalent manifest)
2368
- - \`modify_file\` to replace "## Tech Stack\\n[TO BE ANALYZED]" with detailed findings
2369
-
2370
- **2.2 - Analyze Directory Structure:**
2371
- - \`list_files\` on key directories (src, tests, config, etc.)
2372
- - \`modify_file\` to replace "## Directory Structure\\n[TO BE ANALYZED]" with complete structure
2373
-
2374
- **2.3 - Analyze Architecture:**
2375
- - \`read_file\` entry points (main.ts, index.js, etc.)
2376
- - \`read_file\` 5-10 source files to understand patterns
2377
- - \`modify_file\` to replace "## Architecture\\n[TO BE ANALYZED]" with architectural insights
2378
-
2379
- **2.4 - Document Components:**
2380
- - Identify major modules/components
2381
- - \`modify_file\` to replace "## Key Components\\n[TO BE ANALYZED]" with component details
2382
-
2383
- **2.5 - Document APIs (if applicable):**
2384
- - Search for route definitions, controllers, API endpoints
2385
- - \`modify_file\` to replace "## API / Interfaces\\n[TO BE ANALYZED]"
2386
-
2387
- **2.6 - Document Data Layer (if applicable):**
2388
- - Search for database configs, ORM, models
2389
- - \`modify_file\` to replace "## Data Layer\\n[TO BE ANALYZED]"
2390
-
2391
- **2.7 - Final Touches:**
2392
- - Update remaining sections (Config, Build, Patterns)
2393
- - Ensure Overview is comprehensive
2394
-
2395
- **HOW TO USE modify_file:**
2396
- \`\`\`json
2397
- {
2398
- "actions": [{
2399
- "type": "modify_file",
2400
- "path": "${configFileRelative}",
2401
- "target_content": "## Tech Stack\\n[TO BE ANALYZED]",
2402
- "content": "## Tech Stack\\n- **Language**: TypeScript 5.3\\n- **Runtime**: Node.js 20.x\\n- **Framework**: Express 4.18\\n..."
2403
- }]
2404
- }
2405
- \`\`\`
2406
-
2407
- **SECTION TEMPLATES (For your updates):**
2408
-
2409
- **Tech Stack:**
2410
- \`\`\`markdown
2411
- ## Tech Stack
2412
- - **Language**: [name + version]
2413
- - **Runtime**: [name + version]
2414
- - **Framework**: [name + version]
2415
- - **Build Tool**: [name]
2416
- - **Testing**: [framework]
2417
- - **Key Dependencies**:
2418
- - [dep-name]: [purpose]
2419
- - [dep-name]: [purpose]
2420
- \`\`\`
2421
-
2422
- **Architecture:**
2423
- \`\`\`markdown
2424
- ## Architecture
2425
- [Comprehensive description]
2426
- - **Pattern**: [Clean Arch, MVC, etc.]
2427
- - **Module Organization**: [how modules are structured]
2428
- - **Layer Separation**: [controllers, services, repos]
2429
- - **Configuration**: [how config is managed]
2430
- \`\`\`
2431
-
2432
- **Directory Structure:**
2433
- \`\`\`markdown
2434
- ## Directory Structure
2435
- \\\`\\\`\\\`
2436
- /src
2437
- /core - [Purpose]
2438
- /commands - [Purpose]
2439
- /ui - [Purpose]
2440
- /tests - [Purpose]
2441
- /docs - [Purpose]
2442
- \\\`\\\`\\\`
2443
- \`\`\`
2444
-
2445
- **Key Components:**
2446
- \`\`\`markdown
2447
- ## Key Components
2448
-
2449
- ### [Component Name 1]
2450
- - **Location**: \\\`path/to/component\\\`
2451
- - **Purpose**: [What it does]
2452
- - **Key Files**: [Important files]
2453
-
2454
- ### [Component Name 2]
2455
- - **Location**: \\\`path/to/component\\\`
2456
- - **Purpose**: [What it does]
2457
- - **Key Files**: [Important files]
2458
- \`\`\`
2459
-
2460
- **DEPTH REQUIREMENT**:
2461
- - Read MULTIPLE files (5-10 minimum) to understand patterns
2462
- - Identify ALL major modules/components
2463
- - Document API routes, database schemas if applicable
2464
- - Note design patterns and architectural decisions
2465
- - List important dependencies with their purposes
716
+ let contextContent = "";
717
+ const contextPath = path4.resolve(projectRoot, "_sharkrc", "project-context.md");
718
+ if (fs4.existsSync(contextPath)) {
719
+ contextContent = fs4.readFileSync(contextPath, "utf-8");
720
+ tui.log.info(`\u{1F4D8} Context loaded.`);
721
+ }
722
+ let briefingContent = "";
723
+ if (options.briefingPath && fs4.existsSync(options.briefingPath)) {
724
+ briefingContent = fs4.readFileSync(options.briefingPath, "utf-8");
725
+ tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
726
+ } else {
727
+ const standardBriefing = path4.resolve(projectRoot, "_sharkrc", "briefing.md");
728
+ if (fs4.existsSync(standardBriefing)) {
729
+ briefingContent = fs4.readFileSync(standardBriefing, "utf-8");
730
+ tui.log.info(`\u{1F4C4} Briefing loaded.`);
731
+ }
732
+ }
733
+ let initialPrompt = `
734
+ Voc\xEA \xE9 o **Shark Spec**, um Arquiteto de Software S\xEAnior e Tech Lead.
735
+ Seu objetivo final \xE9 produzir uma especifica\xE7\xE3o t\xE9cnica precisa para a tarefa no arquivo \`_sharkrc/tech-spec.md\`.
2466
736
 
2467
- **RULES**:
2468
- - Create template FIRST (step 1)
2469
- - Update sections INCREMENTALLY (steps 2-7)
2470
- - Do NOT wait to write everything at the end
2471
- - Use \`modify_file\` to replace "[TO BE ANALYZED]" sections
2472
- - Be DETAILED and COMPREHENSIVE
2473
- - Take your time - you have up to 30 steps
2474
- - Verify facts with \`read_file\` or \`search_file\` before documenting
2475
- `.trim();
2476
- await runScanLoop(superPrompt, outputFile);
2477
- }
2478
- async function runScanLoop(initialPrompt, targetPath) {
2479
- let nextPrompt = initialPrompt;
2480
- let keepGoing = true;
2481
- let stepCount = 0;
2482
- const MAX_STEPS = 60;
2483
- while (keepGoing && stepCount < MAX_STEPS) {
2484
- stepCount++;
2485
- const spinner = tui.spinner();
2486
- const msg = t("commands.scan.analyzing").replace("{step}", stepCount.toString());
2487
- spinner.start(msg);
2488
- let responseText = "";
2489
- let lastResponse = null;
2490
- try {
2491
- lastResponse = await callScanAgentApi(nextPrompt, (chunk) => {
2492
- responseText += chunk;
2493
- });
2494
- spinner.stop(t("commands.scan.stepComplete"));
2495
- if (lastResponse && lastResponse.actions && lastResponse.actions.length > 0) {
2496
- let executionResults = "";
2497
- let fileCreated = false;
2498
- for (const action of lastResponse.actions) {
2499
- if (action.type === "list_files") {
2500
- tui.log.info(t("commands.scan.scanningDir").replace("{0}", colors.bold(action.path || ".")));
2501
- const result = handleListFiles(action.path || ".");
2502
- executionResults += `[Action list_files(${action.path}) Result]:
2503
- ${result}
737
+ \u26A0\uFE0F O SEU WORKFLOW \xC9 GUIADO POR FASES.
738
+ N\xC3O TENTE ADIANTAR O TRABALHO (ex: investigar c\xF3digo ou preencher template agora).
2504
739
 
2505
- `;
2506
- } else if (action.type === "read_file") {
2507
- tui.log.info(t("commands.scan.readingFile").replace("{0}", colors.bold(action.path || "")));
2508
- const result = handleReadFile(action.path || "");
2509
- executionResults += `[Action read_file(${action.path}) Result]:
2510
- ${result}
740
+ **VOC\xCA EST\xC1 NA FASE 1: ENTENDIMENTO DA TAREFA**
741
+ - Use \`talk_with_user\` para perguntar ao usu\xE1rio qual tarefa espec\xEDfica, funcionalidade ou bug ele precisa especificar.
742
+ - Confirme o escopo e os limites com o usu\xE1rio.
743
+ - Se o escopo estiver perfeitamente claro e confirmado (com o usu\xE1rio), emita "PHASE_COMPLETED" no campo "summary" do JSON para avan\xE7ar.
2511
744
 
745
+ IMPORTANTE: Toda a sua comunica\xE7\xE3o DEVE ser em Portugu\xEAs.
2512
746
  `;
2513
- } else if (action.type === "search_file") {
2514
- tui.log.info(t("commands.scan.searching").replace("{0}", colors.bold(action.path || "")));
2515
- const result = handleSearchFile(action.path || "");
2516
- executionResults += `[Action search_file(${action.path}) Result]:
2517
- ${result}
747
+ if (briefingContent) {
748
+ initialPrompt += `
749
+ \u2139\uFE0F Um documento de briefing foi encontrado. Ele define parcialmente a tarefa para a Fase 1.
750
+ Confirme seu entendimento com o usu\xE1rio via \`talk_with_user\` antes de prosseguir para a Fase 2.
2518
751
 
752
+ --- BRIEFING ---
753
+ ${briefingContent}
754
+ ----------------
2519
755
  `;
2520
- } else if (action.type === "create_file" || action.type === "modify_file") {
2521
- const resolvedActionPath = path7.resolve(action.path || "");
2522
- const resolvedTargetPath = path7.resolve(targetPath);
2523
- let isTarget = resolvedActionPath === resolvedTargetPath;
2524
- if (!isTarget && path7.basename(action.path || "") === "project-context.md") {
2525
- tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path7.relative(process.cwd(), targetPath)));
2526
- isTarget = true;
2527
- action.path = targetPath;
2528
- }
2529
- if (isTarget) {
2530
- const finalPath = targetPath;
2531
- const BOM = "\uFEFF";
2532
- if (action.type === "create_file") {
2533
- const contentToWrite = action.content || "";
2534
- const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
2535
- fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
2536
- tui.log.success(t("commands.scan.generated").replace("{0}", finalPath));
2537
- fileCreated = true;
2538
- } else {
2539
- if (fs6.existsSync(finalPath)) {
2540
- const currentContent = fs6.readFileSync(finalPath, "utf-8");
2541
- if (action.target_content && currentContent.includes(action.target_content)) {
2542
- const newContent = currentContent.replace(action.target_content, action.content || "");
2543
- const finalContent = newContent.startsWith(BOM) ? newContent : BOM + newContent;
2544
- fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
2545
- tui.log.success(t("commands.scan.updated").replace("{0}", finalPath));
2546
- fileCreated = true;
2547
- } else {
2548
- tui.log.warning(t("commands.scan.error") + ": " + t("commands.scan.contentNotFound"));
2549
- executionResults += `[Action ${action.type}]: Failed. Target content not found in file.
2550
- `;
2551
- fileCreated = false;
2552
- }
2553
- } else {
2554
- tui.log.warning(t("commands.scan.error") + ": " + t("commands.scan.notFound"));
2555
- }
2556
- }
2557
- executionResults += `[Action ${action.type}]: Success. Task Completed.
2558
- `;
2559
- } else {
2560
- tui.log.warning(t("commands.scan.error"));
2561
- executionResults += `[Action ${action.type}]: ${t("commands.scan.skipped")}
2562
- `;
2563
- }
2564
- } else if (action.type === "talk_with_user") {
2565
- tui.log.info(colors.primary(t("commands.scan.agentAsks")));
2566
- console.log(action.content);
2567
- const reply = await tui.text({ message: t("commands.scan.agentInput"), placeholder: t("commands.scan.replyPlaceholder") });
2568
- executionResults += `[User Reply]: ${reply}
756
+ } else {
757
+ initialPrompt += `
758
+ \u2139\uFE0F Nenhum documento de briefing foi encontrado. Inicie a Fase 1 imediatamente: use \`talk_with_user\` para perguntar ao usu\xE1rio o que precisa ser especificado.
2569
759
  `;
2570
- }
2571
- }
2572
- if (fileCreated) {
2573
- const currentContent = fs6.readFileSync(targetPath, "utf-8");
2574
- const pendingSections = [];
2575
- const lines = currentContent.split("\n");
2576
- let currentSection = "";
2577
- for (const line of lines) {
2578
- if (line.startsWith("## ")) {
2579
- currentSection = line.substring(3).trim();
2580
- }
2581
- if (line.includes("[TO BE ANALYZED]")) {
2582
- if (currentSection && !pendingSections.includes(currentSection)) {
2583
- pendingSections.push(currentSection);
2584
- }
2585
- }
2586
- }
2587
- const pendingMsg = pendingSections.length > 0 ? `
2588
-
2589
- [System Helper]: ${t("commands.scan.pendingSections").replace("{0}", pendingSections.join(", "))}` : `
2590
-
2591
- [System Helper]: ${t("commands.scan.allPopulated")}`;
2592
- nextPrompt = `${executionResults}
2593
-
2594
- [System]: File updated successfully.${pendingMsg} Please continue with the next step of the analysis and focus on the pending sections.`;
2595
- FileLogger.log("SCAN", "Section updated, continuing loop", { step: stepCount, pending: pendingSections.length });
2596
- } else {
2597
- nextPrompt = executionResults;
2598
- FileLogger.log("SCAN", "Auto-replying with results", { length: executionResults.length });
2599
- }
2600
- } else {
2601
- tui.log.success(t("commands.scan.completed"));
2602
- keepGoing = false;
2603
- }
2604
- } catch (error) {
2605
- spinner.stop(t("common.error"));
2606
- tui.log.error(error.message);
2607
- keepGoing = false;
2608
- }
2609
- }
2610
- }
2611
- async function callScanAgentApi(prompt, onChunk) {
2612
- const realm = await getActiveRealm();
2613
- const token = await ensureValidToken(realm);
2614
- let conversationId = await conversationManager.getConversationId(AGENT_TYPE3);
2615
- const payload = {
2616
- user_prompt: prompt,
2617
- streaming: true,
2618
- stackspot_knowledge: false,
2619
- return_ks_in_response: true,
2620
- use_conversation: true,
2621
- conversation_id: conversationId
2622
- };
2623
- const agentVersion = getAgentVersion3();
2624
- if (agentVersion) {
2625
- payload.agent_version_number = agentVersion;
2626
- }
2627
- const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${getAgentId3()}/chat`;
2628
- let fullMsg = "";
2629
- let raw = {};
2630
- FileLogger.log("SCAN", "Calling API", { promptLength: prompt.length });
2631
- await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
2632
- onChunk: (c) => {
2633
- fullMsg += c;
2634
- onChunk(c);
2635
- },
2636
- onComplete: (msg, metadata) => {
2637
- const returnedId = metadata?.conversation_id;
2638
- raw = {
2639
- message: msg || fullMsg,
2640
- conversation_id: returnedId || conversationId
2641
- };
2642
- },
2643
- onError: (e) => {
2644
- throw e;
2645
- }
2646
- });
2647
- const parsed = parseAgentResponse(raw);
2648
- if (parsed.conversation_id) {
2649
- await conversationManager.saveConversationId(AGENT_TYPE3, parsed.conversation_id);
2650
- }
2651
- return parsed;
2652
- }
2653
-
2654
- // src/commands/scan.ts
2655
- var scanCommand = new Command2("scan").description("Analyze the project and generate context documentation").option("-o, --output <path>", "Output file path (default: _bmad/project-context/project-context.md)").option("--depth <level>", "Scan depth (quick, deep, exhaustive)", "quick").action(async (options) => {
2656
- try {
2657
- await interactiveScanAgent(options);
2658
- } catch (error) {
2659
- console.error("Error during scan:", error.message);
2660
- process.exit(1);
2661
- }
2662
- });
2663
-
2664
- // src/commands/dev.ts
2665
- import { Command as Command3 } from "commander";
2666
-
2667
- // src/core/agents/developer-agent.ts
2668
- import fs7 from "fs";
2669
- import path8 from "path";
2670
- var AGENT_TYPE4 = "developer_agent";
2671
- async function validateTypeScript(filePath) {
2672
- try {
2673
- const result = await handleRunCommand(`npx tsc --noEmit --skipLibCheck ${filePath}`);
2674
- if (result.trim() === "" || !result.includes("error TS")) {
2675
- return { valid: true };
2676
- }
2677
- return { valid: false, error: result };
2678
- } catch (e) {
2679
- return { valid: false, error: e.message || "TypeScript validation failed" };
2680
- }
2681
- }
2682
- function getAgentId4(overrideId) {
2683
- if (overrideId) return overrideId;
2684
- const config = ConfigManager.getInstance().getConfig();
2685
- if (config.agents?.dev) return config.agents.dev;
2686
- if (process.env.STACKSPOT_DEV_AGENT_ID) return process.env.STACKSPOT_DEV_AGENT_ID;
2687
- return "01KEQCGJ65YENRA4QBXVN1YFFX";
2688
- }
2689
- function getAgentVersion4(overrideVersion) {
2690
- if (overrideVersion) return overrideVersion;
2691
- const config = ConfigManager.getInstance().getConfig();
2692
- if (config.agentVersions?.dev) return config.agentVersions.dev;
2693
- if (process.env.STACKSPOT_DEV_AGENT_VERSION) return process.env.STACKSPOT_DEV_AGENT_VERSION;
2694
- return void 0;
2695
- }
2696
- async function interactiveDeveloperAgent(options = {}) {
2697
- FileLogger.init();
2698
- const agentId = getAgentId4();
2699
- if (agentId === "PENDING_CONFIGURATION") {
2700
- tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
2701
- return { success: false, summary: "Missing configuration." };
2702
760
  }
2703
- const projectRoot = process.cwd();
2704
- let contextContent = "";
2705
- const defaultContextPath = path8.resolve(projectRoot, "_sharkrc", "project-context.md");
2706
- const specificContextPath = options.context ? path8.resolve(projectRoot, options.context) : defaultContextPath;
2707
- if (fs7.existsSync(specificContextPath)) {
2708
- try {
2709
- contextContent = fs7.readFileSync(specificContextPath, "utf-8");
2710
- } catch (e) {
2711
- tui.log.warning(`Failed to read context file: ${e}`);
2712
- }
761
+ if (options.initialContext) {
762
+ initialPrompt += `
763
+ --- CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER/FEEDBACK) ---
764
+ ${options.initialContext}
765
+ -----------------------------------------------------
766
+ `;
2713
767
  }
2714
- const currentTask = options.taskInstruction || "Analyze the project and fix pending issues.";
2715
- let basePrompt = ``;
2716
768
  if (contextContent) {
2717
- 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.
2718
771
 
2719
772
  --- PROJECT CONTEXT ---
2720
773
  ${contextContent}
2721
774
  -----------------------
2722
775
  `;
2723
776
  }
2724
- if (options.history) {
2725
- basePrompt += `
2726
-
2727
- --- PREVIOUS EXECUTION SUMMARY ---
2728
- ${options.history}
2729
- ----------------------------------
2730
- `;
2731
- }
2732
- basePrompt += `
2733
-
2734
- \u{1F7E2} EXECUTION MODE
2735
-
2736
- You are a highly skilled Developer Agent.
2737
- \u{1F449} **CURRENT TASK**: "${currentTask}"
2738
-
2739
- Your goal is to COMPLETE this specific task and then STOP.
2740
- 1. Implement the necessary changes.
2741
- 2. Verify (compile/test).
2742
- 3. **MANDATORY**: When you are confident the task is done, output a final message starting with "TASK_COMPLETED:" followed by a brief technical summary of what you did.
2743
- `;
2744
- let nextPrompt = basePrompt;
777
+ await runSpecLoop(initialPrompt.trim(), outputFile, options.agentId);
778
+ }
779
+ async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
780
+ let nextPrompt = initialMessage;
2745
781
  let keepGoing = true;
2746
- const spinner = tui.spinner();
2747
- let finalSummary = "";
2748
- let isTaskCompleted = false;
2749
- const conversationKey = options.taskId ? `dev_agent_${options.taskId}` : `dev_agent_${Date.now()}`;
2750
- let autoApprovals = {
2751
- files: false,
2752
- commands: false
2753
- };
2754
- 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;
2755
799
  try {
2756
- spinner.start("\u{1F988} Shark Dev working...");
2757
- const lastResponse = await callDevAgentApi(nextPrompt, (chunk) => {
2758
- }, conversationKey);
800
+ lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
801
+ responseText += chunk;
802
+ }, overrideAgentId);
2759
803
  spinner.stop("Response received");
2760
- if (lastResponse) {
2761
- const response = lastResponse;
2762
- const actions = response.actions || [];
2763
- if (response.message && response.message.includes("TASK_COMPLETED:")) {
2764
- isTaskCompleted = true;
2765
- finalSummary = response.message.split("TASK_COMPLETED:")[1].trim();
2766
- keepGoing = false;
2767
- }
2768
- if (response.message && response.message.includes("TASK_FAILED:")) {
2769
- const failureReason = response.message.split("TASK_FAILED:")[1].trim();
2770
- tui.log.error(`\u274C Agent reported task failure: ${failureReason}`);
2771
- return { success: false, summary: failureReason };
2772
- }
2773
- if (actions.length === 0 && response.message && !isTaskCompleted) {
2774
- tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
2775
- console.log(response.message);
2776
- const userReply = await tui.text({ message: "Your answer:" });
2777
- if (tui.isCancel(userReply)) {
2778
- keepGoing = false;
2779
- break;
2780
- }
2781
- nextPrompt = userReply;
2782
- }
804
+ if (lastResponse && lastResponse.actions) {
2783
805
  let executionResults = "";
2784
806
  let waitingForUser = false;
2785
- for (const action of actions) {
807
+ let specUpdated = false;
808
+ let hasSystemError = false;
809
+ let systemErrorContent = "";
810
+ for (const action of lastResponse.actions) {
2786
811
  if (action.type === "talk_with_user") {
2787
- tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
2788
- console.log(action.content);
2789
- 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
+ }
2790
834
  } else if (action.type === "list_files") {
2791
835
  tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
2792
836
  const result = handleListFiles(action.path || ".");
@@ -2802,359 +846,205 @@ ${result}
2802
846
 
2803
847
  `;
2804
848
  } else if (action.type === "search_file") {
849
+ tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
2805
850
  const result = handleSearchFile(action.path || "");
2806
851
  executionResults += `[Action search_file(${action.path}) Result]:
2807
852
  ${result}
2808
853
 
2809
854
  `;
2810
- } else if (action.type === "run_command") {
2811
- const cmd = action.command || "";
2812
- tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
2813
- let approved = autoApprovals.commands;
2814
- if (!approved) {
2815
- const choice = await tui.select({
2816
- message: `Execute: ${cmd}?`,
2817
- options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
2818
- });
2819
- if (choice === "always") {
2820
- autoApprovals.commands = true;
2821
- approved = true;
2822
- } else if (choice === "yes") approved = true;
2823
- }
2824
- if (approved) {
2825
- const result = await handleRunCommand(cmd);
2826
- executionResults += `[Action run_command(${cmd}) Result]:
855
+ } else if (action.type === "search_code") {
856
+ const glob = action.path || "src/**/*";
857
+ const query = action.query || "";
858
+ const isRegex = action.is_regex === true;
859
+ tui.log.info(`\u{1F50E} Search code: ${colors.dim(`"${query}" in ${glob}`)}`);
860
+ const result = handleSearchCode(glob, query, isRegex);
861
+ executionResults += `[Action search_code("${query}" in "${glob}") Result]:
2827
862
  ${result}
2828
863
 
2829
864
  `;
2830
- } else {
2831
- executionResults += `[Action run_command]: User blocked execution.
2832
-
2833
- `;
2834
- }
2835
865
  } else if (["create_file", "modify_file"].includes(action.type)) {
2836
- const filePath = action.path || "";
2837
- tui.log.warning(`\u{1F4DD} ${action.type === "create_file" ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
2838
- let approved = autoApprovals.files;
2839
- if (!approved) {
2840
- const choice = await tui.select({
2841
- message: `Approve changes to ${filePath}?`,
2842
- options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
2843
- });
2844
- if (choice === "always") {
2845
- autoApprovals.files = true;
2846
- approved = true;
2847
- } else if (choice === "yes") approved = true;
866
+ let actionPath = path4.resolve(action.path || "");
867
+ const resolvedTargetPath = path4.resolve(targetPath);
868
+ let isTarget = actionPath === resolvedTargetPath;
869
+ if (!isTarget && path4.basename(actionPath) === "tech-spec.md") {
870
+ tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${path4.relative(process.cwd(), targetPath)}`);
871
+ action.path = targetPath;
872
+ actionPath = resolvedTargetPath;
873
+ isTarget = true;
2848
874
  }
2849
- if (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 {
2850
884
  if (action.type === "create_file") {
2851
- const dir = path8.dirname(path8.resolve(projectRoot, filePath));
2852
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2853
- fs7.writeFileSync(path8.resolve(projectRoot, filePath), action.content || "", "utf-8");
2854
- executionResults += `[Action create_file]: Success
2855
-
885
+ const BOM = "\uFEFF";
886
+ fs4.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
887
+ tui.log.success(`\u2705 Created: ${action.path}`);
888
+ executionResults += `[Action create_file]: Success.
2856
889
  `;
2857
- } else {
2858
- let success = false;
2859
- if (action.line_range) success = replaceLineRange(filePath, action.line_range[0], action.line_range[1], action.content || "", tui);
2860
- else if (action.target_content) success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
2861
- executionResults += success ? `[Action modify_file]: Success
2862
-
2863
- ` : `[Action modify_file]: Failed
2864
-
890
+ } else if (action.type === "modify_file") {
891
+ if (action.target_content) {
892
+ const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
893
+ if (success) {
894
+ executionResults += `[Action modify_file]: Success.
2865
895
  `;
2866
- }
2867
- const val = await validateTypeScript(path8.resolve(projectRoot, filePath));
2868
- if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
2869
-
896
+ specUpdated = true;
897
+ } else {
898
+ executionResults += `[Action modify_file]: Failed. Target content not found or ambiguous.
2870
899
  `;
2871
- } else {
2872
- executionResults += `[Action ${action.type}]: User Denied.
2873
-
900
+ }
901
+ } else {
902
+ executionResults += `[Action modify_file]: Failed. 'target_content' is required.
2874
903
  `;
2875
- }
2876
- } else if (action.type.startsWith("ast_")) {
2877
- try {
2878
- let result = "";
2879
- if (action.type === "ast_list_structure") {
2880
- result = await astListStructure(action.path || "");
2881
- } else if (action.type === "ast_get_method") {
2882
- result = await astGetMethod(action.path || "", action.class_name || "", action.method_name || "");
2883
- } else if (action.type === "ast_add_method") {
2884
- const success = await astAddMethod(action.path || "", action.class_name || "", action.method_code || "");
2885
- result = success ? "Method added successfully." : "Failed to add method.";
2886
- } else if (action.type === "ast_modify_method") {
2887
- const success = await astModifyMethod(action.path || "", action.class_name || "", action.method_name || "", action.new_body || "");
2888
- result = success ? "Method modified successfully." : "Failed to modify method.";
2889
- } else if (action.type === "ast_remove_method") {
2890
- const success = await astRemoveMethod(action.path || "", action.class_name || "", action.method_name || "");
2891
- result = success ? "Method removed successfully." : "Failed to remove method.";
2892
- } else if (action.type === "ast_add_class") {
2893
- const success = await astAddClass(action.path || "", action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
2894
- result = success ? "Class added successfully." : "Failed to add class.";
2895
- } else if (action.type === "ast_get_property") {
2896
- tui.log.info(`\u{1F50D} Reading property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
2897
- const propContent = await astGetProperty(action.path || "", action.class_name || "", action.property_name || "");
2898
- result = `[AST Get Property] Content:
2899
- ${propContent}`;
2900
- } else if (action.type === "ast_add_property") {
2901
- const success = await astAddProperty(action.path || "", action.class_name || "", action.property_code || "");
2902
- result = success ? "Property added successfully." : "Failed to add property.";
2903
- } else if (action.type === "ast_modify_property") {
2904
- tui.log.info(`\u270F\uFE0F Modifying property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
2905
- const success = await astModifyProperty(action.path || "", action.class_name || "", action.property_name || "", action.property_code || "");
2906
- result = success ? "Property modified successfully." : "Failed to modify property.";
2907
- } else if (action.type === "ast_remove_property") {
2908
- const success = await astRemoveProperty(action.path || "", action.class_name || "", action.property_name || "");
2909
- result = success ? "Property removed successfully." : "Failed to remove property.";
2910
- } else if (action.type === "ast_add_decorator") {
2911
- const success = await astAddDecorator(action.path || "", action.class_name || "", action.decorator_code || "");
2912
- result = success ? "Decorator added successfully." : "Failed to add decorator.";
2913
- } else if (action.type === "ast_add_interface") {
2914
- const success = await astAddInterface(action.path || "", action.interface_code || "");
2915
- result = success ? "Interface added successfully." : "Failed to add interface.";
2916
- } else if (action.type === "ast_add_type_alias") {
2917
- const success = await astAddTypeAlias(action.path || "", action.type_code || "");
2918
- result = success ? "Type alias added successfully." : "Failed to add type alias.";
2919
- } else if (action.type === "ast_add_function") {
2920
- const success = await astAddFunction(action.path || "", action.function_code || "");
2921
- result = success ? "Function added successfully." : "Failed to add function.";
2922
- } else if (action.type === "ast_remove_function") {
2923
- const success = await astRemoveFunction(action.path || "", action.function_name || "");
2924
- result = success ? "Function removed successfully." : "Failed to remove function.";
2925
- } else if (action.type === "ast_add_import") {
2926
- const success = await astAddImport(action.path || "", action.import_statement || "");
2927
- result = success ? "Import added successfully." : "Failed to add import.";
2928
- } else if (action.type === "ast_remove_import") {
2929
- const success = await astRemoveImport(action.path || "", action.module_path || "");
2930
- result = success ? "Import removed successfully." : "Failed to remove import.";
2931
- } else if (action.type === "ast_organize_imports") {
2932
- const success = await astOrganizeImports(action.path || "");
2933
- result = success ? "Imports organized successfully." : "Failed to organize imports.";
2934
- } else {
2935
- result = `Unknown AST action: ${action.type}`;
904
+ }
2936
905
  }
2937
- executionResults += `[Action ${action.type} Result]:
2938
- ${result}
2939
-
2940
- `;
2941
- tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
2942
906
  } catch (e) {
2943
- executionResults += `[Action ${action.type} Failed]: ${e.message}
2944
-
907
+ executionResults += `[Action ${action.type}]: Error: ${e.message}
2945
908
  `;
2946
- tui.log.error(`\u274C AST Action Error: ${e.message}`);
2947
909
  }
2948
- } else if (action.type === "search_ast") {
2949
- const result = await astGrepSearch(action.pattern || "", action.path || "", action.language || "typescript", tui);
2950
- executionResults += `[Action search_ast Result]:
2951
- ${result}
910
+ }
911
+ }
912
+ if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
913
+ const extraContext = executionResults ? `
2952
914
 
2953
- `;
2954
- } else if (action.type === "modify_ast") {
2955
- const success = await astGrepRewrite(action.pattern || "", action.fix || "", action.path || "", action.language || "typescript", tui);
2956
- 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.
2957
922
 
2958
- ` : `[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.
2959
935
 
2960
- `;
936
+ **VOC\xCA AGORA EST\xC1 NA FASE 3: PREENCHIMENTO DO TEMPLATE**
937
+ - Use \`modify_file\` no arquivo \`${targetPath}\` para substituir os placeholders pelo conte\xFAdo real levantado na fase de investiga\xE7\xE3o.
938
+ - As se\xE7\xF5es 1-4 devem descrever o contexto da TAREFA, e n\xE3o o projeto como um todo.
939
+ - Passos de Implementa\xE7\xE3o (Implementation Steps): APENAS checkboxes markdown: \`- [ ] [Verbo de A\xE7\xE3o] [O Que] em [Caminho Relativo]\`.
940
+ - Quando TODOS os placeholders ([TO BE ANALYZED...] ou [TO BE FILLED]) forem substitu\xEDdos e o trabalho conclu\xEDdo, emita "SPEC_UPDATED: Complete" no summary para finalizar.${extraContext}`;
941
+ continue;
942
+ }
943
+ }
944
+ if (lastResponse.message && lastResponse.message.includes("SPEC_UPDATED:")) {
945
+ if (currentPhase < 3) {
946
+ tui.log.warning(`O agente tentou finalizar prematuramente. For\xE7ando retorno para a fase atual...`);
947
+ nextPrompt = `[System Error]: Voc\xEA tentou finalizar a especifica\xE7\xE3o prematuramente emitindo SPEC_UPDATED, mas ainda est\xE1 na Fase ${currentPhase}. Voc\xEA s\xF3 pode finalizar quando estiver na Fase 3.
948
+
949
+ Continue seu trabalho na Fase ${currentPhase} ou emita "PHASE_COMPLETED" se terminou esta etapa atual.`;
950
+ continue;
951
+ }
952
+ const content = fs4.existsSync(targetPath) ? fs4.readFileSync(targetPath, "utf-8") : "";
953
+ if (content.includes("[TO BE")) {
954
+ const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
955
+ let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "algumas se\xE7\xF5es";
956
+ tui.log.warning(`O agente tentou concluir prematuramente, mas h\xE1 placeholders pendentes. For\xE7ando retorno...`);
957
+ nextPrompt = `[System Error]: A valida\xE7\xE3o falhou e o bloqueio autom\xE1tico foi acionado.
958
+ Voc\xEA tentou concluir a tarefa, mas o arquivo AINDA possui placeholders '[TO BE ANALYZED...]' ou '[TO BE FILLED]'.
959
+ As seguintes se\xE7\xF5es ainda cont\xEAm estes placeholders: ${missing}.
960
+ Voc\xEA \xE9 OBRIGADO a usar a action \`modify_file\` para preencher o conte\xFAdo de cada uma dessas se\xE7\xF5es. Use o placeholder exato no campo \`target_content\`. N\xC3O repita a conclus\xE3o da tarefa at\xE9 corrigir todas as pend\xEAncias.`;
961
+ continue;
962
+ } else {
963
+ const updateSummary = lastResponse.message.split("SPEC_UPDATED:")[1].trim();
964
+ tui.log.success(`\u2705 Spec Finalized: ${updateSummary}`);
965
+ return;
2961
966
  }
2962
967
  }
2963
968
  if (executionResults) {
2964
- 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);
2965
1006
  const userReply = await tui.text({ message: "Your answer:" });
2966
1007
  if (tui.isCancel(userReply)) {
2967
1008
  keepGoing = false;
2968
1009
  break;
2969
1010
  }
2970
- nextPrompt = `${executionResults}
2971
- User Reply: ${userReply}`;
1011
+ FileLogger.log("USER_INPUT", "User response to specification agent (Message only)", { userReply });
1012
+ nextPrompt = userReply;
2972
1013
  } else {
2973
- nextPrompt = `${executionResults}
2974
- [System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
2975
- tui.log.info(colors.dim("Processing results..."));
1014
+ keepGoing = false;
2976
1015
  }
2977
- } else if (!keepGoing) {
2978
- } else if (waitingForUser) {
2979
- } else {
2980
- if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
2981
1016
  }
2982
1017
  } else {
2983
- tui.log.warning("No response received from agent.");
1018
+ tui.log.warning("No actions received.");
1019
+ keepGoing = false;
2984
1020
  }
2985
- } catch (e) {
2986
- tui.log.error(e.message);
1021
+ } catch (error) {
1022
+ spinner.stop("Error");
1023
+ tui.log.error(error.message);
2987
1024
  keepGoing = false;
2988
- return { success: false, summary: `Error: ${e.message}` };
2989
1025
  }
2990
1026
  }
2991
- tui.log.success("\u2705 Task Scope Completed");
2992
- return { success: true, summary: finalSummary || "Task completed without summary." };
2993
1027
  }
2994
- async function callDevAgentApi(prompt, onChunk, conversationKey = AGENT_TYPE4) {
2995
- const realm = await getActiveRealm();
2996
- const token = await ensureValidToken(realm);
2997
- const conversationId = await conversationManager.getConversationId(conversationKey);
2998
- const payload = {
2999
- user_prompt: prompt,
3000
- streaming: true,
3001
- use_conversation: true,
3002
- conversation_id: conversationId,
3003
- stackspot_knowledge: false
3004
- };
3005
- const agentVersion = getAgentVersion4();
3006
- if (agentVersion) {
3007
- payload.agent_version_number = agentVersion;
3008
- }
3009
- const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${getAgentId4()}/chat`;
3010
- let fullMsg = "";
3011
- let raw = {};
3012
- await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
3013
- onChunk: (c) => {
3014
- fullMsg += c;
3015
- onChunk(c);
3016
- },
3017
- onComplete: (msg, metadata) => {
3018
- const returnedId = metadata?.conversation_id;
3019
- raw = {
3020
- message: msg || fullMsg,
3021
- conversation_id: returnedId || conversationId
3022
- };
3023
- },
3024
- onError: (e) => {
3025
- throw e;
3026
- }
1028
+ async function callSpecAgentApi(prompt, onChunk, agentId) {
1029
+ const conversationId = await conversationManager.getConversationId(AGENT_TYPE2);
1030
+ FileLogger.log("AGENT", "Calling Agent API", { agentId, conversationId });
1031
+ const provider = ProviderResolver.getProvider("specification_agent");
1032
+ if (agentId && "agentId" in provider) {
1033
+ provider.agentId = agentId;
1034
+ }
1035
+ const parsed = await provider.streamChat(prompt, {
1036
+ conversationId,
1037
+ agentType: "specification_agent",
1038
+ onChunk
3027
1039
  });
3028
- const parsed = parseAgentResponse(raw);
3029
1040
  if (parsed.conversation_id) {
3030
- await conversationManager.saveConversationId(conversationKey, parsed.conversation_id);
1041
+ await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
3031
1042
  }
3032
1043
  return parsed;
3033
1044
  }
3034
1045
 
3035
- // src/core/workflow/task-manager.ts
3036
- import fs8 from "fs";
3037
- import path9 from "path";
3038
- var TaskManager = class {
3039
- projectRoot;
3040
- specPath;
3041
- constructor(projectRoot = process.cwd()) {
3042
- this.projectRoot = projectRoot;
3043
- this.specPath = path9.resolve(this.projectRoot, "_sharkrc", "tech-spec.md");
3044
- }
3045
- /**
3046
- * Reads the tech-spec.md file and analyzes its current state.
3047
- */
3048
- analyzeSpecState() {
3049
- if (!fs8.existsSync(this.specPath)) {
3050
- return { status: "MISSING", allTasks: [] };
3051
- }
3052
- const content = fs8.readFileSync(this.specPath, "utf-8");
3053
- const lines = content.split("\n");
3054
- const tasks = [];
3055
- let taskIndex = 1;
3056
- for (let i = 0; i < lines.length; i++) {
3057
- const line = lines[i];
3058
- const trimmed = line.trim();
3059
- const pendingMatch = trimmed.match(/^- \[ \] (.*)/);
3060
- const completedMatch = trimmed.match(/^- \[x\] (.*)/i);
3061
- const progressMatch = trimmed.match(/^- \[\/\] (.*)/);
3062
- let currentTask = null;
3063
- let status2 = null;
3064
- let description = "";
3065
- if (pendingMatch) {
3066
- description = pendingMatch[1].trim();
3067
- status2 = "PENDING";
3068
- } else if (completedMatch) {
3069
- description = completedMatch[1].trim();
3070
- status2 = "COMPLETED";
3071
- } else if (progressMatch) {
3072
- description = progressMatch[1].trim();
3073
- status2 = "IN_PROGRESS";
3074
- }
3075
- if (status2 && description) {
3076
- let j = i + 1;
3077
- while (j < lines.length) {
3078
- const nextLine = lines[j];
3079
- const nextTrimmed = nextLine.trim();
3080
- if (!nextTrimmed || nextTrimmed.match(/^- \[[ x\/]\]/) || nextTrimmed.startsWith("#")) {
3081
- break;
3082
- }
3083
- description += "\n" + nextTrimmed;
3084
- j++;
3085
- }
3086
- currentTask = {
3087
- id: `task-${taskIndex++}`,
3088
- description,
3089
- status: status2,
3090
- line_number: i
3091
- };
3092
- tasks.push(currentTask);
3093
- }
3094
- }
3095
- let nextTask = tasks.find((t2) => t2.status === "IN_PROGRESS");
3096
- if (!nextTask) {
3097
- nextTask = tasks.find((t2) => t2.status === "PENDING");
3098
- }
3099
- const status = !nextTask && tasks.length > 0 && tasks.every((t2) => t2.status === "COMPLETED") ? "COMPLETED" : "PENDING";
3100
- return {
3101
- status: tasks.length === 0 ? "MISSING" : status,
3102
- // Empty file is effectively "pending creation" but we treat as missing content logic elsewhere
3103
- nextTask,
3104
- allTasks: tasks
3105
- };
3106
- }
3107
- /**
3108
- * Marks a specific task as COMPLETED in the file.
3109
- * Uses line-based replacement to be safe.
3110
- */
3111
- markTaskAsDone(taskId) {
3112
- const state = this.analyzeSpecState();
3113
- const task = state.allTasks.find((t2) => t2.id === taskId);
3114
- if (!task) {
3115
- console.error(`Task ${taskId} not found.`);
3116
- return false;
3117
- }
3118
- const content = fs8.readFileSync(this.specPath, "utf-8");
3119
- const lines = content.split("\n");
3120
- const targetLine = lines[task.line_number];
3121
- if (!targetLine.includes(task.description)) {
3122
- console.error(`Concurrency Error: Task line content mistmatch. Expected "${task.description}" at line ${task.line_number}.`);
3123
- return false;
3124
- }
3125
- const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
3126
- lines[task.line_number] = newLine;
3127
- fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
3128
- return true;
3129
- }
3130
- /**
3131
- * Marks a task as IN_PROGRESS.
3132
- */
3133
- markTaskInProgress(taskId) {
3134
- const state = this.analyzeSpecState();
3135
- const task = state.allTasks.find((t2) => t2.id === taskId);
3136
- if (!task) return false;
3137
- const content = fs8.readFileSync(this.specPath, "utf-8");
3138
- const lines = content.split("\n");
3139
- let targetLine = lines[task.line_number];
3140
- targetLine = targetLine.replace("- [ ]", "- [/]");
3141
- lines[task.line_number] = targetLine;
3142
- fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
3143
- return true;
3144
- }
3145
- /**
3146
- * Completely updates the spec file content (used by Spec Agent).
3147
- */
3148
- updateSpecContent(newContent) {
3149
- fs8.writeFileSync(this.specPath, newContent, "utf-8");
3150
- }
3151
- getSpecPath() {
3152
- return this.specPath;
3153
- }
3154
- };
3155
-
3156
- // src/commands/dev.ts
3157
- var devCommand = new Command3("dev").description("Starts the Shark Developer Agent (Shark Dev Orchestration V2)").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").action(async (options) => {
1046
+ // src/commands/legacy.ts
1047
+ var legacyCommand = new Command3("legacy").description("Starts the Legacy Developer Agent task orchestration loop").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").action(async (options) => {
3158
1048
  const taskManager = new TaskManager(process.cwd());
3159
1049
  let state = taskManager.analyzeSpecState();
3160
1050
  if (state.status === "MISSING") {
@@ -3163,7 +1053,6 @@ var devCommand = new Command3("dev").description("Starts the Shark Developer Age
3163
1053
  if (confirm) {
3164
1054
  await interactiveSpecificationAgent({
3165
1055
  briefingPath: options.task ? void 0 : void 0
3166
- // If task provided, maybe write a temp briefing? For now standard flow.
3167
1056
  });
3168
1057
  state = taskManager.analyzeSpecState();
3169
1058
  if (state.status === "MISSING") {
@@ -3177,7 +1066,7 @@ var devCommand = new Command3("dev").description("Starts the Shark Developer Age
3177
1066
  let keepOrchestrating = true;
3178
1067
  let burnMode = false;
3179
1068
  let contextHistory = "";
3180
- tui.intro("\u{1F988} Shark Orchestrator V2");
1069
+ tui.intro("\u{1F988} Shark Legacy Orchestrator");
3181
1070
  while (keepOrchestrating) {
3182
1071
  state = taskManager.analyzeSpecState();
3183
1072
  if (state.status === "COMPLETED") {
@@ -3236,7 +1125,7 @@ ${contextHistory}`
3236
1125
  }
3237
1126
  taskManager.markTaskInProgress(currentTask.id);
3238
1127
  tui.log.info(`\u26A1 Starting Micro-Context for Task: "${currentTask.description}"`);
3239
- const result = await interactiveDeveloperAgent({
1128
+ const result = await interactiveDeveloperAgent2({
3240
1129
  taskId: currentTask.id,
3241
1130
  taskInstruction: currentTask.description,
3242
1131
  history: contextHistory,
@@ -3271,279 +1160,52 @@ ${contextHistory}`
3271
1160
  }
3272
1161
  }
3273
1162
  }
3274
- tui.outro("\u{1F988} Orchestration Finished.");
1163
+ tui.outro("\u{1F988} Legacy Orchestration Finished.");
3275
1164
  });
3276
1165
 
3277
- // src/commands/qa.ts
1166
+ // src/commands/export-schema.ts
3278
1167
  import { Command as Command4 } from "commander";
1168
+ var exportSchemaCommand = new Command4("export-schema").description("Outputs the agent response JSON Schema").action(() => {
1169
+ console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
1170
+ });
3279
1171
 
3280
- // src/core/agents/qa-agent.ts
3281
- import fs9 from "fs";
3282
- import path10 from "path";
3283
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3284
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3285
- var AGENT_TYPE5 = "qa_agent";
3286
- function getAgentId5() {
3287
- const config = ConfigManager.getInstance().getConfig();
3288
- if (config.agents?.qa) return config.agents.qa;
3289
- return process.env.STACKSPOT_QA_AGENT_ID || "01KEQFJZ3Q3JER11NH22HEZX9X";
3290
- }
3291
- function getAgentVersion5() {
3292
- const config = ConfigManager.getInstance().getConfig();
3293
- if (config.agentVersions?.qa) return config.agentVersions.qa;
3294
- return process.env.STACKSPOT_QA_AGENT_VERSION;
3295
- }
3296
- var ChromeDevToolsClient = class {
3297
- client = null;
3298
- transport = null;
3299
- async connect() {
3300
- if (this.client) return;
3301
- try {
3302
- this.transport = new StdioClientTransport({
3303
- command: "npx",
3304
- args: ["-y", "chrome-devtools-mcp@latest"]
3305
- });
3306
- this.client = new Client({
3307
- name: "shark-qa-client",
3308
- version: "1.0.0"
3309
- }, {
3310
- capabilities: {}
3311
- });
3312
- await this.client.connect(this.transport);
3313
- tui.log.success("\u{1F50C} Connected to Chrome DevTools MCP");
3314
- } catch (e) {
3315
- tui.log.error(`Failed to connect to Chrome MCP: ${e.message}`);
3316
- throw e;
3317
- }
3318
- }
3319
- async callTool(name, args) {
3320
- if (!this.client) await this.connect();
3321
- try {
3322
- const result = await this.client.callTool({
3323
- name,
3324
- arguments: args
3325
- });
3326
- return result;
3327
- } catch (e) {
3328
- return { isError: true, content: [{ type: "text", text: `MCP Error: ${e.message}` }] };
3329
- }
3330
- }
3331
- async close() {
3332
- if (this.transport) {
3333
- await this.transport.close();
3334
- }
3335
- }
3336
- };
3337
- var mcpClient = new ChromeDevToolsClient();
3338
- async function runQAAgent(options) {
3339
- const agentId = getAgentId5();
3340
- if (!agentId) {
3341
- tui.log.error("\u274C STACKSPOT_QA_AGENT_ID not configured.");
3342
- tui.log.info("Please run: set STACKSPOT_QA_AGENT_ID=<your-id>");
3343
- return;
3344
- }
3345
- await mcpClient.connect();
3346
- tui.intro("\u{1F988} Shark QA Agent");
3347
- tui.log.info("Connecting to Chrome DevTools...");
3348
- const realm = await getActiveRealm();
3349
- const token = await tokenStorage.getToken(realm);
3350
- if (!token) {
3351
- tui.log.error('Authentication required. Run "shark login".');
3352
- return;
3353
- }
3354
- let projectContext = "";
1172
+ // src/commands/super.ts
1173
+ import { Command as Command5 } from "commander";
1174
+ import { fileURLToPath } from "url";
1175
+ import path5 from "path";
1176
+ import os2 from "os";
1177
+ import fs5 from "fs/promises";
1178
+ var superCommandAction = async (options = {}) => {
1179
+ const __filename2 = fileURLToPath(import.meta.url);
1180
+ const __dirname2 = path5.dirname(__filename2);
1181
+ const packageRoot = path5.resolve(__dirname2, "../../");
1182
+ const internalSkillsPath = path5.join(packageRoot, "skills");
1183
+ const targetPath = options.local ? path5.join(process.cwd(), ".agents", "skills") : path5.join(os2.homedir(), ".shark", "skills");
3355
1184
  try {
3356
- const contextPath = path10.join(process.cwd(), "_sharkrc", "project-context.md");
3357
- if (fs9.existsSync(contextPath)) {
3358
- projectContext = fs9.readFileSync(contextPath, "utf-8");
3359
- tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
3360
- }
3361
- } catch (e) {
3362
- }
3363
- let userMessage = `CONTEXTO DO PROJETO:
3364
- ${projectContext}
3365
-
3366
- `;
3367
- if (options.initialUrl) {
3368
- userMessage += `URL ALVO: ${options.initialUrl}
3369
- `;
3370
- }
3371
- if (options.scenario) {
3372
- userMessage += `CEN\xC1RIO DE TESTE: ${options.scenario}
3373
- `;
3374
- } else {
3375
- userMessage += `Por favor, aguarde instru\xE7\xF5es do usu\xE1rio.`;
3376
- }
3377
- let keepRunning = true;
3378
- while (keepRunning) {
3379
- const spinner = tui.spinner();
3380
- spinner.start("\u{1F916} Shark QA is thinking...");
3381
- let agentResponseText = "";
3382
- let agentResponse = null;
3383
1185
  try {
3384
- const existingConversationId = await conversationManager.getConversationId(AGENT_TYPE5);
3385
- await sseClient.streamAgentResponse(
3386
- `https://genai-inference-app.stackspot.com/v1/agent/${getAgentId5()}/chat`,
3387
- {
3388
- user_prompt: userMessage,
3389
- streaming: true,
3390
- use_conversation: true,
3391
- conversation_id: existingConversationId,
3392
- ...getAgentVersion5() ? { agent_version_number: getAgentVersion5() } : {}
3393
- },
3394
- {
3395
- "Authorization": `Bearer ${token}`
3396
- },
3397
- {
3398
- onChunk: (chunk) => {
3399
- agentResponseText += chunk;
3400
- if (agentResponseText.length > 10 && agentResponseText.trim().startsWith("{")) {
3401
- spinner.message("Receiving structured plan...");
3402
- }
3403
- },
3404
- onComplete: (fullText, metadata) => {
3405
- try {
3406
- if (metadata?.conversation_id) {
3407
- conversationManager.saveConversationId(AGENT_TYPE5, metadata.conversation_id);
3408
- }
3409
- agentResponse = parseAgentResponse(fullText || agentResponseText);
3410
- } catch (e) {
3411
- tui.log.error(`Parse Error: ${e.message}`);
3412
- agentResponse = { actions: [], summary: "Error parsing response", message: fullText };
3413
- }
3414
- }
3415
- }
3416
- );
3417
- spinner.stop("Response Received");
3418
- } catch (error) {
3419
- spinner.stop("Communication Error", 1);
3420
- tui.log.error(error.message);
3421
- keepRunning = false;
3422
- break;
3423
- }
3424
- const currentResponse = agentResponse;
3425
- if (!currentResponse) continue;
3426
- if (currentResponse.summary) {
3427
- tui.log.info(colors.primary(`\u{1F4CB} Plan: ${currentResponse.summary}`));
3428
- }
3429
- if (currentResponse.actions.length === 0) {
3430
- const reply = await tui.text({
3431
- message: "\u{1F916} Shark QA:",
3432
- placeholder: "Your reply..."
3433
- });
3434
- if (tui.isCancel(reply)) {
3435
- keepRunning = false;
3436
- } else {
3437
- userMessage = reply;
3438
- }
3439
- continue;
3440
- }
3441
- for (const action of currentResponse.actions) {
3442
- tui.log.info(colors.dim(`Executing: ${action.type}`));
3443
- let result = "";
3444
- try {
3445
- switch (action.type) {
3446
- case "talk_with_user":
3447
- const reply = await tui.text({
3448
- message: `\u{1F916} ${action.content}`
3449
- });
3450
- if (tui.isCancel(reply)) keepRunning = false;
3451
- else result = reply;
3452
- break;
3453
- case "use_mcp_tool":
3454
- if (action.tool_name) {
3455
- tui.log.info(`\u{1F527} MCP Tool: ${colors.bold(action.tool_name)}`);
3456
- let args = {};
3457
- try {
3458
- args = typeof action.tool_args === "string" ? JSON.parse(action.tool_args) : action.tool_args || {};
3459
- } catch (e) {
3460
- tui.log.warning("Failed to parse tool_args, using empty object");
3461
- }
3462
- const mcpResult = await mcpClient.callTool(action.tool_name, args);
3463
- result = JSON.stringify(mcpResult);
3464
- tui.log.success(`Result: ${result.substring(0, 100)}...`);
3465
- }
3466
- break;
3467
- case "create_file":
3468
- if (action.path && action.content) {
3469
- const fullPath = path10.resolve(process.cwd(), action.path);
3470
- const BOM = "\uFEFF";
3471
- const contentToWrite = action.content;
3472
- const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
3473
- fs9.writeFileSync(fullPath, finalContent, { encoding: "utf-8" });
3474
- tui.log.success(`File created: ${action.path}`);
3475
- result = "File created successfully.";
3476
- }
3477
- break;
3478
- case "read_file":
3479
- result = handleReadFile(action.path || "");
3480
- break;
3481
- case "run_command":
3482
- const confirm = await tui.confirm({ message: `Run command: ${action.command}?` });
3483
- if (confirm && action.command) {
3484
- result = await handleRunCommand(action.command);
3485
- } else {
3486
- result = "Command execution denied by user.";
3487
- }
3488
- break;
3489
- default:
3490
- result = `Action ${action.type} not fully implemented in local client.`;
3491
- }
3492
- } catch (e) {
3493
- result = `Error executing ${action.type}: ${e.message}`;
3494
- tui.log.error(result);
3495
- }
3496
- userMessage = `[Action ${action.type} Result]:
3497
- ${result}
3498
-
3499
- `;
1186
+ await fs5.rm(targetPath, { recursive: true, force: true });
1187
+ } catch {
3500
1188
  }
3501
- }
3502
- await mcpClient.close();
3503
- tui.outro("\u{1F988} Shark QA Session Ended");
3504
- }
3505
-
3506
- // src/commands/qa.ts
3507
- var qaCommand = new Command4("qa").description("Start the Shark QA Agent to test web applications").option("--url <url>", "Initial URL to test").option("--scenario <scenario>", "Scenario description or test case to execute").action(async (options) => {
3508
- try {
3509
- await runQAAgent({
3510
- initialUrl: options.url,
3511
- scenario: options.scenario
3512
- });
1189
+ await fs5.mkdir(targetPath, { recursive: true });
1190
+ await fs5.cp(internalSkillsPath, targetPath, { recursive: true, force: true });
1191
+ console.log(`\u{1F680} Superpowers skills installed successfully to ${targetPath}`);
3513
1192
  } catch (error) {
3514
- console.error("Failed to run QA Agent:", error.message);
1193
+ console.error(`\u274C Failed to install superpowers skills: ${error.message}`);
3515
1194
  process.exit(1);
3516
1195
  }
3517
- });
1196
+ };
1197
+ var superCommand = new Command5("super").description("Install Superpowers skills globally or locally").option("-l, --local", "Install skills locally in the current project under .agents/skills").action(superCommandAction);
3518
1198
 
3519
1199
  // src/bin/shark.ts
3520
1200
  crashHandler.init();
3521
- var program = new Command5();
1201
+ var program = new Command6();
3522
1202
  program.name("shark").description("Shark CLI: AI-Native Collaborative Development Tool").version("0.0.1");
3523
1203
  program.addCommand(loginCommand);
3524
1204
  program.addCommand(initCommand);
3525
- program.addCommand(scanCommand);
3526
1205
  program.addCommand(devCommand);
3527
- program.addCommand(qaCommand);
3528
- program.command("ba").description("Start Business Analyst Agent interactive session").option("--id <agent_id>", "Override Agent ID").action(async (options) => {
3529
- try {
3530
- await interactiveBusinessAnalyst();
3531
- } catch (error) {
3532
- console.error("Error:", error.message);
3533
- process.exit(1);
3534
- }
3535
- });
3536
- program.command("spec").description("Start Specification Agent interactive session").option("--id <agent_id>", "Override Agent ID").option("--briefing <path>", "Path to briefing file").action(async (options) => {
3537
- try {
3538
- await interactiveSpecificationAgent({
3539
- agentId: options.id,
3540
- briefingPath: options.briefing
3541
- });
3542
- } catch (error) {
3543
- console.error("Error:", error.message);
3544
- process.exit(1);
3545
- }
3546
- });
1206
+ program.addCommand(legacyCommand);
1207
+ program.addCommand(exportSchemaCommand);
1208
+ program.addCommand(superCommand);
3547
1209
  program.command("config").description("Manage global configuration").action(configCommand.action);
3548
1210
  process.on("unhandledRejection", (err) => {
3549
1211
  console.error(colors.error("\u274C Unhandled Error:"), err);