@sun-asterisk/sungen 3.2.11 → 3.2.12-beta.10
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/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +5 -2
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +44 -2
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +7 -2
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +15 -2
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +10 -5
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/result-variants.d.ts +23 -5
- package/dist/exporters/result-variants.d.ts.map +1 -1
- package/dist/exporters/result-variants.js +65 -9
- package/dist/exporters/result-variants.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/harness/capability.d.ts +1 -0
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +2 -1
- package/dist/orchestrator/templates/ai-src/commands/create-data-test.md +111 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +3 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -0
- package/dist/orchestrator/templates/ai-src/config/claude.md +3 -1
- package/dist/orchestrator/templates/ai-src/config/copilot.md +3 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +213 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
- package/package.json +3 -2
- package/src/capabilities/discover.ts +5 -2
- package/src/cli/commands/capability.ts +40 -2
- package/src/dashboard/templates/index.html +1 -1
- package/src/exporters/csv-exporter.ts +7 -3
- package/src/exporters/feature-parser.ts +15 -2
- package/src/exporters/json-exporter.ts +10 -6
- package/src/exporters/result-variants.ts +68 -9
- package/src/exporters/test-data-resolver.ts +1 -0
- package/src/harness/capability.ts +1 -0
- package/src/harness/catalog/drivers.yaml +2 -1
- package/src/orchestrator/templates/ai-src/commands/create-data-test.md +111 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +3 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -0
- package/src/orchestrator/templates/ai-src/config/claude.md +3 -1
- package/src/orchestrator/templates/ai-src/config/copilot.md +3 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +213 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-data-test
|
|
3
|
+
description: "Generate standardized test-data for a screen/flow from the Data Factory catalog — valid/boundary/invalid values with CHK-* traceability (no free-form guessing)"
|
|
4
|
+
argument-hint: [screen-or-flow-name]
|
|
5
|
+
order: 25
|
|
6
|
+
claude-tools: "Read, Grep, Bash, Glob, Write, Edit, AskUserQuestion, Skill"
|
|
7
|
+
copilot-tools: "[vscode, execute, read, agent, edit, search, todo]"
|
|
8
|
+
codex-trigger: "Run when the user asks to CREATE, generate, or standardize TEST DATA / test values for a screen or flow (valid/boundary/invalid, boundary values, negative data). Uses the Data Factory driver. Not for generating the .feature scenarios themselves (that is create-test)."
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**Input**: A screen or flow name (e.g., `/sungen:create-data-test login`), OR **no name** to do every unit (with a confirmation — see ALL mode). Load the `sungen-data-factory` skill for the full method.
|
|
12
|
+
|
|
13
|
+
> **When to use.** `/sungen:create-test` already standardizes field test-data via Data Factory **inline** (its step 5.3), so you normally do **not** need this command as a follow-up. Use it **on demand** to *re-standardize*: after you edit a field-map or the `.overwrite`, for a unit authored before Data Factory existed, to refresh error-code mappings, or to (re)generate across many units at once (ALL mode). It MERGES into existing test-data (never clobbers).
|
|
14
|
+
|
|
15
|
+
## Role
|
|
16
|
+
|
|
17
|
+
You are a **Senior QA Engineer** producing **test data to a standard**, not by guessing. You do NOT invent values field-by-field. You follow the 4-source method (spec rules → field-type catalog → design technique → DB mapping), driven by the **Data Factory** capability driver. Values come from the catalog (`common/` + the project's `.overwrite`), each tagged with a `CHK-*` id that traces back to the spec. Where the catalog cannot cover a project-specific field, you **ask the QA** — you never fabricate domain data.
|
|
18
|
+
|
|
19
|
+
## Preconditions
|
|
20
|
+
|
|
21
|
+
1. **Availability** — Data Factory is **bundled with core** (no `capability add` needed); its CLI (`sungen data …`) is available out of the box. Just continue.
|
|
22
|
+
2. Determine mode from `$ARGUMENTS`:
|
|
23
|
+
- A **name** given → single-unit mode (that screen/flow only).
|
|
24
|
+
- **No name** → **ALL mode** (see below — always confirm first).
|
|
25
|
+
|
|
26
|
+
## ALL mode (no name) — confirm before doing everything
|
|
27
|
+
|
|
28
|
+
If no name was given, the user wants **all** screens/flows — but this is wide, and a missing name is often a typo (they meant `create-data-test home`). So:
|
|
29
|
+
|
|
30
|
+
1. List every unit under `qa/screens/*` and `qa/flows/*`, and classify each as **new** (no `test-data/<name>.yaml` yet) vs **would-overwrite**.
|
|
31
|
+
2. Show the list and use `AskUserQuestion` to confirm: *"Generate test-data for ALL N units? (X new · Y would overwrite)"* — default to **No**.
|
|
32
|
+
3. Only proceed when the user confirms. Then run the per-unit flow below for each (the CLI `sungen data gen --all` also enforces this confirm; `--yes` after the user agrees).
|
|
33
|
+
|
|
34
|
+
## Per-unit flow (single unit, or each unit in ALL mode)
|
|
35
|
+
|
|
36
|
+
Auto-detect: `qa/flows/<name>/` → flow; else `qa/screens/<name>/` → screen. (API units carry their contract in `apis.yaml` — data there is `@cases` in create-test, not this command.)
|
|
37
|
+
|
|
38
|
+
### 1. Read the sources (Nguồn 1 + 4)
|
|
39
|
+
|
|
40
|
+
Read `qa/<screens|flows>/<name>/requirements/spec.md` and `test-viewpoint.md`. For **each input field / parameter**, extract:
|
|
41
|
+
- data **type** (map to a catalog field-type: email · password · phone · string · number-integer · number-float · currency · postcode · katakana …),
|
|
42
|
+
- `required`, format, **min/max** length or value,
|
|
43
|
+
- the real **error/message code** the spec shows when a rule is violated,
|
|
44
|
+
- any **DB mapping** (unique, cross-entity dependency) noted in the spec.
|
|
45
|
+
|
|
46
|
+
Viewpoint tells you which fields are high-priority → give them the fullest data.
|
|
47
|
+
|
|
48
|
+
**No input fields → nothing to generate.** If the unit is a pure navigation / listing / capture-compare flow (e.g. a home-shopping browse flow) with **no user-input fields or parameters**, Data Factory has nothing to standardize. Report *"`<name>`: no input fields → nothing to generate"* and stop for this unit — do **not** write an empty `qa/data-factory/<name>.fields.yaml`. (Its scenarios are covered by create-test's data, not by field-level test data.)
|
|
49
|
+
|
|
50
|
+
### 2. Write the field-map — `qa/data-factory/<name>.fields.yaml`
|
|
51
|
+
|
|
52
|
+
This is the input the deterministic generator expands. One entry per field:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
screen: <name> # or: flow: <name>
|
|
56
|
+
fields:
|
|
57
|
+
- name: email
|
|
58
|
+
type: email
|
|
59
|
+
required: true
|
|
60
|
+
unique: true # fresh-per-run (register email won't collide)
|
|
61
|
+
constraints: { maxLocal: 64, maxTotal: 254 } # project's real limits
|
|
62
|
+
errorMap: { M13: E1042, M02: E1001 } # catalog placeholder → project's real code
|
|
63
|
+
- name: password
|
|
64
|
+
type: password
|
|
65
|
+
constraints: { minLength: 8, maxLength: 32 }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**`unique: true`** — set it for a field that must be **different every run** (register email/username that can't clash with a record a previous run created). The valid value then gets a runtime `{{$timestamp}}` (email → `user+{{$timestamp}}@…`), so each run is fresh — no cross-run cache, no order-dependent flakiness. Boundary/invalid values stay literal (they test rules, not record creation).
|
|
69
|
+
|
|
70
|
+
Map every placeholder error code you can (`errorMap`) to the spec's real code. Leave a code unmapped only if the spec truly has none — the lint will flag it as an open point to confirm with BA/Dev.
|
|
71
|
+
|
|
72
|
+
**Complex forms — nested & dependent fields.** For a grouped/object field, give it **`fields:`** (sub-fields, expanded recursively; no `type` on the group). For a field whose rule depends on another, add **`dependsOn: { field: <other>, value: <v> }`** — it's recorded as a note so the scenario/`@cases` honor it (the generator doesn't resolve the runtime condition):
|
|
73
|
+
```yaml
|
|
74
|
+
fields:
|
|
75
|
+
- name: address # group → nested object
|
|
76
|
+
fields:
|
|
77
|
+
- { name: city, type: string, constraints: { maxLength: 50 } }
|
|
78
|
+
- { name: postcode, type: postcode, dependsOn: { field: country, value: JP } }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Blind spots — ask, do not invent
|
|
82
|
+
|
|
83
|
+
If a field's type is **not** in the catalog (a business enum, a cross-entity constraint, a project data model), **stop and ask the QA** with `AskUserQuestion` for: (1) the valid set, (2) invalid / non-existent values, (3) the error code when wrong, (4) dependencies on other fields. Record the answer in `qa/data-factory/project/<entity>.yaml` for reuse. Never fabricate domain values.
|
|
84
|
+
|
|
85
|
+
### 4. Lint the coverage
|
|
86
|
+
|
|
87
|
+
Run (local-first): `[ -x ./bin/sungen.js ] && ./bin/sungen.js data lint --screen <name> || npx sungen data lint --screen <name>`. Fix what it reports: add missing boundary values, resolve `UNRESOLVED` items (supply the constraint), map `OPEN_ERRORREF` codes. The bar is the phuong-phap §6 checklist — every required field has valid + boundary + invalid, one invalid per rule.
|
|
88
|
+
|
|
89
|
+
### 5. Generate the test-data
|
|
90
|
+
|
|
91
|
+
Run (local-first): `[ -x ./bin/sungen.js ] && ./bin/sungen.js data gen --screen <name> || npx sungen data gen --screen <name>`. If `test-data/<name>.yaml` already exists it **merges** the standardized field data in — it **replaces only the field keys it owns and preserves every other key** (create-test's data, flow-namespaced keys, `@cases` datasets, overrides). It asks before merging (in ALL mode after they confirmed, add `--yes`; `--force` skips the prompt). The output is standardized `valid/boundary/invalid` values with `CHK-*` traceability.
|
|
92
|
+
|
|
93
|
+
### 5.5 Validate semantic correctness
|
|
94
|
+
|
|
95
|
+
Run (local-first): `[ -x ./bin/sungen.js ] && ./bin/sungen.js data validate --screen <name> || npx sungen data validate --screen <name>`. This is stronger than lint (coverage): it checks every generated value is **semantically** correct — each `valid`/`boundary` value actually satisfies the constraints, and each synthesized `invalid` value actually violates them (a value in the `valid` set that breaks `maxLength`, or a "below min" case that landed inside the range, is a `VALUE_NOT_VALID` error). Fix any `error`; `INVALID_LOOKS_VALID` warns are a prompt to confirm the value is invalid for a non-length reason (charset/format). Each generated value also carries an `expected: valid|invalid` field in the test-data for traceability.
|
|
96
|
+
|
|
97
|
+
### 5.6 Domain / stateful preconditions (spec + viewpoint only — no DB/API needed)
|
|
98
|
+
|
|
99
|
+
If the spec describes a **stateful precondition** — an entity with relationships or a business state the test needs *before* it runs (e.g. "a paid order of user A", "a valid unused reset token ≤24h", "the session-storage buffer holds the entered values") — **auto-draft it: run `… data state --scaffold <name>`** to generate a recipe skeleton from the spec's precondition sentences (one resource per signal, provenance guessed, entity as `TODO`). Then **fill each `entity`/`desiredState`/`bindings` and confirm the domain specifics with the QA** (`AskUserQuestion`) — never invent domain values. (You can also hand-author the `resources:` graph in `qa/data-factory/recipes.yaml`.) Provenance is **not only api/db** — pick `prior-flow` / `fixture` / `client-storage` / `server-context` / `manual` (use `api`/`db` only when a datasource exists). Then run `data state --recipe <name>`: it validates the graph and renders a standardized **manual precondition** block (`@manual:data-setup` when it can't be auto-provisioned) to weave into the scenario's `Background`. This is the domain/stateful improvement that works with **only spec + viewpoint** — the testcase states the required state precisely instead of a vague note; it auto-provisions later if an api/db datasource is added (no rewrite). For a **journey where the same data flows across screens** (setup → confirmation → complete), declare a `shared: { entity, screens: [...] }` so one dataset is used across all of them (the Cross-artifact Gate then asserts they match).
|
|
100
|
+
|
|
101
|
+
### 5.7 Cross-artifact check (testcase ↔ data agree)
|
|
102
|
+
|
|
103
|
+
Run (local-first): `… data crosscheck --screen <name>`. It verifies the testcase and its test-data agree: no dangling `{{var}}` (every referenced var has a value), any cross-screen **shared** dataset is identical across the journey, and a declared state-precondition is surfaced in the feature. Fix any `error`. This is the `data-only` harness profile — `data validate` (values correct) + `data crosscheck` (values match the test); together they are the deterministic Data Gate + Cross-artifact Gate.
|
|
104
|
+
|
|
105
|
+
### 6. Weave into scenarios (if create-test already ran)
|
|
106
|
+
|
|
107
|
+
If the `.feature` exists, turn the generated invalid/boundary sets into data-driven `Scenario Outline` + `Examples` (`@cases`) referencing the values — following `sungen-gherkin-syntax`. Weave any `data state` precondition block into the `Background`. Otherwise leave the standardized `test-data/<name>.yaml` for `/sungen:run-test` to consume.
|
|
108
|
+
|
|
109
|
+
## After running
|
|
110
|
+
|
|
111
|
+
Use `AskUserQuestion` to offer the next step: review the generated data, run `/sungen:create-test <name>` (if scenarios don't exist yet), or `/sungen:run-test <name>`.
|
|
@@ -167,6 +167,8 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
167
167
|
|
|
168
168
|
**5d. Sequential fallback.** Use the single-context incremental path (Step 2: tier-by-tier `Write`/`Edit` batches) when: only **one** shard applies, **Copilot / no sub-agents**, or a constrained setup. Same output, just no speedup. **For flows**: `[Screen:Element]` namespace refs, test-data namespaced by phase, `@flow` tag.
|
|
169
169
|
|
|
170
|
+
5.3. **Standardize field test-data — Data Factory (when the unit has input fields).** Don't hand-invent per-field values — make them **standard**. For each input field, author the field-map `qa/data-factory/<name>.fields.yaml` (field → catalog `type` + the spec's real `constraints` + `errorMap` placeholder→real code), then run (Bash, local-first) `[ -x ./bin/sungen.js ] && ./bin/sungen.js data lint --screen <name> || npx sungen data lint --screen <name>` and `… data gen --screen <name>`, per the **`sungen-data-factory`** skill. `data gen` **MERGES** into `test-data.yaml` (replaces only its field keys, preserves your scenario/namespaced data), so it is safe to run here. Weave the standardized **boundary/invalid** sets into `@cases` (`Scenario Outline` + `Examples`) with `CHK-*` trace; keep the map's `errorMap`/`constraints` aligned to the spec. Then run `… data validate --screen <name>` (values are semantically correct) and — once the `.feature` exists — `… data crosscheck --screen <name>` (testcase ↔ data agree: no dangling `{{var}}`, shared datasets identical); fix any `error`. If the spec has a **stateful precondition** (an entity/state the test needs first), run `… data state --scaffold <name>` to auto-draft a recipe skeleton from the spec, **fill each `entity`/`desiredState` + confirm domain values with the QA**, then `… data state` and weave its `@manual:data-setup` `Background` block (see the `sungen-data-factory` skill — works with spec+viewpoint only, no DB/API). **This is the same standard as `/sungen:create-data-test`, applied inline — so you do NOT run that command separately afterwards.** A unit with **no input fields** (navigation / list / capture-compare) → **skip this step** (nothing to standardize).
|
|
171
|
+
|
|
170
172
|
5.4. **Depth self-check (deterministic — run BEFORE the audit).** Run `sungen depth-lint --screen <name>` (Bash). It reuses the audit's businessDepth classifier and splits every shallow business-critical scenario into two actionable buckets — act on them now so the audit/repair loop doesn't burn rounds on depth:
|
|
171
173
|
- **DEEPEN IN PLACE** — add a real value assertion to each (`User see all [X] contain {{v}}`, `User remember [X] as {{v}}` + `… with {{v}}`). The printed `template` is a **hint** keyed off the theme — apply judgment to the scenario's actual claim; do NOT paste a value assertion that doesn't fit (e.g. a carousel-visibility scenario should assert the product SET, not a price). If a flagged scenario is genuinely visibility/behavior (not data-correctness), that's an over-count — leave it and note it, never fake an assertion.
|
|
172
174
|
- **CROSS-SCREEN** — route to a flow (`/sungen:add-flow`) or tag `@manual:Mx` + reason; do NOT fake an on-screen data assertion. This removes it from the screen's depth denominator honestly.
|
|
@@ -194,6 +196,7 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
194
196
|
4. Follow the `sungen-tc-generation` skill for section identification, viewpoint generation, and output format. **For flows**, use the "Flow Test Generation" section in the skill. When requirements exist, use the "Requirements-Driven Generation" strategy. **For Tier 1**, apply the **Lightweight Guard** — verify required fields, validation rules, business rules, security checks, and key state transitions all have TCs after generation. **For Tier 2+**, **MUST** apply the full **Mapping Contract** — walk every `spec.md` section top-to-bottom and produce the indicated TCs per Table 1; handle `test-viewpoint.md` per Table 2. Do not silently skip sections. Present sections as a numbered list and let user pick.
|
|
195
197
|
5. Generate or update `.feature` + `test-data.yaml` following `sungen-gherkin-syntax` and `sungen-tc-generation` skills. Generate **group-by-group** (one viewpoint group at a time, tier-by-tier `Write`/`Edit` batches) to stay under the output-token cap. **For flows**: use `[Screen:Element]` namespace format, namespace test-data by phase, add `@flow` tag. **If a scenario needs `@query` DB verification**, check the datasource `engine` first — unsupported engine → follow `sungen-gherkin-syntax` § "Unsupported DB engine — fallback" instead of authoring `@query` steps.
|
|
196
198
|
> **No parallel fan-out here.** Copilot has no sub-agents, so generation is sequential (the Claude Code variant fans out one `sungen-generator` per viewpoint group and merges). Same output, no speedup.
|
|
199
|
+
5.3. **Standardize field test-data — Data Factory (when the unit has input fields).** For each input field, author the field-map `qa/data-factory/<name>.fields.yaml` (field → catalog `type` + real `constraints` + `errorMap`), then run `sungen data lint --screen ${input:name}` and `sungen data gen --screen ${input:name}` per the **`sungen-data-factory`** skill. `data gen` **MERGES** into `test-data.yaml` (replaces only its field keys, preserves the rest), so it is safe here. Weave the standardized boundary/invalid sets into `@cases` with `CHK-*` trace. Then `… data validate` (values correct) + `… data crosscheck` (testcase↔data agree); for a stateful precondition, `… data state --scaffold <name>` to auto-draft → fill entities + confirm with QA → `… data state`, weave the `@manual:data-setup` `Background` (works spec+viewpoint only). **Same standard as `/sungen:create-data-test`, applied inline — do NOT run that command separately afterwards.** No input fields (navigation/list) → skip.
|
|
197
200
|
5.4. **Depth self-check (deterministic — BEFORE the audit).** Run `sungen depth-lint --screen ${input:name}`. It splits every shallow business-critical scenario into **DEEPEN IN PLACE** (add a real value assertion — the printed `template` is a theme-keyed hint, apply judgment to the actual claim; never fake one onto a visibility/behavior scenario) and **CROSS-SCREEN** (route to a flow / tag `@manual:Mx` + reason — removes it from the depth denominator honestly). Act on both, re-run until `deepen` is empty (or only honest over-counts remain), THEN gate. Lifts first-pass `businessDepth` mechanically instead of via 2–3 repair rounds.
|
|
198
201
|
5.5. **Quality gate & repair (harness — always run).** Per `sungen-harness-audit`: run `sungen audit --screen ${input:name}` (structural), THEN do an **independent semantic review inline** using the `sungen-reviewer` criteria (does each scenario's steps PROVE its title/viewpoint? observable Thens? business-critical assertion depth?). Merge both sets of issues; if gate FAILs / findings exist, repair (budget 3) and re-audit — GATE missing theme → generate it (cross-screen → **automate it in the flow** via `/sungen:add-flow`, NOT a full `@manual` screen duplicate — `sungen audit` flags an automatable `@manual` as `MANUAL-AUTOMATABLE`; reserve `@manual:Mx` for true judgment/missing-capability); DEPTH → add data assertions; BALANCE → add business-core first; TRACE → align VP ids. Never fake a pass.
|
|
199
202
|
5.6. **Record.** `sungen manifest --screen ${input:name}`. Ledger **each phase** (not just repair) — pick one `runId` at the start and pass it so `trace`/`ledger report` show THIS run, not a mix: `sungen ledger record --screen ${input:name} --run <runId> --step <discovery|viewpoint|gherkin|audit|repair:N> --ms <elapsed>`. On re-run, start with `sungen manifest --screen ${input:name} --diff` and only regenerate changed sections.
|
|
@@ -127,6 +127,7 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
127
127
|
## Pre-run (phased — per `sungen-selector-fix` skill)
|
|
128
128
|
|
|
129
129
|
1. Verify `<base>/<name>/` has `.feature` + `test-data.yaml`. **If the feature has `@query` steps**, check the resolved datasource `engine` in `qa/datasources.yaml` — unsupported engine → follow the `sungen-gherkin-syntax` skill § "Unsupported DB engine — fallback" before compiling (do not attempt a direct connect).
|
|
130
|
+
1.5. **Data Factory pre-check (before running)** — Data Factory is bundled with core, so whenever a field-map exists (`qa/data-factory/<name>.fields.yaml`), confirm the test-data is standardized and covers each field before you run: `[ -x ./bin/sungen.js ] && ./bin/sungen.js data lint --screen <name> || npx sungen data lint --screen <name>`. If it reports coverage gaps (missing boundary/invalid, unresolved values, unmapped error codes) or the field-map is newer than `test-data/<name>.yaml`, refresh via `/sungen:create-data-test <name>` (or `sungen data gen --screen <name>`) — then compile. Blind spots (a `type` not in the catalog) are for the QA to answer, not to invent. No field-map / driver disabled → skip this step.
|
|
130
131
|
2. **Phase 0 — Selector Pre-gen**: if `selectors.yaml` is missing/empty or doesn't cover the feature file's `[Reference]`s, apply the following decision tree before running Phase 0 from `sungen-selector-fix`:
|
|
131
132
|
|
|
132
133
|
```
|
|
@@ -18,14 +18,16 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
|
|
|
18
18
|
| `sungen-delivery` | Export Gherkin + Playwright results → CSV test case deliverable |
|
|
19
19
|
| `sungen-capture` | Acquire visual/design context — one skill, 4 modes: figma-mcp (Dev Mode MCP), figma-pat (URL → spec_figma.md), live (Playwright MCP scan), local (images in `requirements/ui/`) |
|
|
20
20
|
| `sungen-locale` | Bootstrap i18n — audit selectors, detect locale switch mechanism, generate test-data overlay |
|
|
21
|
+
| `sungen-data-factory` | Standardized test-data — field-type/security catalog, 4-source method, `.overwrite`, blind-spot prompts |
|
|
21
22
|
|
|
22
|
-
## Workflow (
|
|
23
|
+
## Workflow (8 AI commands)
|
|
23
24
|
|
|
24
25
|
| Command | What it does |
|
|
25
26
|
|---|---|
|
|
26
27
|
| `/sungen:add-screen <name> <path>` | Scaffold `qa/screens/<name>/` directories |
|
|
27
28
|
| `/sungen:add-flow <name> [--path <url>]` | Scaffold `qa/flows/<name>/` directories for E2E cross-screen testing |
|
|
28
29
|
| `/sungen:create-test <name>` | Generate `.feature` + `test-data.yaml` (auto-detects screen or flow) |
|
|
30
|
+
| `/sungen:create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
|
|
29
31
|
| `/sungen:review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
|
|
30
32
|
| `/sungen:run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen or flow) |
|
|
31
33
|
| `/sungen:delivery [name...]` | Export test cases → CSV for QA delivery (all screens if no arg) |
|
|
@@ -18,14 +18,16 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
|
|
|
18
18
|
| `sungen-delivery` | Export Gherkin + Playwright results → CSV test case deliverable |
|
|
19
19
|
| `sungen-capture` | Acquire visual/design context — one skill, 4 modes: figma-mcp (Dev Mode MCP), figma-pat (URL → spec_figma.md), live (Playwright MCP scan), local (images in `requirements/ui/`) |
|
|
20
20
|
| `sungen-locale` | Bootstrap i18n — audit selectors, detect locale switch mechanism, generate test-data overlay |
|
|
21
|
+
| `sungen-data-factory` | Standardized test-data — field-type/security catalog, 4-source method, `.overwrite`, blind-spot prompts |
|
|
21
22
|
|
|
22
|
-
## Workflow (
|
|
23
|
+
## Workflow (8 AI commands)
|
|
23
24
|
|
|
24
25
|
| Command | What it does |
|
|
25
26
|
|---|---|
|
|
26
27
|
| `/sungen-add-screen <name> <path>` | Scaffold `qa/screens/<name>/` directories |
|
|
27
28
|
| `/sungen-add-flow <name> [--path <url>]` | Scaffold `qa/flows/<name>/` directories for E2E cross-screen testing |
|
|
28
29
|
| `/sungen-create-test <name>` | Generate `.feature` + `test-data.yaml` (auto-detects screen or flow) |
|
|
30
|
+
| `/sungen-create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
|
|
29
31
|
| `/sungen-review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
|
|
30
32
|
| `/sungen-run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen or flow) |
|
|
31
33
|
| `/sungen-delivery [name...]` | Export test cases → CSV for QA delivery (all screens if no arg) |
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sungen-data-factory
|
|
3
|
+
description: 'Use when create-data-test (or create-test) needs standardized TEST DATA — valid/boundary/invalid values for input fields with CHK-* traceability. Drives the Data Factory catalog + the 4-source method. Invoke after reading spec.md, when authoring the field-map or filling test-data.'
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What this skill does
|
|
8
|
+
|
|
9
|
+
Turn a screen/flow's fields into **standardized test data** — not guessed values. You author a
|
|
10
|
+
**field-map**; the deterministic `sungen data gen` expands it from the Data Factory **catalog**
|
|
11
|
+
(field-type + security + technique library) into valid / boundary / invalid values, each carrying a
|
|
12
|
+
`CHK-*` id that traces to the spec. Your judgment goes into *reading the spec correctly* and
|
|
13
|
+
*answering blind spots* — never into inventing values the catalog or the QA should own.
|
|
14
|
+
|
|
15
|
+
Data Factory is **bundled with core** (runtime-free) — no `capability add` needed; the `sungen data …`
|
|
16
|
+
CLI is available out of the box.
|
|
17
|
+
|
|
18
|
+
**Auto-detection (act on these, don't wait to be asked).** `sungen generate`/`sungen audit` now NOTICE
|
|
19
|
+
the work a unit needs, even before any field-map exists:
|
|
20
|
+
- **`DATA-FACTORY-UNSTANDARDIZED`** — the unit has input fields but no `qa/data-factory/<name>.fields.yaml`.
|
|
21
|
+
→ author the field-map + `data gen` (this skill's main job).
|
|
22
|
+
- **`DATA-FACTORY-PRECONDITION-UNDECLARED`** — the spec describes a stateful precondition (a required
|
|
23
|
+
entity/state) but the test declares none. → **auto-draft a state-recipe** from the spec sentence, run
|
|
24
|
+
`sungen data state`, and weave its `@manual:data-setup` `Background`; confirm the drafted values with
|
|
25
|
+
the QA (`AskUserQuestion`) — never invent domain values.
|
|
26
|
+
When you see either finding, do the work rather than leaving it — that is what makes Sungen self-driving
|
|
27
|
+
instead of a toolbox the QA must operate.
|
|
28
|
+
|
|
29
|
+
## The 4-source method (phuong-phap-tao-test-data.md)
|
|
30
|
+
|
|
31
|
+
Never invent values from memory. Cross-reference four sources — each covers the previous one's gap:
|
|
32
|
+
|
|
33
|
+
1. **Spec validate rules** — required, format, min/max, business rules, and the **error/message code**
|
|
34
|
+
per rule. Defines the legal/illegal boundary. (Says the *law*, not every way to *break* it.)
|
|
35
|
+
2. **Field-type catalog** — the accumulated "how this type breaks" knowledge (email/password/phone/
|
|
36
|
+
number/…). This is the Data Factory `common/` — it fills the "spec doesn't list every break" gap.
|
|
37
|
+
3. **Design technique** — Boundary Value Analysis / Equivalence Partitioning / Decision Table / State
|
|
38
|
+
Transition — reduces the rule + candidates to a *minimal-but-sufficient* representative set.
|
|
39
|
+
4. **DB mapping** — unique constraints, cross-entity dependency, real backend limits → precondition
|
|
40
|
+
data, not just a single input value.
|
|
41
|
+
|
|
42
|
+
Then classify every value into **Valid / Boundary / Invalid** (each invalid tagged with its error
|
|
43
|
+
code) and give it a `CHK-*` id for traceability.
|
|
44
|
+
|
|
45
|
+
## The field-map — `qa/data-factory/<name>.fields.yaml`
|
|
46
|
+
|
|
47
|
+
Your main artifact. One entry per input field; the generator expands it:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
screen: login # or flow: <name>
|
|
51
|
+
fields:
|
|
52
|
+
- name: email
|
|
53
|
+
type: email # a catalog field-type (see list below)
|
|
54
|
+
required: true
|
|
55
|
+
constraints: { maxLocal: 64, maxTotal: 254 } # the project's REAL limits from spec
|
|
56
|
+
errorMap: { M13: E1042, M02: E1001 } # catalog placeholder code → project's real code
|
|
57
|
+
- name: password
|
|
58
|
+
type: password
|
|
59
|
+
constraints: { minLength: 8, maxLength: 32 }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Catalog field-types** (P1): `email` · `password` · `phone` · `string` · `number-integer` ·
|
|
63
|
+
`number-float` · `currency` · `postcode` · `katakana` (JP). Security payload banks auto-append to a
|
|
64
|
+
field's invalid group via the type's `uses_security` (`sqli`, `xss`, `idor`, `broken-auth`, `upload`,
|
|
65
|
+
`input-validation`).
|
|
66
|
+
|
|
67
|
+
Rules:
|
|
68
|
+
- Pick the closest `type`; put the project's real numbers in `constraints` (they override the
|
|
69
|
+
catalog defaults).
|
|
70
|
+
- Map every `errorRef` placeholder (`M13`, `M39`, …) to the spec's real code via `errorMap`. Leave
|
|
71
|
+
one unmapped **only** if the spec has none — the lint flags it as an open point to confirm.
|
|
72
|
+
- **Complex forms:** a **group** field takes nested **`fields:`** (recursive; no `type` on the group → renders a nested object like `address.city`). A field whose rule depends on another takes **`dependsOn: { field, value }`** — recorded as a `_depends_on` note (the generator doesn't resolve the runtime condition; weave it into the scenario/`@cases`).
|
|
73
|
+
- Set **`unique: true`** on a field that must be fresh every run (register email/username, anything
|
|
74
|
+
that would clash with a record a previous run left behind). Its valid value then carries a runtime
|
|
75
|
+
`{{$timestamp}}` (email → `user+{{$timestamp}}@…`) so each run is unique — no cross-run cache
|
|
76
|
+
(which would make tests order-dependent/flaky). Prefer this over caching prior-run values. For
|
|
77
|
+
reusing a value produced earlier **within the same run** (register → then log in with it), bind it
|
|
78
|
+
via the scenario (`@dataFactory` recipe / `testData.bind`), not a cross-run cache.
|
|
79
|
+
|
|
80
|
+
## No input fields → nothing to generate
|
|
81
|
+
|
|
82
|
+
Data Factory standardizes **field-level input data**. If a unit has no user-input fields or
|
|
83
|
+
parameters — a pure navigation, listing, or capture-compare flow — there is nothing to generate.
|
|
84
|
+
Report *"no input fields → nothing to generate"* and skip it; do **not** write an empty field-map.
|
|
85
|
+
Those scenarios are covered by the data create-test already produced, not by field-level test data.
|
|
86
|
+
(This is a correct outcome, not a gap — don't force a field-map to make the unit "processed".)
|
|
87
|
+
|
|
88
|
+
## Blind spots — ask, never fabricate
|
|
89
|
+
|
|
90
|
+
If a field's type is **not** in the catalog — a business enum, a cross-entity constraint, a
|
|
91
|
+
project-specific data model — do **not** guess. Ask the QA (via `AskUserQuestion`) for: (1) the valid
|
|
92
|
+
set, (2) invalid / non-existent values, (3) the error code when wrong, (4) dependencies on other
|
|
93
|
+
fields. Record it in `qa/data-factory/project/<entity>.yaml` so it becomes reusable project catalog.
|
|
94
|
+
This is the core discipline: *common is never enough; when blind, think and ask — don't invent.*
|
|
95
|
+
|
|
96
|
+
## Customizing the catalog — `.overwrite` (survives `sungen update`)
|
|
97
|
+
|
|
98
|
+
The shipped `common/` is a floor. Project rules go in `qa/data-factory/common.overwrite.yaml`,
|
|
99
|
+
deep-merged over the common (**later-wins**, keyed by `type` + data-point `id`) via three explicit
|
|
100
|
+
operations: **EXTEND** (a new `id` appends), **REPLACE** (same `id` wins; `constraints` merge
|
|
101
|
+
key-by-key), **DISABLE** (list ids under `disable:` to drop shipped cases that don't apply — a stale
|
|
102
|
+
target id warns). You can also override `security:` banks and `techniques:` the same way. `sungen
|
|
103
|
+
update` refreshes `common/` but never touches your overlay; a stale entry **warns you to prune it** —
|
|
104
|
+
it never edits your file. Nothing is silently overwritten: **`sungen data provenance`** (and a
|
|
105
|
+
`⟲ .overwrite →` line at `data gen`) shows exactly what your overlay changed, and each overlaid value
|
|
106
|
+
is tagged `via: .overwrite` in the test-data. Prefer a targeted `errorMap`/`constraints` in the
|
|
107
|
+
field-map for per-screen tweaks; use `.overwrite` only for rules that apply project-wide.
|
|
108
|
+
|
|
109
|
+
## Bulk-import fixtures (CSV)
|
|
110
|
+
|
|
111
|
+
For CSV/Excel import or bulk-upload testing, `sungen data fixture --screen <name> [--rows N] [--invalid]`
|
|
112
|
+
turns the field-map into a fixture FILE under `qa/fixtures/`: `<name>.csv` (N valid rows; nested groups
|
|
113
|
+
→ dotted headers; `unique` fields row-indexed) that should import cleanly, and — with `--invalid` —
|
|
114
|
+
`<name>.invalid.csv` (one seeded-bad row per field, **same columns as the valid file so a strict
|
|
115
|
+
importer fails on the data, not an extra column**) plus a **sidecar** `<name>.invalid.expected.yaml`
|
|
116
|
+
that says which 0-based row must be rejected and why (`field`, `chk`, `reason`, `expected_error`).
|
|
117
|
+
Reference the CSV from an `@api` upload (`files:`) or a UI file input, and assert row rejection against
|
|
118
|
+
the sidecar.
|
|
119
|
+
|
|
120
|
+
## Verify before done — the phuong-phap §6 checklist
|
|
121
|
+
|
|
122
|
+
Run `sungen data lint` and clear it: every required field has **≥1 valid**, **boundary present**
|
|
123
|
+
(exact min/max, not only over-boundary) for bounded types, **≥1 invalid per rule** (not one generic
|
|
124
|
+
"wrong" value for many rules), no `UNRESOLVED` values, error codes mapped or explicitly left open.
|
|
125
|
+
Then `sungen data gen` writes the standardized `test-data/<name>.yaml`. Do not hand-edit the
|
|
126
|
+
generated values — change the field-map or `.overwrite` and re-run (determinism: same input → same
|
|
127
|
+
data).
|
|
128
|
+
|
|
129
|
+
Then run **`sungen data validate`** — the semantic check beyond coverage. It verifies each generated
|
|
130
|
+
value is *correct*, not just *present*: every `valid`/`boundary` value actually satisfies the
|
|
131
|
+
constraints and every synthesized `invalid` value actually violates them. A boundary is expanded to
|
|
132
|
+
**one case per point** (`min / min+1` → two cases; `< min` → min-1, below the minimum), each stamped
|
|
133
|
+
`expected: valid|invalid` in the output. Any `VALUE_NOT_VALID` error means a value labeled valid breaks
|
|
134
|
+
a constraint — fix the field-map/constraints and re-run. This is what stops the "structurally right but
|
|
135
|
+
semantically wrong" data (e.g. a "below minimum length" case that is actually long enough to pass).
|
|
136
|
+
|
|
137
|
+
**Applicability & profiles.** The generator only emits cases that apply, and reports the rest (never
|
|
138
|
+
silent). Set **`required: true|false`** correctly — an optional field drops the "empty (required)" case;
|
|
139
|
+
a numeric field with `min ≥ 0` drops the negative example. Security/injection payloads land in their own
|
|
140
|
+
**`adversarial:`** bucket, separate from `invalid:`. Dropped cases show up as `_not_applicable` in the
|
|
141
|
+
output + a `NOT_APPLICABLE` lint info. A `--profile` (`regression` default · `functional` · `smoke` ·
|
|
142
|
+
`security-min`) and `--locale`/`--channel`/`--sink` on `data gen|validate|lint` select a reduced set;
|
|
143
|
+
selection is deterministic. Prefer setting `required` in the field-map over disabling the empty case.
|
|
144
|
+
|
|
145
|
+
## Domain / stateful preconditions (spec + viewpoint only — no DB/API)
|
|
146
|
+
|
|
147
|
+
Field values are not enough when a test needs a **business state first** — "a paid order of user A",
|
|
148
|
+
"a valid unused reset token ≤24h", "the session-storage buffer holds the entered values". Declare a
|
|
149
|
+
**state-recipe** in `qa/data-factory/recipes.yaml` under `resources:` — each resource has an `entity`,
|
|
150
|
+
a **`provenance`** (`prior-flow` | `fixture` | `client-storage` | `server-context` | `manual` |
|
|
151
|
+
`api` | `db`), optional `dependsOn`, `desiredState`, `lifecycle` (`reusable`/`consumable`/`expiring`/
|
|
152
|
+
`leased`/`mutable-state`/`unique-per-run`), and `with:` values — plus `bindings:` (`orderId: ${order.id}`).
|
|
153
|
+
**Auto-draft it — don't make the QA hand-write it.** When the spec describes a precondition (or you saw
|
|
154
|
+
a `PRECONDITION-UNDECLARED` finding), run **`sungen data state --scaffold <unit>`**: it drafts a recipe
|
|
155
|
+
skeleton from the spec's precondition sentences (one resource per signal, provenance guessed, entity
|
|
156
|
+
left as `TODO`). Then **fill each `entity` + `desiredState`/`bindings` from the spec and confirm the
|
|
157
|
+
domain specifics with the QA (`AskUserQuestion`)** — never invent domain values.
|
|
158
|
+
|
|
159
|
+
Run **`sungen data state`**: it validates the graph (cycles, dangling deps, bindings) and renders a
|
|
160
|
+
standardized **manual precondition** block — tagged **`@manual:data-setup`** when it can't be
|
|
161
|
+
auto-provisioned — to weave into the scenario's `Background`. **This works with only spec + viewpoint:
|
|
162
|
+
provenance is usually `prior-flow`/`fixture`/`manual`, NOT DB/API.** If a datasource is later added and
|
|
163
|
+
a resource uses `api`/`db` provenance (+ a `provider:` endpoint), the *same* recipe auto-provisions — no
|
|
164
|
+
rewrite. For a **journey where the same data flows across screens** (setup → confirmation → complete),
|
|
165
|
+
add `shared: { entity, screens: [...] }` so one dataset is used across all of them.
|
|
166
|
+
|
|
167
|
+
## One QA-facing door: `/sungen:create-test`
|
|
168
|
+
|
|
169
|
+
QA never needs to remember which `sungen data …` sub-command to run. **`/sungen:create-test` is the
|
|
170
|
+
single entry** — it standardizes fields, validates, cross-checks, and drafts any stateful precondition
|
|
171
|
+
inline. The `sungen data gen|lint|validate|crosscheck|state|provenance|fixture` commands are the
|
|
172
|
+
**deterministic plumbing** the create-test loop (and CI) drives; run them directly only for a targeted
|
|
173
|
+
re-standardize. `/sungen:create-data-test` is the same pipeline on demand (re-standardize after editing
|
|
174
|
+
a field-map/`.overwrite`, or a unit authored before Data Factory) — not a step you must invoke after
|
|
175
|
+
create-test.
|
|
176
|
+
|
|
177
|
+
## Cross-artifact check — testcase ↔ data agree
|
|
178
|
+
|
|
179
|
+
`data validate` checks the data alone; **`sungen data crosscheck`** checks the testcase and its data
|
|
180
|
+
AGREE: no dangling `{{var}}` (every referenced var has a value), a cross-screen `shared` dataset is
|
|
181
|
+
identical across the journey (confirmation shows what setup entered), and a declared state-precondition
|
|
182
|
+
is surfaced in the feature. Run it after the `.feature` exists; fix any `error`. Together
|
|
183
|
+
`data validate` + `data crosscheck` are the **Data Gate + Cross-artifact Gate** (the `data-only`
|
|
184
|
+
harness profile). Every `data gen` also stamps a `_fingerprint` (catalog+generator+context hash) so a
|
|
185
|
+
reader/cache can tell whether the inputs that produced the data changed.
|
|
186
|
+
|
|
187
|
+
## Boundary completeness + trim
|
|
188
|
+
|
|
189
|
+
Boundary Value Analysis is **complete by construction**: for every bounded dimension in the field-map's
|
|
190
|
+
`constraints` — including ones the field-type's catalog didn't model (e.g. an **email `minLength`**) —
|
|
191
|
+
the generator auto-emits the full set **min-1 (invalid) · min · min+1 · max-1 · max · max+1 (invalid)**.
|
|
192
|
+
So a spec "email min 5" yields length 4 (invalid), 5, 6 — don't hand-add them. Text fields also carry a
|
|
193
|
+
**leading/trailing-whitespace** case (trim behaviour: the server must trim then accept, or reject per
|
|
194
|
+
spec — confirm). If `data validate`/the QA still finds a missing boundary, it means the constraint was
|
|
195
|
+
absent from the field-map — add it there.
|
|
196
|
+
|
|
197
|
+
## Login / authentication screens — credential-state cases (experience)
|
|
198
|
+
|
|
199
|
+
A login screen is not just field-format validation. The catalog carries an **experience checklist**
|
|
200
|
+
(`auth-account-states`) of credential/account STATES a login must cover — each a scenario, most needing
|
|
201
|
+
a domain-state precondition (a seeded account in that state; declare it with a state-recipe →
|
|
202
|
+
`@manual:data-setup` Background):
|
|
203
|
+
- **active** (happy path) · **wrong-password** · **non-existent** (same generic error — anti-enumeration)
|
|
204
|
+
- **soft-deleted** (must NOT authenticate — real-world bug precedent) · **locked/suspended** ·
|
|
205
|
+
**unverified-email** · **password-expired** · **valid-with-surrounding-whitespace** (trim → accept).
|
|
206
|
+
When the unit is a login/auth screen, generate these as scenarios (confirm the exact outcome/message
|
|
207
|
+
code with the spec). Do not assume the outcome — ask the QA for states the spec doesn't pin.
|
|
208
|
+
|
|
209
|
+
## Weaving into scenarios
|
|
210
|
+
|
|
211
|
+
When a `.feature` exists, express invalid/boundary sets as data-driven `Scenario Outline` + `Examples`
|
|
212
|
+
(`@cases`) referencing the values — see `sungen-gherkin-syntax`. This keeps one logic + many values
|
|
213
|
+
instead of copy-pasted scenarios, and each row carries its `CHK-*` id for traceability.
|
|
@@ -6,6 +6,7 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
## ⚠️ Gotchas — read before generating
|
|
8
8
|
|
|
9
|
+
- **Field-level test-data follows the Data Factory standard — don't hand-invent values.** When the unit has input fields, the field values (valid / boundary / invalid + error codes) come from the **`sungen-data-factory`** catalog via a field-map + `sungen data gen` (create-test step 5.3), not free-form guessing. Weave the standardized boundary/invalid sets into `@cases` with `CHK-*` trace. This skill still owns scenario structure, viewpoints, and non-field oracles.
|
|
9
10
|
- **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
|
|
10
11
|
→ One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
|
|
11
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.12-beta.10",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"@babel/types": "^7.28.5",
|
|
40
40
|
"@cucumber/gherkin": "^37.0.0",
|
|
41
41
|
"@cucumber/messages": "^31.0.0",
|
|
42
|
-
"@sungen/driver-
|
|
42
|
+
"@sungen/driver-data-factory": "3.2.12-beta.10",
|
|
43
|
+
"@sungen/driver-ui": "3.2.12-beta.10",
|
|
43
44
|
"chalk": "^5.6.2",
|
|
44
45
|
"commander": "^14.0.2",
|
|
45
46
|
"dotenv": "^17.2.3",
|
|
@@ -27,7 +27,7 @@ import { LOCAL_DRIVERS, registerCoreCapability } from './builtins';
|
|
|
27
27
|
* move to their packages (R5.5/R5.6) they join this list and leave `LOCAL_DRIVERS`; eventually this
|
|
28
28
|
* becomes a scan of installed `@sungen/driver-*` packages (R5.7).
|
|
29
29
|
*/
|
|
30
|
-
const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api', '@sungen/driver-mobile'];
|
|
30
|
+
const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api', '@sungen/driver-mobile', '@sungen/driver-data-factory'];
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Bundled (non-opt-in) drivers: shipped as a core dependency (`drivers.yaml` `bundled: true`), so the
|
|
@@ -36,7 +36,10 @@ const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/dri
|
|
|
36
36
|
* symlink for the package is missing). Surface it LOUDLY: otherwise the entire UI step vocabulary is
|
|
37
37
|
* absent and every step silently compiles to "Unrecognized step pattern" with no clue why.
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
// `@sungen/driver-data-factory` is bundled too — but it is RUNTIME-FREE (only a knowledge catalog +
|
|
40
|
+
// codegen), so shipping it by default adds no runtime, unlike ui/db/api/mobile. It makes standardized
|
|
41
|
+
// test-data available out of the box (no `capability add` needed); inert until a field-map/recipe exists.
|
|
42
|
+
const BUNDLED_DRIVERS = new Set(['@sungen/driver-ui', '@sungen/driver-data-factory']);
|
|
40
43
|
|
|
41
44
|
function loadExternalDriver(name: string): void {
|
|
42
45
|
// Resolve from the user's PROJECT first, then from core's own location. Opt-in drivers
|
|
@@ -15,6 +15,36 @@ function coreVersion(): string {
|
|
|
15
15
|
try { return require('../../../package.json').version || 'latest'; } catch { return 'latest'; }
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the driver version to install for the running core `core`.
|
|
20
|
+
*
|
|
21
|
+
* Lockstep wants the EXACT `core` version. But the beta channel publishes `<core>-beta.N` before a
|
|
22
|
+
* stable `<core>` ever exists — so a released core (e.g. 3.2.10) asking for `@3.2.10` hits ETARGET
|
|
23
|
+
* out-of-the-box when npm only has `3.2.10-beta.2` (`sungen capability add` then fails for a fresh
|
|
24
|
+
* user). Fall back on the SAME version line: prefer exact `core`, else the highest `core-beta.N`,
|
|
25
|
+
* else the `beta` dist-tag as a last resort. Keeps the driver's pinned core-dep on the same line.
|
|
26
|
+
*/
|
|
27
|
+
function resolveDriverVersion(pkg: string, core: string): string {
|
|
28
|
+
if (core === 'latest') return 'beta';
|
|
29
|
+
try {
|
|
30
|
+
const out = spawnSync('npm', ['view', pkg, 'versions', '--json'], { encoding: 'utf-8', shell: true });
|
|
31
|
+
if (out.status !== 0 || !out.stdout) return core; // can't query → let the exact install try/fail
|
|
32
|
+
let versions: string[] = [];
|
|
33
|
+
const parsed = JSON.parse(out.stdout);
|
|
34
|
+
versions = Array.isArray(parsed) ? parsed : [parsed];
|
|
35
|
+
if (versions.includes(core)) return core; // exact exists → lockstep
|
|
36
|
+
const prefix = `${core}-beta.`;
|
|
37
|
+
const betas = versions
|
|
38
|
+
.filter((v) => typeof v === 'string' && v.startsWith(prefix))
|
|
39
|
+
.map((v) => parseInt(v.slice(prefix.length), 10))
|
|
40
|
+
.filter((n) => Number.isInteger(n));
|
|
41
|
+
if (betas.length) return `${core}-beta.${Math.max(...betas)}`; // highest same-line prerelease
|
|
42
|
+
return 'beta'; // nothing on this line → newest beta dist-tag
|
|
43
|
+
} catch {
|
|
44
|
+
return core;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
18
48
|
/**
|
|
19
49
|
* Add `test:mobile` to the project's package.json scripts (if absent).
|
|
20
50
|
* Idempotent — skips if the script is already set.
|
|
@@ -187,14 +217,22 @@ export function registerCapabilityCommand(program: Command): void {
|
|
|
187
217
|
}
|
|
188
218
|
|
|
189
219
|
const adapterName = meta.adapter || meta.id;
|
|
190
|
-
|
|
220
|
+
// Bundled = a platform adapter baked into core (e.g. `web`), OR a capability driver shipped
|
|
221
|
+
// as a core dependency (`drivers.yaml` bundled: true, e.g. data-factory) — present already,
|
|
222
|
+
// so no install/verify: `capability add` on it just records it in the profile.
|
|
223
|
+
const bundled = adapterRegistry.hasAdapter(adapterName) || meta.bundled === true;
|
|
191
224
|
|
|
192
225
|
if (!bundled && !o.skipInstall) {
|
|
193
226
|
// Pin the driver to the RUNNING core's exact version (lockstep). Without this, plain
|
|
194
227
|
// `npm install @sungen/driver-x` resolves the package's `latest` dist-tag — which on a
|
|
195
228
|
// beta channel can lag (e.g. a prerelease first-published-as-latest), pulling a driver
|
|
196
229
|
// whose core dep resolves to the wrong @sun-asterisk/sungen and fails to load.
|
|
197
|
-
const
|
|
230
|
+
const core = coreVersion();
|
|
231
|
+
const target = resolveDriverVersion(meta.package, core); // exact core, else same-line beta, else 'beta' tag
|
|
232
|
+
const spec = `${meta.package}@${target}`;
|
|
233
|
+
if (target !== core) {
|
|
234
|
+
console.log(`ℹ ${meta.package}@${core} is not published yet — installing ${spec} (closest on the ${core} line).`);
|
|
235
|
+
}
|
|
198
236
|
console.log(`📦 Installing ${spec} (dev dependency)...`);
|
|
199
237
|
const r = spawnSync('npm', ['install', '-D', spec], { stdio: 'inherit', shell: true });
|
|
200
238
|
if (r.status !== 0) throw new Error(`npm install -D ${spec} failed.`);
|