shark-ai 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/shark.js CHANGED
@@ -420,17 +420,17 @@ var AgentActionSchema = z2.object({
420
420
  type: z2.enum([
421
421
  "create_file",
422
422
  "modify_file",
423
- "modify_ast",
424
- "search_ast",
425
- "delete_file",
426
- "talk_with_user",
427
423
  "list_files",
428
- "read_file",
429
424
  "search_file",
425
+ "read_file",
426
+ "delete_file",
427
+ "list_structure",
428
+ "modify_ast",
429
+ "search_ast",
430
430
  "run_command",
431
- "use_mcp_tool",
432
- // New AST Tools
431
+ "talk_with_user",
433
432
  "ast_list_structure",
433
+ "ast_get_method",
434
434
  "ast_add_method",
435
435
  "ast_modify_method",
436
436
  "ast_remove_method",
@@ -831,11 +831,11 @@ async function interactiveBusinessAnalyst() {
831
831
 
832
832
  // src/core/agents/specification-agent.ts
833
833
  import fs5 from "fs";
834
- import path7 from "path";
834
+ import path6 from "path";
835
835
 
836
836
  // src/core/agents/agent-tools.ts
837
837
  import fs4 from "fs";
838
- import path6 from "path";
838
+ import path5 from "path";
839
839
  import fg from "fast-glob";
840
840
  import { exec } from "child_process";
841
841
  import { promisify } from "util";
@@ -1086,6 +1086,20 @@ var TypeScriptEditor = class {
1086
1086
  return false;
1087
1087
  }
1088
1088
  }
1089
+ async getMethod(filePath, className, methodName) {
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
+ return null;
1096
+ }
1097
+ return method.getText();
1098
+ } catch (error) {
1099
+ console.error(`Failed to get method "${methodName}":`, error);
1100
+ return null;
1101
+ }
1102
+ }
1089
1103
  // ═══════════════════════════════════════════════════════
1090
1104
  // IMPLEMENTATION: Interface/Type Operations
1091
1105
  // ═══════════════════════════════════════════════════════
@@ -1226,134 +1240,6 @@ var CodeEditorFactory = class {
1226
1240
  }
1227
1241
  };
1228
1242
 
1229
- // src/core/services/code-review.service.ts
1230
- import path5 from "path";
1231
- var CodeReviewService = class {
1232
- /**
1233
- * Reviews the code modification and returns a status/feedback string.
1234
- * This string is intended to be appended to the user Preview so the Agent can see it.
1235
- */
1236
- static async reviewCode(filePath, newContent) {
1237
- const config = ConfigManager.getInstance().getConfig();
1238
- const validation = config.validation || { llmReviewExtensions: [".ts", ".tsx"], syntaxCheckExtensions: ["*"] };
1239
- const ext = path5.extname(filePath).toLowerCase();
1240
- const needsLlmReview = validation.llmReviewExtensions.includes(ext) || validation.llmReviewExtensions.includes("*");
1241
- if (needsLlmReview) {
1242
- return await this.performLlmReview(filePath, newContent);
1243
- }
1244
- const needsSyntaxCheck = validation.syntaxCheckExtensions.includes(ext) || validation.syntaxCheckExtensions.includes("*");
1245
- if (needsSyntaxCheck) {
1246
- return this.performSyntaxCheck(filePath, newContent);
1247
- }
1248
- return "\u2139\uFE0F No specific validation configured for this file type.";
1249
- }
1250
- static getCodeReviewAgentId() {
1251
- const config = ConfigManager.getInstance().getConfig();
1252
- if (config.agents?.codeReview) return config.agents.codeReview;
1253
- if (process.env.STACKSPOT_CODE_REVIEW_AGENT_ID) return process.env.STACKSPOT_CODE_REVIEW_AGENT_ID;
1254
- return "";
1255
- }
1256
- static async performLlmReview(filePath, content) {
1257
- const agentId = this.getCodeReviewAgentId();
1258
- if (!agentId) {
1259
- return this.performSyntaxCheck(filePath, content);
1260
- }
1261
- try {
1262
- const realm = await getActiveRealm();
1263
- const token = await ensureValidToken(realm);
1264
- const prompt = `You are a Code Review Specialist. Analyze the following code modification for file: ${path5.basename(filePath)}
1265
-
1266
- NEW CODE TO BE ADDED:
1267
- \`\`\`
1268
- ${content}
1269
- \`\`\`
1270
-
1271
- Provide a concise review focusing on:
1272
- 1. Syntax errors or obvious bugs
1273
- 2. Potential runtime issues
1274
- 3. Code quality concerns
1275
-
1276
- Respond in JSON format with status, issues array, and summary.`;
1277
- const payload = {
1278
- user_prompt: prompt,
1279
- streaming: true,
1280
- use_conversation: true,
1281
- stackspot_knowledge: false
1282
- };
1283
- const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${agentId}/chat`;
1284
- let reviewText = "";
1285
- await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
1286
- onChunk: (c) => {
1287
- reviewText += c;
1288
- },
1289
- onComplete: () => {
1290
- },
1291
- onError: (e) => {
1292
- throw e;
1293
- }
1294
- });
1295
- const cleanedText = reviewText.trim();
1296
- FileLogger.log("CODE_REVIEW", "Raw Response Received", { length: cleanedText.length, content: cleanedText });
1297
- try {
1298
- const reviewData = extractFirstJson(cleanedText);
1299
- return this.formatReviewResponse(reviewData);
1300
- } catch (parseErr) {
1301
- FileLogger.log("CODE_REVIEW", "JSON Parse Failed", {
1302
- error: parseErr.message,
1303
- rawText: cleanedText
1304
- });
1305
- return `\u{1F916} **[AI Code Review Agent]**
1306
- \u26A0\uFE0F Response format error. Raw output:
1307
- ${cleanedText}`;
1308
- }
1309
- } catch (err) {
1310
- FileLogger.log("CODE_REVIEW", "LLM Review Failed", { error: err.message });
1311
- return `\u26A0\uFE0F AI Review unavailable (${err.message}). Using syntax check:
1312
- ` + await this.performSyntaxCheck(filePath, content);
1313
- }
1314
- }
1315
- static formatReviewResponse(data) {
1316
- let report = `\u{1F916} **[AI Code Review Agent]**
1317
- `;
1318
- const statusIcon = data.status === "ok" ? "\u2705" : data.status === "warning" ? "\u26A0\uFE0F" : "\u274C";
1319
- report += `${statusIcon} **Status:** ${data.status.toUpperCase()}
1320
- `;
1321
- if (data.issues && data.issues.length > 0) {
1322
- report += `
1323
- **Issues Found:**
1324
- `;
1325
- for (const issue of data.issues) {
1326
- const severityIcon = issue.severity === "error" ? "\u274C" : issue.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
1327
- const lineInfo = issue.line ? `Linha ${issue.line}: ` : "";
1328
- report += `${severityIcon} ${lineInfo}${issue.message}
1329
- `;
1330
- if (issue.suggestion) {
1331
- report += ` \u{1F4A1} ${issue.suggestion}
1332
- `;
1333
- }
1334
- }
1335
- }
1336
- report += `
1337
- **Resumo:** ${data.summary}`;
1338
- return report;
1339
- }
1340
- static performSyntaxCheck(filePath, content) {
1341
- let report = `\u{1F50D} **[Syntax Validator]**
1342
- `;
1343
- const openBraces = (content.match(/\{/g) || []).length;
1344
- const closeBraces = (content.match(/\}/g) || []).length;
1345
- if (openBraces !== closeBraces) {
1346
- report += `\u274C **CRITICAL:** Brace mismatch detected! Found ${openBraces} '{' and ${closeBraces} '}'.
1347
- `;
1348
- report += ` **Review your code carefully. Do not confirm if the block is incomplete.**`;
1349
- } else {
1350
- report += `\u2705 File structure looks balanced (Braces match).
1351
- `;
1352
- }
1353
- return Promise.resolve(report);
1354
- }
1355
- };
1356
-
1357
1243
  // src/core/agents/agent-tools.ts
1358
1244
  var execAsync = promisify(exec);
1359
1245
  function detectLineEnding(content) {
@@ -1363,7 +1249,7 @@ function detectLineEnding(content) {
1363
1249
  }
1364
1250
  function handleListFiles(dirPath) {
1365
1251
  try {
1366
- const fullPath = path6.resolve(process.cwd(), dirPath);
1252
+ const fullPath = path5.resolve(process.cwd(), dirPath);
1367
1253
  if (!fs4.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
1368
1254
  const items = fs4.readdirSync(fullPath, { withFileTypes: true });
1369
1255
  return items.map((item) => {
@@ -1375,7 +1261,7 @@ function handleListFiles(dirPath) {
1375
1261
  }
1376
1262
  function handleReadFile(filePath, showLineNumbers = true) {
1377
1263
  try {
1378
- const fullPath = path6.resolve(process.cwd(), filePath);
1264
+ const fullPath = path5.resolve(process.cwd(), filePath);
1379
1265
  if (!fs4.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
1380
1266
  const stats = fs4.statSync(fullPath);
1381
1267
  if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
@@ -1421,47 +1307,6 @@ function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
1421
1307
  return false;
1422
1308
  }
1423
1309
  }
1424
- async function generateFilePreview(filePath, startLine, endLine, newContent) {
1425
- try {
1426
- const fullPath = path6.resolve(process.cwd(), filePath);
1427
- if (!fs4.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
1428
- const content = fs4.readFileSync(fullPath, "utf-8");
1429
- const lines = content.split(/\r\n|\r|\n/);
1430
- const startIdx = startLine - 1;
1431
- const endIdx = endLine - 1;
1432
- if (startIdx < 0 || startIdx >= lines.length) return `Error: Start line ${startLine} is out of bounds (File has ${lines.length} lines).`;
1433
- if (endIdx < startIdx || endIdx >= lines.length) return `Error: End line ${endLine} is invalid.`;
1434
- const contextBefore = lines.slice(Math.max(0, startIdx - 3), startIdx).map((l, i) => `${Math.max(1, startLine - 3) + i} | ${l}`).join("\n");
1435
- const contentReplacing = lines.slice(startIdx, endIdx + 1).map((l, i) => `${startLine + i} | - ${l}`).join("\n");
1436
- const contextAfter = lines.slice(endIdx + 1, Math.min(lines.length, endIdx + 4)).map((l, i) => `${endLine + 1 + i} | ${l}`).join("\n");
1437
- const newLines = newContent.split("\n").map((l) => `+ ${l}`).join("\n");
1438
- let reviewFeedback = "";
1439
- try {
1440
- reviewFeedback = await CodeReviewService.reviewCode(filePath, newContent);
1441
- } catch (err) {
1442
- reviewFeedback = `\u26A0\uFE0F Code Review Service Unavailable: ${err.message}`;
1443
- }
1444
- return `PREVIEW OF CHANGES to ${filePath}:
1445
- --------------------------------------------------
1446
- CONTEXT BEFORE:
1447
- ${contextBefore}
1448
-
1449
- CHANGES:
1450
- ${contentReplacing}
1451
- ${newLines}
1452
-
1453
- CONTEXT AFTER:
1454
- ${contextAfter}
1455
- --------------------------------------------------
1456
- ${reviewFeedback}
1457
-
1458
- IMPORTANT: Please verify that the lines being replaced (marked with -) are exactly what you intend to remove.
1459
- If the context looks wrong, DO NOT CONFIRM. Re-read the file to check line numbers.
1460
- `;
1461
- } catch (e) {
1462
- return `Error generating preview: ${e.message}`;
1463
- }
1464
- }
1465
1310
  function handleSearchFile(pattern) {
1466
1311
  try {
1467
1312
  const entries = fg.sync(pattern, { dot: true });
@@ -1549,19 +1394,19 @@ function resolveAstGrepCommand() {
1549
1394
  const binName = isWin ? "sg.cmd" : "sg";
1550
1395
  try {
1551
1396
  const currentFile = fileURLToPath(import.meta.url);
1552
- let dir = path6.dirname(currentFile);
1397
+ let dir = path5.dirname(currentFile);
1553
1398
  for (let i = 0; i < 5; i++) {
1554
- const candidate = path6.join(dir, "node_modules", ".bin", binName);
1399
+ const candidate = path5.join(dir, "node_modules", ".bin", binName);
1555
1400
  if (fs4.existsSync(candidate)) {
1556
1401
  return `"${candidate}"`;
1557
1402
  }
1558
- const parent = path6.dirname(dir);
1403
+ const parent = path5.dirname(dir);
1559
1404
  if (parent === dir) break;
1560
1405
  dir = parent;
1561
1406
  }
1562
1407
  } catch (e) {
1563
1408
  }
1564
- const cwdBin = path6.resolve(process.cwd(), "node_modules", ".bin", binName);
1409
+ const cwdBin = path5.resolve(process.cwd(), "node_modules", ".bin", binName);
1565
1410
  if (fs4.existsSync(cwdBin)) {
1566
1411
  return `"${cwdBin}"`;
1567
1412
  }
@@ -1733,9 +1578,22 @@ async function astListStructure(filePath) {
1733
1578
  }
1734
1579
  async function astAddMethod(filePath, className, methodCode) {
1735
1580
  const editor = CodeEditorFactory.getEditor(filePath);
1736
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1581
+ if (!editor) {
1582
+ throw new Error(`No AST editor available for ${filePath}`);
1583
+ }
1737
1584
  return await editor.addMethod(filePath, className, methodCode);
1738
1585
  }
1586
+ async function astGetMethod(filePath, className, methodName) {
1587
+ const editor = CodeEditorFactory.getEditor(filePath);
1588
+ if (!editor) {
1589
+ throw new Error(`No AST editor available for ${filePath}`);
1590
+ }
1591
+ const methodContent = await editor.getMethod(filePath, className, methodName);
1592
+ if (!methodContent) {
1593
+ return `Method "${methodName}" not found in class "${className}"`;
1594
+ }
1595
+ return methodContent;
1596
+ }
1739
1597
  async function astAddClass(filePath, className, extendsClass, implementsInterfaces) {
1740
1598
  const editor = CodeEditorFactory.getEditor(filePath);
1741
1599
  if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
@@ -1815,11 +1673,11 @@ async function interactiveSpecificationAgent(options = {}) {
1815
1673
  tui.intro("\u{1F3D7}\uFE0F Specification Agent");
1816
1674
  const projectRoot = process.cwd();
1817
1675
  let contextContent = "";
1818
- const contextPath = path7.resolve(projectRoot, "_sharkrc", "project-context.md");
1676
+ const contextPath = path6.resolve(projectRoot, "_sharkrc", "project-context.md");
1819
1677
  if (fs5.existsSync(contextPath)) {
1820
1678
  try {
1821
1679
  contextContent = fs5.readFileSync(contextPath, "utf-8");
1822
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path7.relative(projectRoot, contextPath))}`);
1680
+ tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path6.relative(projectRoot, contextPath))}`);
1823
1681
  } catch (e) {
1824
1682
  tui.log.warning(`Failed to read context file: ${e}`);
1825
1683
  }
@@ -1829,7 +1687,7 @@ async function interactiveSpecificationAgent(options = {}) {
1829
1687
  briefingContent = fs5.readFileSync(options.briefingPath, "utf-8");
1830
1688
  tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
1831
1689
  } else {
1832
- const sharkRcBriefing = path7.resolve(projectRoot, "_sharkrc", "briefing.md");
1690
+ const sharkRcBriefing = path6.resolve(projectRoot, "_sharkrc", "briefing.md");
1833
1691
  if (fs5.existsSync(sharkRcBriefing)) {
1834
1692
  briefingContent = fs5.readFileSync(sharkRcBriefing, "utf-8");
1835
1693
  tui.log.info(`\u{1F4C4} Standard Briefing loaded: ${colors.dim("_sharkrc/briefing.md")}`);
@@ -1838,6 +1696,16 @@ async function interactiveSpecificationAgent(options = {}) {
1838
1696
  }
1839
1697
  }
1840
1698
  let initialPrompt = "";
1699
+ if (options.initialContext) {
1700
+ initialPrompt += `
1701
+ \u26A0\uFE0F **CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER)**:
1702
+ O Developer Agent estava executando tarefas. Aqui est\xE1 o resumo do que aconteceu at\xE9 agora:
1703
+ """
1704
+ ${options.initialContext}
1705
+ """
1706
+ Analise esse hist\xF3rico acima. Se houve falha, proponha fixes na spec. Se o usu\xE1rio pediu mudan\xE7a, use isso como base.
1707
+ `;
1708
+ }
1841
1709
  if (briefingContent) {
1842
1710
  initialPrompt += `
