@captain_z/zsk 1.6.1 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -32
- package/dist/bin.js +22 -15
- package/dist/bin.js.map +1 -1
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +209 -12
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/demo.d.ts +1 -0
- package/dist/commands/demo.js +341 -36
- package/dist/commands/demo.js.map +1 -1
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +102 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/issue.d.ts +2 -1
- package/dist/commands/issue.js +75 -30
- package/dist/commands/issue.js.map +1 -1
- package/dist/commands/module.js +42 -27
- package/dist/commands/module.js.map +1 -1
- package/dist/commands/prep.js +100 -6
- package/dist/commands/prep.js.map +1 -1
- package/dist/commands/project-init.js +63 -11
- package/dist/commands/project-init.js.map +1 -1
- package/dist/core/config.d.ts +14 -2
- package/dist/core/config.js +91 -12
- package/dist/core/config.js.map +1 -1
- package/dist/core/raw-manifest.d.ts +10 -0
- package/dist/core/raw-manifest.js +48 -1
- package/dist/core/raw-manifest.js.map +1 -1
- package/dist/core/scaffolder.js +5 -2
- package/dist/core/scaffolder.js.map +1 -1
- package/dist/core/workspace-layout.d.ts +53 -0
- package/dist/core/workspace-layout.js +108 -0
- package/dist/core/workspace-layout.js.map +1 -0
- package/package.json +2 -2
- package/schemas/zsk-config.schema.json +141 -11
- package/templates/issue/default/issue.md +9 -5
- package/templates/module/frontend-module/design.md +22 -0
- package/templates/module/frontend-module/module.yaml +4 -3
- package/templates/module/frontend-module/proposal.md +26 -1
- package/templates/module/frontend-module/spec.md +13 -0
- package/templates/module/frontend-module/tasks.md +15 -2
- package/templates/project-init/.zsk/config.yaml +41 -29
- package/templates/project-init/.zsk/docs/PROJECT-CONFIG.md +62 -0
- package/templates/project-init/.zsk/docs/SYSTEM-SPEC.md +59 -0
- package/templates/project-init/.zsk/modules/index.md +7 -0
- package/templates/project-init/.zsk/raws/backend/index.md +3 -0
- package/templates/project-init/.zsk/raws/index.md +7 -0
- package/templates/project-init/.zsk/raws/jira/index.md +3 -0
- package/templates/project-init/.zsk/raws/manual/index.md +3 -0
- package/templates/project-init/.zsk/raws/product/index.md +3 -0
- package/templates/project-init/.zsk/raws/qa/index.md +3 -0
- package/templates/project-init/.zsk/raws/ue/index.md +3 -0
- package/templates/module/frontend-module/acceptance.md +0 -18
- package/templates/module/frontend-module/archive.md +0 -17
- package/templates/module/frontend-module/commit.md +0 -15
- package/templates/module/frontend-module/demo-outline.md +0 -59
- package/templates/module/frontend-module/demo-report.md +0 -23
- package/templates/module/frontend-module/deploy.md +0 -18
- package/templates/module/frontend-module/ready.md +0 -12
- package/templates/module/frontend-module/review.md +0 -12
- package/templates/module/frontend-module/scenarios/index.md +0 -21
- package/templates/module/frontend-module/scenarios/p0-happy-path.spec.ts +0 -13
- package/templates/module/frontend-module/smoke.md +0 -21
- package/templates/module/frontend-module/verification.md +0 -6
- package/templates/module/frontend-module/verify.md +0 -12
- package/templates/project-init/.issues/README.md +0 -57
- package/templates/project-init/.issues/_taxonomy.md +0 -35
- package/templates/project-init/.issues/index.md +0 -7
- package/templates/project-init/.raws/README.md +0 -27
- package/templates/project-init/.raws/api-contracts/index.md +0 -22
- package/templates/project-init/.raws/backend-repositories/index.md +0 -12
- package/templates/project-init/.raws/design-assets/index.md +0 -25
- package/templates/project-init/.raws/design-sources/index.md +0 -24
- package/templates/project-init/.raws/index.md +0 -56
- package/templates/project-init/.raws/issues/index.md +0 -4
- package/templates/project-init/.raws/requirements/index.md +0 -23
- package/templates/project-init/.raws/testing/index.md +0 -22
- package/templates/project-init/.zsk/checkpoints/index.md +0 -4
- package/templates/project-init/.zsk/learning/index.md +0 -14
- package/templates/project-init/.zsk/learning/proposals/.gitkeep +0 -1
- package/templates/project-init/.zsk/resource-manifest.json +0 -55
- package/templates/project-init/.zsk/workflow-state.json +0 -6
- package/templates/project-init/docs/SYSTEM-SPEC.md +0 -53
- package/templates/project-init/docs/_module-index.md +0 -15
- package/templates/project-init/project-config.md +0 -154
- /package/templates/project-init/{.raws → .zsk/raws}/manifest.json +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAM3E,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAA2B,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAChD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACpD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,WAAW,CAAC,MAAM,mBAAmB,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,OAAO;IACT,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAA2B,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAChD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CACT;QACE,EAAE;QACF,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAM3E,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAA2B,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAChD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACpD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,WAAW,CAAC,MAAM,mBAAmB,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,OAAO;IACT,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAA2B,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAChD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CACT;QACE,EAAE;QACF,2HAA2H;QAC3H,mJAAmJ;KACpJ,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC"}
|
package/dist/commands/demo.d.ts
CHANGED
package/dist/commands/demo.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import YAML from "yaml";
|
|
4
5
|
import { resolvePath } from "../core/targets.js";
|
|
5
|
-
import { checkProjectConfig, readProjectConfig } from "../core/config.js";
|
|
6
|
+
import { checkProjectConfig, findModuleConfigFiles, readProjectConfig, } from "../core/config.js";
|
|
7
|
+
import { getWorkspacePath, interpolateModulePath } from "../core/workspace-layout.js";
|
|
6
8
|
export async function start(opts = {}) {
|
|
7
9
|
const ctx = await prepare(opts);
|
|
8
10
|
const now = new Date().toISOString();
|
|
11
|
+
const bridge = bridgeForMode(ctx.defaultMode, ctx.bridge);
|
|
9
12
|
const session = {
|
|
10
13
|
version: 1,
|
|
11
14
|
id: createSessionId(now),
|
|
@@ -15,7 +18,7 @@ export async function start(opts = {}) {
|
|
|
15
18
|
cursor: null,
|
|
16
19
|
evidenceDir: ctx.evidenceDir,
|
|
17
20
|
scenarioDir: ctx.scenarioDir,
|
|
18
|
-
bridge
|
|
21
|
+
bridge,
|
|
19
22
|
startedAt: now,
|
|
20
23
|
updatedAt: now,
|
|
21
24
|
note: opts.note,
|
|
@@ -38,14 +41,17 @@ export async function complete(opts = {}) {
|
|
|
38
41
|
await updateExisting(opts, "completed", "complete", true);
|
|
39
42
|
}
|
|
40
43
|
export async function run(opts = {}) {
|
|
44
|
+
const ctx = await prepare(opts);
|
|
41
45
|
const mode = opts.hybrid || (opts.computerUse && opts.playwright)
|
|
42
46
|
? "hybrid"
|
|
43
|
-
: opts.
|
|
44
|
-
? "
|
|
45
|
-
: opts.
|
|
46
|
-
? "
|
|
47
|
-
:
|
|
48
|
-
|
|
47
|
+
: opts.optimized
|
|
48
|
+
? "optimized"
|
|
49
|
+
: opts.computerUse
|
|
50
|
+
? "computer-use"
|
|
51
|
+
: opts.playwright
|
|
52
|
+
? "playwright"
|
|
53
|
+
: ctx.defaultMode;
|
|
54
|
+
const bridge = bridgeForMode(mode, ctx.bridge);
|
|
49
55
|
const existing = await readSession(ctx);
|
|
50
56
|
const now = new Date().toISOString();
|
|
51
57
|
const session = existing ?? {
|
|
@@ -57,14 +63,14 @@ export async function run(opts = {}) {
|
|
|
57
63
|
cursor: null,
|
|
58
64
|
evidenceDir: ctx.evidenceDir,
|
|
59
65
|
scenarioDir: ctx.scenarioDir,
|
|
60
|
-
bridge
|
|
66
|
+
bridge,
|
|
61
67
|
startedAt: now,
|
|
62
68
|
updatedAt: now,
|
|
63
69
|
events: [],
|
|
64
70
|
};
|
|
65
71
|
session.status = "running";
|
|
66
72
|
session.mode = mode;
|
|
67
|
-
session.bridge =
|
|
73
|
+
session.bridge = bridge;
|
|
68
74
|
session.updatedAt = now;
|
|
69
75
|
session.note = opts.note ?? session.note;
|
|
70
76
|
session.events.push({
|
|
@@ -72,12 +78,17 @@ export async function run(opts = {}) {
|
|
|
72
78
|
type: `run:${mode}`,
|
|
73
79
|
note: opts.note ?? automationNote(mode),
|
|
74
80
|
});
|
|
75
|
-
await persistSession(ctx, session);
|
|
76
81
|
if (mode === "hybrid")
|
|
77
82
|
await ensureBridgeArtifacts(ctx, session);
|
|
83
|
+
if (mode === "optimized")
|
|
84
|
+
await ensureOptimizedArtifacts(ctx, session);
|
|
85
|
+
await persistSession(ctx, session);
|
|
78
86
|
console.log(pc.green(`✓ zsk demo run prepared (${mode})`));
|
|
87
|
+
if (mode === "optimized") {
|
|
88
|
+
console.log(pc.dim("optimized: raw testing inputs -> test-plan.json -> Playwright spec; no MCP or bridge fallback."));
|
|
89
|
+
}
|
|
79
90
|
if (mode === "computer-use") {
|
|
80
|
-
console.log(pc.yellow("! Computer Use is
|
|
91
|
+
console.log(pc.yellow("! Computer Use is the preferred visual/system observation lane when available; record fallback if the runtime does not provide it."));
|
|
81
92
|
}
|
|
82
93
|
if (mode === "hybrid") {
|
|
83
94
|
console.log(pc.dim("bridge: configured decision tool understands/chooses operations; Playwright executes and records when feasible."));
|
|
@@ -88,7 +99,7 @@ export async function run(opts = {}) {
|
|
|
88
99
|
console.log(pc.dim(`execution: ${session.bridge.executionFile}`));
|
|
89
100
|
}
|
|
90
101
|
}
|
|
91
|
-
if (mode === "playwright" || mode === "hybrid") {
|
|
102
|
+
if (mode === "playwright" || mode === "hybrid" || mode === "optimized") {
|
|
92
103
|
console.log(pc.dim(`scenario dir: ${session.scenarioDir}`));
|
|
93
104
|
}
|
|
94
105
|
printSession(session);
|
|
@@ -159,16 +170,16 @@ export async function scenarioGenerate(opts = {}) {
|
|
|
159
170
|
version: 1,
|
|
160
171
|
module: ctx.moduleId,
|
|
161
172
|
inputs: {
|
|
162
|
-
testCase: opts.testCase ?? "
|
|
173
|
+
testCase: opts.testCase ?? "unmapped .zsk/raws/qa/testing test case",
|
|
163
174
|
prdRefs: [],
|
|
164
175
|
specRefs: [],
|
|
165
176
|
designRefs: [],
|
|
166
177
|
taskRefs: [],
|
|
167
178
|
pageStructure: {
|
|
168
179
|
kind: snapshot ? "aria_snapshot" : "playwright_mcp",
|
|
169
|
-
source: opts.snapshot ?? "
|
|
180
|
+
source: opts.snapshot ?? "unmapped accessibility snapshot, Computer Use observation, or compatible handoff",
|
|
170
181
|
},
|
|
171
|
-
|
|
182
|
+
uiObservationHandoff: "optional Computer Use, Playwright MCP/ARIA/CDP, documented manual evidence, or supported Browser Use handoff path",
|
|
172
183
|
},
|
|
173
184
|
output: {
|
|
174
185
|
playwrightSpec: `${ctx.scenarioDir}/${generatedName}.spec.ts`,
|
|
@@ -181,8 +192,9 @@ export async function scenarioGenerate(opts = {}) {
|
|
|
181
192
|
``,
|
|
182
193
|
`test.describe("${ctx.moduleId} synthesized scenarios", () => {`,
|
|
183
194
|
` test("${escapeForDoubleQuotedString(opts.name ?? "generated from test case and aria snapshot")}", async ({ page }) => {`,
|
|
184
|
-
`
|
|
185
|
-
`
|
|
195
|
+
` const baseUrl = process.env.ZSK_DEMO_BASE_URL;`,
|
|
196
|
+
` if (!baseUrl) throw new Error("Set ZSK_DEMO_BASE_URL to run this scenario.");`,
|
|
197
|
+
` await page.goto(baseUrl);`,
|
|
186
198
|
``,
|
|
187
199
|
...generatedSteps.map((step) => ` ${step}`),
|
|
188
200
|
` });`,
|
|
@@ -247,31 +259,28 @@ async function prepare(opts) {
|
|
|
247
259
|
}
|
|
248
260
|
const config = await readProjectConfig(target);
|
|
249
261
|
const demoConfig = config.automation?.demo;
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const bridgeExecution = interpolate(demoConfig?.bridge?.executionFile ?? `.zsk/demo-sessions/{module}/playwright-execution.json`, moduleId);
|
|
256
|
-
const sessionRoot = resolve(target, ".zsk", "demo-sessions", moduleId);
|
|
262
|
+
const evidenceDir = interpolate(demoConfig?.evidenceDir ?? getWorkspacePath(config, "demoEvidence", { module: moduleId }), moduleId);
|
|
263
|
+
const scenarioDir = interpolate(demoConfig?.scenarioDir ?? getWorkspacePath(config, "playwrightSpecs", { module: moduleId }), moduleId);
|
|
264
|
+
const bridgePlan = interpolate(demoConfig?.bridge?.planFile ?? getWorkspacePath(config, "bridgeOperationPlan", { module: moduleId }), moduleId);
|
|
265
|
+
const bridgeExecution = interpolate(demoConfig?.bridge?.executionFile ?? getWorkspacePath(config, "bridgeExecution", { module: moduleId }), moduleId);
|
|
266
|
+
const sessionRoot = resolve(target, getWorkspacePath(config, "playwrightExecution", { module: moduleId }));
|
|
257
267
|
await mkdir(sessionRoot, { recursive: true });
|
|
258
268
|
await mkdir(resolve(target, evidenceDir), { recursive: true });
|
|
259
269
|
await mkdir(resolve(target, scenarioDir), { recursive: true });
|
|
260
|
-
await mkdir(resolve(target, bridgePlan, ".."), { recursive: true });
|
|
261
|
-
await mkdir(resolve(target, bridgeExecution, ".."), { recursive: true });
|
|
262
270
|
return {
|
|
263
271
|
target,
|
|
264
272
|
moduleId,
|
|
265
273
|
config,
|
|
266
274
|
sessionFile: join(sessionRoot, "current.json"),
|
|
267
|
-
workflowStateFile: resolve(target,
|
|
275
|
+
workflowStateFile: resolve(target, getWorkspacePath(config, "workflowState")),
|
|
268
276
|
evidenceDir,
|
|
269
277
|
evidenceDirAbs: resolve(target, evidenceDir),
|
|
270
278
|
scenarioDir,
|
|
271
279
|
scenarioDirAbs: resolve(target, scenarioDir),
|
|
280
|
+
playwrightAuthState: getWorkspacePath(config, "playwrightAuthState", { module: moduleId }),
|
|
272
281
|
defaultMode: normalizeMode(demoConfig?.defaultMode),
|
|
273
282
|
bridge: {
|
|
274
|
-
decisionTool: demoConfig?.bridge?.decisionTool ?? "playwright_mcp",
|
|
283
|
+
decisionTool: demoConfig?.bridge?.decisionTool ?? (demoConfig?.computerUse?.enabled ? "computer_use" : "playwright_mcp"),
|
|
275
284
|
executionTool: demoConfig?.bridge?.executionTool ?? "playwright",
|
|
276
285
|
planFile: bridgePlan,
|
|
277
286
|
executionFile: bridgeExecution,
|
|
@@ -311,15 +320,15 @@ async function ensureBridgeArtifacts(ctx, session) {
|
|
|
311
320
|
functionPoint: session.cursor ?? "next-demo-function-point",
|
|
312
321
|
observation: {
|
|
313
322
|
source: observationSourceForDecisionTool(session.bridge.decisionTool),
|
|
314
|
-
summary: "
|
|
323
|
+
summary: "Fill from Computer Use observation, Playwright MCP/ARIA/CDP handoff, documented manual evidence, or supported Browser Use note before execution.",
|
|
315
324
|
rawRef: "",
|
|
316
325
|
},
|
|
317
326
|
operation: {
|
|
318
|
-
intent: "
|
|
327
|
+
intent: "Describe next demo operation before execution.",
|
|
319
328
|
locator: {
|
|
320
329
|
kind: "role",
|
|
321
330
|
role: "button",
|
|
322
|
-
name: "
|
|
331
|
+
name: "Unspecified",
|
|
323
332
|
},
|
|
324
333
|
action: {
|
|
325
334
|
type: "click",
|
|
@@ -353,6 +362,275 @@ async function ensureBridgeArtifacts(ctx, session) {
|
|
|
353
362
|
updatedAt: now,
|
|
354
363
|
});
|
|
355
364
|
}
|
|
365
|
+
async function ensureOptimizedArtifacts(ctx, session) {
|
|
366
|
+
const moduleConfig = await readModuleConfig(ctx.target, ctx.moduleId);
|
|
367
|
+
const rawInputs = [
|
|
368
|
+
...(moduleConfig.tests?.raw_cases ?? []),
|
|
369
|
+
...((moduleConfig.tests?.raw_cases?.length ?? 0) > 0 ? [] : (moduleConfig.sources?.testing ?? [])),
|
|
370
|
+
];
|
|
371
|
+
if (rawInputs.length === 0) {
|
|
372
|
+
console.error(pc.red("✗ optimized demo stopped: no raw testing inputs mapped"));
|
|
373
|
+
console.error(pc.dim("Add .zsk/raws/qa/testing cases to .zsk/modules/{module}/module.yaml tests.raw_cases or sources.testing."));
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
const testPlanRel = chooseTestPlanPath(moduleConfig, ctx.scenarioDir);
|
|
377
|
+
const specRel = chooseSpecPath(moduleConfig, ctx.scenarioDir, ctx.moduleId);
|
|
378
|
+
const now = new Date().toISOString();
|
|
379
|
+
const authValidation = await validatePlaywrightAuthState(ctx.target, ctx.playwrightAuthState, now);
|
|
380
|
+
if (authValidation.status === "invalid") {
|
|
381
|
+
console.error(pc.red(`✗ optimized demo stopped: invalid Playwright storageState at ${ctx.playwrightAuthState}`));
|
|
382
|
+
console.error(pc.dim(authValidation.message));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
session.auth = authValidation;
|
|
386
|
+
const cases = [];
|
|
387
|
+
for (const rawInput of rawInputs) {
|
|
388
|
+
const rawPath = resolve(ctx.target, rawInput);
|
|
389
|
+
const content = await readOptional(rawPath);
|
|
390
|
+
if (!content.trim()) {
|
|
391
|
+
console.error(pc.red(`✗ optimized demo stopped: raw testing input is empty or missing: ${rawInput}`));
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
const title = inferScenarioName(content) ?? rawInput.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "generated case";
|
|
395
|
+
const fillValue = inferFillValue(content);
|
|
396
|
+
const expectedText = inferExpectedText(content, fillValue);
|
|
397
|
+
const caseId = slugify(`${ctx.moduleId}-${title}`);
|
|
398
|
+
const steps = [
|
|
399
|
+
{
|
|
400
|
+
id: "step-1",
|
|
401
|
+
intent: "Open the demo entry page",
|
|
402
|
+
action: "goto",
|
|
403
|
+
target: "{{baseUrl}}",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "step-2",
|
|
407
|
+
intent: "Perform the primary user input from the raw test case",
|
|
408
|
+
action: "fill",
|
|
409
|
+
locator: {
|
|
410
|
+
strategy: "role",
|
|
411
|
+
role: "textbox",
|
|
412
|
+
},
|
|
413
|
+
value: fillValue,
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: "step-3",
|
|
417
|
+
intent: "Submit or advance the flow",
|
|
418
|
+
action: "click",
|
|
419
|
+
locator: {
|
|
420
|
+
strategy: "role",
|
|
421
|
+
role: "button",
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
id: "step-4",
|
|
426
|
+
intent: "Verify the expected visible result",
|
|
427
|
+
action: "assert",
|
|
428
|
+
locator: {
|
|
429
|
+
strategy: "text",
|
|
430
|
+
text: expectedText,
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
];
|
|
434
|
+
cases.push({
|
|
435
|
+
id: caseId,
|
|
436
|
+
title,
|
|
437
|
+
source: rawInput,
|
|
438
|
+
priority: "P0",
|
|
439
|
+
preconditions: [
|
|
440
|
+
"Raw test case is mapped in module.yaml.",
|
|
441
|
+
`If authentication is required, Playwright storageState is prepared under ${ctx.playwrightAuthState} and gitignored.`,
|
|
442
|
+
"Computer Use observation is preferred for visual/current-page refinement when available; otherwise use Playwright MCP/ARIA/CDP, manual evidence, or Browser Use only in runtimes that support it.",
|
|
443
|
+
],
|
|
444
|
+
uiObservationBoundary: {
|
|
445
|
+
role: "observation-only",
|
|
446
|
+
allowedOutput: "structured observation with URL, page title, visible targets, role/label hints, auth/session note, and privacy note",
|
|
447
|
+
prohibitedOutputs: ["test-plan.json", ".spec.ts"],
|
|
448
|
+
},
|
|
449
|
+
steps,
|
|
450
|
+
assertions: [
|
|
451
|
+
{
|
|
452
|
+
intent: "Expected result from raw test case is visible",
|
|
453
|
+
type: "visible",
|
|
454
|
+
locator: {
|
|
455
|
+
strategy: "text",
|
|
456
|
+
text: expectedText,
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
risks: [
|
|
461
|
+
"Generated locator names must be refined from Computer Use, Playwright MCP/ARIA/CDP, manual evidence, or supported Browser Use observation before promotion.",
|
|
462
|
+
"Do not fall back to Playwright MCP or the hybrid bridge in optimized mode.",
|
|
463
|
+
],
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
await writeJson(resolve(ctx.target, testPlanRel), {
|
|
467
|
+
version: 1,
|
|
468
|
+
mode: "optimized",
|
|
469
|
+
module: ctx.moduleId,
|
|
470
|
+
session: session.id,
|
|
471
|
+
generatedAt: now,
|
|
472
|
+
toolchain: ["ui-observation", "playwright-cli", "playwright-skills", "playwright-test"],
|
|
473
|
+
forbiddenSurfaces: ["playwright_mcp", "hybrid_bridge"],
|
|
474
|
+
sources: {
|
|
475
|
+
testing: rawInputs,
|
|
476
|
+
},
|
|
477
|
+
auth: {
|
|
478
|
+
storageState: ctx.playwrightAuthState,
|
|
479
|
+
validation: authValidation,
|
|
480
|
+
gitignoreRequired: true,
|
|
481
|
+
runtimeEnv: "ZSK_PLAYWRIGHT_STORAGE_STATE",
|
|
482
|
+
note: authValidation.status === "valid"
|
|
483
|
+
? "Storage state was validated as a reusable Playwright login/session state. It may contain sensitive cookies/localStorage and must not be committed."
|
|
484
|
+
: "No reusable storageState file was found. Public demos may proceed; authenticated demos must capture and validate storageState before completion.",
|
|
485
|
+
},
|
|
486
|
+
uiObservationHandoff: {
|
|
487
|
+
requiredBeforePromotion: true,
|
|
488
|
+
writesRepoArtifacts: false,
|
|
489
|
+
expectedShape: {
|
|
490
|
+
url: "",
|
|
491
|
+
title: "",
|
|
492
|
+
humanGoal: "",
|
|
493
|
+
visibleTargets: [],
|
|
494
|
+
locatorHints: [],
|
|
495
|
+
authSessionNote: "",
|
|
496
|
+
privacyNote: "",
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
output: {
|
|
500
|
+
playwrightSpec: specRel,
|
|
501
|
+
evidenceDir: session.evidenceDir,
|
|
502
|
+
},
|
|
503
|
+
cases,
|
|
504
|
+
});
|
|
505
|
+
const specPath = resolve(ctx.target, specRel);
|
|
506
|
+
await mkdir(resolve(specPath, ".."), { recursive: true });
|
|
507
|
+
await writeFile(specPath, renderOptimizedSpec(ctx.moduleId, cases), "utf8");
|
|
508
|
+
console.log(pc.dim(`test plan: ${testPlanRel}`));
|
|
509
|
+
console.log(pc.dim(`spec: ${specRel}`));
|
|
510
|
+
}
|
|
511
|
+
async function readModuleConfig(target, moduleId) {
|
|
512
|
+
const config = await readProjectConfig(target);
|
|
513
|
+
const moduleFiles = await findModuleConfigFiles(target, config);
|
|
514
|
+
for (const file of moduleFiles) {
|
|
515
|
+
const parsed = YAML.parse(await readFile(file, "utf8"));
|
|
516
|
+
if (isModuleConfig(parsed) && parsed.module?.id === moduleId)
|
|
517
|
+
return parsed;
|
|
518
|
+
}
|
|
519
|
+
console.error(pc.red(`✗ optimized demo stopped: module config not found for ${moduleId}`));
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
function chooseTestPlanPath(moduleConfig, scenarioDir) {
|
|
523
|
+
return (moduleConfig.tests?.derived_cases?.find((entry) => entry.endsWith(".json")) ??
|
|
524
|
+
join(scenarioDir, "test-plan.json"));
|
|
525
|
+
}
|
|
526
|
+
function chooseSpecPath(moduleConfig, scenarioDir, moduleId) {
|
|
527
|
+
const e2e = moduleConfig.tests?.automated?.e2e?.[0];
|
|
528
|
+
if (e2e?.endsWith(".ts"))
|
|
529
|
+
return e2e;
|
|
530
|
+
return join(e2e ?? scenarioDir, `${moduleId}-generated.spec.ts`);
|
|
531
|
+
}
|
|
532
|
+
function renderOptimizedSpec(moduleId, cases) {
|
|
533
|
+
const lines = [
|
|
534
|
+
`import { test, expect } from "@playwright/test";`,
|
|
535
|
+
``,
|
|
536
|
+
`const storageState = process.env.ZSK_PLAYWRIGHT_STORAGE_STATE;`,
|
|
537
|
+
`if (storageState) test.use({ storageState });`,
|
|
538
|
+
``,
|
|
539
|
+
`test.describe("${escapeForDoubleQuotedString(moduleId)} optimized demo scenarios", () => {`,
|
|
540
|
+
];
|
|
541
|
+
for (const testCase of cases) {
|
|
542
|
+
const fill = testCase.steps.find((step) => step.action === "fill");
|
|
543
|
+
const click = testCase.steps.find((step) => step.action === "click");
|
|
544
|
+
const assertion = testCase.steps.find((step) => step.action === "assert");
|
|
545
|
+
lines.push(` test("${escapeForDoubleQuotedString(testCase.title)}", async ({ page }) => {`, ` const baseUrl = process.env.ZSK_DEMO_BASE_URL;`, ` if (!baseUrl) throw new Error("Set ZSK_DEMO_BASE_URL to run this scenario.");`, ` await page.goto(baseUrl);`, ``, ` // Source: ${escapeForLineComment(testCase.source)}`, ` // ZSK_GENERATED_REFINEMENT_REQUIRED: refine locator names from Computer Use, Playwright MCP/ARIA/CDP, manual evidence, or compatible Browser Use before promotion.`);
|
|
546
|
+
if (fill?.locator?.role && fill.value) {
|
|
547
|
+
lines.push(` await ${renderRoleLocator(fill.locator.role, fill.locator.name)}.fill(${quote(fill.value)});`);
|
|
548
|
+
}
|
|
549
|
+
if (click?.locator?.role) {
|
|
550
|
+
lines.push(` await ${renderRoleLocator(click.locator.role, click.locator.name)}.click();`);
|
|
551
|
+
}
|
|
552
|
+
if (assertion?.locator?.text) {
|
|
553
|
+
lines.push(` await expect(page.getByText(${quote(assertion.locator.text)})).toBeVisible();`);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
lines.push(` await expect(page).toHaveURL(/.+/);`);
|
|
557
|
+
}
|
|
558
|
+
lines.push(` });`, ``);
|
|
559
|
+
}
|
|
560
|
+
lines.push(`});`, ``);
|
|
561
|
+
return lines.join("\n");
|
|
562
|
+
}
|
|
563
|
+
async function validatePlaywrightAuthState(target, storageState, validatedAt) {
|
|
564
|
+
const file = resolve(target, storageState);
|
|
565
|
+
let parsed;
|
|
566
|
+
try {
|
|
567
|
+
parsed = JSON.parse(await readFile(file, "utf8"));
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
571
|
+
? String(error.code)
|
|
572
|
+
: "";
|
|
573
|
+
if (code === "ENOENT") {
|
|
574
|
+
return {
|
|
575
|
+
storageState,
|
|
576
|
+
status: "missing",
|
|
577
|
+
validatedAt,
|
|
578
|
+
cookies: 0,
|
|
579
|
+
origins: 0,
|
|
580
|
+
message: "storageState file does not exist yet",
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
storageState,
|
|
585
|
+
status: "invalid",
|
|
586
|
+
validatedAt,
|
|
587
|
+
cookies: 0,
|
|
588
|
+
origins: 0,
|
|
589
|
+
message: "storageState file is not valid JSON",
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (!isRecord(parsed)) {
|
|
593
|
+
return {
|
|
594
|
+
storageState,
|
|
595
|
+
status: "invalid",
|
|
596
|
+
validatedAt,
|
|
597
|
+
cookies: 0,
|
|
598
|
+
origins: 0,
|
|
599
|
+
message: "storageState JSON must be an object",
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const cookies = Array.isArray(parsed.cookies) ? parsed.cookies.length : -1;
|
|
603
|
+
const origins = Array.isArray(parsed.origins) ? parsed.origins.length : -1;
|
|
604
|
+
if (cookies < 0 || origins < 0) {
|
|
605
|
+
return {
|
|
606
|
+
storageState,
|
|
607
|
+
status: "invalid",
|
|
608
|
+
validatedAt,
|
|
609
|
+
cookies: Math.max(cookies, 0),
|
|
610
|
+
origins: Math.max(origins, 0),
|
|
611
|
+
message: "storageState must include Playwright cookies and origins arrays",
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
if (cookies === 0 && origins === 0) {
|
|
615
|
+
return {
|
|
616
|
+
storageState,
|
|
617
|
+
status: "invalid",
|
|
618
|
+
validatedAt,
|
|
619
|
+
cookies,
|
|
620
|
+
origins,
|
|
621
|
+
message: "storageState contains no cookies or localStorage origins, so it cannot prove reusable login/session credentials",
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
storageState,
|
|
626
|
+
status: "valid",
|
|
627
|
+
validatedAt,
|
|
628
|
+
cookies,
|
|
629
|
+
origins,
|
|
630
|
+
message: "storageState is parseable and contains reusable session material",
|
|
631
|
+
loginBypassEvidence: "Generated Playwright specs can reuse this state through ZSK_PLAYWRIGHT_STORAGE_STATE.",
|
|
632
|
+
};
|
|
633
|
+
}
|
|
356
634
|
async function updateWorkflowState(file, moduleId, session) {
|
|
357
635
|
let state = { version: 1, modules: {} };
|
|
358
636
|
try {
|
|
@@ -389,17 +667,18 @@ function createSessionId(now) {
|
|
|
389
667
|
return `demo-${now.replace(/[-:.TZ]/g, "").slice(0, 14)}`;
|
|
390
668
|
}
|
|
391
669
|
function interpolate(value, moduleId) {
|
|
392
|
-
return value
|
|
670
|
+
return interpolateModulePath(value, moduleId);
|
|
393
671
|
}
|
|
394
672
|
function normalizeMode(value) {
|
|
395
673
|
if (value === "manual" ||
|
|
396
674
|
value === "script" ||
|
|
675
|
+
value === "optimized" ||
|
|
397
676
|
value === "playwright" ||
|
|
398
677
|
value === "computer-use" ||
|
|
399
678
|
value === "hybrid") {
|
|
400
679
|
return value;
|
|
401
680
|
}
|
|
402
|
-
return "
|
|
681
|
+
return "optimized";
|
|
403
682
|
}
|
|
404
683
|
function mapWorkflowStatus(status) {
|
|
405
684
|
if (status === "completed")
|
|
@@ -407,14 +686,19 @@ function mapWorkflowStatus(status) {
|
|
|
407
686
|
return status;
|
|
408
687
|
}
|
|
409
688
|
function automationNote(mode) {
|
|
689
|
+
if (mode === "optimized")
|
|
690
|
+
return "Generated test-plan.json and Playwright spec from raw testing inputs.";
|
|
410
691
|
if (mode === "playwright")
|
|
411
692
|
return "Prepared Playwright demo run and scenario preservation paths.";
|
|
412
693
|
if (mode === "hybrid")
|
|
413
694
|
return "Prepared decision-tool -> Playwright bridge run.";
|
|
414
695
|
if (mode === "computer-use")
|
|
415
|
-
return "Prepared explicit
|
|
696
|
+
return "Prepared explicit Computer Use observation run with compatible fallback evidence if unavailable.";
|
|
416
697
|
return "Prepared scripted demo run.";
|
|
417
698
|
}
|
|
699
|
+
function bridgeForMode(mode, bridge) {
|
|
700
|
+
return mode === "hybrid" ? bridge : undefined;
|
|
701
|
+
}
|
|
418
702
|
function observationSourceForDecisionTool(decisionTool) {
|
|
419
703
|
if (decisionTool === "playwright_mcp")
|
|
420
704
|
return "aria_snapshot";
|
|
@@ -486,11 +770,16 @@ function synthesizePlaywrightSteps(targets, testCase) {
|
|
|
486
770
|
steps.push(`await expect(page).toHaveURL(/.+/);`);
|
|
487
771
|
}
|
|
488
772
|
else {
|
|
489
|
-
steps.push(`//
|
|
773
|
+
steps.push(`// Provide a Playwright accessibility snapshot to synthesize role-first locators.`);
|
|
490
774
|
steps.push(`await expect(page).toHaveURL(/.+/);`);
|
|
491
775
|
}
|
|
492
776
|
return steps;
|
|
493
777
|
}
|
|
778
|
+
function renderRoleLocator(role, name) {
|
|
779
|
+
if (name)
|
|
780
|
+
return `page.getByRole(${quote(role)}, { name: ${quote(name)} })`;
|
|
781
|
+
return `page.getByRole(${quote(role)}).first()`;
|
|
782
|
+
}
|
|
494
783
|
function normalizeRole(role) {
|
|
495
784
|
if (role === "textbox")
|
|
496
785
|
return "textbox";
|
|
@@ -530,4 +819,20 @@ function quote(value) {
|
|
|
530
819
|
function escapeForDoubleQuotedString(value) {
|
|
531
820
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
532
821
|
}
|
|
822
|
+
function escapeForLineComment(value) {
|
|
823
|
+
return value.replace(/\*\//g, "* /").replace(/\n/g, " ");
|
|
824
|
+
}
|
|
825
|
+
function inferExpectedText(testCase, fallback) {
|
|
826
|
+
const expected = testCase.match(/(?:expect|should see|visible|断言|期望|显示|可见)\s*[::]?\s*["“”']?([^"“”'\n。]{2,80})/i);
|
|
827
|
+
if (expected?.[1])
|
|
828
|
+
return expected[1].trim();
|
|
829
|
+
const quoted = [...testCase.matchAll(/["“”']([^"“”']{2,80})["“”']/g)].map((match) => match[1]).filter(Boolean);
|
|
830
|
+
return quoted.at(-1) ?? fallback;
|
|
831
|
+
}
|
|
832
|
+
function isModuleConfig(value) {
|
|
833
|
+
return typeof value === "object" && value !== null;
|
|
834
|
+
}
|
|
835
|
+
function isRecord(value) {
|
|
836
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
837
|
+
}
|
|
533
838
|
//# sourceMappingURL=demo.js.map
|