waygraph 0.15.42 → 0.15.44

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.
Files changed (60) hide show
  1. package/dist/analysis/coverage-gap.d.ts +11 -0
  2. package/dist/analysis/coverage-gap.js +119 -0
  3. package/dist/analysis/graph.d.ts +92 -0
  4. package/dist/analysis/graph.js +467 -0
  5. package/dist/analysis/map-check.d.ts +18 -0
  6. package/dist/analysis/map-check.js +140 -0
  7. package/dist/analysis/practices-check.d.ts +14 -0
  8. package/dist/analysis/practices-check.js +195 -0
  9. package/dist/auto-explore.d.ts +1 -1
  10. package/dist/auto-explore.js +1 -1
  11. package/dist/auto-session/session.js +2 -2
  12. package/dist/cli/util.d.ts +8 -1
  13. package/dist/cli/util.js +9 -2
  14. package/dist/commands/check.js +2 -2
  15. package/dist/commands/map.js +1 -1
  16. package/dist/commands/session.js +2 -2
  17. package/dist/commands/traverse.js +2 -2
  18. package/dist/engine/branch-regression.js +2 -1
  19. package/dist/engine/compose.js +3 -2
  20. package/dist/engine/core.js +5 -4
  21. package/dist/engine/engine-class.js +13 -12
  22. package/dist/engine/flow.js +4 -3
  23. package/dist/engine/run-graph.js +4 -3
  24. package/dist/errors.d.ts +122 -0
  25. package/dist/errors.js +125 -0
  26. package/dist/highlights/shorthand.d.ts +24 -0
  27. package/dist/highlights/shorthand.js +30 -0
  28. package/dist/index.d.ts +9 -7
  29. package/dist/index.js +5 -4
  30. package/dist/mem-page.js +2 -1
  31. package/dist/pilot.d.ts +1 -1
  32. package/dist/pilot.js +1 -1
  33. package/dist/practices-check.js +7 -5
  34. package/dist/traverse/coverage.d.ts +59 -0
  35. package/dist/traverse/coverage.js +127 -0
  36. package/dist/traverse/lease.d.ts +18 -0
  37. package/dist/traverse/lease.js +62 -0
  38. package/dist/traverse/run.d.ts +47 -0
  39. package/dist/traverse/run.js +439 -0
  40. package/examples/saucedemo/.waygraph-auto/050e5b55.log +0 -0
  41. package/examples/saucedemo/.waygraph-auto/096fea05.log +20 -0
  42. package/examples/saucedemo/.waygraph-auto/123d1cad.log +5 -0
  43. package/examples/saucedemo/.waygraph-auto/12ed8e28.log +0 -0
  44. package/examples/saucedemo/.waygraph-auto/1cb6f44c.log +5 -0
  45. package/examples/saucedemo/.waygraph-auto/1fdb2100.log +0 -0
  46. package/examples/saucedemo/.waygraph-auto/573abedd.log +0 -0
  47. package/examples/saucedemo/.waygraph-auto/9a26134c.log +20 -0
  48. package/examples/saucedemo/.waygraph-auto/aa226dd0.log +22 -0
  49. package/examples/saucedemo/.waygraph-auto/ac5dde3d.log +0 -0
  50. package/examples/saucedemo/.waygraph-auto/cae91469.log +0 -0
  51. package/examples/saucedemo/.waygraph-auto/d846cd78.log +0 -0
  52. package/examples/saucedemo/.waygraph-auto/eb38d92e.log +22 -0
  53. package/examples/saucedemo/.waygraph-auto/ee86203e.log +0 -0
  54. package/package.json +7 -4
  55. package/templates/scaffold/scripts/README.md +13 -0
  56. package/templates/scaffold/src/flows/README.md +18 -0
  57. package/templates/scaffold/src/map/README.md +31 -0
  58. package/templates/scaffold/src/states/README.md +15 -0
  59. package/templates/scaffold/tests/README.md +10 -0
  60. package/templates/skills/waygraph-convention.skill.md +6 -4
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Every error the engine throws, in one file, each with a stable code. When something breaks,
3
+ * `grep WG_ the-error-you-saw` (or open this file and Ctrl-F the code) tells you exactly what
4
+ * threw, why, and where to look - without re-explaining the engine to an AI every time.
5
+ *
6
+ * Convention: `WG_<AREA>_<WHAT>`. A code, once shipped, never changes meaning or gets reused for
7
+ * something else - add a new one instead. Every thrown `WaygraphError` message starts with
8
+ * `[CODE]`, so the code survives being copy-pasted out of a terminal/CI log on its own.
9
+ */
10
+ export declare const WAYGRAPH_ERRORS: {
11
+ readonly WG_PREFLIGHT_MISSING_KEY: {
12
+ readonly where: "engine/run-graph.ts - preflight()";
13
+ readonly means: "A Flow/chain needs a MemKey that mem.set() never received before run() started.";
14
+ readonly fix: "Pass --data / a mem.set() call for that key, or registerMemStub + --mem-stub for a fake value.";
15
+ };
16
+ readonly WG_RUNGRAPH_MAX_STEPS: {
17
+ readonly where: "engine/run-graph.ts - runGraph()";
18
+ readonly means: "A chain took more steps than maxSteps without reaching a Block with no .next() - almost always an unintended self-loop in branch()/next routing.";
19
+ readonly fix: "Check the Block(s) involved in the loop; raise maxSteps only if the loop is genuinely intended.";
20
+ };
21
+ readonly WG_RUNGRAPH_NOT_TERMINAL: {
22
+ readonly where: "engine/run-graph.ts - runGraph()";
23
+ readonly means: "A Block with no .next() resolved to a Checkpoint that wasn't in the caller's declared `terminals` set.";
24
+ readonly fix: "Add that Checkpoint to `terminals`, or fix the Block's resolve() - it landed somewhere the caller didn't expect.";
25
+ };
26
+ readonly WG_MAP_WRONG_KIND: {
27
+ readonly where: "engine/engine-class.ts - MapBuilder.gotoPage/gotoExternal/assert/method()";
28
+ readonly means: "A Block passed to a MapBuilder step method doesn't carry the __waygraphKind a real defineNavBlock/definePageBlock/defineAssertBlock/defineMethodBlock/defineEffectBlock stamps - either the wrong factory was used, or the object was hand-rolled instead of going through one.";
29
+ readonly fix: "Use the factory the error names, or move this Block to the matching .gotoPage()/.assert()/.method() call.";
30
+ };
31
+ readonly WG_MAP_WRONG_SALT: {
32
+ readonly where: "engine/engine-class.ts - MapBuilder.method()";
33
+ readonly means: "A Block passed to .method() isn't a real Method/Effect Block (missing __waygraphSalt).";
34
+ readonly fix: "Build it with defineMethodBlock/defineEffectBlock, or use .assert() if it's actually a defineAssertBlock.";
35
+ };
36
+ readonly WG_MAP_WRONG_ORIGIN: {
37
+ readonly where: "engine/engine-class.ts - MapBuilder.gotoPage/gotoExternal()";
38
+ readonly means: "A Block's static `url` origin doesn't match what .gotoPage() (same-origin) or .gotoExternal() (cross-origin) expects, given this map()'s homeOrigin.";
39
+ readonly fix: "Use .gotoExternal() for a genuinely different origin, .gotoPage() for the app's own homeOrigin, or fix the Block's url.";
40
+ };
41
+ readonly WG_MAP_FF_ALREADY_OPEN: {
42
+ readonly where: "engine/engine-class.ts - MapBuilder.ffStart()";
43
+ readonly means: ".ffStart() was called while a previous .ffStart() window is still open.";
44
+ readonly fix: "Call .ffEnd() before opening another fast-forward window.";
45
+ };
46
+ readonly WG_MAP_FF_EMPTY: {
47
+ readonly where: "engine/engine-class.ts - MapBuilder.ffEnd()";
48
+ readonly means: ".ffEnd() closed a window with zero steps inside it.";
49
+ readonly fix: "Add at least one .gotoPage()/.method()/.assert() inside the .ffStart()/.ffEnd() window.";
50
+ };
51
+ readonly WG_MAP_FF_NOT_CLOSED: {
52
+ readonly where: "engine/engine-class.ts - MapBuilder.end()";
53
+ readonly means: ".end() was called while a .ffStart() window was still open.";
54
+ readonly fix: "Call .ffEnd() before .end().";
55
+ };
56
+ readonly WG_MAP_FF_NOT_OPEN: {
57
+ readonly where: "engine/engine-class.ts - MapBuilder.ffEnd()";
58
+ readonly means: ".ffEnd() was called with no matching .ffStart().";
59
+ readonly fix: "Remove the stray .ffEnd(), or add the .ffStart() it's meant to close.";
60
+ };
61
+ readonly WG_MAP_EMPTY: {
62
+ readonly where: "engine/engine-class.ts - MapBuilder.end()";
63
+ readonly means: ".end() was called with zero steps added.";
64
+ readonly fix: "Add at least one .gotoPage()/.gotoExternal()/.assert()/.method() before .end().";
65
+ };
66
+ readonly WG_MAP_BRANCH_NO_PRIOR_STEP: {
67
+ readonly where: "engine/engine-class.ts - MapBuilder.branch()";
68
+ readonly means: ".branch() was called before any step existed to branch FROM.";
69
+ readonly fix: "Add a .gotoPage()/.gotoExternal()/.method()/.assert() before .branch().";
70
+ };
71
+ readonly WG_MAP_BRANCH_SHARED_SESSION_UNSUPPORTED: {
72
+ readonly where: "engine/engine-class.ts - a .branch()-built Flow's run()";
73
+ readonly means: "run(mem, config) was called on a branched Flow - that convenience form always closes its own browser/page before the branch's tag is known, so the branch can never be reached.";
74
+ readonly fix: "Call run(context, mem[, options]) instead, keeping the same page across the branch.";
75
+ };
76
+ readonly WG_BRANCH_REGRESSION_NO_BROWSER: {
77
+ readonly where: "engine/branch-regression.ts - runBranchRegression()";
78
+ readonly means: "cloneSession is on (the default) but context.browser() returned null - a persistent context has no Browser to clone a new context from.";
79
+ readonly fix: "Pass { cloneSession: false } to run the single live path in this context instead.";
80
+ };
81
+ readonly WG_COMPOSE_EMPTY: {
82
+ readonly where: "engine/compose.ts - composeBlock()";
83
+ readonly means: "composeBlock() was called with zero steps.";
84
+ readonly fix: "Pass at least one Block.";
85
+ };
86
+ readonly WG_FASTFORWARD_EMPTY: {
87
+ readonly where: "engine/compose.ts - fastForwardComposeBlock()";
88
+ readonly means: "A fast-forward window was closed with zero steps inside it.";
89
+ readonly fix: "Add at least one Block between ffStart()/ffEnd() (or the equivalent Map builder call).";
90
+ };
91
+ readonly WG_FLOW_BLOCK_NOT_FOUND: {
92
+ readonly where: "engine/core.ts - findBlockIndex() (used by withBlockVerify/modBlockVerify/chainFlow)";
93
+ readonly means: "The Block name/index/reference given doesn't exist in this Flow (or chained Flows).";
94
+ readonly fix: "Check the name/index against flow.blocks() - it lists every Block this Flow actually has, tag-prefixed for branches.";
95
+ };
96
+ readonly WG_CHAINFLOW_EMPTY: {
97
+ readonly where: "engine/flow.ts - chainFlow()";
98
+ readonly means: "chainFlow() was called with zero Flows.";
99
+ readonly fix: "Pass at least one Flow.";
100
+ };
101
+ readonly WG_MODVERIFY_FUNCTION_FORM: {
102
+ readonly where: "engine/core.ts - modVerify()";
103
+ readonly means: "modVerify() was called on a Block whose verify is a function (tag-driven), which has no fixed list to address an item into.";
104
+ readonly fix: "Use withVerify() to replace the whole verify instead.";
105
+ };
106
+ readonly WG_MODVERIFY_NOT_FOUND: {
107
+ readonly where: "engine/core.ts - modVerify()";
108
+ readonly means: "The Trait name/index given doesn't exist in this Block's verify list.";
109
+ readonly fix: "Check the Block's actual verify list for the right name/index.";
110
+ };
111
+ readonly WG_MEM_READ_BEFORE_SET: {
112
+ readonly where: "mem-page.ts - MemPage.get()";
113
+ readonly means: "mem.get(key) was called for a key that was never mem.set() on this MemPage.";
114
+ readonly fix: "Set it first, or use mem.has(key) to check before an optional read.";
115
+ };
116
+ };
117
+ export type WaygraphErrorCode = keyof typeof WAYGRAPH_ERRORS;
118
+ /** A thrown engine error with a stable, greppable `.code` - see WAYGRAPH_ERRORS for the full list. */
119
+ export declare class WaygraphError extends Error {
120
+ readonly code: WaygraphErrorCode;
121
+ constructor(code: WaygraphErrorCode, message: string);
122
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Every error the engine throws, in one file, each with a stable code. When something breaks,
3
+ * `grep WG_ the-error-you-saw` (or open this file and Ctrl-F the code) tells you exactly what
4
+ * threw, why, and where to look - without re-explaining the engine to an AI every time.
5
+ *
6
+ * Convention: `WG_<AREA>_<WHAT>`. A code, once shipped, never changes meaning or gets reused for
7
+ * something else - add a new one instead. Every thrown `WaygraphError` message starts with
8
+ * `[CODE]`, so the code survives being copy-pasted out of a terminal/CI log on its own.
9
+ */
10
+ export const WAYGRAPH_ERRORS = {
11
+ WG_PREFLIGHT_MISSING_KEY: {
12
+ where: "engine/run-graph.ts - preflight()",
13
+ means: "A Flow/chain needs a MemKey that mem.set() never received before run() started.",
14
+ fix: "Pass --data / a mem.set() call for that key, or registerMemStub + --mem-stub for a fake value.",
15
+ },
16
+ WG_RUNGRAPH_MAX_STEPS: {
17
+ where: "engine/run-graph.ts - runGraph()",
18
+ means: "A chain took more steps than maxSteps without reaching a Block with no .next() - almost always an unintended self-loop in branch()/next routing.",
19
+ fix: "Check the Block(s) involved in the loop; raise maxSteps only if the loop is genuinely intended.",
20
+ },
21
+ WG_RUNGRAPH_NOT_TERMINAL: {
22
+ where: "engine/run-graph.ts - runGraph()",
23
+ means: "A Block with no .next() resolved to a Checkpoint that wasn't in the caller's declared `terminals` set.",
24
+ fix: "Add that Checkpoint to `terminals`, or fix the Block's resolve() - it landed somewhere the caller didn't expect.",
25
+ },
26
+ WG_MAP_WRONG_KIND: {
27
+ where: "engine/engine-class.ts - MapBuilder.gotoPage/gotoExternal/assert/method()",
28
+ means: "A Block passed to a MapBuilder step method doesn't carry the __waygraphKind a real defineNavBlock/definePageBlock/defineAssertBlock/defineMethodBlock/defineEffectBlock stamps - either the wrong factory was used, or the object was hand-rolled instead of going through one.",
29
+ fix: "Use the factory the error names, or move this Block to the matching .gotoPage()/.assert()/.method() call.",
30
+ },
31
+ WG_MAP_WRONG_SALT: {
32
+ where: "engine/engine-class.ts - MapBuilder.method()",
33
+ means: "A Block passed to .method() isn't a real Method/Effect Block (missing __waygraphSalt).",
34
+ fix: "Build it with defineMethodBlock/defineEffectBlock, or use .assert() if it's actually a defineAssertBlock.",
35
+ },
36
+ WG_MAP_WRONG_ORIGIN: {
37
+ where: "engine/engine-class.ts - MapBuilder.gotoPage/gotoExternal()",
38
+ means: "A Block's static `url` origin doesn't match what .gotoPage() (same-origin) or .gotoExternal() (cross-origin) expects, given this map()'s homeOrigin.",
39
+ fix: "Use .gotoExternal() for a genuinely different origin, .gotoPage() for the app's own homeOrigin, or fix the Block's url.",
40
+ },
41
+ WG_MAP_FF_ALREADY_OPEN: {
42
+ where: "engine/engine-class.ts - MapBuilder.ffStart()",
43
+ means: ".ffStart() was called while a previous .ffStart() window is still open.",
44
+ fix: "Call .ffEnd() before opening another fast-forward window.",
45
+ },
46
+ WG_MAP_FF_EMPTY: {
47
+ where: "engine/engine-class.ts - MapBuilder.ffEnd()",
48
+ means: ".ffEnd() closed a window with zero steps inside it.",
49
+ fix: "Add at least one .gotoPage()/.method()/.assert() inside the .ffStart()/.ffEnd() window.",
50
+ },
51
+ WG_MAP_FF_NOT_CLOSED: {
52
+ where: "engine/engine-class.ts - MapBuilder.end()",
53
+ means: ".end() was called while a .ffStart() window was still open.",
54
+ fix: "Call .ffEnd() before .end().",
55
+ },
56
+ WG_MAP_FF_NOT_OPEN: {
57
+ where: "engine/engine-class.ts - MapBuilder.ffEnd()",
58
+ means: ".ffEnd() was called with no matching .ffStart().",
59
+ fix: "Remove the stray .ffEnd(), or add the .ffStart() it's meant to close.",
60
+ },
61
+ WG_MAP_EMPTY: {
62
+ where: "engine/engine-class.ts - MapBuilder.end()",
63
+ means: ".end() was called with zero steps added.",
64
+ fix: "Add at least one .gotoPage()/.gotoExternal()/.assert()/.method() before .end().",
65
+ },
66
+ WG_MAP_BRANCH_NO_PRIOR_STEP: {
67
+ where: "engine/engine-class.ts - MapBuilder.branch()",
68
+ means: ".branch() was called before any step existed to branch FROM.",
69
+ fix: "Add a .gotoPage()/.gotoExternal()/.method()/.assert() before .branch().",
70
+ },
71
+ WG_MAP_BRANCH_SHARED_SESSION_UNSUPPORTED: {
72
+ where: "engine/engine-class.ts - a .branch()-built Flow's run()",
73
+ means: "run(mem, config) was called on a branched Flow - that convenience form always closes its own browser/page before the branch's tag is known, so the branch can never be reached.",
74
+ fix: "Call run(context, mem[, options]) instead, keeping the same page across the branch.",
75
+ },
76
+ WG_BRANCH_REGRESSION_NO_BROWSER: {
77
+ where: "engine/branch-regression.ts - runBranchRegression()",
78
+ means: "cloneSession is on (the default) but context.browser() returned null - a persistent context has no Browser to clone a new context from.",
79
+ fix: "Pass { cloneSession: false } to run the single live path in this context instead.",
80
+ },
81
+ WG_COMPOSE_EMPTY: {
82
+ where: "engine/compose.ts - composeBlock()",
83
+ means: "composeBlock() was called with zero steps.",
84
+ fix: "Pass at least one Block.",
85
+ },
86
+ WG_FASTFORWARD_EMPTY: {
87
+ where: "engine/compose.ts - fastForwardComposeBlock()",
88
+ means: "A fast-forward window was closed with zero steps inside it.",
89
+ fix: "Add at least one Block between ffStart()/ffEnd() (or the equivalent Map builder call).",
90
+ },
91
+ WG_FLOW_BLOCK_NOT_FOUND: {
92
+ where: "engine/core.ts - findBlockIndex() (used by withBlockVerify/modBlockVerify/chainFlow)",
93
+ means: "The Block name/index/reference given doesn't exist in this Flow (or chained Flows).",
94
+ fix: "Check the name/index against flow.blocks() - it lists every Block this Flow actually has, tag-prefixed for branches.",
95
+ },
96
+ WG_CHAINFLOW_EMPTY: {
97
+ where: "engine/flow.ts - chainFlow()",
98
+ means: "chainFlow() was called with zero Flows.",
99
+ fix: "Pass at least one Flow.",
100
+ },
101
+ WG_MODVERIFY_FUNCTION_FORM: {
102
+ where: "engine/core.ts - modVerify()",
103
+ means: "modVerify() was called on a Block whose verify is a function (tag-driven), which has no fixed list to address an item into.",
104
+ fix: "Use withVerify() to replace the whole verify instead.",
105
+ },
106
+ WG_MODVERIFY_NOT_FOUND: {
107
+ where: "engine/core.ts - modVerify()",
108
+ means: "The Trait name/index given doesn't exist in this Block's verify list.",
109
+ fix: "Check the Block's actual verify list for the right name/index.",
110
+ },
111
+ WG_MEM_READ_BEFORE_SET: {
112
+ where: "mem-page.ts - MemPage.get()",
113
+ means: "mem.get(key) was called for a key that was never mem.set() on this MemPage.",
114
+ fix: "Set it first, or use mem.has(key) to check before an optional read.",
115
+ },
116
+ };
117
+ /** A thrown engine error with a stable, greppable `.code` - see WAYGRAPH_ERRORS for the full list. */
118
+ export class WaygraphError extends Error {
119
+ code;
120
+ constructor(code, message) {
121
+ super(`[${code}] ${message}`);
122
+ this.code = code;
123
+ this.name = "WaygraphError";
124
+ }
125
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Compact alternative to full-JSON `browser highlight` payloads, for the
3
+ * common case (a couple of rings, each just selector + label + maybe tone).
4
+ * Full JSON stays fully supported for anything needing size/weight/zoom/
5
+ * focus/detail/tag/todos - this only ever covers a subset.
6
+ *
7
+ * Grammar: `<selector>|<label>[|<tone>]` rings joined by `;`.
8
+ * "#submit|Login button|warning; .error|Error banner|danger"
9
+ *
10
+ * `;` and `|` were picked because neither is valid inside a CSS/Playwright
11
+ * selector or an ordinary label - unlike `,`/`:`, which real selectors use
12
+ * constantly (`:nth-child(2)`, `:not(.x)`, `[href*="mailto:"]`, compound
13
+ * `a, b` lists). A selector/label containing a literal `;` or `|` needs the
14
+ * full-JSON form instead - documented, not silently mishandled.
15
+ */
16
+ import type { PilotHighlightFixtures } from "../pilot-overlay.js";
17
+ export type ParsedHighlightShorthand = {
18
+ type: "ok";
19
+ fixtures: PilotHighlightFixtures;
20
+ } | {
21
+ type: "error";
22
+ reason: string;
23
+ };
24
+ export declare function parseHighlightShorthand(raw: string): ParsedHighlightShorthand;
@@ -0,0 +1,30 @@
1
+ export function parseHighlightShorthand(raw) {
2
+ const tokens = raw
3
+ .split(";")
4
+ .map((t) => t.trim())
5
+ .filter((t) => t.length > 0);
6
+ if (tokens.length === 0) {
7
+ return { type: "error", reason: "empty highlight shorthand - give at least one <selector>|<label> ring" };
8
+ }
9
+ const rings = [];
10
+ for (let i = 0; i < tokens.length; i++) {
11
+ const token = tokens[i];
12
+ const parts = token.split("|").map((p) => p.trim());
13
+ if (parts.length < 2 || parts.length > 3) {
14
+ return {
15
+ type: "error",
16
+ reason: `ring ${i + 1} ("${token}") must be <selector>|<label> or <selector>|<label>|<tone> ` +
17
+ `- got ${parts.length} part(s) separated by "|"`,
18
+ };
19
+ }
20
+ const [selector, label, tone] = parts;
21
+ if (!selector) {
22
+ return { type: "error", reason: `ring ${i + 1} ("${token}") has an empty selector` };
23
+ }
24
+ if (!label) {
25
+ return { type: "error", reason: `ring ${i + 1} ("${token}") has an empty label` };
26
+ }
27
+ rings.push(tone ? { selector, label, tone } : { selector, label });
28
+ }
29
+ return { type: "ok", fixtures: { rings } };
30
+ }
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export { connect, checkpoint } from "./types.js";
3
3
  export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
4
4
  export { registerMemStub, seedMemStub, getMemStub } from "./mem-stub.js";
5
5
  export { css, byText, byRole } from "./selector.js";
6
+ export { WaygraphError, WAYGRAPH_ERRORS } from "./errors.js";
7
+ export type { WaygraphErrorCode } from "./errors.js";
6
8
  export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
7
9
  export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./engine.js";
8
10
  export type { BranchEntry, BranchRunResult, RunBranchRegressionOptions } from "./engine/branch-regression.js";
@@ -10,15 +12,15 @@ export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwell
10
12
  export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, TodoDockUiOpts, BannerPos, BannerUiOpts, TodoDockUiResolved, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
11
13
  export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, AssertBlock, AssertBlockOptions, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, Layout, LayoutOptions, MapBuilderOptions, } from "./engine.js";
12
14
  export type { ActionPage } from "./types.js";
13
- export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
14
- export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./graph.js";
15
+ export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./analysis/graph.js";
16
+ export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./analysis/graph.js";
15
17
  export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
16
18
  export type { BlocksSelect, BlocksSelectKind } from "./blocks-select.js";
17
- export { EdgeLeaseCoordinator } from "./traverse-lease.js";
18
- export { runTraverse } from "./traverse-run.js";
19
- export type { TraverseOptions, TraverseSessionMode, TraverseWorkerResult, } from "./traverse-run.js";
20
- export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport, } from "./traverse-coverage.js";
21
- export type { TraverseCoverageReport } from "./traverse-coverage.js";
19
+ export { EdgeLeaseCoordinator } from "./traverse/lease.js";
20
+ export { runTraverse } from "./traverse/run.js";
21
+ export type { TraverseOptions, TraverseSessionMode, TraverseWorkerResult, } from "./traverse/run.js";
22
+ export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport, } from "./traverse/coverage.js";
23
+ export type { TraverseCoverageReport } from "./traverse/coverage.js";
22
24
  export { WG_UI_SEL, WG_READY_PANEL_SEL, WG_READY_AUTO_SEL, WG_READY_BANNER_SEL, readWgOverlayBeacon, readWgOverlayBeaconInPage, assertWgOverlayReady, } from "./overlay-beacon.js";
