openfeel 1.1.7 → 1.2.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 (78) hide show
  1. package/README.en.md +13 -5
  2. package/README.md +8 -2
  3. package/README.zh-CN.md +13 -5
  4. package/dist/cli/index.d.ts.map +1 -1
  5. package/dist/cli/index.js +2 -0
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/commands/config.d.ts.map +1 -1
  8. package/dist/commands/config.js +23 -4
  9. package/dist/commands/config.js.map +1 -1
  10. package/dist/commands/flow.d.ts.map +1 -1
  11. package/dist/commands/flow.js +279 -6
  12. package/dist/commands/flow.js.map +1 -1
  13. package/dist/commands/plan.d.ts.map +1 -1
  14. package/dist/commands/plan.js +12 -5
  15. package/dist/commands/plan.js.map +1 -1
  16. package/dist/commands/req.d.ts +14 -0
  17. package/dist/commands/req.d.ts.map +1 -0
  18. package/dist/commands/req.js +562 -0
  19. package/dist/commands/req.js.map +1 -0
  20. package/dist/core/config.js +2 -2
  21. package/dist/core/flow-archive/archive-file.d.ts +35 -0
  22. package/dist/core/flow-archive/archive-file.d.ts.map +1 -0
  23. package/dist/core/flow-archive/archive-file.js +3 -0
  24. package/dist/core/flow-archive/archive-file.js.map +1 -0
  25. package/dist/core/flow-archive/archive-health.d.ts +34 -0
  26. package/dist/core/flow-archive/archive-health.d.ts.map +1 -0
  27. package/dist/core/flow-archive/archive-health.js +76 -0
  28. package/dist/core/flow-archive/archive-health.js.map +1 -0
  29. package/dist/core/flow-archive/archive-ops.d.ts +68 -0
  30. package/dist/core/flow-archive/archive-ops.d.ts.map +1 -0
  31. package/dist/core/flow-archive/archive-ops.js +184 -0
  32. package/dist/core/flow-archive/archive-ops.js.map +1 -0
  33. package/dist/core/flow-archive/grouping.d.ts +58 -0
  34. package/dist/core/flow-archive/grouping.d.ts.map +1 -0
  35. package/dist/core/flow-archive/grouping.js +153 -0
  36. package/dist/core/flow-archive/grouping.js.map +1 -0
  37. package/dist/core/flow-manager.d.ts +7 -0
  38. package/dist/core/flow-manager.d.ts.map +1 -1
  39. package/dist/core/flow-manager.js +54 -13
  40. package/dist/core/flow-manager.js.map +1 -1
  41. package/dist/core/i18n-data/en.d.ts +1 -0
  42. package/dist/core/i18n-data/en.d.ts.map +1 -1
  43. package/dist/core/i18n-data/en.js +134 -0
  44. package/dist/core/i18n-data/en.js.map +1 -1
  45. package/dist/core/i18n-data/zh-CN.d.ts +1 -0
  46. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  47. package/dist/core/i18n-data/zh-CN.js +135 -0
  48. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  49. package/dist/core/i18n.js +4 -4
  50. package/dist/core/i18n.js.map +1 -1
  51. package/dist/core/plan/path.d.ts +17 -0
  52. package/dist/core/plan/path.d.ts.map +1 -1
  53. package/dist/core/plan/path.js +31 -0
  54. package/dist/core/plan/path.js.map +1 -1
  55. package/dist/core/req/aggregate.d.ts +61 -0
  56. package/dist/core/req/aggregate.d.ts.map +1 -0
  57. package/dist/core/req/aggregate.js +134 -0
  58. package/dist/core/req/aggregate.js.map +1 -0
  59. package/dist/core/req/body-file.d.ts +16 -0
  60. package/dist/core/req/body-file.d.ts.map +1 -0
  61. package/dist/core/req/body-file.js +80 -0
  62. package/dist/core/req/body-file.js.map +1 -0
  63. package/dist/core/req/requirement-ops.d.ts +74 -0
  64. package/dist/core/req/requirement-ops.d.ts.map +1 -0
  65. package/dist/core/req/requirement-ops.js +290 -0
  66. package/dist/core/req/requirement-ops.js.map +1 -0
  67. package/dist/core/req/types.d.ts +40 -0
  68. package/dist/core/req/types.d.ts.map +1 -0
  69. package/dist/core/req/types.js +13 -0
  70. package/dist/core/req/types.js.map +1 -0
  71. package/dist/core/setup.js +4 -4
  72. package/dist/core/template-loader.d.ts.map +1 -1
  73. package/dist/core/template-loader.js +305 -12
  74. package/dist/core/template-loader.js.map +1 -1
  75. package/dist/core/update.d.ts.map +1 -1
  76. package/dist/core/update.js +91 -8
  77. package/dist/core/update.js.map +1 -1
  78. package/package.json +1 -1
