create-pathfinder 4.2.0 → 4.3.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 (39) hide show
  1. package/CLAUDE.md +2 -0
  2. package/package.json +1 -1
  3. package/skills/learn-codebase/SKILL.md +188 -17
  4. package/skills/learn-feature/SKILL.md +136 -15
  5. package/skills/map-system/SKILL.md +293 -0
  6. package/skills/render-artifact/SKILL.md +187 -0
  7. package/skills/render-artifact/engine/bin/render.mjs +225 -0
  8. package/skills/render-artifact/engine/deliver.mjs +197 -0
  9. package/skills/render-artifact/engine/doctor.mjs +96 -0
  10. package/skills/render-artifact/engine/examples/diagram.json +223 -0
  11. package/skills/render-artifact/engine/examples/lesson.json +242 -0
  12. package/skills/render-artifact/engine/references/determinism.md +71 -0
  13. package/skills/render-artifact/engine/references/specification.md +149 -0
  14. package/skills/render-artifact/engine/references/validation.md +268 -0
  15. package/skills/render-artifact/engine/render/behavior.mjs +128 -0
  16. package/skills/render-artifact/engine/render/diagram.mjs +342 -0
  17. package/skills/render-artifact/engine/render/escape.mjs +34 -0
  18. package/skills/render-artifact/engine/render/graph/behavior.mjs +394 -0
  19. package/skills/render-artifact/engine/render/graph/draw.mjs +204 -0
  20. package/skills/render-artifact/engine/render/graph/interaction.mjs +174 -0
  21. package/skills/render-artifact/engine/render/graph/layout.mjs +698 -0
  22. package/skills/render-artifact/engine/render/graph/style.mjs +200 -0
  23. package/skills/render-artifact/engine/render/graph/width.mjs +204 -0
  24. package/skills/render-artifact/engine/render/index.mjs +50 -0
  25. package/skills/render-artifact/engine/render/lesson.mjs +294 -0
  26. package/skills/render-artifact/engine/render/shell.mjs +275 -0
  27. package/skills/render-artifact/engine/render/theme.mjs +592 -0
  28. package/skills/render-artifact/engine/schemas/common.schema.json +101 -0
  29. package/skills/render-artifact/engine/schemas/diagram.schema.json +176 -0
  30. package/skills/render-artifact/engine/schemas/lesson.schema.json +210 -0
  31. package/skills/render-artifact/engine/validate/composition.mjs +395 -0
  32. package/skills/render-artifact/engine/validate/diagnostics.mjs +83 -0
  33. package/skills/render-artifact/engine/validate/diagram-parts.mjs +68 -0
  34. package/skills/render-artifact/engine/validate/evidence.mjs +302 -0
  35. package/skills/render-artifact/engine/validate/index.mjs +132 -0
  36. package/skills/render-artifact/engine/validate/jsonschema.mjs +312 -0
  37. package/skills/render-artifact/engine/validate/structural.mjs +241 -0
  38. package/skills/render-artifact/engine/verification.mjs +76 -0
  39. package/skills/render-artifact/engine/version.mjs +24 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * The semantic index a reader's interactions run on, and the traversal that
