@epoint-testtech/ep-stage-skill 0.0.3-alpha.2 → 0.0.4-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 (155) hide show
  1. package/SKILL.md +2 -2
  2. package/codex-skill/ep-stage/.omc/state/sessions/d636ad35-5fc9-4d5e-85b0-2bcad7d86ee5/pre-tool-advisory-throttle.json +10 -0
  3. package/codex-skill/ep-stage/glue-create-project/SKILL.md +206 -0
  4. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +274 -0
  5. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +112 -0
  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 +289 -0
  8. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +145 -0
  9. package/codex-skill/ep-stage/{glue-test → glue-run-test}/scripts/generate-crud-spec.mjs +3 -3
  10. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +1 -0
  11. package/codex-skill/ep-stage/scripts/validate-skill.mjs +29 -7
  12. package/dist/src/cli/dev/extract-contract.d.ts +14 -0
  13. package/dist/src/cli/dev/extract-contract.d.ts.map +1 -0
  14. package/dist/src/cli/dev/extract-contract.js +114 -0
  15. package/dist/src/cli/generate-crud-contract.js +7 -77
  16. package/dist/src/cli/generate-playwright-tests.d.ts +0 -28
  17. package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -1
  18. package/dist/src/cli/generate-playwright-tests.js +4 -81
  19. package/dist/src/cli/generate-testcase.d.ts +84 -0
  20. package/dist/src/cli/generate-testcase.d.ts.map +1 -0
  21. package/dist/src/cli/generate-testcase.js +300 -0
  22. package/dist/src/cli/index.d.ts +26 -0
  23. package/dist/src/cli/index.d.ts.map +1 -0
  24. package/dist/src/cli/index.js +75 -0
  25. package/dist/src/cli/probe.d.ts +46 -0
  26. package/dist/src/cli/probe.d.ts.map +1 -0
  27. package/dist/src/cli/probe.js +447 -0
  28. package/dist/src/cli/run-gap-pipeline.js +4 -0
  29. package/dist/src/cli/run.d.ts +63 -0
  30. package/dist/src/cli/run.d.ts.map +1 -0
  31. package/dist/src/cli/run.js +116 -0
  32. package/dist/src/cli/spec.d.ts +48 -0
  33. package/dist/src/cli/spec.d.ts.map +1 -0
  34. package/dist/src/cli/spec.js +102 -0
  35. package/dist/src/context/stage-context.d.ts +72 -8
  36. package/dist/src/context/stage-context.d.ts.map +1 -1
  37. package/dist/src/context/stage-context.js +61 -15
  38. package/dist/src/contracts/inference-trace.d.ts +223 -0
  39. package/dist/src/contracts/inference-trace.d.ts.map +1 -0
  40. package/dist/src/contracts/inference-trace.js +12 -0
  41. package/dist/src/extractors/code-list.d.ts +3 -3
  42. package/dist/src/extractors/code-list.d.ts.map +1 -1
  43. package/dist/src/extractors/code-list.js +46 -11
  44. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  45. package/dist/src/generators/stage-skeleton-script.js +9 -0
  46. package/dist/src/index.d.ts +7 -2
  47. package/dist/src/index.d.ts.map +1 -1
  48. package/dist/src/index.js +3 -1
  49. package/dist/src/inference/module-hints-inference.d.ts +30 -0
  50. package/dist/src/inference/module-hints-inference.d.ts.map +1 -0
  51. package/dist/src/inference/module-hints-inference.js +363 -0
  52. package/dist/src/inference/source-analysis.d.ts +26 -0
  53. package/dist/src/inference/source-analysis.d.ts.map +1 -0
  54. package/dist/src/inference/source-analysis.js +258 -0
  55. package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
  56. package/dist/src/testcase/testcase-generator.js +4 -0
  57. package/dist/src/testcase/testcase-v2.d.ts +50 -0
  58. package/dist/src/testcase/testcase-v2.d.ts.map +1 -0
  59. package/dist/src/testcase/testcase-v2.js +1 -0
  60. package/dist/src/util/credentials.d.ts +12 -0
  61. package/dist/src/util/credentials.d.ts.map +1 -0
  62. package/dist/src/util/credentials.js +19 -0
  63. package/dist/src/util/i18n-testcase.d.ts +8 -0
  64. package/dist/src/util/i18n-testcase.d.ts.map +1 -0
  65. package/dist/src/util/i18n-testcase.js +55 -0
  66. package/dist/src/util/softlink.d.ts +33 -0
  67. package/dist/src/util/softlink.d.ts.map +1 -0
  68. package/dist/src/util/softlink.js +43 -0
  69. package/dist/src/validation/credentials.d.ts +19 -0
  70. package/dist/src/validation/credentials.d.ts.map +1 -0
  71. package/dist/src/validation/credentials.js +38 -0
  72. package/dist/src/validation/index.d.ts +6 -0
  73. package/dist/src/validation/index.d.ts.map +1 -0
  74. package/dist/src/validation/index.js +4 -0
  75. package/dist/src/validation/inference-trace.d.ts +13 -0
  76. package/dist/src/validation/inference-trace.d.ts.map +1 -0
  77. package/dist/src/validation/inference-trace.js +31 -0
  78. package/dist/src/validation/projects-index.d.ts +13 -0
  79. package/dist/src/validation/projects-index.d.ts.map +1 -0
  80. package/dist/src/validation/projects-index.js +37 -0
  81. package/dist/src/validation/testcase.d.ts +13 -0
  82. package/dist/src/validation/testcase.d.ts.map +1 -0
  83. package/dist/src/validation/testcase.js +53 -0
  84. package/dist/test/cli/extract-contract.test.d.ts +2 -0
  85. package/dist/test/cli/extract-contract.test.d.ts.map +1 -0
  86. package/dist/test/cli/extract-contract.test.js +32 -0
  87. package/dist/test/cli/generate-testcase-inference.test.d.ts +2 -0
  88. package/dist/test/cli/generate-testcase-inference.test.d.ts.map +1 -0
  89. package/dist/test/cli/generate-testcase-inference.test.js +63 -0
  90. package/dist/test/cli/generate-testcase-paths.test.d.ts +2 -0
  91. package/dist/test/cli/generate-testcase-paths.test.d.ts.map +1 -0
  92. package/dist/test/cli/generate-testcase-paths.test.js +165 -0
  93. package/dist/test/cli/generate-testcase-requirements.test.d.ts +2 -0
  94. package/dist/test/cli/generate-testcase-requirements.test.d.ts.map +1 -0
  95. package/dist/test/cli/generate-testcase-requirements.test.js +229 -0
  96. package/dist/test/cli/generate-testcase.test.d.ts +2 -0
  97. package/dist/test/cli/generate-testcase.test.d.ts.map +1 -0
  98. package/dist/test/cli/generate-testcase.test.js +130 -0
  99. package/dist/test/cli/index.test.d.ts +2 -0
  100. package/dist/test/cli/index.test.d.ts.map +1 -0
  101. package/dist/test/cli/index.test.js +113 -0
  102. package/dist/test/cli/probe.test.d.ts +2 -0
  103. package/dist/test/cli/probe.test.d.ts.map +1 -0
  104. package/dist/test/cli/probe.test.js +31 -0
  105. package/dist/test/cli/run.test.d.ts +2 -0
  106. package/dist/test/cli/run.test.d.ts.map +1 -0
  107. package/dist/test/cli/run.test.js +149 -0
  108. package/dist/test/cli/spec-boundary.test.d.ts +2 -0
  109. package/dist/test/cli/spec-boundary.test.d.ts.map +1 -0
  110. package/dist/test/cli/spec-boundary.test.js +198 -0
  111. package/dist/test/cli/spec.test.d.ts +2 -0
  112. package/dist/test/cli/spec.test.d.ts.map +1 -0
  113. package/dist/test/cli/spec.test.js +196 -0
  114. package/dist/test/code-list.test.d.ts +2 -0
  115. package/dist/test/code-list.test.d.ts.map +1 -0
  116. package/dist/test/code-list.test.js +39 -0
  117. package/dist/test/crud-contract.test.js +12 -32
  118. package/dist/test/inference/module-hints-inference.test.d.ts +2 -0
  119. package/dist/test/inference/module-hints-inference.test.d.ts.map +1 -0
  120. package/dist/test/inference/module-hints-inference.test.js +25 -0
  121. package/dist/test/inference/source-analysis.test.d.ts +2 -0
  122. package/dist/test/inference/source-analysis.test.d.ts.map +1 -0
  123. package/dist/test/inference/source-analysis.test.js +23 -0
  124. package/dist/test/material-inventory.test.js +13 -0
  125. package/dist/test/observable-pipeline.test.js +12 -3
  126. package/dist/test/stage-context.test.js +145 -13
  127. package/dist/test/util/credentials.test.d.ts +2 -0
  128. package/dist/test/util/credentials.test.d.ts.map +1 -0
  129. package/dist/test/util/credentials.test.js +64 -0
  130. package/dist/test/util/i18n-testcase.test.d.ts +2 -0
  131. package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
  132. package/dist/test/util/i18n-testcase.test.js +119 -0
  133. package/dist/test/util/softlink.test.d.ts +2 -0
  134. package/dist/test/util/softlink.test.d.ts.map +1 -0
  135. package/dist/test/util/softlink.test.js +82 -0
  136. package/dist/test/validation/credentials.test.d.ts +2 -0
  137. package/dist/test/validation/credentials.test.d.ts.map +1 -0
  138. package/dist/test/validation/credentials.test.js +72 -0
  139. package/dist/test/validation/projects-index.test.d.ts +2 -0
  140. package/dist/test/validation/projects-index.test.d.ts.map +1 -0
  141. package/dist/test/validation/projects-index.test.js +48 -0
  142. package/dist/test/validation/testcase.test.d.ts +2 -0
  143. package/dist/test/validation/testcase.test.d.ts.map +1 -0
  144. package/dist/test/validation/testcase.test.js +129 -0
  145. package/docs/README.md +6 -6
  146. package/docs/mvp-usage-guide.md +3 -3
  147. package/package.json +9 -4
  148. package/codex-skill/ep-stage/create-project/SKILL.md +0 -59
  149. package/codex-skill/ep-stage/glue-test/SKILL.md +0 -258
  150. package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +0 -139
  151. package/codex-skill/ep-stage/glue-testcase/SKILL.md +0 -31
  152. package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +0 -67
  153. /package/codex-skill/ep-stage/{glue-testcase → glue-generate-testcase}/examples/observable-testcase.json +0 -0
  154. /package/codex-skill/ep-stage/{glue-test → glue-run-test}/references/gap-review-protocol.md +0 -0
  155. /package/codex-skill/ep-stage/{glue-test → glue-run-test}/references/harness-principles.md +0 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * spec.ts 单元测试(REQ-SPEC-01)。
