openfeel 1.1.5 → 1.1.7

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 (72) hide show
  1. package/README.en.md +25 -5
  2. package/README.md +22 -3
  3. package/README.zh-CN.md +26 -5
  4. package/dist/commands/config.d.ts.map +1 -1
  5. package/dist/commands/config.js +66 -4
  6. package/dist/commands/config.js.map +1 -1
  7. package/dist/commands/flow.d.ts +8 -0
  8. package/dist/commands/flow.d.ts.map +1 -1
  9. package/dist/commands/flow.js +169 -10
  10. package/dist/commands/flow.js.map +1 -1
  11. package/dist/commands/plan.d.ts.map +1 -1
  12. package/dist/commands/plan.js +59 -2
  13. package/dist/commands/plan.js.map +1 -1
  14. package/dist/commands/setup.d.ts.map +1 -1
  15. package/dist/commands/setup.js +24 -2
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/commands/stage.d.ts.map +1 -1
  18. package/dist/commands/stage.js +56 -2
  19. package/dist/commands/stage.js.map +1 -1
  20. package/dist/commands/update.d.ts.map +1 -1
  21. package/dist/commands/update.js +16 -4
  22. package/dist/commands/update.js.map +1 -1
  23. package/dist/core/backup.d.ts.map +1 -1
  24. package/dist/core/backup.js +18 -5
  25. package/dist/core/backup.js.map +1 -1
  26. package/dist/core/claude-settings.d.ts +31 -0
  27. package/dist/core/claude-settings.d.ts.map +1 -0
  28. package/dist/core/claude-settings.js +120 -0
  29. package/dist/core/claude-settings.js.map +1 -0
  30. package/dist/core/config.d.ts +17 -2
  31. package/dist/core/config.d.ts.map +1 -1
  32. package/dist/core/config.js +21 -6
  33. package/dist/core/config.js.map +1 -1
  34. package/dist/core/flow-manager.d.ts +71 -14
  35. package/dist/core/flow-manager.d.ts.map +1 -1
  36. package/dist/core/flow-manager.js +266 -51
  37. package/dist/core/flow-manager.js.map +1 -1
  38. package/dist/core/global-paths.d.ts +10 -0
  39. package/dist/core/global-paths.d.ts.map +1 -1
  40. package/dist/core/global-paths.js +21 -0
  41. package/dist/core/global-paths.js.map +1 -1
  42. package/dist/core/i18n-data/en.d.ts.map +1 -1
  43. package/dist/core/i18n-data/en.js +44 -9
  44. package/dist/core/i18n-data/en.js.map +1 -1
  45. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  46. package/dist/core/i18n-data/zh-CN.js +45 -9
  47. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  48. package/dist/core/pipeline-schema.d.ts +5 -0
  49. package/dist/core/pipeline-schema.d.ts.map +1 -1
  50. package/dist/core/pipeline-schema.js +5 -0
  51. package/dist/core/pipeline-schema.js.map +1 -1
  52. package/dist/core/plan/scheme.d.ts +22 -0
  53. package/dist/core/plan/scheme.d.ts.map +1 -1
  54. package/dist/core/plan/scheme.js +71 -2
  55. package/dist/core/plan/scheme.js.map +1 -1
  56. package/dist/core/plan/stage.d.ts +2 -0
  57. package/dist/core/plan/stage.d.ts.map +1 -1
  58. package/dist/core/plan/stage.js +8 -0
  59. package/dist/core/plan/stage.js.map +1 -1
  60. package/dist/core/setup.d.ts +2 -2
  61. package/dist/core/setup.d.ts.map +1 -1
  62. package/dist/core/setup.js +81 -53
  63. package/dist/core/setup.js.map +1 -1
  64. package/dist/core/template-loader.d.ts +15 -0
  65. package/dist/core/template-loader.d.ts.map +1 -1
  66. package/dist/core/template-loader.js +4218 -145
  67. package/dist/core/template-loader.js.map +1 -1
  68. package/dist/core/update.d.ts +8 -0
  69. package/dist/core/update.d.ts.map +1 -1
  70. package/dist/core/update.js +368 -141
  71. package/dist/core/update.js.map +1 -1
  72. package/package.json +1 -1
