ai-spec-dev 0.46.0 → 0.55.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.
Files changed (39) hide show
  1. package/README.md +60 -30
  2. package/cli/commands/config.ts +129 -1
  3. package/cli/commands/create.ts +14 -0
  4. package/cli/commands/fix-history.ts +176 -0
  5. package/cli/commands/init.ts +36 -1
  6. package/cli/index.ts +2 -6
  7. package/cli/pipeline/helpers.ts +6 -0
  8. package/cli/pipeline/multi-repo.ts +291 -26
  9. package/cli/pipeline/single-repo.ts +103 -2
  10. package/cli/utils.ts +23 -0
  11. package/core/code-generator.ts +63 -14
  12. package/core/cross-stack-verifier.ts +395 -0
  13. package/core/fix-history.ts +333 -0
  14. package/core/import-fixer.ts +827 -0
  15. package/core/import-verifier.ts +569 -0
  16. package/core/knowledge-memory.ts +55 -6
  17. package/core/self-evaluator.ts +44 -7
  18. package/core/spec-generator.ts +3 -3
  19. package/core/types-generator.ts +2 -2
  20. package/dist/cli/index.js +3759 -2207
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/index.mjs +3747 -2195
  23. package/dist/cli/index.mjs.map +1 -1
  24. package/dist/index.d.mts +14 -0
  25. package/dist/index.d.ts +14 -0
  26. package/dist/index.js +249 -128
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +249 -128
  29. package/dist/index.mjs.map +1 -1
  30. package/package.json +2 -2
  31. package/tests/cross-stack-verifier.test.ts +301 -0
  32. package/tests/fix-history.test.ts +335 -0
  33. package/tests/import-fixer.test.ts +944 -0
  34. package/tests/import-verifier.test.ts +420 -0
  35. package/tests/knowledge-memory.test.ts +40 -0
  36. package/tests/self-evaluator.test.ts +97 -0
  37. package/cli/commands/model.ts +0 -152
  38. package/cli/commands/scan.ts +0 -99
  39. package/cli/commands/workspace.ts +0 -219
package/dist/index.js CHANGED
@@ -405,12 +405,12 @@ var PROVIDER_CATALOG = {
405
405
  },
406
406
  deepseek: {
407
407
  displayName: "DeepSeek",
408
- description: "DeepSeek \u2014 V3 (chat) / R1 (reasoning)",
408
+ description: "DeepSeek V3.2 (chat) / R1 (reasoner) \u2014 alias auto-tracks latest stable",
409
409
  models: [
410
410
  "deepseek-chat",
411
- // DeepSeek-V3
411
+ // V3.2 (alias auto-updates as DeepSeek releases new versions)
412
412
  "deepseek-reasoner"
413
- // DeepSeek-R1
413
+ // R1 (reasoning model)
414
414
  ],
415
415
  envKey: "DEEPSEEK_API_KEY",
416
416
  baseURL: "https://api.deepseek.com/v1"
@@ -4233,8 +4233,8 @@ ${currentSpec}`,
4233
4233
  // core/code-generator.ts
4234
4234
  var import_chalk10 = __toESM(require("chalk"));
4235
4235
  var import_child_process2 = require("child_process");
4236
- var path6 = __toESM(require("path"));
4237
- var fs10 = __toESM(require("fs-extra"));
4236
+ var path7 = __toESM(require("path"));
4237
+ var fs11 = __toESM(require("fs-extra"));
4238
4238
 
4239
4239
  // prompts/codegen.prompt.ts
4240
4240
  var codeGenSystemPrompt = `You are a Senior Full-Stack Developer implementing features based on provided specifications.
@@ -4904,32 +4904,32 @@ function validateDsl(raw) {
4904
4904
  }
4905
4905
  return { valid: true, dsl: raw };
4906
4906
  }
4907
- function validateFeature(raw, path10, errors) {
4907
+ function validateFeature(raw, path11, errors) {
4908
4908
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4909
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4909
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4910
4910
  return;
4911
4911
  }
4912
4912
  const f = raw;
4913
- requireNonEmptyString(f["id"], `${path10}.id`, errors);
4914
- requireNonEmptyString(f["title"], `${path10}.title`, errors);
4915
- requireNonEmptyString(f["description"], `${path10}.description`, errors);
4913
+ requireNonEmptyString(f["id"], `${path11}.id`, errors);
4914
+ requireNonEmptyString(f["title"], `${path11}.title`, errors);
4915
+ requireNonEmptyString(f["description"], `${path11}.description`, errors);
4916
4916
  }
