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.js CHANGED
@@ -182,9 +182,77 @@ CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uF
182
182
  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
183
183
  4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
184
184
 
185
- // core/provider-utils.ts
185
+ // core/cli-ui.ts
186
186
  var import_chalk = __toESM(require("chalk"));
187
- var sleep = (ms2) => new Promise((r) => setTimeout(r, ms2));
187
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
188
+ function startSpinner(text) {
189
+ const isTTY = process.stderr.isTTY;
190
+ let frame = 0;
191
+ let currentText = text;
192
+ let stopped = false;
193
+ function render() {
194
+ if (stopped) return;
195
+ const symbol = import_chalk.default.cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
196
+ if (isTTY) {
197
+ process.stderr.write(`\r ${symbol} ${currentText}${" ".repeat(10)}`);
198
+ }
199
+ frame++;
200
+ }
201
+ if (!isTTY) {
202
+ process.stderr.write(` \u2026 ${currentText}
203
+ `);
204
+ }
205
+ const timer = setInterval(render, 80);
206
+ render();
207
+ return {
208
+ update(newText) {
209
+ currentText = newText;
210
+ },
211
+ stop(finalText) {
212
+ if (stopped) return;
213
+ stopped = true;
214
+ clearInterval(timer);
215
+ if (isTTY) {
216
+ process.stderr.write(`\r${" ".repeat(currentText.length + 20)}\r`);
217
+ }
218
+ if (finalText) {
219
+ process.stderr.write(` ${finalText}
220
+ `);
221
+ }
222
+ },
223
+ succeed(successText) {
224
+ this.stop(import_chalk.default.green(`\u2714 ${successText}`));
225
+ },
226
+ fail(failText) {
227
+ this.stop(import_chalk.default.red(`\u2718 ${failText}`));
228
+ }
229
+ };
230
+ }
231
+ async function retryCountdown(opts) {
232
+ const { attempt, maxAttempts, waitMs, errorMessage, label } = opts;
233
+ const isTTY = process.stderr.isTTY;
234
+ const shortErr = errorMessage.length > 120 ? errorMessage.slice(0, 117) + "..." : errorMessage;
235
+ process.stderr.write("\n");
236
+ process.stderr.write(import_chalk.default.yellow(` \u250C\u2500 Retry ${attempt}/${maxAttempts} `) + import_chalk.default.gray(`[${label}]`) + import_chalk.default.yellow(` ${"\u2500".repeat(Math.max(1, 40 - label.length))}
237
+ `));
238
+ process.stderr.write(import_chalk.default.yellow(` \u2502 `) + import_chalk.default.white(shortErr) + "\n");
239
+ process.stderr.write(import_chalk.default.yellow(` \u2502 `) + import_chalk.default.gray(`Waiting before retry...`) + "\n");
240
+ const totalSeconds = Math.ceil(waitMs / 1e3);
241
+ for (let s = totalSeconds; s > 0; s--) {
242
+ const bar = import_chalk.default.green("\u2588".repeat(totalSeconds - s)) + import_chalk.default.gray("\u2591".repeat(s));
243
+ const line = import_chalk.default.yellow(` \u2502 `) + `${bar} ${import_chalk.default.bold.white(`${s}s`)}`;
244
+ if (isTTY) {
245
+ process.stderr.write(`\r${line}${" ".repeat(10)}`);
246
+ }
247
+ await new Promise((r) => setTimeout(r, 1e3));
248
+ }
249
+ if (isTTY) {
250
+ process.stderr.write(`\r${" ".repeat(70)}\r`);
251
+ }
252
+ process.stderr.write(import_chalk.default.yellow(` \u2514\u2500 `) + import_chalk.default.cyan(`Retrying now...`) + "\n\n");
253
+ }
254
+
255
+ // core/provider-utils.ts
188
256
  var ProviderError = class extends Error {
189
257
  constructor(message, kind, originalError) {
190
258
  super(message);
@@ -268,11 +336,14 @@ async function withReliability(fn, opts) {
268
336
  throw classifyError(err, label);
269
337
  }
270
338
  const waitMs = attempt === 0 ? 2e3 : 6e3;
271
- console.warn(
272
- import_chalk.default.yellow(` \u26A0 ${label} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${waitMs / 1e3}s`) + import_chalk.default.gray(` \u2014 ${err.message}`)
273
- );
274
339
  onRetry?.(attempt + 1, err);
275
- await sleep(waitMs);
340
+ await retryCountdown({
341
+ attempt: attempt + 1,
342
+ maxAttempts: retries + 1,
343
+ waitMs,
344
+ errorMessage: err.message ?? String(err),
345
+ label
346
+ });
276
347
  }
277
348
  }
278
349
  throw new Error("unreachable");
