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.
@@ -402,8 +402,8 @@ var init_workspace_loader = __esm({
402
402
  const repos = [];
403
403
  for (const entry of entries) {
404
404
  const absPath = path17.join(this.workspaceRoot, entry);
405
- const stat4 = await fs18.stat(absPath).catch(() => null);
406
- if (!stat4 || !stat4.isDirectory()) continue;
405
+ const stat3 = await fs18.stat(absPath).catch(() => null);
406
+ if (!stat3 || !stat3.isDirectory()) continue;
407
407
  if (entry.startsWith(".") || entry === "node_modules") continue;
408
408
  if (names && !names.includes(entry)) continue;
409
409
  const hasManifest = await fs18.pathExists(path17.join(absPath, "package.json")) || await fs18.pathExists(path17.join(absPath, "go.mod")) || await fs18.pathExists(path17.join(absPath, "Cargo.toml")) || await fs18.pathExists(path17.join(absPath, "pom.xml")) || await fs18.pathExists(path17.join(absPath, "build.gradle")) || await fs18.pathExists(path17.join(absPath, "requirements.txt")) || await fs18.pathExists(path17.join(absPath, "pyproject.toml")) || await fs18.pathExists(path17.join(absPath, "composer.json"));
@@ -467,8 +467,8 @@ var init_workspace_loader = __esm({
467
467
 
468
468
  // cli/index.ts
469
469
  import { Command } from "commander";
470
- import * as path23 from "path";
471
- import * as fs24 from "fs-extra";
470
+ import * as path22 from "path";
471
+ import * as fs23 from "fs-extra";
472
472
  import chalk19 from "chalk";
473
473
  import * as dotenv from "dotenv";
474
474
  import { input, confirm as confirm2, select as select3 } from "@inquirer/prompts";
@@ -4849,221 +4849,221 @@ function validateDsl(raw) {
4849
4849
  }
4850
4850
  return { valid: true, dsl: raw };
4851
4851
  }
4852
- function validateFeature(raw, path24, errors) {
4852
+ function validateFeature(raw, path23, errors) {
4853
4853
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4854
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4854
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4855
4855
  return;
4856
4856
  }
4857
4857
  const f = raw;
4858
- requireNonEmptyString(f["id"], `${path24}.id`, errors);
4859
- requireNonEmptyString(f["title"], `${path24}.title`, errors);
4860
- requireNonEmptyString(f["description"], `${path24}.description`, errors);
4858
+ requireNonEmptyString(f["id"], `${path23}.id`, errors);
4859
+ requireNonEmptyString(f["title"], `${path23}.title`, errors);
4860
+ requireNonEmptyString(f["description"], `${path23}.description`, errors);
4861
4861
  }
4862
- function validateModel(raw, path24, errors) {
4862
+ function validateModel(raw, path23, errors) {
4863
4863
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4864
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4864
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4865
4865
  return;
4866
4866
  }
4867
4867
  const m = raw;
4868
- requireNonEmptyString(m["name"], `${path24}.name`, errors);
4868
+ requireNonEmptyString(m["name"], `${path23}.name`, errors);
4869
4869
  if (!Array.isArray(m["fields"])) {
4870
- errors.push({ path: `${path24}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4870
+ errors.push({ path: `${path23}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4871
4871
  } else {
4872
4872
  const fields = m["fields"];
4873
4873
  if (fields.length > MAX_FIELDS_PER_MODEL) {
4874
- errors.push({ path: `${path24}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4874
+ errors.push({ path: `${path23}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4875
4875
  }
4876
4876
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
4877
- validateModelField(fields[j2], `${path24}.fields[${j2}]`, errors);
4877
+ validateModelField(fields[j2], `${path23}.fields[${j2}]`, errors);
4878
4878
  }
4879
4879
  }
4880
4880
  if (m["relations"] !== void 0) {
4881
4881
  if (!Array.isArray(m["relations"])) {
4882
- errors.push({ path: `${path24}.relations`, message: "Must be an array of strings if present" });
4882
+ errors.push({ path: `${path23}.relations`, message: "Must be an array of strings if present" });
4883
4883
  } else {
4884
4884
  const rels = m["relations"];
4885
4885
  for (let j2 = 0; j2 < rels.length; j2++) {
4886
4886
  if (typeof rels[j2] !== "string") {
4887
- errors.push({ path: `${path24}.relations[${j2}]`, message: "Must be a string" });
4887
+ errors.push({ path: `${path23}.relations[${j2}]`, message: "Must be a string" });
4888
4888
  }
4889
4889
  }
4890
4890
  }
4891
4891
  }
4892
4892
  }
4893
- function validateModelField(raw, path24, errors) {
4893
+ function validateModelField(raw, path23, errors) {
4894
4894
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4895
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4895
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4896
4896
  return;
4897
4897
  }
4898
4898
  const f = raw;
4899
- requireNonEmptyString(f["name"], `${path24}.name`, errors);
4900
- requireNonEmptyString(f["type"], `${path24}.type`, errors);
4899
+ requireNonEmptyString(f["name"], `${path23}.name`, errors);
4900
+ requireNonEmptyString(f["type"], `${path23}.type`, errors);
4901
4901
  if (typeof f["required"] !== "boolean") {
4902
- errors.push({ path: `${path24}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4902
+ errors.push({ path: `${path23}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4903
4903
  }
4904
4904
  }
4905
- function validateEndpoint(raw, path24, errors) {
4905
+ function validateEndpoint(raw, path23, errors) {
4906
4906
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4907
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4907
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4908
4908
  return;
4909
4909
  }
4910
4910
  const e = raw;
4911
- requireNonEmptyString(e["id"], `${path24}.id`, errors);
4912
- requireNonEmptyString(e["description"], `${path24}.description`, errors);
4911
+ requireNonEmptyString(e["id"], `${path23}.id`, errors);
4912
+ requireNonEmptyString(e["description"], `${path23}.description`, errors);
4913
4913
  if (!VALID_METHODS.includes(e["method"])) {
4914
4914
  errors.push({
4915
- path: `${path24}.method`,
4915
+ path: `${path23}.method`,
4916
4916
  message: `Must be one of ${VALID_METHODS.join("|")}, got: ${JSON.stringify(e["method"])}`
4917
4917
  });
4918
4918
  }
4919
4919
  if (typeof e["path"] !== "string" || !e["path"].startsWith("/")) {
4920
4920
  errors.push({
4921
- path: `${path24}.path`,
4921
+ path: `${path23}.path`,
4922
4922
  message: `Must be a string starting with "/", got: ${JSON.stringify(e["path"])}`
4923
4923
  });
4924
4924
  }
4925
4925
  if (typeof e["auth"] !== "boolean") {
4926
- errors.push({ path: `${path24}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4926
+ errors.push({ path: `${path23}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4927
4927
  }
4928
4928
  if (typeof e["successStatus"] !== "number" || e["successStatus"] < 100 || e["successStatus"] > 599) {
4929
4929
  errors.push({
4930
- path: `${path24}.successStatus`,
4930
+ path: `${path23}.successStatus`,
4931
4931
  message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["successStatus"])}`
4932
4932
  });
4933
4933
  }
4934
- requireNonEmptyString(e["successDescription"], `${path24}.successDescription`, errors);
4934
+ requireNonEmptyString(e["successDescription"], `${path23}.successDescription`, errors);
4935
4935
  if (e["request"] !== void 0) {
4936
- validateRequestSchema(e["request"], `${path24}.request`, errors);
4936
+ validateRequestSchema(e["request"], `${path23}.request`, errors);
4937
4937
  }
4938
4938
  if (e["errors"] !== void 0) {
4939
4939
  if (!Array.isArray(e["errors"])) {
4940
- errors.push({ path: `${path24}.errors`, message: "Must be an array if present" });
4940
+ errors.push({ path: `${path23}.errors`, message: "Must be an array if present" });
4941
4941
  } else {
4942
4942
  const errs = e["errors"];
4943
4943
  if (errs.length > MAX_ERRORS_PER_ENDPOINT) {
4944
- errors.push({ path: `${path24}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4944
+ errors.push({ path: `${path23}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4945
4945
  }
4946
4946
  for (let j2 = 0; j2 < Math.min(errs.length, MAX_ERRORS_PER_ENDPOINT); j2++) {
4947
- validateResponseError(errs[j2], `${path24}.errors[${j2}]`, errors);
4947
+ validateResponseError(errs[j2], `${path23}.errors[${j2}]`, errors);
4948
4948
  }
4949
4949
  }
4950
4950
  }
4951
4951
  }
4952
- function validateRequestSchema(raw, path24, errors) {
4952
+ function validateRequestSchema(raw, path23, errors) {
4953
4953
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4954
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4954
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4955
4955
  return;
4956
4956
  }
4957
4957
  const r = raw;
4958
4958
  for (const key of ["body", "query", "params"]) {
4959
4959
  if (r[key] !== void 0) {
4960
- validateFieldMap(r[key], `${path24}.${key}`, errors);
4960
+ validateFieldMap(r[key], `${path23}.${key}`, errors);
4961
4961
  }
4962
4962
  }
4963
4963
  }
4964
- function validateFieldMap(raw, path24, errors) {
4964
+ function validateFieldMap(raw, path23, errors) {
4965
4965
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4966
- errors.push({ path: path24, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4966
+ errors.push({ path: path23, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4967
4967
  return;
4968
4968
  }
4969
4969
  const map = raw;
4970
4970
  for (const [k2, v2] of Object.entries(map)) {
4971
4971
  if (typeof v2 !== "string") {
4972
- errors.push({ path: `${path24}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4972
+ errors.push({ path: `${path23}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4973
4973
  }
4974
4974
  }
4975
4975
  }
4976
- function validateResponseError(raw, path24, errors) {
4976
+ function validateResponseError(raw, path23, errors) {
4977
4977
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4978
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4978
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4979
4979
  return;
4980
4980
  }
4981
4981
  const e = raw;
4982
4982
  if (typeof e["status"] !== "number" || e["status"] < 100 || e["status"] > 599) {
4983
- errors.push({ path: `${path24}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
4983
+ errors.push({ path: `${path23}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
4984
4984
  }
4985
- requireNonEmptyString(e["code"], `${path24}.code`, errors);
4986
- requireNonEmptyString(e["description"], `${path24}.description`, errors);
4985
+ requireNonEmptyString(e["code"], `${path23}.code`, errors);
4986
+ requireNonEmptyString(e["description"], `${path23}.description`, errors);
4987
4987
  }
4988
- function validateBehavior(raw, path24, errors) {
4988
+ function validateBehavior(raw, path23, errors) {
4989
4989
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4990
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
4990
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
4991
4991
  return;
4992
4992
  }
4993
4993
  const b = raw;
4994
- requireNonEmptyString(b["id"], `${path24}.id`, errors);
4995
- requireNonEmptyString(b["description"], `${path24}.description`, errors);
4994
+ requireNonEmptyString(b["id"], `${path23}.id`, errors);
4995
+ requireNonEmptyString(b["description"], `${path23}.description`, errors);
4996
4996
  if (b["constraints"] !== void 0) {
4997
4997
  if (!Array.isArray(b["constraints"])) {
4998
- errors.push({ path: `${path24}.constraints`, message: "Must be an array of strings if present" });
4998
+ errors.push({ path: `${path23}.constraints`, message: "Must be an array of strings if present" });
4999
4999
  } else {
5000
5000
  const cs2 = b["constraints"];
5001
5001
  for (let j2 = 0; j2 < cs2.length; j2++) {
5002
5002
  if (typeof cs2[j2] !== "string") {
5003
- errors.push({ path: `${path24}.constraints[${j2}]`, message: "Must be a string" });
5003
+ errors.push({ path: `${path23}.constraints[${j2}]`, message: "Must be a string" });
5004
5004
  }
5005
5005
  }
5006
5006
  }
5007
5007
  }
5008
5008
  }
5009
- function validateComponent(raw, path24, errors) {
5009
+ function validateComponent(raw, path23, errors) {
5010
5010
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5011
- errors.push({ path: path24, message: `Must be an object, got: ${typeLabel(raw)}` });
5011
+ errors.push({ path: path23, message: `Must be an object, got: ${typeLabel(raw)}` });
5012
5012
  return;
5013
5013
  }
5014
5014
  const c = raw;
5015
- requireNonEmptyString(c["id"], `${path24}.id`, errors);
5016
- requireNonEmptyString(c["name"], `${path24}.name`, errors);
5017
- requireNonEmptyString(c["description"], `${path24}.description`, errors);
5015
+ requireNonEmptyString(c["id"], `${path23}.id`, errors);
5016
+ requireNonEmptyString(c["name"], `${path23}.name`, errors);
5017
+ requireNonEmptyString(c["description"], `${path23}.description`, errors);
5018
5018
  if (c["props"] !== void 0) {
5019
5019
  if (!Array.isArray(c["props"])) {
5020
- errors.push({ path: `${path24}.props`, message: "Must be an array if present" });
5020
+ errors.push({ path: `${path23}.props`, message: "Must be an array if present" });
5021
5021
  } else {
5022
5022
  const props = c["props"];
5023
5023
  for (let j2 = 0; j2 < props.length; j2++) {
5024
5024
  const p = props[j2];
5025
5025
  if (typeof p !== "object" || p === null) {
5026
- errors.push({ path: `${path24}.props[${j2}]`, message: "Must be an object" });
5026
+ errors.push({ path: `${path23}.props[${j2}]`, message: "Must be an object" });
5027
5027
  continue;
5028
5028
  }
5029
- requireNonEmptyString(p["name"], `${path24}.props[${j2}].name`, errors);
5030
- requireNonEmptyString(p["type"], `${path24}.props[${j2}].type`, errors);
5029
+ requireNonEmptyString(p["name"], `${path23}.props[${j2}].name`, errors);
5030
+ requireNonEmptyString(p["type"], `${path23}.props[${j2}].type`, errors);
5031
5031
  if (typeof p["required"] !== "boolean") {
5032
- errors.push({ path: `${path24}.props[${j2}].required`, message: "Must be boolean" });
5032
+ errors.push({ path: `${path23}.props[${j2}].required`, message: "Must be boolean" });
5033
5033
  }
5034
5034
  }
5035
5035
  }
5036
5036
  }
5037
5037
  if (c["events"] !== void 0) {
5038
5038
  if (!Array.isArray(c["events"])) {
5039
- errors.push({ path: `${path24}.events`, message: "Must be an array if present" });
5039
+ errors.push({ path: `${path23}.events`, message: "Must be an array if present" });
5040
5040
  } else {
5041
5041
  const events = c["events"];
5042
5042
  for (let j2 = 0; j2 < events.length; j2++) {
5043
5043
  const e = events[j2];
5044
5044
  if (typeof e !== "object" || e === null) {
5045
- errors.push({ path: `${path24}.events[${j2}]`, message: "Must be an object" });
5045
+ errors.push({ path: `${path23}.events[${j2}]`, message: "Must be an object" });
5046
5046
  continue;
5047
5047
  }
5048
- requireNonEmptyString(e["name"], `${path24}.events[${j2}].name`, errors);
5048
+ requireNonEmptyString(e["name"], `${path23}.events[${j2}].name`, errors);
5049
5049
  }
5050
5050
  }
5051
5051
  }
5052
5052
  if (c["state"] !== void 0) {
5053
5053
  if (typeof c["state"] !== "object" || Array.isArray(c["state"]) || c["state"] === null) {
5054
- errors.push({ path: `${path24}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5054
+ errors.push({ path: `${path23}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5055
5055
  }
5056
5056
  }
5057
5057
  if (c["apiCalls"] !== void 0) {
5058
5058
  if (!Array.isArray(c["apiCalls"])) {
5059
- errors.push({ path: `${path24}.apiCalls`, message: "Must be an array of strings if present" });
5059
+ errors.push({ path: `${path23}.apiCalls`, message: "Must be an array of strings if present" });
5060
5060
  }
5061
5061
  }
5062
5062
  }
5063
- function requireNonEmptyString(v2, path24, errors) {
5063
+ function requireNonEmptyString(v2, path23, errors) {
5064
5064
  if (typeof v2 !== "string" || v2.trim().length === 0) {
5065
5065
  errors.push({
5066
- path: path24,
5066
+ path: path23,
5067
5067
  message: `Must be a non-empty string, got: ${typeLabel(v2)}`
5068
5068
  });
5069
5069
  }
@@ -6106,6 +6106,16 @@ var RunLogger = class {
6106
6106
  this.log.errors.push(`[${event}] ${error}`);
6107
6107
  this.flush();
6108
6108
  }
6109
+ /** Record the prompt hash for this run (call once at run start). */
6110
+ setPromptHash(hash) {
6111
+ this.log.promptHash = hash;
6112
+ this.flush();
6113
+ }
6114
+ /** Record the harness self-eval score (call once at run end). */
6115
+ setHarnessScore(score) {
6116
+ this.log.harnessScore = score;
6117
+ this.flush();
6118
+ }
6109
6119
  fileWritten(filePath) {
6110
6120
  if (!this.log.filesWritten.includes(filePath)) {
6111
6121
  this.log.filesWritten.push(filePath);
@@ -8318,108 +8328,6 @@ async function clearKey(provider) {
8318
8328
  await writeStore(store);
8319
8329
  }
8320
8330
 
8321
- // cli/welcome.ts
8322
- import chalk17 from "chalk";
8323
- import * as os4 from "os";
8324
- import * as path19 from "path";
8325
- import * as fs20 from "fs-extra";
8326
- var VERSION = "0.14.1";
8327
- var TOTAL_W = 76;
8328
- var L_WIDTH = 44;
8329
- var R_WIDTH = TOTAL_W - L_WIDTH - 4;
8330
- var ROBOT_COLOR = chalk17.hex("#E8885A");
8331
- var ROBOT = [
8332
- ROBOT_COLOR(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),
8333
- ROBOT_COLOR(" \u2502") + chalk17.bold.white(" \u25C9 ") + ROBOT_COLOR(" ") + chalk17.bold.white("\u25C9 ") + ROBOT_COLOR("\u2502"),
8334
- ROBOT_COLOR(" \u2502") + chalk17.dim(" \u2570\u2500\u256F ") + ROBOT_COLOR("\u2502"),
8335
- ROBOT_COLOR(" \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2518"),
8336
- ROBOT_COLOR(" \u2502"),
8337
- ROBOT_COLOR(" \u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500")
8338
- ];
8339
- function visLen(s) {
8340
- return s.replace(/\x1b\[[0-9;]*m/g, "").length;
8341
- }
8342
- function padR(s, width) {
8343
- const vl = visLen(s);
8344
- return vl >= width ? s : s + " ".repeat(width - vl);
8345
- }
8346
- function row(left, right) {
8347
- return padR(left, L_WIDTH) + " " + chalk17.gray("\u2502") + " " + padR(right, R_WIDTH);
8348
- }
8349
- function center(s, width) {
8350
- const vl = visLen(s);
8351
- const pad = Math.max(0, Math.floor((width - vl) / 2));
8352
- return " ".repeat(pad) + s;
8353
- }
8354
- async function getRecentSpecs(dir) {
8355
- const specsDir = path19.join(dir, "specs");
8356
- if (!await fs20.pathExists(specsDir)) return [];
8357
- try {
8358
- const files = await fs20.readdir(specsDir);
8359
- const mdFiles = files.filter((f) => f.endsWith(".md"));
8360
- const withStats = await Promise.all(
8361
- mdFiles.map(async (f) => {
8362
- const stat4 = await fs20.stat(path19.join(specsDir, f));
8363
- return { name: f, mtime: stat4.mtime.getTime() };
8364
- })
8365
- );
8366
- withStats.sort((a, b) => b.mtime - a.mtime);
8367
- return withStats.slice(0, 3).map(({ name, mtime }) => {
8368
- const ms2 = Date.now() - mtime;
8369
- const hours = Math.floor(ms2 / 36e5);
8370
- const days = Math.floor(hours / 24);
8371
- const age = days > 0 ? `${days}d ago` : hours > 0 ? `${hours}h ago` : "just now";
8372
- const slug = name.replace(/\.md$/, "").slice(0, R_WIDTH - age.length - 2);
8373
- return chalk17.white(slug) + chalk17.dim(" " + age);
8374
- });
8375
- } catch {
8376
- return [];
8377
- }
8378
- }
8379
- async function printWelcome(currentDir, config2) {
8380
- const username = os4.userInfo().username;
8381
- const homeDir = os4.homedir();
8382
- const shortDir = currentDir.startsWith(homeDir) ? "~" + currentDir.slice(homeDir.length) : currentDir;
8383
- const recentSpecs = await getRecentSpecs(currentDir);
8384
- const providerBit = config2?.provider ? config2.provider + (config2.model ? " \xB7 " + config2.model : "") : "";
8385
- const bottomRaw = [providerBit, shortDir].filter(Boolean).join(" \xB7 ");
8386
- const maxInfoLen = L_WIDTH - 2;
8387
- const bottomTruncated = bottomRaw.length > maxInfoLen ? bottomRaw.slice(0, maxInfoLen - 1) + "\u2026" : bottomRaw;
8388
- const bottomLine = " " + chalk17.dim(bottomTruncated);
8389
- const titleInner = `ai-spec v${VERSION} `;
8390
- const titleDashes = "\u2500".repeat(Math.max(0, TOTAL_W - titleInner.length - 4));
8391
- console.log(
8392
- "\n" + chalk17.hex("#FF6B35")("\u2500\u2500\u2500 " + titleInner + titleDashes)
8393
- );
8394
- const welcomeText = "Welcome back, " + chalk17.bold.white(username) + "!";
8395
- const leftLines = [
8396
- "",
8397
- center(welcomeText, L_WIDTH),
8398
- "",
8399
- ...ROBOT,
8400
- "",
8401
- bottomLine,
8402
- ""
8403
- ];
8404
- const rightLines = [
8405
- chalk17.hex("#FF8C00").bold("Tips for getting started"),
8406
- chalk17.gray("\u2500".repeat(R_WIDTH)),
8407
- chalk17.white('ai-spec create "feature"'),
8408
- chalk17.gray("ai-spec workspace run"),
8409
- chalk17.gray('ai-spec update "change"'),
8410
- "",
8411
- chalk17.hex("#FF8C00").bold("Recent activity"),
8412
- chalk17.gray("\u2500".repeat(R_WIDTH)),
8413
- ...recentSpecs.length > 0 ? recentSpecs : [chalk17.dim("No recent activity")]
8414
- ];
8415
- const maxLines = Math.max(leftLines.length, rightLines.length);
8416
- for (let i = 0; i < maxLines; i++) {
8417
- console.log(row(leftLines[i] ?? "", rightLines[i] ?? ""));
8418
- }
8419
- console.log(chalk17.gray("\u2500".repeat(TOTAL_W)));
8420
- console.log();
8421
- }
8422
-
8423
8331
  // prompts/global-constitution.prompt.ts
8424
8332
  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.
8425
8333
 
@@ -8929,8 +8837,8 @@ Existing API/service files:`);
8929
8837
  }
8930
8838
 
8931
8839
  // core/mock-server-generator.ts
8932
- import * as path20 from "path";
8933
- import * as fs21 from "fs-extra";
8840
+ import * as path19 from "path";
8841
+ import * as fs20 from "fs-extra";
8934
8842
  import { spawn } from "child_process";
8935
8843
  function typeToFixture(fieldName, typeDesc) {
8936
8844
  const t = typeDesc.toLowerCase();
@@ -9066,22 +8974,22 @@ function generateMockServerJs(dsl, port) {
9066
8974
  }
9067
8975
  function detectFrontendFramework(projectDir) {
9068
8976
  for (const f of ["vite.config.ts", "vite.config.js", "vite.config.mts"]) {
9069
- if (fs21.existsSync(path20.join(projectDir, f))) return "vite";
8977
+ if (fs20.existsSync(path19.join(projectDir, f))) return "vite";
9070
8978
  }
9071
8979
  for (const f of ["next.config.js", "next.config.ts", "next.config.mjs"]) {
9072
- if (fs21.existsSync(path20.join(projectDir, f))) return "next";
8980
+ if (fs20.existsSync(path19.join(projectDir, f))) return "next";
9073
8981
  }
9074
- const pkgPath = path20.join(projectDir, "package.json");
9075
- if (fs21.existsSync(pkgPath)) {
8982
+ const pkgPath = path19.join(projectDir, "package.json");
8983
+ if (fs20.existsSync(pkgPath)) {
9076
8984
  try {
9077
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
8985
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
9078
8986
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
9079
8987
  if (deps["react-scripts"]) return "cra";
9080
8988
  } catch {
9081
8989
  }
9082
8990
  }
9083
8991
  for (const f of ["webpack.config.js", "webpack.config.ts"]) {
9084
- if (fs21.existsSync(path20.join(projectDir, f))) return "webpack";
8992
+ if (fs20.existsSync(path19.join(projectDir, f))) return "webpack";
9085
8993
  }
9086
8994
  return "unknown";
9087
8995
  }
@@ -9261,7 +9169,7 @@ export const worker = setupWorker(...handlers);
9261
9169
  var MOCK_LOCK_FILE = ".ai-spec-mock.lock.json";
9262
9170
  function findViteConfigFile(projectDir) {
9263
9171
  for (const f of ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]) {
9264
- if (fs21.existsSync(path20.join(projectDir, f))) return f;
9172
+ if (fs20.existsSync(path19.join(projectDir, f))) return f;
9265
9173
  }
9266
9174
  return null;
9267
9175
  }
@@ -9307,73 +9215,73 @@ async function applyMockProxy(frontendDir, mockPort, endpoints = []) {
9307
9215
  if (framework === "vite") {
9308
9216
  const viteConfigFile = findViteConfigFile(frontendDir) ?? "vite.config.ts";
9309
9217
  const mockConfigContent = generateViteMockConfigTs(viteConfigFile, mockPort, endpoints);
9310
- const mockConfigPath = path20.join(frontendDir, "vite.config.ai-spec-mock.ts");
9311
- await fs21.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9218
+ const mockConfigPath = path19.join(frontendDir, "vite.config.ai-spec-mock.ts");
9219
+ await fs20.writeFile(mockConfigPath, mockConfigContent, "utf-8");
9312
9220
  actions.push({ type: "wrote-file", filePath: "vite.config.ai-spec-mock.ts" });
9313
- const pkgPath = path20.join(frontendDir, "package.json");
9314
- if (await fs21.pathExists(pkgPath)) {
9315
- const pkg = await fs21.readJson(pkgPath);
9221
+ const pkgPath = path19.join(frontendDir, "package.json");
9222
+ if (await fs20.pathExists(pkgPath)) {
9223
+ const pkg = await fs20.readJson(pkgPath);
9316
9224
  pkg.scripts = pkg.scripts ?? {};
9317
9225
  const originalValue = pkg.scripts["dev:mock"] ?? null;
9318
9226
  pkg.scripts["dev:mock"] = "vite --config vite.config.ai-spec-mock.ts";
9319
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9227
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9320
9228
  actions.push({ type: "added-pkg-script", key: "dev:mock", originalValue });
9321
9229
  }
9322
9230
  const lock2 = { framework, mockPort, frontendDir, actions };
9323
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9231
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9324
9232
  return { framework, applied: true, devCommand: "npm run dev:mock" };
9325
9233
  }
9326
9234
  if (framework === "cra") {
9327
- const pkgPath = path20.join(frontendDir, "package.json");
9328
- if (await fs21.pathExists(pkgPath)) {
9329
- const pkg = await fs21.readJson(pkgPath);
9235
+ const pkgPath = path19.join(frontendDir, "package.json");
9236
+ if (await fs20.pathExists(pkgPath)) {
9237
+ const pkg = await fs20.readJson(pkgPath);
9330
9238
  const originalProxy = pkg.proxy ?? null;
9331
9239
  pkg.proxy = `http://localhost:${mockPort}`;
9332
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9240
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9333
9241
  actions.push({ type: "patched-pkg-proxy", originalProxy });
9334
9242
  const lock2 = { framework, mockPort, frontendDir, actions };
9335
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9243
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock2, { spaces: 2 });
9336
9244
  return { framework, applied: true, devCommand: "npm start" };
9337
9245
  }
9338
9246
  return { framework, applied: false, devCommand: null, note: "No package.json found." };
9339
9247
  }
9340
9248
  const lock = { framework, mockPort, frontendDir, actions };
9341
- await fs21.writeJson(path20.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9249
+ await fs20.writeJson(path19.join(frontendDir, MOCK_LOCK_FILE), lock, { spaces: 2 });
9342
9250
  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}`;
9343
9251
  return { framework, applied: false, devCommand: null, note: manualNote };
9344
9252
  }
9345
9253
  async function restoreMockProxy(frontendDir) {
9346
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9347
- if (!await fs21.pathExists(lockPath)) {
9254
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9255
+ if (!await fs20.pathExists(lockPath)) {
9348
9256
  return { restored: false, note: "No lock file found \u2014 nothing to restore." };
9349
9257
  }
9350
- const lock = await fs21.readJson(lockPath);
9258
+ const lock = await fs20.readJson(lockPath);
9351
9259
  for (const action of lock.actions) {
9352
9260
  if (action.type === "wrote-file") {
9353
- const fp = path20.join(frontendDir, action.filePath);
9354
- if (await fs21.pathExists(fp)) await fs21.remove(fp);
9261
+ const fp = path19.join(frontendDir, action.filePath);
9262
+ if (await fs20.pathExists(fp)) await fs20.remove(fp);
9355
9263
  } else if (action.type === "added-pkg-script") {
9356
- const pkgPath = path20.join(frontendDir, "package.json");
9357
- if (await fs21.pathExists(pkgPath)) {
9358
- const pkg = await fs21.readJson(pkgPath);
9264
+ const pkgPath = path19.join(frontendDir, "package.json");
9265
+ if (await fs20.pathExists(pkgPath)) {
9266
+ const pkg = await fs20.readJson(pkgPath);
9359
9267
  if (action.originalValue == null) {
9360
9268
  delete pkg.scripts?.[action.key];
9361
9269
  } else {
9362
9270
  pkg.scripts = pkg.scripts ?? {};
9363
9271
  pkg.scripts[action.key] = action.originalValue;
9364
9272
  }
9365
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9273
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9366
9274
  }
9367
9275
  } else if (action.type === "patched-pkg-proxy") {
9368
- const pkgPath = path20.join(frontendDir, "package.json");
9369
- if (await fs21.pathExists(pkgPath)) {
9370
- const pkg = await fs21.readJson(pkgPath);
9276
+ const pkgPath = path19.join(frontendDir, "package.json");
9277
+ if (await fs20.pathExists(pkgPath)) {
9278
+ const pkg = await fs20.readJson(pkgPath);
9371
9279
  if (action.originalProxy == null) {
9372
9280
  delete pkg.proxy;
9373
9281
  } else {
9374
9282
  pkg.proxy = action.originalProxy;
9375
9283
  }
9376
- await fs21.writeJson(pkgPath, pkg, { spaces: 2 });
9284
+ await fs20.writeJson(pkgPath, pkg, { spaces: 2 });
9377
9285
  }
9378
9286
  }
9379
9287
  }
@@ -9383,7 +9291,7 @@ async function restoreMockProxy(frontendDir) {
9383
9291
  } catch {
9384
9292
  }
9385
9293
  }
9386
- await fs21.remove(lockPath);
9294
+ await fs20.remove(lockPath);
9387
9295
  return { restored: true };
9388
9296
  }
9389
9297
  function startMockServerBackground(serverJsPath, port) {
@@ -9396,23 +9304,23 @@ function startMockServerBackground(serverJsPath, port) {
9396
9304
  return child.pid;
9397
9305
  }
9398
9306
  async function saveMockServerPid(frontendDir, pid) {
9399
- const lockPath = path20.join(frontendDir, MOCK_LOCK_FILE);
9400
- if (await fs21.pathExists(lockPath)) {
9401
- const lock = await fs21.readJson(lockPath);
9307
+ const lockPath = path19.join(frontendDir, MOCK_LOCK_FILE);
9308
+ if (await fs20.pathExists(lockPath)) {
9309
+ const lock = await fs20.readJson(lockPath);
9402
9310
  lock.mockServerPid = pid;
9403
- await fs21.writeJson(lockPath, lock, { spaces: 2 });
9311
+ await fs20.writeJson(lockPath, lock, { spaces: 2 });
9404
9312
  }
9405
9313
  }
9406
9314
  async function generateMockAssets(dsl, projectDir, opts = {}) {
9407
9315
  const port = opts.port ?? 3001;
9408
- const outputDir = path20.join(projectDir, opts.outputDir ?? "mock");
9316
+ const outputDir = path19.join(projectDir, opts.outputDir ?? "mock");
9409
9317
  const result = { files: [] };
9410
- await fs21.ensureDir(outputDir);
9318
+ await fs20.ensureDir(outputDir);
9411
9319
  const serverJs = generateMockServerJs(dsl, port);
9412
- const serverPath = path20.join(outputDir, "server.js");
9413
- await fs21.writeFile(serverPath, serverJs, "utf-8");
9320
+ const serverPath = path19.join(outputDir, "server.js");
9321
+ await fs20.writeFile(serverPath, serverJs, "utf-8");
9414
9322
  result.files.push({
9415
- path: path20.relative(projectDir, serverPath),
9323
+ path: path19.relative(projectDir, serverPath),
9416
9324
  description: `Express mock server \u2014 run with: node mock/server.js`
9417
9325
  });
9418
9326
  const mockReadme = `# Mock Server
@@ -9442,52 +9350,52 @@ ${dsl.endpoints.map(
9442
9350
 
9443
9351
  Append \`?simulate_error=1\` to any request to test error handling (not yet auto-wired \u2014 edit server.js manually).
9444
9352
  `;
9445
- const readmePath = path20.join(outputDir, "README.md");
9446
- await fs21.writeFile(readmePath, mockReadme, "utf-8");
9353
+ const readmePath = path19.join(outputDir, "README.md");
9354
+ await fs20.writeFile(readmePath, mockReadme, "utf-8");
9447
9355
  result.files.push({
9448
- path: path20.relative(projectDir, readmePath),
9356
+ path: path19.relative(projectDir, readmePath),
9449
9357
  description: "Mock server usage guide"
9450
9358
  });
9451
9359
  if (opts.proxy) {
9452
9360
  const { content, filename } = generateProxyConfig(dsl, port, projectDir);
9453
- const proxyPath = path20.join(projectDir, filename);
9454
- await fs21.ensureDir(path20.dirname(proxyPath));
9455
- await fs21.writeFile(proxyPath, content, "utf-8");
9361
+ const proxyPath = path19.join(projectDir, filename);
9362
+ await fs20.ensureDir(path19.dirname(proxyPath));
9363
+ await fs20.writeFile(proxyPath, content, "utf-8");
9456
9364
  result.files.push({
9457
9365
  path: filename,
9458
9366
  description: "Proxy config snippet \u2014 copy instructions into your framework config"
9459
9367
  });
9460
9368
  }
9461
9369
  if (opts.msw) {
9462
- const mswDir = path20.join(projectDir, "src", "mocks");
9463
- await fs21.ensureDir(mswDir);
9370
+ const mswDir = path19.join(projectDir, "src", "mocks");
9371
+ await fs20.ensureDir(mswDir);
9464
9372
  const handlersContent = generateMswHandlers(dsl);
9465
- const handlersPath = path20.join(mswDir, "handlers.ts");
9466
- await fs21.writeFile(handlersPath, handlersContent, "utf-8");
9373
+ const handlersPath = path19.join(mswDir, "handlers.ts");
9374
+ await fs20.writeFile(handlersPath, handlersContent, "utf-8");
9467
9375
  result.files.push({
9468
- path: path20.relative(projectDir, handlersPath),
9376
+ path: path19.relative(projectDir, handlersPath),
9469
9377
  description: "MSW request handlers"
9470
9378
  });
9471
9379
  const browserContent = generateMswBrowser();
9472
- const browserPath = path20.join(mswDir, "browser.ts");
9473
- await fs21.writeFile(browserPath, browserContent, "utf-8");
9380
+ const browserPath = path19.join(mswDir, "browser.ts");
9381
+ await fs20.writeFile(browserPath, browserContent, "utf-8");
9474
9382
  result.files.push({
9475
- path: path20.relative(projectDir, browserPath),
9383
+ path: path19.relative(projectDir, browserPath),
9476
9384
  description: "MSW browser worker setup"
9477
9385
  });
9478
9386
  }
9479
9387
  return result;
9480
9388
  }
9481
9389
  async function findLatestDslFile(projectDir) {
9482
- const specDir = path20.join(projectDir, ".ai-spec");
9483
- if (!await fs21.pathExists(specDir)) return null;
9390
+ const specDir = path19.join(projectDir, ".ai-spec");
9391
+ if (!await fs20.pathExists(specDir)) return null;
9484
9392
  const allFiles = [];
9485
9393
  async function scan(dir) {
9486
- const entries = await fs21.readdir(dir);
9394
+ const entries = await fs20.readdir(dir);
9487
9395
  for (const entry of entries) {
9488
- const abs = path20.join(dir, entry);
9489
- const stat4 = await fs21.stat(abs);
9490
- if (stat4.isDirectory()) {
9396
+ const abs = path19.join(dir, entry);
9397
+ const stat3 = await fs20.stat(abs);
9398
+ if (stat3.isDirectory()) {
9491
9399
  await scan(abs);
9492
9400
  } else if (entry.endsWith(".dsl.json")) {
9493
9401
  allFiles.push(abs);
@@ -9497,16 +9405,16 @@ async function findLatestDslFile(projectDir) {
9497
9405
  await scan(specDir);
9498
9406
  if (allFiles.length === 0) return null;
9499
9407
  const withMtimes = await Promise.all(
9500
- allFiles.map(async (f) => ({ f, mtime: (await fs21.stat(f)).mtime }))
9408
+ allFiles.map(async (f) => ({ f, mtime: (await fs20.stat(f)).mtime }))
9501
9409
  );
9502
9410
  withMtimes.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
9503
9411
  return withMtimes[0].f;
9504
9412
  }
9505
9413
 
9506
9414
  // core/spec-updater.ts
9507
- import chalk18 from "chalk";
9508
- import * as path21 from "path";
9509
- import * as fs22 from "fs-extra";
9415
+ import chalk17 from "chalk";
9416
+ import * as path20 from "path";
9417
+ import * as fs21 from "fs-extra";
9510
9418
 
9511
9419
  // prompts/update.prompt.ts
9512
9420
  var specUpdateSystemPrompt = `You are a Senior Software Architect updating an existing Feature Spec based on a change request.
@@ -9652,8 +9560,8 @@ var SpecUpdater = class {
9652
9560
  * Returns all .md spec files sorted newest-first.
9653
9561
  */
9654
9562
  static async findLatestSpec(specsDir) {
9655
- if (!await fs22.pathExists(specsDir)) return null;
9656
- const files = await fs22.readdir(specsDir);
9563
+ if (!await fs21.pathExists(specsDir)) return null;
9564
+ const files = await fs21.readdir(specsDir);
9657
9565
  const pattern = /^feature-(.+)-v(\d+)\.md$/;
9658
9566
  let latest = null;
9659
9567
  for (const file of files) {
@@ -9661,8 +9569,8 @@ var SpecUpdater = class {
9661
9569
  if (!m) continue;
9662
9570
  const version = parseInt(m[2], 10);
9663
9571
  if (!latest || version > latest.version) {
9664
- const filePath = path21.join(specsDir, file);
9665
- const content = await fs22.readFile(filePath, "utf-8");
9572
+ const filePath = path20.join(specsDir, file);
9573
+ const content = await fs21.readFile(filePath, "utf-8");
9666
9574
  latest = { filePath, version, slug: m[1], content };
9667
9575
  }
9668
9576
  }
@@ -9673,16 +9581,16 @@ var SpecUpdater = class {
9673
9581
  * Generates a new version of the spec, re-extracts the DSL, and identifies affected files.
9674
9582
  */
9675
9583
  async update(changeRequest, existingSpecPath, projectDir, context, opts = {}) {
9676
- const existingSpec = await fs22.readFile(existingSpecPath, "utf-8");
9584
+ const existingSpec = await fs21.readFile(existingSpecPath, "utf-8");
9677
9585
  let existingDsl = null;
9678
9586
  const dslFile = await findLatestDslFile(projectDir);
9679
9587
  if (dslFile) {
9680
9588
  try {
9681
- existingDsl = await fs22.readJson(dslFile);
9589
+ existingDsl = await fs21.readJson(dslFile);
9682
9590
  } catch {
9683
9591
  }
9684
9592
  }
9685
- console.log(chalk18.blue(" [1/3] Generating updated spec..."));
9593
+ console.log(chalk17.blue(" [1/3] Generating updated spec..."));
9686
9594
  const updatePrompt = buildSpecUpdatePrompt(changeRequest, existingSpec, existingDsl, context);
9687
9595
  let updatedSpecContent;
9688
9596
  try {
@@ -9691,15 +9599,15 @@ var SpecUpdater = class {
9691
9599
  } catch (err) {
9692
9600
  throw new Error(`Spec update generation failed: ${err.message}`);
9693
9601
  }
9694
- const specBasename = path21.basename(existingSpecPath);
9602
+ const specBasename = path20.basename(existingSpecPath);
9695
9603
  const slugMatch = specBasename.match(/^feature-(.+)-v\d+\.md$/);
9696
9604
  const slug = slugMatch ? slugMatch[1] : "feature";
9697
- const specsDir = path21.dirname(existingSpecPath);
9605
+ const specsDir = path20.dirname(existingSpecPath);
9698
9606
  const { filePath: newSpecPath, version: newVersion } = await nextVersionPath(specsDir, slug);
9699
- await fs22.ensureDir(specsDir);
9700
- await fs22.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9701
- console.log(chalk18.green(` \u2714 New spec written: ${path21.relative(projectDir, newSpecPath)}`));
9702
- console.log(chalk18.blue(" [2/3] Updating DSL..."));
9607
+ await fs21.ensureDir(specsDir);
9608
+ await fs21.writeFile(newSpecPath, updatedSpecContent, "utf-8");
9609
+ console.log(chalk17.green(` \u2714 New spec written: ${path20.relative(projectDir, newSpecPath)}`));
9610
+ console.log(chalk17.blue(" [2/3] Updating DSL..."));
9703
9611
  let updatedDsl = null;
9704
9612
  let newDslPath = null;
9705
9613
  if (existingDsl) {
@@ -9711,7 +9619,7 @@ var SpecUpdater = class {
9711
9619
  updatedDsl = parsed;
9712
9620
  }
9713
9621
  } catch {
9714
- console.log(chalk18.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9622
+ console.log(chalk17.gray(" Targeted DSL update failed \u2014 falling back to full extraction."));
9715
9623
  }
9716
9624
  }
9717
9625
  if (!updatedDsl) {
@@ -9720,15 +9628,15 @@ var SpecUpdater = class {
9720
9628
  }
9721
9629
  if (updatedDsl) {
9722
9630
  const dslPath = newSpecPath.replace(/\.md$/, ".dsl.json");
9723
- await fs22.writeJson(dslPath, updatedDsl, { spaces: 2 });
9631
+ await fs21.writeJson(dslPath, updatedDsl, { spaces: 2 });
9724
9632
  newDslPath = dslPath;
9725
- console.log(chalk18.green(` \u2714 DSL updated: ${path21.relative(projectDir, dslPath)}`));
9633
+ console.log(chalk17.green(` \u2714 DSL updated: ${path20.relative(projectDir, dslPath)}`));
9726
9634
  } else {
9727
- console.log(chalk18.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9635
+ console.log(chalk17.yellow(" \u26A0 DSL update failed \u2014 continuing without DSL."));
9728
9636
  }
9729
9637
  let affectedFiles = [];
9730
9638
  if (!opts.skipAffectedFiles && updatedDsl && existingDsl && context) {
9731
- console.log(chalk18.blue(" [3/3] Identifying affected files..."));
9639
+ console.log(chalk17.blue(" [3/3] Identifying affected files..."));
9732
9640
  const systemPrompt = getCodeGenSystemPrompt(opts.repoType);
9733
9641
  const affectedPrompt = buildAffectedFilesPrompt(
9734
9642
  changeRequest,
@@ -9739,9 +9647,9 @@ var SpecUpdater = class {
9739
9647
  try {
9740
9648
  const affectedRaw = await this.provider.generate(affectedPrompt, systemPrompt);
9741
9649
  affectedFiles = parseAffectedFiles(affectedRaw);
9742
- console.log(chalk18.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9650
+ console.log(chalk17.green(` \u2714 ${affectedFiles.length} file(s) identified for update`));
9743
9651
  } catch {
9744
- console.log(chalk18.gray(" Could not identify affected files \u2014 use manual selection."));
9652
+ console.log(chalk17.gray(" Could not identify affected files \u2014 use manual selection."));
9745
9653
  }
9746
9654
  }
9747
9655
  return { newSpecPath, newVersion, newDslPath, affectedFiles, updatedDsl };
@@ -9749,8 +9657,8 @@ var SpecUpdater = class {
9749
9657
  };
9750
9658
 
9751
9659
  // core/openapi-exporter.ts
9752
- import * as path22 from "path";
9753
- import * as fs23 from "fs-extra";
9660
+ import * as path21 from "path";
9661
+ import * as fs22 from "fs-extra";
9754
9662
  function dslTypeToOASchema(typeDesc, fieldName = "") {
9755
9663
  const t = typeDesc.toLowerCase();
9756
9664
  if (t === "string" || t.includes("string")) {
@@ -9979,7 +9887,7 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9979
9887
  const format = opts.format ?? "yaml";
9980
9888
  const serverUrl = opts.serverUrl ?? "http://localhost:3000";
9981
9889
  const defaultName = `openapi.${format}`;
9982
- const outputPath = opts.outputPath ? path22.isAbsolute(opts.outputPath) ? opts.outputPath : path22.join(projectDir, opts.outputPath) : path22.join(projectDir, defaultName);
9890
+ const outputPath = opts.outputPath ? path21.isAbsolute(opts.outputPath) ? opts.outputPath : path21.join(projectDir, opts.outputPath) : path21.join(projectDir, defaultName);
9983
9891
  const doc = dslToOpenApi(dsl, serverUrl);
9984
9892
  let content;
9985
9893
  if (format === "json") {
@@ -9987,18 +9895,115 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
9987
9895
  } else {
9988
9896
  content = buildYamlDoc(doc);
9989
9897
  }
9990
- await fs23.ensureDir(path22.dirname(outputPath));
9991
- await fs23.writeFile(outputPath, content, "utf-8");
9898
+ await fs22.ensureDir(path21.dirname(outputPath));
9899
+ await fs22.writeFile(outputPath, content, "utf-8");
9992
9900
  return outputPath;
9993
9901
  }
9994
9902
 
9903
+ // core/prompt-hasher.ts
9904
+ init_codegen_prompt();
9905
+ init_codegen_prompt();
9906
+ import { createHash } from "crypto";
9907
+ function computePromptHash() {
9908
+ const segments = [
9909
+ codeGenSystemPrompt,
9910
+ dslSystemPrompt,
9911
+ specPrompt,
9912
+ reviewArchitectureSystemPrompt,
9913
+ reviewImplementationSystemPrompt,
9914
+ reviewImpactComplexitySystemPrompt
9915
+ ];
9916
+ return createHash("sha256").update(segments.join("\0")).digest("hex").slice(0, 8);
9917
+ }
9918
+
9919
+ // core/self-evaluator.ts
9920
+ import chalk18 from "chalk";
9921
+ var ENDPOINT_LAYER_PATTERNS = [
9922
+ /src\/api/,
9923
+ /src\/routes?/,
9924
+ /src\/controller/,
9925
+ /src\/handler/,
9926
+ /src\/endpoints?/
9927
+ ];
9928
+ var MODEL_LAYER_PATTERNS = [
9929
+ /src\/model/,
9930
+ /src\/schema/,
9931
+ /src\/entit/,
9932
+ /src\/db/,
9933
+ /prisma/,
9934
+ /src\/data/,
9935
+ /src\/domain/
9936
+ ];
9937
+ function extractReviewScore(reviewText) {
9938
+ const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
9939
+ return match ? parseFloat(match[1]) : null;
9940
+ }
9941
+ function runSelfEval(opts) {
9942
+ const { dsl, generatedFiles, compilePassed, reviewText, promptHash, logger } = opts;
9943
+ const endpointsTotal = dsl?.endpoints?.length ?? 0;
9944
+ const modelsTotal = dsl?.models?.length ?? 0;
9945
+ const endpointLayerCovered = generatedFiles.some(
9946
+ (f) => ENDPOINT_LAYER_PATTERNS.some((p) => p.test(f))
9947
+ );
9948
+ const modelLayerCovered = generatedFiles.some(
9949
+ (f) => MODEL_LAYER_PATTERNS.some((p) => p.test(f))
9950
+ );
9951
+ let dslCoverageScore = 10;
9952
+ if (generatedFiles.length === 0) {
9953
+ dslCoverageScore = 0;
9954
+ } else {
9955
+ if (endpointsTotal > 0 && !endpointLayerCovered) dslCoverageScore -= 4;
9956
+ if (modelsTotal > 0 && !modelLayerCovered) dslCoverageScore -= 3;
9957
+ }
9958
+ const compileScore = compilePassed ? 10 : 5;
9959
+ const reviewScore = reviewText ? extractReviewScore(reviewText) : null;
9960
+ const harnessScore = reviewScore !== null ? Math.round((dslCoverageScore * 0.4 + compileScore * 0.3 + reviewScore * 0.3) * 10) / 10 : Math.round((dslCoverageScore * 0.55 + compileScore * 0.45) * 10) / 10;
9961
+ const result = {
9962
+ dslCoverageScore,
9963
+ compileScore,
9964
+ reviewScore,
9965
+ harnessScore,
9966
+ promptHash,
9967
+ detail: {
9968
+ endpointsTotal,
9969
+ endpointLayerCovered,
9970
+ modelsTotal,
9971
+ modelLayerCovered,
9972
+ filesWritten: generatedFiles.length
9973
+ }
9974
+ };
9975
+ logger.setHarnessScore(harnessScore);
9976
+ logger.stageEnd("self_eval", {
9977
+ harnessScore,
9978
+ dslCoverageScore,
9979
+ compileScore,
9980
+ reviewScore: reviewScore ?? void 0,
9981
+ promptHash
9982
+ });
9983
+ return result;
9984
+ }
9985
+ function printSelfEval(result) {
9986
+ const scoreColor = result.harnessScore >= 8 ? chalk18.green : result.harnessScore >= 6 ? chalk18.yellow : chalk18.red;
9987
+ const filled = Math.round(result.harnessScore);
9988
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
9989
+ const compileTag = result.compileScore === 10 ? chalk18.green("pass") : chalk18.yellow("partial");
9990
+ const reviewTag = result.reviewScore !== null ? `Review: ${result.reviewScore}/10` : chalk18.gray("Review: skipped");
9991
+ console.log(chalk18.cyan("\n\u2500\u2500\u2500 Harness Self-Eval \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
9992
+ console.log(` Score : ${scoreColor(`[${bar}] ${result.harnessScore}/10`)}`);
9993
+ console.log(
9994
+ ` DSL : ${scoreColor(result.dslCoverageScore + "/10")} Compile: ${compileTag} ${reviewTag}`
9995
+ );
9996
+ console.log(chalk18.gray(` Prompt : ${result.promptHash}`));
9997
+ console.log(chalk18.gray("\u2500".repeat(49)));
9998
+ }
9999
+
9995
10000
  // cli/index.ts
9996
10001
  dotenv.config();
9997
10002
  var CONFIG_FILE = ".ai-spec.json";
9998
10003
  async function loadConfig(dir) {
9999
- const p = path23.join(dir, CONFIG_FILE);
10000
- if (await fs24.pathExists(p)) {
10001
- return fs24.readJson(p);
10004
+ const p = path22.join(dir, CONFIG_FILE);
10005
+ if (await fs23.pathExists(p)) {
10006
+ return fs23.readJson(p);
10002
10007
  }
10003
10008
  return {};
10004
10009
  }
@@ -10076,7 +10081,7 @@ program.command("create").description("Generate a feature spec and kick off code
10076
10081
  } else {
10077
10082
  const mockPort = 3001;
10078
10083
  const mockResult = await generateMockAssets(backendResult.dsl, backendResult.repoAbsPath, { port: mockPort });
10079
- const serverJsPath = path23.join(backendResult.repoAbsPath, "mock", "server.js");
10084
+ const serverJsPath = path22.join(backendResult.repoAbsPath, "mock", "server.js");
10080
10085
  console.log(chalk19.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
10081
10086
  const pid = startMockServerBackground(serverJsPath, mockPort);
10082
10087
  console.log(chalk19.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
@@ -10127,6 +10132,8 @@ program.command("create").description("Generate a feature spec and kick off code
10127
10132
  model: specModelName
10128
10133
  });
10129
10134
  setActiveLogger(runLogger);
10135
+ const promptHash = computePromptHash();
10136
+ runLogger.setPromptHash(promptHash);
10130
10137
  console.log(chalk19.blue("[1/6] Loading project context..."));
10131
10138
  runLogger.stageStart("context_load");
10132
10139
  const loader = new ContextLoader(currentDir);
@@ -10240,7 +10247,7 @@ program.command("create").description("Generate a feature spec and kick off code
10240
10247
  const taskCountHint = initialTasks.length > 0 ? ` Tasks generated : ${initialTasks.length}` : "";
10241
10248
  console.log(chalk19.gray(` Spec length : ${specLines} lines / ${specWords} words`));
10242
10249
  if (taskCountHint) console.log(chalk19.gray(taskCountHint));
10243
- const previewSpecsDir = path23.join(currentDir, "specs");
10250
+ const previewSpecsDir = path22.join(currentDir, "specs");
10244
10251
  const slug = featureSlug;
10245
10252
  const prevVersion = await findLatestVersion(previewSpecsDir, slug);
10246
10253
  if (prevVersion) {
@@ -10318,10 +10325,10 @@ program.command("create").description("Generate a feature spec and kick off code
10318
10325
  const reason = opts.worktree ? "" : isFrontendProject2 ? " (frontend project \u2014 use --worktree to override)" : " (--skip-worktree)";
10319
10326
  console.log(chalk19.gray(`[4/6] Skipping worktree${reason}.`));
10320
10327
  }
10321
- const specsDir = path23.join(workingDir, "specs");
10322
- await fs24.ensureDir(specsDir);
10328
+ const specsDir = path22.join(workingDir, "specs");
10329
+ await fs23.ensureDir(specsDir);
10323
10330
  const { filePath: specFile, version: specVersion } = await nextVersionPath(specsDir, featureSlug);
10324
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10331
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10325
10332
  console.log(chalk19.green(`
10326
10333
  [5/6] \u2714 Spec saved: ${specFile}`) + chalk19.gray(` (v${specVersion})`));
10327
10334
  let savedDslFile = null;
@@ -10383,14 +10390,16 @@ program.command("create").description("Generate a feature spec and kick off code
10383
10390
  generatedTestFiles = await testGen.generate(extractedDsl, workingDir);
10384
10391
  runLogger.stageEnd("test_gen", { filesGenerated: generatedTestFiles.length });
10385
10392
  }
10393
+ let compilePassed = false;
10386
10394
  if (opts.skipErrorFeedback) {
10387
10395
  console.log(chalk19.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
10396
+ compilePassed = true;
10388
10397
  } else {
10389
10398
  if (opts.tdd) {
10390
10399
  console.log(chalk19.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
10391
10400
  }
10392
10401
  runLogger.stageStart("error_feedback");
10393
- await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
10402
+ compilePassed = await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
10394
10403
  maxCycles: opts.tdd ? 3 : 2
10395
10404
  // TDD gets one extra cycle
10396
10405
  });
@@ -10401,7 +10410,7 @@ program.command("create").description("Generate a feature spec and kick off code
10401
10410
  console.log(chalk19.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
10402
10411
  runLogger.stageStart("review");
10403
10412
  const reviewer = new CodeReviewer(specProvider, currentDir);
10404
- const savedSpec = await fs24.readFile(specFile, "utf-8");
10413
+ const savedSpec = await fs23.readFile(specFile, "utf-8");
10405
10414
  if (codegenMode === "api" && generatedFiles.length > 0) {
10406
10415
  reviewResult = await reviewer.reviewFiles(savedSpec, generatedFiles, workingDir, specFile);
10407
10416
  } else {
@@ -10416,6 +10425,16 @@ program.command("create").description("Generate a feature spec and kick off code
10416
10425
  runLogger.stageEnd("review");
10417
10426
  await accumulateReviewKnowledge(specProvider, currentDir, reviewResult);
10418
10427
  }
10428
+ runLogger.stageStart("self_eval");
10429
+ const selfEvalResult = runSelfEval({
10430
+ dsl: extractedDsl,
10431
+ generatedFiles,
10432
+ compilePassed,
10433
+ reviewText: reviewResult,
10434
+ promptHash,
10435
+ logger: runLogger
10436
+ });
10437
+ printSelfEval(selfEvalResult);
10419
10438
  runLogger.finish();
10420
10439
  console.log(chalk19.bold.green("\n\u2714 All done!"));
10421
10440
  console.log(chalk19.gray(` Spec : ${specFile}`));
@@ -10446,17 +10465,17 @@ program.command("review").description("Run AI code review on current git diff ag
10446
10465
  const reviewer = new CodeReviewer(provider, currentDir);
10447
10466
  let specContent = "";
10448
10467
  let resolvedSpecFile;
10449
- if (specFile && await fs24.pathExists(specFile)) {
10450
- specContent = await fs24.readFile(specFile, "utf-8");
10468
+ if (specFile && await fs23.pathExists(specFile)) {
10469
+ specContent = await fs23.readFile(specFile, "utf-8");
10451
10470
  resolvedSpecFile = specFile;
10452
10471
  console.log(chalk19.gray(`Using spec: ${specFile}`));
10453
10472
  } else {
10454
- const specsDir = path23.join(currentDir, "specs");
10455
- if (await fs24.pathExists(specsDir)) {
10456
- const files = (await fs24.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10473
+ const specsDir = path22.join(currentDir, "specs");
10474
+ if (await fs23.pathExists(specsDir)) {
10475
+ const files = (await fs23.readdir(specsDir)).filter((f) => f.endsWith(".md")).sort().reverse();
10457
10476
  if (files.length > 0) {
10458
- const latest = path23.join(specsDir, files[0]);
10459
- specContent = await fs24.readFile(latest, "utf-8");
10477
+ const latest = path22.join(specsDir, files[0]);
10478
+ specContent = await fs23.readFile(latest, "utf-8");
10460
10479
  resolvedSpecFile = latest;
10461
10480
  console.log(chalk19.gray(`Auto-detected spec: specs/${files[0]}`));
10462
10481
  }
@@ -10494,7 +10513,7 @@ program.command("init").description(`Analyze codebase and generate Project Const
10494
10513
  console.log(chalk19.gray(` Lines : ${result.before.totalLines} \u2192 ${result.after.totalLines} (${result.before.totalLines - result.after.totalLines > 0 ? "-" : "+"}${Math.abs(result.before.totalLines - result.after.totalLines)})`));
10495
10514
  console.log(chalk19.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
10496
10515
  if (result.backupPath) {
10497
- console.log(chalk19.gray(` Backup: ${path23.basename(result.backupPath)}`));
10516
+ console.log(chalk19.gray(` Backup: ${path22.basename(result.backupPath)}`));
10498
10517
  }
10499
10518
  }
10500
10519
  } catch (err) {
@@ -10520,7 +10539,7 @@ program.command("init").description(`Analyze codebase and generate Project Const
10520
10539
  `Tech stack: ${ctx.techStack.join(", ") || "unknown"}`,
10521
10540
  `Dependencies: ${ctx.dependencies.slice(0, 20).join(", ")}`
10522
10541
  ].join("\n");
10523
- const prompt = buildGlobalConstitutionPrompt([{ name: path23.basename(currentDir), summary }]);
10542
+ const prompt = buildGlobalConstitutionPrompt([{ name: path22.basename(currentDir), summary }]);
10524
10543
  let globalConstitution;
10525
10544
  try {
10526
10545
  globalConstitution = await provider.generate(prompt, globalConstitutionSystemPrompt);
@@ -10540,8 +10559,8 @@ program.command("init").description(`Analyze codebase and generate Project Const
10540
10559
  }
10541
10560
  return;
10542
10561
  }
10543
- const constitutionPath = path23.join(currentDir, CONSTITUTION_FILE);
10544
- if (!opts.force && await fs24.pathExists(constitutionPath)) {
10562
+ const constitutionPath = path22.join(currentDir, CONSTITUTION_FILE);
10563
+ if (!opts.force && await fs23.pathExists(constitutionPath)) {
10545
10564
  console.log(chalk19.yellow(`
10546
10565
  ${CONSTITUTION_FILE} already exists.`));
10547
10566
  console.log(chalk19.gray(" Use --force to overwrite it."));
@@ -10560,7 +10579,7 @@ program.command("init").description(`Analyze codebase and generate Project Const
10560
10579
  process.exit(1);
10561
10580
  }
10562
10581
  const saved = await generator.saveConstitution(currentDir, constitution);
10563
- const globalResult = await loadGlobalConstitution([path23.dirname(currentDir)]);
10582
+ const globalResult = await loadGlobalConstitution([path22.dirname(currentDir)]);
10564
10583
  if (globalResult) {
10565
10584
  console.log(chalk19.cyan(`
10566
10585
  \u2139 Global constitution detected: ${globalResult.source}`));
@@ -10582,7 +10601,7 @@ program.command("config").description(`Set default configuration for this projec
10582
10601
  "Default code generation mode (claude-code|api|plan)"
10583
10602
  ).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) => {
10584
10603
  const currentDir = process.cwd();
10585
- const configPath = path23.join(currentDir, CONFIG_FILE);
10604
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10586
10605
  if (opts.clearKeys) {
10587
10606
  await clearAllKeys();
10588
10607
  console.log(chalk19.green(`\u2714 All saved API keys cleared.`));
@@ -10594,7 +10613,7 @@ program.command("config").description(`Set default configuration for this projec
10594
10613
  return;
10595
10614
  }
10596
10615
  if (opts.listKeys) {
10597
- const store = await fs24.readJson(KEY_STORE_FILE).catch(() => ({}));
10616
+ const store = await fs23.readJson(KEY_STORE_FILE).catch(() => ({}));
10598
10617
  const providers = Object.keys(store);
10599
10618
  if (providers.length === 0) {
10600
10619
  console.log(chalk19.gray("No saved API keys."));
@@ -10610,7 +10629,7 @@ File: ${KEY_STORE_FILE}`));
10610
10629
  return;
10611
10630
  }
10612
10631
  if (opts.reset) {
10613
- await fs24.writeJson(configPath, {}, { spaces: 2 });
10632
+ await fs23.writeJson(configPath, {}, { spaces: 2 });
10614
10633
  console.log(chalk19.green(`\u2714 Config reset: ${configPath}`));
10615
10634
  return;
10616
10635
  }
@@ -10638,13 +10657,13 @@ File: ${KEY_STORE_FILE}`));
10638
10657
  }
10639
10658
  updated.minSpecScore = score;
10640
10659
  }
10641
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10660
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10642
10661
  console.log(chalk19.green(`\u2714 Config saved to ${configPath}`));
10643
10662
  console.log(JSON.stringify(updated, null, 2));
10644
10663
  });
10645
10664
  program.command("model").description("Interactively switch the active AI provider/model and save to .ai-spec.json").option("--list", "List all available providers and models").action(async (opts) => {
10646
10665
  const currentDir = process.cwd();
10647
- const configPath = path23.join(currentDir, CONFIG_FILE);
10666
+ const configPath = path22.join(currentDir, CONFIG_FILE);
10648
10667
  if (opts.list) {
10649
10668
  console.log(chalk19.bold("\nAvailable providers & models:\n"));
10650
10669
  for (const [key, meta] of Object.entries(PROVIDER_CATALOG)) {
@@ -10748,7 +10767,7 @@ program.command("model").description("Interactively switch the active AI provide
10748
10767
  console.log(chalk19.gray(" Cancelled."));
10749
10768
  return;
10750
10769
  }
10751
- await fs24.writeJson(configPath, updated, { spaces: 2 });
10770
+ await fs23.writeJson(configPath, updated, { spaces: 2 });
10752
10771
  console.log(chalk19.green(`
10753
10772
  \u2714 Saved to ${configPath}`));
10754
10773
  const providerToCheck = updated.provider ?? "gemini";
@@ -10843,17 +10862,17 @@ ${contractContextSection}`;
10843
10862
  } else {
10844
10863
  console.log(chalk19.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
10845
10864
  }
10846
- const specsDir = path23.join(workingDir, "specs");
10847
- await fs24.ensureDir(specsDir);
10865
+ const specsDir = path22.join(workingDir, "specs");
10866
+ await fs23.ensureDir(specsDir);
10848
10867
  const featureSlug = slugify(idea);
10849
10868
  const { filePath: specFile } = await nextVersionPath(specsDir, featureSlug);
10850
- await fs24.writeFile(specFile, finalSpec, "utf-8");
10851
- console.log(chalk19.green(` Spec saved: ${path23.relative(repoAbsPath, specFile)}`));
10869
+ await fs23.writeFile(specFile, finalSpec, "utf-8");
10870
+ console.log(chalk19.green(` Spec saved: ${path22.relative(repoAbsPath, specFile)}`));
10852
10871
  let savedDslFile = null;
10853
10872
  if (extractedDsl) {
10854
10873
  const dslExtractorForSave = new DslExtractor(specProvider);
10855
10874
  savedDslFile = await dslExtractorForSave.saveDsl(extractedDsl, specFile);
10856
- console.log(chalk19.green(` DSL saved: ${path23.relative(repoAbsPath, savedDslFile)}`));
10875
+ console.log(chalk19.green(` DSL saved: ${path22.relative(repoAbsPath, savedDslFile)}`));
10857
10876
  }
10858
10877
  console.log(chalk19.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
10859
10878
  try {
@@ -11083,8 +11102,8 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
11083
11102
  var workspaceCmd = program.command("workspace").description("Manage multi-repo workspace configuration");
11084
11103
  workspaceCmd.command("init").description(`Interactive workspace setup \u2014 creates ${WORKSPACE_CONFIG_FILE}`).action(async () => {
11085
11104
  const currentDir = process.cwd();
11086
- const configPath = path23.join(currentDir, WORKSPACE_CONFIG_FILE);
11087
- if (await fs24.pathExists(configPath)) {
11105
+ const configPath = path22.join(currentDir, WORKSPACE_CONFIG_FILE);
11106
+ if (await fs23.pathExists(configPath)) {
11088
11107
  const overwrite = await confirm2({
11089
11108
  message: `${WORKSPACE_CONFIG_FILE} already exists. Overwrite?`,
11090
11109
  default: false
@@ -11165,10 +11184,10 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
11165
11184
  message: `Relative path to "${repoName}" from here (default: ./${repoName}):`,
11166
11185
  default: `./${repoName}`
11167
11186
  });
11168
- const absPath = path23.resolve(currentDir, repoPath);
11187
+ const absPath = path22.resolve(currentDir, repoPath);
11169
11188
  let detectedType = "unknown";
11170
11189
  let detectedRole = "shared";
11171
- if (await fs24.pathExists(absPath)) {
11190
+ if (await fs23.pathExists(absPath)) {
11172
11191
  const { type, role } = await detectRepoType(absPath);
11173
11192
  detectedType = type;
11174
11193
  detectedRole = role;
@@ -11231,12 +11250,12 @@ workspaceCmd.command("status").description("Show current workspace configuration
11231
11250
  }
11232
11251
  console.log(chalk19.bold(`
11233
11252
  Workspace: ${config2.name}`));
11234
- console.log(chalk19.gray(` Config: ${path23.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11253
+ console.log(chalk19.gray(` Config: ${path22.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
11235
11254
  console.log(chalk19.gray(` Repos (${config2.repos.length}):
11236
11255
  `));
11237
11256
  for (const repo of config2.repos) {
11238
11257
  const absPath = loader.resolveAbsPath(repo);
11239
- const exists = await fs24.pathExists(absPath);
11258
+ const exists = await fs23.pathExists(absPath);
11240
11259
  const status = exists ? chalk19.green("found") : chalk19.red("not found");
11241
11260
  console.log(
11242
11261
  ` ${chalk19.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
@@ -11270,14 +11289,14 @@ program.command("update").description("Update an existing spec with a change req
11270
11289
  console.log(chalk19.gray(` Run ID: ${updateRunId}`));
11271
11290
  let specPath = opts.spec ?? null;
11272
11291
  if (!specPath) {
11273
- const specsDir = path23.join(currentDir, "specs");
11292
+ const specsDir = path22.join(currentDir, "specs");
11274
11293
  const latest = await SpecUpdater.findLatestSpec(specsDir);
11275
11294
  if (!latest) {
11276
11295
  console.error(chalk19.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
11277
11296
  process.exit(1);
11278
11297
  }
11279
11298
  specPath = latest.filePath;
11280
- console.log(chalk19.gray(` Using spec: ${path23.relative(currentDir, specPath)} (v${latest.version})`));
11299
+ console.log(chalk19.gray(` Using spec: ${path22.relative(currentDir, specPath)} (v${latest.version})`));
11281
11300
  }
11282
11301
  console.log(chalk19.gray(" Loading project context..."));
11283
11302
  const loader = new ContextLoader(currentDir);
@@ -11299,9 +11318,9 @@ program.command("update").description("Update an existing spec with a change req
11299
11318
  process.exit(1);
11300
11319
  }
11301
11320
  console.log(chalk19.green(`
11302
- \u2714 Spec updated \u2192 v${result.newVersion}: ${path23.relative(currentDir, result.newSpecPath)}`));
11321
+ \u2714 Spec updated \u2192 v${result.newVersion}: ${path22.relative(currentDir, result.newSpecPath)}`));
11303
11322
  if (result.newDslPath) {
11304
- console.log(chalk19.green(` \u2714 DSL updated: ${path23.relative(currentDir, result.newDslPath)}`));
11323
+ console.log(chalk19.green(` \u2714 DSL updated: ${path22.relative(currentDir, result.newDslPath)}`));
11305
11324
  }
11306
11325
  if (result.affectedFiles.length > 0) {
11307
11326
  console.log(chalk19.cyan("\n Affected files:"));
@@ -11317,7 +11336,7 @@ program.command("update").description("Update an existing spec with a change req
11317
11336
  const codegenProvider = createProvider(codegenProviderName, codegenApiKey, codegenModelName);
11318
11337
  console.log(chalk19.blue("\n Regenerating affected files..."));
11319
11338
  const codeGenerator = new CodeGenerator(codegenProvider, "api");
11320
- const specContent = await fs24.readFile(result.newSpecPath, "utf-8");
11339
+ const specContent = await fs23.readFile(result.newSpecPath, "utf-8");
11321
11340
  const constitutionSection = context.constitution ? `
11322
11341
  === Project Constitution (MUST follow) ===
11323
11342
  ${context.constitution}
@@ -11328,10 +11347,10 @@ ${JSON.stringify(result.updatedDsl, null, 2).slice(0, 3e3)}
11328
11347
  ` : "";
11329
11348
  updateLogger.stageStart("update_codegen");
11330
11349
  for (const affected of result.affectedFiles) {
11331
- const fullPath = path23.join(currentDir, affected.file);
11350
+ const fullPath = path22.join(currentDir, affected.file);
11332
11351
  let existing = "";
11333
11352
  try {
11334
- existing = await fs24.readFile(fullPath, "utf-8");
11353
+ existing = await fs23.readFile(fullPath, "utf-8");
11335
11354
  } catch {
11336
11355
  }
11337
11356
  const codePrompt = `Apply this change to the file.
@@ -11350,9 +11369,9 @@ ${existing || "Create from scratch."}`;
11350
11369
  const { getCodeGenSystemPrompt: _getPrompt } = await Promise.resolve().then(() => (init_codegen_prompt(), codegen_prompt_exports));
11351
11370
  const raw = await codegenProvider.generate(codePrompt, _getPrompt(repoType));
11352
11371
  const content = raw.replace(/^```\w*\n?/gm, "").replace(/\n?```$/gm, "").trim();
11353
- await fs24.ensureDir(path23.dirname(fullPath));
11372
+ await fs23.ensureDir(path22.dirname(fullPath));
11354
11373
  await updateSnapshot.snapshotFile(fullPath);
11355
- await fs24.writeFile(fullPath, content, "utf-8");
11374
+ await fs23.writeFile(fullPath, content, "utf-8");
11356
11375
  updateLogger.fileWritten(affected.file);
11357
11376
  console.log(chalk19.green("\u2714"));
11358
11377
  } catch (err) {
@@ -11361,7 +11380,7 @@ ${existing || "Create from scratch."}`;
11361
11380
  }
11362
11381
  }
11363
11382
  updateLogger.stageEnd("update_codegen", { filesUpdated: result.affectedFiles.length });
11364
- const updatedSpecContent = await fs24.readFile(result.newSpecPath, "utf-8").catch(() => "");
11383
+ const updatedSpecContent = await fs23.readFile(result.newSpecPath, "utf-8").catch(() => "");
11365
11384
  if (updatedSpecContent) {
11366
11385
  const updateReviewer = new CodeReviewer(provider, currentDir);
11367
11386
  const reviewResult = await updateReviewer.reviewCode(updatedSpecContent, result.newSpecPath).catch(() => "");
@@ -11391,11 +11410,11 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11391
11410
  console.error(chalk19.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
11392
11411
  process.exit(1);
11393
11412
  }
11394
- console.log(chalk19.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11413
+ console.log(chalk19.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11395
11414
  }
11396
11415
  let dsl;
11397
11416
  try {
11398
- dsl = await fs24.readJson(dslPath);
11417
+ dsl = await fs23.readJson(dslPath);
11399
11418
  } catch (err) {
11400
11419
  console.error(chalk19.red(` Failed to read DSL: ${err.message}`));
11401
11420
  process.exit(1);
@@ -11409,7 +11428,7 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
11409
11428
  serverUrl,
11410
11429
  outputPath: opts.output
11411
11430
  });
11412
- const rel = path23.relative(currentDir, outputPath);
11431
+ const rel = path22.relative(currentDir, outputPath);
11413
11432
  console.log(chalk19.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
11414
11433
  console.log(chalk19.gray(` Feature : ${dsl.feature.title}`));
11415
11434
  console.log(chalk19.gray(` Endpoints: ${dsl.endpoints.length}`));
@@ -11428,7 +11447,7 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11428
11447
  const port = parseInt(opts.port, 10) || 3001;
11429
11448
  console.log(chalk19.blue("\n\u2500\u2500\u2500 ai-spec mock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11430
11449
  if (opts.restore) {
11431
- const frontendDir = opts.frontend ? path23.resolve(opts.frontend) : currentDir;
11450
+ const frontendDir = opts.frontend ? path22.resolve(opts.frontend) : currentDir;
11432
11451
  const r = await restoreMockProxy(frontendDir);
11433
11452
  if (r.restored) {
11434
11453
  console.log(chalk19.green(" \u2714 Proxy restored and mock server stopped."));
@@ -11458,7 +11477,7 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11458
11477
  console.log(chalk19.yellow(` No DSL file found \u2014 skipping.`));
11459
11478
  continue;
11460
11479
  }
11461
- const dsl2 = await fs24.readJson(dslFile);
11480
+ const dsl2 = await fs23.readJson(dslFile);
11462
11481
  const result2 = await generateMockAssets(dsl2, repoAbsPath, {
11463
11482
  port,
11464
11483
  msw: opts.msw,
@@ -11482,11 +11501,11 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11482
11501
  );
11483
11502
  process.exit(1);
11484
11503
  }
11485
- console.log(chalk19.gray(` Using DSL: ${path23.relative(currentDir, dslPath)}`));
11504
+ console.log(chalk19.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
11486
11505
  }
11487
11506
  let dsl;
11488
11507
  try {
11489
- dsl = await fs24.readJson(dslPath);
11508
+ dsl = await fs23.readJson(dslPath);
11490
11509
  } catch (err) {
11491
11510
  console.error(chalk19.red(` Failed to read DSL file: ${err.message}`));
11492
11511
  process.exit(1);
@@ -11503,8 +11522,8 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11503
11522
  console.log(chalk19.gray(` ${f.description}`));
11504
11523
  }
11505
11524
  if (opts.serve) {
11506
- const serverJsPath = path23.join(currentDir, "mock", "server.js");
11507
- if (!await fs24.pathExists(serverJsPath)) {
11525
+ const serverJsPath = path22.join(currentDir, "mock", "server.js");
11526
+ if (!await fs23.pathExists(serverJsPath)) {
11508
11527
  console.error(chalk19.red(" mock/server.js not found \u2014 generation may have failed."));
11509
11528
  process.exit(1);
11510
11529
  }
@@ -11512,7 +11531,7 @@ program.command("mock").description("Generate a standalone mock server + proxy c
11512
11531
  console.log(chalk19.green(`
11513
11532
  \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${port}`));
11514
11533
  if (opts.frontend) {
11515
- const frontendDir = path23.resolve(opts.frontend);
11534
+ const frontendDir = path22.resolve(opts.frontend);
11516
11535
  const proxyResult = await applyMockProxy(frontendDir, port, dsl.endpoints);
11517
11536
  await saveMockServerPid(frontendDir, pid);
11518
11537
  if (proxyResult.applied) {
@@ -11584,13 +11603,5 @@ program.command("restore").description("Restore files modified by a previous run
11584
11603
  \u2714 ${restored.length} file(s) restored.`));
11585
11604
  }
11586
11605
  });
11587
- if (process.argv.length <= 2) {
11588
- (async () => {
11589
- const currentDir = process.cwd();
11590
- const config2 = await loadConfig(currentDir);
11591
- await printWelcome(currentDir, config2);
11592
- })();
11593
- } else {
11594
- program.parse();
11595
- }
11606
+ program.parse();
11596
11607
  //# sourceMappingURL=index.mjs.map