@zibby/skills 0.2.15 → 0.2.17

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.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "Built-in skill definitions for the Zibby agent-workflow framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/dist/report.d.ts CHANGED
@@ -123,33 +123,33 @@ export declare const reportObjectSchema: z.ZodObject<{
123
123
  sub: z.ZodOptional<z.ZodString>;
124
124
  severity: z.ZodOptional<z.ZodEnum<["ok", "info", "warn", "critical"]>>;
125
125
  }, "strip", z.ZodTypeAny, {
126
+ label?: string;
126
127
  sub?: string;
127
128
  severity?: "warn" | "info" | "critical" | "ok";
128
129
  value?: string;
129
- label?: string;
130
130
  }, {
131
+ label?: string;
131
132
  sub?: string;
132
133
  severity?: "warn" | "info" | "critical" | "ok";
133
134
  value?: string;
134
- label?: string;
135
135
  }>, "many">;
136
136
  }, "strip", z.ZodTypeAny, {
137
137
  title?: string;
138
138
  kind?: "breakdown";
139
139
  rows?: {
140
+ label?: string;
140
141
  sub?: string;
141
142
  severity?: "warn" | "info" | "critical" | "ok";
142
143
  value?: string;
143
- label?: string;
144
144
  }[];
145
145
  }, {
146
146
  title?: string;
147
147
  kind?: "breakdown";
148
148
  rows?: {
149
+ label?: string;
149
150
  sub?: string;
150
151
  severity?: "warn" | "info" | "critical" | "ok";
151
152
  value?: string;
152
- label?: string;
153
153
  }[];
154
154
  }>, z.ZodObject<{
155
155
  kind: z.ZodLiteral<"paragraph">;
@@ -184,10 +184,10 @@ export declare const reportObjectSchema: z.ZodObject<{
184
184
  title?: string;
185
185
  kind?: "breakdown";
186
186
  rows?: {
187
+ label?: string;
187
188
  sub?: string;
188
189
  severity?: "warn" | "info" | "critical" | "ok";
189
190
  value?: string;
190
- label?: string;
191
191
  }[];
192
192
  } | {
193
193
  title?: string;
@@ -214,10 +214,10 @@ export declare const reportObjectSchema: z.ZodObject<{
214
214
  title?: string;
215
215
  kind?: "breakdown";
216
216
  rows?: {
217
+ label?: string;
217
218
  sub?: string;
218
219
  severity?: "warn" | "info" | "critical" | "ok";
219
220
  value?: string;
220
- label?: string;
221
221
  }[];
222
222
  } | {
223
223
  title?: string;
@@ -267,10 +267,10 @@ export declare const reportObjectSchema: z.ZodObject<{
267
267
  title?: string;
268
268
  kind?: "breakdown";
269
269
  rows?: {
270
+ label?: string;
270
271
  sub?: string;
271
272
  severity?: "warn" | "info" | "critical" | "ok";
272
273
  value?: string;
273
- label?: string;
274
274
  }[];
275
275
  } | {
276
276
  title?: string;
@@ -314,10 +314,10 @@ export declare const reportObjectSchema: z.ZodObject<{
314
314
  title?: string;
315
315
  kind?: "breakdown";
316
316
  rows?: {
317
+ label?: string;
317
318
  sub?: string;
318
319
  severity?: "warn" | "info" | "critical" | "ok";
319
320
  value?: string;
320
- label?: string;
321
321
  }[];
322
322
  } | {
323
323
  title?: string;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * reportCheck.ts — a DATA-DRIVEN pre-flight check for a rendered report page.
3
+ *
4
+ * WHY THIS EXISTS, AND WHY IT IS NOT THE PUBLISH GATE
5
+ * ───────────────────────────────────────────────────
6
+ * `backend/src/utils/artifact-validate.js` is the ONE authoritative gate on
7
+ * artifact content and it stays that way. It is regex-over-source with eight
8
+ * static rules (escaped markup, markdown-in-html, html-in-markdown, blocked
9
+ * subresources, script syntax, structure, empty body, zero-width bar). Those
10
+ * rules answer ONE question: *is this document well-formed enough to render?*
11
+ * None of them is reimplemented here — that would be exactly the two-places
12
+ * drift CLAUDE.md keeps getting burned by.
13
+ *
14
+ * This module answers the DIFFERENT question the gate's own header says it
15
+ * structurally cannot: *does the rendered page agree with the DATA it was built
16
+ * from?* The gate says it "cannot see that 42 should have been 45". That
17
+ * sentence is this module's entire scope:
18
+ *
19
+ * 1. DATA ↔ RENDER — the data has 23 records, the table renders 20.
20
+ * A bar drawn at 4% of full scale for the LARGEST value.
21
+ * A printed number that disagrees with its own datum.
22
+ * 2. GEOMETRY — a bar whose width looks fine in source but is
23
+ * NULLIFIED by the cascade (a % width on an inline box).
24
+ * 3. OVERFLOW — a fixed-px child inside a narrower clipping ancestor.
25
+ * 4. PLACEHOLDER — `[object Object]`, a bare `NaN`, an un-substituted
26
+ * `${…}` that reached the page as VISIBLE TEXT.
27
+ *
28
+ * THE DESIGN RULE THAT MATTERS MOST — NO PER-PAGE EXPECTATIONS
29
+ * ────────────────────────────────────────────────────────────
30
+ * The failed prior attempt was for the model to hand-write "bar widths should be
31
+ * 900/80/200/140/300" in the SAME pass that authored the HTML. That is not a
32
+ * check: the pass that got the page wrong writes assertions that agree with it,
33
+ * and the next report needs brand-new assertions.
34
+ *
35
+ * So the caller supplies `{ html, data }` and NOTHING ELSE. `data` is whatever
36
+ * JSON the page was built from. This module DISCOVERS the collections and the
37
+ * numeric series inside it, DISCOVERS the tables and bars inside the page, binds
38
+ * them to each other using evidence found in the markup, and derives what must
39
+ * be true. Written once; generic over any report. If a change here ever needs a
40
+ * caller to describe its own page, the change is wrong.
41
+ *
42
+ * HOW GEOMETRY IS MEASURED — AND WHY THERE IS NO BROWSER
43
+ * ──────────────────────────────────────────────────────
44
+ * By PARSING, because the numbers are literally in the source:
45
+ *
46
+ * - An ECharts SSR chart emits each bar as `<path d="M86.6 220l51.8 0l0
47
+ * -139.5l-51.7 0Z" ecmeta_series_index="0" ecmeta_data_index="0">`. The
48
+ * geometry is literal numbers AND the binding back to the datum is an
49
+ * explicit attribute — no heuristic, no layout engine.
50
+ * - An HTML bar is `style="width:45%"` — also a literal number.
51
+ *
52
+ * jsdom was evaluated and REJECTED: it resolves the cascade but performs NO
53
+ * layout, so `getBoundingClientRect()`, `scrollWidth`, `clientWidth` and
54
+ * `offsetWidth` all return 0 (measured, jsdom 29.1.1). It therefore buys nothing
55
+ * this file cannot compute, at 25 MB in a package that installs into every run
56
+ * container and is vendored into the Copilot Lambda image. A real browser IS the
57
+ * only way to get TRUE layout — flex/grid shrink, text wrapping, a table wider
58
+ * than its column — and that class is deliberately OUT OF SCOPE here rather than
59
+ * faked. What IS in scope is the arithmetic subset: an explicit width nullified
60
+ * by an explicit display, and an explicit px child inside an explicit px
61
+ * clipping box.
62
+ *
63
+ * FALSE POSITIVES ARE THE ENEMY — EVERY RULE FAILS OPEN
64
+ * ─────────────────────────────────────────────────────
65
+ * A pre-flight that cries wolf burns the model's repair budget on nothing. So
66
+ * every rule is conservative in the FLAGGING direction: a complex CSS selector
67
+ * is ignored rather than guessed at, a page that renders a top-N subset of a big
68
+ * collection is not "missing rows", a series with fewer than three bars gets no
69
+ * proportionality verdict, and a literal `width:0` is left to the gate that
70
+ * already owns it. The cost is false negatives, which is the right trade for a
71
+ * check that runs before a human ever sees the page.
72
+ *
73
+ * ...WHICH IS WHY THE RESULT CARRIES A PROBE RECEIPT
74
+ * ───────────────────────────────────────────────────
75
+ * "A NEGATIVE result means suspect your PROBE first" (CLAUDE.md). A checker that
76
+ * finds nothing because its selector never matched is indistinguishable from a
77
+ * clean page — unless it says what it looked at. Every result therefore carries
78
+ * `checked`: how many tables, body rows, bars, collections and series it found,
79
+ * and how many bars it actually managed to BIND to a datum. `bars: 5, boundBars:
80
+ * 0` is a broken probe wearing a clean result's clothes, and it is visible.
81
+ */
82
+ /**
83
+ * The defect shape is identical to the gate's (`{ code, line, message, hint }`)
84
+ * so the Copilot handles pre-flight defects and gate defects through ONE code
85
+ * path.
86
+ *
87
+ * `zero-width-bar` is REUSED from the gate's vocabulary deliberately: it means
88
+ * the same thing to the model ("this bar will render invisible") and the two
89
+ * DETECTIONS are disjoint — the gate fires when a bar-ish element has no width
90
+ * source anywhere or a literally broken one (`width:0`, `width:NaN%`,
91
+ * `width:${pct}%`); this fires only when the element HAS a valid positive width
92
+ * that the cascade then throws away. A page cannot trip both for the same
93
+ * element. Every other code below is a failure kind the gate has no rule for.
94
+ */
95
+ export declare const REPORT_CODES: {
96
+ ZERO_WIDTH: string;
97
+ ROW_COUNT: string;
98
+ ROW_MISSING: string;
99
+ BAR_COUNT: string;
100
+ BAR_PROPORTION: string;
101
+ VALUE_MISMATCH: string;
102
+ PLACEHOLDER_TEXT: string;
103
+ OVERFLOW: string;
104
+ };
105
+ export interface ReportCheckResult {
106
+ ok: boolean;
107
+ defects: Array<{
108
+ code: string;
109
+ line: number | null;
110
+ message: string;
111
+ hint: string;
112
+ }>;
113
+ /**
114
+ * The PROBE RECEIPT — what was actually measured. A clean verdict with zeros
115
+ * here is a broken probe, not a clean page. Always read it.
116
+ */
117
+ checked: any;
118
+ }
119
+ /**
120
+ * Check a rendered page against the data it was built from.
121
+ *
122
+ * @param html the exact HTML that would be published
123
+ * @param data whatever JSON the page was rendered from — collections and
124
+ * series are DISCOVERED inside it, never declared by the caller
125
+ */
126
+ export declare function checkRenderedReport({ html, data }: {
127
+ html: string;
128
+ data: any;
129
+ }): ReportCheckResult;
130
+ /** Test hook — the internals are implementation detail, but a probe deserves its own unit tests. */
131
+ export declare const __reportCheckInternals: any;
132
+ /**
133
+ * A pure, offline, zero-credential skill: it reads two arguments and returns
134
+ * JSON. No network, no filesystem, no env — so it declares no `envKeys`, is not
135
+ * `callsBackend`, and runs anywhere `@zibby/skills` is installed (the Fargate
136
+ * run container AND the Copilot Lambda, which vendors the same tarball).
137
+ */
138
+ export declare const reportCheckSkill: any;
@@ -0,0 +1,17 @@
1
+ import{existsSync as G}from"node:fs";import{dirname as Z,resolve as V}from"node:path";import{fileURLToPath as X}from"node:url";var y={ZERO_WIDTH:"zero-width-bar",ROW_COUNT:"data-row-count",ROW_MISSING:"data-row-missing",BAR_COUNT:"data-bar-count",BAR_PROPORTION:"bar-proportion",VALUE_MISMATCH:"data-value-mismatch",PLACEHOLDER_TEXT:"placeholder-text",OVERFLOW:"overflow-clipped"},q=5,K=20,Y=.04,Q=3,ee=.5;function x(t,s){if(s==null||s<0)return null;let e=1;for(let n=0;n<s&&n<t.length;n+=1)t.charCodeAt(n)===10&&(e+=1);return e}function N(t,s,e){let n=s[e.code]||0;n>=q||t.length>=K||(s[e.code]=n+1,t.push(e))}var te={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" ",ndash:"\u2013",mdash:"\u2014",hellip:"\u2026"};function B(t){return String(t).replace(/&(#x?[0-9a-f]+|[a-z]+);/gi,(s,e)=>{if(e[0]==="#"){let o=e[1]==="x"||e[1]==="X"?parseInt(e.slice(2),16):parseInt(e.slice(1),10);return Number.isFinite(o)?String.fromCodePoint(o):s}let n=te[String(e).toLowerCase()];return n??s})}function S(t){return B(String(t??"")).replace(/\s+/g," ").trim().toLowerCase()}function L(t){return Math.round(t*10)/10}var ne=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),$=new Set(["script","style","textarea"]),re=new Set(["span","a","em","strong","b","i","u","s","small","code","label","abbr","cite","q","sub","sup","mark","time","var","kbd","samp","font","tt"]),se=/^<\s*(\/?)\s*([a-zA-Z][a-zA-Z0-9:_.-]*)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/;function ae(t){let s={},e=/([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'=<>`]+))|([a-zA-Z_:][-a-zA-Z0-9_:.]*)/g,n;for(;(n=e.exec(t))!==null;)n[1]?s[n[1].toLowerCase()]=n[3]!=null?n[3]:n[4]!=null?n[4]:n[5]==null?"":n[5]:n[6]&&(s[n[6].toLowerCase()]="");return s}function D(t){let s={tag:"#root",attrs:{},children:[],parent:null,start:0},e=s,n=[s],o=0,i=(r,a)=>{if(a<=r)return;let c=t.slice(r,a);c.trim()&&e.children.push({tag:"#text",attrs:{},children:[],parent:e,start:r,value:c})};for(;o<t.length;){let r=t.indexOf("<",o);if(r<0){i(o,t.length);break}if(i(o,r),t.startsWith("<!--",r)){let u=t.indexOf("-->",r);o=u<0?t.length:u+3;continue}if(t.startsWith("<!",r)||t.startsWith("<?",r)){let u=t.indexOf(">",r);o=u<0?t.length:u+1;continue}let a=se.exec(t.slice(r));if(!a){o=r+1;continue}let c=a[0],l=a[2].toLowerCase();if(a[1]){for(let u=n.length-1;u>0;u-=1)if(n[u].tag===l){n.length=u,e=n[u-1];break}o=r+c.length;continue}let d={tag:l,attrs:ae(a[3]||""),children:[],parent:e,start:r};if(e.children.push(d),o=r+c.length,!(a[4]||ne.has(l))){if($.has(l)){let u=t.slice(o),h=new RegExp(`</\\s*${l}\\s*>`,"i").exec(u);d.raw=h?u.slice(0,h.index):u,o+=h?h.index+h[0].length:u.length;continue}n.push(d),e=d}}return s}function v(t,s){for(let e of t.children)e.tag!=="#text"&&(s(e),v(e,s))}function T(t){let s=[],e=n=>{for(let o of n.children)o.tag==="#text"?s.push(o.value||""):(s.push(" "),e(o),s.push(" "))};return e(t),S(s.join(""))}function ie(t){let s=[],e=n=>{for(let o of n.children)o.tag==="#text"?s.push({value:o.value||"",start:o.start,parent:n}):o.tag!=="pre"&&o.tag!=="code"&&e(o)};return e(t),s}function A(t){let s={};for(let e of String(t||"").split(";")){let n=e.indexOf(":");n<0||(s[e.slice(0,n).trim().toLowerCase()]=e.slice(n+1).trim().replace(/\s*!important$/i,""))}return s}function M(t){let s=[];return v(t,e=>{if(e.tag!=="style"||!e.raw)return;let n=e.raw.replace(/@[a-z-]+[^{]*\{/gi," "),o=/([^{}]+)\{([^{}]*)\}/g,i;for(;(i=o.exec(n))!==null;){let r=A(i[2]);for(let a of i[1].split(",")){let c=a.trim();if(!c||/[\s>+~:[\]*]/.test(c))continue;let l=[],d=null,u=null,h=!0,f=/([.#]?)([A-Za-z_][-\w]*)/g,g;for(;(g=f.exec(c))!==null;)if(g[1]===".")l.push(g[2].toLowerCase());else if(g[1]==="#")d=g[2];else if(g.index===0)u=g[2].toLowerCase();else{h=!1;break}h&&(u||d||l.length)&&s.push({classes:l,id:d,tag:u,decls:r})}}}),s}function P(t){return String(t.attrs.class||"").split(/\s+/).filter(Boolean).map(s=>s.toLowerCase())}function oe(t,s,e){if(t.tag&&t.tag!==s.tag||t.id&&t.id!==s.attrs.id)return!1;for(let n of t.classes)if(e.indexOf(n)<0)return!1;return!0}function R(t,s,e){let n=A(t.attrs.style)[s];if(n!=null&&n!=="")return n.toLowerCase();let o=P(t),i=null;for(let r of e){if(!oe(r,t,o))continue;let a=r.decls[s];a!=null&&a!==""&&(i=a.toLowerCase())}return i}function H(t,s){let e=R(t,"display",s);return e||(re.has(t.tag)?"inline":"block")}function I(t){if(!t)return null;let s=/^(-?\d+(?:\.\d+)?)px$/.exec(String(t).trim());return s?Number(s[1]):null}function F(t){if(!t)return null;let s=/^(-?\d+(?:\.\d+)?)%$/.exec(String(t).trim());return s?Number(s[1]):null}function E(t){return t!=null&&typeof t=="object"&&!Array.isArray(t)}function le(t){if(!t.length||!E(t[0]))return t.map(n=>n==null?"":String(n));let s=null,e=0;for(let n of Object.keys(t[0])){let o=t.map(r=>E(r)&&r[n]!=null?String(r[n]):"");if(o.some(r=>r.length<2))continue;let i=new Set(o.map(S)).size;if(i>e&&(e=i,s=o),i===t.length)break}return s||t.map((n,o)=>String(E(n)?Object.values(n)[0]==null?o:Object.values(n)[0]:n))}function j(t){let s=[],e=[],n=new Set,o=(r,a)=>{if(!(a.length<2)){if(a.every(E)){s.push({path:r,records:a,identities:le(a)});let c=Object.keys(a[0]),l=c.filter(u=>a.every(h=>typeof h[u]=="number"&&Number.isFinite(h[u]))),d=c.filter(u=>a.every(h=>typeof h[u]=="string"));l.length===1&&d.length>=1&&e.push({path:r,labels:a.map(u=>String(u[d[0]])),values:a.map(u=>Number(u[l[0]]))});return}a.every(c=>typeof c=="number"&&Number.isFinite(c))&&e.push({path:r,labels:a.map((c,l)=>`#${l}`),values:a.slice()})}},i=(r,a,c)=>{if(!(c>6||r==null||typeof r!="object"||n.has(r))){if(n.add(r),Array.isArray(r)){o(a,r);for(let l=0;l<r.length&&l<200;l+=1)i(r[l],`${a}[${l}]`,c+1);return}for(let l of Object.keys(r))i(r[l],a?`${a}.${l}`:l,c+1)}};return i(t,"",0),{collections:s,series:e}}function z(t){let s=String(t||"").match(/[MmLlHhVvZz]|-?\d*\.?\d+(?:e-?\d+)?/gi);if(!s)return null;let e=0,n=0,o="",i=1/0,r=-1/0,a=1/0,c=-1/0,l=()=>{!Number.isFinite(e)||!Number.isFinite(n)||(e<i&&(i=e),e>r&&(r=e),n<a&&(a=n),n>c&&(c=n))},d=0,u=0;for(;d<s.length&&u++<2e4;){let h=s[d];if(/[A-Za-z]/.test(h)&&(o=h,d+=1,o==="Z"||o==="z"))continue;if(d>=s.length)break;let f=()=>{let g=Number(s[d]);return d+=1,g};switch(o){case"M":e=f(),n=f(),l(),o="L";break;case"m":e+=f(),n+=f(),l(),o="l";break;case"L":e=f(),n=f(),l();break;case"l":e+=f(),n+=f(),l();break;case"H":e=f(),l();break;case"h":e+=f(),l();break;case"V":n=f(),l();break;case"v":n+=f(),l();break;default:d+=1;break}}return!Number.isFinite(i)||!Number.isFinite(a)?null:{w:r-i,h:c-a}}var ce=/(^|[-_])(bar|gauge|meter|fill|progress|track)([-_]|$)/i;function ue(t){let s=t.parent;for(let e=0;s&&e<3;e+=1,s=s.parent){let n=T(s);if(n)return n}return""}function U(t,s){if(P(t).some(o=>ce.test(o)))return!0;let e=A(t.attrs.style);return!!(e.background||e["background-color"]||R(t,"background",s)||R(t,"background-color",s))&&T(t)===""}function he(t){let s=new Map;for(let n of t)for(let o=n.node.parent;o;o=o.parent)s.set(o,(s.get(o)||0)+1);let e=new Map;for(let n of t){let o=null;for(let i=n.node.parent;i;i=i.parent)if((s.get(i)||0)>=2){o=i;break}o&&(e.has(o)||e.set(o,[]),e.get(o).push(n))}return[...e.values()].filter(n=>n.length>=2)}function de(t,s){let e=new Map,n=[];v(t,i=>{if(i.tag==="path"||i.tag==="rect"){let d=i.attrs.ecmeta_series_index;if(d==null)return;let u=null;if(i.tag==="rect"){let g=Number(i.attrs.width),w=Number(i.attrs.height);Number.isFinite(g)&&Number.isFinite(w)&&(u={w:g,h:w})}else u=z(i.attrs.d||"");if(!u)return;let h=i.attrs.ecmeta_data_index,f=String(d);e.has(f)||e.set(f,[]),e.get(f).push({bar:{node:i,extent:NaN,unit:"user",dataIndex:h==null||h===""?null:Number(h),context:"",start:i.start},box:u});return}if($.has(i.tag)||i.tag==="svg")return;let r=A(i.attrs.style),a=r.width==null?r["inline-size"]:r.width;if(a==null||a==="")return;let c=F(a),l=I(a);c==null&&l==null||U(i,s)&&n.push({node:i,extent:c??l,unit:c==null?"px":"%",dataIndex:null,context:ue(i),start:i.start})});let o=[];for(let i of e.values()){if(i.length<2)continue;let r=l=>Math.max(...l)-Math.min(...l),a=r(i.map(l=>l.box.h))>=r(i.map(l=>l.box.w));for(let l of i)l.bar.extent=a?l.box.h:l.box.w;let c=i.map(l=>l.bar).sort((l,d)=>l.dataIndex!=null&&d.dataIndex!=null?l.dataIndex-d.dataIndex:l.start-d.start);o.push(c)}for(let i of he(n)){let r=i.filter(l=>l.unit==="%"),a=i.filter(l=>l.unit==="px"),c=r.length>=a.length?r:a;c.length>=2&&o.push(c)}return o}function fe(t){let s=[];return v(t,e=>{if(e.tag!=="table")return;let n=[];v(e,o=>{if(o.tag!=="tr")return;let i=o.children.filter(r=>r.tag==="td"||r.tag==="th");i.length&&i.every(r=>r.tag==="th")||n.push(o)}),s.push({node:e,rows:n,rowTexts:n.map(T),start:e.start})}),s}function ge(t,s,e,n,o,i,r){let a=T(s);for(let c of n){let l=c.records.length;if(l<3)continue;let d=[],u=0;for(let p of c.identities){let m=S(p);!m||m.length<2||(u+=1,a.includes(m)||d.push(String(p)))}if(u<3)continue;let h=u-d.length,f=u?h/u:1,g=null,w=0;for(let p of e){if(!p.rows.length)continue;let m=0,k=new Set;for(let _ of p.rowTexts){let O=!1;c.identities.forEach((J,W)=>{let C=S(J);C.length>=2&&_.indexOf(C)>=0&&(O=!0,k.add(W))}),O&&(m+=1)}m<Math.max(2,Math.ceil(p.rows.length*.5))||k.size<Math.ceil(m*.8)||m>w&&(w=m,g=p)}let b=g!=null;r.push({path:c.path||"(root)",records:l,identitiesSearched:u,presentInPage:h,renderedFraction:Number(f.toFixed(3)),boundTableRows:b?g.rows.length:null}),!(f<ee)&&(d.length&&N(o,i,{code:y.ROW_MISSING,line:b?x(t,g.start):1,message:`The data at \`${c.path||"(root)"}\` has ${l} records but ${d.length} of them appear NOWHERE on the page: ${d.slice(0,6).map(p=>JSON.stringify(String(p).slice(0,40))).join(", ")}${d.length>6?`, +${d.length-6} more`:""}.`,hint:"Render every record you were given, or say in the page that it is a partial view and how many were left out. A silently truncated table reads as the complete answer."}),d.length===0&&b&&g.rows.length!==l&&N(o,i,{code:y.ROW_COUNT,line:x(t,g.start),message:`The table renders ${g.rows.length} body row(s) but the data at \`${c.path||"(root)"}\` has ${l} records \u2014 ${Math.abs(l-g.rows.length)} ${l>g.rows.length?"missing":"extra"}.`,hint:'Emit exactly one <tr> per record. If you meant to show only some of them, state the cut ("top 10 of 23") in the page itself.'}))}}function pe(t,s){let e=new Array(t.length).fill(-1);if(t.every(i=>i.dataIndex!=null)){let i=0;if(t.forEach((r,a)=>{let c=r.dataIndex;c>=0&&c<s.values.length&&(e[a]=c,i+=1)}),i===t.length)return e}e.fill(-1);let n=new Set,o=0;if(t.forEach((i,r)=>{for(let a=0;a<s.labels.length;a+=1){if(n.has(a))continue;let c=S(s.labels[a]);if(c.length>=2&&i.context.indexOf(c)>=0){e[r]=a,n.add(a),o+=1;break}}}),o===t.length)return e;if(t.length!==s.values.length)return null;for(let i=0;i<t.length;i+=1)e[i]=i;return e}function be(t,s){let e=null,n=0;for(let i of s){let r=i.labels.filter(a=>{let c=S(a);return c.length>=2&&t.some(l=>l.context.indexOf(c)>=0)}).length;r>n&&(n=r,e=i)}if(n>=2)return e;let o=s.filter(i=>i.values.length===t.length);return o.length===1?o[0]:null}function me(t,s,e,n,o,i){i.barGroups=s.map(r=>r.length),i.boundBars=0;for(let r of s){let a=be(r,e);if(!a)continue;if(a.values.length!==r.length){N(n,o,{code:y.BAR_COUNT,line:x(t,r[0].start),message:`The chart draws ${r.length} bar(s) but the series at \`${a.path||"(root)"}\` has ${a.values.length} value(s).`,hint:"Draw one bar per value. A dropped bar silently removes a whole category from the reader's view of the data."});continue}let c=pe(r,a);if(!c||c.some(h=>h<0))continue;i.boundBars+=r.length;let l=[];for(let h=0;h<r.length;h+=1)r[h].extent>0&&a.values[c[h]]>0&&l.push(h);if(l.length>=Q){let h=l.map(p=>r[p].extent/a.values[c[p]]).sort((p,m)=>p-m),f=Math.floor(h.length/2),g=h.length%2?h[f]:(h[f-1]+h[f])/2,w=Math.max(...a.values.map(p=>Math.abs(p))),b=g*w;if(g>0&&b>0)for(let p of l){let m=a.values[c[p]],k=g*m,_=Math.abs(r[p].extent-k)/b;if(_<=Y)continue;let O=r[p].unit==="%"?"%":"";N(n,o,{code:y.BAR_PROPORTION,line:x(t,r[p].start),message:`The bar for ${JSON.stringify(a.labels[c[p]])} (value ${m}) is drawn at ${L(r[p].extent)}${O}, but every other bar in this chart puts it at ${L(k)}${O} \u2014 off by ${Math.round(_*100)}% of the full-scale bar. The chart misrepresents the data.`,hint:"Every bar in one chart must use the SAME value\u2192length scale. Compute each length from its datum with one formula (value / max * 100), never by writing the numbers out by hand."})}}let d=a.values.reduce((h,f)=>h+f,0),u=Math.max(...a.values.map(h=>Math.abs(h)));for(let h=0;h<r.length;h+=1){let f=a.values[c[h]],g=r[h].context;if(!g)continue;let w=(g.match(/-?\d[\d,]*(?:\.\d+)?/g)||[]).map(b=>Number(b.replace(/,/g,""))).filter(b=>Number.isFinite(b));w.length&&(w.some(b=>Math.abs(b-f)<1e-9)||d>0&&w.some(b=>Math.abs(b-f/d*100)<.6)||u>0&&w.some(b=>Math.abs(b-f/u*100)<.6)||N(n,o,{code:y.VALUE_MISMATCH,line:x(t,r[h].start),message:`The row for ${JSON.stringify(a.labels[c[h]])} prints ${w.slice(0,3).join(", ")} but its datum is ${f}.`,hint:"Print the figure your tools returned, unchanged, and use that same figure everywhere it appears on the page."}))}}}var we=/^(flex|inline-flex|grid|inline-grid)$/;function ye(t,s,e,n,o,i){let r=0;v(s,a=>{if($.has(a.tag))return;let c=A(a.attrs.style).width;if(!c)return;let l=F(c);if(l==null||l<=0||!U(a,e))return;r+=1;let d=H(a,e);if(d==="none"){N(n,o,{code:y.ZERO_WIDTH,line:x(t,a.start),message:`<${a.tag}> is given style="width:${c}" but its effective display is \`none\` \u2014 it is not rendered at all.`,hint:"Remove the display:none rule that matches this element, or remove the element. A width on a display:none box is dead code."});return}if(d!=="inline")return;let u=a.parent;if(u&&u.tag!=="#root"&&we.test(H(u,e)))return;let h=R(a,"position",e);if(h==="absolute"||h==="fixed")return;let f=R(a,"float",e);f==="left"||f==="right"||N(n,o,{code:y.ZERO_WIDTH,line:x(t,a.start),message:`<${a.tag}${a.attrs.class?` class="${a.attrs.class}"`:""}> is given style="width:${c}" but its effective display is \`inline\` \u2014 CSS IGNORES width on a non-replaced inline box, so this bar renders with no length at all. The source looks right; the page will not.`,hint:"Give the bar display:block or display:inline-block (or make its container display:flex). A <span> is inline by DEFAULT \u2014 that alone is enough to swallow the width."})}),i.percentWidthBars=r}function xe(t,s,e,n,o){v(s,i=>{let r=I(R(i,"width",e));if(!(r==null||r<=0))for(let a=i.parent;a&&a.tag!=="#root";a=a.parent){let c=I(R(a,"width",e));if(c==null||c<=0)continue;if(r<=c)return;let l=R(a,"overflow",e)||R(a,"overflow-x",e);if(l!=="hidden"&&l!=="clip")return;N(n,o,{code:y.OVERFLOW,line:x(t,i.start),message:`<${i.tag}> is ${r}px wide inside a ${c}px ancestor with overflow:${l} \u2014 ${Math.round(r-c)}px of it is clipped and can never be seen.`,hint:"Size the child from its container (a percentage, or max-width:100%), or widen the container. Content the reader cannot see is content you did not publish."});return}})}var Ne=[{re:/\[object (?:Object|Array|Promise|Map|Set|Null|Undefined)\]/g,what:"an object stringified instead of a field being picked off it"},{re:/\$\{[^}\n]{0,80}\}/g,what:"an un-substituted JavaScript template placeholder"},{re:/\{\{[^}\n]{0,80}\}\}/g,what:"an un-substituted moustache placeholder"},{re:/(?:^|[^\w.])(NaN)(?![\w])/g,what:"NaN \u2014 an arithmetic result that was never a number"},{re:/(?:^|[^\w.])(-?Infinity)(?![\w])/g,what:"Infinity \u2014 a division by zero reached the page"}],Re=/^(undefined|null)$/i,ve=new Set(["td","th","li","span","div","strong","em","b"]);function Se(t,s,e,n,o){let i=0;for(let r of ie(s)){let a=r.value;i+=a.length;for(let l of Ne){l.re.lastIndex=0;let d;for(;(d=l.re.exec(a))!==null;){let u=d[1]==null?d[0]:d[1];N(e,n,{code:y.PLACEHOLDER_TEXT,line:x(t,r.start+d.index+d[0].indexOf(u)),message:`The page prints ${JSON.stringify(u.slice(0,60))} as VISIBLE TEXT \u2014 ${l.what}. A reader sees this literally.`,hint:"Format the value before it reaches the page: pick the field you meant off the object, and guard the arithmetic so a missing input renders as an em dash rather than NaN."}),d.index===l.re.lastIndex&&(l.re.lastIndex+=1)}}let c=B(a).trim();Re.test(c)&&ve.has(r.parent.tag)&&N(e,n,{code:y.PLACEHOLDER_TEXT,line:x(t,r.start),message:`A <${r.parent.tag}> contains only the literal text "${c}" \u2014 the value it was meant to hold was ${c}.`,hint:'Render an em dash (\u2014) or "n/a" for a missing value; never let the JavaScript literal reach the reader.'})}o.textCharsScanned=i}function ke({html:t,data:s}){let e=typeof t=="string"?t:"",n=[],o={};if(!e.trim())return{ok:!1,defects:[{code:"empty-content",line:1,message:"No html was passed to the check.",hint:"Pass the exact page source you are about to publish."}],checked:{tables:0,bodyRows:0,bars:0,barGroups:[],boundBars:0,seriesInData:0,collections:[]}};let i=D(e),r=M(i),a=fe(i),c=de(i,r),l=j(s),d={tables:a.length,bodyRows:a.reduce((u,h)=>u+h.rows.length,0),bars:c.reduce((u,h)=>u+h.length,0),barGroups:[],boundBars:0,seriesInData:l.series.length,collections:[]};return ge(e,i,a,l.collections,n,o,d.collections),me(e,c,l.series,n,o,d),ye(e,i,r,n,o,d),xe(e,i,r,n,o),Se(e,i,n,o,d),n.sort((u,h)=>(u.line||0)-(h.line||0)),{ok:n.length===0,defects:n,checked:d}}var Ee={parseDocument:D,pathBBox:z,discoverData:j,textOf:T,collectSimpleRules:M,effectiveDisplay:H};function Oe(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=Z(X(import.meta.url)),s=V(t,"..","bin","mcp-skill.mjs");return G(s)?s:null}var He={id:"report-check",serverName:"report_check",allowedTools:["mcp__report_check__*"],envKeys:[],description:"Report check \u2014 verify a rendered report page against the DATA it was built from (row counts, bar proportionality, collapsed bars, placeholder residue) before publishing it.",promptFragment:`## Report Check (run this BEFORE you publish a data page)
2
+ When you are about to publish a page that RENDERS DATA \u2014 a table of records, a
3
+ bar chart, a set of meters \u2014 call \`report_check\` first with the exact \`html\`
4
+ you are about to publish and the \`data\` you built it from (the raw tool
5
+ results, as JSON). It compares the two and tells you where the page disagrees
6
+ with the data: a table short of rows, a bar drawn at the wrong length, a printed
7
+ number that is not its datum, a bar whose width the CSS silently throws away, an
8
+ \`[object Object]\` or an un-substituted \`\${\u2026}\` left in a cell.
9
+
10
+ This is NOT the publish validator \u2014 that one runs server-side and checks the
11
+ MARKUP. This one checks the FACTS. Pass the data unchanged; do not write your own
12
+ expectations and do not describe the page. The check derives what must be true
13
+ from the data by itself.
14
+
15
+ Then READ the \`checked\` block in the result: it says how many tables, rows and
16
+ bars were actually found. If it reports 0 bars for a page full of bars, the check
17
+ never saw your chart and a clean verdict means nothing.`,resolve(){let t=Oe();return t?{type:"stdio",command:"node",args:[t,"../dist/reportCheck.js","reportCheckSkill"],env:{},description:this.description,alwaysLoad:!0}:{command:null,args:[],env:{},description:this.description}},async handleToolCall(t,s){if(t!=="report_check")return JSON.stringify({error:`Unknown tool: ${t}`});try{let e=typeof s?.html=="string"?s.html:"";if(!e.trim())return JSON.stringify({error:"html is required \u2014 pass the exact page source you are about to publish."});let n=s?.data;if(typeof n=="string")try{n=JSON.parse(n)}catch{return JSON.stringify({error:"data was a string but not valid JSON \u2014 pass the data as a JSON object."})}return n==null?JSON.stringify({error:"data is required \u2014 pass the tool results the page was built from, unchanged."}):JSON.stringify(ke({html:e,data:n}))}catch(e){return JSON.stringify({error:`report_check failed: ${e.message}`})}},tools:[{name:"report_check",description:'Pre-flight a data page BEFORE publishing it: compares the rendered HTML against the DATA it was built from and reports every place they disagree. Catches what the publish validator structurally cannot \u2014 a table that renders fewer rows than the data has records, a bar drawn at a length that is not proportional to its value, a number printed beside a bar that is not that bar\'s datum, a bar whose width is silently nullified by the CSS cascade, and placeholder residue ([object Object], NaN, an un-substituted ${\u2026}) left in visible text. Pass the data UNCHANGED, exactly as your tools returned it (wrap a bare array as {"rows": [...]}) \u2014 the check discovers the collections and series inside it and derives what must be true on its own. Do NOT write your own expectations. Returns { ok, defects:[{code,line,message,hint}], checked }. Always read `checked`: it reports how many tables, rows and bars were found, so a clean `ok:true` with `bars: 0` on a chart page tells you the check never saw your chart.',input_schema:{type:"object",properties:{html:{type:"string",description:"The exact HTML you are about to publish (the same string you would pass to artifact_publish)."},data:{type:"object",description:'The data the page was built from, as JSON \u2014 the raw tool results. Any shape: arrays of records and label/value series are found automatically. Wrap a bare top-level array as {"rows": [...]}.'}},required:["html","data"]}}]};export{y as REPORT_CODES,Ee as __reportCheckInternals,ke as checkRenderedReport,He as reportCheckSkill};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "Built-in skill definitions for the Zibby agent-workflow framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",