@@ -4173,8 +4244,8 @@ ${currentSpec}`,
4173
4244
  };
4174
4245
 
4175
4246
  // core/code-generator.ts
4176
- var import_chalk8 = __toESM(require("chalk"));
4177
- var import_child_process = require("child_process");
4247
+ var import_chalk10 = __toESM(require("chalk"));
4248
+ var import_child_process2 = require("child_process");
4178
4249
  var path6 = __toESM(require("path"));
4179
4250
  var fs10 = __toESM(require("fs-extra"));
4180
4251
 
@@ -4751,7 +4822,7 @@ async function updateTaskStatus(specFilePath, taskId, status) {
4751
4822
  }
4752
4823
 
4753
4824
  // core/dsl-extractor.ts
4754
- var import_chalk6 = __toESM(require("chalk"));
4825
+ var import_chalk7 = __toESM(require("chalk"));
4755
4826
  var fs6 = __toESM(require("fs-extra"));
4756
4827
  var path4 = __toESM(require("path"));
4757
4828
  var import_prompts2 = require("@inquirer/prompts");
@@ -4840,6 +4911,7 @@ function validateDsl(raw) {
4840
4911
  }
4841
4912
  }
4842
4913
  }
4914
+ crossReferenceChecks(obj, errors);
4843
4915
  if (errors.length > 0) {
4844
4916
  return { valid: false, errors };
4845
4917
  }
@@ -5071,6 +5143,64 @@ function validateComponent(raw, path10, errors) {
5071
5143
  }
5072
5144
  }
5073
5145
  }
5146
+ function crossReferenceChecks(obj, errors) {
5147
+ const models = Array.isArray(obj["models"]) ? obj["models"] : [];
5148
+ const endpoints = Array.isArray(obj["endpoints"]) ? obj["endpoints"] : [];
5149
+ const components = Array.isArray(obj["components"]) ? obj["components"] : [];
5150
+ const seenRoutes = /* @__PURE__ */ new Map();
5151
+ for (let i = 0; i < endpoints.length; i++) {
5152
+ const ep = endpoints[i];
5153
+ if (typeof ep?.["method"] === "string" && typeof ep?.["path"] === "string") {
5154
+ const route = `${ep["method"].toUpperCase()} ${ep["path"]}`;
5155
+ if (seenRoutes.has(route)) {
5156
+ errors.push({
5157
+ path: `endpoints[${i}]`,
5158
+ message: `Duplicate route "${route}" \u2014 also defined at endpoints[${seenRoutes.get(route)}]`
5159
+ });
5160
+ } else {
5161
+ seenRoutes.set(route, i);
5162
+ }
5163
+ }
5164
+ }
5165
+ const modelNames = new Set(
5166
+ models.filter((m) => typeof m?.["name"] === "string").map((m) => m["name"])
5167
+ );
5168
+ for (let i = 0; i < models.length; i++) {
5169
+ const m = models[i];
5170
+ if (!Array.isArray(m?.["relations"])) continue;
5171
+ for (const rel of m["relations"]) {
5172
+ if (typeof rel !== "string") continue;
5173
+ const refMatch = rel.match(/(?:hasMany|hasOne|belongsTo|manyToMany)\s+(\w+)/i);
5174
+ if (refMatch) {
5175
+ const refName = refMatch[1];
5176
+ if (!modelNames.has(refName)) {
5177
+ errors.push({
5178
+ path: `models[${i}].relations`,
5179
+ message: `Relation references model "${refName}" which is not defined in models[]`
5180
+ });
5181
+ }
5182
+ }
5183
+ }
5184
+ }
5185
+ const endpointIds = new Set(
5186
+ endpoints.filter((e) => typeof e?.["id"] === "string").map((e) => e["id"])
5187
+ );
5188
+ if (endpointIds.size > 0) {
5189
+ for (let i = 0; i < components.length; i++) {
5190
+ const c = components[i];
5191
+ if (!Array.isArray(c?.["apiCalls"])) continue;
5192
+ for (const call of c["apiCalls"]) {
5193
+ if (typeof call !== "string") continue;
5194
+ if (!endpointIds.has(call)) {
5195
+ errors.push({
5196
+ path: `components[${i}].apiCalls`,
5197
+ message: `References endpoint "${call}" which is not defined in endpoints[]`
5198
+ });
5199
+ }
5200
+ }
5201
+ }
5202
+ }
5203
+ }
5074
5204
  function requireNonEmptyString(v2, path10, errors) {
5075
5205
  if (typeof v2 !== "string" || v2.trim().length === 0) {
5076
5206
  errors.push({
@@ -5085,6 +5215,26 @@ function typeLabel(v2) {
5085
5215
  return typeof v2;
5086
5216
  }
5087
5217
 
5218
+ // core/token-budget.ts
5219
+ var import_chalk6 = __toESM(require("chalk"));
5220
+ var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
5221
+ function estimateTokens(text) {
5222
+ if (!text) return 0;
5223
+ const cjkCount = (text.match(CJK_RANGE) ?? []).length;
5224
+ const nonCjkLength = text.length - cjkCount;
5225
+ return Math.ceil(cjkCount + nonCjkLength / 4);
5226
+ }
5227
+ var DEFAULT_TOKEN_BUDGETS = {
5228
+ gemini: 9e5,
5229
+ claude: 18e4,
5230
+ openai: 12e4,
5231
+ deepseek: 6e4,
5232
+ default: 1e5
5233
+ };
5234
+ function getDefaultBudget(providerName) {
5235
+ return DEFAULT_TOKEN_BUDGETS[providerName] ?? DEFAULT_TOKEN_BUDGETS.default;
5236
+ }
5237
+
5088
5238
  // core/dsl-extractor.ts
5089
5239
  function dslFilePath(specFilePath) {
5090
5240
  const dir = path4.dirname(specFilePath);
@@ -5243,7 +5393,9 @@ var ROUTING_LIBS = [
5243
5393
  ["@tanstack/react-router", "tanstack-router"],
5244
5394
  ["react-navigation", "react-navigation"],
5245
5395
  ["expo-router", "expo-router"],
5246
- ["vue-router", "vue-router"]
5396
+ ["vue-router", "vue-router"],
5397
+ ["@solidjs/router", "solid-router"],
5398
+ ["@builder.io/qwik-city", "qwik-city"]
5247
5399
  ];
5248
5400
  async function loadFrontendContext(projectRoot) {
5249
5401
  const ctx = {
@@ -5275,6 +5427,18 @@ async function loadFrontendContext(projectRoot) {
5275
5427
  const has = (name) => depKeys.includes(name);
5276
5428
  if (has("react-native") || has("expo")) {
5277
5429
  ctx.framework = "react-native";
5430
+ } else if (has("@sveltejs/kit")) {
5431
+ ctx.framework = "sveltekit";
5432
+ } else if (has("svelte")) {
5433
+ ctx.framework = "svelte";
5434
+ } else if (has("@builder.io/qwik")) {
5435
+ ctx.framework = "qwik";
5436
+ } else if (has("@remix-run/react") || has("@remix-run/node")) {
5437
+ ctx.framework = "remix";
5438
+ } else if (has("astro")) {
5439
+ ctx.framework = "astro";
5440
+ } else if (has("solid-js")) {
5441
+ ctx.framework = "solid";
5278
5442
  } else if (has("next")) {
5279
5443
  ctx.framework = "next";
5280
5444
  } else if (has("react")) {
@@ -5301,6 +5465,12 @@ async function loadFrontendContext(projectRoot) {
5301
5465
  if (ctx.framework === "next") {
5302
5466
  const hasAppDir = await fs7.pathExists(path5.join(projectRoot, "app"));
5303
5467
  ctx.routingPattern = hasAppDir ? "next-app-router" : "next-pages-router";
5468
+ } else if (ctx.framework === "sveltekit") {
5469
+ ctx.routingPattern = "sveltekit-file-router";
5470
+ } else if (ctx.framework === "remix") {
5471
+ ctx.routingPattern = "remix-file-router";
5472
+ } else if (ctx.framework === "astro") {
5473
+ ctx.routingPattern = "astro-file-router";
5304
5474
  } else {
5305
5475
  for (const [lib, label] of ROUTING_LIBS) {
5306
5476
  if (has(lib)) {
@@ -5686,13 +5856,14 @@ function getActiveSnapshot() {
5686
5856
 
5687
5857
  // core/run-logger.ts
5688
5858
  var fs9 = __toESM(require("fs-extra"));
5689
- var import_chalk7 = __toESM(require("chalk"));
5859
+ var import_chalk8 = __toESM(require("chalk"));
5690
5860
  var _activeLogger = null;
5691
5861
  function getActiveLogger() {
5692
5862
  return _activeLogger;
5693
5863
  }
5694
5864
 
5695
- // core/code-generator.ts
5865
+ // core/codegen/helpers.ts
5866
+ var import_child_process = require("child_process");
5696
5867
  function buildSharedConfigSection(context) {
5697
5868
  if (!context?.sharedConfigFiles || context.sharedConfigFiles.length === 0) return "";
5698
5869
  const lines = [
@@ -5824,7 +5995,7 @@ function buildGeneratedFilesSection(cache) {
5824
5995
  }
5825
5996
  function isRtkAvailable() {
5826
5997
  try {
5827
- (0, import_child_process.execSync)("rtk --version", { stdio: "ignore" });
5998
+ (0, import_child_process.execSync)("rtk --version", { stdio: "ignore", timeout: 1e4 });
5828
5999
  return true;
5829
6000
  } catch {
5830
6001
  return false;
@@ -5848,6 +6019,73 @@ function parseJsonArray(text) {
5848
6019
  }
5849
6020
  return [];
5850
6021
  }
6022
+
6023
+ // core/codegen/topo-sort.ts
6024
+ var import_chalk9 = __toESM(require("chalk"));
6025
+ function topoSortLayerTasks(tasks) {
6026
+ if (tasks.length <= 1) return [tasks];
6027
+ const idSet = new Set(tasks.map((t) => t.id));
6028
+ const taskById = new Map(tasks.map((t) => [t.id, t]));
6029
+ const inDegree = /* @__PURE__ */ new Map();
6030
+ const dependents = /* @__PURE__ */ new Map();
6031
+ for (const task of tasks) {
6032
+ inDegree.set(task.id, 0);
6033
+ dependents.set(task.id, []);
6034
+ }
6035
+ for (const task of tasks) {
6036
+ const intraDeps = task.dependencies.filter((dep) => idSet.has(dep));
6037
+ inDegree.set(task.id, intraDeps.length);
6038
+ for (const dep of intraDeps) {
6039
+ dependents.get(dep).push(task.id);
6040
+ }
6041
+ }
6042
+ const batches = [];
6043
+ const remaining = new Set(tasks.map((t) => t.id));
6044
+ while (remaining.size > 0) {
6045
+ const batch = [...remaining].filter((id) => inDegree.get(id) === 0).map((id) => taskById.get(id));
6046
+ if (batch.length === 0) {
6047
+ batches.push([...remaining].map((id) => taskById.get(id)));
6048
+ break;
6049
+ }
6050
+ batches.push(batch);
6051
+ for (const task of batch) {
6052
+ remaining.delete(task.id);
6053
+ for (const dependent of dependents.get(task.id)) {
6054
+ inDegree.set(dependent, inDegree.get(dependent) - 1);
6055
+ }
6056
+ }
6057
+ }
6058
+ return batches;
6059
+ }
6060
+ var LAYER_ICONS = {
6061
+ data: "\u{1F4BE}",
6062
+ infra: "\u2699\uFE0F ",
6063
+ service: "\u{1F527}",
6064
+ api: "\u{1F310}",
6065
+ view: "\u{1F5A5}\uFE0F ",
6066
+ route: "\u{1F5FA}\uFE0F ",
6067
+ test: "\u{1F9EA}"
6068
+ };
6069
+ function printTaskProgress(completed, total, task, mode) {
6070
+ const pct = total > 0 ? Math.round(completed / total * 100) : 0;
6071
+ const barWidth = 20;
6072
+ const filled = Math.round(pct / 100 * barWidth);
6073
+ const bar = import_chalk9.default.green("\u2588".repeat(filled)) + import_chalk9.default.gray("\u2591".repeat(barWidth - filled));
6074
+ const icon = LAYER_ICONS[task.layer] ?? " ";
6075
+ if (mode === "skip") {
6076
+ console.log(
6077
+ import_chalk9.default.gray(`
6078
+ [${bar}] ${pct}% \u2713 ${task.id} ${icon} ${task.title} \u2014 already done`)
6079
+ );
6080
+ } else {
6081
+ console.log(
6082
+ import_chalk9.default.bold(`
6083
+ [${bar}] ${pct}% \u2192 ${task.id} ${icon} ${task.title}`)
6084
+ );
6085
+ }
6086
+ }
6087
+
6088
+ // core/code-generator.ts
5851
6089
  var CodeGenerator = class {
5852
6090
  constructor(provider, mode = "claude-code") {
5853
6091
  this.provider = provider;
@@ -5858,13 +6096,13 @@ var CodeGenerator = class {
5858
6096
  let effectiveMode = this.mode;
5859
6097
  if (effectiveMode === "claude-code" && this.provider.providerName !== "claude") {
5860
6098
  console.log(
5861
- import_chalk8.default.yellow(
6099
+ import_chalk10.default.yellow(
5862
6100
  `