23
25
  export type { WgModalKind, WgBeaconRow } from "./overlay-beacon.js";
24
26
  export { AutoSession, stubFilePath } from "./auto-session.js";
package/dist/index.js CHANGED
@@ -2,14 +2,15 @@ export { connect, checkpoint } from "./types.js";
2
2
  export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
3
3
  export { registerMemStub, seedMemStub, getMemStub } from "./mem-stub.js";
4
4
  export { css, byText, byRole } from "./selector.js";
5
+ export { WaygraphError, WAYGRAPH_ERRORS } from "./errors.js";
5
6
  export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
6
7
  export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./engine.js";
7
8
  export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, resolveTodoDockUi, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, WAYGRAPH_RING_CSS, } from "./highlights.js";
8
- export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
9
+ export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./analysis/graph.js";
9
10
  export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
10
- export { EdgeLeaseCoordinator } from "./traverse-lease.js";
11
- export { runTraverse } from "./traverse-run.js";
12
- export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport, } from "./traverse-coverage.js";
11
+ export { EdgeLeaseCoordinator } from "./traverse/lease.js";
12
+ export { runTraverse } from "./traverse/run.js";
13
+ export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport, } from "./traverse/coverage.js";
13
14
  export { WG_UI_SEL, WG_READY_PANEL_SEL, WG_READY_AUTO_SEL, WG_READY_BANNER_SEL, readWgOverlayBeacon, readWgOverlayBeaconInPage, assertWgOverlayReady, } from "./overlay-beacon.js";
