@epoint-testtech/ep-stage-skill 0.0.9 → 0.5.0-alpha.2

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 (158) hide show
  1. package/SKILL.md +27 -27
  2. package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -223
  3. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -275
  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 -95
  6. package/codex-skill/ep-stage/glue-run-test/SKILL.md +273 -296
  7. package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -30
  8. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -132
  9. package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -44
  10. package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
  11. package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +127 -166
  12. package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
  13. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
  14. package/codex-skill/ep-stage/scripts/validate-skill.mjs +95 -98
  15. package/dist/src/cli/generate-testcase.d.ts +0 -1
  16. package/dist/src/cli/generate-testcase.d.ts.map +1 -1
  17. package/dist/src/cli/generate-testcase.js +1 -6
  18. package/dist/src/cli/index.d.ts.map +1 -1
  19. package/dist/src/cli/index.js +14 -38
  20. package/dist/src/cli/run.d.ts +0 -2
  21. package/dist/src/cli/run.d.ts.map +1 -1
  22. package/dist/src/cli/run.js +21 -70
  23. package/dist/src/cli/spec.d.ts.map +1 -1
  24. package/dist/src/cli/spec.js +0 -9
  25. package/dist/src/contracts/glue-contract-v1.d.ts +0 -35
  26. package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -1
  27. package/dist/src/extractors/code-list-json5.d.ts +1 -34
  28. package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
  29. package/dist/src/extractors/code-list-json5.js +0 -1
  30. package/dist/src/extractors/html-page.d.ts +0 -9
  31. package/dist/src/extractors/html-page.d.ts.map +1 -1
  32. package/dist/src/extractors/html-page.js +2 -26
  33. package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
  34. package/dist/src/generators/stage-skeleton-script.d.ts +0 -50
  35. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  36. package/dist/src/generators/stage-skeleton-script.js +116 -449
  37. package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -1
  38. package/dist/src/runtime/ai-runtime-trace.js +0 -8
  39. package/dist/src/testcase/contract-testcase-assembler.d.ts +0 -28
  40. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
  41. package/dist/src/testcase/contract-testcase-assembler.js +75 -329
  42. package/dist/src/validation/contract.d.ts.map +1 -1
  43. package/dist/src/validation/contract.js +0 -29
  44. package/dist/test/cli/generate-testcase-json5.test.d.ts +2 -0
  45. package/dist/test/cli/generate-testcase-json5.test.d.ts.map +1 -0
  46. package/dist/test/cli/generate-testcase-json5.test.js +154 -0
  47. package/dist/test/cli/index.test.d.ts +2 -0
  48. package/dist/test/cli/index.test.d.ts.map +1 -0
  49. package/dist/test/cli/index.test.js +114 -0
  50. package/dist/test/cli/run.test.d.ts +2 -0
  51. package/dist/test/cli/run.test.d.ts.map +1 -0
  52. package/dist/test/cli/run.test.js +429 -0
  53. package/dist/test/cli/spec.test.d.ts +2 -0
  54. package/dist/test/cli/spec.test.d.ts.map +1 -0
  55. package/dist/test/cli/spec.test.js +112 -0
  56. package/dist/test/code-list-json5.test.d.ts +2 -0
  57. package/dist/test/code-list-json5.test.d.ts.map +1 -0
  58. package/dist/test/code-list-json5.test.js +84 -0
  59. package/dist/test/code-list.test.d.ts +2 -0
  60. package/dist/test/code-list.test.d.ts.map +1 -0
  61. package/dist/test/code-list.test.js +39 -0
  62. package/dist/test/gap-inference.test.d.ts +2 -0
  63. package/dist/test/gap-inference.test.d.ts.map +1 -0
  64. package/dist/test/gap-inference.test.js +597 -0
  65. package/dist/test/generalization.test.d.ts +2 -0
  66. package/dist/test/generalization.test.d.ts.map +1 -0
  67. package/dist/test/generalization.test.js +73 -0
  68. package/dist/test/material-inventory.test.d.ts +2 -0
  69. package/dist/test/material-inventory.test.d.ts.map +1 -0
  70. package/dist/test/material-inventory.test.js +155 -0
  71. package/dist/test/observable-chain.test.d.ts +2 -0
  72. package/dist/test/observable-chain.test.d.ts.map +1 -0
  73. package/dist/test/observable-chain.test.js +123 -0
  74. package/dist/test/page-structure.test.d.ts +2 -0
  75. package/dist/test/page-structure.test.d.ts.map +1 -0
  76. package/dist/test/page-structure.test.js +45 -0
  77. package/dist/test/runtime/ai-runtime-trace.test.d.ts +2 -0
  78. package/dist/test/runtime/ai-runtime-trace.test.d.ts.map +1 -0
  79. package/dist/test/runtime/ai-runtime-trace.test.js +78 -0
  80. package/dist/test/scenario-inference.test.d.ts +2 -0
  81. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  82. package/dist/test/scenario-inference.test.js +73 -0
  83. package/dist/test/stage-context.test.d.ts +2 -0
  84. package/dist/test/stage-context.test.d.ts.map +1 -0
  85. package/dist/test/stage-context.test.js +395 -0
  86. package/dist/test/testcase/testcase-markdown.test.d.ts +2 -0
  87. package/dist/test/testcase/testcase-markdown.test.d.ts.map +1 -0
  88. package/dist/test/testcase/testcase-markdown.test.js +48 -0
  89. package/dist/test/testcase-generator.test.d.ts +2 -0
  90. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  91. package/dist/test/testcase-generator.test.js +276 -0
  92. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  93. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  94. package/dist/test/testcase-skeleton.test.js +185 -0
  95. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  96. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  97. package/dist/test/testcase-spec-assembly.test.js +105 -0
  98. package/dist/test/util/credentials.test.d.ts +2 -0
  99. package/dist/test/util/credentials.test.d.ts.map +1 -0
  100. package/dist/test/util/credentials.test.js +64 -0
  101. package/dist/test/util/i18n-testcase.test.d.ts +2 -0
  102. package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
  103. package/dist/test/util/i18n-testcase.test.js +119 -0
  104. package/dist/test/util/softlink.test.d.ts +2 -0
  105. package/dist/test/util/softlink.test.d.ts.map +1 -0
  106. package/dist/test/util/softlink.test.js +82 -0
  107. package/dist/test/validation/contract.test.d.ts +2 -0
  108. package/dist/test/validation/contract.test.d.ts.map +1 -0
  109. package/dist/test/validation/contract.test.js +116 -0
  110. package/dist/test/validation/credentials.test.d.ts +2 -0
  111. package/dist/test/validation/credentials.test.d.ts.map +1 -0
  112. package/dist/test/validation/credentials.test.js +74 -0
  113. package/dist/test/validation/projects-index.test.d.ts +2 -0
  114. package/dist/test/validation/projects-index.test.d.ts.map +1 -0
  115. package/dist/test/validation/projects-index.test.js +48 -0
  116. package/dist/test/validation/testcase-v1-draft.test.d.ts +2 -0
  117. package/dist/test/validation/testcase-v1-draft.test.d.ts.map +1 -0
  118. package/dist/test/validation/testcase-v1-draft.test.js +173 -0
  119. package/dist/vitest.config.d.ts +3 -0
  120. package/dist/vitest.config.d.ts.map +1 -0
  121. package/dist/vitest.config.js +7 -0
  122. package/docs/README.md +124 -124
  123. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
  124. package/examples/schemeresource.module-hints.json +38 -38
  125. package/examples/schemeresource.observable.code_list.md +37 -37
  126. package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
  127. package/examples/zwplace-placecategory-validation.json +29 -29
  128. package/examples/zwplace.module-hints.json +69 -69
  129. package/examples/zwplace.observable.code_list.md +37 -37
  130. package/package.json +19 -13
  131. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +0 -72
  132. package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +0 -233
  133. package/codex-skill/ep-stage/page-element-scan/SKILL.md +0 -91
  134. package/codex-skill/ep-stage/page-element-scan/evals/evals.json +0 -37
  135. package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +0 -85
  136. package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +0 -153
  137. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +0 -233
  138. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +0 -298
  139. package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +0 -749
  140. package/dist/src/cli/probe.d.ts +0 -46
  141. package/dist/src/cli/probe.d.ts.map +0 -1
  142. package/dist/src/cli/probe.js +0 -447
  143. package/dist/src/cli/run-report.d.ts +0 -46
  144. package/dist/src/cli/run-report.d.ts.map +0 -1
  145. package/dist/src/cli/run-report.js +0 -316
  146. package/dist/src/extractors/prd-extractor.d.ts +0 -70
  147. package/dist/src/extractors/prd-extractor.d.ts.map +0 -1
  148. package/dist/src/extractors/prd-extractor.js +0 -248
  149. package/dist/src/testcase/button-interaction-inference.d.ts +0 -37
  150. package/dist/src/testcase/button-interaction-inference.d.ts.map +0 -1
  151. package/dist/src/testcase/button-interaction-inference.js +0 -385
  152. package/dist/src/testcase/prd-button-mapper.d.ts +0 -72
  153. package/dist/src/testcase/prd-button-mapper.d.ts.map +0 -1
  154. package/dist/src/testcase/prd-button-mapper.js +0 -148
  155. package/dist/src/testcase/testcase-v2.d.ts +0 -50
  156. package/dist/src/testcase/testcase-v2.d.ts.map +0 -1
  157. package/dist/src/testcase/testcase-v2.js +0 -1
  158. package/docs/mvp-usage-guide.md +0 -298
