@epoint-testtech/ep-stage-skill 0.0.4-alpha.0 → 0.0.5
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 -27
- package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -186
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -199
- package/codex-skill/ep-stage/glue-generate-testcase/examples/observable-testcase.json +40 -40
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +95 -112
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +72 -0
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +292 -249
- package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -0
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -145
- package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -43
- package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
- package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +166 -0
- package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +233 -0
- package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
- package/codex-skill/ep-stage/page-element-scan/SKILL.md +91 -0
- package/codex-skill/ep-stage/page-element-scan/evals/evals.json +37 -0
- package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +85 -0
- package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +153 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +233 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +298 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +749 -0
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +98 -95
- package/dist/src/cli/generate-testcase.d.ts +18 -51
- package/dist/src/cli/generate-testcase.d.ts.map +1 -1
- package/dist/src/cli/generate-testcase.js +127 -131
- package/dist/src/cli/index.d.ts +11 -2
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +49 -16
- package/dist/src/cli/probe.d.ts +2 -0
- package/dist/src/cli/probe.d.ts.map +1 -1
- package/dist/src/cli/probe.js +239 -13
- package/dist/src/cli/run.d.ts +136 -29
- package/dist/src/cli/run.d.ts.map +1 -1
- package/dist/src/cli/run.js +494 -65
- package/dist/src/cli/spec.d.ts +18 -11
- package/dist/src/cli/spec.d.ts.map +1 -1
- package/dist/src/cli/spec.js +74 -20
- package/dist/src/contracts/crud-business-module.d.ts +6 -0
- package/dist/src/contracts/crud-business-module.d.ts.map +1 -1
- package/dist/src/contracts/glue-contract-v1.d.ts +98 -0
- package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -0
- package/dist/src/contracts/glue-contract-v1.js +9 -0
- 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/contracts/observable-chain.d.ts +4 -0
- package/dist/src/contracts/observable-chain.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.d.ts +192 -0
- package/dist/src/extractors/code-list-json5.d.ts.map +1 -0
- package/dist/src/extractors/code-list-json5.js +91 -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/extractors/html-page.d.ts +9 -0
- package/dist/src/extractors/html-page.d.ts.map +1 -1
- package/dist/src/extractors/html-page.js +26 -2
- package/dist/src/extractors/prd-extractor.d.ts +70 -0
- package/dist/src/extractors/prd-extractor.d.ts.map +1 -0
- package/dist/src/extractors/prd-extractor.js +248 -0
- package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
- package/dist/src/generators/stage-skeleton-script.d.ts +87 -20
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +656 -238
- 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/normalizers/crud-contract.d.ts +3 -0
- package/dist/src/normalizers/crud-contract.d.ts.map +1 -1
- package/dist/src/normalizers/crud-contract.js +3 -0
- package/dist/src/runtime/ai-runtime-trace.d.ts +88 -0
- package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -0
- package/dist/src/runtime/ai-runtime-trace.js +119 -0
- package/dist/src/testcase/button-interaction-inference.d.ts +37 -0
- package/dist/src/testcase/button-interaction-inference.d.ts.map +1 -0
- package/dist/src/testcase/button-interaction-inference.js +385 -0
- package/dist/src/testcase/contract-testcase-assembler.d.ts +44 -0
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -0
- package/dist/src/testcase/contract-testcase-assembler.js +806 -0
- package/dist/src/testcase/prd-button-mapper.d.ts +72 -0
- package/dist/src/testcase/prd-button-mapper.d.ts.map +1 -0
- package/dist/src/testcase/prd-button-mapper.js +148 -0
- package/dist/src/testcase/runtime-context.d.ts +9 -0
- package/dist/src/testcase/runtime-context.d.ts.map +1 -0
- package/dist/src/testcase/runtime-context.js +1 -0
- package/dist/src/testcase/test-access.d.ts +38 -0
- package/dist/src/testcase/test-access.d.ts.map +1 -0
- package/dist/src/testcase/test-access.js +106 -0
- package/dist/src/testcase/testcase-execution-fields.d.ts +10 -0
- package/dist/src/testcase/testcase-execution-fields.d.ts.map +1 -0
- package/dist/src/testcase/testcase-execution-fields.js +1 -0
- package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
- package/dist/src/testcase/testcase-generator.js +6 -0
- package/dist/src/testcase/testcase-markdown.d.ts +11 -0
- package/dist/src/testcase/testcase-markdown.d.ts.map +1 -0
- package/dist/src/testcase/testcase-markdown.js +181 -0
- package/dist/src/testcase/testcase-skeleton.d.ts +4 -0
- package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -1
- package/dist/src/testcase/testcase-spec-assembly.d.ts +4 -0
- package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -1
- package/dist/src/testcase/testcase-v1-draft.d.ts +58 -0
- package/dist/src/testcase/testcase-v1-draft.d.ts.map +1 -0
- package/dist/src/testcase/testcase-v1-draft.js +13 -0
- package/dist/src/validation/contract.d.ts +14 -0
- package/dist/src/validation/contract.d.ts.map +1 -0
- package/dist/src/validation/contract.js +132 -0
- package/dist/src/validation/credentials.d.ts +7 -2
- package/dist/src/validation/credentials.d.ts.map +1 -1
- package/dist/src/validation/credentials.js +15 -4
- package/dist/src/validation/index.d.ts +2 -0
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +2 -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/testcase.d.ts +9 -7
- package/dist/src/validation/testcase.d.ts.map +1 -1
- package/dist/src/validation/testcase.js +181 -34
- package/docs/README.md +124 -134
- package/docs/mvp-usage-guide.md +298 -298
- package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
- package/examples/schemeresource.module-hints.json +38 -38
- package/examples/schemeresource.observable.code_list.md +37 -37
- package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
- package/examples/zwplace-placecategory-validation.json +29 -29
- package/examples/zwplace.module-hints.json +69 -69
- package/examples/zwplace.observable.code_list.md +37 -37
- package/package.json +2 -2
- package/dist/test/cli/extract-contract.test.d.ts +0 -2
- package/dist/test/cli/extract-contract.test.d.ts.map +0 -1
- package/dist/test/cli/extract-contract.test.js +0 -32
- package/dist/test/cli/generate-testcase.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase.test.js +0 -130
- package/dist/test/cli/index.test.d.ts +0 -2
- package/dist/test/cli/index.test.d.ts.map +0 -1
- package/dist/test/cli/index.test.js +0 -93
- package/dist/test/cli/run.test.d.ts +0 -2
- package/dist/test/cli/run.test.d.ts.map +0 -1
- package/dist/test/cli/run.test.js +0 -149
- package/dist/test/cli/spec.test.d.ts +0 -2
- package/dist/test/cli/spec.test.d.ts.map +0 -1
- package/dist/test/cli/spec.test.js +0 -196
- package/dist/test/crud-contract.test.d.ts +0 -2
- package/dist/test/crud-contract.test.d.ts.map +0 -1
- package/dist/test/crud-contract.test.js +0 -819
- package/dist/test/gap-inference.test.d.ts +0 -2
- package/dist/test/gap-inference.test.d.ts.map +0 -1
- package/dist/test/gap-inference.test.js +0 -597
- package/dist/test/generalization.test.d.ts +0 -2
- package/dist/test/generalization.test.d.ts.map +0 -1
- package/dist/test/generalization.test.js +0 -73
- package/dist/test/material-inventory.test.d.ts +0 -2
- package/dist/test/material-inventory.test.d.ts.map +0 -1
- package/dist/test/material-inventory.test.js +0 -141
- package/dist/test/observable-chain.test.d.ts +0 -2
- package/dist/test/observable-chain.test.d.ts.map +0 -1
- package/dist/test/observable-chain.test.js +0 -123
- package/dist/test/observable-pipeline.test.d.ts +0 -2
- package/dist/test/observable-pipeline.test.d.ts.map +0 -1
- package/dist/test/observable-pipeline.test.js +0 -461
- package/dist/test/page-structure.test.d.ts +0 -2
- package/dist/test/page-structure.test.d.ts.map +0 -1
- package/dist/test/page-structure.test.js +0 -45
- package/dist/test/scenario-inference.test.d.ts +0 -2
- package/dist/test/scenario-inference.test.d.ts.map +0 -1
- package/dist/test/scenario-inference.test.js +0 -73
- package/dist/test/stage-context.test.d.ts +0 -2
- package/dist/test/stage-context.test.d.ts.map +0 -1
- package/dist/test/stage-context.test.js +0 -395
- package/dist/test/testcase-generator.test.d.ts +0 -2
- package/dist/test/testcase-generator.test.d.ts.map +0 -1
- package/dist/test/testcase-generator.test.js +0 -276
- package/dist/test/testcase-skeleton.test.d.ts +0 -2
- package/dist/test/testcase-skeleton.test.d.ts.map +0 -1
- package/dist/test/testcase-skeleton.test.js +0 -185
- package/dist/test/testcase-spec-assembly.test.d.ts +0 -2
- package/dist/test/testcase-spec-assembly.test.d.ts.map +0 -1
- package/dist/test/testcase-spec-assembly.test.js +0 -105
- package/dist/test/util/credentials.test.d.ts +0 -2
- package/dist/test/util/credentials.test.d.ts.map +0 -1
- package/dist/test/util/credentials.test.js +0 -64
- package/dist/test/util/i18n-testcase.test.d.ts +0 -2
- package/dist/test/util/i18n-testcase.test.d.ts.map +0 -1
- package/dist/test/util/i18n-testcase.test.js +0 -119
- package/dist/test/util/softlink.test.d.ts +0 -2
- package/dist/test/util/softlink.test.d.ts.map +0 -1
- package/dist/test/util/softlink.test.js +0 -82
- package/dist/test/validation/credentials.test.d.ts +0 -2
- package/dist/test/validation/credentials.test.d.ts.map +0 -1
- package/dist/test/validation/credentials.test.js +0 -72
- package/dist/test/validation/projects-index.test.d.ts +0 -2
- package/dist/test/validation/projects-index.test.d.ts.map +0 -1
- package/dist/test/validation/projects-index.test.js +0 -48
- package/dist/test/validation/testcase.test.d.ts +0 -2
- package/dist/test/validation/testcase.test.d.ts.map +0 -1
- package/dist/test/validation/testcase.test.js +0 -129
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
- package/dist/vitest.config.js +0 -7
|
@@ -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":"crud-contract.d.ts","sourceRoot":"","sources":["../../../src/normalizers/crud-contract.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"crud-contract.d.ts","sourceRoot":"","sources":["../../../src/normalizers/crud-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAIV,0BAA0B,EAQ1B,WAAW,EASZ,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,KAAK,EAAuB,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACzF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,wBAAwB,CAAC;IACnC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAuhCF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,sBAAsB,GAAG,0BAA0B,CAmDzG"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type AiRuntimeTaskStatus = 'pending' | 'resolved' | 'blocked' | 'needs_review' | 'failed';
|
|
2
|
+
export type AiRuntimeTask = {
|
|
3
|
+
caseId: string;
|
|
4
|
+
title: string;
|
|
5
|
+
role: string;
|
|
6
|
+
businessIntent: string;
|
|
7
|
+
assertionExpectation: string;
|
|
8
|
+
entryContext: Record<string, unknown>;
|
|
9
|
+
knownContext: Record<string, unknown>;
|
|
10
|
+
codingRules: Record<string, unknown>;
|
|
11
|
+
status: AiRuntimeTaskStatus;
|
|
12
|
+
};
|
|
13
|
+
export type AiRuntimeResult = {
|
|
14
|
+
caseId: string;
|
|
15
|
+
status: AiRuntimeTaskStatus;
|
|
16
|
+
observation?: Record<string, unknown>;
|
|
17
|
+
decision?: Record<string, unknown>;
|
|
18
|
+
actionFlow?: string[];
|
|
19
|
+
verification?: Record<string, unknown>;
|
|
20
|
+
specSnippet?: string;
|
|
21
|
+
evidence?: Array<{
|
|
22
|
+
source: string;
|
|
23
|
+
note: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
export type RepairRound = {
|
|
27
|
+
round: number;
|
|
28
|
+
status: 'failed' | 'repaired' | 'blocked' | 'needs_review';
|
|
29
|
+
failureType: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
};
|
|
32
|
+
export type AiRuntimeTrace = {
|
|
33
|
+
schemaVersion: 'ep-stage-ai-runtime-trace/v1';
|
|
34
|
+
testcasePath: string;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
tasks: AiRuntimeTask[];
|
|
37
|
+
results: AiRuntimeResult[];
|
|
38
|
+
repairRounds: RepairRound[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* 创建 AI Runtime Trace 初始结构。
|
|
42
|
+
*
|
|
43
|
+
* @param input - testcase 路径和本轮待执行任务。
|
|
44
|
+
* @returns 初始 trace。
|
|
45
|
+
*/
|
|
46
|
+
export declare function createInitialAiRuntimeTrace(input: {
|
|
47
|
+
testcasePath: string;
|
|
48
|
+
tasks: AiRuntimeTask[];
|
|
49
|
+
}): AiRuntimeTrace;
|
|
50
|
+
/**
|
|
51
|
+
* 写入 AI Runtime Trace 文件。
|
|
52
|
+
*
|
|
53
|
+
* @param tracePath - trace 文件路径。
|
|
54
|
+
* @param trace - 待写入 trace 内容。
|
|
55
|
+
*/
|
|
56
|
+
export declare function writeAiRuntimeTrace(tracePath: string, trace: AiRuntimeTrace): void;
|
|
57
|
+
/**
|
|
58
|
+
* 将 Playwright 执行输出归类为需要人工确认的修补方向。
|
|
59
|
+
*
|
|
60
|
+
* @param output - Playwright stdout/stderr 合并输出。
|
|
61
|
+
* @returns 失败类型、中文摘要和是否需要人工确认。
|
|
62
|
+
*/
|
|
63
|
+
export declare function classifyRunFailure(output: string): {
|
|
64
|
+
failureType: string;
|
|
65
|
+
summary: string;
|
|
66
|
+
needsHumanDirection: boolean;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* 计算下一个 repair round 编号。
|
|
70
|
+
*
|
|
71
|
+
* @param caseDir - 当前需求目录。
|
|
72
|
+
* @returns 从 1 开始递增的 repair round 编号。
|
|
73
|
+
*/
|
|
74
|
+
export declare function nextRepairRound(caseDir: string): number;
|
|
75
|
+
/**
|
|
76
|
+
* 写入等待人审的修补报告。
|
|
77
|
+
*
|
|
78
|
+
* @param input - 需求目录、轮次、失败分类和执行输出。
|
|
79
|
+
* @returns repair-report.md 路径。
|
|
80
|
+
*/
|
|
81
|
+
export declare function writeRepairReport(input: {
|
|
82
|
+
caseDir: string;
|
|
83
|
+
round: number;
|
|
84
|
+
failureType: string;
|
|
85
|
+
summary: string;
|
|
86
|
+
output: string;
|
|
87
|
+
}): string;
|
|
88
|
+
//# sourceMappingURL=ai-runtime-trace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-runtime-trace.d.ts","sourceRoot":"","sources":["../../../src/runtime/ai-runtime-trace.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,cAAc,GAAG,QAAQ,CAAC;AAEjG,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,MAAM,EAAE,mBAAmB,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,mBAAmB,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,cAAc,CAAC;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,8BAA8B,CAAC;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB,GAAG,cAAc,CASjB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,CAGlF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,OAAO,CAAC;CAC9B,CAmCA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQvD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAyBT"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import JSON5 from 'json5';
|
|
4
|
+
/**
|
|
5
|
+
* 创建 AI Runtime Trace 初始结构。
|
|
6
|
+
*
|
|
7
|
+
* @param input - testcase 路径和本轮待执行任务。
|
|
8
|
+
* @returns 初始 trace。
|
|
9
|
+
*/
|
|
10
|
+
export function createInitialAiRuntimeTrace(input) {
|
|
11
|
+
return {
|
|
12
|
+
schemaVersion: 'ep-stage-ai-runtime-trace/v1',
|
|
13
|
+
testcasePath: input.testcasePath,
|
|
14
|
+
createdAt: new Date().toISOString(),
|
|
15
|
+
tasks: input.tasks,
|
|
16
|
+
results: [],
|
|
17
|
+
repairRounds: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 写入 AI Runtime Trace 文件。
|
|
22
|
+
*
|
|
23
|
+
* @param tracePath - trace 文件路径。
|
|
24
|
+
* @param trace - 待写入 trace 内容。
|
|
25
|
+
*/
|
|
26
|
+
export function writeAiRuntimeTrace(tracePath, trace) {
|
|
27
|
+
mkdirSync(path.dirname(tracePath), { recursive: true });
|
|
28
|
+
writeFileSync(tracePath, `${JSON5.stringify(trace, null, 2)}\n`, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 将 Playwright 执行输出归类为需要人工确认的修补方向。
|
|
32
|
+
*
|
|
33
|
+
* @param output - Playwright stdout/stderr 合并输出。
|
|
34
|
+
* @returns 失败类型、中文摘要和是否需要人工确认。
|
|
35
|
+
*/
|
|
36
|
+
export function classifyRunFailure(output) {
|
|
37
|
+
// 断言失败优先于 iframe/locator 关键字,因为 Playwright 错误信息里常同时包含这些词。
|
|
38
|
+
if (/expect\(.*\)\.(toHaveCount|toBe|toContain|toEqual|toHaveText|toBeVisible)/i.test(output)) {
|
|
39
|
+
return {
|
|
40
|
+
failureType: 'assertion_failure',
|
|
41
|
+
summary: '业务断言未通过(如行数、文本值不匹配)',
|
|
42
|
+
needsHumanDirection: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (/frame|iframe/i.test(output)) {
|
|
46
|
+
return {
|
|
47
|
+
failureType: 'iframe_context_error',
|
|
48
|
+
summary: '疑似 iframe 上下文错误',
|
|
49
|
+
needsHumanDirection: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (/locator|strict mode|Timeout/i.test(output)) {
|
|
53
|
+
return {
|
|
54
|
+
failureType: 'locator_or_wait_error',
|
|
55
|
+
summary: '疑似定位或等待时机错误',
|
|
56
|
+
needsHumanDirection: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (/permission|403|unauthorized/i.test(output)) {
|
|
60
|
+
return {
|
|
61
|
+
failureType: 'permission_blocked',
|
|
62
|
+
summary: '疑似权限不足',
|
|
63
|
+
needsHumanDirection: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
failureType: 'unknown_failure',
|
|
68
|
+
summary: '未分类执行失败',
|
|
69
|
+
needsHumanDirection: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 计算下一个 repair round 编号。
|
|
74
|
+
*
|
|
75
|
+
* @param caseDir - 当前需求目录。
|
|
76
|
+
* @returns 从 1 开始递增的 repair round 编号。
|
|
77
|
+
*/
|
|
78
|
+
export function nextRepairRound(caseDir) {
|
|
79
|
+
const repairRoot = path.join(caseDir, '.ep-stage', 'repair-runs');
|
|
80
|
+
if (!existsSync(repairRoot))
|
|
81
|
+
return 1;
|
|
82
|
+
const maxRound = readdirSync(repairRoot, { withFileTypes: true })
|
|
83
|
+
.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
|
|
84
|
+
.map((entry) => Number(entry.name))
|
|
85
|
+
.reduce((max, current) => Math.max(max, current), 0);
|
|
86
|
+
return maxRound + 1;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 写入等待人审的修补报告。
|
|
90
|
+
*
|
|
91
|
+
* @param input - 需求目录、轮次、失败分类和执行输出。
|
|
92
|
+
* @returns repair-report.md 路径。
|
|
93
|
+
*/
|
|
94
|
+
export function writeRepairReport(input) {
|
|
95
|
+
const roundName = String(input.round).padStart(3, '0');
|
|
96
|
+
const reportDir = path.join(input.caseDir, '.ep-stage', 'repair-runs', roundName);
|
|
97
|
+
mkdirSync(reportDir, { recursive: true });
|
|
98
|
+
const reportPath = path.join(reportDir, 'repair-report.md');
|
|
99
|
+
const body = [
|
|
100
|
+
`# 第 ${input.round} 轮修补报告`,
|
|
101
|
+
'',
|
|
102
|
+
`失败用例:完整 Playwright spec 执行失败`,
|
|
103
|
+
`失败类型:${input.failureType}`,
|
|
104
|
+
`失败摘要:${input.summary}`,
|
|
105
|
+
'',
|
|
106
|
+
'## 失败输出',
|
|
107
|
+
'',
|
|
108
|
+
'```text',
|
|
109
|
+
input.output.slice(0, 4000),
|
|
110
|
+
'```',
|
|
111
|
+
'',
|
|
112
|
+
'## 需要你确认',
|
|
113
|
+
'',
|
|
114
|
+
'请确认修补方向。Agent 不会在未确认时静默改变测试意图。',
|
|
115
|
+
'',
|
|
116
|
+
].join('\n');
|
|
117
|
+
writeFileSync(reportPath, body, 'utf8');
|
|
118
|
+
return reportPath;
|
|
119
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 前端按钮交互行为推理器。
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 从 code_list.json5 的 sourceRoot + pages.list.path 定位列表页 HTML;
|
|
6
|
+
* 2. 解析 HTML 中自定义按钮的 onclick 属性及同页内联脚本中的函数体;
|
|
7
|
+
* 3. 判断交互模式(确认弹窗 / toast / 异步调用 / 打开弹窗 / 通用);
|
|
8
|
+
* 4. 为每个 custom 按钮生成精确的中文步骤与断言;
|
|
9
|
+
* 5. 当 HTML 不可读、解析失败或模式未知时,回退到通用描述并记录原因。
|
|
10
|
+
*/
|
|
11
|
+
import type { ExtractedCodeListJson5 } from '../extractors/code-list-json5.js';
|
|
12
|
+
/** 按钮交互模式。 */
|
|
13
|
+
export type ButtonInteractionPattern = 'confirm_then_execute' | 'toast' | 'async_call' | 'open_dialog' | 'generic';
|
|
14
|
+
/** 单按钮推理结果。 */
|
|
15
|
+
export type ButtonInteractionInference = {
|
|
16
|
+
label: string;
|
|
17
|
+
pattern: ButtonInteractionPattern;
|
|
18
|
+
functionName?: string;
|
|
19
|
+
steps: string;
|
|
20
|
+
assertion: string;
|
|
21
|
+
evidence: string[];
|
|
22
|
+
fallbackReason?: string;
|
|
23
|
+
};
|
|
24
|
+
/** 推理选项。 */
|
|
25
|
+
export type InferButtonInteractionsOptions = {
|
|
26
|
+
/** 覆盖 code_list.sourceRoot.root 的源码根目录。 */
|
|
27
|
+
sourceRootOverride?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 推理所有 custom 按钮的交互行为。
|
|
31
|
+
*
|
|
32
|
+
* @param codeList - 解析后的 code_list.json5。
|
|
33
|
+
* @param options - 可选覆盖。
|
|
34
|
+
* @returns label -> 推理结果 的映射。
|
|
35
|
+
*/
|
|
36
|
+
export declare function inferButtonInteractions(codeList: ExtractedCodeListJson5, options?: InferButtonInteractionsOptions): Map<string, ButtonInteractionInference>;
|
|
37
|
+
//# sourceMappingURL=button-interaction-inference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button-interaction-inference.d.ts","sourceRoot":"","sources":["../../../src/testcase/button-interaction-inference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAE/E,cAAc;AACd,MAAM,MAAM,wBAAwB,GAChC,sBAAsB,GACtB,OAAO,GACP,YAAY,GACZ,aAAa,GACb,SAAS,CAAC;AAEd,eAAe;AACf,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,wBAAwB,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,YAAY;AACZ,MAAM,MAAM,8BAA8B,GAAG;IAC3C,2CAA2C;IAC3C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AA0QF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,sBAAsB,EAChC,OAAO,CAAC,EAAE,8BAA8B,GACvC,GAAG,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAqIzC"}
|