3
+ *
4
+ * 覆盖:
5
+ * - parseSpecArgs 缺参 / 非法 / -- 分隔符
6
+ * - runSpec:
7
+ * · 缺 contract.json 时报错(引导用户先跑 testcase 子命令);
8
+ * · 正确装配 v1 .spec.ts(含 SKELETON_MARKER_LINE 头部标记 + skeleton 主体);
9
+ * · testcase.json5 不通过 validateTestcase 时抛错(透传 v2 schema 校验)。
10
+ */
11
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
15
+ import { parseSpecArgs, runSpec } from '../../src/cli/spec.js';
16
+ let tmpRoot;
17
+ beforeEach(() => {
18
+ tmpRoot = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-spec-'));
19
+ });
20
+ afterEach(() => {
21
+ // 留 tmpdir 给宿主清理,避免删 fixture 时误命中其他测试。
22
+ });
23
+ function createContract() {
24
+ return {
25
+ contractVersion: 'crud-business-module/v1',
26
+ module: { id: 'zwplace', label: '场所窗口信息管理' },
27
+ pages: {
28
+ list: {
29
+ role: 'list',
30
+ pageId: 'gxhzwplacelist',
31
+ title: '场所窗口信息列表',
32
+ iframeSrcKeyword: 'gxhzwplacelist',
33
+ fields: [
34
+ { name: 'windowname', label: '窗口名称', controlType: 'minitext', required: false, availability: 'auto_extractable', sources: [] },
35
+ ],
36
+ buttons: [],
37
+ grids: [],
38
+ dialogs: [],
39
+ sources: [],
40
+ },
41
+ add: {
42
+ role: 'add',
43
+ pageId: 'gxhzwplaceadd',
44
+ title: '新增场所窗口',
45
+ iframeSrcKeyword: 'gxhzwplaceadd',
46
+ fields: [
47
+ { name: 'windowname', label: '窗口名称', controlType: 'minitext', required: true, availability: 'auto_extractable', sources: [] },
48
+ ],
49
+ buttons: [],
50
+ grids: [],
51
+ dialogs: [],
52
+ sources: [],
53
+ },
54
+ edit: {
55
+ role: 'edit',
56
+ pageId: 'gxhzwplaceedit',
57
+ title: '修改场所窗口',
58
+ iframeSrcKeyword: 'gxhzwplaceedit',
59
+ fields: [
60
+ { name: 'windowname', label: '窗口名称', controlType: 'minitext', required: true, availability: 'auto_extractable', sources: [] },
61
+ ],
62
+ buttons: [],
63
+ grids: [],
64
+ dialogs: [],
65
+ sources: [],
66
+ },
67
+ },
68
+ dataKey: {
69
+ field: 'windowname',
70
+ label: '窗口名称',
71
+ availability: 'derivable',
72
+ generationStrategy: 'timestamp_prefix',
73
+ sources: [],
74
+ },
75
+ searchConditions: [
76
+ {
77
+ field: 'windowname',
78
+ label: '窗口名称',
79
+ component: 'input',
80
+ seedValue: '自动化测试窗口',
81
+ generationStrategy: 'timestamp_prefix',
82
+ sources: [],
83
+ },
84
+ ],
85
+ flows: {
86
+ create: {
87
+ entryButton: { pageRole: 'list', label: '新增', sources: [] },
88
+ targetPageId: 'gxhzwplaceadd',
89
+ saveButton: { pageRole: 'add', label: '保存并关闭', sources: [] },
90
+ overrideFields: [],
91
+ },
92
+ search: {
93
+ searchField: { pageRole: 'list', field: 'windowname', label: '窗口名称', sources: [] },
94
+ submitControl: { pageRole: 'list', label: '查询', sources: [] },
95
+ resultGrid: 'datagrid',
96
+ },
97
+ update: {
98
+ entryAction: { pageRole: 'list', label: '修改', sources: [] },
99
+ targetPageId: 'gxhzwplaceedit',
100
+ updateField: { pageRole: 'edit', field: 'windowname', label: '窗口名称', sources: [] },
101
+ saveButton: { pageRole: 'edit', label: '保存并关闭', sources: [] },
102
+ },
103
+ delete: {
104
+ entryButton: { pageRole: 'list', label: '删除', sources: [] },
105
+ expectedOutcome: 'success_delete',
106
+ confirmControl: { pageRole: 'list', label: '确定', sources: [] },
107
+ },
108
+ },
109
+ assertions: [],
110
+ businessRules: [],
111
+ unresolvedSlots: [],
112
+ };
113
+ }
114
+ function createTestcase() {
115
+ return {
116
+ schemaVersion: 'v2',
117
+ scenario: 'crud.single-page',
118
+ moduleId: 'zwplace',
119
+ moduleName: '场所窗口信息管理',
120
+ menuPath: '业务管理 > 场所窗口',
121
+ requiredSystems: [{ url: '' }],
122
+ requiredRoles: [{ role: 'admin' }],
123
+ coveredActions: [
124
+ { actionId: 'create', kind: '新增', evidence: ['contract=crud-business-module/v1'] },
125
+ ],
126
+ uncoveredCandidates: [],
127
+ cases: [
128
+ {
129
+ caseId: 'zwplace.create',
130
+ title: '新增',
131
+ scenario: 'crud.single-page',
132
+ reviewStatus: 'confirmed',
133
+ requiredRole: 'admin',
134
+ evidence: ['contract=crud-business-module/v1'],
135
+ steps: [],
136
+ assertions: [],
137
+ unresolved: [],
138
+ },
139
+ ],
140
+ reasoningSummary: {
141
+ conclusion: '',
142
+ evidenceChain: [],
143
+ alternatives: [],
144
+ confidence: 'medium',
145
+ risks: [],
146
+ needsHumanReview: true,
147
+ },
148
+ };
149
+ }
150
+ function writeFixtures() {
151
+ const testsDir = path.join(tmpRoot, 'requirement-X');
152
+ mkdirSync(path.join(testsDir, '.ep-stage'), { recursive: true });
153
+ const testcasePath = path.join(testsDir, 'testcase.json5');
154
+ const contractPath = path.join(testsDir, '.ep-stage', 'contract.json');
155
+ writeFileSync(testcasePath, JSON.stringify(createTestcase()), 'utf8');
156
+ writeFileSync(contractPath, JSON.stringify(createContract()), 'utf8');
157
+ return { testcasePath, specOut: path.join(testsDir, 'crud.spec.ts') };
158
+ }
159
+ describe('parseSpecArgs', () => {
160
+ it('缺 --testcase 时抛错', () => {
161
+ expect(() => parseSpecArgs(['--out', '/x'])).toThrow(/缺少 --testcase/);
162
+ });
163
+ it('缺 --out 时抛错', () => {
164
+ expect(() => parseSpecArgs(['--testcase', '/x'])).toThrow(/缺少 --out/);
165
+ });
166
+ it('参数对非法时抛错', () => {
167
+ expect(() => parseSpecArgs(['--testcase'])).toThrow(/无效参数对/);
168
+ });
169
+ it('-- 分隔符被忽略', () => {
170
+ const args = parseSpecArgs(['--', '--testcase', '/a', '--out', '/b']);
171
+ expect(args).toEqual({ testcase: '/a', out: '/b' });
172
+ });
173
+ });
174
+ describe('runSpec', () => {
175
+ it('正常装配 v1 .spec.ts(含 SKELETON_MARKER_LINE)', () => {
176
+ const { testcasePath, specOut } = writeFixtures();
177
+ const result = runSpec({ testcase: testcasePath, out: specOut });
178
+ expect(result).toEqual({ specPath: specOut, version: 'v1' });
179
+ const body = readFileSync(specOut, 'utf8');
180
+ expect(body).toMatch(/^\/\/ === 胶水模式生成(基于 contract \+ coveredActions)===/);
181
+ // skeleton 主体(来自 generateStageSkeletonCrudSpec)应含 Playwright import 或 test 入口
182
+ expect(body.length).toBeGreaterThan(100);
183
+ });
184
+ it('缺 .ep-stage/contract.json 时抛错引导', () => {
185
+ const testsDir = path.join(tmpRoot, 'requirement-Y');
186
+ mkdirSync(testsDir, { recursive: true });
187
+ const testcasePath = path.join(testsDir, 'testcase.json5');
188
+ writeFileSync(testcasePath, JSON.stringify(createTestcase()), 'utf8');
189
+ expect(() => runSpec({ testcase: testcasePath, out: path.join(testsDir, 'out.spec.ts') })).toThrow(/未找到.*contract\.json/);
190
+ });
191
+ it('testcase.json5 schemaVersion 不是 v2 时由 validateTestcase 抛错', () => {
192
+ const { testcasePath, specOut } = writeFixtures();
193
+ writeFileSync(testcasePath, JSON.stringify({ schemaVersion: 'v1' }), 'utf8');
194
+ expect(() => runSpec({ testcase: testcasePath, out: specOut })).toThrow(/schemaVersion 不是 v2/);
195
+ });
196
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=code-list.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-list.test.d.ts","sourceRoot":"","sources":["../../test/code-list.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ import { mkdtempSync, writeFileSync } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { extractCodeListSummary } from '../src/index.js';
6
+ /**
7
+ * code_list.md 解析器单元测试。
8
+ *
9
+ * 重点覆盖菜单字段的人工读写约定:
10
+ * - 中文键「菜单」为 code_list.md 人工首选(与「整理目录」同风格),支持 blockquote 与裸行;
11
+ * - 英文键 menu / menuPath 向后兼容(编码内部字段名仍为 menu)。
12
+ */
13
+ describe('code_list.md 菜单字段解析', () => {
14
+ /** 在临时目录写一份最小 code_list.md 并返回其解析摘要。 */
15
+ function summarizeMenu(lines) {
16
+ const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-codelist-'));
17
+ const codeListPath = path.join(root, 'code_list.md');
18
+ writeFileSync(codeListPath, lines.join('\n'), 'utf8');
19
+ return extractCodeListSummary(codeListPath).menu;
20
+ }
21
+ it('中文键「菜单」blockquote 形式(无反引号)', () => {
22
+ expect(summarizeMenu(['# 场所窗口信息管理代码清单', '> 菜单:场所窗口信息管理>场所窗口信息列表'])).toBe('场所窗口信息管理>场所窗口信息列表');
23
+ });
24
+ it('中文键「菜单」blockquote 形式(反引号包裹)', () => {
25
+ expect(summarizeMenu(['# 场所窗口信息管理代码清单', '> 菜单:`场所窗口信息管理>场所窗口信息列表`'])).toBe('场所窗口信息管理>场所窗口信息列表');
26
+ });
27
+ it('中文键「菜单」裸行形式', () => {
28
+ expect(summarizeMenu(['# 场所窗口信息管理代码清单', '菜单: 场所窗口信息管理>场所窗口信息列表'])).toBe('场所窗口信息管理>场所窗口信息列表');
29
+ });
30
+ it('英文键 menu 向后兼容', () => {
31
+ expect(summarizeMenu(['# 场所窗口信息管理代码清单', 'menu: 场所窗口信息管理>场所窗口信息列表'])).toBe('场所窗口信息管理>场所窗口信息列表');
32
+ });
33
+ it('英文键 menuPath 向后兼容', () => {
34
+ expect(summarizeMenu(['# 场所窗口信息管理代码清单', 'menuPath: 场所窗口信息管理>场所窗口信息列表'])).toBe('场所窗口信息管理>场所窗口信息列表');
35
+ });
36
+ it('未声明菜单时返回 undefined', () => {
37
+ expect(summarizeMenu(['# 场所窗口信息管理代码清单'])).toBeUndefined();
38
+ });
39
+ });
@@ -11,7 +11,7 @@ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
11
11
  const zwplaceDocs = join(repoRoot, 'knowledge-project/epoint-web-v9.5.2/_docs');
12
12
  const zwplaceWebapp = join(repoRoot, 'knowledge-project/epoint-web-v9.5.2/src/main/webapp/perpage/zwplace');
13
13
  const zwplaceActions = join(repoRoot, 'knowledge-project/epoint-web-v9.5.2/src/main/java/com/epoint/zwplace/action');
14
- const codeListPath = join(repoRoot, 'knowledge-project/epoint-web-v9.5.2/_docs/code_list.md');
14
+ const codeListPath = join(repoRoot, 'knowledge-project/epoint-web-v9.5.2/_docs/014-测试需求10-20260622/code_list.md');
15
15
  function readZwplaceSpecFixture() {
16
16
  return {
17
17
  path: join(zwplaceDocs, 'spec.yaml'),
@@ -743,37 +743,6 @@ describe('stage-skeleton Playwright generator', () => {
743
743
  expect(compileResult.status).toBe(0);
744
744
  expect(compileResult.stderr).toBe('');
745
745
  });
746
- it('generates a stage-skeleton spec from CLI', () => {
747
- const outDir = mkdtempSync(join(tmpdir(), 'test-glue-playwright-'));
748
- const contractFile = join(outDir, 'zwplace.crud.contract.json');
749
- const specFile = join(outDir, 'zwplace.spec.ts');
750
- try {
751
- writeFileSync(contractFile, JSON.stringify(buildZwplaceContractWithHints(), null, 2), 'utf8');
752
- execFileSync('pnpm', [
753
- 'generate:playwright-tests',
754
- '--',
755
- '--contract',
756
- contractFile,
757
- '--out',
758
- specFile,
759
- '--menu',
760
- '场所窗口信息管理>场所窗口信息列表'
761
- ], { cwd: packageRoot, stdio: 'pipe' });
762
- const script = readFileSync(specFile, 'utf8');
763
- expect(script).toContain("import { CrudPage, ExportPage, LoginPage, MenuPage, type CrudFlowSlots, type ExportFlowSlots, type CrudWorkflowData } from '../skeletons';");
764
- expect(script).toContain("import { GlueWorkflowRecorder, runRecordedWorkflow, type GlueWorkflowRecordInput } from '../report/glue-report';");
765
- expect(script).toContain('const crudSlots: CrudFlowSlots =');
766
- expect(script).toContain("workflowId: 'zwplace.create'");
767
- expect(script).toContain('workflowData = crudPage.prepareWorkflowData();');
768
- expect(script).not.toContain('runCrudFlow');
769
- const compileResult = compileGlueTemplateSpec(script);
770
- expect(compileResult.status).toBe(0);
771
- expect(compileResult.stderr).toBe('');
772
- }
773
- finally {
774
- rmSync(outDir, { recursive: true, force: true });
775
- }
776
- });
777
746
  it('prefers contract locators and result grid ids over hard-coded zwplace fallbacks', () => {
778
747
  const contract = buildZwplaceContractWithHints();
779
748
  contract.flows.create.entryButton.locator = 'css=[data-create]';
@@ -816,4 +785,15 @@ describe('stage-skeleton Playwright generator', () => {
816
785
  menuNavigation: '场所窗口信息管理>场所窗口信息列表'
817
786
  })).toThrow('当前数据生成策略无法自动映射到 CrudFlowSlots: external_data_pool');
818
787
  });
788
+ it('defaults human_required generation strategy to timestamp_prefix (CRUD 冒烟标配,避免 tester 未喂策略时硬死锁)', () => {
789
+ const contract = buildZwplaceContractWithHints();
790
+ contract.dataKey.generationStrategy = 'human_required';
791
+ contract.searchConditions[0].generationStrategy = 'human_required';
792
+ const script = generateStageSkeletonCrudSpec({
793
+ contract,
794
+ menuNavigation: '场所窗口信息管理>场所窗口信息列表'
795
+ });
796
+ expect(script).toContain("strategy: 'timestamp_prefix'");
797
+ expect(script).not.toContain('human_required');
798
+ });
819
799
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=module-hints-inference.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-hints-inference.test.d.ts","sourceRoot":"","sources":["../../../test/inference/module-hints-inference.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import { dirname, join, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { extractCodeListSummary, extractHtmlPage, extractJavaAction } from '../../src/index.js';
5
+ import { buildSourceAnalysis } from '../../src/inference/source-analysis.js';
6
+ import { inferModuleHintsFromMaterials } from '../../src/inference/module-hints-inference.js';
7
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
8
+ const knowledgeRoot = join(repoRoot, 'knowledge-project/epoint-web-v9.5.2');
9
+ const requirementDir = join(knowledgeRoot, '_docs/014-测试需求10-20260622');
10
+ const webappDir = join(knowledgeRoot, 'src/main/webapp/perpage/zwplace');
11
+ const javaActionsDir = join(knowledgeRoot, 'src/main/java/com/epoint/zwplace/action');
12
+ describe('inferModuleHintsFromMaterials', () => {
13
+ it('不依赖 module-hints 文件也能推理 zwplace 的基础骨架槽位', () => {
14
+ const codeListPath = join(requirementDir, 'code_list.md');
15
+ const codeList = extractCodeListSummary(codeListPath);
16
+ const pages = ['gxhzwplacelist.html', 'gxhzwplaceadd.html', 'gxhzwplaceedit.html'].map((file) => extractHtmlPage(join(webappDir, file)));
17
+ const actions = ['GxhZwPlaceListAction.java', 'GxhZwPlaceAddAction.java', 'GxhZwPlaceEditAction.java'].map((file) => extractJavaAction(join(javaActionsDir, file)));
18
+ const sourceAnalysis = buildSourceAnalysis({ codeListPath, docsDir: requirementDir, codeList, pages, actions });
19
+ const result = inferModuleHintsFromMaterials({ sourceAnalysis, pages, actions });
20
+ expect(result.hints.dataKey?.selectedField).toBe('placename');
21
+ expect(result.hints.searchConditions?.[0]?.field).toBe('placename');
22
+ expect(result.inference.mode).toBe('inferred');
23
+ expect(result.inference.customWorkflows.map((workflow) => workflow.label)).toContain('同步窗口管理系统');
24
+ });
25
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=source-analysis.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-analysis.test.d.ts","sourceRoot":"","sources":["../../../test/inference/source-analysis.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import { dirname, join, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { extractCodeListSummary, extractHtmlPage, extractJavaAction } from '../../src/index.js';
5
+ import { buildSourceAnalysis } from '../../src/inference/source-analysis.js';
6
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
7
+ const knowledgeRoot = join(repoRoot, 'knowledge-project/epoint-web-v9.5.2');
8
+ const requirementDir = join(knowledgeRoot, '_docs/014-测试需求10-20260622');
9
+ const webappDir = join(knowledgeRoot, 'src/main/webapp/perpage/zwplace');
10
+ const javaActionsDir = join(knowledgeRoot, 'src/main/java/com/epoint/zwplace/action');
11
+ describe('buildSourceAnalysis', () => {
12
+ it('标记 014 真实物料为 prd+html+java 源分析', () => {
13
+ const codeListPath = join(requirementDir, 'code_list.md');
14
+ const codeList = extractCodeListSummary(codeListPath);
15
+ const pages = ['gxhzwplacelist.html', 'gxhzwplaceadd.html'].map((file) => extractHtmlPage(join(webappDir, file)));
16
+ const actions = ['GxhZwPlaceListAction.java', 'GxhZwPlaceAddAction.java'].map((file) => extractJavaAction(join(javaActionsDir, file)));
17
+ const analysis = buildSourceAnalysis({ codeListPath, docsDir: requirementDir, codeList, pages, actions });
18
+ expect(analysis.mode).toBe('prd+html+java');
19
+ expect(analysis.sources.map((source) => source.kind)).toContain('prd_markdown');
20
+ expect(analysis.uiSignals.length).toBeGreaterThan(0);
21
+ expect(analysis.backendSignals.length).toBeGreaterThan(0);
22
+ });
23
+ });
@@ -21,6 +21,7 @@ describe('material inventory resolver', () => {
21
21
  writeFileSync(hintsPath, '{}');
22
22
  writeFileSync(codeListPath, [
23
23
  '# 场所窗口信息管理代码清单',
24
+ `> 整理目录:\`${root}\``,
24
25
  'menu: 场所窗口信息管理>场所窗口信息列表',
25
26
  '`_docs/003-场所窗口信息管理-20260518/spec.yaml`',
26
27
  '`src/main/webapp/perpage/zwplace/gxhzwplacelist.html`',
@@ -53,6 +54,18 @@ describe('material inventory resolver', () => {
53
54
  });
54
55
  expect(result.gate.status).toBe('confirmed');
55
56
  });
57
+ it('does not infer knowledgeRoot from _docs hierarchy when code_list lacks structured root info', () => {
58
+ const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-no-root-'));
59
+ const codeListPath = path.join(root, '_docs', '014-测试需求10-20260622', 'code_list.md');
60
+ mkdirSync(path.dirname(codeListPath), { recursive: true });
61
+ writeFileSync(codeListPath, [
62
+ '# 场所窗口信息管理代码清单',
63
+ '`src/main/webapp/perpage/zwplace/gxhzwplacelist.html`',
64
+ ].join('\n'), 'utf8');
65
+ const codeList = extractCodeListSummary(codeListPath);
66
+ expect(codeList.knowledgeRoot).toBeUndefined();
67
+ expect(codeList.knowledgeRootSource).toBe('none');
68
+ });
56
69
  it('stops at material gate when webapp or java actions cannot be resolved', () => {
57
70
  const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-missing-'));
58
71
  const codeListPath = path.join(root, 'code_list.md');
@@ -75,7 +75,7 @@ describe('observable pipeline material inventory', () => {
75
75
  },
76
76
  ]);
77
77
  });
78
- it('resolves paths relative to knowledge root when code_list is under _docs', () => {
78
+ it('resolves paths relative to the 整理目录 declared inside code_list', () => {
79
79
  const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-knowledge-root-'));
80
80
  const docsDir = path.join(root, '_docs');
81
81
  const htmlPath = path.join(root, 'src', 'main', 'webapp', 'perpage', 'zwplace', 'gxhzwplacelist.html');
@@ -83,9 +83,18 @@ describe('observable pipeline material inventory', () => {
83
83
  mkdirSync(path.dirname(htmlPath), { recursive: true });
84
84
  mkdirSync(docsDir, { recursive: true });
85
85
  writeFileSync(htmlPath, '<html></html>');
86
- writeFileSync(codeListPath, '| HTML | `src/main/webapp/perpage/zwplace/gxhzwplacelist.html` |', 'utf8');
86
+ writeFileSync(codeListPath, [
87
+ `> 整理目录:\`${root}\``,
88
+ '',
89
+ '| HTML | `src/main/webapp/perpage/zwplace/gxhzwplacelist.html` |',
90
+ ].join('\n'), 'utf8');
87
91
  const summary = extractCodeListSummary(codeListPath);
88
- expect(summary.materialRefs[0]).toMatchObject({
92
+ expect(summary.knowledgeRoot).toBe(root);
93
+ expect(summary.knowledgeRootSource).toBe('code_list');
94
+ // `> 整理目录:` 声明行因含反引号路径会被物料提取器多读出一条 other 引用,
95
+ // 故用 find 定位 HTML 引用,而非依赖数组首位。
96
+ const htmlRef = summary.materialRefs.find((ref) => ref.kind === 'html');
97
+ expect(htmlRef).toMatchObject({
89
98
  kind: 'html',
90
99
  path: htmlPath,
91
100
  exists: true,