@novastorm-ai/cli 0.1.1 → 0.1.3

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-4EPZMOY6.js";
4
+ } from "../chunk-HVDG2MLB.js";
5
5
  import "../chunk-KE7XWO5N.js";
6
- import "../chunk-QKD6A4EK.js";
7
- import "../chunk-KKTDQOQX.js";
6
+ import "../chunk-DQXTUNZA.js";
7
+ import "../chunk-7K55GHF5.js";
8
8
  import "../chunk-3RG5ZIWI.js";
9
9
 
10
10
  // bin/nova.ts
@@ -331,6 +331,30 @@ var GitManager = class {
331
331
  }
332
332
  }
333
333
  };
334
+ var CommitQueue = class {
335
+ constructor(gitManager) {
336
+ this.gitManager = gitManager;
337
+ }
338
+ queue = Promise.resolve("");
339
+ /**
340
+ * Enqueues a commit operation. The commit will execute after all
341
+ * previously enqueued commits have completed.
342
+ *
343
+ * @param message - commit message
344
+ * @param files - relative file paths to stage (passed to gitManager.commit)
345
+ * @returns the commit hash from gitManager.commit
346
+ */
347
+ enqueue(message, files) {
348
+ this.queue = this.queue.then(
349
+ () => this.gitManager.commit(message, files),
350
+ (err) => {
351
+ console.warn("[Nova] Previous commit failed:", err instanceof Error ? err.message : err);
352
+ return this.gitManager.commit(message, files);
353
+ }
354
+ );
355
+ return this.queue;
356
+ }
357
+ };
334
358
  var DEFAULT_AGENT_PROMPTS = {
335
359
  developer: `You are a code generation tool. You output ONLY code. No explanations. No questions. No descriptions.
336
360
 
@@ -3161,10 +3185,20 @@ var ProjectIndexer = class {
3161
3185
  const dependencies = /* @__PURE__ */ new Map();
3162
3186
  const fileContexts = /* @__PURE__ */ new Map();
3163
3187
  const models = [];
3164
- for (const absPath of scannableFiles) {
3188
+ const BATCH_SIZE2 = 50;
3189
+ const fileContents = /* @__PURE__ */ new Map();
3190
+ for (let i = 0; i < scannableFiles.length; i += BATCH_SIZE2) {
3191
+ const batch = scannableFiles.slice(i, i + BATCH_SIZE2);
3192
+ const results = await Promise.all(batch.map(async (absPath) => {
3193
+ const content = await this.readFileSafe(absPath);
3194
+ return [absPath, content];
3195
+ }));
3196
+ for (const [absPath, content] of results) {
3197
+ if (content) fileContents.set(absPath, content);
3198
+ }
3199
+ }
3200
+ for (const [absPath, content] of fileContents) {
3165
3201
  const rel = this.toPosix(relative4(projectPath, absPath));
3166
- const content = await this.readFileSafe(absPath);
3167
- if (!content) continue;
3168
3202
  const imports = this.extractImports(content);
3169
3203
  const exports = this.extractExports(content);
3170
3204
  const type = this.classifyFile(rel, components, endpoints);
@@ -3187,18 +3221,20 @@ var ProjectIndexer = class {
3187
3221
  });
3188
3222
  this.extractModels(content, rel, models);
3189
3223
  }
3224
+ const TYPE_DEF_REGEX = /export\s+(?:interface|type)\s+\w+[^}]*}/g;
3225
+ const typeDefsCache = /* @__PURE__ */ new Map();
3226
+ for (const [filePath, ctx] of fileContexts) {
3227
+ const matches = ctx.content.match(TYPE_DEF_REGEX);
3228
+ if (matches) typeDefsCache.set(filePath, matches);
3229
+ }
3190
3230
  for (const [filePath, ctx] of fileContexts) {
3191
3231
  const node = dependencies.get(filePath);
3192
3232
  if (!node) continue;
3193
3233
  const importedTypes = [];
3194
3234
  for (const imp of node.imports) {
3195
- const importedCtx = fileContexts.get(imp);
3196
- if (!importedCtx) continue;
3197
- const typeMatches = importedCtx.content.match(
3198
- /export\s+(?:interface|type)\s+\w+[^}]*}/g
3199
- );
3200
- if (typeMatches) {
3201
- importedTypes.push(...typeMatches);
3235
+ const cached = typeDefsCache.get(imp);
3236
+ if (cached) {
3237
+ importedTypes.push(...cached);
3202
3238
  }
3203
3239
  }
3204
3240
  ctx.importedTypes = importedTypes.join("\n");
@@ -4708,21 +4744,32 @@ var CodeValidator = class {
4708
4744
  constructor(projectPath) {
4709
4745
  this.projectPath = projectPath;
4710
4746
  }
4747
+ installedDepsCache = null;
4711
4748
  /**
4712
4749
  * Validate generated files. Returns only errors in the specified files (filters out pre-existing project errors).
4713
4750
  */
4714
- async validateFiles(files) {
4751
+ async validateFiles(files, options) {
4715
4752
  const errors = [];
4716
4753
  const generatedPaths = new Set(files.map((f) => f.path));
4717
- const tscErrors = await this.runTsc(generatedPaths);
4718
- errors.push(...tscErrors);
4719
- for (const file of files) {
4720
- const importErrors = await this.checkImports(file.path, file.content);
4721
- errors.push(...importErrors);
4754
+ if (!options?.skipTsc) {
4755
+ const tscErrors = await this.runTsc(generatedPaths);
4756
+ errors.push(...tscErrors);
4722
4757
  }
4723
- for (const file of files) {
4758
+ if (!options?.skipImportCheck) {
4759
+ await this.loadInstalledDeps();
4760
+ }
4761
+ const fileErrors = await Promise.all(files.map(async (file) => {
4762
+ const result = [];
4763
+ if (!options?.skipImportCheck) {
4764
+ const importErrors = this.checkImportsSync(file.path, file.content);
4765
+ result.push(...importErrors);
4766
+ }
4724
4767
  const relErrors = this.checkRelativeImports(file.path, file.content, generatedPaths);
4725
- errors.push(...relErrors);
4768
+ result.push(...relErrors);
4769
+ return result;
4770
+ }));
4771
+ for (const fileErrs of fileErrors) {
4772
+ errors.push(...fileErrs);
4726
4773
  }
4727
4774
  return errors;
4728
4775
  }
@@ -4775,19 +4822,26 @@ var CodeValidator = class {
4775
4822
  }
4776
4823
  return errors;
4777
4824
  }
4778
- async checkImports(filePath, content) {
4779
- const errors = [];
4780
- let installedDeps = /* @__PURE__ */ new Set();
4825
+ /**
4826
+ * Load and cache installed dependencies from package.json.
4827
+ * Called once per validateFiles invocation instead of once per file.
4828
+ */
4829
+ async loadInstalledDeps() {
4830
+ if (this.installedDepsCache) return;
4781
4831
  try {
4782
4832
  const pkgRaw = await readFile16(join17(this.projectPath, "package.json"), "utf-8");
4783
4833
  const pkg = JSON.parse(pkgRaw);
4784
- installedDeps = /* @__PURE__ */ new Set([
4834
+ this.installedDepsCache = /* @__PURE__ */ new Set([
4785
4835
  ...Object.keys(pkg.dependencies ?? {}),
4786
4836
  ...Object.keys(pkg.devDependencies ?? {})
4787
4837
  ]);
4788
4838
  } catch {
4789
- return [];
4839
+ this.installedDepsCache = /* @__PURE__ */ new Set();
4790
4840
  }
4841
+ }
4842
+ checkImportsSync(filePath, content) {
4843
+ const errors = [];
4844
+ const installedDeps = this.installedDepsCache ?? /* @__PURE__ */ new Set();
4791
4845
  const safePackages = /* @__PURE__ */ new Set([
4792
4846
  "react",
4793
4847
  "react-dom",
@@ -5835,14 +5889,16 @@ function extractDiff(response) {
5835
5889
  return response.trim();
5836
5890
  }
5837
5891
  var Lane2Executor = class {
5838
- constructor(projectPath, llmClient, gitManager, pathGuard) {
5892
+ constructor(projectPath, llmClient, gitManager, pathGuard, commitQueue) {
5839
5893
  this.projectPath = projectPath;
5840
5894
  this.llmClient = llmClient;
5841
5895
  this.gitManager = gitManager;
5842
5896
  this.pathGuard = pathGuard;
5843
5897
  this.diffApplier = new DiffApplier();
5898
+ this.commitQueue = commitQueue ?? new CommitQueue(this.gitManager);
5844
5899
  }
5845
5900
  diffApplier;
5901
+ commitQueue;
5846
5902
  async execute(task, projectMap) {
5847
5903
  try {
5848
5904
  const targetFile = task.files[0];
@@ -5875,7 +5931,7 @@ var Lane2Executor = class {
5875
5931
  const absPath = join19(this.projectPath, targetFile);
5876
5932
  await this.pathGuard?.check(absPath);
5877
5933
  await this.diffApplier.apply(absPath, diff);
5878
- const commitHash = await this.gitManager.commit(
5934
+ const commitHash = await this.commitQueue.enqueue(
5879
5935
  `nova: ${task.description}`,
5880
5936
  [targetFile]
5881
5937
  );
@@ -6145,7 +6201,7 @@ Available packages: ${deps}`);
6145
6201
  return parts.join("\n");
