openfeel 1.1.7 → 1.2.1

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 (115) hide show
  1. package/README.en.md +35 -8
  2. package/README.md +29 -4
  3. package/README.zh-CN.md +35 -8
  4. package/bin/openfeel.js +3 -2
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +7 -4
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/commands/config.d.ts.map +1 -1
  9. package/dist/commands/config.js +29 -4
  10. package/dist/commands/config.js.map +1 -1
  11. package/dist/commands/flow.d.ts.map +1 -1
  12. package/dist/commands/flow.js +385 -14
  13. package/dist/commands/flow.js.map +1 -1
  14. package/dist/commands/plan.d.ts.map +1 -1
  15. package/dist/commands/plan.js +19 -6
  16. package/dist/commands/plan.js.map +1 -1
  17. package/dist/commands/req.d.ts +15 -0
  18. package/dist/commands/req.d.ts.map +1 -0
  19. package/dist/commands/req.js +735 -0
  20. package/dist/commands/req.js.map +1 -0
  21. package/dist/commands/stage.d.ts.map +1 -1
  22. package/dist/commands/stage.js +7 -1
  23. package/dist/commands/stage.js.map +1 -1
  24. package/dist/core/config.d.ts +6 -4
  25. package/dist/core/config.d.ts.map +1 -1
  26. package/dist/core/config.js +6 -5
  27. package/dist/core/config.js.map +1 -1
  28. package/dist/core/flow-archive/archive-file.d.ts +35 -0
  29. package/dist/core/flow-archive/archive-file.d.ts.map +1 -0
  30. package/dist/core/flow-archive/archive-file.js +3 -0
  31. package/dist/core/flow-archive/archive-file.js.map +1 -0
  32. package/dist/core/flow-archive/archive-health.d.ts +34 -0
  33. package/dist/core/flow-archive/archive-health.d.ts.map +1 -0
  34. package/dist/core/flow-archive/archive-health.js +76 -0
  35. package/dist/core/flow-archive/archive-health.js.map +1 -0
  36. package/dist/core/flow-archive/archive-ops.d.ts +68 -0
  37. package/dist/core/flow-archive/archive-ops.d.ts.map +1 -0
  38. package/dist/core/flow-archive/archive-ops.js +184 -0
  39. package/dist/core/flow-archive/archive-ops.js.map +1 -0
  40. package/dist/core/flow-archive/grouping.d.ts +58 -0
  41. package/dist/core/flow-archive/grouping.d.ts.map +1 -0
  42. package/dist/core/flow-archive/grouping.js +153 -0
  43. package/dist/core/flow-archive/grouping.js.map +1 -0
  44. package/dist/core/flow-manager.d.ts +67 -9
  45. package/dist/core/flow-manager.d.ts.map +1 -1
  46. package/dist/core/flow-manager.js +243 -51
  47. package/dist/core/flow-manager.js.map +1 -1
  48. package/dist/core/i18n-data/en.d.ts +1 -0
  49. package/dist/core/i18n-data/en.d.ts.map +1 -1
  50. package/dist/core/i18n-data/en.js +174 -4
  51. package/dist/core/i18n-data/en.js.map +1 -1
  52. package/dist/core/i18n-data/zh-CN.d.ts +1 -0
  53. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  54. package/dist/core/i18n-data/zh-CN.js +175 -4
  55. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  56. package/dist/core/i18n.js +4 -4
  57. package/dist/core/i18n.js.map +1 -1
  58. package/dist/core/managed-region.d.ts +16 -2
  59. package/dist/core/managed-region.d.ts.map +1 -1
  60. package/dist/core/managed-region.js +24 -3
  61. package/dist/core/managed-region.js.map +1 -1
  62. package/dist/core/metrics.d.ts +2 -0
  63. package/dist/core/metrics.d.ts.map +1 -1
  64. package/dist/core/metrics.js +15 -0
  65. package/dist/core/metrics.js.map +1 -1
  66. package/dist/core/model-config.d.ts +1 -1
  67. package/dist/core/model-config.d.ts.map +1 -1
  68. package/dist/core/model-config.js +2 -2
  69. package/dist/core/model-config.js.map +1 -1
  70. package/dist/core/opencode-config.js +1 -1
  71. package/dist/core/opencode-config.js.map +1 -1
  72. package/dist/core/pipeline-schema.d.ts +15 -0
  73. package/dist/core/pipeline-schema.d.ts.map +1 -1
  74. package/dist/core/pipeline-schema.js +17 -0
  75. package/dist/core/pipeline-schema.js.map +1 -1
  76. package/dist/core/plan/path.d.ts +17 -0
  77. package/dist/core/plan/path.d.ts.map +1 -1
  78. package/dist/core/plan/path.js +31 -0
  79. package/dist/core/plan/path.js.map +1 -1
  80. package/dist/core/plan/stage.d.ts +2 -1
  81. package/dist/core/plan/stage.d.ts.map +1 -1
  82. package/dist/core/plan/stage.js +7 -2
  83. package/dist/core/plan/stage.js.map +1 -1
  84. package/dist/core/req/aggregate.d.ts +62 -0
  85. package/dist/core/req/aggregate.d.ts.map +1 -0
  86. package/dist/core/req/aggregate.js +134 -0
  87. package/dist/core/req/aggregate.js.map +1 -0
  88. package/dist/core/req/body-file.d.ts +16 -0
  89. package/dist/core/req/body-file.d.ts.map +1 -0
  90. package/dist/core/req/body-file.js +84 -0
  91. package/dist/core/req/body-file.js.map +1 -0
  92. package/dist/core/req/requirement-ops.d.ts +116 -0
  93. package/dist/core/req/requirement-ops.d.ts.map +1 -0
  94. package/dist/core/req/requirement-ops.js +395 -0
  95. package/dist/core/req/requirement-ops.js.map +1 -0
  96. package/dist/core/req/types.d.ts +46 -0
  97. package/dist/core/req/types.d.ts.map +1 -0
  98. package/dist/core/req/types.js +7 -0
  99. package/dist/core/req/types.js.map +1 -0
  100. package/dist/core/runtime-log.d.ts +18 -0
  101. package/dist/core/runtime-log.d.ts.map +1 -1
  102. package/dist/core/runtime-log.js +72 -0
  103. package/dist/core/runtime-log.js.map +1 -1
  104. package/dist/core/session.d.ts +24 -0
  105. package/dist/core/session.d.ts.map +1 -0
  106. package/dist/core/session.js +107 -0
  107. package/dist/core/session.js.map +1 -0
  108. package/dist/core/setup.js +4 -4
  109. package/dist/core/template-loader.d.ts.map +1 -1
  110. package/dist/core/template-loader.js +3160 -6571
  111. package/dist/core/template-loader.js.map +1 -1
  112. package/dist/core/update.d.ts.map +1 -1
  113. package/dist/core/update.js +770 -26
  114. package/dist/core/update.js.map +1 -1
  115. package/package.json +1 -1
