openfeel 1.2.0 → 1.2.3
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 +49 -8
- package/README.md +46 -5
- package/README.zh-CN.md +49 -8
- package/bin/openfeel.js +3 -2
- package/dist/cli/index.d.ts +17 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +99 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/project-option.d.ts +40 -0
- package/dist/cli/project-option.d.ts.map +1 -0
- package/dist/cli/project-option.js +140 -0
- package/dist/cli/project-option.js.map +1 -0
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +32 -2
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +71 -7
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +490 -112
- package/dist/commands/flow.js.map +1 -1
- package/dist/commands/instructions.d.ts.map +1 -1
- package/dist/commands/instructions.js +13 -3
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/knowledge.d.ts.map +1 -1
- package/dist/commands/knowledge.js +113 -17
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +31 -7
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/project.d.ts +66 -0
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +122 -64
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/req.d.ts +2 -1
- package/dist/commands/req.d.ts.map +1 -1
- package/dist/commands/req.js +219 -34
- package/dist/commands/req.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +11 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/shared/errors.d.ts +55 -4
- package/dist/commands/shared/errors.d.ts.map +1 -1
- package/dist/commands/shared/errors.js +108 -10
- package/dist/commands/shared/errors.js.map +1 -1
- package/dist/commands/shared/json-output.d.ts +60 -0
- package/dist/commands/shared/json-output.d.ts.map +1 -0
- package/dist/commands/shared/json-output.js +152 -0
- package/dist/commands/shared/json-output.js.map +1 -0
- package/dist/commands/stage.d.ts.map +1 -1
- package/dist/commands/stage.js +69 -7
- package/dist/commands/stage.js.map +1 -1
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +27 -0
- package/dist/commands/view.js.map +1 -1
- package/dist/core/config.d.ts +6 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +6 -5
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.d.ts +100 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +58 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/flow-manager.d.ts +73 -9
- package/dist/core/flow-manager.d.ts.map +1 -1
- package/dist/core/flow-manager.js +211 -38
- package/dist/core/flow-manager.js.map +1 -1
- package/dist/core/fs/file-lock.d.ts +9 -0
- package/dist/core/fs/file-lock.d.ts.map +1 -1
- package/dist/core/fs/file-lock.js +27 -1
- package/dist/core/fs/file-lock.js.map +1 -1
- package/dist/core/i18n-data/en.d.ts.map +1 -1
- package/dist/core/i18n-data/en.js +84 -9
- package/dist/core/i18n-data/en.js.map +1 -1
- package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
- package/dist/core/i18n-data/zh-CN.js +84 -9
- package/dist/core/i18n-data/zh-CN.js.map +1 -1
- package/dist/core/managed-region.d.ts +16 -2
- package/dist/core/managed-region.d.ts.map +1 -1
- package/dist/core/managed-region.js +24 -3
- package/dist/core/managed-region.js.map +1 -1
- package/dist/core/metrics.d.ts +2 -0
- package/dist/core/metrics.d.ts.map +1 -1
- package/dist/core/metrics.js +15 -0
- package/dist/core/metrics.js.map +1 -1
- package/dist/core/model-config.d.ts +1 -1
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +2 -2
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/opencode-config.js +1 -1
- package/dist/core/opencode-config.js.map +1 -1
- package/dist/core/pipeline-schema.d.ts +22 -0
- package/dist/core/pipeline-schema.d.ts.map +1 -1
- package/dist/core/pipeline-schema.js +24 -0
- package/dist/core/pipeline-schema.js.map +1 -1
- package/dist/core/plan/stage.d.ts +2 -1
- package/dist/core/plan/stage.d.ts.map +1 -1
- package/dist/core/plan/stage.js +7 -2
- package/dist/core/plan/stage.js.map +1 -1
- package/dist/core/req/aggregate.d.ts +21 -2
- package/dist/core/req/aggregate.d.ts.map +1 -1
- package/dist/core/req/aggregate.js +18 -5
- package/dist/core/req/aggregate.js.map +1 -1
- package/dist/core/req/body-file.d.ts.map +1 -1
- package/dist/core/req/body-file.js +7 -3
- package/dist/core/req/body-file.js.map +1 -1
- package/dist/core/req/requirement-ops.d.ts +44 -2
- package/dist/core/req/requirement-ops.d.ts.map +1 -1
- package/dist/core/req/requirement-ops.js +108 -3
- package/dist/core/req/requirement-ops.js.map +1 -1
- package/dist/core/req/types.d.ts +7 -1
- package/dist/core/req/types.d.ts.map +1 -1
- package/dist/core/req/types.js +0 -6
- package/dist/core/req/types.js.map +1 -1
- package/dist/core/runtime-log.d.ts +18 -0
- package/dist/core/runtime-log.d.ts.map +1 -1
- package/dist/core/runtime-log.js +72 -0
- package/dist/core/runtime-log.js.map +1 -1
- package/dist/core/session.d.ts +24 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +107 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/template-loader.d.ts.map +1 -1
- package/dist/core/template-loader.js +3004 -6684
- package/dist/core/template-loader.js.map +1 -1
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +720 -35
- package/dist/core/update.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,15 +13,16 @@ 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, FastTransitionsSchema, } from './pipeline-schema.js';
|
|
16
|
+
import { PipelineConfigSchema, PipelinePhaseSchema, PIPELINE_PHASES, MetaPhaseSchema, META_PHASES, transitionKeyMatches, parseTransitionKey, FastTransitionsSchema, TIER_PRUNED_PHASES, } from './pipeline-schema.js';
|
|
17
17
|
import { PublicLogger, formatDate } from './public-logger.js';
|
|
18
18
|
import { t, getCliLang } from './i18n.js';
|
|
19
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
|
-
import { DEFAULT_CONFIG, ConfigDefaultsSchema, STAGE_FIELD_BY_CONFIG_KEY } from './config.js';
|
|
22
|
+
import { DEFAULT_CONFIG, ConfigDefaultsSchema, STAGE_FIELD_BY_CONFIG_KEY, WORKFLOW_MODES } from './config.js';
|
|
23
23
|
import { getGlobalProfilePath } from './global-paths.js';
|
|
24
24
|
import { syncRequirementStatus } from './req/aggregate.js'; // 需求聚合回写入口(stage-83 T2;纯内存)
|
|
25
|
+
import { getSessionId } from './session.js'; // 会话标识(stage-86 T4;core→core 零环)
|
|
25
26
|
/** flow.json 乐观并发冲突:磁盘 revision 与本次加载时不一致(存在并发写入) */
|
|
26
27
|
export class FlowConcurrentModificationError extends Error {
|
|
27
28
|
/** 本次实例加载时的 revision */
|
|
@@ -40,6 +41,20 @@ export function isFlowConcurrentError(err) {
|
|
|
40
41
|
return err instanceof FlowConcurrentModificationError
|
|
41
42
|
|| (typeof err === 'object' && err !== null && err.name === 'FlowConcurrentModificationError');
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 过滤指定阶段域的阻塞 open REV(单点谓词)。
|
|
46
|
+
* 阶段域:r.op === stage 或 r.op.startsWith(stage + '.')(`.` 分隔防前缀重叠)。
|
|
47
|
+
* 阻塞判定:r.blocking !== false(undefined 视为阻塞)且 r.status === 'open'。
|
|
48
|
+
* 纯只读;空 stage → []。
|
|
49
|
+
*/
|
|
50
|
+
export function listStageBlockingOpenReviews(reviews, stage) {
|
|
51
|
+
if (!stage) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return reviews.filter((r) => (r.op === stage || r.op.startsWith(stage + '.'))
|
|
55
|
+
&& r.blocking !== false
|
|
56
|
+
&& r.status === 'open');
|
|
57
|
+
}
|
|
43
58
|
/** 阶段目录冲突:两个不同 stageId 映射同一 (series, stageDir)(cli/BUG-002) */
|
|
44
59
|
export class StageDirConflictError extends Error {
|
|
45
60
|
/** 本次冲突检测的 stageId */
|
|
@@ -181,6 +196,17 @@ const FAST_TRANSITIONS = {
|
|
|
181
196
|
archiving: ['done'],
|
|
182
197
|
done: [],
|
|
183
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* 工作流模式 → 派生的审查档位(**单源常量**;stage-92 D-92-3)。
|
|
201
|
+
* 仅用于「展示 / 提示」(`flow phases` 档位分节行与 `--json.reviewTier`);**不参与推进语义**
|
|
202
|
+
* (`flow advance` 的有效档位 = 显式声明 ?? full,缺省恒零裁剪 ⟹ 既有流程零回归)。
|
|
203
|
+
* general(无流水线)恒全量语义,不特判。
|
|
204
|
+
*/
|
|
205
|
+
export const MODE_DERIVED_TIER = {
|
|
206
|
+
full: 'full',
|
|
207
|
+
fast: 'lean',
|
|
208
|
+
general: 'full',
|
|
209
|
+
};
|
|
184
210
|
// ── 核心类 ──
|
|
185
211
|
export class FlowManager {
|
|
186
212
|
projectPath;
|
|
@@ -1050,14 +1076,48 @@ export class FlowManager {
|
|
|
1050
1076
|
}
|
|
1051
1077
|
return synced;
|
|
1052
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* T4:跨会话恢复判定。从 log 尾部回扫(本次条目尚未写入)取该 stage 最近一条
|
|
1081
|
+
* advance_stage_phase / reset_stage_phase 条目(要求 detail.to === exitedPhase);
|
|
1082
|
+
* 任一侧不可信 / 缺失 → 返回 null(调用方省略 resumed 字段,不臆断;proc: 级不参与跨会话判定)。
|
|
1083
|
+
* @param log 审计日志(本次条目尚未 append)
|
|
1084
|
+
* @param stageKey 阶段键(完整 stageId)
|
|
1085
|
+
* @param exitedPhase 本次转换退出的 phase(= 上一次转换进入的 phase)
|
|
1086
|
+
* @param currentId 本次会话标识(含来源前缀)
|
|
1087
|
+
* @returns true = 异会话恢复;false = 同会话;null = 不可判定(省略字段)
|
|
1088
|
+
*/
|
|
1089
|
+
computeResumed(log, stageKey, exitedPhase, currentId) {
|
|
1090
|
+
// 可信判定:仅 env: / project: 前缀参与跨会话判定;proc: → 不可判定
|
|
1091
|
+
if (!(currentId.startsWith('env:') || currentId.startsWith('project:'))) {
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
for (let k = log.length - 1; k >= 0; k--) {
|
|
1095
|
+
const e = log[k];
|
|
1096
|
+
if (e.action !== 'advance_stage_phase' && e.action !== 'reset_stage_phase') {
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
const detail = e.detail;
|
|
1100
|
+
if (detail.stageName !== stageKey || detail.to !== exitedPhase) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
const prevId = typeof detail.session_id === 'string' ? detail.session_id : '';
|
|
1104
|
+
// 旧条目无 session_id / 值非法 / 上一侧不可信 → 不可判定
|
|
1105
|
+
if (!(prevId.startsWith('env:') || prevId.startsWith('project:'))) {
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
return prevId !== currentId; // 同值 → 同会话(false);异值 → 跨会话恢复(true)
|
|
1109
|
+
}
|
|
1110
|
+
return null; // 无历史转换条目 → 不可判定
|
|
1111
|
+
}
|
|
1053
1112
|
/**
|
|
1054
1113
|
* 推进指定阶段到目标流水线阶段(新 API)
|
|
1055
1114
|
* @param stageName 阶段名(如 stage-01)
|
|
1056
1115
|
* @param phase 目标流水线阶段(PipelinePhase)
|
|
1057
1116
|
* @param triggeredBy 触发者标识(如 'cli' / 'Feel'),默认 'flow-manager'
|
|
1117
|
+
* @param opts.reviewTier 本次调用的审查档位(**仅显式声明时写审计键** `review_tier`;缺省不写 ⟹ 既有条目逐字不变)
|
|
1058
1118
|
* @returns 是否触发 done 归档(true = 本次推进将阶段变为 done 且之前非 done,需在 save 后执行归档 commit)
|
|
1059
1119
|
*/
|
|
1060
|
-
advanceStagePhase(stageName, phase, triggeredBy) {
|
|
1120
|
+
advanceStagePhase(stageName, phase, triggeredBy, opts) {
|
|
1061
1121
|
if (!this.data) {
|
|
1062
1122
|
return false;
|
|
1063
1123
|
}
|
|
@@ -1123,12 +1183,21 @@ export class FlowManager {
|
|
|
1123
1183
|
const allDone = Object.keys(this.data.stages).length > 0
|
|
1124
1184
|
&& Object.values(this.data.stages).every((s) => s.phase === 'done');
|
|
1125
1185
|
this.data.pipeline.phase = (allDone ? 'done' : 'active');
|
|
1186
|
+
// T4:跨会话恢复标注(可选字段;任一侧不可信 / 缺失 → 省略 resumed,不臆断)
|
|
1187
|
+
// 计算在 appendLog 之前 → 回扫天然排除本次条目
|
|
1188
|
+
const sessionId = getSessionId({ projectPath: this.projectPath });
|
|
1189
|
+
const resumed = this.computeResumed(this.data.log, key, fromPhase, sessionId);
|
|
1126
1190
|
// 追加日志(使用实际触发者名,而非硬编码 'flow-manager')
|
|
1191
|
+
// 审查档位留痕(stage-92 D-92-4):review_tier **仅显式声明时**追加(既有键逐字保留,缺省条目字节级不变)
|
|
1127
1192
|
this.appendLog({
|
|
1128
1193
|
time: '',
|
|
1129
1194
|
agent: actualTrigger,
|
|
1130
1195
|
action: 'advance_stage_phase',
|
|
1131
|
-
detail: {
|
|
1196
|
+
detail: {
|
|
1197
|
+
stageName: key, from: fromPhase, to: targetPhase, session_id: sessionId,
|
|
1198
|
+
...(resumed === null ? {} : { resumed }),
|
|
1199
|
+
...(opts?.reviewTier === undefined ? {} : { review_tier: opts.reviewTier }),
|
|
1200
|
+
},
|
|
1132
1201
|
});
|
|
1133
1202
|
// REV-003: advance_stage_phase 改为 endStage 时批量聚合,取消逐条写入
|
|
1134
1203
|
// this.publicLogger.logPhaseChange({...});
|
|
@@ -1204,11 +1273,14 @@ export class FlowManager {
|
|
|
1204
1273
|
stage.phase = targetPhase;
|
|
1205
1274
|
stage.status = projectedStatus;
|
|
1206
1275
|
// 审计日志:reset 为修复而非里程碑推进
|
|
1276
|
+
// T4:跨会话恢复标注(与 advanceStagePhase 同款;计算在 appendLog 之前)
|
|
1277
|
+
const resetSessionId = getSessionId({ projectPath: this.projectPath });
|
|
1278
|
+
const resetResumed = this.computeResumed(this.data.log, key, fromPhase, resetSessionId);
|
|
1207
1279
|
this.appendLog({
|
|
1208
1280
|
time: '',
|
|
1209
1281
|
agent: 'flow-manager',
|
|
1210
1282
|
action: 'reset_stage_phase',
|
|
1211
|
-
detail: { stageName: key, from: fromPhase, to: targetPhase },
|
|
1283
|
+
detail: { stageName: key, from: fromPhase, to: targetPhase, session_id: resetSessionId, ...(resetResumed === null ? {} : { resumed: resetResumed }) },
|
|
1212
1284
|
});
|
|
1213
1285
|
// 需求聚合对称回退(T2):聚合视图重算——聚合 ≠ done ∧ 需求 status === done → 回退 in_progress;
|
|
1214
1286
|
// 纯内存;dryRun / no-op 已在 :1585 早退(本点不可达);不新增写盘点。
|
|
@@ -1504,10 +1576,11 @@ export class FlowManager {
|
|
|
1504
1576
|
* 无 '|' 的简单 key 保持单条件原行为(精确相等)。
|
|
1505
1577
|
* @param fromPhase 当前源 phase
|
|
1506
1578
|
* @param mode 工作流模式(**必传**;由调用点经 resolveStageWorkflowMode 从 stage 派生——禁止默认 mode)
|
|
1579
|
+
* @param tier 审查档位(可选,缺省 'full' = 零裁剪 ⟹ 既有调用逐字零回归)
|
|
1507
1580
|
* @returns 去重后的可达目标 phase 列表
|
|
1508
1581
|
*/
|
|
1509
|
-
getValidTargets(fromPhase, mode) {
|
|
1510
|
-
const transitions = this.resolveTransitionsForMode(mode);
|
|
1582
|
+
getValidTargets(fromPhase, mode, tier = 'full') {
|
|
1583
|
+
const transitions = this.resolveTransitionsForMode(mode, tier);
|
|
1511
1584
|
const result = new Set();
|
|
1512
1585
|
for (const [key, targets] of Object.entries(transitions)) {
|
|
1513
1586
|
if (transitionKeyMatches(key, fromPhase)) {
|
|
@@ -1537,8 +1610,9 @@ export class FlowManager {
|
|
|
1537
1610
|
* 获取当前阶段的所有可达下一阶段(基于 transitions 表,支持组合条件)
|
|
1538
1611
|
* 供 flow wizard 交互模式使用
|
|
1539
1612
|
* @param stageName 可选阶段名,提供时使用该阶段 phase 进行查找;否则使用 current.stage 的 phase
|
|
1613
|
+
* @param tier 审查档位(可选,缺省 'full' = 零裁剪)
|
|
1540
1614
|
*/
|
|
1541
|
-
getAvailablePhases(stageName) {
|
|
1615
|
+
getAvailablePhases(stageName, tier = 'full') {
|
|
1542
1616
|
if (!this.data) {
|
|
1543
1617
|
return [];
|
|
1544
1618
|
}
|
|
@@ -1549,7 +1623,7 @@ export class FlowManager {
|
|
|
1549
1623
|
if (!currentPhase)
|
|
1550
1624
|
return [];
|
|
1551
1625
|
const mode = this.resolveStageWorkflowMode(stageName).value;
|
|
1552
|
-
return this.getValidTargets(currentPhase, mode);
|
|
1626
|
+
return this.getValidTargets(currentPhase, mode, tier);
|
|
1553
1627
|
}
|
|
1554
1628
|
/**
|
|
1555
1629
|
* 在运行时 transitions 上求 stageName 当前 phase → to 的**唯一**可达路径(B5-1)。
|
|
@@ -1557,9 +1631,10 @@ export class FlowManager {
|
|
|
1557
1631
|
* 纯只读:不写盘、不改内存。
|
|
1558
1632
|
* @param stageName 阶段 ID
|
|
1559
1633
|
* @param to 目标 phase
|
|
1634
|
+
* @param tier 审查档位(可选,缺省 'full' = 零裁剪;显式声明时按裁剪表求路径)
|
|
1560
1635
|
* @returns 路径与原因(no-path/ambiguous/depth-exceeded 供命令层报错;already-at-target 按 no-op)
|
|
1561
1636
|
*/
|
|
1562
|
-
findPhasePath(stageName, to) {
|
|
1637
|
+
findPhasePath(stageName, to, tier = 'full') {
|
|
1563
1638
|
// REV-005:归一化 stageId(短名 → 全名),与 setStageDeps / plan scheme * 同范式
|
|
1564
1639
|
// 双键回退:兼容以短名建键的存量 / 测试数据(归一化键不存在时回退原始键)
|
|
1565
1640
|
const normalized = normalizeStageId(stageName) ?? stageName;
|
|
@@ -1585,7 +1660,7 @@ export class FlowManager {
|
|
|
1585
1660
|
if (node.path.length >= PHASE_PATH_MAX_DEPTH) {
|
|
1586
1661
|
continue;
|
|
1587
1662
|
}
|
|
1588
|
-
for (const target of this.getValidTargets(node.phase, mode)) {
|
|
1663
|
+
for (const target of this.getValidTargets(node.phase, mode, tier)) {
|
|
1589
1664
|
const path = [...node.path, target];
|
|
1590
1665
|
if (target === to) {
|
|
1591
1666
|
hits.push(path);
|
|
@@ -1622,24 +1697,85 @@ export class FlowManager {
|
|
|
1622
1697
|
const phases = this.pipelineConfig?.phases ?? this.getDefaultPipelineConfig().phases;
|
|
1623
1698
|
return [...phases];
|
|
1624
1699
|
}
|
|
1700
|
+
/**
|
|
1701
|
+
* ═══ 审查档位裁剪(stage-92 D-92-2 / D-92-3;**唯一调用点** = resolveTransitionsForMode)═══
|
|
1702
|
+
* 纯函数:不改入参(不污染 FAST_TRANSITIONS 常量 / pipelineConfig),恒返回新对象 + 新数组。
|
|
1703
|
+
* 算法:
|
|
1704
|
+
* ① `tier === 'full'` → 深拷贝原样返回(零裁剪 ⟹ 既有调用逐字零回归);
|
|
1705
|
+
* ② 各 key 的 targets 过滤被裁剪相位(目标侧裁剪 = 跳过该审查关);
|
|
1706
|
+
* ③ **边收缩回退**——若某 key 过滤后 targets 为空且原 targets 非空,则并入「被删目标的 targets」
|
|
1707
|
+
* (再过滤被裁剪项与自身)后去重。该回退保证 fast 基表裁 `plan_review` 后 `plan_pending` 非空集
|
|
1708
|
+
* (`['plan_review']` → 收缩 `plan_review` 的 targets → `plan_pending → ['plan_passed']`)。
|
|
1709
|
+
* **裁剪只作用于入边 / 目标侧**:被裁剪相位保留出边(阶段已处于被裁剪关时可向该档位下游推进);
|
|
1710
|
+
* 「**进入**被裁剪关」仍被目标侧过滤裁掉。
|
|
1711
|
+
* 边界(登记):含被裁剪相位的**组合 key**(如 `'scheme_review|x'`)按 ② 过滤 targets(不再整键删除);
|
|
1712
|
+
* 内置 full/fast 表与本仓运行时表均无此类键(唯一组合键 `'review_passed|test_passed'` 不含被裁剪相位)。
|
|
1713
|
+
* @param table 模式基表(由 resolveTransitionsForMode 按既有三分支解析所得)
|
|
1714
|
+
* @param tier 审查档位
|
|
1715
|
+
* @returns 裁剪后的新表
|
|
1716
|
+
*/
|
|
1717
|
+
pruneTransitionsByTier(table, tier) {
|
|
1718
|
+
// 全量档:零裁剪(深拷贝返回,防调用方改内存污染基表)
|
|
1719
|
+
if (tier === 'full') {
|
|
1720
|
+
return Object.fromEntries(Object.entries(table).map(([k, v]) => [k, [...v]]));
|
|
1721
|
+
}
|
|
1722
|
+
const pruned = TIER_PRUNED_PHASES[tier];
|
|
1723
|
+
const isPruned = (p) => pruned.includes(p);
|
|
1724
|
+
const result = {};
|
|
1725
|
+
for (const [key, targets] of Object.entries(table)) {
|
|
1726
|
+
const keyConds = parseTransitionKey(key);
|
|
1727
|
+
// ② 目标侧过滤被裁剪相位(裁剪只作用于入边 / 目标侧——被裁剪相位作源键时保留,其出边可推进)
|
|
1728
|
+
let filtered = targets.filter((t) => !isPruned(t));
|
|
1729
|
+
// ③ 边收缩回退:过滤后为空且原 targets 非空 → 并入被删目标的 targets(过滤被裁剪项与自身后去重)
|
|
1730
|
+
if (filtered.length === 0 && targets.length > 0) {
|
|
1731
|
+
const merged = new Set();
|
|
1732
|
+
for (const dropped of targets) {
|
|
1733
|
+
if (!isPruned(dropped)) {
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
for (const cascaded of table[dropped] ?? []) {
|
|
1737
|
+
if (!isPruned(cascaded) && !keyConds.includes(cascaded)) {
|
|
1738
|
+
merged.add(cascaded);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
filtered = [...merged];
|
|
1743
|
+
}
|
|
1744
|
+
result[key] = filtered;
|
|
1745
|
+
}
|
|
1746
|
+
return result;
|
|
1747
|
+
}
|
|
1625
1748
|
/**
|
|
1626
1749
|
* 按模式解析运行时转移表(唯一取表法则;校验/推进/路径 与 展示 同经本函数 ⇒ 单一信源)。
|
|
1627
1750
|
* full → 运行时 pipelineConfig.transitions(pipeline.yaml 覆盖线);fast → fast_transitions ?? 内置快线表。
|
|
1751
|
+
* general → 空表(无流水线;模式语义本身,非「缺省回落」)。
|
|
1752
|
+
* 审查档位为**第二参延伸**(stage-92 D-92-3):末尾统一经 pruneTransitionsByTier 裁剪;
|
|
1753
|
+
* 缺省 tier='full' = 零裁剪 ⟹ 与原逻辑等价(唯一取表点不变,禁第二份层序 / 取表)。
|
|
1628
1754
|
* 说明:pipelineConfig 构造期恒已加载(constructor 无条件调用),`?? 内置` 保留既有访问器回退语义。
|
|
1629
1755
|
*/
|
|
1630
|
-
resolveTransitionsForMode(mode) {
|
|
1756
|
+
resolveTransitionsForMode(mode, tier = 'full') {
|
|
1757
|
+
let raw;
|
|
1631
1758
|
if (mode === 'fast') {
|
|
1632
|
-
|
|
1759
|
+
raw = this.fastTransitions ?? this.getDefaultPipelineConfig('fast').transitions;
|
|
1760
|
+
}
|
|
1761
|
+
else if (mode === 'general') {
|
|
1762
|
+
// general(无流水线):空表为**模式语义**的忠实表达(D-90-3;与 pipeline.yaml fast_transitions:{} 的「配置层空值回落」路径 / 语义均不同)
|
|
1763
|
+
raw = {};
|
|
1633
1764
|
}
|
|
1634
|
-
|
|
1765
|
+
else {
|
|
1766
|
+
raw = this.pipelineConfig?.transitions ?? this.getDefaultPipelineConfig().transitions;
|
|
1767
|
+
}
|
|
1768
|
+
// 档位裁剪统一落此处(唯一取表点的单一裁剪入口;general 空表裁剪后仍空表)
|
|
1769
|
+
return this.pruneTransitionsByTier(raw, tier);
|
|
1635
1770
|
}
|
|
1636
1771
|
/**
|
|
1637
1772
|
* 获取运行时生效的 phase 转移表(key 可含 '|' 组合条件)。
|
|
1638
1773
|
* 缺省/解析失败时回退内置默认配置。返回浅拷贝。
|
|
1639
1774
|
* **无参 = full**(零回归既有消费点);fast 供 stage-75 `flow phases` 模式分节消费。
|
|
1775
|
+
* @param tier 审查档位(可选,缺省 'full' = 零裁剪)
|
|
1640
1776
|
*/
|
|
1641
|
-
getPipelineTransitions(mode = 'full') {
|
|
1642
|
-
const transitions = this.resolveTransitionsForMode(mode);
|
|
1777
|
+
getPipelineTransitions(mode = 'full', tier = 'full') {
|
|
1778
|
+
const transitions = this.resolveTransitionsForMode(mode, tier);
|
|
1643
1779
|
return Object.fromEntries(Object.entries(transitions).map(([k, v]) => [k, [...v]]));
|
|
1644
1780
|
}
|
|
1645
1781
|
/**
|
|
@@ -1809,7 +1945,8 @@ export class FlowManager {
|
|
|
1809
1945
|
// 匹配执行模式、自动推进、工作流模式
|
|
1810
1946
|
const execMatch = content.match(/\*\*执行模式\*\*[::]\s*(manual|auto)/);
|
|
1811
1947
|
const autoMatch = content.match(/\*\*自动推进\*\*[::]\s*(disabled|enabled)/);
|
|
1812
|
-
|
|
1948
|
+
// 值域自 WORKFLOW_MODES 派生(单源;stage-90——漏改本行即第 1 层丢弃、静默回落 config)
|
|
1949
|
+
const modeMatch = content.match(new RegExp(`\\*\\*工作流模式\\*\\*[::]\\s*(${WORKFLOW_MODES.join('|')})`));
|
|
1813
1950
|
if (execMatch) {
|
|
1814
1951
|
statusOverrides['execution_mode'] = execMatch[1];
|
|
1815
1952
|
}
|
|
@@ -1839,6 +1976,11 @@ export class FlowManager {
|
|
|
1839
1976
|
const cascade = this.buildCascadeConfig();
|
|
1840
1977
|
const result = {};
|
|
1841
1978
|
for (const key of EFFECTIVE_CONFIG_KEYS) {
|
|
1979
|
+
if (key === 'workflow_mode') {
|
|
1980
|
+
// Q4 定稿(D-90-6):委托单点层级函数(无参 = 当前阶段域,与 config effective 语义域一致);禁第二份层序判定
|
|
1981
|
+
result[key] = this.resolveStageWorkflowMode();
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1842
1984
|
if (cascade.statusOverrides[key] !== undefined) {
|
|
1843
1985
|
result[key] = { value: cascade.statusOverrides[key], source: 'status.md' };
|
|
1844
1986
|
}
|
|
@@ -1858,11 +2000,11 @@ export class FlowManager {
|
|
|
1858
2000
|
/**
|
|
1859
2001
|
* 解析指定阶段的工作流模式(裁定 E:「stage → mode」表选择解析入口;stage-75 `flow mode` 契约)。
|
|
1860
2002
|
* 带 stage 形参独立解析(REV-74-001):该阶段 status.md(findStatusPath(stageId))
|
|
1861
|
-
* > config.yaml defaults > profile.yaml(**有意不承载**,实际不产出)> builtin(DEFAULT_CONFIG.workflow_mode = fast)。
|
|
2003
|
+
* > 需求级(关联需求携带的 workflow_mode)> config.yaml defaults > profile.yaml(**有意不承载**,实际不产出)> builtin(DEFAULT_CONFIG.workflow_mode = fast)。
|
|
1862
2004
|
* 语义边界:`config effective` / `flow status --verbose` = **当前阶段域**(无参);
|
|
1863
2005
|
* 本方法显式 stageId = **任意阶段域**(flow mode --stage / 表选择)。
|
|
1864
2006
|
* @param stageId 目标阶段 ID(缺省 = pipeline.current.stage;无该阶段 status.md → 自然回落下一层)
|
|
1865
|
-
* @returns { value, source },source ∈ status.md | config.yaml | profile.yaml | builtin
|
|
2007
|
+
* @returns { value, source },source ∈ status.md | requirement | config.yaml | profile.yaml | builtin
|
|
1866
2008
|
*/
|
|
1867
2009
|
resolveStageWorkflowMode(stageId) {
|
|
1868
2010
|
const cascade = this.buildCascadeConfig(stageId);
|
|
@@ -1870,6 +2012,18 @@ export class FlowManager {
|
|
|
1870
2012
|
if (cascade.statusOverrides[key] !== undefined) {
|
|
1871
2013
|
return { value: cascade.statusOverrides[key], source: 'status.md' };
|
|
1872
2014
|
}
|
|
2015
|
+
// ── 需求级层(stage-90 T2.2):阶段关联需求携带的模式属性 ──
|
|
2016
|
+
// 读侧宽容:缺失 / 悬空 / 非法 → 自然回落下一层(零报错);阶段定位与 status.md 层同域(归一 + 键回退)
|
|
2017
|
+
const reqTarget = stageId ?? this.data?.pipeline.current.stage;
|
|
2018
|
+
const reqStageRec = reqTarget ? this.data?.stages?.[normalizeStageId(reqTarget) ?? reqTarget] : undefined;
|
|
2019
|
+
const reqId = reqStageRec?.requirement_id ?? null;
|
|
2020
|
+
if (reqId) {
|
|
2021
|
+
const reqRec = (Array.isArray(this.data?.requirements) ? this.data.requirements : []).find((r) => r?.id === reqId);
|
|
2022
|
+
const reqMode = reqRec?.workflow_mode;
|
|
2023
|
+
if (reqMode !== undefined && WORKFLOW_MODES.includes(reqMode)) {
|
|
2024
|
+
return { value: reqMode, source: 'requirement' };
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1873
2027
|
if (cascade.configDefaults[key] !== undefined) {
|
|
1874
2028
|
return { value: cascade.configDefaults[key], source: 'config.yaml' };
|
|
1875
2029
|
}
|
|
@@ -1950,6 +2104,14 @@ export class FlowManager {
|
|
|
1950
2104
|
}
|
|
1951
2105
|
return result;
|
|
1952
2106
|
}
|
|
2107
|
+
/**
|
|
2108
|
+
* 公开单点:将 PipelinePhase 映射为负责 Agent 标识。
|
|
2109
|
+
* 命令层 / 适配器一律经本方法消费,禁止复制映射;内部委托 mapPhaseToAgent。
|
|
2110
|
+
* 与 flow status --verbose 的 downstreamPhases.responsibleAgent 同源。
|
|
2111
|
+
*/
|
|
2112
|
+
getResponsibleAgent(phase) {
|
|
2113
|
+
return this.mapPhaseToAgent(phase);
|
|
2114
|
+
}
|
|
1953
2115
|
/** 将 PipelinePhase 映射为负责 Agent 标识(返回新名,供新写入使用) */
|
|
1954
2116
|
mapPhaseToAgent(phase) {
|
|
1955
2117
|
const prefix = phase.split('_')[0];
|
|
@@ -2005,6 +2167,9 @@ export class FlowManager {
|
|
|
2005
2167
|
const stageIdOfOp = this.parseOpId(opId)?.stageId;
|
|
2006
2168
|
if (result === 'pass') {
|
|
2007
2169
|
op.state = 'done';
|
|
2170
|
+
// T3:op 级埋点(唯一写入点 = recordAttempt;pass 同时进入 done 终态)
|
|
2171
|
+
op.attempted_at = new Date().toISOString();
|
|
2172
|
+
op.completed_at = op.attempted_at;
|
|
2008
2173
|
if (stageIdOfOp) {
|
|
2009
2174
|
this.syncCurrentOp(stageIdOfOp);
|
|
2010
2175
|
}
|
|
@@ -2026,6 +2191,8 @@ export class FlowManager {
|
|
|
2026
2191
|
// result === 'fail'
|
|
2027
2192
|
if (op.attempts < op.max_attempts) {
|
|
2028
2193
|
op.state = 'pending'; // 回到 pending 等待重试
|
|
2194
|
+
// T3:仅记 attempted_at(未进入终态 → completed_at 缺省)
|
|
2195
|
+
op.attempted_at = new Date().toISOString();
|
|
2029
2196
|
if (stageIdOfOp) {
|
|
2030
2197
|
this.syncCurrentOp(stageIdOfOp);
|
|
2031
2198
|
}
|
|
@@ -2046,6 +2213,9 @@ export class FlowManager {
|
|
|
2046
2213
|
}
|
|
2047
2214
|
// 重试耗尽
|
|
2048
2215
|
op.state = 'failed';
|
|
2216
|
+
// T3:重试耗尽 = 进入 failed 终态 → 双写
|
|
2217
|
+
op.attempted_at = new Date().toISOString();
|
|
2218
|
+
op.completed_at = op.attempted_at;
|
|
2049
2219
|
this.data.pipeline.retry += 1;
|
|
2050
2220
|
this.appendLog({
|
|
2051
2221
|
time: '',
|
|
@@ -3624,6 +3794,7 @@ export class FlowManager {
|
|
|
3624
3794
|
* 当 pipeline.yaml 不存在或解析失败时使用。
|
|
3625
3795
|
* @param mode 工作流模式;**无参 = full**(内置 full 表逐字等于历史现状,零回归既有消费点);
|
|
3626
3796
|
* 'fast' 返回快线表(FAST_TRANSITIONS 常量引用)。phases / checkpoint_mapping / phase_corrections 两模式共用。
|
|
3797
|
+
* 'general' 返回空转移表(无流水线;phases 仍 15 项)。
|
|
3627
3798
|
*/
|
|
3628
3799
|
getDefaultPipelineConfig(mode = 'full') {
|
|
3629
3800
|
return {
|
|
@@ -3636,25 +3807,27 @@ export class FlowManager {
|
|
|
3636
3807
|
],
|
|
3637
3808
|
transitions: mode === 'fast'
|
|
3638
3809
|
? FAST_TRANSITIONS
|
|
3639
|
-
:
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3810
|
+
: mode === 'general'
|
|
3811
|
+
? {} // general:无流水线(空表;phases 仍 15 项,不新增 phase)
|
|
3812
|
+
: {
|
|
3813
|
+
plan_pending: ['plan_review', 'plan_passed'],
|
|
3814
|
+
plan_review: ['plan_passed', 'plan_pending'],
|
|
3815
|
+
plan_passed: ['scheme_pending'],
|
|
3816
|
+
scheme_pending: ['scheme_review', 'scheme_passed'],
|
|
3817
|
+
scheme_review: ['scheme_passed', 'scheme_pending'],
|
|
3818
|
+
scheme_passed: ['exec_running'],
|
|
3819
|
+
exec_running: ['review_pending', 'scheme_pending'], // BUG 修复:增加 scheme_pending
|
|
3820
|
+
review_pending: ['review_failed', 'review_passed'],
|
|
3821
|
+
review_failed: ['review_pending', 'scheme_pending'], // BUG 修复:增加 scheme_pending
|
|
3822
|
+
review_passed: ['test_pending'],
|
|
3823
|
+
test_pending: ['test_failed', 'test_passed'],
|
|
3824
|
+
test_failed: ['test_pending', 'scheme_pending'], // BUG 修复:增加 scheme_pending
|
|
3825
|
+
test_passed: ['archiving'],
|
|
3826
|
+
// 组合条件示例:审查通过 或 测试通过 任一即可推进到 archiving(多 Agent 并行场景,任一完成即推进)
|
|
3827
|
+
'review_passed|test_passed': ['archiving'],
|
|
3828
|
+
archiving: ['done'],
|
|
3829
|
+
done: [],
|
|
3830
|
+
},
|
|
3658
3831
|
checkpoint_mapping: {
|
|
3659
3832
|
plan: 'plan',
|
|
3660
3833
|
scheme: 'scheme',
|