6146
6202
  }
6147
6203
  var Lane3Executor = class {
6148
- constructor(projectPath, llmClient, gitManager, eventBus, maxFixIterations = 3, modelName, agentPromptLoader, pathGuard) {
6204
+ constructor(projectPath, llmClient, gitManager, eventBus, maxFixIterations = 3, modelName, agentPromptLoader, pathGuard, commitQueue) {
6149
6205
  this.projectPath = projectPath;
6150
6206
  this.llmClient = llmClient;
6151
6207
  this.gitManager = gitManager;
@@ -6155,8 +6211,10 @@ var Lane3Executor = class {
6155
6211
  this.agentPromptLoader = agentPromptLoader;
6156
6212
  this.pathGuard = pathGuard;
6157
6213
  this.diffApplier = new DiffApplier();
6214
+ this.commitQueue = commitQueue ?? new CommitQueue(this.gitManager);
6158
6215
  }
6159
6216
  diffApplier;
6217
+ commitQueue;
6160
6218
  async execute(task, projectMap) {
6161
6219
  try {
6162
6220
  console.log(`[Nova] Developer: task "${task.description}"`);
@@ -6260,18 +6318,24 @@ Remember: Output ONLY === FILE === or === DIFF === blocks. No text, no explanati
6260
6318
  this.eventBus.emit({ type: "secrets_required", data: { envVars: missingVars, taskId: task.id } });
6261
6319
  }
6262
6320
  const skipValidation = fileBlocks.length === 1 && fileBlocks[0].content.length < 3e3;
6321
+ const tscSkip = this.shouldSkipTsc(fileBlocks);
6263
6322
  const validator = new CodeValidator(this.projectPath);
6264
6323
  const fixer = new CodeFixer(this.llmClient, this.eventBus);
6265
6324
  let currentBlocks = [...fileBlocks];
6266
6325
  let errors = [];
6267
6326
  if (skipValidation) {
6268
6327
  console.log(`[Nova] Tester: skipping validation (small single-file change)`);
6328
+ } else if (tscSkip.skipTsc) {
6329
+ console.log(`[Nova] Tester: skipping tsc (${tscSkip.reason})`);
6269
6330
  }
6270
6331
  for (let iteration = 1; !skipValidation && iteration <= this.maxFixIterations; iteration++) {
6271
6332
  console.log(`[Nova] Tester: validating (iteration ${iteration}/${this.maxFixIterations})...`);
6272
6333
  this.eventBus?.emit({ type: "status", data: { message: `Validating code (${iteration}/${this.maxFixIterations})...` } });
6273
6334
  try {
6274
- errors = await validator.validateFiles(currentBlocks);
6335
+ errors = await validator.validateFiles(currentBlocks, {
6336
+ skipTsc: tscSkip.skipTsc,
6337
+ skipImportCheck: tscSkip.skipImportCheck
6338
+ });
6275
6339
  } catch (validationCrash) {
6276
6340
  const msg = validationCrash instanceof Error ? validationCrash.message : String(validationCrash);
6277
6341
  console.log(`[Nova] Tester: validation crashed, skipping validation: ${msg}`);
@@ -6301,17 +6365,17 @@ Remember: Output ONLY === FILE === or === DIFF === blocks. No text, no explanati
6301
6365
  language: projectMap.stack.language,
6302
6366
  packageJson: pkgContent
6303
6367
  });
6304
- for (const block of fixedBlocks) {
6368
+ await Promise.all(fixedBlocks.map(async (block) => {
6305
6369
  const absPath = join21(this.projectPath, block.path);
6306
6370
  await this.pathGuard?.check(absPath);
6307
6371
  await mkdir7(dirname3(absPath), { recursive: true });
6308
6372
  await writeFile12(absPath, block.content, "utf-8");
6309
- }
6373
+ }));
6310
6374
  currentBlocks = fixedBlocks;
6311
6375
  }
6312
6376
  const writtenFiles = currentBlocks.map((b) => b.path);
6313
6377
  const safeMsg = `nova: ${task.description.replace(/[\n\r'"\\`$]/g, " ").replace(/\s+/g, " ").trim().slice(0, 72)}`;
6314
- const commitHash = await this.gitManager.commit(
6378
+ const commitHash = await this.commitQueue.enqueue(
6315
6379
  safeMsg,
6316
6380
  writtenFiles
6317
6381
  );
@@ -6329,6 +6393,33 @@ Remember: Output ONLY === FILE === or === DIFF === blocks. No text, no explanati
6329
6393
  };
6330
6394
  }
6331
6395
  }
6396
+ /**
6397
+ * Determine whether tsc and/or import checks can be skipped based on file extensions.
6398
+ */
6399
+ shouldSkipTsc(blocks) {
6400
+ if (blocks.length === 0) {
6401
+ return { skipTsc: false, skipImportCheck: false, reason: "" };
6402
+ }
6403
+ const cssExts = /* @__PURE__ */ new Set([".css", ".scss", ".less", ".sass"]);
6404
+ const nonTsExts = /* @__PURE__ */ new Set([".css", ".scss", ".less", ".sass", ".json", ".md", ".html", ".svg"]);
6405
+ const tsExts = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
6406
+ const getExt = (path2) => {
6407
+ const dot = path2.lastIndexOf(".");
6408
+ return dot !== -1 ? path2.slice(dot) : "";
6409
+ };
6410
+ const exts = blocks.map((b) => getExt(b.path));
6411
+ if (exts.every((ext) => cssExts.has(ext))) {
6412
+ return { skipTsc: true, skipImportCheck: true, reason: "CSS-only changes" };
6413
+ }
6414
+ if (exts.every((ext) => nonTsExts.has(ext))) {
6415
+ return { skipTsc: true, skipImportCheck: true, reason: "no TypeScript/JavaScript files" };
6416
+ }
6417
+ const tsBlocks = blocks.filter((b) => tsExts.has(getExt(b.path)));
6418
+ if (tsBlocks.length === 1 && tsBlocks[0].content.length < 5e3) {
6419
+ return { skipTsc: true, skipImportCheck: false, reason: "single small TS file" };
6420
+ }
6421
+ return { skipTsc: false, skipImportCheck: false, reason: "" };
6422
+ }
6332
6423
  /**
6333
6424
  * Fuzzy diff apply — find removed lines in the file and replace with added lines.
6334
6425
  * Ignores context lines (doesn't require exact line numbers).
@@ -6477,14 +6568,15 @@ ${retryPrompt}`;
6477
6568
  }
