@epoint-testtech/ep-stage-skill 0.0.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +27 -0
- package/codex-skill/ep-stage/create-project/SKILL.md +59 -0
- package/codex-skill/ep-stage/glue-test/SKILL.md +258 -0
- package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +139 -0
- package/codex-skill/ep-stage/glue-test/references/gap-review-protocol.md +43 -0
- package/codex-skill/ep-stage/glue-test/references/harness-principles.md +46 -0
- package/codex-skill/ep-stage/glue-test/scripts/generate-crud-spec.mjs +149 -0
- package/codex-skill/ep-stage/glue-testcase/SKILL.md +31 -0
- package/codex-skill/ep-stage/glue-testcase/examples/observable-testcase.json +40 -0
- package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +67 -0
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +27 -0
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +73 -0
- package/dist/src/capability/coverage-diff.d.ts +34 -0
- package/dist/src/capability/coverage-diff.d.ts.map +1 -0
- package/dist/src/capability/coverage-diff.js +91 -0
- package/dist/src/capability/page-structure.d.ts +31 -0
- package/dist/src/capability/page-structure.d.ts.map +1 -0
- package/dist/src/capability/page-structure.js +50 -0
- package/dist/src/capability/scenario-inference.d.ts +36 -0
- package/dist/src/capability/scenario-inference.d.ts.map +1 -0
- package/dist/src/capability/scenario-inference.js +114 -0
- package/dist/src/cli/generate-crud-contract.d.ts +2 -0
- package/dist/src/cli/generate-crud-contract.d.ts.map +1 -0
- package/dist/src/cli/generate-crud-contract.js +77 -0
- package/dist/src/cli/generate-playwright-tests.d.ts +30 -0
- package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -0
- package/dist/src/cli/generate-playwright-tests.js +81 -0
- package/dist/src/cli/run-gap-pipeline.d.ts +256 -0
- package/dist/src/cli/run-gap-pipeline.d.ts.map +1 -0
- package/dist/src/cli/run-gap-pipeline.js +1468 -0
- package/dist/src/context/stage-context.d.ts +63 -0
- package/dist/src/context/stage-context.d.ts.map +1 -0
- package/dist/src/context/stage-context.js +297 -0
- package/dist/src/contracts/crud-business-module.d.ts +645 -0
- package/dist/src/contracts/crud-business-module.d.ts.map +1 -0
- package/dist/src/contracts/crud-business-module.js +1 -0
- package/dist/src/contracts/gap-inference.d.ts +213 -0
- package/dist/src/contracts/gap-inference.d.ts.map +1 -0
- package/dist/src/contracts/gap-inference.js +11 -0
- package/dist/src/contracts/observable-chain.d.ts +250 -0
- package/dist/src/contracts/observable-chain.d.ts.map +1 -0
- package/dist/src/contracts/observable-chain.js +1 -0
- package/dist/src/extractors/code-list.d.ts +40 -0
- package/dist/src/extractors/code-list.d.ts.map +1 -0
- package/dist/src/extractors/code-list.js +225 -0
- package/dist/src/extractors/html-page.d.ts +67 -0
- package/dist/src/extractors/html-page.d.ts.map +1 -0
- package/dist/src/extractors/html-page.js +195 -0
- package/dist/src/extractors/java-action.d.ts +8 -0
- package/dist/src/extractors/java-action.d.ts.map +1 -0
- package/dist/src/extractors/java-action.js +53 -0
- package/dist/src/extractors/spec-yaml.d.ts +28 -0
- package/dist/src/extractors/spec-yaml.d.ts.map +1 -0
- package/dist/src/extractors/spec-yaml.js +29 -0
- package/dist/src/gap-planner/action-candidates.d.ts +9 -0
- package/dist/src/gap-planner/action-candidates.d.ts.map +1 -0
- package/dist/src/gap-planner/action-candidates.js +66 -0
- package/dist/src/gap-planner/list-gap-workflows.d.ts +17 -0
- package/dist/src/gap-planner/list-gap-workflows.d.ts.map +1 -0
- package/dist/src/gap-planner/list-gap-workflows.js +47 -0
- package/dist/src/gap-planner/plan-agent-workflows.d.ts +26 -0
- package/dist/src/gap-planner/plan-agent-workflows.d.ts.map +1 -0
- package/dist/src/gap-planner/plan-agent-workflows.js +116 -0
- package/dist/src/gap-planner/skeleton-coverage.d.ts +9 -0
- package/dist/src/gap-planner/skeleton-coverage.d.ts.map +1 -0
- package/dist/src/gap-planner/skeleton-coverage.js +41 -0
- package/dist/src/gap-planner/stable-id.d.ts +16 -0
- package/dist/src/gap-planner/stable-id.d.ts.map +1 -0
- package/dist/src/gap-planner/stable-id.js +19 -0
- package/dist/src/generalization/generalization-eval.d.ts +71 -0
- package/dist/src/generalization/generalization-eval.d.ts.map +1 -0
- package/dist/src/generalization/generalization-eval.js +53 -0
- package/dist/src/generators/agent-inferred-workflow-script.d.ts +22 -0
- package/dist/src/generators/agent-inferred-workflow-script.d.ts.map +1 -0
- package/dist/src/generators/agent-inferred-workflow-script.js +230 -0
- package/dist/src/generators/stage-skeleton-script.d.ts +107 -0
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -0
- package/dist/src/generators/stage-skeleton-script.js +607 -0
- package/dist/src/index.d.ts +52 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +26 -0
- package/dist/src/material/material-inventory.d.ts +92 -0
- package/dist/src/material/material-inventory.d.ts.map +1 -0
- package/dist/src/material/material-inventory.js +191 -0
- package/dist/src/normalizers/crud-contract.d.ts +107 -0
- package/dist/src/normalizers/crud-contract.d.ts.map +1 -0
- package/dist/src/normalizers/crud-contract.js +1068 -0
- package/dist/src/testcase/testcase-generator.d.ts +43 -0
- package/dist/src/testcase/testcase-generator.d.ts.map +1 -0
- package/dist/src/testcase/testcase-generator.js +152 -0
- package/dist/src/testcase/testcase-skeleton.d.ts +91 -0
- package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -0
- package/dist/src/testcase/testcase-skeleton.js +121 -0
- package/dist/src/testcase/testcase-spec-assembly.d.ts +11 -0
- package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -0
- package/dist/src/testcase/testcase-spec-assembly.js +24 -0
- package/dist/src/trace/review-summary.d.ts +17 -0
- package/dist/src/trace/review-summary.d.ts.map +1 -0
- package/dist/src/trace/review-summary.js +34 -0
- package/dist/src/trace/trace-writer.d.ts +17 -0
- package/dist/src/trace/trace-writer.d.ts.map +1 -0
- package/dist/src/trace/trace-writer.js +81 -0
- package/dist/test/crud-contract.test.d.ts +2 -0
- package/dist/test/crud-contract.test.d.ts.map +1 -0
- package/dist/test/crud-contract.test.js +819 -0
- package/dist/test/gap-inference.test.d.ts +2 -0
- package/dist/test/gap-inference.test.d.ts.map +1 -0
- package/dist/test/gap-inference.test.js +597 -0
- package/dist/test/generalization.test.d.ts +2 -0
- package/dist/test/generalization.test.d.ts.map +1 -0
- package/dist/test/generalization.test.js +73 -0
- package/dist/test/material-inventory.test.d.ts +2 -0
- package/dist/test/material-inventory.test.d.ts.map +1 -0
- package/dist/test/material-inventory.test.js +141 -0
- package/dist/test/observable-chain.test.d.ts +2 -0
- package/dist/test/observable-chain.test.d.ts.map +1 -0
- package/dist/test/observable-chain.test.js +123 -0
- package/dist/test/observable-pipeline.test.d.ts +2 -0
- package/dist/test/observable-pipeline.test.d.ts.map +1 -0
- package/dist/test/observable-pipeline.test.js +461 -0
- package/dist/test/page-structure.test.d.ts +2 -0
- package/dist/test/page-structure.test.d.ts.map +1 -0
- package/dist/test/page-structure.test.js +45 -0
- package/dist/test/scenario-inference.test.d.ts +2 -0
- package/dist/test/scenario-inference.test.d.ts.map +1 -0
- package/dist/test/scenario-inference.test.js +73 -0
- package/dist/test/stage-context.test.d.ts +2 -0
- package/dist/test/stage-context.test.d.ts.map +1 -0
- package/dist/test/stage-context.test.js +263 -0
- package/dist/test/testcase-generator.test.d.ts +2 -0
- package/dist/test/testcase-generator.test.d.ts.map +1 -0
- package/dist/test/testcase-generator.test.js +276 -0
- package/dist/test/testcase-skeleton.test.d.ts +2 -0
- package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
- package/dist/test/testcase-skeleton.test.js +185 -0
- package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
- package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
- package/dist/test/testcase-spec-assembly.test.js +105 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +7 -0
- package/docs/README.md +134 -0
- package/docs/mvp-usage-guide.md +298 -0
- package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -0
- package/examples/schemeresource.module-hints.json +38 -0
- package/examples/schemeresource.observable.code_list.md +37 -0
- package/examples/zwplace-observable-docs/zwplace.context.md +16 -0
- package/examples/zwplace-placecategory-validation.json +29 -0
- package/examples/zwplace.module-hints.json +69 -0
- package/examples/zwplace.observable.code_list.md +37 -0
- package/package.json +38 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CoverageDiffTrace, GlueTestcaseDocument, PageScenario } from '../contracts/observable-chain.js';
|
|
2
|
+
import type { CrudBusinessModuleContract } from '../contracts/crud-business-module.js';
|
|
3
|
+
/**
|
|
4
|
+
* generateGlueTestcases 的入参。
|
|
5
|
+
*
|
|
6
|
+
* @property contract - CRUD 业务模块契约;生成器运行时必需。
|
|
7
|
+
* @property menu - 菜单路由;生成器运行时必需。
|
|
8
|
+
* @property scenarios - 推理出的全部场景。
|
|
9
|
+
* @property confirmedScenarios - 人工已确认的场景(决定用例 reviewStatus)。
|
|
10
|
+
* @property coverageDiff - coverage diff trace。
|
|
11
|
+
* @property menuReviewHint - 可选菜单候选提示,供 testcase markdown 与 gate 一并审阅。
|
|
12
|
+
*/
|
|
13
|
+
export type GenerateGlueTestcasesInput = {
|
|
14
|
+
contract: CrudBusinessModuleContract;
|
|
15
|
+
menu: string;
|
|
16
|
+
scenarios: PageScenario[];
|
|
17
|
+
confirmedScenarios?: PageScenario[];
|
|
18
|
+
coverageDiff: CoverageDiffTrace;
|
|
19
|
+
menuReviewHint?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* generateGlueTestcases 的产物。
|
|
23
|
+
*
|
|
24
|
+
* @property json - 结构化用例文档。
|
|
25
|
+
* @property markdown - 人类可读 markdown。
|
|
26
|
+
*/
|
|
27
|
+
export type GenerateGlueTestcasesOutput = {
|
|
28
|
+
json: GlueTestcaseDocument;
|
|
29
|
+
markdown: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* 从同一份 CRUD 契约、已确认场景和 coverage diff 生成胶水测试用例。
|
|
33
|
+
*
|
|
34
|
+
* - 选择已确认场景或第一个候选场景。
|
|
35
|
+
* - 通过 testcase skeleton + contract slots 渲染人工审阅 Markdown。
|
|
36
|
+
* - 用例证据记录 skeleton、contract、confirmed scenario、slotSources 和 unresolved。
|
|
37
|
+
* - 未解析槽位只进入 unresolved,不静默补成业务值。
|
|
38
|
+
*
|
|
39
|
+
* @param input - 用例生成入参。
|
|
40
|
+
* @returns 用例 JSON + Markdown。
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateGlueTestcases(input: GenerateGlueTestcasesInput): GenerateGlueTestcasesOutput;
|
|
43
|
+
//# sourceMappingURL=testcase-generator.d.ts.map
|
|
@@ -0,0 +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;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,2BAA2B,CA2GpG"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { mapContractToCrudTestcaseSlots, renderGlueTestcaseSkeleton, selectTestcaseSkeletonByScenario, } from './testcase-skeleton.js';
|
|
3
|
+
/**
|
|
4
|
+
* 从同一份 CRUD 契约、已确认场景和 coverage diff 生成胶水测试用例。
|
|
5
|
+
*
|
|
6
|
+
* - 选择已确认场景或第一个候选场景。
|
|
7
|
+
* - 通过 testcase skeleton + contract slots 渲染人工审阅 Markdown。
|
|
8
|
+
* - 用例证据记录 skeleton、contract、confirmed scenario、slotSources 和 unresolved。
|
|
9
|
+
* - 未解析槽位只进入 unresolved,不静默补成业务值。
|
|
10
|
+
*
|
|
11
|
+
* @param input - 用例生成入参。
|
|
12
|
+
* @returns 用例 JSON + Markdown。
|
|
13
|
+
*/
|
|
14
|
+
export function generateGlueTestcases(input) {
|
|
15
|
+
if (!input.contract || !input.menu) {
|
|
16
|
+
throw new Error('生成胶水测试用例必须提供 contract 和 menu,不能再由 moduleId/moduleName 自由拼装');
|
|
17
|
+
}
|
|
18
|
+
const confirmedScenarios = input.confirmedScenarios ?? [];
|
|
19
|
+
const scenario = confirmedScenarios.find((item) => input.scenarios.includes(item)) ?? input.scenarios[0];
|
|
20
|
+
if (!scenario) {
|
|
21
|
+
throw new Error('生成胶水测试用例前必须至少提供一个场景');
|
|
22
|
+
}
|
|
23
|
+
const selection = selectTestcaseSkeletonByScenario(scenario);
|
|
24
|
+
const slots = mapContractToCrudTestcaseSlots({
|
|
25
|
+
contract: input.contract,
|
|
26
|
+
scenario,
|
|
27
|
+
menu: input.menu,
|
|
28
|
+
});
|
|
29
|
+
const unresolved = collectReviewUnresolved(input.coverageDiff, slots.unresolved, scenario, confirmedScenarios, input.menuReviewHint);
|
|
30
|
+
const reviewStatus = confirmedScenarios.includes(scenario) && unresolved.length === 0
|
|
31
|
+
? 'confirmed'
|
|
32
|
+
: 'needs_review';
|
|
33
|
+
const evidence = [
|
|
34
|
+
`skeleton=${selection.skeletonId}`,
|
|
35
|
+
`contract=${input.contract.contractVersion}`,
|
|
36
|
+
`listPage=${slots.listPageId}`,
|
|
37
|
+
`confirmedScenario=${confirmedScenarios.includes(scenario) ? scenario : 'none'}`,
|
|
38
|
+
`slotSources=${slots.slotSources.join(',') || 'none'}`,
|
|
39
|
+
`unresolved=${unresolved.join(',') || 'none'}`,
|
|
40
|
+
];
|
|
41
|
+
const cases = [
|
|
42
|
+
{
|
|
43
|
+
caseId: `${input.contract.module.id}.create`,
|
|
44
|
+
title: slots.createCaseTitle,
|
|
45
|
+
scenario,
|
|
46
|
+
reviewStatus,
|
|
47
|
+
evidence,
|
|
48
|
+
steps: [slots.createSteps],
|
|
49
|
+
assertions: [slots.createAssertions],
|
|
50
|
+
unresolved,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
caseId: `${input.contract.module.id}.read`,
|
|
54
|
+
title: slots.searchCaseTitle,
|
|
55
|
+
scenario,
|
|
56
|
+
reviewStatus,
|
|
57
|
+
evidence,
|
|
58
|
+
steps: [slots.searchSteps],
|
|
59
|
+
assertions: [slots.searchAssertions],
|
|
60
|
+
unresolved,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
caseId: `${input.contract.module.id}.update`,
|
|
64
|
+
title: slots.updateCaseTitle,
|
|
65
|
+
scenario,
|
|
66
|
+
reviewStatus,
|
|
67
|
+
evidence,
|
|
68
|
+
steps: [slots.updateSteps],
|
|
69
|
+
assertions: [slots.updateAssertions],
|
|
70
|
+
unresolved,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
caseId: `${input.contract.module.id}.delete`,
|
|
74
|
+
title: slots.deleteCaseTitle,
|
|
75
|
+
scenario,
|
|
76
|
+
reviewStatus,
|
|
77
|
+
evidence,
|
|
78
|
+
steps: [slots.deleteSteps],
|
|
79
|
+
assertions: [slots.deleteAssertions],
|
|
80
|
+
unresolved,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
const json = {
|
|
84
|
+
moduleId: input.contract.module.id,
|
|
85
|
+
moduleName: input.contract.module.label,
|
|
86
|
+
cases,
|
|
87
|
+
reasoningSummary: {
|
|
88
|
+
conclusion: `基于 ${selection.skeletonId} 和同一份 ${input.contract.contractVersion} 契约生成 ${cases.length} 条胶水测试用例。`,
|
|
89
|
+
evidenceChain: [
|
|
90
|
+
...input.coverageDiff.reasoningSummary.evidenceChain,
|
|
91
|
+
createHumanEvidence(`skeleton=${selection.skeletonId}`),
|
|
92
|
+
createHumanEvidence(`confirmedScenario=${confirmedScenarios.includes(scenario) ? scenario : 'none'}`),
|
|
93
|
+
createHumanEvidence(`slotSources=${slots.slotSources.join(',') || 'none'}`),
|
|
94
|
+
createHumanEvidence(`unresolved=${unresolved.join(',') || 'none'}`),
|
|
95
|
+
],
|
|
96
|
+
alternatives: input.coverageDiff.reasoningSummary.alternatives,
|
|
97
|
+
confidence: input.coverageDiff.reasoningSummary.confidence,
|
|
98
|
+
risks: unresolved,
|
|
99
|
+
needsHumanReview: cases.some((item) => item.reviewStatus !== 'confirmed'),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
json,
|
|
104
|
+
markdown: renderGlueTestcaseSkeleton(readTestcaseTemplate(selection.templateName), {
|
|
105
|
+
...slots,
|
|
106
|
+
reviewStatus,
|
|
107
|
+
unresolved,
|
|
108
|
+
unresolvedSummary: unresolved.join(';') || '无',
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 合并契约槽位、coverage diff 和嵌套 CRUD 候选中的未确认项。
|
|
114
|
+
*
|
|
115
|
+
* @param coverageDiff - coverage diff trace。
|
|
116
|
+
* @param slotUnresolved - 契约槽位未解析项。
|
|
117
|
+
* @param menuReviewHint - 菜单候选提示。
|
|
118
|
+
* @returns testcase gate 需要展示的未确认项。
|
|
119
|
+
*/
|
|
120
|
+
function collectReviewUnresolved(coverageDiff, slotUnresolved, scenario, confirmedScenarios, menuReviewHint) {
|
|
121
|
+
const ignoreNestedCrudCandidates = scenario === 'crud.single-page' && confirmedScenarios.includes('crud.single-page');
|
|
122
|
+
return [
|
|
123
|
+
...slotUnresolved,
|
|
124
|
+
...(menuReviewHint ? [menuReviewHint] : []),
|
|
125
|
+
...coverageDiff.unresolved.map((item) => `coverageDiff.unresolved: ${item.reason}`),
|
|
126
|
+
...(ignoreNestedCrudCandidates
|
|
127
|
+
? []
|
|
128
|
+
: coverageDiff.nestedCrudCandidates.map((item) => `coverageDiff.nestedCrudCandidates[${item.pageId}]: ${item.unresolvedReason}`)),
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 读取用例骨架模板文件。
|
|
133
|
+
*
|
|
134
|
+
* @param templateName - 模板文件名。
|
|
135
|
+
* @returns Markdown 模板内容。
|
|
136
|
+
*/
|
|
137
|
+
function readTestcaseTemplate(templateName) {
|
|
138
|
+
return readFileSync(new URL(`./templates/${templateName}`, import.meta.url), 'utf8');
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 构造 testcase-generation 阶段的人审证据。
|
|
142
|
+
*
|
|
143
|
+
* @param text - 证据摘要。
|
|
144
|
+
* @returns 可观测推理证据。
|
|
145
|
+
*/
|
|
146
|
+
function createHumanEvidence(text) {
|
|
147
|
+
return {
|
|
148
|
+
source: 'human',
|
|
149
|
+
path: 'testcase-generation.json',
|
|
150
|
+
text,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { CrudBusinessModuleContract } from '../contracts/crud-business-module.js';
|
|
2
|
+
import type { PageScenario } from '../contracts/observable-chain.js';
|
|
3
|
+
/**
|
|
4
|
+
* 用例骨架选择结果。
|
|
5
|
+
*
|
|
6
|
+
* @property skeletonId - 用例骨架版本标识。
|
|
7
|
+
* @property templateName - 模板文件名。
|
|
8
|
+
* @property scenario - 适配的页面场景。
|
|
9
|
+
*/
|
|
10
|
+
export type TestcaseSkeletonSelection = {
|
|
11
|
+
skeletonId: 'crud.skeleton-testcase/v1';
|
|
12
|
+
templateName: 'crud.skeleton-testcase.md';
|
|
13
|
+
scenario: 'crud.single-page' | 'crud.nested';
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* CRUD 用例骨架渲染槽位。
|
|
17
|
+
*
|
|
18
|
+
* @property unresolved - 无法从契约确定的槽位,必须交给人工补齐。
|
|
19
|
+
* @property slotSources - 槽位来源说明,用于 testcase-generation.json 审阅。
|
|
20
|
+
*/
|
|
21
|
+
export type CrudTestcaseSlots = {
|
|
22
|
+
skeletonId: string;
|
|
23
|
+
contractVersion: CrudBusinessModuleContract['contractVersion'];
|
|
24
|
+
scenario: PageScenario;
|
|
25
|
+
reviewStatus: 'confirmed' | 'needs_review';
|
|
26
|
+
moduleId: string;
|
|
27
|
+
moduleName: string;
|
|
28
|
+
menu: string;
|
|
29
|
+
listPageId: string;
|
|
30
|
+
listPageTitle: string;
|
|
31
|
+
createActionLabel: string;
|
|
32
|
+
searchFieldLabel: string;
|
|
33
|
+
updateActionLabel: string;
|
|
34
|
+
deleteActionLabel: string;
|
|
35
|
+
createCaseTitle: string;
|
|
36
|
+
searchCaseTitle: string;
|
|
37
|
+
updateCaseTitle: string;
|
|
38
|
+
deleteCaseTitle: string;
|
|
39
|
+
createSteps: string;
|
|
40
|
+
searchSteps: string;
|
|
41
|
+
updateSteps: string;
|
|
42
|
+
deleteSteps: string;
|
|
43
|
+
createAssertions: string;
|
|
44
|
+
searchAssertions: string;
|
|
45
|
+
updateAssertions: string;
|
|
46
|
+
deleteAssertions: string;
|
|
47
|
+
envBaseUrl: string;
|
|
48
|
+
envUsername: string;
|
|
49
|
+
envPassword: string;
|
|
50
|
+
slotSources: string[];
|
|
51
|
+
slotSourcesSummary: string;
|
|
52
|
+
unresolved: string[];
|
|
53
|
+
unresolvedSummary: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* 从 CRUD 契约映射用例骨架槽位的入参。
|
|
57
|
+
*
|
|
58
|
+
* @property contract - CRUD 业务模块契约。
|
|
59
|
+
* @property scenario - 已确认或候选场景。
|
|
60
|
+
* @property menu - 菜单路由文本。
|
|
61
|
+
*/
|
|
62
|
+
export type MapContractToCrudTestcaseSlotsInput = {
|
|
63
|
+
contract: CrudBusinessModuleContract;
|
|
64
|
+
scenario: PageScenario;
|
|
65
|
+
menu: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* 按场景选择可审阅用例骨架。
|
|
69
|
+
*
|
|
70
|
+
* @param scenario - 页面场景。
|
|
71
|
+
* @returns 用例骨架选择结果。
|
|
72
|
+
* @throws 当场景暂不支持生成用例骨架时抛出中文错误。
|
|
73
|
+
*/
|
|
74
|
+
export declare function selectTestcaseSkeletonByScenario(scenario: PageScenario): TestcaseSkeletonSelection;
|
|
75
|
+
/**
|
|
76
|
+
* 从同一份 CRUD 契约映射 Markdown 用例骨架所需槽位。
|
|
77
|
+
*
|
|
78
|
+
* @param input - 契约、场景与菜单路由。
|
|
79
|
+
* @returns 可渲染槽位;缺失业务来源进入 unresolved。
|
|
80
|
+
*/
|
|
81
|
+
export declare function mapContractToCrudTestcaseSlots(input: MapContractToCrudTestcaseSlotsInput): CrudTestcaseSlots;
|
|
82
|
+
/**
|
|
83
|
+
* 使用 `{{slotName}}` 占位符渲染用例骨架模板。
|
|
84
|
+
*
|
|
85
|
+
* @param template - Markdown 模板内容。
|
|
86
|
+
* @param slots - 用例骨架槽位。
|
|
87
|
+
* @returns 完整 Markdown。
|
|
88
|
+
* @throws 当模板引用未知槽位时抛出中文错误。
|
|
89
|
+
*/
|
|
90
|
+
export declare function renderGlueTestcaseSkeleton(template: string, slots: CrudTestcaseSlots): string;
|
|
91
|
+
//# sourceMappingURL=testcase-skeleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testcase-skeleton.d.ts","sourceRoot":"","sources":["../../../src/testcase/testcase-skeleton.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAOrE;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,UAAU,EAAE,2BAA2B,CAAC;IACxC,YAAY,EAAE,2BAA2B,CAAC;IAC1C,QAAQ,EAAE,kBAAkB,GAAG,aAAa,CAAC;CAC9C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,0BAA0B,CAAC,iBAAiB,CAAC,CAAC;IAC/D,QAAQ,EAAE,YAAY,CAAC;IACvB,YAAY,EAAE,WAAW,GAAG,cAAc,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,mCAAmC,GAAG;IAChD,QAAQ,EAAE,0BAA0B,CAAC;IACrC,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,YAAY,GAAG,yBAAyB,CAUlG;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,mCAAmC,GAAG,iBAAiB,CA4E5G;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAS7F"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const CRUD_TESTCASE_SKELETON_ID = 'crud.skeleton-testcase/v1';
|
|
2
|
+
const CONTRACT_VERSION = 'crud-business-module/v1';
|
|
3
|
+
const HUMAN_REQUIRED = '待人工确认';
|
|
4
|
+
const UNRESOLVED_SLOT_VALUES = new Set(['未确认菜单路径', HUMAN_REQUIRED]);
|
|
5
|
+
/**
|
|
6
|
+
* 按场景选择可审阅用例骨架。
|
|
7
|
+
*
|
|
8
|
+
* @param scenario - 页面场景。
|
|
9
|
+
* @returns 用例骨架选择结果。
|
|
10
|
+
* @throws 当场景暂不支持生成用例骨架时抛出中文错误。
|
|
11
|
+
*/
|
|
12
|
+
export function selectTestcaseSkeletonByScenario(scenario) {
|
|
13
|
+
if (scenario !== 'crud.single-page' && scenario !== 'crud.nested') {
|
|
14
|
+
throw new Error(`暂不支持为场景 ${scenario} 生成胶水测试用例骨架`);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
skeletonId: CRUD_TESTCASE_SKELETON_ID,
|
|
18
|
+
templateName: 'crud.skeleton-testcase.md',
|
|
19
|
+
scenario,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 从同一份 CRUD 契约映射 Markdown 用例骨架所需槽位。
|
|
24
|
+
*
|
|
25
|
+
* @param input - 契约、场景与菜单路由。
|
|
26
|
+
* @returns 可渲染槽位;缺失业务来源进入 unresolved。
|
|
27
|
+
*/
|
|
28
|
+
export function mapContractToCrudTestcaseSlots(input) {
|
|
29
|
+
const selection = selectTestcaseSkeletonByScenario(input.scenario);
|
|
30
|
+
const unresolved = [...input.contract.unresolvedSlots.map((slot) => `${slot.slotId}: ${slot.reason}`)];
|
|
31
|
+
const slotSources = [];
|
|
32
|
+
const contract = input.contract;
|
|
33
|
+
const moduleId = requireSlot(contract.module.id, 'moduleId', 'contract.module.id', unresolved, slotSources);
|
|
34
|
+
const moduleName = requireSlot(contract.module.label, 'moduleName', 'contract.module.label', unresolved, slotSources);
|
|
35
|
+
const listPageId = requireSlot(contract.pages.list.pageId, 'listPageId', 'contract.pages.list.pageId', unresolved, slotSources);
|
|
36
|
+
const listPageTitle = requireSlot(contract.pages.list.title, 'listPageTitle', 'contract.pages.list.title', unresolved, slotSources);
|
|
37
|
+
const menu = requireSlot(input.menu, 'menu', 'input.menu', unresolved, slotSources);
|
|
38
|
+
const createActionLabel = requireSlot(contract.flows.create?.entryButton.label, 'createActionLabel', 'contract.flows.create.entryButton.label', unresolved, slotSources);
|
|
39
|
+
const searchFieldLabel = requireSlot(contract.flows.search?.searchField.label ?? firstSearchConditionLabel(contract), 'searchFieldLabel', 'contract.flows.search.searchField.label', unresolved, slotSources);
|
|
40
|
+
const updateActionLabel = requireSlot(contract.flows.update?.entryAction.label, 'updateActionLabel', 'contract.flows.update.entryAction.label', unresolved, slotSources);
|
|
41
|
+
const deleteActionLabel = requireSlot(contract.flows.delete?.entryButton.label, 'deleteActionLabel', 'contract.flows.delete.entryButton.label', unresolved, slotSources);
|
|
42
|
+
const slots = {
|
|
43
|
+
skeletonId: selection.skeletonId,
|
|
44
|
+
contractVersion: CONTRACT_VERSION,
|
|
45
|
+
scenario: input.scenario,
|
|
46
|
+
reviewStatus: unresolved.length === 0 ? 'confirmed' : 'needs_review',
|
|
47
|
+
moduleId,
|
|
48
|
+
moduleName,
|
|
49
|
+
menu,
|
|
50
|
+
listPageId,
|
|
51
|
+
listPageTitle,
|
|
52
|
+
createActionLabel,
|
|
53
|
+
searchFieldLabel,
|
|
54
|
+
updateActionLabel,
|
|
55
|
+
deleteActionLabel,
|
|
56
|
+
createCaseTitle: `新增${moduleName}`,
|
|
57
|
+
searchCaseTitle: `查询${moduleName}`,
|
|
58
|
+
updateCaseTitle: `修改${moduleName}`,
|
|
59
|
+
deleteCaseTitle: `删除${moduleName}`,
|
|
60
|
+
createSteps: `进入菜单 ${menu};确认列表页 ${listPageTitle};点击 ${createActionLabel};填写并保存测试数据`,
|
|
61
|
+
searchSteps: `进入菜单 ${menu};按查询字段:${searchFieldLabel} 输入测试值;提交查询`,
|
|
62
|
+
updateSteps: `在列表结果中定位测试记录;点击 ${updateActionLabel};修改查询字段:${searchFieldLabel};保存`,
|
|
63
|
+
deleteSteps: `在列表结果中定位测试记录;点击 ${deleteActionLabel};按契约策略确认删除结果`,
|
|
64
|
+
createAssertions: `新增后可在 ${listPageTitle} 按 ${searchFieldLabel} 查询到记录`,
|
|
65
|
+
searchAssertions: `查询结果包含 ${searchFieldLabel} 对应的测试记录`,
|
|
66
|
+
updateAssertions: `修改后可按 ${searchFieldLabel} 查询到更新后的记录`,
|
|
67
|
+
deleteAssertions: '删除结果符合契约中的 delete.expectedOutcome 与断言策略',
|
|
68
|
+
envBaseUrl: '.env: LOGIN_SYSTEM_URL',
|
|
69
|
+
envUsername: '.env: LOGIN_USERNAME',
|
|
70
|
+
envPassword: '.env: LOGIN_PASSWORD',
|
|
71
|
+
slotSources,
|
|
72
|
+
slotSourcesSummary: slotSources.join(';') || '无',
|
|
73
|
+
unresolved,
|
|
74
|
+
unresolvedSummary: unresolved.join(';') || '无',
|
|
75
|
+
};
|
|
76
|
+
return slots;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 使用 `{{slotName}}` 占位符渲染用例骨架模板。
|
|
80
|
+
*
|
|
81
|
+
* @param template - Markdown 模板内容。
|
|
82
|
+
* @param slots - 用例骨架槽位。
|
|
83
|
+
* @returns 完整 Markdown。
|
|
84
|
+
* @throws 当模板引用未知槽位时抛出中文错误。
|
|
85
|
+
*/
|
|
86
|
+
export function renderGlueTestcaseSkeleton(template, slots) {
|
|
87
|
+
return template.replace(/\{\{(\w+)\}\}/g, (placeholder, slotName) => {
|
|
88
|
+
if (!(slotName in slots)) {
|
|
89
|
+
throw new Error(`用例骨架模板引用了未知槽位:${placeholder}`);
|
|
90
|
+
}
|
|
91
|
+
const value = slots[slotName];
|
|
92
|
+
return Array.isArray(value) ? value.join(';') : String(value);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 读取第一个搜索条件标签。
|
|
97
|
+
*
|
|
98
|
+
* @param contract - CRUD 业务模块契约。
|
|
99
|
+
* @returns 搜索条件标签;不存在时返回 undefined。
|
|
100
|
+
*/
|
|
101
|
+
function firstSearchConditionLabel(contract) {
|
|
102
|
+
return contract.searchConditions?.[0]?.label;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 读取必需槽位,不存在时登记 unresolved。
|
|
106
|
+
*
|
|
107
|
+
* @param value - 候选槽位值。
|
|
108
|
+
* @param slotId - 槽位标识。
|
|
109
|
+
* @param source - 槽位来源。
|
|
110
|
+
* @param unresolved - 未解析槽位列表。
|
|
111
|
+
* @param slotSources - 已解析槽位来源列表。
|
|
112
|
+
* @returns 已解析值或人工确认占位。
|
|
113
|
+
*/
|
|
114
|
+
function requireSlot(value, slotId, source, unresolved, slotSources) {
|
|
115
|
+
if (value && value.trim().length > 0 && !UNRESOLVED_SLOT_VALUES.has(value.trim())) {
|
|
116
|
+
slotSources.push(`${slotId}=${source}`);
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
unresolved.push(`${slotId}: 缺少 ${source}`);
|
|
120
|
+
return HUMAN_REQUIRED;
|
|
121
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CrudBusinessModuleContract } from '../contracts/crud-business-module.js';
|
|
2
|
+
import type { GlueTestcaseDocument } from '../contracts/observable-chain.js';
|
|
3
|
+
/**
|
|
4
|
+
* 校验已确认的 glue testcase 是否可作为 spec assembly 输入。
|
|
5
|
+
*
|
|
6
|
+
* @param document - testcase-generation 阶段产出的结构化用例。
|
|
7
|
+
* @param contract - 同一次链路产出的 CRUD 契约。
|
|
8
|
+
* @throws 当用例未确认、模块不一致或缺少 CRUD 工作流颗粒度时抛出中文错误。
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateGlueTestcaseDocumentForSpecAssembly(document: GlueTestcaseDocument, contract: CrudBusinessModuleContract): void;
|
|
11
|
+
//# sourceMappingURL=testcase-spec-assembly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testcase-spec-assembly.d.ts","sourceRoot":"","sources":["../../../src/testcase/testcase-spec-assembly.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAI7E;;;;;;GAMG;AACH,wBAAgB,2CAA2C,CACzD,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,0BAA0B,GACnC,IAAI,CAkBN"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const CRUD_WORKFLOW_KINDS = ['create', 'read', 'update', 'delete'];
|
|
2
|
+
/**
|
|
3
|
+
* 校验已确认的 glue testcase 是否可作为 spec assembly 输入。
|
|
4
|
+
*
|
|
5
|
+
* @param document - testcase-generation 阶段产出的结构化用例。
|
|
6
|
+
* @param contract - 同一次链路产出的 CRUD 契约。
|
|
7
|
+
* @throws 当用例未确认、模块不一致或缺少 CRUD 工作流颗粒度时抛出中文错误。
|
|
8
|
+
*/
|
|
9
|
+
export function validateGlueTestcaseDocumentForSpecAssembly(document, contract) {
|
|
10
|
+
if (document.moduleId !== contract.module.id) {
|
|
11
|
+
throw new Error(`glue testcase moduleId 与 contract 不一致:${document.moduleId} !== ${contract.module.id}`);
|
|
12
|
+
}
|
|
13
|
+
const pendingCases = document.cases.filter((item) => item.reviewStatus !== 'confirmed');
|
|
14
|
+
if (pendingCases.length > 0) {
|
|
15
|
+
throw new Error(`glue testcase 仍有未确认用例:${pendingCases.map((item) => item.caseId).join(', ')}`);
|
|
16
|
+
}
|
|
17
|
+
const caseIds = new Set(document.cases.map((item) => item.caseId));
|
|
18
|
+
const missingCaseIds = CRUD_WORKFLOW_KINDS
|
|
19
|
+
.map((kind) => `${contract.module.id}.${kind}`)
|
|
20
|
+
.filter((caseId) => !caseIds.has(caseId));
|
|
21
|
+
if (missingCaseIds.length > 0) {
|
|
22
|
+
throw new Error(`glue testcase 缺少 spec assembly 需要的用例:${missingCaseIds.join(', ')}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 人工审阅摘要渲染器。
|
|
3
|
+
*/
|
|
4
|
+
import type { ReasoningSummary } from '../contracts/observable-chain.js';
|
|
5
|
+
export type ReviewSummaryInput = {
|
|
6
|
+
title: string;
|
|
7
|
+
reasoningSummary: ReasoningSummary;
|
|
8
|
+
nextAction: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* 将结构化推理摘要渲染为适合对话输出的中文文本。
|
|
12
|
+
*
|
|
13
|
+
* @param input 审阅标题、推理摘要和下一步动作。
|
|
14
|
+
* @returns 中文人工审阅摘要。
|
|
15
|
+
*/
|
|
16
|
+
export declare function renderReviewSummary(input: ReviewSummaryInput): string;
|
|
17
|
+
//# sourceMappingURL=review-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-summary.d.ts","sourceRoot":"","sources":["../../../src/trace/review-summary.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CA4BrE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将结构化推理摘要渲染为适合对话输出的中文文本。
|
|
3
|
+
*
|
|
4
|
+
* @param input 审阅标题、推理摘要和下一步动作。
|
|
5
|
+
* @returns 中文人工审阅摘要。
|
|
6
|
+
*/
|
|
7
|
+
export function renderReviewSummary(input) {
|
|
8
|
+
const { reasoningSummary } = input;
|
|
9
|
+
const evidenceLines = reasoningSummary.evidenceChain.length > 0
|
|
10
|
+
? reasoningSummary.evidenceChain.map((item) => {
|
|
11
|
+
const pathText = item.path ? `(${item.path})` : '';
|
|
12
|
+
return `- ${item.source}${pathText}:${item.text}`;
|
|
13
|
+
})
|
|
14
|
+
: ['- 无'];
|
|
15
|
+
const riskText = reasoningSummary.risks.length > 0
|
|
16
|
+
? reasoningSummary.risks.join(';')
|
|
17
|
+
: '无';
|
|
18
|
+
const alternativesText = reasoningSummary.alternatives.length > 0
|
|
19
|
+
? reasoningSummary.alternatives.join(' / ')
|
|
20
|
+
: '无';
|
|
21
|
+
return [
|
|
22
|
+
`# ${input.title}`,
|
|
23
|
+
'',
|
|
24
|
+
`结论:${reasoningSummary.conclusion}`,
|
|
25
|
+
`置信度:${reasoningSummary.confidence}`,
|
|
26
|
+
'证据链:',
|
|
27
|
+
...evidenceLines,
|
|
28
|
+
`候选方案:${alternativesText}`,
|
|
29
|
+
`待确认项:${riskText}`,
|
|
30
|
+
`是否需要人工确认:${reasoningSummary.needsHumanReview ? '是' : '否'}`,
|
|
31
|
+
`下一步:${input.nextAction}`,
|
|
32
|
+
'',
|
|
33
|
+
].join('\n');
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ObservableStageName, ObservableStagePayload, ObservableStageSummary, TraceWriterOptions } from '../contracts/observable-chain.js';
|
|
2
|
+
export type TraceWriter = {
|
|
3
|
+
runId: string;
|
|
4
|
+
traceDir: string;
|
|
5
|
+
/** 写入单个阶段 JSON,并返回写入路径。 */
|
|
6
|
+
writeStage(stage: ObservableStageName, payload: ObservableStagePayload): string;
|
|
7
|
+
/** 写入批次 summary.md,并返回写入路径。 */
|
|
8
|
+
writeSummary(stages: ObservableStageSummary[]): string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* 创建 trace writer,并确保运行批次目录存在。
|
|
12
|
+
*
|
|
13
|
+
* @param options trace writer 初始化参数。
|
|
14
|
+
* @returns 可写阶段 JSON 与 summary.md 的 writer。
|
|
15
|
+
*/
|
|
16
|
+
export declare function createTraceWriter(options: TraceWriterOptions): TraceWriter;
|
|
17
|
+
//# sourceMappingURL=trace-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-writer.d.ts","sourceRoot":"","sources":["../../../src/trace/trace-writer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,2BAA2B;IAC3B,UAAU,CAAC,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAAC;IAChF,+BAA+B;IAC/B,YAAY,CAAC,MAAM,EAAE,sBAAsB,EAAE,GAAG,MAAM,CAAC;CACxD,CAAC;AAwBF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CA6B1E"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可观测链路 trace writer。
|
|
3
|
+
*/
|
|
4
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
/**
|
|
7
|
+
* 生成默认运行批次标识。
|
|
8
|
+
*
|
|
9
|
+
* @returns UTC 时间格式的运行批次标识。
|
|
10
|
+
*/
|
|
11
|
+
function defaultRunId() {
|
|
12
|
+
return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 校验 runId 只能是单个安全路径段。
|
|
16
|
+
*
|
|
17
|
+
* @param runId 待校验的运行批次标识。
|
|
18
|
+
* @returns 无返回值。
|
|
19
|
+
* @throws 当 runId 含路径分隔或非白名单字符时抛错。
|
|
20
|
+
*/
|
|
21
|
+
function assertSafeRunId(runId) {
|
|
22
|
+
if (!/^[A-Za-z0-9._-]+$/.test(runId) || runId.includes('..') || runId === '.') {
|
|
23
|
+
throw new Error(`invalid trace runId: ${runId}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 创建 trace writer,并确保运行批次目录存在。
|
|
28
|
+
*
|
|
29
|
+
* @param options trace writer 初始化参数。
|
|
30
|
+
* @returns 可写阶段 JSON 与 summary.md 的 writer。
|
|
31
|
+
*/
|
|
32
|
+
export function createTraceWriter(options) {
|
|
33
|
+
const runId = options.runId ?? defaultRunId();
|
|
34
|
+
assertSafeRunId(runId);
|
|
35
|
+
const traceDir = path.join(options.projectDir, '.stage', 'trace', runId);
|
|
36
|
+
mkdirSync(traceDir, { recursive: true });
|
|
37
|
+
return {
|
|
38
|
+
runId,
|
|
39
|
+
traceDir,
|
|
40
|
+
writeStage(stage, payload) {
|
|
41
|
+
if (stage !== payload.stage) {
|
|
42
|
+
throw new Error(`trace stage mismatch: file stage "${stage}" does not match payload stage "${payload.stage}"`);
|
|
43
|
+
}
|
|
44
|
+
const filePath = path.join(traceDir, `${stage}.json`);
|
|
45
|
+
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
46
|
+
return filePath;
|
|
47
|
+
},
|
|
48
|
+
writeSummary(stages) {
|
|
49
|
+
const filePath = path.join(traceDir, 'summary.md');
|
|
50
|
+
writeFileSync(filePath, renderTraceSummary(stages, options, runId), 'utf8');
|
|
51
|
+
return filePath;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 渲染批次级 summary.md,供人工快速审阅每个阶段状态。
|
|
57
|
+
*
|
|
58
|
+
* @param stages 阶段摘要列表。
|
|
59
|
+
* @param options trace writer 初始化参数。
|
|
60
|
+
* @param runId 实际使用的运行批次标识。
|
|
61
|
+
* @returns Markdown 文本。
|
|
62
|
+
*/
|
|
63
|
+
function renderTraceSummary(stages, options, runId) {
|
|
64
|
+
const header = [
|
|
65
|
+
'# ep-stage glue trace summary',
|
|
66
|
+
'',
|
|
67
|
+
`- 运行批次:${runId}`,
|
|
68
|
+
options.codeListPath ? `- code_list:${options.codeListPath}` : undefined,
|
|
69
|
+
'',
|
|
70
|
+
].filter((line) => line !== undefined);
|
|
71
|
+
const stageLines = stages.flatMap((stage) => [
|
|
72
|
+
`## ${stage.stageLabel}`,
|
|
73
|
+
'',
|
|
74
|
+
`- 阶段:${stage.stage}`,
|
|
75
|
+
`- 状态:${stage.status}`,
|
|
76
|
+
`- 结论:${stage.conclusion}`,
|
|
77
|
+
`- 需要人工确认:${stage.needsHumanReview ? '是,需要人工确认' : '否,无需人工确认'}`,
|
|
78
|
+
'',
|
|
79
|
+
]);
|
|
80
|
+
return `${[...header, ...stageLines].join('\n')}\n`;
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-contract.test.d.ts","sourceRoot":"","sources":["../../test/crud-contract.test.ts"],"names":[],"mappings":""}
|