@epoint-testtech/ep-stage-skill 0.0.3-alpha.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 (151) hide show
  1. package/SKILL.md +27 -0
  2. package/codex-skill/ep-stage/create-project/SKILL.md +59 -0
  3. package/codex-skill/ep-stage/glue-test/SKILL.md +258 -0
  4. package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +139 -0
  5. package/codex-skill/ep-stage/glue-test/references/gap-review-protocol.md +43 -0
  6. package/codex-skill/ep-stage/glue-test/references/harness-principles.md +46 -0
  7. package/codex-skill/ep-stage/glue-test/scripts/generate-crud-spec.mjs +149 -0
  8. package/codex-skill/ep-stage/glue-testcase/SKILL.md +31 -0
  9. package/codex-skill/ep-stage/glue-testcase/examples/observable-testcase.json +40 -0
  10. package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +67 -0
  11. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +27 -0
  12. package/codex-skill/ep-stage/scripts/validate-skill.mjs +73 -0
  13. package/dist/src/capability/coverage-diff.d.ts +34 -0
  14. package/dist/src/capability/coverage-diff.d.ts.map +1 -0
  15. package/dist/src/capability/coverage-diff.js +91 -0
  16. package/dist/src/capability/page-structure.d.ts +31 -0
  17. package/dist/src/capability/page-structure.d.ts.map +1 -0
  18. package/dist/src/capability/page-structure.js +50 -0
  19. package/dist/src/capability/scenario-inference.d.ts +36 -0
  20. package/dist/src/capability/scenario-inference.d.ts.map +1 -0
  21. package/dist/src/capability/scenario-inference.js +114 -0
  22. package/dist/src/cli/generate-crud-contract.d.ts +2 -0
  23. package/dist/src/cli/generate-crud-contract.d.ts.map +1 -0
  24. package/dist/src/cli/generate-crud-contract.js +77 -0
  25. package/dist/src/cli/generate-playwright-tests.d.ts +30 -0
  26. package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -0
  27. package/dist/src/cli/generate-playwright-tests.js +81 -0
  28. package/dist/src/cli/run-gap-pipeline.d.ts +256 -0
  29. package/dist/src/cli/run-gap-pipeline.d.ts.map +1 -0
  30. package/dist/src/cli/run-gap-pipeline.js +1468 -0
  31. package/dist/src/context/stage-context.d.ts +63 -0
  32. package/dist/src/context/stage-context.d.ts.map +1 -0
  33. package/dist/src/context/stage-context.js +297 -0
  34. package/dist/src/contracts/crud-business-module.d.ts +645 -0
  35. package/dist/src/contracts/crud-business-module.d.ts.map +1 -0
  36. package/dist/src/contracts/crud-business-module.js +1 -0
  37. package/dist/src/contracts/gap-inference.d.ts +213 -0
  38. package/dist/src/contracts/gap-inference.d.ts.map +1 -0
  39. package/dist/src/contracts/gap-inference.js +11 -0
  40. package/dist/src/contracts/observable-chain.d.ts +250 -0
  41. package/dist/src/contracts/observable-chain.d.ts.map +1 -0
  42. package/dist/src/contracts/observable-chain.js +1 -0
  43. package/dist/src/extractors/code-list.d.ts +40 -0
  44. package/dist/src/extractors/code-list.d.ts.map +1 -0
  45. package/dist/src/extractors/code-list.js +225 -0
  46. package/dist/src/extractors/html-page.d.ts +67 -0
  47. package/dist/src/extractors/html-page.d.ts.map +1 -0
  48. package/dist/src/extractors/html-page.js +195 -0
  49. package/dist/src/extractors/java-action.d.ts +8 -0
  50. package/dist/src/extractors/java-action.d.ts.map +1 -0
  51. package/dist/src/extractors/java-action.js +53 -0
  52. package/dist/src/extractors/spec-yaml.d.ts +28 -0
  53. package/dist/src/extractors/spec-yaml.d.ts.map +1 -0
  54. package/dist/src/extractors/spec-yaml.js +29 -0
  55. package/dist/src/gap-planner/action-candidates.d.ts +9 -0
  56. package/dist/src/gap-planner/action-candidates.d.ts.map +1 -0
  57. package/dist/src/gap-planner/action-candidates.js +66 -0
  58. package/dist/src/gap-planner/list-gap-workflows.d.ts +17 -0
  59. package/dist/src/gap-planner/list-gap-workflows.d.ts.map +1 -0
  60. package/dist/src/gap-planner/list-gap-workflows.js +47 -0
  61. package/dist/src/gap-planner/plan-agent-workflows.d.ts +26 -0
  62. package/dist/src/gap-planner/plan-agent-workflows.d.ts.map +1 -0
  63. package/dist/src/gap-planner/plan-agent-workflows.js +116 -0
  64. package/dist/src/gap-planner/skeleton-coverage.d.ts +9 -0
  65. package/dist/src/gap-planner/skeleton-coverage.d.ts.map +1 -0
  66. package/dist/src/gap-planner/skeleton-coverage.js +41 -0
  67. package/dist/src/gap-planner/stable-id.d.ts +16 -0
  68. package/dist/src/gap-planner/stable-id.d.ts.map +1 -0
  69. package/dist/src/gap-planner/stable-id.js +19 -0
  70. package/dist/src/generalization/generalization-eval.d.ts +71 -0
  71. package/dist/src/generalization/generalization-eval.d.ts.map +1 -0
  72. package/dist/src/generalization/generalization-eval.js +53 -0
  73. package/dist/src/generators/agent-inferred-workflow-script.d.ts +22 -0
  74. package/dist/src/generators/agent-inferred-workflow-script.d.ts.map +1 -0
  75. package/dist/src/generators/agent-inferred-workflow-script.js +230 -0
  76. package/dist/src/generators/stage-skeleton-script.d.ts +107 -0
  77. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -0
  78. package/dist/src/generators/stage-skeleton-script.js +607 -0
  79. package/dist/src/index.d.ts +52 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +26 -0
  82. package/dist/src/material/material-inventory.d.ts +92 -0
  83. package/dist/src/material/material-inventory.d.ts.map +1 -0
  84. package/dist/src/material/material-inventory.js +191 -0
  85. package/dist/src/normalizers/crud-contract.d.ts +107 -0
  86. package/dist/src/normalizers/crud-contract.d.ts.map +1 -0
  87. package/dist/src/normalizers/crud-contract.js +1068 -0
  88. package/dist/src/testcase/testcase-generator.d.ts +43 -0
  89. package/dist/src/testcase/testcase-generator.d.ts.map +1 -0
  90. package/dist/src/testcase/testcase-generator.js +152 -0
  91. package/dist/src/testcase/testcase-skeleton.d.ts +91 -0
  92. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -0
  93. package/dist/src/testcase/testcase-skeleton.js +121 -0
  94. package/dist/src/testcase/testcase-spec-assembly.d.ts +11 -0
  95. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -0
  96. package/dist/src/testcase/testcase-spec-assembly.js +24 -0
  97. package/dist/src/trace/review-summary.d.ts +17 -0
  98. package/dist/src/trace/review-summary.d.ts.map +1 -0
  99. package/dist/src/trace/review-summary.js +34 -0
  100. package/dist/src/trace/trace-writer.d.ts +17 -0
  101. package/dist/src/trace/trace-writer.d.ts.map +1 -0
  102. package/dist/src/trace/trace-writer.js +81 -0
  103. package/dist/test/crud-contract.test.d.ts +2 -0
  104. package/dist/test/crud-contract.test.d.ts.map +1 -0
  105. package/dist/test/crud-contract.test.js +819 -0
  106. package/dist/test/gap-inference.test.d.ts +2 -0
  107. package/dist/test/gap-inference.test.d.ts.map +1 -0
  108. package/dist/test/gap-inference.test.js +597 -0
  109. package/dist/test/generalization.test.d.ts +2 -0
  110. package/dist/test/generalization.test.d.ts.map +1 -0
  111. package/dist/test/generalization.test.js +73 -0
  112. package/dist/test/material-inventory.test.d.ts +2 -0
  113. package/dist/test/material-inventory.test.d.ts.map +1 -0
  114. package/dist/test/material-inventory.test.js +141 -0
  115. package/dist/test/observable-chain.test.d.ts +2 -0
  116. package/dist/test/observable-chain.test.d.ts.map +1 -0
  117. package/dist/test/observable-chain.test.js +123 -0
  118. package/dist/test/observable-pipeline.test.d.ts +2 -0
  119. package/dist/test/observable-pipeline.test.d.ts.map +1 -0
  120. package/dist/test/observable-pipeline.test.js +461 -0
  121. package/dist/test/page-structure.test.d.ts +2 -0
  122. package/dist/test/page-structure.test.d.ts.map +1 -0
  123. package/dist/test/page-structure.test.js +45 -0
  124. package/dist/test/scenario-inference.test.d.ts +2 -0
  125. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  126. package/dist/test/scenario-inference.test.js +73 -0
  127. package/dist/test/stage-context.test.d.ts +2 -0
  128. package/dist/test/stage-context.test.d.ts.map +1 -0
  129. package/dist/test/stage-context.test.js +263 -0
  130. package/dist/test/testcase-generator.test.d.ts +2 -0
  131. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  132. package/dist/test/testcase-generator.test.js +276 -0
  133. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  134. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  135. package/dist/test/testcase-skeleton.test.js +185 -0
  136. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  137. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  138. package/dist/test/testcase-spec-assembly.test.js +105 -0
  139. package/dist/vitest.config.d.ts +3 -0
  140. package/dist/vitest.config.d.ts.map +1 -0
  141. package/dist/vitest.config.js +7 -0
  142. package/docs/README.md +134 -0
  143. package/docs/mvp-usage-guide.md +298 -0
  144. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -0
  145. package/examples/schemeresource.module-hints.json +38 -0
  146. package/examples/schemeresource.observable.code_list.md +37 -0
  147. package/examples/zwplace-observable-docs/zwplace.context.md +16 -0
  148. package/examples/zwplace-placecategory-validation.json +29 -0
  149. package/examples/zwplace.module-hints.json +69 -0
  150. package/examples/zwplace.observable.code_list.md +37 -0
  151. package/package.json +38 -0
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const skillRoot = path.resolve(__dirname, '..');
9
+ const packageRoot = path.resolve(skillRoot, '../..');
10
+ const repoRoot = path.resolve(packageRoot, '../..');
11
+
12
+ function help() {
13
+ console.log(`Usage:
14
+ node scripts/generate-crud-spec.mjs \\
15
+ --module-id <module-id> \\
16
+ --docs <docs-dir> \\
17
+ --code-list <code-list-md> \\
18
+ --webapp <html-dir> \\
19
+ --java-actions <java-action-dir> \\
20
+ --hints <module-hints-json> \\
21
+ --contract-out <package-output-json> \\
22
+ --spec-out <glue-project-spec-ts> \\
23
+ --menu <menu-path>
24
+
25
+ Notes:
26
+ - Relative upstream paths are resolved from the repository root.
27
+ - The contract path is passed to @epoint/ep-stage-skill relative to its package root.
28
+ - The script fails when unresolvedSlots is non-empty.
29
+ `);
30
+ }
31
+
32
+ function parseArgs(argv) {
33
+ const args = {};
34
+ for (let index = 0; index < argv.length; index += 2) {
35
+ const key = argv[index];
36
+ const value = argv[index + 1];
37
+ if (key === '--help' || key === '-h') {
38
+ help();
39
+ process.exit(0);
40
+ }
41
+ if (!key?.startsWith('--') || !value) {
42
+ throw new Error(`Invalid argument near ${key ?? '<empty>'}`);
43
+ }
44
+ args[key.slice(2)] = value;
45
+ }
46
+ return args;
47
+ }
48
+
49
+ function resolveFromRepo(value) {
50
+ return path.isAbsolute(value) ? value : path.resolve(repoRoot, value);
51
+ }
52
+
53
+ function requirePath(label, value) {
54
+ if (!value) throw new Error(`Missing --${label}`);
55
+ const resolved = resolveFromRepo(value);
56
+ if (!existsSync(resolved)) throw new Error(`Path for --${label} does not exist: ${resolved}`);
57
+ return resolved;
58
+ }
59
+
60
+ function run(commandArgs) {
61
+ const result = spawnSync('pnpm', commandArgs, {
62
+ cwd: repoRoot,
63
+ stdio: 'inherit',
64
+ shell: false
65
+ });
66
+ if (result.status !== 0) {
67
+ process.exit(result.status ?? 1);
68
+ }
69
+ }
70
+
71
+ const args = parseArgs(process.argv.slice(2));
72
+ const moduleId = args['module-id'];
73
+ if (!moduleId) throw new Error('Missing --module-id');
74
+ const docs = requirePath('docs', args.docs);
75
+ const codeList = requirePath('code-list', args['code-list']);
76
+ const webapp = requirePath('webapp', args.webapp);
77
+ const javaActions = requirePath('java-actions', args['java-actions']);
78
+ const hints = requirePath('hints', args.hints);
79
+ const contractOut = args['contract-out'] ?? `packages/ep-stage-skill/output/${moduleId}.crud.contract.json`;
80
+ const specOut = args['spec-out'];
81
+ const menu = args.menu;
82
+ if (!specOut) throw new Error('Missing --spec-out');
83
+ if (!menu) throw new Error('Missing --menu');
84
+
85
+ const contractAbs = resolveFromRepo(contractOut);
86
+ const contractForPackage = path.relative(packageRoot, contractAbs);
87
+ mkdirSync(path.dirname(contractAbs), { recursive: true });
88
+
89
+ run([
90
+ '--filter',
91
+ '@epoint/ep-stage-skill',
92
+ 'generate:crud-contract',
93
+ '--',
94
+ '--module-id',
95
+ moduleId,
96
+ '--docs',
97
+ docs,
98
+ '--code-list',
99
+ codeList,
100
+ '--webapp',
101
+ webapp,
102
+ '--java-actions',
103
+ javaActions,
104
+ '--hints',
105
+ hints,
106
+ '--out',
107
+ contractForPackage
108
+ ]);
109
+
110
+ const contract = JSON.parse(readFileSync(contractAbs, 'utf8'));
111
+ if (contract.unresolvedSlots?.length) {
112
+ console.error(JSON.stringify({
113
+ error: 'Contract has unresolvedSlots; update ModuleHints before generating a final spec.',
114
+ unresolvedSlots: contract.unresolvedSlots.map(({ slotId, reason, suggestedFormat }) => ({
115
+ slotId,
116
+ reason,
117
+ suggestedFormat
118
+ }))
119
+ }, null, 2));
120
+ process.exit(2);
121
+ }
122
+
123
+ const specAbs = resolveFromRepo(specOut);
124
+ mkdirSync(path.dirname(specAbs), { recursive: true });
125
+
126
+ run([
127
+ '--filter',
128
+ '@epoint/ep-stage-skill',
129
+ 'generate:playwright-tests',
130
+ '--',
131
+ '--contract',
132
+ contractForPackage,
133
+ '--out',
134
+ specAbs,
135
+ '--menu',
136
+ menu
137
+ ]);
138
+
139
+ console.log(JSON.stringify({
140
+ module: contract.module,
141
+ searchConditions: contract.searchConditions?.map(({ field, label, component, value }) => ({
142
+ field,
143
+ label,
144
+ component,
145
+ value
146
+ })),
147
+ contract: contractAbs,
148
+ spec: specAbs
149
+ }, null, 2));
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: glue-testcase
3
+ description: 使用时机:当需要基于 ep-stage 可观测 trace、场景推理结果和 coverage diff 先生成可人工审阅的胶水测试用例中间产物,再供 glue-test 组装 Playwright spec 时使用。
4
+ ---
5
+
6
+ # ep-stage: glue-testcase
7
+
8
+ 本 skill 只负责测试用例中间产物(`glue-testcase.json` / `.md`),不直接生成或执行 Playwright spec。
9
+
10
+ ## 工作流
11
+
12
+ ```text
13
+ 读取 .stage/trace
14
+ -> 检查 scenario-inference 是否已确认(--scenario)
15
+ -> 读取 coverage-diff
16
+ -> 生成 testcase JSON / Markdown(testcases/<module>.glue-testcase.*)
17
+ -> 展示推理摘要和未确认项
18
+ -> 人工确认后供 glue-test 消费
19
+ ```
20
+
21
+ ## 关键约束
22
+
23
+ - 未确认的 `scenario-inference` 不得生成可执行 spec。
24
+ - `candidate` 和 `unresolved` 必须写入 testcase 的 `reviewStatus` 和 `unresolved`。
25
+ - 嵌套 CRUD 一期只能作为候选用例进入 testcase,不写入正式 CRUD 契约。
26
+ - 对话中展示的是可审阅推理摘要和证据链,不展示原始隐式思维链。
27
+
28
+ ## 按需加载
29
+
30
+ - 用例 schema:`references/testcase-schema.md`
31
+ - 样例产物:`examples/observable-testcase.json`
@@ -0,0 +1,40 @@
1
+ {
2
+ "moduleId": "zwplace",
3
+ "moduleName": "场所窗口信息管理",
4
+ "cases": [
5
+ {
6
+ "caseId": "zwplace-crud-single-page-smoke",
7
+ "title": "标准 CRUD 冒烟用例",
8
+ "scenario": "crud.single-page",
9
+ "reviewStatus": "confirmed",
10
+ "evidence": ["新增、查询、修改、删除动作被当前骨架覆盖"],
11
+ "steps": ["登录系统", "进入菜单", "执行 CRUD 骨架流程"],
12
+ "assertions": ["创建和修改后的记录可查询"],
13
+ "unresolved": []
14
+ },
15
+ {
16
+ "caseId": "zwplace-nested-crud-candidate",
17
+ "title": "嵌套 CRUD 候选用例",
18
+ "scenario": "crud.nested",
19
+ "reviewStatus": "needs_review",
20
+ "evidence": ["新增页面中存在 mini-datagrid 与 添加窗口 按钮"],
21
+ "steps": ["打开新增页面", "定位窗口明细子表", "验证添加窗口候选动作"],
22
+ "assertions": [],
23
+ "unresolved": ["子表保存后断言需要人工确认"]
24
+ }
25
+ ],
26
+ "reasoningSummary": {
27
+ "conclusion": "生成 2 条用例,其中 1 条需要人工确认。",
28
+ "evidenceChain": [
29
+ {
30
+ "source": "html",
31
+ "path": "gxhzwplaceadd.html",
32
+ "text": "mini-datagrid + 添加窗口"
33
+ }
34
+ ],
35
+ "alternatives": ["crud.single-page", "crud.nested"],
36
+ "confidence": "medium",
37
+ "risks": ["子表保存后断言需要人工确认"],
38
+ "needsHumanReview": true
39
+ }
40
+ }
@@ -0,0 +1,67 @@
1
+ # glue-testcase schema
2
+
3
+ `glue-testcase` 的 JSON 顶层结构:
4
+
5
+ ```json
6
+ {
7
+ "moduleId": "zwplace",
8
+ "moduleName": "场所窗口信息管理",
9
+ "cases": [
10
+ {
11
+ "caseId": "zwplace.create",
12
+ "title": "新增场所窗口信息管理",
13
+ "scenario": "crud.single-page",
14
+ "reviewStatus": "confirmed",
15
+ "evidence": ["skeleton=crud.skeleton-testcase/v1", "contract=crud-business-module/v1"],
16
+ "steps": ["进入菜单", "点击新增", "填写并保存测试数据"],
17
+ "assertions": ["新增后可查询到记录"],
18
+ "unresolved": []
19
+ },
20
+ {
21
+ "caseId": "zwplace.read",
22
+ "title": "查询场所窗口信息管理",
23
+ "scenario": "crud.single-page",
24
+ "reviewStatus": "confirmed",
25
+ "evidence": ["skeleton=crud.skeleton-testcase/v1", "contract=crud-business-module/v1"],
26
+ "steps": ["进入菜单", "输入查询字段", "提交查询"],
27
+ "assertions": ["查询结果包含测试记录"],
28
+ "unresolved": []
29
+ },
30
+ {
31
+ "caseId": "zwplace.update",
32
+ "title": "修改场所窗口信息管理",
33
+ "scenario": "crud.single-page",
34
+ "reviewStatus": "confirmed",
35
+ "evidence": ["skeleton=crud.skeleton-testcase/v1", "contract=crud-business-module/v1"],
36
+ "steps": ["定位测试记录", "点击修改", "保存更新值"],
37
+ "assertions": ["修改后可查询到更新记录"],
38
+ "unresolved": []
39
+ },
40
+ {
41
+ "caseId": "zwplace.delete",
42
+ "title": "删除场所窗口信息管理",
43
+ "scenario": "crud.single-page",
44
+ "reviewStatus": "confirmed",
45
+ "evidence": ["skeleton=crud.skeleton-testcase/v1", "contract=crud-business-module/v1"],
46
+ "steps": ["定位测试记录", "点击删除", "确认删除策略"],
47
+ "assertions": ["删除结果符合契约断言策略"],
48
+ "unresolved": []
49
+ }
50
+ ],
51
+ "reasoningSummary": {
52
+ "conclusion": "生成 4 条 CRUD 胶水测试用例。",
53
+ "evidenceChain": [],
54
+ "alternatives": ["crud.single-page"],
55
+ "confidence": "high",
56
+ "risks": [],
57
+ "needsHumanReview": false
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## 字段说明
63
+
64
+ - `reviewStatus`:`confirmed` / `needs_review` / `blocked`。`needs_review` 的用例不得进入 spec assembly。
65
+ - `caseId`:spec assembly 需要与生成脚本工作流粒度一致,CRUD 用例固定为 `<moduleId>.create`、`<moduleId>.read`、`<moduleId>.update`、`<moduleId>.delete`。
66
+ - `unresolved`:未确认项(如嵌套 CRUD 子表保存后断言)。必须落盘,不能只在对话解释。
67
+ - `reasoningSummary`:与 `.stage/trace/<run-id>/testcase-generation.json` 同构。
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: recording-to-glue
3
+ description: 使用时机:当用户讨论或规划「将录制产出的 Playwright 脚本转换为 ep-stage 胶水骨架(glue skeleton)或组件(component)候选代码」时使用。
4
+ ---
5
+
6
+ # ep-stage: recording-to-glue
7
+
8
+ 本 skill 是一个规划中的能力入口。
9
+
10
+ ## 当前状态
11
+
12
+ 转换工作流在本期尚未实现。
13
+
14
+ ## 预期输入(未来)
15
+
16
+ - 来自 `stage-recorder-ext` 或其他录制器的原始 Playwright 脚本。
17
+ - 标识页面、iframe(frame)、选择器(selector)以及用户动作意图的录制元数据。
18
+
19
+ ## 预期产出(未来)
20
+
21
+ - 候选胶水骨架代码(glue skeleton code)。
22
+ - 候选组件动作(component actions)。
23
+ - 关于代码应归入骨架、组件、测试 spec 还是 `ModuleHints` 的评审意见。
24
+
25
+ ## 当前行为
26
+
27
+ 当用户调用本 skill 时,应说明实现尚未开始,并提议为该转换工作流创建设计或实施计划,而不是在本期从录制产物生成生产级胶水代码。
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { lstatSync, readFileSync, realpathSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const epStageRoot = path.resolve(__dirname, '..');
8
+ const packageRoot = path.resolve(epStageRoot, '../..');
9
+ const repoRoot = path.resolve(packageRoot, '../..');
10
+
11
+ function readFrontmatter(skillRoot) {
12
+ const skillPath = path.join(skillRoot, 'SKILL.md');
13
+ const text = readFileSync(skillPath, 'utf8');
14
+ const match = text.match(/^---\n([\s\S]*?)\n---\n/);
15
+ if (!match) throw new Error(`${skillPath} missing YAML frontmatter`);
16
+
17
+ const frontmatter = match[1];
18
+ return {
19
+ name: frontmatter.match(/^name:\s*(.+)$/m)?.[1]?.trim(),
20
+ description: frontmatter.match(/^description:\s*(.+)$/m)?.[1]?.trim(),
21
+ skillPath,
22
+ };
23
+ }
24
+
25
+ function assertSkill(skillRoot, expectedName) {
26
+ const { name, description, skillPath } = readFrontmatter(skillRoot);
27
+ if (name !== expectedName) throw new Error(`Expected name ${expectedName} in ${skillPath}, got ${name ?? 'missing'}`);
28
+ if (!description?.startsWith('使用时机') && !description?.startsWith('Use when')) throw new Error(`${skillPath} description 必须以「使用时机」或「Use when」开头`);
29
+ if (description.length > 1024) throw new Error(`${skillPath} description exceeds 1024 characters`);
30
+ if (path.basename(skillRoot) !== name) throw new Error(`${skillPath} directory basename must match name`);
31
+ return { name, descriptionLength: description.length, skillRoot };
32
+ }
33
+
34
+ function assertSymlink(linkPath, targetPath) {
35
+ const linkStat = lstatSync(linkPath);
36
+ if (!linkStat.isSymbolicLink()) throw new Error(`${linkPath} must be a symlink`);
37
+ if (realpathSync(linkPath) !== realpathSync(targetPath)) {
38
+ throw new Error(`${linkPath} does not point at ${targetPath}`);
39
+ }
40
+ }
41
+
42
+ // ep-stage scope 下四个 skill 的包内真相源目录。
43
+ const skillSources = [
44
+ { name: 'create-project', source: path.join(epStageRoot, 'create-project') },
45
+ { name: 'glue-test', source: path.join(epStageRoot, 'glue-test') },
46
+ { name: 'glue-testcase', source: path.join(epStageRoot, 'glue-testcase') },
47
+ { name: 'recording-to-glue', source: path.join(epStageRoot, 'recording-to-glue') },
48
+ ];
49
+
50
+ // 校验每个源 skill 的 SKILL.md frontmatter。
51
+ const checks = skillSources.map(({ name, source }) => assertSkill(source, name));
52
+
53
+ // 发现目录:Claude Code 扫 .claude/skills/,Codex 扫 .agents/skills/。
54
+ // 两个工具都只扫直接子目录,每个子目录是一个 skill;用 symlink 指向包内源。
55
+ const discoveryDirs = [
56
+ path.join(repoRoot, '.claude/skills'),
57
+ path.join(repoRoot, '.agents/skills'),
58
+ ];
59
+
60
+ const symlinks = [];
61
+ for (const dir of discoveryDirs) {
62
+ for (const { name, source } of skillSources) {
63
+ assertSymlink(path.join(dir, name), source);
64
+ symlinks.push({ dir: path.relative(repoRoot, dir), name, pointsTo: path.relative(repoRoot, source) });
65
+ }
66
+ }
67
+
68
+ console.log(JSON.stringify({
69
+ ok: true,
70
+ packageRoot,
71
+ checks,
72
+ symlinks,
73
+ }, null, 2));
@@ -0,0 +1,34 @@
1
+ import type { ActionCandidate, AgentInferredWorkflow, SkeletonCoverage } from '../contracts/gap-inference.js';
2
+ import type { CoverageDiffTrace } from '../contracts/observable-chain.js';
3
+ import type { PageStructureSummary } from './page-structure.js';
4
+ /**
5
+ * createCoverageDiff 的入参。
6
+ *
7
+ * @property moduleId - 当前模块 id。
8
+ * @property actionCandidates - 动作候选列表(来自确定性 extractor)。
9
+ * @property skeletonCoverage - 当前骨架能力模型。
10
+ * @property baselineWorkflows - 现有 gap planner 的基线 workflow。
11
+ * @property pageStructures - 页面结构摘要列表。
12
+ */
13
+ export type CreateCoverageDiffInput = {
14
+ moduleId: string;
15
+ actionCandidates: ActionCandidate[];
16
+ skeletonCoverage: SkeletonCoverage;
17
+ baselineWorkflows: AgentInferredWorkflow[];
18
+ pageStructures: PageStructureSummary[];
19
+ };
20
+ /**
21
+ * 把动作候选、骨架覆盖、基线 workflow 与页面结构组合成 coverage diff trace。
22
+ *
23
+ * - covered:动作标签已被骨架覆盖。
24
+ * - gapCandidates:动作不在骨架覆盖标签中,需证据或人工确认。
25
+ * - nestedCrudCandidates:页面结构标记的嵌套 CRUD 候选。
26
+ * - comparison:基线 workflow 与泛化 gap 候选的集合差异。
27
+ *
28
+ * 不删除现有 gap planner 基线,新旧 diff 并行输出。
29
+ *
30
+ * @param input - coverage diff 入参。
31
+ * @returns coverage diff trace。
32
+ */
33
+ export declare function createCoverageDiff(input: CreateCoverageDiffInput): CoverageDiffTrace;
34
+ //# sourceMappingURL=coverage-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-diff.d.ts","sourceRoot":"","sources":["../../../src/capability/coverage-diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAC9G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE;;;;;;;;GAQG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,iBAAiB,EAAE,qBAAqB,EAAE,CAAC;IAC3C,cAAc,EAAE,oBAAoB,EAAE,CAAC;CACxC,CAAC;AAYF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,iBAAiB,CAuEpF"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * 汇总骨架覆盖的所有动作标签为集合。
3
+ *
4
+ * @param coverage - 骨架能力模型。
5
+ * @returns 所有 coveredActionLabels 的扁平集合。
6
+ */
7
+ function allCoveredLabels(coverage) {
8
+ return new Set(Object.values(coverage.coveredActionLabels).flatMap((labels) => labels ?? []));
9
+ }
10
+ /**
11
+ * 把动作候选、骨架覆盖、基线 workflow 与页面结构组合成 coverage diff trace。
12
+ *
13
+ * - covered:动作标签已被骨架覆盖。
14
+ * - gapCandidates:动作不在骨架覆盖标签中,需证据或人工确认。
15
+ * - nestedCrudCandidates:页面结构标记的嵌套 CRUD 候选。
16
+ * - comparison:基线 workflow 与泛化 gap 候选的集合差异。
17
+ *
18
+ * 不删除现有 gap planner 基线,新旧 diff 并行输出。
19
+ *
20
+ * @param input - coverage diff 入参。
21
+ * @returns coverage diff trace。
22
+ */
23
+ export function createCoverageDiff(input) {
24
+ const coveredLabels = allCoveredLabels(input.skeletonCoverage);
25
+ const covered = input.actionCandidates
26
+ .filter((candidate) => coveredLabels.has(candidate.label))
27
+ .map((candidate) => ({
28
+ actionId: candidate.actionId,
29
+ label: candidate.label,
30
+ pageId: candidate.pageId,
31
+ status: 'covered',
32
+ reason: '动作标签已被当前骨架能力覆盖。',
33
+ }));
34
+ const gapCandidates = input.actionCandidates
35
+ .filter((candidate) => !coveredLabels.has(candidate.label))
36
+ .map((candidate) => ({
37
+ actionId: candidate.actionId,
38
+ label: candidate.label,
39
+ pageId: candidate.pageId,
40
+ status: 'gap-candidate',
41
+ reason: '动作不在当前骨架覆盖标签中,需要证据或人工确认。',
42
+ }));
43
+ const nestedCrudCandidates = input.pageStructures
44
+ .filter((page) => page.nestedCrudCandidate)
45
+ .map((page) => ({
46
+ pageId: page.pageId,
47
+ evidence: page.evidenceLabels,
48
+ supportedActions: [
49
+ ...(page.createCandidate ? ['create'] : []),
50
+ ...(page.deleteCandidate ? ['delete'] : []),
51
+ ...(page.updateOrDetailCandidate ? ['update-or-detail'] : []),
52
+ ],
53
+ unresolvedReason: page.risks[0] ?? '嵌套 CRUD 操作边界需要人工确认',
54
+ }));
55
+ const baseline = {
56
+ plannedWorkflows: input.baselineWorkflows
57
+ .filter((item) => item.status === 'planned')
58
+ .map((item) => item.workflowId),
59
+ candidateWorkflows: input.baselineWorkflows
60
+ .filter((item) => item.status === 'candidate')
61
+ .map((item) => item.workflowId),
62
+ unresolvedWorkflows: input.baselineWorkflows
63
+ .filter((item) => item.status === 'unresolved')
64
+ .map((item) => item.workflowId),
65
+ };
66
+ const generalizedIds = gapCandidates.map((item) => item.actionId);
67
+ const baselineIds = input.baselineWorkflows.map((item) => item.workflowId);
68
+ return {
69
+ moduleId: input.moduleId,
70
+ skeletonCoverageId: input.skeletonCoverage.coverageId,
71
+ baseline,
72
+ covered,
73
+ partiallyCovered: [],
74
+ gapCandidates,
75
+ unresolved: [],
76
+ nestedCrudCandidates,
77
+ comparison: {
78
+ both: baselineIds.filter((id) => generalizedIds.includes(id)),
79
+ onlyBaseline: baselineIds.filter((id) => !generalizedIds.includes(id)),
80
+ onlyGeneralized: generalizedIds.filter((id) => !baselineIds.includes(id)),
81
+ },
82
+ reasoningSummary: {
83
+ conclusion: `识别 covered=${covered.length},gap-candidate=${gapCandidates.length},nested-crud=${nestedCrudCandidates.length};基线 workflow=${baselineIds.length}。`,
84
+ evidenceChain: nestedCrudCandidates.flatMap((candidate) => candidate.evidence.map((text) => ({ source: 'html', path: candidate.pageId, text }))),
85
+ alternatives: ['当前 gap planner 基线', '泛化 coverage diff'],
86
+ confidence: 'medium',
87
+ risks: nestedCrudCandidates.map((candidate) => candidate.unresolvedReason),
88
+ needsHumanReview: gapCandidates.length > 0 || nestedCrudCandidates.length > 0,
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,31 @@
1
+ import type { ExtractedHtmlPage } from '../extractors/html-page.js';
2
+ /**
3
+ * 页面结构摘要:把静态结构信号归纳为 CRUD 场景候选的布尔判断与可审阅证据标签。
4
+ *
5
+ * 结构信号只提升候选置信度,不直接生成测试(遵守 dev-rules:通用优先,业务后移)。
6
+ */
7
+ export type PageStructureSummary = {
8
+ pageId: string;
9
+ readSearchCandidate: boolean;
10
+ createCandidate: boolean;
11
+ deleteCandidate: boolean;
12
+ updateOrDetailCandidate: boolean;
13
+ nestedCrudCandidate: boolean;
14
+ evidenceLabels: string[];
15
+ risks: string[];
16
+ };
17
+ /**
18
+ * 把 ExtractedHtmlPage 的 pageStructureSignals 归纳为场景候选摘要。
19
+ *
20
+ * 判定规则:
21
+ * - readSearchCandidate:存在 datagrid 且(condition 或 pager)。
22
+ * - createCandidate:工具栏按钮含 新增/添加。
23
+ * - deleteCandidate:存在 checkcolumn 选择列且工具栏含 删除。
24
+ * - updateOrDetailCandidate:存在 actioncolumn 行操作。
25
+ * - nestedCrudCandidate:datagrid + (create/delete) + 页面 id 以 add/edit 结尾或存在弹窗入口。
26
+ *
27
+ * @param page - 已抽取的 HTML 页面结构。
28
+ * @returns 场景候选摘要,含证据标签与风险。
29
+ */
30
+ export declare function summarizePageStructure(page: ExtractedHtmlPage): PageStructureSummary;
31
+ //# sourceMappingURL=page-structure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-structure.d.ts","sourceRoot":"","sources":["../../../src/capability/page-structure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,EAAE,OAAO,CAAC;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,uBAAuB,EAAE,OAAO,CAAC;IACjC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAaF;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,GAAG,oBAAoB,CA8BpF"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 判断 values 中是否存在任一包含 keywords 中关键词的项。
3
+ *
4
+ * @param values - 待检文本列表(如工具栏按钮标签)。
5
+ * @param keywords - 触发关键词列表。
6
+ * @returns 是否命中任一关键词。
7
+ */
8
+ function includesAny(values, keywords) {
9
+ return values.some((value) => keywords.some((keyword) => value.includes(keyword)));
10
+ }
11
+ /**
12
+ * 把 ExtractedHtmlPage 的 pageStructureSignals 归纳为场景候选摘要。
13
+ *
14
+ * 判定规则:
15
+ * - readSearchCandidate:存在 datagrid 且(condition 或 pager)。
16
+ * - createCandidate:工具栏按钮含 新增/添加。
17
+ * - deleteCandidate:存在 checkcolumn 选择列且工具栏含 删除。
18
+ * - updateOrDetailCandidate:存在 actioncolumn 行操作。
19
+ * - nestedCrudCandidate:datagrid + (create/delete) + 页面 id 以 add/edit 结尾或存在弹窗入口。
20
+ *
21
+ * @param page - 已抽取的 HTML 页面结构。
22
+ * @returns 场景候选摘要,含证据标签与风险。
23
+ */
24
+ export function summarizePageStructure(page) {
25
+ const signals = page.pageStructureSignals;
26
+ const evidenceLabels = [
27
+ ...(signals.dataGrid.staticPresent ? ['可见 mini-datagrid 结构存在'] : []),
28
+ ...(signals.searchCondition.staticPresent ? ['可见 fui-condition 结构存在'] : []),
29
+ ...(signals.pager.staticPresent ? ['可见 mini-grid-pager 结构存在'] : []),
30
+ ...(signals.hasSelectionColumn ? ['表格存在 checkcolumn 选择列'] : []),
31
+ ...(signals.hasActionColumn ? ['表格存在 actioncolumn 行操作'] : []),
32
+ ];
33
+ const createCandidate = includesAny(signals.toolbarButtonLabels, ['新增', '添加']);
34
+ const deleteCandidate = signals.hasSelectionColumn && includesAny(signals.toolbarButtonLabels, ['删除']);
35
+ const updateOrDetailCandidate = signals.hasActionColumn;
36
+ const readSearchCandidate = signals.dataGrid.staticPresent && (signals.searchCondition.staticPresent || signals.pager.staticPresent);
37
+ const nestedCrudCandidate = signals.dataGrid.staticPresent &&
38
+ (createCandidate || deleteCandidate) &&
39
+ (page.pageId.endsWith('add') || page.pageId.endsWith('edit') || signals.dialogTargets.length > 0);
40
+ return {
41
+ pageId: page.pageId,
42
+ readSearchCandidate,
43
+ createCandidate,
44
+ deleteCandidate,
45
+ updateOrDetailCandidate,
46
+ nestedCrudCandidate,
47
+ evidenceLabels,
48
+ risks: nestedCrudCandidate ? ['嵌套 CRUD 断言和保存边界需要人工确认'] : [],
49
+ };
50
+ }
@@ -0,0 +1,36 @@
1
+ import type { ScenarioInferenceTrace } from '../contracts/observable-chain.js';
2
+ import type { PageStructureSummary } from './page-structure.js';
3
+ /**
4
+ * inferPageScenarios 的入参。
5
+ *
6
+ * @property moduleId - 当前模块 id。
7
+ * @property pages - 参与推理的页面结构摘要列表。
8
+ * @property skeletonCapabilities - 当前骨架已覆盖的能力(如 crud.create)。
9
+ */
10
+ export type InferPageScenariosInput = {
11
+ moduleId: string;
12
+ pages: PageStructureSummary[];
13
+ skeletonCapabilities: string[];
14
+ };
15
+ /**
16
+ * 根据页面结构候选生成场景推理结果。
17
+ *
18
+ * 候选优先级:嵌套 CRUD > 单页 CRUD > unknown。嵌套 CRUD 存在时单页 CRUD 降为中置信。
19
+ * 场景推理 gate 恒为 needs_review,需人工 confirm / correct / add-evidence / skip。
20
+ *
21
+ * @param input - 模块 id、页面结构摘要、骨架能力列表。
22
+ * @returns 场景推理 trace,含候选列表、gate 与推理摘要。
23
+ */
24
+ export declare function inferPageScenarios(input: InferPageScenariosInput): ScenarioInferenceTrace;
25
+ /**
26
+ * 记录人工对场景推理的纠偏,标记需要重新推理。
27
+ *
28
+ * @param input.inference - 原始场景推理 trace。
29
+ * @param input.correction - 人工纠偏文本。
30
+ * @returns 更新后的 trace,gate 为 corrected,推理摘要追加人工证据。
31
+ */
32
+ export declare function applyScenarioHumanCorrection(input: {
33
+ inference: ScenarioInferenceTrace;
34
+ correction: string;
35
+ }): ScenarioInferenceTrace;
36
+ //# sourceMappingURL=scenario-inference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenario-inference.d.ts","sourceRoot":"","sources":["../../../src/capability/scenario-inference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,sBAAsB,EACvB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AA2CF;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,sBAAsB,CAgDzF;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,SAAS,EAAE,sBAAsB,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,sBAAsB,CAsBzB"}