@@ -3,13 +3,18 @@ import { existsSync, copyFileSync, readFileSync, rmSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { FlowManager, isFlowConcurrentError, normalizeAgentName, detectFillState, PHASE_PATH_MAX_DEPTH } from '../core/flow-manager.js';
5
5
  import { PipelinePhaseSchema, PIPELINE_PHASES } from '../core/pipeline-schema.js';
6
- import { validateStageId, suggestStageId, normalizeStageId, findStageStatusPath } from '../core/plan/path.js';
6
+ import { validateStageId, suggestStageId, normalizeStageId, findStageStatusPath, isArchivedVersionOf } from '../core/plan/path.js';
7
7
  import { getConfigFieldLegalValues, setConfigValue } from '../core/config.js';
8
8
  import { parse as parseYaml } from 'yaml';
9
9
  import { MetricsStore } from '../core/metrics.js';
10
10
  import { addReviewEntry } from '../core/view/entry.js';
11
11
  import { handleAddStageError, handleConcurrentConflict } from './shared/errors.js';
12
12
  import { t, getCliLang } from '../core/i18n.js';
13
+ import { ARCHIVE_SCHEMA_VERSION } from '../core/flow-archive/archive-file.js';
14
+ import { listArchivableCandidates, listBlockingOpenRevsForVersion, splitForArchive } from '../core/flow-archive/grouping.js';
15
+ import { normalizeArchiveVersion, writeArchiveFile, addArchivedVersion, applyArchivePipelineDisposition, listArchiveVersions, readArchiveFile } from '../core/flow-archive/archive-ops.js';
16
+ import { archiveHealthItems } from '../core/flow-archive/archive-health.js';
17
+ import { requirementHealthItems } from '../core/req/aggregate.js'; // 需求对账项(stage-83 T3)
13
18
  export function registerFlowCommand(program) {
14
19
  const flow = program
15
20
  .command('flow')
@@ -625,7 +630,12 @@ export function registerFlowCommand(program) {
625
630
  .filter(Boolean);
626
631
  // 悬空依赖:命令层强制校验(核心层仅 warn),非法一律 exit 1 且不写盘
627
632
  const known = new Set(Object.keys(data.stages).map((k) => normalizeStageId(k) ?? k));
628
- const invalid = deps.filter((d) => !known.has(normalizeStageId(d) ?? d));
633
+ // 已归档依赖(显式版本前缀 ∧ 版本 ∈ archived_versions)→ 视为已满足(同 plan.ts 单一口径;恒传原始 d)
634
+ const archivedOk = deps.filter((d) => !known.has(normalizeStageId(d) ?? d) && isArchivedVersionOf(d, data.meta?.archived_versions));
635
+ const invalid = deps.filter((d) => !known.has(normalizeStageId(d) ?? d) && !isArchivedVersionOf(d, data.meta?.archived_versions));
636
+ if (archivedOk.length > 0) {
637
+ console.log(t('stage.set.archivedDepsTmpl', lang, { deps: archivedOk.join(', ') }));
638
+ }
629
639
  if (invalid.length > 0) {
630
640
  console.error(t('stage.set.depsDanglingTmpl', lang, { deps: invalid.join(', ') }));
631
641
  process.exit(1);
@@ -1513,6 +1523,253 @@ export function registerFlowCommand(program) {
1513
1523
  process.exit(1);
1514
1524
  }
1515
1525
  });
1526
+ // flow archive <version> — flow.json 版本分片归档(裁定 O2;与顶层 openfeel archive〔阶段摘要〕职责分离,裁定 O3)
1527
+ const archiveCmd = flow
1528
+ .command('archive')
1529
+ .description('flow.json 版本分片归档(按版本号归档已收官历史)')
1530
+ .argument('<version>', '版本号(如 v1.1.7;前缀 v 可省略)')
1531
+ .option('--dry-run', '仅预览(判据门照常校验;零写盘)')
1532
+ .option('--json', '以 JSON 输出 (machine-readable, single JSON document)')
1533
+ .action((versionInput, options) => {
1534
+ const projectPath = process.cwd();
1535
+ const lang = getCliLang(projectPath);
1536
+ const mgr = createManager();
1537
+ // --json 失败出口统一:纯 JSON 单文档 + exit 1(不混人类文案、不走 stderr)
1538
+ const failJson = (payload) => {
1539
+ console.log(JSON.stringify({ schemaVersion: 1, ...payload }, null, 2));
1540
+ process.exitCode = 1;
1541
+ };
1542
+ if (!mgr.isLoaded()) {
1543
+ if (options.json) {
1544
+ failJson({ error: 'no-init' });
1545
+ return;
1546
+ }
1547
+ console.error(t('common.errorNoInit', lang));
1548
+ process.exitCode = 1;
1549
+ return;
1550
+ }
1551
+ // 版本归一(REV-79-004):去 v 再加 v;非法 → exit 1 零写盘
1552
+ const version = normalizeArchiveVersion(versionInput);
1553
+ if (!version) {
1554
+ if (options.json) {
1555
+ failJson({ error: 'invalid-version', input: versionInput });
1556
+ return;
1557
+ }
1558
+ console.error(t('flow.archive.invalidVersionTmpl', lang, { input: versionInput }));
1559
+ process.exitCode = 1;
1560
+ return;
1561
+ }
1562
+ const data = mgr.getData();
1563
+ // 版本存在性门(前置;REV-79-001):无候选(不存在/已归档)→ 拒绝零写盘;覆盖写仅限活跃集仍含该版本条目
1564
+ const candidate = listArchivableCandidates(data).find((c) => c.version === version);
1565
+ if (!candidate) {
1566
+ if (options.json) {
1567
+ failJson({ error: 'version-not-active', version });
1568
+ return;
1569
+ }
1570
+ console.error(t('flow.archive.versionNotActiveTmpl', lang, { version }));
1571
+ process.exitCode = 1;
1572
+ return;
1573
+ }
1574
+ // 判据门:全 done(candidate.allDone 空集守卫)+ 无 blocking open REV(全仓唯一谓词,REV-79-002)
1575
+ const notDone = candidate.stages.filter((id) => data.stages[id]?.phase !== 'done');
1576
+ const blockingOpen = listBlockingOpenRevsForVersion(data, version);
1577
+ if (!candidate.allDone || blockingOpen.length > 0) {
1578
+ if (options.json) {
1579
+ failJson({ error: 'gate-rejected', version, notDone, blockingOpen: blockingOpen.map((r) => r.id) });
1580
+ return;
1581
+ }
1582
+ if (notDone.length > 0) {
1583
+ console.error(t('flow.archive.gateNotDoneTitleTmpl', lang, { version, n: String(notDone.length) }));
1584
+ for (const id of notDone) {
1585
+ console.error(t('flow.archive.gateItemTmpl', lang, { item: id + ' [' + (data.stages[id]?.phase ?? '') + ']' }));
1586
+ }
1587
+ }
1588
+ if (blockingOpen.length > 0) {
1589
+ console.error(t('flow.archive.gateBlockingRevTitleTmpl', lang, { version, n: String(blockingOpen.length) }));
1590
+ for (const r of blockingOpen) {
1591
+ console.error(t('flow.archive.gateItemTmpl', lang, { item: r.id + ': ' + r.title + ' (priority=' + r.priority + ')' }));
1592
+ }
1593
+ }
1594
+ console.error(t('flow.archive.gateRejected', lang));
1595
+ process.exitCode = 1;
1596
+ return;
1597
+ }
1598
+ // 切分(纯函数;禁命令层第二套解析/切分)
1599
+ const { archive, remainder } = splitForArchive(data, version);
1600
+ const counts = archive.counts;
1601
+ // 装配(REV-79-009):SplitResult.archive 不含 schemaVersion / archived_at
1602
+ const file = { ...archive, schemaVersion: ARCHIVE_SCHEMA_VERSION, archived_at: new Date().toISOString() };
1603
+ // --dry-run:纯内存推演(不写归档文件 / 不 addArchivedVersion / 不 appendLog / 不 save)——四写盘点零触碰
1604
+ if (options.dryRun) {
1605
+ // 深拷 pipeline(REV-79-015):处置 helper 会就地改 current/phase,预览不得污染 live data
1606
+ const previewData = {
1607
+ ...data,
1608
+ stages: remainder.stages,
1609
+ reviews: remainder.reviews,
1610
+ log: remainder.log,
1611
+ pipeline: { ...data.pipeline, current: { ...data.pipeline.current } },
1612
+ };
1613
+ const preview = applyArchivePipelineDisposition(previewData, version);
1614
+ if (options.json) {
1615
+ console.log(JSON.stringify({ schemaVersion: 1, version, stages: counts.stages, reviews: counts.reviews, log: counts.log, dryRun: true }, null, 2));
1616
+ return;
1617
+ }
1618
+ console.log(t('flow.archive.previewTmpl', lang, { version, stages: String(counts.stages), reviews: String(counts.reviews), log: String(counts.log) }));
1619
+ console.log(t('flow.archive.pipelineTmpl', lang, {
1620
+ current: preview.currentCleared ? t('common.none', lang) : (previewData.pipeline.current.stage || t('common.none', lang)),
1621
+ phase: preview.phase,
1622
+ }));
1623
+ console.log(t('flow.archive.previewNoWrite', lang));
1624
+ return;
1625
+ }
1626
+ // ① 先写归档文件(新增;独立锁 flow-archive,与 flow 锁不嵌套)
1627
+ try {
1628
+ writeArchiveFile(projectPath, file);
1629
+ }
1630
+ catch (err) {
1631
+ // 写盘失败(REV-79-013):内存未改动(移除/索引/审计均在其后),无需回滚;exit 1 零 flow.json 变更
1632
+ if (options.json) {
1633
+ failJson({ error: 'archive-write-failed', version });
1634
+ return;
1635
+ }
1636
+ console.error(t('common.errorTmpl', lang, { msg: err instanceof Error ? err.message : String(err) }));
1637
+ process.exitCode = 1;
1638
+ return;
1639
+ }
1640
+ // ② 内存移除(stages / reviews / log 三段)
1641
+ data.stages = remainder.stages;
1642
+ data.reviews = remainder.reviews;
1643
+ data.log = remainder.log;
1644
+ // ③ 索引(幂等去重;纯内存)
1645
+ const added = addArchivedVersion(data, version);
1646
+ // ④ pipeline 处置(纯内存;空集 → active,与 flow-manager.ts:1495-1496 守卫同效)
1647
+ const disposed = applyArchivePipelineDisposition(data, version);
1648
+ // ⑤ 审计(须晚于 ②,记录在归档后 flow.json——不混入被归档段)
1649
+ mgr.appendLog({ time: '', agent: 'cli', action: 'archive_flow_version', detail: { version, stages: counts.stages, reviews: counts.reviews, log: counts.log } });
1650
+ // ⑥ 后 save()(顺序不变量:归档文件写成功之前不改 flow.json)
1651
+ try {
1652
+ mgr.save();
1653
+ }
1654
+ catch (err) {
1655
+ if (isFlowConcurrentError(err)) {
1656
+ handleConcurrentConflict(err, lang); // exit 2(归档文件已落盘;重跑幂等收敛)
1657
+ return;
1658
+ }
1659
+ console.error(t('common.errorTmpl', lang, { msg: err instanceof Error ? err.message : String(err) }));
1660
+ process.exitCode = 1;
1661
+ return;
1662
+ }
1663
+ if (options.json) {
1664
+ console.log(JSON.stringify({ schemaVersion: 1, version, stages: counts.stages, reviews: counts.reviews, log: counts.log, dryRun: false }, null, 2));
1665
+ return;
1666
+ }
1667
+ if (added) {
1668
+ console.log(t('flow.archive.doneTmpl', lang, { version, stages: String(counts.stages), reviews: String(counts.reviews), log: String(counts.log) }));
1669
+ }
1670
+ else {
1671
+ console.log(t('flow.archive.rearchivedTmpl', lang, { version }));
1672
+ }
1673
+ console.log(t('flow.archive.pipelineTmpl', lang, {
1674
+ current: disposed.currentCleared ? t('common.none', lang) : (data.pipeline.current.stage || t('common.none', lang)),
1675
+ phase: disposed.phase,
1676
+ }));
1677
+ });
1678
+ // flow archive list — 列出已归档版本及计数(只读;引擎字典序)
1679
+ archiveCmd
1680
+ .command('list')
1681
+ .description('列出已归档版本及计数(只读)')
1682
+ .option('--json', '以 JSON 输出 (machine-readable, single JSON document)')
1683
+ .action((options, command) => {
1684
+ const projectPath = process.cwd();
1685
+ const lang = getCliLang(projectPath);
1686
+ // 父命令 archive 亦声明 --json,Commander 派发子命令前会先由父命令消费该标志
1687
+ // → 合并父级 opts(否则 `flow archive list --json` 的 --json 不可达;REV-79-018)
1688
+ const json = options.json === true || command.parent?.opts().json === true;
1689
+ // 引擎字典序(零命令层第二套排序);逐文件 counts 经读侧复用(损坏 → null)
1690
+ const rows = listArchiveVersions(projectPath).map((v) => ({
1691
+ version: v,
1692
+ counts: readArchiveFile(projectPath, v)?.counts ?? null,
1693
+ }));
1694
+ if (json) {
1695
+ console.log(JSON.stringify({ schemaVersion: 1, versions: rows }, null, 2));
1696
+ return;
1697
+ }
1698
+ if (rows.length === 0) {
1699
+ console.log(t('flow.archive.list.empty', lang));
1700
+ return;
1701
+ }
1702
+ console.log(t('flow.archive.list.title', lang));
1703
+ for (const r of rows) {
1704
+ if (r.counts) {
1705
+ console.log(t('flow.archive.list.itemTmpl', lang, {
1706
+ version: r.version, stages: String(r.counts.stages), reviews: String(r.counts.reviews), log: String(r.counts.log),
1707
+ }));
1708
+ }
1709
+ else {
1710
+ console.log(t('flow.archive.list.unreadableTmpl', lang, { version: r.version }));
1711
+ }
1712
+ }
1713
+ // 只读保证:不 save()、不 appendLog、不读 flow.json
1714
+ });
1715
+ // flow archive show <version> — 查看指定归档版本(只读)
1716
+ archiveCmd
1717
+ .command('show')
1718
+ .description('查看指定归档版本(只读)')
1719
+ .argument('<version>', '版本号(如 v1.1.7;前缀 v 可省略)')
1720
+ .option('--json', '以 JSON 输出(全量,含 log / reviews)')
1721
+ .action((versionInput, options, command) => {
1722
+ const projectPath = process.cwd();
1723
+ const lang = getCliLang(projectPath);
1724
+ // 同 list:合并父级 --json(REV-79-018)
1725
+ const json = options.json === true || command.parent?.opts().json === true;
1726
+ const available = listArchiveVersions(projectPath);
1727
+ // 版本归一(前缀兼容):非法 → exit 1 零写盘
1728
+ const version = normalizeArchiveVersion(versionInput);
1729
+ if (!version) {
1730
+ if (json) {
1731
+ console.log(JSON.stringify({ schemaVersion: 1, error: 'invalid-version', input: versionInput, available }, null, 2));
1732
+ process.exitCode = 1;
1733
+ return;
1734
+ }
1735
+ console.error(t('flow.archive.invalidVersionTmpl', lang, { input: versionInput }));
1736
+ process.exitCode = 1;
1737
+ return;
1738
+ }
1739
+ const file = readArchiveFile(projectPath, version);
1740
+ if (!file) {
1741
+ if (json) {
1742
+ console.log(JSON.stringify({ schemaVersion: 1, error: 'not-found', version, available }, null, 2));
1743
+ process.exitCode = 1;
1744
+ return;
1745
+ }
1746
+ console.error(t('flow.archive.show.notFoundTmpl', lang, {
1747
+ version, versions: available.length > 0 ? available.join(', ') : t('common.none', lang),
1748
+ }));
1749
+ process.exitCode = 1;
1750
+ return;
1751
+ }
1752
+ // --json = 全量载荷(ArchiveFile 原文自带 schemaVersion:1,含 log / reviews)——显式请求才取全量(REV-79-003)
1753
+ if (json) {
1754
+ console.log(JSON.stringify(file, null, 2));
1755
+ return;
1756
+ }
1757
+ console.log(t('flow.archive.show.titleTmpl', lang, { version, at: typeof file.archived_at === 'string' ? file.archived_at : '' }));
1758
+ console.log(t('flow.archive.show.countsTmpl', lang, {
1759
+ stages: String(file.counts.stages), reviews: String(file.counts.reviews), log: String(file.counts.log),
1760
+ }));
1761
+ console.log(t('flow.archive.show.stagesLabel', lang));
1762
+ const entries = Object.entries(file.stages ?? {});
1763
+ if (entries.length === 0) {
1764
+ console.log(t('common.noData', lang));
1765
+ }
1766
+ else {
1767
+ for (const [id, stage] of entries) {
1768
+ console.log(t('flow.archive.show.stageLineTmpl', lang, { stage: id, phase: stage?.phase ?? '' }));
1769
+ }
1770
+ }
1771
+ console.log(t('flow.archive.show.fullHint', lang));
1772
+ });
1516
1773
  // flow health [--quick]
1517
1774
  flow
1518
1775
  .command('health')
@@ -1524,6 +1781,15 @@ export function registerFlowCommand(program) {
1524
1781
  .action((options) => {
1525
1782
  const lang = getCliLang(process.cwd());
1526
1783
  const mgr = createManager();
1784
+ const quick = options.quick ?? false;
1785
+ // 归档一致性检查项(T2):warn-only;--quick 跳过;flow.json 未加载 → 跳过(与既有检查降级一致)
1786
+ const archiveItems = !quick && mgr.isLoaded()
1787
+ ? archiveHealthItems({ projectPath: process.cwd(), data: mgr.getData() })
1788
+ : [];
1789
+ // 需求对账检查项(stage-83 T3):warn-only(悬空关联 / done 漂移);--quick 跳过;未加载 → 跳过
1790
+ const reqItems = !quick && mgr.isLoaded()
1791
+ ? requirementHealthItems({ data: mgr.getData(), lang })
1792
+ : [];
1527
1793
  // B2-2:--fix 状态对账(仅回写 status.md「状态」字段;--dry-run 预览不写盘)
1528
1794
  let reconciled;
1529
1795
  if (options.fix) {
@@ -1540,8 +1806,14 @@ export function registerFlowCommand(program) {
1540
1806
  }
1541
1807
  // B1-3:--json 分支纯 JSON 单文档;退出码语义不变(有 fail → 非 0)
1542
1808
  if (options.json) {
1543
- const report = mgr.getHealthReport(options.quick ?? false);
1544
- const payload = { schemaVersion: 1, ok: report.ok, items: report.items };
1809
+ const report = mgr.getHealthReport(quick);
1810
+ // 合并追加归档一致性项(warn-only,恒无 fail → ok / 退出码不变)
1811
+ const items = [
1812
+ ...report.items,
1813
+ ...archiveItems.map((i) => ({ name: i.section, status: i.status, detail: i.message })),
1814
+ ...reqItems.map((i) => ({ name: i.section, status: i.status, detail: i.message })),
1815
+ ];
1816
+ const payload = { schemaVersion: 1, ok: report.ok, items };
1545
1817
  // 与 op-001 的 health --json 合并字段(--fix 时追加 reconciled)
1546
1818
  if (reconciled) {
1547
1819
  payload.reconciled = reconciled;
@@ -1578,7 +1850,7 @@ export function registerFlowCommand(program) {
1578
1850
  console.log('');
1579
1851
  }
1580
1852
  console.log(t('flow.health.title', lang) + '\n');
1581
- const result = mgr.healthCheck(options.quick ?? false);
1853
+ const result = mgr.healthCheck(quick);
1582
1854
  // 图标映射
1583
1855
  const icon = (status) => {
1584
1856
  if (status === 'pass')
@@ -1587,7 +1859,8 @@ export function registerFlowCommand(program) {
1587
1859
  return '⚠️ ';
1588
1860
  return '❌';
1589
1861
  };
1590
- for (const item of result.items) {
1862
+ // 合并追加归档一致性项(warn-only;--quick 时 archiveItems 恒空)
1863
+ for (const item of [...result.items, ...archiveItems, ...reqItems]) {
1591
1864
  console.log(`${icon(item.status)} ${item.section}: ${item.message}`);
1592
1865
  }
1593
1866
  console.log('');