@pixerate/editor 0.223.1 → 0.223.3

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 (28) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/{chunk-SVHXD2VH.js → chunk-H73MSHYZ.js} +5 -1
  4. package/dist/{chunk-BDT2IYXK.cjs → chunk-VXLYEYP4.cjs} +6 -2
  5. package/dist/tools/index.cjs +2 -2
  6. package/dist/tools/index.js +1 -1
  7. package/dist/tools/skill-evals.d.cts +6 -1
  8. package/dist/tools/skill-evals.d.ts +6 -1
  9. package/package.json +1 -1
  10. package/skills/README.md +5 -1
  11. package/skills/pixerate-diagram/SKILL.md +8 -0
  12. package/skills/pixerate-diagram-architecture/SKILL.md +104 -0
  13. package/skills/pixerate-diagram-architecture/evals/label-protocols.json +62 -0
  14. package/skills/pixerate-diagram-architecture/evals/trust-boundaries.json +64 -0
  15. package/skills/pixerate-diagram-architecture/evals/web-app.json +56 -0
  16. package/skills/pixerate-diagram-process-flow/SKILL.md +99 -0
  17. package/skills/pixerate-diagram-process-flow/evals/order-process.json +73 -0
  18. package/skills/pixerate-diagram-process-flow/evals/swimlanes.json +74 -0
  19. package/skills/pixerate-diagram-process-flow/evals/tidy-decision.json +76 -0
  20. package/skills/pixerate-spreadsheet/SKILL.md +2 -0
  21. package/skills/pixerate-spreadsheet-analysis/SKILL.md +97 -0
  22. package/skills/pixerate-spreadsheet-analysis/evals/count-and-average.json +148 -0
  23. package/skills/pixerate-spreadsheet-analysis/evals/month-over-month.json +128 -0
  24. package/skills/pixerate-spreadsheet-analysis/evals/summary-by-region.json +178 -0
  25. package/skills/pixerate-spreadsheet-financial-model/SKILL.md +98 -0
  26. package/skills/pixerate-spreadsheet-financial-model/evals/find-hardcoded.json +113 -0
  27. package/skills/pixerate-spreadsheet-financial-model/evals/projection-with-inputs.json +118 -0
  28. package/skills/pixerate-spreadsheet-financial-model/evals/scenarios.json +136 -0
