@domandigital/craft 0.9.0 → 0.10.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/dist/index.js CHANGED
@@ -1782,10 +1782,14 @@ var COPY_TELLS = [
1782
1782
  surface: "copy",
1783
1783
  why: "Three fragments in a row ('Fast. Friendly. Local.' or 'No fuss. No jargon. Just results.') is the second wave's favourite rhythm.",
1784
1784
  fix: "Write one sentence that says which of the three is true, and how you know.",
1785
- detect: phrases([/(?:\b[A-Z][\w'’-]*(?:\s+[\w'’-]+){0,2}[.!]\s+){2}[A-Z][\w'’-]*(?:\s+[\w'’-]+){0,2}[.!](?=\s|$)/], quoted),
1785
+ // The fragments share one paragraph: "You don't. Again.", a blank line, then "It's frustrating." is two beats, not three.
1786
+ detect: phrases([/(?:\b[A-Z][\w'’-]*(?:[ \t]+[\w'’-]+){0,2}[.!](?:[ \t]+|[ \t]*\n(?![ \t]*\n)[ \t]*)){2}[A-Z][\w'’-]*(?:[ \t]+[\w'’-]+){0,2}[.!](?=\s|$)/], quoted),
1786
1787
  fixtures: {
1787
- flag: [f("content/home.md", "No fuss. No jargon. Just results.")],
1788
- pass: [f("content/home.md", "We fix boilers the same day, and we tell you the price before we start.")]
1788
+ flag: [f("content/home.md", "No fuss. No jargon. Just results."), f("content/home.md", "No fuss.\nNo jargon.\nJust results.")],
1789
+ pass: [
1790
+ f("content/home.md", "We fix boilers the same day, and we tell you the price before we start."),
1791
+ f("content/guide.md", "Your competitor appears in the top three. You don't. Again.\n\nIt's frustrating.")
1792
+ ]
1789
1793
  }
1790
1794
  },
1791
1795
  {
@@ -1804,6 +1808,14 @@ var COPY_TELLS = [
1804
1808
  }
1805
1809
  ];
1806
1810
  var ING_TAIL = /,\s+(?:ensuring|highlighting|underscoring|showcasing|reflecting|demonstrating|emphasi[sz]ing|fostering|cementing|solidifying|reinforcing|symboli[sz]ing|signalling|signaling)\b[^.!?\n]{0,80}/i;
