qfai 1.0.6 → 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 +327 -245
- 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 +197 -0
- 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/.claude/commands/qfai-configure.md +14 -0
- package/assets/init/root/.claude/commands/qfai-discuss.md +14 -0
- package/assets/init/root/.claude/commands/qfai-implement.md +14 -0
- package/assets/init/root/.claude/commands/qfai-require.md +14 -0
- package/assets/init/root/.claude/commands/qfai-scenario-test.md +14 -0
- package/assets/init/root/.claude/commands/qfai-spec.md +14 -0
- package/assets/init/root/.claude/commands/qfai-unit-test.md +14 -0
- package/assets/init/root/.claude/commands/qfai-verify.md +14 -0
- package/assets/init/root/.codex/README.md +16 -0
- package/assets/init/root/.codex/skills/qfai-configure/SKILL.md +18 -0
- package/assets/init/root/.codex/skills/qfai-discuss/SKILL.md +18 -0
- package/assets/init/root/.codex/skills/qfai-implement/SKILL.md +18 -0
- package/assets/init/root/.codex/skills/qfai-require/SKILL.md +18 -0
- package/assets/init/root/.codex/skills/qfai-scenario-test/SKILL.md +18 -0
- package/assets/init/root/.codex/skills/qfai-spec/SKILL.md +18 -0
- package/assets/init/root/.codex/skills/qfai-unit-test/SKILL.md +18 -0
- package/assets/init/root/.codex/skills/qfai-verify/SKILL.md +18 -0
- package/assets/init/root/.github/copilot-instructions.md +14 -0
- package/assets/init/root/.github/prompts/qfai-configure.prompt.md +17 -0
- package/assets/init/root/.github/prompts/qfai-discuss.prompt.md +17 -0
- package/assets/init/root/.github/prompts/qfai-implement.prompt.md +17 -0
- package/assets/init/root/.github/prompts/qfai-require.prompt.md +17 -0
- package/assets/init/root/.github/prompts/qfai-scenario-test.prompt.md +17 -0
- package/assets/init/root/.github/prompts/qfai-spec.prompt.md +17 -0
- package/assets/init/root/.github/prompts/qfai-unit-test.prompt.md +17 -0
- package/assets/init/root/.github/prompts/qfai-verify.prompt.md +17 -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 +880 -196
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +866 -182
- 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"
|
|
@@ -1658,22 +2198,28 @@ async function runInit(options) {
|
|
|
1658
2198
|
[...rootResult.copied, ...qfaiResult.copied, ...promptsResult.copied],
|
|
1659
2199
|
[...rootResult.skipped, ...qfaiResult.skipped, ...promptsResult.skipped],
|
|
1660
2200
|
options.dryRun,
|
|
1661
|
-
"init"
|
|
2201
|
+
"init",
|
|
2202
|
+
destRoot
|
|
1662
2203
|
);
|
|
1663
2204
|
}
|
|
1664
|
-
function report(copied, skipped, dryRun, label) {
|
|
2205
|
+
function report(copied, skipped, dryRun, label, baseDir) {
|
|
1665
2206
|
info(`qfai ${label}: ${dryRun ? "dry-run" : "done"}`);
|
|
1666
2207
|
if (copied.length > 0) {
|
|
1667
2208
|
info(` created: ${copied.length}`);
|
|
1668
2209
|
}
|
|
1669
2210
|
if (skipped.length > 0) {
|
|
1670
2211
|
info(` skipped: ${skipped.length}`);
|
|
2212
|
+
info(" skipped paths:");
|
|
2213
|
+
for (const skippedPath of skipped) {
|
|
2214
|
+
const relative = import_node_path15.default.relative(baseDir, skippedPath);
|
|
2215
|
+
info(` - ${relative}`);
|
|
2216
|
+
}
|
|
1671
2217
|
}
|
|
1672
2218
|
}
|
|
1673
2219
|
|
|
1674
2220
|
// src/cli/commands/report.ts
|
|
1675
|
-
var
|
|
1676
|
-
var
|
|
2221
|
+
var import_promises20 = require("fs/promises");
|
|
2222
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
1677
2223
|
|
|
1678
2224
|
// src/core/normalize.ts
|
|
1679
2225
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1713,12 +2259,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1713
2259
|
}
|
|
1714
2260
|
|
|
1715
2261
|
// src/core/report.ts
|
|
1716
|
-
var
|
|
1717
|
-
var
|
|
2262
|
+
var import_promises19 = require("fs/promises");
|
|
2263
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
1718
2264
|
|
|
1719
2265
|
// src/core/contractIndex.ts
|
|
1720
|
-
var
|
|
1721
|
-
var
|
|
2266
|
+
var import_promises12 = require("fs/promises");
|
|
2267
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
1722
2268
|
|
|
1723
2269
|
// src/core/contractsDecl.ts
|
|
1724
2270
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1740,9 +2286,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1740
2286
|
// src/core/contractIndex.ts
|
|
1741
2287
|
async function buildContractIndex(root, config) {
|
|
1742
2288
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1743
|
-
const uiRoot =
|
|
1744
|
-
const apiRoot =
|
|
1745
|
-
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");
|
|
1746
2292
|
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1747
2293
|
collectUiContractFiles(uiRoot),
|
|
1748
2294
|
collectThemaContractFiles(uiRoot),
|
|
@@ -1762,7 +2308,7 @@ async function buildContractIndex(root, config) {
|
|
|
1762
2308
|
}
|
|
1763
2309
|
async function indexContractFiles(files, index) {
|
|
1764
2310
|
for (const file of files) {
|
|
1765
|
-
const text = await (0,
|
|
2311
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
1766
2312
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1767
2313
|
}
|
|
1768
2314
|
}
|
|
@@ -1887,53 +2433,11 @@ function unique3(values) {
|
|
|
1887
2433
|
return Array.from(new Set(values));
|
|
1888
2434
|
}
|
|
1889
2435
|
|
|
1890
|
-
// src/core/parse/markdown.ts
|
|
1891
|
-
var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
1892
|
-
function parseHeadings(md) {
|
|
1893
|
-
const lines = md.split(/\r?\n/);
|
|
1894
|
-
const headings = [];
|
|
1895
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1896
|
-
const line = lines[i] ?? "";
|
|
1897
|
-
const match = line.match(HEADING_RE);
|
|
1898
|
-
if (!match) continue;
|
|
1899
|
-
const levelToken = match[1];
|
|
1900
|
-
const title = match[2];
|
|
1901
|
-
if (!levelToken || !title) continue;
|
|
1902
|
-
headings.push({
|
|
1903
|
-
level: levelToken.length,
|
|
1904
|
-
title: title.trim(),
|
|
1905
|
-
line: i + 1
|
|
1906
|
-
});
|
|
1907
|
-
}
|
|
1908
|
-
return headings;
|
|
1909
|
-
}
|
|
1910
|
-
function extractH2Sections(md) {
|
|
1911
|
-
const lines = md.split(/\r?\n/);
|
|
1912
|
-
const headings = parseHeadings(md).filter((heading) => heading.level === 2);
|
|
1913
|
-
const sections = /* @__PURE__ */ new Map();
|
|
1914
|
-
for (let i = 0; i < headings.length; i++) {
|
|
1915
|
-
const current = headings[i];
|
|
1916
|
-
if (!current) continue;
|
|
1917
|
-
const next = headings[i + 1];
|
|
1918
|
-
const startLine = current.line + 1;
|
|
1919
|
-
const endLine = (next?.line ?? lines.length + 1) - 1;
|
|
1920
|
-
const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
|
|
1921
|
-
sections.set(current.title.trim(), {
|
|
1922
|
-
title: current.title.trim(),
|
|
1923
|
-
startLine,
|
|
1924
|
-
endLine,
|
|
1925
|
-
body
|
|
1926
|
-
});
|
|
1927
|
-
}
|
|
1928
|
-
return sections;
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
2436
|
// src/core/parse/spec.ts
|
|
1932
2437
|
var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
|
|
1933
2438
|
var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
|
|
1934
2439
|
var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
|
|
1935
2440
|
var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
|
|
1936
|
-
var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
|
|
1937
2441
|
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
1938
2442
|
function parseSpec(md, file) {
|
|
1939
2443
|
const headings = parseHeadings(md);
|
|
@@ -1941,15 +2445,13 @@ function parseSpec(md, file) {
|
|
|
1941
2445
|
const specId = h1?.title.match(SPEC_ID_RE)?.[0];
|
|
1942
2446
|
const sections = extractH2Sections(md);
|
|
1943
2447
|
const sectionNames = new Set(Array.from(sections.keys()));
|
|
1944
|
-
const
|
|
1945
|
-
const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
|
|
1946
|
-
const startLine = brSection?.startLine ?? 1;
|
|
2448
|
+
const lines = md.split(/\r?\n/);
|
|
1947
2449
|
const brs = [];
|
|
1948
2450
|
const brsWithoutPriority = [];
|
|
1949
2451
|
const brsWithInvalidPriority = [];
|
|
1950
|
-
for (let i = 0; i <
|
|
1951
|
-
const lineText =
|
|
1952
|
-
const lineNumber =
|
|
2452
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2453
|
+
const lineText = lines[i] ?? "";
|
|
2454
|
+
const lineNumber = i + 1;
|
|
1953
2455
|
const validMatch = lineText.match(BR_LINE_RE);
|
|
1954
2456
|
if (validMatch) {
|
|
1955
2457
|
const id = validMatch[1];
|
|
@@ -2007,14 +2509,14 @@ function parseSpec(md, file) {
|
|
|
2007
2509
|
}
|
|
2008
2510
|
|
|
2009
2511
|
// src/core/validators/contracts.ts
|
|
2010
|
-
var
|
|
2011
|
-
var
|
|
2512
|
+
var import_promises13 = require("fs/promises");
|
|
2513
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2012
2514
|
|
|
2013
2515
|
// src/core/contracts.ts
|
|
2014
|
-
var
|
|
2516
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2015
2517
|
var import_yaml2 = require("yaml");
|
|
2016
2518
|
function parseStructuredContract(file, text) {
|
|
2017
|
-
const ext =
|
|
2519
|
+
const ext = import_node_path17.default.extname(file).toLowerCase();
|
|
2018
2520
|
if (ext === ".json") {
|
|
2019
2521
|
return JSON.parse(text);
|
|
2020
2522
|
}
|
|
@@ -2036,14 +2538,14 @@ async function validateContracts(root, config) {
|
|
|
2036
2538
|
const issues = [];
|
|
2037
2539
|
const contractIndex = await buildContractIndex(root, config);
|
|
2038
2540
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2039
|
-
const uiRoot =
|
|
2541
|
+
const uiRoot = import_node_path18.default.join(contractsRoot, "ui");
|
|
2040
2542
|
const themaIds = new Set(
|
|
2041
2543
|
Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
|
|
2042
2544
|
);
|
|
2043
2545
|
issues.push(...await validateUiContracts(uiRoot, themaIds));
|
|
2044
2546
|
issues.push(...await validateThemaContracts(uiRoot));
|
|
2045
|
-
issues.push(...await validateApiContracts(
|
|
2046
|
-
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")));
|
|
2047
2549
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
2048
2550
|
return issues;
|
|
2049
2551
|
}
|
|
@@ -2062,7 +2564,7 @@ async function validateUiContracts(uiRoot, themaIds) {
|
|
|
2062
2564
|
}
|
|
2063
2565
|
const issues = [];
|
|
2064
2566
|
for (const file of files) {
|
|
2065
|
-
const text = await (0,
|
|
2567
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2066
2568
|
const declaredIds = extractDeclaredContractIds(text);
|
|
2067
2569
|
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
2068
2570
|
let doc = null;
|
|
@@ -2116,7 +2618,7 @@ async function validateThemaContracts(uiRoot) {
|
|
|
2116
2618
|
}
|
|
2117
2619
|
const issues = [];
|
|
2118
2620
|
for (const file of files) {
|
|
2119
|
-
const text = await (0,
|
|
2621
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2120
2622
|
const invalidIds = extractInvalidIds(text, [
|
|
2121
2623
|
"SPEC",
|
|
2122
2624
|
"BR",
|
|
@@ -2250,7 +2752,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
2250
2752
|
}
|
|
2251
2753
|
const issues = [];
|
|
2252
2754
|
for (const file of files) {
|
|
2253
|
-
const text = await (0,
|
|
2755
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2254
2756
|
const invalidIds = extractInvalidIds(text, [
|
|
2255
2757
|
"SPEC",
|
|
2256
2758
|
"BR",
|
|
@@ -2319,7 +2821,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
2319
2821
|
}
|
|
2320
2822
|
const issues = [];
|
|
2321
2823
|
for (const file of files) {
|
|
2322
|
-
const text = await (0,
|
|
2824
|
+
const text = await (0, import_promises13.readFile)(file, "utf-8");
|
|
2323
2825
|
const invalidIds = extractInvalidIds(text, [
|
|
2324
2826
|
"SPEC",
|
|
2325
2827
|
"BR",
|
|
@@ -2516,9 +3018,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2516
3018
|
);
|
|
2517
3019
|
return issues;
|
|
2518
3020
|
}
|
|
2519
|
-
const packDir =
|
|
2520
|
-
const packRelative =
|
|
2521
|
-
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)) {
|
|
2522
3024
|
issues.push(
|
|
2523
3025
|
issue(
|
|
2524
3026
|
"QFAI-ASSET-001",
|
|
@@ -2544,7 +3046,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2544
3046
|
);
|
|
2545
3047
|
return issues;
|
|
2546
3048
|
}
|
|
2547
|
-
const assetsYamlPath =
|
|
3049
|
+
const assetsYamlPath = import_node_path18.default.join(packDir, "assets.yaml");
|
|
2548
3050
|
if (!await exists6(assetsYamlPath)) {
|
|
2549
3051
|
issues.push(
|
|
2550
3052
|
issue(
|
|
@@ -2559,7 +3061,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2559
3061
|
}
|
|
2560
3062
|
let manifest;
|
|
2561
3063
|
try {
|
|
2562
|
-
const manifestText = await (0,
|
|
3064
|
+
const manifestText = await (0, import_promises13.readFile)(assetsYamlPath, "utf-8");
|
|
2563
3065
|
manifest = parseStructuredContract(assetsYamlPath, manifestText);
|
|
2564
3066
|
} catch (error2) {
|
|
2565
3067
|
issues.push(
|
|
@@ -2632,9 +3134,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2632
3134
|
);
|
|
2633
3135
|
continue;
|
|
2634
3136
|
}
|
|
2635
|
-
const assetPath =
|
|
2636
|
-
const assetRelative =
|
|
2637
|
-
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)) {
|
|
2638
3140
|
issues.push(
|
|
2639
3141
|
issue(
|
|
2640
3142
|
"QFAI-ASSET-004",
|
|
@@ -2675,7 +3177,7 @@ function shouldIgnoreInvalidId(value, doc) {
|
|
|
2675
3177
|
return false;
|
|
2676
3178
|
}
|
|
2677
3179
|
const normalized = packValue.replace(/\\/g, "/");
|
|
2678
|
-
const basename =
|
|
3180
|
+
const basename = import_node_path18.default.posix.basename(normalized);
|
|
2679
3181
|
if (!basename) {
|
|
2680
3182
|
return false;
|
|
2681
3183
|
}
|
|
@@ -2685,7 +3187,7 @@ function isSafeRelativePath(value) {
|
|
|
2685
3187
|
if (!value) {
|
|
2686
3188
|
return false;
|
|
2687
3189
|
}
|
|
2688
|
-
if (
|
|
3190
|
+
if (import_node_path18.default.isAbsolute(value)) {
|
|
2689
3191
|
return false;
|
|
2690
3192
|
}
|
|
2691
3193
|
const normalized = value.replace(/\\/g, "/");
|
|
@@ -2700,7 +3202,7 @@ function isSafeRelativePath(value) {
|
|
|
2700
3202
|
}
|
|
2701
3203
|
async function exists6(target) {
|
|
2702
3204
|
try {
|
|
2703
|
-
await (0,
|
|
3205
|
+
await (0, import_promises13.access)(target);
|
|
2704
3206
|
return true;
|
|
2705
3207
|
} catch {
|
|
2706
3208
|
return false;
|
|
@@ -2735,8 +3237,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
2735
3237
|
}
|
|
2736
3238
|
|
|
2737
3239
|
// src/core/validators/delta.ts
|
|
2738
|
-
var
|
|
2739
|
-
var
|
|
3240
|
+
var import_promises14 = require("fs/promises");
|
|
3241
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
2740
3242
|
async function validateDeltas(root, config) {
|
|
2741
3243
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2742
3244
|
const packs = await collectSpecPackDirs(specsRoot);
|
|
@@ -2745,9 +3247,9 @@ async function validateDeltas(root, config) {
|
|
|
2745
3247
|
}
|
|
2746
3248
|
const issues = [];
|
|
2747
3249
|
for (const pack of packs) {
|
|
2748
|
-
const deltaPath =
|
|
3250
|
+
const deltaPath = import_node_path19.default.join(pack, "delta.md");
|
|
2749
3251
|
try {
|
|
2750
|
-
await (0,
|
|
3252
|
+
await (0, import_promises14.readFile)(deltaPath, "utf-8");
|
|
2751
3253
|
} catch (error2) {
|
|
2752
3254
|
if (isMissingFileError2(error2)) {
|
|
2753
3255
|
issues.push(
|
|
@@ -2798,8 +3300,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
2798
3300
|
}
|
|
2799
3301
|
|
|
2800
3302
|
// src/core/validators/ids.ts
|
|
2801
|
-
var
|
|
2802
|
-
var
|
|
3303
|
+
var import_promises15 = require("fs/promises");
|
|
3304
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
2803
3305
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2804
3306
|
async function validateDefinedIds(root, config) {
|
|
2805
3307
|
const issues = [];
|
|
@@ -2834,7 +3336,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2834
3336
|
}
|
|
2835
3337
|
async function collectSpecDefinitionIds(files, out) {
|
|
2836
3338
|
for (const file of files) {
|
|
2837
|
-
const text = await (0,
|
|
3339
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2838
3340
|
const parsed = parseSpec(text, file);
|
|
2839
3341
|
if (parsed.specId) {
|
|
2840
3342
|
recordId(out, parsed.specId, file);
|
|
@@ -2844,7 +3346,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2844
3346
|
}
|
|
2845
3347
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2846
3348
|
for (const file of files) {
|
|
2847
|
-
const text = await (0,
|
|
3349
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2848
3350
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2849
3351
|
if (!document || errors.length > 0) {
|
|
2850
3352
|
continue;
|
|
@@ -2865,7 +3367,7 @@ function recordId(out, id, file) {
|
|
|
2865
3367
|
}
|
|
2866
3368
|
function formatFileList(files, root) {
|
|
2867
3369
|
return files.map((file) => {
|
|
2868
|
-
const relative =
|
|
3370
|
+
const relative = import_node_path20.default.relative(root, file);
|
|
2869
3371
|
return relative.length > 0 ? relative : file;
|
|
2870
3372
|
}).join(", ");
|
|
2871
3373
|
}
|
|
@@ -2923,8 +3425,8 @@ async function validatePromptsIntegrity(root, config) {
|
|
|
2923
3425
|
}
|
|
2924
3426
|
|
|
2925
3427
|
// src/core/validators/scenario.ts
|
|
2926
|
-
var
|
|
2927
|
-
var
|
|
3428
|
+
var import_promises16 = require("fs/promises");
|
|
3429
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
2928
3430
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
2929
3431
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
2930
3432
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -2947,7 +3449,7 @@ async function validateScenarios(root, config) {
|
|
|
2947
3449
|
}
|
|
2948
3450
|
const issues = [];
|
|
2949
3451
|
for (const entry of entries) {
|
|
2950
|
-
const legacyScenarioPath =
|
|
3452
|
+
const legacyScenarioPath = import_node_path21.default.join(entry.dir, "scenario.md");
|
|
2951
3453
|
if (await fileExists(legacyScenarioPath)) {
|
|
2952
3454
|
issues.push(
|
|
2953
3455
|
issue4(
|
|
@@ -2961,7 +3463,7 @@ async function validateScenarios(root, config) {
|
|
|
2961
3463
|
}
|
|
2962
3464
|
let text;
|
|
2963
3465
|
try {
|
|
2964
|
-
text = await (0,
|
|
3466
|
+
text = await (0, import_promises16.readFile)(entry.scenarioPath, "utf-8");
|
|
2965
3467
|
} catch (error2) {
|
|
2966
3468
|
if (isMissingFileError3(error2)) {
|
|
2967
3469
|
issues.push(
|
|
@@ -3136,7 +3638,7 @@ function isMissingFileError3(error2) {
|
|
|
3136
3638
|
}
|
|
3137
3639
|
async function fileExists(target) {
|
|
3138
3640
|
try {
|
|
3139
|
-
await (0,
|
|
3641
|
+
await (0, import_promises16.access)(target);
|
|
3140
3642
|
return true;
|
|
3141
3643
|
} catch {
|
|
3142
3644
|
return false;
|
|
@@ -3144,7 +3646,7 @@ async function fileExists(target) {
|
|
|
3144
3646
|
}
|
|
3145
3647
|
|
|
3146
3648
|
// src/core/validators/spec.ts
|
|
3147
|
-
var
|
|
3649
|
+
var import_promises17 = require("fs/promises");
|
|
3148
3650
|
async function validateSpecs(root, config) {
|
|
3149
3651
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
3150
3652
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -3165,7 +3667,7 @@ async function validateSpecs(root, config) {
|
|
|
3165
3667
|
for (const entry of entries) {
|
|
3166
3668
|
let text;
|
|
3167
3669
|
try {
|
|
3168
|
-
text = await (0,
|
|
3670
|
+
text = await (0, import_promises17.readFile)(entry.specPath, "utf-8");
|
|
3169
3671
|
} catch (error2) {
|
|
3170
3672
|
if (isMissingFileError4(error2)) {
|
|
3171
3673
|
issues.push(
|
|
@@ -3319,7 +3821,7 @@ function isMissingFileError4(error2) {
|
|
|
3319
3821
|
}
|
|
3320
3822
|
|
|
3321
3823
|
// src/core/validators/traceability.ts
|
|
3322
|
-
var
|
|
3824
|
+
var import_promises18 = require("fs/promises");
|
|
3323
3825
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
3324
3826
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
3325
3827
|
async function validateTraceability(root, config) {
|
|
@@ -3339,7 +3841,7 @@ async function validateTraceability(root, config) {
|
|
|
3339
3841
|
const contractIndex = await buildContractIndex(root, config);
|
|
3340
3842
|
const contractIds = contractIndex.ids;
|
|
3341
3843
|
for (const file of specFiles) {
|
|
3342
|
-
const text = await (0,
|
|
3844
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3343
3845
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3344
3846
|
const parsed = parseSpec(text, file);
|
|
3345
3847
|
if (parsed.specId) {
|
|
@@ -3412,7 +3914,7 @@ async function validateTraceability(root, config) {
|
|
|
3412
3914
|
}
|
|
3413
3915
|
}
|
|
3414
3916
|
for (const file of scenarioFiles) {
|
|
3415
|
-
const text = await (0,
|
|
3917
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3416
3918
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3417
3919
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
3418
3920
|
allowCommentPrefix: true
|
|
@@ -3734,7 +4236,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
3734
4236
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
3735
4237
|
let found = false;
|
|
3736
4238
|
for (const file of targetFiles) {
|
|
3737
|
-
const text = await (0,
|
|
4239
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3738
4240
|
if (pattern.test(text)) {
|
|
3739
4241
|
found = true;
|
|
3740
4242
|
break;
|
|
@@ -3833,16 +4335,17 @@ var ID_PREFIXES2 = [
|
|
|
3833
4335
|
"DB",
|
|
3834
4336
|
"THEMA"
|
|
3835
4337
|
];
|
|
4338
|
+
var REPORT_GUARDRAILS_MAX = 20;
|
|
3836
4339
|
async function createReportData(root, validation, configResult) {
|
|
3837
|
-
const resolvedRoot =
|
|
4340
|
+
const resolvedRoot = import_node_path22.default.resolve(root);
|
|
3838
4341
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3839
4342
|
const config = resolved.config;
|
|
3840
4343
|
const configPath = resolved.configPath;
|
|
3841
4344
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3842
4345
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3843
|
-
const apiRoot =
|
|
3844
|
-
const uiRoot =
|
|
3845
|
-
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");
|
|
3846
4349
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3847
4350
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3848
4351
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -3901,6 +4404,27 @@ async function createReportData(root, validation, configResult) {
|
|
|
3901
4404
|
const scSourceRecord = mapToSortedRecord(
|
|
3902
4405
|
normalizeScSources(resolvedRoot, scSources)
|
|
3903
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
|
+
}));
|
|
3904
4428
|
const version = await resolveToolVersion();
|
|
3905
4429
|
const displayRoot = toRelativePath(resolvedRoot, resolvedRoot);
|
|
3906
4430
|
const displayConfigPath = toRelativePath(resolvedRoot, configPath);
|
|
@@ -3948,6 +4472,34 @@ async function createReportData(root, validation, configResult) {
|
|
|
3948
4472
|
specToContracts: specToContractsRecord
|
|
3949
4473
|
}
|
|
3950
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
|
+
},
|
|
3951
4503
|
issues: normalizedValidation.issues
|
|
3952
4504
|
};
|
|
3953
4505
|
}
|
|
@@ -4043,6 +4595,7 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
4043
4595
|
lines.push("");
|
|
4044
4596
|
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
4045
4597
|
lines.push("- [Change Issues](#change-issues)");
|
|
4598
|
+
lines.push("- [Decision Guardrails](#decision-guardrails)");
|
|
4046
4599
|
lines.push("- [IDs](#ids)");
|
|
4047
4600
|
lines.push("- [Traceability](#traceability)");
|
|
4048
4601
|
lines.push("");
|
|
@@ -4134,6 +4687,49 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
4134
4687
|
lines.push("### Issues");
|
|
4135
4688
|
lines.push("");
|
|
4136
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("");
|
|
4137
4733
|
lines.push("## IDs");
|
|
4138
4734
|
lines.push("");
|
|
4139
4735
|
lines.push(formatIdLine("SPEC", data.ids.spec));
|
|
@@ -4324,7 +4920,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
4324
4920
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
4325
4921
|
}
|
|
4326
4922
|
for (const file of specFiles) {
|
|
4327
|
-
const text = await (0,
|
|
4923
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4328
4924
|
const parsed = parseSpec(text, file);
|
|
4329
4925
|
const specKey = parsed.specId;
|
|
4330
4926
|
if (!specKey) {
|
|
@@ -4366,7 +4962,7 @@ async function collectIds(files) {
|
|
|
4366
4962
|
THEMA: /* @__PURE__ */ new Set()
|
|
4367
4963
|
};
|
|
4368
4964
|
for (const file of files) {
|
|
4369
|
-
const text = await (0,
|
|
4965
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4370
4966
|
for (const prefix of ID_PREFIXES2) {
|
|
4371
4967
|
const ids = extractIds(text, prefix);
|
|
4372
4968
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -4385,7 +4981,7 @@ async function collectIds(files) {
|
|
|
4385
4981
|
async function collectUpstreamIds(files) {
|
|
4386
4982
|
const ids = /* @__PURE__ */ new Set();
|
|
4387
4983
|
for (const file of files) {
|
|
4388
|
-
const text = await (0,
|
|
4984
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4389
4985
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
4390
4986
|
}
|
|
4391
4987
|
return ids;
|
|
@@ -4406,7 +5002,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
4406
5002
|
}
|
|
4407
5003
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
4408
5004
|
for (const file of targetFiles) {
|
|
4409
|
-
const text = await (0,
|
|
5005
|
+
const text = await (0, import_promises19.readFile)(file, "utf-8");
|
|
4410
5006
|
if (pattern.test(text)) {
|
|
4411
5007
|
return true;
|
|
4412
5008
|
}
|
|
@@ -4543,7 +5139,7 @@ function warnIfTruncated(scan, context) {
|
|
|
4543
5139
|
|
|
4544
5140
|
// src/cli/commands/report.ts
|
|
4545
5141
|
async function runReport(options) {
|
|
4546
|
-
const root =
|
|
5142
|
+
const root = import_node_path23.default.resolve(options.root);
|
|
4547
5143
|
const configResult = await loadConfig(root);
|
|
4548
5144
|
let validation;
|
|
4549
5145
|
if (options.runValidate) {
|
|
@@ -4560,7 +5156,7 @@ async function runReport(options) {
|
|
|
4560
5156
|
validation = normalized;
|
|
4561
5157
|
} else {
|
|
4562
5158
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
4563
|
-
const inputPath =
|
|
5159
|
+
const inputPath = import_node_path23.default.isAbsolute(input) ? input : import_node_path23.default.resolve(root, input);
|
|
4564
5160
|
try {
|
|
4565
5161
|
validation = await readValidationResult(inputPath);
|
|
4566
5162
|
} catch (err) {
|
|
@@ -4587,11 +5183,11 @@ async function runReport(options) {
|
|
|
4587
5183
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
4588
5184
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
4589
5185
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
4590
|
-
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");
|
|
4591
5187
|
const out = options.outPath ?? defaultOut;
|
|
4592
|
-
const outPath =
|
|
4593
|
-
await (0,
|
|
4594
|
-
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}
|
|
4595
5191
|
`, "utf-8");
|
|
4596
5192
|
info(
|
|
4597
5193
|
`report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
|
|
@@ -4599,7 +5195,7 @@ async function runReport(options) {
|
|
|
4599
5195
|
info(`wrote report: ${outPath}`);
|
|
4600
5196
|
}
|
|
4601
5197
|
async function readValidationResult(inputPath) {
|
|
4602
|
-
const raw = await (0,
|
|
5198
|
+
const raw = await (0, import_promises20.readFile)(inputPath, "utf-8");
|
|
4603
5199
|
const parsed = JSON.parse(raw);
|
|
4604
5200
|
if (!isValidationResult(parsed)) {
|
|
4605
5201
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -4655,15 +5251,15 @@ function isMissingFileError5(error2) {
|
|
|
4655
5251
|
return record2.code === "ENOENT";
|
|
4656
5252
|
}
|
|
4657
5253
|
async function writeValidationResult(root, outputPath, result) {
|
|
4658
|
-
const abs =
|
|
4659
|
-
await (0,
|
|
4660
|
-
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)}
|
|
4661
5257
|
`, "utf-8");
|
|
4662
5258
|
}
|
|
4663
5259
|
|
|
4664
5260
|
// src/cli/commands/validate.ts
|
|
4665
|
-
var
|
|
4666
|
-
var
|
|
5261
|
+
var import_promises21 = require("fs/promises");
|
|
5262
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4667
5263
|
|
|
4668
5264
|
// src/cli/lib/failOn.ts
|
|
4669
5265
|
function shouldFail(result, failOn) {
|
|
@@ -4678,7 +5274,7 @@ function shouldFail(result, failOn) {
|
|
|
4678
5274
|
|
|
4679
5275
|
// src/cli/commands/validate.ts
|
|
4680
5276
|
async function runValidate(options) {
|
|
4681
|
-
const root =
|
|
5277
|
+
const root = import_node_path24.default.resolve(options.root);
|
|
4682
5278
|
const configResult = await loadConfig(root);
|
|
4683
5279
|
const result = await validateProject(root, configResult);
|
|
4684
5280
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4803,12 +5399,12 @@ function issueKey(issue7) {
|
|
|
4803
5399
|
}
|
|
4804
5400
|
async function emitJson(result, root, jsonPath) {
|
|
4805
5401
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4806
|
-
await (0,
|
|
4807
|
-
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)}
|
|
4808
5404
|
`, "utf-8");
|
|
4809
5405
|
}
|
|
4810
5406
|
function resolveJsonPath(root, jsonPath) {
|
|
4811
|
-
return
|
|
5407
|
+
return import_node_path24.default.isAbsolute(jsonPath) ? jsonPath : import_node_path24.default.resolve(root, jsonPath);
|
|
4812
5408
|
}
|
|
4813
5409
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4814
5410
|
|
|
@@ -4826,7 +5422,9 @@ function parseArgs(argv, cwd) {
|
|
|
4826
5422
|
doctorFormat: "text",
|
|
4827
5423
|
validateFormat: "text",
|
|
4828
5424
|
strict: false,
|
|
4829
|
-
|
|
5425
|
+
guardrailsPaths: [],
|
|
5426
|
+
help: false,
|
|
5427
|
+
invalidExitCode: 1
|
|
4830
5428
|
};
|
|
4831
5429
|
const args = [...argv];
|
|
4832
5430
|
let command = args.shift() ?? null;
|
|
@@ -4835,6 +5433,25 @@ function parseArgs(argv, cwd) {
|
|
|
4835
5433
|
options.help = true;
|
|
4836
5434
|
command = null;
|
|
4837
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
|
+
}
|
|
4838
5455
|
for (let i = 0; i < args.length; i += 1) {
|
|
4839
5456
|
const arg = args[i];
|
|
4840
5457
|
switch (arg) {
|
|
@@ -4842,8 +5459,7 @@ function parseArgs(argv, cwd) {
|
|
|
4842
5459
|
{
|
|
4843
5460
|
const next = readOptionValue(args, i);
|
|
4844
5461
|
if (next === null) {
|
|
4845
|
-
|
|
4846
|
-
options.help = true;
|
|
5462
|
+
markInvalid();
|
|
4847
5463
|
break;
|
|
4848
5464
|
}
|
|
4849
5465
|
options.root = next;
|
|
@@ -4855,8 +5471,7 @@ function parseArgs(argv, cwd) {
|
|
|
4855
5471
|
{
|
|
4856
5472
|
const next = readOptionValue(args, i);
|
|
4857
5473
|
if (next === null) {
|
|
4858
|
-
|
|
4859
|
-
options.help = true;
|
|
5474
|
+
markInvalid();
|
|
4860
5475
|
break;
|
|
4861
5476
|
}
|
|
4862
5477
|
options.dir = next;
|
|
@@ -4875,8 +5490,7 @@ function parseArgs(argv, cwd) {
|
|
|
4875
5490
|
case "--format": {
|
|
4876
5491
|
const next = readOptionValue(args, i);
|
|
4877
5492
|
if (next === null) {
|
|
4878
|
-
|
|
4879
|
-
options.help = true;
|
|
5493
|
+
markInvalid();
|
|
4880
5494
|
break;
|
|
4881
5495
|
}
|
|
4882
5496
|
applyFormatOption(command, next, options);
|
|
@@ -4889,8 +5503,7 @@ function parseArgs(argv, cwd) {
|
|
|
4889
5503
|
case "--fail-on": {
|
|
4890
5504
|
const next = readOptionValue(args, i);
|
|
4891
5505
|
if (next === null) {
|
|
4892
|
-
|
|
4893
|
-
options.help = true;
|
|
5506
|
+
markInvalid();
|
|
4894
5507
|
break;
|
|
4895
5508
|
}
|
|
4896
5509
|
if (next === "never" || next === "warning" || next === "error") {
|
|
@@ -4902,8 +5515,7 @@ function parseArgs(argv, cwd) {
|
|
|
4902
5515
|
case "--out": {
|
|
4903
5516
|
const next = readOptionValue(args, i);
|
|
4904
5517
|
if (next === null) {
|
|
4905
|
-
|
|
4906
|
-
options.help = true;
|
|
5518
|
+
markInvalid();
|
|
4907
5519
|
break;
|
|
4908
5520
|
}
|
|
4909
5521
|
if (command === "doctor") {
|
|
@@ -4917,8 +5529,7 @@ function parseArgs(argv, cwd) {
|
|
|
4917
5529
|
case "--in": {
|
|
4918
5530
|
const next = readOptionValue(args, i);
|
|
4919
5531
|
if (next === null) {
|
|
4920
|
-
|
|
4921
|
-
options.help = true;
|
|
5532
|
+
markInvalid();
|
|
4922
5533
|
break;
|
|
4923
5534
|
}
|
|
4924
5535
|
options.reportIn = next;
|
|
@@ -4931,14 +5542,57 @@ function parseArgs(argv, cwd) {
|
|
|
4931
5542
|
case "--base-url": {
|
|
4932
5543
|
const next = readOptionValue(args, i);
|
|
4933
5544
|
if (next === null) {
|
|
4934
|
-
|
|
4935
|
-
options.help = true;
|
|
5545
|
+
markInvalid();
|
|
4936
5546
|
break;
|
|
4937
5547
|
}
|
|
4938
5548
|
options.reportBaseUrl = next;
|
|
4939
5549
|
i += 1;
|
|
4940
5550
|
break;
|
|
4941
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
|
+
}
|
|
4942
5596
|
case "--help":
|
|
4943
5597
|
case "-h":
|
|
4944
5598
|
options.help = true;
|
|
@@ -4947,6 +5601,9 @@ function parseArgs(argv, cwd) {
|
|
|
4947
5601
|
break;
|
|
4948
5602
|
}
|
|
4949
5603
|
}
|
|
5604
|
+
if (command === "guardrails" && !options.help && !options.guardrailsAction) {
|
|
5605
|
+
markInvalid();
|
|
5606
|
+
}
|
|
4950
5607
|
return { command, invalid, options };
|
|
4951
5608
|
}
|
|
4952
5609
|
function readOptionValue(args, index) {
|
|
@@ -4985,6 +5642,16 @@ function applyFormatOption(command, value, options) {
|
|
|
4985
5642
|
options.validateFormat = value;
|
|
4986
5643
|
}
|
|
4987
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
|
+
}
|
|
4988
5655
|
|
|
4989
5656
|
// src/cli/main.ts
|
|
4990
5657
|
async function run(argv, cwd) {
|
|
@@ -4992,7 +5659,7 @@ async function run(argv, cwd) {
|
|
|
4992
5659
|
if (!command || options.help) {
|
|
4993
5660
|
info(usage());
|
|
4994
5661
|
if (invalid) {
|
|
4995
|
-
process.exitCode =
|
|
5662
|
+
process.exitCode = options.invalidExitCode;
|
|
4996
5663
|
}
|
|
4997
5664
|
return;
|
|
4998
5665
|
}
|
|
@@ -5041,6 +5708,19 @@ async function run(argv, cwd) {
|
|
|
5041
5708
|
process.exitCode = exitCode;
|
|
5042
5709
|
}
|
|
5043
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;
|
|
5044
5724
|
default:
|
|
5045
5725
|
error(`Unknown command: ${command}`);
|
|
5046
5726
|
info(usage());
|
|
@@ -5055,6 +5735,7 @@ Commands:
|
|
|
5055
5735
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
5056
5736
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
5057
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
|
|
5058
5739
|
|
|
5059
5740
|
Options:
|
|
5060
5741
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
@@ -5072,6 +5753,9 @@ Options:
|
|
|
5072
5753
|
--in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
|
|
5073
5754
|
--run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
|
|
5074
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
|
|
5075
5759
|
-h, --help \u30D8\u30EB\u30D7\u8868\u793A
|
|
5076
5760
|
`;
|
|
5077
5761
|
}
|