@sun-asterisk/sungen 3.1.1 → 3.1.2-beta.100
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/README.md +4 -428
- package/dist/capabilities/builtins.d.ts +31 -0
- package/dist/capabilities/builtins.d.ts.map +1 -0
- package/dist/capabilities/builtins.js +84 -0
- package/dist/capabilities/builtins.js.map +1 -0
- package/dist/capabilities/context-router.d.ts +34 -0
- package/dist/capabilities/context-router.d.ts.map +1 -0
- package/dist/capabilities/context-router.js +49 -0
- package/dist/capabilities/context-router.js.map +1 -0
- package/dist/capabilities/context.d.ts +51 -0
- package/dist/capabilities/context.d.ts.map +1 -0
- package/dist/capabilities/context.js +17 -0
- package/dist/capabilities/context.js.map +1 -0
- package/dist/capabilities/discover.d.ts +2 -0
- package/dist/capabilities/discover.d.ts.map +1 -0
- package/dist/capabilities/discover.js +48 -0
- package/dist/capabilities/discover.js.map +1 -0
- package/dist/capabilities/registry.d.ts +90 -0
- package/dist/capabilities/registry.d.ts.map +1 -0
- package/dist/capabilities/registry.js +43 -0
- package/dist/capabilities/registry.js.map +1 -0
- package/dist/capabilities/sensor.d.ts +49 -0
- package/dist/capabilities/sensor.d.ts.map +1 -0
- package/dist/capabilities/sensor.js +3 -0
- package/dist/capabilities/sensor.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +7 -3
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +10 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/spec-parser.d.ts.map +1 -1
- package/dist/exporters/spec-parser.js +4 -1
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/code-generator.d.ts +18 -9
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +76 -119
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +0 -10
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +10 -47
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +1 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +1 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/harness/annotation-overrides.d.ts +9 -0
- package/dist/harness/annotation-overrides.d.ts.map +1 -0
- package/dist/harness/annotation-overrides.js +36 -0
- package/dist/harness/annotation-overrides.js.map +1 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +35 -7
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +35 -12
- package/dist/harness/parse.d.ts +1 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +13 -4
- package/dist/harness/parse.js.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +3 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +3 -0
- package/dist/orchestrator/templates/specs-api.d.ts +19 -0
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-api.js +128 -0
- package/dist/orchestrator/templates/specs-api.js.map +1 -0
- package/dist/orchestrator/templates/specs-api.ts +101 -0
- package/package.json +7 -30
- package/src/capabilities/builtins.ts +85 -0
- package/src/capabilities/context-router.ts +66 -0
- package/src/capabilities/context.ts +46 -0
- package/src/capabilities/discover.ts +42 -0
- package/src/capabilities/registry.ts +111 -0
- package/src/capabilities/sensor.ts +47 -0
- package/src/cli/commands/generate.ts +7 -3
- package/src/cli/index.ts +10 -1
- package/src/exporters/spec-parser.ts +4 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/code-generator.ts +71 -118
- package/src/generators/test-generator/patterns/index.ts +9 -35
- package/src/generators/test-generator/template-engine.ts +2 -2
- package/src/harness/annotation-overrides.ts +25 -0
- package/src/harness/audit.ts +37 -8
- package/src/harness/catalog/drivers.yaml +35 -12
- package/src/harness/parse.ts +7 -2
- package/src/index.ts +30 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +3 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +3 -0
- package/src/orchestrator/templates/specs-api.ts +101 -0
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +0 -626
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts +0 -21
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/capture-patterns.js +0 -87
- package/dist/generators/test-generator/patterns/capture-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/database-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/database-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/database-patterns.js +0 -95
- package/dist/generators/test-generator/patterns/database-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/form-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/form-patterns.js +0 -160
- package/dist/generators/test-generator/patterns/form-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -433
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/keyboard-patterns.js +0 -47
- package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.js +0 -125
- package/dist/generators/test-generator/patterns/navigation-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/scope-patterns.js +0 -36
- package/dist/generators/test-generator/patterns/scope-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/scroll-patterns.js +0 -25
- package/dist/generators/test-generator/patterns/scroll-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/setup-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/setup-patterns.js +0 -72
- package/dist/generators/test-generator/patterns/setup-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/table-patterns.d.ts +0 -19
- package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/table-patterns.js +0 -239
- package/dist/generators/test-generator/patterns/table-patterns.js.map +0 -1
- package/docs/orchestration-spec.md +0 -267
- package/src/generators/test-generator/patterns/assertion-patterns.ts +0 -691
- package/src/generators/test-generator/patterns/capture-patterns.ts +0 -97
- package/src/generators/test-generator/patterns/database-patterns.ts +0 -96
- package/src/generators/test-generator/patterns/form-patterns.ts +0 -167
- package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -465
- package/src/generators/test-generator/patterns/keyboard-patterns.ts +0 -51
- package/src/generators/test-generator/patterns/navigation-patterns.ts +0 -140
- package/src/generators/test-generator/patterns/scope-patterns.ts +0 -40
- package/src/generators/test-generator/patterns/scroll-patterns.ts +0 -27
- package/src/generators/test-generator/patterns/setup-patterns.ts +0 -76
- package/src/generators/test-generator/patterns/table-patterns.ts +0 -279
package/src/harness/audit.ts
CHANGED
|
@@ -11,7 +11,7 @@ import * as fs from 'fs';
|
|
|
11
11
|
import { loadScenarios, parseViewpointOverview, ScenarioInfo, ViewpointEntry } from './parse';
|
|
12
12
|
import {
|
|
13
13
|
loadCatalog, viewpointGate, assertionDepth, dataThemesFor, coverageBalance, duplicateClusters, traceability, claimProof, taxonomyLint,
|
|
14
|
-
GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult,
|
|
14
|
+
GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult, Catalog,
|
|
15
15
|
} from './sensors';
|
|
16
16
|
import { readIntent, projectRootFromScreenDir, IntentProfile } from './intent';
|
|
17
17
|
import { getProvenance, Provenance } from './provenance';
|
|
@@ -19,6 +19,9 @@ import { specCoverage, SpecCoverageResult, parseSpecClauses } from './spec-cover
|
|
|
19
19
|
import { downstreamScope, manualOracle, readText, DownstreamResult, ManualOracleResult,
|
|
20
20
|
negativeSideEffect, sourceBacked, crossArtifactOwnership } from './quality-gates';
|
|
21
21
|
import { viewpointLedger, parseViewpointItems, LedgerResult } from './viewpoint-ledger';
|
|
22
|
+
import { capabilityRegistry } from '../capabilities/registry';
|
|
23
|
+
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
24
|
+
import { contextRouter } from '../capabilities/context-router';
|
|
22
25
|
|
|
23
26
|
export interface AuditReport {
|
|
24
27
|
screen: string;
|
|
@@ -63,13 +66,23 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
63
66
|
|
|
64
67
|
const scenarios: ScenarioInfo[] = loadScenarios(featurePath);
|
|
65
68
|
const viewpoints: ViewpointEntry[] = parseViewpointOverview(viewpointPath);
|
|
66
|
-
|
|
69
|
+
// The viewpoint catalog is owned by the default (UI) capability via the SPI; falls back to the
|
|
70
|
+
// in-core loader if no capability provides one. Same catalog content → identical scores (R2).
|
|
71
|
+
discoverAndRegisterCapabilities();
|
|
72
|
+
const defaultCap = capabilityRegistry.defaultCapabilityId();
|
|
73
|
+
const catalog = ((defaultCap && capabilityRegistry.get(defaultCap)?.viewpoints?.()) as Catalog | undefined) || loadCatalog();
|
|
67
74
|
const spec = specCoverage(specPath, scenarios, featureText);
|
|
68
75
|
|
|
69
|
-
const gate = viewpointGate(scenarios, viewpoints, catalog);
|
|
70
76
|
// P3 — intent profile from qa/context.md drives the depth threshold (focus).
|
|
71
77
|
const intent = readIntent(projectRootFromScreenDir(screenDir));
|
|
72
|
-
|
|
78
|
+
// The viewpoint coverage gate + assertion depth are owned by the default (UI) capability and
|
|
79
|
+
// obtained via its `gateProvider` (R2.2b). Same functions underneath → byte-identical gate/depth
|
|
80
|
+
// → identical score. Falls back to the in-core functions if no capability provides them.
|
|
81
|
+
const uiGate = (defaultCap && capabilityRegistry.get(defaultCap)?.gateProvider) as
|
|
82
|
+
((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus }) => { gate: GateResult; depth: DepthResult }) | undefined;
|
|
83
|
+
const provided = uiGate?.({ scenarios, viewpoints, catalog, focus: intent.focus });
|
|
84
|
+
const gate = provided?.gate ?? viewpointGate(scenarios, viewpoints, catalog);
|
|
85
|
+
const depth = provided?.depth ?? assertionDepth(scenarios, dataThemesFor(catalog, gate.pageType), intent.focus);
|
|
73
86
|
const claim = claimProof(scenarios, intent.focus);
|
|
74
87
|
const taxonomy = taxonomyLint(scenarios);
|
|
75
88
|
const balance = coverageBalance(scenarios);
|
|
@@ -125,9 +138,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
125
138
|
if (trace.mappedRatio < 0.5) {
|
|
126
139
|
findings.push(`TRACE: ${trace.note}`);
|
|
127
140
|
}
|
|
128
|
-
|
|
129
|
-
findings.push(`UNIVERSAL: missing theme(s): ${gate.universalGaps.join(', ')} (low priority reminder).`);
|
|
130
|
-
}
|
|
141
|
+
// (UNIVERSAL viewpoint-gap finding now emitted by the `ui` gate sensor — see the gate block below.)
|
|
131
142
|
for (const g of spec.triggerGaps) {
|
|
132
143
|
findings.push(`TRIGGER-UNCOVERED: spec validates "${g.constraint}"${g.code ? ` (${g.code})` : ''} on [${g.required.join(', ')}] but scenarios only exercise it on [${g.found.join(', ') || 'none'}] → add a ${g.missing.join(', ')}-trigger scenario for this constraint (don't collapse the trigger × input matrix).`);
|
|
133
144
|
}
|
|
@@ -157,6 +168,24 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
157
168
|
findings.push(`UNSOURCEABLE-SCENARIO: "${u}" doesn't trace to any FR / viewpoint item — link it to a source, or tag it @exploration (not part of the official suite).`);
|
|
158
169
|
}
|
|
159
170
|
|
|
171
|
+
// Capability gate sensors (Capability SPI): the ContextRouter scopes WHICH gate sensors run to
|
|
172
|
+
// the capabilities this feature actually uses — generic ('core') + the default UI + any whose
|
|
173
|
+
// annotation tags appear (e.g. @query). Today core+ui gate sensors are always in scope, so this
|
|
174
|
+
// is behaviour-identical; it bounds the set as capability-specific gate sensors (@api, …) are
|
|
175
|
+
// added. Each runs over the audit context; an 'error' finding fails the gate.
|
|
176
|
+
const featureTags = [
|
|
177
|
+
...(scenarios.some((s) => s.queryRefs && s.queryRefs.length) ? ['@query'] : []),
|
|
178
|
+
...(scenarios.some((s) => s.apiRefs && s.apiRefs.length) ? ['@api'] : []),
|
|
179
|
+
];
|
|
180
|
+
const routedGateIds = contextRouter.route({ target: { kind: 'screen', id: screenName }, artifact: 'feature', tags: featureTags }).gateSensorIds;
|
|
181
|
+
const gateSensorFindings = capabilityRegistry.sensors('gate')
|
|
182
|
+
.filter((s) => routedGateIds.includes(s.id))
|
|
183
|
+
.flatMap((s) => s.run({ screenName, cwd: projectRootFromScreenDir(screenDir), featureText, scenarios, universalGaps: gate.universalGaps }));
|
|
184
|
+
// Each gate sensor's message carries its own code prefix (VERIFICATION-FAIL / UNIVERSAL / …)
|
|
185
|
+
// → push verbatim.
|
|
186
|
+
for (const f of gateSensorFindings) findings.push(f.message);
|
|
187
|
+
const gateSensorError = gateSensorFindings.some((f) => f.severity === 'error');
|
|
188
|
+
|
|
160
189
|
// #8 — multi-axis calibration: a high overall must not hide a weak axis.
|
|
161
190
|
const manualCompleteness = manualOracleResult.manualTotal
|
|
162
191
|
? 1 - manualOracleResult.insufficient.length / manualOracleResult.manualTotal : 1;
|
|
@@ -180,7 +209,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
180
209
|
// Gate spans coverage (viewpoint themes), depth, claim-proof, spec-clause coverage,
|
|
181
210
|
// AND taxonomy-match (scenarios must use the project's viewpoint IDs when defined).
|
|
182
211
|
const gateStatus: 'PASS' | 'FAIL' =
|
|
183
|
-
gate.gaps.length === 0 && depth.verdict !== 'fail' && claim.verdict !== 'fail' && spec.verdict !== 'fail' && !taxonomyMismatch ? 'PASS' : 'FAIL';
|
|
212
|
+
gate.gaps.length === 0 && depth.verdict !== 'fail' && claim.verdict !== 'fail' && spec.verdict !== 'fail' && !taxonomyMismatch && !gateSensorError ? 'PASS' : 'FAIL';
|
|
184
213
|
|
|
185
214
|
return {
|
|
186
215
|
screen: screenName,
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
# Driver Catalog (metadata only — NO driver code is bundled here).
|
|
2
2
|
# Lets Sungen RECOMMEND/RESOLVE a driver that may not be installed yet, and tells
|
|
3
|
-
# `sungen capability add` which package to install. See docs/spec/sungen_phase2a_spec.md
|
|
3
|
+
# `sungen capability add` which package to install. See docs/spec/sungen_phase2a_spec.md
|
|
4
|
+
# and docs/spec/sungen_packaging_spec.md (R5 — the capability SPI + npm packages).
|
|
4
5
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# Two axes:
|
|
7
|
+
# kind: platform → HOW tests run (runtime/codegen adapter). Pick ONE per project.
|
|
8
|
+
# kind: capability → WHAT extra thing is verified, added on top of a platform.
|
|
9
|
+
# Fields:
|
|
10
|
+
# status: shipped → published as an npm package (R5); planned → not built yet.
|
|
11
|
+
# bundled: true → installed automatically (a dependency of @sun-asterisk/sungen),
|
|
12
|
+
# so `capability add` is unnecessary.
|
|
13
|
+
# unblocks: manual-reason codes (M1–M9) this driver resolves (Phase 2b taxonomy).
|
|
14
|
+
#
|
|
15
|
+
# R5 status: the three real capabilities ship as packages — @sungen/driver-ui (web UI,
|
|
16
|
+
# bundled as the default), @sungen/driver-db, @sungen/driver-api. The web *platform*
|
|
17
|
+
# entry below points at @sungen/driver-ui (the UI step vocabulary + viewpoint gate); the
|
|
18
|
+
# Playwright codegen *adapter* itself is still in-core (Phase 2a). Mobile + the remaining
|
|
19
|
+
# capabilities are planned. See the "Platform axis & mobile evolution" section of the
|
|
20
|
+
# packaging spec for the `sungen init --platform <web|mobile>` roadmap.
|
|
8
21
|
|
|
9
22
|
drivers:
|
|
10
23
|
web:
|
|
11
24
|
kind: platform
|
|
12
|
-
package: "@sungen/driver-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
package: "@sungen/driver-ui" # R5: web UI capability (step patterns + viewpoint gate)
|
|
26
|
+
status: shipped
|
|
27
|
+
bundled: true # @sun-asterisk/sungen depends on it → UI works out of the box
|
|
28
|
+
runtime: playwright # codegen adapter still in-core (Phase 2a)
|
|
29
|
+
adapter: web # registry adapter name
|
|
15
30
|
capabilities: ["@ui"]
|
|
16
31
|
mobile:
|
|
17
32
|
kind: platform
|
|
18
33
|
package: "@sungen/driver-mobile"
|
|
34
|
+
status: planned # PoC on the feat/mobile branch (Appium / Flutter)
|
|
19
35
|
runtime: appium
|
|
20
36
|
adapter: mobile
|
|
21
37
|
capabilities: ["@ui"]
|
|
@@ -23,35 +39,42 @@ drivers:
|
|
|
23
39
|
api:
|
|
24
40
|
kind: capability
|
|
25
41
|
package: "@sungen/driver-api"
|
|
42
|
+
status: shipped
|
|
26
43
|
capabilities: ["@api", "@apiAssert", "@hybrid"]
|
|
27
44
|
unblocks: [M2]
|
|
28
|
-
data-factory:
|
|
29
|
-
kind: capability
|
|
30
|
-
package: "@sungen/driver-data-factory"
|
|
31
|
-
capabilities: ["@dataFactory"]
|
|
32
|
-
unblocks: [M1]
|
|
33
45
|
db:
|
|
34
46
|
kind: capability
|
|
35
47
|
package: "@sungen/driver-db"
|
|
48
|
+
status: shipped
|
|
36
49
|
capabilities: ["@dbAssert"]
|
|
37
50
|
unblocks: [M2]
|
|
51
|
+
data-factory:
|
|
52
|
+
kind: capability
|
|
53
|
+
package: "@sungen/driver-data-factory"
|
|
54
|
+
status: planned
|
|
55
|
+
capabilities: ["@dataFactory"]
|
|
56
|
+
unblocks: [M1]
|
|
38
57
|
mock:
|
|
39
58
|
kind: capability
|
|
40
59
|
package: "@sungen/driver-mock"
|
|
60
|
+
status: planned
|
|
41
61
|
capabilities: ["@mock", "@network"]
|
|
42
62
|
unblocks: [M3]
|
|
43
63
|
mail-file:
|
|
44
64
|
kind: capability
|
|
45
65
|
package: "@sungen/driver-mail-file"
|
|
66
|
+
status: planned
|
|
46
67
|
capabilities: ["@mail", "@file"]
|
|
47
68
|
unblocks: [M5]
|
|
48
69
|
contract:
|
|
49
70
|
kind: capability
|
|
50
71
|
package: "@sungen/driver-contract"
|
|
72
|
+
status: planned
|
|
51
73
|
capabilities: ["@contract"]
|
|
52
74
|
unblocks: [M5]
|
|
53
75
|
specialized:
|
|
54
76
|
kind: capability
|
|
55
77
|
package: "@sungen/driver-specialized"
|
|
78
|
+
status: planned
|
|
56
79
|
capabilities: ["@specialized"]
|
|
57
80
|
unblocks: [M6]
|
package/src/harness/parse.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface ScenarioInfo {
|
|
|
32
32
|
vpId?: string; // raw leading ID token of the title (project's scheme: VP0-001, MS-HP-001, VP-LIST-001)
|
|
33
33
|
casesDataset?: string; // @cases:<dataset> — data-driven; one scenario expands to N row-tests
|
|
34
34
|
queryRefs?: string[]; // named queries referenced by this scenario (inline `query [name]` + @query: tags)
|
|
35
|
+
apiRefs?: string[]; // named API endpoints referenced by this scenario (@api: tags)
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/** Format-tolerant: is this token an ID (project's scheme), not a prose word?
|
|
@@ -102,12 +103,15 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
|
|
|
102
103
|
const manual = tags.includes('@manual');
|
|
103
104
|
const casesTag = tags.find((t) => t.startsWith('@cases:'));
|
|
104
105
|
const casesDataset = casesTag ? casesTag.slice('@cases:'.length).trim() : undefined;
|
|
105
|
-
// Named-query references: @query:<name> tags + inline `query [name]` step refs.
|
|
106
|
+
// Named-query references: @query:<name>[(overrides)] tags + inline `query [name]` step refs.
|
|
106
107
|
const queryRefs = new Set<string>();
|
|
107
|
-
for (const t of tags) if (t.startsWith('@query:')) { const
|
|
108
|
+
for (const t of tags) if (t.startsWith('@query:')) { const m = t.slice('@query:'.length).match(/^([A-Za-z_][A-Za-z0-9_]*)/); if (m) queryRefs.add(m[1]); }
|
|
108
109
|
for (const step of (sc.steps as ParsedStep[]) || []) {
|
|
109
110
|
for (const m of (step.text || '').matchAll(/\bquery\s+\[([A-Za-z_][A-Za-z0-9_]*)\]/gi)) queryRefs.add(m[1]);
|
|
110
111
|
}
|
|
112
|
+
// Named-API references: @api:<name>[(overrides)] tags.
|
|
113
|
+
const apiRefs = new Set<string>();
|
|
114
|
+
for (const t of tags) if (t.startsWith('@api:')) { const m = t.slice('@api:'.length).match(/^([A-Za-z_][A-Za-z0-9_]*)/); if (m) apiRefs.add(m[1]); }
|
|
111
115
|
let priority: Priority = 'unknown';
|
|
112
116
|
for (const t of tags) if (PRIORITY_TAGS[t]) priority = PRIORITY_TAGS[t];
|
|
113
117
|
|
|
@@ -164,6 +168,7 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
|
|
|
164
168
|
vpId,
|
|
165
169
|
casesDataset,
|
|
166
170
|
queryRefs: queryRefs.size ? [...queryRefs] : undefined,
|
|
171
|
+
apiRefs: apiRefs.size ? [...apiRefs] : undefined,
|
|
167
172
|
};
|
|
168
173
|
}
|
|
169
174
|
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API of `@sun-asterisk/sungen` — the capability SPI plus the shared compiler/harness surface
|
|
3
|
+
* that capability drivers (`@sungen/driver-*`) build against. Drivers import from here; core never
|
|
4
|
+
* imports from a driver (discovery loads them at runtime). Keep this surface small and intentional.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// --- Capability SPI ---
|
|
8
|
+
export { capabilityRegistry, CapabilityRegistry } from './capabilities/registry';
|
|
9
|
+
export type { CapabilityDescriptor } from './capabilities/registry';
|
|
10
|
+
export type { Sensor, SensorFinding, AdvisoryScanInput, GateInput } from './capabilities/sensor';
|
|
11
|
+
export type { Context, DiscoveryProvider, ContextMapper, GenerationUnit } from './capabilities/context';
|
|
12
|
+
|
|
13
|
+
// --- Step-pattern authoring (a driver contributes step patterns via its descriptor) ---
|
|
14
|
+
export type { PatternContext, StepPattern, StepTemplateData } from './generators/test-generator/patterns/types';
|
|
15
|
+
export type { MappedStep } from './generators/test-generator/step-mapper';
|
|
16
|
+
export type { ParsedStep } from './generators/gherkin-parser';
|
|
17
|
+
export { getPathCode, inferPath, resolvePathVariables } from './generators/test-generator/utils/path-inference';
|
|
18
|
+
|
|
19
|
+
// --- Precondition-annotation override grammar (shared by the @query / @api driver codegen) ---
|
|
20
|
+
export { parseQueryOverrides } from './harness/annotation-overrides';
|
|
21
|
+
|
|
22
|
+
// --- Named-query catalog (shared: the DB driver's codegen + core's data-driven advisory lint) ---
|
|
23
|
+
export { resolveQuery, compileQuery, lintCatalog } from './harness/query-catalog';
|
|
24
|
+
export type { QueryEntry } from './harness/query-catalog';
|
|
25
|
+
|
|
26
|
+
// --- Shared harness: viewpoint catalog + coverage gate / assertion depth ---
|
|
27
|
+
// (the UI capability's gateProvider composes these; they also back core's ingest + audit fallback)
|
|
28
|
+
export { loadCatalog, viewpointGate, assertionDepth, dataThemesFor } from './harness/sensors';
|
|
29
|
+
export type { Catalog, GateResult, DepthResult } from './harness/sensors';
|
|
30
|
+
export type { ScenarioInfo, ViewpointEntry } from './harness/parse';
|
|
@@ -31,9 +31,10 @@ Parse **name** from `$ARGUMENTS`. If missing, ask the user.
|
|
|
31
31
|
**Screen**: Verify `qa/screens/<name>/` exists. If not → `/sungen:add-screen` first.
|
|
32
32
|
2. Check if `.feature` file already has scenarios.
|
|
33
33
|
- If yes → use `AskUserQuestion` to ask the update mode (see `sungen-tc-generation` skill — mode depends on which tiers already exist).
|
|
34
|
-
- If no → fresh creation. Use `AskUserQuestion` to ask generation scope:
|
|
35
|
-
- **Tier 1 — Critical & High priority** — ~10-15 scenarios/section
|
|
36
|
-
- **Full coverage
|
|
34
|
+
- If no → fresh creation. **Write the feature file incrementally** (successive `Write`/`Edit`, ≈10-15 scenarios per call) — never emit the whole suite in one response, or it can exceed the model's output-token cap (`API Error: Claude's response exceeded the N output token maximum`). Use `AskUserQuestion` to ask generation scope:
|
|
35
|
+
- **Tier 1 — Critical & High priority** — ~10-15 scenarios/section: happy paths, core validation, security basics **(Recommended)**
|
|
36
|
+
- **Full coverage (incremental)** — Tier 1 + 2 + 3, written tier-by-tier in batches (`Write` T1 → `Edit` append T2 → `Edit` append T3). Safe on any output-token budget.
|
|
37
|
+
- **Full coverage (single pass)** — generate everything in one go (~40-60 scenarios/section). Faster, but **only if you raised your output cap** (`CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`) — otherwise it errors mid-generation. For power users on a high-token model/config.
|
|
37
38
|
3. **Read project context + screen requirements**
|
|
38
39
|
|
|
39
40
|
**Project context** — check `qa/context.md` (project root, not screen-specific):
|
|
@@ -213,6 +213,7 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
|
|
|
213
213
|
| `@flow` | Mark feature as E2E flow (cross-screen testing) |
|
|
214
214
|
| `@cases:dataset` | Data-driven: run the scenario once per row of the `dataset` LIST in test-data → one `test()` per row |
|
|
215
215
|
| `@query:name` | Database: run the named query from `database/queries.yaml` (precondition) and bind its rows to `{{name}}`; assert with `expect {{name.count}} …` + path access. Override params `@query:name(p={{v}})`. Repeatable. (Optional Data Driver — see Database verification above) |
|
|
216
|
+
| `@api:name` | API: run the named request from `api/apis.yaml` (precondition) and bind the response to `{{name}}`; assert with `expect {{name.status}} …` + path access (`{{name.body.<path>}}`). Override params `@api:name(p={{v}})`. Repeatable. (Optional API Driver) |
|
|
216
217
|
|
|
217
218
|
### Data-driven scenarios (`@cases`)
|
|
218
219
|
|
|
@@ -6,6 +6,9 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
## ⚠️ Gotchas — read before generating
|
|
8
8
|
|
|
9
|
+
- **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
|
|
10
|
+
→ One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
|
|
11
|
+
|
|
9
12
|
- `spec_figma.md` exists → read file only, **NEVER** call `mcp__figma__*`
|
|
10
13
|
→ PAT auth flow already done by `sungen-capture` (mode figma-pat); re-calling fails or duplicates work.
|
|
11
14
|
|
|
@@ -26,9 +26,10 @@ You are a **Senior QA Engineer**. You structure test cases by viewpoint categori
|
|
|
26
26
|
**Screen**: Verify `qa/screens/${input:name}/` exists. If not → `/sungen-add-screen` first.
|
|
27
27
|
2. Check if `.feature` already has scenarios.
|
|
28
28
|
- If yes → summarize existing coverage and ask update mode (options depend on which tiers already exist — see `sungen-tc-generation` skill for details).
|
|
29
|
-
- If no → fresh creation. Ask generation scope:
|
|
30
|
-
- **1) Tier 1 — Critical & High priority** — ~10-15 scenarios/section
|
|
31
|
-
- **2) Full coverage
|
|
29
|
+
- If no → fresh creation. **Write the feature file incrementally** (successive writes/edits, ≈10-15 scenarios per call) — never emit the whole suite in one response, or it can exceed the model's output-token cap (`response exceeded the N output token maximum`). Ask generation scope:
|
|
30
|
+
- **1) Tier 1 — Critical & High priority** — ~10-15 scenarios/section: happy paths, core validation, security basics **(Recommended)**
|
|
31
|
+
- **2) Full coverage (incremental)** — Tier 1 + 2 + 3, written tier-by-tier in batches. Safe on any output-token budget.
|
|
32
|
+
- **3) Full coverage (single pass)** — everything in one go (~40-60 scenarios/section). Faster, but **only if you raised your output cap** (`CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`) — otherwise it errors mid-generation. For power users on a high-token model/config.
|
|
32
33
|
3. **Read project context + screen requirements**
|
|
33
34
|
|
|
34
35
|
**Project context** — check `qa/context.md` (project root, not screen-specific):
|
|
@@ -213,6 +213,7 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
|
|
|
213
213
|
| `@flow` | Mark feature as E2E flow (cross-screen testing) |
|
|
214
214
|
| `@cases:dataset` | Data-driven: run the scenario once per row of the `dataset` LIST in test-data → one `test()` per row |
|
|
215
215
|
| `@query:name` | Database: run the named query from `database/queries.yaml` (precondition) and bind its rows to `{{name}}`; assert with `expect {{name.count}} …` + path access. Override params `@query:name(p={{v}})`. Repeatable. (Optional Data Driver — see Database verification above) |
|
|
216
|
+
| `@api:name` | API: run the named request from `api/apis.yaml` (precondition) and bind the response to `{{name}}`; assert with `expect {{name.status}} …` + path access (`{{name.body.<path>}}`). Override params `@api:name(p={{v}})`. Repeatable. (Optional API Driver) |
|
|
216
217
|
|
|
217
218
|
### Data-driven scenarios (`@cases`)
|
|
218
219
|
|
|
@@ -6,6 +6,9 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
## ⚠️ Gotchas — read before generating
|
|
8
8
|
|
|
9
|
+
- **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
|
|
10
|
+
→ One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
|
|
11
|
+
|
|
9
12
|
- `spec_figma.md` exists → read file only, **NEVER** call `mcp__figma__*`
|
|
10
13
|
→ PAT auth flow already done by `sungen-capture` (mode figma-pat); re-calling fails or duplicates work.
|
|
11
14
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Sungen API Driver — runtime helper (auto-generated into specs/api.ts).
|
|
4
|
+
*
|
|
5
|
+
* Runs a catalog-defined HTTP request and returns { status, ok, body, headers } — bound to a
|
|
6
|
+
* `{{name}}` variable by the `@api:<name>` annotation, asserted with `expect {{name.status}} …` /
|
|
7
|
+
* `{{name.body.<path>}}`. Base URL + auth come from a `kind: api` datasource in datasources.yaml,
|
|
8
|
+
* with `${VAR}` resolved from .env.qa / process.env — never inline.
|
|
9
|
+
*
|
|
10
|
+
* Safety: a datasource flagged `env: production` is refused unless SUNGEN_ALLOW_PROD=1.
|
|
11
|
+
* DO NOT EDIT — regenerated by `sungen generate`.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
|
|
16
|
+
interface ApiDataSource {
|
|
17
|
+
kind?: string;
|
|
18
|
+
base_url?: string;
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
env?: string;
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
timeout_ms?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function loadEnvQa(): void {
|
|
26
|
+
for (const name of ['.env.qa', `.env.qa.${process.env.SUNGEN_ENV || ''}`]) {
|
|
27
|
+
const p = path.join(process.cwd(), name);
|
|
28
|
+
if (!name.endsWith('.') && fs.existsSync(p)) {
|
|
29
|
+
for (const line of fs.readFileSync(p, 'utf8').split('\n')) {
|
|
30
|
+
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
|
31
|
+
if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function loadConfig(): Record<string, ApiDataSource> {
|
|
38
|
+
loadEnvQa();
|
|
39
|
+
const file = [path.join(process.cwd(), 'datasources.yaml'), path.join(process.cwd(), 'qa', 'datasources.yaml')].find((f) => fs.existsSync(f));
|
|
40
|
+
if (!file) throw new Error('API Driver: no datasources.yaml found (project root or qa/).');
|
|
41
|
+
const raw = fs.readFileSync(file, 'utf8').replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, k) => process.env[k] ?? '');
|
|
42
|
+
const { parse } = require('yaml');
|
|
43
|
+
const doc = parse(raw) || {};
|
|
44
|
+
return doc.datasources || {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function substitute(text: string, params: Record<string, any>): string {
|
|
48
|
+
return text.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => encodeURIComponent(String(params[p] ?? '')));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class ApiClient {
|
|
52
|
+
private configs: Record<string, ApiDataSource> | null = null;
|
|
53
|
+
|
|
54
|
+
private cfg(name?: string): { key: string; conf: ApiDataSource } {
|
|
55
|
+
if (!this.configs) this.configs = loadConfig();
|
|
56
|
+
const key = name || Object.keys(this.configs).find((k) => (this.configs![k].kind || 'api') === 'api') || Object.keys(this.configs)[0];
|
|
57
|
+
const conf = this.configs[key];
|
|
58
|
+
if (!conf) throw new Error(`API Driver: datasource "${key}" not found in datasources.yaml`);
|
|
59
|
+
if (conf.env === 'production' && process.env.SUNGEN_ALLOW_PROD !== '1') {
|
|
60
|
+
throw new Error(`API Driver: datasource "${key}" is env: production — refused (set SUNGEN_ALLOW_PROD=1 to override).`);
|
|
61
|
+
}
|
|
62
|
+
return { key, conf };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Run a catalog request and return the response. `req` is embedded at compile time; `params` bind at runtime. */
|
|
66
|
+
async call(
|
|
67
|
+
label: string,
|
|
68
|
+
req: { method: string; path: string; body?: unknown; datasource?: string },
|
|
69
|
+
params: Record<string, any> = {},
|
|
70
|
+
): Promise<{ status: number; ok: boolean; body: any; headers: Record<string, string> }> {
|
|
71
|
+
const { conf } = this.cfg(req.datasource);
|
|
72
|
+
const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
|
|
73
|
+
if (!base) throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
|
|
74
|
+
const url = base + substitute(req.path, params);
|
|
75
|
+
|
|
76
|
+
let body: string | undefined;
|
|
77
|
+
const headers: Record<string, string> = { ...(conf.headers || {}) };
|
|
78
|
+
if (req.body !== undefined && req.body !== null) {
|
|
79
|
+
const resolved = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
|
|
80
|
+
body = JSON.stringify(resolved);
|
|
81
|
+
if (!headers['content-type'] && !headers['Content-Type']) headers['content-type'] = 'application/json';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timer = setTimeout(() => controller.abort(), conf.timeout_ms ?? 15000);
|
|
86
|
+
let res: Response;
|
|
87
|
+
try {
|
|
88
|
+
res = await fetch(url, { method: req.method, headers, body, signal: controller.signal });
|
|
89
|
+
} finally {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
}
|
|
92
|
+
const text = await res.text();
|
|
93
|
+
let parsed: any = text;
|
|
94
|
+
try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
|
|
95
|
+
const outHeaders: Record<string, string> = {};
|
|
96
|
+
res.headers.forEach((v, k) => { outHeaders[k] = v; });
|
|
97
|
+
return { status: res.status, ok: res.ok, body: parsed, headers: outHeaders };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const api = new ApiClient();
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { StepPattern } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Assertion patterns: visibility, text content, state, attributes
|
|
4
|
-
* Uses template engine for framework-agnostic code generation
|
|
5
|
-
*/
|
|
6
|
-
export declare const assertionPatterns: StepPattern[];
|
|
7
|
-
//# sourceMappingURL=assertion-patterns.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"assertion-patterns.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/patterns/assertion-patterns.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAoB,MAAM,SAAS,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,EA2qB1C,CAAC"}
|