@epoint-testtech/ep-stage-skill 0.0.10 → 0.5.0-alpha.2
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 -223
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -275
- 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 -95
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +273 -296
- package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -30
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -132
- package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -44
- 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 +127 -166
- package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +95 -98
- package/dist/src/cli/generate-testcase.d.ts +0 -1
- package/dist/src/cli/generate-testcase.d.ts.map +1 -1
- package/dist/src/cli/generate-testcase.js +1 -6
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +14 -38
- package/dist/src/cli/run.d.ts +0 -2
- package/dist/src/cli/run.d.ts.map +1 -1
- package/dist/src/cli/run.js +21 -70
- package/dist/src/cli/spec.d.ts.map +1 -1
- package/dist/src/cli/spec.js +0 -9
- package/dist/src/contracts/glue-contract-v1.d.ts +0 -35
- package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.d.ts +1 -34
- package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.js +0 -1
- package/dist/src/extractors/html-page.d.ts +0 -9
- package/dist/src/extractors/html-page.d.ts.map +1 -1
- package/dist/src/extractors/html-page.js +2 -26
- package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
- package/dist/src/generators/stage-skeleton-script.d.ts +0 -50
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +116 -449
- package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -1
- package/dist/src/runtime/ai-runtime-trace.js +0 -8
- package/dist/src/testcase/contract-testcase-assembler.d.ts +0 -28
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
- package/dist/src/testcase/contract-testcase-assembler.js +75 -329
- package/dist/src/validation/contract.d.ts.map +1 -1
- package/dist/src/validation/contract.js +0 -29
- package/dist/test/cli/generate-testcase-json5.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase-json5.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase-json5.test.js +154 -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 +114 -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 +429 -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 +112 -0
- package/dist/test/code-list-json5.test.d.ts +2 -0
- package/dist/test/code-list-json5.test.d.ts.map +1 -0
- package/dist/test/code-list-json5.test.js +84 -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/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 +155 -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/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/runtime/ai-runtime-trace.test.d.ts +2 -0
- package/dist/test/runtime/ai-runtime-trace.test.d.ts.map +1 -0
- package/dist/test/runtime/ai-runtime-trace.test.js +78 -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 +395 -0
- package/dist/test/testcase/testcase-markdown.test.d.ts +2 -0
- package/dist/test/testcase/testcase-markdown.test.d.ts.map +1 -0
- package/dist/test/testcase/testcase-markdown.test.js +48 -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/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/contract.test.d.ts +2 -0
- package/dist/test/validation/contract.test.d.ts.map +1 -0
- package/dist/test/validation/contract.test.js +116 -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 +74 -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-v1-draft.test.d.ts +2 -0
- package/dist/test/validation/testcase-v1-draft.test.d.ts.map +1 -0
- package/dist/test/validation/testcase-v1-draft.test.js +173 -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 +124 -124
- 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 +19 -13
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +0 -72
- package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +0 -233
- package/codex-skill/ep-stage/page-element-scan/SKILL.md +0 -91
- package/codex-skill/ep-stage/page-element-scan/evals/evals.json +0 -37
- package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +0 -85
- package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +0 -153
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +0 -233
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +0 -298
- package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +0 -749
- package/dist/src/cli/probe.d.ts +0 -46
- package/dist/src/cli/probe.d.ts.map +0 -1
- package/dist/src/cli/probe.js +0 -447
- package/dist/src/cli/run-report.d.ts +0 -46
- package/dist/src/cli/run-report.d.ts.map +0 -1
- package/dist/src/cli/run-report.js +0 -331
- package/dist/src/extractors/prd-extractor.d.ts +0 -70
- package/dist/src/extractors/prd-extractor.d.ts.map +0 -1
- package/dist/src/extractors/prd-extractor.js +0 -248
- package/dist/src/testcase/button-interaction-inference.d.ts +0 -37
- package/dist/src/testcase/button-interaction-inference.d.ts.map +0 -1
- package/dist/src/testcase/button-interaction-inference.js +0 -385
- package/dist/src/testcase/prd-button-mapper.d.ts +0 -72
- package/dist/src/testcase/prd-button-mapper.d.ts.map +0 -1
- package/dist/src/testcase/prd-button-mapper.js +0 -148
- package/dist/src/testcase/testcase-v2.d.ts +0 -50
- package/dist/src/testcase/testcase-v2.d.ts.map +0 -1
- package/dist/src/testcase/testcase-v2.js +0 -1
- package/docs/mvp-usage-guide.md +0 -298
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { compareObservableRuns, summarizeGeneralizationRun, } from '../src/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* 泛化评估测试。
|
|
5
|
+
*
|
|
6
|
+
* 验证 summarizeGeneralizationRun 把单次 run 汇总为可对比指标,
|
|
7
|
+
* compareObservableRuns 计算两个系统的场景差异/coverage delta/物料解析分歧。
|
|
8
|
+
*/
|
|
9
|
+
describe('generalization evaluation', () => {
|
|
10
|
+
it('summarizes a single run into comparable metrics', () => {
|
|
11
|
+
const metricsA = summarizeGeneralizationRun({
|
|
12
|
+
moduleId: 'zwplace',
|
|
13
|
+
resolvedInputs: {
|
|
14
|
+
moduleId: { status: 'resolved', value: 'zwplace', evidence: '' },
|
|
15
|
+
docsDir: { status: 'resolved', path: '/a/docs', evidence: '' },
|
|
16
|
+
webappDir: { status: 'resolved', path: '/a/webapp', evidence: '' },
|
|
17
|
+
javaActionsDir: { status: 'resolved', path: '/a/java', evidence: '' },
|
|
18
|
+
hintsPath: { status: 'resolved', path: '/a/hints.json', evidence: '' },
|
|
19
|
+
menu: { status: 'resolved', value: 'm', evidence: '' },
|
|
20
|
+
},
|
|
21
|
+
scenario: {
|
|
22
|
+
moduleId: 'zwplace',
|
|
23
|
+
candidates: [
|
|
24
|
+
{ scenario: 'crud.single-page', confidence: 'high', evidenceRefs: [], matchedSkeletonCapabilities: [], unresolved: [] },
|
|
25
|
+
{ scenario: 'crud.nested', confidence: 'medium', evidenceRefs: [], matchedSkeletonCapabilities: [], unresolved: [] },
|
|
26
|
+
],
|
|
27
|
+
gate: { status: 'confirmed', confirmedBy: 'human', reason: 'ok' },
|
|
28
|
+
reasoningSummary: { conclusion: '', evidenceChain: [], alternatives: [], confidence: 'high', risks: [], needsHumanReview: false },
|
|
29
|
+
},
|
|
30
|
+
coverageDiff: {
|
|
31
|
+
moduleId: 'zwplace',
|
|
32
|
+
skeletonCoverageId: 'crud-plus-export/v1',
|
|
33
|
+
baseline: { plannedWorkflows: ['w1'], candidateWorkflows: ['w2'], unresolvedWorkflows: [] },
|
|
34
|
+
covered: [{ actionId: 'a', label: 'l', pageId: 'p', status: 'covered', reason: '' }],
|
|
35
|
+
partiallyCovered: [],
|
|
36
|
+
gapCandidates: [],
|
|
37
|
+
unresolved: [],
|
|
38
|
+
nestedCrudCandidates: [{ pageId: 'p', evidence: [], supportedActions: [], unresolvedReason: 'r' }],
|
|
39
|
+
comparison: { both: [], onlyBaseline: [], onlyGeneralized: [] },
|
|
40
|
+
reasoningSummary: { conclusion: '', evidenceChain: [], alternatives: [], confidence: 'medium', risks: [], needsHumanReview: true },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
expect(metricsA).toMatchObject({
|
|
44
|
+
moduleId: 'zwplace',
|
|
45
|
+
scenarios: ['crud.single-page', 'crud.nested'],
|
|
46
|
+
coverage: { covered: 1, gapCandidate: 0, nestedCrud: 1 },
|
|
47
|
+
baselineWorkflowCount: 2,
|
|
48
|
+
materialResolution: { docsDir: 'resolved', webappDir: 'resolved', javaActionsDir: 'resolved', hintsPath: 'resolved' },
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it('compares two systems by observability metrics', () => {
|
|
52
|
+
const metricsA = {
|
|
53
|
+
moduleId: 'zwplace',
|
|
54
|
+
materialResolution: { docsDir: 'resolved', webappDir: 'resolved', javaActionsDir: 'resolved', hintsPath: 'resolved' },
|
|
55
|
+
scenarios: ['crud.single-page', 'crud.nested'],
|
|
56
|
+
coverage: { covered: 1, gapCandidate: 0, nestedCrud: 1 },
|
|
57
|
+
baselineWorkflowCount: 2,
|
|
58
|
+
};
|
|
59
|
+
const metricsB = {
|
|
60
|
+
moduleId: 'other',
|
|
61
|
+
materialResolution: { docsDir: 'missing', webappDir: 'resolved', javaActionsDir: 'resolved', hintsPath: 'missing' },
|
|
62
|
+
scenarios: ['crud.single-page', 'export'],
|
|
63
|
+
coverage: { covered: 3, gapCandidate: 2, nestedCrud: 0 },
|
|
64
|
+
baselineWorkflowCount: 5,
|
|
65
|
+
};
|
|
66
|
+
const cmp = compareObservableRuns(metricsA, metricsB);
|
|
67
|
+
expect(cmp.scenariosOnlyInA).toEqual(['crud.nested']);
|
|
68
|
+
expect(cmp.scenariosOnlyInB).toEqual(['export']);
|
|
69
|
+
expect(cmp.coverageDelta).toEqual({ covered: -2, gapCandidate: -2, nestedCrud: 1 });
|
|
70
|
+
expect(cmp.materialResolutionDivergence).toContain('docsDir');
|
|
71
|
+
expect(cmp.materialResolutionDivergence).toContain('hintsPath');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"material-inventory.test.d.ts","sourceRoot":"","sources":["../../test/material-inventory.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { extractCodeListSummary, resolveMaterialInventory, } from '../src/index.js';
|
|
7
|
+
describe('material inventory resolver', () => {
|
|
8
|
+
it('maps code_list material refs to contract generation inputs', () => {
|
|
9
|
+
const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-'));
|
|
10
|
+
const codeListPath = path.join(root, '_docs', 'code_list.md');
|
|
11
|
+
const specPath = path.join(root, '_docs', '003-场所窗口信息管理-20260518', 'spec.yaml');
|
|
12
|
+
const htmlPath = path.join(root, 'src/main/webapp/perpage/zwplace/gxhzwplacelist.html');
|
|
13
|
+
const javaPath = path.join(root, 'src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java');
|
|
14
|
+
const hintsPath = path.join(root, '_docs', 'zwplace.module-hints.json');
|
|
15
|
+
mkdirSync(path.dirname(specPath), { recursive: true });
|
|
16
|
+
mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
17
|
+
mkdirSync(path.dirname(javaPath), { recursive: true });
|
|
18
|
+
writeFileSync(specPath, 'module:\n id: zwplace\n');
|
|
19
|
+
writeFileSync(htmlPath, '<html></html>');
|
|
20
|
+
writeFileSync(javaPath, 'class GxhZwPlaceListAction {}');
|
|
21
|
+
writeFileSync(hintsPath, '{}');
|
|
22
|
+
writeFileSync(codeListPath, [
|
|
23
|
+
'# 场所窗口信息管理代码清单',
|
|
24
|
+
`> 整理目录:\`${root}\``,
|
|
25
|
+
'menu: 场所窗口信息管理>场所窗口信息列表',
|
|
26
|
+
'`_docs/003-场所窗口信息管理-20260518/spec.yaml`',
|
|
27
|
+
'`src/main/webapp/perpage/zwplace/gxhzwplacelist.html`',
|
|
28
|
+
'`src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`',
|
|
29
|
+
'`_docs/zwplace.module-hints.json`',
|
|
30
|
+
].join('\n'), 'utf8');
|
|
31
|
+
const codeList = extractCodeListSummary(codeListPath);
|
|
32
|
+
const result = resolveMaterialInventory({
|
|
33
|
+
codeListPath,
|
|
34
|
+
codeList,
|
|
35
|
+
debugOverrides: {},
|
|
36
|
+
stageContext: {
|
|
37
|
+
projectName: 'glue-project',
|
|
38
|
+
projectDir: root,
|
|
39
|
+
mode: 'glue',
|
|
40
|
+
envPath: '.env',
|
|
41
|
+
envFilePath: path.join(root, '.env'),
|
|
42
|
+
knowledgeRoot: root,
|
|
43
|
+
codeListPaths: [codeListPath],
|
|
44
|
+
stageContextPath: path.join(root, 'stage-context.md'),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(result.resolvedInputs).toMatchObject({
|
|
48
|
+
moduleId: { status: 'resolved', value: 'zwplace' },
|
|
49
|
+
docsDir: { status: 'resolved', path: path.dirname(specPath) },
|
|
50
|
+
webappDir: { status: 'resolved', path: path.dirname(htmlPath) },
|
|
51
|
+
javaActionsDir: { status: 'resolved', path: path.dirname(javaPath) },
|
|
52
|
+
hintsPath: { status: 'resolved', path: hintsPath },
|
|
53
|
+
menu: { status: 'resolved', value: '场所窗口信息管理>场所窗口信息列表' },
|
|
54
|
+
});
|
|
55
|
+
expect(result.gate.status).toBe('confirmed');
|
|
56
|
+
});
|
|
57
|
+
it('does not infer knowledgeRoot from _docs hierarchy when code_list lacks structured root info', () => {
|
|
58
|
+
const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-no-root-'));
|
|
59
|
+
const codeListPath = path.join(root, '_docs', '014-测试需求10-20260622', 'code_list.md');
|
|
60
|
+
mkdirSync(path.dirname(codeListPath), { recursive: true });
|
|
61
|
+
writeFileSync(codeListPath, [
|
|
62
|
+
'# 场所窗口信息管理代码清单',
|
|
63
|
+
'`src/main/webapp/perpage/zwplace/gxhzwplacelist.html`',
|
|
64
|
+
].join('\n'), 'utf8');
|
|
65
|
+
const codeList = extractCodeListSummary(codeListPath);
|
|
66
|
+
expect(codeList.knowledgeRoot).toBeUndefined();
|
|
67
|
+
expect(codeList.knowledgeRootSource).toBe('none');
|
|
68
|
+
});
|
|
69
|
+
it('stops at material gate when webapp or java actions cannot be resolved', () => {
|
|
70
|
+
const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-missing-'));
|
|
71
|
+
const codeListPath = path.join(root, 'code_list.md');
|
|
72
|
+
writeFileSync(codeListPath, '# 空代码清单\n');
|
|
73
|
+
const result = resolveMaterialInventory({
|
|
74
|
+
codeListPath,
|
|
75
|
+
codeList: extractCodeListSummary(codeListPath),
|
|
76
|
+
debugOverrides: {},
|
|
77
|
+
});
|
|
78
|
+
expect(result.gate).toMatchObject({
|
|
79
|
+
status: 'needs_review',
|
|
80
|
+
});
|
|
81
|
+
expect(result.resolvedInputs.webappDir.status).toBe('missing');
|
|
82
|
+
expect(result.resolvedInputs.javaActionsDir.status).toBe('missing');
|
|
83
|
+
});
|
|
84
|
+
it('supports a writable external code_list that already contains write-back fields', () => {
|
|
85
|
+
const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-writeback-'));
|
|
86
|
+
const knowledgeRoot = path.join(root, 'knowledge-project', 'zwplace');
|
|
87
|
+
const writableDir = path.join(root, 'agent-input');
|
|
88
|
+
const docsPath = path.join(writableDir, 'zwplace-observable-docs', 'zwplace.context.md');
|
|
89
|
+
const hintsPath = path.join(writableDir, 'zwplace.module-hints.json');
|
|
90
|
+
const htmlPath = path.join(knowledgeRoot, 'src', 'main', 'webapp', 'perpage', 'zwplace', 'gxhzwplacelist.html');
|
|
91
|
+
const javaPath = path.join(knowledgeRoot, 'src', 'main', 'java', 'com', 'epoint', 'zwplace', 'action', 'GxhZwPlaceListAction.java');
|
|
92
|
+
const codeListPath = path.join(writableDir, 'zwplace.observable.code_list.md');
|
|
93
|
+
mkdirSync(path.dirname(docsPath), { recursive: true });
|
|
94
|
+
mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
95
|
+
mkdirSync(path.dirname(javaPath), { recursive: true });
|
|
96
|
+
writeFileSync(docsPath, '# zwplace context\n');
|
|
97
|
+
writeFileSync(hintsPath, '{}');
|
|
98
|
+
writeFileSync(htmlPath, '<html></html>');
|
|
99
|
+
writeFileSync(javaPath, 'class GxhZwPlaceListAction {}');
|
|
100
|
+
writeFileSync(codeListPath, [
|
|
101
|
+
'# 场所窗口信息管理代码清单',
|
|
102
|
+
`knowledgeRoot: ${knowledgeRoot}`,
|
|
103
|
+
'menu: 场所窗口信息管理>场所窗口信息列表',
|
|
104
|
+
'`./zwplace-observable-docs/zwplace.context.md`',
|
|
105
|
+
'`src/main/webapp/perpage/zwplace/gxhzwplacelist.html`',
|
|
106
|
+
'`src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`',
|
|
107
|
+
'`./zwplace.module-hints.json`',
|
|
108
|
+
].join('\n'), 'utf8');
|
|
109
|
+
const codeList = extractCodeListSummary(codeListPath);
|
|
110
|
+
const result = resolveMaterialInventory({
|
|
111
|
+
codeListPath,
|
|
112
|
+
codeList,
|
|
113
|
+
debugOverrides: {},
|
|
114
|
+
});
|
|
115
|
+
expect(codeList.knowledgeRoot).toBe(knowledgeRoot);
|
|
116
|
+
expect(codeList.knowledgeRootSource).toBe('code_list');
|
|
117
|
+
expect(codeList.menu).toBe('场所窗口信息管理>场所窗口信息列表');
|
|
118
|
+
expect(result.resolvedInputs).toMatchObject({
|
|
119
|
+
docsDir: { status: 'resolved', path: path.dirname(docsPath) },
|
|
120
|
+
webappDir: { status: 'resolved', path: path.dirname(htmlPath) },
|
|
121
|
+
javaActionsDir: { status: 'resolved', path: path.dirname(javaPath) },
|
|
122
|
+
hintsPath: { status: 'resolved', path: hintsPath },
|
|
123
|
+
menu: { status: 'resolved', value: '场所窗口信息管理>场所窗口信息列表' },
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
// 本用例依赖 knowledge-project/epoint-web-v9.5.2 submodule 的真实框架源码;CI 不拉取 submodule,检出后只留空目录,因此按实际内容目录判断,缺席时跳过,仅在本机全量验证。
|
|
127
|
+
it.skipIf(!existsSync(path.resolve(path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'examples'), '../../../knowledge-project/epoint-web-v9.5.2/src/main/java/com/epoint/schemeresource/action')))('resolves the checked-in writable schemeresource code_list example', () => {
|
|
128
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
129
|
+
const examplesDir = path.join(packageRoot, 'examples');
|
|
130
|
+
const codeListPath = path.join(examplesDir, 'schemeresource.observable.code_list.md');
|
|
131
|
+
const knowledgeRoot = path.resolve(examplesDir, '../../../knowledge-project/epoint-web-v9.5.2');
|
|
132
|
+
const docsDir = path.join(examplesDir, 'schemeresource-observable-docs');
|
|
133
|
+
const webappDir = path.join(knowledgeRoot, 'src', 'main', 'webapp', 'perpage', 'schemeresource');
|
|
134
|
+
const javaActionsDir = path.join(knowledgeRoot, 'src', 'main', 'java', 'com', 'epoint', 'schemeresource', 'action');
|
|
135
|
+
const hintsPath = path.join(examplesDir, 'schemeresource.module-hints.json');
|
|
136
|
+
const codeList = extractCodeListSummary(codeListPath);
|
|
137
|
+
const result = resolveMaterialInventory({
|
|
138
|
+
codeListPath,
|
|
139
|
+
codeList,
|
|
140
|
+
debugOverrides: {},
|
|
141
|
+
});
|
|
142
|
+
expect(codeList.knowledgeRoot).toBe(knowledgeRoot);
|
|
143
|
+
expect(codeList.knowledgeRootSource).toBe('code_list');
|
|
144
|
+
expect(codeList.menu).toBe('方案资源文档>方案资源文档列表');
|
|
145
|
+
expect(result.resolvedInputs).toMatchObject({
|
|
146
|
+
moduleId: { status: 'resolved', value: 'schemeresource' },
|
|
147
|
+
docsDir: { status: 'resolved', path: docsDir },
|
|
148
|
+
webappDir: { status: 'resolved', path: webappDir },
|
|
149
|
+
javaActionsDir: { status: 'resolved', path: javaActionsDir },
|
|
150
|
+
hintsPath: { status: 'resolved', path: hintsPath },
|
|
151
|
+
menu: { status: 'resolved', value: '方案资源文档>方案资源文档列表' },
|
|
152
|
+
});
|
|
153
|
+
expect(result.gate.status).toBe('confirmed');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observable-chain.test.d.ts","sourceRoot":"","sources":["../../test/observable-chain.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { createTraceWriter, renderReviewSummary, } from '../src/index.js';
|
|
6
|
+
describe('observable glue chain trace writer', () => {
|
|
7
|
+
function createScenarioPayload() {
|
|
8
|
+
return {
|
|
9
|
+
stage: 'scenario-inference',
|
|
10
|
+
stageLabel: '场景推理追踪',
|
|
11
|
+
inputs: [{ kind: 'code_list', path: '/repo/knowledge/_docs/code_list.md' }],
|
|
12
|
+
outputs: [{ kind: 'scenario', path: 'scenario-inference.json' }],
|
|
13
|
+
reasoningSummary: {
|
|
14
|
+
conclusion: '当前页面是嵌套 CRUD 候选。',
|
|
15
|
+
evidenceChain: [
|
|
16
|
+
{
|
|
17
|
+
source: 'html',
|
|
18
|
+
path: '/repo/webapp/gxhzwplaceadd.html',
|
|
19
|
+
text: '弹窗页面中存在 mini-datagrid 与 添加窗口 按钮',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
alternatives: ['crud.single-page', 'crud.nested'],
|
|
23
|
+
confidence: 'medium',
|
|
24
|
+
risks: ['子表保存后断言未确认'],
|
|
25
|
+
needsHumanReview: true,
|
|
26
|
+
},
|
|
27
|
+
candidateCount: 1,
|
|
28
|
+
unresolvedCount: 1,
|
|
29
|
+
gate: {
|
|
30
|
+
status: 'needs_review',
|
|
31
|
+
reason: '需要人工确认嵌套 CRUD 是否进入 testcase。',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
it('writes stage json and summary markdown with reviewable reasoning', () => {
|
|
36
|
+
const projectDir = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-trace-'));
|
|
37
|
+
const writer = createTraceWriter({
|
|
38
|
+
projectDir,
|
|
39
|
+
runId: '20260616T120000Z-demo',
|
|
40
|
+
codeListPath: '/repo/knowledge/_docs/code_list.md',
|
|
41
|
+
});
|
|
42
|
+
const payload = createScenarioPayload();
|
|
43
|
+
const stagePath = writer.writeStage('scenario-inference', payload);
|
|
44
|
+
const summaryPath = writer.writeSummary([
|
|
45
|
+
{
|
|
46
|
+
stage: 'scenario-inference',
|
|
47
|
+
stageLabel: '场景推理追踪',
|
|
48
|
+
status: 'needs_review',
|
|
49
|
+
conclusion: payload.reasoningSummary?.conclusion ?? '',
|
|
50
|
+
needsHumanReview: true,
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
const traceDir = path.join(projectDir, '.stage', 'trace', '20260616T120000Z-demo');
|
|
54
|
+
expect(writer.runId).toBe('20260616T120000Z-demo');
|
|
55
|
+
expect(stagePath).toBe(path.join(traceDir, 'scenario-inference.json'));
|
|
56
|
+
expect(summaryPath).toBe(path.join(traceDir, 'summary.md'));
|
|
57
|
+
expect(existsSync(path.join(traceDir, 'scenario-inference.json'))).toBe(true);
|
|
58
|
+
expect(JSON.parse(readFileSync(path.join(traceDir, 'scenario-inference.json'), 'utf8'))).toMatchObject({
|
|
59
|
+
stage: 'scenario-inference',
|
|
60
|
+
reasoningSummary: {
|
|
61
|
+
confidence: 'medium',
|
|
62
|
+
needsHumanReview: true,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
expect(readFileSync(path.join(traceDir, 'summary.md'), 'utf8')).toContain('场景推理追踪');
|
|
66
|
+
expect(readFileSync(path.join(traceDir, 'summary.md'), 'utf8')).toContain('需要人工确认');
|
|
67
|
+
});
|
|
68
|
+
it('rejects stage write when file stage does not match payload stage', () => {
|
|
69
|
+
const projectDir = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-trace-'));
|
|
70
|
+
const writer = createTraceWriter({
|
|
71
|
+
projectDir,
|
|
72
|
+
runId: '20260616T120000Z-demo',
|
|
73
|
+
codeListPath: '/repo/knowledge/_docs/code_list.md',
|
|
74
|
+
});
|
|
75
|
+
expect(() => writer.writeStage('coverage-diff', createScenarioPayload())).toThrow('trace stage mismatch');
|
|
76
|
+
});
|
|
77
|
+
it('rejects unsafe run id path segments', () => {
|
|
78
|
+
const projectDir = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-trace-'));
|
|
79
|
+
const unsafeRunIds = ['../escape', 'nested/run', '/tmp/escape', 'trace..escape', '.', ''];
|
|
80
|
+
for (const runId of unsafeRunIds) {
|
|
81
|
+
expect(() => createTraceWriter({
|
|
82
|
+
projectDir,
|
|
83
|
+
runId,
|
|
84
|
+
codeListPath: '/repo/knowledge/_docs/code_list.md',
|
|
85
|
+
})).toThrow('invalid trace runId');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
it('renders human-readable review summary for conversation output', () => {
|
|
89
|
+
const text = renderReviewSummary({
|
|
90
|
+
title: '场景推理需要确认',
|
|
91
|
+
reasoningSummary: {
|
|
92
|
+
conclusion: '当前页面是嵌套 CRUD 候选。',
|
|
93
|
+
evidenceChain: [
|
|
94
|
+
{ source: 'html', path: 'gxhzwplaceadd.html', text: 'mini-datagrid + 添加窗口' },
|
|
95
|
+
],
|
|
96
|
+
alternatives: ['crud.single-page', 'crud.nested'],
|
|
97
|
+
confidence: 'medium',
|
|
98
|
+
risks: ['子表保存后断言未确认'],
|
|
99
|
+
needsHumanReview: true,
|
|
100
|
+
},
|
|
101
|
+
nextAction: 'confirm / correct / add-evidence / skip',
|
|
102
|
+
});
|
|
103
|
+
expect(text).toContain('结论:当前页面是嵌套 CRUD 候选。');
|
|
104
|
+
expect(text).toContain('证据链:');
|
|
105
|
+
expect(text).toContain('待确认项:子表保存后断言未确认');
|
|
106
|
+
expect(text).toContain('下一步:confirm / correct / add-evidence / skip');
|
|
107
|
+
});
|
|
108
|
+
it('renders empty evidence chain explicitly for review summary', () => {
|
|
109
|
+
const text = renderReviewSummary({
|
|
110
|
+
title: '场景推理需要确认',
|
|
111
|
+
reasoningSummary: {
|
|
112
|
+
conclusion: '当前证据不足。',
|
|
113
|
+
evidenceChain: [],
|
|
114
|
+
alternatives: [],
|
|
115
|
+
confidence: 'low',
|
|
116
|
+
risks: [],
|
|
117
|
+
needsHumanReview: true,
|
|
118
|
+
},
|
|
119
|
+
nextAction: 'add-evidence',
|
|
120
|
+
});
|
|
121
|
+
expect(text).toContain('证据链:\n- 无');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-structure.test.d.ts","sourceRoot":"","sources":["../../test/page-structure.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { extractHtmlPage, summarizePageStructure } from '../src/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* 页面结构信号抽取与场景归纳测试。
|
|
8
|
+
*
|
|
9
|
+
* 验证 extractHtmlPage 能从 HTML 静态结构钻探出 mini-datagrid / fui-condition /
|
|
10
|
+
* mini-grid-pager 等结构信号,且 summarizePageStructure 能据此识别嵌套 CRUD 候选。
|
|
11
|
+
*/
|
|
12
|
+
describe('page structure signals', () => {
|
|
13
|
+
it('extracts MiniUI grid, condition, pager and nested crud signals', () => {
|
|
14
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-html-'));
|
|
15
|
+
const htmlPath = path.join(dir, 'gxhzwplaceadd.html');
|
|
16
|
+
writeFileSync(htmlPath, [
|
|
17
|
+
'<html><head><title>新增场所信息管理</title></head><body>',
|
|
18
|
+
'<div class="fui-condition"><input /></div>',
|
|
19
|
+
'<a class="mini-button" onclick="addWindow()">添加窗口</a>',
|
|
20
|
+
'<a class="mini-button" onclick="deleteWindow()">删除选中</a>',
|
|
21
|
+
'<div id="windowGrid" class="mini-datagrid">',
|
|
22
|
+
' <div property="columns">',
|
|
23
|
+
' <div type="checkcolumn"></div>',
|
|
24
|
+
' <div type="indexcolumn">序</div>',
|
|
25
|
+
' <div field="windowname">窗口名称</div>',
|
|
26
|
+
' <div field="windowtype">窗口类型</div>',
|
|
27
|
+
' <div field="windowaddr">窗口地址</div>',
|
|
28
|
+
' </div>',
|
|
29
|
+
'</div>',
|
|
30
|
+
'<div class="mini-grid-pager"></div>',
|
|
31
|
+
'<script>function openChild(){ epoint.openTopDialog("新增窗口", "gxhwindowadd.html"); }</script>',
|
|
32
|
+
'</body></html>',
|
|
33
|
+
].join('\n'), 'utf8');
|
|
34
|
+
const page = extractHtmlPage(htmlPath);
|
|
35
|
+
expect(page.pageStructureSignals).toMatchObject({
|
|
36
|
+
dataGrid: { staticPresent: true, selector: "div[class*='mini-datagrid']" },
|
|
37
|
+
searchCondition: { staticPresent: true, selector: "div[class*='fui-condition']" },
|
|
38
|
+
pager: { staticPresent: true, selector: "div[class*='mini-grid-pager']" },
|
|
39
|
+
});
|
|
40
|
+
const summary = summarizePageStructure(page);
|
|
41
|
+
expect(summary.nestedCrudCandidate).toBe(true);
|
|
42
|
+
expect(summary.evidenceLabels).toContain('可见 mini-datagrid 结构存在');
|
|
43
|
+
expect(summary.evidenceLabels).toContain('可见 mini-grid-pager 结构存在');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-runtime-trace.test.d.ts","sourceRoot":"","sources":["../../../test/runtime/ai-runtime-trace.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import JSON5 from 'json5';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { classifyRunFailure, createInitialAiRuntimeTrace, nextRepairRound, writeAiRuntimeTrace, writeRepairReport, } from '../../src/runtime/ai-runtime-trace.js';
|
|
7
|
+
describe('ai runtime trace', () => {
|
|
8
|
+
it('writes minimal trace with tasks and no repair rounds', () => {
|
|
9
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), 'ai-trace-'));
|
|
10
|
+
const trace = createInitialAiRuntimeTrace({
|
|
11
|
+
testcasePath: path.join(dir, 'testcase.json5'),
|
|
12
|
+
tasks: [
|
|
13
|
+
{
|
|
14
|
+
caseId: 'syncWindowSystem',
|
|
15
|
+
title: '同步窗口管理系统按钮',
|
|
16
|
+
role: '业务管理员',
|
|
17
|
+
businessIntent: '验证同步',
|
|
18
|
+
assertionExpectation: '返回同步结果',
|
|
19
|
+
entryContext: {
|
|
20
|
+
loginUrl: 'http://example/login',
|
|
21
|
+
username: 'yz',
|
|
22
|
+
password: 'secret',
|
|
23
|
+
directUrl: 'http://example/page',
|
|
24
|
+
},
|
|
25
|
+
knownContext: {
|
|
26
|
+
queryKey: '不适用',
|
|
27
|
+
precondition: '使用业务管理员登录',
|
|
28
|
+
steps: '点击同步按钮',
|
|
29
|
+
assertion: '返回同步结果',
|
|
30
|
+
},
|
|
31
|
+
codingRules: {
|
|
32
|
+
devRulesPath: 'docs/00-memory/dev-rules.md',
|
|
33
|
+
},
|
|
34
|
+
status: 'pending',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
const tracePath = path.join(dir, 'ai-runtime-trace.json5');
|
|
39
|
+
writeAiRuntimeTrace(tracePath, trace);
|
|
40
|
+
const parsed = JSON5.parse(readFileSync(tracePath, 'utf8'));
|
|
41
|
+
expect(parsed.tasks[0].caseId).toBe('syncWindowSystem');
|
|
42
|
+
expect(parsed.repairRounds).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
it('classifies common Playwright run failures for human repair', () => {
|
|
45
|
+
expect(classifyRunFailure('frame was detached while resolving iframe')).toMatchObject({
|
|
46
|
+
failureType: 'iframe_context_error',
|
|
47
|
+
needsHumanDirection: true,
|
|
48
|
+
});
|
|
49
|
+
expect(classifyRunFailure('Timeout 30000ms exceeded for locator')).toMatchObject({
|
|
50
|
+
failureType: 'locator_or_wait_error',
|
|
51
|
+
needsHumanDirection: true,
|
|
52
|
+
});
|
|
53
|
+
expect(classifyRunFailure('403 unauthorized permission denied')).toMatchObject({
|
|
54
|
+
failureType: 'permission_blocked',
|
|
55
|
+
needsHumanDirection: true,
|
|
56
|
+
});
|
|
57
|
+
expect(classifyRunFailure('unexpected browser crash')).toMatchObject({
|
|
58
|
+
failureType: 'unknown_failure',
|
|
59
|
+
needsHumanDirection: true,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it('writes repair report into the next repair round directory', () => {
|
|
63
|
+
const caseDir = mkdtempSync(path.join(os.tmpdir(), 'ai-repair-'));
|
|
64
|
+
expect(nextRepairRound(caseDir)).toBe(1);
|
|
65
|
+
const reportPath = writeRepairReport({
|
|
66
|
+
caseDir,
|
|
67
|
+
round: nextRepairRound(caseDir),
|
|
68
|
+
failureType: 'locator_or_wait_error',
|
|
69
|
+
summary: '疑似定位或等待时机错误',
|
|
70
|
+
output: 'Timeout 30000ms exceeded for locator(".submit")',
|
|
71
|
+
});
|
|
72
|
+
expect(reportPath).toBe(path.join(caseDir, '.ep-stage', 'repair-runs', '001', 'repair-report.md'));
|
|
73
|
+
expect(existsSync(reportPath)).toBe(true);
|
|
74
|
+
expect(readFileSync(reportPath, 'utf8')).toContain('失败用例');
|
|
75
|
+
expect(readFileSync(reportPath, 'utf8')).toContain('需要你确认');
|
|
76
|
+
expect(nextRepairRound(caseDir)).toBe(2);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-inference.test.d.ts","sourceRoot":"","sources":["../../test/scenario-inference.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { applyScenarioHumanCorrection, inferPageScenarios, } from '../src/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* 场景推理门禁测试。
|
|
5
|
+
*
|
|
6
|
+
* 验证 inferPageScenarios 能从页面结构候选推出嵌套 CRUD 候选并停在 needs_review gate,
|
|
7
|
+
* 且 applyScenarioHumanCorrection 能记录人工纠偏并要求重跑。
|
|
8
|
+
*/
|
|
9
|
+
describe('scenario inference gate', () => {
|
|
10
|
+
it('infers nested CRUD candidate and requires human confirmation', () => {
|
|
11
|
+
const pages = [
|
|
12
|
+
{
|
|
13
|
+
pageId: 'gxhzwplacelist',
|
|
14
|
+
readSearchCandidate: true,
|
|
15
|
+
createCandidate: true,
|
|
16
|
+
deleteCandidate: true,
|
|
17
|
+
updateOrDetailCandidate: true,
|
|
18
|
+
nestedCrudCandidate: false,
|
|
19
|
+
evidenceLabels: ['可见 mini-datagrid 结构存在', '可见 mini-grid-pager 结构存在'],
|
|
20
|
+
risks: [],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
pageId: 'gxhzwplaceadd',
|
|
24
|
+
readSearchCandidate: true,
|
|
25
|
+
createCandidate: true,
|
|
26
|
+
deleteCandidate: true,
|
|
27
|
+
updateOrDetailCandidate: false,
|
|
28
|
+
nestedCrudCandidate: true,
|
|
29
|
+
evidenceLabels: ['弹窗页面存在 mini-datagrid', '按钮 添加窗口', '按钮 删除选中'],
|
|
30
|
+
risks: ['嵌套 CRUD 断言和保存边界需要人工确认'],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
const result = inferPageScenarios({
|
|
34
|
+
moduleId: 'zwplace',
|
|
35
|
+
pages,
|
|
36
|
+
skeletonCapabilities: ['crud.create', 'crud.read', 'crud.update', 'crud.delete', 'export.run'],
|
|
37
|
+
});
|
|
38
|
+
expect(result.candidates[0]).toMatchObject({
|
|
39
|
+
scenario: 'crud.nested',
|
|
40
|
+
confidence: 'medium',
|
|
41
|
+
matchedSkeletonCapabilities: ['crud.create', 'crud.read', 'crud.delete'],
|
|
42
|
+
unresolved: ['嵌套 CRUD 断言和保存边界需要人工确认'],
|
|
43
|
+
});
|
|
44
|
+
expect(result.gate.status).toBe('needs_review');
|
|
45
|
+
expect(result.reasoningSummary.needsHumanReview).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
it('records human correction and requires rerun', () => {
|
|
48
|
+
const corrected = applyScenarioHumanCorrection({
|
|
49
|
+
inference: {
|
|
50
|
+
moduleId: 'zwplace',
|
|
51
|
+
candidates: [],
|
|
52
|
+
gate: { status: 'needs_review', reason: '未识别到场景' },
|
|
53
|
+
reasoningSummary: {
|
|
54
|
+
conclusion: '未识别到稳定场景。',
|
|
55
|
+
evidenceChain: [],
|
|
56
|
+
alternatives: ['unknown'],
|
|
57
|
+
confidence: 'low',
|
|
58
|
+
risks: ['证据不足'],
|
|
59
|
+
needsHumanReview: true,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
correction: '弹窗 iframe 中存在窗口明细嵌套 CRUD,重点检查添加窗口和删除选中。',
|
|
63
|
+
});
|
|
64
|
+
expect(corrected.conversationReview).toMatchObject({
|
|
65
|
+
status: 'corrected',
|
|
66
|
+
rerunRequired: true,
|
|
67
|
+
});
|
|
68
|
+
expect(corrected.reasoningSummary.evidenceChain[0]).toMatchObject({
|
|
69
|
+
source: 'human',
|
|
70
|
+
text: '弹窗 iframe 中存在窗口明细嵌套 CRUD,重点检查添加窗口和删除选中。',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-context.test.d.ts","sourceRoot":"","sources":["../../test/stage-context.test.ts"],"names":[],"mappings":""}
|