ai-spec-dev 0.38.0 → 0.42.0

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.
Files changed (89) hide show
  1. package/.ai-spec-workspace.json +17 -0
  2. package/.ai-spec.json +7 -0
  3. package/cli/commands/create.ts +9 -1176
  4. package/cli/commands/dashboard.ts +1 -1
  5. package/cli/pipeline/helpers.ts +34 -0
  6. package/cli/pipeline/multi-repo.ts +483 -0
  7. package/cli/pipeline/single-repo.ts +764 -0
  8. package/cli/utils.ts +2 -0
  9. package/core/cli-ui.ts +136 -0
  10. package/core/code-generator.ts +56 -343
  11. package/core/codegen/helpers.ts +219 -0
  12. package/core/codegen/topo-sort.ts +98 -0
  13. package/core/constitution-consolidator.ts +2 -2
  14. package/core/dsl-coverage-checker.ts +298 -0
  15. package/core/dsl-extractor.ts +19 -46
  16. package/core/dsl-feedback.ts +1 -1
  17. package/core/dsl-validator.ts +74 -0
  18. package/core/error-feedback.ts +99 -13
  19. package/core/frontend-context-loader.ts +27 -5
  20. package/core/knowledge-memory.ts +52 -0
  21. package/core/mock/fixtures.ts +89 -0
  22. package/core/mock/proxy.ts +380 -0
  23. package/core/mock-server-generator.ts +12 -460
  24. package/core/provider-utils.ts +8 -7
  25. package/core/requirement-decomposer.ts +4 -28
  26. package/core/reviewer.ts +1 -1
  27. package/core/safe-json.ts +76 -0
  28. package/core/spec-updater.ts +5 -21
  29. package/core/token-budget.ts +124 -0
  30. package/core/vcr.ts +20 -1
  31. package/demo-backend/.ai-spec-constitution.md +65 -0
  32. package/demo-backend/package.json +21 -0
  33. package/demo-backend/prisma/schema.prisma +22 -0
  34. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +186 -0
  35. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +211 -0
  36. package/demo-backend/src/controllers/bookmark.controller.test.ts +255 -0
  37. package/demo-backend/src/controllers/bookmark.controller.ts +187 -0
  38. package/demo-backend/src/index.ts +17 -0
  39. package/demo-backend/src/routes/bookmark.routes.test.ts +264 -0
  40. package/demo-backend/src/routes/bookmark.routes.ts +11 -0
  41. package/demo-backend/src/routes/index.ts +8 -0
  42. package/demo-backend/src/services/bookmark.service.test.ts +433 -0
  43. package/demo-backend/src/services/bookmark.service.ts +261 -0
  44. package/demo-backend/tsconfig.json +12 -0
  45. package/demo-frontend/.ai-spec-constitution.md +95 -0
  46. package/demo-frontend/package.json +23 -0
  47. package/demo-frontend/src/App.tsx +12 -0
  48. package/demo-frontend/src/main.tsx +9 -0
  49. package/demo-frontend/tsconfig.json +13 -0
  50. package/dist/cli/index.js +4351 -3666
  51. package/dist/cli/index.js.map +1 -1
  52. package/dist/cli/index.mjs +3997 -3312
  53. package/dist/cli/index.mjs.map +1 -1
  54. package/dist/index.d.mts +18 -16
  55. package/dist/index.d.ts +18 -16
  56. package/dist/index.js +388 -188
  57. package/dist/index.js.map +1 -1
  58. package/dist/index.mjs +386 -186
  59. package/dist/index.mjs.map +1 -1
  60. package/package.json +2 -2
  61. package/tests/auto-consolidation.test.ts +109 -0
  62. package/tests/combined-generator.test.ts +81 -0
  63. package/tests/constitution-consolidator.test.ts +161 -0
  64. package/tests/constitution-generator.test.ts +94 -0
  65. package/tests/contract-bridge.test.ts +201 -0
  66. package/tests/design-dialogue.test.ts +108 -0
  67. package/tests/dsl-coverage-checker.test.ts +230 -0
  68. package/tests/dsl-feedback.test.ts +45 -0
  69. package/tests/dsl-validator-xref.test.ts +99 -0
  70. package/tests/error-feedback-repair.test.ts +319 -0
  71. package/tests/error-feedback-validation.test.ts +91 -0
  72. package/tests/frontend-context-loader.test.ts +609 -0
  73. package/tests/global-constitution.test.ts +110 -0
  74. package/tests/key-store.test.ts +73 -0
  75. package/tests/knowledge-memory.test.ts +327 -0
  76. package/tests/project-index.test.ts +206 -0
  77. package/tests/prompt-hasher.test.ts +19 -0
  78. package/tests/requirement-decomposer.test.ts +171 -0
  79. package/tests/reviewer.test.ts +4 -1
  80. package/tests/run-logger.test.ts +289 -0
  81. package/tests/run-snapshot.test.ts +113 -0
  82. package/tests/safe-json.test.ts +63 -0
  83. package/tests/spec-updater.test.ts +161 -0
  84. package/tests/test-generator.test.ts +146 -0
  85. package/tests/token-budget.test.ts +124 -0
  86. package/tests/vcr-hash.test.ts +101 -0
  87. package/tests/workspace-loader.test.ts +277 -0
  88. package/RELEASE_LOG.md +0 -2731
  89. package/purpose.md +0 -1294
package/dist/index.mjs CHANGED
@@ -116,9 +116,77 @@ CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uF
116
116
  3. \u5BF9\u4E8E\u6BCF\u6761\u76F4\u63A5\u76F8\u5173\u7684\u6559\u8BAD\uFF0C\u5728 \xA78 \u5B9E\u65BD\u8981\u70B9\u672B\u5C3E\u8FFD\u52A0\u4E00\u884C\uFF1A\u300C\u26A0 \u57FA\u4E8E\u5386\u53F2\u6559\u8BAD\uFF1A[\u7B80\u8FF0\u672C\u6B21 spec \u5982\u4F55\u89C4\u907F\u8BE5\u95EE\u9898]\u300D
117
117
  4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
118
118
 
119
- // core/provider-utils.ts
119
+ // core/cli-ui.ts
120
120
  import chalk from "chalk";
121
- var sleep = (ms2) => new Promise((r) => setTimeout(r, ms2));
121
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
122
+ function startSpinner(text) {
123
+ const isTTY = process.stderr.isTTY;
124
+ let frame = 0;
125
+ let currentText = text;
126
+ let stopped = false;
127
+ function render() {
128
+ if (stopped) return;
129
+ const symbol = chalk.cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
130
+ if (isTTY) {
131
+ process.stderr.write(`\r ${symbol} ${currentText}${" ".repeat(10)}`);
132
+ }
133
+ frame++;
134
+ }
135
+ if (!isTTY) {
136
+ process.stderr.write(` \u2026 ${currentText}
137
+ `);
138
+ }
139
+ const timer = setInterval(render, 80);
140
+ render();
141
+ return {
142
+ update(newText) {
143
+ currentText = newText;
144
+ },
145
+ stop(finalText) {
146
+ if (stopped) return;
147
+ stopped = true;
148
+ clearInterval(timer);
149
+ if (isTTY) {
150
+ process.stderr.write(`\r${" ".repeat(currentText.length + 20)}\r`);
151
+ }
152
+ if (finalText) {
153
+ process.stderr.write(` ${finalText}
154
+ `);
155
+ }
156
+ },
157
+ succeed(successText) {
158
+ this.stop(chalk.green(`\u2714 ${successText}`));
159
+ },
160
+ fail(failText) {
161
+ this.stop(chalk.red(`\u2718 ${failText}`));
162
+ }
163
+ };
164
+ }
165
+ async function retryCountdown(opts) {
166
+ const { attempt, maxAttempts, waitMs, errorMessage, label } = opts;
167
+ const isTTY = process.stderr.isTTY;
168
+ const shortErr = errorMessage.length > 120 ? errorMessage.slice(0, 117) + "..." : errorMessage;
169
+ process.stderr.write("\n");
170
+ process.stderr.write(chalk.yellow(` \u250C\u2500 Retry ${attempt}/${maxAttempts} `) + chalk.gray(`[${label}]`) + chalk.yellow(` ${"\u2500".repeat(Math.max(1, 40 - label.length))}
171
+ `));
172
+ process.stderr.write(chalk.yellow(` \u2502 `) + chalk.white(shortErr) + "\n");
173
+ process.stderr.write(chalk.yellow(` \u2502 `) + chalk.gray(`Waiting before retry...`) + "\n");
174
+ const totalSeconds = Math.ceil(waitMs / 1e3);
175
+ for (let s = totalSeconds; s > 0; s--) {
176
+ const bar = chalk.green("\u2588".repeat(totalSeconds - s)) + chalk.gray("\u2591".repeat(s));
177
+ const line = chalk.yellow(` \u2502 `) + `${bar} ${chalk.bold.white(`${s}s`)}`;
178
+ if (isTTY) {
179
+ process.stderr.write(`\r${line}${" ".repeat(10)}`);
180
+ }
181
+ await new Promise((r) => setTimeout(r, 1e3));
182
+ }
183
+ if (isTTY) {
184
+ process.stderr.write(`\r${" ".repeat(70)}\r`);
185
+ }
186
+ process.stderr.write(chalk.yellow(` \u2514\u2500 `) + chalk.cyan(`Retrying now...`) + "\n\n");
187
+ }
188
+
189
+ // core/provider-utils.ts
122
190
  var ProviderError = class extends Error {
123
191
  constructor(message, kind, originalError) {
124
192
  super(message);
@@ -202,11 +270,14 @@ async function withReliability(fn, opts) {
202
270
  throw classifyError(err, label);
203
271
  }
204
272
  const waitMs = attempt === 0 ? 2e3 : 6e3;
205
- console.warn(
206
- chalk.yellow(` \u26A0 ${label} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${waitMs / 1e3}s`) + chalk.gray(` \u2014 ${err.message}`)
207
- );
208
273
  onRetry?.(attempt + 1, err);
209
- await sleep(waitMs);
274
+ await retryCountdown({
275
+ attempt: attempt + 1,
276
+ maxAttempts: retries + 1,
277
+ waitMs,
278
+ errorMessage: err.message ?? String(err),
279
+ label
280
+ });
210
281
  }
211
282
  }
