shark-ai 0.4.5 → 0.4.7

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
@@ -1008,6 +1008,20 @@ var TypeScriptEditor = class {
1008
1008
  return false;
1009
1009
  }
1010
1010
  }
1011
+ async getProperty(filePath, className, propertyName) {
1012
+ try {
1013
+ const sourceFile = this.getSourceFile(filePath);
1014
+ const classDecl = this.getClass(sourceFile, className);
1015
+ const property = classDecl.getProperty(propertyName);
1016
+ if (!property) {
1017
+ return void 0;
1018
+ }
1019
+ return property.getText();
1020
+ } catch (error) {
1021
+ console.error(`Failed to get property "${propertyName}":`, error);
1022
+ return void 0;
1023
+ }
1024
+ }
1011
1025
  async modifyProperty(filePath, className, propertyName, newCode) {
1012
1026
  try {
1013
1027
  const sourceFile = this.getSourceFile(filePath);
@@ -1703,95 +1717,141 @@ function getAgentId2(overrideId) {
1703
1717
  }
1704
1718
  async function interactiveSpecificationAgent(options = {}) {
1705
1719
  FileLogger.init();
1706
- tui.intro("\u{1F3D7}\uFE0F Specification Agent");
1720
+ tui.intro("\u{1F3D7}\uFE0F Specification Agent (Template-Based)");
1707
1721
  const projectRoot = process.cwd();
1722
+ const sharkRcDir = path6.resolve(projectRoot, "_sharkrc");
1723
+ if (!fs5.existsSync(sharkRcDir)) fs5.mkdirSync(sharkRcDir, { recursive: true });
1724
+ const outputFile = path6.resolve(sharkRcDir, "tech-spec.md");
1725
+ if (!fs5.existsSync(outputFile)) {
1726
+ let initialContent = `# Technical Specification: {{PROJECT_NAME}}
1727
+
1728
+ ## 1. Technology Stack
1729
+ [TO BE ANALYZED]
1730
+ - Language: [e.g. TypeScript]
1731
+ - Framework: [e.g. Node.js / React]
1732
+ - Database: [e.g. SQLite / PostgreSQL]
1733
+ - Key Libraries: [Top 5 dependencies]
1734
+
1735
+ ## 2. Architecture Overview
1736
+ [TO BE ANALYZED]
1737
+ [Brief description of architectural pattern]
1738
+
1739
+ ## 3. Data Model
1740
+ [TO BE ANALYZED]
1741
+ [Schema/ERD definitions]
1742
+
1743
+ ## 4. API / Interface Contracts
1744
+ [TO BE ANALYZED]
1745
+ [Main endpoints or CLI commands]
1746
+
1747
+ ## 5. Implementation Steps
1748
+ [TO BE FILLED - MUST BE CHECKBOXES]
1749
+ `;
1750
+ const projectName = path6.basename(projectRoot);
1751
+ initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
1752
+ const BOM = "\uFEFF";
1753
+ fs5.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
1754
+ tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
1755
+ } else {
1756
+ tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
1757
+ }
1708
1758
  let contextContent = "";
1709
1759
  const contextPath = path6.resolve(projectRoot, "_sharkrc", "project-context.md");
1710
1760
  if (fs5.existsSync(contextPath)) {
1711
- try {
1712
- contextContent = fs5.readFileSync(contextPath, "utf-8");
1713
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path6.relative(projectRoot, contextPath))}`);
1714
- } catch (e) {
1715
- tui.log.warning(`Failed to read context file: ${e}`);
1716
- }
1761
+ contextContent = fs5.readFileSync(contextPath, "utf-8");
1762
+ tui.log.info(`\u{1F4D8} Context loaded.`);
1717
1763
  }
1718
1764
  let briefingContent = "";
1719
1765
  if (options.briefingPath && fs5.existsSync(options.briefingPath)) {
1720
1766
  briefingContent = fs5.readFileSync(options.briefingPath, "utf-8");
1721
1767
  tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
1722
1768
  } else {
1723
- const sharkRcBriefing = path6.resolve(projectRoot, "_sharkrc", "briefing.md");
1724
- if (fs5.existsSync(sharkRcBriefing)) {
1725
- briefingContent = fs5.readFileSync(sharkRcBriefing, "utf-8");
1726
- tui.log.info(`\u{1F4C4} Standard Briefing loaded: ${colors.dim("_sharkrc/briefing.md")}`);
1727
- } else {
1728
- tui.log.info(colors.dim("\u2139\uFE0F No briefing file found in _sharkrc/briefing.md. Starting in interactive mode."));
1769
+ const standardBriefing = path6.resolve(projectRoot, "_sharkrc", "briefing.md");
1770
+ if (fs5.existsSync(standardBriefing)) {
1771
+ briefingContent = fs5.readFileSync(standardBriefing, "utf-8");
1772
+ tui.log.info(`\u{1F4C4} Briefing loaded.`);
1729
1773
  }
1730
1774
  }
1731
- let initialPrompt = "";
1732
- if (options.initialContext) {
1733
- initialPrompt += `
1734
- \u26A0\uFE0F **CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER)**:
1735
- O Developer Agent estava executando tarefas. Aqui est\xE1 o resumo do que aconteceu at\xE9 agora:
1736
- """
1737
- ${options.initialContext}
1738
- """
1739
- Analise esse hist\xF3rico acima. Se houve falha, proponha fixes na spec. Se o usu\xE1rio pediu mudan\xE7a, use isso como base.
1775
+ let initialPrompt = `
1776
+ You are the **Shark Spec Agent**, a Senior Software Architect.
1777
+ Your goal is to COMPLETE the technical specification file \`_sharkrc/tech-spec.md\`.
1778
+
1779
+ **CURRENT STATE:**
1780
+ The file \`_sharkrc/tech-spec.md\` exists. It contains placeholders like \`[TO BE ANALYZED]\` or \`[TO BE FILLED]\`.
1781
+
1782
+ **YOUR MISSION:**
1783
+ Iteratively analyze the project and fill in these placeholders.
1784
+
1785
+ **INPUTS:**
1740
1786
  `;
1741
- }
1742
1787
  if (briefingContent) {
1743
1788
  initialPrompt += `
1744
- Abaixo est\xE1 o **Briefing de Neg\xF3cio** ou Descri\xE7\xE3o da Tarefa.
1745
- Analise-o e ajude-me a definir a Especifica\xE7\xE3o T\xE9cnica (tech-spec.md).
1746
-
1747
1789
  --- BRIEFING ---
1748
1790
  ${briefingContent}
1749
1791
  ----------------
1750
1792
  `;
1751
1793
  } else {
1752
1794
  initialPrompt += `
1753
- N\xE3o h\xE1 um documento de briefing formal.
1754
- Por favor, pergunte-me qual \xE9 a tarefa ou funcionalidade que vamos especificar hoje.
1795
+ (No formal briefing provided. Ask the user for requirements if needed.)
1796
+ `;
1797
+ }
1798
+ if (options.initialContext) {
1799
+ initialPrompt += `
1800
+ --- HANDOVER CONTEXT (PREVIOUS FAILURES/FEEDBACK) ---
1801
+ ${options.initialContext}
1802
+ -----------------------------------------------------
1755
1803
  `;
1756
1804
  }
1757
1805
  if (contextContent) {
1758
1806
  initialPrompt += `
1759
- Abaixo est\xE1 o **Contexto do Projeto** atual. Use-o para alinhar a especifica\xE7\xE3o com a arquitetura existente.
1760
-
1761
1807
  --- PROJECT CONTEXT ---
1762
1808
  ${contextContent}
1763
1809
  -----------------------
1764
1810
  `;
1765
1811
  }
1766
1812
  initialPrompt += `
1767
-
1768
- Seu objetivo final \xE9 gerar o arquivo 'tech-spec.md'.
1769
-
1770
- \u26A0\uFE0F ATEN\xC7\xC3O: WORKFLOW DE AN\xC1LISE
1771
- 1. **Entenda**: Alinhe o objetivo com o usu\xE1rio.
1772
- 2. **Explore**: Use 'list_files' e 'read_file' para encontrar os arquivos RELEVANTES para a tarefa.
1773
- 3. **Especifique**: Gere o 'tech-spec.md' citando nomes de arquivos REAIS que voc\xEA leu.
1774
-
1775
- \u26A0\uFE0F REGRA DE FORMATA\xC7\xC3O (CRITICA):
1776
- Na se\xE7\xE3o 'Implementation Steps', voc\xEA DEVE usar CHECKBOXES markdown ( - [ ] ) e N\xC3O listas numeradas.
1777
- O agente de desenvolvimento S\xD3 reconhece checkboxes.
1778
-
1779
- Exemplo CORRETO:
1780
- - [ ] Criar arquivo X
1781
- - [ ] Atualizar fun\xE7\xE3o Y
1782
-
1783
- Exemplo ERRADO (N\xC3O FA\xC7A):
1784
- 1. Criar arquivo X
1785
- 2. Atualizar fun\xE7\xE3o Y
1813
+ **RULES OF ENGAGEMENT (STRICT):**
1814
+
1815
+ 1. **INCREMENTAL WORK**: Do NOT try to write the whole file at once. Focus on one section at a time.
1816
+ 2. **READ BEFORE WRITING**:
1817
+ - Before filling **Tech Stack** or **Architecture**, run \`list_files\` and \`read_file\` to verify existing code.
1818
+ - Before adding **Implementation Steps**, you MUST read the target files referenced in the tasks.
1819
+ - **PROHIBITED**: Adding a task like "- [ ] Modify src/auth.ts" without having read "src/auth.ts" first (unless it's a new file).
1820
+
1821
+ 3. **TASK FORMAT for 'Implementation Steps'**:
1822
+ - MUST be Markdown Checkboxes: \`- [ ] ...\`
1823
+ - MUST be simple, atomic lines. NO indentation.
1824
+ - Format: \`- [ ] [Action verb] [What] in [Rel Path]\`
1825
+ - Example: \`- [ ] Add validation function to src/utils/validators.ts\`
1826
+
1827
+ 4. **USER INTERACTION**:
1828
+ - If requirements are vague, use \`talk_with_user\` to clarify BEFORE defining tasks.
1829
+
1830
+ **STRATEGY:**
1831
+ 1. Check \`_sharkrc/tech-spec.md\` content (I will provide snippets of what's missing).
1832
+ 2. Explore necessary files.
1833
+ 3. Update \`_sharkrc/tech-spec.md\` using \`modify_file\` to replace placeholders.
1834
+ 4. Repeat untill all placeholders are gone.
1786
1835
  `;
1787
- await runSpecLoop(initialPrompt.trim(), options.agentId);
1836
+ await runSpecLoop(initialPrompt.trim(), outputFile, options.agentId);
1788
1837
  }
