@opacedev/astro-ai-content-checker 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/LICENSE +9 -0
  3. package/README.md +236 -0
  4. package/SBOM.cdx.json +172 -0
  5. package/SECURITY.md +9 -0
  6. package/THIRD_PARTY_NOTICES.md +36 -0
  7. package/dist/build-report-html.d.ts +3 -0
  8. package/dist/highlight.js +155 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +43 -0
  11. package/dist/options.d.ts +21 -0
  12. package/dist/options.js +6 -0
  13. package/dist/receipt.d.ts +15 -0
  14. package/dist/receipt.js +100 -0
  15. package/dist/report.d.ts +55 -0
  16. package/dist/report.js +12 -0
  17. package/dist/sections.js +181 -0
  18. package/dist/share.d.ts +47 -0
  19. package/dist/share.js +72 -0
  20. package/dist/shared-EX54KI6H.js +1503 -0
  21. package/dist/shared-MMXWTX2U.js +43 -0
  22. package/dist/toolbar.js +13478 -0
  23. package/node_modules/@opacedev/ai-content-checker-browser/LICENSE +21 -0
  24. package/node_modules/@opacedev/ai-content-checker-browser/README.md +145 -0
  25. package/node_modules/@opacedev/ai-content-checker-browser/dist/dom/visible-text.d.ts +14 -0
  26. package/node_modules/@opacedev/ai-content-checker-browser/dist/dom/visible-text.js +47 -0
  27. package/node_modules/@opacedev/ai-content-checker-browser/dist/index.d.ts +3 -0
  28. package/node_modules/@opacedev/ai-content-checker-browser/dist/index.js +99 -0
  29. package/node_modules/@opacedev/ai-content-checker-browser/dist/worker/client.d.ts +12 -0
  30. package/node_modules/@opacedev/ai-content-checker-browser/dist/worker/client.js +17 -0
  31. package/node_modules/@opacedev/ai-content-checker-browser/dist/worker/entry.d.ts +1 -0
  32. package/node_modules/@opacedev/ai-content-checker-browser/dist/worker/entry.js +3984 -0
  33. package/node_modules/@opacedev/ai-content-checker-browser/dist/worker/protocol.d.ts +30 -0
  34. package/node_modules/@opacedev/ai-content-checker-browser/dist/worker/protocol.js +1 -0
  35. package/node_modules/@opacedev/ai-content-checker-browser/package.json +60 -0
  36. package/node_modules/@opacedev/ai-content-checker-contracts/LICENSE +21 -0
  37. package/node_modules/@opacedev/ai-content-checker-contracts/README.md +93 -0
  38. package/node_modules/@opacedev/ai-content-checker-contracts/dist/index.d.ts +58 -0
  39. package/node_modules/@opacedev/ai-content-checker-contracts/dist/index.js +17 -0
  40. package/node_modules/@opacedev/ai-content-checker-contracts/package.json +57 -0
  41. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/analysis-request.schema.d.ts +44 -0
  42. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/analysis-result.schema.d.ts +122 -0
  43. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/candidate.schema.d.ts +81 -0
  44. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/capabilities.schema.d.ts +24 -0
  45. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/checker-result.schema.d.ts +342 -0
  46. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/common.schema.d.ts +10 -0
  47. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/envelope.schema.d.ts +14 -0
  48. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/gate-result.schema.d.ts +19 -0
  49. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/integrity-receipt.schema.d.ts +122 -0
  50. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/job.schema.d.ts +188 -0
  51. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/method-result.schema.d.ts +39 -0
  52. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/pattern-finding.schema.d.ts +26 -0
  53. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/protected-span.schema.d.ts +35 -0
  54. package/node_modules/@opacedev/ai-content-checker-contracts/src/generated/rewrite-request.schema.d.ts +65 -0
  55. package/node_modules/@opacedev/ai-content-checker-contracts/src/index.ts +63 -0
  56. package/node_modules/@opacedev/ai-content-checker-contracts/src/semantic-validation.mjs +32 -0
  57. package/node_modules/@opacedev/ai-content-checker-core/LICENSE +21 -0
  58. package/node_modules/@opacedev/ai-content-checker-core/README.md +186 -0
  59. package/node_modules/@opacedev/ai-content-checker-core/dist/bundle.js +4744 -0
  60. package/node_modules/@opacedev/ai-content-checker-core/dist/capabilities.d.ts +16 -0
  61. package/node_modules/@opacedev/ai-content-checker-core/dist/capabilities.js +7 -0
  62. package/node_modules/@opacedev/ai-content-checker-core/dist/diff/diff.d.ts +17 -0
  63. package/node_modules/@opacedev/ai-content-checker-core/dist/diff/diff.js +76 -0
  64. package/node_modules/@opacedev/ai-content-checker-core/dist/fixes/preview.d.ts +27 -0
  65. package/node_modules/@opacedev/ai-content-checker-core/dist/fixes/preview.js +47 -0
  66. package/node_modules/@opacedev/ai-content-checker-core/dist/gates/policy.d.ts +14 -0
  67. package/node_modules/@opacedev/ai-content-checker-core/dist/gates/policy.js +16 -0
  68. package/node_modules/@opacedev/ai-content-checker-core/dist/index.d.ts +15 -0
  69. package/node_modules/@opacedev/ai-content-checker-core/dist/index.js +15 -0
  70. package/node_modules/@opacedev/ai-content-checker-core/dist/inspect.d.ts +8 -0
  71. package/node_modules/@opacedev/ai-content-checker-core/dist/inspect.js +84 -0
  72. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-gb-v1.d.ts +12 -0
  73. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-gb-v1.js +68 -0
  74. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v2-data.d.ts +73 -0
  75. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v2-data.js +499 -0
  76. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v2.d.ts +76 -0
  77. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v2.js +1067 -0
  78. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v3-data.d.ts +165 -0
  79. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v3-data.js +614 -0
  80. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v3.d.ts +19 -0
  81. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v3.js +542 -0
  82. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v4-corpus.d.ts +40 -0
  83. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v4-corpus.js +40 -0
  84. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v4-data.d.ts +138 -0
  85. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v4-data.js +200 -0
  86. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v4.d.ts +71 -0
  87. package/node_modules/@opacedev/ai-content-checker-core/dist/patterns/en-signals-v4.js +388 -0
  88. package/node_modules/@opacedev/ai-content-checker-core/dist/protected/extract.d.ts +17 -0
  89. package/node_modules/@opacedev/ai-content-checker-core/dist/protected/extract.js +75 -0
  90. package/node_modules/@opacedev/ai-content-checker-core/dist/protected/validate.d.ts +3 -0
  91. package/node_modules/@opacedev/ai-content-checker-core/dist/protected/validate.js +17 -0
  92. package/node_modules/@opacedev/ai-content-checker-core/dist/provenance/c2pa-text.d.ts +55 -0
  93. package/node_modules/@opacedev/ai-content-checker-core/dist/provenance/c2pa-text.js +86 -0
  94. package/node_modules/@opacedev/ai-content-checker-core/dist/receipts/build.d.ts +48 -0
  95. package/node_modules/@opacedev/ai-content-checker-core/dist/receipts/build.js +61 -0
  96. package/node_modules/@opacedev/ai-content-checker-core/dist/report/checker-result.d.ts +241 -0
  97. package/node_modules/@opacedev/ai-content-checker-core/dist/report/checker-result.js +419 -0
  98. package/node_modules/@opacedev/ai-content-checker-core/dist/source/offsets.d.ts +34 -0
  99. package/node_modules/@opacedev/ai-content-checker-core/dist/source/offsets.js +51 -0
  100. package/node_modules/@opacedev/ai-content-checker-core/dist/source/utf8.d.ts +3 -0
  101. package/node_modules/@opacedev/ai-content-checker-core/dist/source/utf8.js +58 -0
  102. package/node_modules/@opacedev/ai-content-checker-core/dist/source/visible-text.d.ts +12 -0
  103. package/node_modules/@opacedev/ai-content-checker-core/dist/source/visible-text.js +30 -0
  104. package/node_modules/@opacedev/ai-content-checker-core/dist/unicode/data.d.ts +22 -0
  105. package/node_modules/@opacedev/ai-content-checker-core/dist/unicode/data.js +137 -0
  106. package/node_modules/@opacedev/ai-content-checker-core/dist/unicode/inspect.d.ts +16 -0
  107. package/node_modules/@opacedev/ai-content-checker-core/dist/unicode/inspect.js +84 -0
  108. package/node_modules/@opacedev/ai-content-checker-core/dist/verdict/combine.d.ts +162 -0
  109. package/node_modules/@opacedev/ai-content-checker-core/dist/verdict/combine.js +501 -0
  110. package/node_modules/@opacedev/ai-content-checker-core/package.json +62 -0
  111. package/node_modules/canonicalize/LICENSE +201 -0
  112. package/node_modules/canonicalize/README.md +65 -0
  113. package/node_modules/canonicalize/bin/canonicalize.js +17 -0
  114. package/node_modules/canonicalize/lib/canonicalize.d.ts +2 -0
  115. package/node_modules/canonicalize/lib/canonicalize.js +74 -0
  116. package/node_modules/canonicalize/package.json +64 -0
  117. package/node_modules/entities/LICENSE +11 -0
  118. package/node_modules/entities/lib/decode.d.ts +211 -0
  119. package/node_modules/entities/lib/decode.d.ts.map +1 -0
  120. package/node_modules/entities/lib/decode.js +536 -0
  121. package/node_modules/entities/lib/decode.js.map +1 -0
  122. package/node_modules/entities/lib/decode_codepoint.d.ts +19 -0
  123. package/node_modules/entities/lib/decode_codepoint.d.ts.map +1 -0
  124. package/node_modules/entities/lib/decode_codepoint.js +76 -0
  125. package/node_modules/entities/lib/decode_codepoint.js.map +1 -0
  126. package/node_modules/entities/lib/encode.d.ts +22 -0
  127. package/node_modules/entities/lib/encode.d.ts.map +1 -0
  128. package/node_modules/entities/lib/encode.js +77 -0
  129. package/node_modules/entities/lib/encode.js.map +1 -0
  130. package/node_modules/entities/lib/escape.d.ts +43 -0
  131. package/node_modules/entities/lib/escape.d.ts.map +1 -0
  132. package/node_modules/entities/lib/escape.js +122 -0
  133. package/node_modules/entities/lib/escape.js.map +1 -0
  134. package/node_modules/entities/lib/esm/decode.d.ts +211 -0
  135. package/node_modules/entities/lib/esm/decode.d.ts.map +1 -0
  136. package/node_modules/entities/lib/esm/decode.js +496 -0
  137. package/node_modules/entities/lib/esm/decode.js.map +1 -0
  138. package/node_modules/entities/lib/esm/decode_codepoint.d.ts +19 -0
  139. package/node_modules/entities/lib/esm/decode_codepoint.d.ts.map +1 -0
  140. package/node_modules/entities/lib/esm/decode_codepoint.js +71 -0
  141. package/node_modules/entities/lib/esm/decode_codepoint.js.map +1 -0
  142. package/node_modules/entities/lib/esm/encode.d.ts +22 -0
  143. package/node_modules/entities/lib/esm/encode.d.ts.map +1 -0
  144. package/node_modules/entities/lib/esm/encode.js +69 -0
  145. package/node_modules/entities/lib/esm/encode.js.map +1 -0
  146. package/node_modules/entities/lib/esm/escape.d.ts +43 -0
  147. package/node_modules/entities/lib/esm/escape.d.ts.map +1 -0
  148. package/node_modules/entities/lib/esm/escape.js +116 -0
  149. package/node_modules/entities/lib/esm/escape.js.map +1 -0
  150. package/node_modules/entities/lib/esm/generated/decode-data-html.d.ts +3 -0
  151. package/node_modules/entities/lib/esm/generated/decode-data-html.d.ts.map +1 -0
  152. package/node_modules/entities/lib/esm/generated/decode-data-html.js +7 -0
  153. package/node_modules/entities/lib/esm/generated/decode-data-html.js.map +1 -0
  154. package/node_modules/entities/lib/esm/generated/decode-data-xml.d.ts +3 -0
  155. package/node_modules/entities/lib/esm/generated/decode-data-xml.d.ts.map +1 -0
  156. package/node_modules/entities/lib/esm/generated/decode-data-xml.js +7 -0
  157. package/node_modules/entities/lib/esm/generated/decode-data-xml.js.map +1 -0
  158. package/node_modules/entities/lib/esm/generated/encode-html.d.ts +8 -0
  159. package/node_modules/entities/lib/esm/generated/encode-html.d.ts.map +1 -0
  160. package/node_modules/entities/lib/esm/generated/encode-html.js +10 -0
  161. package/node_modules/entities/lib/esm/generated/encode-html.js.map +1 -0
  162. package/node_modules/entities/lib/esm/index.d.ts +96 -0
  163. package/node_modules/entities/lib/esm/index.d.ts.map +1 -0
  164. package/node_modules/entities/lib/esm/index.js +99 -0
  165. package/node_modules/entities/lib/esm/index.js.map +1 -0
  166. package/node_modules/entities/lib/esm/package.json +1 -0
  167. package/node_modules/entities/lib/generated/decode-data-html.d.ts +3 -0
  168. package/node_modules/entities/lib/generated/decode-data-html.d.ts.map +1 -0
  169. package/node_modules/entities/lib/generated/decode-data-html.js +9 -0
  170. package/node_modules/entities/lib/generated/decode-data-html.js.map +1 -0
  171. package/node_modules/entities/lib/generated/decode-data-xml.d.ts +3 -0
  172. package/node_modules/entities/lib/generated/decode-data-xml.d.ts.map +1 -0
  173. package/node_modules/entities/lib/generated/decode-data-xml.js +9 -0
  174. package/node_modules/entities/lib/generated/decode-data-xml.js.map +1 -0
  175. package/node_modules/entities/lib/generated/encode-html.d.ts +8 -0
  176. package/node_modules/entities/lib/generated/encode-html.d.ts.map +1 -0
  177. package/node_modules/entities/lib/generated/encode-html.js +12 -0
  178. package/node_modules/entities/lib/generated/encode-html.js.map +1 -0
  179. package/node_modules/entities/lib/index.d.ts +96 -0
  180. package/node_modules/entities/lib/index.d.ts.map +1 -0
  181. package/node_modules/entities/lib/index.js +126 -0
  182. package/node_modules/entities/lib/index.js.map +1 -0
  183. package/node_modules/entities/package.json +90 -0
  184. package/node_modules/entities/readme.md +122 -0
  185. package/package.json +91 -0
