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.
@@ -2,11 +2,11 @@
2
2
 
3
3
  // src/cli/commands/doctor.ts
4
4
  import { mkdir, writeFile } from "fs/promises";
5
- import path11 from "path";
5
+ import path12 from "path";
6
6
 
7
7
  // src/core/doctor.ts
8
8
  import { access as access4 } from "fs/promises";
9
- import path10 from "path";
9
+ import path11 from "path";
10
10
 
11
11
  // src/core/config.ts
12
12
  import { access, readFile } from "fs/promises";
@@ -24,15 +24,7 @@ var defaultConfig = {
24
24
  validation: {
25
25
  failOn: "error",
26
26
  require: {
27
- specSections: [
28
- "\u80CC\u666F",
29
- "\u30B9\u30B3\u30FC\u30D7",
30
- "\u975E\u30B4\u30FC\u30EB",
31
- "\u7528\u8A9E",
32
- "\u524D\u63D0",
33
- "\u6C7A\u5B9A\u4E8B\u9805",
34
- "\u696D\u52D9\u30EB\u30FC\u30EB"
35
- ]
27
+ specSections: []
36
28
  },
37
29
  traceability: {
38
30
  brMustHaveSc: true,
@@ -1056,8 +1048,8 @@ import { readFile as readFile4 } from "fs/promises";
1056
1048
  import path9 from "path";
1057
1049
  import { fileURLToPath as fileURLToPath2 } from "url";
1058
1050
  async function resolveToolVersion() {
1059
- if ("1.0.7".length > 0) {
1060
- return "1.0.7";
1051
+ if ("1.1.0".length > 0) {
1052
+ return "1.1.0";
1061
1053
  }
1062
1054
  try {
1063
1055
  const packagePath = resolvePackageJsonPath();
@@ -1075,6 +1067,460 @@ function resolvePackageJsonPath() {
1075
1067
  return path9.resolve(path9.dirname(basePath), "../../package.json");
1076
1068
  }
1077
1069
 
1070
+ // src/core/decisionGuardrails.ts
1071
+ import { readFile as readFile5, stat } from "fs/promises";
1072
+ import path10 from "path";
1073
+
1074
+ // src/core/parse/markdown.ts
1075
+ var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1076
+ function parseHeadings(md) {
1077
+ const lines = md.split(/\r?\n/);
1078
+ const headings = [];
1079
+ for (let i = 0; i < lines.length; i++) {
1080
+ const line = lines[i] ?? "";
1081
+ const match = line.match(HEADING_RE);
1082
+ if (!match) continue;
1083
+ const levelToken = match[1];
1084
+ const title = match[2];
1085
+ if (!levelToken || !title) continue;
1086
+ headings.push({
1087
+ level: levelToken.length,
1088
+ title: title.trim(),
1089
+ line: i + 1
1090
+ });
1091
+ }
1092
+ return headings;
1093
+ }
1094
+ function extractH2Sections(md) {
1095
+ const lines = md.split(/\r?\n/);
1096
+ const headings = parseHeadings(md).filter((heading) => heading.level === 2);
1097
+ const sections = /* @__PURE__ */ new Map();
1098
+ for (let i = 0; i < headings.length; i++) {
1099
+ const current = headings[i];
1100
+ if (!current) continue;
1101
+ const next = headings[i + 1];
1102
+ const startLine = current.line + 1;
1103
+ const endLine = (next?.line ?? lines.length + 1) - 1;
1104
+ const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
1105
+ sections.set(current.title.trim(), {
1106
+ title: current.title.trim(),
1107
+ startLine,
1108
+ endLine,
1109
+ body
1110
+ });
1111
+ }
1112
+ return sections;
1113
+ }
1114
+
1115
+ // src/core/decisionGuardrails.ts
1116
+ var DEFAULT_DECISION_GUARDRAILS_GLOBS = [".qfai/specs/**/delta.md"];
1117
+ var DEFAULT_GUARDRAILS_IGNORE_GLOBS = [
1118
+ "**/node_modules/**",
1119
+ "**/.git/**",
1120
+ "**/dist/**",
1121
+ "**/build/**",
1122
+ "**/.pnpm/**",
1123
+ "**/tmp/**",
1124
+ "**/.mcp-tools/**"
1125
+ ];
1126
+ var SECTION_TITLE = "decision guardrails";
1127
+ var ENTRY_START_RE = /^\s*[-*]\s+ID:\s*(.+?)\s*$/i;
1128
+ var FIELD_RE = /^\s{2,}([A-Za-z][A-Za-z0-9 _-]*):\s*(.*)$/;
1129
+ var CONTINUATION_RE = /^\s{4,}(.+)$/;
1130
+ var ID_FORMAT_RE = /^DG-\d{4}$/;
1131
+ var TYPE_ORDER = {
1132
+ "non-goal": 0,
1133
+ "not-now": 1,
1134
+ "trade-off": 2
1135
+ };
1136
+ async function loadDecisionGuardrails(root, options = {}) {
1137
+ const errors = [];
1138
+ const files = await scanDecisionGuardrailFiles(
1139
+ root,
1140
+ options.paths,
1141
+ errors,
1142
+ options.specsRoot
1143
+ );
1144
+ const entries = [];
1145
+ for (const filePath of files) {
1146
+ try {
1147
+ const content = await readFile5(filePath, "utf-8");
1148
+ const parsed = extractDecisionGuardrailsFromMarkdown(content, filePath);
1149
+ entries.push(...parsed);
1150
+ } catch (error2) {
1151
+ errors.push({ path: filePath, message: String(error2) });
1152
+ }
1153
+ }
1154
+ return { entries, errors, files };
1155
+ }
1156
+ function extractDecisionGuardrailsFromMarkdown(markdown, filePath) {
1157
+ const sections = extractH2Sections(markdown);
1158
+ const section = findDecisionGuardrailsSection(sections);
1159
+ if (!section) {
1160
+ return [];
1161
+ }
1162
+ const lines = section.body.split(/\r?\n/);
1163
+ const entries = [];
1164
+ let current = null;
1165
+ const flush = () => {
1166
+ if (!current) {
1167
+ return;
1168
+ }
1169
+ const entry = {
1170
+ keywords: current.keywords,
1171
+ source: { file: filePath, line: current.startLine },
1172
+ ...current.fields.id ? { id: current.fields.id } : {},
1173
+ ...current.fields.type ? { type: current.fields.type } : {},
1174
+ ...current.fields.guardrail ? { guardrail: current.fields.guardrail } : {},
1175
+ ...current.fields.rationale ? { rationale: current.fields.rationale } : {},
1176
+ ...current.fields.reconsider ? { reconsider: current.fields.reconsider } : {},
1177
+ ...current.fields.related ? { related: current.fields.related } : {},
1178
+ ...current.fields.title ? { title: current.fields.title } : {}
1179
+ };
1180
+ entries.push(entry);
1181
+ current = null;
1182
+ };
1183
+ for (let i = 0; i < lines.length; i += 1) {
1184
+ const rawLine = lines[i] ?? "";
1185
+ const lineNumber = section.startLine + i;
1186
+ const entryMatch = rawLine.match(ENTRY_START_RE);
1187
+ if (entryMatch) {
1188
+ flush();
1189
+ const id = entryMatch[1]?.trim() ?? "";
1190
+ current = {
1191
+ startLine: lineNumber,
1192
+ fields: { id },
1193
+ keywords: []
1194
+ };
1195
+ continue;
1196
+ }
1197
+ if (!current) {
1198
+ continue;
1199
+ }
1200
+ const fieldMatch = rawLine.match(FIELD_RE);
1201
+ if (fieldMatch) {
1202
+ const rawKey = fieldMatch[1] ?? "";
1203
+ const value = fieldMatch[2] ?? "";
1204
+ const key = normalizeFieldKey(rawKey);
1205
+ if (key) {
1206
+ if (key === "keywords") {
1207
+ current.keywords.push(
1208
+ ...value.split(",").map((item) => item.trim()).filter((item) => item.length > 0)
1209
+ );
1210
+ } else {
1211
+ const trimmed = value.trim();
1212
+ if (trimmed.length > 0) {
1213
+ const existing = current.fields[key];
1214
+ current.fields[key] = existing ? `${existing}
1215
+ ${trimmed}` : trimmed;
1216
+ }
1217
+ }
1218
+ current.lastKey = key;
1219
+ } else {
1220
+ delete current.lastKey;
1221
+ }
1222
+ continue;
1223
+ }
1224
+ const continuationMatch = rawLine.match(CONTINUATION_RE);
1225
+ if (continuationMatch && current.lastKey) {
1226
+ const value = continuationMatch[1]?.trim() ?? "";
1227
+ if (value.length > 0) {
1228
+ const existing = current.fields[current.lastKey];
1229
+ current.fields[current.lastKey] = existing ? `${existing}
1230
+ ${value}` : value;
1231
+ }
1232
+ }
1233
+ }
1234
+ flush();
1235
+ return entries;
1236
+ }
1237
+ function normalizeDecisionGuardrails(entries) {
1238
+ const items = [];
1239
+ for (const entry of entries) {
1240
+ const id = entry.id?.trim();
1241
+ const type = normalizeGuardrailType(entry.type);
1242
+ const guardrail = entry.guardrail?.trim();
1243
+ if (!id || !type || !guardrail) {
1244
+ continue;
1245
+ }
1246
+ const item = {
1247
+ id,
1248
+ type,
1249
+ guardrail,
1250
+ keywords: entry.keywords?.filter((word) => word.length > 0) ?? [],
1251
+ source: entry.source
1252
+ };
1253
+ const rationale = entry.rationale?.trim();
1254
+ if (rationale) {
1255
+ item.rationale = rationale;
1256
+ }
1257
+ const reconsider = entry.reconsider?.trim();
1258
+ if (reconsider) {
1259
+ item.reconsider = reconsider;
1260
+ }
1261
+ const related = entry.related?.trim();
1262
+ if (related) {
1263
+ item.related = related;
1264
+ }
1265
+ const title = entry.title?.trim();
1266
+ if (title) {
1267
+ item.title = title;
1268
+ }
1269
+ items.push(item);
1270
+ }
1271
+ return items;
1272
+ }
1273
+ function sortDecisionGuardrails(items) {
1274
+ return [...items].sort((a, b) => {
1275
+ const typeOrder = (TYPE_ORDER[a.type] ?? 999) - (TYPE_ORDER[b.type] ?? 999);
1276
+ if (typeOrder !== 0) {
1277
+ return typeOrder;
1278
+ }
1279
+ return a.id.localeCompare(b.id);
1280
+ });
1281
+ }
1282
+ function filterDecisionGuardrailsByKeyword(items, keyword) {
1283
+ const needle = keyword?.trim().toLowerCase();
1284
+ if (!needle) {
1285
+ return items;
1286
+ }
1287
+ return items.filter((item) => {
1288
+ const haystack = [
1289
+ item.title,
1290
+ item.guardrail,
1291
+ item.rationale,
1292
+ item.related,
1293
+ item.keywords.join(" ")
1294
+ ].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
1295
+ return haystack.some((value) => value.includes(needle));
1296
+ });
1297
+ }
1298
+ function formatGuardrailsForLlm(items, max) {
1299
+ const limit = Math.max(0, Math.floor(max));
1300
+ const lines = ["# Decision Guardrails (extract)", ""];
1301
+ const slice = limit > 0 ? items.slice(0, limit) : [];
1302
+ if (slice.length === 0) {
1303
+ lines.push("- (none)");
1304
+ return lines.join("\n");
1305
+ }
1306
+ for (const item of slice) {
1307
+ lines.push(`- [${item.id}][${item.type}] ${item.guardrail}`);
1308
+ if (item.rationale) {
1309
+ lines.push(` Rationale: ${item.rationale}`);
1310
+ }
1311
+ if (item.reconsider) {
1312
+ lines.push(` Reconsider: ${item.reconsider}`);
1313
+ }
1314
+ if (item.related) {
1315
+ lines.push(` Related: ${item.related}`);
1316
+ }
1317
+ }
1318
+ return lines.join("\n");
1319
+ }
1320
+ function checkDecisionGuardrails(entries) {
1321
+ const errors = [];
1322
+ const warnings = [];
1323
+ const idMap = /* @__PURE__ */ new Map();
1324
+ for (const entry of entries) {
1325
+ const file = entry.source.file;
1326
+ const line = entry.source.line;
1327
+ const id = entry.id?.trim();
1328
+ const typeRaw = entry.type?.trim();
1329
+ const guardrail = entry.guardrail?.trim();
1330
+ const rationale = entry.rationale?.trim();
1331
+ const reconsider = entry.reconsider?.trim();
1332
+ if (!id) {
1333
+ errors.push({
1334
+ severity: "error",
1335
+ code: "QFAI-GR-001",
1336
+ message: "ID is missing",
1337
+ file,
1338
+ line
1339
+ });
1340
+ } else {
1341
+ const list = idMap.get(id) ?? [];
1342
+ list.push(entry);
1343
+ idMap.set(id, list);
1344
+ if (!ID_FORMAT_RE.test(id)) {
1345
+ warnings.push({
1346
+ severity: "warning",
1347
+ code: "QFAI-GR-002",
1348
+ message: `ID format is not standard: ${id}`,
1349
+ file,
1350
+ line,
1351
+ id
1352
+ });
1353
+ }
1354
+ }
1355
+ if (!typeRaw) {
1356
+ errors.push({
1357
+ severity: "error",
1358
+ code: "QFAI-GR-003",
1359
+ message: "Type is missing",
1360
+ file,
1361
+ line,
1362
+ ...id ? { id } : {}
1363
+ });
1364
+ } else if (!normalizeGuardrailType(typeRaw)) {
1365
+ errors.push({
1366
+ severity: "error",
1367
+ code: "QFAI-GR-004",
1368
+ message: `Type is invalid: ${typeRaw}`,
1369
+ file,
1370
+ line,
1371
+ ...id ? { id } : {}
1372
+ });
1373
+ }
1374
+ if (!guardrail) {
1375
+ errors.push({
1376
+ severity: "error",
1377
+ code: "QFAI-GR-005",
1378
+ message: "Guardrail is missing",
1379
+ file,
1380
+ line,
1381
+ ...id ? { id } : {}
1382
+ });
1383
+ }
1384
+ if (!rationale) {
1385
+ warnings.push({
1386
+ severity: "warning",
1387
+ code: "QFAI-GR-006",
1388
+ message: "Rationale is missing",
1389
+ file,
1390
+ line,
1391
+ ...id ? { id } : {}
1392
+ });
1393
+ }
1394
+ if (!reconsider) {
1395
+ warnings.push({
1396
+ severity: "warning",
1397
+ code: "QFAI-GR-007",
1398
+ message: "Reconsider is missing",
1399
+ file,
1400
+ line,
1401
+ ...id ? { id } : {}
1402
+ });
1403
+ }
1404
+ }
1405
+ for (const [id, list] of idMap.entries()) {
1406
+ if (list.length > 1) {
1407
+ const locations = list.map((entry) => `${entry.source.file}:${entry.source.line}`).join(", ");
1408
+ const first = list[0];
1409
+ const file = first?.source.file ?? "";
1410
+ const line = first?.source.line;
1411
+ errors.push({
1412
+ severity: "error",
1413
+ code: "QFAI-GR-008",
1414
+ message: `ID is duplicated: ${id} (${locations})`,
1415
+ file,
1416
+ ...line !== void 0 ? { line } : {},
1417
+ id
1418
+ });
1419
+ }
1420
+ }
1421
+ return { errors, warnings };
1422
+ }
1423
+ function normalizeGuardrailType(raw) {
1424
+ if (!raw) {
1425
+ return null;
1426
+ }
1427
+ const normalized = raw.trim().toLowerCase().replace(/[_\s]+/g, "-");
1428
+ if (normalized === "non-goal") {
1429
+ return "non-goal";
1430
+ }
1431
+ if (normalized === "not-now") {
1432
+ return "not-now";
1433
+ }
1434
+ if (normalized === "trade-off") {
1435
+ return "trade-off";
1436
+ }
1437
+ return null;
1438
+ }
1439
+ function normalizeFieldKey(raw) {
1440
+ const normalized = raw.trim().toLowerCase().replace(/[_\s-]+/g, "");
1441
+ switch (normalized) {
1442
+ case "id":
1443
+ return "id";
1444
+ case "type":
1445
+ return "type";
1446
+ case "guardrail":
1447
+ return "guardrail";
1448
+ case "rationale":
1449
+ case "reason":
1450
+ return "rationale";
1451
+ case "reconsider":
1452
+ return "reconsider";
1453
+ case "related":
1454
+ return "related";
1455
+ case "keywords":
1456
+ case "keyword":
1457
+ return "keywords";
1458
+ case "title":
1459
+ case "heading":
1460
+ return "title";
1461
+ default:
1462
+ return null;
1463
+ }
1464
+ }
1465
+ async function scanDecisionGuardrailFiles(root, rawPaths, errors, specsRoot) {
1466
+ if (!rawPaths || rawPaths.length === 0) {
1467
+ const scanRoot = specsRoot ? path10.isAbsolute(specsRoot) ? specsRoot : path10.resolve(root, specsRoot) : root;
1468
+ const globs = specsRoot ? ["**/delta.md"] : DEFAULT_DECISION_GUARDRAILS_GLOBS;
1469
+ try {
1470
+ const result = await collectFilesByGlobs(scanRoot, {
1471
+ globs,
1472
+ ignore: DEFAULT_GUARDRAILS_IGNORE_GLOBS
1473
+ });
1474
+ return result.files.sort((a, b) => a.localeCompare(b));
1475
+ } catch (error2) {
1476
+ errors.push({ path: scanRoot, message: String(error2) });
1477
+ return [];
1478
+ }
1479
+ }
1480
+ const files = /* @__PURE__ */ new Set();
1481
+ for (const rawPath of rawPaths) {
1482
+ const resolved = path10.isAbsolute(rawPath) ? rawPath : path10.resolve(root, rawPath);
1483
+ const stats = await safeStat(resolved);
1484
+ if (!stats) {
1485
+ errors.push({ path: resolved, message: "Path does not exist" });
1486
+ continue;
1487
+ }
1488
+ if (stats.isFile()) {
1489
+ files.add(resolved);
1490
+ continue;
1491
+ }
1492
+ if (stats.isDirectory()) {
1493
+ try {
1494
+ const result = await collectFilesByGlobs(resolved, {
1495
+ globs: ["**/delta.md"],
1496
+ ignore: DEFAULT_GUARDRAILS_IGNORE_GLOBS
1497
+ });
1498
+ result.files.forEach((file) => files.add(file));
1499
+ } catch (error2) {
1500
+ errors.push({ path: resolved, message: String(error2) });
1501
+ }
1502
+ continue;
1503
+ }
1504
+ errors.push({ path: resolved, message: "Unsupported path type" });
1505
+ }
1506
+ return Array.from(files).sort((a, b) => a.localeCompare(b));
1507
+ }
1508
+ async function safeStat(target) {
1509
+ try {
1510
+ return await stat(target);
1511
+ } catch {
1512
+ return null;
1513
+ }
1514
+ }
1515
+ function findDecisionGuardrailsSection(sections) {
1516
+ for (const [title, section] of sections.entries()) {
1517
+ if (title.trim().toLowerCase() === SECTION_TITLE) {
1518
+ return section;
1519
+ }
1520
+ }
1521
+ return null;
1522
+ }
1523
+
1078
1524
  // src/core/doctor.ts
1079
1525
  async function exists4(target) {
1080
1526
  try {
@@ -1098,7 +1544,7 @@ function normalizeGlobs2(values) {
1098
1544
  return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
1099
1545
  }
1100
1546
  async function createDoctorData(options) {
1101
- const startDir = path10.resolve(options.startDir);
1547
+ const startDir = path11.resolve(options.startDir);
1102
1548
  const checks = [];
1103
1549
  const configPath = getConfigPath(startDir);
1104
1550
  const search = options.rootExplicit ? {
@@ -1160,9 +1606,9 @@ async function createDoctorData(options) {
1160
1606
  details: { path: toRelativePath(root, resolved) }
1161
1607
  });
1162
1608
  if (key === "promptsDir") {
1163
- const promptsLocalDir = path10.join(
1164
- path10.dirname(resolved),
1165
- `${path10.basename(resolved)}.local`
1609
+ const promptsLocalDir = path11.join(
1610
+ path11.dirname(resolved),
1611
+ `${path11.basename(resolved)}.local`
1166
1612
  );
1167
1613
  const found = await exists4(promptsLocalDir);
1168
1614
  addCheck(checks, {
@@ -1235,7 +1681,36 @@ async function createDoctorData(options) {
1235
1681
  message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
1236
1682
  details: { specPacks: entries.length, missingFiles }
1237
1683
  });
1238
- const validateJsonAbs = path10.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : path10.resolve(root, config.output.validateJsonPath);
1684
+ const guardrailsLoad = await loadDecisionGuardrails(root, {
1685
+ specsRoot
1686
+ });
1687
+ const guardrailsItems = normalizeDecisionGuardrails(guardrailsLoad.entries);
1688
+ let guardrailsSeverity;
1689
+ let guardrailsMessage;
1690
+ if (guardrailsLoad.errors.length > 0) {
1691
+ guardrailsSeverity = "warning";
1692
+ guardrailsMessage = `Decision Guardrails scan failed (errors=${guardrailsLoad.errors.length})`;
1693
+ } else if (guardrailsItems.length === 0) {
1694
+ guardrailsSeverity = "info";
1695
+ guardrailsMessage = "Decision Guardrails not found (optional)";
1696
+ } else {
1697
+ guardrailsSeverity = "ok";
1698
+ guardrailsMessage = `Decision Guardrails detected (count=${guardrailsItems.length})`;
1699
+ }
1700
+ addCheck(checks, {
1701
+ id: "guardrails.present",
1702
+ severity: guardrailsSeverity,
1703
+ title: "Decision Guardrails",
1704
+ message: guardrailsMessage,
1705
+ details: {
1706
+ count: guardrailsItems.length,
1707
+ errors: guardrailsLoad.errors.map((item) => ({
1708
+ path: toRelativePath(root, item.path),
1709
+ message: item.message
1710
+ }))
1711
+ }
1712
+ });
1713
+ const validateJsonAbs = path11.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : path11.resolve(root, config.output.validateJsonPath);
1239
1714
  const validateJsonExists = await exists4(validateJsonAbs);
1240
1715
  addCheck(checks, {
1241
1716
  id: "output.validateJson",
@@ -1245,8 +1720,8 @@ async function createDoctorData(options) {
1245
1720
  details: { path: toRelativePath(root, validateJsonAbs) }
1246
1721
  });
1247
1722
  const outDirAbs = resolvePath(root, config, "outDir");
1248
- const rel = path10.relative(outDirAbs, validateJsonAbs);
1249
- const inside = rel !== "" && !rel.startsWith("..") && !path10.isAbsolute(rel);
1723
+ const rel = path11.relative(outDirAbs, validateJsonAbs);
1724
+ const inside = rel !== "" && !rel.startsWith("..") && !path11.isAbsolute(rel);
1250
1725
  addCheck(checks, {
1251
1726
  id: "output.pathAlignment",
1252
1727
  severity: inside ? "ok" : "warning",
@@ -1369,12 +1844,12 @@ async function detectOutDirCollisions(root) {
1369
1844
  });
1370
1845
  const configPaths = configScan.files;
1371
1846
  const configRoots = Array.from(
1372
- new Set(configPaths.map((configPath) => path10.dirname(configPath)))
1847
+ new Set(configPaths.map((configPath) => path11.dirname(configPath)))
1373
1848
  ).sort((a, b) => a.localeCompare(b));
1374
1849
  const outDirToRoots = /* @__PURE__ */ new Map();
1375
1850
  for (const configRoot of configRoots) {
1376
1851
  const { config } = await loadConfig(configRoot);
1377
- const outDir = path10.normalize(resolvePath(configRoot, config, "outDir"));
1852
+ const outDir = path11.normalize(resolvePath(configRoot, config, "outDir"));
1378
1853
  const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
1379
1854
  roots.add(configRoot);
1380
1855
  outDirToRoots.set(outDir, roots);
@@ -1400,20 +1875,20 @@ async function detectOutDirCollisions(root) {
1400
1875
  };
1401
1876
  }
1402
1877
  async function findMonorepoRoot(startDir) {
1403
- let current = path10.resolve(startDir);
1878
+ let current = path11.resolve(startDir);
1404
1879
  while (true) {
1405
- const gitPath = path10.join(current, ".git");
1406
- const workspacePath = path10.join(current, "pnpm-workspace.yaml");
1880
+ const gitPath = path11.join(current, ".git");
1881
+ const workspacePath = path11.join(current, "pnpm-workspace.yaml");
1407
1882
  if (await exists4(gitPath) || await exists4(workspacePath)) {
1408
1883
  return current;
1409
1884
  }
1410
- const parent = path10.dirname(current);
1885
+ const parent = path11.dirname(current);
1411
1886
  if (parent === current) {
1412
1887
  break;
1413
1888
  }
1414
1889
  current = parent;
1415
1890
  }
1416
- return path10.resolve(startDir);
1891
+ return path11.resolve(startDir);
1417
1892
  }
1418
1893
 
1419
1894
  // src/cli/lib/logger.ts
@@ -1455,8 +1930,8 @@ async function runDoctor(options) {
1455
1930
  const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
1456
1931
  const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
1457
1932
  if (options.outPath) {
1458
- const outAbs = path11.isAbsolute(options.outPath) ? options.outPath : path11.resolve(process.cwd(), options.outPath);
1459
- await mkdir(path11.dirname(outAbs), { recursive: true });
1933
+ const outAbs = path12.isAbsolute(options.outPath) ? options.outPath : path12.resolve(process.cwd(), options.outPath);
1934
+ await mkdir(path12.dirname(outAbs), { recursive: true });
1460
1935
  await writeFile(outAbs, `${output}
1461
1936
  `, "utf-8");
1462
1937
  info(`doctor: wrote ${outAbs}`);
@@ -1475,12 +1950,77 @@ function shouldFailDoctor(summary, failOn) {
1475
1950
  return summary.warning + summary.error > 0;
1476
1951
  }
1477
1952
 
1478
- // src/cli/commands/init.ts
1953
+ // src/cli/commands/guardrails.ts
1479
1954
  import path13 from "path";
1955
+ var DEFAULT_EXTRACT_MAX = 20;
1956
+ async function runGuardrails(options) {
1957
+ if (!options.action) {
1958
+ error("guardrails: action is required (list|extract|check)");
1959
+ return 2;
1960
+ }
1961
+ const root = path13.resolve(options.root);
1962
+ const { entries, errors } = await loadDecisionGuardrails(root, {
1963
+ paths: options.paths
1964
+ });
1965
+ if (errors.length > 0) {
1966
+ errors.forEach((item) => {
1967
+ error(`guardrails: ${item.path}: ${item.message}`);
1968
+ });
1969
+ return 2;
1970
+ }
1971
+ if (options.action === "check") {
1972
+ return runGuardrailsCheck(entries, root);
1973
+ }
1974
+ const items = sortDecisionGuardrails(normalizeDecisionGuardrails(entries));
1975
+ const filtered = filterDecisionGuardrailsByKeyword(items, options.keyword);
1976
+ if (options.action === "extract") {
1977
+ const max = options.max !== void 0 ? options.max : DEFAULT_EXTRACT_MAX;
1978
+ if (!Number.isFinite(max) || max < 0) {
1979
+ error("guardrails: --max must be a non-negative number");
1980
+ return 2;
1981
+ }
1982
+ info(formatGuardrailsForLlm(filtered, max));
1983
+ return 0;
1984
+ }
1985
+ info(formatGuardrailsList(filtered, root));
1986
+ return 0;
1987
+ }
1988
+ function formatGuardrailsList(items, root) {
1989
+ const lines = ["# Decision Guardrails (list)", ""];
1990
+ if (items.length === 0) {
1991
+ lines.push("- (none)");
1992
+ return lines.join("\n");
1993
+ }
1994
+ for (const item of items) {
1995
+ const relPath = toRelativePath(root, item.source.file);
1996
+ const location = `${relPath}:${item.source.line}`;
1997
+ lines.push(`- [${item.id}][${item.type}] ${item.guardrail} (${location})`);
1998
+ }
1999
+ return lines.join("\n");
2000
+ }
2001
+ function runGuardrailsCheck(entries, root) {
2002
+ const result = checkDecisionGuardrails(entries);
2003
+ const lines = [
2004
+ `guardrails check: error=${result.errors.length} warning=${result.warnings.length}`
2005
+ ];
2006
+ const formatIssue = (issue7) => {
2007
+ const relPath = toRelativePath(root, issue7.file);
2008
+ const line = issue7.line ? `:${issue7.line}` : "";
2009
+ const id = issue7.id ? ` id=${issue7.id}` : "";
2010
+ return `[${issue7.severity}] ${issue7.code} ${issue7.message} (${relPath}${line})${id}`;
2011
+ };
2012
+ result.errors.forEach((issue7) => lines.push(formatIssue(issue7)));
2013
+ result.warnings.forEach((issue7) => lines.push(formatIssue(issue7)));
2014
+ info(lines.join("\n"));
2015
+ return result.errors.length > 0 ? 1 : 0;
2016
+ }
2017
+
2018
+ // src/cli/commands/init.ts
2019
+ import path15 from "path";
1480
2020
 
1481
2021
  // src/cli/lib/fs.ts
1482
2022
  import { access as access5, copyFile, mkdir as mkdir2, readdir as readdir3 } from "fs/promises";
1483
- import path12 from "path";
2023
+ import path14 from "path";
1484
2024
  async function copyTemplateTree(sourceRoot, destRoot, options) {
1485
2025
  const files = await collectTemplateFiles(sourceRoot);
1486
2026
  return copyFiles(files, sourceRoot, destRoot, options);
@@ -1488,7 +2028,7 @@ async function copyTemplateTree(sourceRoot, destRoot, options) {
1488
2028
  async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
1489
2029
  const allFiles = [];
1490
2030
  for (const relPath of relativePaths) {
1491
- const fullPath = path12.join(sourceRoot, relPath);
2031
+ const fullPath = path14.join(sourceRoot, relPath);
1492
2032
  const files = await collectTemplateFiles(fullPath);
1493
2033
  allFiles.push(...files);
1494
2034
  }
@@ -1498,13 +2038,13 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1498
2038
  const copied = [];
1499
2039
  const skipped = [];
1500
2040
  const conflicts = [];
1501
- const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path12.sep);
1502
- const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path12.sep);
2041
+ const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path14.sep);
2042
+ const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path14.sep);
1503
2043
  const isProtectedRelative = (relative) => {
1504
2044
  if (protectPrefixes.length === 0) {
1505
2045
  return false;
1506
2046
  }
1507
- const normalized = relative.replace(/[\\/]+/g, path12.sep);
2047
+ const normalized = relative.replace(/[\\/]+/g, path14.sep);
1508
2048
  return protectPrefixes.some(
1509
2049
  (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1510
2050
  );
@@ -1513,7 +2053,7 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1513
2053
  if (excludePrefixes.length === 0) {
1514
2054
  return false;
1515
2055
  }
1516
- const normalized = relative.replace(/[\\/]+/g, path12.sep);
2056
+ const normalized = relative.replace(/[\\/]+/g, path14.sep);
1517
2057
  return excludePrefixes.some(
1518
2058
  (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1519
2059
  );
@@ -1521,14 +2061,14 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1521
2061
  const conflictPolicy = options.conflictPolicy ?? "error";
1522
2062
  if (!options.force && conflictPolicy === "error") {
1523
2063
  for (const file of files) {
1524
- const relative = path12.relative(sourceRoot, file);
2064
+ const relative = path14.relative(sourceRoot, file);
1525
2065
  if (isExcludedRelative(relative)) {
1526
2066
  continue;
1527
2067
  }
1528
2068
  if (isProtectedRelative(relative)) {
1529
2069
  continue;
1530
2070
  }
1531
- const dest = path12.join(destRoot, relative);
2071
+ const dest = path14.join(destRoot, relative);
1532
2072
  if (!await shouldWrite(dest, options.force)) {
1533
2073
  conflicts.push(dest);
1534
2074
  }
@@ -1538,18 +2078,18 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1538
2078
  }
1539
2079
  }
1540
2080
  for (const file of files) {
1541
- const relative = path12.relative(sourceRoot, file);
2081
+ const relative = path14.relative(sourceRoot, file);
1542
2082
  if (isExcludedRelative(relative)) {
1543
2083
  continue;
1544
2084
  }
1545
- const dest = path12.join(destRoot, relative);
2085
+ const dest = path14.join(destRoot, relative);
1546
2086
  const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
1547
2087
  if (!await shouldWrite(dest, forceForThisFile)) {
1548
2088
  skipped.push(dest);
1549
2089
  continue;
1550
2090
  }
1551
2091
  if (!options.dryRun) {
1552
- await mkdir2(path12.dirname(dest), { recursive: true });
2092
+ await mkdir2(path14.dirname(dest), { recursive: true });
1553
2093
  await copyFile(file, dest);
1554
2094
  }
1555
2095
  copied.push(dest);
@@ -1573,7 +2113,7 @@ async function collectTemplateFiles(root) {
1573
2113
  }
1574
2114
  const items = await readdir3(root, { withFileTypes: true });
1575
2115
  for (const item of items) {
1576
- const fullPath = path12.join(root, item.name);
2116
+ const fullPath = path14.join(root, item.name);
1577
2117
  if (item.isDirectory()) {
1578
2118
  const nested = await collectTemplateFiles(fullPath);
1579
2119
  entries.push(...nested);
@@ -1603,10 +2143,10 @@ async function exists5(target) {
1603
2143
  // src/cli/commands/init.ts
1604
2144
  async function runInit(options) {
1605
2145
  const assetsRoot = getInitAssetsDir();
1606
- const rootAssets = path13.join(assetsRoot, "root");
1607
- const qfaiAssets = path13.join(assetsRoot, ".qfai");
1608
- const destRoot = path13.resolve(options.dir);
1609
- const destQfai = path13.join(destRoot, ".qfai");
2146
+ const rootAssets = path15.join(assetsRoot, "root");
2147
+ const qfaiAssets = path15.join(assetsRoot, ".qfai");
2148
+ const destRoot = path15.resolve(options.dir);
2149
+ const destQfai = path15.join(destRoot, ".qfai");
1610
2150
  if (options.force) {
1611
2151
  info(
1612
2152
  "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"
@@ -1652,15 +2192,15 @@ function report(copied, skipped, dryRun, label, baseDir) {
1652
2192
  info(` skipped: ${skipped.length}`);
1653
2193
  info(" skipped paths:");
1654
2194
  for (const skippedPath of skipped) {
1655
- const relative = path13.relative(baseDir, skippedPath);
2195
+ const relative = path15.relative(baseDir, skippedPath);
1656
2196
  info(` - ${relative}`);
1657
2197
  }
1658
2198
  }
1659
2199
  }
1660
2200
 
1661
2201
  // src/cli/commands/report.ts
1662
- import { mkdir as mkdir3, readFile as readFile13, writeFile as writeFile2 } from "fs/promises";
1663
- import path21 from "path";
2202
+ import { mkdir as mkdir3, readFile as readFile14, writeFile as writeFile2 } from "fs/promises";
2203
+ import path23 from "path";
1664
2204
 
1665
2205
  // src/core/normalize.ts
1666
2206
  function normalizeIssuePaths(root, issues) {
@@ -1700,12 +2240,12 @@ function normalizeValidationResult(root, result) {
1700
2240
  }
1701
2241
 
1702
2242
  // src/core/report.ts
1703
- import { readFile as readFile12 } from "fs/promises";
1704
- import path20 from "path";
2243
+ import { readFile as readFile13 } from "fs/promises";
2244
+ import path22 from "path";
1705
2245
 
1706
2246
  // src/core/contractIndex.ts
1707
- import { readFile as readFile5 } from "fs/promises";
1708
- import path14 from "path";
2247
+ import { readFile as readFile6 } from "fs/promises";
2248
+ import path16 from "path";
1709
2249
 
1710
2250
  // src/core/contractsDecl.ts
1711
2251
  var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
@@ -1727,9 +2267,9 @@ function stripContractDeclarationLines(text) {
1727
2267
  // src/core/contractIndex.ts
1728
2268
  async function buildContractIndex(root, config) {
1729
2269
  const contractsRoot = resolvePath(root, config, "contractsDir");
1730
- const uiRoot = path14.join(contractsRoot, "ui");
1731
- const apiRoot = path14.join(contractsRoot, "api");
1732
- const dbRoot = path14.join(contractsRoot, "db");
2270
+ const uiRoot = path16.join(contractsRoot, "ui");
2271
+ const apiRoot = path16.join(contractsRoot, "api");
2272
+ const dbRoot = path16.join(contractsRoot, "db");
1733
2273
  const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
1734
2274
  collectUiContractFiles(uiRoot),
1735
2275
  collectThemaContractFiles(uiRoot),
@@ -1749,7 +2289,7 @@ async function buildContractIndex(root, config) {
1749
2289
  }
1750
2290
  async function indexContractFiles(files, index) {
1751
2291
  for (const file of files) {
1752
- const text = await readFile5(file, "utf-8");
2292
+ const text = await readFile6(file, "utf-8");
1753
2293
  extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
1754
2294
  }
1755
2295
  }
@@ -1874,53 +2414,11 @@ function unique3(values) {
1874
2414
  return Array.from(new Set(values));
1875
2415
  }
1876
2416
 
1877
- // src/core/parse/markdown.ts
1878
- var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1879
- function parseHeadings(md) {
1880
- const lines = md.split(/\r?\n/);
1881
- const headings = [];
1882
- for (let i = 0; i < lines.length; i++) {
1883
- const line = lines[i] ?? "";
1884
- const match = line.match(HEADING_RE);
1885
- if (!match) continue;
1886
- const levelToken = match[1];
1887
- const title = match[2];
1888
- if (!levelToken || !title) continue;
1889
- headings.push({
1890
- level: levelToken.length,
1891
- title: title.trim(),
1892
- line: i + 1
1893
- });
1894
- }
1895
- return headings;
1896
- }
1897
- function extractH2Sections(md) {
1898
- const lines = md.split(/\r?\n/);
1899
- const headings = parseHeadings(md).filter((heading) => heading.level === 2);
1900
- const sections = /* @__PURE__ */ new Map();
1901
- for (let i = 0; i < headings.length; i++) {
1902
- const current = headings[i];
1903
- if (!current) continue;
1904
- const next = headings[i + 1];
1905
- const startLine = current.line + 1;
1906
- const endLine = (next?.line ?? lines.length + 1) - 1;
1907
- const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
1908
- sections.set(current.title.trim(), {
1909
- title: current.title.trim(),
1910
- startLine,
1911
- endLine,
1912
- body
1913
- });
1914
- }
1915
- return sections;
1916
- }
1917
-
1918
2417
  // src/core/parse/spec.ts
1919
2418
  var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
1920
2419
  var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
1921
2420
  var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
1922
2421
  var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
1923
- var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
1924
2422
  var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
1925
2423
  function parseSpec(md, file) {
1926
2424
  const headings = parseHeadings(md);
@@ -1928,15 +2426,13 @@ function parseSpec(md, file) {
1928
2426
  const specId = h1?.title.match(SPEC_ID_RE)?.[0];
1929
2427
  const sections = extractH2Sections(md);
1930
2428
  const sectionNames = new Set(Array.from(sections.keys()));
1931
- const brSection = sections.get(BR_SECTION_TITLE);
1932
- const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
1933
- const startLine = brSection?.startLine ?? 1;
2429
+ const lines = md.split(/\r?\n/);
1934
2430
  const brs = [];
1935
2431
  const brsWithoutPriority = [];
1936
2432
  const brsWithInvalidPriority = [];
1937
- for (let i = 0; i < brLines.length; i++) {
1938
- const lineText = brLines[i] ?? "";
1939
- const lineNumber = startLine + i;
2433
+ for (let i = 0; i < lines.length; i++) {
2434
+ const lineText = lines[i] ?? "";
2435
+ const lineNumber = i + 1;
1940
2436
  const validMatch = lineText.match(BR_LINE_RE);
1941
2437
  if (validMatch) {
1942
2438
  const id = validMatch[1];
@@ -1994,14 +2490,14 @@ function parseSpec(md, file) {
1994
2490
  }
1995
2491
 
1996
2492
  // src/core/validators/contracts.ts
1997
- import { access as access6, readFile as readFile6 } from "fs/promises";
1998
- import path16 from "path";
2493
+ import { access as access6, readFile as readFile7 } from "fs/promises";
2494
+ import path18 from "path";
1999
2495
 
2000
2496
  // src/core/contracts.ts
2001
- import path15 from "path";
2497
+ import path17 from "path";
2002
2498
  import { parse as parseYaml2 } from "yaml";
2003
2499
  function parseStructuredContract(file, text) {
2004
- const ext = path15.extname(file).toLowerCase();
2500
+ const ext = path17.extname(file).toLowerCase();
2005
2501
  if (ext === ".json") {
2006
2502
  return JSON.parse(text);
2007
2503
  }
@@ -2023,14 +2519,14 @@ async function validateContracts(root, config) {
2023
2519
  const issues = [];
2024
2520
  const contractIndex = await buildContractIndex(root, config);
2025
2521
  const contractsRoot = resolvePath(root, config, "contractsDir");
2026
- const uiRoot = path16.join(contractsRoot, "ui");
2522
+ const uiRoot = path18.join(contractsRoot, "ui");
2027
2523
  const themaIds = new Set(
2028
2524
  Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
2029
2525
  );
2030
2526
  issues.push(...await validateUiContracts(uiRoot, themaIds));
2031
2527
  issues.push(...await validateThemaContracts(uiRoot));
2032
- issues.push(...await validateApiContracts(path16.join(contractsRoot, "api")));
2033
- issues.push(...await validateDbContracts(path16.join(contractsRoot, "db")));
2528
+ issues.push(...await validateApiContracts(path18.join(contractsRoot, "api")));
2529
+ issues.push(...await validateDbContracts(path18.join(contractsRoot, "db")));
2034
2530
  issues.push(...validateDuplicateContractIds(contractIndex));
2035
2531
  return issues;
2036
2532
  }
@@ -2049,7 +2545,7 @@ async function validateUiContracts(uiRoot, themaIds) {
2049
2545
  }
2050
2546
  const issues = [];
2051
2547
  for (const file of files) {
2052
- const text = await readFile6(file, "utf-8");
2548
+ const text = await readFile7(file, "utf-8");
2053
2549
  const declaredIds = extractDeclaredContractIds(text);
2054
2550
  issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
2055
2551
  let doc = null;
@@ -2103,7 +2599,7 @@ async function validateThemaContracts(uiRoot) {
2103
2599
  }
2104
2600
  const issues = [];
2105
2601
  for (const file of files) {
2106
- const text = await readFile6(file, "utf-8");
2602
+ const text = await readFile7(file, "utf-8");
2107
2603
  const invalidIds = extractInvalidIds(text, [
2108
2604
  "SPEC",
2109
2605
  "BR",
@@ -2237,7 +2733,7 @@ async function validateApiContracts(apiRoot) {
2237
2733
  }
2238
2734
  const issues = [];
2239
2735
  for (const file of files) {
2240
- const text = await readFile6(file, "utf-8");
2736
+ const text = await readFile7(file, "utf-8");
2241
2737
  const invalidIds = extractInvalidIds(text, [
2242
2738
  "SPEC",
2243
2739
  "BR",
@@ -2306,7 +2802,7 @@ async function validateDbContracts(dbRoot) {
2306
2802
  }
2307
2803
  const issues = [];
2308
2804
  for (const file of files) {
2309
- const text = await readFile6(file, "utf-8");
2805
+ const text = await readFile7(file, "utf-8");
2310
2806
  const invalidIds = extractInvalidIds(text, [
2311
2807
  "SPEC",
2312
2808
  "BR",
@@ -2503,9 +2999,9 @@ async function validateUiAssets(assets, file, uiRoot) {
2503
2999
  );
2504
3000
  return issues;
2505
3001
  }
2506
- const packDir = path16.resolve(uiRoot, packValue);
2507
- const packRelative = path16.relative(uiRoot, packDir);
2508
- if (packRelative.startsWith("..") || path16.isAbsolute(packRelative)) {
3002
+ const packDir = path18.resolve(uiRoot, packValue);
3003
+ const packRelative = path18.relative(uiRoot, packDir);
3004
+ if (packRelative.startsWith("..") || path18.isAbsolute(packRelative)) {
2509
3005
  issues.push(
2510
3006
  issue(
2511
3007
  "QFAI-ASSET-001",
@@ -2531,7 +3027,7 @@ async function validateUiAssets(assets, file, uiRoot) {
2531
3027
  );
2532
3028
  return issues;
2533
3029
  }
2534
- const assetsYamlPath = path16.join(packDir, "assets.yaml");
3030
+ const assetsYamlPath = path18.join(packDir, "assets.yaml");
2535
3031
  if (!await exists6(assetsYamlPath)) {
2536
3032
  issues.push(
2537
3033
  issue(
@@ -2546,7 +3042,7 @@ async function validateUiAssets(assets, file, uiRoot) {
2546
3042
  }
2547
3043
  let manifest;
2548
3044
  try {
2549
- const manifestText = await readFile6(assetsYamlPath, "utf-8");
3045
+ const manifestText = await readFile7(assetsYamlPath, "utf-8");
2550
3046
  manifest = parseStructuredContract(assetsYamlPath, manifestText);
2551
3047
  } catch (error2) {
2552
3048
  issues.push(
@@ -2619,9 +3115,9 @@ async function validateUiAssets(assets, file, uiRoot) {
2619
3115
  );
2620
3116
  continue;
2621
3117
  }
2622
- const assetPath = path16.resolve(packDir, entry.path);
2623
- const assetRelative = path16.relative(packDir, assetPath);
2624
- if (assetRelative.startsWith("..") || path16.isAbsolute(assetRelative)) {
3118
+ const assetPath = path18.resolve(packDir, entry.path);
3119
+ const assetRelative = path18.relative(packDir, assetPath);
3120
+ if (assetRelative.startsWith("..") || path18.isAbsolute(assetRelative)) {
2625
3121
  issues.push(
2626
3122
  issue(
2627
3123
  "QFAI-ASSET-004",
@@ -2662,7 +3158,7 @@ function shouldIgnoreInvalidId(value, doc) {
2662
3158
  return false;
2663
3159
  }
2664
3160
  const normalized = packValue.replace(/\\/g, "/");
2665
- const basename = path16.posix.basename(normalized);
3161
+ const basename = path18.posix.basename(normalized);
2666
3162
  if (!basename) {
2667
3163
  return false;
2668
3164
  }
@@ -2672,7 +3168,7 @@ function isSafeRelativePath(value) {
2672
3168
  if (!value) {
2673
3169
  return false;
2674
3170
  }
2675
- if (path16.isAbsolute(value)) {
3171
+ if (path18.isAbsolute(value)) {
2676
3172
  return false;
2677
3173
  }
2678
3174
  const normalized = value.replace(/\\/g, "/");
@@ -2722,8 +3218,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
2722
3218
  }
2723
3219
 
2724
3220
  // src/core/validators/delta.ts
2725
- import { readFile as readFile7 } from "fs/promises";
2726
- import path17 from "path";
3221
+ import { readFile as readFile8 } from "fs/promises";
3222
+ import path19 from "path";
2727
3223
  async function validateDeltas(root, config) {
2728
3224
  const specsRoot = resolvePath(root, config, "specsDir");
2729
3225
  const packs = await collectSpecPackDirs(specsRoot);
@@ -2732,9 +3228,9 @@ async function validateDeltas(root, config) {
2732
3228
  }
2733
3229
  const issues = [];
2734
3230
  for (const pack of packs) {
2735
- const deltaPath = path17.join(pack, "delta.md");
3231
+ const deltaPath = path19.join(pack, "delta.md");
2736
3232
  try {
2737
- await readFile7(deltaPath, "utf-8");
3233
+ await readFile8(deltaPath, "utf-8");
2738
3234
  } catch (error2) {
2739
3235
  if (isMissingFileError2(error2)) {
2740
3236
  issues.push(
@@ -2785,8 +3281,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
2785
3281
  }
2786
3282
 
2787
3283
  // src/core/validators/ids.ts
2788
- import { readFile as readFile8 } from "fs/promises";
2789
- import path18 from "path";
3284
+ import { readFile as readFile9 } from "fs/promises";
3285
+ import path20 from "path";
2790
3286
  var SC_TAG_RE3 = /^SC-\d{4}$/;
2791
3287
  async function validateDefinedIds(root, config) {
2792
3288
  const issues = [];
@@ -2821,7 +3317,7 @@ async function validateDefinedIds(root, config) {
2821
3317
  }
2822
3318
  async function collectSpecDefinitionIds(files, out) {
2823
3319
  for (const file of files) {
2824
- const text = await readFile8(file, "utf-8");
3320
+ const text = await readFile9(file, "utf-8");
2825
3321
  const parsed = parseSpec(text, file);
2826
3322
  if (parsed.specId) {
2827
3323
  recordId(out, parsed.specId, file);
@@ -2831,7 +3327,7 @@ async function collectSpecDefinitionIds(files, out) {
2831
3327
  }
2832
3328
  async function collectScenarioDefinitionIds(files, out) {
2833
3329
  for (const file of files) {
2834
- const text = await readFile8(file, "utf-8");
3330
+ const text = await readFile9(file, "utf-8");
2835
3331
  const { document, errors } = parseScenarioDocument(text, file);
2836
3332
  if (!document || errors.length > 0) {
2837
3333
  continue;
@@ -2852,7 +3348,7 @@ function recordId(out, id, file) {
2852
3348
  }
2853
3349
  function formatFileList(files, root) {
2854
3350
  return files.map((file) => {
2855
- const relative = path18.relative(root, file);
3351
+ const relative = path20.relative(root, file);
2856
3352
  return relative.length > 0 ? relative : file;
2857
3353
  }).join(", ");
2858
3354
  }
@@ -2910,8 +3406,8 @@ async function validatePromptsIntegrity(root, config) {
2910
3406
  }
2911
3407
 
2912
3408
  // src/core/validators/scenario.ts
2913
- import { access as access7, readFile as readFile9 } from "fs/promises";
2914
- import path19 from "path";
3409
+ import { access as access7, readFile as readFile10 } from "fs/promises";
3410
+ import path21 from "path";
2915
3411
  var GIVEN_PATTERN = /\bGiven\b/;
2916
3412
  var WHEN_PATTERN = /\bWhen\b/;
2917
3413
  var THEN_PATTERN = /\bThen\b/;
@@ -2934,7 +3430,7 @@ async function validateScenarios(root, config) {
2934
3430
  }
2935
3431
  const issues = [];
2936
3432
  for (const entry of entries) {
2937
- const legacyScenarioPath = path19.join(entry.dir, "scenario.md");
3433
+ const legacyScenarioPath = path21.join(entry.dir, "scenario.md");
2938
3434
  if (await fileExists(legacyScenarioPath)) {
2939
3435
  issues.push(
2940
3436
  issue4(
@@ -2948,7 +3444,7 @@ async function validateScenarios(root, config) {
2948
3444
  }
2949
3445
  let text;
2950
3446
  try {
2951
- text = await readFile9(entry.scenarioPath, "utf-8");
3447
+ text = await readFile10(entry.scenarioPath, "utf-8");
2952
3448
  } catch (error2) {
2953
3449
  if (isMissingFileError3(error2)) {
2954
3450
  issues.push(
@@ -3131,7 +3627,7 @@ async function fileExists(target) {
3131
3627
  }
3132
3628
 
3133
3629
  // src/core/validators/spec.ts
3134
- import { readFile as readFile10 } from "fs/promises";
3630
+ import { readFile as readFile11 } from "fs/promises";
3135
3631
  async function validateSpecs(root, config) {
3136
3632
  const specsRoot = resolvePath(root, config, "specsDir");
3137
3633
  const entries = await collectSpecEntries(specsRoot);
@@ -3152,7 +3648,7 @@ async function validateSpecs(root, config) {
3152
3648
  for (const entry of entries) {
3153
3649
  let text;
3154
3650
  try {
3155
- text = await readFile10(entry.specPath, "utf-8");
3651
+ text = await readFile11(entry.specPath, "utf-8");
3156
3652
  } catch (error2) {
3157
3653
  if (isMissingFileError4(error2)) {
3158
3654
  issues.push(
@@ -3306,7 +3802,7 @@ function isMissingFileError4(error2) {
3306
3802
  }
3307
3803
 
3308
3804
  // src/core/validators/traceability.ts
3309
- import { readFile as readFile11 } from "fs/promises";
3805
+ import { readFile as readFile12 } from "fs/promises";
3310
3806
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
3311
3807
  var BR_TAG_RE2 = /^BR-\d{4}$/;
3312
3808
  async function validateTraceability(root, config) {
@@ -3326,7 +3822,7 @@ async function validateTraceability(root, config) {
3326
3822
  const contractIndex = await buildContractIndex(root, config);
3327
3823
  const contractIds = contractIndex.ids;
3328
3824
  for (const file of specFiles) {
3329
- const text = await readFile11(file, "utf-8");
3825
+ const text = await readFile12(file, "utf-8");
3330
3826
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
3331
3827
  const parsed = parseSpec(text, file);
3332
3828
  if (parsed.specId) {
@@ -3399,7 +3895,7 @@ async function validateTraceability(root, config) {
3399
3895
  }
3400
3896
  }
3401
3897
  for (const file of scenarioFiles) {
3402
- const text = await readFile11(file, "utf-8");
3898
+ const text = await readFile12(file, "utf-8");
3403
3899
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
3404
3900
  const scenarioContractRefs = parseContractRefs(text, {
3405
3901
  allowCommentPrefix: true
@@ -3721,7 +4217,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
3721
4217
  const pattern = buildIdPattern(Array.from(upstreamIds));
3722
4218
  let found = false;
3723
4219
  for (const file of targetFiles) {
3724
- const text = await readFile11(file, "utf-8");
4220
+ const text = await readFile12(file, "utf-8");
3725
4221
  if (pattern.test(text)) {
3726
4222
  found = true;
3727
4223
  break;
@@ -3820,16 +4316,17 @@ var ID_PREFIXES2 = [
3820
4316
  "DB",
3821
4317
  "THEMA"
3822
4318
  ];
4319
+ var REPORT_GUARDRAILS_MAX = 20;
3823
4320
  async function createReportData(root, validation, configResult) {
3824
- const resolvedRoot = path20.resolve(root);
4321
+ const resolvedRoot = path22.resolve(root);
3825
4322
  const resolved = configResult ?? await loadConfig(resolvedRoot);
3826
4323
  const config = resolved.config;
3827
4324
  const configPath = resolved.configPath;
3828
4325
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
3829
4326
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
3830
- const apiRoot = path20.join(contractsRoot, "api");
3831
- const uiRoot = path20.join(contractsRoot, "ui");
3832
- const dbRoot = path20.join(contractsRoot, "db");
4327
+ const apiRoot = path22.join(contractsRoot, "api");
4328
+ const uiRoot = path22.join(contractsRoot, "ui");
4329
+ const dbRoot = path22.join(contractsRoot, "db");
3833
4330
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
3834
4331
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
3835
4332
  const specFiles = await collectSpecFiles(specsRoot);
@@ -3888,6 +4385,27 @@ async function createReportData(root, validation, configResult) {
3888
4385
  const scSourceRecord = mapToSortedRecord(
3889
4386
  normalizeScSources(resolvedRoot, scSources)
3890
4387
  );
4388
+ const guardrailsLoad = await loadDecisionGuardrails(resolvedRoot, {
4389
+ specsRoot
4390
+ });
4391
+ const guardrailsAll = sortDecisionGuardrails(
4392
+ normalizeDecisionGuardrails(guardrailsLoad.entries)
4393
+ );
4394
+ const guardrailsDisplay = guardrailsAll.slice(0, REPORT_GUARDRAILS_MAX);
4395
+ const guardrailsByType = { nonGoal: 0, notNow: 0, tradeOff: 0 };
4396
+ for (const item of guardrailsAll) {
4397
+ if (item.type === "non-goal") {
4398
+ guardrailsByType.nonGoal += 1;
4399
+ } else if (item.type === "not-now") {
4400
+ guardrailsByType.notNow += 1;
4401
+ } else if (item.type === "trade-off") {
4402
+ guardrailsByType.tradeOff += 1;
4403
+ }
4404
+ }
4405
+ const guardrailsErrors = guardrailsLoad.errors.map((item) => ({
4406
+ path: toRelativePath(resolvedRoot, item.path),
4407
+ message: item.message
4408
+ }));
3891
4409
  const version = await resolveToolVersion();
3892
4410
  const displayRoot = toRelativePath(resolvedRoot, resolvedRoot);
3893
4411
  const displayConfigPath = toRelativePath(resolvedRoot, configPath);
@@ -3935,6 +4453,34 @@ async function createReportData(root, validation, configResult) {
3935
4453
  specToContracts: specToContractsRecord
3936
4454
  }
3937
4455
  },
4456
+ guardrails: {
4457
+ total: guardrailsAll.length,
4458
+ max: REPORT_GUARDRAILS_MAX,
4459
+ truncated: guardrailsAll.length > guardrailsDisplay.length,
4460
+ byType: guardrailsByType,
4461
+ items: guardrailsDisplay.map((item) => {
4462
+ const entry = {
4463
+ id: item.id,
4464
+ type: item.type,
4465
+ guardrail: item.guardrail,
4466
+ source: {
4467
+ file: toRelativePath(resolvedRoot, item.source.file),
4468
+ line: item.source.line
4469
+ }
4470
+ };
4471
+ if (item.rationale) {
4472
+ entry.rationale = item.rationale;
4473
+ }
4474
+ if (item.reconsider) {
4475
+ entry.reconsider = item.reconsider;
4476
+ }
4477
+ if (item.related) {
4478
+ entry.related = item.related;
4479
+ }
4480
+ return entry;
4481
+ }),
4482
+ scanErrors: guardrailsErrors
4483
+ },
3938
4484
  issues: normalizedValidation.issues
3939
4485
  };
3940
4486
  }
@@ -4030,6 +4576,7 @@ function formatReportMarkdown(data, options = {}) {
4030
4576
  lines.push("");
4031
4577
  lines.push("- [Compatibility Issues](#compatibility-issues)");
4032
4578
  lines.push("- [Change Issues](#change-issues)");
4579
+ lines.push("- [Decision Guardrails](#decision-guardrails)");
4033
4580
  lines.push("- [IDs](#ids)");
4034
4581
  lines.push("- [Traceability](#traceability)");
4035
4582
  lines.push("");
@@ -4121,6 +4668,49 @@ function formatReportMarkdown(data, options = {}) {
4121
4668
  lines.push("### Issues");
4122
4669
  lines.push("");
4123
4670
  lines.push(...formatIssueCards(issuesByCategory.change));
4671
+ lines.push("## Decision Guardrails");
4672
+ lines.push("");
4673
+ lines.push(`- total: ${data.guardrails.total}`);
4674
+ lines.push(
4675
+ `- types: non-goal ${data.guardrails.byType.nonGoal} / not-now ${data.guardrails.byType.notNow} / trade-off ${data.guardrails.byType.tradeOff}`
4676
+ );
4677
+ if (data.guardrails.truncated) {
4678
+ lines.push(`- truncated: true (max=${data.guardrails.max})`);
4679
+ }
4680
+ if (data.guardrails.scanErrors.length > 0) {
4681
+ lines.push(`- scanErrors: ${data.guardrails.scanErrors.length}`);
4682
+ }
4683
+ lines.push("");
4684
+ if (data.guardrails.items.length === 0) {
4685
+ lines.push("- (none)");
4686
+ } else {
4687
+ for (const item of data.guardrails.items) {
4688
+ lines.push(`- [${item.id}][${item.type}] ${item.guardrail}`);
4689
+ lines.push(
4690
+ ` - source: ${formatPathWithLine(item.source.file, { line: item.source.line }, baseUrl)}`
4691
+ );
4692
+ if (item.rationale) {
4693
+ lines.push(` - Rationale: ${item.rationale}`);
4694
+ }
4695
+ if (item.reconsider) {
4696
+ lines.push(` - Reconsider: ${item.reconsider}`);
4697
+ }
4698
+ if (item.related) {
4699
+ lines.push(` - Related: ${item.related}`);
4700
+ }
4701
+ }
4702
+ }
4703
+ if (data.guardrails.scanErrors.length > 0) {
4704
+ lines.push("");
4705
+ lines.push("### Scan errors");
4706
+ lines.push("");
4707
+ for (const errorItem of data.guardrails.scanErrors) {
4708
+ lines.push(
4709
+ `- ${formatPathLink(errorItem.path, baseUrl)}: ${errorItem.message}`
4710
+ );
4711
+ }
4712
+ }
4713
+ lines.push("");
4124
4714
  lines.push("## IDs");
4125
4715
  lines.push("");
4126
4716
  lines.push(formatIdLine("SPEC", data.ids.spec));
@@ -4311,7 +4901,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
4311
4901
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
4312
4902
  }
4313
4903
  for (const file of specFiles) {
4314
- const text = await readFile12(file, "utf-8");
4904
+ const text = await readFile13(file, "utf-8");
4315
4905
  const parsed = parseSpec(text, file);
4316
4906
  const specKey = parsed.specId;
4317
4907
  if (!specKey) {
@@ -4353,7 +4943,7 @@ async function collectIds(files) {
4353
4943
  THEMA: /* @__PURE__ */ new Set()
4354
4944
  };
4355
4945
  for (const file of files) {
4356
- const text = await readFile12(file, "utf-8");
4946
+ const text = await readFile13(file, "utf-8");
4357
4947
  for (const prefix of ID_PREFIXES2) {
4358
4948
  const ids = extractIds(text, prefix);
4359
4949
  ids.forEach((id) => result[prefix].add(id));
@@ -4372,7 +4962,7 @@ async function collectIds(files) {
4372
4962
  async function collectUpstreamIds(files) {
4373
4963
  const ids = /* @__PURE__ */ new Set();
4374
4964
  for (const file of files) {
4375
- const text = await readFile12(file, "utf-8");
4965
+ const text = await readFile13(file, "utf-8");
4376
4966
  extractAllIds(text).forEach((id) => ids.add(id));
4377
4967
  }
4378
4968
  return ids;
@@ -4393,7 +4983,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
4393
4983
  }
4394
4984
  const pattern = buildIdPattern2(Array.from(upstreamIds));
4395
4985
  for (const file of targetFiles) {
4396
- const text = await readFile12(file, "utf-8");
4986
+ const text = await readFile13(file, "utf-8");
4397
4987
  if (pattern.test(text)) {
4398
4988
  return true;
4399
4989
  }
@@ -4530,7 +5120,7 @@ function warnIfTruncated(scan, context) {
4530
5120
 
4531
5121
  // src/cli/commands/report.ts
4532
5122
  async function runReport(options) {
4533
- const root = path21.resolve(options.root);
5123
+ const root = path23.resolve(options.root);
4534
5124
  const configResult = await loadConfig(root);
4535
5125
  let validation;
4536
5126
  if (options.runValidate) {
@@ -4547,7 +5137,7 @@ async function runReport(options) {
4547
5137
  validation = normalized;
4548
5138
  } else {
4549
5139
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
4550
- const inputPath = path21.isAbsolute(input) ? input : path21.resolve(root, input);
5140
+ const inputPath = path23.isAbsolute(input) ? input : path23.resolve(root, input);
4551
5141
  try {
4552
5142
  validation = await readValidationResult(inputPath);
4553
5143
  } catch (err) {
@@ -4574,10 +5164,10 @@ async function runReport(options) {
4574
5164
  warnIfTruncated(data.traceability.testFiles, "report");
4575
5165
  const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
4576
5166
  const outRoot = resolvePath(root, configResult.config, "outDir");
4577
- const defaultOut = options.format === "json" ? path21.join(outRoot, "report.json") : path21.join(outRoot, "report.md");
5167
+ const defaultOut = options.format === "json" ? path23.join(outRoot, "report.json") : path23.join(outRoot, "report.md");
4578
5168
  const out = options.outPath ?? defaultOut;
4579
- const outPath = path21.isAbsolute(out) ? out : path21.resolve(root, out);
4580
- await mkdir3(path21.dirname(outPath), { recursive: true });
5169
+ const outPath = path23.isAbsolute(out) ? out : path23.resolve(root, out);
5170
+ await mkdir3(path23.dirname(outPath), { recursive: true });
4581
5171
  await writeFile2(outPath, `${output}
4582
5172
  `, "utf-8");
4583
5173
  info(
@@ -4586,7 +5176,7 @@ async function runReport(options) {
4586
5176
  info(`wrote report: ${outPath}`);
4587
5177
  }
4588
5178
  async function readValidationResult(inputPath) {
4589
- const raw = await readFile13(inputPath, "utf-8");
5179
+ const raw = await readFile14(inputPath, "utf-8");
4590
5180
  const parsed = JSON.parse(raw);
4591
5181
  if (!isValidationResult(parsed)) {
4592
5182
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -4642,15 +5232,15 @@ function isMissingFileError5(error2) {
4642
5232
  return record2.code === "ENOENT";
4643
5233
  }
4644
5234
  async function writeValidationResult(root, outputPath, result) {
4645
- const abs = path21.isAbsolute(outputPath) ? outputPath : path21.resolve(root, outputPath);
4646
- await mkdir3(path21.dirname(abs), { recursive: true });
5235
+ const abs = path23.isAbsolute(outputPath) ? outputPath : path23.resolve(root, outputPath);
5236
+ await mkdir3(path23.dirname(abs), { recursive: true });
4647
5237
  await writeFile2(abs, `${JSON.stringify(result, null, 2)}
4648
5238
  `, "utf-8");
4649
5239
  }
4650
5240
 
4651
5241
  // src/cli/commands/validate.ts
4652
5242
  import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
4653
- import path22 from "path";
5243
+ import path24 from "path";
4654
5244
 
4655
5245
  // src/cli/lib/failOn.ts
4656
5246
  function shouldFail(result, failOn) {
@@ -4665,7 +5255,7 @@ function shouldFail(result, failOn) {
4665
5255
 
4666
5256
  // src/cli/commands/validate.ts
4667
5257
  async function runValidate(options) {
4668
- const root = path22.resolve(options.root);
5258
+ const root = path24.resolve(options.root);
4669
5259
  const configResult = await loadConfig(root);
4670
5260
  const result = await validateProject(root, configResult);
4671
5261
  const normalized = normalizeValidationResult(root, result);
@@ -4790,12 +5380,12 @@ function issueKey(issue7) {
4790
5380
  }
4791
5381
  async function emitJson(result, root, jsonPath) {
4792
5382
  const abs = resolveJsonPath(root, jsonPath);
4793
- await mkdir4(path22.dirname(abs), { recursive: true });
5383
+ await mkdir4(path24.dirname(abs), { recursive: true });
4794
5384
  await writeFile3(abs, `${JSON.stringify(result, null, 2)}
4795
5385
  `, "utf-8");
4796
5386
  }
4797
5387
  function resolveJsonPath(root, jsonPath) {
4798
- return path22.isAbsolute(jsonPath) ? jsonPath : path22.resolve(root, jsonPath);
5388
+ return path24.isAbsolute(jsonPath) ? jsonPath : path24.resolve(root, jsonPath);
4799
5389
  }
4800
5390
  var GITHUB_ANNOTATION_LIMIT = 100;
4801
5391
 
@@ -4813,7 +5403,9 @@ function parseArgs(argv, cwd) {
4813
5403
  doctorFormat: "text",
4814
5404
  validateFormat: "text",
4815
5405
  strict: false,
4816
- help: false
5406
+ guardrailsPaths: [],
5407
+ help: false,
5408
+ invalidExitCode: 1
4817
5409
  };
4818
5410
  const args = [...argv];
4819
5411
  let command = args.shift() ?? null;
@@ -4822,6 +5414,25 @@ function parseArgs(argv, cwd) {
4822
5414
  options.help = true;
4823
5415
  command = null;
4824
5416
  }
5417
+ const markInvalid = () => {
5418
+ invalid = true;
5419
+ options.help = true;
5420
+ if (command === "guardrails") {
5421
+ options.invalidExitCode = 2;
5422
+ }
5423
+ };
5424
+ if (command === "guardrails") {
5425
+ const candidate = args[0];
5426
+ if (candidate && !candidate.startsWith("--")) {
5427
+ const action = normalizeGuardrailsAction(candidate);
5428
+ if (action) {
5429
+ options.guardrailsAction = action;
5430
+ } else {
5431
+ markInvalid();
5432
+ }
5433
+ args.shift();
5434
+ }
5435
+ }
4825
5436
  for (let i = 0; i < args.length; i += 1) {
4826
5437
  const arg = args[i];
4827
5438
  switch (arg) {
@@ -4829,8 +5440,7 @@ function parseArgs(argv, cwd) {
4829
5440
  {
4830
5441
  const next = readOptionValue(args, i);
4831
5442
  if (next === null) {
4832
- invalid = true;
4833
- options.help = true;
5443
+ markInvalid();
4834
5444
  break;
4835
5445
  }
4836
5446
  options.root = next;
@@ -4842,8 +5452,7 @@ function parseArgs(argv, cwd) {
4842
5452
  {
4843
5453
  const next = readOptionValue(args, i);
4844
5454
  if (next === null) {
4845
- invalid = true;
4846
- options.help = true;
5455
+ markInvalid();
4847
5456
  break;
4848
5457
  }
4849
5458
  options.dir = next;
@@ -4862,8 +5471,7 @@ function parseArgs(argv, cwd) {
4862
5471
  case "--format": {
4863
5472
  const next = readOptionValue(args, i);
4864
5473
  if (next === null) {
4865
- invalid = true;
4866
- options.help = true;
5474
+ markInvalid();
4867
5475
  break;
4868
5476
  }
4869
5477
  applyFormatOption(command, next, options);
@@ -4876,8 +5484,7 @@ function parseArgs(argv, cwd) {
4876
5484
  case "--fail-on": {
4877
5485
  const next = readOptionValue(args, i);
4878
5486
  if (next === null) {
4879
- invalid = true;
4880
- options.help = true;
5487
+ markInvalid();
4881
5488
  break;
4882
5489
  }
4883
5490
  if (next === "never" || next === "warning" || next === "error") {
@@ -4889,8 +5496,7 @@ function parseArgs(argv, cwd) {
4889
5496
  case "--out": {
4890
5497
  const next = readOptionValue(args, i);
4891
5498
  if (next === null) {
4892
- invalid = true;
4893
- options.help = true;
5499
+ markInvalid();
4894
5500
  break;
4895
5501
  }
4896
5502
  if (command === "doctor") {
@@ -4904,8 +5510,7 @@ function parseArgs(argv, cwd) {
4904
5510
  case "--in": {
4905
5511
  const next = readOptionValue(args, i);
4906
5512
  if (next === null) {
4907
- invalid = true;
4908
- options.help = true;
5513
+ markInvalid();
4909
5514
  break;
4910
5515
  }
4911
5516
  options.reportIn = next;
@@ -4918,14 +5523,57 @@ function parseArgs(argv, cwd) {
4918
5523
  case "--base-url": {
4919
5524
  const next = readOptionValue(args, i);
4920
5525
  if (next === null) {
4921
- invalid = true;
4922
- options.help = true;
5526
+ markInvalid();
4923
5527
  break;
4924
5528
  }
4925
5529
  options.reportBaseUrl = next;
4926
5530
  i += 1;
4927
5531
  break;
4928
5532
  }
5533
+ case "--path": {
5534
+ if (command !== "guardrails") {
5535
+ break;
5536
+ }
5537
+ const next = readOptionValue(args, i);
5538
+ if (next === null) {
5539
+ markInvalid();
5540
+ break;
5541
+ }
5542
+ options.guardrailsPaths.push(next);
5543
+ i += 1;
5544
+ break;
5545
+ }
5546
+ case "--max": {
5547
+ if (command !== "guardrails") {
5548
+ break;
5549
+ }
5550
+ const next = readOptionValue(args, i);
5551
+ if (next === null) {
5552
+ markInvalid();
5553
+ break;
5554
+ }
5555
+ const parsed = Number.parseInt(next, 10);
5556
+ if (Number.isNaN(parsed)) {
5557
+ markInvalid();
5558
+ break;
5559
+ }
5560
+ options.guardrailsMax = parsed;
5561
+ i += 1;
5562
+ break;
5563
+ }
5564
+ case "--keyword": {
5565
+ if (command !== "guardrails") {
5566
+ break;
5567
+ }
5568
+ const next = readOptionValue(args, i);
5569
+ if (next === null) {
5570
+ markInvalid();
5571
+ break;
5572
+ }
5573
+ options.guardrailsKeyword = next;
5574
+ i += 1;
5575
+ break;
5576
+ }
4929
5577
  case "--help":
4930
5578
  case "-h":
4931
5579
  options.help = true;
@@ -4934,6 +5582,9 @@ function parseArgs(argv, cwd) {
4934
5582
  break;
4935
5583
  }
4936
5584
  }
5585
+ if (command === "guardrails" && !options.help && !options.guardrailsAction) {
5586
+ markInvalid();
5587
+ }
4937
5588
  return { command, invalid, options };
4938
5589
  }
4939
5590
  function readOptionValue(args, index) {
@@ -4972,6 +5623,16 @@ function applyFormatOption(command, value, options) {
4972
5623
  options.validateFormat = value;
4973
5624
  }
4974
5625
  }
5626
+ function normalizeGuardrailsAction(value) {
5627
+ switch (value) {
5628
+ case "list":
5629
+ case "extract":
5630
+ case "check":
5631
+ return value;
5632
+ default:
5633
+ return null;
5634
+ }
5635
+ }
4975
5636
 
4976
5637
  // src/cli/main.ts
4977
5638
  async function run(argv, cwd) {
@@ -4979,7 +5640,7 @@ async function run(argv, cwd) {
4979
5640
  if (!command || options.help) {
4980
5641
  info(usage());
4981
5642
  if (invalid) {
4982
- process.exitCode = 1;
5643
+ process.exitCode = options.invalidExitCode;
4983
5644
  }
4984
5645
  return;
4985
5646
  }
@@ -5028,6 +5689,19 @@ async function run(argv, cwd) {
5028
5689
  process.exitCode = exitCode;
5029
5690
  }
5030
5691
  return;
5692
+ case "guardrails":
5693
+ {
5694
+ const resolvedRoot = await resolveRoot(options);
5695
+ const exitCode = await runGuardrails({
5696
+ root: resolvedRoot,
5697
+ ...options.guardrailsAction ? { action: options.guardrailsAction } : {},
5698
+ paths: options.guardrailsPaths,
5699
+ ...options.guardrailsMax !== void 0 ? { max: options.guardrailsMax } : {},
5700
+ ...options.guardrailsKeyword !== void 0 ? { keyword: options.guardrailsKeyword } : {}
5701
+ });
5702
+ process.exitCode = exitCode;
5703
+ }
5704
+ return;
5031
5705
  default:
5032
5706
  error(`Unknown command: ${command}`);
5033
5707
  info(usage());
@@ -5042,6 +5716,7 @@ Commands:
5042
5716
  validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
5043
5717
  report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
5044
5718
  doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
5719
+ guardrails Decision Guardrails \u306E\u62BD\u51FA/\u691C\u67FB\uFF08list|extract|check\uFF09
5045
5720
 
5046
5721
  Options:
5047
5722
  --root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
@@ -5059,6 +5734,9 @@ Options:
5059
5734
  --in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
5060
5735
  --run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
5061
5736
  --base-url <url> report: \u30D1\u30B9\u3092\u30EA\u30F3\u30AF\u5316\u3059\u308B\u57FA\u6E96URL
5737
+ --path <path> guardrails: \u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB/\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF08\u8907\u6570\u6307\u5B9A\u53EF\uFF09
5738
+ --max <number> guardrails extract: \u6700\u5927\u4EF6\u6570
5739
+ --keyword <text> guardrails list/extract: \u30AD\u30FC\u30EF\u30FC\u30C9\u30D5\u30A3\u30EB\u30BF
5062
5740
  -h, --help \u30D8\u30EB\u30D7\u8868\u793A
5063
5741
  `;
5064
5742
  }