5863
6101
  \u26A0 codegen \u6A21\u5F0F "claude-code" \u9700\u8981 Claude\uFF0C\u4F46\u5F53\u524D provider \u662F "${this.provider.providerName}"\u3002`
5864
6102
  )
5865
6103
  );
5866
- console.log(import_chalk8.default.gray(` \u81EA\u52A8\u5207\u6362\u5230 "api" \u6A21\u5F0F\uFF08\u4F7F\u7528 ${this.provider.providerName}/${this.provider.modelName} \u751F\u6210\u4EE3\u7801\uFF09\u3002`));
5867
- console.log(import_chalk8.default.gray(` \u63D0\u793A\uFF1A\u8FD0\u884C \`ai-spec config --codegen api\` \u53EF\u56FA\u5316\u6B64\u8BBE\u7F6E\u3002
6104
+ console.log(import_chalk10.default.gray(` \u81EA\u52A8\u5207\u6362\u5230 "api" \u6A21\u5F0F\uFF08\u4F7F\u7528 ${this.provider.providerName}/${this.provider.modelName} \u751F\u6210\u4EE3\u7801\uFF09\u3002`));
6105
+ console.log(import_chalk10.default.gray(` \u63D0\u793A\uFF1A\u8FD0\u884C \`ai-spec config --codegen api\` \u53EF\u56FA\u5316\u6B64\u8BBE\u7F6E\u3002
5868
6106
  `));
5869
6107
  effectiveMode = "api";
5870
6108
  }
