@novastorm-ai/cli 0.1.3 → 0.1.6

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/nova.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "../chunk-HVDG2MLB.js";
4
+ } from "../chunk-7ZE7J73C.js";
5
5
  import "../chunk-KE7XWO5N.js";
6
- import "../chunk-DQXTUNZA.js";
7
- import "../chunk-7K55GHF5.js";
6
+ import "../chunk-53K63TXF.js";
7
+ import "../chunk-3FVS3SB3.js";
8
8
  import "../chunk-3RG5ZIWI.js";
9
9
 
10
10
  // bin/nova.ts
@@ -2007,7 +2007,12 @@ var FRAMEWORK_DEPS = [
2007
2007
  { dep: "@sveltejs/kit", framework: "sveltekit" },
2008
2008
  { dep: "astro", framework: "astro" },
2009
2009
  { dep: "vite", framework: "vite" },
2010
- { dep: "react-scripts", framework: "cra" }
2010
+ { dep: "react-scripts", framework: "cra" },
2011
+ { dep: "express", framework: "express" },
2012
+ { dep: "@nestjs/core", framework: "nest" },
2013
+ { dep: "fastify", framework: "fastify" },
2014
+ { dep: "koa", framework: "koa" },
2015
+ { dep: "@hapi/hapi", framework: "hapi" }
2011
2016
  ];
