@dailephd/my-dev-kit-lab 0.4.6 → 0.4.8
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/CHANGELOG.md +25 -2
- package/README.md +8 -1
- package/dist/src/browser/index.js +2 -0
- package/dist/src/browser/playwrightRuntime.js +74 -0
- package/dist/src/browser/types.js +10 -0
- package/dist/src/cli/help.js +76 -0
- package/dist/src/cli/runLabCli.js +29 -1
- package/dist/src/commands/runTutorialRunCommand.js +170 -0
- package/dist/src/commands/runTutorialValidateCommand.js +138 -0
- package/dist/src/core/processTree.js +52 -0
- package/dist/src/core/resolveCommand.js +34 -0
- package/dist/src/core/runMeasuredCommand.js +39 -31
- package/dist/src/runtime/index.js +1 -0
- package/dist/src/runtime/managedProcess.js +380 -0
- package/dist/src/screenshot/captureReportScreenshot.js +30 -27
- package/dist/src/tutorial/index.js +17 -0
- package/dist/src/tutorial/loadTutorialContracts.js +60 -0
- package/dist/src/tutorial/markdownTutorialWriter.js +53 -0
- package/dist/src/tutorial/runTutorial.js +465 -0
- package/dist/src/tutorial/scenarioValidation.js +427 -0
- package/dist/src/tutorial/subtitleWriter.js +88 -0
- package/dist/src/tutorial/targetContractValidation.js +212 -0
- package/dist/src/tutorial/tutorialActions.js +175 -0
- package/dist/src/tutorial/tutorialArtifacts.js +176 -0
- package/dist/src/tutorial/tutorialAssertions.js +249 -0
- package/dist/src/tutorial/tutorialCursor.js +149 -0
- package/dist/src/tutorial/tutorialLocators.js +37 -0
- package/dist/src/tutorial/tutorialManifest.js +74 -0
- package/dist/src/tutorial/tutorialOverlay.js +180 -0
- package/dist/src/tutorial/tutorialPaths.js +86 -0
- package/dist/src/tutorial/tutorialPointerGeometry.js +16 -0
- package/dist/src/tutorial/tutorialSession.js +424 -0
- package/dist/src/tutorial/types.js +51 -0
- package/dist/src/tutorial/validationPrimitives.js +141 -0
- package/examples/tutorial-browser/index.html +109 -0
- package/examples/tutorial-browser/prepare.mjs +14 -0
- package/examples/tutorial-browser/scenario.json +19 -0
- package/examples/tutorial-browser/server.mjs +14 -0
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to my-dev-kit-lab are documented here.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.4.8] - 2026-09-19
|
|
6
|
+
|
|
7
|
+
Locator-anchored pointer gestures for declarative browser tutorials.
|
|
8
|
+
|
|
9
|
+
- Added `pointer-click` and `pointer-drag` declarative tutorial actions anchored to a single `TutorialLocatorV1` interaction surface.
|
|
10
|
+
- Added normalized fraction coordinates (`coordinateSpace: "fraction"`) with inclusive `[0, 1]` bounds and zero-length pointer-drag rejection.
|
|
11
|
+
- Used real Playwright mouse input (`mouse.move`, `mouse.down`, `mouse.up`) with a fixed 8-step drag movement (`POINTER_DRAG_MOVE_STEPS = 8`) and robust `mouse.up` error cleanup.
|
|
12
|
+
- Preserved existing element-to-element `drag` (`source` and `target` locators via Playwright `dragTo`) and all existing scenario action/assertion behaviors without change.
|
|
13
|
+
- Preserved closed security boundaries: no arbitrary JavaScript, no `page.evaluate` callbacks, no generic DOM event dispatch, no shell actions, and no unanchored page/screen coordinates.
|
|
14
|
+
- Extended the generic real-browser fixture (`examples/tutorial-browser/`) with an interactive SVG pointer surface, verifying pointer clicks, pointer drags, and intermediate pointermove tracking through genuine Chromium.
|
|
15
|
+
- Extended packed-package verification (`npm run verify:packed-package`) to validate and execute the new pointer actions against a clean installed-tarball consumer while maintaining package, target, and example immutability.
|
|
16
|
+
- Validated downstream `my-frontend-observer` compatibility evidence as a read-only compatibility consumer (rectangle, line, arrow, point, and note gestures confirmed expressible without Observer code modifications or runtime dependencies).
|
|
17
|
+
- Kept `TutorialScenarioV1`, `TutorialTargetContractV1`, `TutorialRunResultV1`, and `TutorialManifestV1` schemas at version `1.0.0`.
|
|
18
|
+
|
|
19
|
+
## [0.4.7] - 2026-09-18
|
|
20
|
+
|
|
21
|
+
The v0.4.7 implementation adds generic declarative browser tutorial automation while preserving product-specific demo ownership outside this repository.
|
|
22
|
+
|
|
23
|
+
- Added the shared Playwright browser runtime, managed local process lifecycle/readiness, and persistent tutorial browser sessions.
|
|
24
|
+
- Added strict `TutorialScenarioV1` and `TutorialTargetContractV1` contracts with bounded locators, actions, and assertions.
|
|
25
|
+
- Added tutorial-only cursor/click feedback, highlights, callouts, WebM recording, step screenshots, SRT/VTT subtitles, Markdown, and `tutorial-manifest.json`.
|
|
26
|
+
- Added installed `tutorial validate` and `tutorial run` routes, an exact Playwright runtime dependency, and the packaged generic `examples/tutorial-browser/` fixture.
|
|
27
|
+
- Extended exact packed-tarball clean-consumer acceptance to validate tutorial help/contracts, browser-unavailable behavior without auto-download, real Chromium execution, canonical artifacts, manifest boundaries, workspace routing, and package/example immutability.
|
|
28
|
+
- v0.4.7 was released after cross-platform CI, security validation, code-rot audit, package validation, and packed-package acceptance passed.
|
|
6
29
|
|
|
7
30
|
## [0.4.6] - 2026-08-29
|
|
8
31
|
|
|
@@ -40,7 +63,7 @@ Context-integrity validation for the published my-dev-kit v1.10.4 and my-dev-kit
|
|
|
40
63
|
|
|
41
64
|
## [0.4.3] - 2026-07-22
|
|
42
65
|
|
|
43
|
-
`v0.4.3` (stage-specific bounded-context and workflow-instruction evaluation)
|
|
66
|
+
`v0.4.3` (stage-specific bounded-context and workflow-instruction evaluation) was published and was later superseded by `v0.4.4`. Its complete scope, dependencies, exclusions, and acceptance criteria are preserved in [docs/ROADMAP.md](docs/ROADMAP.md).
|
|
44
67
|
|
|
45
68
|
### Added
|
|
46
69
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ my-dev-kit-lab is the experiment, audit, and evidence companion for [my-dev-kit]
|
|
|
4
4
|
|
|
5
5
|
my-dev-kit provides local repository indexing and graph-guided retrieval. my-dev-kit-lab supplies the controlled benchmarks, agent adapters, metrics, security checks, and reports needed to evaluate when that retrieval is useful. Results are evidence for a specific target and configuration; they do not guarantee token savings or security.
|
|
6
6
|
|
|
7
|
-
The latest
|
|
7
|
+
The latest release is v0.4.8 (locator-anchored pointer gestures for declarative browser tutorials, adding `pointer-click` and `pointer-drag` using normalized locator-relative fraction positions while existing `drag` remains element-to-element). The v0.4.5 context-integrity evaluation remains intentionally frozen against the published `@dailephd/my-dev-kit@1.10.4` and `@dailephd/my-dev-kit-orchestrator@1.2.3` contracts; those versions are historical validation baselines, not a statement that they are the ecosystem's current releases. See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md).
|
|
8
8
|
|
|
9
9
|
v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--version`, `security validate`, `audit`, the `experiment` family, `report render`, `plots generate`, `gallery build`, `demo final`, and the historical direct final-demo invocation form), a writable lab workspace model kept separate from the installed package and the inspected target, and a permanent packed-tarball installation/execution acceptance gate (`npm run verify:packed-package`). See [docs/ROADMAP.md](docs/ROADMAP.md) and [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for status detail and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the runtime path model. The "Installed CLI" section below documents the shipped command surface; the source-checkout `npm run` workflow in Quickstart remains available for contributors.
|
|
10
10
|
|
|
@@ -15,6 +15,7 @@ v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--vers
|
|
|
15
15
|
- **Validate CLI/package security:** inspect dependencies, package contents, path and subprocess boundaries, malformed inputs, optional static scanners, and bounded fuzz targets.
|
|
16
16
|
- **Validate Android projects:** run nineteen static checks by default, with Gradle operations, external tools, and network access available only through explicit opt-in flags.
|
|
17
17
|
- **Review evidence:** generate JSON and HTML reports, SVG plots, optional screenshots, visualization demos, and a static gallery.
|
|
18
|
+
- **Generate declarative browser tutorials:** validate and execute bounded local browser scenarios with WebM recording, step screenshots, SRT/VTT subtitles, Markdown, and a tutorial manifest. Locator-anchored pointer gestures (`pointer-click` and `pointer-drag`) operate using normalized fraction coordinates inside a single located surface, while existing `drag` remains element-to-element. Tutorial execution requires a compatible locally installed Chromium; see [docs/COMMANDS.md](docs/COMMANDS.md) and [docs/TUTORIAL.md](docs/TUTORIAL.md) for details.
|
|
18
19
|
- **Evaluate stage-context strategies:** compare the two legacy strategies against six additional bounded stage-context strategies — `architecture-context-only`, `architecture-plus-implementation-refresh`, `architecture-plus-implementation-and-test-refresh`, `full-workflow-library`, `bounded-workflow-instruction-packet`, and `combined-bounded-stage-context` — selected through programmatic configuration, not CLI flags. Each strategy's evidence is reported through bounded `report.json`, `report.html`, and `report.txt` output with an explicit `available`/`unavailable`/`not-applicable` metric-availability model and no composite score, grade, ranking, or winning strategy.
|
|
19
20
|
- **Evaluate the producer-readiness bridge (v0.4.4):** optionally extend `combined-bounded-stage-context` with the frozen my-dev-kit-orchestrator supplemental implementation/test-context packet and retrieval-report documents plus an observed readiness result, all supplied programmatically (there is no CLI flag), to measure owner, allocation, truncation-cause, supplemental/raw agreement, readiness-agreement, and criticality-overlay evidence without reimplementing upstream owner-selection, allocation, producer-parity, or readiness policy.
|
|
20
21
|
- **Evaluate context integrity (v0.4.5):** compare condition-aware producer evidence from my-dev-kit v1.10.4 (role condition coverage, allocation/spillover, required-evidence-loss) against my-dev-kit-orchestrator v1.2.3 run-integrity evidence (run-integrity gate, judge integrity, final-report eligibility, artifact lifecycle state), reporting agreement or contradiction between them rather than re-deriving a verdict. Evaluation runs against a frozen, hash-verified regression fixture pair — a byte-exact real historical failed run and a hand-distilled corrected-replay counterpart representing the same validated contracts — and is programmatic/test-driven only. `npm run report:context-integrity-smoke` renders both fixtures' reports for manual inspection; it takes no arguments and is a developer convenience, not a configurable evaluation CLI.
|
|
@@ -39,6 +40,8 @@ my-dev-kit-lab report render [options]
|
|
|
39
40
|
my-dev-kit-lab plots generate [options]
|
|
40
41
|
my-dev-kit-lab gallery build [options]
|
|
41
42
|
my-dev-kit-lab demo final [options]
|
|
43
|
+
my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>] [--json]
|
|
44
|
+
my-dev-kit-lab tutorial run --scenario <path> --target-contract <path> [--out <dir>] [--json]
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
A global `--workspace <path>` option, placed before the command, selects the writable lab workspace. The default workspace, used when `--workspace` is omitted, is `<home>/.my-dev-kit-lab`. Generated reports and experiment output are written under the workspace by default, never under the installed package directory or an inspected `--target` project. Explicit output paths (`--out`, and similar flags) keep their existing resolution behavior and are never redirected under the workspace.
|
|
@@ -49,6 +52,8 @@ Low-level developer helpers (`security:deps`, `security:package`, `security:code
|
|
|
49
52
|
|
|
50
53
|
See [docs/COMMANDS.md](docs/COMMANDS.md) for full command syntax and flags.
|
|
51
54
|
|
|
55
|
+
Tutorial runs keep the scenario source, disposable target, logs, browser recording, and generated artifacts separate. The canonical generic example is packaged at `examples/tutorial-browser/`; product-specific demo sites and scenarios remain owned by their product repositories.
|
|
56
|
+
|
|
52
57
|
## Quickstart (contributor / source-checkout workflow)
|
|
53
58
|
|
|
54
59
|
The commands in this section run from a cloned repository checkout and are the contributor/development workflow. They are not required to use the installed CLI documented above.
|
|
@@ -203,6 +208,8 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
|
|
|
203
208
|
- The published upstream artifacts the stage-context strategies read do not expose considered-but-unselected reads or unnecessary-read evidence; those metrics report `unavailable` rather than zero
|
|
204
209
|
- The v0.4.4 producer-readiness bridge is released. All bridge inputs are programmatic — there is no CLI flag. The coordinated upstream releases my-dev-kit@1.10.3 and orchestrator@1.2.2 were verified published before lab publication.
|
|
205
210
|
- The v0.4.5 context-integrity evaluation is released. It has no CLI flags, no plots/screenshot/gallery integration, and produces no composite score, grade, ranking, or winner. Its corrected-replay fixture is a hand-distilled representation of the validated my-dev-kit v1.10.4 and my-dev-kit-orchestrator v1.2.3 contracts, not a live capture of a complete ten-stage workflow. The orchestrator agreement evidence does not read a literal upstream `promptMode` field; `stageMayRenderNormalPrompt`, derived from structured blocked-stage evidence, is the bounded substitute used instead.
|
|
211
|
+
- v0.4.7 added declarative browser tutorial validation and execution with bounded actions/assertions, persistent Chromium sessions, WebM, screenshots, SRT/VTT, Markdown, and `tutorial-manifest.json`. Chromium is a separate local runtime prerequisite; the package does not download browser binaries automatically. Gallery consumption remains future scope.
|
|
212
|
+
- v0.4.8 added `pointer-click` and `pointer-drag` for normalized fraction positions inside a single located element. Existing `drag` remains element-to-element between source and target locators.
|
|
206
213
|
|
|
207
214
|
---
|
|
208
215
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const DEFAULT_BROWSER_HEADLESS = true;
|
|
2
|
+
/**
|
|
3
|
+
* Message fragments that identify a genuinely missing Chromium/browser binary.
|
|
4
|
+
*
|
|
5
|
+
* This is an intentionally closed list carried over unchanged from the v0.4.6
|
|
6
|
+
* screenshot classifier so existing skip/fail behavior is preserved exactly. It
|
|
7
|
+
* is not a general-purpose error classifier and must not be broadened into one.
|
|
8
|
+
*/
|
|
9
|
+
export const MISSING_BROWSER_RUNTIME_MESSAGE_FRAGMENTS = [
|
|
10
|
+
"Executable doesn't exist",
|
|
11
|
+
"browserType.launch",
|
|
12
|
+
"playwright install",
|
|
13
|
+
"Failed to launch",
|
|
14
|
+
"Could not find Chrome",
|
|
15
|
+
"ENOENT"
|
|
16
|
+
];
|
|
17
|
+
export function isMissingBrowserRuntimeError(error) {
|
|
18
|
+
const message = errorMessage(error);
|
|
19
|
+
return MISSING_BROWSER_RUNTIME_MESSAGE_FRAGMENTS.some((fragment) => message.includes(fragment));
|
|
20
|
+
}
|
|
21
|
+
export async function loadPlaywrightModule() {
|
|
22
|
+
return (await import("playwright"));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Launches Chromium and classifies the three outcomes callers care about.
|
|
26
|
+
*
|
|
27
|
+
* Returns a structured result rather than throwing for the two expected
|
|
28
|
+
* environment states (module missing, browser binary missing), because every
|
|
29
|
+
* current and planned consumer has to make its own policy decision about them.
|
|
30
|
+
* This function owns no consumer-specific status names or warning text.
|
|
31
|
+
*/
|
|
32
|
+
export async function launchChromium(options = {}) {
|
|
33
|
+
const loadPlaywright = options.loadPlaywright ?? loadPlaywrightModule;
|
|
34
|
+
const headless = options.headless ?? DEFAULT_BROWSER_HEADLESS;
|
|
35
|
+
let playwright;
|
|
36
|
+
try {
|
|
37
|
+
playwright = await loadPlaywright();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
status: "unavailable",
|
|
42
|
+
reason: "playwright-module-unavailable",
|
|
43
|
+
error: errorMessage(error)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const browser = await playwright.chromium.launch({ headless });
|
|
48
|
+
return { status: "launched", browser };
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (isMissingBrowserRuntimeError(error)) {
|
|
52
|
+
return {
|
|
53
|
+
status: "unavailable",
|
|
54
|
+
reason: "browser-runtime-unavailable",
|
|
55
|
+
error: errorMessage(error)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { status: "failed", error: errorMessage(error) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Best-effort cleanup shared by every browser consumer. Closing a browser that
|
|
63
|
+
* is already gone, or whose transport already failed, must never mask the error
|
|
64
|
+
* the caller is actually reporting.
|
|
65
|
+
*/
|
|
66
|
+
export async function closeBrowserQuietly(browser) {
|
|
67
|
+
if (!browser) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await browser.close().catch(() => undefined);
|
|
71
|
+
}
|
|
72
|
+
export function errorMessage(error) {
|
|
73
|
+
return error instanceof Error ? error.message : String(error);
|
|
74
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural types for the parts of Playwright this repository actually
|
|
3
|
+
* uses. They exist so production code and tests can depend on a narrow shape
|
|
4
|
+
* instead of the full Playwright type surface, and so a fake runtime can be
|
|
5
|
+
* injected without installing a browser.
|
|
6
|
+
*
|
|
7
|
+
* These are deliberately generic: they describe a browser runtime, not report
|
|
8
|
+
* screenshots and not tutorials.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
package/dist/src/cli/help.js
CHANGED
|
@@ -14,6 +14,8 @@ export function renderTopLevelHelp() {
|
|
|
14
14
|
" my-dev-kit-lab [--workspace <path>] report render --experiment <dir> --out <dir> [options]",
|
|
15
15
|
" my-dev-kit-lab [--workspace <path>] plots generate --experiment <dir> --out <dir>",
|
|
16
16
|
" my-dev-kit-lab [--workspace <path>] gallery build --out <dir> [options]",
|
|
17
|
+
" my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>]",
|
|
18
|
+
" my-dev-kit-lab [--workspace <path>] tutorial run --scenario <path> --target-contract <path> [options]",
|
|
17
19
|
" my-dev-kit-lab demo final --cases <path> --out <dir> --kit-command <command> [options]",
|
|
18
20
|
"",
|
|
19
21
|
"Options:",
|
|
@@ -32,6 +34,8 @@ export function renderTopLevelHelp() {
|
|
|
32
34
|
" report render Render an experiment report from controlled-experiment artifacts",
|
|
33
35
|
" plots generate Generate experiment plot artifacts",
|
|
34
36
|
" gallery build Build a gallery manifest/index",
|
|
37
|
+
" tutorial validate Validate a tutorial scenario and optional target contract",
|
|
38
|
+
" tutorial run Run a declarative browser tutorial scenario",
|
|
35
39
|
" demo final Run the full final demo workflow",
|
|
36
40
|
"",
|
|
37
41
|
"Run \"my-dev-kit-lab <command> --help\" (or \"my-dev-kit-lab <family> <command> --help\") for details.",
|
|
@@ -298,3 +302,75 @@ export function renderUnknownCommandError(command) {
|
|
|
298
302
|
"Run \"my-dev-kit-lab --help\" for usage."
|
|
299
303
|
].join("\n");
|
|
300
304
|
}
|
|
305
|
+
export function renderTutorialHelp() {
|
|
306
|
+
return [
|
|
307
|
+
"my-dev-kit-lab tutorial - declarative browser tutorial command family",
|
|
308
|
+
"",
|
|
309
|
+
"Usage:",
|
|
310
|
+
" my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>] [--json]",
|
|
311
|
+
" my-dev-kit-lab [--workspace <path>] tutorial run --scenario <path> --target-contract <path> [options]",
|
|
312
|
+
" my-dev-kit-lab tutorial --help",
|
|
313
|
+
"",
|
|
314
|
+
"Commands:",
|
|
315
|
+
" validate Validate a tutorial scenario, and optionally a target contract and their",
|
|
316
|
+
" declared target identity. Does not start processes or a browser.",
|
|
317
|
+
" Run \"my-dev-kit-lab tutorial validate --help\" for details.",
|
|
318
|
+
" run Prepare a disposable target, start its declared processes, and execute a",
|
|
319
|
+
" tutorial scenario in a persistent browser session.",
|
|
320
|
+
" Run \"my-dev-kit-lab tutorial run --help\" for details."
|
|
321
|
+
].join("\n");
|
|
322
|
+
}
|
|
323
|
+
export function renderTutorialValidateHelp() {
|
|
324
|
+
return [
|
|
325
|
+
"my-dev-kit-lab tutorial validate - validate tutorial contracts",
|
|
326
|
+
"",
|
|
327
|
+
"Usage:",
|
|
328
|
+
" my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>] [--json]",
|
|
329
|
+
"",
|
|
330
|
+
"Required:",
|
|
331
|
+
" --scenario <path> Path to a TutorialScenarioV1 JSON file",
|
|
332
|
+
"",
|
|
333
|
+
"Options:",
|
|
334
|
+
" --target-contract <path> Also validate a TutorialTargetContractV1 JSON file and",
|
|
335
|
+
" verify that the scenario targetId matches its id",
|
|
336
|
+
" --json Print a deterministic JSON report instead of a summary",
|
|
337
|
+
"",
|
|
338
|
+
"Relative paths resolve against the directory the command was invoked from.",
|
|
339
|
+
"This command is read-only: it creates no directories, runs no commands, starts no",
|
|
340
|
+
"processes, and does not require a browser runtime.",
|
|
341
|
+
"",
|
|
342
|
+
"Exit codes:",
|
|
343
|
+
" 0 Contracts are valid",
|
|
344
|
+
" 1 Contract validation failed",
|
|
345
|
+
" 2 Invalid command usage"
|
|
346
|
+
].join("\n");
|
|
347
|
+
}
|
|
348
|
+
export function renderTutorialRunHelp() {
|
|
349
|
+
return [
|
|
350
|
+
"my-dev-kit-lab tutorial run - run a declarative browser tutorial scenario",
|
|
351
|
+
"",
|
|
352
|
+
"Usage:",
|
|
353
|
+
" my-dev-kit-lab [--workspace <path>] tutorial run --scenario <path> --target-contract <path> [--out <dir>] [--json]",
|
|
354
|
+
"",
|
|
355
|
+
"Required:",
|
|
356
|
+
" --scenario <path> Path to a TutorialScenarioV1 JSON file",
|
|
357
|
+
" --target-contract <path> Path to a TutorialTargetContractV1 JSON file",
|
|
358
|
+
"",
|
|
359
|
+
"Options:",
|
|
360
|
+
" --out <dir> Explicit run directory. An absolute path is used as given;",
|
|
361
|
+
" a relative path resolves against the invocation directory.",
|
|
362
|
+
" When omitted, the run is written to",
|
|
363
|
+
" <workspace>/tutorials/<scenario-id>/<run-id>/",
|
|
364
|
+
" --json Print the TutorialRunResultV1 JSON instead of a summary",
|
|
365
|
+
"",
|
|
366
|
+
"The run prepares a disposable target working copy beneath the run directory, starts the",
|
|
367
|
+
"processes the target contract declares, waits for each declared local readiness probe,",
|
|
368
|
+
"then executes every scenario step in one persistent browser page. Process logs are",
|
|
369
|
+
"written beneath the run directory and are not printed to the terminal.",
|
|
370
|
+
"",
|
|
371
|
+
"Exit codes:",
|
|
372
|
+
" 0 The tutorial run passed",
|
|
373
|
+
" 1 The invocation was valid but the tutorial did not pass",
|
|
374
|
+
" 2 Invalid command usage"
|
|
375
|
+
].join("\n");
|
|
376
|
+
}
|
|
@@ -10,8 +10,10 @@ import { runExperimentRunCommandFromArgs } from "../commands/runExperimentRunCom
|
|
|
10
10
|
import { runRenderExperimentReportCommand } from "../commands/renderExperimentReportCommand.js";
|
|
11
11
|
import { runGenerateExperimentPlotsCommand } from "../commands/generateExperimentPlotsCommand.js";
|
|
12
12
|
import { runBuildGalleryCommand } from "../commands/buildGalleryCommand.js";
|
|
13
|
+
import { runTutorialValidateCommandFromArgs } from "../commands/runTutorialValidateCommand.js";
|
|
14
|
+
import { runTutorialRunCommandFromArgs } from "../commands/runTutorialRunCommand.js";
|
|
13
15
|
import { createLabExecutionContext, discoverPackageRoot } from "../runtime/index.js";
|
|
14
|
-
import { renderDemoHelp, renderExperimentControlledHelp, renderExperimentDescribeHelp, renderExperimentHelp, renderExperimentListHelp, renderExperimentRunHelp, renderFinalDemoHelp, renderGalleryBuildHelp, renderGalleryHelp, renderPlotsGenerateHelp, renderPlotsHelp, renderReportHelp, renderReportRenderHelp, renderSecurityHelp, renderTopLevelHelp, renderUnknownCommandError } from "./help.js";
|
|
16
|
+
import { renderDemoHelp, renderExperimentControlledHelp, renderExperimentDescribeHelp, renderExperimentHelp, renderExperimentListHelp, renderExperimentRunHelp, renderFinalDemoHelp, renderGalleryBuildHelp, renderGalleryHelp, renderPlotsGenerateHelp, renderPlotsHelp, renderReportHelp, renderReportRenderHelp, renderSecurityHelp, renderTopLevelHelp, renderTutorialHelp, renderTutorialRunHelp, renderTutorialValidateHelp, renderUnknownCommandError } from "./help.js";
|
|
15
17
|
export const CLI_USAGE_EXIT_CODE = 2;
|
|
16
18
|
/**
|
|
17
19
|
* Recognized legacy final-demo flag tokens (mirrors parseRunFinalDemoArgs in
|
|
@@ -80,6 +82,9 @@ export async function runLabCli(argv, options = {}) {
|
|
|
80
82
|
if (command === "gallery") {
|
|
81
83
|
return runGalleryFamily(rest, writers);
|
|
82
84
|
}
|
|
85
|
+
if (command === "tutorial") {
|
|
86
|
+
return runTutorialFamily(rest, writers, context);
|
|
87
|
+
}
|
|
83
88
|
if (command === "demo") {
|
|
84
89
|
return runDemoCommand(rest, writers);
|
|
85
90
|
}
|
|
@@ -202,6 +207,29 @@ async function runPlotsFamily(argv, writers) {
|
|
|
202
207
|
writers.stderr(renderUnknownCommandError(`plots ${subcommand}`));
|
|
203
208
|
return CLI_USAGE_EXIT_CODE;
|
|
204
209
|
}
|
|
210
|
+
async function runTutorialFamily(argv, writers, context) {
|
|
211
|
+
const [subcommand, ...rest] = argv;
|
|
212
|
+
if (argv.length === 0 || subcommand === "--help" || subcommand === "-h") {
|
|
213
|
+
writers.stdout(renderTutorialHelp());
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
if (subcommand === "validate") {
|
|
217
|
+
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
218
|
+
writers.stdout(renderTutorialValidateHelp());
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
return runTutorialValidateCommandFromArgs(rest, { context });
|
|
222
|
+
}
|
|
223
|
+
if (subcommand === "run") {
|
|
224
|
+
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
225
|
+
writers.stdout(renderTutorialRunHelp());
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
return runTutorialRunCommandFromArgs(rest, { context });
|
|
229
|
+
}
|
|
230
|
+
writers.stderr(renderUnknownCommandError(`tutorial ${subcommand}`));
|
|
231
|
+
return CLI_USAGE_EXIT_CODE;
|
|
232
|
+
}
|
|
205
233
|
async function runGalleryFamily(argv, writers) {
|
|
206
234
|
const [subcommand, ...rest] = argv;
|
|
207
235
|
if (argv.length === 0 || subcommand === "--help" || subcommand === "-h") {
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createLabExecutionContext } from "../runtime/index.js";
|
|
2
|
+
import { runTutorial } from "../tutorial/index.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// v0.4.7 Batch 2 -- tutorial run command owner.
|
|
5
|
+
//
|
|
6
|
+
// A thin adapter: parse a closed option set, resolve execution roots through
|
|
7
|
+
// LabExecutionContext, delegate to runTutorial(), render a summary, map the run
|
|
8
|
+
// status to an exit code. No tutorial semantics live here.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const TUTORIAL_CLI_USAGE_EXIT_CODE = 2;
|
|
11
|
+
const RUN_USAGE = "Usage: my-dev-kit-lab tutorial run --scenario <path> --target-contract <path> [--out <dir>] [--json]";
|
|
12
|
+
export async function runTutorialRunCommandFromArgs(argv, options = {}) {
|
|
13
|
+
const writers = options.writers ?? {
|
|
14
|
+
stdout: (message) => console.log(message),
|
|
15
|
+
stderr: (message) => console.error(message)
|
|
16
|
+
};
|
|
17
|
+
const parsed = parseRunArgs(argv);
|
|
18
|
+
if (!parsed.ok) {
|
|
19
|
+
writers.stderr(parsed.error);
|
|
20
|
+
return TUTORIAL_CLI_USAGE_EXIT_CODE;
|
|
21
|
+
}
|
|
22
|
+
const context = options.context ?? createLabExecutionContext();
|
|
23
|
+
const execute = options.runTutorialImpl ?? runTutorial;
|
|
24
|
+
const result = await execute({
|
|
25
|
+
scenarioPath: parsed.scenarioPath,
|
|
26
|
+
targetContractPath: parsed.targetContractPath,
|
|
27
|
+
context,
|
|
28
|
+
...(parsed.outDir !== undefined ? { outDir: parsed.outDir } : {}),
|
|
29
|
+
...(options.launchBrowser ? { launchBrowser: options.launchBrowser } : {}),
|
|
30
|
+
...(options.generateRunId ? { generateRunId: options.generateRunId } : {})
|
|
31
|
+
});
|
|
32
|
+
if (parsed.json) {
|
|
33
|
+
writers.stdout(JSON.stringify(result, null, 2));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
writers.stdout(renderRunSummary(result));
|
|
37
|
+
}
|
|
38
|
+
return result.status === "passed" ? 0 : 1;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Concise by design: identity, location, outcome, and the first thing that went
|
|
42
|
+
* wrong. Child-process logs are written to the run's logs directory and are
|
|
43
|
+
* never dumped to the terminal automatically.
|
|
44
|
+
*/
|
|
45
|
+
function renderRunSummary(result) {
|
|
46
|
+
const lines = [];
|
|
47
|
+
lines.push(`Scenario ID: ${result.scenarioId ?? "(not loaded)"}`);
|
|
48
|
+
lines.push(`Target ID: ${result.targetId ?? "(not loaded)"}`);
|
|
49
|
+
lines.push(`Run ID: ${result.runId ?? "(not started)"}`);
|
|
50
|
+
lines.push(`Run root: ${result.paths?.runRoot ?? "(not created)"}`);
|
|
51
|
+
lines.push(`Status: ${result.status}`);
|
|
52
|
+
lines.push(`Duration: ${result.durationMs}ms`);
|
|
53
|
+
const stepCounts = summarizeSteps(result);
|
|
54
|
+
if (result.steps.length > 0) {
|
|
55
|
+
lines.push(`Steps: ${result.steps.length} (passed ${stepCounts.passed}, failed ${stepCounts.failed}, not-run ${stepCounts.notRun})`);
|
|
56
|
+
}
|
|
57
|
+
const failedStep = result.steps.find((step) => step.status === "failed");
|
|
58
|
+
if (failedStep) {
|
|
59
|
+
lines.push(`Failed step: ${failedStep.id}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push(...renderArtifactLines(result));
|
|
62
|
+
if (result.error) {
|
|
63
|
+
lines.push(`Error: ${result.error}`);
|
|
64
|
+
}
|
|
65
|
+
for (const warning of result.warnings) {
|
|
66
|
+
lines.push(`Warning: ${warning}`);
|
|
67
|
+
}
|
|
68
|
+
for (const cleanupError of result.cleanupErrors) {
|
|
69
|
+
lines.push(`Cleanup error: ${cleanupError}`);
|
|
70
|
+
}
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* One line per canonical artifact that was actually written, plus a screenshot
|
|
75
|
+
* count. Failed or skipped artifacts are named so a reader sees why, but the
|
|
76
|
+
* manifest itself is never dumped here -- that is what --json is for.
|
|
77
|
+
*/
|
|
78
|
+
function renderArtifactLines(result) {
|
|
79
|
+
const artifacts = result.artifacts;
|
|
80
|
+
const lines = [];
|
|
81
|
+
const named = [
|
|
82
|
+
["Video", artifacts.video],
|
|
83
|
+
["SRT", artifacts.srt],
|
|
84
|
+
["VTT", artifacts.vtt],
|
|
85
|
+
["Markdown", artifacts.markdown],
|
|
86
|
+
["Manifest", artifacts.manifest]
|
|
87
|
+
];
|
|
88
|
+
const written = named.filter(([, record]) => record?.status === "written");
|
|
89
|
+
for (const [label, record] of written) {
|
|
90
|
+
lines.push(`${label}: ${record.path ?? ""}`);
|
|
91
|
+
}
|
|
92
|
+
const writtenScreenshots = artifacts.screenshots.filter((record) => record.status === "written");
|
|
93
|
+
if (artifacts.screenshots.length > 0) {
|
|
94
|
+
lines.push(`Screenshots: ${writtenScreenshots.length} of ${artifacts.screenshots.length} written`);
|
|
95
|
+
}
|
|
96
|
+
for (const [label, record] of named) {
|
|
97
|
+
if (record && record.status !== "written") {
|
|
98
|
+
lines.push(`${label} artifact ${record.status}: ${record.error ?? "no detail"}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const record of artifacts.screenshots) {
|
|
102
|
+
if (record.status !== "written") {
|
|
103
|
+
lines.push(`Screenshot ${record.id ?? "?"} ${record.status}: ${record.error ?? "no detail"}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return lines;
|
|
107
|
+
}
|
|
108
|
+
function summarizeSteps(result) {
|
|
109
|
+
let passed = 0;
|
|
110
|
+
let failed = 0;
|
|
111
|
+
let notRun = 0;
|
|
112
|
+
for (const step of result.steps) {
|
|
113
|
+
if (step.status === "passed")
|
|
114
|
+
passed += 1;
|
|
115
|
+
else if (step.status === "failed")
|
|
116
|
+
failed += 1;
|
|
117
|
+
else
|
|
118
|
+
notRun += 1;
|
|
119
|
+
}
|
|
120
|
+
return { passed, failed, notRun };
|
|
121
|
+
}
|
|
122
|
+
function parseRunArgs(argv) {
|
|
123
|
+
let scenarioPath;
|
|
124
|
+
let targetContractPath;
|
|
125
|
+
let outDir;
|
|
126
|
+
let json = false;
|
|
127
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
128
|
+
const arg = argv[index];
|
|
129
|
+
if (arg === "--scenario") {
|
|
130
|
+
const value = argv[++index];
|
|
131
|
+
if (value === undefined) {
|
|
132
|
+
return { ok: false, error: `Missing value for --scenario. ${RUN_USAGE}` };
|
|
133
|
+
}
|
|
134
|
+
scenarioPath = value;
|
|
135
|
+
}
|
|
136
|
+
else if (arg === "--target-contract") {
|
|
137
|
+
const value = argv[++index];
|
|
138
|
+
if (value === undefined) {
|
|
139
|
+
return { ok: false, error: `Missing value for --target-contract. ${RUN_USAGE}` };
|
|
140
|
+
}
|
|
141
|
+
targetContractPath = value;
|
|
142
|
+
}
|
|
143
|
+
else if (arg === "--out") {
|
|
144
|
+
const value = argv[++index];
|
|
145
|
+
if (value === undefined) {
|
|
146
|
+
return { ok: false, error: `Missing value for --out. ${RUN_USAGE}` };
|
|
147
|
+
}
|
|
148
|
+
outDir = value;
|
|
149
|
+
}
|
|
150
|
+
else if (arg === "--json") {
|
|
151
|
+
json = true;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
return { ok: false, error: `Unknown argument for "tutorial run": ${arg}. ${RUN_USAGE}` };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (scenarioPath === undefined) {
|
|
158
|
+
return { ok: false, error: `Missing required --scenario. ${RUN_USAGE}` };
|
|
159
|
+
}
|
|
160
|
+
if (targetContractPath === undefined) {
|
|
161
|
+
return { ok: false, error: `Missing required --target-contract. ${RUN_USAGE}` };
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
ok: true,
|
|
165
|
+
scenarioPath,
|
|
166
|
+
targetContractPath,
|
|
167
|
+
...(outDir !== undefined ? { outDir } : {}),
|
|
168
|
+
json
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createLabExecutionContext } from "../runtime/index.js";
|
|
2
|
+
import { describeTargetIdMismatch, loadTutorialScenario, loadTutorialTargetContract, tutorialTargetIdsMatch } from "../tutorial/index.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// v0.4.7 Batch 2 -- tutorial contract validation command owner.
|
|
5
|
+
//
|
|
6
|
+
// Read-only by construction: it loads and validates contracts and nothing else.
|
|
7
|
+
// No run directory is created, no prepare command runs, no process starts and
|
|
8
|
+
// no browser launches, so this route is safe to run anywhere and needs neither
|
|
9
|
+
// Chromium nor a prepared target.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export const TUTORIAL_CLI_USAGE_EXIT_CODE = 2;
|
|
12
|
+
export async function runTutorialValidateCommandFromArgs(argv, options = {}) {
|
|
13
|
+
const writers = options.writers ?? {
|
|
14
|
+
stdout: (message) => console.log(message),
|
|
15
|
+
stderr: (message) => console.error(message)
|
|
16
|
+
};
|
|
17
|
+
const parsed = parseValidateArgs(argv);
|
|
18
|
+
if (!parsed.ok) {
|
|
19
|
+
writers.stderr(parsed.error);
|
|
20
|
+
return TUTORIAL_CLI_USAGE_EXIT_CODE;
|
|
21
|
+
}
|
|
22
|
+
const context = options.context ?? createLabExecutionContext();
|
|
23
|
+
const report = { status: "valid", scenarioPath: parsed.scenarioPath, errors: [] };
|
|
24
|
+
const loadedScenario = await loadTutorialScenario(context.invocationCwd, parsed.scenarioPath);
|
|
25
|
+
if (!loadedScenario.ok) {
|
|
26
|
+
report.status = "invalid";
|
|
27
|
+
report.errors.push(...loadedScenario.errors);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
report.scenarioPath = loadedScenario.value.scenarioPath;
|
|
31
|
+
report.scenarioId = loadedScenario.value.scenario.id;
|
|
32
|
+
report.scenarioTitle = loadedScenario.value.scenario.title;
|
|
33
|
+
report.targetIdDeclared = loadedScenario.value.scenario.targetId;
|
|
34
|
+
report.stepCount = loadedScenario.value.scenario.steps.length;
|
|
35
|
+
}
|
|
36
|
+
if (parsed.targetContractPath !== undefined) {
|
|
37
|
+
const loadedTarget = await loadTutorialTargetContract(context.invocationCwd, parsed.targetContractPath);
|
|
38
|
+
if (!loadedTarget.ok) {
|
|
39
|
+
report.status = "invalid";
|
|
40
|
+
report.targetContractPath = parsed.targetContractPath;
|
|
41
|
+
report.errors.push(...loadedTarget.errors);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
report.targetContractPath = loadedTarget.value.targetContractPath;
|
|
45
|
+
report.targetContractId = loadedTarget.value.targetContract.id;
|
|
46
|
+
report.processCount = loadedTarget.value.targetContract.processes.length;
|
|
47
|
+
report.applicationUrl = loadedTarget.value.targetContract.applicationUrl;
|
|
48
|
+
if (loadedScenario.ok) {
|
|
49
|
+
const matches = tutorialTargetIdsMatch(loadedScenario.value.scenario.targetId, loadedTarget.value.targetContract.id);
|
|
50
|
+
report.targetIdMatches = matches;
|
|
51
|
+
if (!matches) {
|
|
52
|
+
report.status = "invalid";
|
|
53
|
+
report.errors.push(describeTargetIdMismatch(loadedScenario.value.scenario.targetId, loadedTarget.value.targetContract.id));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (parsed.json) {
|
|
59
|
+
writers.stdout(JSON.stringify(report, null, 2));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
writers.stdout(renderValidateSummary(report));
|
|
63
|
+
}
|
|
64
|
+
return report.status === "valid" ? 0 : 1;
|
|
65
|
+
}
|
|
66
|
+
function renderValidateSummary(report) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
lines.push(`Scenario: ${report.scenarioPath}`);
|
|
69
|
+
if (report.scenarioId) {
|
|
70
|
+
lines.push(`Scenario ID: ${report.scenarioId}`);
|
|
71
|
+
lines.push(`Title: ${report.scenarioTitle ?? ""}`);
|
|
72
|
+
lines.push(`Declared target ID: ${report.targetIdDeclared ?? ""}`);
|
|
73
|
+
lines.push(`Steps: ${report.stepCount ?? 0}`);
|
|
74
|
+
}
|
|
75
|
+
if (report.targetContractPath) {
|
|
76
|
+
lines.push(`Target contract: ${report.targetContractPath}`);
|
|
77
|
+
}
|
|
78
|
+
if (report.targetContractId) {
|
|
79
|
+
lines.push(`Target contract ID: ${report.targetContractId}`);
|
|
80
|
+
lines.push(`Managed processes: ${report.processCount ?? 0}`);
|
|
81
|
+
lines.push(`Application URL: ${report.applicationUrl ?? ""}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(`Status: ${report.status}`);
|
|
84
|
+
if (report.errors.length > 0) {
|
|
85
|
+
lines.push("");
|
|
86
|
+
lines.push("Errors:");
|
|
87
|
+
for (const error of report.errors) {
|
|
88
|
+
lines.push(` - ${error}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
function parseValidateArgs(argv) {
|
|
94
|
+
let scenarioPath;
|
|
95
|
+
let targetContractPath;
|
|
96
|
+
let json = false;
|
|
97
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
98
|
+
const arg = argv[index];
|
|
99
|
+
if (arg === "--scenario") {
|
|
100
|
+
const value = argv[++index];
|
|
101
|
+
if (value === undefined) {
|
|
102
|
+
return { ok: false, error: "Missing value for --scenario. Expected a path to a tutorial scenario JSON file." };
|
|
103
|
+
}
|
|
104
|
+
scenarioPath = value;
|
|
105
|
+
}
|
|
106
|
+
else if (arg === "--target-contract") {
|
|
107
|
+
const value = argv[++index];
|
|
108
|
+
if (value === undefined) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: "Missing value for --target-contract. Expected a path to a tutorial target contract JSON file."
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
targetContractPath = value;
|
|
115
|
+
}
|
|
116
|
+
else if (arg === "--json") {
|
|
117
|
+
json = true;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
error: `Unknown argument for "tutorial validate": ${arg}. Usage: my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>] [--json]`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (scenarioPath === undefined) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: "Missing required --scenario. Usage: my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>] [--json]"
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
scenarioPath,
|
|
135
|
+
...(targetContractPath !== undefined ? { targetContractPath } : {}),
|
|
136
|
+
json
|
|
137
|
+
};
|
|
138
|
+
}
|