@@ -0,0 +1,3984 @@
1
+ // ../core/dist/bundle.js
2
+ var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
3
+ var decodeEntity = (raw) => {
4
+ if (raw[1] !== "#") return ENTITIES[raw.slice(1, -1)] ?? raw;
5
+ const value = raw[2]?.toLowerCase() === "x" ? Number.parseInt(raw.slice(3, -1), 16) : Number.parseInt(raw.slice(2, -1), 10);
6
+ return Number.isInteger(value) && value >= 0 && value <= 1114111 && !(value >= 55296 && value <= 57343) ? String.fromCodePoint(value) : raw;
7
+ };
8
+ function projectVisibleText(source, contentType) {
9
+ if (contentType === "plain_text") return { text: source, runs: source ? [{ visible_start_utf16: 0, visible_end_utf16: source.length, source_start_utf16: 0, source_end_utf16: source.length }] : [], limitations: [] };
10
+ if (contentType === "markdown") {
11
+ const text2 = source.replace(/```[\s\S]*?```/g, (m) => m).replace(/!?(\[([^\]]*)\])\(([^)]+)\)/g, (_m, _a, label) => label).replace(/(^|\s)[*_]{1,3}([^*_]+)[*_]{1,3}/g, "$1$2");
12
+ return { text: text2, runs: text2 ? [{ visible_start_utf16: 0, visible_end_utf16: text2.length, source_start_utf16: 0, source_end_utf16: source.length }] : [], limitations: ["Markdown mapping is projection-level; protected extractors retain exact source ranges."] };
13
+ }
14
+ let text = "", cursor = 0;
15
+ const runs = [];
16
+ const excluded = /<(script|style|template|noscript)\b[^>]*>[\s\S]*?<\/\1\s*>|<!--[\s\S]*?-->/gi;
17
+ const safe = source.replace(excluded, (m) => " ".repeat(m.length));
18
+ const token = /<[^>]*>|&(?:#x?[0-9a-f]+|[a-z]+);/gi;
19
+ let match;
20
+ const append = (value, start, end) => {
21
+ if (!value) return;
22
+ const v = text.length;
23
+ text += value;
24
+ runs.push({ visible_start_utf16: v, visible_end_utf16: text.length, source_start_utf16: start, source_end_utf16: end });
25
+ };
26
+ while (match = token.exec(safe)) {
27
+ append(safe.slice(cursor, match.index), cursor, match.index);
28
+ const raw = match[0];
29
+ if (/^<(br|\/?(?:p|div|section|article|li|h[1-6]|blockquote|tr))\b/i.test(raw)) append("\n", match.index, token.lastIndex);
30
+ else if (raw[0] === "&") append(decodeEntity(raw), match.index, token.lastIndex);
31
+ cursor = token.lastIndex;
32
+ }
33
+ append(safe.slice(cursor), cursor, safe.length);
34
+ return { text, runs, limitations: ["HTML projection is deterministic text extraction, not sanitisation."] };
35
+ }
36
+ var K = new Uint32Array([
37
+ 1116352408,
38
+ 1899447441,
39
+ 3049323471,
40
+ 3921009573,
41
+ 961987163,
42
+ 1508970993,
43
+ 2453635748,
44
+ 2870763221,
45
+ 3624381080,
46
+ 310598401,
47
+ 607225278,
48
+ 1426881987,
49
+ 1925078388,
50
+ 2162078206,
51
+ 2614888103,
52
+ 3248222580,
53
+ 3835390401,
54
+ 4022224774,
55
+ 264347078,
56
+ 604807628,
57
+ 770255983,
58
+ 1249150122,
59
+ 1555081692,
60
+ 1996064986,
61
+ 2554220882,
62
+ 2821834349,
63
+ 2952996808,
64
+ 3210313671,
65
+ 3336571891,
66
+ 3584528711,
67
+ 113926993,
68
+ 338241895,
69
+ 666307205,
70
+ 773529912,
71
+ 1294757372,
72
+ 1396182291,
73
+ 1695183700,
74
+ 1986661051,
75
+ 2177026350,
76
+ 2456956037,
77
+ 2730485921,
78
+ 2820302411,
79
+ 3259730800,
80
+ 3345764771,
81
+ 3516065817,
82
+ 3600352804,
83
+ 4094571909,
84
+ 275423344,
85
+ 430227734,
86
+ 506948616,
87
+ 659060556,
88
+ 883997877,
89
+ 958139571,
90
+ 1322822218,
91
+ 1537002063,
92
+ 1747873779,
93
+ 1955562222,
94
+ 2024104815,
95
+ 2227730452,
96
+ 2361852424,
97
+ 2428436474,
98
+ 2756734187,
99
+ 3204031479,
100
+ 3329325298
101
+ ]);
102
+ var rotr = (x, n) => x >>> n | x << 32 - n;
103
+ function utf8Bytes(value) {
104
+ return new TextEncoder().encode(value);
105
+ }
106
+ function sha256Hex(value) {
107
+ const source = utf8Bytes(value);
108
+ const bitLength = source.length * 8;
109
+ const paddedLength = Math.ceil((source.length + 9) / 64) * 64;
110
+ const bytes = new Uint8Array(paddedLength);
111
+ bytes.set(source);
112
+ bytes[source.length] = 128;
113
+ const view = new DataView(bytes.buffer);
114
+ view.setUint32(paddedLength - 4, bitLength >>> 0, false);
115
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296), false);
116
+ const h = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
117
+ const w = new Uint32Array(64);
118
+ for (let offset = 0; offset < paddedLength; offset += 64) {
119
+ for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4, false);
120
+ for (let i = 16; i < 64; i++) {
121
+ const a2 = w[i - 15], b2 = w[i - 2];
122
+ w[i] = (rotr(a2, 7) ^ rotr(a2, 18) ^ a2 >>> 3) + w[i - 16] + (rotr(b2, 17) ^ rotr(b2, 19) ^ b2 >>> 10) + w[i - 7] >>> 0;
123
+ }
124
+ let [a, b, c, d, e, f, g, hh] = h;
125
+ for (let i = 0; i < 64; i++) {
126
+ const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
127
+ const ch = e & f ^ ~e & g;
128
+ const t1 = hh + s1 + ch + K[i] + w[i] >>> 0;
129
+ const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
130
+ const maj = a & b ^ a & c ^ b & c;
131
+ const t2 = s0 + maj >>> 0;
132
+ hh = g;
133
+ g = f;
134
+ f = e;
135
+ e = d + t1 >>> 0;
136
+ d = c;
137
+ c = b;
138
+ b = a;
139
+ a = t1 + t2 >>> 0;
140
+ }
141
+ h[0] = h[0] + a >>> 0;
142
+ h[1] = h[1] + b >>> 0;
143
+ h[2] = h[2] + c >>> 0;
144
+ h[3] = h[3] + d >>> 0;
145
+ h[4] = h[4] + e >>> 0;
146
+ h[5] = h[5] + f >>> 0;
147
+ h[6] = h[6] + g >>> 0;
148
+ h[7] = h[7] + hh >>> 0;
149
+ }
150
+ return Array.from(h, (n) => n.toString(16).padStart(8, "0")).join("");
151
+ }
152
+ var prefixedSha256 = (value) => `sha256:${sha256Hex(value)}`;
153
+ var isHighSurrogate = (code) => code >= 55296 && code <= 56319;
154
+ var isLowSurrogate = (code) => code >= 56320 && code <= 57343;
155
+ function splitsSurrogatePair(text, offset) {
156
+ return offset > 0 && offset < text.length && isHighSurrogate(text.charCodeAt(offset - 1)) && isLowSurrogate(text.charCodeAt(offset));
157
+ }
158
+ function alignUtf16Range(text, start, end) {
159
+ return [splitsSurrogatePair(text, start) ? start - 1 : start, splitsSurrogatePair(text, end) ? end + 1 : end];
160
+ }
161
+ function utf16ToCodePointOffset(text, offset) {
162
+ if (!Number.isInteger(offset) || offset < 0 || offset > text.length) throw new RangeError("invalid_utf16_offset");
163
+ if (splitsSurrogatePair(text, offset)) throw new RangeError("split_surrogate");
164
+ return Array.from(text.slice(0, offset)).length;
165
+ }
166
+ function rangeFromUtf16(text, start, end) {
167
+ if (end <= start) throw new RangeError("empty_or_reversed_range");
168
+ const [alignedStart, alignedEnd] = alignUtf16Range(text, start, end);
169
+ return { start_utf16: alignedStart, end_utf16: alignedEnd, start_codepoint: utf16ToCodePointOffset(text, alignedStart), end_codepoint: utf16ToCodePointOffset(text, alignedEnd) };
170
+ }
171
+ var UNICODE_RULES_VERSION = "unicode:2026.08.2";
172
+ var MSG_INVISIBLE = (what) => `An invisible ${what} is present.`;
173
+ var MSG_BIDI = "A bidirectional formatting control is present.";
174
+ var BIDI_LIMIT = "Bidirectional controls are required for correct display of mixed right-to-left and left-to-right text.";
175
+ var CARRIER_RULES = [
176
+ // Soft hyphen and combining grapheme joiner
177
+ { from: 173, name: "SOFT HYPHEN", severity: "low", fix: "remove", message: "A discretionary soft hyphen is present." },
178
+ { from: 847, name: "COMBINING GRAPHEME JOINER", severity: "medium", fix: "remove", message: MSG_INVISIBLE("combining grapheme joiner"), limitation: "The combining grapheme joiner has rare legitimate uses in collation and diacritic ordering." },
179
+ // Script-specific format characters
180
+ { from: 1564, name: "ARABIC LETTER MARK", severity: "medium", fix: "review", message: MSG_BIDI, limitation: BIDI_LIMIT },
181
+ { from: 1807, name: "SYRIAC ABBREVIATION MARK", severity: "medium", fix: "review", message: "A Syriac abbreviation mark format character is present.", limitation: "This character is legitimate inside Syriac-script text." },
182
+ { from: 2192, to: 2193, name: (cp) => cp === 2192 ? "ARABIC POUND MARK ABOVE" : "ARABIC PIASTRE MARK ABOVE", severity: "medium", fix: "review", message: "An Arabic prepended format mark is present.", limitation: "This character is legitimate inside Arabic-script text." },
183
+ { from: 2274, name: "ARABIC DISPUTED END OF AYAH", severity: "medium", fix: "review", message: "An Arabic prepended format mark is present.", limitation: "This character is legitimate inside Arabic-script text." },
184
+ { from: 6155, to: 6157, name: (cp) => `MONGOLIAN FREE VARIATION SELECTOR-${cp - 6154}`, severity: "medium", fix: "remove", message: MSG_INVISIBLE("Mongolian free variation selector"), context: "variation", limitation: "Free variation selectors are legitimate inside Mongolian-script text." },
185
+ { from: 6158, name: "MONGOLIAN VOWEL SEPARATOR", severity: "medium", fix: "remove", message: MSG_INVISIBLE("Mongolian vowel separator"), limitation: "This character is legitimate inside Mongolian-script text." },
186
+ { from: 6159, name: "MONGOLIAN FREE VARIATION SELECTOR-4", severity: "medium", fix: "remove", message: MSG_INVISIBLE("Mongolian free variation selector"), context: "variation", limitation: "Free variation selectors are legitimate inside Mongolian-script text." },
187
+ // Zero-width and joining controls
188
+ { from: 8203, name: "ZERO WIDTH SPACE", severity: "medium", fix: "remove", message: MSG_INVISIBLE("zero-width space") },
189
+ { from: 8204, name: "ZERO WIDTH NON-JOINER", severity: "medium", fix: "remove", message: MSG_INVISIBLE("zero-width non-joiner"), context: "joiner", limitation: "The zero-width non-joiner is standard orthography in Persian and several Indic languages." },
190
+ { from: 8205, name: "ZERO WIDTH JOINER", severity: "medium", fix: "remove", message: MSG_INVISIBLE("zero-width joiner"), context: "joiner", limitation: "The zero-width joiner is standard in emoji sequences and several complex scripts." },
191
+ // Bidirectional controls
192
+ { from: 8206, name: "LEFT-TO-RIGHT MARK", severity: "medium", fix: "review", message: MSG_BIDI, limitation: BIDI_LIMIT },
193
+ { from: 8207, name: "RIGHT-TO-LEFT MARK", severity: "medium", fix: "review", message: MSG_BIDI, limitation: BIDI_LIMIT },
194
+ { from: 8234, to: 8238, name: (cp) => ["LEFT-TO-RIGHT EMBEDDING", "RIGHT-TO-LEFT EMBEDDING", "POP DIRECTIONAL FORMATTING", "LEFT-TO-RIGHT OVERRIDE", "RIGHT-TO-LEFT OVERRIDE"][cp - 8234], severity: "medium", fix: "review", message: MSG_BIDI, limitation: BIDI_LIMIT },
195
+ { from: 8294, to: 8297, name: (cp) => ["LEFT-TO-RIGHT ISOLATE", "RIGHT-TO-LEFT ISOLATE", "FIRST STRONG ISOLATE", "POP DIRECTIONAL ISOLATE"][cp - 8294], severity: "medium", fix: "review", message: MSG_BIDI, limitation: BIDI_LIMIT },
196
+ // Word joiner, invisible operators and deprecated format characters
197
+ { from: 8288, name: "WORD JOINER", severity: "medium", fix: "remove", message: MSG_INVISIBLE("word joiner") },
198
+ { from: 8289, to: 8292, name: (cp) => ["FUNCTION APPLICATION", "INVISIBLE TIMES", "INVISIBLE SEPARATOR", "INVISIBLE PLUS"][cp - 8289], severity: "medium", fix: "remove", message: MSG_INVISIBLE("mathematical operator character"), limitation: "Invisible operators are legitimate inside machine-generated mathematical notation." },
199
+ { from: 8298, to: 8303, name: (cp) => ["INHIBIT SYMMETRIC SWAPPING", "ACTIVATE SYMMETRIC SWAPPING", "INHIBIT ARABIC FORM SHAPING", "ACTIVATE ARABIC FORM SHAPING", "NATIONAL DIGIT SHAPES", "NOMINAL DIGIT SHAPES"][cp - 8298], severity: "medium", fix: "remove", message: "A deprecated invisible format character is present.", limitation: "These characters are deprecated by Unicode and have no place in modern interchange text." },
200
+ // BOM, interlinear annotation, replacement
201
+ { from: 65279, name: "BYTE ORDER MARK", severity: "low", fix: "remove", message: "A byte-order mark is present." },
202
+ { from: 65529, to: 65531, name: (cp) => ["INTERLINEAR ANNOTATION ANCHOR", "INTERLINEAR ANNOTATION SEPARATOR", "INTERLINEAR ANNOTATION TERMINATOR"][cp - 65529], severity: "medium", fix: "review", message: "An interlinear annotation control is present.", limitation: "Interlinear annotation controls are legitimate in Japanese ruby markup pipelines." },
203
+ { from: 65533, name: "REPLACEMENT CHARACTER", severity: "high", fix: "review", message: "A replacement character may indicate damaged text." },
204
+ // Supplementary-plane format characters
205
+ { from: 69821, name: "KAITHI NUMBER SIGN", severity: "medium", fix: "review", message: "A Kaithi number-sign format character is present.", limitation: "This character is legitimate inside Kaithi-script text." },
206
+ { from: 69837, name: "KAITHI NUMBER SIGN ABOVE", severity: "medium", fix: "review", message: "A Kaithi number-sign format character is present.", limitation: "This character is legitimate inside Kaithi-script text." },
207
+ // Tag characters: the classic covert payload carrier
208
+ { from: 917505, name: "LANGUAGE TAG", severity: "high", fix: "remove", message: "A deprecated invisible language tag is present." },
209
+ { from: 917536, to: 917631, name: (cp) => cp === 917631 ? "CANCEL TAG" : `TAG ${String.fromCodePoint(cp - 917504) === " " ? "SPACE" : `CHARACTER '${String.fromCodePoint(cp - 917504)}'`}`, severity: "high", fix: "remove", message: "An invisible tag character is present; tag runs are a known covert payload carrier.", limitation: "Tag characters have no legitimate use in ordinary interchange text outside emoji flag sequences." },
210
+ // Variation selectors
211
+ { from: 65024, to: 65039, name: (cp) => `VARIATION SELECTOR-${cp - 65024 + 1}`, severity: "medium", fix: "remove", message: MSG_INVISIBLE("variation selector"), context: "variation", limitation: "Variation selectors are legitimate after emoji-capable and CJK base characters." },
212
+ { from: 917760, to: 917999, name: (cp) => `VARIATION SELECTOR-${cp - 917760 + 17}`, severity: "medium", fix: "review", message: MSG_INVISIBLE("supplementary variation selector"), context: "variation_sup", limitation: "Supplementary variation selectors are legitimate after CJK ideographs to select registered glyph variants." },
213
+ // Space separators beyond U+0020
214
+ { from: 160, name: "NO-BREAK SPACE", severity: "note", fix: "space", message: "A non-breaking space is present.", limitation: "Non-breaking spaces are standard French and general typographic practice." },
215
+ { from: 5760, name: "OGHAM SPACE MARK", severity: "low", fix: "space", message: "An unusual space character is present.", limitation: "This character is legitimate inside Ogham-script text." },
216
+ { from: 8192, to: 8200, name: (cp) => ["EN QUAD", "EM QUAD", "EN SPACE", "EM SPACE", "THREE-PER-EM SPACE", "FOUR-PER-EM SPACE", "SIX-PER-EM SPACE", "FIGURE SPACE", "PUNCTUATION SPACE"][cp - 8192], severity: "low", fix: "space", message: "A typographic space that substitutes for an ordinary space is present.", limitation: "Fixed-width spaces are legitimate in carefully typeset material." },
217
+ { from: 8201, name: "THIN SPACE", severity: "note", fix: "space", message: "A thin space is present.", limitation: "Thin spaces are standard French and general typographic practice." },
218
+ { from: 8202, name: "HAIR SPACE", severity: "low", fix: "space", message: "A hair space that substitutes for an ordinary space is present.", limitation: "Hair spaces are legitimate in carefully typeset material." },
219
+ { from: 8239, name: "NARROW NO-BREAK SPACE", severity: "note", fix: "space", message: "A narrow no-break space is present.", limitation: "Narrow no-break spaces are standard French punctuation spacing and appear in Mongolian text." },
220
+ { from: 8287, name: "MEDIUM MATHEMATICAL SPACE", severity: "note", fix: "space", message: "A medium mathematical space is present.", limitation: "This space is legitimate inside typeset mathematical notation." },
221
+ { from: 12288, name: "IDEOGRAPHIC SPACE", severity: "note", fix: "space", message: "An ideographic space is present.", limitation: "Ideographic spaces are standard in Chinese and Japanese text." },
222
+ // Line and paragraph separators
223
+ { from: 8232, name: "LINE SEPARATOR", severity: "low", fix: "review", message: "A Unicode line separator is present instead of a conventional line break." },
224
+ { from: 8233, name: "PARAGRAPH SEPARATOR", severity: "low", fix: "review", message: "A Unicode paragraph separator is present instead of a conventional line break." }
225
+ ];
226
+ var CARRIER_RANGE_RULES = [
227
+ { from: 57344, to: 63743, name: "PRIVATE USE CHARACTER", severity: "medium", fix: "review", message: "A private-use area character is present; its meaning is defined only by a private agreement between sender and receiver.", limitation: "Private-use characters are legitimate in icon fonts and legacy vendor symbols such as platform logos; a single one is routine." },
228
+ { from: 983040, to: 1048573, name: "SUPPLEMENTARY PRIVATE USE CHARACTER", severity: "medium", fix: "review", message: "A supplementary private-use area character is present; its meaning is defined only by a private agreement between sender and receiver.", limitation: "Private-use characters are legitimate in icon fonts and legacy vendor symbols; a single one is routine." },
229
+ { from: 1048576, to: 1114109, name: "SUPPLEMENTARY PRIVATE USE CHARACTER", severity: "medium", fix: "review", message: "A supplementary private-use area character is present; its meaning is defined only by a private agreement between sender and receiver.", limitation: "Private-use characters are legitimate in icon fonts and legacy vendor symbols; a single one is routine." }
230
+ ];
231
+ var CONFUSABLES = /* @__PURE__ */ new Map([
232
+ // Cyrillic lower case
233
+ [1072, { name: "CYRILLIC SMALL LETTER A", latin: "a" }],
234
+ [1074, { name: "CYRILLIC SMALL LETTER VE", latin: "b" }],
235
+ [1077, { name: "CYRILLIC SMALL LETTER IE", latin: "e" }],
236
+ [1082, { name: "CYRILLIC SMALL LETTER KA", latin: "k" }],
237
+ [1084, { name: "CYRILLIC SMALL LETTER EM", latin: "m" }],
238
+ [1085, { name: "CYRILLIC SMALL LETTER EN", latin: "h" }],
239
+ [1086, { name: "CYRILLIC SMALL LETTER O", latin: "o" }],
240
+ [1088, { name: "CYRILLIC SMALL LETTER ER", latin: "p" }],
241
+ [1089, { name: "CYRILLIC SMALL LETTER ES", latin: "c" }],
242
+ [1090, { name: "CYRILLIC SMALL LETTER TE", latin: "t" }],
243
+ [1091, { name: "CYRILLIC SMALL LETTER U", latin: "y" }],
244
+ [1093, { name: "CYRILLIC SMALL LETTER HA", latin: "x" }],
245
+ [1109, { name: "CYRILLIC SMALL LETTER DZE", latin: "s" }],
246
+ [1110, { name: "CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I", latin: "i" }],
247
+ [1112, { name: "CYRILLIC SMALL LETTER JE", latin: "j" }],
248
+ [1121, { name: "CYRILLIC SMALL LETTER OMEGA", latin: "w" }],
249
+ [1141, { name: "CYRILLIC SMALL LETTER IZHITSA", latin: "v" }],
250
+ [1211, { name: "CYRILLIC SMALL LETTER SHHA", latin: "h" }],
251
+ [1281, { name: "CYRILLIC SMALL LETTER KOMI DE", latin: "d" }],
252
+ [1307, { name: "CYRILLIC SMALL LETTER QA", latin: "q" }],
253
+ [1309, { name: "CYRILLIC SMALL LETTER WE", latin: "w" }],
254
+ // Cyrillic upper case
255
+ [1029, { name: "CYRILLIC CAPITAL LETTER DZE", latin: "S" }],
256
+ [1030, { name: "CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I", latin: "I" }],
257
+ [1032, { name: "CYRILLIC CAPITAL LETTER JE", latin: "J" }],
258
+ [1040, { name: "CYRILLIC CAPITAL LETTER A", latin: "A" }],
259
+ [1042, { name: "CYRILLIC CAPITAL LETTER VE", latin: "B" }],
260
+ [1045, { name: "CYRILLIC CAPITAL LETTER IE", latin: "E" }],
261
+ [1050, { name: "CYRILLIC CAPITAL LETTER KA", latin: "K" }],
262
+ [1052, { name: "CYRILLIC CAPITAL LETTER EM", latin: "M" }],
263
+ [1053, { name: "CYRILLIC CAPITAL LETTER EN", latin: "H" }],
264
+ [1054, { name: "CYRILLIC CAPITAL LETTER O", latin: "O" }],
265
+ [1056, { name: "CYRILLIC CAPITAL LETTER ER", latin: "P" }],
266
+ [1057, { name: "CYRILLIC CAPITAL LETTER ES", latin: "C" }],
267
+ [1058, { name: "CYRILLIC CAPITAL LETTER TE", latin: "T" }],
268
+ [1059, { name: "CYRILLIC CAPITAL LETTER U", latin: "Y" }],
269
+ [1061, { name: "CYRILLIC CAPITAL LETTER HA", latin: "X" }],
270
+ [1280, { name: "CYRILLIC CAPITAL LETTER KOMI DE", latin: "D" }],
271
+ // Greek lower case
272
+ [945, { name: "GREEK SMALL LETTER ALPHA", latin: "a" }],
273
+ [953, { name: "GREEK SMALL LETTER IOTA", latin: "i" }],
274
+ [954, { name: "GREEK SMALL LETTER KAPPA", latin: "k" }],
275
+ [957, { name: "GREEK SMALL LETTER NU", latin: "v" }],
276
+ [959, { name: "GREEK SMALL LETTER OMICRON", latin: "o" }],
277
+ [961, { name: "GREEK SMALL LETTER RHO", latin: "p" }],
278
+ [965, { name: "GREEK SMALL LETTER UPSILON", latin: "u" }],
279
+ [1010, { name: "GREEK LUNATE SIGMA SYMBOL", latin: "c" }],
280
+ // Greek upper case
281
+ [913, { name: "GREEK CAPITAL LETTER ALPHA", latin: "A" }],
282
+ [914, { name: "GREEK CAPITAL LETTER BETA", latin: "B" }],
283
+ [917, { name: "GREEK CAPITAL LETTER EPSILON", latin: "E" }],
284
+ [918, { name: "GREEK CAPITAL LETTER ZETA", latin: "Z" }],
285
+ [919, { name: "GREEK CAPITAL LETTER ETA", latin: "H" }],
286
+ [921, { name: "GREEK CAPITAL LETTER IOTA", latin: "I" }],
287
+ [922, { name: "GREEK CAPITAL LETTER KAPPA", latin: "K" }],
288
+ [924, { name: "GREEK CAPITAL LETTER MU", latin: "M" }],
289
+ [925, { name: "GREEK CAPITAL LETTER NU", latin: "N" }],
290
+ [927, { name: "GREEK CAPITAL LETTER OMICRON", latin: "O" }],
291
+ [929, { name: "GREEK CAPITAL LETTER RHO", latin: "P" }],
292
+ [932, { name: "GREEK CAPITAL LETTER TAU", latin: "T" }],
293
+ [933, { name: "GREEK CAPITAL LETTER UPSILON", latin: "Y" }],
294
+ [935, { name: "GREEK CAPITAL LETTER CHI", latin: "X" }],
295
+ [1017, { name: "GREEK CAPITAL LUNATE SIGMA SYMBOL", latin: "C" }]
296
+ ]);
297
+ var TABLE = /* @__PURE__ */ new Map();
298
+ for (const rule of CARRIER_RULES) {
299
+ for (let cp = rule.from; cp <= (rule.to ?? rule.from); cp++) {
300
+ TABLE.set(cp, { name: typeof rule.name === "function" ? rule.name(cp) : rule.name, severity: rule.severity, fix: rule.fix, message: rule.message, context: rule.context, limitation: rule.limitation });
301
+ }
302
+ }
303
+ var BASE_LIMIT = "Unicode controls can be legitimate in multilingual text; this finding is not evidence of authorship.";
304
+ var EMOJI_SIDE = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\uFE0F0-9#*]/u;
305
+ var JOINING_SCRIPT = /[\p{sc=Arabic}\p{sc=Syriac}\p{sc=Nko}\p{sc=Mongolian}\p{sc=Devanagari}\p{sc=Bengali}\p{sc=Gurmukhi}\p{sc=Gujarati}\p{sc=Oriya}\p{sc=Tamil}\p{sc=Telugu}\p{sc=Kannada}\p{sc=Malayalam}\p{sc=Sinhala}\p{sc=Myanmar}\p{sc=Khmer}\p{sc=Tibetan}]/u;
306
+ var CJK_BASE = /[\p{sc=Han}\p{sc=Hiragana}\p{sc=Katakana}\p{sc=Mongolian}]/u;
307
+ function contextualise(rule, cp, prev, next) {
308
+ if (rule.context === "joiner") {
309
+ if (cp === 8205 && prev && next && EMOJI_SIDE.test(prev) && EMOJI_SIDE.test(next)) return void 0;
310
+ if (prev && next && JOINING_SCRIPT.test(prev) && JOINING_SCRIPT.test(next)) return void 0;
311
+ return rule;
312
+ }
313
+ if (rule.context === "variation") {
314
+ if (prev && EMOJI_SIDE.test(prev)) return void 0;
315
+ if (prev && CJK_BASE.test(prev)) return { ...rule, severity: "note", fix: "review", message: `${rule.message} It follows a base character that commonly takes glyph variants.` };
316
+ return rule;
317
+ }
318
+ if (rule.context === "variation_sup") {
319
+ if (prev && CJK_BASE.test(prev)) return { ...rule, severity: "note", message: `${rule.message} It follows a CJK ideograph and may select a registered glyph variant.` };
320
+ return rule;
321
+ }
322
+ return rule;
323
+ }
324
+ function rangeRule(cp) {
325
+ for (const rule of CARRIER_RANGE_RULES) {
326
+ if (cp >= rule.from && cp <= (rule.to ?? rule.from)) return { name: typeof rule.name === "function" ? rule.name(cp) : rule.name, severity: rule.severity, fix: rule.fix, message: rule.message, context: rule.context, limitation: rule.limitation };
327
+ }
328
+ return void 0;
329
+ }
330
+ function inspectUnicode(text) {
331
+ const findings = [];
332
+ let prev = "";
333
+ for (let i = 0; i < text.length; ) {
334
+ const cp = text.codePointAt(i);
335
+ const width = cp > 65535 ? 2 : 1;
336
+ const raw = text.slice(i, i + width);
337
+ const base = TABLE.get(cp) ?? (cp >= 57344 ? rangeRule(cp) : void 0);
338
+ if (base) {
339
+ const next = i + width < text.length ? String.fromCodePoint(text.codePointAt(i + width)) : "";
340
+ const rule = contextualise(base, cp, prev, next);
341
+ if (rule) {
342
+ findings.push({ id: `unicode_${i}_${cp.toString(16)}`, code_point: `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`, name: rule.name, severity: rule.severity, message: rule.message, suggestion: rule.fix === "review" ? "Review the surrounding script and direction before editing." : "Preview the deterministic change before approval.", span: rangeFromUtf16(text, i, i + width), matched_text_hash: prefixedSha256(raw), fix: rule.fix, limitations: rule.limitation ? [BASE_LIMIT, rule.limitation] : [BASE_LIMIT] });
343
+ }
344
+ }
345
+ if (cp >= 55296 && cp <= 57343) {
346
+ findings.push({ id: `unicode_${i}_surrogate`, code_point: `U+${cp.toString(16).toUpperCase()}`, name: "UNPAIRED SURROGATE", severity: "high", message: "An unpaired UTF-16 surrogate cannot be encoded as valid UTF-8.", suggestion: "Replace or remove it after checking the source encoding.", span: { start_utf16: i, end_utf16: i + 1, start_codepoint: Array.from(text.slice(0, i)).length, end_codepoint: Array.from(text.slice(0, i)).length + 1 }, matched_text_hash: prefixedSha256("\uFFFD"), fix: "review", limitations: ["The displayed replacement may differ from the original invalid code unit."] });
347
+ }
348
+ prev = raw;
349
+ i += width;
350
+ }
351
+ const tokens2 = [...text.matchAll(/[\p{L}\p{N}_-]+/gu)];
352
+ for (const token of tokens2) {
353
+ const value = token[0];
354
+ if (!/\p{Script=Latin}/u.test(value) || !/\p{Script=Cyrillic}|\p{Script=Greek}/u.test(value)) continue;
355
+ let local = 0;
356
+ for (const char of value) {
357
+ const cp = char.codePointAt(0), entry = CONFUSABLES.get(cp);
358
+ if (entry) {
359
+ const start = token.index + local;
360
+ findings.push({ id: `unicode_${start}_homoglyph_${cp.toString(16)}`, code_point: `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`, name: entry.name, severity: "medium", message: `A mixed-script token contains a character visually confusable with Latin '${entry.latin}'.`, suggestion: "Verify the intended spelling; homoglyphs are never replaced automatically.", span: rangeFromUtf16(text, start, start + char.length), matched_text_hash: prefixedSha256(char), fix: "review", limitations: ["Mixed scripts can be legitimate in names and multilingual text; this is contextual evidence only."] });
361
+ }
362
+ local += char.length;
363
+ }
364
+ }
365
+ return findings;
366
+ }
367
+ var RULES = [
368
+ ["code", /```[\s\S]*?```|`[^`\n]+`/g],
369
+ ["url", /https?:\/\/[^\s<>)\]]+/g],
370
+ ["email", /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi],
371
+ ["citation", /\[[0-9]+\]|\((?:[A-Z][A-Za-z-]+(?:\s+(?:and|&)\s+[A-Z][A-Za-z-]+)*(?:\s+et al\.)?),?\s+\d{4}[a-z]?\)|\b[A-Z][A-Za-z-]+(?:\s+(?:and|&)\s+[A-Z][A-Za-z-]+)*\s+et al\.,?\s*\(?\d{4}[a-z]?\)?/g],
372
+ ["quote", /[“"][^”"\n]+[”"]/g],
373
+ ["currency", /(?:£|\$|€)\s?\d[\d,]*(?:\.\d+)?/g],
374
+ ["date", /\b(?:\d{1,2}\s+[A-Z][a-z]+\s+\d{4}|\d{4}-\d{2}-\d{2})\b/g],
375
+ ["time", /\b\d{1,2}:\d{2}(?:\s?[ap]m)?\b/gi],
376
+ ["unit", /\b\d+(?:\.\d+)?\s?(?:kg|g|km|m|cm|mm|GB|MB|%|°C)\b/g],
377
+ ["number", /\b\d[\d,]*(?:\.\d+)?%?\b/g]
378
+ ];
379
+ var HONORIFIC_NAME = /\b(?:Dr|Mr|Mrs|Ms|Prof|Sir|Dame)\.?\s+[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}/g;
380
+ var NAME_RUN = /\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,2}\b/g;
381
+ var ORG_SUFFIXED = /\b(?:[A-Z][A-Za-z&.'-]*\s+){1,5}(?:Ltd\.?|Limited|LLC|Inc\.?|plc|GmbH|&\s?Co\.?|Agency|Council|University)(?!\w)/g;
382
+ var ORG_ACRONYM = /\b[A-Z]{2,6}(?:\.[A-Z]{2,6})*\b/g;
383
+ var NAME_STOPLIST = new Set("The A An This That These Those It He She They We You I In On At For To From With By Of And But Or Nor If As Is Are Was Were Be Been Not No Yes See Run New Our Your Their His Her Its My Do Does Did Will Would Can Could Should May Might Must Have Has Had So Then There Here What Which Who Whose When Where Why How All Any Each Per Both More Most Some Such Other Also Just Only Now Today Yesterday Tomorrow Please Note".split(" "));
384
+ var ACRONYM_STOPLIST = /* @__PURE__ */ new Set(["AM", "PM", "GMT", "UTC", "BST", "CET", "CEST", "EST", "EDT", "PST", "PDT", "EUR", "USD", "GBP", "JPY", "CHF", "AI", "IT", "TV", "OK", "PS", "NB", "ID", "IP", "FAQ", "API", "URL", "URI", "HTML", "CSS", "SQL", "PDF", "HTTP", "HTTPS", "VAT", "ASAP", "DIY", "CEO", "CTO", "CFO", "COO", "UK", "EU", "US", "USA", "RSVP", "ETA", "FYI", "QA", "DNA", "GPS", "SMS"]);
385
+ function atSentenceStart(content, index) {
386
+ const before = content.slice(0, index).replace(/[\s"“”'‘’(\[]+$/, "");
387
+ return before === "" || /[.!?:;…]$/.test(before);
388
+ }
389
+ function extractEntitySpans(content) {
390
+ const found = [];
391
+ const overlaps = (start, end) => found.some((x) => start < x.start + x.text.length && x.start < end);
392
+ for (const m of content.matchAll(ORG_SUFFIXED)) {
393
+ let text = m[0], start = m.index;
394
+ for (; ; ) {
395
+ const lead = /^([A-Z][A-Za-z&.'-]*)\s+/.exec(text);
396
+ if (lead && NAME_STOPLIST.has(lead[1])) {
397
+ start += lead[0].length;
398
+ text = text.slice(lead[0].length);
399
+ } else break;
400
+ }
401
+ if (/\s/.test(text)) found.push({ kind: "organisation", text, start });
402
+ }
403
+ for (const m of content.matchAll(ORG_ACRONYM)) {
404
+ if (!ACRONYM_STOPLIST.has(m[0]) && !overlaps(m.index, m.index + m[0].length)) found.push({ kind: "organisation", text: m[0], start: m.index });
405
+ }
406
+ for (const m of content.matchAll(HONORIFIC_NAME)) {
407
+ if (!overlaps(m.index, m.index + m[0].length)) found.push({ kind: "name", text: m[0], start: m.index });
408
+ }
409
+ for (const m of content.matchAll(NAME_RUN)) {
410
+ if (atSentenceStart(content, m.index) || overlaps(m.index, m.index + m[0].length)) continue;
411
+ if (m[0].split(/\s+/).some((token) => NAME_STOPLIST.has(token))) continue;
412
+ found.push({ kind: "name", text: m[0], start: m.index });
413
+ }
414
+ return found;
415
+ }
416
+ function extractProtectedSpans(source, policy = {}) {
417
+ const hash = source.content_hash ?? prefixedSha256(source.content);
418
+ const spans = [];
419
+ const add = (text, start, kind, origin, protection = "exact") => {
420
+ const r = rangeFromUtf16(source.content, start, start + text.length);
421
+ spans.push({ id: `ps_${kind}_${start}_${prefixedSha256(text).slice(7, 15)}`, kind, text, ...r, normalised_value: text, policy: protection, source: origin, confidence: null, content_hash: hash });
422
+ };
423
+ for (const [kind, regex] of RULES) {
424
+ regex.lastIndex = 0;
425
+ for (const m of source.content.matchAll(regex)) add(m[0], m.index, kind, "deterministic", kind === "date" || kind === "number" ? "equivalent_format" : "exact");
426
+ }
427
+ for (const entity of extractEntitySpans(source.content)) add(entity.text, entity.start, entity.kind, "deterministic");
428
+ for (const term of policy.configured_terms ?? []) {
429
+ let at = 0;
430
+ while ((at = source.content.indexOf(term.text, at)) >= 0) {
431
+ add(term.text, at, term.kind ?? "name", "user");
432
+ at += term.text.length;
433
+ }
434
+ }
435
+ for (const selected of policy.user_spans ?? []) add(source.content.slice(selected.start_utf16, selected.end_utf16), selected.start_utf16, "user_selected", "user");
436
+ const unique = /* @__PURE__ */ new Map();
437
+ for (const span of spans) unique.set(`${span.start_utf16}:${span.end_utf16}:${span.kind}:${span.policy}`, span);
438
+ return [...unique.values()].sort((a, b) => a.start_utf16 - b.start_utf16 || b.end_utf16 - b.start_utf16 - (a.end_utf16 - a.start_utf16) || a.id.localeCompare(b.id));
439
+ }
440
+ var CYRILLIC_LOOKALIKES = {
441
+ "\u0430": "a",
442
+ "\u0435": "e",
443
+ "\u043E": "o",
444
+ "\u0440": "p",
445
+ "\u0441": "c",
446
+ "\u0445": "x",
447
+ "\u0443": "y",
448
+ "\u043A": "k",
449
+ "\u043C": "m",
450
+ "\u043D": "h",
451
+ "\u0432": "b",
452
+ "\u0442": "t",
453
+ "\u0410": "A",
454
+ "\u0415": "E",
455
+ "\u041E": "O",
456
+ "\u0420": "P",
457
+ "\u0421": "C",
458
+ "\u0425": "X",
459
+ "\u0423": "Y",
460
+ "\u041A": "K",
461
+ "\u041C": "M",
462
+ "\u041D": "H",
463
+ "\u0412": "B",
464
+ "\u0422": "T"
465
+ };
466
+ var GREEK_LOOKALIKES = {
467
+ "\u03BF": "o",
468
+ "\u039F": "O",
469
+ "\u03B1": "a",
470
+ "\u0391": "A",
471
+ "\u03C1": "p",
472
+ "\u03A1": "P"
473
+ };
474
+ var ROLEPLAY_VERBS = /^(?:nods|sighs|laughs|smiles|frowns|shrugs|grins|winks|chuckles|gasps|pauses|thinks|wonders|whispers|shouts|gestures|raises|leans|turns|looks|glances|smirks|blinks|nodding|sighing|laughing|smiling|thinking|gesturing)\b/i;
475
+ var TIER1 = {
476
+ "delve": "explore, dig into, look at",
477
+ "tapestry": "describe the actual complexity",
478
+ "paradigm": "model, approach, framework",
479
+ "beacon": "rewrite entirely",
480
+ "robust": "strong, reliable, solid",
481
+ "comprehensive": "thorough, complete, full",
482
+ "cutting-edge": "latest, newest, advanced",
483
+ "pivotal": "important, key, critical",
484
+ "meticulous": "careful, detailed, precise",
485
+ "meticulously": "carefully, precisely",
486
+ "seamless": "smooth, easy, without friction",
487
+ "seamlessly": "smoothly, easily",
488
+ "game-changer": "describe what changed",
489
+ "game-changing": "describe what changed",
490
+ "nestled": "is located, sits",
491
+ "vibrant": "describe what makes it active",
492
+ "thriving": "growing, active",
493
+ "bustling": "busy, active",
494
+ "intricate": "complex, detailed",
495
+ "intricacies": "complexities, details",
496
+ "ever-evolving": "changing, growing",
497
+ "enduring": "lasting, long-running",
498
+ "daunting": "hard, difficult",
499
+ "holistic": "complete, full, whole",
500
+ "holistically": "completely, fully",
501
+ "actionable": "practical, useful, concrete",
502
+ "impactful": "effective, significant",
503
+ "learnings": "lessons, findings, takeaways",
504
+ "synergy": "describe the combined effect",
505
+ "synergies": "describe the combined effect",
506
+ "interplay": "relationship, connection",
507
+ "symphony": "describe the coordination",
508
+ "embrace": "adopt, accept, use",
509
+ // 2026.08.3 owner-docs tier A additions (OWNER-DOCS-TELLS.md §8).
510
+ "enigma": "mystery, puzzle",
511
+ "labyrinth": "maze, tangle",
512
+ "top-notch": "excellent, first-rate"
513
+ };
514
+ var TIER1_PHRASES = [
515
+ { pattern: /\bdelve\s+into\b/gi, replace: "explore, dig into" },
516
+ { pattern: /\blandscape\b/gi, replace: "field, space, industry" },
517
+ { pattern: /\brealm\b/gi, replace: "area, field, domain" },
518
+ { pattern: /\btestament\s+to\b/gi, replace: "shows, proves" },
519
+ { pattern: /\bleverag(?:e|es|ing|ed)\b/gi, replace: "use" },
520
+ { pattern: /\bwatershed\s+moment\b/gi, replace: "turning point, shift" },
521
+ { pattern: /\bmarking\s+a\s+pivotal\s+moment\b/gi, replace: "state what happened" },
522
+ { pattern: /\bthe\s+future\s+looks\s+bright\b/gi, replace: "cut or say something specific" },
523
+ { pattern: /\bonly\s+time\s+will\s+tell\b/gi, replace: "cut or say something specific" },
524
+ { pattern: /\bdespite\s+challenges[^.]*continues?\s+to\s+thrive\b/gi, replace: "name the challenge and response" },
525
+ { pattern: /\bdeep\s+dive\b/gi, replace: "look at, examine" },
526
+ { pattern: /\bdive\s+into\b/gi, replace: "look at, examine" },
527
+ { pattern: /\bunpack(?:ing)?\b/gi, replace: "explain, break down" },
528
+ { pattern: /\bcomplexities\b/gi, replace: "name the actual problems" },
529
+ { pattern: /\bthought\s+leader(?:ship)?\b/gi, replace: "expert, authority" },
530
+ { pattern: /\bbest\s+practices\b/gi, replace: "what works, proven methods" },
531
+ { pattern: /\bat\s+its\s+core\b/gi, replace: "cut, just state it" },
532
+ { pattern: /\bin\s+order\s+to\b/gi, replace: "to", clarity: true },
533
+ { pattern: /\bdue\s+to\s+the\s+fact\s+that\b/gi, replace: "because", clarity: true },
534
+ { pattern: /\bserves\s+as\b/gi, replace: "is", clarity: true },
535
+ { pattern: /\bfeatures\b/gi, replace: "has, includes", clarity: true },
536
+ { pattern: /\bboasts\b/gi, replace: "has", clarity: true },
537
+ { pattern: /\butiliz(?:e|es|ing|ed)\b/gi, replace: "use", clarity: true },
538
+ { pattern: /\bshowcas(?:e|es|ing|ed)\b/gi, replace: "show, demonstrate" },
539
+ { pattern: /\bembark(?:s|ing|ed)?\b/gi, replace: "start, begin" },
540
+ { pattern: /\bcommenc(?:e|es|ing|ed)\b/gi, replace: "start, begin", clarity: true },
541
+ { pattern: /\bascertain(?:s|ing|ed)?\b/gi, replace: "find out, determine", clarity: true },
542
+ { pattern: /\bendeavou?r(?:s|ing|ed)?\b/gi, replace: "effort, attempt, try", clarity: true },
543
+ { pattern: /\bunderscor(?:es|ing|ed)\b/gi, replace: "highlights, shows" },
544
+ // 2026.08.3 owner-docs tier A phrase additions.
545
+ { pattern: /\bbuckle\s+up\b/gi, replace: "cut, or say what actually follows" },
546
+ { pattern: /\bload-bearing\b(?!\s+(?:(?:structural|exterior|interior|internal|external|concrete|steel|timber|wooden|brick|masonry|perimeter|basement|main|primary|existing|original)\s+)?(?:walls?|beams?|columns?|joists?|truss(?:es)?|members?|footings?|slabs?|studs?|partitions?|masonry|lintels?|piers?|rafters?|girders?|capacity|capacities)\b)/gi, replace: "essential, critical, or say what breaks if it is removed" }
547
+ ];
548
+ var TIER2 = {
549
+ "harness": "use, take advantage of",
550
+ "navigate": "work through, handle",
551
+ "navigating": "working through, handling",
552
+ "foster": "encourage, support, build",
553
+ "elevate": "improve, raise, strengthen",
554
+ "unleash": "release, enable, unlock",
555
+ "streamline": "simplify, speed up",
556
+ "empower": "enable, let, allow",
557
+ "bolster": "support, strengthen",
558
+ "spearhead": "lead, drive, run",
559
+ "resonate": "connect with, appeal to",
560
+ "resonates": "connects with, appeals to",
561
+ "revolutionize": "change, transform",
562
+ "facilitate": "enable, help, allow",
563
+ "facilitates": "enables, helps, allows",
564
+ "underpin": "support, form the basis of",
565
+ "nuanced": "specific, subtle, detailed",
566
+ "crucial": "important, key, necessary",
567
+ "multifaceted": "describe the actual facets",
568
+ "ecosystem": "system, community, network",
569
+ "myriad": "many, numerous",
570
+ "plethora": "many, a lot of",
571
+ "encompass": "include, cover, span",
572
+ "catalyze": "start, trigger, accelerate",
573
+ "reimagine": "rethink, redesign, rebuild",
574
+ "galvanize": "motivate, rally, push",
575
+ "augment": "add to, expand, supplement",
576
+ "cultivate": "build, develop, grow",
577
+ "illuminate": "clarify, explain, show",
578
+ "elucidate": "explain, clarify",
579
+ "juxtapose": "compare, contrast",
580
+ "transformative": "describe what changed",
581
+ "transformation": "describe what changed",
582
+ "cornerstone": "foundation, basis, key part",
583
+ "paramount": "most important, top priority",
584
+ "poised": "ready, set, about to",
585
+ "burgeoning": "growing, emerging",
586
+ "nascent": "new, early-stage",
587
+ "quintessential": "typical, classic, defining",
588
+ "overarching": "main, central, broad",
589
+ "quietly": "cut, or name the concrete contrast",
590
+ "underpinning": "basis, foundation",
591
+ "underpinnings": "basis, foundations",
592
+ "paradigm-shifting": "describe what shifted",
593
+ // 2026.08.3 owner-docs additions (medium FP → cluster-scored tier 2).
594
+ "revolutionary": "describe what changed",
595
+ "ai-powered": "say what the AI part actually does"
596
+ };
597
+ var TIER2_CONDITIONAL = [
598
+ {
599
+ word: "deeply",
600
+ pattern: /\bdeeply\s+(?:integrated|committed|rooted|personal|human|flawed|resonant|transformative|interconnected|ingrained|embedded|meaningful)\b/i,
601
+ suggestion: "cut, or name what specifically runs deep"
602
+ }
603
+ ];
604
+ var TIER3 = [
605
+ "significant",
606
+ "significantly",
607
+ "innovative",
608
+ "innovation",
609
+ "effective",
610
+ "effectively",
611
+ "dynamic",
612
+ "dynamics",
613
+ "scalable",
614
+ "scalability",
615
+ "compelling",
616
+ "unprecedented",
617
+ "exceptional",
618
+ "exceptionally",
619
+ "remarkable",
620
+ "remarkably",
621
+ "sophisticated",
622
+ "instrumental",
623
+ "world-class",
624
+ "state-of-the-art",
625
+ "best-in-class",
626
+ "verbatim"
627
+ ];
628
+ var TIER3_PHRASES = [
629
+ /\bemerging\s+(?:sector|space|category|industry)\b/gi,
630
+ /\bthe\s+integration\s+of\b/gi,
631
+ /\bthe\s+intersection\s+of\b/gi,
632
+ /\bcommunity-?driven\b/gi,
633
+ /\blong-?term\s+sustainability\b/gi,
634
+ /\buser\s+engagement\b/gi,
635
+ /\bdecentralized\s+compute\b/gi,
636
+ /\b(?:sustainable\s+)?reward\s+emissions?\b/gi,
637
+ /\btokenized\s+incentive\s+structures?\b/gi,
638
+ /\bdesigned\s+for\s+long-?term\b/gi
639
+ ];
640
+ var TRANSITIONS = [
641
+ /\bmoreover\b/gi,
642
+ /\bfurthermore\b/gi,
643
+ /\badditionally\b/gi,
644
+ /\bin\s+today'?s\b/gi,
645
+ /\bin\s+an\s+era\s+where\b/gi,
646
+ /\bit'?s\s+worth\s+noting\s+that\b/gi,
647
+ /\bnotably\b/gi,
648
+ /\bin\s+conclusion\b/gi,
649
+ /\bin\s+summary\b/gi,
650
+ /\bto\s+summarize\b/gi,
651
+ /\bwhen\s+it\s+comes\s+to\b/gi,
652
+ /\bat\s+the\s+end\s+of\s+the\s+day\b/gi,
653
+ /\bthat\s+(?:being\s+)?said\b/gi,
654
+ // 2026.08.3: ritual-conclusion opener variant (seed str-ritual-conclusion).
655
+ /\bto\s+sum\s+up\b/gi
656
+ ];
657
+ var CHATBOT_ARTIFACTS = [
658
+ /\bi\s+hope\s+this\s+helps\b/gi,
659
+ /\bcertainly!\B/gi,
660
+ /\babsolutely!\B/gi,
661
+ /\bgreat\s+question!\B/gi,
662
+ /\bexcellent\s+point!\B/gi,
663
+ /\bfeel\s+free\s+to\s+reach\s+out\b/gi,
664
+ /\blet\s+me\s+know\s+if\s+you\s+need\s+anything\b/gi,
665
+ /\bin\s+this\s+article,?\s+we\s+will\s+explore\b/gi,
666
+ /\blet'?s\s+dive\s+in!?\b/gi,
667
+ // 2026.08.3: chat-wrapper leakage variants (seed art-collab-leakage /
668
+ // art-sycophantic-openers).
669
+ /\bwould\s+you\s+like\s+me\s+to\b/gi,
670
+ /\bi'?d\s+be\s+happy\s+to\b/gi
671
+ ];
672
+ var SYCOPHANTIC = [
673
+ /\byou'?re\s+absolutely\s+right\b/gi,
674
+ /\bthat'?s\s+a\s+really\s+insightful\b/gi,
675
+ /\bthat'?s\s+a\s+great\s+question\b/gi,
676
+ /\bexcellent\s+question\b/gi
677
+ ];
678
+ var FILLERS = [
679
+ /\bit\s+is\s+important\s+to\s+note\s+that\b/gi,
680
+ /\bin\s+terms\s+of\b/gi,
681
+ /\bthe\s+reality\s+is\s+that\b/gi,
682
+ /\bit'?s\s+important\s+to\s+note\s+that\b/gi
683
+ ];
684
+ var GENERIC_CONCLUSIONS = [
685
+ /\bthe\s+future\s+looks\s+bright\b/gi,
686
+ /\bonly\s+time\s+will\s+tell\b/gi,
687
+ /\bone\s+thing\s+is\s+certain\b/gi,
688
+ /\bas\s+we\s+move\s+forward\b/gi
689
+ ];
690
+ var LETS_PATTERNS = [
691
+ /\blet'?s\s+explore\b/gi,
692
+ /\blet'?s\s+take\s+a\s+look\b/gi,
693
+ /\blet'?s\s+break\s+this\s+down\b/gi,
694
+ /\blet'?s\s+examine\b/gi,
695
+ /\blet'?s\s+(?:consider|discuss|delve|unpack|walk\s+through)\b/gi
696
+ ];
697
+ var REASONING_ARTIFACTS = [
698
+ /\blet\s+me\s+think\s+step\s+by\s+step\b/gi,
699
+ /\bbreaking\s+this\s+down\b/gi,
700
+ /\bto\s+approach\s+this\s+systematically\b/gi,
701
+ /\bhere'?s\s+my\s+thought\s+process\b/gi,
702
+ /\bfirst,?\s+let'?s\s+consider\b/gi,
703
+ /\bworking\s+through\s+this\s+logically\b/gi
704
+ ];
705
+ var ACKNOWLEDGMENT_LOOPS = [
706
+ /\byou'?re\s+asking\s+about\b/gi,
707
+ /\bthe\s+question\s+of\s+whether\b/gi,
708
+ /\bto\s+answer\s+your\s+question\b/gi
709
+ ];
710
+ var SIGNIFICANCE_INFLATION = [
711
+ /\bmarking\s+a\s+(?:pivotal|significant|important)\s+moment\b/gi,
712
+ /\ba\s+watershed\s+moment\s+for\b/gi,
713
+ /\bin\s+the\s+evolution\s+of\b/gi,
714
+ /\ba\s+(?:pivotal|defining)\s+moment\s+in\b/gi
715
+ ];
716
+ var VAGUE_ATTRIBUTIONS = [
717
+ /\bexperts\s+(?:believe|say|suggest|agree)\b/gi,
718
+ /\bstudies\s+(?:show|suggest|indicate)\b/gi,
719
+ /\bresearch\s+(?:shows|suggests|indicates)\b/gi,
720
+ /\bindustry\s+leaders\s+(?:agree|believe|say)\b/gi,
721
+ // 2026.08.3: weasel-attribution variants (seed phr-weasel-attribution).
722
+ /\b(?:observers?|critics|analysts)\s+(?:argue|suggest|believe|say|have\s+(?:cited|noted|shown))\b/gi,
723
+ /\bindustry\s+reports?\s+(?:suggest|show|indicate)\b/gi,
724
+ /\bit\s+is\s+widely\s+believed\b/gi
725
+ ];
726
+ var HOLLOW_INTENSIFIERS = [
727
+ /\bgenuine(?:ly)?\b/gi,
728
+ /\btruly\b/gi,
729
+ /\bquite\s+frankly\b/gi,
730
+ /\bto\s+be\s+honest\b/gi,
731
+ /\blet'?s\s+be\s+clear\b/gi
732
+ ];
733
+ var EMOTIONAL_FLATLINE = [
734
+ /\bwhat\s+surprised\s+me\s+most\b/gi,
735
+ /\bi\s+was\s+fascinated\s+to\b/gi,
736
+ /\bwhat\s+struck\s+me\s+was\b/gi,
737
+ /\bi\s+was\s+excited\s+to\s+learn\b/gi,
738
+ /\bthe\s+most\s+interesting\s+(?:part|thing|aspect|piece)\b/gi,
739
+ /^\s*interesting\s+(?:part|thing|aspect|piece)(?:\s+of\s+(?:the\s+)?\w+)?\s*:/gim
740
+ ];
741
+ var LINGERING_ATTENTION = [
742
+ /\b(?:the|that|this)\s+(?:one\s+)?(?:line|quote|bit|part|idea|point|framing|comment|thing)\s+(?:that\s+)?i\s+keep\s+(?:coming\s+back\s+to|thinking\s+about)\b/gi,
743
+ /\bi\s+can'?t\s+stop\s+thinking\s+about\b/gi,
744
+ /\bstill\s+thinking\s+about\s+(?:this|that)\s+one\b/gi,
745
+ /\b(?:been|be)\s+rattling\s+around\s+(?:in\s+)?my\s+(?:head|brain)\b/gi,
746
+ /\bi'?ve\s+been\s+chewing\s+on\s+(?:this|that)\b/gi
747
+ ];
748
+ var NOVELTY_INFLATION = [
749
+ /\bthe\s+failure\s+mode\s+nobody'?s?\s+naming\b/gi,
750
+ /\ba\s+problem\s+nobody\s+talks\s+about\b/gi,
751
+ /\bthe\s+insight\s+everyone'?s?\s+missing\b/gi,
752
+ /\bwhat\s+nobody\s+tells\s+you\b/gi
753
+ ];
754
+ var CUTOFF_DISCLAIMERS = [
755
+ /\bas\s+of\s+my\s+last\s+update\b/gi,
756
+ /\bas\s+of\s+my\s+(?:knowledge\s+)?(?:cut-?off|last\s+training)\b/gi,
757
+ /\bi\s+don'?t\s+have\s+access\s+to\s+real-?time\s+(?:data|information)\b/gi,
758
+ /\bbased\s+on\s+available\s+information\b/gi,
759
+ /\bas\s+an?\s+(?:ai|artificial\s+intelligence|large\s+language|ai\s+language)\s+(?:language\s+)?model\b/gi,
760
+ /\bi\s+(?:am|'m)\s+an?\s+(?:ai|artificial\s+intelligence|large\s+language)\s+(?:assistant|model)?\b/gi,
761
+ /\bi\s+cannot\s+(?:provide|give|offer)\s+(?:legal|medical|financial|professional)\s+advice\b/gi,
762
+ /\bmy\s+training\s+data\s+(?:only\s+)?(?:goes\s+up\s+to|extends\s+to|ends\s+(?:in|at))\b/gi,
763
+ // 2026.08.3: RAG-era source-gap variant (seed art-cutoff-disclaimer).
764
+ /\bin\s+the\s+provided\s+search\s+results\b/gi
765
+ ];
766
+ var AI_PLACEHOLDERS = [
767
+ /\[(?:Your|Insert|Add|Enter|Describe|Specify|Choose|Pick)[^\]\n]{1,80}\]/gi,
768
+ /\[(?:Recipient|Sender|Topic|Subject|Salutation|Closing|Position|Department|Project Name|Company Name|Date)(?:\s+[^\]\n]{0,60})?\]/gi,
769
+ /\[(?:INSERT|FILL\s+IN|ADD|TODO|TBD|PLACEHOLDER)[^\]\n]{0,80}\]/g,
770
+ /\b(?:19|20)\d{2}-XX-XX\b/g,
771
+ /\bXX\/XX\/(?:19|20)\d{2}\b/g,
772
+ /<!--\s*(?:add|fill\s+in|insert|todo|placeholder)[^>]{0,120}-->/gi
773
+ ];
774
+ var AI_CITATION_MARKUP = [
775
+ /\bcite(?:turn|news|search|navigation)\d+(?:search|turn|news|navigation)\d+/gi,
776
+ /contentReference\s*\[oaicite:[^\]]+\]\s*\{[^}]*\}/gi,
777
+ /\boai_citation\b/gi,
778
+ /\[attached_file:\d+\]/gi,
779
+ /\bgrok_card\b/gi
780
+ ];
781
+ var AI_UTM_SOURCE = [
782
+ /[?&]utm_source=(?:chatgpt|openai|copilot|claude|grok|gemini|perplexity)(?:\.com|\.ai)?\b/gi,
783
+ /[?&]referrer=(?:chatgpt|copilot|grok|claude|gemini|perplexity)\.(?:com|ai)\b/gi
784
+ ];
785
+ var TEMPLATE_PHRASES = [
786
+ /\ba\s+\w+\s+step\s+(?:towards?|forward\s+for)\b/gi,
787
+ /\bwhether\s+you'?re\s+\w+\s+or\s+\w+/gi,
788
+ /\bi\s+recently\s+had\s+the\s+pleasure\s+of\b/gi
789
+ ];
790
+ var FALSE_CONCESSION = [
791
+ /\bwhile\s+\w+\s+is\s+impressive\b/gi,
792
+ /\balthough\s+\w+\s+has\s+made\s+strides\b/gi,
793
+ /\bdespite\s+\w+\s+challenges?\b/gi
794
+ ];
795
+ var RHETORICAL_QUESTIONS = [
796
+ /\bbut\s+what\s+does\s+this\s+mean\s+for\b/gi,
797
+ /\bso\s+why\s+should\s+you\s+care\b/gi,
798
+ /\bwhat'?s\s+next\?\s*/gi
799
+ ];
800
+ var HEDGE_STACK = [
801
+ /\b(?:could|may|might)\s+(?:(?!not\b|never\b|hardly\b|scarcely\b|barely\b)\w+\s+)?(?:potentially|eventually|ultimately|possibly|conceivably)\b/gi,
802
+ /\b(?:potentially|eventually|ultimately)\s+(?:could|may|might)\b/gi
803
+ ];
804
+ var FUTURE_NARRATIVE = [
805
+ /\b(?:may|could|will|is\s+(?:poised|set)\s+to)\s+become\s+(?:one\s+of\s+)?(?:the\s+)?(?:most\s+)?\w+\s+(?:narratives?|stories|developments?|trends?|movements?|chapters?|themes?|forces?)\b/gi,
806
+ /\bone\s+of\s+the\s+most\s+important\s+(?:narratives?|stories|trends?|themes?)\s+of\s+the\s+(?:next|coming)\s+\w+\b/gi
807
+ ];
808
+ var REAL_ACTUAL_INFLATION = [
809
+ /\b(?:real|actual|genuine|true)\s+(?:on-?chain\s+)?(?:tokenomics|economics|utility|adoption|sustainability|impact|revenue|fundamentals|demand|value|innovation|traction)\b/gi
810
+ ];
811
+ var FORMULAIC_OPENERS = [
812
+ /\bin\s+the\s+(?:rapidly\s+|ever-?\s*)?(?:evolving|changing|expanding|growing|shifting)\s+(?:world|landscape|realm|space|field|domain|era)\s+of\b/gi,
813
+ /\bin\s+(?:an?|the)\s+(?:digital\s+)?age\s+(?:where|of)\b/gi,
814
+ /\bas\s+(?:we|the\s+world|society|industries?)\s+(?:continue|move|navigate|enter)\s+(?:to\s+)?(?:evolve|forward|into|through)\b/gi,
815
+ /\bhas\s+emerged\s+as\s+(?:a|the|one\s+of)\s+(?:leading|key|major|critical|essential|fundamental|pivotal|prominent|dominant|important)\s+\w+/gi,
816
+ /\bhas\s+become\s+increasingly\s+(?:important|critical|popular|relevant|prominent|essential)\b/gi
817
+ ];
818
+ var SPECULATIVE_OPENERS = [
819
+ /\b(?:imagine|picture|envision)(?:\s*,[^,\n]{1,30},)?\s+a\s+(?:world|future|reality)\s+(?:where|in\s+which)\b/gi
820
+ ];
821
+ var PARENTHETICAL_HEDGE = [
822
+ /\(\s*(?:and\s+)?(?:increasingly|notably|importantly|crucially|interestingly|perhaps)[,]?\s+[^)]{3,60}\)/gi,
823
+ /\(\s*or\s+more\s+(?:precisely|accurately|specifically)[,]?\s+[^)]{3,60}\)/gi,
824
+ /\(\s*though\s+to\s+be\s+fair[,]?\s+[^)]{3,60}\)/gi,
825
+ /\(\s*at\s+least\s+(?:in\s+)?(?:theory|principle|part)[,]?\s+[^)]{0,60}\)/gi
826
+ ];
827
+ var CONFIDENCE_CALIBRATION = [
828
+ /\binterestingly\b/gi,
829
+ /\bsurprisingly\b/gi,
830
+ /\bimportantly\b/gi,
831
+ /\bsignificantly\b/gi,
832
+ /\bcertainly\b/gi,
833
+ /\bundoubtedly\b/gi,
834
+ /\bwithout\s+a\s+doubt\b/gi
835
+ ];
836
+ var SOCIAL_CTA_CLOSER = [
837
+ /\bthis\s+one['’]?s?\s+(?:is\s+)?(?:well\s+|totally\s+|absolutely\s+|definitely\s+|really\s+|truly\s+|easily\s+|more\s+than\s+)?worth\s+(?:your\s+time|the\s+read|a\s+read|every\s+(?:minute|second)|reading|watching|a\s+listen|a\s+watch|a\s+look|it)\b/gi,
838
+ /\bthis\s+one['’]?s?\s+(?:is\s+)?a\s+must[-\s]?(?:read|watch|listen|see)\b/gi,
839
+ /\b(?:highly|strongly|can['’]?t|cannot)\s+recommend\w*\s+(?:giving\s+)?(?:this|it)\s+(?:one\s+)?a\s+(?:read|listen|watch|look|go)\b/gi,
840
+ /\bdo\s+yourself\s+a\s+favou?r\s+and\s+(?:read|watch|check\s+out)\s+(?:this|it)\b/gi,
841
+ /\byou\s+(?:really\s+)?(?:won['’]?t|do\s*n['’]?t|will\s+not|do\s+not)\s+want\s+to\s+miss\s+this(?:\s+one)?(?=\s*(?:[:.!\n]|$))/gi,
842
+ /(?<=^|[,.!?:\n]\s{0,4})(?:you\s+can\s+)?thank\s+me\s+later\b/gim,
843
+ /(?<=^|[.!?:\n]\s{0,4})save\s+this\s+(?:one\s+)?for\s+later\b/gim,
844
+ /\bbookmark\s+this(?:\s+(?:one|post|thread))?(?=\s*(?:[:.!\n]|$))/gi,
845
+ /\bdo\s*n['’]?t\s+sleep\s+on\s+this\b/gi,
846
+ /\btrust\s+me,?\s+(?:on\s+this|you['’]?ll)\b/gi
847
+ ];
848
+ var NOT_JUST_CONTRAST = [
849
+ /\b(?:isn|aren|wasn|weren|doesn|don|didn)['’]t\s+(?:just\s+)?[^.!?\n]{1,60}?\s*(?:—|–|--)\s*(?:it|this|that|they|we)['’](?:s|re)\b/gi,
850
+ /\b(?:is|are|was|were)\s+not\s+just\s+[^.!?\n]{1,60}?\s*(?:—|–|--|,)\s*(?:it|this|that|they)['’](?:s|re)\b/gi,
851
+ /\bnot\s+just\s+[^.!?\n]{1,60}?\s*(?:—|–|--)\s*(?:but\s+)?(?:it|this|that|they)['’](?:s|re)\b/gi
852
+ ];
853
+ var FUNCTION_WORD_IN_TITLE = /\b(?:And|Or|Of|The|In|For|To|A|An)\b/;
854
+ var MD_HEADING_PREFIX = /^#{1,6}[ \t]+/;
855
+ var TITLE_CASE_HEADER = /^(?:#{1,6}[ \t]+)?([A-Z][a-z]+(?:\s+(?:[A-Z][a-z]+|and|or|of|the|in|for|to|a|an))+\s+[A-Z][a-z]+)\s*$/gm;
856
+ var SEPARATOR_DASH_RE = /^\s*(?:[-*+]|\d+[.)])\s+(?:\*\*[^*\n]+\*\*|\[[^\]\n]+\]\([^)\n]*\))(?:[ \t]*(?:\([^)\n]*\)|`[^`\n]+`))?[ \t]*—/gm;
857
+ var VERSION_HEADING_DASH_RE = /^#{1,6}[ \t]+\[?v?\d+\.\d+\.\d+[^\]\n]*\]?[ \t]*—[ \t]*\d{4}-\d{2}-\d{2}[ \t]*$/gm;
858
+ var FUNC_WORDS = /* @__PURE__ */ new Set([
859
+ "the",
860
+ "a",
861
+ "an",
862
+ "and",
863
+ "or",
864
+ "but",
865
+ "of",
866
+ "to",
867
+ "in",
868
+ "on",
869
+ "at",
870
+ "by",
871
+ "for",
872
+ "with",
873
+ "from",
874
+ "as",
875
+ "is",
876
+ "was",
877
+ "are",
878
+ "were",
879
+ "be",
880
+ "been",
881
+ "being",
882
+ "have",
883
+ "has",
884
+ "had",
885
+ "do",
886
+ "does",
887
+ "did",
888
+ "will",
889
+ "would",
890
+ "should",
891
+ "could",
892
+ "may",
893
+ "might",
894
+ "must",
895
+ "can",
896
+ "this",
897
+ "that",
898
+ "these",
899
+ "those",
900
+ "it",
901
+ "its",
902
+ "they",
903
+ "them",
904
+ "their",
905
+ "there",
906
+ "here",
907
+ "we",
908
+ "our",
909
+ "us",
910
+ "i",
911
+ "you",
912
+ "your",
913
+ "he",
914
+ "she",
915
+ "his",
916
+ "her",
917
+ "him",
918
+ "not",
919
+ "no",
920
+ "so",
921
+ "if",
922
+ "then",
923
+ "than",
924
+ "when",
925
+ "where",
926
+ "which",
927
+ "who",
928
+ "what",
929
+ "how",
930
+ "why",
931
+ "because"
932
+ ]);
933
+ var ISSUE_WEIGHTS = {
934
+ "tier1": 5,
935
+ "tier1-clarity": 3,
936
+ "tier2": 3,
937
+ "tier3": 2,
938
+ "transition": 2,
939
+ "chatbot": 8,
940
+ "sycophantic": 8,
941
+ "filler": 2,
942
+ "generic-conclusion": 3,
943
+ "lets-construction": 2,
944
+ "reasoning-artifact": 6,
945
+ "acknowledgment-loop": 3,
946
+ "significance-inflation": 4,
947
+ "vague-attribution": 5,
948
+ "hollow-intensifier": 2,
949
+ "emotional-flatline": 2,
950
+ "lingering-attention": 3,
951
+ "novelty-inflation": 3,
952
+ "cutoff-disclaimer": 10,
953
+ "template-phrase": 3,
954
+ "false-concession": 2,
955
+ "rhetorical-question": 2,
956
+ "confidence-calibration": 2,
957
+ "em-dash-density": 4,
958
+ "not-just-contrast": 6,
959
+ "uniform-sections": 5,
960
+ "uniform-list-items": 4,
961
+ "sentence-flatline": 5,
962
+ "uniformity": 5,
963
+ "formatting": 3,
964
+ "tier3-phrase": 3,
965
+ "tier3-phrase-cluster": 12,
966
+ "hashtag-stuff": 12,
967
+ "bullet-np-list": 10,
968
+ "hedge-stack": 6,
969
+ "future-narrative": 12,
970
+ "real-actual-inflation": 5,
971
+ "social-cta-closer": 8,
972
+ "formulaic-opener": 8,
973
+ "speculative-opener": 8,
974
+ "title-case-header": 4,
975
+ "parenthetical-hedge": 3,
976
+ "smart-punct-signature": 6,
977
+ "punct-distribution": 6,
978
+ "fnword-trigram-entropy": 5,
979
+ "cross-para-burstiness": 5,
980
+ "normalization-flag": 9,
981
+ "low-ttr": 3,
982
+ "ai-placeholder": 10,
983
+ "ai-citation-markup": 15,
984
+ "ai-utm-source": 12
985
+ };
986
+ var CATEGORY_META = {
987
+ "tier1": { severity: "high", message: 'A word that turns up a lot in generic machine-written copy, like "delve", "robust" or "seamless".', suggestion: "Swap it for a plainer word." },
988
+ "tier1-clarity": { severity: "medium", message: 'This is a long way of saying something short, like "in order to" for "to", or "due to the fact that" for "because".', suggestion: "Use the shorter form." },
989
+ "tier2": { severity: "medium", message: 'Several buzzwords sit in one paragraph, words like "foster", "facilitate" and "ecosystem". One is fine. A pile of them reads as filler.', suggestion: "Keep one at most, and say the plain thing instead." },
990
+ "tier3": { severity: "low", message: 'A vague filler word such as "significant", "effective" or "dynamic" is used a lot for a piece this long.', suggestion: "Cut some of them, or say what you actually mean." },
991
+ "transition": { severity: "medium", message: 'A joining phrase that could link any two sentences, like "Moreover", "Furthermore" or "In conclusion".', suggestion: "Cut it, or say how the two sentences really connect." },
992
+ "chatbot": { severity: "high", message: 'A line from a chat window is still in the text, like "I hope this helps" or "Certainly!".', suggestion: "Delete it." },
993
+ "sycophantic": { severity: "high", message: `A compliment aimed at the reader's question, the way a chatbot opens a reply: "Great question!".`, suggestion: "Delete it and go straight to the point." },
994
+ "filler": { severity: "medium", message: "A phrase that adds words but no meaning.", suggestion: "Cut it. The sentence almost always still works." },
995
+ "generic-conclusion": { severity: "medium", message: 'A closing line that could end any article, like "only time will tell" or "the future looks bright".', suggestion: "End on something specific instead." },
996
+ "lets-construction": { severity: "medium", message: `A "let's take a look at\u2026" line, the way a tutorial talks to you.`, suggestion: "Just say the thing. Do not announce it first." },
997
+ "reasoning-artifact": { severity: "high", message: 'Step-by-step working is still showing, like "First, let me\u2026" or "Step 1:".', suggestion: "Delete the working and keep the answer." },
998
+ "acknowledgment-loop": { severity: "medium", message: 'The text repeats the question back before answering it: "You asked about X. X is\u2026".', suggestion: "Answer straight away." },
999
+ "significance-inflation": { severity: "high", message: 'A stock line telling the reader how big something was, instead of saying what happened: "marking a pivotal moment".', suggestion: "Say what happened and let the reader judge." },
1000
+ "vague-attribution": { severity: "high", message: 'A claim is credited to nobody in particular: "experts say", "studies show".', suggestion: "Name the study or the person, or drop the claim." },
1001
+ "hollow-intensifier": { severity: "medium", message: 'An emphasis word that adds no information, like "truly", "incredibly" or "absolutely".', suggestion: "Cut it." },
1002
+ "emotional-flatline": { severity: "low", message: 'A ready-made feeling word stands in for a real reaction, like "fascinating" or "surprising".', suggestion: "Say what was actually surprising, or cut it." },
1003
+ "lingering-attention": { severity: "medium", message: 'A share-post line about how long an idea stayed with you, which tells the reader nothing about the idea: "this stuck with me for days".', suggestion: "Say why it matters instead." },
1004
+ "novelty-inflation": { severity: "medium", message: 'The text announces that it is about to say something rare: "few people realise".', suggestion: "Show the point. Do not advertise it." },
1005
+ "cutoff-disclaimer": { severity: "high", message: 'A line where a chatbot describes itself or its training cut-off, like "as of my last update" or "as an AI language model".', suggestion: "Delete it, and check the facts around it yourself." },
1006
+ "template-phrase": { severity: "high", message: "A ready-made phrase that turns up in a lot of generated copy.", suggestion: "Replace it with something specific to your subject." },
1007
+ "false-concession": { severity: "medium", message: `A formula that pretends to give ground before making the point: "while it's true that\u2026".`, suggestion: "Name the real trade-off, or drop it." },
1008
+ "rhetorical-question": { severity: "medium", message: 'A question the writer asks and then answers: "So what does this mean?".', suggestion: "Give the answer without asking first." },
1009
+ "confidence-calibration": { severity: "low", message: 'Words like "clearly", "certainly" and "undoubtedly" pile up across the text.', suggestion: "Keep the one or two you have earned and cut the rest." },
1010
+ "em-dash-density": { severity: "medium", message: "Em dashes (\u2014), or spaced hyphens used as dashes, turn up a lot for a piece this long. Worth knowing: since OpenAI cut back on em dashes in late 2025, heavy dash use is more common in Claude's writing than in machine writing generally, and plenty of professional human writers use more dashes than this.", suggestion: "Swap some for commas, colons or full stops." },
1011
+ "not-just-contrast": { severity: "high", message: `The "it isn't just X, it's Y" shape.`, suggestion: "Say what it is, without the set-up." },
1012
+ "uniform-sections": { severity: "medium", message: "Every section is close to the same length. Real writing runs long in places and short in others.", suggestion: "Let each section be as long as its content needs." },
1013
+ "uniform-list-items": { severity: "medium", message: "The list items are all close to the same length. Real lists are lumpy.", suggestion: "Let some items run shorter or longer, or merge ones that repeat." },
1014
+ "sentence-flatline": { severity: "medium", message: "Sentence lengths barely change across the whole piece. Real writing swings between short and long.", suggestion: "Mix a few short, punchy sentences in with the longer ones." },
1015
+ "uniformity": { severity: "medium", message: "The paragraphs are all close to the same size.", suggestion: "Make some paragraphs shorter or longer on purpose." },
1016
+ "formatting": { severity: "medium", message: "A lot of bold is used.", suggestion: "Take the bold off most phrases, and put the important thing first." },
1017
+ "tier3-phrase": { severity: "medium", message: "The same stock phrase keeps coming back through the text.", suggestion: "Replace at least one of them with something concrete." },
1018
+ "tier3-phrase-cluster": { severity: "high", message: "Several different stock phrases are stacked up in one piece.", suggestion: "Rewrite it around one real point." },
1019
+ "hashtag-stuff": { severity: "medium", message: "A long block of hashtags.", suggestion: "Cut to two or three that fit, or none." },
1020
+ "bullet-np-list": { severity: "high", message: "A long bullet list where every item is a bare noun phrase with no verb.", suggestion: "Turn it into a paragraph, or join the items up." },
1021
+ "hedge-stack": { severity: "high", message: 'Two hedges on the same claim, like "may potentially" or "could possibly".', suggestion: "Pick one hedge, or make a claim someone could check." },
1022
+ "future-narrative": { severity: "high", message: 'A vague line about the future that nobody could ever check: "is set to reshape the industry".', suggestion: "Say something concrete a reader could test." },
1023
+ "real-actual-inflation": { severity: "medium", message: '"Real" or "actual" is doing no work here, as in "real value" or "actual impact".', suggestion: "Cut the word, or say what makes it real." },
1024
+ "social-cta-closer": { severity: "high", message: 'A closing line begging for engagement: "What do you think? Let me know below."', suggestion: "Give the reader a reason to care instead of an instruction." },
1025
+ "formulaic-opener": { severity: "high", message: `A stock opening line that would fit any article: "In today's fast-paced world\u2026".`, suggestion: "Open on a fact or a specific detail." },
1026
+ "speculative-opener": { severity: "high", message: 'The piece opens by asking you to picture a scene: "Imagine a world where\u2026".', suggestion: "Make the point directly." },
1027
+ "title-case-header": { severity: "medium", message: "The heading puts a capital on nearly every word, the way a lot of marketing copy does.", suggestion: "Capitalise the first word only, as you would in a sentence." },
1028
+ "parenthetical-hedge": { severity: "medium", message: 'An aside in brackets that sounds thoughtful and adds nothing: "(at least in most cases)".', suggestion: "Cut it, or make it a proper sentence." },
1029
+ "smart-punct-signature": { severity: "high", message: "Curly quotes, an em dash, an Oxford comma and no typos, all in one piece. That combination is rare in text somebody typed straight out by hand.", suggestion: "Nothing to fix. This is just something we noticed." },
1030
+ "punct-distribution": { severity: "medium", message: "Every paragraph uses about the same amount of punctuation.", suggestion: "Let some paragraphs run busier than others." },
1031
+ "fnword-trigram-entropy": { severity: "medium", message: "Sentences are built to the same grammar pattern again and again.", suggestion: "Change the way you build some of the sentences." },
1032
+ "cross-para-burstiness": { severity: "medium", message: "Every paragraph has the same inner rhythm of long and short sentences.", suggestion: "Let some paragraphs be clipped and others rambling." },
1033
+ "normalization-flag": { severity: "high", message: "The text held invisible characters or lookalike letters, the kind used to hide copied text from checkers. We swapped them back before reading it.", suggestion: "Take the hidden characters out and check the text again." },
1034
+ "low-ttr": { severity: "low", message: "The same words come round a lot for a piece this long.", suggestion: "Vary the nouns and verbs, unless the subject really does need the repeating." },
1035
+ "ai-placeholder": { severity: "high", message: 'A blank somebody forgot to fill in is still here, like "[INSERT NAME]".', suggestion: "Fill it in or delete it before this goes out." },
1036
+ "ai-citation-markup": { severity: "high", message: "Chatbot citation code was pasted in along with the text.", suggestion: "Delete it and put a real reference in its place." },
1037
+ "ai-utm-source": { severity: "high", message: 'A link carries a tracking tag that AI tools add to the links they hand out, like "utm_source=chatgpt.com".', suggestion: "Strip the tracking part off the link." }
1038
+ };
1039
+ var RULE_ERA = {
1040
+ // ── existing v2 categories ──
1041
+ "tier1": { era: "2023" },
1042
+ "tier1-clarity": { era: "evergreen" },
1043
+ "tier2": { era: "2023" },
1044
+ "tier3": { era: "2023" },
1045
+ "transition": { era: "2023" },
1046
+ "chatbot": { era: "evergreen", attribution: "multi" },
1047
+ "sycophantic": { era: "evergreen", attribution: "multi" },
1048
+ "filler": { era: "2023" },
1049
+ "generic-conclusion": { era: "2023" },
1050
+ "lets-construction": { era: "evergreen" },
1051
+ "reasoning-artifact": { era: "2025-26" },
1052
+ "acknowledgment-loop": { era: "evergreen" },
1053
+ "significance-inflation": { era: "2024-25" },
1054
+ "vague-attribution": { era: "evergreen" },
1055
+ "hollow-intensifier": { era: "evergreen" },
1056
+ "emotional-flatline": { era: "2024-25" },
1057
+ "lingering-attention": { era: "2025-26" },
1058
+ "novelty-inflation": { era: "2025-26" },
1059
+ "cutoff-disclaimer": { era: "2023", attribution: "multi" },
1060
+ "template-phrase": { era: "2023" },
1061
+ "false-concession": { era: "2024-25" },
1062
+ "rhetorical-question": { era: "2024-25" },
1063
+ "confidence-calibration": { era: "evergreen" },
1064
+ // Post-2025 nuance: OpenAI suppressed em dashes in Nov 2025; by mid-2026
1065
+ // only Claude-family output exceeds professional writers, making density a
1066
+ // partial model-attribution hint rather than a generic AI tell.
1067
+ "em-dash-density": { era: "2025-26", attribution: "claude" },
1068
+ "not-just-contrast": { era: "2024-25" },
1069
+ "uniform-sections": { era: "evergreen" },
1070
+ "uniform-list-items": { era: "evergreen" },
1071
+ "sentence-flatline": { era: "2023" },
1072
+ "uniformity": { era: "evergreen" },
1073
+ "formatting": { era: "evergreen", attribution: "multi" },
1074
+ "tier3-phrase": { era: "2023" },
1075
+ "tier3-phrase-cluster": { era: "2023" },
1076
+ "hashtag-stuff": { era: "evergreen" },
1077
+ "bullet-np-list": { era: "evergreen" },
1078
+ "hedge-stack": { era: "evergreen" },
1079
+ "future-narrative": { era: "2024-25" },
1080
+ "real-actual-inflation": { era: "2025-26" },
1081
+ "social-cta-closer": { era: "2025-26" },
1082
+ "formulaic-opener": { era: "2023" },
1083
+ "speculative-opener": { era: "2023" },
1084
+ "title-case-header": { era: "evergreen" },
1085
+ "parenthetical-hedge": { era: "2025-26" },
1086
+ "smart-punct-signature": { era: "evergreen" },
1087
+ "punct-distribution": { era: "evergreen" },
1088
+ "fnword-trigram-entropy": { era: "evergreen" },
1089
+ "cross-para-burstiness": { era: "evergreen" },
1090
+ "normalization-flag": { era: "evergreen" },
1091
+ "low-ttr": { era: "2023" },
1092
+ "ai-placeholder": { era: "evergreen", attribution: "multi" },
1093
+ "ai-citation-markup": { era: "2025-26", attribution: "multi" },
1094
+ "ai-utm-source": { era: "2024-25", attribution: "multi" },
1095
+ // ── v3 artefact forensics ──
1096
+ "ai-citation-token": { era: "2025-26", attribution: "multi" },
1097
+ "reasoning-leak": { era: "2025-26" },
1098
+ "placeholder-token": { era: "2024-25" },
1099
+ "pua-character": { era: "2025-26", attribution: "chatgpt" },
1100
+ "math-alphanumeric": { era: "2025-26", attribution: "chatgpt" },
1101
+ "arrow-decoration": { era: "2025-26", attribution: "chatgpt" },
1102
+ "escaped-markup-literal": { era: "evergreen", attribution: "multi" },
1103
+ // ── v3 tier A phrase/structural ──
1104
+ "neg-parallelism": { era: "2024-25" },
1105
+ "tripled-negation": { era: "2025-26", attribution: "chatgpt" },
1106
+ "despite-challenges-arc": { era: "2023" },
1107
+ "metaphor-cluster": { era: "2023" },
1108
+ "participial-tail": { era: "evergreen" },
1109
+ "focal-density": { era: "2023" },
1110
+ "owner-phrase": { era: "2023" },
1111
+ "power-verb-compound": { era: "2023" },
1112
+ "outcome-tail": { era: "2023" },
1113
+ "conclusion-cta": { era: "2023" },
1114
+ // ── v3 tier B (corroboration-weight) ──
1115
+ "liang-cluster": { era: "2024-25" },
1116
+ "kobak-density": { era: "2023" },
1117
+ "promo-travel": { era: "2023" },
1118
+ "pivotal-role": { era: "2024-25" },
1119
+ "legacy-framing": { era: "2024-25", attribution: "multi" },
1120
+ "notability-canned": { era: "2025-26" },
1121
+ "buzzword-phrase": { era: "2023" },
1122
+ "faux-insight": { era: "2025-26" },
1123
+ "rhetorical-qa": { era: "2024-25", attribution: "chatgpt" },
1124
+ "didactic-note": { era: "2023" },
1125
+ "narrative-cliche": { era: "2024-25" },
1126
+ "valuable-insights": { era: "2024-25" },
1127
+ "copula-avoidance": { era: "evergreen" },
1128
+ "bold-label-bullets": { era: "evergreen", attribution: "multi" },
1129
+ "emoji-decoration": { era: "2024-25", attribution: "chatgpt" },
1130
+ "heading-inflation": { era: "2024-25", attribution: "gemini" },
1131
+ "staccato-fragments": { era: "2025-26" },
1132
+ "tricolon-density": { era: "evergreen" },
1133
+ "transition-stacking": { era: "2023" },
1134
+ "quote-inconsistency": { era: "2025-26", attribution: "chatgpt" },
1135
+ "token-cutoff": { era: "2023", attribution: "chatgpt" },
1136
+ "setup-expansion-cadence": { era: "2025-26" },
1137
+ "passive-ratio": { era: "evergreen" },
1138
+ "low-specificity": { era: "evergreen" },
1139
+ "adjacent-lemma-repeat": { era: "evergreen" },
1140
+ "fiction-claudeism": { era: "2024-25", attribution: "claude" },
1141
+ "fiction-promptonym": { era: "2024-25", attribution: "multi" },
1142
+ "fiction-slop-phrase": { era: "2024-25", attribution: "multi" },
1143
+ "owner-phrase-b": { era: "2023" },
1144
+ "owner-vocab-b": { era: "2023" },
1145
+ "directive-colon-bullets": { era: "2024-25" },
1146
+ "teach-preach-headings": { era: "2023" },
1147
+ "by-ving-template": { era: "2023" },
1148
+ "invalid-isbn": { era: "evergreen" },
1149
+ "proximity-cluster": { era: "evergreen" },
1150
+ // ── 2026.08.6 provider-eval furniture rules ──
1151
+ // deepseek 99.3% / google-25 95.3% / mistral 94.7% carry bold; heaviest in
1152
+ // the 2024+ chat register across vendors → multi attribution.
1153
+ "markdown-bold": { era: "2024-25", attribution: "multi" },
1154
+ "markdown-heading": { era: "2025-26", attribution: "multi" },
1155
+ "markdown-furniture": { era: "2024-25", attribution: "multi" }
1156
+ };
1157
+ var CORROBORATION_CATEGORIES = /* @__PURE__ */ new Set([
1158
+ "liang-cluster",
1159
+ "kobak-density",
1160
+ "promo-travel",
1161
+ "pivotal-role",
1162
+ "legacy-framing",
1163
+ "notability-canned",
1164
+ "buzzword-phrase",
1165
+ "faux-insight",
1166
+ "rhetorical-qa",
1167
+ "didactic-note",
1168
+ "narrative-cliche",
1169
+ "valuable-insights",
1170
+ "copula-avoidance",
1171
+ "bold-label-bullets",
1172
+ "emoji-decoration",
1173
+ "heading-inflation",
1174
+ "staccato-fragments",
1175
+ "tricolon-density",
1176
+ "transition-stacking",
1177
+ "quote-inconsistency",
1178
+ "token-cutoff",
1179
+ "setup-expansion-cadence",
1180
+ "passive-ratio",
1181
+ "low-specificity",
1182
+ "adjacent-lemma-repeat",
1183
+ "fiction-claudeism",
1184
+ "fiction-promptonym",
1185
+ "fiction-slop-phrase",
1186
+ "owner-phrase-b",
1187
+ "owner-vocab-b",
1188
+ "directive-colon-bullets",
1189
+ "teach-preach-headings",
1190
+ "by-ving-template",
1191
+ "invalid-isbn",
1192
+ "proximity-cluster",
1193
+ "escaped-markup-literal",
1194
+ // 2026.08.6: markdown-furniture rules are corroboration-weight by design —
1195
+ // their absence must never count in favour of a human verdict, because an
1196
+ // editor paste that strips formatting removes the signal entirely
1197
+ // (PROVIDER-EVAL-2026-08.md §1 honest gaps, §4.1 caveats). Their
1198
+ // classification power comes from the zero-FP escalation floors, not from
1199
+ // severity or weight.
1200
+ "markdown-bold",
1201
+ "markdown-heading",
1202
+ "markdown-furniture"
1203
+ ]);
1204
+ var ARTEFACT_CORE_CATEGORIES = /* @__PURE__ */ new Set([
1205
+ "ai-citation-markup",
1206
+ "ai-citation-token",
1207
+ "ai-utm-source",
1208
+ "reasoning-leak",
1209
+ "placeholder-token",
1210
+ "ai-placeholder",
1211
+ "pua-character",
1212
+ "math-alphanumeric"
1213
+ ]);
1214
+ var ARTEFACT_SUPPORT_CATEGORIES = /* @__PURE__ */ new Set([
1215
+ "arrow-decoration",
1216
+ "escaped-markup-literal"
1217
+ ]);
1218
+ var FORMATTING_CLUSTER_CATEGORIES = /* @__PURE__ */ new Set([
1219
+ "bold-label-bullets",
1220
+ "heading-inflation",
1221
+ "emoji-decoration",
1222
+ "arrow-decoration"
1223
+ ]);
1224
+ var V6_FURNITURE_THRESHOLDS = {
1225
+ /** R5: bullet lines per 1,000 words above which bullets alone open the gate. */
1226
+ bulletsPer1000: 10.75
1227
+ };
1228
+ var STYLOMETRIC_CATEGORIES = /* @__PURE__ */ new Set([
1229
+ "punct-distribution",
1230
+ "cross-para-burstiness",
1231
+ "fnword-trigram-entropy",
1232
+ "sentence-flatline",
1233
+ "uniformity",
1234
+ "uniform-sections",
1235
+ "uniform-list-items",
1236
+ "low-ttr",
1237
+ "smart-punct-signature",
1238
+ "em-dash-density",
1239
+ "setup-expansion-cadence",
1240
+ "passive-ratio",
1241
+ "low-specificity",
1242
+ "adjacent-lemma-repeat",
1243
+ "copula-avoidance",
1244
+ "tricolon-density",
1245
+ "staccato-fragments",
1246
+ "transition-stacking",
1247
+ "heading-inflation"
1248
+ ]);
1249
+ var AI_CITATION_TOKENS = [
1250
+ { pattern: /【\d+†L\d+(?:-L?\d+)?】/g, attribution: "deepseek" },
1251
+ { pattern: /\bgrok_render_citation_card_json\b/gi, attribution: "grok" },
1252
+ { pattern: /\bgrok-card\s+data-id\b/gi, attribution: "grok" },
1253
+ { pattern: /ppl-ai-file-upload/gi, attribution: "perplexity" },
1254
+ { pattern: /\[attached_file:\d+\]/gi, attribution: "perplexity" },
1255
+ { pattern: /\[web:\d+\]/g, attribution: "perplexity" },
1256
+ { pattern: /\[cite:\s*\d+\]/g, attribution: "gemini" },
1257
+ { pattern: /\[span_\d+\]\(start_span\)/g, attribution: "gemini" },
1258
+ { pattern: /\battributableIndex\b/g, attribution: "chatgpt" },
1259
+ { pattern: /:::writing\{/g, attribution: "chatgpt" },
1260
+ { pattern: /\bciteturn\d+(?:search|image|news|navigation)\d+/gi, attribution: "chatgpt" }
1261
+ ];
1262
+ var REASONING_LEAKS = [
1263
+ /\bthe\s+user\s+(?:wants|is\s+asking|requested|has\s+asked)\b/gi,
1264
+ /\breviewer\s+note\s*[:—-]/gi,
1265
+ /\bas\s+per\s+(?:the\s+)?(?:system\s+)?prompt\b/gi,
1266
+ /\bso\s+i\s+should\s+(?:structure|frame|word)\s+(?:the|this|my)\b/gi
1267
+ ];
1268
+ var PLACEHOLDER_TOKENS = [
1269
+ /\bINSERT_[A-Z][A-Z_]{2,40}\b/g,
1270
+ /\bPASTE_[A-Z][A-Z_]{2,40}\b/g,
1271
+ /\baccess-date\s*=\s*\d{4}-XX-XX\b/gi
1272
+ ];
1273
+ var PUA_RANGE_RE = /[\uE000-\uF8FF]/g;
1274
+ var MATH_ALPHANUMERIC_RE = /[\u{1D400}-\u{1D7FF}]/gu;
1275
+ var ARROW_CONNECTOR_RE = /(?<=\S)\s[\u2192\u2794\u27A1]\s(?=\S)/g;
1276
+ var ESCAPED_MARKUP_LITERALS = [
1277
+ /&nbsp;/g,
1278
+ /(?<!\\)\\n\\n/g
1279
+ ];
1280
+ var NEG_PARALLELISM_RE = /\bnot\s+(?:just|only|merely)\s+[^.!?\n]{2,60}?,?\s+but\s+(?:also\s+|rather\s+)?\w+/gi;
1281
+ var TRIPLED_NEGATION_RE = /\b(?:Not|No)\s+[^.!?,\n]{2,30}[.,]\s*(?:Not|no)\s+[^.!?,\n]{2,30}[.,]\s*(?:Just|just)\s+\w+/g;
1282
+ var DESPITE_CHALLENGES_RE = /\bDespite\s+(?:these|its|numerous|various|several)\s+(?:challenges|setbacks|obstacles)\b[^.!?\n]{0,120}?(?:continues?\s+to\s+(?:thrive|grow|flourish)|faces?\s+(?:several\s+|numerous\s+)?challenges)/gi;
1283
+ var METAPHOR_CLUSTER_RES = [
1284
+ /\b(?:rich\s+tapestry|tapestry\s+of)\b/gi,
1285
+ /\b(?:complex|intricate)\s+interplay\b/gi,
1286
+ /\b(?:ever-)?evolving\s+landscape\b/gi,
1287
+ /\ba\s+testament\s+to\b/gi,
1288
+ /\bbeacon\s+of\b/gi
1289
+ ];
1290
+ var PARTICIPIAL_TAIL_RE = /,\s+(?:highlighting|underscoring|reflecting|symboli[sz]ing|showcasing|emphasi[sz]ing|demonstrating|ensuring|fostering|solidifying|cementing|signaling|signalling|contributing\s+to)\b[^.!?\n]{5,80}[.!?]/g;
1291
+ var FOCAL_WORD_RE = /\b(?:delv(?:e|es|ed|ing)|showcas(?:e|es|ing)|boasts?|underscor(?:e|es|ing)|intricac(?:y|ies)|intricate(?:ly)?|surpass(?:es|ing)|garner(?:ed|s)?|emphasi[sz]ing|groundbreaking|meticulous(?:ly)?|commendable|pivotal|elucidat(?:e|es|ing)|advancements)\b/gi;
1292
+ var OWNER_PHRASES = [
1293
+ /\blook\s+no\s+further\b/gi,
1294
+ /\bcomprehensive\s+suite\s+of\b/gi,
1295
+ /\btailored\s+solutions?\b/gi,
1296
+ /\bunlock(?:ing)?\s+(?:its|your|their|the)\s+full\s+potential\b/gi,
1297
+ /\bbefore\s+diving\s+in(?:to)?\b/gi,
1298
+ /\bcommon\s+pitfalls\s+and\s+how\s+to\s+avoid\s+them\b/gi,
1299
+ /\b(?:break(?:ing)?\s+(?:down\s+)?the\s+process\s+(?:down\s+)?into|process\s+can\s+be\s+broken\s+down\s+into)\b/gi,
1300
+ /\bunderstanding\s+your\s+starting\s+point\b/gi,
1301
+ /\bwhile\s+it\s+might\s+seem\s+counterintuitive\b/gi,
1302
+ /\bcommon\s+choices\s+include\b/gi,
1303
+ /\bwhere\s+things\s+get\s+interesting\b/gi,
1304
+ /\bright\?\s*Well,\s+not\s+any\s*more\b/gi,
1305
+ /\bthe\s+future\s+of\s+[\w\s]{1,30}\s+is\s+increasingly\s+(?:shaped|driven|defined)\s+by\b/gi,
1306
+ /\btake\s+action\s+now\b/gi,
1307
+ /\banother\s+(?:important|key|crucial)\s+(?:aspect|factor|thing|element|point)\s+to\s+consider\b/gi,
1308
+ /\bby\s+doing\s+so,?\s+you(?:['’]ll|\s+can|\s+will)\b/gi,
1309
+ /\bit['’]?s\s+(?:crucial|important|essential)\s+to\s+remember\b/gi,
1310
+ /\bhere\s+are\s+(?:\d+|five|six|seven|eight|nine|ten)\s+(?:simple\s+|key\s+|essential\s+)?(?:steps|things|ways|tips|strategies|best\s+practices|key\s+elements)\b/gi,
1311
+ /\bkey\s+(?:elements|aspects|factors|components|considerations)\s+include\b/gi,
1312
+ /\b(?:to\s+)?put\s+it\s+simply\b/gi,
1313
+ /\bescape\s+the\s+stresses\s+of\s+daily\s+life\b/gi,
1314
+ /\bworks?\s+wonders\b/gi
1315
+ ];
1316
+ var POWER_VERB_COMPOUND_RE = /\b(?:ensur(?:e|es|ing)|leverag(?:e|es|ing)|prioriti[sz](?:e|es|ing)|harness(?:es|ing)?|enhanc(?:e|es|ing)|capitali[sz](?:e|es|ing)\s+on|deliver(?:s|ing)?)\s+(?:\w+\s+){0,3}(?:holistic|seamless|robust|sustainable|scalable|exceptional|strategic|transformative)\b/gi;
1317
+ var OUTCOME_TAIL_RE = /,\s*leading\s+to\s+(?:increased|improved|enhanced|greater|better)\s+\w+/gi;
1318
+ var CONCLUSION_CTA_RE = /\bby\s+following\s+these\s+(?:steps|tips|strategies|best\s+practices)\b[^.!?\n]{0,80}?\b(?:boost|improve|enhance|transform|elevate|significantly)\w*/gi;
1319
+ var LIANG_CLUSTER_RE = /\b(?:versatile|ingenious|methodical(?:ly)?|insightful|laudable|admirable|profound|intriguing(?:ly)?|cogent|lucid|noteworthy|thoughtfully|judiciously|elegantly|compellingly|synergistically|succinctly|comprehensively|strategically|aptly|hitherto|herein)\b/gi;
1320
+ var KOBAK_CLUSTER_RE = /\b(?:notable|noteworthy|unparalleled|invaluable|culminating|thereby|garnered|surpassing|groundbreaking|commendable|advancements|encompass(?:es|ing)?)\b/gi;
1321
+ var PROMO_TRAVEL_RE = /\b(?:in\s+the\s+heart\s+of|breathtaking|rich\s+cultural\s+heritage|treasure\s+trove|diverse\s+array|must-visit|hidden\s+gem)\b/gi;
1322
+ var PIVOTAL_ROLE_RE = /\bplays?\s+a\s+(?:crucial|pivotal|vital|key)\s+role\s+in(?:\s+shaping)?\b/gi;
1323
+ var LEGACY_FRAMING_RE = /\b(?:pivotal\s+moment|enduring\s+legacy|indelible\s+mark|key\s+turning\s+point|setting\s+the\s+stage\s+for|deeply\s+rooted)\b/gi;
1324
+ var NOTABILITY_CANNED_RE = /\b(?:profiled\s+in\s+multiple\s+outlets|independent\s+coverage|active\s+social\s+media\s+presence|widely-read\s+outlets)\b/gi;
1325
+ var BUZZWORD_PHRASES = [
1326
+ /\bunlock(?:ing)?\s+the\s+(?:potential|power)\s+of\b/gi,
1327
+ /\bharness(?:ing)?\s+the\s+power\s+of\b/gi,
1328
+ /\bembark(?:ing)?\s+on\s+a\s+journey\b/gi,
1329
+ /\bat\s+the\s+forefront\s+of\b/gi,
1330
+ /\bbridg(?:e|ing)\s+the\s+gap\s+between\b/gi,
1331
+ /\bpav(?:e|ing)\s+the\s+way\s+for\b/gi,
1332
+ /\bpush(?:ing)?\s+the\s+boundaries\s+of\b/gi,
1333
+ /\bnavigat(?:e|ing)\s+the\s+complexit(?:y|ies)\b/gi,
1334
+ /\btake\s+(?:it|things)\s+to\s+the\s+next\s+level\b/gi
1335
+ ];
1336
+ var FAUX_INSIGHT_RE = /\b(?:here['’]?s\s+what\s+nobody\s+tells\s+you|what\s+most\s+people\s+get\s+wrong|here['’]?s\s+the\s+kicker|plot\s+twist:|the\s+part\s+everyone\s+misses)\b/gi;
1337
+ var RHETORICAL_QA_RE = /\b[Tt]he\s+(?:result|goal|answer|solution|problem|catch|best\s+part)\?\s+[A-Z]/g;
1338
+ var DIDACTIC_NOTE_RE = /\b(?:it['’]?s\s+(?:important|crucial|essential)\s+to\s+(?:understand|recognise|recognize)|(?:results|experiences|mileage)\s+may\s+vary|it\s+should\s+be\s+noted)\b/gi;
1339
+ var NARRATIVE_CLICHE_RE = /\b(?:faced\s+numerous\s+challenges|newfound\s+sense\s+of\s+purpose|poignant\s+reminder|serves\s+as\s+a\s+(?:powerful|poignant)\s+reminder)\b/gi;
1340
+ var VALUABLE_INSIGHTS_RE = /\b(?:provid(?:es?|ing)\s+valuable\s+insights?\s+into|key\s+takeaways?\b)/gi;
1341
+ var COPULA_ALTERNATIVE_RE = /\b(?:serves?|stands?|functions?|operates?)\s+as\s+(?:a|an|the)\b/gi;
1342
+ var BOLD_LABEL_BULLET_RE = /^\s*[-*+•]\s*\*\*[^*\n]{2,40}\*\*[:.]?\s/;
1343
+ var EMOJI_DECOR_RE = /[\u{1F680}\u{2728}\u{1F9E0}\u{2705}\u{1F449}\u{1F4A1}\u{1F3AF}\u{1F525}\u{1F4CC}\u{1F4C8}\u{26A1}\u{1F511}]/u;
1344
+ var RITUAL_HEADING_RE = /^(?:#{1,6}[ \t]+\S.*|<h[1-6][^>]*>.*)$/gim;
1345
+ var STACCATO_MAX_WORDS = 4;
1346
+ var TRANSITION_OPENER_RE = /^\s*(?:Additionally|Moreover|Furthermore|Subsequently|In\s+addition|What['’]s\s+more)\b/i;
1347
+ var FICTION_CLAUDEISM_RE = /\b(?:ministrations|audible\s+pop|rivulets\s+of|half-lidded\s+eyes|despite\s+(?:herself|himself)|with\s+reckless\s+abandon|knuckles\s+(?:turning|turned)\s+white|chuckl(?:es|ed)\s+darkly)\b/gi;
1348
+ var FICTION_PROMPTONYM_RE = /\b(?:Elara\s+(?:Voss|Vex)|Aris\s+Thorne|Elias\s+Vance|Whispering\s+(?:Woods|Pines|Hollow)|Eldoria)\b/g;
1349
+ var FICTION_SLOP_RE = /\b(?:took\s+a\s+deep\s+breath|voice\s+barely\s+above\s+a\s+whisper|couldn['’]?t\s+help\s+but\s+feel|casting\s+long\s+shadows|shivers?\s+(?:ran|run(?:ning)?)\s+down\s+(?:my|her|his|their)\s+spine|heart\s+pounding\s+in\s+(?:her|his|my)\s+chest|the\s+room\s+fell\s+silent|days\s+turned\s+into\s+weeks|maybe,\s+just\s+maybe|little\s+did\s+(?:she|he|they)\s+know|unbeknownst\s+to\s+(?:them|her|him))\b/gi;
1350
+ var OWNER_PHRASES_B = [
1351
+ /\bshed(?:s|ding)?\s+light\s+on\b/gi,
1352
+ /\ba\s+popular\s+choice\s+for\b/gi,
1353
+ /\bconsult(?:ing)?\s+with\s+a\s+(?:professional|specialist|qualified)\b/gi,
1354
+ /\bas\s+a\s+professional\s*,/gi,
1355
+ /\bstay(?:ing)?\s+ahead\s+of\s+the\s+curve\b/gi,
1356
+ /\bI\s+am\s+not\s+a\s+(?:lawyer|doctor|financial\s+advisor|professional)\s*,?\s+but\b/gi,
1357
+ /\bsafety\s+should\s+never\s+be\s+(?:overlooked|compromised)\b/gi,
1358
+ /\bpresent\s+challenges\s+due\s+to\b/gi,
1359
+ /\bextending\s+their\s+lifespan\b/gi,
1360
+ /\beven\s+the\s+best\s+plans\s+can\b/gi
1361
+ ];
1362
+ var OWNER_VOCAB_B_RE = /\b(?:essence|facets?|exhaustive|pesky|folks)\b/gi;
1363
+ var DIRECTIVE_COLON_BULLET_RE = /^\s*(?:[-*+•]|\d+[.)])\s*(?:Plan|Ensure|Optimi[sz]e|Enhance|Leverage|Prioriti[sz]e|Implement|Utili[sz]e|Consider|Embrace)\b[^.:\n]{0,50}:/;
1364
+ var TEACH_PREACH_HEADING_RE = /^#{1,6}\s+(?:why\s+(?:it|this)\s+(?:matters|is\s+important)|how\s+to\s+get\s+started|final\s+thoughts|key\s+takeaways|common\s+pitfalls)\s*\??\s*$/gim;
1365
+ var BY_VING_TEMPLATE_RE = /\bBy\s+\w+ing\s+[^,.\n]{5,60},\s+you\s+can\s+\w+/g;
1366
+ var PASSIVE_RE = /\b(?:is|are|was|were|been|being|be)\s+(?:\w+ly\s+)?\w{3,}(?:ed|en|wn|lt)\b/gi;
1367
+ var V3_ISSUE_WEIGHTS = {
1368
+ // artefact forensics
1369
+ "ai-citation-token": 15,
1370
+ "reasoning-leak": 12,
1371
+ "placeholder-token": 10,
1372
+ "pua-character": 14,
1373
+ "math-alphanumeric": 12,
1374
+ "arrow-decoration": 4,
1375
+ "escaped-markup-literal": 3,
1376
+ // tier A phrase/structural
1377
+ "neg-parallelism": 5,
1378
+ "tripled-negation": 5,
1379
+ "despite-challenges-arc": 5,
1380
+ "metaphor-cluster": 4,
1381
+ "participial-tail": 5,
1382
+ "focal-density": 5,
1383
+ "owner-phrase": 5,
1384
+ "power-verb-compound": 6,
1385
+ "outcome-tail": 4,
1386
+ "conclusion-cta": 6,
1387
+ // tier B (low weights; corroboration)
1388
+ "liang-cluster": 2,
1389
+ "kobak-density": 2,
1390
+ "promo-travel": 2,
1391
+ "pivotal-role": 2,
1392
+ "legacy-framing": 3,
1393
+ "notability-canned": 2,
1394
+ "buzzword-phrase": 2,
1395
+ "faux-insight": 2,
1396
+ "rhetorical-qa": 2,
1397
+ "didactic-note": 2,
1398
+ "narrative-cliche": 3,
1399
+ "valuable-insights": 2,
1400
+ "copula-avoidance": 3,
1401
+ "bold-label-bullets": 3,
1402
+ "emoji-decoration": 2,
1403
+ "heading-inflation": 3,
1404
+ "staccato-fragments": 3,
1405
+ "tricolon-density": 2,
1406
+ "transition-stacking": 3,
1407
+ "quote-inconsistency": 2,
1408
+ "token-cutoff": 2,
1409
+ "setup-expansion-cadence": 3,
1410
+ "passive-ratio": 3,
1411
+ "low-specificity": 2,
1412
+ "adjacent-lemma-repeat": 3,
1413
+ "fiction-claudeism": 3,
1414
+ "fiction-promptonym": 3,
1415
+ "fiction-slop-phrase": 2,
1416
+ "owner-phrase-b": 2,
1417
+ "owner-vocab-b": 2,
1418
+ "directive-colon-bullets": 3,
1419
+ "teach-preach-headings": 2,
1420
+ "by-ving-template": 3,
1421
+ "invalid-isbn": 3,
1422
+ "proximity-cluster": 2,
1423
+ // 2026.08.6 furniture rules (low weights; the escalation floors carry the
1424
+ // detection, and the weights stay small so furniture cannot fake breadth).
1425
+ "markdown-bold": 3,
1426
+ "markdown-heading": 3,
1427
+ "markdown-furniture": 4
1428
+ };
1429
+ var PASTE = "This only shows up when chat formatting survives the paste. If an editor stripped the formatting, the check finds nothing, which says nothing either way.";
1430
+ var FORMAL = "Formal writing, and writing by people whose first language is not English, can read this way too.";
1431
+ var V3_CATEGORY_META = {
1432
+ "ai-citation-token": { severity: "high", message: "A chatbot's own citation code is sitting in the text. The code itself says which chatbot it came from.", suggestion: "Delete the code and put a real reference in its place." },
1433
+ "reasoning-leak": { severity: "high", message: 'Parts of this text talk about the writing job itself, the kind of notes an AI leaves in its answer, like "as requested" or "let me revise".', suggestion: "Delete those notes and keep the finished writing." },
1434
+ "placeholder-token": { severity: "high", message: 'A machine placeholder is still in the text, something like "{{name}}" or "<insert>".', suggestion: "Fill it in or delete it before this goes out." },
1435
+ "pua-character": { severity: "high", message: "The text holds characters from a private corner of Unicode that has no agreed meaning. ChatGPT wraps its citation codes in these. Icon fonts are the only other common reason for them.", suggestion: "Delete them, and look for chatbot citation codes next to them." },
1436
+ "math-alphanumeric": { severity: "high", message: "Fake bold or italic letters built from maths symbols (\u{1D5F9}\u{1D5F6}\u{1D5F8}\u{1D5F2} \u{1D601}\u{1D5F5}\u{1D5F6}\u{1D600}). They come from chatbot copy-paste and social-media text formatters.", suggestion: "Retype them as ordinary letters and use real bold or italic." },
1437
+ "arrow-decoration": { severity: "medium", message: 'Arrows stand in for words again and again: "input \u2192 output \u2192 result".', suggestion: "Write the connection out in words." },
1438
+ "escaped-markup-literal": { severity: "low", message: 'A stray scrap of code is showing through the text, like "&nbsp;" or "\\n". It usually comes from pasting out of a chat window.', suggestion: "Delete it." },
1439
+ "neg-parallelism": { severity: "medium", message: 'The "not only X but Y" shape comes back more than once.', suggestion: "Keep one. Say the rest plainly." },
1440
+ "tripled-negation": { severity: "medium", message: 'The "Not X. Not Y. Just Z." shape.', suggestion: "Say what it is, without the three-part build-up." },
1441
+ "despite-challenges-arc": { severity: "medium", message: 'The stock "despite challenges, it continues to thrive" story shape.', suggestion: "Name the real problem and what was really done about it." },
1442
+ "metaphor-cluster": { severity: "medium", message: 'Several worn-out picture words are stacked together: "tapestry", "interplay", "evolving landscape", "testament to".', suggestion: "Say the plain facts they are standing in for." },
1443
+ "participial-tail": { severity: "medium", message: 'Sentences keep ending with a tacked-on "-ing" clause that tells you why it mattered: ", highlighting the need for\u2026", ", underscoring the importance of\u2026". That ending turns up several times more often in machine writing than in human writing.', suggestion: "Stop the sentence at the fact. Cut the tail, or turn it into a claim you can source." },
1444
+ "focal-density": { severity: "medium", message: 'A lot of words from the AI-favourite list turn up here: "delve", "showcase", "pivotal", "meticulous". Any one of them is normal English. It is how many there are that stands out.', suggestion: "Swap most of them for plainer verbs and adjectives." },
1445
+ "owner-phrase": { severity: "medium", message: "A ready-made phrase from the generic-writing phrasebook.", suggestion: "Replace it with something specific to your subject." },
1446
+ "power-verb-compound": { severity: "high", message: 'A big verb glued to a vague adjective: "leverage a robust solution", "ensure seamless delivery". It sounds like value and says nothing.', suggestion: "Name the real action and the thing you can measure." },
1447
+ "outcome-tail": { severity: "medium", message: 'The sentence trails off into a vague result: ", leading to increased engagement".', suggestion: "Say the exact result, or cut the tail." },
1448
+ "conclusion-cta": { severity: "high", message: 'The stock marketing sign-off: "by following these steps you can boost\u2026".', suggestion: "Close on something specific, not a general promise." },
1449
+ "liang-cluster": { severity: "low", message: 'Several judgement words from the AI-overuse lists sit close together, like "crucial", "notable" and "significant".', suggestion: "Keep the judgements you can back up with detail." },
1450
+ "kobak-density": { severity: "low", message: "Several words sit here that turned up far more often in machine writing when a large body of text was counted.", suggestion: "Vary the words, or back the claims with detail." },
1451
+ "promo-travel": { severity: "low", message: 'Brochure words are bunched together: "nestled", "breathtaking", "hidden gem". They stand out most when the piece is not a travel brochure.', suggestion: "Describe the place or the product with real detail." },
1452
+ "pivotal-role": { severity: "low", message: 'The "plays a crucial role in shaping" formula.', suggestion: "Say what it actually does." },
1453
+ "legacy-framing": { severity: "low", message: 'Several grand phrases about legacy and importance are stacked up: "enduring legacy", "pivotal moment".', suggestion: "Let the events speak for themselves." },
1454
+ "notability-canned": { severity: "low", message: 'A canned line about how well known something is: "profiled in multiple outlets".', suggestion: "Name the outlets, or drop the claim." },
1455
+ "buzzword-phrase": { severity: "low", message: 'A stock office phrase: "harness the power of", "at the forefront of".', suggestion: "Say what it can actually do." },
1456
+ "faux-insight": { severity: "low", message: `A line promising a secret: "here's what nobody tells you".`, suggestion: "Show the point. Do not announce it." },
1457
+ "rhetorical-qa": { severity: "low", message: 'The "The result? X." trick keeps coming back.', suggestion: "Use it once at most." },
1458
+ "didactic-note": { severity: "low", message: `A teacherly disclaimer: "it's important to understand", "results may vary".`, suggestion: "Cut it, or make it specific." },
1459
+ "narrative-cliche": { severity: "low", message: 'A worn-out story phrase: "faced numerous challenges", "a poignant reminder".', suggestion: "Say what actually happened." },
1460
+ "valuable-insights": { severity: "low", message: 'A stock academic filler phrase: "provides valuable insights into".', suggestion: "State the insight itself." },
1461
+ "copula-avoidance": { severity: "low", message: 'The text keeps dodging plain "is" and "has" in favour of "serves as", "stands as" and "functions as". ' + FORMAL, suggestion: 'Use "is" and "has" where they fit.' },
1462
+ "bold-label-bullets": { severity: "low", message: 'A run of bullets all shaped "**Label:** description". Technical documents use this shape too.', suggestion: "Turn it into sentences, or vary the shape of the items." },
1463
+ "emoji-decoration": { severity: "low", message: "Emoji are used on headings or bullets. That is a chat-window habit in a piece of business writing.", suggestion: "Take the decorative emoji out of this kind of writing." },
1464
+ "heading-inflation": { severity: "low", message: "There are a lot of headings for the amount of writing under them. SEO advice produces the same shape.", suggestion: "Merge sections whose body is only a sentence or two." },
1465
+ "staccato-fragments": { severity: "low", message: "A run of very short, punchy fragments. Ad copy does this on purpose too.", suggestion: "Join some of them into full sentences." },
1466
+ "tricolon-density": { severity: "low", message: 'Three-part lists such as "faster, cheaper, simpler" turn up a lot for a piece this long.', suggestion: "Break the pattern: use two items, or four." },
1467
+ "transition-stacking": { severity: "low", message: 'Most paragraphs open on a formal joining word: "Furthermore", "Moreover", "Additionally". ' + FORMAL, suggestion: "Let the content do the joining in most paragraphs." },
1468
+ "quote-inconsistency": { severity: "low", message: "Curly and straight quotation marks are mixed together. That usually comes from pasting out of a chat window, though word processors do it too.", suggestion: "Make the quotation marks match, either way round." },
1469
+ "token-cutoff": { severity: "low", message: "The text stops in the middle of a sentence, the shape of an answer that ran out of room, or a paste that went wrong.", suggestion: "Finish the last sentence, or delete it." },
1470
+ "setup-expansion-cadence": { severity: "low", message: "Short sentence, then long one, over and over, or the other way round. " + FORMAL, suggestion: "Keep the pattern only where the short sentence works on its own." },
1471
+ "passive-ratio": { severity: "low", message: 'A lot of the sentences hide who did the thing: "mistakes were made". That is high for a blog or a marketing piece. Academic writing does it constantly. ' + FORMAL, suggestion: "Rewrite most sentences so the person or thing doing it comes first." },
1472
+ "low-specificity": { severity: "low", message: "Almost no numbers, dates or names for a piece this long. Corporate writers produce empty writing too.", suggestion: "Add facts a reader could go and check." },
1473
+ "adjacent-lemma-repeat": { severity: "low", message: "Neighbouring sentences keep reusing the same word. " + FORMAL, suggestion: "Merge the repetitive sentences, or change the wording where it reads naturally." },
1474
+ "fiction-claudeism": { severity: "low", message: "Phrases that turn up a lot in Claude's fiction writing. Romance authors use several of them quite normally.", suggestion: "Put the stock phrases into your own words." },
1475
+ "fiction-promptonym": { severity: "low", message: 'A character name that AI writing produces far more often than people do. "Elara Voss" is the best-known one.', suggestion: "Pick a less loaded name if you want one." },
1476
+ "fiction-slop-phrase": { severity: "low", message: "Several well-worn fiction lines land in the same piece. All of them were human clich\xE9s long before AI.", suggestion: "Cut or rework the stock moments." },
1477
+ "owner-phrase-b": { severity: "low", message: "A phrase from the second generic-writing phrasebook.", suggestion: "Replace it with something specific." },
1478
+ "owner-vocab-b": { severity: "low", message: 'Several second-string filler words sit close together: "essence", "facet", "pesky", "folks".', suggestion: "Swap them for plainer words where they add nothing." },
1479
+ "directive-colon-bullets": { severity: "low", message: 'Several list items open on an order and a colon: "Ensure X:", "Optimise Y:". Real technical checklists look like this too.', suggestion: "Vary the way the items are built." },
1480
+ "teach-preach-headings": { severity: "low", message: 'Stock tutorial headings hold the piece together: "Why it matters", "Final thoughts", "Key takeaways".', suggestion: "Name each section after what is actually in it." },
1481
+ "by-ving-template": { severity: "low", message: 'The "By doing X, you can Y" shape keeps coming back.', suggestion: "Say the benefit straight out most of the time." },
1482
+ "invalid-isbn": { severity: "low", message: "An ISBN in the text does not add up. Made-up references often fail this check, and so do typos.", suggestion: "Check the reference against the real book." },
1483
+ "proximity-cluster": { severity: "low", message: "The same flagged buzzword comes back within a sentence or two.", suggestion: "Keep one use at most in each passage." },
1484
+ // 2026.08.6 chat-formatting rules. Each one states the paste caveat: the
1485
+ // check can only see formatting that survived the paste, so its ABSENCE says
1486
+ // nothing about who wrote the text.
1487
+ "markdown-bold": { severity: "low", message: "Raw **bold** markdown is showing in the text. None of the 169 human-written documents we checked had it. " + PASTE, suggestion: "Delete the stars, or apply real bold." },
1488
+ "markdown-heading": { severity: "low", message: "A raw markdown heading line, the kind that starts with # signs, is showing in the text. None of the 169 human-written documents we checked had one. " + PASTE, suggestion: "Turn it into a real heading, or delete it." },
1489
+ "markdown-furniture": { severity: "low", message: "Chat-window formatting shapes this text: runs of bold, heading lines, or a wall of bullets. None of the 169 human-written documents we checked had that combination. " + PASTE, suggestion: "Rebuild the formatting properly for wherever this is going." }
1490
+ };
1491
+ function execAll(pattern, text) {
1492
+ const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
1493
+ const out = [];
1494
+ let m;
1495
+ while ((m = re.exec(text)) !== null) {
1496
+ out.push(m);
1497
+ if (m[0].length === 0) re.lastIndex += 1;
1498
+ }
1499
+ return out;
1500
+ }
1501
+ function countWords(text) {
1502
+ return (text.match(/\S+/g) ?? []).length;
1503
+ }
1504
+ function pushDistinctCluster(ctx, pattern, category, minDistinct) {
1505
+ const matches = execAll(pattern, ctx.text);
1506
+ const distinct = new Set(matches.map((m) => m[0].toLowerCase()));
1507
+ if (distinct.size < minDistinct) return;
1508
+ const seen = /* @__PURE__ */ new Set();
1509
+ for (const m of matches) {
1510
+ const lower = m[0].toLowerCase();
1511
+ if (seen.has(lower)) continue;
1512
+ seen.add(lower);
1513
+ ctx.push(category, lower, m.index, m.index + m[0].length);
1514
+ }
1515
+ }
1516
+ function pushCountThreshold(ctx, pattern, category, minCount) {
1517
+ const matches = execAll(pattern, ctx.text);
1518
+ if (matches.length < minCount) return;
1519
+ for (const m of matches) ctx.push(category, m[0], m.index, m.index + m[0].length);
1520
+ }
1521
+ function isbn10Valid(digits) {
1522
+ let sum = 0;
1523
+ for (let i = 0; i < 10; i += 1) {
1524
+ const ch = digits[i];
1525
+ const val = ch === "X" || ch === "x" ? 10 : ch.charCodeAt(0) - 48;
1526
+ if (val < 0 || val > 10) return false;
1527
+ sum += val * (10 - i);
1528
+ }
1529
+ return sum % 11 === 0;
1530
+ }
1531
+ function isbn13Valid(digits) {
1532
+ let sum = 0;
1533
+ for (let i = 0; i < 13; i += 1) {
1534
+ const val = digits.charCodeAt(i) - 48;
1535
+ if (val < 0 || val > 9) return false;
1536
+ sum += val * (i % 2 === 0 ? 1 : 3);
1537
+ }
1538
+ return sum % 10 === 0;
1539
+ }
1540
+ function collectV3Issues(ctx) {
1541
+ const { text, wordCount, paragraphs, sentences, push, pushEx, pushPatterns } = ctx;
1542
+ const perThousand = (n) => n / (wordCount / 1e3);
1543
+ for (const { pattern, attribution } of AI_CITATION_TOKENS) {
1544
+ for (const m of execAll(pattern, text)) {
1545
+ pushEx("ai-citation-token", m[0], m.index, m.index + m[0].length, {
1546
+ extra: { attribution }
1547
+ });
1548
+ }
1549
+ }
1550
+ pushPatterns(REASONING_LEAKS, "reasoning-leak");
1551
+ pushPatterns(PLACEHOLDER_TOKENS, "placeholder-token");
1552
+ pushPatterns(ESCAPED_MARKUP_LITERALS, "escaped-markup-literal");
1553
+ {
1554
+ const pua = execAll(PUA_RANGE_RE, text);
1555
+ if (pua.length > 0) {
1556
+ const first = pua[0];
1557
+ pushEx("pua-character", `${pua.length} private-use character(s)`, first.index, first.index + first[0].length, { count: pua.length });
1558
+ }
1559
+ const math = execAll(MATH_ALPHANUMERIC_RE, text);
1560
+ if (math.length > 0) {
1561
+ const first = math[0];
1562
+ pushEx("math-alphanumeric", `${math.length} mathematical-alphanumeric character(s)`, first.index, first.index + first[0].length, { count: math.length });
1563
+ }
1564
+ const arrows = execAll(ARROW_CONNECTOR_RE, text);
1565
+ if (arrows.length >= 3) {
1566
+ const first = arrows[0];
1567
+ pushEx("arrow-decoration", `${arrows.length} arrow connectors`, first.index, first.index + first[0].length, { count: arrows.length });
1568
+ }
1569
+ }
1570
+ pushCountThreshold(ctx, NEG_PARALLELISM_RE, "neg-parallelism", 2);
1571
+ pushPatterns([TRIPLED_NEGATION_RE], "tripled-negation");
1572
+ pushPatterns([DESPITE_CHALLENGES_RE], "despite-challenges-arc");
1573
+ {
1574
+ const found = [];
1575
+ for (const re of METAPHOR_CLUSTER_RES) {
1576
+ const m = execAll(re, text)[0];
1577
+ if (m) found.push({ key: m[0].toLowerCase(), index: m.index, len: m[0].length });
1578
+ }
1579
+ if (found.length >= 2) {
1580
+ for (const f of found) push("metaphor-cluster", f.key, f.index, f.index + f.len);
1581
+ }
1582
+ }
1583
+ {
1584
+ const tails = execAll(PARTICIPIAL_TAIL_RE, text);
1585
+ if (tails.length >= 3 && (wordCount < 1e3 || perThousand(tails.length) >= 3)) {
1586
+ for (const m of tails) push("participial-tail", m[0].slice(0, 60), m.index, m.index + m[0].length);
1587
+ }
1588
+ }
1589
+ {
1590
+ const hits = execAll(FOCAL_WORD_RE, text);
1591
+ if (hits.length >= 3 && perThousand(hits.length) >= 3) {
1592
+ const first = hits[0];
1593
+ pushEx("focal-density", `${hits.length} focal-lexicon hits in ${wordCount} words`, first.index, first.index + first[0].length, {
1594
+ count: hits.length,
1595
+ extra: { rate_per_1000_words: Math.round(perThousand(hits.length) * 10) / 10 }
1596
+ });
1597
+ }
1598
+ }
1599
+ pushPatterns(OWNER_PHRASES, "owner-phrase");
1600
+ pushPatterns([POWER_VERB_COMPOUND_RE], "power-verb-compound");
1601
+ pushPatterns([OUTCOME_TAIL_RE], "outcome-tail");
1602
+ pushPatterns([CONCLUSION_CTA_RE], "conclusion-cta");
1603
+ pushDistinctCluster(ctx, LIANG_CLUSTER_RE, "liang-cluster", 3);
1604
+ pushDistinctCluster(ctx, KOBAK_CLUSTER_RE, "kobak-density", 4);
1605
+ pushDistinctCluster(ctx, PROMO_TRAVEL_RE, "promo-travel", 2);
1606
+ pushPatterns([PIVOTAL_ROLE_RE], "pivotal-role");
1607
+ pushDistinctCluster(ctx, LEGACY_FRAMING_RE, "legacy-framing", 2);
1608
+ pushPatterns([NOTABILITY_CANNED_RE], "notability-canned");
1609
+ pushPatterns(BUZZWORD_PHRASES, "buzzword-phrase");
1610
+ pushPatterns([FAUX_INSIGHT_RE], "faux-insight");
1611
+ pushCountThreshold(ctx, RHETORICAL_QA_RE, "rhetorical-qa", 2);
1612
+ pushPatterns([DIDACTIC_NOTE_RE], "didactic-note");
1613
+ pushPatterns([NARRATIVE_CLICHE_RE], "narrative-cliche");
1614
+ pushPatterns([VALUABLE_INSIGHTS_RE], "valuable-insights");
1615
+ pushDistinctCluster(ctx, FICTION_CLAUDEISM_RE, "fiction-claudeism", 2);
1616
+ pushPatterns([FICTION_PROMPTONYM_RE], "fiction-promptonym");
1617
+ pushDistinctCluster(ctx, FICTION_SLOP_RE, "fiction-slop-phrase", 2);
1618
+ pushPatterns(OWNER_PHRASES_B, "owner-phrase-b");
1619
+ pushDistinctCluster(ctx, OWNER_VOCAB_B_RE, "owner-vocab-b", 2);
1620
+ pushPatterns([TEACH_PREACH_HEADING_RE], "teach-preach-headings");
1621
+ pushCountThreshold(ctx, BY_VING_TEMPLATE_RE, "by-ving-template", 2);
1622
+ {
1623
+ const alts = execAll(COPULA_ALTERNATIVE_RE, text);
1624
+ if (alts.length >= 3) {
1625
+ const copulas = (text.match(/\b(?:is|are)\b/gi) ?? []).length;
1626
+ const ratio = alts.length / (alts.length + copulas);
1627
+ if (ratio > 0.25) {
1628
+ const first = alts[0];
1629
+ pushEx("copula-avoidance", `${alts.length} copula alternatives vs ${copulas} is/are (${Math.round(ratio * 100)}%)`, first.index, first.index + first[0].length, {
1630
+ count: alts.length,
1631
+ extra: { copula_count: copulas, alternative_ratio: Math.round(ratio * 100) / 100 }
1632
+ });
1633
+ }
1634
+ }
1635
+ }
1636
+ const lines = text.split(/\r?\n/);
1637
+ {
1638
+ let offset = 0;
1639
+ let boldRun = 0;
1640
+ let boldRunStart = -1;
1641
+ let boldRunEnd = -1;
1642
+ let emojiLines = 0;
1643
+ let firstEmojiAt = -1;
1644
+ let firstEmojiEnd = -1;
1645
+ let directiveHits = 0;
1646
+ let firstDirectiveAt = -1;
1647
+ let firstDirectiveEnd = -1;
1648
+ const flushBold = () => {
1649
+ if (boldRun >= 3) {
1650
+ pushEx("bold-label-bullets", `${boldRun} bold-label bullets`, boldRunStart, boldRunEnd, { count: boldRun });
1651
+ }
1652
+ boldRun = 0;
1653
+ boldRunStart = -1;
1654
+ boldRunEnd = -1;
1655
+ };
1656
+ for (const line of lines) {
1657
+ if (BOLD_LABEL_BULLET_RE.test(line)) {
1658
+ if (boldRun === 0) boldRunStart = offset + (line.length - line.trimStart().length);
1659
+ boldRunEnd = offset + line.replace(/\s+$/, "").length;
1660
+ boldRun += 1;
1661
+ } else if (line.trim() !== "") {
1662
+ flushBold();
1663
+ }
1664
+ const isHeadingOrBullet = /^\s*(?:#{1,6}[ \t]|[-*+•]\s|\d+[.)]\s)/.test(line);
1665
+ if (isHeadingOrBullet) {
1666
+ const emoji = EMOJI_DECOR_RE.exec(line);
1667
+ if (emoji !== null) {
1668
+ emojiLines += 1;
1669
+ if (firstEmojiAt < 0) {
1670
+ firstEmojiAt = offset + emoji.index;
1671
+ firstEmojiEnd = firstEmojiAt + emoji[0].length;
1672
+ }
1673
+ }
1674
+ }
1675
+ const directive = DIRECTIVE_COLON_BULLET_RE.exec(line);
1676
+ if (directive !== null) {
1677
+ directiveHits += 1;
1678
+ if (firstDirectiveAt < 0) {
1679
+ firstDirectiveAt = offset + directive.index;
1680
+ firstDirectiveEnd = firstDirectiveAt + directive[0].length;
1681
+ }
1682
+ }
1683
+ offset += line.length + 1;
1684
+ }
1685
+ flushBold();
1686
+ if (emojiLines >= 3) {
1687
+ pushEx("emoji-decoration", `${emojiLines} emoji-decorated headings/bullets`, firstEmojiAt, firstEmojiEnd, { count: emojiLines });
1688
+ }
1689
+ if (directiveHits >= 3) {
1690
+ pushEx("directive-colon-bullets", `${directiveHits} directive-colon list items`, firstDirectiveAt, firstDirectiveEnd, { count: directiveHits });
1691
+ }
1692
+ }
1693
+ {
1694
+ const headings = execAll(RITUAL_HEADING_RE, text);
1695
+ if (headings.length >= 4 && wordCount >= 60 && headings.length / (wordCount / 300) > 3) {
1696
+ const first = headings[0];
1697
+ pushEx("heading-inflation", `${headings.length} headings in ${wordCount} words`, first.index, first.index + first[0].length, {
1698
+ count: headings.length,
1699
+ extra: { headings_per_300_words: Math.round(headings.length / (wordCount / 300) * 10) / 10 }
1700
+ });
1701
+ }
1702
+ }
1703
+ {
1704
+ for (const para of paragraphs) {
1705
+ const paraSents = para.text.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length > 0);
1706
+ let cursor = 0;
1707
+ const located = paraSents.map((s) => {
1708
+ const at = para.text.indexOf(s, cursor);
1709
+ const start = at >= 0 ? at : cursor;
1710
+ cursor = start + s.length;
1711
+ return { text: s, start: para.start + start, end: para.start + start + s.length };
1712
+ });
1713
+ let run = 0;
1714
+ let runStart = -1;
1715
+ let fired = false;
1716
+ for (const s of located) {
1717
+ const words = countWords(s.text);
1718
+ if (words > 0 && words <= STACCATO_MAX_WORDS && /[.!?]$/.test(s.text)) {
1719
+ run += 1;
1720
+ if (run === 1) runStart = s.start;
1721
+ if (run >= 3 && !fired) {
1722
+ pushEx("staccato-fragments", `${run}+ consecutive short fragments`, runStart, s.end, { count: run });
1723
+ fired = true;
1724
+ }
1725
+ } else {
1726
+ run = 0;
1727
+ runStart = -1;
1728
+ }
1729
+ }
1730
+ }
1731
+ }
1732
+ {
1733
+ const triads = execAll(/\b\w+,\s+\w+,\s+and\s+\w+\b/g, text);
1734
+ if (triads.length >= 4 && perThousand(triads.length) > 10) {
1735
+ const first = triads[0];
1736
+ pushEx("tricolon-density", `${triads.length} balanced triads in ${wordCount} words`, first.index, first.index + first[0].length, { count: triads.length });
1737
+ }
1738
+ }
1739
+ {
1740
+ if (paragraphs.length >= 3) {
1741
+ let openers = 0;
1742
+ let consecutive = 0;
1743
+ let maxConsecutive = 0;
1744
+ let firstAt = -1;
1745
+ let firstEnd = -1;
1746
+ for (const p of paragraphs) {
1747
+ const opener = TRANSITION_OPENER_RE.exec(p.text);
1748
+ if (opener !== null) {
1749
+ openers += 1;
1750
+ consecutive += 1;
1751
+ maxConsecutive = Math.max(maxConsecutive, consecutive);
1752
+ if (firstAt < 0) {
1753
+ const lead = opener[0].length - opener[0].trimStart().length;
1754
+ firstAt = p.start + opener.index + lead;
1755
+ const tail = /^[,;:]/.exec(p.text.slice(opener.index + opener[0].length));
1756
+ firstEnd = p.start + opener.index + opener[0].length + (tail === null ? 0 : 1);
1757
+ }
1758
+ } else {
1759
+ consecutive = 0;
1760
+ }
1761
+ }
1762
+ const majority = paragraphs.length >= 4 && openers / paragraphs.length > 0.5;
1763
+ if (majority || maxConsecutive >= 3) {
1764
+ pushEx("transition-stacking", `${openers}/${paragraphs.length} paragraphs open with a formal connective`, firstAt >= 0 ? firstAt : null, firstAt >= 0 ? firstEnd : null, { count: openers });
1765
+ }
1766
+ }
1767
+ }
1768
+ {
1769
+ const curly = (text.match(/[“”]/g) ?? []).length;
1770
+ const straight = (text.match(/"/g) ?? []).length;
1771
+ if (curly >= 2 && straight >= 2) {
1772
+ pushEx("quote-inconsistency", `${curly} curly + ${straight} straight double quotes mixed`, null, null, {
1773
+ count: curly + straight,
1774
+ extra: { curly_double_quotes: curly, straight_double_quotes: straight }
1775
+ });
1776
+ }
1777
+ }
1778
+ {
1779
+ const trimmed = text.replace(/\s+$/, "");
1780
+ const lastLine = trimmed.slice(trimmed.lastIndexOf("\n") + 1);
1781
+ const looksStructural = /^\s*(?:#{1,6}[ \t]|[-*+•]\s|\d+[.)]\s|\|)/.test(lastLine) || /^```|^~~~/.test(lastLine.trim());
1782
+ if (wordCount >= 100 && trimmed.length > 0 && /[a-z,;]$/.test(trimmed) && !looksStructural && countWords(lastLine) >= 5) {
1783
+ const lineStart = trimmed.length - lastLine.length;
1784
+ const lastStop = lastLine.search(/[.!?](?=[^.!?]*$)/);
1785
+ const fragment = lastStop >= 0 ? lastLine.slice(lastStop + 1) : lastLine;
1786
+ const start = trimmed.length - fragment.length + (fragment.length - fragment.trimStart().length);
1787
+ pushEx("token-cutoff", "text ends mid-sentence", Math.max(lineStart, start), trimmed.length, {});
1788
+ }
1789
+ }
1790
+ const sentenceWordCounts = sentences.map(countWords);
1791
+ {
1792
+ if (sentenceWordCounts.length >= 8) {
1793
+ let hits = 0;
1794
+ for (let i = 0; i < sentenceWordCounts.length - 1; i += 1) {
1795
+ const a = sentenceWordCounts[i];
1796
+ const b = sentenceWordCounts[i + 1];
1797
+ if (a > 0 && a <= 6 && b >= 3 * a && b >= 12 || b > 0 && b <= 6 && a >= 3 * b && a >= 12) hits += 1;
1798
+ }
1799
+ const ratio = hits / (sentenceWordCounts.length - 1);
1800
+ if (hits >= 3 && ratio >= 0.2) {
1801
+ pushEx("setup-expansion-cadence", `${hits} setup/expansion sentence pairs (${Math.round(ratio * 100)}%)`, null, null, {
1802
+ count: hits,
1803
+ extra: { pair_ratio: Math.round(ratio * 100) / 100 }
1804
+ });
1805
+ }
1806
+ }
1807
+ }
1808
+ {
1809
+ if (sentences.length >= 10) {
1810
+ let passiveSentences = 0;
1811
+ for (const s of sentences) {
1812
+ PASSIVE_RE.lastIndex = 0;
1813
+ if (PASSIVE_RE.test(s)) passiveSentences += 1;
1814
+ }
1815
+ const ratio = passiveSentences / sentences.length;
1816
+ if (ratio > 0.4) {
1817
+ pushEx("passive-ratio", `${passiveSentences}/${sentences.length} sentences read as passive (${Math.round(ratio * 100)}%)`, null, null, {
1818
+ count: passiveSentences,
1819
+ extra: { passive_ratio: Math.round(ratio * 100) / 100 }
1820
+ });
1821
+ }
1822
+ }
1823
+ }
1824
+ {
1825
+ if (wordCount >= 300) {
1826
+ const digitTokens = (text.match(/(?<![\w.])[£$€]?\d[\d,.]*%?/g) ?? []).length;
1827
+ const midCaps = (text.match(/(?<=[a-z,;]\s)[A-Z][a-z]{2,}/g) ?? []).length;
1828
+ const specifics = digitTokens + midCaps;
1829
+ if (perThousand(specifics) < 2) {
1830
+ pushEx("low-specificity", `${specifics} concrete specifics in ${wordCount} words`, null, null, {
1831
+ count: specifics,
1832
+ extra: { specifics_per_1000_words: Math.round(perThousand(specifics) * 10) / 10 }
1833
+ });
1834
+ }
1835
+ }
1836
+ }
1837
+ {
1838
+ if (sentences.length >= 10) {
1839
+ const contentSets = sentences.map((s) => new Set(s.toLowerCase().match(/\b[a-z]{6,}\b/g) ?? []));
1840
+ let repeats = 0;
1841
+ for (let i = 0; i < contentSets.length - 1; i += 1) {
1842
+ const a = contentSets[i];
1843
+ const b = contentSets[i + 1];
1844
+ let shared = false;
1845
+ for (const w of a) {
1846
+ if (b.has(w)) {
1847
+ shared = true;
1848
+ break;
1849
+ }
1850
+ }
1851
+ if (shared) repeats += 1;
1852
+ }
1853
+ const ratio = repeats / (contentSets.length - 1);
1854
+ if (ratio >= 0.45) {
1855
+ pushEx("adjacent-lemma-repeat", `${repeats}/${contentSets.length - 1} adjacent sentence pairs repeat a content word`, null, null, {
1856
+ count: repeats,
1857
+ extra: { repeat_ratio: Math.round(ratio * 100) / 100 }
1858
+ });
1859
+ }
1860
+ }
1861
+ }
1862
+ {
1863
+ const hits = execAll(FOCAL_WORD_RE, text);
1864
+ const byWord = /* @__PURE__ */ new Map();
1865
+ for (const m of hits) {
1866
+ const w = m[0].toLowerCase();
1867
+ (byWord.get(w) ?? byWord.set(w, []).get(w)).push(m.index);
1868
+ }
1869
+ for (const [word, positions] of byWord) {
1870
+ for (let i = 0; i < positions.length - 1; i += 1) {
1871
+ if (positions[i + 1] - positions[i] <= 300) {
1872
+ push("proximity-cluster", `"${word}" repeats within 300 chars`, positions[i], positions[i] + word.length, void 0, positions.length);
1873
+ break;
1874
+ }
1875
+ }
1876
+ }
1877
+ }
1878
+ {
1879
+ for (const m of execAll(/\bISBN(?:-1[03])?:?\s*((?:97[89][- ]?)?(?:\d[- ]?){9,12}[\dXx])\b/g, text)) {
1880
+ const digits = m[1].replace(/[- ]/g, "");
1881
+ const valid = digits.length === 10 ? isbn10Valid(digits) : digits.length === 13 ? isbn13Valid(digits) : false;
1882
+ if (!valid && (digits.length === 10 || digits.length === 13)) {
1883
+ push("invalid-isbn", m[0], m.index, m.index + m[0].length);
1884
+ }
1885
+ }
1886
+ }
1887
+ {
1888
+ const boldRuns = execAll(/\*\*[^*\n]{1,120}\*\*/g, text);
1889
+ if (boldRuns.length >= 1) {
1890
+ const first = boldRuns[0];
1891
+ pushEx("markdown-bold", `${boldRuns.length} literal **bold** run(s)`, first.index, first.index + first[0].length, { count: boldRuns.length });
1892
+ }
1893
+ const mdHeadings = execAll(/^#{1,6}[ \t]+\S/gm, text);
1894
+ if (mdHeadings.length >= 1) {
1895
+ const first = mdHeadings[0];
1896
+ pushEx("markdown-heading", `${mdHeadings.length} markdown heading line(s)`, first.index, first.index + first[0].length, { count: mdHeadings.length });
1897
+ }
1898
+ const bulletLines = (text.match(/^\s*[-*•]\s+/gm) ?? []).length;
1899
+ const bulletsPer1000 = wordCount > 0 ? bulletLines / (wordCount / 1e3) : 0;
1900
+ const gateOpen = boldRuns.length >= 1 || mdHeadings.length >= 1 || bulletsPer1000 > V6_FURNITURE_THRESHOLDS.bulletsPer1000;
1901
+ if (gateOpen) {
1902
+ pushEx(
1903
+ "markdown-furniture",
1904
+ `${boldRuns.length} bold / ${mdHeadings.length} headings / ${Math.round(bulletsPer1000 * 10) / 10} bullets per 1000 words`,
1905
+ null,
1906
+ null,
1907
+ {
1908
+ count: boldRuns.length + mdHeadings.length + bulletLines,
1909
+ extra: {
1910
+ bold_runs: boldRuns.length,
1911
+ heading_lines: mdHeadings.length,
1912
+ bullets_per_1000_words: Math.round(bulletsPer1000 * 10) / 10
1913
+ }
1914
+ }
1915
+ );
1916
+ }
1917
+ }
1918
+ }
1919
+ var V4_RHYTHM_CATEGORIES = /* @__PURE__ */ new Set([
1920
+ "sentence-length-spectral-flatness",
1921
+ "conditional-compression",
1922
+ "lexical-register-distance",
1923
+ "punchline-fragment-density",
1924
+ "mic-drop-paragraph",
1925
+ "contrast-density",
1926
+ "rhetorical-procedural-ratio"
1927
+ ]);
1928
+ var V4_THRESHOLDS = {
1929
+ /** Fixed window length in sentences for the spectral estimator — the
1930
+ * plan's length-artefact correction: flatness is only compared between
1931
+ * equal-length series. */
1932
+ spectralWindowSentences: 12,
1933
+ /** Minimum number of full windows before the signal is computed. */
1934
+ spectralMinWindows: 2,
1935
+ /** Fire when the window-averaged spectral flatness falls below this. */
1936
+ spectralFlatnessMax: 0.28,
1937
+ /** Conditional compression is computed only inside this word band: the
1938
+ * gain statistic falls with document length for EVERY author (the plan's
1939
+ * length-confound warning), so out-of-band documents are exempt rather
1940
+ * than mis-thresholded. */
1941
+ compressionMinWords: 250,
1942
+ compressionMaxWords: 900,
1943
+ /** Fire when the relative gain from the human-corpus prior falls below
1944
+ * this. Calibrated in-band: the lowest human sample measured 0.177; the
1945
+ * chat-export-furniture positive measures ~0.10. */
1946
+ compressionGainMin: 0.14,
1947
+ /** Minimum words before the register profile is computed (the L1 distance
1948
+ * is noisy on short texts — a 50-word human note measures >0.5). */
1949
+ registerMinWords: 300,
1950
+ /** Fire when function-word L1 distance from the human reference profile
1951
+ * exceeds this AND the long-word share exceeds the reference by the
1952
+ * delta below (both must hold — the genre-confound guard). Calibrated:
1953
+ * the highest human fixture measured funcL1 0.266 / longΔ 0.058. */
1954
+ registerFuncL1Min: 0.3,
1955
+ registerLongWordDeltaMin: 0.1,
1956
+ /** Punchline fragments: at least this many, at at least this share of
1957
+ * sentences, with at least this many paragraph-final. */
1958
+ punchlineMinCount: 4,
1959
+ punchlineMinRate: 0.18,
1960
+ punchlineMinParagraphFinal: 2,
1961
+ /** Mic-drop paragraphs required before the rule fires (never one). */
1962
+ micDropMinParagraphs: 2,
1963
+ /** Contrast constructions: minimum count AND minimum per-1000-words rate. */
1964
+ contrastMinCount: 4,
1965
+ contrastMinPer1000: 4,
1966
+ /** Rhetorical/procedural: minimum sentences, minimum abstract-claim
1967
+ * sentences, minimum abstract share, maximum concrete sentences. */
1968
+ ratioMinSentences: 12,
1969
+ ratioMinAbstract: 9,
1970
+ ratioMinShare: 0.7,
1971
+ ratioMaxConcrete: 2
1972
+ };
1973
+ var ABSTRACT_PUNCH_RE = /^(?:(?:and\s+)?(?:that|this|it)['’]?s?\b)|\b(?:matters?|everything|nothing|difference|point|result|future|answer|story|truth|shift|mindset|game|lesson|magic|secret|power|possible|real\s+\w+)\b/i;
1974
+ var MIC_DROP_CONTRAST_RE = /\bnot\b|n[’']t\b|\bbut\b|\b(?:it|that|this)\s+(?:is|was)\b|\bdifference\b|\bmatters?\b|\binstead\b|\bwho\b|\bwhat\b|\bwhy\b|\bjust\b/i;
1975
+ var CONTRAST_VARIANT_RES = [
1976
+ /\b\w+\s+(?:is|was|are|were)\s+not\s+(?:about\s+|just\s+|only\s+)?[^.!?\n]{1,50}[.!?]\s+(?:It|They|That|This)\s+(?:is|was|are|were)\b/g,
1977
+ /\b(?:this|it)\s+(?:is|was)\s*n[’']t\s+about\s+[^.!?\n]{1,50}[.;—]\s*[Ii]t['’]s\s+about\b/g,
1978
+ /\b(?:this|it)\s+isn[’']t\s+[^.!?\n]{1,50}[.!?]\s+It['’]s\b/g
1979
+ ];
1980
+ var CONCRETE_ACTION_VERB_RE = /\b(?:moved|installed|checked|rang|phoned|emailed|sent|built|wrote|ordered|fixed|booked|painted|measured|delivered|signed|printed|uploaded|configured|tested|deployed|migrated|invoiced|quoted|packed|shipped|cleaned|repaired|replaced|bought|paid|hired|visited|opened|closed|launched|updated|added|removed|reviewed|approved|submitted|filed|called|drove|attended|arranged|cancelled|refunded|scheduled|recorded|photographed|wired|plumbed|stocked|priced|posted|drafted|edited|published)\b/i;
1981
+ var ABSTRACT_CLAIM_RE = /\b(?:is|are|was|were|isn[’']t|aren[’']t|means|matters|becomes?|feels?|represents?)\b[^.!?\n]{0,80}\b(?:important|essential|critical|everything|nothing|different|possible|powerful|real|value|success|growth|potential|future|journey|transformation|opportunity|mindset|game|advantage|key|vital|crucial|remarkable|extraordinary|leadership|innovation|vision|impact|change)\b/i;
1982
+ var REGISTER_FUNCTION_WORDS = [
1983
+ "the",
1984
+ "of",
1985
+ "and",
1986
+ "a",
1987
+ "to",
1988
+ "in",
1989
+ "is",
1990
+ "it",
1991
+ "that",
1992
+ "was",
1993
+ "he",
1994
+ "for",
1995
+ "on",
1996
+ "are",
1997
+ "as",
1998
+ "with",
1999
+ "his",
2000
+ "they",
2001
+ "i",
2002
+ "at",
2003
+ "be",
2004
+ "this",
2005
+ "have",
2006
+ "from",
2007
+ "or",
2008
+ "one",
2009
+ "had",
2010
+ "by",
2011
+ "not",
2012
+ "but",
2013
+ "what",
2014
+ "all",
2015
+ "were",
2016
+ "we",
2017
+ "when",
2018
+ "your",
2019
+ "can",
2020
+ "there",
2021
+ "an",
2022
+ "which",
2023
+ "their",
2024
+ "if",
2025
+ "will",
2026
+ "so",
2027
+ "no",
2028
+ "would",
2029
+ "who",
2030
+ "them",
2031
+ "these",
2032
+ "than"
2033
+ ];
2034
+ var REGISTER_LONG_WORD_LEN = 7;
2035
+ var V4_ISSUE_WEIGHTS = {
2036
+ "sentence-length-spectral-flatness": 2,
2037
+ "conditional-compression": 2,
2038
+ "lexical-register-distance": 2,
2039
+ "punchline-fragment-density": 2,
2040
+ "mic-drop-paragraph": 2,
2041
+ "contrast-density": 2,
2042
+ "rhetorical-procedural-ratio": 2
2043
+ };
2044
+ var V4_CATEGORY_META = {
2045
+ "sentence-length-spectral-flatness": {
2046
+ severity: "low",
2047
+ message: "Sentence lengths follow a pattern that is more regular than most writing this long. We only measure it on fixed-size chunks, so short pieces are skipped. Careful human editing produces the same shape.",
2048
+ suggestion: "Nothing to change unless the other checks agree. Varying sentence length is a choice, not a rule."
2049
+ },
2050
+ "conditional-compression": {
2051
+ severity: "low",
2052
+ message: "Held up against a sample of varied human writing, this text has less in common with it than most. Copy written to a tight template scores the same way.",
2053
+ suggestion: "Nothing to change on its own. If the other checks agree, vary phrasing you have repeated."
2054
+ },
2055
+ "lexical-register-distance": {
2056
+ severity: "low",
2057
+ message: "The mix of small joining words, and the length of the words, sits a long way from our sample of everyday human writing. Worth knowing: that sample is general writing, so academic, legal and technical pieces land far away quite fairly.",
2058
+ suggestion: "Nothing to change on its own. Plainer wording moves it closer if the other checks agree."
2059
+ },
2060
+ "punchline-fragment-density": {
2061
+ severity: "low",
2062
+ message: `Very short, abstract closing lines keep turning up, especially at the end of paragraphs: "That's the point." Good copywriters use punchlines on purpose, which is why we only count how many there are.`,
2063
+ suggestion: "Keep the punchlines that earn their place. Turn the rest into full sentences."
2064
+ },
2065
+ "mic-drop-paragraph": {
2066
+ severity: "low",
2067
+ message: "Several paragraphs are built the same way: a few medium sentences, then a much shorter line to land on. One paragraph like that is ordinary. It is the repeat that stands out.",
2068
+ suggestion: "Let some paragraphs finish on their facts instead of a quotable line."
2069
+ },
2070
+ "contrast-density": {
2071
+ severity: "low",
2072
+ message: `Two-sided contrasts keep coming back: "not X, but Y"; "It wasn't A. It was B." One is ordinary writing. It is the rate that stands out.`,
2073
+ suggestion: "Keep the strongest contrast and say the rest plainly."
2074
+ },
2075
+ "rhetorical-procedural-ratio": {
2076
+ severity: "low",
2077
+ message: "Sentences making broad claims heavily outnumber sentences naming a real action, object or number. We count a sentence as concrete when it holds a number, a name, or a specific action verb. Opinion and vision pieces are broad on purpose.",
2078
+ suggestion: "Back more claims with a specific action, name or figure if the other checks agree."
2079
+ }
2080
+ };
2081
+ var REFERENCE_CORPUS = `At five o\u2019clock the two ladies retired to dress, and at half-past six Elizabeth was summoned to dinner. To the civil inquiries which then poured in, and amongst which she had the pleasure of distinguishing the much superior solicitude of Mr. Bingley, she could not make a very favourable answer. Jane was by no means better. The sisters, on hearing this, repeated three or four times how much they were grieved, how shocking it was to have a bad cold, and how excessively they disliked being ill themselves; and then thought no more of the matter: and their indifference towards Jane, when not immediately before them, restored Elizabeth to the enjoyment of all her original dislike.
2082
+
2083
+ Their brother, indeed, was the only one of the party whom she could regard with any complacency. His anxiety for Jane was evident, and his attentions to herself most pleasing; and they prevented her feeling herself so much an intruder as she believed she was considered by the others. She had very little notice from any but him. Miss Bingley was engrossed by Mr. Darcy, her sister scarcely less so; and as for Mr. Hurst, by whom Elizabeth sat, he was an indolent man, who lived only to eat, drink, and play at cards, who, when he found her prefer a plain dish to a ragout, had nothing to say to her.
2084
+
2085
+ When dinner was over, she returned directly to Jane, and Miss Bingley began abusing her as soon as she was out of the room. Her manners were pronounced to be very bad indeed,--a mixture of pride and impertinence: she had no conversation, no style, no taste, no beauty. Mrs. Hurst thought the same, and added,--
2086
+
2087
+ \u201CShe has nothing, in short, to recommend her, but being an excellent walker. I shall never forget her appearance this morning. She really looked almost wild.\u201D
2088
+
2089
+ \u201CShe did indeed, Louisa. I could hardly keep my countenance. Very nonsensical to come at all! Why must _she_ be scampering about the country, because her sister had a cold? Her hair so untidy, so blowzy!\u201D
2090
+
2091
+ \u201CYes, and her petticoat; I hope you saw her petticoat, six inches deep in mud, I am absolutely certain, and the gown which had been let down to hide it not doing its office.\u201D
2092
+
2093
+ \u201CYour picture may be very exact, Louisa,\u201D said Bingley; \u201Cbut this was all lost upon me. I thought Miss Elizabeth Bennet looked remarkably well when she came into the room this morning. Her dirty petticoat quite escaped my notice.\u201D
2094
+
2095
+ \u201C_You_ observed it, Mr. Darcy, I am sure,\u201D said Miss Bingley; \u201Cand I am inclined to think that you would not wish to see _your sister_ make such an exhibition.\u201D
2096
+
2097
+ \u201CTo walk three miles, or four miles, or five miles, or whatever it is, above her ancles in dirt, and alone, quite alone! what could she mean by it? It seems to me to show an abominable sort of conceited independence, a most country-town indifference to decorum.\u201D
2098
+
2099
+ \u201CIt shows an affection for her sister that is very pleasing,\u201D said Bingley.
2100
+
2101
+ \u201CI am afraid, Mr. Darcy,\u201D observed Miss Bingley, in a half whisper, \u201Cthat this adventure has rather affected your admiration of her fine eyes.\u201D
2102
+
2103
+ \u201CNot at all,\u201D he replied: \u201Cthey were brightened by the exercise.\u201D A short pause followed this speech, and Mrs. Hurst began again,--
2104
+
2105
+ \u201CI have an excessive regard for Jane Bennet,--she is really a very sweet girl,--and I wish with all my heart she were well settled. But with such a father and mother, and such low connections, I am afraid there is no chance of it.\u201D
2106
+
2107
+ \u201CI think I have heard you say that their uncle is an attorney in Meryton?\u201D
2108
+
2109
+ \u201CYes; and they have another, who lives somewhere near Cheapside.\u201D
2110
+
2111
+ \u201CThat is capital,\u201D added her sister; and they both laughed heartily.
2112
+
2113
+ \u201CIf they had uncles enough to fill _all_ Cheapside,\u201D cried Bingley, \u201Cit would not make them one jot less agreeable.\u201D
2114
+
2115
+ \u201CBut it must very materially lessen their chance of marrying men of any consideration in the world,\u201D replied Darcy.
2116
+
2117
+ To this speech Bingley made no answer; but his sisters gave it their hearty assent, and indulged their mirth for some time at the expense of their dear friend\u2019s vulgar relations.
2118
+
2119
+ With a renewal of tenderness, however, they repaired to her room on leaving the dining-parlour, and sat with her till summoned to coffee. She was still very poorly, and Elizabeth would not quit her at all, till late in the evening, when she had the comfort of seeing her asleep, and when it appeared to her rather right than pleasant that she should go down stairs herself. On entering the drawing-room, she found the whole party at loo, and was immediately invited to join them; but suspecting them to be playing high, she declined it, and making her sister the excuse, said she would amuse herself, for the short time she could stay below, with a book. Mr. Hurst looked at her with astonishment.
2120
+
2121
+ \u201CDo you prefer reading to cards?\u201D said he; \u201Cthat is rather singular.\u201D
2122
+
2123
+ \u201CMiss Eliza Bennet,\u201D said Miss Bingley, \u201Cdespises cards. She is a great reader, and has no pleasure in anything else.\u201D
2124
+
2125
+ \u201CI deserve neither such praise nor such censure,\u201D cried Elizabeth; \u201CI am _not_ a great reader, and I have pleasure in many things.\u201D
2126
+
2127
+ \u201CIn nursing your sister I am sure you have pleasure,\u201D said Bingley; \u201Cand I hope it will soon be increased by seeing her quite well.\u201D
2128
+
2129
+ Elizabeth thanked him from her heart, and then walked towards a table where a few books were lying. He immediately offered to fetch her others; all that his library afforded.
2130
+
2131
+ \u201CAnd I wish my collection were larger for your benefit and my own credit; but I am an idle fellow; and though I have not many, I have more than I ever looked into.\u201D
2132
+
2133
+ Elizabeth assured him that she could suit herself perfectly with those in the room.
2134
+
2135
+ \u201CI am astonished,\u201D said Miss Bingley, \u201Cthat my father should have left so small a collection of books. What a delightful library you have at Pemberley, Mr. Darcy!\u201D
2136
+
2137
+ \u201CIt ought to be good,\u201D he replied: \u201Cit has been the work of many generations.\u201D
2138
+
2139
+ \u201CAnd then you have added so much to it yourself--you are always buying books.\u201D
2140
+
2141
+ \u201CI cannot comprehend the neglect of a family library in such days as these.\u201D
2142
+
2143
+ \u201CNeglect! I am sure you neglect nothing that can add to the beauties of that noble place. Charles, when you build _your_ house, I wish it may be half as delightful as Pemberley.\u201D
2144
+
2145
+ \u201CBut I would really advise you to make your purchase in that neighbourhood, and take Pemberley for a kind of model. There is not a finer county in England than Derbyshire.\u201D
2146
+
2147
+ \u201CWith all my heart: I will buy Pemberley itself, if Darcy will sell it.\u201D
2148
+
2149
+ \u201CI am talking of possibilities, Charles.\u201D
2150
+
2151
+ \u201CUpon my word, Caroline, I should think it more possible to get Pemberley by purchase than by imitation.\u201D
2152
+
2153
+ Elizabeth was so much caught by what passed, as to leave her very little attention for her book; and, soon laying it wholly aside, she drew near the card-table, and stationed herself between Mr. Bingley and his eldest sister, to observe the game.
2154
+
2155
+ \u201CIs Miss Darcy much grown since the spring?\u201D said Miss Bingley: \u201Cwill she be as tall as I am?\u201D
2156
+
2157
+ \u201CI think she will. She is now about Miss Elizabeth Bennet\u2019s height, or rather taller.\u201D
2158
+
2159
+ \u201CHow I long to see her again! I never met with anybody who delighted me so much. Such a countenance, such manners, and so extremely accomplished for her age! Her performance on the pianoforte is exquisite.\u201D
2160
+
2161
+ \u201CIt is amazing to me,\u201D said Bingley, \u201Chow young ladies can have patience to be so very accomplished as they all are.\u201D
2162
+
2163
+ \u201CAll young ladies accomplished! My dear Charles, what do you mean?\u201D
2164
+
2165
+ \u201CYes, all of them, I think. They all paint tables, cover screens, and net purses. I scarcely know any one who cannot do all this; and I am sure I never heard a young lady spoken of for the first time, without being informed that she was very accomplished.\u201D
2166
+
2167
+ \u201CYour list of the common extent of accomplishments,\u201D said Darcy, \u201Chas too much truth. The word is applied to many a woman who deserves it no otherwise than by netting a purse or covering a screen; but I am very far from agreeing with you in your estimation of ladies in general. I cannot boast of knowing more than half-a-dozen in the whole range of my acquaintance that are really accomplished.\u201D
2168
+
2169
+ \u201CThen,\u201D observed Elizabeth, \u201Cyou must comprehend a great deal in your idea of an accomplished woman.\u201D
2170
+
2171
+ \u201CYes; I do comprehend a great deal in it.\u201D
2172
+
2173
+ \u201COh, certainly,\u201D cried his faithful assistant, \u201Cno one can be really esteemed accomplished who does not greatly surpass what is usually met with. A woman must have a thorough knowledge of music, singing, drawing, dancing, and the modern languages, to deserve the word; and, besides all this, she must possess a certain something in her air and manner of walking, the tone of her voice, her address and expressions, or the word will be but half deserved.\u201D
2174
+
2175
+ \u201CAll this she must possess,\u201D added Darcy; \u201Cand to all she must yet add something more substantial in the improvement of her mind by extensive reading.\u201D
2176
+
2177
+ \u201CI am no longer surprised at your knowing _only_ six accomplished women. I rather wonder now at your knowing _any_.\u201D
2178
+
2179
+ \u201CAre you so severe upon your own sex as to doubt the possibility of all this?\u201D
2180
+
2181
+ \u201C_I_ never saw such a woman. _I_ never saw such capacity, and taste, and application, and elegance, as you describe, united.\u201D.
2182
+
2183
+ At the present time, eminent breeders try by methodical selection, with a distinct object in view, to make a new strain or sub-breed, superior to anything of the kind in the country. But, for our purpose, a form of selection, which may be called unconscious, and which results from every one trying to possess and breed from the best individual animals, is more important. Thus, a man who intends keeping pointers naturally tries to get as good dogs as he can, and afterwards breeds from his own best dogs, but he has no wish or expectation of permanently altering the breed. Nevertheless we may infer that this process, continued during centuries, would improve and modify any breed, in the same way as Bakewell, Collins, &c., by this very same process, only carried on more methodically, did greatly modify, even during their lifetimes, the forms and qualities of their cattle. Slow and insensible changes of this kind could never be recognised unless actual measurements or careful drawings of the breeds in question have been made long ago, which may serve for comparison. In some cases, however, unchanged, or but little changed, individuals of the same breed exist in less civilised districts, where the breed has been less improved. There is reason to believe that King Charles\u2019 spaniel has been unconsciously modified to a large extent since the time of that monarch. Some highly competent authorities are convinced that the setter is directly derived from the spaniel, and has probably been slowly altered from it. It is known that the English pointer has been greatly changed within the last century, and in this case the change has, it is believed, been chiefly effected by crosses with the foxhound; but what concerns us is, that the change has been effected unconsciously and gradually, and yet so effectually that, though the old Spanish pointer certainly came from Spain, Mr. Borrow has not seen, as I am informed by him, any native dog in Spain like our pointer.
2184
+
2185
+ By a similar process of selection, and by careful training, English race-horses have come to surpass in fleetness and size the parent Arabs, so that the latter, by the regulations for the Goodwood Races, are favoured in the weights which they carry. Lord Spencer and others have shown how the cattle of England have increased in weight and in early maturity, compared with the stock formerly kept in this country. By comparing the accounts given in various old treatises of the former and present state of carrier and tumbler pigeons in Britain, India, and Persia, we can trace the stages through which they have insensibly passed, and come to differ so greatly from the rock-pigeon.
2186
+
2187
+ Youatt gives an excellent illustration of the effects of a course of selection which may be considered as unconscious, in so far that the breeders could never have expected, or even wished, to produce the result which ensued\u2014namely, the production of the distinct strains. The two flocks of Leicester sheep kept by Mr. Buckley and Mr. Burgess, as Mr. Youatt remarks, \u201CHave been purely bred from the original stock of Mr. Bakewell for upwards of fifty years. There is not a suspicion existing in the mind of any one at all acquainted with the subject that the owner of either of them has deviated in any one instance from the pure blood of Mr. Bakewell\u2019s flock, and yet the difference between the sheep possessed by these two gentlemen is so great that they have the appearance of being quite different varieties.\u201D
2188
+
2189
+ If there exist savages so barbarous as never to think of the inherited character of the offspring of their domestic animals, yet any one animal particularly useful to them, for any special purpose, would be carefully preserved during famines and other accidents, to which savages are so liable, and such choice animals would thus generally leave more offspring than the inferior ones; so that in this case there would be a kind of unconscious selection going on. We see the value set on animals even by the barbarians of Tierra del Fuego, by their killing and devouring their old women, in times of dearth, as of less value than their dogs.
2190
+
2191
+ In plants the same gradual process of improvement through the occasional preservation of the best individuals, whether or not sufficiently distinct to be ranked at their first appearance as distinct varieties, and whether or not two or more species or races have become blended together by crossing, may plainly be recognised in the increased size and beauty which we now see in the varieties of the heartsease, rose, pelargonium, dahlia, and other plants, when compared with the older varieties or with their parent-stocks. No one would ever expect to get a first-rate heartsease or dahlia from the seed of a wild plant. No one would expect to raise a first-rate melting pear from the seed of a wild pear, though he might succeed from a poor seedling growing wild, if it had come from a garden-stock. The pear, though cultivated in classical times, appears, from Pliny\u2019s description, to have been a fruit of very inferior quality. I have seen great surprise expressed in horticultural works at the wonderful skill of gardeners in having produced such splendid results from such poor materials; but the art has been simple, and, as far as the final result is concerned, has been followed almost unconsciously. It has consisted in always cultivating the best known variety, sowing its seeds, and, when a slightly better variety chanced to appear, selecting it, and so onwards. But the gardeners of the classical period, who cultivated the best pears which they could procure, never thought what splendid fruit we should eat; though we owe our excellent fruit in some small degree to their having naturally chosen and preserved the best varieties they could anywhere find.
2192
+
2193
+ A large amount of change, thus slowly and unconsciously accumulated, explains, as I believe, the well-known fact, that in a number of cases we cannot recognise, and therefore do not know, the wild parent-stocks of the plants which have been longest cultivated in our flower and kitchen gardens. If it has taken centuries or thousands of years to improve or modify most of our plants up to their present standard of usefulness to man, we can understand how it is that neither Australia, the Cape of Good Hope, nor any other region inhabited by quite uncivilised man, has afforded us a single plant worth culture. It is not that these countries, so rich in species, do not by a strange chance possess the aboriginal stocks of any useful plants, but that the native plants have not been improved by continued selection up to a standard of perfection comparable with that acquired by the plants in countries anciently civilised.
2194
+
2195
+ In regard to the domestic animals kept by uncivilised man, it should not be overlooked that they almost always have to struggle for their own food, at least during certain seasons.
2196
+
2197
+ About this time I met with an odd volume of the Spectator. It was the third. I had never before seen any of them. I bought it, read it over and over, and was much delighted with it. I thought the writing excellent, and wished, if possible, to imitate it. With this view I took some of the papers, and, making short hints of the sentiment in each sentence, laid them by a few days, and then, without looking at the book, try'd to compleat the papers again, by expressing each hinted sentiment at length, and as fully as it had been expressed before, in any suitable words that should come to hand. Then I compared my Spectator with the original, discovered some of my faults, and corrected them. But I found I wanted a stock of words, or a readiness in recollecting and using them, which I thought I should have acquired before that time if I had gone on making verses; since the continual occasion for words of the same import, but of different length, to suit the measure, or of different sound for the rhyme, would have laid me under a constant necessity of searching for variety, and also have tended to fix that variety in my mind, and make me master of it. Therefore I took some of the tales and turned them into verse; and, after a time, when I had pretty well forgotten the prose, turned them back again. I also sometimes jumbled my collections of hints into confusion, and after some weeks endeavored to reduce them into the best order, before I began to form the full sentences and compleat the paper. This was to teach me method in the arrangement of thoughts. By comparing my work afterwards with the original, I discovered many faults and amended them; but I sometimes had the pleasure of fancying that, in certain particulars of small import, I had been lucky enough to improve the method or the language, and this encouraged me to think I might possibly in time come to be a tolerable English writer, of which I was extremely ambitious. My time for these exercises and for reading was at night, after work or before it began in the morning, or on Sundays, when I contrived to be in the printing-house alone, evading as much as I could the common attendance on public worship which my father used to exact on me when I was under his care, and which indeed I still thought a duty, though I could not, as it seemed to me, afford time to practise it.
2198
+
2199
+ When about 16 years of age I happened to meet with a book, written by one Tryon, recommending a vegetable diet. I determined to go into it. My brother, being yet unmarried, did not keep house, but boarded himself and his apprentices in another family. My refusing to eat flesh occasioned an inconveniency, and I was frequently chid for my singularity. I made myself acquainted with Tryon's manner of preparing some of his dishes, such as boiling potatoes or rice, making hasty pudding, and a few others, and then proposed to my brother, that if he would give me, weekly, half the money he paid for my board, I would board myself. He instantly agreed to it, and I presently found that I could save half what he paid me. This was an additional fund for buying books. But I had another advantage in it. My brother and the rest going from the printing-house to their meals, I remained there alone, and, despatching presently my light repast, which often was no more than a bisket or a slice of bread, a handful of raisins or a tart from the pastry-cook's, and a glass of water, had the rest of the time till their return for study, in which I made the greater progress, from that greater clearness of head and quicker apprehension which usually attend temperance in eating and drinking.
2200
+
2201
+ And now it was that, being on some occasion made asham'd of my ignorance in figures, which I had twice failed in learning when at school, I took Cocker's book of Arithmetick, and went through the whole by myself with great ease. I also read Seller's and Shermy's books of Navigation, and became acquainted with the little geometry they contain; but never proceeded far in that science. And I read about this time Locke On Human Understanding, and the Art of Thinking, by Messrs. du Port Royal.
2202
+
2203
+ While I was intent on improving my language, I met with an English grammar (I think it was Greenwood's), at the end of which there were two little sketches of the arts of rhetoric and logic, the latter finishing with a specimen of a dispute in the Socratic method; and soon after I procur'd Xenophon's Memorable Things of Socrates, wherein there are many instances of the same method. I was charm'd with it, adopted it, dropt my abrupt contradiction and positive argumentation, and put on the humble inquirer and doubter. And being then, from reading Shaftesbury and Collins, become a real doubter in many points of our religious doctrine, I found this method safest for myself and very embarrassing to those against whom I used it; therefore I took a delight in it, practis'd it continually, and grew very artful and expert in drawing people, even of superior knowledge, into concessions, the consequences of which they did not foresee, entangling them in difficulties out of which they could not extricate themselves, and so obtaining victories that neither myself nor my cause always deserved. I continu'd this method some few years, but gradually left it, retaining only the habit of expressing myself in terms of modest diffidence; never using, when I advanced any thing that may possibly be disputed, the words certainly, undoubtedly, or any others that give the air of positiveness to an opinion; but rather say, I conceive or apprehend a thing to be so and so; it appears to me, or I should think it so or so, for such and such reasons; or I imagine it to be so; or it is so, if I am not mistaken. This habit, I believe, has been of great advantage to me when I have had occasion to inculcate my opinions, and persuade men into measures that I have been from time to time engag'd in promoting; and, as the chief ends of conversation are to inform or to be informed, to please or to persuade, I wish well-meaning, sensible men would not lessen their power of doing good by a positive, assuming manner, that seldom fails to disgust, tends to create opposition, and to defeat every one of those purposes for which speech was given to us, to wit, giving or receiving information or pleasure. For, if you would inform, a positive and dogmatical manner in advancing your sentiments may provoke contradiction and prevent a candid attention.
2204
+
2205
+ \u201COh, everything. Latitude and longitude, noon every day; and how many miles we made last twenty-four hours; and all the domino games I beat and horse billiards; and whales and sharks and porpoises; and the text of the sermon Sundays (because that'll tell at home, you know); and the ships we saluted and what nation they were; and which way the wind was, and whether there was a heavy sea, and what sail we carried, though we don't ever carry any, principally, going against a head wind always--wonder what is the reason of that?--and how many lies Moult has told--Oh, every thing! I've got everything down. My father told me to keep that journal. Father wouldn't take a thousand dollars for it when I get it done.\u201D
2206
+
2207
+ \u201CNo, Jack; it will be worth more than a thousand dollars--when you get it done.\u201D
2208
+
2209
+ \u201CDo you?--no, but do you think it will, though?
2210
+
2211
+ \u201CYes, it will be worth at least as much as a thousand dollars--when you get it done. May be more.\u201D
2212
+
2213
+ \u201CWell, I about half think so, myself. It ain't no slouch of a journal.\u201D
2214
+
2215
+ But it shortly became a most lamentable \u201Cslouch of a journal.\u201D One night in Paris, after a hard day's toil in sightseeing, I said:
2216
+
2217
+ \u201CNow I'll go and stroll around the cafes awhile, Jack, and give you a chance to write up your journal, old fellow.\u201D
2218
+
2219
+ \u201CWell, no, you needn't mind. I think I won't run that journal anymore. It is awful tedious. Do you know--I reckon I'm as much as four thousand pages behind hand. I haven't got any France in it at all. First I thought I'd leave France out and start fresh. But that wouldn't do, would it? The governor would say, 'Hello, here--didn't see anything in France? That cat wouldn't fight, you know. First I thought I'd copy France out of the guide-book, like old Badger in the for'rard cabin, who's writing a book, but there's more than three hundred pages of it. Oh, I don't think a journal's any use--do you? They're only a bother, ain't they?\u201D
2220
+
2221
+ \u201CYes, a journal that is incomplete isn't of much use, but a journal properly kept is worth a thousand dollars--when you've got it done.\u201D
2222
+
2223
+ \u201CA thousand!--well, I should think so. I wouldn't finish it for a million.\u201D
2224
+
2225
+ His experience was only the experience of the majority of that industrious night school in the cabin. If you wish to inflict a heartless and malignant punishment upon a young person, pledge him to keep a journal a year.
2226
+
2227
+ A good many expedients were resorted to to keep the excursionists amused and satisfied. A club was formed, of all the passengers, which met in the writing school after prayers and read aloud about the countries we were approaching and discussed the information so obtained.
2228
+
2229
+ Several times the photographer of the expedition brought out his transparent pictures and gave us a handsome magic-lantern exhibition. His views were nearly all of foreign scenes, but there were one or two home pictures among them. He advertised that he would \u201Copen his performance in the after cabin at 'two bells' (nine P.M.) and show the passengers where they shall eventually arrive\u201D--which was all very well, but by a funny accident the first picture that flamed out upon the canvas was a view of Greenwood Cemetery!
2230
+
2231
+ On several starlight nights we danced on the upper deck, under the awnings, and made something of a ball-room display of brilliancy by hanging a number of ship's lanterns to the stanchions. Our music consisted of the well-mixed strains of a melodeon which was a little asthmatic and apt to catch its breath where it ought to come out strong, a clarinet which was a little unreliable on the high keys and rather melancholy on the low ones, and a disreputable accordion that had a leak somewhere and breathed louder than it squawked--a more elegant term does not occur to me just now. However, the dancing was infinitely worse than the music. When the ship rolled to starboard the whole platoon of dancers came charging down to starboard with it, and brought up in mass at the rail; and when it rolled to port they went floundering down to port with the same unanimity of sentiment. Waltzers spun around precariously for a matter of fifteen seconds and then went scurrying down to the rail as if they meant to go overboard. The Virginia reel, as performed on board the __Quaker City__, had more genuine reel about it than any reel I ever saw before, and was as full of interest to the spectator as it was full of desperate chances and hairbreadth escapes to the participant. We gave up dancing, finally.
2232
+
2233
+ We celebrated a lady's birthday anniversary with toasts, speeches, a poem, and so forth. We also had a mock trial. No ship ever went to sea that hadn't a mock trial on board. The purser was accused of stealing an overcoat from stateroom No. 10. A judge was appointed; also clerks, a crier of the court, constables, sheriffs; counsel for the State and for the defendant; witnesses were subpoenaed, and a jury empaneled after much challenging. The witnesses were stupid and unreliable and contradictory, as witnesses always are. The counsel were eloquent, argumentative, and vindictively abusive of each other, as was characteristic and proper. The case was at last submitted and duly finished by the judge with an absurd decision and a ridiculous sentence.
2234
+
2235
+ The acting of charades was tried on several evenings by the young gentlemen and ladies, in the cabins, and proved the most distinguished success of all the amusement experiments.
2236
+
2237
+ An attempt was made to organize a debating club, but it was a failure. There was no oratorical talent in the ship.
2238
+
2239
+ We all enjoyed ourselves--I think I can safely say that, but it was in a rather quiet way. We very, very seldom played the piano; we played the flute and the clarinet together, and made good music, too, what there was of it, but we always played the same old tune; it was a very pretty tune --how well I remember it--I wonder when I shall ever get rid of it. We never played either the melodeon or the organ except at devotions--but I am too fast: young Albert did know part of a tune something about \u201CO Something-Or-Other How Sweet It Is to Know That He's His What's-his-Name\u201D (I do not remember the exact title of it, but it was very plaintive and full of sentiment); Albert played that pretty much all the time until we contracted with him to restrain himself. But nobody ever sang by moonlight on the upper deck, and the congregational singing at church and prayers was not of a superior order of architecture. I put up with it as long as I could and then joined in and tried to improve it, but this encouraged young George to join in too, and that made a failure of it; because George's voice was just \u201Cturning,\u201D and when he was singing a dismal sort of bass it was apt to fly off the handle and startle everybody with a most discordant cackle on the upper notes. George didn't know the tunes, either, which was also a drawback to his performances. I said:
2240
+
2241
+ \u201CCome, now, George, don't improvise. It looks too egotistical. It will provoke remark. Just stick to 'Coronation,' like the others. It is a good tune--you can't improve it any, just off-hand, in this way.\u201D
2242
+
2243
+ \u201CWhy, I'm not trying to improve it--and I am singing like the others --just as it is in the notes.\u201D
2244
+
2245
+ And he honestly thought he was, too; and so he had no one to blame but himself when his voice caught on the center occasionally and gave him the lockjaw.
2246
+
2247
+ In the trade to China and India, we interfere with more than one nation, inasmuch as it enables us to partake in advantages which they had in a manner monopolized, and as we thereby supply ourselves with commodities which we used to purchase from them.
2248
+
2249
+ The extension of our own commerce in our own vessels cannot give pleasure to any nations who possess territories on or near this continent, because the cheapness and excellence of our productions, added to the circumstance of vicinity, and the enterprise and address of our merchants and navigators, will give us a greater share in the advantages which those territories afford, than consists with the wishes or policy of their respective sovereigns.
2250
+
2251
+ Spain thinks it convenient to shut the Mississippi against us on the one side, and Britain excludes us from the Saint Lawrence on the other; nor will either of them permit the other waters which are between them and us to become the means of mutual intercourse and traffic.
2252
+
2253
+ From these and such like considerations, which might, if consistent with prudence, be more amplified and detailed, it is easy to see that jealousies and uneasinesses may gradually slide into the minds and cabinets of other nations, and that we are not to expect that they should regard our advancement in union, in power and consequence by land and by sea, with an eye of indifference and composure.
2254
+
2255
+ The people of America are aware that inducements to war may arise out of these circumstances, as well as from others not so obvious at present, and that whenever such inducements may find fit time and opportunity for operation, pretenses to color and justify them will not be wanting. Wisely, therefore, do they consider union and a good national government as necessary to put and keep them in SUCH A SITUATION as, instead of INVITING war, will tend to repress and discourage it. That situation consists in the best possible state of defense, and necessarily depends on the government, the arms, and the resources of the country.
2256
+
2257
+ As the safety of the whole is the interest of the whole, and cannot be provided for without government, either one or more or many, let us inquire whether one good government is not, relative to the object in question, more competent than any other given number whatever.
2258
+
2259
+ One government can collect and avail itself of the talents and experience of the ablest men, in whatever part of the Union they may be found. It can move on uniform principles of policy. It can harmonize, assimilate, and protect the several parts and members, and extend the benefit of its foresight and precautions to each. In the formation of treaties, it will regard the interest of the whole, and the particular interests of the parts as connected with that of the whole. It can apply the resources and power of the whole to the defense of any particular part, and that more easily and expeditiously than State governments or separate confederacies can possibly do, for want of concert and unity of system. It can place the militia under one plan of discipline, and, by putting their officers in a proper line of subordination to the Chief Magistrate, will, as it were, consolidate them into one corps, and thereby render them more efficient than if divided into thirteen or into three or four distinct independent companies.
2260
+
2261
+ What would the militia of Britain be if the English militia obeyed the government of England, if the Scotch militia obeyed the government of Scotland, and if the Welsh militia obeyed the government of Wales? Suppose an invasion; would those three governments (if they agreed at all) be able, with all their respective forces, to operate against the enemy so effectually as the single government of Great Britain would?
2262
+
2263
+ We have heard much of the fleets of Britain, and the time may come, if we are wise, when the fleets of America may engage attention. But if one national government, had not so regulated the navigation of Britain as to make it a nursery for seamen--if one national government had not called forth all the national means and materials for forming fleets, their prowess and their thunder would never have been celebrated. Let England have its navigation and fleet--let Scotland have its navigation and fleet--let Wales have its navigation and fleet--let Ireland have its navigation and fleet--let those four of the constituent parts of the British empire be under four independent governments, and it is easy to perceive how soon they would each dwindle into comparative insignificance.
2264
+
2265
+ Apply these facts to our own case. Leave America divided into thirteen or, if you please, into three or four independent governments--what armies could they raise and pay--what fleets could they ever hope to have? If one was attacked, would the others fly to its succor, and spend their blood and money in its defense? Would there be no danger of their being flattered into neutrality by its specious promises, or seduced by a too great fondness for peace to decline hazarding their tranquillity and present safety for the sake of neighbors, of whom perhaps they have been jealous, and whose importance they are content to see diminished? Although such conduct would not be wise, it would, nevertheless, be natural. The history of the states of Greece, and of other countries, abounds with such instances, and it is not improbable that what has so often happened would, under similar circumstances, happen again.
2266
+
2267
+ But admit that they might be willing to help the invaded State or confederacy. How, and when, and in what proportion shall aids of men and money be afforded? Who shall command the allied armies, and from which of them shall he receive his orders? Who shall settle the terms of peace, and in case of disputes what umpire shall decide between them and compel acquiescence? Various difficulties and inconveniences would be inseparable from such a situation; whereas one government, watching over the general and common interests, and combining and directing the powers and resources of the whole, would be free from all these embarrassments, and conduce far more to the safety of the people.
2268
+
2269
+ But whatever may be our situation, whether firmly united under one national government, or split into a number of confederacies, certain it is, that foreign nations will know and view it exactly as it is; and they will act toward us accordingly. If they see that our national government is efficient and well administered, our trade prudently regulated, our militia properly organized and disciplined, our resources and finances discreetly managed, our credit re-established, our people free, contented, and united, they will be much more disposed to cultivate our friendship than provoke our resentment. If, on the other hand, they find us either destitute of an effectual government (each State doing right or wrong, as to its rulers may seem convenient), or split into three or four independent and probably discordant republics or confederacies, one inclining to Britain, another to France, and a third to Spain, and perhaps played off against each other by the three, what a poor, pitiful figure will America make in their eyes! How liable would she become not only to their contempt but to their outrage, and how soon would dear-bought experience proclaim that when a people or family so divide, it never fails to be against themselves.
2270
+
2271
+ "The world was sad! the garden was a wild! And man the hermit sigh'd, till _woman_ smiled."
2272
+
2273
+ Let her prove herself, then, the happy companion of man, and able to take unto herself the praises of the pious prelate, Jeremy Taylor, who says,--"A good wife is Heaven's last best gift to man,--his angel and minister of graces innumerable,--his gem of many virtues,--his casket of jewels--her voice is sweet music--her smiles his brightest day;--her kiss, the guardian of his innocence;--her arms, the pale of his safety, the balm of his health, the balsam of his life;--her industry, his surest wealth;--her economy, his safest steward;--her lips, his faithful counsellors;--her bosom, the softest pillow of his cares; and her prayers, the ablest advocates of Heaven's blessings on his head."
2274
+
2275
+ Cherishing, then, in her breast the respected utterances of the good and the great, let the mistress of every house rise to the responsibility of its management; so that, in doing her duty to all around her, she may receive the genuine reward of respect, love, and affection!
2276
+
2277
+ Although the choice of a house must be dependent on so many different circumstances with different people, that to give any specific directions on this head would be impossible and useless; yet it will be advantageous, perhaps, to many, if we point out some of those general features as to locality, soil, aspect, &c., to which the attention of all house-takers should be carefully directed.
2278
+
2279
+ Regarding the locality, we may say, speaking now more particularly of a town house, that it is very important to the health and comfort of a family, that the neighbourhood of all factories of any kind, producing unwholesome effluvia or smells, should be strictly avoided. Neither is it well to take a house in the immediate vicinity of where a noisy trade is carried on, as it is unpleasant to the feelings, and tends to increase any existing irritation of the system.
2280
+
2281
+ Referring to soils; it is held as a rule, that a gravel soil is superior to any other, as the rain drains through it very quickly, and it is consequently drier and less damp than clay, upon which water rests a far longer time. A clay country, too, is not so pleasant for walking exercise as one in which gravel predominates.
2282
+
2283
+ The aspect of the house should be well considered, and it should be borne in mind that the more sunlight that comes into the house, the healthier is the habitation. The close, fetid smell which assails one on entering a narrow court, or street, in towns, is to be assigned to the want of light, and, consequently, air. A house with a south or south-west aspect, is lighter, warmer, drier, and consequently more healthy, than one facing the north or north-east.
2284
+
2285
+ Great advances have been made, during the last few years, in the principles of sanitary knowledge, and one most essential point to be observed in reference to a house, is its "drainage," as it has been proved in an endless number of cases, that bad or defective drainage is as certain to destroy health as the taking of poisons. This arises from its injuriously affecting the atmosphere; thus rendering the air we breathe unwholesome and deleterious. Let it be borne in mind, then, that unless a house is effectually drained, the health of its inhabitants is sure to suffer; and they will be susceptible of ague, rheumatism, diarrhoea, fevers, and cholera.
2286
+
2287
+ We now come to an all-important point,--that of the water supply. The value of this necessary article has also been lately more and more recognized in connection with the question of health and life; and most houses are well supplied with every convenience connected with water. Let it, however, be well understood, that no house, however suitable in other respects, can be desirable, if this grand means of health and comfort is, in the slightest degree, scarce or impure. No caution can be too great to see that it is pure and good, as well as plentiful; for, knowing, as we do, that not a single part of our daily food is prepared without it, the importance of its influence on the health of the inmates of a house cannot be over-rated.
2288
+
2289
+ Ventilation is another feature which must not be overlooked. In a general way, enough of air is admitted by the cracks round the doors and windows; but if this be not the case, the chimney will smoke; and other plans, such as the placing of a plate of finely-perforated zinc in the upper part of the window, must be used. Cold air should never be admitted under the doors, or at the bottom of a room, unless it be close to the fire or stove; for it will flow along the floor towards the fireplace, and thus leave the foul air in the upper part of the room, unpurified, cooling, at the same time, unpleasantly and injuriously, the feet and legs of the inmates.
2290
+
2291
+ The rent of a house, it has been said, should not exceed one-eighth of the whole income of its occupier; and, as a general rule, we are disposed to assent to this estimate, although there may be many circumstances which would not admit of its being considered infallible.
2292
+
2293
+ 55. AS SECOND IN COMMAND IN THE HOUSE, except in large establishments, where there is a house steward, the housekeeper must consider herself as the immediate representative of her mistress, and bring, to the management of the household, all those qualities of honesty, industry, and vigilance, in the same degree as if she were at the head of her _own_ family. Constantly on the watch to detect any wrong-doing on the part of any of the domestics, she will overlook all that goes on in the house, and will see that every department is thoroughly attended to, and that the servants are comfortable, at the same time that their various duties are properly performed.
2294
+
2295
+ Cleanliness, punctuality, order, and method, are essentials in the character of a good housekeeper. Without the first, no household can be said to be well managed. The second is equally all-important; for those who are under the housekeeper will take their "cue" from her; and in the same proportion as punctuality governs her movements, so will it theirs. Order, again, is indispensable; for by it we wish to be understood that "there should be a place for everything, and everything in its place." Method, too, is most necessary; for when the work is properly contrived, and each part arranged in regular succession, it will be done more quickly and more effectually.
2296
+
2297
+ 56. A NECESSARY QUALIFICATION FOR A HOUSEKEEPER is, that she should thoroughly understand accounts.
2298
+
2299
+ Common farmers seldom employ any overseer to direct the general operations of the farm. They generally, too, work a good deal with their own hands, as ploughmen, harrowers, etc. What remains of the crop, after paying the rent, therefore, should not only replace to them their stock employed in cultivation, together with its ordinary profits, but pay them the wages which are due to them, both as labourers and overseers. Whatever remains, however, after paying the rent and keeping up the stock, is called profit. But wages evidently make a part of it. The farmer, by saving these wages, must necessarily gain them. Wages, therefore, are in this case confounded with profit.
2300
+
2301
+ An independent manufacturer, who has stock enough both to purchase materials, and to maintain himself till he can carry his work to market, should gain both the wages of a journeyman who works under a master, and the profit which that master makes by the sale of that journeyman\u2019s work. His whole gains, however, are commonly called profit, and wages are, in this case, too, confounded with profit.
2302
+
2303
+ A gardener who cultivates his own garden with his own hands, unites in his own person the three different characters, of landlord, farmer, and labourer. His produce, therefore, should pay him the rent of the first, the profit of the second, and the wages of the third. The whole, however, is commonly considered as the earnings of his labour. Both rent and profit are, in this case, confounded with wages.
2304
+
2305
+ As in a civilized country there are but few commodities of which the exchangeable value arises from labour only, rent and profit contributing largely to that of the far greater part of them, so the annual produce of its labour will always be sufficient to purchase or command a much greater quantity of labour than what was employed in raising, preparing, and bringing that produce to market. If the society were annually to employ all the labour which it can annually purchase, as the quantity of labour would increase greatly every year, so the produce of every succeeding year would be of vastly greater value than that of the foregoing. But there is no country in which the whole annual produce is employed in maintaining the industrious. The idle everywhere consume a great part of it; and, according to the different proportions in which it is annually divided between those two different orders of people, its ordinary or average value must either annually increase or diminish, or continue the same from one year to another.
2306
+
2307
+ There is in every society or neighbourhood an ordinary or average rate, both of wages and profit, in every different employment of labour and stock. This rate is naturally regulated, as I shall shew hereafter, partly by the general circumstances of the society, their riches or poverty, their advancing, stationary, or declining condition, and partly by the particular nature of each employment.
2308
+
2309
+ There is likewise in every society or neighbourhood an ordinary or average rate of rent, which is regulated, too, as I shall shew hereafter, partly by the general circumstances of the society or neighbourhood in which the land is situated, and partly by the natural or improved fertility of the land.
2310
+
2311
+ These ordinary or average rates may be called the natural rates of wages, profit and rent, at the time and place in which they commonly prevail.
2312
+
2313
+ When the price of any commodity is neither more nor less than what is sufficient to pay the rent of the land, the wages of the labour, and the profits of the stock employed in raising, preparing, and bringing it to market, according to their natural rates, the commodity is then sold for what may be called its natural price.
2314
+
2315
+ The commodity is then sold precisely for what it is worth, or for what it really costs the person who brings it to market; for though, in common language, what is called the prime cost of any commodity does not comprehend the profit of the person who is to sell it again, yet, if he sells it at a price which does not allow him the ordinary rate of profit in his neighbourhood, he is evidently a loser by the trade; since, by employing his stock in some other way, he might have made that profit. His profit, besides, is his revenue, the proper fund of his subsistence. As, while he is preparing and bringing the goods to market, he advances to his workmen their wages, or their subsistence; so he advances to himself, in the same manner, his own subsistence, which is generally suitable to the profit which he may reasonably expect from the sale of his goods. Unless they yield him this profit, therefore, they do not repay him what they may very properly be said to have really cost him.
2316
+
2317
+ Though the price, therefore, which leaves him this profit, is not always the lowest at which a dealer may sometimes sell his goods, it is the lowest at which he is likely to sell them for any considerable time; at least where there is perfect liberty, or where he may change his trade as often as he pleases.
2318
+
2319
+ The actual price at which any commodity is commonly sold, is called its market price. It may either be above, or below, or exactly the same with its natural price.
2320
+
2321
+ The market price of every particular commodity is regulated by the proportion between the quantity which is actually brought to market, and the demand of those who are willing to pay the natural price of the commodity, or the whole value of the rent, labour, and profit, which must be paid in order to bring it thither. Such people may be called the effectual demanders, and their demand the effectual demand; since it maybe sufficient to effectuate the bringing of the commodity to market. It is different from the absolute demand. A very poor man may be said, in some sense, to have a demand for a coach and six; he might like to have it; but his demand is not an effectual demand, as the commodity can never be brought to market in order to satisfy it.
2322
+
2323
+ When the quantity of any commodity which is brought to market falls short of the effectual demand, all those who are willing to pay the whole value of the rent, wages, and profit, which must be paid in order to bring it thither, cannot be supplied with the quantity which they want. Rather than want it altogether, some of them will be willing to give more. A competition will immediately begin among them, and the market price will rise more or less above the natural price, according as either the greatness of the deficiency, or the wealth and wanton luxury of the competitors, happen to animate more or less the eagerness of the competition.`;
2324
+ var T = V4_THRESHOLDS;
2325
+ function countWords2(text) {
2326
+ return (text.match(/\S+/g) ?? []).length;
2327
+ }
2328
+ function countMatches(re, text) {
2329
+ const g = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
2330
+ let n = 0;
2331
+ while (g.exec(text) !== null) {
2332
+ n += 1;
2333
+ if (g.lastIndex === 0) break;
2334
+ }
2335
+ return n;
2336
+ }
2337
+ function paragraphSentences(paraText) {
2338
+ return paraText.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length > 0);
2339
+ }
2340
+ var NUMERIC_RE = /[£$€]?\d|\d%/;
2341
+ var MID_CAP_RE = /(?<=[a-z][,;]?\s)[A-Z][a-z]{2,}/;
2342
+ function isAbstractShort(sentence) {
2343
+ if (NUMERIC_RE.test(sentence)) return false;
2344
+ if (MID_CAP_RE.test(sentence)) return false;
2345
+ return ABSTRACT_PUNCH_RE.test(sentence);
2346
+ }
2347
+ function spectralFlatness(sentenceWordCounts) {
2348
+ const W = T.spectralWindowSentences;
2349
+ const windows = Math.floor(sentenceWordCounts.length / W);
2350
+ if (windows < T.spectralMinWindows) return null;
2351
+ const EPS = 1e-9;
2352
+ let sum = 0;
2353
+ for (let w = 0; w < windows; w += 1) {
2354
+ const seg = sentenceWordCounts.slice(w * W, (w + 1) * W);
2355
+ const mean = seg.reduce((a, b) => a + b, 0) / W;
2356
+ const x = seg.map((v) => v - mean);
2357
+ let logSum = 0;
2358
+ let linSum = 0;
2359
+ const bins = Math.floor(W / 2);
2360
+ for (let k = 1; k <= bins; k += 1) {
2361
+ let re = 0;
2362
+ let im = 0;
2363
+ for (let n = 0; n < W; n += 1) {
2364
+ const ang = 2 * Math.PI * k * n / W;
2365
+ re += x[n] * Math.cos(ang);
2366
+ im -= x[n] * Math.sin(ang);
2367
+ }
2368
+ const p = re * re + im * im + EPS;
2369
+ logSum += Math.log(p);
2370
+ linSum += p;
2371
+ }
2372
+ const flat = Math.exp(logSum / bins) / (linSum / bins);
2373
+ sum += flat;
2374
+ }
2375
+ return sum / windows;
2376
+ }
2377
+ var HASH_SPAN = 4;
2378
+ function hashAt(s, i) {
2379
+ return ((s.charCodeAt(i) * 131 + s.charCodeAt(i + 1)) * 131 + s.charCodeAt(i + 2)) * 131 + s.charCodeAt(i + 3) >>> 0;
2380
+ }
2381
+ var MAX_CHAIN = 32;
2382
+ var MAX_MATCH = 258;
2383
+ function indexInto(index, s, from, to) {
2384
+ for (let i = from; i <= to - HASH_SPAN; i += 1) {
2385
+ const h = hashAt(s, i);
2386
+ let chain = index.get(h);
2387
+ if (!chain) {
2388
+ chain = [];
2389
+ index.set(h, chain);
2390
+ }
2391
+ chain.push(i);
2392
+ if (chain.length > MAX_CHAIN * 2) chain.splice(0, chain.length - MAX_CHAIN);
2393
+ }
2394
+ }
2395
+ var corpusIndex = null;
2396
+ function getCorpusIndex() {
2397
+ if (corpusIndex === null) {
2398
+ corpusIndex = /* @__PURE__ */ new Map();
2399
+ indexInto(corpusIndex, REFERENCE_CORPUS, 0, REFERENCE_CORPUS.length);
2400
+ }
2401
+ return corpusIndex;
2402
+ }
2403
+ function lzCostBits(target, withCorpusPrior) {
2404
+ const dict = withCorpusPrior ? REFERENCE_CORPUS : "";
2405
+ const combined = withCorpusPrior ? dict + target : target;
2406
+ const base = dict.length;
2407
+ const selfIndex = /* @__PURE__ */ new Map();
2408
+ const dictIndex = withCorpusPrior ? getCorpusIndex() : null;
2409
+ let bits = 0;
2410
+ let i = base;
2411
+ while (i < combined.length) {
2412
+ let bestLen = 0;
2413
+ let bestDist = 0;
2414
+ if (i + HASH_SPAN <= combined.length) {
2415
+ const h = hashAt(combined, i);
2416
+ const tryChain = (chain, offset) => {
2417
+ if (!chain) return;
2418
+ const start = Math.max(0, chain.length - MAX_CHAIN);
2419
+ for (let c = chain.length - 1; c >= start; c -= 1) {
2420
+ const j = chain[c] + offset;
2421
+ if (j >= i) continue;
2422
+ let len = 0;
2423
+ const maxLen = Math.min(MAX_MATCH, combined.length - i);
2424
+ while (len < maxLen && combined.charCodeAt(j + len) === combined.charCodeAt(i + len)) len += 1;
2425
+ if (len > bestLen) {
2426
+ bestLen = len;
2427
+ bestDist = i - j;
2428
+ }
2429
+ }
2430
+ };
2431
+ tryChain(selfIndex.get(h), base);
2432
+ if (dictIndex) tryChain(dictIndex.get(h), 0);
2433
+ }
2434
+ if (bestLen >= HASH_SPAN) {
2435
+ bits += 13 + Math.log2(bestDist);
2436
+ for (let k = i; k < i + bestLen && k + HASH_SPAN <= combined.length; k += 2) {
2437
+ const h2 = hashAt(combined, k);
2438
+ let chain = selfIndex.get(h2);
2439
+ if (!chain) {
2440
+ chain = [];
2441
+ selfIndex.set(h2, chain);
2442
+ }
2443
+ chain.push(k - base);
2444
+ if (chain.length > MAX_CHAIN * 2) chain.splice(0, chain.length - MAX_CHAIN);
2445
+ }
2446
+ i += bestLen;
2447
+ } else {
2448
+ bits += 9;
2449
+ if (i + HASH_SPAN <= combined.length) {
2450
+ const h2 = hashAt(combined, i);
2451
+ let chain = selfIndex.get(h2);
2452
+ if (!chain) {
2453
+ chain = [];
2454
+ selfIndex.set(h2, chain);
2455
+ }
2456
+ chain.push(i - base);
2457
+ if (chain.length > MAX_CHAIN * 2) chain.splice(0, chain.length - MAX_CHAIN);
2458
+ }
2459
+ i += 1;
2460
+ }
2461
+ }
2462
+ return bits;
2463
+ }
2464
+ function compressionGain(text) {
2465
+ const solo = lzCostBits(text, false);
2466
+ if (solo <= 0) return 0;
2467
+ const cond = lzCostBits(text, true);
2468
+ return 1 - cond / solo;
2469
+ }
2470
+ function registerProfile(text) {
2471
+ const tokens2 = text.toLowerCase().match(/[a-z][a-z'’-]*/g) ?? [];
2472
+ const counts = /* @__PURE__ */ new Map();
2473
+ let long = 0;
2474
+ for (const t of tokens2) {
2475
+ counts.set(t, (counts.get(t) ?? 0) + 1);
2476
+ if (t.length >= REGISTER_LONG_WORD_LEN) long += 1;
2477
+ }
2478
+ const n = Math.max(1, tokens2.length);
2479
+ return {
2480
+ func: REGISTER_FUNCTION_WORDS.map((w) => (counts.get(w) ?? 0) / n),
2481
+ longWordShare: long / n,
2482
+ tokenCount: tokens2.length
2483
+ };
2484
+ }
2485
+ var refProfile = null;
2486
+ function referenceRegisterProfile() {
2487
+ if (refProfile === null) refProfile = registerProfile(REFERENCE_CORPUS);
2488
+ return refProfile;
2489
+ }
2490
+ function registerDistance(text) {
2491
+ const p = registerProfile(text);
2492
+ const r = referenceRegisterProfile();
2493
+ let l1 = 0;
2494
+ for (let i = 0; i < p.func.length; i += 1) l1 += Math.abs(p.func[i] - r.func[i]);
2495
+ return { funcL1: l1, longWordDelta: p.longWordShare - r.longWordShare };
2496
+ }
2497
+ function collectMetrics(doc) {
2498
+ const { text, wordCount, paragraphs } = doc;
2499
+ const allSentences = [];
2500
+ for (const p of paragraphs) allSentences.push(...paragraphSentences(p.text));
2501
+ const counts = allSentences.map(countWords2).filter((c) => c > 0);
2502
+ const flat = spectralFlatness(counts);
2503
+ const gain = wordCount >= T.compressionMinWords && wordCount <= T.compressionMaxWords ? compressionGain(text) : null;
2504
+ const reg = registerDistance(text);
2505
+ let punchCount = 0;
2506
+ let punchFinal = 0;
2507
+ for (const p of paragraphs) {
2508
+ const sents = paragraphSentences(p.text);
2509
+ for (let i = 0; i < sents.length; i += 1) {
2510
+ const s = sents[i];
2511
+ const wc = countWords2(s);
2512
+ if (wc > 0 && wc <= 8 && /[.!?]$/.test(s) && isAbstractShort(s)) {
2513
+ punchCount += 1;
2514
+ if (i === sents.length - 1) punchFinal += 1;
2515
+ }
2516
+ }
2517
+ }
2518
+ const punchRate = allSentences.length > 0 ? punchCount / allSentences.length : 0;
2519
+ let micDrops = 0;
2520
+ for (const p of paragraphs) {
2521
+ const sents = paragraphSentences(p.text);
2522
+ if (sents.length < 4) continue;
2523
+ const closer = sents[sents.length - 1];
2524
+ const closerWc = countWords2(closer);
2525
+ const setupWcs = sents.slice(0, -1).map(countWords2).filter((c) => c >= 12);
2526
+ if (setupWcs.length < 3) continue;
2527
+ const setupMean = setupWcs.reduce((a, b) => a + b, 0) / setupWcs.length;
2528
+ if (closerWc > 0 && closerWc <= 8 && closerWc <= 0.45 * setupMean && !NUMERIC_RE.test(closer) && !MID_CAP_RE.test(closer) && MIC_DROP_CONTRAST_RE.test(closer)) {
2529
+ micDrops += 1;
2530
+ }
2531
+ }
2532
+ let contrastCount = 0;
2533
+ for (const re of NOT_JUST_CONTRAST) contrastCount += countMatches(re, text);
2534
+ contrastCount += countMatches(NEG_PARALLELISM_RE, text);
2535
+ for (const re of CONTRAST_VARIANT_RES) contrastCount += countMatches(re, text);
2536
+ const contrastPer1000 = wordCount > 0 ? contrastCount / (wordCount / 1e3) : 0;
2537
+ let abstract = 0;
2538
+ let concrete = 0;
2539
+ for (const s of allSentences) {
2540
+ const isConcrete = NUMERIC_RE.test(s) || MID_CAP_RE.test(s) || CONCRETE_ACTION_VERB_RE.test(s);
2541
+ if (isConcrete) {
2542
+ concrete += 1;
2543
+ } else if (ABSTRACT_CLAIM_RE.test(s) || countWords2(s) <= 8) {
2544
+ abstract += 1;
2545
+ }
2546
+ }
2547
+ const share = allSentences.length > 0 ? abstract / allSentences.length : 0;
2548
+ return {
2549
+ wordCount,
2550
+ sentenceCount: allSentences.length,
2551
+ spectralFlatness: flat,
2552
+ compressionGain: gain,
2553
+ registerFuncL1: reg.funcL1,
2554
+ registerLongWordDelta: reg.longWordDelta,
2555
+ punchlineCount: punchCount,
2556
+ punchlineRate: punchRate,
2557
+ punchlineParagraphFinal: punchFinal,
2558
+ micDropParagraphs: micDrops,
2559
+ contrastCount,
2560
+ contrastPer1000,
2561
+ ratioSentences: allSentences.length,
2562
+ ratioAbstract: abstract,
2563
+ ratioConcrete: concrete,
2564
+ ratioAbstractShare: share
2565
+ };
2566
+ }
2567
+ var round = (v, dp = 3) => Math.round(v * 10 ** dp) / 10 ** dp;
2568
+ function collectV4Issues(ctx) {
2569
+ const m = collectMetrics(ctx);
2570
+ const { pushEx } = ctx;
2571
+ if (m.spectralFlatness !== null && m.spectralFlatness < T.spectralFlatnessMax) {
2572
+ pushEx(
2573
+ "sentence-length-spectral-flatness",
2574
+ `window-averaged spectral flatness ${round(m.spectralFlatness)} (threshold ${T.spectralFlatnessMax})`,
2575
+ null,
2576
+ null,
2577
+ {
2578
+ extra: {
2579
+ spectral_flatness: round(m.spectralFlatness),
2580
+ window_sentences: T.spectralWindowSentences,
2581
+ sentence_count: m.sentenceCount
2582
+ }
2583
+ }
2584
+ );
2585
+ }
2586
+ if (m.compressionGain !== null && m.compressionGain < T.compressionGainMin) {
2587
+ pushEx(
2588
+ "conditional-compression",
2589
+ `human-prior compression gain ${round(m.compressionGain)} (threshold ${T.compressionGainMin})`,
2590
+ null,
2591
+ null,
2592
+ {
2593
+ extra: { compression_gain: round(m.compressionGain), reference_corpus: "en-signals-v4-corpus 2026.08.5 (public-domain, pre-1929)" }
2594
+ }
2595
+ );
2596
+ }
2597
+ if (m.wordCount >= T.registerMinWords && m.registerFuncL1 > T.registerFuncL1Min && m.registerLongWordDelta > T.registerLongWordDeltaMin) {
2598
+ pushEx(
2599
+ "lexical-register-distance",
2600
+ `function-word L1 ${round(m.registerFuncL1)} + long-word share +${round(m.registerLongWordDelta)} vs human reference`,
2601
+ null,
2602
+ null,
2603
+ {
2604
+ extra: {
2605
+ function_word_l1: round(m.registerFuncL1),
2606
+ long_word_share_delta: round(m.registerLongWordDelta),
2607
+ genre_caveat: "specialised genres legitimately measure as distant; corroboration only"
2608
+ }
2609
+ }
2610
+ );
2611
+ }
2612
+ if (m.punchlineCount >= T.punchlineMinCount && m.punchlineRate >= T.punchlineMinRate && m.punchlineParagraphFinal >= T.punchlineMinParagraphFinal) {
2613
+ pushEx(
2614
+ "punchline-fragment-density",
2615
+ `${m.punchlineCount} abstract punchline fragments in ${m.sentenceCount} sentences (${m.punchlineParagraphFinal} paragraph-final)`,
2616
+ null,
2617
+ null,
2618
+ {
2619
+ count: m.punchlineCount,
2620
+ extra: { punchline_rate: round(m.punchlineRate), paragraph_final: m.punchlineParagraphFinal }
2621
+ }
2622
+ );
2623
+ }
2624
+ if (m.micDropParagraphs >= T.micDropMinParagraphs) {
2625
+ pushEx(
2626
+ "mic-drop-paragraph",
2627
+ `${m.micDropParagraphs} paragraphs end in a short abstract contrast closer`,
2628
+ null,
2629
+ null,
2630
+ { count: m.micDropParagraphs }
2631
+ );
2632
+ }
2633
+ if (m.contrastCount >= T.contrastMinCount && m.contrastPer1000 >= T.contrastMinPer1000) {
2634
+ pushEx(
2635
+ "contrast-density",
2636
+ `${m.contrastCount} contrast constructions (${round(m.contrastPer1000, 1)}/1000 words)`,
2637
+ null,
2638
+ null,
2639
+ {
2640
+ count: m.contrastCount,
2641
+ extra: { rate_per_1000_words: round(m.contrastPer1000, 1) }
2642
+ }
2643
+ );
2644
+ }
2645
+ if (m.ratioSentences >= T.ratioMinSentences && m.ratioAbstract >= T.ratioMinAbstract && m.ratioAbstractShare >= T.ratioMinShare && m.ratioConcrete <= T.ratioMaxConcrete) {
2646
+ pushEx(
2647
+ "rhetorical-procedural-ratio",
2648
+ `${m.ratioAbstract} abstract-claim vs ${m.ratioConcrete} concrete-action sentences of ${m.ratioSentences}`,
2649
+ null,
2650
+ null,
2651
+ {
2652
+ count: m.ratioAbstract,
2653
+ extra: {
2654
+ abstract_sentences: m.ratioAbstract,
2655
+ concrete_sentences: m.ratioConcrete,
2656
+ abstract_share: round(m.ratioAbstractShare)
2657
+ }
2658
+ }
2659
+ );
2660
+ }
2661
+ }
2662
+ var EN_SIGNALS_PATTERN_VERSION = "en-signals:2026.08.6";
2663
+ var MERGED_WEIGHTS = { ...ISSUE_WEIGHTS, ...V3_ISSUE_WEIGHTS, ...V4_ISSUE_WEIGHTS };
2664
+ var MERGED_META = { ...CATEGORY_META, ...V3_CATEGORY_META, ...V4_CATEGORY_META };
2665
+ var MAX_SCORED_WORDS = 1e4;
2666
+ var DESCRIPTION = "Editorial writing-signals score from documented writing-pattern rules and stylometric measurements. It is stylistic evidence about how the text reads, not proof of who or what wrote it.";
2667
+ var ZERO_WIDTH = /[\u200B\u200C\u200D\uFEFF\u2060]/;
2668
+ var CYRILLIC_GREEK = /[Ѐ-ӿͰ-Ͽ]/;
2669
+ function normalise(original) {
2670
+ const flags = { zeroWidth: 0, homoglyph: 0, roleplay: 0 };
2671
+ let firstStrippedAt = -1;
2672
+ let chars = [];
2673
+ let map = [];
2674
+ for (let i = 0; i < original.length; i += 1) {
2675
+ const ch = original[i];
2676
+ if (ZERO_WIDTH.test(ch)) {
2677
+ flags.zeroWidth += 1;
2678
+ if (firstStrippedAt < 0) firstStrippedAt = i;
2679
+ continue;
2680
+ }
2681
+ if (CYRILLIC_GREEK.test(ch)) {
2682
+ const swap = CYRILLIC_LOOKALIKES[ch] ?? GREEK_LOOKALIKES[ch];
2683
+ if (swap !== void 0) {
2684
+ flags.homoglyph += 1;
2685
+ if (firstStrippedAt < 0) firstStrippedAt = i;
2686
+ chars.push(swap);
2687
+ map.push(i);
2688
+ continue;
2689
+ }
2690
+ }
2691
+ chars.push(ch);
2692
+ map.push(i);
2693
+ }
2694
+ const joined = chars.join("");
2695
+ const roleplayRe = /(?<!\*)\*([^*\n]{1,80}?)\*(?!\*)/gu;
2696
+ const removals = [];
2697
+ let m;
2698
+ while ((m = roleplayRe.exec(joined)) !== null) {
2699
+ if (ROLEPLAY_VERBS.test(m[1])) {
2700
+ flags.roleplay += 1;
2701
+ removals.push([m.index, m.index + m[0].length]);
2702
+ }
2703
+ }
2704
+ if (removals.length > 0) {
2705
+ const keptChars = [];
2706
+ const keptMap = [];
2707
+ let r = 0;
2708
+ for (let i = 0; i < joined.length; i += 1) {
2709
+ while (r < removals.length && i >= removals[r][1]) r += 1;
2710
+ if (r < removals.length && i >= removals[r][0] && i < removals[r][1]) continue;
2711
+ keptChars.push(joined[i]);
2712
+ keptMap.push(map[i]);
2713
+ }
2714
+ chars = keptChars;
2715
+ map = keptMap;
2716
+ }
2717
+ return { text: chars.join(""), map, flags, firstStrippedAt };
2718
+ }
2719
+ function countWords3(text) {
2720
+ return (text.match(/\S+/g) ?? []).length;
2721
+ }
2722
+ function tokenizeWithIndex(text) {
2723
+ const out = [];
2724
+ const re = /[\w'-]+/g;
2725
+ let m;
2726
+ while ((m = re.exec(text)) !== null) out.push({ token: m[0].toLowerCase(), index: m.index });
2727
+ return out;
2728
+ }
2729
+ function paragraphsWithOffsets(text) {
2730
+ const parts = [];
2731
+ const re = /\n\s*\n/g;
2732
+ let last = 0;
2733
+ let m;
2734
+ while ((m = re.exec(text)) !== null) {
2735
+ parts.push({ text: text.slice(last, m.index), start: last });
2736
+ last = m.index + m[0].length;
2737
+ }
2738
+ parts.push({ text: text.slice(last), start: last });
2739
+ return parts.filter((p) => p.text.trim().length > 0);
2740
+ }
2741
+ function getSentences(text) {
2742
+ return text.split(/[.!?]+/).filter((s) => s.trim().length > 5);
2743
+ }
2744
+ function execAll2(pattern, text) {
2745
+ const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
2746
+ const out = [];
2747
+ let m;
2748
+ while ((m = re.exec(text)) !== null) {
2749
+ out.push(m);
2750
+ if (m[0].length === 0) re.lastIndex += 1;
2751
+ }
2752
+ return out;
2753
+ }
2754
+ function fenceRanges(text) {
2755
+ const re = /^[ \t]{0,3}(`{3,}|~{3,})([^\n]*)$/gm;
2756
+ const ranges = [];
2757
+ let open = null;
2758
+ let m;
2759
+ while ((m = re.exec(text)) !== null) {
2760
+ const marker = m[1];
2761
+ if (!open) {
2762
+ open = { char: marker[0], len: marker.length, start: m.index };
2763
+ } else if (marker[0] === open.char && marker.length >= open.len && /^[ \t]*\r?$/.test(m[2])) {
2764
+ ranges.push([open.start, m.index + m[0].length]);
2765
+ open = null;
2766
+ }
2767
+ }
2768
+ if (open) ranges.push([open.start, text.length]);
2769
+ return ranges;
2770
+ }
2771
+ function inFenceRange(ranges, index) {
2772
+ return ranges.some(([a, b]) => index >= a && index < b);
2773
+ }
2774
+ function maskCode(text) {
2775
+ const chars = text.split("");
2776
+ const blank = (a, b) => {
2777
+ for (let i = a; i < b && i < chars.length; i += 1) if (chars[i] !== "\n") chars[i] = " ";
2778
+ };
2779
+ for (const [a, b] of fenceRanges(text)) blank(a, b);
2780
+ const withoutFences = chars.join("");
2781
+ const inlineRe = /(`+)(?:(?!\1)[^\n])+\1/g;
2782
+ let m;
2783
+ while ((m = inlineRe.exec(withoutFences)) !== null) blank(m.index, m.index + m[0].length);
2784
+ return chars.join("");
2785
+ }
2786
+ var HEX_COLOUR = /^(?=[0-9a-f]*\d)(?:[0-9a-f]{6}|[0-9a-f]{8})$/i;
2787
+ var CPP_DIRECTIVE = /^(?:include|define|undef|if|ifdef|ifndef|elif|else|endif|pragma|error|warning|line)$/;
2788
+ function isSocialTag(tag) {
2789
+ return !/^\d+$/.test(tag) && !HEX_COLOUR.test(tag) && !CPP_DIRECTIVE.test(tag);
2790
+ }
2791
+ var byLengthDesc = (a, b) => b.length - a.length || a.localeCompare(b);
2792
+ var TIER1_WORD_RE = new RegExp("\\b(?:" + Object.keys(TIER1).sort(byLengthDesc).join("|") + ")\\b", "gi");
2793
+ var TIER2_WORD_RE = new RegExp("\\b(?:" + Object.keys(TIER2).sort(byLengthDesc).join("|") + ")\\b", "gi");
2794
+ var TIER3_LOOKUP = /* @__PURE__ */ new Map();
2795
+ for (const word of TIER3) {
2796
+ TIER3_LOOKUP.set(word, word);
2797
+ const dashless = word.replace(/-/g, "");
2798
+ if (dashless !== word) TIER3_LOOKUP.set(dashless, word);
2799
+ }
2800
+ function analyse(original) {
2801
+ const norm = normalise(original);
2802
+ const text = norm.text;
2803
+ const map = norm.map;
2804
+ const span = (nStart, nEnd) => {
2805
+ if (nEnd <= nStart || nStart >= map.length) return [null, null];
2806
+ const last = Math.min(nEnd, map.length) - 1;
2807
+ return [map[nStart], map[last] + 1];
2808
+ };
2809
+ const issues = [];
2810
+ const push = (category, key, nStart, nEnd, suggestion, count) => {
2811
+ let s = null;
2812
+ let e = null;
2813
+ if (nStart !== null && nEnd !== null) [s, e] = span(nStart, nEnd);
2814
+ issues.push({ category, key, start: s, end: e, ...suggestion !== void 0 ? { suggestion } : {}, ...count !== void 0 ? { count } : {} });
2815
+ };
2816
+ const pushEx = (category, key, nStart, nEnd, opts = {}) => {
2817
+ let s = null;
2818
+ let e = null;
2819
+ if (nStart !== null && nEnd !== null) [s, e] = span(nStart, nEnd);
2820
+ issues.push({
2821
+ category,
2822
+ key,
2823
+ start: s,
2824
+ end: e,
2825
+ ...opts.suggestion !== void 0 ? { suggestion: opts.suggestion } : {},
2826
+ ...opts.count !== void 0 ? { count: opts.count } : {},
2827
+ ...opts.extra !== void 0 ? { extra: opts.extra } : {},
2828
+ ...opts.severityOverride !== void 0 ? { severityOverride: opts.severityOverride } : {}
2829
+ });
2830
+ };
2831
+ const pushPatterns = (patterns, category) => {
2832
+ const added = [];
2833
+ for (const pattern of patterns) {
2834
+ for (const m of execAll2(pattern, text)) {
2835
+ push(category, m[0], m.index, m.index + m[0].length);
2836
+ added.push(issues[issues.length - 1]);
2837
+ }
2838
+ }
2839
+ return added;
2840
+ };
2841
+ const wordCount = countWords3(text);
2842
+ const tokens2 = tokenizeWithIndex(text);
2843
+ const paragraphs = paragraphsWithOffsets(text);
2844
+ const sentences = getSentences(text);
2845
+ if (wordCount < 10) {
2846
+ return { issues: [], wordCount, tier2Clusters: 0, tier1Distinct: 0, normFlags: norm.flags };
2847
+ }
2848
+ const tier1Found = /* @__PURE__ */ new Set();
2849
+ for (const m of execAll2(TIER1_WORD_RE, text)) {
2850
+ const lower = m[0].toLowerCase();
2851
+ if (tier1Found.has(lower)) continue;
2852
+ tier1Found.add(lower);
2853
+ push("tier1", lower, m.index, m.index + m[0].length, TIER1[lower]);
2854
+ }
2855
+ for (const phrase of TIER1_PHRASES) {
2856
+ for (const m of execAll2(phrase.pattern, text)) {
2857
+ const lower = m[0].toLowerCase();
2858
+ if (tier1Found.has(lower)) continue;
2859
+ tier1Found.add(lower);
2860
+ push(phrase.clarity ? "tier1-clarity" : "tier1", lower, m.index, m.index + m[0].length, phrase.replace);
2861
+ }
2862
+ }
2863
+ let tier2Clusters = 0;
2864
+ for (const para of paragraphs) {
2865
+ const found = /* @__PURE__ */ new Map();
2866
+ for (const m of execAll2(TIER2_WORD_RE, para.text)) {
2867
+ const lower = m[0].toLowerCase();
2868
+ if (!found.has(lower)) found.set(lower, { index: para.start + m.index, suggestion: TIER2[lower] ?? "" });
2869
+ }
2870
+ for (const cond of TIER2_CONDITIONAL) {
2871
+ if (found.has(cond.word)) continue;
2872
+ const m = new RegExp(cond.pattern.source, cond.pattern.flags).exec(para.text);
2873
+ if (m) found.set(cond.word, { index: para.start + m.index, suggestion: cond.suggestion });
2874
+ }
2875
+ if (found.size >= 2) {
2876
+ tier2Clusters += 1;
2877
+ for (const [word, at] of found) push("tier2", word, at.index, at.index + word.length, at.suggestion);
2878
+ }
2879
+ }
2880
+ const tier3Counts = /* @__PURE__ */ new Map();
2881
+ for (const t of tokens2) {
2882
+ const canonical2 = TIER3_LOOKUP.get(t.token);
2883
+ if (!canonical2) continue;
2884
+ const entry = tier3Counts.get(canonical2);
2885
+ if (entry) entry.count += 1;
2886
+ else tier3Counts.set(canonical2, { count: 1, first: t.index, firstLen: t.token.length });
2887
+ }
2888
+ const densityThreshold = Math.max(3, Math.floor(wordCount * 0.03));
2889
+ for (const [word, entry] of tier3Counts) {
2890
+ if (entry.count >= densityThreshold) {
2891
+ push(
2892
+ "tier3",
2893
+ `"${word}" x${entry.count}`,
2894
+ entry.first,
2895
+ entry.first + entry.firstLen,
2896
+ `Used ${entry.count} times in ${wordCount} words; vary the wording.`,
2897
+ entry.count
2898
+ );
2899
+ }
2900
+ }
2901
+ pushPatterns(TRANSITIONS, "transition");
2902
+ pushPatterns(CHATBOT_ARTIFACTS, "chatbot");
2903
+ pushPatterns(SYCOPHANTIC, "sycophantic");
2904
+ pushPatterns(FILLERS, "filler");
2905
+ pushPatterns(GENERIC_CONCLUSIONS, "generic-conclusion");
2906
+ pushPatterns(LETS_PATTERNS, "lets-construction");
2907
+ pushPatterns(REASONING_ARTIFACTS, "reasoning-artifact");
2908
+ pushPatterns(ACKNOWLEDGMENT_LOOPS, "acknowledgment-loop");
2909
+ pushPatterns(SIGNIFICANCE_INFLATION, "significance-inflation");
2910
+ pushPatterns(VAGUE_ATTRIBUTIONS, "vague-attribution");
2911
+ pushPatterns(HOLLOW_INTENSIFIERS, "hollow-intensifier");
2912
+ pushPatterns(EMOTIONAL_FLATLINE, "emotional-flatline");
2913
+ pushPatterns(LINGERING_ATTENTION, "lingering-attention");
2914
+ pushPatterns(NOVELTY_INFLATION, "novelty-inflation");
2915
+ pushPatterns(CUTOFF_DISCLAIMERS, "cutoff-disclaimer");
2916
+ pushPatterns(AI_PLACEHOLDERS, "ai-placeholder");
2917
+ pushPatterns(AI_CITATION_MARKUP, "ai-citation-markup");
2918
+ pushPatterns(AI_UTM_SOURCE, "ai-utm-source");
2919
+ pushPatterns(TEMPLATE_PHRASES, "template-phrase");
2920
+ pushPatterns(FALSE_CONCESSION, "false-concession");
2921
+ pushPatterns(RHETORICAL_QUESTIONS, "rhetorical-question");
2922
+ pushPatterns(HEDGE_STACK, "hedge-stack");
2923
+ pushPatterns(FUTURE_NARRATIVE, "future-narrative");
2924
+ pushPatterns(REAL_ACTUAL_INFLATION, "real-actual-inflation");
2925
+ pushPatterns(SOCIAL_CTA_CLOSER, "social-cta-closer");
2926
+ pushPatterns(NOT_JUST_CONTRAST, "not-just-contrast");
2927
+ pushPatterns(FORMULAIC_OPENERS, "formulaic-opener");
2928
+ pushPatterns(SPECULATIVE_OPENERS, "speculative-opener");
2929
+ pushPatterns(PARENTHETICAL_HEDGE, "parenthetical-hedge");
2930
+ {
2931
+ const hits = execAll2(TITLE_CASE_HEADER, text).filter((m) => {
2932
+ const title = m[0].replace(MD_HEADING_PREFIX, "");
2933
+ const parts = title.trim().split(/\s+/);
2934
+ if (parts.length < 4) return false;
2935
+ return FUNCTION_WORD_IN_TITLE.test(parts.slice(1).join(" "));
2936
+ });
2937
+ const fences = hits.length ? fenceRanges(text) : [];
2938
+ for (const m of hits) {
2939
+ if (!inFenceRange(fences, m.index)) push("title-case-header", m[0], m.index, m.index + m[0].length);
2940
+ }
2941
+ }
2942
+ if (norm.flags.zeroWidth > 0 || norm.flags.homoglyph >= 2) {
2943
+ const at = norm.firstStrippedAt;
2944
+ issues.push({
2945
+ category: "normalization-flag",
2946
+ key: `${norm.flags.zeroWidth} zero-width + ${norm.flags.homoglyph} homoglyph swaps`,
2947
+ start: at >= 0 ? at : null,
2948
+ end: at >= 0 ? at + 1 : null,
2949
+ count: norm.flags.zeroWidth + norm.flags.homoglyph
2950
+ });
2951
+ }
2952
+ if (norm.flags.roleplay >= 2) {
2953
+ issues.push({
2954
+ category: "normalization-flag",
2955
+ key: `${norm.flags.roleplay} roleplay-action markers stripped`,
2956
+ start: null,
2957
+ end: null,
2958
+ count: norm.flags.roleplay
2959
+ });
2960
+ }
2961
+ {
2962
+ const hasCurly = /[“”‘’]/.test(text);
2963
+ const totalEmDashes = (text.match(/—/g) ?? []).length;
2964
+ const separatorEmDashes = (text.match(SEPARATOR_DASH_RE) ?? []).length + (text.match(VERSION_HEADING_DASH_RE) ?? []).length;
2965
+ const hasEmDash = totalEmDashes > separatorEmDashes;
2966
+ const hasOxford = (text.match(/\b\w+,\s+\w+,\s+and\s+\w+/g)?.length ?? 0) >= 1;
2967
+ const doubleSpaces = (text.match(/[^.!?] +/g) ?? []).length;
2968
+ const missingApos = /\b(?:dont|wont|cant|isnt|wasnt|shouldnt|wouldnt|couldnt|youre|theyre|its\s+a\s+\w+ing)\b/i.test(text);
2969
+ const clean = doubleSpaces === 0 && !missingApos;
2970
+ const signals = [hasCurly, hasEmDash, hasOxford, clean].filter(Boolean).length;
2971
+ if (signals >= 4 && wordCount >= 80) {
2972
+ const first = text.search(/[“”‘’—]/);
2973
+ push(
2974
+ "smart-punct-signature",
2975
+ "curly quotes + em dash + Oxford comma + zero typos",
2976
+ first >= 0 ? first : null,
2977
+ first >= 0 ? first + 1 : null
2978
+ );
2979
+ }
2980
+ }
2981
+ if (paragraphs.length >= 4) {
2982
+ const densities = paragraphs.map((p) => {
2983
+ const words = (p.text.match(/\S+/g) ?? []).length;
2984
+ if (words < 5) return null;
2985
+ return (p.text.match(/[,;:—()]/g) ?? []).length / words;
2986
+ }).filter((d) => d !== null);
2987
+ if (densities.length >= 4) {
2988
+ const mean = densities.reduce((a, b) => a + b, 0) / densities.length;
2989
+ const variance = densities.reduce((s, d) => s + (d - mean) ** 2, 0) / densities.length;
2990
+ const cv = mean > 0 ? Math.sqrt(variance) / mean : 0;
2991
+ if (cv < 0.25 && mean >= 0.04) {
2992
+ push("punct-distribution", `Punctuation density uniform across paragraphs (CV=${cv.toFixed(2)})`, null, null);
2993
+ }
2994
+ }
2995
+ }
2996
+ if (wordCount >= 150) {
2997
+ const mapped = tokens2.map((t) => FUNC_WORDS.has(t.token) ? t.token : "_");
2998
+ const seq = mapped.filter((v, i, arr) => v !== "_" || i > 0 && arr[i - 1] !== "_");
2999
+ if (seq.length >= 50) {
3000
+ const trigrams = /* @__PURE__ */ new Map();
3001
+ for (let i = 0; i < seq.length - 2; i += 1) {
3002
+ const tg = `${seq[i]}|${seq[i + 1]}|${seq[i + 2]}`;
3003
+ trigrams.set(tg, (trigrams.get(tg) ?? 0) + 1);
3004
+ }
3005
+ const total = seq.length - 2;
3006
+ let entropy = 0;
3007
+ for (const c of trigrams.values()) {
3008
+ const p = c / total;
3009
+ entropy -= p * Math.log2(p);
3010
+ }
3011
+ const distinct = trigrams.size;
3012
+ const normalized = distinct > 1 ? entropy / Math.log2(distinct) : 1;
3013
+ if (normalized < 0.82 && total >= 50) {
3014
+ push("fnword-trigram-entropy", `Function-word trigram entropy ${normalized.toFixed(2)} (low)`, null, null);
3015
+ }
3016
+ if (distinct === 1 && total >= 50) {
3017
+ push("fnword-trigram-entropy", "Single function-word trigram repeated across document", null, null);
3018
+ }
3019
+ }
3020
+ }
3021
+ if (paragraphs.length >= 4) {
3022
+ const cvs = paragraphs.map((p) => {
3023
+ const sents = getSentences(p.text);
3024
+ if (sents.length < 3) return null;
3025
+ const lens = sents.map(countWords3);
3026
+ const mean = lens.reduce((a, b) => a + b, 0) / lens.length;
3027
+ if (mean === 0) return null;
3028
+ const v = lens.reduce((s, l) => s + (l - mean) ** 2, 0) / lens.length;
3029
+ return Math.sqrt(v) / mean;
3030
+ }).filter((c) => c !== null);
3031
+ if (cvs.length >= 4) {
3032
+ const cvMean = cvs.reduce((a, b) => a + b, 0) / cvs.length;
3033
+ const cvStd = Math.sqrt(cvs.reduce((s, c) => s + (c - cvMean) ** 2, 0) / cvs.length);
3034
+ if (cvStd < 0.08 && cvMean < 0.45) {
3035
+ push("cross-para-burstiness", `Sentence rhythm uniform across paragraphs (sigmaCV=${cvStd.toFixed(2)})`, null, null);
3036
+ }
3037
+ }
3038
+ }
3039
+ {
3040
+ const claimed = [];
3041
+ const overlaps = (a, b) => claimed.some(([s, e]) => a < e && b > s);
3042
+ let distinctPhrasesHit = 0;
3043
+ for (const phrase of TIER3_PHRASES) {
3044
+ const phraseSpans = [];
3045
+ for (const m of execAll2(phrase, text)) {
3046
+ const a = m.index;
3047
+ const b = a + m[0].length;
3048
+ if (!overlaps(a, b)) phraseSpans.push([a, b, m[0]]);
3049
+ }
3050
+ if (phraseSpans.length === 0) continue;
3051
+ for (const [a, b] of phraseSpans) claimed.push([a, b]);
3052
+ distinctPhrasesHit += 1;
3053
+ if (phraseSpans.length >= 2) {
3054
+ const [a, b, matched] = phraseSpans[0];
3055
+ push(
3056
+ "tier3-phrase",
3057
+ `"${matched.toLowerCase()}" x${phraseSpans.length}`,
3058
+ a,
3059
+ b,
3060
+ `Boilerplate phrase repeated ${phraseSpans.length} times; replace at least one with specifics.`,
3061
+ phraseSpans.length
3062
+ );
3063
+ }
3064
+ }
3065
+ if (distinctPhrasesHit >= 3) {
3066
+ const firstClaim = claimed.slice().sort((x, y) => x[0] - y[0])[0];
3067
+ push(
3068
+ "tier3-phrase-cluster",
3069
+ `${distinctPhrasesHit} distinct boilerplate phrases`,
3070
+ firstClaim ? firstClaim[0] : null,
3071
+ firstClaim ? firstClaim[1] : null,
3072
+ void 0,
3073
+ distinctPhrasesHit
3074
+ );
3075
+ }
3076
+ }
3077
+ {
3078
+ const tagMatches = [...maskCode(text).matchAll(/(?:^|\W)#(\w[\w-]*)/g)].filter((m) => isSocialTag(m[1]));
3079
+ if (tagMatches.length >= 6) {
3080
+ const first = tagMatches[0];
3081
+ const hashAt2 = first.index + first[0].indexOf("#");
3082
+ push("hashtag-stuff", `${tagMatches.length} hashtags`, hashAt2, hashAt2 + 1 + first[1].length, void 0, tagMatches.length);
3083
+ }
3084
+ }
3085
+ {
3086
+ const lines = text.split(/\r?\n/);
3087
+ const bulletRe = /^\s*(?:\*|-|•|\+)\s+(.+)$/;
3088
+ const verbRe = /\b(?:is|are|was|were|has|have|had|will|would|should|must|do|does|did|can|could|may|might|am|been|being)\b/i;
3089
+ const fenceRe = /^\s*(?:```|~~~)/;
3090
+ let run = [];
3091
+ let runStart = -1;
3092
+ let blankStreak = 0;
3093
+ let inFence = false;
3094
+ let offset = 0;
3095
+ const flushRun = () => {
3096
+ if (run.length >= 5) {
3097
+ const bareNP = run.filter((it) => {
3098
+ const wc = (it.match(/\S+/g) ?? []).length;
3099
+ return wc > 0 && wc <= 6 && !verbRe.test(it);
3100
+ });
3101
+ if (bareNP.length >= 5 && bareNP.length / run.length >= 0.75) {
3102
+ push(
3103
+ "bullet-np-list",
3104
+ `${run.length}-item bullet list of bare noun phrases`,
3105
+ runStart,
3106
+ Math.min(text.length, runStart + 1),
3107
+ void 0,
3108
+ run.length
3109
+ );
3110
+ }
3111
+ }
3112
+ run = [];
3113
+ runStart = -1;
3114
+ blankStreak = 0;
3115
+ };
3116
+ for (const line of lines) {
3117
+ if (fenceRe.test(line)) {
3118
+ flushRun();
3119
+ inFence = !inFence;
3120
+ } else if (!inFence) {
3121
+ const m = line.match(bulletRe);
3122
+ if (m) {
3123
+ if (run.length === 0) runStart = offset;
3124
+ run.push(m[1].trim());
3125
+ blankStreak = 0;
3126
+ } else if (line.trim() === "") {
3127
+ blankStreak += 1;
3128
+ if (blankStreak >= 2) flushRun();
3129
+ } else {
3130
+ flushRun();
3131
+ }
3132
+ }
3133
+ offset += line.length + 1;
3134
+ }
3135
+ flushRun();
3136
+ }
3137
+ {
3138
+ const confMatches = [];
3139
+ for (const pattern of CONFIDENCE_CALIBRATION) {
3140
+ for (const m of execAll2(pattern, text)) confMatches.push({ text: m[0], index: m.index });
3141
+ }
3142
+ if (confMatches.length >= 3) {
3143
+ for (const m of confMatches) push("confidence-calibration", m.text, m.index, m.index + m.text.length);
3144
+ }
3145
+ }
3146
+ {
3147
+ const rawEmDashCount = (text.match(/—|(?<=\s)--(?=\s|$)|(?<=^|\s)--(?=\s)/gm) ?? []).length;
3148
+ const spacedHyphenCount = (text.match(/(?<=\S) (?:-|–) (?=\S)/g) ?? []).length;
3149
+ const separatorDashCount = (text.match(SEPARATOR_DASH_RE) ?? []).length + (text.match(VERSION_HEADING_DASH_RE) ?? []).length;
3150
+ const dashCount = rawEmDashCount + spacedHyphenCount - separatorDashCount;
3151
+ const rate = dashCount / (wordCount / 1e3);
3152
+ if (dashCount >= 3 && rate > 6) {
3153
+ issues.push({
3154
+ category: "em-dash-density",
3155
+ key: `${dashCount} dash separators in ${wordCount} words`,
3156
+ start: null,
3157
+ end: null,
3158
+ count: dashCount,
3159
+ extra: { rate_per_1000_words: Math.round(rate * 10) / 10, em_dash_count: rawEmDashCount, spaced_hyphen_count: spacedHyphenCount }
3160
+ });
3161
+ }
3162
+ }
3163
+ if (sentences.length >= 5) {
3164
+ const lengths = sentences.map(countWords3);
3165
+ const avg = lengths.reduce((a, b) => a + b, 0) / lengths.length;
3166
+ const stdDev = Math.sqrt(lengths.reduce((s, l) => s + (l - avg) ** 2, 0) / lengths.length);
3167
+ const cv = avg > 0 ? stdDev / avg : 0;
3168
+ if (cv < 0.25 && avg > 10) {
3169
+ issues.push({
3170
+ category: "sentence-flatline",
3171
+ key: `Sentence lengths cluster around ${Math.round(avg)} words (CV=${cv.toFixed(2)})`,
3172
+ start: null,
3173
+ end: null,
3174
+ count: sentences.length,
3175
+ extra: { sentence_count: sentences.length, mean_words: Math.round(avg * 10) / 10, std_dev: Math.round(stdDev * 100) / 100, cv: Math.round(cv * 100) / 100 }
3176
+ });
3177
+ }
3178
+ }
3179
+ {
3180
+ const headingRe = /^(?:#{1,6}[ \t]+\S.*|<h[1-6][^>]*>.*)$/gim;
3181
+ const headings = execAll2(headingRe, text);
3182
+ let sectionLengths = [];
3183
+ if (headings.length >= 2) {
3184
+ for (let i = 0; i < headings.length; i += 1) {
3185
+ const bodyStart = headings[i].index + headings[i][0].length;
3186
+ const bodyEnd = i + 1 < headings.length ? headings[i + 1].index : text.length;
3187
+ const words = countWords3(text.slice(bodyStart, bodyEnd));
3188
+ if (words >= 20) {
3189
+ sectionLengths.push(words);
3190
+ }
3191
+ }
3192
+ } else {
3193
+ for (const p of paragraphs) {
3194
+ const words = countWords3(p.text);
3195
+ if (words >= 20) {
3196
+ sectionLengths.push(words);
3197
+ }
3198
+ }
3199
+ }
3200
+ if (sectionLengths.length >= 4) {
3201
+ const mean = sectionLengths.reduce((a, b) => a + b, 0) / sectionLengths.length;
3202
+ const std = Math.sqrt(sectionLengths.reduce((s, l) => s + (l - mean) ** 2, 0) / sectionLengths.length);
3203
+ const cv = mean > 0 ? std / mean : 0;
3204
+ if (cv < 0.15) {
3205
+ issues.push({
3206
+ category: "uniform-sections",
3207
+ key: `${sectionLengths.length} sections of near-identical length (CV=${cv.toFixed(2)})`,
3208
+ start: null,
3209
+ end: null,
3210
+ count: sectionLengths.length,
3211
+ ...sectionLengths.length >= 8 && cv < 0.1 ? { severityOverride: "high" } : {},
3212
+ extra: { section_count: sectionLengths.length, mean_words: Math.round(mean * 10) / 10, cv: Math.round(cv * 100) / 100 }
3213
+ });
3214
+ }
3215
+ }
3216
+ }
3217
+ {
3218
+ const lines = text.split(/\r?\n/);
3219
+ const itemRe = /^\s*(?:[-*+•]|\d+[.)])\s+(\S.*)$/;
3220
+ let offset = 0;
3221
+ let run = [];
3222
+ let runStart = null;
3223
+ let runEnd = null;
3224
+ const flush = () => {
3225
+ if (run.length >= 4) {
3226
+ const mean = run.reduce((a, b) => a + b, 0) / run.length;
3227
+ const std = Math.sqrt(run.reduce((s, l) => s + (l - mean) ** 2, 0) / run.length);
3228
+ const cv = mean > 0 ? std / mean : 0;
3229
+ if (mean >= 3 && cv < 0.15) {
3230
+ issues.push({
3231
+ category: "uniform-list-items",
3232
+ key: `${run.length} list items of near-identical length (CV=${cv.toFixed(2)})`,
3233
+ ...runStart !== null && runEnd !== null && runEnd > runStart ? (([s, e]) => ({ start: s, end: e }))(span(runStart, runEnd)) : { start: null, end: null },
3234
+ count: run.length,
3235
+ extra: { item_count: run.length, mean_words: Math.round(mean * 10) / 10, cv: Math.round(cv * 100) / 100 }
3236
+ });
3237
+ }
3238
+ }
3239
+ run = [];
3240
+ runStart = null;
3241
+ runEnd = null;
3242
+ };
3243
+ for (const line of lines) {
3244
+ const m = line.match(itemRe);
3245
+ if (m) {
3246
+ if (run.length === 0) runStart = offset + (line.length - line.trimStart().length);
3247
+ runEnd = offset + line.replace(/\s+$/, "").length;
3248
+ run.push(countWords3(m[1]));
3249
+ } else if (line.trim() !== "") {
3250
+ flush();
3251
+ }
3252
+ offset += line.length + 1;
3253
+ }
3254
+ flush();
3255
+ }
3256
+ if (tokens2.length >= 200) {
3257
+ const unique = new Set(tokens2.map((t) => t.token)).size;
3258
+ const ttr = unique / tokens2.length;
3259
+ if (ttr < 0.4) {
3260
+ push("low-ttr", `Vocabulary diversity ${(ttr * 100).toFixed(1)}% (${unique} unique / ${tokens2.length} tokens)`, null, null);
3261
+ }
3262
+ }
3263
+ if (paragraphs.length >= 4) {
3264
+ const paraLengths = paragraphs.map((p) => getSentences(p.text).length);
3265
+ const avg = paraLengths.reduce((a, b) => a + b, 0) / paraLengths.length;
3266
+ if (paraLengths.every((l) => Math.abs(l - avg) <= 1) && avg >= 3) {
3267
+ push("uniformity", `All paragraphs are ~${Math.round(avg)} sentences`, null, null);
3268
+ }
3269
+ }
3270
+ {
3271
+ const boldRe = /\*\*[^*]+\*\*/g;
3272
+ const bolds = execAll2(boldRe, text);
3273
+ if (bolds.length > 3) {
3274
+ const first = bolds[0];
3275
+ push("formatting", `${bolds.length} bold phrases`, first.index, first.index + first[0].length, void 0, bolds.length);
3276
+ }
3277
+ }
3278
+ collectV3Issues({
3279
+ text,
3280
+ wordCount,
3281
+ paragraphs,
3282
+ sentences,
3283
+ push,
3284
+ pushEx,
3285
+ pushPatterns: (patterns, category) => {
3286
+ pushPatterns(patterns, category);
3287
+ }
3288
+ });
3289
+ collectV4Issues({
3290
+ text,
3291
+ wordCount,
3292
+ paragraphs,
3293
+ sentences,
3294
+ push,
3295
+ pushEx,
3296
+ pushPatterns: (patterns, category) => {
3297
+ pushPatterns(patterns, category);
3298
+ }
3299
+ });
3300
+ const seen = /* @__PURE__ */ new Set();
3301
+ const deduped = issues.filter((issue) => {
3302
+ const k = `${issue.category}:${issue.key.toLowerCase()}`;
3303
+ if (seen.has(k)) return false;
3304
+ seen.add(k);
3305
+ return true;
3306
+ });
3307
+ const tier1Distinct = new Set(deduped.filter((i) => i.category === "tier1").map((i) => i.key.toLowerCase())).size;
3308
+ return { issues: deduped, wordCount, tier2Clusters, tier1Distinct, normFlags: norm.flags };
3309
+ }
3310
+ function docAnchor(text) {
3311
+ const cp = text.codePointAt(0);
3312
+ return [0, cp !== void 0 && cp > 65535 ? 2 : 1];
3313
+ }
3314
+ function toFinding(original, issue) {
3315
+ const meta = MERGED_META[issue.category] ?? { severity: "low", message: "This passage set off one of our writing checks.", suggestion: "Have a look at the flagged text." };
3316
+ let start = issue.start;
3317
+ let end = issue.end;
3318
+ let documentLevel = false;
3319
+ if (start === null || end === null || end <= start) {
3320
+ [start, end] = docAnchor(original);
3321
+ documentLevel = true;
3322
+ }
3323
+ [start, end] = alignUtf16Range(original, start, end);
3324
+ const matched = original.slice(start, end);
3325
+ const weight = MERGED_WEIGHTS[issue.category] ?? 2;
3326
+ const eraInfo = RULE_ERA[issue.category] ?? { era: "evergreen" };
3327
+ return {
3328
+ rule_id: "signals." + issue.category.replace(/-/g, "_"),
3329
+ rule_version: EN_SIGNALS_PATTERN_VERSION,
3330
+ severity: issue.severityOverride ?? meta.severity,
3331
+ message: meta.message,
3332
+ suggestion: issue.suggestion !== void 0 && issue.suggestion !== "" ? `Consider: ${issue.suggestion}.` : meta.suggestion,
3333
+ span: rangeFromUtf16(original, start, end),
3334
+ matched_text_hash: prefixedSha256(matched),
3335
+ evidence: {
3336
+ matched,
3337
+ count: issue.count ?? 1,
3338
+ weight,
3339
+ category: issue.category,
3340
+ detail: issue.key,
3341
+ era: eraInfo.era,
3342
+ ...eraInfo.attribution !== void 0 ? { attribution: eraInfo.attribution } : {},
3343
+ ...CORROBORATION_CATEGORIES.has(issue.category) || V4_RHYTHM_CATEGORIES.has(issue.category) ? { corroboration: true } : {},
3344
+ ...issue.extra ?? {},
3345
+ ...documentLevel ? { document_level: true } : {}
3346
+ }
3347
+ };
3348
+ }
3349
+ function inspectSignalsV2(text) {
3350
+ if (!text || text.trim().length === 0) return [];
3351
+ const analysis = analyse(text);
3352
+ return analysis.issues.map((issue) => toFinding(text, issue)).sort((a, b) => a.span.start_utf16 - b.span.start_utf16 || a.rule_id.localeCompare(b.rule_id));
3353
+ }
3354
+ function classify(score, issues, normFlags, wordCount, denseAIVocab) {
3355
+ const has = (category) => issues.some((i) => i.category === category);
3356
+ const hasCutoff = has("cutoff-disclaimer");
3357
+ const hasNormFlag = normFlags.zeroWidth >= 2 || normFlags.homoglyph >= 2;
3358
+ const strongCorrob = (hasCutoff ? 1 : 0) + (hasNormFlag ? 1 : 0) + (has("reasoning-artifact") && has("chatbot") ? 1 : 0) + (denseAIVocab ? 1 : 0);
3359
+ const stylometricHits = ["punct-distribution", "cross-para-burstiness", "fnword-trigram-entropy"].filter(has).length;
3360
+ const weakCorrob = (stylometricHits >= 2 ? 1 : 0) + (has("smart-punct-signature") ? 1 : 0);
3361
+ const totalCorrob = strongCorrob + weakCorrob;
3362
+ let band;
3363
+ if (score < 15 && strongCorrob === 0) band = "human_like";
3364
+ else if (strongCorrob >= 1 || score >= 70) band = "ai_like";
3365
+ else if (score >= 40 && totalCorrob >= 1) band = "ai_like";
3366
+ else band = "mixed_signals";
3367
+ const aiSoft = Math.min(0.97, score / 100 + totalCorrob * 0.06 + strongCorrob * 0.08);
3368
+ let p;
3369
+ if (band === "human_like") p = { human: Math.max(0.6, 1 - aiSoft), mixed: Math.min(0.35, aiSoft * 0.8), ai: Math.min(0.1, aiSoft * 0.3) };
3370
+ else if (band === "ai_like") p = { human: Math.max(0.02, 1 - aiSoft - 0.05), mixed: 0.1, ai: aiSoft };
3371
+ else p = { human: Math.max(0.15, 0.6 - aiSoft * 0.5), mixed: 0.5, ai: aiSoft * 0.7 };
3372
+ const rawSum = p.human + p.mixed + p.ai;
3373
+ const human = +(p.human / rawSum).toFixed(3);
3374
+ const mixed = +(p.mixed / rawSum).toFixed(3);
3375
+ const ai = Math.max(0, +(1 - human - mixed).toFixed(3));
3376
+ let classification;
3377
+ if (human >= mixed && human >= ai) classification = "human_like";
3378
+ else if (mixed >= ai) classification = "mixed_signals";
3379
+ else classification = "ai_like";
3380
+ let confidence;
3381
+ if (strongCorrob >= 2 || hasCutoff || score < 8 && wordCount >= 100) confidence = "high";
3382
+ else if (strongCorrob >= 1 || score >= 45 && weakCorrob >= 1 || score < 20) confidence = "medium";
3383
+ else confidence = "low";
3384
+ return { classification, probabilities: { human_like: human, mixed_signals: mixed, ai_like: ai }, confidence };
3385
+ }
3386
+ var CLASS_RANK = { human_like: 0, mixed_signals: 1, ai_like: 2 };
3387
+ function applyEscalationPolicy(base, confidence, score, findingCount, categories) {
3388
+ const cats = new Set(categories);
3389
+ const coreArtefacts = categories.filter((c) => ARTEFACT_CORE_CATEGORIES.has(c));
3390
+ const supportArtefacts = categories.filter((c) => ARTEFACT_SUPPORT_CATEGORIES.has(c));
3391
+ const artefactHit = coreArtefacts.length >= 1 || supportArtefacts.length >= 2;
3392
+ const artefactCats = artefactHit ? [...coreArtefacts, ...supportArtefacts] : [];
3393
+ const formattingCats = categories.filter((c) => FORMATTING_CLUSTER_CATEGORIES.has(c));
3394
+ const candidates = [];
3395
+ if (cats.has("ai-citation-markup") && cats.has("ai-citation-token")) {
3396
+ candidates.push({
3397
+ applied: "citation_co_occurrence",
3398
+ classification: "ai_like",
3399
+ reason: "Internal citation markup and a leaked citation token both appear \u2014 the residue of an unstripped chatbot export, with no plausible human origin. This remains stylistic-artefact evidence, not proof of authorship."
3400
+ });
3401
+ }
3402
+ if (findingCount >= 6 && cats.size >= 4) {
3403
+ const bumped = base === "human_like" ? "mixed_signals" : "ai_like";
3404
+ candidates.push({
3405
+ applied: "finding_breadth",
3406
+ classification: bumped,
3407
+ reason: `Documented writing signals are unusually broad (${findingCount} findings across ${cats.size} categories), so more editorial suggestions are surfaced. Breadth is an observation about the writing, not evidence of authorship: measured on a representative 4,144-sample human corpus, genuine human writing reaches up to 9 categories and 135 of those documents trip this same gate.`
3408
+ });
3409
+ }
3410
+ const artefactScore = artefactHit && score >= 10;
3411
+ if (artefactScore) {
3412
+ candidates.push({
3413
+ applied: "artefact_score",
3414
+ classification: "mixed_signals",
3415
+ reason: `Machine-artefact evidence (${artefactCats.join(", ")}) combines with a score of ${score}. Measured on a representative 4,144-sample human corpus, human writing reaches a score of 11 and 2 of those documents clear this gate, so this is a strong editorial signal rather than a finding about authorship.`
3416
+ });
3417
+ }
3418
+ if (artefactHit) {
3419
+ candidates.push({
3420
+ applied: "artefact_floor",
3421
+ classification: "mixed_signals",
3422
+ reason: `Machine-artefact evidence (${artefactCats.join(", ")}) was found. Artefact-class findings are rare in human writing \u2014 4 of 4,144 documents in a representative human corpus \u2014 but they are not absent, so this raises the editorial reading and is not evidence of authorship.`
3423
+ });
3424
+ }
3425
+ if (cats.has("markdown-furniture")) {
3426
+ candidates.push({
3427
+ applied: "furniture_gate",
3428
+ classification: "mixed_signals",
3429
+ reason: "Chat-export markdown furniture (bold runs, heading lines, or dense bullets) shapes this text \u2014 the combined gate fired on 0 of 169 held-out human documents. Absence of furniture (e.g. after a format-stripping paste) never counts the other way."
3430
+ });
3431
+ }
3432
+ if (cats.has("formatting")) {
3433
+ candidates.push({
3434
+ applied: "formatting_floor",
3435
+ classification: "mixed_signals",
3436
+ reason: "Heavy bold styling (the formatting rule) fired \u2014 measured on 0 of 169 held-out human documents and 9-95% of AI chat text per provider slice; the classification is floored at mixed_signals."
3437
+ });
3438
+ }
3439
+ if (new Set(formattingCats).size >= 3) {
3440
+ candidates.push({
3441
+ applied: "formatting_cluster",
3442
+ classification: "mixed_signals",
3443
+ reason: `Chat-export formatting furniture clusters (${[...new Set(formattingCats)].join(", ")}). This compound signal fired on 0 of 4,144 documents in a representative human corpus, but it detects how a draft was pasted rather than who wrote it: an editor that strips formatting removes it entirely.`
3444
+ });
3445
+ }
3446
+ let finalClass = base;
3447
+ let applied = null;
3448
+ let reason = "No escalation applied; the classification is the argmax of the published probabilities.";
3449
+ for (const c of candidates) {
3450
+ if (CLASS_RANK[c.classification] > CLASS_RANK[finalClass]) {
3451
+ finalClass = c.classification;
3452
+ applied = c.applied;
3453
+ reason = c.reason;
3454
+ }
3455
+ }
3456
+ const finalConfidence = artefactScore && confidence === "low" ? "medium" : confidence;
3457
+ return { classification: finalClass, confidence: finalConfidence, escalation: { applied, reason } };
3458
+ }
3459
+ function unscored(status, wordCount) {
3460
+ return {
3461
+ score: 0,
3462
+ classification: "human_like",
3463
+ probabilities: { human_like: 0.334, mixed_signals: 0.333, ai_like: 0.333 },
3464
+ confidence: "low",
3465
+ categoriesHit: [],
3466
+ findingCount: 0,
3467
+ wordCount,
3468
+ version: EN_SIGNALS_PATTERN_VERSION,
3469
+ status,
3470
+ escalation: { applied: null, reason: "Text was outside the scoring window; the escalation policy was not evaluated." },
3471
+ description: DESCRIPTION + " This text was outside the scoring window (" + status.replace("_", " ") + "), so no stylistic assessment was made."
3472
+ };
3473
+ }
3474
+ function computeEditorialSignals(text) {
3475
+ if (!text || text.trim().length === 0) return unscored("empty", 0);
3476
+ const analysis = analyse(text);
3477
+ const { wordCount } = analysis;
3478
+ if (wordCount < 10) return unscored("too_short", wordCount);
3479
+ if (wordCount > MAX_SCORED_WORDS) return unscored("too_long", wordCount);
3480
+ let styloRaw = 0;
3481
+ let otherRaw = 0;
3482
+ for (const issue of analysis.issues) {
3483
+ const w = MERGED_WEIGHTS[issue.category] ?? 2;
3484
+ if (STYLOMETRIC_CATEGORIES.has(issue.category) || V4_RHYTHM_CATEGORIES.has(issue.category)) styloRaw += w;
3485
+ else otherRaw += w;
3486
+ }
3487
+ const rawScore = otherRaw + Math.min(styloRaw, Math.max(otherRaw, 12));
3488
+ const lengthFactor = Math.max(1, Math.log2(wordCount / 50));
3489
+ const score = Math.min(100, Math.round(rawScore / lengthFactor));
3490
+ const denseAIVocab = wordCount >= 150 && analysis.tier1Distinct >= 5 && analysis.tier2Clusters >= 2 && analysis.issues.some((i) => i.category === "transition");
3491
+ const verdict = classify(score, analysis.issues, analysis.normFlags, wordCount, denseAIVocab);
3492
+ const categoriesHit = [...new Set(analysis.issues.map((i) => i.category))].sort();
3493
+ const FURNITURE_CATS = /* @__PURE__ */ new Set(["markdown-bold", "markdown-heading", "markdown-furniture"]);
3494
+ const v4IssueCount = analysis.issues.filter((i) => V4_RHYTHM_CATEGORIES.has(i.category)).length;
3495
+ const furnitureIssueCount = analysis.issues.filter((i) => FURNITURE_CATS.has(i.category)).length;
3496
+ const breadthFindingCount = analysis.issues.length - Math.max(0, v4IssueCount - 1) - Math.max(0, furnitureIssueCount - 1);
3497
+ const breadthCategories = categoriesHit.filter((c) => !V4_RHYTHM_CATEGORIES.has(c) && !FURNITURE_CATS.has(c));
3498
+ if (v4IssueCount > 0) breadthCategories.push("stylometric-rhythm-combined");
3499
+ if (furnitureIssueCount > 0) breadthCategories.push("markdown-furniture-combined");
3500
+ const escalated = applyEscalationPolicy(
3501
+ verdict.classification,
3502
+ verdict.confidence,
3503
+ score,
3504
+ breadthFindingCount,
3505
+ breadthCategories
3506
+ );
3507
+ return {
3508
+ score,
3509
+ classification: escalated.classification,
3510
+ probabilities: verdict.probabilities,
3511
+ confidence: escalated.confidence,
3512
+ categoriesHit,
3513
+ findingCount: analysis.issues.length,
3514
+ wordCount,
3515
+ version: EN_SIGNALS_PATTERN_VERSION,
3516
+ status: "scored",
3517
+ escalation: escalated.escalation,
3518
+ description: DESCRIPTION
3519
+ };
3520
+ }
3521
+ var EN_GB_PATTERN_VERSION = "en-gb:2026.08.1";
3522
+ var PHRASES = ["in today's rapidly evolving landscape", "game-changer", "in conclusion", "it is important to note", "delve into"];
3523
+ var finding = (text, start, rule, severity, message, suggestion, evidence) => {
3524
+ const raw = evidence.matched === void 0 ? text.slice(start, start + 1) : String(evidence.matched);
3525
+ const [alignedStart, alignedEnd] = alignUtf16Range(text, start, start + raw.length);
3526
+ const matched = evidence.matched === void 0 ? void 0 : text.slice(alignedStart, alignedEnd);
3527
+ return { rule_id: rule, rule_version: EN_GB_PATTERN_VERSION, severity, message, suggestion, span: rangeFromUtf16(text, alignedStart, alignedEnd), matched_text_hash: prefixedSha256(matched ?? ""), evidence: matched === void 0 ? evidence : { ...evidence, matched } };
3528
+ };
3529
+ function inspectPatterns(text) {
3530
+ const v1 = inspectPatternsV1(text);
3531
+ const v1Spans = new Set(v1.map((f) => `${f.span.start_utf16}:${f.span.end_utf16}`));
3532
+ const v2 = inspectSignalsV2(text).filter((f) => !v1Spans.has(`${f.span.start_utf16}:${f.span.end_utf16}`));
3533
+ return [...v1, ...v2].sort((a, b) => a.span.start_utf16 - b.span.start_utf16 || a.rule_id.localeCompare(b.rule_id));
3534
+ }
3535
+ function inspectPatternsV1(text) {
3536
+ const findings = [];
3537
+ const lower = text.toLocaleLowerCase("en-GB");
3538
+ for (const phrase of PHRASES) {
3539
+ let at = 0, count = 0;
3540
+ while ((at = lower.indexOf(phrase, at)) >= 0) {
3541
+ count++;
3542
+ at += phrase.length;
3543
+ }
3544
+ if (count >= 1) {
3545
+ const start = lower.indexOf(phrase);
3546
+ findings.push(finding(text, start, "style.overused_phrase", count > 1 ? "medium" : "low", "A stock phrase may make the passage feel generic.", "Review whether a more specific statement would be clearer.", { matched: text.slice(start, start + phrase.length), count, threshold: 1 }));
3547
+ }
3548
+ }
3549
+ const sentences = text.split(/(?<=[.!?])\s+/).filter(Boolean);
3550
+ const openings = /* @__PURE__ */ new Map();
3551
+ let cursor = 0;
3552
+ for (const sentence of sentences) {
3553
+ const start = text.indexOf(sentence, cursor);
3554
+ cursor = start + sentence.length;
3555
+ const opening = sentence.trim().split(/\s+/).slice(0, 3).join(" ").toLocaleLowerCase("en-GB");
3556
+ if (opening.split(" ").length >= 2) (openings.get(opening) ?? (openings.set(opening, []), openings.get(opening))).push(start);
3557
+ }
3558
+ for (const [opening, starts] of openings) if (starts.length >= 3) findings.push(finding(text, starts[0], "style.repeated_opening", "medium", "Several sentences begin the same way.", "Vary only the openings that genuinely benefit from it.", { matched: text.slice(starts[0], starts[0] + opening.length), count: starts.length, threshold: 3 }));
3559
+ const transitions = (lower.match(/\b(?:moreover|furthermore|additionally|consequently|therefore|however)\b/g) ?? []).length;
3560
+ const wordCount = text.trim() ? text.trim().split(/\s+/).length : 0;
3561
+ if (wordCount >= 40 && transitions / wordCount > 0.04) {
3562
+ const m = /\b(?:moreover|furthermore|additionally|consequently|therefore|however)\b/i.exec(text);
3563
+ findings.push(finding(text, m.index, "style.transition_density", "low", "Transition words are unusually dense.", "Remove transitions that do not clarify the relationship between sentences.", { matched: m[0], count: transitions, word_count: wordCount, threshold_ratio: 0.04 }));
3564
+ }
3565
+ return findings.sort((a, b) => a.span.start_utf16 - b.span.start_utf16 || a.rule_id.localeCompare(b.rule_id));
3566
+ }
3567
+ var packs = /* @__PURE__ */ new Map();
3568
+ var runRegisteredPacks = (text) => [...packs.values()].flatMap((pack) => pack.inspect(text));
3569
+ var COMBINED_VERDICT_VERSION = "combined:2026.08.8";
3570
+ var DESCRIPTION2 = "Three independent readings, published side by side and never merged: the AI probability, which only a trained model may set; text-integrity and provenance findings, which describe what was done to the text; and editorial suggestions about phrasing and structure. Integrity findings describe manipulation, not authorship.";
3571
+ var AXES_LIMIT = "These three readings are independent and are never combined into a single verdict. Hidden characters, homoglyph substitutions and watermark marks show that something was done to the text; they are not evidence of AI authorship. Only the trained model gives an AI reading.";
3572
+ var AUTHORSHIP_LIMIT = "Authorship cannot be proved from these checks; character evidence shows how text was produced or pasted, not by whom.";
3573
+ var ABSENCE_LIMIT = "Absence of carrier characters is not evidence of human authorship. Most published prose carries none, and any copy-paste, CMS save or format strip removes them.";
3574
+ var PROTECTED_LIMIT = "Protected spans are excluded from this verdict by design: they are facts to preserve, not evidence about origin.";
3575
+ var EDITORIAL_LIMIT = "Writing suggestions are editorial feedback on phrasing and structure. Measured on 5,558 fresh long-form documents the named rules flagged 24.8% of human writing, so they say nothing about who or what wrote a draft and are never counted toward the AI reading.";
3576
+ var NO_MODEL_LIMIT = "No trained model ran on this text, so no AI probability is available. That is reported as not assessed, and not assessed does not mean human.";
3577
+ var INTEGRITY_RANK = { clean: 0, attention: 1, manipulated: 2 };
3578
+ function isDeliberateCarrier(cp) {
3579
+ return cp === 8203 || // ZERO WIDTH SPACE
3580
+ cp === 8204 || // ZWNJ that survived the cursive/Indic joining exemption
3581
+ cp === 8205 || // ZWJ that survived the emoji and joining-script exemptions
3582
+ cp === 8288 || // WORD JOINER
3583
+ cp >= 8298 && cp <= 8303 || // deprecated format characters
3584
+ cp >= 65024 && cp <= 65039 || // variation selectors past the emoji/CJK exemption
3585
+ cp >= 917760 && cp <= 917999 || // supplementary variation selectors past the CJK exemption
3586
+ cp === 917505 || // LANGUAGE TAG
3587
+ cp >= 917536 && cp <= 917631;
3588
+ }
3589
+ function isVariationSelector(cp) {
3590
+ return cp >= 65024 && cp <= 65039 || cp >= 917760 && cp <= 917999;
3591
+ }
3592
+ function isSupportingCarrier(cp) {
3593
+ return cp === 173 || // SOFT HYPHEN - justified typography and word-processor exports
3594
+ cp === 847 || // COMBINING GRAPHEME JOINER - rare collation and diacritic ordering
3595
+ cp === 65279 || // BYTE ORDER MARK - routine file-encoding artefact
3596
+ cp >= 8289 && cp <= 8292 || // invisible operators - typeset mathematics
3597
+ cp >= 65529 && cp <= 65531 || // interlinear annotation - Japanese ruby pipelines
3598
+ cp >= 6155 && cp <= 6159 || // Mongolian free variation selectors and vowel separator
3599
+ isPrivateUse(cp);
3600
+ }
3601
+ function isPrivateUse(cp) {
3602
+ return cp >= 57344 && cp <= 63743 || cp >= 983040 && cp <= 1048573 || cp >= 1048576 && cp <= 1114109;
3603
+ }
3604
+ function isInvisibleCarrier(cp) {
3605
+ return isDeliberateCarrier(cp) || isSupportingCarrier(cp) && !isPrivateUse(cp);
3606
+ }
3607
+ var DELIBERATE_RATIONALE = "No ordinary authoring or publishing tool emits this character into prose, and every documented legitimate context for it was checked and did not apply.";
3608
+ var SUPPORTING_RATIONALE = "Unusual in prose but with a documented innocent origin, so it corroborates other evidence and never raises a status on its own.";
3609
+ var EXCLUDED_RATIONALE = "This character has a documented legitimate use in typography, multilingual text or encoding, so it is reported but excluded from the integrity status.";
3610
+ var PUA_RATIONALE = "A private-use character carries meaning only under a private agreement. A single one is common in icon fonts and vendor logos, so one alone only corroborates.";
3611
+ var LATIN_LETTER = /\p{Script=Latin}/u;
3612
+ function isInteriorHomoglyph(text, finding2) {
3613
+ const start = finding2.span.start_utf16;
3614
+ const end = finding2.span.end_utf16;
3615
+ if (start <= 0 || end >= text.length) return false;
3616
+ return LATIN_LETTER.test(text[start - 1]) && LATIN_LETTER.test(text[end]);
3617
+ }
3618
+ var BLACK_FLAG = 127988;
3619
+ function tagIsInFlagSequence(text, index) {
3620
+ let i = index;
3621
+ while (i >= 2) {
3622
+ const previous = text.codePointAt(i - 2);
3623
+ if (previous !== void 0 && previous >= 917536 && previous <= 917631) {
3624
+ i -= 2;
3625
+ continue;
3626
+ }
3627
+ break;
3628
+ }
3629
+ if (i < 2) return false;
3630
+ return text.codePointAt(i - 2) === BLACK_FLAG;
3631
+ }
3632
+ var SUGGESTION_LEVEL = {
3633
+ human_like: "none",
3634
+ mixed_signals: "some",
3635
+ ai_like: "many"
3636
+ };
3637
+ var SUGGESTION_REASON = {
3638
+ none: "The named writing rules found nothing worth suggesting a change to.",
3639
+ some: "The named writing rules produced some editorial suggestions about phrasing or structure. Genuine human copy triggers them routinely.",
3640
+ many: "The named writing rules produced a lot of editorial suggestions about phrasing and structure. That is a comment on the writing, not on who wrote it: measured on fresh long-form documents these rules flag roughly one human document in four."
3641
+ };
3642
+ function computeCombinedVerdict(input) {
3643
+ const findings = input.unicodeFindings ?? [];
3644
+ const text = input.text;
3645
+ const signals = input.signals;
3646
+ const watermarkOutcome = input.watermark?.outcome ?? "not_available";
3647
+ const deliberate = [];
3648
+ const supporting = [];
3649
+ const excluded = [];
3650
+ let interiorHomoglyphs = 0;
3651
+ const carrierPositions = [];
3652
+ for (const finding2 of findings) {
3653
+ const cp = codePointOf(finding2);
3654
+ const isHomoglyph = finding2.id.includes("_homoglyph_");
3655
+ if (isHomoglyph) {
3656
+ const interior = text !== void 0 && isInteriorHomoglyph(text, finding2);
3657
+ if (interior) {
3658
+ interiorHomoglyphs++;
3659
+ deliberate.push(item(finding2, "deliberate", "A Latin-lookalike character sits between two Latin letters, which is a substitution inside a Latin word rather than multilingual text."));
3660
+ } else {
3661
+ supporting.push(item(finding2, "supporting", "A Latin-lookalike character at a token boundary, which is the shape genuinely multilingual and scientific text produces."));
3662
+ }
3663
+ continue;
3664
+ }
3665
+ if (cp === null) {
3666
+ excluded.push(item(finding2, "excluded", EXCLUDED_RATIONALE));
3667
+ continue;
3668
+ }
3669
+ if (cp >= 917536 && cp <= 917631 && text !== void 0 && tagIsInFlagSequence(text, finding2.span.start_utf16)) {
3670
+ excluded.push(item(finding2, "excluded", "This tag character belongs to a subdivision flag emoji sequence, which is a legitimate tag run."));
3671
+ continue;
3672
+ }
3673
+ if (isVariationSelector(cp) && finding2.severity === "note") {
3674
+ excluded.push(item(finding2, "excluded", "This variation selector follows a base character that commonly takes registered glyph variants, which is its documented legitimate use."));
3675
+ continue;
3676
+ }
3677
+ if (isInvisibleCarrier(cp)) carrierPositions.push(finding2.span.start_utf16);
3678
+ if (isDeliberateCarrier(cp)) {
3679
+ deliberate.push(item(finding2, "deliberate", DELIBERATE_RATIONALE));
3680
+ continue;
3681
+ }
3682
+ if (isSupportingCarrier(cp)) {
3683
+ supporting.push(item(finding2, "supporting", isPrivateUse(cp) ? PUA_RATIONALE : SUPPORTING_RATIONALE));
3684
+ continue;
3685
+ }
3686
+ excluded.push(item(finding2, "excluded", EXCLUDED_RATIONALE));
3687
+ }
3688
+ const privateUseCount = supporting.filter((x) => {
3689
+ const cp = codePointFromLabel(x.code_point);
3690
+ return cp !== null && isPrivateUse(cp);
3691
+ }).length;
3692
+ const longestRun = longestAdjacentRun(carrierPositions, text);
3693
+ const deliberateCount = deliberate.length;
3694
+ const tagCharacters = deliberate.filter((x) => {
3695
+ const cp = codePointFromLabel(x.code_point);
3696
+ return cp !== null && cp >= 917505 && cp <= 917631;
3697
+ }).length;
3698
+ const inputs = [];
3699
+ if (input.model) inputs.push("model");
3700
+ if (signals) inputs.push("writing_signals");
3701
+ if (findings.some((f) => !f.id.includes("_homoglyph_"))) inputs.push("invisible_unicode");
3702
+ if (findings.some((f) => f.id.includes("_homoglyph_"))) inputs.push("homoglyphs");
3703
+ if (watermarkOutcome === "detected") inputs.push("watermark");
3704
+ const limitations = [AXES_LIMIT, AUTHORSHIP_LIMIT, ABSENCE_LIMIT, PROTECTED_LIMIT];
3705
+ const integrityFindings = [];
3706
+ if (watermarkOutcome === "detected") {
3707
+ integrityFindings.push({
3708
+ applied: "watermark_signal",
3709
+ status: "manipulated",
3710
+ reason: "A watermark detector reported a positive signal, so this text carries a generator's mark. That is provenance evidence about the text, and it is reported here rather than as an AI probability."
3711
+ });
3712
+ limitations.push("A watermark signal identifies the generator that marked the text, not the person who published it, and says nothing about later human editing. It is not, on its own, an AI reading.");
3713
+ }
3714
+ if (longestRun >= 3 || tagCharacters >= 2 || deliberateCount >= 8) {
3715
+ integrityFindings.push({
3716
+ applied: "carrier_payload",
3717
+ status: "manipulated",
3718
+ reason: describePayload(longestRun, tagCharacters, deliberateCount)
3719
+ });
3720
+ limitations.push("A carrier payload shows that something deliberately encoded data into this text. It does not identify what encoded it, it can be inserted by any tool in the chain including one the author never saw, and it is not evidence that a machine wrote the words.");
3721
+ }
3722
+ if (deliberateCount - interiorHomoglyphs >= 1) {
3723
+ integrityFindings.push({
3724
+ applied: "carrier_deliberate",
3725
+ status: "attention",
3726
+ reason: `This text contains an invisible character with no ordinary authoring explanation (${describeEvidence(deliberate.filter((x) => !x.rationale.startsWith("A Latin-lookalike")))}). Every documented legitimate context for it was checked and did not apply.`
3727
+ });
3728
+ limitations.push("Invisible carriers can be introduced by any tool that touched the text after it was written, including editors, CMS filters and paste handlers, so they place the text in a pipeline rather than with an author, and they say nothing about whether a person or a machine composed it.");
3729
+ }
3730
+ if (interiorHomoglyphs >= 1) {
3731
+ integrityFindings.push({
3732
+ applied: "homoglyph_substitution",
3733
+ status: "attention",
3734
+ reason: `${interiorHomoglyphs} Latin-lookalike character${interiorHomoglyphs === 1 ? " sits" : "s sit"} between Latin letters inside a word. Genuinely multilingual and scientific text places such characters at token boundaries instead.`
3735
+ });
3736
+ limitations.push("Homoglyph substitution indicates the text passed through a tool that rewrites characters. It is never corrected automatically, it does not establish intent or authorship, and it is not an AI signal.");
3737
+ }
3738
+ if (privateUseCount >= 2) {
3739
+ integrityFindings.push({
3740
+ applied: "private_use_cluster",
3741
+ status: "attention",
3742
+ reason: `${privateUseCount} private-use characters are present. One is a routine icon-font or vendor logo; a cluster is a private encoding.`
3743
+ });
3744
+ limitations.push("Private-use characters carry meaning only under a private agreement, which this engine cannot read, so their presence is described and not interpreted.");
3745
+ }
3746
+ let integrityStatus = "clean";
3747
+ let integrityApplied = null;
3748
+ let integrityReason = "No hidden characters, homoglyph substitutions or watermark marks were found that lack an ordinary explanation.";
3749
+ for (const finding2 of integrityFindings) {
3750
+ if (INTEGRITY_RANK[finding2.status] > INTEGRITY_RANK[integrityStatus]) {
3751
+ integrityStatus = finding2.status;
3752
+ integrityApplied = finding2.applied;
3753
+ integrityReason = finding2.reason;
3754
+ }
3755
+ }
3756
+ const integrityConfidence = watermarkOutcome === "detected" || integrityStatus === "manipulated" ? "high" : integrityStatus === "attention" ? "medium" : "low";
3757
+ const model = input.model;
3758
+ const aiProbability = model ? {
3759
+ source: model.version ? `${model.name}@${model.version}` : model.name,
3760
+ value: model.probability,
3761
+ threshold: model.threshold,
3762
+ reading: model.below_reliable_range ? "not_assessed" : model.probability >= model.threshold ? "ai_like" : model.probability >= model.threshold * 0.5 ? "uncertain" : "human_like",
3763
+ confidence: model.below_reliable_range ? "not_assessed" : modelConfidence(model),
3764
+ reason: model.below_reliable_range ? "The trained model ran but this text is outside the length range where its reading is reliable, so no AI probability is published." : `The trained model scored this text at ${(model.probability * 100).toFixed(1)}% against an operating point of ${(model.threshold * 100).toFixed(1)}%. This is the only AI reading the engine publishes.`
3765
+ } : {
3766
+ source: null,
3767
+ value: null,
3768
+ threshold: null,
3769
+ reading: "not_assessed",
3770
+ confidence: "not_assessed",
3771
+ reason: "No trained model ran on this text, so no AI probability is available. Character findings and writing rules cannot supply one."
3772
+ };
3773
+ if (!model) limitations.push(NO_MODEL_LIMIT);
3774
+ if (model?.below_reliable_range) {
3775
+ limitations.push("Short samples remain outside the reliable range. Cycle 5 detects 43 of 56 held-out 100-word AI passages (76.8%) on the server evaluation route; that cell is small and does not justify publishing a probability for this text.");
3776
+ }
3777
+ const suggestionLevel = signals ? SUGGESTION_LEVEL[signals.classification] : "none";
3778
+ const editorial = {
3779
+ suggestion_level: suggestionLevel,
3780
+ score: signals?.score ?? null,
3781
+ categories_hit: signals?.categoriesHit ?? null,
3782
+ finding_count: signals?.findingCount ?? null,
3783
+ confidence: signals?.confidence ?? "not_assessed",
3784
+ reason: signals ? SUGGESTION_REASON[suggestionLevel] : "The named writing rules were not requested for this text.",
3785
+ rule_probabilities: signals?.probabilities ?? null
3786
+ };
3787
+ if (signals) limitations.push(EDITORIAL_LIMIT);
3788
+ const result = {
3789
+ ai_probability: aiProbability,
3790
+ text_integrity: {
3791
+ status: integrityStatus,
3792
+ applied: integrityApplied,
3793
+ reason: integrityReason,
3794
+ findings: integrityFindings,
3795
+ character_evidence: {
3796
+ deliberate,
3797
+ supporting,
3798
+ excluded,
3799
+ interior_homoglyph_count: interiorHomoglyphs,
3800
+ longest_carrier_run: longestRun
3801
+ },
3802
+ watermark: { outcome: watermarkOutcome, counted_as_evidence: watermarkOutcome === "detected" },
3803
+ confidence: integrityConfidence
3804
+ },
3805
+ editorial,
3806
+ inputs_considered: inputs,
3807
+ version: COMBINED_VERDICT_VERSION,
3808
+ limitations: dedupe(limitations),
3809
+ description: DESCRIPTION2
3810
+ };
3811
+ assertAxisIndependence(result, input);
3812
+ return result;
3813
+ }
3814
+ function assertAxisIndependence(result, input) {
3815
+ if (!input.model && result.ai_probability.reading !== "not_assessed") {
3816
+ throw new Error("combined_verdict_axis_violation: an AI reading was published without a trained model");
3817
+ }
3818
+ if (!input.model && result.ai_probability.value !== null) {
3819
+ throw new Error("combined_verdict_axis_violation: an AI probability was published without a trained model");
3820
+ }
3821
+ const evidence = result.text_integrity.character_evidence;
3822
+ const hasCharacterEvidence = evidence.deliberate.length > 0 || result.text_integrity.watermark.counted_as_evidence || evidence.supporting.filter((x) => {
3823
+ const cp = codePointFromLabel(x.code_point);
3824
+ return cp !== null && isPrivateUse(cp);
3825
+ }).length >= 2;
3826
+ if (result.text_integrity.status !== "clean" && !hasCharacterEvidence) {
3827
+ throw new Error("combined_verdict_axis_violation: the integrity status was raised without character or watermark evidence");
3828
+ }
3829
+ const AUTHORSHIP_VOCABULARY = /\b(?:ai[-\s]?(?:like|generated|written|authored)|likely\s+ai|machine[-\s]written|human[-\s]?(?:like|written|authored))\b/i;
3830
+ const strings = [
3831
+ result.text_integrity.reason,
3832
+ ...result.text_integrity.findings.map((f) => f.reason),
3833
+ result.editorial.reason
3834
+ ];
3835
+ for (const line of strings) {
3836
+ if (AUTHORSHIP_VOCABULARY.test(line)) {
3837
+ throw new Error(`combined_verdict_axis_violation: an integrity or editorial string made an authorship claim: ${line.slice(0, 80)}`);
3838
+ }
3839
+ }
3840
+ }
3841
+ function modelConfidence(model) {
3842
+ const distance = Math.abs(model.probability - model.threshold);
3843
+ if (distance >= 0.3) return "high";
3844
+ if (distance >= 0.1) return "medium";
3845
+ return "low";
3846
+ }
3847
+ function item(finding2, tier, rationale) {
3848
+ return { finding_id: finding2.id, code_point: finding2.code_point, name: finding2.name, tier, rationale };
3849
+ }
3850
+ function codePointFromLabel(label) {
3851
+ const parsed = Number.parseInt(label.replace(/^U\+/, ""), 16);
3852
+ return Number.isFinite(parsed) ? parsed : null;
3853
+ }
3854
+ function codePointOf(finding2) {
3855
+ if (finding2.name === "UNPAIRED SURROGATE") return null;
3856
+ return codePointFromLabel(finding2.code_point);
3857
+ }
3858
+ function longestAdjacentRun(positions, text) {
3859
+ if (text === void 0 || positions.length === 0) return 0;
3860
+ const sorted = [...new Set(positions)].sort((a, b) => a - b);
3861
+ let best = 1;
3862
+ let run = 1;
3863
+ for (let i = 1; i < sorted.length; i++) {
3864
+ const previous = sorted[i - 1];
3865
+ const previousCp = text.codePointAt(previous);
3866
+ const width = previousCp !== void 0 && previousCp > 65535 ? 2 : 1;
3867
+ if (previous + width === sorted[i]) {
3868
+ run++;
3869
+ best = Math.max(best, run);
3870
+ } else run = 1;
3871
+ }
3872
+ return best;
3873
+ }
3874
+ function describeEvidence(items) {
3875
+ const names = [...new Set(items.map((x) => `${x.name} ${x.code_point}`))];
3876
+ return names.slice(0, 4).join(", ") + (names.length > 4 ? `, and ${names.length - 4} more` : "");
3877
+ }
3878
+ function describePayload(run, tags, deliberateCount) {
3879
+ const parts = [];
3880
+ if (run >= 3) parts.push(`${run} invisible carriers sit adjacent to one another with no visible character between them`);
3881
+ if (tags >= 2) parts.push(`${tags} tag characters form a run outside any flag sequence`);
3882
+ if (deliberateCount >= 8) parts.push(`${deliberateCount} deliberate carriers are present`);
3883
+ return `This text carries the shape of an encoded payload rather than a stray character: ${parts.join("; ")}. It shows the text was written into, not who composed it.`;
3884
+ }
3885
+ function dedupe(values) {
3886
+ return [...new Set(values)];
3887
+ }
3888
+ var limits = (message) => [message, "Authorship cannot be proved from this check."];
3889
+ var WRITING_SIGNAL_RULES_RUN = 116;
3890
+ async function inspect(request, options = {}) {
3891
+ const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()), started = now();
3892
+ const progress = (p) => {
3893
+ if (options.signal?.aborted) throw new DOMException("Inspection cancelled", "AbortError");
3894
+ options.onProgress?.(p);
3895
+ };
3896
+ progress("validating");
3897
+ if (request.schema_version !== "1.0" || !request.contract_version.startsWith("1.")) throw new Error("contract_incompatible");
3898
+ if (request.source.content.length > 25e4) throw new Error("request_too_large");
3899
+ if (hasUnpairedSurrogate(request.source.content)) throw new Error("invalid_unicode_unpaired_surrogate");
3900
+ const sourceHash = prefixedSha256(request.source.content);
3901
+ progress("mapping_text");
3902
+ const projection = projectVisibleText(request.source.content, request.source.content_type);
3903
+ progress("unicode_checks");
3904
+ const unicode = inspectUnicode(request.source.content);
3905
+ progress("protected_spans");
3906
+ const protectedSpans = extractProtectedSpans({ ...request.source, content_hash: sourceHash });
3907
+ progress("writing_patterns");
3908
+ const patternFindings = [...inspectPatterns(projection.text), ...runRegisteredPacks(projection.text)];
3909
+ const editorialSignals = request.checks.includes("style.patterns") ? computeEditorialSignals(projection.text) : void 0;
3910
+ const methods = [];
3911
+ for (const id of request.checks) {
3912
+ const methodStarted = now();
3913
+ if (id.startsWith("unicode.")) {
3914
+ methods.push(method(id, "unicode", "Opace deterministic Unicode inspection", UNICODE_RULES_VERSION, unicode.length ? "attention" : "pass", unicode.map((x) => ({ type: "unicode_finding", ...x })), limits("Unicode controls can be legitimate in multilingual text."), methodStarted, now(), "browser"));
3915
+ } else if (id === "style.patterns") {
3916
+ const signalsAttention = patternFindings.length > 0 || editorialSignals?.status === "scored" && editorialSignals.classification !== "human_like";
3917
+ const evidence = patternFindings.length ? patternFindings.map((x) => ({ type: "pattern_finding", rule_id: x.rule_id, span: x.span })) : [{ type: "scope_note", rules_run: WRITING_SIGNAL_RULES_RUN, note: "No selected writing-signal rule matched. This is not evidence of human authorship." }];
3918
+ if (editorialSignals) evidence.push({ type: "editorial_signals", ...editorialSignals });
3919
+ methods.push(method(id, "pattern", "Opace writing-signal rules", EN_SIGNALS_PATTERN_VERSION, signalsAttention ? "attention" : "pass", evidence, limits("Writing patterns are editorial prompts, not detector or watermark evidence."), methodStarted, now(), "browser"));
3920
+ } else if (id === "watermark.anthropic") {
3921
+ methods.push({ ...method(id, "watermark", "Anthropic official text-watermark detector", "unavailable-2026-08-26", "unsupported", [], ["No official detector call was available. Local style or public SynthID tests are not substitutes.", "Not yet available in this release."], methodStarted, now(), "browser"), availability: "not_available", native_outcome: "not_available" });
3922
+ } else methods.push(method(id, "detector", id, "unsupported/1", "unsupported", [], ["This requested method is not implemented in the deterministic browser core.", "Not yet available in this release."], methodStarted, now(), "browser"));
3923
+ }
3924
+ const watermarkOutcome = request.checks.includes("watermark.anthropic") ? "not_available" : "not_supported";
3925
+ const combinedVerdict = computeCombinedVerdict({ signals: editorialSignals, unicodeFindings: unicode, text: request.source.content, watermark: { outcome: watermarkOutcome } });
3926
+ const combinedRaised = combinedVerdict.text_integrity.status !== "clean";
3927
+ const summary = { pass: 0, attention: 0, fail: 0, inconclusive: 0, unsupported: 0, not_configured: 0, not_run: 0, error: 0 };
3928
+ for (const item2 of methods) summary[item2.status]++;
3929
+ progress("complete");
3930
+ const result = { schema_version: "1.0", contract_version: "1.0.0", request_id: request.request_id, analysis_id: options.analysisId?.() ?? `analysis_${sourceHash.slice(7, 23)}`, source: { content_hash: sourceHash, normalised_hash: prefixedSha256(projection.text.normalize("NFC")), content_type: request.source.content_type, language: request.source.language, word_count: (projection.text.trim().match(/\S+/g) ?? []).length }, protected_spans: protectedSpans, pattern_findings: patternFindings, methods, summary, combined_verdict: combinedVerdict, limitations: [.../* @__PURE__ */ new Set(["Authorship cannot be proved from these checks.", ...projection.limitations, ...combinedRaised ? combinedVerdict.limitations : []])], started_at: started, completed_at: now() };
3931
+ return deepFreeze(result);
3932
+ }
3933
+ function method(id, category, provider, version, status, evidence, limitations, started_at, completed_at, privacy_route) {
3934
+ if (!limitations.length) throw new Error("method_limitations_required");
3935
+ return { id, category, provider_or_method: provider, version, status, score: null, threshold: null, segments: [], evidence, limitations, started_at, completed_at, privacy_route };
3936
+ }
3937
+ function deepFreeze(value) {
3938
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
3939
+ Object.freeze(value);
3940
+ for (const child of Object.values(value)) deepFreeze(child);
3941
+ }
3942
+ return value;
3943
+ }
3944
+ function hasUnpairedSurrogate(value) {
3945
+ for (let i = 0; i < value.length; i++) {
3946
+ const code = value.charCodeAt(i);
3947
+ if (code >= 55296 && code <= 56319) {
3948
+ const next = value.charCodeAt(i + 1);
3949
+ if (!(next >= 56320 && next <= 57343)) return true;
3950
+ i++;
3951
+ } else if (code >= 56320 && code <= 57343) return true;
3952
+ }
3953
+ return false;
3954
+ }
3955
+ var METHODS = Object.freeze([
3956
+ Object.freeze({ id: "unicode.invisible", category: "unicode", version: "unicode:2026.08.1", state: "available", privacy_routes: ["browser"], limitations: ["Controls can be legitimate in multilingual text."] }),
3957
+ Object.freeze({ id: "unicode.homoglyph", category: "unicode", version: "unicode:2026.08.1", state: "available", privacy_routes: ["browser"], limitations: ["Mixed scripts require contextual human review."] }),
3958
+ Object.freeze({ id: "style.patterns", category: "pattern", version: "en-gb:2026.08.1", state: "available", privacy_routes: ["browser"], limitations: ["Editorial pattern findings are not authorship evidence."] }),
3959
+ Object.freeze({ id: "watermark.anthropic", category: "watermark", version: "adapter-placeholder/1", state: "unsupported", privacy_routes: ["browser"], limitations: ["No official detector interface is available."] })
3960
+ ]);
3961
+
3962
+ // src/worker/protocol.ts
3963
+ var WORKER_PROTOCOL_VERSION = "1.0";
3964
+
3965
+ // src/worker/entry.ts
3966
+ var controllers = /* @__PURE__ */ new Map();
3967
+ self.onmessage = async (event) => {
3968
+ const m = event.data;
3969
+ if (m.protocol_version !== WORKER_PROTOCOL_VERSION) return;
3970
+ if (m.type === "cancel") {
3971
+ controllers.get(m.id)?.abort();
3972
+ return;
3973
+ }
3974
+ const controller = new AbortController();
3975
+ controllers.set(m.id, controller);
3976
+ try {
3977
+ const result = await inspect(m.request, { signal: controller.signal, onProgress: (phase) => self.postMessage({ protocol_version: WORKER_PROTOCOL_VERSION, type: "progress", id: m.id, phase }) });
3978
+ self.postMessage({ protocol_version: WORKER_PROTOCOL_VERSION, type: "result", id: m.id, result });
3979
+ } catch (error) {
3980
+ self.postMessage({ protocol_version: WORKER_PROTOCOL_VERSION, type: "error", id: m.id, code: error instanceof DOMException && error.name === "AbortError" ? "job_cancelled" : "internal_error", message: "Inspection did not complete." });
3981
+ } finally {
3982
+ controllers.delete(m.id);
3983
+ }
3984
+ };