openfeel 1.0.8 → 1.1.0
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 +11 -11
- package/README.md +2 -1
- package/README.zh-CN.md +13 -13
- package/bin/openfeel.js +2 -2
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/archive.d.ts +1 -1
- package/dist/commands/flow.d.ts +1 -1
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +20 -8
- package/dist/commands/flow.js.map +1 -1
- package/dist/commands/migrate.d.ts +10 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +97 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/model.d.ts +10 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +135 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/plan.js +2 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/stage.d.ts.map +1 -1
- package/dist/commands/stage.js +66 -47
- package/dist/commands/stage.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +12 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +2 -1
- package/dist/commands/view.js.map +1 -1
- package/dist/core/archive/merge.d.ts.map +1 -1
- package/dist/core/archive/merge.js +5 -4
- package/dist/core/archive/merge.js.map +1 -1
- package/dist/core/artifact-graph/resolver.js +2 -2
- package/dist/core/artifact-graph/resolver.js.map +1 -1
- package/dist/core/config.d.ts +8 -8
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +22 -17
- package/dist/core/config.js.map +1 -1
- package/dist/core/flow-manager.d.ts +27 -6
- package/dist/core/flow-manager.d.ts.map +1 -1
- package/dist/core/flow-manager.js +152 -103
- package/dist/core/flow-manager.js.map +1 -1
- package/dist/core/fs/atomic-write.d.ts +31 -0
- package/dist/core/fs/atomic-write.d.ts.map +1 -0
- package/dist/core/fs/atomic-write.js +80 -0
- package/dist/core/fs/atomic-write.js.map +1 -0
- package/dist/core/fs/file-lock.d.ts +34 -0
- package/dist/core/fs/file-lock.d.ts.map +1 -0
- package/dist/core/fs/file-lock.js +142 -0
- package/dist/core/fs/file-lock.js.map +1 -0
- package/dist/core/fs/sequence.d.ts +42 -0
- package/dist/core/fs/sequence.d.ts.map +1 -0
- package/dist/core/fs/sequence.js +69 -0
- package/dist/core/fs/sequence.js.map +1 -0
- package/dist/core/global-paths.d.ts +25 -0
- package/dist/core/global-paths.d.ts.map +1 -0
- package/dist/core/global-paths.js +57 -0
- package/dist/core/global-paths.js.map +1 -0
- package/dist/core/i18n-data/en.d.ts +2 -0
- package/dist/core/i18n-data/en.d.ts.map +1 -1
- package/dist/core/i18n-data/en.js +69 -1
- package/dist/core/i18n-data/en.js.map +1 -1
- package/dist/core/i18n-data/zh-CN.d.ts +2 -0
- package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
- package/dist/core/i18n-data/zh-CN.js +69 -1
- package/dist/core/i18n-data/zh-CN.js.map +1 -1
- package/dist/core/i18n.js +4 -4
- package/dist/core/i18n.js.map +1 -1
- package/dist/core/init.d.ts +3 -3
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +58 -42
- package/dist/core/init.js.map +1 -1
- package/dist/core/managed-region.d.ts +54 -0
- package/dist/core/managed-region.d.ts.map +1 -0
- package/dist/core/managed-region.js +171 -0
- package/dist/core/managed-region.js.map +1 -0
- package/dist/core/metrics.d.ts.map +1 -1
- package/dist/core/metrics.js +6 -3
- package/dist/core/metrics.js.map +1 -1
- package/dist/core/migrate.d.ts +103 -0
- package/dist/core/migrate.d.ts.map +1 -0
- package/dist/core/migrate.js +533 -0
- package/dist/core/migrate.js.map +1 -0
- package/dist/core/model-config.d.ts +62 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/model-config.js +336 -0
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/opencode-config.d.ts +17 -0
- package/dist/core/opencode-config.d.ts.map +1 -0
- package/dist/core/opencode-config.js +123 -0
- package/dist/core/opencode-config.js.map +1 -0
- package/dist/core/plan/path.d.ts +64 -0
- package/dist/core/plan/path.d.ts.map +1 -0
- package/dist/core/plan/path.js +150 -0
- package/dist/core/plan/path.js.map +1 -0
- package/dist/core/plan/roadmap.d.ts.map +1 -1
- package/dist/core/plan/roadmap.js +3 -2
- package/dist/core/plan/roadmap.js.map +1 -1
- package/dist/core/plan/scheme.d.ts +4 -3
- package/dist/core/plan/scheme.d.ts.map +1 -1
- package/dist/core/plan/scheme.js +113 -110
- package/dist/core/plan/scheme.js.map +1 -1
- package/dist/core/plan/stage.d.ts +2 -2
- package/dist/core/plan/stage.d.ts.map +1 -1
- package/dist/core/plan/stage.js +47 -32
- package/dist/core/plan/stage.js.map +1 -1
- package/dist/core/public-logger.d.ts +3 -3
- package/dist/core/public-logger.d.ts.map +1 -1
- package/dist/core/public-logger.js +36 -40
- package/dist/core/public-logger.js.map +1 -1
- package/dist/core/template-loader.d.ts +1 -1
- package/dist/core/template-loader.d.ts.map +1 -1
- package/dist/core/template-loader.js +2849 -2826
- package/dist/core/template-loader.js.map +1 -1
- package/dist/core/update-infos.d.ts +32 -0
- package/dist/core/update-infos.d.ts.map +1 -0
- package/dist/core/update-infos.js +153 -0
- package/dist/core/update-infos.js.map +1 -0
- package/dist/core/update-state.d.ts +18 -0
- package/dist/core/update-state.d.ts.map +1 -1
- package/dist/core/update-state.js +66 -3
- package/dist/core/update-state.js.map +1 -1
- package/dist/core/update.d.ts +12 -0
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +340 -485
- package/dist/core/update.js.map +1 -1
- package/dist/core/view/entry.js +4 -4
- package/dist/core/view/entry.js.map +1 -1
- package/dist/core/workspace/identity.d.ts.map +1 -1
- package/dist/core/workspace/identity.js +13 -10
- package/dist/core/workspace/identity.js.map +1 -1
- package/dist/core/workspace/knowledge.d.ts.map +1 -1
- package/dist/core/workspace/knowledge.js +24 -14
- package/dist/core/workspace/knowledge.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** 短名(无版本前缀)默认系列 */
|
|
2
|
+
export declare const DEFAULT_SERIES = "v1";
|
|
3
|
+
/** 短名规范化为完整 stageId 时的默认四级版本前缀(与 init 示例阶段一致) */
|
|
4
|
+
export declare const DEFAULT_STAGE_VERSION = "v1.0.0";
|
|
5
|
+
/** stageId 解析结果 */
|
|
6
|
+
export interface ParsedStageId {
|
|
7
|
+
/** 大版本系列,如 v1 */
|
|
8
|
+
series: string;
|
|
9
|
+
/** 阶段目录名,如 stage-34 */
|
|
10
|
+
stageDir: string;
|
|
11
|
+
/** 规范化后的完整 stageId(短名补齐版本前缀) */
|
|
12
|
+
fullStageId: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 解析 stageId → { series, stageDir, fullStageId }
|
|
16
|
+
*
|
|
17
|
+
* 支持三种格式(决策 2 正向映射):
|
|
18
|
+
* - 完整:v1.0.0-stage-34 → { series: 'v1', stageDir: 'stage-34', fullStageId: 'v1.0.0-stage-34' }
|
|
19
|
+
* - 历史:v4-stage-04 → { series: 'v4', stageDir: 'stage-04', fullStageId: 'v4-stage-04' }
|
|
20
|
+
* - 短名:stage-01 → { series: 'v1', stageDir: 'stage-01', fullStageId: 'v1.0.0-stage-01' }
|
|
21
|
+
*
|
|
22
|
+
* 无法解析(如 'foo'、'v1'、空字符串)返回 null。
|
|
23
|
+
* 注意 stageDir 保留原始数字格式(stage-04 不丢前导零),NN 用捕获组原样保留,不做 parseInt。
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseStageId(stageId: string): ParsedStageId | null;
|
|
26
|
+
/**
|
|
27
|
+
* stageId → plan 相对目录(不含 .openfeel 前缀)
|
|
28
|
+
* 例:v1.0.0-stage-34 → 'plan/v1/stage-34/';stage-01 → 'plan/v1/stage-01/'
|
|
29
|
+
* 无法解析返回 null。
|
|
30
|
+
*/
|
|
31
|
+
export declare function stageIdToPlanDir(stageId: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* 短名/完整 stageId → 规范化完整 stageId
|
|
34
|
+
* 短名 stage-01 → v1.0.0-stage-01;完整/历史格式原样返回。
|
|
35
|
+
* 无法解析返回 null。
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeStageId(stageId: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* 目录名(stage-NN)→ 完整 stageId(反向映射,回查 flow.json stages 键)
|
|
40
|
+
*
|
|
41
|
+
* 目录名 stage-33 无法唯一还原版本前缀,须回查 flow.json 匹配 `*-stage-33`。
|
|
42
|
+
* 去歧义规则(遗留风险 1):若多个 stageId 匹配 `*-stage-{NN}`,
|
|
43
|
+
* 1. 优先 pipeline.current.stage(若在匹配集中);
|
|
44
|
+
* 2. 否则按版本前缀字典序降序取最新(v1.0.0 > v0.9 > v0.5)。
|
|
45
|
+
* 注:同 stage-NN 跨多版本前缀重复在理论上不应发生,此规则仅兜底。
|
|
46
|
+
*
|
|
47
|
+
* @returns 完整 stageId,找不到匹配或 flow.json 不可读时返回 null
|
|
48
|
+
*/
|
|
49
|
+
export declare function planDirToStageId(projectPath: string, stageDir: string): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* 三级回退查找 status.md(决策 4,供 flow-manager 与命令层复用)
|
|
52
|
+
*
|
|
53
|
+
* 1. plan/{series}/stage-NN/status.md(解析 stageId → 精确路径,首选)
|
|
54
|
+
* 2. plan/ 下递归搜索 stage-NN/status.md(fast-glob 递归,兼容 series 变化/旧平铺)
|
|
55
|
+
* 3. stages/{stageId}/status.md(历史遗留,只读兜底)
|
|
56
|
+
*
|
|
57
|
+
* 性能说明(遗留风险 3):第 1 级精确路径命中率最高(新写入均在此),
|
|
58
|
+
* 第 2 级 fast-glob 仅在精确未命中时触发(兼容旧数据/系列变化的罕见场景),
|
|
59
|
+
* 因此对 flow status 高频调用性能影响可控;本 stage 不做缓存,留作后续观察。
|
|
60
|
+
*
|
|
61
|
+
* @returns status.md 绝对路径,三级均未命中返回 null
|
|
62
|
+
*/
|
|
63
|
+
export declare function findStageStatusPath(projectPath: string, stageId: string): string | null;
|
|
64
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/core/plan/path.ts"],"names":[],"mappings":"AAaA,oBAAoB;AACpB,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,iDAAiD;AACjD,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,mBAAmB;AACnB,MAAM,WAAW,aAAa;IAC5B,iBAAiB;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAsBlE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6BrF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+BvF"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阶段路径映射工具 — stageId ↔ plan 目录双向映射的唯一权威
|
|
3
|
+
*
|
|
4
|
+
* 统一规则(决策 1/2):
|
|
5
|
+
* - plan 目录结构:.openfeel/plan/{series}/stage-{NN}/
|
|
6
|
+
* - {series} = 大版本系列 = v{MAJOR}(如 v1、v4)
|
|
7
|
+
* - {stage} = 阶段目录名 = stage-{NN}(不含版本前缀)
|
|
8
|
+
* - flow.json stageId 是权威标识(唯一键),目录名是派生组织单位
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
import fastGlob from 'fast-glob';
|
|
13
|
+
/** 短名(无版本前缀)默认系列 */
|
|
14
|
+
export const DEFAULT_SERIES = 'v1';
|
|
15
|
+
/** 短名规范化为完整 stageId 时的默认四级版本前缀(与 init 示例阶段一致) */
|
|
16
|
+
export const DEFAULT_STAGE_VERSION = 'v1.0.0';
|
|
17
|
+
/**
|
|
18
|
+
* 解析 stageId → { series, stageDir, fullStageId }
|
|
19
|
+
*
|
|
20
|
+
* 支持三种格式(决策 2 正向映射):
|
|
21
|
+
* - 完整:v1.0.0-stage-34 → { series: 'v1', stageDir: 'stage-34', fullStageId: 'v1.0.0-stage-34' }
|
|
22
|
+
* - 历史:v4-stage-04 → { series: 'v4', stageDir: 'stage-04', fullStageId: 'v4-stage-04' }
|
|
23
|
+
* - 短名:stage-01 → { series: 'v1', stageDir: 'stage-01', fullStageId: 'v1.0.0-stage-01' }
|
|
24
|
+
*
|
|
25
|
+
* 无法解析(如 'foo'、'v1'、空字符串)返回 null。
|
|
26
|
+
* 注意 stageDir 保留原始数字格式(stage-04 不丢前导零),NN 用捕获组原样保留,不做 parseInt。
|
|
27
|
+
*/
|
|
28
|
+
export function parseStageId(stageId) {
|
|
29
|
+
// 完整/历史格式:{version}-stage-{NN},version 可为 v1.0.0(四级)或 v4(仅主版本)
|
|
30
|
+
const full = stageId.match(/^(v\d+(?:\.\d+)*)-stage-(\d+)$/);
|
|
31
|
+
if (full) {
|
|
32
|
+
const version = full[1]; // v1.0.0 或 v4
|
|
33
|
+
const stageDir = `stage-${full[2]}`;
|
|
34
|
+
const series = version.split('.')[0]; // v1 或 v4
|
|
35
|
+
return { series, stageDir, fullStageId: stageId };
|
|
36
|
+
}
|
|
37
|
+
// 短名格式:stage-{NN}
|
|
38
|
+
const short = stageId.match(/^stage-(\d+)$/);
|
|
39
|
+
if (short) {
|
|
40
|
+
const stageDir = `stage-${short[1]}`;
|
|
41
|
+
return {
|
|
42
|
+
series: DEFAULT_SERIES,
|
|
43
|
+
stageDir,
|
|
44
|
+
fullStageId: `${DEFAULT_STAGE_VERSION}-${stageDir}`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* stageId → plan 相对目录(不含 .openfeel 前缀)
|
|
51
|
+
* 例:v1.0.0-stage-34 → 'plan/v1/stage-34/';stage-01 → 'plan/v1/stage-01/'
|
|
52
|
+
* 无法解析返回 null。
|
|
53
|
+
*/
|
|
54
|
+
export function stageIdToPlanDir(stageId) {
|
|
55
|
+
const parsed = parseStageId(stageId);
|
|
56
|
+
if (!parsed) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return `plan/${parsed.series}/${parsed.stageDir}/`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 短名/完整 stageId → 规范化完整 stageId
|
|
63
|
+
* 短名 stage-01 → v1.0.0-stage-01;完整/历史格式原样返回。
|
|
64
|
+
* 无法解析返回 null。
|
|
65
|
+
*/
|
|
66
|
+
export function normalizeStageId(stageId) {
|
|
67
|
+
const parsed = parseStageId(stageId);
|
|
68
|
+
return parsed ? parsed.fullStageId : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 目录名(stage-NN)→ 完整 stageId(反向映射,回查 flow.json stages 键)
|
|
72
|
+
*
|
|
73
|
+
* 目录名 stage-33 无法唯一还原版本前缀,须回查 flow.json 匹配 `*-stage-33`。
|
|
74
|
+
* 去歧义规则(遗留风险 1):若多个 stageId 匹配 `*-stage-{NN}`,
|
|
75
|
+
* 1. 优先 pipeline.current.stage(若在匹配集中);
|
|
76
|
+
* 2. 否则按版本前缀字典序降序取最新(v1.0.0 > v0.9 > v0.5)。
|
|
77
|
+
* 注:同 stage-NN 跨多版本前缀重复在理论上不应发生,此规则仅兜底。
|
|
78
|
+
*
|
|
79
|
+
* @returns 完整 stageId,找不到匹配或 flow.json 不可读时返回 null
|
|
80
|
+
*/
|
|
81
|
+
export function planDirToStageId(projectPath, stageDir) {
|
|
82
|
+
const flowPath = resolve(projectPath, '.openfeel', 'flow.json');
|
|
83
|
+
if (!existsSync(flowPath)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
let flowData;
|
|
87
|
+
try {
|
|
88
|
+
flowData = JSON.parse(readFileSync(flowPath, 'utf-8'));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const stages = flowData.stages ?? {};
|
|
94
|
+
const suffix = `-${stageDir}`; // -stage-33
|
|
95
|
+
const matches = Object.keys(stages).filter((k) => k.endsWith(suffix));
|
|
96
|
+
if (matches.length === 0) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (matches.length === 1) {
|
|
100
|
+
return matches[0];
|
|
101
|
+
}
|
|
102
|
+
// 去歧义:优先当前阶段,否则取版本字典序最新
|
|
103
|
+
const current = flowData.pipeline?.current?.stage;
|
|
104
|
+
if (current && matches.includes(current)) {
|
|
105
|
+
return current;
|
|
106
|
+
}
|
|
107
|
+
return matches.slice().sort().at(-1) ?? matches[0];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 三级回退查找 status.md(决策 4,供 flow-manager 与命令层复用)
|
|
111
|
+
*
|
|
112
|
+
* 1. plan/{series}/stage-NN/status.md(解析 stageId → 精确路径,首选)
|
|
113
|
+
* 2. plan/ 下递归搜索 stage-NN/status.md(fast-glob 递归,兼容 series 变化/旧平铺)
|
|
114
|
+
* 3. stages/{stageId}/status.md(历史遗留,只读兜底)
|
|
115
|
+
*
|
|
116
|
+
* 性能说明(遗留风险 3):第 1 级精确路径命中率最高(新写入均在此),
|
|
117
|
+
* 第 2 级 fast-glob 仅在精确未命中时触发(兼容旧数据/系列变化的罕见场景),
|
|
118
|
+
* 因此对 flow status 高频调用性能影响可控;本 stage 不做缓存,留作后续观察。
|
|
119
|
+
*
|
|
120
|
+
* @returns status.md 绝对路径,三级均未命中返回 null
|
|
121
|
+
*/
|
|
122
|
+
export function findStageStatusPath(projectPath, stageId) {
|
|
123
|
+
const parsed = parseStageId(stageId);
|
|
124
|
+
if (parsed) {
|
|
125
|
+
// 1. 精确路径 plan/{series}/stage-NN/status.md
|
|
126
|
+
const exact = resolve(projectPath, '.openfeel', 'plan', parsed.series, parsed.stageDir, 'status.md');
|
|
127
|
+
if (existsSync(exact)) {
|
|
128
|
+
return exact;
|
|
129
|
+
}
|
|
130
|
+
// 2. 递归搜索 plan/**/stage-NN/status.md
|
|
131
|
+
const planDir = resolve(projectPath, '.openfeel', 'plan');
|
|
132
|
+
if (existsSync(planDir)) {
|
|
133
|
+
const matches = fastGlob.sync(`**/${parsed.stageDir}/status.md`, {
|
|
134
|
+
cwd: planDir,
|
|
135
|
+
onlyFiles: true,
|
|
136
|
+
caseSensitiveMatch: true,
|
|
137
|
+
});
|
|
138
|
+
if (matches.length > 0) {
|
|
139
|
+
return resolve(planDir, matches[0]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// 3. 历史兜底 stages/{stageId}/status.md
|
|
144
|
+
const legacy = resolve(projectPath, '.openfeel', 'stages', stageId, 'status.md');
|
|
145
|
+
if (existsSync(legacy)) {
|
|
146
|
+
return legacy;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../../src/core/plan/path.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roadmap.d.ts","sourceRoot":"","sources":["../../../src/core/plan/roadmap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"roadmap.d.ts","sourceRoot":"","sources":["../../../src/core/plan/roadmap.ts"],"names":[],"mappings":"AAQA,WAAW;AACX,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAOD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAoCxE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA0BzE"}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* 分期大纲管理
|
|
3
3
|
* 负责 .openfeel/roadmap/ 下的版本大纲文件创建与读取
|
|
4
4
|
*/
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
6
6
|
import { resolve, join } from 'node:path';
|
|
7
|
+
import { atomicWriteFileSync } from '../fs/atomic-write.js';
|
|
7
8
|
/** 规范化版本号:去掉用户可能输入的前导 v */
|
|
8
9
|
function normalizeVersion(version) {
|
|
9
10
|
return version.replace(/^v/, '');
|
|
@@ -41,7 +42,7 @@ export function createRoadmap(projectPath, version) {
|
|
|
41
42
|
|
|
42
43
|
> 待补充
|
|
43
44
|
`;
|
|
44
|
-
|
|
45
|
+
atomicWriteFileSync(filePath, content);
|
|
45
46
|
console.log(`已创建分期大纲: v${ver}.md`);
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roadmap.js","sourceRoot":"","sources":["../../../src/core/plan/roadmap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"roadmap.js","sourceRoot":"","sources":["../../../src/core/plan/roadmap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAS5D,2BAA2B;AAC3B,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,WAAmB,EAAE,OAAe;IAChE,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAEhE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,KAAK,CAAC,CAAC;IAEhD,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,CAAC;QACjC,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,WAAW,GAAG;;;;;;;;;;;;;;;;;CAiB/B,CAAC;IAEA,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,WAAmB,EAAE,OAAgB;IAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAEhE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,kCAAkC,CAAC;IAC5C,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,KAAK,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,KAAK,CAAC,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC9C,CAAC;IAEF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -8,21 +8,22 @@ export interface Scheme {
|
|
|
8
8
|
title: string;
|
|
9
9
|
/** Markdown 全文 */
|
|
10
10
|
content: string;
|
|
11
|
-
/** 相对路径 .openfeel/
|
|
11
|
+
/** 相对路径 .openfeel/plan/{series}/{stage}/ops/op-001_{title}.md */
|
|
12
12
|
filePath: string;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* 创建操作方案
|
|
16
|
-
* 在 .openfeel/
|
|
16
|
+
* 在 .openfeel/plan/{series}/{stage}/ops/ 下创建 op-NNN_{title}.md
|
|
17
17
|
* NNN 自动递增(从该阶段的已有方案中计算)
|
|
18
18
|
* 必须按固定模板生成,包含:目标、实施步骤(checkbox)、产出文件、自测清单、修正记录
|
|
19
19
|
* 创建后自动同步到 flow.json(如果存在)
|
|
20
|
+
* @param stageName 阶段名(短名 stage-01 或完整 v1.0.0-stage-01 均可)
|
|
20
21
|
* @returns opId(如 op-001)
|
|
21
22
|
*/
|
|
22
23
|
export declare function createScheme(projectPath: string, stageName: string, title: string): string;
|
|
23
24
|
/**
|
|
24
25
|
* 读取操作方案
|
|
25
|
-
* @param opId 操作ID(如 op-001)或完整 opId(如 stage-01.op-001)
|
|
26
|
+
* @param opId 操作ID(如 op-001)或完整 opId(如 stage-01.op-001 / v1.0.0-stage-01.op-001)
|
|
26
27
|
* @returns Scheme 或 null
|
|
27
28
|
*/
|
|
28
29
|
export declare function getScheme(projectPath: string, opId: string): Scheme | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheme.d.ts","sourceRoot":"","sources":["../../../src/core/plan/scheme.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scheme.d.ts","sourceRoot":"","sources":["../../../src/core/plan/scheme.ts"],"names":[],"mappings":"AAaA,WAAW;AACX,MAAM,WAAW,MAAM;IACrB,eAAe;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW;IACX,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;CAClB;AA0HD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkC1F;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+B1E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CA6E7E"}
|
package/dist/core/plan/scheme.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 操作方案管理(
|
|
3
|
-
* 负责 .openfeel/
|
|
2
|
+
* 操作方案管理(openfeel-schemer 产出层)
|
|
3
|
+
* 负责 .openfeel/plan/{series}/{stage}/ops/ 下的操作方案文件 CRUD
|
|
4
4
|
* 创建后自动同步到 flow.json 的 stages/{stage}.ops 中
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
7
7
|
import { resolve, join } from 'node:path';
|
|
8
|
-
import { FlowManager } from '../flow-manager.js';
|
|
8
|
+
import { FlowManager, isFlowConcurrentError } from '../flow-manager.js';
|
|
9
|
+
import { parseStageId } from './path.js';
|
|
10
|
+
import { atomicWriteFileSync } from '../fs/atomic-write.js';
|
|
11
|
+
import { withFileLock, projectLockPath } from '../fs/file-lock.js';
|
|
12
|
+
import { reserveSequence } from '../fs/sequence.js';
|
|
9
13
|
/**
|
|
10
14
|
* 生成操作方案的固定 Markdown 模板
|
|
11
15
|
*/
|
|
@@ -15,7 +19,7 @@ function generateSchemeTemplate(opId, stageName, title) {
|
|
|
15
19
|
- **阶段**:${stageName}
|
|
16
20
|
- **状态**:pending
|
|
17
21
|
- **前置**:无
|
|
18
|
-
- **负责 Agent**:
|
|
22
|
+
- **负责 Agent**:openfeel-executor
|
|
19
23
|
- **最多重试**:3
|
|
20
24
|
|
|
21
25
|
## 目标
|
|
@@ -52,29 +56,14 @@ function extractTitle(fileName) {
|
|
|
52
56
|
const noPrefix = fileName.replace(/^op-\d+_?/, '');
|
|
53
57
|
return noPrefix.replace(/\.md$/, '').replace(/_/g, ' ');
|
|
54
58
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
function getNextOpId(opsDir) {
|
|
59
|
-
if (!existsSync(opsDir)) {
|
|
60
|
-
return 'op-001';
|
|
61
|
-
}
|
|
62
|
-
const files = readdirSync(opsDir).filter((f) => f.endsWith('.md'));
|
|
63
|
-
let maxNum = 0;
|
|
64
|
-
for (const file of files) {
|
|
65
|
-
const match = file.match(/^op-(\d+)/);
|
|
66
|
-
if (match) {
|
|
67
|
-
const num = parseInt(match[1], 10);
|
|
68
|
-
if (num > maxNum) {
|
|
69
|
-
maxNum = num;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return `op-${String(maxNum + 1).padStart(3, '0')}`;
|
|
59
|
+
/** 序号 → opId(3 位补零) */
|
|
60
|
+
function opIdOf(seq) {
|
|
61
|
+
return `op-${String(seq).padStart(3, '0')}`;
|
|
74
62
|
}
|
|
75
63
|
/**
|
|
76
64
|
* 同步方案到 flow.json
|
|
77
65
|
* 若 flow.json 不存在或对应 stage 不存在,跳过同步(不报错)
|
|
66
|
+
* @param stageName 完整 stageId(如 v1.0.0-stage-01,调用方已规范化)
|
|
78
67
|
*/
|
|
79
68
|
function syncToFlowJson(projectPath, stageName, opId, title) {
|
|
80
69
|
const flowJsonPath = resolve(projectPath, '.openfeel', 'flow.json');
|
|
@@ -103,7 +92,7 @@ function syncToFlowJson(projectPath, stageName, opId, title) {
|
|
|
103
92
|
id: opId,
|
|
104
93
|
title,
|
|
105
94
|
state: 'pending',
|
|
106
|
-
assignee: '
|
|
95
|
+
assignee: 'openfeel-executor',
|
|
107
96
|
attempts: 0,
|
|
108
97
|
max_attempts: 3,
|
|
109
98
|
checkpoints: {
|
|
@@ -116,91 +105,88 @@ function syncToFlowJson(projectPath, stageName, opId, title) {
|
|
|
116
105
|
};
|
|
117
106
|
flowMgr.save();
|
|
118
107
|
}
|
|
119
|
-
catch {
|
|
120
|
-
//
|
|
108
|
+
catch (err) {
|
|
109
|
+
// 并发冲突:op 文件已创建,但 flow.json 注册失败;不静默吞错,告警并提示兜底
|
|
110
|
+
if (isFlowConcurrentError(err)) {
|
|
111
|
+
console.warn(`[WARN] op ${opId} 已创建,但 flow.json 同步因并发冲突失败;` +
|
|
112
|
+
`请执行 openfeel flow repair 兜底或重新注册该 op。`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// 其它同步失败不阻塞方案创建(既有语义:静默忽略)
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
118
|
/**
|
|
124
119
|
* 创建操作方案
|
|
125
|
-
* 在 .openfeel/
|
|
120
|
+
* 在 .openfeel/plan/{series}/{stage}/ops/ 下创建 op-NNN_{title}.md
|
|
126
121
|
* NNN 自动递增(从该阶段的已有方案中计算)
|
|
127
122
|
* 必须按固定模板生成,包含:目标、实施步骤(checkbox)、产出文件、自测清单、修正记录
|
|
128
123
|
* 创建后自动同步到 flow.json(如果存在)
|
|
124
|
+
* @param stageName 阶段名(短名 stage-01 或完整 v1.0.0-stage-01 均可)
|
|
129
125
|
* @returns opId(如 op-001)
|
|
130
126
|
*/
|
|
131
127
|
export function createScheme(projectPath, stageName, title) {
|
|
132
|
-
//
|
|
133
|
-
const
|
|
128
|
+
// 解析 stageId(短名/完整)得到 series + stageDir + 完整 ID
|
|
129
|
+
const parsed = parseStageId(stageName);
|
|
130
|
+
if (!parsed) {
|
|
131
|
+
throw new Error(`非法阶段名: ${stageName}(应为 stage-NN 或 vX.Y.Z.W-stage-NN)`);
|
|
132
|
+
}
|
|
133
|
+
// 1. 确保 .openfeel/plan/{series}/{stageDir}/ops/ 目录存在
|
|
134
|
+
const opsDir = resolve(projectPath, '.openfeel', 'plan', parsed.series, parsed.stageDir, 'ops');
|
|
134
135
|
if (!existsSync(opsDir)) {
|
|
135
136
|
mkdirSync(opsDir, { recursive: true });
|
|
136
137
|
}
|
|
137
|
-
// 2.
|
|
138
|
-
const opId = getNextOpId(opsDir);
|
|
139
|
-
// 3. 生成模板内容并写入文件
|
|
140
|
-
// 文件名中空格用下划线替换
|
|
138
|
+
// 2. 锁内:原子占号 + 原子写 op 文件(临界区仅含占号与写文件,不含 flow 同步)
|
|
141
139
|
const safeTitle = title.replace(/\s+/g, '_');
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
const lockPath = projectLockPath(projectPath, `scheme-${parsed.stageDir}`);
|
|
141
|
+
const opId = withFileLock(lockPath, () => {
|
|
142
|
+
const reserved = reserveSequence({
|
|
143
|
+
dir: opsDir,
|
|
144
|
+
candidate: (seq) => `${opIdOf(seq)}_${safeTitle}.md`,
|
|
145
|
+
parse: (fileName) => {
|
|
146
|
+
const m = fileName.match(/^op-(\d+)/);
|
|
147
|
+
return m ? parseInt(m[1], 10) : null;
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const content = generateSchemeTemplate(opIdOf(reserved.seq), parsed.fullStageId, title);
|
|
151
|
+
atomicWriteFileSync(reserved.path, content);
|
|
152
|
+
return opIdOf(reserved.seq);
|
|
153
|
+
});
|
|
154
|
+
// 3. 同步到 flow.json(键用完整 stageId;由 FlowManager.save 的 flow.lock 保护)
|
|
155
|
+
syncToFlowJson(projectPath, parsed.fullStageId, opId, title);
|
|
149
156
|
return opId;
|
|
150
157
|
}
|
|
151
158
|
/**
|
|
152
159
|
* 读取操作方案
|
|
153
|
-
* @param opId 操作ID(如 op-001)或完整 opId(如 stage-01.op-001)
|
|
160
|
+
* @param opId 操作ID(如 op-001)或完整 opId(如 stage-01.op-001 / v1.0.0-stage-01.op-001)
|
|
154
161
|
* @returns Scheme 或 null
|
|
155
162
|
*/
|
|
156
163
|
export function getScheme(projectPath, opId) {
|
|
157
|
-
|
|
158
|
-
if (!existsSync(stagesDir)) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
let targetStageName;
|
|
164
|
+
let targetStage = null;
|
|
162
165
|
let targetOpId;
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
// 解析 opId:'{stage}.{op-XXX}' 或纯 '{op-XXX}'
|
|
167
|
+
// 用正则锚定尾部 .op-NNN,避免完整 stageId 版本号中的点号干扰
|
|
168
|
+
const match = opId.match(/^(.+)\.(op-\d+)$/);
|
|
169
|
+
if (match) {
|
|
170
|
+
targetStage = match[1];
|
|
171
|
+
targetOpId = match[2];
|
|
168
172
|
}
|
|
169
173
|
else {
|
|
170
|
-
|
|
174
|
+
targetStage = null;
|
|
171
175
|
targetOpId = opId;
|
|
172
176
|
}
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// 若指定了阶段名,只检查匹配的目录
|
|
180
|
-
if (targetStageName && stageEntry.name !== targetStageName) {
|
|
181
|
-
continue;
|
|
177
|
+
// 若指定阶段,解析出 stageDir 后遍历查找
|
|
178
|
+
let targetStageDir = null;
|
|
179
|
+
if (targetStage) {
|
|
180
|
+
const parsed = parseStageId(targetStage);
|
|
181
|
+
if (!parsed) {
|
|
182
|
+
return null; // 阶段名非法
|
|
182
183
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const opFiles = readdirSync(opsDir).filter((f) => f.startsWith(targetOpId) && f.endsWith('.md'));
|
|
189
|
-
if (opFiles.length === 0) {
|
|
190
|
-
continue;
|
|
184
|
+
targetStageDir = parsed.stageDir;
|
|
185
|
+
}
|
|
186
|
+
for (const scheme of listSchemes(projectPath, targetStageDir ?? undefined)) {
|
|
187
|
+
if (scheme.opId === targetOpId) {
|
|
188
|
+
return scheme;
|
|
191
189
|
}
|
|
192
|
-
// 找到匹配文件,读取内容
|
|
193
|
-
const matchedFile = opFiles[0]; // 取第一个匹配
|
|
194
|
-
const filePath = join(opsDir, matchedFile);
|
|
195
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
196
|
-
const scheme = {
|
|
197
|
-
opId: targetOpId,
|
|
198
|
-
stage: stageEntry.name,
|
|
199
|
-
title: extractTitle(matchedFile),
|
|
200
|
-
content,
|
|
201
|
-
filePath: `.openfeel/stages/${stageEntry.name}/ops/${matchedFile}`,
|
|
202
|
-
};
|
|
203
|
-
return scheme;
|
|
204
190
|
}
|
|
205
191
|
return null;
|
|
206
192
|
}
|
|
@@ -209,44 +195,61 @@ export function getScheme(projectPath, opId) {
|
|
|
209
195
|
* @param stageName 可选,不传则列出所有阶段的方案
|
|
210
196
|
*/
|
|
211
197
|
export function listSchemes(projectPath, stageName) {
|
|
212
|
-
const
|
|
198
|
+
const planDir = resolve(projectPath, '.openfeel', 'plan');
|
|
213
199
|
const result = [];
|
|
214
|
-
if (!existsSync(
|
|
200
|
+
if (!existsSync(planDir)) {
|
|
215
201
|
return result;
|
|
216
202
|
}
|
|
217
|
-
//
|
|
218
|
-
|
|
203
|
+
// 解析目标 stageDir(若指定阶段过滤)
|
|
204
|
+
let targetStageDir = null;
|
|
219
205
|
if (stageName) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const stageEntries = readdirSync(stagesDir, { withFileTypes: true });
|
|
224
|
-
for (const entry of stageEntries) {
|
|
225
|
-
if (entry.isDirectory()) {
|
|
226
|
-
stageNames.push(entry.name);
|
|
227
|
-
}
|
|
206
|
+
const parsed = parseStageId(stageName);
|
|
207
|
+
if (!parsed) {
|
|
208
|
+
return result; // 阶段名非法时返回空
|
|
228
209
|
}
|
|
210
|
+
targetStageDir = parsed.stageDir;
|
|
229
211
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
212
|
+
// 遍历 plan/{series}/stage-NN/ 两层目录
|
|
213
|
+
const seriesEntries = readdirSync(planDir, { withFileTypes: true });
|
|
214
|
+
for (const seriesEntry of seriesEntries) {
|
|
215
|
+
if (!seriesEntry.isDirectory()) {
|
|
233
216
|
continue;
|
|
234
217
|
}
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
218
|
+
const seriesDir = join(planDir, seriesEntry.name);
|
|
219
|
+
let stageEntries;
|
|
220
|
+
try {
|
|
221
|
+
stageEntries = readdirSync(seriesDir, { withFileTypes: true });
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
for (const stageEntry of stageEntries) {
|
|
227
|
+
if (!stageEntry.isDirectory()) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (targetStageDir && stageEntry.name !== targetStageDir) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const opsDir = join(seriesDir, stageEntry.name, 'ops');
|
|
234
|
+
if (!existsSync(opsDir)) {
|
|
239
235
|
continue;
|
|
240
236
|
}
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
opId
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
filePath
|
|
249
|
-
|
|
237
|
+
const opFiles = readdirSync(opsDir).filter((f) => f.endsWith('.md'));
|
|
238
|
+
for (const fileName of opFiles) {
|
|
239
|
+
const opId = extractOpId(fileName);
|
|
240
|
+
if (!opId) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const filePath = join(opsDir, fileName);
|
|
244
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
245
|
+
result.push({
|
|
246
|
+
opId,
|
|
247
|
+
stage: stageEntry.name,
|
|
248
|
+
title: extractTitle(fileName),
|
|
249
|
+
content,
|
|
250
|
+
filePath: `.openfeel/plan/${seriesEntry.name}/${stageEntry.name}/ops/${fileName}`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
// 按阶段名 + opId 排序
|