@epoint-testtech/ep-stage-skill 0.0.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/SKILL.md +27 -0
  2. package/codex-skill/ep-stage/create-project/SKILL.md +59 -0
  3. package/codex-skill/ep-stage/glue-test/SKILL.md +258 -0
  4. package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +139 -0
  5. package/codex-skill/ep-stage/glue-test/references/gap-review-protocol.md +43 -0
  6. package/codex-skill/ep-stage/glue-test/references/harness-principles.md +46 -0
  7. package/codex-skill/ep-stage/glue-test/scripts/generate-crud-spec.mjs +149 -0
  8. package/codex-skill/ep-stage/glue-testcase/SKILL.md +31 -0
  9. package/codex-skill/ep-stage/glue-testcase/examples/observable-testcase.json +40 -0
  10. package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +67 -0
  11. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +27 -0
  12. package/codex-skill/ep-stage/scripts/validate-skill.mjs +73 -0
  13. package/dist/src/capability/coverage-diff.d.ts +34 -0
  14. package/dist/src/capability/coverage-diff.d.ts.map +1 -0
  15. package/dist/src/capability/coverage-diff.js +91 -0
  16. package/dist/src/capability/page-structure.d.ts +31 -0
  17. package/dist/src/capability/page-structure.d.ts.map +1 -0
  18. package/dist/src/capability/page-structure.js +50 -0
  19. package/dist/src/capability/scenario-inference.d.ts +36 -0
  20. package/dist/src/capability/scenario-inference.d.ts.map +1 -0
  21. package/dist/src/capability/scenario-inference.js +114 -0
  22. package/dist/src/cli/generate-crud-contract.d.ts +2 -0
  23. package/dist/src/cli/generate-crud-contract.d.ts.map +1 -0
  24. package/dist/src/cli/generate-crud-contract.js +77 -0
  25. package/dist/src/cli/generate-playwright-tests.d.ts +30 -0
  26. package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -0
  27. package/dist/src/cli/generate-playwright-tests.js +81 -0
  28. package/dist/src/cli/run-gap-pipeline.d.ts +256 -0
  29. package/dist/src/cli/run-gap-pipeline.d.ts.map +1 -0
  30. package/dist/src/cli/run-gap-pipeline.js +1468 -0
  31. package/dist/src/context/stage-context.d.ts +63 -0
  32. package/dist/src/context/stage-context.d.ts.map +1 -0
  33. package/dist/src/context/stage-context.js +297 -0
  34. package/dist/src/contracts/crud-business-module.d.ts +645 -0
  35. package/dist/src/contracts/crud-business-module.d.ts.map +1 -0
  36. package/dist/src/contracts/crud-business-module.js +1 -0
  37. package/dist/src/contracts/gap-inference.d.ts +213 -0
  38. package/dist/src/contracts/gap-inference.d.ts.map +1 -0
  39. package/dist/src/contracts/gap-inference.js +11 -0
  40. package/dist/src/contracts/observable-chain.d.ts +250 -0
  41. package/dist/src/contracts/observable-chain.d.ts.map +1 -0
  42. package/dist/src/contracts/observable-chain.js +1 -0
  43. package/dist/src/extractors/code-list.d.ts +40 -0
  44. package/dist/src/extractors/code-list.d.ts.map +1 -0
  45. package/dist/src/extractors/code-list.js +225 -0
  46. package/dist/src/extractors/html-page.d.ts +67 -0
  47. package/dist/src/extractors/html-page.d.ts.map +1 -0
  48. package/dist/src/extractors/html-page.js +195 -0
  49. package/dist/src/extractors/java-action.d.ts +8 -0
  50. package/dist/src/extractors/java-action.d.ts.map +1 -0
  51. package/dist/src/extractors/java-action.js +53 -0
  52. package/dist/src/extractors/spec-yaml.d.ts +28 -0
  53. package/dist/src/extractors/spec-yaml.d.ts.map +1 -0
  54. package/dist/src/extractors/spec-yaml.js +29 -0
  55. package/dist/src/gap-planner/action-candidates.d.ts +9 -0
  56. package/dist/src/gap-planner/action-candidates.d.ts.map +1 -0
  57. package/dist/src/gap-planner/action-candidates.js +66 -0
  58. package/dist/src/gap-planner/list-gap-workflows.d.ts +17 -0
  59. package/dist/src/gap-planner/list-gap-workflows.d.ts.map +1 -0
  60. package/dist/src/gap-planner/list-gap-workflows.js +47 -0
  61. package/dist/src/gap-planner/plan-agent-workflows.d.ts +26 -0
  62. package/dist/src/gap-planner/plan-agent-workflows.d.ts.map +1 -0
  63. package/dist/src/gap-planner/plan-agent-workflows.js +116 -0
  64. package/dist/src/gap-planner/skeleton-coverage.d.ts +9 -0
  65. package/dist/src/gap-planner/skeleton-coverage.d.ts.map +1 -0
  66. package/dist/src/gap-planner/skeleton-coverage.js +41 -0
  67. package/dist/src/gap-planner/stable-id.d.ts +16 -0
  68. package/dist/src/gap-planner/stable-id.d.ts.map +1 -0
  69. package/dist/src/gap-planner/stable-id.js +19 -0
  70. package/dist/src/generalization/generalization-eval.d.ts +71 -0
  71. package/dist/src/generalization/generalization-eval.d.ts.map +1 -0
  72. package/dist/src/generalization/generalization-eval.js +53 -0
  73. package/dist/src/generators/agent-inferred-workflow-script.d.ts +22 -0
  74. package/dist/src/generators/agent-inferred-workflow-script.d.ts.map +1 -0
  75. package/dist/src/generators/agent-inferred-workflow-script.js +230 -0
  76. package/dist/src/generators/stage-skeleton-script.d.ts +107 -0
  77. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -0
  78. package/dist/src/generators/stage-skeleton-script.js +607 -0
  79. package/dist/src/index.d.ts +52 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +26 -0
  82. package/dist/src/material/material-inventory.d.ts +92 -0
  83. package/dist/src/material/material-inventory.d.ts.map +1 -0
  84. package/dist/src/material/material-inventory.js +191 -0
  85. package/dist/src/normalizers/crud-contract.d.ts +107 -0
  86. package/dist/src/normalizers/crud-contract.d.ts.map +1 -0
  87. package/dist/src/normalizers/crud-contract.js +1068 -0
  88. package/dist/src/testcase/testcase-generator.d.ts +43 -0
  89. package/dist/src/testcase/testcase-generator.d.ts.map +1 -0
  90. package/dist/src/testcase/testcase-generator.js +152 -0
  91. package/dist/src/testcase/testcase-skeleton.d.ts +91 -0
  92. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -0
  93. package/dist/src/testcase/testcase-skeleton.js +121 -0
  94. package/dist/src/testcase/testcase-spec-assembly.d.ts +11 -0
  95. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -0
  96. package/dist/src/testcase/testcase-spec-assembly.js +24 -0
  97. package/dist/src/trace/review-summary.d.ts +17 -0
  98. package/dist/src/trace/review-summary.d.ts.map +1 -0
  99. package/dist/src/trace/review-summary.js +34 -0
  100. package/dist/src/trace/trace-writer.d.ts +17 -0
  101. package/dist/src/trace/trace-writer.d.ts.map +1 -0
  102. package/dist/src/trace/trace-writer.js +81 -0
  103. package/dist/test/crud-contract.test.d.ts +2 -0
  104. package/dist/test/crud-contract.test.d.ts.map +1 -0
  105. package/dist/test/crud-contract.test.js +819 -0
  106. package/dist/test/gap-inference.test.d.ts +2 -0
  107. package/dist/test/gap-inference.test.d.ts.map +1 -0
  108. package/dist/test/gap-inference.test.js +597 -0
  109. package/dist/test/generalization.test.d.ts +2 -0
  110. package/dist/test/generalization.test.d.ts.map +1 -0
  111. package/dist/test/generalization.test.js +73 -0
  112. package/dist/test/material-inventory.test.d.ts +2 -0
  113. package/dist/test/material-inventory.test.d.ts.map +1 -0
  114. package/dist/test/material-inventory.test.js +141 -0
  115. package/dist/test/observable-chain.test.d.ts +2 -0
  116. package/dist/test/observable-chain.test.d.ts.map +1 -0
  117. package/dist/test/observable-chain.test.js +123 -0
  118. package/dist/test/observable-pipeline.test.d.ts +2 -0
  119. package/dist/test/observable-pipeline.test.d.ts.map +1 -0
  120. package/dist/test/observable-pipeline.test.js +461 -0
  121. package/dist/test/page-structure.test.d.ts +2 -0
  122. package/dist/test/page-structure.test.d.ts.map +1 -0
  123. package/dist/test/page-structure.test.js +45 -0
  124. package/dist/test/scenario-inference.test.d.ts +2 -0
  125. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  126. package/dist/test/scenario-inference.test.js +73 -0
  127. package/dist/test/stage-context.test.d.ts +2 -0
  128. package/dist/test/stage-context.test.d.ts.map +1 -0
  129. package/dist/test/stage-context.test.js +263 -0
  130. package/dist/test/testcase-generator.test.d.ts +2 -0
  131. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  132. package/dist/test/testcase-generator.test.js +276 -0
  133. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  134. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  135. package/dist/test/testcase-skeleton.test.js +185 -0
  136. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  137. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  138. package/dist/test/testcase-spec-assembly.test.js +105 -0
  139. package/dist/vitest.config.d.ts +3 -0
  140. package/dist/vitest.config.d.ts.map +1 -0
  141. package/dist/vitest.config.js +7 -0
  142. package/docs/README.md +134 -0
  143. package/docs/mvp-usage-guide.md +298 -0
  144. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -0
  145. package/examples/schemeresource.module-hints.json +38 -0
  146. package/examples/schemeresource.observable.code_list.md +37 -0
  147. package/examples/zwplace-observable-docs/zwplace.context.md +16 -0
  148. package/examples/zwplace-placecategory-validation.json +29 -0
  149. package/examples/zwplace.module-hints.json +69 -0
  150. package/examples/zwplace.observable.code_list.md +37 -0
  151. package/package.json +38 -0
