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.mjs CHANGED
@@ -339,12 +339,12 @@ var PROVIDER_CATALOG = {
339
339
  },
340
340
  deepseek: {
341
341
  displayName: "DeepSeek",
342
- description: "DeepSeek \u2014 V3 (chat) / R1 (reasoning)",
342
+ description: "DeepSeek V3.2 (chat) / R1 (reasoner) \u2014 alias auto-tracks latest stable",
343
343
  models: [
344
344
  "deepseek-chat",
345
- // DeepSeek-V3
345
+ // V3.2 (alias auto-updates as DeepSeek releases new versions)
346
346
  "deepseek-reasoner"
347
- // DeepSeek-R1
347
+ // R1 (reasoning model)
348
348
  ],
349
349
  envKey: "DEEPSEEK_API_KEY",
350
350
  baseURL: "https://api.deepseek.com/v1"
@@ -4167,8 +4167,8 @@ ${currentSpec}`,
4167
4167
  // core/code-generator.ts
4168
4168
  import chalk10 from "chalk";
4169
4169
  import { execSync as execSync2, spawnSync } from "child_process";
4170
- import * as path6 from "path";
4171
- import * as fs10 from "fs-extra";
4170
+ import * as path7 from "path";
4171
+ import * as fs11 from "fs-extra";
4172
4172
 
4173
4173
  // prompts/codegen.prompt.ts
4174
4174
  var codeGenSystemPrompt = `You are a Senior Full-Stack Developer implementing features based on provided specifications.
@@ -4838,32 +4838,32 @@ function validateDsl(raw) {
4838
4838
  }
4839
4839
  return { valid: true, dsl: raw };
4840
4840
  }
4841
- function validateFeature(raw, path10, errors) {
4841
+ function validateFeature(raw, path11, errors) {
4842
4842
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4843
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4843
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4844
4844
  return;
4845
4845
  }
4846
4846
  const f = raw;
4847
- requireNonEmptyString(f["id"], `${path10}.id`, errors);
4848
- requireNonEmptyString(f["title"], `${path10}.title`, errors);
4849
- requireNonEmptyString(f["description"], `${path10}.description`, errors);
4847
+ requireNonEmptyString(f["id"], `${path11}.id`, errors);
4848
+ requireNonEmptyString(f["title"], `${path11}.title`, errors);
4849
+ requireNonEmptyString(f["description"], `${path11}.description`, errors);
4850
4850
  }
