shark-ai 0.3.14 → 0.3.16

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
@@ -9,7 +9,7 @@ import {
9
9
  t,
10
10
  tokenStorage,
11
11
  tui
12
- } from "../chunk-VN5M6CIZ.js";
12
+ } from "../chunk-WVBOO6LE.js";
13
13
 
14
14
  // src/core/error/crash-handler.ts
15
15
  import fs from "fs";
@@ -773,15 +773,139 @@ async function interactiveBusinessAnalyst() {
773
773
 
774
774
  // src/core/agents/specification-agent.ts
775
775
  import fs4 from "fs";
776
- import path4 from "path";
776
+ import path5 from "path";
777
777
 
778
778
  // src/core/agents/agent-tools.ts
779
779
  import fs3 from "fs";
780
- import path3 from "path";
780
+ import path4 from "path";
781
781
  import fg from "fast-glob";
782
782
  import { exec } from "child_process";
783
783
  import { promisify } from "util";
784
784
  import { fileURLToPath } from "url";
785
+
786
+ // src/core/services/code-review.service.ts
787
+ import path3 from "path";
788
+ var CodeReviewService = class {
789
+ /**
790
+ * Reviews the code modification and returns a status/feedback string.
791
+ * This string is intended to be appended to the user Preview so the Agent can see it.
792
+ */
793
+ static async reviewCode(filePath, newContent) {
794
+ const config = ConfigManager.getInstance().getConfig();
795
+ const validation = config.validation || { llmReviewExtensions: [".ts", ".tsx"], syntaxCheckExtensions: ["*"] };
796
+ const ext = path3.extname(filePath).toLowerCase();
797
+ const needsLlmReview = validation.llmReviewExtensions.includes(ext) || validation.llmReviewExtensions.includes("*");
798
+ if (needsLlmReview) {
799
+ return await this.performLlmReview(filePath, newContent);
800
+ }
801
+ const needsSyntaxCheck = validation.syntaxCheckExtensions.includes(ext) || validation.syntaxCheckExtensions.includes("*");
802
+ if (needsSyntaxCheck) {
803
+ return this.performSyntaxCheck(filePath, newContent);
804
+ }
805
+ return "\u2139\uFE0F No specific validation configured for this file type.";
806
+ }
807
+ static getCodeReviewAgentId() {
808
+ const config = ConfigManager.getInstance().getConfig();
809
+ if (config.agents?.codeReview) return config.agents.codeReview;
810
+ if (process.env.STACKSPOT_CODE_REVIEW_AGENT_ID) return process.env.STACKSPOT_CODE_REVIEW_AGENT_ID;
811
+ return "";
812
+ }
813
+ static async performLlmReview(filePath, content) {
814
+ const agentId = this.getCodeReviewAgentId();
815
+ if (!agentId) {
816
+ return this.performSyntaxCheck(filePath, content);
817
+ }
818
+ try {
819
+ const realm = await getActiveRealm();
820
+ const token = await ensureValidToken(realm);
821
+ const prompt = `You are a Code Review Specialist. Analyze the following code modification for file: ${path3.basename(filePath)}
822
+
823
+ NEW CODE TO BE ADDED:
824
+ \`\`\`
825
+ ${content}
826
+ \`\`\`
827
+
828
+ Provide a concise review focusing on:
829
+ 1. Syntax errors or obvious bugs
830
+ 2. Potential runtime issues
831
+ 3. Code quality concerns
832
+
833
+ Respond in JSON format with status, issues array, and summary.`;
834
+ const payload = {
835
+ user_prompt: prompt,
836
+ streaming: false,
837
+ use_conversation: false,
838
+ stackspot_knowledge: false
839
+ };
840
+ const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${agentId}/chat`;
841
+ let reviewText = "";
842
+ await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
843
+ onChunk: (c) => {
844
+ reviewText += c;
845
+ },
846
+ onComplete: () => {
847
+ },
848
+ onError: (e) => {
849
+ throw e;
850
+ }
851
+ });
852
+ try {
853
+ const reviewData = JSON.parse(reviewText.trim());
854
+ return this.formatReviewResponse(reviewData);
855
+ } catch (parseErr) {
856
+ return `\u{1F916} **[AI Code Review Agent]**
857
+ \u26A0\uFE0F Response format error. Raw output:
858
+ ${reviewText.trim()}`;
859
+ }
860
+ } catch (err) {
861
+ FileLogger.log("CODE_REVIEW", "LLM Review Failed", { error: err.message });
862
+ return `\u26A0\uFE0F AI Review unavailable (${err.message}). Using syntax check:
863
+ ` + await this.performSyntaxCheck(filePath, content);
864
+ }
865
+ }
866
+ static formatReviewResponse(data) {
867
+ let report = `\u{1F916} **[AI Code Review Agent]**
868
+ `;
869
+ const statusIcon = data.status === "ok" ? "\u2705" : data.status === "warning" ? "\u26A0\uFE0F" : "\u274C";
870
+ report += `${statusIcon} **Status:** ${data.status.toUpperCase()}
871
+ `;
872
+ if (data.issues && data.issues.length > 0) {
873
+ report += `
874
+ **Issues Found:**
875
+ `;
876
+ for (const issue of data.issues) {
877
+ const severityIcon = issue.severity === "error" ? "\u274C" : issue.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
878
+ const lineInfo = issue.line ? `Linha ${issue.line}: ` : "";
879
+ report += `${severityIcon} ${lineInfo}${issue.message}
880
+ `;
881
+ if (issue.suggestion) {
882
+ report += ` \u{1F4A1} ${issue.suggestion}
883
+ `;
884
+ }
885
+ }
886
+ }
887
+ report += `
888
+ **Resumo:** ${data.summary}`;
889
+ return report;
890
+ }
891
+ static performSyntaxCheck(filePath, content) {
892
+ let report = `\u{1F50D} **[Syntax Validator]**
893
+ `;
894
+ const openBraces = (content.match(/\{/g) || []).length;
895
+ const closeBraces = (content.match(/\}/g) || []).length;
896
+ if (openBraces !== closeBraces) {
897
+ report += `\u274C **CRITICAL:** Brace mismatch detected! Found ${openBraces} '{' and ${closeBraces} '}'.
898
+ `;
899
+ report += ` **Review your code carefully. Do not confirm if the block is incomplete.**`;
900
+ } else {
901
+ report += `\u2705 File structure looks balanced (Braces match).
902
+ `;
903
+ }
904
+ return Promise.resolve(report);
905
+ }
906
+ };
907
+
908
+ // src/core/agents/agent-tools.ts
785
909
  var execAsync = promisify(exec);
786
910
  function detectLineEnding(content) {
787
911
  const crlf = content.split("\r\n").length - 1;
@@ -790,7 +914,7 @@ function detectLineEnding(content) {
790
914
  }
791
915
  function handleListFiles(dirPath) {
792
916
  try {
793
- const fullPath = path3.resolve(process.cwd(), dirPath);
917
+ const fullPath = path4.resolve(process.cwd(), dirPath);
794
918
  if (!fs3.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
795
919
  const items = fs3.readdirSync(fullPath, { withFileTypes: true });
796
920
  return items.map((item) => {
@@ -802,7 +926,7 @@ function handleListFiles(dirPath) {
802
926
  }
803
927
  function handleReadFile(filePath, showLineNumbers = true) {
804
928
  try {
805
- const fullPath = path3.resolve(process.cwd(), filePath);
929
+ const fullPath = path4.resolve(process.cwd(), filePath);
806
930
  if (!fs3.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
807
931
  const stats = fs3.statSync(fullPath);
808
932
  if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
@@ -848,9 +972,9 @@ function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
848
972
  return false;
849
973
  }
850
974
  }
851
- function generateFilePreview(filePath, startLine, endLine, newContent) {
975
+ async function generateFilePreview(filePath, startLine, endLine, newContent) {
852
976
  try {
853
- const fullPath = path3.resolve(process.cwd(), filePath);
977
+ const fullPath = path4.resolve(process.cwd(), filePath);
854
978
  if (!fs3.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
855
979
  const content = fs3.readFileSync(fullPath, "utf-8");
856
980
  const lines = content.split(/\r\n|\r|\n/);
@@ -862,6 +986,12 @@ function generateFilePreview(filePath, startLine, endLine, newContent) {
862
986
  const contentReplacing = lines.slice(startIdx, endIdx + 1).map((l, i) => `${startLine + i} | - ${l}`).join("\n");
863
987
  const contextAfter = lines.slice(endIdx + 1, Math.min(lines.length, endIdx + 4)).map((l, i) => `${endLine + 1 + i} | ${l}`).join("\n");
864
988
  const newLines = newContent.split("\n").map((l) => `+ ${l}`).join("\n");
989
+ let reviewFeedback = "";
990
+ try {
991
+ reviewFeedback = await CodeReviewService.reviewCode(filePath, newContent);
992
+ } catch (err) {
993
+ reviewFeedback = `\u26A0\uFE0F Code Review Service Unavailable: ${err.message}`;
994
+ }
865
995
  return `PREVIEW OF CHANGES to ${filePath}:
866
996
  --------------------------------------------------
867
997
  CONTEXT BEFORE:
@@ -874,6 +1004,8 @@ ${newLines}
874
1004
  CONTEXT AFTER:
875
1005
  ${contextAfter}
876
1006
  --------------------------------------------------
1007
+ ${reviewFeedback}
1008
+
877
1009
  IMPORTANT: Please verify that the lines being replaced (marked with -) are exactly what you intend to remove.
878
1010
  If the context looks wrong, DO NOT CONFIRM. Re-read the file to check line numbers.
879
1011
  `;
@@ -968,19 +1100,19 @@ function resolveAstGrepCommand() {
968
1100
  const binName = isWin ? "sg.cmd" : "sg";
969
1101
  try {
970
1102
  const currentFile = fileURLToPath(import.meta.url);
971
- let dir = path3.dirname(currentFile);
1103
+ let dir = path4.dirname(currentFile);
972
1104
  for (let i = 0; i < 5; i++) {
973
- const candidate = path3.join(dir, "node_modules", ".bin", binName);
1105
+ const candidate = path4.join(dir, "node_modules", ".bin", binName);
974
1106
  if (fs3.existsSync(candidate)) {
975
1107
  return `"${candidate}"`;
976
1108
  }
977
- const parent = path3.dirname(dir);
1109
+ const parent = path4.dirname(dir);
978
1110
  if (parent === dir) break;
979
1111
  dir = parent;
980
1112
  }
981
1113
  } catch (e) {
982
1114
  }
983
- const cwdBin = path3.resolve(process.cwd(), "node_modules", ".bin", binName);
1115
+ const cwdBin = path4.resolve(process.cwd(), "node_modules", ".bin", binName);
984
1116
  if (fs3.existsSync(cwdBin)) {
985
1117
  return `"${cwdBin}"`;
986
1118
  }
@@ -1080,11 +1212,11 @@ async function interactiveSpecificationAgent(options = {}) {
1080
1212
  tui.intro("\u{1F3D7}\uFE0F Specification Agent");
1081
1213
  const projectRoot = process.cwd();
1082
1214
  let contextContent = "";
1083
- const contextPath = path4.resolve(projectRoot, "_sharkrc", "project-context.md");
1215
+ const contextPath = path5.resolve(projectRoot, "_sharkrc", "project-context.md");
1084
1216
  if (fs4.existsSync(contextPath)) {
1085
1217
  try {
1086
1218
  contextContent = fs4.readFileSync(contextPath, "utf-8");
1087
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path4.relative(projectRoot, contextPath))}`);
1219
+ tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path5.relative(projectRoot, contextPath))}`);
1088
1220
  } catch (e) {
1089
1221
  tui.log.warning(`Failed to read context file: ${e}`);
1090
1222
  }
@@ -1094,7 +1226,7 @@ async function interactiveSpecificationAgent(options = {}) {
1094
1226
  briefingContent = fs4.readFileSync(options.briefingPath, "utf-8");
1095
1227
  tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
1096
1228
  } else {
1097
- const sharkRcBriefing = path4.resolve(projectRoot, "_sharkrc", "briefing.md");
1229
+ const sharkRcBriefing = path5.resolve(projectRoot, "_sharkrc", "briefing.md");
1098
1230
  if (fs4.existsSync(sharkRcBriefing)) {
1099
1231
  briefingContent = fs4.readFileSync(sharkRcBriefing, "utf-8");
1100
1232
  tui.log.info(`\u{1F4C4} Standard Briefing loaded: ${colors.dim("_sharkrc/briefing.md")}`);
@@ -1211,7 +1343,7 @@ ${result}
1211
1343
  const BOM = "\uFEFF";
1212
1344
  const contentToWrite = action.content || "";
1213
1345
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
1214
- const dir = path4.dirname(action.path);
1346
+ const dir = path5.dirname(action.path);
1215
1347
  if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
1216
1348
  fs4.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
1217
1349
  tui.log.success(`\u2705 Created: ${action.path}`);
@@ -1361,7 +1493,7 @@ import { Command as Command2 } from "commander";
1361
1493
 
1362
1494
  // src/core/agents/scan-agent.ts
1363
1495
  import fs5 from "fs";
1364
- import path5 from "path";
1496
+ import path6 from "path";
1365
1497
  var AGENT_TYPE3 = "scan_agent";
1366
1498
  function getAgentId3() {
1367
1499
  const config = ConfigManager.getInstance().getConfig();
@@ -1376,29 +1508,29 @@ async function interactiveScanAgent(options = {}) {
1376
1508
  const projectRoot = process.cwd();
1377
1509
  let outputFile;
1378
1510
  if (options.output) {
1379
- outputFile = path5.resolve(process.cwd(), options.output);
1511
+ outputFile = path6.resolve(process.cwd(), options.output);
1380
1512
  } else {
1381
- const outputDir = path5.resolve(projectRoot, "_sharkrc");
1513
+ const outputDir = path6.resolve(projectRoot, "_sharkrc");
1382
1514
  if (!fs5.existsSync(outputDir)) {
1383
1515
  const stat = fs5.existsSync(outputDir) ? fs5.statSync(outputDir) : null;
1384
1516
  if (stat && stat.isFile()) {
1385
1517
  tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
1386
- const fallbackDir = path5.resolve(projectRoot, "_bmad/project-context");
1518
+ const fallbackDir = path6.resolve(projectRoot, "_bmad/project-context");
1387
1519
  if (!fs5.existsSync(fallbackDir)) fs5.mkdirSync(fallbackDir, { recursive: true });
1388
- outputFile = path5.join(fallbackDir, "project-context.md");
1520
+ outputFile = path6.join(fallbackDir, "project-context.md");
1389
1521
  } else {
1390
1522
  fs5.mkdirSync(outputDir, { recursive: true });
1391
- outputFile = path5.join(outputDir, "project-context.md");
1523
+ outputFile = path6.join(outputDir, "project-context.md");
1392
1524
  }
1393
1525
  } else {
1394
1526
  fs5.mkdirSync(outputDir, { recursive: true });
1395
- outputFile = path5.join(outputDir, "project-context.md");
1527
+ outputFile = path6.join(outputDir, "project-context.md");
1396
1528
  }
1397
1529
  }
1398
1530
  tui.log.info(`${t("commands.scan.scanningProject")} ${colors.bold(projectRoot)}`);
1399
1531
  tui.log.info(`${t("commands.scan.outputTarget")} ${colors.bold(outputFile)}`);
1400
1532
  tui.log.info(`${t("commands.scan.language")} ${colors.bold(language)}`);
1401
- const configFileRelative = path5.relative(projectRoot, outputFile);
1533
+ const configFileRelative = path6.relative(projectRoot, outputFile);
1402
1534
  const initialTemplate = `# Project Context
1403
1535
 
1404
1536
  ## Overview
@@ -1731,11 +1863,11 @@ ${result}
1731
1863
 
1732
1864
  `;
1733
1865
  } else if (action.type === "create_file" || action.type === "modify_file") {
1734
- const resolvedActionPath = path5.resolve(action.path || "");
1735
- const resolvedTargetPath = path5.resolve(targetPath);
1866
+ const resolvedActionPath = path6.resolve(action.path || "");
1867
+ const resolvedTargetPath = path6.resolve(targetPath);
1736
1868
  let isTarget = resolvedActionPath === resolvedTargetPath;
1737
- if (!isTarget && path5.basename(action.path || "") === "project-context.md") {
1738
- tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path5.relative(process.cwd(), targetPath)));
1869
+ if (!isTarget && path6.basename(action.path || "") === "project-context.md") {
1870
+ tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path6.relative(process.cwd(), targetPath)));
1739
1871
  isTarget = true;
1740
1872
  action.path = targetPath;
1741
1873
  }
@@ -1876,7 +2008,7 @@ import { Command as Command3 } from "commander";
1876
2008
 
1877
2009
  // src/core/agents/developer-agent.ts
1878
2010
  import fs6 from "fs";
1879
- import path6 from "path";
2011
+ import path7 from "path";
1880
2012
  var AGENT_TYPE4 = "developer_agent";
1881
2013
  async function validateTypeScript(filePath) {
1882
2014
  try {
@@ -1923,7 +2055,7 @@ function getAgentId4(overrideId) {
1923
2055
  return "01KEQCGJ65YENRA4QBXVN1YFFX";
1924
2056
  }
1925
2057
  function analyzeSpecState(projectRoot) {
1926
- const specPath = path6.resolve(projectRoot, "tech-spec.md");
2058
+ const specPath = path7.resolve(projectRoot, "tech-spec.md");
1927
2059
  if (!fs6.existsSync(specPath)) {
1928
2060
  return { status: "MISSING" };
1929
2061
  }
@@ -2007,12 +2139,12 @@ async function interactiveDeveloperAgent(options = {}) {
2007
2139
  }
2008
2140
  const projectRoot = process.cwd();
2009
2141
  let contextContent = "";
2010
- const defaultContextPath = path6.resolve(projectRoot, "_sharkrc", "project-context.md");
2011
- const specificContextPath = options.context ? path6.resolve(projectRoot, options.context) : defaultContextPath;
2142
+ const defaultContextPath = path7.resolve(projectRoot, "_sharkrc", "project-context.md");
2143
+ const specificContextPath = options.context ? path7.resolve(projectRoot, options.context) : defaultContextPath;
2012
2144
  if (fs6.existsSync(specificContextPath)) {
2013
2145
  try {
2014
2146
  contextContent = fs6.readFileSync(specificContextPath, "utf-8");
2015
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path6.relative(projectRoot, specificContextPath))}`);
2147
+ tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path7.relative(projectRoot, specificContextPath))}`);
2016
2148
  } catch (e) {
2017
2149
  tui.log.warning(`Failed to read context file: ${e}`);
2018
2150
  }
@@ -2121,13 +2253,13 @@ ${result}
2121
2253
  if (!isCreate && action.line_range && !action.confirmed) {
2122
2254
  tui.log.info("\u{1F6E1}\uFE0F Generation Preview for Agent Verification...");
2123
2255
  const [start, end] = action.line_range;
2124
- const previewDiff = generateFilePreview(filePath, start, end, action.content || "");
2256
+ const previewDiff = await generateFilePreview(filePath, start, end, action.content || "");
2125
2257
  executionResults += `[Action modify_file]: PENDING CONFIRMATION.
