@domandigital/craft 0.15.0 → 0.16.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.
- package/CHANGELOG.md +7 -0
- package/CHARACTER.md +34 -0
- package/dist/{audit-MTUC24XU.js → audit-UJCUWORL.js} +2 -2
- package/dist/{chunk-LFNSB6W7.js → chunk-B2MJLGF6.js} +1 -0
- package/dist/{chunk-QU6Q2QZZ.js → chunk-CVG5GSMN.js} +5 -5
- package/dist/{chunk-RXXRXV3V.js → chunk-LWVGIJ5A.js} +171 -16
- package/dist/{chunk-MUVFECFC.js → chunk-LXDNIJFG.js} +28 -11
- package/dist/{cli-FCKHYAVZ.js → chunk-NTIPJ5U7.js} +2 -185
- package/dist/{chunk-2IQ5OXFR.js → chunk-TQKDCA4V.js} +4 -4
- package/dist/{chunk-YDURAOYR.js → chunk-WAV6AKR4.js} +1 -1
- package/dist/{chunk-OUQ25LY2.js → chunk-YK2FFL6Z.js} +1 -1
- package/dist/cli-3GQROFCB.js +241 -0
- package/dist/{cli-5ACKICBO.js → cli-52M62QBN.js} +5 -5
- package/dist/{cli-FLG3ZCAF.js → cli-AVYNHBFJ.js} +11 -5
- package/dist/cli-CWORJYOU.js +190 -0
- package/dist/{cli-7OZCYOQY.js → cli-K4LSRYBE.js} +4 -4
- package/dist/{cli-22TSWAX6.js → cli-PEATEW57.js} +5 -5
- package/dist/cli.js +2 -2
- package/dist/index.cjs +299 -100
- package/dist/index.d.cts +149 -2
- package/dist/index.d.ts +149 -2
- package/dist/index.js +287 -100
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -757,10 +757,10 @@ function stripLayers(css, ignored) {
|
|
|
757
757
|
const names = new Set(ignored.map((n) => n.trim().toLowerCase()));
|
|
758
758
|
let out = "";
|
|
759
759
|
let i = 0;
|
|
760
|
-
const
|
|
760
|
+
const pattern2 = /@layer\s+([a-zA-Z0-9_-]+)\s*\{/gi;
|
|
761
761
|
while (i < css.length) {
|
|
762
|
-
|
|
763
|
-
const match =
|
|
762
|
+
pattern2.lastIndex = i;
|
|
763
|
+
const match = pattern2.exec(css);
|
|
764
764
|
if (match === null) {
|
|
765
765
|
out += css.slice(i);
|
|
766
766
|
break;
|
|
@@ -790,12 +790,12 @@ var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
790
790
|
]);
|
|
791
791
|
function atRuleBodies(css, prelude) {
|
|
792
792
|
const out = [];
|
|
793
|
-
const
|
|
793
|
+
const pattern2 = new RegExp(
|
|
794
794
|
prelude.source,
|
|
795
795
|
prelude.flags.includes("g") ? prelude.flags : `${prelude.flags}g`
|
|
796
796
|
);
|
|
797
797
|
let match;
|
|
798
|
-
while ((match =
|
|
798
|
+
while ((match = pattern2.exec(css)) !== null) {
|
|
799
799
|
const start = match.index + match[0].length;
|
|
800
800
|
let depth = 1;
|
|
801
801
|
let i = start;
|
|
@@ -805,7 +805,7 @@ function atRuleBodies(css, prelude) {
|
|
|
805
805
|
i += 1;
|
|
806
806
|
}
|
|
807
807
|
out.push(css.slice(start, i - 1));
|
|
808
|
-
|
|
808
|
+
pattern2.lastIndex = i;
|
|
809
809
|
}
|
|
810
810
|
return out;
|
|
811
811
|
}
|
|
@@ -838,9 +838,9 @@ function isVarOnlyList(value) {
|
|
|
838
838
|
return parts2.length > 1 && parts2.every((part) => /^\s*var\(\s*--[\w-]+\s*\)\s*$/.test(part));
|
|
839
839
|
}
|
|
840
840
|
function distinctValues(css, property, props, unresolved) {
|
|
841
|
-
const
|
|
841
|
+
const pattern2 = new RegExp(`(?:^|[;{\\s])${property}\\s*:\\s*([^;}]+)`, "gi");
|
|
842
842
|
const seen = /* @__PURE__ */ new Set();
|
|
843
|
-
for (const match of css.matchAll(
|
|
843
|
+
for (const match of css.matchAll(pattern2)) {
|
|
844
844
|
const raw = match[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
845
845
|
if (CSS_WIDE_KEYWORDS.has(raw)) continue;
|
|
846
846
|
const value = unwrapShadowColor(raw);
|
|
@@ -1259,14 +1259,14 @@ function linkSpans(text) {
|
|
|
1259
1259
|
return [...text.matchAll(LINK)].map((m) => [m.index ?? 0, (m.index ?? 0) + m[0].length]);
|
|
1260
1260
|
}
|
|
1261
1261
|
var insideLink = (spans, at) => spans.some(([a, b]) => at >= a && at < b);
|
|
1262
|
-
function phrases(
|
|
1262
|
+
function phrases(patterns2, label, source = (ctx) => ctx.blocks, skip = () => false) {
|
|
1263
1263
|
return (ctx) => {
|
|
1264
1264
|
const hits = [];
|
|
1265
1265
|
for (const block of source(ctx)) {
|
|
1266
1266
|
const spans = [];
|
|
1267
1267
|
const links = linkSpans(block.text);
|
|
1268
|
-
for (const
|
|
1269
|
-
const global = new RegExp(
|
|
1268
|
+
for (const pattern2 of patterns2) {
|
|
1269
|
+
const global = new RegExp(pattern2.source, pattern2.flags.includes("g") ? pattern2.flags : `${pattern2.flags}g`);
|
|
1270
1270
|
for (const m of block.text.matchAll(global)) {
|
|
1271
1271
|
if (skip(block, m[0], m.index ?? 0)) continue;
|
|
1272
1272
|
if (insideLink(links, m.index ?? 0)) continue;
|
|
@@ -2653,11 +2653,11 @@ function baseClass(cls) {
|
|
|
2653
2653
|
function bases(unit) {
|
|
2654
2654
|
return unit.classes.map(baseClass);
|
|
2655
2655
|
}
|
|
2656
|
-
function has(unit,
|
|
2657
|
-
return bases(unit).some((c) =>
|
|
2656
|
+
function has(unit, pattern2) {
|
|
2657
|
+
return bases(unit).some((c) => pattern2.test(c));
|
|
2658
2658
|
}
|
|
2659
|
-
function find(unit,
|
|
2660
|
-
return bases(unit).find((c) =>
|
|
2659
|
+
function find(unit, pattern2) {
|
|
2660
|
+
return bases(unit).find((c) => pattern2.test(c));
|
|
2661
2661
|
}
|
|
2662
2662
|
function arbitrary(cls) {
|
|
2663
2663
|
const m = cls.match(/\[(.*)\]$/);
|
|
@@ -2673,9 +2673,9 @@ function eachUnit(ctx, fn) {
|
|
|
2673
2673
|
}
|
|
2674
2674
|
return hits;
|
|
2675
2675
|
}
|
|
2676
|
-
function firstMatch(text,
|
|
2677
|
-
const m =
|
|
2678
|
-
|
|
2676
|
+
function firstMatch(text, pattern2) {
|
|
2677
|
+
const m = pattern2.exec(text);
|
|
2678
|
+
pattern2.lastIndex = 0;
|
|
2679
2679
|
return m ? m.index : -1;
|
|
2680
2680
|
}
|
|
2681
2681
|
var FRAMEWORK_PALETTE = /^--(?:color-)?(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+$|^--tw-/;
|
|
@@ -3119,9 +3119,9 @@ function shadcnCardHits(ctx) {
|
|
|
3119
3119
|
}
|
|
3120
3120
|
function marqueeHits(ctx) {
|
|
3121
3121
|
const hits = [];
|
|
3122
|
-
const
|
|
3122
|
+
const pattern2 = /@keyframes\s+[\w-]*marquee|animate-(?:marquee|scroll-x|infinite-scroll)\b|<Marquee\b|["']react-fast-marquee["']|ui\/marquee["']|animation(?:-name)?\s*:\s*[^;]*marquee/i;
|
|
3123
3123
|
for (const file of ctx.files) {
|
|
3124
|
-
const at = firstMatch(file.text,
|
|
3124
|
+
const at = firstMatch(file.text, pattern2);
|
|
3125
3125
|
if (at !== -1) hits.push({ path: file.path, offset: at, message: "scrolling marquee" });
|
|
3126
3126
|
}
|
|
3127
3127
|
return hits;
|
|
@@ -3963,8 +3963,8 @@ function protectedFacts(text) {
|
|
|
3963
3963
|
const mask = (start, length) => {
|
|
3964
3964
|
rest = rest.slice(0, start) + " ".repeat(length) + rest.slice(start + length);
|
|
3965
3965
|
};
|
|
3966
|
-
for (const [kind,
|
|
3967
|
-
for (const m of [...rest.matchAll(
|
|
3966
|
+
for (const [kind, pattern2] of PATTERNS) {
|
|
3967
|
+
for (const m of [...rest.matchAll(pattern2)]) {
|
|
3968
3968
|
const value = m[0].replace(/[.,;:]+$/, "");
|
|
3969
3969
|
facts.push({ kind, value, key: `${kind}:${normalise(kind, value)}` });
|
|
3970
3970
|
mask(m.index ?? 0, m[0].length);
|
|
@@ -4290,7 +4290,7 @@ function side(page, other, brief) {
|
|
|
4290
4290
|
function compareCompetitor(ours, competitor, brief) {
|
|
4291
4291
|
return { ours: side(ours, competitor, brief), competitor: side(competitor, ours, brief), brief: Boolean(brief) };
|
|
4292
4292
|
}
|
|
4293
|
-
var pctOf = (
|
|
4293
|
+
var pctOf = (share2) => share2 === null ? "no sentences" : `${Math.round(share2 * 100)}%`;
|
|
4294
4294
|
function formatCompetitor(result) {
|
|
4295
4295
|
const row = (label, s) => ` ${label.padEnd(11)} ${s.interchangeable.length} of ${s.sentences} (${pctOf(s.share)}), ${s.perHundred} specifics per 100 words`;
|
|
4296
4296
|
const lines = [
|
|
@@ -4666,11 +4666,11 @@ function reflexFonts(list) {
|
|
|
4666
4666
|
for (const font of s.fonts) {
|
|
4667
4667
|
const name = wanted.get(normaliseFamily(font.family).toLowerCase());
|
|
4668
4668
|
if (!name || seen.has(name)) continue;
|
|
4669
|
-
const
|
|
4669
|
+
const share2 = font.chars / total;
|
|
4670
4670
|
const displayShare = font.displayChars / display;
|
|
4671
|
-
if (
|
|
4671
|
+
if (share2 >= 0.15 || displayShare >= 0.3) {
|
|
4672
4672
|
seen.add(name);
|
|
4673
|
-
hits.push(hit(s, `${name} sets ${pct(
|
|
4673
|
+
hits.push(hit(s, `${name} sets ${pct(share2)} of the text on the page${displayShare >= 0.3 ? ` and ${pct(displayShare)} of the headings` : ""}`));
|
|
4674
4674
|
}
|
|
4675
4675
|
}
|
|
4676
4676
|
return hits;
|
|
@@ -5873,8 +5873,8 @@ var IMPERATIVE = /^(?:Add|Use|Check|Make|Get|Set|Include|Create|Update|List|Keep
|
|
|
5873
5873
|
var atStart = (sentence, index) => /^[\s*_"“”’(>#|-]*(?:\d{1,3}[.)]\s*)?[\s*_]*(?:[\w ]+:\**\s*)?$/.test(sentence.slice(0, index)) || /\|\s*\**$/.test(sentence.slice(0, index));
|
|
5874
5874
|
function namedSources(sentence) {
|
|
5875
5875
|
const found = /* @__PURE__ */ new Set();
|
|
5876
|
-
ATTRIBUTION.forEach((
|
|
5877
|
-
for (const m of sentence.matchAll(
|
|
5876
|
+
ATTRIBUTION.forEach((pattern2, i) => {
|
|
5877
|
+
for (const m of sentence.matchAll(pattern2)) {
|
|
5878
5878
|
const raw = m[1].trim();
|
|
5879
5879
|
const word = !/\s/.test(raw) && !/[’']s$/.test(m[0].split(/\s+/)[0]);
|
|
5880
5880
|
if (i === 2 && word && atStart(sentence, m.index ?? 0)) continue;
|
|
@@ -6118,7 +6118,7 @@ function shapeOf(text) {
|
|
|
6118
6118
|
const ss = splitSentences(text);
|
|
6119
6119
|
const wordCount = words(text).length;
|
|
6120
6120
|
const per100 = (n) => wordCount === 0 ? 0 : 100 * n / wordCount;
|
|
6121
|
-
const
|
|
6121
|
+
const share2 = (re) => ss.length === 0 ? 0 : ss.filter((s) => re.test(s)).length / ss.length;
|
|
6122
6122
|
const tri = ss.flatMap(tricolons);
|
|
6123
6123
|
const commas = (text.match(/,/g) ?? []).length;
|
|
6124
6124
|
return {
|
|
@@ -6132,9 +6132,9 @@ function shapeOf(text) {
|
|
|
6132
6132
|
hedgeRate: per100(hedgeCount(text)),
|
|
6133
6133
|
hedgedClose: hedgedClose(ss),
|
|
6134
6134
|
nominalisationRate: per100(nominalisations(text)),
|
|
6135
|
-
pronounOpenerShare:
|
|
6136
|
-
transitionOpenerShare:
|
|
6137
|
-
impersonalOpenerShare:
|
|
6135
|
+
pronounOpenerShare: share2(PRONOUN_OPENER),
|
|
6136
|
+
transitionOpenerShare: share2(TRANSITION_OPENER),
|
|
6137
|
+
impersonalOpenerShare: share2(IMPERSONAL_OPENER),
|
|
6138
6138
|
commasPerSentence: ss.length === 0 ? 0 : commas / ss.length,
|
|
6139
6139
|
commasPer100: per100(commas),
|
|
6140
6140
|
participialRate: ss.length === 0 ? 0 : ss.reduce((s, x) => s + participialClauses(x), 0) / ss.length,
|
|
@@ -6161,6 +6161,89 @@ function isBlurb(f4) {
|
|
|
6161
6161
|
return f4.sentences >= 2 && f4.sentences <= 8 && f4.words >= 20 && f4.words <= 160;
|
|
6162
6162
|
}
|
|
6163
6163
|
|
|
6164
|
+
// src/character/lexical.ts
|
|
6165
|
+
var JUZEK = "Juzek and Ward, arXiv:2412.11385, Appendix A (PubMed abstracts, 2020 to 2024)";
|
|
6166
|
+
var STUDY_WORDS = [
|
|
6167
|
+
"delves",
|
|
6168
|
+
"delved",
|
|
6169
|
+
"delving",
|
|
6170
|
+
"delve",
|
|
6171
|
+
"showcasing",
|
|
6172
|
+
"boasts",
|
|
6173
|
+
"underscores",
|
|
6174
|
+
"underscoring",
|
|
6175
|
+
"comprehending",
|
|
6176
|
+
"intricacies",
|
|
6177
|
+
"surpassing",
|
|
6178
|
+
"intricate"
|
|
6179
|
+
].map((word) => ({ word, source: JUZEK }));
|
|
6180
|
+
function vocabulary() {
|
|
6181
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6182
|
+
const out = [];
|
|
6183
|
+
const add = (word, source) => {
|
|
6184
|
+
const w = word.toLowerCase();
|
|
6185
|
+
if (seen.has(w)) return;
|
|
6186
|
+
seen.add(w);
|
|
6187
|
+
out.push({ word: w, source });
|
|
6188
|
+
};
|
|
6189
|
+
for (const s of STUDY_WORDS) add(s.word, s.source);
|
|
6190
|
+
for (const w of AI_WORDS) add(w, "AI_WORDS");
|
|
6191
|
+
for (const w of PLAINER_WORDS) add(w, "PLAINER_WORDS");
|
|
6192
|
+
for (const w of AI_PHRASES) add(w, "AI_PHRASES");
|
|
6193
|
+
for (const w of STOCK_PHRASES) add(w, "STOCK_PHRASES");
|
|
6194
|
+
for (const w of BUZZWORDS) add(w, "BUZZWORDS");
|
|
6195
|
+
return out;
|
|
6196
|
+
}
|
|
6197
|
+
var escape4 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6198
|
+
var pattern = (w) => new RegExp(`(?<![\\w-])${escape4(w).replace(/\s+/g, "\\s+")}(?![\\w-])`, "gi");
|
|
6199
|
+
var compiled = null;
|
|
6200
|
+
var patterns = () => compiled ?? (compiled = vocabulary().map((v) => ({ ...v, re: pattern(v.word) })));
|
|
6201
|
+
var plain = (text) => text.replace(/[’‘]/g, "'").replace(/\s+/g, " ");
|
|
6202
|
+
function vocabularyCounts(text) {
|
|
6203
|
+
const t = plain(text);
|
|
6204
|
+
const out = /* @__PURE__ */ new Map();
|
|
6205
|
+
for (const { word, re } of patterns()) {
|
|
6206
|
+
const n = (t.match(re) ?? []).length;
|
|
6207
|
+
if (n) out.set(word, n);
|
|
6208
|
+
}
|
|
6209
|
+
return out;
|
|
6210
|
+
}
|
|
6211
|
+
var LEXICAL_FEATURES = [
|
|
6212
|
+
"listRate",
|
|
6213
|
+
"studyRate",
|
|
6214
|
+
"hardPer100",
|
|
6215
|
+
"tradePer100",
|
|
6216
|
+
"specificsPer100",
|
|
6217
|
+
"generic",
|
|
6218
|
+
"figures",
|
|
6219
|
+
"names"
|
|
6220
|
+
];
|
|
6221
|
+
var STUDY = new Set(STUDY_WORDS.map((s) => s.word));
|
|
6222
|
+
var LISTED = new Set([...AI_WORDS, ...PLAINER_WORDS, ...AI_PHRASES, ...STOCK_PHRASES, ...BUZZWORDS].map((w) => w.toLowerCase()));
|
|
6223
|
+
var r3 = (n) => Math.round(n * 1e3) / 1e3;
|
|
6224
|
+
function lexicalOf(text, brief) {
|
|
6225
|
+
const words3 = Math.max(1, countWords2(text));
|
|
6226
|
+
const counts = vocabularyCounts(text);
|
|
6227
|
+
let list = 0;
|
|
6228
|
+
let study = 0;
|
|
6229
|
+
for (const [w, n] of counts) {
|
|
6230
|
+
if (STUDY.has(w)) study += n;
|
|
6231
|
+
if (LISTED.has(w)) list += n;
|
|
6232
|
+
}
|
|
6233
|
+
const s = specificity(text, brief);
|
|
6234
|
+
const kind = (...k) => s.specifics.filter((x) => k.includes(x.kind)).length;
|
|
6235
|
+
return {
|
|
6236
|
+
listRate: r3(list / words3 * 100),
|
|
6237
|
+
studyRate: r3(study / words3 * 100),
|
|
6238
|
+
hardPer100: r3(s.hard / words3 * 100),
|
|
6239
|
+
tradePer100: r3(s.trade / words3 * 100),
|
|
6240
|
+
specificsPer100: r3(s.perHundred),
|
|
6241
|
+
generic: s.generic ? 1 : 0,
|
|
6242
|
+
figures: kind("money", "date", "time", "number"),
|
|
6243
|
+
names: kind("name", "brief")
|
|
6244
|
+
};
|
|
6245
|
+
}
|
|
6246
|
+
|
|
6164
6247
|
// src/character/baseline.ts
|
|
6165
6248
|
var BASELINE_VERSION = 1;
|
|
6166
6249
|
function fnv1a(text) {
|
|
@@ -6261,23 +6344,23 @@ function parseCraftConfig(data, knownTells, source = "craft.config.json") {
|
|
|
6261
6344
|
return { ignore, severity, ...data.copyPaths ? { copyPaths: data.copyPaths } : {} };
|
|
6262
6345
|
}
|
|
6263
6346
|
function globToRegExp(glob) {
|
|
6264
|
-
const
|
|
6347
|
+
const pattern2 = glob.replace(/^\.\//, "").replace(/\/$/, "").replace(/\/\*\*$/, "");
|
|
6265
6348
|
let body = "";
|
|
6266
|
-
for (let i = 0; i <
|
|
6267
|
-
const c =
|
|
6268
|
-
if (c === "*" &&
|
|
6269
|
-
const slash =
|
|
6349
|
+
for (let i = 0; i < pattern2.length; i += 1) {
|
|
6350
|
+
const c = pattern2[i];
|
|
6351
|
+
if (c === "*" && pattern2[i + 1] === "*") {
|
|
6352
|
+
const slash = pattern2[i + 2] === "/";
|
|
6270
6353
|
body += slash ? "(?:.*/)?" : ".*";
|
|
6271
6354
|
i += slash ? 2 : 1;
|
|
6272
6355
|
} else if (c === "*") body += "[^/]*";
|
|
6273
6356
|
else if (c === "?") body += "[^/]";
|
|
6274
6357
|
else body += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6275
6358
|
}
|
|
6276
|
-
const anywhere = !
|
|
6359
|
+
const anywhere = !pattern2.includes("/");
|
|
6277
6360
|
return new RegExp(`^${anywhere ? "(?:.*/)?" : ""}${body}(?:/.*)?$`);
|
|
6278
6361
|
}
|
|
6279
|
-
function ignoreMatcher(
|
|
6280
|
-
const res =
|
|
6362
|
+
function ignoreMatcher(patterns2) {
|
|
6363
|
+
const res = patterns2.map(globToRegExp);
|
|
6281
6364
|
return (path) => {
|
|
6282
6365
|
const p = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6283
6366
|
return res.some((re) => re.test(p));
|
|
@@ -6444,11 +6527,11 @@ function visualMeasures(img, maxSamples = 1e6) {
|
|
|
6444
6527
|
const sdRg = Math.sqrt(Math.max(0, sumRg2 / n - meanRg * meanRg));
|
|
6445
6528
|
const sdYb = Math.sqrt(Math.max(0, sumYb2 / n - meanYb * meanYb));
|
|
6446
6529
|
const colourfulness = Math.sqrt(sdRg ** 2 + sdYb ** 2) + 0.3 * Math.sqrt(meanRg ** 2 + meanYb ** 2);
|
|
6447
|
-
const
|
|
6530
|
+
const round3 = (v, d = 3) => Math.round(v * 10 ** d) / 10 ** d;
|
|
6448
6531
|
return {
|
|
6449
|
-
colourfulness:
|
|
6450
|
-
edgeDensity:
|
|
6451
|
-
symmetry:
|
|
6532
|
+
colourfulness: round3(colourfulness, 1),
|
|
6533
|
+
edgeDensity: round3(edgeN ? edges / edgeN : 0),
|
|
6534
|
+
symmetry: round3(asymN ? 1 - asym / asymN / 255 : 1),
|
|
6452
6535
|
width: w,
|
|
6453
6536
|
height: h,
|
|
6454
6537
|
sampled: n
|
|
@@ -7312,62 +7395,6 @@ function propose(input) {
|
|
|
7312
7395
|
};
|
|
7313
7396
|
}
|
|
7314
7397
|
|
|
7315
|
-
// src/estate/index.ts
|
|
7316
|
-
var ESTATE_VERSION = 1;
|
|
7317
|
-
var SIBLING_AT = 0.31;
|
|
7318
|
-
function emptyEstate() {
|
|
7319
|
-
return { version: ESTATE_VERSION, sites: [] };
|
|
7320
|
-
}
|
|
7321
|
-
function addSite(register, site) {
|
|
7322
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(site.id)) throw new Error(`site id "${site.id}" must be short kebab-case`);
|
|
7323
|
-
const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
|
|
7324
|
-
return { version: ESTATE_VERSION, sites };
|
|
7325
|
-
}
|
|
7326
|
-
function sharedParts(a, b, d = fingerprintDistance(a, b)) {
|
|
7327
|
-
const out = [];
|
|
7328
|
-
const close = (k) => d.parts[k] <= SHARED_PART;
|
|
7329
|
-
if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
|
|
7330
|
-
if (close("type")) {
|
|
7331
|
-
if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
|
|
7332
|
-
if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
|
|
7333
|
-
}
|
|
7334
|
-
if (close("ground")) out.push(`${groundName(a.ground)} ground`);
|
|
7335
|
-
if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
|
|
7336
|
-
if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
|
|
7337
|
-
if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
|
|
7338
|
-
if (close("layout")) out.push("running order");
|
|
7339
|
-
return out;
|
|
7340
|
-
}
|
|
7341
|
-
function compareToEstate(target2, register, options = {}) {
|
|
7342
|
-
const at = options.siblingAt ?? SIBLING_AT;
|
|
7343
|
-
return register.sites.filter((s) => s.id !== options.exclude && s.fingerprint).map((s) => {
|
|
7344
|
-
const d = fingerprintDistance(target2, s.fingerprint);
|
|
7345
|
-
return { id: s.id, distance: d.total, sibling: d.total < at, shared: sharedParts(target2, s.fingerprint, d) };
|
|
7346
|
-
}).sort((a, b) => a.distance - b.distance);
|
|
7347
|
-
}
|
|
7348
|
-
function estatePairs(register, siblingAt = SIBLING_AT) {
|
|
7349
|
-
const out = [];
|
|
7350
|
-
const s = register.sites.filter((site) => site.fingerprint);
|
|
7351
|
-
for (let i = 0; i < s.length; i += 1) {
|
|
7352
|
-
for (let j = i + 1; j < s.length; j += 1) {
|
|
7353
|
-
const d = fingerprintDistance(s[i].fingerprint, s[j].fingerprint);
|
|
7354
|
-
out.push({ a: s[i].id, b: s[j].id, distance: d.total, sibling: d.total < siblingAt, shared: sharedParts(s[i].fingerprint, s[j].fingerprint, d) });
|
|
7355
|
-
}
|
|
7356
|
-
}
|
|
7357
|
-
return out.sort((x, y) => x.distance - y.distance);
|
|
7358
|
-
}
|
|
7359
|
-
function siblingLine(briefs) {
|
|
7360
|
-
const d = [];
|
|
7361
|
-
for (const runs of briefs) {
|
|
7362
|
-
for (let i = 0; i < runs.length; i += 1) for (let j = i + 1; j < runs.length; j += 1) d.push(fingerprintDistance(runs[i], runs[j]).total);
|
|
7363
|
-
}
|
|
7364
|
-
if (d.length === 0) throw new Error("no null pages to measure the sibling line from");
|
|
7365
|
-
d.sort((a, b) => a - b);
|
|
7366
|
-
const mid = Math.floor(d.length / 2);
|
|
7367
|
-
const m = d.length % 2 ? d[mid] : (d[mid - 1] + d[mid]) / 2;
|
|
7368
|
-
return Math.round(m * 1e3) / 1e3;
|
|
7369
|
-
}
|
|
7370
|
-
|
|
7371
7398
|
// src/report/index.ts
|
|
7372
7399
|
var TELL_HEAVY = 3;
|
|
7373
7400
|
var MIN_DECIDED = 5;
|
|
@@ -7523,6 +7550,154 @@ function choiceOfShared(part) {
|
|
|
7523
7550
|
return /running order/.test(part) ? void 0 : "motif";
|
|
7524
7551
|
}
|
|
7525
7552
|
|
|
7553
|
+
// src/calibrate/index.ts
|
|
7554
|
+
var LABELS = ["ai", "ai-looking", "human"];
|
|
7555
|
+
var FLAGGED_TARGET = 0.9;
|
|
7556
|
+
var HUMAN_TYPICAL_TARGET = 0.1;
|
|
7557
|
+
var SURFACE = new Map(CATALOGUE.map((t) => [t.id, t.surface]));
|
|
7558
|
+
function designTells(tells) {
|
|
7559
|
+
return tells.filter((t) => SURFACE.has(t) && SURFACE.get(t) !== "copy");
|
|
7560
|
+
}
|
|
7561
|
+
function isFlagged(page) {
|
|
7562
|
+
return Boolean(page.typicality?.typical) || designTells(page.tells).length >= TELL_HEAVY;
|
|
7563
|
+
}
|
|
7564
|
+
var positive = (label) => label !== "human";
|
|
7565
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
7566
|
+
var share = (n, of) => `${n} of ${of} (${Math.round(100 * n / of)}%)`;
|
|
7567
|
+
function summarise(label, pages, typicality2) {
|
|
7568
|
+
const mine = pages.filter((p) => p.label === label);
|
|
7569
|
+
return {
|
|
7570
|
+
label,
|
|
7571
|
+
pages: mine.length,
|
|
7572
|
+
// A set measured partly against the null would count the same page two ways.
|
|
7573
|
+
flagged: mine.filter((p) => typicality2 ? isFlagged(p) : designTells(p.tells).length >= TELL_HEAVY).length,
|
|
7574
|
+
typical: typicality2 ? mine.filter((p) => p.typicality?.typical).length : null,
|
|
7575
|
+
tellHeavy: mine.filter((p) => designTells(p.tells).length >= TELL_HEAVY).length,
|
|
7576
|
+
blocking: mine.reduce((s, p) => s + p.blocking, 0)
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7579
|
+
function flaggedTarget(target2, s, missing) {
|
|
7580
|
+
const note = missing ? `; ${missing} not measured, counted neither way` : "";
|
|
7581
|
+
if (s.pages === 0) return { target: target2, met: null, detail: `no page measured${note}` };
|
|
7582
|
+
return { target: target2, met: s.flagged / s.pages >= FLAGGED_TARGET, detail: `${share(s.flagged, s.pages)} flagged, target ${FLAGGED_TARGET * 100}% or more${note}` };
|
|
7583
|
+
}
|
|
7584
|
+
function calibrate(pages, unmeasured = []) {
|
|
7585
|
+
const typicality2 = pages.length > 0 && pages.every((p) => p.typicality !== void 0);
|
|
7586
|
+
const labels = LABELS.map((l) => summarise(l, pages, typicality2));
|
|
7587
|
+
const byLabel = Object.fromEntries(labels.map((s) => [s.label, s]));
|
|
7588
|
+
const missing = (l) => unmeasured.filter((u) => u.label === l).length;
|
|
7589
|
+
const positives = pages.filter((p) => positive(p.label)).length;
|
|
7590
|
+
const fired = new Set(pages.flatMap((p) => p.tells));
|
|
7591
|
+
const tells = [...fired].map((tell) => {
|
|
7592
|
+
const hits = Object.fromEntries(LABELS.map((l) => [l, pages.filter((p) => p.label === l && p.tells.includes(tell)).length]));
|
|
7593
|
+
const on = hits.ai + hits["ai-looking"];
|
|
7594
|
+
return { tell, hits, precision: round2(on / (on + hits.human)), recall: positives ? round2(on / positives) : null };
|
|
7595
|
+
}).sort((a, b) => b.hits.ai + b.hits["ai-looking"] + b.hits.human - (a.hits.ai + a.hits["ai-looking"] + a.hits.human) || a.tell.localeCompare(b.tell));
|
|
7596
|
+
const human = byLabel.human;
|
|
7597
|
+
const humanNote = missing("human") ? `; ${missing("human")} not measured, counted neither way` : "";
|
|
7598
|
+
const targets = [
|
|
7599
|
+
flaggedTarget("1. Generated pages get caught", byLabel.ai, missing("ai")),
|
|
7600
|
+
human.pages === 0 ? { target: "2. Good human sites don't get caught", met: null, detail: `no page measured${humanNote}` } : {
|
|
7601
|
+
target: "2. Good human sites don't get caught",
|
|
7602
|
+
// A block hit fails it outright; without null models the other half is not known.
|
|
7603
|
+
met: human.blocking > 0 ? false : human.typical === null ? null : human.typical / human.pages <= HUMAN_TYPICAL_TARGET,
|
|
7604
|
+
detail: `${human.blocking} block hits, target 0; ${human.typical === null ? "typicality not measured (no null models)" : `${share(human.typical, human.pages)} typical, target ${HUMAN_TYPICAL_TARGET * 100}% or fewer`}${humanNote}`
|
|
7605
|
+
},
|
|
7606
|
+
flaggedTarget("3. Sites a person labelled AI-looking get caught", byLabel["ai-looking"], missing("ai-looking"))
|
|
7607
|
+
];
|
|
7608
|
+
return {
|
|
7609
|
+
pages: pages.length,
|
|
7610
|
+
typicality: typicality2,
|
|
7611
|
+
labels,
|
|
7612
|
+
tells,
|
|
7613
|
+
silent: CATALOGUE.map((t) => t.id).filter((id) => !fired.has(id)),
|
|
7614
|
+
targets,
|
|
7615
|
+
unmeasured
|
|
7616
|
+
};
|
|
7617
|
+
}
|
|
7618
|
+
function formatCalibration(c, notes = []) {
|
|
7619
|
+
const counts = c.labels.map((s) => `${s.pages} ${s.label}`).join(", ");
|
|
7620
|
+
const lines = [`craft calibrate: ${c.pages} pages measured (${counts})`, ...notes];
|
|
7621
|
+
if (!c.typicality) lines.push("No null models: flagged means tell-heavy only.");
|
|
7622
|
+
if (c.unmeasured.length) {
|
|
7623
|
+
lines.push("", `Not measured: ${c.unmeasured.length}. A page not measured never counts as a pass.`);
|
|
7624
|
+
for (const u of c.unmeasured) lines.push(` ${u.id} (${u.label}): ${u.reason}`);
|
|
7625
|
+
}
|
|
7626
|
+
lines.push("", "Targets");
|
|
7627
|
+
for (const t of c.targets) lines.push(` ${t.target.padEnd(50)} ${t.met === null ? "not known" : t.met ? "met" : "NOT MET"}`, ` ${t.detail}`);
|
|
7628
|
+
lines.push("", ` ${"label".padEnd(11)} ${"pages".padStart(5)} ${"flagged".padStart(8)} ${"typical".padStart(8)} ${"tell-heavy".padStart(11)} ${"blocking".padStart(9)}`);
|
|
7629
|
+
for (const s of c.labels) {
|
|
7630
|
+
lines.push(` ${s.label.padEnd(11)} ${String(s.pages).padStart(5)} ${String(s.flagged).padStart(8)} ${String(s.typical ?? "-").padStart(8)} ${String(s.tellHeavy).padStart(11)} ${String(s.blocking).padStart(9)}`);
|
|
7631
|
+
}
|
|
7632
|
+
lines.push("", "Per tell. A hit on ai or ai-looking counts for the tell; a hit on human counts against it.");
|
|
7633
|
+
lines.push(` ${"tell".padEnd(28)} ${"ai".padStart(7)} ${"ai-looking".padStart(10)} ${"human".padStart(7)} ${"precision".padStart(9)} ${"recall".padStart(7)}`);
|
|
7634
|
+
const of = (l) => c.labels.find((s) => s.label === l)?.pages ?? 0;
|
|
7635
|
+
for (const t of c.tells) {
|
|
7636
|
+
lines.push(
|
|
7637
|
+
` ${t.tell.padEnd(28)} ${`${t.hits.ai}/${of("ai")}`.padStart(7)} ${`${t.hits["ai-looking"]}/${of("ai-looking")}`.padStart(10)} ${`${t.hits.human}/${of("human")}`.padStart(7)} ${t.precision.toFixed(2).padStart(9)} ${(t.recall === null ? "-" : t.recall.toFixed(2)).padStart(7)}`
|
|
7638
|
+
);
|
|
7639
|
+
}
|
|
7640
|
+
if (!c.tells.length) lines.push(" no tell fired");
|
|
7641
|
+
lines.push("", `Never fired on this set: ${c.silent.length} of ${CATALOGUE.length} catalogue tells.`);
|
|
7642
|
+
return lines.join("\n");
|
|
7643
|
+
}
|
|
7644
|
+
|
|
7645
|
+
// src/estate/index.ts
|
|
7646
|
+
var ESTATE_VERSION = 1;
|
|
7647
|
+
var SIBLING_AT = 0.31;
|
|
7648
|
+
function emptyEstate() {
|
|
7649
|
+
return { version: ESTATE_VERSION, sites: [] };
|
|
7650
|
+
}
|
|
7651
|
+
function addSite(register, site) {
|
|
7652
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(site.id)) throw new Error(`site id "${site.id}" must be short kebab-case`);
|
|
7653
|
+
const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
|
|
7654
|
+
return { version: ESTATE_VERSION, sites };
|
|
7655
|
+
}
|
|
7656
|
+
function sharedParts(a, b, d = fingerprintDistance(a, b)) {
|
|
7657
|
+
const out = [];
|
|
7658
|
+
const close = (k) => d.parts[k] <= SHARED_PART;
|
|
7659
|
+
if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
|
|
7660
|
+
if (close("type")) {
|
|
7661
|
+
if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
|
|
7662
|
+
if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
|
|
7663
|
+
}
|
|
7664
|
+
if (close("ground")) out.push(`${groundName(a.ground)} ground`);
|
|
7665
|
+
if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
|
|
7666
|
+
if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
|
|
7667
|
+
if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
|
|
7668
|
+
if (close("layout")) out.push("running order");
|
|
7669
|
+
return out;
|
|
7670
|
+
}
|
|
7671
|
+
function compareToEstate(target2, register, options = {}) {
|
|
7672
|
+
const at = options.siblingAt ?? SIBLING_AT;
|
|
7673
|
+
return register.sites.filter((s) => s.id !== options.exclude && s.fingerprint).map((s) => {
|
|
7674
|
+
const d = fingerprintDistance(target2, s.fingerprint);
|
|
7675
|
+
return { id: s.id, distance: d.total, sibling: d.total < at, shared: sharedParts(target2, s.fingerprint, d) };
|
|
7676
|
+
}).sort((a, b) => a.distance - b.distance);
|
|
7677
|
+
}
|
|
7678
|
+
function estatePairs(register, siblingAt = SIBLING_AT) {
|
|
7679
|
+
const out = [];
|
|
7680
|
+
const s = register.sites.filter((site) => site.fingerprint);
|
|
7681
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
7682
|
+
for (let j = i + 1; j < s.length; j += 1) {
|
|
7683
|
+
const d = fingerprintDistance(s[i].fingerprint, s[j].fingerprint);
|
|
7684
|
+
out.push({ a: s[i].id, b: s[j].id, distance: d.total, sibling: d.total < siblingAt, shared: sharedParts(s[i].fingerprint, s[j].fingerprint, d) });
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
return out.sort((x, y) => x.distance - y.distance);
|
|
7688
|
+
}
|
|
7689
|
+
function siblingLine(briefs) {
|
|
7690
|
+
const d = [];
|
|
7691
|
+
for (const runs of briefs) {
|
|
7692
|
+
for (let i = 0; i < runs.length; i += 1) for (let j = i + 1; j < runs.length; j += 1) d.push(fingerprintDistance(runs[i], runs[j]).total);
|
|
7693
|
+
}
|
|
7694
|
+
if (d.length === 0) throw new Error("no null pages to measure the sibling line from");
|
|
7695
|
+
d.sort((a, b) => a - b);
|
|
7696
|
+
const mid = Math.floor(d.length / 2);
|
|
7697
|
+
const m = d.length % 2 ? d[mid] : (d[mid - 1] + d[mid]) / 2;
|
|
7698
|
+
return Math.round(m * 1e3) / 1e3;
|
|
7699
|
+
}
|
|
7700
|
+
|
|
7526
7701
|
// src/report/retrofit.ts
|
|
7527
7702
|
var HEADINGS = {
|
|
7528
7703
|
direction: "Decide first",
|
|
@@ -7604,6 +7779,7 @@ export {
|
|
|
7604
7779
|
ESTATE_VERSION,
|
|
7605
7780
|
EXIT_RATIO,
|
|
7606
7781
|
FINGERPRINT_VERSION,
|
|
7782
|
+
FLAGGED_TARGET,
|
|
7607
7783
|
FUNCTION_WORDS,
|
|
7608
7784
|
GROUND_SCALE,
|
|
7609
7785
|
GROUND_SHARED,
|
|
@@ -7614,8 +7790,11 @@ export {
|
|
|
7614
7790
|
HOUSE_BUDGET,
|
|
7615
7791
|
HOUSE_LOCKED,
|
|
7616
7792
|
HOUSE_TYPE,
|
|
7793
|
+
HUMAN_TYPICAL_TARGET,
|
|
7617
7794
|
IMAGERY_TELLS,
|
|
7618
7795
|
JSON_SCHEMA_VERSION,
|
|
7796
|
+
LABELS,
|
|
7797
|
+
LEXICAL_FEATURES,
|
|
7619
7798
|
LICENCES,
|
|
7620
7799
|
LIGHTNESS_CURVE,
|
|
7621
7800
|
MIN_DECIDED,
|
|
@@ -7656,6 +7835,7 @@ export {
|
|
|
7656
7835
|
STOCK_FIGURES,
|
|
7657
7836
|
STOCK_PHOTO_HOSTS,
|
|
7658
7837
|
STOCK_PHRASES,
|
|
7838
|
+
STUDY_WORDS,
|
|
7659
7839
|
TELL_HEAVY,
|
|
7660
7840
|
TRADE_NOUNS,
|
|
7661
7841
|
TYPE_STEPS,
|
|
@@ -7669,6 +7849,7 @@ export {
|
|
|
7669
7849
|
applySeverity,
|
|
7670
7850
|
auditSnapshot,
|
|
7671
7851
|
briefTerms,
|
|
7852
|
+
calibrate,
|
|
7672
7853
|
catalogueTable,
|
|
7673
7854
|
characterReport,
|
|
7674
7855
|
checkCopy,
|
|
@@ -7698,6 +7879,7 @@ export {
|
|
|
7698
7879
|
deltaEOkHex,
|
|
7699
7880
|
densityCss,
|
|
7700
7881
|
densityTokens,
|
|
7882
|
+
designTells,
|
|
7701
7883
|
emitCss,
|
|
7702
7884
|
emptyEstate,
|
|
7703
7885
|
estateComponentPairs,
|
|
@@ -7714,6 +7896,7 @@ export {
|
|
|
7714
7896
|
fluidSpace,
|
|
7715
7897
|
fluidType,
|
|
7716
7898
|
fontClass,
|
|
7899
|
+
formatCalibration,
|
|
7717
7900
|
formatClaims,
|
|
7718
7901
|
formatComparison,
|
|
7719
7902
|
formatCompetitor,
|
|
@@ -7738,8 +7921,10 @@ export {
|
|
|
7738
7921
|
isBlurb,
|
|
7739
7922
|
isCategoryLabel,
|
|
7740
7923
|
isCream,
|
|
7924
|
+
isFlagged,
|
|
7741
7925
|
jobWords,
|
|
7742
7926
|
layoutDistance,
|
|
7927
|
+
lexicalOf,
|
|
7743
7928
|
licencesJson,
|
|
7744
7929
|
makeSnapshot,
|
|
7745
7930
|
motionTokens,
|
|
@@ -7806,6 +7991,8 @@ export {
|
|
|
7806
7991
|
validateJob,
|
|
7807
7992
|
visibleText,
|
|
7808
7993
|
visualMeasures,
|
|
7994
|
+
vocabulary,
|
|
7995
|
+
vocabularyCounts,
|
|
7809
7996
|
wcagContrast,
|
|
7810
7997
|
wordListsJson
|
|
7811
7998
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domandigital/craft",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Doman Digital house craft standard as numbers: OKLCh colour ramps that snap to existing anchors, derived semantic tokens with measured WCAG and APCA contrast, one shared motion vocabulary for CSS and JS, and a base stylesheet. Zero dependencies.",
|
|
6
6
|
"license": "Apache-2.0",
|