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.
package/dist/cli/index.js CHANGED
@@ -423,8 +423,8 @@ var init_workspace_loader = __esm({
423
423
  const repos = [];
424
424
  for (const entry of entries) {
425
425
  const absPath = path17.join(this.workspaceRoot, entry);
426
- const stat4 = await fs18.stat(absPath).catch(() => null);
427
- if (!stat4 || !stat4.isDirectory()) continue;
426
+ const stat3 = await fs18.stat(absPath).catch(() => null);
427
+ if (!stat3 || !stat3.isDirectory()) continue;
428
428
  if (entry.startsWith(".") || entry === "node_modules") continue;
429
429
  if (names && !names.includes(entry)) continue;
430
430
  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"));
@@ -488,9 +488,9 @@ var init_workspace_loader = __esm({
488
488
 
489
489
  // cli/index.ts
490
490
  var import_commander = require("commander");
491
- var path23 = __toESM(require("path"));
492
- var fs24 = __toESM(require("fs-extra"));
493
- var import_chalk19 = __toESM(require("chalk"));
491
+ var path22 = __toESM(require("path"));
492
+ var fs23 = __toESM(require("fs-extra"));
493
+ var import_chalk18 = __toESM(require("chalk"));
494
494
  var dotenv = __toESM(require("dotenv"));
495
495
  var import_prompts3 = require("@inquirer/prompts");
496
496
 
@@ -4870,221 +4870,221 @@ function validateDsl(raw) {
4870
4870
  }
4871
4871
  return { valid: true, dsl: raw };
4872
4872
  }
4873
- function validateFeature(raw, path24, errors) {
4873
+ function validateFeature(raw, path23, errors) {
4874
4874
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4875
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4875
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4876
4876
  return;
4877
4877
  }
4878
4878
  const f = raw;
4879
- requireNonEmptyString(f["id"], `${path24}.id`, errors);
4880
- requireNonEmptyString(f["title"], `${path24}.title`, errors);
4881
- requireNonEmptyString(f["description"], `${path24}.description`, errors);
4879
+ requireNonEmptyString(f["id"], `${path23}.id`, errors);
4880
+ requireNonEmptyString(f["title"], `${path23}.title`, errors);
4881
+ requireNonEmptyString(f["description"], `${path23}.description`, errors);
4882
4882
  }
4883
- function validateModel(raw, path24, errors) {
4883
+ function validateModel(raw, path23, errors) {
4884
4884
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4885
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4885
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4886
4886
  return;
4887
4887
  }
4888
4888
  const m = raw;
4889
- requireNonEmptyString(m["name"], `${path24}.name`, errors);
4889
+ requireNonEmptyString(m["name"], `${path23}.name`, errors);
4890
4890
  if (!Array.isArray(m["fields"])) {
4891
- errors.push({ path: `${path24}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4891
+ errors.push({ path: `${path23}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4892
4892
  } else {
4893
4893
  const fields = m["fields"];
4894
4894
  if (fields.length > MAX_FIELDS_PER_MODEL) {
4895
- errors.push({ path: `${path24}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4895
+ errors.push({ path: `${path23}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4896
4896
  }
4897
4897
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
4898
- validateModelField(fields[j2], `${path24}.fields[${j2}]`, errors);
4898
+ validateModelField(fields[j2], `${path23}.fields[${j2}]`, errors);
4899
4899
  }
4900
4900
  }
4901
4901
  if (m["relations"] !== void 0) {
4902
4902
  if (!Array.isArray(m["relations"])) {
4903
- errors.push({ path: `${path24}.relations`, message: "Must be an array of strings if present" });
4903
+ errors.push({ path: `${path23}.relations`, message: "Must be an array of strings if present" });
4904
4904
  } else {
4905
4905
  const rels = m["relations"];
4906
4906
  for (let j2 = 0; j2 < rels.length; j2++) {
4907
4907
  if (typeof rels[j2] !== "string") {
4908
- errors.push({ path: `${path24}.relations[${j2}]`, message: "Must be a string" });
4908
+ errors.push({ path: `${path23}.relations[${j2}]`, message: "Must be a string" });
4909
4909
  }
4910
4910
  }
4911
4911
  }
4912
4912
  }
4913
4913
  }
4914
- function validateModelField(raw, path24, errors) {
4914
+ function validateModelField(raw, path23, errors) {
4915
4915
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4916
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4916
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4917
4917
  return;
4918
4918
  }
4919
4919
  const f = raw;
4920
- requireNonEmptyString(f["name"], `${path24}.name`, errors);
4921
- requireNonEmptyString(f["type"], `${path24}.type`, errors);
4920
+ requireNonEmptyString(f["name"], `${path23}.name`, errors);
4921
+ requireNonEmptyString(f["type"], `${path23}.type`, errors);
4922
4922
  if (typeof f["required"] !== "boolean") {
4923
- errors.push({ path: `${path24}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4923
+ errors.push({ path: `${path23}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4924
4924
  }
4925
4925
  }
4926
- function validateEndpoint(raw, path24, errors) {
4926
+ function validateEndpoint(raw, path23, errors) {
4927
4927
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4928
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4928
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4929
4929
  return;
4930
4930
  }
4931
4931
  const e = raw;
4932
- requireNonEmptyString(e["id"], `${path24}.id`, errors);
4933
- requireNonEmptyString(e["description"], `${path24}.description`, errors);
4932
+ requireNonEmptyString(e["id"], `${path23}.id`, errors);
4933
+ requireNonEmptyString(e["description"], `${path23}.description`, errors);
4934
4934
  if (!VALID_METHODS.includes(e["method"])) {
4935
4935
  errors.push({
4936
- path: `${path24}.method`,
4936
+ path: `${path23}.method`,
4937
4937
  message: `Must be one of ${VALID_METHODS.join("|")}, got: ${JSON.stringify(e["method"])}`
4938
4938
  });
4939
4939
  }
4940
4940
  if (typeof e["path"] !== "string" || !e["path"].startsWith("/")) {
4941
4941
  errors.push({
4942
- path: `${path24}.path`,
4942
+ path: `${path23}.path`,
4943
4943
  message: `Must be a string starting with "/", got: ${JSON.stringify(e["path"])}`
4944
4944
  });
4945
4945
  }
4946
4946
  if (typeof e["auth"] !== "boolean") {
4947
- errors.push({ path: `${path24}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4947
+ errors.push({ path: `${path23}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4948
4948
  }
4949
4949
  if (typeof e["successStatus"] !== "number" || e["successStatus"] < 100 || e["successStatus"] > 599) {
4950
4950
  errors.push({
4951
- path: `${path24}.successStatus`,
4951
+ path: `${path23}.successStatus`,
4952
4952
  message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["successStatus"])}`
4953
4953
  });
4954
4954
  }
4955
- requireNonEmptyString(e["successDescription"], `${path24}.successDescription`, errors);
4955
+ requireNonEmptyString(e["successDescription"], `${path23}.successDescription`, errors);
4956
4956
  if (e["request"] !== void 0) {
4957
- validateRequestSchema(e["request"], `${path24}.request`, errors);
4957
+ validateRequestSchema(e["request"], `${path23}.request`, errors);
4958
4958
  }
4959
4959
  if (e["errors"] !== void 0) {
4960
4960
  if (!Array.isArray(e["errors"])) {
4961
- errors.push({ path: `${path24}.errors`, message: "Must be an array if present" });
4961
+ errors.push({ path: `${path23}.errors`, message: "Must be an array if present" });
4962
4962
  } else {
4963
4963
  const errs = e["errors"];
4964
4964
  if (errs.length > MAX_ERRORS_PER_ENDPOINT) {
4965
- errors.push({ path: `${path24}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4965
+ errors.push({ path: `${path23}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4966
4966
  }
4967
4967
  for (let j2 = 0; j2 < Math.min(errs.length, MAX_ERRORS_PER_ENDPOINT); j2++) {
4968
- validateResponseError(errs[j2], `${path24}.errors[${j2}]`, errors);
4968
+ validateResponseError(errs[j2], `${path23}.errors[${j2}]`, errors);
4969
4969
  }
4970
4970
  }
4971
4971
  }
4972
4972
  }
4973
- function validateRequestSchema(raw, path24, errors) {
4973
+ function validateRequestSchema(raw, path23, errors) {
4974
4974
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4975
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4975
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4976
4976
  return;
4977
4977
  }
4978
4978
  const r = raw;
4979
4979
  for (const key of ["body", "query", "params"]) {
4980
4980
  if (r[key] !== void 0) {
4981
- validateFieldMap(r[key], `${path24}.${key}`, errors);
4981
+ validateFieldMap(r[key], `${path23}.${key}`, errors);
4982
4982
  }
4983
4983
  }
4984
4984
  }
4985
- function validateFieldMap(raw, path24, errors) {
4985
+ function validateFieldMap(raw, path23, errors) {
4986
4986
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4987
- errors.push({ path: path24, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4987
+ errors.push({ path: path23, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4988
4988
  return;
4989
4989
  }
4990
4990
  const map = raw;
4991
4991
  for (const [k2, v2] of Object.entries(map)) {
4992
4992
  if (typeof v2 !== "string") {
4993
- errors.push({ path: `${path24}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4993
+ errors.push({ path: `${path23}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4994
4994
  }
4995
4995
  }
4996
4996
  }
4997
- function validateResponseError(raw, path24, errors) {
4997
+ function validateResponseError(raw, path23, errors) {
4998
4998
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4999
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4999
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
5000
5000
  return;
5001
5001
  }
5002
5002
  const e = raw;
5003
5003
  if (typeof e["status"] !== "number" || e["status"] < 100 || e["status"] > 599) {
5004
- errors.push({ path: `${path24}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
5004
+ errors.push({ path: `${path23}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
5005
5005
  }
5006
- requireNonEmptyString(e["code"], `${path24}.code`, errors);
5007
- requireNonEmptyString(e["description"], `${path24}.description`, errors);
5006
+ requireNonEmptyString(e["code"], `${path23}.code`, errors);
5007
+ requireNonEmptyString(e["description"], `${path23}.description`, errors);
5008
5008
  }
5009
- function validateBehavior(raw, path24, errors) {
5009
+ function validateBehavior(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 b = raw;
5015
- requireNonEmptyString(b["id"], `${path24}.id`, errors);
5016
- requireNonEmptyString(b["description"], `${path24}.description`, errors);
5015
+ requireNonEmptyString(b["id"], `${path23}.id`, errors);
5016
+ requireNonEmptyString(b["description"], `${path23}.description`, errors);
5017
5017
  if (b["constraints"] !== void 0) {
5018
5018
  if (!Array.isArray(b["constraints"])) {
5019
- errors.push({ path: `${path24}.constraints`, message: "Must be an array of strings if present" });
5019
+ errors.push({ path: `${path23}.constraints`, message: "Must be an array of strings if present" });
5020
5020
  } else {
5021
5021
  const cs2 = b["constraints"];
5022
5022
  for (let j2 = 0; j2 < cs2.length; j2++) {
5023
5023
  if (typeof cs2[j2] !== "string") {
5024
- errors.push({ path: `${path24}.constraints[${j2}]`, message: "Must be a string" });
5024
+ errors.push({ path: `${path23}.constraints[${j2}]`, message: "Must be a string" });
5025
5025
  }
5026
5026
  }
5027
5027
  }
5028
5028
  }
5029
5029
  }
5030
- function validateComponent(raw, path24, errors) {
5030
+ function validateComponent(raw, path23, errors) {
5031
5031
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5032
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
5032
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
5033
5033
  return;
5034
5034
  }
5035
5035
  const c = raw;
5036
- requireNonEmptyString(c["id"], `${path24}.id`, errors);
5037
- requireNonEmptyString(c["name"], `${path24}.name`, errors);
5038
- requireNonEmptyString(c["description"], `${path24}.description`, errors);
5036
+ requireNonEmptyString(c["id"], `${path23}.id`, errors);
5037
+ requireNonEmptyString(c["name"], `${path23}.name`, errors);
5038
+ requireNonEmptyString(c["description"], `${path23}.description`, errors);
5039
5039
  if (c["props"] !== void 0) {
5040
5040
  if (!Array.isArray(c["props"])) {
5041
- errors.push({ path: `${path24}.props`, message: "Must be an array if present" });
5041
+ errors.push({ path: `${path23}.props`, message: "Must be an array if present" });
5042
5042
  } else {
5043
5043
  const props = c["props"];
5044
5044
  for (let j2 = 0; j2 < props.length; j2++) {
5045
5045
  const p = props[j2];
5046
5046
  if (typeof p !== "object" || p === null) {
5047
- errors.push({ path: `${path24}.props[${j2}]`, message: "Must be an object" });
5047
+ errors.push({ path: `${path23}.props[${j2}]`, message: "Must be an object" });
5048
5048
  continue;
5049
5049
  }
5050
- requireNonEmptyString(p["name"], `${path24}.props[${j2}].name`, errors);
5051
- requireNonEmptyString(p["type"], `${path24}.props[${j2}].type`, errors);
5050
+ requireNonEmptyString(p["name"], `${path23}.props[${j2}].name`, errors);
5051
+ requireNonEmptyString(p["type"], `${path23}.props[${j2}].type`, errors);
5052
5052
  if (typeof p["required"] !== "boolean") {
5053
- errors.push({ path: `${path24}.props[${j2}].required`, message: "Must be boolean" });
5053
+ errors.push({ path: `${path23}.props[${j2}].required`, message: "Must be boolean" });
5054
5054
  }
5055
5055
  }
5056
5056
  }
5057
5057
  }
5058
5058
  if (c["events"] !== void 0) {
5059
5059
  if (!Array.isArray(c["events"])) {
5060
- errors.push({ path: `${path24}.events`, message: "Must be an array if present" });
5060
+ errors.push({ path: `${path23}.events`, message: "Must be an array if present" });
5061
5061
  } else {
5062
5062
  const events = c["events"];
5063
5063
  for (let j2 = 0; j2 < events.length; j2++) {
5064
5064
  const e = events[j2];
5065
5065
  if (typeof e !== "object" || e === null) {
5066
- errors.push({ path: `${path24}.events[${j2}]`, message: "Must be an object" });
5066
+ errors.push({ path: `${path23}.events[${j2}]`, message: "Must be an object" });
5067
5067
  continue;
5068
5068
  }
5069
- requireNonEmptyString(e["name"], `${path24}.events[${j2}].name`, errors);
5069
+ requireNonEmptyString(e["name"], `${path23}.events[${j2}].name`, errors);
5070
5070
  }
5071
5071
  }
5072
5072
  }
5073
5073
  if (c["state"] !== void 0) {
5074
5074
  if (typeof c["state"] !== "object" || Array.isArray(c["state"]) || c["state"] === null) {
5075
- errors.push({ path: `${path24}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5075
+ errors.push({ path: `${path23}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5076
5076
  }
5077
5077
  }
5078
5078
  if (c["apiCalls"] !== void 0) {
5079
5079
  if (!Array.isArray(c["apiCalls"])) {
5080
- errors.push({ path: `${path24}.apiCalls`, message: "Must be an array of strings if present" });
5080
+ errors.push({ path: `${path23}.apiCalls`, message: "Must be an array of strings if present" });
5081
5081
  }
5082
5082
  }
5083
5083
  }
5084
- function requireNonEmptyString(v2, path24, errors) {
5084
+ function requireNonEmptyString(v2, path23, errors) {
5085
5085
  if (typeof v2 !== "string" || v2.trim().length === 0) {
5086
5086
  errors.push({
5087
- path: path24,
5087
+ path: path23,
5088
5088
  message: `Must be a non-empty string, got: ${typeLabel(v2)}`
5089
5089
  });
5090
5090
  }
@@ -5518,6 +5518,41 @@ async function loadDslForSpec(specFilePath) {
5518
5518
  // core/frontend-context-loader.ts
5519
5519
  var fs7 = __toESM(require("fs-extra"));
5520
5520
  var path6 = __toESM(require("path"));
5521
+ function parseImportStatements(content) {
5522
+ const stripped = content.replace(/\/\*[\s\S]*?\*\//g, (m) => "\n".repeat(m.split("\n").length - 1));
5523
+ const collapsed = stripped.replace(/import\s*\{[^}]*\}/gs, (m) => m.replace(/\n\s*/g, " "));
5524
+ const results = [];
5525
+ for (const rawLine of collapsed.split("\n")) {
5526
+ const line = rawLine.trim();
5527
+ if (!line) continue;
5528
+ if (/^import\s+type\b/.test(line)) continue;
5529
+ if (!line.startsWith("import")) continue;
5530
+ const match = line.match(/^(import\s+([\s\S]+?)\s+from\s+['"]([^'"]+)['"])/);
5531
+ if (!match) continue;
5532
+ results.push({
5533
+ line: match[1],
5534
+ modulePath: match[3],
5535
+ specifiers: match[2]
5536
+ });
5537
+ }
5538
+ return results;
5539
+ }
5540
+ var HTTP_MODULE_PATTERNS = [
5541
+ // Project path aliases (@/, @@/, ~/, #/) — catches '@/utils/request', '~/lib/http', etc.
5542
+ /^(?:@{1,2}|~|#)[/\\]/,
5543
+ // Well-known HTTP libraries (exact name match)
5544
+ /^(?:axios|ky(?:-universal)?|undici|node-fetch|cross-fetch|got|superagent|alova|openapi-fetch)$/,
5545
+ // Relative imports whose path contains an HTTP-utility keyword
5546
+ /\.{1,2}\/[^'"]*(?:http|request|fetch|client|api)[^'"]*/
5547
+ ];
5548
+ function findHttpClientImport(content) {
5549
+ for (const stmt of parseImportStatements(content)) {
5550
+ if (HTTP_MODULE_PATTERNS.some((p) => p.test(stmt.modulePath))) {
5551
+ return stmt.line;
5552
+ }
5553
+ }
5554
+ return void 0;
5555
+ }
5521
5556
  var STATE_MANAGEMENT_LIBS = [
5522
5557
  "zustand",
5523
5558
  "redux",
@@ -5706,13 +5741,12 @@ ${preview}`);
5706
5741
  } catch {
5707
5742
  }
5708
5743
  }
5709
- 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;
5710
5744
  for (const relPath of ctx.existingApiFiles.slice(0, 5)) {
5711
5745
  try {
5712
5746
  const content = await fs7.readFile(path6.join(projectRoot, relPath), "utf-8");
5713
- const match = content.match(httpImportRegex);
5714
- if (match) {
5715
- ctx.httpClientImport = match[0].trim();
5747
+ const found = findHttpClientImport(content);
5748
+ if (found) {
5749
+ ctx.httpClientImport = found;
5716
5750
  break;
5717
5751
  }
5718
5752
  } catch {
@@ -5842,13 +5876,28 @@ async function extractRouteModuleContext(projectRoot, ctx) {
5842
5876
  moduleFiles.push(...files);
5843
5877
  }
5844
5878
  if (moduleFiles.length === 0) return;
5845
- const layoutImportRegex = /^(?:const\s+Layout\s*=.*import\(['"][^'"]+['"]\)|import\s+Layout\s+from\s+['"][^'"]+['"])/m;
5879
+ const dynamicLayoutRegex = /const\s+Layout\s*=\s*(?:defineAsyncComponent\s*\(\s*)?(?:\(\s*\))?\s*(?:=>|function[^(]*\()\s*(?:[^)]*\))?\s*(?:=>)?\s*import\s*\(\s*['"]([^'"]+)['"]\s*\)/;
5846
5880
  for (const relPath of moduleFiles) {
5847
5881
  try {
5848
5882
  const content = await fs7.readFile(path6.join(projectRoot, relPath), "utf-8");
5849
- const match = content.match(layoutImportRegex);
5850
- if (match) {
5851
- ctx.layoutImport = match[0].trim();
5883
+ const stmts = parseImportStatements(content);
5884
+ const staticLayout = stmts.find(
5885
+ (s) => /\bLayout\b/.test(s.specifiers) && /layout/i.test(s.modulePath)
5886
+ );
5887
+ if (staticLayout) {
5888
+ ctx.layoutImport = staticLayout.line;
5889
+ const preview = content.split("\n").slice(0, 100).join("\n");
5890
+ ctx.routeModuleExample = { path: relPath, content: preview };
5891
+ break;
5892
+ }
5893
+ const singleLine = content.replace(
5894
+ /const\s+Layout\s*=[\s\S]*?import\s*\([^)]+\)/gm,
5895
+ (m) => m.replace(/\n\s*/g, " ")
5896
+ );
5897
+ const dynMatch = singleLine.match(dynamicLayoutRegex);
5898
+ if (dynMatch) {
5899
+ const constMatch = content.match(/^const\s+Layout\s*=.+/m);
5900
+ ctx.layoutImport = constMatch ? constMatch[0].trim() : dynMatch[0].trim();
5852
5901
  const preview = content.split("\n").slice(0, 100).join("\n");
5853
5902
  ctx.routeModuleExample = { path: relPath, content: preview };
5854
5903
  break;
@@ -7833,6 +7882,60 @@ function parseErrors(output, source) {
7833
7882
  }
7834
7883
  return errors;
7835
7884
  }
7885
+ function parseRelativeImports(content, fromFileRel) {
7886
+ const relDir = path15.dirname(fromFileRel);
7887
+ const results = [];
7888
+ const normalized = content.replace(/import\s*\{[^}]*\}/gs, (m) => m.replace(/\n\s*/g, " "));
7889
+ for (const line of normalized.split("\n")) {
7890
+ const trimmed = line.trim();
7891
+ if (/^import\s+type\b/.test(trimmed)) continue;
7892
+ const match = trimmed.match(/^import\b[^'"]*from\s+['"](\.\.?\/[^'"]+)['"]/);
7893
+ if (!match) continue;
7894
+ const resolved = path15.normalize(path15.join(relDir, match[1]));
7895
+ results.push(resolved);
7896
+ }
7897
+ return results;
7898
+ }
7899
+ async function buildRepairOrder(errorsByFile, workingDir) {
7900
+ const files = Array.from(errorsByFile.keys());
7901
+ if (files.length <= 1) return Array.from(errorsByFile.entries());
7902
+ const deps = new Map(files.map((f) => [f, []]));
7903
+ for (const file of files) {
7904
+ try {
7905
+ const content = await fs16.readFile(path15.join(workingDir, file), "utf-8");
7906
+ const importedPaths = parseRelativeImports(content, file);
7907
+ for (const importedPath of importedPaths) {
7908
+ const matched = files.find((f) => {
7909
+ if (f === file) return false;
7910
+ const fNoExt = f.replace(/\.[^.]+$/, "");
7911
+ return importedPath === fNoExt || importedPath === f || `${importedPath}.ts` === f || `${importedPath}.tsx` === f || `${importedPath}.js` === f || `${importedPath}.jsx` === f;
7912
+ });
7913
+ if (matched) deps.get(file).push(matched);
7914
+ }
7915
+ } catch {
7916
+ }
7917
+ }
7918
+ const dependents = new Map(files.map((f) => [f, []]));
7919
+ for (const [file, fileDeps] of deps) {
7920
+ for (const dep of fileDeps) dependents.get(dep).push(file);
7921
+ }
7922
+ const inDegree = new Map(files.map((f) => [f, deps.get(f).length]));
7923
+ const queue = files.filter((f) => inDegree.get(f) === 0);
7924
+ const sorted = [];
7925
+ while (queue.length > 0) {
7926
+ const file = queue.shift();
7927
+ sorted.push(file);
7928
+ for (const dependent of dependents.get(file) ?? []) {
7929
+ const degree = (inDegree.get(dependent) ?? 1) - 1;
7930
+ inDegree.set(dependent, degree);
7931
+ if (degree === 0) queue.push(dependent);
7932
+ }
7933
+ }
7934
+ for (const f of files) {
7935
+ if (!sorted.includes(f)) sorted.push(f);
7936
+ }
7937
+ return sorted.map((f) => [f, errorsByFile.get(f)]);
7938
+ }
7836
7939
  async function attemptFix(provider, errors, workingDir, dsl) {
7837
7940
  const results = [];
7838
7941
  const errorsByFile = /* @__PURE__ */ new Map();
@@ -7841,7 +7944,8 @@ async function attemptFix(provider, errors, workingDir, dsl) {
7841
7944
  if (!errorsByFile.has(file)) errorsByFile.set(file, []);
7842
7945
  errorsByFile.get(file).push(err);
7843
7946
  }
7844
- for (const [file, fileErrors] of errorsByFile) {
7947
+ const sortedEntries = await buildRepairOrder(errorsByFile, workingDir);
7948
+ for (const [file, fileErrors] of sortedEntries) {
7845
7949
  const fullPath = path15.join(workingDir, file);
7846
7950
  let existingContent = "";
7847
7951
  try {
@@ -8235,108 +8339,6 @@ async function clearKey(provider) {
8235
8339
  await writeStore(store);
8236
8340
  }
8237
8341
 
8238
- // cli/welcome.ts
8239
- var import_chalk17 = __toESM(require("chalk"));
8240
- var os4 = __toESM(require("os"));
8241
- var path19 = __toESM(require("path"));
8242
- var fs20 = __toESM(require("fs-extra"));
8243
- var VERSION = "0.14.1";
8244
- var TOTAL_W = 76;
8245
- var L_WIDTH = 44;
8246
- var R_WIDTH = TOTAL_W - L_WIDTH - 4;
8247
- var ROBOT_COLOR = import_chalk17.default.hex("#E8885A");
8248
- var ROBOT = [
8249
- ROBOT_COLOR(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),
8250
- ROBOT_COLOR(" \u2502") + import_chalk17.default.bold.white(" \u25C9 ") + ROBOT_COLOR(" ") + import_chalk17.default.bold.white("\u25C9 ") + ROBOT_COLOR("\u2502"),
8251
- ROBOT_COLOR(" \u2502") + import_chalk17.default.dim(" \u2570\u2500\u256F ") + ROBOT_COLOR("\u2502"),
8252
- ROBOT_COLOR(" \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2518"),
8253
- ROBOT_COLOR(" \u2502"),
8254
- ROBOT_COLOR(" \u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500")
8255
- ];
8256
- function visLen(s) {
8257
- return s.replace(/\x1b\[[0-9;]*m/g, "").length;
8258
- }
8259
- function padR(s, width) {
8260
- const vl = visLen(s);
8261
- return vl >= width ? s : s + " ".repeat(width - vl);
8262
- }
8263
- function row(left, right) {
8264
- return padR(left, L_WIDTH) + " " + import_chalk17.default.gray("\u2502") + " " + padR(right, R_WIDTH);
8265
- }
8266
- function center(s, width) {
8267
- const vl = visLen(s);
8268
- const pad = Math.max(0, Math.floor((width - vl) / 2));
8269
- return " ".repeat(pad) + s;
8270
- }
8271
- async function getRecentSpecs(dir) {
8272
- const specsDir = path19.join(dir, "specs");
8273
- if (!await fs20.pathExists(specsDir)) return [];
8274
- try {
8275
- const files = await fs20.readdir(specsDir);
8276
- const mdFiles = files.filter((f) => f.endsWith(".md"));
8277
- const withStats = await Promise.all(
8278
- mdFiles.map(async (f) => {
8279
- const stat4 = await fs20.stat(path19.join(specsDir, f));
8280
- return { name: f, mtime: stat4.mtime.getTime() };
8281
- })
8282
- );
8283
- withStats.sort((a, b) => b.mtime - a.mtime);
8284
- return withStats.slice(0, 3).map(({ name, mtime }) => {
8285
- const ms2 = Date.now() - mtime;
8286
- const hours = Math.floor(ms2 / 36e5);
8287
- const days = Math.floor(hours / 24);
8288
- const age = days > 0 ? `${days}d ago` : hours > 0 ? `${hours}h ago` : "just now";
8289
- const slug = name.replace(/\.md$/, "").slice(0, R_WIDTH - age.length - 2);
8290
- return import_chalk17.default.white(slug) + import_chalk17.default.dim(" " + age);
8291
- });
8292
- } catch {
8293
- return [];
8294
- }
8295
- }
8296
- async function printWelcome(currentDir, config2) {
8297
- const username = os4.userInfo().username;
8298
- const homeDir = os4.homedir();
8299
- const shortDir = currentDir.startsWith(homeDir) ? "~" + currentDir.slice(homeDir.length) : currentDir;
8300
- const recentSpecs = await getRecentSpecs(currentDir);
8301
- const providerBit = config2?.provider ? config2.provider + (config2.model ? " \xB7 " + config2.model : "") : "";
8302
- const bottomRaw = [providerBit, shortDir].filter(Boolean).join(" \xB7 ");
8303
- const maxInfoLen = L_WIDTH - 2;
8304
- const bottomTruncated = bottomRaw.length > maxInfoLen ? bottomRaw.slice(0, maxInfoLen - 1) + "\u2026" : bottomRaw;
8305
- const bottomLine = " " + import_chalk17.default.dim(bottomTruncated);
8306
- const titleInner = `ai-spec v${VERSION} `;
8307
- const titleDashes = "\u2500".repeat(Math.max(0, TOTAL_W - titleInner.length - 4));
8308
- console.log(
8309
- "\n" + import_chalk17.default.hex("#FF6B35")("\u2500\u2500\u2500 " + titleInner + titleDashes)
8310
- );
8311
- const welcomeText = "Welcome back, " + import_chalk17.default.bold.white(username) + "!";
8312
- const leftLines = [
8313
- "",
8314
- center(welcomeText, L_WIDTH),
8315
- "",
8316
- ...ROBOT,
8317
- "",
8318
- bottomLine,
8319
- ""
8320
- ];
8321
- const rightLines = [
8322
- import_chalk17.default.hex("#FF8C00").bold("Tips for getting started"),
8323
- import_chalk17.default.gray("\u2500".repeat(R_WIDTH)),
8324
- import_chalk17.default.white('ai-spec create "feature"'),
8325
- import_chalk17.default.gray("ai-spec workspace run"),
8326
- import_chalk17.default.gray('ai-spec update "change"'),
8327
- "",
8328
- import_chalk17.default.hex("#FF8C00").bold("Recent activity"),
8329
- import_chalk17.default.gray("\u2500".repeat(R_WIDTH)),
8330
- ...recentSpecs.length > 0 ? recentSpecs : [import_chalk17.default.dim("No recent activity")]
8331
- ];
8332
- const maxLines = Math.max(leftLines.length, rightLines.length);
8333
- for (let i = 0; i < maxLines; i++) {
8334
- console.log(row(leftLines[i] ?? "", rightLines[i] ?? ""));
8335
- }
8336
- console.log(import_chalk17.default.gray("\u2500".repeat(TOTAL_W)));
8337
- console.log();
8338
- }
8339
-
8340
8342
  // prompts/global-constitution.prompt.ts
8341
8343
  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.
8342
8344
 
@@ -8846,8 +8848,8 @@ Existing API/service files:`);
8846
8848
  }
8847
8849
 
8848
8850
  // core/mock-server-generator.ts
8849
- var path20 = __toESM(require("path"));
8850
- var fs21 = __toESM(require("fs-extra"));
8851
+ var path19 = __toESM(require("path"));
8852
+ var fs20 = __toESM(require("fs-extra"));
8851
8853
  var import_child_process5 = require("child_process");
8852
8854
  function typeToFixture(fieldName, typeDesc) {
8853
8855
  const t = typeDesc.toLowerCase();
@@ -8983,22 +8985,22 @@ function generateMockServerJs(dsl, port) {
8983
8985
  }
8984
8986
  function detectFrontendFramework(projectDir) {
8985
8987
  for (const f of ["vite.config.ts", "vite.config.js", "vite.config.mts"]) {
8986
- if (fs21.existsSync(path20.join(projectDir, f))) return "vite";
8988
+ if (fs20.existsSync(path19.join(projectDir, f))) return "vite";
8987
8989
  }
8988
8990
  for (const f of ["next.config.js", "next.config.ts", "next.config.mjs"]) {
8989
- if (fs21.existsSync(path20.join(projectDir, f))) return "next";
8991
+ if (fs20.existsSync(path19.join(projectDir, f))) return "next";
8990
8992
  }
8991
- const pkgPath = path20.join(projectDir, "package.json");
8992
- if (fs21.existsSync(pkgPath)) {
8993
+ const pkgPath = path19.join(projectDir, "package.json");
8994
+ if (fs20.existsSync(pkgPath)) {
8993
8995
  try {
8994
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
8996
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
8995
8997
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8996
8998
  if (deps["react-scripts"]) return "cra";
8997
8999
  } catch {
8998
9000
  }
8999
9001
  }
9000
9002
  for (const f of ["webpack.config.js", "webpack.config.ts"]) {
9001
- if (fs21.existsSync(path20.join(projectDir, f))) return "webpack";
9003
+ if (fs20.existsSync(path19.join(projectDir, f))) return "webpack";
9002
9004
  }
9003
9005
  return "unknown";
9004
9006
  }
@@ -9178,7 +9180,7 @@ export const worker = setupWorker(...handlers);
9178
9180
  var MOCK_LOCK_FILE = ".ai-spec-mock.lock.json";
9179
9181
  function findViteConfigFile(projectDir) {
9180
9182
  for (const f of ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]) {
9181
- if (fs21.existsSync(path20.join(projectDir, f))) return f;
9183
+ if (fs20.existsSync(path19.join(projectDir, f))) return f;
9182
9184
  }
9183
9185
  return null;
9184
9186
  }
@@ -9224,73 +9226,73 @@ async function applyMockProxy(frontendDir, mockPort, endpoints = []) {
9224
9226
  if (framework === "vite") {
9225
9227
  const viteConfigFile = findViteConfigFile(frontendDir) ?? "vite.config.ts";
9226
9228
  const mockConfigContent = generateViteMockConfigTs(viteConfigFile, mockPort, endpoints);
9227
- const mockConfigPath = path20.join(frontendDir, "vite.config.ai-spec-mock.ts");
9228
- await fs21.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9229
+ const mockConfigPath = path19.join(frontendDir, "vite.config.ai-spec-mock.ts");
9230
+ await fs20.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9229
9231
  actions.push({ type: "wrote-file", filePath: "vite.config.ai-spec-mock.ts" });
9230
- const pkgPath = path20.join(frontendDir, "package.json");
9231
- if (await fs21.pathExists(pkgPath)) {
9232
- const pkg = await fs21.readJson(pkgPath);
9232
+ const pkgPath = path19.join(frontendDir, "package.json");
9233
+ if (await fs20.pathExists(pkgPath)) {
9234
+ const pkg = await fs20.readJson(pkgPath);
9233
9235
  pkg.scripts = pkg.scripts ?? {};
9234
9236
  const originalValue = pkg.scripts["dev:mock"] ?? null;
9235
9237
  pkg.scripts["dev:mock"] = "vite --config vite.config.ai-spec-mock.ts";
9236
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9238
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9237
9239
  actions.push({ type: "added-pkg-script", key: "dev:mock", originalValue });
9238
9240
  }
9239
9241
  const lock2 = { framework, mockPort, frontendDir, actions };
9240
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9242
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9241
9243
  return { framework, applied: true, devCommand: "npm run dev:mock" };
9242
9244
  }
9243
9245
  if (framework === "cra") {
9244
- const pkgPath = path20.join(frontendDir, "package.json");
9245
- if (await fs21.pathExists(pkgPath)) {
9246
- const pkg = await fs21.readJson(pkgPath);
9246
+ const pkgPath = path19.join(frontendDir, "package.json");
9247
+ if (await fs20.pathExists(pkgPath)) {
9248
+ const pkg = await fs20.readJson(pkgPath);
9247
9249
  const originalProxy = pkg.proxy ?? null;
9248
9250
  pkg.proxy = `http://localhost:${mockPort}`;
9249
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9251
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9250
9252
  actions.push({ type: "patched-pkg-proxy", originalProxy });
9251
9253
  const lock2 = { framework, mockPort, frontendDir, actions };
9252
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9254
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9253
9255
  return { framework, applied: true, devCommand: "npm start" };
9254
9256
  }
9255
9257
  return { framework, applied: false, devCommand: null, note: "No package.json found." };
9256
9258
  }
9257
9259
  const lock = { framework, mockPort, frontendDir, actions };
9258
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9260
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9259
9261
  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}`;
9260
9262
  return { framework, applied: false, devCommand: null, note: manualNote };
9261
9263
  }
9262
9264
  async function restoreMockProxy(frontendDir) {
9263
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9264
- if (!await fs21.pathExists(lockPath)) {
9265
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9266
+ if (!await fs20.pathExists(lockPath)) {
9265
9267
  return { restored: false, note: "No lock file found \u2014 nothing to restore." };
9266
9268
  }
9267
- const lock = await fs21.readJson(lockPath);
9269
+ const lock = await fs20.readJson(lockPath);
9268
9270
  for (const action of lock.actions) {
9269
9271
  if (action.type === "wrote-file") {
9270
- const fp = path20.join(frontendDir, action.filePath);
9271
- if (await fs21.pathExists(fp)) await fs21.remove(fp);
9272
+ const fp = path19.join(frontendDir, action.filePath);
9273
+ if (await fs20.pathExists(fp)) await fs20.remove(fp);
9272
9274
  } else if (action.type === "added-pkg-script") {
9273
- const pkgPath = path20.join(frontendDir, "package.json");
9274
- if (await fs21.pathExists(pkgPath)) {
9275
- const pkg = await fs21.readJson(pkgPath);
9275
+ const pkgPath = path19.join(frontendDir, "package.json");
9276
+ if (await fs20.pathExists(pkgPath)) {
9277
+ const pkg = await fs20.readJson(pkgPath);
9276
9278
  if (action.originalValue == null) {
9277
9279
  delete pkg.scripts?.[action.key];
9278
9280
  } else {
9279
9281
  pkg.scripts = pkg.scripts ?? {};
9280
9282
  pkg.scripts[action.key] = action.originalValue;
9281
9283
  }
9282
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9284
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9283
9285
  }
9284
9286
  } else if (action.type === "patched-pkg-proxy") {
9285
- const pkgPath = path20.join(frontendDir, "package.json");
9286
- if (await fs21.pathExists(pkgPath)) {
9287
- const pkg = await fs21.readJson(pkgPath);
9287
+ const pkgPath = path19.join(frontendDir, "package.json");
9288
+ if (await fs20.pathExists(pkgPath)) {
9289
+ const pkg = await fs20.readJson(pkgPath);
9288
9290
  if (action.originalProxy == null) {
9289
9291
  delete pkg.proxy;
9290
9292
  } else {
9291
9293
  pkg.proxy = action.originalProxy;
9292
9294
  }
9293
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9295
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9294
9296
  }
9295
9297
  }
9296
9298
  }
@@ -9300,7 +9302,7 @@ async function restoreMockProxy(frontendDir) {
9300
9302
  } catch {
9301
9303
  }
9302
9304
  }
9303
- await fs21.remove(lockPath);
9305
+ await fs20.remove(lockPath);
9304
9306
  return { restored: true };
9305
9307
  }
9306
9308
  function startMockServerBackground(serverJsPath, port) {
@@ -9313,23 +9315,23 @@ function startMockServerBackground(serverJsPath, port) {
9313
9315
  return child.pid;
9314
9316
  }
9315
9317
  async function saveMockServerPid(frontendDir, pid) {
9316
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9317
- if (await fs21.pathExists(lockPath)) {
9318
- const lock = await fs21.readJson(lockPath);
9318
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9319
+ if (await fs20.pathExists(lockPath)) {
9320
+ const lock = await fs20.readJson(lockPath);
9319
9321
  lock.mockServerPid = pid;
9320
- await fs21.writeJson(lockPath, lock, { spaces: 2 });
9322
+ await fs20.writeJson(lockPath, lock, { spaces: 2 });
9321
9323
  }
9322
9324
  }
9323
9325
  async function generateMockAssets(dsl, projectDir, opts = {}) {
9324
9326
  const port = opts.port ?? 3001;
9325
- const outputDir = path20.join(projectDir, opts.outputDir ?? "mock");
9327
+ const outputDir = path19.join(projectDir, opts.outputDir ?? "mock");
9326
9328
  const result = { files: [] };
9327
- await fs21.ensureDir(outputDir);
9329
+ await fs20.ensureDir(outputDir);
9328
9330
  const serverJs = generateMockServerJs(dsl, port);
9329
- const serverPath = path20.join(outputDir, "server.js");
9330
- await fs21.writeFile(serverPath, serverJs, "utf-8");
9331
+ const serverPath = path19.join(outputDir, "server.js");
9332
+ await fs20.writeFile(serverPath, serverJs, "utf-8");
9331
9333
  result.files.push({
9332
- path: path20.relative(projectDir, serverPath),
9334
+ path: path19.relative(projectDir, serverPath),
9333
9335
  description: `Express mock server \u2014 run with: node mock/server.js`
9334
9336
  });
9335
9337
  const mockReadme = `# Mock Server
@@ -9359,52 +9361,52 @@ ${dsl.endpoints.map(
9359
9361
 
9360
9362
  Append \`?simulate_error=1\` to any request to test error handling (not yet auto-wired \u2014 edit server.js manually).
9361
9363
  `;
9362
- const readmePath = path20.join(outputDir, "README.md");
9363
- await fs21.writeFile(readmePath, mockReadme, "utf-8");
9364
+ const readmePath = path19.join(outputDir, "README.md");
9365
+ await fs20.writeFile(readmePath, mockReadme, "utf-8");
9364
9366
  result.files.push({
9365
- path: path20.relative(projectDir, readmePath),
9367
+ path: path19.relative(projectDir, readmePath),
9366
9368
  description: "Mock server usage guide"
9367
9369
  });
9368
9370
  if (opts.proxy) {
9369
9371
  const { content, filename } = generateProxyConfig(dsl, port, projectDir);
9370
- const proxyPath = path20.join(projectDir, filename);
9371
- await fs21.ensureDir(path20.dirname(proxyPath));
9372
- await fs21.writeFile(proxyPath, content, "utf-8");
9372
+ const proxyPath = path19.join(projectDir, filename);
9373
+ await fs20.ensureDir(path19.dirname(proxyPath));
9374
+ await fs20.writeFile(proxyPath, content, "utf-8");
9373
9375
  result.files.push({
9374
9376
  path: filename,
9375
9377
  description: "Proxy config snippet \u2014 copy instructions into your framework config"
9376
9378
  });
9377
9379
  }
9378
9380
  if (opts.msw) {
9379
- const mswDir = path20.join(projectDir, "src", "mocks");
9380
- await fs21.ensureDir(mswDir);
9381
+ const mswDir = path19.join(projectDir, "src", "mocks");
9382
+ await fs20.ensureDir(mswDir);
9381
9383
  const handlersContent = generateMswHandlers(dsl);
9382
- const handlersPath = path20.join(mswDir, "handlers.ts");
9383
- await fs21.writeFile(handlersPath, handlersContent, "utf-8");
9384
+ const handlersPath = path19.join(mswDir, "handlers.ts");
9385
+ await fs20.writeFile(handlersPath, handlersContent, "utf-8");
9384
9386
  result.files.push({
9385
- path: path20.relative(projectDir, handlersPath),
9387
+ path: path19.relative(projectDir, handlersPath),
9386
9388
  description: "MSW request handlers"
9387
9389
  });
9388
9390
  const browserContent = generateMswBrowser();
9389
- const browserPath = path20.join(mswDir, "browser.ts");
9390
- await fs21.writeFile(browserPath, browserContent, "utf-8");
9391
+ const browserPath = path19.join(mswDir, "browser.ts");
9392
+ await fs20.writeFile(browserPath, browserContent, "utf-8");
9391
9393
  result.files.push({
9392
- path: path20.relative(projectDir, browserPath),
9394
+ path: path19.relative(projectDir, browserPath),
9393
9395
  description: "MSW browser worker setup"
9394
9396
  });
9395
9397
  }
9396
9398
  return result;
9397
9399
  }
9398
9400
  async function findLatestDslFile(projectDir) {
9399
- const specDir = path20.join(projectDir, ".ai-spec");
9400
- if (!await fs21.pathExists(specDir)) return null;
9401
+ const specDir = path19.join(projectDir, ".ai-spec");
9402
+ if (!await fs20.pathExists(specDir)) return null;
9401
9403
  const allFiles = [];
9402
9404
  async function scan(dir) {
9403
- const entries = await fs21.readdir(dir);
9405
+ const entries = await fs20.readdir(dir);
9404
9406
  for (const entry of entries) {
9405
- const abs = path20.join(dir, entry);
9406
- const stat4 = await fs21.stat(abs);
9407
- if (stat4.isDirectory()) {
9407
+ const abs = path19.join(dir, entry);
9408
+ const stat3 = await fs20.stat(abs);
9409
+ if (stat3.isDirectory()) {
9408
9410
  await scan(abs);
9409
9411
  } else if (entry.endsWith(".dsl.json")) {
9410
9412
  allFiles.push(abs);
@@ -9414,16 +9416,16 @@ async function findLatestDslFile(projectDir) {
9414
9416
  await scan(specDir);
9415
9417
  if (allFiles.length === 0) return null;
9416
9418
  const withMtimes = await Promise.all(
9417
- allFiles.map(async (f) => ({ f, mtime: (await fs21.stat(f)).mtime }))
9419
+ allFiles.map(async (f) => ({ f, mtime: (await fs20.stat(f)).mtime }))
9418
9420
  );
9419
9421
  withMtimes.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
9420
9422
  return withMtimes[0].f;
9421
9423
  }
9422
9424
 
9423
9425
  // core/spec-updater.ts
9424
- var import_chalk18 = __toESM(require("chalk"));
9425
- var path21 = __toESM(require("path"));
9426
- var fs22 = __toESM(require("fs-extra"));
9426
+ var import_chalk17 = __toESM(require("chalk"));
9427
+ var path20 = __toESM(require("path"));
9428
+ var fs21 = __toESM(require("fs-extra"));
9427
9429
 
9428
9430
  // prompts/update.prompt.ts
9429
9431
  var specUpdateSystemPrompt = `You are a Senior Software Architect updating an existing Feature Spec based on a change request.
@@ -9569,8 +9571,8 @@ var SpecUpdater = class {
9569
9571
  * Returns all .md spec files sorted newest-first.
9570
9572
  */
9571
9573
  static async findLatestSpec(specsDir) {
9572
- if (!await fs22.pathExists(specsDir)) return null;
9573
- const files = await fs22.readdir(specsDir);
9574
+ if (!await fs21.pathExists(specsDir)) return null;
9575
+ const files = await fs21.readdir(specsDir);
9574
9576
  const pattern = /^feature-(.+)-v(\d+)\.md$/;
9575
9577
  let latest = null;
9576
9578
  for (const file of files) {
@@ -9578,8 +9580,8 @@ var SpecUpdater = class {
9578
9580
  if (!m) continue;
9579
9581
  const version = parseInt(m[2], 10);
9580
9582
  if (!latest || version > latest.version) {
9581
- const filePath = path21.join(specsDir, file);
9582
- const content = await fs22.readFile(filePath, "utf-8");
9583
+ const filePath = path20.join(specsDir, file);
9584
+ const content = await fs21.readFile(filePath, "utf-8");
9583
9585
  latest = { filePath, version, slug: m[1], content };
9584
9586
  }
9585
9587
  }
@@ -9590,16 +9592,16 @@ var SpecUpdater = class {
9590
9592
  * Generates a new version of the spec, re-extracts the DSL, and identifies affected files.
9591
9593
  */
9592
9594
  async update(changeRequest, existingSpecPath, projectDir, context, opts = {}) {
9593
- const existingSpec = await fs22.readFile(existingSpecPath, "utf-8");
9595
+ const existingSpec = await fs21.readFile(existingSpecPath, "utf-8");
9594
9596
  let existingDsl = null;
9595
9597
  const dslFile = await findLatestDslFile(projectDir);
9596
9598
  if (dslFile) {
9597
9599
  try {
9598
- existingDsl = await fs22.readJson(dslFile);
9600
+ existingDsl = await fs21.readJson(dslFile);
9599
9601
  } catch {
9600
9602
  }
9601
9603
  }
9602
- console.log(import_chalk18.default.blue(" [1/3] Generating updated spec..."));
9604
+ console.log(import_chalk17.default.blue(" [1/3] Generating updated spec..."));
9603
9605
  const updatePrompt = buildSpecUpdatePrompt(changeRequest, existingSpec, existingDsl, context);
9604
9606
  let updatedSpecContent;
9605
9607
  try {
@@ -9608,15 +9610,15 @@ var SpecUpdater = class {
9608
9610
  } catch (err) {
9609
9611
  throw new Error(`Spec update generation failed: ${err.message}`);
9610
9612
  }
9611
- const specBasename = path21.basename(existingSpecPath);
9613
+ const specBasename = path20.basename(existingSpecPath);
9612
9614
  const slugMatch = specBasename.match(/^feature-(.+)-v\d+\.md$/);
9613
9615
  const slug = slugMatch ? slugMatch[1] : "feature";
9614
- const specsDir = path21.dirname(existingSpecPath);
9616
+ const specsDir = path20.dirname(existingSpecPath);
9615
9617
  const { filePath: newSpecPath, version: newVersion } = await nextVersionPath(specsDir, slug);
9616
- await fs22.ensureDir(specsDir);
9617
- await fs22.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9618
- console.log(import_chalk18.default.green(` \u2714 New spec written: ${path21.relative(projectDir, newSpecPath)}`));
9619
- console.log(import_chalk18.default.blue(" [2/3] Updating DSL..."));
9618
+ await fs21.ensureDir(specsDir);
9619
+ await fs21.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9620
+ console.log(import_chalk17.default.green(` \u2714 New spec written: ${path20.relative(projectDir, newSpecPath)}`));
9621
+ console.log(import_chalk17.default.blue(" [2/3] Updating DSL..."));
9620
9622
  let updatedDsl = null;
9621
9623
  let newDslPath = null;
9622
9624
  if (existingDsl) {
@@ -9628,7 +9630,7 @@ var SpecUpdater = class {
9628
9630
  updatedDsl = parsed;
9629
9631
  }
9630
9632
  } catch {
9631
- console.log(import_chalk18.default.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9633
+ console.log(import_chalk17.default.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9632
9634
  }
9633
9635
  }
9634
9636
  if (!updatedDsl) {
@@ -9637,15 +9639,15 @@ var SpecUpdater = class {
9637
9639
  }
9638
9640
  if (updatedDsl) {
9639
9641
  const dslPath = newSpecPath.replace(/\.md$/, ".dsl.json");
9640
- await fs22.writeJson(dslPath, updatedDsl, { spaces: 2 });
9642
+ await fs21.writeJson(dslPath, updatedDsl, { spaces: 2 });
9641
9643
  newDslPath = dslPath;
9642
- console.log(import_chalk18.default.green(` \u2714 DSL updated: ${path21.relative(projectDir, dslPath)}`));
9644
+ console.log(import_chalk17.default.green(` \u2714 DSL updated: ${path20.relative(projectDir, dslPath)}`));
9643
9645
  } else {
9644
- console.log(import_chalk18.default.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9646
+ console.log(import_chalk17.default.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9645
9647
  }
9646
9648
  let affectedFiles = [];
9647
9649
  if (!opts.skipAffectedFiles && updatedDsl && existingDsl && context) {
9648
- console.log(import_chalk18.default.blue(" [3/3] Identifying affected files..."));
9650
+ console.log(import_chalk17.default.blue(" [3/3] Identifying affected files..."));
9649
9651
  const systemPrompt = getCodeGenSystemPrompt(opts.repoType);
9650
9652
  const affectedPrompt = buildAffectedFilesPrompt(
9651
9653
  changeRequest,
@@ -9656,9 +9658,9 @@ var SpecUpdater = class {
9656
9658
  try {
9657
9659
  const affectedRaw = await this.provider.generate(affectedPrompt, systemPrompt);
9658
9660
  affectedFiles = parseAffectedFiles(affectedRaw);
9659
- console.log(import_chalk18.default.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9661
+ console.log(import_chalk17.default.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9660
9662
  } catch {
9661
- console.log(import_chalk18.default.gray(" Could not identify affected files \u2014 use manual selection."));
9663
+ console.log(import_chalk17.default.gray(" Could not identify affected files \u2014 use manual selection."));
9662
9664
  }
9663
9665
  }
9664
9666
  return { newSpecPath, newVersion, newDslPath, affectedFiles, updatedDsl };
@@ -9666,8 +9668,8 @@ var SpecUpdater = class {
9666
9668
  };
9667
9669
 
9668
9670
  // core/openapi-exporter.ts
9669
- var path22 = __toESM(require("path"));
9670
- var fs23 = __toESM(require("fs-extra"));
9671
+ var path21 = __toESM(require("path"));
9672
+ var fs22 = __toESM(require("fs-extra"));
9671
9673
  function dslTypeToOASchema(typeDesc, fieldName = "") {
9672
9674
  const t = typeDesc.toLowerCase();
9673
9675
  if (t === "string" || t.includes("string")) {
@@ -9896,7 +9898,7 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9896
9898
  const format = opts.format ?? "yaml";
9897
9899
  const serverUrl = opts.serverUrl ?? "http://localhost:3000";
9898
9900
  const defaultName = `openapi.${format}`;
9899
- const outputPath = opts.outputPath ? path22.isAbsolute(opts.outputPath) ? opts.outputPath : path22.join(projectDir, opts.outputPath) : path22.join(projectDir, defaultName);
9901
+ const outputPath = opts.outputPath ? path21.isAbsolute(opts.outputPath) ? opts.outputPath : path21.join(projectDir, opts.outputPath) : path21.join(projectDir, defaultName);
9900
9902
  const doc = dslToOpenApi(dsl, serverUrl);
9901
9903
  let content;
9902
9904
  if (format === "json") {
@@ -9904,8 +9906,8 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9904
9906
  } else {
9905
9907
  content = buildYamlDoc(doc);
9906
9908
  }
9907
- await fs23.ensureDir(path22.dirname(outputPath));
9908
- await fs23.writeFile(outputPath, content, "utf-8");
9909
+ await fs22.ensureDir(path21.dirname(outputPath));
9910
+ await fs22.writeFile(outputPath, content, "utf-8");
9909
9911
  return outputPath;
9910
9912
  }
9911
9913
 
@@ -9913,9 +9915,9 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9913
9915
  dotenv.config();
9914
9916
  var CONFIG_FILE = ".ai-spec.json";
9915
9917
  async function loadConfig(dir) {
9916
- const p = path23.join(dir, CONFIG_FILE);
9917
- if (await fs24.pathExists(p)) {
9918
- return fs24.readJson(p);
9918
+ const p = path22.join(dir, CONFIG_FILE);
9919
+ if (await fs23.pathExists(p)) {
9920
+ return fs23.readJson(p);
9919
9921
  }
9920
9922
  return {};
9921
9923
  }
@@ -9940,20 +9942,20 @@ async function resolveApiKey(providerName, cliKey) {
9940
9942
  validate: (v2) => v2.trim().length > 0 || "API key cannot be empty"
9941
9943
  });
9942
9944
  await saveKey(providerName, newKey.trim());
9943
- console.log(import_chalk19.default.gray(` Key saved to ${KEY_STORE_FILE}`));
9945
+ console.log(import_chalk18.default.gray(` Key saved to ${KEY_STORE_FILE}`));
9944
9946
  return newKey.trim();
9945
9947
  }
9946
9948
  function printBanner(opts) {
9947
- console.log(import_chalk19.default.blue("\n" + "\u2500".repeat(52)));
9948
- console.log(import_chalk19.default.bold(" ai-spec \u2014 AI-driven Development Orchestrator"));
9949
- console.log(import_chalk19.default.blue("\u2500".repeat(52)));
9950
- console.log(import_chalk19.default.gray(` Spec : ${opts.specProvider} / ${opts.specModel}`));
9949
+ console.log(import_chalk18.default.blue("\n" + "\u2500".repeat(52)));
9950
+ console.log(import_chalk18.default.bold(" ai-spec \u2014 AI-driven Development Orchestrator"));
9951
+ console.log(import_chalk18.default.blue("\u2500".repeat(52)));
9952
+ console.log(import_chalk18.default.gray(` Spec : ${opts.specProvider} / ${opts.specModel}`));
9951
9953
  console.log(
9952
- import_chalk19.default.gray(
9954
+ import_chalk18.default.gray(
9953
9955
  ` Codegen : ${opts.codegenMode} (${opts.codegenProvider} / ${opts.codegenModel})`
9954
9956
  )
9955
9957
  );
9956
- console.log(import_chalk19.default.blue("\u2500".repeat(52) + "\n"));
9958
+ console.log(import_chalk18.default.blue("\u2500".repeat(52) + "\n"));
9957
9959
  }
9958
9960
  var program = new import_commander.Command();
9959
9961
  program.name("ai-spec").description("AI-driven Development Orchestrator \u2014 spec, generate, review").version("0.14.1");
@@ -9980,42 +9982,42 @@ program.command("create").description("Generate a feature spec and kick off code
9980
9982
  const workspaceLoader = new WorkspaceLoader(currentDir);
9981
9983
  const workspaceConfig = await workspaceLoader.load();
9982
9984
  if (workspaceConfig) {
9983
- console.log(import_chalk19.default.cyan(`
9985
+ console.log(import_chalk18.default.cyan(`
9984
9986
  [Workspace] Detected workspace: ${workspaceConfig.name}`));
9985
- console.log(import_chalk19.default.gray(` Repos: ${workspaceConfig.repos.map((r) => r.name).join(", ")}`));
9987
+ console.log(import_chalk18.default.gray(` Repos: ${workspaceConfig.repos.map((r) => r.name).join(", ")}`));
9986
9988
  const pipelineResults = await runMultiRepoPipeline(idea, workspaceConfig, opts, currentDir, config2);
9987
9989
  if (opts.serve) {
9988
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Auto-serve: starting mock server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9990
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Auto-serve: starting mock server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9989
9991
  const backendResult = pipelineResults.find((r) => r.role === "backend" && r.status === "success" && r.dsl);
9990
9992
  const frontendResult = pipelineResults.find((r) => (r.role === "frontend" || r.role === "mobile") && r.status === "success");
9991
9993
  if (!backendResult) {
9992
- console.log(import_chalk19.default.yellow(" No successful backend with DSL found \u2014 skipping auto-serve."));
9994
+ console.log(import_chalk18.default.yellow(" No successful backend with DSL found \u2014 skipping auto-serve."));
9993
9995
  } else {
9994
9996
  const mockPort = 3001;
9995
9997
  const mockResult = await generateMockAssets(backendResult.dsl, backendResult.repoAbsPath, { port: mockPort });
9996
- const serverJsPath = path23.join(backendResult.repoAbsPath, "mock", "server.js");
9997
- console.log(import_chalk19.default.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
9998
+ const serverJsPath = path22.join(backendResult.repoAbsPath, "mock", "server.js");
9999
+ console.log(import_chalk18.default.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
9998
10000
  const pid = startMockServerBackground(serverJsPath, mockPort);
9999
- console.log(import_chalk19.default.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
10001
+ console.log(import_chalk18.default.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
10000
10002
  if (frontendResult) {
10001
10003
  const proxyResult = await applyMockProxy(frontendResult.repoAbsPath, mockPort, backendResult.dsl.endpoints);
10002
10004
  await saveMockServerPid(frontendResult.repoAbsPath, pid);
10003
10005
  if (proxyResult.applied) {
10004
- console.log(import_chalk19.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
10005
- console.log(import_chalk19.default.bold.cyan(`
10006
+ console.log(import_chalk18.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
10007
+ console.log(import_chalk18.default.bold.cyan(`
10006
10008
  Ready! Run your frontend dev server:`));
10007
- console.log(import_chalk19.default.white(` cd ${frontendResult.repoAbsPath}`));
10008
- console.log(import_chalk19.default.white(` ${proxyResult.devCommand}`));
10009
- console.log(import_chalk19.default.gray(`
10009
+ console.log(import_chalk18.default.white(` cd ${frontendResult.repoAbsPath}`));
10010
+ console.log(import_chalk18.default.white(` ${proxyResult.devCommand}`));
10011
+ console.log(import_chalk18.default.gray(`
10010
10012
  When done, restore: ai-spec mock --restore --frontend ${frontendResult.repoAbsPath}`));
10011
10013
  } else {
10012
- console.log(import_chalk19.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
10013
- if (proxyResult.note) console.log(import_chalk19.default.gray(` ${proxyResult.note}`));
10014
- console.log(import_chalk19.default.gray(` Mock server: http://localhost:${mockPort}`));
10014
+ console.log(import_chalk18.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
10015
+ if (proxyResult.note) console.log(import_chalk18.default.gray(` ${proxyResult.note}`));
10016
+ console.log(import_chalk18.default.gray(` Mock server: http://localhost:${mockPort}`));
10015
10017
  }
10016
10018
  } else {
10017
- console.log(import_chalk19.default.gray(` No frontend repo found \u2014 mock server is running at http://localhost:${mockPort}`));
10018
- console.log(import_chalk19.default.gray(` Configure your frontend proxy manually to point to http://localhost:${mockPort}`));
10019
+ console.log(import_chalk18.default.gray(` No frontend repo found \u2014 mock server is running at http://localhost:${mockPort}`));
10020
+ console.log(import_chalk18.default.gray(` Configure your frontend proxy manually to point to http://localhost:${mockPort}`));
10019
10021
  }
10020
10022
  }
10021
10023
  }
@@ -10036,7 +10038,7 @@ program.command("create").description("Generate a feature spec and kick off code
10036
10038
  codegenModel: codegenModelName
10037
10039
  });
10038
10040
  const runId = generateRunId();
10039
- console.log(import_chalk19.default.gray(` Run ID: ${runId}`));
10041
+ console.log(import_chalk18.default.gray(` Run ID: ${runId}`));
10040
10042
  const runSnapshot = new RunSnapshot(currentDir, runId);
10041
10043
  setActiveSnapshot(runSnapshot);
10042
10044
  const runLogger = new RunLogger(currentDir, runId, {
@@ -10044,25 +10046,25 @@ program.command("create").description("Generate a feature spec and kick off code
10044
10046
  model: specModelName
10045
10047
  });
10046
10048
  setActiveLogger(runLogger);
10047
- console.log(import_chalk19.default.blue("[1/6] Loading project context..."));
10049
+ console.log(import_chalk18.default.blue("[1/6] Loading project context..."));
10048
10050
  runLogger.stageStart("context_load");
10049
10051
  const loader = new ContextLoader(currentDir);
10050
10052
  const context = await loader.loadProjectContext();
10051
10053
  const { type: detectedRepoType } = await detectRepoType(currentDir);
10052
10054
  runLogger.stageEnd("context_load", { techStack: context.techStack, repoType: detectedRepoType });
10053
- console.log(import_chalk19.default.gray(` Tech stack : ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10054
- console.log(import_chalk19.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10055
- console.log(import_chalk19.default.gray(` API files : ${context.apiStructure.length} files`));
10055
+ console.log(import_chalk18.default.gray(` Tech stack : ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10056
+ console.log(import_chalk18.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10057
+ console.log(import_chalk18.default.gray(` API files : ${context.apiStructure.length} files`));
10056
10058
  if (context.schema) {
10057
- console.log(import_chalk19.default.gray(` Prisma schema: found`));
10059
+ console.log(import_chalk18.default.gray(` Prisma schema: found`));
10058
10060
  }
10059
10061
  if (context.constitution) {
10060
- console.log(import_chalk19.default.green(` Constitution : found (.ai-spec-constitution.md)`));
10062
+ console.log(import_chalk18.default.green(` Constitution : found (.ai-spec-constitution.md)`));
10061
10063
  if (context.constitution.length > 6e3) {
10062
- console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10064
+ console.log(import_chalk18.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10063
10065
  }
10064
10066
  } else {
10065
- console.log(import_chalk19.default.yellow(" Constitution : not found \u2014 auto-generating..."));
10067
+ console.log(import_chalk18.default.yellow(" Constitution : not found \u2014 auto-generating..."));
10066
10068
  try {
10067
10069
  const constitutionGen = new ConstitutionGenerator(
10068
10070
  createProvider(specProviderName, specApiKey, specModelName)
@@ -10070,12 +10072,12 @@ program.command("create").description("Generate a feature spec and kick off code
10070
10072
  const constitutionContent = await constitutionGen.generate(currentDir);
10071
10073
  await constitutionGen.saveConstitution(currentDir, constitutionContent);
10072
10074
  context.constitution = constitutionContent;
10073
- console.log(import_chalk19.default.green(` Constitution : \u2714 generated and saved (.ai-spec-constitution.md)`));
10075
+ console.log(import_chalk18.default.green(` Constitution : \u2714 generated and saved (.ai-spec-constitution.md)`));
10074
10076
  } catch (err) {
10075
- console.log(import_chalk19.default.yellow(` Constitution : \u26A0 auto-generation failed (${err.message}), continuing without it.`));
10077
+ console.log(import_chalk18.default.yellow(` Constitution : \u26A0 auto-generation failed (${err.message}), continuing without it.`));
10076
10078
  }
10077
10079
  }
10078
- console.log(import_chalk19.default.blue(`
10080
+ console.log(import_chalk18.default.blue(`
10079
10081
  [2/6] Generating spec with ${specProviderName}/${specModelName}...`));
10080
10082
  const specProvider = createProvider(specProviderName, specApiKey, specModelName);
10081
10083
  let initialSpec;
@@ -10085,30 +10087,30 @@ program.command("create").description("Generate a feature spec and kick off code
10085
10087
  if (opts.skipTasks) {
10086
10088
  const generator = new SpecGenerator(specProvider);
10087
10089
  initialSpec = await generator.generateSpec(idea, context);
10088
- console.log(import_chalk19.default.green(" \u2714 Spec generated."));
10090
+ console.log(import_chalk18.default.green(" \u2714 Spec generated."));
10089
10091
  } else {
10090
10092
  const result = await generateSpecWithTasks(specProvider, idea, context);
10091
10093
  initialSpec = result.spec;
10092
10094
  initialTasks = result.tasks;
10093
- console.log(import_chalk19.default.green(` \u2714 Spec generated.`));
10095
+ console.log(import_chalk18.default.green(` \u2714 Spec generated.`));
10094
10096
  if (initialTasks.length > 0) {
10095
- console.log(import_chalk19.default.green(` \u2714 ${initialTasks.length} tasks generated (combined call).`));
10097
+ console.log(import_chalk18.default.green(` \u2714 ${initialTasks.length} tasks generated (combined call).`));
10096
10098
  } else {
10097
- console.log(import_chalk19.default.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
10099
+ console.log(import_chalk18.default.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
10098
10100
  }
10099
10101
  }
10100
10102
  runLogger.stageEnd("spec_gen", { taskCount: initialTasks.length });
10101
10103
  } catch (err) {
10102
10104
  runLogger.stageFail("spec_gen", err.message);
10103
- console.error(import_chalk19.default.red(" \u2718 Spec generation failed:"), err);
10105
+ console.error(import_chalk18.default.red(" \u2718 Spec generation failed:"), err);
10104
10106
  process.exit(1);
10105
10107
  }
10106
10108
  let finalSpec;
10107
10109
  if (opts.fast) {
10108
- console.log(import_chalk19.default.gray("\n[3/6] Skipping refinement (--fast)."));
10110
+ console.log(import_chalk18.default.gray("\n[3/6] Skipping refinement (--fast)."));
10109
10111
  finalSpec = initialSpec;
10110
10112
  } else {
10111
- console.log(import_chalk19.default.blue("\n[3/6] Interactive spec refinement..."));
10113
+ console.log(import_chalk18.default.blue("\n[3/6] Interactive spec refinement..."));
10112
10114
  runLogger.stageStart("spec_refine");
10113
10115
  const refiner = new SpecRefiner(specProvider);
10114
10116
  finalSpec = await refiner.refineLoop(initialSpec);
@@ -10119,7 +10121,7 @@ program.command("create").description("Generate a feature spec and kick off code
10119
10121
  const shouldRunAssessment = !opts.skipAssessment && (!opts.auto || minScore > 0);
10120
10122
  if (shouldRunAssessment) {
10121
10123
  if (!opts.auto) {
10122
- console.log(import_chalk19.default.blue("\n[3.4/6] Spec quality assessment..."));
10124
+ console.log(import_chalk18.default.blue("\n[3.4/6] Spec quality assessment..."));
10123
10125
  }
10124
10126
  runLogger.stageStart("spec_assess");
10125
10127
  const assessment = await assessSpec(specProvider, finalSpec, context.constitution ?? void 0);
@@ -10128,45 +10130,45 @@ program.command("create").description("Generate a feature spec and kick off code
10128
10130
  if (!opts.auto) printSpecAssessment(assessment);
10129
10131
  if (minScore > 0 && assessment.overallScore < minScore) {
10130
10132
  if (opts.force) {
10131
- console.log(import_chalk19.default.yellow(`
10133
+ console.log(import_chalk18.default.yellow(`
10132
10134
  \u26A0 Score gate: ${assessment.overallScore}/10 < minimum ${minScore}/10 \u2014 bypassed with --force.`));
10133
10135
  } else {
10134
10136
  runLogger.stageFail("spec_assess", `Score gate: ${assessment.overallScore} < ${minScore}`);
10135
- console.log(import_chalk19.default.red(`
10137
+ console.log(import_chalk18.default.red(`
10136
10138
  \u2718 Spec quality gate failed: overallScore ${assessment.overallScore}/10 < minimum ${minScore}/10`));
10137
10139
  if (!opts.auto) {
10138
- console.log(import_chalk19.default.gray(` Address the issues above and re-run, or use --force to bypass.`));
10140
+ console.log(import_chalk18.default.gray(` Address the issues above and re-run, or use --force to bypass.`));
10139
10141
  } else {
10140
- console.log(import_chalk19.default.gray(` Auto mode: gate enforced. Fix the spec or lower minSpecScore, or use --force to bypass.`));
10142
+ console.log(import_chalk18.default.gray(` Auto mode: gate enforced. Fix the spec or lower minSpecScore, or use --force to bypass.`));
10141
10143
  }
10142
- console.log(import_chalk19.default.gray(` Gate threshold set in .ai-spec.json \u2192 "minSpecScore": ${minScore}`));
10144
+ console.log(import_chalk18.default.gray(` Gate threshold set in .ai-spec.json \u2192 "minSpecScore": ${minScore}`));
10143
10145
  process.exit(1);
10144
10146
  }
10145
10147
  }
10146
10148
  } else {
10147
10149
  runLogger.stageEnd("spec_assess", { skipped: true });
10148
10150
  if (!opts.auto) {
10149
- console.log(import_chalk19.default.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
10151
+ console.log(import_chalk18.default.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
10150
10152
  }
10151
10153
  }
10152
10154
  }
10153
10155
  if (!opts.auto) {
10154
- console.log(import_chalk19.default.blue("\n[3.5/6] Approval Gate \u2014 review before code generation"));
10156
+ console.log(import_chalk18.default.blue("\n[3.5/6] Approval Gate \u2014 review before code generation"));
10155
10157
  const specLines = finalSpec.split("\n").length;
10156
10158
  const specWords = finalSpec.split(/\s+/).length;
10157
10159
  const taskCountHint = initialTasks.length > 0 ? ` Tasks generated : ${initialTasks.length}` : "";
10158
- console.log(import_chalk19.default.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10159
- if (taskCountHint) console.log(import_chalk19.default.gray(taskCountHint));
10160
- const previewSpecsDir = path23.join(currentDir, "specs");
10160
+ console.log(import_chalk18.default.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10161
+ if (taskCountHint) console.log(import_chalk18.default.gray(taskCountHint));
10162
+ const previewSpecsDir = path22.join(currentDir, "specs");
10161
10163
  const slug = featureSlug;
10162
10164
  const prevVersion = await findLatestVersion(previewSpecsDir, slug);
10163
10165
  if (prevVersion) {
10164
- console.log(import_chalk19.default.gray(` Previous version: v${prevVersion.version} (${prevVersion.filePath})`));
10166
+ console.log(import_chalk18.default.gray(` Previous version: v${prevVersion.version} (${prevVersion.filePath})`));
10165
10167
  const diff = computeDiff(prevVersion.content, finalSpec);
10166
- console.log(import_chalk19.default.cyan("\n \u2500\u2500 Changes vs previous version \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10168
+ console.log(import_chalk18.default.cyan("\n \u2500\u2500 Changes vs previous version \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10167
10169
  printDiffSummary(diff, `v${prevVersion.version} \u2192 v${prevVersion.version + 1}`);
10168
10170
  printDiff(diff);
10169
- console.log(import_chalk19.default.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10171
+ console.log(import_chalk18.default.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10170
10172
  }
10171
10173
  const gate = await (0, import_prompts3.select)({
10172
10174
  message: "Ready to proceed to code generation?",
@@ -10177,9 +10179,9 @@ program.command("create").description("Generate a feature spec and kick off code
10177
10179
  ]
10178
10180
  });
10179
10181
  if (gate === "view") {
10180
- console.log(import_chalk19.default.cyan("\n" + "\u2500".repeat(52)));
10182
+ console.log(import_chalk18.default.cyan("\n" + "\u2500".repeat(52)));
10181
10183
  console.log(finalSpec);
10182
- console.log(import_chalk19.default.cyan("\u2500".repeat(52) + "\n"));
10184
+ console.log(import_chalk18.default.cyan("\u2500".repeat(52) + "\n"));
10183
10185
  const confirm22 = await (0, import_prompts3.select)({
10184
10186
  message: "Proceed to code generation?",
10185
10187
  choices: [
@@ -10188,92 +10190,92 @@ program.command("create").description("Generate a feature spec and kick off code
10188
10190
  ]
10189
10191
  });
10190
10192
  if (confirm22 === "abort") {
10191
- console.log(import_chalk19.default.yellow(" Aborted. Spec was NOT saved."));
10193
+ console.log(import_chalk18.default.yellow(" Aborted. Spec was NOT saved."));
10192
10194
  process.exit(0);
10193
10195
  }
10194
10196
  } else if (gate === "abort") {
10195
- console.log(import_chalk19.default.yellow(" Aborted. Spec was NOT saved."));
10197
+ console.log(import_chalk18.default.yellow(" Aborted. Spec was NOT saved."));
10196
10198
  process.exit(0);
10197
10199
  }
10198
- console.log(import_chalk19.default.green(" \u2714 Approved \u2014 continuing to code generation."));
10200
+ console.log(import_chalk18.default.green(" \u2714 Approved \u2014 continuing to code generation."));
10199
10201
  } else {
10200
- console.log(import_chalk19.default.gray("[3.5/6] Approval Gate: skipped (--auto)."));
10202
+ console.log(import_chalk18.default.gray("[3.5/6] Approval Gate: skipped (--auto)."));
10201
10203
  }
10202
10204
  let extractedDsl = null;
10203
10205
  if (opts.skipDsl) {
10204
- console.log(import_chalk19.default.gray("\n[DSL] Skipped (--skip-dsl)."));
10206
+ console.log(import_chalk18.default.gray("\n[DSL] Skipped (--skip-dsl)."));
10205
10207
  } else {
10206
- console.log(import_chalk19.default.blue("\n[DSL] Extracting structured DSL from spec..."));
10207
- console.log(import_chalk19.default.gray(` Provider: ${specProviderName}/${specModelName}`));
10208
+ console.log(import_chalk18.default.blue("\n[DSL] Extracting structured DSL from spec..."));
10209
+ console.log(import_chalk18.default.gray(` Provider: ${specProviderName}/${specModelName}`));
10208
10210
  runLogger.stageStart("dsl_extract");
10209
10211
  try {
10210
10212
  const isFrontend = isFrontendDeps(context.dependencies);
10211
- if (isFrontend) console.log(import_chalk19.default.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
10213
+ if (isFrontend) console.log(import_chalk18.default.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
10212
10214
  const dslExtractor = new DslExtractor(specProvider);
10213
10215
  extractedDsl = await dslExtractor.extract(finalSpec, { auto: opts.auto, isFrontend });
10214
10216
  if (extractedDsl) {
10215
10217
  runLogger.stageEnd("dsl_extract", { endpoints: extractedDsl.endpoints?.length ?? 0, models: extractedDsl.models?.length ?? 0 });
10216
- console.log(import_chalk19.default.green(" \u2714 DSL extracted and validated."));
10218
+ console.log(import_chalk18.default.green(" \u2714 DSL extracted and validated."));
10217
10219
  } else {
10218
10220
  runLogger.stageEnd("dsl_extract", { skipped: true });
10219
- console.log(import_chalk19.default.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
10221
+ console.log(import_chalk18.default.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
10220
10222
  }
10221
10223
  } catch (err) {
10222
10224
  runLogger.stageFail("dsl_extract", err.message);
10223
- console.log(import_chalk19.default.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
10225
+ console.log(import_chalk18.default.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
10224
10226
  }
10225
10227
  }
10226
10228
  const isFrontendProject2 = isFrontendDeps(context.dependencies ?? []);
10227
10229
  const skipWorktree = opts.worktree ? false : opts.skipWorktree || isFrontendProject2;
10228
10230
  let workingDir = currentDir;
10229
10231
  if (!skipWorktree) {
10230
- console.log(import_chalk19.default.blue("\n[4/6] Setting up git worktree..."));
10232
+ console.log(import_chalk18.default.blue("\n[4/6] Setting up git worktree..."));
10231
10233
  const worktreeManager = new GitWorktreeManager(currentDir);
10232
10234
  const worktreePath = await worktreeManager.createWorktree(idea);
10233
10235
  if (worktreePath) workingDir = worktreePath;
10234
10236
  } else {
10235
10237
  const reason = opts.worktree ? "" : isFrontendProject2 ? " (frontend project \u2014 use --worktree to override)" : " (--skip-worktree)";
10236
- console.log(import_chalk19.default.gray(`[4/6] Skipping worktree${reason}.`));
10238
+ console.log(import_chalk18.default.gray(`[4/6] Skipping worktree${reason}.`));
10237
10239
  }
10238
- const specsDir = path23.join(workingDir, "specs");
10239
- await fs24.ensureDir(specsDir);
10240
+ const specsDir = path22.join(workingDir, "specs");
10241
+ await fs23.ensureDir(specsDir);
10240
10242
  const { filePath: specFile, version: specVersion } = await nextVersionPath(specsDir, featureSlug);
10241
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10242
- console.log(import_chalk19.default.green(`
10243
- [5/6] \u2714 Spec saved: ${specFile}`) + import_chalk19.default.gray(` (v${specVersion})`));
10243
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10244
+ console.log(import_chalk18.default.green(`
10245
+ [5/6] \u2714 Spec saved: ${specFile}`) + import_chalk18.default.gray(` (v${specVersion})`));
10244
10246
  let savedDslFile = null;
10245
10247
  if (extractedDsl) {
10246
10248
  const dslExtractor = new DslExtractor(specProvider);
10247
10249
  savedDslFile = await dslExtractor.saveDsl(extractedDsl, specFile);
10248
- console.log(import_chalk19.default.green(` \u2714 DSL saved : ${savedDslFile}`));
10250
+ console.log(import_chalk18.default.green(` \u2714 DSL saved : ${savedDslFile}`));
10249
10251
  }
10250
10252
  if (!opts.skipTasks) {
10251
10253
  const taskGen = new TaskGenerator(specProvider);
10252
10254
  let tasksToSave = initialTasks;
10253
10255
  if (tasksToSave.length === 0) {
10254
- console.log(import_chalk19.default.blue(`
10256
+ console.log(import_chalk18.default.blue(`
10255
10257
  Generating tasks (separate call)...`));
10256
10258
  try {
10257
10259
  tasksToSave = await taskGen.generateTasks(finalSpec, context);
10258
10260
  } catch (err) {
10259
- console.log(import_chalk19.default.yellow(` \u26A0 Task generation failed: ${err.message}`));
10261
+ console.log(import_chalk18.default.yellow(` \u26A0 Task generation failed: ${err.message}`));
10260
10262
  }
10261
10263
  }
10262
10264
  if (tasksToSave.length > 0) {
10263
10265
  const sorted = taskGen.sortByLayer(tasksToSave);
10264
10266
  const tasksFile = await taskGen.saveTasks(sorted, specFile);
10265
10267
  printTasks(sorted);
10266
- console.log(import_chalk19.default.green(` \u2714 Tasks saved: ${tasksFile}`));
10268
+ console.log(import_chalk18.default.green(` \u2714 Tasks saved: ${tasksFile}`));
10267
10269
  } else {
10268
- console.log(import_chalk19.default.yellow(" \u26A0 No tasks generated \u2014 code generation will use fallback file planning."));
10270
+ console.log(import_chalk18.default.yellow(" \u26A0 No tasks generated \u2014 code generation will use fallback file planning."));
10269
10271
  }
10270
10272
  }
10271
- console.log(import_chalk19.default.blue(`
10273
+ console.log(import_chalk18.default.blue(`
10272
10274
  [6/6] Code generation (mode: ${codegenMode})...`));
10273
10275
  const codegenProvider = codegenProviderName === specProviderName && codegenApiKey === specApiKey ? specProvider : createProvider(codegenProviderName, codegenApiKey, codegenModelName);
10274
10276
  let generatedTestFiles = [];
10275
10277
  if (opts.tdd && extractedDsl) {
10276
- console.log(import_chalk19.default.cyan("\n[TDD] Generating pre-implementation tests (will fail until code is written)..."));
10278
+ console.log(import_chalk18.default.cyan("\n[TDD] Generating pre-implementation tests (will fail until code is written)..."));
10277
10279
  const testGen = new TestGenerator(codegenProvider);
10278
10280
  generatedTestFiles = await testGen.generateTdd(extractedDsl, workingDir);
10279
10281
  }
@@ -10287,13 +10289,13 @@ program.command("create").description("Generate a feature spec and kick off code
10287
10289
  });
10288
10290
  runLogger.stageEnd("codegen", { filesGenerated: generatedFiles.length });
10289
10291
  if (opts.tdd) {
10290
- console.log(import_chalk19.default.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
10292
+ console.log(import_chalk18.default.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
10291
10293
  } else if (opts.skipTests) {
10292
- console.log(import_chalk19.default.gray("\n[7/9] Skipping test generation (--skip-tests)."));
10294
+ console.log(import_chalk18.default.gray("\n[7/9] Skipping test generation (--skip-tests)."));
10293
10295
  } else if (!extractedDsl) {
10294
- console.log(import_chalk19.default.gray("\n[7/9] Skipping test generation (no DSL available)."));
10296
+ console.log(import_chalk18.default.gray("\n[7/9] Skipping test generation (no DSL available)."));
10295
10297
  } else {
10296
- console.log(import_chalk19.default.blue(`
10298
+ console.log(import_chalk18.default.blue(`
10297
10299
  [7/9] Test skeleton generation...`));
10298
10300
  runLogger.stageStart("test_gen");
10299
10301
  const testGen = new TestGenerator(codegenProvider);
@@ -10301,10 +10303,10 @@ program.command("create").description("Generate a feature spec and kick off code
10301
10303
  runLogger.stageEnd("test_gen", { filesGenerated: generatedTestFiles.length });
10302
10304
  }
10303
10305
  if (opts.skipErrorFeedback) {
10304
- console.log(import_chalk19.default.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10306
+ console.log(import_chalk18.default.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10305
10307
  } else {
10306
10308
  if (opts.tdd) {
10307
- console.log(import_chalk19.default.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10309
+ console.log(import_chalk18.default.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10308
10310
  }
10309
10311
  runLogger.stageStart("error_feedback");
10310
10312
  await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
@@ -10315,10 +10317,10 @@ program.command("create").description("Generate a feature spec and kick off code
10315
10317
  }
10316
10318
  let reviewResult = "";
10317
10319
  if (!opts.skipReview) {
10318
- console.log(import_chalk19.default.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10320
+ console.log(import_chalk18.default.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10319
10321
  runLogger.stageStart("review");
10320
10322
  const reviewer = new CodeReviewer(specProvider, currentDir);
10321
- const savedSpec = await fs24.readFile(specFile, "utf-8");
10323
+ const savedSpec = await fs23.readFile(specFile, "utf-8");
10322
10324
  if (codegenMode === "api" && generatedFiles.length > 0) {
10323
10325
  reviewResult = await reviewer.reviewFiles(savedSpec, generatedFiles, workingDir, specFile);
10324
10326
  } else {
@@ -10334,19 +10336,19 @@ program.command("create").description("Generate a feature spec and kick off code
10334
10336
  await accumulateReviewKnowledge(specProvider, currentDir, reviewResult);
10335
10337
  }
10336
10338
  runLogger.finish();
10337
- console.log(import_chalk19.default.bold.green("\n\u2714 All done!"));
10338
- console.log(import_chalk19.default.gray(` Spec : ${specFile}`));
10339
- if (savedDslFile) console.log(import_chalk19.default.gray(` DSL : ${savedDslFile}`));
10339
+ console.log(import_chalk18.default.bold.green("\n\u2714 All done!"));
10340
+ console.log(import_chalk18.default.gray(` Spec : ${specFile}`));
10341
+ if (savedDslFile) console.log(import_chalk18.default.gray(` DSL : ${savedDslFile}`));
10340
10342
  if (generatedTestFiles.length > 0) {
10341
- console.log(import_chalk19.default.gray(` Tests : ${generatedTestFiles.length} skeleton file(s) generated`));
10343
+ console.log(import_chalk18.default.gray(` Tests : ${generatedTestFiles.length} skeleton file(s) generated`));
10342
10344
  }
10343
- console.log(import_chalk19.default.gray(` Working dir : ${workingDir}`));
10345
+ console.log(import_chalk18.default.gray(` Working dir : ${workingDir}`));
10344
10346
  if (workingDir !== currentDir) {
10345
- console.log(import_chalk19.default.gray(` Run \`cd ${workingDir}\` to enter the worktree.`));
10347
+ console.log(import_chalk18.default.gray(` Run \`cd ${workingDir}\` to enter the worktree.`));
10346
10348
  }
10347
10349
  runLogger.printSummary();
10348
10350
  if (runSnapshot.fileCount > 0) {
10349
- console.log(import_chalk19.default.gray(` To undo changes: ai-spec restore ${runId}`));
10351
+ console.log(import_chalk18.default.gray(` To undo changes: ai-spec restore ${runId}`));
10350
10352
  }
10351
10353
  });
10352
10354
  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(
@@ -10363,24 +10365,24 @@ program.command("review").description("Run AI code review on current git diff ag
10363
10365
  const reviewer = new CodeReviewer(provider, currentDir);
10364
10366
  let specContent = "";
10365
10367
  let resolvedSpecFile;
10366
- if (specFile && await fs24.pathExists(specFile)) {
10367
- specContent = await fs24.readFile(specFile, "utf-8");
10368
+ if (specFile && await fs23.pathExists(specFile)) {
10369
+ specContent = await fs23.readFile(specFile, "utf-8");
10368
10370
  resolvedSpecFile = specFile;
10369
- console.log(import_chalk19.default.gray(`Using spec: ${specFile}`));
10371
+ console.log(import_chalk18.default.gray(`Using spec: ${specFile}`));
10370
10372
  } else {
10371
- const specsDir = path23.join(currentDir, "specs");
10372
- if (await fs24.pathExists(specsDir)) {
10373
- const files = (await fs24.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10373
+ const specsDir = path22.join(currentDir, "specs");
10374
+ if (await fs23.pathExists(specsDir)) {
10375
+ const files = (await fs23.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10374
10376
  if (files.length > 0) {
10375
- const latest = path23.join(specsDir, files[0]);
10376
- specContent = await fs24.readFile(latest, "utf-8");
10377
+ const latest = path22.join(specsDir, files[0]);
10378
+ specContent = await fs23.readFile(latest, "utf-8");
10377
10379
  resolvedSpecFile = latest;
10378
- console.log(import_chalk19.default.gray(`Auto-detected spec: specs/${files[0]}`));
10380
+ console.log(import_chalk18.default.gray(`Auto-detected spec: specs/${files[0]}`));
10379
10381
  }
10380
10382
  }
10381
10383
  }
10382
10384
  if (!specContent) {
10383
- console.log(import_chalk19.default.yellow("No spec file found. Running review without spec context."));
10385
+ console.log(import_chalk18.default.yellow("No spec file found. Running review without spec context."));
10384
10386
  }
10385
10387
  await reviewer.reviewCode(specContent, resolvedSpecFile);
10386
10388
  await reviewer.printScoreTrend();
@@ -10407,15 +10409,15 @@ program.command("init").description(`Analyze codebase and generate Project Const
10407
10409
  auto: opts.auto
10408
10410
  });
10409
10411
  if (result.written) {
10410
- console.log(import_chalk19.default.blue("\n Summary:"));
10411
- console.log(import_chalk19.default.gray(` Lines : ${result.before.totalLines} \u2192 ${result.after.totalLines} (${result.before.totalLines - result.after.totalLines > 0 ? "-" : "+"}${Math.abs(result.before.totalLines - result.after.totalLines)})`));
10412
- console.log(import_chalk19.default.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
10412
+ console.log(import_chalk18.default.blue("\n Summary:"));
10413
+ console.log(import_chalk18.default.gray(` Lines : ${result.before.totalLines} \u2192 ${result.after.totalLines} (${result.before.totalLines - result.after.totalLines > 0 ? "-" : "+"}${Math.abs(result.before.totalLines - result.after.totalLines)})`));
10414
+ console.log(import_chalk18.default.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
10413
10415
  if (result.backupPath) {
10414
- console.log(import_chalk19.default.gray(` Backup: ${path23.basename(result.backupPath)}`));
10416
+ console.log(import_chalk18.default.gray(` Backup: ${path22.basename(result.backupPath)}`));
10415
10417
  }
10416
10418
  }
10417
10419
  } catch (err) {
10418
- console.error(import_chalk19.default.red(` \u2718 Consolidation failed: ${err.message}`));
10420
+ console.error(import_chalk18.default.red(` \u2718 Consolidation failed: ${err.message}`));
10419
10421
  process.exit(1);
10420
10422
  }
10421
10423
  return;
@@ -10423,75 +10425,75 @@ program.command("init").description(`Analyze codebase and generate Project Const
10423
10425
  if (opts.global) {
10424
10426
  const existing = await loadGlobalConstitution([currentDir]);
10425
10427
  if (existing && !opts.force) {
10426
- console.log(import_chalk19.default.yellow(`
10428
+ console.log(import_chalk18.default.yellow(`
10427
10429
  Global constitution already exists at: ${existing.source}`));
10428
- console.log(import_chalk19.default.gray(" Use --force to overwrite it."));
10430
+ console.log(import_chalk18.default.gray(" Use --force to overwrite it."));
10429
10431
  return;
10430
10432
  }
10431
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Generating Global Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10432
- console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
10433
- console.log(import_chalk19.default.gray(" Scanning repos in workspace..."));
10433
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Generating Global Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10434
+ console.log(import_chalk18.default.gray(` Provider: ${providerName}/${modelName}`));
10435
+ console.log(import_chalk18.default.gray(" Scanning repos in workspace..."));
10434
10436
  const loader = new ContextLoader(currentDir);
10435
10437
  const ctx = await loader.loadProjectContext();
10436
10438
  const summary = [
10437
10439
  `Tech stack: ${ctx.techStack.join(", ") || "unknown"}`,
10438
10440
  `Dependencies: ${ctx.dependencies.slice(0, 20).join(", ")}`
10439
10441
  ].join("\n");
10440
- const prompt = buildGlobalConstitutionPrompt([{ name: path23.basename(currentDir), summary }]);
10442
+ const prompt = buildGlobalConstitutionPrompt([{ name: path22.basename(currentDir), summary }]);
10441
10443
  let globalConstitution;
10442
10444
  try {
10443
10445
  globalConstitution = await provider.generate(prompt, globalConstitutionSystemPrompt);
10444
10446
  } catch (err) {
10445
- console.error(import_chalk19.default.red(" \u2718 Failed to generate global constitution:"), err);
10447
+ console.error(import_chalk18.default.red(" \u2718 Failed to generate global constitution:"), err);
10446
10448
  process.exit(1);
10447
10449
  }
10448
10450
  const saved2 = await saveGlobalConstitution(globalConstitution, currentDir);
10449
- console.log(import_chalk19.default.green(`
10451
+ console.log(import_chalk18.default.green(`
10450
10452
  \u2714 Global constitution saved: ${saved2}`));
10451
- console.log(import_chalk19.default.gray(" This will be automatically merged into all project constitutions in this workspace."));
10452
- console.log(import_chalk19.default.gray(" Project-level rules always override global rules.\n"));
10453
- console.log(import_chalk19.default.bold(" Preview:"));
10454
- console.log(import_chalk19.default.gray(globalConstitution.split("\n").slice(0, 12).join("\n")));
10453
+ console.log(import_chalk18.default.gray(" This will be automatically merged into all project constitutions in this workspace."));
10454
+ console.log(import_chalk18.default.gray(" Project-level rules always override global rules.\n"));
10455
+ console.log(import_chalk18.default.bold(" Preview:"));
10456
+ console.log(import_chalk18.default.gray(globalConstitution.split("\n").slice(0, 12).join("\n")));
10455
10457
  if (globalConstitution.split("\n").length > 12) {
10456
- console.log(import_chalk19.default.gray(` ... (${globalConstitution.split("\n").length} lines total)`));
10458
+ console.log(import_chalk18.default.gray(` ... (${globalConstitution.split("\n").length} lines total)`));
10457
10459
  }
10458
10460
  return;
10459
10461
  }
10460
- const constitutionPath = path23.join(currentDir, CONSTITUTION_FILE);
10461
- if (!opts.force && await fs24.pathExists(constitutionPath)) {
10462
- console.log(import_chalk19.default.yellow(`
10462
+ const constitutionPath = path22.join(currentDir, CONSTITUTION_FILE);
10463
+ if (!opts.force && await fs23.pathExists(constitutionPath)) {
10464
+ console.log(import_chalk18.default.yellow(`
10463
10465
  ${CONSTITUTION_FILE} already exists.`));
10464
- console.log(import_chalk19.default.gray(" Use --force to overwrite it."));
10465
- console.log(import_chalk19.default.gray(` Or edit it directly: ${constitutionPath}`));
10466
+ console.log(import_chalk18.default.gray(" Use --force to overwrite it."));
10467
+ console.log(import_chalk18.default.gray(` Or edit it directly: ${constitutionPath}`));
10466
10468
  return;
10467
10469
  }
10468
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Generating Project Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10469
- console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
10470
- console.log(import_chalk19.default.gray(" Analyzing codebase..."));
10470
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Generating Project Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10471
+ console.log(import_chalk18.default.gray(` Provider: ${providerName}/${modelName}`));
10472
+ console.log(import_chalk18.default.gray(" Analyzing codebase..."));
10471
10473
  const generator = new ConstitutionGenerator(provider);
10472
10474
  let constitution;
10473
10475
  try {
10474
10476
  constitution = await generator.generate(currentDir);
10475
10477
  } catch (err) {
10476
- console.error(import_chalk19.default.red(" \u2718 Failed to generate constitution:"), err);
10478
+ console.error(import_chalk18.default.red(" \u2718 Failed to generate constitution:"), err);
10477
10479
  process.exit(1);
10478
10480
  }
10479
10481
  const saved = await generator.saveConstitution(currentDir, constitution);
10480
- const globalResult = await loadGlobalConstitution([path23.dirname(currentDir)]);
10482
+ const globalResult = await loadGlobalConstitution([path22.dirname(currentDir)]);
10481
10483
  if (globalResult) {
10482
- console.log(import_chalk19.default.cyan(`
10484
+ console.log(import_chalk18.default.cyan(`
10483
10485
  \u2139 Global constitution detected: ${globalResult.source}`));
10484
- console.log(import_chalk19.default.gray(" It will be merged with this project constitution at runtime."));
10485
- console.log(import_chalk19.default.gray(" Project rules take priority over global rules."));
10486
+ console.log(import_chalk18.default.gray(" It will be merged with this project constitution at runtime."));
10487
+ console.log(import_chalk18.default.gray(" Project rules take priority over global rules."));
10486
10488
  }
10487
- console.log(import_chalk19.default.green(`
10489
+ console.log(import_chalk18.default.green(`
10488
10490
  \u2714 Constitution saved: ${saved}`));
10489
- console.log(import_chalk19.default.gray(" This file will be automatically used in all future `ai-spec create` runs."));
10490
- console.log(import_chalk19.default.gray(" Edit it to add custom rules or red lines for your project.\n"));
10491
- console.log(import_chalk19.default.bold(" Preview:"));
10492
- console.log(import_chalk19.default.gray(constitution.split("\n").slice(0, 15).join("\n")));
10491
+ console.log(import_chalk18.default.gray(" This file will be automatically used in all future `ai-spec create` runs."));
10492
+ console.log(import_chalk18.default.gray(" Edit it to add custom rules or red lines for your project.\n"));
10493
+ console.log(import_chalk18.default.bold(" Preview:"));
10494
+ console.log(import_chalk18.default.gray(constitution.split("\n").slice(0, 15).join("\n")));
10493
10495
  if (constitution.split("\n").length > 15) {
10494
- console.log(import_chalk19.default.gray(` ... (${constitution.split("\n").length} lines total)`));
10496
+ console.log(import_chalk18.default.gray(` ... (${constitution.split("\n").length} lines total)`));
10495
10497
  }
10496
10498
  });
10497
10499
  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(
@@ -10499,44 +10501,44 @@ program.command("config").description(`Set default configuration for this projec
10499
10501
  "Default code generation mode (claude-code|api|plan)"
10500
10502
  ).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) => {
10501
10503
  const currentDir = process.cwd();
10502
- const configPath = path23.join(currentDir, CONFIG_FILE);
10504
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10503
10505
  if (opts.clearKeys) {
10504
10506
  await clearAllKeys();
10505
- console.log(import_chalk19.default.green(`\u2714 All saved API keys cleared.`));
10507
+ console.log(import_chalk18.default.green(`\u2714 All saved API keys cleared.`));
10506
10508
  return;
10507
10509
  }
10508
10510
  if (opts.clearKey) {
10509
10511
  await clearKey(opts.clearKey);
10510
- console.log(import_chalk19.default.green(`\u2714 Saved key for "${opts.clearKey}" removed.`));
10512
+ console.log(import_chalk18.default.green(`\u2714 Saved key for "${opts.clearKey}" removed.`));
10511
10513
  return;
10512
10514
  }
10513
10515
  if (opts.listKeys) {
10514
- const store = await fs24.readJson(KEY_STORE_FILE).catch(() => ({}));
10516
+ const store = await fs23.readJson(KEY_STORE_FILE).catch(() => ({}));
10515
10517
  const providers = Object.keys(store);
10516
10518
  if (providers.length === 0) {
10517
- console.log(import_chalk19.default.gray("No saved API keys."));
10519
+ console.log(import_chalk18.default.gray("No saved API keys."));
10518
10520
  } else {
10519
- console.log(import_chalk19.default.bold("Saved API keys:"));
10521
+ console.log(import_chalk18.default.bold("Saved API keys:"));
10520
10522
  for (const p of providers) {
10521
10523
  const k2 = store[p];
10522
- console.log(import_chalk19.default.gray(` ${p}: ${k2.slice(0, 6)}...${k2.slice(-4)}`));
10524
+ console.log(import_chalk18.default.gray(` ${p}: ${k2.slice(0, 6)}...${k2.slice(-4)}`));
10523
10525
  }
10524
- console.log(import_chalk19.default.gray(`
10526
+ console.log(import_chalk18.default.gray(`
10525
10527
  File: ${KEY_STORE_FILE}`));
10526
10528
  }
10527
10529
  return;
10528
10530
  }
10529
10531
  if (opts.reset) {
10530
- await fs24.writeJson(configPath, {}, { spaces: 2 });
10531
- console.log(import_chalk19.default.green(`\u2714 Config reset: ${configPath}`));
10532
+ await fs23.writeJson(configPath, {}, { spaces: 2 });
10533
+ console.log(import_chalk18.default.green(`\u2714 Config reset: ${configPath}`));
10532
10534
  return;
10533
10535
  }
10534
10536
  const existing = await loadConfig(currentDir);
10535
10537
  if (opts.show) {
10536
10538
  if (Object.keys(existing).length === 0) {
10537
- console.log(import_chalk19.default.gray("No config file found. Using built-in defaults."));
10539
+ console.log(import_chalk18.default.gray("No config file found. Using built-in defaults."));
10538
10540
  } else {
10539
- console.log(import_chalk19.default.bold(`${configPath}:`));
10541
+ console.log(import_chalk18.default.bold(`${configPath}:`));
10540
10542
  console.log(JSON.stringify(existing, null, 2));
10541
10543
  }
10542
10544
  return;
@@ -10550,27 +10552,27 @@ File: ${KEY_STORE_FILE}`));
10550
10552
  if (opts.minSpecScore !== void 0) {
10551
10553
  const score = parseInt(opts.minSpecScore, 10);
10552
10554
  if (isNaN(score) || score < 0 || score > 10) {
10553
- console.error(import_chalk19.default.red(" --min-spec-score must be a number between 0 and 10"));
10555
+ console.error(import_chalk18.default.red(" --min-spec-score must be a number between 0 and 10"));
10554
10556
  process.exit(1);
10555
10557
  }
10556
10558
  updated.minSpecScore = score;
10557
10559
  }
10558
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10559
- console.log(import_chalk19.default.green(`\u2714 Config saved to ${configPath}`));
10560
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10561
+ console.log(import_chalk18.default.green(`\u2714 Config saved to ${configPath}`));
10560
10562
  console.log(JSON.stringify(updated, null, 2));
10561
10563
  });
10562
10564
  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) => {
10563
10565
  const currentDir = process.cwd();
10564
- const configPath = path23.join(currentDir, CONFIG_FILE);
10566
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10565
10567
  if (opts.list) {
10566
- console.log(import_chalk19.default.bold("\nAvailable providers & models:\n"));
10568
+ console.log(import_chalk18.default.bold("\nAvailable providers & models:\n"));
10567
10569
  for (const [key, meta] of Object.entries(PROVIDER_CATALOG)) {
10568
10570
  console.log(
10569
- ` ${import_chalk19.default.bold.cyan(key.padEnd(10))} ${import_chalk19.default.white(meta.displayName)}`
10571
+ ` ${import_chalk18.default.bold.cyan(key.padEnd(10))} ${import_chalk18.default.white(meta.displayName)}`
10570
10572
  );
10571
- console.log(import_chalk19.default.gray(` ${meta.description}`));
10573
+ console.log(import_chalk18.default.gray(` ${meta.description}`));
10572
10574
  console.log(
10573
- import_chalk19.default.gray(
10575
+ import_chalk18.default.gray(
10574
10576
  ` env: ${meta.envKey} | models: ${meta.models.join(", ")}`
10575
10577
  )
10576
10578
  );
@@ -10579,10 +10581,10 @@ program.command("model").description("Interactively switch the active AI provide
10579
10581
  return;
10580
10582
  }
10581
10583
  const existing = await loadConfig(currentDir);
10582
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Model Switcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10584
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Model Switcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10583
10585
  if (Object.keys(existing).length > 0) {
10584
10586
  console.log(
10585
- import_chalk19.default.gray(
10587
+ import_chalk18.default.gray(
10586
10588
  ` Current: spec=${existing.provider ?? "gemini"}/${existing.model ?? DEFAULT_MODELS[existing.provider ?? "gemini"]}` + (existing.codegenProvider ? ` codegen=${existing.codegenProvider}/${existing.codegenModel ?? ""}` : "")
10587
10589
  )
10588
10590
  );
@@ -10600,7 +10602,7 @@ program.command("model").description("Interactively switch the active AI provide
10600
10602
  const providerKey = await (0, import_prompts3.select)({
10601
10603
  message: `${label} \u2014 select provider:`,
10602
10604
  choices: Object.entries(PROVIDER_CATALOG).map(([key, meta2]) => ({
10603
- name: `${meta2.displayName.padEnd(22)} ${import_chalk19.default.gray(meta2.description)}`,
10605
+ name: `${meta2.displayName.padEnd(22)} ${import_chalk18.default.gray(meta2.description)}`,
10604
10606
  value: key,
10605
10607
  short: meta2.displayName
10606
10608
  }))
@@ -10608,7 +10610,7 @@ program.command("model").description("Interactively switch the active AI provide
10608
10610
  const meta = PROVIDER_CATALOG[providerKey];
10609
10611
  const modelChoices = [
10610
10612
  ...meta.models.map((m) => ({ name: m, value: m })),
10611
- { name: import_chalk19.default.italic("\u270E Enter custom model name..."), value: "__custom__" }
10613
+ { name: import_chalk18.default.italic("\u270E Enter custom model name..."), value: "__custom__" }
10612
10614
  ];
10613
10615
  let chosenModel = await (0, import_prompts3.select)({
10614
10616
  message: `${label} \u2014 select model (${meta.displayName}):`,
@@ -10642,37 +10644,37 @@ program.command("model").description("Interactively switch the active AI provide
10642
10644
  if (!updated.codegen || updated.codegen === "claude-code") {
10643
10645
  updated.codegen = "api";
10644
10646
  console.log(
10645
- import_chalk19.default.yellow(
10647
+ import_chalk18.default.yellow(
10646
10648
  `
10647
10649
  \u26A0 provider "${effectiveCodegenProvider}" \u4E0D\u652F\u6301 "claude-code" \u6A21\u5F0F\u3002`
10648
10650
  )
10649
10651
  );
10650
- console.log(import_chalk19.default.gray(` \u5DF2\u81EA\u52A8\u5C06 codegen \u6A21\u5F0F\u8BBE\u4E3A "api"\u3002`));
10652
+ console.log(import_chalk18.default.gray(` \u5DF2\u81EA\u52A8\u5C06 codegen \u6A21\u5F0F\u8BBE\u4E3A "api"\u3002`));
10651
10653
  }
10652
10654
  }
10653
10655
  }
10654
- console.log(import_chalk19.default.blue("\n Preview:"));
10655
- console.log(import_chalk19.default.gray(` spec \u2192 ${updated.provider}/${updated.model}`));
10656
+ console.log(import_chalk18.default.blue("\n Preview:"));
10657
+ console.log(import_chalk18.default.gray(` spec \u2192 ${updated.provider}/${updated.model}`));
10656
10658
  if (updated.codegenProvider) {
10657
10659
  console.log(
10658
- import_chalk19.default.gray(
10660
+ import_chalk18.default.gray(
10659
10661
  ` codegen \u2192 ${updated.codegenProvider}/${updated.codegenModel} (mode: ${updated.codegen ?? "claude-code"})`
10660
10662
  )
10661
10663
  );
10662
10664
  }
10663
10665
  const ok = await (0, import_prompts3.confirm)({ message: "Save to .ai-spec.json?", default: true });
10664
10666
  if (!ok) {
10665
- console.log(import_chalk19.default.gray(" Cancelled."));
10667
+ console.log(import_chalk18.default.gray(" Cancelled."));
10666
10668
  return;
10667
10669
  }
10668
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10669
- console.log(import_chalk19.default.green(`
10670
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10671
+ console.log(import_chalk18.default.green(`
10670
10672
  \u2714 Saved to ${configPath}`));
10671
10673
  const providerToCheck = updated.provider ?? "gemini";
10672
10674
  const envKey = ENV_KEY_MAP[providerToCheck];
10673
10675
  if (envKey && !process.env[envKey]) {
10674
10676
  console.log(
10675
- import_chalk19.default.yellow(
10677
+ import_chalk18.default.yellow(
10676
10678
  ` \u26A0 Remember to set ${envKey} in your environment or .env file.`
10677
10679
  )
10678
10680
  );
@@ -10691,29 +10693,29 @@ async function runSingleRepoPipelineInWorkspace(opts) {
10691
10693
  cliOpts,
10692
10694
  contractContextSection
10693
10695
  } = opts;
10694
- console.log(import_chalk19.default.blue(`
10696
+ console.log(import_chalk18.default.blue(`
10695
10697
  [${repoName}] Loading project context...`));
10696
10698
  const loader = new ContextLoader(repoAbsPath);
10697
10699
  let context = await loader.loadProjectContext();
10698
10700
  const { type: detectedRepoType } = await detectRepoType(repoAbsPath);
10699
- console.log(import_chalk19.default.gray(` Tech stack: ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10700
- console.log(import_chalk19.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10701
+ console.log(import_chalk18.default.gray(` Tech stack: ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
10702
+ console.log(import_chalk18.default.gray(` Dependencies: ${context.dependencies.length} packages`));
10701
10703
  if (context.constitution && context.constitution.length > 6e3) {
10702
- console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10704
+ console.log(import_chalk18.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
10703
10705
  }
10704
10706
  if (!context.constitution) {
10705
- console.log(import_chalk19.default.yellow(` Constitution: not found \u2014 auto-generating...`));
10707
+ console.log(import_chalk18.default.yellow(` Constitution: not found \u2014 auto-generating...`));
10706
10708
  try {
10707
10709
  const constitutionGen = new ConstitutionGenerator(specProvider);
10708
10710
  const constitutionContent = await constitutionGen.generate(repoAbsPath);
10709
10711
  await constitutionGen.saveConstitution(repoAbsPath, constitutionContent);
10710
10712
  context.constitution = constitutionContent;
10711
- console.log(import_chalk19.default.green(` Constitution: generated`));
10713
+ console.log(import_chalk18.default.green(` Constitution: generated`));
10712
10714
  } catch (err) {
10713
- console.log(import_chalk19.default.yellow(` Constitution: auto-generation failed (${err.message}), continuing.`));
10715
+ console.log(import_chalk18.default.yellow(` Constitution: auto-generation failed (${err.message}), continuing.`));
10714
10716
  }
10715
10717
  } else {
10716
- console.log(import_chalk19.default.green(` Constitution: found`));
10718
+ console.log(import_chalk18.default.green(` Constitution: found`));
10717
10719
  }
10718
10720
  let fullIdea = idea;
10719
10721
  if (contractContextSection) {
@@ -10721,58 +10723,58 @@ async function runSingleRepoPipelineInWorkspace(opts) {
10721
10723
 
10722
10724
  ${contractContextSection}`;
10723
10725
  }
10724
- console.log(import_chalk19.default.blue(` [${repoName}] Generating spec...`));
10726
+ console.log(import_chalk18.default.blue(` [${repoName}] Generating spec...`));
10725
10727
  let finalSpec;
10726
10728
  try {
10727
10729
  const result = await generateSpecWithTasks(specProvider, fullIdea, context);
10728
10730
  finalSpec = result.spec;
10729
- console.log(import_chalk19.default.green(` Spec generated.`));
10731
+ console.log(import_chalk18.default.green(` Spec generated.`));
10730
10732
  } catch (err) {
10731
- console.error(import_chalk19.default.red(` Spec generation failed: ${err.message}`));
10733
+ console.error(import_chalk18.default.red(` Spec generation failed: ${err.message}`));
10732
10734
  return { dsl: null, specFile: null };
10733
10735
  }
10734
10736
  let extractedDsl = null;
10735
10737
  if (!cliOpts.skipDsl) {
10736
- console.log(import_chalk19.default.blue(` [${repoName}] Extracting DSL...`));
10738
+ console.log(import_chalk18.default.blue(` [${repoName}] Extracting DSL...`));
10737
10739
  try {
10738
10740
  const dslExtractor = new DslExtractor(specProvider);
10739
10741
  const repoIsFrontend = isFrontendDeps(context.dependencies);
10740
10742
  extractedDsl = await dslExtractor.extract(finalSpec, { auto: true, isFrontend: repoIsFrontend });
10741
10743
  if (extractedDsl) {
10742
- console.log(import_chalk19.default.green(` DSL extracted.`));
10744
+ console.log(import_chalk18.default.green(` DSL extracted.`));
10743
10745
  }
10744
10746
  } catch (err) {
10745
- console.log(import_chalk19.default.yellow(` DSL extraction failed: ${err.message}`));
10747
+ console.log(import_chalk18.default.yellow(` DSL extraction failed: ${err.message}`));
10746
10748
  }
10747
10749
  }
10748
10750
  const isFrontendRepo = isFrontendDeps(context.dependencies ?? []);
10749
10751
  const skipWorktreeForRepo = cliOpts.worktree ? false : cliOpts.skipWorktree || isFrontendRepo;
10750
10752
  let workingDir = repoAbsPath;
10751
10753
  if (!skipWorktreeForRepo) {
10752
- console.log(import_chalk19.default.blue(` [${repoName}] Setting up git worktree...`));
10754
+ console.log(import_chalk18.default.blue(` [${repoName}] Setting up git worktree...`));
10753
10755
  try {
10754
10756
  const worktreeManager = new GitWorktreeManager(repoAbsPath);
10755
10757
  const worktreePath = await worktreeManager.createWorktree(idea);
10756
10758
  if (worktreePath) workingDir = worktreePath;
10757
10759
  } catch (err) {
10758
- console.log(import_chalk19.default.yellow(` Worktree setup failed: ${err.message}. Using main branch.`));
10760
+ console.log(import_chalk18.default.yellow(` Worktree setup failed: ${err.message}. Using main branch.`));
10759
10761
  }
10760
10762
  } else {
10761
- console.log(import_chalk19.default.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10763
+ console.log(import_chalk18.default.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10762
10764
  }
10763
- const specsDir = path23.join(workingDir, "specs");
10764
- await fs24.ensureDir(specsDir);
10765
+ const specsDir = path22.join(workingDir, "specs");
10766
+ await fs23.ensureDir(specsDir);
10765
10767
  const featureSlug = slugify(idea);
10766
10768
  const { filePath: specFile } = await nextVersionPath(specsDir, featureSlug);
10767
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10768
- console.log(import_chalk19.default.green(` Spec saved: ${path23.relative(repoAbsPath, specFile)}`));
10769
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10770
+ console.log(import_chalk18.default.green(` Spec saved: ${path22.relative(repoAbsPath, specFile)}`));
10769
10771
  let savedDslFile = null;
10770
10772
  if (extractedDsl) {
10771
10773
  const dslExtractorForSave = new DslExtractor(specProvider);
10772
10774
  savedDslFile = await dslExtractorForSave.saveDsl(extractedDsl, specFile);
10773
- console.log(import_chalk19.default.green(` DSL saved: ${path23.relative(repoAbsPath, savedDslFile)}`));
10775
+ console.log(import_chalk18.default.green(` DSL saved: ${path22.relative(repoAbsPath, savedDslFile)}`));
10774
10776
  }
10775
- console.log(import_chalk19.default.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10777
+ console.log(import_chalk18.default.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10776
10778
  try {
10777
10779
  const codegen = new CodeGenerator(codegenProvider, codegenMode);
10778
10780
  await codegen.generateCode(specFile, workingDir, context, {
@@ -10780,29 +10782,29 @@ ${contractContextSection}`;
10780
10782
  dslFilePath: savedDslFile ?? void 0,
10781
10783
  repoType: detectedRepoType
10782
10784
  });
10783
- console.log(import_chalk19.default.green(` Code generation complete.`));
10785
+ console.log(import_chalk18.default.green(` Code generation complete.`));
10784
10786
  } catch (err) {
10785
- console.log(import_chalk19.default.yellow(` Code generation failed: ${err.message}`));
10787
+ console.log(import_chalk18.default.yellow(` Code generation failed: ${err.message}`));
10786
10788
  }
10787
10789
  if (!cliOpts.skipTests && extractedDsl) {
10788
- console.log(import_chalk19.default.blue(` [${repoName}] Generating test skeletons...`));
10790
+ console.log(import_chalk18.default.blue(` [${repoName}] Generating test skeletons...`));
10789
10791
  try {
10790
10792
  const testGen = new TestGenerator(codegenProvider);
10791
10793
  const testFiles = await testGen.generate(extractedDsl, workingDir);
10792
- console.log(import_chalk19.default.green(` ${testFiles.length} test file(s) generated.`));
10794
+ console.log(import_chalk18.default.green(` ${testFiles.length} test file(s) generated.`));
10793
10795
  } catch (err) {
10794
- console.log(import_chalk19.default.yellow(` Test generation failed: ${err.message}`));
10796
+ console.log(import_chalk18.default.yellow(` Test generation failed: ${err.message}`));
10795
10797
  }
10796
10798
  }
10797
10799
  if (!cliOpts.skipErrorFeedback) {
10798
10800
  try {
10799
10801
  await runErrorFeedback(codegenProvider, workingDir, extractedDsl, { maxCycles: 1 });
10800
10802
  } catch (err) {
10801
- console.log(import_chalk19.default.yellow(` Error feedback failed: ${err.message}`));
10803
+ console.log(import_chalk18.default.yellow(` Error feedback failed: ${err.message}`));
10802
10804
  }
10803
10805
  }
10804
10806
  if (!cliOpts.skipReview) {
10805
- console.log(import_chalk19.default.blue(` [${repoName}] Running code review...`));
10807
+ console.log(import_chalk18.default.blue(` [${repoName}] Running code review...`));
10806
10808
  try {
10807
10809
  const reviewer = new CodeReviewer(specProvider);
10808
10810
  const originalDir = process.cwd();
@@ -10814,9 +10816,9 @@ ${contractContextSection}`;
10814
10816
  process.chdir(originalDir);
10815
10817
  }
10816
10818
  await accumulateReviewKnowledge(specProvider, repoAbsPath, reviewResult);
10817
- console.log(import_chalk19.default.green(` Code review complete.`));
10819
+ console.log(import_chalk18.default.green(` Code review complete.`));
10818
10820
  } catch (err) {
10819
- console.log(import_chalk19.default.yellow(` Code review failed: ${err.message}`));
10821
+ console.log(import_chalk18.default.yellow(` Code review failed: ${err.message}`));
10820
10822
  }
10821
10823
  }
10822
10824
  return { dsl: extractedDsl, specFile };
@@ -10839,7 +10841,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10839
10841
  codegenModel: codegenModelName
10840
10842
  });
10841
10843
  const workspaceLoader = new WorkspaceLoader(currentDir);
10842
- console.log(import_chalk19.default.blue("\n[W1] Loading per-repo contexts..."));
10844
+ console.log(import_chalk18.default.blue("\n[W1] Loading per-repo contexts..."));
10843
10845
  const contexts = /* @__PURE__ */ new Map();
10844
10846
  const frontendContexts = /* @__PURE__ */ new Map();
10845
10847
  for (const repo of workspace.repos) {
@@ -10851,27 +10853,27 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10851
10853
  if (repo.role === "frontend" || repo.role === "mobile") {
10852
10854
  const fctx = await loadFrontendContext(repoAbsPath);
10853
10855
  frontendContexts.set(repo.name, fctx);
10854
- console.log(import_chalk19.default.gray(` ${repo.name}: ${fctx.framework} / ${fctx.httpClient} / hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
10856
+ console.log(import_chalk18.default.gray(` ${repo.name}: ${fctx.framework} / ${fctx.httpClient} / hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
10855
10857
  } else {
10856
- console.log(import_chalk19.default.gray(` ${repo.name}: ${ctx.techStack.join(", ") || "unknown"} (${ctx.dependencies.length} deps)`));
10858
+ console.log(import_chalk18.default.gray(` ${repo.name}: ${ctx.techStack.join(", ") || "unknown"} (${ctx.dependencies.length} deps)`));
10857
10859
  }
10858
10860
  } catch (err) {
10859
- console.log(import_chalk19.default.yellow(` ${repo.name}: context load failed \u2014 ${err.message}`));
10861
+ console.log(import_chalk18.default.yellow(` ${repo.name}: context load failed \u2014 ${err.message}`));
10860
10862
  }
10861
10863
  }
10862
- console.log(import_chalk19.default.blue("\n[W2] Decomposing requirement across repos..."));
10864
+ console.log(import_chalk18.default.blue("\n[W2] Decomposing requirement across repos..."));
10863
10865
  const decomposer = new RequirementDecomposer(specProvider);
10864
10866
  let decomposition;
10865
10867
  try {
10866
10868
  decomposition = await decomposer.decompose(idea, workspace, contexts, frontendContexts);
10867
- console.log(import_chalk19.default.green(` Summary: ${decomposition.summary}`));
10868
- console.log(import_chalk19.default.gray(` Repos affected: ${decomposition.repos.map((r) => r.repoName).join(", ")}`));
10869
+ console.log(import_chalk18.default.green(` Summary: ${decomposition.summary}`));
10870
+ console.log(import_chalk18.default.gray(` Repos affected: ${decomposition.repos.map((r) => r.repoName).join(", ")}`));
10869
10871
  if (decomposition.coordinationNotes) {
10870
- console.log(import_chalk19.default.gray(` Coordination: ${decomposition.coordinationNotes}`));
10872
+ console.log(import_chalk18.default.gray(` Coordination: ${decomposition.coordinationNotes}`));
10871
10873
  }
10872
10874
  } catch (err) {
10873
- console.error(import_chalk19.default.red(` Decomposition failed: ${err.message}`));
10874
- console.log(import_chalk19.default.yellow(" Falling back to running all repos independently."));
10875
+ console.error(import_chalk18.default.red(` Decomposition failed: ${err.message}`));
10876
+ console.log(import_chalk18.default.yellow(" Falling back to running all repos independently."));
10875
10877
  decomposition = {
10876
10878
  originalRequirement: idea,
10877
10879
  summary: idea,
@@ -10887,11 +10889,11 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10887
10889
  };
10888
10890
  }
10889
10891
  if (!opts.auto) {
10890
- console.log(import_chalk19.default.cyan("\n[W3] Decomposition Preview:"));
10891
- console.log(import_chalk19.default.cyan("\u2500".repeat(52)));
10892
+ console.log(import_chalk18.default.cyan("\n[W3] Decomposition Preview:"));
10893
+ console.log(import_chalk18.default.cyan("\u2500".repeat(52)));
10892
10894
  for (const r of decomposition.repos) {
10893
- console.log(import_chalk19.default.bold(` ${r.repoName} (${r.role})`));
10894
- console.log(import_chalk19.default.gray(` ${r.specIdea.slice(0, 150)}${r.specIdea.length > 150 ? "..." : ""}`));
10895
+ console.log(import_chalk18.default.bold(` ${r.repoName} (${r.role})`));
10896
+ console.log(import_chalk18.default.gray(` ${r.specIdea.slice(0, 150)}${r.specIdea.length > 150 ? "..." : ""}`));
10895
10897
  if (r.uxDecisions) {
10896
10898
  const ux = r.uxDecisions;
10897
10899
  const uxSummary = [
@@ -10900,13 +10902,13 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10900
10902
  ux.optimisticUpdate ? "optimistic-update" : "",
10901
10903
  ux.errorRollback ? "rollback" : ""
10902
10904
  ].filter(Boolean).join(", ");
10903
- if (uxSummary) console.log(import_chalk19.default.cyan(` UX: ${uxSummary}`));
10905
+ if (uxSummary) console.log(import_chalk18.default.cyan(` UX: ${uxSummary}`));
10904
10906
  }
10905
10907
  if (r.dependsOnRepos.length > 0) {
10906
- console.log(import_chalk19.default.gray(` Depends on: ${r.dependsOnRepos.join(", ")}`));
10908
+ console.log(import_chalk18.default.gray(` Depends on: ${r.dependsOnRepos.join(", ")}`));
10907
10909
  }
10908
10910
  }
10909
- console.log(import_chalk19.default.cyan("\u2500".repeat(52)));
10911
+ console.log(import_chalk18.default.cyan("\u2500".repeat(52)));
10910
10912
  const gate = await (0, import_prompts3.select)({
10911
10913
  message: "Proceed with multi-repo pipeline?",
10912
10914
  choices: [
@@ -10915,24 +10917,24 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10915
10917
  ]
10916
10918
  });
10917
10919
  if (gate === "abort") {
10918
- console.log(import_chalk19.default.yellow(" Aborted."));
10920
+ console.log(import_chalk18.default.yellow(" Aborted."));
10919
10921
  process.exit(0);
10920
10922
  }
10921
10923
  }
10922
10924
  const sortedRepoRequirements = RequirementDecomposer.sortByDependency(decomposition.repos);
10923
10925
  const contractDsls = /* @__PURE__ */ new Map();
10924
- console.log(import_chalk19.default.blue(`
10926
+ console.log(import_chalk18.default.blue(`
10925
10927
  [W4] Running pipeline for ${sortedRepoRequirements.length} repo(s)...`));
10926
10928
  const results = [];
10927
10929
  for (const repoReq of sortedRepoRequirements) {
10928
10930
  const repoConfig = workspace.repos.find((r) => r.name === repoReq.repoName);
10929
10931
  if (!repoConfig) {
10930
- console.log(import_chalk19.default.yellow(` Skipping ${repoReq.repoName} \u2014 not found in workspace config.`));
10932
+ console.log(import_chalk18.default.yellow(` Skipping ${repoReq.repoName} \u2014 not found in workspace config.`));
10931
10933
  results.push({ repoName: repoReq.repoName, status: "skipped", specFile: null, dsl: null, repoAbsPath: "", role: repoReq.role });
10932
10934
  continue;
10933
10935
  }
10934
10936
  const repoAbsPath = workspaceLoader.resolveAbsPath(repoConfig);
10935
- console.log(import_chalk19.default.bold.blue(`
10937
+ console.log(import_chalk18.default.bold.blue(`
10936
10938
  \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`));
10937
10939
  let contractContextSection;
10938
10940
  if (repoReq.dependsOnRepos.length > 0) {
@@ -10940,7 +10942,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10940
10942
  for (const depName of repoReq.dependsOnRepos) {
10941
10943
  const depDsl = contractDsls.get(depName);
10942
10944
  if (depDsl) {
10943
- console.log(import_chalk19.default.gray(` Using API contract from: ${depName}`));
10945
+ console.log(import_chalk18.default.gray(` Using API contract from: ${depName}`));
10944
10946
  const contract = buildFrontendApiContract(depDsl);
10945
10947
  contractParts.push(buildContractContextSection(contract));
10946
10948
  }
@@ -10960,7 +10962,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10960
10962
  frontendContext: frontendCtx
10961
10963
  });
10962
10964
  contractContextSection = void 0;
10963
- console.log(import_chalk19.default.gray(` Frontend context: ${frontendCtx.framework} / ${frontendCtx.httpClient} / ${frontendCtx.uiLibrary}`));
10965
+ console.log(import_chalk18.default.gray(` Frontend context: ${frontendCtx.framework} / ${frontendCtx.httpClient} / ${frontendCtx.uiLibrary}`));
10964
10966
  }
10965
10967
  try {
10966
10968
  const { dsl, specFile } = await runSingleRepoPipelineInWorkspace({
@@ -10977,22 +10979,22 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
10977
10979
  });
10978
10980
  if (repoReq.isContractProvider && dsl) {
10979
10981
  contractDsls.set(repoReq.repoName, dsl);
10980
- console.log(import_chalk19.default.green(` Contract stored for downstream repos.`));
10982
+ console.log(import_chalk18.default.green(` Contract stored for downstream repos.`));
10981
10983
  }
10982
10984
  results.push({ repoName: repoReq.repoName, status: "success", specFile, dsl, repoAbsPath, role: repoReq.role });
10983
- console.log(import_chalk19.default.green(` \u2714 ${repoReq.repoName} complete`));
10985
+ console.log(import_chalk18.default.green(` \u2714 ${repoReq.repoName} complete`));
10984
10986
  } catch (err) {
10985
- console.error(import_chalk19.default.red(` \u2718 ${repoReq.repoName} failed: ${err.message}`));
10987
+ console.error(import_chalk18.default.red(` \u2718 ${repoReq.repoName} failed: ${err.message}`));
10986
10988
  results.push({ repoName: repoReq.repoName, status: "failed", specFile: null, dsl: null, repoAbsPath, role: repoReq.role });
10987
10989
  }
10988
10990
  }
10989
- console.log(import_chalk19.default.bold.green("\n\u2714 Multi-repo pipeline complete!"));
10990
- console.log(import_chalk19.default.gray(` Workspace: ${workspace.name}`));
10991
- console.log(import_chalk19.default.gray(` Requirement: ${idea}`));
10991
+ console.log(import_chalk18.default.bold.green("\n\u2714 Multi-repo pipeline complete!"));
10992
+ console.log(import_chalk18.default.gray(` Workspace: ${workspace.name}`));
10993
+ console.log(import_chalk18.default.gray(` Requirement: ${idea}`));
10992
10994
  console.log();
10993
10995
  for (const r of results) {
10994
- const icon = r.status === "success" ? import_chalk19.default.green("\u2714") : r.status === "failed" ? import_chalk19.default.red("\u2718") : import_chalk19.default.gray("\u2212");
10995
- const specInfo = r.specFile ? import_chalk19.default.gray(` \u2192 ${r.specFile}`) : "";
10996
+ const icon = r.status === "success" ? import_chalk18.default.green("\u2714") : r.status === "failed" ? import_chalk18.default.red("\u2718") : import_chalk18.default.gray("\u2212");
10997
+ const specInfo = r.specFile ? import_chalk18.default.gray(` \u2192 ${r.specFile}`) : "";
10996
10998
  console.log(` ${icon} ${r.repoName} (${r.status})${specInfo}`);
10997
10999
  }
10998
11000
  return results;
@@ -11000,18 +11002,18 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
11000
11002
  var workspaceCmd = program.command("workspace").description("Manage multi-repo workspace configuration");
11001
11003
  workspaceCmd.command("init").description(`Interactive workspace setup \u2014 creates ${WORKSPACE_CONFIG_FILE}`).action(async () => {
11002
11004
  const currentDir = process.cwd();
11003
- const configPath = path23.join(currentDir, WORKSPACE_CONFIG_FILE);
11004
- if (await fs24.pathExists(configPath)) {
11005
+ const configPath = path22.join(currentDir, WORKSPACE_CONFIG_FILE);
11006
+ if (await fs23.pathExists(configPath)) {
11005
11007
  const overwrite = await (0, import_prompts3.confirm)({
11006
11008
  message: `${WORKSPACE_CONFIG_FILE} already exists. Overwrite?`,
11007
11009
  default: false
11008
11010
  });
11009
11011
  if (!overwrite) {
11010
- console.log(import_chalk19.default.gray(" Cancelled."));
11012
+ console.log(import_chalk18.default.gray(" Cancelled."));
11011
11013
  return;
11012
11014
  }
11013
11015
  }
11014
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Workspace Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11016
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Workspace Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11015
11017
  const workspaceName = await (0, import_prompts3.input)({
11016
11018
  message: "Workspace name:",
11017
11019
  validate: (v2) => v2.trim().length > 0 || "Name cannot be empty"
@@ -11025,11 +11027,11 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11025
11027
  const workspaceLoader = new WorkspaceLoader(currentDir);
11026
11028
  const detected = await workspaceLoader.autoDetect();
11027
11029
  if (detected.length === 0) {
11028
- console.log(import_chalk19.default.yellow(" No recognizable repos found in sibling directories."));
11030
+ console.log(import_chalk18.default.yellow(" No recognizable repos found in sibling directories."));
11029
11031
  } else {
11030
- console.log(import_chalk19.default.cyan("\n Detected repos:"));
11032
+ console.log(import_chalk18.default.cyan("\n Detected repos:"));
11031
11033
  for (const r of detected) {
11032
- console.log(import_chalk19.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11034
+ console.log(import_chalk18.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11033
11035
  }
11034
11036
  const keepAll = await (0, import_prompts3.confirm)({
11035
11037
  message: `Include all ${detected.length} detected repo(s)?`,
@@ -11046,7 +11048,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11046
11048
  if (keep) repos.push(r);
11047
11049
  }
11048
11050
  }
11049
- console.log(import_chalk19.default.green(` \u2714 ${repos.length} repo(s) added from auto-scan.`));
11051
+ console.log(import_chalk18.default.green(` \u2714 ${repos.length} repo(s) added from auto-scan.`));
11050
11052
  }
11051
11053
  }
11052
11054
  const repoTypeChoices = [
@@ -11068,7 +11070,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11068
11070
  default: repos.length === 0
11069
11071
  });
11070
11072
  while (addMore) {
11071
- console.log(import_chalk19.default.cyan(`
11073
+ console.log(import_chalk18.default.cyan(`
11072
11074
  Adding repo #${repos.length + 1}`));
11073
11075
  const repoName = await (0, import_prompts3.input)({
11074
11076
  message: "Repo name (e.g. api, web, app):",
@@ -11082,16 +11084,16 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11082
11084
  message: `Relative path to "${repoName}" from here (default: ./${repoName}):`,
11083
11085
  default: `./${repoName}`
11084
11086
  });
11085
- const absPath = path23.resolve(currentDir, repoPath);
11087
+ const absPath = path22.resolve(currentDir, repoPath);
11086
11088
  let detectedType = "unknown";
11087
11089
  let detectedRole = "shared";
11088
- if (await fs24.pathExists(absPath)) {
11090
+ if (await fs23.pathExists(absPath)) {
11089
11091
  const { type, role } = await detectRepoType(absPath);
11090
11092
  detectedType = type;
11091
11093
  detectedRole = role;
11092
- console.log(import_chalk19.default.gray(` Auto-detected: type=${type}, role=${role}`));
11094
+ console.log(import_chalk18.default.gray(` Auto-detected: type=${type}, role=${role}`));
11093
11095
  } else {
11094
- console.log(import_chalk19.default.yellow(` Path "${absPath}" not found \u2014 type/role will be manual.`));
11096
+ console.log(import_chalk18.default.yellow(` Path "${absPath}" not found \u2014 type/role will be manual.`));
11095
11097
  }
11096
11098
  const repoType = await (0, import_prompts3.select)({
11097
11099
  message: `Repo type for "${repoName}":`,
@@ -11114,53 +11116,53 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11114
11116
  type: repoType,
11115
11117
  role: repoRole
11116
11118
  });
11117
- console.log(import_chalk19.default.green(` \u2714 Added: ${repoName} (${repoRole}, ${repoType})`));
11119
+ console.log(import_chalk18.default.green(` \u2714 Added: ${repoName} (${repoRole}, ${repoType})`));
11118
11120
  addMore = await (0, import_prompts3.confirm)({
11119
11121
  message: "Add another repo?",
11120
11122
  default: false
11121
11123
  });
11122
11124
  }
11123
11125
  const workspaceConfig = { name: workspaceName, repos };
11124
- console.log(import_chalk19.default.cyan("\n Workspace summary:"));
11125
- console.log(import_chalk19.default.gray(` Name: ${workspaceName}`));
11126
+ console.log(import_chalk18.default.cyan("\n Workspace summary:"));
11127
+ console.log(import_chalk18.default.gray(` Name: ${workspaceName}`));
11126
11128
  for (const r of repos) {
11127
- console.log(import_chalk19.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11129
+ console.log(import_chalk18.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
11128
11130
  }
11129
11131
  const ok = await (0, import_prompts3.confirm)({ message: `Save to ${WORKSPACE_CONFIG_FILE}?`, default: true });
11130
11132
  if (!ok) {
11131
- console.log(import_chalk19.default.gray(" Cancelled."));
11133
+ console.log(import_chalk18.default.gray(" Cancelled."));
11132
11134
  return;
11133
11135
  }
11134
11136
  const loader = new WorkspaceLoader(currentDir);
11135
11137
  const saved = await loader.save(workspaceConfig);
11136
- console.log(import_chalk19.default.green(`
11138
+ console.log(import_chalk18.default.green(`
11137
11139
  \u2714 Workspace saved: ${saved}`));
11138
- console.log(import_chalk19.default.gray(` Run \`ai-spec create "your feature"\` \u2014 workspace mode will activate automatically.`));
11140
+ console.log(import_chalk18.default.gray(` Run \`ai-spec create "your feature"\` \u2014 workspace mode will activate automatically.`));
11139
11141
  });
11140
11142
  workspaceCmd.command("status").description("Show current workspace configuration").action(async () => {
11141
11143
  const currentDir = process.cwd();
11142
11144
  const loader = new WorkspaceLoader(currentDir);
11143
11145
  const config2 = await loader.load();
11144
11146
  if (!config2) {
11145
- console.log(import_chalk19.default.yellow(`No ${WORKSPACE_CONFIG_FILE} found in ${currentDir}`));
11146
- console.log(import_chalk19.default.gray(" Run `ai-spec workspace init` to create one."));
11147
+ console.log(import_chalk18.default.yellow(`No ${WORKSPACE_CONFIG_FILE} found in ${currentDir}`));
11148
+ console.log(import_chalk18.default.gray(" Run `ai-spec workspace init` to create one."));
11147
11149
  return;
11148
11150
  }
11149
- console.log(import_chalk19.default.bold(`
11151
+ console.log(import_chalk18.default.bold(`
11150
11152
  Workspace: ${config2.name}`));
11151
- console.log(import_chalk19.default.gray(` Config: ${path23.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11152
- console.log(import_chalk19.default.gray(` Repos (${config2.repos.length}):
11153
+ console.log(import_chalk18.default.gray(` Config: ${path22.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11154
+ console.log(import_chalk18.default.gray(` Repos (${config2.repos.length}):
11153
11155
  `));
11154
11156
  for (const repo of config2.repos) {
11155
11157
  const absPath = loader.resolveAbsPath(repo);
11156
- const exists = await fs24.pathExists(absPath);
11157
- const status = exists ? import_chalk19.default.green("found") : import_chalk19.default.red("not found");
11158
+ const exists = await fs23.pathExists(absPath);
11159
+ const status = exists ? import_chalk18.default.green("found") : import_chalk18.default.red("not found");
11158
11160
  console.log(
11159
- ` ${import_chalk19.default.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
11161
+ ` ${import_chalk18.default.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
11160
11162
  );
11161
- console.log(import_chalk19.default.gray(` path: ${absPath}`));
11163
+ console.log(import_chalk18.default.gray(` path: ${absPath}`));
11162
11164
  if (repo.constitution) {
11163
- console.log(import_chalk19.default.green(` constitution: found`));
11165
+ console.log(import_chalk18.default.green(` constitution: found`));
11164
11166
  }
11165
11167
  }
11166
11168
  });
@@ -11177,30 +11179,30 @@ program.command("update").description("Update an existing spec with a change req
11177
11179
  const modelName = opts.model || config2.model || DEFAULT_MODELS[providerName];
11178
11180
  const apiKey = await resolveApiKey(providerName, opts.key);
11179
11181
  const provider = createProvider(providerName, apiKey, modelName);
11180
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec update \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11181
- console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
11182
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 ai-spec update \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11183
+ console.log(import_chalk18.default.gray(` Provider: ${providerName}/${modelName}`));
11182
11184
  const updateRunId = generateRunId();
11183
11185
  const updateSnapshot = new RunSnapshot(currentDir, updateRunId);
11184
11186
  setActiveSnapshot(updateSnapshot);
11185
11187
  const updateLogger = new RunLogger(currentDir, updateRunId, { provider: providerName, model: modelName });
11186
11188
  setActiveLogger(updateLogger);
11187
- console.log(import_chalk19.default.gray(` Run ID: ${updateRunId}`));
11189
+ console.log(import_chalk18.default.gray(` Run ID: ${updateRunId}`));
11188
11190
  let specPath = opts.spec ?? null;
11189
11191
  if (!specPath) {
11190
- const specsDir = path23.join(currentDir, "specs");
11192
+ const specsDir = path22.join(currentDir, "specs");
11191
11193
  const latest = await SpecUpdater.findLatestSpec(specsDir);
11192
11194
  if (!latest) {
11193
- console.error(import_chalk19.default.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11195
+ console.error(import_chalk18.default.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11194
11196
  process.exit(1);
11195
11197
  }
11196
11198
  specPath = latest.filePath;
11197
- console.log(import_chalk19.default.gray(` Using spec: ${path23.relative(currentDir, specPath)} (v${latest.version})`));
11199
+ console.log(import_chalk18.default.gray(` Using spec: ${path22.relative(currentDir, specPath)} (v${latest.version})`));
11198
11200
  }
11199
- console.log(import_chalk19.default.gray(" Loading project context..."));
11201
+ console.log(import_chalk18.default.gray(" Loading project context..."));
11200
11202
  const loader = new ContextLoader(currentDir);
11201
11203
  const context = await loader.loadProjectContext();
11202
11204
  if (context.constitution && context.constitution.length > 6e3) {
11203
- console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
11205
+ console.log(import_chalk18.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
11204
11206
  }
11205
11207
  const { detectRepoType: _detectRepoType } = await Promise.resolve().then(() => (init_workspace_loader(), workspace_loader_exports));
11206
11208
  const { type: repoType } = await _detectRepoType(currentDir);
@@ -11212,19 +11214,19 @@ program.command("update").description("Update an existing spec with a change req
11212
11214
  repoType
11213
11215
  });
11214
11216
  } catch (err) {
11215
- console.error(import_chalk19.default.red(` Update failed: ${err.message}`));
11217
+ console.error(import_chalk18.default.red(` Update failed: ${err.message}`));
11216
11218
  process.exit(1);
11217
11219
  }
11218
- console.log(import_chalk19.default.green(`
11219
- \u2714 Spec updated \u2192 v${result.newVersion}: ${path23.relative(currentDir, result.newSpecPath)}`));
11220
+ console.log(import_chalk18.default.green(`
11221
+ \u2714 Spec updated \u2192 v${result.newVersion}: ${path22.relative(currentDir, result.newSpecPath)}`));
11220
11222
  if (result.newDslPath) {
11221
- console.log(import_chalk19.default.green(` \u2714 DSL updated: ${path23.relative(currentDir, result.newDslPath)}`));
11223
+ console.log(import_chalk18.default.green(` \u2714 DSL updated: ${path22.relative(currentDir, result.newDslPath)}`));
11222
11224
  }
11223
11225
  if (result.affectedFiles.length > 0) {
11224
- console.log(import_chalk19.default.cyan("\n Affected files:"));
11226
+ console.log(import_chalk18.default.cyan("\n Affected files:"));
11225
11227
  for (const f of result.affectedFiles) {
11226
- const icon = f.action === "create" ? import_chalk19.default.green("+") : import_chalk19.default.yellow("~");
11227
- console.log(` ${icon} ${f.file}: ${import_chalk19.default.gray(f.description)}`);
11228
+ const icon = f.action === "create" ? import_chalk18.default.green("+") : import_chalk18.default.yellow("~");
11229
+ console.log(` ${icon} ${f.file}: ${import_chalk18.default.gray(f.description)}`);
11228
11230
  }
11229
11231
  }
11230
11232
  if (opts.codegen && result.affectedFiles.length > 0) {
@@ -11232,9 +11234,9 @@ program.command("update").description("Update an existing spec with a change req
11232
11234
  const codegenModelName = opts.codegenModel || config2.codegenModel || DEFAULT_MODELS[codegenProviderName];
11233
11235
  const codegenApiKey = opts.codegenKey ?? (codegenProviderName === providerName ? apiKey : await resolveApiKey(codegenProviderName, opts.codegenKey));
11234
11236
  const codegenProvider = createProvider(codegenProviderName, codegenApiKey, codegenModelName);
11235
- console.log(import_chalk19.default.blue("\n Regenerating affected files..."));
11237
+ console.log(import_chalk18.default.blue("\n Regenerating affected files..."));
11236
11238
  const codeGenerator = new CodeGenerator(codegenProvider, "api");
11237
- const specContent = await fs24.readFile(result.newSpecPath, "utf-8");
11239
+ const specContent = await fs23.readFile(result.newSpecPath, "utf-8");
11238
11240
  const constitutionSection = context.constitution ? `
11239
11241
  === Project Constitution (MUST follow) ===
11240
11242
  ${context.constitution}
@@ -11245,10 +11247,10 @@ ${JSON.stringify(result.updatedDsl, null, 2).slice(0, 3e3)}
11245
11247
  ` : "";
11246
11248
  updateLogger.stageStart("update_codegen");
11247
11249
  for (const affected of result.affectedFiles) {
11248
- const fullPath = path23.join(currentDir, affected.file);
11250
+ const fullPath = path22.join(currentDir, affected.file);
11249
11251
  let existing = "";
11250
11252
  try {
11251
- existing = await fs24.readFile(fullPath, "utf-8");
11253
+ existing = await fs23.readFile(fullPath, "utf-8");
11252
11254
  } catch {
11253
11255
  }
11254
11256
  const codePrompt = `Apply this change to the file.
@@ -11262,23 +11264,23 @@ ${specContent}
11262
11264
  ${constitutionSection}${dslSection}
11263
11265
  === ${existing ? "Current File (return the FULL updated content)" : "New File"} ===
11264
11266
  ${existing || "Create from scratch."}`;
11265
- process.stdout.write(` ${existing ? import_chalk19.default.yellow("~") : import_chalk19.default.green("+")} ${affected.file}... `);
11267
+ process.stdout.write(` ${existing ? import_chalk18.default.yellow("~") : import_chalk18.default.green("+")} ${affected.file}... `);
11266
11268
  try {
11267
11269
  const { getCodeGenSystemPrompt: _getPrompt } = await Promise.resolve().then(() => (init_codegen_prompt(), codegen_prompt_exports));
11268
11270
  const raw = await codegenProvider.generate(codePrompt, _getPrompt(repoType));
11269
11271
  const content = raw.replace(/^```\w*\n?/gm, "").replace(/\n?```$/gm, "").trim();
11270
- await fs24.ensureDir(path23.dirname(fullPath));
11272
+ await fs23.ensureDir(path22.dirname(fullPath));
11271
11273
  await updateSnapshot.snapshotFile(fullPath);
11272
- await fs24.writeFile(fullPath, content, "utf-8");
11274
+ await fs23.writeFile(fullPath, content, "utf-8");
11273
11275
  updateLogger.fileWritten(affected.file);
11274
- console.log(import_chalk19.default.green("\u2714"));
11276
+ console.log(import_chalk18.default.green("\u2714"));
11275
11277
  } catch (err) {
11276
11278
  updateLogger.stageFail("update_codegen", `${affected.file}: ${err.message}`);
11277
- console.log(import_chalk19.default.red(`\u2718 ${err.message}`));
11279
+ console.log(import_chalk18.default.red(`\u2718 ${err.message}`));
11278
11280
  }
11279
11281
  }
11280
11282
  updateLogger.stageEnd("update_codegen", { filesUpdated: result.affectedFiles.length });
11281
- const updatedSpecContent = await fs24.readFile(result.newSpecPath, "utf-8").catch(() => "");
11283
+ const updatedSpecContent = await fs23.readFile(result.newSpecPath, "utf-8").catch(() => "");
11282
11284
  if (updatedSpecContent) {
11283
11285
  const updateReviewer = new CodeReviewer(provider, currentDir);
11284
11286
  const reviewResult = await updateReviewer.reviewCode(updatedSpecContent, result.newSpecPath).catch(() => "");
@@ -11290,13 +11292,13 @@ ${existing || "Create from scratch."}`;
11290
11292
  updateLogger.finish();
11291
11293
  updateLogger.printSummary();
11292
11294
  if (updateSnapshot.fileCount > 0) {
11293
- console.log(import_chalk19.default.gray(` To undo changes: ai-spec restore ${updateRunId}`));
11295
+ console.log(import_chalk18.default.gray(` To undo changes: ai-spec restore ${updateRunId}`));
11294
11296
  }
11295
11297
  if (!opts.codegen && result.affectedFiles.length > 0) {
11296
- console.log(import_chalk19.default.blue("\n Next steps:"));
11297
- console.log(import_chalk19.default.gray(` \u2022 Re-run with --codegen to regenerate affected files automatically`));
11298
- console.log(import_chalk19.default.gray(` \u2022 Or update files manually based on the affected files list above`));
11299
- console.log(import_chalk19.default.gray(` \u2022 Run \`ai-spec mock\` to refresh the mock server with the new DSL`));
11298
+ console.log(import_chalk18.default.blue("\n Next steps:"));
11299
+ console.log(import_chalk18.default.gray(` \u2022 Re-run with --codegen to regenerate affected files automatically`));
11300
+ console.log(import_chalk18.default.gray(` \u2022 Or update files manually based on the affected files list above`));
11301
+ console.log(import_chalk18.default.gray(` \u2022 Run \`ai-spec mock\` to refresh the mock server with the new DSL`));
11300
11302
  }
11301
11303
  });
11302
11304
  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) => {
@@ -11305,19 +11307,19 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11305
11307
  if (!dslPath) {
11306
11308
  dslPath = await findLatestDslFile(currentDir);
11307
11309
  if (!dslPath) {
11308
- console.error(import_chalk19.default.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11310
+ console.error(import_chalk18.default.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11309
11311
  process.exit(1);
11310
11312
  }
11311
- console.log(import_chalk19.default.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11313
+ console.log(import_chalk18.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11312
11314
  }
11313
11315
  let dsl;
11314
11316
  try {
11315
- dsl = await fs24.readJson(dslPath);
11317
+ dsl = await fs23.readJson(dslPath);
11316
11318
  } catch (err) {
11317
- console.error(import_chalk19.default.red(` Failed to read DSL: ${err.message}`));
11319
+ console.error(import_chalk18.default.red(` Failed to read DSL: ${err.message}`));
11318
11320
  process.exit(1);
11319
11321
  }
11320
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11322
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 ai-spec export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11321
11323
  const format = opts.format === "json" ? "json" : "yaml";
11322
11324
  const serverUrl = opts.server || "http://localhost:3000";
11323
11325
  try {
@@ -11326,31 +11328,31 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11326
11328
  serverUrl,
11327
11329
  outputPath: opts.output
11328
11330
  });
11329
- const rel = path23.relative(currentDir, outputPath);
11330
- console.log(import_chalk19.default.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
11331
- console.log(import_chalk19.default.gray(` Feature : ${dsl.feature.title}`));
11332
- console.log(import_chalk19.default.gray(` Endpoints: ${dsl.endpoints.length}`));
11333
- console.log(import_chalk19.default.gray(` Models : ${dsl.models.length}`));
11334
- console.log(import_chalk19.default.gray(` Server : ${serverUrl}`));
11335
- console.log(import_chalk19.default.blue("\n Next steps:"));
11336
- console.log(import_chalk19.default.gray(` \u2022 Import ${rel} into Postman / Insomnia / Swagger UI`));
11337
- console.log(import_chalk19.default.gray(` \u2022 Use openapi-generator to generate client SDKs`));
11331
+ const rel = path22.relative(currentDir, outputPath);
11332
+ console.log(import_chalk18.default.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
11333
+ console.log(import_chalk18.default.gray(` Feature : ${dsl.feature.title}`));
11334
+ console.log(import_chalk18.default.gray(` Endpoints: ${dsl.endpoints.length}`));
11335
+ console.log(import_chalk18.default.gray(` Models : ${dsl.models.length}`));
11336
+ console.log(import_chalk18.default.gray(` Server : ${serverUrl}`));
11337
+ console.log(import_chalk18.default.blue("\n Next steps:"));
11338
+ console.log(import_chalk18.default.gray(` \u2022 Import ${rel} into Postman / Insomnia / Swagger UI`));
11339
+ console.log(import_chalk18.default.gray(` \u2022 Use openapi-generator to generate client SDKs`));
11338
11340
  } catch (err) {
11339
- console.error(import_chalk19.default.red(` Export failed: ${err.message}`));
11341
+ console.error(import_chalk18.default.red(` Export failed: ${err.message}`));
11340
11342
  process.exit(1);
11341
11343
  }
11342
11344
  });
11343
11345
  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) => {
11344
11346
  const currentDir = process.cwd();
11345
11347
  const port = parseInt(opts.port, 10) || 3001;
11346
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec mock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11348
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 ai-spec mock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11347
11349
  if (opts.restore) {
11348
- const frontendDir = opts.frontend ? path23.resolve(opts.frontend) : currentDir;
11350
+ const frontendDir = opts.frontend ? path22.resolve(opts.frontend) : currentDir;
11349
11351
  const r = await restoreMockProxy(frontendDir);
11350
11352
  if (r.restored) {
11351
- console.log(import_chalk19.default.green(" \u2714 Proxy restored and mock server stopped."));
11353
+ console.log(import_chalk18.default.green(" \u2714 Proxy restored and mock server stopped."));
11352
11354
  } else {
11353
- console.log(import_chalk19.default.yellow(` ${r.note ?? "Nothing to restore."}`));
11355
+ console.log(import_chalk18.default.yellow(` ${r.note ?? "Nothing to restore."}`));
11354
11356
  }
11355
11357
  return;
11356
11358
  }
@@ -11358,32 +11360,32 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11358
11360
  const workspaceLoader = new WorkspaceLoader(currentDir);
11359
11361
  const workspaceConfig = await workspaceLoader.load();
11360
11362
  if (!workspaceConfig) {
11361
- console.error(import_chalk19.default.red(` No ${WORKSPACE_CONFIG_FILE} found. Run \`ai-spec workspace init\` first.`));
11363
+ console.error(import_chalk18.default.red(` No ${WORKSPACE_CONFIG_FILE} found. Run \`ai-spec workspace init\` first.`));
11362
11364
  process.exit(1);
11363
11365
  }
11364
11366
  const backendRepos = workspaceConfig.repos.filter((r) => r.role === "backend");
11365
11367
  if (backendRepos.length === 0) {
11366
- console.log(import_chalk19.default.yellow(" No backend repos found in workspace."));
11368
+ console.log(import_chalk18.default.yellow(" No backend repos found in workspace."));
11367
11369
  return;
11368
11370
  }
11369
11371
  for (const repo of backendRepos) {
11370
11372
  const repoAbsPath = workspaceLoader.resolveAbsPath(repo);
11371
- console.log(import_chalk19.default.cyan(`
11373
+ console.log(import_chalk18.default.cyan(`
11372
11374
  Repo: ${repo.name} (${repoAbsPath})`));
11373
11375
  const dslFile = await findLatestDslFile(repoAbsPath);
11374
11376
  if (!dslFile) {
11375
- console.log(import_chalk19.default.yellow(` No DSL file found \u2014 skipping.`));
11377
+ console.log(import_chalk18.default.yellow(` No DSL file found \u2014 skipping.`));
11376
11378
  continue;
11377
11379
  }
11378
- const dsl2 = await fs24.readJson(dslFile);
11380
+ const dsl2 = await fs23.readJson(dslFile);
11379
11381
  const result2 = await generateMockAssets(dsl2, repoAbsPath, {
11380
11382
  port,
11381
11383
  msw: opts.msw,
11382
11384
  proxy: opts.proxy
11383
11385
  });
11384
11386
  for (const f of result2.files) {
11385
- console.log(import_chalk19.default.green(` \u2714 ${f.path}`));
11386
- console.log(import_chalk19.default.gray(` ${f.description}`));
11387
+ console.log(import_chalk18.default.green(` \u2714 ${f.path}`));
11388
+ console.log(import_chalk18.default.gray(` ${f.description}`));
11387
11389
  }
11388
11390
  }
11389
11391
  return;
@@ -11393,19 +11395,19 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11393
11395
  dslPath = await findLatestDslFile(currentDir);
11394
11396
  if (!dslPath) {
11395
11397
  console.error(
11396
- import_chalk19.default.red(
11398
+ import_chalk18.default.red(
11397
11399
  " No .dsl.json file found in .ai-spec/. Run `ai-spec create` first or use --dsl <path>."
11398
11400
  )
11399
11401
  );
11400
11402
  process.exit(1);
11401
11403
  }
11402
- console.log(import_chalk19.default.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11404
+ console.log(import_chalk18.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11403
11405
  }
11404
11406
  let dsl;
11405
11407
  try {
11406
- dsl = await fs24.readJson(dslPath);
11408
+ dsl = await fs23.readJson(dslPath);
11407
11409
  } catch (err) {
11408
- console.error(import_chalk19.default.red(` Failed to read DSL file: ${err.message}`));
11410
+ console.error(import_chalk18.default.red(` Failed to read DSL file: ${err.message}`));
11409
11411
  process.exit(1);
11410
11412
  }
11411
11413
  const result = await generateMockAssets(dsl, currentDir, {
@@ -11413,58 +11415,58 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11413
11415
  msw: opts.msw,
11414
11416
  proxy: opts.proxy
11415
11417
  });
11416
- console.log(import_chalk19.default.green(`
11418
+ console.log(import_chalk18.default.green(`
11417
11419
  \u2714 Mock assets generated (${result.files.length} file(s)):`));
11418
11420
  for (const f of result.files) {
11419
- console.log(import_chalk19.default.green(` ${f.path}`));
11420
- console.log(import_chalk19.default.gray(` ${f.description}`));
11421
+ console.log(import_chalk18.default.green(` ${f.path}`));
11422
+ console.log(import_chalk18.default.gray(` ${f.description}`));
11421
11423
  }
11422
11424
  if (opts.serve) {
11423
- const serverJsPath = path23.join(currentDir, "mock", "server.js");
11424
- if (!await fs24.pathExists(serverJsPath)) {
11425
- console.error(import_chalk19.default.red(" mock/server.js not found \u2014 generation may have failed."));
11425
+ const serverJsPath = path22.join(currentDir, "mock", "server.js");
11426
+ if (!await fs23.pathExists(serverJsPath)) {
11427
+ console.error(import_chalk18.default.red(" mock/server.js not found \u2014 generation may have failed."));
11426
11428
  process.exit(1);
11427
11429
  }
11428
11430
  const pid = startMockServerBackground(serverJsPath, port);
11429
- console.log(import_chalk19.default.green(`
11431
+ console.log(import_chalk18.default.green(`
11430
11432
  \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${port}`));
11431
11433
  if (opts.frontend) {
11432
- const frontendDir = path23.resolve(opts.frontend);
11434
+ const frontendDir = path22.resolve(opts.frontend);
11433
11435
  const proxyResult = await applyMockProxy(frontendDir, port, dsl.endpoints);
11434
11436
  await saveMockServerPid(frontendDir, pid);
11435
11437
  if (proxyResult.applied) {
11436
- console.log(import_chalk19.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
11437
- console.log(import_chalk19.default.bold.cyan(`
11438
+ console.log(import_chalk18.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
11439
+ console.log(import_chalk18.default.bold.cyan(`
11438
11440
  Ready! Open a new terminal and run:`));
11439
- console.log(import_chalk19.default.white(` cd ${frontendDir}`));
11440
- console.log(import_chalk19.default.white(` ${proxyResult.devCommand}`));
11441
- console.log(import_chalk19.default.gray(`
11441
+ console.log(import_chalk18.default.white(` cd ${frontendDir}`));
11442
+ console.log(import_chalk18.default.white(` ${proxyResult.devCommand}`));
11443
+ console.log(import_chalk18.default.gray(`
11442
11444
  When done: ai-spec mock --restore --frontend ${frontendDir}`));
11443
11445
  } else {
11444
- console.log(import_chalk19.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
11445
- if (proxyResult.note) console.log(import_chalk19.default.gray(` ${proxyResult.note}`));
11446
+ console.log(import_chalk18.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
11447
+ if (proxyResult.note) console.log(import_chalk18.default.gray(` ${proxyResult.note}`));
11446
11448
  }
11447
11449
  } else {
11448
- console.log(import_chalk19.default.gray(` Tip: use --frontend <path> to also auto-patch your frontend proxy config.`));
11449
- console.log(import_chalk19.default.gray(` Mock server: http://localhost:${port}`));
11450
+ console.log(import_chalk18.default.gray(` Tip: use --frontend <path> to also auto-patch your frontend proxy config.`));
11451
+ console.log(import_chalk18.default.gray(` Mock server: http://localhost:${port}`));
11450
11452
  }
11451
11453
  return;
11452
11454
  }
11453
- console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11454
- console.log(import_chalk19.default.white(` 1. Install express (if not already):`));
11455
- console.log(import_chalk19.default.gray(` npm install --save-dev express`));
11456
- console.log(import_chalk19.default.white(` 2. Start mock server:`));
11457
- console.log(import_chalk19.default.gray(` node mock/server.js`));
11458
- console.log(import_chalk19.default.gray(` # or: ai-spec mock --serve --frontend <path-to-frontend>`));
11459
- console.log(import_chalk19.default.white(` 3. Configure your frontend to proxy API calls to:`));
11460
- console.log(import_chalk19.default.gray(` http://localhost:${port}`));
11455
+ console.log(import_chalk18.default.blue("\n\u2500\u2500\u2500 Quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11456
+ console.log(import_chalk18.default.white(` 1. Install express (if not already):`));
11457
+ console.log(import_chalk18.default.gray(` npm install --save-dev express`));
11458
+ console.log(import_chalk18.default.white(` 2. Start mock server:`));
11459
+ console.log(import_chalk18.default.gray(` node mock/server.js`));
11460
+ console.log(import_chalk18.default.gray(` # or: ai-spec mock --serve --frontend <path-to-frontend>`));
11461
+ console.log(import_chalk18.default.white(` 3. Configure your frontend to proxy API calls to:`));
11462
+ console.log(import_chalk18.default.gray(` http://localhost:${port}`));
11461
11463
  if (opts.proxy) {
11462
- console.log(import_chalk19.default.gray(` (See the generated proxy config file for framework-specific instructions)`));
11464
+ console.log(import_chalk18.default.gray(` (See the generated proxy config file for framework-specific instructions)`));
11463
11465
  }
11464
11466
  if (opts.msw) {
11465
- console.log(import_chalk19.default.white(` 4. MSW: import and start the worker in your app entry:`));
11466
- console.log(import_chalk19.default.gray(` import { worker } from './mocks/browser';`));
11467
- console.log(import_chalk19.default.gray(` if (process.env.NODE_ENV === 'development') worker.start();`));
11467
+ console.log(import_chalk18.default.white(` 4. MSW: import and start the worker in your app entry:`));
11468
+ console.log(import_chalk18.default.gray(` import { worker } from './mocks/browser';`));
11469
+ console.log(import_chalk18.default.gray(` if (process.env.NODE_ENV === 'development') worker.start();`));
11468
11470
  }
11469
11471
  });
11470
11472
  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) => {
@@ -11480,34 +11482,26 @@ program.command("learn").description("Append a lesson or engineering decision di
11480
11482
  }
11481
11483
  const result = await appendDirectLesson(currentDir, lesson.trim());
11482
11484
  if (result.appended) {
11483
- console.log(import_chalk19.default.green(`
11485
+ console.log(import_chalk18.default.green(`
11484
11486
  \u2714 Lesson appended to constitution \xA79`));
11485
- console.log(import_chalk19.default.gray(` File: .ai-spec-constitution.md`));
11487
+ console.log(import_chalk18.default.gray(` File: .ai-spec-constitution.md`));
11486
11488
  } else {
11487
- console.log(import_chalk19.default.yellow(`
11489
+ console.log(import_chalk18.default.yellow(`
11488
11490
  \u26A0 Not appended: ${result.reason}`));
11489
11491
  }
11490
11492
  });
11491
11493
  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) => {
11492
11494
  const currentDir = process.cwd();
11493
11495
  const snapshot = new RunSnapshot(currentDir, runId);
11494
- console.log(import_chalk19.default.blue(`Restoring run: ${runId}...`));
11496
+ console.log(import_chalk18.default.blue(`Restoring run: ${runId}...`));
11495
11497
  const restored = await snapshot.restore();
11496
11498
  if (restored.length === 0) {
11497
- console.log(import_chalk19.default.yellow(" No backup found for this run ID."));
11499
+ console.log(import_chalk18.default.yellow(" No backup found for this run ID."));
11498
11500
  } else {
11499
- restored.forEach((f) => console.log(import_chalk19.default.green(` \u2714 restored: ${f}`)));
11500
- console.log(import_chalk19.default.bold.green(`
11501
+ restored.forEach((f) => console.log(import_chalk18.default.green(` \u2714 restored: ${f}`)));
11502
+ console.log(import_chalk18.default.bold.green(`
11501
11503
  \u2714 ${restored.length} file(s) restored.`));
11502
11504
  }
11503
11505
  });
11504
- if (process.argv.length <= 2) {
11505
- (async () => {
11506
- const currentDir = process.cwd();
11507
- const config2 = await loadConfig(currentDir);
11508
- await printWelcome(currentDir, config2);
11509
- })();
11510
- } else {
11511
- program.parse();
11512
- }
11506
+ program.parse();
11513
11507
  //# sourceMappingURL=index.js.map