@sun-asterisk/sungen 2.7.0-beta.1 → 3.0.0-beta.72
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 +1 -1
- package/dist/cli/commands/add.js +3 -3
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +3 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +134 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/blindspot.d.ts +3 -0
- package/dist/cli/commands/blindspot.d.ts.map +1 -0
- package/dist/cli/commands/blindspot.js +58 -0
- package/dist/cli/commands/blindspot.js.map +1 -0
- package/dist/cli/commands/capability.d.ts +3 -0
- package/dist/cli/commands/capability.d.ts.map +1 -0
- package/dist/cli/commands/capability.js +196 -0
- package/dist/cli/commands/capability.js.map +1 -0
- package/dist/cli/commands/challenge.d.ts +3 -0
- package/dist/cli/commands/challenge.d.ts.map +1 -0
- package/dist/cli/commands/challenge.js +102 -0
- package/dist/cli/commands/challenge.js.map +1 -0
- package/dist/cli/commands/feedback.d.ts +3 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -0
- package/dist/cli/commands/feedback.js +72 -0
- package/dist/cli/commands/feedback.js.map +1 -0
- package/dist/cli/commands/flow-check.d.ts +3 -0
- package/dist/cli/commands/flow-check.d.ts.map +1 -0
- package/dist/cli/commands/flow-check.js +136 -0
- package/dist/cli/commands/flow-check.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +50 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/ledger.d.ts +3 -0
- package/dist/cli/commands/ledger.d.ts.map +1 -0
- package/dist/cli/commands/ledger.js +71 -0
- package/dist/cli/commands/ledger.js.map +1 -0
- package/dist/cli/commands/manifest.d.ts +3 -0
- package/dist/cli/commands/manifest.d.ts.map +1 -0
- package/dist/cli/commands/manifest.js +101 -0
- package/dist/cli/commands/manifest.js.map +1 -0
- package/dist/cli/commands/script-check.d.ts +3 -0
- package/dist/cli/commands/script-check.d.ts.map +1 -0
- package/dist/cli/commands/script-check.js +97 -0
- package/dist/cli/commands/script-check.js.map +1 -0
- package/dist/cli/commands/trace.d.ts +3 -0
- package/dist/cli/commands/trace.d.ts.map +1 -0
- package/dist/cli/commands/trace.js +110 -0
- package/dist/cli/commands/trace.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +22 -9
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +20 -0
- 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/adapter-registry.d.ts +13 -0
- package/dist/generators/test-generator/adapters/adapter-registry.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-registry.js +73 -1
- package/dist/generators/test-generator/adapters/adapter-registry.js.map +1 -1
- package/dist/generators/test-generator/adapters/index.d.ts +1 -1
- package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/index.js +5 -1
- package/dist/generators/test-generator/adapters/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +6 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +6 -2
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts +16 -0
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/capture-patterns.js +54 -0
- package/dist/generators/test-generator/patterns/capture-patterns.js.map +1 -0
- package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/form-patterns.js +3 -1
- package/dist/generators/test-generator/patterns/form-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +2 -0
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +1 -0
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +5 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +17 -0
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +4 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +12 -6
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts +24 -0
- package/dist/harness/audit.d.ts.map +1 -0
- package/dist/harness/audit.js +115 -0
- package/dist/harness/audit.js.map +1 -0
- package/dist/harness/blindspot.d.ts +15 -0
- package/dist/harness/blindspot.d.ts.map +1 -0
- package/dist/harness/blindspot.js +85 -0
- package/dist/harness/blindspot.js.map +1 -0
- package/dist/harness/capability-plan.d.ts +49 -0
- package/dist/harness/capability-plan.d.ts.map +1 -0
- package/dist/harness/capability-plan.js +215 -0
- package/dist/harness/capability-plan.js.map +1 -0
- package/dist/harness/capability.d.ts +23 -0
- package/dist/harness/capability.d.ts.map +1 -0
- package/dist/harness/capability.js +98 -0
- package/dist/harness/capability.js.map +1 -0
- package/dist/harness/catalog/drivers.yaml +57 -0
- package/dist/harness/catalog/universal-viewpoints.yaml +114 -0
- package/dist/harness/challenge.d.ts +21 -0
- package/dist/harness/challenge.d.ts.map +1 -0
- package/dist/harness/challenge.js +151 -0
- package/dist/harness/challenge.js.map +1 -0
- package/dist/harness/feedback.d.ts +29 -0
- package/dist/harness/feedback.d.ts.map +1 -0
- package/dist/harness/feedback.js +106 -0
- package/dist/harness/feedback.js.map +1 -0
- package/dist/harness/flow-check.d.ts +23 -0
- package/dist/harness/flow-check.d.ts.map +1 -0
- package/dist/harness/flow-check.js +132 -0
- package/dist/harness/flow-check.js.map +1 -0
- package/dist/harness/flow-plan.d.ts +23 -0
- package/dist/harness/flow-plan.d.ts.map +1 -0
- package/dist/harness/flow-plan.js +166 -0
- package/dist/harness/flow-plan.js.map +1 -0
- package/dist/harness/intent.d.ts +11 -0
- package/dist/harness/intent.d.ts.map +1 -0
- package/dist/harness/intent.js +86 -0
- package/dist/harness/intent.js.map +1 -0
- package/dist/harness/ledger.d.ts +42 -0
- package/dist/harness/ledger.d.ts.map +1 -0
- package/dist/harness/ledger.js +171 -0
- package/dist/harness/ledger.js.map +1 -0
- package/dist/harness/manifest.d.ts +42 -0
- package/dist/harness/manifest.d.ts.map +1 -0
- package/dist/harness/manifest.js +209 -0
- package/dist/harness/manifest.js.map +1 -0
- package/dist/harness/parse.d.ts +22 -0
- package/dist/harness/parse.d.ts.map +1 -0
- package/dist/harness/parse.js +163 -0
- package/dist/harness/parse.js.map +1 -0
- package/dist/harness/script-check.d.ts +39 -0
- package/dist/harness/script-check.d.ts.map +1 -0
- package/dist/harness/script-check.js +251 -0
- package/dist/harness/script-check.js.map +1 -0
- package/dist/harness/secret-scan.d.ts +8 -0
- package/dist/harness/secret-scan.d.ts.map +1 -0
- package/dist/harness/secret-scan.js +88 -0
- package/dist/harness/secret-scan.js.map +1 -0
- package/dist/harness/sensors.d.ts +88 -0
- package/dist/harness/sensors.d.ts.map +1 -0
- package/dist/harness/sensors.js +232 -0
- package/dist/harness/sensors.js.map +1 -0
- package/dist/harness/trace.d.ts +31 -0
- package/dist/harness/trace.d.ts.map +1 -0
- package/dist/harness/trace.js +173 -0
- package/dist/harness/trace.js.map +1 -0
- package/dist/orchestrator/ai-rules-updater.d.ts +1 -0
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +55 -11
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/figma/spec-figma-renderer.d.ts +2 -2
- package/dist/orchestrator/figma/spec-figma-renderer.js +2 -2
- package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +1 -1
- package/dist/orchestrator/figma/spec-figma-section-renderers.js +1 -1
- package/dist/orchestrator/project-initializer.d.ts +5 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +30 -6
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
- package/dist/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
- package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -4
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
- package/dist/orchestrator/templates/ai-instructions/{github-skill-sungen-figma-source.md → claude-skill-capture-mode-figma-pat.md} +14 -48
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
- package/{src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md → dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
- package/dist/orchestrator/templates/specs-test-data.ts +20 -6
- package/dist/tools/figma/figma-auth.d.ts +5 -2
- package/dist/tools/figma/figma-auth.d.ts.map +1 -1
- package/dist/tools/figma/figma-auth.js +19 -9
- package/dist/tools/figma/figma-auth.js.map +1 -1
- package/docs/orchestration-spec.md +267 -0
- package/package.json +12 -7
- package/src/cli/commands/add.ts +3 -3
- package/src/cli/commands/audit.ts +92 -0
- package/src/cli/commands/blindspot.ts +48 -0
- package/src/cli/commands/capability.ts +160 -0
- package/src/cli/commands/challenge.ts +55 -0
- package/src/cli/commands/feedback.ts +65 -0
- package/src/cli/commands/flow-check.ts +97 -0
- package/src/cli/commands/generate.ts +47 -2
- package/src/cli/commands/ledger.ts +61 -0
- package/src/cli/commands/manifest.ts +55 -0
- package/src/cli/commands/script-check.ts +50 -0
- package/src/cli/commands/trace.ts +60 -0
- package/src/cli/commands/update.ts +30 -10
- package/src/cli/index.ts +20 -0
- package/src/generators/test-generator/adapters/adapter-interface.ts +1 -0
- package/src/generators/test-generator/adapters/adapter-registry.ts +37 -0
- package/src/generators/test-generator/adapters/index.ts +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +6 -0
- package/src/generators/test-generator/code-generator.ts +6 -2
- package/src/generators/test-generator/patterns/capture-patterns.ts +59 -0
- package/src/generators/test-generator/patterns/form-patterns.ts +3 -1
- package/src/generators/test-generator/patterns/index.ts +2 -0
- package/src/generators/test-generator/step-mapper.ts +1 -0
- package/src/generators/test-generator/utils/data-resolver.ts +20 -0
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +8 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +13 -6
- package/src/harness/audit.ts +112 -0
- package/src/harness/blindspot.ts +51 -0
- package/src/harness/capability-plan.ts +180 -0
- package/src/harness/capability.ts +75 -0
- package/src/harness/catalog/drivers.yaml +57 -0
- package/src/harness/catalog/universal-viewpoints.yaml +114 -0
- package/src/harness/challenge.ts +131 -0
- package/src/harness/feedback.ts +84 -0
- package/src/harness/flow-check.ts +99 -0
- package/src/harness/flow-plan.ts +135 -0
- package/src/harness/intent.ts +58 -0
- package/src/harness/ledger.ts +155 -0
- package/src/harness/manifest.ts +173 -0
- package/src/harness/parse.ts +145 -0
- package/src/harness/script-check.ts +222 -0
- package/src/harness/secret-scan.ts +51 -0
- package/src/harness/sensors.ts +279 -0
- package/src/harness/trace.ts +138 -0
- package/src/orchestrator/ai-rules-updater.ts +57 -10
- package/src/orchestrator/figma/spec-figma-renderer.ts +2 -2
- package/src/orchestrator/figma/spec-figma-section-renderers.ts +1 -1
- package/src/orchestrator/project-initializer.ts +33 -7
- package/src/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
- package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
- package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -4
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
- package/{dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md → src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-pat.md} +14 -48
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
- package/{dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md → src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
- package/src/orchestrator/templates/specs-test-data.ts +20 -6
- package/src/tools/figma/figma-auth.ts +20 -9
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
- package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +0 -151
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +0 -151
|
@@ -2,6 +2,9 @@ import { Command } from 'commander';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { CodeGenerator } from '../../generators/test-generator/code-generator';
|
|
5
|
+
import { adapterRegistry } from '../../generators/test-generator/adapters';
|
|
6
|
+
import { scanTestDataSecrets } from '../../harness/secret-scan';
|
|
7
|
+
import { readCapabilities, writeCapabilities, driverMeta, loadDriverCatalog } from '../../harness/capability';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Find .feature files recursively in a directory
|
|
@@ -78,7 +81,7 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
78
81
|
.option('-s, --screen <name>', 'Generate tests for a specific screen')
|
|
79
82
|
.option('--flow <name>', 'Generate tests for a specific flow')
|
|
80
83
|
.option('--all', 'Generate tests for all screens and flows')
|
|
81
|
-
.option('--framework <name>', '
|
|
84
|
+
.option('--framework <name>', 'Override the platform driver (else read from qa/capabilities.yaml)')
|
|
82
85
|
.option('--inline-data', 'Hardcode test data at compile time instead of runtime loading')
|
|
83
86
|
.action(async (options) => {
|
|
84
87
|
try {
|
|
@@ -121,9 +124,33 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
121
124
|
// Output directory
|
|
122
125
|
const outputDir = path.join(process.cwd(), 'specs', 'generated');
|
|
123
126
|
|
|
127
|
+
// Phase 2a — resolve the PLATFORM driver (no silent default to a runtime).
|
|
128
|
+
// Order: --framework → qa/capabilities.yaml → back-compat web (scaffold + notice).
|
|
129
|
+
const cwd = process.cwd();
|
|
130
|
+
let platform = options.framework as string | undefined;
|
|
131
|
+
if (!platform) {
|
|
132
|
+
const cap = readCapabilities(cwd);
|
|
133
|
+
if (cap.platform) {
|
|
134
|
+
platform = cap.platform;
|
|
135
|
+
} else {
|
|
136
|
+
platform = 'web';
|
|
137
|
+
writeCapabilities(cwd, { platform: 'web', enabled: ['web'] });
|
|
138
|
+
console.log('ℹ️ No qa/capabilities.yaml — using platform "web" (Playwright) and scaffolding it.');
|
|
139
|
+
console.log(' Mobile/API projects: see `sungen capability list`.\n');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Ensure the platform adapter is available; load an external driver if needed.
|
|
143
|
+
if (!adapterRegistry.hasAdapter(platform)) {
|
|
144
|
+
const meta = driverMeta(platform);
|
|
145
|
+
if (!meta) {
|
|
146
|
+
throw new Error(`Unknown platform "${platform}". Known: ${Object.keys(loadDriverCatalog()).join(', ')}`);
|
|
147
|
+
}
|
|
148
|
+
adapterRegistry.loadDriver(meta.adapter || platform, meta.package, cwd); // throws DriverNotInstalledError if missing
|
|
149
|
+
}
|
|
150
|
+
|
|
124
151
|
// Create generator and compile
|
|
125
152
|
const generator = new CodeGenerator({
|
|
126
|
-
framework:
|
|
153
|
+
framework: platform,
|
|
127
154
|
screenName: screenName || flowName,
|
|
128
155
|
verbose: program.opts().verbose,
|
|
129
156
|
runtimeData: !options.inlineData,
|
|
@@ -137,6 +164,24 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
137
164
|
);
|
|
138
165
|
|
|
139
166
|
console.log(`\n${results.length} test file(s) generated.`);
|
|
167
|
+
|
|
168
|
+
// Security S0 — warn (never block) if test-data looks to hold a real secret.
|
|
169
|
+
const scanDirs: string[] = [];
|
|
170
|
+
if (screenName) scanDirs.push(path.join(process.cwd(), 'qa', 'screens', screenName));
|
|
171
|
+
else if (flowName) scanDirs.push(path.join(process.cwd(), 'qa', 'flows', flowName));
|
|
172
|
+
else {
|
|
173
|
+
for (const base of ['screens', 'flows']) {
|
|
174
|
+
const d = path.join(process.cwd(), 'qa', base);
|
|
175
|
+
if (fs.existsSync(d)) for (const n of fs.readdirSync(d)) scanDirs.push(path.join(d, n));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const secretHits = scanDirs.flatMap((d) => scanTestDataSecrets(d));
|
|
179
|
+
if (secretHits.length) {
|
|
180
|
+
console.log(`\n⚠️ Possible secret(s) in committed test-data (review — do NOT commit real credentials):`);
|
|
181
|
+
for (const h of secretHits.slice(0, 10)) console.log(` ${h.file}:${h.line} — ${h.reason}`);
|
|
182
|
+
console.log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.`);
|
|
183
|
+
}
|
|
184
|
+
|
|
140
185
|
console.log(`Next step: npx playwright test --ui\n`);
|
|
141
186
|
} catch (error) {
|
|
142
187
|
console.error('Error:', error instanceof Error ? error.message : error);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { recordEvent, buildReport } from '../../harness/ledger';
|
|
3
|
+
|
|
4
|
+
export function registerLedgerCommand(program: Command): void {
|
|
5
|
+
const ledger = program
|
|
6
|
+
.command('ledger')
|
|
7
|
+
.description('Usage ledger: record AI resource per step + report efficiency');
|
|
8
|
+
|
|
9
|
+
ledger
|
|
10
|
+
.command('record')
|
|
11
|
+
.description('Append a step event to the ledger')
|
|
12
|
+
.requiredOption('-s, --screen <name>', 'Screen or flow name')
|
|
13
|
+
.requiredOption('--step <name>', 'Step name (discovery | viewpoint | gherkin | audit | repair:1 ...)')
|
|
14
|
+
.option('--run <id>', 'Run id — groups all phases of one create-test invocation (else auto-segmented by time gap)')
|
|
15
|
+
.option('--model <id>', 'Model id')
|
|
16
|
+
.option('--tokens-in <n>', 'Input tokens', (v) => parseInt(v, 10))
|
|
17
|
+
.option('--tokens-out <n>', 'Output tokens', (v) => parseInt(v, 10))
|
|
18
|
+
.option('--ms <n>', 'Duration in ms', (v) => parseInt(v, 10))
|
|
19
|
+
.option('--note <text>', 'Free note')
|
|
20
|
+
.action((o) => {
|
|
21
|
+
try {
|
|
22
|
+
recordEvent(o.screen, {
|
|
23
|
+
step: o.step, runId: o.run, model: o.model, tokensIn: o.tokensIn, tokensOut: o.tokensOut, ms: o.ms, note: o.note,
|
|
24
|
+
});
|
|
25
|
+
console.log(`✓ ledger: ${o.screen} · ${o.step}`);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
ledger
|
|
33
|
+
.command('report')
|
|
34
|
+
.description('Summarise ledger + efficiency verdicts (pulls audit score if present)')
|
|
35
|
+
.requiredOption('-s, --screen <name>', 'Screen or flow name')
|
|
36
|
+
.option('--all-runs', 'Aggregate ALL runs (default: latest run only)')
|
|
37
|
+
.option('--json', 'Output raw JSON')
|
|
38
|
+
.action((o) => {
|
|
39
|
+
try {
|
|
40
|
+
const r = buildReport(o.screen, { allRuns: o.allRuns });
|
|
41
|
+
if (o.json) { console.log(JSON.stringify(r, null, 2)); return; }
|
|
42
|
+
const scope = r.runScope === 'all' ? `all ${r.runs} runs` : `latest run${r.runs > 1 ? ` of ${r.runs}` : ''}`;
|
|
43
|
+
console.log(`\n━━━ Usage Ledger: ${r.screen} (${r.events} events · ${scope}) ━━━`);
|
|
44
|
+
if (r.runScope === 'latest' && r.runs > 1) console.log(` (${r.runs} runs on file — use --all-runs to aggregate)`);
|
|
45
|
+
console.log(` total tokens: ${r.totalTokens} total time: ${(r.totalMs / 1000).toFixed(1)}s repair rounds: ${r.repairRounds}`);
|
|
46
|
+
if (r.totalTokens) console.log(` repair token share: ${(r.repairTokenPct * 100).toFixed(0)}%`);
|
|
47
|
+
if (r.tokensPerCoveredCritical != null) console.log(` tokens / covered-critical-viewpoint: ${r.tokensPerCoveredCritical}`);
|
|
48
|
+
if (r.tokensPerScenario != null) console.log(` tokens / scenario: ${r.tokensPerScenario}`);
|
|
49
|
+
console.log(' by step:');
|
|
50
|
+
for (const [step, v] of Object.entries(r.byStep)) {
|
|
51
|
+
console.log(` ${step.padEnd(14)} tokens=${v.tokens} ms=${v.ms} events=${v.count}`);
|
|
52
|
+
}
|
|
53
|
+
console.log(' verdict:');
|
|
54
|
+
for (const v of r.verdicts) console.log(` • ${v}`);
|
|
55
|
+
console.log('');
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { buildManifest, diffManifest, loadManifest, saveManifest } from '../../harness/manifest';
|
|
5
|
+
|
|
6
|
+
function findScreenDir(name: string): string | null {
|
|
7
|
+
const screen = path.join(process.cwd(), 'qa', 'screens', name);
|
|
8
|
+
if (fs.existsSync(screen)) return screen;
|
|
9
|
+
const flow = path.join(process.cwd(), 'qa', 'flows', name);
|
|
10
|
+
if (fs.existsSync(flow)) return flow;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerManifestCommand(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('manifest')
|
|
17
|
+
.description('Spec-fingerprint manifest: build a scenario↔spec-section map, or diff to plan keep/regenerate/retire')
|
|
18
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
19
|
+
.option('--diff', 'Compare current spec vs stored manifest → change plan')
|
|
20
|
+
.option('--json', 'Output raw JSON')
|
|
21
|
+
.action((options) => {
|
|
22
|
+
try {
|
|
23
|
+
const name = options.screen;
|
|
24
|
+
if (!name) throw new Error('Provide --screen <name>');
|
|
25
|
+
const dir = findScreenDir(name);
|
|
26
|
+
if (!dir) throw new Error(`Screen/flow not found: ${name}`);
|
|
27
|
+
|
|
28
|
+
if (options.diff) {
|
|
29
|
+
const manifest = loadManifest(name);
|
|
30
|
+
if (!manifest) throw new Error(`No manifest for "${name}". Run \`sungen manifest --screen ${name}\` first.`);
|
|
31
|
+
const plan = diffManifest(dir, name, manifest);
|
|
32
|
+
if (options.json) { console.log(JSON.stringify(plan, null, 2)); process.exit(0); }
|
|
33
|
+
console.log(`\n━━━ Spec-change plan: ${name} ━━━`);
|
|
34
|
+
console.log(` keep=${plan.summary.keep} regenerate=${plan.summary.regenerate} retire=${plan.summary.retire} newSections=${plan.summary.newSections}\n`);
|
|
35
|
+
for (const s of plan.scenarios.filter((x) => x.change !== 'keep')) {
|
|
36
|
+
console.log(` ${s.change === 'regenerate' ? '↻ REGENERATE' : '✗ RETIRE'}: ${s.scenario}`);
|
|
37
|
+
console.log(` ${s.reason}`);
|
|
38
|
+
}
|
|
39
|
+
if (plan.newSections.length) console.log(`\n ✚ NEW spec sections (no scenario yet): ${plan.newSections.join(', ')}`);
|
|
40
|
+
if (plan.summary.regenerate + plan.summary.retire + plan.summary.newSections === 0) console.log(' ✓ Spec unchanged — all scenarios still reflect the spec.');
|
|
41
|
+
console.log('');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const manifest = buildManifest(dir, name);
|
|
46
|
+
const out = saveManifest(manifest);
|
|
47
|
+
if (options.json) { console.log(JSON.stringify(manifest, null, 2)); return; }
|
|
48
|
+
console.log(`\n✓ Manifest built: ${manifest.entries.length} scenarios across ${Object.keys(manifest.specSections).length} spec sections`);
|
|
49
|
+
console.log(` ${path.relative(process.cwd(), out)}\n`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { runScriptCheck } from '../../harness/script-check';
|
|
5
|
+
|
|
6
|
+
export function registerScriptCheckCommand(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('script-check')
|
|
9
|
+
.description('Verify the generated Playwright spec is a faithful 1:1 of the Gherkin feature (no hand-edit / stale drift)')
|
|
10
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
11
|
+
.option('--json', 'Output raw JSON')
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
try {
|
|
14
|
+
const name = options.screen;
|
|
15
|
+
if (!name) throw new Error('Provide --screen <name>');
|
|
16
|
+
const screen = path.join(process.cwd(), 'qa', 'screens', name);
|
|
17
|
+
const flow = path.join(process.cwd(), 'qa', 'flows', name);
|
|
18
|
+
const flowMode = !fs.existsSync(screen) && fs.existsSync(flow);
|
|
19
|
+
const dir = fs.existsSync(screen) ? screen : (fs.existsSync(flow) ? flow : null);
|
|
20
|
+
if (!dir) throw new Error(`Screen/flow not found: ${name}`);
|
|
21
|
+
|
|
22
|
+
const r = await runScriptCheck(dir, name, flowMode);
|
|
23
|
+
|
|
24
|
+
const outDir = path.join(process.cwd(), '.sungen', 'reports');
|
|
25
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(outDir, `${name}-script-check.json`), JSON.stringify(r, null, 2), 'utf-8');
|
|
27
|
+
|
|
28
|
+
if (options.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.status === 'OK' ? 0 : 2); }
|
|
29
|
+
|
|
30
|
+
const L = console.log;
|
|
31
|
+
L('');
|
|
32
|
+
L(`━━━ Script-check: ${name} — Gherkin ↔ Playwright 1:1 ━━━`);
|
|
33
|
+
L('');
|
|
34
|
+
L(` status: ${r.status === 'OK' ? '✓ IN SYNC' : '✗ ' + (r.bypass ? 'BYPASS' : r.drift === 'drift' ? 'DRIFT' : 'MISMATCH')}`);
|
|
35
|
+
L(` scenarios: ${r.automatedScenarios} automated (+${r.manualScenarios} @manual) spec test() blocks: ${r.specTestBlocks} count-match: ${r.countMatch ? '✓' : '✗'}`);
|
|
36
|
+
L(` drift: ${r.drift}`);
|
|
37
|
+
if (r.driftHunks.length) { L(' differing lines (committed vs fresh regenerate):'); for (const h of r.driftHunks) L(h); }
|
|
38
|
+
L('');
|
|
39
|
+
if (r.findings.length) { L(' findings:'); for (const f of r.findings) L(` • ${f}`); }
|
|
40
|
+
else L(' ✓ The test code faithfully reflects the Gherkin (1:1).');
|
|
41
|
+
L('');
|
|
42
|
+
if (r.drift === 'drift') L(' → Fix: re-run `sungen generate --screen ' + name + '` (or /sungen:run-test) so the spec matches the feature. Never hand-edit generated specs.');
|
|
43
|
+
L('');
|
|
44
|
+
process.exit(r.status === 'OK' ? 0 : 2);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { buildTrace } from '../../harness/trace';
|
|
5
|
+
|
|
6
|
+
export function registerTraceCommand(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('trace')
|
|
9
|
+
.description('Visualise the executed test-design process (workflow/skill steps, repair loops), find bottlenecks, and show where to focus human review')
|
|
10
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
11
|
+
.option('--json', 'Output raw JSON')
|
|
12
|
+
.option('--mermaid', 'Print only the Mermaid flowchart')
|
|
13
|
+
.action((options) => {
|
|
14
|
+
try {
|
|
15
|
+
const name = options.screen;
|
|
16
|
+
if (!name) throw new Error('Provide --screen <name>');
|
|
17
|
+
const screen = path.join(process.cwd(), 'qa', 'screens', name);
|
|
18
|
+
const flow = path.join(process.cwd(), 'qa', 'flows', name);
|
|
19
|
+
const dir = fs.existsSync(screen) ? screen : (fs.existsSync(flow) ? flow : null);
|
|
20
|
+
if (!dir) throw new Error(`Screen/flow not found: ${name}`);
|
|
21
|
+
|
|
22
|
+
const r = buildTrace(dir, name);
|
|
23
|
+
if (options.json) { console.log(JSON.stringify(r, null, 2)); return; }
|
|
24
|
+
if (options.mermaid) { console.log(r.mermaid); return; }
|
|
25
|
+
|
|
26
|
+
const L = console.log;
|
|
27
|
+
L('');
|
|
28
|
+
L(`━━━ Process Trace: ${name} ━━━`);
|
|
29
|
+
L('');
|
|
30
|
+
L(` ① Executed process (from ledger${r.runs > 1 ? ` — latest of ${r.runs} runs` : ''})`);
|
|
31
|
+
if (r.ledger.length) {
|
|
32
|
+
for (const e of r.ledger) L(` → ${e.step.padEnd(12)} ${e.ms}ms`);
|
|
33
|
+
L(` repair rounds: ${r.repairRounds} total recorded: ${(r.totalMs / 1000).toFixed(1)}s`);
|
|
34
|
+
} else {
|
|
35
|
+
L(' (ledger empty — process not instrumented this run)');
|
|
36
|
+
}
|
|
37
|
+
if (r.missingSteps.length) L(` ⚠ phases not recorded: ${r.missingSteps.join(', ')}`);
|
|
38
|
+
L('');
|
|
39
|
+
L(' ② Quality signals');
|
|
40
|
+
if (r.audit) L(` audit score=${r.audit.score}/10 gate=${r.audit.gate} weakest=${r.audit.weakest} findings=${r.audit.findings}`);
|
|
41
|
+
else L(' (no audit report — run `sungen audit`)');
|
|
42
|
+
L(` script-check drift: ${r.drift ?? '(not run)'}`);
|
|
43
|
+
L('');
|
|
44
|
+
L(' ③ Bottlenecks / weak points');
|
|
45
|
+
for (const b of r.bottlenecks) L(` • ${b}`);
|
|
46
|
+
L('');
|
|
47
|
+
L(' ④ HUMAN-LOOP FOCUS — where you (the QA) must look');
|
|
48
|
+
for (const h of r.humanFocus) L(` ${h.startsWith(' ') ? h : '• ' + h}`);
|
|
49
|
+
L('');
|
|
50
|
+
L(' ⑤ Visual map (Mermaid — paste into mermaid.live or a Markdown viewer)');
|
|
51
|
+
L(' ```mermaid');
|
|
52
|
+
for (const line of r.mermaid.split('\n')) L(' ' + line);
|
|
53
|
+
L(' ```');
|
|
54
|
+
L('');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -27,22 +27,35 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
27
27
|
program
|
|
28
28
|
.command('update')
|
|
29
29
|
.description(
|
|
30
|
-
'Reinstall @sun-asterisk/sungen
|
|
30
|
+
'Reinstall @sun-asterisk/sungen (stable by default, --beta for prerelease) + refresh AI rules, commands, skills',
|
|
31
31
|
)
|
|
32
32
|
.option('--dry-run', 'Show what would be updated without making changes')
|
|
33
|
+
.option(
|
|
34
|
+
'--beta',
|
|
35
|
+
'Switch to the BETA channel (installs @beta — prerelease builds for testing)',
|
|
36
|
+
false,
|
|
37
|
+
)
|
|
38
|
+
.option(
|
|
39
|
+
'--tag <dist-tag>',
|
|
40
|
+
'Install a specific npm dist-tag (e.g. latest, beta, next). Overrides --beta.',
|
|
41
|
+
)
|
|
33
42
|
.option(
|
|
34
43
|
'--skip-npm-install',
|
|
35
|
-
'Skip the
|
|
44
|
+
'Skip the npm install step (refresh project AI assets only)',
|
|
36
45
|
false,
|
|
37
46
|
)
|
|
38
|
-
.action(async (options: { dryRun?: boolean; skipNpmInstall?: boolean }) => {
|
|
47
|
+
.action(async (options: { dryRun?: boolean; beta?: boolean; tag?: string; skipNpmInstall?: boolean }) => {
|
|
39
48
|
try {
|
|
40
49
|
const skipNpm =
|
|
41
50
|
Boolean(options.skipNpmInstall) || process.env[SKIP_NPM_ENV] === '1';
|
|
42
51
|
|
|
52
|
+
// Channel resolution via npm dist-tags. Default `latest` = official/stable.
|
|
53
|
+
// `--beta` → beta channel; running plain `sungen update` later switches back to stable.
|
|
54
|
+
const channel = options.tag || (options.beta ? 'beta' : 'latest');
|
|
55
|
+
|
|
43
56
|
if (!skipNpm) {
|
|
44
|
-
|
|
45
|
-
printCurrentVersion();
|
|
57
|
+
reinstallSungen(channel);
|
|
58
|
+
printCurrentVersion(channel);
|
|
46
59
|
reExecUpdateForAIAssets(options.dryRun ?? false);
|
|
47
60
|
return;
|
|
48
61
|
}
|
|
@@ -57,22 +70,29 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
57
70
|
});
|
|
58
71
|
}
|
|
59
72
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
const
|
|
73
|
+
function reinstallSungen(channel: string): void {
|
|
74
|
+
const spec = `@sun-asterisk/sungen@${channel}`;
|
|
75
|
+
const label = channel === 'latest' ? 'stable (latest)' : `${channel} channel`;
|
|
76
|
+
console.log(`📦 Installing ${spec} → ${label}...`);
|
|
77
|
+
const result = spawnSync('npm', ['install', '-g', spec], {
|
|
63
78
|
stdio: 'inherit',
|
|
64
79
|
shell: true,
|
|
65
80
|
});
|
|
66
81
|
if (result.status !== 0) {
|
|
67
82
|
throw new Error(
|
|
68
|
-
|
|
83
|
+
`npm install -g ${spec} failed. Run it manually or check your npm setup.`,
|
|
69
84
|
);
|
|
70
85
|
}
|
|
71
86
|
}
|
|
72
87
|
|
|
73
|
-
function printCurrentVersion(): void {
|
|
88
|
+
function printCurrentVersion(channel: string): void {
|
|
74
89
|
console.log('\n🔎 Installed version:');
|
|
75
90
|
spawnSync('sungen', ['--version'], { stdio: 'inherit', shell: true });
|
|
91
|
+
if (channel === 'latest') {
|
|
92
|
+
console.log(' Channel: stable. To try prereleases: sungen update --beta');
|
|
93
|
+
} else {
|
|
94
|
+
console.log(` Channel: ${channel} (prerelease). To return to stable: sungen update`);
|
|
95
|
+
}
|
|
76
96
|
console.log('');
|
|
77
97
|
}
|
|
78
98
|
|
package/src/cli/index.ts
CHANGED
|
@@ -14,6 +14,16 @@ import { registerDeliveryCommand } from './commands/delivery';
|
|
|
14
14
|
import { registerFigmaCommand } from './commands/figma';
|
|
15
15
|
import { registerAddFlowCommand } from './commands/add-flow';
|
|
16
16
|
import { registerDashboardCommand } from './commands/dashboard';
|
|
17
|
+
import { registerAuditCommand } from './commands/audit';
|
|
18
|
+
import { registerManifestCommand } from './commands/manifest';
|
|
19
|
+
import { registerLedgerCommand } from './commands/ledger';
|
|
20
|
+
import { registerFeedbackCommand } from './commands/feedback';
|
|
21
|
+
import { registerScriptCheckCommand } from './commands/script-check';
|
|
22
|
+
import { registerTraceCommand } from './commands/trace';
|
|
23
|
+
import { registerChallengeCommand } from './commands/challenge';
|
|
24
|
+
import { registerBlindspotCommand } from './commands/blindspot';
|
|
25
|
+
import { registerCapabilityCommand } from './commands/capability';
|
|
26
|
+
import { registerFlowCheckCommand } from './commands/flow-check';
|
|
17
27
|
|
|
18
28
|
// Read version from package.json so `--version` never drifts from the released version.
|
|
19
29
|
const { version } = require('../../package.json') as { version: string };
|
|
@@ -40,6 +50,16 @@ async function main() {
|
|
|
40
50
|
registerFigmaCommand(program);
|
|
41
51
|
registerAddFlowCommand(program);
|
|
42
52
|
registerDashboardCommand(program);
|
|
53
|
+
registerAuditCommand(program);
|
|
54
|
+
registerManifestCommand(program);
|
|
55
|
+
registerLedgerCommand(program);
|
|
56
|
+
registerFeedbackCommand(program);
|
|
57
|
+
registerScriptCheckCommand(program);
|
|
58
|
+
registerTraceCommand(program);
|
|
59
|
+
registerChallengeCommand(program);
|
|
60
|
+
registerBlindspotCommand(program);
|
|
61
|
+
registerCapabilityCommand(program);
|
|
62
|
+
registerFlowCheckCommand(program);
|
|
43
63
|
|
|
44
64
|
await program.parseAsync(process.argv);
|
|
45
65
|
}
|
|
@@ -22,6 +22,7 @@ export interface TestFileData {
|
|
|
22
22
|
screenName?: string; // Screen name for TestDataLoader.load()
|
|
23
23
|
featureFileName?: string; // Feature file name for TestDataLoader.load()
|
|
24
24
|
isParallel?: boolean; // @parallel tag: fresh page per test (opt-out from serial default)
|
|
25
|
+
flowMode?: boolean; // flow: cross-screen → longer per-test timeout (bug #243)
|
|
25
26
|
cleanup?: { overlay?: boolean; forms?: boolean; scroll?: boolean; storage?: boolean };
|
|
26
27
|
backgroundSteps?: Array<{ comment?: string; code: string }>; // Raw background steps for serial beforeAll
|
|
27
28
|
scenarios: string[];
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import * as path from 'path';
|
|
1
3
|
import { TestGeneratorAdapter } from './adapter-interface';
|
|
2
4
|
|
|
5
|
+
/** Thrown when a platform/capability driver is selected but its package isn't installed. */
|
|
6
|
+
export class DriverNotInstalledError extends Error {
|
|
7
|
+
constructor(public adapterName: string, public packageName: string) {
|
|
8
|
+
super(
|
|
9
|
+
`No driver for "${adapterName}". Install it for this project:\n` +
|
|
10
|
+
` sungen capability add ${adapterName}\n` +
|
|
11
|
+
` (installs ${packageName}). Or keep affected scenarios @manual.`,
|
|
12
|
+
);
|
|
13
|
+
this.name = 'DriverNotInstalledError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
/**
|
|
4
18
|
* Adapter Registry
|
|
5
19
|
* Manages available test framework adapters
|
|
@@ -50,6 +64,29 @@ export class AdapterRegistry {
|
|
|
50
64
|
getAvailableAdapters(): string[] {
|
|
51
65
|
return Array.from(this.adapters.keys());
|
|
52
66
|
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load an EXTERNAL driver package (resolved from the PROJECT's node_modules) and
|
|
70
|
+
* let it register its adapter. Idempotent — a no-op if already registered.
|
|
71
|
+
* The driver package should export `activate(registry)`; some self-register on import.
|
|
72
|
+
* @throws DriverNotInstalledError when the package isn't installed in the project.
|
|
73
|
+
*/
|
|
74
|
+
loadDriver(adapterName: string, packageName: string, cwd: string = process.cwd()): void {
|
|
75
|
+
const name = adapterName.toLowerCase();
|
|
76
|
+
if (this.adapters.has(name)) return;
|
|
77
|
+
const projectRequire = createRequire(path.join(cwd, 'package.json'));
|
|
78
|
+
let mod: any;
|
|
79
|
+
try {
|
|
80
|
+
mod = projectRequire(packageName);
|
|
81
|
+
} catch {
|
|
82
|
+
throw new DriverNotInstalledError(adapterName, packageName);
|
|
83
|
+
}
|
|
84
|
+
if (typeof mod?.activate === 'function') mod.activate(this);
|
|
85
|
+
if (!this.adapters.has(name)) {
|
|
86
|
+
// Package present but didn't register the expected adapter.
|
|
87
|
+
throw new DriverNotInstalledError(adapterName, packageName);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
53
90
|
}
|
|
54
91
|
|
|
55
92
|
// Singleton instance
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { TestGeneratorAdapter, TestFileData, ScenarioData, StepTemplateData, LocatorExpression } from './adapter-interface';
|
|
2
|
-
export { AdapterRegistry, adapterRegistry } from './adapter-registry';
|
|
2
|
+
export { AdapterRegistry, adapterRegistry, DriverNotInstalledError } from './adapter-registry';
|
|
3
3
|
export { PlaywrightAdapter } from './playwright/playwright-adapter';
|
|
4
4
|
|
|
5
5
|
// Auto-register built-in adapters
|
|
@@ -7,3 +7,6 @@ import { adapterRegistry } from './adapter-registry';
|
|
|
7
7
|
import { PlaywrightAdapter } from './playwright/playwright-adapter';
|
|
8
8
|
|
|
9
9
|
adapterRegistry.register('playwright', () => new PlaywrightAdapter());
|
|
10
|
+
// Phase 2a: platform alias. `web` is the bundled Playwright adapter (back-compat
|
|
11
|
+
// baseline) until it is externalized to @sungen/driver-web in a later cut.
|
|
12
|
+
adapterRegistry.register('web', () => new PlaywrightAdapter());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
testData.set('{{varName}}', ((await {{> locator}}.{{capture}}()) ?? '').trim());
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
const __items_{{stepCounter}} = await {{> locator}}.allInnerTexts();
|
|
3
|
+
expect(__items_{{stepCounter}}.length, 'Expected at least one [{{selectorRef}}]').toBeGreaterThan(0);
|
|
4
|
+
for (const __t_{{stepCounter}} of __items_{{stepCounter}}) {
|
|
5
|
+
expect(__t_{{stepCounter}}).toContain('{{expectedText}}');
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -13,6 +13,9 @@ const testData = TestDataLoader.load('{{screenName}}', '{{featureFileName}}');
|
|
|
13
13
|
|
|
14
14
|
{{#if isParallel}}
|
|
15
15
|
test.describe('{{featureName}}', () => {
|
|
16
|
+
{{#if flowMode}}
|
|
17
|
+
test.describe.configure({ timeout: 90_000 }); // cross-screen flow — allow for multi-page navigation
|
|
18
|
+
{{/if}}
|
|
16
19
|
{{#if singleAuthRole}}
|
|
17
20
|
test.use({ storageState: 'specs/.auth/{{singleAuthRole}}.json' });
|
|
18
21
|
|
|
@@ -65,6 +68,9 @@ test.describe('{{featureName}}', () => {
|
|
|
65
68
|
});
|
|
66
69
|
{{else}}
|
|
67
70
|
test.describe.serial('{{featureName}}', () => {
|
|
71
|
+
{{#if flowMode}}
|
|
72
|
+
test.describe.configure({ timeout: 90_000 }); // cross-screen flow — allow for multi-page navigation
|
|
73
|
+
{{/if}}
|
|
68
74
|
let page: Page;
|
|
69
75
|
let context: BrowserContext;
|
|
70
76
|
|
|
@@ -176,8 +176,11 @@ export class CodeGenerator {
|
|
|
176
176
|
this.options = options;
|
|
177
177
|
this.screenName = options.screenName;
|
|
178
178
|
this.stepMapper = new StepMapper(options);
|
|
179
|
-
// Get adapter from registry (
|
|
180
|
-
|
|
179
|
+
// Get adapter from registry. Platform comes from the caller (resolved from
|
|
180
|
+
// qa/capabilities.yaml); default `web` = the bundled Playwright adapter
|
|
181
|
+
// (back-compat baseline). External platforms (e.g. mobile) are loaded by the
|
|
182
|
+
// command layer before construction; here we only resolve a registered adapter.
|
|
183
|
+
const frameworkName = options.framework || 'web';
|
|
181
184
|
this.adapter = adapterRegistry.getAdapter(frameworkName);
|
|
182
185
|
|
|
183
186
|
if (options.verbose) {
|
|
@@ -514,6 +517,7 @@ export class CodeGenerator {
|
|
|
514
517
|
screenName: isFlowFeature ? `flows/${effectiveScreenName}` : effectiveScreenName,
|
|
515
518
|
featureFileName: featureName,
|
|
516
519
|
isParallel,
|
|
520
|
+
flowMode: isFlowFeature,
|
|
517
521
|
cleanup,
|
|
518
522
|
backgroundSteps,
|
|
519
523
|
scenarios: needsGrouping ? [] : scenarios,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ParsedStep } from '../../gherkin-parser';
|
|
2
|
+
import { StepPattern, StepTemplateData } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Capture & collection patterns (P5) — enable cross-screen data consistency and
|
|
6
|
+
* filter-result correctness that plain single-element assertions can't express.
|
|
7
|
+
*
|
|
8
|
+
* 1. Capture: `User remember [Product Name] text as {{selected_product_name}}`
|
|
9
|
+
* → stores the element's text/value into a runtime variable so a
|
|
10
|
+
* later step (on another screen) can assert against it.
|
|
11
|
+
* REQUIRES runtime data mode (default) — emits `testData.set(...)`.
|
|
12
|
+
*
|
|
13
|
+
* 2. List: `User see all [Product Name] contain {{selected_category}}`
|
|
14
|
+
* → asserts EVERY matching element's text contains the value
|
|
15
|
+
* (e.g. all products belong to the selected category/brand).
|
|
16
|
+
*/
|
|
17
|
+
export const capturePatterns: StepPattern[] = [
|
|
18
|
+
{
|
|
19
|
+
name: 'capture-variable',
|
|
20
|
+
matcher: (step: ParsedStep) =>
|
|
21
|
+
/\bremember\b/i.test(step.text) && /\bas\b/i.test(step.text) && !!step.selectorRef && !!step.dataRef,
|
|
22
|
+
resolver: (step, context): StepTemplateData => {
|
|
23
|
+
const resolved = context.selectorResolver.resolveSelector(
|
|
24
|
+
step.selectorRef!, undefined, step.elementType, step.nth,
|
|
25
|
+
);
|
|
26
|
+
const varName = step.dataRef!;
|
|
27
|
+
const isValue = /\bvalue\b/i.test(step.text);
|
|
28
|
+
// Register so later `{{varName}}` references resolve to testData.get(varName)
|
|
29
|
+
// and skip YAML validation.
|
|
30
|
+
context.dataResolver.registerCaptured(varName);
|
|
31
|
+
return {
|
|
32
|
+
templateName: 'capture-variable',
|
|
33
|
+
data: { ...resolved, varName, capture: isValue ? 'inputValue' : 'textContent' },
|
|
34
|
+
comment: `Remember ${step.selectorRef} ${isValue ? 'value' : 'text'} as ${varName}`,
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
priority: 35,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'all-contain-assertion',
|
|
41
|
+
matcher: (step: ParsedStep) =>
|
|
42
|
+
/\b(see|sees)\b/i.test(step.text) &&
|
|
43
|
+
/\ball\b/i.test(step.text) &&
|
|
44
|
+
/(contain|contains|match|matches|belong)/i.test(step.text) &&
|
|
45
|
+
!!step.selectorRef && !!(step.value || step.dataRef),
|
|
46
|
+
resolver: (step, context): StepTemplateData => {
|
|
47
|
+
const resolved = context.selectorResolver.resolveSelector(
|
|
48
|
+
step.selectorRef!, undefined, step.elementType, step.nth,
|
|
49
|
+
);
|
|
50
|
+
const expectedText = step.value || context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
51
|
+
return {
|
|
52
|
+
templateName: 'all-contain-assertion',
|
|
53
|
+
data: { ...resolved, expectedText, selectorRef: step.selectorRef, stepCounter: context.stepCounter },
|
|
54
|
+
comment: `Assert all ${step.selectorRef} contain "${step.value || step.dataRef}"`,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
priority: 34,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
@@ -129,7 +129,9 @@ export const formPatterns: StepPattern[] = [
|
|
|
129
129
|
data = { ...resolved, selectorRef: step.selectorRef };
|
|
130
130
|
} else {
|
|
131
131
|
const nativeSelectRoles = ['combobox', 'listbox', 'select'];
|
|
132
|
-
|
|
132
|
+
// A native <select> is detected by its ARIA role, or by an explicit `variant: native`
|
|
133
|
+
// on the selector entry (lets a CSS/#id-located select opt into .selectOption()).
|
|
134
|
+
const isNativeSelect = nativeSelectRoles.includes(resolved.role) || resolved.variant === 'native';
|
|
133
135
|
|
|
134
136
|
if (isNativeSelect) {
|
|
135
137
|
templateName = 'select-action';
|
|
@@ -10,6 +10,7 @@ import { keyboardPatterns } from './keyboard-patterns';
|
|
|
10
10
|
import { scrollPatterns } from './scroll-patterns';
|
|
11
11
|
import { scopePatterns } from './scope-patterns';
|
|
12
12
|
import { tablePatterns } from './table-patterns';
|
|
13
|
+
import { capturePatterns } from './capture-patterns';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Pattern Registry - manages all step patterns
|
|
@@ -34,6 +35,7 @@ export class PatternRegistry {
|
|
|
34
35
|
this.patterns.push(...scrollPatterns);
|
|
35
36
|
this.patterns.push(...scopePatterns);
|
|
36
37
|
this.patterns.push(...tablePatterns);
|
|
38
|
+
this.patterns.push(...capturePatterns);
|
|
37
39
|
|
|
38
40
|
// Sort by priority (higher first)
|
|
39
41
|
this.patterns.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|