@epoint-testtech/ep-stage-skill 0.0.4-alpha.0 → 0.0.5

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 (206) hide show
  1. package/SKILL.md +27 -27
  2. package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -186
  3. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -199
  4. package/codex-skill/ep-stage/glue-generate-testcase/examples/observable-testcase.json +40 -40
  5. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +95 -112
  6. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +72 -0
  7. package/codex-skill/ep-stage/glue-run-test/SKILL.md +292 -249
  8. package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -0
  9. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -145
  10. package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -43
  11. package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
  12. package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +166 -0
  13. package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +233 -0
  14. package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
  15. package/codex-skill/ep-stage/page-element-scan/SKILL.md +91 -0
  16. package/codex-skill/ep-stage/page-element-scan/evals/evals.json +37 -0
  17. package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +85 -0
  18. package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +153 -0
  19. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +233 -0
  20. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +298 -0
  21. package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +749 -0
  22. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
  23. package/codex-skill/ep-stage/scripts/validate-skill.mjs +98 -95
  24. package/dist/src/cli/generate-testcase.d.ts +18 -51
  25. package/dist/src/cli/generate-testcase.d.ts.map +1 -1
  26. package/dist/src/cli/generate-testcase.js +127 -131
  27. package/dist/src/cli/index.d.ts +11 -2
  28. package/dist/src/cli/index.d.ts.map +1 -1
  29. package/dist/src/cli/index.js +49 -16
  30. package/dist/src/cli/probe.d.ts +2 -0
  31. package/dist/src/cli/probe.d.ts.map +1 -1
  32. package/dist/src/cli/probe.js +239 -13
  33. package/dist/src/cli/run.d.ts +136 -29
  34. package/dist/src/cli/run.d.ts.map +1 -1
  35. package/dist/src/cli/run.js +494 -65
  36. package/dist/src/cli/spec.d.ts +18 -11
  37. package/dist/src/cli/spec.d.ts.map +1 -1
  38. package/dist/src/cli/spec.js +74 -20
  39. package/dist/src/contracts/crud-business-module.d.ts +6 -0
  40. package/dist/src/contracts/crud-business-module.d.ts.map +1 -1
  41. package/dist/src/contracts/glue-contract-v1.d.ts +98 -0
  42. package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -0
  43. package/dist/src/contracts/glue-contract-v1.js +9 -0
  44. package/dist/src/contracts/inference-trace.d.ts +223 -0
  45. package/dist/src/contracts/inference-trace.d.ts.map +1 -0
  46. package/dist/src/contracts/inference-trace.js +12 -0
  47. package/dist/src/contracts/observable-chain.d.ts +4 -0
  48. package/dist/src/contracts/observable-chain.d.ts.map +1 -1
  49. package/dist/src/extractors/code-list-json5.d.ts +192 -0
  50. package/dist/src/extractors/code-list-json5.d.ts.map +1 -0
  51. package/dist/src/extractors/code-list-json5.js +91 -0
  52. package/dist/src/extractors/code-list.d.ts +3 -3
  53. package/dist/src/extractors/code-list.d.ts.map +1 -1
  54. package/dist/src/extractors/code-list.js +46 -11
  55. package/dist/src/extractors/html-page.d.ts +9 -0
  56. package/dist/src/extractors/html-page.d.ts.map +1 -1
  57. package/dist/src/extractors/html-page.js +26 -2
  58. package/dist/src/extractors/prd-extractor.d.ts +70 -0
  59. package/dist/src/extractors/prd-extractor.d.ts.map +1 -0
  60. package/dist/src/extractors/prd-extractor.js +248 -0
  61. package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
  62. package/dist/src/generators/stage-skeleton-script.d.ts +87 -20
  63. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  64. package/dist/src/generators/stage-skeleton-script.js +656 -238
  65. package/dist/src/index.d.ts +7 -2
  66. package/dist/src/index.d.ts.map +1 -1
  67. package/dist/src/index.js +3 -1
  68. package/dist/src/inference/module-hints-inference.d.ts +30 -0
  69. package/dist/src/inference/module-hints-inference.d.ts.map +1 -0
  70. package/dist/src/inference/module-hints-inference.js +363 -0
  71. package/dist/src/inference/source-analysis.d.ts +26 -0
  72. package/dist/src/inference/source-analysis.d.ts.map +1 -0
  73. package/dist/src/inference/source-analysis.js +258 -0
  74. package/dist/src/normalizers/crud-contract.d.ts +3 -0
  75. package/dist/src/normalizers/crud-contract.d.ts.map +1 -1
  76. package/dist/src/normalizers/crud-contract.js +3 -0
  77. package/dist/src/runtime/ai-runtime-trace.d.ts +88 -0
  78. package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -0
  79. package/dist/src/runtime/ai-runtime-trace.js +119 -0
  80. package/dist/src/testcase/button-interaction-inference.d.ts +37 -0
  81. package/dist/src/testcase/button-interaction-inference.d.ts.map +1 -0
  82. package/dist/src/testcase/button-interaction-inference.js +385 -0
  83. package/dist/src/testcase/contract-testcase-assembler.d.ts +44 -0
  84. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -0
  85. package/dist/src/testcase/contract-testcase-assembler.js +806 -0
  86. package/dist/src/testcase/prd-button-mapper.d.ts +72 -0
  87. package/dist/src/testcase/prd-button-mapper.d.ts.map +1 -0
  88. package/dist/src/testcase/prd-button-mapper.js +148 -0
  89. package/dist/src/testcase/runtime-context.d.ts +9 -0
  90. package/dist/src/testcase/runtime-context.d.ts.map +1 -0
  91. package/dist/src/testcase/runtime-context.js +1 -0
  92. package/dist/src/testcase/test-access.d.ts +38 -0
  93. package/dist/src/testcase/test-access.d.ts.map +1 -0
  94. package/dist/src/testcase/test-access.js +106 -0
  95. package/dist/src/testcase/testcase-execution-fields.d.ts +10 -0
  96. package/dist/src/testcase/testcase-execution-fields.d.ts.map +1 -0
  97. package/dist/src/testcase/testcase-execution-fields.js +1 -0
  98. package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
  99. package/dist/src/testcase/testcase-generator.js +6 -0
  100. package/dist/src/testcase/testcase-markdown.d.ts +11 -0
  101. package/dist/src/testcase/testcase-markdown.d.ts.map +1 -0
  102. package/dist/src/testcase/testcase-markdown.js +181 -0
  103. package/dist/src/testcase/testcase-skeleton.d.ts +4 -0
  104. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -1
  105. package/dist/src/testcase/testcase-spec-assembly.d.ts +4 -0
  106. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -1
  107. package/dist/src/testcase/testcase-v1-draft.d.ts +58 -0
  108. package/dist/src/testcase/testcase-v1-draft.d.ts.map +1 -0
  109. package/dist/src/testcase/testcase-v1-draft.js +13 -0
  110. package/dist/src/validation/contract.d.ts +14 -0
  111. package/dist/src/validation/contract.d.ts.map +1 -0
  112. package/dist/src/validation/contract.js +132 -0
  113. package/dist/src/validation/credentials.d.ts +7 -2
  114. package/dist/src/validation/credentials.d.ts.map +1 -1
  115. package/dist/src/validation/credentials.js +15 -4
  116. package/dist/src/validation/index.d.ts +2 -0
  117. package/dist/src/validation/index.d.ts.map +1 -1
  118. package/dist/src/validation/index.js +2 -0
  119. package/dist/src/validation/inference-trace.d.ts +13 -0
  120. package/dist/src/validation/inference-trace.d.ts.map +1 -0
  121. package/dist/src/validation/inference-trace.js +31 -0
  122. package/dist/src/validation/testcase.d.ts +9 -7
  123. package/dist/src/validation/testcase.d.ts.map +1 -1
  124. package/dist/src/validation/testcase.js +181 -34
  125. package/docs/README.md +124 -134
  126. package/docs/mvp-usage-guide.md +298 -298
  127. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
  128. package/examples/schemeresource.module-hints.json +38 -38
  129. package/examples/schemeresource.observable.code_list.md +37 -37
  130. package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
  131. package/examples/zwplace-placecategory-validation.json +29 -29
  132. package/examples/zwplace.module-hints.json +69 -69
  133. package/examples/zwplace.observable.code_list.md +37 -37
  134. package/package.json +2 -2
  135. package/dist/test/cli/extract-contract.test.d.ts +0 -2
  136. package/dist/test/cli/extract-contract.test.d.ts.map +0 -1
  137. package/dist/test/cli/extract-contract.test.js +0 -32
  138. package/dist/test/cli/generate-testcase.test.d.ts +0 -2
  139. package/dist/test/cli/generate-testcase.test.d.ts.map +0 -1
  140. package/dist/test/cli/generate-testcase.test.js +0 -130
  141. package/dist/test/cli/index.test.d.ts +0 -2
  142. package/dist/test/cli/index.test.d.ts.map +0 -1
  143. package/dist/test/cli/index.test.js +0 -93
  144. package/dist/test/cli/run.test.d.ts +0 -2
  145. package/dist/test/cli/run.test.d.ts.map +0 -1
  146. package/dist/test/cli/run.test.js +0 -149
  147. package/dist/test/cli/spec.test.d.ts +0 -2
  148. package/dist/test/cli/spec.test.d.ts.map +0 -1
  149. package/dist/test/cli/spec.test.js +0 -196
  150. package/dist/test/crud-contract.test.d.ts +0 -2
  151. package/dist/test/crud-contract.test.d.ts.map +0 -1
  152. package/dist/test/crud-contract.test.js +0 -819
  153. package/dist/test/gap-inference.test.d.ts +0 -2
  154. package/dist/test/gap-inference.test.d.ts.map +0 -1
  155. package/dist/test/gap-inference.test.js +0 -597
  156. package/dist/test/generalization.test.d.ts +0 -2
  157. package/dist/test/generalization.test.d.ts.map +0 -1
  158. package/dist/test/generalization.test.js +0 -73
  159. package/dist/test/material-inventory.test.d.ts +0 -2
  160. package/dist/test/material-inventory.test.d.ts.map +0 -1
  161. package/dist/test/material-inventory.test.js +0 -141
  162. package/dist/test/observable-chain.test.d.ts +0 -2
  163. package/dist/test/observable-chain.test.d.ts.map +0 -1
  164. package/dist/test/observable-chain.test.js +0 -123
  165. package/dist/test/observable-pipeline.test.d.ts +0 -2
  166. package/dist/test/observable-pipeline.test.d.ts.map +0 -1
  167. package/dist/test/observable-pipeline.test.js +0 -461
  168. package/dist/test/page-structure.test.d.ts +0 -2
  169. package/dist/test/page-structure.test.d.ts.map +0 -1
  170. package/dist/test/page-structure.test.js +0 -45
  171. package/dist/test/scenario-inference.test.d.ts +0 -2
  172. package/dist/test/scenario-inference.test.d.ts.map +0 -1
  173. package/dist/test/scenario-inference.test.js +0 -73
  174. package/dist/test/stage-context.test.d.ts +0 -2
  175. package/dist/test/stage-context.test.d.ts.map +0 -1
  176. package/dist/test/stage-context.test.js +0 -395
  177. package/dist/test/testcase-generator.test.d.ts +0 -2
  178. package/dist/test/testcase-generator.test.d.ts.map +0 -1
  179. package/dist/test/testcase-generator.test.js +0 -276
  180. package/dist/test/testcase-skeleton.test.d.ts +0 -2
  181. package/dist/test/testcase-skeleton.test.d.ts.map +0 -1
  182. package/dist/test/testcase-skeleton.test.js +0 -185
  183. package/dist/test/testcase-spec-assembly.test.d.ts +0 -2
  184. package/dist/test/testcase-spec-assembly.test.d.ts.map +0 -1
  185. package/dist/test/testcase-spec-assembly.test.js +0 -105
  186. package/dist/test/util/credentials.test.d.ts +0 -2
  187. package/dist/test/util/credentials.test.d.ts.map +0 -1
  188. package/dist/test/util/credentials.test.js +0 -64
  189. package/dist/test/util/i18n-testcase.test.d.ts +0 -2
  190. package/dist/test/util/i18n-testcase.test.d.ts.map +0 -1
  191. package/dist/test/util/i18n-testcase.test.js +0 -119
  192. package/dist/test/util/softlink.test.d.ts +0 -2
  193. package/dist/test/util/softlink.test.d.ts.map +0 -1
  194. package/dist/test/util/softlink.test.js +0 -82
  195. package/dist/test/validation/credentials.test.d.ts +0 -2
  196. package/dist/test/validation/credentials.test.d.ts.map +0 -1
  197. package/dist/test/validation/credentials.test.js +0 -72
  198. package/dist/test/validation/projects-index.test.d.ts +0 -2
  199. package/dist/test/validation/projects-index.test.d.ts.map +0 -1
  200. package/dist/test/validation/projects-index.test.js +0 -48
  201. package/dist/test/validation/testcase.test.d.ts +0 -2
  202. package/dist/test/validation/testcase.test.d.ts.map +0 -1
  203. package/dist/test/validation/testcase.test.js +0 -129
  204. package/dist/vitest.config.d.ts +0 -3
  205. package/dist/vitest.config.d.ts.map +0 -1
  206. package/dist/vitest.config.js +0 -7
