flowfig 0.6.0 → 0.8.0

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 (56) hide show
  1. package/README.md +28 -8
  2. package/dist/browser.d.ts +1 -3
  3. package/dist/browser.js +67 -59
  4. package/dist/check.d.ts +5 -3
  5. package/dist/check.js +24 -17
  6. package/dist/cli.js +43 -49
  7. package/dist/code.d.ts +26 -0
  8. package/dist/code.js +568 -0
  9. package/dist/diff.js +0 -4
  10. package/dist/draw.d.ts +1 -3
  11. package/dist/draw.js +3 -7
  12. package/dist/edges.d.ts +7 -0
  13. package/dist/edges.js +510 -0
  14. package/dist/geometry.d.ts +6 -8
  15. package/dist/geometry.js +75 -82
  16. package/dist/gif.d.ts +1 -2
  17. package/dist/gif.js +115 -42
  18. package/dist/guide.d.ts +1 -4
  19. package/dist/guide.js +10 -11
  20. package/dist/imports.d.ts +11 -0
  21. package/dist/imports.js +235 -0
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +20 -78
  24. package/dist/init.d.ts +3 -13
  25. package/dist/init.js +13 -18
  26. package/dist/load.d.ts +1 -3
  27. package/dist/load.js +2 -5
  28. package/dist/mcp.d.ts +1 -2
  29. package/dist/mcp.js +15 -15
  30. package/dist/model.d.ts +57 -109
  31. package/dist/model.js +20 -89
  32. package/dist/open.d.ts +2 -5
  33. package/dist/open.js +6 -11
  34. package/dist/page.d.ts +0 -1
  35. package/dist/page.js +1 -3
  36. package/dist/picker.d.ts +2 -4
  37. package/dist/picker.js +11 -14
  38. package/dist/png.js +9 -10
  39. package/dist/rail.d.ts +1 -3
  40. package/dist/rail.js +2 -16
  41. package/dist/scene.d.ts +8 -8
  42. package/dist/source.d.ts +1 -2
  43. package/dist/source.js +5 -5
  44. package/dist/svg.d.ts +5 -8
  45. package/dist/svg.js +50 -112
  46. package/dist/text.d.ts +0 -2
  47. package/dist/text.js +1 -4
  48. package/dist/type-index.d.ts +55 -0
  49. package/dist/type-index.js +383 -0
  50. package/dist/types.d.ts +32 -0
  51. package/dist/types.js +733 -0
  52. package/dist/verify.d.ts +26 -4
  53. package/dist/verify.js +68 -26
  54. package/dist/version.d.ts +1 -2
  55. package/dist/version.js +2 -2
  56. package/package.json +4 -2
package/dist/verify.d.ts CHANGED
@@ -1,7 +1,29 @@
1
- import type { FlowProps } from './model.ts';
1
+ import { type FlowProps } from './model.ts';
2
2
  import type { Finding } from './scene.ts';
3
+ import { type Read } from './code.ts';
3
4
  export { parseSource, headingSlug, owners, links, type Link } from './source.ts';
4
- /** The links against the files under `root`. A path outside the root is a missing file: the figure names the repo, not the disk. */
5
- export declare function verify(fig: FlowProps, { root }?: {
5
+ export type Coverage = {
6
+ boxes: number;
7
+ boxesDefined: number;
8
+ found: number;
9
+ notFound: number;
10
+ unsure: number;
11
+ notChecked: number;
12
+ unsureEdges: {
13
+ id: string;
14
+ reason: string;
15
+ }[];
16
+ };
17
+ export declare const coverageLine: (figure: string, c: Coverage) => string;
18
+ export declare const unsureLines: (c: Coverage) => string[];
19
+ /** The links against the files under `root`. A path outside `root` counts as a missing file. */
20
+ export declare function verifyReport(fig: FlowProps, { root, read }?: {
6
21
  root?: string;
7
- }): Finding[];
22
+ read?: Read;
23
+ }): {
24
+ findings: Finding[];
25
+ coverage: Coverage;
26
+ };
27
+ export declare const verify: (fig: FlowProps, opts?: {
28
+ root?: string;
29
+ }) => Finding[];
package/dist/verify.js CHANGED
@@ -1,43 +1,85 @@
1
- // `flowfig verify`: checks that the code a link names is still there. The link parsing lives in source.ts, which has no Node import.
2
- import { readFileSync, statSync } from 'node:fs';
3
- import { isAbsolute, relative, resolve, sep } from 'node:path';
1
+ import { resolve } from 'node:path';
2
+ import { edgeId, nodes, toBeat } from './model.js';
4
3
  import { headingSlug, links } from './source.js';
