qfai 1.0.7 → 1.1.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 +71 -10
- package/assets/init/.qfai/README.md +2 -1
- package/assets/init/.qfai/assistant/README.md +1 -1
- package/assets/init/.qfai/assistant/prompts/README.md +1 -1
- package/assets/init/.qfai/assistant/prompts/qfai-configure.md +31 -8
- package/assets/init/.qfai/assistant/prompts/qfai-verify.md +1 -1
- package/assets/init/.qfai/assistant/steering/README.md +6 -0
- package/assets/init/.qfai/assistant/steering/manifest.md +43 -0
- package/assets/init/.qfai/contracts/db/README.md +10 -3
- package/assets/init/.qfai/samples/guardrails/delta_with_guardrails.md +19 -0
- package/assets/init/.qfai/specs/README.md +4 -0
- package/assets/init/root/.github/workflows/qfai.yml +0 -2
- package/assets/init/root/qfai.config.yaml +1 -8
- package/dist/cli/index.cjs +873 -195
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +859 -181
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +731 -221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -1
- package/dist/index.d.ts +91 -1
- package/dist/index.mjs +719 -216
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/assets/init/.qfai/assistant/prompts/qfai-pr.md +0 -209
package/dist/cli/index.cjs
CHANGED
|
@@ -24,12 +24,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli/commands/doctor.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_promises10 = require("fs/promises");
|
|
28
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
29
29
|
|
|
30
30
|
// src/core/doctor.ts
|
|
31
|
-
var
|
|
32
|
-
var
|
|
31
|
+
var import_promises9 = require("fs/promises");
|
|
32
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
33
33
|
|
|
34
34
|
// src/core/config.ts
|
|
35
35
|
var import_promises = require("fs/promises");
|
|
@@ -47,15 +47,7 @@ var defaultConfig = {
|
|
|
47
47
|
validation: {
|
|
48
48
|
failOn: "error",
|
|
49
49
|
require: {
|
|
50
|
-
specSections: [
|
|
51
|
-
"\u80CC\u666F",
|
|
52
|
-
"\u30B9\u30B3\u30FC\u30D7",
|
|
53
|
-
"\u975E\u30B4\u30FC\u30EB",
|
|
54
|
-
"\u7528\u8A9E",
|
|
55
|
-
"\u524D\u63D0",
|
|
56
|
-
"\u6C7A\u5B9A\u4E8B\u9805",
|
|
57
|
-
"\u696D\u52D9\u30EB\u30FC\u30EB"
|
|
58
|
-
]
|
|
50
|
+
specSections: []
|
|
59
51
|
},
|
|
60
52
|
traceability: {
|
|
61
53
|
brMustHaveSc: true,
|
|
@@ -1075,8 +1067,8 @@ var import_promises7 = require("fs/promises");
|
|
|
1075
1067
|
var import_node_path9 = __toESM(require("path"), 1);
|
|
1076
1068
|
var import_node_url2 = require("url");
|
|
1077
1069
|
async function resolveToolVersion() {
|
|
1078
|
-
if ("1.0
|
|
1079
|
-
return "1.0
|
|
1070
|
+
if ("1.1.0".length > 0) {
|
|
1071
|
+
return "1.1.0";
|
|
1080
1072
|
}
|
|
1081
1073
|
try {
|
|
1082
1074
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1094,10 +1086,464 @@ function resolvePackageJsonPath() {
|
|
|
1094
1086
|
return import_node_path9.default.resolve(import_node_path9.default.dirname(basePath), "../../package.json");
|
|
1095
1087
|
}
|
|
1096
1088
|
|
|
1089
|
+
// src/core/decisionGuardrails.ts
|
|
1090
|
+
var import_promises8 = require("fs/promises");
|
|
1091
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1092
|
+
|
|
1093
|
+
// src/core/parse/markdown.ts
|
|
1094
|
+
var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
1095
|
+
function parseHeadings(md) {
|
|
1096
|
+
const lines = md.split(/\r?\n/);
|
|
1097
|
+
const headings = [];
|
|
1098
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1099
|
+
const line = lines[i] ?? "";
|
|
1100
|
+
const match = line.match(HEADING_RE);
|
|
1101
|
+
if (!match) continue;
|
|
1102
|
+
const levelToken = match[1];
|
|
1103
|
+
const title = match[2];
|
|
1104
|
+
if (!levelToken || !title) continue;
|
|
1105
|
+
headings.push({
|
|
1106
|
+
level: levelToken.length,
|
|
1107
|
+
title: title.trim(),
|
|
1108
|
+
line: i + 1
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
return headings;
|
|
1112
|
+
}
|
|
1113
|
+
function extractH2Sections(md) {
|
|
1114
|
+
const lines = md.split(/\r?\n/);
|
|
1115
|
+
const headings = parseHeadings(md).filter((heading) => heading.level === 2);
|
|
1116
|
+
const sections = /* @__PURE__ */ new Map();
|
|
1117
|
+
for (let i = 0; i < headings.length; i++) {
|
|
1118
|
+
const current = headings[i];
|
|
1119
|
+
if (!current) continue;
|
|
1120
|
+
const next = headings[i + 1];
|
|
1121
|
+
const startLine = current.line + 1;
|
|
1122
|
+
const endLine = (next?.line ?? lines.length + 1) - 1;
|
|
1123
|
+
const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
|
|
1124
|
+
sections.set(current.title.trim(), {
|
|
1125
|
+
title: current.title.trim(),
|
|
1126
|
+
startLine,
|
|
1127
|
+
endLine,
|
|
1128
|
+
body
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
return sections;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/core/decisionGuardrails.ts
|
|
1135
|
+
var DEFAULT_DECISION_GUARDRAILS_GLOBS = [".qfai/specs/**/delta.md"];
|
|
1136
|
+
var DEFAULT_GUARDRAILS_IGNORE_GLOBS = [
|
|
1137
|
+
"**/node_modules/**",
|
|
1138
|
+
"**/.git/**",
|
|
1139
|
+
"**/dist/**",
|
|
1140
|
+
"**/build/**",
|
|
1141
|
+
"**/.pnpm/**",
|
|
1142
|
+
"**/tmp/**",
|
|
1143
|
+
"**/.mcp-tools/**"
|
|
1144
|
+
];
|
|
1145
|
+
var SECTION_TITLE = "decision guardrails";
|
|
1146
|
+
var ENTRY_START_RE = /^\s*[-*]\s+ID:\s*(.+?)\s*$/i;
|
|
1147
|
+
var FIELD_RE = /^\s{2,}([A-Za-z][A-Za-z0-9 _-]*):\s*(.*)$/;
|
|
1148
|
+
var CONTINUATION_RE = /^\s{4,}(.+)$/;
|
|
1149
|
+
var ID_FORMAT_RE = /^DG-\d{4}$/;
|
|
1150
|
+
var TYPE_ORDER = {
|
|
1151
|
+
"non-goal": 0,
|
|
1152
|
+
"not-now": 1,
|
|
1153
|
+
"trade-off": 2
|
|
1154
|
+
};
|
|
1155
|
+
async function loadDecisionGuardrails(root, options = {}) {
|
|
1156
|
+
const errors = [];
|
|
1157
|
+
const files = await scanDecisionGuardrailFiles(
|
|
1158
|
+
root,
|
|
1159
|
+
options.paths,
|
|
1160
|
+
errors,
|
|
1161
|
+
options.specsRoot
|
|
1162
|
+
);
|
|
1163
|
+
const entries = [];
|
|
1164
|
+
for (const filePath of files) {
|
|
1165
|
+
try {
|
|
1166
|
+
const content = await (0, import_promises8.readFile)(filePath, "utf-8");
|
|
1167
|
+
const parsed = extractDecisionGuardrailsFromMarkdown(content, filePath);
|
|
1168
|
+
entries.push(...parsed);
|
|
1169
|
+
} catch (error2) {
|
|
1170
|
+
errors.push({ path: filePath, message: String(error2) });
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return { entries, errors, files };
|
|
1174
|
+
}
|
|
1175
|
+
function extractDecisionGuardrailsFromMarkdown(markdown, filePath) {
|
|
1176
|
+
const sections = extractH2Sections(markdown);
|
|
1177
|
+
const section = findDecisionGuardrailsSection(sections);
|
|
1178
|
+
if (!section) {
|
|
1179
|
+
return [];
|
|
1180
|
+
}
|
|
1181
|
+
const lines = section.body.split(/\r?\n/);
|
|
1182
|
+
const entries = [];
|
|
1183
|
+
let current = null;
|
|
1184
|
+
const flush = () => {
|
|
1185
|
+
if (!current) {
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const entry = {
|
|
1189
|
+
keywords: current.keywords,
|
|
1190
|
+
source: { file: filePath, line: current.startLine },
|
|
1191
|
+
...current.fields.id ? { id: current.fields.id } : {},
|
|
1192
|
+
...current.fields.type ? { type: current.fields.type } : {},
|
|
1193
|
+
...current.fields.guardrail ? { guardrail: current.fields.guardrail } : {},
|
|
1194
|
+
...current.fields.rationale ? { rationale: current.fields.rationale } : {},
|
|
1195
|
+
...current.fields.reconsider ? { reconsider: current.fields.reconsider } : {},
|
|
1196
|
+
...current.fields.related ? { related: current.fields.related } : {},
|
|
1197
|
+
...current.fields.title ? { title: current.fields.title } : {}
|
|
1198
|
+
};
|
|
1199
|
+
entries.push(entry);
|
|
1200
|
+
current = null;
|
|
1201
|
+
};
|
|
1202
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1203
|
+
const rawLine = lines[i] ?? "";
|
|
1204
|
+
const lineNumber = section.startLine + i;
|
|
1205
|
+
const entryMatch = rawLine.match(ENTRY_START_RE);
|
|
1206
|
+
if (entryMatch) {
|
|
1207
|
+
flush();
|
|
1208
|
+
const id = entryMatch[1]?.trim() ?? "";
|
|
1209
|
+
current = {
|
|
1210
|
+
startLine: lineNumber,
|
|
1211
|
+
fields: { id },
|
|
1212
|
+
keywords: []
|
|
1213
|
+
};
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
if (!current) {
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
const fieldMatch = rawLine.match(FIELD_RE);
|
|
1220
|
+
if (fieldMatch) {
|
|
1221
|
+
const rawKey = fieldMatch[1] ?? "";
|
|
1222
|
+
const value = fieldMatch[2] ?? "";
|
|
1223
|
+
const key = normalizeFieldKey(rawKey);
|
|
1224
|
+
if (key) {
|
|
1225
|
+
if (key === "keywords") {
|
|
1226
|
+
current.keywords.push(
|
|
1227
|
+
...value.split(",").map((item) => item.trim()).filter((item) => item.length > 0)
|
|
1228
|
+
);
|
|
1229
|
+
} else {
|
|
1230
|
+
const trimmed = value.trim();
|
|
1231
|
+
if (trimmed.length > 0) {
|
|
1232
|
+
const existing = current.fields[key];
|
|
1233
|
+
current.fields[key] = existing ? `${existing}
|
|
1234
|
+
${trimmed}` : trimmed;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
current.lastKey = key;
|
|
1238
|
+
} else {
|
|
1239
|
+
delete current.lastKey;
|
|
1240
|
+
}
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
const continuationMatch = rawLine.match(CONTINUATION_RE);
|
|
1244
|
+
if (continuationMatch && current.lastKey) {
|
|
1245
|
+
const value = continuationMatch[1]?.trim() ?? "";
|
|
1246
|
+
if (value.length > 0) {
|
|
1247
|
+
const existing = current.fields[current.lastKey];
|
|
1248
|
+
current.fields[current.lastKey] = existing ? `${existing}
|
|
1249
|
+
${value}` : value;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
flush();
|
|
1254
|
+
return entries;
|
|
1255
|
+
}
|
|
1256
|
+
function normalizeDecisionGuardrails(entries) {
|
|
1257
|
+
const items = [];
|
|
1258
|
+
for (const entry of entries) {
|
|
1259
|
+
const id = entry.id?.trim();
|
|
1260
|
+
const type = normalizeGuardrailType(entry.type);
|
|
1261
|
+
const guardrail = entry.guardrail?.trim();
|
|
1262
|
+
if (!id || !type || !guardrail) {
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const item = {
|
|
1266
|
+
id,
|
|
1267
|
+
type,
|
|
1268
|
+
guardrail,
|
|
1269
|
+
keywords: entry.keywords?.filter((word) => word.length > 0) ?? [],
|
|
1270
|
+
source: entry.source
|
|
1271
|
+
};
|
|
1272
|
+
const rationale = entry.rationale?.trim();
|
|
1273
|
+
if (rationale) {
|
|
1274
|
+
item.rationale = rationale;
|
|
1275
|
+
}
|
|
1276
|
+
const reconsider = entry.reconsider?.trim();
|
|
1277
|
+
if (reconsider) {
|
|
1278
|
+
item.reconsider = reconsider;
|
|
1279
|
+
}
|
|
1280
|
+
const related = entry.related?.trim();
|
|
1281
|
+
if (related) {
|
|
1282
|
+
item.related = related;
|
|
1283
|
+
}
|
|
1284
|
+
const title = entry.title?.trim();
|
|
1285
|
+
if (title) {
|
|
1286
|
+
item.title = title;
|
|
1287
|
+
}
|
|
1288
|
+
items.push(item);
|
|
1289
|
+
}
|
|
1290
|
+
return items;
|
|
1291
|
+
}
|
|
1292
|
+
function sortDecisionGuardrails(items) {
|
|
1293
|
+
return [...items].sort((a, b) => {
|
|
1294
|
+
const typeOrder = (TYPE_ORDER[a.type] ?? 999) - (TYPE_ORDER[b.type] ?? 999);
|
|
1295
|
+
if (typeOrder !== 0) {
|
|
1296
|
+
return typeOrder;
|
|
1297
|
+
}
|
|
1298
|
+
return a.id.localeCompare(b.id);
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function filterDecisionGuardrailsByKeyword(items, keyword) {
|
|
1302
|
+
const needle = keyword?.trim().toLowerCase();
|
|
1303
|
+
if (!needle) {
|
|
1304
|
+
return items;
|
|
1305
|
+
}
|
|
1306
|
+
return items.filter((item) => {
|
|
1307
|
+
const haystack = [
|
|
1308
|
+
item.title,
|
|
1309
|
+
item.guardrail,
|
|
1310
|
+
item.rationale,
|
|
1311
|
+
item.related,
|
|
1312
|
+
item.keywords.join(" ")
|
|
1313
|
+
].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
|
|
1314
|
+
return haystack.some((value) => value.includes(needle));
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
function formatGuardrailsForLlm(items, max) {
|
|
1318
|
+
const limit = Math.max(0, Math.floor(max));
|
|
1319
|
+
const lines = ["# Decision Guardrails (extract)", ""];
|
|
1320
|
+
const slice = limit > 0 ? items.slice(0, limit) : [];
|
|
1321
|
+
if (slice.length === 0) {
|
|
1322
|
+
lines.push("- (none)");
|
|
1323
|
+
return lines.join("\n");
|
|
1324
|
+
}
|
|
1325
|
+
for (const item of slice) {
|
|
1326
|
+
lines.push(`- [${item.id}][${item.type}] ${item.guardrail}`);
|
|
1327
|
+
if (item.rationale) {
|
|
1328
|
+
lines.push(` Rationale: ${item.rationale}`);
|
|
1329
|
+
}
|
|
1330
|
+
if (item.reconsider) {
|
|
1331
|
+
lines.push(` Reconsider: ${item.reconsider}`);
|
|
1332
|
+
}
|
|
1333
|
+
if (item.related) {
|
|
1334
|
+
lines.push(` Related: ${item.related}`);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return lines.join("\n");
|
|
1338
|
+
}
|
|
1339
|
+
function checkDecisionGuardrails(entries) {
|
|
1340
|
+
const errors = [];
|
|
1341
|
+
const warnings = [];
|
|
1342
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1343
|
+
for (const entry of entries) {
|
|
1344
|
+
const file = entry.source.file;
|
|
1345
|
+
const line = entry.source.line;
|
|
1346
|
+
const id = entry.id?.trim();
|
|
1347
|
+
const typeRaw = entry.type?.trim();
|
|
1348
|
+
const guardrail = entry.guardrail?.trim();
|
|
1349
|
+
const rationale = entry.rationale?.trim();
|
|
1350
|
+
const reconsider = entry.reconsider?.trim();
|
|
1351
|
+
if (!id) {
|
|
1352
|
+
errors.push({
|
|
1353
|
+
severity: "error",
|
|
1354
|
+
code: "QFAI-GR-001",
|
|
1355
|
+
message: "ID is missing",
|
|
1356
|
+
file,
|
|
1357
|
+
line
|
|
1358
|
+
});
|
|
1359
|
+
} else {
|
|
1360
|
+
const list = idMap.get(id) ?? [];
|
|
1361
|
+
list.push(entry);
|
|
1362
|
+
idMap.set(id, list);
|
|
1363
|
+
if (!ID_FORMAT_RE.test(id)) {
|
|
1364
|
+
warnings.push({
|
|
1365
|
+
severity: "warning",
|
|
1366
|
+
code: "QFAI-GR-002",
|
|
1367
|
+
message: `ID format is not standard: ${id}`,
|
|
1368
|
+
file,
|
|
1369
|
+
line,
|
|
1370
|
+
id
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (!typeRaw) {
|
|
1375
|
+
errors.push({
|
|
1376
|
+
severity: "error",
|
|
1377
|
+
code: "QFAI-GR-003",
|
|
1378
|
+
message: "Type is missing",
|
|
1379
|
+
file,
|
|
1380
|
+
line,
|
|
1381
|
+
...id ? { id } : {}
|
|
1382
|
+
});
|
|
1383
|
+
} else if (!normalizeGuardrailType(typeRaw)) {
|
|
1384
|
+
errors.push({
|
|
1385
|
+
severity: "error",
|
|
1386
|
+
code: "QFAI-GR-004",
|
|
1387
|
+
message: `Type is invalid: ${typeRaw}`,
|
|
1388
|
+
file,
|
|
1389
|
+
line,
|
|
1390
|
+
...id ? { id } : {}
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
if (!guardrail) {
|
|
1394
|
+
errors.push({
|
|
1395
|
+
severity: "error",
|
|
1396
|
+
code: "QFAI-GR-005",
|
|
1397
|
+
message: "Guardrail is missing",
|
|
1398
|
+
file,
|
|
1399
|
+
line,
|
|
1400
|
+
...id ? { id } : {}
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
if (!rationale) {
|
|
1404
|
+
warnings.push({
|
|
1405
|
+
severity: "warning",
|
|
1406
|
+
code: "QFAI-GR-006",
|
|
1407
|
+
message: "Rationale is missing",
|
|
1408
|
+
file,
|
|
1409
|
+
line,
|
|
1410
|
+
...id ? { id } : {}
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
if (!reconsider) {
|
|
1414
|
+
warnings.push({
|
|
1415
|
+
severity: "warning",
|
|
1416
|
+
code: "QFAI-GR-007",
|
|
1417
|
+
message: "Reconsider is missing",
|
|
1418
|
+
file,
|
|
1419
|
+
line,
|
|
1420
|
+
...id ? { id } : {}
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
for (const [id, list] of idMap.entries()) {
|
|
1425
|
+
if (list.length > 1) {
|
|
1426
|
+
const locations = list.map((entry) => `${entry.source.file}:${entry.source.line}`).join(", ");
|
|
1427
|
+
const first = list[0];
|
|
1428
|
+
const file = first?.source.file ?? "";
|
|
1429
|
+
const line = first?.source.line;
|
|
1430
|
+
errors.push({
|
|
1431
|
+
severity: "error",
|
|
1432
|
+
code: "QFAI-GR-008",
|
|
1433
|
+
message: `ID is duplicated: ${id} (${locations})`,
|
|
1434
|
+
file,
|
|
1435
|
+
...line !== void 0 ? { line } : {},
|
|
1436
|
+
id
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return { errors, warnings };
|
|
1441
|
+
}
|
|
1442
|
+
function normalizeGuardrailType(raw) {
|
|
1443
|
+
if (!raw) {
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
const normalized = raw.trim().toLowerCase().replace(/[_\s]+/g, "-");
|
|
1447
|
+
if (normalized === "non-goal") {
|
|
1448
|
+
return "non-goal";
|
|
1449
|
+
}
|
|
1450
|
+
if (normalized === "not-now") {
|
|
1451
|
+
return "not-now";
|
|
1452
|
+
}
|
|
1453
|
+
if (normalized === "trade-off") {
|
|
1454
|
+
return "trade-off";
|
|
1455
|
+
}
|
|
1456
|
+
return null;
|
|
1457
|
+
}
|
|
1458
|
+
function normalizeFieldKey(raw) {
|
|
1459
|
+
const normalized = raw.trim().toLowerCase().replace(/[_\s-]+/g, "");
|
|
1460
|
+
switch (normalized) {
|
|
1461
|
+
case "id":
|
|
1462
|
+
return "id";
|
|
1463
|
+
case "type":
|
|
1464
|
+
return "type";
|
|
1465
|
+
case "guardrail":
|
|
1466
|
+
return "guardrail";
|
|
1467
|
+
case "rationale":
|
|
1468
|
+
case "reason":
|
|
1469
|
+
return "rationale";
|
|
1470
|
+
case "reconsider":
|
|
1471
|
+
return "reconsider";
|
|
1472
|
+
case "related":
|
|
1473
|
+
return "related";
|
|
1474
|
+
case "keywords":
|
|
1475
|
+
case "keyword":
|
|
1476
|
+
return "keywords";
|
|
1477
|
+
case "title":
|
|
1478
|
+
case "heading":
|
|
1479
|
+
return "title";
|
|
1480
|
+
default:
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
async function scanDecisionGuardrailFiles(root, rawPaths, errors, specsRoot) {
|
|
1485
|
+
if (!rawPaths || rawPaths.length === 0) {
|
|
1486
|
+
const scanRoot = specsRoot ? import_node_path10.default.isAbsolute(specsRoot) ? specsRoot : import_node_path10.default.resolve(root, specsRoot) : root;
|
|
1487
|
+
const globs = specsRoot ? ["**/delta.md"] : DEFAULT_DECISION_GUARDRAILS_GLOBS;
|
|
1488
|
+
try {
|
|
1489
|
+
const result = await collectFilesByGlobs(scanRoot, {
|
|
1490
|
+
globs,
|
|
1491
|
+
ignore: DEFAULT_GUARDRAILS_IGNORE_GLOBS
|
|
1492
|
+
});
|
|
1493
|
+
return result.files.sort((a, b) => a.localeCompare(b));
|
|
1494
|
+
} catch (error2) {
|
|
1495
|
+
errors.push({ path: scanRoot, message: String(error2) });
|
|
1496
|
+
return [];
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
const files = /* @__PURE__ */ new Set();
|
|
1500
|
+
for (const rawPath of rawPaths) {
|
|
1501
|
+
const resolved = import_node_path10.default.isAbsolute(rawPath) ? rawPath : import_node_path10.default.resolve(root, rawPath);
|
|
1502
|
+
const stats = await safeStat(resolved);
|
|
1503
|
+
if (!stats) {
|
|
1504
|
+
errors.push({ path: resolved, message: "Path does not exist" });
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if (stats.isFile()) {
|
|
1508
|
+
files.add(resolved);
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (stats.isDirectory()) {
|
|
1512
|
+
try {
|
|
1513
|
+
const result = await collectFilesByGlobs(resolved, {
|
|
1514
|
+
globs: ["**/delta.md"],
|
|
1515
|
+
ignore: DEFAULT_GUARDRAILS_IGNORE_GLOBS
|
|
1516
|
+
});
|
|
1517
|
+
result.files.forEach((file) => files.add(file));
|
|
1518
|
+
} catch (error2) {
|
|
1519
|
+
errors.push({ path: resolved, message: String(error2) });
|
|
1520
|
+
}
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
errors.push({ path: resolved, message: "Unsupported path type" });
|
|
1524
|
+
}
|
|
1525
|
+
return Array.from(files).sort((a, b) => a.localeCompare(b));
|
|
1526
|
+
}
|
|
1527
|
+
async function safeStat(target) {
|
|
1528
|
+
try {
|
|
1529
|
+
return await (0, import_promises8.stat)(target);
|
|
1530
|
+
} catch {
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
function findDecisionGuardrailsSection(sections) {
|
|
1535
|
+
for (const [title, section] of sections.entries()) {
|
|
1536
|
+
if (title.trim().toLowerCase() === SECTION_TITLE) {
|
|
1537
|
+
return section;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1097
1543
|
// src/core/doctor.ts
|
|
1098
1544
|
async function exists4(target) {
|
|
1099
1545
|
try {
|
|
1100
|
-
await (0,
|
|
1546
|
+
await (0, import_promises9.access)(target);
|
|
1101
1547
|
return true;
|
|
1102
1548
|
} catch {
|
|
1103
1549
|
return false;
|
|
@@ -1117,7 +1563,7 @@ function normalizeGlobs2(values) {
|
|
|
1117
1563
|
return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
1118
1564
|
}
|
|
1119
1565
|
async function createDoctorData(options) {
|
|
1120
|
-
const startDir =
|
|
1566
|
+
const startDir = import_node_path11.default.resolve(options.startDir);
|
|
1121
1567
|
const checks = [];
|
|
1122
1568
|
const configPath = getConfigPath(startDir);
|
|
1123
1569
|
const search = options.rootExplicit ? {
|
|
@@ -1179,9 +1625,9 @@ async function createDoctorData(options) {
|
|
|
1179
1625
|
details: { path: toRelativePath(root, resolved) }
|
|
1180
1626
|
});
|
|
1181
1627
|
if (key === "promptsDir") {
|
|
1182
|
-
const promptsLocalDir =
|
|
1183
|
-
|
|
1184
|
-
`${
|
|
1628
|
+
const promptsLocalDir = import_node_path11.default.join(
|
|
1629
|
+
import_node_path11.default.dirname(resolved),
|
|
1630
|
+
`${import_node_path11.default.basename(resolved)}.local`
|
|
1185
1631
|
);
|
|
1186
1632
|
const found = await exists4(promptsLocalDir);
|
|
1187
1633
|
addCheck(checks, {
|
|
@@ -1254,7 +1700,36 @@ async function createDoctorData(options) {
|
|
|
1254
1700
|
message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
|
|
1255
1701
|
details: { specPacks: entries.length, missingFiles }
|
|
1256
1702
|
});
|
|
1257
|
-
const
|
|
1703
|
+
const guardrailsLoad = await loadDecisionGuardrails(root, {
|
|
1704
|
+
specsRoot
|
|
1705
|
+
});
|
|
1706
|
+
const guardrailsItems = normalizeDecisionGuardrails(guardrailsLoad.entries);
|
|
1707
|
+
let guardrailsSeverity;
|
|
1708
|
+
let guardrailsMessage;
|
|
1709
|
+
if (guardrailsLoad.errors.length > 0) {
|
|
1710
|
+
guardrailsSeverity = "warning";
|
|
1711
|
+
guardrailsMessage = `Decision Guardrails scan failed (errors=${guardrailsLoad.errors.length})`;
|
|
1712
|
+
} else if (guardrailsItems.length === 0) {
|
|
1713
|
+
guardrailsSeverity = "info";
|
|
1714
|
+
guardrailsMessage = "Decision Guardrails not found (optional)";
|
|
1715
|
+
} else {
|
|
1716
|
+
guardrailsSeverity = "ok";
|
|
1717
|
+
guardrailsMessage = `Decision Guardrails detected (count=${guardrailsItems.length})`;
|
|
1718
|
+
}
|
|
1719
|
+
addCheck(checks, {
|
|
1720
|
+
id: "guardrails.present",
|
|
1721
|
+
severity: guardrailsSeverity,
|
|
1722
|
+
title: "Decision Guardrails",
|
|
1723
|
+
message: guardrailsMessage,
|
|
1724
|
+
details: {
|
|
1725
|
+
count: guardrailsItems.length,
|
|
1726
|
+
errors: guardrailsLoad.errors.map((item) => ({
|
|
1727
|
+
path: toRelativePath(root, item.path),
|
|
1728
|
+
message: item.message
|
|
1729
|
+
}))
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
const validateJsonAbs = import_node_path11.default.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : import_node_path11.default.resolve(root, config.output.validateJsonPath);
|
|
1258
1733
|
const validateJsonExists = await exists4(validateJsonAbs);
|
|
1259
1734
|
addCheck(checks, {
|
|
1260
1735
|
id: "output.validateJson",
|
|
@@ -1264,8 +1739,8 @@ async function createDoctorData(options) {
|
|
|
1264
1739
|
details: { path: toRelativePath(root, validateJsonAbs) }
|
|
1265
1740
|
});
|
|
1266
1741
|
const outDirAbs = resolvePath(root, config, "outDir");
|
|
1267
|
-
const rel =
|
|
1268
|
-
const inside = rel !== "" && !rel.startsWith("..") && !
|
|
1742
|
+
const rel = import_node_path11.default.relative(outDirAbs, validateJsonAbs);
|
|
1743
|
+
const inside = rel !== "" && !rel.startsWith("..") && !import_node_path11.default.isAbsolute(rel);
|
|
1269
1744
|
addCheck(checks, {
|
|
1270
1745
|
id: "output.pathAlignment",
|
|
1271
1746
|
severity: inside ? "ok" : "warning",
|
|
@@ -1388,12 +1863,12 @@ async function detectOutDirCollisions(root) {
|
|
|
1388
1863
|
});
|
|
1389
1864
|
const configPaths = configScan.files;
|
|
1390
1865
|
const configRoots = Array.from(
|
|
1391
|
-
new Set(configPaths.map((configPath) =>
|
|
1866
|
+
new Set(configPaths.map((configPath) => import_node_path11.default.dirname(configPath)))
|
|
1392
1867
|
).sort((a, b) => a.localeCompare(b));
|
|
1393
1868
|
const outDirToRoots = /* @__PURE__ */ new Map();
|
|
1394
1869
|
for (const configRoot of configRoots) {
|
|
1395
1870
|
const { config } = await loadConfig(configRoot);
|
|
1396
|
-
const outDir =
|
|
1871
|
+
const outDir = import_node_path11.default.normalize(resolvePath(configRoot, config, "outDir"));
|
|
1397
1872
|
const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
|
|
1398
1873
|
roots.add(configRoot);
|
|
1399
1874
|
outDirToRoots.set(outDir, roots);
|
|
@@ -1419,20 +1894,20 @@ async function detectOutDirCollisions(root) {
|
|
|
1419
1894
|
};
|
|
1420
1895
|
}
|
|
1421
1896
|
async function findMonorepoRoot(startDir) {
|
|
1422
|
-
let current =
|
|
1897
|
+
let current = import_node_path11.default.resolve(startDir);
|
|
1423
1898
|
while (true) {
|
|
1424
|
-
const gitPath =
|
|
1425
|
-
const workspacePath =
|
|
1899
|
+
const gitPath = import_node_path11.default.join(current, ".git");
|
|
1900
|
+
const workspacePath = import_node_path11.default.join(current, "pnpm-workspace.yaml");
|
|
1426
1901
|
if (await exists4(gitPath) || await exists4(workspacePath)) {
|
|
1427
1902
|
return current;
|
|
1428
1903
|
}
|
|
1429
|
-
const parent =
|
|
1904
|
+
const parent = import_node_path11.default.dirname(current);
|
|
1430
1905
|
if (parent === current) {
|
|
1431
1906
|
break;
|
|
1432
1907
|
}
|
|
1433
1908
|
current = parent;
|
|
1434
1909
|
}
|
|
1435
|
-
return
|
|
1910
|
+
return import_node_path11.default.resolve(startDir);
|
|
1436
1911
|
}
|
|
1437
1912
|
|
|
1438
1913
|
// src/cli/lib/logger.ts
|
|
@@ -1474,9 +1949,9 @@ async function runDoctor(options) {
|
|
|
1474
1949
|
const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
|
|
1475
1950
|
const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
|
|
1476
1951
|
if (options.outPath) {
|
|
1477
|
-
const outAbs =
|
|
1478
|
-
await (0,
|
|
1479
|
-
await (0,
|
|
1952
|
+
const outAbs = import_node_path12.default.isAbsolute(options.outPath) ? options.outPath : import_node_path12.default.resolve(process.cwd(), options.outPath);
|
|
1953
|
+
await (0, import_promises10.mkdir)(import_node_path12.default.dirname(outAbs), { recursive: true });
|
|
1954
|
+
await (0, import_promises10.writeFile)(outAbs, `${output}
|
|
1480
1955
|
`, "utf-8");
|
|
1481
1956
|
info(`doctor: wrote ${outAbs}`);
|
|
1482
1957
|
return exitCode;
|
|
@@ -1494,12 +1969,77 @@ function shouldFailDoctor(summary, failOn) {
|
|
|
1494
1969
|
return summary.warning + summary.error > 0;
|
|
1495
1970
|
}
|
|
1496
1971
|
|
|
1497
|
-
// src/cli/commands/
|
|
1972
|
+
// src/cli/commands/guardrails.ts
|
|
1498
1973
|
var import_node_path13 = __toESM(require("path"), 1);
|
|
1974
|
+
var DEFAULT_EXTRACT_MAX = 20;
|
|
1975
|
+
async function runGuardrails(options) {
|
|
1976
|
+
if (!options.action) {
|
|
1977
|
+
error("guardrails: action is required (list|extract|check)");
|
|
1978
|
+
return 2;
|
|
1979
|
+
}
|
|
1980
|
+
const root = import_node_path13.default.resolve(options.root);
|
|
1981
|
+
const { entries, errors } = await loadDecisionGuardrails(root, {
|
|
1982
|
+
paths: options.paths
|
|
1983
|
+
});
|
|
1984
|
+
if (errors.length > 0) {
|
|
1985
|
+
errors.forEach((item) => {
|
|
1986
|
+
error(`guardrails: ${item.path}: ${item.message}`);
|
|
1987
|
+
});
|
|
1988
|
+
return 2;
|
|
1989
|
+
}
|
|
1990
|
+
if (options.action === "check") {
|
|
1991
|
+
return runGuardrailsCheck(entries, root);
|
|
1992
|
+
}
|
|
1993
|
+
const items = sortDecisionGuardrails(normalizeDecisionGuardrails(entries));
|
|
1994
|
+
const filtered = filterDecisionGuardrailsByKeyword(items, options.keyword);
|
|
1995
|
+
if (options.action === "extract") {
|
|
1996
|
+
const max = options.max !== void 0 ? options.max : DEFAULT_EXTRACT_MAX;
|
|
1997
|
+
if (!Number.isFinite(max) || max < 0) {
|
|
1998
|
+
error("guardrails: --max must be a non-negative number");
|
|
1999
|
+
return 2;
|
|
2000
|
+
}
|
|
2001
|
+
info(formatGuardrailsForLlm(filtered, max));
|
|
2002
|
+
return 0;
|
|
2003
|
+
}
|
|
2004
|
+
info(formatGuardrailsList(filtered, root));
|
|
2005
|
+
return 0;
|
|
2006
|
+
}
|
|
2007
|
+
function formatGuardrailsList(items, root) {
|
|
2008
|
+
const lines = ["# Decision Guardrails (list)", ""];
|
|
2009
|
+
if (items.length === 0) {
|
|
2010
|
+
lines.push("- (none)");
|
|
2011
|
+
return lines.join("\n");
|
|
2012
|
+
}
|
|
2013
|
+
for (const item of items) {
|
|
2014
|
+
const relPath = toRelativePath(root, item.source.file);
|
|
2015
|
+
const location = `${relPath}:${item.source.line}`;
|
|
2016
|
+
lines.push(`- [${item.id}][${item.type}] ${item.guardrail} (${location})`);
|
|
2017
|
+
}
|
|
2018
|
+
return lines.join("\n");
|
|
2019
|
+
}
|
|
2020
|
+
function runGuardrailsCheck(entries, root) {
|
|
2021
|
+
const result = checkDecisionGuardrails(entries);
|
|
2022
|
+
const lines = [
|
|
2023
|
+
`guardrails check: error=${result.errors.length} warning=${result.warnings.length}`
|
|
2024
|
+
];
|
|
2025
|
+
const formatIssue = (issue7) => {
|
|
2026
|
+
const relPath = toRelativePath(root, issue7.file);
|
|
2027
|
+
const line = issue7.line ? `:${issue7.line}` : "";
|
|
2028
|
+
const id = issue7.id ? ` id=${issue7.id}` : "";
|
|
2029
|
+
return `[${issue7.severity}] ${issue7.code} ${issue7.message} (${relPath}${line})${id}`;
|
|
2030
|
+
};
|
|
2031
|
+
result.errors.forEach((issue7) => lines.push(formatIssue(issue7)));
|
|
2032
|
+
result.warnings.forEach((issue7) => lines.push(formatIssue(issue7)));
|
|
2033
|
+
info(lines.join("\n"));
|
|
2034
|
+
return result.errors.length > 0 ? 1 : 0;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// src/cli/commands/init.ts
|
|
2038
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
1499
2039
|
|
|
1500
2040
|
// src/cli/lib/fs.ts
|
|
1501
|
-
var
|
|
1502
|
-
var
|
|
2041
|
+
var import_promises11 = require("fs/promises");
|
|
2042
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
1503
2043
|
async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
1504
2044
|
const files = await collectTemplateFiles(sourceRoot);
|
|
1505
2045
|
return copyFiles(files, sourceRoot, destRoot, options);
|
|
@@ -1507,7 +2047,7 @@ async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
|
1507
2047
|
async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
|
|
1508
2048
|
const allFiles = [];
|
|
1509
2049
|
for (const relPath of relativePaths) {
|
|
1510
|
-
const fullPath =
|
|
2050
|
+
const fullPath = import_node_path14.default.join(sourceRoot, relPath);
|
|
1511
2051
|
const files = await collectTemplateFiles(fullPath);
|
|
1512
2052
|
allFiles.push(...files);
|
|
1513
2053
|
}
|
|
@@ -1517,13 +2057,13 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1517
2057
|
const copied = [];
|
|
1518
2058
|
const skipped = [];
|
|
1519
2059
|
const conflicts = [];
|
|
1520
|
-
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
1521
|
-
const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
2060
|
+
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path14.default.sep);
|
|
2061
|
+
const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path14.default.sep);
|
|
1522
2062
|
const isProtectedRelative = (relative) => {
|
|
1523
2063
|
if (protectPrefixes.length === 0) {
|
|
1524
2064
|
return false;
|
|
1525
2065
|
}
|
|
1526
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
2066
|
+
const normalized = relative.replace(/[\\/]+/g, import_node_path14.default.sep);
|
|
1527
2067
|
return protectPrefixes.some(
|
|
1528
2068
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1529
2069
|
);
|
|
@@ -1532,7 +2072,7 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1532
2072
|
if (excludePrefixes.length === 0) {
|
|
1533
2073
|
return false;
|
|
1534
2074
|
}
|
|
1535
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
2075
|
+
const normalized = relative.replace(/[\\/]+/g, import_node_path14.default.sep);
|
|
1536
2076
|
return excludePrefixes.some(
|
|
1537
2077
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1538
2078
|
);
|
|
@@ -1540,14 +2080,14 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1540
2080
|
const conflictPolicy = options.conflictPolicy ?? "error";
|
|
1541
2081
|
if (!options.force && conflictPolicy === "error") {
|
|
1542
2082
|
for (const file of files) {
|
|
1543
|
-
const relative =
|
|
2083
|
+
const relative = import_node_path14.default.relative(sourceRoot, file);
|
|
1544
2084
|
if (isExcludedRelative(relative)) {
|
|
1545
2085
|
continue;
|
|
1546
2086
|
}
|
|
1547
2087
|
if (isProtectedRelative(relative)) {
|
|
1548
2088
|
continue;
|
|
1549
2089
|
}
|
|
1550
|
-
const dest =
|
|
2090
|
+
const dest = import_node_path14.default.join(destRoot, relative);
|
|
1551
2091
|
if (!await shouldWrite(dest, options.force)) {
|
|
1552
2092
|
conflicts.push(dest);
|
|
1553
2093
|
}
|
|
@@ -1557,19 +2097,19 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1557
2097
|
}
|
|
1558
2098
|
}
|
|
1559
2099
|
for (const file of files) {
|
|
1560
|
-
const relative =
|
|
2100
|
+
const relative = import_node_path14.default.relative(sourceRoot, file);
|
|
1561
2101
|
if (isExcludedRelative(relative)) {
|
|
1562
2102
|
continue;
|
|
1563
2103
|
}
|
|
1564
|
-
const dest =
|
|
2104
|
+
const dest = import_node_path14.default.join(destRoot, relative);
|
|
1565
2105
|
const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
|
|
1566
2106
|
if (!await shouldWrite(dest, forceForThisFile)) {
|
|
1567
2107
|
skipped.push(dest);
|
|
1568
2108
|
continue;
|
|
1569
2109
|
}
|
|
1570
2110
|
if (!options.dryRun) {
|
|
1571
|
-
await (0,
|
|
1572
|
-
await (0,
|
|
2111
|
+
await (0, import_promises11.mkdir)(import_node_path14.default.dirname(dest), { recursive: true });
|
|
2112
|
+
await (0, import_promises11.copyFile)(file, dest);
|
|
1573
2113
|
}
|
|
1574
2114
|
copied.push(dest);
|
|
1575
2115
|
}
|
|
@@ -1590,9 +2130,9 @@ async function collectTemplateFiles(root) {
|
|
|
1590
2130
|
if (!await exists5(root)) {
|
|
1591
2131
|
return entries;
|
|
1592
2132
|
}
|
|
1593
|
-
const items = await (0,
|
|
2133
|
+
const items = await (0, import_promises11.readdir)(root, { withFileTypes: true });
|
|
1594
2134
|
for (const item of items) {
|
|
1595
|
-
const fullPath =
|
|
2135
|
+
const fullPath = import_node_path14.default.join(root, item.name);
|
|
1596
2136
|
if (item.isDirectory()) {
|
|
1597
2137
|
const nested = await collectTemplateFiles(fullPath);
|
|
1598
2138
|
entries.push(...nested);
|
|
@@ -1612,7 +2152,7 @@ async function shouldWrite(target, force) {
|
|
|
1612
2152
|
}
|
|
1613
2153
|
async function exists5(target) {
|
|
1614
2154
|
try {
|
|
1615
|
-
await (0,
|
|
2155
|
+
await (0, import_promises11.access)(target);
|
|
1616
2156
|
return true;
|
|
1617
2157
|
} catch {
|
|
1618
2158
|
return false;
|
|
@@ -1622,10 +2162,10 @@ async function exists5(target) {
|
|
|
1622
2162
|
// src/cli/commands/init.ts
|
|
1623
2163
|
async function runInit(options) {
|
|
1624
2164
|
const assetsRoot = getInitAssetsDir();
|
|
1625
|
-
const rootAssets =
|
|
1626
|
-
const qfaiAssets =
|
|
1627
|
-
const destRoot =
|
|
1628
|
-
const destQfai =
|
|
2165
|
+
const rootAssets = import_node_path15.default.join(assetsRoot, "root");
|
|
2166
|
+
const qfaiAssets = import_node_path15.default.join(assetsRoot, ".qfai");
|
|
2167
|
+
const destRoot = import_node_path15.default.resolve(options.dir);
|
|
2168
|
+
const destQfai = import_node_path15.default.join(destRoot, ".qfai");
|
|
1629
2169
|
if (options.force) {
|
|
1630
2170
|
info(
|
|
1631
2171
|
"NOTE: --force \u306F .qfai/assistant/prompts/** \u306E\u307F\u4E0A\u66F8\u304D\u3057\u307E\u3059\uFF08prompts.local \u306F\u4FDD\u8B77\u3055\u308C\u3001specs/contracts \u7B49\u306F\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093\uFF09\u3002"
|
|
@@ -1671,15 +2211,15 @@ function report(copied, skipped, dryRun, label, baseDir) {
|
|
|
1671
2211
|
info(` skipped: ${skipped.length}`);
|
|
1672
2212
|
info(" skipped paths:");
|
|
1673
2213
|
for (const skippedPath of skipped) {
|
|
1674
|
-
const relative =
|
|
2214
|
+
const relative = import_node_path15.default.relative(baseDir, skippedPath);
|
|
1675
2215
|
info(` - ${relative}`);
|
|
1676
2216
|
}
|
|
1677
2217
|
}
|
|
1678
2218
|
}
|
|
1679
2219
|
|
|
1680
2220
|
// src/cli/commands/report.ts
|
|
1681
|
-
var
|
|
1682
|
-
var
|
|
2221
|
+
var import_promises20 = require("fs/promises");
|
|
2222
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
1683
2223
|
|
|
1684
2224
|
// src/core/normalize.ts
|
|
1685
2225
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1719,12 +2259,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1719
2259
|
}
|
|
1720
2260
|
|
|
1721
2261
|
// src/core/report.ts
|
|
1722
|
-
var
|
|
1723
|
-
var
|
|
2262
|
+
var import_promises19 = require("fs/promises");
|
|
2263
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
1724
2264
|
|
|
1725
2265
|
// src/core/contractIndex.ts
|
|
1726
|
-
var
|
|
1727
|
-
var
|
|
2266
|
+
var import_promises12 = require("fs/promises");
|
|
2267
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
1728
2268
|
|
|
1729
2269
|
// src/core/contractsDecl.ts
|
|
1730
2270
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1746,9 +2286,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1746
2286
|
// src/core/contractIndex.ts
|
|
1747
2287
|
async function buildContractIndex(root, config) {
|
|
1748
2288
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1749
|
-
const uiRoot =
|
|
1750
|
-
const apiRoot =
|
|
1751
|
-
const dbRoot =
|
|
2289
|
+
const uiRoot = import_node_path16.default.join(contractsRoot, "ui");
|
|
2290
|
+
const apiRoot = import_node_path16.default.join(contractsRoot, "api");
|
|
2291
|
+
const dbRoot = import_node_path16.default.join(contractsRoot, "db");
|
|
1752
2292
|
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1753
2293
|
collectUiContractFiles(uiRoot),
|
|
1754
2294
|
collectThemaContractFiles(uiRoot),
|
|
@@ -1768,7 +2308,7 @@ async function buildContractIndex(root, config) {
|
|
|
1768
2308
|
}
|
|
1769
2309
|
async function indexContractFiles(files, index) {
|
|
1770
2310
|
for (const file of files) {
|
|
1771
|
-
const text = await (0,
|
|
2311
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
1772
2312
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1773
2313
|
}
|
|
1774
2314
|
}
|
|
@@ -1893,53 +2433,11 @@ function unique3(values) {
|
|
|
1893
2433
|
return Array.from(new Set(values));
|
|
1894
2434
|
}
|
|
1895
2435
|
|
|
1896
|
-
// src/core/parse/markdown.ts
|
|
1897
|
-
var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
1898
|
-
function parseHeadings(md) {
|
|
1899
|
-
const lines = md.split(/\r?\n/);
|
|
1900
|
-
const headings = [];
|
|
1901
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1902
|
-
const line = lines[i] ?? "";
|
|
1903
|
-
const match = line.match(HEADING_RE);
|
|
1904
|
-
if (!match) continue;
|
|
1905
|
-
const levelToken = match[1];
|
|
1906
|
-
const title = match[2];
|
|
1907
|
-
if (!levelToken || !title) continue;
|
|
1908
|
-
headings.push({
|
|
1909
|
-
level: levelToken.length,
|
|
1910
|
-
title: title.trim(),
|
|
1911
|
-
line: i + 1
|
|
1912
|
-
});
|
|
1913
|
-
}
|
|
1914
|
-
return headings;
|
|
1915
|
-
}
|
|
1916
|
-
function extractH2Sections(md) {
|
|
1917
|
-
const lines = md.split(/\r?\n/);
|
|
1918
|
-
const headings = parseHeadings(md).filter((heading) => heading.level === 2);
|
|
1919
|
-
const sections = /* @__PURE__ */ new Map();
|
|
1920
|
-
for (let i = 0; i < headings.length; i++) {
|
|
1921
|
-
const current = headings[i];
|
|
1922
|
-
if (!current) continue;
|
|
1923
|
-
const next = headings[i + 1];
|
|
1924
|
-
const startLine = current.line + 1;
|
|
1925
|
-
const endLine = (next?.line ?? lines.length + 1) - 1;
|
|
1926
|
-
const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
|
|
1927
|
-
sections.set(current.title.trim(), {
|
|
1928
|
-
title: current.title.trim(),
|
|
1929
|
-
startLine,
|
|
1930
|
-
endLine,
|
|
1931
|
-
body
|
|
1932
|
-
});
|
|
1933
|
-
}
|
|
1934
|
-
return sections;
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
2436
|
// src/core/parse/spec.ts
|
|
1938
2437
|
var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
|
|
1939
2438
|
var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
|
|
1940
2439
|
var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
|
|
1941
2440
|
var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
|
|
1942
|
-
var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
|
|
1943
2441
|
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
1944
2442
|
function parseSpec(md, file) {
|
|
1945
2443
|
const headings = parseHeadings(md);
|
|
@@ -1947,15 +2445,13 @@ function parseSpec(md, file) {
|
|
|
1947
2445
|
const specId = h1?.title.match(SPEC_ID_RE)?.[0];
|
|
1948
2446
|
const sections = extractH2Sections(md);
|
|
1949
2447
|
const sectionNames = new Set(Array.from(sections.keys()));
|
|
1950
|
-
const
|
|
1951
|
-
const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
|
|
1952
|
-
const startLine = brSection?.startLine ?? 1;
|
|
2448
|
+
const lines = md.split(/\r?\n/);
|
|
1953
2449
|
const brs = [];
|
|
1954
2450
|
const brsWithoutPriority = [];
|
|
1955
2451
|
const brsWithInvalidPriority = [];
|
|
1956
|
-
for (let i = 0; i <
|
|
1957
|
-
const lineText =
|
|
1958
|
-
const lineNumber =
|
|
2452
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2453
|
+
const lineText = lines[i] ?? "";
|
|
2454
|
+
const lineNumber = i + 1;
|
|
1959
2455
|
const validMatch = lineText.match(BR_LINE_RE);
|
|
1960
2456
|
if (validMatch) {
|
|
1961
2457
|
const id = validMatch[1];
|
|
@@ -2013,14 +2509,14 @@ function parseSpec(md, file) {
|
|
|
2013
2509
|
}
|
|
2014
2510
|
|
|
2015
2511
|
// src/core/validators/contracts.ts
|
|
2016
|
-
var
|
|
2017
|
-
var
|
|
2512
|
+
var import_promises13 = require("fs/promises");
|
|
2513
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2018
2514
|
|
|
2019
2515
|
// src/core/contracts.ts
|
|
2020
|
-
var
|
|
2516
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2021
2517
|
var import_yaml2 = require("yaml");
|
|
2022
2518
|
function parseStructuredContract(file, text) {
|
|
2023
|
-
const ext =
|
|
2519
|
+
const ext = import_node_path17.default.extname(file).toLowerCase();
|
|
2024
2520
|
if (ext === ".json") {
|
|
2025
2521
|
return JSON.parse(text);
|
|
2026
2522
|
}
|
|
@@ -2042,14 +2538,14 @@ async function validateContracts(root, config) {
|
|
|
2042
2538
|
const issues = [];
|
|
2043
2539
|
const contractIndex = await buildContractIndex(root, config);
|
|
2044
2540
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2045
|
-
const uiRoot =
|
|
2541
|
+
const uiRoot = import_node_path18.default.join(contractsRoot, "ui");
|
|
2046
2542
|
const themaIds = new Set(
|
|
2047
2543
|
Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
|
|
2048
2544
|
);
|
|
2049
2545
|
issues.push(...await validateUiContracts(uiRoot, themaIds));
|
|
2050
2546
|
issues.push(...await validateThemaContracts(uiRoot));
|
|
2051
|
-
issues.push(...await validateApiContracts(
|
|
2052
|
-
issues.push(...await validateDbContracts(
|
|
2547
|
+
issues.push(...await validateApiContracts(import_node_path18.default.join(contractsRoot, "api")));
|
|
2548
|
+
issues.push(...await validateDbContracts(import_node_path18.default.join(contractsRoot, "db")));
|
|
2053
2549
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
2054
2550
|
return issues;
|
|
2055
2551
|
}
|
|
@@ -2068,7 +2564,7 @@ async function validateUiContracts(uiRoot, themaIds) {
|
|
|
2068
2564
|
}
|
|
2069
2565
|
const issues = [];
|
|
2070
2566
|
for (const file of files) {
|
|
2071
|
-
const text = await (0,
|
|
2567
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2072
2568
|
const declaredIds = extractDeclaredContractIds(text);
|
|
2073
2569
|
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
2074
2570
|
let doc = null;
|
|
@@ -2122,7 +2618,7 @@ async function validateThemaContracts(uiRoot) {
|
|
|
2122
2618
|
}
|
|
2123
2619
|
const issues = [];
|
|
2124
2620
|
for (const file of files) {
|
|
2125
|
-
const text = await (0,
|
|
2621
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2126
2622
|
const invalidIds = extractInvalidIds(text, [
|
|
2127
2623
|
"SPEC",
|
|
2128
2624
|
"BR",
|
|
@@ -2256,7 +2752,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
2256
2752
|
}
|
|
2257
2753
|
const issues = [];
|
|
2258
2754
|
for (const file of files) {
|
|
2259
|
-
const text = await (0,
|
|
2755
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2260
2756
|
const invalidIds = extractInvalidIds(text, [
|
|
2261
2757
|
"SPEC",
|
|
2262
2758
|
"BR",
|
|
@@ -2325,7 +2821,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
2325
2821
|
}
|
|
2326
2822
|
const issues = [];
|
|
2327
2823
|
for (const file of files) {
|
|
2328
|
-
const text = await (0,
|
|
2824
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2329
2825
|
const invalidIds = extractInvalidIds(text, [
|
|
2330
2826
|
"SPEC",
|
|
2331
2827
|
"BR",
|
|
@@ -2522,9 +3018,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2522
3018
|
);
|
|
2523
3019
|
return issues;
|
|
2524
3020
|
}
|
|
2525
|
-
const packDir =
|
|
2526
|
-
const packRelative =
|
|
2527
|
-
if (packRelative.startsWith("..") ||
|
|
3021
|
+
const packDir = import_node_path18.default.resolve(uiRoot, packValue);
|
|
3022
|
+
const packRelative = import_node_path18.default.relative(uiRoot, packDir);
|
|
3023
|
+
if (packRelative.startsWith("..") || import_node_path18.default.isAbsolute(packRelative)) {
|
|
2528
3024
|
issues.push(
|
|
2529
3025
|
issue(
|
|
2530
3026
|
"QFAI-ASSET-001",
|
|
@@ -2550,7 +3046,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2550
3046
|
);
|
|
2551
3047
|
return issues;
|
|
2552
3048
|
}
|
|
2553
|
-
const assetsYamlPath =
|
|
3049
|
+
const assetsYamlPath = import_node_path18.default.join(packDir, "assets.yaml");
|
|
2554
3050
|
if (!await exists6(assetsYamlPath)) {
|
|
2555
3051
|
issues.push(
|
|
2556
3052
|
issue(
|
|
@@ -2565,7 +3061,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2565
3061
|
}
|
|
2566
3062
|
let manifest;
|
|
2567
3063
|
try {
|
|
2568
|
-
const manifestText = await (0,
|
|
3064
|
+
const manifestText = await (0, import_promises13.readFile)(assetsYamlPath, "utf-8");
|
|
2569
3065
|
manifest = parseStructuredContract(assetsYamlPath, manifestText);
|
|
2570
3066
|
} catch (error2) {
|
|
2571
3067
|
issues.push(
|
|
@@ -2638,9 +3134,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2638
3134
|
);
|
|
2639
3135
|
continue;
|
|
2640
3136
|
}
|
|
2641
|
-
const assetPath =
|
|
2642
|
-
const assetRelative =
|
|
2643
|
-
if (assetRelative.startsWith("..") ||
|
|
3137
|
+
const assetPath = import_node_path18.default.resolve(packDir, entry.path);
|
|
3138
|
+
const assetRelative = import_node_path18.default.relative(packDir, assetPath);
|
|
3139
|
+
if (assetRelative.startsWith("..") || import_node_path18.default.isAbsolute(assetRelative)) {
|
|
2644
3140
|
issues.push(
|
|
2645
3141
|
issue(
|
|
2646
3142
|
"QFAI-ASSET-004",
|
|
@@ -2681,7 +3177,7 @@ function shouldIgnoreInvalidId(value, doc) {
|
|
|
2681
3177
|
return false;
|
|
2682
3178
|
}
|
|
2683
3179
|
const normalized = packValue.replace(/\\/g, "/");
|
|
2684
|
-
const basename =
|
|
3180
|
+
const basename = import_node_path18.default.posix.basename(normalized);
|
|
2685
3181
|
if (!basename) {
|
|
2686
3182
|
return false;
|
|
2687
3183
|
}
|
|
@@ -2691,7 +3187,7 @@ function isSafeRelativePath(value) {
|
|
|
2691
3187
|
if (!value) {
|
|
2692
3188
|
return false;
|
|
2693
3189
|
}
|
|
2694
|
-
if (
|
|
3190
|
+
if (import_node_path18.default.isAbsolute(value)) {
|
|
2695
3191
|
return false;
|
|
2696
3192
|
}
|
|
2697
3193
|
const normalized = value.replace(/\\/g, "/");
|
|
@@ -2706,7 +3202,7 @@ function isSafeRelativePath(value) {
|
|
|
2706
3202
|
}
|
|
2707
3203
|
async function exists6(target) {
|
|
2708
3204
|
try {
|
|
2709
|
-
await (0,
|
|
3205
|
+
await (0, import_promises13.access)(target);
|
|
2710
3206
|
return true;
|
|
2711
3207
|
} catch {
|
|
2712
3208
|
return false;
|
|
@@ -2741,8 +3237,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
2741
3237
|
}
|
|
2742
3238
|
|
|
2743
3239
|
// src/core/validators/delta.ts
|
|
2744
|
-
var
|
|
2745
|
-
var
|
|
3240
|
+
var import_promises14 = require("fs/promises");
|
|
3241
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
2746
3242
|
async function validateDeltas(root, config) {
|
|
2747
3243
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2748
3244
|
const packs = await collectSpecPackDirs(specsRoot);
|
|
@@ -2751,9 +3247,9 @@ async function validateDeltas(root, config) {
|
|
|
2751
3247
|
}
|
|
2752
3248
|
const issues = [];
|
|
2753
3249
|
for (const pack of packs) {
|
|
2754
|
-
const deltaPath =
|
|
3250
|
+
const deltaPath = import_node_path19.default.join(pack, "delta.md");
|
|
2755
3251
|
try {
|
|
2756
|
-
await (0,
|
|
3252
|
+
await (0, import_promises14.readFile)(deltaPath, "utf-8");
|
|
2757
3253
|
} catch (error2) {
|
|
2758
3254
|
if (isMissingFileError2(error2)) {
|
|
2759
3255
|
issues.push(
|
|
@@ -2804,8 +3300,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
2804
3300
|
}
|
|
2805
3301
|
|
|
2806
3302
|
// src/core/validators/ids.ts
|
|
2807
|
-
var
|
|
2808
|
-
var
|
|
3303
|
+
var import_promises15 = require("fs/promises");
|
|
3304
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
2809
3305
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2810
3306
|
async function validateDefinedIds(root, config) {
|
|
2811
3307
|
const issues = [];
|
|
@@ -2840,7 +3336,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2840
3336
|
}
|
|
2841
3337
|
async function collectSpecDefinitionIds(files, out) {
|
|
2842
3338
|
for (const file of files) {
|
|
2843
|
-
const text = await (0,
|
|
3339
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2844
3340
|
const parsed = parseSpec(text, file);
|
|
2845
3341
|
if (parsed.specId) {
|
|
2846
3342
|
recordId(out, parsed.specId, file);
|
|
@@ -2850,7 +3346,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2850
3346
|
}
|
|
2851
3347
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2852
3348
|
for (const file of files) {
|
|
2853
|
-
const text = await (0,
|
|
3349
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2854
3350
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2855
3351
|
if (!document || errors.length > 0) {
|
|
2856
3352
|
continue;
|
|
@@ -2871,7 +3367,7 @@ function recordId(out, id, file) {
|
|
|
2871
3367
|
}
|
|
2872
3368
|
function formatFileList(files, root) {
|
|
2873
3369
|
return files.map((file) => {
|
|
2874
|
-
const relative =
|
|
3370
|
+
const relative = import_node_path20.default.relative(root, file);
|
|
2875
3371
|
return relative.length > 0 ? relative : file;
|
|
2876
3372
|
}).join(", ");
|
|
2877
3373
|
}
|
|
@@ -2929,8 +3425,8 @@ async function validatePromptsIntegrity(root, config) {
|
|
|
2929
3425
|
}
|
|
2930
3426
|
|
|
2931
3427
|
// src/core/validators/scenario.ts
|
|
2932
|
-
var
|
|
2933
|
-
var
|
|
3428
|
+
var import_promises16 = require("fs/promises");
|
|
3429
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
2934
3430
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
2935
3431
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
2936
3432
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -2953,7 +3449,7 @@ async function validateScenarios(root, config) {
|
|
|
2953
3449
|
}
|
|
2954
3450
|
const issues = [];
|
|
2955
3451
|
for (const entry of entries) {
|
|
2956
|
-
const legacyScenarioPath =
|
|
3452
|
+
const legacyScenarioPath = import_node_path21.default.join(entry.dir, "scenario.md");
|
|
2957
3453
|
if (await fileExists(legacyScenarioPath)) {
|
|
2958
3454
|
issues.push(
|
|
2959
3455
|
issue4(
|
|
@@ -2967,7 +3463,7 @@ async function validateScenarios(root, config) {
|
|
|
2967
3463
|
}
|
|
2968
3464
|
let text;
|
|
2969
3465
|
try {
|
|
2970
|
-
text = await (0,
|
|
3466
|
+
text = await (0, import_promises16.readFile)(entry.scenarioPath, "utf-8");
|
|
2971
3467
|
} catch (error2) {
|
|
2972
3468
|
if (isMissingFileError3(error2)) {
|
|
2973
3469
|
issues.push(
|
|
@@ -3142,7 +3638,7 @@ function isMissingFileError3(error2) {
|
|
|
3142
3638
|
}
|
|
3143
3639
|
async function fileExists(target) {
|
|
3144
3640
|
try {
|
|
3145
|
-
await (0,
|
|
3641
|
+
await (0, import_promises16.access)(target);
|
|
3146
3642
|
return true;
|
|
3147
3643
|
} catch {
|
|
3148
3644
|
return false;
|
|
@@ -3150,7 +3646,7 @@ async function fileExists(target) {
|
|
|
3150
3646
|
}
|
|
3151
3647
|
|
|
3152
3648
|
// src/core/validators/spec.ts
|
|
3153
|
-
var
|
|
3649
|
+
var import_promises17 = require("fs/promises");
|
|
3154
3650
|
async function validateSpecs(root, config) {
|
|
3155
3651
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
3156
3652
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -3171,7 +3667,7 @@ async function validateSpecs(root, config) {
|
|
|
3171
3667
|
for (const entry of entries) {
|
|
3172
3668
|
let text;
|
|
3173
3669
|
try {
|
|
3174
|
-
text = await (0,
|
|
3670
|
+
text = await (0, import_promises17.readFile)(entry.specPath, "utf-8");
|
|
3175
3671
|
} catch (error2) {
|
|
3176
3672
|
if (isMissingFileError4(error2)) {
|
|
3177
3673
|
issues.push(
|
|
@@ -3325,7 +3821,7 @@ function isMissingFileError4(error2) {
|
|
|
3325
3821
|
}
|
|
3326
3822
|
|
|
3327
3823
|
// src/core/validators/traceability.ts
|
|
3328
|
-
var
|
|
3824
|
+
var import_promises18 = require("fs/promises");
|
|
3329
3825
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
3330
3826
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
3331
3827
|
async function validateTraceability(root, config) {
|
|
@@ -3345,7 +3841,7 @@ async function validateTraceability(root, config) {
|
|
|
3345
3841
|
const contractIndex = await buildContractIndex(root, config);
|
|
3346
3842
|
const contractIds = contractIndex.ids;
|
|
3347
3843
|
for (const file of specFiles) {
|
|
3348
|
-
const text = await (0,
|
|
3844
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3349
3845
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3350
3846
|
const parsed = parseSpec(text, file);
|
|
3351
3847
|
if (parsed.specId) {
|
|
@@ -3418,7 +3914,7 @@ async function validateTraceability(root, config) {
|
|
|
3418
3914
|
}
|
|
3419
3915
|
}
|
|
3420
3916
|
for (const file of scenarioFiles) {
|
|
3421
|
-
const text = await (0,
|
|
3917
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3422
3918
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3423
3919
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
3424
3920
|
allowCommentPrefix: true
|
|
@@ -3740,7 +4236,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
3740
4236
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
3741
4237
|
let found = false;
|
|
3742
4238
|
for (const file of targetFiles) {
|
|
3743
|
-
const text = await (0,
|
|
4239
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3744
4240
|
if (pattern.test(text)) {
|
|
3745
4241
|
found = true;
|
|
3746
4242
|
break;
|
|
@@ -3839,16 +4335,17 @@ var ID_PREFIXES2 = [
|
|
|
3839
4335
|
"DB",
|
|
3840
4336
|
"THEMA"
|
|
3841
4337
|
];
|
|
4338
|
+
var REPORT_GUARDRAILS_MAX = 20;
|
|
3842
4339
|
async function createReportData(root, validation, configResult) {
|
|
3843
|
-
const resolvedRoot =
|
|
4340
|
+
const resolvedRoot = import_node_path22.default.resolve(root);
|
|
3844
4341
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3845
4342
|
const config = resolved.config;
|
|
3846
4343
|
const configPath = resolved.configPath;
|
|
3847
4344
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3848
4345
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3849
|
-
const apiRoot =
|
|
3850
|
-
const uiRoot =
|
|
3851
|
-
const dbRoot =
|
|
4346
|
+
const apiRoot = import_node_path22.default.join(contractsRoot, "api");
|
|
4347
|
+
const uiRoot = import_node_path22.default.join(contractsRoot, "ui");
|
|
4348
|
+
const dbRoot = import_node_path22.default.join(contractsRoot, "db");
|
|
3852
4349
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3853
4350
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3854
4351
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -3907,6 +4404,27 @@ async function createReportData(root, validation, configResult) {
|
|
|
3907
4404
|
const scSourceRecord = mapToSortedRecord(
|
|
3908
4405
|
normalizeScSources(resolvedRoot, scSources)
|
|
3909
4406
|
);
|
|
4407
|
+
const guardrailsLoad = await loadDecisionGuardrails(resolvedRoot, {
|
|
4408
|
+
specsRoot
|
|
4409
|
+
});
|
|
4410
|
+
const guardrailsAll = sortDecisionGuardrails(
|
|
4411
|
+
normalizeDecisionGuardrails(guardrailsLoad.entries)
|
|
4412
|
+
);
|
|
4413
|
+
const guardrailsDisplay = guardrailsAll.slice(0, REPORT_GUARDRAILS_MAX);
|
|
4414
|
+
const guardrailsByType = { nonGoal: 0, notNow: 0, tradeOff: 0 };
|
|
4415
|
+
for (const item of guardrailsAll) {
|
|
4416
|
+
if (item.type === "non-goal") {
|
|
4417
|
+
guardrailsByType.nonGoal += 1;
|
|
4418
|
+
} else if (item.type === "not-now") {
|
|
4419
|
+
guardrailsByType.notNow += 1;
|
|
4420
|
+
} else if (item.type === "trade-off") {
|
|
4421
|
+
guardrailsByType.tradeOff += 1;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
const guardrailsErrors = guardrailsLoad.errors.map((item) => ({
|
|
4425
|
+
path: toRelativePath(resolvedRoot, item.path),
|
|
4426
|
+
message: item.message
|
|
4427
|
+
}));
|
|
3910
4428
|
const version = await resolveToolVersion();
|
|
3911
4429
|
const displayRoot = toRelativePath(resolvedRoot, resolvedRoot);
|
|
3912
4430
|
const displayConfigPath = toRelativePath(resolvedRoot, configPath);
|
|
@@ -3954,6 +4472,34 @@ async function createReportData(root, validation, configResult) {
|
|
|
3954
4472
|
specToContracts: specToContractsRecord
|
|
3955
4473
|
}
|
|
3956
4474
|
},
|
|
4475
|
+
guardrails: {
|
|
4476
|
+
total: guardrailsAll.length,
|
|
4477
|
+
max: REPORT_GUARDRAILS_MAX,
|
|
4478
|
+
truncated: guardrailsAll.length > guardrailsDisplay.length,
|
|
4479
|
+
byType: guardrailsByType,
|
|
4480
|
+
items: guardrailsDisplay.map((item) => {
|
|
4481
|
+
const entry = {
|
|
4482
|
+
id: item.id,
|
|
4483
|
+
type: item.type,
|
|
4484
|
+
guardrail: item.guardrail,
|
|
4485
|
+
source: {
|
|
4486
|
+
file: toRelativePath(resolvedRoot, item.source.file),
|
|
4487
|
+
line: item.source.line
|
|
4488
|
+
}
|
|
4489
|
+
};
|
|
4490
|
+
if (item.rationale) {
|
|
4491
|
+
entry.rationale = item.rationale;
|
|
4492
|
+
}
|
|
4493
|
+
if (item.reconsider) {
|
|
4494
|
+
entry.reconsider = item.reconsider;
|
|
4495
|
+
}
|
|
4496
|
+
if (item.related) {
|
|
4497
|
+
entry.related = item.related;
|
|
4498
|
+
}
|
|
4499
|
+
return entry;
|
|
4500
|
+
}),
|
|
4501
|
+
scanErrors: guardrailsErrors
|
|
4502
|
+
},
|
|
3957
4503
|
issues: normalizedValidation.issues
|
|
3958
4504
|
};
|
|
3959
4505
|
}
|
|
@@ -4049,6 +4595,7 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
4049
4595
|
lines.push("");
|
|
4050
4596
|
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
4051
4597
|
lines.push("- [Change Issues](#change-issues)");
|
|
4598
|
+
lines.push("- [Decision Guardrails](#decision-guardrails)");
|
|
4052
4599
|
lines.push("- [IDs](#ids)");
|
|
4053
4600
|
lines.push("- [Traceability](#traceability)");
|
|
4054
4601
|
lines.push("");
|
|
@@ -4140,6 +4687,49 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
4140
4687
|
lines.push("### Issues");
|
|
4141
4688
|
lines.push("");
|
|
4142
4689
|
lines.push(...formatIssueCards(issuesByCategory.change));
|
|
4690
|
+
lines.push("## Decision Guardrails");
|
|
4691
|
+
lines.push("");
|
|
4692
|
+
lines.push(`- total: ${data.guardrails.total}`);
|
|
4693
|
+
lines.push(
|
|
4694
|
+
`- types: non-goal ${data.guardrails.byType.nonGoal} / not-now ${data.guardrails.byType.notNow} / trade-off ${data.guardrails.byType.tradeOff}`
|
|
4695
|
+
);
|
|
4696
|
+
if (data.guardrails.truncated) {
|
|
4697
|
+
lines.push(`- truncated: true (max=${data.guardrails.max})`);
|
|
4698
|
+
}
|
|
4699
|
+
if (data.guardrails.scanErrors.length > 0) {
|
|
4700
|
+
lines.push(`- scanErrors: ${data.guardrails.scanErrors.length}`);
|
|
4701
|
+
}
|
|
4702
|
+
lines.push("");
|
|
4703
|
+
if (data.guardrails.items.length === 0) {
|
|
4704
|
+
lines.push("- (none)");
|
|
4705
|
+
} else {
|
|
4706
|
+
for (const item of data.guardrails.items) {
|
|
4707
|
+
lines.push(`- [${item.id}][${item.type}] ${item.guardrail}`);
|
|
4708
|
+
lines.push(
|
|
4709
|
+
` - source: ${formatPathWithLine(item.source.file, { line: item.source.line }, baseUrl)}`
|
|
4710
|
+
);
|
|
4711
|
+
if (item.rationale) {
|
|
4712
|
+
lines.push(` - Rationale: ${item.rationale}`);
|
|
4713
|
+
}
|
|
4714
|
+
if (item.reconsider) {
|
|
4715
|
+
lines.push(` - Reconsider: ${item.reconsider}`);
|
|
4716
|
+
}
|
|
4717
|
+
if (item.related) {
|
|
4718
|
+
lines.push(` - Related: ${item.related}`);
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
if (data.guardrails.scanErrors.length > 0) {
|
|
4723
|
+
lines.push("");
|
|
4724
|
+
lines.push("### Scan errors");
|
|
4725
|
+
lines.push("");
|
|
4726
|
+
for (const errorItem of data.guardrails.scanErrors) {
|
|
4727
|
+
lines.push(
|
|
4728
|
+
`- ${formatPathLink(errorItem.path, baseUrl)}: ${errorItem.message}`
|
|
4729
|
+
);
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
lines.push("");
|
|
4143
4733
|
lines.push("## IDs");
|
|
4144
4734
|
lines.push("");
|
|
4145
4735
|
lines.push(formatIdLine("SPEC", data.ids.spec));
|
|
@@ -4330,7 +4920,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
4330
4920
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
4331
4921
|
}
|
|
4332
4922
|
for (const file of specFiles) {
|
|
4333
|
-
const text = await (0,
|
|
4923
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4334
4924
|
const parsed = parseSpec(text, file);
|
|
4335
4925
|
const specKey = parsed.specId;
|
|
4336
4926
|
if (!specKey) {
|
|
@@ -4372,7 +4962,7 @@ async function collectIds(files) {
|
|
|
4372
4962
|
THEMA: /* @__PURE__ */ new Set()
|
|
4373
4963
|
};
|
|
4374
4964
|
for (const file of files) {
|
|
4375
|
-
const text = await (0,
|
|
4965
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4376
4966
|
for (const prefix of ID_PREFIXES2) {
|
|
4377
4967
|
const ids = extractIds(text, prefix);
|
|
4378
4968
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -4391,7 +4981,7 @@ async function collectIds(files) {
|
|
|
4391
4981
|
async function collectUpstreamIds(files) {
|
|
4392
4982
|
const ids = /* @__PURE__ */ new Set();
|
|
4393
4983
|
for (const file of files) {
|
|
4394
|
-
const text = await (0,
|
|
4984
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4395
4985
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
4396
4986
|
}
|
|
4397
4987
|
return ids;
|
|
@@ -4412,7 +5002,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
4412
5002
|
}
|
|
4413
5003
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
4414
5004
|
for (const file of targetFiles) {
|
|
4415
|
-
const text = await (0,
|
|
5005
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4416
5006
|
if (pattern.test(text)) {
|
|
4417
5007
|
return true;
|
|
4418
5008
|
}
|
|
@@ -4549,7 +5139,7 @@ function warnIfTruncated(scan, context) {
|
|
|
4549
5139
|
|
|
4550
5140
|
// src/cli/commands/report.ts
|
|
4551
5141
|
async function runReport(options) {
|
|
4552
|
-
const root =
|
|
5142
|
+
const root = import_node_path23.default.resolve(options.root);
|
|
4553
5143
|
const configResult = await loadConfig(root);
|
|
4554
5144
|
let validation;
|
|
4555
5145
|
if (options.runValidate) {
|
|
@@ -4566,7 +5156,7 @@ async function runReport(options) {
|
|
|
4566
5156
|
validation = normalized;
|
|
4567
5157
|
} else {
|
|
4568
5158
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
4569
|
-
const inputPath =
|
|
5159
|
+
const inputPath = import_node_path23.default.isAbsolute(input) ? input : import_node_path23.default.resolve(root, input);
|
|
4570
5160
|
try {
|
|
4571
5161
|
validation = await readValidationResult(inputPath);
|
|
4572
5162
|
} catch (err) {
|
|
@@ -4593,11 +5183,11 @@ async function runReport(options) {
|
|
|
4593
5183
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
4594
5184
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
4595
5185
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
4596
|
-
const defaultOut = options.format === "json" ?
|
|
5186
|
+
const defaultOut = options.format === "json" ? import_node_path23.default.join(outRoot, "report.json") : import_node_path23.default.join(outRoot, "report.md");
|
|
4597
5187
|
const out = options.outPath ?? defaultOut;
|
|
4598
|
-
const outPath =
|
|
4599
|
-
await (0,
|
|
4600
|
-
await (0,
|
|
5188
|
+
const outPath = import_node_path23.default.isAbsolute(out) ? out : import_node_path23.default.resolve(root, out);
|
|
5189
|
+
await (0, import_promises20.mkdir)(import_node_path23.default.dirname(outPath), { recursive: true });
|
|
5190
|
+
await (0, import_promises20.writeFile)(outPath, `${output}
|
|
4601
5191
|
`, "utf-8");
|
|
4602
5192
|
info(
|
|
4603
5193
|
`report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
|
|
@@ -4605,7 +5195,7 @@ async function runReport(options) {
|
|
|
4605
5195
|
info(`wrote report: ${outPath}`);
|
|
4606
5196
|
}
|
|
4607
5197
|
async function readValidationResult(inputPath) {
|
|
4608
|
-
const raw = await (0,
|
|
5198
|
+
const raw = await (0, import_promises20.readFile)(inputPath, "utf-8");
|
|
4609
5199
|
const parsed = JSON.parse(raw);
|
|
4610
5200
|
if (!isValidationResult(parsed)) {
|
|
4611
5201
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -4661,15 +5251,15 @@ function isMissingFileError5(error2) {
|
|
|
4661
5251
|
return record2.code === "ENOENT";
|
|
4662
5252
|
}
|
|
4663
5253
|
async function writeValidationResult(root, outputPath, result) {
|
|
4664
|
-
const abs =
|
|
4665
|
-
await (0,
|
|
4666
|
-
await (0,
|
|
5254
|
+
const abs = import_node_path23.default.isAbsolute(outputPath) ? outputPath : import_node_path23.default.resolve(root, outputPath);
|
|
5255
|
+
await (0, import_promises20.mkdir)(import_node_path23.default.dirname(abs), { recursive: true });
|
|
5256
|
+
await (0, import_promises20.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
4667
5257
|
`, "utf-8");
|
|
4668
5258
|
}
|
|
4669
5259
|
|
|
4670
5260
|
// src/cli/commands/validate.ts
|
|
4671
|
-
var
|
|
4672
|
-
var
|
|
5261
|
+
var import_promises21 = require("fs/promises");
|
|
5262
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4673
5263
|
|
|
4674
5264
|
// src/cli/lib/failOn.ts
|
|
4675
5265
|
function shouldFail(result, failOn) {
|
|
@@ -4684,7 +5274,7 @@ function shouldFail(result, failOn) {
|
|
|
4684
5274
|
|
|
4685
5275
|
// src/cli/commands/validate.ts
|
|
4686
5276
|
async function runValidate(options) {
|
|
4687
|
-
const root =
|
|
5277
|
+
const root = import_node_path24.default.resolve(options.root);
|
|
4688
5278
|
const configResult = await loadConfig(root);
|
|
4689
5279
|
const result = await validateProject(root, configResult);
|
|
4690
5280
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4809,12 +5399,12 @@ function issueKey(issue7) {
|
|
|
4809
5399
|
}
|
|
4810
5400
|
async function emitJson(result, root, jsonPath) {
|
|
4811
5401
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4812
|
-
await (0,
|
|
4813
|
-
await (0,
|
|
5402
|
+
await (0, import_promises21.mkdir)(import_node_path24.default.dirname(abs), { recursive: true });
|
|
5403
|
+
await (0, import_promises21.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
4814
5404
|
`, "utf-8");
|
|
4815
5405
|
}
|
|
4816
5406
|
function resolveJsonPath(root, jsonPath) {
|
|
4817
|
-
return
|
|
5407
|
+
return import_node_path24.default.isAbsolute(jsonPath) ? jsonPath : import_node_path24.default.resolve(root, jsonPath);
|
|
4818
5408
|
}
|
|
4819
5409
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4820
5410
|
|
|
@@ -4832,7 +5422,9 @@ function parseArgs(argv, cwd) {
|
|
|
4832
5422
|
doctorFormat: "text",
|
|
4833
5423
|
validateFormat: "text",
|
|
4834
5424
|
strict: false,
|
|
4835
|
-
|
|
5425
|
+
guardrailsPaths: [],
|
|
5426
|
+
help: false,
|
|
5427
|
+
invalidExitCode: 1
|
|
4836
5428
|
};
|
|
4837
5429
|
const args = [...argv];
|
|
4838
5430
|
let command = args.shift() ?? null;
|
|
@@ -4841,6 +5433,25 @@ function parseArgs(argv, cwd) {
|
|
|
4841
5433
|
options.help = true;
|
|
4842
5434
|
command = null;
|
|
4843
5435
|
}
|
|
5436
|
+
const markInvalid = () => {
|
|
5437
|
+
invalid = true;
|
|
5438
|
+
options.help = true;
|
|
5439
|
+
if (command === "guardrails") {
|
|
5440
|
+
options.invalidExitCode = 2;
|
|
5441
|
+
}
|
|
5442
|
+
};
|
|
5443
|
+
if (command === "guardrails") {
|
|
5444
|
+
const candidate = args[0];
|
|
5445
|
+
if (candidate && !candidate.startsWith("--")) {
|
|
5446
|
+
const action = normalizeGuardrailsAction(candidate);
|
|
5447
|
+
if (action) {
|
|
5448
|
+
options.guardrailsAction = action;
|
|
5449
|
+
} else {
|
|
5450
|
+
markInvalid();
|
|
5451
|
+
}
|
|
5452
|
+
args.shift();
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
4844
5455
|
for (let i = 0; i < args.length; i += 1) {
|
|
4845
5456
|
const arg = args[i];
|
|
4846
5457
|
switch (arg) {
|
|
@@ -4848,8 +5459,7 @@ function parseArgs(argv, cwd) {
|
|
|
4848
5459
|
{
|
|
4849
5460
|
const next = readOptionValue(args, i);
|
|
4850
5461
|
if (next === null) {
|
|
4851
|
-
|
|
4852
|
-
options.help = true;
|
|
5462
|
+
markInvalid();
|
|
4853
5463
|
break;
|
|
4854
5464
|
}
|
|
4855
5465
|
options.root = next;
|
|
@@ -4861,8 +5471,7 @@ function parseArgs(argv, cwd) {
|
|
|
4861
5471
|
{
|
|
4862
5472
|
const next = readOptionValue(args, i);
|
|
4863
5473
|
if (next === null) {
|
|
4864
|
-
|
|
4865
|
-
options.help = true;
|
|
5474
|
+
markInvalid();
|
|
4866
5475
|
break;
|
|
4867
5476
|
}
|
|
4868
5477
|
options.dir = next;
|
|
@@ -4881,8 +5490,7 @@ function parseArgs(argv, cwd) {
|
|
|
4881
5490
|
case "--format": {
|
|
4882
5491
|
const next = readOptionValue(args, i);
|
|
4883
5492
|
if (next === null) {
|
|
4884
|
-
|
|
4885
|
-
options.help = true;
|
|
5493
|
+
markInvalid();
|
|
4886
5494
|
break;
|
|
4887
5495
|
}
|
|
4888
5496
|
applyFormatOption(command, next, options);
|
|
@@ -4895,8 +5503,7 @@ function parseArgs(argv, cwd) {
|
|
|
4895
5503
|
case "--fail-on": {
|
|
4896
5504
|
const next = readOptionValue(args, i);
|
|
4897
5505
|
if (next === null) {
|
|
4898
|
-
|
|
4899
|
-
options.help = true;
|
|
5506
|
+
markInvalid();
|
|
4900
5507
|
break;
|
|
4901
5508
|
}
|
|
4902
5509
|
if (next === "never" || next === "warning" || next === "error") {
|
|
@@ -4908,8 +5515,7 @@ function parseArgs(argv, cwd) {
|
|
|
4908
5515
|
case "--out": {
|
|
4909
5516
|
const next = readOptionValue(args, i);
|
|
4910
5517
|
if (next === null) {
|
|
4911
|
-
|
|
4912
|
-
options.help = true;
|
|
5518
|
+
markInvalid();
|
|
4913
5519
|
break;
|
|
4914
5520
|
}
|
|
4915
5521
|
if (command === "doctor") {
|
|
@@ -4923,8 +5529,7 @@ function parseArgs(argv, cwd) {
|
|
|
4923
5529
|
case "--in": {
|
|
4924
5530
|
const next = readOptionValue(args, i);
|
|
4925
5531
|
if (next === null) {
|
|
4926
|
-
|
|
4927
|
-
options.help = true;
|
|
5532
|
+
markInvalid();
|
|
4928
5533
|
break;
|
|
4929
5534
|
}
|
|
4930
5535
|
options.reportIn = next;
|
|
@@ -4937,14 +5542,57 @@ function parseArgs(argv, cwd) {
|
|
|
4937
5542
|
case "--base-url": {
|
|
4938
5543
|
const next = readOptionValue(args, i);
|
|
4939
5544
|
if (next === null) {
|
|
4940
|
-
|
|
4941
|
-
options.help = true;
|
|
5545
|
+
markInvalid();
|
|
4942
5546
|
break;
|
|
4943
5547
|
}
|
|
4944
5548
|
options.reportBaseUrl = next;
|
|
4945
5549
|
i += 1;
|
|
4946
5550
|
break;
|
|
4947
5551
|
}
|
|
5552
|
+
case "--path": {
|
|
5553
|
+
if (command !== "guardrails") {
|
|
5554
|
+
break;
|
|
5555
|
+
}
|
|
5556
|
+
const next = readOptionValue(args, i);
|
|
5557
|
+
if (next === null) {
|
|
5558
|
+
markInvalid();
|
|
5559
|
+
break;
|
|
5560
|
+
}
|
|
5561
|
+
options.guardrailsPaths.push(next);
|
|
5562
|
+
i += 1;
|
|
5563
|
+
break;
|
|
5564
|
+
}
|
|
5565
|
+
case "--max": {
|
|
5566
|
+
if (command !== "guardrails") {
|
|
5567
|
+
break;
|
|
5568
|
+
}
|
|
5569
|
+
const next = readOptionValue(args, i);
|
|
5570
|
+
if (next === null) {
|
|
5571
|
+
markInvalid();
|
|
5572
|
+
break;
|
|
5573
|
+
}
|
|
5574
|
+
const parsed = Number.parseInt(next, 10);
|
|
5575
|
+
if (Number.isNaN(parsed)) {
|
|
5576
|
+
markInvalid();
|
|
5577
|
+
break;
|
|
5578
|
+
}
|
|
5579
|
+
options.guardrailsMax = parsed;
|
|
5580
|
+
i += 1;
|
|
5581
|
+
break;
|
|
5582
|
+
}
|
|
5583
|
+
case "--keyword": {
|
|
5584
|
+
if (command !== "guardrails") {
|
|
5585
|
+
break;
|
|
5586
|
+
}
|
|
5587
|
+
const next = readOptionValue(args, i);
|
|
5588
|
+
if (next === null) {
|
|
5589
|
+
markInvalid();
|
|
5590
|
+
break;
|
|
5591
|
+
}
|
|
5592
|
+
options.guardrailsKeyword = next;
|
|
5593
|
+
i += 1;
|
|
5594
|
+
break;
|
|
5595
|
+
}
|
|
4948
5596
|
case "--help":
|
|
4949
5597
|
case "-h":
|
|
4950
5598
|
options.help = true;
|
|
@@ -4953,6 +5601,9 @@ function parseArgs(argv, cwd) {
|
|
|
4953
5601
|
break;
|
|
4954
5602
|
}
|
|
4955
5603
|
}
|
|
5604
|
+
if (command === "guardrails" && !options.help && !options.guardrailsAction) {
|
|
5605
|
+
markInvalid();
|
|
5606
|
+
}
|
|
4956
5607
|
return { command, invalid, options };
|
|
4957
5608
|
}
|
|
4958
5609
|
function readOptionValue(args, index) {
|
|
@@ -4991,6 +5642,16 @@ function applyFormatOption(command, value, options) {
|
|
|
4991
5642
|
options.validateFormat = value;
|
|
4992
5643
|
}
|
|
4993
5644
|
}
|
|
5645
|
+
function normalizeGuardrailsAction(value) {
|
|
5646
|
+
switch (value) {
|
|
5647
|
+
case "list":
|
|
5648
|
+
case "extract":
|
|
5649
|
+
case "check":
|
|
5650
|
+
return value;
|
|
5651
|
+
default:
|
|
5652
|
+
return null;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
4994
5655
|
|
|
4995
5656
|
// src/cli/main.ts
|
|
4996
5657
|
async function run(argv, cwd) {
|
|
@@ -4998,7 +5659,7 @@ async function run(argv, cwd) {
|
|
|
4998
5659
|
if (!command || options.help) {
|
|
4999
5660
|
info(usage());
|
|
5000
5661
|
if (invalid) {
|
|
5001
|
-
process.exitCode =
|
|
5662
|
+
process.exitCode = options.invalidExitCode;
|
|
5002
5663
|
}
|
|
5003
5664
|
return;
|
|
5004
5665
|
}
|
|
@@ -5047,6 +5708,19 @@ async function run(argv, cwd) {
|
|
|
5047
5708
|
process.exitCode = exitCode;
|
|
5048
5709
|
}
|
|
5049
5710
|
return;
|
|
5711
|
+
case "guardrails":
|
|
5712
|
+
{
|
|
5713
|
+
const resolvedRoot = await resolveRoot(options);
|
|
5714
|
+
const exitCode = await runGuardrails({
|
|
5715
|
+
root: resolvedRoot,
|
|
5716
|
+
...options.guardrailsAction ? { action: options.guardrailsAction } : {},
|
|
5717
|
+
paths: options.guardrailsPaths,
|
|
5718
|
+
...options.guardrailsMax !== void 0 ? { max: options.guardrailsMax } : {},
|
|
5719
|
+
...options.guardrailsKeyword !== void 0 ? { keyword: options.guardrailsKeyword } : {}
|
|
5720
|
+
});
|
|
5721
|
+
process.exitCode = exitCode;
|
|
5722
|
+
}
|
|
5723
|
+
return;
|
|
5050
5724
|
default:
|
|
5051
5725
|
error(`Unknown command: ${command}`);
|
|
5052
5726
|
info(usage());
|
|
@@ -5061,6 +5735,7 @@ Commands:
|
|
|
5061
5735
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
5062
5736
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
5063
5737
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
5738
|
+
guardrails Decision Guardrails \u306E\u62BD\u51FA/\u691C\u67FB\uFF08list|extract|check\uFF09
|
|
5064
5739
|
|
|
5065
5740
|
Options:
|
|
5066
5741
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
@@ -5078,6 +5753,9 @@ Options:
|
|
|
5078
5753
|
--in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
|
|
5079
5754
|
--run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
|
|
5080
5755
|
--base-url <url> report: \u30D1\u30B9\u3092\u30EA\u30F3\u30AF\u5316\u3059\u308B\u57FA\u6E96URL
|
|
5756
|
+
--path <path> guardrails: \u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB/\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF08\u8907\u6570\u6307\u5B9A\u53EF\uFF09
|
|
5757
|
+
--max <number> guardrails extract: \u6700\u5927\u4EF6\u6570
|
|
5758
|
+
--keyword <text> guardrails list/extract: \u30AD\u30FC\u30EF\u30FC\u30C9\u30D5\u30A3\u30EB\u30BF
|
|
5081
5759
|
-h, --help \u30D8\u30EB\u30D7\u8868\u793A
|
|
5082
5760
|
`;
|
|
5083
5761
|
}
|