3
+ * runs on it.
4
+ *
5
+ * **Interactions read the graph, never the picture.** Downstream of a node is
6
+ * decided by following directed edges the producer authored, not by asking
7
+ * which boxes happen to sit to the right of it or which polylines happen to
8
+ * touch. Geometry is a rendering of the graph and is the wrong thing to infer
9
+ * meaning from: two nodes can be adjacent on the canvas and unrelated in the
10
+ * system, an edge can route past a node it has nothing to do with, and a
11
+ * layout change would silently change what "downstream" meant. So the model
12
+ * below is built from `nodes` and `edges` and consulted by id.
13
+ *
14
+ * **The model is an index, never content.** Ids, labels, adjacency, and each
15
+ * path's exact ordered edge ids — and nothing else. No summary, no detail, no
16
+ * citation, no evidence. Every one of those is already in the document as
17
+ * text, which is what lets the artifact be read with scripting off; putting a
18
+ * second copy in a script would be the one way to make a fact exist only
19
+ * behind an interaction, and that is precisely what the progressive
20
+ * enhancement contract forbids.
21
+ *
22
+ * **Nothing here is producer-authored.** There is no traversal field, no
23
+ * reachable-set field, no adjacency in the specification. All of it is derived,
24
+ * because all of it is derivable: a producer who could hand-write an adjacency
25
+ * list could hand-write one that disagreed with its own edges, and then the
26
+ * diagram and its behaviour would be two different claims.
27
+ *
28
+ * Imports nothing. This is on the render path, which reaches no `node:`
29
+ * builtin by design.
30
+ */
31
+
32
+ /**
33
+ * @typedef {object} InteractionModel
34
+ * @property {string[]} nodes node ids, in specification order
35
+ * @property {Record<string,string>} labels node id to its label, for the
36
+ * renderer's own status line. A label is a name, not a claim.
37
+ * @property {Record<string,[string,string][]>} out node id to [edge id, target]
38
+ * @property {Record<string,[string,string][]>} in node id to [edge id, source]
39
+ * @property {Record<string,[string,string]>} edges edge id to [source, target]
40
+ * @property {Record<string,string[]>} paths path id to its authored edge ids,
41
+ * in order, exactly as written
42
+ */
43
+
44
+ /**
45
+ * Build the index for one validated diagram.
46
+ *
47
+ * Adjacency is keyed by node and carries the **edge id** alongside the
48
+ * neighbour, not just the neighbour. That is what keeps two edges joining one
49
+ * pair distinguishable: a traversal that recorded only "from `a` you can reach
50
+ * `b`" would lose which of the two relationships it walked, and highlighting
51
+ * would then light up both — reporting a claim the producer did not make.
52
+ *
53
+ * Ordering is specification order throughout, so the serialized model is a
54
+ * pure function of the specification.
55
+ *
56
+ * @param {object} diagram a `diagram` body that passed validation
57
+ * @returns {InteractionModel}
58
+ */
59
+ export function interactionModel(diagram) {
60
+ const nodes = diagram.nodes.map((node) => node.id);
61
+
62
+ const labels = {};
63
+ for (const node of diagram.nodes) labels[node.id] = node.label;
64
+
65
+ const out = {};
66
+ const inbound = {};
67
+ for (const id of nodes) {
68
+ out[id] = [];
69
+ inbound[id] = [];
70
+ }
71
+
72
+ // Edge id to its endpoints. This is what makes a path highlight exact: a
73
+ // path names edge ids, and lighting up the nodes a path touches has to start
74
+ // from the edges the producer wrote rather than from any pair of nodes that
75
+ // happen to be joined somehow.
76
+ const edges = {};
77
+
78
+ for (const edge of diagram.edges) {
79
+ // Endpoints resolved by the composition layer before this runs, so an
80
+ // edge naming a node that does not exist cannot reach here. The guards are
81
+ // still here because a model that silently dropped an edge would make
82
+ // traversal quietly wrong rather than loudly broken.
83
+ if (out[edge.from] !== undefined) out[edge.from].push([edge.id, edge.to]);
84
+ if (inbound[edge.to] !== undefined) inbound[edge.to].push([edge.id, edge.from]);
85
+ edges[edge.id] = [edge.from, edge.to];
86
+ }
87
+
88
+ const paths = {};
89
+ for (const path of diagram.paths ?? []) paths[path.id] = [...path.edges];
90
+
91
+ return { nodes, labels, out, in: inbound, edges, paths };
92
+ }
93
+
94
+ /**
95
+ * The traversal, as the source that ships.
96
+ *
97
+ * This is a string rather than a function for one reason: it is emitted into
98
+ * the artifact, and it is also the thing the tests run. Writing it twice —
99
+ * once for the browser and once for Node — would leave two implementations
100
+ * free to disagree about what "downstream" means, and the one that shipped
101
+ * would be the one nobody had tested. The test evaluates this exact text.
102
+ *
103
+ * Breadth-first over the index, with a visited set, which is what makes the
104
+ * three awkward graph shapes safe rather than special-cased:
105
+ *
106
+ * a cycle a node already visited is not queued again, so a ring
107
+ * terminates instead of spinning
108
+ * a self-edge the node is already visited when its own edge is read,
109
+ * so `a -> a` adds the edge and queues nothing
110
+ * repeated edges both are recorded, because the frontier is keyed by node
111
+ * and the result keeps edge ids separately
112
+ *
113
+ * The start node is reported in `nodes` — a reader tracing downstream of a
114
+ * thing is still looking at that thing — and every edge crossed is reported in
115
+ * `edges`, so the caller can light up exactly the relationships walked rather
116
+ * than every relationship between the nodes involved.
117
+ */
118
+ export const TRAVERSAL_JS = `
119
+ function pfTraverse(model, start, direction) {
120
+ var adjacency = direction === "in" ? model["in"] : model.out;
121
+ if (!adjacency || !adjacency[start]) return { nodes: [start], edges: [] };
122
+
123
+ var seenNodes = Object.create(null);
124
+ var seenEdges = Object.create(null);
125
+ var nodes = [];
126
+ var edges = [];
127
+ var queue = [start];
128
+
129
+ seenNodes[start] = true;
130
+ nodes.push(start);
131
+
132
+ while (queue.length > 0) {
133
+ var current = queue.shift();
134
+ var next = adjacency[current] || [];
135
+
136
+ for (var i = 0; i < next.length; i += 1) {
137
+ var edgeId = next[i][0];
138
+ var neighbour = next[i][1];
139
+
140
+ /* Every edge crossed is recorded, including one that leads somewhere
141
+ already reached. Two edges joining one pair are two claims, and a
142
+ traversal that kept only the first would highlight the wrong one. */
143
+ if (!seenEdges[edgeId]) {
144
+ seenEdges[edgeId] = true;
145
+ edges.push(edgeId);
146
+ }
147
+
148
+ /* A node already reached is never queued again. This single guard is
149
+ what terminates a cycle and what stops a self-edge looping. */
150
+ if (!seenNodes[neighbour]) {
151
+ seenNodes[neighbour] = true;
152
+ nodes.push(neighbour);
153
+ queue.push(neighbour);
154
+ }
155
+ }
156
+ }
157
+
158
+ return { nodes: nodes, edges: edges };
159
+ }
160
+ `.trim();
161
+
162
+ /**
163
+ * Serialize the model for the artifact.
164
+ *
165
+ * `JSON.stringify` with no spacing, and with the keys written in the order
166
+ * this module builds them, so the bytes are a function of the specification
167
+ * and this file. The closing-tag guard matters: a producer label containing
168
+ * `</script>` would otherwise end the script element early and put the rest of
169
+ * the model into the document as text. Escaping the slash keeps the JSON
170
+ * identical to the parser and inert to the HTML tokenizer.
171
+ */
172
+ export function serializeModel(model) {
173
+ return JSON.stringify(model).replace(/<\/script/gi, "<\\/script");
174
+ }