@@ -5882,23 +6120,23 @@ var CodeGenerator = class {
5882
6120
  // ── Mode: claude-code ──────────────────────────────────────────────────────
5883
6121
  isClaudeCLIAvailable() {
5884
6122
  try {
5885
- (0, import_child_process.execSync)("claude --version", { stdio: "ignore" });
6123
+ (0, import_child_process2.execSync)("claude --version", { stdio: "ignore", timeout: 1e4 });
5886
6124
  return true;
5887
6125
  } catch {
5888
6126
  return false;
5889
6127
  }
5890
6128
  }
5891
6129
  async runClaudeCode(specFilePath, workingDir, options = {}) {
5892
- console.log(import_chalk8.default.blue("\n\u2500\u2500\u2500 Code Generation: Claude Code CLI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6130
+ console.log(import_chalk10.default.blue("\n\u2500\u2500\u2500 Code Generation: Claude Code CLI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
5893
6131
  if (!this.isClaudeCLIAvailable()) {
5894
- console.log(import_chalk8.default.yellow(" \u26A0\uFE0F Claude Code CLI not found. Falling back to plan mode."));
5895
- console.log(import_chalk8.default.gray(" Install: npm install -g @anthropic-ai/claude-code"));
6132
+ console.log(import_chalk10.default.yellow(" \u26A0\uFE0F Claude Code CLI not found. Falling back to plan mode."));
6133
+ console.log(import_chalk10.default.gray(" Install: npm install -g @anthropic-ai/claude-code"));
5896
6134
  return this.runPlanMode(specFilePath);
5897
6135
  }
5898
6136
  const rtkAvailable = isRtkAvailable();
5899
6137
  const claudeCmd = rtkAvailable ? "rtk claude" : "claude";
5900
6138
  if (rtkAvailable) {
5901
- console.log(import_chalk8.default.green(" \u2713 RTK detected \u2014 using rtk claude for token savings"));
6139
+ console.log(import_chalk10.default.green(" \u2713 RTK detected \u2014 using rtk claude for token savings"));
5902
6140
  }
5903
6141
  const tasks = await loadTasksForSpec(specFilePath);
5904
6142
  if (options.auto && tasks && tasks.length > 0) {
@@ -5914,28 +6152,28 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
5914
6152
  const promptFile = path6.join(workingDir, ".claude-prompt.txt");
5915
6153
  await fs10.writeFile(promptFile, promptContent, "utf-8");
5916
6154
  if (options.auto) {
5917
- console.log(import_chalk8.default.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
5918
- console.log(import_chalk8.default.gray(` Spec: ${specFilePath}`));
6155
+ console.log(import_chalk10.default.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
6156
+ console.log(import_chalk10.default.gray(` Spec: ${specFilePath}`));
5919
6157
  try {
5920
- (0, import_child_process.spawnSync)(claudeCmd, ["-p", promptContent], {
6158
+ (0, import_child_process2.spawnSync)(claudeCmd, ["-p", promptContent], {
5921
6159
  cwd: workingDir,
5922
6160
  stdio: "inherit",
5923
6161
  shell: false
5924
6162
  });
5925
- console.log(import_chalk8.default.green("\n \u2714 Claude Code completed."));
6163
+ console.log(import_chalk10.default.green("\n \u2714 Claude Code completed."));
5926
6164
  } catch {
5927
- console.log(import_chalk8.default.yellow("\n Claude Code exited. Check output above."));
6165
+ console.log(import_chalk10.default.yellow("\n Claude Code exited. Check output above."));
5928
6166
  }
5929
6167
  } else {
5930
- console.log(import_chalk8.default.cyan(` \u{1F680} Launching ${claudeCmd} in: ${workingDir}`));
5931
- console.log(import_chalk8.default.gray(` Spec: ${specFilePath}`));
5932
- if (tasks) console.log(import_chalk8.default.gray(` Tasks: ${tasks.length} tasks loaded into .claude-prompt.txt`));
5933
- console.log(import_chalk8.default.gray(" Prompt pre-loaded in .claude-prompt.txt\n"));
6168
+ console.log(import_chalk10.default.cyan(` \u{1F680} Launching ${claudeCmd} in: ${workingDir}`));
6169
+ console.log(import_chalk10.default.gray(` Spec: ${specFilePath}`));
6170
+ if (tasks) console.log(import_chalk10.default.gray(` Tasks: ${tasks.length} tasks loaded into .claude-prompt.txt`));
6171
+ console.log(import_chalk10.default.gray(" Prompt pre-loaded in .claude-prompt.txt\n"));
5934
6172
  try {
5935
- (0, import_child_process.execSync)(claudeCmd, { cwd: workingDir, stdio: "inherit" });
5936
- console.log(import_chalk8.default.green("\n \u2714 Claude Code session completed."));
6173
+ (0, import_child_process2.execSync)(claudeCmd, { cwd: workingDir, stdio: "inherit" });
6174
+ console.log(import_chalk10.default.green("\n \u2714 Claude Code session completed."));
5937
6175
  } catch {
5938
- console.log(import_chalk8.default.yellow("\n Claude Code session ended. Continuing workflow."));
6176
+ console.log(import_chalk10.default.yellow("\n Claude Code session ended. Continuing workflow."));
5939
6177
  }
5940
6178
  }
5941
6179
  }
@@ -5948,10 +6186,10 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
5948
6186
  const pending = tasks.filter((t) => t.status !== "done");
5949
6187
  const doneCount = tasks.length - pending.length;
5950
6188
  if (options.resume && doneCount > 0) {
5951
- console.log(import_chalk8.default.cyan(`
6189
+ console.log(import_chalk10.default.cyan(`
5952
6190
  Resuming: ${doneCount}/${tasks.length} tasks already done \u2014 skipping.`));
5953
6191
  } else {
5954
- console.log(import_chalk8.default.cyan(`
6192
+ console.log(import_chalk10.default.cyan(`
5955
6193
  Incremental mode: ${tasks.length} tasks`));
5956
6194
  }
5957
6195
  let completed = doneCount;
@@ -5972,7 +6210,7 @@ Full spec is at: ${specFilePath}
5972
6210
  Implement ONLY this task. Do not implement other tasks.`;
5973
6211
  let taskStatus = "done";
5974
6212
  try {
5975
- (0, import_child_process.spawnSync)(claudeCmd, ["-p", taskPrompt], {
6213
+ (0, import_child_process2.spawnSync)(claudeCmd, ["-p", taskPrompt], {
5976
6214
  cwd: workingDir,
5977
6215
  stdio: "inherit",
5978
6216
  shell: false
@@ -5980,33 +6218,33 @@ Implement ONLY this task. Do not implement other tasks.`;
5980
6218
  completed++;
5981
6219
  } catch {
5982
6220
  taskStatus = "failed";
5983
- console.log(import_chalk8.default.yellow(`
6221
+ console.log(import_chalk10.default.yellow(`
5984
6222
  \u26A0 Task ${task.id} exited with error \u2014 marked as failed. Re-run with --resume to retry.`));
5985
6223
  }
5986
6224
  await updateTaskStatus(specFilePath, task.id, taskStatus);
5987
6225
  }
5988
6226
  const successCount = tasks.filter((t) => t.status === "done").length + (completed - doneCount);
5989
6227
  console.log(
5990
- import_chalk8.default.bold(
6228
+ import_chalk10.default.bold(
5991
6229
  `
5992
- ${successCount === tasks.length ? import_chalk8.default.green("\u2714") : import_chalk8.default.yellow("!")} Incremental build: ${completed}/${tasks.length} tasks completed.`
6230
+ ${successCount === tasks.length ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!")} Incremental build: ${completed}/${tasks.length} tasks completed.`
5993
6231
  )
5994
6232
  );
5995
6233
  }
5996
6234
  // ── Mode: api ─────────────────────────────────────────────────────────────
5997
6235
  async runApiMode(specFilePath, workingDir, context, options = {}) {
5998
6236
  console.log(
5999
- import_chalk8.default.blue(
6237
+ import_chalk10.default.blue(
6000
6238
  `
6001
6239
  \u2500\u2500\u2500 Code Generation: API (${this.provider.providerName}/${this.provider.modelName}) \u2500\u2500\u2500`
6002
6240
  )
6003
6241
  );
6004
6242
  const systemPrompt = getCodeGenSystemPrompt(options.repoType);
6005
6243
  if (options.repoType && options.repoType !== "node-express" && options.repoType !== "node-koa" && options.repoType !== "unknown") {
6006
- console.log(import_chalk8.default.gray(` Language: ${options.repoType} (using language-specific codegen prompt)`));
6244
+ console.log(import_chalk10.default.gray(` Language: ${options.repoType} (using language-specific codegen prompt)`));
6007
6245
  }
6008
6246
  const spec = await fs10.readFile(specFilePath, "utf-8");
6009
- const constitutionSection = context?.constitution ? `
6247
+ let constitutionSection = context?.constitution ? `
6010
6248
  === Project Constitution (MUST follow) ===
6011
6249
  ${context.constitution}
6012
6250
  ` : "";
@@ -6021,7 +6259,7 @@ ${buildDslContextSection(dsl)}
6021
6259
  if (dsl) {
6022
6260
  const cmpCount = dsl.components?.length ?? 0;
6023
6261
  const cmpSuffix = cmpCount > 0 ? `, ${cmpCount} components` : "";
6024
- console.log(import_chalk8.default.green(` \u2713 DSL loaded \u2014 ${dsl.endpoints.length} endpoints, ${dsl.models.length} models${cmpSuffix}`));
6262
+ console.log(import_chalk10.default.green(` \u2713 DSL loaded \u2014 ${dsl.endpoints.length} endpoints, ${dsl.models.length} models${cmpSuffix}`));
6025
6263
  }
6026
6264
  const isFrontend = isFrontendDeps(context?.dependencies ?? []);
6027
6265
  let frontendSection = "";
@@ -6030,13 +6268,30 @@ ${buildDslContextSection(dsl)}
6030
6268
  frontendSection = `
6031
6269
  ${buildFrontendContextSection(fctx)}
6032
6270
  `;
6033
- console.log(import_chalk8.default.gray(` Frontend context: ${fctx.framework} / ${fctx.httpClient} | hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
6271
+ console.log(import_chalk10.default.gray(` Frontend context: ${fctx.framework} / ${fctx.httpClient} | hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
6272
+ }
6273
+ const allContextText = spec + constitutionSection + dslSection + frontendSection + installedPackagesSection + sharedConfigSection;
6274
+ const estimatedTokenCount = estimateTokens(allContextText);
6275
+ const budget = getDefaultBudget(this.provider.providerName);
6276
+ if (estimatedTokenCount > budget * 0.7) {
6277
+ console.log(
6278
+ import_chalk10.default.yellow(
6279
+ ` \u26A0 Context size: ~${Math.round(estimatedTokenCount / 1e3)}K tokens (budget: ${Math.round(budget / 1e3)}K for ${this.provider.providerName})`
6280
+ )
6281
+ );
6282
+ if (constitutionSection.length > 4e3) {
6283
+ const s9Start = constitutionSection.indexOf("## 9.");
6284
+ if (s9Start > 0) {
6285
+ 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";
6286
+ console.log(import_chalk10.default.gray(" \u2192 \xA79 trimmed from constitution to save tokens."));
6287
+ }
6288
+ }
6034
6289
  }
6035
6290
  const tasks = await loadTasksForSpec(specFilePath);
6036
6291
  if (tasks && tasks.length > 0) {
6037
6292
  return this.runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection + dslSection + installedPackagesSection, frontendSection, sharedConfigSection, options, systemPrompt, context);
6038
6293
  }
6039
- console.log(import_chalk8.default.gray(" [1/2] Planning implementation files..."));
6294
+ console.log(import_chalk10.default.gray(" [1/2] Planning implementation files..."));
6040
6295
  const planPrompt = `Based on the feature spec and project context below, list ALL files that need to be created or modified.
6041
6296
 
6042
6297
  IMPORTANT: Check the "Existing Shared Config Files" section below FIRST. For any file listed there,
@@ -6059,18 +6314,18 @@ Output ONLY a valid JSON array:
6059
6314
  const planResponse = await this.provider.generate(planPrompt, systemPrompt);
6060
6315
  filePlan = parseJsonArray(planResponse);
6061
6316
  } catch (err) {
6062
- console.error(import_chalk8.default.red(" Failed to generate file plan:"), err);
6317
+ console.error(import_chalk10.default.red(" Failed to generate file plan:"), err);
6063
6318
  }
6064
6319
  if (filePlan.length === 0) {
6065
- console.log(import_chalk8.default.yellow(" Could not determine file plan. Falling back to plan mode."));
6320
+ console.log(import_chalk10.default.yellow(" Could not determine file plan. Falling back to plan mode."));
6066
6321
  await this.runPlanMode(specFilePath);
6067
6322
  return [];
6068
6323
  }
6069
- console.log(import_chalk8.default.cyan(`
6324
+ console.log(import_chalk10.default.cyan(`
6070
6325
  Plan: ${filePlan.length} file(s) to process`));
6071
6326
  filePlan.forEach((item) => {
6072
- const icon = item.action === "create" ? import_chalk8.default.green("+") : import_chalk8.default.yellow("~");
6073
- console.log(` ${icon} ${item.file}: ${import_chalk8.default.gray(item.description)}`);
6327
+ const icon = item.action === "create" ? import_chalk10.default.green("+") : import_chalk10.default.yellow("~");
6328
+ console.log(` ${icon} ${item.file}: ${import_chalk10.default.gray(item.description)}`);
6074
6329
  });
6075
6330
  const { files } = await this.generateFiles(filePlan, spec, workingDir, constitutionSection + dslSection + frontendSection + installedPackagesSection, systemPrompt);
6076
6331
  return files;
@@ -6079,13 +6334,13 @@ Output ONLY a valid JSON array:
6079
6334
  const pendingTasks = tasks.filter((t) => t.status !== "done");
6080
6335
  const doneCount = tasks.length - pendingTasks.length;
6081
6336
  if (options.resume && doneCount > 0) {
6082
- console.log(import_chalk8.default.cyan(`
6083
- Task-based generation (resume): ${tasks.length} tasks (${import_chalk8.default.green(doneCount + " already done")}, skipping)`));
6337
+ console.log(import_chalk10.default.cyan(`
6338
+ Task-based generation (resume): ${tasks.length} tasks (${import_chalk10.default.green(doneCount + " already done")}, skipping)`));
6084
6339
  } else if (doneCount > 0) {
6085
- console.log(import_chalk8.default.cyan(`
6086
- Task-based generation: ${tasks.length} tasks (${import_chalk8.default.green(doneCount + " already done")}, resuming from checkpoint)`));
6340
+ console.log(import_chalk10.default.cyan(`
6341
+ Task-based generation: ${tasks.length} tasks (${import_chalk10.default.green(doneCount + " already done")}, resuming from checkpoint)`));
6087
6342
  } else {
6088
- console.log(import_chalk8.default.cyan(`
6343
+ console.log(import_chalk10.default.cyan(`
6089
6344
  Task-based generation: ${tasks.length} tasks`));
6090
6345
  }
6091
6346
  const sharedConfigPaths = new Set(
@@ -6117,9 +6372,9 @@ Output ONLY a valid JSON array:
6117
6372
  const pct = Math.round(completedTasks / tasks.length * 100);
6118
6373
  const barWidth = 20;
6119
6374
  const filled = Math.round(pct / 100 * barWidth);
6120
- const bar = import_chalk8.default.green("\u2588".repeat(filled)) + import_chalk8.default.gray("\u2591".repeat(barWidth - filled));
6375
+ const bar = import_chalk10.default.green("\u2588".repeat(filled)) + import_chalk10.default.gray("\u2591".repeat(barWidth - filled));
6121
6376
  console.log(
6122
- import_chalk8.default.bold(`
6377
+ import_chalk10.default.bold(`
6123
6378
  [${bar}] ${pct}% \u26A1 Layer [${layer}] ${layerIcon} \u2014 ${layerTasks.length} tasks running in parallel`)
6124
6379
  );
6125
6380
  } else {
@@ -6127,7 +6382,7 @@ Output ONLY a valid JSON array:
6127
6382
  }
6128
6383
  const executeTask = async (task, batchIsParallel) => {
6129
6384
  if (task.filesToTouch.length === 0) {
6130
- if (!batchIsParallel) console.log(import_chalk8.default.gray(" No files specified, skipping."));
6385
+ if (!batchIsParallel) console.log(import_chalk10.default.gray(" No files specified, skipping."));
6131
6386
  return { task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false };
6132
6387
  }
6133
6388
  const filePlan = await Promise.all(
@@ -6184,13 +6439,19 @@ ${taskContext}`,
6184
6439
  const layerResults = [];
6185
6440
  for (const batch of taskBatches) {
6186
6441
  const batchIsParallel = batch.length > 1;
6187
- const batchResultPromises = batch.map(
6188
- (task) => executeTask(task, batchIsParallel).catch((err) => {
6189
- console.log(import_chalk8.default.yellow(` \u26A0 ${task.id} threw unexpectedly: ${err.message}`));
6190
- return { task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false };
6191
- })
6192
- );
6193
- const batchResults = await Promise.all(batchResultPromises);
6442
+ const batchResultPromises = batch.map((task) => executeTask(task, batchIsParallel));
6443
+ const settled = await Promise.allSettled(batchResultPromises);
6444
+ const batchResults = [];
6445
+ for (let i = 0; i < settled.length; i++) {
6446
+ const outcome = settled[i];
6447
+ if (outcome.status === "fulfilled") {
6448
+ batchResults.push(outcome.value);
6449
+ } else {
6450
+ const task = batch[i];
6451
+ console.log(import_chalk10.default.yellow(` \u26A0 ${task.id} threw unexpectedly: ${outcome.reason?.message ?? outcome.reason}`));
6452
+ batchResults.push({ task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false });
6453
+ }
6454
+ }
6194
6455
  layerResults.push(...batchResults);
6195
6456
  await updateCacheFromBatch(batchResults);
6196
6457
  }
@@ -6202,14 +6463,14 @@ ${taskContext}`,
6202
6463
  totalFiles += result.total;
6203
6464
  allGeneratedFiles.push(...result.files);
6204
6465
  if (isParallel) {
6205
- const icon = result.success === result.total ? import_chalk8.default.green("\u2714") : import_chalk8.default.yellow("!");
6466
+ const icon = result.success === result.total ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!");
6206
6467
  const layerTaskIcon = LAYER_ICONS[result.task.layer] ?? " ";
6207
6468
  console.log(` ${icon} ${result.task.id} ${layerTaskIcon} ${result.task.title} \u2014 ${result.success}/${result.total} files`);
6208
6469
  }
6209
6470
  const taskStatus = result.success === result.total ? "done" : "failed";
6210
6471
  await updateTaskStatus(specFilePath, result.task.id, taskStatus);
6211
6472
  if (taskStatus === "failed") {
6212
- console.log(import_chalk8.default.yellow(` \u26A0 ${result.task.id} marked as failed \u2014 re-run with --resume to retry`));
6473
+ console.log(import_chalk10.default.yellow(` \u26A0 ${result.task.id} marked as failed \u2014 re-run with --resume to retry`));
6213
6474
  }
6214
6475
  }
6215
6476
  completedTasks += layerTasks.length;
@@ -6224,7 +6485,7 @@ ${taskContext}`,
6224
6485
  if ((sharedFile.category === "route-index" || sharedFile.category === "store-index") && newModuleNames.length > 0) {
6225
6486
  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.`;
6226
6487
  }
6227
- console.log(import_chalk8.default.gray(`
6488
+ console.log(import_chalk10.default.gray(`
6228
6489
  + updating shared config: ${sharedFile.path} [${sharedFile.category}]`));
6229
6490
  const updatedGeneratedFilesSection = buildGeneratedFilesSection(generatedFileCache);
6230
6491
  await this.generateFiles(
@@ -6242,17 +6503,17 @@ Updating shared registration after layer [${layer}] completed. New modules: ${ne
6242
6503
  }
6243
6504
  }
6244
6505
  console.log(
6245
- import_chalk8.default.bold(
6506
+ import_chalk10.default.bold(
6246
6507
  `
6247
- ${totalSuccess === totalFiles ? import_chalk8.default.green("\u2714") : import_chalk8.default.yellow("!")} Task-based generation: ${totalSuccess}/${totalFiles} files written across ${pendingTasks.length} tasks.`
6508
+ ${totalSuccess === totalFiles ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!")} Task-based generation: ${totalSuccess}/${totalFiles} files written across ${pendingTasks.length} tasks.`
6248
6509
  )
6249
6510
  );
6250
6511
  return allGeneratedFiles;
6251
6512
  }
6252
6513
  async generateFiles(filePlan, spec, workingDir, constitutionSection, systemPrompt = getCodeGenSystemPrompt(), taskLabel) {
6253
- const prefix = taskLabel ? ` [${import_chalk8.default.cyan(taskLabel)}] ` : " ";
6514
+ const prefix = taskLabel ? ` [${import_chalk10.default.cyan(taskLabel)}] ` : " ";
6254
6515
  if (!taskLabel) {
6255
- console.log(import_chalk8.default.gray(`
6516
+ console.log(import_chalk10.default.gray(`
6256
6517
  Generating ${filePlan.length} file(s)...`));
6257
6518
  }
6258
6519
  let successCount = 0;
@@ -6273,6 +6534,7 @@ ${spec}
6273
6534
  ${constitutionSection}
6274
6535
  === ${existingContent ? "Existing content (modify and return the complete file)" : "Create this file from scratch"} ===
6275
6536
  ${existingContent || "Output only the complete file content."}`;
6537
+ const fileSpinner = startSpinner(`${prefix}Generating ${import_chalk10.default.bold(item.file)}...`);
6276
6538
  try {
6277
6539
  const raw = await this.provider.generate(codePrompt, systemPrompt);
6278
6540
  const fileContent = stripCodeFences(raw);
@@ -6280,17 +6542,17 @@ ${existingContent || "Output only the complete file content."}`;
6280
6542
  await fs10.ensureDir(path6.dirname(fullPath));
6281
6543
  await fs10.writeFile(fullPath, fileContent, "utf-8");
6282
6544
  getActiveLogger()?.fileWritten(item.file);
6283
- console.log(`${prefix}${existingContent ? import_chalk8.default.yellow("~") : import_chalk8.default.green("+")} ${import_chalk8.default.bold(item.file)} ${import_chalk8.default.green("\u2714")}`);
6545
+ fileSpinner.succeed(`${existingContent ? import_chalk10.default.yellow("~") : import_chalk10.default.green("+")} ${import_chalk10.default.bold(item.file)}`);
6284
6546
  successCount++;
6285
6547
  writtenFiles.push(item.file);
6286
6548
  } catch (err) {
6287
- console.log(`${prefix}${import_chalk8.default.red("\u2718")} ${import_chalk8.default.bold(item.file)} \u2014 ${import_chalk8.default.red(err.message)}`);
6549
+ fileSpinner.fail(`${import_chalk10.default.bold(item.file)} \u2014 ${err.message}`);
6288
6550
  }
6289
6551
  }
6290
6552
  if (!taskLabel) {
6291
6553
  console.log(
6292
- import_chalk8.default.bold(
6293
- ` ${successCount === filePlan.length ? import_chalk8.default.green("\u2714") : import_chalk8.default.yellow("!")} ${successCount}/${filePlan.length} files written.`
6554
+ import_chalk10.default.bold(
6555
+ ` ${successCount === filePlan.length ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!")} ${successCount}/${filePlan.length} files written.`
6294
6556
  )
6295
6557
  );
6296
6558
  }
@@ -6298,7 +6560,7 @@ ${existingContent || "Output only the complete file content."}`;
6298
6560
  }
6299
6561
  // ── Mode: plan ─────────────────────────────────────────────────────────────
6300
6562
  async runPlanMode(specFilePath) {
6301
- console.log(import_chalk8.default.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"));
6563
+ console.log(import_chalk10.default.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"));
6302
6564
  const spec = await fs10.readFile(specFilePath, "utf-8");
6303
6565
  const plan = await this.provider.generate(
6304
6566
  `Create a detailed, step-by-step implementation plan for the following feature spec.
@@ -6311,80 +6573,18 @@ Be specific about:
6311
6573
  ${spec}`,
6312
6574
  "You are a senior developer creating an actionable implementation guide."
6313
6575
  );
6314
- console.log(import_chalk8.default.cyan("\n") + plan);
6315
- }
6316
- };
6317
- function topoSortLayerTasks(tasks) {
6318
- if (tasks.length <= 1) return [tasks];
6319
- const idSet = new Set(tasks.map((t) => t.id));
6320
- const taskById = new Map(tasks.map((t) => [t.id, t]));
6321
- const inDegree = /* @__PURE__ */ new Map();
6322
- const dependents = /* @__PURE__ */ new Map();
6323
- for (const task of tasks) {
6324
- inDegree.set(task.id, 0);
6325
- dependents.set(task.id, []);
6326
- }
6327
- for (const task of tasks) {
6328
- const intraDeps = task.dependencies.filter((dep) => idSet.has(dep));
6329
- inDegree.set(task.id, intraDeps.length);
6330
- for (const dep of intraDeps) {
6331
- dependents.get(dep).push(task.id);
6332
- }
6576
+ console.log(import_chalk10.default.cyan("\n") + plan);
6333
6577
  }
6334
- const batches = [];
6335
- const remaining = new Set(tasks.map((t) => t.id));
6336
- while (remaining.size > 0) {
6337
- const batch = [...remaining].filter((id) => inDegree.get(id) === 0).map((id) => taskById.get(id));
6338
- if (batch.length === 0) {
6339
- batches.push([...remaining].map((id) => taskById.get(id)));
6340
- break;
6341
- }
6342
- batches.push(batch);
6343
- for (const task of batch) {
6344
- remaining.delete(task.id);
6345
- for (const dependent of dependents.get(task.id)) {
6346
- inDegree.set(dependent, inDegree.get(dependent) - 1);
6347
- }
6348
- }
6349
- }
6350
- return batches;
6351
- }
6352
- var LAYER_ICONS = {
6353
- data: "\u{1F4BE}",
6354
- infra: "\u2699\uFE0F ",
6355
- service: "\u{1F527}",
6356
- api: "\u{1F310}",
6357
- view: "\u{1F5A5}\uFE0F ",
6358
- route: "\u{1F5FA}\uFE0F ",
6359
- test: "\u{1F9EA}"
6360
6578
  };
6361
- function printTaskProgress(completed, total, task, mode) {
6362
- const pct = total > 0 ? Math.round(completed / total * 100) : 0;
6363
- const barWidth = 20;
6364
- const filled = Math.round(pct / 100 * barWidth);
6365
- const bar = import_chalk8.default.green("\u2588".repeat(filled)) + import_chalk8.default.gray("\u2591".repeat(barWidth - filled));
6366
- const icon = LAYER_ICONS[task.layer] ?? " ";
6367
- if (mode === "skip") {
6368
- console.log(
6369
- import_chalk8.default.gray(`
6370
- [${bar}] ${pct}% \u2713 ${task.id} ${icon} ${task.title} \u2014 already done`)
6371
- );
6372
- } else {
6373
- console.log(
6374
- import_chalk8.default.bold(`
6375
- [${bar}] ${pct}% \u2192 ${task.id} ${icon} ${task.title}`)
6376
- );
6377
- }
6378
- }
6379
6579
 
6380
6580
  // core/reviewer.ts
6381
- var import_chalk10 = __toESM(require("chalk"));
6382
- var import_child_process2 = require("child_process");
6581
+ var import_chalk12 = __toESM(require("chalk"));
6582
+ var import_child_process3 = require("child_process");
6383
6583
  var path8 = __toESM(require("path"));
6384
6584
  var fs12 = __toESM(require("fs-extra"));
6385
6585
 
6386
6586
  // core/constitution-generator.ts
6387
- var import_chalk9 = __toESM(require("chalk"));
6587
+ var import_chalk11 = __toESM(require("chalk"));
6388
6588
  var fs11 = __toESM(require("fs-extra"));
6389
6589
  var path7 = __toESM(require("path"));
6390
6590
 
@@ -6534,7 +6734,7 @@ async function loadConstitution(projectRoot) {
6534
6734
  function printConstitutionHint(exists) {
6535
6735
  if (!exists) {
6536
6736
  console.log(
6537
- import_chalk9.default.yellow(
6737
+ import_chalk11.default.yellow(
6538
6738
  " \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
6539
6739
  )
6540
6740
  );
@@ -6618,16 +6818,16 @@ var CodeReviewer = class {
6618
6818
  this.projectRoot = projectRoot;
6619
6819
  }
6620
6820
  getGitDiff() {
6621
- const silent = { encoding: "utf-8", stdio: "pipe" };
6821
+ const silent = { encoding: "utf-8", stdio: "pipe", cwd: this.projectRoot, timeout: 3e4 };
6622
6822
  try {
6623
- (0, import_child_process2.execSync)("git rev-parse --is-inside-work-tree", silent);
6823
+ (0, import_child_process3.execSync)("git rev-parse --is-inside-work-tree", silent);
6624
6824
  } catch {
6625
6825
  return "";
6626
6826
  }
6627
6827
  try {
6628
- let diff = (0, import_child_process2.execSync)("git diff --cached", silent);
6629
- if (!diff.trim()) diff = (0, import_child_process2.execSync)("git diff HEAD", silent);
6630
- if (!diff.trim()) diff = (0, import_child_process2.execSync)("git diff", silent);
6828
+ let diff = (0, import_child_process3.execSync)("git diff --cached", silent);
6829
+ if (!diff.trim()) diff = (0, import_child_process3.execSync)("git diff HEAD", silent);
6830
+ if (!diff.trim()) diff = (0, import_child_process3.execSync)("git diff", silent);
6631
6831
  return diff;
6632
6832
  } catch {
6633
6833
  return "";
@@ -6652,7 +6852,7 @@ var CodeReviewer = class {
6652
6852
  async runThreePassReview(specContent, codeContext, specFile) {
6653
6853
  let complianceReview = "";
6654
6854
  if (specContent && specContent.trim() && specContent !== "(No spec \u2014 review for general code quality)") {
6655
- console.log(import_chalk10.default.gray(" Pass 0/3: Spec compliance check..."));
6855
+ console.log(import_chalk12.default.gray(" Pass 0/3: Spec compliance check..."));
6656
6856
  const compliancePrompt = `Check whether the implementation covers every requirement in the spec.
6657
6857
 
6658
6858
  === Feature Spec ===
@@ -6664,13 +6864,13 @@ ${codeContext}`;
6664
6864
  const complianceScore2 = extractComplianceScore(complianceReview);
6665
6865
  const missingCount = extractMissingCount(complianceReview);
6666
6866
  if (complianceScore2 > 0) {
6667
- const scoreColor = complianceScore2 >= 8 ? import_chalk10.default.green : complianceScore2 >= 6 ? import_chalk10.default.yellow : import_chalk10.default.red;
6867
+ const scoreColor = complianceScore2 >= 8 ? import_chalk12.default.green : complianceScore2 >= 6 ? import_chalk12.default.yellow : import_chalk12.default.red;
6668
6868
  console.log(
6669
- import_chalk10.default.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? import_chalk10.default.red(` \xB7 ${missingCount} missing requirement(s)`) : import_chalk10.default.green(" \xB7 all requirements covered"))
6869
+ import_chalk12.default.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? import_chalk12.default.red(` \xB7 ${missingCount} missing requirement(s)`) : import_chalk12.default.green(" \xB7 all requirements covered"))
6670
6870
  );
6671
6871
  }
6672
6872
  }
6673
- console.log(import_chalk10.default.gray(` Pass 1/3: Architecture review...`));
6873
+ console.log(import_chalk12.default.gray(` Pass 1/3: Architecture review...`));
6674
6874
  const accumulatedLessons = await loadAccumulatedLessons(this.projectRoot);
6675
6875
  const archPrompt = `Review the architecture of this change.
6676
6876
  ${complianceReview ? `
@@ -6687,7 +6887,7 @@ ${specContent || "(No spec \u2014 review for general code quality)"}
6687
6887
  === Code ===
6688
6888
  ${codeContext}`;
6689
6889
  const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
6690
- console.log(import_chalk10.default.gray(" Pass 2/3: Implementation review..."));
6890
+ console.log(import_chalk12.default.gray(" Pass 2/3: Implementation review..."));
6691
6891
  const history = await loadReviewHistory(this.projectRoot);
6692
6892
  const historyContext = buildHistoryContext(history);
6693
6893
  const implPrompt = `Review the implementation details of this change.
@@ -6702,7 +6902,7 @@ ${codeContext}
6702
6902
  ${archReview}
6703
6903
  ${historyContext}`;
6704
6904
  const implReview = await this.provider.generate(implPrompt, reviewImplementationSystemPrompt);
6705
- console.log(import_chalk10.default.gray(" Pass 3/3: Impact & complexity assessment..."));
6905
+ console.log(import_chalk12.default.gray(" Pass 3/3: Impact & complexity assessment..."));
6706
6906
  const impactPrompt = `Assess the impact and complexity of this change.
6707
6907
 
6708
6908
  === Feature Spec ===
@@ -6743,37 +6943,37 @@ ${sep}
6743
6943
  return combined;
6744
6944
  }
6745
6945
  async reviewCode(specContent, specFile) {
6746
- console.log(import_chalk10.default.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"));
6946
+ console.log(import_chalk12.default.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"));
6747
6947
  const diff = this.getGitDiff();
6748
6948
  if (!diff.trim()) {
6749
6949
  console.log(
6750
- import_chalk10.default.yellow(" No git diff found. Stage or commit changes first, then run review.")
6950
+ import_chalk12.default.yellow(" No git diff found. Stage or commit changes first, then run review.")
6751
6951
  );
6752
- console.log(import_chalk10.default.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
6952
+ console.log(import_chalk12.default.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
6753
6953
  return "No changes";
6754
6954
  }
6755
6955
  const { files, added, removed } = this.getDiffStats(diff);
6756
6956
  console.log(
6757
- import_chalk10.default.gray(` Diff: ${files} file(s), ${import_chalk10.default.green("+" + added)} ${import_chalk10.default.red("-" + removed)}`)
6957
+ import_chalk12.default.gray(` Diff: ${files} file(s), ${import_chalk12.default.green("+" + added)} ${import_chalk12.default.red("-" + removed)}`)
6758
6958
  );
6759
6959
  console.log(
6760
- import_chalk10.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6960
+ import_chalk12.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6761
6961
  );
6762
6962
  const codeContext = diff.slice(0, 1e4);
6763
6963
  const reviewResult = await this.runThreePassReview(specContent, codeContext, specFile);
6764
- console.log(import_chalk10.default.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"));
6964
+ console.log(import_chalk12.default.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"));
6765
6965
  console.log(reviewResult);
6766
- console.log(import_chalk10.default.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"));
6966
+ console.log(import_chalk12.default.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"));
6767
6967
  return reviewResult;
6768
6968
  }
6769
6969
  /**
6770
6970
  * Review directly from generated file contents (for api mode where git diff is empty).
6771
6971
  */
6772
6972
  async reviewFiles(specContent, filePaths, workingDir, specFile) {
6773
- console.log(import_chalk10.default.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6774
- console.log(import_chalk10.default.gray(` Reviewing ${filePaths.length} generated file(s)...`));
6973
+ console.log(import_chalk12.default.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6974
+ console.log(import_chalk12.default.gray(` Reviewing ${filePaths.length} generated file(s)...`));
6775
6975
  console.log(
6776
- import_chalk10.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6976
+ import_chalk12.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
6777
6977
  );
6778
6978
  let filesSection = "";
6779
6979
  for (const filePath of filePaths) {
@@ -6794,28 +6994,28 @@ ${content.slice(0, 3e3)}`;
6794
6994
  }
6795
6995
  }
6796
6996
  const reviewResult = await this.runThreePassReview(specContent, filesSection, specFile);
6797
- console.log(import_chalk10.default.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"));
6997
+ console.log(import_chalk12.default.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"));
6798
6998
  console.log(reviewResult);
6799
- console.log(import_chalk10.default.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"));
6999
+ console.log(import_chalk12.default.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"));
6800
7000
  return reviewResult;
6801
7001
  }
6802
7002
  /** Print score trend from history (last N reviews) */
6803
7003
  async printScoreTrend(limit = 5) {
6804
7004
  const history = await loadReviewHistory(this.projectRoot);
6805
7005
  if (history.length === 0) {
6806
- console.log(import_chalk10.default.gray(" No review history yet."));
7006
+ console.log(import_chalk12.default.gray(" No review history yet."));
6807
7007
  return;
6808
7008
  }
6809
7009
  const recent = history.slice(-limit);
6810
- console.log(import_chalk10.default.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"));
7010
+ console.log(import_chalk12.default.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"));
6811
7011
  for (const entry of recent) {
6812
7012
  const bar = "\u2588".repeat(entry.score) + "\u2591".repeat(10 - entry.score);
6813
- const color = entry.score >= 8 ? import_chalk10.default.green : entry.score >= 6 ? import_chalk10.default.yellow : import_chalk10.default.red;
6814
- const impactTag = entry.impactLevel ? import_chalk10.default.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? import_chalk10.default.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? import_chalk10.default.yellow(entry.impactLevel) : import_chalk10.default.green(entry.impactLevel)}`) : "";
6815
- const complexityTag = entry.complexityLevel ? import_chalk10.default.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? import_chalk10.default.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? import_chalk10.default.yellow(entry.complexityLevel) : import_chalk10.default.green(entry.complexityLevel)}`) : "";
7013
+ const color = entry.score >= 8 ? import_chalk12.default.green : entry.score >= 6 ? import_chalk12.default.yellow : import_chalk12.default.red;
7014
+ const impactTag = entry.impactLevel ? import_chalk12.default.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? import_chalk12.default.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? import_chalk12.default.yellow(entry.impactLevel) : import_chalk12.default.green(entry.impactLevel)}`) : "";
7015
+ const complexityTag = entry.complexityLevel ? import_chalk12.default.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? import_chalk12.default.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? import_chalk12.default.yellow(entry.complexityLevel) : import_chalk12.default.green(entry.complexityLevel)}`) : "";
6816
7016
  console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
6817
7017
  }
6818
- console.log(import_chalk10.default.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"));
7018
+ console.log(import_chalk12.default.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"));
6819
7019
  }
6820
7020
  };
6821
7021
 
@@ -6865,17 +7065,17 @@ function parseSpecAndTasks(raw) {
6865
7065
  }
6866
7066
 
6867
7067
  // git/worktree.ts
6868
- var import_child_process3 = require("child_process");
7068
+ var import_child_process4 = require("child_process");
6869
7069
  var path9 = __toESM(require("path"));
6870
7070
  var fs13 = __toESM(require("fs-extra"));
6871
- var import_chalk11 = __toESM(require("chalk"));
7071
+ var import_chalk13 = __toESM(require("chalk"));
6872
7072
  var GitWorktreeManager = class {
6873
7073
  constructor(baseDir) {
6874
7074
  this.baseDir = baseDir;
6875
7075
  }
6876
7076
  isGitRepo() {
6877
7077
  try {
6878
- (0, import_child_process3.execSync)("git rev-parse --is-inside-work-tree", { cwd: this.baseDir, stdio: "ignore" });
7078
+ (0, import_child_process4.execSync)("git rev-parse --is-inside-work-tree", { cwd: this.baseDir, stdio: "ignore" });
6879
7079
  return true;
6880
7080
  } catch {
6881
7081
  return false;
@@ -6899,58 +7099,58 @@ var GitWorktreeManager = class {
6899
7099
  if (await fs13.pathExists(dest)) continue;
6900
7100
  try {
6901
7101
  await fs13.ensureSymlink(src, dest, "dir");
6902
- console.log(import_chalk11.default.gray(` Symlinked ${dir}/ from base repo \u2192 worktree`));
7102
+ console.log(import_chalk13.default.gray(` Symlinked ${dir}/ from base repo \u2192 worktree`));
6903
7103
  } catch (err) {
6904
- console.log(import_chalk11.default.yellow(` \u26A0 Could not symlink ${dir}/: ${err.message}`));
6905
- console.log(import_chalk11.default.yellow(` Run \`npm install\` inside the worktree manually.`));
7104
+ console.log(import_chalk13.default.yellow(` \u26A0 Could not symlink ${dir}/: ${err.message}`));
7105
+ console.log(import_chalk13.default.yellow(` Run \`npm install\` inside the worktree manually.`));
6906
7106
  }
6907
7107
  }
6908
7108
  }
6909
7109
  async createWorktree(idea) {
6910
7110
  if (!this.isGitRepo()) {
6911
- console.log(import_chalk11.default.yellow("\u26A0\uFE0F Not a git repository. Skipping worktree creation."));
7111
+ console.log(import_chalk13.default.yellow("\u26A0\uFE0F Not a git repository. Skipping worktree creation."));
6912
7112
  return null;
6913
7113
  }
6914
7114
  const featureName = this.sanitizeFeatureName(idea);
6915
7115
  const branchName = `feature/${featureName}`;
6916
7116
  const repoName = path9.basename(this.baseDir);
6917
7117
  const worktreePath = path9.resolve(this.baseDir, "..", `${repoName}-${featureName}`);
6918
- console.log(import_chalk11.default.cyan(`
7118
+ console.log(import_chalk13.default.cyan(`
6919
7119
  --- Setting up Git Worktree ---`));
6920
7120
  if (await fs13.pathExists(worktreePath)) {
6921
- console.log(import_chalk11.default.yellow(`\u26A0\uFE0F Worktree directory already exists at: ${worktreePath}`));
7121
+ console.log(import_chalk13.default.yellow(`\u26A0\uFE0F Worktree directory already exists at: ${worktreePath}`));
6922
7122
  await this.linkDependencies(worktreePath);
6923
7123
  return worktreePath;
6924
7124
  }
6925
7125
  try {
6926
7126
  let branchExists = false;
6927
7127
  try {
6928
- (0, import_child_process3.execSync)(`git show-ref --verify refs/heads/${branchName}`, {
7128
+ (0, import_child_process4.execSync)(`git show-ref --verify refs/heads/${branchName}`, {
6929
7129
  cwd: this.baseDir,
6930
7130
  stdio: "ignore"
6931
7131
  });
6932
7132
  branchExists = true;
6933
7133
  } catch {
6934
7134
  }
6935
- console.log(import_chalk11.default.gray(`Creating worktree at: ${worktreePath}`));
7135
+ console.log(import_chalk13.default.gray(`Creating worktree at: ${worktreePath}`));
6936
7136
  if (branchExists) {
6937
- (0, import_child_process3.execSync)(`git worktree add "${worktreePath}" ${branchName}`, {
7137
+ (0, import_child_process4.execSync)(`git worktree add "${worktreePath}" ${branchName}`, {
6938
7138
  cwd: this.baseDir,
6939
7139
  stdio: "inherit"
6940
7140
  });
6941
7141
  } else {
6942
- (0, import_child_process3.execSync)(`git worktree add -b ${branchName} "${worktreePath}"`, {
7142
+ (0, import_child_process4.execSync)(`git worktree add -b ${branchName} "${worktreePath}"`, {
6943
7143
  cwd: this.baseDir,
6944
7144
  stdio: "inherit"
6945
7145
  });
6946
7146
  }
6947
7147
  console.log(
6948
- import_chalk11.default.green(`\u2714 Worktree successfully created and isolated on branch '${branchName}'`)
7148
+ import_chalk13.default.green(`\u2714 Worktree successfully created and isolated on branch '${branchName}'`)
6949
7149
  );
6950
7150
  await this.linkDependencies(worktreePath);
6951
7151
  return worktreePath;
6952
7152
  } catch (error) {
6953
- console.error(import_chalk11.default.red("Failed to create git worktree:"), error);
7153
+ console.error(import_chalk13.default.red("Failed to create git worktree:"), error);
6954
7154
  return null;
6955
7155
  }
6956
7156
  }