qfai 1.0.6 → 1.1.0

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