6478
6569
  };
6479
6570
  var ExecutorPool = class {
6480
- constructor(lane1, lane2, eventBus, llm, gitManager, projectPath, fastModel, strongModel, agentPromptLoader, pathGuard, lane4) {
6571
+ constructor(lane1, lane2, eventBus, llm, gitManager, projectPath, fastModel, strongModel, agentPromptLoader, pathGuard, lane4, commitQueue) {
6481
6572
  this.lane1 = lane1;
6482
6573
  this.lane2 = lane2;
6483
6574
  this.eventBus = eventBus;
6484
6575
  this.llm = llm;
6485
6576
  this.lane4 = lane4;
6486
- this.lane3Fast = llm && gitManager && projectPath ? new Lane3Executor(projectPath, llm, gitManager, this.eventBus, 3, fastModel, agentPromptLoader, pathGuard) : null;
6487
- this.lane3Strong = llm && gitManager && projectPath ? new Lane3Executor(projectPath, llm, gitManager, this.eventBus, 3, strongModel, agentPromptLoader, pathGuard) : null;
6577
+ const sharedQueue = commitQueue ?? (gitManager ? new CommitQueue(gitManager) : void 0);
6578
+ this.lane3Fast = llm && gitManager && projectPath ? new Lane3Executor(projectPath, llm, gitManager, this.eventBus, 3, fastModel, agentPromptLoader, pathGuard, sharedQueue) : null;
6579
+ this.lane3Strong = llm && gitManager && projectPath ? new Lane3Executor(projectPath, llm, gitManager, this.eventBus, 3, strongModel, agentPromptLoader, pathGuard, sharedQueue) : null;
6488
6580
  }
6489
6581
  lane3Fast;
6490
6582
  lane3Strong;
@@ -7297,6 +7389,7 @@ export {
7297
7389
  LogLevel,
7298
7390
  NovaEventBus,
7299
7391
  GitManager,
7392
+ CommitQueue,
7300
7393
  DEFAULT_AGENT_PROMPTS,
7301
7394
  NovaDir,
7302
7395
  GraphStore,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ConfigError,
3
3
  DEFAULT_CONFIG
4
- } from "./chunk-KKTDQOQX.js";
4
+ } from "./chunk-7K55GHF5.js";
5
5
 
6
6
  // src/setup.ts
7
7
  import * as fs2 from "fs/promises";
@@ -6,10 +6,11 @@ import {
6
6
  import {
7
7
  ConfigReader,
8
8
  runSetup
9
- } from "./chunk-QKD6A4EK.js";
9
+ } from "./chunk-DQXTUNZA.js";
10
10
  import {
11
11
  AgentPromptLoader,
12
12
  Brain,
13
+ CommitQueue,
13
14
  DEFAULT_CONFIG,
14
15
  EnvDetector,
15
16
  ExecutorPool,
@@ -25,7 +26,7 @@ import {
25
26
  ProjectScaffolder,
26
27
  ProviderFactory,
27
28
  SCAFFOLD_PRESETS
28
- } from "./chunk-KKTDQOQX.js";
29
+ } from "./chunk-7K55GHF5.js";
29
30
  import {
30
31
  __require
31
32
  } from "./chunk-3RG5ZIWI.js";
@@ -489,18 +490,19 @@ var IMAGE_PATTERNS = [
489
490
  /next\/image.*not configured/i
490
491
  ];
491
492
  var ErrorAutoFixer = class {
492
- constructor(projectPath, llmClient, gitManager, eventBus, wsServer, projectMap) {
493
+ constructor(projectPath, llmClient, gitManager, eventBus, wsServer, projectMap, commitQueue) {
493
494
  this.projectPath = projectPath;
494
495
  this.llmClient = llmClient;
495
496
  this.gitManager = gitManager;
496
497
  this.eventBus = eventBus;
497
498
  this.wsServer = wsServer;
498
499
  this.projectMap = projectMap;
500
+ this.commitQueue = commitQueue;
499
501
  }
500
502
  isFixing = false;
501
503
  errorBuffer = "";
502
504
  debounceTimer = null;
503
- DEBOUNCE_MS = 2e3;
505
+ DEBOUNCE_MS = 1e3;
504
506
  fixAttempts = 0;
505
507
  MAX_FIX_ATTEMPTS = 3;
506
508
  lastErrorSignature = "";
@@ -605,7 +607,16 @@ Error: ${errorOutput.slice(0, 300)}`;
605
607
  this.projectPath,
606
608
  this.llmClient,
607
609
  this.gitManager,
608
- this.eventBus
610
+ this.eventBus,
611
+ 1,
612
+ // maxFixIterations — single pass for auto-fix
613
+ void 0,
614
+ // modelName
615
+ void 0,
616
+ // agentPromptLoader
617
+ void 0,
618
+ // pathGuard
619
+ this.commitQueue
609
620
  );
610
621
  console.log(chalk3.cyan("[Nova] Auto-fixing image errors..."));
611
622
  this.wsServer.sendEvent({ type: "status", data: { message: "autofix_start" } });
@@ -649,7 +660,16 @@ ${truncatedError}`,
649
660
  this.projectPath,
650
661
  this.llmClient,
651
662
  this.gitManager,
652
- this.eventBus
663
+ this.eventBus,
664
+ 1,
665
+ // maxFixIterations — single pass for auto-fix
666
+ void 0,
667
+ // modelName
668
+ void 0,
669
+ // agentPromptLoader
670
+ void 0,
671
+ // pathGuard
672
+ this.commitQueue
653
673
  );
654
674
  console.log(chalk3.cyan("[Nova] Auto-fixing compilation error..."));
655
675
  this.wsServer.sendEvent({ type: "status", data: { message: "autofix_start" } });
@@ -879,6 +899,24 @@ Available: ${SETTABLE_FIELDS.map((f) => f.path).join(", ")}`);
879
899
 
880
900
  // src/commands/start.ts
881
901
  var PROXY_PORT_OFFSET = 1;
902
+ var MAX_TASK_CONCURRENCY = 3;
903
+ async function runWithConcurrency(tasks, maxConcurrency) {
904
+ const results = new Array(tasks.length);
905
+ const executing = /* @__PURE__ */ new Set();
906
+ for (let i = 0; i < tasks.length; i++) {
907
+ const index = i;
908
+ const p = tasks[index]().then((result) => {
909
+ results[index] = result;
910
+ executing.delete(p);
911
+ });
912
+ executing.add(p);
913
+ if (executing.size >= maxConcurrency) {
914
+ await Promise.race(executing);
915
+ }
916
+ }
917
+ await Promise.all(executing);
918
+ return results;
919
+ }
882
920
  function isPortInUse(port) {
883
921
  return new Promise((resolve4) => {
884
922
  const server = net.createServer();
@@ -964,7 +1002,7 @@ async function startCommand() {
964
1002
  projectHash = createHash2("sha256").update(cwd).digest("hex");
965
1003
  }
966
1004
  const telemetry = new Telemetry();
967
- const cliPkg = await import("./package-XCCIIMWT.js").catch(
1005
+ const cliPkg = await import("./package-BSAVJZ7S.js").catch(
968
1006
  () => ({ default: { version: "0.0.1" } })
969
1007
  );
970
1008
  telemetry.send({
@@ -994,7 +1032,7 @@ ${nudgeMessage}
994
1032
  });
995
1033
  }
996
1034
  spinner.start("Detecting project...");
997
- const { StackDetector } = await import("./dist-EMATXD3M.js");
1035
+ const { StackDetector } = await import("./dist-NNJKY4T4.js");
998
1036
  const stackDetector = new StackDetector();
999
1037
  let stack = await stackDetector.detectStack(cwd);
1000
1038
  let detectedDevCommand = await stackDetector.detectDevCommand(stack, cwd);
@@ -1040,7 +1078,7 @@ ${nudgeMessage}
1040
1078
  throw err;
1041
1079
  }
1042
1080
  spinner.succeed("Project indexed.");
1043
- const { ProjectAnalyzer, RagIndexer, createEmbeddingService } = await import("./dist-EMATXD3M.js");
1081
+ const { ProjectAnalyzer, RagIndexer, createEmbeddingService } = await import("./dist-NNJKY4T4.js");
1044
1082
  const { ProjectMapApi } = await import("./dist-5FLNK6MH.js");
1045
1083
  const projectAnalyzer = new ProjectAnalyzer();
1046
1084
  spinner.start("Analyzing project structure...");
@@ -1048,7 +1086,7 @@ ${nudgeMessage}
1048
1086
  spinner.succeed(`Project analyzed: ${analysis.fileCount} files, ${analysis.methods.length} methods.`);
1049
1087
  let ragIndexer = null;
1050
1088
  try {
1051
- const { VectorStore } = await import("./dist-EMATXD3M.js");
1089
+ const { VectorStore } = await import("./dist-NNJKY4T4.js");
1052
1090
  let embeddingProvider = "tfidf";
1053
1091
  let embeddingApiKey;
1054
1092
  let embeddingBaseUrl;
@@ -1140,7 +1178,7 @@ Tips:`));
1140
1178
  wsServer.start(httpServer);
