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.
- package/README.md +60 -30
- package/cli/commands/config.ts +129 -1
- package/cli/commands/create.ts +14 -0
- package/cli/commands/fix-history.ts +176 -0
- package/cli/commands/init.ts +36 -1
- package/cli/index.ts +2 -6
- package/cli/pipeline/helpers.ts +6 -0
- package/cli/pipeline/multi-repo.ts +291 -26
- package/cli/pipeline/single-repo.ts +103 -2
- package/cli/utils.ts +23 -0
- package/core/code-generator.ts +63 -14
- package/core/cross-stack-verifier.ts +395 -0
- package/core/fix-history.ts +333 -0
- package/core/import-fixer.ts +827 -0
- package/core/import-verifier.ts +569 -0
- package/core/knowledge-memory.ts +55 -6
- package/core/self-evaluator.ts +44 -7
- package/core/spec-generator.ts +3 -3
- package/core/types-generator.ts +2 -2
- package/dist/cli/index.js +3759 -2207
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +3747 -2195
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +249 -128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +249 -128
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/tests/cross-stack-verifier.test.ts +301 -0
- package/tests/fix-history.test.ts +335 -0
- package/tests/import-fixer.test.ts +944 -0
- package/tests/import-verifier.test.ts +420 -0
- package/tests/knowledge-memory.test.ts +40 -0
- package/tests/self-evaluator.test.ts +97 -0
- package/cli/commands/model.ts +0 -152
- package/cli/commands/scan.ts +0 -99
- 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
|
|
408
|
+
description: "DeepSeek V3.2 (chat) / R1 (reasoner) \u2014 alias auto-tracks latest stable",
|
|
409
409
|
models: [
|
|
410
410
|
"deepseek-chat",
|
|
411
|
-
// DeepSeek
|
|
411
|
+
// V3.2 (alias auto-updates as DeepSeek releases new versions)
|
|
412
412
|
"deepseek-reasoner"
|
|
413
|
-
//
|
|
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
|
|
4237
|
-
var
|
|
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,
|
|
4907
|
+
function validateFeature(raw, path11, errors) {
|
|
4908
4908
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
4909
|
-
errors.push({ path:
|
|
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"], `${
|
|
4914
|
-
requireNonEmptyString(f["title"], `${
|
|
4915
|
-
requireNonEmptyString(f["description"], `${
|
|
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,
|
|
4917
|
+
function validateModel(raw, path11, errors) {
|
|
4918
4918
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
4919
|
-
errors.push({ path:
|
|
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"], `${
|
|
4923
|
+
requireNonEmptyString(m["name"], `${path11}.name`, errors);
|
|
4924
4924
|
if (!Array.isArray(m["fields"])) {
|
|
4925
|
-
errors.push({ path: `${
|
|
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: `${
|
|
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], `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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,
|
|
4963
|
+
function validateModelField(raw, path11, errors) {
|
|
4964
4964
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
4965
|
-
errors.push({ path:
|
|
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"], `${
|
|
4970
|
-
requireNonEmptyString(f["type"], `${
|
|
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: `${
|
|
4972
|
+
errors.push({ path: `${path11}.required`, message: `Must be boolean, got: ${typeLabel(f["required"])}` });
|
|
4973
4973
|
}
|
|
4974
4974
|
}
|
|
4975
|
-
function validateEndpoint(raw,
|
|
4975
|
+
function validateEndpoint(raw, path11, errors) {
|
|
4976
4976
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
4977
|
-
errors.push({ path:
|
|
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"], `${
|
|
4982
|
-
requireNonEmptyString(e["description"], `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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"], `${
|
|
5004
|
+
requireNonEmptyString(e["successDescription"], `${path11}.successDescription`, errors);
|
|
5005
5005
|
if (e["request"] !== void 0) {
|
|
5006
|
-
validateRequestSchema(e["request"], `${
|
|
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: `${
|
|
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: `${
|
|
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], `${
|
|
5017
|
+
validateResponseError(errs[j2], `${path11}.errors[${j2}]`, errors);
|
|
5018
5018
|
}
|
|
5019
5019
|
}
|
|
5020
5020
|
}
|
|
5021
5021
|
}
|
|
5022
|
-
function validateRequestSchema(raw,
|
|
5022
|
+
function validateRequestSchema(raw, path11, errors) {
|
|
5023
5023
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5024
|
-
errors.push({ path:
|
|
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], `${
|
|
5030
|
+
validateFieldMap(r[key], `${path11}.${key}`, errors);
|
|
5031
5031
|
}
|
|
5032
5032
|
}
|
|
5033
5033
|
}
|
|
5034
|
-
function validateFieldMap(raw,
|
|
5034
|
+
function validateFieldMap(raw, path11, errors) {
|
|
5035
5035
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5036
|
-
errors.push({ path:
|
|
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: `${
|
|
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,
|
|
5046
|
+
function validateResponseError(raw, path11, errors) {
|
|
5047
5047
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5048
|
-
errors.push({ path:
|
|
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: `${
|
|
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"], `${
|
|
5056
|
-
requireNonEmptyString(e["description"], `${
|
|
5055
|
+
requireNonEmptyString(e["code"], `${path11}.code`, errors);
|
|
5056
|
+
requireNonEmptyString(e["description"], `${path11}.description`, errors);
|
|
5057
5057
|
}
|
|
5058
|
-
function validateBehavior(raw,
|
|
5058
|
+
function validateBehavior(raw, path11, errors) {
|
|
5059
5059
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5060
|
-
errors.push({ path:
|
|
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"], `${
|
|
5065
|
-
requireNonEmptyString(b["description"], `${
|
|
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: `${
|
|
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: `${
|
|
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,
|
|
5079
|
+
function validateComponent(raw, path11, errors) {
|
|
5080
5080
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5081
|
-
errors.push({ path:
|
|
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"], `${
|
|
5086
|
-
requireNonEmptyString(c["name"], `${
|
|
5087
|
-
requireNonEmptyString(c["description"], `${
|
|
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: `${
|
|
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: `${
|
|
5096
|
+
errors.push({ path: `${path11}.props[${j2}]`, message: "Must be an object" });
|
|
5097
5097
|
continue;
|
|
5098
5098
|
}
|
|
5099
|
-
requireNonEmptyString(p["name"], `${
|
|
5100
|
-
requireNonEmptyString(p["type"], `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
5115
|
+
errors.push({ path: `${path11}.events[${j2}]`, message: "Must be an object" });
|
|
5116
5116
|
continue;
|
|
5117
5117
|
}
|
|
5118
|
-
requireNonEmptyString(e["name"], `${
|
|
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: `${
|
|
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: `${
|
|
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,
|
|
5191
|
+
function requireNonEmptyString(v2, path11, errors) {
|
|
5192
5192
|
if (typeof v2 !== "string" || v2.trim().length === 0) {
|
|
5193
5193
|
errors.push({
|
|
5194
|
-
path:
|
|
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 =
|
|
6148
|
-
await
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
6441
|
-
const
|
|
6442
|
-
if (
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
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) =>
|
|
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 =
|
|
6638
|
+
const fullPath = path7.join(workingDir, item.file);
|
|
6518
6639
|
let existingContent = "";
|
|
6519
|
-
if (await
|
|
6520
|
-
existingContent = await
|
|
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
|
|
6538
|
-
await
|
|
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
|
|
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
|
|
6579
|
-
var
|
|
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
|
|
6584
|
-
var
|
|
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 =
|
|
6665
|
-
await
|
|
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 =
|
|
6724
|
-
if (await
|
|
6725
|
-
return
|
|
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 =
|
|
6862
|
+
const constitutionPath = path9.join(projectRoot, CONSTITUTION_FILE);
|
|
6742
6863
|
let content;
|
|
6743
6864
|
try {
|
|
6744
|
-
content = await
|
|
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 =
|
|
6878
|
+
const historyPath = path9.join(projectRoot, REVIEW_HISTORY_FILE);
|
|
6758
6879
|
try {
|
|
6759
|
-
if (await
|
|
6760
|
-
return await
|
|
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 =
|
|
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
|
|
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}] ${
|
|
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:
|
|
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 =
|
|
7091
|
+
const fullPath = path9.join(workingDir, filePath);
|
|
6971
7092
|
try {
|
|
6972
|
-
const content = await
|
|
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} ${
|
|
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
|
|
7060
|
-
var
|
|
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 =
|
|
7087
|
-
const dest =
|
|
7088
|
-
if (!await
|
|
7089
|
-
if (await
|
|
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
|
|
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 =
|
|
7107
|
-
const worktreePath =
|
|
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
|
|
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;
|