@@ -0,0 +1,395 @@
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
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=testcase-markdown.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testcase-markdown.test.d.ts","sourceRoot":"","sources":["../../../test/testcase/testcase-markdown.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { renderUnifiedTestcaseMarkdown } from '../../src/testcase/testcase-markdown.js';
3
+ const testcase = {
4
+ schemaVersion: 'ep-stage-testcase/v1-draft',
5
+ testcaseId: 'zwplace-testcase',
6
+ contractRef: '.ep-stage/contract.json5',
7
+ scenario: 'crud_export',
8
+ moduleId: 'zwplace',
9
+ moduleName: '场所窗口信息管理',
10
+ cases: [
11
+ {
12
+ caseId: 'zwplace-placename-create',
13
+ title: '场所名称-新增',
14
+ generationStrategy: 'glue',
15
+ coverageStatus: 'covered',
16
+ reviewStatus: 'confirmed',
17
+ queryKey: '场所名称',
18
+ precondition: '使用业务管理员登录并进入目标页面',
19
+ steps: '新增、查询、编辑、删除',
20
+ assertion: '列表数据符合新增、编辑、删除结果',
21
+ },
22
+ ],
23
+ uncoveredCandidates: [
24
+ {
25
+ caseId: 'syncWindowSystem',
26
+ label: '同步窗口管理系统按钮',
27
+ generationStrategy: 'ai',
28
+ coverageStatus: 'gap',
29
+ requiredRole: '业务管理员',
30
+ businessIntent: '验证同步窗口管理系统按钮',
31
+ assertionExpectation: '点击后返回同步结果',
32
+ queryKey: '不适用',
33
+ precondition: '使用业务管理员登录并进入目标页面',
34
+ steps: '观察同步按钮并执行同步动作',
35
+ assertion: '页面返回同步结果',
36
+ },
37
+ ],
38
+ };
39
+ describe('renderUnifiedTestcaseMarkdown', () => {
40
+ it('把 glue 和 ai 用例渲染到同一张表,并用生成策略列区分', () => {
41
+ const markdown = renderUnifiedTestcaseMarkdown(testcase);
42
+ expect(markdown).toContain('| 用例 ID | 生成策略 | 用例标题 | 查询主键 | 前置条件 | 执行步骤 | 执行断言 |');
43
+ expect(markdown).toContain('| zwplace-placename-create | 胶水组装 | 场所名称-新增 |');
44
+ expect(markdown).toContain('| syncWindowSystem | AI 推理 | 同步窗口管理系统按钮 |');
45
+ expect(markdown).not.toContain('## 已覆盖用例');
46
+ expect(markdown).not.toContain('## 未覆盖候选');
47
+ });
48
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=testcase-generator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testcase-generator.test.d.ts","sourceRoot":"","sources":["../../test/testcase-generator.test.ts"],"names":[],"mappings":""}