@sun-asterisk/sungen 3.1.2-beta.119 → 3.1.2-beta.121
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/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +33 -33
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/ledger.d.ts.map +1 -1
- package/dist/cli/commands/ledger.js +13 -5
- package/dist/cli/commands/ledger.js.map +1 -1
- package/dist/cli/commands/manifest.d.ts.map +1 -1
- package/dist/cli/commands/manifest.js +9 -9
- package/dist/cli/commands/manifest.js.map +1 -1
- package/dist/cli/commands/script-check.d.ts.map +1 -1
- package/dist/cli/commands/script-check.js +10 -8
- package/dist/cli/commands/script-check.js.map +1 -1
- package/dist/cli/commands/trace.d.ts.map +1 -1
- package/dist/cli/commands/trace.js +6 -4
- package/dist/cli/commands/trace.js.map +1 -1
- package/dist/harness/audit.js +1 -1
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/intent.d.ts +3 -0
- package/dist/harness/intent.d.ts.map +1 -1
- package/dist/harness/intent.js +11 -2
- package/dist/harness/intent.js.map +1 -1
- package/dist/harness/script-check.d.ts +3 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +16 -7
- package/dist/harness/script-check.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +1 -1
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +1 -1
- package/package.json +2 -2
- package/src/cli/commands/delivery.ts +32 -33
- package/src/cli/commands/ledger.ts +11 -5
- package/src/cli/commands/manifest.ts +8 -7
- package/src/cli/commands/script-check.ts +9 -7
- package/src/cli/commands/trace.ts +6 -4
- package/src/harness/audit.ts +2 -2
- package/src/harness/intent.ts +13 -2
- package/src/harness/script-check.ts +20 -7
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +1 -1
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent.js","sourceRoot":"","sources":["../../src/harness/intent.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"intent.js","sourceRoot":"","sources":["../../src/harness/intent.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,5 +35,7 @@ export declare function analyzeFaithfulness(specSrc: string, automatedTitles: Se
|
|
|
35
35
|
step: string;
|
|
36
36
|
}[];
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
/** The unit kind — drives the generated-spec subdir + the qa source dir. */
|
|
39
|
+
export type UnitKind = 'screen' | 'flow' | 'api';
|
|
40
|
+
export declare function runScriptCheck(screenDir: string, screenName: string, kind: UnitKind): Promise<ScriptCheckResult>;
|
|
39
41
|
//# sourceMappingURL=script-check.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-check.d.ts","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACvC,UAAU,EAAE,MAAM,EAAE,CAAC;IAErB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAgBtF;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC;;;cAEpD,MAAM;cAAQ,MAAM;;EAqBhD;
|
|
1
|
+
{"version":3,"file":"script-check.d.ts","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACvC,UAAU,EAAE,MAAM,EAAE,CAAC;IAErB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAgBtF;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC;;;cAEpD,MAAM;cAAQ,MAAM;;EAqBhD;AAqBD,4EAA4E;AAC5E,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AA6BjD,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsGtH"}
|
|
@@ -134,9 +134,14 @@ function normalize(src) {
|
|
|
134
134
|
.replace(/\n{3,}/g, '\n\n')
|
|
135
135
|
.trim();
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
/** Generated-spec subdir for a unit: screen → <name>, flow → flows/<name>, api → api/<name>. */
|
|
138
|
+
function specSubdir(dir, name, kind) {
|
|
139
|
+
return kind === 'flow' ? path.join(dir, 'flows', name) : kind === 'api' ? path.join(dir, 'api', name) : path.join(dir, name);
|
|
140
|
+
}
|
|
141
|
+
function findSpec(dir, name, kind) {
|
|
138
142
|
// Screens compile to <dir>/<name>/<feature>.spec.ts
|
|
139
143
|
// Flows compile to <dir>/flows/<name>/<feature>.spec.ts
|
|
144
|
+
// Api compile to <dir>/api/<name>/<feature>.spec.ts
|
|
140
145
|
// Scope the search to THIS target's own subdir — otherwise the first spec of
|
|
141
146
|
// ANY other screen/flow is returned, which (for an uncompiled flow) falsely
|
|
142
147
|
// reports the wrong screen's tests as drift.
|
|
@@ -152,18 +157,18 @@ function findSpec(dir, name, flowMode) {
|
|
|
152
157
|
hits.push(p);
|
|
153
158
|
}
|
|
154
159
|
};
|
|
155
|
-
const scoped =
|
|
160
|
+
const scoped = specSubdir(dir, name, kind);
|
|
156
161
|
if (!fs.existsSync(scoped))
|
|
157
162
|
return null; // no spec for this target (e.g. not compiled yet)
|
|
158
163
|
walk(scoped);
|
|
159
164
|
return hits[0] ?? null;
|
|
160
165
|
}
|
|
161
|
-
async function runScriptCheck(screenDir, screenName,
|
|
166
|
+
async function runScriptCheck(screenDir, screenName, kind) {
|
|
162
167
|
const featurePath = path.join(screenDir, 'features', `${screenName}.feature`);
|
|
163
168
|
const scenarios = (0, parse_1.loadScenarios)(featurePath);
|
|
164
169
|
const automated = scenarios.filter((s) => !s.manual);
|
|
165
170
|
const manual = scenarios.filter((s) => s.manual);
|
|
166
|
-
const committedSpec = findSpec(path.join(process.cwd(), 'specs', 'generated'), screenName,
|
|
171
|
+
const committedSpec = findSpec(path.join(process.cwd(), 'specs', 'generated'), screenName, kind);
|
|
167
172
|
const findings = [];
|
|
168
173
|
let specTitles = [];
|
|
169
174
|
let specSrc = '';
|
|
@@ -197,10 +202,14 @@ async function runScriptCheck(screenDir, screenName, flowMode) {
|
|
|
197
202
|
try {
|
|
198
203
|
const { CodeGenerator } = require('../generators/test-generator/code-generator');
|
|
199
204
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sungen-scriptcheck-'));
|
|
200
|
-
const qaSourceDir = path.join(process.cwd(), 'qa',
|
|
201
|
-
|
|
205
|
+
const qaSourceDir = path.join(process.cwd(), 'qa', kind === 'flow' ? 'flows' : kind === 'api' ? 'api' : 'screens');
|
|
206
|
+
// api units derive their unit id (api/<area>) from the feature path — like `generate --api`;
|
|
207
|
+
// screen/flow pass screenName + flowMode explicitly (unchanged → byte-identical regenerate).
|
|
208
|
+
const gen = kind === 'api'
|
|
209
|
+
? new CodeGenerator({ framework: 'playwright', runtimeData: true })
|
|
210
|
+
: new CodeGenerator({ framework: 'playwright', screenName, runtimeData: true, flowMode: kind === 'flow' });
|
|
202
211
|
await gen.generateAllTests(qaSourceDir, tmp, [featurePath]);
|
|
203
|
-
const fresh = findSpec(tmp, screenName,
|
|
212
|
+
const fresh = findSpec(tmp, screenName, kind);
|
|
204
213
|
if (fresh) {
|
|
205
214
|
const a = normalize(specSrc);
|
|
206
215
|
const b = normalize(fs.readFileSync(fresh, 'utf-8'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-check.js","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,8CAgBC;AASD,kDAuBC;
|
|
1
|
+
{"version":3,"file":"script-check.js","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":"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"}
|
|
@@ -45,7 +45,7 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
45
45
|
- **missing/empty bound param** → trace `{{var}}` to test-data or a prior `@api` response; fill it.
|
|
46
46
|
- **`expect.status` mismatch** → reconcile against `apis.yaml`/spec (the catalog is the oracle); **never hand-edit the generated spec** (re-`generate --api` instead).
|
|
47
47
|
- **flaky** → enforce self-cleaning flows, per-row isolation (`@cases`), `@concurrent` caps.
|
|
48
|
-
5. **
|
|
48
|
+
5. **Integrity + trace** — `sungen script-check --api <name>` (verify the spec is a 1:1 of the Gherkin; on DRIFT re-`generate --api`, never hand-edit) and `sungen trace --api <name>` (process map + HUMAN-LOOP FOCUS). Then report + offer next steps.
|
|
49
49
|
|
|
50
50
|
## Pre-run (phased — per `sungen-selector-fix` skill)
|
|
51
51
|
|
|
@@ -39,7 +39,7 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
39
39
|
2. **Compile**: `npx sungen generate --api <name>` → `specs/generated/api/<name>/`.
|
|
40
40
|
3. **Run**: `npx playwright test specs/generated/api/<name>/<name>.spec.ts`.
|
|
41
41
|
4. **Auto-fix** (use `sungen-error-mapping`): 401/403 → `@hybrid`+`@auth` or `Bearer :token` header (`sungen makeauth`); base_url unresolved → set `${X_URL}`; missing param → trace `{{var}}` to test-data/a prior `@api` response; `expect.status` mismatch → reconcile against `apis.yaml` (re-`generate --api`, never hand-edit the spec); flaky → self-clean + `@concurrent` caps.
|
|
42
|
-
5. **
|
|
42
|
+
5. **Integrity + trace** — `sungen script-check --api <name>` (1:1; on DRIFT re-`generate --api`, never hand-edit the spec) + `sungen trace --api <name>` (process map + HUMAN-LOOP FOCUS). Report + offer next steps.
|
|
43
43
|
|
|
44
44
|
## Pre-run (phased — per `sungen-selector-fix` skill)
|
|
45
45
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.1.2-beta.
|
|
3
|
+
"version": "3.1.2-beta.121",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=18.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sungen/driver-ui": "3.1.2-beta.
|
|
36
|
+
"@sungen/driver-ui": "3.1.2-beta.121",
|
|
37
37
|
"@anthropic-ai/sdk": "^0.71.0",
|
|
38
38
|
"@babel/parser": "^7.28.5",
|
|
39
39
|
"@babel/traverse": "^7.28.5",
|
|
@@ -52,47 +52,49 @@ function log(msg: string): void {
|
|
|
52
52
|
* `name` is kept as an alias of `featureBaseName` so existing callers/labels
|
|
53
53
|
* (preflight table, summary) read naturally — every visible row is per-feature.
|
|
54
54
|
*/
|
|
55
|
+
type UnitKind = 'screen' | 'flow' | 'api';
|
|
56
|
+
/** qa/ subfolder for a unit kind. */
|
|
57
|
+
const qaParent = (kind: UnitKind): string => (kind === 'flow' ? 'flows' : kind === 'api' ? 'api' : 'screens');
|
|
58
|
+
|
|
55
59
|
interface DeliveryTarget {
|
|
56
60
|
screen: string;
|
|
57
61
|
featureBaseName: string;
|
|
58
62
|
/** Alias of `featureBaseName` — preserves the old `target.name` call sites. */
|
|
59
63
|
name: string;
|
|
64
|
+
kind: UnitKind;
|
|
65
|
+
/** Back-compat: flows kept distinct labels/paths before api was added. */
|
|
60
66
|
isFlow: boolean;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
function makeTarget(screen: string, featureBaseName: string,
|
|
64
|
-
return { screen, featureBaseName, name: featureBaseName, isFlow };
|
|
69
|
+
function makeTarget(screen: string, featureBaseName: string, kind: UnitKind): DeliveryTarget {
|
|
70
|
+
return { screen, featureBaseName, name: featureBaseName, kind, isFlow: kind === 'flow' };
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/**
|
|
68
|
-
* List all `.feature` files inside a screen/flow as separate targets.
|
|
74
|
+
* List all `.feature` files inside a screen/flow/api unit as separate targets.
|
|
69
75
|
* Returns empty array when the directory has no features yet.
|
|
70
76
|
*/
|
|
71
|
-
function listFeatureTargets(cwd: string, screen: string,
|
|
72
|
-
const featuresDir = path.join(cwd, 'qa',
|
|
77
|
+
function listFeatureTargets(cwd: string, screen: string, kind: UnitKind): DeliveryTarget[] {
|
|
78
|
+
const featuresDir = path.join(cwd, 'qa', qaParent(kind), screen, 'features');
|
|
73
79
|
if (!fs.existsSync(featuresDir)) return [];
|
|
74
80
|
return fs.readdirSync(featuresDir)
|
|
75
81
|
.filter((f) => f.endsWith('.feature'))
|
|
76
|
-
.map((f) => makeTarget(screen, f.slice(0, -'.feature'.length),
|
|
82
|
+
.map((f) => makeTarget(screen, f.slice(0, -'.feature'.length), kind))
|
|
77
83
|
.sort((a, b) => a.featureBaseName.localeCompare(b.featureBaseName));
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
function listAllTargets(cwd: string): DeliveryTarget[] {
|
|
81
87
|
const targets: DeliveryTarget[] = [];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (const d of fs.readdirSync(
|
|
86
|
-
if (d.isDirectory()) targets.push(...listFeatureTargets(cwd, d.name,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const flowsDir = path.join(cwd, 'qa', 'flows');
|
|
91
|
-
if (fs.existsSync(flowsDir)) {
|
|
92
|
-
for (const d of fs.readdirSync(flowsDir, { withFileTypes: true })) {
|
|
93
|
-
if (d.isDirectory()) targets.push(...listFeatureTargets(cwd, d.name, true));
|
|
88
|
+
const scan = (kind: UnitKind, skip: (n: string) => boolean = () => false) => {
|
|
89
|
+
const root = path.join(cwd, 'qa', qaParent(kind));
|
|
90
|
+
if (!fs.existsSync(root)) return;
|
|
91
|
+
for (const d of fs.readdirSync(root, { withFileTypes: true })) {
|
|
92
|
+
if (d.isDirectory() && !skip(d.name)) targets.push(...listFeatureTargets(cwd, d.name, kind));
|
|
94
93
|
}
|
|
95
|
-
}
|
|
94
|
+
};
|
|
95
|
+
scan('screen');
|
|
96
|
+
scan('flow');
|
|
97
|
+
scan('api', (n) => n === 'flows'); // qa/api/<area> (api flows live under qa/api/flows — a follow-up)
|
|
96
98
|
|
|
97
99
|
return targets.sort((a, b) => a.featureBaseName.localeCompare(b.featureBaseName));
|
|
98
100
|
}
|
|
@@ -108,28 +110,24 @@ function listAllTargets(cwd: string): DeliveryTarget[] {
|
|
|
108
110
|
* feature file with the basename across all screens & flows.
|
|
109
111
|
*/
|
|
110
112
|
function resolveTargetsFromArg(cwd: string, name: string): DeliveryTarget[] {
|
|
111
|
-
if (fs.existsSync(path.join(cwd, 'qa', 'flows', name)))
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return listFeatureTargets(cwd, name, false);
|
|
116
|
-
}
|
|
117
|
-
// Treat as feature basename — find the parent screen/flow that hosts it.
|
|
113
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'flows', name))) return listFeatureTargets(cwd, name, 'flow');
|
|
114
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'screens', name))) return listFeatureTargets(cwd, name, 'screen');
|
|
115
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'api', name))) return listFeatureTargets(cwd, name, 'api');
|
|
116
|
+
// Treat as feature basename — find the parent unit that hosts it.
|
|
118
117
|
const candidates = listAllTargets(cwd).filter((t) => t.featureBaseName === name);
|
|
119
118
|
if (candidates.length > 0) return candidates;
|
|
120
119
|
// Fallback: treat as screen name even if directory missing (lets preflight
|
|
121
120
|
// surface the "feature file missing" error with the right path).
|
|
122
|
-
return [makeTarget(name, name,
|
|
121
|
+
return [makeTarget(name, name, 'screen')];
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
function qaDir(cwd: string, target: DeliveryTarget): string {
|
|
126
|
-
return path.join(cwd, 'qa', target.
|
|
125
|
+
return path.join(cwd, 'qa', qaParent(target.kind), target.screen);
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
function generatedDir(cwd: string, target: DeliveryTarget): string {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
: path.join(cwd, 'specs', 'generated', target.screen);
|
|
129
|
+
const sub = target.kind === 'flow' ? path.join('flows', target.screen) : target.kind === 'api' ? path.join('api', target.screen) : target.screen;
|
|
130
|
+
return path.join(cwd, 'specs', 'generated', sub);
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
// ----------------------------------------------------------------------------
|
|
@@ -262,7 +260,8 @@ function runPreflight(cwd: string, target: DeliveryTarget): PreflightCheck {
|
|
|
262
260
|
|
|
263
261
|
const featureOk = checkFeatureReal(featureFile);
|
|
264
262
|
const testDataOk = checkTestDataHasVars(testDataFile);
|
|
265
|
-
|
|
263
|
+
// API units have no DOM → no selectors; the catalog (apis.yaml) is the contract. N/A, not missing.
|
|
264
|
+
const selectorsOk = target.kind === 'api' ? true : checkSelectorsHasEntries(selectorsFile, target.featureBaseName);
|
|
266
265
|
const specOk = fs.existsSync(specFile);
|
|
267
266
|
const resultsOk = resultsFile !== null;
|
|
268
267
|
|
|
@@ -284,7 +283,7 @@ function runPreflight(cwd: string, target: DeliveryTarget): PreflightCheck {
|
|
|
284
283
|
}
|
|
285
284
|
if (!specOk) {
|
|
286
285
|
missing.push(`compiled .spec.ts missing: ${path.relative(cwd, specFile)}`);
|
|
287
|
-
suggestions.push(target.
|
|
286
|
+
suggestions.push(`sungen generate --${target.kind === 'flow' ? 'flow' : target.kind === 'api' ? 'api' : 'screen'} ${target.screen}`);
|
|
288
287
|
}
|
|
289
288
|
if (!resultsOk) {
|
|
290
289
|
const env = process.env.SUNGEN_ENV;
|
|
@@ -9,7 +9,8 @@ export function registerLedgerCommand(program: Command): void {
|
|
|
9
9
|
ledger
|
|
10
10
|
.command('record')
|
|
11
11
|
.description('Append a step event to the ledger')
|
|
12
|
-
.
|
|
12
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
13
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
13
14
|
.requiredOption('--step <name>', 'Step name (discovery | viewpoint | gherkin | audit | repair:1 ...)')
|
|
14
15
|
.option('--run <id>', 'Run id — groups all phases of one create-test invocation (else auto-segmented by time gap)')
|
|
15
16
|
.option('--model <id>', 'Model id')
|
|
@@ -19,10 +20,12 @@ export function registerLedgerCommand(program: Command): void {
|
|
|
19
20
|
.option('--note <text>', 'Free note')
|
|
20
21
|
.action((o) => {
|
|
21
22
|
try {
|
|
22
|
-
|
|
23
|
+
const name = o.screen || o.api;
|
|
24
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
25
|
+
recordEvent(name, {
|
|
23
26
|
step: o.step, runId: o.run, model: o.model, tokensIn: o.tokensIn, tokensOut: o.tokensOut, ms: o.ms, note: o.note,
|
|
24
27
|
});
|
|
25
|
-
console.log(`✓ ledger: ${
|
|
28
|
+
console.log(`✓ ledger: ${name} · ${o.step}`);
|
|
26
29
|
} catch (e) {
|
|
27
30
|
console.error('Error:', e instanceof Error ? e.message : e);
|
|
28
31
|
process.exit(1);
|
|
@@ -32,12 +35,15 @@ export function registerLedgerCommand(program: Command): void {
|
|
|
32
35
|
ledger
|
|
33
36
|
.command('report')
|
|
34
37
|
.description('Summarise ledger + efficiency verdicts (pulls audit score if present)')
|
|
35
|
-
.
|
|
38
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
39
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
36
40
|
.option('--all-runs', 'Aggregate ALL runs (default: latest run only)')
|
|
37
41
|
.option('--json', 'Output raw JSON')
|
|
38
42
|
.action((o) => {
|
|
39
43
|
try {
|
|
40
|
-
const
|
|
44
|
+
const name = o.screen || o.api;
|
|
45
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
46
|
+
const r = buildReport(name, { allRuns: o.allRuns });
|
|
41
47
|
if (o.json) { console.log(JSON.stringify(r, null, 2)); return; }
|
|
42
48
|
const scope = r.runScope === 'all' ? `all ${r.runs} runs` : `latest run${r.runs > 1 ? ` of ${r.runs}` : ''}`;
|
|
43
49
|
console.log(`\n━━━ Usage Ledger: ${r.screen} (${r.events} events · ${scope}) ━━━`);
|
|
@@ -4,10 +4,10 @@ import * as fs from 'fs';
|
|
|
4
4
|
import { buildManifest, diffManifest, loadManifest, saveManifest } from '../../harness/manifest';
|
|
5
5
|
|
|
6
6
|
function findScreenDir(name: string): string | null {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
for (const p of ['screens', 'flows', 'api']) {
|
|
8
|
+
const d = path.join(process.cwd(), 'qa', p, name);
|
|
9
|
+
if (fs.existsSync(d)) return d;
|
|
10
|
+
}
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -16,14 +16,15 @@ export function registerManifestCommand(program: Command): void {
|
|
|
16
16
|
.command('manifest')
|
|
17
17
|
.description('Spec-fingerprint manifest: build a scenario↔spec-section map, or diff to plan keep/regenerate/retire')
|
|
18
18
|
.option('-s, --screen <name>', 'Screen or flow name')
|
|
19
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
19
20
|
.option('--diff', 'Compare current spec vs stored manifest → change plan')
|
|
20
21
|
.option('--json', 'Output raw JSON')
|
|
21
22
|
.action((options) => {
|
|
22
23
|
try {
|
|
23
|
-
const name = options.screen;
|
|
24
|
-
if (!name) throw new Error('Provide --screen <name>');
|
|
24
|
+
const name = options.screen || options.api;
|
|
25
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
25
26
|
const dir = findScreenDir(name);
|
|
26
|
-
if (!dir) throw new Error(`
|
|
27
|
+
if (!dir) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
27
28
|
|
|
28
29
|
if (options.diff) {
|
|
29
30
|
const manifest = loadManifest(name);
|
|
@@ -8,18 +8,20 @@ export function registerScriptCheckCommand(program: Command): void {
|
|
|
8
8
|
.command('script-check')
|
|
9
9
|
.description('Verify the generated Playwright spec is a faithful 1:1 of the Gherkin feature (no hand-edit / stale drift)')
|
|
10
10
|
.option('-s, --screen <name>', 'Screen or flow name')
|
|
11
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
11
12
|
.option('--json', 'Output raw JSON')
|
|
12
13
|
.action(async (options) => {
|
|
13
14
|
try {
|
|
14
|
-
const name = options.screen;
|
|
15
|
-
if (!name) throw new Error('Provide --screen <name>');
|
|
15
|
+
const name = options.screen || options.api;
|
|
16
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
16
17
|
const screen = path.join(process.cwd(), 'qa', 'screens', name);
|
|
17
18
|
const flow = path.join(process.cwd(), 'qa', 'flows', name);
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const api = path.join(process.cwd(), 'qa', 'api', name);
|
|
20
|
+
const kind = fs.existsSync(screen) ? 'screen' : fs.existsSync(flow) ? 'flow' : fs.existsSync(api) ? 'api' : null;
|
|
21
|
+
const dir = kind === 'screen' ? screen : kind === 'flow' ? flow : kind === 'api' ? api : null;
|
|
22
|
+
if (!dir || !kind) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
21
23
|
|
|
22
|
-
const r = await runScriptCheck(dir, name,
|
|
24
|
+
const r = await runScriptCheck(dir, name, kind);
|
|
23
25
|
|
|
24
26
|
const outDir = path.join(process.cwd(), '.sungen', 'reports');
|
|
25
27
|
fs.mkdirSync(outDir, { recursive: true });
|
|
@@ -39,7 +41,7 @@ export function registerScriptCheckCommand(program: Command): void {
|
|
|
39
41
|
if (r.findings.length) { L(' findings:'); for (const f of r.findings) L(` • ${f}`); }
|
|
40
42
|
else L(' ✓ The test code faithfully reflects the Gherkin (1:1).');
|
|
41
43
|
L('');
|
|
42
|
-
if (r.drift === 'drift') L(
|
|
44
|
+
if (r.drift === 'drift') L(` → Fix: re-run \`sungen generate --${kind === 'api' ? 'api' : kind === 'flow' ? 'flow' : 'screen'} ${name}\` (or /sungen:run-test) so the spec matches the feature. Never hand-edit generated specs.`);
|
|
43
45
|
L('');
|
|
44
46
|
process.exit(r.status === 'OK' ? 0 : 2);
|
|
45
47
|
} catch (error) {
|
|
@@ -8,16 +8,18 @@ export function registerTraceCommand(program: Command): void {
|
|
|
8
8
|
.command('trace')
|
|
9
9
|
.description('Visualise the executed test-design process (workflow/skill steps, repair loops), find bottlenecks, and show where to focus human review')
|
|
10
10
|
.option('-s, --screen <name>', 'Screen or flow name')
|
|
11
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
11
12
|
.option('--json', 'Output raw JSON')
|
|
12
13
|
.option('--mermaid', 'Print only the Mermaid flowchart')
|
|
13
14
|
.action((options) => {
|
|
14
15
|
try {
|
|
15
|
-
const name = options.screen;
|
|
16
|
-
if (!name) throw new Error('Provide --screen <name>');
|
|
16
|
+
const name = options.screen || options.api;
|
|
17
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
17
18
|
const screen = path.join(process.cwd(), 'qa', 'screens', name);
|
|
18
19
|
const flow = path.join(process.cwd(), 'qa', 'flows', name);
|
|
19
|
-
const
|
|
20
|
-
|
|
20
|
+
const api = path.join(process.cwd(), 'qa', 'api', name);
|
|
21
|
+
const dir = fs.existsSync(screen) ? screen : fs.existsSync(flow) ? flow : fs.existsSync(api) ? api : null;
|
|
22
|
+
if (!dir) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
21
23
|
|
|
22
24
|
const r = buildTrace(dir, name);
|
|
23
25
|
if (options.json) { console.log(JSON.stringify(r, null, 2)); return; }
|
package/src/harness/audit.ts
CHANGED
|
@@ -112,8 +112,8 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
112
112
|
// A capability gate may need project context (the API gate resolves endpoint methods from the
|
|
113
113
|
// catalog) + the focus depth threshold (so it scores depth with the SAME bar as the UI gate).
|
|
114
114
|
const capGate = scoringCap?.gateProvider as
|
|
115
|
-
((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus; cwd: string; screenName: string; threshold: number }) => { gate: GateResult; depth: DepthResult }) | undefined;
|
|
116
|
-
const provided = capGate?.({ scenarios, viewpoints, catalog, focus: intent.focus, cwd: projectRootFromScreenDir(screenDir), screenName: catalogScreenName, threshold: depthThresholdFor(intent.focus) });
|
|
115
|
+
((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus; cwd: string; screenName: string; threshold: number; businessCriticalMethods?: string[] }) => { gate: GateResult; depth: DepthResult }) | undefined;
|
|
116
|
+
const provided = capGate?.({ scenarios, viewpoints, catalog, focus: intent.focus, cwd: projectRootFromScreenDir(screenDir), screenName: catalogScreenName, threshold: depthThresholdFor(intent.focus), businessCriticalMethods: intent.businessCriticalMethods });
|
|
117
117
|
const gate = provided?.gate ?? viewpointGate(scenarios, viewpoints, catalog);
|
|
118
118
|
const depth = provided?.depth ?? assertionDepth(scenarios, dataThemesFor(catalog, gate.pageType), intent.focus);
|
|
119
119
|
const claim = claimProof(scenarios, intent.focus);
|
package/src/harness/intent.ts
CHANGED
|
@@ -21,6 +21,9 @@ export interface IntentProfile {
|
|
|
21
21
|
focus: IntentFocus;
|
|
22
22
|
riskTier: 'high' | 'normal' | 'low';
|
|
23
23
|
tierScope: 'tier-1' | 'full';
|
|
24
|
+
/** End-user override (AO-6): HTTP methods the API gate treats as business-critical (depth-required).
|
|
25
|
+
* Default (undefined → the gate's POST/PUT/PATCH/DELETE) lets a project mark e.g. GET as critical. */
|
|
26
|
+
businessCriticalMethods?: string[];
|
|
24
27
|
source: 'context.md' | 'default';
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -45,6 +48,13 @@ export function readIntent(projectRoot: string): IntentProfile {
|
|
|
45
48
|
const m = text.match(new RegExp(`(?:^|\\n)\\s*${key}\\s*:\\s*([a-z0-9-]+)`));
|
|
46
49
|
return m?.[1];
|
|
47
50
|
};
|
|
51
|
+
// A comma/space/slash list (e.g. `business_critical_methods: post, put, patch, delete, get`).
|
|
52
|
+
const grabList = (key: string): string[] | undefined => {
|
|
53
|
+
const m = text.match(new RegExp(`(?:^|\\n)\\s*${key}\\s*:\\s*([a-z0-9,\\s/-]+)`));
|
|
54
|
+
if (!m) return undefined;
|
|
55
|
+
const items = m[1].split(/[,\s/]+/).map((s) => s.trim().toUpperCase()).filter(Boolean);
|
|
56
|
+
return items.length ? items : undefined;
|
|
57
|
+
};
|
|
48
58
|
|
|
49
59
|
const focusRaw = grab('focus');
|
|
50
60
|
const focus = (FOCI.includes(focusRaw as IntentFocus) ? focusRaw : DEFAULT_INTENT.focus) as IntentFocus;
|
|
@@ -53,6 +63,7 @@ export function readIntent(projectRoot: string): IntentProfile {
|
|
|
53
63
|
const scope = grab('tier_scope');
|
|
54
64
|
const tierScope = (['tier-1', 'full'].includes(scope as string) ? scope : DEFAULT_INTENT.tierScope) as IntentProfile['tierScope'];
|
|
55
65
|
|
|
56
|
-
const
|
|
57
|
-
|
|
66
|
+
const businessCriticalMethods = grabList('business_critical_methods');
|
|
67
|
+
const found = focusRaw || risk || scope || businessCriticalMethods;
|
|
68
|
+
return { focus, riskTier, tierScope, businessCriticalMethods, source: found ? 'context.md' : 'default' };
|
|
58
69
|
}
|
|
@@ -106,9 +106,18 @@ function normalize(src: string): string {
|
|
|
106
106
|
.trim();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
/** The unit kind — drives the generated-spec subdir + the qa source dir. */
|
|
110
|
+
export type UnitKind = 'screen' | 'flow' | 'api';
|
|
111
|
+
|
|
112
|
+
/** Generated-spec subdir for a unit: screen → <name>, flow → flows/<name>, api → api/<name>. */
|
|
113
|
+
function specSubdir(dir: string, name: string, kind: UnitKind): string {
|
|
114
|
+
return kind === 'flow' ? path.join(dir, 'flows', name) : kind === 'api' ? path.join(dir, 'api', name) : path.join(dir, name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function findSpec(dir: string, name: string, kind: UnitKind): string | null {
|
|
110
118
|
// Screens compile to <dir>/<name>/<feature>.spec.ts
|
|
111
119
|
// Flows compile to <dir>/flows/<name>/<feature>.spec.ts
|
|
120
|
+
// Api compile to <dir>/api/<name>/<feature>.spec.ts
|
|
112
121
|
// Scope the search to THIS target's own subdir — otherwise the first spec of
|
|
113
122
|
// ANY other screen/flow is returned, which (for an uncompiled flow) falsely
|
|
114
123
|
// reports the wrong screen's tests as drift.
|
|
@@ -121,19 +130,19 @@ function findSpec(dir: string, name: string, flowMode: boolean): string | null {
|
|
|
121
130
|
else if (e.name.endsWith('.spec.ts')) hits.push(p);
|
|
122
131
|
}
|
|
123
132
|
};
|
|
124
|
-
const scoped =
|
|
133
|
+
const scoped = specSubdir(dir, name, kind);
|
|
125
134
|
if (!fs.existsSync(scoped)) return null; // no spec for this target (e.g. not compiled yet)
|
|
126
135
|
walk(scoped);
|
|
127
136
|
return hits[0] ?? null;
|
|
128
137
|
}
|
|
129
138
|
|
|
130
|
-
export async function runScriptCheck(screenDir: string, screenName: string,
|
|
139
|
+
export async function runScriptCheck(screenDir: string, screenName: string, kind: UnitKind): Promise<ScriptCheckResult> {
|
|
131
140
|
const featurePath = path.join(screenDir, 'features', `${screenName}.feature`);
|
|
132
141
|
const scenarios = loadScenarios(featurePath);
|
|
133
142
|
const automated = scenarios.filter((s) => !s.manual);
|
|
134
143
|
const manual = scenarios.filter((s) => s.manual);
|
|
135
144
|
|
|
136
|
-
const committedSpec = findSpec(path.join(process.cwd(), 'specs', 'generated'), screenName,
|
|
145
|
+
const committedSpec = findSpec(path.join(process.cwd(), 'specs', 'generated'), screenName, kind);
|
|
137
146
|
|
|
138
147
|
const findings: string[] = [];
|
|
139
148
|
let specTitles: string[] = [];
|
|
@@ -167,10 +176,14 @@ export async function runScriptCheck(screenDir: string, screenName: string, flow
|
|
|
167
176
|
try {
|
|
168
177
|
const { CodeGenerator } = require('../generators/test-generator/code-generator');
|
|
169
178
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sungen-scriptcheck-'));
|
|
170
|
-
const qaSourceDir = path.join(process.cwd(), 'qa',
|
|
171
|
-
|
|
179
|
+
const qaSourceDir = path.join(process.cwd(), 'qa', kind === 'flow' ? 'flows' : kind === 'api' ? 'api' : 'screens');
|
|
180
|
+
// api units derive their unit id (api/<area>) from the feature path — like `generate --api`;
|
|
181
|
+
// screen/flow pass screenName + flowMode explicitly (unchanged → byte-identical regenerate).
|
|
182
|
+
const gen = kind === 'api'
|
|
183
|
+
? new CodeGenerator({ framework: 'playwright', runtimeData: true })
|
|
184
|
+
: new CodeGenerator({ framework: 'playwright', screenName, runtimeData: true, flowMode: kind === 'flow' });
|
|
172
185
|
await gen.generateAllTests(qaSourceDir, tmp, [featurePath]);
|
|
173
|
-
const fresh = findSpec(tmp, screenName,
|
|
186
|
+
const fresh = findSpec(tmp, screenName, kind);
|
|
174
187
|
if (fresh) {
|
|
175
188
|
const a = normalize(specSrc);
|
|
176
189
|
const b = normalize(fs.readFileSync(fresh, 'utf-8'));
|
|
@@ -45,7 +45,7 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
45
45
|
- **missing/empty bound param** → trace `{{var}}` to test-data or a prior `@api` response; fill it.
|
|
46
46
|
- **`expect.status` mismatch** → reconcile against `apis.yaml`/spec (the catalog is the oracle); **never hand-edit the generated spec** (re-`generate --api` instead).
|
|
47
47
|
- **flaky** → enforce self-cleaning flows, per-row isolation (`@cases`), `@concurrent` caps.
|
|
48
|
-
5. **
|
|
48
|
+
5. **Integrity + trace** — `sungen script-check --api <name>` (verify the spec is a 1:1 of the Gherkin; on DRIFT re-`generate --api`, never hand-edit) and `sungen trace --api <name>` (process map + HUMAN-LOOP FOCUS). Then report + offer next steps.
|
|
49
49
|
|
|
50
50
|
## Pre-run (phased — per `sungen-selector-fix` skill)
|
|
51
51
|
|
|
@@ -39,7 +39,7 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
39
39
|
2. **Compile**: `npx sungen generate --api <name>` → `specs/generated/api/<name>/`.
|
|
40
40
|
3. **Run**: `npx playwright test specs/generated/api/<name>/<name>.spec.ts`.
|
|
41
41
|
4. **Auto-fix** (use `sungen-error-mapping`): 401/403 → `@hybrid`+`@auth` or `Bearer :token` header (`sungen makeauth`); base_url unresolved → set `${X_URL}`; missing param → trace `{{var}}` to test-data/a prior `@api` response; `expect.status` mismatch → reconcile against `apis.yaml` (re-`generate --api`, never hand-edit the spec); flaky → self-clean + `@concurrent` caps.
|
|
42
|
-
5. **
|
|
42
|
+
5. **Integrity + trace** — `sungen script-check --api <name>` (1:1; on DRIFT re-`generate --api`, never hand-edit the spec) + `sungen trace --api <name>` (process map + HUMAN-LOOP FOCUS). Report + offer next steps.
|
|
43
43
|
|
|
44
44
|
## Pre-run (phased — per `sungen-selector-fix` skill)
|
|
45
45
|
|