2126
2258
 
2127
2259
  ${previewDiff}
2128
2260
 
2129
2261
  `;
2130
- executionResults += `USER/SYSTEM INSTRUCTION: Please review the above preview carefully.
2262
+ executionResults += `USER/SYSTEM INSTRUCTION: Please review the above preview carefully.
2131
2263
  1. If the context and changes look correct, call 'modify_file' again with the SAME parameters AND "confirmed": true.
2132
2264
  2. If the context is wrong (e.g. wrong line numbers), call 'read_file' to check line numbers again, then correct your request.
2133
2265
 
@@ -2159,8 +2291,8 @@ ${previewDiff}
2159
2291
  }
2160
2292
  if (approved) {
2161
2293
  if (filePath) {
2162
- const targetPath = path6.resolve(projectRoot, filePath);
2163
- const dir = path6.dirname(targetPath);
2294
+ const targetPath = path7.resolve(projectRoot, filePath);
2295
+ const dir = path7.dirname(targetPath);
2164
2296
  if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
2165
2297
  if (isCreate) {
2166
2298
  const BOM = "\uFEFF";
@@ -2172,37 +2304,41 @@ ${previewDiff}
2172
2304
 
2173
2305
  `;
2174
2306
  if (filePath.endsWith("tech-spec.md")) specUpdated = true;
