@sun-asterisk/sungen 3.2.34-beta.1 → 3.2.35
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/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +3 -29
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/index.js +0 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +12 -2
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +1 -0
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +2 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/expanded-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/date-picker-unsupported.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +13 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +9 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/expanded-assertion.hbs +12 -0
- package/dist/generators/test-generator/code-generator.js +4 -5
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +9 -3
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +9 -3
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +8 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +52 -8
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +16 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +59 -0
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +10 -2
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +2 -13
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/next-step.d.ts +0 -2
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +1 -15
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +3 -6
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +4 -9
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +9 -11
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
- package/dist/harness/viewpoint-baseline.js +1 -7
- package/dist/harness/viewpoint-baseline.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +3 -13
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +8 -38
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/commands.js +5 -3
- package/dist/orchestrator/ai-skills/commands.js.map +1 -1
- package/dist/orchestrator/ai-skills/golden-skills.test.js +29 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -1
- package/dist/orchestrator/flow-manager.d.ts +1 -0
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +52 -3
- package/dist/orchestrator/flow-manager.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +52 -4
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +26 -4
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +22 -30
- package/dist/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/dist/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +24 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +0 -18
- package/dist/orchestrator/templates/specs-test-data.ts +22 -4
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +1 -4
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -1
- package/dist/orchestrator/viewpoint-pattern-catalog.js +2 -5
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +3 -26
- package/src/cli/index.ts +0 -2
- package/src/exporters/test-data-resolver.ts +10 -2
- package/src/generators/gherkin-parser/index.ts +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +2 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/expanded-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/date-picker-unsupported.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +13 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +9 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/expanded-assertion.hbs +12 -0
- package/src/generators/test-generator/code-generator.ts +4 -4
- package/src/generators/test-generator/diagnostics.ts +9 -3
- package/src/generators/test-generator/patterns/types.ts +2 -0
- package/src/generators/test-generator/step-mapper.ts +56 -9
- package/src/generators/test-generator/utils/data-resolver.ts +53 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +10 -2
- package/src/harness/audit.ts +2 -13
- package/src/harness/next-step.ts +1 -17
- package/src/harness/parse.ts +5 -13
- package/src/harness/sensors.ts +8 -9
- package/src/harness/viewpoint-baseline.ts +1 -7
- package/src/harness/viewpoint-ledger.ts +11 -52
- package/src/index.ts +2 -1
- package/src/orchestrator/ai-skills/commands.ts +5 -3
- package/src/orchestrator/ai-skills/golden-skills.test.ts +23 -0
- package/src/orchestrator/flow-manager.ts +53 -3
- package/src/orchestrator/screen-manager.ts +52 -4
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +26 -4
- package/src/orchestrator/templates/ai-src/commands/create-test.md +22 -30
- package/src/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/src/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +24 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +0 -18
- package/src/orchestrator/templates/specs-test-data.ts +22 -4
- package/src/orchestrator/viewpoint-pattern-catalog.ts +2 -5
- package/dist/cli/commands/viewpoint.d.ts +0 -7
- package/dist/cli/commands/viewpoint.d.ts.map +0 -1
- package/dist/cli/commands/viewpoint.js +0 -70
- package/dist/cli/commands/viewpoint.js.map +0 -1
- package/dist/harness/viewpoint-draft-header.d.ts +0 -7
- package/dist/harness/viewpoint-draft-header.d.ts.map +0 -1
- package/dist/harness/viewpoint-draft-header.js +0 -100
- package/dist/harness/viewpoint-draft-header.js.map +0 -1
- package/dist/harness/viewpoint-mode.d.ts +0 -9
- package/dist/harness/viewpoint-mode.d.ts.map +0 -1
- package/dist/harness/viewpoint-mode.js +0 -52
- package/dist/harness/viewpoint-mode.js.map +0 -1
- package/dist/harness/viewpoint-priority.d.ts +0 -19
- package/dist/harness/viewpoint-priority.d.ts.map +0 -1
- package/dist/harness/viewpoint-priority.js +0 -36
- package/dist/harness/viewpoint-priority.js.map +0 -1
- package/dist/harness/viewpoint-review-open.d.ts +0 -6
- package/dist/harness/viewpoint-review-open.d.ts.map +0 -1
- package/dist/harness/viewpoint-review-open.js +0 -90
- package/dist/harness/viewpoint-review-open.js.map +0 -1
- package/dist/harness/viewpoint-scan-refs.d.ts +0 -50
- package/dist/harness/viewpoint-scan-refs.d.ts.map +0 -1
- package/dist/harness/viewpoint-scan-refs.js +0 -200
- package/dist/harness/viewpoint-scan-refs.js.map +0 -1
- package/dist/harness/viewpoint-scan.d.ts +0 -41
- package/dist/harness/viewpoint-scan.d.ts.map +0 -1
- package/dist/harness/viewpoint-scan.js +0 -118
- package/dist/harness/viewpoint-scan.js.map +0 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +0 -63
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +0 -158
- package/dist/orchestrator/viewpoint-scaffold.d.ts +0 -3
- package/dist/orchestrator/viewpoint-scaffold.d.ts.map +0 -1
- package/dist/orchestrator/viewpoint-scaffold.js +0 -70
- package/dist/orchestrator/viewpoint-scaffold.js.map +0 -1
- package/src/cli/commands/viewpoint.ts +0 -58
- package/src/harness/viewpoint-draft-header.ts +0 -55
- package/src/harness/viewpoint-mode.ts +0 -49
- package/src/harness/viewpoint-priority.ts +0 -38
- package/src/harness/viewpoint-review-open.ts +0 -51
- package/src/harness/viewpoint-scan-refs.ts +0 -154
- package/src/harness/viewpoint-scan.ts +0 -111
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +0 -63
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +0 -158
- package/src/orchestrator/viewpoint-scaffold.ts +0 -36
|
@@ -58,13 +58,6 @@ function claimedCount(text: string): number | null {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const ID_RE = /\b([A-Z]{1,5}\d{0,2}(?:[.\-][A-Za-z0-9]+)*-?\d{0,3})\b/; // VP0.Title, VP7-002, MS-HP-001, TV-01
|
|
61
|
-
/**
|
|
62
|
-
* A bullet declares only the id it STARTS with (after a `[ ]` checkbox); one further along is a
|
|
63
|
-
* citation (`… (SG-01, …)`). `- **High** VP-…` or `- Email shown — VP-…` therefore carry no id.
|
|
64
|
-
*/
|
|
65
|
-
const BULLET_ID = new RegExp(`^[\\s(#]*(?:\\[[ xX]\\]\\s*)?\\[?${ID_RE.source}`);
|
|
66
|
-
/** A table cell that is an id and nothing else. */
|
|
67
|
-
const WHOLE_ID = new RegExp(`^${ID_RE.source}$`);
|
|
68
61
|
|
|
69
62
|
/**
|
|
70
63
|
* Lines that DECLARE something about the checklist rather than being a checklist item —
|
|
@@ -109,31 +102,17 @@ const PROCEDURE_LEAD = /^(?:how to (?:run|test|reproduce|verify)|(?:manual )?pro
|
|
|
109
102
|
const LIST_ITEM = /^(?:[-*+]|\d+[.)])\s+/;
|
|
110
103
|
const GENERIC = new Set(['display', 'shown', 'value', 'field', 'input', 'page', 'screen', 'button', 'link', 'text', 'check', 'verify', 'should', 'with', 'when', 'then', 'user', 'this', 'that', 'each', 'item', 'items']);
|
|
111
104
|
|
|
112
|
-
/**
|
|
113
|
-
* `decision`: a `- D4: …` line under Design Decisions. It names the spec's intent so rows can cite
|
|
114
|
-
* it; its testable part is carried by a VP row (review check 8). No scenario ever carries `D4`, so
|
|
115
|
-
* as a ledger item it stayed MISSING for good — "not tested" decisions included (phase-7 D5). The
|
|
116
|
-
* ledger skips it; the continuity, source and integrity checks still read it. Only that section
|
|
117
|
-
* declares: a QA claim labelled `D8` elsewhere stays a claim, and an id-less decision bullet stays
|
|
118
|
-
* the prose claim it always was.
|
|
119
|
-
*/
|
|
120
|
-
export interface ViewpointItem { id?: string; text: string; decision?: true }
|
|
121
|
-
const DECISION_ID = /^D\d+$/;
|
|
122
|
-
const DECISIONS_SECTION = /^(?:design decisions?|quyết định(?: thiết kế)?)\b/i;
|
|
123
|
-
|
|
124
105
|
/** Extract atomic checklist items from a viewpoint file (format-tolerant). */
|
|
125
|
-
export function parseViewpointItems(viewpointPath: string):
|
|
106
|
+
export function parseViewpointItems(viewpointPath: string): { id?: string; text: string }[] {
|
|
126
107
|
if (!fs.existsSync(viewpointPath)) return [];
|
|
127
108
|
const lines = readViewpointText(viewpointPath).split('\n');
|
|
128
|
-
const items:
|
|
109
|
+
const items: { id?: string; text: string }[] = [];
|
|
129
110
|
let inFence = false;
|
|
130
111
|
let inDeclarationSection = false;
|
|
131
112
|
// The level an excluded section was opened at. A SUBSECTION inherits it: `### Optional`
|
|
132
113
|
// nested under `## 8. Traceability Index` used to re-open the section and hand its rows
|
|
133
114
|
// back as claims, because exclusion was recomputed from scratch at every heading.
|
|
134
115
|
let excludedAt: number | null = null;
|
|
135
|
-
// The level the Design Decisions section was opened at, while inside it (see `decision`).
|
|
136
|
-
let decisionsAt: number | null = null;
|
|
137
116
|
// Inside the list a PROCEDURE_LEAD introduced: the indent of the lead (-1 for a paragraph lead),
|
|
138
117
|
// whose list items — and their wrapped continuation lines — are steps. A heading, a table, a
|
|
139
118
|
// new lead, or (after a paragraph lead) a blank line once a step was read ends it: when in
|
|
@@ -168,28 +147,21 @@ export function parseViewpointItems(viewpointPath: string): ViewpointItem[] {
|
|
|
168
147
|
if (heading) {
|
|
169
148
|
const level = heading[1].length;
|
|
170
149
|
if (excludedAt !== null && level > excludedAt) continue; // still inside the excluded section
|
|
171
|
-
if (decisionsAt !== null && level > decisionsAt) continue; // a subsection of Design Decisions
|
|
172
150
|
// A heading both ends the previous section and decides whether this one declares or claims.
|
|
173
151
|
// Strip an ordinal prefix ("## 5. Known Issues", "## 8. Viewpoint Grouping") before
|
|
174
152
|
// testing. The regex anchors at the start, so a NUMBERED heading matched nothing and
|
|
175
153
|
// every excluded section leaked its rows back in as claims — a real viewpoint numbers
|
|
176
154
|
// its sections, so in practice the exclusion list was doing nothing at all (#657).
|
|
177
|
-
|
|
178
|
-
const headText = heading[2].replace(/[*`]/g, '').replace(/^\s*(?:(?:[Pp]hần|[Pp]art|[Mm]ục|[Ss]ection)\s+)?(?:\d+|[A-Z])\s*[.):]\s*/, '').trim();
|
|
155
|
+
const headText = heading[2].replace(/[*`]/g, '').replace(/^\s*(?:\d+|[A-Z])\s*[.):]\s*/, '').trim();
|
|
179
156
|
inDeclarationSection = DECLARATION_SECTION.test(headText);
|
|
180
157
|
excludedAt = inDeclarationSection ? level : null;
|
|
181
|
-
decisionsAt = DECISIONS_SECTION.test(headText) ? level : null;
|
|
182
158
|
continue;
|
|
183
159
|
}
|
|
184
160
|
if (inDeclarationSection) continue;
|
|
185
161
|
let text = '';
|
|
186
162
|
let lead = '';
|
|
187
|
-
let declared: RegExpMatchArray | null = null;
|
|
188
163
|
const bullet = line.match(/^(?:[-*+]|\d+[.)])\s+(.*)$/);
|
|
189
|
-
if (bullet) {
|
|
190
|
-
text = bullet[1]; lead = bullet[1];
|
|
191
|
-
declared = lead.replace(/[*`]/g, '').match(BULLET_ID);
|
|
192
|
-
}
|
|
164
|
+
if (bullet) { text = bullet[1]; lead = bullet[1]; }
|
|
193
165
|
else if (line.startsWith('|')) { // table data row
|
|
194
166
|
if (/^\|[\s|:-]+\|?$/.test(line)) continue; // separator
|
|
195
167
|
// The row right above a `|---|` separator IS the header, whatever its words (`Skill #`,
|
|
@@ -200,14 +172,7 @@ export function parseViewpointItems(viewpointPath: string): ViewpointItem[] {
|
|
|
200
172
|
const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
|
|
201
173
|
if (/^(vp|id|viewpoint|priority|reason|no\.?|category|item|trigger|#|pattern|applicable|notes|field|constraint|code|description|status|step|flow|ref|level|question|screen|actor|branches from|own steps|component|thành phần|trường|bước)$/i.test(cells[0] || '')) continue; // header
|
|
202
174
|
text = cells.join(' — ');
|
|
203
|
-
|
|
204
|
-
// declares nothing. A later cell holding an id and nothing else still declares (`| x | VP-UI-001 |`),
|
|
205
|
-
// and so does a row number followed by a cell that starts with one (`| 1 | VP-UI-001 — … |`).
|
|
206
|
-
const raw = line.replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.replace(/[*`]/g, '').trim());
|
|
207
|
-
lead = raw[0] ?? '';
|
|
208
|
-
declared = lead.match(ID_RE)
|
|
209
|
-
?? (/^\d+\.?$/.test(lead) ? (raw[1] ?? '').match(BULLET_ID) : null)
|
|
210
|
-
?? raw.slice(1).map((c) => c.match(WHOLE_ID)).find(Boolean) ?? null;
|
|
175
|
+
lead = cells[0] ?? '';
|
|
211
176
|
} else continue;
|
|
212
177
|
text = text.replace(/[*`]/g, '').trim();
|
|
213
178
|
if (!text) continue;
|
|
@@ -222,16 +187,15 @@ export function parseViewpointItems(viewpointPath: string): ViewpointItem[] {
|
|
|
222
187
|
// under a grouping section. It declares nothing; it points at things declared elsewhere.
|
|
223
188
|
const leadIds = new Set((leadClean.match(new RegExp(ID_RE.source, 'g')) || []).filter((x) => /\d/.test(x)));
|
|
224
189
|
if (leadIds.size > 1) continue;
|
|
225
|
-
const
|
|
190
|
+
const idM = leadClean.match(ID_RE) ?? text.match(ID_RE);
|
|
191
|
+
const id = idM && /\d/.test(idM[1]) ? idM[1] : undefined; // require a digit so prose words aren't IDs
|
|
226
192
|
// A row keyed by a bare GROUP prefix ("VP-LOGIC — transitions — High — why the flow exists")
|
|
227
193
|
// declares a family and its priority, not a checklist item: nothing can "cover" it, and
|
|
228
194
|
// demanding coverage sends the author looking for a test that was never meant to exist.
|
|
229
195
|
if (/^VP-[A-Z]+\s*(?:—|-|\||$)/.test(leadClean) && !/\d/.test(leadClean)) continue;
|
|
230
196
|
const words = (text.toLowerCase().match(/[a-z][a-z-]{3,}/g) || []).filter((w) => !GENERIC.has(w));
|
|
231
197
|
if (!id && words.length < 2) continue; // not substantive enough to track
|
|
232
|
-
items.push(
|
|
233
|
-
? { id, text: text.slice(0, 100), decision: true }
|
|
234
|
-
: { id, text: text.slice(0, 100) });
|
|
198
|
+
items.push({ id, text: text.slice(0, 100) });
|
|
235
199
|
}
|
|
236
200
|
return items;
|
|
237
201
|
}
|
|
@@ -242,7 +206,6 @@ export function viewpointLedger(viewpointPath: string, scenarios: ScenarioInfo[]
|
|
|
242
206
|
// (#622) — silently collapsing those would remove its input.
|
|
243
207
|
const seen = new Set<string>();
|
|
244
208
|
const items = parseViewpointItems(viewpointPath)
|
|
245
|
-
.filter((i) => !i.decision)
|
|
246
209
|
.filter((i) => { const k = `${i.id ?? ''}|${i.text}`; if (seen.has(k)) return false; seen.add(k); return true; });
|
|
247
210
|
if (!fs.existsSync(viewpointPath) || items.length === 0) {
|
|
248
211
|
return { hasViewpoint: fs.existsSync(viewpointPath), total: 0, covered: 0, ratio: 1, missing: [], manualOnly: [], partial: [] };
|
|
@@ -434,15 +397,11 @@ export function viewpointIntegrity(
|
|
|
434
397
|
|
|
435
398
|
// The Priority-Viewpoints table: `| <id> | <priority> | <reason> |`.
|
|
436
399
|
const vpText = readViewpointText(viewpointPath);
|
|
437
|
-
const
|
|
400
|
+
const rows = [...vpText
|
|
438
401
|
.matchAll(new RegExp(`^\\|\\s*((?:VP|FL)[A-Z0-9._-]*)\\s*\\|\\s*(${PRIORITY_WORDS})\\s*\\|`, 'gim'))]
|
|
439
402
|
.map((m) => m[2].toLowerCase());
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const level = (w: string): string => (/^(?:medium|med|normal)$/.test(w) ? 'normal' : w);
|
|
443
|
-
const uniformPriority = words.length >= 3 && new Set(words.map(level)).size === 1
|
|
444
|
-
? { levels: [words[0]], rows: words.length } // the word QA wrote, not the folded key
|
|
445
|
-
: null;
|
|
403
|
+
const levels = [...new Set(rows)];
|
|
404
|
+
const uniformPriority = rows.length >= 3 && levels.length === 1 ? { levels, rows: rows.length } : null;
|
|
446
405
|
|
|
447
406
|
return { duplicateIds, danglingRefs, uniformPriority };
|
|
448
407
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,8 @@ export type { ResolvedAppProfile } from './generators/test-generator/app-profile
|
|
|
20
20
|
export type { MappedStep } from './generators/test-generator/step-mapper';
|
|
21
21
|
// The shared "nothing honest to compile" step body. Exported for the driver SPI: a pattern that
|
|
22
22
|
// must refuse (SG-W017 and siblings) emits THIS rather than shipping a one-sided refusal template,
|
|
23
|
-
// which would fail the adapter-parity gate and push playwright off its refusal-only floor
|
|
23
|
+
// which would fail the adapter-parity gate and push playwright off its refusal-only floor (1 — the
|
|
24
|
+
// `date-picker-unsupported` refusal both adapters ship for the resolver-based fill-input pattern, #907).
|
|
24
25
|
export { unsupportedStepCode } from './generators/test-generator/patterns/unsupported-step-code';
|
|
25
26
|
export type { ParsedStep } from './generators/gherkin-parser';
|
|
26
27
|
export { getPathCode, inferPath, resolvePathVariables } from './generators/test-generator/utils/path-inference';
|
|
@@ -63,13 +63,15 @@ export function parseCommandSource(text: string, id: string): CommandSource {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const unquote = (s: string) => s.replace(/^['"]|['"]$/g, '');
|
|
66
|
+
/** A YAML single-quoted scalar: an apostrophe is written twice, or it ends the value early (#899). */
|
|
67
|
+
const sq = (s: string) => `'${s.replace(/'/g, "''")}'`;
|
|
66
68
|
|
|
67
69
|
/** Claude command file — MUST reproduce the exact existing format (byte-identical gate). */
|
|
68
70
|
export function renderClaudeCommand(c: CommandSource): string {
|
|
69
71
|
const fm = [
|
|
70
72
|
'---',
|
|
71
73
|
...(c.name ? [`name: ${c.name}`] : []), // some commands (locale) omit name
|
|
72
|
-
`description:
|
|
74
|
+
`description: ${sq(c.description)}`,
|
|
73
75
|
`argument-hint: ${c.argumentHint}`,
|
|
74
76
|
`allowed-tools: ${c.claudeTools}`,
|
|
75
77
|
'---',
|
|
@@ -82,8 +84,8 @@ export function renderCopilotCommand(c: CommandSource): string {
|
|
|
82
84
|
const fm = [
|
|
83
85
|
'---',
|
|
84
86
|
`name: sungen-${c.id}`,
|
|
85
|
-
`description:
|
|
86
|
-
`argument-hint:
|
|
87
|
+
`description: ${sq(c.description)}`,
|
|
88
|
+
`argument-hint: ${sq(unquote(c.argumentHint))}`,
|
|
87
89
|
`agent: 'agent'`,
|
|
88
90
|
`tools: ${c.copilotTools}`,
|
|
89
91
|
'---',
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import * as fs from 'fs';
|
|
18
18
|
import * as path from 'path';
|
|
19
|
+
import { parseDocument } from 'yaml';
|
|
19
20
|
import { buildOutputs } from './build';
|
|
20
21
|
import { parseCommandSource } from './commands';
|
|
21
22
|
|
|
22
23
|
const FIX = path.resolve(__dirname, '..', '..', '..', 'tests', 'golden', 'ai-skills', 'fixtures');
|
|
23
24
|
const SRC = path.resolve(__dirname, '..', 'templates', 'ai-src', 'skills');
|
|
24
25
|
const SRC_CMD = path.resolve(__dirname, '..', 'templates', 'ai-src', 'commands');
|
|
26
|
+
const SRC_CFG = path.resolve(__dirname, '..', 'templates', 'ai-src', 'config');
|
|
25
27
|
const UPDATE = process.argv.includes('--update');
|
|
26
28
|
|
|
27
29
|
function lint(): string[] {
|
|
@@ -47,6 +49,18 @@ function lint(): string[] {
|
|
|
47
49
|
if (!c.description) errs.push(`commands/${f}: missing description`);
|
|
48
50
|
if (c.order != null) { const k = String(c.order); if (seen.has(k)) { /* allowed: parallel entrypoints */ } else seen.set(k, f); }
|
|
49
51
|
}
|
|
52
|
+
// The CLAUDE.md / copilot-instructions tables are the always-loaded index of what ships, so
|
|
53
|
+
// they must list every skill and command, with the right count (#899).
|
|
54
|
+
const skills = fs.readdirSync(SRC).filter((d) => fs.existsSync(path.join(SRC, d, 'SKILL.md')));
|
|
55
|
+
const cmds = fs.readdirSync(SRC_CMD).filter((f) => f.endsWith('.md')).map((f) => f.replace(/\.md$/, ''));
|
|
56
|
+
for (const [cfg, prefix] of [['claude.md', '/sungen:'], ['copilot.md', '/sungen-']] as const) {
|
|
57
|
+
const t = fs.readFileSync(path.join(SRC_CFG, cfg), 'utf8');
|
|
58
|
+
for (const s of skills) if (!t.includes(`| \`${s}\` |`)) errs.push(`config/${cfg}: skills table misses ${s}`);
|
|
59
|
+
const listed = (c: string) => [' ', '`'].some((end) => t.includes(`| \`${prefix}${c}${end}`));
|
|
60
|
+
for (const c of cmds) if (!listed(c)) errs.push(`config/${cfg}: workflow table misses ${prefix}${c}`);
|
|
61
|
+
const n = t.match(/## Workflow \((\d+) AI commands\)/)?.[1];
|
|
62
|
+
if (Number(n) !== cmds.length) errs.push(`config/${cfg}: says ${n} AI commands, ${cmds.length} ship`);
|
|
63
|
+
}
|
|
50
64
|
return errs;
|
|
51
65
|
}
|
|
52
66
|
|
|
@@ -77,6 +91,15 @@ function run() {
|
|
|
77
91
|
else codexNames.set(m[1], f.outputPath);
|
|
78
92
|
}
|
|
79
93
|
|
|
94
|
+
// The key regex in lint() accepts frontmatter a real YAML parser rejects (an unquoted ": ", an
|
|
95
|
+
// unescaped apostrophe inside '...'), and assistants then drop every field silently (#899).
|
|
96
|
+
// Parse what ships — renderers re-quote command fields, so a valid source is not enough.
|
|
97
|
+
for (const f of outputs) {
|
|
98
|
+
if (!f.content.startsWith('---')) continue;
|
|
99
|
+
const doc = parseDocument(f.content.slice(3, f.content.indexOf('\n---', 3)));
|
|
100
|
+
for (const e of doc.errors) lintErrs.push(`${f.outputPath}: frontmatter is not valid YAML — ${e.message.split('\n')[0]}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
80
103
|
if (UPDATE) {
|
|
81
104
|
fs.rmSync(FIX, { recursive: true, force: true });
|
|
82
105
|
for (const f of outputs) {
|
|
@@ -8,7 +8,6 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { readCapabilities } from '../harness/capability';
|
|
10
10
|
import { testDataHeader, ensureTestDataGuideFiles } from './test-data-guide';
|
|
11
|
-
import { viewpointScaffold } from './viewpoint-scaffold';
|
|
12
11
|
|
|
13
12
|
export interface FlowOptions {
|
|
14
13
|
name: string;
|
|
@@ -144,7 +143,7 @@ export class FlowManager {
|
|
|
144
143
|
fs.writeFileSync(specPath, this.generateSpecTemplate(options, flowName, isMobile), 'utf-8');
|
|
145
144
|
|
|
146
145
|
const viewpointPath = path.join(requirementsDir, 'test-viewpoint.md');
|
|
147
|
-
fs.writeFileSync(viewpointPath,
|
|
146
|
+
fs.writeFileSync(viewpointPath, this.generateViewpointTemplate(options), 'utf-8');
|
|
148
147
|
|
|
149
148
|
console.log(`Created files:`);
|
|
150
149
|
console.log(` ${path.relative(this.cwd, featurePath)}`);
|
|
@@ -218,7 +217,7 @@ export class FlowManager {
|
|
|
218
217
|
restored.push(file);
|
|
219
218
|
};
|
|
220
219
|
ensure(path.join(paths.requirementsDir, 'spec.md'), () => this.generateSpecTemplate(options, flowName, isMobile));
|
|
221
|
-
ensure(path.join(paths.requirementsDir, 'test-viewpoint.md'), () =>
|
|
220
|
+
ensure(path.join(paths.requirementsDir, 'test-viewpoint.md'), () => this.generateViewpointTemplate(options));
|
|
222
221
|
ensure(paths.featurePath, () => this.generateFeatureTemplate(options, flowName, isMobile));
|
|
223
222
|
return restored;
|
|
224
223
|
}
|
|
@@ -288,6 +287,57 @@ export class FlowManager {
|
|
|
288
287
|
`;
|
|
289
288
|
}
|
|
290
289
|
|
|
290
|
+
private generateViewpointTemplate(options: FlowOptions): string {
|
|
291
|
+
return [
|
|
292
|
+
`# ${options.name} Flow — Test Viewpoints`,
|
|
293
|
+
'',
|
|
294
|
+
'## Edge Cases',
|
|
295
|
+
'',
|
|
296
|
+
'<!-- Sample — replace with actual edge cases for this flow:',
|
|
297
|
+
'- Network drops between screen transitions — should resume or show error?',
|
|
298
|
+
'- Session expires mid-flow — should redirect to login and preserve progress?',
|
|
299
|
+
'- Browser back button at step 3 — should return to step 2 with data intact?',
|
|
300
|
+
'- Complete flow twice rapidly — should prevent duplicate submissions',
|
|
301
|
+
'-->',
|
|
302
|
+
'',
|
|
303
|
+
'## Known Issues',
|
|
304
|
+
'',
|
|
305
|
+
'<!-- Sample — replace with actual known issues:',
|
|
306
|
+
'- [BUG-001] Form data lost if user navigates away and returns',
|
|
307
|
+
'- [BUG-002] Confirmation page shows stale data on slow connections',
|
|
308
|
+
'-->',
|
|
309
|
+
'',
|
|
310
|
+
'## Design Decisions',
|
|
311
|
+
'',
|
|
312
|
+
'<!-- Sample — replace with actual design decisions:',
|
|
313
|
+
'- Flow requires completing all steps in order (no skip)',
|
|
314
|
+
'- Draft is auto-saved at each step transition',
|
|
315
|
+
'- Cancel at any step discards the entire flow',
|
|
316
|
+
'-->',
|
|
317
|
+
'',
|
|
318
|
+
'## Cross-Screen Concerns',
|
|
319
|
+
'',
|
|
320
|
+
'<!-- Sample — replace with actual cross-screen concerns:',
|
|
321
|
+
'- Auth role must remain consistent across all screens',
|
|
322
|
+
'- Data entered in step 1 must be visible in confirmation (step 3)',
|
|
323
|
+
'- Error in step 2 should allow returning to step 1 without re-entering data',
|
|
324
|
+
'-->',
|
|
325
|
+
'',
|
|
326
|
+
'## Priority Viewpoints',
|
|
327
|
+
'',
|
|
328
|
+
'<!-- Rate importance for this flow (High / Medium / Low / Skip):',
|
|
329
|
+
'',
|
|
330
|
+
'| VP | Priority | Reason |',
|
|
331
|
+
'|---|----------|--------|',
|
|
332
|
+
'| VP-UI | Medium | Standard forms across screens |',
|
|
333
|
+
'| VP-VAL | High | Cross-screen validation rules |',
|
|
334
|
+
'| VP-LOGIC | High | Multi-step business logic, state transitions |',
|
|
335
|
+
'| VP-SEC | Medium | Auth must persist, role-based access at each screen |',
|
|
336
|
+
'-->',
|
|
337
|
+
'',
|
|
338
|
+
].join('\n');
|
|
339
|
+
}
|
|
340
|
+
|
|
291
341
|
private generateFeatureTemplate(options: FlowOptions, flowName: string, isMobile: boolean): string {
|
|
292
342
|
const description = options.description || `complete the ${options.name} flow`;
|
|
293
343
|
|
|
@@ -8,7 +8,7 @@ import * as path from 'path';
|
|
|
8
8
|
import { readCapabilities } from '../harness/capability';
|
|
9
9
|
import { testDataHeader, ensureTestDataGuideFiles } from './test-data-guide';
|
|
10
10
|
import { resolveWebBaseUrl } from '../exporters/env-qa';
|
|
11
|
-
import {
|
|
11
|
+
import { uiPatternRows } from './viewpoint-pattern-catalog';
|
|
12
12
|
|
|
13
13
|
export interface ScreenOptions {
|
|
14
14
|
name: string;
|
|
@@ -158,11 +158,59 @@ export class ScreenManager {
|
|
|
158
158
|
fs.writeFileSync(specPath, this.generateSpecTemplate(options, screenName), 'utf-8');
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
// Generate requirements test-viewpoint.md (only on first screen creation)
|
|
162
|
-
// samples inside comments, so an untouched file declares nothing (see viewpoint-scaffold.ts).
|
|
161
|
+
// Generate requirements test-viewpoint.md (only on first screen creation)
|
|
163
162
|
const viewpointPath = path.join(requirementsDir, 'test-viewpoint.md');
|
|
164
163
|
if (!fs.existsSync(viewpointPath)) {
|
|
165
|
-
fs.writeFileSync(viewpointPath,
|
|
164
|
+
fs.writeFileSync(viewpointPath, [
|
|
165
|
+
`# ${options.name} — Test Viewpoints`,
|
|
166
|
+
'',
|
|
167
|
+
'## Edge Cases',
|
|
168
|
+
'',
|
|
169
|
+
'<!-- Sample — replace with actual edge cases for this screen:',
|
|
170
|
+
'- Paste emoji into text fields (e.g., 🎉👋) — should accept or sanitize?',
|
|
171
|
+
'- Input 10,000+ characters into free text — truncate or reject?',
|
|
172
|
+
'- Double-click Submit rapidly — should only create 1 record',
|
|
173
|
+
'- Browser back button after successful submit — should not re-submit',
|
|
174
|
+
'-->',
|
|
175
|
+
'',
|
|
176
|
+
'## Known Issues',
|
|
177
|
+
'',
|
|
178
|
+
'<!-- Sample — replace with actual known issues:',
|
|
179
|
+
'- [BUG-001] Date picker does not work on Safari < 16',
|
|
180
|
+
'- [BUG-002] File upload silently fails for files > 20MB (no error shown)',
|
|
181
|
+
'- Search does not handle Vietnamese accents correctly ("Nguyên" ≠ "Nguyen")',
|
|
182
|
+
'-->',
|
|
183
|
+
'',
|
|
184
|
+
'## Design Decisions',
|
|
185
|
+
'',
|
|
186
|
+
'<!-- Sample — replace with actual design decisions:',
|
|
187
|
+
'- Show all validation errors at once (not one at a time) — per UX review',
|
|
188
|
+
'- Cancel button resets form completely (no draft saving)',
|
|
189
|
+
'- Anonymous mode hides sender name but keeps department visible',
|
|
190
|
+
'-->',
|
|
191
|
+
'',
|
|
192
|
+
'## UI Patterns Identified',
|
|
193
|
+
'',
|
|
194
|
+
'<!-- Check applicable patterns for this screen (numbered as in the sungen-viewpoint skill):',
|
|
195
|
+
'',
|
|
196
|
+
'| # | Pattern | Applicable? | Notes |',
|
|
197
|
+
'|---|---------|-------------|-------|',
|
|
198
|
+
...uiPatternRows(isMobile),
|
|
199
|
+
'-->',
|
|
200
|
+
'',
|
|
201
|
+
'## Priority Viewpoints',
|
|
202
|
+
'',
|
|
203
|
+
'<!-- Rate importance for this screen (High / Medium / Low / Skip):',
|
|
204
|
+
'',
|
|
205
|
+
'| VP | Priority | Reason |',
|
|
206
|
+
'|---|----------|--------|',
|
|
207
|
+
'| VP-UI | High | Complex form with many states |',
|
|
208
|
+
'| VP-VAL | High | 14 validation rules from spec |',
|
|
209
|
+
'| VP-LOGIC | Medium | Standard CRUD, no complex business rules |',
|
|
210
|
+
'| VP-SEC | Low | Internal tool, basic auth only |',
|
|
211
|
+
'-->',
|
|
212
|
+
'',
|
|
213
|
+
].join('\n'), 'utf-8');
|
|
166
214
|
}
|
|
167
215
|
|
|
168
216
|
// Auto-screenshot: capture page only when --capture is set and not mobile (requires --path)
|
|
@@ -119,7 +119,7 @@ if 1, 3 or 8 fails, propose SPLITTING into separate flows:
|
|
|
119
119
|
4. Is that outcome **valuable to the actor**? (an order placed, a password reset — not "a page rendered")
|
|
120
120
|
5. Is **every step necessary** for that outcome?
|
|
121
121
|
6. Are all steps at the **same business abstraction**?
|
|
122
|
-
7. Are optional/error branches **
|
|
122
|
+
7. Are optional/error branches **flows of this goal** (Alternate / Exception flows), not new goals?
|
|
123
123
|
8. Does **no segment** form an independently valuable flow on its own?
|
|
124
124
|
9. Can you write **a single clear final assertion**?
|
|
125
125
|
10. Can you name it "**Verb + outcome**"? (`place-order`, `reset-password` — not `cart-and-filter`)
|
|
@@ -134,8 +134,11 @@ Record the screen list — you will need it for:
|
|
|
134
134
|
### 1b. Author the Flow Contract (`requirements/flow-contract.yaml`)
|
|
135
135
|
|
|
136
136
|
Write the answers down as the flow's contract — `sungen audit` scores the flow **against it**
|
|
137
|
-
(the `flowCoverage` axis
|
|
138
|
-
|
|
137
|
+
(the `flowCoverage` axis, per declared flow; `FLOW-OUTCOME-UNPROVEN` when no automated scenario
|
|
138
|
+
asserts data on the outcome screen; `FLOW-SCOPE-CREEP` when scenarios never touch it). The audit
|
|
139
|
+
also requires **both guarantees** and the **`flows:` inventory** on every contract —
|
|
140
|
+
`FLOW-GUARANTEE-MISSING` / `FLOW-INVENTORY-MISSING` otherwise, and the top mark is withheld — so
|
|
141
|
+
write them now rather than meet them after the spec and viewpoint are reviewed:
|
|
139
142
|
|
|
140
143
|
```yaml
|
|
141
144
|
goal: "Place an order for a product added from home" # Verb + outcome
|
|
@@ -146,7 +149,22 @@ outcome:
|
|
|
146
149
|
screen: checkout # the [Screen:...] namespace carrying the final proof
|
|
147
150
|
assertion: "The confirmation shows the order number and the paid total"
|
|
148
151
|
value: "The customer has paid; the shop has a new order"
|
|
149
|
-
|
|
152
|
+
successGuarantee: "An order exists with the cart's lines and total; the cart is empty"
|
|
153
|
+
minimalGuarantee: "No order and no charge without a confirmation; the cart is unchanged" # what Exception flows assert
|
|
154
|
+
phases: [BF, AF, EF] # Basic / Alternate / Exception flows (default) — the first one must reach `outcome`
|
|
155
|
+
flows: # the inventory: every flow of the use case, each with a status
|
|
156
|
+
- id: BF
|
|
157
|
+
outcome: "The successGuarantee — order confirmed with its number and total"
|
|
158
|
+
status: covered
|
|
159
|
+
- id: AF01
|
|
160
|
+
branchFrom: "BF step 3, the user edits the quantity in the cart"
|
|
161
|
+
outcome: "Re-enters BF at step 3 with the new total"
|
|
162
|
+
status: covered
|
|
163
|
+
- id: EF01
|
|
164
|
+
branchFrom: "BF step 4, the payment is declined"
|
|
165
|
+
outcome: "The minimalGuarantee — no order, cart kept, an error shown"
|
|
166
|
+
status: pending-clarification # covered | deferred | pending-clarification | out-of-scope
|
|
167
|
+
reason: "The decline message is not in the spec — asked the BA"
|
|
150
168
|
stateful: cart # the mutated collection, if any — enables regression-depth dims
|
|
151
169
|
golden: true # optional — release-critical: Final Inspection expects @golden scenarios here
|
|
152
170
|
external: # optional — legs owned by another team/vendor (System INTEGRATION Testing)
|
|
@@ -155,6 +173,10 @@ external: # optional — legs owned by another team/vendor (
|
|
|
155
173
|
screens: [payment] # the flow namespaces that leg passes through
|
|
156
174
|
```
|
|
157
175
|
|
|
176
|
+
`phases: [HP, ER, EH]` (happy path / error recovery / guards) is the lighter vocabulary for a short
|
|
177
|
+
single-outcome flow — it still needs both guarantees and a `flows:` inventory. Enumerating the
|
|
178
|
+
flows (the step × risk matrix) is described in the `sungen-tc-generation` skill, flow section.
|
|
179
|
+
|
|
158
180
|
**A filled contract is an INPUT to generation — never an output.** Like `test-viewpoint.md`,
|
|
159
181
|
generation must not rewrite it to match what was generated; disagree → propose the diff and ask.
|
|
160
182
|
|
|
@@ -64,8 +64,7 @@ restated from screen specs the project does not hold, say so: the audit reports
|
|
|
64
64
|
`SPEC-RESTATED-UNVERIFIED` because `specFR 100%` over a hand-copied list certifies the copy, not
|
|
65
65
|
the source.
|
|
66
66
|
|
|
67
|
-
**
|
|
68
|
-
(mode 1) a missing axis or guarantee line is a row in `viewpoint-review.md`, not an edit. `VP-LOGIC / VP-VAL / VP-SEC / VP-NAV` classify
|
|
67
|
+
**The viewpoint carries BOTH classification axes.** `VP-LOGIC / VP-VAL / VP-SEC / VP-NAV` classify
|
|
69
68
|
by TECHNICAL RISK; `BF / AF / EF` classify by BUSINESS BRANCH. They answer different questions and
|
|
70
69
|
neither replaces the other — a reviewer needs to see both, so use the compound id
|
|
71
70
|
(`VP-SEC-EF02-001`) and give the viewpoint a section per flow as well as its priority table by
|
|
@@ -82,8 +81,8 @@ questions and only one of them is a yardstick:
|
|
|
82
81
|
| `flow-contract.yaml` | the USE CASE — actor · trigger · goal · precondition · both guarantees · the flow inventory | `flowCoverage` |
|
|
83
82
|
| `test-viewpoint.md` | the CLAIMS the suite must prove — cross-screen concerns, edge cases, design decisions, open questions, priorities | `atomicLedger` · `traceability` |
|
|
84
83
|
|
|
85
|
-
If `requirements/test-viewpoint.md` is missing
|
|
86
|
-
|
|
84
|
+
If `requirements/test-viewpoint.md` is missing, **write it before generating** (`add-flow` scaffolds
|
|
85
|
+
a template; a run that deletes or replaces it has removed the yardstick). Without it two scored
|
|
87
86
|
axes are excluded and four sensors cannot run at all — the atomic ledger, the
|
|
88
87
|
declaration-integrity checks, the browser-gesture check and the two-way continuity check — so the
|
|
89
88
|
audit reports `VIEWPOINT-MISSING` and the result is *unmeasured*, not passing. And the reason the
|
|
@@ -96,8 +95,8 @@ ordering choice: a yardstick derived from the scenarios reads 100% by constructi
|
|
|
96
95
|
dropped along the way stops being missing from anything. `sungen audit` now reports
|
|
97
96
|
`VIEWPOINT-ADOPTED-POST-HOC` on the first sighting of a viewpoint alongside an existing suite and
|
|
98
97
|
holds `atomicLedger` + `traceability` as unverified until a human accepts it — so the honest move
|
|
99
|
-
is to write the claims from `spec.md` and the contract, mark
|
|
100
|
-
|
|
98
|
+
is to write the claims from `spec.md` and the contract, mark plainly that they are a draft for QA
|
|
99
|
+
review, and let the QA revise before `--accept-viewpoint`. **You never run `--accept-viewpoint` yourself** — `sungen next` lists it as a `⚖ decide` step for exactly this reason: an agent accepting the yardstick it wrote in the same session makes `atomicLedger` and `traceability` read 100% against its own checklist, and the Exception Flow it forgot stops being missing from anything. Present the acceptance to the QA (`AskUserQuestion`) with what the file declares, and stop.
|
|
101
100
|
|
|
102
101
|
**When you give a claim an id, a scenario must carry that id.** The id is the contract: a claim
|
|
103
102
|
whose id no scenario carries is reported MISSING, and word overlap will not rescue it. Several
|
|
@@ -111,9 +110,8 @@ narrow the claim to what is actually proven.
|
|
|
111
110
|
|
|
112
111
|
**Priority comes from the declaration.** A scenario tagged `@high` under a viewpoint row that says
|
|
113
112
|
`Low` is `PRIORITY-CONTRADICTED` — a smoke run selects on the tag, so it picks up work the QA
|
|
114
|
-
deliberately deprioritised. Inherit the declared priority; if the tag is right, the
|
|
115
|
-
|
|
116
|
-
(mode 1). Never leave both on record silently.
|
|
113
|
+
deliberately deprioritised. Inherit the declared priority; if the tag is right, fix the
|
|
114
|
+
declaration instead of leaving both on record.
|
|
117
115
|
|
|
118
116
|
---
|
|
119
117
|
|
|
@@ -207,11 +205,22 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
207
205
|
**Screen requirements** — check `<base>/${input:name}/requirements/`:
|
|
208
206
|
{{/cap}}
|
|
209
207
|
- If `spec.md` exists → read it as PRIMARY source (sections, fields, validation rules, business rules, states).
|
|
210
|
-
- `test-viewpoint.md` → **Viewpoint — review or draft** below, once every source is read. No question here.
|
|
211
208
|
{{#cap parallel-subagents}}
|
|
209
|
+
- If `test-viewpoint.md` exists → read it. If it only contains HTML comments (scaffold template), use `AskUserQuestion` to ask:
|
|
210
|
+
- **Fill test-viewpoint.md first** — I'll help you identify edge cases, known issues, and design decisions for this screen before generating tests
|
|
211
|
+
- **Continue without it** — generate tests from spec and other sources only
|
|
212
|
+
|
|
213
|
+
**A filled `test-viewpoint.md` is an INPUT — never an output.** Do NOT rewrite, re-order, or replace its viewpoint declarations as part of generating tests. It is the yardstick two scored axes measure the suite against (`traceability`, `atomicLedger`): rewrite it to match what you just generated and both read 100% by construction, while any viewpoint you quietly dropped stops being missing from anything. If the declared taxonomy genuinely does not fit the screen, **report the mismatch and ask** — propose the diff (ids added / removed and why) and let the QA decide. `sungen audit` reports `VIEWPOINT-BASELINE-CHANGED` and stops scoring both axes when this file moves, so an unannounced rewrite lowers the score rather than raising it.
|
|
212
214
|
|
|
213
215
|
**Context discovery (prefer an isolated agent).** Reading all sources here can flood this context. **Claude Code:** spawn the **`sungen-discovery`** sub-agent (Task tool, `subagent_type: sungen-discovery`) to read spec/figma/ui/live in isolation and return a **compact discovery report** (sources, completeness, conflicts, recommended route, key facts); use that report instead of pasting raw sources. **Copilot / no sub-agents:** do the reading inline as below.
|
|
214
216
|
{{/cap}}
|
|
217
|
+
{{^cap parallel-subagents}}
|
|
218
|
+
- If `test-viewpoint.md` exists → read it. If it only contains HTML comments (scaffold template), ask:
|
|
219
|
+
- **1) Fill test-viewpoint.md first** — identify edge cases, known issues, and design decisions before generating tests
|
|
220
|
+
- **2) Continue without it** — generate tests from spec and other sources only
|
|
221
|
+
|
|
222
|
+
**A filled `test-viewpoint.md` is an INPUT — never an output.** Do NOT rewrite, re-order, or replace its viewpoint declarations while generating tests. It is the yardstick two scored axes measure the suite against (`traceability`, `atomicLedger`): rewrite it to match what you just generated and both read 100% by construction, while any viewpoint you quietly dropped stops being missing from anything. If the declared taxonomy genuinely does not fit the screen, **report the mismatch and ask** — propose the diff (ids added / removed and why) and let the QA decide. `sungen audit` reports `VIEWPOINT-BASELINE-CHANGED` and stops scoring both axes when this file moves.
|
|
223
|
+
{{/cap}}
|
|
215
224
|
|
|
216
225
|
**Auto-detect visual source** — do NOT ask the user to pick a source. Instead, check what already exists and use it:
|
|
217
226
|
1. If `spec_figma.md` exists → read it as Figma supplement (PAT flow already completed during `add-screen`). Do NOT call any `mcp__figma__*` tool.
|
|
@@ -235,18 +244,6 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
235
244
|
|
|
236
245
|
**Cross-check + Design Inventory**: when the unit holds any design source (`ui/` images, `ui/figma-meta.md`, `spec_figma.md`), follow `sungen-tc-generation` § **Design Inventory** (reading order, one `UI-NNN` row per item, `spec+design` / `design-only` / `conflict`) and **write `requirements/ui-inventory.md`** before generating — from the discovery report's UI INVENTORY block when a sub-agent read the sources. Every `design-only` / `conflict` item must then be covered (`@ui:UI-NNN`) or deferred with a reason (`# UI-DEFERRED: UI-NNN — <reason>`) in the `.feature`; `sungen audit` reports `UI-ITEM-UNCOVERED` otherwise. Show the user the conflicts and design-only items — they are decisions, not details.
|
|
237
246
|
|
|
238
|
-
**Viewpoint — review or draft (no question asked).** Run `sungen viewpoint scan --screen <name> --json` (a flow: `--flow <name>`). It reads ids and item numbers only. Its `mode` (1 or 2) and `modeReason` name the row below — follow them; the table explains them:
|
|
239
|
-
|
|
240
|
-
| `requirements/test-viewpoint.md` | Mode | You |
|
|
241
|
-
|---|---|---|
|
|
242
|
-
| missing; nothing but the scaffold (headings, `<…>` placeholders, empty tables); or ONE OR MORE VP rows and every one of them lacks a `Kỳ vọng` | **2 — draft** | Write it from `sungen-viewpoint/viewpoint-template.md`, first line `drafted-by: ai`, keeping any row already there. Each row answers "test CÁI GÌ" and "kỳ vọng ra sao"; where the spec states the expected result, write it; only what the spec leaves open becomes a closed question in mục 5, never an invented expectation; every accepted-input class the spec names (whitelist, value list, conversion) gets an "accepted, no error" row, citing the list by name; a VP that needs a real device, account or time is `# MANUAL/ENV`. Then scan again. |
|
|
243
|
-
| first line `drafted-by: ai` | **2 — re-review** | Your draft, not yet accepted: you may revise it, from the spec and the review. |
|
|
244
|
-
| anything else — QA-written, with or without VP ids (a scaffold from an older sungen reads as this row too: delete the file to have it drafted) | **1 — review only** | Never edit it. Generate from it as written; a gap is reported, not filled. |
|
|
245
|
-
|
|
246
|
-
Then review it with `sungen-viewpoint/review-checklist.md` and the scan JSON (mode 2: fix your draft until only QA decisions remain) and write `requirements/viewpoint-review.md`, overwritten each run. Mode 2 also runs `sungen audit --screen <name> --no-baseline` before generating. Keep the review's row count for the hand-back.
|
|
247
|
-
|
|
248
|
-
**A filled `test-viewpoint.md` is an INPUT — never an output.** Do NOT rewrite, re-order, or replace its viewpoint declarations as part of generating tests — your own draft included: a draft is revised from the spec, never from the scenarios. It is the yardstick two scored axes measure the suite against (`traceability`, `atomicLedger`): rewrite it to match what you just generated and both read 100% by construction, while any viewpoint you quietly dropped stops being missing from anything. If the declared taxonomy genuinely does not fit the screen, **report the mismatch in `viewpoint-review.md`** — the diff (ids added / removed and why) — and let the QA decide. `sungen audit` reports `VIEWPOINT-BASELINE-CHANGED` and stops scoring both axes when this file moves, so an unannounced rewrite lowers the score rather than raising it.
|
|
249
|
-
|
|
250
247
|
Summarize what you found in requirements and present to the user.
|
|
251
248
|
|
|
252
249
|
{{#cap parallel-subagents}}
|
|
@@ -287,7 +284,6 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
287
284
|
- **BALANCE** → stop expanding secondary viewpoints; add business-core scenarios first.
|
|
288
285
|
- **TRACE** → align `VP-` ids with the viewpoint-overview.
|
|
289
286
|
- Stop when the gate PASSes and findings clear, **or** the budget is exhausted → report residual gaps honestly (never fake a pass).
|
|
290
|
-
- **Repair never edits a QA-written viewpoint (mode 1).** A finding whose fix is an edit to `test-viewpoint.md` becomes a row in `viewpoint-review.md` for the QA when it cites one of the file's VP ids (the checklist's audit rule; anything else goes in the hand-back); in a mode-2 draft, fix the draft.
|
|
291
287
|
|
|
292
288
|
5.6. **Record (reuse + observability).** Build the manifest and report usage:
|
|
293
289
|
- `sungen manifest --screen <name>` — fingerprints for next-run change detection. On a **re-run**, start the whole command by `sungen manifest --screen <name> --diff` and only regenerate scenarios whose spec section changed (keep/regenerate/retire).
|
|
@@ -301,7 +297,7 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
301
297
|
> **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.
|
|
302
298
|
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.
|
|
303
299
|
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.
|
|
304
|
-
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.
|
|
300
|
+
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.
|
|
305
301
|
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.
|
|
306
302
|
6. **Converge — show the trace.** Run `sungen trace --screen ${input:name}` and present: process map (phases + repair rounds), bottlenecks, **HUMAN-LOOP FOCUS** (@manual to verify), audit score + gate + residual gaps. Then offer next steps based on which tier was just generated:
|
|
307
303
|
{{/cap}}
|
|
@@ -367,13 +363,9 @@ Do not stop after writing the files. Close every run with, in this order:
|
|
|
367
363
|
1. **The traceability check below** — an untagged requirement becomes an uncovered gap at delivery.
|
|
368
364
|
2. **The harness result in one line** — score, gate verdict, and the axis the `SCORE-CAPPED` finding
|
|
369
365
|
names (that finding is the single answer to "how do I raise the score").
|
|
370
|
-
3.
|
|
371
|
-
of `requirements/viewpoint-review.md`).
|
|
372
|
-
4. **`AskUserQuestion` with the next actions** — never end with prose alone. Offer
|
|
366
|
+
3. **`AskUserQuestion` with the next actions** — never end with prose alone. Offer
|
|
373
367
|
`/sungen:run-test <name>` (recommended once the tiers are written), re-running `create-test` to
|
|
374
|
-
extend,
|
|
375
|
-
the decision `sungen audit --screen <name> --accept-viewpoint` — for the QA to run once the draft
|
|
376
|
-
is reviewed; you never run it yourself. A run that ends without this leaves the operator guessing.
|
|
368
|
+
extend, and "Done for now". A run that ends without this leaves the operator guessing.
|
|
377
369
|
|
|
378
370
|
### Requirement traceability
|
|
379
371
|
|