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.
Files changed (54) hide show
  1. package/README.en.md +1 -0
  2. package/README.md +2 -1
  3. package/README.zh-CN.md +1 -0
  4. package/dist/commands/flow.d.ts.map +1 -1
  5. package/dist/commands/flow.js +98 -14
  6. package/dist/commands/flow.js.map +1 -1
  7. package/dist/commands/instructions.js +1 -1
  8. package/dist/commands/instructions.js.map +1 -1
  9. package/dist/commands/plan.d.ts.map +1 -1
  10. package/dist/commands/plan.js +9 -1
  11. package/dist/commands/plan.js.map +1 -1
  12. package/dist/core/archive/merge.js +8 -8
  13. package/dist/core/archive/merge.js.map +1 -1
  14. package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
  15. package/dist/core/artifact-graph/resolver.js +8 -0
  16. package/dist/core/artifact-graph/resolver.js.map +1 -1
  17. package/dist/core/config.d.ts +12 -0
  18. package/dist/core/config.d.ts.map +1 -1
  19. package/dist/core/config.js +25 -3
  20. package/dist/core/config.js.map +1 -1
  21. package/dist/core/flow-manager.d.ts +26 -1
  22. package/dist/core/flow-manager.d.ts.map +1 -1
  23. package/dist/core/flow-manager.js +112 -9
  24. package/dist/core/flow-manager.js.map +1 -1
  25. package/dist/core/i18n-data/en.d.ts.map +1 -1
  26. package/dist/core/i18n-data/en.js +12 -0
  27. package/dist/core/i18n-data/en.js.map +1 -1
  28. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  29. package/dist/core/i18n-data/zh-CN.js +12 -0
  30. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  31. package/dist/core/plan/stage.d.ts.map +1 -1
  32. package/dist/core/plan/stage.js +3 -1
  33. package/dist/core/plan/stage.js.map +1 -1
  34. package/dist/core/template-loader.d.ts.map +1 -1
  35. package/dist/core/template-loader.js +29 -18
  36. package/dist/core/template-loader.js.map +1 -1
  37. package/dist/core/update.d.ts.map +1 -1
  38. package/dist/core/update.js +16 -5
  39. package/dist/core/update.js.map +1 -1
  40. package/dist/core/view/entry.d.ts +13 -4
  41. package/dist/core/view/entry.d.ts.map +1 -1
  42. package/dist/core/view/entry.js +40 -16
  43. package/dist/core/view/entry.js.map +1 -1
  44. package/dist/core/workspace/structure.d.ts +21 -1
  45. package/dist/core/workspace/structure.d.ts.map +1 -1
  46. package/dist/core/workspace/structure.js +30 -11
  47. package/dist/core/workspace/structure.js.map +1 -1
  48. package/dsh/ADAPTER.md +14 -7
  49. package/dsh/README.md +3 -3
  50. package/dsh/lib/adapters/dashboard.js +2 -2
  51. package/dsh/lib/guard.js +78 -11
  52. package/dsh/lib/tools/shared.js +28 -14
  53. package/dsh/package.json +2 -2
  54. 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
- * 阶段域:r.op === stage 或 r.op.startsWith(stage + '.')(`.` 分隔防前缀重叠)。
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
- return reviews.filter((r) => (r.op === stage || r.op.startsWith(stage + '.'))
55
- && r.blocking !== false
56
- && r.status === 'open');
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
- review_passed: ['archiving'],
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
- const stageReviews = this.data.reviews.filter((r) => r.op.startsWith(key + '.') || r.op === key);
1153
- const blockingOpen = stageReviews.filter((r) => r.blocking !== false && r.status === 'open');
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
- const stageReviews = this.data.reviews.filter((r) => r.op.startsWith(key + '.') || r.op === key);
1259
- const blockingOpen = stageReviews.filter((r) => r.blocking !== false && r.status === 'open');
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`(替换语义)并列的**独立**实现,禁双重语义: