@sun-asterisk/sungen 3.1.2-beta.93 → 3.1.2-beta.97
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/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 +11 -9
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +53 -76
- 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-skill-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -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/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 +51 -74
- 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-skill-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -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
|
@@ -6,6 +6,9 @@ import { TestDataLoader } from '{{basePath}}/test-data';
|
|
|
6
6
|
{{#if needsDb}}
|
|
7
7
|
import { db } from '{{basePath}}/db';
|
|
8
8
|
{{/if}}
|
|
9
|
+
{{#if needsApi}}
|
|
10
|
+
import { api } from '{{basePath}}/api';
|
|
11
|
+
{{/if}}
|
|
9
12
|
|
|
10
13
|
// This file is auto-generated from Gherkin feature files
|
|
11
14
|
// DO NOT EDIT MANUALLY - changes will be overwritten
|
|
@@ -4,8 +4,8 @@ import { ParsedFeature, ParsedScenario, ParsedStep } from '../gherkin-parser';
|
|
|
4
4
|
import { StepMapper } from './step-mapper';
|
|
5
5
|
import { TestGeneratorAdapter, adapterRegistry } from './adapters';
|
|
6
6
|
import { transformToRuntimeData } from './utils/runtime-data-transformer';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { capabilityRegistry } from '../../capabilities/registry';
|
|
8
|
+
import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Filter base scenario steps for @extend: only keep Given→When steps.
|
|
@@ -240,11 +240,17 @@ export class CodeGenerator {
|
|
|
240
240
|
const hasCleanupTags = (feature.tags || []).some(t => t.startsWith('@cleanup:'));
|
|
241
241
|
const needsCleanupImport = !isParallelFeature && hasCleanupTags;
|
|
242
242
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
// Active capabilities for this feature (registry-driven): the default UI + any whose annotation
|
|
244
|
+
// tags appear (@query) or whose detectsStep matches (declarative DB steps). Each active
|
|
245
|
+
// capability emits its declared runtime helpers (db → specs/db.ts).
|
|
246
|
+
const activeCapabilityIds = this.activeCapabilityIds(feature);
|
|
247
|
+
const needsDb = activeCapabilityIds.includes('db');
|
|
248
|
+
const needsApi = activeCapabilityIds.includes('api');
|
|
249
|
+
for (const id of activeCapabilityIds) {
|
|
250
|
+
for (const h of capabilityRegistry.get(id)?.runtimeHelpers ?? []) this.syncGeneratedHelper(outputDir, h.file, h.template);
|
|
251
|
+
}
|
|
246
252
|
|
|
247
|
-
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb });
|
|
253
|
+
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb, needsApi });
|
|
248
254
|
|
|
249
255
|
// Generate test code (async now to support AI mapping)
|
|
250
256
|
const testCode = await this.generateTestCode(feature);
|
|
@@ -300,62 +306,40 @@ export class CodeGenerator {
|
|
|
300
306
|
/**
|
|
301
307
|
* Ensure specs/base.ts exists in the output directory
|
|
302
308
|
*/
|
|
303
|
-
/**
|
|
304
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Capabilities active for a feature (registry-driven): the default (UI) capability, plus any
|
|
311
|
+
* whose annotation tags appear on a scenario (e.g. `@query` → db) or whose `detectsStep`
|
|
312
|
+
* matches a step (db's declarative `User see [table] row where …`). Drives runtime-helper
|
|
313
|
+
* emission + the `db` import — replaces the hardcoded `featureUsesDb` check (R4).
|
|
314
|
+
*/
|
|
315
|
+
private activeCapabilityIds(feature: ParsedFeature): string[] {
|
|
316
|
+
discoverAndRegisterCapabilities();
|
|
305
317
|
const steps: ParsedStep[] = [];
|
|
306
318
|
if (feature.background?.steps) steps.push(...feature.background.steps);
|
|
307
319
|
for (const sc of feature.scenarios || []) if (sc.steps) steps.push(...sc.steps);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
320
|
+
const scenarioTags = (feature.scenarios || []).flatMap((sc) => sc.tags || []);
|
|
321
|
+
const ids = new Set<string>();
|
|
322
|
+
const def = capabilityRegistry.defaultCapabilityId();
|
|
323
|
+
if (def) ids.add(def);
|
|
324
|
+
for (const cap of capabilityRegistry.all()) {
|
|
325
|
+
const annoMatch = (cap.annotations ?? []).some((a) => scenarioTags.some((t) => t === a || t.startsWith(a + ':')));
|
|
326
|
+
const stepMatch = cap.detectsStep ? steps.some((s) => s && typeof s.text === 'string' && cap.detectsStep!(s.text)) : false;
|
|
327
|
+
if (annoMatch || stepMatch) ids.add(cap.id);
|
|
328
|
+
}
|
|
329
|
+
return [...ids];
|
|
311
330
|
}
|
|
312
331
|
|
|
313
332
|
/**
|
|
314
|
-
*
|
|
315
|
-
* Each
|
|
333
|
+
* Precondition steps a scenario's capabilities inject (e.g. db `@query:<name>` → bind {{name}}).
|
|
334
|
+
* Each capability owns its annotation codegen via the SPI (`preconditionCodegen`); the compiler
|
|
335
|
+
* just composes + indents the returned statements (R4).
|
|
316
336
|
*/
|
|
317
|
-
private
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
for (const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const name = m[1];
|
|
324
|
-
const overrides = this.parseQueryOverrides(m[2]);
|
|
325
|
-
const entry = resolveQuery(name, this.queryScreenName); // throws (fail-fast) if missing/ambiguous
|
|
326
|
-
const { sql, paramNames } = compileQuery(entry);
|
|
327
|
-
const paramExprs = paramNames.map((p) =>
|
|
328
|
-
p in overrides ? overrides[p] : `testData.get(${JSON.stringify(p)})`,
|
|
329
|
-
);
|
|
330
|
-
const label = JSON.stringify(entry.description ? `query "${name}" — ${entry.description}` : `query "${name}"`);
|
|
331
|
-
const ds = entry.datasource ? JSON.stringify(entry.datasource) : 'undefined';
|
|
332
|
-
out.push({
|
|
333
|
-
comment: `@query:${name} → bind {{${name}}} from ${entry.datasource || 'default datasource'}`,
|
|
334
|
-
code: this.indentCode(
|
|
335
|
-
`testData.bind(${JSON.stringify(name)}, await db.fetchQuery(${label}, ${JSON.stringify(sql)}, [${paramExprs.join(', ')}], ${ds}));`,
|
|
336
|
-
4,
|
|
337
|
-
),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return out;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** Parse `@query:name(a={{x}},b="lit",c=3)` overrides → { a: "testData.get('x')", … } JS exprs. */
|
|
344
|
-
private parseQueryOverrides(raw?: string): Record<string, string> {
|
|
345
|
-
const out: Record<string, string> = {};
|
|
346
|
-
if (!raw) return out;
|
|
347
|
-
for (const part of raw.split(',')) {
|
|
348
|
-
const eq = part.indexOf('=');
|
|
349
|
-
if (eq < 0) continue;
|
|
350
|
-
const key = part.slice(0, eq).trim();
|
|
351
|
-
const val = part.slice(eq + 1).trim();
|
|
352
|
-
if (!key) continue;
|
|
353
|
-
const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
354
|
-
const q = val.match(/^["'](.*)["']$/);
|
|
355
|
-
if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
|
|
356
|
-
else if (q) out[key] = JSON.stringify(q[1]);
|
|
357
|
-
else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
|
|
358
|
-
else out[key] = JSON.stringify(val);
|
|
337
|
+
private capabilityPreconditions(scenario: ParsedScenario): Array<{ comment?: string; code: string; boundVars?: string[] }> {
|
|
338
|
+
discoverAndRegisterCapabilities();
|
|
339
|
+
const out: Array<{ comment?: string; code: string; boundVars?: string[] }> = [];
|
|
340
|
+
for (const cap of capabilityRegistry.all()) {
|
|
341
|
+
if (!cap.preconditionCodegen) continue;
|
|
342
|
+
out.push(...cap.preconditionCodegen({ tags: scenario.tags || [], screenName: this.queryScreenName, cwd: process.cwd() }));
|
|
359
343
|
}
|
|
360
344
|
return out;
|
|
361
345
|
}
|
|
@@ -378,11 +362,6 @@ export class CodeGenerator {
|
|
|
378
362
|
console.log(`✓ ${exists ? 'Updated' : 'Created'}: specs/${fileName}`);
|
|
379
363
|
}
|
|
380
364
|
|
|
381
|
-
/** Ensure specs/db.ts is present and current (Data Driver runtime helper). */
|
|
382
|
-
ensureDbFile(outputDir: string): void {
|
|
383
|
-
this.syncGeneratedHelper(outputDir, 'db.ts', 'specs-db.ts');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
365
|
ensureBaseFile(outputDir: string): void {
|
|
387
366
|
this.syncGeneratedHelper(outputDir, 'base.ts', 'specs-base.ts');
|
|
388
367
|
// base.ts depends on locale-fixture.ts — keep them paired.
|
|
@@ -683,18 +662,16 @@ export class CodeGenerator {
|
|
|
683
662
|
for (const r of refs) this.stepMapper.registerCaptured(r);
|
|
684
663
|
}
|
|
685
664
|
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
// instead of a compile-time YAML lookup that would fail.
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
.map((m) => m[1]);
|
|
693
|
-
if (queryNames.length) {
|
|
665
|
+
// Capability preconditions (db `@query:<name>` → bind {{name}}) are owned by the capability via
|
|
666
|
+
// the SPI. Their bound `{{name.*}}` vars exist only at runtime → register them as captured so
|
|
667
|
+
// they resolve to a runtime get() instead of a compile-time YAML lookup that would fail.
|
|
668
|
+
const preconditions = this.capabilityPreconditions(scenario);
|
|
669
|
+
const boundVars = preconditions.flatMap((p) => p.boundVars || []);
|
|
670
|
+
if (boundVars.length) {
|
|
694
671
|
for (const st of stepsToMap) {
|
|
695
672
|
for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
|
|
696
673
|
const head = mt[1].split(/[.[]/)[0];
|
|
697
|
-
if (
|
|
674
|
+
if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
|
|
698
675
|
}
|
|
699
676
|
}
|
|
700
677
|
}
|
|
@@ -728,11 +705,11 @@ export class CodeGenerator {
|
|
|
728
705
|
}
|
|
729
706
|
}
|
|
730
707
|
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
708
|
+
// Capability preconditions (db `@query:<name>` → bind {{name}}; computed above) run BEFORE the
|
|
709
|
+
// scenario's own steps — prepend them, indenting the capability-supplied statements.
|
|
710
|
+
if (preconditions.length) {
|
|
711
|
+
steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
|
|
712
|
+
}
|
|
736
713
|
|
|
737
714
|
// Extract pass-through tags (feature + scenario, excluding functional tags)
|
|
738
715
|
const tags = extractPassThroughTags(scenario.tags, featureTags);
|
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { ParsedStep } from '../../gherkin-parser';
|
|
2
2
|
import { MappedStep } from '../step-mapper';
|
|
3
3
|
import { StepPattern, PatternContext } from './types';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { interactionPatterns } from './interaction-patterns';
|
|
7
|
-
import { assertionPatterns } from './assertion-patterns';
|
|
8
|
-
import { setupPatterns } from './setup-patterns';
|
|
9
|
-
import { keyboardPatterns } from './keyboard-patterns';
|
|
10
|
-
import { scrollPatterns } from './scroll-patterns';
|
|
11
|
-
import { scopePatterns } from './scope-patterns';
|
|
12
|
-
import { tablePatterns } from './table-patterns';
|
|
13
|
-
import { capturePatterns } from './capture-patterns';
|
|
14
|
-
import { databasePatterns } from './database-patterns';
|
|
15
|
-
import { expectPatterns } from './expect-patterns';
|
|
4
|
+
import { capabilityRegistry } from '../../../capabilities/registry';
|
|
5
|
+
import { discoverAndRegisterCapabilities } from '../../../capabilities/discover';
|
|
16
6
|
|
|
17
7
|
/**
|
|
18
8
|
* Pattern Registry - manages all step patterns
|
|
@@ -28,18 +18,11 @@ export class PatternRegistry {
|
|
|
28
18
|
* Register default patterns from all pattern modules
|
|
29
19
|
*/
|
|
30
20
|
private registerDefaultPatterns(): void {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.patterns.push(...
|
|
36
|
-
this.patterns.push(...keyboardPatterns);
|
|
37
|
-
this.patterns.push(...scrollPatterns);
|
|
38
|
-
this.patterns.push(...scopePatterns);
|
|
39
|
-
this.patterns.push(...tablePatterns);
|
|
40
|
-
this.patterns.push(...capturePatterns);
|
|
41
|
-
this.patterns.push(...databasePatterns);
|
|
42
|
-
this.patterns.push(...expectPatterns);
|
|
21
|
+
// Patterns are composed from the capability registry (Capability SPI, R1) instead of a
|
|
22
|
+
// hardcoded push list. Built-ins (ui · db · core) register the same set as before, so the
|
|
23
|
+
// composed list + priority sort is behaviour-identical (golden snapshots are the contract).
|
|
24
|
+
discoverAndRegisterCapabilities();
|
|
25
|
+
this.patterns.push(...capabilityRegistry.patterns());
|
|
43
26
|
|
|
44
27
|
// Sort by priority (higher first)
|
|
45
28
|
this.patterns.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
@@ -159,15 +142,6 @@ export class PatternRegistry {
|
|
|
159
142
|
}
|
|
160
143
|
}
|
|
161
144
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
export { navigationPatterns } from './navigation-patterns';
|
|
165
|
-
export { formPatterns } from './form-patterns';
|
|
166
|
-
export { interactionPatterns } from './interaction-patterns';
|
|
167
|
-
export { assertionPatterns } from './assertion-patterns';
|
|
168
|
-
export { keyboardPatterns } from './keyboard-patterns';
|
|
169
|
-
export { scrollPatterns } from './scroll-patterns';
|
|
170
|
-
export { scopePatterns } from './scope-patterns';
|
|
171
|
-
export { tablePatterns } from './table-patterns';
|
|
172
|
-
export { databasePatterns, isDbStep } from './database-patterns';
|
|
145
|
+
// The UI step patterns now live in @sungen/driver-ui (R5.4) and the DB patterns in @sungen/driver-db
|
|
146
|
+
// (R5.5); both are contributed via the capability registry, not re-exported here.
|
|
173
147
|
export * from './types';
|
|
@@ -229,8 +229,8 @@ export class TemplateEngine {
|
|
|
229
229
|
this.baseContext = {};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string {
|
|
233
|
-
return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb });
|
|
232
|
+
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string {
|
|
233
|
+
return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb, needsApi: options?.needsApi });
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
renderTestFile(data: {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared annotation-override grammar for precondition annotations (`@query`/`@api`).
|
|
3
|
+
*
|
|
4
|
+
* Parses `name(a={{x}},b="lit",c=3)` overrides into a map of JS expressions, e.g.
|
|
5
|
+
* `{ a: "testData.get('x')", b: "\"lit\"", c: "3" }`. Used by the DB and API capability drivers'
|
|
6
|
+
* precondition codegen; lives in core so both drivers (and core's `api` until R5.6) can share it.
|
|
7
|
+
*/
|
|
8
|
+
export function parseQueryOverrides(raw?: string): Record<string, string> {
|
|
9
|
+
const out: Record<string, string> = {};
|
|
10
|
+
if (!raw) return out;
|
|
11
|
+
for (const part of raw.split(',')) {
|
|
12
|
+
const eq = part.indexOf('=');
|
|
13
|
+
if (eq < 0) continue;
|
|
14
|
+
const key = part.slice(0, eq).trim();
|
|
15
|
+
const val = part.slice(eq + 1).trim();
|
|
16
|
+
if (!key) continue;
|
|
17
|
+
const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
18
|
+
const q = val.match(/^["'](.*)["']$/);
|
|
19
|
+
if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
|
|
20
|
+
else if (q) out[key] = JSON.stringify(q[1]);
|
|
21
|
+
else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
|
|
22
|
+
else out[key] = JSON.stringify(val);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
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';
|
|
@@ -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
|
|
|
@@ -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
|
|