waygraph 0.15.11 → 0.15.15

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 (53) hide show
  1. package/README.md +67 -26
  2. package/dist/auto-explore.d.ts +7 -6
  3. package/dist/auto-explore.js +43 -32
  4. package/dist/auto-session-ipc.d.ts +13 -0
  5. package/dist/auto-session-ipc.js +119 -7
  6. package/dist/auto-session.d.ts +16 -13
  7. package/dist/auto-session.js +72 -53
  8. package/dist/block-inject.d.ts +15 -0
  9. package/dist/block-inject.js +91 -0
  10. package/dist/browser.d.ts +15 -0
  11. package/dist/browser.js +29 -0
  12. package/dist/cli.js +335 -47
  13. package/dist/coverage-gap.d.ts +11 -0
  14. package/dist/coverage-gap.js +119 -0
  15. package/dist/graph.d.ts +1 -0
  16. package/dist/graph.js +99 -91
  17. package/dist/pilot-overlay.d.ts +7 -1
  18. package/dist/pilot-overlay.js +59 -41
  19. package/dist/pilot.d.ts +7 -2
  20. package/dist/pilot.js +34 -7
  21. package/dist/practices-check.d.ts +14 -0
  22. package/dist/practices-check.js +115 -0
  23. package/examples/saucedemo/.waygraph-auto/0b937f26.log +4 -0
  24. package/examples/saucedemo/.waygraph-auto/2b608ed3.log +0 -0
  25. package/examples/saucedemo/.waygraph-auto/2c13567b.log +0 -0
  26. package/examples/saucedemo/.waygraph-auto/2d9622a5.log +4 -0
  27. package/examples/saucedemo/.waygraph-auto/305f6e5b.log +4 -0
  28. package/examples/saucedemo/.waygraph-auto/418befbf.log +4 -0
  29. package/examples/saucedemo/.waygraph-auto/483bd763.log +4 -0
  30. package/examples/saucedemo/.waygraph-auto/48e3596a.log +0 -0
  31. package/examples/saucedemo/.waygraph-auto/4adfbe91.json +8 -0
  32. package/examples/saucedemo/.waygraph-auto/4adfbe91.log +0 -0
  33. package/examples/saucedemo/.waygraph-auto/4f0e33e7.log +4 -0
  34. package/examples/saucedemo/.waygraph-auto/5bd8fedf.log +4 -0
  35. package/examples/saucedemo/.waygraph-auto/5dde8331.log +4 -0
  36. package/examples/saucedemo/.waygraph-auto/68903903.log +0 -0
  37. package/examples/saucedemo/.waygraph-auto/78bbf43c.log +0 -0
  38. package/examples/saucedemo/.waygraph-auto/7efa646f.log +4 -0
  39. package/examples/saucedemo/.waygraph-auto/9cecb0c8.log +4 -0
  40. package/examples/saucedemo/.waygraph-auto/aae57d51.log +0 -0
  41. package/examples/saucedemo/.waygraph-auto/b61a08d8.log +0 -0
  42. package/examples/saucedemo/.waygraph-auto/c946f66c.log +0 -0
  43. package/examples/saucedemo/.waygraph-auto/cd0d5b50.log +4 -0
  44. package/examples/saucedemo/.waygraph-auto/cd5b203c.log +0 -0
  45. package/examples/saucedemo/.waygraph-auto/ea2df316.log +4 -0
  46. package/examples/saucedemo/.waygraph-auto/ee60ed24.log +0 -0
  47. package/examples/saucedemo/.waygraph-auto/f2b6cb1b.log +0 -0
  48. package/examples/saucedemo/.waygraph-auto/f392cdc8.log +0 -0
  49. package/package.json +2 -2
  50. package/templates/scaffold/package.json +1 -1
  51. package/templates/skills/waygraph-blind-pilot.skill.md +30 -15
  52. package/templates/skills/waygraph-convention.skill.md +11 -1
  53. package/templates/skills/waygraph-pilot.skill.md +71 -4
