@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.
- package/SKILL.md +2 -2
- package/codex-skill/ep-stage/.omc/state/sessions/d636ad35-5fc9-4d5e-85b0-2bcad7d86ee5/pre-tool-advisory-throttle.json +10 -0
- package/codex-skill/ep-stage/glue-create-project/SKILL.md +206 -0
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +274 -0
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +112 -0
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +72 -0
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +289 -0
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +145 -0
- package/codex-skill/ep-stage/{glue-test → glue-run-test}/scripts/generate-crud-spec.mjs +3 -3
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +1 -0
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +29 -7
- package/dist/src/cli/dev/extract-contract.d.ts +14 -0
- package/dist/src/cli/dev/extract-contract.d.ts.map +1 -0
- package/dist/src/cli/dev/extract-contract.js +114 -0
- package/dist/src/cli/generate-crud-contract.js +7 -77
- package/dist/src/cli/generate-playwright-tests.d.ts +0 -28
- package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -1
- package/dist/src/cli/generate-playwright-tests.js +4 -81
- package/dist/src/cli/generate-testcase.d.ts +84 -0
- package/dist/src/cli/generate-testcase.d.ts.map +1 -0
- package/dist/src/cli/generate-testcase.js +300 -0
- package/dist/src/cli/index.d.ts +26 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +75 -0
- package/dist/src/cli/probe.d.ts +46 -0
- package/dist/src/cli/probe.d.ts.map +1 -0
- package/dist/src/cli/probe.js +447 -0
- package/dist/src/cli/run-gap-pipeline.js +4 -0
- package/dist/src/cli/run.d.ts +63 -0
- package/dist/src/cli/run.d.ts.map +1 -0
- package/dist/src/cli/run.js +116 -0
- package/dist/src/cli/spec.d.ts +48 -0
- package/dist/src/cli/spec.d.ts.map +1 -0
- package/dist/src/cli/spec.js +102 -0
- package/dist/src/context/stage-context.d.ts +72 -8
- package/dist/src/context/stage-context.d.ts.map +1 -1
- package/dist/src/context/stage-context.js +61 -15
- package/dist/src/contracts/inference-trace.d.ts +223 -0
- package/dist/src/contracts/inference-trace.d.ts.map +1 -0
- package/dist/src/contracts/inference-trace.js +12 -0
- package/dist/src/extractors/code-list.d.ts +3 -3
- package/dist/src/extractors/code-list.d.ts.map +1 -1
- package/dist/src/extractors/code-list.js +46 -11
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +9 -0
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/inference/module-hints-inference.d.ts +30 -0
- package/dist/src/inference/module-hints-inference.d.ts.map +1 -0
- package/dist/src/inference/module-hints-inference.js +363 -0
- package/dist/src/inference/source-analysis.d.ts +26 -0
- package/dist/src/inference/source-analysis.d.ts.map +1 -0
- package/dist/src/inference/source-analysis.js +258 -0
- package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
- package/dist/src/testcase/testcase-generator.js +4 -0
- package/dist/src/testcase/testcase-v2.d.ts +50 -0
- package/dist/src/testcase/testcase-v2.d.ts.map +1 -0
- package/dist/src/testcase/testcase-v2.js +1 -0
- package/dist/src/util/credentials.d.ts +12 -0
- package/dist/src/util/credentials.d.ts.map +1 -0
- package/dist/src/util/credentials.js +19 -0
- package/dist/src/util/i18n-testcase.d.ts +8 -0
- package/dist/src/util/i18n-testcase.d.ts.map +1 -0
- package/dist/src/util/i18n-testcase.js +55 -0
- package/dist/src/util/softlink.d.ts +33 -0
- package/dist/src/util/softlink.d.ts.map +1 -0
- package/dist/src/util/softlink.js +43 -0
- package/dist/src/validation/credentials.d.ts +19 -0
- package/dist/src/validation/credentials.d.ts.map +1 -0
- package/dist/src/validation/credentials.js +38 -0
- package/dist/src/validation/index.d.ts +6 -0
- package/dist/src/validation/index.d.ts.map +1 -0
- package/dist/src/validation/index.js +4 -0
- package/dist/src/validation/inference-trace.d.ts +13 -0
- package/dist/src/validation/inference-trace.d.ts.map +1 -0
- package/dist/src/validation/inference-trace.js +31 -0
- package/dist/src/validation/projects-index.d.ts +13 -0
- package/dist/src/validation/projects-index.d.ts.map +1 -0
- package/dist/src/validation/projects-index.js +37 -0
- package/dist/src/validation/testcase.d.ts +13 -0
- package/dist/src/validation/testcase.d.ts.map +1 -0
- package/dist/src/validation/testcase.js +53 -0
- package/dist/test/cli/extract-contract.test.d.ts +2 -0
- package/dist/test/cli/extract-contract.test.d.ts.map +1 -0
- package/dist/test/cli/extract-contract.test.js +32 -0
- package/dist/test/cli/generate-testcase-inference.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase-inference.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase-inference.test.js +63 -0
- package/dist/test/cli/generate-testcase-paths.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase-paths.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase-paths.test.js +165 -0
- package/dist/test/cli/generate-testcase-requirements.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase-requirements.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase-requirements.test.js +229 -0
- package/dist/test/cli/generate-testcase.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase.test.js +130 -0
- package/dist/test/cli/index.test.d.ts +2 -0
- package/dist/test/cli/index.test.d.ts.map +1 -0
- package/dist/test/cli/index.test.js +113 -0
- package/dist/test/cli/probe.test.d.ts +2 -0
- package/dist/test/cli/probe.test.d.ts.map +1 -0
- package/dist/test/cli/probe.test.js +31 -0
- package/dist/test/cli/run.test.d.ts +2 -0
- package/dist/test/cli/run.test.d.ts.map +1 -0
- package/dist/test/cli/run.test.js +149 -0
- package/dist/test/cli/spec-boundary.test.d.ts +2 -0
- package/dist/test/cli/spec-boundary.test.d.ts.map +1 -0
- package/dist/test/cli/spec-boundary.test.js +198 -0
- package/dist/test/cli/spec.test.d.ts +2 -0
- package/dist/test/cli/spec.test.d.ts.map +1 -0
- package/dist/test/cli/spec.test.js +196 -0
- package/dist/test/code-list.test.d.ts +2 -0
- package/dist/test/code-list.test.d.ts.map +1 -0
- package/dist/test/code-list.test.js +39 -0
- package/dist/test/crud-contract.test.js +12 -32
- package/dist/test/inference/module-hints-inference.test.d.ts +2 -0
- package/dist/test/inference/module-hints-inference.test.d.ts.map +1 -0
- package/dist/test/inference/module-hints-inference.test.js +25 -0
- package/dist/test/inference/source-analysis.test.d.ts +2 -0
- package/dist/test/inference/source-analysis.test.d.ts.map +1 -0
- package/dist/test/inference/source-analysis.test.js +23 -0
- package/dist/test/material-inventory.test.js +13 -0
- package/dist/test/observable-pipeline.test.js +12 -3
- package/dist/test/stage-context.test.js +145 -13
- package/dist/test/util/credentials.test.d.ts +2 -0
- package/dist/test/util/credentials.test.d.ts.map +1 -0
- package/dist/test/util/credentials.test.js +64 -0
- package/dist/test/util/i18n-testcase.test.d.ts +2 -0
- package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
- package/dist/test/util/i18n-testcase.test.js +119 -0
- package/dist/test/util/softlink.test.d.ts +2 -0
- package/dist/test/util/softlink.test.d.ts.map +1 -0
- package/dist/test/util/softlink.test.js +82 -0
- package/dist/test/validation/credentials.test.d.ts +2 -0
- package/dist/test/validation/credentials.test.d.ts.map +1 -0
- package/dist/test/validation/credentials.test.js +72 -0
- package/dist/test/validation/projects-index.test.d.ts +2 -0
- package/dist/test/validation/projects-index.test.d.ts.map +1 -0
- package/dist/test/validation/projects-index.test.js +48 -0
- package/dist/test/validation/testcase.test.d.ts +2 -0
- package/dist/test/validation/testcase.test.d.ts.map +1 -0
- package/dist/test/validation/testcase.test.js +129 -0
- package/docs/README.md +6 -6
- package/docs/mvp-usage-guide.md +3 -3
- package/package.json +9 -4
- package/codex-skill/ep-stage/create-project/SKILL.md +0 -59
- package/codex-skill/ep-stage/glue-test/SKILL.md +0 -258
- package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +0 -139
- package/codex-skill/ep-stage/glue-testcase/SKILL.md +0 -31
- package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +0 -67
- /package/codex-skill/ep-stage/{glue-testcase → glue-generate-testcase}/examples/observable-testcase.json +0 -0
- /package/codex-skill/ep-stage/{glue-test → glue-run-test}/references/gap-review-protocol.md +0 -0
- /package/codex-skill/ep-stage/{glue-test → glue-run-test}/references/harness-principles.md +0 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { extractCodeListSummary } from '../extractors/code-list.js';
|
|
4
|
+
import { extractHtmlPage } from '../extractors/html-page.js';
|
|
5
|
+
import { extractJavaAction } from '../extractors/java-action.js';
|
|
6
|
+
import { extractSpecYaml } from '../extractors/spec-yaml.js';
|
|
7
|
+
import { buildCrudBusinessModuleContract } from '../normalizers/crud-contract.js';
|
|
8
|
+
import { collectActionCandidates } from '../gap-planner/action-candidates.js';
|
|
9
|
+
import { createSkeletonCoverage } from '../gap-planner/skeleton-coverage.js';
|
|
10
|
+
import { planAgentInferredWorkflows } from '../gap-planner/plan-agent-workflows.js';
|
|
11
|
+
import { summarizePageStructure } from '../capability/page-structure.js';
|
|
12
|
+
import { createCoverageDiff } from '../capability/coverage-diff.js';
|
|
13
|
+
import { buildSourceAnalysis } from '../inference/source-analysis.js';
|
|
14
|
+
import { inferModuleHintsFromMaterials } from '../inference/module-hints-inference.js';
|
|
15
|
+
/**
|
|
16
|
+
* 收集指定目录下的 HTML 物料文件。
|
|
17
|
+
*
|
|
18
|
+
* @param dir - 物料目录。
|
|
19
|
+
* @returns 排序后的绝对路径数组。
|
|
20
|
+
*/
|
|
21
|
+
function htmlFiles(dir) {
|
|
22
|
+
return readdirSync(dir)
|
|
23
|
+
.filter((fileName) => fileName.endsWith('.html'))
|
|
24
|
+
.sort()
|
|
25
|
+
.map((fileName) => path.join(dir, fileName));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 收集指定目录下的 *Action.java 物料文件。
|
|
29
|
+
*
|
|
30
|
+
* @param dir - 物料目录。
|
|
31
|
+
* @returns 排序后的绝对路径数组。
|
|
32
|
+
*/
|
|
33
|
+
function javaFiles(dir) {
|
|
34
|
+
return readdirSync(dir)
|
|
35
|
+
.filter((fileName) => fileName.endsWith('Action.java'))
|
|
36
|
+
.sort()
|
|
37
|
+
.map((fileName) => path.join(dir, fileName));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 根据页面 ID 推断 CRUD 页面角色。
|
|
41
|
+
*
|
|
42
|
+
* @param pageId - 页面标识。
|
|
43
|
+
* @returns CRUD 页面角色。
|
|
44
|
+
*/
|
|
45
|
+
function inferCrudPageRole(pageId) {
|
|
46
|
+
if (pageId.endsWith('add'))
|
|
47
|
+
return 'add';
|
|
48
|
+
if (pageId.endsWith('edit'))
|
|
49
|
+
return 'edit';
|
|
50
|
+
if (pageId.endsWith('detail'))
|
|
51
|
+
return 'detail';
|
|
52
|
+
return 'list';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 将 HTML 控件类型映射为 spec.yaml 兼容的组件类型。
|
|
56
|
+
*
|
|
57
|
+
* @param controlType - HTML extractor 提取到的控件类型。
|
|
58
|
+
* @returns spec 字段组件类型。
|
|
59
|
+
*/
|
|
60
|
+
function mapHtmlControlTypeToSpecComponent(controlType) {
|
|
61
|
+
if (controlType === 'mini-combobox') {
|
|
62
|
+
return 'listbox';
|
|
63
|
+
}
|
|
64
|
+
if (controlType === 'mini-textarea') {
|
|
65
|
+
return 'textarea';
|
|
66
|
+
}
|
|
67
|
+
return 'input';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 在缺少 spec.yaml 时,根据 HTML 页面结构合成最小可用的 spec。
|
|
71
|
+
*
|
|
72
|
+
* 设计取舍:
|
|
73
|
+
* - searchable: 仅由列表页搜索区字段决定;
|
|
74
|
+
* - formVisible: 由 add/edit 页面字段决定;
|
|
75
|
+
* - listVisible: 优先取列表页 datagrid 列定义;
|
|
76
|
+
* - businessRules: 当前无法可靠结构化抽取,保持空数组,交给 testcase 审阅阶段。
|
|
77
|
+
*
|
|
78
|
+
* @param docsDir - 需求文档目录。
|
|
79
|
+
* @param moduleId - 模块 ID。
|
|
80
|
+
* @param codeListSummary - code_list 摘要。
|
|
81
|
+
* @param pages - 已提取的 HTML 页面集合。
|
|
82
|
+
* @returns 供 contract 正常生成使用的推理 spec。
|
|
83
|
+
*/
|
|
84
|
+
function inferSpecFromPages(docsDir, moduleId, codeListSummary, pages) {
|
|
85
|
+
const fieldMap = new Map();
|
|
86
|
+
const listPage = pages.find((page) => inferCrudPageRole(page.pageId) === 'list');
|
|
87
|
+
/**
|
|
88
|
+
* 合并同名字段的页面证据,避免列表页 / 新增页重复字段互相覆盖。
|
|
89
|
+
*
|
|
90
|
+
* @param field - 当前页面字段。
|
|
91
|
+
* @param role - 当前页面角色。
|
|
92
|
+
* @returns 无返回值。
|
|
93
|
+
*/
|
|
94
|
+
function mergeField(field, role) {
|
|
95
|
+
const existing = fieldMap.get(field.name) ?? {
|
|
96
|
+
name: field.name,
|
|
97
|
+
label: field.label,
|
|
98
|
+
component: mapHtmlControlTypeToSpecComponent(field.controlType),
|
|
99
|
+
required: field.required,
|
|
100
|
+
searchable: false,
|
|
101
|
+
listVisible: false,
|
|
102
|
+
formVisible: false,
|
|
103
|
+
length: field.maxLength,
|
|
104
|
+
};
|
|
105
|
+
existing.label ||= field.label;
|
|
106
|
+
existing.component ||= mapHtmlControlTypeToSpecComponent(field.controlType);
|
|
107
|
+
existing.required ||= field.required;
|
|
108
|
+
existing.length ??= field.maxLength;
|
|
109
|
+
if (role === 'list') {
|
|
110
|
+
existing.searchable = true;
|
|
111
|
+
}
|
|
112
|
+
if (role === 'add' || role === 'edit') {
|
|
113
|
+
existing.formVisible = true;
|
|
114
|
+
}
|
|
115
|
+
fieldMap.set(field.name, existing);
|
|
116
|
+
}
|
|
117
|
+
for (const page of pages) {
|
|
118
|
+
const role = inferCrudPageRole(page.pageId);
|
|
119
|
+
for (const field of page.fields) {
|
|
120
|
+
mergeField(field, role);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const column of listPage?.grids.flatMap((grid) => grid.dataColumns) ?? []) {
|
|
124
|
+
if (!column.field) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const existing = fieldMap.get(column.field) ?? {
|
|
128
|
+
name: column.field,
|
|
129
|
+
label: column.headerText || column.field,
|
|
130
|
+
component: 'input',
|
|
131
|
+
required: false,
|
|
132
|
+
searchable: false,
|
|
133
|
+
listVisible: false,
|
|
134
|
+
formVisible: false,
|
|
135
|
+
};
|
|
136
|
+
existing.label ||= column.headerText || column.field;
|
|
137
|
+
existing.listVisible = true;
|
|
138
|
+
fieldMap.set(column.field, existing);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
path: path.join(docsDir, 'spec.inferred-from-html.yaml'),
|
|
142
|
+
module: {
|
|
143
|
+
id: moduleId,
|
|
144
|
+
label: codeListSummary.moduleLabel || moduleId,
|
|
145
|
+
},
|
|
146
|
+
fields: Array.from(fieldMap.values()),
|
|
147
|
+
businessRules: [],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 解析 docs 目录下的 spec.yaml;缺失时根据 HTML 页面结构合成 fallback spec。
|
|
152
|
+
*
|
|
153
|
+
* @param docsDir - 需求文档目录。
|
|
154
|
+
* @param moduleId - 模块 ID。
|
|
155
|
+
* @param codeListSummary - 已解析的 code_list 摘要。
|
|
156
|
+
* @param pages - 已提取的 HTML 页面集合。
|
|
157
|
+
* @returns ExtractedSpecYaml。
|
|
158
|
+
*/
|
|
159
|
+
function resolveSpec(docsDir, moduleId, codeListSummary, pages) {
|
|
160
|
+
const specPath = path.join(docsDir, 'spec.yaml');
|
|
161
|
+
if (existsSync(specPath))
|
|
162
|
+
return extractSpecYaml(specPath);
|
|
163
|
+
return inferSpecFromPages(docsDir, moduleId, codeListSummary, pages);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 推断 requiredRole 默认值。
|
|
167
|
+
*
|
|
168
|
+
* 设计:testcase v2 schema 要求 cases[].requiredRole / uncoveredCandidates[].requiredRole 必填。
|
|
169
|
+
* 当前 BusinessRuleContract 不带 role 字段,hints.scenarioMatrix 也未覆盖角色;
|
|
170
|
+
* 回落到 'admin',后续由 generate-testcase 子命令的 --required-role / hints 覆盖。
|
|
171
|
+
*
|
|
172
|
+
* @returns 默认 requiredRole 字面量。
|
|
173
|
+
*/
|
|
174
|
+
function deriveRequiredRole() {
|
|
175
|
+
return 'admin';
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 从 contract 推导出 v2 schema 要求的 requiredRoles 集合。
|
|
179
|
+
*
|
|
180
|
+
* 目前只生成单一默认角色;后续可由 hints / scenario decision 注入更丰富的角色矩阵。
|
|
181
|
+
*
|
|
182
|
+
* @returns 至少包含一个默认 admin 的角色清单。
|
|
183
|
+
*/
|
|
184
|
+
function deriveRequiredRoles() {
|
|
185
|
+
return [{ role: 'admin', hint: undefined }];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 把 coverageDiff 中已覆盖动作映射成 testcase v2 coveredActions 段。
|
|
189
|
+
*
|
|
190
|
+
* @param contract - CRUD 契约(用于 evidence kind 提示)。
|
|
191
|
+
* @param coverageDiff - coverage diff trace。
|
|
192
|
+
* @returns coveredActions 数组(含 evidence <kind>=<ref>)。
|
|
193
|
+
*/
|
|
194
|
+
function mapCoveredActions(contract, coverageDiff) {
|
|
195
|
+
return coverageDiff.covered.map((item) => ({
|
|
196
|
+
actionId: item.actionId,
|
|
197
|
+
kind: item.label,
|
|
198
|
+
evidence: [
|
|
199
|
+
`contract=${contract.contractVersion}`,
|
|
200
|
+
`skeleton=${coverageDiff.skeletonCoverageId}`,
|
|
201
|
+
],
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 把 coverageDiff 中未覆盖动作映射成 v2 uncoveredCandidates 段。
|
|
206
|
+
*
|
|
207
|
+
* @param contract - CRUD 契约(用于 evidence kind 提示)。
|
|
208
|
+
* @param coverageDiff - coverage diff trace。
|
|
209
|
+
* @param requiredRole - 默认 role;后续 spec/run 子命令可改写。
|
|
210
|
+
* @returns uncoveredCandidates 数组(含 requiredRole/businessIntent/assertionExpectation/evidence)。
|
|
211
|
+
*/
|
|
212
|
+
function mapUncoveredCandidates(contract, coverageDiff, requiredRole) {
|
|
213
|
+
const status = (diffStatus) => {
|
|
214
|
+
if (diffStatus === 'gap-candidate')
|
|
215
|
+
return 'planned';
|
|
216
|
+
if (diffStatus === 'partially-covered')
|
|
217
|
+
return 'candidate';
|
|
218
|
+
return 'unresolved';
|
|
219
|
+
};
|
|
220
|
+
return [
|
|
221
|
+
...coverageDiff.gapCandidates,
|
|
222
|
+
...coverageDiff.partiallyCovered,
|
|
223
|
+
...coverageDiff.unresolved,
|
|
224
|
+
].map((item) => ({
|
|
225
|
+
actionId: item.actionId,
|
|
226
|
+
label: item.label,
|
|
227
|
+
status: status(item.status),
|
|
228
|
+
requiredRole,
|
|
229
|
+
businessIntent: item.reason,
|
|
230
|
+
assertionExpectation: `验证 ${item.label} 完成后业务断言(待 run 子命令推理细化)`,
|
|
231
|
+
evidence: [`contract=${contract.contractVersion}`, `skeleton=${coverageDiff.skeletonCoverageId}`],
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 装配 testcase v2 顶层结构。
|
|
236
|
+
*
|
|
237
|
+
* @param input - 装配所需要素。
|
|
238
|
+
* @returns TestcaseV2。
|
|
239
|
+
*/
|
|
240
|
+
function assembleTestcaseV2(input) {
|
|
241
|
+
const requiredRoles = deriveRequiredRoles();
|
|
242
|
+
const defaultRole = deriveRequiredRole();
|
|
243
|
+
const coveredActions = mapCoveredActions(input.contract, input.coverageDiff);
|
|
244
|
+
const uncoveredCandidates = mapUncoveredCandidates(input.contract, input.coverageDiff, defaultRole);
|
|
245
|
+
const caseEvidence = [
|
|
246
|
+
`contract=${input.contract.contractVersion}`,
|
|
247
|
+
`skeleton=${input.coverageDiff.skeletonCoverageId}`,
|
|
248
|
+
];
|
|
249
|
+
const cases = ['create', 'read', 'update', 'delete'].map((kind) => ({
|
|
250
|
+
caseId: `${input.contract.module.id}.${kind}`,
|
|
251
|
+
title: `${input.contract.module.label} ${kind}`,
|
|
252
|
+
scenario: input.scenario,
|
|
253
|
+
reviewStatus: 'needs_review',
|
|
254
|
+
requiredRole: defaultRole,
|
|
255
|
+
evidence: caseEvidence,
|
|
256
|
+
steps: [`执行 ${kind} 主流程(详见 .spec.ts)`],
|
|
257
|
+
assertions: [`验证 ${kind} 完成后业务断言`],
|
|
258
|
+
unresolved: [],
|
|
259
|
+
}));
|
|
260
|
+
return {
|
|
261
|
+
schemaVersion: 'v2',
|
|
262
|
+
scenario: input.scenario,
|
|
263
|
+
moduleId: input.contract.module.id,
|
|
264
|
+
moduleName: input.contract.module.label,
|
|
265
|
+
menuPath: input.menuPath,
|
|
266
|
+
requiredSystems: [{ url: '', systemName: undefined }],
|
|
267
|
+
requiredRoles,
|
|
268
|
+
coveredActions,
|
|
269
|
+
uncoveredCandidates,
|
|
270
|
+
cases,
|
|
271
|
+
reasoningSummary: {
|
|
272
|
+
conclusion: `基于 ${input.contract.contractVersion} 契约和 ${input.coverageDiff.skeletonCoverageId} 骨架覆盖度生成 ${cases.length} 条胶水用例。`,
|
|
273
|
+
evidenceChain: [
|
|
274
|
+
`contract=${input.contract.contractVersion}`,
|
|
275
|
+
`skeleton=${input.coverageDiff.skeletonCoverageId}`,
|
|
276
|
+
`codeList=${path.basename(input.codeListPath)}`,
|
|
277
|
+
],
|
|
278
|
+
alternatives: [],
|
|
279
|
+
confidence: input.coverageDiff.reasoningSummary.confidence,
|
|
280
|
+
risks: input.coverageDiff.reasoningSummary.risks,
|
|
281
|
+
needsHumanReview: true,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 组装 materialInventory 章节(spec §7)。
|
|
287
|
+
*
|
|
288
|
+
* 把 probe 已解析到的 codeList / pages / actions / args 登记成可审阅的物料清单,
|
|
289
|
+
* 缺失项进 `missing` 数组。保持私有,不做跨文件抽象(Task 3 范围)。
|
|
290
|
+
*
|
|
291
|
+
* @param args - probe 入参。
|
|
292
|
+
* @param codeList - 已解析的 code_list 摘要。
|
|
293
|
+
* @param pages - 已抽取的 HTML 页面。
|
|
294
|
+
* @param actions - 已抽取的 Java Action。
|
|
295
|
+
* @returns MaterialInventoryTrace。
|
|
296
|
+
*/
|
|
297
|
+
function buildMaterialInventory(args, codeList, pages, actions) {
|
|
298
|
+
const missing = [];
|
|
299
|
+
const moduleIdSlot = { status: 'resolved', value: args.moduleId };
|
|
300
|
+
const menuSlot = codeList.menu
|
|
301
|
+
? { status: 'resolved', value: codeList.menu }
|
|
302
|
+
: (() => {
|
|
303
|
+
missing.push('menu');
|
|
304
|
+
return { status: 'missing', reason: 'code_list.md 未提供 menu' };
|
|
305
|
+
})();
|
|
306
|
+
const htmlFiles = pages.map((page) => ({
|
|
307
|
+
status: 'resolved',
|
|
308
|
+
value: page.path,
|
|
309
|
+
}));
|
|
310
|
+
const javaActionFiles = actions.map((action) => ({
|
|
311
|
+
status: 'resolved',
|
|
312
|
+
value: action.path,
|
|
313
|
+
}));
|
|
314
|
+
const docsFiles = [];
|
|
315
|
+
if (args.docsDir && existsSync(args.docsDir)) {
|
|
316
|
+
docsFiles.push({ status: 'resolved', value: args.docsDir });
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
missing.push('docsDir');
|
|
320
|
+
docsFiles.push({ status: 'missing', reason: '未提供 docsDir' });
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
knowledgeRoot: codeList.knowledgeRoot ?? '',
|
|
324
|
+
moduleId: moduleIdSlot,
|
|
325
|
+
menu: menuSlot,
|
|
326
|
+
htmlFiles,
|
|
327
|
+
javaActionFiles,
|
|
328
|
+
docsFiles,
|
|
329
|
+
missing,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 把 coverageDiff(CoverageDiffTrace)映射为 InferenceTraceV1.coverageDiff 的内联形状。
|
|
334
|
+
*
|
|
335
|
+
* 映射规则(controller 裁定):
|
|
336
|
+
* - coveredActions ← coverageDiff.covered
|
|
337
|
+
* - uncoveredCandidates ← [...gapCandidates, ...partiallyCovered, ...unresolved]
|
|
338
|
+
* - needsReview ← unresolved.map(i => i.actionId)
|
|
339
|
+
*
|
|
340
|
+
* @param coverageDiff - probe 产出的 CoverageDiffTrace。
|
|
341
|
+
* @returns InferenceTraceV1.coverageDiff。
|
|
342
|
+
*/
|
|
343
|
+
function mapCoverageDiffForTrace(coverageDiff) {
|
|
344
|
+
return {
|
|
345
|
+
coveredActions: coverageDiff.covered,
|
|
346
|
+
uncoveredCandidates: [
|
|
347
|
+
...coverageDiff.gapCandidates,
|
|
348
|
+
...coverageDiff.partiallyCovered,
|
|
349
|
+
...coverageDiff.unresolved,
|
|
350
|
+
],
|
|
351
|
+
needsReview: coverageDiff.unresolved.map((item) => item.actionId),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* probe + coverage + testcase v2 装配的统一入口(REQ-CHAIN-01)。
|
|
356
|
+
*
|
|
357
|
+
* 把 run-gap-pipeline.ts 与 generate-testcase.ts 共用的物料解析 → contract → skeleton coverage →
|
|
358
|
+
* coverage diff 段抽成单一函数,避免 1881 行 run-gap-pipeline.ts 与新 CLI 双份维护。
|
|
359
|
+
*
|
|
360
|
+
* 注意:本函数只做钻探与装配,不写文件;写盘由调用方负责(保持 I/O 边界清晰)。
|
|
361
|
+
*
|
|
362
|
+
* @param args - 物料路径 + 场景。
|
|
363
|
+
* @returns contract + coverageDiff + 装配好的 TestcaseV2。
|
|
364
|
+
*/
|
|
365
|
+
export function runProbeAndCoverage(args) {
|
|
366
|
+
const codeList = extractCodeListSummary(args.codeListPath);
|
|
367
|
+
const pages = htmlFiles(args.webappDir).map(extractHtmlPage);
|
|
368
|
+
const actions = javaFiles(args.javaActionsDir).map(extractJavaAction);
|
|
369
|
+
// 源分析快照(spec §7 sourceAnalysis)。
|
|
370
|
+
const sourceAnalysis = buildSourceAnalysis({
|
|
371
|
+
codeListPath: args.codeListPath,
|
|
372
|
+
docsDir: args.docsDir,
|
|
373
|
+
codeList,
|
|
374
|
+
pages,
|
|
375
|
+
actions,
|
|
376
|
+
});
|
|
377
|
+
// 显式 hints 文件 vs 推理 hints:
|
|
378
|
+
// - 有 hintsPath 时读取为 explicitHints,mode 标记 explicit_override,explicitHints 流入 builders。
|
|
379
|
+
// - 无 hintsPath 时调 inferModuleHintsFromMaterials,推理出的 hints 流入 builders(替换原 undefined)。
|
|
380
|
+
let hints;
|
|
381
|
+
let moduleHintsInference;
|
|
382
|
+
if (args.hintsPath) {
|
|
383
|
+
const explicitHints = JSON.parse(readFileSync(args.hintsPath, 'utf8'));
|
|
384
|
+
// 仍跑一次推理以记录推理过程,但 mode 标记 explicit_override,builders 用 explicitHints。
|
|
385
|
+
const inferred = inferModuleHintsFromMaterials({
|
|
386
|
+
sourceAnalysis,
|
|
387
|
+
pages,
|
|
388
|
+
actions,
|
|
389
|
+
explicitHints,
|
|
390
|
+
});
|
|
391
|
+
moduleHintsInference = { ...inferred.inference, mode: 'explicit_override' };
|
|
392
|
+
hints = explicitHints;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
const inferred = inferModuleHintsFromMaterials({ sourceAnalysis, pages, actions });
|
|
396
|
+
moduleHintsInference = inferred.inference;
|
|
397
|
+
hints = inferred.hints;
|
|
398
|
+
}
|
|
399
|
+
const pageStructures = pages.map(summarizePageStructure);
|
|
400
|
+
const contract = buildCrudBusinessModuleContract({
|
|
401
|
+
moduleId: args.moduleId,
|
|
402
|
+
spec: resolveSpec(args.docsDir, args.moduleId, codeList, pages),
|
|
403
|
+
codeList,
|
|
404
|
+
pages,
|
|
405
|
+
actions,
|
|
406
|
+
hints,
|
|
407
|
+
});
|
|
408
|
+
const skeletonCoverage = createSkeletonCoverage(contract, hints);
|
|
409
|
+
const actionCandidates = collectActionCandidates(contract);
|
|
410
|
+
const baselineWorkflows = planAgentInferredWorkflows({ contract, actions, hints });
|
|
411
|
+
const coverageDiff = createCoverageDiff({
|
|
412
|
+
moduleId: args.moduleId,
|
|
413
|
+
actionCandidates,
|
|
414
|
+
skeletonCoverage,
|
|
415
|
+
baselineWorkflows,
|
|
416
|
+
pageStructures,
|
|
417
|
+
});
|
|
418
|
+
const testcaseV2 = assembleTestcaseV2({
|
|
419
|
+
contract,
|
|
420
|
+
coverageDiff,
|
|
421
|
+
scenario: args.scenario,
|
|
422
|
+
menuPath: args.menuPath ?? codeList.menu ?? '',
|
|
423
|
+
codeListPath: args.codeListPath,
|
|
424
|
+
});
|
|
425
|
+
// 组装 inference trace(REQ-INFER-004 / REQ-INFER-006)。
|
|
426
|
+
const materialInventory = buildMaterialInventory(args, codeList, pages, actions);
|
|
427
|
+
const reasoningSummary = {
|
|
428
|
+
conclusion: `基于 ${sourceAnalysis.mode} 源分析(置信度 ${sourceAnalysis.confidence})与 ${moduleHintsInference.mode} 槽位推理生成契约;coverageDiff unresolved ${coverageDiff.unresolved.length} 项。`,
|
|
429
|
+
confidence: sourceAnalysis.confidence,
|
|
430
|
+
risks: [
|
|
431
|
+
...sourceAnalysis.unresolved,
|
|
432
|
+
...moduleHintsInference.unresolved.map((slot) => `未解析槽位:${slot}`),
|
|
433
|
+
],
|
|
434
|
+
needsHumanReview: moduleHintsInference.unresolved.length > 0 || sourceAnalysis.confidence === 'low',
|
|
435
|
+
};
|
|
436
|
+
const inferenceTrace = {
|
|
437
|
+
schemaVersion: 'inference-trace/v1',
|
|
438
|
+
codeListPath: args.codeListPath,
|
|
439
|
+
generatedAt: new Date().toISOString(),
|
|
440
|
+
materialInventory,
|
|
441
|
+
sourceAnalysis,
|
|
442
|
+
moduleHintsInference,
|
|
443
|
+
coverageDiff: mapCoverageDiffForTrace(coverageDiff),
|
|
444
|
+
reasoningSummary,
|
|
445
|
+
};
|
|
446
|
+
return { contract, coverageDiff, testcaseV2, inferenceTrace };
|
|
447
|
+
}
|
|
@@ -1217,6 +1217,10 @@ async function main() {
|
|
|
1217
1217
|
gate: { status: 'confirmed', confirmedBy: 'policy', reason: '确定性 extractor 已完成' },
|
|
1218
1218
|
};
|
|
1219
1219
|
recordStage(writer, stageSummaries, 'evidence-extraction', evidenceTrace);
|
|
1220
|
+
// TODO(Phase 3 convergence):本段(contract 提取 → coverage diff → testcase 生成)的规范实现已迁到
|
|
1221
|
+
// `src/cli/probe.ts` 的 runProbeAndCoverage / assembleTestcaseV2。保留此处的内联调用是 Task 1.6 为
|
|
1222
|
+
// 避免现有 vitest fixture 联动断裂做的过渡。Phase 3.1 task 完成 generator 升级后应替换为
|
|
1223
|
+
// import { runProbeAndCoverage } from './probe.js' 一次调用,并删除此段重复。
|
|
1220
1224
|
const contract = buildCrudBusinessModuleContract({
|
|
1221
1225
|
moduleId,
|
|
1222
1226
|
spec,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run 子命令参数(REQ-CHAIN-02 / REQ-SPEC-01)。
|
|
3
|
+
*
|
|
4
|
+
* - spec:crud.spec.ts 路径(必填,由 spec 子命令产出,v1 或已含 AI 追加段 v2);
|
|
5
|
+
* - testcase:testcase.json5 路径(必填,提供 uncoveredCandidates 与 cases[].requiredRole);
|
|
6
|
+
* - headless:是否启动无头浏览器,真实业务系统通常 false;
|
|
7
|
+
* - projectDir:报告目录基址,默认 process.cwd()。
|
|
8
|
+
*/
|
|
9
|
+
export type RunArgs = {
|
|
10
|
+
spec: string;
|
|
11
|
+
testcase: string;
|
|
12
|
+
headless: boolean;
|
|
13
|
+
projectDir?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* run 子命令产物。
|
|
17
|
+
*
|
|
18
|
+
* @property specPath - 写盘后的 .spec.ts(含 AI 追加段)。
|
|
19
|
+
* @property reportDir - 本次 run 产出的报告目录。
|
|
20
|
+
* @property version - 'v1-with-planned-stubs' 表示当前 run.ts 阶段产物(已追加 planned 占位但未跑真实 Playwright + AI 推理);
|
|
21
|
+
* 'v2' 保留给 Phase 5 真机验收阶段迁移完整 runtime 后使用。
|
|
22
|
+
*/
|
|
23
|
+
export type RunResult = {
|
|
24
|
+
specPath: string;
|
|
25
|
+
reportDir: string;
|
|
26
|
+
version: 'v1' | 'v1-with-planned-stubs' | 'v2';
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* AI 追加段分隔标记。重跑时 detect 已有标记则覆盖(保证幂等)。
|
|
30
|
+
*/
|
|
31
|
+
export declare const AI_APPEND_MARKER = "// === AI \u94BB\u63A2\u8FFD\u52A0\uFF08\u57FA\u4E8E uncoveredCandidates[].status=planned\uFF09===";
|
|
32
|
+
/**
|
|
33
|
+
* 解析 run 子命令参数。
|
|
34
|
+
*
|
|
35
|
+
* @param argv - 已剥离子命令名的命令行参数。
|
|
36
|
+
* @returns RunArgs。
|
|
37
|
+
* @throws 缺少 --spec / --testcase 或参数对非法时抛错。
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseRunArgs(argv: string[]): RunArgs;
|
|
40
|
+
/**
|
|
41
|
+
* run 子命令主体(REQ-CHAIN-02 / REQ-SPEC-01)。
|
|
42
|
+
*
|
|
43
|
+
* 完整设计(spec §5.4 / brief Step 6):
|
|
44
|
+
* 1. 按 cases[].requiredRole 用 credentials.json5 登录;
|
|
45
|
+
* 2. 跑 v1 胶水部分 spec;
|
|
46
|
+
* 3. 对 uncoveredCandidates 里 status='planned' 的项做 AI 实时推理(含 requiredRole 切换);
|
|
47
|
+
* 4. 追加段以 AI_APPEND_MARKER 分隔;重跑检测已有段则覆盖(幂等);
|
|
48
|
+
* 5. 报告落 e2e/glue-code-mvp/glue-report/<timestamp>/;
|
|
49
|
+
* 6. 回写 projects.index.json5 的 systems[].credentialsCache 与 requirements[].lastRun*。
|
|
50
|
+
*
|
|
51
|
+
* 当前 Sub-step 6 范围:
|
|
52
|
+
* - 完整实现「步骤 4」(AI 追加段幂等覆盖契约);
|
|
53
|
+
* - 完整实现「步骤 5」(报告目录生成);
|
|
54
|
+
* - 步骤 1 / 2 / 3 / 6 的 Playwright runtime + AI 推理 + 索引回写在 Phase 5 真机验收阶段
|
|
55
|
+
* 迁移 run-gap-pipeline.ts 的 runtime runner 实现接入(brief 已说明 AI 推理在 Phase 5 证)。
|
|
56
|
+
*
|
|
57
|
+
* @param args - 已解析参数。
|
|
58
|
+
* @returns version: 'v1-with-planned-stubs' — 真实的 v2(Playwright 执行 + AI 推理填充 planned 项)
|
|
59
|
+
* 待 Phase 5 真机验收阶段从 run-gap-pipeline.ts 的 runtime runner 迁移过来。当前返回 'v1-with-planned-stubs'
|
|
60
|
+
* 而非 'v2' 是为了让下游 skill(glue-run-test 等)能区分"真 v2"与"stub 形态"。
|
|
61
|
+
*/
|
|
62
|
+
export declare function runRun(args: RunArgs): Promise<RunResult>;
|
|
63
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cli/run.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,IAAI,GAAG,uBAAuB,GAAG,IAAI,CAAC;CAChD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,uGAA+D,CAAC;AAE7F;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAoBpD;AAyCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAoB9D"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import JSON5 from 'json5';
|
|
4
|
+
import { validateTestcase } from '../validation/testcase.js';
|
|
5
|
+
/**
|
|
6
|
+
* AI 追加段分隔标记。重跑时 detect 已有标记则覆盖(保证幂等)。
|
|
7
|
+
*/
|
|
8
|
+
export const AI_APPEND_MARKER = '// === AI 钻探追加(基于 uncoveredCandidates[].status=planned)===';
|
|
9
|
+
/**
|
|
10
|
+
* 解析 run 子命令参数。
|
|
11
|
+
*
|
|
12
|
+
* @param argv - 已剥离子命令名的命令行参数。
|
|
13
|
+
* @returns RunArgs。
|
|
14
|
+
* @throws 缺少 --spec / --testcase 或参数对非法时抛错。
|
|
15
|
+
*/
|
|
16
|
+
export function parseRunArgs(argv) {
|
|
17
|
+
const result = {};
|
|
18
|
+
const normalizedArgv = argv.filter((arg) => arg !== '--');
|
|
19
|
+
for (let index = 0; index < normalizedArgv.length; index += 2) {
|
|
20
|
+
const key = normalizedArgv[index];
|
|
21
|
+
const value = normalizedArgv[index + 1];
|
|
22
|
+
if (!key?.startsWith('--') || !value) {
|
|
23
|
+
throw new Error(`无效参数对: ${key ?? '<empty>'}`);
|
|
24
|
+
}
|
|
25
|
+
result[key.slice(2)] = value;
|
|
26
|
+
}
|
|
27
|
+
if (!result.spec)
|
|
28
|
+
throw new Error('缺少 --spec');
|
|
29
|
+
if (!result.testcase)
|
|
30
|
+
throw new Error('缺少 --testcase');
|
|
31
|
+
return {
|
|
32
|
+
spec: result.spec,
|
|
33
|
+
testcase: result.testcase,
|
|
34
|
+
// headless 默认 true;--headless false 时关闭无头。
|
|
35
|
+
headless: result.headless !== 'false',
|
|
36
|
+
projectDir: result['project-dir'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 渲染 AI 追加段:每条 planned uncoveredCandidate 输出一个占位 test,含
|
|
41
|
+
* requiredRole / businessIntent / assertionExpectation 三条注释(供 AI runtime
|
|
42
|
+
* 推理时拓展为真实业务步骤)。
|
|
43
|
+
*
|
|
44
|
+
* 当前实现只生成占位骨架;真正的 AI runtime 推理在 Phase 5 真机验收阶段接入
|
|
45
|
+
* (参考 run-gap-pipeline.ts 的 planAgentInferredWorkflows 调用链)。本 Sub-step
|
|
46
|
+
* 聚焦于「追加段幂等覆盖」契约,确保重跑不会无限叠加注释体。
|
|
47
|
+
*
|
|
48
|
+
* @param planned - status='planned' 的 uncoveredCandidates。
|
|
49
|
+
* @returns 拼接后的追加段(不含 marker,由调用方串接)。
|
|
50
|
+
*/
|
|
51
|
+
function renderAppendedTests(planned) {
|
|
52
|
+
return planned
|
|
53
|
+
.map((item) => {
|
|
54
|
+
return [
|
|
55
|
+
`test(${JSON.stringify(item.label)}, async ({ page: _page }) => {`,
|
|
56
|
+
` // actionId: ${item.actionId}`,
|
|
57
|
+
` // requiredRole: ${item.requiredRole}`,
|
|
58
|
+
` // businessIntent: ${item.businessIntent}`,
|
|
59
|
+
` // assertionExpectation: ${item.assertionExpectation}`,
|
|
60
|
+
` // TODO: AI runtime 推理后填入实际 Playwright 步骤`,
|
|
61
|
+
`});`,
|
|
62
|
+
].join('\n');
|
|
63
|
+
})
|
|
64
|
+
.join('\n\n');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 截断现有 spec 的 AI 追加段(用于重跑覆盖)。
|
|
68
|
+
*
|
|
69
|
+
* @param specBody - 现有 spec 文件全文。
|
|
70
|
+
* @returns 截断到 AI_APPEND_MARKER 之前的基线 spec。
|
|
71
|
+
*/
|
|
72
|
+
function truncateAtMarker(specBody) {
|
|
73
|
+
const markerIndex = specBody.indexOf(AI_APPEND_MARKER);
|
|
74
|
+
return markerIndex >= 0 ? specBody.slice(0, markerIndex).trimEnd() : specBody.trimEnd();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* run 子命令主体(REQ-CHAIN-02 / REQ-SPEC-01)。
|
|
78
|
+
*
|
|
79
|
+
* 完整设计(spec §5.4 / brief Step 6):
|
|
80
|
+
* 1. 按 cases[].requiredRole 用 credentials.json5 登录;
|
|
81
|
+
* 2. 跑 v1 胶水部分 spec;
|
|
82
|
+
* 3. 对 uncoveredCandidates 里 status='planned' 的项做 AI 实时推理(含 requiredRole 切换);
|
|
83
|
+
* 4. 追加段以 AI_APPEND_MARKER 分隔;重跑检测已有段则覆盖(幂等);
|
|
84
|
+
* 5. 报告落 e2e/glue-code-mvp/glue-report/<timestamp>/;
|
|
85
|
+
* 6. 回写 projects.index.json5 的 systems[].credentialsCache 与 requirements[].lastRun*。
|
|
86
|
+
*
|
|
87
|
+
* 当前 Sub-step 6 范围:
|
|
88
|
+
* - 完整实现「步骤 4」(AI 追加段幂等覆盖契约);
|
|
89
|
+
* - 完整实现「步骤 5」(报告目录生成);
|
|
90
|
+
* - 步骤 1 / 2 / 3 / 6 的 Playwright runtime + AI 推理 + 索引回写在 Phase 5 真机验收阶段
|
|
91
|
+
* 迁移 run-gap-pipeline.ts 的 runtime runner 实现接入(brief 已说明 AI 推理在 Phase 5 证)。
|
|
92
|
+
*
|
|
93
|
+
* @param args - 已解析参数。
|
|
94
|
+
* @returns version: 'v1-with-planned-stubs' — 真实的 v2(Playwright 执行 + AI 推理填充 planned 项)
|
|
95
|
+
* 待 Phase 5 真机验收阶段从 run-gap-pipeline.ts 的 runtime runner 迁移过来。当前返回 'v1-with-planned-stubs'
|
|
96
|
+
* 而非 'v2' 是为了让下游 skill(glue-run-test 等)能区分"真 v2"与"stub 形态"。
|
|
97
|
+
*/
|
|
98
|
+
export async function runRun(args) {
|
|
99
|
+
if (!existsSync(args.spec))
|
|
100
|
+
throw new Error(`spec 不存在: ${args.spec}`);
|
|
101
|
+
const testcaseRaw = JSON5.parse(readFileSync(args.testcase, 'utf8'));
|
|
102
|
+
const testcase = validateTestcase(testcaseRaw);
|
|
103
|
+
const planned = testcase.uncoveredCandidates.filter((item) => item.status === 'planned');
|
|
104
|
+
const currentSpec = readFileSync(args.spec, 'utf8');
|
|
105
|
+
const baseSpec = truncateAtMarker(currentSpec);
|
|
106
|
+
if (planned.length > 0) {
|
|
107
|
+
const appendedTests = renderAppendedTests(planned);
|
|
108
|
+
writeFileSync(args.spec, `${baseSpec}\n\n${AI_APPEND_MARKER}\n${appendedTests}\n`, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// 没有 planned 项时,仍然把 spec 收敛到 baseSpec(保证幂等:重跑能擦掉旧追加段)。
|
|
112
|
+
writeFileSync(args.spec, `${baseSpec}\n`, 'utf8');
|
|
113
|
+
}
|
|
114
|
+
const reportDir = path.join(args.projectDir ?? process.cwd(), 'e2e/glue-code-mvp/glue-report', new Date().toISOString().replace(/[:.]/g, '-'));
|
|
115
|
+
return { specPath: args.spec, reportDir, version: 'v1-with-planned-stubs' };
|
|
116
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spec 子命令参数(REQ-SPEC-01)。
|
|
3
|
+
*
|
|
4
|
+
* - testcase:testcase.json5 路径(必填,由 testcase 子命令产出);
|
|
5
|
+
* - out:.spec.ts 输出路径(必填,通常为 <tests-dir>/crud.spec.ts)。
|
|
6
|
+
*/
|
|
7
|
+
export type SpecArgs = {
|
|
8
|
+
testcase: string;
|
|
9
|
+
out: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* spec 子命令产物。
|
|
13
|
+
*
|
|
14
|
+
* @property specPath - 写盘后的 .spec.ts 路径。
|
|
15
|
+
* @property version - 'v1' 表示只含 coveredActions 胶水部分,待 run 子命令追加 v2。
|
|
16
|
+
*/
|
|
17
|
+
export type SpecResult = {
|
|
18
|
+
specPath: string;
|
|
19
|
+
version: 'v1';
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 解析 spec 子命令参数。
|
|
23
|
+
*
|
|
24
|
+
* @param argv - 已剥离子命令名的命令行参数。
|
|
25
|
+
* @returns SpecArgs。
|
|
26
|
+
* @throws 缺少必填参数或参数对非法时抛错。
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseSpecArgs(argv: string[]): SpecArgs;
|
|
29
|
+
/**
|
|
30
|
+
* spec 子命令主体(REQ-SPEC-01):只生成 v1 初始版 .spec.ts,不启动浏览器。
|
|
31
|
+
*
|
|
32
|
+
* v1 = 仅 coveredActions 胶水部分(顶部注释 `// === 胶水模式生成 ===`);
|
|
33
|
+
* run 子命令后续在底部追加 AI 推理段产 v2。
|
|
34
|
+
*
|
|
35
|
+
* 实现:
|
|
36
|
+
* 1. 校验 testcase.json5 通过 validateTestcase(v2 schema);
|
|
37
|
+
* 2. 门禁检查:用例必须全部 confirmed、未覆盖项不得为 candidate/unresolved(REQ-INFER-007);
|
|
38
|
+
* 3. 加载 contract.json(位于 <tests-dir>/.ep-stage/contract.json;不存在时报错引导用户先跑 testcase 子命令);
|
|
39
|
+
* 4. 调 generateStageSkeletonCrudSpec 生成基线骨架脚本;
|
|
40
|
+
* 5. 头部插入 SKELETON_MARKER_LINE 标记,写盘。
|
|
41
|
+
*
|
|
42
|
+
* 消费边界(REQ-INFER-008):只读 testcase.json5 + contract.json,不读 inference-trace.json。
|
|
43
|
+
*
|
|
44
|
+
* @param args - 解析后的参数。
|
|
45
|
+
* @returns SpecResult(含 version='v1' 显式标识阶段)。
|
|
46
|
+
*/
|
|
47
|
+
export declare function runSpec(args: SpecArgs): SpecResult;
|
|
48
|
+
//# sourceMappingURL=spec.d.ts.map
|