openfeel 1.1.6 → 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.
- package/README.en.md +25 -6
- package/README.md +17 -2
- package/README.zh-CN.md +25 -6
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +85 -4
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/flow.d.ts +8 -0
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +447 -15
- package/dist/commands/flow.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +71 -7
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/req.d.ts +14 -0
- package/dist/commands/req.d.ts.map +1 -0
- package/dist/commands/req.js +562 -0
- package/dist/commands/req.js.map +1 -0
- package/dist/commands/stage.d.ts.map +1 -1
- package/dist/commands/stage.js +56 -2
- package/dist/commands/stage.js.map +1 -1
- package/dist/core/config.d.ts +17 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +21 -6
- package/dist/core/config.js.map +1 -1
- package/dist/core/flow-archive/archive-file.d.ts +35 -0
- package/dist/core/flow-archive/archive-file.d.ts.map +1 -0
- package/dist/core/flow-archive/archive-file.js +3 -0
- package/dist/core/flow-archive/archive-file.js.map +1 -0
- package/dist/core/flow-archive/archive-health.d.ts +34 -0
- package/dist/core/flow-archive/archive-health.d.ts.map +1 -0
- package/dist/core/flow-archive/archive-health.js +76 -0
- package/dist/core/flow-archive/archive-health.js.map +1 -0
- package/dist/core/flow-archive/archive-ops.d.ts +68 -0
- package/dist/core/flow-archive/archive-ops.d.ts.map +1 -0
- package/dist/core/flow-archive/archive-ops.js +184 -0
- package/dist/core/flow-archive/archive-ops.js.map +1 -0
- package/dist/core/flow-archive/grouping.d.ts +58 -0
- package/dist/core/flow-archive/grouping.d.ts.map +1 -0
- package/dist/core/flow-archive/grouping.js +153 -0
- package/dist/core/flow-archive/grouping.js.map +1 -0
- package/dist/core/flow-manager.d.ts +78 -14
- package/dist/core/flow-manager.d.ts.map +1 -1
- package/dist/core/flow-manager.js +311 -55
- package/dist/core/flow-manager.js.map +1 -1
- package/dist/core/i18n-data/en.d.ts +1 -0
- package/dist/core/i18n-data/en.d.ts.map +1 -1
- package/dist/core/i18n-data/en.js +159 -3
- package/dist/core/i18n-data/en.js.map +1 -1
- package/dist/core/i18n-data/zh-CN.d.ts +1 -0
- package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
- package/dist/core/i18n-data/zh-CN.js +161 -3
- package/dist/core/i18n-data/zh-CN.js.map +1 -1
- package/dist/core/i18n.js +4 -4
- package/dist/core/i18n.js.map +1 -1
- package/dist/core/pipeline-schema.d.ts +5 -0
- package/dist/core/pipeline-schema.d.ts.map +1 -1
- package/dist/core/pipeline-schema.js +5 -0
- package/dist/core/pipeline-schema.js.map +1 -1
- package/dist/core/plan/path.d.ts +17 -0
- package/dist/core/plan/path.d.ts.map +1 -1
- package/dist/core/plan/path.js +31 -0
- package/dist/core/plan/path.js.map +1 -1
- package/dist/core/plan/scheme.d.ts +22 -0
- package/dist/core/plan/scheme.d.ts.map +1 -1
- package/dist/core/plan/scheme.js +71 -2
- package/dist/core/plan/scheme.js.map +1 -1
- package/dist/core/plan/stage.d.ts +2 -0
- package/dist/core/plan/stage.d.ts.map +1 -1
- package/dist/core/plan/stage.js +8 -0
- package/dist/core/plan/stage.js.map +1 -1
- package/dist/core/req/aggregate.d.ts +61 -0
- package/dist/core/req/aggregate.d.ts.map +1 -0
- package/dist/core/req/aggregate.js +134 -0
- package/dist/core/req/aggregate.js.map +1 -0
- package/dist/core/req/body-file.d.ts +16 -0
- package/dist/core/req/body-file.d.ts.map +1 -0
- package/dist/core/req/body-file.js +80 -0
- package/dist/core/req/body-file.js.map +1 -0
- package/dist/core/req/requirement-ops.d.ts +74 -0
- package/dist/core/req/requirement-ops.d.ts.map +1 -0
- package/dist/core/req/requirement-ops.js +290 -0
- package/dist/core/req/requirement-ops.js.map +1 -0
- package/dist/core/req/types.d.ts +40 -0
- package/dist/core/req/types.d.ts.map +1 -0
- package/dist/core/req/types.js +13 -0
- package/dist/core/req/types.js.map +1 -0
- package/dist/core/setup.js +4 -4
- package/dist/core/template-loader.d.ts.map +1 -1
- package/dist/core/template-loader.js +649 -65
- package/dist/core/template-loader.js.map +1 -1
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +131 -15
- package/dist/core/update.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,14 +13,15 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, readd
|
|
|
13
13
|
import { resolve, dirname, basename } from 'node:path';
|
|
14
14
|
import { execFileSync } from 'node:child_process';
|
|
15
15
|
import { parse as parseYaml } from 'yaml';
|
|
16
|
-
import { PipelineConfigSchema, PipelinePhaseSchema, PIPELINE_PHASES, MetaPhaseSchema, META_PHASES, transitionKeyMatches, } from './pipeline-schema.js';
|
|
16
|
+
import { PipelineConfigSchema, PipelinePhaseSchema, PIPELINE_PHASES, MetaPhaseSchema, META_PHASES, transitionKeyMatches, FastTransitionsSchema, } from './pipeline-schema.js';
|
|
17
17
|
import { PublicLogger, formatDate } from './public-logger.js';
|
|
18
18
|
import { t, getCliLang } from './i18n.js';
|
|
19
|
-
import { findStageStatusPath, findStageDirConflict, parseStageId, normalizeStageId } from './plan/path.js';
|
|
19
|
+
import { findStageStatusPath, findStageDirConflict, parseStageId, normalizeStageId, isArchivedVersionOf } from './plan/path.js';
|
|
20
20
|
import { atomicWriteFileSync } from './fs/atomic-write.js';
|
|
21
21
|
import { withFileLock, projectLockPath } from './fs/file-lock.js';
|
|
22
22
|
import { DEFAULT_CONFIG, ConfigDefaultsSchema, STAGE_FIELD_BY_CONFIG_KEY } from './config.js';
|
|
23
23
|
import { getGlobalProfilePath } from './global-paths.js';
|
|
24
|
+
import { syncRequirementStatus } from './req/aggregate.js'; // 需求聚合回写入口(stage-83 T2;纯内存)
|
|
24
25
|
/** flow.json 乐观并发冲突:磁盘 revision 与本次加载时不一致(存在并发写入) */
|
|
25
26
|
export class FlowConcurrentModificationError extends Error {
|
|
26
27
|
/** 本次实例加载时的 revision */
|
|
@@ -52,8 +53,8 @@ export class StageDirConflictError extends Error {
|
|
|
52
53
|
this.other = other;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
|
-
/** `openfeel config effective`
|
|
56
|
-
const EFFECTIVE_CONFIG_KEYS = ['execution_mode', 'auto_advance', 'merge_mode'];
|
|
56
|
+
/** `openfeel config effective` 的四个受管键(与 DEFAULT_CONFIG 一致) */
|
|
57
|
+
const EFFECTIVE_CONFIG_KEYS = ['execution_mode', 'auto_advance', 'merge_mode', 'workflow_mode'];
|
|
57
58
|
// ── 默认值 ──
|
|
58
59
|
/** 创建默认的 Checkpoints */
|
|
59
60
|
function defaultCheckpoints() {
|
|
@@ -163,6 +164,23 @@ export function findOrphanOps(projectPath) {
|
|
|
163
164
|
}
|
|
164
165
|
/** findPhasePath BFS 深度上限(B5-1;超出按拒绝处理) */
|
|
165
166
|
export const PHASE_PATH_MAX_DEPTH = 8;
|
|
167
|
+
/**
|
|
168
|
+
* fast 模式内置快线转移表(裁定 C):8 条流转键 + `done` 终止项(共 9 条目)。
|
|
169
|
+
* 单一来源:仅 getDefaultPipelineConfig('fast') 消费;禁止在命令层/模板层复制(R-74-1)。
|
|
170
|
+
* `plan_review` 在 fast 下必经(plan_pending → plan_review → plan_passed;通过 → plan_passed,问题退回 → plan_pending)。
|
|
171
|
+
* review_failed 双出口 = 失败回环(重审 或 回执行修复,对应 full 表 scheme_pending 位);Q3 收紧项留审查裁定。
|
|
172
|
+
*/
|
|
173
|
+
const FAST_TRANSITIONS = {
|
|
174
|
+
plan_pending: ['plan_review'],
|
|
175
|
+
plan_review: ['plan_passed', 'plan_pending'],
|
|
176
|
+
plan_passed: ['exec_running'],
|
|
177
|
+
exec_running: ['review_pending'],
|
|
178
|
+
review_pending: ['review_failed', 'review_passed'],
|
|
179
|
+
review_failed: ['review_pending', 'exec_running'],
|
|
180
|
+
review_passed: ['archiving'],
|
|
181
|
+
archiving: ['done'],
|
|
182
|
+
done: [],
|
|
183
|
+
};
|
|
166
184
|
// ── 核心类 ──
|
|
167
185
|
export class FlowManager {
|
|
168
186
|
projectPath;
|
|
@@ -172,6 +190,8 @@ export class FlowManager {
|
|
|
172
190
|
filePath;
|
|
173
191
|
/** 从 pipeline.yaml 加载的流水线配置(含后备默认值) */
|
|
174
192
|
pipelineConfig = null;
|
|
193
|
+
/** 从 pipeline.yaml 加载的可选快线表(fast_transitions);null = 缺失/非法/空对象 → 回退内置快线表 [REV-74-009 修订] */
|
|
194
|
+
fastTransitions = null;
|
|
175
195
|
/** 公共日志写入器(审计链) */
|
|
176
196
|
publicLogger;
|
|
177
197
|
constructor(projectPath) {
|
|
@@ -1123,6 +1143,9 @@ export class FlowManager {
|
|
|
1123
1143
|
// 阶段完成时自动结束计时
|
|
1124
1144
|
if (targetPhase === 'done' && prevStatus !== 'done') {
|
|
1125
1145
|
this.endStage(key);
|
|
1146
|
+
// 需求聚合回写(T2):仅 done 分支触发(非 done 推进不触发——REV-83-005);
|
|
1147
|
+
// 纯内存,落盘由命令层既有单次 save() 承担(同事务,不新增写盘点);不改控制流与返回值。
|
|
1148
|
+
syncRequirementStatus(this.data, key);
|
|
1126
1149
|
// 归档 git commit 移出本方法:必须在 flow.json save 之后执行(否则 commit 不含本次 phase 变更)。
|
|
1127
1150
|
// 返回 true 由命令层在 save 后调用 autoCommitOnDone。
|
|
1128
1151
|
return true;
|
|
@@ -1187,6 +1210,9 @@ export class FlowManager {
|
|
|
1187
1210
|
action: 'reset_stage_phase',
|
|
1188
1211
|
detail: { stageName: key, from: fromPhase, to: targetPhase },
|
|
1189
1212
|
});
|
|
1213
|
+
// 需求聚合对称回退(T2):聚合视图重算——聚合 ≠ done ∧ 需求 status === done → 回退 in_progress;
|
|
1214
|
+
// 纯内存;dryRun / no-op 已在 :1585 早退(本点不可达);不新增写盘点。
|
|
1215
|
+
syncRequirementStatus(this.data, key);
|
|
1190
1216
|
// 仅当 current 指向该阶段时同步 op 指针(其它阶段的 current 不被动)
|
|
1191
1217
|
const curStage = this.data.pipeline.current.stage;
|
|
1192
1218
|
const normCur = curStage ? (normalizeStageId(curStage) ?? curStage) : '';
|
|
@@ -1477,10 +1503,11 @@ export class FlowManager {
|
|
|
1477
1503
|
* 遍历 transitions 表,key 与 fromPhase 匹配(组合 key 如 'test_passed|review_passed' 任一条件匹配即可)即收集其目标。
|
|
1478
1504
|
* 无 '|' 的简单 key 保持单条件原行为(精确相等)。
|
|
1479
1505
|
* @param fromPhase 当前源 phase
|
|
1506
|
+
* @param mode 工作流模式(**必传**;由调用点经 resolveStageWorkflowMode 从 stage 派生——禁止默认 mode)
|
|
1480
1507
|
* @returns 去重后的可达目标 phase 列表
|
|
1481
1508
|
*/
|
|
1482
|
-
getValidTargets(fromPhase) {
|
|
1483
|
-
const transitions = this.
|
|
1509
|
+
getValidTargets(fromPhase, mode) {
|
|
1510
|
+
const transitions = this.resolveTransitionsForMode(mode);
|
|
1484
1511
|
const result = new Set();
|
|
1485
1512
|
for (const [key, targets] of Object.entries(transitions)) {
|
|
1486
1513
|
if (transitionKeyMatches(key, fromPhase)) {
|
|
@@ -1503,7 +1530,8 @@ export class FlowManager {
|
|
|
1503
1530
|
const phase = this.resolveCurrentPhase(stageName);
|
|
1504
1531
|
if (!phase)
|
|
1505
1532
|
return false;
|
|
1506
|
-
|
|
1533
|
+
const mode = this.resolveStageWorkflowMode(stageName).value;
|
|
1534
|
+
return this.getValidTargets(phase, mode).includes(to);
|
|
1507
1535
|
}
|
|
1508
1536
|
/**
|
|
1509
1537
|
* 获取当前阶段的所有可达下一阶段(基于 transitions 表,支持组合条件)
|
|
@@ -1520,7 +1548,8 @@ export class FlowManager {
|
|
|
1520
1548
|
const currentPhase = this.resolveCurrentPhase(stageName);
|
|
1521
1549
|
if (!currentPhase)
|
|
1522
1550
|
return [];
|
|
1523
|
-
|
|
1551
|
+
const mode = this.resolveStageWorkflowMode(stageName).value;
|
|
1552
|
+
return this.getValidTargets(currentPhase, mode);
|
|
1524
1553
|
}
|
|
1525
1554
|
/**
|
|
1526
1555
|
* 在运行时 transitions 上求 stageName 当前 phase → to 的**唯一**可达路径(B5-1)。
|
|
@@ -1542,6 +1571,8 @@ export class FlowManager {
|
|
|
1542
1571
|
if (start === to) {
|
|
1543
1572
|
return { path: [], reason: 'already-at-target' };
|
|
1544
1573
|
}
|
|
1574
|
+
// 模式在 BFS 外解析一次(stage 派生;同一 T 内 mode 恒定——单 stage 只读一张表)
|
|
1575
|
+
const mode = this.resolveStageWorkflowMode(key).value;
|
|
1545
1576
|
// BFS 逐层扩展;visitedDepth 记录各 phase 首次到达的最小深度(剪枝,避免重复/环)
|
|
1546
1577
|
const visitedDepth = new Map();
|
|
1547
1578
|
visitedDepth.set(start, 0);
|
|
@@ -1554,7 +1585,7 @@ export class FlowManager {
|
|
|
1554
1585
|
if (node.path.length >= PHASE_PATH_MAX_DEPTH) {
|
|
1555
1586
|
continue;
|
|
1556
1587
|
}
|
|
1557
|
-
for (const target of this.getValidTargets(node.phase)) {
|
|
1588
|
+
for (const target of this.getValidTargets(node.phase, mode)) {
|
|
1558
1589
|
const path = [...node.path, target];
|
|
1559
1590
|
if (target === to) {
|
|
1560
1591
|
hits.push(path);
|
|
@@ -1591,21 +1622,34 @@ export class FlowManager {
|
|
|
1591
1622
|
const phases = this.pipelineConfig?.phases ?? this.getDefaultPipelineConfig().phases;
|
|
1592
1623
|
return [...phases];
|
|
1593
1624
|
}
|
|
1625
|
+
/**
|
|
1626
|
+
* 按模式解析运行时转移表(唯一取表法则;校验/推进/路径 与 展示 同经本函数 ⇒ 单一信源)。
|
|
1627
|
+
* full → 运行时 pipelineConfig.transitions(pipeline.yaml 覆盖线);fast → fast_transitions ?? 内置快线表。
|
|
1628
|
+
* 说明:pipelineConfig 构造期恒已加载(constructor 无条件调用),`?? 内置` 保留既有访问器回退语义。
|
|
1629
|
+
*/
|
|
1630
|
+
resolveTransitionsForMode(mode) {
|
|
1631
|
+
if (mode === 'fast') {
|
|
1632
|
+
return this.fastTransitions ?? this.getDefaultPipelineConfig('fast').transitions;
|
|
1633
|
+
}
|
|
1634
|
+
return this.pipelineConfig?.transitions ?? this.getDefaultPipelineConfig().transitions;
|
|
1635
|
+
}
|
|
1594
1636
|
/**
|
|
1595
1637
|
* 获取运行时生效的 phase 转移表(key 可含 '|' 组合条件)。
|
|
1596
1638
|
* 缺省/解析失败时回退内置默认配置。返回浅拷贝。
|
|
1639
|
+
* **无参 = full**(零回归既有消费点);fast 供 stage-75 `flow phases` 模式分节消费。
|
|
1597
1640
|
*/
|
|
1598
|
-
getPipelineTransitions() {
|
|
1599
|
-
const transitions = this.
|
|
1641
|
+
getPipelineTransitions(mode = 'full') {
|
|
1642
|
+
const transitions = this.resolveTransitionsForMode(mode);
|
|
1600
1643
|
return Object.fromEntries(Object.entries(transitions).map(([k, v]) => [k, [...v]]));
|
|
1601
1644
|
}
|
|
1602
1645
|
/**
|
|
1603
1646
|
* 获取内置默认 phase 转移表(不受项目 pipeline.yaml 影响)。
|
|
1604
1647
|
* 供 `flow phases` 做「运行时 vs 默认」差异报告(T19)。
|
|
1648
|
+
* **无参 = full**(零回归既有消费点);fast 供 stage-75 `flow phases` 模式分节消费。
|
|
1605
1649
|
* @returns 默认 transitions 的深拷贝
|
|
1606
1650
|
*/
|
|
1607
|
-
getDefaultTransitions() {
|
|
1608
|
-
return Object.fromEntries(Object.entries(this.getDefaultPipelineConfig().transitions).map(([k, v]) => [k, [...v]]));
|
|
1651
|
+
getDefaultTransitions(mode = 'full') {
|
|
1652
|
+
return Object.fromEntries(Object.entries(this.getDefaultPipelineConfig(mode).transitions).map(([k, v]) => [k, [...v]]));
|
|
1609
1653
|
}
|
|
1610
1654
|
/**
|
|
1611
1655
|
* 解析当前阶段 phase:优先使用传入的 stageName,其次 current.stage,最后 fallback
|
|
@@ -1698,10 +1742,13 @@ export class FlowManager {
|
|
|
1698
1742
|
}
|
|
1699
1743
|
/**
|
|
1700
1744
|
* 构建配置级联信息(优先级:profileDefaults < configDefaults < statusOverrides)
|
|
1701
|
-
* 全局画像仅作最低优先级兜底(P2),且只取 preferences.auto_advance
|
|
1702
|
-
*
|
|
1745
|
+
* 全局画像仅作最低优先级兜底(P2),且只取 preferences.auto_advance 单值,**不扩展** workflow_mode。
|
|
1746
|
+
* @param stageId 可选目标阶段(stage 形参唯一入口);缺省 = pipeline.current.stage
|
|
1747
|
+
* (**当前阶段域**,无参行为逐字不变)。任意阶段域由 resolveStageWorkflowMode(stageId) 显式传入。
|
|
1748
|
+
* 静态约束(REV-74-001/002):无参调用**仅允许**出现在既有两出口 resolveEffectiveConfig()
|
|
1749
|
+
* 与 verboseSummary();新增消费点一律显式传 stage。
|
|
1703
1750
|
*/
|
|
1704
|
-
buildCascadeConfig() {
|
|
1751
|
+
buildCascadeConfig(stageId) {
|
|
1705
1752
|
const profileDefaults = {};
|
|
1706
1753
|
const configDefaults = {};
|
|
1707
1754
|
const statusOverrides = {};
|
|
@@ -1752,22 +1799,26 @@ export class FlowManager {
|
|
|
1752
1799
|
// 解析失败则用空值
|
|
1753
1800
|
}
|
|
1754
1801
|
}
|
|
1755
|
-
//
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
const statusPath = this.findStatusPath(
|
|
1802
|
+
// 最高优先级:目标 stage 的 status.md(缺省 = pipeline.current.stage;stage 形参路径读该阶段)
|
|
1803
|
+
const targetStage = stageId ?? this.data?.pipeline.current.stage;
|
|
1804
|
+
if (targetStage) {
|
|
1805
|
+
const statusPath = this.findStatusPath(targetStage);
|
|
1759
1806
|
if (statusPath) {
|
|
1760
1807
|
try {
|
|
1761
1808
|
const content = readFileSync(statusPath, 'utf-8');
|
|
1762
|
-
//
|
|
1809
|
+
// 匹配执行模式、自动推进、工作流模式
|
|
1763
1810
|
const execMatch = content.match(/\*\*执行模式\*\*[::]\s*(manual|auto)/);
|
|
1764
1811
|
const autoMatch = content.match(/\*\*自动推进\*\*[::]\s*(disabled|enabled)/);
|
|
1812
|
+
const modeMatch = content.match(/\*\*工作流模式\*\*[::]\s*(full|fast)/);
|
|
1765
1813
|
if (execMatch) {
|
|
1766
1814
|
statusOverrides['execution_mode'] = execMatch[1];
|
|
1767
1815
|
}
|
|
1768
1816
|
if (autoMatch) {
|
|
1769
1817
|
statusOverrides['auto_advance'] = autoMatch[1];
|
|
1770
1818
|
}
|
|
1819
|
+
if (modeMatch) {
|
|
1820
|
+
statusOverrides['workflow_mode'] = modeMatch[1];
|
|
1821
|
+
}
|
|
1771
1822
|
}
|
|
1772
1823
|
catch {
|
|
1773
1824
|
// 读取失败则跳过
|
|
@@ -1779,7 +1830,7 @@ export class FlowManager {
|
|
|
1779
1830
|
return { profileDefaults, configDefaults, statusOverrides, effective };
|
|
1780
1831
|
}
|
|
1781
1832
|
/**
|
|
1782
|
-
*
|
|
1833
|
+
* 解析四个受管配置键的「有效值 + 生效来源」(`openfeel config effective`,P9)。
|
|
1783
1834
|
* 复用 buildCascadeConfig(单一权威,避免第二套解析),优先级:
|
|
1784
1835
|
* status.md > config.yaml > profile.yaml > builtin。
|
|
1785
1836
|
* @returns key → { value, source }
|
|
@@ -1804,6 +1855,30 @@ export class FlowManager {
|
|
|
1804
1855
|
}
|
|
1805
1856
|
return result;
|
|
1806
1857
|
}
|
|
1858
|
+
/**
|
|
1859
|
+
* 解析指定阶段的工作流模式(裁定 E:「stage → mode」表选择解析入口;stage-75 `flow mode` 契约)。
|
|
1860
|
+
* 带 stage 形参独立解析(REV-74-001):该阶段 status.md(findStatusPath(stageId))
|
|
1861
|
+
* > config.yaml defaults > profile.yaml(**有意不承载**,实际不产出)> builtin(DEFAULT_CONFIG.workflow_mode = fast)。
|
|
1862
|
+
* 语义边界:`config effective` / `flow status --verbose` = **当前阶段域**(无参);
|
|
1863
|
+
* 本方法显式 stageId = **任意阶段域**(flow mode --stage / 表选择)。
|
|
1864
|
+
* @param stageId 目标阶段 ID(缺省 = pipeline.current.stage;无该阶段 status.md → 自然回落下一层)
|
|
1865
|
+
* @returns { value, source },source ∈ status.md | config.yaml | profile.yaml | builtin
|
|
1866
|
+
*/
|
|
1867
|
+
resolveStageWorkflowMode(stageId) {
|
|
1868
|
+
const cascade = this.buildCascadeConfig(stageId);
|
|
1869
|
+
const key = 'workflow_mode';
|
|
1870
|
+
if (cascade.statusOverrides[key] !== undefined) {
|
|
1871
|
+
return { value: cascade.statusOverrides[key], source: 'status.md' };
|
|
1872
|
+
}
|
|
1873
|
+
if (cascade.configDefaults[key] !== undefined) {
|
|
1874
|
+
return { value: cascade.configDefaults[key], source: 'config.yaml' };
|
|
1875
|
+
}
|
|
1876
|
+
if (cascade.profileDefaults[key] !== undefined) {
|
|
1877
|
+
// profile 层有意不承载 workflow_mode(画像 = 个人偏好层,REV-74-005);此分支为对称性保留,实际不产出
|
|
1878
|
+
return { value: cascade.profileDefaults[key], source: 'profile.yaml' };
|
|
1879
|
+
}
|
|
1880
|
+
return { value: DEFAULT_CONFIG.workflow_mode, source: 'builtin' };
|
|
1881
|
+
}
|
|
1807
1882
|
/** 查找 status.md 的路径(三级回退:plan/{series}/ 精确 → plan 递归 → stages 兜底) */
|
|
1808
1883
|
findStatusPath(stageId) {
|
|
1809
1884
|
return findStageStatusPath(this.projectPath, stageId);
|
|
@@ -1861,7 +1936,7 @@ export class FlowManager {
|
|
|
1861
1936
|
// 返回最近 N 条(倒序取最后)
|
|
1862
1937
|
return results.slice(-maxChanges);
|
|
1863
1938
|
}
|
|
1864
|
-
/**
|
|
1939
|
+
/** 构建下游阶段列表(当前阶段的可达下一阶段 + Agent 映射;模式经 getAvailablePhases() 无参 → current.stage 派生) */
|
|
1865
1940
|
buildDownstreamPhases() {
|
|
1866
1941
|
const available = this.getAvailablePhases();
|
|
1867
1942
|
const labels = this.getPhaseLabels();
|
|
@@ -2187,8 +2262,10 @@ export class FlowManager {
|
|
|
2187
2262
|
return false;
|
|
2188
2263
|
}
|
|
2189
2264
|
const currentPhase = this.data.stages[parts.stageId].phase;
|
|
2265
|
+
// stage 派生 mode:opId 解析出的 stage(src 内无生产调用方,仅测试;仍为 mode 透传必改点)
|
|
2266
|
+
const mode = this.resolveStageWorkflowMode(parts.stageId).value;
|
|
2190
2267
|
// 检查从当前 phase 到目标 phase 的流转是否合法(支持组合条件 key)
|
|
2191
|
-
const validTargets = this.getValidTargets(currentPhase);
|
|
2268
|
+
const validTargets = this.getValidTargets(currentPhase, mode);
|
|
2192
2269
|
if (!validTargets.includes(to)) {
|
|
2193
2270
|
return false;
|
|
2194
2271
|
}
|
|
@@ -2817,6 +2894,10 @@ export class FlowManager {
|
|
|
2817
2894
|
if (!quick) {
|
|
2818
2895
|
this.checkEmptyTemplates(items);
|
|
2819
2896
|
}
|
|
2897
|
+
// ── 10. 工作流模式冗余字段一致性(stage-75 op-003;仅 warn,不 fail、不自动修复)──
|
|
2898
|
+
if (!quick) {
|
|
2899
|
+
this.checkWorkflowModeRedundancy(items);
|
|
2900
|
+
}
|
|
2820
2901
|
const ok = items.every((i) => i.status !== 'fail');
|
|
2821
2902
|
return { items, ok };
|
|
2822
2903
|
}
|
|
@@ -2902,23 +2983,106 @@ export class FlowManager {
|
|
|
2902
2983
|
return true;
|
|
2903
2984
|
});
|
|
2904
2985
|
}
|
|
2986
|
+
/**
|
|
2987
|
+
* status.md 字段「插入」写(原子写 + 按阶段目录加锁)。
|
|
2988
|
+
* 与 `writeStatusField`(替换语义)并列的**独立**实现,禁双重语义:
|
|
2989
|
+
* 字段行已存在 → `exists`(不写盘);锚点链「自动推进」后 → 回退「执行模式」后 → 皆缺 `no-anchor`(不写盘 / 无 `.bak`)。
|
|
2990
|
+
* 备份 `.openfeel/tmp/status.{stageId}.bak` 仅在**确认插入**时生成。
|
|
2991
|
+
* 不依赖 `this.data`(flow.json 未加载的存量项目也可仅凭 `this.projectPath` 定位锁/备份)。
|
|
2992
|
+
* @param statusPath status.md 绝对路径
|
|
2993
|
+
* @param stageId 备份文件名中的阶段 ID(单阶段 = 用户输入原值;批量 = flow.json 注册键)
|
|
2994
|
+
* @param key 字段名(如「工作流模式」)
|
|
2995
|
+
* @param value 目标值
|
|
2996
|
+
* @returns `inserted` 已插入 | `exists` 字段已存在(竞态防御,调用方可回退替换)| `no-anchor` 锚点缺失
|
|
2997
|
+
*/
|
|
2998
|
+
insertStatusField(statusPath, stageId, key, value) {
|
|
2999
|
+
const stageDir = basename(dirname(statusPath));
|
|
3000
|
+
const lockPath = projectLockPath(this.projectPath, `status-${stageDir}`);
|
|
3001
|
+
return withFileLock(lockPath, () => {
|
|
3002
|
+
const content = readFileSync(statusPath, 'utf-8');
|
|
3003
|
+
// 锁内复查:字段行已存在 → 不写盘(探针与写之间的竞态防御)
|
|
3004
|
+
const fieldRegex = new RegExp(`^-\\s*(?:\\*\\*)?${escapeRegex(key)}(?:\\*\\*)?[::]`, 'm');
|
|
3005
|
+
if (fieldRegex.test(content)) {
|
|
3006
|
+
return 'exists';
|
|
3007
|
+
}
|
|
3008
|
+
// 锚点链:「自动推进」行后优先;缺失则回退「执行模式」行后;皆缺 = no-anchor
|
|
3009
|
+
const anchor = content.match(/^-\s*(?:\*\*)?自动推进(?:\*\*)?[::]/m) ??
|
|
3010
|
+
content.match(/^-\s*(?:\*\*)?执行模式(?:\*\*)?[::]/m);
|
|
3011
|
+
if (!anchor || anchor.index === undefined) {
|
|
3012
|
+
return 'no-anchor';
|
|
3013
|
+
}
|
|
3014
|
+
const newLine = `- **${key}**:${value}`;
|
|
3015
|
+
const lineEnd = content.indexOf('\n', anchor.index + anchor[0].length);
|
|
3016
|
+
let updated;
|
|
3017
|
+
if (lineEnd === -1) {
|
|
3018
|
+
// 错误路径:锚点行位于文件末行(无尾换行)→ 换行后追加
|
|
3019
|
+
updated = `${content}\n${newLine}`;
|
|
3020
|
+
}
|
|
3021
|
+
else {
|
|
3022
|
+
// 保留锚点行原行尾风格(CRLF 感知),其余字节逐字保留
|
|
3023
|
+
const isCrlf = content[lineEnd - 1] === '\r';
|
|
3024
|
+
const eol = isCrlf ? '\r\n' : '\n';
|
|
3025
|
+
updated = `${content.slice(0, lineEnd + 1)}${newLine}${eol}${content.slice(lineEnd + 1)}`;
|
|
3026
|
+
}
|
|
3027
|
+
// 备份仅在确认插入时生成
|
|
3028
|
+
const tmpDir = resolve(this.projectPath, '.openfeel', 'tmp');
|
|
3029
|
+
if (!existsSync(tmpDir)) {
|
|
3030
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
3031
|
+
}
|
|
3032
|
+
copyFileSync(statusPath, resolve(tmpDir, `status.${stageId}.bak`));
|
|
3033
|
+
atomicWriteFileSync(statusPath, updated);
|
|
3034
|
+
return 'inserted';
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
2905
3037
|
/**
|
|
2906
3038
|
* 将受管配置键批量同步到所有已注册阶段的 status.md 对应字段(`config set --sync-stages`)。
|
|
2907
|
-
* 仅 auto_advance→「自动推进」、execution_mode
|
|
2908
|
-
*
|
|
2909
|
-
*
|
|
2910
|
-
*
|
|
3039
|
+
* 仅 auto_advance→「自动推进」、execution_mode→「执行模式」、workflow_mode→「工作流模式」
|
|
3040
|
+
* 三个映射键有阶段级字段;其它键无阶段字段 → 全部 skipped-no-field(D-config 裁定,跳过并报告)。
|
|
3041
|
+
* `workflow_mode` 字段缺失时复用 `insertStatusField`(插入语义,单一实现),锚点缺失 → skipped-no-anchor。
|
|
3042
|
+
* 复用 writeStatusField / insertStatusField 的定向写 + 按阶段锁;同值 → noop(零写盘)。
|
|
3043
|
+
* **冗余写入(裁定 G)**:`workflow_mode` 实际写入(updated / inserted)时同步 `stages[id].workflow_mode`
|
|
3044
|
+
* 并 save 一次;noop / skipped 全量运行 → flow.json 零写盘。
|
|
2911
3045
|
* @param configKey 已归一的受管配置键(如 auto_advance)
|
|
2912
3046
|
* @param value 目标值
|
|
2913
|
-
* @
|
|
3047
|
+
* @param options 可选作用域:`onlyStages` 限定阶段 ID 列表(已归一化;缺省 = 全量现状)
|
|
3048
|
+
* @returns 每阶段一条结果(updated/inserted/noop/skipped-no-field/skipped-no-status/skipped-no-anchor)
|
|
2914
3049
|
*/
|
|
2915
|
-
syncConfigFieldToStages(configKey, value) {
|
|
3050
|
+
syncConfigFieldToStages(configKey, value, options) {
|
|
2916
3051
|
const out = [];
|
|
2917
3052
|
if (!this.data) {
|
|
2918
3053
|
return out;
|
|
2919
3054
|
}
|
|
2920
3055
|
const field = STAGE_FIELD_BY_CONFIG_KEY[configKey];
|
|
2921
|
-
|
|
3056
|
+
const targets = options?.onlyStages;
|
|
3057
|
+
// 冗余写入标记:仅实际写入(updated / inserted)时置位,循环后 save 一次(D6)
|
|
3058
|
+
let flowJsonDirty = false;
|
|
3059
|
+
// 遍历集构造(REV-80-009):仅 targets 时 = 命中 targets 的活跃键(既有 filter 语义逐字保留,顺序 = stages 键序)
|
|
3060
|
+
// + 未被任何活跃键覆盖的 off-set 目标(已归档/未注册——status.md 仍照常同步,flow.json 冗余跳过)
|
|
3061
|
+
// 注(REV-80-015):纯 off-set 运行不置 flowJsonDirty → 不 save;appendLog(status.md 写审计)仅内存态、无 flow.json 写入
|
|
3062
|
+
const loopIds = [];
|
|
3063
|
+
if (targets) {
|
|
3064
|
+
const covered = new Set();
|
|
3065
|
+
for (const stageId of Object.keys(this.data.stages)) {
|
|
3066
|
+
const norm = normalizeStageId(stageId) ?? stageId;
|
|
3067
|
+
if (!targets.includes(norm)) {
|
|
3068
|
+
continue;
|
|
3069
|
+
}
|
|
3070
|
+
loopIds.push(stageId);
|
|
3071
|
+
covered.add(norm);
|
|
3072
|
+
}
|
|
3073
|
+
// off-set 目标(未命中任何活跃键):仅 status.md 同步,冗余写入跳过
|
|
3074
|
+
for (const t of targets) {
|
|
3075
|
+
if (!covered.has(t)) {
|
|
3076
|
+
loopIds.push(t);
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
else {
|
|
3081
|
+
loopIds.push(...Object.keys(this.data.stages));
|
|
3082
|
+
}
|
|
3083
|
+
for (const stageId of loopIds) {
|
|
3084
|
+
// 注册守卫(REV-80-009):off-set 目标无 data.stages 条目 → 冗余写入三点须跳过(防越界解引用)
|
|
3085
|
+
const registered = Object.prototype.hasOwnProperty.call(this.data.stages, stageId);
|
|
2922
3086
|
// 错误路径:该 config 键无阶段级字段 → 跳过并报告
|
|
2923
3087
|
if (!field) {
|
|
2924
3088
|
out.push({ stage: stageId, result: 'skipped-no-field' });
|
|
@@ -2931,8 +3095,37 @@ export class FlowManager {
|
|
|
2931
3095
|
continue;
|
|
2932
3096
|
}
|
|
2933
3097
|
const current = readStatusFieldValue(readFileSync(statusPath, 'utf-8'), field);
|
|
2934
|
-
// 错误路径:字段行缺失 →
|
|
3098
|
+
// 错误路径:字段行缺失 →
|
|
3099
|
+
// workflow_mode 走「插入」语义(复用 op-001 单一实现);其余键保持 skipped-no-field(零回归)
|
|
2935
3100
|
if (current === null) {
|
|
3101
|
+
if (configKey === 'workflow_mode') {
|
|
3102
|
+
const r = this.insertStatusField(statusPath, stageId, field, value);
|
|
3103
|
+
if (r === 'inserted') {
|
|
3104
|
+
this.appendLog({ time: '', agent: 'cli', action: 'config_sync_stage', detail: { stage: stageId, field, value } });
|
|
3105
|
+
out.push({ stage: stageId, result: 'inserted' });
|
|
3106
|
+
// 冗余写入点①:注册守卫(REV-80-009)——off-set 目标不新建冗余键、不置 dirty
|
|
3107
|
+
if (registered) {
|
|
3108
|
+
this.data.stages[stageId].workflow_mode = value;
|
|
3109
|
+
flowJsonDirty = true;
|
|
3110
|
+
}
|
|
3111
|
+
continue;
|
|
3112
|
+
}
|
|
3113
|
+
if (r === 'exists') {
|
|
3114
|
+
// 竞态防御:锁内发现字段已就位 → 回退替换语义(不产生双行)
|
|
3115
|
+
this.writeStatusField(statusPath, field, value);
|
|
3116
|
+
this.appendLog({ time: '', agent: 'cli', action: 'config_sync_stage', detail: { stage: stageId, field, value } });
|
|
3117
|
+
out.push({ stage: stageId, result: 'updated' });
|
|
3118
|
+
// 冗余写入点②:注册守卫(REV-80-009)
|
|
3119
|
+
if (registered) {
|
|
3120
|
+
this.data.stages[stageId].workflow_mode = value;
|
|
3121
|
+
flowJsonDirty = true;
|
|
3122
|
+
}
|
|
3123
|
+
continue;
|
|
3124
|
+
}
|
|
3125
|
+
// 锚点链均缺失 → 跳过类(不写盘、不审计)
|
|
3126
|
+
out.push({ stage: stageId, result: 'skipped-no-anchor' });
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
2936
3129
|
out.push({ stage: stageId, result: 'skipped-no-field' });
|
|
2937
3130
|
continue;
|
|
2938
3131
|
}
|
|
@@ -2944,6 +3137,15 @@ export class FlowManager {
|
|
|
2944
3137
|
this.writeStatusField(statusPath, field, value);
|
|
2945
3138
|
this.appendLog({ time: '', agent: 'cli', action: 'config_sync_stage', detail: { stage: stageId, field, value } });
|
|
2946
3139
|
out.push({ stage: stageId, result: 'updated' });
|
|
3140
|
+
// 冗余写入点③:仅实际写入时同步(noop / skipped 不写);注册守卫(REV-80-009)——off-set 目标跳过
|
|
3141
|
+
if (configKey === 'workflow_mode' && registered) {
|
|
3142
|
+
this.data.stages[stageId].workflow_mode = value;
|
|
3143
|
+
flowJsonDirty = true;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
// 仅在实际发生写入时保存(幂等:全 noop → flow.json 零写盘)
|
|
3147
|
+
if (flowJsonDirty) {
|
|
3148
|
+
this.save();
|
|
2947
3149
|
}
|
|
2948
3150
|
return out;
|
|
2949
3151
|
}
|
|
@@ -2962,9 +3164,15 @@ export class FlowManager {
|
|
|
2962
3164
|
for (const [stageId, stage] of Object.entries(this.data.stages)) {
|
|
2963
3165
|
const deps = Array.isArray(stage.deps) ? stage.deps : [];
|
|
2964
3166
|
for (const d of deps) {
|
|
2965
|
-
if (
|
|
2966
|
-
|
|
3167
|
+
if (known.has(normalizeStageId(d) ?? d)) {
|
|
3168
|
+
continue; // 已注册优先
|
|
2967
3169
|
}
|
|
3170
|
+
// 读侧与写侧同口径(REV-80-002):显式版本前缀 ∧ 已归档 → 视为已满足,不报悬空;
|
|
3171
|
+
// 恒传原始 d(短名不参与归档判定——REV-80-001)
|
|
3172
|
+
if (isArchivedVersionOf(d, this.data.meta?.archived_versions)) {
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
dangling.push(`${stageId} → ${d}`);
|
|
2968
3176
|
}
|
|
2969
3177
|
}
|
|
2970
3178
|
if (dangling.length > 0) {
|
|
@@ -2977,6 +3185,37 @@ export class FlowManager {
|
|
|
2977
3185
|
}
|
|
2978
3186
|
items.push({ section: '悬空依赖', status: 'pass', message: '未检测到悬空依赖' });
|
|
2979
3187
|
}
|
|
3188
|
+
/**
|
|
3189
|
+
* 10. 工作流模式冗余字段一致性:flow.json `stages[].workflow_mode` 与级联解析值不一致 → warn(stage-75 op-003)。
|
|
3190
|
+
* 冗余字段不参与级联(裁定 G),读取权威恒为 status.md/级联;字段缺失(旧 flow.json)零报错;
|
|
3191
|
+
* 仅告警不 fail、不自动修复。无不一致时不产生条目(保持输出精简)——与 checkOrphanOps 同风格。
|
|
3192
|
+
* @param items 健康检查结果收集数组
|
|
3193
|
+
*/
|
|
3194
|
+
checkWorkflowModeRedundancy(items) {
|
|
3195
|
+
if (!this.data) {
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
const mismatched = [];
|
|
3199
|
+
for (const [stageId, stage] of Object.entries(this.data.stages)) {
|
|
3200
|
+
const redundant = stage.workflow_mode;
|
|
3201
|
+
// 错误路径:旧 flow.json 无该字段 → 直接跳过(零报错)
|
|
3202
|
+
if (redundant === undefined) {
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3205
|
+
const resolved = this.resolveStageWorkflowMode(stageId).value;
|
|
3206
|
+
if (redundant !== resolved) {
|
|
3207
|
+
mismatched.push(`${stageId}(冗余 ${redundant} ≠ 解析 ${resolved})`);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
if (mismatched.length === 0) {
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
items.push({
|
|
3214
|
+
section: '工作流模式',
|
|
3215
|
+
status: 'warn',
|
|
3216
|
+
message: `检出 ${mismatched.length} 个阶段的 flow.json 冗余 workflow_mode 与权威解析值不一致(冗余不参与级联,可执行 openfeel config set workflow_mode <v> --sync-stages 修复):${mismatched.slice(0, 5).join('; ')}${mismatched.length > 5 ? ' …' : ''}`,
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
2980
3219
|
/**
|
|
2981
3220
|
* 8. 孤儿 op 检测:flow.json 的 op 键与 ops/ 目录文件是否一致(N1-3)
|
|
2982
3221
|
* 仅作数据卫生告警(warn),不阻断;退出码不受影响(仅 fail 决定)。
|
|
@@ -3361,6 +3600,19 @@ export class FlowManager {
|
|
|
3361
3600
|
const raw = readFileSync(pipelinePath, 'utf-8');
|
|
3362
3601
|
const parsed = parseYaml(raw);
|
|
3363
3602
|
this.pipelineConfig = PipelineConfigSchema.parse(parsed);
|
|
3603
|
+
// 可选快线表:独立容错解析(非法类型 → 忽略该键 + 告警,不 fail 整体配置;缺失/空对象 → 内置快线表)
|
|
3604
|
+
const fastRaw = parsed?.['fast_transitions'];
|
|
3605
|
+
if (fastRaw !== undefined) {
|
|
3606
|
+
const fastParsed = FastTransitionsSchema.safeParse(fastRaw);
|
|
3607
|
+
if (fastParsed.success) {
|
|
3608
|
+
// [REV-74-009 修订] 空对象 {} 等同缺失(空表使 fast 全链不可推进,无合法用途)→ 回落内置快线表
|
|
3609
|
+
this.fastTransitions = Object.keys(fastParsed.data).length > 0 ? fastParsed.data : null;
|
|
3610
|
+
}
|
|
3611
|
+
else {
|
|
3612
|
+
this.fastTransitions = null;
|
|
3613
|
+
console.warn(t('flow.manager.fastTransitionsInvalid', getCliLang(this.projectPath)));
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3364
3616
|
}
|
|
3365
3617
|
catch {
|
|
3366
3618
|
// 解析失败时回退到内置默认值
|
|
@@ -3369,9 +3621,11 @@ export class FlowManager {
|
|
|
3369
3621
|
}
|
|
3370
3622
|
/**
|
|
3371
3623
|
* 获取内置默认流水线配置(等价于硬编码常量 + 3 个 Bug 修复)
|
|
3372
|
-
* 当 pipeline.yaml
|
|
3624
|
+
* 当 pipeline.yaml 不存在或解析失败时使用。
|
|
3625
|
+
* @param mode 工作流模式;**无参 = full**(内置 full 表逐字等于历史现状,零回归既有消费点);
|
|
3626
|
+
* 'fast' 返回快线表(FAST_TRANSITIONS 常量引用)。phases / checkpoint_mapping / phase_corrections 两模式共用。
|
|
3373
3627
|
*/
|
|
3374
|
-
getDefaultPipelineConfig() {
|
|
3628
|
+
getDefaultPipelineConfig(mode = 'full') {
|
|
3375
3629
|
return {
|
|
3376
3630
|
phases: [
|
|
3377
3631
|
'plan_pending', 'plan_review', 'plan_passed',
|
|
@@ -3380,25 +3634,27 @@ export class FlowManager {
|
|
|
3380
3634
|
'review_passed', 'test_pending', 'test_failed',
|
|
3381
3635
|
'test_passed', 'archiving', 'done',
|
|
3382
3636
|
],
|
|
3383
|
-
transitions:
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3637
|
+
transitions: mode === 'fast'
|
|
3638
|
+
? FAST_TRANSITIONS
|
|
3639
|
+
: {
|
|
3640
|
+
plan_pending: ['plan_review', 'plan_passed'],
|
|
3641
|
+
plan_review: ['plan_passed', 'plan_pending'],
|
|
3642
|
+
plan_passed: ['scheme_pending'],
|
|
3643
|
+
scheme_pending: ['scheme_review', 'scheme_passed'],
|
|
3644
|
+
scheme_review: ['scheme_passed', 'scheme_pending'],
|
|
3645
|
+
scheme_passed: ['exec_running'],
|
|
3646
|
+
exec_running: ['review_pending', 'scheme_pending'], // BUG 修复:增加 scheme_pending
|
|
3647
|
+
review_pending: ['review_failed', 'review_passed'],
|
|
3648
|
+
review_failed: ['review_pending', 'scheme_pending'], // BUG 修复:增加 scheme_pending
|
|
3649
|
+
review_passed: ['test_pending'],
|
|
3650
|
+
test_pending: ['test_failed', 'test_passed'],
|
|
3651
|
+
test_failed: ['test_pending', 'scheme_pending'], // BUG 修复:增加 scheme_pending
|
|
3652
|
+
test_passed: ['archiving'],
|
|
3653
|
+
// 组合条件示例:审查通过 或 测试通过 任一即可推进到 archiving(多 Agent 并行场景,任一完成即推进)
|
|
3654
|
+
'review_passed|test_passed': ['archiving'],
|
|
3655
|
+
archiving: ['done'],
|
|
3656
|
+
done: [],
|
|
3657
|
+
},
|
|
3402
3658
|
checkpoint_mapping: {
|
|
3403
3659
|
plan: 'plan',
|
|
3404
3660
|
scheme: 'scheme',
|