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.
@@ -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 import_promises9 = require("fs/promises");
28
- var import_node_path11 = __toESM(require("path"), 1);
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 import_promises8 = require("fs/promises");
32
- var import_node_path10 = __toESM(require("path"), 1);
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.7".length > 0) {
1079
- return "1.0.7";
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, import_promises8.access)(target);
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 = import_node_path10.default.resolve(options.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 = import_node_path10.default.join(
1183
- import_node_path10.default.dirname(resolved),
1184
- `${import_node_path10.default.basename(resolved)}.local`
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 validateJsonAbs = import_node_path10.default.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : import_node_path10.default.resolve(root, config.output.validateJsonPath);
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 = import_node_path10.default.relative(outDirAbs, validateJsonAbs);
1268
- const inside = rel !== "" && !rel.startsWith("..") && !import_node_path10.default.isAbsolute(rel);
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) => import_node_path10.default.dirname(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 = import_node_path10.default.normalize(resolvePath(configRoot, config, "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 = import_node_path10.default.resolve(startDir);
1897
+ let current = import_node_path11.default.resolve(startDir);
1423
1898
  while (true) {
1424
- const gitPath = import_node_path10.default.join(current, ".git");
1425
- const workspacePath = import_node_path10.default.join(current, "pnpm-workspace.yaml");
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 = import_node_path10.default.dirname(current);
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 import_node_path10.default.resolve(startDir);
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 = import_node_path11.default.isAbsolute(options.outPath) ? options.outPath : import_node_path11.default.resolve(process.cwd(), options.outPath);
1478
- await (0, import_promises9.mkdir)(import_node_path11.default.dirname(outAbs), { recursive: true });
1479
- await (0, import_promises9.writeFile)(outAbs, `${output}
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/init.ts
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 import_promises10 = require("fs/promises");
1502
- var import_node_path12 = __toESM(require("path"), 1);
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 = import_node_path12.default.join(sourceRoot, relPath);
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 + import_node_path12.default.sep);
1521
- const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path12.default.sep);
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, import_node_path12.default.sep);
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, import_node_path12.default.sep);
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 = import_node_path12.default.relative(sourceRoot, file);
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 = import_node_path12.default.join(destRoot, relative);
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 = import_node_path12.default.relative(sourceRoot, file);
2100
+ const relative = import_node_path14.default.relative(sourceRoot, file);
1561
2101
  if (isExcludedRelative(relative)) {
1562
2102
  continue;
1563
2103
  }
1564
- const dest = import_node_path12.default.join(destRoot, relative);
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, import_promises10.mkdir)(import_node_path12.default.dirname(dest), { recursive: true });
1572
- await (0, import_promises10.copyFile)(file, dest);
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, import_promises10.readdir)(root, { withFileTypes: true });
2133
+ const items = await (0, import_promises11.readdir)(root, { withFileTypes: true });
1594
2134
  for (const item of items) {
1595
- const fullPath = import_node_path12.default.join(root, item.name);
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, import_promises10.access)(target);
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 = import_node_path13.default.join(assetsRoot, "root");
1626
- const qfaiAssets = import_node_path13.default.join(assetsRoot, ".qfai");
1627
- const destRoot = import_node_path13.default.resolve(options.dir);
1628
- const destQfai = import_node_path13.default.join(destRoot, ".qfai");
2165
+ const rootAssets = import_node_path15.default.join(assetsRoot, "root");
2166
+ const qfaiAssets = import_node_path15.default.join(assetsRoot, ".qfai");
2167
+ const destRoot = import_node_path15.default.resolve(options.dir);
2168
+ const destQfai = import_node_path15.default.join(destRoot, ".qfai");
1629
2169
  if (options.force) {
1630
2170
  info(
1631
2171
  "NOTE: --force \u306F .qfai/assistant/prompts/** \u306E\u307F\u4E0A\u66F8\u304D\u3057\u307E\u3059\uFF08prompts.local \u306F\u4FDD\u8B77\u3055\u308C\u3001specs/contracts \u7B49\u306F\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093\uFF09\u3002"
@@ -1671,15 +2211,15 @@ function report(copied, skipped, dryRun, label, baseDir) {
1671
2211
  info(` skipped: ${skipped.length}`);
1672
2212
  info(" skipped paths:");
1673
2213
  for (const skippedPath of skipped) {
1674
- const relative = import_node_path13.default.relative(baseDir, skippedPath);
2214
+ const relative = import_node_path15.default.relative(baseDir, skippedPath);
1675
2215
  info(` - ${relative}`);
1676
2216
  }
1677
2217
  }
1678
2218
  }
1679
2219
 
1680
2220
  // src/cli/commands/report.ts
1681
- var import_promises19 = require("fs/promises");
1682
- var import_node_path21 = __toESM(require("path"), 1);
2221
+ var import_promises20 = require("fs/promises");
2222
+ var import_node_path23 = __toESM(require("path"), 1);
1683
2223
 
1684
2224
  // src/core/normalize.ts
1685
2225
  function normalizeIssuePaths(root, issues) {
@@ -1719,12 +2259,12 @@ function normalizeValidationResult(root, result) {
1719
2259
  }
1720
2260
 
1721
2261
  // src/core/report.ts
1722
- var import_promises18 = require("fs/promises");
1723
- var import_node_path20 = __toESM(require("path"), 1);
2262
+ var import_promises19 = require("fs/promises");
2263
+ var import_node_path22 = __toESM(require("path"), 1);
1724
2264
 
1725
2265
  // src/core/contractIndex.ts
1726
- var import_promises11 = require("fs/promises");
1727
- var import_node_path14 = __toESM(require("path"), 1);
2266
+ var import_promises12 = require("fs/promises");
2267
+ var import_node_path16 = __toESM(require("path"), 1);
1728
2268
 
1729
2269
  // src/core/contractsDecl.ts
1730
2270
  var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
@@ -1746,9 +2286,9 @@ function stripContractDeclarationLines(text) {
1746
2286
  // src/core/contractIndex.ts
1747
2287
  async function buildContractIndex(root, config) {
1748
2288
  const contractsRoot = resolvePath(root, config, "contractsDir");
1749
- const uiRoot = import_node_path14.default.join(contractsRoot, "ui");
1750
- const apiRoot = import_node_path14.default.join(contractsRoot, "api");
1751
- const dbRoot = import_node_path14.default.join(contractsRoot, "db");
2289
+ const uiRoot = import_node_path16.default.join(contractsRoot, "ui");
2290
+ const apiRoot = import_node_path16.default.join(contractsRoot, "api");
2291
+ const dbRoot = import_node_path16.default.join(contractsRoot, "db");
1752
2292
  const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
1753
2293
  collectUiContractFiles(uiRoot),
1754
2294
  collectThemaContractFiles(uiRoot),
@@ -1768,7 +2308,7 @@ async function buildContractIndex(root, config) {
1768
2308
  }
1769
2309
  async function indexContractFiles(files, index) {
1770
2310
  for (const file of files) {
1771
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2311
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1772
2312
  extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
1773
2313
  }
1774
2314
  }
@@ -1893,53 +2433,11 @@ function unique3(values) {
1893
2433
  return Array.from(new Set(values));
1894
2434
  }
1895
2435
 
1896
- // src/core/parse/markdown.ts
1897
- var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
1898
- function parseHeadings(md) {
1899
- const lines = md.split(/\r?\n/);
1900
- const headings = [];
1901
- for (let i = 0; i < lines.length; i++) {
1902
- const line = lines[i] ?? "";
1903
- const match = line.match(HEADING_RE);
1904
- if (!match) continue;
1905
- const levelToken = match[1];
1906
- const title = match[2];
1907
- if (!levelToken || !title) continue;
1908
- headings.push({
1909
- level: levelToken.length,
1910
- title: title.trim(),
1911
- line: i + 1
1912
- });
1913
- }
1914
- return headings;
1915
- }
1916
- function extractH2Sections(md) {
1917
- const lines = md.split(/\r?\n/);
1918
- const headings = parseHeadings(md).filter((heading) => heading.level === 2);
1919
- const sections = /* @__PURE__ */ new Map();
1920
- for (let i = 0; i < headings.length; i++) {
1921
- const current = headings[i];
1922
- if (!current) continue;
1923
- const next = headings[i + 1];
1924
- const startLine = current.line + 1;
1925
- const endLine = (next?.line ?? lines.length + 1) - 1;
1926
- const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
1927
- sections.set(current.title.trim(), {
1928
- title: current.title.trim(),
1929
- startLine,
1930
- endLine,
1931
- body
1932
- });
1933
- }
1934
- return sections;
1935
- }
1936
-
1937
2436
  // src/core/parse/spec.ts
1938
2437
  var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
1939
2438
  var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
1940
2439
  var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
1941
2440
  var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
1942
- var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
1943
2441
  var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
1944
2442
  function parseSpec(md, file) {
1945
2443
  const headings = parseHeadings(md);
@@ -1947,15 +2445,13 @@ function parseSpec(md, file) {
1947
2445
  const specId = h1?.title.match(SPEC_ID_RE)?.[0];
1948
2446
  const sections = extractH2Sections(md);
1949
2447
  const sectionNames = new Set(Array.from(sections.keys()));
1950
- const brSection = sections.get(BR_SECTION_TITLE);
1951
- const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
1952
- const startLine = brSection?.startLine ?? 1;
2448
+ const lines = md.split(/\r?\n/);
1953
2449
  const brs = [];
1954
2450
  const brsWithoutPriority = [];
1955
2451
  const brsWithInvalidPriority = [];
1956
- for (let i = 0; i < brLines.length; i++) {
1957
- const lineText = brLines[i] ?? "";
1958
- const lineNumber = startLine + i;
2452
+ for (let i = 0; i < lines.length; i++) {
2453
+ const lineText = lines[i] ?? "";
2454
+ const lineNumber = i + 1;
1959
2455
  const validMatch = lineText.match(BR_LINE_RE);
1960
2456
  if (validMatch) {
1961
2457
  const id = validMatch[1];
@@ -2013,14 +2509,14 @@ function parseSpec(md, file) {
2013
2509
  }
2014
2510
 
2015
2511
  // src/core/validators/contracts.ts
2016
- var import_promises12 = require("fs/promises");
2017
- var import_node_path16 = __toESM(require("path"), 1);
2512
+ var import_promises13 = require("fs/promises");
2513
+ var import_node_path18 = __toESM(require("path"), 1);
2018
2514
 
2019
2515
  // src/core/contracts.ts
2020
- var import_node_path15 = __toESM(require("path"), 1);
2516
+ var import_node_path17 = __toESM(require("path"), 1);
2021
2517
  var import_yaml2 = require("yaml");
2022
2518
  function parseStructuredContract(file, text) {
2023
- const ext = import_node_path15.default.extname(file).toLowerCase();
2519
+ const ext = import_node_path17.default.extname(file).toLowerCase();
2024
2520
  if (ext === ".json") {
2025
2521
  return JSON.parse(text);
2026
2522
  }
@@ -2042,14 +2538,14 @@ async function validateContracts(root, config) {
2042
2538
  const issues = [];
2043
2539
  const contractIndex = await buildContractIndex(root, config);
2044
2540
  const contractsRoot = resolvePath(root, config, "contractsDir");
2045
- const uiRoot = import_node_path16.default.join(contractsRoot, "ui");
2541
+ const uiRoot = import_node_path18.default.join(contractsRoot, "ui");
2046
2542
  const themaIds = new Set(
2047
2543
  Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
2048
2544
  );
2049
2545
  issues.push(...await validateUiContracts(uiRoot, themaIds));
2050
2546
  issues.push(...await validateThemaContracts(uiRoot));
2051
- issues.push(...await validateApiContracts(import_node_path16.default.join(contractsRoot, "api")));
2052
- issues.push(...await validateDbContracts(import_node_path16.default.join(contractsRoot, "db")));
2547
+ issues.push(...await validateApiContracts(import_node_path18.default.join(contractsRoot, "api")));
2548
+ issues.push(...await validateDbContracts(import_node_path18.default.join(contractsRoot, "db")));
2053
2549
  issues.push(...validateDuplicateContractIds(contractIndex));
2054
2550
  return issues;
2055
2551
  }
@@ -2068,7 +2564,7 @@ async function validateUiContracts(uiRoot, themaIds) {
2068
2564
  }
2069
2565
  const issues = [];
2070
2566
  for (const file of files) {
2071
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2567
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2072
2568
  const declaredIds = extractDeclaredContractIds(text);
2073
2569
  issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
2074
2570
  let doc = null;
@@ -2122,7 +2618,7 @@ async function validateThemaContracts(uiRoot) {
2122
2618
  }
2123
2619
  const issues = [];
2124
2620
  for (const file of files) {
2125
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2621
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2126
2622
  const invalidIds = extractInvalidIds(text, [
2127
2623
  "SPEC",
2128
2624
  "BR",
@@ -2256,7 +2752,7 @@ async function validateApiContracts(apiRoot) {
2256
2752
  }
2257
2753
  const issues = [];
2258
2754
  for (const file of files) {
2259
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2755
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2260
2756
  const invalidIds = extractInvalidIds(text, [
2261
2757
  "SPEC",
2262
2758
  "BR",
@@ -2325,7 +2821,7 @@ async function validateDbContracts(dbRoot) {
2325
2821
  }
2326
2822
  const issues = [];
2327
2823
  for (const file of files) {
2328
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2824
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2329
2825
  const invalidIds = extractInvalidIds(text, [
2330
2826
  "SPEC",
2331
2827
  "BR",
@@ -2522,9 +3018,9 @@ async function validateUiAssets(assets, file, uiRoot) {
2522
3018
  );
2523
3019
  return issues;
2524
3020
  }
2525
- const packDir = import_node_path16.default.resolve(uiRoot, packValue);
2526
- const packRelative = import_node_path16.default.relative(uiRoot, packDir);
2527
- if (packRelative.startsWith("..") || import_node_path16.default.isAbsolute(packRelative)) {
3021
+ const packDir = import_node_path18.default.resolve(uiRoot, packValue);
3022
+ const packRelative = import_node_path18.default.relative(uiRoot, packDir);
3023
+ if (packRelative.startsWith("..") || import_node_path18.default.isAbsolute(packRelative)) {
2528
3024
  issues.push(
2529
3025
  issue(
2530
3026
  "QFAI-ASSET-001",
@@ -2550,7 +3046,7 @@ async function validateUiAssets(assets, file, uiRoot) {
2550
3046
  );
2551
3047
  return issues;
2552
3048
  }
2553
- const assetsYamlPath = import_node_path16.default.join(packDir, "assets.yaml");
3049
+ const assetsYamlPath = import_node_path18.default.join(packDir, "assets.yaml");
2554
3050
  if (!await exists6(assetsYamlPath)) {
2555
3051
  issues.push(
2556
3052
  issue(
@@ -2565,7 +3061,7 @@ async function validateUiAssets(assets, file, uiRoot) {
2565
3061
  }
2566
3062
  let manifest;
2567
3063
  try {
2568
- const manifestText = await (0, import_promises12.readFile)(assetsYamlPath, "utf-8");
3064
+ const manifestText = await (0, import_promises13.readFile)(assetsYamlPath, "utf-8");
2569
3065
  manifest = parseStructuredContract(assetsYamlPath, manifestText);
2570
3066
  } catch (error2) {
2571
3067
  issues.push(
@@ -2638,9 +3134,9 @@ async function validateUiAssets(assets, file, uiRoot) {
2638
3134
  );
2639
3135
  continue;
2640
3136
  }
2641
- const assetPath = import_node_path16.default.resolve(packDir, entry.path);
2642
- const assetRelative = import_node_path16.default.relative(packDir, assetPath);
2643
- if (assetRelative.startsWith("..") || import_node_path16.default.isAbsolute(assetRelative)) {
3137
+ const assetPath = import_node_path18.default.resolve(packDir, entry.path);
3138
+ const assetRelative = import_node_path18.default.relative(packDir, assetPath);
3139
+ if (assetRelative.startsWith("..") || import_node_path18.default.isAbsolute(assetRelative)) {
2644
3140
  issues.push(
2645
3141
  issue(
2646
3142
  "QFAI-ASSET-004",
@@ -2681,7 +3177,7 @@ function shouldIgnoreInvalidId(value, doc) {
2681
3177
  return false;
2682
3178
  }
2683
3179
  const normalized = packValue.replace(/\\/g, "/");
2684
- const basename = import_node_path16.default.posix.basename(normalized);
3180
+ const basename = import_node_path18.default.posix.basename(normalized);
2685
3181
  if (!basename) {
2686
3182
  return false;
2687
3183
  }
@@ -2691,7 +3187,7 @@ function isSafeRelativePath(value) {
2691
3187
  if (!value) {
2692
3188
  return false;
2693
3189
  }
2694
- if (import_node_path16.default.isAbsolute(value)) {
3190
+ if (import_node_path18.default.isAbsolute(value)) {
2695
3191
  return false;
2696
3192
  }
2697
3193
  const normalized = value.replace(/\\/g, "/");
@@ -2706,7 +3202,7 @@ function isSafeRelativePath(value) {
2706
3202
  }
2707
3203
  async function exists6(target) {
2708
3204
  try {
2709
- await (0, import_promises12.access)(target);
3205
+ await (0, import_promises13.access)(target);
2710
3206
  return true;
2711
3207
  } catch {
2712
3208
  return false;
@@ -2741,8 +3237,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
2741
3237
  }
2742
3238
 
2743
3239
  // src/core/validators/delta.ts
2744
- var import_promises13 = require("fs/promises");
2745
- var import_node_path17 = __toESM(require("path"), 1);
3240
+ var import_promises14 = require("fs/promises");
3241
+ var import_node_path19 = __toESM(require("path"), 1);
2746
3242
  async function validateDeltas(root, config) {
2747
3243
  const specsRoot = resolvePath(root, config, "specsDir");
2748
3244
  const packs = await collectSpecPackDirs(specsRoot);
@@ -2751,9 +3247,9 @@ async function validateDeltas(root, config) {
2751
3247
  }
2752
3248
  const issues = [];
2753
3249
  for (const pack of packs) {
2754
- const deltaPath = import_node_path17.default.join(pack, "delta.md");
3250
+ const deltaPath = import_node_path19.default.join(pack, "delta.md");
2755
3251
  try {
2756
- await (0, import_promises13.readFile)(deltaPath, "utf-8");
3252
+ await (0, import_promises14.readFile)(deltaPath, "utf-8");
2757
3253
  } catch (error2) {
2758
3254
  if (isMissingFileError2(error2)) {
2759
3255
  issues.push(
@@ -2804,8 +3300,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
2804
3300
  }
2805
3301
 
2806
3302
  // src/core/validators/ids.ts
2807
- var import_promises14 = require("fs/promises");
2808
- var import_node_path18 = __toESM(require("path"), 1);
3303
+ var import_promises15 = require("fs/promises");
3304
+ var import_node_path20 = __toESM(require("path"), 1);
2809
3305
  var SC_TAG_RE3 = /^SC-\d{4}$/;
2810
3306
  async function validateDefinedIds(root, config) {
2811
3307
  const issues = [];
@@ -2840,7 +3336,7 @@ async function validateDefinedIds(root, config) {
2840
3336
  }
2841
3337
  async function collectSpecDefinitionIds(files, out) {
2842
3338
  for (const file of files) {
2843
- const text = await (0, import_promises14.readFile)(file, "utf-8");
3339
+ const text = await (0, import_promises15.readFile)(file, "utf-8");
2844
3340
  const parsed = parseSpec(text, file);
2845
3341
  if (parsed.specId) {
2846
3342
  recordId(out, parsed.specId, file);
@@ -2850,7 +3346,7 @@ async function collectSpecDefinitionIds(files, out) {
2850
3346
  }
2851
3347
  async function collectScenarioDefinitionIds(files, out) {
2852
3348
  for (const file of files) {
2853
- const text = await (0, import_promises14.readFile)(file, "utf-8");
3349
+ const text = await (0, import_promises15.readFile)(file, "utf-8");
2854
3350
  const { document, errors } = parseScenarioDocument(text, file);
2855
3351
  if (!document || errors.length > 0) {
2856
3352
  continue;
@@ -2871,7 +3367,7 @@ function recordId(out, id, file) {
2871
3367
  }
2872
3368
  function formatFileList(files, root) {
2873
3369
  return files.map((file) => {
2874
- const relative = import_node_path18.default.relative(root, file);
3370
+ const relative = import_node_path20.default.relative(root, file);
2875
3371
  return relative.length > 0 ? relative : file;
2876
3372
  }).join(", ");
2877
3373
  }
@@ -2929,8 +3425,8 @@ async function validatePromptsIntegrity(root, config) {
2929
3425
  }
2930
3426
 
2931
3427
  // src/core/validators/scenario.ts
2932
- var import_promises15 = require("fs/promises");
2933
- var import_node_path19 = __toESM(require("path"), 1);
3428
+ var import_promises16 = require("fs/promises");
3429
+ var import_node_path21 = __toESM(require("path"), 1);
2934
3430
  var GIVEN_PATTERN = /\bGiven\b/;
2935
3431
  var WHEN_PATTERN = /\bWhen\b/;
2936
3432
  var THEN_PATTERN = /\bThen\b/;
@@ -2953,7 +3449,7 @@ async function validateScenarios(root, config) {
2953
3449
  }
2954
3450
  const issues = [];
2955
3451
  for (const entry of entries) {
2956
- const legacyScenarioPath = import_node_path19.default.join(entry.dir, "scenario.md");
3452
+ const legacyScenarioPath = import_node_path21.default.join(entry.dir, "scenario.md");
2957
3453
  if (await fileExists(legacyScenarioPath)) {
2958
3454
  issues.push(
2959
3455
  issue4(
@@ -2967,7 +3463,7 @@ async function validateScenarios(root, config) {
2967
3463
  }
2968
3464
  let text;
2969
3465
  try {
2970
- text = await (0, import_promises15.readFile)(entry.scenarioPath, "utf-8");
3466
+ text = await (0, import_promises16.readFile)(entry.scenarioPath, "utf-8");
2971
3467
  } catch (error2) {
2972
3468
  if (isMissingFileError3(error2)) {
2973
3469
  issues.push(
@@ -3142,7 +3638,7 @@ function isMissingFileError3(error2) {
3142
3638
  }
3143
3639
  async function fileExists(target) {
3144
3640
  try {
3145
- await (0, import_promises15.access)(target);
3641
+ await (0, import_promises16.access)(target);
3146
3642
  return true;
3147
3643
  } catch {
3148
3644
  return false;
@@ -3150,7 +3646,7 @@ async function fileExists(target) {
3150
3646
  }
3151
3647
 
3152
3648
  // src/core/validators/spec.ts
3153
- var import_promises16 = require("fs/promises");
3649
+ var import_promises17 = require("fs/promises");
3154
3650
  async function validateSpecs(root, config) {
3155
3651
  const specsRoot = resolvePath(root, config, "specsDir");
3156
3652
  const entries = await collectSpecEntries(specsRoot);
@@ -3171,7 +3667,7 @@ async function validateSpecs(root, config) {
3171
3667
  for (const entry of entries) {
3172
3668
  let text;
3173
3669
  try {
3174
- text = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
3670
+ text = await (0, import_promises17.readFile)(entry.specPath, "utf-8");
3175
3671
  } catch (error2) {
3176
3672
  if (isMissingFileError4(error2)) {
3177
3673
  issues.push(
@@ -3325,7 +3821,7 @@ function isMissingFileError4(error2) {
3325
3821
  }
3326
3822
 
3327
3823
  // src/core/validators/traceability.ts
3328
- var import_promises17 = require("fs/promises");
3824
+ var import_promises18 = require("fs/promises");
3329
3825
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
3330
3826
  var BR_TAG_RE2 = /^BR-\d{4}$/;
3331
3827
  async function validateTraceability(root, config) {
@@ -3345,7 +3841,7 @@ async function validateTraceability(root, config) {
3345
3841
  const contractIndex = await buildContractIndex(root, config);
3346
3842
  const contractIds = contractIndex.ids;
3347
3843
  for (const file of specFiles) {
3348
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3844
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3349
3845
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
3350
3846
  const parsed = parseSpec(text, file);
3351
3847
  if (parsed.specId) {
@@ -3418,7 +3914,7 @@ async function validateTraceability(root, config) {
3418
3914
  }
3419
3915
  }
3420
3916
  for (const file of scenarioFiles) {
3421
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3917
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3422
3918
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
3423
3919
  const scenarioContractRefs = parseContractRefs(text, {
3424
3920
  allowCommentPrefix: true
@@ -3740,7 +4236,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
3740
4236
  const pattern = buildIdPattern(Array.from(upstreamIds));
3741
4237
  let found = false;
3742
4238
  for (const file of targetFiles) {
3743
- const text = await (0, import_promises17.readFile)(file, "utf-8");
4239
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3744
4240
  if (pattern.test(text)) {
3745
4241
  found = true;
3746
4242
  break;
@@ -3839,16 +4335,17 @@ var ID_PREFIXES2 = [
3839
4335
  "DB",
3840
4336
  "THEMA"
3841
4337
  ];
4338
+ var REPORT_GUARDRAILS_MAX = 20;
3842
4339
  async function createReportData(root, validation, configResult) {
3843
- const resolvedRoot = import_node_path20.default.resolve(root);
4340
+ const resolvedRoot = import_node_path22.default.resolve(root);
3844
4341
  const resolved = configResult ?? await loadConfig(resolvedRoot);
3845
4342
  const config = resolved.config;
3846
4343
  const configPath = resolved.configPath;
3847
4344
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
3848
4345
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
3849
- const apiRoot = import_node_path20.default.join(contractsRoot, "api");
3850
- const uiRoot = import_node_path20.default.join(contractsRoot, "ui");
3851
- const dbRoot = import_node_path20.default.join(contractsRoot, "db");
4346
+ const apiRoot = import_node_path22.default.join(contractsRoot, "api");
4347
+ const uiRoot = import_node_path22.default.join(contractsRoot, "ui");
4348
+ const dbRoot = import_node_path22.default.join(contractsRoot, "db");
3852
4349
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
3853
4350
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
3854
4351
  const specFiles = await collectSpecFiles(specsRoot);
@@ -3907,6 +4404,27 @@ async function createReportData(root, validation, configResult) {
3907
4404
  const scSourceRecord = mapToSortedRecord(
3908
4405
  normalizeScSources(resolvedRoot, scSources)
3909
4406
  );
4407
+ const guardrailsLoad = await loadDecisionGuardrails(resolvedRoot, {
4408
+ specsRoot
4409
+ });
4410
+ const guardrailsAll = sortDecisionGuardrails(
4411
+ normalizeDecisionGuardrails(guardrailsLoad.entries)
4412
+ );
4413
+ const guardrailsDisplay = guardrailsAll.slice(0, REPORT_GUARDRAILS_MAX);
4414
+ const guardrailsByType = { nonGoal: 0, notNow: 0, tradeOff: 0 };
4415
+ for (const item of guardrailsAll) {
4416
+ if (item.type === "non-goal") {
4417
+ guardrailsByType.nonGoal += 1;
4418
+ } else if (item.type === "not-now") {
4419
+ guardrailsByType.notNow += 1;
4420
+ } else if (item.type === "trade-off") {
4421
+ guardrailsByType.tradeOff += 1;
4422
+ }
4423
+ }
4424
+ const guardrailsErrors = guardrailsLoad.errors.map((item) => ({
4425
+ path: toRelativePath(resolvedRoot, item.path),
4426
+ message: item.message
4427
+ }));
3910
4428
  const version = await resolveToolVersion();
3911
4429
  const displayRoot = toRelativePath(resolvedRoot, resolvedRoot);
3912
4430
  const displayConfigPath = toRelativePath(resolvedRoot, configPath);
@@ -3954,6 +4472,34 @@ async function createReportData(root, validation, configResult) {
3954
4472
  specToContracts: specToContractsRecord
3955
4473
  }
3956
4474
  },
4475
+ guardrails: {
4476
+ total: guardrailsAll.length,
4477
+ max: REPORT_GUARDRAILS_MAX,
4478
+ truncated: guardrailsAll.length > guardrailsDisplay.length,
4479
+ byType: guardrailsByType,
4480
+ items: guardrailsDisplay.map((item) => {
4481
+ const entry = {
4482
+ id: item.id,
4483
+ type: item.type,
4484
+ guardrail: item.guardrail,
4485
+ source: {
4486
+ file: toRelativePath(resolvedRoot, item.source.file),
4487
+ line: item.source.line
4488
+ }
4489
+ };
4490
+ if (item.rationale) {
4491
+ entry.rationale = item.rationale;
4492
+ }
4493
+ if (item.reconsider) {
4494
+ entry.reconsider = item.reconsider;
4495
+ }
4496
+ if (item.related) {
4497
+ entry.related = item.related;
4498
+ }
4499
+ return entry;
4500
+ }),
4501
+ scanErrors: guardrailsErrors
4502
+ },
3957
4503
  issues: normalizedValidation.issues
3958
4504
  };
3959
4505
  }
@@ -4049,6 +4595,7 @@ function formatReportMarkdown(data, options = {}) {
4049
4595
  lines.push("");
4050
4596
  lines.push("- [Compatibility Issues](#compatibility-issues)");
4051
4597
  lines.push("- [Change Issues](#change-issues)");
4598
+ lines.push("- [Decision Guardrails](#decision-guardrails)");
4052
4599
  lines.push("- [IDs](#ids)");
4053
4600
  lines.push("- [Traceability](#traceability)");
4054
4601
  lines.push("");
@@ -4140,6 +4687,49 @@ function formatReportMarkdown(data, options = {}) {
4140
4687
  lines.push("### Issues");
4141
4688
  lines.push("");
4142
4689
  lines.push(...formatIssueCards(issuesByCategory.change));
4690
+ lines.push("## Decision Guardrails");
4691
+ lines.push("");
4692
+ lines.push(`- total: ${data.guardrails.total}`);
4693
+ lines.push(
4694
+ `- types: non-goal ${data.guardrails.byType.nonGoal} / not-now ${data.guardrails.byType.notNow} / trade-off ${data.guardrails.byType.tradeOff}`
4695
+ );
4696
+ if (data.guardrails.truncated) {
4697
+ lines.push(`- truncated: true (max=${data.guardrails.max})`);
4698
+ }
4699
+ if (data.guardrails.scanErrors.length > 0) {
4700
+ lines.push(`- scanErrors: ${data.guardrails.scanErrors.length}`);
4701
+ }
4702
+ lines.push("");
4703
+ if (data.guardrails.items.length === 0) {
4704
+ lines.push("- (none)");
4705
+ } else {
4706
+ for (const item of data.guardrails.items) {
4707
+ lines.push(`- [${item.id}][${item.type}] ${item.guardrail}`);
4708
+ lines.push(
4709
+ ` - source: ${formatPathWithLine(item.source.file, { line: item.source.line }, baseUrl)}`
4710
+ );
4711
+ if (item.rationale) {
4712
+ lines.push(` - Rationale: ${item.rationale}`);
4713
+ }
4714
+ if (item.reconsider) {
4715
+ lines.push(` - Reconsider: ${item.reconsider}`);
4716
+ }
4717
+ if (item.related) {
4718
+ lines.push(` - Related: ${item.related}`);
4719
+ }
4720
+ }
4721
+ }
4722
+ if (data.guardrails.scanErrors.length > 0) {
4723
+ lines.push("");
4724
+ lines.push("### Scan errors");
4725
+ lines.push("");
4726
+ for (const errorItem of data.guardrails.scanErrors) {
4727
+ lines.push(
4728
+ `- ${formatPathLink(errorItem.path, baseUrl)}: ${errorItem.message}`
4729
+ );
4730
+ }
4731
+ }
4732
+ lines.push("");
4143
4733
  lines.push("## IDs");
4144
4734
  lines.push("");
4145
4735
  lines.push(formatIdLine("SPEC", data.ids.spec));
@@ -4330,7 +4920,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
4330
4920
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
4331
4921
  }
4332
4922
  for (const file of specFiles) {
4333
- const text = await (0, import_promises18.readFile)(file, "utf-8");
4923
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
4334
4924
  const parsed = parseSpec(text, file);
4335
4925
  const specKey = parsed.specId;
4336
4926
  if (!specKey) {
@@ -4372,7 +4962,7 @@ async function collectIds(files) {
4372
4962
  THEMA: /* @__PURE__ */ new Set()
4373
4963
  };
4374
4964
  for (const file of files) {
4375
- const text = await (0, import_promises18.readFile)(file, "utf-8");
4965
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
4376
4966
  for (const prefix of ID_PREFIXES2) {
4377
4967
  const ids = extractIds(text, prefix);
4378
4968
  ids.forEach((id) => result[prefix].add(id));
@@ -4391,7 +4981,7 @@ async function collectIds(files) {
4391
4981
  async function collectUpstreamIds(files) {
4392
4982
  const ids = /* @__PURE__ */ new Set();
4393
4983
  for (const file of files) {
4394
- const text = await (0, import_promises18.readFile)(file, "utf-8");
4984
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
4395
4985
  extractAllIds(text).forEach((id) => ids.add(id));
4396
4986
  }
4397
4987
  return ids;
@@ -4412,7 +5002,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
4412
5002
  }
4413
5003
  const pattern = buildIdPattern2(Array.from(upstreamIds));
4414
5004
  for (const file of targetFiles) {
4415
- const text = await (0, import_promises18.readFile)(file, "utf-8");
5005
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
4416
5006
  if (pattern.test(text)) {
4417
5007
  return true;
4418
5008
  }
@@ -4549,7 +5139,7 @@ function warnIfTruncated(scan, context) {
4549
5139
 
4550
5140
  // src/cli/commands/report.ts
4551
5141
  async function runReport(options) {
4552
- const root = import_node_path21.default.resolve(options.root);
5142
+ const root = import_node_path23.default.resolve(options.root);
4553
5143
  const configResult = await loadConfig(root);
4554
5144
  let validation;
4555
5145
  if (options.runValidate) {
@@ -4566,7 +5156,7 @@ async function runReport(options) {
4566
5156
  validation = normalized;
4567
5157
  } else {
4568
5158
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
4569
- const inputPath = import_node_path21.default.isAbsolute(input) ? input : import_node_path21.default.resolve(root, input);
5159
+ const inputPath = import_node_path23.default.isAbsolute(input) ? input : import_node_path23.default.resolve(root, input);
4570
5160
  try {
4571
5161
  validation = await readValidationResult(inputPath);
4572
5162
  } catch (err) {
@@ -4593,11 +5183,11 @@ async function runReport(options) {
4593
5183
  warnIfTruncated(data.traceability.testFiles, "report");
4594
5184
  const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
4595
5185
  const outRoot = resolvePath(root, configResult.config, "outDir");
4596
- const defaultOut = options.format === "json" ? import_node_path21.default.join(outRoot, "report.json") : import_node_path21.default.join(outRoot, "report.md");
5186
+ const defaultOut = options.format === "json" ? import_node_path23.default.join(outRoot, "report.json") : import_node_path23.default.join(outRoot, "report.md");
4597
5187
  const out = options.outPath ?? defaultOut;
4598
- const outPath = import_node_path21.default.isAbsolute(out) ? out : import_node_path21.default.resolve(root, out);
4599
- await (0, import_promises19.mkdir)(import_node_path21.default.dirname(outPath), { recursive: true });
4600
- await (0, import_promises19.writeFile)(outPath, `${output}
5188
+ const outPath = import_node_path23.default.isAbsolute(out) ? out : import_node_path23.default.resolve(root, out);
5189
+ await (0, import_promises20.mkdir)(import_node_path23.default.dirname(outPath), { recursive: true });
5190
+ await (0, import_promises20.writeFile)(outPath, `${output}
4601
5191
  `, "utf-8");
4602
5192
  info(
4603
5193
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -4605,7 +5195,7 @@ async function runReport(options) {
4605
5195
  info(`wrote report: ${outPath}`);
4606
5196
  }
4607
5197
  async function readValidationResult(inputPath) {
4608
- const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
5198
+ const raw = await (0, import_promises20.readFile)(inputPath, "utf-8");
4609
5199
  const parsed = JSON.parse(raw);
4610
5200
  if (!isValidationResult(parsed)) {
4611
5201
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -4661,15 +5251,15 @@ function isMissingFileError5(error2) {
4661
5251
  return record2.code === "ENOENT";
4662
5252
  }
4663
5253
  async function writeValidationResult(root, outputPath, result) {
4664
- const abs = import_node_path21.default.isAbsolute(outputPath) ? outputPath : import_node_path21.default.resolve(root, outputPath);
4665
- await (0, import_promises19.mkdir)(import_node_path21.default.dirname(abs), { recursive: true });
4666
- await (0, import_promises19.writeFile)(abs, `${JSON.stringify(result, null, 2)}
5254
+ const abs = import_node_path23.default.isAbsolute(outputPath) ? outputPath : import_node_path23.default.resolve(root, outputPath);
5255
+ await (0, import_promises20.mkdir)(import_node_path23.default.dirname(abs), { recursive: true });
5256
+ await (0, import_promises20.writeFile)(abs, `${JSON.stringify(result, null, 2)}
4667
5257
  `, "utf-8");
4668
5258
  }
4669
5259
 
4670
5260
  // src/cli/commands/validate.ts
4671
- var import_promises20 = require("fs/promises");
4672
- var import_node_path22 = __toESM(require("path"), 1);
5261
+ var import_promises21 = require("fs/promises");
5262
+ var import_node_path24 = __toESM(require("path"), 1);
4673
5263
 
4674
5264
  // src/cli/lib/failOn.ts
4675
5265
  function shouldFail(result, failOn) {
@@ -4684,7 +5274,7 @@ function shouldFail(result, failOn) {
4684
5274
 
4685
5275
  // src/cli/commands/validate.ts
4686
5276
  async function runValidate(options) {
4687
- const root = import_node_path22.default.resolve(options.root);
5277
+ const root = import_node_path24.default.resolve(options.root);
4688
5278
  const configResult = await loadConfig(root);
4689
5279
  const result = await validateProject(root, configResult);
4690
5280
  const normalized = normalizeValidationResult(root, result);
@@ -4809,12 +5399,12 @@ function issueKey(issue7) {
4809
5399
  }
4810
5400
  async function emitJson(result, root, jsonPath) {
4811
5401
  const abs = resolveJsonPath(root, jsonPath);
4812
- await (0, import_promises20.mkdir)(import_node_path22.default.dirname(abs), { recursive: true });
4813
- await (0, import_promises20.writeFile)(abs, `${JSON.stringify(result, null, 2)}
5402
+ await (0, import_promises21.mkdir)(import_node_path24.default.dirname(abs), { recursive: true });
5403
+ await (0, import_promises21.writeFile)(abs, `${JSON.stringify(result, null, 2)}
4814
5404
  `, "utf-8");
4815
5405
  }
4816
5406
  function resolveJsonPath(root, jsonPath) {
4817
- return import_node_path22.default.isAbsolute(jsonPath) ? jsonPath : import_node_path22.default.resolve(root, jsonPath);
5407
+ return import_node_path24.default.isAbsolute(jsonPath) ? jsonPath : import_node_path24.default.resolve(root, jsonPath);
4818
5408
  }
4819
5409
  var GITHUB_ANNOTATION_LIMIT = 100;
4820
5410
 
@@ -4832,7 +5422,9 @@ function parseArgs(argv, cwd) {
4832
5422
  doctorFormat: "text",
4833
5423
  validateFormat: "text",
4834
5424
  strict: false,
4835
- help: false
5425
+ guardrailsPaths: [],
5426
+ help: false,
5427
+ invalidExitCode: 1
4836
5428
  };
4837
5429
  const args = [...argv];
4838
5430
  let command = args.shift() ?? null;
@@ -4841,6 +5433,25 @@ function parseArgs(argv, cwd) {
4841
5433
  options.help = true;
4842
5434
  command = null;
4843
5435
  }
5436
+ const markInvalid = () => {
5437
+ invalid = true;
5438
+ options.help = true;
5439
+ if (command === "guardrails") {
5440
+ options.invalidExitCode = 2;
5441
+ }
5442
+ };
5443
+ if (command === "guardrails") {
5444
+ const candidate = args[0];
5445
+ if (candidate && !candidate.startsWith("--")) {
5446
+ const action = normalizeGuardrailsAction(candidate);
5447
+ if (action) {
5448
+ options.guardrailsAction = action;
5449
+ } else {
5450
+ markInvalid();
5451
+ }
5452
+ args.shift();
5453
+ }
5454
+ }
4844
5455
  for (let i = 0; i < args.length; i += 1) {
4845
5456
  const arg = args[i];
4846
5457
  switch (arg) {
@@ -4848,8 +5459,7 @@ function parseArgs(argv, cwd) {
4848
5459
  {
4849
5460
  const next = readOptionValue(args, i);
4850
5461
  if (next === null) {
4851
- invalid = true;
4852
- options.help = true;
5462
+ markInvalid();
4853
5463
  break;
4854
5464
  }
4855
5465
  options.root = next;
@@ -4861,8 +5471,7 @@ function parseArgs(argv, cwd) {
4861
5471
  {
4862
5472
  const next = readOptionValue(args, i);
4863
5473
  if (next === null) {
4864
- invalid = true;
4865
- options.help = true;
5474
+ markInvalid();
4866
5475
  break;
4867
5476
  }
4868
5477
  options.dir = next;
@@ -4881,8 +5490,7 @@ function parseArgs(argv, cwd) {
4881
5490
  case "--format": {
4882
5491
  const next = readOptionValue(args, i);
4883
5492
  if (next === null) {
4884
- invalid = true;
4885
- options.help = true;
5493
+ markInvalid();
4886
5494
  break;
4887
5495
  }
4888
5496
  applyFormatOption(command, next, options);
@@ -4895,8 +5503,7 @@ function parseArgs(argv, cwd) {
4895
5503
  case "--fail-on": {
4896
5504
  const next = readOptionValue(args, i);
4897
5505
  if (next === null) {
4898
- invalid = true;
4899
- options.help = true;
5506
+ markInvalid();
4900
5507
  break;
4901
5508
  }
4902
5509
  if (next === "never" || next === "warning" || next === "error") {
@@ -4908,8 +5515,7 @@ function parseArgs(argv, cwd) {
4908
5515
  case "--out": {
4909
5516
  const next = readOptionValue(args, i);
4910
5517
  if (next === null) {
4911
- invalid = true;
4912
- options.help = true;
5518
+ markInvalid();
4913
5519
  break;
4914
5520
  }
4915
5521
  if (command === "doctor") {
@@ -4923,8 +5529,7 @@ function parseArgs(argv, cwd) {
4923
5529
  case "--in": {
4924
5530
  const next = readOptionValue(args, i);
4925
5531
  if (next === null) {
4926
- invalid = true;
4927
- options.help = true;
5532
+ markInvalid();
4928
5533
  break;
4929
5534
  }
4930
5535
  options.reportIn = next;
@@ -4937,14 +5542,57 @@ function parseArgs(argv, cwd) {
4937
5542
  case "--base-url": {
4938
5543
  const next = readOptionValue(args, i);
4939
5544
  if (next === null) {
4940
- invalid = true;
4941
- options.help = true;
5545
+ markInvalid();
4942
5546
  break;
4943
5547
  }
4944
5548
  options.reportBaseUrl = next;
4945
5549
  i += 1;
4946
5550
  break;
4947
5551
  }
5552
+ case "--path": {
5553
+ if (command !== "guardrails") {
5554
+ break;
5555
+ }
5556
+ const next = readOptionValue(args, i);
5557
+ if (next === null) {
5558
+ markInvalid();
5559
+ break;
5560
+ }
5561
+ options.guardrailsPaths.push(next);
5562
+ i += 1;
5563
+ break;
5564
+ }
5565
+ case "--max": {
5566
+ if (command !== "guardrails") {
5567
+ break;
5568
+ }
5569
+ const next = readOptionValue(args, i);
5570
+ if (next === null) {
5571
+ markInvalid();
5572
+ break;
5573
+ }
5574
+ const parsed = Number.parseInt(next, 10);
5575
+ if (Number.isNaN(parsed)) {
5576
+ markInvalid();
5577
+ break;
5578
+ }
5579
+ options.guardrailsMax = parsed;
5580
+ i += 1;
5581
+ break;
5582
+ }
5583
+ case "--keyword": {
5584
+ if (command !== "guardrails") {
5585
+ break;
5586
+ }
5587
+ const next = readOptionValue(args, i);
5588
+ if (next === null) {
5589
+ markInvalid();
5590
+ break;
5591
+ }
5592
+ options.guardrailsKeyword = next;
5593
+ i += 1;
5594
+ break;
5595
+ }
4948
5596
  case "--help":
4949
5597
  case "-h":
4950
5598
  options.help = true;
@@ -4953,6 +5601,9 @@ function parseArgs(argv, cwd) {
4953
5601
  break;
4954
5602
  }
4955
5603
  }
5604
+ if (command === "guardrails" && !options.help && !options.guardrailsAction) {
5605
+ markInvalid();
5606
+ }
4956
5607
  return { command, invalid, options };
4957
5608
  }
4958
5609
  function readOptionValue(args, index) {
@@ -4991,6 +5642,16 @@ function applyFormatOption(command, value, options) {
4991
5642
  options.validateFormat = value;
4992
5643
  }
4993
5644
  }
5645
+ function normalizeGuardrailsAction(value) {
5646
+ switch (value) {
5647
+ case "list":
5648
+ case "extract":
5649
+ case "check":
5650
+ return value;
5651
+ default:
5652
+ return null;
5653
+ }
5654
+ }
4994
5655
 
4995
5656
  // src/cli/main.ts
4996
5657
  async function run(argv, cwd) {
@@ -4998,7 +5659,7 @@ async function run(argv, cwd) {
4998
5659
  if (!command || options.help) {
4999
5660
  info(usage());
5000
5661
  if (invalid) {
5001
- process.exitCode = 1;
5662
+ process.exitCode = options.invalidExitCode;
5002
5663
  }
5003
5664
  return;
5004
5665
  }
@@ -5047,6 +5708,19 @@ async function run(argv, cwd) {
5047
5708
  process.exitCode = exitCode;
5048
5709
  }
5049
5710
  return;
5711
+ case "guardrails":
5712
+ {
5713
+ const resolvedRoot = await resolveRoot(options);
5714
+ const exitCode = await runGuardrails({
5715
+ root: resolvedRoot,
5716
+ ...options.guardrailsAction ? { action: options.guardrailsAction } : {},
5717
+ paths: options.guardrailsPaths,
5718
+ ...options.guardrailsMax !== void 0 ? { max: options.guardrailsMax } : {},
5719
+ ...options.guardrailsKeyword !== void 0 ? { keyword: options.guardrailsKeyword } : {}
5720
+ });
5721
+ process.exitCode = exitCode;
5722
+ }
5723
+ return;
5050
5724
  default:
5051
5725
  error(`Unknown command: ${command}`);
5052
5726
  info(usage());
@@ -5061,6 +5735,7 @@ Commands:
5061
5735
  validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
5062
5736
  report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
5063
5737
  doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
5738
+ guardrails Decision Guardrails \u306E\u62BD\u51FA/\u691C\u67FB\uFF08list|extract|check\uFF09
5064
5739
 
5065
5740
  Options:
5066
5741
  --root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
@@ -5078,6 +5753,9 @@ Options:
5078
5753
  --in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
5079
5754
  --run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
5080
5755
  --base-url <url> report: \u30D1\u30B9\u3092\u30EA\u30F3\u30AF\u5316\u3059\u308B\u57FA\u6E96URL
5756
+ --path <path> guardrails: \u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB/\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF08\u8907\u6570\u6307\u5B9A\u53EF\uFF09
5757
+ --max <number> guardrails extract: \u6700\u5927\u4EF6\u6570
5758
+ --keyword <text> guardrails list/extract: \u30AD\u30FC\u30EF\u30FC\u30C9\u30D5\u30A3\u30EB\u30BF
5081
5759
  -h, --help \u30D8\u30EB\u30D7\u8868\u793A
5082
5760
  `;
5083
5761
  }