@@ -0,0 +1,73 @@
1
+ {
2
+ "prompt": "Draw our order process as a flowchart: it starts when an order arrives, we validate it, then check whether it's paid. If it is, we ship it and the process ends; if not, we send a reminder and check payment again.",
3
+ "fixture": {
4
+ "editor": "diagram"
5
+ },
6
+ "checks": [
7
+ {
8
+ "why": "a flowchart with one direction",
9
+ "tool": "get_diagram",
10
+ "expect": {
11
+ "family": "flowchart",
12
+ "direction": {
13
+ "$in": [
14
+ "LR",
15
+ "TB",
16
+ "TD"
17
+ ]
18
+ }
19
+ }
20
+ },
21
+ {
22
+ "why": "the payment check is a decision (diamond)",
23
+ "tool": "get_diagram",
24
+ "expect": {
25
+ "nodes": {
26
+ "$some": {
27
+ "shape": "diam",
28
+ "label": {
29
+ "$match": [
30
+ "paid|payment",
31
+ "i"
32
+ ]
33
+ }
34
+ }
35
+ }
36
+ }
37
+ },
38
+ {
39
+ "why": "the decision's two exits are labelled",
40
+ "tool": "get_diagram",
41
+ "expect": {
42
+ "$expr": "(() => { const d = v.nodes.find((n) => n.shape === 'diam'); const out = v.edges.filter((e) => e.source === d.id); return out.length === 2 && out.every((e) => e.label); })()"
43
+ }
44
+ },
45
+ {
46
+ "why": "the reminder loops back to the decision",
47
+ "tool": "get_diagram",
48
+ "expect": {
49
+ "$expr": "(() => { const d = v.nodes.find((n) => n.shape === 'diam'); const r = v.nodes.find((n) => /remind/i.test(n.label)); return !!r && v.edges.some((e) => e.source === r.id && e.target === d.id); })()"
50
+ }
51
+ },
52
+ {
53
+ "why": "start and end are terminals (stadium or rounded)",
54
+ "tool": "get_diagram",
55
+ "expect": {
56
+ "nodes": {
57
+ "$expr": "v.filter((n) => ['stadium', 'rounded', 'circle', 'dbl-circ'].includes(n.shape)).length >= 2"
58
+ }
59
+ }
60
+ }
61
+ ],
62
+ "solution": [
63
+ {
64
+ "tool": "load_diagram_mermaid",
65
+ "args": {
66
+ "source": "flowchart LR\n start([Start]) --> validate[Validate order]\n validate --> paid{Paid?}\n paid -->|yes| ship[Ship order]\n paid -->|no| remind[Send reminder]\n remind --> paid\n ship --> done([End])"
67
+ }
68
+ },
69
+ {
70
+ "tool": "get_diagram"
71
+ }
72
+ ]
73
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "prompt": "Put this process into swimlanes: the customer places and pays for the order, the shop validates and ships it.",
3
+ "fixture": {
4
+ "editor": "diagram",
5
+ "setup": [
6
+ {
7
+ "tool": "load_diagram_mermaid",
8
+ "args": {
9
+ "source": "flowchart LR\n place[Place order] --> pay[Pay]\n pay --> validate[Validate order]\n validate --> ship[Ship order]"
10
+ }
11
+ }
12
+ ]
13
+ },
14
+ "checks": [
15
+ {
16
+ "why": "two lanes, Customer and Shop",
17
+ "tool": "get_diagram",
18
+ "expect": {
19
+ "groups": {
20
+ "$length": 2,
21
+ "$some": {
22
+ "label": {
23
+ "$match": [
24
+ "customer",
25
+ "i"
26
+ ]
27
+ }
28
+ },
29
+ "$expr": "v.some((g) => /shop/i.test(g.label))"
30
+ }
31
+ }
32
+ },
33
+ {
34
+ "why": "each step in its lane",
35
+ "tool": "get_diagram",
36
+ "expect": {
37
+ "$expr": "(() => { const lane = (re) => v.groups.find((g) => re.test(g.label)).id; const c = lane(/customer/i), s = lane(/shop/i); const p = (id) => v.nodes.find((n) => n.id === id).parentId; return p('place') === c && p('pay') === c && p('validate') === s && p('ship') === s; })()"
38
+ }
39
+ },
40
+ {
41
+ "why": "the flow is unchanged",
42
+ "tool": "get_diagram",
43
+ "expect": {
44
+ "edges": {
45
+ "$length": 3
46
+ }
47
+ }
48
+ }
49
+ ],
50
+ "solution": [
51
+ {
52
+ "tool": "group_diagram_nodes",
53
+ "args": {
54
+ "nodeIds": [
55
+ "place",
56
+ "pay"
57
+ ],
58
+ "label": "Customer",
59
+ "id": "customer"
60
+ }
61
+ },
62
+ {
63
+ "tool": "group_diagram_nodes",
64
+ "args": {
65
+ "nodeIds": [
66
+ "validate",
67
+ "ship"
68
+ ],
69
+ "label": "Shop",
70
+ "id": "shop"
71
+ }
72
+ }
73
+ ]
74
+ }
@@ -0,0 +1,76 @@
1
+ {
2
+ "prompt": "The decision in this flowchart is hard to read: the condition and the outcomes are crammed into the node. Tidy it up.",
3
+ "fixture": {
4
+ "editor": "diagram",
5
+ "setup": [
6
+ {
7
+ "tool": "load_diagram_mermaid",
8
+ "args": {
9
+ "source": "flowchart LR\n check{Has the payment been received? If yes ship, otherwise remind} --> ship[Ship order]\n check --> remind[Send reminder]"
10
+ }
11
+ }
12
+ ]
13
+ },
14
+ "checks": [
15
+ {
16
+ "why": "a short question in the decision (at most six words)",
17
+ "tool": "get_diagram",
18
+ "expect": {
19
+ "nodes": {
20
+ "$some": {
21
+ "id": "check",
22
+ "shape": "diam",
23
+ "label": {
24
+ "$expr": "v.trim().split(/\\s+/).length <= 6 && /\\?$/.test(v.trim())"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ },
30
+ {
31
+ "why": "the outcomes are on the edges",
32
+ "tool": "get_diagram",
33
+ "expect": {
34
+ "edges": {
35
+ "$every": {
36
+ "label": {
37
+ "$match": [
38
+ "^(yes|no|paid|unpaid|received|not received)$",
39
+ "i"
40
+ ]
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ],
47
+ "solution": [
48
+ {
49
+ "tool": "rename_diagram_node",
50
+ "args": {
51
+ "id": "check",
52
+ "label": "Payment received?"
53
+ }
54
+ },
55
+ {
56
+ "tool": "set_diagram_edge_label",
57
+ "args": {
58
+ "id": {
59
+ "$read": "get_diagram",
60
+ "path": "edges.0.id"
61
+ },
62
+ "label": "yes"
63
+ }
64
+ },
65
+ {
66
+ "tool": "set_diagram_edge_label",
67
+ "args": {
68
+ "id": {
69
+ "$read": "get_diagram",
70
+ "path": "edges.1.id"
71
+ },
72
+ "label": "no"
73
+ }
74
+ }
75
+ ]
76
+ }
@@ -21,6 +21,8 @@ For these jobs, load the workflow skill too: it has the method, the checks betwe
21
21
  the pitfalls, and uses the tools below.
22
22
 
23
23
  - `pixerate-spreadsheet-clean-import`: tidying imported or pasted data: structure, headers, types, dates, duplicates.
24
+ - `pixerate-spreadsheet-analysis`: summaries, growth, shares and rankings as formulas that reconcile.
25
+ - `pixerate-spreadsheet-financial-model`: inputs, consistent calculation rows, scenarios and audits.
24
26
 
25
27
  ## Working method
26
28
 
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: pixerate-spreadsheet-analysis
3
+ description: The analysis workflow in a Pixerate spreadsheet - turning tidy data into answers with live formulas - summary tables by category (SUMIFS, COUNTIFS, AVERAGEIFS), distinct lists with UNIQUE, ranked and filtered views with SORT and FILTER, period totals and growth, shares of a total, checks that the summary reconciles with the data, and handing the result to a chart. Use when asked to summarise, total, count, average, group, pivot, rank, compare, break down or analyse data in a spreadsheet, work out growth or percentages, find the top items, or prepare numbers for a chart, or when get_range and set_range are available. Load with pixerate-spreadsheet, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-spreadsheet
7
+ ---
8
+
9
+ # Analysing data in a spreadsheet
10
+
11
+ An analysis is a set of formulas that answer a question from the data and keep answering it
12
+ when the data changes. Start from tidy data (one header row, one record per row; clean it first
13
+ with `pixerate-spreadsheet-clean-import` if needed), put each summary in its own block beside or
14
+ below the data, and make every number a formula over the data, never a typed result.
15
+
16
+ This skill is the method. `pixerate-spreadsheet` has the full tool reference and the supported
17
+ functions; for the chart that shows the result, see `pixerate-chart-choose-and-build`.
18
+
19
+ ## Goal and quality bar
20
+
21
+ - Every result is a formula over the data (`SUMIFS`, `COUNTIFS`, `AVERAGEIFS`, `FILTER`,
22
+ `UNIQUE`, `SORT`), with absolute references to the data range, so it updates and can be
23
+ checked.
24
+ - Summaries have a header row and one row per group; nothing is typed over a formula.
25
+ - The summary reconciles: group totals add up to the data's total.
26
+ - No formula errors (`get_range` `errors` empty); expected errors handled explicitly
27
+ (`IFERROR` around a division by a possible zero), and blanks where a value does not apply.
28
+ - The data itself is untouched.
29
+
30
+ ## Inputs to confirm
31
+
32
+ - **The question** (totals by region, growth by month, top products) and the measure.
33
+ - **Where the result goes** (a cell such as G1, or below the data).
34
+ - **Groups:** a fixed list the user gives, or every distinct value in the data (`UNIQUE`).
35
+
36
+ ## Working method
37
+
38
+ 1. **Read the data.** `get_range` the header and a sample; note the data range, which column is
39
+ the group and which the measure, and whether it is tidy.
40
+ **Check:** one header row, one record per row, numbers as numbers.
41
+ 2. **Lay out the summary** in an empty area: a header row, then one row per group. List groups
42
+ yourself when there are few and fixed, or `=UNIQUE(B2:B500)` for every value (leave room for
43
+ it to spill).
44
+ 3. **Write the formulas in one block.** `set_range` with the header and a formula per row, each
45
+ with absolute data references and a relative group cell: `=SUMIFS($D$2:$D$500,$B$2:$B$500,G2)`.
46
+ **Check:** the returned `errors` is empty.
47
+ 4. **Reconcile.** Add a total row (`=SUM(H2:H5)`) and compare it with `=SUM($D$2:$D$500)`; for
48
+ counts, with `=COUNTA` of the key column.
49
+ **Check:** they match; if not, a group is missing or a value is text.
50
+ 5. **Derived measures**: growth `=B3/B2-1` (blank for the first period), share `=H2/SUM($H$2:$H$5)`,
51
+ rank `=RANK(H2,$H$2:$H$5)`, ranked lists `=SORT(G2:H5,2,-1)`.
52
+ **Check:** `get_range` values match a hand calculation for one row.
53
+ 6. **Hand over.** Tell the user where the summary is and what each column means; to chart it,
54
+ pass the summary block (categories and values) to the chart editor's `set_chart_data`.
55
+
56
+ ## Decision table
57
+
58
+ | Question | Formula |
59
+ | --- | --- |
60
+ | Total per group | `SUMIFS(measure, group_range, group)` |
61
+ | How many per group | `COUNTIFS(group_range, group)` |
62
+ | Average per group | `AVERAGEIFS(measure, group_range, group)` |
63
+ | Two conditions (region and month) | `SUMIFS` with two criteria pairs |
64
+ | Every distinct group | `UNIQUE(group_range)`, spilled |
65
+ | Rows that meet a condition | `FILTER(data, condition)` |
66
+ | Top items | `SORT` (or `SORTBY`) the summary by its total, descending |
67
+ | Change from the previous period | `=this/previous-1`, first period blank |
68
+ | Share of total | `=value/SUM($total_range)` |
69
+
70
+ ## Recipes
71
+
72
+ **Totals by region at G1.** `set_range` from G1 with `[["Region", "Total"], ["North",
73
+ "=SUMIFS($D$2:$D$11,$B$2:$B$11,G2)"], ...]` (each row's formula pointing at its own G cell).
74
+
75
+ **Month-over-month growth in C.** `set_range` from C1: `[["Growth"], [null], ["=B3/B2-1"],
76
+ ["=B4/B3-1"], ...]`.
77
+
78
+ **Orders and average per product.** Columns `=COUNTIFS($C$2:$C$11,G2)` and
79
+ `=AVERAGEIFS($D$2:$D$11,$C$2:$C$11,G2)`.
80
+
81
+ ## Pitfalls
82
+
83
+ - **Typed results.** A number typed into a summary does not update; always a formula.
84
+ - **Relative data references.** `D2:D11` shifts when you write the formula down; use `$D$2:$D$11`.
85
+ - **Text numbers** in the measure column are skipped by `SUMIFS`; clean the data first.
86
+ - **Spills blocked**: `UNIQUE`, `FILTER` and `SORT` need empty cells below and to the right.
87
+ - **Totals inside the data range** double-count; keep summaries outside it.
88
+ - **0 for "no previous period"** misleads averages; leave it blank.
89
+ - **`set_range` writes formulas as given**: write each row's own references.
90
+
91
+ ## Tools used
92
+
93
+ - `get_range`: read the data and check every result and error
94
+ - `set_range`: write summary blocks with headers and formulas, one undo step
95
+ - `set_cell`: fix one formula
96
+ - `insert_columns`, `insert_rows`: make room for a summary
97
+ - `undo_spreadsheet`: take back a step
@@ -0,0 +1,148 @@
1
+ {
2
+ "prompt": "For each product, show how many orders there were and the average order amount, in a small table starting at G1 (Product, Orders, Average), with formulas.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ "Date",
8
+ "Region",
9
+ "Product",
10
+ "Amount"
11
+ ],
12
+ [
13
+ "2026-01-05",
14
+ "North",
15
+ "Widget",
16
+ 120
17
+ ],
18
+ [
19
+ "2026-01-07",
20
+ "South",
21
+ "Gadget",
22
+ 80
23
+ ],
24
+ [
25
+ "2026-01-12",
26
+ "North",
27
+ "Gadget",
28
+ 95
29
+ ],
30
+ [
31
+ "2026-01-15",
32
+ "East",
33
+ "Widget",
34
+ 60
35
+ ],
36
+ [
37
+ "2026-01-20",
38
+ "West",
39
+ "Widget",
40
+ 150
41
+ ],
42
+ [
43
+ "2026-02-02",
44
+ "South",
45
+ "Widget",
46
+ 110
47
+ ],
48
+ [
49
+ "2026-02-08",
50
+ "East",
51
+ "Gadget",
52
+ 70
53
+ ],
54
+ [
55
+ "2026-02-11",
56
+ "North",
57
+ "Widget",
58
+ 130
59
+ ],
60
+ [
61
+ "2026-02-19",
62
+ "West",
63
+ "Gadget",
64
+ 90
65
+ ],
66
+ [
67
+ "2026-02-25",
68
+ "South",
69
+ "Gadget",
70
+ 85
71
+ ]
72
+ ]
73
+ },
74
+ "checks": [
75
+ {
76
+ "why": "Widget: 5 orders averaging 114; Gadget: 5 orders averaging 84",
77
+ "tool": "get_range",
78
+ "args": {
79
+ "range": "G2:I10"
80
+ },
81
+ "expect": {
82
+ "values": {
83
+ "$nonEmpty": {
84
+ "$length": 2,
85
+ "$expr": "v.some((r) => r[0] === 'Widget' && r[1] === 5 && Math.abs(r[2] - 114) < 0.01) && v.some((r) => r[0] === 'Gadget' && r[1] === 5 && Math.abs(r[2] - 84) < 0.01)"
86
+ }
87
+ }
88
+ }
89
+ },
90
+ {
91
+ "why": "formulas for the counts and averages",
92
+ "tool": "get_range",
93
+ "args": {
94
+ "range": "H2:I3"
95
+ },
96
+ "expect": {
97
+ "raw": {
98
+ "$every": {
99
+ "$every": {
100
+ "$match": "^="
101
+ }
102
+ }
103
+ }
104
+ }
105
+ },
106
+ {
107
+ "why": "headers",
108
+ "tool": "get_range",
109
+ "args": {
110
+ "range": "G1:I1"
111
+ },
112
+ "expect": {
113
+ "values": [
114
+ {
115
+ "$every": {
116
+ "$match": "\\S"
117
+ }
118
+ }
119
+ ]
120
+ }
121
+ }
122
+ ],
123
+ "solution": [
124
+ {
125
+ "tool": "set_range",
126
+ "args": {
127
+ "start": "G1",
128
+ "values": [
129
+ [
130
+ "Product",
131
+ "Orders",
132
+ "Average"
133
+ ],
134
+ [
135
+ "Widget",
136
+ "=COUNTIFS($C$2:$C$11,G2)",
137
+ "=AVERAGEIFS($D$2:$D$11,$C$2:$C$11,G2)"
138
+ ],
139
+ [
140
+ "Gadget",
141
+ "=COUNTIFS($C$2:$C$11,G3)",
142
+ "=AVERAGEIFS($D$2:$D$11,$C$2:$C$11,G3)"
143
+ ]
144
+ ]
145
+ }
146
+ }
147
+ ]
148
+ }
@@ -0,0 +1,128 @@
1
+ {
2
+ "prompt": "Add month-over-month growth in column C (header in C1) as a fraction (0.1 for 10 %), with formulas. January has no previous month.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ "Month",
8
+ "Revenue"
9
+ ],
10
+ [
11
+ "Jan",
12
+ 100
13
+ ],
14
+ [
15
+ "Feb",
16
+ 110
17
+ ],
18
+ [
19
+ "Mar",
20
+ 99
21
+ ],
22
+ [
23
+ "Apr",
24
+ 121
25
+ ],
26
+ [
27
+ "May",
28
+ 121
29
+ ],
30
+ [
31
+ "Jun",
32
+ 133
33
+ ]
34
+ ]
35
+ },
36
+ "checks": [
37
+ {
38
+ "why": "growth for February to June",
39
+ "tool": "get_range",
40
+ "args": {
41
+ "range": "C3:C7"
42
+ },
43
+ "expect": {
44
+ "values": {
45
+ "$expr": "[0.1, -0.1, 0.2222, 0, 0.0992].every((g, i) => Math.abs(v[i][0] - g) < 0.001)"
46
+ }
47
+ }
48
+ },
49
+ {
50
+ "why": "formulas, not typed numbers",
51
+ "tool": "get_range",
52
+ "args": {
53
+ "range": "C3:C7"
54
+ },
55
+ "expect": {
56
+ "raw": {
57
+ "$every": {
58
+ "$every": {
59
+ "$match": "^="
60
+ }
61
+ }
62
+ }
63
+ }
64
+ },
65
+ {
66
+ "why": "January is left empty, not 0 or an error",
67
+ "tool": "get_range",
68
+ "args": {
69
+ "range": "C2"
70
+ },
71
+ "expect": {
72
+ "values": [
73
+ [
74
+ ""
75
+ ]
76
+ ],
77
+ "errors": []
78
+ }
79
+ },
80
+ {
81
+ "why": "a header",
82
+ "tool": "get_range",
83
+ "args": {
84
+ "range": "C1"
85
+ },
86
+ "expect": {
87
+ "values": [
88
+ [
89
+ {
90
+ "$match": "\\S"
91
+ }
92
+ ]
93
+ ]
94
+ }
95
+ }
96
+ ],
97
+ "solution": [
98
+ {
99
+ "tool": "set_range",
100
+ "args": {
101
+ "start": "C1",
102
+ "values": [
103
+ [
104
+ "Growth"
105
+ ],
106
+ [
107
+ null
108
+ ],
109
+ [
110
+ "=B3/B2-1"
111
+ ],
112
+ [
113
+ "=B4/B3-1"
114
+ ],
115
+ [
116
+ "=B5/B4-1"
117
+ ],
118
+ [
119
+ "=B6/B5-1"
120
+ ],
121
+ [
122
+ "=B7/B6-1"
123
+ ]
124
+ ]
125
+ }
126
+ }
127
+ ]
128
+ }