14
15
  export { AutoSession, stubFilePath } from "./auto-session.js";
15
16
  export { pilotStart } from "./pilot.js";
package/dist/mem-page.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { WaygraphError } from "./errors.js";
1
2
  /**
2
3
  * A typed key into MemPage. The phantom `_phantom` field is never assigned - it
3
4
  * exists only so TypeScript can infer `T` at each call site. The store is keyed by
@@ -51,7 +52,7 @@ export class MemPage {
51
52
  */
52
53
  get(k) {
53
54
  if (!this.store.has(k)) {
54
- throw new Error(`MemPage: "${k.name}" read before it was set`);
55
+ throw new WaygraphError("WG_MEM_READ_BEFORE_SET", `MemPage: "${k.name}" read before it was set`);
55
56
  }
56
57
  return this.store.get(k);
57
58
  }
package/dist/pilot.d.ts CHANGED
@@ -28,7 +28,7 @@
28
28
  * hand-picking indices from each status response) that proved this needs no
29
29
  * new execution primitive.
30
30
  */
31
- import { type WaygraphGraph } from "./graph.js";
31
+ import { type WaygraphGraph } from "./analysis/graph.js";
32
32
  import { listBrowserSessions, type SessionMeta } from "./auto-session-ipc.js";
33
33
  import { type BrowserStartInit } from "./browser.js";
