waygraph 0.12.6 → 0.12.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.
Files changed (34) hide show
  1. package/README.md +2 -1
  2. package/dist/agent-dive.d.ts +19 -0
  3. package/dist/agent-dive.js +162 -0
  4. package/dist/auto-explore-run.d.ts +5 -0
  5. package/dist/auto-explore-run.js +15 -5
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +152 -1
  8. package/dist/traverse-run.d.ts +24 -0
  9. package/dist/traverse-run.js +227 -0
  10. package/package.json +1 -1
  11. package/templates/agents/waygraph-author.agent.md +36 -0
  12. package/templates/agents/waygraph-healer.agent.md +33 -0
  13. package/templates/agents/waygraph-planner.agent.md +35 -0
  14. package/templates/scaffold/README.md +11 -8
  15. package/templates/scaffold/STRUCTURE.md +42 -17
  16. package/templates/scaffold/package.json +13 -8
  17. package/templates/scaffold/playwright.config.ts +15 -0
  18. package/templates/scaffold/scripts/fixture-server.mjs +41 -0
  19. package/templates/scaffold/scripts/with-fixture.mjs +69 -0
  20. package/templates/scaffold/src/blocks/SITE-MAP.md +5 -6
  21. package/templates/scaffold/src/blocks/demo-web/NAV.md +17 -8
  22. package/templates/scaffold/src/blocks/demo-web/demo-sel.ts +14 -0
  23. package/templates/scaffold/src/blocks/demo-web/home.html +65 -0
  24. package/templates/scaffold/src/blocks/demo-web/home.page.block.ts +29 -0
  25. package/templates/scaffold/src/blocks/demo-web/methods/add-item.effect.block.ts +68 -0
  26. package/templates/scaffold/src/blocks/demo-web/methods/assert-hello.method.block.ts +11 -12
  27. package/templates/scaffold/src/blocks/demo-web/methods/clear-cart.method.block.ts +26 -0
  28. package/templates/scaffold/src/blocks/demo-web/methods/remove-item.effect.block.ts +63 -0
  29. package/templates/scaffold/src/blocks/demo-web/nav-home.block.ts +5 -8
  30. package/templates/scaffold/src/flows/example.flow.ts +3 -4
  31. package/templates/scaffold/src/flows/shop.flow.ts +42 -0
  32. package/templates/scaffold/src/states/demo.mem-keys.ts +7 -0
  33. package/templates/scaffold/src/states/demo.states.ts +2 -0
  34. package/templates/scaffold/tests/example.spec.ts +9 -0
package/README.md CHANGED
@@ -89,7 +89,8 @@ Live reference: `examples/saucedemo/src/blocks/saucedemo-web/inventory/`
89
89
  |------|---------|
90
90
  | Watch Sauce Demo step-through (temp dir only) | `npx waygraph try demo` |
91
91
  | Interactive explore (Add/Remove/Open details from live page) | `cd examples/saucedemo && npm i && npx waygraph auto` |
92
- | Scaffold a new **offline** project (green `npm test` on a `data:` URL) | `npx create-waygraph my-app` **or** `npx waygraph init my-app` |
92
+ | Scaffold a new **offline** project (green `npm test` on local fixture `:4177`) | `npx create-waygraph my-app` **or** `npx waygraph init my-app` |
93
+ | Install coding-agent defs for diving an app into Blocks | `npx waygraph agent-dive --loop claude` (also `opencode` / `cursor` / `vscode`) |
93
94
  | Add waygraph to an existing repo | `npm install waygraph @playwright/test` |
94
95
 
95
96
  **Scaffold is not hidden inside waygraph alone** - the offline starter also ships as