212
283
  throw new Error("unreachable");
@@ -4107,8 +4178,8 @@ ${currentSpec}`,
4107
4178
  };
4108
4179
 
4109
4180
  // core/code-generator.ts
4110
- import chalk8 from "chalk";
4111
- import { execSync, spawnSync } from "child_process";
4181
+ import chalk10 from "chalk";
4182
+ import { execSync as execSync2, spawnSync } from "child_process";
4112
4183
  import * as path6 from "path";
4113
4184
  import * as fs10 from "fs-extra";
4114
4185
 
@@ -4685,7 +4756,7 @@ async function updateTaskStatus(specFilePath, taskId, status) {
4685
4756
  }
4686
4757
 
4687
4758
  // core/dsl-extractor.ts
4688
- import chalk6 from "chalk";
4759
+ import chalk7 from "chalk";
4689
4760
  import * as fs6 from "fs-extra";
4690
4761
  import * as path4 from "path";
4691
4762
  import { select as select2 } from "@inquirer/prompts";
@@ -4774,6 +4845,7 @@ function validateDsl(raw) {
4774
4845
  }
4775
4846
  }
4776
4847
  }
4848
+ crossReferenceChecks(obj, errors);
4777
4849
  if (errors.length > 0) {
4778
4850
  return { valid: false, errors };
4779
4851
  }
@@ -5005,6 +5077,64 @@ function validateComponent(raw, path10, errors) {
5005
5077
  }
5006
5078
  }
5007
5079
  }
5080
+ function crossReferenceChecks(obj, errors) {
5081
+ const models = Array.isArray(obj["models"]) ? obj["models"] : [];
5082
+ const endpoints = Array.isArray(obj["endpoints"]) ? obj["endpoints"] : [];
5083
+ const components = Array.isArray(obj["components"]) ? obj["components"] : [];
5084
+ const seenRoutes = /* @__PURE__ */ new Map();
5085
+ for (let i = 0; i < endpoints.length; i++) {
5086
+ const ep = endpoints[i];
5087
+ if (typeof ep?.["method"] === "string" && typeof ep?.["path"] === "string") {
5088
+ const route = `${ep["method"].toUpperCase()} ${ep["path"]}`;
5089
+ if (seenRoutes.has(route)) {
5090
+ errors.push({
5091
+ path: `endpoints[${i}]`,
5092
+ message: `Duplicate route "${route}" \u2014 also defined at endpoints[${seenRoutes.get(route)}]`
5093
+ });
5094
+ } else {
5095
+ seenRoutes.set(route, i);
5096
+ }
5097
+ }
5098
+ }
5099
+ const modelNames = new Set(
5100
+ models.filter((m) => typeof m?.["name"] === "string").map((m) => m["name"])
5101
+ );
5102
+ for (let i = 0; i < models.length; i++) {
5103
+ const m = models[i];
5104
+ if (!Array.isArray(m?.["relations"])) continue;
5105
+ for (const rel of m["relations"]) {
5106
+ if (typeof rel !== "string") continue;
5107
+ const refMatch = rel.match(/(?:hasMany|hasOne|belongsTo|manyToMany)\s+(\w+)/i);
5108
+ if (refMatch) {
5109
+ const refName = refMatch[1];
5110
+ if (!modelNames.has(refName)) {
5111
+ errors.push({
5112
+ path: `models[${i}].relations`,
5113
+ message: `Relation references model "${refName}" which is not defined in models[]`
5114
+ });
5115
+ }
5116
+ }
5117
+ }
5118
+ }
5119
+ const endpointIds = new Set(
5120
+ endpoints.filter((e) => typeof e?.["id"] === "string").map((e) => e["id"])
5121
+ );
5122
+ if (endpointIds.size > 0) {
5123
+ for (let i = 0; i < components.length; i++) {
5124
+ const c = components[i];
5125
+ if (!Array.isArray(c?.["apiCalls"])) continue;
5126
+ for (const call of c["apiCalls"]) {
5127
+ if (typeof call !== "string") continue;
5128
+ if (!endpointIds.has(call)) {
5129
+ errors.push({
5130
+ path: `components[${i}].apiCalls`,
5131
+ message: `References endpoint "${call}" which is not defined in endpoints[]`
5132
+ });
5133
+ }
5134
+ }
5135
+ }
5136
+ }
5137
+ }
5008
5138
  function requireNonEmptyString(v2, path10, errors) {
5009
5139
  if (typeof v2 !== "string" || v2.trim().length === 0) {
5010
5140
  errors.push({
@@ -5019,6 +5149,26 @@ function typeLabel(v2) {
5019
5149
  return typeof v2;
5020
5150
  }
5021
5151
 
5152
+ // core/token-budget.ts
5153
+ import chalk6 from "chalk";
5154
+ var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
5155
+ function estimateTokens(text) {
5156
+ if (!text) return 0;
5157
+ const cjkCount = (text.match(CJK_RANGE) ?? []).length;
5158
+ const nonCjkLength = text.length - cjkCount;
5159
+ return Math.ceil(cjkCount + nonCjkLength / 4);
5160
+ }
5161
+ var DEFAULT_TOKEN_BUDGETS = {
5162
+ gemini: 9e5,
5163
+ claude: 18e4,
5164
+ openai: 12e4,
5165
+ deepseek: 6e4,
5166
+ default: 1e5
5167
+ };
5168
+ function getDefaultBudget(providerName) {
5169
+ return DEFAULT_TOKEN_BUDGETS[providerName] ?? DEFAULT_TOKEN_BUDGETS.default;
5170
+ }
5171
+
5022
5172
  // core/dsl-extractor.ts
5023
5173
  function dslFilePath(specFilePath) {
5024
5174
  const dir = path4.dirname(specFilePath);
@@ -5177,7 +5327,9 @@ var ROUTING_LIBS = [
5177
5327
  ["@tanstack/react-router", "tanstack-router"],
5178
5328
  ["react-navigation", "react-navigation"],
5179
5329
  ["expo-router", "expo-router"],
5180
- ["vue-router", "vue-router"]
5330
+ ["vue-router", "vue-router"],
5331
+ ["@solidjs/router", "solid-router"],
5332
+ ["@builder.io/qwik-city", "qwik-city"]
5181
5333
  ];
5182
5334
  async function loadFrontendContext(projectRoot) {
5183
5335
  const ctx = {
@@ -5209,6 +5361,18 @@ async function loadFrontendContext(projectRoot) {
5209
5361
  const has = (name) => depKeys.includes(name);
5210
5362
  if (has("react-native") || has("expo")) {
5211
5363
  ctx.framework = "react-native";
5364
+ } else if (has("@sveltejs/kit")) {
5365
+ ctx.framework = "sveltekit";
5366
+ } else if (has("svelte")) {
5367
+ ctx.framework = "svelte";
5368
+ } else if (has("@builder.io/qwik")) {
5369
+ ctx.framework = "qwik";
5370
+ } else if (has("@remix-run/react") || has("@remix-run/node")) {
5371
+ ctx.framework = "remix";
5372
+ } else if (has("astro")) {
5373
+ ctx.framework = "astro";
5374
+ } else if (has("solid-js")) {
5375
+ ctx.framework = "solid";
5212
5376
  } else if (has("next")) {
5213
5377
  ctx.framework = "next";
5214
5378
  } else if (has("react")) {
@@ -5235,6 +5399,12 @@ async function loadFrontendContext(projectRoot) {
5235
5399
  if (ctx.framework === "next") {
5236
5400
  const hasAppDir = await fs7.pathExists(path5.join(projectRoot, "app"));
5237
5401
  ctx.routingPattern = hasAppDir ? "next-app-router" : "next-pages-router";
5402
+ } else if (ctx.framework === "sveltekit") {
5403
+ ctx.routingPattern = "sveltekit-file-router";
5404
+ } else if (ctx.framework === "remix") {
5405
+ ctx.routingPattern = "remix-file-router";
5406
+ } else if (ctx.framework === "astro") {
5407
+ ctx.routingPattern = "astro-file-router";
5238
5408
  } else {
5239
5409
  for (const [lib, label] of ROUTING_LIBS) {
5240
5410
  if (has(lib)) {
@@ -5620,13 +5790,14 @@ function getActiveSnapshot() {
5620
5790
 
5621
5791
  // core/run-logger.ts
5622
5792
  import * as fs9 from "fs-extra";
5623
- import chalk7 from "chalk";
5793
+ import chalk8 from "chalk";
5624
5794
  var _activeLogger = null;
5625
5795
  function getActiveLogger() {
5626
5796
  return _activeLogger;
5627
5797
  }
5628
5798
 
5629
- // core/code-generator.ts
5799
+ // core/codegen/helpers.ts
5800
+ import { execSync } from "child_process";
5630
5801
  function buildSharedConfigSection(context) {
5631
5802
  if (!context?.sharedConfigFiles || context.sharedConfigFiles.length === 0) return "";
5632
5803
  const lines = [
@@ -5758,7 +5929,7 @@ function buildGeneratedFilesSection(cache) {
5758
5929
  }
5759
5930
  function isRtkAvailable() {
5760
5931
  try {
5761
- execSync("rtk --version", { stdio: "ignore" });
5932
+ execSync("rtk --version", { stdio: "ignore", timeout: 1e4 });
5762
5933
  return true;
5763
5934
  } catch {
5764
5935
  return false;
@@ -5782,6 +5953,73 @@ function parseJsonArray(text) {
5782
5953
  }
5783
5954
  return [];
5784
5955
  }
5956
+
5957
+ // core/codegen/topo-sort.ts
5958
+ import chalk9 from "chalk";
5959
+ function topoSortLayerTasks(tasks) {
5960
+ if (tasks.length <= 1) return [tasks];
5961
+ const idSet = new Set(tasks.map((t) => t.id));
5962
+ const taskById = new Map(tasks.map((t) => [t.id, t]));
5963
+ const inDegree = /* @__PURE__ */ new Map();
5964
+ const dependents = /* @__PURE__ */ new Map();
5965
+ for (const task of tasks) {
5966
+ inDegree.set(task.id, 0);
5967
+ dependents.set(task.id, []);
5968
+ }
5969
+ for (const task of tasks) {
5970
+ const intraDeps = task.dependencies.filter((dep) => idSet.has(dep));
5971
+ inDegree.set(task.id, intraDeps.length);
5972
+ for (const dep of intraDeps) {
5973
+ dependents.get(dep).push(task.id);
5974
+ }
5975
+ }
5976
+ const batches = [];
5977
+ const remaining = new Set(tasks.map((t) => t.id));
5978
+ while (remaining.size > 0) {
5979
+ const batch = [...remaining].filter((id) => inDegree.get(id) === 0).map((id) => taskById.get(id));
5980
+ if (batch.length === 0) {
5981
+ batches.push([...remaining].map((id) => taskById.get(id)));
5982
+ break;
5983
+ }
5984
+ batches.push(batch);
5985
+ for (const task of batch) {
5986
+ remaining.delete(task.id);
5987
+ for (const dependent of dependents.get(task.id)) {
5988
+ inDegree.set(dependent, inDegree.get(dependent) - 1);
5989
+ }
5990
+ }
5991
+ }
5992
+ return batches;
5993
+ }
5994
+ var LAYER_ICONS = {
5995
+ data: "\u{1F4BE}",
5996
+ infra: "\u2699\uFE0F ",
5997
+ service: "\u{1F527}",
5998
+ api: "\u{1F310}",
5999
+ view: "\u{1F5A5}\uFE0F ",
6000
+ route: "\u{1F5FA}\uFE0F ",
6001
+ test: "\u{1F9EA}"
6002
+ };
6003
+ function printTaskProgress(completed, total, task, mode) {
6004
+ const pct = total > 0 ? Math.round(completed / total * 100) : 0;
6005
+ const barWidth = 20;
6006
+ const filled = Math.round(pct / 100 * barWidth);
6007
+ const bar = chalk9.green("\u2588".repeat(filled)) + chalk9.gray("\u2591".repeat(barWidth - filled));
6008
+ const icon = LAYER_ICONS[task.layer] ?? " ";
6009
+ if (mode === "skip") {
6010
+ console.log(
6011
+ chalk9.gray(`
6012
+ [${bar}] ${pct}% \u2713 ${task.id} ${icon} ${task.title} \u2014 already done`)
6013
+ );
6014
+ } else {
6015
+ console.log(
6016
+ chalk9.bold(`
6017
+ [${bar}] ${pct}% \u2192 ${task.id} ${icon} ${task.title}`)
6018
+ );
6019
+ }
6020
+ }
6021
+
6022
+ // core/code-generator.ts
5785
6023
  var CodeGenerator = class {
5786
6024
  constructor(provider, mode = "claude-code") {
5787
6025
  this.provider = provider;
@@ -5792,13 +6030,13 @@ var CodeGenerator = class {
5792
6030
  let effectiveMode = this.mode;
5793
6031
  if (effectiveMode === "claude-code" && this.provider.providerName !== "claude") {
5794
6032
  console.log(
5795
- chalk8.yellow(
6033
+ chalk10.yellow(
5796
6034
  `