@@ -0,0 +1,184 @@
1
+ /**
2
+ * 归档读写原语与索引管理 — 归档引擎的 IO 层
3
+ * 职责:归档文件路径单点 / 写盘(独立锁 flow-archive + 原子写)/ 读取(宽容) / 列举 / meta.archived_versions 管理(纯内存)
4
+ * + pipeline 处置(纯内存)。
5
+ * 写盘与锁复用 src/core/fs/ 工具层(原子写模式 + 建议性文件锁模式);
6
+ * 归档锁名 `flow-archive` 与 flow 锁**不嵌套**(裁定 P;核验 A19)。
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { atomicWriteFileSync } from '../fs/atomic-write.js';
11
+ import { withFileLock, projectLockPath } from '../fs/file-lock.js';
12
+ import { versionOfStageId } from './grouping.js';
13
+ /** 归档写盘锁名(独立于 flow 锁;锁文件落 .openfeel/tmp/locks/flow-archive.lock) */
14
+ const ARCHIVE_LOCK_NAME = 'flow-archive';
15
+ /** 归档目录:{projectPath}/.openfeel/flow-archive(Q2 定稿:不纳入 WORKSPACE_DIRS,按需 mkdir 幂等创建) */
16
+ export function getArchiveDir(projectPath) {
17
+ return join(projectPath, '.openfeel', 'flow-archive');
18
+ }
19
+ /**
20
+ * 归档文件路径:{archiveDir}/{version}.json(路径单点)。
21
+ *
22
+ * ⚠️ 仅接受**已校验**的版本字符串(经 `isValidArchiveVersion`,REV-78-011):
23
+ * 本函数只做拼接、不做合法性/路径穿越校验;调用方须先走 `isValidArchiveVersion`
24
+ * 或使用上游已校验入口(`writeArchiveFile` / `readArchiveFile` / `listArchiveVersions`)。
25
+ */
26
+ export function getArchiveFilePath(projectPath, version) {
27
+ return join(getArchiveDir(projectPath), `${version}.json`);
28
+ }
29
+ /**
30
+ * 版本字符串合法性(探针法复用 parseStageId,禁自写正则):
31
+ * `${s}-stage-00` 可被 parseStageId 解析且版本段恒等 → 合法;空串/含路径分隔符/非版本形态 → 非法。
32
+ *
33
+ * 导出版(原私有版本校验函数重命名并导出),供命令层复用——REV-79-004。
34
+ */
35
+ export function isValidArchiveVersion(s) {
36
+ if (s.length === 0) {
37
+ return false;
38
+ }
39
+ return versionOfStageId(`${s}-stage-00`) === s;
40
+ }
41
+ /**
42
+ * 版本号归一(输入前缀兼容):trim → 去单个前导 `v`(如有)→ 补 `v` → 经 `isValidArchiveVersion` 校验。
43
+ * 归一方向恒为「去 v 再加 v」:引擎键恒带 `v`(与 `versionOfStageId` 输出同形态;REV-79-004)。
44
+ * **禁**复用 `normalizeVersion`(`src/core/plan/roadmap.ts:17`,方向相反:去 `v`)。
45
+ *
46
+ * 错误路径返回 null 的触发条件:空串 / 探针校验失败(非 vX.Y[.Z] 形态、含路径分隔符等)。
47
+ */
48
+ export function normalizeArchiveVersion(input) {
49
+ const trimmed = input.trim();
50
+ // 去单个前导 v(如 `vv1.1.7` 去后剩 `v1.1.7`,仍带 v → 校验失败 → null,防双前缀)
51
+ const body = trimmed.startsWith('v') ? trimmed.slice(1) : trimmed;
52
+ const candidate = `v${body}`;
53
+ return isValidArchiveVersion(candidate) ? candidate : null;
54
+ }
55
+ /**
56
+ * 写归档文件(覆盖写 = 同版本幂等;backup:false,D8 定稿)。
57
+ * 独立锁 `flow-archive` 内原子写(同目录 temp + fsync + rename);目录按需 mkdir(幂等)。
58
+ * @throws 版本字符串非法(路径穿越防御,D9)或写盘失败
59
+ */
60
+ export function writeArchiveFile(projectPath, file) {
61
+ if (!isValidArchiveVersion(file.version)) {
62
+ // 破坏性写路径默认拒绝:非法版本(含路径分隔符/非版本形态)零写盘
63
+ throw new Error(`归档版本非法:${file.version}`);
64
+ }
65
+ const filePath = getArchiveFilePath(projectPath, file.version);
66
+ mkdirSync(getArchiveDir(projectPath), { recursive: true });
67
+ const lockPath = projectLockPath(projectPath, ARCHIVE_LOCK_NAME);
68
+ withFileLock(lockPath, () => {
69
+ atomicWriteFileSync(filePath, JSON.stringify(file, null, 2) + '\n', { backup: false });
70
+ });
71
+ }
72
+ /**
73
+ * 列举已归档版本(目录扫描 + 文件名合法校验)。
74
+ * 目录不存在 → [];仅收 `{version}.json` 且 version 通过 isValidArchiveVersion 者;返回字典序排序(非语义版本序)。
75
+ */
76
+ export function listArchiveVersions(projectPath) {
77
+ const dir = getArchiveDir(projectPath);
78
+ if (!existsSync(dir)) {
79
+ return [];
80
+ }
81
+ return readdirSync(dir)
82
+ .filter((name) => name.endsWith('.json'))
83
+ .map((name) => name.slice(0, -'.json'.length))
84
+ .filter((stem) => isValidArchiveVersion(stem))
85
+ .sort();
86
+ }
87
+ /**
88
+ * 读取归档文件。
89
+ * 缺失 / 损坏(JSON 解析失败)/ 非法版本(路径穿越防御)/ 内容非对象 → null(不抛);
90
+ * 未知键保留(不重建对象);缺 counts → 按实际长度回填(读时计算,不写盘)。
91
+ */
92
+ export function readArchiveFile(projectPath, version) {
93
+ if (!isValidArchiveVersion(version)) {
94
+ return null; // 读侧宽容:非法版本不抛,视同不可读
95
+ }
96
+ const filePath = getArchiveFilePath(projectPath, version);
97
+ if (!existsSync(filePath)) {
98
+ return null;
99
+ }
100
+ try {
101
+ const raw = readFileSync(filePath, 'utf-8');
102
+ const parsed = JSON.parse(raw);
103
+ // 合法 JSON 但非对象(null / 数组 / 数字 / 字符串)→ 视同损坏(契约:缺失/损坏/非法 → null,REV-78-012)
104
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
105
+ return null;
106
+ }
107
+ return withCountsFallback(parsed);
108
+ }
109
+ catch {
110
+ // 损坏(解析失败):返回 null 不崩溃(读侧宽容;kb 读侧分类范式)
111
+ return null;
112
+ }
113
+ }
114
+ /** 读侧宽容:counts 三段齐全则原样返回;否则按实际长度回填(读时计算,不写盘) */
115
+ function withCountsFallback(file) {
116
+ const counts = file.counts;
117
+ if (counts &&
118
+ typeof counts.stages === 'number' &&
119
+ typeof counts.reviews === 'number' &&
120
+ typeof counts.log === 'number') {
121
+ return file;
122
+ }
123
+ const stages = file.stages ?? {};
124
+ const reviews = file.reviews ?? [];
125
+ const log = file.log ?? [];
126
+ return {
127
+ ...file,
128
+ stages,
129
+ reviews,
130
+ log,
131
+ counts: {
132
+ stages: Object.keys(stages).length,
133
+ reviews: reviews.length,
134
+ log: log.length,
135
+ },
136
+ };
137
+ }
138
+ /**
139
+ * 读取已归档版本索引(快照副本,防外部改内存;缺失/非数组 → [])。
140
+ */
141
+ export function getArchivedVersions(data) {
142
+ const list = data.meta?.archived_versions;
143
+ return Array.isArray(list) ? [...list] : [];
144
+ }
145
+ /**
146
+ * 追加已归档版本(幂等去重;**纯内存**,不写盘——持久化由命令层 save() 承担)。
147
+ * @returns 是否新增(false = 已存在,no-op 不追加)
148
+ */
149
+ export function addArchivedVersion(data, version) {
150
+ if (!data.meta) {
151
+ // 防御:缺 meta 的存量/损坏数据(与 save() 的 ??= 兜底同字面量)
152
+ data.meta = { version: '1.0', project: '', updated: '', revision: 0 };
153
+ }
154
+ const existing = getArchivedVersions(data);
155
+ if (existing.includes(version)) {
156
+ return false; // 幂等:重复登记不追加
157
+ }
158
+ data.meta.archived_versions = [...existing, version];
159
+ return true;
160
+ }
161
+ /**
162
+ * 归档后 pipeline 处置(**纯内存**,不 IO;save 由调用方统一(裁定 P))。
163
+ *
164
+ * 前置条件:须在 stages 移除**之后**调用(phase 按归档后活跃集重算)。
165
+ * - `pipeline.current.stage` 属被归档版本 → 置空 `{ stage: '', op: '' }`(currentCleared = true)。
166
+ * - `pipeline.phase` 按活跃集重算:非空且全部 done → `'done'`,否则 `'active'`
167
+ * (**空集 → active**,与 `flow-manager.ts:1495-1496` 空集守卫同效;REV-79-005)。
168
+ *
169
+ * @returns currentCleared = current 是否命中被归档版本;phase = 处置后的 pipeline.phase
170
+ */
171
+ export function applyArchivePipelineDisposition(data, version) {
172
+ const current = data.pipeline?.current;
173
+ // 命中被归档版本 → 置空 current(stage 与 op 同清;versionOfStageId('') === null 故空串永不命中)
174
+ const currentCleared = !!current && versionOfStageId(current.stage) === version;
175
+ if (currentCleared && data.pipeline) {
176
+ data.pipeline.current = { stage: '', op: '' };
177
+ }
178
+ // 宏观 done 判定(空集守卫:无阶段视为非全 done → active)
179
+ const allDone = Object.keys(data.stages).length > 0
180
+ && Object.values(data.stages).every((s) => s.phase === 'done');
181
+ data.pipeline.phase = allDone ? 'done' : 'active';
182
+ return { currentCleared, phase: data.pipeline.phase };
183
+ }
184
+ //# sourceMappingURL=archive-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-ops.js","sourceRoot":"","sources":["../../../src/core/flow-archive/archive-ops.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { FlowData, StageData, ReviewItem, LogEntry } from '../flow-manager.js';
2
+ import type { ArchiveFile } from './archive-file.js';
3
+ /** splitForArchive 返回:归档段(缺 schemaVersion/archived_at,由命令层装配) + 保留段 */
4
+ export interface SplitResult {
5
+ /** 归档段:不含 schemaVersion / archived_at(由 stage-79 命令层装配后交 writeArchiveFile) */
6
+ archive: Omit<ArchiveFile, 'schemaVersion' | 'archived_at'>;
7
+ /** 保留段:留在 flow.json 的条目(不可归档归属者一律落此) */
8
+ remainder: {
9
+ stages: Record<string, StageData>;
10
+ reviews: ReviewItem[];
11
+ log: LogEntry[];
12
+ };
13
+ }
14
+ /** 可归档候选(供命令面判据门消费;blocking REV 判定归命令层,本函数只读纯判定) */
15
+ export interface ArchivableCandidate {
16
+ /** 版本前缀(如 `v1.1.7`) */
17
+ version: string;
18
+ /** 该版本全部 stageId(保持 stages 键遍历顺序) */
19
+ stages: string[];
20
+ /** 该版本是否全部阶段 phase=done(空集守卫:length>0 && every(done)) */
21
+ allDone: boolean;
22
+ }
23
+ /**
24
+ * stageId → 版本前缀(如 `v1.1.7`)。
25
+ * 复用 parseStageId(唯一权威,禁自写正则):fullStageId = `${version}-${stageDir}`,剥离 `-${stageDir}` 后缀即版本段。
26
+ * 短名 stageId 经 parseStageId 归一化为 `v1.0.0-*`(DEFAULT_STAGE_VERSION,既定语义)。
27
+ * 无法解析返回 null。
28
+ */
29
+ export declare function versionOfStageId(stageId: string): string | null;
30
+ /**
31
+ * stages 按版本前缀分组(版本 → stageId 列表)。
32
+ * 保持稳定顺序(按 stages 键遍历顺序);无法解析的 stageId 跳过(不产生分组 → 永不归档)。
33
+ */
34
+ export declare function groupStagesByVersion(stages: Record<string, StageData>): Map<string, string[]>;
35
+ /**
36
+ * 可归档候选列表(只读纯函数):版本 + 该版本 stageId 列表 + 是否全部 phase=done。
37
+ * allDone 采用空集守卫(length > 0 && every(done),对齐 flow-manager.ts:1484-1486 范式——空集不判 done)。
38
+ * 不判 REV(blocking open REV 判定归命令层,裁定 O2)。
39
+ */
40
+ export declare function listArchivableCandidates(data: FlowData): ArchivableCandidate[];
41
+ /**
42
+ * 版本作用域内的「阻塞 open」审查条目(只读纯函数;返回新数组副本,push 不影响 data.reviews)。
43
+ *
44
+ * 谓词为**全仓唯一**口径(对齐 `flow-manager.ts:1456`/`:1570`;REV-79-002):
45
+ * `r.blocking !== false && r.status === 'open'` —— blocking 缺省(undefined)= 阻塞。
46
+ * 版本作用域解析与 splitForArchive **同源**(复用本文件私有 versionOfReviewOp)。
47
+ */
48
+ export declare function listBlockingOpenRevsForVersion(data: FlowData, version: string): ReviewItem[];
49
+ /**
50
+ * 按版本切分 flow 数据(纯函数,不写盘)。
51
+ * - stages:该版本全部 stageId 条目整体搬入 archive;其余(含不可解析 stageId)保留 remainder。
52
+ * - reviews:`versionOfReviewOp(r.op) === version` 者搬入;其余保留(保序)。
53
+ * - log:`versionOfLogEntry(e) === version` 者搬入;其余保留(保序;无 stageName / 不可解析一律保留)。
54
+ * - 版本不存在 → archive 为空集(counts 全 0),不抛错。
55
+ * 返回集合中的对象为原数据浅引用(调用方不得修改)。
56
+ */
57
+ export declare function splitForArchive(data: FlowData, version: string): SplitResult;
58
+ //# sourceMappingURL=grouping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grouping.d.ts","sourceRoot":"","sources":["../../../src/core/flow-archive/grouping.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC;IAC5D,wCAAwC;IACxC,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAClC,OAAO,EAAE,UAAU,EAAE,CAAC;QACtB,GAAG,EAAE,QAAQ,EAAE,CAAC;KACjB,CAAC;CACH;AAED,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,yDAAyD;IACzD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU/D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAe7F;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,QAAQ,GAAG,mBAAmB,EAAE,CAO9E;AAiBD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAI5F;AAeD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAiD5E"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * 版本分组与切分纯函数 — 归档引擎的解析核心
3
+ * 职责:stageId → 版本前缀(复用 parseStageId,禁自写正则);
4
+ * 按版本分组;按版本切分 flow 数据(stages 全搬 / reviews 按 op 前缀 / log 按 detail.stageName)。
5
+ * 本文件全部为纯函数(只读输入,不写盘、不加锁、不读环境)。
6
+ */
7
+ import { parseStageId } from '../plan/path.js';
8
+ /**
9
+ * stageId → 版本前缀(如 `v1.1.7`)。
10
+ * 复用 parseStageId(唯一权威,禁自写正则):fullStageId = `${version}-${stageDir}`,剥离 `-${stageDir}` 后缀即版本段。
11
+ * 短名 stageId 经 parseStageId 归一化为 `v1.0.0-*`(DEFAULT_STAGE_VERSION,既定语义)。
12
+ * 无法解析返回 null。
13
+ */
14
+ export function versionOfStageId(stageId) {
15
+ const parsed = parseStageId(stageId);
16
+ if (!parsed) {
17
+ return null;
18
+ }
19
+ const suffix = `-${parsed.stageDir}`;
20
+ if (!parsed.fullStageId.endsWith(suffix)) {
21
+ return null;
22
+ }
23
+ return parsed.fullStageId.slice(0, -suffix.length);
24
+ }
25
+ /**
26
+ * stages 按版本前缀分组(版本 → stageId 列表)。
27
+ * 保持稳定顺序(按 stages 键遍历顺序);无法解析的 stageId 跳过(不产生分组 → 永不归档)。
28
+ */
29
+ export function groupStagesByVersion(stages) {
30
+ const groups = new Map();
31
+ for (const stageId of Object.keys(stages)) {
32
+ const version = versionOfStageId(stageId);
33
+ if (!version) {
34
+ continue; // 无法解析 → 不分组(保留在 flow.json)
35
+ }
36
+ const list = groups.get(version);
37
+ if (list) {
38
+ list.push(stageId);
39
+ }
40
+ else {
41
+ groups.set(version, [stageId]);
42
+ }
43
+ }
44
+ return groups;
45
+ }
46
+ /**
47
+ * 可归档候选列表(只读纯函数):版本 + 该版本 stageId 列表 + 是否全部 phase=done。
48
+ * allDone 采用空集守卫(length > 0 && every(done),对齐 flow-manager.ts:1484-1486 范式——空集不判 done)。
49
+ * 不判 REV(blocking open REV 判定归命令层,裁定 O2)。
50
+ */
51
+ export function listArchivableCandidates(data) {
52
+ const result = [];
53
+ for (const [version, stageIds] of groupStagesByVersion(data.stages)) {
54
+ const allDone = stageIds.length > 0 && stageIds.every((id) => data.stages[id]?.phase === 'done');
55
+ result.push({ version, stages: stageIds, allDone });
56
+ }
57
+ return result;
58
+ }
59
+ /**
60
+ * reviews 条目 → 版本前缀。
61
+ * `op` 形如 `<stageId>.op-NNN` 或 `<stageId>`:点号锚定正则 `/^(.+)\.(op-\d+)$/` 剥离 opId 段
62
+ * (stageId 自身可含点号,禁 split('.');见 archive/merge.ts:57-67 既有范式);stageId 段经 versionOfStageId。
63
+ * 无法归属返回 null(→ 保留在 flow.json)。
64
+ */
65
+ function versionOfReviewOp(op) {
66
+ if (typeof op !== 'string' || op.length === 0) {
67
+ return null;
68
+ }
69
+ const anchor = op.match(/^(.+)\.(op-\d+)$/);
70
+ const stageId = anchor ? anchor[1] : op;
71
+ return versionOfStageId(stageId);
72
+ }
73
+ /**
74
+ * 版本作用域内的「阻塞 open」审查条目(只读纯函数;返回新数组副本,push 不影响 data.reviews)。
75
+ *
76
+ * 谓词为**全仓唯一**口径(对齐 `flow-manager.ts:1456`/`:1570`;REV-79-002):
77
+ * `r.blocking !== false && r.status === 'open'` —— blocking 缺省(undefined)= 阻塞。
78
+ * 版本作用域解析与 splitForArchive **同源**(复用本文件私有 versionOfReviewOp)。
79
+ */
80
+ export function listBlockingOpenRevsForVersion(data, version) {
81
+ return (data.reviews ?? []).filter((r) => versionOfReviewOp(r.op) === version && r.blocking !== false && r.status === 'open');
82
+ }
83
+ /**
84
+ * log 条目 → 版本前缀。
85
+ * 唯一读 `detail.stageName`(裁定 T);缺失 / 非字符串 / 不可解析 → null(保留在 flow.json)。
86
+ * 注意:`detail.stageId` / `detail.stage` 等其它键一律不看(保守裁定,见 D3)。
87
+ */
88
+ function versionOfLogEntry(entry) {
89
+ const stageName = entry?.detail?.['stageName'];
90
+ if (typeof stageName !== 'string') {
91
+ return null;
92
+ }
93
+ return versionOfStageId(stageName);
94
+ }
95
+ /**
96
+ * 按版本切分 flow 数据(纯函数,不写盘)。
97
+ * - stages:该版本全部 stageId 条目整体搬入 archive;其余(含不可解析 stageId)保留 remainder。
98
+ * - reviews:`versionOfReviewOp(r.op) === version` 者搬入;其余保留(保序)。
99
+ * - log:`versionOfLogEntry(e) === version` 者搬入;其余保留(保序;无 stageName / 不可解析一律保留)。
100
+ * - 版本不存在 → archive 为空集(counts 全 0),不抛错。
101
+ * 返回集合中的对象为原数据浅引用(调用方不得修改)。
102
+ */
103
+ export function splitForArchive(data, version) {
104
+ const archiveStages = {};
105
+ const remainderStages = {};
106
+ for (const [stageId, stage] of Object.entries(data.stages)) {
107
+ if (versionOfStageId(stageId) === version) {
108
+ archiveStages[stageId] = stage;
109
+ }
110
+ else {
111
+ remainderStages[stageId] = stage;
112
+ }
113
+ }
114
+ const archiveReviews = [];
115
+ const remainderReviews = [];
116
+ for (const review of data.reviews ?? []) {
117
+ if (versionOfReviewOp(review.op) === version) {
118
+ archiveReviews.push(review);
119
+ }
120
+ else {
121
+ remainderReviews.push(review);
122
+ }
123
+ }
124
+ const archiveLog = [];
125
+ const remainderLog = [];
126
+ for (const entry of data.log ?? []) {
127
+ if (versionOfLogEntry(entry) === version) {
128
+ archiveLog.push(entry);
129
+ }
130
+ else {
131
+ remainderLog.push(entry);
132
+ }
133
+ }
134
+ return {
135
+ archive: {
136
+ version,
137
+ stages: archiveStages,
138
+ reviews: archiveReviews,
139
+ log: archiveLog,
140
+ counts: {
141
+ stages: Object.keys(archiveStages).length,
142
+ reviews: archiveReviews.length,
143
+ log: archiveLog.length,
144
+ },
145
+ },
146
+ remainder: {
147
+ stages: remainderStages,
148
+ reviews: remainderReviews,
149
+ log: remainderLog,
150
+ },
151
+ };
152
+ }
153
+ //# sourceMappingURL=grouping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grouping.js","sourceRoot":"","sources":["../../../src/core/flow-archive/grouping.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
- import { type PipelinePhase, type MetaPhase, type StageStats } from './pipeline-schema.js';
1
+ import { type PipelinePhase, type MetaPhase, type StageStats, type ReviewTier } from './pipeline-schema.js';
2
2
  export { type PipelinePhase, type MetaPhase, type StageStats } from './pipeline-schema.js';
