shark-ai 0.3.3 → 0.3.5

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
@@ -730,6 +730,7 @@ async function interactiveBusinessAnalyst() {
730
730
 
731
731
  // src/core/agents/specification-agent.ts
732
732
  import fs4 from "fs";
733
+ import path4 from "path";
733
734
 
734
735
  // src/core/agents/agent-tools.ts
735
736
  import fs3 from "fs";
@@ -850,41 +851,67 @@ function getAgentId2(overrideId) {
850
851
  async function interactiveSpecificationAgent(options = {}) {
851
852
  FileLogger.init();
852
853
  tui.intro("\u{1F3D7}\uFE0F Specification Agent");
853
- let briefingContent = "";
854
- let briefingPath = options.briefingPath;
855
- if (!briefingPath) {
856
- const files = fs4.readdirSync(process.cwd());
857
- const defaultBriefing = files.find((f) => f.endsWith("_briefing.md"));
858
- briefingPath = await tui.text({
859
- message: "Path to Briefing file (Leave empty to skip)",
860
- initialValue: defaultBriefing || "",
861
- placeholder: "e.g., todo-list_briefing.md",
862
- validate: (val) => {
863
- if (val && !fs4.existsSync(val)) return "File not found";
864
- }
865
- });
866
- }
867
- if (tui.isCancel(briefingPath)) return;
868
- if (briefingPath) {
854
+ const projectRoot = process.cwd();
855
+ let contextContent = "";
856
+ const contextPath = path4.resolve(projectRoot, "_sharkrc", "project-context.md");
857
+ if (fs4.existsSync(contextPath)) {
869
858
  try {
870
- briefingContent = fs4.readFileSync(briefingPath, "utf-8");
871
- tui.log.info(`Loaded briefing: ${colors.bold(briefingPath)}`);
859
+ contextContent = fs4.readFileSync(contextPath, "utf-8");
860
+ tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path4.relative(projectRoot, contextPath))}`);
872
861
  } catch (e) {
873
- tui.log.error(`Failed to read briefing: ${e}`);
874
- return;
862
+ tui.log.warning(`Failed to read context file: ${e}`);
875
863
  }
864
+ }
865
+ let briefingContent = "";
866
+ if (options.briefingPath && fs4.existsSync(options.briefingPath)) {
867
+ briefingContent = fs4.readFileSync(options.briefingPath, "utf-8");
868
+ tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
876
869
  } else {
877
- tui.log.info("Skipping briefing file. Starting fresh or exploring existing project.");
870
+ const sharkRcBriefing = path4.resolve(projectRoot, "_sharkrc", "briefing.md");
871
+ if (fs4.existsSync(sharkRcBriefing)) {
872
+ briefingContent = fs4.readFileSync(sharkRcBriefing, "utf-8");
873
+ tui.log.info(`\u{1F4C4} Standard Briefing loaded: ${colors.dim("_sharkrc/briefing.md")}`);
874
+ } else {
875
+ tui.log.info(colors.dim("\u2139\uFE0F No briefing file found in _sharkrc/briefing.md. Starting in interactive mode."));
876
+ }
878
877
  }
879
- const initialPrompt = briefingContent ? `
880
- Tenho o seguinte Documento de Briefing de Neg\xF3cio.
881
- Por favor, analise-o e inicie o processo de defini\xE7\xE3o da Especifica\xE7\xE3o T\xE9cnica.
878
+ let initialPrompt = "";
879
+ if (briefingContent) {
880
+ initialPrompt += `
881
+ Abaixo est\xE1 o **Briefing de Neg\xF3cio** ou Descri\xE7\xE3o da Tarefa.
882
+ Analise-o e ajude-me a definir a Especifica\xE7\xE3o T\xE9cnica (tech-spec.md).
882
883
 
883
- ---
884
+ --- BRIEFING ---
884
885
  ${briefingContent}
885
- ---
886
- `.trim() : "Gostaria de ajuda com uma especifica\xE7\xE3o t\xE9cnica ou explorar este projeto.";
887
- await runSpecLoop(initialPrompt, options.agentId);
886
+ ----------------
887
+ `;
888
+ } else {
889
+ initialPrompt += `
890
+ N\xE3o h\xE1 um documento de briefing formal.
891
+ Por favor, pergunte-me qual \xE9 a tarefa ou funcionalidade que vamos especificar hoje.
892
+ `;
893
+ }
894
+ if (contextContent) {
895
+ initialPrompt += `
896
+ Abaixo est\xE1 o **Contexto do Projeto** atual. Use-o para alinhar a especifica\xE7\xE3o com a arquitetura existente.
897
+
898
+ --- PROJECT CONTEXT ---
899
+ ${contextContent}
900
+ -----------------------
901
+ `;
902
+ }
903
+ initialPrompt += `
904
+
905
+ Seu objetivo final \xE9 gerar o arquivo 'tech-spec.md'.
906
+
907
+ \u26A0\uFE0F ATEN\xC7\xC3O: WORKFLOW DE AN\xC1LISE
908
+ 1. **Entenda**: Alinhe o objetivo com o usu\xE1rio.
909
+ 2. **Explore**: Use 'list_files' e 'read_file' para encontrar os arquivos RELEVANTES para a tarefa.
910
+ 3. **Especifique**: Gere o 'tech-spec.md' citando nomes de arquivos REAIS que voc\xEA leu.
911
+
912
+ N\xE3o gere a spec baseada em suposi\xE7\xF5es. Se vai editar algo, LEIA antes.
913
+ `;
914
+ await runSpecLoop(initialPrompt.trim(), options.agentId);
888
915
  }
889
916
  async function runSpecLoop(initialMessage, overrideAgentId) {
890
917
  let nextPrompt = initialMessage;
@@ -897,14 +924,6 @@ async function runSpecLoop(initialMessage, overrideAgentId) {
897
924
  try {
898
925
  lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
899
926
  responseText += chunk;
900
- try {
901
- if (responseText.trim().startsWith("{")) {
902
- spinner.message(colors.dim("Receiving structured data..."));
903
- } else {
904
- spinner.message(colors.dim("Thinking..."));
905
- }
906
- } catch (e) {
907
- }
908
927
  }, overrideAgentId);
909
928
  spinner.stop("Response received");
910
929
  if (lastResponse && lastResponse.actions) {
@@ -916,36 +935,32 @@ async function runSpecLoop(initialMessage, overrideAgentId) {
916
935
  console.log(action.content);
917
936
  waitingForUser = true;
918
937
  } else if (action.type === "list_files") {
919
- tui.log.info(`\u{1F4C2} List files in: ${colors.bold(action.path || ".")}`);
938
+ tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
920
939
  const result = handleListFiles(action.path || ".");
921
940
  executionResults += `[Action list_files(${action.path}) Result]:
922
941
  ${result}
923
942
 
924
943
  `;
925
- tui.log.info(colors.dim(`Files listed.`));
926
944
  } else if (action.type === "read_file") {
927
- tui.log.info(`\u{1F4D6} Read file: ${colors.bold(action.path || "")}`);
945
+ tui.log.info(`\u{1F4D6} Reading: ${colors.dim(action.path || "")}`);
928
946
  const result = handleReadFile(action.path || "");
929
947
  executionResults += `[Action read_file(${action.path}) Result]:
930
948
  ${result}
931
949
 
932
950
  `;
933
- tui.log.info(colors.dim(`File read.`));
934
951
  } else if (action.type === "search_file") {
935
- tui.log.info(`\u{1F50D} Search file: ${colors.bold(action.path || "")}`);
952
+ tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
936
953
  const result = handleSearchFile(action.path || "");
937
954
  executionResults += `[Action search_file(${action.path}) Result]:
938
955
  ${result}
939
956
 
940
957
  `;
941
- tui.log.info(colors.dim(`Files found.`));
942
958
  } else if (["create_file", "modify_file", "delete_file"].includes(action.type)) {
943
959
  tui.log.warning(`
944
960
  \u{1F916} Agent wants to ${action.type}: ${colors.bold(action.path || "unknown")}`);
945
961
  if (action.content) {
946
- console.log(colors.dim("--- Content Preview ---"));
947
- console.log(action.content.substring(0, 300) + "...");
948
- console.log(colors.dim("-----------------------"));
962
+ const preview = action.content.length > 500 ? action.content.substring(0, 500) + "..." : action.content;
963
+ console.log(colors.dim("--- Preview ---\n") + preview + "\n" + colors.dim("---------------"));
949
964
  }
950
965
  const confirm = await tui.confirm({
951
966
  message: `Approve ${action.type}?`,
@@ -959,6 +974,8 @@ ${result}
959
974
  const BOM = "\uFEFF";
960
975
  const contentToWrite = action.content || "";
961
976
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
977
+ const dir = path4.dirname(action.path);
978
+ if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
962
979
  fs4.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
963
980
  tui.log.success(`\u2705 Created: ${action.path}`);
964
981
  executionResults += `[Action create_file(${action.path})]: Success
@@ -971,12 +988,8 @@ ${result}
971
988
 
972
989
  `;
973
990
  } else {
974
- const BOM = "\uFEFF";
975
- const contentToWrite = action.content || "";
976
- const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
977
- fs4.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
978
- tui.log.success(`\u2705 Overwritten: ${action.path}`);
979
- executionResults += `[Action modify_file(${action.path})]: Success (Overwrite)
991
+ tui.log.error("\u274C Missing target_content. Modification aborted.");
992
+ executionResults += `[Action modify_file]: Failed. 'target_content' is mandatory required for precision.
980
993
 
981
994
  `;
982
995
  }
@@ -1015,11 +1028,10 @@ ${result}
1015
1028
  nextPrompt = `${executionResults}
1016
1029
 
1017
1030
  User Reply: ${userReply}`;
1018
- tui.log.info(colors.dim("Auto-replying with tool results..."));
1019
1031
  } else {
1020
1032
  nextPrompt = executionResults;
1021
1033
  FileLogger.log("SYSTEM", "Auto-replying with Tool Results", { length: executionResults.length });
1022
- tui.log.info(colors.dim("Auto-replying with tool results..."));
1034
+ tui.log.info(colors.dim("Processing tool results..."));
1023
1035
  }
1024
1036
  } else if (waitingForUser) {
1025
1037
  const userReply = await tui.text({
@@ -1032,8 +1044,19 @@ User Reply: ${userReply}`;
1032
1044
  }
1033
1045
  nextPrompt = userReply;
1034
1046
  } else {
1035
- tui.log.warning("No actions taken.");
1036
- keepGoing = false;
1047
+ if (lastResponse.message) {
1048
+ tui.log.info(colors.primary("\u{1F916} Architect:"));
1049
+ console.log(lastResponse.message);
1050
+ const userReply = await tui.text({ message: "Your answer:" });
1051
+ if (tui.isCancel(userReply)) {
1052
+ keepGoing = false;
1053
+ break;
1054
+ }
1055
+ nextPrompt = userReply;
1056
+ } else {
1057
+ tui.log.warning("No actions taken.");
1058
+ keepGoing = false;
1059
+ }
1037
1060
  }
1038
1061
  } else {
1039
1062
  tui.log.warning("No actions received from agent.");
@@ -1102,7 +1125,7 @@ import { Command as Command2 } from "commander";
1102
1125
 
1103
1126
  // src/core/agents/scan-agent.ts
1104
1127
  import fs5 from "fs";
1105
- import path4 from "path";
1128
+ import path5 from "path";
1106
1129
  var AGENT_TYPE3 = "scan_agent";
1107
1130
  function getAgentId3() {
1108
1131
  const config = ConfigManager.getInstance().getConfig();
@@ -1117,29 +1140,29 @@ async function interactiveScanAgent(options = {}) {
1117
1140
  const projectRoot = process.cwd();
1118
1141
  let outputFile;
1119
1142
  if (options.output) {
1120
- outputFile = path4.resolve(process.cwd(), options.output);
1143
+ outputFile = path5.resolve(process.cwd(), options.output);
1121
1144
  } else {
1122
- const outputDir = path4.resolve(projectRoot, "_sharkrc");
1145
+ const outputDir = path5.resolve(projectRoot, "_sharkrc");
1123
1146
  if (!fs5.existsSync(outputDir)) {
1124
1147
  const stat = fs5.existsSync(outputDir) ? fs5.statSync(outputDir) : null;
1125
1148
  if (stat && stat.isFile()) {
1126
1149
  tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
1127
- const fallbackDir = path4.resolve(projectRoot, "_bmad/project-context");
1150
+ const fallbackDir = path5.resolve(projectRoot, "_bmad/project-context");
1128
1151
  if (!fs5.existsSync(fallbackDir)) fs5.mkdirSync(fallbackDir, { recursive: true });
1129
- outputFile = path4.join(fallbackDir, "project-context.md");
1152
+ outputFile = path5.join(fallbackDir, "project-context.md");
1130
1153
  } else {
1131
1154
  fs5.mkdirSync(outputDir, { recursive: true });
1132
- outputFile = path4.join(outputDir, "project-context.md");
1155
+ outputFile = path5.join(outputDir, "project-context.md");
1133
1156
  }
1134
1157
  } else {
1135
1158
  fs5.mkdirSync(outputDir, { recursive: true });
1136
- outputFile = path4.join(outputDir, "project-context.md");
1159
+ outputFile = path5.join(outputDir, "project-context.md");
1137
1160
  }
1138
1161
  }
1139
1162
  tui.log.info(`${t("commands.scan.scanningProject")} ${colors.bold(projectRoot)}`);
1140
1163
  tui.log.info(`${t("commands.scan.outputTarget")} ${colors.bold(outputFile)}`);
1141
1164
  tui.log.info(`${t("commands.scan.language")} ${colors.bold(language)}`);
1142
- const configFileRelative = path4.relative(projectRoot, outputFile);
1165
+ const configFileRelative = path5.relative(projectRoot, outputFile);
1143
1166
  const initialTemplate = `# Project Context
1144
1167
 
1145
1168
  ## Overview
@@ -1472,11 +1495,11 @@ ${result}
1472
1495
 
1473
1496
  `;
1474
1497
  } else if (action.type === "create_file" || action.type === "modify_file") {
1475
- const resolvedActionPath = path4.resolve(action.path || "");
1476
- const resolvedTargetPath = path4.resolve(targetPath);
1498
+ const resolvedActionPath = path5.resolve(action.path || "");
1499
+ const resolvedTargetPath = path5.resolve(targetPath);
1477
1500
  let isTarget = resolvedActionPath === resolvedTargetPath;
1478
- if (!isTarget && path4.basename(action.path || "") === "project-context.md") {
1479
- tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path4.relative(process.cwd(), targetPath)));
1501
+ if (!isTarget && path5.basename(action.path || "") === "project-context.md") {
1502
+ tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path5.relative(process.cwd(), targetPath)));
1480
1503
  isTarget = true;
1481
1504
  action.path = targetPath;
1482
1505
  }
@@ -1617,7 +1640,7 @@ import { Command as Command3 } from "commander";
1617
1640
 
1618
1641
  // src/core/agents/developer-agent.ts
1619
1642
  import fs6 from "fs";
1620
- import path5 from "path";
1643
+ import path6 from "path";
1621
1644
  var AGENT_TYPE4 = "developer_agent";
1622
1645
  function getAgentId4(overrideId) {
1623
1646
  if (overrideId) return overrideId;
@@ -1626,9 +1649,84 @@ function getAgentId4(overrideId) {
1626
1649
  if (process.env.STACKSPOT_DEV_AGENT_ID) return process.env.STACKSPOT_DEV_AGENT_ID;
1627
1650
  return "01KEQCGJ65YENRA4QBXVN1YFFX";
1628
1651
  }
1652
+ function analyzeSpecState(projectRoot) {
1653
+ const specPath = path6.resolve(projectRoot, "tech-spec.md");
1654
+ if (!fs6.existsSync(specPath)) {
1655
+ return { status: "MISSING" };
1656
+ }
1657
+ const content = fs6.readFileSync(specPath, "utf-8");
1658
+ const match = content.match(/- \[ \] (.*)/);
1659
+ if (match) {
1660
+ return { status: "PENDING", nextTask: match[1].trim(), specContent: content };
1661
+ }
1662
+ return { status: "COMPLETED", specContent: content };
1663
+ }
1664
+ function buildSystemPrompt(state, contextContent, additionalInstructions = "") {
1665
+ let basePrompt = ``;
1666
+ if (contextContent) {
1667
+ basePrompt += `
1668
+
1669
+ --- PROJECT CONTEXT ---
1670
+ ${contextContent}
1671
+ -----------------------
1672
+ `;
1673
+ }
1674
+ if (state.status === "MISSING") {
1675
+ basePrompt += `
1676
+
1677
+ \u{1F6A8} CRITICAL: NO 'tech-spec.md' FOUND.
1678
+
1679
+ Your FIRST priority is to analyze the user request and CREATE a 'tech-spec.md' file.
1680
+
1681
+ \u26A0\uFE0F WORKFLOW:
1682
+ 1. **Understand**: Clarify the goal with the user if needed.
1683
+ 2. **Explore**: Use 'list_files'/'read_file' to find RELEVANT files for this specific task.
1684
+ 3. **Specify**: Create 'tech-spec.md' referencing REAL file paths you found.
1685
+
1686
+ DO NOT create a spec based on guesses. Verify file existence before writing the plan.
1687
+
1688
+ Structure for 'tech-spec.md':
1689
+ \`\`\`markdown
1690
+ # Technical Spec: [Title]
1691
+
1692
+ ## Goal
1693
+ [Brief description]
1694
+
1695
+ ## Implementation Plan
1696
+ - [ ] Step 1: [Description]
1697
+ - [ ] Step 2: [Description]
1698
+ ...
1699
+ \`\`\`
1700
+ User Request: "${additionalInstructions}"
1701
+ `;
1702
+ } else if (state.status === "PENDING") {
1703
+ basePrompt += `
1704
+
1705
+ \u{1F7E2} EXECUTION MODE
1706
+
1707
+ Use 'tech-spec.md' as your source of truth.
1708
+
1709
+ \u{1F449} **CURRENT TASK**: "${state.nextTask}"
1710
+
1711
+
1712
+ Focus ONLY on this task. Do not jump ahead.
1713
+ 1. Implement the necessary changes.
1714
+ 2. Verify (compile/test).
1715
+ 3. **MANDATORY**: Use 'modify_file' to mark this task as '[x]' in 'tech-spec.md' when done.
1716
+ `;
1717
+ } else {
1718
+ basePrompt += `
1719
+
1720
+ \u2728 ALL TASKS COMPLETED according to 'tech-spec.md'.
1721
+
1722
+ Ask the user if they want to add more tasks or finish the session.
1723
+ `;
1724
+ }
1725
+ return basePrompt;
1726
+ }
1629
1727
  async function interactiveDeveloperAgent(options = {}) {
1630
1728
  FileLogger.init();
1631
- tui.intro("\u{1F988} Shark Dev Agent");
1729
+ tui.intro("\u{1F988} Shark Dev Agent (Spec-Driven)");
1632
1730
  const agentId = getAgentId4();
1633
1731
  if (agentId === "PENDING_CONFIGURATION") {
1634
1732
  tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
@@ -1636,35 +1734,34 @@ async function interactiveDeveloperAgent(options = {}) {
1636
1734
  }
1637
1735
  const projectRoot = process.cwd();
1638
1736
  let contextContent = "";
1639
- const defaultContextPath = path5.resolve(projectRoot, "_sharkrc", "project-context.md");
1640
- const specificContextPath = options.context ? path5.resolve(projectRoot, options.context) : defaultContextPath;
1737
+ const defaultContextPath = path6.resolve(projectRoot, "_sharkrc", "project-context.md");
1738
+ const specificContextPath = options.context ? path6.resolve(projectRoot, options.context) : defaultContextPath;
1641
1739
  if (fs6.existsSync(specificContextPath)) {
1642
1740
  try {
1643
1741
  contextContent = fs6.readFileSync(specificContextPath, "utf-8");
1644
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path5.relative(projectRoot, specificContextPath))}`);
1742
+ tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path6.relative(projectRoot, specificContextPath))}`);
1645
1743
  } catch (e) {
1646
1744
  tui.log.warning(`Failed to read context file: ${e}`);
1647
1745
  }
1648
1746
  } else {
1649
1747
  tui.log.warning(`\u26A0\uFE0F No context file found. Agent will run without pre-loaded context.`);
1650
1748
  }
1651
- let nextPrompt = options.task || "I'm ready to help. What's the task?";
1652
- if (contextContent) {
1653
- nextPrompt += `
1654
-
1655
- --- PROJECT CONTEXT ---
1656
- ${contextContent}
1657
- -----------------------`;
1658
- }
1749
+ let specState = analyzeSpecState(projectRoot);
1750
+ let nextPrompt = buildSystemPrompt(specState, contextContent, options.task || "Start working.");
1659
1751
  let keepGoing = true;
1660
1752
  const spinner = tui.spinner();
1753
+ let stepCount = 0;
1661
1754
  while (keepGoing) {
1755
+ stepCount++;
1662
1756
  try {
1663
- spinner.start("Waiting for Agent...");
1757
+ if (specState.status === "PENDING") {
1758
+ tui.log.info(colors.bold(`\u{1F3AF} DOING: ${specState.nextTask}`));
1759
+ } else if (specState.status === "MISSING") {
1760
+ tui.log.info(colors.warning(`\u{1F4CB} PLANNING: Creating tech-spec.md`));
1761
+ }
1762
+ spinner.start("Waiting for Shark Dev...");
1664
1763
  let lastResponse = null;
1665
1764
  await callDevAgentApi(nextPrompt, (chunk) => {
1666
- if (!lastResponse) {
1667
- }
1668
1765
  }).then((resp) => {
1669
1766
  lastResponse = resp;
1670
1767
  });
@@ -1673,6 +1770,7 @@ ${contextContent}
1673
1770
  const response = lastResponse;
1674
1771
  let executionResults = "";
1675
1772
  let waitingForUser = false;
1773
+ let specUpdated = false;
1676
1774
  for (const action of response.actions) {
1677
1775
  if (action.type === "talk_with_user") {
1678
1776
  tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
@@ -1703,7 +1801,7 @@ ${result}
1703
1801
  const cmd = action.command || "";
1704
1802
  tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
1705
1803
  const confirm = await tui.confirm({
1706
- message: `Execute command: ${cmd}?`,
1804
+ message: `Execute: ${cmd}?`,
1707
1805
  active: "Yes",
1708
1806
  inactive: "No"
1709
1807
  });
@@ -1724,7 +1822,8 @@ ${result}
1724
1822
  tui.log.warning(`
1725
1823
  \u{1F916} Agent wants to ${isCreate ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
1726
1824
  if (action.content) {
1727
- console.log(colors.dim("--- Content ---\n") + action.content.substring(0, 200) + "...\n" + colors.dim("---------------"));
1825
+ const preview = action.content.length > 500 ? action.content.substring(0, 500) + "... (truncated)" : action.content;
1826
+ console.log(colors.dim("--- Content ---\n") + preview + "\n" + colors.dim("---------------"));
1728
1827
  }
1729
1828
  const confirm = await tui.confirm({
1730
1829
  message: `Approve changes to ${filePath}?`,
@@ -1733,8 +1832,8 @@ ${result}
1733
1832
  });
1734
1833
  if (confirm) {
1735
1834
  if (filePath) {
1736
- const targetPath = path5.resolve(projectRoot, filePath);
1737
- const dir = path5.dirname(targetPath);
1835
+ const targetPath = path6.resolve(projectRoot, filePath);
1836
+ const dir = path6.dirname(targetPath);
1738
1837
  if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
1739
1838
  if (isCreate) {
1740
1839
  const BOM = "\uFEFF";
@@ -1745,15 +1844,23 @@ ${result}
1745
1844
  executionResults += `[Action create_file(${filePath})]: Success
1746
1845
 
1747
1846
  `;
1847
+ if (filePath.endsWith("tech-spec.md")) specUpdated = true;
1748
1848
  } else {
1749
1849
  if (action.target_content) {
1750
1850
  const success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
1751
- executionResults += `[Action modify_file(${filePath})]: ${success ? "Success" : "Failed"}
1851
+ if (success) {
1852
+ executionResults += `[Action modify_file(${filePath})]: Success
1853
+
1854
+ `;
1855
+ if (filePath.endsWith("tech-spec.md")) specUpdated = true;
1856
+ } else {
1857
+ executionResults += `[Action modify_file(${filePath})]: FAILED. Target content not found or ambiguous. Read the file again to ensure accuracy.
1752
1858
 
1753
1859
  `;
1860
+ }
1754
1861
  } else {
1755
1862
  tui.log.error("\u274C Missing target_content for modification.");
1756
- executionResults += `[Action modify_file]: Failed. Missing target_content.
1863
+ executionResults += `[Action modify_file]: Failed. Missing target_content. PRESERVE context and use 'target_content' to specify what to replace.
1757
1864
 
1758
1865
  `;
1759
1866
  }
@@ -1767,19 +1874,37 @@ ${result}
1767
1874
  }
1768
1875
  }
1769
1876
  }
1877
+ const previousState = specState;
1878
+ specState = analyzeSpecState(projectRoot);
1879
+ let systemInjection = "";
1770
1880
  if (executionResults) {
1881
+ if (previousState.status === "PENDING" && specState.status === "PENDING" && previousState.nextTask !== specState.nextTask) {
1882
+ systemInjection = `
1883
+ \u{1F389} Task "${previousState.nextTask}" COMPLETED! Next up: "${specState.nextTask}".
1884
+ `;
1885
+ } else if (previousState.status === "PENDING" && specState.status === "PENDING" && previousState.nextTask === specState.nextTask) {
1886
+ if (!specUpdated && stepCount % 3 === 0) {
1887
+ systemInjection = `
1888
+ Reminder: You are still working on "${specState.nextTask}". Don't forget to mark it [x] in 'tech-spec.md' when done.
1889
+ `;
1890
+ }
1891
+ } else if (previousState.status === "MISSING" && specState.status === "PENDING") {
1892
+ systemInjection = `
1893
+ \u2705 Spec Created! Starting execution of: "${specState.nextTask}".
1894
+ `;
1895
+ }
1771
1896
  if (waitingForUser) {
1772
1897
  const userReply = await tui.text({ message: "Your answer:" });
1773
1898
  if (tui.isCancel(userReply)) {
1774
1899
  keepGoing = false;
1775
1900
  break;
1776
1901
  }
1777
- nextPrompt = `${executionResults}
1778
-
1902
+ nextPrompt = `${executionResults}${systemInjection}
1779
1903
  User Reply: ${userReply}`;
1780
1904
  } else {
1781
- nextPrompt = executionResults;
1782
- tui.log.info(colors.dim("Sending tool results to agent..."));
1905
+ nextPrompt = `${executionResults}${systemInjection}
1906
+ [System]: Continue.`;
1907
+ tui.log.info(colors.dim("Processing results..."));
1783
1908
  }
1784
1909
  } else if (waitingForUser) {
1785
1910
  const userReply = await tui.text({ message: "Your answer:" });
@@ -1799,13 +1924,13 @@ User Reply: ${userReply}`;
1799
1924
  nextPrompt = userReply;
1800
1925
  }
1801
1926
  } else {
1802
- tui.log.warning("Agent took no actions.");
1803
- keepGoing = false;
1927
+ tui.log.warning("Agent took no actions and sent no message.");
1928
+ nextPrompt = "Please proceed or ask for clarification.";
1804
1929
  }
1805
1930
  }
1806
1931
  } else {
1807
- tui.log.warning("Invalid response from agent.");
1808
- keepGoing = false;
1932
+ tui.log.warning("Invalid response from agent (no actions).");
1933
+ nextPrompt = "Error: No valid actions returned. Please try again with JSON format.";
1809
1934
  }
1810
1935
  } catch (e) {
1811
1936
  spinner.stop("Error");
@@ -1827,7 +1952,6 @@ async function callDevAgentApi(prompt, onChunk) {
1827
1952
  use_conversation: true,
1828
1953
  conversation_id: conversationId,
1829
1954
  stackspot_knowledge: false
1830
- // Dev Agent focuses on project context
1831
1955
  };
1832
1956
  const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${getAgentId4()}/chat`;
1833
1957
  let fullMsg = "";
@@ -1865,7 +1989,7 @@ import { Command as Command4 } from "commander";
1865
1989
 
1866
1990
  // src/core/agents/qa-agent.ts
1867
1991
  import fs7 from "fs";
1868
- import path6 from "path";
1992
+ import path7 from "path";
1869
1993
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1870
1994
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1871
1995
  var AGENT_TYPE5 = "qa_agent";
@@ -1934,7 +2058,7 @@ async function runQAAgent(options) {
1934
2058
  }
1935
2059
  let projectContext = "";
1936
2060
  try {
1937
- const contextPath = path6.join(process.cwd(), "_sharkrc", "project-context.md");
2061
+ const contextPath = path7.join(process.cwd(), "_sharkrc", "project-context.md");
1938
2062
  if (fs7.existsSync(contextPath)) {
1939
2063
  projectContext = fs7.readFileSync(contextPath, "utf-8");
1940
2064
  tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
@@ -2045,7 +2169,7 @@ ${projectContext}
2045
2169
  break;
2046
2170
  case "create_file":
2047
2171
  if (action.path && action.content) {
2048
- const fullPath = path6.resolve(process.cwd(), action.path);
2172
+ const fullPath = path7.resolve(process.cwd(), action.path);
2049
2173
  const BOM = "\uFEFF";
2050
2174
  const contentToWrite = action.content;
2051
2175
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;