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 +225 -101
- package/dist/bin/shark.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
854
|
-
let
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
871
|
-
tui.log.info(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
887
|
-
|
|
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}
|
|
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}
|
|
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}
|
|
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
|
-
|
|
947
|
-
console.log(
|
|
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
|
-
|
|
975
|
-
|
|
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("
|
|
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
|
-
|
|
1036
|
-
|
|
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
|
|
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 =
|
|
1143
|
+
outputFile = path5.resolve(process.cwd(), options.output);
|
|
1121
1144
|
} else {
|
|
1122
|
-
const outputDir =
|
|
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 =
|
|
1150
|
+
const fallbackDir = path5.resolve(projectRoot, "_bmad/project-context");
|
|
1128
1151
|
if (!fs5.existsSync(fallbackDir)) fs5.mkdirSync(fallbackDir, { recursive: true });
|
|
1129
|
-
outputFile =
|
|
1152
|
+
outputFile = path5.join(fallbackDir, "project-context.md");
|
|
1130
1153
|
} else {
|
|
1131
1154
|
fs5.mkdirSync(outputDir, { recursive: true });
|
|
1132
|
-
outputFile =
|
|
1155
|
+
outputFile = path5.join(outputDir, "project-context.md");
|
|
1133
1156
|
}
|
|
1134
1157
|
} else {
|
|
1135
1158
|
fs5.mkdirSync(outputDir, { recursive: true });
|
|
1136
|
-
outputFile =
|
|
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 =
|
|
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 =
|
|
1476
|
-
const resolvedTargetPath =
|
|
1498
|
+
const resolvedActionPath = path5.resolve(action.path || "");
|
|
1499
|
+
const resolvedTargetPath = path5.resolve(targetPath);
|
|
1477
1500
|
let isTarget = resolvedActionPath === resolvedTargetPath;
|
|
1478
|
-
if (!isTarget &&
|
|
1479
|
-
tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}",
|
|
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
|
|
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 =
|
|
1640
|
-
const specificContextPath = options.context ?
|
|
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(
|
|
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
|
|
1652
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
1737
|
-
const dir =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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;
|