package/dist/cli.js CHANGED
@@ -21,8 +21,11 @@ import { spawn } from "node:child_process";
21
21
  import { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
22
22
  import { checkMap } from "./map-check.js";
23
23
  import { runAutoExplore } from "./auto-explore-run.js";
24
- import { pilotStart } from "./pilot.js";
25
- import { spawnDetachedSession, requestSession, runAttachLoop, runAutoServeCommand, } from "./auto-session-ipc.js";
24
+ import { pilotStart, pilotAttach } from "./pilot.js";
25
+ import { browserStart, listBrowserSessions, stopBrowserSession, stopAllBrowserSessions, } from "./browser.js";
26
+ import { resolveInjectRoots } from "./block-inject.js";
27
+ import { collectPracticeWarnings, practiceKindLabel } from "./practices-check.js";
28
+ import { spawnDetachedSession, requestSession, runAttachLoop, runAutoServeCommand, resolveSessionMeta, } from "./auto-session-ipc.js";
26
29
  import { runAgentDive, printSkillIndex, readSkillMarkdown, SKILL_FLAG_MAP, } from "./agent-dive.js";
27
30
  import { runTraverse } from "./traverse-run.js";
28
31
  import { parseMinEdgeCoverage } from "./traverse-coverage.js";
@@ -6680,7 +6683,8 @@ function initCommand(projectName) {
6680
6683
  console.log(" npx playwright install chromium");
6681
6684
  console.log(" npm test");
6682
6685
  console.log(" waygraph list # .flow.ts → export map");
6683
- console.log(" waygraph check # nav hygiene + orphan Blocks");
6686
+ console.log(" waygraph check # nav hygiene + inline selectors + bad practices + orphan Blocks");
6687
+ console.log(" waygraph typecheck # tsc --noEmit + bad-practice warnings (use --no-practices to skip)");
6684
6688
  console.log(" waygraph auto # interactive explore (headed panel)");
6685
6689
  console.log(" waygraph auto --cli # same menus in the terminal");
6686
6690
  console.log(" waygraph demo --blocks exampleFlow");
@@ -6688,7 +6692,17 @@ function initCommand(projectName) {
6688
6692
  console.log("");
6689
6693
  console.log(" Layout: see STRUCTURE.md (or https://deviate-dv8.github.io/waygraph/scaffold.html)");
6690
6694
  }
6691
- async function checkCommand(projectDir) {
6695
+ function printPracticeReport(projectDir, practiceWarnings) {
6696
+ if (practiceWarnings.length === 0) {
6697
+ console.log(`waygraph check: no bad-practice patterns under ${projectDir}`);
6698
+ return;
6699
+ }
6700
+ for (const w of practiceWarnings) {
6701
+ console.warn(`waygraph check: ${w.file} (${practiceKindLabel(w.kind)}) — ${w.detail}`);
6702
+ }
6703
+ console.log(`waygraph check: ${practiceWarnings.length} bad-practice warning${practiceWarnings.length === 1 ? "" : "s"}`);
6704
+ }
6705
+ async function checkCommand(projectDir, opts) {
6692
6706
  const files = discoverBlocks(projectDir);
6693
6707
  const navWarnings = [];
6694
6708
  const selWarnings = [];
@@ -6718,7 +6732,25 @@ async function checkCommand(projectDir) {
6718
6732
  }
6719
6733
  }
6720
6734
  }
6721
- return { navWarnings, selWarnings };
6735
+ const practiceWarnings = collectPracticeWarnings(projectDir, walkDir, {
6736
+ disabled: opts?.noPractices === true,
6737
+ });
6738
+ return { navWarnings, selWarnings, practiceWarnings };
6739
+ }
6740
+ async function runTypecheckCommand(projectDir, noPractices) {
6741
+ const { spawnSync } = await import("node:child_process");
6742
+ const tsc = spawnSync("npx", ["tsc", "--noEmit"], {
6743
+ cwd: projectDir,
6744
+ stdio: "inherit",
6745
+ shell: false,
6746
+ });
6747
+ if (tsc.status !== 0) {
6748
+ process.exit(tsc.status === null ? 1 : tsc.status);
6749
+ }
6750
+ if (noPractices)
6751
+ return;
6752
+ const practiceWarnings = collectPracticeWarnings(projectDir, walkDir);
6753
+ printPracticeReport(projectDir, practiceWarnings);
6722
6754
  }
6723
6755
  function takeFlagValue(argv, i, a, flag) {
6724
6756
  if (a.startsWith(flag + "=")) {
@@ -6803,6 +6835,29 @@ function parseRunFlags(argv) {
6803
6835
  else if (a === "--non-headless") {
6804
6836
  out.nonHeadless = true;
6805
6837
  }
6838
+ else if (a === "--headless") {
6839
+ out.headlessBrowser = true;
6840
+ }
6841
+ else if (a === "--json") {
6842
+ out.json = true;
6843
+ }
6844
+ else if (a === "--no-practices") {
6845
+ out.noPractices = true;
6846
+ }
6847
+ else if (a === "--blank") {
6848
+ out.blank = true;
6849
+ }
6850
+ else if (a === "--goto" || a.startsWith("--goto=")) {
6851
+ const t = takeFlagValue(argv, i, a, "--goto");
6852
+ out.goto = t.value;
6853
+ i = t.nextI;
6854
+ }
6855
+ else if (a === "--inject" || a.startsWith("--inject=")) {
6856
+ const t = takeFlagValue(argv, i, a, "--inject");
6857
+ out.inject ??= [];
6858
+ out.inject.push(t.value);
6859
+ i = t.nextI;
6860
+ }
6806
6861
  else if (a === "--cli") {
6807
6862
  out.cli = true;
6808
6863
  }
@@ -7131,6 +7186,38 @@ async function expandSpecFlowFiles(projectDir, spec) {
7131
7186
  }
7132
7187
  const args = process.argv.slice(2);
7133
7188
  const command = args[0];
7189
+ function printBrowserUsage() {
7190
+ console.log(`waygraph browser — persistent Playwright sessions (headful by default)
7191
+
7192
+ waygraph browser start [--inject preset|path] [--goto <url>] [--blank] [--headless] [--cli] [project]
7193
+ Open a new session (about:blank by default)
7194
+ waygraph browser sessions [--json] [project]
7195
+ List live sessions for a project
7196
+ waygraph browser stop <sessionId|--all> [project]
7197
+ waygraph browser attach <sessionId> Terminal picker on a live session
7198
+
7199
+ waygraph browser status <sessionId> Current menu (no side effects)
7200
+ waygraph browser send <sessionId> "<pick>"
7201
+ waygraph browser reach <sessionId> <Checkpoint>
7202
+ waygraph browser dom|trace|console|storage|highlight <sessionId> …
7203
+ waygraph browser click|type|press|goto|upload|reload|resync <sessionId> …
7204
+
7205
+ auto send|status|… and pilot send|status|… use the same session ids.`);
7206
+ }
7207
+ function printBrowserSessionsList(sessions, projectDir) {
7208
+ if (sessions.length === 0) {
7209
+ console.log(`No live browser sessions for ${projectDir}.`);
7210
+ console.log(" waygraph browser start");
7211
+ return;
7212
+ }
7213
+ console.log(`Live browser sessions (${projectDir}):`);
7214
+ for (const s of sessions) {
7215
+ const mode = s.headless ? "headless" : "headful";
7216
+ console.log(` ${s.sessionId} ${mode} pid ${s.pid}`);
7217
+ console.log(` waygraph browser attach ${s.sessionId}`);
7218
+ console.log(` waygraph browser stop ${s.sessionId}`);
7219
+ }
7220
+ }
7134
7221
  function usage() {
7135
7222
  console.log(`waygraph -- graph project tool + engine CLI
7136
7223
 
@@ -7209,16 +7296,23 @@ Primary (less is more):
7209
7296
  label,tone?,size?,weight?}], todos[], todoIndex?,
7210
7297
  todoTitle?, holdMs? (0=until next), clear:true.
7211
7298
  Missing selectors listed in response, not fatal.
7212
- waygraph pilot start Bootstrap for an agent: starts a --detach session
7213
- (same as auto --cli --detach) AND reads back the
7214
- whole project's Block graph (same as waygraph
7215
- graph) in one call - {sessionId, socketPath,
7216
- headless, graph, snapshot}. Drive it afterward with
7217
- auto send/status/dom/trace <sessionId> - pilot start
7218
- does not resolve, narrate, or act on anything itself;
7219
- planning a multi-step request is the agent's job.
7220
- --non-headless Real visible browser (same flag as auto/demo/run)
7221
- --base-url / --data Same as auto/demo/run
7299
+ waygraph browser Show browser subcommands
7300
+ waygraph browser start Open a new session (headful by default, about:blank)
7301
+ --inject preset|path Merge an external Block library (e.g. saucedemo)
7302
+ --goto <url> Navigate on start (disables blank page)
7303
+ --blank Force about:blank on start (default)
7304
+ --headless Headless session (default is visible browser)
7305
+ --cli After start, open the terminal picker (auto attach)
7306
+ waygraph browser sessions [project] List live browser sessions (attach/status/stop by id)
7307
+ waygraph browser send|status|attach|… Same session control as auto (see auto send/status/…)
7308
+ waygraph pilot start Bootstrap: browser session + whole-project graph +
7309
+ starting snapshot in one call. Does not plan or act.
7310
+ --inject / --goto / --headless / --cli / --base-url Same flags as browser start
7311
+ waygraph pilot sessions [project] List live sessions (prefer attach over a new start)
7312
+ waygraph pilot attach <sessionId> Graph + snapshot for an existing session (no new browser)
7313
+ waygraph pilot send|status|highlight|… Same session control as browser/auto
7314
+ waygraph auto Interactive explore (picker) — NOT the persistent browser
7315
+ layer; use browser/pilot for agent-driven sessions.
7222
7316
  waygraph demo [--blocks <flow|file|spec>] Watch with step overlay (QA path)
7223
7317
  --data '{...}' Mem seed JSON (or inline flow({...}))
7224
7318
  --auto-next Auto-advance steps (alias: --autoplay)
@@ -7244,7 +7338,8 @@ Primary (less is more):
7244
7338
  --ff-disabled Same as demo (dispute: expand FF)
7245
7339
 
7246
7340
  Also:
7247
- waygraph list | nav | validate | check | graph | init <name>
7341
+ waygraph list | nav | validate | check | typecheck | graph | init <name>
7342
+ check/typecheck --no-practices Skip bad-practice warnings
7248
7343
  waygraph map [project] Waygraph Map convention enforcement: every static
7249
7344
  Nav/Page url must verbatim-match its src/map/ folder
7250
7345
  path ((group) segments excluded) - exits 1 on a
@@ -7668,15 +7763,202 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
7668
7763
  break;
7669
7764
  }
7670
7765
  case "graph":
7671
- case "auto": {
7766
+ case "auto":
7767
+ case "browser":
7768
+ case "pilot": {
7769
+ if (command === "browser") {
7770
+ const sub = args[1];
7771
+ if (sub === "sessions") {
7772
+ const rest = args.slice(2);
7773
+ const json = rest.includes("--json");
7774
+ const positional = rest.filter((a) => a !== "--json");
7775
+ const proj = resolve(positional[0] ?? process.cwd());
7776
+ const sessions = listBrowserSessions(proj);
7777
+ if (json) {
7778
+ console.log(JSON.stringify(sessions, null, 2));
7779
+ }
7780
+ else {
7781
+ printBrowserSessionsList(sessions, proj);
7782
+ }
7783
+ break;
7784
+ }
7785
+ if (sub === "stop") {
7786
+ const proj = resolve(args[3] ?? process.cwd());
7787
+ const target = args[2];
7788
+ if (!target) {
7789
+ console.error("waygraph browser stop: usage: waygraph browser stop <sessionId|--all> [project]");
7790
+ process.exit(1);
7791
+ }
7792
+ if (target === "--all") {
7793
+ const n = stopAllBrowserSessions(proj);
7794
+ console.log(JSON.stringify({ stopped: n, projectDir: proj }));
7795
+ break;
7796
+ }
7797
+ if (!stopBrowserSession(proj, target)) {
7798
+ console.error(`waygraph browser stop: no such session "${target}"`);
7799
+ process.exit(1);
7800
+ }
7801
+ console.log(JSON.stringify({ stopped: target }));
7802
+ break;
7803
+ }
7804
+ if (!sub) {
7805
+ printBrowserUsage();
7806
+ break;
7807
+ }
7808
+ if (sub === "start" || sub.startsWith("-")) {
7809
+ const flagArgs = sub === "start" ? args.slice(2) : args.slice(1);
7810
+ const flags = parseRunFlags(flagArgs);
7811
+ applyRunFlags(flags);
7812
+ const proj = resolve(flags.positionals[0] ?? process.cwd());
7813
+ if (!existsSync(proj)) {
7814
+ console.error(`waygraph browser: no such directory: ${proj}`);
7815
+ process.exit(1);
7816
+ }
7817
+ const baseURL = flags.baseUrl ?? process.env.WAYGRAPH_BASE_URL ?? resolveBaseUrl(proj);
7818
+ try {
7819
+ const inject = flags.inject?.length ? resolveInjectRoots(flags.inject, proj) : undefined;
7820
+ const result = await browserStart({
7821
+ projectDir: proj,
7822
+ ...(inject?.length ? { inject } : {}),
7823
+ ...(baseURL ? { baseURL } : {}),
7824
+ ...(flags.goto
7825
+ ? { startUrl: flags.goto, skipInitialNavigation: false }
7826
+ : { skipInitialNavigation: flags.blank !== false }),
7827
+ headless: flags.headlessBrowser === true,
7828
+ });
7829
+ console.log(JSON.stringify(result));
7830
+ if (!result.headless) {
7831
+ console.error(`Session ${result.sessionId} started. List all: waygraph browser sessions`);
7832
+ }
7833
+ if (flags.cli) {
7834
+ await runAttachLoop(proj, result.sessionId);
7835
+ }
7836
+ }
7837
+ catch (err) {
7838
+ console.error(`waygraph browser start: ${err instanceof Error ? err.message : String(err)}`);
7839
+ process.exit(1);
7840
+ }
7841
+ break;
7842
+ }
7843
+ if (sub !== "send" &&
7844
+ sub !== "status" &&
7845
+ sub !== "attach" &&
7846
+ sub !== "dom" &&
7847
+ sub !== "trace" &&
7848
+ sub !== "console" &&
7849
+ sub !== "storage" &&
7850
+ sub !== "upload" &&
7851
+ sub !== "click" &&
7852
+ sub !== "type" &&
7853
+ sub !== "press" &&
7854
+ sub !== "goto" &&
7855
+ sub !== "reload" &&
7856
+ sub !== "reach" &&
7857
+ sub !== "resync" &&
7858
+ sub !== "highlight") {
7859
+ console.error("waygraph browser: unknown subcommand — run `waygraph browser` for usage");
7860
+ printBrowserUsage();
7861
+ process.exit(1);
7862
+ }
7863
+ }
7864
+ if (command === "pilot") {
7865
+ if (args[1] === "sessions") {
7866
+ const rest = args.slice(2);
7867
+ const json = rest.includes("--json");
7868
+ const positional = rest.filter((a) => a !== "--json");
7869
+ const proj = resolve(positional[0] ?? process.cwd());
7870
+ const sessions = listBrowserSessions(proj);
7871
+ if (json) {
7872
+ console.log(JSON.stringify(sessions, null, 2));
7873
+ }
7874
+ else {
7875
+ printBrowserSessionsList(sessions, proj);
7876
+ }
7877
+ break;
7878
+ }
7879
+ if (args[1] === "attach") {
7880
+ const sessionId = args[2];
7881
+ if (!sessionId) {
7882
+ console.error("waygraph pilot attach: usage: waygraph pilot attach <sessionId> [--inject …] [project]");
7883
+ process.exit(1);
7884
+ }
7885
+ const flags = parseRunFlags(args.slice(3));
7886
+ const proj = resolve(flags.positionals[0] ?? process.cwd());
7887
+ try {
7888
+ const inject = flags.inject?.length ? resolveInjectRoots(flags.inject, proj) : undefined;
7889
+ const result = await pilotAttach(proj, sessionId, inject);
7890
+ console.log(JSON.stringify(result));
7891
+ }
7892
+ catch (err) {
7893
+ console.error(`waygraph pilot attach: ${err instanceof Error ? err.message : String(err)}`);
7894
+ process.exit(1);
7895
+ }
7896
+ break;
7897
+ }
7898
+ if (args[1] === "start" || !args[1] || args[1].startsWith("-")) {
7899
+ const flagArgs = args[1] === "start" ? args.slice(2) : args.slice(1);
7900
+ const flags = parseRunFlags(flagArgs);
7901
+ applyRunFlags(flags);
7902
+ const proj = resolve(flags.positionals[0] ?? process.cwd());
7903
+ const baseURL = flags.baseUrl ?? process.env.WAYGRAPH_BASE_URL ?? resolveBaseUrl(proj);
7904
+ try {
7905
+ const result = await pilotStart({
7906
+ projectDir: proj,
7907
+ ...(flags.inject?.length ? { injectTokens: flags.inject } : {}),
7908
+ ...(baseURL ? { baseURL } : {}),
7909
+ ...(flags.goto
7910
+ ? { startUrl: flags.goto, skipInitialNavigation: false }
7911
+ : flags.blank === true
7912
+ ? { skipInitialNavigation: true }
7913
+ : {}),
7914
+ headless: flags.headlessBrowser === true,
7915
+ });
7916
+ console.log(JSON.stringify(result));
7917
+ if (!result.headless) {
7918
+ console.error(`Chromium window opened (session ${result.sessionId}). ` +
7919
+ `Terminal menu: waygraph pilot attach ${result.sessionId}`);
7920
+ }
7921
+ if (flags.cli) {
7922
+ await runAttachLoop(proj, result.sessionId);
7923
+ }
7924
+ }
7925
+ catch (err) {
7926
+ console.error(`waygraph pilot start: ${err instanceof Error ? err.message : String(err)}`);
7927
+ process.exit(1);
7928
+ }
7929
+ break;
7930
+ }
7931
+ if (args[1] !== "send" &&
7932
+ args[1] !== "status" &&
7933
+ args[1] !== "dom" &&
7934
+ args[1] !== "trace" &&
7935
+ args[1] !== "console" &&
7936
+ args[1] !== "storage" &&
7937
+ args[1] !== "upload" &&
7938
+ args[1] !== "click" &&
7939
+ args[1] !== "type" &&
7940
+ args[1] !== "press" &&
7941
+ args[1] !== "goto" &&
7942
+ args[1] !== "reload" &&
7943
+ args[1] !== "reach" &&
7944
+ args[1] !== "resync" &&
7945
+ args[1] !== "highlight") {
7946
+ console.error("waygraph pilot: usage:\n" +
7947
+ " waygraph pilot start [--inject …] [--goto <url>] [--headless] [--cli] [--base-url <url>] [project]\n" +
7948
+ " waygraph pilot sessions [project] # list live browser sessions\n" +
7949
+ " waygraph pilot attach <sessionId> # graph + snapshot for an existing session\n" +
7950
+ " waygraph pilot send|status|attach|highlight|… <sessionId> … (controls browser — same as browser/auto)");
7951
+ process.exit(1);
7952
+ }
7953
+ }
7672
7954
  // auto send|status|attach <sessionId> - session control against a
7673
7955
  // --detach'd background session. Intercepted before the normal
7674
7956
  // project-directory resolution below, same pattern `try demo|auto|
7675
7957
  // auto:cli` already uses for a sub-verb positional.
7676
- if (command === "auto" &&
7958
+ if ((command === "auto" || command === "browser" || command === "pilot") &&
7677
7959
  (args[1] === "send" ||
7678
7960
  args[1] === "status" ||
7679
- args[1] === "attach" ||
7961
+ (args[1] === "attach" && command !== "pilot") ||
7680
7962
  args[1] === "dom" ||
7681
7963
  args[1] === "trace" ||
7682
7964
  args[1] === "console" ||
@@ -7693,10 +7975,17 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
7693
7975
  const sub = args[1];
7694
7976
  const sessionId = args[2];
7695
7977
  if (!sessionId) {
7696
- console.error(`waygraph auto ${sub}: missing <sessionId>`);
7978
+ console.error(`waygraph ${command} ${sub}: missing <sessionId>`);
7697
7979
  process.exit(1);
7698
7980
  }
7699
- const proj = resolve(process.cwd());
7981
+ const projHint = resolve(process.cwd());
7982
+ const meta = resolveSessionMeta(sessionId, projHint);
7983
+ if (!meta) {
7984
+ console.error(`waygraph ${command} ${sub}: no such session "${sessionId}" — ` +
7985
+ "wrong directory? run `waygraph pilot sessions` from the project that started it");
7986
+ process.exit(1);
7987
+ }
7988
+ const proj = meta.projectDir;
7700
7989
  if (sub === "attach") {
7701
7990
  await runAttachLoop(proj, sessionId);
7702
7991
  break;
@@ -8026,29 +8315,6 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
8026
8315
  await runAutoExplore(proj, baseURL ? { cli: cliPicker, baseURL } : { cli: cliPicker });
8027
8316
  break;
8028
8317
  }
8029
- case "pilot": {
8030
- if (args[1] !== "start") {
8031
- console.error('waygraph pilot: usage: waygraph pilot start [--non-headless] [--base-url <url>] [--data <json>]');
8032
- process.exit(1);
8033
- }
8034
- const flags = parseRunFlags(args.slice(2));
8035
- applyRunFlags(flags);
8036
- const proj = resolve(process.cwd());
8037
- const baseURL = flags.baseUrl ?? process.env.WAYGRAPH_BASE_URL ?? resolveBaseUrl(proj);
8038
- try {
8039
- const result = await pilotStart({
8040
- projectDir: proj,
8041
- ...(baseURL ? { baseURL } : {}),
8042
- ...(flags.nonHeadless ? { headless: false } : {}),
8043
- });
8044
- console.log(JSON.stringify(result));
8045
- }
8046
- catch (err) {
8047
- console.error(`waygraph pilot start: ${err instanceof Error ? err.message : String(err)}`);
8048
- process.exit(1);
8049
- }
8050
- break;
8051
- }
8052
8318
  // Hidden: the detached server's own entry point, spawned by
8053
8319
  // `spawnDetachedSession` (auto --cli --detach). Not documented in
8054
8320
  // usage() - not meant to be invoked directly by a person.
@@ -8062,10 +8328,22 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
8062
8328
  process.exit(1);
8063
8329
  }
8064
8330
  const baseURL = flags.baseUrl ?? process.env.WAYGRAPH_BASE_URL ?? resolveBaseUrl(proj);
8331
+ const inject = flags.inject?.length ? resolveInjectRoots(flags.inject, proj) : undefined;
8332
+ let headless;
8333
+ if (flags.headlessBrowser)
8334
+ headless = true;
8335
+ else if (flags.nonHeadless)
8336
+ headless = false;
8065
8337
  await runAutoServeCommand({
8066
8338
  projectDir: proj,
8067
8339
  ...(baseURL ? { baseURL } : {}),
8068
- ...(flags.nonHeadless ? { headless: false } : {}),
8340
+ ...(flags.goto
8341
+ ? { startUrl: flags.goto, skipInitialNavigation: false }
8342
+ : flags.blank === true
8343
+ ? { skipInitialNavigation: true }
8344
+ : {}),
8345
+ ...(headless !== undefined ? { headless } : {}),
8346
+ ...(inject?.length ? { inject } : {}),
8069
8347
  }, sessionId);
8070
8348
  break;
8071
8349
  }
@@ -8096,9 +8374,16 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
8096
8374
  }
8097
8375
  break;
8098
8376
  }
8377
+ case "typecheck": {
8378
+ const flags = parseRunFlags(args.slice(1));
8379
+ const proj = resolve(flags.positionals[0] ?? process.cwd());
8380
+ await runTypecheckCommand(proj, flags.noPractices === true);
8381
+ break;
8382
+ }
8099
8383
  case "check": {
8100
- const proj = resolve(args[1] ?? process.cwd());
8101
- const { navWarnings, selWarnings } = await checkCommand(proj);
8384
+ const flags = parseRunFlags(args.slice(1));
8385
+ const proj = resolve(flags.positionals[0] ?? process.cwd());
8386
+ const { navWarnings, selWarnings, practiceWarnings } = await checkCommand(proj, flags.noPractices ? { noPractices: true } : undefined);
8102
8387
  if (navWarnings.length === 0) {
8103
8388
  console.log(`waygraph check: no navigation found outside NavBlocks under ${proj}`);
8104
8389
  }
@@ -8119,6 +8404,9 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
8119
8404
  }
8120
8405
  console.log(`waygraph check: ${selWarnings.length} inline-selector warning${selWarnings.length === 1 ? "" : "s"}`);
8121
8406
  }
8407
+ if (!flags.noPractices) {
8408
+ printPracticeReport(proj, practiceWarnings);
8409
+ }
8122
8410
  const orphans = await findOrphanBlocks(proj);
8123
8411
  printOrphanReport(proj, orphans);
8124
8412
  // Warnings only - never fail exit code. Orphans are reported for
@@ -0,0 +1,11 @@
1
+ import type { BlockEntry } from "./auto-explore.js";
2
+ /** Collect selector strings from NavClick blocks and Block/*Sel source files. */
3
+ export declare function collectKnownInteractionSelectors(projectDir: string, library: {
4
+ navBlocks: BlockEntry[];
5
+ }, inject?: string[]): string[];
6
+ export interface UnmappedInteractionPayload {
7
+ knownPathnames: string[];
8
+ knownSelectors: string[];
9
+ }
10
+ /** Page-side: warn once per unmapped same-origin href or visible button. */
11
+ export declare function unmappedInteractionsPageScript(payload: UnmappedInteractionPayload): void;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Runtime coverage-gap helpers: detect live page links/buttons with no Block
3
+ * selector or NavBlock URL in the loaded library (surfaced via console.warn →
4
+ * `auto console`, same channel as unmapped href detection).
5
+ */
6
+ import { readFileSync, readdirSync, statSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { exploreRoots } from "./block-inject.js";
9
+ const SELECTOR_LITERAL = /(?:locator|visible|click|fill|press|selector|getByRole)\(\s*['"`]([^'"`]+)['"`]/g;
10
+ const HASH_ID = /#([a-zA-Z][\w-]*)/g;
11
+ const DATA_TEST = /\[data-test(?:-id)?(?:=["'`][^"'`]+["'`])?\]/g;
12
+ function walkBlockAndSelFiles(dir, acc = []) {
13
+ for (const name of readdirSync(dir)) {
14
+ const p = join(dir, name);
15
+ try {
16
+ const st = statSync(p);
17
+ if (st.isDirectory()) {
18
+ if (name === "node_modules" || name === "dist" || name.startsWith("."))
19
+ continue;
20
+ walkBlockAndSelFiles(p, acc);
21
+ }
22
+ else if (/\.(block|sel)\.ts$/.test(name)) {
23
+ acc.push(p);
24
+ }
25
+ }
26
+ catch {
27
+ /* ignore */
28
+ }
29
+ }
30
+ return acc;
31
+ }
32
+ /** Collect selector strings from NavClick blocks and Block/*Sel source files. */
33
+ export function collectKnownInteractionSelectors(projectDir, library, inject) {
34
+ const out = new Set();
35
+ for (const entry of library.navBlocks) {
36
+ const click = entry.block
37
+ .__waygraphNavClick;
38
+ if (typeof click === "string")
39
+ out.add(click);
40
+ }
41
+ for (const root of exploreRoots(projectDir, inject)) {
42
+ for (const file of walkBlockAndSelFiles(root)) {
43
+ const src = readFileSync(file, "utf-8");
44
+ for (const re of [SELECTOR_LITERAL, HASH_ID, DATA_TEST]) {
45
+ re.lastIndex = 0;
46
+ let m;
47
+ while ((m = re.exec(src))) {
48
+ const sel = m[0].startsWith("#") || m[0].startsWith("[") ? m[0] : (m[1] ?? m[0]);
49
+ if (sel.startsWith("#") || sel.includes("data-test") || sel.includes("[")) {
50
+ out.add(sel.startsWith("#") || sel.startsWith("[") ? sel : `#${sel}`);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ return Array.from(out);
57
+ }
58
+ /** Page-side: warn once per unmapped same-origin href or visible button. */
59
+ export function unmappedInteractionsPageScript(payload) {
60
+ const w = window;
61
+ w.__wgWarnedPaths ??= new Set();
62
+ w.__wgWarnedButtons ??= new Set();
63
+ const matchesKnown = (el, selectors) => {
64
+ for (const sel of selectors) {
65
+ try {
66
+ if (el.matches(sel))
67
+ return true;
68
+ if (document.querySelector(sel) === el)
69
+ return true;
70
+ }
71
+ catch {
72
+ /* invalid selector in library — skip */
73
+ }
74
+ }
75
+ return false;
76
+ };
77
+ const seenPaths = new Set();
78
+ for (const a of Array.from(document.querySelectorAll("a[href]"))) {
79
+ let url;
80
+ try {
81
+ url = new URL(a.getAttribute("href") || "", location.href);
82
+ }
83
+ catch {
84
+ continue;
85
+ }
86
+ if (url.origin !== location.origin)
87
+ continue;
88
+ const path = url.pathname;
89
+ if (seenPaths.has(path) || payload.knownPathnames.includes(path) || w.__wgWarnedPaths.has(path)) {
90
+ continue;
91
+ }
92
+ seenPaths.add(path);
93
+ w.__wgWarnedPaths.add(path);
94
+ console.warn(`[waygraph] unmapped nav link on this page: ${path} - no NavBlock covers this URL; add defineNavBlock`);
95
+ }
96
+ const buttonLike = Array.from(document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]')).filter((el) => {
97
+ const style = window.getComputedStyle(el);
98
+ return style.visibility !== "hidden" && style.display !== "none";
99
+ });
100
+ for (const el of buttonLike) {
101
+ const id = el.id;
102
+ const dt = el.getAttribute("data-test") ?? el.getAttribute("data-testid");
103
+ const name = el.getAttribute("name");
104
+ const label = id
105
+ ? `#${id}`
106
+ : dt
107
+ ? `[data-test="${dt}"]`
108
+ : name
109
+ ? `[name="${name}"]`
110
+ : el.textContent?.trim().slice(0, 40) || el.tagName.toLowerCase();
111
+ const key = `${el.tagName}:${label}`;
112
+ if (w.__wgWarnedButtons.has(key))
113
+ continue;
114
+ if (matchesKnown(el, payload.knownSelectors))
115
+ continue;
116
+ w.__wgWarnedButtons.add(key);
117
+ console.warn(`[waygraph] unmapped button on this page: ${label} - no Method/NavClick Block selector matches; consider defineMethodBlock or defineNavClickBlock`);
118
+ }
119
+ }
package/dist/graph.d.ts CHANGED
@@ -88,4 +88,5 @@ export declare function findBlockPathDetailed(graph: WaygraphGraph, fromTag: str
88
88
  */
89
89
  export declare function discoverGraph(projectDir: string, opts?: {
90
90
  blocksSelect?: BlocksSelect;
91
+ inject?: string[];
91
92
  }): Promise<WaygraphGraph>;