1843
1711
  Abaixo est\xE1 o **Briefing de Neg\xF3cio** ou Descri\xE7\xE3o da Tarefa.
@@ -1946,7 +1814,7 @@ ${result}
1946
1814
  const BOM = "\uFEFF";
1947
1815
  const contentToWrite = action.content || "";
1948
1816
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
1949
- const dir = path7.dirname(action.path);
1817
+ const dir = path6.dirname(action.path);
1950
1818
  if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
1951
1819
  fs5.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
1952
1820
  tui.log.success(`\u2705 Created: ${action.path}`);
@@ -2096,7 +1964,7 @@ import { Command as Command2 } from "commander";
2096
1964
 
2097
1965
  // src/core/agents/scan-agent.ts
2098
1966
  import fs6 from "fs";
2099
- import path8 from "path";
1967
+ import path7 from "path";
2100
1968
  var AGENT_TYPE3 = "scan_agent";
2101
1969
  function getAgentId3() {
2102
1970
  const config = ConfigManager.getInstance().getConfig();
@@ -2111,29 +1979,29 @@ async function interactiveScanAgent(options = {}) {
2111
1979
  const projectRoot = process.cwd();
2112
1980
  let outputFile;
2113
1981
  if (options.output) {
2114
- outputFile = path8.resolve(process.cwd(), options.output);
1982
+ outputFile = path7.resolve(process.cwd(), options.output);
2115
1983
  } else {
2116
- const outputDir = path8.resolve(projectRoot, "_sharkrc");
1984
+ const outputDir = path7.resolve(projectRoot, "_sharkrc");
2117
1985
  if (!fs6.existsSync(outputDir)) {
2118
1986
  const stat = fs6.existsSync(outputDir) ? fs6.statSync(outputDir) : null;
2119
1987
  if (stat && stat.isFile()) {
2120
1988
  tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
2121
- const fallbackDir = path8.resolve(projectRoot, "_bmad/project-context");
1989
+ const fallbackDir = path7.resolve(projectRoot, "_bmad/project-context");
2122
1990
  if (!fs6.existsSync(fallbackDir)) fs6.mkdirSync(fallbackDir, { recursive: true });
2123
- outputFile = path8.join(fallbackDir, "project-context.md");
1991
+ outputFile = path7.join(fallbackDir, "project-context.md");
2124
1992
  } else {
2125
1993
  fs6.mkdirSync(outputDir, { recursive: true });
2126
- outputFile = path8.join(outputDir, "project-context.md");
1994
+ outputFile = path7.join(outputDir, "project-context.md");
2127
1995
  }
2128
1996
  } else {
2129
1997
  fs6.mkdirSync(outputDir, { recursive: true });
2130
- outputFile = path8.join(outputDir, "project-context.md");
1998
+ outputFile = path7.join(outputDir, "project-context.md");
2131
1999
  }
2132
2000
  }
2133
2001
  tui.log.info(`${t("commands.scan.scanningProject")} ${colors.bold(projectRoot)}`);
2134
2002
  tui.log.info(`${t("commands.scan.outputTarget")} ${colors.bold(outputFile)}`);
2135
2003
  tui.log.info(`${t("commands.scan.language")} ${colors.bold(language)}`);
2136
- const configFileRelative = path8.relative(projectRoot, outputFile);
2004
+ const configFileRelative = path7.relative(projectRoot, outputFile);
2137
2005
  const initialTemplate = `# Project Context
2138
2006
 
2139
2007
  ## Overview
@@ -2466,11 +2334,11 @@ ${result}
2466
2334
 
2467
2335
  `;
2468
2336
  } else if (action.type === "create_file" || action.type === "modify_file") {
2469
- const resolvedActionPath = path8.resolve(action.path || "");
2470
- const resolvedTargetPath = path8.resolve(targetPath);
2337
+ const resolvedActionPath = path7.resolve(action.path || "");
2338
+ const resolvedTargetPath = path7.resolve(targetPath);
2471
2339
  let isTarget = resolvedActionPath === resolvedTargetPath;
2472
- if (!isTarget && path8.basename(action.path || "") === "project-context.md") {
2473
- tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path8.relative(process.cwd(), targetPath)));
2340
+ if (!isTarget && path7.basename(action.path || "") === "project-context.md") {
2341
+ tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path7.relative(process.cwd(), targetPath)));
2474
2342
  isTarget = true;