@@ -1,395 +0,0 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import JSON5 from 'json5';
5
- import { describe, expect, it } from 'vitest';
6
- import { lookupProjectByRequirement, readStageContext, resolveStageContext, writeProjectIndex, } from '../src/index.js';
7
- /**
8
- * 创建隔离的临时目录,避免测试读写真实用户 HOME 或项目文件。
9
- *
10
- * @param prefix - 临时目录前缀。
11
- * @returns 生成后的临时目录绝对路径。
12
- */
13
- function createTempDir(prefix) {
14
- return path.join(os.tmpdir(), `${prefix}-${crypto.randomUUID()}`);
15
- }
16
- describe('stage context resolution', () => {
17
- it('parses explicit stage-context.md and normalizes relative env path', () => {
18
- const projectDir = createTempDir('ep-stage-context');
19
- mkdirSync(projectDir, { recursive: true });
20
- const knowledgeRoot = path.join(projectDir, '..', 'knowledge-project');
21
- const stageContextPath = path.join(projectDir, 'stage-context.md');
22
- writeFileSync(stageContextPath, [
23
- '---',
24
- 'projectName: demo-project',
25
- 'projectDir: .',
26
- 'mode: glue',
27
- 'envPath: .env',
28
- 'knowledgeRoot: ../knowledge-project',
29
- 'codeListPaths:',
30
- ' - ../knowledge-project/_docs/code_list.md',
31
- '---',
32
- '',
33
- '# demo-project',
34
- ].join('\n'));
35
- const context = readStageContext(stageContextPath);
36
- expect(context).toMatchObject({
37
- projectName: 'demo-project',
38
- projectDir,
39
- mode: 'glue',
40
- envPath: '.env',
41
- envFilePath: path.join(projectDir, '.env'),
42
- knowledgeRoot: path.resolve(knowledgeRoot),
43
- stageContextPath,
44
- codeListPaths: [path.resolve(projectDir, '../knowledge-project/_docs/code_list.md')],
45
- });
46
- });
47
- it('resolves current project from legacy user index by code list path when local context is absent', () => {
48
- // 旧链路:legacy projects.index.json5 含 envPath/knowledgeRoot/codeListPaths,仍需可读(REQ-DATA-01 graceful read)。
49
- const homeDir = createTempDir('ep-stage-home');
50
- const projectDir = createTempDir('ep-stage-project');
51
- const cwd = createTempDir('ep-stage-outside-cwd');
52
- const knowledgeRoot = createTempDir('ep-stage-knowledge');
53
- const codeListPath = path.join(knowledgeRoot, '_docs', 'code_list.md');
54
- mkdirSync(cwd, { recursive: true });
55
- mkdirSync(projectDir, { recursive: true });
56
- mkdirSync(path.dirname(codeListPath), { recursive: true });
57
- writeFileSync(path.join(projectDir, '.env'), 'LOGIN_SYSTEM_URL=http://example.test/login\n');
58
- writeFileSync(codeListPath, '# code list\n');
59
- // 直接写 legacy schema(含 envPath/knowledgeRoot/codeListPaths),模拟旧版数据残留。
60
- const indexDir = path.join(homeDir, '.ep-stage');
61
- mkdirSync(indexDir, { recursive: true });
62
- writeFileSync(path.join(indexDir, 'projects.index.json5'), JSON5.stringify({
63
- projects: [
64
- {
65
- projectName: 'indexed-project',
66
- projectDir,
67
- mode: 'glue',
68
- stageCreateVersion: '0.0.4-alpha.0',
69
- requirements: [],
70
- // legacy 字段
71
- envPath: '.env',
72
- knowledgeRoot,
73
- codeListPaths: [codeListPath],
74
- },
75
- ],
76
- systems: [],
77
- }, null, 2));
78
- const result = resolveStageContext({
79
- cwd,
80
- codeListPath,
81
- homeDir,
82
- });
83
- expect(result.context).toMatchObject({
84
- projectName: 'indexed-project',
85
- projectDir,
86
- mode: 'glue',
87
- envFilePath: path.join(projectDir, '.env'),
88
- knowledgeRoot,
89
- codeListPaths: [codeListPath],
90
- });
91
- expect(result.trace).toMatchObject({
92
- stage: 'context-resolution',
93
- stageLabel: '项目上下文解析',
94
- gate: { status: 'confirmed' },
95
- reasoningSummary: {
96
- conclusion: expect.stringContaining('projects.index.json5'),
97
- needsHumanReview: false,
98
- },
99
- });
100
- });
101
- it('uses stage-context.md as source of truth when legacy user index has stageContextPath', () => {
102
- // 旧链路:legacy projects.index.json5 含 stageContextPath 时,回到 stage-context.md 作为真相源(graceful read)。
103
- const homeDir = createTempDir('ep-stage-home-source');
104
- const projectDir = createTempDir('ep-stage-source-project');
105
- const oldKnowledgeRoot = path.join(projectDir, 'old knowledge');
106
- const newKnowledgeRoot = path.join(projectDir, 'new knowledge');
107
- const stageContextPath = path.join(projectDir, 'stage-context.md');
108
- const codeListPath = path.join(newKnowledgeRoot, '_docs', 'code_list.md');
109
- mkdirSync(path.dirname(codeListPath), { recursive: true });
110
- writeFileSync(path.join(projectDir, 'actual.env'), 'LOGIN_USERNAME=demo-user\n');
111
- writeFileSync(codeListPath, '# code list\n');
112
- writeFileSync(stageContextPath, [
113
- '---',
114
- 'projectName: context-project',
115
- 'projectDir: .',
116
- 'mode: glue',
117
- 'envPath: actual.env',
118
- 'knowledgeRoot: "new knowledge"',
119
- 'codeListPaths:',
120
- ' - "new knowledge/_docs/code_list.md"',
121
- '---',
122
- '',
123
- '# context-project',
124
- ].join('\n'));
125
- const indexDir = path.join(homeDir, '.ep-stage');
126
- mkdirSync(indexDir, { recursive: true });
127
- writeFileSync(path.join(indexDir, 'projects.index.json5'), JSON5.stringify({
128
- projects: [
129
- {
130
- projectName: 'stale-index-project',
131
- projectDir,
132
- mode: 'glue',
133
- stageCreateVersion: '0.0.4-alpha.0',
134
- requirements: [],
135
- // legacy 字段
136
- envPath: 'old.env',
137
- knowledgeRoot: oldKnowledgeRoot,
138
- codeListPaths: [codeListPath],
139
- stageContextPath,
140
- },
141
- ],
142
- systems: [],
143
- }, null, 2));
144
- const result = resolveStageContext({
145
- cwd: createTempDir('ep-stage-source-outside'),
146
- codeListPath,
147
- homeDir,
148
- });
149
- expect(result.context).toMatchObject({
150
- projectName: 'context-project',
151
- envPath: 'actual.env',
152
- envFilePath: path.join(projectDir, 'actual.env'),
153
- knowledgeRoot: newKnowledgeRoot,
154
- codeListPaths: [codeListPath],
155
- stageContextPath,
156
- });
157
- expect(result.context.knowledgeRoot).not.toBe(oldKnowledgeRoot);
158
- expect(result.trace.inputs).toContainEqual(expect.objectContaining({
159
- kind: 'context_source',
160
- path: path.join(homeDir, '.ep-stage', 'projects.index.json5'),
161
- }));
162
- expect(result.trace.outputs).toContainEqual(expect.objectContaining({
163
- kind: 'stage_context',
164
- path: stageContextPath,
165
- }));
166
- });
167
- it('reads user project index with JSON5 comments and trailing commas', () => {
168
- const homeDir = createTempDir('ep-stage-home-json5');
169
- const projectDir = createTempDir('ep-stage-json5-project');
170
- const codeListPath = path.join(projectDir, 'knowledge', '_docs', 'code_list.md');
171
- const indexDir = path.join(homeDir, '.ep-stage');
172
- mkdirSync(indexDir, { recursive: true });
173
- mkdirSync(path.dirname(codeListPath), { recursive: true });
174
- writeFileSync(path.join(projectDir, '.env'), 'LOGIN_USERNAME=demo-user\n');
175
- writeFileSync(codeListPath, '# code list\n');
176
- writeFileSync(path.join(indexDir, 'projects.index.json5'), `{
177
- // 用户可编辑索引允许注释
178
- projects: [
179
- {
180
- projectName: 'json5-project',
181
- projectDir: '${projectDir}',
182
- mode: 'glue',
183
- stageCreateVersion: '0.0.4-alpha.0',
184
- requirements: [],
185
- // legacy 字段(graceful read,向后兼容)
186
- envPath: '.env',
187
- codeListPaths: [
188
- '${codeListPath}',
189
- ],
190
- },
191
- ],
192
- systems: [],
193
- }`);
194
- const result = resolveStageContext({
195
- projectDir,
196
- codeListPath,
197
- homeDir,
198
- });
199
- expect(result.context.projectName).toBe('json5-project');
200
- expect(result.context.envFilePath).toBe(path.join(projectDir, '.env'));
201
- expect(result.trace.reasoningSummary?.evidenceChain[0]).toMatchObject({
202
- source: 'human',
203
- path: path.join(indexDir, 'projects.index.json5'),
204
- });
205
- });
206
- it('creates a debug stage context from explicit projectDir when no context or index exists', () => {
207
- const homeDir = createTempDir('ep-stage-home-debug');
208
- const projectDir = createTempDir('ep-stage-debug-project');
209
- const codeListPath = path.join(projectDir, 'knowledge', '_docs', 'code_list.md');
210
- mkdirSync(path.dirname(codeListPath), { recursive: true });
211
- writeFileSync(codeListPath, '# code list\n');
212
- const result = resolveStageContext({
213
- projectDir,
214
- codeListPath,
215
- homeDir,
216
- });
217
- expect(result.context).toMatchObject({
218
- projectName: path.basename(projectDir),
219
- projectDir,
220
- mode: 'glue',
221
- envPath: '.env',
222
- envFilePath: path.join(projectDir, '.env'),
223
- codeListPaths: [codeListPath],
224
- });
225
- expect(result.trace).toMatchObject({
226
- gate: { status: 'needs_review' },
227
- reasoningSummary: {
228
- conclusion: expect.stringContaining('显式 --project-dir'),
229
- needsHumanReview: true,
230
- },
231
- });
232
- });
233
- it('writes new schema project index (no version field, with systems[]) and keeps reading legacy JSON5', () => {
234
- const homeDir = createTempDir('ep-stage-home-version');
235
- const projectDir = createTempDir('ep-stage-version-project');
236
- mkdirSync(projectDir, { recursive: true });
237
- const indexPath = writeProjectIndex({
238
- homeDir,
239
- projects: [
240
- {
241
- projectName: 'versioned-project',
242
- projectDir,
243
- mode: 'glue',
244
- stageCreateVersion: '0.0.4-alpha.0',
245
- requirements: [],
246
- },
247
- ],
248
- });
249
- const parsedIndex = JSON5.parse(readFileSync(indexPath, 'utf8'));
250
- // 新 schema:顶层无 version、含 systems[]、projects[] 不含 legacy 字段。
251
- expect(parsedIndex).toMatchObject({
252
- projects: [
253
- {
254
- projectName: 'versioned-project',
255
- projectDir,
256
- mode: 'glue',
257
- stageCreateVersion: '0.0.4-alpha.0',
258
- requirements: [],
259
- },
260
- ],
261
- systems: [],
262
- });
263
- expect(parsedIndex.version).toBeUndefined();
264
- expect(parsedIndex.projects[0].envPath).toBeUndefined();
265
- expect(parsedIndex.projects[0].knowledgeRoot).toBeUndefined();
266
- expect(parsedIndex.projects[0].codeListPaths).toBeUndefined();
267
- expect(parsedIndex.projects[0].stageContextPath).toBeUndefined();
268
- // legacy 数据:旧版 index 不带 systems 字段,仍要可读(graceful read)。
269
- const legacyHomeDir = createTempDir('ep-stage-legacy-version');
270
- const legacyProjectDir = createTempDir('ep-stage-legacy-project');
271
- const legacyIndexDir = path.join(legacyHomeDir, '.ep-stage');
272
- mkdirSync(legacyProjectDir, { recursive: true });
273
- mkdirSync(legacyIndexDir, { recursive: true });
274
- writeFileSync(path.join(legacyProjectDir, '.env'), 'LOGIN_USERNAME=demo-user\n');
275
- writeFileSync(path.join(legacyIndexDir, 'projects.index.json5'), `{
276
- // legacy index without version / systems
277
- projects: [
278
- {
279
- projectName: 'legacy-project',
280
- projectDir: '${legacyProjectDir}',
281
- mode: 'glue',
282
- envPath: '.env',
283
- },
284
- ],
285
- }`);
286
- expect(resolveStageContext({
287
- projectDir: legacyProjectDir,
288
- homeDir: legacyHomeDir,
289
- }).context.projectName).toBe('legacy-project');
290
- });
291
- });
292
- describe('lookupProjectByRequirement (REQ-DATA-01)', () => {
293
- it('returns null when projects.index.json5 is missing', () => {
294
- const homeDir = createTempDir('ep-stage-lookup-missing');
295
- // 不创建 ~/.ep-stage 目录
296
- const result = lookupProjectByRequirement({
297
- requirementPath: path.join(homeDir, 'no-such-requirement'),
298
- homeDir,
299
- });
300
- expect(result).toBeNull();
301
- });
302
- it('matches when requirementPath exactly equals a requirement requirementDir', () => {
303
- const homeDir = createTempDir('ep-stage-lookup-req');
304
- const projectDir = createTempDir('ep-stage-lookup-projdir');
305
- const requirementDir = path.join(projectDir, 'requirements', 'req-001');
306
- const testsDir = path.join(projectDir, 'tests', 'req-001');
307
- writeProjectIndex({
308
- homeDir,
309
- projects: [
310
- {
311
- projectName: 'lookup-project',
312
- projectDir,
313
- mode: 'glue',
314
- stageCreateVersion: '0.0.4-alpha.0',
315
- requirements: [
316
- {
317
- name: 'req-001',
318
- requirementDir,
319
- testsDir,
320
- },
321
- ],
322
- },
323
- ],
324
- });
325
- const result = lookupProjectByRequirement({ requirementPath: requirementDir, homeDir });
326
- expect(result).not.toBeNull();
327
- expect(result?.projectName).toBe('lookup-project');
328
- expect(result?.requirements[0]?.name).toBe('req-001');
329
- });
330
- it('matches when requirementPath exactly equals a requirement testsDir', () => {
331
- const homeDir = createTempDir('ep-stage-lookup-tests');
332
- const projectDir = createTempDir('ep-stage-lookup-projdir');
333
- const requirementDir = path.join(projectDir, 'requirements', 'req-002');
334
- const testsDir = path.join(projectDir, 'tests', 'req-002');
335
- writeProjectIndex({
336
- homeDir,
337
- projects: [
338
- {
339
- projectName: 'lookup-project',
340
- projectDir,
341
- mode: 'glue',
342
- stageCreateVersion: '0.0.4-alpha.0',
343
- requirements: [
344
- {
345
- name: 'req-002',
346
- requirementDir,
347
- testsDir,
348
- },
349
- ],
350
- },
351
- ],
352
- });
353
- const result = lookupProjectByRequirement({ requirementPath: testsDir, homeDir });
354
- expect(result).not.toBeNull();
355
- expect(result?.projectName).toBe('lookup-project');
356
- });
357
- it('matches when requirementPath exactly equals a project projectDir', () => {
358
- const homeDir = createTempDir('ep-stage-lookup-pd');
359
- const projectDir = createTempDir('ep-stage-lookup-projdir-only');
360
- writeProjectIndex({
361
- homeDir,
362
- projects: [
363
- {
364
- projectName: 'projdir-only',
365
- projectDir,
366
- mode: 'glue',
367
- stageCreateVersion: '0.0.4-alpha.0',
368
- requirements: [],
369
- },
370
- ],
371
- });
372
- const result = lookupProjectByRequirement({ requirementPath: projectDir, homeDir });
373
- expect(result).not.toBeNull();
374
- expect(result?.projectName).toBe('projdir-only');
375
- });
376
- it('returns null when no project matches', () => {
377
- const homeDir = createTempDir('ep-stage-lookup-nomatch');
378
- const projectDir = createTempDir('ep-stage-lookup-projdir');
379
- writeProjectIndex({
380
- homeDir,
381
- projects: [
382
- {
383
- projectName: 'unrelated-project',
384
- projectDir,
385
- mode: 'glue',
386
- stageCreateVersion: '0.0.4-alpha.0',
387
- requirements: [],
388
- },
389
- ],
390
- });
391
- const otherDir = createTempDir('ep-stage-lookup-other');
392
- const result = lookupProjectByRequirement({ requirementPath: otherDir, homeDir });
393
- expect(result).toBeNull();
394
- });
395
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=testcase-generator.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testcase-generator.test.d.ts","sourceRoot":"","sources":["../../test/testcase-generator.test.ts"],"names":[],"mappings":""}
@@ -1,276 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { generateGlueTestcases } from '../src/index.js';
3
- /**
4
- * 构造用例生成器测试所需的最小 CRUD 契约。
5
- *
6
- * @returns 包含完整 CRUD 流程槽位的测试契约。
7
- */
8
- function createCrudContract() {
9
- return {
10
- contractVersion: 'crud-business-module/v1',
11
- module: {
12
- id: 'zwplace',
13
- label: '场所窗口信息管理',
14
- },
15
- pages: {
16
- list: {
17
- role: 'list',
18
- pageId: 'gxhzwplacelist',
19
- title: '场所窗口信息列表',
20
- iframeSrcKeyword: 'gxhzwplacelist',
21
- fields: [],
22
- buttons: [],
23
- grids: [],
24
- dialogs: [],
25
- sources: [],
26
- },
27
- },
28
- dataKey: {
29
- field: 'windowname',
30
- label: '窗口名称',
31
- availability: 'derivable',
32
- generationStrategy: 'timestamp_prefix',
33
- sources: [],
34
- },
35
- searchConditions: [
36
- {
37
- field: 'windowname',
38
- label: '窗口名称',
39
- component: 'input',
40
- seedValue: '自动化测试窗口',
41
- generationStrategy: 'timestamp_prefix',
42
- sources: [],
43
- },
44
- ],
45
- flows: {
46
- create: {
47
- entryButton: {
48
- pageRole: 'list',
49
- label: '新增场所窗口',
50
- sources: [],
51
- },
52
- targetPageId: 'gxhzwplaceadd',
53
- saveButton: {
54
- pageRole: 'add',
55
- label: '保存并关闭',
56
- sources: [],
57
- },
58
- overrideFields: [],
59
- },
60
- search: {
61
- searchField: {
62
- pageRole: 'list',
63
- field: 'windowname',
64
- label: '窗口名称',
65
- sources: [],
66
- },
67
- submitControl: {
68
- pageRole: 'list',
69
- label: '查询',
70
- sources: [],
71
- },
72
- resultGrid: 'datagrid',
73
- },
74
- update: {
75
- entryAction: {
76
- pageRole: 'list',
77
- label: '修改',
78
- sources: [],
79
- },
80
- targetPageId: 'gxhzwplaceedit',
81
- updateField: {
82
- pageRole: 'edit',
83
- field: 'windowname',
84
- label: '窗口名称',
85
- sources: [],
86
- },
87
- saveButton: {
88
- pageRole: 'edit',
89
- label: '保存并关闭',
90
- sources: [],
91
- },
92
- },
93
- delete: {
94
- entryButton: {
95
- pageRole: 'list',
96
- label: '删除选定',
97
- sources: [],
98
- },
99
- expectedOutcome: 'success_delete',
100
- confirmControl: {
101
- pageRole: 'list',
102
- label: '确定',
103
- sources: [],
104
- },
105
- },
106
- },
107
- assertions: [],
108
- businessRules: [],
109
- unresolvedSlots: [],
110
- };
111
- }
112
- /**
113
- * 构造用例生成器测试所需的 coverage diff trace。
114
- *
115
- * @returns 默认无未确认项的 coverage diff。
116
- */
117
- function createCoverageDiff() {
118
- return {
119
- moduleId: 'zwplace',
120
- skeletonCoverageId: 'crud-plus-export/v1',
121
- covered: [
122
- {
123
- actionId: 'zwplace.list.create',
124
- label: '新增场所信息管理',
125
- pageId: 'gxhzwplacelist',
126
- status: 'covered',
127
- reason: '动作标签已被当前骨架能力覆盖。',
128
- },
129
- ],
130
- partiallyCovered: [],
131
- gapCandidates: [],
132
- unresolved: [],
133
- nestedCrudCandidates: [],
134
- baseline: { plannedWorkflows: [], candidateWorkflows: [], unresolvedWorkflows: [] },
135
- comparison: { both: [], onlyBaseline: [], onlyGeneralized: [] },
136
- reasoningSummary: {
137
- conclusion: 'CRUD 单页能力已覆盖。',
138
- evidenceChain: [],
139
- alternatives: ['crud.single-page'],
140
- confidence: 'medium',
141
- risks: [],
142
- needsHumanReview: false,
143
- },
144
- };
145
- }
146
- /**
147
- * 胶水测试用例生成器测试。
148
- *
149
- * 验证 generateGlueTestcases 能从同一份 CRUD contract、已确认场景与 coverage diff
150
- * 生成可审阅 JSON + Markdown。
151
- */
152
- describe('glue testcase generator', () => {
153
- it('renders reviewable markdown and json cases from a confirmed scenario and contract', () => {
154
- const output = generateGlueTestcases({
155
- contract: createCrudContract(),
156
- menu: '场所窗口信息管理>场所窗口信息列表',
157
- scenarios: ['crud.single-page', 'crud.nested'],
158
- confirmedScenarios: ['crud.single-page'],
159
- coverageDiff: createCoverageDiff(),
160
- });
161
- expect(output.json.cases).toHaveLength(4);
162
- expect(output.json.cases.map((item) => item.caseId)).toEqual([
163
- 'zwplace.create',
164
- 'zwplace.read',
165
- 'zwplace.update',
166
- 'zwplace.delete',
167
- ]);
168
- expect(output.json.cases[0]).toMatchObject({
169
- caseId: 'zwplace.create',
170
- reviewStatus: 'confirmed',
171
- });
172
- expect(output.json.cases[0]?.evidence).toContain('skeleton=crud.skeleton-testcase/v1');
173
- expect(output.markdown).toContain('| TC-CRUD-001 |');
174
- expect(output.markdown).toContain('查询字段:窗口名称');
175
- expect(output.markdown).not.toContain('{{');
176
- });
177
- it('requires contract and menu instead of free-form module fields', () => {
178
- expect(() => generateGlueTestcases({
179
- moduleId: 'zwplace',
180
- moduleName: '场所窗口信息管理',
181
- scenarioStatus: 'confirmed',
182
- scenarios: ['crud.single-page'],
183
- confirmedScenarios: ['crud.single-page'],
184
- coverageDiff: createCoverageDiff(),
185
- })).toThrow('生成胶水测试用例必须提供 contract 和 menu');
186
- });
187
- it('keeps testcase gate in needs_review when coverage diff has unresolved items', () => {
188
- const coverageDiff = createCoverageDiff();
189
- coverageDiff.unresolved = [
190
- {
191
- actionId: 'zwplace.list.export',
192
- label: '导出',
193
- pageId: 'gxhzwplacelist',
194
- status: 'unresolved',
195
- reason: '导出动作是否纳入本轮 CRUD 用例需要人工确认。',
196
- },
197
- ];
198
- const output = generateGlueTestcases({
199
- contract: createCrudContract(),
200
- menu: '场所窗口信息管理>场所窗口信息列表',
201
- scenarios: ['crud.single-page'],
202
- confirmedScenarios: ['crud.single-page'],
203
- coverageDiff,
204
- });
205
- expect(output.json.cases.every((item) => item.reviewStatus === 'needs_review')).toBe(true);
206
- expect(output.json.cases[0]?.unresolved).toEqual([
207
- 'coverageDiff.unresolved: 导出动作是否纳入本轮 CRUD 用例需要人工确认。',
208
- ]);
209
- expect(output.json.reasoningSummary.risks).toContain('coverageDiff.unresolved: 导出动作是否纳入本轮 CRUD 用例需要人工确认。');
210
- expect(output.json.reasoningSummary.evidenceChain).toContainEqual({
211
- source: 'human',
212
- path: 'testcase-generation.json',
213
- text: 'unresolved=coverageDiff.unresolved: 导出动作是否纳入本轮 CRUD 用例需要人工确认。',
214
- });
215
- });
216
- it('renders nested CRUD candidates with the CRUD testcase skeleton and unresolved gate', () => {
217
- const coverageDiff = createCoverageDiff();
218
- coverageDiff.nestedCrudCandidates = [
219
- {
220
- pageId: 'gxhzwplaceadd',
221
- evidence: ['弹窗页面中存在 mini-datagrid', '按钮 添加窗口'],
222
- supportedActions: ['create', 'delete'],
223
- unresolvedReason: '子表保存后断言需要人工确认',
224
- },
225
- ];
226
- const output = generateGlueTestcases({
227
- contract: createCrudContract(),
228
- menu: '场所窗口信息管理>场所窗口信息列表',
229
- scenarios: ['crud.nested'],
230
- confirmedScenarios: ['crud.nested'],
231
- coverageDiff,
232
- });
233
- expect(output.json.cases[0]).toMatchObject({
234
- scenario: 'crud.nested',
235
- reviewStatus: 'needs_review',
236
- unresolved: ['coverageDiff.nestedCrudCandidates[gxhzwplaceadd]: 子表保存后断言需要人工确认'],
237
- });
238
- expect(output.json.cases[0]?.evidence).toContain('skeleton=crud.skeleton-testcase/v1');
239
- expect(output.markdown).toContain('| TC-CRUD-001 |');
240
- expect(output.markdown).not.toContain('{{');
241
- });
242
- it('does not let nested CRUD candidates block a confirmed crud.single-page scenario', () => {
243
- const coverageDiff = createCoverageDiff();
244
- coverageDiff.nestedCrudCandidates = [
245
- {
246
- pageId: 'gxhzwplaceadd',
247
- evidence: ['弹窗页面中存在 mini-datagrid', '按钮 添加窗口'],
248
- supportedActions: ['create', 'delete'],
249
- unresolvedReason: '子表保存后断言需要人工确认',
250
- },
251
- ];
252
- const output = generateGlueTestcases({
253
- contract: createCrudContract(),
254
- menu: '场所窗口信息管理>场所窗口信息列表',
255
- scenarios: ['crud.single-page', 'crud.nested'],
256
- confirmedScenarios: ['crud.single-page'],
257
- coverageDiff,
258
- });
259
- expect(output.json.cases.every((item) => item.reviewStatus === 'confirmed')).toBe(true);
260
- expect(output.json.cases[0]?.unresolved).toEqual([]);
261
- expect(output.json.reasoningSummary.needsHumanReview).toBe(false);
262
- expect(output.json.reasoningSummary.risks).toEqual([]);
263
- });
264
- it('surfaces inferred menu candidates in testcase unresolved output for human review', () => {
265
- const output = generateGlueTestcases({
266
- contract: createCrudContract(),
267
- menu: '未确认菜单路径',
268
- menuReviewHint: 'menu.candidate: 场所窗口信息管理>场所窗口信息列表 | 场所窗口信息列表',
269
- scenarios: ['crud.single-page'],
270
- confirmedScenarios: ['crud.single-page'],
271
- coverageDiff: createCoverageDiff(),
272
- });
273
- expect(output.json.cases[0]?.unresolved).toContain('menu.candidate: 场所窗口信息管理>场所窗口信息列表 | 场所窗口信息列表');
274
- expect(output.markdown).toContain('menu.candidate: 场所窗口信息管理>场所窗口信息列表 | 场所窗口信息列表');
275
- });
276
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=testcase-skeleton.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testcase-skeleton.test.d.ts","sourceRoot":"","sources":["../../test/testcase-skeleton.test.ts"],"names":[],"mappings":""}