@sun-asterisk/sungen 2.7.0-beta.0 → 3.0.0-beta.71
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/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/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +22 -0
- 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 +16 -0
- package/dist/cli/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/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/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/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/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/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 +16 -0
- package/dist/harness/script-check.d.ts.map +1 -0
- package/dist/harness/script-check.js +169 -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 +26 -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 +45 -13
- 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 +8 -3
- 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-selector-fix.md +57 -11
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +41 -31
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +53 -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 +27 -11
- 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 +6 -3
- 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 +62 -16
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +41 -31
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
- package/dist/orchestrator/templates/qa-context.md +90 -0
- package/dist/orchestrator/templates/readme.md +16 -13
- package/dist/orchestrator/templates/specs-test-data.ts +9 -0
- 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 +10 -6
- 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/challenge.ts +55 -0
- package/src/cli/commands/feedback.ts +65 -0
- package/src/cli/commands/generate.ts +19 -0
- 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 +16 -0
- 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/patterns/capture-patterns.ts +59 -0
- 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/harness/audit.ts +112 -0
- package/src/harness/blindspot.ts +51 -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/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 +149 -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 +30 -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 +45 -13
- 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 +8 -3
- 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-selector-fix.md +57 -11
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +41 -31
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +53 -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 +27 -11
- 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 +6 -3
- 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 +62 -16
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +41 -31
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
- package/src/orchestrator/templates/qa-context.md +90 -0
- package/src/orchestrator/templates/readme.md +16 -13
- package/src/orchestrator/templates/specs-test-data.ts +9 -0
- 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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { buildChallenge, renderChallengeMarkdown } from '../../harness/challenge';
|
|
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 registerChallengeCommand(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('challenge')
|
|
17
|
+
.description('Exploration mode (Loop 2): attack the existing suite for blind spots — advisory, never auto-merges')
|
|
18
|
+
.requiredOption('-s, --screen <name>', 'Screen or flow name')
|
|
19
|
+
.option('--json', 'Output the raw JSON report')
|
|
20
|
+
.action((o) => {
|
|
21
|
+
try {
|
|
22
|
+
const dir = findScreenDir(o.screen);
|
|
23
|
+
if (!dir) throw new Error(`Screen/flow not found: qa/screens/${o.screen} or qa/flows/${o.screen}`);
|
|
24
|
+
const report = buildChallenge(dir, o.screen);
|
|
25
|
+
|
|
26
|
+
const outDir = path.join(process.cwd(), '.sungen', 'reports');
|
|
27
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
28
|
+
const md = renderChallengeMarkdown(report);
|
|
29
|
+
const outPath = path.join(outDir, `${o.screen}-challenge.md`);
|
|
30
|
+
fs.writeFileSync(outPath, md, 'utf-8');
|
|
31
|
+
|
|
32
|
+
if (o.json) { console.log(JSON.stringify(report, null, 2)); return; }
|
|
33
|
+
|
|
34
|
+
const L = console.log;
|
|
35
|
+
L(`\n━━━ Challenge (exploration mode): ${report.screen} ━━━`);
|
|
36
|
+
L(' Advisory — does NOT change the official suite; surfaces what production missed.\n');
|
|
37
|
+
L(' ① Depth — title claims a collection but asserts a single element');
|
|
38
|
+
if (report.collectionClaimSingular.length) {
|
|
39
|
+
for (const f of report.collectionClaimSingular) L(` ⚠ ${f.scenario}\n → ${f.suggestion}`);
|
|
40
|
+
} else L(' ✓ none');
|
|
41
|
+
L(' ② Coverage — over-covered / shallow');
|
|
42
|
+
if (report.overCovered.length) for (const o2 of report.overCovered) L(` • ${o2.bucket}: ${o2.note}`);
|
|
43
|
+
if (report.shallowThemes.length) L(` • shallow themes: ${report.shallowThemes.join(', ')}`);
|
|
44
|
+
if (!report.overCovered.length && !report.shallowThemes.length) L(' ✓ balanced');
|
|
45
|
+
L(' ③ Novelty — prompts for the `sungen-challenge` agent (≤20% of official, no auto-merge)');
|
|
46
|
+
for (const p of report.noveltyPrompts) L(` • ${p}`);
|
|
47
|
+
L(' ── Exploration readiness ──');
|
|
48
|
+
for (const e of report.explorationReadiness) L(` • ${e}`);
|
|
49
|
+
L(`\n Report: ${path.relative(process.cwd(), outPath)}\n`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { recordFeedback, summarize, FeedbackType, FeedbackDecision } from '../../harness/feedback';
|
|
3
|
+
|
|
4
|
+
const TYPES = ['test-design', 'product', 'other'];
|
|
5
|
+
const DECISIONS = ['accept', 'reject', 'edit', 'add', 'none'];
|
|
6
|
+
|
|
7
|
+
export function registerFeedbackCommand(program: Command): void {
|
|
8
|
+
const fb = program
|
|
9
|
+
.command('feedback')
|
|
10
|
+
.description('Local-first QA feedback (test-design knowledge + product telemetry). Synced later when a server exists.');
|
|
11
|
+
|
|
12
|
+
fb
|
|
13
|
+
.command('record')
|
|
14
|
+
.description('Record a feedback entry')
|
|
15
|
+
.requiredOption('--message <text>', 'The feedback')
|
|
16
|
+
.option('--type <t>', `Feedback type (${TYPES.join('|')})`, 'test-design')
|
|
17
|
+
.option('-s, --screen <name>', 'Screen/flow the feedback relates to')
|
|
18
|
+
.option('--target <ref>', 'What it is about (viewpoint id / scenario / command / artifact)')
|
|
19
|
+
.option('--decision <d>', `QA decision (${DECISIONS.join('|')})`, 'none')
|
|
20
|
+
.option('--reason <text>', 'Why')
|
|
21
|
+
.option('--source <who>', 'Who gave the feedback', 'qa')
|
|
22
|
+
.action((o) => {
|
|
23
|
+
try {
|
|
24
|
+
if (!TYPES.includes(o.type)) throw new Error(`--type must be one of ${TYPES.join(', ')}`);
|
|
25
|
+
if (!DECISIONS.includes(o.decision)) throw new Error(`--decision must be one of ${DECISIONS.join(', ')}`);
|
|
26
|
+
const p = recordFeedback({
|
|
27
|
+
type: o.type as FeedbackType,
|
|
28
|
+
screen: o.screen,
|
|
29
|
+
target: o.target,
|
|
30
|
+
decision: o.decision as FeedbackDecision,
|
|
31
|
+
message: o.message,
|
|
32
|
+
reason: o.reason,
|
|
33
|
+
source: o.source,
|
|
34
|
+
});
|
|
35
|
+
console.log(`✓ feedback recorded (${o.type}${o.screen ? ' · ' + o.screen : ''}) → ${p}`);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
fb
|
|
43
|
+
.command('list')
|
|
44
|
+
.description('List / summarise recorded feedback')
|
|
45
|
+
.option('-s, --screen <name>', 'Filter by screen')
|
|
46
|
+
.option('--type <t>', 'Filter by type')
|
|
47
|
+
.option('--json', 'Output raw JSON')
|
|
48
|
+
.action((o) => {
|
|
49
|
+
try {
|
|
50
|
+
const s = summarize({ screen: o.screen, type: o.type });
|
|
51
|
+
if (o.json) { console.log(JSON.stringify(s, null, 2)); return; }
|
|
52
|
+
console.log(`\n━━━ Feedback (${s.total}) ━━━`);
|
|
53
|
+
console.log(` by type: ${Object.entries(s.byType).map(([k, v]) => `${k}=${v}`).join(' ') || '—'}`);
|
|
54
|
+
console.log(` by decision: ${Object.entries(s.byDecision).map(([k, v]) => `${k}=${v}`).join(' ') || '—'}\n`);
|
|
55
|
+
for (const e of s.entries.slice(-20)) {
|
|
56
|
+
console.log(` [${e.type}${e.decision && e.decision !== 'none' ? '/' + e.decision : ''}] ${e.screen ? e.screen + ' · ' : ''}${e.target ? e.target + ' — ' : ''}${e.message}`);
|
|
57
|
+
if (e.reason) console.log(` reason: ${e.reason}`);
|
|
58
|
+
}
|
|
59
|
+
console.log('');
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -2,6 +2,7 @@ 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 { scanTestDataSecrets } from '../../harness/secret-scan';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Find .feature files recursively in a directory
|
|
@@ -137,6 +138,24 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
137
138
|
);
|
|
138
139
|
|
|
139
140
|
console.log(`\n${results.length} test file(s) generated.`);
|
|
141
|
+
|
|
142
|
+
// Security S0 — warn (never block) if test-data looks to hold a real secret.
|
|
143
|
+
const scanDirs: string[] = [];
|
|
144
|
+
if (screenName) scanDirs.push(path.join(process.cwd(), 'qa', 'screens', screenName));
|
|
145
|
+
else if (flowName) scanDirs.push(path.join(process.cwd(), 'qa', 'flows', flowName));
|
|
146
|
+
else {
|
|
147
|
+
for (const base of ['screens', 'flows']) {
|
|
148
|
+
const d = path.join(process.cwd(), 'qa', base);
|
|
149
|
+
if (fs.existsSync(d)) for (const n of fs.readdirSync(d)) scanDirs.push(path.join(d, n));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const secretHits = scanDirs.flatMap((d) => scanTestDataSecrets(d));
|
|
153
|
+
if (secretHits.length) {
|
|
154
|
+
console.log(`\n⚠️ Possible secret(s) in committed test-data (review — do NOT commit real credentials):`);
|
|
155
|
+
for (const h of secretHits.slice(0, 10)) console.log(` ${h.file}:${h.line} — ${h.reason}`);
|
|
156
|
+
console.log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.`);
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
console.log(`Next step: npx playwright test --ui\n`);
|
|
141
160
|
} catch (error) {
|
|
142
161
|
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.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,14 @@ 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';
|
|
17
25
|
|
|
18
26
|
// Read version from package.json so `--version` never drifts from the released version.
|
|
19
27
|
const { version } = require('../../package.json') as { version: string };
|
|
@@ -40,6 +48,14 @@ async function main() {
|
|
|
40
48
|
registerFigmaCommand(program);
|
|
41
49
|
registerAddFlowCommand(program);
|
|
42
50
|
registerDashboardCommand(program);
|
|
51
|
+
registerAuditCommand(program);
|
|
52
|
+
registerManifestCommand(program);
|
|
53
|
+
registerLedgerCommand(program);
|
|
54
|
+
registerFeedbackCommand(program);
|
|
55
|
+
registerScriptCheckCommand(program);
|
|
56
|
+
registerTraceCommand(program);
|
|
57
|
+
registerChallengeCommand(program);
|
|
58
|
+
registerBlindspotCommand(program);
|
|
43
59
|
|
|
44
60
|
await program.parseAsync(process.argv);
|
|
45
61
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
];
|
|
@@ -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));
|
|
@@ -14,6 +14,20 @@ export class DataResolver {
|
|
|
14
14
|
private screenName?: string;
|
|
15
15
|
private runtimeMode: boolean;
|
|
16
16
|
private flowMode: boolean = false;
|
|
17
|
+
// Vars captured at runtime via `User remember [X] as {{var}}`. References to
|
|
18
|
+
// these skip YAML validation (they don't exist in test-data.yaml) and resolve
|
|
19
|
+
// to testData.get('var') — populated by the capture step's testData.set('var', …).
|
|
20
|
+
private capturedVars = new Set<string>();
|
|
21
|
+
|
|
22
|
+
/** Register a variable name produced by a capture step (scenario-scoped). */
|
|
23
|
+
registerCaptured(name: string): void {
|
|
24
|
+
this.capturedVars.add(name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Clear captured vars — call at the start of each scenario. */
|
|
28
|
+
clearCaptured(): void {
|
|
29
|
+
this.capturedVars.clear();
|
|
30
|
+
}
|
|
17
31
|
|
|
18
32
|
constructor(testDataDir?: string, screenName?: string, runtimeMode: boolean = false) {
|
|
19
33
|
this.testDataDir = testDataDir || path.join(process.cwd(), 'qa', 'test-data');
|
|
@@ -36,6 +50,12 @@ export class DataResolver {
|
|
|
36
50
|
* @returns The resolved value
|
|
37
51
|
*/
|
|
38
52
|
resolveData(dataRef: string, featureName?: string): string {
|
|
53
|
+
// Captured vars exist only at runtime → emit a marker (→ testData.get) and
|
|
54
|
+
// skip YAML validation. Requires runtime mode (capture writes testData.set).
|
|
55
|
+
if (this.capturedVars.has(dataRef)) {
|
|
56
|
+
return DataResolver.encodeMarker(dataRef);
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
if (this.runtimeMode) {
|
|
40
60
|
this.validateDataRef(dataRef, featureName);
|
|
41
61
|
return DataResolver.encodeMarker(dataRef);
|