1789
- async function runSpecLoop(initialMessage, overrideAgentId) {
1838
+ async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
1790
1839
  let nextPrompt = initialMessage;
1791
1840
  let keepGoing = true;
1792
- while (keepGoing) {
1841
+ let stepCount = 0;
1842
+ const MAX_STEPS = 30;
1843
+ while (keepGoing && stepCount < MAX_STEPS) {
1844
+ stepCount++;
1793
1845
  const spinner = tui.spinner();
1794
- spinner.start("\u{1F3D7}\uFE0F Specification Agent is thinking...");
1846
+ spinner.start(`\u{1F3D7}\uFE0F Spec Agent working (Step ${stepCount}/${MAX_STEPS})...`);
1847
+ let pendingSections = [];
1848
+ if (fs5.existsSync(targetPath)) {
1849
+ const content = fs5.readFileSync(targetPath, "utf-8");
1850
+ if (content.includes("[TO BE ANALYZED]")) pendingSections.push("Analysis Sections (Stack, Arch, Data, API)");
1851
+ if (content.includes("[TO BE FILLED")) pendingSections.push("Implementation Steps");
1852
+ }
1853
+ if (pendingSections.length === 0 && stepCount > 1) {
1854
+ }
1795
1855
  let responseText = "";
1796
1856
  let lastResponse = null;
1797
1857
  try {
@@ -1802,10 +1862,10 @@ async function runSpecLoop(initialMessage, overrideAgentId) {
1802
1862
  if (lastResponse && lastResponse.actions) {
1803
1863
  let executionResults = "";
1804
1864
  let waitingForUser = false;
1865
+ let specUpdated = false;
1805
1866
  if (lastResponse.message && lastResponse.message.includes("SPEC_UPDATED:")) {
1806
1867
  const updateSummary = lastResponse.message.split("SPEC_UPDATED:")[1].trim();
1807
- tui.log.success(`\u2705 Spec Updated: ${updateSummary}`);
1808
- tui.log.info("\u{1F4CB} Returning to orchestration loop...");
1868
+ tui.log.success(`\u2705 Spec Finalized: ${updateSummary}`);
1809
1869
  return;
1810
1870
  }
1811
1871
  for (const action of lastResponse.actions) {
@@ -1834,97 +1894,78 @@ ${result}
1834
1894
  ${result}
1835
1895
 
1836
1896
  `;
1837
- } else if (["create_file", "modify_file", "delete_file"].includes(action.type)) {
1838
- tui.log.warning(`
1839
- \u{1F916} Agent wants to ${action.type}: ${colors.bold(action.path || "unknown")}`);
1840
- if (action.content) {
1841
- const preview = action.content.length > 500 ? action.content.substring(0, 500) + "..." : action.content;
1842
- console.log(colors.dim("--- Preview ---\n") + preview + "\n" + colors.dim("---------------"));
1897
+ } else if (["create_file", "modify_file"].includes(action.type)) {
1898
+ let actionPath = path6.resolve(action.path || "");
1899
+ const resolvedTargetPath = path6.resolve(targetPath);
1900
+ let isTarget = actionPath === resolvedTargetPath;
1901
+ if (!isTarget && path6.basename(actionPath) === "tech-spec.md") {
1902
+ tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${path6.relative(process.cwd(), targetPath)}`);
1903
+ action.path = targetPath;
1904
+ actionPath = resolvedTargetPath;
1905
+ isTarget = true;
1843
1906
  }
1844
- const confirm = await tui.confirm({
1845
- message: `Approve ${action.type}?`,
1846
- active: "Yes",
1847
- inactive: "No"
1848
- });
1849
- if (confirm) {
1850
- if (action.path) {
1851
- try {
1852
- if (action.type === "create_file") {
1853
- const BOM = "\uFEFF";
1854
- const contentToWrite = action.content || "";
1855
- const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
1856
- const dir = path6.dirname(action.path);
1857
- if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
1858
- fs5.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
1859
- tui.log.success(`\u2705 Created: ${action.path}`);
1860
- executionResults += `[Action create_file(${action.path})]: Success
1861
-
1907
+ if (!isTarget && action.type === "create_file") {
1908
+ const confirm = await tui.confirm({ message: `Agent wants to create ${action.path}. Allow?` });
1909
+ if (!confirm) {
1910
+ executionResults += `[Action create_file]: User denied.
1862
1911
  `;
1863
- } else if (action.type === "modify_file") {
1864
- if (action.target_content) {
1865
- const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
1866
- executionResults += `[Action modify_file(${action.path})]: ${success ? "Success" : "Failed"}
1867
-
1912
+ continue;
1913
+ }
1914
+ }
1915
+ try {
1916
+ if (action.type === "create_file") {
1917
+ const BOM = "\uFEFF";
1918
+ fs5.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
1919
+ tui.log.success(`\u2705 Created: ${action.path}`);
1920
+ executionResults += `[Action create_file]: Success.
1868
1921
  `;
1869
- } else {
1870
- tui.log.error("\u274C Missing target_content. Modification aborted.");
1871
- executionResults += `[Action modify_file]: Failed. 'target_content' is mandatory required for precision.
1872
-
1922
+ } else if (action.type === "modify_file") {
1923
+ if (action.target_content) {
1924
+ const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
1925
+ if (success) {
1926
+ executionResults += `[Action modify_file]: Success.
1873
1927
  `;
1874
- }
1875
- } else if (action.type === "delete_file") {
1876
- fs5.unlinkSync(action.path);
1877
- tui.log.success(`\u2705 Deleted: ${action.path}`);
1878
- executionResults += `[Action delete_file(${action.path})]: Success
1879
-
1928
+ specUpdated = true;
1929
+ } else {
1930
+ executionResults += `[Action modify_file]: Failed. Target content not found.
1880
1931
  `;
1881
1932
  }
1882
- } catch (e) {
1883
- tui.log.error(`\u274C Failed: ${e.message}`);
1884
- executionResults += `[Action ${action.type}(${action.path})]: Error: ${e.message}
1885
-
1933
+ } else {
1934
+ executionResults += `[Action modify_file]: Failed. 'target_content' is required.
1886
1935
  `;
1887
1936
  }
1888
1937
  }
1889
- } else {
1890
- tui.log.error("\u274C Action denied.");
1891
- executionResults += `[Action ${action.type}]: User Denied
1892
-
1938
+ } catch (e) {
1939
+ executionResults += `[Action ${action.type}]: Error: ${e.message}
1893
1940
  `;
1894
1941
  }
1895
1942
  }
1896
1943
  }
1897
- if (executionResults) {
1898
- if (waitingForUser) {
1899
- const userReply = await tui.text({
1900
- message: "Your answer",
1901
- placeholder: "Type your answer..."
1902
- });
1903
- if (tui.isCancel(userReply)) {
1904
- keepGoing = false;
1905
- return;
1906
- }
1907
- nextPrompt = `${executionResults}
1908
-
1909
- User Reply: ${userReply}`;
1910
- } else {
1911
- nextPrompt = executionResults;
1912
- FileLogger.log("SYSTEM", "Auto-replying with Tool Results", { length: executionResults.length });
1913
- tui.log.info(colors.dim("Processing tool results..."));
1914
- }
1915
- } else if (waitingForUser) {
1916
- const userReply = await tui.text({
1917
- message: "Your answer",
1918
- placeholder: "Type your answer..."
1919
- });
1944
+ if (waitingForUser) {
1945
+ const userReply = await tui.text({ message: "Your answer", placeholder: "Type your answer..." });
1920
1946
  if (tui.isCancel(userReply)) {
1921
1947
  keepGoing = false;
1922
1948
  return;
1923
1949
  }
1924
- nextPrompt = userReply;
1950
+ nextPrompt = `${executionResults}
1951
+
1952
+ User Reply: ${userReply}`;
1953
+ } else if (executionResults) {
1954
+ const content = fs5.existsSync(targetPath) ? fs5.readFileSync(targetPath, "utf-8") : "";
1955
+ let systemMsg = "Tool execution completed.";
1956
+ if (specUpdated) {
1957
+ if (content.includes("[TO BE")) {
1958
+ systemMsg += "\n[System]: Section updated. Please continue harmonizing and filling the remaining '[TO BE ...]' placeholders.";
1959
+ } else {
1960
+ systemMsg += "\n[System]: file looks complete! If you are satisfied, output 'SPEC_UPDATED: Complete'.";
1961
+ }
1962
+ }
1963
+ nextPrompt = `${executionResults}
1964
+
1965
+ ${systemMsg}`;
1925
1966
  } else {
1926
1967
  if (lastResponse.message) {
1927
- tui.log.info(colors.primary("\u{1F916} Architect:"));
1968
+ tui.log.info(colors.primary("\u{1F916} Architect (Message only):"));
1928
1969
  console.log(lastResponse.message);
1929
1970
  const userReply = await tui.text({ message: "Your answer:" });
1930
1971
  if (tui.isCancel(userReply)) {
@@ -1933,12 +1974,11 @@ User Reply: ${userReply}`;
1933
1974
  }
1934
1975
  nextPrompt = userReply;
1935
1976
  } else {
1936
- tui.log.warning("No actions taken.");
1937
1977
  keepGoing = false;
1938
1978
  }
1939
1979
  }
1940
1980
  } else {
1941
- tui.log.warning("No actions received from agent.");
1981
+ tui.log.warning("No actions received.");
1942
1982
  keepGoing = false;
1943
1983
  }
1944
1984
  } catch (error) {
@@ -1964,12 +2004,7 @@ async function callSpecAgentApi(prompt, onChunk, agentId) {
1964
2004
  const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${effectiveAgentId}/chat`;
1965
2005
  let fullMsg = "";
1966
2006
  let raw = {};
1967
- FileLogger.log("AGENT", "Calling Agent API", {
1968
- agentId: effectiveAgentId,
1969
- conversationId,
1970
- prompt: prompt.substring(0, 500)
1971
- // Log summary of prompt
1972
- });
2007
+ FileLogger.log("AGENT", "Calling Agent API", { agentId: effectiveAgentId, conversationId });
1973
2008
  await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
1974
2009
  onChunk: (c) => {
1975
2010
  fullMsg += c;
@@ -1977,15 +2012,7 @@ async function callSpecAgentApi(prompt, onChunk, agentId) {
1977
2012
  },
1978
2013
  onComplete: (msg, metadata) => {
1979
2014
  const returnedId = metadata?.conversation_id;
1980
- FileLogger.log("AGENT", "Response Complete", {
1981
- conversationId,
1982
- returnedId,
1983
- messageLength: msg?.length
1984
- });
1985
- raw = {
1986
- message: msg || fullMsg,
1987
- conversation_id: returnedId || conversationId
1988
- };
2015
+ raw = { message: msg || fullMsg, conversation_id: returnedId || conversationId };
1989
2016
  },
1990
2017
  onError: (e) => {
1991
2018
  throw e;