4851
- function validateModel(raw, path10, errors) {
4851
+ function validateModel(raw, path11, errors) {
4852
4852
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4853
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4853
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4854
4854
  return;
4855
4855
  }
4856
4856
  const m = raw;
4857
- requireNonEmptyString(m["name"], `${path10}.name`, errors);
4857
+ requireNonEmptyString(m["name"], `${path11}.name`, errors);
4858
4858
  if (!Array.isArray(m["fields"])) {
4859
- errors.push({ path: `${path10}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4859
+ errors.push({ path: `${path11}.fields`, message: `Must be an array, got: ${typeLabel(m["fields"])}` });
4860
4860
  } else {
4861
4861
  const fields = m["fields"];
4862
4862
  if (fields.length > MAX_FIELDS_PER_MODEL) {
4863
- errors.push({ path: `${path10}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4863
+ errors.push({ path: `${path11}.fields`, message: `Too many fields (${fields.length} > ${MAX_FIELDS_PER_MODEL})` });
4864
4864
  }
4865
4865
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
4866
- validateModelField(fields[j2], `${path10}.fields[${j2}]`, errors);
4866
+ validateModelField(fields[j2], `${path11}.fields[${j2}]`, errors);
4867
4867
  }
4868
4868
  const seenFieldNames = /* @__PURE__ */ new Set();
4869
4869
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
@@ -4872,7 +4872,7 @@ function validateModel(raw, path10, errors) {
4872
4872
  const name = f["name"];
4873
4873
  if (seenFieldNames.has(name)) {
4874
4874
  errors.push({
4875
- path: `${path10}.fields[${j2}].name`,
4875
+ path: `${path11}.fields[${j2}].name`,
4876
4876
  message: `Duplicate field name "${name}" \u2014 each field within a model must have a unique name`
4877
4877
  });
4878
4878
  } else {
@@ -4883,184 +4883,184 @@ function validateModel(raw, path10, errors) {
4883
4883
  }
4884
4884
  if (m["relations"] !== void 0) {
4885
4885
  if (!Array.isArray(m["relations"])) {
4886
- errors.push({ path: `${path10}.relations`, message: "Must be an array of strings if present" });
4886
+ errors.push({ path: `${path11}.relations`, message: "Must be an array of strings if present" });
4887
4887
  } else {
4888
4888
  const rels = m["relations"];
4889
4889
  for (let j2 = 0; j2 < rels.length; j2++) {
4890
4890
  if (typeof rels[j2] !== "string") {
4891
- errors.push({ path: `${path10}.relations[${j2}]`, message: "Must be a string" });
4891
+ errors.push({ path: `${path11}.relations[${j2}]`, message: "Must be a string" });
4892
4892
  }
4893
4893
  }
4894
4894
  }
4895
4895
  }
4896
4896
  }
4897
- function validateModelField(raw, path10, errors) {
4897
+ function validateModelField(raw, path11, errors) {
4898
4898
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4899
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4899
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4900
4900
  return;
4901
4901
  }
4902
4902
  const f = raw;
4903
- requireNonEmptyString(f["name"], `${path10}.name`, errors);
4904
- requireNonEmptyString(f["type"], `${path10}.type`, errors);
4903
+ requireNonEmptyString(f["name"], `${path11}.name`, errors);
4904
+ requireNonEmptyString(f["type"], `${path11}.type`, errors);
4905
4905
  if (typeof f["required"] !== "boolean") {
4906
- errors.push({ path: `${path10}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4906
+ errors.push({ path: `${path11}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
4907
4907
  }
4908
4908
  }
4909
- function validateEndpoint(raw, path10, errors) {
4909
+ function validateEndpoint(raw, path11, errors) {
4910
4910
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4911
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4911
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4912
4912
  return;
4913
4913
  }
4914
4914
  const e = raw;
4915
- requireNonEmptyString(e["id"], `${path10}.id`, errors);
4916
- requireNonEmptyString(e["description"], `${path10}.description`, errors);
4915
+ requireNonEmptyString(e["id"], `${path11}.id`, errors);
4916
+ requireNonEmptyString(e["description"], `${path11}.description`, errors);
4917
4917
  if (!VALID_METHODS.includes(e["method"])) {
4918
4918
  errors.push({
4919
- path: `${path10}.method`,
4919
+ path: `${path11}.method`,
4920
4920
  message: `Must be one of ${VALID_METHODS.join("|")}, got: ${JSON.stringify(e["method"])}`
4921
4921
  });
4922
4922
  }
4923
4923
  if (typeof e["path"] !== "string" || !e["path"].startsWith("/")) {
4924
4924
  errors.push({
4925
- path: `${path10}.path`,
4925
+ path: `${path11}.path`,
4926
4926
  message: `Must be a string starting with "/", got: ${JSON.stringify(e["path"])}`
4927
4927
  });
4928
4928
  }
4929
4929
  if (typeof e["auth"] !== "boolean") {
4930
- errors.push({ path: `${path10}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4930
+ errors.push({ path: `${path11}.auth`, message: `Must be boolean, got: ${typeLabel(e["auth"])}` });
4931
4931
  }
4932
4932
  if (typeof e["successStatus"] !== "number" || e["successStatus"] < 100 || e["successStatus"] > 599) {
4933
4933
  errors.push({
4934
- path: `${path10}.successStatus`,
4934
+ path: `${path11}.successStatus`,
4935
4935
  message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["successStatus"])}`
4936
4936
  });
4937
4937
  }
4938
- requireNonEmptyString(e["successDescription"], `${path10}.successDescription`, errors);
4938
+ requireNonEmptyString(e["successDescription"], `${path11}.successDescription`, errors);
4939
4939
  if (e["request"] !== void 0) {
4940
- validateRequestSchema(e["request"], `${path10}.request`, errors);
4940
+ validateRequestSchema(e["request"], `${path11}.request`, errors);
4941
4941
  }
4942
4942
  if (e["errors"] !== void 0) {
4943
4943
  if (!Array.isArray(e["errors"])) {
4944
- errors.push({ path: `${path10}.errors`, message: "Must be an array if present" });
4944
+ errors.push({ path: `${path11}.errors`, message: "Must be an array if present" });
4945
4945
  } else {
4946
4946
  const errs = e["errors"];
4947
4947
  if (errs.length > MAX_ERRORS_PER_ENDPOINT) {
4948
- errors.push({ path: `${path10}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4948
+ errors.push({ path: `${path11}.errors`, message: `Too many error entries (${errs.length} > ${MAX_ERRORS_PER_ENDPOINT})` });
4949
4949
  }
4950
4950
  for (let j2 = 0; j2 < Math.min(errs.length, MAX_ERRORS_PER_ENDPOINT); j2++) {
4951
- validateResponseError(errs[j2], `${path10}.errors[${j2}]`, errors);
4951
+ validateResponseError(errs[j2], `${path11}.errors[${j2}]`, errors);
4952
4952
  }
4953
4953
  }
4954
4954
  }
4955
4955
  }
4956
- function validateRequestSchema(raw, path10, errors) {
4956
+ function validateRequestSchema(raw, path11, errors) {
4957
4957
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4958
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4958
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4959
4959
  return;
4960
4960
  }
4961
4961
  const r = raw;
4962
4962
  for (const key of ["body", "query", "params"]) {
4963
4963
  if (r[key] !== void 0) {
4964
- validateFieldMap(r[key], `${path10}.${key}`, errors);
4964
+ validateFieldMap(r[key], `${path11}.${key}`, errors);
4965
4965
  }
4966
4966
  }
4967
4967
  }
4968
- function validateFieldMap(raw, path10, errors) {
4968
+ function validateFieldMap(raw, path11, errors) {
4969
4969
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4970
- errors.push({ path: path10, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4970
+ errors.push({ path: path11, message: `Must be a flat object (FieldMap), got: ${typeLabel(raw)}` });
4971
4971
  return;
4972
4972
  }
4973
4973
  const map = raw;
4974
4974
  for (const [k2, v2] of Object.entries(map)) {
4975
4975
  if (typeof v2 !== "string") {
4976
- errors.push({ path: `${path10}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4976
+ errors.push({ path: `${path11}.${k2}`, message: `Value must be a type-description string, got: ${typeLabel(v2)}` });
4977
4977
  }
4978
4978
  }
4979
4979
  }
4980
- function validateResponseError(raw, path10, errors) {
4980
+ function validateResponseError(raw, path11, errors) {
4981
4981
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4982
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4982
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4983
4983
  return;
4984
4984
  }
4985
4985
  const e = raw;
4986
4986
  if (typeof e["status"] !== "number" || e["status"] < 100 || e["status"] > 599) {
4987
- errors.push({ path: `${path10}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
4987
+ errors.push({ path: `${path11}.status`, message: `Must be an HTTP status code (100-599), got: ${JSON.stringify(e["status"])}` });
4988
4988
  }
4989
- requireNonEmptyString(e["code"], `${path10}.code`, errors);
4990
- requireNonEmptyString(e["description"], `${path10}.description`, errors);
4989
+ requireNonEmptyString(e["code"], `${path11}.code`, errors);
4990
+ requireNonEmptyString(e["description"], `${path11}.description`, errors);
4991
4991
  }
4992
- function validateBehavior(raw, path10, errors) {
4992
+ function validateBehavior(raw, path11, errors) {
4993
4993
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4994
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
4994
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
4995
4995
  return;
4996
4996
  }
4997
4997
  const b = raw;
4998
- requireNonEmptyString(b["id"], `${path10}.id`, errors);
4999
- requireNonEmptyString(b["description"], `${path10}.description`, errors);
4998
+ requireNonEmptyString(b["id"], `${path11}.id`, errors);
4999
+ requireNonEmptyString(b["description"], `${path11}.description`, errors);
5000
5000
  if (b["constraints"] !== void 0) {
5001
5001
  if (!Array.isArray(b["constraints"])) {
5002
- errors.push({ path: `${path10}.constraints`, message: "Must be an array of strings if present" });
5002
+ errors.push({ path: `${path11}.constraints`, message: "Must be an array of strings if present" });
5003
5003
  } else {
5004
5004
  const cs2 = b["constraints"];
5005
5005
  for (let j2 = 0; j2 < cs2.length; j2++) {
5006
5006
  if (typeof cs2[j2] !== "string") {
5007
- errors.push({ path: `${path10}.constraints[${j2}]`, message: "Must be a string" });
5007
+ errors.push({ path: `${path11}.constraints[${j2}]`, message: "Must be a string" });
5008
5008
  }
5009
5009
  }
5010
5010
  }
5011
5011
  }
5012
5012
  }
5013
- function validateComponent(raw, path10, errors) {
5013
+ function validateComponent(raw, path11, errors) {
5014
5014
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5015
- errors.push({ path: path10, message: `Must be an object, got: ${typeLabel(raw)}` });
5015
+ errors.push({ path: path11, message: `Must be an object, got: ${typeLabel(raw)}` });
5016
5016
  return;
5017
5017
  }
5018
5018
  const c = raw;
5019
- requireNonEmptyString(c["id"], `${path10}.id`, errors);
5020
- requireNonEmptyString(c["name"], `${path10}.name`, errors);
5021
- requireNonEmptyString(c["description"], `${path10}.description`, errors);
5019
+ requireNonEmptyString(c["id"], `${path11}.id`, errors);
5020
+ requireNonEmptyString(c["name"], `${path11}.name`, errors);
5021
+ requireNonEmptyString(c["description"], `${path11}.description`, errors);
5022
5022
  if (c["props"] !== void 0) {
5023
5023
  if (!Array.isArray(c["props"])) {
5024
- errors.push({ path: `${path10}.props`, message: "Must be an array if present" });
5024
+ errors.push({ path: `${path11}.props`, message: "Must be an array if present" });
5025
5025
  } else {
5026
5026
  const props = c["props"];
5027
5027
  for (let j2 = 0; j2 < props.length; j2++) {
5028
5028
  const p = props[j2];
5029
5029
  if (typeof p !== "object" || p === null) {
5030
- errors.push({ path: `${path10}.props[${j2}]`, message: "Must be an object" });
5030
+ errors.push({ path: `${path11}.props[${j2}]`, message: "Must be an object" });
5031
5031
  continue;
5032
5032
  }
5033
- requireNonEmptyString(p["name"], `${path10}.props[${j2}].name`, errors);
5034
- requireNonEmptyString(p["type"], `${path10}.props[${j2}].type`, errors);
5033
+ requireNonEmptyString(p["name"], `${path11}.props[${j2}].name`, errors);
5034
+ requireNonEmptyString(p["type"], `${path11}.props[${j2}].type`, errors);
5035
5035
  if (typeof p["required"] !== "boolean") {
5036
- errors.push({ path: `${path10}.props[${j2}].required`, message: "Must be boolean" });
5036
+ errors.push({ path: `${path11}.props[${j2}].required`, message: "Must be boolean" });
5037
5037
  }
5038
5038
  }
5039
5039
  }
5040
5040
  }
5041
5041
  if (c["events"] !== void 0) {
5042
5042
  if (!Array.isArray(c["events"])) {
5043
- errors.push({ path: `${path10}.events`, message: "Must be an array if present" });
5043
+ errors.push({ path: `${path11}.events`, message: "Must be an array if present" });
5044
5044
  } else {
5045
5045
  const events = c["events"];
5046
5046
  for (let j2 = 0; j2 < events.length; j2++) {
5047
5047
  const e = events[j2];
5048
5048
  if (typeof e !== "object" || e === null) {
5049
- errors.push({ path: `${path10}.events[${j2}]`, message: "Must be an object" });
5049
+ errors.push({ path: `${path11}.events[${j2}]`, message: "Must be an object" });
5050
5050
  continue;
5051
5051
  }
5052
- requireNonEmptyString(e["name"], `${path10}.events[${j2}].name`, errors);
5052
+ requireNonEmptyString(e["name"], `${path11}.events[${j2}].name`, errors);
5053
5053
  }
5054
5054
  }
5055
5055
  }
5056
5056
  if (c["state"] !== void 0) {
5057
5057
  if (typeof c["state"] !== "object" || Array.isArray(c["state"]) || c["state"] === null) {
5058
- errors.push({ path: `${path10}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5058
+ errors.push({ path: `${path11}.state`, message: "Must be a flat object (Record<string, string>) if present" });
5059
5059
  }
5060
5060
  }
5061
5061
  if (c["apiCalls"] !== void 0) {
5062
5062
  if (!Array.isArray(c["apiCalls"])) {
5063
- errors.push({ path: `${path10}.apiCalls`, message: "Must be an array of strings if present" });
5063
+ errors.push({ path: `${path11}.apiCalls`, message: "Must be an array of strings if present" });
5064
5064
  }
5065
5065
  }
5066
5066
  }
@@ -5122,10 +5122,10 @@ function crossReferenceChecks(obj, errors) {
5122
5122
  }
5123
5123
  }
5124
5124
  }
5125
- function requireNonEmptyString(v2, path10, errors) {
5125
+ function requireNonEmptyString(v2, path11, errors) {
5126
5126
  if (typeof v2 !== "string" || v2.trim().length === 0) {
5127
5127
  errors.push({
5128
- path: path10,
5128
+ path: path11,
5129
5129
  message: `Must be a non-empty string, got: ${typeLabel(v2)}`
5130
5130
  });
5131
5131
  }
@@ -6014,6 +6014,101 @@ function printTaskProgress(completed, total, task, mode) {
6014
6014
  }
6015
6015
  }
6016
6016
 
6017
+ // core/fix-history.ts
6018
+ import * as fs10 from "fs-extra";
6019
+ import * as path6 from "path";
6020
+ var FIX_HISTORY_FILE = ".ai-spec-fix-history.json";
6021
+ var FIX_HISTORY_VERSION = "1.0";
6022
+ async function loadFixHistory(repoRoot) {
6023
+ const filePath = path6.join(repoRoot, FIX_HISTORY_FILE);
6024
+ if (!await fs10.pathExists(filePath)) {
6025
+ return { version: FIX_HISTORY_VERSION, entries: [] };
6026
+ }
6027
+ try {
6028
+ const data = await fs10.readJson(filePath);
6029
+ if (!data || typeof data !== "object" || !Array.isArray(data.entries)) {
6030
+ return { version: FIX_HISTORY_VERSION, entries: [] };
6031
+ }
6032
+ return {
6033
+ version: typeof data.version === "string" ? data.version : FIX_HISTORY_VERSION,
6034
+ entries: data.entries
6035
+ };
6036
+ } catch {
6037
+ return { version: FIX_HISTORY_VERSION, entries: [] };
6038
+ }
6039
+ }
6040
+ function aggregateFixPatterns(history) {
6041
+ const byKey = /* @__PURE__ */ new Map();
6042
+ for (const entry of history.entries) {
6043
+ if (!byKey.has(entry.patternKey)) byKey.set(entry.patternKey, []);
6044
+ byKey.get(entry.patternKey).push(entry);
6045
+ }
6046
+ const aggregates = [];
6047
+ for (const [patternKey, entries] of byKey) {
6048
+ entries.sort((a, b) => a.ts.localeCompare(b.ts));
6049
+ const first = entries[0];
6050
+ const last = entries[entries.length - 1];
6051
+ const uniqueRunIds = new Set(entries.map((e) => e.runId)).size;
6052
+ aggregates.push({
6053
+ patternKey,
6054
+ count: entries.length,
6055
+ firstSeen: first.ts,
6056
+ lastSeen: last.ts,
6057
+ uniqueRunIds,
6058
+ source: last.brokenImport.source,
6059
+ names: last.brokenImport.names,
6060
+ reason: last.brokenImport.reason,
6061
+ fix: {
6062
+ kind: last.fix.kind,
6063
+ target: last.fix.target,
6064
+ stage: last.fix.stage
6065
+ }
6066
+ });
6067
+ }
6068
+ aggregates.sort((a, b) => {
6069
+ if (b.count !== a.count) return b.count - a.count;
6070
+ return b.lastSeen.localeCompare(a.lastSeen);
6071
+ });
6072
+ return aggregates;
6073
+ }
6074
+ function buildHallucinationAvoidanceSection(history, opts = {}) {
6075
+ const minCount = opts.minCount ?? 1;
6076
+ const maxItems = opts.maxItems ?? 10;
6077
+ const patterns = aggregateFixPatterns(history).filter((p) => p.count >= minCount);
6078
+ if (patterns.length === 0) return null;
6079
+ const top = patterns.slice(0, maxItems);
6080
+ const lines = [
6081
+ "=== Prior Hallucinations in This Project (DO NOT REPEAT) ===",
6082
+ "",
6083
+ "The following imports were previously hallucinated by AI codegen in this",
6084
+ "project and had to be auto-fixed. When generating new files, actively avoid",
6085
+ "these exact imports \u2014 they were wrong in the past and will be wrong again.",
6086
+ ""
6087
+ ];
6088
+ for (const p of top) {
6089
+ const namesLabel = p.names.length > 0 ? `{ ${p.names.join(", ")} }` : "(no names)";
6090
+ const reasonLabel = p.reason === "file_not_found" ? "file did not exist" : "named export did not exist";
6091
+ const countLabel = p.count === 1 ? "1x" : `${p.count}x`;
6092
+ const dateLabel = p.lastSeen.slice(0, 10);
6093
+ lines.push(`\u274C Do NOT: import ${namesLabel} from '${p.source}'`);
6094
+ lines.push(` Reason: ${reasonLabel} (seen ${countLabel}, last ${dateLabel})`);
6095
+ if (p.fix.kind === "create_file") {
6096
+ lines.push(` Previously fixed by creating: ${p.fix.target}`);
6097
+ } else if (p.fix.kind === "rewrite_import") {
6098
+ lines.push(` Previously fixed by rewriting the import path`);
6099
+ } else {
6100
+ lines.push(` Previously fixed by appending to: ${p.fix.target}`);
6101
+ }
6102
+ lines.push("");
6103
+ }
6104
+ if (patterns.length > maxItems) {
6105
+ lines.push(`(${patterns.length - maxItems} more pattern(s) hidden \u2014 run \`ai-spec fix-history\` to see all)`);
6106
+ lines.push("");
6107
+ }
6108
+ lines.push("=== End of Prior Hallucinations ===");
6109
+ return lines.join("\n");
6110
+ }
6111
+
6017
6112
  // core/code-generator.ts
6018
6113
  var CodeGenerator = class {
6019
6114
  constructor(provider, mode = "claude-code") {
@@ -6078,8 +6173,8 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
6078
6173
  Files: ${t.filesToTouch.join(", ")}
6079
6174
  Criteria: ${t.acceptanceCriteria.join("; ")}`).join("\n")}` : "";
6080
6175
  const promptContent = `Please read the spec file at ${specFilePath} and implement all the requirements. Create or modify files as necessary.${taskSection}`;
6081
- const promptFile = path6.join(workingDir, ".claude-prompt.txt");
6082
- await fs10.writeFile(promptFile, promptContent, "utf-8");
6176
+ const promptFile = path7.join(workingDir, ".claude-prompt.txt");
6177
+ await fs11.writeFile(promptFile, promptContent, "utf-8");
6083
6178
  if (options.auto) {
6084
6179
  console.log(chalk10.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
6085
6180
  console.log(chalk10.gray(` Spec: ${specFilePath}`));
@@ -6172,7 +6267,7 @@ Implement ONLY this task. Do not implement other tasks.`;
6172
6267
  if (options.repoType && options.repoType !== "node-express" && options.repoType !== "node-koa" && options.repoType !== "unknown") {
6173
6268
  console.log(chalk10.gray(` Language: ${options.repoType} (using language-specific codegen prompt)`));
6174
6269
  }
6175
- const spec = await fs10.readFile(specFilePath, "utf-8");
6270
+ const spec = await fs11.readFile(specFilePath, "utf-8");
6176
6271
  let constitutionSection = context?.constitution ? `
6177
6272
  === Project Constitution (MUST follow) ===
6178
6273
  ${context.constitution}
@@ -6199,7 +6294,24 @@ ${buildFrontendContextSection(fctx)}
6199
6294
  `;
6200
6295
  console.log(chalk10.gray(` Frontend context: ${fctx.framework} / ${fctx.httpClient} | hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
6201
6296
  }
6202
- const allContextText = spec + constitutionSection + dslSection + frontendSection + installedPackagesSection + sharedConfigSection;
6297
+ let fixHistorySection = "";
6298
+ if (options.injectFixHistory !== false) {
6299
+ try {
6300
+ const history = await loadFixHistory(workingDir);
6301
+ const section = buildHallucinationAvoidanceSection(history, {
6302
+ maxItems: options.fixHistoryInjectMax ?? 10
6303
+ });
6304
+ if (section) {
6305
+ fixHistorySection = `
6306
+ ${section}
6307
+ `;
6308
+ const patternCount = (section.match(/❌ Do NOT/g) ?? []).length;
6309
+ console.log(chalk10.cyan(` \u2713 Injected ${patternCount} prior hallucination pattern(s) from fix-history`));
6310
+ }
6311
+ } catch {
6312
+ }
6313
+ }
6314
+ const allContextText = spec + constitutionSection + dslSection + frontendSection + installedPackagesSection + sharedConfigSection + fixHistorySection;
6203
6315
  const estimatedTokenCount = estimateTokens(allContextText);
6204
6316
  const budget = getDefaultBudget(this.provider.providerName);
6205
6317
  if (estimatedTokenCount > budget * 0.7) {
@@ -6218,7 +6330,7 @@ ${buildFrontendContextSection(fctx)}
6218
6330
  }
6219
6331
  const tasks = await loadTasksForSpec(specFilePath);
6220
6332
  if (tasks && tasks.length > 0) {
6221
- return this.runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection + dslSection + installedPackagesSection, frontendSection, sharedConfigSection, options, systemPrompt, context);
6333
+ return this.runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection + dslSection + installedPackagesSection + fixHistorySection, frontendSection, sharedConfigSection, options, systemPrompt, context);
6222
6334
  }
6223
6335
  console.log(chalk10.gray(" [1/2] Planning implementation files..."));
6224
6336
  const planPrompt = `Based on the feature spec and project context below, list ALL files that need to be created or modified.
@@ -6229,7 +6341,7 @@ IMPORTANT: Check the "Frontend Project Context" section below. Extend existing h
6229
6341
 
6230
6342
  === Feature Spec ===
6231
6343
  ${spec}
6232
- ${constitutionSection}${dslSection}${frontendSection}${installedPackagesSection}${sharedConfigSection}
6344
+ ${constitutionSection}${dslSection}${frontendSection}${installedPackagesSection}${sharedConfigSection}${fixHistorySection}
6233
6345
  === Project Context ===
6234
6346
  ${contextSummary}
6235
6347
 
@@ -6256,7 +6368,7 @@ Output ONLY a valid JSON array:
6256
6368
  const icon = item.action === "create" ? chalk10.green("+") : chalk10.yellow("~");
6257
6369
  console.log(` ${icon} ${item.file}: ${chalk10.gray(item.description)}`);
6258
6370
  });
6259
- const { files } = await this.generateFiles(filePlan, spec, workingDir, constitutionSection + dslSection + frontendSection + installedPackagesSection, systemPrompt);
6371
+ const { files } = await this.generateFiles(filePlan, spec, workingDir, constitutionSection + dslSection + frontendSection + installedPackagesSection + fixHistorySection, systemPrompt);
6260
6372
  return files;
6261
6373
  }
6262
6374
  async runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection, frontendSection = "", sharedConfigSection = "", options = {}, systemPrompt = getCodeGenSystemPrompt(), context) {
@@ -6316,7 +6428,7 @@ Output ONLY a valid JSON array:
6316
6428
  }
6317
6429
  const filePlan = await Promise.all(
6318
6430
  task.filesToTouch.filter((f) => !sharedConfigPaths.has(f)).map(async (f) => {
6319
- const exists = await fs10.pathExists(path6.join(workingDir, f));
6431
+ const exists = await fs11.pathExists(path7.join(workingDir, f));
6320
6432
  return {
6321
6433
  file: f,
6322
6434
  action: exists ? "modify" : "create",
@@ -6356,7 +6468,7 @@ ${taskContext}`,
6356
6468
  const isViewFile = /src[\\/](views?|pages?)[\\/]/i.test(writtenFile);
6357
6469
  if (isCodeFile || isViewFile) {
6358
6470
  try {
6359
- const content = isViewFile ? `// view component \u2014 use this exact path for router imports` : await fs10.readFile(path6.join(workingDir, writtenFile), "utf-8");
6471
+ const content = isViewFile ? `// view component \u2014 use this exact path for router imports` : await fs11.readFile(path7.join(workingDir, writtenFile), "utf-8");
6360
6472
  generatedFileCache.set(writtenFile, content);
6361
6473
  } catch {
6362
6474
  }
@@ -6366,19 +6478,28 @@ ${taskContext}`,
6366
6478
  };
6367
6479
  const taskBatches = topoSortLayerTasks(layerTasks);
6368
6480
  const layerResults = [];
6481
+ const maxConcurrency = Math.max(1, options.maxConcurrency ?? 3);
6369
6482
  for (const batch of taskBatches) {
6370
6483
  const batchIsParallel = batch.length > 1;
6371
- const batchResultPromises = batch.map((task) => executeTask(task, batchIsParallel));
6372
- const settled = await Promise.allSettled(batchResultPromises);
6373
6484
  const batchResults = [];
6374
- for (let i = 0; i < settled.length; i++) {
6375
- const outcome = settled[i];
6376
- if (outcome.status === "fulfilled") {
6377
- batchResults.push(outcome.value);
6378
- } else {
6379
- const task = batch[i];
6380
- console.log(chalk10.yellow(` \u26A0 ${task.id} threw unexpectedly: ${outcome.reason?.message ?? outcome.reason}`));
6381
- batchResults.push({ task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false });
6485
+ for (let chunkStart = 0; chunkStart < batch.length; chunkStart += maxConcurrency) {
6486
+ const chunk = batch.slice(chunkStart, chunkStart + maxConcurrency);
6487
+ if (batchIsParallel && batch.length > maxConcurrency) {
6488
+ const chunkIdx = Math.floor(chunkStart / maxConcurrency) + 1;
6489
+ const totalChunks = Math.ceil(batch.length / maxConcurrency);
6490
+ console.log(chalk10.gray(` \u21B3 chunk ${chunkIdx}/${totalChunks} (${chunk.length} tasks, concurrency cap: ${maxConcurrency})`));
6491
+ }
6492
+ const chunkResultPromises = chunk.map((task) => executeTask(task, batchIsParallel));
6493
+ const settled = await Promise.allSettled(chunkResultPromises);
6494
+ for (let i = 0; i < settled.length; i++) {
6495
+ const outcome = settled[i];
6496
+ if (outcome.status === "fulfilled") {
6497
+ batchResults.push(outcome.value);
6498
+ } else {
6499
+ const task = chunk[i];
6500
+ console.log(chalk10.yellow(` \u26A0 ${task.id} threw unexpectedly: ${outcome.reason?.message ?? outcome.reason}`));
6501
+ batchResults.push({ task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false });
6502
+ }
6382
6503
  }
6383
6504
  }
6384
6505
  layerResults.push(...batchResults);
@@ -6408,7 +6529,7 @@ ${taskContext}`,
6408
6529
  const allCreatedInLayer = layerResults.flatMap((r) => r.createdFiles);
6409
6530
  for (const sharedFile of context.sharedConfigFiles) {
6410
6531
  if (processedSharedConfigs.has(sharedFile.path)) continue;
6411
- const newModuleNames = allCreatedInLayer.filter((f) => f !== sharedFile.path).map((f) => path6.basename(f).replace(/\.[jt]sx?$/, ""));
6532
+ const newModuleNames = allCreatedInLayer.filter((f) => f !== sharedFile.path).map((f) => path7.basename(f).replace(/\.[jt]sx?$/, ""));
6412
6533
  if (newModuleNames.length === 0 && sharedFile.category !== "route-index" && sharedFile.category !== "store-index") continue;
6413
6534
  let purpose = `Register/update ${sharedFile.category} entries for the new feature`;
6414
6535
  if ((sharedFile.category === "route-index" || sharedFile.category === "store-index") && newModuleNames.length > 0) {
@@ -6448,10 +6569,10 @@ Updating shared registration after layer [${layer}] completed. New modules: ${ne
6448
6569
  let successCount = 0;
6449
6570
  const writtenFiles = [];
6450
6571
  for (const item of filePlan) {
6451
- const fullPath = path6.join(workingDir, item.file);
6572
+ const fullPath = path7.join(workingDir, item.file);
6452
6573
  let existingContent = "";
6453
- if (await fs10.pathExists(fullPath)) {
6454
- existingContent = await fs10.readFile(fullPath, "utf-8");
6574
+ if (await fs11.pathExists(fullPath)) {
6575
+ existingContent = await fs11.readFile(fullPath, "utf-8");
6455
6576
  }
6456
6577
  const codePrompt = `Implement this file.
6457
6578
 
@@ -6468,8 +6589,8 @@ ${existingContent || "Output only the complete file content."}`;
6468
6589
  const raw = await this.provider.generate(codePrompt, systemPrompt);
6469
6590
  const fileContent = stripCodeFences(raw);
6470
6591
  await getActiveSnapshot()?.snapshotFile(fullPath);
6471
- await fs10.ensureDir(path6.dirname(fullPath));
6472
- await fs10.writeFile(fullPath, fileContent, "utf-8");
6592
+ await fs11.ensureDir(path7.dirname(fullPath));
6593
+ await fs11.writeFile(fullPath, fileContent, "utf-8");
6473
6594
  getActiveLogger()?.fileWritten(item.file);
6474
6595
  fileSpinner.succeed(`${existingContent ? chalk10.yellow("~") : chalk10.green("+")} ${chalk10.bold(item.file)}`);
6475
6596
  successCount++;
@@ -6490,7 +6611,7 @@ ${existingContent || "Output only the complete file content."}`;
6490
6611
  // ── Mode: plan ─────────────────────────────────────────────────────────────
6491
6612
  async runPlanMode(specFilePath) {
6492
6613
  console.log(chalk10.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"));
6493
- const spec = await fs10.readFile(specFilePath, "utf-8");
6614
+ const spec = await fs11.readFile(specFilePath, "utf-8");
6494
6615
  const plan = await this.provider.generate(
6495
6616
  `Create a detailed, step-by-step implementation plan for the following feature spec.
6496
6617
  Be specific about:
@@ -6509,13 +6630,13 @@ ${spec}`,
6509
6630
  // core/reviewer.ts
6510
6631
  import chalk12 from "chalk";
6511
6632
  import { execSync as execSync3 } from "child_process";
6512
- import * as path8 from "path";
6513
- import * as fs12 from "fs-extra";
6633
+ import * as path9 from "path";
6634
+ import * as fs13 from "fs-extra";
6514
6635
 
6515
6636
  // core/constitution-generator.ts
6516
6637
  import chalk11 from "chalk";
6517
- import * as fs11 from "fs-extra";
6518
- import * as path7 from "path";
6638
+ import * as fs12 from "fs-extra";
6639
+ import * as path8 from "path";
6519
6640
 
6520
6641
  // prompts/constitution.prompt.ts
6521
6642
  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.
@@ -6595,8 +6716,8 @@ var ConstitutionGenerator = class {
6595
6716
  return this.provider.generate(prompt, constitutionSystemPrompt);
6596
6717
  }
6597
6718
  async saveConstitution(projectRoot, content) {
6598
- const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
6599
- await fs11.writeFile(filePath, content, "utf-8");
6719
+ const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
6720
+ await fs12.writeFile(filePath, content, "utf-8");
6600
6721
  return filePath;
6601
6722
  }
6602
6723
  };
@@ -6654,9 +6775,9 @@ ${sections.join("\n")}
6654
6775
  return parts.join("\n");
6655
6776
  }
6656
6777
  async function loadConstitution(projectRoot) {
6657
- const filePath = path7.join(projectRoot, CONSTITUTION_FILE);
6658
- if (await fs11.pathExists(filePath)) {
6659
- return fs11.readFile(filePath, "utf-8");
6778
+ const filePath = path8.join(projectRoot, CONSTITUTION_FILE);
6779
+ if (await fs12.pathExists(filePath)) {
6780
+ return fs12.readFile(filePath, "utf-8");
6660
6781
  }
6661
6782
  return void 0;
6662
6783
  }
@@ -6672,10 +6793,10 @@ function printConstitutionHint(exists) {
6672
6793
 
6673
6794
  // core/reviewer.ts
6674
6795
  async function loadAccumulatedLessons(projectRoot) {
6675
- const constitutionPath = path8.join(projectRoot, CONSTITUTION_FILE);
6796
+ const constitutionPath = path9.join(projectRoot, CONSTITUTION_FILE);
6676
6797
  let content;
6677
6798
  try {
6678
- content = await fs12.readFile(constitutionPath, "utf-8");
6799
+ content = await fs13.readFile(constitutionPath, "utf-8");
6679
6800
  } catch {
6680
6801
  return null;
6681
6802
  }
@@ -6688,21 +6809,21 @@ async function loadAccumulatedLessons(projectRoot) {
6688
6809
  }
6689
6810
  var REVIEW_HISTORY_FILE = DEFAULT_REVIEW_HISTORY_FILE;
6690
6811
  async function loadReviewHistory(projectRoot) {
6691
- const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6812
+ const historyPath = path9.join(projectRoot, REVIEW_HISTORY_FILE);
6692
6813
  try {
6693
- if (await fs12.pathExists(historyPath)) {
6694
- return await fs12.readJson(historyPath);
6814
+ if (await fs13.pathExists(historyPath)) {
6815
+ return await fs13.readJson(historyPath);
6695
6816
  }
6696
6817
  } catch {
6697
6818
  }
6698
6819
  return [];
6699
6820
  }
6700
6821
  async function appendReviewHistory(projectRoot, entry) {
6701
- const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6822
+ const historyPath = path9.join(projectRoot, REVIEW_HISTORY_FILE);
6702
6823
  const existing = await loadReviewHistory(projectRoot);
6703
6824
  const updated = [...existing, entry].slice(-20);
6704
6825
  try {
6705
- await fs12.writeJson(historyPath, updated, { spaces: 2 });
6826
+ await fs13.writeJson(historyPath, updated, { spaces: 2 });
6706
6827
  } catch {
6707
6828
  }
6708
6829
  }
@@ -6736,7 +6857,7 @@ function buildHistoryContext(history) {
6736
6857
  const lines = ["\n=== \u5386\u53F2\u5BA1\u67E5\u95EE\u9898 (Past Review Issues \u2014 check if any recur) ==="];
6737
6858
  for (const entry of recent) {
6738
6859
  lines.push(`
6739
- [${entry.date}] ${path8.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
6860
+ [${entry.date}] ${path9.basename(entry.specFile)} \u2014 Score: ${entry.score}/10`);
6740
6861
  entry.topIssues.forEach((issue) => lines.push(` \xB7 ${issue}`));
6741
6862
  }
6742
6863
  return lines.join("\n") + "\n";
@@ -6856,7 +6977,7 @@ ${sep}
6856
6977
  if (score > 0 && specFile) {
6857
6978
  await appendReviewHistory(this.projectRoot, {
6858
6979
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
6859
- specFile: path8.relative(this.projectRoot, specFile),
6980
+ specFile: path9.relative(this.projectRoot, specFile),
6860
6981
  score,
6861
6982
  ...complianceScore > 0 ? { complianceScore } : {},
6862
6983
  topIssues,
@@ -6901,9 +7022,9 @@ ${sep}
6901
7022
  );
6902
7023
  let filesSection = "";
6903
7024
  for (const filePath of filePaths) {
6904
- const fullPath = path8.join(workingDir, filePath);
7025
+ const fullPath = path9.join(workingDir, filePath);
6905
7026
  try {
6906
- const content = await fs12.readFile(fullPath, "utf-8");
7027
+ const content = await fs13.readFile(fullPath, "utf-8");
6907
7028
  filesSection += `
6908
7029
 
6909
7030
  === ${filePath} ===
@@ -6937,7 +7058,7 @@ ${content.slice(0, DEFAULT_MAX_REVIEW_FILE_CHARS)}`;
6937
7058
  const color = entry.score >= 8 ? chalk12.green : entry.score >= 6 ? chalk12.yellow : chalk12.red;
6938
7059
  const impactTag = entry.impactLevel ? chalk12.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? chalk12.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? chalk12.yellow(entry.impactLevel) : chalk12.green(entry.impactLevel)}`) : "";
6939
7060
  const complexityTag = entry.complexityLevel ? chalk12.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? chalk12.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? chalk12.yellow(entry.complexityLevel) : chalk12.green(entry.complexityLevel)}`) : "";
6940
- console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
7061
+ console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path9.basename(entry.specFile)}`);
6941
7062
  }
6942
7063
  console.log(chalk12.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"));
6943
7064
  }
@@ -6990,8 +7111,8 @@ function parseSpecAndTasks(raw) {
6990
7111
 
6991
7112
  // git/worktree.ts
6992
7113
  import { execSync as execSync4 } from "child_process";
6993
- import * as path9 from "path";
6994
- import * as fs13 from "fs-extra";
7114
+ import * as path10 from "path";
7115
+ import * as fs14 from "fs-extra";
6995
7116
  import chalk13 from "chalk";
6996
7117
  var GitWorktreeManager = class {
6997
7118
  constructor(baseDir) {
@@ -7017,12 +7138,12 @@ var GitWorktreeManager = class {
7017
7138
  async linkDependencies(worktreePath) {
7018
7139
  const candidates = ["node_modules", "vendor"];
7019
7140
  for (const dir of candidates) {
7020
- const src = path9.join(this.baseDir, dir);
7021
- const dest = path9.join(worktreePath, dir);
7022
- if (!await fs13.pathExists(src)) continue;
7023
- if (await fs13.pathExists(dest)) continue;
7141
+ const src = path10.join(this.baseDir, dir);
7142
+ const dest = path10.join(worktreePath, dir);
7143
+ if (!await fs14.pathExists(src)) continue;
7144
+ if (await fs14.pathExists(dest)) continue;
7024
7145
  try {
7025
- await fs13.ensureSymlink(src, dest, "dir");
7146
+ await fs14.ensureSymlink(src, dest, "dir");
7026
7147
  console.log(chalk13.gray(` Symlinked ${dir}/ from base repo \u2192 worktree`));
7027
7148
  } catch (err) {
7028
7149
  console.log(chalk13.yellow(` \u26A0 Could not symlink ${dir}/: ${err.message}`));
@@ -7037,11 +7158,11 @@ var GitWorktreeManager = class {
7037
7158
  }
7038
7159
  const featureName = this.sanitizeFeatureName(idea);
7039
7160
  const branchName = `feature/${featureName}`;
7040
- const repoName = path9.basename(this.baseDir);
7041
- const worktreePath = path9.resolve(this.baseDir, "..", `${repoName}-${featureName}`);
7161
+ const repoName = path10.basename(this.baseDir);
7162
+ const worktreePath = path10.resolve(this.baseDir, "..", `${repoName}-${featureName}`);
7042
7163
  console.log(chalk13.cyan(`
7043
7164
  --- Setting up Git Worktree ---`));
7044
- if (await fs13.pathExists(worktreePath)) {
7165
+ if (await fs14.pathExists(worktreePath)) {
7045
7166
  console.log(chalk13.yellow(`\u26A0\uFE0F Worktree directory already exists at: ${worktreePath}`));
7046
7167
  await this.linkDependencies(worktreePath);
7047
7168
  return worktreePath;