ai-spec-dev 0.29.1 → 0.30.1

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.
@@ -402,8 +402,8 @@ var init_workspace_loader = __esm({
402
402
  const repos = [];
403
403
  for (const entry of entries) {
404
404
  const absPath = path17.join(this.workspaceRoot, entry);
405
- const stat4 = await fs18.stat(absPath).catch(() => null);
406
- if (!stat4 || !stat4.isDirectory()) continue;
405
+ const stat3 = await fs18.stat(absPath).catch(() => null);
406
+ if (!stat3 || !stat3.isDirectory()) continue;
407
407
  if (entry.startsWith(".") || entry === "node_modules") continue;
408
408
  if (names && !names.includes(entry)) continue;
409
409
  const hasManifest = await fs18.pathExists(path17.join(absPath, "package.json")) || await fs18.pathExists(path17.join(absPath, "go.mod")) || await fs18.pathExists(path17.join(absPath, "Cargo.toml")) || await fs18.pathExists(path17.join(absPath, "pom.xml")) || await fs18.pathExists(path17.join(absPath, "build.gradle")) || await fs18.pathExists(path17.join(absPath, "requirements.txt")) || await fs18.pathExists(path17.join(absPath, "pyproject.toml")) || await fs18.pathExists(path17.join(absPath, "composer.json"));
@@ -467,9 +467,9 @@ var init_workspace_loader = __esm({
467
467
 
468
468
  // cli/index.ts
469
469
  import { Command } from "commander";
470
- import * as path23 from "path";
471
- import * as fs24 from "fs-extra";
472
- import chalk19 from "chalk";
470
+ import * as path22 from "path";
471
+ import * as fs23 from "fs-extra";
472
+ import chalk18 from "chalk";
473
473
  import * as dotenv from "dotenv";
474
474
  import { input, confirm as confirm2, select as select3 } from "@inquirer/prompts";
475
475
 
@@ -4849,221 +4849,221 @@ function validateDsl(raw) {
4849
4849
  }
4850
4850
  return { valid: true, dsl: raw };
4851
4851
  }
4852
- function validateFeature(raw, path24, errors) {
4852
+ function validateFeature(raw, path23, errors) {
4853
4853
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4854
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4854
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4855
4855
  return;
4856
4856
  }
4857
4857
  const f = raw;
4858
- requireNonEmptyString(f["id"], `${path24}.id`, errors);
4859
- requireNonEmptyString(f["title"], `${path24}.title`, errors);
4860
- requireNonEmptyString(f["description"], `${path24}.description`, errors);
4858
+ requireNonEmptyString(f["id"], `${path23}.id`, errors);
4859
+ requireNonEmptyString(f["title"], `${path23}.title`, errors);
4860
+ requireNonEmptyString(f["description"], `${path23}.description`, errors);
4861
4861
  }
4862
- function validateModel(raw, path24, errors) {
4862
+ function validateModel(raw, path23, errors) {
4863
4863
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4864
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4864
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4865
4865
  return;
4866
4866
  }
4867
4867
  const m = raw;
4868
- requireNonEmptyString(m["name"], `${path24}.name`, errors);
4868
+ requireNonEmptyString(m["name"], `${path23}.name`, errors);
4869
4869
  if (!Array.isArray(m["fields"])) {
4870
- errors.push({ path: `${path24}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4870
+ errors.push({ path: `${path23}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4871
4871
  } else {
4872
4872
  const fields = m["fields"];
4873
4873
  if (fields.length > MAX_FIELDS_PER_MODEL) {
4874
- errors.push({ path: `${path24}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4874
+ errors.push({ path: `${path23}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4875
4875
  }
4876
4876
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
4877
- validateModelField(fields[j2], `${path24}.fields[${j2}]`, errors);
4877
+ validateModelField(fields[j2], `${path23}.fields[${j2}]`, errors);
4878
4878
  }
4879
4879
  }
4880
4880
  if (m["relations"] !== void 0) {
4881
4881
  if (!Array.isArray(m["relations"])) {
4882
- errors.push({ path: `${path24}.relations`, message: "Must be an array of strings if present" });
4882
+ errors.push({ path: `${path23}.relations`, message: "Must be an array of strings if present" });
4883
4883
  } else {
4884
4884
  const rels = m["relations"];
4885
4885
  for (let j2 = 0; j2 < rels.length; j2++) {
4886
4886
  if (typeof rels[j2] !== "string") {
4887
- errors.push({ path: `${path24}.relations[${j2}]`, message: "Must be a string" });
4887
+ errors.push({ path: `${path23}.relations[${j2}]`, message: "Must be a string" });
4888
4888
  }
4889
4889
  }
4890
4890
  }
4891
4891
  }
4892
4892
  }
4893
- function validateModelField(raw, path24, errors) {
4893
+ function validateModelField(raw, path23, errors) {
4894
4894
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4895
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4895
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4896
4896
  return;
4897
4897
  }
4898
4898
  const f = raw;
4899
- requireNonEmptyString(f["name"], `${path24}.name`, errors);
4900
- requireNonEmptyString(f["type"], `${path24}.type`, errors);
4899
+ requireNonEmptyString(f["name"], `${path23}.name`, errors);
4900
+ requireNonEmptyString(f["type"], `${path23}.type`, errors);
4901
4901
  if (typeof f["required"] !== "boolean") {
4902
- errors.push({ path: `${path24}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4902
+ errors.push({ path: `${path23}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4903
4903
  }
4904
4904
  }
4905
- function validateEndpoint(raw, path24, errors) {
4905
+ function validateEndpoint(raw, path23, errors) {
4906
4906
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4907
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4907
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4908
4908
  return;
4909
4909
  }
4910
4910
  const e = raw;
4911
- requireNonEmptyString(e["id"], `${path24}.id`, errors);
4912
- requireNonEmptyString(e["description"], `${path24}.description`, errors);
4911
+ requireNonEmptyString(e["id"], `${path23}.id`, errors);
4912
+ requireNonEmptyString(e["description"], `${path23}.description`, errors);
4913
4913
  if (!VALID_METHODS.includes(e["method"])) {
4914
4914
  errors.push({
4915
- path: `${path24}.method`,
4915
+ path: `${path23}.method`,
4916
4916
  message: `Must be one of ${VALID_METHODS.join("|")}, got: ${JSON.stringify(e["method"])}`
4917
4917
  });
4918
4918
  }
4919
4919
  if (typeof e["path"] !== "string" || !e["path"].startsWith("/")) {
4920
4920
  errors.push({
4921
- path: `${path24}.path`,
4921
+ path: `${path23}.path`,
4922
4922
  message: `Must be a string starting with "/", got: ${JSON.stringify(e["path"])}`
4923
4923
  });
4924
4924
  }
4925
4925
  if (typeof e["auth"] !== "boolean") {
4926
- errors.push({ path: `${path24}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4926
+ errors.push({ path: `${path23}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4927
4927
  }
4928
4928
  if (typeof e["successStatus"] !== "number" || e["successStatus"] < 100 || e["successStatus"] > 599) {
4929
4929
  errors.push({
4930
- path: `${path24}.successStatus`,
4930
+ path: `${path23}.successStatus`,
4931
4931
  message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["successStatus"])}`
4932
4932
  });
4933
4933
  }
4934
- requireNonEmptyString(e["successDescription"], `${path24}.successDescription`, errors);
4934
+ requireNonEmptyString(e["successDescription"], `${path23}.successDescription`, errors);
4935
4935
  if (e["request"] !== void 0) {
4936
- validateRequestSchema(e["request"], `${path24}.request`, errors);
4936
+ validateRequestSchema(e["request"], `${path23}.request`, errors);
4937
4937
  }
4938
4938
  if (e["errors"] !== void 0) {
4939
4939
  if (!Array.isArray(e["errors"])) {
4940
- errors.push({ path: `${path24}.errors`, message: "Must be an array if present" });
4940
+ errors.push({ path: `${path23}.errors`, message: "Must be an array if present" });
4941
4941
  } else {
4942
4942
  const errs = e["errors"];
4943
4943
  if (errs.length > MAX_ERRORS_PER_ENDPOINT) {
4944
- errors.push({ path: `${path24}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4944
+ errors.push({ path: `${path23}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4945
4945
  }
4946
4946
  for (let j2 = 0; j2 < Math.min(errs.length, MAX_ERRORS_PER_ENDPOINT); j2++) {
4947
- validateResponseError(errs[j2], `${path24}.errors[${j2}]`, errors);
4947
+ validateResponseError(errs[j2], `${path23}.errors[${j2}]`, errors);
4948
4948
  }
4949
4949
  }
4950
4950
  }
4951
4951
  }
4952
- function validateRequestSchema(raw, path24, errors) {
4952
+ function validateRequestSchema(raw, path23, errors) {
4953
4953
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4954
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4954
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4955
4955
  return;
4956
4956
  }
4957
4957
  const r = raw;
4958
4958
  for (const key of ["body", "query", "params"]) {
4959
4959
  if (r[key] !== void 0) {
4960
- validateFieldMap(r[key], `${path24}.${key}`, errors);
4960
+ validateFieldMap(r[key], `${path23}.${key}`, errors);
4961
4961
  }
4962
4962
  }
4963
4963
  }
4964
- function validateFieldMap(raw, path24, errors) {
4964
+ function validateFieldMap(raw, path23, errors) {
4965
4965
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4966
- errors.push({ path: path24, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4966
+ errors.push({ path: path23, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4967
4967
  return;
4968
4968
  }
4969
4969
  const map = raw;
4970
4970
  for (const [k2, v2] of Object.entries(map)) {
4971
4971
  if (typeof v2 !== "string") {
4972
- errors.push({ path: `${path24}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4972
+ errors.push({ path: `${path23}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4973
4973
  }
4974
4974
  }
4975
4975
  }
4976
- function validateResponseError(raw, path24, errors) {
4976
+ function validateResponseError(raw, path23, errors) {
4977
4977
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4978
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4978
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4979
4979
  return;
4980
4980
  }
4981
4981
  const e = raw;
4982
4982
  if (typeof e["status"] !== "number" || e["status"] < 100 || e["status"] > 599) {
4983
- errors.push({ path: `${path24}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
4983
+ errors.push({ path: `${path23}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
4984
4984
  }
4985
- requireNonEmptyString(e["code"], `${path24}.code`, errors);
4986
- requireNonEmptyString(e["description"], `${path24}.description`, errors);
4985
+ requireNonEmptyString(e["code"], `${path23}.code`, errors);
4986
+ requireNonEmptyString(e["description"], `${path23}.description`, errors);
4987
4987
  }
4988
- function validateBehavior(raw, path24, errors) {
4988
+ function validateBehavior(raw, path23, errors) {
4989
4989
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4990
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4990
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4991
4991
  return;
4992
4992
  }
4993
4993
  const b = raw;
4994
- requireNonEmptyString(b["id"], `${path24}.id`, errors);
4995
- requireNonEmptyString(b["description"], `${path24}.description`, errors);
4994
+ requireNonEmptyString(b["id"], `${path23}.id`, errors);
4995
+ requireNonEmptyString(b["description"], `${path23}.description`, errors);
4996
4996
  if (b["constraints"] !== void 0) {
4997
4997
  if (!Array.isArray(b["constraints"])) {
4998
- errors.push({ path: `${path24}.constraints`, message: "Must be an array of strings if present" });
4998
+ errors.push({ path: `${path23}.constraints`, message: "Must be an array of strings if present" });
4999
4999
  } else {
5000
5000
  const cs2 = b["constraints"];
5001
5001
  for (let j2 = 0; j2 < cs2.length; j2++) {
5002
5002
  if (typeof cs2[j2] !== "string") {
5003
- errors.push({ path: `${path24}.constraints[${j2}]`, message: "Must be a string" });
5003
+ errors.push({ path: `${path23}.constraints[${j2}]`, message: "Must be a string" });
5004
5004
  }
5005
5005
  }
5006
5006
  }
5007
5007
  }
5008
5008
  }
5009
- function validateComponent(raw, path24, errors) {
5009
+ function validateComponent(raw, path23, errors) {
5010
5010
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5011
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
5011
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
5012
5012
  return;
5013
5013
  }
5014
5014
  const c = raw;
5015
- requireNonEmptyString(c["id"], `${path24}.id`, errors);
5016
- requireNonEmptyString(c["name"], `${path24}.name`, errors);
5017
- requireNonEmptyString(c["description"], `${path24}.description`, errors);
5015
+ requireNonEmptyString(c["id"], `${path23}.id`, errors);
5016
+ requireNonEmptyString(c["name"], `${path23}.name`, errors);
5017
+ requireNonEmptyString(c["description"], `${path23}.description`, errors);
5018
5018
  if (c["props"] !== void 0) {
5019
5019
  if (!Array.isArray(c["props"])) {
5020
- errors.push({ path: `${path24}.props`, message: "Must be an array if present" });
5020
+ errors.push({ path: `${path23}.props`, message: "Must be an array if present" });
5021
5021
  } else {
5022
5022
  const props = c["props"];
5023
5023
  for (let j2 = 0; j2 < props.length; j2++) {
5024
5024
  const p = props[j2];
5025
5025
  if (typeof p !== "object" || p === null) {
5026
- errors.push({ path: `${path24}.props[${j2}]`, message: "Must be an object" });
5026
+ errors.push({ path: `${path23}.props[${j2}]`, message: "Must be an object" });
5027
5027
  continue;
5028
5028
  }
5029
- requireNonEmptyString(p["name"], `${path24}.props[${j2}].name`, errors);
5030
- requireNonEmptyString(p["type"], `${path24}.props[${j2}].type`, errors);
5029
+ requireNonEmptyString(p["name"], `${path23}.props[${j2}].name`, errors);
5030
+ requireNonEmptyString(p["type"], `${path23}.props[${j2}].type`, errors);
5031
5031
  if (typeof p["required"] !== "boolean") {
5032
- errors.push({ path: `${path24}.props[${j2}].required`, message: "Must be boolean" });
5032
+ errors.push({ path: `${path23}.props[${j2}].required`, message: "Must be boolean" });
5033
5033
  }
5034
5034
  }
5035
5035
  }
5036
5036
  }
5037
5037
  if (c["events"] !== void 0) {
5038
5038
  if (!Array.isArray(c["events"])) {
5039
- errors.push({ path: `${path24}.events`, message: "Must be an array if present" });
5039
+ errors.push({ path: `${path23}.events`, message: "Must be an array if present" });
5040
5040
  } else {
5041
5041
  const events = c["events"];
5042
5042
  for (let j2 = 0; j2 < events.length; j2++) {
5043
5043
  const e = events[j2];
5044
5044
  if (typeof e !== "object" || e === null) {
5045
- errors.push({ path: `${path24}.events[${j2}]`, message: "Must be an object" });
5045
+ errors.push({ path: `${path23}.events[${j2}]`, message: "Must be an object" });
5046
5046
  continue;
5047
5047
  }
5048
- requireNonEmptyString(e["name"], `${path24}.events[${j2}].name`, errors);
5048
+ requireNonEmptyString(e["name"], `${path23}.events[${j2}].name`, errors);
5049
5049
  }
5050
5050
  }
5051
5051
  }
5052
5052
  if (c["state"] !== void 0) {
5053
5053
  if (typeof c["state"] !== "object" || Array.isArray(c["state"]) || c["state"] === null) {
5054
- errors.push({ path: `${path24}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5054
+ errors.push({ path: `${path23}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5055
5055
  }
5056
5056
  }
5057
5057
  if (c["apiCalls"] !== void 0) {
5058
5058
  if (!Array.isArray(c["apiCalls"])) {
5059
- errors.push({ path: `${path24}.apiCalls`, message: "Must be an array of strings if present" });
5059
+ errors.push({ path: `${path23}.apiCalls`, message: "Must be an array of strings if present" });
5060
5060
  }
5061
5061
  }
5062
5062
  }
5063
- function requireNonEmptyString(v2, path24, errors) {
5063
+ function requireNonEmptyString(v2, path23, errors) {
5064
5064
  if (typeof v2 !== "string" || v2.trim().length === 0) {
5065
5065
  errors.push({
5066
- path: path24,
5066
+ path: path23,
5067
5067
  message: `Must be a non-empty string, got: ${typeLabel(v2)}`
5068
5068
  });
5069
5069
  }
@@ -5497,6 +5497,41 @@ async function loadDslForSpec(specFilePath) {
5497
5497
  // core/frontend-context-loader.ts
5498
5498
  import * as fs7 from "fs-extra";
5499
5499
  import * as path6 from "path";
5500
+ function parseImportStatements(content) {
5501
+ const stripped = content.replace(/\/\*[\s\S]*?\*\//g, (m) => "\n".repeat(m.split("\n").length - 1));
5502
+ const collapsed = stripped.replace(/import\s*\{[^}]*\}/gs, (m) => m.replace(/\n\s*/g, " "));
5503
+ const results = [];
5504
+ for (const rawLine of collapsed.split("\n")) {
5505
+ const line = rawLine.trim();
5506
+ if (!line) continue;
5507
+ if (/^import\s+type\b/.test(line)) continue;
5508
+ if (!line.startsWith("import")) continue;
5509
+ const match = line.match(/^(import\s+([\s\S]+?)\s+from\s+['"]([^'"]+)['"])/);
5510
+ if (!match) continue;
5511
+ results.push({
5512
+ line: match[1],
5513
+ modulePath: match[3],
5514
+ specifiers: match[2]
5515
+ });
5516
+ }
5517
+ return results;
5518
+ }
5519
+ var HTTP_MODULE_PATTERNS = [
5520
+ // Project path aliases (@/, @@/, ~/, #/) — catches '@/utils/request', '~/lib/http', etc.
5521
+ /^(?:@{1,2}|~|#)[/\\]/,
5522
+ // Well-known HTTP libraries (exact name match)
5523
+ /^(?:axios|ky(?:-universal)?|undici|node-fetch|cross-fetch|got|superagent|alova|openapi-fetch)$/,
5524
+ // Relative imports whose path contains an HTTP-utility keyword
5525
+ /\.{1,2}\/[^'"]*(?:http|request|fetch|client|api)[^'"]*/
5526
+ ];
5527
+ function findHttpClientImport(content) {
5528
+ for (const stmt of parseImportStatements(content)) {
5529
+ if (HTTP_MODULE_PATTERNS.some((p) => p.test(stmt.modulePath))) {
5530
+ return stmt.line;
5531
+ }
5532
+ }
5533
+ return void 0;
5534
+ }
5500
5535
  var STATE_MANAGEMENT_LIBS = [
5501
5536
  "zustand",
5502
5537
  "redux",
@@ -5685,13 +5720,12 @@ ${preview}`);
5685
5720
  } catch {
5686
5721
  }
5687
5722
  }
5688
- const httpImportRegex = /^import(?!\s+type)\s+(?:[\w*]+|\{[^}]+\})\s+from\s+['"]((?:@{1,2}|~|#)[/\\][^'"]+|\.{1,2}\/[^'"]*(?:http|request|fetch|client|api)[^'"]*|axios|ky(?:-universal)?|undici|node-fetch|cross-fetch|got|superagent|alova|openapi-fetch)['"]/im;
5689
5723
  for (const relPath of ctx.existingApiFiles.slice(0, 5)) {
5690
5724
  try {
5691
5725
  const content = await fs7.readFile(path6.join(projectRoot, relPath), "utf-8");
5692
- const match = content.match(httpImportRegex);
5693
- if (match) {
5694
- ctx.httpClientImport = match[0].trim();
5726
+ const found = findHttpClientImport(content);
5727
+ if (found) {
5728
+ ctx.httpClientImport = found;
5695
5729
  break;
5696
5730
  }
5697
5731
  } catch {
@@ -5821,13 +5855,28 @@ async function extractRouteModuleContext(projectRoot, ctx) {
5821
5855
  moduleFiles.push(...files);
5822
5856
  }
5823
5857
  if (moduleFiles.length === 0) return;
5824
- const layoutImportRegex = /^(?:const\s+Layout\s*=.*import\(['"][^'"]+['"]\)|import\s+Layout\s+from\s+['"][^'"]+['"])/m;
5858
+ const dynamicLayoutRegex = /const\s+Layout\s*=\s*(?:defineAsyncComponent\s*\(\s*)?(?:\(\s*\))?\s*(?:=>|function[^(]*\()\s*(?:[^)]*\))?\s*(?:=>)?\s*import\s*\(\s*['"]([^'"]+)['"]\s*\)/;
5825
5859
  for (const relPath of moduleFiles) {
5826
5860
  try {
5827
5861
  const content = await fs7.readFile(path6.join(projectRoot, relPath), "utf-8");
5828
- const match = content.match(layoutImportRegex);
5829
- if (match) {
5830
- ctx.layoutImport = match[0].trim();
5862
+ const stmts = parseImportStatements(content);
5863
+ const staticLayout = stmts.find(
5864
+ (s) => /\bLayout\b/.test(s.specifiers) && /layout/i.test(s.modulePath)
5865
+ );
5866
+ if (staticLayout) {
5867
+ ctx.layoutImport = staticLayout.line;
5868
+ const preview = content.split("\n").slice(0, 100).join("\n");
5869
+ ctx.routeModuleExample = { path: relPath, content: preview };
5870
+ break;
5871
+ }
5872
+ const singleLine = content.replace(
5873
+ /const\s+Layout\s*=[\s\S]*?import\s*\([^)]+\)/gm,
5874
+ (m) => m.replace(/\n\s*/g, " ")
5875
+ );
5876
+ const dynMatch = singleLine.match(dynamicLayoutRegex);
5877
+ if (dynMatch) {
5878
+ const constMatch = content.match(/^const\s+Layout\s*=.+/m);
5879
+ ctx.layoutImport = constMatch ? constMatch[0].trim() : dynMatch[0].trim();
5831
5880
  const preview = content.split("\n").slice(0, 100).join("\n");
5832
5881
  ctx.routeModuleExample = { path: relPath, content: preview };
5833
5882
  break;
@@ -7812,6 +7861,60 @@ function parseErrors(output, source) {
7812
7861
  }
7813
7862
  return errors;
7814
7863
  }
7864
+ function parseRelativeImports(content, fromFileRel) {
7865
+ const relDir = path15.dirname(fromFileRel);
7866
+ const results = [];
7867
+ const normalized = content.replace(/import\s*\{[^}]*\}/gs, (m) => m.replace(/\n\s*/g, " "));
7868
+ for (const line of normalized.split("\n")) {
7869
+ const trimmed = line.trim();
7870
+ if (/^import\s+type\b/.test(trimmed)) continue;
7871
+ const match = trimmed.match(/^import\b[^'"]*from\s+['"](\.\.?\/[^'"]+)['"]/);
7872
+ if (!match) continue;
7873
+ const resolved = path15.normalize(path15.join(relDir, match[1]));
7874
+ results.push(resolved);
7875
+ }
7876
+ return results;
7877
+ }
7878
+ async function buildRepairOrder(errorsByFile, workingDir) {
7879
+ const files = Array.from(errorsByFile.keys());
7880
+ if (files.length <= 1) return Array.from(errorsByFile.entries());
7881
+ const deps = new Map(files.map((f) => [f, []]));
7882
+ for (const file of files) {
7883
+ try {
7884
+ const content = await fs16.readFile(path15.join(workingDir, file), "utf-8");
7885
+ const importedPaths = parseRelativeImports(content, file);
7886
+ for (const importedPath of importedPaths) {
7887
+ const matched = files.find((f) => {
7888
+ if (f === file) return false;
7889
+ const fNoExt = f.replace(/\.[^.]+$/, "");
7890
+ return importedPath === fNoExt || importedPath === f || `${importedPath}.ts` === f || `${importedPath}.tsx` === f || `${importedPath}.js` === f || `${importedPath}.jsx` === f;
7891
+ });
7892
+ if (matched) deps.get(file).push(matched);
7893
+ }
7894
+ } catch {
7895
+ }
7896
+ }
7897
+ const dependents = new Map(files.map((f) => [f, []]));
7898
+ for (const [file, fileDeps] of deps) {
7899
+ for (const dep of fileDeps) dependents.get(dep).push(file);
7900
+ }
7901
+ const inDegree = new Map(files.map((f) => [f, deps.get(f).length]));
7902
+ const queue = files.filter((f) => inDegree.get(f) === 0);
7903
+ const sorted = [];
7904
+ while (queue.length > 0) {
7905
+ const file = queue.shift();
7906
+ sorted.push(file);
7907
+ for (const dependent of dependents.get(file) ?? []) {
7908
+ const degree = (inDegree.get(dependent) ?? 1) - 1;
7909
+ inDegree.set(dependent, degree);
7910
+ if (degree === 0) queue.push(dependent);
7911
+ }
7912
+ }
7913
+ for (const f of files) {
7914
+ if (!sorted.includes(f)) sorted.push(f);
7915
+ }
7916
+ return sorted.map((f) => [f, errorsByFile.get(f)]);
7917
+ }
7815
7918
  async function attemptFix(provider, errors, workingDir, dsl) {
7816
7919
  const results = [];
7817
7920
  const errorsByFile = /* @__PURE__ */ new Map();
@@ -7820,7 +7923,8 @@ async function attemptFix(provider, errors, workingDir, dsl) {
7820
7923
  if (!errorsByFile.has(file)) errorsByFile.set(file, []);
7821
7924
  errorsByFile.get(file).push(err);
7822
7925
  }
7823
- for (const [file, fileErrors] of errorsByFile) {
7926
+ const sortedEntries = await buildRepairOrder(errorsByFile, workingDir);
7927
+ for (const [file, fileErrors] of sortedEntries) {
7824
7928
  const fullPath = path15.join(workingDir, file);
7825
7929
  let existingContent = "";
7826
7930
  try {
@@ -8214,108 +8318,6 @@ async function clearKey(provider) {
8214
8318
  await writeStore(store);
8215
8319
  }
8216
8320
 
8217
- // cli/welcome.ts
8218
- import chalk17 from "chalk";
8219
- import * as os4 from "os";
8220
- import * as path19 from "path";
8221
- import * as fs20 from "fs-extra";
8222
- var VERSION = "0.14.1";
8223
- var TOTAL_W = 76;
8224
- var L_WIDTH = 44;
8225
- var R_WIDTH = TOTAL_W - L_WIDTH - 4;
8226
- var ROBOT_COLOR = chalk17.hex("#E8885A");
8227
- var ROBOT = [
8228
- ROBOT_COLOR(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),
8229
- ROBOT_COLOR(" \u2502") + chalk17.bold.white(" \u25C9 ") + ROBOT_COLOR(" ") + chalk17.bold.white("\u25C9 ") + ROBOT_COLOR("\u2502"),
8230
- ROBOT_COLOR(" \u2502") + chalk17.dim(" \u2570\u2500\u256F ") + ROBOT_COLOR("\u2502"),
8231
- ROBOT_COLOR(" \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2518"),
8232
- ROBOT_COLOR(" \u2502"),
8233
- ROBOT_COLOR(" \u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500")
8234
- ];
8235
- function visLen(s) {
8236
- return s.replace(/\x1b\[[0-9;]*m/g, "").length;
8237
- }
8238
- function padR(s, width) {
8239
- const vl = visLen(s);
8240
- return vl >= width ? s : s + " ".repeat(width - vl);
8241
- }
8242
- function row(left, right) {
8243
- return padR(left, L_WIDTH) + " " + chalk17.gray("\u2502") + " " + padR(right, R_WIDTH);
8244
- }
8245
- function center(s, width) {
8246
- const vl = visLen(s);
8247
- const pad = Math.max(0, Math.floor((width - vl) / 2));
8248
- return " ".repeat(pad) + s;
8249
- }
8250
- async function getRecentSpecs(dir) {
8251
- const specsDir = path19.join(dir, "specs");
8252
- if (!await fs20.pathExists(specsDir)) return [];
8253
- try {
8254
- const files = await fs20.readdir(specsDir);
8255
- const mdFiles = files.filter((f) => f.endsWith(".md"));
8256
- const withStats = await Promise.all(
8257
- mdFiles.map(async (f) => {
8258
- const stat4 = await fs20.stat(path19.join(specsDir, f));
8259
- return { name: f, mtime: stat4.mtime.getTime() };
8260
- })
8261
- );
8262
- withStats.sort((a, b) => b.mtime - a.mtime);
8263
- return withStats.slice(0, 3).map(({ name, mtime }) => {
8264
- const ms2 = Date.now() - mtime;
8265
- const hours = Math.floor(ms2 / 36e5);
8266
- const days = Math.floor(hours / 24);
8267
- const age = days > 0 ? `${days}d ago` : hours > 0 ? `${hours}h ago` : "just now";
8268
- const slug = name.replace(/\.md$/, "").slice(0, R_WIDTH - age.length - 2);
8269
- return chalk17.white(slug) + chalk17.dim(" " + age);
8270
- });
8271
- } catch {
8272
- return [];
8273
- }
8274
- }
8275
- async function printWelcome(currentDir, config2) {
8276
- const username = os4.userInfo().username;
8277
- const homeDir = os4.homedir();
8278
- const shortDir = currentDir.startsWith(homeDir) ? "~" + currentDir.slice(homeDir.length) : currentDir;
8279
- const recentSpecs = await getRecentSpecs(currentDir);
8280
- const providerBit = config2?.provider ? config2.provider + (config2.model ? " \xB7 " + config2.model : "") : "";
8281
- const bottomRaw = [providerBit, shortDir].filter(Boolean).join(" \xB7 ");
8282
- const maxInfoLen = L_WIDTH - 2;
8283
- const bottomTruncated = bottomRaw.length > maxInfoLen ? bottomRaw.slice(0, maxInfoLen - 1) + "\u2026" : bottomRaw;
8284
- const bottomLine = " " + chalk17.dim(bottomTruncated);
8285
- const titleInner = `ai-spec v${VERSION} `;
8286
- const titleDashes = "\u2500".repeat(Math.max(0, TOTAL_W - titleInner.length - 4));
8287
- console.log(
8288
- "\n" + chalk17.hex("#FF6B35")("\u2500\u2500\u2500 " + titleInner + titleDashes)
8289
- );
8290
- const welcomeText = "Welcome back, " + chalk17.bold.white(username) + "!";
8291
- const leftLines = [
8292
- "",
8293
- center(welcomeText, L_WIDTH),
8294
- "",
8295
- ...ROBOT,
8296
- "",
8297
- bottomLine,
8298
- ""
8299
- ];
8300
- const rightLines = [
8301
- chalk17.hex("#FF8C00").bold("Tips for getting started"),
8302
- chalk17.gray("\u2500".repeat(R_WIDTH)),
8303
- chalk17.white('ai-spec create "feature"'),
8304
- chalk17.gray("ai-spec workspace run"),
8305
- chalk17.gray('ai-spec update "change"'),
8306
- "",
8307
- chalk17.hex("#FF8C00").bold("Recent activity"),
8308
- chalk17.gray("\u2500".repeat(R_WIDTH)),
8309
- ...recentSpecs.length > 0 ? recentSpecs : [chalk17.dim("No recent activity")]
8310
- ];
8311
- const maxLines = Math.max(leftLines.length, rightLines.length);
8312
- for (let i = 0; i < maxLines; i++) {
8313
- console.log(row(leftLines[i] ?? "", rightLines[i] ?? ""));
8314
- }
8315
- console.log(chalk17.gray("\u2500".repeat(TOTAL_W)));
8316
- console.log();
8317
- }
8318
-
8319
8321
  // prompts/global-constitution.prompt.ts
8320
8322
  var globalConstitutionSystemPrompt = `You are a Senior Software Architect. Analyze the provided multi-project context and generate a "Global Constitution" \u2014 a team-level baseline document that captures cross-project rules, shared conventions, and universal constraints that every repository in this workspace must follow.
8321
8323
 
@@ -8825,8 +8827,8 @@ Existing API/service files:`);
8825
8827
  }
8826
8828
 
8827
8829
  // core/mock-server-generator.ts
8828
- import * as path20 from "path";
8829
- import * as fs21 from "fs-extra";
8830
+ import * as path19 from "path";
8831
+ import * as fs20 from "fs-extra";
8830
8832
  import { spawn } from "child_process";
8831
8833
  function typeToFixture(fieldName, typeDesc) {
8832
8834
  const t = typeDesc.toLowerCase();
@@ -8962,22 +8964,22 @@ function generateMockServerJs(dsl, port) {
8962
8964
  }
8963
8965
  function detectFrontendFramework(projectDir) {
8964
8966
  for (const f of ["vite.config.ts", "vite.config.js", "vite.config.mts"]) {
8965
- if (fs21.existsSync(path20.join(projectDir, f))) return "vite";
8967
+ if (fs20.existsSync(path19.join(projectDir, f))) return "vite";
8966
8968
  }
8967
8969
  for (const f of ["next.config.js", "next.config.ts", "next.config.mjs"]) {
8968
- if (fs21.existsSync(path20.join(projectDir, f))) return "next";
8970
+ if (fs20.existsSync(path19.join(projectDir, f))) return "next";
8969
8971
  }
8970
- const pkgPath = path20.join(projectDir, "package.json");
8971
- if (fs21.existsSync(pkgPath)) {
8972
+ const pkgPath = path19.join(projectDir, "package.json");
8973
+ if (fs20.existsSync(pkgPath)) {
8972
8974
  try {
8973
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
8975
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
8974
8976
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8975
8977
  if (deps["react-scripts"]) return "cra";
8976
8978
  } catch {
8977
8979
  }
8978
8980
  }
8979
8981
  for (const f of ["webpack.config.js", "webpack.config.ts"]) {
8980
- if (fs21.existsSync(path20.join(projectDir, f))) return "webpack";
8982
+ if (fs20.existsSync(path19.join(projectDir, f))) return "webpack";
8981
8983
  }
8982
8984
  return "unknown";
8983
8985
  }
@@ -9157,7 +9159,7 @@ export const worker = setupWorker(...handlers);
9157
9159
  var MOCK_LOCK_FILE = ".ai-spec-mock.lock.json";
9158
9160
  function findViteConfigFile(projectDir) {
9159
9161
  for (const f of ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]) {
9160
- if (fs21.existsSync(path20.join(projectDir, f))) return f;
9162
+ if (fs20.existsSync(path19.join(projectDir, f))) return f;
9161
9163
  }
9162
9164
  return null;
9163
9165
  }
@@ -9203,73 +9205,73 @@ async function applyMockProxy(frontendDir, mockPort, endpoints = []) {
9203
9205
  if (framework === "vite") {
9204
9206
  const viteConfigFile = findViteConfigFile(frontendDir) ?? "vite.config.ts";
9205
9207
  const mockConfigContent = generateViteMockConfigTs(viteConfigFile, mockPort, endpoints);
9206
- const mockConfigPath = path20.join(frontendDir, "vite.config.ai-spec-mock.ts");
9207
- await fs21.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9208
+ const mockConfigPath = path19.join(frontendDir, "vite.config.ai-spec-mock.ts");
9209
+ await fs20.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9208
9210
  actions.push({ type: "wrote-file", filePath: "vite.config.ai-spec-mock.ts" });
9209
- const pkgPath = path20.join(frontendDir, "package.json");
9210
- if (await fs21.pathExists(pkgPath)) {
9211
- const pkg = await fs21.readJson(pkgPath);
9211
+ const pkgPath = path19.join(frontendDir, "package.json");
9212
+ if (await fs20.pathExists(pkgPath)) {
9213
+ const pkg = await fs20.readJson(pkgPath);
9212
9214
  pkg.scripts = pkg.scripts ?? {};
9213
9215
  const originalValue = pkg.scripts["dev:mock"] ?? null;
9214
9216
  pkg.scripts["dev:mock"] = "vite --config vite.config.ai-spec-mock.ts";
9215
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9217
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9216
9218
  actions.push({ type: "added-pkg-script", key: "dev:mock", originalValue });
9217
9219
  }
9218
9220
  const lock2 = { framework, mockPort, frontendDir, actions };
9219
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9221
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9220
9222
  return { framework, applied: true, devCommand: "npm run dev:mock" };
9221
9223
  }
9222
9224
  if (framework === "cra") {
9223
- const pkgPath = path20.join(frontendDir, "package.json");
9224
- if (await fs21.pathExists(pkgPath)) {
9225
- const pkg = await fs21.readJson(pkgPath);
9225
+ const pkgPath = path19.join(frontendDir, "package.json");
9226
+ if (await fs20.pathExists(pkgPath)) {
9227
+ const pkg = await fs20.readJson(pkgPath);
9226
9228
  const originalProxy = pkg.proxy ?? null;
9227
9229
  pkg.proxy = `http://localhost:${mockPort}`;
9228
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9230
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9229
9231
  actions.push({ type: "patched-pkg-proxy", originalProxy });
9230
9232
  const lock2 = { framework, mockPort, frontendDir, actions };
9231
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9233
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9232
9234
  return { framework, applied: true, devCommand: "npm start" };
9233
9235
  }
9234
9236
  return { framework, applied: false, devCommand: null, note: "No package.json found." };
9235
9237
  }
9236
9238
  const lock = { framework, mockPort, frontendDir, actions };
9237
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9239
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9238
9240
  const manualNote = framework === "next" ? `Add rewrites in next.config.js to proxy API calls to http://localhost:${mockPort}` : `Add proxy in webpack.config.js devServer to target http://localhost:${mockPort}`;
9239
9241
  return { framework, applied: false, devCommand: null, note: manualNote };
9240
9242
  }
9241
9243
  async function restoreMockProxy(frontendDir) {
9242
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9243
- if (!await fs21.pathExists(lockPath)) {
9244
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9245
+ if (!await fs20.pathExists(lockPath)) {
9244
9246
  return { restored: false, note: "No lock file found \u2014 nothing to restore." };
9245
9247
  }
9246
- const lock = await fs21.readJson(lockPath);
9248
+ const lock = await fs20.readJson(lockPath);
9247
9249
  for (const action of lock.actions) {
9248
9250
  if (action.type === "wrote-file") {
9249
- const fp = path20.join(frontendDir, action.filePath);
9250
- if (await fs21.pathExists(fp)) await fs21.remove(fp);
9251
+ const fp = path19.join(frontendDir, action.filePath);
9252
+ if (await fs20.pathExists(fp)) await fs20.remove(fp);
9251
9253
  } else if (action.type === "added-pkg-script") {
9252
- const pkgPath = path20.join(frontendDir, "package.json");
9253
- if (await fs21.pathExists(pkgPath)) {
9254
- const pkg = await fs21.readJson(pkgPath);
9254
+ const pkgPath = path19.join(frontendDir, "package.json");
9255
+ if (await fs20.pathExists(pkgPath)) {
9256
+ const pkg = await fs20.readJson(pkgPath);
9255
9257
  if (action.originalValue == null) {
9256
9258
  delete pkg.scripts?.[action.key];
9257
9259
  } else {
9258
9260
  pkg.scripts = pkg.scripts ?? {};
9259
9261
  pkg.scripts[action.key] = action.originalValue;
9260
9262
  }
9261
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9263
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9262
9264
  }
9263
9265
  } else if (action.type === "patched-pkg-proxy") {
9264
- const pkgPath = path20.join(frontendDir, "package.json");
9265
- if (await fs21.pathExists(pkgPath)) {
9266
- const pkg = await fs21.readJson(pkgPath);
9266
+ const pkgPath = path19.join(frontendDir, "package.json");
9267
+ if (await fs20.pathExists(pkgPath)) {
9268
+ const pkg = await fs20.readJson(pkgPath);
9267
9269
  if (action.originalProxy == null) {
9268
9270
  delete pkg.proxy;
9269
9271
  } else {
9270
9272
  pkg.proxy = action.originalProxy;
9271
9273
  }
9272
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9274
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9273
9275
  }
9274
9276
  }
9275
9277
  }
@@ -9279,7 +9281,7 @@ async function restoreMockProxy(frontendDir) {
9279
9281
  } catch {
9280
9282
  }
9281
9283
  }
9282
- await fs21.remove(lockPath);
9284
+ await fs20.remove(lockPath);
9283
9285
  return { restored: true };
9284
9286
  }
9285
9287
  function startMockServerBackground(serverJsPath, port) {
@@ -9292,23 +9294,23 @@ function startMockServerBackground(serverJsPath, port) {
9292
9294
  return child.pid;
9293
9295
  }
9294
9296
  async function saveMockServerPid(frontendDir, pid) {
9295
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9296
- if (await fs21.pathExists(lockPath)) {
9297
- const lock = await fs21.readJson(lockPath);
9297
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9298
+ if (await fs20.pathExists(lockPath)) {
9299
+ const lock = await fs20.readJson(lockPath);
9298
9300
  lock.mockServerPid = pid;
9299
- await fs21.writeJson(lockPath, lock, { spaces: 2 });
9301
+ await fs20.writeJson(lockPath, lock, { spaces: 2 });
9300
9302
  }
9301
9303
  }
9302
9304
  async function generateMockAssets(dsl, projectDir, opts = {}) {
9303
9305
  const port = opts.port ?? 3001;
9304
- const outputDir = path20.join(projectDir, opts.outputDir ?? "mock");
9306
+ const outputDir = path19.join(projectDir, opts.outputDir ?? "mock");
9305
9307
  const result = { files: [] };
9306
- await fs21.ensureDir(outputDir);
9308
+ await fs20.ensureDir(outputDir);
9307
9309
  const serverJs = generateMockServerJs(dsl, port);
9308
- const serverPath = path20.join(outputDir, "server.js");
9309
- await fs21.writeFile(serverPath, serverJs, "utf-8");
9310
+ const serverPath = path19.join(outputDir, "server.js");
9311
+ await fs20.writeFile(serverPath, serverJs, "utf-8");
9310
9312
  result.files.push({
9311
- path: path20.relative(projectDir, serverPath),
9313
+ path: path19.relative(projectDir, serverPath),
9312
9314
  description: `Express mock server \u2014 run with: node mock/server.js`
9313
9315
  });
9314
9316
  const mockReadme = `# Mock Server
@@ -9338,52 +9340,52 @@ ${dsl.endpoints.map(
9338
9340
 
9339
9341
  Append \`?simulate_error=1\` to any request to test error handling (not yet auto-wired \u2014 edit server.js manually).
9340
9342
  `;
9341
- const readmePath = path20.join(outputDir, "README.md");
9342
- await fs21.writeFile(readmePath, mockReadme, "utf-8");
9343
+ const readmePath = path19.join(outputDir, "README.md");
9344
+ await fs20.writeFile(readmePath, mockReadme, "utf-8");
9343
9345
  result.files.push({
9344
- path: path20.relative(projectDir, readmePath),
9346
+ path: path19.relative(projectDir, readmePath),
9345
9347
  description: "Mock server usage guide"
9346
9348
  });
9347
9349
  if (opts.proxy) {
9348
9350
  const { content, filename } = generateProxyConfig(dsl, port, projectDir);
9349
- const proxyPath = path20.join(projectDir, filename);
9350
- await fs21.ensureDir(path20.dirname(proxyPath));
9351
- await fs21.writeFile(proxyPath, content, "utf-8");
9351
+ const proxyPath = path19.join(projectDir, filename);
9352
+ await fs20.ensureDir(path19.dirname(proxyPath));
9353
+ await fs20.writeFile(proxyPath, content, "utf-8");
9352
9354
  result.files.push({
9353
9355
  path: filename,
9354
9356
  description: "Proxy config snippet \u2014 copy instructions into your framework config"
9355
9357
  });
9356
9358
  }
9357
9359
  if (opts.msw) {
9358
- const mswDir = path20.join(projectDir, "src", "mocks");
9359
- await fs21.ensureDir(mswDir);
9360
+ const mswDir = path19.join(projectDir, "src", "mocks");
9361
+ await fs20.ensureDir(mswDir);
9360
9362
  const handlersContent = generateMswHandlers(dsl);
9361
- const handlersPath = path20.join(mswDir, "handlers.ts");
9362
- await fs21.writeFile(handlersPath, handlersContent, "utf-8");
9363
+ const handlersPath = path19.join(mswDir, "handlers.ts");
9364
+ await fs20.writeFile(handlersPath, handlersContent, "utf-8");
9363
9365
  result.files.push({
9364
- path: path20.relative(projectDir, handlersPath),
9366
+ path: path19.relative(projectDir, handlersPath),
9365
9367
  description: "MSW request handlers"
9366
9368
  });
9367
9369
  const browserContent = generateMswBrowser();
9368
- const browserPath = path20.join(mswDir, "browser.ts");
9369
- await fs21.writeFile(browserPath, browserContent, "utf-8");
9370
+ const browserPath = path19.join(mswDir, "browser.ts");
9371
+ await fs20.writeFile(browserPath, browserContent, "utf-8");
9370
9372
  result.files.push({
9371
- path: path20.relative(projectDir, browserPath),
9373
+ path: path19.relative(projectDir, browserPath),
9372
9374
  description: "MSW browser worker setup"
9373
9375
  });
9374
9376
  }
9375
9377
  return result;
9376
9378
  }
9377
9379
  async function findLatestDslFile(projectDir) {
9378
- const specDir = path20.join(projectDir, ".ai-spec");
9379
- if (!await fs21.pathExists(specDir)) return null;
9380
+ const specDir = path19.join(projectDir, ".ai-spec");
9381
+ if (!await fs20.pathExists(specDir)) return null;
9380
9382
  const allFiles = [];
9381
9383
  async function scan(dir) {
9382
- const entries = await fs21.readdir(dir);
9384
+ const entries = await fs20.readdir(dir);
9383
9385
  for (const entry of entries) {
9384
- const abs = path20.join(dir, entry);
9385
- const stat4 = await fs21.stat(abs);
9386
- if (stat4.isDirectory()) {
9386
+ const abs = path19.join(dir, entry);
9387
+ const stat3 = await fs20.stat(abs);
9388
+ if (stat3.isDirectory()) {
9387
9389
  await scan(abs);
9388
9390
  } else if (entry.endsWith(".dsl.json")) {
9389
9391
  allFiles.push(abs);
@@ -9393,16 +9395,16 @@ async function findLatestDslFile(projectDir) {
9393
9395
  await scan(specDir);
9394
9396
  if (allFiles.length === 0) return null;
9395
9397
  const withMtimes = await Promise.all(
9396
- allFiles.map(async (f) => ({ f, mtime: (await fs21.stat(f)).mtime }))
9398
+ allFiles.map(async (f) => ({ f, mtime: (await fs20.stat(f)).mtime }))
9397
9399
  );
9398
9400
  withMtimes.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
9399
9401
  return withMtimes[0].f;
9400
9402
  }
9401
9403
 
9402
9404
  // core/spec-updater.ts
9403
- import chalk18 from "chalk";
9404
- import * as path21 from "path";
9405
- import * as fs22 from "fs-extra";
9405
+ import chalk17 from "chalk";
9406
+ import * as path20 from "path";
9407
+ import * as fs21 from "fs-extra";
9406
9408
 
9407
9409
  // prompts/update.prompt.ts
9408
9410
  var specUpdateSystemPrompt = `You are a Senior Software Architect updating an existing Feature Spec based on a change request.
@@ -9548,8 +9550,8 @@ var SpecUpdater = class {
9548
9550
  * Returns all .md spec files sorted newest-first.
9549
9551
  */
9550
9552
  static async findLatestSpec(specsDir) {
9551
- if (!await fs22.pathExists(specsDir)) return null;
9552
- const files = await fs22.readdir(specsDir);
9553
+ if (!await fs21.pathExists(specsDir)) return null;
9554
+ const files = await fs21.readdir(specsDir);
9553
9555
  const pattern = /^feature-(.+)-v(\d+)\.md$/;
9554
9556
  let latest = null;
9555
9557
  for (const file of files) {
@@ -9557,8 +9559,8 @@ var SpecUpdater = class {
9557
9559
  if (!m) continue;
9558
9560
  const version = parseInt(m[2], 10);
9559
9561
  if (!latest || version > latest.version) {
9560
- const filePath = path21.join(specsDir, file);
9561
- const content = await fs22.readFile(filePath, "utf-8");
9562
+ const filePath = path20.join(specsDir, file);
9563
+ const content = await fs21.readFile(filePath, "utf-8");
9562
9564
  latest = { filePath, version, slug: m[1], content };
9563
9565
  }
9564
9566
  }
@@ -9569,16 +9571,16 @@ var SpecUpdater = class {
9569
9571
  * Generates a new version of the spec, re-extracts the DSL, and identifies affected files.
9570
9572
  */
9571
9573
  async update(changeRequest, existingSpecPath, projectDir, context, opts = {}) {
9572
- const existingSpec = await fs22.readFile(existingSpecPath, "utf-8");
9574
+ const existingSpec = await fs21.readFile(existingSpecPath, "utf-8");
9573
9575
  let existingDsl = null;
9574
9576
  const dslFile = await findLatestDslFile(projectDir);
9575
9577
  if (dslFile) {
9576
9578
  try {
9577
- existingDsl = await fs22.readJson(dslFile);
9579
+ existingDsl = await fs21.readJson(dslFile);
9578
9580
  } catch {
9579
9581
  }
9580
9582
  }
9581
- console.log(chalk18.blue(" [1/3] Generating updated spec..."));
9583
+ console.log(chalk17.blue(" [1/3] Generating updated spec..."));
9582
9584
  const updatePrompt = buildSpecUpdatePrompt(changeRequest, existingSpec, existingDsl, context);
9583
9585
  let updatedSpecContent;
9584
9586
  try {
@@ -9587,15 +9589,15 @@ var SpecUpdater = class {
9587
9589
  } catch (err) {
9588
9590
  throw new Error(`Spec update generation failed: ${err.message}`);
9589
9591
  }
9590
- const specBasename = path21.basename(existingSpecPath);
9592
+ const specBasename = path20.basename(existingSpecPath);
9591
9593
  const slugMatch = specBasename.match(/^feature-(.+)-v\d+\.md$/);
9592
9594
  const slug = slugMatch ? slugMatch[1] : "feature";
9593
- const specsDir = path21.dirname(existingSpecPath);
9595
+ const specsDir = path20.dirname(existingSpecPath);
9594
9596
  const { filePath: newSpecPath, version: newVersion } = await nextVersionPath(specsDir, slug);
9595
- await fs22.ensureDir(specsDir);
9596
- await fs22.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9597
- console.log(chalk18.green(` \u2714 New spec written: ${path21.relative(projectDir, newSpecPath)}`));
9598
- console.log(chalk18.blue(" [2/3] Updating DSL..."));
9597
+ await fs21.ensureDir(specsDir);
9598
+ await fs21.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9599
+ console.log(chalk17.green(` \u2714 New spec written: ${path20.relative(projectDir, newSpecPath)}`));
9600
+ console.log(chalk17.blue(" [2/3] Updating DSL..."));
9599
9601
  let updatedDsl = null;
9600
9602
  let newDslPath = null;
9601
9603
  if (existingDsl) {
@@ -9607,7 +9609,7 @@ var SpecUpdater = class {
9607
9609
  updatedDsl = parsed;
9608
9610
  }
9609
9611
  } catch {
9610
- console.log(chalk18.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9612
+ console.log(chalk17.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9611
9613
  }
9612
9614
  }
9613
9615
  if (!updatedDsl) {
@@ -9616,15 +9618,15 @@ var SpecUpdater = class {
9616
9618
  }
9617
9619
  if (updatedDsl) {
9618
9620
  const dslPath = newSpecPath.replace(/\.md$/, ".dsl.json");
9619
- await fs22.writeJson(dslPath, updatedDsl, { spaces: 2 });
9621
+ await fs21.writeJson(dslPath, updatedDsl, { spaces: 2 });
9620
9622
  newDslPath = dslPath;
9621
- console.log(chalk18.green(` \u2714 DSL updated: ${path21.relative(projectDir, dslPath)}`));
9623
+ console.log(chalk17.green(` \u2714 DSL updated: ${path20.relative(projectDir, dslPath)}`));
9622
9624
  } else {
9623
- console.log(chalk18.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9625
+ console.log(chalk17.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9624
9626
  }
9625
9627
  let affectedFiles = [];
9626
9628
  if (!opts.skipAffectedFiles && updatedDsl && existingDsl && context) {
9627
- console.log(chalk18.blue(" [3/3] Identifying affected files..."));
9629
+ console.log(chalk17.blue(" [3/3] Identifying affected files..."));
9628
9630
  const systemPrompt = getCodeGenSystemPrompt(opts.repoType);
9629
9631
  const affectedPrompt = buildAffectedFilesPrompt(
9630
9632
  changeRequest,
@@ -9635,9 +9637,9 @@ var SpecUpdater = class {
9635
9637
  try {
9636
9638
  const affectedRaw = await this.provider.generate(affectedPrompt, systemPrompt);
9637
9639
  affectedFiles = parseAffectedFiles(affectedRaw);
9638
- console.log(chalk18.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9640
+ console.log(chalk17.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9639
9641
  } catch {
9640
- console.log(chalk18.gray(" Could not identify affected files \u2014 use manual selection."));
9642
+ console.log(chalk17.gray(" Could not identify affected files \u2014 use manual selection."));
9641
9643
  }
9642
9644
  }
9643
9645
  return { newSpecPath, newVersion, newDslPath, affectedFiles, updatedDsl };
@@ -9645,8 +9647,8 @@ var SpecUpdater = class {
9645
9647
  };
9646
9648
 
9647
9649
  // core/openapi-exporter.ts
9648
- import * as path22 from "path";
9649
- import * as fs23 from "fs-extra";
9650
+ import * as path21 from "path";
9651
+ import * as fs22 from "fs-extra";
9650
9652
  function dslTypeToOASchema(typeDesc, fieldName = "") {
9651
9653
  const t = typeDesc.toLowerCase();
9652
9654
  if (t === "string" || t.includes("string")) {
@@ -9875,7 +9877,7 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9875
9877
  const format = opts.format ?? "yaml";
9876
9878
  const serverUrl = opts.serverUrl ?? "http://localhost:3000";
9877
9879
  const defaultName = `openapi.${format}`;
9878
- const outputPath = opts.outputPath ? path22.isAbsolute(opts.outputPath) ? opts.outputPath : path22.join(projectDir, opts.outputPath) : path22.join(projectDir, defaultName);
9880
+ const outputPath = opts.outputPath ? path21.isAbsolute(opts.outputPath) ? opts.outputPath : path21.join(projectDir, opts.outputPath) : path21.join(projectDir, defaultName);
9879
9881
  const doc = dslToOpenApi(dsl, serverUrl);
9880
9882
  let content;
9881
9883
  if (format === "json") {
@@ -9883,8 +9885,8 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9883
9885
  } else {
9884
9886
  content = buildYamlDoc(doc);
9885
9887
  }
9886
- await fs23.ensureDir(path22.dirname(outputPath));
9887
- await fs23.writeFile(outputPath, content, "utf-8");
9888
+ await fs22.ensureDir(path21.dirname(outputPath));
9889
+ await fs22.writeFile(outputPath, content, "utf-8");
9888
9890
  return outputPath;
9889
9891
  }
9890
9892
 
@@ -9892,9 +9894,9 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9892
9894
  dotenv.config();
9893
9895
  var CONFIG_FILE = ".ai-spec.json";
9894
9896
  async function loadConfig(dir) {
9895
- const p = path23.join(dir, CONFIG_FILE);
9896
- if (await fs24.pathExists(p)) {
9897
- return fs24.readJson(p);
9897
+ const p = path22.join(dir, CONFIG_FILE);
9898
+ if (await fs23.pathExists(p)) {
9899
+ return fs23.readJson(p);
9898
9900
  }
9899
9901
  return {};
9900
9902
  }
@@ -9919,20 +9921,20 @@ async function resolveApiKey(providerName, cliKey) {
9919
9921
  validate: (v2) => v2.trim().length > 0 || "API key cannot be empty"
9920
9922
  });
9921
9923
  await saveKey(providerName, newKey.trim());
9922
- console.log(chalk19.gray(` Key saved to ${KEY_STORE_FILE}`));
9924
+ console.log(chalk18.gray(` Key saved to ${KEY_STORE_FILE}`));
9923
9925
  return newKey.trim();
9924
9926
  }
9925
9927
  function printBanner(opts) {
9926
- console.log(chalk19.blue("\n" + "\u2500".repeat(52)));
9927
- console.log(chalk19.bold(" ai-spec \u2014 AI-driven Development Orchestrator"));
9928
- console.log(chalk19.blue("\u2500".repeat(52)));
9929
- console.log(chalk19.gray(` Spec : ${opts.specProvider} / ${opts.specModel}`));
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}`));
9930
9932
  console.log(
9931
- chalk19.gray(
9933
+ chalk18.gray(
9932
9934
  ` Codegen : ${opts.codegenMode} (${opts.codegenProvider} / ${opts.codegenModel})`
9933
9935
  )
9934
9936
  );
9935
- console.log(chalk19.blue("\u2500".repeat(52) + "\n"));
9937
+ console.log(chalk18.blue("\u2500".repeat(52) + "\n"));
9936
9938
  }
9937
9939
  var program = new Command();
9938
9940
  program.name("ai-spec").description("AI-driven Development Orchestrator \u2014 spec, generate, review").version("0.14.1");
@@ -9959,42 +9961,42 @@ program.command("create").description("Generate a feature spec and kick off code
9959
9961
  const workspaceLoader = new WorkspaceLoader(currentDir);
9960
9962
  const workspaceConfig = await workspaceLoader.load();
9961
9963
  if (workspaceConfig) {
9962
- console.log(chalk19.cyan(`
9964
+ console.log(chalk18.cyan(`
9963
9965
  [Workspace] Detected workspace: ${workspaceConfig.name}`));
9964
- console.log(chalk19.gray(` Repos: ${workspaceConfig.repos.map((r) => r.name).join(", ")}`));
9966
+ console.log(chalk18.gray(` Repos: ${workspaceConfig.repos.map((r) => r.name).join(", ")}`));
9965
9967
  const pipelineResults = await runMultiRepoPipeline(idea, workspaceConfig, opts, currentDir, config2);
9966
9968
  if (opts.serve) {
9967
- console.log(chalk19.blue("\n\u2500\u2500\u2500 Auto-serve: starting mock server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
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"));
9968
9970
  const backendResult = pipelineResults.find((r) => r.role === "backend" && r.status === "success" && r.dsl);
9969
9971
  const frontendResult = pipelineResults.find((r) => (r.role === "frontend" || r.role === "mobile") && r.status === "success");
9970
9972
  if (!backendResult) {
9971
- console.log(chalk19.yellow(" No successful backend with DSL found \u2014 skipping auto-serve."));
9973
+ console.log(chalk18.yellow(" No successful backend with DSL found \u2014 skipping auto-serve."));
9972
9974
  } else {
9973
9975
  const mockPort = 3001;
9974
9976
  const mockResult = await generateMockAssets(backendResult.dsl, backendResult.repoAbsPath, { port: mockPort });
9975
- const serverJsPath = path23.join(backendResult.repoAbsPath, "mock", "server.js");
9976
- console.log(chalk19.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
9977
+ const serverJsPath = path22.join(backendResult.repoAbsPath, "mock", "server.js");
9978
+ console.log(chalk18.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
9977
9979
  const pid = startMockServerBackground(serverJsPath, mockPort);
9978
- console.log(chalk19.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
9980
+ console.log(chalk18.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
9979
9981
  if (frontendResult) {
9980
9982
  const proxyResult = await applyMockProxy(frontendResult.repoAbsPath, mockPort, backendResult.dsl.endpoints);
9981
9983
  await saveMockServerPid(frontendResult.repoAbsPath, pid);
9982
9984
  if (proxyResult.applied) {
9983
- console.log(chalk19.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
9984
- console.log(chalk19.bold.cyan(`
9985
+ console.log(chalk18.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
9986
+ console.log(chalk18.bold.cyan(`
9985
9987
  Ready! Run your frontend dev server:`));
9986
- console.log(chalk19.white(` cd ${frontendResult.repoAbsPath}`));
9987
- console.log(chalk19.white(` ${proxyResult.devCommand}`));
9988
- console.log(chalk19.gray(`
9988
+ console.log(chalk18.white(` cd ${frontendResult.repoAbsPath}`));
9989
+ console.log(chalk18.white(` ${proxyResult.devCommand}`));
9990
+ console.log(chalk18.gray(`
9989
9991
  When done, restore: ai-spec mock --restore --frontend ${frontendResult.repoAbsPath}`));
9990
9992
  } else {
9991
- console.log(chalk19.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
9992
- if (proxyResult.note) console.log(chalk19.gray(` ${proxyResult.note}`));
9993
- console.log(chalk19.gray(` Mock server: http://localhost:${mockPort}`));
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}`));
9994
9996
  }
9995
9997
  } else {
9996
- console.log(chalk19.gray(` No frontend repo found \u2014 mock server is running at http://localhost:${mockPort}`));
9997
- console.log(chalk19.gray(` Configure your frontend proxy manually to point to http://localhost:${mockPort}`));
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}`));
9998
10000
  }
9999
10001
  }
10000
10002
  }
@@ -10015,7 +10017,7 @@ program.command("create").description("Generate a feature spec and kick off code
10015
10017
  codegenModel: codegenModelName
10016
10018
  });
10017
10019
  const runId = generateRunId();
10018
- console.log(chalk19.gray(` Run ID: ${runId}`));
10020
+ console.log(chalk18.gray(` Run ID: ${runId}`));
10019
10021
  const runSnapshot = new RunSnapshot(currentDir, runId);
10020
10022
  setActiveSnapshot(runSnapshot);
10021
10023
  const runLogger = new RunLogger(currentDir, runId, {
@@ -10023,25 +10025,25 @@ program.command("create").description("Generate a feature spec and kick off code
10023
10025
  model: specModelName
10024
10026
  });
10025
10027
  setActiveLogger(runLogger);
10026
- console.log(chalk19.blue("[1/6] Loading project context..."));
10028
+ console.log(chalk18.blue("[1/6] Loading project context..."));
10027
10029
  runLogger.stageStart("context_load");
10028
10030
  const loader = new ContextLoader(currentDir);
10029
10031
  const context = await loader.loadProjectContext();
10030
10032
  const { type: detectedRepoType } = await detectRepoType(currentDir);
10031
10033
  runLogger.stageEnd("context_load", { techStack: context.techStack, repoType: detectedRepoType });
10032
- console.log(chalk19.gray(` Tech stack : ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10033
- console.log(chalk19.gray(` Dependencies: ${context.dependencies.length} packages`));
10034
- console.log(chalk19.gray(` API files : ${context.apiStructure.length} files`));
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`));
10035
10037
  if (context.schema) {
10036
- console.log(chalk19.gray(` Prisma schema: found`));
10038
+ console.log(chalk18.gray(` Prisma schema: found`));
10037
10039
  }
10038
10040
  if (context.constitution) {
10039
- console.log(chalk19.green(` Constitution : found (.ai-spec-constitution.md)`));
10041
+ console.log(chalk18.green(` Constitution : found (.ai-spec-constitution.md)`));
10040
10042
  if (context.constitution.length > 6e3) {
10041
- console.log(chalk19.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10043
+ console.log(chalk18.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10042
10044
  }
10043
10045
  } else {
10044
- console.log(chalk19.yellow(" Constitution : not found \u2014 auto-generating..."));
10046
+ console.log(chalk18.yellow(" Constitution : not found \u2014 auto-generating..."));
10045
10047
  try {
10046
10048
  const constitutionGen = new ConstitutionGenerator(
10047
10049
  createProvider(specProviderName, specApiKey, specModelName)
@@ -10049,12 +10051,12 @@ program.command("create").description("Generate a feature spec and kick off code
10049
10051
  const constitutionContent = await constitutionGen.generate(currentDir);
10050
10052
  await constitutionGen.saveConstitution(currentDir, constitutionContent);
10051
10053
  context.constitution = constitutionContent;
10052
- console.log(chalk19.green(` Constitution : \u2714 generated and saved (.ai-spec-constitution.md)`));
10054
+ console.log(chalk18.green(` Constitution : \u2714 generated and saved (.ai-spec-constitution.md)`));
10053
10055
  } catch (err) {
10054
- console.log(chalk19.yellow(` Constitution : \u26A0 auto-generation failed (${err.message}), continuing without it.`));
10056
+ console.log(chalk18.yellow(` Constitution : \u26A0 auto-generation failed (${err.message}), continuing without it.`));
10055
10057
  }
10056
10058
  }
10057
- console.log(chalk19.blue(`
10059
+ console.log(chalk18.blue(`
10058
10060
  [2/6] Generating spec with ${specProviderName}/${specModelName}...`));
10059
10061
  const specProvider = createProvider(specProviderName, specApiKey, specModelName);
10060
10062
  let initialSpec;
@@ -10064,30 +10066,30 @@ program.command("create").description("Generate a feature spec and kick off code
10064
10066
  if (opts.skipTasks) {
10065
10067
  const generator = new SpecGenerator(specProvider);
10066
10068
  initialSpec = await generator.generateSpec(idea, context);
10067
- console.log(chalk19.green(" \u2714 Spec generated."));
10069
+ console.log(chalk18.green(" \u2714 Spec generated."));
10068
10070
  } else {
10069
10071
  const result = await generateSpecWithTasks(specProvider, idea, context);
10070
10072
  initialSpec = result.spec;
10071
10073
  initialTasks = result.tasks;
10072
- console.log(chalk19.green(` \u2714 Spec generated.`));
10074
+ console.log(chalk18.green(` \u2714 Spec generated.`));
10073
10075
  if (initialTasks.length > 0) {
10074
- console.log(chalk19.green(` \u2714 ${initialTasks.length} tasks generated (combined call).`));
10076
+ console.log(chalk18.green(` \u2714 ${initialTasks.length} tasks generated (combined call).`));
10075
10077
  } else {
10076
- console.log(chalk19.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
10078
+ console.log(chalk18.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
10077
10079
  }
10078
10080
  }
10079
10081
  runLogger.stageEnd("spec_gen", { taskCount: initialTasks.length });
10080
10082
  } catch (err) {
10081
10083
  runLogger.stageFail("spec_gen", err.message);
10082
- console.error(chalk19.red(" \u2718 Spec generation failed:"), err);
10084
+ console.error(chalk18.red(" \u2718 Spec generation failed:"), err);
10083
10085
  process.exit(1);
10084
10086
  }
10085
10087
  let finalSpec;
10086
10088
  if (opts.fast) {
10087
- console.log(chalk19.gray("\n[3/6] Skipping refinement (--fast)."));
10089
+ console.log(chalk18.gray("\n[3/6] Skipping refinement (--fast)."));
10088
10090
  finalSpec = initialSpec;
10089
10091
  } else {
10090
- console.log(chalk19.blue("\n[3/6] Interactive spec refinement..."));
10092
+ console.log(chalk18.blue("\n[3/6] Interactive spec refinement..."));
10091
10093
  runLogger.stageStart("spec_refine");
10092
10094
  const refiner = new SpecRefiner(specProvider);
10093
10095
  finalSpec = await refiner.refineLoop(initialSpec);
@@ -10098,7 +10100,7 @@ program.command("create").description("Generate a feature spec and kick off code
10098
10100
  const shouldRunAssessment = !opts.skipAssessment && (!opts.auto || minScore > 0);
10099
10101
  if (shouldRunAssessment) {
10100
10102
  if (!opts.auto) {
10101
- console.log(chalk19.blue("\n[3.4/6] Spec quality assessment..."));
10103
+ console.log(chalk18.blue("\n[3.4/6] Spec quality assessment..."));
10102
10104
  }
10103
10105
  runLogger.stageStart("spec_assess");
10104
10106
  const assessment = await assessSpec(specProvider, finalSpec, context.constitution ?? void 0);
@@ -10107,45 +10109,45 @@ program.command("create").description("Generate a feature spec and kick off code
10107
10109
  if (!opts.auto) printSpecAssessment(assessment);
10108
10110
  if (minScore > 0 && assessment.overallScore < minScore) {
10109
10111
  if (opts.force) {
10110
- console.log(chalk19.yellow(`
10112
+ console.log(chalk18.yellow(`
10111
10113
  \u26A0 Score gate: ${assessment.overallScore}/10 < minimum ${minScore}/10 \u2014 bypassed with --force.`));
10112
10114
  } else {
10113
10115
  runLogger.stageFail("spec_assess", `Score gate: ${assessment.overallScore} < ${minScore}`);
10114
- console.log(chalk19.red(`
10116
+ console.log(chalk18.red(`
10115
10117
  \u2718 Spec quality gate failed: overallScore ${assessment.overallScore}/10 < minimum ${minScore}/10`));
10116
10118
  if (!opts.auto) {
10117
- console.log(chalk19.gray(` Address the issues above and re-run, or use --force to bypass.`));
10119
+ console.log(chalk18.gray(` Address the issues above and re-run, or use --force to bypass.`));
10118
10120
  } else {
10119
- console.log(chalk19.gray(` Auto mode: gate enforced. Fix the spec or lower minSpecScore, or use --force to bypass.`));
10121
+ console.log(chalk18.gray(` Auto mode: gate enforced. Fix the spec or lower minSpecScore, or use --force to bypass.`));
10120
10122
  }
10121
- console.log(chalk19.gray(` Gate threshold set in .ai-spec.json \u2192 "minSpecScore": ${minScore}`));
10123
+ console.log(chalk18.gray(` Gate threshold set in .ai-spec.json \u2192 "minSpecScore": ${minScore}`));
10122
10124
  process.exit(1);
10123
10125
  }
10124
10126
  }
10125
10127
  } else {
10126
10128
  runLogger.stageEnd("spec_assess", { skipped: true });
10127
10129
  if (!opts.auto) {
10128
- console.log(chalk19.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
10130
+ console.log(chalk18.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
10129
10131
  }
10130
10132
  }
10131
10133
  }
10132
10134
  if (!opts.auto) {
10133
- console.log(chalk19.blue("\n[3.5/6] Approval Gate \u2014 review before code generation"));
10135
+ console.log(chalk18.blue("\n[3.5/6] Approval Gate \u2014 review before code generation"));
10134
10136
  const specLines = finalSpec.split("\n").length;
10135
10137
  const specWords = finalSpec.split(/\s+/).length;
10136
10138
  const taskCountHint = initialTasks.length > 0 ? ` Tasks generated : ${initialTasks.length}` : "";
10137
- console.log(chalk19.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10138
- if (taskCountHint) console.log(chalk19.gray(taskCountHint));
10139
- const previewSpecsDir = path23.join(currentDir, "specs");
10139
+ console.log(chalk18.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10140
+ if (taskCountHint) console.log(chalk18.gray(taskCountHint));
10141
+ const previewSpecsDir = path22.join(currentDir, "specs");
10140
10142
  const slug = featureSlug;
10141
10143
  const prevVersion = await findLatestVersion(previewSpecsDir, slug);
10142
10144
  if (prevVersion) {
10143
- console.log(chalk19.gray(` Previous version: v${prevVersion.version} (${prevVersion.filePath})`));
10145
+ console.log(chalk18.gray(` Previous version: v${prevVersion.version} (${prevVersion.filePath})`));
10144
10146
  const diff = computeDiff(prevVersion.content, finalSpec);
10145
- 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"));
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"));
10146
10148
  printDiffSummary(diff, `v${prevVersion.version} \u2192 v${prevVersion.version + 1}`);
10147
10149
  printDiff(diff);
10148
- 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"));
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"));
10149
10151
  }
10150
10152
  const gate = await select3({
10151
10153
  message: "Ready to proceed to code generation?",
@@ -10156,9 +10158,9 @@ program.command("create").description("Generate a feature spec and kick off code
10156
10158
  ]
10157
10159
  });
10158
10160
  if (gate === "view") {
10159
- console.log(chalk19.cyan("\n" + "\u2500".repeat(52)));
10161
+ console.log(chalk18.cyan("\n" + "\u2500".repeat(52)));
10160
10162
  console.log(finalSpec);
10161
- console.log(chalk19.cyan("\u2500".repeat(52) + "\n"));
10163
+ console.log(chalk18.cyan("\u2500".repeat(52) + "\n"));
10162
10164
  const confirm22 = await select3({
10163
10165
  message: "Proceed to code generation?",
10164
10166
  choices: [
@@ -10167,92 +10169,92 @@ program.command("create").description("Generate a feature spec and kick off code
10167
10169
  ]
10168
10170
  });
10169
10171
  if (confirm22 === "abort") {
10170
- console.log(chalk19.yellow(" Aborted. Spec was NOT saved."));
10172
+ console.log(chalk18.yellow(" Aborted. Spec was NOT saved."));
10171
10173
  process.exit(0);
10172
10174
  }
10173
10175
  } else if (gate === "abort") {
10174
- console.log(chalk19.yellow(" Aborted. Spec was NOT saved."));
10176
+ console.log(chalk18.yellow(" Aborted. Spec was NOT saved."));
10175
10177
  process.exit(0);
10176
10178
  }
10177
- console.log(chalk19.green(" \u2714 Approved \u2014 continuing to code generation."));
10179
+ console.log(chalk18.green(" \u2714 Approved \u2014 continuing to code generation."));
10178
10180
  } else {
10179
- console.log(chalk19.gray("[3.5/6] Approval Gate: skipped (--auto)."));
10181
+ console.log(chalk18.gray("[3.5/6] Approval Gate: skipped (--auto)."));
10180
10182
  }
10181
10183
  let extractedDsl = null;
10182
10184
  if (opts.skipDsl) {
10183
- console.log(chalk19.gray("\n[DSL] Skipped (--skip-dsl)."));
10185
+ console.log(chalk18.gray("\n[DSL] Skipped (--skip-dsl)."));
10184
10186
  } else {
10185
- console.log(chalk19.blue("\n[DSL] Extracting structured DSL from spec..."));
10186
- console.log(chalk19.gray(` Provider: ${specProviderName}/${specModelName}`));
10187
+ console.log(chalk18.blue("\n[DSL] Extracting structured DSL from spec..."));
10188
+ console.log(chalk18.gray(` Provider: ${specProviderName}/${specModelName}`));
10187
10189
  runLogger.stageStart("dsl_extract");
10188
10190
  try {
10189
10191
  const isFrontend = isFrontendDeps(context.dependencies);
10190
- if (isFrontend) console.log(chalk19.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
10192
+ if (isFrontend) console.log(chalk18.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
10191
10193
  const dslExtractor = new DslExtractor(specProvider);
10192
10194
  extractedDsl = await dslExtractor.extract(finalSpec, { auto: opts.auto, isFrontend });
10193
10195
  if (extractedDsl) {
10194
10196
  runLogger.stageEnd("dsl_extract", { endpoints: extractedDsl.endpoints?.length ?? 0, models: extractedDsl.models?.length ?? 0 });
10195
- console.log(chalk19.green(" \u2714 DSL extracted and validated."));
10197
+ console.log(chalk18.green(" \u2714 DSL extracted and validated."));
10196
10198
  } else {
10197
10199
  runLogger.stageEnd("dsl_extract", { skipped: true });
10198
- console.log(chalk19.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
10200
+ console.log(chalk18.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
10199
10201
  }
10200
10202
  } catch (err) {
10201
10203
  runLogger.stageFail("dsl_extract", err.message);
10202
- console.log(chalk19.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
10204
+ console.log(chalk18.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
10203
10205
  }
10204
10206
  }
10205
10207
  const isFrontendProject2 = isFrontendDeps(context.dependencies ?? []);
10206
10208
  const skipWorktree = opts.worktree ? false : opts.skipWorktree || isFrontendProject2;
10207
10209
  let workingDir = currentDir;
10208
10210
  if (!skipWorktree) {
10209
- console.log(chalk19.blue("\n[4/6] Setting up git worktree..."));
10211
+ console.log(chalk18.blue("\n[4/6] Setting up git worktree..."));
10210
10212
  const worktreeManager = new GitWorktreeManager(currentDir);
10211
10213
  const worktreePath = await worktreeManager.createWorktree(idea);
10212
10214
  if (worktreePath) workingDir = worktreePath;
10213
10215
  } else {
10214
10216
  const reason = opts.worktree ? "" : isFrontendProject2 ? " (frontend project \u2014 use --worktree to override)" : " (--skip-worktree)";
10215
- console.log(chalk19.gray(`[4/6] Skipping worktree${reason}.`));
10217
+ console.log(chalk18.gray(`[4/6] Skipping worktree${reason}.`));
10216
10218
  }
10217
- const specsDir = path23.join(workingDir, "specs");
10218
- await fs24.ensureDir(specsDir);
10219
+ const specsDir = path22.join(workingDir, "specs");
10220
+ await fs23.ensureDir(specsDir);
10219
10221
  const { filePath: specFile, version: specVersion } = await nextVersionPath(specsDir, featureSlug);
10220
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10221
- console.log(chalk19.green(`
10222
- [5/6] \u2714 Spec saved: ${specFile}`) + chalk19.gray(` (v${specVersion})`));
10222
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10223
+ console.log(chalk18.green(`
10224
+ [5/6] \u2714 Spec saved: ${specFile}`) + chalk18.gray(` (v${specVersion})`));
10223
10225
  let savedDslFile = null;
10224
10226
  if (extractedDsl) {
10225
10227
  const dslExtractor = new DslExtractor(specProvider);
10226
10228
  savedDslFile = await dslExtractor.saveDsl(extractedDsl, specFile);
10227
- console.log(chalk19.green(` \u2714 DSL saved : ${savedDslFile}`));
10229
+ console.log(chalk18.green(` \u2714 DSL saved : ${savedDslFile}`));
10228
10230
  }
10229
10231
  if (!opts.skipTasks) {
10230
10232
  const taskGen = new TaskGenerator(specProvider);
10231
10233
  let tasksToSave = initialTasks;
10232
10234
  if (tasksToSave.length === 0) {
10233
- console.log(chalk19.blue(`
10235
+ console.log(chalk18.blue(`
10234
10236
  Generating tasks (separate call)...`));
10235
10237
  try {
10236
10238
  tasksToSave = await taskGen.generateTasks(finalSpec, context);
10237
10239
  } catch (err) {
10238
- console.log(chalk19.yellow(` \u26A0 Task generation failed: ${err.message}`));
10240
+ console.log(chalk18.yellow(` \u26A0 Task generation failed: ${err.message}`));
10239
10241
  }
10240
10242
  }
10241
10243
  if (tasksToSave.length > 0) {
10242
10244
  const sorted = taskGen.sortByLayer(tasksToSave);
10243
10245
  const tasksFile = await taskGen.saveTasks(sorted, specFile);
10244
10246
  printTasks(sorted);
10245
- console.log(chalk19.green(` \u2714 Tasks saved: ${tasksFile}`));
10247
+ console.log(chalk18.green(` \u2714 Tasks saved: ${tasksFile}`));
10246
10248
  } else {
10247
- console.log(chalk19.yellow(" \u26A0 No tasks generated \u2014 code generation will use fallback file planning."));
10249
+ console.log(chalk18.yellow(" \u26A0 No tasks generated \u2014 code generation will use fallback file planning."));
10248
10250
  }
10249
10251
  }
10250
- console.log(chalk19.blue(`
10252
+ console.log(chalk18.blue(`
10251
10253
  [6/6] Code generation (mode: ${codegenMode})...`));
10252
10254
  const codegenProvider = codegenProviderName === specProviderName && codegenApiKey === specApiKey ? specProvider : createProvider(codegenProviderName, codegenApiKey, codegenModelName);
10253
10255
  let generatedTestFiles = [];
10254
10256
  if (opts.tdd && extractedDsl) {
10255
- console.log(chalk19.cyan("\n[TDD] Generating pre-implementation tests (will fail until code is written)..."));
10257
+ console.log(chalk18.cyan("\n[TDD] Generating pre-implementation tests (will fail until code is written)..."));
10256
10258
  const testGen = new TestGenerator(codegenProvider);
10257
10259
  generatedTestFiles = await testGen.generateTdd(extractedDsl, workingDir);
10258
10260
  }
@@ -10266,13 +10268,13 @@ program.command("create").description("Generate a feature spec and kick off code
10266
10268
  });
10267
10269
  runLogger.stageEnd("codegen", { filesGenerated: generatedFiles.length });
10268
10270
  if (opts.tdd) {
10269
- console.log(chalk19.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
10271
+ console.log(chalk18.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
10270
10272
  } else if (opts.skipTests) {
10271
- console.log(chalk19.gray("\n[7/9] Skipping test generation (--skip-tests)."));
10273
+ console.log(chalk18.gray("\n[7/9] Skipping test generation (--skip-tests)."));
10272
10274
  } else if (!extractedDsl) {
10273
- console.log(chalk19.gray("\n[7/9] Skipping test generation (no DSL available)."));
10275
+ console.log(chalk18.gray("\n[7/9] Skipping test generation (no DSL available)."));
10274
10276
  } else {
10275
- console.log(chalk19.blue(`
10277
+ console.log(chalk18.blue(`
10276
10278
  [7/9] Test skeleton generation...`));
10277
10279
  runLogger.stageStart("test_gen");
10278
10280
  const testGen = new TestGenerator(codegenProvider);
@@ -10280,10 +10282,10 @@ program.command("create").description("Generate a feature spec and kick off code
10280
10282
  runLogger.stageEnd("test_gen", { filesGenerated: generatedTestFiles.length });
10281
10283
  }
10282
10284
  if (opts.skipErrorFeedback) {
10283
- console.log(chalk19.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10285
+ console.log(chalk18.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10284
10286
  } else {
10285
10287
  if (opts.tdd) {
10286
- console.log(chalk19.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10288
+ console.log(chalk18.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10287
10289
  }
10288
10290
  runLogger.stageStart("error_feedback");
10289
10291
  await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
@@ -10294,10 +10296,10 @@ program.command("create").description("Generate a feature spec and kick off code
10294
10296
  }
10295
10297
  let reviewResult = "";
10296
10298
  if (!opts.skipReview) {
10297
- console.log(chalk19.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10299
+ console.log(chalk18.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10298
10300
  runLogger.stageStart("review");
10299
10301
  const reviewer = new CodeReviewer(specProvider, currentDir);
10300
- const savedSpec = await fs24.readFile(specFile, "utf-8");
10302
+ const savedSpec = await fs23.readFile(specFile, "utf-8");
10301
10303
  if (codegenMode === "api" && generatedFiles.length > 0) {
10302
10304
  reviewResult = await reviewer.reviewFiles(savedSpec, generatedFiles, workingDir, specFile);
10303
10305
  } else {
@@ -10313,19 +10315,19 @@ program.command("create").description("Generate a feature spec and kick off code
10313
10315
  await accumulateReviewKnowledge(specProvider, currentDir, reviewResult);
10314
10316
  }
10315
10317
  runLogger.finish();
10316
- console.log(chalk19.bold.green("\n\u2714 All done!"));
10317
- console.log(chalk19.gray(` Spec : ${specFile}`));
10318
- if (savedDslFile) console.log(chalk19.gray(` DSL : ${savedDslFile}`));
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}`));
10319
10321
  if (generatedTestFiles.length > 0) {
10320
- console.log(chalk19.gray(` Tests : ${generatedTestFiles.length} skeleton file(s) generated`));
10322
+ console.log(chalk18.gray(` Tests : ${generatedTestFiles.length} skeleton file(s) generated`));
10321
10323
  }
10322
- console.log(chalk19.gray(` Working dir : ${workingDir}`));
10324
+ console.log(chalk18.gray(` Working dir : ${workingDir}`));
10323
10325
  if (workingDir !== currentDir) {
10324
- console.log(chalk19.gray(` Run \`cd ${workingDir}\` to enter the worktree.`));
10326
+ console.log(chalk18.gray(` Run \`cd ${workingDir}\` to enter the worktree.`));
10325
10327
  }
10326
10328
  runLogger.printSummary();
10327
10329
  if (runSnapshot.fileCount > 0) {
10328
- console.log(chalk19.gray(` To undo changes: ai-spec restore ${runId}`));
10330
+ console.log(chalk18.gray(` To undo changes: ai-spec restore ${runId}`));
10329
10331
  }
10330
10332
  });
10331
10333
  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(
@@ -10342,24 +10344,24 @@ program.command("review").description("Run AI code review on current git diff ag
10342
10344
  const reviewer = new CodeReviewer(provider, currentDir);
10343
10345
  let specContent = "";
10344
10346
  let resolvedSpecFile;
10345
- if (specFile && await fs24.pathExists(specFile)) {
10346
- specContent = await fs24.readFile(specFile, "utf-8");
10347
+ if (specFile && await fs23.pathExists(specFile)) {
10348
+ specContent = await fs23.readFile(specFile, "utf-8");
10347
10349
  resolvedSpecFile = specFile;
10348
- console.log(chalk19.gray(`Using spec: ${specFile}`));
10350
+ console.log(chalk18.gray(`Using spec: ${specFile}`));
10349
10351
  } else {
10350
- const specsDir = path23.join(currentDir, "specs");
10351
- if (await fs24.pathExists(specsDir)) {
10352
- const files = (await fs24.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10352
+ const specsDir = path22.join(currentDir, "specs");
10353
+ if (await fs23.pathExists(specsDir)) {
10354
+ const files = (await fs23.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10353
10355
  if (files.length > 0) {
10354
- const latest = path23.join(specsDir, files[0]);
10355
- specContent = await fs24.readFile(latest, "utf-8");
10356
+ const latest = path22.join(specsDir, files[0]);
10357
+ specContent = await fs23.readFile(latest, "utf-8");
10356
10358
  resolvedSpecFile = latest;
10357
- console.log(chalk19.gray(`Auto-detected spec: specs/${files[0]}`));
10359
+ console.log(chalk18.gray(`Auto-detected spec: specs/${files[0]}`));
10358
10360
  }
10359
10361
  }
10360
10362
  }
10361
10363
  if (!specContent) {
10362
- console.log(chalk19.yellow("No spec file found. Running review without spec context."));
10364
+ console.log(chalk18.yellow("No spec file found. Running review without spec context."));
10363
10365
  }
10364
10366
  await reviewer.reviewCode(specContent, resolvedSpecFile);
10365
10367
  await reviewer.printScoreTrend();
@@ -10386,15 +10388,15 @@ program.command("init").description(`Analyze codebase and generate Project Const
10386
10388
  auto: opts.auto
10387
10389
  });
10388
10390
  if (result.written) {
10389
- console.log(chalk19.blue("\n Summary:"));
10390
- 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)})`));
10391
- console.log(chalk19.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
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`));
10392
10394
  if (result.backupPath) {
10393
- console.log(chalk19.gray(` Backup: ${path23.basename(result.backupPath)}`));
10395
+ console.log(chalk18.gray(` Backup: ${path22.basename(result.backupPath)}`));
10394
10396
  }
10395
10397
  }
10396
10398
  } catch (err) {
10397
- console.error(chalk19.red(` \u2718 Consolidation failed: ${err.message}`));
10399
+ console.error(chalk18.red(` \u2718 Consolidation failed: ${err.message}`));
10398
10400
  process.exit(1);
10399
10401
  }
10400
10402
  return;
@@ -10402,75 +10404,75 @@ program.command("init").description(`Analyze codebase and generate Project Const
10402
10404
  if (opts.global) {
10403
10405
  const existing = await loadGlobalConstitution([currentDir]);
10404
10406
  if (existing && !opts.force) {
10405
- console.log(chalk19.yellow(`
10407
+ console.log(chalk18.yellow(`
10406
10408
  Global constitution already exists at: ${existing.source}`));
10407
- console.log(chalk19.gray(" Use --force to overwrite it."));
10409
+ console.log(chalk18.gray(" Use --force to overwrite it."));
10408
10410
  return;
10409
10411
  }
10410
- 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"));
10411
- console.log(chalk19.gray(` Provider: ${providerName}/${modelName}`));
10412
- console.log(chalk19.gray(" Scanning repos in workspace..."));
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..."));
10413
10415
  const loader = new ContextLoader(currentDir);
10414
10416
  const ctx = await loader.loadProjectContext();
10415
10417
  const summary = [
10416
10418
  `Tech stack: ${ctx.techStack.join(", ") || "unknown"}`,
10417
10419
  `Dependencies: ${ctx.dependencies.slice(0, 20).join(", ")}`
10418
10420
  ].join("\n");
10419
- const prompt = buildGlobalConstitutionPrompt([{ name: path23.basename(currentDir), summary }]);
10421
+ const prompt = buildGlobalConstitutionPrompt([{ name: path22.basename(currentDir), summary }]);
10420
10422
  let globalConstitution;
10421
10423
  try {
10422
10424
  globalConstitution = await provider.generate(prompt, globalConstitutionSystemPrompt);
10423
10425
  } catch (err) {
10424
- console.error(chalk19.red(" \u2718 Failed to generate global constitution:"), err);
10426
+ console.error(chalk18.red(" \u2718 Failed to generate global constitution:"), err);
10425
10427
  process.exit(1);
10426
10428
  }
10427
10429
  const saved2 = await saveGlobalConstitution(globalConstitution, currentDir);
10428
- console.log(chalk19.green(`
10430
+ console.log(chalk18.green(`
10429
10431
  \u2714 Global constitution saved: ${saved2}`));
10430
- console.log(chalk19.gray(" This will be automatically merged into all project constitutions in this workspace."));
10431
- console.log(chalk19.gray(" Project-level rules always override global rules.\n"));
10432
- console.log(chalk19.bold(" Preview:"));
10433
- console.log(chalk19.gray(globalConstitution.split("\n").slice(0, 12).join("\n")));
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")));
10434
10436
  if (globalConstitution.split("\n").length > 12) {
10435
- console.log(chalk19.gray(` ... (${globalConstitution.split("\n").length} lines total)`));
10437
+ console.log(chalk18.gray(` ... (${globalConstitution.split("\n").length} lines total)`));
10436
10438
  }
10437
10439
  return;
10438
10440
  }
10439
- const constitutionPath = path23.join(currentDir, CONSTITUTION_FILE);
10440
- if (!opts.force && await fs24.pathExists(constitutionPath)) {
10441
- console.log(chalk19.yellow(`
10441
+ const constitutionPath = path22.join(currentDir, CONSTITUTION_FILE);
10442
+ if (!opts.force && await fs23.pathExists(constitutionPath)) {
10443
+ console.log(chalk18.yellow(`
10442
10444
  ${CONSTITUTION_FILE} already exists.`));
10443
- console.log(chalk19.gray(" Use --force to overwrite it."));
10444
- console.log(chalk19.gray(` Or edit it directly: ${constitutionPath}`));
10445
+ console.log(chalk18.gray(" Use --force to overwrite it."));
10446
+ console.log(chalk18.gray(` Or edit it directly: ${constitutionPath}`));
10445
10447
  return;
10446
10448
  }
10447
- console.log(chalk19.blue("\n\u2500\u2500\u2500 Generating Project Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10448
- console.log(chalk19.gray(` Provider: ${providerName}/${modelName}`));
10449
- console.log(chalk19.gray(" Analyzing codebase..."));
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..."));
10450
10452
  const generator = new ConstitutionGenerator(provider);
10451
10453
  let constitution;
10452
10454
  try {
10453
10455
  constitution = await generator.generate(currentDir);
10454
10456
  } catch (err) {
10455
- console.error(chalk19.red(" \u2718 Failed to generate constitution:"), err);
10457
+ console.error(chalk18.red(" \u2718 Failed to generate constitution:"), err);
10456
10458
  process.exit(1);
10457
10459
  }
10458
10460
  const saved = await generator.saveConstitution(currentDir, constitution);
10459
- const globalResult = await loadGlobalConstitution([path23.dirname(currentDir)]);
10461
+ const globalResult = await loadGlobalConstitution([path22.dirname(currentDir)]);
10460
10462
  if (globalResult) {
10461
- console.log(chalk19.cyan(`
10463
+ console.log(chalk18.cyan(`
10462
10464
  \u2139 Global constitution detected: ${globalResult.source}`));
10463
- console.log(chalk19.gray(" It will be merged with this project constitution at runtime."));
10464
- console.log(chalk19.gray(" Project rules take priority over global rules."));
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."));
10465
10467
  }
10466
- console.log(chalk19.green(`
10468
+ console.log(chalk18.green(`
10467
10469
  \u2714 Constitution saved: ${saved}`));
10468
- console.log(chalk19.gray(" This file will be automatically used in all future `ai-spec create` runs."));
10469
- console.log(chalk19.gray(" Edit it to add custom rules or red lines for your project.\n"));
10470
- console.log(chalk19.bold(" Preview:"));
10471
- console.log(chalk19.gray(constitution.split("\n").slice(0, 15).join("\n")));
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")));
10472
10474
  if (constitution.split("\n").length > 15) {
10473
- console.log(chalk19.gray(` ... (${constitution.split("\n").length} lines total)`));
10475
+ console.log(chalk18.gray(` ... (${constitution.split("\n").length} lines total)`));
10474
10476
  }
10475
10477
  });
10476
10478
  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(
@@ -10478,44 +10480,44 @@ program.command("config").description(`Set default configuration for this projec
10478
10480
  "Default code generation mode (claude-code|api|plan)"
10479
10481
  ).option("--codegen-provider <name>", "Default provider for code generation").option("--codegen-model <name>", "Default model for code generation").option("--min-spec-score <score>", "Minimum overall spec score (1-10) to pass Approval Gate (0 = disabled)").option("--show", "Print current configuration").option("--reset", "Reset configuration to empty").option("--clear-keys", "Delete all saved API keys from ~/.ai-spec-keys.json").option("--clear-key <provider>", "Delete saved API key for a specific provider").option("--list-keys", "Show which providers have a saved key").action(async (opts) => {
10480
10482
  const currentDir = process.cwd();
10481
- const configPath = path23.join(currentDir, CONFIG_FILE);
10483
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10482
10484
  if (opts.clearKeys) {
10483
10485
  await clearAllKeys();
10484
- console.log(chalk19.green(`\u2714 All saved API keys cleared.`));
10486
+ console.log(chalk18.green(`\u2714 All saved API keys cleared.`));
10485
10487
  return;
10486
10488
  }
10487
10489
  if (opts.clearKey) {
10488
10490
  await clearKey(opts.clearKey);
10489
- console.log(chalk19.green(`\u2714 Saved key for "${opts.clearKey}" removed.`));
10491
+ console.log(chalk18.green(`\u2714 Saved key for "${opts.clearKey}" removed.`));
10490
10492
  return;
10491
10493
  }
10492
10494
  if (opts.listKeys) {
10493
- const store = await fs24.readJson(KEY_STORE_FILE).catch(() => ({}));
10495
+ const store = await fs23.readJson(KEY_STORE_FILE).catch(() => ({}));
10494
10496
  const providers = Object.keys(store);
10495
10497
  if (providers.length === 0) {
10496
- console.log(chalk19.gray("No saved API keys."));
10498
+ console.log(chalk18.gray("No saved API keys."));
10497
10499
  } else {
10498
- console.log(chalk19.bold("Saved API keys:"));
10500
+ console.log(chalk18.bold("Saved API keys:"));
10499
10501
  for (const p of providers) {
10500
10502
  const k2 = store[p];
10501
- console.log(chalk19.gray(` ${p}: ${k2.slice(0, 6)}...${k2.slice(-4)}`));
10503
+ console.log(chalk18.gray(` ${p}: ${k2.slice(0, 6)}...${k2.slice(-4)}`));
10502
10504
  }
10503
- console.log(chalk19.gray(`
10505
+ console.log(chalk18.gray(`
10504
10506
  File: ${KEY_STORE_FILE}`));
10505
10507
  }
10506
10508
  return;
10507
10509
  }
10508
10510
  if (opts.reset) {
10509
- await fs24.writeJson(configPath, {}, { spaces: 2 });
10510
- console.log(chalk19.green(`\u2714 Config reset: ${configPath}`));
10511
+ await fs23.writeJson(configPath, {}, { spaces: 2 });
10512
+ console.log(chalk18.green(`\u2714 Config reset: ${configPath}`));
10511
10513
  return;
10512
10514
  }
10513
10515
  const existing = await loadConfig(currentDir);
10514
10516
  if (opts.show) {
10515
10517
  if (Object.keys(existing).length === 0) {
10516
- console.log(chalk19.gray("No config file found. Using built-in defaults."));
10518
+ console.log(chalk18.gray("No config file found. Using built-in defaults."));
10517
10519
  } else {
10518
- console.log(chalk19.bold(`${configPath}:`));
10520
+ console.log(chalk18.bold(`${configPath}:`));
10519
10521
  console.log(JSON.stringify(existing, null, 2));
10520
10522
  }
10521
10523
  return;
@@ -10529,27 +10531,27 @@ File: ${KEY_STORE_FILE}`));
10529
10531
  if (opts.minSpecScore !== void 0) {
10530
10532
  const score = parseInt(opts.minSpecScore, 10);
10531
10533
  if (isNaN(score) || score < 0 || score > 10) {
10532
- console.error(chalk19.red(" --min-spec-score must be a number between 0 and 10"));
10534
+ console.error(chalk18.red(" --min-spec-score must be a number between 0 and 10"));
10533
10535
  process.exit(1);
10534
10536
  }
10535
10537
  updated.minSpecScore = score;
10536
10538
  }
10537
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10538
- console.log(chalk19.green(`\u2714 Config saved to ${configPath}`));
10539
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10540
+ console.log(chalk18.green(`\u2714 Config saved to ${configPath}`));
10539
10541
  console.log(JSON.stringify(updated, null, 2));
10540
10542
  });
10541
10543
  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) => {
10542
10544
  const currentDir = process.cwd();
10543
- const configPath = path23.join(currentDir, CONFIG_FILE);
10545
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10544
10546
  if (opts.list) {
10545
- console.log(chalk19.bold("\nAvailable providers & models:\n"));
10547
+ console.log(chalk18.bold("\nAvailable providers & models:\n"));
10546
10548
  for (const [key, meta] of Object.entries(PROVIDER_CATALOG)) {
10547
10549
  console.log(
10548
- ` ${chalk19.bold.cyan(key.padEnd(10))} ${chalk19.white(meta.displayName)}`
10550
+ ` ${chalk18.bold.cyan(key.padEnd(10))} ${chalk18.white(meta.displayName)}`
10549
10551
  );
10550
- console.log(chalk19.gray(` ${meta.description}`));
10552
+ console.log(chalk18.gray(` ${meta.description}`));
10551
10553
  console.log(
10552
- chalk19.gray(
10554
+ chalk18.gray(
10553
10555
  ` env: ${meta.envKey} | models: ${meta.models.join(", ")}`
10554
10556
  )
10555
10557
  );
@@ -10558,10 +10560,10 @@ program.command("model").description("Interactively switch the active AI provide
10558
10560
  return;
10559
10561
  }
10560
10562
  const existing = await loadConfig(currentDir);
10561
- 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"));
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"));
10562
10564
  if (Object.keys(existing).length > 0) {
10563
10565
  console.log(
10564
- chalk19.gray(
10566
+ chalk18.gray(
10565
10567
  ` Current: spec=${existing.provider ?? "gemini"}/${existing.model ?? DEFAULT_MODELS[existing.provider ?? "gemini"]}` + (existing.codegenProvider ? ` codegen=${existing.codegenProvider}/${existing.codegenModel ?? ""}` : "")
10566
10568
  )
10567
10569
  );
@@ -10579,7 +10581,7 @@ program.command("model").description("Interactively switch the active AI provide
10579
10581
  const providerKey = await select3({
10580
10582
  message: `${label} \u2014 select provider:`,
10581
10583
  choices: Object.entries(PROVIDER_CATALOG).map(([key, meta2]) => ({
10582
- name: `${meta2.displayName.padEnd(22)} ${chalk19.gray(meta2.description)}`,
10584
+ name: `${meta2.displayName.padEnd(22)} ${chalk18.gray(meta2.description)}`,
10583
10585
  value: key,
10584
10586
  short: meta2.displayName
10585
10587
  }))
@@ -10587,7 +10589,7 @@ program.command("model").description("Interactively switch the active AI provide
10587
10589
  const meta = PROVIDER_CATALOG[providerKey];
10588
10590
  const modelChoices = [
10589
10591
  ...meta.models.map((m) => ({ name: m, value: m })),
10590
- { name: chalk19.italic("\u270E Enter custom model name..."), value: "__custom__" }
10592
+ { name: chalk18.italic("\u270E Enter custom model name..."), value: "__custom__" }
10591
10593
  ];
10592
10594
  let chosenModel = await select3({
10593
10595
  message: `${label} \u2014 select model (${meta.displayName}):`,
@@ -10621,37 +10623,37 @@ program.command("model").description("Interactively switch the active AI provide
10621
10623
  if (!updated.codegen || updated.codegen === "claude-code") {
10622
10624
  updated.codegen = "api";
10623
10625
  console.log(
10624
- chalk19.yellow(
10626
+ chalk18.yellow(
10625
10627
  `
10626
10628
  \u26A0 provider "${effectiveCodegenProvider}" \u4E0D\u652F\u6301 "claude-code" \u6A21\u5F0F\u3002`
10627
10629
  )
10628
10630
  );
10629
- console.log(chalk19.gray(` \u5DF2\u81EA\u52A8\u5C06 codegen \u6A21\u5F0F\u8BBE\u4E3A "api"\u3002`));
10631
+ console.log(chalk18.gray(` \u5DF2\u81EA\u52A8\u5C06 codegen \u6A21\u5F0F\u8BBE\u4E3A "api"\u3002`));
10630
10632
  }
10631
10633
  }
10632
10634
  }
10633
- console.log(chalk19.blue("\n Preview:"));
10634
- console.log(chalk19.gray(` spec \u2192 ${updated.provider}/${updated.model}`));
10635
+ console.log(chalk18.blue("\n Preview:"));
10636
+ console.log(chalk18.gray(` spec \u2192 ${updated.provider}/${updated.model}`));
10635
10637
  if (updated.codegenProvider) {
10636
10638
  console.log(
10637
- chalk19.gray(
10639
+ chalk18.gray(
10638
10640
  ` codegen \u2192 ${updated.codegenProvider}/${updated.codegenModel} (mode: ${updated.codegen ?? "claude-code"})`
10639
10641
  )
10640
10642
  );
10641
10643
  }
10642
10644
  const ok = await confirm2({ message: "Save to .ai-spec.json?", default: true });
10643
10645
  if (!ok) {
10644
- console.log(chalk19.gray(" Cancelled."));
10646
+ console.log(chalk18.gray(" Cancelled."));
10645
10647
  return;
10646
10648
  }
10647
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10648
- console.log(chalk19.green(`
10649
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10650
+ console.log(chalk18.green(`
10649
10651
  \u2714 Saved to ${configPath}`));
10650
10652
  const providerToCheck = updated.provider ?? "gemini";
10651
10653
  const envKey = ENV_KEY_MAP[providerToCheck];
10652
10654
  if (envKey && !process.env[envKey]) {
10653
10655
  console.log(
10654
- chalk19.yellow(
10656
+ chalk18.yellow(
10655
10657
  ` \u26A0 Remember to set ${envKey} in your environment or .env file.`
10656
10658
  )
10657
10659
  );
@@ -10670,29 +10672,29 @@ async function runSingleRepoPipelineInWorkspace(opts) {
10670
10672
  cliOpts,
10671
10673
  contractContextSection
10672
10674
  } = opts;
10673
- console.log(chalk19.blue(`
10675
+ console.log(chalk18.blue(`
10674
10676
  [${repoName}] Loading project context...`));
10675
10677
  const loader = new ContextLoader(repoAbsPath);
10676
10678
  let context = await loader.loadProjectContext();
10677
10679
  const { type: detectedRepoType } = await detectRepoType(repoAbsPath);
10678
- console.log(chalk19.gray(` Tech stack: ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10679
- console.log(chalk19.gray(` Dependencies: ${context.dependencies.length} packages`));
10680
+ console.log(chalk18.gray(` Tech stack: ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10681
+ console.log(chalk18.gray(` Dependencies: ${context.dependencies.length} packages`));
10680
10682
  if (context.constitution && context.constitution.length > 6e3) {
10681
- console.log(chalk19.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10683
+ console.log(chalk18.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10682
10684
  }
10683
10685
  if (!context.constitution) {
10684
- console.log(chalk19.yellow(` Constitution: not found \u2014 auto-generating...`));
10686
+ console.log(chalk18.yellow(` Constitution: not found \u2014 auto-generating...`));
10685
10687
  try {
10686
10688
  const constitutionGen = new ConstitutionGenerator(specProvider);
10687
10689
  const constitutionContent = await constitutionGen.generate(repoAbsPath);
10688
10690
  await constitutionGen.saveConstitution(repoAbsPath, constitutionContent);
10689
10691
  context.constitution = constitutionContent;
10690
- console.log(chalk19.green(` Constitution: generated`));
10692
+ console.log(chalk18.green(` Constitution: generated`));
10691
10693
  } catch (err) {
10692
- console.log(chalk19.yellow(` Constitution: auto-generation failed (${err.message}), continuing.`));
10694
+ console.log(chalk18.yellow(` Constitution: auto-generation failed (${err.message}), continuing.`));
10693
10695
  }
10694
10696
  } else {
10695
- console.log(chalk19.green(` Constitution: found`));
10697
+ console.log(chalk18.green(` Constitution: found`));
10696
10698
  }
10697
10699
  let fullIdea = idea;
10698
10700
  if (contractContextSection) {
@@ -10700,58 +10702,58 @@ async function runSingleRepoPipelineInWorkspace(opts) {
10700
10702
 
10701
10703
  ${contractContextSection}`;
10702
10704
  }
10703
- console.log(chalk19.blue(` [${repoName}] Generating spec...`));
10705
+ console.log(chalk18.blue(` [${repoName}] Generating spec...`));
10704
10706
  let finalSpec;
10705
10707
  try {
10706
10708
  const result = await generateSpecWithTasks(specProvider, fullIdea, context);
10707
10709
  finalSpec = result.spec;
10708
- console.log(chalk19.green(` Spec generated.`));
10710
+ console.log(chalk18.green(` Spec generated.`));
10709
10711
  } catch (err) {
10710
- console.error(chalk19.red(` Spec generation failed: ${err.message}`));
10712
+ console.error(chalk18.red(` Spec generation failed: ${err.message}`));
10711
10713
  return { dsl: null, specFile: null };
10712
10714
  }
10713
10715
  let extractedDsl = null;
10714
10716
  if (!cliOpts.skipDsl) {
10715
- console.log(chalk19.blue(` [${repoName}] Extracting DSL...`));
10717
+ console.log(chalk18.blue(` [${repoName}] Extracting DSL...`));
10716
10718
  try {
10717
10719
  const dslExtractor = new DslExtractor(specProvider);
10718
10720
  const repoIsFrontend = isFrontendDeps(context.dependencies);
10719
10721
  extractedDsl = await dslExtractor.extract(finalSpec, { auto: true, isFrontend: repoIsFrontend });
10720
10722
  if (extractedDsl) {
10721
- console.log(chalk19.green(` DSL extracted.`));
10723
+ console.log(chalk18.green(` DSL extracted.`));
10722
10724
  }
10723
10725
  } catch (err) {
10724
- console.log(chalk19.yellow(` DSL extraction failed: ${err.message}`));
10726
+ console.log(chalk18.yellow(` DSL extraction failed: ${err.message}`));
10725
10727
  }
10726
10728
  }
10727
10729
  const isFrontendRepo = isFrontendDeps(context.dependencies ?? []);
10728
10730
  const skipWorktreeForRepo = cliOpts.worktree ? false : cliOpts.skipWorktree || isFrontendRepo;
10729
10731
  let workingDir = repoAbsPath;
10730
10732
  if (!skipWorktreeForRepo) {
10731
- console.log(chalk19.blue(` [${repoName}] Setting up git worktree...`));
10733
+ console.log(chalk18.blue(` [${repoName}] Setting up git worktree...`));
10732
10734
  try {
10733
10735
  const worktreeManager = new GitWorktreeManager(repoAbsPath);
10734
10736
  const worktreePath = await worktreeManager.createWorktree(idea);
10735
10737
  if (worktreePath) workingDir = worktreePath;
10736
10738
  } catch (err) {
10737
- console.log(chalk19.yellow(` Worktree setup failed: ${err.message}. Using main branch.`));
10739
+ console.log(chalk18.yellow(` Worktree setup failed: ${err.message}. Using main branch.`));
10738
10740
  }
10739
10741
  } else {
10740
- console.log(chalk19.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10742
+ console.log(chalk18.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10741
10743
  }
10742
- const specsDir = path23.join(workingDir, "specs");
10743
- await fs24.ensureDir(specsDir);
10744
+ const specsDir = path22.join(workingDir, "specs");
10745
+ await fs23.ensureDir(specsDir);
10744
10746
  const featureSlug = slugify(idea);
10745
10747
  const { filePath: specFile } = await nextVersionPath(specsDir, featureSlug);
10746
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10747
- console.log(chalk19.green(` Spec saved: ${path23.relative(repoAbsPath, specFile)}`));
10748
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10749
+ console.log(chalk18.green(` Spec saved: ${path22.relative(repoAbsPath, specFile)}`));
10748
10750
  let savedDslFile = null;
10749
10751
  if (extractedDsl) {
10750
10752
  const dslExtractorForSave = new DslExtractor(specProvider);
10751
10753
  savedDslFile = await dslExtractorForSave.saveDsl(extractedDsl, specFile);
10752
- console.log(chalk19.green(` DSL saved: ${path23.relative(repoAbsPath, savedDslFile)}`));
10754
+ console.log(chalk18.green(` DSL saved: ${path22.relative(repoAbsPath, savedDslFile)}`));
10753
10755
  }
10754
- console.log(chalk19.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10756
+ console.log(chalk18.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10755
10757
  try {
10756
10758
  const codegen = new CodeGenerator(codegenProvider, codegenMode);
10757
10759
  await codegen.generateCode(specFile, workingDir, context, {
@@ -10759,29 +10761,29 @@ ${contractContextSection}`;
10759
10761
  dslFilePath: savedDslFile ?? void 0,
10760
10762
  repoType: detectedRepoType
10761
10763
  });
10762
- console.log(chalk19.green(` Code generation complete.`));
10764
+ console.log(chalk18.green(` Code generation complete.`));
10763
10765
  } catch (err) {
10764
- console.log(chalk19.yellow(` Code generation failed: ${err.message}`));
10766
+ console.log(chalk18.yellow(` Code generation failed: ${err.message}`));
10765
10767
  }
10766
10768
  if (!cliOpts.skipTests && extractedDsl) {
10767
- console.log(chalk19.blue(` [${repoName}] Generating test skeletons...`));
10769
+ console.log(chalk18.blue(` [${repoName}] Generating test skeletons...`));
10768
10770
  try {
10769
10771
  const testGen = new TestGenerator(codegenProvider);
10770
10772
  const testFiles = await testGen.generate(extractedDsl, workingDir);
10771
- console.log(chalk19.green(` ${testFiles.length} test file(s) generated.`));
10773
+ console.log(chalk18.green(` ${testFiles.length} test file(s) generated.`));
10772
10774
  } catch (err) {
10773
- console.log(chalk19.yellow(` Test generation failed: ${err.message}`));
10775
+ console.log(chalk18.yellow(` Test generation failed: ${err.message}`));
10774
10776
  }
10775
10777
  }
10776
10778
  if (!cliOpts.skipErrorFeedback) {
10777
10779
  try {
10778
10780
  await runErrorFeedback(codegenProvider, workingDir, extractedDsl, { maxCycles: 1 });
10779
10781
  } catch (err) {
10780
- console.log(chalk19.yellow(` Error feedback failed: ${err.message}`));
10782
+ console.log(chalk18.yellow(` Error feedback failed: ${err.message}`));
10781
10783
  }
10782
10784
  }
10783
10785
  if (!cliOpts.skipReview) {
10784
- console.log(chalk19.blue(` [${repoName}] Running code review...`));
10786
+ console.log(chalk18.blue(` [${repoName}] Running code review...`));
10785
10787
  try {
10786
10788
  const reviewer = new CodeReviewer(specProvider);
10787
10789
  const originalDir = process.cwd();
@@ -10793,9 +10795,9 @@ ${contractContextSection}`;
10793
10795
  process.chdir(originalDir);
10794
10796
  }
10795
10797
  await accumulateReviewKnowledge(specProvider, repoAbsPath, reviewResult);
10796
- console.log(chalk19.green(` Code review complete.`));
10798
+ console.log(chalk18.green(` Code review complete.`));
10797
10799
  } catch (err) {
10798
- console.log(chalk19.yellow(` Code review failed: ${err.message}`));
10800
+ console.log(chalk18.yellow(` Code review failed: ${err.message}`));
10799
10801
  }
10800
10802
  }
10801
10803
  return { dsl: extractedDsl, specFile };
@@ -10818,7 +10820,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10818
10820
  codegenModel: codegenModelName
10819
10821
  });
10820
10822
  const workspaceLoader = new WorkspaceLoader(currentDir);
10821
- console.log(chalk19.blue("\n[W1] Loading per-repo contexts..."));
10823
+ console.log(chalk18.blue("\n[W1] Loading per-repo contexts..."));
10822
10824
  const contexts = /* @__PURE__ */ new Map();
10823
10825
  const frontendContexts = /* @__PURE__ */ new Map();
10824
10826
  for (const repo of workspace.repos) {
@@ -10830,27 +10832,27 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10830
10832
  if (repo.role === "frontend" || repo.role === "mobile") {
10831
10833
  const fctx = await loadFrontendContext(repoAbsPath);
10832
10834
  frontendContexts.set(repo.name, fctx);
10833
- console.log(chalk19.gray(` ${repo.name}: ${fctx.framework} / ${fctx.httpClient} / hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
10835
+ console.log(chalk18.gray(` ${repo.name}: ${fctx.framework} / ${fctx.httpClient} / hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
10834
10836
  } else {
10835
- console.log(chalk19.gray(` ${repo.name}: ${ctx.techStack.join(", ") || "unknown"} (${ctx.dependencies.length} deps)`));
10837
+ console.log(chalk18.gray(` ${repo.name}: ${ctx.techStack.join(", ") || "unknown"} (${ctx.dependencies.length} deps)`));
10836
10838
  }
10837
10839
  } catch (err) {
10838
- console.log(chalk19.yellow(` ${repo.name}: context load failed \u2014 ${err.message}`));
10840
+ console.log(chalk18.yellow(` ${repo.name}: context load failed \u2014 ${err.message}`));
10839
10841
  }
10840
10842
  }
10841
- console.log(chalk19.blue("\n[W2] Decomposing requirement across repos..."));
10843
+ console.log(chalk18.blue("\n[W2] Decomposing requirement across repos..."));
10842
10844
  const decomposer = new RequirementDecomposer(specProvider);
10843
10845
  let decomposition;
10844
10846
  try {
10845
10847
  decomposition = await decomposer.decompose(idea, workspace, contexts, frontendContexts);
10846
- console.log(chalk19.green(` Summary: ${decomposition.summary}`));
10847
- console.log(chalk19.gray(` Repos affected: ${decomposition.repos.map((r) => r.repoName).join(", ")}`));
10848
+ console.log(chalk18.green(` Summary: ${decomposition.summary}`));
10849
+ console.log(chalk18.gray(` Repos affected: ${decomposition.repos.map((r) => r.repoName).join(", ")}`));
10848
10850
  if (decomposition.coordinationNotes) {
10849
- console.log(chalk19.gray(` Coordination: ${decomposition.coordinationNotes}`));
10851
+ console.log(chalk18.gray(` Coordination: ${decomposition.coordinationNotes}`));
10850
10852
  }
10851
10853
  } catch (err) {
10852
- console.error(chalk19.red(` Decomposition failed: ${err.message}`));
10853
- console.log(chalk19.yellow(" Falling back to running all repos independently."));
10854
+ console.error(chalk18.red(` Decomposition failed: ${err.message}`));
10855
+ console.log(chalk18.yellow(" Falling back to running all repos independently."));
10854
10856
  decomposition = {
10855
10857
  originalRequirement: idea,
10856
10858
  summary: idea,
@@ -10866,11 +10868,11 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10866
10868
  };
10867
10869
  }
10868
10870
  if (!opts.auto) {
10869
- console.log(chalk19.cyan("\n[W3] Decomposition Preview:"));
10870
- console.log(chalk19.cyan("\u2500".repeat(52)));
10871
+ console.log(chalk18.cyan("\n[W3] Decomposition Preview:"));
10872
+ console.log(chalk18.cyan("\u2500".repeat(52)));
10871
10873
  for (const r of decomposition.repos) {
10872
- console.log(chalk19.bold(` ${r.repoName} (${r.role})`));
10873
- console.log(chalk19.gray(` ${r.specIdea.slice(0, 150)}${r.specIdea.length > 150 ? "..." : ""}`));
10874
+ console.log(chalk18.bold(` ${r.repoName} (${r.role})`));
10875
+ console.log(chalk18.gray(` ${r.specIdea.slice(0, 150)}${r.specIdea.length > 150 ? "..." : ""}`));
10874
10876
  if (r.uxDecisions) {
10875
10877
  const ux = r.uxDecisions;
10876
10878
  const uxSummary = [
@@ -10879,13 +10881,13 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10879
10881
  ux.optimisticUpdate ? "optimistic-update" : "",
10880
10882
  ux.errorRollback ? "rollback" : ""
10881
10883
  ].filter(Boolean).join(", ");
10882
- if (uxSummary) console.log(chalk19.cyan(` UX: ${uxSummary}`));
10884
+ if (uxSummary) console.log(chalk18.cyan(` UX: ${uxSummary}`));
10883
10885
  }
10884
10886
  if (r.dependsOnRepos.length > 0) {
10885
- console.log(chalk19.gray(` Depends on: ${r.dependsOnRepos.join(", ")}`));
10887
+ console.log(chalk18.gray(` Depends on: ${r.dependsOnRepos.join(", ")}`));
10886
10888
  }
10887
10889
  }
10888
- console.log(chalk19.cyan("\u2500".repeat(52)));
10890
+ console.log(chalk18.cyan("\u2500".repeat(52)));
10889
10891
  const gate = await select3({
10890
10892
  message: "Proceed with multi-repo pipeline?",
10891
10893
  choices: [
@@ -10894,24 +10896,24 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10894
10896
  ]
10895
10897
  });
10896
10898
  if (gate === "abort") {
10897
- console.log(chalk19.yellow(" Aborted."));
10899
+ console.log(chalk18.yellow(" Aborted."));
10898
10900
  process.exit(0);
10899
10901
  }
10900
10902
  }
10901
10903
  const sortedRepoRequirements = RequirementDecomposer.sortByDependency(decomposition.repos);
10902
10904
  const contractDsls = /* @__PURE__ */ new Map();
10903
- console.log(chalk19.blue(`
10905
+ console.log(chalk18.blue(`
10904
10906
  [W4] Running pipeline for ${sortedRepoRequirements.length} repo(s)...`));
10905
10907
  const results = [];
10906
10908
  for (const repoReq of sortedRepoRequirements) {
10907
10909
  const repoConfig = workspace.repos.find((r) => r.name === repoReq.repoName);
10908
10910
  if (!repoConfig) {
10909
- console.log(chalk19.yellow(` Skipping ${repoReq.repoName} \u2014 not found in workspace config.`));
10911
+ console.log(chalk18.yellow(` Skipping ${repoReq.repoName} \u2014 not found in workspace config.`));
10910
10912
  results.push({ repoName: repoReq.repoName, status: "skipped", specFile: null, dsl: null, repoAbsPath: "", role: repoReq.role });
10911
10913
  continue;
10912
10914
  }
10913
10915
  const repoAbsPath = workspaceLoader.resolveAbsPath(repoConfig);
10914
- console.log(chalk19.bold.blue(`
10916
+ console.log(chalk18.bold.blue(`
10915
10917
  \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`));
10916
10918
  let contractContextSection;
10917
10919
  if (repoReq.dependsOnRepos.length > 0) {
@@ -10919,7 +10921,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10919
10921
  for (const depName of repoReq.dependsOnRepos) {
10920
10922
  const depDsl = contractDsls.get(depName);
10921
10923
  if (depDsl) {
10922
- console.log(chalk19.gray(` Using API contract from: ${depName}`));
10924
+ console.log(chalk18.gray(` Using API contract from: ${depName}`));
10923
10925
  const contract = buildFrontendApiContract(depDsl);
10924
10926
  contractParts.push(buildContractContextSection(contract));
10925
10927
  }
@@ -10939,7 +10941,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10939
10941
  frontendContext: frontendCtx
10940
10942
  });
10941
10943
  contractContextSection = void 0;
10942
- console.log(chalk19.gray(` Frontend context: ${frontendCtx.framework} / ${frontendCtx.httpClient} / ${frontendCtx.uiLibrary}`));
10944
+ console.log(chalk18.gray(` Frontend context: ${frontendCtx.framework} / ${frontendCtx.httpClient} / ${frontendCtx.uiLibrary}`));
10943
10945
  }
10944
10946
  try {
10945
10947
  const { dsl, specFile } = await runSingleRepoPipelineInWorkspace({
@@ -10956,22 +10958,22 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10956
10958
  });
10957
10959
  if (repoReq.isContractProvider && dsl) {
10958
10960
  contractDsls.set(repoReq.repoName, dsl);
10959
- console.log(chalk19.green(` Contract stored for downstream repos.`));
10961
+ console.log(chalk18.green(` Contract stored for downstream repos.`));
10960
10962
  }
10961
10963
  results.push({ repoName: repoReq.repoName, status: "success", specFile, dsl, repoAbsPath, role: repoReq.role });
10962
- console.log(chalk19.green(` \u2714 ${repoReq.repoName} complete`));
10964
+ console.log(chalk18.green(` \u2714 ${repoReq.repoName} complete`));
10963
10965
  } catch (err) {
10964
- console.error(chalk19.red(` \u2718 ${repoReq.repoName} failed: ${err.message}`));
10966
+ console.error(chalk18.red(` \u2718 ${repoReq.repoName} failed: ${err.message}`));
10965
10967
  results.push({ repoName: repoReq.repoName, status: "failed", specFile: null, dsl: null, repoAbsPath, role: repoReq.role });
10966
10968
  }
10967
10969
  }
10968
- console.log(chalk19.bold.green("\n\u2714 Multi-repo pipeline complete!"));
10969
- console.log(chalk19.gray(` Workspace: ${workspace.name}`));
10970
- console.log(chalk19.gray(` Requirement: ${idea}`));
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}`));
10971
10973
  console.log();
10972
10974
  for (const r of results) {
10973
- const icon = r.status === "success" ? chalk19.green("\u2714") : r.status === "failed" ? chalk19.red("\u2718") : chalk19.gray("\u2212");
10974
- const specInfo = r.specFile ? chalk19.gray(` \u2192 ${r.specFile}`) : "";
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}`) : "";
10975
10977
  console.log(` ${icon} ${r.repoName} (${r.status})${specInfo}`);
10976
10978
  }
10977
10979
  return results;
@@ -10979,18 +10981,18 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10979
10981
  var workspaceCmd = program.command("workspace").description("Manage multi-repo workspace configuration");
10980
10982
  workspaceCmd.command("init").description(`Interactive workspace setup \u2014 creates ${WORKSPACE_CONFIG_FILE}`).action(async () => {
10981
10983
  const currentDir = process.cwd();
10982
- const configPath = path23.join(currentDir, WORKSPACE_CONFIG_FILE);
10983
- if (await fs24.pathExists(configPath)) {
10984
+ const configPath = path22.join(currentDir, WORKSPACE_CONFIG_FILE);
10985
+ if (await fs23.pathExists(configPath)) {
10984
10986
  const overwrite = await confirm2({
10985
10987
  message: `${WORKSPACE_CONFIG_FILE} already exists. Overwrite?`,
10986
10988
  default: false
10987
10989
  });
10988
10990
  if (!overwrite) {
10989
- console.log(chalk19.gray(" Cancelled."));
10991
+ console.log(chalk18.gray(" Cancelled."));
10990
10992
  return;
10991
10993
  }
10992
10994
  }
10993
- 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"));
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"));
10994
10996
  const workspaceName = await input({
10995
10997
  message: "Workspace name:",
10996
10998
  validate: (v2) => v2.trim().length > 0 || "Name cannot be empty"
@@ -11004,11 +11006,11 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11004
11006
  const workspaceLoader = new WorkspaceLoader(currentDir);
11005
11007
  const detected = await workspaceLoader.autoDetect();
11006
11008
  if (detected.length === 0) {
11007
- console.log(chalk19.yellow(" No recognizable repos found in sibling directories."));
11009
+ console.log(chalk18.yellow(" No recognizable repos found in sibling directories."));
11008
11010
  } else {
11009
- console.log(chalk19.cyan("\n Detected repos:"));
11011
+ console.log(chalk18.cyan("\n Detected repos:"));
11010
11012
  for (const r of detected) {
11011
- console.log(chalk19.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11013
+ console.log(chalk18.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11012
11014
  }
11013
11015
  const keepAll = await confirm2({
11014
11016
  message: `Include all ${detected.length} detected repo(s)?`,
@@ -11025,7 +11027,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11025
11027
  if (keep) repos.push(r);
11026
11028
  }
11027
11029
  }
11028
- console.log(chalk19.green(` \u2714 ${repos.length} repo(s) added from auto-scan.`));
11030
+ console.log(chalk18.green(` \u2714 ${repos.length} repo(s) added from auto-scan.`));
11029
11031
  }
11030
11032
  }
11031
11033
  const repoTypeChoices = [
@@ -11047,7 +11049,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11047
11049
  default: repos.length === 0
11048
11050
  });
11049
11051
  while (addMore) {
11050
- console.log(chalk19.cyan(`
11052
+ console.log(chalk18.cyan(`
11051
11053
  Adding repo #${repos.length + 1}`));
11052
11054
  const repoName = await input({
11053
11055
  message: "Repo name (e.g. api, web, app):",
@@ -11061,16 +11063,16 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11061
11063
  message: `Relative path to "${repoName}" from here (default: ./${repoName}):`,
11062
11064
  default: `./${repoName}`
11063
11065
  });
11064
- const absPath = path23.resolve(currentDir, repoPath);
11066
+ const absPath = path22.resolve(currentDir, repoPath);
11065
11067
  let detectedType = "unknown";
11066
11068
  let detectedRole = "shared";
11067
- if (await fs24.pathExists(absPath)) {
11069
+ if (await fs23.pathExists(absPath)) {
11068
11070
  const { type, role } = await detectRepoType(absPath);
11069
11071
  detectedType = type;
11070
11072
  detectedRole = role;
11071
- console.log(chalk19.gray(` Auto-detected: type=${type}, role=${role}`));
11073
+ console.log(chalk18.gray(` Auto-detected: type=${type}, role=${role}`));
11072
11074
  } else {
11073
- console.log(chalk19.yellow(` Path "${absPath}" not found \u2014 type/role will be manual.`));
11075
+ console.log(chalk18.yellow(` Path "${absPath}" not found \u2014 type/role will be manual.`));
11074
11076
  }
11075
11077
  const repoType = await select3({
11076
11078
  message: `Repo type for "${repoName}":`,
@@ -11093,53 +11095,53 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11093
11095
  type: repoType,
11094
11096
  role: repoRole
11095
11097
  });
11096
- console.log(chalk19.green(` \u2714 Added: ${repoName} (${repoRole}, ${repoType})`));
11098
+ console.log(chalk18.green(` \u2714 Added: ${repoName} (${repoRole}, ${repoType})`));
11097
11099
  addMore = await confirm2({
11098
11100
  message: "Add another repo?",
11099
11101
  default: false
11100
11102
  });
11101
11103
  }
11102
11104
  const workspaceConfig = { name: workspaceName, repos };
11103
- console.log(chalk19.cyan("\n Workspace summary:"));
11104
- console.log(chalk19.gray(` Name: ${workspaceName}`));
11105
+ console.log(chalk18.cyan("\n Workspace summary:"));
11106
+ console.log(chalk18.gray(` Name: ${workspaceName}`));
11105
11107
  for (const r of repos) {
11106
- console.log(chalk19.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11108
+ console.log(chalk18.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11107
11109
  }
11108
11110
  const ok = await confirm2({ message: `Save to ${WORKSPACE_CONFIG_FILE}?`, default: true });
11109
11111
  if (!ok) {
11110
- console.log(chalk19.gray(" Cancelled."));
11112
+ console.log(chalk18.gray(" Cancelled."));
11111
11113
  return;
11112
11114
  }
11113
11115
  const loader = new WorkspaceLoader(currentDir);
11114
11116
  const saved = await loader.save(workspaceConfig);
11115
- console.log(chalk19.green(`
11117
+ console.log(chalk18.green(`
11116
11118
  \u2714 Workspace saved: ${saved}`));
11117
- console.log(chalk19.gray(` Run \`ai-spec create "your feature"\` \u2014 workspace mode will activate automatically.`));
11119
+ console.log(chalk18.gray(` Run \`ai-spec create "your feature"\` \u2014 workspace mode will activate automatically.`));
11118
11120
  });
11119
11121
  workspaceCmd.command("status").description("Show current workspace configuration").action(async () => {
11120
11122
  const currentDir = process.cwd();
11121
11123
  const loader = new WorkspaceLoader(currentDir);
11122
11124
  const config2 = await loader.load();
11123
11125
  if (!config2) {
11124
- console.log(chalk19.yellow(`No ${WORKSPACE_CONFIG_FILE} found in ${currentDir}`));
11125
- console.log(chalk19.gray(" Run `ai-spec workspace init` to create one."));
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."));
11126
11128
  return;
11127
11129
  }
11128
- console.log(chalk19.bold(`
11130
+ console.log(chalk18.bold(`
11129
11131
  Workspace: ${config2.name}`));
11130
- console.log(chalk19.gray(` Config: ${path23.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11131
- console.log(chalk19.gray(` Repos (${config2.repos.length}):
11132
+ console.log(chalk18.gray(` Config: ${path22.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11133
+ console.log(chalk18.gray(` Repos (${config2.repos.length}):
11132
11134
  `));
11133
11135
  for (const repo of config2.repos) {
11134
11136
  const absPath = loader.resolveAbsPath(repo);
11135
- const exists = await fs24.pathExists(absPath);
11136
- const status = exists ? chalk19.green("found") : chalk19.red("not found");
11137
+ const exists = await fs23.pathExists(absPath);
11138
+ const status = exists ? chalk18.green("found") : chalk18.red("not found");
11137
11139
  console.log(
11138
- ` ${chalk19.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
11140
+ ` ${chalk18.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
11139
11141
  );
11140
- console.log(chalk19.gray(` path: ${absPath}`));
11142
+ console.log(chalk18.gray(` path: ${absPath}`));
11141
11143
  if (repo.constitution) {
11142
- console.log(chalk19.green(` constitution: found`));
11144
+ console.log(chalk18.green(` constitution: found`));
11143
11145
  }
11144
11146
  }
11145
11147
  });
@@ -11156,30 +11158,30 @@ program.command("update").description("Update an existing spec with a change req
11156
11158
  const modelName = opts.model || config2.model || DEFAULT_MODELS[providerName];
11157
11159
  const apiKey = await resolveApiKey(providerName, opts.key);
11158
11160
  const provider = createProvider(providerName, apiKey, modelName);
11159
- 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"));
11160
- console.log(chalk19.gray(` Provider: ${providerName}/${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}`));
11161
11163
  const updateRunId = generateRunId();
11162
11164
  const updateSnapshot = new RunSnapshot(currentDir, updateRunId);
11163
11165
  setActiveSnapshot(updateSnapshot);
11164
11166
  const updateLogger = new RunLogger(currentDir, updateRunId, { provider: providerName, model: modelName });
11165
11167
  setActiveLogger(updateLogger);
11166
- console.log(chalk19.gray(` Run ID: ${updateRunId}`));
11168
+ console.log(chalk18.gray(` Run ID: ${updateRunId}`));
11167
11169
  let specPath = opts.spec ?? null;
11168
11170
  if (!specPath) {
11169
- const specsDir = path23.join(currentDir, "specs");
11171
+ const specsDir = path22.join(currentDir, "specs");
11170
11172
  const latest = await SpecUpdater.findLatestSpec(specsDir);
11171
11173
  if (!latest) {
11172
- console.error(chalk19.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11174
+ console.error(chalk18.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11173
11175
  process.exit(1);
11174
11176
  }
11175
11177
  specPath = latest.filePath;
11176
- console.log(chalk19.gray(` Using spec: ${path23.relative(currentDir, specPath)} (v${latest.version})`));
11178
+ console.log(chalk18.gray(` Using spec: ${path22.relative(currentDir, specPath)} (v${latest.version})`));
11177
11179
  }
11178
- console.log(chalk19.gray(" Loading project context..."));
11180
+ console.log(chalk18.gray(" Loading project context..."));
11179
11181
  const loader = new ContextLoader(currentDir);
11180
11182
  const context = await loader.loadProjectContext();
11181
11183
  if (context.constitution && context.constitution.length > 6e3) {
11182
- console.log(chalk19.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
11184
+ console.log(chalk18.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
11183
11185
  }
11184
11186
  const { detectRepoType: _detectRepoType } = await Promise.resolve().then(() => (init_workspace_loader(), workspace_loader_exports));
11185
11187
  const { type: repoType } = await _detectRepoType(currentDir);
@@ -11191,19 +11193,19 @@ program.command("update").description("Update an existing spec with a change req
11191
11193
  repoType
11192
11194
  });
11193
11195
  } catch (err) {
11194
- console.error(chalk19.red(` Update failed: ${err.message}`));
11196
+ console.error(chalk18.red(` Update failed: ${err.message}`));
11195
11197
  process.exit(1);
11196
11198
  }
11197
- console.log(chalk19.green(`
11198
- \u2714 Spec updated \u2192 v${result.newVersion}: ${path23.relative(currentDir, result.newSpecPath)}`));
11199
+ console.log(chalk18.green(`
11200
+ \u2714 Spec updated \u2192 v${result.newVersion}: ${path22.relative(currentDir, result.newSpecPath)}`));
11199
11201
  if (result.newDslPath) {
11200
- console.log(chalk19.green(` \u2714 DSL updated: ${path23.relative(currentDir, result.newDslPath)}`));
11202
+ console.log(chalk18.green(` \u2714 DSL updated: ${path22.relative(currentDir, result.newDslPath)}`));
11201
11203
  }
11202
11204
  if (result.affectedFiles.length > 0) {
11203
- console.log(chalk19.cyan("\n Affected files:"));
11205
+ console.log(chalk18.cyan("\n Affected files:"));
11204
11206
  for (const f of result.affectedFiles) {
11205
- const icon = f.action === "create" ? chalk19.green("+") : chalk19.yellow("~");
11206
- console.log(` ${icon} ${f.file}: ${chalk19.gray(f.description)}`);
11207
+ const icon = f.action === "create" ? chalk18.green("+") : chalk18.yellow("~");
11208
+ console.log(` ${icon} ${f.file}: ${chalk18.gray(f.description)}`);
11207
11209
  }
11208
11210
  }
11209
11211
  if (opts.codegen && result.affectedFiles.length > 0) {
@@ -11211,9 +11213,9 @@ program.command("update").description("Update an existing spec with a change req
11211
11213
  const codegenModelName = opts.codegenModel || config2.codegenModel || DEFAULT_MODELS[codegenProviderName];
11212
11214
  const codegenApiKey = opts.codegenKey ?? (codegenProviderName === providerName ? apiKey : await resolveApiKey(codegenProviderName, opts.codegenKey));
11213
11215
  const codegenProvider = createProvider(codegenProviderName, codegenApiKey, codegenModelName);
11214
- console.log(chalk19.blue("\n Regenerating affected files..."));
11216
+ console.log(chalk18.blue("\n Regenerating affected files..."));
11215
11217
  const codeGenerator = new CodeGenerator(codegenProvider, "api");
11216
- const specContent = await fs24.readFile(result.newSpecPath, "utf-8");
11218
+ const specContent = await fs23.readFile(result.newSpecPath, "utf-8");
11217
11219
  const constitutionSection = context.constitution ? `
11218
11220
  === Project Constitution (MUST follow) ===
11219
11221
  ${context.constitution}
@@ -11224,10 +11226,10 @@ ${JSON.stringify(result.updatedDsl, null, 2).slice(0, 3e3)}
11224
11226
  ` : "";
11225
11227
  updateLogger.stageStart("update_codegen");
11226
11228
  for (const affected of result.affectedFiles) {
11227
- const fullPath = path23.join(currentDir, affected.file);
11229
+ const fullPath = path22.join(currentDir, affected.file);
11228
11230
  let existing = "";
11229
11231
  try {
11230
- existing = await fs24.readFile(fullPath, "utf-8");
11232
+ existing = await fs23.readFile(fullPath, "utf-8");
11231
11233
  } catch {
11232
11234
  }
11233
11235
  const codePrompt = `Apply this change to the file.
@@ -11241,23 +11243,23 @@ ${specContent}
11241
11243
  ${constitutionSection}${dslSection}
11242
11244
  === ${existing ? "Current File (return the FULL updated content)" : "New File"} ===
11243
11245
  ${existing || "Create from scratch."}`;
11244
- process.stdout.write(` ${existing ? chalk19.yellow("~") : chalk19.green("+")} ${affected.file}... `);
11246
+ process.stdout.write(` ${existing ? chalk18.yellow("~") : chalk18.green("+")} ${affected.file}... `);
11245
11247
  try {
11246
11248
  const { getCodeGenSystemPrompt: _getPrompt } = await Promise.resolve().then(() => (init_codegen_prompt(), codegen_prompt_exports));
11247
11249
  const raw = await codegenProvider.generate(codePrompt, _getPrompt(repoType));
11248
11250
  const content = raw.replace(/^```\w*\n?/gm, "").replace(/\n?```$/gm, "").trim();
11249
- await fs24.ensureDir(path23.dirname(fullPath));
11251
+ await fs23.ensureDir(path22.dirname(fullPath));
11250
11252
  await updateSnapshot.snapshotFile(fullPath);
11251
- await fs24.writeFile(fullPath, content, "utf-8");
11253
+ await fs23.writeFile(fullPath, content, "utf-8");
11252
11254
  updateLogger.fileWritten(affected.file);
11253
- console.log(chalk19.green("\u2714"));
11255
+ console.log(chalk18.green("\u2714"));
11254
11256
  } catch (err) {
11255
11257
  updateLogger.stageFail("update_codegen", `${affected.file}: ${err.message}`);
11256
- console.log(chalk19.red(`\u2718 ${err.message}`));
11258
+ console.log(chalk18.red(`\u2718 ${err.message}`));
11257
11259
  }
11258
11260
  }
11259
11261
  updateLogger.stageEnd("update_codegen", { filesUpdated: result.affectedFiles.length });
11260
- const updatedSpecContent = await fs24.readFile(result.newSpecPath, "utf-8").catch(() => "");
11262
+ const updatedSpecContent = await fs23.readFile(result.newSpecPath, "utf-8").catch(() => "");
11261
11263
  if (updatedSpecContent) {
11262
11264
  const updateReviewer = new CodeReviewer(provider, currentDir);
11263
11265
  const reviewResult = await updateReviewer.reviewCode(updatedSpecContent, result.newSpecPath).catch(() => "");
@@ -11269,13 +11271,13 @@ ${existing || "Create from scratch."}`;
11269
11271
  updateLogger.finish();
11270
11272
  updateLogger.printSummary();
11271
11273
  if (updateSnapshot.fileCount > 0) {
11272
- console.log(chalk19.gray(` To undo changes: ai-spec restore ${updateRunId}`));
11274
+ console.log(chalk18.gray(` To undo changes: ai-spec restore ${updateRunId}`));
11273
11275
  }
11274
11276
  if (!opts.codegen && result.affectedFiles.length > 0) {
11275
- console.log(chalk19.blue("\n Next steps:"));
11276
- console.log(chalk19.gray(` \u2022 Re-run with --codegen to regenerate affected files automatically`));
11277
- console.log(chalk19.gray(` \u2022 Or update files manually based on the affected files list above`));
11278
- console.log(chalk19.gray(` \u2022 Run \`ai-spec mock\` to refresh the mock server with the new DSL`));
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`));
11279
11281
  }
11280
11282
  });
11281
11283
  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) => {
@@ -11284,19 +11286,19 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11284
11286
  if (!dslPath) {
11285
11287
  dslPath = await findLatestDslFile(currentDir);
11286
11288
  if (!dslPath) {
11287
- console.error(chalk19.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11289
+ console.error(chalk18.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11288
11290
  process.exit(1);
11289
11291
  }
11290
- console.log(chalk19.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11292
+ console.log(chalk18.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11291
11293
  }
11292
11294
  let dsl;
11293
11295
  try {
11294
- dsl = await fs24.readJson(dslPath);
11296
+ dsl = await fs23.readJson(dslPath);
11295
11297
  } catch (err) {
11296
- console.error(chalk19.red(` Failed to read DSL: ${err.message}`));
11298
+ console.error(chalk18.red(` Failed to read DSL: ${err.message}`));
11297
11299
  process.exit(1);
11298
11300
  }
11299
- 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"));
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"));
11300
11302
  const format = opts.format === "json" ? "json" : "yaml";
11301
11303
  const serverUrl = opts.server || "http://localhost:3000";
11302
11304
  try {
@@ -11305,31 +11307,31 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11305
11307
  serverUrl,
11306
11308
  outputPath: opts.output
11307
11309
  });
11308
- const rel = path23.relative(currentDir, outputPath);
11309
- console.log(chalk19.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
11310
- console.log(chalk19.gray(` Feature : ${dsl.feature.title}`));
11311
- console.log(chalk19.gray(` Endpoints: ${dsl.endpoints.length}`));
11312
- console.log(chalk19.gray(` Models : ${dsl.models.length}`));
11313
- console.log(chalk19.gray(` Server : ${serverUrl}`));
11314
- console.log(chalk19.blue("\n Next steps:"));
11315
- console.log(chalk19.gray(` \u2022 Import ${rel} into Postman / Insomnia / Swagger UI`));
11316
- console.log(chalk19.gray(` \u2022 Use openapi-generator to generate client SDKs`));
11310
+ 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`));
11317
11319
  } catch (err) {
11318
- console.error(chalk19.red(` Export failed: ${err.message}`));
11320
+ console.error(chalk18.red(` Export failed: ${err.message}`));
11319
11321
  process.exit(1);
11320
11322
  }
11321
11323
  });
11322
11324
  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) => {
11323
11325
  const currentDir = process.cwd();
11324
11326
  const port = parseInt(opts.port, 10) || 3001;
11325
- 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"));
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"));
11326
11328
  if (opts.restore) {
11327
- const frontendDir = opts.frontend ? path23.resolve(opts.frontend) : currentDir;
11329
+ const frontendDir = opts.frontend ? path22.resolve(opts.frontend) : currentDir;
11328
11330
  const r = await restoreMockProxy(frontendDir);
11329
11331
  if (r.restored) {
11330
- console.log(chalk19.green(" \u2714 Proxy restored and mock server stopped."));
11332
+ console.log(chalk18.green(" \u2714 Proxy restored and mock server stopped."));
11331
11333
  } else {
11332
- console.log(chalk19.yellow(` ${r.note ?? "Nothing to restore."}`));
11334
+ console.log(chalk18.yellow(` ${r.note ?? "Nothing to restore."}`));
11333
11335
  }
11334
11336
  return;
11335
11337
  }
@@ -11337,32 +11339,32 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11337
11339
  const workspaceLoader = new WorkspaceLoader(currentDir);
11338
11340
  const workspaceConfig = await workspaceLoader.load();
11339
11341
  if (!workspaceConfig) {
11340
- console.error(chalk19.red(` No ${WORKSPACE_CONFIG_FILE} found. Run \`ai-spec workspace init\` first.`));
11342
+ console.error(chalk18.red(` No ${WORKSPACE_CONFIG_FILE} found. Run \`ai-spec workspace init\` first.`));
11341
11343
  process.exit(1);
11342
11344
  }
11343
11345
  const backendRepos = workspaceConfig.repos.filter((r) => r.role === "backend");
11344
11346
  if (backendRepos.length === 0) {
11345
- console.log(chalk19.yellow(" No backend repos found in workspace."));
11347
+ console.log(chalk18.yellow(" No backend repos found in workspace."));
11346
11348
  return;
11347
11349
  }
11348
11350
  for (const repo of backendRepos) {
11349
11351
  const repoAbsPath = workspaceLoader.resolveAbsPath(repo);
11350
- console.log(chalk19.cyan(`
11352
+ console.log(chalk18.cyan(`
11351
11353
  Repo: ${repo.name} (${repoAbsPath})`));
11352
11354
  const dslFile = await findLatestDslFile(repoAbsPath);
11353
11355
  if (!dslFile) {
11354
- console.log(chalk19.yellow(` No DSL file found \u2014 skipping.`));
11356
+ console.log(chalk18.yellow(` No DSL file found \u2014 skipping.`));
11355
11357
  continue;
11356
11358
  }
11357
- const dsl2 = await fs24.readJson(dslFile);
11359
+ const dsl2 = await fs23.readJson(dslFile);
11358
11360
  const result2 = await generateMockAssets(dsl2, repoAbsPath, {
11359
11361
  port,
11360
11362
  msw: opts.msw,
11361
11363
  proxy: opts.proxy
11362
11364
  });
11363
11365
  for (const f of result2.files) {
11364
- console.log(chalk19.green(` \u2714 ${f.path}`));
11365
- console.log(chalk19.gray(` ${f.description}`));
11366
+ console.log(chalk18.green(` \u2714 ${f.path}`));
11367
+ console.log(chalk18.gray(` ${f.description}`));
11366
11368
  }
11367
11369
  }
11368
11370
  return;
@@ -11372,19 +11374,19 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11372
11374
  dslPath = await findLatestDslFile(currentDir);
11373
11375
  if (!dslPath) {
11374
11376
  console.error(
11375
- chalk19.red(
11377
+ chalk18.red(
11376
11378
  " No .dsl.json file found in .ai-spec/. Run `ai-spec create` first or use --dsl <path>."
11377
11379
  )
11378
11380
  );
11379
11381
  process.exit(1);
11380
11382
  }
11381
- console.log(chalk19.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11383
+ console.log(chalk18.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11382
11384
  }
11383
11385
  let dsl;
11384
11386
  try {
11385
- dsl = await fs24.readJson(dslPath);
11387
+ dsl = await fs23.readJson(dslPath);
11386
11388
  } catch (err) {
11387
- console.error(chalk19.red(` Failed to read DSL file: ${err.message}`));
11389
+ console.error(chalk18.red(` Failed to read DSL file: ${err.message}`));
11388
11390
  process.exit(1);
11389
11391
  }
11390
11392
  const result = await generateMockAssets(dsl, currentDir, {
@@ -11392,58 +11394,58 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11392
11394
  msw: opts.msw,
11393
11395
  proxy: opts.proxy
11394
11396
  });
11395
- console.log(chalk19.green(`
11397
+ console.log(chalk18.green(`
11396
11398
  \u2714 Mock assets generated (${result.files.length} file(s)):`));
11397
11399
  for (const f of result.files) {
11398
- console.log(chalk19.green(` ${f.path}`));
11399
- console.log(chalk19.gray(` ${f.description}`));
11400
+ console.log(chalk18.green(` ${f.path}`));
11401
+ console.log(chalk18.gray(` ${f.description}`));
11400
11402
  }
11401
11403
  if (opts.serve) {
11402
- const serverJsPath = path23.join(currentDir, "mock", "server.js");
11403
- if (!await fs24.pathExists(serverJsPath)) {
11404
- console.error(chalk19.red(" mock/server.js not found \u2014 generation may have failed."));
11404
+ const serverJsPath = path22.join(currentDir, "mock", "server.js");
11405
+ if (!await fs23.pathExists(serverJsPath)) {
11406
+ console.error(chalk18.red(" mock/server.js not found \u2014 generation may have failed."));
11405
11407
  process.exit(1);
11406
11408
  }
11407
11409
  const pid = startMockServerBackground(serverJsPath, port);
11408
- console.log(chalk19.green(`
11410
+ console.log(chalk18.green(`
11409
11411
  \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${port}`));
11410
11412
  if (opts.frontend) {
11411
- const frontendDir = path23.resolve(opts.frontend);
11413
+ const frontendDir = path22.resolve(opts.frontend);
11412
11414
  const proxyResult = await applyMockProxy(frontendDir, port, dsl.endpoints);
11413
11415
  await saveMockServerPid(frontendDir, pid);
11414
11416
  if (proxyResult.applied) {
11415
- console.log(chalk19.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
11416
- console.log(chalk19.bold.cyan(`
11417
+ console.log(chalk18.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
11418
+ console.log(chalk18.bold.cyan(`
11417
11419
  Ready! Open a new terminal and run:`));
11418
- console.log(chalk19.white(` cd ${frontendDir}`));
11419
- console.log(chalk19.white(` ${proxyResult.devCommand}`));
11420
- console.log(chalk19.gray(`
11420
+ console.log(chalk18.white(` cd ${frontendDir}`));
11421
+ console.log(chalk18.white(` ${proxyResult.devCommand}`));
11422
+ console.log(chalk18.gray(`
11421
11423
  When done: ai-spec mock --restore --frontend ${frontendDir}`));
11422
11424
  } else {
11423
- console.log(chalk19.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
11424
- if (proxyResult.note) console.log(chalk19.gray(` ${proxyResult.note}`));
11425
+ console.log(chalk18.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
11426
+ if (proxyResult.note) console.log(chalk18.gray(` ${proxyResult.note}`));
11425
11427
  }
11426
11428
  } else {
11427
- console.log(chalk19.gray(` Tip: use --frontend <path> to also auto-patch your frontend proxy config.`));
11428
- console.log(chalk19.gray(` Mock server: http://localhost:${port}`));
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}`));
11429
11431
  }
11430
11432
  return;
11431
11433
  }
11432
- 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"));
11433
- console.log(chalk19.white(` 1. Install express (if not already):`));
11434
- console.log(chalk19.gray(` npm install --save-dev express`));
11435
- console.log(chalk19.white(` 2. Start mock server:`));
11436
- console.log(chalk19.gray(` node mock/server.js`));
11437
- console.log(chalk19.gray(` # or: ai-spec mock --serve --frontend <path-to-frontend>`));
11438
- console.log(chalk19.white(` 3. Configure your frontend to proxy API calls to:`));
11439
- console.log(chalk19.gray(` http://localhost:${port}`));
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}`));
11440
11442
  if (opts.proxy) {
11441
- console.log(chalk19.gray(` (See the generated proxy config file for framework-specific instructions)`));
11443
+ console.log(chalk18.gray(` (See the generated proxy config file for framework-specific instructions)`));
11442
11444
  }
11443
11445
  if (opts.msw) {
11444
- console.log(chalk19.white(` 4. MSW: import and start the worker in your app entry:`));
11445
- console.log(chalk19.gray(` import { worker } from './mocks/browser';`));
11446
- console.log(chalk19.gray(` if (process.env.NODE_ENV === 'development') worker.start();`));
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();`));
11447
11449
  }
11448
11450
  });
11449
11451
  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) => {
@@ -11459,34 +11461,26 @@ program.command("learn").description("Append a lesson or engineering decision di
11459
11461
  }
11460
11462
  const result = await appendDirectLesson(currentDir, lesson.trim());
11461
11463
  if (result.appended) {
11462
- console.log(chalk19.green(`
11464
+ console.log(chalk18.green(`
11463
11465
  \u2714 Lesson appended to constitution \xA79`));
11464
- console.log(chalk19.gray(` File: .ai-spec-constitution.md`));
11466
+ console.log(chalk18.gray(` File: .ai-spec-constitution.md`));
11465
11467
  } else {
11466
- console.log(chalk19.yellow(`
11468
+ console.log(chalk18.yellow(`
11467
11469
  \u26A0 Not appended: ${result.reason}`));
11468
11470
  }
11469
11471
  });
11470
11472
  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) => {
11471
11473
  const currentDir = process.cwd();
11472
11474
  const snapshot = new RunSnapshot(currentDir, runId);
11473
- console.log(chalk19.blue(`Restoring run: ${runId}...`));
11475
+ console.log(chalk18.blue(`Restoring run: ${runId}...`));
11474
11476
  const restored = await snapshot.restore();
11475
11477
  if (restored.length === 0) {
11476
- console.log(chalk19.yellow(" No backup found for this run ID."));
11478
+ console.log(chalk18.yellow(" No backup found for this run ID."));
11477
11479
  } else {
11478
- restored.forEach((f) => console.log(chalk19.green(` \u2714 restored: ${f}`)));
11479
- console.log(chalk19.bold.green(`
11480
+ restored.forEach((f) => console.log(chalk18.green(` \u2714 restored: ${f}`)));
11481
+ console.log(chalk18.bold.green(`
11480
11482
  \u2714 ${restored.length} file(s) restored.`));
11481
11483
  }
11482
11484
  });
11483
- if (process.argv.length <= 2) {
11484
- (async () => {
11485
- const currentDir = process.cwd();
11486
- const config2 = await loadConfig(currentDir);
11487
- await printWelcome(currentDir, config2);
11488
- })();
11489
- } else {
11490
- program.parse();
11491
- }
11485
+ program.parse();
11492
11486
  //# sourceMappingURL=index.mjs.map