openfeel 1.3.2 → 1.3.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 +1 -0
- package/README.md +2 -1
- package/README.zh-CN.md +1 -0
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +98 -14
- package/dist/commands/flow.js.map +1 -1
- package/dist/commands/instructions.js +1 -1
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +9 -1
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/archive/merge.js +8 -8
- package/dist/core/archive/merge.js.map +1 -1
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
- package/dist/core/artifact-graph/resolver.js +8 -0
- package/dist/core/artifact-graph/resolver.js.map +1 -1
- package/dist/core/config.d.ts +12 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +25 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/flow-manager.d.ts +26 -1
- package/dist/core/flow-manager.d.ts.map +1 -1
- package/dist/core/flow-manager.js +112 -9
- package/dist/core/flow-manager.js.map +1 -1
- package/dist/core/i18n-data/en.d.ts.map +1 -1
- package/dist/core/i18n-data/en.js +12 -0
- 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 +12 -0
- package/dist/core/i18n-data/zh-CN.js.map +1 -1
- package/dist/core/plan/stage.d.ts.map +1 -1
- package/dist/core/plan/stage.js +3 -1
- package/dist/core/plan/stage.js.map +1 -1
- package/dist/core/template-loader.d.ts.map +1 -1
- package/dist/core/template-loader.js +29 -18
- package/dist/core/template-loader.js.map +1 -1
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +16 -5
- package/dist/core/update.js.map +1 -1
- package/dist/core/view/entry.d.ts +13 -4
- package/dist/core/view/entry.d.ts.map +1 -1
- package/dist/core/view/entry.js +40 -16
- package/dist/core/view/entry.js.map +1 -1
- package/dist/core/workspace/structure.d.ts +21 -1
- package/dist/core/workspace/structure.d.ts.map +1 -1
- package/dist/core/workspace/structure.js +30 -11
- package/dist/core/workspace/structure.js.map +1 -1
- package/dsh/ADAPTER.md +14 -7
- package/dsh/README.md +3 -3
- package/dsh/lib/adapters/dashboard.js +2 -2
- package/dsh/lib/guard.js +78 -11
- package/dsh/lib/tools/shared.js +28 -14
- package/dsh/package.json +2 -2
- package/package.json +1 -1
|
@@ -41,9 +41,27 @@ export function isFlowConcurrentError(err) {
|
|
|
41
41
|
return err instanceof FlowConcurrentModificationError
|
|
42
42
|
|| (typeof err === 'object' && err !== null && err.name === 'FlowConcurrentModificationError');
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 解析审查条目的所属阶段(单点;v1.3.3 P0-2)。
|
|
46
|
+
* 优先级:显式 `review.stage`(stage 级 REV)> `review.op` 自身是合法 stageId(stage 级)
|
|
47
|
+
* > `review.op` 的 `lastIndexOf('.')` 前缀(op 级)。无法解析 → null。
|
|
48
|
+
* 返回值可能是短名(存量数据),调用方按需 `normalizeStageId` 比较。
|
|
49
|
+
*/
|
|
50
|
+
export function reviewStageOf(review) {
|
|
51
|
+
if (typeof review.stage === 'string' && review.stage !== '')
|
|
52
|
+
return review.stage;
|
|
53
|
+
const op = typeof review.op === 'string' ? review.op : '';
|
|
54
|
+
if (op === '')
|
|
55
|
+
return null;
|
|
56
|
+
// stage 级 REV:op 本身就是阶段 ID(含点,如 v1.0.0-stage-01)
|
|
57
|
+
if (parseStageId(op))
|
|
58
|
+
return op;
|
|
59
|
+
const dotIdx = op.lastIndexOf('.');
|
|
60
|
+
return dotIdx === -1 ? null : op.substring(0, dotIdx);
|
|
61
|
+
}
|
|
44
62
|
/**
|
|
45
63
|
* 过滤指定阶段域的阻塞 open REV(单点谓词)。
|
|
46
|
-
*
|
|
64
|
+
* 阶段域:`reviewStageOf(r)` 与 stage 相等(原始或归一化后)。
|
|
47
65
|
* 阻塞判定:r.blocking !== false(undefined 视为阻塞)且 r.status === 'open'。
|
|
48
66
|
* 纯只读;空 stage → []。
|
|
49
67
|
*/
|
|
@@ -51,9 +69,15 @@ export function listStageBlockingOpenReviews(reviews, stage) {
|
|
|
51
69
|
if (!stage) {
|
|
52
70
|
return [];
|
|
53
71
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
const normStage = normalizeStageId(stage) ?? stage;
|
|
73
|
+
return reviews.filter((r) => {
|
|
74
|
+
const reviewStage = reviewStageOf(r);
|
|
75
|
+
if (!reviewStage)
|
|
76
|
+
return false;
|
|
77
|
+
const belongsToStage = reviewStage === stage
|
|
78
|
+
|| (normalizeStageId(reviewStage) ?? reviewStage) === normStage;
|
|
79
|
+
return belongsToStage && r.blocking !== false && r.status === 'open';
|
|
80
|
+
});
|
|
57
81
|
}
|
|
58
82
|
/** 阶段目录冲突:两个不同 stageId 映射同一 (series, stageDir)(cli/BUG-002) */
|
|
59
83
|
export class StageDirConflictError extends Error {
|
|
@@ -192,7 +216,9 @@ const FAST_TRANSITIONS = {
|
|
|
192
216
|
exec_running: ['review_pending'],
|
|
193
217
|
review_pending: ['review_failed', 'review_passed'],
|
|
194
218
|
review_failed: ['review_pending', 'exec_running'],
|
|
195
|
-
|
|
219
|
+
// v1.3.3 P0-3:审查通过后允许带整改原因返工(feedback P0-3);full 表不变。
|
|
220
|
+
// findPhasePath 对 review_pending→exec_running 的同深度多义有显式 tie-break(优先经 review_failed)。
|
|
221
|
+
review_passed: ['archiving', 'exec_running'],
|
|
196
222
|
archiving: ['done'],
|
|
197
223
|
done: [],
|
|
198
224
|
};
|
|
@@ -1115,6 +1141,7 @@ export class FlowManager {
|
|
|
1115
1141
|
* @param phase 目标流水线阶段(PipelinePhase)
|
|
1116
1142
|
* @param triggeredBy 触发者标识(如 'cli' / 'Feel'),默认 'flow-manager'
|
|
1117
1143
|
* @param opts.reviewTier 本次调用的审查档位(**仅显式声明时写审计键** `review_tier`;缺省不写 ⟹ 既有条目逐字不变)
|
|
1144
|
+
* @param opts.reworkReason 审查通过后返工的整改原因(v1.3.3;仅写审计键 `rework_reason`,不参与状态机判定)
|
|
1118
1145
|
* @returns 是否触发 done 归档(true = 本次推进将阶段变为 done 且之前非 done,需在 save 后执行归档 commit)
|
|
1119
1146
|
*/
|
|
1120
1147
|
advanceStagePhase(stageName, phase, triggeredBy, opts) {
|
|
@@ -1149,8 +1176,8 @@ export class FlowManager {
|
|
|
1149
1176
|
}
|
|
1150
1177
|
// REV 闭环:blocking REV > 0 时拒绝推进到 done(--force 仅降级警告)
|
|
1151
1178
|
if (targetPhase === 'done') {
|
|
1152
|
-
|
|
1153
|
-
const blockingOpen =
|
|
1179
|
+
// v1.3.3:复用单点归属谓词(含 stage 级 REV / 短名键归一),与归档摘要同口径
|
|
1180
|
+
const blockingOpen = listStageBlockingOpenReviews(this.data.reviews, key);
|
|
1154
1181
|
if (blockingOpen.length > 0) {
|
|
1155
1182
|
const revList = blockingOpen
|
|
1156
1183
|
.map((r) => ` ${r.id}: ${r.title} (priority=${r.priority})`)
|
|
@@ -1197,6 +1224,7 @@ export class FlowManager {
|
|
|
1197
1224
|
stageName: key, from: fromPhase, to: targetPhase, session_id: sessionId,
|
|
1198
1225
|
...(resumed === null ? {} : { resumed }),
|
|
1199
1226
|
...(opts?.reviewTier === undefined ? {} : { review_tier: opts.reviewTier }),
|
|
1227
|
+
...(opts?.reworkReason === undefined ? {} : { rework_reason: opts.reworkReason }),
|
|
1200
1228
|
},
|
|
1201
1229
|
});
|
|
1202
1230
|
// REV-003: advance_stage_phase 改为 endStage 时批量聚合,取消逐条写入
|
|
@@ -1205,6 +1233,8 @@ export class FlowManager {
|
|
|
1205
1233
|
const prevStatus = stage.status;
|
|
1206
1234
|
const newStatus = mapPhaseToStageStatus(targetPhase, prevStatus);
|
|
1207
1235
|
stage.status = newStatus;
|
|
1236
|
+
// v1.3.3 P1-2:status.md 受管字段派生写(仅 CLI 写;缺失文件跳过;失败不阻塞推进)
|
|
1237
|
+
this.syncStatusManifest(key, targetPhase, fromPhase, prevStatus);
|
|
1208
1238
|
// 自动记录阶段计时:首次状态变更时启动
|
|
1209
1239
|
if (!stage.stats || !stage.stats.start_time) {
|
|
1210
1240
|
this.startStage(key);
|
|
@@ -1255,8 +1285,8 @@ export class FlowManager {
|
|
|
1255
1285
|
const projectedStatus = mapPhaseToStageStatus(targetPhase, stage.status);
|
|
1256
1286
|
// to=done:复用 advanceStagePhase 的 REV 阻塞检查(同语义,--force 不可绕过)
|
|
1257
1287
|
if (targetPhase === 'done') {
|
|
1258
|
-
|
|
1259
|
-
const blockingOpen =
|
|
1288
|
+
// v1.3.3:单点归属谓词(stage 级 REV / 短名键归一)——与 advance / 归档摘要同口径
|
|
1289
|
+
const blockingOpen = listStageBlockingOpenReviews(this.data.reviews, key);
|
|
1260
1290
|
if (blockingOpen.length > 0) {
|
|
1261
1291
|
const revList = blockingOpen
|
|
1262
1292
|
.map((r) => ` ${r.id}: ${r.title} (priority=${r.priority})`)
|
|
@@ -1315,6 +1345,21 @@ export class FlowManager {
|
|
|
1315
1345
|
console.log(t('flow.advance.gitCommitOkTmpl', lang, { stage: stageName }));
|
|
1316
1346
|
}
|
|
1317
1347
|
catch {
|
|
1348
|
+
// 区分「非 git 仓库」(无版本兜底 → 显式告警)与「无变更」(仅跳过提示)
|
|
1349
|
+
let inGitRepo = false;
|
|
1350
|
+
try {
|
|
1351
|
+
const revParse = execFileSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
1352
|
+
stdio: 'pipe',
|
|
1353
|
+
cwd: this.projectPath,
|
|
1354
|
+
});
|
|
1355
|
+
inGitRepo = String(revParse).trim() === 'true';
|
|
1356
|
+
}
|
|
1357
|
+
catch {
|
|
1358
|
+
inGitRepo = false;
|
|
1359
|
+
}
|
|
1360
|
+
if (!inGitRepo) {
|
|
1361
|
+
console.warn(t('flow.advance.gitCommitNoRepoWarnTmpl', lang, { stage: stageName }));
|
|
1362
|
+
}
|
|
1318
1363
|
// 不在 git 仓库 / git 不可用 / 无变更时静默跳过,不阻塞 done 推进
|
|
1319
1364
|
console.log(t('flow.advance.gitCommitSkipTmpl', lang, { stage: stageName }));
|
|
1320
1365
|
}
|
|
@@ -1675,6 +1720,15 @@ export class FlowManager {
|
|
|
1675
1720
|
}
|
|
1676
1721
|
}
|
|
1677
1722
|
if (hits.length > 1) {
|
|
1723
|
+
// v1.3.3 P0-3 显式 tie-break:fast 新增 review_passed→exec_running 后,
|
|
1724
|
+
// review_pending→exec_running 在深度 2 出现两条等价路径(经 review_failed / 经 review_passed)。
|
|
1725
|
+
// 保持修复前行为:优先「失败回环」路径(review_failed 首跳);其余多义仍拒绝。
|
|
1726
|
+
if (start === 'review_pending' && to === 'exec_running') {
|
|
1727
|
+
const preferred = hits.find((path) => path[0] === 'review_failed');
|
|
1728
|
+
if (preferred) {
|
|
1729
|
+
return { path: preferred, reason: 'ok' };
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1678
1732
|
return { path: [], reason: 'ambiguous' }; // 最短深度多条等价路径 → 拒绝
|
|
1679
1733
|
}
|
|
1680
1734
|
if (hits.length === 1) {
|
|
@@ -3153,6 +3207,55 @@ export class FlowManager {
|
|
|
3153
3207
|
return true;
|
|
3154
3208
|
});
|
|
3155
3209
|
}
|
|
3210
|
+
/**
|
|
3211
|
+
* status.md 多字段定向写(原子写 + 按阶段目录加锁;一次读 → 一次写)。
|
|
3212
|
+
* 仅替换**已存在**的 `- **{key}**:{value}` 行;字段缺失保持原样(不插入、不新建文件)。
|
|
3213
|
+
* 供 `syncStatusManifest`(P1-2 派生写)复用,避免逐字段多次加锁/写盘。
|
|
3214
|
+
*/
|
|
3215
|
+
writeStatusFields(statusPath, fields) {
|
|
3216
|
+
const stageDir = basename(dirname(statusPath));
|
|
3217
|
+
const lockPath = projectLockPath(this.projectPath, `status-${stageDir}`);
|
|
3218
|
+
return withFileLock(lockPath, () => {
|
|
3219
|
+
const content = readFileSync(statusPath, 'utf-8');
|
|
3220
|
+
let updated = content;
|
|
3221
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
3222
|
+
const fieldRegex = new RegExp(`^(-\\s*(?:\\*\\*)?${escapeRegex(key)}(?:\\*\\*)?[::]\\s*)(.*)$`, 'gm');
|
|
3223
|
+
updated = updated.replace(fieldRegex, `$1${value}`);
|
|
3224
|
+
}
|
|
3225
|
+
if (updated === content) {
|
|
3226
|
+
return false; // 全部未命中(缺失字段 / 同值)
|
|
3227
|
+
}
|
|
3228
|
+
atomicWriteFileSync(statusPath, updated);
|
|
3229
|
+
return true;
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
/**
|
|
3233
|
+
* 把 flow.json 权威状态派生到 status.md 的受管字段(v1.3.3 P1-2)。
|
|
3234
|
+
*
|
|
3235
|
+
* 只写 4 个受管字段:`状态` / `当前责任 Agent` / `上一责任 Agent`(首次推进保留 none)/ `更新时间`。
|
|
3236
|
+
* **仅 CLI 派生写**:Agent 反向手改 status.md 仍由写入门禁拒绝。
|
|
3237
|
+
* 缺失 status.md / 读取失败 → 静默跳过(不新建文件、不阻塞推进)。
|
|
3238
|
+
*/
|
|
3239
|
+
syncStatusManifest(stageName, toPhase, fromPhase, prevStatus) {
|
|
3240
|
+
try {
|
|
3241
|
+
const statusPath = this.findStatusPath(stageName);
|
|
3242
|
+
if (!statusPath || !existsSync(statusPath)) {
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
const fields = {
|
|
3246
|
+
'状态': mapPhaseToStageStatus(toPhase, prevStatus),
|
|
3247
|
+
'当前责任 Agent': this.mapPhaseToAgent(toPhase),
|
|
3248
|
+
'更新时间': new Date().toISOString().replace('T', ' ').substring(0, 16),
|
|
3249
|
+
};
|
|
3250
|
+
if (fromPhase !== 'plan_pending') {
|
|
3251
|
+
fields['上一责任 Agent'] = this.mapPhaseToAgent(fromPhase);
|
|
3252
|
+
}
|
|
3253
|
+
this.writeStatusFields(statusPath, fields);
|
|
3254
|
+
}
|
|
3255
|
+
catch {
|
|
3256
|
+
// 派生写失败(权限 / 锁超时等)→ 不阻塞状态机推进(flow.json 仍为权威)
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3156
3259
|
/**
|
|
3157
3260
|
* status.md 字段「插入」写(原子写 + 按阶段目录加锁)。
|
|
3158
3261
|
* 与 `writeStatusField`(替换语义)并列的**独立**实现,禁双重语义:
|