1141
1179
  }
1142
1180
  proxyServer.setProjectMapApi(projectMapApi);
1143
- const { GraphStore: GS, SearchRouter: SR } = await import("./dist-EMATXD3M.js");
1181
+ const { GraphStore: GS, SearchRouter: SR } = await import("./dist-NNJKY4T4.js");
1144
1182
  const novaPath = novaDir.getPath(cwd);
1145
1183
  const graphStoreForApi = new GS(novaPath);
1146
1184
  const searchRouterForApi = new SR(graphStoreForApi);
@@ -1164,7 +1202,7 @@ Tips:`));
1164
1202
  }
1165
1203
  if (!config.apiKeys.key && config.apiKeys.provider !== "ollama" && config.apiKeys.provider !== "claude-cli") {
1166
1204
  console.log(chalk6.yellow("\nNo API key configured. Running setup...\n"));
1167
- const { runSetup: runSetup2 } = await import("./setup-L5TRND4P.js");
1205
+ const { runSetup: runSetup2 } = await import("./setup-VJMYSGJI.js");
1168
1206
  await runSetup2(cwd);
1169
1207
  const updatedConfig = await configReader.read(cwd);
1170
1208
  config.apiKeys = updatedConfig.apiKeys;
@@ -1195,6 +1233,7 @@ Tips:`));
1195
1233
  } catch {
1196
1234
  }