2175
- const ext = path6.extname(filePath);
2176
- if ([".ts", ".tsx"].includes(ext)) {
2177
- tui.log.info("\u{1F50D} Validating TypeScript...");
2178
- const validation = await validateTypeScript(filePath);
2179
- if (!validation.valid) {
2180
- tui.log.error("\u274C TypeScript validation failed");
2181
- executionResults += `
2307
+ const config = ConfigManager.getInstance().getConfig();
2308
+ const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
2309
+ if (validationEnabled) {
2310
+ const ext = path7.extname(filePath);
2311
+ if ([".ts", ".tsx"].includes(ext)) {
2312
+ tui.log.info("\u{1F50D} Validating TypeScript...");
2313
+ const validation = await validateTypeScript(filePath);
2314
+ if (!validation.valid) {
2315
+ tui.log.error("\u274C TypeScript validation failed");
2316
+ executionResults += `
2182
2317
  [TYPESCRIPT VALIDATION FAILED]:
2183
2318
  ${validation.error}
2184
2319
 
2185
2320
  `;
2186
- 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.
2321
+ 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.
2187
2322
  `;
2188
- } else {
2189
- tui.log.success("\u2705 TypeScript OK");
2323
+ } else {
2324
+ tui.log.success("\u2705 TypeScript OK");
2325
+ }
2190
2326
  }
