@sun-asterisk/sungen 3.1.2 → 3.2.0-beta.141
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 +68 -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 +109 -0
- package/dist/capabilities/discover.js.map +1 -0
- package/dist/capabilities/registry.d.ts +92 -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 +52 -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/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +17 -11
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +57 -5
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/context.d.ts +9 -0
- package/dist/cli/commands/context.d.ts.map +1 -0
- package/dist/cli/commands/context.js +91 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +42 -30
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +35 -8
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/ledger.d.ts.map +1 -1
- package/dist/cli/commands/ledger.js +15 -5
- package/dist/cli/commands/ledger.js.map +1 -1
- package/dist/cli/commands/manifest.d.ts.map +1 -1
- package/dist/cli/commands/manifest.js +10 -9
- package/dist/cli/commands/manifest.js.map +1 -1
- package/dist/cli/commands/repair.d.ts +8 -0
- package/dist/cli/commands/repair.d.ts.map +1 -0
- package/dist/cli/commands/repair.js +97 -0
- package/dist/cli/commands/repair.js.map +1 -0
- package/dist/cli/commands/script-check.d.ts.map +1 -1
- package/dist/cli/commands/script-check.js +13 -9
- package/dist/cli/commands/script-check.js.map +1 -1
- package/dist/cli/commands/trace.d.ts.map +1 -1
- package/dist/cli/commands/trace.js +7 -4
- package/dist/cli/commands/trace.js.map +1 -1
- package/dist/cli/index.js +14 -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 +18 -9
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +162 -115
- 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 +11 -0
- package/dist/harness/annotation-overrides.d.ts.map +1 -0
- package/dist/harness/annotation-overrides.js +38 -0
- package/dist/harness/annotation-overrides.js.map +1 -0
- package/dist/harness/audit.d.ts +9 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +140 -10
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +14 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +63 -1
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +35 -12
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +23 -0
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts +9 -0
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +13 -6
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/intent.d.ts +6 -0
- package/dist/harness/intent.d.ts.map +1 -1
- package/dist/harness/intent.js +20 -4
- package/dist/harness/intent.js.map +1 -1
- package/dist/harness/ledger.d.ts.map +1 -1
- package/dist/harness/ledger.js +3 -2
- package/dist/harness/ledger.js.map +1 -1
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +3 -2
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/parse.d.ts +2 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +16 -4
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.js +1 -1
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/query-catalog.d.ts.map +1 -1
- package/dist/harness/query-catalog.js +0 -0
- package/dist/harness/query-catalog.js.map +1 -1
- package/dist/harness/repair.d.ts +20 -0
- package/dist/harness/repair.d.ts.map +1 -0
- package/dist/harness/repair.js +111 -0
- package/dist/harness/repair.js.map +1 -0
- package/dist/harness/script-check.d.ts +3 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +22 -8
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +40 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +54 -2
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/trace.d.ts.map +1 -1
- package/dist/harness/trace.js +4 -3
- package/dist/harness/trace.js.map +1 -1
- package/dist/harness/unit-paths.d.ts +3 -0
- package/dist/harness/unit-paths.d.ts.map +1 -0
- package/dist/harness/unit-paths.js +52 -0
- package/dist/harness/unit-paths.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +2 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/context-discovery.d.ts +12 -0
- package/dist/orchestrator/context-discovery.d.ts.map +1 -0
- package/dist/orchestrator/context-discovery.js +46 -0
- package/dist/orchestrator/context-discovery.js.map +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +10 -5
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +19 -2
- package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +10 -5
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +19 -2
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
- package/dist/orchestrator/templates/specs-api.d.ts +55 -0
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-api.js +171 -0
- package/dist/orchestrator/templates/specs-api.js.map +1 -0
- package/dist/orchestrator/templates/specs-api.ts +154 -0
- package/dist/orchestrator/templates/specs-db.d.ts +3 -0
- package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-db.js +78 -1
- package/dist/orchestrator/templates/specs-db.js.map +1 -1
- package/dist/orchestrator/templates/specs-db.ts +78 -1
- package/dist/orchestrator/templates/specs-test-data.ts +2 -1
- 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 +65 -0
- package/src/capabilities/discover.ts +62 -0
- package/src/capabilities/registry.ts +113 -0
- package/src/capabilities/sensor.ts +47 -0
- package/src/cli/commands/audit.ts +15 -9
- package/src/cli/commands/capability.ts +53 -5
- package/src/cli/commands/context.ts +52 -0
- package/src/cli/commands/delivery.ts +40 -31
- package/src/cli/commands/generate.ts +37 -8
- package/src/cli/commands/ledger.ts +13 -5
- package/src/cli/commands/manifest.ts +9 -7
- package/src/cli/commands/repair.ts +57 -0
- package/src/cli/commands/script-check.ts +12 -8
- package/src/cli/commands/trace.ts +7 -4
- package/src/cli/index.ts +14 -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 +163 -111
- 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 +27 -0
- package/src/harness/audit.ts +141 -12
- package/src/harness/capability-plan.ts +51 -1
- package/src/harness/catalog/drivers.yaml +35 -12
- package/src/harness/data-driven-lint.ts +20 -0
- package/src/harness/flow-check.ts +15 -6
- package/src/harness/intent.ts +25 -4
- package/src/harness/ledger.ts +3 -2
- package/src/harness/manifest.ts +3 -2
- package/src/harness/parse.ts +11 -2
- package/src/harness/quality-gates.ts +1 -1
- package/src/harness/query-catalog.ts +0 -0
- package/src/harness/repair.ts +75 -0
- package/src/harness/script-check.ts +25 -8
- package/src/harness/sensors.ts +71 -2
- package/src/harness/trace.ts +4 -3
- package/src/harness/unit-paths.ts +14 -0
- package/src/index.ts +32 -0
- package/src/orchestrator/ai-rules-updater.ts +2 -0
- package/src/orchestrator/context-discovery.ts +50 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +10 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +19 -2
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +10 -5
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +19 -2
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
- package/src/orchestrator/templates/specs-api.ts +154 -0
- package/src/orchestrator/templates/specs-db.ts +78 -1
- package/src/orchestrator/templates/specs-test-data.ts +2 -1
- 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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { discoverUnitContext } from '../../orchestrator/context-discovery';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `sungen context` — run the Discover → Contextualize phase (AO-3) for a unit and emit the
|
|
8
|
+
* normalized Context + generation-unit work-list. The `/sungen:design` loop calls this first so it
|
|
9
|
+
* knows WHAT to generate (the endpoints/screens + their modes) before authoring scenarios. Pure +
|
|
10
|
+
* deterministic; writes `.sungen/reports/<unit>-context.json`.
|
|
11
|
+
*/
|
|
12
|
+
export function registerContextCommand(program: Command): void {
|
|
13
|
+
program
|
|
14
|
+
.command('context')
|
|
15
|
+
.description('Discover + contextualize a unit (screen/flow/api area) → Context + generation units')
|
|
16
|
+
.option('-s, --screen <name>', 'Screen name')
|
|
17
|
+
.option('--flow <name>', 'Flow name (resolved as flows/<name>)')
|
|
18
|
+
.option('--api <name>', 'API-first area or api flow (resolved as api/<name>)')
|
|
19
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
20
|
+
.option('--json', 'Print the raw JSON')
|
|
21
|
+
.action(async (o: { screen?: string; flow?: string; api?: string; area?: string; json?: boolean }) => {
|
|
22
|
+
try {
|
|
23
|
+
const unitId = (o.api || o.area) ? `api/${o.api || o.area}` : o.flow ? `flows/${o.flow}` : o.screen;
|
|
24
|
+
if (!unitId) throw new Error('Provide --screen <name>, --flow <name>, or --api <area>.');
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const result = await discoverUnitContext(unitId, cwd);
|
|
27
|
+
|
|
28
|
+
const outDir = path.join(cwd, '.sungen', 'reports');
|
|
29
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
30
|
+
const slug = unitId.replace(/\//g, '-');
|
|
31
|
+
const outPath = path.join(outDir, `${slug}-context.json`);
|
|
32
|
+
fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf-8');
|
|
33
|
+
if (o.json) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
34
|
+
|
|
35
|
+
const L = console.log;
|
|
36
|
+
const facts = result.context.facts as { endpoints?: Array<{ name: string; method: string; path: string }> };
|
|
37
|
+
L(`\n━━━ Context: ${unitId} (capability: ${result.capability}) ━━━`);
|
|
38
|
+
const srcKeys = Object.keys(result.context.sources);
|
|
39
|
+
L(` Sources: ${srcKeys.length ? srcKeys.join(', ') : '(none)'}`);
|
|
40
|
+
if (facts.endpoints?.length) {
|
|
41
|
+
L(` Endpoints (${facts.endpoints.length}):`);
|
|
42
|
+
for (const e of facts.endpoints) L(` • ${e.method} ${e.path} (@api:${e.name})`);
|
|
43
|
+
}
|
|
44
|
+
const byMode = result.units.reduce<Record<string, number>>((m, u) => { m[u.mode] = (m[u.mode] || 0) + 1; return m; }, {});
|
|
45
|
+
L(` Generation units: ${result.units.length} — ${Object.entries(byMode).map(([m, n]) => `${m}×${n}`).join(' · ') || '(none)'}`);
|
|
46
|
+
L(`\n Report: ${path.relative(cwd, outPath)}\n`);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -52,45 +52,54 @@ function log(msg: string): void {
|
|
|
52
52
|
* `name` is kept as an alias of `featureBaseName` so existing callers/labels
|
|
53
53
|
* (preflight table, summary) read naturally — every visible row is per-feature.
|
|
54
54
|
*/
|
|
55
|
+
type UnitKind = 'screen' | 'flow' | 'api';
|
|
56
|
+
/** qa/ subfolder for a unit kind. */
|
|
57
|
+
const qaParent = (kind: UnitKind): string => (kind === 'flow' ? 'flows' : kind === 'api' ? 'api' : 'screens');
|
|
58
|
+
|
|
55
59
|
interface DeliveryTarget {
|
|
56
60
|
screen: string;
|
|
57
61
|
featureBaseName: string;
|
|
58
62
|
/** Alias of `featureBaseName` — preserves the old `target.name` call sites. */
|
|
59
63
|
name: string;
|
|
64
|
+
kind: UnitKind;
|
|
65
|
+
/** Back-compat: flows kept distinct labels/paths before api was added. */
|
|
60
66
|
isFlow: boolean;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
function makeTarget(screen: string, featureBaseName: string,
|
|
64
|
-
return { screen, featureBaseName, name: featureBaseName, isFlow };
|
|
69
|
+
function makeTarget(screen: string, featureBaseName: string, kind: UnitKind): DeliveryTarget {
|
|
70
|
+
return { screen, featureBaseName, name: featureBaseName, kind, isFlow: kind === 'flow' };
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/**
|
|
68
|
-
* List all `.feature` files inside a screen/flow as separate targets.
|
|
74
|
+
* List all `.feature` files inside a screen/flow/api unit as separate targets.
|
|
69
75
|
* Returns empty array when the directory has no features yet.
|
|
70
76
|
*/
|
|
71
|
-
function listFeatureTargets(cwd: string, screen: string,
|
|
72
|
-
const featuresDir = path.join(cwd, 'qa',
|
|
77
|
+
function listFeatureTargets(cwd: string, screen: string, kind: UnitKind): DeliveryTarget[] {
|
|
78
|
+
const featuresDir = path.join(cwd, 'qa', qaParent(kind), screen, 'features');
|
|
73
79
|
if (!fs.existsSync(featuresDir)) return [];
|
|
74
80
|
return fs.readdirSync(featuresDir)
|
|
75
81
|
.filter((f) => f.endsWith('.feature'))
|
|
76
|
-
.map((f) => makeTarget(screen, f.slice(0, -'.feature'.length),
|
|
82
|
+
.map((f) => makeTarget(screen, f.slice(0, -'.feature'.length), kind))
|
|
77
83
|
.sort((a, b) => a.featureBaseName.localeCompare(b.featureBaseName));
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
function listAllTargets(cwd: string): DeliveryTarget[] {
|
|
81
87
|
const targets: DeliveryTarget[] = [];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (const d of fs.readdirSync(
|
|
86
|
-
if (d.isDirectory()) targets.push(...listFeatureTargets(cwd, d.name,
|
|
88
|
+
const scan = (kind: UnitKind, skip: (n: string) => boolean = () => false) => {
|
|
89
|
+
const root = path.join(cwd, 'qa', qaParent(kind));
|
|
90
|
+
if (!fs.existsSync(root)) return;
|
|
91
|
+
for (const d of fs.readdirSync(root, { withFileTypes: true })) {
|
|
92
|
+
if (d.isDirectory() && !skip(d.name)) targets.push(...listFeatureTargets(cwd, d.name, kind));
|
|
87
93
|
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
};
|
|
95
|
+
scan('screen');
|
|
96
|
+
scan('flow');
|
|
97
|
+
scan('api', (n) => n === 'flows'); // api areas: qa/api/<area>
|
|
98
|
+
// api flows: qa/api/flows/<flow> → screen `flows/<flow>`, kind 'api' (paths compose to qa/api/flows/… + specs/generated/api/flows/…)
|
|
99
|
+
const apiFlowsRoot = path.join(cwd, 'qa', 'api', 'flows');
|
|
100
|
+
if (fs.existsSync(apiFlowsRoot)) {
|
|
101
|
+
for (const d of fs.readdirSync(apiFlowsRoot, { withFileTypes: true })) {
|
|
102
|
+
if (d.isDirectory()) targets.push(...listFeatureTargets(cwd, path.posix.join('flows', d.name), 'api'));
|
|
94
103
|
}
|
|
95
104
|
}
|
|
96
105
|
|
|
@@ -108,28 +117,27 @@ function listAllTargets(cwd: string): DeliveryTarget[] {
|
|
|
108
117
|
* feature file with the basename across all screens & flows.
|
|
109
118
|
*/
|
|
110
119
|
function resolveTargetsFromArg(cwd: string, name: string): DeliveryTarget[] {
|
|
111
|
-
if (fs.existsSync(path.join(cwd, 'qa', 'flows', name)))
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Treat as feature basename — find the parent
|
|
120
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'flows', name))) return listFeatureTargets(cwd, name, 'flow');
|
|
121
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'screens', name))) return listFeatureTargets(cwd, name, 'screen');
|
|
122
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'api', name))) return listFeatureTargets(cwd, name, 'api');
|
|
123
|
+
// `flows/<flow>` or a bare api-flow name → qa/api/flows/<flow>
|
|
124
|
+
const apiFlow = name.startsWith('flows/') ? name : path.posix.join('flows', name);
|
|
125
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'api', apiFlow))) return listFeatureTargets(cwd, apiFlow, 'api');
|
|
126
|
+
// Treat as feature basename — find the parent unit that hosts it.
|
|
118
127
|
const candidates = listAllTargets(cwd).filter((t) => t.featureBaseName === name);
|
|
119
128
|
if (candidates.length > 0) return candidates;
|
|
120
129
|
// Fallback: treat as screen name even if directory missing (lets preflight
|
|
121
130
|
// surface the "feature file missing" error with the right path).
|
|
122
|
-
return [makeTarget(name, name,
|
|
131
|
+
return [makeTarget(name, name, 'screen')];
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
function qaDir(cwd: string, target: DeliveryTarget): string {
|
|
126
|
-
return path.join(cwd, 'qa', target.
|
|
135
|
+
return path.join(cwd, 'qa', qaParent(target.kind), target.screen);
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
function generatedDir(cwd: string, target: DeliveryTarget): string {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
: path.join(cwd, 'specs', 'generated', target.screen);
|
|
139
|
+
const sub = target.kind === 'flow' ? path.join('flows', target.screen) : target.kind === 'api' ? path.join('api', target.screen) : target.screen;
|
|
140
|
+
return path.join(cwd, 'specs', 'generated', sub);
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
// ----------------------------------------------------------------------------
|
|
@@ -262,7 +270,8 @@ function runPreflight(cwd: string, target: DeliveryTarget): PreflightCheck {
|
|
|
262
270
|
|
|
263
271
|
const featureOk = checkFeatureReal(featureFile);
|
|
264
272
|
const testDataOk = checkTestDataHasVars(testDataFile);
|
|
265
|
-
|
|
273
|
+
// API units have no DOM → no selectors; the catalog (apis.yaml) is the contract. N/A, not missing.
|
|
274
|
+
const selectorsOk = target.kind === 'api' ? true : checkSelectorsHasEntries(selectorsFile, target.featureBaseName);
|
|
266
275
|
const specOk = fs.existsSync(specFile);
|
|
267
276
|
const resultsOk = resultsFile !== null;
|
|
268
277
|
|
|
@@ -284,7 +293,7 @@ function runPreflight(cwd: string, target: DeliveryTarget): PreflightCheck {
|
|
|
284
293
|
}
|
|
285
294
|
if (!specOk) {
|
|
286
295
|
missing.push(`compiled .spec.ts missing: ${path.relative(cwd, specFile)}`);
|
|
287
|
-
suggestions.push(target.
|
|
296
|
+
suggestions.push(`sungen generate --${target.kind === 'flow' ? 'flow' : target.kind === 'api' ? 'api' : 'screen'} ${target.screen}`);
|
|
288
297
|
}
|
|
289
298
|
if (!resultsOk) {
|
|
290
299
|
const env = process.env.SUNGEN_ENV;
|
|
@@ -4,7 +4,8 @@ import * as fs from 'fs';
|
|
|
4
4
|
import { CodeGenerator } from '../../generators/test-generator/code-generator';
|
|
5
5
|
import { adapterRegistry } from '../../generators/test-generator/adapters';
|
|
6
6
|
import { scanTestDataSecrets } from '../../harness/secret-scan';
|
|
7
|
-
import {
|
|
7
|
+
import { capabilityRegistry } from '../../capabilities/registry';
|
|
8
|
+
import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
|
|
8
9
|
import { readCapabilities, writeCapabilities, driverMeta, loadDriverCatalog } from '../../harness/capability';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -43,12 +44,21 @@ function findFeatureFilesForFlow(flowName: string): string[] {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
|
-
* Find
|
|
47
|
+
* Find feature files for a specific API-first unit — an area (`orders`) or an api flow
|
|
48
|
+
* (`flows/<flow>`). Catalog/test-data live alongside under qa/api/<name>/.
|
|
49
|
+
*/
|
|
50
|
+
function findFeatureFilesForApi(name: string): string[] {
|
|
51
|
+
return findFeatureFiles(path.join(process.cwd(), 'qa', 'api', name, 'features'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find all feature files across all screens, flows, and API-first areas/flows.
|
|
47
56
|
*/
|
|
48
57
|
function findAllFeatureFiles(): string[] {
|
|
49
58
|
const allFiles: string[] = [];
|
|
50
59
|
const screensDir = path.join(process.cwd(), 'qa', 'screens');
|
|
51
60
|
const flowsDir = path.join(process.cwd(), 'qa', 'flows');
|
|
61
|
+
const apiDir = path.join(process.cwd(), 'qa', 'api');
|
|
52
62
|
|
|
53
63
|
if (fs.existsSync(screensDir)) {
|
|
54
64
|
const screenDirs = fs.readdirSync(screensDir, { withFileTypes: true })
|
|
@@ -72,6 +82,9 @@ function findAllFeatureFiles(): string[] {
|
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
// API-first: every .feature under qa/api/** (areas qa/api/<area>/features + flows qa/api/flows/<flow>/features).
|
|
86
|
+
if (fs.existsSync(apiDir)) allFiles.push(...findFeatureFiles(apiDir));
|
|
87
|
+
|
|
75
88
|
return allFiles;
|
|
76
89
|
}
|
|
77
90
|
|
|
@@ -81,19 +94,32 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
81
94
|
.description('Generate Playwright test code from Gherkin features')
|
|
82
95
|
.option('-s, --screen <name>', 'Generate tests for a specific screen')
|
|
83
96
|
.option('--flow <name>', 'Generate tests for a specific flow')
|
|
84
|
-
.option('--
|
|
97
|
+
.option('--api <name>', 'Generate tests for an API-first area or api flow (e.g. orders, flows/signup)')
|
|
98
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
99
|
+
.option('--all', 'Generate tests for all screens, flows, and API areas')
|
|
85
100
|
.option('--framework <name>', 'Override the platform driver (else read from qa/capabilities.yaml)')
|
|
86
101
|
.option('--inline-data', 'Hardcode test data at compile time instead of runtime loading')
|
|
87
102
|
.action(async (options) => {
|
|
88
103
|
try {
|
|
89
104
|
const screenName = options.screen;
|
|
90
105
|
const flowName = options.flow;
|
|
106
|
+
const apiName = options.api || options.area;
|
|
91
107
|
|
|
92
108
|
// Find feature files
|
|
93
109
|
let featureFiles: string[];
|
|
94
110
|
let qaSourceDir: string;
|
|
95
111
|
|
|
96
|
-
if (
|
|
112
|
+
if (apiName) {
|
|
113
|
+
featureFiles = findFeatureFilesForApi(apiName);
|
|
114
|
+
if (featureFiles.length === 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`No feature files found for API unit: ${apiName}\n` +
|
|
117
|
+
`Looked in: qa/api/${apiName}/features/ (scaffold with \`sungen api add --area ${apiName}\` or \`--flow\`)`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
qaSourceDir = path.join(process.cwd(), 'qa', 'api');
|
|
121
|
+
console.log(`\nGenerating tests: api/${apiName}\n`);
|
|
122
|
+
} else if (screenName) {
|
|
97
123
|
featureFiles = findFeatureFilesForScreen(screenName);
|
|
98
124
|
if (featureFiles.length === 0) {
|
|
99
125
|
throw new Error(
|
|
@@ -116,10 +142,10 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
116
142
|
} else {
|
|
117
143
|
featureFiles = findAllFeatureFiles();
|
|
118
144
|
if (featureFiles.length === 0) {
|
|
119
|
-
throw new Error('No feature files found in qa/screens/ or qa/
|
|
145
|
+
throw new Error('No feature files found in qa/screens/, qa/flows/, or qa/api/');
|
|
120
146
|
}
|
|
121
147
|
qaSourceDir = path.join(process.cwd(), 'qa');
|
|
122
|
-
console.log(`\nGenerating tests for all screens and
|
|
148
|
+
console.log(`\nGenerating tests for all screens, flows, and API areas\n`);
|
|
123
149
|
}
|
|
124
150
|
|
|
125
151
|
// Output directory
|
|
@@ -183,8 +209,11 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
183
209
|
console.log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.`);
|
|
184
210
|
}
|
|
185
211
|
|
|
186
|
-
//
|
|
187
|
-
|
|
212
|
+
// Advisory harness sensors (e.g. @cases/@query lint) — run via the Capability SPI,
|
|
213
|
+
// never block generation. Each capability registers its own; the kernel just runs them.
|
|
214
|
+
discoverAndRegisterCapabilities();
|
|
215
|
+
const advisorySensors = capabilityRegistry.sensors('advisory');
|
|
216
|
+
const ddWarnings = scanDirs.flatMap((d) => advisorySensors.flatMap((s) => s.run({ dir: d, cwd })));
|
|
188
217
|
if (ddWarnings.length) {
|
|
189
218
|
console.log(`\n⚠️ Data-driven lint (@cases / @query) — review:`);
|
|
190
219
|
for (const w of ddWarnings.slice(0, 20)) console.log(` ${w.scenario ? w.scenario + ': ' : ''}${w.message}`);
|
|
@@ -9,7 +9,9 @@ export function registerLedgerCommand(program: Command): void {
|
|
|
9
9
|
ledger
|
|
10
10
|
.command('record')
|
|
11
11
|
.description('Append a step event to the ledger')
|
|
12
|
-
.
|
|
12
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
13
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
14
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
13
15
|
.requiredOption('--step <name>', 'Step name (discovery | viewpoint | gherkin | audit | repair:1 ...)')
|
|
14
16
|
.option('--run <id>', 'Run id — groups all phases of one create-test invocation (else auto-segmented by time gap)')
|
|
15
17
|
.option('--model <id>', 'Model id')
|
|
@@ -19,10 +21,12 @@ export function registerLedgerCommand(program: Command): void {
|
|
|
19
21
|
.option('--note <text>', 'Free note')
|
|
20
22
|
.action((o) => {
|
|
21
23
|
try {
|
|
22
|
-
|
|
24
|
+
const name = o.screen || o.api || o.area;
|
|
25
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
26
|
+
recordEvent(name, {
|
|
23
27
|
step: o.step, runId: o.run, model: o.model, tokensIn: o.tokensIn, tokensOut: o.tokensOut, ms: o.ms, note: o.note,
|
|
24
28
|
});
|
|
25
|
-
console.log(`✓ ledger: ${
|
|
29
|
+
console.log(`✓ ledger: ${name} · ${o.step}`);
|
|
26
30
|
} catch (e) {
|
|
27
31
|
console.error('Error:', e instanceof Error ? e.message : e);
|
|
28
32
|
process.exit(1);
|
|
@@ -32,12 +36,16 @@ export function registerLedgerCommand(program: Command): void {
|
|
|
32
36
|
ledger
|
|
33
37
|
.command('report')
|
|
34
38
|
.description('Summarise ledger + efficiency verdicts (pulls audit score if present)')
|
|
35
|
-
.
|
|
39
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
40
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
41
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
36
42
|
.option('--all-runs', 'Aggregate ALL runs (default: latest run only)')
|
|
37
43
|
.option('--json', 'Output raw JSON')
|
|
38
44
|
.action((o) => {
|
|
39
45
|
try {
|
|
40
|
-
const
|
|
46
|
+
const name = o.screen || o.api || o.area;
|
|
47
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
48
|
+
const r = buildReport(name, { allRuns: o.allRuns });
|
|
41
49
|
if (o.json) { console.log(JSON.stringify(r, null, 2)); return; }
|
|
42
50
|
const scope = r.runScope === 'all' ? `all ${r.runs} runs` : `latest run${r.runs > 1 ? ` of ${r.runs}` : ''}`;
|
|
43
51
|
console.log(`\n━━━ Usage Ledger: ${r.screen} (${r.events} events · ${scope}) ━━━`);
|
|
@@ -4,10 +4,10 @@ import * as fs from 'fs';
|
|
|
4
4
|
import { buildManifest, diffManifest, loadManifest, saveManifest } from '../../harness/manifest';
|
|
5
5
|
|
|
6
6
|
function findScreenDir(name: string): string | null {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
for (const p of ['screens', 'flows', 'api']) {
|
|
8
|
+
const d = path.join(process.cwd(), 'qa', p, name);
|
|
9
|
+
if (fs.existsSync(d)) return d;
|
|
10
|
+
}
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -16,14 +16,16 @@ export function registerManifestCommand(program: Command): void {
|
|
|
16
16
|
.command('manifest')
|
|
17
17
|
.description('Spec-fingerprint manifest: build a scenario↔spec-section map, or diff to plan keep/regenerate/retire')
|
|
18
18
|
.option('-s, --screen <name>', 'Screen or flow name')
|
|
19
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
20
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
19
21
|
.option('--diff', 'Compare current spec vs stored manifest → change plan')
|
|
20
22
|
.option('--json', 'Output raw JSON')
|
|
21
23
|
.action((options) => {
|
|
22
24
|
try {
|
|
23
|
-
const name = options.screen;
|
|
24
|
-
if (!name) throw new Error('Provide --screen <name>');
|
|
25
|
+
const name = options.screen || options.api || options.area;
|
|
26
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
25
27
|
const dir = findScreenDir(name);
|
|
26
|
-
if (!dir) throw new Error(`
|
|
28
|
+
if (!dir) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
27
29
|
|
|
28
30
|
if (options.diff) {
|
|
29
31
|
const manifest = loadManifest(name);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { planRepair } from '../../harness/repair';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `sungen repair` (#343) — turn a unit's audit findings + Playwright failures into a concrete fix
|
|
8
|
+
* plan, using the unit-capability's fix catalog (the `repair` SPI). Deterministic; the `/sungen:design`
|
|
9
|
+
* + `/sungen:run-test` repair loops apply the same proposals. Run `sungen audit`/the tests first.
|
|
10
|
+
*/
|
|
11
|
+
export function registerRepairCommand(program: Command): void {
|
|
12
|
+
program
|
|
13
|
+
.command('repair')
|
|
14
|
+
.description('Propose concrete fixes for a unit from its audit findings + test failures (capability fix catalog)')
|
|
15
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
16
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
17
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
18
|
+
.option('--json', 'Output raw JSON')
|
|
19
|
+
.action((o: { screen?: string; api?: string; area?: string; json?: boolean }) => {
|
|
20
|
+
try {
|
|
21
|
+
const name = o.screen || o.api || o.area;
|
|
22
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
// Capability-resolution id + the generated dir, per kind.
|
|
25
|
+
const kind = fs.existsSync(path.join(cwd, 'qa', 'api', name)) || o.api ? 'api'
|
|
26
|
+
: fs.existsSync(path.join(cwd, 'qa', 'flows', name)) ? 'flow' : 'screen';
|
|
27
|
+
const unitId = kind === 'api' ? `api/${name}` : kind === 'flow' ? `flows/${name}` : name;
|
|
28
|
+
const generatedDir = path.join(cwd, 'specs', 'generated', kind === 'api' ? path.join('api', name) : kind === 'flow' ? path.join('flows', name) : name);
|
|
29
|
+
|
|
30
|
+
const plan = planRepair(unitId, name, cwd, generatedDir);
|
|
31
|
+
if (o.json) { console.log(JSON.stringify(plan, null, 2)); return; }
|
|
32
|
+
|
|
33
|
+
const L = console.log;
|
|
34
|
+
L(`\n━━━ Repair plan: ${name} (capability: ${plan.capability ?? 'none'}) ━━━`);
|
|
35
|
+
if (!plan.rulesAvailable) L(` (this capability ships no repair rules)`);
|
|
36
|
+
if (!plan.proposals.length && !plan.unmatched.length) {
|
|
37
|
+
L(` ✓ Nothing to repair — run \`sungen audit --${kind === 'api' ? 'api' : 'screen'} ${name}\` + the tests first, or the unit is clean.\n`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (plan.proposals.length) {
|
|
41
|
+
L(`\n Proposed fixes (${plan.proposals.length}):`);
|
|
42
|
+
for (const p of plan.proposals) {
|
|
43
|
+
L(` • [${p.ruleId}] ${p.source === 'runtime' ? '(test) ' : ''}${p.signal}`);
|
|
44
|
+
L(` → ${p.fix}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (plan.unmatched.length) {
|
|
48
|
+
L(`\n No known rule (review manually):`);
|
|
49
|
+
for (const u of plan.unmatched.slice(0, 10)) L(` - ${u}`);
|
|
50
|
+
}
|
|
51
|
+
L('');
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -2,28 +2,32 @@ import { Command } from 'commander';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { runScriptCheck } from '../../harness/script-check';
|
|
5
|
+
import { reportSlug } from '../../harness/unit-paths';
|
|
5
6
|
|
|
6
7
|
export function registerScriptCheckCommand(program: Command): void {
|
|
7
8
|
program
|
|
8
9
|
.command('script-check')
|
|
9
10
|
.description('Verify the generated Playwright spec is a faithful 1:1 of the Gherkin feature (no hand-edit / stale drift)')
|
|
10
11
|
.option('-s, --screen <name>', 'Screen or flow name')
|
|
12
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
13
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
11
14
|
.option('--json', 'Output raw JSON')
|
|
12
15
|
.action(async (options) => {
|
|
13
16
|
try {
|
|
14
|
-
const name = options.screen;
|
|
15
|
-
if (!name) throw new Error('Provide --screen <name>');
|
|
17
|
+
const name = options.screen || options.api || options.area;
|
|
18
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
16
19
|
const screen = path.join(process.cwd(), 'qa', 'screens', name);
|
|
17
20
|
const flow = path.join(process.cwd(), 'qa', 'flows', name);
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
+
const api = path.join(process.cwd(), 'qa', 'api', name);
|
|
22
|
+
const kind = fs.existsSync(screen) ? 'screen' : fs.existsSync(flow) ? 'flow' : fs.existsSync(api) ? 'api' : null;
|
|
23
|
+
const dir = kind === 'screen' ? screen : kind === 'flow' ? flow : kind === 'api' ? api : null;
|
|
24
|
+
if (!dir || !kind) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
21
25
|
|
|
22
|
-
const r = await runScriptCheck(dir, name,
|
|
26
|
+
const r = await runScriptCheck(dir, name, kind);
|
|
23
27
|
|
|
24
28
|
const outDir = path.join(process.cwd(), '.sungen', 'reports');
|
|
25
29
|
fs.mkdirSync(outDir, { recursive: true });
|
|
26
|
-
fs.writeFileSync(path.join(outDir, `${name}-script-check.json`), JSON.stringify(r, null, 2), 'utf-8');
|
|
30
|
+
fs.writeFileSync(path.join(outDir, `${reportSlug(name)}-script-check.json`), JSON.stringify(r, null, 2), 'utf-8');
|
|
27
31
|
|
|
28
32
|
if (options.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.status === 'OK' ? 0 : 2); }
|
|
29
33
|
|
|
@@ -39,7 +43,7 @@ export function registerScriptCheckCommand(program: Command): void {
|
|
|
39
43
|
if (r.findings.length) { L(' findings:'); for (const f of r.findings) L(` • ${f}`); }
|
|
40
44
|
else L(' ✓ The test code faithfully reflects the Gherkin (1:1).');
|
|
41
45
|
L('');
|
|
42
|
-
if (r.drift === 'drift') L(
|
|
46
|
+
if (r.drift === 'drift') L(` → Fix: re-run \`sungen generate --${kind === 'api' ? 'api' : kind === 'flow' ? 'flow' : 'screen'} ${name}\` (or /sungen:run-test) so the spec matches the feature. Never hand-edit generated specs.`);
|
|
43
47
|
L('');
|
|
44
48
|
process.exit(r.status === 'OK' ? 0 : 2);
|
|
45
49
|
} catch (error) {
|
|
@@ -8,16 +8,19 @@ export function registerTraceCommand(program: Command): void {
|
|
|
8
8
|
.command('trace')
|
|
9
9
|
.description('Visualise the executed test-design process (workflow/skill steps, repair loops), find bottlenecks, and show where to focus human review')
|
|
10
10
|
.option('-s, --screen <name>', 'Screen or flow name')
|
|
11
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
12
|
+
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
11
13
|
.option('--json', 'Output raw JSON')
|
|
12
14
|
.option('--mermaid', 'Print only the Mermaid flowchart')
|
|
13
15
|
.action((options) => {
|
|
14
16
|
try {
|
|
15
|
-
const name = options.screen;
|
|
16
|
-
if (!name) throw new Error('Provide --screen <name>');
|
|
17
|
+
const name = options.screen || options.api || options.area;
|
|
18
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
17
19
|
const screen = path.join(process.cwd(), 'qa', 'screens', name);
|
|
18
20
|
const flow = path.join(process.cwd(), 'qa', 'flows', name);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
+
const api = path.join(process.cwd(), 'qa', 'api', name);
|
|
22
|
+
const dir = fs.existsSync(screen) ? screen : fs.existsSync(flow) ? flow : fs.existsSync(api) ? api : null;
|
|
23
|
+
if (!dir) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
21
24
|
|
|
22
25
|
const r = buildTrace(dir, name);
|
|
23
26
|
if (options.json) { console.log(JSON.stringify(r, null, 2)); return; }
|
package/src/cli/index.ts
CHANGED
|
@@ -26,6 +26,10 @@ import { registerChallengeCommand } from './commands/challenge';
|
|
|
26
26
|
import { registerBlindspotCommand } from './commands/blindspot';
|
|
27
27
|
import { registerCapabilityCommand } from './commands/capability';
|
|
28
28
|
import { registerFlowCheckCommand } from './commands/flow-check';
|
|
29
|
+
import { registerContextCommand } from './commands/context';
|
|
30
|
+
import { registerRepairCommand } from './commands/repair';
|
|
31
|
+
import { capabilityRegistry } from '../capabilities/registry';
|
|
32
|
+
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
29
33
|
|
|
30
34
|
// Read version from package.json so `--version` never drifts from the released version.
|
|
31
35
|
const { version } = require('../../package.json') as { version: string };
|
|
@@ -42,7 +46,7 @@ async function main() {
|
|
|
42
46
|
program
|
|
43
47
|
.option('-v, --verbose', 'Enable verbose logging');
|
|
44
48
|
|
|
45
|
-
// Register commands
|
|
49
|
+
// Register commands
|
|
46
50
|
registerInitCommand(program);
|
|
47
51
|
registerAddCommand(program);
|
|
48
52
|
registerGenerateCommand(program);
|
|
@@ -62,9 +66,18 @@ async function main() {
|
|
|
62
66
|
registerBlindspotCommand(program);
|
|
63
67
|
registerCapabilityCommand(program);
|
|
64
68
|
registerFlowCheckCommand(program);
|
|
69
|
+
registerContextCommand(program);
|
|
70
|
+
registerRepairCommand(program);
|
|
65
71
|
registerIngestCommand(program);
|
|
66
72
|
registerEvalCommand(program);
|
|
67
73
|
|
|
74
|
+
// Capability-contributed CLI commands (Capability SPI): drivers own their authoring commands
|
|
75
|
+
// (e.g. @sungen/driver-api → `sungen api import`). Discover, then register each.
|
|
76
|
+
discoverAndRegisterCapabilities();
|
|
77
|
+
for (const cap of capabilityRegistry.all()) {
|
|
78
|
+
for (const registerCommand of cap.cliCommands ?? []) registerCommand(program);
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
await program.parseAsync(process.argv);
|
|
69
82
|
}
|
|
70
83
|
|
|
@@ -64,7 +64,7 @@ export interface TestGeneratorAdapter {
|
|
|
64
64
|
// Template rendering methods
|
|
65
65
|
renderTestFile(data: TestFileData): string;
|
|
66
66
|
renderScenario(data: ScenarioData): string;
|
|
67
|
-
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string;
|
|
67
|
+
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string;
|
|
68
68
|
renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
|
|
69
69
|
renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
|
|
70
70
|
renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
|
|
@@ -26,7 +26,7 @@ export class PlaywrightAdapter implements TestGeneratorAdapter {
|
|
|
26
26
|
return this.templateEngine.renderScenario(data);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string {
|
|
29
|
+
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string {
|
|
30
30
|
return this.templateEngine.renderImports(options);
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -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
|