@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,258 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* 判断 docsDir 下是否存在 PRD markdown 文件。
|
|
5
|
+
*
|
|
6
|
+
* PRD 判定规则(满足任一):
|
|
7
|
+
* - 文件名包含 `prd`(大小写不敏感)。
|
|
8
|
+
* - 文件正文包含 `## 4. 功能需求` 标题。
|
|
9
|
+
*
|
|
10
|
+
* @param docsDir - 需求文档目录。
|
|
11
|
+
* @returns PRD 文件绝对路径;不存在时返回 undefined。
|
|
12
|
+
*/
|
|
13
|
+
function findPrdMarkdown(docsDir) {
|
|
14
|
+
if (!existsSync(docsDir)) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const candidates = readdirSync(docsDir).filter((file) => file.endsWith('.md'));
|
|
18
|
+
for (const file of candidates) {
|
|
19
|
+
const fullPath = path.join(docsDir, file);
|
|
20
|
+
if (/prd/i.test(file)) {
|
|
21
|
+
return fullPath;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// 文件名不含 prd 时回落到正文匹配。
|
|
25
|
+
for (const file of candidates) {
|
|
26
|
+
const fullPath = path.join(docsDir, file);
|
|
27
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
28
|
+
if (/##\s*4\.\s*功能需求/.test(content)) {
|
|
29
|
+
return fullPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 判断 docsDir 或 code_list 物料引用中是否存在 spec.yaml。
|
|
36
|
+
*
|
|
37
|
+
* @param codeList - code_list 摘要。
|
|
38
|
+
* @param docsDir - 需求文档目录。
|
|
39
|
+
* @returns spec.yaml 绝对路径;不存在时返回 undefined。
|
|
40
|
+
*/
|
|
41
|
+
function findSpecYaml(codeList, docsDir) {
|
|
42
|
+
const fromRefs = codeList.materialRefs.find((ref) => ref.kind === 'spec' && ref.exists);
|
|
43
|
+
if (fromRefs) {
|
|
44
|
+
return fromRefs.path;
|
|
45
|
+
}
|
|
46
|
+
if (existsSync(docsDir)) {
|
|
47
|
+
const direct = readdirSync(docsDir).find((file) => file.endsWith('spec.yaml'));
|
|
48
|
+
if (direct) {
|
|
49
|
+
return path.join(docsDir, direct);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 把 HTML 页面字段登记为 UI 信号。
|
|
56
|
+
*
|
|
57
|
+
* @param pages - 已抽取的 HTML 页面列表。
|
|
58
|
+
* @returns UI 信号数组(字段、按钮、表格列、弹窗)。
|
|
59
|
+
*/
|
|
60
|
+
function collectUiSignals(pages) {
|
|
61
|
+
const signals = [];
|
|
62
|
+
for (const page of pages) {
|
|
63
|
+
for (const field of page.fields) {
|
|
64
|
+
signals.push({
|
|
65
|
+
pageId: page.pageId,
|
|
66
|
+
kind: 'field',
|
|
67
|
+
label: field.label,
|
|
68
|
+
value: field.name
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
for (const button of page.buttons) {
|
|
72
|
+
signals.push({
|
|
73
|
+
pageId: page.pageId,
|
|
74
|
+
kind: 'button',
|
|
75
|
+
label: button.label
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
for (const grid of page.grids) {
|
|
79
|
+
for (const column of grid.dataColumns) {
|
|
80
|
+
signals.push({
|
|
81
|
+
pageId: page.pageId,
|
|
82
|
+
kind: 'gridColumn',
|
|
83
|
+
label: column.headerText || column.field,
|
|
84
|
+
value: column.field
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const dialog of page.dialogs) {
|
|
89
|
+
signals.push({
|
|
90
|
+
pageId: page.pageId,
|
|
91
|
+
kind: 'dialog',
|
|
92
|
+
label: dialog.title,
|
|
93
|
+
value: dialog.pagePath
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return signals;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 把 Java Action 登记为后端信号。
|
|
101
|
+
*
|
|
102
|
+
* @param actions - 已抽取的 Java Action 列表。
|
|
103
|
+
* @returns 后端信号数组(方法名、callback 消息)。
|
|
104
|
+
*/
|
|
105
|
+
function collectBackendSignals(actions) {
|
|
106
|
+
const signals = [];
|
|
107
|
+
for (const action of actions) {
|
|
108
|
+
for (const method of action.methods) {
|
|
109
|
+
signals.push({
|
|
110
|
+
controllerName: action.controllerName,
|
|
111
|
+
kind: 'method',
|
|
112
|
+
value: method
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
for (const message of action.messages) {
|
|
116
|
+
signals.push({
|
|
117
|
+
controllerName: action.controllerName,
|
|
118
|
+
kind: 'callbackMessage',
|
|
119
|
+
value: message
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return signals;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 跨页面汇总字段名候选(去重)。
|
|
127
|
+
*
|
|
128
|
+
* @param pages - 已抽取的 HTML 页面列表。
|
|
129
|
+
* @returns 去重后的字段名数组。
|
|
130
|
+
*/
|
|
131
|
+
function collectFieldNames(pages) {
|
|
132
|
+
const names = new Set();
|
|
133
|
+
for (const page of pages) {
|
|
134
|
+
for (const field of page.fields) {
|
|
135
|
+
if (field.name) {
|
|
136
|
+
names.add(field.name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const grid of page.grids) {
|
|
140
|
+
for (const column of grid.dataColumns) {
|
|
141
|
+
if (column.field) {
|
|
142
|
+
names.add(column.field);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return Array.from(names);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 从 Java callback 消息中提取业务规则线索。
|
|
151
|
+
*
|
|
152
|
+
* @param actions - 已抽取的 Java Action 列表。
|
|
153
|
+
* @returns 业务规则描述数组。
|
|
154
|
+
*/
|
|
155
|
+
function collectBusinessRules(actions) {
|
|
156
|
+
const rules = [];
|
|
157
|
+
for (const action of actions) {
|
|
158
|
+
for (const message of action.messages) {
|
|
159
|
+
if (/不可删除|不允许删除|禁止删除/.test(message)) {
|
|
160
|
+
rules.push(`删除受业务规则限制:${message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return Array.from(new Set(rules));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 构建 sourceAnalysis 章节。
|
|
168
|
+
*
|
|
169
|
+
* 实现规则:
|
|
170
|
+
* - docsDir 下存在 PRD markdown(文件名含 prd 或正文含 `## 4. 功能需求`)时登记 prd_markdown source。
|
|
171
|
+
* - HTML 页面登记为 uiSignals(字段、按钮、表格列、弹窗)。
|
|
172
|
+
* - Java Action 登记为 backendSignals(方法名、callback 消息)。
|
|
173
|
+
* - 无 spec.yaml 时 mode='prd+html+java';有 spec.yaml 时 mode='extracted'。
|
|
174
|
+
*
|
|
175
|
+
* @param input - 输入参数(codeListPath、docsDir、codeList、pages、actions)。
|
|
176
|
+
* @returns 源分析快照。
|
|
177
|
+
*/
|
|
178
|
+
export function buildSourceAnalysis(input) {
|
|
179
|
+
const sources = [];
|
|
180
|
+
const unresolved = [];
|
|
181
|
+
const prdPath = findPrdMarkdown(input.docsDir);
|
|
182
|
+
if (prdPath) {
|
|
183
|
+
sources.push({
|
|
184
|
+
kind: 'prd_markdown',
|
|
185
|
+
path: prdPath,
|
|
186
|
+
provides: ['businessRules', 'featureList', 'fieldDescriptions']
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
unresolved.push('docsDir 下未找到 PRD markdown');
|
|
191
|
+
}
|
|
192
|
+
const specYamlPath = findSpecYaml(input.codeList, input.docsDir);
|
|
193
|
+
if (specYamlPath) {
|
|
194
|
+
sources.push({
|
|
195
|
+
kind: 'spec_yaml',
|
|
196
|
+
path: specYamlPath,
|
|
197
|
+
provides: ['contractSlots', 'fieldContracts', 'flowContracts']
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
for (const page of input.pages) {
|
|
201
|
+
sources.push({
|
|
202
|
+
kind: 'html',
|
|
203
|
+
path: page.path,
|
|
204
|
+
provides: ['searchFields', 'gridColumns', 'buttons', 'dialogs']
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
for (const action of input.actions) {
|
|
208
|
+
sources.push({
|
|
209
|
+
kind: 'java_action',
|
|
210
|
+
path: action.path,
|
|
211
|
+
provides: ['backendMethods', 'callbackMessages', 'deletePolicyEvidence']
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const uiSignals = collectUiSignals(input.pages);
|
|
215
|
+
const backendSignals = collectBackendSignals(input.actions);
|
|
216
|
+
const fields = collectFieldNames(input.pages);
|
|
217
|
+
const businessRules = collectBusinessRules(input.actions);
|
|
218
|
+
const hasPrd = Boolean(prdPath);
|
|
219
|
+
const hasHtml = input.pages.length > 0;
|
|
220
|
+
const hasJava = input.actions.length > 0;
|
|
221
|
+
const hasSpec = Boolean(specYamlPath);
|
|
222
|
+
let mode;
|
|
223
|
+
if (hasSpec) {
|
|
224
|
+
mode = 'extracted';
|
|
225
|
+
}
|
|
226
|
+
else if (hasPrd && hasHtml && hasJava) {
|
|
227
|
+
mode = 'prd+html+java';
|
|
228
|
+
}
|
|
229
|
+
else if (hasHtml || hasJava) {
|
|
230
|
+
mode = 'inferred';
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
mode = 'mixed';
|
|
234
|
+
}
|
|
235
|
+
let confidence;
|
|
236
|
+
if (hasSpec) {
|
|
237
|
+
confidence = 'high';
|
|
238
|
+
}
|
|
239
|
+
else if (hasPrd && hasHtml && hasJava) {
|
|
240
|
+
confidence = 'medium';
|
|
241
|
+
}
|
|
242
|
+
else if (hasHtml || hasJava) {
|
|
243
|
+
confidence = 'medium';
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
confidence = 'low';
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
mode,
|
|
250
|
+
confidence,
|
|
251
|
+
sources,
|
|
252
|
+
fields,
|
|
253
|
+
businessRules,
|
|
254
|
+
uiSignals,
|
|
255
|
+
backendSignals,
|
|
256
|
+
unresolved
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testcase-generator.d.ts","sourceRoot":"","sources":["../../../src/testcase/testcase-generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EAEb,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAOvF;;;;;;;;;GASG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,kBAAkB,CAAC,EAAE,YAAY,EAAE,CAAC;IACpC,YAAY,EAAE,iBAAiB,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"testcase-generator.d.ts","sourceRoot":"","sources":["../../../src/testcase/testcase-generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EAEb,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAOvF;;;;;;;;;GASG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,kBAAkB,CAAC,EAAE,YAAY,EAAE,CAAC;IACpC,YAAY,EAAE,iBAAiB,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;GAUG;AAKH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,2BAA2B,CA2GpG"}
|
|
@@ -11,6 +11,10 @@ import { mapContractToCrudTestcaseSlots, renderGlueTestcaseSkeleton, selectTestc
|
|
|
11
11
|
* @param input - 用例生成入参。
|
|
12
12
|
* @returns 用例 JSON + Markdown。
|
|
13
13
|
*/
|
|
14
|
+
// TODO(Task 3.1 convergence):本函数当前产 v1 形态 testcase 文档(仍被 run-gap-pipeline.ts 使用)。
|
|
15
|
+
// Task 1.6 的新 testcase 子命令改走 src/cli/probe.ts.assembleTestcaseV2 产 v2 形态。两条产线
|
|
16
|
+
// 必然在 Task 3.1(升级 testcase-schema.md 到 v2)合并:届时本函数签名升级为 v2,删除
|
|
17
|
+
// assembleTestcaseV2。在那之前任何对本函数的修改务必同步检视 probe.assembleTestcaseV2。
|
|
14
18
|
export function generateGlueTestcases(input) {
|
|
15
19
|
if (!input.contract || !input.menu) {
|
|
16
20
|
throw new Error('生成胶水测试用例必须提供 contract 和 menu,不能再由 moduleId/moduleName 自由拼装');
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** testcase.json5 v2 顶层结构(REQ-DATA-02)。字段名永远英文(机器格式)。 */
|
|
2
|
+
export type TestcaseV2 = {
|
|
3
|
+
schemaVersion: 'v2';
|
|
4
|
+
scenario: string;
|
|
5
|
+
moduleId: string;
|
|
6
|
+
moduleName: string;
|
|
7
|
+
menuPath: string;
|
|
8
|
+
requiredSystems: Array<{
|
|
9
|
+
url: string;
|
|
10
|
+
systemName?: string;
|
|
11
|
+
}>;
|
|
12
|
+
requiredRoles: Array<{
|
|
13
|
+
role: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
}>;
|
|
16
|
+
coveredActions: Array<{
|
|
17
|
+
actionId: string;
|
|
18
|
+
kind: string;
|
|
19
|
+
evidence: string[];
|
|
20
|
+
}>;
|
|
21
|
+
uncoveredCandidates: Array<{
|
|
22
|
+
actionId: string;
|
|
23
|
+
label: string;
|
|
24
|
+
status: 'planned' | 'candidate' | 'unresolved' | 'resolved';
|
|
25
|
+
requiredRole: string;
|
|
26
|
+
businessIntent: string;
|
|
27
|
+
assertionExpectation: string;
|
|
28
|
+
evidence: string[];
|
|
29
|
+
}>;
|
|
30
|
+
cases: Array<{
|
|
31
|
+
caseId: string;
|
|
32
|
+
title: string;
|
|
33
|
+
scenario: string;
|
|
34
|
+
reviewStatus: 'confirmed' | 'needs_review' | 'blocked';
|
|
35
|
+
requiredRole: string;
|
|
36
|
+
evidence: string[];
|
|
37
|
+
steps: string[];
|
|
38
|
+
assertions: string[];
|
|
39
|
+
unresolved: string[];
|
|
40
|
+
}>;
|
|
41
|
+
reasoningSummary: {
|
|
42
|
+
conclusion: string;
|
|
43
|
+
evidenceChain: string[];
|
|
44
|
+
alternatives: string[];
|
|
45
|
+
confidence: 'high' | 'medium' | 'low';
|
|
46
|
+
risks: string[];
|
|
47
|
+
needsHumanReview: boolean;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=testcase-v2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testcase-v2.d.ts","sourceRoot":"","sources":["../../../src/testcase/testcase-v2.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,MAAM,UAAU,GAAG;IACvB,aAAa,EAAE,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,cAAc,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC9E,mBAAmB,EAAE,KAAK,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;QAC5D,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,CAAC;QACvD,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC,CAAC;IACH,gBAAgB,EAAE;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,EAAE,CAAC;QACxB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtC,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,gBAAgB,EAAE,OAAO,CAAC;KAC3B,CAAC;CACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Credential } from '../validation/credentials.js';
|
|
2
|
+
/**
|
|
3
|
+
* 从需求目录读取 credentials.json5 数组形态凭据(REQ-ENV-02 / REQ-ENV-03)。
|
|
4
|
+
*
|
|
5
|
+
* 供 run 子命令按 cases[].requiredRole 匹配登录凭据。
|
|
6
|
+
*
|
|
7
|
+
* @param credentialsPath - <需求名>/credentials.json5 绝对路径。
|
|
8
|
+
* @returns 校验后的凭据数组。
|
|
9
|
+
* @throws 文件不存在或 schema 校验失败时抛结构化错误。
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadCredentials(credentialsPath: string): Credential[];
|
|
12
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/util/credentials.ts"],"names":[],"mappings":"AAEA,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAEpF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,eAAe,EAAE,MAAM,GAAG,UAAU,EAAE,CAQrE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import JSON5 from 'json5';
|
|
3
|
+
import { validateCredentials } from '../validation/credentials.js';
|
|
4
|
+
/**
|
|
5
|
+
* 从需求目录读取 credentials.json5 数组形态凭据(REQ-ENV-02 / REQ-ENV-03)。
|
|
6
|
+
*
|
|
7
|
+
* 供 run 子命令按 cases[].requiredRole 匹配登录凭据。
|
|
8
|
+
*
|
|
9
|
+
* @param credentialsPath - <需求名>/credentials.json5 绝对路径。
|
|
10
|
+
* @returns 校验后的凭据数组。
|
|
11
|
+
* @throws 文件不存在或 schema 校验失败时抛结构化错误。
|
|
12
|
+
*/
|
|
13
|
+
export function loadCredentials(credentialsPath) {
|
|
14
|
+
if (!existsSync(credentialsPath)) {
|
|
15
|
+
throw new Error(`credentials.json5 不存在: ${credentialsPath}(请先填好凭据,或重跑 glue-generate-testcase 交互输入)`);
|
|
16
|
+
}
|
|
17
|
+
const raw = JSON5.parse(readFileSync(credentialsPath, 'utf8'));
|
|
18
|
+
return validateCredentials(raw);
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 testcase.json5 中的英文字段名 / 状态值翻译成中文 testcase.md 显示文案。
|
|
3
|
+
*
|
|
4
|
+
* @param value - testcase.json5 中的英文 token(字段名 / 状态值 / 推荐动作 / 场景值)。
|
|
5
|
+
* @returns 对应中文文案;未在映射表中的 token 原样返回(兜底)。
|
|
6
|
+
*/
|
|
7
|
+
export declare function i18nTestcase(value: string): string;
|
|
8
|
+
//# sourceMappingURL=i18n-testcase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-testcase.d.ts","sourceRoot":"","sources":["../../../src/util/i18n-testcase.ts"],"names":[],"mappings":"AA+CA;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REQ-UX-03:testcase.json5 字段名 / 状态值 → testcase.md 中文显示文案。
|
|
3
|
+
*
|
|
4
|
+
* 设计原则(spec §4.6 约束):
|
|
5
|
+
* - testcase.json5 内部字段名 / 状态值永远英文(TypeScript types 一致 + AI Agent 解析稳定)。
|
|
6
|
+
* - testcase.md 是人类视图,CLI 写 .md 时调用本 util 把英文 token 翻译成中文。
|
|
7
|
+
* - 纯查表函数,无副作用,无 I/O;未知 token 原样返回(兜底保证可显示)。
|
|
8
|
+
*
|
|
9
|
+
* 已知 trade-off:spec §4.6 中 `unresolved` 同时是字段名(未确认项)和状态值(未解决)。
|
|
10
|
+
* 扁平 lookup 表无法同时持有两个映射 —— 当前实现保留「状态值 = 未解决」(后定义覆盖前者)。
|
|
11
|
+
* 字段名场景下 CLI 可在调用前对 key 做硬编码标签替换(如 `'unresolved'` 字段标题写死「未确认项」)。
|
|
12
|
+
*/
|
|
13
|
+
const I18N_MAP = {
|
|
14
|
+
// 字段名
|
|
15
|
+
caseId: '用例 ID',
|
|
16
|
+
actionId: '动作 ID',
|
|
17
|
+
scenario: '场景类型',
|
|
18
|
+
menuPath: '菜单路径',
|
|
19
|
+
requiredRole: '操作角色',
|
|
20
|
+
requiredSystems: '需求系统集',
|
|
21
|
+
requiredRoles: '需求角色集',
|
|
22
|
+
coveredActions: '骨架已命中动作',
|
|
23
|
+
uncoveredCandidates: '骨架未命中候选',
|
|
24
|
+
businessIntent: '业务意图',
|
|
25
|
+
assertionExpectation: '预期断言',
|
|
26
|
+
recommendedAction: '推荐动作',
|
|
27
|
+
reviewStatus: '审阅状态',
|
|
28
|
+
evidence: '证据',
|
|
29
|
+
// 状态值
|
|
30
|
+
confirmed: '已确认',
|
|
31
|
+
needs_review: '待审阅',
|
|
32
|
+
blocked: '已阻断',
|
|
33
|
+
planned: '已计划',
|
|
34
|
+
candidate: '候选',
|
|
35
|
+
unresolved: '未解决',
|
|
36
|
+
resolved: '已解决',
|
|
37
|
+
// 推荐动作
|
|
38
|
+
infer: '推理',
|
|
39
|
+
'supplement-hints': '补充 hints',
|
|
40
|
+
skip: '跳过',
|
|
41
|
+
// 场景值
|
|
42
|
+
'crud.single-page': '单页 CRUD',
|
|
43
|
+
'crud.nested': '嵌套 CRUD',
|
|
44
|
+
statistic: '统计视图',
|
|
45
|
+
approval: '审批流',
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* 把 testcase.json5 中的英文字段名 / 状态值翻译成中文 testcase.md 显示文案。
|
|
49
|
+
*
|
|
50
|
+
* @param value - testcase.json5 中的英文 token(字段名 / 状态值 / 推荐动作 / 场景值)。
|
|
51
|
+
* @returns 对应中文文案;未在映射表中的 token 原样返回(兜底)。
|
|
52
|
+
*/
|
|
53
|
+
export function i18nTestcase(value) {
|
|
54
|
+
return I18N_MAP[value] ?? value;
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { symlinkSync, linkSync, copyFileSync, existsSync, lstatSync, unlinkSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* 软链 fallback 返回结果。
|
|
4
|
+
* - strategy: 实际采用的链接策略;
|
|
5
|
+
* - warned: 仅在 copy fallback 时为 true,提示「上游更新不会自动同步到链接落点」。
|
|
6
|
+
*/
|
|
7
|
+
export type SoftlinkResult = {
|
|
8
|
+
strategy: 'symlink' | 'hardlink' | 'copy';
|
|
9
|
+
warned?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* 内部 fs 操作集合。抽象为对象是为了在测试中可替换具体实现,
|
|
13
|
+
* 绕开 ESM namespace 不可写带来的 vi.spyOn 限制(REQ-FILE-02 测试需要模拟 EPERM)。
|
|
14
|
+
* 生产代码不应替换此对象。
|
|
15
|
+
*/
|
|
16
|
+
export declare const softlinkFs: {
|
|
17
|
+
symlinkSync: typeof symlinkSync;
|
|
18
|
+
linkSync: typeof linkSync;
|
|
19
|
+
copyFileSync: typeof copyFileSync;
|
|
20
|
+
existsSync: typeof existsSync;
|
|
21
|
+
lstatSync: typeof lstatSync;
|
|
22
|
+
unlinkSync: typeof unlinkSync;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 跨平台创建「指向 target 的 link」,三档 fallback(REQ-FILE-02):
|
|
26
|
+
* symlink(macOS/Linux)→ hardlink → copy + 时间戳警示(Windows 无管理员权限)。
|
|
27
|
+
*
|
|
28
|
+
* @param target - 被链接的源文件绝对路径(上游 code_list.md)。
|
|
29
|
+
* @param linkPath - 链接落点(需求目录下的 code_list.md)。
|
|
30
|
+
* @returns 实际采用的策略;copy 时 warned=true。
|
|
31
|
+
*/
|
|
32
|
+
export declare function createSoftlinkWithFallback(target: string, linkPath: string): SoftlinkResult;
|
|
33
|
+
//# sourceMappingURL=softlink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"softlink.d.ts","sourceRoot":"","sources":["../../../src/util/softlink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEjG;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;CAOtB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,CAiB3F"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { symlinkSync, linkSync, copyFileSync, existsSync, lstatSync, unlinkSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* 内部 fs 操作集合。抽象为对象是为了在测试中可替换具体实现,
|
|
4
|
+
* 绕开 ESM namespace 不可写带来的 vi.spyOn 限制(REQ-FILE-02 测试需要模拟 EPERM)。
|
|
5
|
+
* 生产代码不应替换此对象。
|
|
6
|
+
*/
|
|
7
|
+
export const softlinkFs = {
|
|
8
|
+
symlinkSync,
|
|
9
|
+
linkSync,
|
|
10
|
+
copyFileSync,
|
|
11
|
+
existsSync,
|
|
12
|
+
lstatSync,
|
|
13
|
+
unlinkSync,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 跨平台创建「指向 target 的 link」,三档 fallback(REQ-FILE-02):
|
|
17
|
+
* symlink(macOS/Linux)→ hardlink → copy + 时间戳警示(Windows 无管理员权限)。
|
|
18
|
+
*
|
|
19
|
+
* @param target - 被链接的源文件绝对路径(上游 code_list.md)。
|
|
20
|
+
* @param linkPath - 链接落点(需求目录下的 code_list.md)。
|
|
21
|
+
* @returns 实际采用的策略;copy 时 warned=true。
|
|
22
|
+
*/
|
|
23
|
+
export function createSoftlinkWithFallback(target, linkPath) {
|
|
24
|
+
if (softlinkFs.existsSync(linkPath)) {
|
|
25
|
+
const st = softlinkFs.lstatSync(linkPath);
|
|
26
|
+
if (st.isSymbolicLink() || st.isFile())
|
|
27
|
+
softlinkFs.unlinkSync(linkPath);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
softlinkFs.symlinkSync(target, linkPath, 'file');
|
|
31
|
+
return { strategy: 'symlink' };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
try {
|
|
35
|
+
softlinkFs.linkSync(target, linkPath);
|
|
36
|
+
return { strategy: 'hardlink' };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
softlinkFs.copyFileSync(target, linkPath);
|
|
40
|
+
return { strategy: 'copy', warned: true };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** credentials.json5 单条凭据(REQ-ENV-03)。 */
|
|
2
|
+
export type Credential = {
|
|
3
|
+
url: string;
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
systemName?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* 校验 credentials.json5 数组形态(REQ-ENV-02 / REQ-ENV-03)。
|
|
11
|
+
*
|
|
12
|
+
* 不变量:数组至少 1 条;必填 url/username/password 非空;同 (url, role) 组合唯一。
|
|
13
|
+
*
|
|
14
|
+
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
15
|
+
* @returns 归一化后的凭据数组。
|
|
16
|
+
* @throws 违反不变量时抛结构化错误。
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateCredentials(obj: unknown): Credential[];
|
|
19
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/validation/credentials.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,EAAE,CAyB9D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 校验 credentials.json5 数组形态(REQ-ENV-02 / REQ-ENV-03)。
|
|
3
|
+
*
|
|
4
|
+
* 不变量:数组至少 1 条;必填 url/username/password 非空;同 (url, role) 组合唯一。
|
|
5
|
+
*
|
|
6
|
+
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
7
|
+
* @returns 归一化后的凭据数组。
|
|
8
|
+
* @throws 违反不变量时抛结构化错误。
|
|
9
|
+
*/
|
|
10
|
+
export function validateCredentials(obj) {
|
|
11
|
+
if (!Array.isArray(obj))
|
|
12
|
+
throw new Error('credentials.json5 顶层必须是数组');
|
|
13
|
+
if (obj.length === 0)
|
|
14
|
+
throw new Error('credentials.json5 至少需要 1 条凭据');
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const result = [];
|
|
17
|
+
for (let i = 0; i < obj.length; i++) {
|
|
18
|
+
const e = obj[i];
|
|
19
|
+
for (const f of ['url', 'username', 'password']) {
|
|
20
|
+
if (typeof e[f] !== 'string' || !e[f].trim()) {
|
|
21
|
+
throw new Error(`credentials.json5 第 ${i + 1} 条缺必填字段 ${f}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const role = typeof e.role === 'string' ? e.role : '';
|
|
25
|
+
const key = `${e.url}|${role}`;
|
|
26
|
+
if (seen.has(key))
|
|
27
|
+
throw new Error(`credentials.json5 同 (url, role) 重复: ${key}`);
|
|
28
|
+
seen.add(key);
|
|
29
|
+
result.push({
|
|
30
|
+
url: e.url,
|
|
31
|
+
username: e.username,
|
|
32
|
+
password: e.password,
|
|
33
|
+
...(role ? { role } : {}),
|
|
34
|
+
...(typeof e.systemName === 'string' ? { systemName: e.systemName } : {}),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { validateProjectsIndex } from './projects-index.js';
|
|
2
|
+
export { validateTestcase } from './testcase.js';
|
|
3
|
+
export { validateCredentials } from './credentials.js';
|
|
4
|
+
export { validateInferenceTrace } from './inference-trace.js';
|
|
5
|
+
export type { Credential } from './credentials.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { InferenceTraceV1 } from '../contracts/inference-trace.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验 inference-trace.json 的顶层结构(REQ-INFER-006)。
|
|
4
|
+
*
|
|
5
|
+
* 最小校验:只保证 schemaVersion 正确且 5 个必需顶层章节存在。
|
|
6
|
+
* 章节内部证据数组 / reason 允许扩展(spec §8),不做深校验。
|
|
7
|
+
*
|
|
8
|
+
* @param obj - 从 JSON.parse 得到的任意值。
|
|
9
|
+
* @returns 类型收窄后的 InferenceTraceV1。
|
|
10
|
+
* @throws 顶层非对象、schemaVersion 不符或缺章节时抛错。
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateInferenceTrace(obj: unknown): InferenceTraceV1;
|
|
13
|
+
//# sourceMappingURL=inference-trace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference-trace.d.ts","sourceRoot":"","sources":["../../../src/validation/inference-trace.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAoBrE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 校验 inference-trace.json 的顶层结构(REQ-INFER-006)。
|
|
3
|
+
*
|
|
4
|
+
* 最小校验:只保证 schemaVersion 正确且 5 个必需顶层章节存在。
|
|
5
|
+
* 章节内部证据数组 / reason 允许扩展(spec §8),不做深校验。
|
|
6
|
+
*
|
|
7
|
+
* @param obj - 从 JSON.parse 得到的任意值。
|
|
8
|
+
* @returns 类型收窄后的 InferenceTraceV1。
|
|
9
|
+
* @throws 顶层非对象、schemaVersion 不符或缺章节时抛错。
|
|
10
|
+
*/
|
|
11
|
+
export function validateInferenceTrace(obj) {
|
|
12
|
+
const raw = obj;
|
|
13
|
+
if (!raw || typeof raw !== 'object') {
|
|
14
|
+
throw new Error('inference-trace.json 顶层必须是对象');
|
|
15
|
+
}
|
|
16
|
+
if (raw.schemaVersion !== 'inference-trace/v1') {
|
|
17
|
+
throw new Error('inference-trace.json schemaVersion 必须是 inference-trace/v1');
|
|
18
|
+
}
|
|
19
|
+
for (const key of [
|
|
20
|
+
'materialInventory',
|
|
21
|
+
'sourceAnalysis',
|
|
22
|
+
'moduleHintsInference',
|
|
23
|
+
'coverageDiff',
|
|
24
|
+
'reasoningSummary',
|
|
25
|
+
]) {
|
|
26
|
+
if (!raw[key]) {
|
|
27
|
+
throw new Error(`inference-trace.json 缺 ${key}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProjectsIndex } from '../context/stage-context.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验 ~/.ep-stage/projects.index.json5 的顶层结构(REQ-DATA-01 修订)。
|
|
4
|
+
*
|
|
5
|
+
* 必含 projects[] 与 systems[];projects[] 条目必含 stageCreateVersion;
|
|
6
|
+
* 拒绝 stage-context.md 砍掉后遗留的 stageContextPath/envPath/knowledgeRoot/codeListPaths 字段。
|
|
7
|
+
*
|
|
8
|
+
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
9
|
+
* @returns 归一化后的 ProjectsIndex。
|
|
10
|
+
* @throws 缺字段或含已废弃字段时抛结构化错误。
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateProjectsIndex(obj: unknown): ProjectsIndex;
|
|
13
|
+
//# sourceMappingURL=projects-index.d.ts.map
|