2475
2343
  action.path = targetPath;
2476
2344
  }
@@ -2611,7 +2479,7 @@ import { Command as Command3 } from "commander";
2611
2479
 
2612
2480
  // src/core/agents/developer-agent.ts
2613
2481
  import fs7 from "fs";
2614
- import path9 from "path";
2482
+ import path8 from "path";
2615
2483
  var AGENT_TYPE4 = "developer_agent";
2616
2484
  async function validateTypeScript(filePath) {
2617
2485
  try {
@@ -2624,32 +2492,6 @@ async function validateTypeScript(filePath) {
2624
2492
  return { valid: false, error: e.message || "TypeScript validation failed" };
2625
2493
  }
2626
2494
  }
2627
- function validateHtmlTagBalance(filePath) {
2628
- const content = fs7.readFileSync(filePath, "utf-8");
2629
- const stack = [];
2630
- const tagRegex = /<\/?(\w+)(?:\s[^>]*)?\s*\/?>/g;
2631
- const selfClosingTags = ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "param", "source", "track", "wbr"];
2632
- let match;
2633
- while ((match = tagRegex.exec(content)) !== null) {
2634
- const fullTag = match[0];
2635
- const tagName = match[1].toLowerCase();
2636
- if (fullTag.startsWith("</")) {
2637
- const expected = stack.pop();
2638
- if (expected !== tagName) {
2639
- return {
2640
- valid: false,
2641
- error: `Tag mismatch: expected </${expected}> but found </${tagName}> at position ${match.index}`
2642
- };
2643
- }
2644
- } else if (!fullTag.endsWith("/>") && !selfClosingTags.includes(tagName)) {
2645
- stack.push(tagName);
2646
- }
2647
- }
2648
- if (stack.length > 0) {
2649
- return { valid: false, error: `Unclosed tags: <${stack.join(">, <")}>` };
2650
- }
2651
- return { valid: true };
2652
- }
2653
2495
  function getAgentId4(overrideId) {
2654
2496
  if (overrideId) return overrideId;
2655
2497
  const config = ConfigManager.getInstance().getConfig();
@@ -2657,19 +2499,25 @@ function getAgentId4(overrideId) {
2657
2499
  if (process.env.STACKSPOT_DEV_AGENT_ID) return process.env.STACKSPOT_DEV_AGENT_ID;
2658
2500
  return "01KEQCGJ65YENRA4QBXVN1YFFX";
2659
2501
  }
2660
- function analyzeSpecState(projectRoot) {
2661
- const specPath = path9.resolve(projectRoot, "tech-spec.md");
2662
- if (!fs7.existsSync(specPath)) {
2663
- return { status: "MISSING" };
2502
+ async function interactiveDeveloperAgent(options = {}) {
2503
+ FileLogger.init();
2504
+ const agentId = getAgentId4();
2505
+ if (agentId === "PENDING_CONFIGURATION") {
2506
+ tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
2507
+ return { success: false, summary: "Missing configuration." };
2664
2508
  }
2665
- const content = fs7.readFileSync(specPath, "utf-8");
2666
- const match = content.match(/- \[ \] (.*)/);
2667
- if (match) {
2668
- return { status: "PENDING", nextTask: match[1].trim(), specContent: content };
2509
+ const projectRoot = process.cwd();
2510
+ let contextContent = "";
2511
+ const defaultContextPath = path8.resolve(projectRoot, "_sharkrc", "project-context.md");
2512
+ const specificContextPath = options.context ? path8.resolve(projectRoot, options.context) : defaultContextPath;
2513
+ if (fs7.existsSync(specificContextPath)) {
2514
+ try {
2515
+ contextContent = fs7.readFileSync(specificContextPath, "utf-8");
2516
+ } catch (e) {
2517
+ tui.log.warning(`Failed to read context file: ${e}`);
2518
+ }
2669
2519
  }
2670
- return { status: "COMPLETED", specContent: content };
2671
- }
2672
- function buildSystemPrompt(state, contextContent, additionalInstructions = "") {
2520
+ const currentTask = options.taskInstruction || "Analyze the project and fix pending issues.";
2673
2521
  let basePrompt = ``;
2674
2522
  if (contextContent) {
2675
2523
  basePrompt += `
@@ -2679,117 +2527,69 @@ ${contextContent}
2679
2527
  -----------------------
2680
2528
  `;
2681
2529
  }
2682
- if (state.status === "MISSING") {
2530
+ if (options.history) {
2683
2531
  basePrompt += `
2684
2532
 
2685
- \u{1F6A8} CRITICAL: NO 'tech-spec.md' FOUND.
2686
-
2687
- Your FIRST priority is to analyze the user request and CREATE a 'tech-spec.md' file.
2688
-
2689
- \u26A0\uFE0F WORKFLOW:
2690
- 1. **Understand**: Clarify the goal with the user if needed.
2691
- 2. **Explore**: Use 'list_files'/'read_file' to find RELEVANT files for this specific task.
2692
- 3. **Specify**: Create 'tech-spec.md' referencing REAL file paths you found.
2693
-
2694
- DO NOT create a spec based on guesses. Verify file existence before writing the plan.
2695
-
2696
- Structure for 'tech-spec.md':
2697
- \`\`\`markdown
2698
- # Technical Spec: [Title]
2699
-
2700
- ## Goal
2701
- [Brief description]
2702
-
2703
- ## Implementation Plan
2704
- - [ ] Step 1: [Description]
2705
- - [ ] Step 2: [Description]
2706
- ...
2707
- \`\`\`
2708
- User Request: "${additionalInstructions}"
2533
+ --- PREVIOUS EXECUTION SUMMARY ---
2534
+ ${options.history}
2535
+ ----------------------------------
2709
2536
  `;
2710
- } else if (state.status === "PENDING") {
2711
- basePrompt += `
2537
+ }
2538
+ basePrompt += `
2712
2539
 
2713
2540
  \u{1F7E2} EXECUTION MODE
2714
2541
 
2715
- Use 'tech-spec.md' as your source of truth.
2542
+ You are a highly skilled Developer Agent.
2543
+ \u{1F449} **CURRENT TASK**: "${currentTask}"
2716
2544
 
2717
- \u{1F449} **CURRENT TASK**: "${state.nextTask}"
2718
-
2719
-
2720
- Focus ONLY on this task. Do not jump ahead.
2545
+ Your goal is to COMPLETE this specific task and then STOP.
2721
2546
  1. Implement the necessary changes.
2722
2547
  2. Verify (compile/test).
2723
- 3. **MANDATORY**: Use 'modify_file' to mark this task as '[x]' in 'tech-spec.md' when done.
2724
- `;
2725
- } else {
2726
- basePrompt += `
2727
-
2728
- \u2728 ALL TASKS COMPLETED according to 'tech-spec.md'.
2729
-
2730
- Ask the user if they want to add more tasks or finish the session.
2548
+ 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.
2731
2549
  `;
2732
- }
2733
- return basePrompt;
2734
- }
2735
- async function interactiveDeveloperAgent(options = {}) {
2736
- FileLogger.init();
2737
- tui.intro("\u{1F988} Shark Dev Agent (Spec-Driven)");
2738
- const agentId = getAgentId4();
2739
- if (agentId === "PENDING_CONFIGURATION") {
2740
- tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
2741
- return;
2742
- }
2743
- const projectRoot = process.cwd();
2744
- let contextContent = "";
2745
- const defaultContextPath = path9.resolve(projectRoot, "_sharkrc", "project-context.md");
2746
- const specificContextPath = options.context ? path9.resolve(projectRoot, options.context) : defaultContextPath;
2747
- if (fs7.existsSync(specificContextPath)) {
2748
- try {
2749
- contextContent = fs7.readFileSync(specificContextPath, "utf-8");
2750
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path9.relative(projectRoot, specificContextPath))}`);
2751
- } catch (e) {
2752
- tui.log.warning(`Failed to read context file: ${e}`);
2753
- }
2754
- } else {
2755
- tui.log.warning(`\u26A0\uFE0F No context file found. Agent will run without pre-loaded context.`);
2756
- }
2757
- let specState = analyzeSpecState(projectRoot);
2758
- let nextPrompt = buildSystemPrompt(specState, contextContent, options.task || "Start working.");
2550
+ let nextPrompt = basePrompt;
2759
2551
  let keepGoing = true;
2760
2552
  const spinner = tui.spinner();
2761
- let stepCount = 0;
2553
+ let finalSummary = "";
2554
+ let isTaskCompleted = false;
2555
+ const conversationKey = options.taskId ? `dev_agent_${options.taskId}` : `dev_agent_${Date.now()}`;
2762
2556
  let autoApprovals = {
2763
2557
  files: false,
2764
2558
  commands: false
2765
2559
  };
2766
2560
  while (keepGoing) {
2767
- stepCount++;
2768
2561
  try {
2769
- if (specState.status === "PENDING") {
2770
- tui.log.info(colors.bold(`\u{1F3AF} DOING: ${specState.nextTask}`));
2771
- } else if (specState.status === "MISSING") {
2772
- tui.log.info(colors.warning(`\u{1F4CB} PLANNING: Creating tech-spec.md`));
2773
- }
2774
- spinner.start("Waiting for Shark Dev...");
2775
- let lastResponse = null;
2776
- await callDevAgentApi(nextPrompt, (chunk) => {
2777
- }).then((resp) => {
2778
- lastResponse = resp;
2779
- });
2562
+ spinner.start("\u{1F988} Shark Dev working...");
2563
+ const lastResponse = await callDevAgentApi(nextPrompt, (chunk) => {
2564
+ }, conversationKey);
2780
2565
  spinner.stop("Response received");
2781
- if (lastResponse && lastResponse.actions) {
2566
+ if (lastResponse) {
2782
2567
  const response = lastResponse;
2568
+ const actions = response.actions || [];
2569
+ if (response.message && response.message.includes("TASK_COMPLETED:")) {
2570
+ isTaskCompleted = true;
2571
+ finalSummary = response.message.split("TASK_COMPLETED:")[1].trim();
2572
+ keepGoing = false;
2573
+ }
2574
+ if (actions.length === 0 && response.message && !isTaskCompleted) {
2575
+ tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
2576
+ console.log(response.message);
2577
+ const userReply = await tui.text({ message: "Your answer:" });
2578
+ if (tui.isCancel(userReply)) {
2579
+ keepGoing = false;
2580
+ break;
2581
+ }
2582
+ nextPrompt = userReply;
2583
+ }
2783
2584
  let executionResults = "";
2784
2585
  let waitingForUser = false;
2785
- let specUpdated = false;
2786
- for (const action of response.actions) {
2586
+ for (const action of actions) {
2787
2587
  if (action.type === "talk_with_user") {
2788
2588
  tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
2789
2589
  console.log(action.content);
2790
- waitingForUser = true;
2590
+ if (!isTaskCompleted) waitingForUser = true;
2791
2591
  } else if (action.type === "list_files") {
2792
- tui.log.info(`\u{1F4C2} Scanning dir: ${colors.dim(action.path || ".")}`);
2592
+ tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
2793
2593
  const result = handleListFiles(action.path || ".");
2794
2594
  executionResults += `[Action list_files(${action.path}) Result]:
2795
2595
  ${result}
@@ -2803,7 +2603,6 @@ ${result}
2803
2603
 
2804
2604
  `;
2805
2605
  } else if (action.type === "search_file") {
2806
- tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
2807
2606
  const result = handleSearchFile(action.path || "");
2808
2607
  executionResults += `[Action search_file(${action.path}) Result]:
2809
2608
  ${result}
@@ -2812,26 +2611,16 @@ ${result}
2812
2611
  } else if (action.type === "run_command") {
2813
2612
  const cmd = action.command || "";
2814
2613
  tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
2815
- let approved = false;
2816
- if (autoApprovals.commands) {
2817
- approved = true;
2818
- tui.log.success(`\u26A1 Auto-Approved Command: ${cmd}`);
2819
- } else {
2614
+ let approved = autoApprovals.commands;
2615
+ if (!approved) {
2820
2616
  const choice = await tui.select({
2821
2617
  message: `Execute: ${cmd}?`,
2822
- options: [
2823
- { value: "yes", label: "Yes (Execute once)" },
2824
- { value: "always", label: "Yes (Output & Auto-Approve Commands for Session)" },
2825
- { value: "no", label: "No (Skip)" }
2826
- ]
2618
+ options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
2827
2619
  });
2828
2620
  if (choice === "always") {
2829
2621
  autoApprovals.commands = true;
2830
2622
  approved = true;
2831
- tui.log.success("\u26A1 COMMANDS Auto-Approval ENABLED for this session.");
2832
- } else if (choice === "yes") {
2833
- approved = true;
2834
- }
2623
+ } else if (choice === "yes") approved = true;
2835
2624
  }
2836
2625
  if (approved) {
2837
2626
  const result = await handleRunCommand(cmd);
@@ -2845,417 +2634,159 @@ ${result}
2845
2634
  `;
2846
2635
  }
2847
2636
  } else if (["create_file", "modify_file"].includes(action.type)) {
2848
- const isCreate = action.type === "create_file";
2849
2637
  const filePath = action.path || "";
2850
- tui.log.warning(`
2851
- \u{1F916} Agent wants to ${isCreate ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
2852
- if (action.content) {
2853
- const preview = action.content.length > 500 ? action.content.substring(0, 500) + "... (truncated)" : action.content;
2854
- console.log(colors.dim("--- Content ---\n") + preview + "\n" + colors.dim("---------------"));
2855
- }
2856
- if (!isCreate && action.line_range && !action.confirmed) {
2857
- tui.log.info("\u{1F6E1}\uFE0F Generation Preview for Agent Verification...");
2858
- const [start, end] = action.line_range;
2859
- const previewDiff = await generateFilePreview(filePath, start, end, action.content || "");
2860
- executionResults += `[Action modify_file]: PENDING CONFIRMATION.
2861
-
2862
- ${previewDiff}
2863
-
2864
- `;
2865
- executionResults += `USER/SYSTEM INSTRUCTION: Please review the above preview carefully.
2866
- 1. If the context and changes look correct, call 'modify_file' again with the SAME parameters AND "confirmed": true.
2867
- 2. If the context is wrong (e.g. wrong line numbers), call 'read_file' to check line numbers again, then correct your request.
2868
-
2869
- `;
2870
- tui.log.warning("\u26A0\uFE0F Sent Preview to Agent for confirmation first.");
2871
- waitingForUser = false;
2872
- continue;
2873
- }
2874
- let approved = false;
2875
- if (autoApprovals.files) {
2876
- approved = true;
2877
- tui.log.success(`\u26A1 Auto-Approved File Action: ${filePath}`);
2878
- } else {
2638
+ tui.log.warning(`\u{1F4DD} ${action.type === "create_file" ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
2639
+ let approved = autoApprovals.files;
2640
+ if (!approved) {
2879
2641
  const choice = await tui.select({
2880
2642
  message: `Approve changes to ${filePath}?`,
2881
- options: [
2882
- { value: "yes", label: "Yes (Approve once)" },
2883
- { value: "always", label: "Yes (Approve & Auto-Approve FILES for Session)" },
2884
- { value: "no", label: "No (Skip)" }
2885
- ]
2643
+ options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
2886
2644
  });
2887
2645
  if (choice === "always") {
2888
2646
  autoApprovals.files = true;
2889
2647
  approved = true;
2890
- tui.log.success("\u26A1 FILE ACTIONS Auto-Approval ENABLED for this session.");
2891
- } else if (choice === "yes") {
2892
- approved = true;
2893
- }
2648
+ } else if (choice === "yes") approved = true;
2894
2649
  }
2895
2650
  if (approved) {
2896
- if (filePath) {
2897
- const targetPath = path9.resolve(projectRoot, filePath);
2898
- const dir = path9.dirname(targetPath);
2651
+ if (action.type === "create_file") {
2652
+ const dir = path8.dirname(path8.resolve(projectRoot, filePath));
2899
2653
  if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2900
- if (isCreate) {
2901
- const BOM = "\uFEFF";
2902
- const contentToWrite = action.content || "";
2903
- const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
2904
- fs7.writeFileSync(targetPath, finalContent, { encoding: "utf-8" });
2905
- tui.log.success(`\u2705 Created: ${filePath}`);
2906
- executionResults += `[Action create_file(${filePath})]: Success
2907
-
2908
- `;
2909
- if (filePath.endsWith("tech-spec.md")) specUpdated = true;
2910
- const config = ConfigManager.getInstance().getConfig();
2911
- const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
2912
- if (validationEnabled) {
2913
- const ext = path9.extname(filePath);
2914
- if ([".ts", ".tsx"].includes(ext)) {
2915
- tui.log.info("\u{1F50D} Validating TypeScript...");
2916
- const validation = await validateTypeScript(filePath);
2917
- if (!validation.valid) {
2918
- tui.log.error("\u274C TypeScript validation failed");
2919
- executionResults += `
2920
- [TYPESCRIPT VALIDATION FAILED]:
2921
- ${validation.error}
2922
-
2923
- `;
2924
- executionResults += `CRITICAL: The file has been saved but contains errors. Read the file again to verify line numbers before attempting to fix. Do not guess line numbers.
2925
- `;
2926
- } else {
2927
- tui.log.success("\u2705 TypeScript OK");
2928
- }
2929
- }
2930
- if (ext === ".html") {
2931
- tui.log.info("\u{1F50D} Validating HTML...");
2932
- const validation = validateHtmlTagBalance(filePath);
2933
- if (!validation.valid) {
2934
- tui.log.error("\u274C HTML validation failed");
2935
- executionResults += `
2936
- [HTML VALIDATION FAILED]:
2937
- ${validation.error}
2938
-
2939
- `;
2940
- executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2941
- `;
2942
- } else {
2943
- tui.log.success("\u2705 HTML OK");
2944
- }
2945
- }
2946
- }
2947
- } else {
2948
- let success = false;
2949
- tui.log.info(`\u{1F50D} Debug modify_file: line_range=${JSON.stringify(action.line_range)}, type=${typeof action.line_range}, isArray=${Array.isArray(action.line_range)}`);
2950
- if (action.line_range && Array.isArray(action.line_range) && action.line_range.length === 2) {
2951
- const [start, end] = action.line_range;
2952
- success = replaceLineRange(filePath, start, end, action.content || "", tui);
2953
- } else if (action.target_content) {
2954
- success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
2955
- } else {
2956
- tui.log.error("\u274C Missing line_range (recommended) or target_content for modification.");
2957
- executionResults += `[Action modify_file]: Failed. Missing line_range or target_content.
2654
+ fs7.writeFileSync(path8.resolve(projectRoot, filePath), action.content || "", "utf-8");
2655
+ executionResults += `[Action create_file]: Success
2958
2656
 
2959
2657
  `;
2960
- }
2961
- if (success) {
2962
- executionResults += `[Action modify_file(${filePath})]: Success
2963
-
2964
- `;
2965
- if (filePath.endsWith("tech-spec.md")) specUpdated = true;
2966
- const config = ConfigManager.getInstance().getConfig();
2967
- const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
2968
- if (validationEnabled) {
2969
- const ext = path9.extname(filePath);
2970
- if ([".ts", ".tsx"].includes(ext)) {
2971
- tui.log.info("\u{1F50D} Validating TypeScript...");
2972
- const validation = await validateTypeScript(filePath);
2973
- if (!validation.valid) {
2974
- tui.log.error("\u274C TypeScript validation failed");
2975
- executionResults += `
2976
- [TYPESCRIPT VALIDATION FAILED]:
2977
- ${validation.error}
2658
+ } else {
2659
+ let success = false;
2660
+ if (action.line_range) success = replaceLineRange(filePath, action.line_range[0], action.line_range[1], action.content || "", tui);
2661
+ else if (action.target_content) success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
2662
+ executionResults += success ? `[Action modify_file]: Success
2978
2663
 
2979
- `;
2980
- executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2981
- `;
2982
- } else {
2983
- tui.log.success("\u2705 TypeScript OK");
2984
- }
2985
- }
2986
- if (ext === ".html") {
2987
- tui.log.info("\u{1F50D} Validating HTML...");
2988
- const validation = validateHtmlTagBalance(filePath);
2989
- if (!validation.valid) {
2990
- tui.log.error("\u274C HTML validation failed");
2991
- executionResults += `
2992
- [HTML VALIDATION FAILED]:
2993
- ${validation.error}
2664
+ ` : `[Action modify_file]: Failed
2994
2665
 
2995
2666
  `;
2996
- executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2997
- `;
2998
- } else {
2999
- tui.log.success("\u2705 HTML OK");
3000
- }
3001
- }
3002
- }
3003
- } else if (!action.line_range && !action.target_content) {
3004
- } else {
3005
- executionResults += `[Action modify_file(${filePath})]: FAILED. Target content not found or ambiguous. Read the file again to ensure accuracy.
3006
-
3007
- `;
3008
- }
3009
- }
3010
2667
  }
3011
- } else {
3012
- tui.log.error("\u274C Denied.");
3013
- executionResults += `[Action ${action.type}]: User Denied.
2668
+ const val = await validateTypeScript(path8.resolve(projectRoot, filePath));
2669
+ if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
3014
2670
 
3015
2671
  `;
3016
- }
3017
- } else if (action.type === "search_ast") {
3018
- tui.log.info(`\u{1F50D} Searching AST: ${action.pattern} in ${action.file_path || action.path}`);
3019
- const result = await astGrepSearch(
3020
- action.pattern || "",
3021
- action.file_path || action.path || "",
3022
- action.language || "typescript",
3023
- // default TS
3024
- tui
3025
- );
3026
- executionResults += `[Action search_ast]:
3027
- ${result}
3028
-
3029
- `;
3030
- } else if (action.type === "modify_ast") {
3031
- const targetPath = action.file_path || action.path || "";
3032
- tui.log.step(`\u{1F504} [AST] Modifying: ${targetPath}`);
3033
- tui.log.info(`Pattern: ${colors.primary(action.pattern || "")}`);
3034
- tui.log.info(`Fix: ${colors.success(action.fix || "")}`);
3035
- const approved = await tui.confirm({ message: "Execute this AST modification?" });
3036
- if (approved) {
3037
- const success = await astGrepRewrite(
3038
- action.pattern || "",
3039
- action.fix || "",
3040
- targetPath,
3041
- action.language || "typescript",
3042
- tui
3043
- );
3044
- if (success) {
3045
- executionResults += `[Action modify_ast]: Success.
3046
-
3047
- `;
3048
- if (targetPath.endsWith("tech-spec.md")) specUpdated = true;
3049
- const ext = path9.extname(targetPath);
3050
- if ([".ts", ".tsx"].includes(ext)) {
3051
- const validation = await validateTypeScript(targetPath);
3052
- if (!validation.valid) {
3053
- executionResults += `
3054
- [TYPESCRIPT VALIDATION FAILED]:
3055
- ${validation.error}
3056
-
3057
- `;
3058
- }
3059
- }
3060
- } else {
3061
- executionResults += `[Action modify_ast]: Failed. Check logs.
3062
-
3063
- `;
3064
- }
3065
2672
  } else {
3066
- tui.log.error("\u274C Denied.");
3067
- executionResults += `[Action modify_ast]: User Denied.
2673
+ executionResults += `[Action ${action.type}]: User Denied.
3068
2674
 
3069
2675
  `;
3070
2676
  }
3071
2677
  } else if (action.type.startsWith("ast_")) {
3072
- const targetPath = action.file_path || action.path || "";
3073
- tui.log.info(`\u{1F527} [AST] Action: ${colors.bold(action.type)} on ${targetPath}`);
3074
- if (action.type === "ast_list_structure") {
3075
- const result = await astListStructure(targetPath);
3076
- executionResults += `[Action ast_list_structure]:
3077
- ${result}
3078
-
3079
- `;
3080
- } else {
3081
- let approved = false;
3082
- if (autoApprovals.files) {
3083
- approved = true;
3084
- tui.log.success(`\u26A1 Auto-Approved AST Action: ${action.type}`);
2678
+ try {
2679
+ let result = "";
2680
+ if (action.type === "ast_list_structure") {
2681
+ result = await astListStructure(action.path || "");
2682
+ } else if (action.type === "ast_get_method") {
2683
+ result = await astGetMethod(action.path || "", action.class_name || "", action.method_name || "");
2684
+ } else if (action.type === "ast_add_method") {
2685
+ const success = await astAddMethod(action.path || "", action.class_name || "", action.method_code || "");
2686
+ result = success ? "Method added successfully." : "Failed to add method.";
2687
+ } else if (action.type === "ast_modify_method") {
2688
+ const success = await astModifyMethod(action.path || "", action.class_name || "", action.method_name || "", action.new_body || "");
2689
+ result = success ? "Method modified successfully." : "Failed to modify method.";
2690
+ } else if (action.type === "ast_remove_method") {
2691
+ const success = await astRemoveMethod(action.path || "", action.class_name || "", action.method_name || "");
2692
+ result = success ? "Method removed successfully." : "Failed to remove method.";
2693
+ } else if (action.type === "ast_add_class") {
2694
+ const success = await astAddClass(action.path || "", action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
2695
+ result = success ? "Class added successfully." : "Failed to add class.";
2696
+ } else if (action.type === "ast_add_property") {
2697
+ const success = await astAddProperty(action.path || "", action.class_name || "", action.property_code || "");
2698
+ result = success ? "Property added successfully." : "Failed to add property.";
2699
+ } else if (action.type === "ast_remove_property") {
2700
+ const success = await astRemoveProperty(action.path || "", action.class_name || "", action.property_name || "");
2701
+ result = success ? "Property removed successfully." : "Failed to remove property.";
2702
+ } else if (action.type === "ast_add_decorator") {
2703
+ const success = await astAddDecorator(action.path || "", action.class_name || "", action.decorator_code || "");
2704
+ result = success ? "Decorator added successfully." : "Failed to add decorator.";
2705
+ } else if (action.type === "ast_add_interface") {
2706
+ const success = await astAddInterface(action.path || "", action.interface_code || "");
2707
+ result = success ? "Interface added successfully." : "Failed to add interface.";
2708
+ } else if (action.type === "ast_add_type_alias") {
2709
+ const success = await astAddTypeAlias(action.path || "", action.type_code || "");
2710
+ result = success ? "Type alias added successfully." : "Failed to add type alias.";
2711
+ } else if (action.type === "ast_add_function") {
2712
+ const success = await astAddFunction(action.path || "", action.function_code || "");
2713
+ result = success ? "Function added successfully." : "Failed to add function.";
2714
+ } else if (action.type === "ast_remove_function") {
2715
+ const success = await astRemoveFunction(action.path || "", action.function_name || "");
2716
+ result = success ? "Function removed successfully." : "Failed to remove function.";
2717
+ } else if (action.type === "ast_add_import") {
2718
+ const success = await astAddImport(action.path || "", action.import_statement || "");
2719
+ result = success ? "Import added successfully." : "Failed to add import.";
2720
+ } else if (action.type === "ast_remove_import") {
2721
+ const success = await astRemoveImport(action.path || "", action.module_path || "");
2722
+ result = success ? "Import removed successfully." : "Failed to remove import.";
2723
+ } else if (action.type === "ast_organize_imports") {
2724
+ const success = await astOrganizeImports(action.path || "");
2725
+ result = success ? "Imports organized successfully." : "Failed to organize imports.";
3085
2726
  } else {
3086
- const choice = await tui.select({
3087
- message: `Execute ${action.type} on ${targetPath}?`,
3088
- options: [
3089
- { value: "yes", label: "Yes" },
3090
- { value: "always", label: "Yes (Auto-Approve ALL Files)" },
3091
- { value: "no", label: "No" }
3092
- ]
3093
- });
3094
- if (choice === "always") {
3095
- autoApprovals.files = true;
3096
- approved = true;
3097
- tui.log.success("\u26A1 FILE ACTIONS Auto-Approval ENABLED for this session.");
3098
- } else if (choice === "yes") {
3099
- approved = true;
3100
- }
2727
+ result = `Unknown AST action: ${action.type}`;
3101
2728
  }
3102
- if (approved) {
3103
- try {
3104
- let success = false;
3105
- switch (action.type) {
3106
- case "ast_add_class":
3107
- success = await astAddClass(targetPath, action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
3108
- break;
3109
- case "ast_add_method":
3110
- success = await astAddMethod(targetPath, action.class_name || "", action.method_code || "");
3111
- break;
3112
- case "ast_add_property":
3113
- success = await astAddProperty(targetPath, action.class_name || "", action.property_code || "");
3114
- break;
3115
- case "ast_remove_property":
3116
- success = await astRemoveProperty(targetPath, action.class_name || "", action.property_name || "");
3117
- break;
3118
- case "ast_modify_method":
3119
- success = await astModifyMethod(targetPath, action.class_name || "", action.method_name || "", action.new_body || "");
3120
- break;
3121
- case "ast_remove_method":
3122
- success = await astRemoveMethod(targetPath, action.class_name || "", action.method_name || "");
3123
- break;
3124
- case "ast_add_decorator":
3125
- success = await astAddDecorator(targetPath, action.class_name || "", action.decorator_code || "");
3126
- break;
3127
- case "ast_add_interface":
3128
- success = await astAddInterface(targetPath, action.interface_code || "");
3129
- break;
3130
- case "ast_add_type_alias":
3131
- success = await astAddTypeAlias(targetPath, action.type_code || "");
3132
- break;
3133
- case "ast_add_function":
3134
- success = await astAddFunction(targetPath, action.function_code || "");
3135
- break;
3136
- case "ast_remove_function":
3137
- success = await astRemoveFunction(targetPath, action.function_name || "");
3138
- break;
3139
- case "ast_add_import":
3140
- success = await astAddImport(targetPath, action.import_statement || "");
3141
- break;
3142
- case "ast_remove_import":
3143
- success = await astRemoveImport(targetPath, action.module_path || "");
3144
- break;
3145
- case "ast_organize_imports":
3146
- success = await astOrganizeImports(targetPath);
3147
- break;
3148
- }
3149
- if (success) {
3150
- executionResults += `[Action ${action.type}]: Success
2729
+ executionResults += `[Action ${action.type} Result]:
2730
+ ${result}
3151
2731
 
3152
2732
  `;
3153
- const config = ConfigManager.getInstance().getConfig();
3154
- const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
3155
- if (validationEnabled) {
3156
- const ext = path9.extname(targetPath);
3157
- if ([".ts", ".tsx"].includes(ext)) {
3158
- const validation = await validateTypeScript(targetPath);
3159
- if (!validation.valid) {
3160
- executionResults += `
3161
- [TYPESCRIPT VALIDATION FAILED]:
3162
- ${validation.error}
2733
+ tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
2734
+ } catch (e) {
2735
+ executionResults += `[Action ${action.type} Failed]: ${e.message}
3163
2736
 
3164
2737
  `;
3165
- } else {
3166
- tui.log.success("\u2705 TypeScript OK");
3167
- }
3168
- }
3169
- }
3170
- } else {
3171
- executionResults += `[Action ${action.type}]: Failed (internal editor returned false).
2738
+ tui.log.error(`\u274C AST Action Error: ${e.message}`);
2739
+ }
2740
+ } else if (action.type === "search_ast") {
2741
+ const result = await astGrepSearch(action.pattern || "", action.path || "", action.language || "typescript", tui);
2742
+ executionResults += `[Action search_ast Result]:
2743
+ ${result}
3172
2744
 
3173
2745
  `;
3174
- }
3175
- } catch (e) {
3176
- executionResults += `[Action ${action.type}]: Exception: ${e.message}
2746
+ } else if (action.type === "modify_ast") {
2747
+ const success = await astGrepRewrite(action.pattern || "", action.fix || "", action.path || "", action.language || "typescript", tui);
2748
+ executionResults += success ? `[Action modify_ast]: Success
3177
2749
 
3178
- `;
3179
- }
3180
- } else {
3181
- executionResults += `[Action ${action.type}]: User Denied.
2750
+ ` : `[Action modify_ast]: Failed
3182
2751
 
3183
2752
  `;
3184
- }
3185
- }
3186
2753
  }
3187
2754
  }
3188
- const previousState = specState;
3189
- specState = analyzeSpecState(projectRoot);
3190
- let systemInjection = "";
3191
2755
  if (executionResults) {
3192
- if (previousState.status === "PENDING" && specState.status === "PENDING" && previousState.nextTask !== specState.nextTask) {
3193
- systemInjection = `
3194
- \u{1F389} Task "${previousState.nextTask}" COMPLETED! Next up: "${specState.nextTask}".
3195
- `;
3196
- } else if (previousState.status === "PENDING" && specState.status === "PENDING" && previousState.nextTask === specState.nextTask) {
3197
- if (!specUpdated && stepCount % 3 === 0) {
3198
- systemInjection = `
3199
- Reminder: You are still working on "${specState.nextTask}". Don't forget to mark it [x] in 'tech-spec.md' when done.
3200
- `;
3201
- }
3202
- } else if (previousState.status === "MISSING" && specState.status === "PENDING") {
3203
- systemInjection = `
3204
- \u2705 Spec Created! Starting execution of: "${specState.nextTask}".
3205
- `;
3206
- }
3207
2756
  if (waitingForUser) {
3208
2757
  const userReply = await tui.text({ message: "Your answer:" });
3209
2758
  if (tui.isCancel(userReply)) {
3210
2759
  keepGoing = false;
3211
2760
  break;
3212
2761
  }
3213
- nextPrompt = `${executionResults}${systemInjection}
2762
+ nextPrompt = `${executionResults}
3214
2763
  User Reply: ${userReply}`;
3215
2764
  } else {
3216
- nextPrompt = `${executionResults}${systemInjection}
3217
- [System]: Continue.`;
2765
+ nextPrompt = `${executionResults}
2766
+ [System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
3218
2767
  tui.log.info(colors.dim("Processing results..."));
3219
2768
  }
2769
+ } else if (!keepGoing) {
3220
2770
  } else if (waitingForUser) {
3221
- const userReply = await tui.text({ message: "Your answer:" });
3222
- if (tui.isCancel(userReply)) {
3223
- keepGoing = false;
3224
- break;
3225
- }
3226
- nextPrompt = userReply;
3227
2771
  } else {
3228
- if (response.message) {
3229
- tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
3230
- console.log(response.message);
3231
- const userReply = await tui.text({ message: "Your answer:" });
3232
- if (tui.isCancel(userReply)) {
3233
- keepGoing = false;
3234
- } else {
3235
- nextPrompt = userReply;
3236
- }
3237
- } else {
3238
- tui.log.warning("Agent took no actions and sent no message.");
3239
- nextPrompt = "Please proceed or ask for clarification.";
3240
- }
2772
+ if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
3241
2773
  }
3242
2774
  } else {
3243
- tui.log.warning("Invalid response from agent (no actions).");
3244
- nextPrompt = "Error: No valid actions returned. Please try again with JSON format.";
2775
+ tui.log.warning("No response received from agent.");
3245
2776
  }
3246
2777
  } catch (e) {
3247
- spinner.stop("Error");
3248
2778
  tui.log.error(e.message);
3249
- FileLogger.log("DEV_AGENT", "Main Loop Error", e);
3250
2779
  keepGoing = false;
2780
+ return { success: false, summary: `Error: ${e.message}` };
3251
2781
  }
3252
2782
  }
3253
- tui.outro("\u{1F44B} Shark Dev Session Ended");
2783
+ tui.log.success("\u2705 Task Scope Completed");
2784
+ return { success: true, summary: finalSummary || "Task completed without summary." };
3254
2785
  }
3255
- async function callDevAgentApi(prompt, onChunk) {
2786
+ async function callDevAgentApi(prompt, onChunk, conversationKey = AGENT_TYPE4) {
3256
2787
  const realm = await getActiveRealm();
3257
2788
  const token = await ensureValidToken(realm);
3258
- const conversationId = await conversationManager.getConversationId(AGENT_TYPE4);
2789
+ const conversationId = await conversationManager.getConversationId(conversationKey);
3259
2790
  const payload = {
3260
2791
  user_prompt: prompt,
3261
2792
  streaming: true,
@@ -3284,21 +2815,247 @@ async function callDevAgentApi(prompt, onChunk) {
3284
2815
  });
3285
2816
  const parsed = parseAgentResponse(raw);
3286
2817
  if (parsed.conversation_id) {
3287
- await conversationManager.saveConversationId(AGENT_TYPE4, parsed.conversation_id);
2818
+ await conversationManager.saveConversationId(conversationKey, parsed.conversation_id);
3288
2819
  }
3289
2820
  return parsed;
3290
2821
  }
3291
2822
 
2823
+ // src/core/workflow/task-manager.ts
2824
+ import fs8 from "fs";
2825
+ import path9 from "path";
2826
+ var TaskManager = class {
2827
+ projectRoot;
2828
+ specPath;
2829
+ constructor(projectRoot = process.cwd()) {
2830
+ this.projectRoot = projectRoot;
2831
+ this.specPath = path9.resolve(this.projectRoot, "tech-spec.md");
2832
+ }
2833
+ /**
2834
+ * Reads the tech-spec.md file and analyzes its current state.
2835
+ */
2836
+ analyzeSpecState() {
2837
+ if (!fs8.existsSync(this.specPath)) {
2838
+ return { status: "MISSING", allTasks: [] };
2839
+ }
2840
+ const content = fs8.readFileSync(this.specPath, "utf-8");
2841
+ const lines = content.split("\n");
2842
+ const tasks = [];
2843
+ let taskIndex = 1;
2844
+ lines.forEach((line, index) => {
2845
+ const trimmed = line.trim();
2846
+ const pendingMatch = trimmed.match(/^- \[ \] (.*)/);
2847
+ const completedMatch = trimmed.match(/^- \[x\] (.*)/i);
2848
+ const progressMatch = trimmed.match(/^- \[\/\] (.*)/);
2849
+ if (pendingMatch) {
2850
+ tasks.push({
2851
+ id: `task-${taskIndex++}`,
2852
+ description: pendingMatch[1].trim(),
2853
+ status: "PENDING",
2854
+ line_number: index
2855
+ });
2856
+ } else if (completedMatch) {
2857
+ tasks.push({
2858
+ id: `task-${taskIndex++}`,
2859
+ description: completedMatch[1].trim(),
2860
+ status: "COMPLETED",
2861
+ line_number: index
2862
+ });
2863
+ } else if (progressMatch) {
2864
+ tasks.push({
2865
+ id: `task-${taskIndex++}`,
2866
+ description: progressMatch[1].trim(),
2867
+ status: "IN_PROGRESS",
2868
+ line_number: index
2869
+ });
2870
+ }
2871
+ });
2872
+ let nextTask = tasks.find((t2) => t2.status === "IN_PROGRESS");
2873
+ if (!nextTask) {
2874
+ nextTask = tasks.find((t2) => t2.status === "PENDING");
2875
+ }
2876
+ const status = !nextTask && tasks.length > 0 && tasks.every((t2) => t2.status === "COMPLETED") ? "COMPLETED" : "PENDING";
2877
+ return {
2878
+ status: tasks.length === 0 ? "MISSING" : status,
2879
+ // Empty file is effectively "pending creation" but we treat as missing content logic elsewhere
2880
+ nextTask,
2881
+ allTasks: tasks
2882
+ };
2883
+ }
2884
+ /**
2885
+ * Marks a specific task as COMPLETED in the file.
2886
+ * Uses line-based replacement to be safe.
2887
+ */
2888
+ markTaskAsDone(taskId) {
2889
+ const state = this.analyzeSpecState();
2890
+ const task = state.allTasks.find((t2) => t2.id === taskId);
2891
+ if (!task) {
2892
+ console.error(`Task ${taskId} not found.`);
2893
+ return false;
2894
+ }
2895
+ const content = fs8.readFileSync(this.specPath, "utf-8");
2896
+ const lines = content.split("\n");
2897
+ const targetLine = lines[task.line_number];
2898
+ if (!targetLine.includes(task.description)) {
2899
+ console.error(`Concurrency Error: Task line content mistmatch. Expected "${task.description}" at line ${task.line_number}.`);
2900
+ return false;
2901
+ }
2902
+ const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
2903
+ lines[task.line_number] = newLine;
2904
+ fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
2905
+ return true;
2906
+ }
2907
+ /**
2908
+ * Marks a task as IN_PROGRESS.
2909
+ */
2910
+ markTaskInProgress(taskId) {
2911
+ const state = this.analyzeSpecState();
2912
+ const task = state.allTasks.find((t2) => t2.id === taskId);
2913
+ if (!task) return false;
2914
+ const content = fs8.readFileSync(this.specPath, "utf-8");
2915
+ const lines = content.split("\n");
2916
+ let targetLine = lines[task.line_number];
2917
+ targetLine = targetLine.replace("- [ ]", "- [/]");
2918
+ lines[task.line_number] = targetLine;
2919
+ fs8.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
2920
+ return true;
2921
+ }
2922
+ /**
2923
+ * Completely updates the spec file content (used by Spec Agent).
2924
+ */
2925
+ updateSpecContent(newContent) {
2926
+ fs8.writeFileSync(this.specPath, newContent, "utf-8");
2927
+ }
2928
+ getSpecPath() {
2929
+ return this.specPath;
2930
+ }
2931
+ };
2932
+
3292
2933
  // src/commands/dev.ts
3293
- var devCommand = new Command3("dev").description("Starts the Shark Developer Agent (Shark Dev)").option("-t, --task <type>", "Initial task description").option("-c, --context <path>", "Path to custom context file").action(async (options) => {
3294
- await interactiveDeveloperAgent(options);
2934
+ 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) => {
2935
+ const taskManager = new TaskManager(process.cwd());
2936
+ let state = taskManager.analyzeSpecState();
2937
+ if (state.status === "MISSING") {
2938
+ tui.log.warning("\u{1F4CB} No tech-spec.md found.");
2939
+ const confirm = await tui.confirm({ message: "Create a new Specification (Tech Spec)?" });
2940
+ if (confirm) {
2941
+ await interactiveSpecificationAgent({
2942
+ briefingPath: options.task ? void 0 : void 0
2943
+ // If task provided, maybe write a temp briefing? For now standard flow.
2944
+ });
2945
+ state = taskManager.analyzeSpecState();
2946
+ if (state.status === "MISSING") {
2947
+ tui.log.error("\u274C Spec creation aborted or failed.");
2948
+ return;
2949
+ }
2950
+ } else {
2951
+ return;
2952
+ }
2953
+ }
2954
+ let keepOrchestrating = true;
2955
+ let burnMode = false;
2956
+ let contextHistory = "";
2957
+ tui.intro("\u{1F988} Shark Orchestrator V2");
2958
+ while (keepOrchestrating) {
2959
+ state = taskManager.analyzeSpecState();
2960
+ if (state.status === "COMPLETED") {
2961
+ tui.log.success("\u{1F389} All tasks in tech-spec.md are COMPLETED!");
2962
+ const choice = await tui.select({
2963
+ message: "What next?",
2964
+ options: [
2965
+ { value: "exit", label: "Exit" },
2966
+ { value: "new_spec", label: "New Specification (Reset)" }
2967
+ ]
2968
+ });
2969
+ if (choice === "new_spec") {
2970
+ await interactiveSpecificationAgent();
2971
+ contextHistory = "";
2972
+ continue;
2973
+ } else {
2974
+ keepOrchestrating = false;
2975
+ break;
2976
+ }
2977
+ }
2978
+ const currentTask = state.nextTask;
2979
+ if (!currentTask) {
2980
+ tui.log.error("Something went wrong. Status is not completed but no next task found.");
2981
+ break;
2982
+ }
2983
+ tui.log.info(`
2984
+ \u{1F449} **NEXT TASK**: ${colors.bold(currentTask.description)}`);
2985
+ if (!burnMode) {
2986
+ const action = await tui.select({
2987
+ message: "Orchestration Checkpoint:",
2988
+ options: [
2989
+ { value: "execute", label: "\u{1F680} Execute Task (Start)" },
2990
+ { value: "burn", label: "\u{1F525} Burn Mode (Auto-Execute Remaining)" },
2991
+ { value: "pivot", label: "\u{1F527} Pivot/Correct (Edit Spec)" },
2992
+ { value: "skip", label: "\u23ED\uFE0F Skip Task (Mark Done without Executing)" },
2993
+ { value: "stop", label: "\u{1F6D1} Stop Session" }
2994
+ ]
2995
+ });
2996
+ if (action === "stop") {
2997
+ keepOrchestrating = false;
2998
+ break;
2999
+ } else if (action === "burn") {
3000
+ burnMode = true;
3001
+ } else if (action === "skip") {
3002
+ taskManager.markTaskAsDone(currentTask.id);
3003
+ tui.log.info("Task skipped.");
3004
+ continue;
3005
+ } else if (action === "pivot") {
3006
+ tui.log.info("Transferring control to Specification Agent...");
3007
+ await interactiveSpecificationAgent({
3008
+ initialContext: `User requested pivot after tasks:
3009
+ ${contextHistory}`
3010
+ });
3011
+ continue;
3012
+ }
3013
+ }
3014
+ taskManager.markTaskInProgress(currentTask.id);
3015
+ tui.log.info(`\u26A1 Starting Micro-Context for Task: "${currentTask.description}"`);
3016
+ const result = await interactiveDeveloperAgent({
3017
+ taskId: currentTask.id,
3018
+ taskInstruction: currentTask.description,
3019
+ history: contextHistory,
3020
+ context: options.context
3021
+ });
3022
+ if (result.success) {
3023
+ tui.log.success(`\u2705 Task Completed: ${currentTask.description}`);
3024
+ taskManager.markTaskAsDone(currentTask.id);
3025
+ contextHistory += `
3026
+ [Task "${currentTask.description}" completed]: ${result.summary}`;
3027
+ } else {
3028
+ tui.log.error(`\u274C Task Failed: ${result.summary}`);
3029
+ burnMode = false;
3030
+ const recovery = await tui.select({
3031
+ message: "Task Failed. Recovery Action:",
3032
+ options: [
3033
+ { value: "retry", label: "Retry (Run Agent Again)" },
3034
+ { value: "pivot", label: "Pivot (Adjust Spec/Instructions)" },
3035
+ { value: "ignore", label: "Ignore (Mark Done anyway)" },
3036
+ { value: "stop", label: "Stop" }
3037
+ ]
3038
+ });
3039
+ if (recovery === "stop") break;
3040
+ if (recovery === "ignore") taskManager.markTaskAsDone(currentTask.id);
3041
+ if (recovery === "pivot") {
3042
+ await interactiveSpecificationAgent({
3043
+ initialContext: `Task "${currentTask.description}" FAILED.
3044
+ Error: ${result.summary}
3045
+ History:
3046
+ ${contextHistory}`
3047
+ });
3048
+ }
3049
+ }
3050
+ }
3051
+ tui.outro("\u{1F988} Orchestration Finished.");
3295
3052
  });
3296
3053
 
3297
3054
  // src/commands/qa.ts
3298
3055
  import { Command as Command4 } from "commander";
3299
3056
 
3300
3057
  // src/core/agents/qa-agent.ts
3301
- import fs8 from "fs";
3058
+ import fs9 from "fs";
3302
3059
  import path10 from "path";
3303
3060
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3304
3061
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -3369,8 +3126,8 @@ async function runQAAgent(options) {
3369
3126
  let projectContext = "";
3370
3127
  try {
3371
3128
  const contextPath = path10.join(process.cwd(), "_sharkrc", "project-context.md");
3372
- if (fs8.existsSync(contextPath)) {
3373
- projectContext = fs8.readFileSync(contextPath, "utf-8");
3129
+ if (fs9.existsSync(contextPath)) {
3130
+ projectContext = fs9.readFileSync(contextPath, "utf-8");
3374
3131
  tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
3375
3132
  }
3376
3133
  } catch (e) {
@@ -3483,7 +3240,7 @@ ${projectContext}
3483
3240
  const BOM = "\uFEFF";
3484
3241
  const contentToWrite = action.content;
3485
3242
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
3486
- fs8.writeFileSync(fullPath, finalContent, { encoding: "utf-8" });
3243
+ fs9.writeFileSync(fullPath, finalContent, { encoding: "utf-8" });
3487
3244
  tui.log.success(`File created: ${action.path}`);
3488
3245
  result = "File created successfully.";
3489
3246
  }