1197
1235
  let executorPool = null;
1236
+ const commitQueue = new CommitQueue(gitManager);
1198
1237
  if (llmClient) {
1199
1238
  const pathGuard = new PathGuard(cwd);
1200
1239
  if (config.project.frontend) pathGuard.allow(resolve2(cwd, config.project.frontend));
@@ -1206,12 +1245,12 @@ Tips:`));
1206
1245
  }
1207
1246
  const agentPromptLoader = new AgentPromptLoader();
1208
1247
  const lane1 = new Lane1Executor(cwd, pathGuard);
1209
- const lane2 = new Lane2Executor(cwd, llmClient, gitManager, pathGuard);
1210
- executorPool = new ExecutorPool(lane1, lane2, eventBus, llmClient, gitManager, cwd, config.models.fast, config.models.strong, agentPromptLoader, pathGuard);
1248
+ const lane2 = new Lane2Executor(cwd, llmClient, gitManager, pathGuard, commitQueue);
1249
+ executorPool = new ExecutorPool(lane1, lane2, eventBus, llmClient, gitManager, cwd, config.models.fast, config.models.strong, agentPromptLoader, pathGuard, void 0, commitQueue);
1211
1250
  }
1212
1251
  let autoFixer = null;
1213
1252
  if (llmClient) {
1214
- autoFixer = new ErrorAutoFixer(cwd, llmClient, gitManager, eventBus, wsServer, projectMap);
1253
+ autoFixer = new ErrorAutoFixer(cwd, llmClient, gitManager, eventBus, wsServer, projectMap, commitQueue);
1215
1254
  }
1216
1255
  devServer.onOutput((output) => {
1217
1256
  autoFixer?.handleOutput(output);
@@ -1278,9 +1317,7 @@ Tips:`));
1278
1317
  console.log(chalk6.green(`Auto-executing ${tasks.length} task(s)...`));