@@ -0,0 +1,225 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ function unique(values) {
4
+ return Array.from(new Set(values));
5
+ }
6
+ function sectionBetween(content, start, end) {
7
+ const startMatch = content.match(start);
8
+ if (startMatch?.index === undefined)
9
+ return '';
10
+ const rest = content.slice(startMatch.index);
11
+ const endMatch = rest.slice(startMatch[0].length).match(end);
12
+ return endMatch?.index === undefined
13
+ ? rest
14
+ : rest.slice(0, startMatch[0].length + endMatch.index);
15
+ }
16
+ function methodsFromTable(section) {
17
+ return unique(Array.from(section.matchAll(/`([A-Za-z_][A-Za-z0-9_]*)\([^`]*\)`/g), (match) => match[1])
18
+ .concat(Array.from(section.matchAll(/\|\s*`([A-Za-z_][A-Za-z0-9_]*)`\s*\|/g), (match) => match[1])));
19
+ }
20
+ function subsectionBlocks(section, heading) {
21
+ const headings = Array.from(section.matchAll(heading));
22
+ return headings.map((match, index) => {
23
+ const startIndex = match.index ?? 0;
24
+ const endIndex = headings[index + 1]?.index ?? section.length;
25
+ return section.slice(startIndex, endIndex);
26
+ });
27
+ }
28
+ /**
29
+ * 根据相对路径后缀判断物料类别。
30
+ *
31
+ * @param rawPath - markdown 中提取的原始相对路径。
32
+ * @returns 对应的物料 kind。
33
+ */
34
+ function materialKind(rawPath) {
35
+ if (rawPath.endsWith('.html'))
36
+ return 'html';
37
+ if (rawPath.endsWith('Action.java'))
38
+ return 'java_action';
39
+ if (rawPath.endsWith('spec.yaml') || rawPath.endsWith('.md'))
40
+ return 'spec';
41
+ if (rawPath.endsWith('.module-hints.json'))
42
+ return 'module_hints';
43
+ return 'other';
44
+ }
45
+ /**
46
+ * 解析 code_list.md 中声明的 knowledgeRoot。
47
+ *
48
+ * @param content - code_list.md 文本内容。
49
+ * @param filePath - code_list.md 文件路径。
50
+ * @returns 归一化后的 knowledgeRoot;未声明时返回 undefined。
51
+ */
52
+ function extractDeclaredKnowledgeRoot(content, filePath) {
53
+ const match = extractScalar(content, 'knowledgeRoot');
54
+ if (!match?.[1]) {
55
+ return undefined;
56
+ }
57
+ const rawValue = cleanScalarValue(match[1]);
58
+ return path.isAbsolute(rawValue) ? rawValue : path.resolve(path.dirname(filePath), rawValue);
59
+ }
60
+ /**
61
+ * 提取 code_list.md 顶层标量字段。
62
+ *
63
+ * @param content - code_list.md 文本内容。
64
+ * @param key - 字段名。
65
+ * @returns 正则匹配结果。
66
+ */
67
+ function extractScalar(content, key) {
68
+ return content.match(new RegExp(`^\\s*${key}\\s*:\\s*(.+?)\\s*$`, 'm'));
69
+ }
70
+ /**
71
+ * 清理 code_list.md 顶层标量字段值。
72
+ *
73
+ * @param value - 原始字段值。
74
+ * @returns 去掉首尾引号和空白后的值。
75
+ */
76
+ function cleanScalarValue(value) {
77
+ return value.trim().replace(/^["']|["']$/g, '');
78
+ }
79
+ /**
80
+ * 解析 code_list.md 中声明的菜单路由。
81
+ *
82
+ * @param content - code_list.md 文本内容。
83
+ * @returns 菜单路由;未声明时返回 undefined。
84
+ */
85
+ function extractDeclaredMenu(content) {
86
+ const match = extractScalar(content, 'menu') ?? extractScalar(content, 'menuPath');
87
+ return match?.[1] ? cleanScalarValue(match[1]) : undefined;
88
+ }
89
+ /**
90
+ * 解析本次 code_list 应使用的上游物料根路径。
91
+ *
92
+ * @param content - code_list.md 文本内容。
93
+ * @param filePath - code_list.md 文件路径。
94
+ * @param options - CLI 或 stage-context 传入的显式 knowledgeRoot。
95
+ * @returns knowledgeRoot 及其来源。
96
+ */
97
+ function resolveKnowledgeRoot(content, filePath, options) {
98
+ const declaredKnowledgeRoot = extractDeclaredKnowledgeRoot(content, filePath);
99
+ if (declaredKnowledgeRoot) {
100
+ if (!existsSync(declaredKnowledgeRoot)) {
101
+ throw new Error(`code_list.md 声明的 knowledgeRoot 不存在:${declaredKnowledgeRoot}`);
102
+ }
103
+ return {
104
+ knowledgeRoot: declaredKnowledgeRoot,
105
+ source: 'code_list',
106
+ };
107
+ }
108
+ if (options.knowledgeRoot) {
109
+ return {
110
+ knowledgeRoot: path.resolve(options.knowledgeRoot),
111
+ source: 'input',
112
+ };
113
+ }
114
+ const codeListDir = path.dirname(filePath);
115
+ if (path.basename(codeListDir) === '_docs') {
116
+ return {
117
+ knowledgeRoot: path.dirname(codeListDir),
118
+ source: 'inferred',
119
+ };
120
+ }
121
+ return {
122
+ source: 'none',
123
+ };
124
+ }
125
+ /**
126
+ * 按 knowledge-root / code_list 所在目录 / `_docs/` 父目录的顺序产生候选根目录。
127
+ *
128
+ * @param filePath - code_list.md 的绝对路径。
129
+ * @param knowledgeRoot - 显式知识库根(可选)。
130
+ * @returns 按优先级排序的根目录候选列表。
131
+ */
132
+ function materialRoots(filePath, knowledgeRoot) {
133
+ const codeListDir = path.dirname(filePath);
134
+ return [
135
+ ...(knowledgeRoot ? [knowledgeRoot] : []),
136
+ codeListDir,
137
+ ...(path.basename(codeListDir) === '_docs' ? [path.dirname(codeListDir)] : []),
138
+ ];
139
+ }
140
+ /**
141
+ * 把相对路径解析为绝对路径,优先返回真实存在的候选。
142
+ *
143
+ * @param rawPath - markdown 中提取的原始相对路径。
144
+ * @param filePath - code_list.md 的绝对路径。
145
+ * @param knowledgeRoot - 显式知识库根(可选)。
146
+ * @returns 解析后的绝对路径,若所有候选均不存在则取最高优先级候选。
147
+ */
148
+ function resolveMaterialPath(rawPath, filePath, knowledgeRoot) {
149
+ if (path.isAbsolute(rawPath)) {
150
+ return rawPath;
151
+ }
152
+ const candidates = materialRoots(filePath, knowledgeRoot).map((root) => path.resolve(root, rawPath));
153
+ return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
154
+ }
155
+ /**
156
+ * 从 markdown 中钻探所有反引号包裹的物料路径,并解析为 `ExtractedMaterialRef`。
157
+ *
158
+ * @param content - code_list.md 文本内容。
159
+ * @param filePath - code_list.md 的绝对路径。
160
+ * @returns 去重后的物料引用列表,保持 markdown 中出现顺序。
161
+ */
162
+ function extractMaterialRefs(content, filePath, options = {}) {
163
+ const rawPaths = unique(Array.from(content.matchAll(/`([^`]+)`/g), (match) => match[1])
164
+ .filter((item) => item.includes('/') && !item.includes('(')));
165
+ return rawPaths.map((rawPath) => {
166
+ const resolvedPath = resolveMaterialPath(rawPath, filePath, options.knowledgeRoot);
167
+ return {
168
+ kind: materialKind(rawPath),
169
+ rawPath,
170
+ path: resolvedPath,
171
+ exists: existsSync(resolvedPath),
172
+ };
173
+ });
174
+ }
175
+ function extractActionMethods(content) {
176
+ const result = {};
177
+ const actionSection = sectionBetween(content, /^### 2\.1 Action层/m, /^### 2\.2 /m);
178
+ const blocks = subsectionBlocks(actionSection, /^####\s+\d+\.\d+\.\d+\s+Gxh\w+Action\.java/mg);
179
+ for (const block of blocks) {
180
+ const classMatch = block.match(/^####\s+\d+\.\d+\.\d+\s+(Gxh\w+Action)\.java/m);
181
+ if (!classMatch)
182
+ continue;
183
+ result[classMatch[1]] = methodsFromTable(block);
184
+ }
185
+ return result;
186
+ }
187
+ /**
188
+ * 解析 code_list.md 并提取上游物料摘要。
189
+ *
190
+ * @param filePath - code_list.md 文件路径。
191
+ * @param options - 可选的 knowledgeRoot,用于 code_list 不在知识库根目录下时解析物料路径。
192
+ * @returns code_list 摘要。
193
+ */
194
+ export function extractCodeListSummary(filePath, options = {}) {
195
+ const content = readFileSync(filePath, 'utf8');
196
+ const knowledgeRootResolution = resolveKnowledgeRoot(content, filePath, options);
197
+ const titleMatch = content.match(/^#\s+(.+?)代码清单\s*$/m);
198
+ const menu = extractDeclaredMenu(content);
199
+ const pages = unique(Array.from(content.matchAll(/`src\/main\/webapp\/perpage\/[^`/]+\/([^`]+\.html)`/g), (match) => match[1]));
200
+ const actionMethods = extractActionMethods(content);
201
+ const serviceSection = sectionBetween(content, /^### 2\.2 Service层/m, /^### 2\.3 /m);
202
+ const daoSection = sectionBetween(content, /^#### 2\.2\.2 /m, /^### 2\.3 /m);
203
+ const entitySection = sectionBetween(content, /^### 2\.3 Entity层/m, /^## 三、/m);
204
+ const serviceMethods = methodsFromTable(serviceSection);
205
+ const daoMethods = methodsFromTable(daoSection);
206
+ const entityFields = unique(Array.from(entitySection.matchAll(/^\|\s*([a-z][A-Za-z0-9_]*)\s*\|/gm), (match) => match[1]));
207
+ const methods = unique(Object.values(actionMethods).flat());
208
+ return {
209
+ path: filePath,
210
+ knowledgeRoot: knowledgeRootResolution.knowledgeRoot,
211
+ knowledgeRootSource: knowledgeRootResolution.source,
212
+ menu,
213
+ moduleLabel: titleMatch?.[1]?.trim() ?? '',
214
+ pages,
215
+ methods,
216
+ actionMethods,
217
+ serviceMethods,
218
+ daoMethods,
219
+ entityFields,
220
+ materialRefs: extractMaterialRefs(content, filePath, {
221
+ ...options,
222
+ knowledgeRoot: knowledgeRootResolution.knowledgeRoot,
223
+ }),
224
+ };
225
+ }
@@ -0,0 +1,67 @@
1
+ export type ExtractedHtmlField = {
2
+ name: string;
3
+ label: string;
4
+ controlType: string;
5
+ bind?: string;
6
+ required: boolean;
7
+ maxLength?: number;
8
+ locator: string;
9
+ };
10
+ export type ExtractedHtmlButton = {
11
+ id: string;
12
+ label: string;
13
+ onclick?: string;
14
+ locator: string;
15
+ };
16
+ export type ExtractedHtmlDialog = {
17
+ trigger: string;
18
+ title: string;
19
+ pageId: string;
20
+ pagePath: string;
21
+ };
22
+ export type ExtractedHtmlGridColumn = {
23
+ field: string;
24
+ headerText: string;
25
+ columnType: string;
26
+ };
27
+ export type ExtractedHtmlRowAction = {
28
+ label: string;
29
+ onclick?: string;
30
+ locator: string;
31
+ };
32
+ export type ExtractedHtmlGrid = {
33
+ id: string;
34
+ columns: ExtractedHtmlGridColumn[];
35
+ dataColumns: ExtractedHtmlGridColumn[];
36
+ selectionColumn?: ExtractedHtmlGridColumn;
37
+ indexColumn?: ExtractedHtmlGridColumn;
38
+ rowActions: ExtractedHtmlRowAction[];
39
+ };
40
+ export type ExtractedPageStructureSignal = {
41
+ staticPresent: boolean;
42
+ runtimeVisible?: boolean;
43
+ selector: string;
44
+ confidence: 'low' | 'medium' | 'high';
45
+ evidenceText?: string;
46
+ };
47
+ export type ExtractedPageStructureSignals = {
48
+ dataGrid: ExtractedPageStructureSignal;
49
+ searchCondition: ExtractedPageStructureSignal;
50
+ pager: ExtractedPageStructureSignal;
51
+ hasSelectionColumn: boolean;
52
+ hasActionColumn: boolean;
53
+ toolbarButtonLabels: string[];
54
+ dialogTargets: string[];
55
+ };
56
+ export type ExtractedHtmlPage = {
57
+ path: string;
58
+ pageId: string;
59
+ title: string;
60
+ fields: ExtractedHtmlField[];
61
+ buttons: ExtractedHtmlButton[];
62
+ dialogs: ExtractedHtmlDialog[];
63
+ grids: ExtractedHtmlGrid[];
64
+ pageStructureSignals: ExtractedPageStructureSignals;
65
+ };
66
+ export declare function extractHtmlPage(filePath: string): ExtractedHtmlPage;
67
+ //# sourceMappingURL=html-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-page.d.ts","sourceRoot":"","sources":["../../../src/extractors/html-page.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC,WAAW,EAAE,uBAAuB,EAAE,CAAC;IACvC,eAAe,CAAC,EAAE,uBAAuB,CAAC;IAC1C,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,UAAU,EAAE,sBAAsB,EAAE,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,eAAe,EAAE,4BAA4B,CAAC;IAC9C,KAAK,EAAE,4BAA4B,CAAC;IACpC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,oBAAoB,EAAE,6BAA6B,CAAC;CACrD,CAAC;AAgFF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CA0InE"}
@@ -0,0 +1,195 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import * as cheerio from 'cheerio';
4
+ function normalizeText(value) {
5
+ return (value ?? '').replace(/\s+/g, ' ').trim();
6
+ }
7
+ function normalizeAttribute(value) {
8
+ return normalizeText(value)
9
+ .replaceAll('"', '"')
10
+ .replaceAll('"', '"')
11
+ .replace(/^"+|"+$/g, '');
12
+ }
13
+ function booleanAttribute(value) {
14
+ const normalized = normalizeAttribute(value).toLowerCase();
15
+ return normalized === 'true' || normalized === 'required' || normalized === 'starred';
16
+ }
17
+ function numberAttribute(value) {
18
+ const normalized = normalizeAttribute(value);
19
+ if (!normalized)
20
+ return undefined;
21
+ const parsed = Number(normalized);
22
+ return Number.isFinite(parsed) ? parsed : undefined;
23
+ }
24
+ function pageIdFromPath(filePath) {
25
+ return path.basename(filePath, path.extname(filePath));
26
+ }
27
+ function bindName(bind, fallbackLabel) {
28
+ if (!bind) {
29
+ return fallbackLabel;
30
+ }
31
+ const name = bind.split('.').pop();
32
+ return name && name.length > 0 ? name : fallbackLabel;
33
+ }
34
+ function buildButtonLocator(label, tagName) {
35
+ const normalizedTagName = normalizeText(tagName).toLowerCase();
36
+ if (normalizedTagName === 'a' || normalizedTagName === 'button') {
37
+ return `xpath=//${normalizedTagName}[contains(@class,'mini-button')][normalize-space()='${label}']`;
38
+ }
39
+ return `xpath=//a[contains(@class,'mini-button')][normalize-space()='${label}'] | //button[contains(@class,'mini-button')][normalize-space()='${label}']`;
40
+ }
41
+ function buildFieldLocator(label, controlType) {
42
+ if (controlType === 'mini-textbox') {
43
+ return `//div[@label='${label}']//input[@class='mini-textbox-input']`;
44
+ }
45
+ if (controlType === 'mini-combobox') {
46
+ return `//div[@label='${label}']//input[contains(@class,'mini-combobox')]`;
47
+ }
48
+ return `//div[@label='${label}']//input`;
49
+ }
50
+ function parseDialogs(scriptContent) {
51
+ const dialogs = [];
52
+ const seen = new Set();
53
+ const functionPattern = /function\s+([A-Za-z0-9_]+)\s*\([^)]*\)\s*\{[\s\S]*?epoint\.openTopDialog\('([^']+)'\s*,\s*'([^']+)'/g;
54
+ for (const match of scriptContent.matchAll(functionPattern)) {
55
+ const trigger = match[1];
56
+ const title = match[2];
57
+ const pagePath = match[3];
58
+ const pageId = pagePath.split('?')[0] ?? pagePath;
59
+ const key = `${trigger}:${pagePath}`;
60
+ if (seen.has(key)) {
61
+ continue;
62
+ }
63
+ seen.add(key);
64
+ dialogs.push({ trigger, title, pageId, pagePath });
65
+ }
66
+ return dialogs;
67
+ }
68
+ export function extractHtmlPage(filePath) {
69
+ const html = readFileSync(filePath, 'utf8');
70
+ const $ = cheerio.load(html);
71
+ const fields = [];
72
+ $('[role="control"]').each((_, element) => {
73
+ const wrapper = $(element);
74
+ const label = normalizeText(wrapper.attr('label'));
75
+ const input = wrapper.find('input, textarea, select').first();
76
+ if (!label || input.length === 0) {
77
+ return;
78
+ }
79
+ const controlType = normalizeText(input.attr('class')).split(/\s+/)[0] || 'html-input';
80
+ const bind = input.attr('bind') ?? undefined;
81
+ const maxLength = numberAttribute(input.attr('maxLength') ?? input.attr('maxlength'));
82
+ const required = booleanAttribute(input.attr('required')) || booleanAttribute(wrapper.attr('starred'));
83
+ fields.push({
84
+ name: bindName(bind, label),
85
+ label,
86
+ controlType,
87
+ bind,
88
+ required,
89
+ maxLength,
90
+ locator: buildFieldLocator(label, controlType)
91
+ });
92
+ });
93
+ const buttons = [];
94
+ $('.mini-button').each((index, element) => {
95
+ const button = $(element);
96
+ const label = normalizeText(button.text());
97
+ if (!label) {
98
+ return;
99
+ }
100
+ buttons.push({
101
+ id: button.attr('id') ?? `mini-button-${index + 1}`,
102
+ label,
103
+ onclick: button.attr('onclick') ?? undefined,
104
+ locator: buildButtonLocator(label, element.tagName)
105
+ });
106
+ });
107
+ const grids = [];
108
+ $('.mini-datagrid').each((_, element) => {
109
+ const grid = $(element);
110
+ const dataColumns = [];
111
+ const rowActions = [];
112
+ let selectionColumn;
113
+ let indexColumn;
114
+ grid.find('[property="columns"] > div').each((__, columnElement) => {
115
+ const column = $(columnElement);
116
+ const field = column.attr('field') ?? '';
117
+ const columnType = column.attr('type') ?? (field ? 'data' : 'unknown');
118
+ const headerText = normalizeText(column.clone().children().remove().end().text());
119
+ const extractedColumn = { field, headerText, columnType };
120
+ if (columnType === 'checkcolumn') {
121
+ selectionColumn = extractedColumn;
122
+ return;
123
+ }
124
+ if (columnType === 'indexcolumn') {
125
+ indexColumn = extractedColumn;
126
+ return;
127
+ }
128
+ if (columnType === 'actioncolumn') {
129
+ column.find('[type="action"]').each((__, actionElement) => {
130
+ const action = $(actionElement);
131
+ const label = normalizeText(action.text());
132
+ if (!label)
133
+ return;
134
+ rowActions.push({
135
+ label,
136
+ onclick: action.attr('onclick') ?? undefined,
137
+ locator: `xpath=//i[@data-tooltip='${label}']`
138
+ });
139
+ });
140
+ return;
141
+ }
142
+ if (field) {
143
+ dataColumns.push(extractedColumn);
144
+ }
145
+ });
146
+ grids.push({
147
+ id: grid.attr('id') ?? 'datagrid',
148
+ columns: dataColumns,
149
+ dataColumns,
150
+ selectionColumn,
151
+ indexColumn,
152
+ rowActions
153
+ });
154
+ });
155
+ const scriptContent = $('script')
156
+ .toArray()
157
+ .map((element) => $(element).html() ?? '')
158
+ .join('\n');
159
+ const dialogs = parseDialogs(scriptContent);
160
+ // 把 MiniUI 结构信号归纳为页面结构证据,供场景推理消费(只提升候选置信度)。
161
+ const pageStructureSignals = {
162
+ dataGrid: {
163
+ staticPresent: $('.mini-datagrid').length > 0,
164
+ selector: "div[class*='mini-datagrid']",
165
+ confidence: $('.mini-datagrid').length > 0 ? 'high' : 'low',
166
+ evidenceText: `${$('.mini-datagrid').length} mini-datagrid`,
167
+ },
168
+ searchCondition: {
169
+ staticPresent: $('.fui-condition').length > 0,
170
+ selector: "div[class*='fui-condition']",
171
+ confidence: $('.fui-condition').length > 0 ? 'high' : 'low',
172
+ evidenceText: `${$('.fui-condition').length} fui-condition`,
173
+ },
174
+ pager: {
175
+ staticPresent: $('.mini-grid-pager').length > 0,
176
+ selector: "div[class*='mini-grid-pager']",
177
+ confidence: $('.mini-grid-pager').length > 0 ? 'high' : 'low',
178
+ evidenceText: `${$('.mini-grid-pager').length} mini-grid-pager`,
179
+ },
180
+ hasSelectionColumn: grids.some((grid) => Boolean(grid.selectionColumn)),
181
+ hasActionColumn: grids.some((grid) => grid.rowActions.length > 0),
182
+ toolbarButtonLabels: buttons.map((button) => button.label),
183
+ dialogTargets: dialogs.map((dialog) => dialog.pagePath),
184
+ };
185
+ return {
186
+ path: filePath,
187
+ pageId: pageIdFromPath(filePath),
188
+ title: normalizeText($('title').first().text()),
189
+ fields,
190
+ buttons,
191
+ dialogs,
192
+ grids,
193
+ pageStructureSignals,
194
+ };
195
+ }
@@ -0,0 +1,8 @@
1
+ export type ExtractedJavaAction = {
2
+ path: string;
3
+ controllerName: string;
4
+ methods: string[];
5
+ messages: string[];
6
+ };
7
+ export declare function extractJavaAction(filePath: string): ExtractedJavaAction;
8
+ //# sourceMappingURL=java-action.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"java-action.d.ts","sourceRoot":"","sources":["../../../src/extractors/java-action.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AA+CF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAwBvE"}
@@ -0,0 +1,53 @@
1
+ import { readFileSync } from 'node:fs';
2
+ function unique(values) {
3
+ return Array.from(new Set(values));
4
+ }
5
+ function escapeRegExp(value) {
6
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
+ }
8
+ function chineseLiterals(value) {
9
+ return Array.from(value.matchAll(/"([^"]*[\u4e00-\u9fff][^"]*)"/g), (match) => match[1]);
10
+ }
11
+ function assignedChineseLiterals(content, variableName) {
12
+ const pattern = new RegExp(`\\b${escapeRegExp(variableName)}\\s*=\\s*"([^"]*[\\u4e00-\\u9fff][^"]*)"`, 'g');
13
+ return Array.from(content.matchAll(pattern), (match) => match[1]);
14
+ }
15
+ function callbackMessages(content) {
16
+ const messages = [];
17
+ const callbackPattern = /addCallbackParam\(\s*"(?:msg|error)"\s*,\s*([^;]+)\);/g;
18
+ for (const match of content.matchAll(callbackPattern)) {
19
+ const argument = match[1].trim();
20
+ messages.push(...chineseLiterals(argument));
21
+ const variableMatch = argument.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
22
+ if (variableMatch) {
23
+ messages.push(...assignedChineseLiterals(content, variableMatch[1]));
24
+ }
25
+ }
26
+ return messages;
27
+ }
28
+ function literalMessagesFromCall(content, methodName) {
29
+ const pattern = new RegExp(`${escapeRegExp(methodName)}\\(([^;]+)\\);`, 'g');
30
+ const messages = [];
31
+ for (const call of content.matchAll(pattern)) {
32
+ messages.push(...chineseLiterals(call[1]));
33
+ }
34
+ return messages;
35
+ }
36
+ export function extractJavaAction(filePath) {
37
+ const content = readFileSync(filePath, 'utf8');
38
+ const controllerName = content.match(/@RestController\("([^"]+)"\)/)?.[1] ??
39
+ content.match(/@Controller\("([^"]+)"\)/)?.[1] ??
40
+ '';
41
+ const methods = unique(Array.from(content.matchAll(/public\s+(?:static\s+)?(?:[\w<>\[\], ?]+)\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g), (match) => match[1]));
42
+ const messages = unique([
43
+ ...callbackMessages(content),
44
+ ...literalMessagesFromCall(content, 'epoint.showTips'),
45
+ ...literalMessagesFromCall(content, 'Aisp.tipAndClose')
46
+ ]);
47
+ return {
48
+ path: filePath,
49
+ controllerName,
50
+ methods,
51
+ messages
52
+ };
53
+ }
@@ -0,0 +1,28 @@
1
+ export type ExtractedSpecField = {
2
+ name: string;
3
+ label: string;
4
+ component?: string;
5
+ required: boolean;
6
+ searchable: boolean;
7
+ listVisible: boolean;
8
+ formVisible: boolean;
9
+ length?: number;
10
+ };
11
+ export type ExtractedSpecBusinessRule = {
12
+ name: string;
13
+ description: string;
14
+ trigger?: string;
15
+ action?: string;
16
+ };
17
+ export type ExtractedSpecYaml = {
18
+ path: string;
19
+ module: {
20
+ id: string;
21
+ label: string;
22
+ description?: string;
23
+ };
24
+ fields: ExtractedSpecField[];
25
+ businessRules: ExtractedSpecBusinessRule[];
26
+ };
27
+ export declare function extractSpecYaml(filePath: string): ExtractedSpecYaml;
28
+ //# sourceMappingURL=spec-yaml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-yaml.d.ts","sourceRoot":"","sources":["../../../src/extractors/spec-yaml.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,aAAa,EAAE,yBAAyB,EAAE,CAAC;CAC5C,CAAC;AAcF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CA2BnE"}
@@ -0,0 +1,29 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import YAML from 'yaml';
3
+ export function extractSpecYaml(filePath) {
4
+ const raw = YAML.parse(readFileSync(filePath, 'utf8'));
5
+ return {
6
+ path: filePath,
7
+ module: {
8
+ id: raw.module?.name ?? '',
9
+ label: raw.module?.label ?? '',
10
+ description: raw.module?.description
11
+ },
12
+ fields: (raw.fields ?? []).map((field) => ({
13
+ name: field.name ?? '',
14
+ label: field.label ?? '',
15
+ component: field.component,
16
+ required: Boolean(field.required),
17
+ searchable: Boolean(field.searchable),
18
+ listVisible: Boolean(field.listVisible),
19
+ formVisible: Boolean(field.formVisible),
20
+ length: field.length
21
+ })),
22
+ businessRules: (raw.businessRules ?? []).map((rule) => ({
23
+ name: rule.name ?? '',
24
+ description: rule.description ?? '',
25
+ trigger: rule.trigger,
26
+ action: rule.action
27
+ }))
28
+ };
29
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 从 CrudBusinessModuleContract 的页面按钮、行操作、弹窗动作归一化 ActionCandidate。
3
+ *
4
+ * 只收集事实,不判断是否生成测试。证据来源通过 sourceRefToEvidenceRef 转换保障类型安全。
5
+ */
6
+ import type { ActionCandidate } from '../contracts/gap-inference.js';
7
+ import type { CrudBusinessModuleContract } from '../contracts/crud-business-module.js';
8
+ export declare function collectActionCandidates(contract: CrudBusinessModuleContract): ActionCandidate[];
9
+ //# sourceMappingURL=action-candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-candidates.d.ts","sourceRoot":"","sources":["../../../src/gap-planner/action-candidates.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EACV,0BAA0B,EAK3B,MAAM,sCAAsC,CAAC;AA6E9C,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,0BAA0B,GAAG,eAAe,EAAE,CAE/F"}