1811
+ var PROMPT_CONTEXT = [
1812
+ /\b(?:the|this|that)\s+(?:attached|uploaded|provided)\s+(?:(?:Perplexity|ChatGPT|Claude|Gemini|Copilot)\s+)?(?:research|research file|research pack|source file|sources)\b/i,
1813
+ /\b(?:attached|uploaded)\s+(?:Perplexity|ChatGPT|Claude|Gemini|Copilot)\b/i,
1814
+ /\bthe\s+(?:attached|uploaded|provided)\s+brief\s+(?:asks|says|wants|requests|suggests|mentions)\b/i
1815
+ ];
1816
+ function gerundList(block, _match, index) {
1817
+ return /(?:^|[(,;:]\s*)\w+ing\b[^,.;()!?]{0,40}$/.test(block.text.slice(Math.max(0, index - 60), index));
1818
+ }
1807
1819
  var VAGUE_ATTRIBUTION = [
1808
1820
  /\b(?:studies|research|experts|scientists|surveys|statistics|the data)\s+(?:show|shows|suggest|suggests|agree|say|says|confirm|confirms|prove|proves|indicate|indicates)\b/i,
1809
1821
  /\b(?:many|most)\s+(?:experts|people|customers|professionals|homeowners)\s+(?:agree|say|believe)\b/i,
@@ -1889,8 +1901,10 @@ function inlineLabelHits(ctx) {
1889
1901
  run = [];
1890
1902
  };
1891
1903
  for (const line of lines) {
1892
- const m = /^\s*(?:[-*+]|\d+\.)\s+\*\*[^*]{1,40}:?\*\*:?\s+(\S.*)$/.exec(line);
1893
- if (m && m[1].trim().split(/\s+/).length <= 6) run.push(offset);
1904
+ const m = /^\s*[-*+]\s+\*\*([^*]{1,40}?):?\*\*:?\s+(\S.*)$/.exec(line);
1905
+ const data = m && /[\d£$€%`]|https?:|^\*\*/.test(m[2]);
1906
+ const named = m && /\S[A-Z]|[./]/.test(m[1]);
1907
+ if (m && !data && !named && m[2].trim().split(/\s+/).length <= 6) run.push(offset);
1894
1908
  else flush();
1895
1909
  offset += line.length + 1;
1896
1910
  }
@@ -1928,6 +1942,29 @@ var RESEARCH_TELLS = [
1928
1942
  ]
1929
1943
  }
1930
1944
  },
1945
+ {
1946
+ id: "prompt-context",
1947
+ name: "Prompt context",
1948
+ generation: 1,
1949
+ severity: "warn",
1950
+ surface: "copy",
1951
+ why: "'This article uses the attached Perplexity research file as the primary source', 'the attached research is right to say': the model talking about what it was given. A reader of a web page has no attachment. Found 38 times across five live DD articles in 2026-09, none caught by any other tell.",
1952
+ fix: "Delete the reference to the research or brief. Where it carried a claim, name the real source or cut the claim.",
1953
+ detect: phrases(PROMPT_CONTEXT, quoted),
1954
+ fixtures: {
1955
+ flag: [
1956
+ f("content/guide.md", "This article uses the attached Perplexity research file as the primary source."),
1957
+ f("content/guide.md", "The attached research is right to say these platforms suit early-stage businesses."),
1958
+ f("content/guide.md", "That is exactly how the uploaded research frames the category."),
1959
+ f("content/guide.md", "The attached brief asks for the point that most tradespeople miss.")
1960
+ ],
1961
+ pass: [
1962
+ f("content/guide.md", "We research every quote before we send it."),
1963
+ f("app/email.tsx", `export const Invoice = () => <p>Your invoice is attached.</p>;`),
1964
+ f("content/guide.md", "BrightLocal's research shows that reviews drive 20% of local ranking.")
1965
+ ]
1966
+ }
1967
+ },
1931
1968
  {
1932
1969
  id: "placeholder",
1933
1970
  name: "Unfilled placeholder",
@@ -1980,7 +2017,11 @@ var RESEARCH_TELLS = [
1980
2017
  pass: [
1981
2018
  f("content/home.md", "- Pages load in under a second\n- Card details never touch our server\n- Support by phone until 8pm\n"),
1982
2019
  f("content/home.md", "- **Speed:** Faster pages\n- **Security:** Safer data\n"),
1983
- f("content/guide.md", "- **GDC**: The statutory regulator for all dental professionals.\n- **GCC**: The statutory regulator for chiropractors in the UK.\n- **CQC**: The independent regulator of health and social care in England.\n")
2020
+ f("content/guide.md", "- **GDC**: The statutory regulator for all dental professionals.\n- **GCC**: The statutory regulator for chiropractors in the UK.\n- **CQC**: The independent regulator of health and social care in England.\n"),
2021
+ f("content/guide.md", "- **Per page**: \xA3100-\xA3200 per page\n- **Hourly**: \xA350-\xA375/hour\n- **Day rate**: \xA3350-\xA3500\n"),
2022
+ f("content/guide.md", "- **Law Society Find a Solicitor:** For solicitors\n- **AccountingWEB / ICAEW directories:** For accountants\n- **NHS.uk:** For NHS services\n"),
2023
+ f("content/guide.md", "1. **Visit PageSpeed Insights** and paste the address\n2. **Enter their URL** and run it\n3. **Note their scores** for later\n"),
2024
+ f("content/guide.md", "- **Structural problems:** **REBUILD.**\n- **Messaging issues:** **REFRESH.**\n- **Neither:** **LEAVE IT.**\n")
1984
2025
  ]
1985
2026
  }
1986
2027
  },
@@ -1992,7 +2033,7 @@ var RESEARCH_TELLS = [
1992
2033
  surface: "copy",
1993
2034
  why: "'..., ensuring peace of mind', '..., highlighting our commitment': a participle tacked on the end that claims significance and names no mechanism. One of the most common shapes in the Wikipedia guide to AI writing.",
1994
2035
  fix: "Cut the tail, or replace it with the mechanism: 'so you can drop the car off before work'.",
1995
- detect: phrases([ING_TAIL], (m) => quoted(m.replace(/^,\s*/, ""))),
2036
+ detect: phrases([ING_TAIL], (m) => quoted(m.replace(/^,\s*/, "")), void 0, gerundList),
1996
2037
  fixtures: {
1997
2038
  flag: [
1998
2039
  f("content/home.md", "Every engineer is Gas Safe registered, ensuring complete peace of mind."),
@@ -2000,7 +2041,8 @@ var RESEARCH_TELLS = [
2000
2041
  ],
2001
2042
  pass: [
2002
2043
  f("content/home.md", "We open at 7am, so you can drop the car off before work."),
2003
- f("content/home.md", "Engineers arrive by 9, carrying the parts for most boilers.")
2044
+ f("content/home.md", "Engineers arrive by 9, carrying the parts for most boilers."),
2045
+ f("content/guide.md", "Many items (claiming your GBP, ensuring NAP consistency, writing title tags) are straightforward.")
2004
2046
  ]
2005
2047
  }
2006
2048
  },
@@ -2250,6 +2292,7 @@ function repeatHits(ctx) {
2250
2292
  for (const s of sentences(doc2)) {
2251
2293
  const words2 = normal(s.text);
2252
2294
  if (words2.length < RUN || !/[.!?]["'’”)]*$/.test(s.text)) continue;
2295
+ if (/^\s*\[\^[\w-]+\]:/.test(s.text)) continue;
2253
2296
  const key = words2.join(" ");
2254
2297
  const earlier = whole.get(key);
2255
2298
  if (earlier) {
@@ -2447,7 +2490,15 @@ var DENSITY_TELLS = [
2447
2490
  f2("content/proposal.md", doc("We check every valve before the engineer leaves the house.\n\nMore here.\n\nWe check every valve before the engineer leaves the house.")),
2448
2491
  f2("content/proposal.md", doc("We check every valve before the engineer leaves the house.\n\nBy then we check every valve before the engineer leaves the site."))
2449
2492
  ],
2450
- pass: [f2("content/proposal.md", doc("We check every valve before the engineer leaves the house.\n\nThe engineer leaves once the customer has signed the sheet."))]
2493
+ pass: [
2494
+ f2("content/proposal.md", doc("We check every valve before the engineer leaves the house.\n\nThe engineer leaves once the customer has signed the sheet.")),
2495
+ f2(
2496
+ "content/guide.md",
2497
+ doc(
2498
+ "The Independent Healthcare Providers Network Private Patient Insight Report 2024 found that private demand rose.[^1]\n\n[^1]: Independent Healthcare Providers Network, Private Patient Insight Report 2024, published online."
2499
+ )
2500
+ )
2501
+ ]
2451
2502
  }
2452
2503
  },
2453
2504
  {
@@ -3484,6 +3535,9 @@ var HOUSE = {
3484
3535
  "closing-summary": { tier: "review", label: "closing summary" },
3485
3536
  "false-range": { tier: "review", label: "false range" },
3486
3537
  "placeholder": { tier: "review", label: "unfilled placeholder" },
3538
+ // Every estate hit so far is real (38 in five DD articles, 2026-09-24), but
3539
+ // a proposal can honestly say "the attached research". Review until measured there.
3540
+ "prompt-context": { tier: "review", label: "prompt context: the model naming its own inputs" },
3487
3541
  "question-reveal": { tier: "review", label: "staged reveal" },
3488
3542
  "inline-label-list": { tier: "review", label: "bold-label bullets" },
3489
3543
  "phrase-density": { tier: "review", label: "density" },
@@ -4166,7 +4220,7 @@ var RENDERED_PATHS = {
4166
4220
  };
4167
4221
 
4168
4222
  // src/character/check.ts
4169
- var CATALOGUE_VERSION = "2026.09.7";
4223
+ var CATALOGUE_VERSION = "2026.09.8";
4170
4224
  var CATALOGUE = [...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map(
4171
4225
  (t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t
4172
4226
  );
@@ -4431,6 +4485,120 @@ function formatComparison(result, beforePath, afterPath) {
4431
4485
  return lines.join("\n");
4432
4486
  }
4433
4487
 
4488
+ // src/character/claims.ts
4489
+ var CLAIM_KINDS = /* @__PURE__ */ new Set(["money", "number", "date"]);
4490
+ var PROPER = "[A-Z][\\w&.\u2019'-]*(?:\\s+(?:of|and|&|the|for)?\\s*[A-Z][\\w&.\u2019'-]*)*";
4491
+ var NOT_A_SOURCE = /^(?:We|I|It|This|That|These|Those|They|He|She|You|Our|Your|The|A|An|Each|Every|Most|Many|Some|One|Research|Studies|Experts|Data|Analysis|If|When|So|But|And|Or|In|On|At|From|For|By|As|Since|After|Before|Across|Over|Under|Its|Their|External|Source|UK|US|EU|Sometimes|Public|Recent|Industry|Independent|What|How|Why|Who|Where|Businesses|Customers|Clients|Patients|Consumers|People|Users|Online)$/;
4492
+ var NOUN = "study|survey|report|research|analysis|benchmark|benchmarks|guidelines|docs|documentation|help centre|help center|pricing page|figures|statistics|case study";
4493
+ var OWNED_NOUN = "guide|audit|support|pricing|data|site|website|blog";
4494
+ var VERB = "found|finds|reports?|reported|says|said|states?|stated|estimates?|estimated|recommends?|publishes|published|advertises|advertised|shows|showed|claims";
4495
+ var ATTRIBUTION = [
4496
+ new RegExp(`\\b[Aa]ccording to\\s+(?:the\\s+)?(${PROPER})`, "g"),
4497
+ new RegExp(`\\b(?:${NOUN})\\s+(?:by|from|commissioned by)\\s+(?:the\\s+)?(${PROPER})`, "g"),
4498
+ // "Akamai’s 2017 retail study", "the 2020 Deloitte study", "Bark’s UK guide", "Wix’s own developer guidelines".
4499
+ new RegExp(`\\b(${PROPER})(?:(?:\u2019s|'s)\\s+(?:own\\s+)?(?:\\d{4}\\s+)?(?:[A-Za-z]+\\s+)?(?:${NOUN}|${OWNED_NOUN})|\\s+(?:\\d{4}\\s+)?(?:[a-z]+\\s+)?(?:${NOUN}))\\b`, "g"),
4500
+ // "Google also reported", "Shopify UK currently advertises".
4501
+ new RegExp(`\\b(${PROPER})\\s+(?:also\\s+|now\\s+|currently\\s+|still\\s+)?(?:${VERB})\\b`, "g"),
4502
+ /\((?:[Ss]ources?:\s*|via\s+(?=[A-Z]))([^)]+)\)/g,
4503
+ /^\s*(?:source|sources)\s*:\s*(.+)$/gim
4504
+ ];
4505
+ var LINK2 = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)|\bhttps?:\/\/[^\s<>"')\]]+/g;
4506
+ function cleanName(raw) {
4507
+ const words2 = raw.trim().replace(/[.,;:]+$/, "").split(/\s+/);
4508
+ while (words2.length && NOT_A_SOURCE.test(words2[0])) words2.shift();
4509
+ return words2.join(" ").replace(/(?:’s|'s)(?=\s|$).*$/, "").trim();
4510
+ }
4511
+ var IMPERATIVE = /^(?:Add|Use|Check|Make|Get|Set|Include|Create|Update|List|Keep|Ask|Write|Track|Test|Claim|Build|Link|Submit|Verify|Review|Fix|Remove|Upload|Respond|Reply|Choose|Pick|Start|Run|Open|Install|Connect|Enable|Turn|Put|Give|Send|Book|Call|Read|See|Try|Compare|Aim|Target|Avoid|Post|Share|Mark|Tag)$/;
4512
+ var atStart = (sentence, index) => /^[\s*_"“”’(>#|-]*(?:\d{1,3}[.)]\s*)?[\s*_]*(?:[\w ]+:\**\s*)?$/.test(sentence.slice(0, index)) || /\|\s*\**$/.test(sentence.slice(0, index));
4513
+ function namedSources(sentence) {
4514
+ const found = /* @__PURE__ */ new Set();
4515
+ ATTRIBUTION.forEach((pattern, i) => {
4516
+ for (const m of sentence.matchAll(pattern)) {
4517
+ const raw = m[1].trim();
4518
+ const word = !/\s/.test(raw) && !/[’']s$/.test(m[0].split(/\s+/)[0]);
4519
+ if (i === 2 && word && atStart(sentence, m.index ?? 0)) continue;
4520
+ if (IMPERATIVE.test(raw.split(/\s+/)[0]) && atStart(sentence, m.index ?? 0)) continue;
4521
+ const name = cleanName(raw);
4522
+ if (!name || name.length < 2 || !/\s/.test(name) && /[a-z]ing$/.test(name)) continue;
4523
+ found.add(name);
4524
+ }
4525
+ });
4526
+ for (const m of sentence.matchAll(LINK2)) found.add(m[2] ?? m[0]);
4527
+ return [...found];
4528
+ }
4529
+ var FOOTNOTE_REF = /\[\^([\w-]+)\](?!:)/g;
4530
+ var FOOTNOTE_DEF = /^[ \t]*\[\^([\w-]+)\]:[ \t]*(.+)$/gm;
4531
+ var ASSUMPTION = /^(?:\*\*)?(?:assume|suppose|imagine|say|let's say|for example, say|picture)\b/i;
4532
+ var LEAD = /^\s*(?:#{1,6}\s+|>\s*|[-*+]\s+|\d+[.)]\s+)*/;
4533
+ function sentences2(text) {
4534
+ const out = [];
4535
+ let start = 0;
4536
+ let paragraph = 0;
4537
+ const push = (end) => {
4538
+ const raw = text.slice(start, end);
4539
+ const lead = raw.length - raw.trimStart().length;
4540
+ if (raw.trim()) out.push({ text: raw.trim(), offset: start + lead, paragraph });
4541
+ };
4542
+ for (const m of text.matchAll(/(?<!(?:^|\n)[ \t]*\d{1,3})[.!?](?:\[\^[\w-]+\])*(?=["”’)]?[ \t]+["“(*]*[A-Z0-9£$€])|\n+/g)) {
4543
+ const end = (m.index ?? 0) + m[0].length;
4544
+ push(end);
4545
+ start = end;
4546
+ if (/\n\s*\n/.test(m[0]) || /^\s*(?:#|\|)/.test(text.slice(end))) paragraph += 1;
4547
+ }
4548
+ push(text.length);
4549
+ return out;
4550
+ }
4551
+ var MAX = 180;
4552
+ var trim = (s) => s.length > MAX ? `${s.slice(0, MAX - 1).trimEnd()}\u2026` : s;
4553
+ function findClaims(files) {
4554
+ const claims = [];
4555
+ for (const file of files) {
4556
+ for (const block of extractCopy(file)) {
4557
+ const notes = new Map([...block.text.matchAll(FOOTNOTE_DEF)].map((m) => [m[1], m[2].trim()]));
4558
+ let carried;
4559
+ for (const s of sentences2(block.text)) {
4560
+ if (/^\s*#{1,6}\s/.test(s.text) || /^\s*\[\^[\w-]+\]:/.test(s.text)) continue;
4561
+ const refs = [...s.text.matchAll(FOOTNOTE_REF)].map((m) => notes.get(m[1]) ?? `footnote ${m[1]}`);
4562
+ const text = s.text.replace(FOOTNOTE_REF, "");
4563
+ const body = text.replace(LEAD, "");
4564
+ const own = [...namedSources(text), ...refs];
4565
+ if (own.length) carried = { paragraph: s.paragraph, sources: own };
4566
+ if (ASSUMPTION.test(body)) continue;
4567
+ const facts = protectedFacts(body).filter((f4) => CLAIM_KINDS.has(f4.kind));
4568
+ if (facts.length === 0 && own.length === 0) continue;
4569
+ const nearby = own.length === 0 && carried?.paragraph === s.paragraph ? carried.sources : void 0;
4570
+ const at = block.offset + s.offset;
4571
+ claims.push({
4572
+ path: file.path,
4573
+ line: file.text.slice(0, at).split("\n").length,
4574
+ sentence: trim(text.replace(/\s+/g, " ")),
4575
+ facts,
4576
+ sources: own,
4577
+ ...nearby ? { nearby } : {}
4578
+ });
4579
+ }
4580
+ }
4581
+ }
4582
+ return { files: files.length, claims, unsourced: claims.filter((c) => c.sources.length === 0).length };
4583
+ }
4584
+ function formatClaims(report2, label = "craft copy claims") {
4585
+ const { claims, unsourced } = report2;
4586
+ if (claims.length === 0) return `${label}: no prices, figures, dates or named sources in ${report2.files} file${report2.files === 1 ? "" : "s"}.`;
4587
+ const lines = [
4588
+ `${label}: ${claims.length} claim${claims.length === 1 ? "" : "s"} to check, ${unsourced} with no source named.`,
4589
+ " Check each against its primary source before it ships. Sourced means a source is named, not that it agrees."
4590
+ ];
4591
+ for (const c of claims) {
4592
+ const what = [
4593
+ ...c.facts.map((f4) => f4.value),
4594
+ ...c.sources.map((s) => `source: ${s}`),
4595
+ ...c.nearby ? [`${c.nearby.join(", ")} named earlier in the paragraph: does it cover this?`] : []
4596
+ ].join(" \xB7 ");
4597
+ lines.push("", ` ${c.path}:${c.line} ${c.sources.length ? "sourced " : "UNSOURCED"} ${what}`, ` ${c.sentence}`);
4598
+ }
4599
+ return lines.join("\n");
4600
+ }
4601
+
4434
4602
  // src/fingerprint/index.ts
4435
4603
  var FINGERPRINT_VERSION = 1;
4436
4604
  var CHROMATIC = 0.03;
@@ -5393,6 +5561,7 @@ export {
5393
5561
  estatePairs,
5394
5562
  exitDuration,
5395
5563
  extractHtml,
5564
+ findClaims,
5396
5565
  findColours,
5397
5566
  fingerprint,
5398
5567
  fingerprintDistance,
@@ -5400,6 +5569,7 @@ export {
5400
5569
  fluidSpace,
5401
5570
  fluidType,
5402
5571
  fontClass,
5572
+ formatClaims,
5403
5573
  formatComparison,
5404
5574
  formatHex,
5405
5575
  formatReport,
@@ -5416,6 +5586,7 @@ export {
5416
5586
  isCream,
5417
5587
  makeSnapshot,
5418
5588
  motionTokens,
5589
+ namedSources,
5419
5590
  nearest,
5420
5591
  normaliseFamily,
5421
5592
  nullPrompt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@domandigital/craft",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",
package/word-lists.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "catalogueVersion": "2026.09.7",
2
+ "catalogueVersion": "2026.09.8",
3
3
  "lists": {
4
4
  "aiWords": [
5
5
  "tapestry",
@@ -1,16 +0,0 @@
1
- import {
2
- formatMatches,
3
- formatPairs,
4
- loadEstate,
5
- runEstate
6
- } from "./chunk-5LI2VFN5.js";
7
- import "./chunk-VKQDOUHO.js";
8
- import "./chunk-YLQE35PN.js";
9
- import "./chunk-EHNEGKFR.js";
10
- import "./chunk-ZT3WLN7R.js";
11
- export {
12
- formatMatches,
13
- formatPairs,
14
- loadEstate,
15
- runEstate
16
- };
@@ -1,15 +0,0 @@
1
- import {
2
- describeFingerprint,
3
- merge,
4
- runAudit
5
- } from "./chunk-DAENYJUO.js";
6
- import "./chunk-BYY32NIN.js";
7
- import "./chunk-VKQDOUHO.js";
8
- import "./chunk-YLQE35PN.js";
9
- import "./chunk-EHNEGKFR.js";
10
- import "./chunk-ZT3WLN7R.js";
11
- export {
12
- describeFingerprint,
13
- merge,
14
- runAudit
15
- };