1279
1318
  wsServer.sendEvent({ type: "status", data: { message: `Auto-executing ${tasks.length} task(s)...` } });
1280
1319
  wsServer.sendEvent({ type: "status", data: { message: "Confirmed! Executing tasks..." } });
1281
- for (const task of tasks) {
1282
- eventBus.emit({ type: "task_created", data: task });
1283
- }
1320
+ executeTasks(tasks);
1284
1321
  } catch (err) {
1285
1322
  const message = err instanceof Error ? err.message : String(err);
1286
1323
  console.error(chalk6.red(`Analysis error: ${message}`));
@@ -1294,10 +1331,9 @@ Tips:`));
1294
1331
  }
1295
1332
  console.log(chalk6.green(`Confirmed ${pendingTasks.length} task(s). Executing...`));
1296
1333
  wsServer.sendEvent({ type: "status", data: { message: "Confirmed! Executing tasks..." } });
1297
- for (const task of pendingTasks) {
1298
- eventBus.emit({ type: "task_created", data: task });
1299
- }
1334
+ const tasksToRun = [...pendingTasks];
1300
1335
  pendingTasks = [];
1336
+ executeTasks(tasksToRun);
1301
1337
  });
1302
1338
  wsServer.onCancel(() => {
1303
1339
  if (pendingTasks.length === 0) {
@@ -1340,16 +1376,28 @@ ${pendingMessage}
1340
1376
  wsServer.sendEvent({ type: "status", data: { message: `Analysis error: ${message}` } });
1341
1377
  }
1342
1378
  });
1343
- eventBus.on("task_created", async (event) => {
1344
- taskMap.set(event.data.id, event.data);
1345
- logger.logTaskStarted(event.data);
1346
- wsServer.sendEvent(event);
1347
- if (executorPool) {
1379
+ function executeTasks(tasks) {
1380
+ for (const task of tasks) {
1381
+ eventBus.emit({ type: "task_created", data: task });
1382
+ }
1383
+ if (!executorPool) return;
1384
+ const pool = executorPool;
1385
+ const taskFns = tasks.map((task) => async () => {
1348
1386
  try {
1349
- await executorPool.execute(event.data, projectMap);
1387
+ return await pool.execute(task, projectMap);
1350
1388
  } catch {
1389
+ return { success: false, taskId: task.id, error: "Execution failed" };
1351
1390
  }
1352
- }
1391
+ });
1392
+ runWithConcurrency(taskFns, MAX_TASK_CONCURRENCY).catch((err) => {
1393
+ const message = err instanceof Error ? err.message : String(err);
1394
+ console.error(chalk6.red(`Task batch error: ${message}`));
1395
+ });
1396
+ }
1397
+ eventBus.on("task_created", (event) => {
1398
+ taskMap.set(event.data.id, event.data);
1399
+ logger.logTaskStarted(event.data);
1400
+ wsServer.sendEvent(event);
1353
1401
  });
1354
1402
  eventBus.on("task_completed", (event) => {
1355
1403
  const task = taskMap.get(event.data.taskId);
@@ -1388,7 +1436,7 @@ ${pendingMessage}
1388
1436
  }
1389
1437
  } catch {
1390
1438
  }
1391
- }, 3e3);
1439
+ }, 1500);
1392
1440
  });
1393
1441
  eventBus.on("task_failed", (event) => {
1394
1442
  const task = taskMap.get(event.data.taskId);
@@ -9,6 +9,7 @@ import {
9
9
  CodeChunker,
10
10
  CodeFixer,
11
11
  CodeValidator,
12
+ CommitQueue,
12
13
  ComponentExtractor,
13
14
  ConfigError,
14
15
  ContextDistiller,
@@ -72,7 +73,7 @@ import {
72
73
  parseManifest,
73
74
  parseMixedBlocks,
74
75
  streamWithEvents
75
- } from "./chunk-KKTDQOQX.js";
76
+ } from "./chunk-7K55GHF5.js";
76
77
  import "./chunk-3RG5ZIWI.js";
77
78
  export {
78
79
  AgentPromptLoader,
@@ -85,6 +86,7 @@ export {
85
86
  CodeChunker,
86
87
  CodeFixer,
87
88
  CodeValidator,
89
+ CommitQueue,
88
90
  ComponentExtractor,
89
91
  ConfigError,
90
92
  ContextDistiller,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { IConfigReader, NovaConfig, Observation, TaskItem, LlmClient, IGitManager, EventBus, ProjectMap } from '@novastorm-ai/core';
2
+ import { IConfigReader, NovaConfig, Observation, TaskItem, LlmClient, IGitManager, EventBus, ProjectMap, CommitQueue } from '@novastorm-ai/core';
3
3
  import { WebSocketServer } from '@novastorm-ai/proxy';
4
4
 
5
5
  declare class ConfigReader implements IConfigReader {
@@ -44,6 +44,7 @@ declare class ErrorAutoFixer {
44
44
  private readonly eventBus;
45
45
  private readonly wsServer;
46
46
  private readonly projectMap;
47
+ private readonly commitQueue?;
47
48
  private isFixing;
48
49
  private errorBuffer;
49
50
  private debounceTimer;
@@ -52,7 +53,7 @@ declare class ErrorAutoFixer {
52
53
  private readonly MAX_FIX_ATTEMPTS;
53
54
  private lastErrorSignature;
54
55
  private cooldownUntil;
55
- constructor(projectPath: string, llmClient: LlmClient, gitManager: IGitManager, eventBus: EventBus, wsServer: WebSocketServer, projectMap: ProjectMap);
56
+ constructor(projectPath: string, llmClient: LlmClient, gitManager: IGitManager, eventBus: EventBus, wsServer: WebSocketServer, projectMap: ProjectMap, commitQueue?: CommitQueue | undefined);
56
57
  /**
57
58
  * Process dev server output. Call this for every stdout/stderr chunk.
58
59
  */
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import {
4
4
  createCli,
5
5
  promptAndScaffold,
6
6
  run
7
- } from "./chunk-4EPZMOY6.js";
7
+ } from "./chunk-HVDG2MLB.js";
8
8
  import "./chunk-KE7XWO5N.js";
9
9
  import {
10
10
  ConfigReader,
11
11
  runSetup
12
- } from "./chunk-QKD6A4EK.js";
13
- import "./chunk-KKTDQOQX.js";
12
+ } from "./chunk-DQXTUNZA.js";
13
+ import "./chunk-7K55GHF5.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.1",
7
+ version: "0.1.3",
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-QKD6A4EK.js";
4
- import "./chunk-KKTDQOQX.js";
3
+ } from "./chunk-DQXTUNZA.js";
4
+ import "./chunk-7K55GHF5.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.1",
6
+ "version": "0.1.3",
7
7
  "license": "SEE LICENSE IN LICENSE.md",
8
8
  "type": "module",
9
9
  "main": "dist/index.js",