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