@@ -3,7 +3,9 @@ import { existsSync, copyFileSync, readFileSync, rmSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { FlowManager, isFlowConcurrentError, normalizeAgentName, detectFillState, PHASE_PATH_MAX_DEPTH } from '../core/flow-manager.js';
5
5
  import { PipelinePhaseSchema, PIPELINE_PHASES } from '../core/pipeline-schema.js';
6
- import { validateStageId, suggestStageId, normalizeStageId } from '../core/plan/path.js';
6
+ import { validateStageId, suggestStageId, normalizeStageId, findStageStatusPath } from '../core/plan/path.js';
7
+ import { getConfigFieldLegalValues, setConfigValue } from '../core/config.js';
8
+ import { parse as parseYaml } from 'yaml';
7
9
  import { MetricsStore } from '../core/metrics.js';
8
10
  import { addReviewEntry } from '../core/view/entry.js';
9
11
  import { handleAddStageError, handleConcurrentConflict } from './shared/errors.js';
@@ -341,22 +343,58 @@ export function registerFlowCommand(program) {
341
343
  .command('phases')
342
344
  .description('列出全部合法 phase 及其流转映射(自描述)')
343
345
  .option('--json', 'Output { phases, transitions, advanceAccepted, schemaVersion } as JSON')
346
+ .option('--mode <mode>', '显式指定工作流模式(full | fast;缺省经 --stage / 当前阶段派生)')
347
+ .option('--stage <id>', '按指定阶段的模式展示转移表(缺省 = 当前阶段)')
344
348
  .action((options) => {
345
- const lang = getCliLang(process.cwd());
349
+ const projectPath = process.cwd();
350
+ const lang = getCliLang(projectPath);
346
351
  const mgr = createManager();
347
- const phases = mgr.getPipelinePhases();
348
- const transitions = mgr.getPipelineTransitions();
349
- // 运行时 vs 内置默认 transitions 差异(T19:使漂移可见,不补组合键)
350
- const defaults = mgr.getDefaultTransitions();
352
+ // ① --mode 显式值域校验(schema 派生;非法 → exit 1,不进入只读取值)
353
+ let explicitMode;
354
+ if (options.mode !== undefined) {
355
+ const allowed = getConfigFieldLegalValues('workflow_mode') ?? [];
356
+ if (!allowed.includes(options.mode)) {
357
+ console.error(t('stage.set.invalidValueTmpl', lang, { field: '工作流模式', value: options.mode, allowed: allowed.join(', ') }));
358
+ process.exit(1);
359
+ return;
360
+ }
361
+ explicitMode = options.mode;
362
+ }
363
+ // ② --stage 校验(格式 + 存在性):先于任何只读取值
364
+ let stageArg;
365
+ if (options.stage !== undefined) {
366
+ const v = validateStageId(options.stage);
367
+ if (!v.ok) {
368
+ console.error(t('common.stageIdInvalidTmpl', lang, { input: options.stage }));
369
+ console.error(t('common.stageIdSuggestTmpl', lang, { suggest: suggestStageId(projectPath, options.stage) }));
370
+ process.exit(1);
371
+ return;
372
+ }
373
+ stageArg = normalizeStageId(options.stage) ?? options.stage;
374
+ const known = new Set(Object.keys(mgr.isLoaded() ? mgr.getData().stages : {}).map((k) => normalizeStageId(k) ?? k));
375
+ if (!known.has(stageArg) && !findStageStatusPath(projectPath, stageArg)) {
376
+ console.error(t('common.stageIdInvalidTmpl', lang, { input: options.stage }));
377
+ console.error(t('common.stageIdSuggestTmpl', lang, { suggest: suggestStageId(projectPath, options.stage) }));
378
+ process.exit(1);
379
+ return;
380
+ }
381
+ }
382
+ // ③ 单点派生:显式 --mode 优先,否则经 --stage / 当前阶段级联解析
383
+ const resolved = mgr.resolveStageWorkflowMode(stageArg);
384
+ const derivedMode = explicitMode ?? resolved.value;
385
+ // ④ 显式传 mode(消除无参调用;访问器默认形参保留)
386
+ const transitions = mgr.getPipelineTransitions(derivedMode);
387
+ // 运行时 vs 内置默认 transitions 差异(T19:使漂移可见,不补组合键;同模式对比)
388
+ const defaults = mgr.getDefaultTransitions(derivedMode);
351
389
  const missingKeys = Object.keys(defaults).filter((k) => !(k in transitions));
352
390
  const extraKeys = Object.keys(transitions).filter((k) => !(k in defaults));
353
391
  const changedKeys = Object.keys(defaults).filter((k) => k in transitions && JSON.stringify(defaults[k]) !== JSON.stringify(transitions[k]));
354
392
  const transitionsDiff = { missing: missingKeys, extra: extraKeys, changed: changedKeys };
393
+ const phases = mgr.getPipelinePhases();
355
394
  if (options.json) {
356
395
  // advanceAccepted = 内置 15 phase(flow advance 的推进白名单),与 phases(运行时存在视图,可含自定义)显式区分
357
- // 既有三字段(phases/transitions/advanceAccepted)+ transitionsDiff 逐字保留;仅追加 schemaVersion:1(B1-6)
358
- // 注:schemaVersion 未来变更须递增版本号
359
- console.log(JSON.stringify({ schemaVersion: 1, phases, transitions, advanceAccepted: [...PIPELINE_PHASES], transitionsDiff }, null, 2));
396
+ // 既有三字段(phases/transitions/advanceAccepted)+ transitionsDiff 逐字保留;schemaVersion 与 workflowMode 追加于末位(B1-6 / stage-75 op-002)
397
+ console.log(JSON.stringify({ schemaVersion: 1, phases, transitions, advanceAccepted: [...PIPELINE_PHASES], transitionsDiff, workflowMode: derivedMode }, null, 2));
360
398
  return;
361
399
  }
362
400
  console.log(t('flow.phases.title', lang));
@@ -365,6 +403,9 @@ export function registerFlowCommand(program) {
365
403
  console.log(` - ${p}`);
366
404
  }
367
405
  console.log('');
406
+ // 模式分节行(stage-75 op-002:模式 + 来源 + 查看指引)
407
+ console.log(t('flow.mode.lineTmpl', lang, { mode: derivedMode, source: explicitMode ? '--mode' : resolved.source }));
408
+ console.log(t('flow.phases.modeHint', lang));
368
409
  console.log(t('flow.phases.transitionsLabel', lang) + ':');
369
410
  for (const [key, targets] of Object.entries(transitions)) {
370
411
  console.log(` ${key} → [${targets.join(', ')}]`);
@@ -391,6 +432,62 @@ export function registerFlowCommand(program) {
391
432
  }
392
433
  console.log(t('flow.phases.hint', lang));
393
434
  });
435
+ // flow mode — 当前工作流模式与来源(只读)
436
+ flow
437
+ .command('mode')
438
+ .description('显示当前工作流模式与来源(只读)')
439
+ .option('--stage <id>', '目标阶段(缺省 = 当前阶段)')
440
+ .option('--json', 'Output as JSON')
441
+ .action((options) => {
442
+ const projectPath = process.cwd();
443
+ const lang = getCliLang(projectPath);
444
+ const mgr = createManager();
445
+ let key;
446
+ if (options.stage !== undefined) {
447
+ const v = validateStageId(options.stage);
448
+ if (!v.ok) {
449
+ console.error(t('common.stageIdInvalidTmpl', lang, { input: options.stage }));
450
+ console.error(t('common.stageIdSuggestTmpl', lang, { suggest: suggestStageId(projectPath, options.stage) }));
451
+ process.exit(1);
452
+ return;
453
+ }
454
+ key = normalizeStageId(options.stage) ?? options.stage;
455
+ // 存在性:已注册(flow.json)或有 status.md;二者皆无 → exit 1(防 typo 静默落 config 层)
456
+ const known = new Set(Object.keys(mgr.isLoaded() ? mgr.getData().stages : {}).map((k) => normalizeStageId(k) ?? k));
457
+ if (!known.has(key) && !findStageStatusPath(projectPath, key)) {
458
+ console.error(t('common.stageIdInvalidTmpl', lang, { input: options.stage }));
459
+ console.error(t('common.stageIdSuggestTmpl', lang, { suggest: suggestStageId(projectPath, options.stage) }));
460
+ process.exit(1);
461
+ return;
462
+ }
463
+ }
464
+ else {
465
+ // 缺省 = 当前阶段;无 current 时 undefined(级联仍可解析 config/builtin)
466
+ key = mgr.getData()?.pipeline?.current?.stage || undefined;
467
+ }
468
+ // 唯一解析入口(禁第二套来源判定)
469
+ const resolved = mgr.resolveStageWorkflowMode(key);
470
+ const modes = {
471
+ full: mgr.getPipelineTransitions('full'),
472
+ fast: mgr.getPipelineTransitions('fast'),
473
+ };
474
+ if (options.json) {
475
+ // 未初始化亦保纯 JSON 单文档(stage 空串 + 无 stage 域级联解析值)
476
+ console.log(JSON.stringify({ schemaVersion: 1, stage: key ?? '', mode: resolved.value, source: resolved.source, modes }, null, 2));
477
+ return;
478
+ }
479
+ if (!mgr.isLoaded()) {
480
+ console.log(t('common.noInit', lang));
481
+ return;
482
+ }
483
+ console.log(t('flow.mode.lineTmpl', lang, { mode: resolved.value, source: resolved.source }));
484
+ // 阶段 phase 已知时补可达目标行(同源 getAvailablePhases)
485
+ const phase = key ? mgr.getData()?.stages[key]?.phase : undefined;
486
+ if (key && phase) {
487
+ console.log(t('flow.mode.targetsLabel', lang, { stage: key, phase, targets: mgr.getAvailablePhases(key).join(', ') }));
488
+ }
489
+ // 只读保证:不 save()、不 appendLog、不写任何文件
490
+ });
394
491
  // flow stage — 阶段管理子命令组
395
492
  const stageCmd = flow
396
493
  .command('stage')
@@ -611,7 +708,7 @@ export function registerFlowCommand(program) {
611
708
  .command('advance')
612
709
  .description('推进流水线阶段')
613
710
  .option('--op <id>', '操作 ID(如 stage-01.op-001),仅用于日志/展示')
614
- .requiredOption('--to <phase>', '目标阶段(如 exec_running)。合法 phase 与转移表:openfeel flow phases')
711
+ .requiredOption('--to <phase>', '目标阶段(如 exec_running)。合法 phase 与转移表:openfeel flow phases;查看当前模式:openfeel flow mode')
615
712
  .option('--stage <id>', '阶段 ID(如 stage-03),必须指定')
616
713
  .option('--force', '强制执行(跳过非法 phase 校验和阶段跳跃检查,但不可绕过 REV 阻塞检查)')
617
714
  .option('--dry-run', '仅验证不执行修改(预览输出)。与 --force 组合时跳过校验但仍不执行修改')
@@ -1641,6 +1738,32 @@ export function registerFlowCommand(program) {
1641
1738
  console.log(t('flow.wizard.unavailable', lang));
1642
1739
  return;
1643
1740
  }
1741
+ // stage-75 T4:首次选型 + 项目级记忆(TTY + 阶段无字段 + 项目未显式设默认 → 询问一次)
1742
+ {
1743
+ const modeResolved = mgr.resolveStageWorkflowMode(currentStage);
1744
+ const stageHasField = modeResolved.source === 'status.md';
1745
+ const projectHasDefault = hasExplicitProjectDefault(process.cwd(), 'workflow_mode');
1746
+ if (shouldPromptWorkflowMode(stageHasField, projectHasDefault) === 'prompt') {
1747
+ const chosen = (await select({
1748
+ message: t('flow.wizard.modePrompt', lang),
1749
+ choices: [
1750
+ { name: t('flow.wizard.modeFull', lang), value: 'full' },
1751
+ { name: t('flow.wizard.modeFast', lang), value: 'fast' },
1752
+ ],
1753
+ }));
1754
+ const statusPath = findStageStatusPath(process.cwd(), currentStage);
1755
+ if (statusPath) {
1756
+ const r = mgr.insertStatusField(statusPath, currentStage, '工作流模式', chosen);
1757
+ if (r === 'no-anchor') {
1758
+ // 错误路径:手工破坏文件(骨架必含锚点)→ 警告而非阻断(D5)
1759
+ console.error(t('stage.set.noAnchorTmpl', lang, { stageId: currentStage, field: '工作流模式' }));
1760
+ }
1761
+ }
1762
+ // 记忆写入仅 defaults.workflow_mode(增量写、保注释),不覆盖其它 defaults 键
1763
+ setConfigValue(process.cwd(), 'workflow_mode', chosen);
1764
+ console.log(t('flow.wizard.modeRememberedTmpl', lang, { mode: chosen }));
1765
+ }
1766
+ }
1644
1767
  const stagePhase = data.stages[currentStage].phase;
1645
1768
  // 显示当前状态
1646
1769
  console.log('\n' + t('flow.wizard.statusHeader', lang));
@@ -1720,6 +1843,42 @@ export function registerFlowCommand(program) {
1720
1843
  }
1721
1844
  });
1722
1845
  }
1846
+ /**
1847
+ * 「是否询问工作流模式」决策矩阵(stage-75 T4,D4)。
1848
+ * 仅当阶段 status.md 无「工作流模式」字段 **且** 项目 config.yaml 未显式设 `defaults.workflow_mode` 时询问;
1849
+ * 其余 3 态(有字段 / 有项目默认 / 两者皆有)一律 skip(保持既有推进流不变)。
1850
+ * @param stageHasField 阶段 status.md 是否已有「工作流模式」字段
1851
+ * @param projectHasDefault 项目 config.yaml 是否**显式**设过 `defaults.workflow_mode`
1852
+ */
1853
+ export function shouldPromptWorkflowMode(stageHasField, projectHasDefault) {
1854
+ return !stageHasField && !projectHasDefault ? 'prompt' : 'skip';
1855
+ }
1856
+ /**
1857
+ * 项目 config.yaml 是否**显式**声明了 `defaults.{key}`(stage-75 T4,D3)。
1858
+ * 必须**裸读 YAML**:`getConfigValue` 经 `ConfigDefaultsSchema` 逐键 `.default()` 填充,
1859
+ * 缺失键亦返回 schema 默认值(实测恒 `fast`),无法区分「设过」与「未设」。
1860
+ * 文件缺失 / 解析失败 / 无 defaults 块 / 非对象 → false(视为未显式设置)。
1861
+ * @param projectPath 项目根路径
1862
+ * @param key defaults 下的配置键(如 workflow_mode)
1863
+ */
1864
+ function hasExplicitProjectDefault(projectPath, key) {
1865
+ try {
1866
+ const configPath = resolve(projectPath, '.openfeel', 'config.yaml');
1867
+ if (!existsSync(configPath)) {
1868
+ return false;
1869
+ }
1870
+ const raw = parseYaml(readFileSync(configPath, 'utf-8'));
1871
+ const defaults = raw?.defaults;
1872
+ if (!defaults || typeof defaults !== 'object') {
1873
+ return false;
1874
+ }
1875
+ return Object.prototype.hasOwnProperty.call(defaults, key);
1876
+ }
1877
+ catch {
1878
+ // 错误路径:YAML 解析失败 → 视为未显式设置(不阻断向导)
1879
+ return false;
1880
+ }
1881
+ }
1723
1882
  /** 创建 FlowManager 实例(使用当前工作目录) */
1724
1883
  function createManager() {
1725
1884
  return new FlowManager(process.cwd());