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