openfeel 1.2.0 → 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 (88) hide show
  1. package/README.en.md +27 -8
  2. package/README.md +24 -5
  3. package/README.zh-CN.md +27 -8
  4. package/bin/openfeel.js +3 -2
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +5 -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 +6 -0
  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 +106 -8
  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 +7 -1
  16. package/dist/commands/plan.js.map +1 -1
  17. package/dist/commands/req.d.ts +2 -1
  18. package/dist/commands/req.d.ts.map +1 -1
  19. package/dist/commands/req.js +199 -26
  20. package/dist/commands/req.js.map +1 -1
  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-manager.d.ts +60 -9
  29. package/dist/core/flow-manager.d.ts.map +1 -1
  30. package/dist/core/flow-manager.js +189 -38
  31. package/dist/core/flow-manager.js.map +1 -1
  32. package/dist/core/i18n-data/en.d.ts.map +1 -1
  33. package/dist/core/i18n-data/en.js +44 -8
  34. package/dist/core/i18n-data/en.js.map +1 -1
  35. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  36. package/dist/core/i18n-data/zh-CN.js +44 -8
  37. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  38. package/dist/core/managed-region.d.ts +16 -2
  39. package/dist/core/managed-region.d.ts.map +1 -1
  40. package/dist/core/managed-region.js +24 -3
  41. package/dist/core/managed-region.js.map +1 -1
  42. package/dist/core/metrics.d.ts +2 -0
  43. package/dist/core/metrics.d.ts.map +1 -1
  44. package/dist/core/metrics.js +15 -0
  45. package/dist/core/metrics.js.map +1 -1
  46. package/dist/core/model-config.d.ts +1 -1
  47. package/dist/core/model-config.d.ts.map +1 -1
  48. package/dist/core/model-config.js +2 -2
  49. package/dist/core/model-config.js.map +1 -1
  50. package/dist/core/opencode-config.js +1 -1
  51. package/dist/core/opencode-config.js.map +1 -1
  52. package/dist/core/pipeline-schema.d.ts +15 -0
  53. package/dist/core/pipeline-schema.d.ts.map +1 -1
  54. package/dist/core/pipeline-schema.js +17 -0
  55. package/dist/core/pipeline-schema.js.map +1 -1
  56. package/dist/core/plan/stage.d.ts +2 -1
  57. package/dist/core/plan/stage.d.ts.map +1 -1
  58. package/dist/core/plan/stage.js +7 -2
  59. package/dist/core/plan/stage.js.map +1 -1
  60. package/dist/core/req/aggregate.d.ts +2 -1
  61. package/dist/core/req/aggregate.d.ts.map +1 -1
  62. package/dist/core/req/aggregate.js.map +1 -1
  63. package/dist/core/req/body-file.d.ts.map +1 -1
  64. package/dist/core/req/body-file.js +7 -3
  65. package/dist/core/req/body-file.js.map +1 -1
  66. package/dist/core/req/requirement-ops.d.ts +44 -2
  67. package/dist/core/req/requirement-ops.d.ts.map +1 -1
  68. package/dist/core/req/requirement-ops.js +108 -3
  69. package/dist/core/req/requirement-ops.js.map +1 -1
  70. package/dist/core/req/types.d.ts +7 -1
  71. package/dist/core/req/types.d.ts.map +1 -1
  72. package/dist/core/req/types.js +0 -6
  73. package/dist/core/req/types.js.map +1 -1
  74. package/dist/core/runtime-log.d.ts +18 -0
  75. package/dist/core/runtime-log.d.ts.map +1 -1
  76. package/dist/core/runtime-log.js +72 -0
  77. package/dist/core/runtime-log.js.map +1 -1
  78. package/dist/core/session.d.ts +24 -0
  79. package/dist/core/session.d.ts.map +1 -0
  80. package/dist/core/session.js +107 -0
  81. package/dist/core/session.js.map +1 -0
  82. package/dist/core/template-loader.d.ts.map +1 -1
  83. package/dist/core/template-loader.js +2982 -6686
  84. package/dist/core/template-loader.js.map +1 -1
  85. package/dist/core/update.d.ts.map +1 -1
  86. package/dist/core/update.js +694 -33
  87. package/dist/core/update.js.map +1 -1
  88. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { existsSync, copyFileSync, readFileSync, rmSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { FlowManager, isFlowConcurrentError, normalizeAgentName, detectFillState, PHASE_PATH_MAX_DEPTH } from '../core/flow-manager.js';
5
- import { PipelinePhaseSchema, PIPELINE_PHASES } from '../core/pipeline-schema.js';
4
+ import { FlowManager, isFlowConcurrentError, normalizeAgentName, detectFillState, MODE_DERIVED_TIER, PHASE_PATH_MAX_DEPTH } from '../core/flow-manager.js';
5
+ import { PipelinePhaseSchema, PIPELINE_PHASES, REVIEW_TIERS, TIER_PRUNED_PHASES } from '../core/pipeline-schema.js';
6
6
  import { validateStageId, suggestStageId, normalizeStageId, findStageStatusPath, isArchivedVersionOf } from '../core/plan/path.js';
7
7
  import { getConfigFieldLegalValues, setConfigValue } from '../core/config.js';
8
8
  import { parse as parseYaml } from 'yaml';
9
- import { MetricsStore } from '../core/metrics.js';
9
+ import { MetricsStore, PHASE_AGENT_MAP } from '../core/metrics.js';
10
+ import { runtimeLog } from '../core/runtime-log.js';
10
11
  import { addReviewEntry } from '../core/view/entry.js';
11
12
  import { handleAddStageError, handleConcurrentConflict } from './shared/errors.js';
12
13
  import { t, getCliLang } from '../core/i18n.js';
@@ -348,7 +349,7 @@ export function registerFlowCommand(program) {
348
349
  .command('phases')
349
350
  .description('列出全部合法 phase 及其流转映射(自描述)')
350
351
  .option('--json', 'Output { phases, transitions, advanceAccepted, schemaVersion } as JSON')
351
- .option('--mode <mode>', '显式指定工作流模式(full | fast;缺省经 --stage / 当前阶段派生)')
352
+ .option('--mode <mode>', '显式指定工作流模式(full | fast | general;缺省经 --stage / 当前阶段派生)')
352
353
  .option('--stage <id>', '按指定阶段的模式展示转移表(缺省 = 当前阶段)')
353
354
  .action((options) => {
354
355
  const projectPath = process.cwd();
@@ -399,7 +400,8 @@ export function registerFlowCommand(program) {
399
400
  if (options.json) {
400
401
  // advanceAccepted = 内置 15 phase(flow advance 的推进白名单),与 phases(运行时存在视图,可含自定义)显式区分
401
402
  // 既有三字段(phases/transitions/advanceAccepted)+ transitionsDiff 逐字保留;schemaVersion 与 workflowMode 追加于末位(B1-6 / stage-75 op-002)
402
- console.log(JSON.stringify({ schemaVersion: 1, phases, transitions, advanceAccepted: [...PIPELINE_PHASES], transitionsDiff, workflowMode: derivedMode }, null, 2));
403
+ // reviewTier 再追加于末位(stage-92 op-001;既有 6 键逐字保留、顺序不变)
404
+ console.log(JSON.stringify({ schemaVersion: 1, phases, transitions, advanceAccepted: [...PIPELINE_PHASES], transitionsDiff, workflowMode: derivedMode, reviewTier: MODE_DERIVED_TIER[derivedMode] }, null, 2));
403
405
  return;
404
406
  }
405
407
  console.log(t('flow.phases.title', lang));
@@ -411,6 +413,9 @@ export function registerFlowCommand(program) {
411
413
  // 模式分节行(stage-75 op-002:模式 + 来源 + 查看指引)
412
414
  console.log(t('flow.mode.lineTmpl', lang, { mode: derivedMode, source: explicitMode ? '--mode' : resolved.source }));
413
415
  console.log(t('flow.phases.modeHint', lang));
416
+ // 审查档位分节行(stage-92 op-001:档位 + 来源(模式派生)+ 查看指引;档位仅由模式**派生展示**,不改变推进语义)
417
+ console.log(t('flow.phases.tierLineTmpl', lang, { tier: MODE_DERIVED_TIER[derivedMode], mode: derivedMode }));
418
+ console.log(t('flow.phases.tierHint', lang));
414
419
  console.log(t('flow.phases.transitionsLabel', lang) + ':');
415
420
  for (const [key, targets] of Object.entries(transitions)) {
416
421
  console.log(` ${key} → [${targets.join(', ')}]`);
@@ -475,6 +480,7 @@ export function registerFlowCommand(program) {
475
480
  const modes = {
476
481
  full: mgr.getPipelineTransitions('full'),
477
482
  fast: mgr.getPipelineTransitions('fast'),
483
+ general: mgr.getPipelineTransitions('general'),
478
484
  };
479
485
  if (options.json) {
480
486
  // 未初始化亦保纯 JSON 单文档(stage 空串 + 无 stage 域级联解析值)
@@ -722,6 +728,7 @@ export function registerFlowCommand(program) {
722
728
  .option('--stage <id>', '阶段 ID(如 stage-03),必须指定')
723
729
  .option('--force', '强制执行(跳过非法 phase 校验和阶段跳跃检查,但不可绕过 REV 阻塞检查)')
724
730
  .option('--dry-run', '仅验证不执行修改(预览输出)。与 --force 组合时跳过校验但仍不执行修改')
731
+ .option('--review-tier <tier>', '审查档位(full / lean / skip;缺省=按模式派生;仅显式声明时裁剪审查关,不可绕过 REV 阻塞检查)')
725
732
  .option('--quiet', '静默非错误输出(成功确认与 Git 警告均不打印)')
726
733
  .action((options) => {
727
734
  const lang = getCliLang(process.cwd());
@@ -730,6 +737,19 @@ export function registerFlowCommand(program) {
730
737
  console.error(t('flow.advance.errorNoStage', lang));
731
738
  process.exit(1);
732
739
  }
740
+ // 审查档位值域校验(stage-92 op-001;fail-fast 且**先于任何取值 / 路径解析 / autoRepair 写盘**)
741
+ // 复用 stage.set.invalidValueTmpl(与 flow phases --mode 同款,零新增键);非法 → exit 1 零写盘
742
+ let explicitTier;
743
+ if (options.reviewTier !== undefined) {
744
+ if (!REVIEW_TIERS.includes(options.reviewTier)) {
745
+ console.error(t('stage.set.invalidValueTmpl', lang, { field: '审查档位', value: options.reviewTier, allowed: REVIEW_TIERS.join(', ') }));
746
+ process.exit(1);
747
+ return;
748
+ }
749
+ explicitTier = options.reviewTier;
750
+ }
751
+ // 生效档位:显式声明 ?? full(**不传 = 零裁剪**;派生档仅用于 `flow phases` 展示与提示,不改变推进语义)
752
+ const effTier = explicitTier ?? 'full';
733
753
  const mgr = createManager();
734
754
  if (!mgr.isLoaded()) {
735
755
  console.error(t('common.errorNoInit', lang));
@@ -781,11 +801,11 @@ export function registerFlowCommand(program) {
781
801
  // - 已在 to → no-op 成功
782
802
  let phasePath = [options.to];
783
803
  if (!options.force) {
784
- const pathResult = mgr.findPhasePath(stageArg, options.to);
804
+ const pathResult = mgr.findPhasePath(stageArg, options.to, effTier);
785
805
  if (pathResult.reason === 'no-path' || pathResult.reason === 'ambiguous' || pathResult.reason === 'depth-exceeded') {
786
806
  const data = mgr.getData();
787
807
  const currentPhase = data?.stages[stageArg]?.phase ?? t('common.unknown', lang);
788
- const availableTargets = mgr.getAvailablePhases(stageArg);
808
+ const availableTargets = mgr.getAvailablePhases(stageArg, effTier);
789
809
  if (pathResult.reason === 'ambiguous') {
790
810
  console.error(t('flow.advance.ambiguousTmpl', lang));
791
811
  }
@@ -793,6 +813,12 @@ export function registerFlowCommand(program) {
793
813
  console.error(t('flow.advance.depthExceededTmpl', lang, { max: String(PHASE_PATH_MAX_DEPTH) }));
794
814
  }
795
815
  else {
816
+ // general(无流水线)→ 显式错误引导(防御路径:常规写路径不产生 general 阶段态;REV-90-005 / D-90-3)
817
+ if (mgr.resolveStageWorkflowMode(stageArg).value === 'general') {
818
+ console.error(t('flow.advance.generalNoPipeline', lang, { stage: stageArg }));
819
+ process.exit(1);
820
+ return;
821
+ }
796
822
  console.error(t('flow.advance.noPathTmpl', lang, {
797
823
  from: currentPhase,
798
824
  to: options.to,
@@ -829,6 +855,14 @@ export function registerFlowCommand(program) {
829
855
  console.warn(t('flow.advance.warnSkipReview', lang));
830
856
  }
831
857
  }
858
+ // 审查档位协同警告(stage-92 op-001 / D-92-4):**仅显式声明**非全量档位时输出一行
859
+ // 非阻断(退出码不变);`--review-tier full` 与缺省不告警 ⟹ 既有流程零输出回归
860
+ if (explicitTier === 'lean' || explicitTier === 'skip') {
861
+ console.warn(t('flow.advance.warnTierTmpl', lang, {
862
+ tier: explicitTier,
863
+ gates: TIER_PRUNED_PHASES[explicitTier].join(', '),
864
+ }));
865
+ }
832
866
  // REV 闭环(命令层兜底):推进到 done 时检查 blocking REV(单步入口;多步循环每步后另复检,B5-3)
833
867
  if (options.to === 'done' && options.stage) {
834
868
  const blockingOpen = assertNoBlockingOpenRev(mgr, stageArg, lang);
@@ -853,6 +887,13 @@ export function registerFlowCommand(program) {
853
887
  console.log(` ` + t('common.stage', lang) + `: ${stageArg}`);
854
888
  console.log(` ` + t('flow.advance.dryRunFrom', lang) + `: ${startPhase}`);
855
889
  console.log(` ` + t('flow.advance.dryRunTo', lang) + `: ${options.to}`);
890
+ // 审查档位行(stage-92 op-001):仅显式声明时追加(缺省零输出回归);档位为空裁剪时 gates 显示「(无)」
891
+ if (explicitTier !== undefined) {
892
+ console.log(` ` + t('flow.advance.dryRunTierTmpl', lang, {
893
+ tier: explicitTier,
894
+ gates: TIER_PRUNED_PHASES[explicitTier].join(', ') || t('common.none', lang),
895
+ }));
896
+ }
856
897
  if (phasePath.length > 1) {
857
898
  const fullPath = [startPhase, ...phasePath].join(t('flow.advance.pathArrow', lang));
858
899
  console.log(` ` + t('flow.advance.pathTitle', lang) + ` ${fullPath}`);
@@ -863,13 +904,19 @@ export function registerFlowCommand(program) {
863
904
  }
864
905
  // 逐步推进(B5-2 / A3):唯一路径 → 依次调用 advanceStagePhase(保留每步校验/日志/checkpoint)
865
906
  // 中间态不回滚(A3);失败 → 输出「已完成 / 失败点 / 剩余路径」+ exit 1
907
+ // T1:recordRun 接线(命令层;--dry-run 已于上方提前 return——天然排除)
908
+ const metricsStore = MetricsStore.getInstance();
909
+ metricsStore.load(); // 读盘合并(防 save 覆盖历史记录);幂等
910
+ const logLenBefore = mgr.getData()?.log.length ?? 0; // 基线回扫上界(排除本次写入;REV-86-002)
911
+ let metricsRecorded = false;
912
+ let prevStepDoneMs = 0; // 多步时上一步完成时刻(i>0 的窗口基线)
866
913
  const totalSteps = phasePath.length;
867
914
  let archivedAny = false;
868
915
  for (let i = 0; i < totalSteps; i++) {
869
916
  const next = phasePath[i];
870
917
  let archived = false;
871
918
  try {
872
- archived = mgr.advanceStagePhase(stageArg, next, 'cli');
919
+ archived = mgr.advanceStagePhase(stageArg, next, 'cli', explicitTier ? { reviewTier: explicitTier } : undefined);
873
920
  }
874
921
  catch (err) {
875
922
  if (totalSteps > 1) {
@@ -884,6 +931,21 @@ export function registerFlowCommand(program) {
884
931
  }
885
932
  archivedAny = archivedAny || archived;
886
933
  mgr.save();
934
+ // T1:对刚退出的 fromPhase 记一条 run(窗口 = 进入该 phase 的转换 → 本次退出)
935
+ const exitedPhase = i > 0 ? phasePath[i - 1] : startPhase;
936
+ const agentName = PHASE_AGENT_MAP[exitedPhase];
937
+ if (agentName) {
938
+ const baseMs = i > 0 ? prevStepDoneMs : findPhaseEnteredAtMs(mgr, stageArg, exitedPhase, logLenBefore);
939
+ if (baseMs !== null && baseMs > 0) {
940
+ metricsStore.recordRun(agentName, Date.now() - baseMs, 'success', 0);
941
+ metricsRecorded = true;
942
+ }
943
+ else {
944
+ // 无基线 → 跳过不记(不写 0 值污染 avg);debug 留痕(默认关,不扰输出)
945
+ runtimeLog('debug', 'metrics skip (no baseline): stage=' + stageArg + ' phase=' + exitedPhase);
946
+ }
947
+ }
948
+ prevStepDoneMs = Date.now();
887
949
  // 归档 commit 必须在 flow.json save 之后执行,确保 commit 包含本次 phase 变更
888
950
  if (archived) {
889
951
  mgr.autoCommitOnDone(stageArg);
@@ -912,6 +974,10 @@ export function registerFlowCommand(program) {
912
974
  }
913
975
  }
914
976
  }
977
+ // T1:仅本命令内有记录时落盘一次(低并发非关键路径;推进失败提前 exit 则不 save)
978
+ if (metricsRecorded) {
979
+ metricsStore.save();
980
+ }
915
981
  // 成功确认(单步与多步统一);--quiet 完全静默
916
982
  if (!options.quiet) {
917
983
  console.log(t('flow.advance.okTmpl', lang, { stage: stageArg, to: options.to }));
@@ -2152,6 +2218,38 @@ function hasExplicitProjectDefault(projectPath, key) {
2152
2218
  return false;
2153
2219
  }
2154
2220
  }
2221
+ /**
2222
+ * 求「进入 fromPhase 的那次转换」的时间戳(T1 duration 基线;REV-86-002 口径)。
2223
+ * 回扫范围 [0, logLenBefore)——本次命令写入之前的存量条目(天然排除本次新写入);
2224
+ * 按 detail.to === fromPhase 匹配 advance/reset 转换条目;plan_pending 无转换条目时兜底 register_stage;
2225
+ * 无基线返回 null(调用方跳过不记——不写 0 值)。
2226
+ */
2227
+ function findPhaseEnteredAtMs(mgr, stageArg, fromPhase, logLenBefore) {
2228
+ const log = mgr.getData()?.log ?? [];
2229
+ const upper = Math.min(logLenBefore, log.length);
2230
+ for (let k = upper - 1; k >= 0; k--) {
2231
+ const e = log[k];
2232
+ if (e.action !== 'advance_stage_phase' && e.action !== 'reset_stage_phase') {
2233
+ continue;
2234
+ }
2235
+ if (e.detail.stageName !== stageArg || e.detail.to !== fromPhase) {
2236
+ continue;
2237
+ }
2238
+ const ms = Date.parse(e.time);
2239
+ return Number.isNaN(ms) ? null : ms;
2240
+ }
2241
+ if (fromPhase === 'plan_pending') {
2242
+ for (let k = upper - 1; k >= 0; k--) {
2243
+ const e = log[k];
2244
+ if (e.action !== 'register_stage' || e.detail.stageName !== stageArg) {
2245
+ continue;
2246
+ }
2247
+ const ms = Date.parse(e.time);
2248
+ return Number.isNaN(ms) ? null : ms;
2249
+ }
2250
+ }
2251
+ return null;
2252
+ }
2155
2253
  /** 创建 FlowManager 实例(使用当前工作目录) */
2156
2254
  function createManager() {
2157
2255
  return new FlowManager(process.cwd());