4
+ import { codeFile, isDefined, outside, readFile } from './code.js';
5
+ import { edgeResult } from './edges.js';
5
6
  export { parseSource, headingSlug, owners, links } from './source.js';
6
7
  const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
- /** The file text, or null for a missing path, a folder or an unreadable file. */
8
- function read(full) {
9
- try {
10
- return statSync(full).isFile() ? readFileSync(full, 'utf8') : null;
11
- }
12
- catch {
13
- return null;
14
- }
15
- }
16
- /** True if a heading line of the Markdown text has this slug. */
17
8
  const hasHeading = (text, slug) => text.split('\n').some((line) => {
18
9
  const m = /^#{1,6}\s+(.*?)\s*#*\s*$/.exec(line);
19
10
  return m != null && headingSlug(m[1]) === slug;
20
11
  });
21
- /** The links against the files under `root`. A path outside the root is a missing file: the figure names the repo, not the disk. */
22
- export function verify(fig, { root = process.cwd() } = {}) {
23
- const out = [];
12
+ const emptyCoverage = () => ({ boxes: 0, boxesDefined: 0, found: 0, notFound: 0, unsure: 0, notChecked: 0, unsureEdges: [] });
13
+ export const coverageLine = (figure, c) => `${figure}: ${c.boxesDefined} of ${c.boxes} boxes defined` +
14
+ (c.found + c.notFound + c.unsure + c.notChecked
15
+ ? `; edges: ${c.found} found, ${c.notFound} not found, ${c.unsure} unsure, ${c.notChecked} not checked`
16
+ : '');
17
+ export const unsureLines = (c) => c.unsureEdges.map((e) => `unsure edge "${e.id}": ${e.reason}`);
18
+ /** The links against the files under `root`. A path outside `root` counts as a missing file. */
19
+ export function verifyReport(fig, { root = process.cwd(), read = readFile } = {}) {
20
+ const findings = [];
21
+ const coverage = emptyCoverage();
24
22
  const all = links(fig);
25
23
  if (!all.length)
26
- return [{ rule: 'no-source', severity: 'warning', ids: [], message: 'no box, edge or hop has a source' }];
24
+ return {
25
+ findings: [{ rule: 'no-source', severity: 'warning', ids: [], message: 'no box, edge or hop has a source' }],
26
+ coverage,
27
+ };
27
28
  const files = new Map();
29
+ const parsed = new Map();
28
30
  for (const l of all) {
29
31
  const full = resolve(root, l.path);
30
- const rel = relative(root, full);
31
- const outside = isAbsolute(l.path) || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
32
32
  if (!files.has(full))
33
- files.set(full, outside ? null : read(full));
33
+ files.set(full, outside(root, l.path) ? null : read(full));
34
34
  const text = files.get(full);
35
+ const before = findings.length;
36
+ const fail = (rule, what) => findings.push({ rule, severity: 'error', ids: [], message: `${l.owner} -> ${l.source}: ${what}` });
35
37
  if (text == null)
36
- out.push({ rule: 'missing-file', severity: 'error', ids: [], message: `${l.owner} -> ${l.source}: file not found` });
37
- else if (l.symbol &&
38
- !new RegExp(`(^|[^\\w$])${escape(l.symbol)}(?![\\w$])`).test(text) &&
39
- !(/\.(md|markdown)$/i.test(l.path) && hasHeading(text, l.symbol)))
40
- out.push({ rule: 'missing-symbol', severity: 'error', ids: [], message: `${l.owner} -> ${l.source}: symbol not found` });
38
+ fail('missing-file', 'file not found');
39
+ else if (l.symbol) {
40
+ const code = codeFile(root, l.path, read, parsed);
41
+ if (code) {
42
+ if (!isDefined(code, l.symbol))
43
+ fail('missing-symbol', 'symbol not defined');
44
+ }
45
+ else if (!new RegExp(`(^|[^\\w$])${escape(l.symbol)}(?![\\w$])`).test(text) &&
46
+ !(/\.(md|markdown)$/i.test(l.path) && hasHeading(text, l.symbol)))
47
+ fail('missing-symbol', 'symbol not found');
48
+ }
49
+ if (l.owner.startsWith('box "')) {
50
+ coverage.boxes++;
51
+ if (findings.length === before)
52
+ coverage.boxesDefined++;
53
+ }
41
54
  }
42
- return out;
55
+ const source = new Map(nodes(fig.layout).map((n) => [n.id, n.source]));
56
+ const check = (who, id, caller, callee, via) => {
57
+ const { result, reason } = caller ? edgeResult(root, caller, callee, via, read, parsed) : { result: 'not-checked', reason: '' };
58
+ if (result === 'found')
59
+ coverage.found++;
60
+ else if (result === 'not-found') {
61
+ coverage.notFound++;
62
+ findings.push({ rule: 'edge-not-found', severity: 'warning', ids: [id], message: `${who}: ${reason}` });
63
+ }
64
+ else if (result === 'unsure') {
65
+ coverage.unsure++;
66
+ coverage.unsureEdges.push({ id, reason });
67
+ }
68
+ else
69
+ coverage.notChecked++;
70
+ };
71
+ for (const e of fig.edges)
72
+ check(`edge "${edgeId(e)}"`, edgeId(e), e.source ?? source.get(e.from), source.get(e.to), e.via);
73
+ const byId = new Map(fig.edges.map((e) => [edgeId(e), e]));
74
+ for (const step of fig.steps ?? [])
75
+ for (const beat of step.flow)
76
+ for (const h of toBeat(beat).hops) {
77
+ const e = byId.get(h.edge);
78
+ if (!e || (h.source == null && h.via == null))
79
+ continue;
80
+ const [a, b] = h.back ? [e.to, e.from] : [e.from, e.to];
81
+ check(`hop on "${h.edge}"`, h.edge, h.source ?? (h.back ? undefined : e.source) ?? source.get(a), source.get(b), h.via);
82
+ }
83
+ return { findings, coverage };
43
84
  }
85
+ export const verify = (fig, opts = {}) => verifyReport(fig, opts).findings;
package/dist/version.d.ts CHANGED
@@ -1,2 +1 @@
1
- /** The package version, for the MCP `serverInfo`. The release list bumps it with package.json. */
2
- export declare const VERSION = "0.6.0";
1
+ export declare const VERSION = "0.8.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- /** The package version, for the MCP `serverInfo`. The release list bumps it with package.json. */
2
- export const VERSION = '0.6.0';
1
+ // The release list bumps this with package.json.
2
+ export const VERSION = '0.8.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowfig",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Your coding agent draws animated diagrams from your code, and flowfig checks them. One animated SVG for a GitHub README, PR or issue, and a React player. Set up Claude Code, Cursor, GitHub Copilot, Codex, Gemini CLI, Windsurf and Kiro with npx flowfig init.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -58,7 +58,8 @@
58
58
  "format:check": "prettier --check .",
59
59
  "svg": "node scripts/figure-svg.mjs",
60
60
  "test": "npm run build && node --test src/*.test.ts",
61
- "typecheck": "tsc --noEmit"
61
+ "typecheck": "tsc --noEmit",
62
+ "lint": "oxlint src scripts"
62
63
  },
63
64
  "peerDependencies": {
64
65
  "react": ">=18"
@@ -71,6 +72,7 @@
71
72
  "@types/react": "^19.0.0",
72
73
  "@types/react-dom": "^19.0.0",
73
74
  "@vitejs/plugin-react": "^6.1.1",
75
+ "oxlint": "1.86.0",
74
76
  "playwright": "^1.50.0",
75
77
  "prettier": "^3.7.4",
76
78
  "react": "^19.0.0",