ai-spec-dev 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -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,8 +488,8 @@ 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"));
491
+ var path22 = __toESM(require("path"));
492
+ var fs23 = __toESM(require("fs-extra"));
493
493
  var import_chalk19 = __toESM(require("chalk"));
494
494
  var dotenv = __toESM(require("dotenv"));
495
495
  var import_prompts3 = require("@inquirer/prompts");
@@ -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
  }
@@ -6127,6 +6127,16 @@ var RunLogger = class {
6127
6127
  this.log.errors.push(`[${event}] ${error}`);
6128
6128
  this.flush();
6129
6129
  }
6130
+ /** Record the prompt hash for this run (call once at run start). */
6131
+ setPromptHash(hash) {
6132
+ this.log.promptHash = hash;
6133
+ this.flush();
6134
+ }
6135
+ /** Record the harness self-eval score (call once at run end). */
6136
+ setHarnessScore(score) {
6137
+ this.log.harnessScore = score;
6138
+ this.flush();
6139
+ }
6130
6140
  fileWritten(filePath) {
6131
6141
  if (!this.log.filesWritten.includes(filePath)) {
6132
6142
  this.log.filesWritten.push(filePath);
@@ -8339,108 +8349,6 @@ async function clearKey(provider) {
8339
8349
  await writeStore(store);
8340
8350
  }
8341
8351
 
8342
- // cli/welcome.ts
8343
- var import_chalk17 = __toESM(require("chalk"));
8344
- var os4 = __toESM(require("os"));
8345
- var path19 = __toESM(require("path"));
8346
- var fs20 = __toESM(require("fs-extra"));
8347
- var VERSION = "0.14.1";
8348
- var TOTAL_W = 76;
8349
- var L_WIDTH = 44;
8350
- var R_WIDTH = TOTAL_W - L_WIDTH - 4;
8351
- var ROBOT_COLOR = import_chalk17.default.hex("#E8885A");
8352
- var ROBOT = [
8353
- ROBOT_COLOR(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),
8354
- ROBOT_COLOR(" \u2502") + import_chalk17.default.bold.white(" \u25C9 ") + ROBOT_COLOR(" ") + import_chalk17.default.bold.white("\u25C9 ") + ROBOT_COLOR("\u2502"),
8355
- ROBOT_COLOR(" \u2502") + import_chalk17.default.dim(" \u2570\u2500\u256F ") + ROBOT_COLOR("\u2502"),
8356
- ROBOT_COLOR(" \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2518"),
8357
- ROBOT_COLOR(" \u2502"),
8358
- ROBOT_COLOR(" \u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500")
8359
- ];
8360
- function visLen(s) {
8361
- return s.replace(/\x1b\[[0-9;]*m/g, "").length;
8362
- }
8363
- function padR(s, width) {
8364
- const vl = visLen(s);
8365
- return vl >= width ? s : s + " ".repeat(width - vl);
8366
- }
8367
- function row(left, right) {
8368
- return padR(left, L_WIDTH) + " " + import_chalk17.default.gray("\u2502") + " " + padR(right, R_WIDTH);
8369
- }
8370
- function center(s, width) {
8371
- const vl = visLen(s);
8372
- const pad = Math.max(0, Math.floor((width - vl) / 2));
8373
- return " ".repeat(pad) + s;
8374
- }
8375
- async function getRecentSpecs(dir) {
8376
- const specsDir = path19.join(dir, "specs");
8377
- if (!await fs20.pathExists(specsDir)) return [];
8378
- try {
8379
- const files = await fs20.readdir(specsDir);
8380
- const mdFiles = files.filter((f) => f.endsWith(".md"));
8381
- const withStats = await Promise.all(
8382
- mdFiles.map(async (f) => {
8383
- const stat4 = await fs20.stat(path19.join(specsDir, f));
8384
- return { name: f, mtime: stat4.mtime.getTime() };
8385
- })
8386
- );
8387
- withStats.sort((a, b) => b.mtime - a.mtime);
8388
- return withStats.slice(0, 3).map(({ name, mtime }) => {
8389
- const ms2 = Date.now() - mtime;
8390
- const hours = Math.floor(ms2 / 36e5);
8391
- const days = Math.floor(hours / 24);
8392
- const age = days > 0 ? `${days}d ago` : hours > 0 ? `${hours}h ago` : "just now";
8393
- const slug = name.replace(/\.md$/, "").slice(0, R_WIDTH - age.length - 2);
8394
- return import_chalk17.default.white(slug) + import_chalk17.default.dim(" " + age);
8395
- });
8396
- } catch {
8397
- return [];
8398
- }
8399
- }
8400
- async function printWelcome(currentDir, config2) {
8401
- const username = os4.userInfo().username;
8402
- const homeDir = os4.homedir();
8403
- const shortDir = currentDir.startsWith(homeDir) ? "~" + currentDir.slice(homeDir.length) : currentDir;
8404
- const recentSpecs = await getRecentSpecs(currentDir);
8405
- const providerBit = config2?.provider ? config2.provider + (config2.model ? " \xB7 " + config2.model : "") : "";
8406
- const bottomRaw = [providerBit, shortDir].filter(Boolean).join(" \xB7 ");
8407
- const maxInfoLen = L_WIDTH - 2;
8408
- const bottomTruncated = bottomRaw.length > maxInfoLen ? bottomRaw.slice(0, maxInfoLen - 1) + "\u2026" : bottomRaw;
8409
- const bottomLine = " " + import_chalk17.default.dim(bottomTruncated);
8410
- const titleInner = `ai-spec v${VERSION} `;
8411
- const titleDashes = "\u2500".repeat(Math.max(0, TOTAL_W - titleInner.length - 4));
8412
- console.log(
8413
- "\n" + import_chalk17.default.hex("#FF6B35")("\u2500\u2500\u2500 " + titleInner + titleDashes)
8414
- );
8415
- const welcomeText = "Welcome back, " + import_chalk17.default.bold.white(username) + "!";
8416
- const leftLines = [
8417
- "",
8418
- center(welcomeText, L_WIDTH),
8419
- "",
8420
- ...ROBOT,
8421
- "",
8422
- bottomLine,
8423
- ""
8424
- ];
8425
- const rightLines = [
8426
- import_chalk17.default.hex("#FF8C00").bold("Tips for getting started"),
8427
- import_chalk17.default.gray("\u2500".repeat(R_WIDTH)),
8428
- import_chalk17.default.white('ai-spec create "feature"'),
8429
- import_chalk17.default.gray("ai-spec workspace run"),
8430
- import_chalk17.default.gray('ai-spec update "change"'),
8431
- "",
8432
- import_chalk17.default.hex("#FF8C00").bold("Recent activity"),
8433
- import_chalk17.default.gray("\u2500".repeat(R_WIDTH)),
8434
- ...recentSpecs.length > 0 ? recentSpecs : [import_chalk17.default.dim("No recent activity")]
8435
- ];
8436
- const maxLines = Math.max(leftLines.length, rightLines.length);
8437
- for (let i = 0; i < maxLines; i++) {
8438
- console.log(row(leftLines[i] ?? "", rightLines[i] ?? ""));
8439
- }
8440
- console.log(import_chalk17.default.gray("\u2500".repeat(TOTAL_W)));
8441
- console.log();
8442
- }
8443
-
8444
8352
  // prompts/global-constitution.prompt.ts
8445
8353
  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.
8446
8354
 
@@ -8950,8 +8858,8 @@ Existing API/service files:`);
8950
8858
  }
8951
8859
 
8952
8860
  // core/mock-server-generator.ts
8953
- var path20 = __toESM(require("path"));
8954
- var fs21 = __toESM(require("fs-extra"));
8861
+ var path19 = __toESM(require("path"));
8862
+ var fs20 = __toESM(require("fs-extra"));
8955
8863
  var import_child_process5 = require("child_process");
8956
8864
  function typeToFixture(fieldName, typeDesc) {
8957
8865
  const t = typeDesc.toLowerCase();
@@ -9087,22 +8995,22 @@ function generateMockServerJs(dsl, port) {
9087
8995
  }
9088
8996
  function detectFrontendFramework(projectDir) {
9089
8997
  for (const f of ["vite.config.ts", "vite.config.js", "vite.config.mts"]) {
9090
- if (fs21.existsSync(path20.join(projectDir, f))) return "vite";
8998
+ if (fs20.existsSync(path19.join(projectDir, f))) return "vite";
9091
8999
  }
9092
9000
  for (const f of ["next.config.js", "next.config.ts", "next.config.mjs"]) {
9093
- if (fs21.existsSync(path20.join(projectDir, f))) return "next";
9001
+ if (fs20.existsSync(path19.join(projectDir, f))) return "next";
9094
9002
  }
9095
- const pkgPath = path20.join(projectDir, "package.json");
9096
- if (fs21.existsSync(pkgPath)) {
9003
+ const pkgPath = path19.join(projectDir, "package.json");
9004
+ if (fs20.existsSync(pkgPath)) {
9097
9005
  try {
9098
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
9006
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
9099
9007
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
9100
9008
  if (deps["react-scripts"]) return "cra";
9101
9009
  } catch {
9102
9010
  }
9103
9011
  }
9104
9012
  for (const f of ["webpack.config.js", "webpack.config.ts"]) {
9105
- if (fs21.existsSync(path20.join(projectDir, f))) return "webpack";
9013
+ if (fs20.existsSync(path19.join(projectDir, f))) return "webpack";
9106
9014
  }
9107
9015
  return "unknown";
9108
9016
  }
@@ -9282,7 +9190,7 @@ export const worker = setupWorker(...handlers);
9282
9190
  var MOCK_LOCK_FILE = ".ai-spec-mock.lock.json";
9283
9191
  function findViteConfigFile(projectDir) {
9284
9192
  for (const f of ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]) {
9285
- if (fs21.existsSync(path20.join(projectDir, f))) return f;
9193
+ if (fs20.existsSync(path19.join(projectDir, f))) return f;
9286
9194
  }
9287
9195
  return null;
9288
9196
  }
@@ -9328,73 +9236,73 @@ async function applyMockProxy(frontendDir, mockPort, endpoints = []) {
9328
9236
  if (framework === "vite") {
9329
9237
  const viteConfigFile = findViteConfigFile(frontendDir) ?? "vite.config.ts";
9330
9238
  const mockConfigContent = generateViteMockConfigTs(viteConfigFile, mockPort, endpoints);
9331
- const mockConfigPath = path20.join(frontendDir, "vite.config.ai-spec-mock.ts");
9332
- await fs21.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9239
+ const mockConfigPath = path19.join(frontendDir, "vite.config.ai-spec-mock.ts");
9240
+ await fs20.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9333
9241
  actions.push({ type: "wrote-file", filePath: "vite.config.ai-spec-mock.ts" });
9334
- const pkgPath = path20.join(frontendDir, "package.json");
9335
- if (await fs21.pathExists(pkgPath)) {
9336
- const pkg = await fs21.readJson(pkgPath);
9242
+ const pkgPath = path19.join(frontendDir, "package.json");
9243
+ if (await fs20.pathExists(pkgPath)) {
9244
+ const pkg = await fs20.readJson(pkgPath);
9337
9245
  pkg.scripts = pkg.scripts ?? {};
9338
9246
  const originalValue = pkg.scripts["dev:mock"] ?? null;
9339
9247
  pkg.scripts["dev:mock"] = "vite --config vite.config.ai-spec-mock.ts";
9340
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9248
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9341
9249
  actions.push({ type: "added-pkg-script", key: "dev:mock", originalValue });
9342
9250
  }
9343
9251
  const lock2 = { framework, mockPort, frontendDir, actions };
9344
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9252
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9345
9253
  return { framework, applied: true, devCommand: "npm run dev:mock" };
9346
9254
  }
9347
9255
  if (framework === "cra") {
9348
- const pkgPath = path20.join(frontendDir, "package.json");
9349
- if (await fs21.pathExists(pkgPath)) {
9350
- const pkg = await fs21.readJson(pkgPath);
9256
+ const pkgPath = path19.join(frontendDir, "package.json");
9257
+ if (await fs20.pathExists(pkgPath)) {
9258
+ const pkg = await fs20.readJson(pkgPath);
9351
9259
  const originalProxy = pkg.proxy ?? null;
9352
9260
  pkg.proxy = `http://localhost:${mockPort}`;
9353
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9261
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9354
9262
  actions.push({ type: "patched-pkg-proxy", originalProxy });
9355
9263
  const lock2 = { framework, mockPort, frontendDir, actions };
9356
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9264
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9357
9265
  return { framework, applied: true, devCommand: "npm start" };
9358
9266
  }
9359
9267
  return { framework, applied: false, devCommand: null, note: "No package.json found." };
9360
9268
  }
9361
9269
  const lock = { framework, mockPort, frontendDir, actions };
9362
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9270
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9363
9271
  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}`;
9364
9272
  return { framework, applied: false, devCommand: null, note: manualNote };
9365
9273
  }
9366
9274
  async function restoreMockProxy(frontendDir) {
9367
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9368
- if (!await fs21.pathExists(lockPath)) {
9275
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9276
+ if (!await fs20.pathExists(lockPath)) {
9369
9277
  return { restored: false, note: "No lock file found \u2014 nothing to restore." };
9370
9278
  }
9371
- const lock = await fs21.readJson(lockPath);
9279
+ const lock = await fs20.readJson(lockPath);
9372
9280
  for (const action of lock.actions) {
9373
9281
  if (action.type === "wrote-file") {
9374
- const fp = path20.join(frontendDir, action.filePath);
9375
- if (await fs21.pathExists(fp)) await fs21.remove(fp);
9282
+ const fp = path19.join(frontendDir, action.filePath);
9283
+ if (await fs20.pathExists(fp)) await fs20.remove(fp);
9376
9284
  } else if (action.type === "added-pkg-script") {
9377
- const pkgPath = path20.join(frontendDir, "package.json");
9378
- if (await fs21.pathExists(pkgPath)) {
9379
- const pkg = await fs21.readJson(pkgPath);
9285
+ const pkgPath = path19.join(frontendDir, "package.json");
9286
+ if (await fs20.pathExists(pkgPath)) {
9287
+ const pkg = await fs20.readJson(pkgPath);
9380
9288
  if (action.originalValue == null) {
9381
9289
  delete pkg.scripts?.[action.key];
9382
9290
  } else {
9383
9291
  pkg.scripts = pkg.scripts ?? {};
9384
9292
  pkg.scripts[action.key] = action.originalValue;
9385
9293
  }
9386
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9294
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9387
9295
  }
9388
9296
  } else if (action.type === "patched-pkg-proxy") {
9389
- const pkgPath = path20.join(frontendDir, "package.json");
9390
- if (await fs21.pathExists(pkgPath)) {
9391
- const pkg = await fs21.readJson(pkgPath);
9297
+ const pkgPath = path19.join(frontendDir, "package.json");
9298
+ if (await fs20.pathExists(pkgPath)) {
9299
+ const pkg = await fs20.readJson(pkgPath);
9392
9300
  if (action.originalProxy == null) {
9393
9301
  delete pkg.proxy;
9394
9302
  } else {
9395
9303
  pkg.proxy = action.originalProxy;
9396
9304
  }
9397
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9305
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9398
9306
  }
9399
9307
  }
9400
9308
  }
@@ -9404,7 +9312,7 @@ async function restoreMockProxy(frontendDir) {
9404
9312
  } catch {
9405
9313
  }
9406
9314
  }
9407
- await fs21.remove(lockPath);
9315
+ await fs20.remove(lockPath);
9408
9316
  return { restored: true };
9409
9317
  }
9410
9318
  function startMockServerBackground(serverJsPath, port) {
@@ -9417,23 +9325,23 @@ function startMockServerBackground(serverJsPath, port) {
9417
9325
  return child.pid;
9418
9326
  }
9419
9327
  async function saveMockServerPid(frontendDir, pid) {
9420
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9421
- if (await fs21.pathExists(lockPath)) {
9422
- const lock = await fs21.readJson(lockPath);
9328
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9329
+ if (await fs20.pathExists(lockPath)) {
9330
+ const lock = await fs20.readJson(lockPath);
9423
9331
  lock.mockServerPid = pid;
9424
- await fs21.writeJson(lockPath, lock, { spaces: 2 });
9332
+ await fs20.writeJson(lockPath, lock, { spaces: 2 });
9425
9333
  }
9426
9334
  }
9427
9335
  async function generateMockAssets(dsl, projectDir, opts = {}) {
9428
9336
  const port = opts.port ?? 3001;
9429
- const outputDir = path20.join(projectDir, opts.outputDir ?? "mock");
9337
+ const outputDir = path19.join(projectDir, opts.outputDir ?? "mock");
9430
9338
  const result = { files: [] };
9431
- await fs21.ensureDir(outputDir);
9339
+ await fs20.ensureDir(outputDir);
9432
9340
  const serverJs = generateMockServerJs(dsl, port);
9433
- const serverPath = path20.join(outputDir, "server.js");
9434
- await fs21.writeFile(serverPath, serverJs, "utf-8");
9341
+ const serverPath = path19.join(outputDir, "server.js");
9342
+ await fs20.writeFile(serverPath, serverJs, "utf-8");
9435
9343
  result.files.push({
9436
- path: path20.relative(projectDir, serverPath),
9344
+ path: path19.relative(projectDir, serverPath),
9437
9345
  description: `Express mock server \u2014 run with: node mock/server.js`
9438
9346
  });
9439
9347
  const mockReadme = `# Mock Server
