ai-spec-dev 0.30.1 → 0.31.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.
package/dist/cli/index.js CHANGED
@@ -490,7 +490,7 @@ var init_workspace_loader = __esm({
490
490
  var import_commander = require("commander");
491
491
  var path22 = __toESM(require("path"));
492
492
  var fs23 = __toESM(require("fs-extra"));
493
- var import_chalk18 = __toESM(require("chalk"));
493
+ var import_chalk19 = __toESM(require("chalk"));
494
494
  var dotenv = __toESM(require("dotenv"));
495
495
  var import_prompts3 = require("@inquirer/prompts");
496
496
 
@@ -6127,6 +6127,16 @@ var RunLogger = class {
6127
6127
  this.log.errors.push(`[${event}] ${error}`);
6128
6128
  this.flush();
6129
6129
  }
6130
+ /** Record the prompt hash for this run (call once at run start). */
6131
+ setPromptHash(hash) {
6132
+ this.log.promptHash = hash;
6133
+ this.flush();
6134
+ }
6135
+ /** Record the harness self-eval score (call once at run end). */
6136
+ setHarnessScore(score) {
6137
+ this.log.harnessScore = score;
6138
+ this.flush();
6139
+ }
6130
6140
  fileWritten(filePath) {
6131
6141
  if (!this.log.filesWritten.includes(filePath)) {
6132
6142
  this.log.filesWritten.push(filePath);
@@ -9911,6 +9921,103 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9911
9921
  return outputPath;
9912
9922
  }
9913
9923
 
9924
+ // core/prompt-hasher.ts
9925
+ var import_crypto = require("crypto");
9926
+ init_codegen_prompt();
9927
+ init_codegen_prompt();
9928
+ function computePromptHash() {
9929
+ const segments = [
9930
+ codeGenSystemPrompt,
9931
+ dslSystemPrompt,
9932
+ specPrompt,
9933
+ reviewArchitectureSystemPrompt,
9934
+ reviewImplementationSystemPrompt,
9935
+ reviewImpactComplexitySystemPrompt
9936
+ ];
9937
+ return (0, import_crypto.createHash)("sha256").update(segments.join("\0")).digest("hex").slice(0, 8);
9938
+ }
9939
+
9940
+ // core/self-evaluator.ts
9941
+ var import_chalk18 = __toESM(require("chalk"));
9942
+ var ENDPOINT_LAYER_PATTERNS = [
9943
+ /src\/api/,
9944
+ /src\/routes?/,
9945
+ /src\/controller/,
9946
+ /src\/handler/,
9947
+ /src\/endpoints?/
9948
+ ];
9949
+ var MODEL_LAYER_PATTERNS = [
9950
+ /src\/model/,
9951
+ /src\/schema/,
9952
+ /src\/entit/,
9953
+ /src\/db/,
9954
+ /prisma/,
9955
+ /src\/data/,
9956
+ /src\/domain/
9957
+ ];
9958
+ function extractReviewScore(reviewText) {
9959
+ const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
9960
+ return match ? parseFloat(match[1]) : null;
9961
+ }
9962
+ function runSelfEval(opts) {
9963
+ const { dsl, generatedFiles, compilePassed, reviewText, promptHash, logger } = opts;
9964
+ const endpointsTotal = dsl?.endpoints?.length ?? 0;
9965
+ const modelsTotal = dsl?.models?.length ?? 0;
9966
+ const endpointLayerCovered = generatedFiles.some(
9967
+ (f) => ENDPOINT_LAYER_PATTERNS.some((p) => p.test(f))
9968
+ );
9969
+ const modelLayerCovered = generatedFiles.some(
9970
+ (f) => MODEL_LAYER_PATTERNS.some((p) => p.test(f))
9971
+ );
9972
+ let dslCoverageScore = 10;
9973
+ if (generatedFiles.length === 0) {
9974
+ dslCoverageScore = 0;
9975
+ } else {
9976
+ if (endpointsTotal > 0 && !endpointLayerCovered) dslCoverageScore -= 4;
9977
+ if (modelsTotal > 0 && !modelLayerCovered) dslCoverageScore -= 3;
9978
+ }
9979
+ const compileScore = compilePassed ? 10 : 5;
9980
+ const reviewScore = reviewText ? extractReviewScore(reviewText) : null;
9981
+ const harnessScore = reviewScore !== null ? Math.round((dslCoverageScore * 0.4 + compileScore * 0.3 + reviewScore * 0.3) * 10) / 10 : Math.round((dslCoverageScore * 0.55 + compileScore * 0.45) * 10) / 10;
9982
+ const result = {
9983
+ dslCoverageScore,
9984
+ compileScore,
9985
+ reviewScore,
9986
+ harnessScore,
9987
+ promptHash,
9988
+ detail: {
9989
+ endpointsTotal,
9990
+ endpointLayerCovered,
9991
+ modelsTotal,
9992
+ modelLayerCovered,
9993
+ filesWritten: generatedFiles.length
9994
+ }
9995
+ };
9996
+ logger.setHarnessScore(harnessScore);
9997
+ logger.stageEnd("self_eval", {
9998
+ harnessScore,
9999
+ dslCoverageScore,
10000
+ compileScore,
10001
+ reviewScore: reviewScore ?? void 0,
10002
+ promptHash
10003
+ });
10004
+ return result;
10005
+ }
10006
+ function printSelfEval(result) {
10007
+ const scoreColor = result.harnessScore >= 8 ? import_chalk18.default.green : result.harnessScore >= 6 ? import_chalk18.default.yellow : import_chalk18.default.red;
10008
+ const filled = Math.round(result.harnessScore);
10009
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
10010
+ const compileTag = result.compileScore === 10 ? import_chalk18.default.green("pass") : import_chalk18.default.yellow("partial");
10011
+ const reviewTag = result.reviewScore !== null ? `Review: ${result.reviewScore}/10` : import_chalk18.default.gray("Review: skipped");
10012
+ console.log(import_chalk18.default.cyan("\n\u2500\u2500\u2500 Harness Self-Eval \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10013
+ console.log(` Score : ${scoreColor(`[${bar}] ${result.harnessScore}/10`)}`);
10014
+ console.log(
10015
+ ` DSL : ${scoreColor(result.dslCoverageScore + "/10")} Compile: ${compileTag} ${reviewTag}`
10016
+ );
10017
+ console.log(import_chalk18.default.gray(` Prompt : ${result.promptHash}`));
10018
+ console.log(import_chalk18.default.gray("\u2500".repeat(49)));
10019
+ }
10020
+
9914
10021
  // cli/index.ts
9915
10022
  dotenv.config();
9916
10023
  var CONFIG_FILE = ".ai-spec.json";
@@ -9942,20 +10049,20 @@ async function resolveApiKey(providerName, cliKey) {
9942
10049
  validate: (v2) => v2.trim().length > 0 || "API key cannot be empty"
9943
10050
  });
9944
10051
  await saveKey(providerName, newKey.trim());
9945
- console.log(import_chalk18.default.gray(` Key saved to ${KEY_STORE_FILE}`));
10052
+ console.log(import_chalk19.default.gray(` Key saved to ${KEY_STORE_FILE}`));
9946
10053
  return newKey.trim();
9947
10054
  }
9948
10055
  function printBanner(opts) {
9949
- console.log(import_chalk18.default.blue("\n" + "\u2500".repeat(52)));
9950
- console.log(import_chalk18.default.bold(" ai-spec \u2014 AI-driven Development Orchestrator"));
9951
- console.log(import_chalk18.default.blue("\u2500".repeat(52)));
9952
- console.log(import_chalk18.default.gray(` Spec : ${opts.specProvider} / ${opts.specModel}`));
10056
+ console.log(import_chalk19.default.blue("\n" + "\u2500".repeat(52)));
10057
+ console.log(import_chalk19.default.bold(" ai-spec \u2014 AI-driven Development Orchestrator"));
10058
+ console.log(import_chalk19.default.blue("\u2500".repeat(52)));
10059
+ console.log(import_chalk19.default.gray(` Spec : ${opts.specProvider} / ${opts.specModel}`));
9953
10060
  console.log(
9954
- import_chalk18.default.gray(
10061
+ import_chalk19.default.gray(
9955
10062
  ` Codegen : ${opts.codegenMode} (${opts.codegenProvider} / ${opts.codegenModel})`
9956
10063
  )
9957
10064
  );
9958
- console.log(import_chalk18.default.blue("\u2500".repeat(52) + "\n"));
10065
+ console.log(import_chalk19.default.blue("\u2500".repeat(52) + "\n"));
9959
10066
  }
9960
10067
  var program = new import_commander.Command();
9961
10068
  program.name("ai-spec").description("AI-driven Development Orchestrator \u2014 spec, generate, review").version("0.14.1");
@@ -9982,42 +10089,42 @@ program.command("create").description("Generate a feature spec and kick off code
9982
10089
  const workspaceLoader = new WorkspaceLoader(currentDir);
9983
10090
  const workspaceConfig = await workspaceLoader.load();
9984
10091
  if (workspaceConfig) {
9985
- console.log(import_chalk18.default.cyan(`
10092
+ console.log(import_chalk19.default.cyan(`
9986
10093
  [Workspace] Detected workspace: ${workspaceConfig.name}`));
9987
- console.log(import_chalk18.default.gray(` Repos: ${workspaceConfig.repos.map((r) => r.name).join(", ")}`));
10094
+ console.log(import_chalk19.default.gray(` Repos: ${workspaceConfig.repos.map((r) => r.name).join(", ")}`));
9988
10095
  const pipelineResults = await runMultiRepoPipeline(idea, workspaceConfig, opts, currentDir, config2);
9989
10096
  if (opts.serve) {
9990
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Auto-serve: starting mock server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10097
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Auto-serve: starting mock server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9991
10098
  const backendResult = pipelineResults.find((r) => r.role === "backend" && r.status === "success" && r.dsl);
9992
10099
  const frontendResult = pipelineResults.find((r) => (r.role === "frontend" || r.role === "mobile") && r.status === "success");
9993
10100
  if (!backendResult) {
9994
- console.log(import_chalk18.default.yellow(" No successful backend with DSL found \u2014 skipping auto-serve."));
10101
+ console.log(import_chalk19.default.yellow(" No successful backend with DSL found \u2014 skipping auto-serve."));
9995
10102
  } else {
9996
10103
  const mockPort = 3001;
9997
10104
  const mockResult = await generateMockAssets(backendResult.dsl, backendResult.repoAbsPath, { port: mockPort });
9998
10105
  const serverJsPath = path22.join(backendResult.repoAbsPath, "mock", "server.js");
9999
- console.log(import_chalk18.default.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
10106
+ console.log(import_chalk19.default.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
10000
10107
  const pid = startMockServerBackground(serverJsPath, mockPort);
10001
- console.log(import_chalk18.default.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
10108
+ console.log(import_chalk19.default.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
10002
10109
  if (frontendResult) {
10003
10110
  const proxyResult = await applyMockProxy(frontendResult.repoAbsPath, mockPort, backendResult.dsl.endpoints);
10004
10111
  await saveMockServerPid(frontendResult.repoAbsPath, pid);
10005
10112
  if (proxyResult.applied) {
10006
- console.log(import_chalk18.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
10007
- console.log(import_chalk18.default.bold.cyan(`
10113
+ console.log(import_chalk19.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
10114
+ console.log(import_chalk19.default.bold.cyan(`
10008
10115
  Ready! Run your frontend dev server:`));
10009
- console.log(import_chalk18.default.white(` cd ${frontendResult.repoAbsPath}`));
10010
- console.log(import_chalk18.default.white(` ${proxyResult.devCommand}`));
10011
- console.log(import_chalk18.default.gray(`
10116
+ console.log(import_chalk19.default.white(` cd ${frontendResult.repoAbsPath}`));
10117
+ console.log(import_chalk19.default.white(` ${proxyResult.devCommand}`));
10118
+ console.log(import_chalk19.default.gray(`
10012
10119
  When done, restore: ai-spec mock --restore --frontend ${frontendResult.repoAbsPath}`));
10013
10120
  } else {
10014
- console.log(import_chalk18.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
10015
- if (proxyResult.note) console.log(import_chalk18.default.gray(` ${proxyResult.note}`));
10016
- console.log(import_chalk18.default.gray(` Mock server: http://localhost:${mockPort}`));
10121
+ console.log(import_chalk19.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
10122
+ if (proxyResult.note) console.log(import_chalk19.default.gray(` ${proxyResult.note}`));
10123
+ console.log(import_chalk19.default.gray(` Mock server: http://localhost:${mockPort}`));
10017
10124
  }
10018
10125
  } else {
10019
- console.log(import_chalk18.default.gray(` No frontend repo found \u2014 mock server is running at http://localhost:${mockPort}`));
10020
- console.log(import_chalk18.default.gray(` Configure your frontend proxy manually to point to http://localhost:${mockPort}`));
10126
+ console.log(import_chalk19.default.gray(` No frontend repo found \u2014 mock server is running at http://localhost:${mockPort}`));
10127
+ console.log(import_chalk19.default.gray(` Configure your frontend proxy manually to point to http://localhost:${mockPort}`));
10021
10128
  }
10022
10129
  }
10023
10130
  }
@@ -10038,7 +10145,7 @@ program.command("create").description("Generate a feature spec and kick off code
10038
10145
  codegenModel: codegenModelName
10039
10146
  });
10040
10147
  const runId = generateRunId();
10041
- console.log(import_chalk18.default.gray(` Run ID: ${runId}`));
10148
+ console.log(import_chalk19.default.gray(` Run ID: ${runId}`));
10042
10149
  const runSnapshot = new RunSnapshot(currentDir, runId);
10043
10150
  setActiveSnapshot(runSnapshot);
10044
10151
  const runLogger = new RunLogger(currentDir, runId, {
@@ -10046,25 +10153,27 @@ program.command("create").description("Generate a feature spec and kick off code
10046
10153
  model: specModelName
10047
10154
  });
10048
10155
  setActiveLogger(runLogger);
10049
- console.log(import_chalk18.default.blue("[1/6] Loading project context..."));
10156
+ const promptHash = computePromptHash();
10157
+ runLogger.setPromptHash(promptHash);
10158
+ console.log(import_chalk19.default.blue("[1/6] Loading project context..."));
10050
10159
  runLogger.stageStart("context_load");
10051
10160
  const loader = new ContextLoader(currentDir);
10052
10161
  const context = await loader.loadProjectContext();
10053
10162
  const { type: detectedRepoType } = await detectRepoType(currentDir);
10054
10163
  runLogger.stageEnd("context_load", { techStack: context.techStack, repoType: detectedRepoType });
10055
- console.log(import_chalk18.default.gray(` Tech stack : ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10056
- console.log(import_chalk18.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10057
- console.log(import_chalk18.default.gray(` API files : ${context.apiStructure.length} files`));
10164
+ console.log(import_chalk19.default.gray(` Tech stack : ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10165
+ console.log(import_chalk19.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10166
+ console.log(import_chalk19.default.gray(` API files : ${context.apiStructure.length} files`));
10058
10167
  if (context.schema) {
10059
- console.log(import_chalk18.default.gray(` Prisma schema: found`));
10168
+ console.log(import_chalk19.default.gray(` Prisma schema: found`));
10060
10169
  }
10061
10170
  if (context.constitution) {
10062
- console.log(import_chalk18.default.green(` Constitution : found (.ai-spec-constitution.md)`));
10171
+ console.log(import_chalk19.default.green(` Constitution : found (.ai-spec-constitution.md)`));
10063
10172
  if (context.constitution.length > 6e3) {
10064
- console.log(import_chalk18.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10173
+ console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10065
10174
  }
10066
10175
  } else {
10067
- console.log(import_chalk18.default.yellow(" Constitution : not found \u2014 auto-generating..."));
10176
+ console.log(import_chalk19.default.yellow(" Constitution : not found \u2014 auto-generating..."));
10068
10177
  try {
10069
10178
  const constitutionGen = new ConstitutionGenerator(
10070
10179
  createProvider(specProviderName, specApiKey, specModelName)
@@ -10072,12 +10181,12 @@ program.command("create").description("Generate a feature spec and kick off code
10072
10181
  const constitutionContent = await constitutionGen.generate(currentDir);
10073
10182
  await constitutionGen.saveConstitution(currentDir, constitutionContent);
10074
10183
  context.constitution = constitutionContent;
10075
- console.log(import_chalk18.default.green(` Constitution : \u2714 generated and saved (.ai-spec-constitution.md)`));
10184
+ console.log(import_chalk19.default.green(` Constitution : \u2714 generated and saved (.ai-spec-constitution.md)`));
10076
10185
  } catch (err) {
10077
- console.log(import_chalk18.default.yellow(` Constitution : \u26A0 auto-generation failed (${err.message}), continuing without it.`));
10186
+ console.log(import_chalk19.default.yellow(` Constitution : \u26A0 auto-generation failed (${err.message}), continuing without it.`));
10078
10187
  }
10079
10188
  }
10080
- console.log(import_chalk18.default.blue(`
10189
+ console.log(import_chalk19.default.blue(`
10081
10190
  [2/6] Generating spec with ${specProviderName}/${specModelName}...`));
10082
10191
  const specProvider = createProvider(specProviderName, specApiKey, specModelName);
10083
10192
  let initialSpec;
@@ -10087,30 +10196,30 @@ program.command("create").description("Generate a feature spec and kick off code
10087
10196
  if (opts.skipTasks) {
10088
10197
  const generator = new SpecGenerator(specProvider);
10089
10198
  initialSpec = await generator.generateSpec(idea, context);
10090
- console.log(import_chalk18.default.green(" \u2714 Spec generated."));
10199
+ console.log(import_chalk19.default.green(" \u2714 Spec generated."));
10091
10200
  } else {
10092
10201
  const result = await generateSpecWithTasks(specProvider, idea, context);
10093
10202
  initialSpec = result.spec;
10094
10203
  initialTasks = result.tasks;
10095
- console.log(import_chalk18.default.green(` \u2714 Spec generated.`));
10204
+ console.log(import_chalk19.default.green(` \u2714 Spec generated.`));
10096
10205
  if (initialTasks.length > 0) {
10097
- console.log(import_chalk18.default.green(` \u2714 ${initialTasks.length} tasks generated (combined call).`));
10206
+ console.log(import_chalk19.default.green(` \u2714 ${initialTasks.length} tasks generated (combined call).`));
10098
10207
  } else {
10099
- console.log(import_chalk18.default.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
10208
+ console.log(import_chalk19.default.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
10100
10209
  }
10101
10210
  }
10102
10211
  runLogger.stageEnd("spec_gen", { taskCount: initialTasks.length });
10103
10212
  } catch (err) {
10104
10213
  runLogger.stageFail("spec_gen", err.message);
10105
- console.error(import_chalk18.default.red(" \u2718 Spec generation failed:"), err);
10214
+ console.error(import_chalk19.default.red(" \u2718 Spec generation failed:"), err);
10106
10215
  process.exit(1);
10107
10216
  }
10108
10217
  let finalSpec;
10109
10218
  if (opts.fast) {
10110
- console.log(import_chalk18.default.gray("\n[3/6] Skipping refinement (--fast)."));
10219
+ console.log(import_chalk19.default.gray("\n[3/6] Skipping refinement (--fast)."));
10111
10220
  finalSpec = initialSpec;
10112
10221
  } else {
10113
- console.log(import_chalk18.default.blue("\n[3/6] Interactive spec refinement..."));
10222
+ console.log(import_chalk19.default.blue("\n[3/6] Interactive spec refinement..."));
10114
10223
  runLogger.stageStart("spec_refine");
10115
10224
  const refiner = new SpecRefiner(specProvider);
10116
10225
  finalSpec = await refiner.refineLoop(initialSpec);
@@ -10121,7 +10230,7 @@ program.command("create").description("Generate a feature spec and kick off code
10121
10230
  const shouldRunAssessment = !opts.skipAssessment && (!opts.auto || minScore > 0);
10122
10231
  if (shouldRunAssessment) {
10123
10232
  if (!opts.auto) {
10124
- console.log(import_chalk18.default.blue("\n[3.4/6] Spec quality assessment..."));
10233
+ console.log(import_chalk19.default.blue("\n[3.4/6] Spec quality assessment..."));
10125
10234
  }
10126
10235
  runLogger.stageStart("spec_assess");
10127
10236
  const assessment = await assessSpec(specProvider, finalSpec, context.constitution ?? void 0);
@@ -10130,45 +10239,45 @@ program.command("create").description("Generate a feature spec and kick off code
10130
10239
  if (!opts.auto) printSpecAssessment(assessment);
10131
10240
  if (minScore > 0 && assessment.overallScore < minScore) {
10132
10241
  if (opts.force) {
10133
- console.log(import_chalk18.default.yellow(`
10242
+ console.log(import_chalk19.default.yellow(`
10134
10243
  \u26A0 Score gate: ${assessment.overallScore}/10 < minimum ${minScore}/10 \u2014 bypassed with --force.`));
10135
10244
  } else {
10136
10245
  runLogger.stageFail("spec_assess", `Score gate: ${assessment.overallScore} < ${minScore}`);
10137
- console.log(import_chalk18.default.red(`
10246
+ console.log(import_chalk19.default.red(`
10138
10247
  \u2718 Spec quality gate failed: overallScore ${assessment.overallScore}/10 < minimum ${minScore}/10`));
10139
10248
  if (!opts.auto) {
10140
- console.log(import_chalk18.default.gray(` Address the issues above and re-run, or use --force to bypass.`));
10249
+ console.log(import_chalk19.default.gray(` Address the issues above and re-run, or use --force to bypass.`));
10141
10250
  } else {
10142
- console.log(import_chalk18.default.gray(` Auto mode: gate enforced. Fix the spec or lower minSpecScore, or use --force to bypass.`));
10251
+ console.log(import_chalk19.default.gray(` Auto mode: gate enforced. Fix the spec or lower minSpecScore, or use --force to bypass.`));
10143
10252
  }
10144
- console.log(import_chalk18.default.gray(` Gate threshold set in .ai-spec.json \u2192 "minSpecScore": ${minScore}`));
10253
+ console.log(import_chalk19.default.gray(` Gate threshold set in .ai-spec.json \u2192 "minSpecScore": ${minScore}`));
10145
10254
  process.exit(1);
10146
10255
  }
10147
10256
  }
10148
10257
  } else {
10149
10258
  runLogger.stageEnd("spec_assess", { skipped: true });
10150
10259
  if (!opts.auto) {
10151
- console.log(import_chalk18.default.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
10260
+ console.log(import_chalk19.default.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
10152
10261
  }
10153
10262
  }
10154
10263
  }
10155
10264
  if (!opts.auto) {
10156
- console.log(import_chalk18.default.blue("\n[3.5/6] Approval Gate \u2014 review before code generation"));
10265
+ console.log(import_chalk19.default.blue("\n[3.5/6] Approval Gate \u2014 review before code generation"));
10157
10266
  const specLines = finalSpec.split("\n").length;
10158
10267
  const specWords = finalSpec.split(/\s+/).length;
10159
10268
  const taskCountHint = initialTasks.length > 0 ? ` Tasks generated : ${initialTasks.length}` : "";
10160
- console.log(import_chalk18.default.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10161
- if (taskCountHint) console.log(import_chalk18.default.gray(taskCountHint));
10269
+ console.log(import_chalk19.default.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10270
+ if (taskCountHint) console.log(import_chalk19.default.gray(taskCountHint));
10162
10271
  const previewSpecsDir = path22.join(currentDir, "specs");
10163
10272
  const slug = featureSlug;
10164
10273
  const prevVersion = await findLatestVersion(previewSpecsDir, slug);
10165
10274
  if (prevVersion) {
10166
- console.log(import_chalk18.default.gray(` Previous version: v${prevVersion.version} (${prevVersion.filePath})`));
10275
+ console.log(import_chalk19.default.gray(` Previous version: v${prevVersion.version} (${prevVersion.filePath})`));
10167
10276
  const diff = computeDiff(prevVersion.content, finalSpec);
10168
- console.log(import_chalk18.default.cyan("\n \u2500\u2500 Changes vs previous version \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10277
+ console.log(import_chalk19.default.cyan("\n \u2500\u2500 Changes vs previous version \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10169
10278
  printDiffSummary(diff, `v${prevVersion.version} \u2192 v${prevVersion.version + 1}`);
10170
10279
  printDiff(diff);
10171
- console.log(import_chalk18.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"));
10280
+ console.log(import_chalk19.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"));
10172
10281
  }
10173
10282
  const gate = await (0, import_prompts3.select)({
10174
10283
  message: "Ready to proceed to code generation?",
@@ -10179,9 +10288,9 @@ program.command("create").description("Generate a feature spec and kick off code
10179
10288
  ]
10180
10289
  });
10181
10290
  if (gate === "view") {
10182
- console.log(import_chalk18.default.cyan("\n" + "\u2500".repeat(52)));
10291
+ console.log(import_chalk19.default.cyan("\n" + "\u2500".repeat(52)));
10183
10292
  console.log(finalSpec);
10184
- console.log(import_chalk18.default.cyan("\u2500".repeat(52) + "\n"));
10293
+ console.log(import_chalk19.default.cyan("\u2500".repeat(52) + "\n"));
10185
10294
  const confirm22 = await (0, import_prompts3.select)({
10186
10295
  message: "Proceed to code generation?",
10187
10296
  choices: [
@@ -10190,92 +10299,92 @@ program.command("create").description("Generate a feature spec and kick off code
10190
10299
  ]
10191
10300
  });
10192
10301
  if (confirm22 === "abort") {
10193
- console.log(import_chalk18.default.yellow(" Aborted. Spec was NOT saved."));
10302
+ console.log(import_chalk19.default.yellow(" Aborted. Spec was NOT saved."));
10194
10303
  process.exit(0);
10195
10304
  }
10196
10305
  } else if (gate === "abort") {
10197
- console.log(import_chalk18.default.yellow(" Aborted. Spec was NOT saved."));
10306
+ console.log(import_chalk19.default.yellow(" Aborted. Spec was NOT saved."));
10198
10307
  process.exit(0);
10199
10308
  }
10200
- console.log(import_chalk18.default.green(" \u2714 Approved \u2014 continuing to code generation."));
10309
+ console.log(import_chalk19.default.green(" \u2714 Approved \u2014 continuing to code generation."));
10201
10310
  } else {
10202
- console.log(import_chalk18.default.gray("[3.5/6] Approval Gate: skipped (--auto)."));
10311
+ console.log(import_chalk19.default.gray("[3.5/6] Approval Gate: skipped (--auto)."));
10203
10312
  }
10204
10313
  let extractedDsl = null;
10205
10314
  if (opts.skipDsl) {
10206
- console.log(import_chalk18.default.gray("\n[DSL] Skipped (--skip-dsl)."));
10315
+ console.log(import_chalk19.default.gray("\n[DSL] Skipped (--skip-dsl)."));
10207
10316
  } else {
10208
- console.log(import_chalk18.default.blue("\n[DSL] Extracting structured DSL from spec..."));
10209
- console.log(import_chalk18.default.gray(` Provider: ${specProviderName}/${specModelName}`));
10317
+ console.log(import_chalk19.default.blue("\n[DSL] Extracting structured DSL from spec..."));
10318
+ console.log(import_chalk19.default.gray(` Provider: ${specProviderName}/${specModelName}`));
10210
10319
  runLogger.stageStart("dsl_extract");
10211
10320
  try {
10212
10321
  const isFrontend = isFrontendDeps(context.dependencies);
10213
- if (isFrontend) console.log(import_chalk18.default.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
10322
+ if (isFrontend) console.log(import_chalk19.default.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
10214
10323
  const dslExtractor = new DslExtractor(specProvider);
10215
10324
  extractedDsl = await dslExtractor.extract(finalSpec, { auto: opts.auto, isFrontend });
10216
10325
  if (extractedDsl) {
10217
10326
  runLogger.stageEnd("dsl_extract", { endpoints: extractedDsl.endpoints?.length ?? 0, models: extractedDsl.models?.length ?? 0 });
10218
- console.log(import_chalk18.default.green(" \u2714 DSL extracted and validated."));
10327
+ console.log(import_chalk19.default.green(" \u2714 DSL extracted and validated."));
10219
10328
  } else {
10220
10329
  runLogger.stageEnd("dsl_extract", { skipped: true });
10221
- console.log(import_chalk18.default.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
10330
+ console.log(import_chalk19.default.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
10222
10331
  }
10223
10332
  } catch (err) {
10224
10333
  runLogger.stageFail("dsl_extract", err.message);
10225
- console.log(import_chalk18.default.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
10334
+ console.log(import_chalk19.default.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
10226
10335
  }
10227
10336
  }
10228
10337
  const isFrontendProject2 = isFrontendDeps(context.dependencies ?? []);
10229
10338
  const skipWorktree = opts.worktree ? false : opts.skipWorktree || isFrontendProject2;
10230
10339
  let workingDir = currentDir;
10231
10340
  if (!skipWorktree) {
10232
- console.log(import_chalk18.default.blue("\n[4/6] Setting up git worktree..."));
10341
+ console.log(import_chalk19.default.blue("\n[4/6] Setting up git worktree..."));
10233
10342
  const worktreeManager = new GitWorktreeManager(currentDir);
10234
10343
  const worktreePath = await worktreeManager.createWorktree(idea);
10235
10344
  if (worktreePath) workingDir = worktreePath;
10236
10345
  } else {
10237
10346
  const reason = opts.worktree ? "" : isFrontendProject2 ? " (frontend project \u2014 use --worktree to override)" : " (--skip-worktree)";
10238
- console.log(import_chalk18.default.gray(`[4/6] Skipping worktree${reason}.`));
10347
+ console.log(import_chalk19.default.gray(`[4/6] Skipping worktree${reason}.`));
10239
10348
  }
10240
10349
  const specsDir = path22.join(workingDir, "specs");
10241
10350
  await fs23.ensureDir(specsDir);
10242
10351
  const { filePath: specFile, version: specVersion } = await nextVersionPath(specsDir, featureSlug);
10243
10352
  await fs23.writeFile(specFile, finalSpec, "utf-8");
10244
- console.log(import_chalk18.default.green(`
10245
- [5/6] \u2714 Spec saved: ${specFile}`) + import_chalk18.default.gray(` (v${specVersion})`));
10353
+ console.log(import_chalk19.default.green(`
10354
+ [5/6] \u2714 Spec saved: ${specFile}`) + import_chalk19.default.gray(` (v${specVersion})`));
10246
10355
  let savedDslFile = null;
10247
10356
  if (extractedDsl) {
10248
10357
  const dslExtractor = new DslExtractor(specProvider);
10249
10358
  savedDslFile = await dslExtractor.saveDsl(extractedDsl, specFile);
10250
- console.log(import_chalk18.default.green(` \u2714 DSL saved : ${savedDslFile}`));
10359
+ console.log(import_chalk19.default.green(` \u2714 DSL saved : ${savedDslFile}`));
10251
10360
  }
10252
10361
  if (!opts.skipTasks) {
10253
10362
  const taskGen = new TaskGenerator(specProvider);
10254
10363
  let tasksToSave = initialTasks;
10255
10364
  if (tasksToSave.length === 0) {
10256
- console.log(import_chalk18.default.blue(`
10365
+ console.log(import_chalk19.default.blue(`
10257
10366
  Generating tasks (separate call)...`));
10258
10367
  try {
10259
10368
  tasksToSave = await taskGen.generateTasks(finalSpec, context);
10260
10369
  } catch (err) {
10261
- console.log(import_chalk18.default.yellow(` \u26A0 Task generation failed: ${err.message}`));
10370
+ console.log(import_chalk19.default.yellow(` \u26A0 Task generation failed: ${err.message}`));
10262
10371
  }
10263
10372
  }
10264
10373
  if (tasksToSave.length > 0) {
10265
10374
  const sorted = taskGen.sortByLayer(tasksToSave);
10266
10375
  const tasksFile = await taskGen.saveTasks(sorted, specFile);
10267
10376
  printTasks(sorted);
10268
- console.log(import_chalk18.default.green(` \u2714 Tasks saved: ${tasksFile}`));
10377
+ console.log(import_chalk19.default.green(` \u2714 Tasks saved: ${tasksFile}`));
10269
10378
  } else {
10270
- console.log(import_chalk18.default.yellow(" \u26A0 No tasks generated \u2014 code generation will use fallback file planning."));
10379
+ console.log(import_chalk19.default.yellow(" \u26A0 No tasks generated \u2014 code generation will use fallback file planning."));
10271
10380
  }
10272
10381
  }
10273
- console.log(import_chalk18.default.blue(`
10382
+ console.log(import_chalk19.default.blue(`
10274
10383
  [6/6] Code generation (mode: ${codegenMode})...`));
10275
10384
  const codegenProvider = codegenProviderName === specProviderName && codegenApiKey === specApiKey ? specProvider : createProvider(codegenProviderName, codegenApiKey, codegenModelName);
10276
10385
  let generatedTestFiles = [];
10277
10386
  if (opts.tdd && extractedDsl) {
10278
- console.log(import_chalk18.default.cyan("\n[TDD] Generating pre-implementation tests (will fail until code is written)..."));
10387
+ console.log(import_chalk19.default.cyan("\n[TDD] Generating pre-implementation tests (will fail until code is written)..."));
10279
10388
  const testGen = new TestGenerator(codegenProvider);
10280
10389
  generatedTestFiles = await testGen.generateTdd(extractedDsl, workingDir);
10281
10390
  }
@@ -10289,27 +10398,29 @@ program.command("create").description("Generate a feature spec and kick off code
10289
10398
  });
10290
10399
  runLogger.stageEnd("codegen", { filesGenerated: generatedFiles.length });
10291
10400
  if (opts.tdd) {
10292
- console.log(import_chalk18.default.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
10401
+ console.log(import_chalk19.default.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
10293
10402
  } else if (opts.skipTests) {
10294
- console.log(import_chalk18.default.gray("\n[7/9] Skipping test generation (--skip-tests)."));
10403
+ console.log(import_chalk19.default.gray("\n[7/9] Skipping test generation (--skip-tests)."));
10295
10404
  } else if (!extractedDsl) {
10296
- console.log(import_chalk18.default.gray("\n[7/9] Skipping test generation (no DSL available)."));
10405
+ console.log(import_chalk19.default.gray("\n[7/9] Skipping test generation (no DSL available)."));
10297
10406
  } else {
10298
- console.log(import_chalk18.default.blue(`
10407
+ console.log(import_chalk19.default.blue(`
10299
10408
  [7/9] Test skeleton generation...`));
10300
10409
  runLogger.stageStart("test_gen");
10301
10410
  const testGen = new TestGenerator(codegenProvider);
10302
10411
  generatedTestFiles = await testGen.generate(extractedDsl, workingDir);
10303
10412
  runLogger.stageEnd("test_gen", { filesGenerated: generatedTestFiles.length });
10304
10413
  }
10414
+ let compilePassed = false;
10305
10415
  if (opts.skipErrorFeedback) {
10306
- console.log(import_chalk18.default.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10416
+ console.log(import_chalk19.default.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10417
+ compilePassed = true;
10307
10418
  } else {
10308
10419
  if (opts.tdd) {
10309
- console.log(import_chalk18.default.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10420
+ console.log(import_chalk19.default.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10310
10421
  }
10311
10422
  runLogger.stageStart("error_feedback");
10312
- await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
10423
+ compilePassed = await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
10313
10424
  maxCycles: opts.tdd ? 3 : 2
10314
10425
  // TDD gets one extra cycle
10315
10426
  });
@@ -10317,7 +10428,7 @@ program.command("create").description("Generate a feature spec and kick off code
10317
10428
  }
10318
10429
  let reviewResult = "";
10319
10430
  if (!opts.skipReview) {
10320
- console.log(import_chalk18.default.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10431
+ console.log(import_chalk19.default.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10321
10432
  runLogger.stageStart("review");
10322
10433
  const reviewer = new CodeReviewer(specProvider, currentDir);
10323
10434
  const savedSpec = await fs23.readFile(specFile, "utf-8");
@@ -10335,20 +10446,30 @@ program.command("create").description("Generate a feature spec and kick off code
10335
10446
  runLogger.stageEnd("review");
10336
10447
  await accumulateReviewKnowledge(specProvider, currentDir, reviewResult);
10337
10448
  }
10449
+ runLogger.stageStart("self_eval");
10450
+ const selfEvalResult = runSelfEval({
10451
+ dsl: extractedDsl,
10452
+ generatedFiles,
10453
+ compilePassed,
10454
+ reviewText: reviewResult,
10455
+ promptHash,
10456
+ logger: runLogger
10457
+ });
10458
+ printSelfEval(selfEvalResult);
10338
10459
  runLogger.finish();
10339
- console.log(import_chalk18.default.bold.green("\n\u2714 All done!"));
10340
- console.log(import_chalk18.default.gray(` Spec : ${specFile}`));
10341
- if (savedDslFile) console.log(import_chalk18.default.gray(` DSL : ${savedDslFile}`));
10460
+ console.log(import_chalk19.default.bold.green("\n\u2714 All done!"));
10461
+ console.log(import_chalk19.default.gray(` Spec : ${specFile}`));
10462
+ if (savedDslFile) console.log(import_chalk19.default.gray(` DSL : ${savedDslFile}`));
10342
10463
  if (generatedTestFiles.length > 0) {
10343
- console.log(import_chalk18.default.gray(` Tests : ${generatedTestFiles.length} skeleton file(s) generated`));
10464
+ console.log(import_chalk19.default.gray(` Tests : ${generatedTestFiles.length} skeleton file(s) generated`));
10344
10465
  }
10345
- console.log(import_chalk18.default.gray(` Working dir : ${workingDir}`));
10466
+ console.log(import_chalk19.default.gray(` Working dir : ${workingDir}`));
10346
10467
  if (workingDir !== currentDir) {
10347
- console.log(import_chalk18.default.gray(` Run \`cd ${workingDir}\` to enter the worktree.`));
10468
+ console.log(import_chalk19.default.gray(` Run \`cd ${workingDir}\` to enter the worktree.`));
10348
10469
  }
10349
10470
  runLogger.printSummary();
10350
10471
  if (runSnapshot.fileCount > 0) {
10351
- console.log(import_chalk18.default.gray(` To undo changes: ai-spec restore ${runId}`));
10472
+ console.log(import_chalk19.default.gray(` To undo changes: ai-spec restore ${runId}`));
10352
10473
  }
10353
10474
  });
10354
10475
  program.command("review").description("Run AI code review on current git diff against a spec").argument("[specFile]", "Path to spec file (auto-detects latest in specs/ if omitted)").option(
@@ -10368,7 +10489,7 @@ program.command("review").description("Run AI code review on current git diff ag
10368
10489
  if (specFile && await fs23.pathExists(specFile)) {
10369
10490
  specContent = await fs23.readFile(specFile, "utf-8");
10370
10491
  resolvedSpecFile = specFile;
10371
- console.log(import_chalk18.default.gray(`Using spec: ${specFile}`));
10492
+ console.log(import_chalk19.default.gray(`Using spec: ${specFile}`));
10372
10493
  } else {
10373
10494
  const specsDir = path22.join(currentDir, "specs");
10374
10495
  if (await fs23.pathExists(specsDir)) {
@@ -10377,12 +10498,12 @@ program.command("review").description("Run AI code review on current git diff ag
10377
10498
  const latest = path22.join(specsDir, files[0]);
10378
10499
  specContent = await fs23.readFile(latest, "utf-8");
10379
10500
  resolvedSpecFile = latest;
10380
- console.log(import_chalk18.default.gray(`Auto-detected spec: specs/${files[0]}`));
10501
+ console.log(import_chalk19.default.gray(`Auto-detected spec: specs/${files[0]}`));
10381
10502
  }
10382
10503
  }
10383
10504
  }
10384
10505
  if (!specContent) {
10385
- console.log(import_chalk18.default.yellow("No spec file found. Running review without spec context."));
10506
+ console.log(import_chalk19.default.yellow("No spec file found. Running review without spec context."));
10386
10507
  }
10387
10508
  await reviewer.reviewCode(specContent, resolvedSpecFile);
10388
10509
  await reviewer.printScoreTrend();
@@ -10409,15 +10530,15 @@ program.command("init").description(`Analyze codebase and generate Project Const
10409
10530
  auto: opts.auto
10410
10531
  });
10411
10532
  if (result.written) {
10412
- console.log(import_chalk18.default.blue("\n Summary:"));
10413
- console.log(import_chalk18.default.gray(` Lines : ${result.before.totalLines} \u2192 ${result.after.totalLines} (${result.before.totalLines - result.after.totalLines > 0 ? "-" : "+"}${Math.abs(result.before.totalLines - result.after.totalLines)})`));
10414
- console.log(import_chalk18.default.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
10533
+ console.log(import_chalk19.default.blue("\n Summary:"));
10534
+ console.log(import_chalk19.default.gray(` Lines : ${result.before.totalLines} \u2192 ${result.after.totalLines} (${result.before.totalLines - result.after.totalLines > 0 ? "-" : "+"}${Math.abs(result.before.totalLines - result.after.totalLines)})`));
10535
+ console.log(import_chalk19.default.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
10415
10536
  if (result.backupPath) {
10416
- console.log(import_chalk18.default.gray(` Backup: ${path22.basename(result.backupPath)}`));
10537
+ console.log(import_chalk19.default.gray(` Backup: ${path22.basename(result.backupPath)}`));
10417
10538
  }
10418
10539
  }
10419
10540
  } catch (err) {
10420
- console.error(import_chalk18.default.red(` \u2718 Consolidation failed: ${err.message}`));
10541
+ console.error(import_chalk19.default.red(` \u2718 Consolidation failed: ${err.message}`));
10421
10542
  process.exit(1);
10422
10543
  }
10423
10544
  return;
@@ -10425,14 +10546,14 @@ program.command("init").description(`Analyze codebase and generate Project Const
10425
10546
  if (opts.global) {
10426
10547
  const existing = await loadGlobalConstitution([currentDir]);
10427
10548
  if (existing && !opts.force) {
10428
- console.log(import_chalk18.default.yellow(`
10549
+ console.log(import_chalk19.default.yellow(`
10429
10550
  Global constitution already exists at: ${existing.source}`));
10430
- console.log(import_chalk18.default.gray(" Use --force to overwrite it."));
10551
+ console.log(import_chalk19.default.gray(" Use --force to overwrite it."));
10431
10552
  return;
10432
10553
  }
10433
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Generating Global Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10434
- console.log(import_chalk18.default.gray(` Provider: ${providerName}/${modelName}`));
10435
- console.log(import_chalk18.default.gray(" Scanning repos in workspace..."));
10554
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Generating Global Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10555
+ console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
10556
+ console.log(import_chalk19.default.gray(" Scanning repos in workspace..."));
10436
10557
  const loader = new ContextLoader(currentDir);
10437
10558
  const ctx = await loader.loadProjectContext();
10438
10559
  const summary = [
@@ -10444,56 +10565,56 @@ program.command("init").description(`Analyze codebase and generate Project Const
10444
10565
  try {
10445
10566
  globalConstitution = await provider.generate(prompt, globalConstitutionSystemPrompt);
10446
10567
  } catch (err) {
10447
- console.error(import_chalk18.default.red(" \u2718 Failed to generate global constitution:"), err);
10568
+ console.error(import_chalk19.default.red(" \u2718 Failed to generate global constitution:"), err);
10448
10569
  process.exit(1);
10449
10570
  }
10450
10571
  const saved2 = await saveGlobalConstitution(globalConstitution, currentDir);
10451
- console.log(import_chalk18.default.green(`
10572
+ console.log(import_chalk19.default.green(`
10452
10573
  \u2714 Global constitution saved: ${saved2}`));
10453
- console.log(import_chalk18.default.gray(" This will be automatically merged into all project constitutions in this workspace."));
10454
- console.log(import_chalk18.default.gray(" Project-level rules always override global rules.\n"));
10455
- console.log(import_chalk18.default.bold(" Preview:"));
10456
- console.log(import_chalk18.default.gray(globalConstitution.split("\n").slice(0, 12).join("\n")));
10574
+ console.log(import_chalk19.default.gray(" This will be automatically merged into all project constitutions in this workspace."));
10575
+ console.log(import_chalk19.default.gray(" Project-level rules always override global rules.\n"));
10576
+ console.log(import_chalk19.default.bold(" Preview:"));
10577
+ console.log(import_chalk19.default.gray(globalConstitution.split("\n").slice(0, 12).join("\n")));
10457
10578
  if (globalConstitution.split("\n").length > 12) {
10458
- console.log(import_chalk18.default.gray(` ... (${globalConstitution.split("\n").length} lines total)`));
10579
+ console.log(import_chalk19.default.gray(` ... (${globalConstitution.split("\n").length} lines total)`));
10459
10580
  }
10460
10581
  return;
10461
10582
  }
10462
10583
  const constitutionPath = path22.join(currentDir, CONSTITUTION_FILE);
10463
10584
  if (!opts.force && await fs23.pathExists(constitutionPath)) {
10464
- console.log(import_chalk18.default.yellow(`
10585
+ console.log(import_chalk19.default.yellow(`
10465
10586
  ${CONSTITUTION_FILE} already exists.`));
10466
- console.log(import_chalk18.default.gray(" Use --force to overwrite it."));
10467
- console.log(import_chalk18.default.gray(` Or edit it directly: ${constitutionPath}`));
10587
+ console.log(import_chalk19.default.gray(" Use --force to overwrite it."));
10588
+ console.log(import_chalk19.default.gray(` Or edit it directly: ${constitutionPath}`));
10468
10589
  return;
10469
10590
  }
10470
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Generating Project Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10471
- console.log(import_chalk18.default.gray(` Provider: ${providerName}/${modelName}`));
10472
- console.log(import_chalk18.default.gray(" Analyzing codebase..."));
10591
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Generating Project Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10592
+ console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
10593
+ console.log(import_chalk19.default.gray(" Analyzing codebase..."));
10473
10594
  const generator = new ConstitutionGenerator(provider);
10474
10595
  let constitution;
10475
10596
  try {
10476
10597
  constitution = await generator.generate(currentDir);
10477
10598
  } catch (err) {
10478
- console.error(import_chalk18.default.red(" \u2718 Failed to generate constitution:"), err);
10599
+ console.error(import_chalk19.default.red(" \u2718 Failed to generate constitution:"), err);
10479
10600
  process.exit(1);
10480
10601
  }
10481
10602
  const saved = await generator.saveConstitution(currentDir, constitution);
10482
10603
  const globalResult = await loadGlobalConstitution([path22.dirname(currentDir)]);
10483
10604
  if (globalResult) {
10484
- console.log(import_chalk18.default.cyan(`
10605
+ console.log(import_chalk19.default.cyan(`
10485
10606
  \u2139 Global constitution detected: ${globalResult.source}`));
10486
- console.log(import_chalk18.default.gray(" It will be merged with this project constitution at runtime."));
10487
- console.log(import_chalk18.default.gray(" Project rules take priority over global rules."));
10607
+ console.log(import_chalk19.default.gray(" It will be merged with this project constitution at runtime."));
10608
+ console.log(import_chalk19.default.gray(" Project rules take priority over global rules."));
10488
10609
  }
10489
- console.log(import_chalk18.default.green(`
10610
+ console.log(import_chalk19.default.green(`
10490
10611
  \u2714 Constitution saved: ${saved}`));
10491
- console.log(import_chalk18.default.gray(" This file will be automatically used in all future `ai-spec create` runs."));
10492
- console.log(import_chalk18.default.gray(" Edit it to add custom rules or red lines for your project.\n"));
10493
- console.log(import_chalk18.default.bold(" Preview:"));
10494
- console.log(import_chalk18.default.gray(constitution.split("\n").slice(0, 15).join("\n")));
10612
+ console.log(import_chalk19.default.gray(" This file will be automatically used in all future `ai-spec create` runs."));
10613
+ console.log(import_chalk19.default.gray(" Edit it to add custom rules or red lines for your project.\n"));
10614
+ console.log(import_chalk19.default.bold(" Preview:"));
10615
+ console.log(import_chalk19.default.gray(constitution.split("\n").slice(0, 15).join("\n")));
10495
10616
  if (constitution.split("\n").length > 15) {
10496
- console.log(import_chalk18.default.gray(` ... (${constitution.split("\n").length} lines total)`));
10617
+ console.log(import_chalk19.default.gray(` ... (${constitution.split("\n").length} lines total)`));
10497
10618
  }
10498
10619
  });
10499
10620
  program.command("config").description(`Set default configuration for this project (saved to ${CONFIG_FILE})`).option("--provider <name>", "Default AI provider for spec generation").option("--model <name>", "Default model for spec generation").option(
@@ -10504,41 +10625,41 @@ program.command("config").description(`Set default configuration for this projec
10504
10625
  const configPath = path22.join(currentDir, CONFIG_FILE);
10505
10626
  if (opts.clearKeys) {
10506
10627
  await clearAllKeys();
10507
- console.log(import_chalk18.default.green(`\u2714 All saved API keys cleared.`));
10628
+ console.log(import_chalk19.default.green(`\u2714 All saved API keys cleared.`));
10508
10629
  return;
10509
10630
  }
10510
10631
  if (opts.clearKey) {
10511
10632
  await clearKey(opts.clearKey);
10512
- console.log(import_chalk18.default.green(`\u2714 Saved key for "${opts.clearKey}" removed.`));
10633
+ console.log(import_chalk19.default.green(`\u2714 Saved key for "${opts.clearKey}" removed.`));
10513
10634
  return;
10514
10635
  }
10515
10636
  if (opts.listKeys) {
10516
10637
  const store = await fs23.readJson(KEY_STORE_FILE).catch(() => ({}));
10517
10638
  const providers = Object.keys(store);
10518
10639
  if (providers.length === 0) {
10519
- console.log(import_chalk18.default.gray("No saved API keys."));
10640
+ console.log(import_chalk19.default.gray("No saved API keys."));
10520
10641
  } else {
10521
- console.log(import_chalk18.default.bold("Saved API keys:"));
10642
+ console.log(import_chalk19.default.bold("Saved API keys:"));
10522
10643
  for (const p of providers) {
10523
10644
  const k2 = store[p];
10524
- console.log(import_chalk18.default.gray(` ${p}: ${k2.slice(0, 6)}...${k2.slice(-4)}`));
10645
+ console.log(import_chalk19.default.gray(` ${p}: ${k2.slice(0, 6)}...${k2.slice(-4)}`));
10525
10646
  }
10526
- console.log(import_chalk18.default.gray(`
10647
+ console.log(import_chalk19.default.gray(`
10527
10648
  File: ${KEY_STORE_FILE}`));
10528
10649
  }
10529
10650
  return;
10530
10651
  }
10531
10652
  if (opts.reset) {
10532
10653
  await fs23.writeJson(configPath, {}, { spaces: 2 });
10533
- console.log(import_chalk18.default.green(`\u2714 Config reset: ${configPath}`));
10654
+ console.log(import_chalk19.default.green(`\u2714 Config reset: ${configPath}`));
10534
10655
  return;
10535
10656
  }
10536
10657
  const existing = await loadConfig(currentDir);
10537
10658
  if (opts.show) {
10538
10659
  if (Object.keys(existing).length === 0) {
10539
- console.log(import_chalk18.default.gray("No config file found. Using built-in defaults."));
10660
+ console.log(import_chalk19.default.gray("No config file found. Using built-in defaults."));
10540
10661
  } else {
10541
- console.log(import_chalk18.default.bold(`${configPath}:`));
10662
+ console.log(import_chalk19.default.bold(`${configPath}:`));
10542
10663
  console.log(JSON.stringify(existing, null, 2));
10543
10664
  }
10544
10665
  return;
@@ -10552,27 +10673,27 @@ File: ${KEY_STORE_FILE}`));
10552
10673
  if (opts.minSpecScore !== void 0) {
10553
10674
  const score = parseInt(opts.minSpecScore, 10);
10554
10675
  if (isNaN(score) || score < 0 || score > 10) {
10555
- console.error(import_chalk18.default.red(" --min-spec-score must be a number between 0 and 10"));
10676
+ console.error(import_chalk19.default.red(" --min-spec-score must be a number between 0 and 10"));
10556
10677
  process.exit(1);
10557
10678
  }
10558
10679
  updated.minSpecScore = score;
10559
10680
  }
10560
10681
  await fs23.writeJson(configPath, updated, { spaces: 2 });
10561
- console.log(import_chalk18.default.green(`\u2714 Config saved to ${configPath}`));
10682
+ console.log(import_chalk19.default.green(`\u2714 Config saved to ${configPath}`));
10562
10683
  console.log(JSON.stringify(updated, null, 2));
10563
10684
  });
10564
10685
  program.command("model").description("Interactively switch the active AI provider/model and save to .ai-spec.json").option("--list", "List all available providers and models").action(async (opts) => {
10565
10686
  const currentDir = process.cwd();
10566
10687
  const configPath = path22.join(currentDir, CONFIG_FILE);
10567
10688
  if (opts.list) {
10568
- console.log(import_chalk18.default.bold("\nAvailable providers & models:\n"));
10689
+ console.log(import_chalk19.default.bold("\nAvailable providers & models:\n"));
10569
10690
  for (const [key, meta] of Object.entries(PROVIDER_CATALOG)) {
10570
10691
  console.log(
10571
- ` ${import_chalk18.default.bold.cyan(key.padEnd(10))} ${import_chalk18.default.white(meta.displayName)}`
10692
+ ` ${import_chalk19.default.bold.cyan(key.padEnd(10))} ${import_chalk19.default.white(meta.displayName)}`
10572
10693
  );
10573
- console.log(import_chalk18.default.gray(` ${meta.description}`));
10694
+ console.log(import_chalk19.default.gray(` ${meta.description}`));
10574
10695
  console.log(
10575
- import_chalk18.default.gray(
10696
+ import_chalk19.default.gray(
10576
10697
  ` env: ${meta.envKey} | models: ${meta.models.join(", ")}`
10577
10698
  )
10578
10699
  );
@@ -10581,10 +10702,10 @@ program.command("model").description("Interactively switch the active AI provide
10581
10702
  return;
10582
10703
  }
10583
10704
  const existing = await loadConfig(currentDir);
10584
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Model Switcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10705
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Model Switcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10585
10706
  if (Object.keys(existing).length > 0) {
10586
10707
  console.log(
10587
- import_chalk18.default.gray(
10708
+ import_chalk19.default.gray(
10588
10709
  ` Current: spec=${existing.provider ?? "gemini"}/${existing.model ?? DEFAULT_MODELS[existing.provider ?? "gemini"]}` + (existing.codegenProvider ? ` codegen=${existing.codegenProvider}/${existing.codegenModel ?? ""}` : "")
10589
10710
  )
10590
10711
  );
@@ -10602,7 +10723,7 @@ program.command("model").description("Interactively switch the active AI provide
10602
10723
  const providerKey = await (0, import_prompts3.select)({
10603
10724
  message: `${label} \u2014 select provider:`,
10604
10725
  choices: Object.entries(PROVIDER_CATALOG).map(([key, meta2]) => ({
10605
- name: `${meta2.displayName.padEnd(22)} ${import_chalk18.default.gray(meta2.description)}`,
10726
+ name: `${meta2.displayName.padEnd(22)} ${import_chalk19.default.gray(meta2.description)}`,
10606
10727
  value: key,
10607
10728
  short: meta2.displayName
10608
10729
  }))
@@ -10610,7 +10731,7 @@ program.command("model").description("Interactively switch the active AI provide
10610
10731
  const meta = PROVIDER_CATALOG[providerKey];
10611
10732
  const modelChoices = [
10612
10733
  ...meta.models.map((m) => ({ name: m, value: m })),
10613
- { name: import_chalk18.default.italic("\u270E Enter custom model name..."), value: "__custom__" }
10734
+ { name: import_chalk19.default.italic("\u270E Enter custom model name..."), value: "__custom__" }
10614
10735
  ];
10615
10736
  let chosenModel = await (0, import_prompts3.select)({
10616
10737
  message: `${label} \u2014 select model (${meta.displayName}):`,
@@ -10644,37 +10765,37 @@ program.command("model").description("Interactively switch the active AI provide
10644
10765
  if (!updated.codegen || updated.codegen === "claude-code") {
10645
10766
  updated.codegen = "api";
10646
10767
  console.log(
10647
- import_chalk18.default.yellow(
10768
+ import_chalk19.default.yellow(
10648
10769
  `
10649
10770
  \u26A0 provider "${effectiveCodegenProvider}" \u4E0D\u652F\u6301 "claude-code" \u6A21\u5F0F\u3002`
10650
10771
  )
10651
10772
  );
10652
- console.log(import_chalk18.default.gray(` \u5DF2\u81EA\u52A8\u5C06 codegen \u6A21\u5F0F\u8BBE\u4E3A "api"\u3002`));
10773
+ console.log(import_chalk19.default.gray(` \u5DF2\u81EA\u52A8\u5C06 codegen \u6A21\u5F0F\u8BBE\u4E3A "api"\u3002`));
10653
10774
  }
10654
10775
  }
10655
10776
  }
10656
- console.log(import_chalk18.default.blue("\n Preview:"));
10657
- console.log(import_chalk18.default.gray(` spec \u2192 ${updated.provider}/${updated.model}`));
10777
+ console.log(import_chalk19.default.blue("\n Preview:"));
10778
+ console.log(import_chalk19.default.gray(` spec \u2192 ${updated.provider}/${updated.model}`));
10658
10779
  if (updated.codegenProvider) {
10659
10780
  console.log(
10660
- import_chalk18.default.gray(
10781
+ import_chalk19.default.gray(
10661
10782
  ` codegen \u2192 ${updated.codegenProvider}/${updated.codegenModel} (mode: ${updated.codegen ?? "claude-code"})`
10662
10783
  )
10663
10784
  );
10664
10785
  }
10665
10786
  const ok = await (0, import_prompts3.confirm)({ message: "Save to .ai-spec.json?", default: true });
10666
10787
  if (!ok) {
10667
- console.log(import_chalk18.default.gray(" Cancelled."));
10788
+ console.log(import_chalk19.default.gray(" Cancelled."));
10668
10789
  return;
10669
10790
  }
10670
10791
  await fs23.writeJson(configPath, updated, { spaces: 2 });
10671
- console.log(import_chalk18.default.green(`
10792
+ console.log(import_chalk19.default.green(`
10672
10793
  \u2714 Saved to ${configPath}`));
10673
10794
  const providerToCheck = updated.provider ?? "gemini";
10674
10795
  const envKey = ENV_KEY_MAP[providerToCheck];
10675
10796
  if (envKey && !process.env[envKey]) {
10676
10797
  console.log(
10677
- import_chalk18.default.yellow(
10798
+ import_chalk19.default.yellow(
10678
10799
  ` \u26A0 Remember to set ${envKey} in your environment or .env file.`
10679
10800
  )
10680
10801
  );
@@ -10693,29 +10814,29 @@ async function runSingleRepoPipelineInWorkspace(opts) {
10693
10814
  cliOpts,
10694
10815
  contractContextSection
10695
10816
  } = opts;
10696
- console.log(import_chalk18.default.blue(`
10817
+ console.log(import_chalk19.default.blue(`
10697
10818
  [${repoName}] Loading project context...`));
10698
10819
  const loader = new ContextLoader(repoAbsPath);
10699
10820
  let context = await loader.loadProjectContext();
10700
10821
  const { type: detectedRepoType } = await detectRepoType(repoAbsPath);
10701
- console.log(import_chalk18.default.gray(` Tech stack: ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10702
- console.log(import_chalk18.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10822
+ console.log(import_chalk19.default.gray(` Tech stack: ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10823
+ console.log(import_chalk19.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10703
10824
  if (context.constitution && context.constitution.length > 6e3) {
10704
- console.log(import_chalk18.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10825
+ console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10705
10826
  }
10706
10827
  if (!context.constitution) {
10707
- console.log(import_chalk18.default.yellow(` Constitution: not found \u2014 auto-generating...`));
10828
+ console.log(import_chalk19.default.yellow(` Constitution: not found \u2014 auto-generating...`));
10708
10829
  try {
10709
10830
  const constitutionGen = new ConstitutionGenerator(specProvider);
10710
10831
  const constitutionContent = await constitutionGen.generate(repoAbsPath);
10711
10832
  await constitutionGen.saveConstitution(repoAbsPath, constitutionContent);
10712
10833
  context.constitution = constitutionContent;
10713
- console.log(import_chalk18.default.green(` Constitution: generated`));
10834
+ console.log(import_chalk19.default.green(` Constitution: generated`));
10714
10835
  } catch (err) {
10715
- console.log(import_chalk18.default.yellow(` Constitution: auto-generation failed (${err.message}), continuing.`));
10836
+ console.log(import_chalk19.default.yellow(` Constitution: auto-generation failed (${err.message}), continuing.`));
10716
10837
  }
10717
10838
  } else {
10718
- console.log(import_chalk18.default.green(` Constitution: found`));
10839
+ console.log(import_chalk19.default.green(` Constitution: found`));
10719
10840
  }
10720
10841
  let fullIdea = idea;
10721
10842
  if (contractContextSection) {
@@ -10723,58 +10844,58 @@ async function runSingleRepoPipelineInWorkspace(opts) {
10723
10844
 
10724
10845
  ${contractContextSection}`;
10725
10846
  }
10726
- console.log(import_chalk18.default.blue(` [${repoName}] Generating spec...`));
10847
+ console.log(import_chalk19.default.blue(` [${repoName}] Generating spec...`));
10727
10848
  let finalSpec;
10728
10849
  try {
10729
10850
  const result = await generateSpecWithTasks(specProvider, fullIdea, context);
10730
10851
  finalSpec = result.spec;
10731
- console.log(import_chalk18.default.green(` Spec generated.`));
10852
+ console.log(import_chalk19.default.green(` Spec generated.`));
10732
10853
  } catch (err) {
10733
- console.error(import_chalk18.default.red(` Spec generation failed: ${err.message}`));
10854
+ console.error(import_chalk19.default.red(` Spec generation failed: ${err.message}`));
10734
10855
  return { dsl: null, specFile: null };
10735
10856
  }
10736
10857
  let extractedDsl = null;
10737
10858
  if (!cliOpts.skipDsl) {
10738
- console.log(import_chalk18.default.blue(` [${repoName}] Extracting DSL...`));
10859
+ console.log(import_chalk19.default.blue(` [${repoName}] Extracting DSL...`));
10739
10860
  try {
10740
10861
  const dslExtractor = new DslExtractor(specProvider);
10741
10862
  const repoIsFrontend = isFrontendDeps(context.dependencies);
10742
10863
  extractedDsl = await dslExtractor.extract(finalSpec, { auto: true, isFrontend: repoIsFrontend });
10743
10864
  if (extractedDsl) {
10744
- console.log(import_chalk18.default.green(` DSL extracted.`));
10865
+ console.log(import_chalk19.default.green(` DSL extracted.`));
10745
10866
  }
10746
10867
  } catch (err) {
10747
- console.log(import_chalk18.default.yellow(` DSL extraction failed: ${err.message}`));
10868
+ console.log(import_chalk19.default.yellow(` DSL extraction failed: ${err.message}`));
10748
10869
  }
10749
10870
  }
10750
10871
  const isFrontendRepo = isFrontendDeps(context.dependencies ?? []);
10751
10872
  const skipWorktreeForRepo = cliOpts.worktree ? false : cliOpts.skipWorktree || isFrontendRepo;
10752
10873
  let workingDir = repoAbsPath;
10753
10874
  if (!skipWorktreeForRepo) {
10754
- console.log(import_chalk18.default.blue(` [${repoName}] Setting up git worktree...`));
10875
+ console.log(import_chalk19.default.blue(` [${repoName}] Setting up git worktree...`));
10755
10876
  try {
10756
10877
  const worktreeManager = new GitWorktreeManager(repoAbsPath);
10757
10878
  const worktreePath = await worktreeManager.createWorktree(idea);
10758
10879
  if (worktreePath) workingDir = worktreePath;
10759
10880
  } catch (err) {
10760
- console.log(import_chalk18.default.yellow(` Worktree setup failed: ${err.message}. Using main branch.`));
10881
+ console.log(import_chalk19.default.yellow(` Worktree setup failed: ${err.message}. Using main branch.`));
10761
10882
  }
10762
10883
  } else {
10763
- console.log(import_chalk18.default.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10884
+ console.log(import_chalk19.default.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10764
10885
  }
10765
10886
  const specsDir = path22.join(workingDir, "specs");
10766
10887
  await fs23.ensureDir(specsDir);
10767
10888
  const featureSlug = slugify(idea);
10768
10889
  const { filePath: specFile } = await nextVersionPath(specsDir, featureSlug);
10769
10890
  await fs23.writeFile(specFile, finalSpec, "utf-8");
10770
- console.log(import_chalk18.default.green(` Spec saved: ${path22.relative(repoAbsPath, specFile)}`));
10891
+ console.log(import_chalk19.default.green(` Spec saved: ${path22.relative(repoAbsPath, specFile)}`));
10771
10892
  let savedDslFile = null;
10772
10893
  if (extractedDsl) {
10773
10894
  const dslExtractorForSave = new DslExtractor(specProvider);
10774
10895
  savedDslFile = await dslExtractorForSave.saveDsl(extractedDsl, specFile);
10775
- console.log(import_chalk18.default.green(` DSL saved: ${path22.relative(repoAbsPath, savedDslFile)}`));
10896
+ console.log(import_chalk19.default.green(` DSL saved: ${path22.relative(repoAbsPath, savedDslFile)}`));
10776
10897
  }
10777
- console.log(import_chalk18.default.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10898
+ console.log(import_chalk19.default.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10778
10899
  try {
10779
10900
  const codegen = new CodeGenerator(codegenProvider, codegenMode);
10780
10901
  await codegen.generateCode(specFile, workingDir, context, {
@@ -10782,29 +10903,29 @@ ${contractContextSection}`;
10782
10903
  dslFilePath: savedDslFile ?? void 0,
10783
10904
  repoType: detectedRepoType
10784
10905
  });
10785
- console.log(import_chalk18.default.green(` Code generation complete.`));
10906
+ console.log(import_chalk19.default.green(` Code generation complete.`));
10786
10907
  } catch (err) {
10787
- console.log(import_chalk18.default.yellow(` Code generation failed: ${err.message}`));
10908
+ console.log(import_chalk19.default.yellow(` Code generation failed: ${err.message}`));
10788
10909
  }
10789
10910
  if (!cliOpts.skipTests && extractedDsl) {
10790
- console.log(import_chalk18.default.blue(` [${repoName}] Generating test skeletons...`));
10911
+ console.log(import_chalk19.default.blue(` [${repoName}] Generating test skeletons...`));
10791
10912
  try {
10792
10913
  const testGen = new TestGenerator(codegenProvider);
10793
10914
  const testFiles = await testGen.generate(extractedDsl, workingDir);
10794
- console.log(import_chalk18.default.green(` ${testFiles.length} test file(s) generated.`));
10915
+ console.log(import_chalk19.default.green(` ${testFiles.length} test file(s) generated.`));
10795
10916
  } catch (err) {
10796
- console.log(import_chalk18.default.yellow(` Test generation failed: ${err.message}`));
10917
+ console.log(import_chalk19.default.yellow(` Test generation failed: ${err.message}`));
10797
10918
  }
10798
10919
  }
10799
10920
  if (!cliOpts.skipErrorFeedback) {
10800
10921
  try {
10801
10922
  await runErrorFeedback(codegenProvider, workingDir, extractedDsl, { maxCycles: 1 });
10802
10923
  } catch (err) {
10803
- console.log(import_chalk18.default.yellow(` Error feedback failed: ${err.message}`));
10924
+ console.log(import_chalk19.default.yellow(` Error feedback failed: ${err.message}`));
10804
10925
  }
10805
10926
  }
10806
10927
  if (!cliOpts.skipReview) {
10807
- console.log(import_chalk18.default.blue(` [${repoName}] Running code review...`));
10928
+ console.log(import_chalk19.default.blue(` [${repoName}] Running code review...`));
10808
10929
  try {
10809
10930
  const reviewer = new CodeReviewer(specProvider);
10810
10931
  const originalDir = process.cwd();
@@ -10816,9 +10937,9 @@ ${contractContextSection}`;
10816
10937
  process.chdir(originalDir);
10817
10938
  }
10818
10939
  await accumulateReviewKnowledge(specProvider, repoAbsPath, reviewResult);
10819
- console.log(import_chalk18.default.green(` Code review complete.`));
10940
+ console.log(import_chalk19.default.green(` Code review complete.`));
10820
10941
  } catch (err) {
10821
- console.log(import_chalk18.default.yellow(` Code review failed: ${err.message}`));
10942
+ console.log(import_chalk19.default.yellow(` Code review failed: ${err.message}`));
10822
10943
  }
10823
10944
  }
10824
10945
  return { dsl: extractedDsl, specFile };
@@ -10841,7 +10962,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10841
10962
  codegenModel: codegenModelName
10842
10963
  });
10843
10964
  const workspaceLoader = new WorkspaceLoader(currentDir);
10844
- console.log(import_chalk18.default.blue("\n[W1] Loading per-repo contexts..."));
10965
+ console.log(import_chalk19.default.blue("\n[W1] Loading per-repo contexts..."));
10845
10966
  const contexts = /* @__PURE__ */ new Map();
10846
10967
  const frontendContexts = /* @__PURE__ */ new Map();
10847
10968
  for (const repo of workspace.repos) {
@@ -10853,27 +10974,27 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10853
10974
  if (repo.role === "frontend" || repo.role === "mobile") {
10854
10975
  const fctx = await loadFrontendContext(repoAbsPath);
10855
10976
  frontendContexts.set(repo.name, fctx);
10856
- console.log(import_chalk18.default.gray(` ${repo.name}: ${fctx.framework} / ${fctx.httpClient} / hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
10977
+ console.log(import_chalk19.default.gray(` ${repo.name}: ${fctx.framework} / ${fctx.httpClient} / hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
10857
10978
  } else {
10858
- console.log(import_chalk18.default.gray(` ${repo.name}: ${ctx.techStack.join(", ") || "unknown"} (${ctx.dependencies.length} deps)`));
10979
+ console.log(import_chalk19.default.gray(` ${repo.name}: ${ctx.techStack.join(", ") || "unknown"} (${ctx.dependencies.length} deps)`));
10859
10980
  }
10860
10981
  } catch (err) {
10861
- console.log(import_chalk18.default.yellow(` ${repo.name}: context load failed \u2014 ${err.message}`));
10982
+ console.log(import_chalk19.default.yellow(` ${repo.name}: context load failed \u2014 ${err.message}`));
10862
10983
  }
10863
10984
  }
10864
- console.log(import_chalk18.default.blue("\n[W2] Decomposing requirement across repos..."));
10985
+ console.log(import_chalk19.default.blue("\n[W2] Decomposing requirement across repos..."));
10865
10986
  const decomposer = new RequirementDecomposer(specProvider);
10866
10987
  let decomposition;
10867
10988
  try {
10868
10989
  decomposition = await decomposer.decompose(idea, workspace, contexts, frontendContexts);
10869
- console.log(import_chalk18.default.green(` Summary: ${decomposition.summary}`));
10870
- console.log(import_chalk18.default.gray(` Repos affected: ${decomposition.repos.map((r) => r.repoName).join(", ")}`));
10990
+ console.log(import_chalk19.default.green(` Summary: ${decomposition.summary}`));
10991
+ console.log(import_chalk19.default.gray(` Repos affected: ${decomposition.repos.map((r) => r.repoName).join(", ")}`));
10871
10992
  if (decomposition.coordinationNotes) {
10872
- console.log(import_chalk18.default.gray(` Coordination: ${decomposition.coordinationNotes}`));
10993
+ console.log(import_chalk19.default.gray(` Coordination: ${decomposition.coordinationNotes}`));
10873
10994
  }
10874
10995
  } catch (err) {
10875
- console.error(import_chalk18.default.red(` Decomposition failed: ${err.message}`));
10876
- console.log(import_chalk18.default.yellow(" Falling back to running all repos independently."));
10996
+ console.error(import_chalk19.default.red(` Decomposition failed: ${err.message}`));
10997
+ console.log(import_chalk19.default.yellow(" Falling back to running all repos independently."));
10877
10998
  decomposition = {
10878
10999
  originalRequirement: idea,
10879
11000
  summary: idea,
@@ -10889,11 +11010,11 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10889
11010
  };
10890
11011
  }
10891
11012
  if (!opts.auto) {
10892
- console.log(import_chalk18.default.cyan("\n[W3] Decomposition Preview:"));
10893
- console.log(import_chalk18.default.cyan("\u2500".repeat(52)));
11013
+ console.log(import_chalk19.default.cyan("\n[W3] Decomposition Preview:"));
11014
+ console.log(import_chalk19.default.cyan("\u2500".repeat(52)));
10894
11015
  for (const r of decomposition.repos) {
10895
- console.log(import_chalk18.default.bold(` ${r.repoName} (${r.role})`));
10896
- console.log(import_chalk18.default.gray(` ${r.specIdea.slice(0, 150)}${r.specIdea.length > 150 ? "..." : ""}`));
11016
+ console.log(import_chalk19.default.bold(` ${r.repoName} (${r.role})`));
11017
+ console.log(import_chalk19.default.gray(` ${r.specIdea.slice(0, 150)}${r.specIdea.length > 150 ? "..." : ""}`));
10897
11018
  if (r.uxDecisions) {
10898
11019
  const ux = r.uxDecisions;
10899
11020
  const uxSummary = [
@@ -10902,13 +11023,13 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10902
11023
  ux.optimisticUpdate ? "optimistic-update" : "",
10903
11024
  ux.errorRollback ? "rollback" : ""
10904
11025
  ].filter(Boolean).join(", ");
10905
- if (uxSummary) console.log(import_chalk18.default.cyan(` UX: ${uxSummary}`));
11026
+ if (uxSummary) console.log(import_chalk19.default.cyan(` UX: ${uxSummary}`));
10906
11027
  }
10907
11028
  if (r.dependsOnRepos.length > 0) {
10908
- console.log(import_chalk18.default.gray(` Depends on: ${r.dependsOnRepos.join(", ")}`));
11029
+ console.log(import_chalk19.default.gray(` Depends on: ${r.dependsOnRepos.join(", ")}`));
10909
11030
  }
10910
11031
  }
10911
- console.log(import_chalk18.default.cyan("\u2500".repeat(52)));
11032
+ console.log(import_chalk19.default.cyan("\u2500".repeat(52)));
10912
11033
  const gate = await (0, import_prompts3.select)({
10913
11034
  message: "Proceed with multi-repo pipeline?",
10914
11035
  choices: [
@@ -10917,24 +11038,24 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10917
11038
  ]
10918
11039
  });
10919
11040
  if (gate === "abort") {
10920
- console.log(import_chalk18.default.yellow(" Aborted."));
11041
+ console.log(import_chalk19.default.yellow(" Aborted."));
10921
11042
  process.exit(0);
10922
11043
  }
10923
11044
  }
10924
11045
  const sortedRepoRequirements = RequirementDecomposer.sortByDependency(decomposition.repos);
10925
11046
  const contractDsls = /* @__PURE__ */ new Map();
10926
- console.log(import_chalk18.default.blue(`
11047
+ console.log(import_chalk19.default.blue(`
10927
11048
  [W4] Running pipeline for ${sortedRepoRequirements.length} repo(s)...`));
10928
11049
  const results = [];
10929
11050
  for (const repoReq of sortedRepoRequirements) {
10930
11051
  const repoConfig = workspace.repos.find((r) => r.name === repoReq.repoName);
10931
11052
  if (!repoConfig) {
10932
- console.log(import_chalk18.default.yellow(` Skipping ${repoReq.repoName} \u2014 not found in workspace config.`));
11053
+ console.log(import_chalk19.default.yellow(` Skipping ${repoReq.repoName} \u2014 not found in workspace config.`));
10933
11054
  results.push({ repoName: repoReq.repoName, status: "skipped", specFile: null, dsl: null, repoAbsPath: "", role: repoReq.role });
10934
11055
  continue;
10935
11056
  }
10936
11057
  const repoAbsPath = workspaceLoader.resolveAbsPath(repoConfig);
10937
- console.log(import_chalk18.default.bold.blue(`
11058
+ console.log(import_chalk19.default.bold.blue(`
10938
11059
  \u2500\u2500 ${repoReq.repoName} (${repoReq.role}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
10939
11060
  let contractContextSection;
10940
11061
  if (repoReq.dependsOnRepos.length > 0) {
@@ -10942,7 +11063,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10942
11063
  for (const depName of repoReq.dependsOnRepos) {
10943
11064
  const depDsl = contractDsls.get(depName);
10944
11065
  if (depDsl) {
10945
- console.log(import_chalk18.default.gray(` Using API contract from: ${depName}`));
11066
+ console.log(import_chalk19.default.gray(` Using API contract from: ${depName}`));
10946
11067
  const contract = buildFrontendApiContract(depDsl);
10947
11068
  contractParts.push(buildContractContextSection(contract));
10948
11069
  }
@@ -10962,7 +11083,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10962
11083
  frontendContext: frontendCtx
10963
11084
  });
10964
11085
  contractContextSection = void 0;
10965
- console.log(import_chalk18.default.gray(` Frontend context: ${frontendCtx.framework} / ${frontendCtx.httpClient} / ${frontendCtx.uiLibrary}`));
11086
+ console.log(import_chalk19.default.gray(` Frontend context: ${frontendCtx.framework} / ${frontendCtx.httpClient} / ${frontendCtx.uiLibrary}`));
10966
11087
  }
10967
11088
  try {
10968
11089
  const { dsl, specFile } = await runSingleRepoPipelineInWorkspace({
@@ -10979,22 +11100,22 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10979
11100
  });
10980
11101
  if (repoReq.isContractProvider && dsl) {
10981
11102
  contractDsls.set(repoReq.repoName, dsl);
10982
- console.log(import_chalk18.default.green(` Contract stored for downstream repos.`));
11103
+ console.log(import_chalk19.default.green(` Contract stored for downstream repos.`));
10983
11104
  }
10984
11105
  results.push({ repoName: repoReq.repoName, status: "success", specFile, dsl, repoAbsPath, role: repoReq.role });
10985
- console.log(import_chalk18.default.green(` \u2714 ${repoReq.repoName} complete`));
11106
+ console.log(import_chalk19.default.green(` \u2714 ${repoReq.repoName} complete`));
10986
11107
  } catch (err) {
10987
- console.error(import_chalk18.default.red(` \u2718 ${repoReq.repoName} failed: ${err.message}`));
11108
+ console.error(import_chalk19.default.red(` \u2718 ${repoReq.repoName} failed: ${err.message}`));
10988
11109
  results.push({ repoName: repoReq.repoName, status: "failed", specFile: null, dsl: null, repoAbsPath, role: repoReq.role });
10989
11110
  }
10990
11111
  }
10991
- console.log(import_chalk18.default.bold.green("\n\u2714 Multi-repo pipeline complete!"));
10992
- console.log(import_chalk18.default.gray(` Workspace: ${workspace.name}`));
10993
- console.log(import_chalk18.default.gray(` Requirement: ${idea}`));
11112
+ console.log(import_chalk19.default.bold.green("\n\u2714 Multi-repo pipeline complete!"));
11113
+ console.log(import_chalk19.default.gray(` Workspace: ${workspace.name}`));
11114
+ console.log(import_chalk19.default.gray(` Requirement: ${idea}`));
10994
11115
  console.log();
10995
11116
  for (const r of results) {
10996
- const icon = r.status === "success" ? import_chalk18.default.green("\u2714") : r.status === "failed" ? import_chalk18.default.red("\u2718") : import_chalk18.default.gray("\u2212");
10997
- const specInfo = r.specFile ? import_chalk18.default.gray(` \u2192 ${r.specFile}`) : "";
11117
+ const icon = r.status === "success" ? import_chalk19.default.green("\u2714") : r.status === "failed" ? import_chalk19.default.red("\u2718") : import_chalk19.default.gray("\u2212");
11118
+ const specInfo = r.specFile ? import_chalk19.default.gray(` \u2192 ${r.specFile}`) : "";
10998
11119
  console.log(` ${icon} ${r.repoName} (${r.status})${specInfo}`);
10999
11120
  }
11000
11121
  return results;
@@ -11009,11 +11130,11 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11009
11130
  default: false
11010
11131
  });
11011
11132
  if (!overwrite) {
11012
- console.log(import_chalk18.default.gray(" Cancelled."));
11133
+ console.log(import_chalk19.default.gray(" Cancelled."));
11013
11134
  return;
11014
11135
  }
11015
11136
  }
11016
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Workspace Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11137
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Workspace Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11017
11138
  const workspaceName = await (0, import_prompts3.input)({
11018
11139
  message: "Workspace name:",
11019
11140
  validate: (v2) => v2.trim().length > 0 || "Name cannot be empty"
@@ -11027,11 +11148,11 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11027
11148
  const workspaceLoader = new WorkspaceLoader(currentDir);
11028
11149
  const detected = await workspaceLoader.autoDetect();
11029
11150
  if (detected.length === 0) {
11030
- console.log(import_chalk18.default.yellow(" No recognizable repos found in sibling directories."));
11151
+ console.log(import_chalk19.default.yellow(" No recognizable repos found in sibling directories."));
11031
11152
  } else {
11032
- console.log(import_chalk18.default.cyan("\n Detected repos:"));
11153
+ console.log(import_chalk19.default.cyan("\n Detected repos:"));
11033
11154
  for (const r of detected) {
11034
- console.log(import_chalk18.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11155
+ console.log(import_chalk19.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11035
11156
  }
11036
11157
  const keepAll = await (0, import_prompts3.confirm)({
11037
11158
  message: `Include all ${detected.length} detected repo(s)?`,
@@ -11048,7 +11169,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11048
11169
  if (keep) repos.push(r);
11049
11170
  }
11050
11171
  }
11051
- console.log(import_chalk18.default.green(` \u2714 ${repos.length} repo(s) added from auto-scan.`));
11172
+ console.log(import_chalk19.default.green(` \u2714 ${repos.length} repo(s) added from auto-scan.`));
11052
11173
  }
11053
11174
  }
11054
11175
  const repoTypeChoices = [
@@ -11070,7 +11191,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11070
11191
  default: repos.length === 0
11071
11192
  });
11072
11193
  while (addMore) {
11073
- console.log(import_chalk18.default.cyan(`
11194
+ console.log(import_chalk19.default.cyan(`
11074
11195
  Adding repo #${repos.length + 1}`));
11075
11196
  const repoName = await (0, import_prompts3.input)({
11076
11197
  message: "Repo name (e.g. api, web, app):",
@@ -11091,9 +11212,9 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11091
11212
  const { type, role } = await detectRepoType(absPath);
11092
11213
  detectedType = type;
11093
11214
  detectedRole = role;
11094
- console.log(import_chalk18.default.gray(` Auto-detected: type=${type}, role=${role}`));
11215
+ console.log(import_chalk19.default.gray(` Auto-detected: type=${type}, role=${role}`));
11095
11216
  } else {
11096
- console.log(import_chalk18.default.yellow(` Path "${absPath}" not found \u2014 type/role will be manual.`));
11217
+ console.log(import_chalk19.default.yellow(` Path "${absPath}" not found \u2014 type/role will be manual.`));
11097
11218
  }
11098
11219
  const repoType = await (0, import_prompts3.select)({
11099
11220
  message: `Repo type for "${repoName}":`,
@@ -11116,53 +11237,53 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11116
11237
  type: repoType,
11117
11238
  role: repoRole
11118
11239
  });
11119
- console.log(import_chalk18.default.green(` \u2714 Added: ${repoName} (${repoRole}, ${repoType})`));
11240
+ console.log(import_chalk19.default.green(` \u2714 Added: ${repoName} (${repoRole}, ${repoType})`));
11120
11241
  addMore = await (0, import_prompts3.confirm)({
11121
11242
  message: "Add another repo?",
11122
11243
  default: false
11123
11244
  });
11124
11245
  }
11125
11246
  const workspaceConfig = { name: workspaceName, repos };
11126
- console.log(import_chalk18.default.cyan("\n Workspace summary:"));
11127
- console.log(import_chalk18.default.gray(` Name: ${workspaceName}`));
11247
+ console.log(import_chalk19.default.cyan("\n Workspace summary:"));
11248
+ console.log(import_chalk19.default.gray(` Name: ${workspaceName}`));
11128
11249
  for (const r of repos) {
11129
- console.log(import_chalk18.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11250
+ console.log(import_chalk19.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11130
11251
  }
11131
11252
  const ok = await (0, import_prompts3.confirm)({ message: `Save to ${WORKSPACE_CONFIG_FILE}?`, default: true });
11132
11253
  if (!ok) {
11133
- console.log(import_chalk18.default.gray(" Cancelled."));
11254
+ console.log(import_chalk19.default.gray(" Cancelled."));
11134
11255
  return;
11135
11256
  }
11136
11257
  const loader = new WorkspaceLoader(currentDir);
11137
11258
  const saved = await loader.save(workspaceConfig);
11138
- console.log(import_chalk18.default.green(`
11259
+ console.log(import_chalk19.default.green(`
11139
11260
  \u2714 Workspace saved: ${saved}`));
11140
- console.log(import_chalk18.default.gray(` Run \`ai-spec create "your feature"\` \u2014 workspace mode will activate automatically.`));
11261
+ console.log(import_chalk19.default.gray(` Run \`ai-spec create "your feature"\` \u2014 workspace mode will activate automatically.`));
11141
11262
  });
11142
11263
  workspaceCmd.command("status").description("Show current workspace configuration").action(async () => {
11143
11264
  const currentDir = process.cwd();
11144
11265
  const loader = new WorkspaceLoader(currentDir);
11145
11266
  const config2 = await loader.load();
11146
11267
  if (!config2) {
11147
- console.log(import_chalk18.default.yellow(`No ${WORKSPACE_CONFIG_FILE} found in ${currentDir}`));
11148
- console.log(import_chalk18.default.gray(" Run `ai-spec workspace init` to create one."));
11268
+ console.log(import_chalk19.default.yellow(`No ${WORKSPACE_CONFIG_FILE} found in ${currentDir}`));
11269
+ console.log(import_chalk19.default.gray(" Run `ai-spec workspace init` to create one."));
11149
11270
  return;
11150
11271
  }
11151
- console.log(import_chalk18.default.bold(`
11272
+ console.log(import_chalk19.default.bold(`
11152
11273
  Workspace: ${config2.name}`));
11153
- console.log(import_chalk18.default.gray(` Config: ${path22.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11154
- console.log(import_chalk18.default.gray(` Repos (${config2.repos.length}):
11274
+ console.log(import_chalk19.default.gray(` Config: ${path22.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11275
+ console.log(import_chalk19.default.gray(` Repos (${config2.repos.length}):
11155
11276
  `));
11156
11277
  for (const repo of config2.repos) {
11157
11278
  const absPath = loader.resolveAbsPath(repo);
11158
11279
  const exists = await fs23.pathExists(absPath);
11159
- const status = exists ? import_chalk18.default.green("found") : import_chalk18.default.red("not found");
11280
+ const status = exists ? import_chalk19.default.green("found") : import_chalk19.default.red("not found");
11160
11281
  console.log(
11161
- ` ${import_chalk18.default.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
11282
+ ` ${import_chalk19.default.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
11162
11283
  );
11163
- console.log(import_chalk18.default.gray(` path: ${absPath}`));
11284
+ console.log(import_chalk19.default.gray(` path: ${absPath}`));
11164
11285
  if (repo.constitution) {
11165
- console.log(import_chalk18.default.green(` constitution: found`));
11286
+ console.log(import_chalk19.default.green(` constitution: found`));
11166
11287
  }
11167
11288
  }
11168
11289
  });
@@ -11179,30 +11300,30 @@ program.command("update").description("Update an existing spec with a change req
11179
11300
  const modelName = opts.model || config2.model || DEFAULT_MODELS[providerName];
11180
11301
  const apiKey = await resolveApiKey(providerName, opts.key);
11181
11302
  const provider = createProvider(providerName, apiKey, modelName);
11182
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 ai-spec update \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11183
- console.log(import_chalk18.default.gray(` Provider: ${providerName}/${modelName}`));
11303
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec update \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11304
+ console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
11184
11305
  const updateRunId = generateRunId();
11185
11306
  const updateSnapshot = new RunSnapshot(currentDir, updateRunId);
11186
11307
  setActiveSnapshot(updateSnapshot);
11187
11308
  const updateLogger = new RunLogger(currentDir, updateRunId, { provider: providerName, model: modelName });
11188
11309
  setActiveLogger(updateLogger);
11189
- console.log(import_chalk18.default.gray(` Run ID: ${updateRunId}`));
11310
+ console.log(import_chalk19.default.gray(` Run ID: ${updateRunId}`));
11190
11311
  let specPath = opts.spec ?? null;
11191
11312
  if (!specPath) {
11192
11313
  const specsDir = path22.join(currentDir, "specs");
11193
11314
  const latest = await SpecUpdater.findLatestSpec(specsDir);
11194
11315
  if (!latest) {
11195
- console.error(import_chalk18.default.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11316
+ console.error(import_chalk19.default.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11196
11317
  process.exit(1);
11197
11318
  }
11198
11319
  specPath = latest.filePath;
11199
- console.log(import_chalk18.default.gray(` Using spec: ${path22.relative(currentDir, specPath)} (v${latest.version})`));
11320
+ console.log(import_chalk19.default.gray(` Using spec: ${path22.relative(currentDir, specPath)} (v${latest.version})`));
11200
11321
  }
11201
- console.log(import_chalk18.default.gray(" Loading project context..."));
11322
+ console.log(import_chalk19.default.gray(" Loading project context..."));
11202
11323
  const loader = new ContextLoader(currentDir);
11203
11324
  const context = await loader.loadProjectContext();
11204
11325
  if (context.constitution && context.constitution.length > 6e3) {
11205
- console.log(import_chalk18.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
11326
+ console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
11206
11327
  }
11207
11328
  const { detectRepoType: _detectRepoType } = await Promise.resolve().then(() => (init_workspace_loader(), workspace_loader_exports));
11208
11329
  const { type: repoType } = await _detectRepoType(currentDir);
@@ -11214,19 +11335,19 @@ program.command("update").description("Update an existing spec with a change req
11214
11335
  repoType
11215
11336
  });
11216
11337
  } catch (err) {
11217
- console.error(import_chalk18.default.red(` Update failed: ${err.message}`));
11338
+ console.error(import_chalk19.default.red(` Update failed: ${err.message}`));
11218
11339
  process.exit(1);
11219
11340
  }
11220
- console.log(import_chalk18.default.green(`
11341
+ console.log(import_chalk19.default.green(`
11221
11342
  \u2714 Spec updated \u2192 v${result.newVersion}: ${path22.relative(currentDir, result.newSpecPath)}`));
11222
11343
  if (result.newDslPath) {
11223
- console.log(import_chalk18.default.green(` \u2714 DSL updated: ${path22.relative(currentDir, result.newDslPath)}`));
11344
+ console.log(import_chalk19.default.green(` \u2714 DSL updated: ${path22.relative(currentDir, result.newDslPath)}`));
11224
11345
  }
11225
11346
  if (result.affectedFiles.length > 0) {
11226
- console.log(import_chalk18.default.cyan("\n Affected files:"));
11347
+ console.log(import_chalk19.default.cyan("\n Affected files:"));
11227
11348
  for (const f of result.affectedFiles) {
11228
- const icon = f.action === "create" ? import_chalk18.default.green("+") : import_chalk18.default.yellow("~");
11229
- console.log(` ${icon} ${f.file}: ${import_chalk18.default.gray(f.description)}`);
11349
+ const icon = f.action === "create" ? import_chalk19.default.green("+") : import_chalk19.default.yellow("~");
11350
+ console.log(` ${icon} ${f.file}: ${import_chalk19.default.gray(f.description)}`);
11230
11351
  }
11231
11352
  }
11232
11353
  if (opts.codegen && result.affectedFiles.length > 0) {
@@ -11234,7 +11355,7 @@ program.command("update").description("Update an existing spec with a change req
11234
11355
  const codegenModelName = opts.codegenModel || config2.codegenModel || DEFAULT_MODELS[codegenProviderName];
11235
11356
  const codegenApiKey = opts.codegenKey ?? (codegenProviderName === providerName ? apiKey : await resolveApiKey(codegenProviderName, opts.codegenKey));
11236
11357
  const codegenProvider = createProvider(codegenProviderName, codegenApiKey, codegenModelName);
11237
- console.log(import_chalk18.default.blue("\n Regenerating affected files..."));
11358
+ console.log(import_chalk19.default.blue("\n Regenerating affected files..."));
11238
11359
  const codeGenerator = new CodeGenerator(codegenProvider, "api");
11239
11360
  const specContent = await fs23.readFile(result.newSpecPath, "utf-8");
11240
11361
  const constitutionSection = context.constitution ? `
@@ -11264,7 +11385,7 @@ ${specContent}
11264
11385
  ${constitutionSection}${dslSection}
11265
11386
  === ${existing ? "Current File (return the FULL updated content)" : "New File"} ===
11266
11387
  ${existing || "Create from scratch."}`;
11267
- process.stdout.write(` ${existing ? import_chalk18.default.yellow("~") : import_chalk18.default.green("+")} ${affected.file}... `);
11388
+ process.stdout.write(` ${existing ? import_chalk19.default.yellow("~") : import_chalk19.default.green("+")} ${affected.file}... `);
11268
11389
  try {
11269
11390
  const { getCodeGenSystemPrompt: _getPrompt } = await Promise.resolve().then(() => (init_codegen_prompt(), codegen_prompt_exports));
11270
11391
  const raw = await codegenProvider.generate(codePrompt, _getPrompt(repoType));
@@ -11273,10 +11394,10 @@ ${existing || "Create from scratch."}`;
11273
11394
  await updateSnapshot.snapshotFile(fullPath);
11274
11395
  await fs23.writeFile(fullPath, content, "utf-8");
11275
11396
  updateLogger.fileWritten(affected.file);
11276
- console.log(import_chalk18.default.green("\u2714"));
11397
+ console.log(import_chalk19.default.green("\u2714"));
11277
11398
  } catch (err) {
11278
11399
  updateLogger.stageFail("update_codegen", `${affected.file}: ${err.message}`);
11279
- console.log(import_chalk18.default.red(`\u2718 ${err.message}`));
11400
+ console.log(import_chalk19.default.red(`\u2718 ${err.message}`));
11280
11401
  }
11281
11402
  }
11282
11403
  updateLogger.stageEnd("update_codegen", { filesUpdated: result.affectedFiles.length });
@@ -11292,13 +11413,13 @@ ${existing || "Create from scratch."}`;
11292
11413
  updateLogger.finish();
11293
11414
  updateLogger.printSummary();
11294
11415
  if (updateSnapshot.fileCount > 0) {
11295
- console.log(import_chalk18.default.gray(` To undo changes: ai-spec restore ${updateRunId}`));
11416
+ console.log(import_chalk19.default.gray(` To undo changes: ai-spec restore ${updateRunId}`));
11296
11417
  }
11297
11418
  if (!opts.codegen && result.affectedFiles.length > 0) {
11298
- console.log(import_chalk18.default.blue("\n Next steps:"));
11299
- console.log(import_chalk18.default.gray(` \u2022 Re-run with --codegen to regenerate affected files automatically`));
11300
- console.log(import_chalk18.default.gray(` \u2022 Or update files manually based on the affected files list above`));
11301
- console.log(import_chalk18.default.gray(` \u2022 Run \`ai-spec mock\` to refresh the mock server with the new DSL`));
11419
+ console.log(import_chalk19.default.blue("\n Next steps:"));
11420
+ console.log(import_chalk19.default.gray(` \u2022 Re-run with --codegen to regenerate affected files automatically`));
11421
+ console.log(import_chalk19.default.gray(` \u2022 Or update files manually based on the affected files list above`));
11422
+ console.log(import_chalk19.default.gray(` \u2022 Run \`ai-spec mock\` to refresh the mock server with the new DSL`));
11302
11423
  }
11303
11424
  });
11304
11425
  program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (YAML or JSON)").option("--openapi", "Export as OpenAPI 3.1.0 (default behaviour)").option("--format <fmt>", "Output format: yaml | json (default: yaml)", "yaml").option("--output <path>", "Output file path (default: openapi.yaml)").option("--server <url>", "API server URL in the OpenAPI document (default: http://localhost:3000)").option("--dsl <path>", "Path to a specific .dsl.json file (auto-detected if omitted)").action(async (opts) => {
@@ -11307,19 +11428,19 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11307
11428
  if (!dslPath) {
11308
11429
  dslPath = await findLatestDslFile(currentDir);
11309
11430
  if (!dslPath) {
11310
- console.error(import_chalk18.default.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11431
+ console.error(import_chalk19.default.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11311
11432
  process.exit(1);
11312
11433
  }
11313
- console.log(import_chalk18.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11434
+ console.log(import_chalk19.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11314
11435
  }
11315
11436
  let dsl;
11316
11437
  try {
11317
11438
  dsl = await fs23.readJson(dslPath);
11318
11439
  } catch (err) {
11319
- console.error(import_chalk18.default.red(` Failed to read DSL: ${err.message}`));
11440
+ console.error(import_chalk19.default.red(` Failed to read DSL: ${err.message}`));
11320
11441
  process.exit(1);
11321
11442
  }
11322
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 ai-spec export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11443
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11323
11444
  const format = opts.format === "json" ? "json" : "yaml";
11324
11445
  const serverUrl = opts.server || "http://localhost:3000";
11325
11446
  try {
@@ -11329,30 +11450,30 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11329
11450
  outputPath: opts.output
11330
11451
  });
11331
11452
  const rel = path22.relative(currentDir, outputPath);
11332
- console.log(import_chalk18.default.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
11333
- console.log(import_chalk18.default.gray(` Feature : ${dsl.feature.title}`));
11334
- console.log(import_chalk18.default.gray(` Endpoints: ${dsl.endpoints.length}`));
11335
- console.log(import_chalk18.default.gray(` Models : ${dsl.models.length}`));
11336
- console.log(import_chalk18.default.gray(` Server : ${serverUrl}`));
11337
- console.log(import_chalk18.default.blue("\n Next steps:"));
11338
- console.log(import_chalk18.default.gray(` \u2022 Import ${rel} into Postman / Insomnia / Swagger UI`));
11339
- console.log(import_chalk18.default.gray(` \u2022 Use openapi-generator to generate client SDKs`));
11453
+ console.log(import_chalk19.default.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
11454
+ console.log(import_chalk19.default.gray(` Feature : ${dsl.feature.title}`));
11455
+ console.log(import_chalk19.default.gray(` Endpoints: ${dsl.endpoints.length}`));
11456
+ console.log(import_chalk19.default.gray(` Models : ${dsl.models.length}`));
11457
+ console.log(import_chalk19.default.gray(` Server : ${serverUrl}`));
11458
+ console.log(import_chalk19.default.blue("\n Next steps:"));
11459
+ console.log(import_chalk19.default.gray(` \u2022 Import ${rel} into Postman / Insomnia / Swagger UI`));
11460
+ console.log(import_chalk19.default.gray(` \u2022 Use openapi-generator to generate client SDKs`));
11340
11461
  } catch (err) {
11341
- console.error(import_chalk18.default.red(` Export failed: ${err.message}`));
11462
+ console.error(import_chalk19.default.red(` Export failed: ${err.message}`));
11342
11463
  process.exit(1);
11343
11464
  }
11344
11465
  });
11345
11466
  program.command("mock").description("Generate a standalone mock server + proxy config from the latest DSL").option("--port <n>", "Mock server port (default: 3001)", "3001").option("--msw", "Also generate MSW (Mock Service Worker) handlers at src/mocks/").option("--proxy", "Also generate frontend proxy config snippet").option("--dsl <path>", "Path to a specific .dsl.json file (auto-detected if omitted)").option("--workspace", "Generate mock assets for all backend repos in the workspace").option("--serve", "Start mock server in background + patch frontend proxy (use with --frontend)").option("--frontend <path>", "Path to frontend project for proxy patching (used with --serve/--restore)").option("--restore", "Undo proxy changes and stop mock server (requires --frontend or auto-detects)").action(async (opts) => {
11346
11467
  const currentDir = process.cwd();
11347
11468
  const port = parseInt(opts.port, 10) || 3001;
11348
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 ai-spec mock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11469
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec mock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11349
11470
  if (opts.restore) {
11350
11471
  const frontendDir = opts.frontend ? path22.resolve(opts.frontend) : currentDir;
11351
11472
  const r = await restoreMockProxy(frontendDir);
11352
11473
  if (r.restored) {
11353
- console.log(import_chalk18.default.green(" \u2714 Proxy restored and mock server stopped."));
11474
+ console.log(import_chalk19.default.green(" \u2714 Proxy restored and mock server stopped."));
11354
11475
  } else {
11355
- console.log(import_chalk18.default.yellow(` ${r.note ?? "Nothing to restore."}`));
11476
+ console.log(import_chalk19.default.yellow(` ${r.note ?? "Nothing to restore."}`));
11356
11477
  }
11357
11478
  return;
11358
11479
  }
@@ -11360,21 +11481,21 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11360
11481
  const workspaceLoader = new WorkspaceLoader(currentDir);
11361
11482
  const workspaceConfig = await workspaceLoader.load();
11362
11483
  if (!workspaceConfig) {
11363
- console.error(import_chalk18.default.red(` No ${WORKSPACE_CONFIG_FILE} found. Run \`ai-spec workspace init\` first.`));
11484
+ console.error(import_chalk19.default.red(` No ${WORKSPACE_CONFIG_FILE} found. Run \`ai-spec workspace init\` first.`));
11364
11485
  process.exit(1);
11365
11486
  }
11366
11487
  const backendRepos = workspaceConfig.repos.filter((r) => r.role === "backend");
11367
11488
  if (backendRepos.length === 0) {
11368
- console.log(import_chalk18.default.yellow(" No backend repos found in workspace."));
11489
+ console.log(import_chalk19.default.yellow(" No backend repos found in workspace."));
11369
11490
  return;
11370
11491
  }
11371
11492
  for (const repo of backendRepos) {
11372
11493
  const repoAbsPath = workspaceLoader.resolveAbsPath(repo);
11373
- console.log(import_chalk18.default.cyan(`
11494
+ console.log(import_chalk19.default.cyan(`
11374
11495
  Repo: ${repo.name} (${repoAbsPath})`));
11375
11496
  const dslFile = await findLatestDslFile(repoAbsPath);
11376
11497
  if (!dslFile) {
11377
- console.log(import_chalk18.default.yellow(` No DSL file found \u2014 skipping.`));
11498
+ console.log(import_chalk19.default.yellow(` No DSL file found \u2014 skipping.`));
11378
11499
  continue;
11379
11500
  }
11380
11501
  const dsl2 = await fs23.readJson(dslFile);
@@ -11384,8 +11505,8 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11384
11505
  proxy: opts.proxy
11385
11506
  });
11386
11507
  for (const f of result2.files) {
11387
- console.log(import_chalk18.default.green(` \u2714 ${f.path}`));
11388
- console.log(import_chalk18.default.gray(` ${f.description}`));
11508
+ console.log(import_chalk19.default.green(` \u2714 ${f.path}`));
11509
+ console.log(import_chalk19.default.gray(` ${f.description}`));
11389
11510
  }
11390
11511
  }
11391
11512
  return;
@@ -11395,19 +11516,19 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11395
11516
  dslPath = await findLatestDslFile(currentDir);
11396
11517
  if (!dslPath) {
11397
11518
  console.error(
11398
- import_chalk18.default.red(
11519
+ import_chalk19.default.red(
11399
11520
  " No .dsl.json file found in .ai-spec/. Run `ai-spec create` first or use --dsl <path>."
11400
11521
  )
11401
11522
  );
11402
11523
  process.exit(1);
11403
11524
  }
11404
- console.log(import_chalk18.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11525
+ console.log(import_chalk19.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11405
11526
  }
11406
11527
  let dsl;
11407
11528
  try {
11408
11529
  dsl = await fs23.readJson(dslPath);
11409
11530
  } catch (err) {
11410
- console.error(import_chalk18.default.red(` Failed to read DSL file: ${err.message}`));
11531
+ console.error(import_chalk19.default.red(` Failed to read DSL file: ${err.message}`));
11411
11532
  process.exit(1);
11412
11533
  }
11413
11534
  const result = await generateMockAssets(dsl, currentDir, {
@@ -11415,58 +11536,58 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11415
11536
  msw: opts.msw,
11416
11537
  proxy: opts.proxy
11417
11538
  });
11418
- console.log(import_chalk18.default.green(`
11539
+ console.log(import_chalk19.default.green(`
11419
11540
  \u2714 Mock assets generated (${result.files.length} file(s)):`));
11420
11541
  for (const f of result.files) {
11421
- console.log(import_chalk18.default.green(` ${f.path}`));
11422
- console.log(import_chalk18.default.gray(` ${f.description}`));
11542
+ console.log(import_chalk19.default.green(` ${f.path}`));
11543
+ console.log(import_chalk19.default.gray(` ${f.description}`));
11423
11544
  }
11424
11545
  if (opts.serve) {
11425
11546
  const serverJsPath = path22.join(currentDir, "mock", "server.js");
11426
11547
  if (!await fs23.pathExists(serverJsPath)) {
11427
- console.error(import_chalk18.default.red(" mock/server.js not found \u2014 generation may have failed."));
11548
+ console.error(import_chalk19.default.red(" mock/server.js not found \u2014 generation may have failed."));
11428
11549
  process.exit(1);
11429
11550
  }
11430
11551
  const pid = startMockServerBackground(serverJsPath, port);
11431
- console.log(import_chalk18.default.green(`
11552
+ console.log(import_chalk19.default.green(`
11432
11553
  \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${port}`));
11433
11554
  if (opts.frontend) {
11434
11555
  const frontendDir = path22.resolve(opts.frontend);
11435
11556
  const proxyResult = await applyMockProxy(frontendDir, port, dsl.endpoints);
11436
11557
  await saveMockServerPid(frontendDir, pid);
11437
11558
  if (proxyResult.applied) {
11438
- console.log(import_chalk18.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
11439
- console.log(import_chalk18.default.bold.cyan(`
11559
+ console.log(import_chalk19.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
11560
+ console.log(import_chalk19.default.bold.cyan(`
11440
11561
  Ready! Open a new terminal and run:`));
11441
- console.log(import_chalk18.default.white(` cd ${frontendDir}`));
11442
- console.log(import_chalk18.default.white(` ${proxyResult.devCommand}`));
11443
- console.log(import_chalk18.default.gray(`
11562
+ console.log(import_chalk19.default.white(` cd ${frontendDir}`));
11563
+ console.log(import_chalk19.default.white(` ${proxyResult.devCommand}`));
11564
+ console.log(import_chalk19.default.gray(`
11444
11565
  When done: ai-spec mock --restore --frontend ${frontendDir}`));
11445
11566
  } else {
11446
- console.log(import_chalk18.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
11447
- if (proxyResult.note) console.log(import_chalk18.default.gray(` ${proxyResult.note}`));
11567
+ console.log(import_chalk19.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
11568
+ if (proxyResult.note) console.log(import_chalk19.default.gray(` ${proxyResult.note}`));
11448
11569
  }
11449
11570
  } else {
11450
- console.log(import_chalk18.default.gray(` Tip: use --frontend <path> to also auto-patch your frontend proxy config.`));
11451
- console.log(import_chalk18.default.gray(` Mock server: http://localhost:${port}`));
11571
+ console.log(import_chalk19.default.gray(` Tip: use --frontend <path> to also auto-patch your frontend proxy config.`));
11572
+ console.log(import_chalk19.default.gray(` Mock server: http://localhost:${port}`));
11452
11573
  }
11453
11574
  return;
11454
11575
  }
11455
- console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11456
- console.log(import_chalk18.default.white(` 1. Install express (if not already):`));
11457
- console.log(import_chalk18.default.gray(` npm install --save-dev express`));
11458
- console.log(import_chalk18.default.white(` 2. Start mock server:`));
11459
- console.log(import_chalk18.default.gray(` node mock/server.js`));
11460
- console.log(import_chalk18.default.gray(` # or: ai-spec mock --serve --frontend <path-to-frontend>`));
11461
- console.log(import_chalk18.default.white(` 3. Configure your frontend to proxy API calls to:`));
11462
- console.log(import_chalk18.default.gray(` http://localhost:${port}`));
11576
+ console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11577
+ console.log(import_chalk19.default.white(` 1. Install express (if not already):`));
11578
+ console.log(import_chalk19.default.gray(` npm install --save-dev express`));
11579
+ console.log(import_chalk19.default.white(` 2. Start mock server:`));
11580
+ console.log(import_chalk19.default.gray(` node mock/server.js`));
11581
+ console.log(import_chalk19.default.gray(` # or: ai-spec mock --serve --frontend <path-to-frontend>`));
11582
+ console.log(import_chalk19.default.white(` 3. Configure your frontend to proxy API calls to:`));
11583
+ console.log(import_chalk19.default.gray(` http://localhost:${port}`));
11463
11584
  if (opts.proxy) {
11464
- console.log(import_chalk18.default.gray(` (See the generated proxy config file for framework-specific instructions)`));
11585
+ console.log(import_chalk19.default.gray(` (See the generated proxy config file for framework-specific instructions)`));
11465
11586
  }
11466
11587
  if (opts.msw) {
11467
- console.log(import_chalk18.default.white(` 4. MSW: import and start the worker in your app entry:`));
11468
- console.log(import_chalk18.default.gray(` import { worker } from './mocks/browser';`));
11469
- console.log(import_chalk18.default.gray(` if (process.env.NODE_ENV === 'development') worker.start();`));
11588
+ console.log(import_chalk19.default.white(` 4. MSW: import and start the worker in your app entry:`));
11589
+ console.log(import_chalk19.default.gray(` import { worker } from './mocks/browser';`));
11590
+ console.log(import_chalk19.default.gray(` if (process.env.NODE_ENV === 'development') worker.start();`));
11470
11591
  }
11471
11592
  });
11472
11593
  program.command("learn").description("Append a lesson or engineering decision directly to constitution \xA79").argument("[lesson]", "The lesson or decision to record (prompted if omitted)").action(async (lesson) => {
@@ -11482,24 +11603,24 @@ program.command("learn").description("Append a lesson or engineering decision di
11482
11603
  }
11483
11604
  const result = await appendDirectLesson(currentDir, lesson.trim());
11484
11605
  if (result.appended) {
11485
- console.log(import_chalk18.default.green(`
11606
+ console.log(import_chalk19.default.green(`
11486
11607
  \u2714 Lesson appended to constitution \xA79`));
11487
- console.log(import_chalk18.default.gray(` File: .ai-spec-constitution.md`));
11608
+ console.log(import_chalk19.default.gray(` File: .ai-spec-constitution.md`));
11488
11609
  } else {
11489
- console.log(import_chalk18.default.yellow(`
11610
+ console.log(import_chalk19.default.yellow(`
11490
11611
  \u26A0 Not appended: ${result.reason}`));
11491
11612
  }
11492
11613
  });
11493
11614
  program.command("restore").description("Restore files modified by a previous run").argument("<runId>", "Run ID shown at the end of a create / generate run").action(async (runId) => {
11494
11615
  const currentDir = process.cwd();
11495
11616
  const snapshot = new RunSnapshot(currentDir, runId);
11496
- console.log(import_chalk18.default.blue(`Restoring run: ${runId}...`));
11617
+ console.log(import_chalk19.default.blue(`Restoring run: ${runId}...`));
11497
11618
  const restored = await snapshot.restore();
11498
11619
  if (restored.length === 0) {
11499
- console.log(import_chalk18.default.yellow(" No backup found for this run ID."));
11620
+ console.log(import_chalk19.default.yellow(" No backup found for this run ID."));
11500
11621
  } else {
11501
- restored.forEach((f) => console.log(import_chalk18.default.green(` \u2714 restored: ${f}`)));
11502
- console.log(import_chalk18.default.bold.green(`
11622
+ restored.forEach((f) => console.log(import_chalk19.default.green(` \u2714 restored: ${f}`)));
11623
+ console.log(import_chalk19.default.bold.green(`
11503
11624
  \u2714 ${restored.length} file(s) restored.`));
11504
11625
  }
11505
11626
  });