@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,300 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import JSON5 from 'json5';
|
|
5
|
+
import { runProbeAndCoverage } from './probe.js';
|
|
6
|
+
import { createSoftlinkWithFallback } from '../util/softlink.js';
|
|
7
|
+
import { i18nTestcase } from '../util/i18n-testcase.js';
|
|
8
|
+
import { extractCodeListSummary } from '../extractors/code-list.js';
|
|
9
|
+
import { resolveMaterialInventory } from '../material/material-inventory.js';
|
|
10
|
+
import { validateInferenceTrace } from '../validation/inference-trace.js';
|
|
11
|
+
import { writeProjectIndex } from '../context/stage-context.js';
|
|
12
|
+
/**
|
|
13
|
+
* 解析 testcase 子命令参数。
|
|
14
|
+
*
|
|
15
|
+
* @param argv - 已剥离子命令名的命令行参数。
|
|
16
|
+
* @returns 解析后的 GenerateTestcaseArgs。
|
|
17
|
+
* @throws 缺少 --code-list 或 --tests-dir 时抛错。
|
|
18
|
+
*/
|
|
19
|
+
export function parseGenerateTestcaseArgs(argv) {
|
|
20
|
+
const result = {};
|
|
21
|
+
const normalizedArgv = argv.filter((arg) => arg !== '--');
|
|
22
|
+
for (let index = 0; index < normalizedArgv.length; index += 2) {
|
|
23
|
+
const key = normalizedArgv[index];
|
|
24
|
+
const value = normalizedArgv[index + 1];
|
|
25
|
+
if (!key?.startsWith('--') || !value) {
|
|
26
|
+
throw new Error(`无效参数对: ${key ?? '<empty>'}`);
|
|
27
|
+
}
|
|
28
|
+
result[key.slice(2)] = value;
|
|
29
|
+
}
|
|
30
|
+
if (!result['code-list'])
|
|
31
|
+
throw new Error('缺少 --code-list');
|
|
32
|
+
if (!result['tests-dir'])
|
|
33
|
+
throw new Error('缺少 --tests-dir');
|
|
34
|
+
return {
|
|
35
|
+
codeList: result['code-list'],
|
|
36
|
+
testsDir: result['tests-dir'],
|
|
37
|
+
scenario: result.scenario,
|
|
38
|
+
hints: result.hints,
|
|
39
|
+
projectDir: result['project-dir'],
|
|
40
|
+
requirementDir: result['requirement-dir'],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* scenario 3 档决策(REQ-SCENE-01):
|
|
45
|
+
* 1. --scenario CLI 参数(最高优先级)
|
|
46
|
+
* 2. ModuleHints 文件路径包含 `scenario=<value>` 字串(粗推断)
|
|
47
|
+
* 3. 默认 "crud.single-page"(与 i18n 表 / skeleton scenario 一致)
|
|
48
|
+
*
|
|
49
|
+
* @param args - 解析后的参数。
|
|
50
|
+
* @returns 决策结果。
|
|
51
|
+
*/
|
|
52
|
+
export function decideScenario(args) {
|
|
53
|
+
if (args.scenario)
|
|
54
|
+
return args.scenario;
|
|
55
|
+
if (args.hints && args.hints.includes('scenario=')) {
|
|
56
|
+
const inferred = args.hints.split('scenario=')[1]?.split(/\s|,/)[0];
|
|
57
|
+
if (inferred)
|
|
58
|
+
return inferred;
|
|
59
|
+
}
|
|
60
|
+
return 'crud.single-page';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 把 TestcaseV2 渲染成中文 testcase.md(REQ-UX-03)。
|
|
64
|
+
*
|
|
65
|
+
* 设计:
|
|
66
|
+
* - 字段名 / 状态值通过 i18nTestcase() 翻译;
|
|
67
|
+
* - `unresolved` 字段标题硬编码「未确认项」(i18n 表中 `unresolved` 解析为状态值「未解决」);
|
|
68
|
+
* - 每次重跑覆盖(REQ-DATA-03 由调用方 writeFileSync 触发)。
|
|
69
|
+
*
|
|
70
|
+
* @param testcase - TestcaseV2 顶层结构。
|
|
71
|
+
* @returns Markdown 文本。
|
|
72
|
+
*/
|
|
73
|
+
export function renderTestcaseMarkdown(testcase) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
lines.push(`# ${testcase.moduleName} 胶水测试用例`);
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push(`- 模块 ID:${testcase.moduleId}`);
|
|
78
|
+
lines.push(`- ${i18nTestcase('scenario')}:${i18nTestcase(testcase.scenario)}`);
|
|
79
|
+
lines.push(`- ${i18nTestcase('menuPath')}:${testcase.menuPath || '(未填写)'}`);
|
|
80
|
+
lines.push(`- ${i18nTestcase('requiredSystems')}:${testcase.requiredSystems.map((s) => s.url || '(待填写)').join(',')}`);
|
|
81
|
+
lines.push(`- ${i18nTestcase('requiredRoles')}:${testcase.requiredRoles.map((r) => r.role).join(',')}`);
|
|
82
|
+
lines.push('');
|
|
83
|
+
lines.push(`## ${i18nTestcase('coveredActions')}`);
|
|
84
|
+
if (testcase.coveredActions.length === 0)
|
|
85
|
+
lines.push('(无)');
|
|
86
|
+
for (const action of testcase.coveredActions) {
|
|
87
|
+
lines.push(`- ${action.actionId}(${action.kind})证据:${action.evidence.join(' / ')}`);
|
|
88
|
+
}
|
|
89
|
+
lines.push('');
|
|
90
|
+
lines.push(`## ${i18nTestcase('uncoveredCandidates')}`);
|
|
91
|
+
if (testcase.uncoveredCandidates.length === 0)
|
|
92
|
+
lines.push('(无)');
|
|
93
|
+
for (const cand of testcase.uncoveredCandidates) {
|
|
94
|
+
lines.push(`- ${cand.actionId} ${cand.label}|状态:${i18nTestcase(cand.status)}|${i18nTestcase('requiredRole')}:${cand.requiredRole}`);
|
|
95
|
+
lines.push(` - ${i18nTestcase('businessIntent')}:${cand.businessIntent}`);
|
|
96
|
+
lines.push(` - ${i18nTestcase('assertionExpectation')}:${cand.assertionExpectation}`);
|
|
97
|
+
lines.push(` - ${i18nTestcase('evidence')}:${cand.evidence.join(' / ')}`);
|
|
98
|
+
}
|
|
99
|
+
lines.push('');
|
|
100
|
+
lines.push('## 用例');
|
|
101
|
+
for (const c of testcase.cases) {
|
|
102
|
+
lines.push(`### ${c.title}(${i18nTestcase('caseId')}:${c.caseId})`);
|
|
103
|
+
lines.push(`- ${i18nTestcase('scenario')}:${i18nTestcase(c.scenario)}|${i18nTestcase('reviewStatus')}:${i18nTestcase(c.reviewStatus)}|${i18nTestcase('requiredRole')}:${c.requiredRole}`);
|
|
104
|
+
lines.push(`- ${i18nTestcase('evidence')}:${c.evidence.join(' / ')}`);
|
|
105
|
+
lines.push('- 步骤:');
|
|
106
|
+
for (const s of c.steps)
|
|
107
|
+
lines.push(` 1. ${s}`);
|
|
108
|
+
lines.push('- 断言:');
|
|
109
|
+
for (const a of c.assertions)
|
|
110
|
+
lines.push(` - ${a}`);
|
|
111
|
+
// 硬编码标题「未确认项」:i18nTestcase('unresolved') 返回状态值「未解决」(spec §4.6 trade-off)。
|
|
112
|
+
lines.push(`- 未确认项:${c.unresolved.length === 0 ? '(无)' : c.unresolved.join(';')}`);
|
|
113
|
+
lines.push('');
|
|
114
|
+
}
|
|
115
|
+
lines.push('## 推理摘要');
|
|
116
|
+
lines.push(testcase.reasoningSummary.conclusion);
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push('证据链:');
|
|
119
|
+
for (const ev of testcase.reasoningSummary.evidenceChain)
|
|
120
|
+
lines.push(`- ${ev}`);
|
|
121
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 渲染 credentials.template.json5 数组内容。每条对应 requiredRoles[i],
|
|
125
|
+
* url/systemName 复用 requiredSystems[0](多系统场景由用户手工补齐)。
|
|
126
|
+
*
|
|
127
|
+
* @param testcase - TestcaseV2。
|
|
128
|
+
* @returns JSON5 字符串。
|
|
129
|
+
*/
|
|
130
|
+
function renderCredentialsTemplate(testcase) {
|
|
131
|
+
const baseUrl = testcase.requiredSystems[0]?.url ?? '';
|
|
132
|
+
const baseSystem = testcase.requiredSystems[0]?.systemName ?? '';
|
|
133
|
+
const entries = testcase.requiredRoles.map((roleEntry) => ({
|
|
134
|
+
url: baseUrl,
|
|
135
|
+
username: '',
|
|
136
|
+
password: '',
|
|
137
|
+
role: roleEntry.role,
|
|
138
|
+
systemName: baseSystem,
|
|
139
|
+
}));
|
|
140
|
+
return `${JSON5.stringify(entries, null, 2)}\n`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 从 code_list.md 解析 testcase 子命令所需的 HTML / Action 物料目录。
|
|
144
|
+
*
|
|
145
|
+
* @param codeListPath - code_list.md 绝对路径。
|
|
146
|
+
* @returns probe 阶段所需的 webapp / java 目录。
|
|
147
|
+
* @throws 无法唯一解析 HTML 或 Action 目录时抛错。
|
|
148
|
+
*/
|
|
149
|
+
function resolveProbeInputsFromCodeList(codeListPath) {
|
|
150
|
+
const codeList = extractCodeListSummary(codeListPath);
|
|
151
|
+
const materialResult = resolveMaterialInventory({
|
|
152
|
+
codeListPath,
|
|
153
|
+
codeList,
|
|
154
|
+
debugOverrides: {},
|
|
155
|
+
});
|
|
156
|
+
if (materialResult.resolvedInputs.webappDir.status !== 'resolved') {
|
|
157
|
+
throw new Error(`code_list.md 无法唯一解析 HTML 目录:${codeListPath}`);
|
|
158
|
+
}
|
|
159
|
+
if (materialResult.resolvedInputs.javaActionsDir.status !== 'resolved') {
|
|
160
|
+
throw new Error(`code_list.md 无法唯一解析 Action 目录:${codeListPath}`);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
docsDir: materialResult.resolvedInputs.docsDir.status === 'resolved'
|
|
164
|
+
? materialResult.resolvedInputs.docsDir.path
|
|
165
|
+
: undefined,
|
|
166
|
+
webappDir: materialResult.resolvedInputs.webappDir.path,
|
|
167
|
+
javaActionsDir: materialResult.resolvedInputs.javaActionsDir.path,
|
|
168
|
+
hintsPath: materialResult.resolvedInputs.hintsPath.status === 'resolved'
|
|
169
|
+
? materialResult.resolvedInputs.hintsPath.path
|
|
170
|
+
: undefined,
|
|
171
|
+
moduleId: materialResult.resolvedInputs.moduleId.status === 'resolved'
|
|
172
|
+
? materialResult.resolvedInputs.moduleId.value
|
|
173
|
+
: undefined,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 往 projects.index.json5 对应 project 的 requirements[] 追加(去重)一条需求条目。
|
|
178
|
+
*
|
|
179
|
+
* 设计:
|
|
180
|
+
* - 读 index 时同时保留 systems[](writeProjectIndex 整体覆写,丢失 systems 会丢凭据缓存);
|
|
181
|
+
* - 用 JSON5.parse(readFileSync) 直接读,不复用 readProjectIndexRaw(后者未导出且丢 systems);
|
|
182
|
+
* - 按 projectDir 精确匹配 project,找不到抛中文错误引导先跑 /glue-create-project;
|
|
183
|
+
* - 同 name 的 requirement 去重:存在则更新 requirementDir/testsDir/lastRunStatus,不存在则追加。
|
|
184
|
+
*
|
|
185
|
+
* @param input - projectDir / requirementName / requirementDir / testsDir / homeDir。
|
|
186
|
+
* @returns 写入后的索引文件路径。
|
|
187
|
+
* @throws projectDir 不在 index 时抛中文错误。
|
|
188
|
+
*/
|
|
189
|
+
function appendRequirementToProjectIndex(input) {
|
|
190
|
+
const homeDir = input.homeDir ?? os.homedir();
|
|
191
|
+
const indexPath = path.join(homeDir, '.ep-stage', 'projects.index.json5');
|
|
192
|
+
let parsed;
|
|
193
|
+
if (existsSync(indexPath)) {
|
|
194
|
+
parsed = JSON5.parse(readFileSync(indexPath, 'utf8'));
|
|
195
|
+
parsed.projects = parsed.projects ?? [];
|
|
196
|
+
parsed.systems = parsed.systems ?? [];
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
parsed = { projects: [], systems: [] };
|
|
200
|
+
}
|
|
201
|
+
const targetProjectDir = path.resolve(input.projectDir);
|
|
202
|
+
const projectIdx = parsed.projects.findIndex((p) => path.resolve(p.projectDir) === targetProjectDir);
|
|
203
|
+
if (projectIdx === -1) {
|
|
204
|
+
throw new Error(`未在 ~/.ep-stage/projects.index.json5 找到 projectDir=${input.projectDir} 的 glue 项目,请先跑 /glue-create-project 创建脚手架`);
|
|
205
|
+
}
|
|
206
|
+
const projects = parsed.projects;
|
|
207
|
+
const project = projects[projectIdx];
|
|
208
|
+
project.requirements = project.requirements ?? [];
|
|
209
|
+
const entry = {
|
|
210
|
+
name: input.requirementName,
|
|
211
|
+
requirementDir: input.requirementDir,
|
|
212
|
+
testsDir: input.testsDir,
|
|
213
|
+
lastRunStatus: 'never',
|
|
214
|
+
};
|
|
215
|
+
// 去重:同 name 存在则更新,不存在则追加。
|
|
216
|
+
const existIdx = project.requirements.findIndex((r) => r.name === entry.name);
|
|
217
|
+
if (existIdx >= 0) {
|
|
218
|
+
project.requirements[existIdx] = entry;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
project.requirements.push(entry);
|
|
222
|
+
}
|
|
223
|
+
return writeProjectIndex({ homeDir, projects, systems: parsed.systems });
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* testcase 子命令主体(REQ-CHAIN-01 / REQ-CHAIN-02 / REQ-SCENE-01 / REQ-DATA-02)。
|
|
227
|
+
*
|
|
228
|
+
* 流程:
|
|
229
|
+
* 1. 决定 scenario(3 档优先级,写入 testcase 顶层 scenario 字段);
|
|
230
|
+
* 2. 调 runProbeAndCoverage 钻探上游物料 + 装配 TestcaseV2;
|
|
231
|
+
* 3. 落盘 testcase.json5 + testcase.md(中文 i18n) + credentials.template.json5;
|
|
232
|
+
* 4. 软链上游 code_list.md → <tests-dir>/code_list.md;
|
|
233
|
+
* 5. 返回推断结果(供 skill Step 5 交互确认 systemUrl / credentialsCache 命中)。
|
|
234
|
+
*
|
|
235
|
+
* 注意:contract 提取通过 probe 内部完成,不直接复用 dev/extract-contract.ts 的 CLI 入口
|
|
236
|
+
* (probe + extract-contract 共享同一组 buildCrudBusinessModuleContract + extractor 依赖,
|
|
237
|
+
* 二者各自负责自己的 I/O 边界,保持职责单一)。
|
|
238
|
+
*
|
|
239
|
+
* @param args - 已解析参数。
|
|
240
|
+
* @returns 产物路径 + 推断结果。
|
|
241
|
+
*/
|
|
242
|
+
export function runGenerateTestcase(args) {
|
|
243
|
+
const scenario = decideScenario(args);
|
|
244
|
+
mkdirSync(args.testsDir, { recursive: true });
|
|
245
|
+
const requirementDir = args.requirementDir ?? path.dirname(args.codeList);
|
|
246
|
+
const resolvedInputs = resolveProbeInputsFromCodeList(args.codeList);
|
|
247
|
+
const docsDir = resolvedInputs.docsDir ?? requirementDir;
|
|
248
|
+
const moduleId = resolvedInputs.moduleId ?? path.basename(args.testsDir);
|
|
249
|
+
const { contract, testcaseV2, inferenceTrace } = runProbeAndCoverage({
|
|
250
|
+
moduleId,
|
|
251
|
+
codeListPath: args.codeList,
|
|
252
|
+
docsDir,
|
|
253
|
+
webappDir: resolvedInputs.webappDir,
|
|
254
|
+
javaActionsDir: resolvedInputs.javaActionsDir,
|
|
255
|
+
hintsPath: args.hints ?? resolvedInputs.hintsPath,
|
|
256
|
+
scenario,
|
|
257
|
+
});
|
|
258
|
+
const epStageDir = path.join(args.testsDir, '.ep-stage');
|
|
259
|
+
mkdirSync(epStageDir, { recursive: true });
|
|
260
|
+
const contractPath = path.join(epStageDir, 'contract.json');
|
|
261
|
+
const inferenceTracePath = path.join(epStageDir, 'inference-trace.json');
|
|
262
|
+
const testcasePath = path.join(args.testsDir, 'testcase.json5');
|
|
263
|
+
const testcaseMdPath = path.join(args.testsDir, 'testcase.md');
|
|
264
|
+
const credentialsTemplatePath = path.join(args.testsDir, 'credentials.template.json5');
|
|
265
|
+
const credentialsActualPath = path.join(args.testsDir, 'credentials.json5');
|
|
266
|
+
// 落盘 contract.json 供 spec / run 子命令复用(避免重复钻探)。
|
|
267
|
+
writeFileSync(contractPath, `${JSON.stringify(contract, null, 2)}\n`, 'utf8');
|
|
268
|
+
// 落盘 inference-trace.json(解释链产物,REQ-INFER-004 / REQ-INFER-008;trace 只做解释,不驱动 spec)。
|
|
269
|
+
writeFileSync(inferenceTracePath, `${JSON.stringify(validateInferenceTrace(inferenceTrace), null, 2)}\n`, 'utf8');
|
|
270
|
+
writeFileSync(testcasePath, `${JSON5.stringify(testcaseV2, null, 2)}\n`, 'utf8');
|
|
271
|
+
writeFileSync(credentialsTemplatePath, renderCredentialsTemplate(testcaseV2), 'utf8');
|
|
272
|
+
writeFileSync(testcaseMdPath, renderTestcaseMarkdown(testcaseV2), 'utf8');
|
|
273
|
+
createSoftlinkWithFallback(args.codeList, path.join(args.testsDir, 'code_list.md'));
|
|
274
|
+
// 往 projects.index.json5 对应 project 追加(去重)一条 requirement 条目(skill Step 7)。
|
|
275
|
+
// 仅当显式传入 projectDir 时追加(skill Step 4 命令总带 --project-dir);
|
|
276
|
+
// 未传则跳过 —— 不反推 projectDir,避免非标准 testsDir 位置静默写入错误 project。
|
|
277
|
+
if (args.projectDir) {
|
|
278
|
+
appendRequirementToProjectIndex({
|
|
279
|
+
projectDir: args.projectDir,
|
|
280
|
+
// 需求名 = testsDir 的 basename(与 moduleId 来源一致:resolvedInputs.moduleId ?? path.basename(args.testsDir))。
|
|
281
|
+
requirementName: path.basename(args.testsDir),
|
|
282
|
+
requirementDir,
|
|
283
|
+
testsDir: args.testsDir,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
testcasePath,
|
|
288
|
+
testcaseMdPath,
|
|
289
|
+
credentialsTemplatePath,
|
|
290
|
+
inferenceTracePath,
|
|
291
|
+
inference: {
|
|
292
|
+
scenario,
|
|
293
|
+
requiredSystems: testcaseV2.requiredSystems,
|
|
294
|
+
requiredRoles: testcaseV2.requiredRoles,
|
|
295
|
+
credentialsTemplatePath,
|
|
296
|
+
credentialsActualPath,
|
|
297
|
+
cachedSystemsHit: [],
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ep-stage-skill CLI 统一入口(REQ-CLI-02 修订):1 bin + 3 子命令。
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* ep-stage-skill testcase --code-list <p> --tests-dir <p> [--scenario <v>] [--hints <p>]
|
|
7
|
+
* ep-stage-skill spec --testcase <p> --out <p>
|
|
8
|
+
* ep-stage-skill run --spec <p> --testcase <p> [--headless false]
|
|
9
|
+
*
|
|
10
|
+
* 无 contract 子命令(contract 提取作为 testcase 子命令内部步骤;
|
|
11
|
+
* 开发期独立调试入口为 dev/extract-contract.ts,被 pnpm generate:crud-contract 调用)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function main(argv?: string[]): Promise<unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* 打印 CLI help(仅 testcase / spec / run 3 子命令,无 contract)。
|
|
16
|
+
*/
|
|
17
|
+
export declare function printHelp(): void;
|
|
18
|
+
/**
|
|
19
|
+
* 判断当前 CLI 文件是否以“直接执行”方式启动。
|
|
20
|
+
*
|
|
21
|
+
* @param importMetaUrl - 当前模块的 import.meta.url。
|
|
22
|
+
* @param argv1 - 进程入口参数,可能是 npm bin 软链路径。
|
|
23
|
+
* @returns 若当前模块就是实际执行入口则返回 true。
|
|
24
|
+
*/
|
|
25
|
+
export declare function isDirectExecution(importMetaUrl: string, argv1: string | undefined): boolean;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":";AAOA;;;;;;;;;;GAUG;AACH,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB1E;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,IAAI,CAYhC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAS3F"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parseGenerateTestcaseArgs, runGenerateTestcase } from './generate-testcase.js';
|
|
5
|
+
import { parseSpecArgs, runSpec } from './spec.js';
|
|
6
|
+
import { parseRunArgs, runRun } from './run.js';
|
|
7
|
+
/**
|
|
8
|
+
* ep-stage-skill CLI 统一入口(REQ-CLI-02 修订):1 bin + 3 子命令。
|
|
9
|
+
*
|
|
10
|
+
* 用法:
|
|
11
|
+
* ep-stage-skill testcase --code-list <p> --tests-dir <p> [--scenario <v>] [--hints <p>]
|
|
12
|
+
* ep-stage-skill spec --testcase <p> --out <p>
|
|
13
|
+
* ep-stage-skill run --spec <p> --testcase <p> [--headless false]
|
|
14
|
+
*
|
|
15
|
+
* 无 contract 子命令(contract 提取作为 testcase 子命令内部步骤;
|
|
16
|
+
* 开发期独立调试入口为 dev/extract-contract.ts,被 pnpm generate:crud-contract 调用)。
|
|
17
|
+
*/
|
|
18
|
+
export async function main(argv = process.argv) {
|
|
19
|
+
const [, , subcommand, ...rest] = argv;
|
|
20
|
+
const args = rest.filter((arg) => arg !== '--');
|
|
21
|
+
switch (subcommand) {
|
|
22
|
+
case 'testcase':
|
|
23
|
+
return runGenerateTestcase(parseGenerateTestcaseArgs(args));
|
|
24
|
+
case 'spec':
|
|
25
|
+
return runSpec(parseSpecArgs(args));
|
|
26
|
+
case 'run':
|
|
27
|
+
return runRun(parseRunArgs(args));
|
|
28
|
+
case undefined:
|
|
29
|
+
case '--help':
|
|
30
|
+
case '-h':
|
|
31
|
+
printHelp();
|
|
32
|
+
return;
|
|
33
|
+
default:
|
|
34
|
+
console.error(`未知子命令: ${subcommand}。可用: testcase / spec / run`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 打印 CLI help(仅 testcase / spec / run 3 子命令,无 contract)。
|
|
40
|
+
*/
|
|
41
|
+
export function printHelp() {
|
|
42
|
+
console.log(`ep-stage-skill — 胶水测试链路 CLI
|
|
43
|
+
|
|
44
|
+
用法:
|
|
45
|
+
ep-stage-skill testcase --code-list <path> --tests-dir <path> [--scenario <value>] [--hints <path>]
|
|
46
|
+
ep-stage-skill spec --testcase <path> --out <path>
|
|
47
|
+
ep-stage-skill run --spec <path> --testcase <path> [--headless false]
|
|
48
|
+
|
|
49
|
+
子命令:
|
|
50
|
+
testcase 钻探上游物料 + 生成 testcase v2 + credentials.template + 软链 code_list
|
|
51
|
+
spec 基于 testcase + contract 生成 v1 初始版 .spec.ts(只生成不执行)
|
|
52
|
+
run 启动 Playwright 跑胶水部分 + AI 推理追加 uncoveredCandidates → 产 v2 + 报告`);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 判断当前 CLI 文件是否以“直接执行”方式启动。
|
|
56
|
+
*
|
|
57
|
+
* @param importMetaUrl - 当前模块的 import.meta.url。
|
|
58
|
+
* @param argv1 - 进程入口参数,可能是 npm bin 软链路径。
|
|
59
|
+
* @returns 若当前模块就是实际执行入口则返回 true。
|
|
60
|
+
*/
|
|
61
|
+
export function isDirectExecution(importMetaUrl, argv1) {
|
|
62
|
+
try {
|
|
63
|
+
if (!argv1) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return realpathSync(fileURLToPath(importMetaUrl)) === realpathSync(argv1);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// 直接执行时调用 main;作为模块 import 时由调用方决定是否执行。
|
|
73
|
+
if (isDirectExecution(import.meta.url, process.argv[1])) {
|
|
74
|
+
await main();
|
|
75
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CrudBusinessModuleContract } from '../contracts/crud-business-module.js';
|
|
2
|
+
import type { CoverageDiffTrace } from '../contracts/observable-chain.js';
|
|
3
|
+
import type { TestcaseV2 } from '../testcase/testcase-v2.js';
|
|
4
|
+
import type { InferenceTraceV1 } from '../contracts/inference-trace.js';
|
|
5
|
+
/**
|
|
6
|
+
* probe 子模块入参(REQ-CHAIN-01)。
|
|
7
|
+
*
|
|
8
|
+
* 把 run-gap-pipeline.ts 与 generate-testcase.ts 共用的「物料解析 → contract 构建 →
|
|
9
|
+
* skeleton coverage → coverage diff」段抽成单一入口,避免两侧复制实现。
|
|
10
|
+
*/
|
|
11
|
+
export type ProbeAndCoverageArgs = {
|
|
12
|
+
moduleId: string;
|
|
13
|
+
codeListPath: string;
|
|
14
|
+
docsDir: string;
|
|
15
|
+
webappDir: string;
|
|
16
|
+
javaActionsDir: string;
|
|
17
|
+
hintsPath?: string;
|
|
18
|
+
scenario: string;
|
|
19
|
+
menuPath?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* probe 子模块产物。
|
|
23
|
+
*
|
|
24
|
+
* @property contract - 标准化后的 CRUD 业务模块契约。
|
|
25
|
+
* @property coverageDiff - 骨架 vs gap 覆盖度差异 trace。
|
|
26
|
+
* @property testcaseV2 - 装配后的 testcase v2 顶层结构(待 generate-testcase 写入 .json5)。
|
|
27
|
+
*/
|
|
28
|
+
export type ProbeAndCoverageResult = {
|
|
29
|
+
contract: CrudBusinessModuleContract;
|
|
30
|
+
coverageDiff: CoverageDiffTrace;
|
|
31
|
+
testcaseV2: TestcaseV2;
|
|
32
|
+
inferenceTrace: InferenceTraceV1;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* probe + coverage + testcase v2 装配的统一入口(REQ-CHAIN-01)。
|
|
36
|
+
*
|
|
37
|
+
* 把 run-gap-pipeline.ts 与 generate-testcase.ts 共用的物料解析 → contract → skeleton coverage →
|
|
38
|
+
* coverage diff 段抽成单一函数,避免 1881 行 run-gap-pipeline.ts 与新 CLI 双份维护。
|
|
39
|
+
*
|
|
40
|
+
* 注意:本函数只做钻探与装配,不写文件;写盘由调用方负责(保持 I/O 边界清晰)。
|
|
41
|
+
*
|
|
42
|
+
* @param args - 物料路径 + 场景。
|
|
43
|
+
* @returns contract + coverageDiff + 装配好的 TestcaseV2。
|
|
44
|
+
*/
|
|
45
|
+
export declare function runProbeAndCoverage(args: ProbeAndCoverageArgs): ProbeAndCoverageResult;
|
|
46
|
+
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../../src/cli/probe.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,0BAA0B,EAAe,MAAM,sCAAsC,CAAC;AACpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAI7D,OAAO,KAAK,EAAE,gBAAgB,EAAoE,MAAM,iCAAiC,CAAC;AAI1I;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,YAAY,EAAE,iBAAiB,CAAC;IAChC,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,gBAAgB,CAAC;CAClC,CAAC;AAoYF;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,sBAAsB,CAuFtF"}
|