5797
6035
  \u26A0 codegen \u6A21\u5F0F "claude-code" \u9700\u8981 Claude\uFF0C\u4F46\u5F53\u524D provider \u662F "${this.provider.providerName}"\u3002`
5798
6036
  )
5799
6037
  );
5800
- console.log(chalk8.gray(` \u81EA\u52A8\u5207\u6362\u5230 "api" \u6A21\u5F0F\uFF08\u4F7F\u7528 ${this.provider.providerName}/${this.provider.modelName} \u751F\u6210\u4EE3\u7801\uFF09\u3002`));
5801
- console.log(chalk8.gray(` \u63D0\u793A\uFF1A\u8FD0\u884C \`ai-spec config --codegen api\` \u53EF\u56FA\u5316\u6B64\u8BBE\u7F6E\u3002
6038
+ console.log(chalk10.gray(` \u81EA\u52A8\u5207\u6362\u5230 "api" \u6A21\u5F0F\uFF08\u4F7F\u7528 ${this.provider.providerName}/${this.provider.modelName} \u751F\u6210\u4EE3\u7801\uFF09\u3002`));
6039
+ console.log(chalk10.gray(` \u63D0\u793A\uFF1A\u8FD0\u884C \`ai-spec config --codegen api\` \u53EF\u56FA\u5316\u6B64\u8BBE\u7F6E\u3002
5802
6040
  `));
5803
6041
  effectiveMode = "api";
5804
6042
  }
