@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/artifact.d.ts +21 -0
- package/dist/artifact.js +48 -10
- package/dist/chartRender.d.ts +8 -0
- package/dist/chartRender.js +14 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +202 -139
- package/dist/package.json +1 -1
- package/dist/report.d.ts +8 -8
- package/dist/reportCheck.d.ts +138 -0
- package/dist/reportCheck.js +17 -0
- package/package.json +1 -1
package/dist/package.json
CHANGED
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};
|