3
3
  import { type WorkflowMode } from './config.js';
4
+ import type { RequirementData } from './req/types.js';
4
5
  /** 操作执行状态(B4/A1:新增 draft = 已创建未发布) */
5
6
  export type OpState = 'pending' | 'executing' | 'done' | 'failed' | 'draft';
6
7
  /** 检查点结构 */
@@ -23,6 +24,10 @@ export interface Op {
23
24
  attempts: number;
24
25
  max_attempts: number;
25
26
  checkpoints: Checkpoints;
27
+ /** 最近一次 attempt 记录时间(ISO;可选——存量 op 缺省,读侧宽容不注入) */
28
+ attempted_at?: string;
29
+ /** 进入 done / failed 终态时间(ISO;可选——读侧宽容不注入) */
30
+ completed_at?: string;
26
31
  }
27
32
  /** 审查条目 */
28
33
  export interface ReviewItem {
@@ -64,7 +69,9 @@ export interface StageData {
64
69
  /** 阶段耗时统计(可选,运行时填充) */
65
70
  stats?: StageStats;
66
71
  /** 工作流模式(可选冗余,不参与级联;读取权威恒为 status.md/级联,裁定 G) */
67
- workflow_mode?: 'full' | 'fast';
72
+ workflow_mode?: WorkflowMode;
73
+ /** 关联需求 ID(单值可空;存量缺省 ≡ null,不迁移——裁定 D/L) */
74
+ requirement_id?: string | null;
68
75
  }
69
76
  /** Flow 完整数据结构 */
70
77
  export interface FlowData {
@@ -73,6 +80,8 @@ export interface FlowData {
73
80
  project: string;
74
81
  updated: string;
75
82
  revision?: number;
83
+ /** 已归档版本前缀索引(轻量索引,裁定 Q;缺失视为 [],由 getArchivedVersions 承担——REV-78-015 移除 load 读侧补齐) */
84
+ archived_versions?: string[];
76
85
  };
77
86
  pipeline: {
78
87
  phase: MetaPhase;
@@ -84,6 +93,8 @@ export interface FlowData {
84
93
  };
85
94
  stages: Record<string, StageData>;
86
95
  reviews: ReviewItem[];
96
+ /** 需求条目(顶层可选;读侧宽容——getRequirements 派生 ?? [],**不** load 补齐,REV-81-003) */
97
+ requirements?: RequirementData[];
87
98
  log: LogEntry[];
88
99
  }
89
100
  /** flow.json 乐观并发冲突:磁盘 revision 与本次加载时不一致(存在并发写入) */
@@ -123,7 +134,7 @@ export interface CascadeConfig {
123
134
  effective: Record<string, string>;
124
135
  }
125
136
  /** 配置生效来源枚举(`config effective` 出口,P9) */
126
- export type ConfigSource = 'status.md' | 'config.yaml' | 'profile.yaml' | 'builtin';
137
+ export type ConfigSource = 'status.md' | 'config.yaml' | 'profile.yaml' | 'builtin' | 'requirement';
127
138
  /** verbose 模式下的状态变更记录 */
128
139
  export interface RecentChange {
129
140
  time: string;
@@ -269,6 +280,13 @@ export interface PhasePathResult {
269
280
  }
270
281
  /** findPhasePath BFS 深度上限(B5-1;超出按拒绝处理) */
271
282
  export declare const PHASE_PATH_MAX_DEPTH = 8;
283
+ /**
284
+ * 工作流模式 → 派生的审查档位(**单源常量**;stage-92 D-92-3)。
285
+ * 仅用于「展示 / 提示」(`flow phases` 档位分节行与 `--json.reviewTier`);**不参与推进语义**
286
+ * (`flow advance` 的有效档位 = 显式声明 ?? full,缺省恒零裁剪 ⟹ 既有流程零回归)。
287
+ * general(无流水线)恒全量语义,不特判。
288
+ */
289
+ export declare const MODE_DERIVED_TIER: Record<WorkflowMode, ReviewTier>;
272
290
  export declare class FlowManager {
273
291
  private projectPath;
274
292
  private data;
@@ -456,14 +474,28 @@ export declare class FlowManager {
456
474
  stage: string;
457
475
  op: string;
458
476
  };
477
+ /**
478
+ * T4:跨会话恢复判定。从 log 尾部回扫(本次条目尚未写入)取该 stage 最近一条
479
+ * advance_stage_phase / reset_stage_phase 条目(要求 detail.to === exitedPhase);
480
+ * 任一侧不可信 / 缺失 → 返回 null(调用方省略 resumed 字段,不臆断;proc: 级不参与跨会话判定)。
481
+ * @param log 审计日志(本次条目尚未 append)
482
+ * @param stageKey 阶段键(完整 stageId)
483
+ * @param exitedPhase 本次转换退出的 phase(= 上一次转换进入的 phase)
484
+ * @param currentId 本次会话标识(含来源前缀)
485
+ * @returns true = 异会话恢复;false = 同会话;null = 不可判定(省略字段)
486
+ */
487
+ private computeResumed;
459
488
  /**
460
489
  * 推进指定阶段到目标流水线阶段(新 API)
461
490
  * @param stageName 阶段名(如 stage-01)
462
491
  * @param phase 目标流水线阶段(PipelinePhase)
463
492
  * @param triggeredBy 触发者标识(如 'cli' / 'Feel'),默认 'flow-manager'
493
+ * @param opts.reviewTier 本次调用的审查档位(**仅显式声明时写审计键** `review_tier`;缺省不写 ⟹ 既有条目逐字不变)
464
494
  * @returns 是否触发 done 归档(true = 本次推进将阶段变为 done 且之前非 done,需在 save 后执行归档 commit)
465
495
  */
466
- advanceStagePhase(stageName: string, phase: PipelinePhase, triggeredBy?: string): boolean;
496
+ advanceStagePhase(stageName: string, phase: PipelinePhase, triggeredBy?: string, opts?: {
497
+ reviewTier?: ReviewTier;
498
+ }): boolean;
467
499
  /**
468
500
  * 复位指定阶段的 phase(允许越过正向转移表回退/前进)。
469
501
  * 约束:
@@ -543,6 +575,7 @@ export declare class FlowManager {
543
575
  * 无 '|' 的简单 key 保持单条件原行为(精确相等)。
544
576
  * @param fromPhase 当前源 phase
545
577
  * @param mode 工作流模式(**必传**;由调用点经 resolveStageWorkflowMode 从 stage 派生——禁止默认 mode)
578
+ * @param tier 审查档位(可选,缺省 'full' = 零裁剪 ⟹ 既有调用逐字零回归)
546
579
  * @returns 去重后的可达目标 phase 列表
547
580
  */
548
581
  private getValidTargets;
@@ -556,26 +589,49 @@ export declare class FlowManager {
556
589
  * 获取当前阶段的所有可达下一阶段(基于 transitions 表,支持组合条件)
557
590
  * 供 flow wizard 交互模式使用
558
591
  * @param stageName 可选阶段名,提供时使用该阶段 phase 进行查找;否则使用 current.stage 的 phase
592
+ * @param tier 审查档位(可选,缺省 'full' = 零裁剪)
559
593
  */
560
- getAvailablePhases(stageName?: string): PipelinePhase[];
594
+ getAvailablePhases(stageName?: string, tier?: ReviewTier): PipelinePhase[];
561
595
  /**
562
596
  * 在运行时 transitions 上求 stageName 当前 phase → to 的**唯一**可达路径(B5-1)。
563
597
  * BFS(深度上限 PHASE_PATH_MAX_DEPTH);最短深度上存在多条路径 → ambiguous(拒绝,不猜测)。
564
598
  * 纯只读:不写盘、不改内存。
565
599
  * @param stageName 阶段 ID
566
600
  * @param to 目标 phase
601
+ * @param tier 审查档位(可选,缺省 'full' = 零裁剪;显式声明时按裁剪表求路径)
567
602
  * @returns 路径与原因(no-path/ambiguous/depth-exceeded 供命令层报错;already-at-target 按 no-op)
568
603
  */
569
- findPhasePath(stageName: string, to: string): PhasePathResult;
604
+ findPhasePath(stageName: string, to: string, tier?: ReviewTier): PhasePathResult;
570
605
  /**
571
606
  * 获取运行时生效的 phase 列表(供 flow phases 自描述)。
572
607
  * 数据源 = this.pipelineConfig(构造时经 loadPipelineConfig 加载 .openfeel/pipeline.yaml);
573
608
  * 缺省/解析失败时回退内置默认配置。返回副本,避免调用方修改内部状态。
574
609
  */
575
610
  getPipelinePhases(): string[];
611
+ /**
612
+ * ═══ 审查档位裁剪(stage-92 D-92-2 / D-92-3;**唯一调用点** = resolveTransitionsForMode)═══
613
+ * 纯函数:不改入参(不污染 FAST_TRANSITIONS 常量 / pipelineConfig),恒返回新对象 + 新数组。
614
+ * 算法:
615
+ * ① `tier === 'full'` → 深拷贝原样返回(零裁剪 ⟹ 既有调用逐字零回归);
616
+ * ② 各 key 的 targets 过滤被裁剪相位(目标侧裁剪 = 跳过该审查关);
617
+ * ③ **边收缩回退**——若某 key 过滤后 targets 为空且原 targets 非空,则并入「被删目标的 targets」
618
+ * (再过滤被裁剪项与自身)后去重。该回退保证 fast 基表裁 `plan_review` 后 `plan_pending` 非空集
619
+ * (`['plan_review']` → 收缩 `plan_review` 的 targets → `plan_pending → ['plan_passed']`)。
620
+ * **裁剪只作用于入边 / 目标侧**:被裁剪相位保留出边(阶段已处于被裁剪关时可向该档位下游推进);
621
+ * 「**进入**被裁剪关」仍被目标侧过滤裁掉。
622
+ * 边界(登记):含被裁剪相位的**组合 key**(如 `'scheme_review|x'`)按 ② 过滤 targets(不再整键删除);
623
+ * 内置 full/fast 表与本仓运行时表均无此类键(唯一组合键 `'review_passed|test_passed'` 不含被裁剪相位)。
624
+ * @param table 模式基表(由 resolveTransitionsForMode 按既有三分支解析所得)
625
+ * @param tier 审查档位
626
+ * @returns 裁剪后的新表
627
+ */
628
+ private pruneTransitionsByTier;
576
629
  /**
577
630
  * 按模式解析运行时转移表(唯一取表法则;校验/推进/路径 与 展示 同经本函数 ⇒ 单一信源)。
578
631
  * full → 运行时 pipelineConfig.transitions(pipeline.yaml 覆盖线);fast → fast_transitions ?? 内置快线表。
632
+ * general → 空表(无流水线;模式语义本身,非「缺省回落」)。
633
+ * 审查档位为**第二参延伸**(stage-92 D-92-3):末尾统一经 pruneTransitionsByTier 裁剪;
634
+ * 缺省 tier='full' = 零裁剪 ⟹ 与原逻辑等价(唯一取表点不变,禁第二份层序 / 取表)。
579
635
  * 说明:pipelineConfig 构造期恒已加载(constructor 无条件调用),`?? 内置` 保留既有访问器回退语义。
580
636
  */
581
637
  private resolveTransitionsForMode;
@@ -583,8 +639,9 @@ export declare class FlowManager {
583
639
  * 获取运行时生效的 phase 转移表(key 可含 '|' 组合条件)。
584
640
  * 缺省/解析失败时回退内置默认配置。返回浅拷贝。
585
641
  * **无参 = full**(零回归既有消费点);fast 供 stage-75 `flow phases` 模式分节消费。
642
+ * @param tier 审查档位(可选,缺省 'full' = 零裁剪)
586
643
  */
587
- getPipelineTransitions(mode?: WorkflowMode): Record<string, string[]>;
644
+ getPipelineTransitions(mode?: WorkflowMode, tier?: ReviewTier): Record<string, string[]>;
588
645
  /**
589
646
  * 获取内置默认 phase 转移表(不受项目 pipeline.yaml 影响)。
590
647
  * 供 `flow phases` 做「运行时 vs 默认」差异报告(T19)。
@@ -631,11 +688,11 @@ export declare class FlowManager {
631
688
  /**
632
689
  * 解析指定阶段的工作流模式(裁定 E:「stage → mode」表选择解析入口;stage-75 `flow mode` 契约)。
633
690
  * 带 stage 形参独立解析(REV-74-001):该阶段 status.md(findStatusPath(stageId))
634
- * > config.yaml defaults > profile.yaml(**有意不承载**,实际不产出)> builtin(DEFAULT_CONFIG.workflow_mode = fast)。
691
+ * > 需求级(关联需求携带的 workflow_mode)> config.yaml defaults > profile.yaml(**有意不承载**,实际不产出)> builtin(DEFAULT_CONFIG.workflow_mode = fast)。
635
692
  * 语义边界:`config effective` / `flow status --verbose` = **当前阶段域**(无参);
636
693
  * 本方法显式 stageId = **任意阶段域**(flow mode --stage / 表选择)。
637
694
  * @param stageId 目标阶段 ID(缺省 = pipeline.current.stage;无该阶段 status.md → 自然回落下一层)
638
- * @returns { value, source },source ∈ status.md | config.yaml | profile.yaml | builtin
695
+ * @returns { value, source },source ∈ status.md | requirement | config.yaml | profile.yaml | builtin
639
696
  */
640
697
  resolveStageWorkflowMode(stageId?: string): {
641
698
  value: WorkflowMode;
@@ -896,6 +953,7 @@ export declare class FlowManager {
896
953
  * 当 pipeline.yaml 不存在或解析失败时使用。
897
954
  * @param mode 工作流模式;**无参 = full**(内置 full 表逐字等于历史现状,零回归既有消费点);
898
955
  * 'fast' 返回快线表(FAST_TRANSITIONS 常量引用)。phases / checkpoint_mapping / phase_corrections 两模式共用。
956
+ * 'general' 返回空转移表(无流水线;phases 仍 15 项)。
899
957
  */
900
958
  private getDefaultPipelineConfig;
901
959
  }