@@ -9463,52 +9371,52 @@ ${dsl.endpoints.map(
9463
9371
 
9464
9372
  Append \`?simulate_error=1\` to any request to test error handling (not yet auto-wired \u2014 edit server.js manually).
9465
9373
  `;
9466
- const readmePath = path20.join(outputDir, "README.md");
9467
- await fs21.writeFile(readmePath, mockReadme, "utf-8");
9374
+ const readmePath = path19.join(outputDir, "README.md");
9375
+ await fs20.writeFile(readmePath, mockReadme, "utf-8");
9468
9376
  result.files.push({
9469
- path: path20.relative(projectDir, readmePath),
9377
+ path: path19.relative(projectDir, readmePath),
9470
9378
  description: "Mock server usage guide"
9471
9379
  });
9472
9380
  if (opts.proxy) {
9473
9381
  const { content, filename } = generateProxyConfig(dsl, port, projectDir);
9474
- const proxyPath = path20.join(projectDir, filename);
9475
- await fs21.ensureDir(path20.dirname(proxyPath));
9476
- await fs21.writeFile(proxyPath, content, "utf-8");
9382
+ const proxyPath = path19.join(projectDir, filename);
9383
+ await fs20.ensureDir(path19.dirname(proxyPath));
9384
+ await fs20.writeFile(proxyPath, content, "utf-8");
9477
9385
  result.files.push({
9478
9386
  path: filename,
9479
9387
  description: "Proxy config snippet \u2014 copy instructions into your framework config"
9480
9388
  });
9481
9389
  }
9482
9390
  if (opts.msw) {
9483
- const mswDir = path20.join(projectDir, "src", "mocks");
9484
- await fs21.ensureDir(mswDir);
9391
+ const mswDir = path19.join(projectDir, "src", "mocks");
9392
+ await fs20.ensureDir(mswDir);
9485
9393
  const handlersContent = generateMswHandlers(dsl);
9486
- const handlersPath = path20.join(mswDir, "handlers.ts");
9487
- await fs21.writeFile(handlersPath, handlersContent, "utf-8");
9394
+ const handlersPath = path19.join(mswDir, "handlers.ts");
9395
+ await fs20.writeFile(handlersPath, handlersContent, "utf-8");
9488
9396
  result.files.push({
9489
- path: path20.relative(projectDir, handlersPath),
9397
+ path: path19.relative(projectDir, handlersPath),
9490
9398
  description: "MSW request handlers"
9491
9399
  });
9492
9400
  const browserContent = generateMswBrowser();
9493
- const browserPath = path20.join(mswDir, "browser.ts");
9494
- await fs21.writeFile(browserPath, browserContent, "utf-8");
9401
+ const browserPath = path19.join(mswDir, "browser.ts");
9402
+ await fs20.writeFile(browserPath, browserContent, "utf-8");
9495
9403
  result.files.push({
9496
- path: path20.relative(projectDir, browserPath),
9404
+ path: path19.relative(projectDir, browserPath),
9497
9405
  description: "MSW browser worker setup"
9498
9406
  });
9499
9407
  }
9500
9408
  return result;
9501
9409
  }
9502
9410
  async function findLatestDslFile(projectDir) {
9503
- const specDir = path20.join(projectDir, ".ai-spec");
9504
- if (!await fs21.pathExists(specDir)) return null;
9411
+ const specDir = path19.join(projectDir, ".ai-spec");
9412
+ if (!await fs20.pathExists(specDir)) return null;
9505
9413
  const allFiles = [];
9506
9414
  async function scan(dir) {
9507
- const entries = await fs21.readdir(dir);
9415
+ const entries = await fs20.readdir(dir);
9508
9416
  for (const entry of entries) {
9509
- const abs = path20.join(dir, entry);
9510
- const stat4 = await fs21.stat(abs);
9511
- if (stat4.isDirectory()) {
9417
+ const abs = path19.join(dir, entry);
9418
+ const stat3 = await fs20.stat(abs);
9419
+ if (stat3.isDirectory()) {
9512
9420
  await scan(abs);
9513
9421
  } else if (entry.endsWith(".dsl.json")) {
9514
9422
  allFiles.push(abs);
@@ -9518,16 +9426,16 @@ async function findLatestDslFile(projectDir) {
9518
9426
  await scan(specDir);
9519
9427
  if (allFiles.length === 0) return null;
9520
9428
  const withMtimes = await Promise.all(
9521
- allFiles.map(async (f) => ({ f, mtime: (await fs21.stat(f)).mtime }))
9429
+ allFiles.map(async (f) => ({ f, mtime: (await fs20.stat(f)).mtime }))
9522
9430
  );
9523
9431
  withMtimes.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
9524
9432
  return withMtimes[0].f;
9525
9433
  }
9526
9434
 
9527
9435
  // core/spec-updater.ts
9528
- var import_chalk18 = __toESM(require("chalk"));
9529
- var path21 = __toESM(require("path"));
9530
- var fs22 = __toESM(require("fs-extra"));
9436
+ var import_chalk17 = __toESM(require("chalk"));
9437
+ var path20 = __toESM(require("path"));
9438
+ var fs21 = __toESM(require("fs-extra"));
9531
9439
 
9532
9440
  // prompts/update.prompt.ts
9533
9441
  var specUpdateSystemPrompt = `You are a Senior Software Architect updating an existing Feature Spec based on a change request.
@@ -9673,8 +9581,8 @@ var SpecUpdater = class {
9673
9581
  * Returns all .md spec files sorted newest-first.
9674
9582
  */
9675
9583
  static async findLatestSpec(specsDir) {
9676
- if (!await fs22.pathExists(specsDir)) return null;
9677
- const files = await fs22.readdir(specsDir);
9584
+ if (!await fs21.pathExists(specsDir)) return null;
9585
+ const files = await fs21.readdir(specsDir);
9678
9586
  const pattern = /^feature-(.+)-v(\d+)\.md$/;
9679
9587
  let latest = null;
9680
9588
  for (const file of files) {
@@ -9682,8 +9590,8 @@ var SpecUpdater = class {
9682
9590
  if (!m) continue;
9683
9591
  const version = parseInt(m[2], 10);
9684
9592
  if (!latest || version > latest.version) {
9685
- const filePath = path21.join(specsDir, file);
9686
- const content = await fs22.readFile(filePath, "utf-8");
9593
+ const filePath = path20.join(specsDir, file);
9594
+ const content = await fs21.readFile(filePath, "utf-8");
9687
9595
  latest = { filePath, version, slug: m[1], content };
9688
9596
  }
9689
9597
  }
@@ -9694,16 +9602,16 @@ var SpecUpdater = class {
9694
9602
  * Generates a new version of the spec, re-extracts the DSL, and identifies affected files.
9695
9603
  */
9696
9604
  async update(changeRequest, existingSpecPath, projectDir, context, opts = {}) {
9697
- const existingSpec = await fs22.readFile(existingSpecPath, "utf-8");
9605
+ const existingSpec = await fs21.readFile(existingSpecPath, "utf-8");
9698
9606
  let existingDsl = null;
9699
9607
  const dslFile = await findLatestDslFile(projectDir);
9700
9608
  if (dslFile) {
9701
9609
  try {
9702
- existingDsl = await fs22.readJson(dslFile);
9610
+ existingDsl = await fs21.readJson(dslFile);
9703
9611
  } catch {
9704
9612
  }
9705
9613
  }
9706
- console.log(import_chalk18.default.blue(" [1/3] Generating updated spec..."));
9614
+ console.log(import_chalk17.default.blue(" [1/3] Generating updated spec..."));
9707
9615
  const updatePrompt = buildSpecUpdatePrompt(changeRequest, existingSpec, existingDsl, context);
9708
9616
  let updatedSpecContent;
9709
9617
  try {
@@ -9712,15 +9620,15 @@ var SpecUpdater = class {
9712
9620
  } catch (err) {
9713
9621
  throw new Error(`Spec update generation failed: ${err.message}`);
9714
9622
  }
9715
- const specBasename = path21.basename(existingSpecPath);
9623
+ const specBasename = path20.basename(existingSpecPath);
9716
9624
  const slugMatch = specBasename.match(/^feature-(.+)-v\d+\.md$/);
9717
9625
  const slug = slugMatch ? slugMatch[1] : "feature";
9718
- const specsDir = path21.dirname(existingSpecPath);
9626
+ const specsDir = path20.dirname(existingSpecPath);
9719
9627
  const { filePath: newSpecPath, version: newVersion } = await nextVersionPath(specsDir, slug);
9720
- await fs22.ensureDir(specsDir);
9721
- await fs22.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9722
- console.log(import_chalk18.default.green(` \u2714 New spec written: ${path21.relative(projectDir, newSpecPath)}`));
9723
- console.log(import_chalk18.default.blue(" [2/3] Updating DSL..."));
9628
+ await fs21.ensureDir(specsDir);
9629
+ await fs21.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9630
+ console.log(import_chalk17.default.green(` \u2714 New spec written: ${path20.relative(projectDir, newSpecPath)}`));
9631
+ console.log(import_chalk17.default.blue(" [2/3] Updating DSL..."));
9724
9632
  let updatedDsl = null;
9725
9633
  let newDslPath = null;
9726
9634
  if (existingDsl) {
@@ -9732,7 +9640,7 @@ var SpecUpdater = class {
9732
9640
  updatedDsl = parsed;
9733
9641
  }
9734
9642
  } catch {
9735
- console.log(import_chalk18.default.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9643
+ console.log(import_chalk17.default.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9736
9644
  }
9737
9645
  }
9738
9646
  if (!updatedDsl) {
@@ -9741,15 +9649,15 @@ var SpecUpdater = class {
9741
9649
  }
9742
9650
  if (updatedDsl) {
9743
9651
  const dslPath = newSpecPath.replace(/\.md$/, ".dsl.json");
9744
- await fs22.writeJson(dslPath, updatedDsl, { spaces: 2 });
9652
+ await fs21.writeJson(dslPath, updatedDsl, { spaces: 2 });
9745
9653
  newDslPath = dslPath;
9746
- console.log(import_chalk18.default.green(` \u2714 DSL updated: ${path21.relative(projectDir, dslPath)}`));
9654
+ console.log(import_chalk17.default.green(` \u2714 DSL updated: ${path20.relative(projectDir, dslPath)}`));
9747
9655
  } else {
9748
- console.log(import_chalk18.default.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9656
+ console.log(import_chalk17.default.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9749
9657
  }
9750
9658
  let affectedFiles = [];
9751
9659
  if (!opts.skipAffectedFiles && updatedDsl && existingDsl && context) {
9752
- console.log(import_chalk18.default.blue(" [3/3] Identifying affected files..."));
9660
+ console.log(import_chalk17.default.blue(" [3/3] Identifying affected files..."));
9753
9661
  const systemPrompt = getCodeGenSystemPrompt(opts.repoType);
9754
9662
  const affectedPrompt = buildAffectedFilesPrompt(
9755
9663
  changeRequest,
@@ -9760,9 +9668,9 @@ var SpecUpdater = class {
9760
9668
  try {
9761
9669
  const affectedRaw = await this.provider.generate(affectedPrompt, systemPrompt);
9762
9670
  affectedFiles = parseAffectedFiles(affectedRaw);
9763
- console.log(import_chalk18.default.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9671
+ console.log(import_chalk17.default.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9764
9672
  } catch {
9765
- console.log(import_chalk18.default.gray(" Could not identify affected files \u2014 use manual selection."));
9673
+ console.log(import_chalk17.default.gray(" Could not identify affected files \u2014 use manual selection."));
9766
9674
  }
9767
9675
  }
9768
9676
  return { newSpecPath, newVersion, newDslPath, affectedFiles, updatedDsl };
@@ -9770,8 +9678,8 @@ var SpecUpdater = class {
9770
9678
  };
9771
9679
 
9772
9680
  // core/openapi-exporter.ts
9773
- var path22 = __toESM(require("path"));
9774
- var fs23 = __toESM(require("fs-extra"));
9681
+ var path21 = __toESM(require("path"));
9682
+ var fs22 = __toESM(require("fs-extra"));
9775
9683
  function dslTypeToOASchema(typeDesc, fieldName = "") {
9776
9684
  const t = typeDesc.toLowerCase();
9777
9685
  if (t === "string" || t.includes("string")) {
@@ -10000,7 +9908,7 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
10000
9908
  const format = opts.format ?? "yaml";
10001
9909
  const serverUrl = opts.serverUrl ?? "http://localhost:3000";
10002
9910
  const defaultName = `openapi.${format}`;
10003
- const outputPath = opts.outputPath ? path22.isAbsolute(opts.outputPath) ? opts.outputPath : path22.join(projectDir, opts.outputPath) : path22.join(projectDir, defaultName);
9911
+ const outputPath = opts.outputPath ? path21.isAbsolute(opts.outputPath) ? opts.outputPath : path21.join(projectDir, opts.outputPath) : path21.join(projectDir, defaultName);
10004
9912
  const doc = dslToOpenApi(dsl, serverUrl);
10005
9913
  let content;
10006
9914
  if (format === "json") {
@@ -10008,18 +9916,115 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
10008
9916
  } else {
10009
9917
  content = buildYamlDoc(doc);
10010
9918
  }
10011
- await fs23.ensureDir(path22.dirname(outputPath));
10012
- await fs23.writeFile(outputPath, content, "utf-8");
9919
+ await fs22.ensureDir(path21.dirname(outputPath));
9920
+ await fs22.writeFile(outputPath, content, "utf-8");
10013
9921
  return outputPath;
10014
9922
  }
10015
9923
 
9924
+ // core/prompt-hasher.ts
9925
+ var import_crypto = require("crypto");
9926
+ init_codegen_prompt();
9927
+ init_codegen_prompt();
9928
+ function computePromptHash() {
9929
+ const segments = [
9930
+ codeGenSystemPrompt,
9931
+ dslSystemPrompt,
9932
+ specPrompt,
9933
+ reviewArchitectureSystemPrompt,
9934
+ reviewImplementationSystemPrompt,
9935
+ reviewImpactComplexitySystemPrompt
9936
+ ];
9937
+ return (0, import_crypto.createHash)("sha256").update(segments.join("\0")).digest("hex").slice(0, 8);
9938
+ }
9939
+
9940
+ // core/self-evaluator.ts
9941
+ var import_chalk18 = __toESM(require("chalk"));
9942
+ var ENDPOINT_LAYER_PATTERNS = [
9943
+ /src\/api/,
9944
+ /src\/routes?/,
9945
+ /src\/controller/,
9946
+ /src\/handler/,
9947
+ /src\/endpoints?/
9948
+ ];
9949
+ var MODEL_LAYER_PATTERNS = [
9950
+ /src\/model/,
9951
+ /src\/schema/,
9952
+ /src\/entit/,
9953
+ /src\/db/,
9954
+ /prisma/,
9955
+ /src\/data/,
9956
+ /src\/domain/
9957
+ ];
9958
+ function extractReviewScore(reviewText) {
9959
+ const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
9960
+ return match ? parseFloat(match[1]) : null;
9961
+ }
9962
+ function runSelfEval(opts) {
9963
+ const { dsl, generatedFiles, compilePassed, reviewText, promptHash, logger } = opts;
9964
+ const endpointsTotal = dsl?.endpoints?.length ?? 0;
9965
+ const modelsTotal = dsl?.models?.length ?? 0;
9966
+ const endpointLayerCovered = generatedFiles.some(
9967
+ (f) => ENDPOINT_LAYER_PATTERNS.some((p) => p.test(f))
9968
+ );
9969
+ const modelLayerCovered = generatedFiles.some(
9970
+ (f) => MODEL_LAYER_PATTERNS.some((p) => p.test(f))
9971
+ );
9972
+ let dslCoverageScore = 10;
9973
+ if (generatedFiles.length === 0) {
9974
+ dslCoverageScore = 0;
9975
+ } else {
9976
+ if (endpointsTotal > 0 && !endpointLayerCovered) dslCoverageScore -= 4;
9977
+ if (modelsTotal > 0 && !modelLayerCovered) dslCoverageScore -= 3;
9978
+ }
9979
+ const compileScore = compilePassed ? 10 : 5;
9980
+ const reviewScore = reviewText ? extractReviewScore(reviewText) : null;
9981
+ const harnessScore = reviewScore !== null ? Math.round((dslCoverageScore * 0.4 + compileScore * 0.3 + reviewScore * 0.3) * 10) / 10 : Math.round((dslCoverageScore * 0.55 + compileScore * 0.45) * 10) / 10;
9982
+ const result = {
9983
+ dslCoverageScore,
9984
+ compileScore,
9985
+ reviewScore,
9986
+ harnessScore,
9987
+ promptHash,
9988
+ detail: {
9989
+ endpointsTotal,
9990
+ endpointLayerCovered,
9991
+ modelsTotal,
9992
+ modelLayerCovered,
9993
+ filesWritten: generatedFiles.length
9994
+ }
9995
+ };
9996
+ logger.setHarnessScore(harnessScore);
9997
+ logger.stageEnd("self_eval", {
9998
+ harnessScore,
9999
+ dslCoverageScore,
10000
+ compileScore,
10001
+ reviewScore: reviewScore ?? void 0,
10002
+ promptHash
10003
+ });
10004
+ return result;
10005
+ }
10006
+ function printSelfEval(result) {
10007
+ const scoreColor = result.harnessScore >= 8 ? import_chalk18.default.green : result.harnessScore >= 6 ? import_chalk18.default.yellow : import_chalk18.default.red;
10008
+ const filled = Math.round(result.harnessScore);
10009
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
10010
+ const compileTag = result.compileScore === 10 ? import_chalk18.default.green("pass") : import_chalk18.default.yellow("partial");
10011
+ const reviewTag = result.reviewScore !== null ? `Review: ${result.reviewScore}/10` : import_chalk18.default.gray("Review: skipped");
10012
+ console.log(import_chalk18.default.cyan("\n\u2500\u2500\u2500 Harness Self-Eval \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10013
+ console.log(` Score : ${scoreColor(`[${bar}] ${result.harnessScore}/10`)}`);
10014
+ console.log(
10015
+ ` DSL : ${scoreColor(result.dslCoverageScore + "/10")} Compile: ${compileTag} ${reviewTag}`
10016
+ );
10017
+ console.log(import_chalk18.default.gray(` Prompt : ${result.promptHash}`));
10018
+ console.log(import_chalk18.default.gray("\u2500".repeat(49)));
10019
+ }
10020
+
10016
10021
  // cli/index.ts
10017
10022
  dotenv.config();
10018
10023
  var CONFIG_FILE = ".ai-spec.json";
10019
10024
  async function loadConfig(dir) {
10020
- const p = path23.join(dir, CONFIG_FILE);
10021
- if (await fs24.pathExists(p)) {
10022
- return fs24.readJson(p);
10025
+ const p = path22.join(dir, CONFIG_FILE);
10026
+ if (await fs23.pathExists(p)) {
10027
+ return fs23.readJson(p);
10023
10028
  }
10024
10029
  return {};
10025
10030
  }
@@ -10097,7 +10102,7 @@ program.command("create").description("Generate a feature spec and kick off code
10097
10102
  } else {
10098
10103
  const mockPort = 3001;
10099
10104
  const mockResult = await generateMockAssets(backendResult.dsl, backendResult.repoAbsPath, { port: mockPort });
10100
- const serverJsPath = path23.join(backendResult.repoAbsPath, "mock", "server.js");
10105
+ const serverJsPath = path22.join(backendResult.repoAbsPath, "mock", "server.js");
10101
10106
  console.log(import_chalk19.default.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
10102
10107
  const pid = startMockServerBackground(serverJsPath, mockPort);
10103
10108
  console.log(import_chalk19.default.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
@@ -10148,6 +10153,8 @@ program.command("create").description("Generate a feature spec and kick off code
10148
10153
  model: specModelName
10149
10154
  });
10150
10155
  setActiveLogger(runLogger);
10156
+ const promptHash = computePromptHash();
10157
+ runLogger.setPromptHash(promptHash);
10151
10158
  console.log(import_chalk19.default.blue("[1/6] Loading project context..."));
10152
10159
  runLogger.stageStart("context_load");
10153
10160
  const loader = new ContextLoader(currentDir);
@@ -10261,7 +10268,7 @@ program.command("create").description("Generate a feature spec and kick off code
10261
10268
  const taskCountHint = initialTasks.length > 0 ? ` Tasks generated : ${initialTasks.length}` : "";
10262
10269
  console.log(import_chalk19.default.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10263
10270
  if (taskCountHint) console.log(import_chalk19.default.gray(taskCountHint));
10264
- const previewSpecsDir = path23.join(currentDir, "specs");
10271
+ const previewSpecsDir = path22.join(currentDir, "specs");
10265
10272
  const slug = featureSlug;
10266
10273
  const prevVersion = await findLatestVersion(previewSpecsDir, slug);
10267
10274
  if (prevVersion) {
@@ -10339,10 +10346,10 @@ program.command("create").description("Generate a feature spec and kick off code
10339
10346
  const reason = opts.worktree ? "" : isFrontendProject2 ? " (frontend project \u2014 use --worktree to override)" : " (--skip-worktree)";
10340
10347
  console.log(import_chalk19.default.gray(`[4/6] Skipping worktree${reason}.`));
10341
10348
  }
10342
- const specsDir = path23.join(workingDir, "specs");
10343
- await fs24.ensureDir(specsDir);
10349
+ const specsDir = path22.join(workingDir, "specs");
10350
+ await fs23.ensureDir(specsDir);
10344
10351
  const { filePath: specFile, version: specVersion } = await nextVersionPath(specsDir, featureSlug);
10345
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10352
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10346
10353
  console.log(import_chalk19.default.green(`
10347
10354
  [5/6] \u2714 Spec saved: ${specFile}`) + import_chalk19.default.gray(` (v${specVersion})`));
10348
10355
  let savedDslFile = null;
@@ -10404,14 +10411,16 @@ program.command("create").description("Generate a feature spec and kick off code
10404
10411
  generatedTestFiles = await testGen.generate(extractedDsl, workingDir);
10405
10412
  runLogger.stageEnd("test_gen", { filesGenerated: generatedTestFiles.length });
10406
10413
  }
10414
+ let compilePassed = false;
10407
10415
  if (opts.skipErrorFeedback) {
10408
10416
  console.log(import_chalk19.default.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10417
+ compilePassed = true;
10409
10418
  } else {
10410
10419
  if (opts.tdd) {
10411
10420
  console.log(import_chalk19.default.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10412
10421
  }
10413
10422
  runLogger.stageStart("error_feedback");
10414
- await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
10423
+ compilePassed = await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
10415
10424
  maxCycles: opts.tdd ? 3 : 2
10416
10425
  // TDD gets one extra cycle
10417
10426
  });
@@ -10422,7 +10431,7 @@ program.command("create").description("Generate a feature spec and kick off code
10422
10431
  console.log(import_chalk19.default.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10423
10432
  runLogger.stageStart("review");
10424
10433
  const reviewer = new CodeReviewer(specProvider, currentDir);
10425
- const savedSpec = await fs24.readFile(specFile, "utf-8");
10434
+ const savedSpec = await fs23.readFile(specFile, "utf-8");
10426
10435
  if (codegenMode === "api" && generatedFiles.length > 0) {
10427
10436
  reviewResult = await reviewer.reviewFiles(savedSpec, generatedFiles, workingDir, specFile);
10428
10437
  } else {
@@ -10437,6 +10446,16 @@ program.command("create").description("Generate a feature spec and kick off code
10437
10446
  runLogger.stageEnd("review");
10438
10447
  await accumulateReviewKnowledge(specProvider, currentDir, reviewResult);
10439
10448
  }
10449
+ runLogger.stageStart("self_eval");
10450
+ const selfEvalResult = runSelfEval({
10451
+ dsl: extractedDsl,
10452
+ generatedFiles,
10453
+ compilePassed,
10454
+ reviewText: reviewResult,
10455
+ promptHash,
10456
+ logger: runLogger
10457
+ });
10458
+ printSelfEval(selfEvalResult);
10440
10459
  runLogger.finish();
10441
10460
  console.log(import_chalk19.default.bold.green("\n\u2714 All done!"));
10442
10461
  console.log(import_chalk19.default.gray(` Spec : ${specFile}`));
@@ -10467,17 +10486,17 @@ program.command("review").description("Run AI code review on current git diff ag
10467
10486
  const reviewer = new CodeReviewer(provider, currentDir);
10468
10487
  let specContent = "";
10469
10488
  let resolvedSpecFile;
10470
- if (specFile && await fs24.pathExists(specFile)) {
10471
- specContent = await fs24.readFile(specFile, "utf-8");
10489
+ if (specFile && await fs23.pathExists(specFile)) {
10490
+ specContent = await fs23.readFile(specFile, "utf-8");
10472
10491
  resolvedSpecFile = specFile;
10473
10492
  console.log(import_chalk19.default.gray(`Using spec: ${specFile}`));
10474
10493
  } else {
10475
- const specsDir = path23.join(currentDir, "specs");
10476
- if (await fs24.pathExists(specsDir)) {
10477
- const files = (await fs24.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10494
+ const specsDir = path22.join(currentDir, "specs");
10495
+ if (await fs23.pathExists(specsDir)) {
10496
+ const files = (await fs23.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10478
10497
  if (files.length > 0) {
10479
- const latest = path23.join(specsDir, files[0]);
10480
- specContent = await fs24.readFile(latest, "utf-8");
10498
+ const latest = path22.join(specsDir, files[0]);
10499
+ specContent = await fs23.readFile(latest, "utf-8");
10481
10500
  resolvedSpecFile = latest;
10482
10501
  console.log(import_chalk19.default.gray(`Auto-detected spec: specs/${files[0]}`));
10483
10502
  }
@@ -10515,7 +10534,7 @@ program.command("init").description(`Analyze codebase and generate Project Const
10515
10534
  console.log(import_chalk19.default.gray(` Lines : ${result.before.totalLines} \u2192 ${result.after.totalLines} (${result.before.totalLines - result.after.totalLines > 0 ? "-" : "+"}${Math.abs(result.before.totalLines - result.after.totalLines)})`));
10516
10535
  console.log(import_chalk19.default.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
10517
10536
  if (result.backupPath) {
10518
- console.log(import_chalk19.default.gray(` Backup: ${path23.basename(result.backupPath)}`));
10537
+ console.log(import_chalk19.default.gray(` Backup: ${path22.basename(result.backupPath)}`));
10519
10538
  }
10520
10539
  }
10521
10540
  } catch (err) {
@@ -10541,7 +10560,7 @@ program.command("init").description(`Analyze codebase and generate Project Const
10541
10560
  `Tech stack: ${ctx.techStack.join(", ") || "unknown"}`,
10542
10561
  `Dependencies: ${ctx.dependencies.slice(0, 20).join(", ")}`
10543
10562
  ].join("\n");
10544
- const prompt = buildGlobalConstitutionPrompt([{ name: path23.basename(currentDir), summary }]);
10563
+ const prompt = buildGlobalConstitutionPrompt([{ name: path22.basename(currentDir), summary }]);
10545
10564
  let globalConstitution;
10546
10565
  try {
10547
10566
  globalConstitution = await provider.generate(prompt, globalConstitutionSystemPrompt);
@@ -10561,8 +10580,8 @@ program.command("init").description(`Analyze codebase and generate Project Const
10561
10580
  }
10562
10581
  return;
10563
10582
  }
10564
- const constitutionPath = path23.join(currentDir, CONSTITUTION_FILE);
10565
- if (!opts.force && await fs24.pathExists(constitutionPath)) {
10583
+ const constitutionPath = path22.join(currentDir, CONSTITUTION_FILE);
10584
+ if (!opts.force && await fs23.pathExists(constitutionPath)) {
10566
10585
  console.log(import_chalk19.default.yellow(`
10567
10586
  ${CONSTITUTION_FILE} already exists.`));
10568
10587
  console.log(import_chalk19.default.gray(" Use --force to overwrite it."));
@@ -10581,7 +10600,7 @@ program.command("init").description(`Analyze codebase and generate Project Const
10581
10600
  process.exit(1);
10582
10601
  }
10583
10602
  const saved = await generator.saveConstitution(currentDir, constitution);
10584
- const globalResult = await loadGlobalConstitution([path23.dirname(currentDir)]);
10603
+ const globalResult = await loadGlobalConstitution([path22.dirname(currentDir)]);
10585
10604
  if (globalResult) {
10586
10605
  console.log(import_chalk19.default.cyan(`
10587
10606
  \u2139 Global constitution detected: ${globalResult.source}`));
@@ -10603,7 +10622,7 @@ program.command("config").description(`Set default configuration for this projec
10603
10622
  "Default code generation mode (claude-code|api|plan)"
10604
10623
  ).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) => {
10605
10624
  const currentDir = process.cwd();
10606
- const configPath = path23.join(currentDir, CONFIG_FILE);
10625
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10607
10626
  if (opts.clearKeys) {
10608
10627
  await clearAllKeys();
10609
10628
  console.log(import_chalk19.default.green(`\u2714 All saved API keys cleared.`));
@@ -10615,7 +10634,7 @@ program.command("config").description(`Set default configuration for this projec
10615
10634
  return;
10616
10635
  }
10617
10636
  if (opts.listKeys) {
10618
- const store = await fs24.readJson(KEY_STORE_FILE).catch(() => ({}));
10637
+ const store = await fs23.readJson(KEY_STORE_FILE).catch(() => ({}));
10619
10638
  const providers = Object.keys(store);
10620
10639
  if (providers.length === 0) {
10621
10640
  console.log(import_chalk19.default.gray("No saved API keys."));
@@ -10631,7 +10650,7 @@ File: ${KEY_STORE_FILE}`));
10631
10650
  return;
10632
10651
  }
10633
10652
  if (opts.reset) {
10634
- await fs24.writeJson(configPath, {}, { spaces: 2 });
10653
+ await fs23.writeJson(configPath, {}, { spaces: 2 });
10635
10654
  console.log(import_chalk19.default.green(`\u2714 Config reset: ${configPath}`));
10636
10655
  return;
10637
10656
  }
@@ -10659,13 +10678,13 @@ File: ${KEY_STORE_FILE}`));
10659
10678
  }
10660
10679
  updated.minSpecScore = score;
10661
10680
  }
10662
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10681
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10663
10682
  console.log(import_chalk19.default.green(`\u2714 Config saved to ${configPath}`));
10664
10683
  console.log(JSON.stringify(updated, null, 2));
10665
10684
  });
10666
10685
  program.command("model").description("Interactively switch the active AI provider/model and save to .ai-spec.json").option("--list", "List all available providers and models").action(async (opts) => {
10667
10686
  const currentDir = process.cwd();
10668
- const configPath = path23.join(currentDir, CONFIG_FILE);
10687
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10669
10688
  if (opts.list) {
10670
10689
  console.log(import_chalk19.default.bold("\nAvailable providers & models:\n"));
10671
10690
  for (const [key, meta] of Object.entries(PROVIDER_CATALOG)) {
@@ -10769,7 +10788,7 @@ program.command("model").description("Interactively switch the active AI provide
10769
10788
  console.log(import_chalk19.default.gray(" Cancelled."));
10770
10789
  return;
10771
10790
  }
10772
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10791
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10773
10792
  console.log(import_chalk19.default.green(`
10774
10793
  \u2714 Saved to ${configPath}`));
10775
10794
  const providerToCheck = updated.provider ?? "gemini";
@@ -10864,17 +10883,17 @@ ${contractContextSection}`;
10864
10883
  } else {
10865
10884
  console.log(import_chalk19.default.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10866
10885
  }
10867
- const specsDir = path23.join(workingDir, "specs");
10868
- await fs24.ensureDir(specsDir);
10886
+ const specsDir = path22.join(workingDir, "specs");
10887
+ await fs23.ensureDir(specsDir);
10869
10888
  const featureSlug = slugify(idea);
10870
10889
  const { filePath: specFile } = await nextVersionPath(specsDir, featureSlug);
10871
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10872
- console.log(import_chalk19.default.green(` Spec saved: ${path23.relative(repoAbsPath, specFile)}`));
10890
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10891
+ console.log(import_chalk19.default.green(` Spec saved: ${path22.relative(repoAbsPath, specFile)}`));
10873
10892
  let savedDslFile = null;
10874
10893
  if (extractedDsl) {
10875
10894
  const dslExtractorForSave = new DslExtractor(specProvider);
10876
10895
  savedDslFile = await dslExtractorForSave.saveDsl(extractedDsl, specFile);
10877
- console.log(import_chalk19.default.green(` DSL saved: ${path23.relative(repoAbsPath, savedDslFile)}`));
10896
+ console.log(import_chalk19.default.green(` DSL saved: ${path22.relative(repoAbsPath, savedDslFile)}`));
10878
10897
  }
10879
10898
  console.log(import_chalk19.default.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10880
10899
  try {
@@ -11104,8 +11123,8 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
11104
11123
  var workspaceCmd = program.command("workspace").description("Manage multi-repo workspace configuration");
11105
11124
  workspaceCmd.command("init").description(`Interactive workspace setup \u2014 creates ${WORKSPACE_CONFIG_FILE}`).action(async () => {
11106
11125
  const currentDir = process.cwd();
11107
- const configPath = path23.join(currentDir, WORKSPACE_CONFIG_FILE);
11108
- if (await fs24.pathExists(configPath)) {
11126
+ const configPath = path22.join(currentDir, WORKSPACE_CONFIG_FILE);
11127
+ if (await fs23.pathExists(configPath)) {
11109
11128
  const overwrite = await (0, import_prompts3.confirm)({
11110
11129
  message: `${WORKSPACE_CONFIG_FILE} already exists. Overwrite?`,
11111
11130
  default: false
@@ -11186,10 +11205,10 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11186
11205
  message: `Relative path to "${repoName}" from here (default: ./${repoName}):`,
11187
11206
  default: `./${repoName}`
11188
11207
  });
11189
- const absPath = path23.resolve(currentDir, repoPath);
11208
+ const absPath = path22.resolve(currentDir, repoPath);
11190
11209
  let detectedType = "unknown";
11191
11210
  let detectedRole = "shared";
11192
- if (await fs24.pathExists(absPath)) {
11211
+ if (await fs23.pathExists(absPath)) {
11193
11212
  const { type, role } = await detectRepoType(absPath);
11194
11213
  detectedType = type;
11195
11214
  detectedRole = role;
@@ -11252,12 +11271,12 @@ workspaceCmd.command("status").description("Show current workspace configuration
11252
11271
  }
11253
11272
  console.log(import_chalk19.default.bold(`
11254
11273
  Workspace: ${config2.name}`));
11255
- console.log(import_chalk19.default.gray(` Config: ${path23.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11274
+ console.log(import_chalk19.default.gray(` Config: ${path22.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11256
11275
  console.log(import_chalk19.default.gray(` Repos (${config2.repos.length}):
11257
11276
  `));
11258
11277
  for (const repo of config2.repos) {
11259
11278
  const absPath = loader.resolveAbsPath(repo);
11260
- const exists = await fs24.pathExists(absPath);
11279
+ const exists = await fs23.pathExists(absPath);
11261
11280
  const status = exists ? import_chalk19.default.green("found") : import_chalk19.default.red("not found");
11262
11281
  console.log(
11263
11282
  ` ${import_chalk19.default.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
@@ -11291,14 +11310,14 @@ program.command("update").description("Update an existing spec with a change req
11291
11310
  console.log(import_chalk19.default.gray(` Run ID: ${updateRunId}`));
11292
11311
  let specPath = opts.spec ?? null;
11293
11312
  if (!specPath) {
11294
- const specsDir = path23.join(currentDir, "specs");
11313
+ const specsDir = path22.join(currentDir, "specs");
11295
11314
  const latest = await SpecUpdater.findLatestSpec(specsDir);
11296
11315
  if (!latest) {
11297
11316
  console.error(import_chalk19.default.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11298
11317
  process.exit(1);
11299
11318
  }
11300
11319
  specPath = latest.filePath;
11301
- console.log(import_chalk19.default.gray(` Using spec: ${path23.relative(currentDir, specPath)} (v${latest.version})`));
11320
+ console.log(import_chalk19.default.gray(` Using spec: ${path22.relative(currentDir, specPath)} (v${latest.version})`));
11302
11321
  }
11303
11322
  console.log(import_chalk19.default.gray(" Loading project context..."));
11304
11323
  const loader = new ContextLoader(currentDir);
@@ -11320,9 +11339,9 @@ program.command("update").description("Update an existing spec with a change req
11320
11339
  process.exit(1);
11321
11340
  }
11322
11341
  console.log(import_chalk19.default.green(`
11323
- \u2714 Spec updated \u2192 v${result.newVersion}: ${path23.relative(currentDir, result.newSpecPath)}`));
11342
+ \u2714 Spec updated \u2192 v${result.newVersion}: ${path22.relative(currentDir, result.newSpecPath)}`));
11324
11343
  if (result.newDslPath) {
11325
- console.log(import_chalk19.default.green(` \u2714 DSL updated: ${path23.relative(currentDir, result.newDslPath)}`));
11344
+ console.log(import_chalk19.default.green(` \u2714 DSL updated: ${path22.relative(currentDir, result.newDslPath)}`));
11326
11345
  }
11327
11346
  if (result.affectedFiles.length > 0) {
11328
11347
  console.log(import_chalk19.default.cyan("\n Affected files:"));
@@ -11338,7 +11357,7 @@ program.command("update").description("Update an existing spec with a change req
11338
11357
  const codegenProvider = createProvider(codegenProviderName, codegenApiKey, codegenModelName);
11339
11358
  console.log(import_chalk19.default.blue("\n Regenerating affected files..."));
11340
11359
  const codeGenerator = new CodeGenerator(codegenProvider, "api");
11341
- const specContent = await fs24.readFile(result.newSpecPath, "utf-8");
11360
+ const specContent = await fs23.readFile(result.newSpecPath, "utf-8");
11342
11361
  const constitutionSection = context.constitution ? `
11343
11362
  === Project Constitution (MUST follow) ===
11344
11363
  ${context.constitution}
@@ -11349,10 +11368,10 @@ ${JSON.stringify(result.updatedDsl, null, 2).slice(0, 3e3)}
11349
11368
  ` : "";
11350
11369
  updateLogger.stageStart("update_codegen");
11351
11370
  for (const affected of result.affectedFiles) {
11352
- const fullPath = path23.join(currentDir, affected.file);
11371
+ const fullPath = path22.join(currentDir, affected.file);
11353
11372
  let existing = "";
11354
11373
  try {
11355
- existing = await fs24.readFile(fullPath, "utf-8");
11374
+ existing = await fs23.readFile(fullPath, "utf-8");
11356
11375
  } catch {
11357
11376
  }
11358
11377
  const codePrompt = `Apply this change to the file.
@@ -11371,9 +11390,9 @@ ${existing || "Create from scratch."}`;
11371
11390
  const { getCodeGenSystemPrompt: _getPrompt } = await Promise.resolve().then(() => (init_codegen_prompt(), codegen_prompt_exports));
11372
11391
  const raw = await codegenProvider.generate(codePrompt, _getPrompt(repoType));
11373
11392
  const content = raw.replace(/^```\w*\n?/gm, "").replace(/\n?```$/gm, "").trim();
11374
- await fs24.ensureDir(path23.dirname(fullPath));
11393
+ await fs23.ensureDir(path22.dirname(fullPath));
11375
11394
  await updateSnapshot.snapshotFile(fullPath);
11376
- await fs24.writeFile(fullPath, content, "utf-8");
11395
+ await fs23.writeFile(fullPath, content, "utf-8");
11377
11396
  updateLogger.fileWritten(affected.file);
11378
11397
  console.log(import_chalk19.default.green("\u2714"));
11379
11398
  } catch (err) {
@@ -11382,7 +11401,7 @@ ${existing || "Create from scratch."}`;
11382
11401
  }
11383
11402
  }
11384
11403
  updateLogger.stageEnd("update_codegen", { filesUpdated: result.affectedFiles.length });
11385
- const updatedSpecContent = await fs24.readFile(result.newSpecPath, "utf-8").catch(() => "");
11404
+ const updatedSpecContent = await fs23.readFile(result.newSpecPath, "utf-8").catch(() => "");
11386
11405
  if (updatedSpecContent) {
11387
11406
  const updateReviewer = new CodeReviewer(provider, currentDir);
11388
11407
  const reviewResult = await updateReviewer.reviewCode(updatedSpecContent, result.newSpecPath).catch(() => "");
@@ -11412,11 +11431,11 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11412
11431
  console.error(import_chalk19.default.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11413
11432
  process.exit(1);
11414
11433
  }
11415
- console.log(import_chalk19.default.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11434
+ console.log(import_chalk19.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11416
11435
  }
11417
11436
  let dsl;
11418
11437
  try {
11419
- dsl = await fs24.readJson(dslPath);
11438
+ dsl = await fs23.readJson(dslPath);
11420
11439
  } catch (err) {
11421
11440
  console.error(import_chalk19.default.red(` Failed to read DSL: ${err.message}`));
11422
11441
  process.exit(1);
@@ -11430,7 +11449,7 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11430
11449
  serverUrl,
11431
11450
  outputPath: opts.output
11432
11451
  });
11433
- const rel = path23.relative(currentDir, outputPath);
11452
+ const rel = path22.relative(currentDir, outputPath);
11434
11453
  console.log(import_chalk19.default.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
11435
11454
  console.log(import_chalk19.default.gray(` Feature : ${dsl.feature.title}`));
11436
11455
  console.log(import_chalk19.default.gray(` Endpoints: ${dsl.endpoints.length}`));
@@ -11449,7 +11468,7 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11449
11468
  const port = parseInt(opts.port, 10) || 3001;
11450
11469
  console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec mock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11451
11470
  if (opts.restore) {
11452
- const frontendDir = opts.frontend ? path23.resolve(opts.frontend) : currentDir;
11471
+ const frontendDir = opts.frontend ? path22.resolve(opts.frontend) : currentDir;
11453
11472
  const r = await restoreMockProxy(frontendDir);
11454
11473
  if (r.restored) {
11455
11474
  console.log(import_chalk19.default.green(" \u2714 Proxy restored and mock server stopped."));
@@ -11479,7 +11498,7 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11479
11498
  console.log(import_chalk19.default.yellow(` No DSL file found \u2014 skipping.`));
11480
11499
  continue;
11481
11500
  }
11482
- const dsl2 = await fs24.readJson(dslFile);
11501
+ const dsl2 = await fs23.readJson(dslFile);
11483
11502
  const result2 = await generateMockAssets(dsl2, repoAbsPath, {
11484
11503
  port,
11485
11504
  msw: opts.msw,
@@ -11503,11 +11522,11 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11503
11522
  );
11504
11523
  process.exit(1);
11505
11524
  }
11506
- console.log(import_chalk19.default.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11525
+ console.log(import_chalk19.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11507
11526
  }
11508
11527
  let dsl;
11509
11528
  try {
11510
- dsl = await fs24.readJson(dslPath);
11529
+ dsl = await fs23.readJson(dslPath);
11511
11530
  } catch (err) {
11512
11531
  console.error(import_chalk19.default.red(` Failed to read DSL file: ${err.message}`));
11513
11532
  process.exit(1);
@@ -11524,8 +11543,8 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11524
11543
  console.log(import_chalk19.default.gray(` ${f.description}`));
11525
11544
  }
11526
11545
  if (opts.serve) {
11527
- const serverJsPath = path23.join(currentDir, "mock", "server.js");
11528
- if (!await fs24.pathExists(serverJsPath)) {
11546
+ const serverJsPath = path22.join(currentDir, "mock", "server.js");
11547
+ if (!await fs23.pathExists(serverJsPath)) {
11529
11548
  console.error(import_chalk19.default.red(" mock/server.js not found \u2014 generation may have failed."));
11530
11549
  process.exit(1);
11531
11550
  }
@@ -11533,7 +11552,7 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11533
11552
  console.log(import_chalk19.default.green(`
11534
11553
  \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${port}`));
11535
11554
  if (opts.frontend) {
11536
- const frontendDir = path23.resolve(opts.frontend);
11555
+ const frontendDir = path22.resolve(opts.frontend);
11537
11556
  const proxyResult = await applyMockProxy(frontendDir, port, dsl.endpoints);
11538
11557
  await saveMockServerPid(frontendDir, pid);
11539
11558
  if (proxyResult.applied) {
@@ -11605,13 +11624,5 @@ program.command("restore").description("Restore files modified by a previous run
11605
11624
  \u2714 ${restored.length} file(s) restored.`));
11606
11625
  }
11607
11626
  });
11608
- if (process.argv.length <= 2) {
11609
- (async () => {
11610
- const currentDir = process.cwd();
11611
- const config2 = await loadConfig(currentDir);
11612
- await printWelcome(currentDir, config2);
11613
- })();
11614
- } else {
11615
- program.parse();
11616
- }
11627
+ program.parse();
11617
11628
  //# sourceMappingURL=index.js.map