4917
- function validateModel(raw, path10, errors) {
4917
+ function validateModel(raw, path11, errors) {
4918
4918
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4919
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4919
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4920
4920
  return;
4921
4921
  }
4922
4922
  const m = raw;
4923
- requireNonEmptyString(m["name"], `${path10}.name`, errors);
4923
+ requireNonEmptyString(m["name"], `${path11}.name`, errors);
4924
4924
  if (!Array.isArray(m["fields"])) {
4925
- errors.push({ path: `${path10}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4925
+ errors.push({ path: `${path11}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4926
4926
  } else {
4927
4927
  const fields = m["fields"];
4928
4928
  if (fields.length > MAX_FIELDS_PER_MODEL) {
4929
- errors.push({ path: `${path10}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4929
+ errors.push({ path: `${path11}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4930
4930
  }
4931
4931
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
4932
- validateModelField(fields[j2], `${path10}.fields[${j2}]`, errors);
4932
+ validateModelField(fields[j2], `${path11}.fields[${j2}]`, errors);
4933
4933
  }
4934
4934
  const seenFieldNames = /* @__PURE__ */ new Set();
4935
4935
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
@@ -4938,7 +4938,7 @@ function validateModel(raw, path10, errors) {
4938
4938
  const name = f["name"];
4939
4939
  if (seenFieldNames.has(name)) {
4940
4940
  errors.push({
4941
- path: `${path10}.fields[${j2}].name`,
4941
+ path: `${path11}.fields[${j2}].name`,
4942
4942
  message: `Duplicate field name "${name}" \u2014 each field within a model must have a unique name`
4943
4943
  });
4944
4944
  } else {
@@ -4949,184 +4949,184 @@ function validateModel(raw, path10, errors) {
4949
4949
  }
4950
4950
  if (m["relations"] !== void 0) {
4951
4951
  if (!Array.isArray(m["relations"])) {
4952
- errors.push({ path: `${path10}.relations`, message: "Must be an array of strings if present" });
4952
+ errors.push({ path: `${path11}.relations`, message: "Must be an array of strings if present" });
4953
4953
  } else {
4954
4954
  const rels = m["relations"];
4955
4955
  for (let j2 = 0; j2 < rels.length; j2++) {
4956
4956
  if (typeof rels[j2] !== "string") {
4957
- errors.push({ path: `${path10}.relations[${j2}]`, message: "Must be a string" });
4957
+ errors.push({ path: `${path11}.relations[${j2}]`, message: "Must be a string" });
4958
4958
  }
4959
4959
  }
4960
4960
  }
4961
4961
  }
4962
4962
  }
4963
- function validateModelField(raw, path10, errors) {
4963
+ function validateModelField(raw, path11, errors) {
4964
4964
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4965
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4965
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4966
4966
  return;
4967
4967
  }
4968
4968
  const f = raw;
4969
- requireNonEmptyString(f["name"], `${path10}.name`, errors);
4970
- requireNonEmptyString(f["type"], `${path10}.type`, errors);
4969
+ requireNonEmptyString(f["name"], `${path11}.name`, errors);
4970
+ requireNonEmptyString(f["type"], `${path11}.type`, errors);
4971
4971
  if (typeof f["required"] !== "boolean") {
4972
- errors.push({ path: `${path10}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4972
+ errors.push({ path: `${path11}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4973
4973
  }
4974
4974
  }
4975
- function validateEndpoint(raw, path10, errors) {
4975
+ function validateEndpoint(raw, path11, errors) {
4976
4976
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4977
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4977
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4978
4978
  return;
4979
4979
  }
4980
4980
  const e = raw;
4981
- requireNonEmptyString(e["id"], `${path10}.id`, errors);
4982
- requireNonEmptyString(e["description"], `${path10}.description`, errors);
4981
+ requireNonEmptyString(e["id"], `${path11}.id`, errors);
4982
+ requireNonEmptyString(e["description"], `${path11}.description`, errors);
4983
4983
  if (!VALID_METHODS.includes(e["method"])) {
4984
4984
  errors.push({
4985
- path: `${path10}.method`,
4985
+ path: `${path11}.method`,
4986
4986
  message: `Must be one of ${VALID_METHODS.join("|")}, got: ${JSON.stringify(e["method"])}`
4987
4987
  });
4988
4988
  }
4989
4989
  if (typeof e["path"] !== "string" || !e["path"].startsWith("/")) {
4990
4990
  errors.push({
4991
- path: `${path10}.path`,
4991
+ path: `${path11}.path`,
4992
4992
  message: `Must be a string starting with "/", got: ${JSON.stringify(e["path"])}`
4993
4993
  });
4994
4994
  }
4995
4995
  if (typeof e["auth"] !== "boolean") {
4996
- errors.push({ path: `${path10}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4996
+ errors.push({ path: `${path11}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4997
4997
  }
4998
4998
  if (typeof e["successStatus"] !== "number" || e["successStatus"] < 100 || e["successStatus"] > 599) {
4999
4999
  errors.push({
5000
- path: `${path10}.successStatus`,
5000
+ path: `${path11}.successStatus`,
5001
5001
  message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["successStatus"])}`
5002
5002
  });
5003
5003
  }
5004
- requireNonEmptyString(e["successDescription"], `${path10}.successDescription`, errors);
5004
+ requireNonEmptyString(e["successDescription"], `${path11}.successDescription`, errors);
5005
5005
  if (e["request"] !== void 0) {
5006
- validateRequestSchema(e["request"], `${path10}.request`, errors);
5006
+ validateRequestSchema(e["request"], `${path11}.request`, errors);
5007
5007
  }
5008
5008
  if (e["errors"] !== void 0) {
5009
5009
  if (!Array.isArray(e["errors"])) {
5010
- errors.push({ path: `${path10}.errors`, message: "Must be an array if present" });
5010
+ errors.push({ path: `${path11}.errors`, message: "Must be an array if present" });
5011
5011
  } else {
5012
5012
  const errs = e["errors"];
5013
5013
  if (errs.length > MAX_ERRORS_PER_ENDPOINT) {
5014
- errors.push({ path: `${path10}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
5014
+ errors.push({ path: `${path11}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
5015
5015
  }
5016
5016
  for (let j2 = 0; j2 < Math.min(errs.length, MAX_ERRORS_PER_ENDPOINT); j2++) {
5017
- validateResponseError(errs[j2], `${path10}.errors[${j2}]`, errors);
5017
+ validateResponseError(errs[j2], `${path11}.errors[${j2}]`, errors);
5018
5018
  }
5019
5019
  }
5020
5020
  }
5021
5021
  }
5022
- function validateRequestSchema(raw, path10, errors) {
5022
+ function validateRequestSchema(raw, path11, errors) {
5023
5023
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5024
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
5024
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
5025
5025
  return;
5026
5026
  }
5027
5027
  const r = raw;
5028
5028
  for (const key of ["body", "query", "params"]) {
5029
5029
  if (r[key] !== void 0) {
5030
- validateFieldMap(r[key], `${path10}.${key}`, errors);
5030
+ validateFieldMap(r[key], `${path11}.${key}`, errors);
5031
5031
  }
5032
5032
  }
5033
5033
  }
5034
- function validateFieldMap(raw, path10, errors) {
5034
+ function validateFieldMap(raw, path11, errors) {
5035
5035
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5036
- errors.push({ path: path10, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
5036
+ errors.push({ path: path11, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
5037
5037
  return;
5038
5038
  }
5039
5039
  const map = raw;
5040
5040
  for (const [k2, v2] of Object.entries(map)) {
5041
5041
  if (typeof v2 !== "string") {
5042
- errors.push({ path: `${path10}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
5042
+ errors.push({ path: `${path11}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
5043
5043
  }
5044
5044
  }
5045
5045
  }
5046
- function validateResponseError(raw, path10, errors) {
5046
+ function validateResponseError(raw, path11, errors) {
5047
5047
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5048
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
5048
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
5049
5049
  return;
5050
5050
  }
5051
5051
  const e = raw;
5052
5052
  if (typeof e["status"] !== "number" || e["status"] < 100 || e["status"] > 599) {
5053
- errors.push({ path: `${path10}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
5053
+ errors.push({ path: `${path11}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
5054
5054
  }
5055
- requireNonEmptyString(e["code"], `${path10}.code`, errors);
5056
- requireNonEmptyString(e["description"], `${path10}.description`, errors);
5055
+ requireNonEmptyString(e["code"], `${path11}.code`, errors);
5056
+ requireNonEmptyString(e["description"], `${path11}.description`, errors);
5057
5057
  }
5058
- function validateBehavior(raw, path10, errors) {
5058
+ function validateBehavior(raw, path11, errors) {
5059
5059
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5060
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
5060
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
5061
5061
  return;
5062
5062
  }
5063
5063
  const b = raw;
5064
- requireNonEmptyString(b["id"], `${path10}.id`, errors);
5065
- requireNonEmptyString(b["description"], `${path10}.description`, errors);
5064
+ requireNonEmptyString(b["id"], `${path11}.id`, errors);
5065
+ requireNonEmptyString(b["description"], `${path11}.description`, errors);
5066
5066
  if (b["constraints"] !== void 0) {
5067
5067
  if (!Array.isArray(b["constraints"])) {
5068
- errors.push({ path: `${path10}.constraints`, message: "Must be an array of strings if present" });
5068
+ errors.push({ path: `${path11}.constraints`, message: "Must be an array of strings if present" });
5069
5069
  } else {
5070
5070
  const cs2 = b["constraints"];
5071
5071
  for (let j2 = 0; j2 < cs2.length; j2++) {
5072
5072
  if (typeof cs2[j2] !== "string") {
5073
- errors.push({ path: `${path10}.constraints[${j2}]`, message: "Must be a string" });
5073
+ errors.push({ path: `${path11}.constraints[${j2}]`, message: "Must be a string" });
5074
5074
  }
5075
5075
  }
5076
5076
  }
5077
5077
  }
5078
5078
  }
5079
- function validateComponent(raw, path10, errors) {
5079
+ function validateComponent(raw, path11, errors) {
5080
5080
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5081
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
5081
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
5082
5082
  return;
5083
5083
  }
5084
5084
  const c = raw;
5085
- requireNonEmptyString(c["id"], `${path10}.id`, errors);
5086
- requireNonEmptyString(c["name"], `${path10}.name`, errors);
5087
- requireNonEmptyString(c["description"], `${path10}.description`, errors);
5085
+ requireNonEmptyString(c["id"], `${path11}.id`, errors);
5086
+ requireNonEmptyString(c["name"], `${path11}.name`, errors);
5087
+ requireNonEmptyString(c["description"], `${path11}.description`, errors);
5088
5088
  if (c["props"] !== void 0) {
5089
5089
  if (!Array.isArray(c["props"])) {
5090
- errors.push({ path: `${path10}.props`, message: "Must be an array if present" });
5090
+ errors.push({ path: `${path11}.props`, message: "Must be an array if present" });
5091
5091
  } else {
5092
5092
  const props = c["props"];
5093
5093
  for (let j2 = 0; j2 < props.length; j2++) {
5094
5094
  const p = props[j2];
5095
5095
  if (typeof p !== "object" || p === null) {
5096
- errors.push({ path: `${path10}.props[${j2}]`, message: "Must be an object" });
5096
+ errors.push({ path: `${path11}.props[${j2}]`, message: "Must be an object" });
5097
5097
  continue;
5098
5098
  }
5099
- requireNonEmptyString(p["name"], `${path10}.props[${j2}].name`, errors);
5100
- requireNonEmptyString(p["type"], `${path10}.props[${j2}].type`, errors);
5099
+ requireNonEmptyString(p["name"], `${path11}.props[${j2}].name`, errors);
5100
+ requireNonEmptyString(p["type"], `${path11}.props[${j2}].type`, errors);
5101
5101
  if (typeof p["required"] !== "boolean") {
5102
- errors.push({ path: `${path10}.props[${j2}].required`, message: "Must be boolean" });
5102
+ errors.push({ path: `${path11}.props[${j2}].required`, message: "Must be boolean" });
5103
5103
  }
5104
5104
  }
5105
5105
  }
5106
5106
  }
5107
5107
  if (c["events"] !== void 0) {
5108
5108
  if (!Array.isArray(c["events"])) {
5109
- errors.push({ path: `${path10}.events`, message: "Must be an array if present" });
5109
+ errors.push({ path: `${path11}.events`, message: "Must be an array if present" });
5110
5110
  } else {
5111
5111
  const events = c["events"];
5112
5112
  for (let j2 = 0; j2 < events.length; j2++) {
5113
5113
  const e = events[j2];
5114
5114
  if (typeof e !== "object" || e === null) {
5115
- errors.push({ path: `${path10}.events[${j2}]`, message: "Must be an object" });
5115
+ errors.push({ path: `${path11}.events[${j2}]`, message: "Must be an object" });
5116
5116
  continue;
5117
5117
  }
5118
- requireNonEmptyString(e["name"], `${path10}.events[${j2}].name`, errors);
5118
+ requireNonEmptyString(e["name"], `${path11}.events[${j2}].name`, errors);
5119
5119
  }
5120
5120
  }
5121
5121
  }
5122
5122
  if (c["state"] !== void 0) {
5123
5123
  if (typeof c["state"] !== "object" || Array.isArray(c["state"]) || c["state"] === null) {
5124
- errors.push({ path: `${path10}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5124
+ errors.push({ path: `${path11}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5125
5125
  }
5126
5126
  }
5127
5127
  if (c["apiCalls"] !== void 0) {
5128
5128
  if (!Array.isArray(c["apiCalls"])) {
5129
- errors.push({ path: `${path10}.apiCalls`, message: "Must be an array of strings if present" });
5129
+ errors.push({ path: `${path11}.apiCalls`, message: "Must be an array of strings if present" });
5130
5130
  }
5131
5131
  }
5132
5132
  }
@@ -5188,10 +5188,10 @@ function crossReferenceChecks(obj, errors) {
5188
5188
  }
5189
5189
  }
5190
5190
  }
5191
- function requireNonEmptyString(v2, path10, errors) {
5191
+ function requireNonEmptyString(v2, path11, errors) {
5192
5192
  if (typeof v2 !== "string" || v2.trim().length === 0) {
5193
5193
  errors.push({
5194
- path: path10,
5194
+ path: path11,
5195
5195
  message: `Must be a non-empty string, got: ${typeLabel(v2)}`
5196
5196
  });
5197
5197
  }
@@ -6080,6 +6080,101 @@ function printTaskProgress(completed, total, task, mode) {
6080
6080
  }
6081
6081
  }
6082
6082
 
6083
+ // core/fix-history.ts
6084
+ var fs10 = __toESM(require("fs-extra"));
6085
+ var path6 = __toESM(require("path"));
6086
+ var FIX_HISTORY_FILE = ".ai-spec-fix-history.json";
6087
+ var FIX_HISTORY_VERSION = "1.0";
6088
+ async function loadFixHistory(repoRoot) {
6089
+ const filePath = path6.join(repoRoot, FIX_HISTORY_FILE);
6090
+ if (!await fs10.pathExists(filePath)) {
6091
+ return { version: FIX_HISTORY_VERSION, entries: [] };
6092
+ }
6093
+ try {
6094
+ const data = await fs10.readJson(filePath);
6095
+ if (!data || typeof data !== "object" || !Array.isArray(data.entries)) {
6096
+ return { version: FIX_HISTORY_VERSION, entries: [] };
6097
+ }
6098
+ return {
6099
+ version: typeof data.version === "string" ? data.version : FIX_HISTORY_VERSION,
6100
+ entries: data.entries
6101
+ };
6102
+ } catch {
6103
+ return { version: FIX_HISTORY_VERSION, entries: [] };
6104
+ }
6105
+ }
6106
+ function aggregateFixPatterns(history) {
6107
+ const byKey = /* @__PURE__ */ new Map();
6108
+ for (const entry of history.entries) {
6109
+ if (!byKey.has(entry.patternKey)) byKey.set(entry.patternKey, []);
6110
+ byKey.get(entry.patternKey).push(entry);
6111
+ }
6112
+ const aggregates = [];
6113
+ for (const [patternKey, entries] of byKey) {
6114
+ entries.sort((a, b) => a.ts.localeCompare(b.ts));
6115
+ const first = entries[0];
6116
+ const last = entries[entries.length - 1];
6117
+ const uniqueRunIds = new Set(entries.map((e) => e.runId)).size;
6118
+ aggregates.push({
6119
+ patternKey,
6120
+ count: entries.length,
6121
+ firstSeen: first.ts,
6122
+ lastSeen: last.ts,
6123
+ uniqueRunIds,
6124
+ source: last.brokenImport.source,
6125
+ names: last.brokenImport.names,
6126
+ reason: last.brokenImport.reason,
6127
+ fix: {
6128
+ kind: last.fix.kind,
6129
+ target: last.fix.target,
6130
+ stage: last.fix.stage
6131
+ }
6132
+ });
6133
+ }
6134
+ aggregates.sort((a, b) => {
6135
+ if (b.count !== a.count) return b.count - a.count;
6136
+ return b.lastSeen.localeCompare(a.lastSeen);
6137
+ });
6138
+ return aggregates;
6139
+ }
6140
+ function buildHallucinationAvoidanceSection(history, opts = {}) {
6141
+ const minCount = opts.minCount ?? 1;
6142
+ const maxItems = opts.maxItems ?? 10;
6143
+ const patterns = aggregateFixPatterns(history).filter((p) => p.count >= minCount);
6144
+ if (patterns.length === 0) return null;
6145
+ const top = patterns.slice(0, maxItems);
6146
+ const lines = [
6147
+ "=== Prior Hallucinations in This Project (DO NOT REPEAT) ===",
6148
+ "",
6149
+ "The following imports were previously hallucinated by AI codegen in this",
6150
+ "project and had to be auto-fixed. When generating new files, actively avoid",
6151
+ "these exact imports \u2014 they were wrong in the past and will be wrong again.",
6152
+ ""
6153
+ ];
6154
+ for (const p of top) {
6155
+ const namesLabel = p.names.length > 0 ? `{ ${p.names.join(", ")} }` : "(no names)";
6156
+ const reasonLabel = p.reason === "file_not_found" ? "file did not exist" : "named export did not exist";
6157
+ const countLabel = p.count === 1 ? "1x" : `${p.count}x`;
6158
+ const dateLabel = p.lastSeen.slice(0, 10);
6159
+ lines.push(`\u274C Do NOT: import ${namesLabel} from '${p.source}'`);
6160
+ lines.push(` Reason: ${reasonLabel} (seen ${countLabel}, last ${dateLabel})`);
6161
+ if (p.fix.kind === "create_file") {
6162
+ lines.push(` Previously fixed by creating: ${p.fix.target}`);
6163
+ } else if (p.fix.kind === "rewrite_import") {
6164
+ lines.push(` Previously fixed by rewriting the import path`);
6165
+ } else {
6166
+ lines.push(` Previously fixed by appending to: ${p.fix.target}`);
6167
+ }
6168
+ lines.push("");
6169
+ }
6170
+ if (patterns.length > maxItems) {
6171
+ lines.push(`(${patterns.length - maxItems} more pattern(s) hidden \u2014 run \`ai-spec fix-history\` to see all)`);
6172
+ lines.push("");
6173
+ }
6174
+ lines.push("=== End of Prior Hallucinations ===");
6175
+ return lines.join("\n");
6176
+ }
6177
+
6083
6178
  // core/code-generator.ts
6084
6179
  var CodeGenerator = class {
6085
6180
  constructor(provider, mode = "claude-code") {
@@ -6144,8 +6239,8 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
6144
6239
  Files: ${t.filesToTouch.join(", ")}
6145
6240
  Criteria: ${t.acceptanceCriteria.join("; ")}`).join("\n")}` : "";
6146
6241
  const promptContent = `Please read the spec file at ${specFilePath} and implement all the requirements. Create or modify files as necessary.${taskSection}`;
6147
- const promptFile = path6.join(workingDir, ".claude-prompt.txt");
6148
- await fs10.writeFile(promptFile, promptContent, "utf-8");
6242
+ const promptFile = path7.join(workingDir, ".claude-prompt.txt");
6243
+ await fs11.writeFile(promptFile, promptContent, "utf-8");
6149
6244
  if (options.auto) {
6150
6245
  console.log(import_chalk10.default.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
6151
6246
  console.log(import_chalk10.default.gray(` Spec: ${specFilePath}`));
@@ -6238,7 +6333,7 @@ Implement ONLY this task. Do not implement other tasks.`;
6238
6333
  if (options.repoType && options.repoType !== "node-express" && options.repoType !== "node-koa" && options.repoType !== "unknown") {
6239
6334
  console.log(import_chalk10.default.gray(` Language: ${options.repoType} (using language-specific codegen prompt)`));
6240
6335
  }
6241
- const spec = await fs10.readFile(specFilePath, "utf-8");
6336
+ const spec = await fs11.readFile(specFilePath, "utf-8");
6242
6337
  let constitutionSection = context?.constitution ? `
6243
6338
  === Project Constitution (MUST follow) ===
6244
6339
  ${context.constitution}
@@ -6265,7 +6360,24 @@ ${buildFrontendContextSection(fctx)}
6265
6360
  `;
6266
6361
  console.log(import_chalk10.default.gray(` Frontend context: ${fctx.framework} / ${fctx.httpClient} | hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
6267
6362
  }
6268
- const allContextText = spec + constitutionSection + dslSection + frontendSection + installedPackagesSection + sharedConfigSection;
6363
+ let fixHistorySection = "";
6364
+ if (options.injectFixHistory !== false) {
6365
+ try {
6366
+ const history = await loadFixHistory(workingDir);
6367
+ const section = buildHallucinationAvoidanceSection(history, {
6368
+ maxItems: options.fixHistoryInjectMax ?? 10
6369
+ });
6370
+ if (section) {
6371
+ fixHistorySection = `
6372
+ ${section}
6373
+ `;
6374
+ const patternCount = (section.match(/❌ Do NOT/g) ?? []).length;
6375
+ console.log(import_chalk10.default.cyan(` \u2713 Injected ${patternCount} prior hallucination pattern(s) from fix-history`));
6376
+ }
6377
+ } catch {
6378
+ }
6379
+ }
6380
+ const allContextText = spec + constitutionSection + dslSection + frontendSection + installedPackagesSection + sharedConfigSection + fixHistorySection;
6269
6381
  const estimatedTokenCount = estimateTokens(allContextText);
6270
6382
  const budget = getDefaultBudget(this.provider.providerName);
6271
6383
  if (estimatedTokenCount > budget * 0.7) {
@@ -6284,7 +6396,7 @@ ${buildFrontendContextSection(fctx)}
6284
6396
  }
6285
6397
  const tasks = await loadTasksForSpec(specFilePath);
6286
6398
  if (tasks && tasks.length > 0) {
6287
- return this.runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection + dslSection + installedPackagesSection, frontendSection, sharedConfigSection, options, systemPrompt, context);
6399
+ return this.runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection + dslSection + installedPackagesSection + fixHistorySection, frontendSection, sharedConfigSection, options, systemPrompt, context);
6288
6400
  }
6289
6401
  console.log(import_chalk10.default.gray(" [1/2] Planning implementation files..."));
6290
6402
  const planPrompt = `Based on the feature spec and project context below, list ALL files that need to be created or modified.
@@ -6295,7 +6407,7 @@ IMPORTANT: Check the "Frontend Project Context" section below. Extend existing h
6295
6407
 
6296
6408
  === Feature Spec ===
6297
6409
  ${spec}
6298
- ${constitutionSection}${dslSection}${frontendSection}${installedPackagesSection}${sharedConfigSection}
6410
+ ${constitutionSection}${dslSection}${frontendSection}${installedPackagesSection}${sharedConfigSection}${fixHistorySection}
6299
6411
  === Project Context ===
6300
6412
  ${contextSummary}
6301
6413
 
@@ -6322,7 +6434,7 @@ Output ONLY a valid JSON array:
6322
6434
  const icon = item.action === "create" ? import_chalk10.default.green("+") : import_chalk10.default.yellow("~");
6323
6435
  console.log(` ${icon} ${item.file}: ${import_chalk10.default.gray(item.description)}`);
6324
6436
  });
6325
- const { files } = await this.generateFiles(filePlan, spec, workingDir, constitutionSection + dslSection + frontendSection + installedPackagesSection, systemPrompt);
6437
+ const { files } = await this.generateFiles(filePlan, spec, workingDir, constitutionSection + dslSection + frontendSection + installedPackagesSection + fixHistorySection, systemPrompt);
6326
6438
  return files;
6327
6439
  }
6328
6440
  async runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection, frontendSection = "", sharedConfigSection = "", options = {}, systemPrompt = getCodeGenSystemPrompt(), context) {
@@ -6382,7 +6494,7 @@ Output ONLY a valid JSON array:
6382
6494
  }
6383
6495
  const filePlan = await Promise.all(
6384
6496
  task.filesToTouch.filter((f) => !sharedConfigPaths.has(f)).map(async (f) => {
6385
- const exists = await fs10.pathExists(path6.join(workingDir, f));
6497
+ const exists = await fs11.pathExists(path7.join(workingDir, f));
6386
6498
  return {
6387
6499
  file: f,
6388
6500
  action: exists ? "modify" : "create",
@@ -6422,7 +6534,7 @@ ${taskContext}`,
6422
6534
  const isViewFile = /src[\\/](views?|pages?)[\\/]/i.test(writtenFile);
6423
6535
  if (isCodeFile || isViewFile) {
6424
6536
  try {
6425
- const content = isViewFile ? `// view component \u2014 use this exact path for router imports` : await fs10.readFile(path6.join(workingDir, writtenFile), "utf-8");
6537
+ const content = isViewFile ? `// view component \u2014 use this exact path for router imports` : await fs11.readFile(path7.join(workingDir, writtenFile), "utf-8");
6426
6538
  generatedFileCache.set(writtenFile, content);
6427
6539
  } catch {
6428
6540
  }
@@ -6432,19 +6544,28 @@ ${taskContext}`,
6432
6544
  };
6433
6545
  const taskBatches = topoSortLayerTasks(layerTasks);
6434
6546
  const layerResults = [];
6547
+ const maxConcurrency = Math.max(1, options.maxConcurrency ?? 3);
6435
6548
  for (const batch of taskBatches) {
6436
6549
  const batchIsParallel = batch.length > 1;
6437
- const batchResultPromises = batch.map((task) => executeTask(task, batchIsParallel));
6438
- const settled = await Promise.allSettled(batchResultPromises);
6439
6550
  const batchResults = [];
6440
- for (let i = 0; i < settled.length; i++) {
6441
- const outcome = settled[i];
6442
- if (outcome.status === "fulfilled") {
6443
- batchResults.push(outcome.value);
6444
- } else {
6445
- const task = batch[i];
6446
- console.log(import_chalk10.default.yellow(` \u26A0 ${task.id} threw unexpectedly: ${outcome.reason?.message ?? outcome.reason}`));
6447
- batchResults.push({ task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false });
6551
+ for (let chunkStart = 0; chunkStart < batch.length; chunkStart += maxConcurrency) {
6552
+ const chunk = batch.slice(chunkStart, chunkStart + maxConcurrency);
6553
+ if (batchIsParallel && batch.length > maxConcurrency) {
6554
+ const chunkIdx = Math.floor(chunkStart / maxConcurrency) + 1;
6555
+ const totalChunks = Math.ceil(batch.length / maxConcurrency);
6556
+ console.log(import_chalk10.default.gray(` \u21B3 chunk ${chunkIdx}/${totalChunks} (${chunk.length} tasks, concurrency cap: ${maxConcurrency})`));
6557
+ }
6558
+ const chunkResultPromises = chunk.map((task) => executeTask(task, batchIsParallel));
6559
+ const settled = await Promise.allSettled(chunkResultPromises);
6560
+ for (let i = 0; i < settled.length; i++) {
6561
+ const outcome = settled[i];
6562
+ if (outcome.status === "fulfilled") {
6563
+ batchResults.push(outcome.value);
6564
+ } else {
6565
+ const task = chunk[i];
6566
+ console.log(import_chalk10.default.yellow(` \u26A0 ${task.id} threw unexpectedly: ${outcome.reason?.message ?? outcome.reason}`));
6567
+ batchResults.push({ task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false });
6568
+ }
6448
6569
  }
6449
6570
  }
6450
6571
  layerResults.push(...batchResults);
@@ -6474,7 +6595,7 @@ ${taskContext}`,
6474
6595
  const allCreatedInLayer = layerResults.flatMap((r) => r.createdFiles);
6475
6596
  for (const sharedFile of context.sharedConfigFiles) {
6476
6597
  if (processedSharedConfigs.has(sharedFile.path)) continue;
6477
- const newModuleNames = allCreatedInLayer.filter((f) => f !== sharedFile.path).map((f) => path6.basename(f).replace(/\.[jt]sx?$/, ""));
6598
+ const newModuleNames = allCreatedInLayer.filter((f) => f !== sharedFile.path).map((f) => path7.basename(f).replace(/\.[jt]sx?$/, ""));
6478
6599
  if (newModuleNames.length === 0 && sharedFile.category !== "route-index" && sharedFile.category !== "store-index") continue;
6479
6600
  let purpose = `Register/update ${sharedFile.category} entries for the new feature`;
6480
6601
  if ((sharedFile.category === "route-index" || sharedFile.category === "store-index") && newModuleNames.length > 0) {
@@ -6514,10 +6635,10 @@ Updating shared registration after layer [${layer}] completed. New modules: ${ne
6514
6635
  let successCount = 0;
6515
6636
  const writtenFiles = [];
6516
6637
  for (const item of filePlan) {
6517
- const fullPath = path6.join(workingDir, item.file);
6638
+ const fullPath = path7.join(workingDir, item.file);
6518
6639
  let existingContent = "";
6519
- if (await fs10.pathExists(fullPath)) {
6520
- existingContent = await fs10.readFile(fullPath, "utf-8");
6640
+ if (await fs11.pathExists(fullPath)) {
6641
+ existingContent = await fs11.readFile(fullPath, "utf-8");
6521
6642
  }
6522
6643
  const codePrompt = `Implement this file.
6523
6644
 
@@ -6534,8 +6655,8 @@ ${existingContent || "Output only the complete file content."}`;
6534
6655
  const raw = await this.provider.generate(codePrompt, systemPrompt);
6535
6656
  const fileContent = stripCodeFences(raw);
6536
6657
  await getActiveSnapshot()?.snapshotFile(fullPath);
6537
- await fs10.ensureDir(path6.dirname(fullPath));
6538
- await fs10.writeFile(fullPath, fileContent, "utf-8");
6658
+ await fs11.ensureDir(path7.dirname(fullPath));
6659
+ await fs11.writeFile(fullPath, fileContent, "utf-8");
6539
6660
  getActiveLogger()?.fileWritten(item.file);
6540
6661
  fileSpinner.succeed(`${existingContent ? import_chalk10.default.yellow("~") : import_chalk10.default.green("+")} ${import_chalk10.default.bold(item.file)}`);
6541
6662
  successCount++;
@@ -6556,7 +6677,7 @@ ${existingContent || "Output only the complete file content."}`;
6556
6677
  // ── Mode: plan ─────────────────────────────────────────────────────────────
6557
6678
  async runPlanMode(specFilePath) {
6558
6679
  console.log(import_chalk10.default.blue("\n\u2500\u2500\u2500 Implementation Plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6559
- const spec = await fs10.readFile(specFilePath, "utf-8");
6680
+ const spec = await fs11.readFile(specFilePath, "utf-8");
6560
6681
  const plan = await this.provider.generate(
6561
6682
  `Create a detailed, step-by-step implementation plan for the following feature spec.
6562
6683
  Be specific about:
@@ -6575,13 +6696,13 @@ ${spec}`,
6575
6696
  // core/reviewer.ts
6576
6697
  var import_chalk12 = __toESM(require("chalk"));
6577
6698
  var import_child_process3 = require("child_process");
6578
- var path8 = __toESM(require("path"));
6579
- var fs12 = __toESM(require("fs-extra"));
6699
+ var path9 = __toESM(require("path"));
6700
+ var fs13 = __toESM(require("fs-extra"));
6580
6701
 
6581
6702
  // core/constitution-generator.ts
6582
6703
  var import_chalk11 = __toESM(require("chalk"));
6583
- var fs11 = __toESM(require("fs-extra"));
6584
- var path7 = __toESM(require("path"));
6704
+ var fs12 = __toESM(require("fs-extra"));
6705
+ var path8 = __toESM(require("path"));
6585
6706
 
6586
6707
  // prompts/constitution.prompt.ts
6587
6708
  var constitutionSystemPrompt = `You are a Senior Software Architect. Analyze the provided project codebase context and generate a concise "Project Constitution" \u2014 a living document that captures the architectural rules, conventions, and red lines that ALL future feature specs and code generation MUST follow.
@@ -6661,8 +6782,8 @@ var ConstitutionGenerator = class {
6661
6782
  return this.provider.generate(prompt, constitutionSystemPrompt);
6662
6783
  }
6663
6784
  async saveConstitution(projectRoot, content) {
6664
- const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
6665
- await fs11.writeFile(filePath, content, "utf-8");
6785
+ const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
6786
+ await fs12.writeFile(filePath, content, "utf-8");
6666
6787
  return filePath;
6667
6788
  }
6668
6789
  };
@@ -6720,9 +6841,9 @@ ${sections.join("\n")}
6720
6841
  return parts.join("\n");
6721
6842
  }
6722
6843
  async function loadConstitution(projectRoot) {
6723
- const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
6724
- if (await fs11.pathExists(filePath)) {
6725
- return fs11.readFile(filePath, "utf-8");
6844
+ const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
6845
+ if (await fs12.pathExists(filePath)) {
6846
+ return fs12.readFile(filePath, "utf-8");
6726
6847
  }
6727
6848
  return void 0;
6728
6849
  }
@@ -6738,10 +6859,10 @@ function printConstitutionHint(exists) {
6738
6859
 
6739
6860
  // core/reviewer.ts
6740
6861
  async function loadAccumulatedLessons(projectRoot) {
6741
- const constitutionPath = path8.join(projectRoot, CONSTITUTION_FILE);
6862
+ const constitutionPath = path9.join(projectRoot, CONSTITUTION_FILE);
6742
6863
  let content;
6743
6864
  try {
6744
- content = await fs12.readFile(constitutionPath, "utf-8");
6865
+ content = await fs13.readFile(constitutionPath, "utf-8");
6745
6866
  } catch {
6746
6867
  return null;
6747
6868
  }
@@ -6754,21 +6875,21 @@ async function loadAccumulatedLessons(projectRoot) {
6754
6875
  }
6755
6876
  var REVIEW_HISTORY_FILE = DEFAULT_REVIEW_HISTORY_FILE;
6756
6877
  async function loadReviewHistory(projectRoot) {
6757
- const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6878
+ const historyPath = path9.join(projectRoot, REVIEW_HISTORY_FILE);
6758
6879
  try {
6759
- if (await fs12.pathExists(historyPath)) {
6760
- return await fs12.readJson(historyPath);
6880
+ if (await fs13.pathExists(historyPath)) {
6881
+ return await fs13.readJson(historyPath);
6761
6882
  }
6762
6883
  } catch {
6763
6884
  }
6764
6885
  return [];
6765
6886
  }
6766
6887
  async function appendReviewHistory(projectRoot, entry) {
6767
- const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6888
+ const historyPath = path9.join(projectRoot, REVIEW_HISTORY_FILE);
6768
6889
  const existing = await loadReviewHistory(projectRoot);
6769
6890
  const updated = [...existing, entry].slice(-20);
6770
6891
  try {
6771
- await fs12.writeJson(historyPath, updated, { spaces: 2 });
6892
+ await fs13.writeJson(historyPath, updated, { spaces: 2 });
6772
6893
  } catch {
6773
6894
  }
6774
6895
  }
@@ -6802,7 +6923,7 @@ function buildHistoryContext(history) {
6802
6923
  const lines = ["\n=== \u5386\u53F2\u5BA1\u67E5\u95EE\u9898 (Past Review Issues \u2014 check if any recur) ==="];
6803
6924
  for (const entry of recent) {
6804
6925
  lines.push(`
6805
- [${entry.date}] ${path8.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
6926
+ [${entry.date}] ${path9.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
6806
6927
  entry.topIssues.forEach((issue) => lines.push(` \xB7 ${issue}`));
6807
6928
  }
6808
6929
  return lines.join("\n") + "\n";
@@ -6922,7 +7043,7 @@ ${sep}
6922
7043
  if (score > 0 && specFile) {
6923
7044
  await appendReviewHistory(this.projectRoot, {
6924
7045
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
6925
- specFile: path8.relative(this.projectRoot, specFile),
7046
+ specFile: path9.relative(this.projectRoot, specFile),
6926
7047
  score,
6927
7048
  ...complianceScore > 0 ? { complianceScore } : {},
6928
7049
  topIssues,
@@ -6967,9 +7088,9 @@ ${sep}
6967
7088
  );
6968
7089
  let filesSection = "";
6969
7090
  for (const filePath of filePaths) {
6970
- const fullPath = path8.join(workingDir, filePath);
7091
+ const fullPath = path9.join(workingDir, filePath);
6971
7092
  try {
6972
- const content = await fs12.readFile(fullPath, "utf-8");
7093
+ const content = await fs13.readFile(fullPath, "utf-8");
6973
7094
  filesSection += `
6974
7095
 
6975
7096
  === ${filePath} ===
@@ -7003,7 +7124,7 @@ ${content.slice(0, DEFAULT_MAX_REVIEW_FILE_CHARS)}`;
7003
7124
  const color = entry.score >= 8 ? import_chalk12.default.green : entry.score >= 6 ? import_chalk12.default.yellow : import_chalk12.default.red;
7004
7125
  const impactTag = entry.impactLevel ? import_chalk12.default.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? import_chalk12.default.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? import_chalk12.default.yellow(entry.impactLevel) : import_chalk12.default.green(entry.impactLevel)}`) : "";
7005
7126
  const complexityTag = entry.complexityLevel ? import_chalk12.default.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? import_chalk12.default.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? import_chalk12.default.yellow(entry.complexityLevel) : import_chalk12.default.green(entry.complexityLevel)}`) : "";
7006
- console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
7127
+ console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path9.basename(entry.specFile)}`);
7007
7128
  }
7008
7129
  console.log(import_chalk12.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
7009
7130
  }
@@ -7056,8 +7177,8 @@ function parseSpecAndTasks(raw) {
7056
7177
 
7057
7178
  // git/worktree.ts
7058
7179
  var import_child_process4 = require("child_process");
7059
- var path9 = __toESM(require("path"));
7060
- var fs13 = __toESM(require("fs-extra"));
7180
+ var path10 = __toESM(require("path"));
7181
+ var fs14 = __toESM(require("fs-extra"));
7061
7182
  var import_chalk13 = __toESM(require("chalk"));
7062
7183
  var GitWorktreeManager = class {
7063
7184
  constructor(baseDir) {
@@ -7083,12 +7204,12 @@ var GitWorktreeManager = class {
7083
7204
  async linkDependencies(worktreePath) {
7084
7205
  const candidates = ["node_modules", "vendor"];
7085
7206
  for (const dir of candidates) {
7086
- const src = path9.join(this.baseDir, dir);
7087
- const dest = path9.join(worktreePath, dir);
7088
- if (!await fs13.pathExists(src)) continue;
7089
- if (await fs13.pathExists(dest)) continue;
7207
+ const src = path10.join(this.baseDir, dir);
7208
+ const dest = path10.join(worktreePath, dir);
7209
+ if (!await fs14.pathExists(src)) continue;
7210
+ if (await fs14.pathExists(dest)) continue;
7090
7211
  try {
7091
- await fs13.ensureSymlink(src, dest, "dir");
7212
+ await fs14.ensureSymlink(src, dest, "dir");
7092
7213
  console.log(import_chalk13.default.gray(` Symlinked ${dir}/ from base repo \u2192 worktree`));
7093
7214
  } catch (err) {
7094
7215
  console.log(import_chalk13.default.yellow(` \u26A0 Could not symlink ${dir}/: ${err.message}`));
@@ -7103,11 +7224,11 @@ var GitWorktreeManager = class {
7103
7224
  }
7104
7225
  const featureName = this.sanitizeFeatureName(idea);
7105
7226
  const branchName = `feature/${featureName}`;
7106
- const repoName = path9.basename(this.baseDir);
7107
- const worktreePath = path9.resolve(this.baseDir, "..", `${repoName}-${featureName}`);
7227
+ const repoName = path10.basename(this.baseDir);
7228
+ const worktreePath = path10.resolve(this.baseDir, "..", `${repoName}-${featureName}`);
7108
7229
  console.log(import_chalk13.default.cyan(`
7109
7230
  --- Setting up Git Worktree ---`));
7110
- if (await fs13.pathExists(worktreePath)) {
7231
+ if (await fs14.pathExists(worktreePath)) {
7111
7232
  console.log(import_chalk13.default.yellow(`\u26A0\uFE0F Worktree directory already exists at: ${worktreePath}`));
7112
7233
  await this.linkDependencies(worktreePath);
7113
7234
  return worktreePath;