34
34
  import type { SessionSnapshot } from "./auto-session.js";
package/dist/pilot.js CHANGED
@@ -28,7 +28,7 @@
28
28
  * hand-picking indices from each status response) that proved this needs no
29
29
  * new execution primitive.
30
30
  */
31
- import { discoverGraph } from "./graph.js";
31
+ import { discoverGraph } from "./analysis/graph.js";
32
32
  import { requestSession, listBrowserSessions, resolveSessionMeta, } from "./auto-session-ipc.js";
33
33
  import { browserStart } from "./browser.js";
34
34
  /**
@@ -130,17 +130,19 @@ export function collectPracticeWarnings(projectDir, walkDir, opts) {
130
130
  warnings.push({
131
131
  kind: "overcomplex-selector",
132
132
  file: rel,
133
- detail: `uses ${via} — reach for a plain CSS selector (id/class/data-attribute, via a .sel.ts ` +
134
- "export) instead of a regex/XPath match unless the DOM genuinely has no stable selector",
133
+ detail: `uses ${via} — reach for a plain CSS selector (id/class/data-attribute, via a _sel.ts/` +
134
+ "*.sel.ts export) instead of a regex/XPath match unless the DOM genuinely has no stable selector",
135
135
  });
136
136
  }
137
137
  if (!isIgnored("inline-selector-should-use-sel-file", ignore) && INLINE_SIMPLE_SELECTOR.test(src)) {
138
- const siblingSelFile = readdirSync(dirname(file)).some((f) => f.endsWith(".sel.ts"));
138
+ // Two real, coexisting conventions: the Map layout's own `_sel.ts` (one per page folder, next
139
+ // to `_nav`/`_page`/`_methods`) and the freeform layout's per-concern `<name>.sel.ts`.
140
+ const siblingSelFile = readdirSync(dirname(file)).some((f) => f === "_sel.ts" || f.endsWith(".sel.ts"));
139
141
  if (siblingSelFile) {
140
142
  warnings.push({
141
143
  kind: "inline-selector-should-use-sel-file",
142
144
  file: rel,
143
- detail: "a selector string is inlined here, but this directory already has a *.sel.ts file — " +
145
+ detail: "a selector string is inlined here, but this directory already has a _sel.ts / *.sel.ts file — " +
144
146
  "add/reuse the selector there instead of a new one-off literal",
145
147
  });
146
148
  }
@@ -171,7 +173,7 @@ export function practiceKindLabel(kind) {
171
173
  case "overcomplex-selector":
172
174
  return "regex/XPath selector where a plain one would do";
173
175
  case "inline-selector-should-use-sel-file":
174
- return "inline selector — a .sel.ts file already exists here";
176
+ return "inline selector — a _sel.ts/*.sel.ts file already exists here";
175
177
  default: {
176
178
  const _exhaustive = kind;
177
179
  return _exhaustive;
@@ -0,0 +1,59 @@
1
+ export interface TraverseCoverageReport {
2
+ version: 1;
3
+ generatedAt: string;
4
+ projectDir: string;
5
+ parallel: number;
6
+ /** Unique edge keys in the discovered graph (block names). */
7
+ edgesTotal: number;
8
+ /** Graph edges successfully walked this suite (after instance normalize). */
9
+ edgesHit: number;
10
+ /** edgesHit / edgesTotal (0 when total is 0). */
11
+ ratio: number;
12
+ /** Optional suite gate that was applied (0-1). */
13
+ minEdgeCoverage?: number;
14
+ /** true when minEdgeCoverage was set and ratio met it. */
15
+ coveragePass?: boolean;
16
+ /** Sorted graph edge keys that were hit. */
17
+ hit: string[];
18
+ /** Sorted graph edge keys never walked. */
19
+ missed: string[];
20
+ /** Raw worker hit keys (may include block::instanceId). */
21
+ rawHit?: string[];
22
+ workers?: Array<{
23
+ traverseId: string;
24
+ exitCode: number;
25
+ steps: number;
26
+ edgesHit: number;
27
+ leaf?: string;
28
+ }>;
29
+ }
30
+ /** Static graph edge key = Block runtime name. */
31
+ export declare function edgeKeyFromGraphEdge(edge: {
32
+ block: string;
33
+ }): string;
34
+ /** Collect unique edge keys from a WaygraphGraph-like edges list. */
35
+ export declare function collectGraphEdgeKeys(edges: ReadonlyArray<{
36
+ block: string;
37
+ }>): string[];
38
+ /**
39
+ * Map a worker hit key onto the static graph key it covers.
40
+ * `add-to-cart::sku-1` -> `add-to-cart` when that block is in the graph.
41
+ */
42
+ export declare function normalizeHitKey(hitKey: string, graphKeys: ReadonlySet<string>): string | null;
43
+ /**
44
+ * Parse --min-edge-coverage: `0.8`, `80%`, `80` (as percent when > 1).
45
+ * Returns 0-1 fraction, or null when unset/invalid.
46
+ */
47
+ export declare function parseMinEdgeCoverage(raw: unknown): number | null;
48
+ export declare function buildCoverageReport(opts: {
49
+ projectDir: string;
50
+ parallel: number;
51
+ allEdgeKeys: readonly string[];
52
+ hitKeys: ReadonlySet<string> | readonly string[];
53
+ minEdgeCoverage?: number | null;
54
+ workers?: TraverseCoverageReport["workers"];
55
+ }): TraverseCoverageReport;
56
+ /** Greppable one-liner for agents/CI. */
57
+ export declare function formatCoverageLine(report: TraverseCoverageReport): string;
58
+ export declare function defaultCoverageOutPath(projectDir: string): string;
59
+ export declare function writeCoverageReport(path: string, report: TraverseCoverageReport): void;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Phase E: traverse edge coverage report + suite gate.
3
+ * Coverage is independent of leaf PASS - a green leaf can still fail CI
4
+ * when --min-edge-coverage is set and ratio is too low.
5
+ *
6
+ * Denominator = unique static graph edge block names (FF inners stay as their
7
+ * own graph edges when discovered; RFC open Q deferred - no covered-via-prefix yet).
8
+ * A hit of `block::instanceId` counts as covering `block`.
9
+ */
10
+ import { mkdirSync, writeFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ /** Static graph edge key = Block runtime name. */
13
+ export function edgeKeyFromGraphEdge(edge) {
14
+ return edge.block;
15
+ }
16
+ /** Collect unique edge keys from a WaygraphGraph-like edges list. */
17
+ export function collectGraphEdgeKeys(edges) {
18
+ const set = new Set();
19
+ for (const e of edges)
20
+ set.add(edgeKeyFromGraphEdge(e));
21
+ return [...set].sort();
22
+ }
23
+ /**
24
+ * Map a worker hit key onto the static graph key it covers.
25
+ * `add-to-cart::sku-1` -> `add-to-cart` when that block is in the graph.
26
+ */
27
+ export function normalizeHitKey(hitKey, graphKeys) {
28
+ if (graphKeys.has(hitKey))
29
+ return hitKey;
30
+ const base = hitKey.includes("::") ? hitKey.slice(0, hitKey.indexOf("::")) : hitKey;
31
+ if (graphKeys.has(base))
32
+ return base;
33
+ return null;
34
+ }
35
+ /**
36
+ * Parse --min-edge-coverage: `0.8`, `80%`, `80` (as percent when > 1).
37
+ * Returns 0-1 fraction, or null when unset/invalid.
38
+ */
39
+ export function parseMinEdgeCoverage(raw) {
40
+ if (raw === undefined || raw === null || raw === "")
41
+ return null;
42
+ if (typeof raw === "number" && Number.isFinite(raw)) {
43
+ if (raw < 0)
44
+ return null;
45
+ if (raw <= 1)
46
+ return raw;
47
+ if (raw <= 100)
48
+ return raw / 100;
49
+ return null;
50
+ }
51
+ if (typeof raw !== "string")
52
+ return null;
53
+ const t = raw.trim().toLowerCase();
54
+ if (!t)
55
+ return null;
56
+ if (t.endsWith("%")) {
57
+ const n = Number(t.slice(0, -1));
58
+ if (!Number.isFinite(n) || n < 0 || n > 100)
59
+ return null;
60
+ return n / 100;
61
+ }
62
+ const n = Number(t);
63
+ if (!Number.isFinite(n) || n < 0)
64
+ return null;
65
+ if (n <= 1)
66
+ return n;
67
+ if (n <= 100)
68
+ return n / 100;
69
+ return null;
70
+ }
71
+ export function buildCoverageReport(opts) {
72
+ const all = [...new Set(opts.allEdgeKeys)].sort();
73
+ const graphSet = new Set(all);
74
+ const rawList = Array.isArray(opts.hitKeys) ? [...opts.hitKeys] : [...opts.hitKeys];
75
+ const covered = new Set();
76
+ for (const raw of rawList) {
77
+ const n = normalizeHitKey(raw, graphSet);
78
+ if (n)
79
+ covered.add(n);
80
+ }
81
+ const hit = all.filter((k) => covered.has(k));
82
+ const missed = all.filter((k) => !covered.has(k));
83
+ const edgesTotal = all.length;
84
+ const edgesHit = hit.length;
85
+ const ratio = edgesTotal === 0 ? 0 : edgesHit / edgesTotal;
86
+ const min = opts.minEdgeCoverage != null && Number.isFinite(opts.minEdgeCoverage)
87
+ ? Math.min(1, Math.max(0, opts.minEdgeCoverage))
88
+ : undefined;
89
+ const coveragePass = min === undefined ? undefined : ratio + 1e-12 >= min;
90
+ const coverageExtra = min !== undefined && coveragePass !== undefined
91
+ ? { minEdgeCoverage: min, coveragePass }
92
+ : {};
93
+ return {
94
+ version: 1,
95
+ generatedAt: new Date().toISOString(),
96
+ projectDir: opts.projectDir,
97
+ parallel: opts.parallel,
98
+ edgesTotal,
99
+ edgesHit,
100
+ ratio: Math.round(ratio * 10000) / 10000,
101
+ ...coverageExtra,
102
+ hit,
103
+ missed,
104
+ rawHit: [...new Set(rawList)].sort(),
105
+ ...(opts.workers ? { workers: opts.workers } : {}),
106
+ };
107
+ }
108
+ /** Greppable one-liner for agents/CI. */
109
+ export function formatCoverageLine(report) {
110
+ const pct = (report.ratio * 100).toFixed(1);
111
+ const minPart = report.minEdgeCoverage !== undefined
112
+ ? ` min=${(report.minEdgeCoverage * 100).toFixed(1)}%` +
113
+ (report.coveragePass === false
114
+ ? " FAIL"
115
+ : report.coveragePass === true
116
+ ? " PASS"
117
+ : "")
118
+ : "";
119
+ return `[Coverage edges=${report.edgesHit}/${report.edgesTotal} ratio=${pct}%${minPart}]`;
120
+ }
121
+ export function defaultCoverageOutPath(projectDir) {
122
+ return join(projectDir, ".waygraph-traverse", "coverage.json");
123
+ }
124
+ export function writeCoverageReport(path, report) {
125
+ mkdirSync(dirname(path), { recursive: true });
126
+ writeFileSync(path, JSON.stringify(report, null, 2) + "\n", "utf-8");
127
+ }
@@ -0,0 +1,18 @@
1
+ export interface EdgeLeaseRecord {
2
+ workerId: string;
3
+ claimedAt: number;
4
+ }
5
+ export declare class EdgeLeaseCoordinator {
6
+ private readonly held;
7
+ private readonly dir;
8
+ constructor(opts?: {
9
+ projectDir?: string;
10
+ persist?: boolean;
11
+ });
12
+ /** True if this worker now owns `edgeKey` (or already owned it). */
13
+ tryClaim(edgeKey: string, workerId: string): boolean;
14
+ release(edgeKey: string, workerId: string): void;
15
+ /** Stable partition: same key always maps to the same worker index. */
16
+ static partitionIndex(edgeKey: string, parallel: number): number;
17
+ private flush;
18
+ }