2012
2017
  var PYTHON_FRAMEWORKS = [
2013
2018
  { dep: "django", framework: "django" },
@@ -2021,6 +2026,12 @@ var DEFAULT_PORTS = {
2021
2026
  "sveltekit": 5173,
2022
2027
  "astro": 4321,
2023
2028
  "vite": 5173,
2029
+ "express": 3e3,
2030
+ "nest": 3e3,
2031
+ "fastify": 3e3,
2032
+ "koa": 3e3,
2033
+ "hapi": 3e3,
2034
+ "node": 3e3,
2024
2035
  "dotnet": 5e3,
2025
2036
  "django": 8e3,
2026
2037
  "fastapi": 8e3,
@@ -2036,37 +2047,70 @@ var DEFAULT_PORTS = {
2036
2047
  };
2037
2048
  var StackDetector = class {
2038
2049
  async detectStack(projectPath) {
2039
- const pkgResult = await this.detectFromPackageJson(projectPath);
2040
- if (pkgResult) {
2041
- return pkgResult;
2042
- }
2043
- if (await this.hasCsproj(projectPath)) {
2050
+ const detected = [];
2051
+ const [pkgResult, hasDotnet, pythonFw, rubyFw, phpFw, javaFw, hasGo, hasRust] = await Promise.all([
2052
+ this.detectFromPackageJson(projectPath),
2053
+ this.hasDotnet(projectPath),
2054
+ this.detectPython(projectPath),
2055
+ this.detectRuby(projectPath),
2056
+ this.detectPhp(projectPath),
2057
+ this.detectJava(projectPath),
2058
+ this.fileExists(join9(projectPath, "go.mod")),
2059
+ this.fileExists(join9(projectPath, "Cargo.toml"))
2060
+ ]);
2061
+ if (pkgResult) detected.push(pkgResult);
2062
+ if (hasDotnet) {
2044
2063
  const typescript = await this.hasTypescript(projectPath);
2045
- return { framework: "dotnet", language: "csharp", typescript };
2046
- }
2047
- const pythonFramework = await this.detectPython(projectPath);
2048
- if (pythonFramework) {
2049
- return { framework: pythonFramework, language: "python", typescript: false };
2050
- }
2051
- const rubyFramework = await this.detectRuby(projectPath);
2052
- if (rubyFramework) {
2053
- return { framework: rubyFramework, language: "ruby", typescript: false };
2054
- }
2055
- const phpFramework = await this.detectPhp(projectPath);
2056
- if (phpFramework) {
2057
- return { framework: phpFramework, language: "php", typescript: false };
2058
- }
2059
- const javaFramework = await this.detectJava(projectPath);
2060
- if (javaFramework) {
2061
- return { framework: javaFramework, language: "java", typescript: false };
2062
- }
2063
- if (await this.fileExists(join9(projectPath, "go.mod"))) {
2064
- return { framework: "go", language: "go", typescript: false };
2065
- }
2066
- if (await this.fileExists(join9(projectPath, "Cargo.toml"))) {
2067
- return { framework: "rust", language: "rust", typescript: false };
2064
+ detected.push({ framework: "dotnet", language: "csharp", typescript });
2065
+ }
2066
+ if (pythonFw) detected.push({ framework: pythonFw, language: "python", typescript: false });
2067
+ if (rubyFw) detected.push({ framework: rubyFw, language: "ruby", typescript: false });
2068
+ if (phpFw) detected.push({ framework: phpFw, language: "php", typescript: false });
2069
+ if (javaFw) detected.push({ framework: javaFw, language: "java", typescript: false });
2070
+ if (hasGo) detected.push({ framework: "go", language: "go", typescript: false });
2071
+ if (hasRust) detected.push({ framework: "rust", language: "rust", typescript: false });
2072
+ if (detected.length === 0) {
2073
+ return { framework: "unknown", language: "unknown", typescript: false };
2074
+ }
2075
+ const PRIORITY = [
2076
+ "next.js",
2077
+ "nuxt",
2078
+ "sveltekit",
2079
+ "astro",
2080
+ "vite",
2081
+ "cra",
2082
+ "dotnet",
2083
+ "django",
2084
+ "fastapi",
2085
+ "flask",
2086
+ "rails",
2087
+ "sinatra",
2088
+ "laravel",
2089
+ "symfony",
2090
+ "spring-boot",
2091
+ "express",
2092
+ "nest",
2093
+ "fastify",
2094
+ "koa",
2095
+ "hapi",
2096
+ "node",
2097
+ "python",
2098
+ "ruby",
2099
+ "php",
2100
+ "java",
2101
+ "go",
2102
+ "rust"
2103
+ ];
2104
+ detected.sort((a, b) => {
2105
+ const ai = PRIORITY.indexOf(a.framework);
2106
+ const bi = PRIORITY.indexOf(b.framework);
2107
+ return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
2108
+ });
2109
+ const primary = detected[0];
2110
+ if (detected.length > 1) {
2111
+ primary.additionalStacks = detected.slice(1).map((s) => s.framework);
2068
2112
  }
2069
- return { framework: "unknown", language: "unknown", typescript: false };
2113
+ return primary;
2070
2114
  }
2071
2115
  async detectDevCommand(stack, projectPath) {
2072
2116
  const { framework, language, packageManager } = stack;
@@ -2113,20 +2157,19 @@ var StackDetector = class {
2113
2157
  ...pkg.devDependencies
2114
2158
  };
2115
2159
  const framework = FRAMEWORK_DEPS.find((f) => f.dep in allDeps);
2116
- if (!framework) return null;
2117
2160
  const typescript = await this.hasTypescript(projectPath);
2118
2161
  const packageManager = await this.detectPackageManager(projectPath);
2119
2162
  return {
2120
- framework: framework.framework,
2163
+ framework: framework?.framework ?? "node",
2121
2164
  language: typescript ? "typescript" : "javascript",
2122
2165
  packageManager,
2123
2166
  typescript
2124
2167
  };
2125
2168
  }
2126
- async hasCsproj(projectPath) {
2169
+ async hasDotnet(projectPath) {
2127
2170
  try {
2128
2171
  const entries = await readdir3(projectPath);
2129
- return entries.some((e) => e.endsWith(".csproj"));
2172
+ return entries.some((e) => e.endsWith(".csproj") || e.endsWith(".sln"));
2130
2173
  } catch {
2131
2174
  return false;
2132
2175
  }
@@ -6201,7 +6244,7 @@ Available packages: ${deps}`);
6201
6244
  return parts.join("\n");
6202
6245
  }
6203
6246
  var Lane3Executor = class {
6204
- constructor(projectPath, llmClient, gitManager, eventBus, maxFixIterations = 3, modelName, agentPromptLoader, pathGuard, commitQueue) {
6247
+ constructor(projectPath, llmClient, gitManager, eventBus, maxFixIterations = 3, modelName, agentPromptLoader, pathGuard, commitQueue, forceSkipValidation = false) {
6205
6248
  this.projectPath = projectPath;
6206
6249
  this.llmClient = llmClient;
6207
6250
  this.gitManager = gitManager;
@@ -6210,6 +6253,7 @@ var Lane3Executor = class {
6210
6253
  this.modelName = modelName;
6211
6254
  this.agentPromptLoader = agentPromptLoader;
6212
6255
  this.pathGuard = pathGuard;
6256
+ this.forceSkipValidation = forceSkipValidation;
6213
6257
  this.diffApplier = new DiffApplier();
6214
6258
  this.commitQueue = commitQueue ?? new CommitQueue(this.gitManager);
6215
6259
  }
@@ -6317,7 +6361,7 @@ Remember: Output ONLY === FILE === or === DIFF === blocks. No text, no explanati
6317
6361
  if (missingVars.length > 0 && this.eventBus) {
6318
6362
  this.eventBus.emit({ type: "secrets_required", data: { envVars: missingVars, taskId: task.id } });
6319
6363
  }
6320
- const skipValidation = fileBlocks.length === 1 && fileBlocks[0].content.length < 3e3;
6364
+ const skipValidation = this.forceSkipValidation || fileBlocks.length === 1 && fileBlocks[0].content.length < 3e3;
6321
6365
  const tscSkip = this.shouldSkipTsc(fileBlocks);
6322
6366
  const validator = new CodeValidator(this.projectPath);
6323
6367
  const fixer = new CodeFixer(this.llmClient, this.eventBus);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ConfigError,
3
3
  DEFAULT_CONFIG
4
- } from "./chunk-7K55GHF5.js";
4
+ } from "./chunk-3FVS3SB3.js";
5
5
 
6
6
  // src/setup.ts
7
7
  import * as fs2 from "fs/promises";
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  ConfigReader,
8
8
  runSetup
9
- } from "./chunk-DQXTUNZA.js";
9
+ } from "./chunk-53K63TXF.js";
10
10
  import {
11
11
  AgentPromptLoader,
12
12
  Brain,
@@ -25,8 +25,9 @@ import {
25
25
  ProjectIndexer,
26
26
  ProjectScaffolder,
27
27
  ProviderFactory,
28
- SCAFFOLD_PRESETS
29
- } from "./chunk-7K55GHF5.js";
28
+ SCAFFOLD_PRESETS,
29
+ StackDetector
30
+ } from "./chunk-3FVS3SB3.js";
30
31
  import {
31
32
  __require
32
33
  } from "./chunk-3RG5ZIWI.js";
@@ -39,11 +40,15 @@ import { Command } from "commander";
39
40
 
40
41
  // src/commands/start.ts
41
42
  import { exec } from "child_process";
43
+ import { existsSync, readdirSync } from "fs";
44
+ import { writeFile as writeFile2 } from "fs/promises";
42
45
  import * as net from "net";
43
46
  import * as path from "path";
44
47
  import chalk6 from "chalk";
45
48
  import ora2 from "ora";
46
- import { resolve as resolve2 } from "path";
49
+ import { resolve as resolve2, join as join2 } from "path";
50
+ import { input as input2 } from "@inquirer/prompts";
51
+ import TOML from "@iarna/toml";
47
52
 
48
53
  // ../licensing/dist/index.js
49
54
  import { createHash } from "crypto";
@@ -507,6 +512,10 @@ var ErrorAutoFixer = class {
507
512
  MAX_FIX_ATTEMPTS = 3;
508
513
  lastErrorSignature = "";
509
514
  cooldownUntil = 0;
515
+ autofixTaskIds = /* @__PURE__ */ new Set();
516
+ isAutofixTask(taskId) {
517
+ return this.autofixTaskIds.has(taskId);
518
+ }
510
519
  /**
511
520
  * Process dev server output. Call this for every stdout/stderr chunk.
512
521
  */
@@ -603,6 +612,7 @@ Error: ${errorOutput.slice(0, 300)}`;
603
612
  lane: 3,
604
613
  status: "pending"
605
614
  };
615
+ this.autofixTaskIds.add(task.id);
606
616
  const executor = new Lane3Executor(
607
617
  this.projectPath,
608
618
  this.llmClient,
@@ -616,13 +626,16 @@ Error: ${errorOutput.slice(0, 300)}`;
616
626
  // agentPromptLoader
617
627
  void 0,
618
628
  // pathGuard
619
- this.commitQueue
629
+ this.commitQueue,
630
+ true
631
+ // skipValidation — auto-fix tasks skip tsc
620
632
  );
621
633
  console.log(chalk3.cyan("[Nova] Auto-fixing image errors..."));
622
634
  this.wsServer.sendEvent({ type: "status", data: { message: "autofix_start" } });
623
635
  this.eventBus.emit({ type: "task_started", data: { taskId: task.id } });
624
636
  this.wsServer.sendEvent({ type: "task_created", data: task });
625
637
  const result = await executor.execute(task, this.projectMap);
638
+ this.autofixTaskIds.delete(task.id);
626
639
  if (result.success) {
627
640
  console.log(chalk3.green("[Nova] Image errors fixed automatically"));
628
641
  this.eventBus.emit({
@@ -639,7 +652,6 @@ Error: ${errorOutput.slice(0, 300)}`;
639
652
  }
640
653
  }
641
654
  async fixCompilationError(errorOutput) {
642
- const truncatedError = errorOutput.slice(0, 500);
643
655
  console.log(
644
656
  chalk3.yellow("[Nova] Detected compilation error \u2014 attempting auto-fix")
645
657
  );
@@ -647,6 +659,56 @@ Error: ${errorOutput.slice(0, 300)}`;
647
659
  type: "status",
648
660
  data: { message: "Compilation error detected. Auto-fixing..." }
649
661
  });
662
+ const targetFile = this.extractFilePath(errorOutput);
663
+ if (targetFile && this.projectMap.fileContexts.has(targetFile)) {
664
+ await this.fixWithLane2(targetFile, errorOutput);
665
+ } else {
666
+ await this.fixWithLane3(errorOutput);
667
+ }
668
+ }
669
+ async fixWithLane2(targetFile, errorOutput) {
670
+ const truncatedError = errorOutput.slice(0, 500);
671
+ const task = {
672
+ id: crypto.randomUUID(),
673
+ description: `Fix the following compilation/build error in the project. Read the error carefully and fix the root cause:
674
+ ${truncatedError}`,
675
+ files: [targetFile],
676
+ type: "single_file",
677
+ lane: 2,
678
+ status: "pending"
679
+ };
680
+ this.autofixTaskIds.add(task.id);
681
+ const executor = new Lane2Executor(
682
+ this.projectPath,
683
+ this.llmClient,
684
+ this.gitManager,
685
+ void 0,
686
+ // pathGuard
687
+ this.commitQueue
688
+ );
689
+ console.log(chalk3.cyan(`[Nova] Auto-fixing compilation error via Lane 2 (${targetFile})...`));
690
+ this.wsServer.sendEvent({ type: "status", data: { message: "autofix_start" } });
691
+ this.eventBus.emit({ type: "task_started", data: { taskId: task.id } });
692
+ this.wsServer.sendEvent({ type: "task_created", data: task });
693
+ const result = await executor.execute(task, this.projectMap);
694
+ this.autofixTaskIds.delete(task.id);
695
+ if (result.success) {
696
+ console.log(chalk3.green("[Nova] Compilation error fixed automatically (Lane 2)"));
697
+ this.eventBus.emit({
698
+ type: "task_completed",
699
+ data: { taskId: task.id, diff: result.diff ?? "", commitHash: result.commitHash ?? "" }
700
+ });
701
+ this.wsServer.sendEvent({ type: "status", data: { message: "autofix_end" } });
702
+ } else {
703
+ console.log(chalk3.red(`[Nova] Auto-fix failed: ${result.error}`));
704
+ const failEvent = { type: "task_failed", data: { taskId: task.id, error: result.error ?? "Auto-fix failed" } };
705
+ this.eventBus.emit(failEvent);
706
+ this.wsServer.sendEvent(failEvent);
707
+ this.wsServer.sendEvent({ type: "status", data: { message: "autofix_failed" } });
708
+ }
709
+ }
710
+ async fixWithLane3(errorOutput) {
711
+ const truncatedError = errorOutput.slice(0, 500);
650
712
  const task = {
651
713
  id: crypto.randomUUID(),
652
714
  description: `Fix the following compilation/build error in the project. Read the error carefully and fix the root cause:
@@ -656,6 +718,7 @@ ${truncatedError}`,
656
718
  lane: 3,
657
719
  status: "pending"
658
720
  };
721
+ this.autofixTaskIds.add(task.id);
659
722
  const executor = new Lane3Executor(
660
723
  this.projectPath,
661
724
  this.llmClient,
@@ -669,13 +732,16 @@ ${truncatedError}`,
669
732
  // agentPromptLoader
670
733
  void 0,
671
734
  // pathGuard
672
- this.commitQueue
735
+ this.commitQueue,
736
+ true
737
+ // skipValidation — auto-fix tasks skip tsc
673
738
  );
674
739
  console.log(chalk3.cyan("[Nova] Auto-fixing compilation error..."));
675
740
  this.wsServer.sendEvent({ type: "status", data: { message: "autofix_start" } });
676
741
  this.eventBus.emit({ type: "task_started", data: { taskId: task.id } });
677
742
  this.wsServer.sendEvent({ type: "task_created", data: task });
678
743
  const result = await executor.execute(task, this.projectMap);
744
+ this.autofixTaskIds.delete(task.id);
679
745
  if (result.success) {
680
746
  console.log(chalk3.green("[Nova] Compilation error fixed automatically"));
681
747
  this.eventBus.emit({
@@ -691,6 +757,21 @@ ${truncatedError}`,
691
757
  this.wsServer.sendEvent({ type: "status", data: { message: "autofix_failed" } });
692
758
  }
693
759
  }
760
+ extractFilePath(errorOutput) {
761
+ const patterns = [
762
+ /\.\/([^\s:]+\.[tj]sx?)/,
763
+ // ./path/to/file.tsx
764
+ /(?:in|at|from)\s+([^\s:]+\.[tj]sx?)/i,
765
+ // in path/to/file.tsx
766
+ /([^\s:]+\.[tj]sx?)[\s:]/
767
+ // path/to/file.tsx:line
768
+ ];
769
+ for (const p of patterns) {
770
+ const match = errorOutput.match(p);
771
+ if (match) return match[1];
772
+ }
773
+ return null;
774
+ }
694
775
  };
695
776
 
696
777
  // src/chat.ts
@@ -753,11 +834,11 @@ var NovaChat = class {
753
834
  this.rl?.close();
754
835
  this.rl = null;
755
836
  }
756
- parse(input2) {
757
- const lower = input2.toLowerCase();
837
+ parse(input3) {
838
+ const lower = input3.toLowerCase();
758
839
  for (const [prefix, type] of Object.entries(SLASH_COMMANDS)) {
759
840
  if (lower === prefix || lower.startsWith(prefix + " ")) {
760
- const args = input2.slice(prefix.length).trim();
841
+ const args = input3.slice(prefix.length).trim();
761
842
  return { type, args };
762
843
  }
763
844
  }
@@ -767,7 +848,7 @@ var NovaChat = class {
767
848
  if (lower === "n" || lower === "no" || lower === "cancel") {
768
849
  return { type: "cancel", args: "" };
769
850
  }
770
- return { type: "text", args: input2 };
851
+ return { type: "text", args: input3 };
771
852
  }
772
853
  };
773
854
 
@@ -1002,7 +1083,7 @@ async function startCommand() {
1002
1083
  projectHash = createHash2("sha256").update(cwd).digest("hex");
1003
1084
  }
1004
1085
  const telemetry = new Telemetry();
1005
- const cliPkg = await import("./package-BSAVJZ7S.js").catch(
1086
+ const cliPkg = await import("./package-4UBQQT6Y.js").catch(
1006
1087
  () => ({ default: { version: "0.0.1" } })
1007
1088
  );
1008
1089
  telemetry.send({
@@ -1031,39 +1112,100 @@ ${nudgeMessage}
1031
1112
  }).catch(() => {
1032
1113
  });
1033
1114
  }
1115
+ if (!config.apiKeys.key && config.apiKeys.provider !== "ollama" && config.apiKeys.provider !== "claude-cli") {
1116
+ console.log(chalk6.yellow("\nNo API key configured. Running setup...\n"));
1117
+ const { runSetup: runSetup2 } = await import("./setup-33G3DXAH.js");
1118
+ await runSetup2(cwd);
1119
+ const updatedConfig = await configReader.read(cwd);
1120
+ config.apiKeys = updatedConfig.apiKeys;
1121
+ }
1122
+ const providerFactory = new ProviderFactory();
1123
+ let llmClient;
1124
+ try {
1125
+ llmClient = providerFactory.create(config.apiKeys.provider, config.apiKeys.key);
1126
+ } catch (err) {
1127
+ console.log(chalk6.yellow("\nAI provider not configured. Nova is running without AI analysis."));
1128
+ console.log(chalk6.dim('Run "nova setup" to configure your API key.\n'));
1129
+ llmClient = null;
1130
+ }
1131
+ const brain = llmClient ? new Brain(llmClient, eventBus) : null;
1034
1132
  spinner.start("Detecting project...");
1035
- const { StackDetector } = await import("./dist-NNJKY4T4.js");
1036
1133
  const stackDetector = new StackDetector();
1037
1134
  let stack = await stackDetector.detectStack(cwd);
1038
1135
  let detectedDevCommand = await stackDetector.detectDevCommand(stack, cwd);
1039
1136
  let detectedPort = await stackDetector.detectPort(stack, cwd);
1040
- spinner.succeed(`Detecting project... ${chalk6.cyan(stack.framework || "unknown")} + ${chalk6.cyan(stack.typescript ? "TypeScript" : stack.language || "unknown")}`);
1137
+ const allStacks = [stack.framework, ...stack.additionalStacks ?? []];
1138
+ const stackLabel = allStacks.filter((s) => s !== "unknown").join(" + ") || "unknown";
1139
+ const langLabel = stack.typescript ? "TypeScript" : stack.language || "unknown";
1140
+ spinner.succeed(`Detecting project... ${chalk6.cyan(stackLabel)} (${chalk6.dim(langLabel)})`);
1141
+ if (stack.framework !== "unknown") {
1142
+ console.log(chalk6.green(` Detected: ${stackLabel}`));
1143
+ } else {
1144
+ const dirFiles = readdirSync(cwd).slice(0, 10).join(", ");
1145
+ console.log(chalk6.yellow(` Could not detect framework. Files in directory: ${dirFiles}`));
1146
+ }
1041
1147
  let devCommand = config.project.devCommand || detectedDevCommand;
1042
1148
  let devPort = config.project.port || detectedPort;
1043
1149
  if (!devCommand) {
1044
- if (novaDir.exists(cwd)) {
1045
- await novaDir.clean(cwd);
1046
- }
1047
- const scaffoldInfo = await promptAndScaffold(cwd);
1048
- if (!scaffoldInfo.scaffolded) {
1049
- process.exit(0);
1050
- }
1051
- if (scaffoldInfo.frontend) config.project.frontend = scaffoldInfo.frontend;
1052
- if (scaffoldInfo.backends) config.project.backends = scaffoldInfo.backends;
1053
- spinner.start("Re-detecting project...");
1054
- stack = await stackDetector.detectStack(cwd);
1055
- detectedDevCommand = await stackDetector.detectDevCommand(stack, cwd);
1056
- detectedPort = await stackDetector.detectPort(stack, cwd);
1057
- spinner.succeed(
1058
- `Detecting project... ${chalk6.cyan(stack.framework || "unknown")} + ${chalk6.cyan(stack.typescript ? "TypeScript" : stack.language || "unknown")}`
1059
- );
1060
- devCommand = config.project.devCommand || detectedDevCommand;
1061
- devPort = config.project.port || detectedPort;
1062
- if (!devCommand) {
1063
- console.error(
1064
- chalk6.red('No dev command found after scaffolding. Set project.devCommand in nova.toml or ensure package.json has a "dev" script.')
1065
- );
1066
- process.exit(1);
1150
+ const projectMarkers = ["package.json", "requirements.txt", "go.mod", "Cargo.toml", "pom.xml", "build.gradle", "composer.json", "Gemfile"];
1151
+ const hasProjectFiles = projectMarkers.some((f) => existsSync(join2(cwd, f))) || readdirSync(cwd).some((f) => f.endsWith(".sln") || f.endsWith(".csproj"));
1152
+ if (hasProjectFiles) {
1153
+ const defaultCmd = stack.framework === "dotnet" ? "dotnet run" : stack.framework === "django" ? "python manage.py runserver" : stack.framework === "fastapi" ? "uvicorn main:app --reload" : stack.framework === "flask" ? "flask run" : stack.framework === "rails" ? "bin/rails server" : stack.framework === "laravel" ? "php artisan serve" : stack.framework === "spring-boot" ? "./mvnw spring-boot:run" : existsSync(join2(cwd, "package.json")) ? "npm run dev" : "";
1154
+ const stackLabel2 = stack.framework !== "unknown" ? ` (${chalk6.cyan(stack.framework)} detected)` : "";
1155
+ let devCmd;
1156
+ try {
1157
+ devCmd = await input2({
1158
+ message: `Dev command not found${stackLabel2}. Enter your dev command:`,
1159
+ default: defaultCmd || void 0
1160
+ });
1161
+ } catch {
1162
+ console.log("\nCancelled.");
1163
+ process.exit(0);
1164
+ }
1165
+ if (devCmd && devCmd.trim()) {
1166
+ devCommand = devCmd.trim();
1167
+ try {
1168
+ const novaTomlPath = join2(cwd, "nova.toml");
1169
+ let tomlContent = {};
1170
+ if (existsSync(novaTomlPath)) {
1171
+ const { readFileSync: readFileSync2 } = await import("fs");
1172
+ tomlContent = TOML.parse(readFileSync2(novaTomlPath, "utf-8"));
1173
+ }
1174
+ const project = tomlContent["project"] ?? {};
1175
+ project["devCommand"] = devCommand;
1176
+ tomlContent["project"] = project;
1177
+ await writeFile2(novaTomlPath, TOML.stringify(tomlContent), "utf-8");
1178
+ console.log(chalk6.dim(`Saved devCommand to nova.toml`));
1179
+ } catch {
1180
+ }
1181
+ } else {
1182
+ console.error(chalk6.red('Dev command is required. Add [project] devCommand = "..." to nova.toml'));
1183
+ process.exit(1);
1184
+ }
1185
+ } else {
1186
+ if (novaDir.exists(cwd)) {
1187
+ await novaDir.clean(cwd);
1188
+ }
1189
+ const scaffoldInfo = await promptAndScaffold(cwd);
1190
+ if (!scaffoldInfo.scaffolded) {
1191
+ process.exit(0);
1192
+ }
1193
+ if (scaffoldInfo.frontend) config.project.frontend = scaffoldInfo.frontend;
1194
+ if (scaffoldInfo.backends) config.project.backends = scaffoldInfo.backends;
1195
+ spinner.start("Re-detecting project...");
1196
+ stack = await stackDetector.detectStack(cwd);
1197
+ detectedDevCommand = await stackDetector.detectDevCommand(stack, cwd);
1198
+ detectedPort = await stackDetector.detectPort(stack, cwd);
1199
+ const reStacks = [stack.framework, ...stack.additionalStacks ?? []].filter((s) => s !== "unknown").join(" + ") || "unknown";
1200
+ spinner.succeed(`Detecting project... ${chalk6.cyan(reStacks)} (${chalk6.dim(stack.typescript ? "TypeScript" : stack.language || "unknown")})`);
1201
+ devCommand = config.project.devCommand || detectedDevCommand;
1202
+ devPort = config.project.port || detectedPort;
1203
+ if (!devCommand) {
1204
+ console.error(
1205
+ chalk6.red('No dev command found after scaffolding. Set project.devCommand in nova.toml or ensure package.json has a "dev" script.')
1206
+ );
1207
+ process.exit(1);
1208
+ }
1067
1209
  }
1068
1210
  }
1069
1211
  spinner.start("Initializing .nova/ directory...");
@@ -1078,7 +1220,7 @@ ${nudgeMessage}
1078
1220
  throw err;
1079
1221
  }
1080
1222
  spinner.succeed("Project indexed.");
1081
- const { ProjectAnalyzer, RagIndexer, createEmbeddingService } = await import("./dist-NNJKY4T4.js");
1223
+ const { ProjectAnalyzer, RagIndexer, createEmbeddingService } = await import("./dist-NVKTAKZX.js");
1082
1224
  const { ProjectMapApi } = await import("./dist-5FLNK6MH.js");
1083
1225
  const projectAnalyzer = new ProjectAnalyzer();
1084
1226
  spinner.start("Analyzing project structure...");
@@ -1086,7 +1228,7 @@ ${nudgeMessage}
1086
1228
  spinner.succeed(`Project analyzed: ${analysis.fileCount} files, ${analysis.methods.length} methods.`);
1087
1229
  let ragIndexer = null;
1088
1230
  try {
1089
- const { VectorStore } = await import("./dist-NNJKY4T4.js");
1231
+ const { VectorStore } = await import("./dist-NVKTAKZX.js");
1090
1232
  let embeddingProvider = "tfidf";
1091
1233
  let embeddingApiKey;
1092
1234
  let embeddingBaseUrl;
@@ -1142,6 +1284,21 @@ ${nudgeMessage}
1142
1284
  process.exit(1);
1143
1285
  }
1144
1286
  spinner.succeed("Ports available");
1287
+ const NODE_FRAMEWORKS = ["node", "express", "nest", "fastify", "koa", "hapi", "next.js", "nuxt", "sveltekit", "astro", "vite", "cra"];
1288
+ if (NODE_FRAMEWORKS.includes(stack.framework) && !existsSync(join2(cwd, "node_modules"))) {
1289
+ const pm = stack.packageManager ?? "npm";
1290
+ const installCmd = pm === "yarn" ? "yarn" : `${pm} install`;
1291
+ spinner.stop();
1292
+ console.log(chalk6.dim(` Installing dependencies (${installCmd})...`));
1293
+ try {
1294
+ const { execSync } = await import("child_process");
1295
+ execSync(installCmd, { cwd, stdio: "inherit" });
1296
+ console.log(chalk6.green(" Dependencies installed."));
1297
+ } catch {
1298
+ console.log(chalk6.red(` Failed to install dependencies. Run "${installCmd}" manually.`));
1299
+ process.exit(1);
1300
+ }
1301
+ }
1145
1302
  spinner.start(`Starting dev server (${chalk6.dim(devCommand)})...`);
1146
1303
  try {
1147
1304
  await devServer.spawn(devCommand, cwd, devPort);
@@ -1178,7 +1335,7 @@ Tips:`));
1178
1335
  wsServer.start(httpServer);
1179
1336
  }
1180
1337
  proxyServer.setProjectMapApi(projectMapApi);
1181
- const { GraphStore: GS, SearchRouter: SR } = await import("./dist-NNJKY4T4.js");
1338
+ const { GraphStore: GS, SearchRouter: SR } = await import("./dist-NVKTAKZX.js");
1182
1339
  const novaPath = novaDir.getPath(cwd);
1183
1340
  const graphStoreForApi = new GS(novaPath);
1184
1341
  const searchRouterForApi = new SR(graphStoreForApi);
@@ -1200,23 +1357,6 @@ Tips:`));
1200
1357
  } else {
1201
1358
  exec(`google-chrome "${openUrl}" 2>/dev/null || chromium "${openUrl}" 2>/dev/null || xdg-open "${openUrl}"`);
1202
1359
  }
1203
- if (!config.apiKeys.key && config.apiKeys.provider !== "ollama" && config.apiKeys.provider !== "claude-cli") {
1204
- console.log(chalk6.yellow("\nNo API key configured. Running setup...\n"));
1205
- const { runSetup: runSetup2 } = await import("./setup-VJMYSGJI.js");
1206
- await runSetup2(cwd);
1207
- const updatedConfig = await configReader.read(cwd);
1208
- config.apiKeys = updatedConfig.apiKeys;
1209
- }
1210
- const providerFactory = new ProviderFactory();
1211
- let llmClient;
1212
- try {
1213
- llmClient = providerFactory.create(config.apiKeys.provider, config.apiKeys.key);
1214
- } catch (err) {
1215
- console.log(chalk6.yellow("\nAI provider not configured. Nova is running without AI analysis."));
1216
- console.log(chalk6.dim('Run "nova setup" to configure your API key.\n'));
1217
- llmClient = null;
1218
- }
1219
- const brain = llmClient ? new Brain(llmClient, eventBus) : null;
1220
1360
  try {
1221
1361
  const { execSync } = await import("child_process");
1222
1362
  execSync("git rev-parse --git-dir", { cwd, stdio: "ignore" });
@@ -1407,6 +1547,7 @@ ${pendingMessage}
1407
1547
  }
1408
1548
  wsServer.sendEvent(event);
1409
1549
  setTimeout(async () => {
1550
+ if (autoFixer?.isAutofixTask(event.data.taskId)) return;
1410
1551
  const logs = devServer.getLogs();
1411
1552
  const recentLogs = logs.slice(-2e3);
1412
1553
  const hasLogError = /error|Error|failed|Failed|Module not found|SyntaxError|TypeError/i.test(recentLogs) && !/Successfully compiled|Compiled/.test(recentLogs.slice(-500));
@@ -2278,14 +2419,14 @@ function createCli() {
2278
2419
  if (opts.provider && (opts.key || opts.provider === "ollama" || opts.provider === "claude-cli")) {
2279
2420
  const fs2 = await import("fs/promises");
2280
2421
  const path4 = await import("path");
2281
- const TOML = (await import("@iarna/toml")).default;
2422
+ const TOML2 = (await import("@iarna/toml")).default;
2282
2423
  const cwd = process.cwd();
2283
2424
  const novaDir = path4.join(cwd, ".nova");
2284
2425
  await fs2.mkdir(novaDir, { recursive: true });
2285
2426
  const config = { apiKeys: { provider: opts.provider, key: opts.key } };
2286
2427
  await fs2.writeFile(
2287
2428
  path4.join(novaDir, "config.toml"),
2288
- TOML.stringify(config),
2429
+ TOML2.stringify(config),
2289
2430
  "utf-8"
2290
2431
  );
2291
2432
  console.log(`Saved ${opts.provider} config to .nova/config.toml`);
@@ -73,7 +73,7 @@ import {
73
73
  parseManifest,
74
74
  parseMixedBlocks,
75
75
  streamWithEvents
76
- } from "./chunk-7K55GHF5.js";
76
+ } from "./chunk-3FVS3SB3.js";
77
77
  import "./chunk-3RG5ZIWI.js";
78
78
  export {
79
79
  AgentPromptLoader,
package/dist/index.d.ts CHANGED
@@ -53,7 +53,9 @@ declare class ErrorAutoFixer {
53
53
  private readonly MAX_FIX_ATTEMPTS;
54
54
  private lastErrorSignature;
55
55
  private cooldownUntil;
56
+ readonly autofixTaskIds: Set<string>;
56
57
  constructor(projectPath: string, llmClient: LlmClient, gitManager: IGitManager, eventBus: EventBus, wsServer: WebSocketServer, projectMap: ProjectMap, commitQueue?: CommitQueue | undefined);
58
+ isAutofixTask(taskId: string): boolean;
57
59
  /**
58
60
  * Process dev server output. Call this for every stdout/stderr chunk.
59
61
  */
@@ -63,6 +65,9 @@ declare class ErrorAutoFixer {
63
65
  private attemptAutoFix;
64
66
  private fixImageError;
65
67
  private fixCompilationError;
68
+ private fixWithLane2;
69
+ private fixWithLane3;
70
+ private extractFilePath;
66
71
  }
67
72
 
68
73
  declare function createCli(): Command;
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import {
4
4
  createCli,
5
5
  promptAndScaffold,
6
6
  run
7
- } from "./chunk-HVDG2MLB.js";
7
+ } from "./chunk-7ZE7J73C.js";
8
8
  import "./chunk-KE7XWO5N.js";
9
9
  import {
10
10
  ConfigReader,
11
11
  runSetup
12
- } from "./chunk-DQXTUNZA.js";
13
- import "./chunk-7K55GHF5.js";
12
+ } from "./chunk-53K63TXF.js";
13
+ import "./chunk-3FVS3SB3.js";
14
14
  import "./chunk-3RG5ZIWI.js";
15
15
  export {
16
16
  ConfigReader,
@@ -4,7 +4,7 @@ import "./chunk-3RG5ZIWI.js";
4
4
  var package_default = {
5
5
  name: "@novastorm-ai/cli",
6
6
  publishConfig: { access: "public" },
7
- version: "0.1.3",
7
+ version: "0.1.6",
8
8
  license: "SEE LICENSE IN LICENSE.md",
9
9
  type: "module",
10
10
  main: "dist/index.js",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runSetup
3
- } from "./chunk-DQXTUNZA.js";
4
- import "./chunk-7K55GHF5.js";
3
+ } from "./chunk-53K63TXF.js";
4
+ import "./chunk-3FVS3SB3.js";
5
5
  import "./chunk-3RG5ZIWI.js";
6
6
  export {
7
7
  runSetup
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.3",
6
+ "version": "0.1.6",
7
7
  "license": "SEE LICENSE IN LICENSE.md",
8
8
  "type": "module",
9
9
  "main": "dist/index.js",
@@ -35,9 +35,9 @@
35
35
  "devDependencies": {
36
36
  "tsup": "^8.4.0",
37
37
  "typescript": "^5.7.0",
38
- "@novastorm-ai/licensing": "0.0.1",
38
+ "@novastorm-ai/core": "0.0.1",
39
39
  "@novastorm-ai/proxy": "0.0.1",
40
- "@novastorm-ai/core": "0.0.1"
40
+ "@novastorm-ai/licensing": "0.0.1"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsup",