@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.
Files changed (151) hide show
  1. package/SKILL.md +27 -0
  2. package/codex-skill/ep-stage/create-project/SKILL.md +59 -0
  3. package/codex-skill/ep-stage/glue-test/SKILL.md +258 -0
  4. package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +139 -0
  5. package/codex-skill/ep-stage/glue-test/references/gap-review-protocol.md +43 -0
  6. package/codex-skill/ep-stage/glue-test/references/harness-principles.md +46 -0
  7. package/codex-skill/ep-stage/glue-test/scripts/generate-crud-spec.mjs +149 -0
  8. package/codex-skill/ep-stage/glue-testcase/SKILL.md +31 -0
  9. package/codex-skill/ep-stage/glue-testcase/examples/observable-testcase.json +40 -0
  10. package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +67 -0
  11. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +27 -0
  12. package/codex-skill/ep-stage/scripts/validate-skill.mjs +73 -0
  13. package/dist/src/capability/coverage-diff.d.ts +34 -0
  14. package/dist/src/capability/coverage-diff.d.ts.map +1 -0
  15. package/dist/src/capability/coverage-diff.js +91 -0
  16. package/dist/src/capability/page-structure.d.ts +31 -0
  17. package/dist/src/capability/page-structure.d.ts.map +1 -0
  18. package/dist/src/capability/page-structure.js +50 -0
  19. package/dist/src/capability/scenario-inference.d.ts +36 -0
  20. package/dist/src/capability/scenario-inference.d.ts.map +1 -0
  21. package/dist/src/capability/scenario-inference.js +114 -0
  22. package/dist/src/cli/generate-crud-contract.d.ts +2 -0
  23. package/dist/src/cli/generate-crud-contract.d.ts.map +1 -0
  24. package/dist/src/cli/generate-crud-contract.js +77 -0
  25. package/dist/src/cli/generate-playwright-tests.d.ts +30 -0
  26. package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -0
  27. package/dist/src/cli/generate-playwright-tests.js +81 -0
  28. package/dist/src/cli/run-gap-pipeline.d.ts +256 -0
  29. package/dist/src/cli/run-gap-pipeline.d.ts.map +1 -0
  30. package/dist/src/cli/run-gap-pipeline.js +1468 -0
  31. package/dist/src/context/stage-context.d.ts +63 -0
  32. package/dist/src/context/stage-context.d.ts.map +1 -0
  33. package/dist/src/context/stage-context.js +297 -0
  34. package/dist/src/contracts/crud-business-module.d.ts +645 -0
  35. package/dist/src/contracts/crud-business-module.d.ts.map +1 -0
  36. package/dist/src/contracts/crud-business-module.js +1 -0
  37. package/dist/src/contracts/gap-inference.d.ts +213 -0
  38. package/dist/src/contracts/gap-inference.d.ts.map +1 -0
  39. package/dist/src/contracts/gap-inference.js +11 -0
  40. package/dist/src/contracts/observable-chain.d.ts +250 -0
  41. package/dist/src/contracts/observable-chain.d.ts.map +1 -0
  42. package/dist/src/contracts/observable-chain.js +1 -0
  43. package/dist/src/extractors/code-list.d.ts +40 -0
  44. package/dist/src/extractors/code-list.d.ts.map +1 -0
  45. package/dist/src/extractors/code-list.js +225 -0
  46. package/dist/src/extractors/html-page.d.ts +67 -0
  47. package/dist/src/extractors/html-page.d.ts.map +1 -0
  48. package/dist/src/extractors/html-page.js +195 -0
  49. package/dist/src/extractors/java-action.d.ts +8 -0
  50. package/dist/src/extractors/java-action.d.ts.map +1 -0
  51. package/dist/src/extractors/java-action.js +53 -0
  52. package/dist/src/extractors/spec-yaml.d.ts +28 -0
  53. package/dist/src/extractors/spec-yaml.d.ts.map +1 -0
  54. package/dist/src/extractors/spec-yaml.js +29 -0
  55. package/dist/src/gap-planner/action-candidates.d.ts +9 -0
  56. package/dist/src/gap-planner/action-candidates.d.ts.map +1 -0
  57. package/dist/src/gap-planner/action-candidates.js +66 -0
  58. package/dist/src/gap-planner/list-gap-workflows.d.ts +17 -0
  59. package/dist/src/gap-planner/list-gap-workflows.d.ts.map +1 -0
  60. package/dist/src/gap-planner/list-gap-workflows.js +47 -0
  61. package/dist/src/gap-planner/plan-agent-workflows.d.ts +26 -0
  62. package/dist/src/gap-planner/plan-agent-workflows.d.ts.map +1 -0
  63. package/dist/src/gap-planner/plan-agent-workflows.js +116 -0
  64. package/dist/src/gap-planner/skeleton-coverage.d.ts +9 -0
  65. package/dist/src/gap-planner/skeleton-coverage.d.ts.map +1 -0
  66. package/dist/src/gap-planner/skeleton-coverage.js +41 -0
  67. package/dist/src/gap-planner/stable-id.d.ts +16 -0
  68. package/dist/src/gap-planner/stable-id.d.ts.map +1 -0
  69. package/dist/src/gap-planner/stable-id.js +19 -0
  70. package/dist/src/generalization/generalization-eval.d.ts +71 -0
  71. package/dist/src/generalization/generalization-eval.d.ts.map +1 -0
  72. package/dist/src/generalization/generalization-eval.js +53 -0
  73. package/dist/src/generators/agent-inferred-workflow-script.d.ts +22 -0
  74. package/dist/src/generators/agent-inferred-workflow-script.d.ts.map +1 -0
  75. package/dist/src/generators/agent-inferred-workflow-script.js +230 -0
  76. package/dist/src/generators/stage-skeleton-script.d.ts +107 -0
  77. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -0
  78. package/dist/src/generators/stage-skeleton-script.js +607 -0
  79. package/dist/src/index.d.ts +52 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +26 -0
  82. package/dist/src/material/material-inventory.d.ts +92 -0
  83. package/dist/src/material/material-inventory.d.ts.map +1 -0
  84. package/dist/src/material/material-inventory.js +191 -0
  85. package/dist/src/normalizers/crud-contract.d.ts +107 -0
  86. package/dist/src/normalizers/crud-contract.d.ts.map +1 -0
  87. package/dist/src/normalizers/crud-contract.js +1068 -0
  88. package/dist/src/testcase/testcase-generator.d.ts +43 -0
  89. package/dist/src/testcase/testcase-generator.d.ts.map +1 -0
  90. package/dist/src/testcase/testcase-generator.js +152 -0
  91. package/dist/src/testcase/testcase-skeleton.d.ts +91 -0
  92. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -0
  93. package/dist/src/testcase/testcase-skeleton.js +121 -0
  94. package/dist/src/testcase/testcase-spec-assembly.d.ts +11 -0
  95. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -0
  96. package/dist/src/testcase/testcase-spec-assembly.js +24 -0
  97. package/dist/src/trace/review-summary.d.ts +17 -0
  98. package/dist/src/trace/review-summary.d.ts.map +1 -0
  99. package/dist/src/trace/review-summary.js +34 -0
  100. package/dist/src/trace/trace-writer.d.ts +17 -0
  101. package/dist/src/trace/trace-writer.d.ts.map +1 -0
  102. package/dist/src/trace/trace-writer.js +81 -0
  103. package/dist/test/crud-contract.test.d.ts +2 -0
  104. package/dist/test/crud-contract.test.d.ts.map +1 -0
  105. package/dist/test/crud-contract.test.js +819 -0
  106. package/dist/test/gap-inference.test.d.ts +2 -0
  107. package/dist/test/gap-inference.test.d.ts.map +1 -0
  108. package/dist/test/gap-inference.test.js +597 -0
  109. package/dist/test/generalization.test.d.ts +2 -0
  110. package/dist/test/generalization.test.d.ts.map +1 -0
  111. package/dist/test/generalization.test.js +73 -0
  112. package/dist/test/material-inventory.test.d.ts +2 -0
  113. package/dist/test/material-inventory.test.d.ts.map +1 -0
  114. package/dist/test/material-inventory.test.js +141 -0
  115. package/dist/test/observable-chain.test.d.ts +2 -0
  116. package/dist/test/observable-chain.test.d.ts.map +1 -0
  117. package/dist/test/observable-chain.test.js +123 -0
  118. package/dist/test/observable-pipeline.test.d.ts +2 -0
  119. package/dist/test/observable-pipeline.test.d.ts.map +1 -0
  120. package/dist/test/observable-pipeline.test.js +461 -0
  121. package/dist/test/page-structure.test.d.ts +2 -0
  122. package/dist/test/page-structure.test.d.ts.map +1 -0
  123. package/dist/test/page-structure.test.js +45 -0
  124. package/dist/test/scenario-inference.test.d.ts +2 -0
  125. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  126. package/dist/test/scenario-inference.test.js +73 -0
  127. package/dist/test/stage-context.test.d.ts +2 -0
  128. package/dist/test/stage-context.test.d.ts.map +1 -0
  129. package/dist/test/stage-context.test.js +263 -0
  130. package/dist/test/testcase-generator.test.d.ts +2 -0
  131. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  132. package/dist/test/testcase-generator.test.js +276 -0
  133. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  134. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  135. package/dist/test/testcase-skeleton.test.js +185 -0
  136. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  137. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  138. package/dist/test/testcase-spec-assembly.test.js +105 -0
  139. package/dist/vitest.config.d.ts +3 -0
  140. package/dist/vitest.config.d.ts.map +1 -0
  141. package/dist/vitest.config.js +7 -0
  142. package/docs/README.md +134 -0
  143. package/docs/mvp-usage-guide.md +298 -0
  144. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -0
  145. package/examples/schemeresource.module-hints.json +38 -0
  146. package/examples/schemeresource.observable.code_list.md +37 -0
  147. package/examples/zwplace-observable-docs/zwplace.context.md +16 -0
  148. package/examples/zwplace-placecategory-validation.json +29 -0
  149. package/examples/zwplace.module-hints.json +69 -0
  150. package/examples/zwplace.observable.code_list.md +37 -0
  151. package/package.json +38 -0
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=material-inventory.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material-inventory.test.d.ts","sourceRoot":"","sources":["../../test/material-inventory.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,141 @@
1
+ import { 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
+ 'menu: 场所窗口信息管理>场所窗口信息列表',
25
+ '`_docs/003-场所窗口信息管理-20260518/spec.yaml`',
26
+ '`src/main/webapp/perpage/zwplace/gxhzwplacelist.html`',
27
+ '`src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`',
28
+ '`_docs/zwplace.module-hints.json`',
29
+ ].join('\n'), 'utf8');
30
+ const codeList = extractCodeListSummary(codeListPath);
31
+ const result = resolveMaterialInventory({
32
+ codeListPath,
33
+ codeList,
34
+ debugOverrides: {},
35
+ stageContext: {
36
+ projectName: 'glue-project',
37
+ projectDir: root,
38
+ mode: 'glue',
39
+ envPath: '.env',
40
+ envFilePath: path.join(root, '.env'),
41
+ knowledgeRoot: root,
42
+ codeListPaths: [codeListPath],
43
+ stageContextPath: path.join(root, 'stage-context.md'),
44
+ },
45
+ });
46
+ expect(result.resolvedInputs).toMatchObject({
47
+ moduleId: { status: 'resolved', value: 'zwplace' },
48
+ docsDir: { status: 'resolved', path: path.dirname(specPath) },
49
+ webappDir: { status: 'resolved', path: path.dirname(htmlPath) },
50
+ javaActionsDir: { status: 'resolved', path: path.dirname(javaPath) },
51
+ hintsPath: { status: 'resolved', path: hintsPath },
52
+ menu: { status: 'resolved', value: '场所窗口信息管理>场所窗口信息列表' },
53
+ });
54
+ expect(result.gate.status).toBe('confirmed');
55
+ });
56
+ it('stops at material gate when webapp or java actions cannot be resolved', () => {
57
+ const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-missing-'));
58
+ const codeListPath = path.join(root, 'code_list.md');
59
+ writeFileSync(codeListPath, '# 空代码清单\n');
60
+ const result = resolveMaterialInventory({
61
+ codeListPath,
62
+ codeList: extractCodeListSummary(codeListPath),
63
+ debugOverrides: {},
64
+ });
65
+ expect(result.gate).toMatchObject({
66
+ status: 'needs_review',
67
+ });
68
+ expect(result.resolvedInputs.webappDir.status).toBe('missing');
69
+ expect(result.resolvedInputs.javaActionsDir.status).toBe('missing');
70
+ });
71
+ it('supports a writable external code_list that already contains write-back fields', () => {
72
+ const root = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-material-writeback-'));
73
+ const knowledgeRoot = path.join(root, 'knowledge-project', 'zwplace');
74
+ const writableDir = path.join(root, 'agent-input');
75
+ const docsPath = path.join(writableDir, 'zwplace-observable-docs', 'zwplace.context.md');
76
+ const hintsPath = path.join(writableDir, 'zwplace.module-hints.json');
77
+ const htmlPath = path.join(knowledgeRoot, 'src', 'main', 'webapp', 'perpage', 'zwplace', 'gxhzwplacelist.html');
78
+ const javaPath = path.join(knowledgeRoot, 'src', 'main', 'java', 'com', 'epoint', 'zwplace', 'action', 'GxhZwPlaceListAction.java');
79
+ const codeListPath = path.join(writableDir, 'zwplace.observable.code_list.md');
80
+ mkdirSync(path.dirname(docsPath), { recursive: true });
81
+ mkdirSync(path.dirname(htmlPath), { recursive: true });
82
+ mkdirSync(path.dirname(javaPath), { recursive: true });
83
+ writeFileSync(docsPath, '# zwplace context\n');
84
+ writeFileSync(hintsPath, '{}');
85
+ writeFileSync(htmlPath, '<html></html>');
86
+ writeFileSync(javaPath, 'class GxhZwPlaceListAction {}');
87
+ writeFileSync(codeListPath, [
88
+ '# 场所窗口信息管理代码清单',
89
+ `knowledgeRoot: ${knowledgeRoot}`,
90
+ 'menu: 场所窗口信息管理>场所窗口信息列表',
91
+ '`./zwplace-observable-docs/zwplace.context.md`',
92
+ '`src/main/webapp/perpage/zwplace/gxhzwplacelist.html`',
93
+ '`src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`',
94
+ '`./zwplace.module-hints.json`',
95
+ ].join('\n'), 'utf8');
96
+ const codeList = extractCodeListSummary(codeListPath);
97
+ const result = resolveMaterialInventory({
98
+ codeListPath,
99
+ codeList,
100
+ debugOverrides: {},
101
+ });
102
+ expect(codeList.knowledgeRoot).toBe(knowledgeRoot);
103
+ expect(codeList.knowledgeRootSource).toBe('code_list');
104
+ expect(codeList.menu).toBe('场所窗口信息管理>场所窗口信息列表');
105
+ expect(result.resolvedInputs).toMatchObject({
106
+ docsDir: { status: 'resolved', path: path.dirname(docsPath) },
107
+ webappDir: { status: 'resolved', path: path.dirname(htmlPath) },
108
+ javaActionsDir: { status: 'resolved', path: path.dirname(javaPath) },
109
+ hintsPath: { status: 'resolved', path: hintsPath },
110
+ menu: { status: 'resolved', value: '场所窗口信息管理>场所窗口信息列表' },
111
+ });
112
+ });
113
+ it('resolves the checked-in writable schemeresource code_list example', () => {
114
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
115
+ const examplesDir = path.join(packageRoot, 'examples');
116
+ const codeListPath = path.join(examplesDir, 'schemeresource.observable.code_list.md');
117
+ const knowledgeRoot = path.resolve(examplesDir, '../../../knowledge-project/epoint-web-v9.5.2');
118
+ const docsDir = path.join(examplesDir, 'schemeresource-observable-docs');
119
+ const webappDir = path.join(knowledgeRoot, 'src', 'main', 'webapp', 'perpage', 'schemeresource');
120
+ const javaActionsDir = path.join(knowledgeRoot, 'src', 'main', 'java', 'com', 'epoint', 'schemeresource', 'action');
121
+ const hintsPath = path.join(examplesDir, 'schemeresource.module-hints.json');
122
+ const codeList = extractCodeListSummary(codeListPath);
123
+ const result = resolveMaterialInventory({
124
+ codeListPath,
125
+ codeList,
126
+ debugOverrides: {},
127
+ });
128
+ expect(codeList.knowledgeRoot).toBe(knowledgeRoot);
129
+ expect(codeList.knowledgeRootSource).toBe('code_list');
130
+ expect(codeList.menu).toBe('方案资源文档>方案资源文档列表');
131
+ expect(result.resolvedInputs).toMatchObject({
132
+ moduleId: { status: 'resolved', value: 'schemeresource' },
133
+ docsDir: { status: 'resolved', path: docsDir },
134
+ webappDir: { status: 'resolved', path: webappDir },
135
+ javaActionsDir: { status: 'resolved', path: javaActionsDir },
136
+ hintsPath: { status: 'resolved', path: hintsPath },
137
+ menu: { status: 'resolved', value: '方案资源文档>方案资源文档列表' },
138
+ });
139
+ expect(result.gate.status).toBe('confirmed');
140
+ });
141
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=observable-chain.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=observable-pipeline.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observable-pipeline.test.d.ts","sourceRoot":"","sources":["../../test/observable-pipeline.test.ts"],"names":[],"mappings":""}