@@ -5816,23 +6054,23 @@ var CodeGenerator = class {
5816
6054
  // ── Mode: claude-code ──────────────────────────────────────────────────────
5817
6055
  isClaudeCLIAvailable() {
5818
6056
  try {
5819
- execSync("claude --version", { stdio: "ignore" });
6057
+ execSync2("claude --version", { stdio: "ignore", timeout: 1e4 });
5820
6058
  return true;
5821
6059
  } catch {
5822
6060
  return false;
5823
6061
  }
5824
6062
  }
5825
6063
  async runClaudeCode(specFilePath, workingDir, options = {}) {
5826
- console.log(chalk8.blue("\n\u2500\u2500\u2500 Code Generation: Claude Code CLI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6064
+ console.log(chalk10.blue("\n\u2500\u2500\u2500 Code Generation: Claude Code CLI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5827
6065
  if (!this.isClaudeCLIAvailable()) {
5828
- console.log(chalk8.yellow(" \u26A0\uFE0F Claude Code CLI not found. Falling back to plan mode."));
5829
- console.log(chalk8.gray(" Install: npm install -g @anthropic-ai/claude-code"));
6066
+ console.log(chalk10.yellow(" \u26A0\uFE0F Claude Code CLI not found. Falling back to plan mode."));
6067
+ console.log(chalk10.gray(" Install: npm install -g @anthropic-ai/claude-code"));
5830
6068
  return this.runPlanMode(specFilePath);
5831
6069
  }
5832
6070
  const rtkAvailable = isRtkAvailable();
5833
6071
  const claudeCmd = rtkAvailable ? "rtk claude" : "claude";
5834
6072
  if (rtkAvailable) {
5835
- console.log(chalk8.green(" \u2713 RTK detected \u2014 using rtk claude for token savings"));
6073
+ console.log(chalk10.green(" \u2713 RTK detected \u2014 using rtk claude for token savings"));
5836
6074
  }
5837
6075
  const tasks = await loadTasksForSpec(specFilePath);
5838
6076
  if (options.auto && tasks && tasks.length > 0) {
@@ -5848,28 +6086,28 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
5848
6086
  const promptFile = path6.join(workingDir, ".claude-prompt.txt");
5849
6087
  await fs10.writeFile(promptFile, promptContent, "utf-8");
5850
6088
  if (options.auto) {
5851
- console.log(chalk8.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
5852
- console.log(chalk8.gray(` Spec: ${specFilePath}`));
6089
+ console.log(chalk10.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
6090
+ console.log(chalk10.gray(` Spec: ${specFilePath}`));
5853
6091
  try {
5854
6092
  spawnSync(claudeCmd, ["-p", promptContent], {
5855
6093
  cwd: workingDir,
5856
6094
  stdio: "inherit",
5857
6095
  shell: false
5858
6096
  });
5859
- console.log(chalk8.green("\n \u2714 Claude Code completed."));
6097
+ console.log(chalk10.green("\n \u2714 Claude Code completed."));
5860
6098
  } catch {
5861
- console.log(chalk8.yellow("\n Claude Code exited. Check output above."));
6099
+ console.log(chalk10.yellow("\n Claude Code exited. Check output above."));
5862
6100
  }
5863
6101
  } else {
5864
- console.log(chalk8.cyan(` \u{1F680} Launching ${claudeCmd} in: ${workingDir}`));
5865
- console.log(chalk8.gray(` Spec: ${specFilePath}`));
5866
- if (tasks) console.log(chalk8.gray(` Tasks: ${tasks.length} tasks loaded into .claude-prompt.txt`));
5867
- console.log(chalk8.gray(" Prompt pre-loaded in .claude-prompt.txt\n"));
6102
+ console.log(chalk10.cyan(` \u{1F680} Launching ${claudeCmd} in: ${workingDir}`));
6103
+ console.log(chalk10.gray(` Spec: ${specFilePath}`));
6104
+ if (tasks) console.log(chalk10.gray(` Tasks: ${tasks.length} tasks loaded into .claude-prompt.txt`));
6105
+ console.log(chalk10.gray(" Prompt pre-loaded in .claude-prompt.txt\n"));
5868
6106
  try {
5869
- execSync(claudeCmd, { cwd: workingDir, stdio: "inherit" });
5870
- console.log(chalk8.green("\n \u2714 Claude Code session completed."));
6107
+ execSync2(claudeCmd, { cwd: workingDir, stdio: "inherit" });
6108
+ console.log(chalk10.green("\n \u2714 Claude Code session completed."));
5871
6109
  } catch {
5872
- console.log(chalk8.yellow("\n Claude Code session ended. Continuing workflow."));
6110
+ console.log(chalk10.yellow("\n Claude Code session ended. Continuing workflow."));
5873
6111
  }
5874
6112
  }
5875
6113
  }
@@ -5882,10 +6120,10 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
5882
6120
  const pending = tasks.filter((t) => t.status !== "done");
5883
6121
  const doneCount = tasks.length - pending.length;
5884
6122
  if (options.resume && doneCount > 0) {
5885
- console.log(chalk8.cyan(`
6123
+ console.log(chalk10.cyan(`
5886
6124
  Resuming: ${doneCount}/${tasks.length} tasks already done \u2014 skipping.`));
5887
6125
  } else {
5888
- console.log(chalk8.cyan(`
6126
+ console.log(chalk10.cyan(`
5889
6127
  Incremental mode: ${tasks.length} tasks`));
5890
6128
  }
5891
6129
  let completed = doneCount;
@@ -5914,33 +6152,33 @@ Implement ONLY this task. Do not implement other tasks.`;
5914
6152
  completed++;
5915
6153
  } catch {
5916
6154
  taskStatus = "failed";
5917
- console.log(chalk8.yellow(`
6155
+ console.log(chalk10.yellow(`
5918
6156
  \u26A0 Task ${task.id} exited with error \u2014 marked as failed. Re-run with --resume to retry.`));
5919
6157
  }
5920
6158
  await updateTaskStatus(specFilePath, task.id, taskStatus);
5921
6159
  }
5922
6160
  const successCount = tasks.filter((t) => t.status === "done").length + (completed - doneCount);
5923
6161
  console.log(
5924
- chalk8.bold(
6162
+ chalk10.bold(
5925
6163
  `
5926
- ${successCount === tasks.length ? chalk8.green("\u2714") : chalk8.yellow("!")} Incremental build: ${completed}/${tasks.length} tasks completed.`
6164
+ ${successCount === tasks.length ? chalk10.green("\u2714") : chalk10.yellow("!")} Incremental build: ${completed}/${tasks.length} tasks completed.`
5927
6165
  )
5928
6166
  );
5929
6167
  }
5930
6168
  // ── Mode: api ─────────────────────────────────────────────────────────────
5931
6169
  async runApiMode(specFilePath, workingDir, context, options = {}) {
5932
6170
  console.log(
5933
- chalk8.blue(
6171
+ chalk10.blue(
5934
6172
  `
5935
6173
  \u2500\u2500\u2500 Code Generation: API (${this.provider.providerName}/${this.provider.modelName}) \u2500\u2500\u2500`
5936
6174
  )
5937
6175
  );
5938
6176
  const systemPrompt = getCodeGenSystemPrompt(options.repoType);
5939
6177
  if (options.repoType && options.repoType !== "node-express" && options.repoType !== "node-koa" && options.repoType !== "unknown") {
5940
- console.log(chalk8.gray(` Language: ${options.repoType} (using language-specific codegen prompt)`));
6178
+ console.log(chalk10.gray(` Language: ${options.repoType} (using language-specific codegen prompt)`));
5941
6179
  }
5942
6180
  const spec = await fs10.readFile(specFilePath, "utf-8");
5943
- const constitutionSection = context?.constitution ? `
6181
+ let constitutionSection = context?.constitution ? `
5944
6182
  === Project Constitution (MUST follow) ===
5945
6183
  ${context.constitution}
5946
6184
  ` : "";
@@ -5955,7 +6193,7 @@ ${buildDslContextSection(dsl)}
5955
6193
  if (dsl) {
5956
6194
  const cmpCount = dsl.components?.length ?? 0;
5957
6195
  const cmpSuffix = cmpCount > 0 ? `, ${cmpCount} components` : "";
5958
- console.log(chalk8.green(` \u2713 DSL loaded \u2014 ${dsl.endpoints.length} endpoints, ${dsl.models.length} models${cmpSuffix}`));
6196
+ console.log(chalk10.green(` \u2713 DSL loaded \u2014 ${dsl.endpoints.length} endpoints, ${dsl.models.length} models${cmpSuffix}`));
5959
6197
  }
5960
6198
  const isFrontend = isFrontendDeps(context?.dependencies ?? []);
5961
6199
  let frontendSection = "";
@@ -5964,13 +6202,30 @@ ${buildDslContextSection(dsl)}
5964
6202
  frontendSection = `
5965
6203
  ${buildFrontendContextSection(fctx)}
5966
6204
  `;
5967
- console.log(chalk8.gray(` Frontend context: ${fctx.framework} / ${fctx.httpClient} | hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
6205
+ console.log(chalk10.gray(` Frontend context: ${fctx.framework} / ${fctx.httpClient} | hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
6206
+ }
6207
+ const allContextText = spec + constitutionSection + dslSection + frontendSection + installedPackagesSection + sharedConfigSection;
6208
+ const estimatedTokenCount = estimateTokens(allContextText);
6209
+ const budget = getDefaultBudget(this.provider.providerName);
6210
+ if (estimatedTokenCount > budget * 0.7) {
6211
+ console.log(
6212
+ chalk10.yellow(
6213
+ ` \u26A0 Context size: ~${Math.round(estimatedTokenCount / 1e3)}K tokens (budget: ${Math.round(budget / 1e3)}K for ${this.provider.providerName})`
6214
+ )
6215
+ );
6216
+ if (constitutionSection.length > 4e3) {
6217
+ const s9Start = constitutionSection.indexOf("## 9.");
6218
+ if (s9Start > 0) {
6219
+ constitutionSection = constitutionSection.slice(0, s9Start) + "## 9. \u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons)\n[Trimmed for context budget \u2014 run `ai-spec init --consolidate` to prune]\n";
6220
+ console.log(chalk10.gray(" \u2192 \xA79 trimmed from constitution to save tokens."));
6221
+ }
6222
+ }
5968
6223
  }
5969
6224
  const tasks = await loadTasksForSpec(specFilePath);
5970
6225
  if (tasks && tasks.length > 0) {
5971
6226
  return this.runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection + dslSection + installedPackagesSection, frontendSection, sharedConfigSection, options, systemPrompt, context);
5972
6227
  }
5973
- console.log(chalk8.gray(" [1/2] Planning implementation files..."));
6228
+ console.log(chalk10.gray(" [1/2] Planning implementation files..."));
5974
6229
  const planPrompt = `Based on the feature spec and project context below, list ALL files that need to be created or modified.
5975
6230
 
5976
6231
  IMPORTANT: Check the "Existing Shared Config Files" section below FIRST. For any file listed there,
@@ -5993,18 +6248,18 @@ Output ONLY a valid JSON array:
5993
6248
  const planResponse = await this.provider.generate(planPrompt, systemPrompt);
5994
6249
  filePlan = parseJsonArray(planResponse);
5995
6250
  } catch (err) {
5996
- console.error(chalk8.red(" Failed to generate file plan:"), err);
6251
+ console.error(chalk10.red(" Failed to generate file plan:"), err);
5997
6252
  }
5998
6253
  if (filePlan.length === 0) {
5999
- console.log(chalk8.yellow(" Could not determine file plan. Falling back to plan mode."));
6254
+ console.log(chalk10.yellow(" Could not determine file plan. Falling back to plan mode."));
6000
6255
  await this.runPlanMode(specFilePath);
6001
6256
  return [];
6002
6257
  }
6003
- console.log(chalk8.cyan(`
6258
+ console.log(chalk10.cyan(`
6004
6259
  Plan: ${filePlan.length} file(s) to process`));
6005
6260
  filePlan.forEach((item) => {
6006
- const icon = item.action === "create" ? chalk8.green("+") : chalk8.yellow("~");
6007
- console.log(` ${icon} ${item.file}: ${chalk8.gray(item.description)}`);
6261
+ const icon = item.action === "create" ? chalk10.green("+") : chalk10.yellow("~");
6262
+ console.log(` ${icon} ${item.file}: ${chalk10.gray(item.description)}`);
6008
6263
  });
6009
6264
  const { files } = await this.generateFiles(filePlan, spec, workingDir, constitutionSection + dslSection + frontendSection + installedPackagesSection, systemPrompt);
6010
6265
  return files;
@@ -6013,13 +6268,13 @@ Output ONLY a valid JSON array:
6013
6268
  const pendingTasks = tasks.filter((t) => t.status !== "done");
6014
6269
  const doneCount = tasks.length - pendingTasks.length;
6015
6270
  if (options.resume && doneCount > 0) {
6016
- console.log(chalk8.cyan(`
6017
- Task-based generation (resume): ${tasks.length} tasks (${chalk8.green(doneCount + " already done")}, skipping)`));
6271
+ console.log(chalk10.cyan(`
6272
+ Task-based generation (resume): ${tasks.length} tasks (${chalk10.green(doneCount + " already done")}, skipping)`));
6018
6273
  } else if (doneCount > 0) {
6019
- console.log(chalk8.cyan(`
6020
- Task-based generation: ${tasks.length} tasks (${chalk8.green(doneCount + " already done")}, resuming from checkpoint)`));
6274
+ console.log(chalk10.cyan(`
6275
+ Task-based generation: ${tasks.length} tasks (${chalk10.green(doneCount + " already done")}, resuming from checkpoint)`));
6021
6276
  } else {
6022
- console.log(chalk8.cyan(`
6277
+ console.log(chalk10.cyan(`
6023
6278
  Task-based generation: ${tasks.length} tasks`));
6024
6279
  }
6025
6280
  const sharedConfigPaths = new Set(
@@ -6051,9 +6306,9 @@ Output ONLY a valid JSON array:
6051
6306
  const pct = Math.round(completedTasks / tasks.length * 100);
6052
6307
  const barWidth = 20;
6053
6308
  const filled = Math.round(pct / 100 * barWidth);
6054
- const bar = chalk8.green("\u2588".repeat(filled)) + chalk8.gray("\u2591".repeat(barWidth - filled));
6309
+ const bar = chalk10.green("\u2588".repeat(filled)) + chalk10.gray("\u2591".repeat(barWidth - filled));
6055
6310
  console.log(
6056
- chalk8.bold(`
6311
+ chalk10.bold(`
6057
6312
  [${bar}] ${pct}% \u26A1 Layer [${layer}] ${layerIcon} \u2014 ${layerTasks.length} tasks running in parallel`)
6058
6313
  );
6059
6314
  } else {
@@ -6061,7 +6316,7 @@ Output ONLY a valid JSON array:
6061
6316
  }
6062
6317
  const executeTask = async (task, batchIsParallel) => {
6063
6318
  if (task.filesToTouch.length === 0) {
6064
- if (!batchIsParallel) console.log(chalk8.gray(" No files specified, skipping."));
6319
+ if (!batchIsParallel) console.log(chalk10.gray(" No files specified, skipping."));
6065
6320
  return { task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false };
6066
6321
  }
6067
6322
  const filePlan = await Promise.all(
@@ -6118,13 +6373,19 @@ ${taskContext}`,
6118
6373
  const layerResults = [];
6119
6374
  for (const batch of taskBatches) {
6120
6375
  const batchIsParallel = batch.length > 1;
6121
- const batchResultPromises = batch.map(
6122
- (task) => executeTask(task, batchIsParallel).catch((err) => {
6123
- console.log(chalk8.yellow(` \u26A0 ${task.id} threw unexpectedly: ${err.message}`));
6124
- return { task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false };
6125
- })
6126
- );
6127
- const batchResults = await Promise.all(batchResultPromises);
6376
+ const batchResultPromises = batch.map((task) => executeTask(task, batchIsParallel));
6377
+ const settled = await Promise.allSettled(batchResultPromises);
6378
+ const batchResults = [];
6379
+ for (let i = 0; i < settled.length; i++) {
6380
+ const outcome = settled[i];
6381
+ if (outcome.status === "fulfilled") {
6382
+ batchResults.push(outcome.value);
6383
+ } else {
6384
+ const task = batch[i];
6385
+ console.log(chalk10.yellow(` \u26A0 ${task.id} threw unexpectedly: ${outcome.reason?.message ?? outcome.reason}`));
6386
+ batchResults.push({ task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false });
6387
+ }
6388
+ }
6128
6389
  layerResults.push(...batchResults);
6129
6390
  await updateCacheFromBatch(batchResults);
6130
6391
  }
@@ -6136,14 +6397,14 @@ ${taskContext}`,
6136
6397
  totalFiles += result.total;
6137
6398
  allGeneratedFiles.push(...result.files);
6138
6399
  if (isParallel) {
6139
- const icon = result.success === result.total ? chalk8.green("\u2714") : chalk8.yellow("!");
6400
+ const icon = result.success === result.total ? chalk10.green("\u2714") : chalk10.yellow("!");
6140
6401
  const layerTaskIcon = LAYER_ICONS[result.task.layer] ?? " ";
6141
6402
  console.log(` ${icon} ${result.task.id} ${layerTaskIcon} ${result.task.title} \u2014 ${result.success}/${result.total} files`);
6142
6403
  }
6143
6404
  const taskStatus = result.success === result.total ? "done" : "failed";
6144
6405
  await updateTaskStatus(specFilePath, result.task.id, taskStatus);
6145
6406
  if (taskStatus === "failed") {
6146
- console.log(chalk8.yellow(` \u26A0 ${result.task.id} marked as failed \u2014 re-run with --resume to retry`));
6407
+ console.log(chalk10.yellow(` \u26A0 ${result.task.id} marked as failed \u2014 re-run with --resume to retry`));
6147
6408
  }
6148
6409
  }
6149
6410
  completedTasks += layerTasks.length;
@@ -6158,7 +6419,7 @@ ${taskContext}`,
6158
6419
  if ((sharedFile.category === "route-index" || sharedFile.category === "store-index") && newModuleNames.length > 0) {
6159
6420
  purpose = `Add to this file: import ${newModuleNames.join(", ")} from their respective paths and register them in the export/default array. Do NOT remove any existing imports.`;
6160
6421
  }
6161
- console.log(chalk8.gray(`
6422
+ console.log(chalk10.gray(`
6162
6423
  + updating shared config: ${sharedFile.path} [${sharedFile.category}]`));
6163
6424
  const updatedGeneratedFilesSection = buildGeneratedFilesSection(generatedFileCache);
6164
6425
  await this.generateFiles(
@@ -6176,17 +6437,17 @@ Updating shared registration after layer [${layer}] completed. New modules: ${ne
6176
6437
  }
6177
6438
  }
6178
6439
  console.log(
6179
- chalk8.bold(
6440
+ chalk10.bold(
6180
6441
  `
6181
- ${totalSuccess === totalFiles ? chalk8.green("\u2714") : chalk8.yellow("!")} Task-based generation: ${totalSuccess}/${totalFiles} files written across ${pendingTasks.length} tasks.`
6442
+ ${totalSuccess === totalFiles ? chalk10.green("\u2714") : chalk10.yellow("!")} Task-based generation: ${totalSuccess}/${totalFiles} files written across ${pendingTasks.length} tasks.`
6182
6443
  )
6183
6444
  );
6184
6445
  return allGeneratedFiles;
6185
6446
  }
6186
6447
  async generateFiles(filePlan, spec, workingDir, constitutionSection, systemPrompt = getCodeGenSystemPrompt(), taskLabel) {
6187
- const prefix = taskLabel ? ` [${chalk8.cyan(taskLabel)}] ` : " ";
6448
+ const prefix = taskLabel ? ` [${chalk10.cyan(taskLabel)}] ` : " ";
6188
6449
  if (!taskLabel) {
6189
- console.log(chalk8.gray(`
6450
+ console.log(chalk10.gray(`
6190
6451
  Generating ${filePlan.length} file(s)...`));
6191
6452
  }
6192
6453
  let successCount = 0;
@@ -6207,6 +6468,7 @@ ${spec}
6207
6468
  ${constitutionSection}
6208
6469
  === ${existingContent ? "Existing content (modify and return the complete file)" : "Create this file from scratch"} ===
6209
6470
  ${existingContent || "Output only the complete file content."}`;
6471
+ const fileSpinner = startSpinner(`${prefix}Generating ${chalk10.bold(item.file)}...`);
6210
6472
  try {
6211
6473
  const raw = await this.provider.generate(codePrompt, systemPrompt);
6212
6474
  const fileContent = stripCodeFences(raw);
@@ -6214,17 +6476,17 @@ ${existingContent || "Output only the complete file content."}`;
6214
6476
  await fs10.ensureDir(path6.dirname(fullPath));
6215
6477
  await fs10.writeFile(fullPath, fileContent, "utf-8");
6216
6478
  getActiveLogger()?.fileWritten(item.file);
6217
- console.log(`${prefix}${existingContent ? chalk8.yellow("~") : chalk8.green("+")} ${chalk8.bold(item.file)} ${chalk8.green("\u2714")}`);
6479
+ fileSpinner.succeed(`${existingContent ? chalk10.yellow("~") : chalk10.green("+")} ${chalk10.bold(item.file)}`);
6218
6480
  successCount++;
6219
6481
  writtenFiles.push(item.file);
6220
6482
  } catch (err) {
6221
- console.log(`${prefix}${chalk8.red("\u2718")} ${chalk8.bold(item.file)} \u2014 ${chalk8.red(err.message)}`);
6483
+ fileSpinner.fail(`${chalk10.bold(item.file)} \u2014 ${err.message}`);
6222
6484
  }
6223
6485
  }
6224
6486
  if (!taskLabel) {
6225
6487
  console.log(
6226
- chalk8.bold(
6227
- ` ${successCount === filePlan.length ? chalk8.green("\u2714") : chalk8.yellow("!")} ${successCount}/${filePlan.length} files written.`
6488
+ chalk10.bold(
6489
+ ` ${successCount === filePlan.length ? chalk10.green("\u2714") : chalk10.yellow("!")} ${successCount}/${filePlan.length} files written.`
6228
6490
  )
6229
6491
  );
6230
6492
  }
@@ -6232,7 +6494,7 @@ ${existingContent || "Output only the complete file content."}`;
6232
6494
  }
6233
6495
  // ── Mode: plan ─────────────────────────────────────────────────────────────
6234
6496
  async runPlanMode(specFilePath) {
6235
- console.log(chalk8.blue("\n\u2500\u2500\u2500 Implementation Plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6497
+ console.log(chalk10.blue("\n\u2500\u2500\u2500 Implementation Plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6236
6498
  const spec = await fs10.readFile(specFilePath, "utf-8");
6237
6499
  const plan = await this.provider.generate(
6238
6500
  `Create a detailed, step-by-step implementation plan for the following feature spec.
@@ -6245,80 +6507,18 @@ Be specific about:
6245
6507
  ${spec}`,
6246
6508
  "You are a senior developer creating an actionable implementation guide."
6247
6509
  );
6248
- console.log(chalk8.cyan("\n") + plan);
6249
- }
6250
- };
6251
- function topoSortLayerTasks(tasks) {
6252
- if (tasks.length <= 1) return [tasks];
6253
- const idSet = new Set(tasks.map((t) => t.id));
6254
- const taskById = new Map(tasks.map((t) => [t.id, t]));
6255
- const inDegree = /* @__PURE__ */ new Map();
6256
- const dependents = /* @__PURE__ */ new Map();
6257
- for (const task of tasks) {
6258
- inDegree.set(task.id, 0);
6259
- dependents.set(task.id, []);
6260
- }
6261
- for (const task of tasks) {
6262
- const intraDeps = task.dependencies.filter((dep) => idSet.has(dep));
6263
- inDegree.set(task.id, intraDeps.length);
6264
- for (const dep of intraDeps) {
6265
- dependents.get(dep).push(task.id);
6266
- }
6510
+ console.log(chalk10.cyan("\n") + plan);
6267
6511
  }
6268
- const batches = [];
6269
- const remaining = new Set(tasks.map((t) => t.id));
6270
- while (remaining.size > 0) {
6271
- const batch = [...remaining].filter((id) => inDegree.get(id) === 0).map((id) => taskById.get(id));
6272
- if (batch.length === 0) {
6273
- batches.push([...remaining].map((id) => taskById.get(id)));
6274
- break;
6275
- }
6276
- batches.push(batch);
6277
- for (const task of batch) {
6278
- remaining.delete(task.id);
6279
- for (const dependent of dependents.get(task.id)) {
6280
- inDegree.set(dependent, inDegree.get(dependent) - 1);
6281
- }
6282
- }
6283
- }
6284
- return batches;
6285
- }
6286
- var LAYER_ICONS = {
6287
- data: "\u{1F4BE}",
6288
- infra: "\u2699\uFE0F ",
6289
- service: "\u{1F527}",
6290
- api: "\u{1F310}",
6291
- view: "\u{1F5A5}\uFE0F ",
6292
- route: "\u{1F5FA}\uFE0F ",
6293
- test: "\u{1F9EA}"
6294
6512
  };
6295
- function printTaskProgress(completed, total, task, mode) {
6296
- const pct = total > 0 ? Math.round(completed / total * 100) : 0;
6297
- const barWidth = 20;
6298
- const filled = Math.round(pct / 100 * barWidth);
6299
- const bar = chalk8.green("\u2588".repeat(filled)) + chalk8.gray("\u2591".repeat(barWidth - filled));
6300
- const icon = LAYER_ICONS[task.layer] ?? " ";
6301
- if (mode === "skip") {
6302
- console.log(
6303
- chalk8.gray(`
6304
- [${bar}] ${pct}% \u2713 ${task.id} ${icon} ${task.title} \u2014 already done`)
6305
- );
6306
- } else {
6307
- console.log(
6308
- chalk8.bold(`
6309
- [${bar}] ${pct}% \u2192 ${task.id} ${icon} ${task.title}`)
6310
- );
6311
- }
6312
- }
6313
6513
 
6314
6514
  // core/reviewer.ts
6315
- import chalk10 from "chalk";
6316
- import { execSync as execSync2 } from "child_process";
6515
+ import chalk12 from "chalk";
6516
+ import { execSync as execSync3 } from "child_process";
6317
6517
  import * as path8 from "path";
6318
6518
  import * as fs12 from "fs-extra";
6319
6519
 
6320
6520
  // core/constitution-generator.ts
6321
- import chalk9 from "chalk";
6521
+ import chalk11 from "chalk";
6322
6522
  import * as fs11 from "fs-extra";
6323
6523
  import * as path7 from "path";
6324
6524
 
@@ -6468,7 +6668,7 @@ async function loadConstitution(projectRoot) {
6468
6668
  function printConstitutionHint(exists) {
6469
6669
  if (!exists) {
6470
6670
  console.log(
6471
- chalk9.yellow(
6671
+ chalk11.yellow(
6472
6672
  " \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
6473
6673
  )
6474
6674
  );
@@ -6552,16 +6752,16 @@ var CodeReviewer = class {
6552
6752
  this.projectRoot = projectRoot;
6553
6753
  }
6554
6754
  getGitDiff() {
6555
- const silent = { encoding: "utf-8", stdio: "pipe" };
6755
+ const silent = { encoding: "utf-8", stdio: "pipe", cwd: this.projectRoot, timeout: 3e4 };
6556
6756
  try {
6557
- execSync2("git rev-parse --is-inside-work-tree", silent);
6757
+ execSync3("git rev-parse --is-inside-work-tree", silent);
6558
6758
  } catch {
6559
6759
  return "";
6560
6760
  }
6561
6761
  try {
6562
- let diff = execSync2("git diff --cached", silent);
6563
- if (!diff.trim()) diff = execSync2("git diff HEAD", silent);
6564
- if (!diff.trim()) diff = execSync2("git diff", silent);
6762
+ let diff = execSync3("git diff --cached", silent);
6763
+ if (!diff.trim()) diff = execSync3("git diff HEAD", silent);
6764
+ if (!diff.trim()) diff = execSync3("git diff", silent);
6565
6765
  return diff;
6566
6766
  } catch {
6567
6767
  return "";
@@ -6586,7 +6786,7 @@ var CodeReviewer = class {
6586
6786
  async runThreePassReview(specContent, codeContext, specFile) {
6587
6787
  let complianceReview = "";
6588
6788
  if (specContent && specContent.trim() && specContent !== "(No spec \u2014 review for general code quality)") {
6589
- console.log(chalk10.gray(" Pass 0/3: Spec compliance check..."));
6789
+ console.log(chalk12.gray(" Pass 0/3: Spec compliance check..."));
6590
6790
  const compliancePrompt = `Check whether the implementation covers every requirement in the spec.
6591
6791
 
6592
6792
  === Feature Spec ===
@@ -6598,13 +6798,13 @@ ${codeContext}`;
6598
6798
  const complianceScore2 = extractComplianceScore(complianceReview);
6599
6799
  const missingCount = extractMissingCount(complianceReview);
6600
6800
  if (complianceScore2 > 0) {
6601
- const scoreColor = complianceScore2 >= 8 ? chalk10.green : complianceScore2 >= 6 ? chalk10.yellow : chalk10.red;
6801
+ const scoreColor = complianceScore2 >= 8 ? chalk12.green : complianceScore2 >= 6 ? chalk12.yellow : chalk12.red;
6602
6802
  console.log(
6603
- chalk10.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? chalk10.red(` \xB7 ${missingCount} missing requirement(s)`) : chalk10.green(" \xB7 all requirements covered"))
6803
+ chalk12.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? chalk12.red(` \xB7 ${missingCount} missing requirement(s)`) : chalk12.green(" \xB7 all requirements covered"))
6604
6804
  );
6605
6805
  }
6606
6806
  }
6607
- console.log(chalk10.gray(` Pass 1/3: Architecture review...`));
6807
+ console.log(chalk12.gray(` Pass 1/3: Architecture review...`));
6608
6808
  const accumulatedLessons = await loadAccumulatedLessons(this.projectRoot);
6609
6809
  const archPrompt = `Review the architecture of this change.
6610
6810
  ${complianceReview ? `
@@ -6621,7 +6821,7 @@ ${specContent || "(No spec \u2014 review for general code quality)"}
6621
6821
  === Code ===
6622
6822
  ${codeContext}`;
6623
6823
  const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
6624
- console.log(chalk10.gray(" Pass 2/3: Implementation review..."));
6824
+ console.log(chalk12.gray(" Pass 2/3: Implementation review..."));
6625
6825
  const history = await loadReviewHistory(this.projectRoot);
6626
6826
  const historyContext = buildHistoryContext(history);
6627
6827
  const implPrompt = `Review the implementation details of this change.
@@ -6636,7 +6836,7 @@ ${codeContext}
6636
6836
  ${archReview}
6637
6837
  ${historyContext}`;
6638
6838
  const implReview = await this.provider.generate(implPrompt, reviewImplementationSystemPrompt);
6639
- console.log(chalk10.gray(" Pass 3/3: Impact & complexity assessment..."));
6839
+ console.log(chalk12.gray(" Pass 3/3: Impact & complexity assessment..."));
6640
6840
  const impactPrompt = `Assess the impact and complexity of this change.
6641
6841
 
6642
6842
  === Feature Spec ===
@@ -6677,37 +6877,37 @@ ${sep}
6677
6877
  return combined;
6678
6878
  }
6679
6879
  async reviewCode(specContent, specFile) {
6680
- console.log(chalk10.cyan("\n\u2500\u2500\u2500 Automated Code Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6880
+ console.log(chalk12.cyan("\n\u2500\u2500\u2500 Automated Code Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6681
6881
  const diff = this.getGitDiff();
6682
6882
  if (!diff.trim()) {
6683
6883
  console.log(
6684
- chalk10.yellow(" No git diff found. Stage or commit changes first, then run review.")
6884
+ chalk12.yellow(" No git diff found. Stage or commit changes first, then run review.")
6685
6885
  );
6686
- console.log(chalk10.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
6886
+ console.log(chalk12.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
6687
6887
  return "No changes";
6688
6888
  }
6689
6889
  const { files, added, removed } = this.getDiffStats(diff);
6690
6890
  console.log(
6691
- chalk10.gray(` Diff: ${files} file(s), ${chalk10.green("+" + added)} ${chalk10.red("-" + removed)}`)
6891
+ chalk12.gray(` Diff: ${files} file(s), ${chalk12.green("+" + added)} ${chalk12.red("-" + removed)}`)
6692
6892
  );
6693
6893
  console.log(
6694
- chalk10.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6894
+ chalk12.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6695
6895
  );
6696
6896
  const codeContext = diff.slice(0, 1e4);
6697
6897
  const reviewResult = await this.runThreePassReview(specContent, codeContext, specFile);
6698
- console.log(chalk10.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6898
+ console.log(chalk12.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6699
6899
  console.log(reviewResult);
6700
- console.log(chalk10.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6900
+ console.log(chalk12.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6701
6901
  return reviewResult;
6702
6902
  }
6703
6903
  /**
6704
6904
  * Review directly from generated file contents (for api mode where git diff is empty).
6705
6905
  */
6706
6906
  async reviewFiles(specContent, filePaths, workingDir, specFile) {
6707
- console.log(chalk10.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6708
- console.log(chalk10.gray(` Reviewing ${filePaths.length} generated file(s)...`));
6907
+ console.log(chalk12.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6908
+ console.log(chalk12.gray(` Reviewing ${filePaths.length} generated file(s)...`));
6709
6909
  console.log(
6710
- chalk10.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6910
+ chalk12.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6711
6911
  );
6712
6912
  let filesSection = "";
6713
6913
  for (const filePath of filePaths) {
@@ -6728,28 +6928,28 @@ ${content.slice(0, 3e3)}`;
6728
6928
  }
6729
6929
  }
6730
6930
  const reviewResult = await this.runThreePassReview(specContent, filesSection, specFile);
6731
- console.log(chalk10.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6931
+ console.log(chalk12.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6732
6932
  console.log(reviewResult);
6733
- console.log(chalk10.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6933
+ console.log(chalk12.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
6734
6934
  return reviewResult;
6735
6935
  }
6736
6936
  /** Print score trend from history (last N reviews) */
6737
6937
  async printScoreTrend(limit = 5) {
6738
6938
  const history = await loadReviewHistory(this.projectRoot);
6739
6939
  if (history.length === 0) {
6740
- console.log(chalk10.gray(" No review history yet."));
6940
+ console.log(chalk12.gray(" No review history yet."));
6741
6941
  return;
6742
6942
  }
6743
6943
  const recent = history.slice(-limit);
6744
- console.log(chalk10.cyan("\n\u2500\u2500\u2500 Review Score Trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6944
+ console.log(chalk12.cyan("\n\u2500\u2500\u2500 Review Score Trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6745
6945
  for (const entry of recent) {
6746
6946
  const bar = "\u2588".repeat(entry.score) + "\u2591".repeat(10 - entry.score);
6747
- const color = entry.score >= 8 ? chalk10.green : entry.score >= 6 ? chalk10.yellow : chalk10.red;
6748
- const impactTag = entry.impactLevel ? chalk10.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? chalk10.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? chalk10.yellow(entry.impactLevel) : chalk10.green(entry.impactLevel)}`) : "";
6749
- const complexityTag = entry.complexityLevel ? chalk10.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? chalk10.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? chalk10.yellow(entry.complexityLevel) : chalk10.green(entry.complexityLevel)}`) : "";
6947
+ const color = entry.score >= 8 ? chalk12.green : entry.score >= 6 ? chalk12.yellow : chalk12.red;
6948
+ const impactTag = entry.impactLevel ? chalk12.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? chalk12.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? chalk12.yellow(entry.impactLevel) : chalk12.green(entry.impactLevel)}`) : "";
6949
+ const complexityTag = entry.complexityLevel ? chalk12.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? chalk12.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? chalk12.yellow(entry.complexityLevel) : chalk12.green(entry.complexityLevel)}`) : "";
6750
6950
  console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
6751
6951
  }
6752
- console.log(chalk10.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6952
+ console.log(chalk12.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6753
6953
  }
6754
6954
  };
6755
6955
 
@@ -6799,17 +6999,17 @@ function parseSpecAndTasks(raw) {
6799
6999
  }
6800
7000
 
6801
7001
  // git/worktree.ts
6802
- import { execSync as execSync3 } from "child_process";
7002
+ import { execSync as execSync4 } from "child_process";
6803
7003
  import * as path9 from "path";
6804
7004
  import * as fs13 from "fs-extra";
6805
- import chalk11 from "chalk";
7005
+ import chalk13 from "chalk";
6806
7006
  var GitWorktreeManager = class {
6807
7007
  constructor(baseDir) {
6808
7008
  this.baseDir = baseDir;
6809
7009
  }
6810
7010
  isGitRepo() {
6811
7011
  try {
6812
- execSync3("git rev-parse --is-inside-work-tree", { cwd: this.baseDir, stdio: "ignore" });
7012
+ execSync4("git rev-parse --is-inside-work-tree", { cwd: this.baseDir, stdio: "ignore" });
6813
7013
  return true;
6814
7014
  } catch {
6815
7015
  return false;
@@ -6833,58 +7033,58 @@ var GitWorktreeManager = class {
6833
7033
  if (await fs13.pathExists(dest)) continue;
6834
7034
  try {
6835
7035
  await fs13.ensureSymlink(src, dest, "dir");
6836
- console.log(chalk11.gray(` Symlinked ${dir}/ from base repo \u2192 worktree`));
7036
+ console.log(chalk13.gray(` Symlinked ${dir}/ from base repo \u2192 worktree`));
6837
7037
  } catch (err) {
6838
- console.log(chalk11.yellow(` \u26A0 Could not symlink ${dir}/: ${err.message}`));
6839
- console.log(chalk11.yellow(` Run \`npm install\` inside the worktree manually.`));
7038
+ console.log(chalk13.yellow(` \u26A0 Could not symlink ${dir}/: ${err.message}`));
7039
+ console.log(chalk13.yellow(` Run \`npm install\` inside the worktree manually.`));
6840
7040
  }
6841
7041
  }
6842
7042
  }
6843
7043
  async createWorktree(idea) {
6844
7044
  if (!this.isGitRepo()) {
6845
- console.log(chalk11.yellow("\u26A0\uFE0F Not a git repository. Skipping worktree creation."));
7045
+ console.log(chalk13.yellow("\u26A0\uFE0F Not a git repository. Skipping worktree creation."));
6846
7046
  return null;
6847
7047
  }
6848
7048
  const featureName = this.sanitizeFeatureName(idea);
6849
7049
  const branchName = `feature/${featureName}`;
6850
7050
  const repoName = path9.basename(this.baseDir);
6851
7051
  const worktreePath = path9.resolve(this.baseDir, "..", `${repoName}-${featureName}`);
6852
- console.log(chalk11.cyan(`
7052
+ console.log(chalk13.cyan(`
6853
7053
  --- Setting up Git Worktree ---`));
6854
7054
  if (await fs13.pathExists(worktreePath)) {
6855
- console.log(chalk11.yellow(`\u26A0\uFE0F Worktree directory already exists at: ${worktreePath}`));
7055
+ console.log(chalk13.yellow(`\u26A0\uFE0F Worktree directory already exists at: ${worktreePath}`));
6856
7056
  await this.linkDependencies(worktreePath);
6857
7057
  return worktreePath;
6858
7058
  }
6859
7059
  try {
6860
7060
  let branchExists = false;
6861
7061
  try {
6862
- execSync3(`git show-ref --verify refs/heads/${branchName}`, {
7062
+ execSync4(`git show-ref --verify refs/heads/${branchName}`, {
6863
7063
  cwd: this.baseDir,
6864
7064
  stdio: "ignore"
6865
7065
  });
6866
7066
  branchExists = true;
6867
7067
  } catch {
6868
7068
  }
6869
- console.log(chalk11.gray(`Creating worktree at: ${worktreePath}`));
7069
+ console.log(chalk13.gray(`Creating worktree at: ${worktreePath}`));
6870
7070
  if (branchExists) {
6871
- execSync3(`git worktree add "${worktreePath}" ${branchName}`, {
7071
+ execSync4(`git worktree add "${worktreePath}" ${branchName}`, {
6872
7072
  cwd: this.baseDir,
6873
7073
  stdio: "inherit"
6874
7074
  });
6875
7075
  } else {
6876
- execSync3(`git worktree add -b ${branchName} "${worktreePath}"`, {
7076
+ execSync4(`git worktree add -b ${branchName} "${worktreePath}"`, {
6877
7077
  cwd: this.baseDir,
6878
7078
  stdio: "inherit"
6879
7079
  });
6880
7080
  }
6881
7081
  console.log(
6882
- chalk11.green(`\u2714 Worktree successfully created and isolated on branch '${branchName}'`)
7082
+ chalk13.green(`\u2714 Worktree successfully created and isolated on branch '${branchName}'`)
6883
7083
  );
6884
7084
  await this.linkDependencies(worktreePath);
6885
7085
  return worktreePath;
6886
7086
  } catch (error) {
6887
- console.error(chalk11.red("Failed to create git worktree:"), error);
7087
+ console.error(chalk13.red("Failed to create git worktree:"), error);
6888
7088
  return null;
6889
7089
  }
6890
7090
  }