2191
- }
2192
- if (ext === ".html") {
2193
- tui.log.info("\u{1F50D} Validating HTML...");
2194
- const validation = validateHtmlTagBalance(filePath);
2195
- if (!validation.valid) {
2196
- tui.log.error("\u274C HTML validation failed");
2197
- executionResults += `
2327
+ if (ext === ".html") {
2328
+ tui.log.info("\u{1F50D} Validating HTML...");
2329
+ const validation = validateHtmlTagBalance(filePath);
2330
+ if (!validation.valid) {
2331
+ tui.log.error("\u274C HTML validation failed");
2332
+ executionResults += `
2198
2333
  [HTML VALIDATION FAILED]:
2199
2334
  ${validation.error}
2200
2335
 
2201
2336
  `;
2202
- executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2337
+ executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2203
2338
  `;
2204
- } else {
2205
- tui.log.success("\u2705 HTML OK");
2339
+ } else {
2340
+ tui.log.success("\u2705 HTML OK");
2341
+ }
2206
2342
  }
2207
2343
  }
2208
2344
  } else {
@@ -2224,37 +2360,41 @@ ${validation.error}
2224
2360
 
2225
2361
  `;
2226
2362
  if (filePath.endsWith("tech-spec.md")) specUpdated = true;
2227
- const ext = path6.extname(filePath);
2228
- if ([".ts", ".tsx"].includes(ext)) {
2229
- tui.log.info("\u{1F50D} Validating TypeScript...");
2230
- const validation = await validateTypeScript(filePath);
2231
- if (!validation.valid) {
2232
- tui.log.error("\u274C TypeScript validation failed");
2233
- executionResults += `
2363
+ const config = ConfigManager.getInstance().getConfig();
2364
+ const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
2365
+ if (validationEnabled) {
2366
+ const ext = path7.extname(filePath);
2367
+ if ([".ts", ".tsx"].includes(ext)) {
2368
+ tui.log.info("\u{1F50D} Validating TypeScript...");
2369
+ const validation = await validateTypeScript(filePath);
2370
+ if (!validation.valid) {
2371
+ tui.log.error("\u274C TypeScript validation failed");
2372
+ executionResults += `
2234
2373
  [TYPESCRIPT VALIDATION FAILED]:
2235
2374
  ${validation.error}
2236
2375
 
2237
2376
  `;
2238
- executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2377
+ executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2239
2378
  `;
2240
- } else {
2241
- tui.log.success("\u2705 TypeScript OK");
2379
+ } else {
2380
+ tui.log.success("\u2705 TypeScript OK");
2381
+ }
2242
2382
  }
2243
- }
2244
- if (ext === ".html") {
2245
- tui.log.info("\u{1F50D} Validating HTML...");
2246
- const validation = validateHtmlTagBalance(filePath);
2247
- if (!validation.valid) {
2248
- tui.log.error("\u274C HTML validation failed");
2249
- executionResults += `
2383
+ if (ext === ".html") {
2384
+ tui.log.info("\u{1F50D} Validating HTML...");
2385
+ const validation = validateHtmlTagBalance(filePath);
2386
+ if (!validation.valid) {
2387
+ tui.log.error("\u274C HTML validation failed");
2388
+ executionResults += `
2250
2389
  [HTML VALIDATION FAILED]:
2251
2390
  ${validation.error}
2252
2391
 
2253
2392
  `;
2254
- executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2393
+ executionResults += `CRITICAL: Fix these errors before proceeding to next task.
2255
2394
  `;
2256
- } else {
2257
- tui.log.success("\u2705 HTML OK");
2395
+ } else {
2396
+ tui.log.success("\u2705 HTML OK");
2397
+ }
2258
2398
  }
2259
2399
  }
2260
2400
  } else if (!action.line_range && !action.target_content) {
@@ -2303,7 +2443,7 @@ ${result}
2303
2443
 
2304
2444
  `;
2305
2445
  if (targetPath.endsWith("tech-spec.md")) specUpdated = true;
2306
- const ext = path6.extname(targetPath);
2446
+ const ext = path7.extname(targetPath);
2307
2447
  if ([".ts", ".tsx"].includes(ext)) {
2308
2448
  const validation = await validateTypeScript(targetPath);
2309
2449
  if (!validation.valid) {
@@ -2441,7 +2581,7 @@ import { Command as Command4 } from "commander";
2441
2581
 
2442
2582
  // src/core/agents/qa-agent.ts
2443
2583
  import fs7 from "fs";
2444
- import path7 from "path";
2584
+ import path8 from "path";
2445
2585
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2446
2586
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2447
2587
  var AGENT_TYPE5 = "qa_agent";
@@ -2510,7 +2650,7 @@ async function runQAAgent(options) {
2510
2650
  }
2511
2651
  let projectContext = "";
2512
2652
  try {
2513
- const contextPath = path7.join(process.cwd(), "_sharkrc", "project-context.md");
2653
+ const contextPath = path8.join(process.cwd(), "_sharkrc", "project-context.md");
2514
2654
  if (fs7.existsSync(contextPath)) {
2515
2655
  projectContext = fs7.readFileSync(contextPath, "utf-8");
2516
2656
  tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
@@ -2621,7 +2761,7 @@ ${projectContext}
2621
2761
  break;
2622
2762
  case "create_file":
2623
2763
  if (action.path && action.content) {
2624
- const fullPath = path7.resolve(process.cwd(), action.path);
2764
+ const fullPath = path8.resolve(process.cwd(), action.path);
2625
2765
  const BOM = "\uFEFF";
2626
2766
  const contentToWrite = action.content;
2627
2767
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;