@@ -0,0 +1,19 @@
1
+ export type AgentDiveLoop = "claude" | "opencode" | "cursor" | "vscode";
2
+ export interface AgentSpec {
3
+ name: string;
4
+ description: string;
5
+ model?: string;
6
+ color?: string;
7
+ tools: string[];
8
+ instructions: string;
9
+ }
10
+ /** Minimal YAML-ish frontmatter parser for `*.agent.md` templates. */
11
+ export declare function parseAgentSpec(filePath: string): AgentSpec;
12
+ export declare function loadAgentSpecs(): AgentSpec[];
13
+ export interface AgentDiveOptions {
14
+ loop: AgentDiveLoop;
15
+ projectDir: string;
16
+ /** Also copy raw prompts under docs/waygraph-agents/ */
17
+ prompts?: boolean;
18
+ }
19
+ export declare function runAgentDive(opts: AgentDiveOptions): void;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `waygraph agent-dive` — initialize coding-agent definitions for diving an app
3
+ * into waygraph Blocks (Playwright `init-agents` analogue).
4
+ */
5
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "node:fs";
6
+ import { basename, dirname, join, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ function packageRoot() {
9
+ // dist/agent-dive.js -> ..
10
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
+ }
12
+ function agentsTemplateDir() {
13
+ return join(packageRoot(), "templates", "agents");
14
+ }
15
+ /** Minimal YAML-ish frontmatter parser for `*.agent.md` templates. */
16
+ export function parseAgentSpec(filePath) {
17
+ const raw = readFileSync(filePath, "utf-8");
18
+ if (!raw.startsWith("---\n")) {
19
+ throw new Error(`agent-dive: missing frontmatter in ${filePath}`);
20
+ }
21
+ const end = raw.indexOf("\n---\n", 4);
22
+ if (end < 0)
23
+ throw new Error(`agent-dive: unclosed frontmatter in ${filePath}`);
24
+ const fm = raw.slice(4, end);
25
+ const body = raw.slice(end + 5).trim();
26
+ const meta = {};
27
+ let listKey = null;
28
+ const lists = {};
29
+ for (const line of fm.split("\n")) {
30
+ if (/^\s+-\s+/.test(line) && listKey) {
31
+ lists[listKey].push(line.replace(/^\s+-\s+/, "").trim());
32
+ continue;
33
+ }
34
+ listKey = null;
35
+ const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
36
+ if (!m)
37
+ continue;
38
+ const key = m[1];
39
+ const val = m[2].trim();
40
+ if (val === "") {
41
+ listKey = key;
42
+ lists[key] = [];
43
+ }
44
+ else {
45
+ meta[key] = val;
46
+ }
47
+ }
48
+ const name = meta.name || basename(filePath).replace(/\.agent\.md$/, "");
49
+ return {
50
+ name,
51
+ description: meta.description || name,
52
+ ...(meta.model ? { model: meta.model } : {}),
53
+ ...(meta.color ? { color: meta.color } : {}),
54
+ tools: lists.tools ?? [],
55
+ instructions: body,
56
+ };
57
+ }
58
+ export function loadAgentSpecs() {
59
+ const dir = agentsTemplateDir();
60
+ if (!existsSync(dir)) {
61
+ throw new Error(`agent-dive: templates missing at ${dir}`);
62
+ }
63
+ return readdirSync(dir)
64
+ .filter((f) => f.endsWith(".agent.md"))
65
+ .sort()
66
+ .map((f) => parseAgentSpec(join(dir, f)));
67
+ }
68
+ function writeFileLogged(path, content, kind) {
69
+ mkdirSync(dirname(path), { recursive: true });
70
+ writeFileSync(path, content.endsWith("\n") ? content : content + "\n");
71
+ console.log(` wrote ${path} (${kind})`);
72
+ }
73
+ function claudeAgentMd(agent) {
74
+ const tools = agent.tools.join(", ");
75
+ const lines = [
76
+ "---",
77
+ `name: ${agent.name}`,
78
+ `description: ${agent.description}`,
79
+ ...(agent.model ? [`model: ${agent.model}`] : []),
80
+ ...(agent.color ? [`color: ${agent.color}`] : []),
81
+ ...(tools ? [`tools: ${tools}`] : []),
82
+ "---",
83
+ "",
84
+ agent.instructions,
85
+ "",
86
+ ];
87
+ return lines.join("\n");
88
+ }
89
+ function opencodePromptMd(agent) {
90
+ return `${agent.instructions}\n`;
91
+ }
92
+ function cursorRuleMdc(agent) {
93
+ return `---
94
+ description: ${agent.description}
95
+ globs:
96
+ alwaysApply: false
97
+ ---
98
+
99
+ # ${agent.name}
100
+
101
+ ${agent.instructions}
102
+ `;
103
+ }
104
+ function vscodeAgentMd(agent) {
105
+ return claudeAgentMd(agent);
106
+ }
107
+ export function runAgentDive(opts) {
108
+ const agents = loadAgentSpecs();
109
+ const root = opts.projectDir;
110
+ console.log(`waygraph agent-dive (${opts.loop}) -> ${root}`);
111
+ console.log(` ${agents.length} agent(s) from package templates`);
112
+ switch (opts.loop) {
113
+ case "claude": {
114
+ const dir = join(root, ".claude", "agents");
115
+ mkdirSync(dir, { recursive: true });
116
+ for (const a of agents) {
117
+ writeFileLogged(join(dir, `${a.name}.md`), claudeAgentMd(a), "claude agent");
118
+ }
119
+ break;
120
+ }
121
+ case "opencode": {
122
+ const dir = join(root, ".opencode", "prompts");
123
+ mkdirSync(dir, { recursive: true });
124
+ for (const a of agents) {
125
+ writeFileLogged(join(dir, `${a.name}.md`), opencodePromptMd(a), "opencode prompt");
126
+ }
127
+ break;
128
+ }
129
+ case "cursor": {
130
+ const dir = join(root, ".cursor", "rules");
131
+ mkdirSync(dir, { recursive: true });
132
+ for (const a of agents) {
133
+ writeFileLogged(join(dir, `${a.name}.mdc`), cursorRuleMdc(a), "cursor rule");
134
+ }
135
+ break;
136
+ }
137
+ case "vscode": {
138
+ const dir = join(root, ".github", "agents");
139
+ mkdirSync(dir, { recursive: true });
140
+ for (const a of agents) {
141
+ writeFileLogged(join(dir, `${a.name}.md`), vscodeAgentMd(a), "vscode agent");
142
+ }
143
+ break;
144
+ }
145
+ default: {
146
+ const _exhaustive = opts.loop;
147
+ throw new Error(`agent-dive: unsupported loop ${_exhaustive}`);
148
+ }
149
+ }
150
+ if (opts.prompts) {
151
+ const dir = join(root, "docs", "waygraph-agents");
152
+ mkdirSync(dir, { recursive: true });
153
+ for (const a of agents) {
154
+ writeFileLogged(join(dir, `${a.name}.md`), `# ${a.name}\n\n${a.description}\n\n${a.instructions}\n`, "prompt copy");
155
+ }
156
+ }
157
+ console.log("");
158
+ console.log("Next: point your coding agent at those files, then:");
159
+ console.log(" npx waygraph check .");
160
+ console.log(" npx waygraph auto --cli");
161
+ console.log(" (scaffold: npx waygraph init my-app)");
162
+ }
@@ -5,6 +5,11 @@ export interface AutoExploreOptions {
5
5
  /** Initial URL when the page is blank (defaults to baseURL). */
6
6
  startUrl?: string;
7
7
  }
8
+ /**
9
+ * Saucedemo-only defaults. Exact key names only — never substring `"login"`
10
+ * (PIA `login-email` string must stay unset; see docs/proposals/auto-login-email-seed-bug.md).
11
+ */
12
+ export declare function defaultMemValueForKey(keyName: string): unknown | undefined;
8
13
  /**
9
14
  * Interactive waygraph auto: locate current Checkpoint, list runnable Blocks, pick one, repeat.
10
15
  */
@@ -77,12 +77,24 @@ function missingMemKeys(entry, mem) {
77
77
  return requires.filter((k) => !mem.has(k));
78
78
  }
79
79
  const DEFAULT_CREDENTIALS = { username: "standard_user", password: "secret_sauce" };
80
- function defaultMemValueForKey(keyName) {
81
- if (keyName.includes("credential") || keyName.includes("login")) {
80
+ /**
81
+ * Saucedemo-only defaults. Exact key names only — never substring `"login"`
82
+ * (PIA `login-email` string must stay unset; see docs/proposals/auto-login-email-seed-bug.md).
83
+ */
84
+ export function defaultMemValueForKey(keyName) {
85
+ if (keyName === "login-credentials" ||
86
+ keyName === "credentials" ||
87
+ keyName === "saucedemo.credentials") {
82
88
  return DEFAULT_CREDENTIALS;
83
89
  }
84
90
  return undefined;
85
91
  }
92
+ function defaultMemJsonPrompt(keyName) {
93
+ const def = defaultMemValueForKey(keyName);
94
+ if (def !== undefined)
95
+ return JSON.stringify(def);
96
+ return "{}";
97
+ }
86
98
  function seedMemFromEnv(mem, entry) {
87
99
  const raw = process.env.WAYGRAPH_DATA ?? process.env.WAYGRAPH_AUTO_MEM;
88
100
  if (!raw)
@@ -129,9 +141,7 @@ async function promptMemCli(entry, mem) {
129
141
  const rl = createInterface({ input, output });
130
142
  try {
131
143
  for (const k of missingMemKeys(entry, mem)) {
132
- const def = k.name.includes("credential") || k.name.includes("login")
133
- ? '{"username":"standard_user","password":"secret_sauce"}'
134
- : "{}";
144
+ const def = defaultMemJsonPrompt(k.name);
135
145
  const line = await rl.question(` ${k.name} JSON [${def}]: `);
136
146
  const json = line.trim() || def;
137
147
  mem.set(k, JSON.parse(json));
package/dist/cli.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--ff-expand`
8
8
  * run Execute; `--blocks` `--data` `--non-headless` `--video`
9
9
  *
10
- * Also: list / nav / validate / check / graph / init / try
10
+ * Also: list / nav / validate / check / graph / init / agent-dive / traverse / try
11
11
  * Aliases (one release): `chain` -> run/demo --blocks; `--autoplay` -> `--auto-next`
12
12
  *
13
13
  * "project" defaults to cwd. Flags beat WAYGRAPH_* env.
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--ff-expand`
8
8
  * run Execute; `--blocks` `--data` `--non-headless` `--video`
9
9
  *
10
- * Also: list / nav / validate / check / graph / init / try
10
+ * Also: list / nav / validate / check / graph / init / agent-dive / traverse / try
11
11
  * Aliases (one release): `chain` -> run/demo --blocks; `--autoplay` -> `--auto-next`
12
12
  *
13
13
  * "project" defaults to cwd. Flags beat WAYGRAPH_* env.
@@ -19,6 +19,8 @@ import { fileURLToPath } from "node:url";
19
19
  import { spawn } from "node:child_process";
20
20
  import { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
21
21
  import { runAutoExplore } from "./auto-explore-run.js";
22
+ import { runAgentDive } from "./agent-dive.js";
23
+ import { runTraverse } from "./traverse-run.js";
22
24
  // ---------------------------------------------------------------------------
23
25
  // Filesystem
24
26
  // ---------------------------------------------------------------------------
@@ -3660,6 +3662,14 @@ Primary (less is more):
3660
3662
 
3661
3663
  Also:
3662
3664
  waygraph list | nav | validate | check | graph | init <name>
3665
+ waygraph traverse [project] Serial graph crawl (Phase B)
3666
+ --from <Checkpoint> Seed / start checkpoint
3667
+ --data '{...}' Mem seed
3668
+ --max-steps N Cap Block runs (default 50)
3669
+ --max-visits N Cap visits per node (default 2)
3670
+ --non-headless Show browser
3671
+ waygraph agent-dive [--loop claude|opencode|cursor|vscode] [--prompts]
3672
+ Initialize coding-agent defs (Playwright init-agents analogue)
3663
3673
  waygraph try [demo|auto|auto:cli] One-shot saucedemo in a temp dir
3664
3674
  try auto Headed browser panel (default)
3665
3675
  try auto:cli / --cli Terminal menu instead
@@ -3756,6 +3766,147 @@ async function main() {
3756
3766
  initCommand(args[1] ?? "");
3757
3767
  break;
3758
3768
  }
3769
+ case "traverse": {
3770
+ if (args.includes("--step") || args.includes("--auto-next") || args.includes("--autoplay")) {
3771
+ console.error("waygraph traverse: --step / --auto-next not supported (not a demo)");
3772
+ process.exit(1);
3773
+ }
3774
+ const rest = args.slice(1);
3775
+ let from;
3776
+ let data;
3777
+ let maxSteps;
3778
+ let maxVisits;
3779
+ let maxEdge;
3780
+ let headed = false;
3781
+ let baseUrl;
3782
+ let projectDir = process.cwd();
3783
+ for (let i = 0; i < rest.length; i++) {
3784
+ const a = rest[i];
3785
+ if (a === "--from" || a.startsWith("--from=")) {
3786
+ from = a.startsWith("--from=") ? a.slice(7) : rest[++i];
3787
+ continue;
3788
+ }
3789
+ if (a === "--data" || a.startsWith("--data=")) {
3790
+ data = a.startsWith("--data=") ? a.slice(7) : rest[++i];
3791
+ continue;
3792
+ }
3793
+ if (a === "--max-steps" || a.startsWith("--max-steps=")) {
3794
+ const v = a.startsWith("--max-steps=") ? a.slice(12) : rest[++i];
3795
+ maxSteps = Number(v);
3796
+ continue;
3797
+ }
3798
+ if (a === "--max-visits" || a.startsWith("--max-visits=")) {
3799
+ const v = a.startsWith("--max-visits=") ? a.slice(13) : rest[++i];
3800
+ maxVisits = Number(v);
3801
+ continue;
3802
+ }
3803
+ if (a === "--max-visits-per-edge" || a.startsWith("--max-visits-per-edge=")) {
3804
+ const v = a.startsWith("--max-visits-per-edge=")
3805
+ ? a.slice("--max-visits-per-edge=".length)
3806
+ : rest[++i];
3807
+ maxEdge = Number(v);
3808
+ continue;
3809
+ }
3810
+ if (a === "--non-headless" || a === "--headed") {
3811
+ headed = true;
3812
+ continue;
3813
+ }
3814
+ if (a === "--base-url" || a.startsWith("--base-url=")) {
3815
+ baseUrl = a.startsWith("--base-url=") ? a.slice(11) : rest[++i];
3816
+ continue;
3817
+ }
3818
+ if (a === "--help" || a === "-h") {
3819
+ console.log(`waygraph traverse [project] [flags]
3820
+
3821
+ Serial graph crawl (RFC Phase B). Walks unused legal edges until a leaf,
3822
+ budget kill, or first broken edge.
3823
+
3824
+ --from <Checkpoint> start checkpoint (optional)
3825
+ --data '{...}' Mem seed JSON
3826
+ --max-steps N default 50
3827
+ --max-visits N per-node visit cap (default 2)
3828
+ --max-visits-per-edge N default 1
3829
+ --non-headless show browser
3830
+ --base-url URL
3831
+
3832
+ PASS: [Reached Leaf Node[traverse-1] at=... steps=N]
3833
+ FAIL: [Broke at edge[traverse-1] block=... from=... to=...]
3834
+ `);
3835
+ process.exit(0);
3836
+ }
3837
+ if (!a.startsWith("-")) {
3838
+ projectDir = resolve(a);
3839
+ }
3840
+ }
3841
+ if (!existsSync(projectDir) || !statSync(projectDir).isDirectory()) {
3842
+ console.error(`waygraph traverse: no such directory: ${projectDir}`);
3843
+ process.exit(1);
3844
+ }
3845
+ const code = await runTraverse(projectDir, {
3846
+ ...(from ? { from } : {}),
3847
+ ...(data ? { data } : {}),
3848
+ ...(maxSteps !== undefined && Number.isFinite(maxSteps) ? { maxSteps } : {}),
3849
+ ...(maxVisits !== undefined && Number.isFinite(maxVisits)
3850
+ ? { maxVisitsPerNode: maxVisits }
3851
+ : {}),
3852
+ ...(maxEdge !== undefined && Number.isFinite(maxEdge)
3853
+ ? { maxVisitsPerEdge: maxEdge }
3854
+ : {}),
3855
+ headed,
3856
+ ...(baseUrl ? { baseURL: baseUrl } : {}),
3857
+ });
3858
+ process.exitCode = code;
3859
+ break;
3860
+ }
3861
+ case "agent-dive":
3862
+ case "init-agents": {
3863
+ // Playwright analogue: npx playwright init-agents --loop <provider>
3864
+ const rest = args.slice(1);
3865
+ let loop = "claude";
3866
+ let prompts = false;
3867
+ let projectDir = process.cwd();
3868
+ for (let i = 0; i < rest.length; i++) {
3869
+ const a = rest[i];
3870
+ if (a === "--prompts") {
3871
+ prompts = true;
3872
+ continue;
3873
+ }
3874
+ if (a === "--loop" || a.startsWith("--loop=")) {
3875
+ const v = a.startsWith("--loop=") ? a.slice("--loop=".length) : rest[++i];
3876
+ if (!v || !["claude", "opencode", "cursor", "vscode"].includes(v)) {
3877
+ console.error("waygraph agent-dive: --loop must be claude | opencode | cursor | vscode");
3878
+ process.exit(1);
3879
+ }
3880
+ loop = v;
3881
+ continue;
3882
+ }
3883
+ if (a === "--help" || a === "-h") {
3884
+ console.log(`waygraph agent-dive [--loop claude|opencode|cursor|vscode] [--prompts] [project]
3885
+
3886
+ Initialize coding-agent definitions for diving an app into waygraph Blocks
3887
+ (Playwright \`init-agents\` analogue).
3888
+
3889
+ --loop claude write .claude/agents/*.md (default)
3890
+ --loop opencode write .opencode/prompts/*.md
3891
+ --loop cursor write .cursor/rules/waygraph-*.mdc
3892
+ --loop vscode write .github/agents/*.md
3893
+ --prompts also copy docs/waygraph-agents/*.md
3894
+
3895
+ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
3896
+ `);
3897
+ process.exit(0);
3898
+ }
3899
+ if (!a.startsWith("-")) {
3900
+ projectDir = resolve(a);
3901
+ }
3902
+ }
3903
+ if (!existsSync(projectDir) || !statSync(projectDir).isDirectory()) {
3904
+ console.error(`waygraph agent-dive: no such directory: ${projectDir}`);
3905
+ process.exit(1);
3906
+ }
3907
+ runAgentDive({ loop, projectDir, prompts });
3908
+ break;
3909
+ }
3759
3910
  case "chain": {
3760
3911
  // Compat alias: chain auto A B -> auto --blocks A B; else -> run --blocks.
3761
3912
  const flags = parseRunFlags(args.slice(1));
@@ -0,0 +1,24 @@
1
+ export interface TraverseOptions {
2
+ baseURL?: string;
3
+ startUrl?: string;
4
+ /** Seed checkpoint label (optional - otherwise detect from page / nav). */
5
+ from?: string;
6
+ /** Global Mem seed JSON (or use WAYGRAPH_DATA / WAYGRAPH_AUTO_MEM). */
7
+ data?: string;
8
+ /** Max Block runs this traverse (default 50). */
9
+ maxSteps?: number;
10
+ /** Max times a checkpoint may be visited (default 2). */
11
+ maxVisitsPerNode?: number;
12
+ /** Max times an edge key (block::instanceId) may be taken (default 1). */
13
+ maxVisitsPerEdge?: number;
14
+ /** Wall-clock budget ms (default 15m). */
15
+ timeoutMs?: number;
16
+ /** Show browser (default headless). */
17
+ headed?: boolean;
18
+ /** Traverse id for greppable lines (default traverse-1). */
19
+ traverseId?: string;
20
+ }
21
+ /**
22
+ * Serial graph crawl. Prints greppable PASS/FAIL lines; non-zero exit on break.
23
+ */
24
+ export declare function runTraverse(projectDir: string, options?: TraverseOptions): Promise<number>;