@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,155 @@
1
+ // src/highlight.ts
2
+ var BAND = {
3
+ "signal-likely-human": "human",
4
+ "signal-unclear": "unclear",
5
+ "signal-potentially-ai": "potential",
6
+ "signal-likely-ai": "likely",
7
+ "signal-strongly-ai": "strong"
8
+ };
9
+ var BAND_COLOURS = {
10
+ human: { ink: "#1c6e46", wash: "#d9f2e5" },
11
+ unclear: { ink: "#5c6360", wash: "#e6e0d6" },
12
+ potential: { ink: "#8a5a00", wash: "#f8e5c7" },
13
+ likely: { ink: "#a84a08", wash: "#f9dcc4" },
14
+ strong: { ink: "#96261b", wash: "#f6d7d0" }
15
+ };
16
+ var PAGE_MARK_CLASS = "oaci-page-mark";
17
+ var STYLE_ID = "oaci-page-mark-style";
18
+ var PAGE_MARK_CSS = `.${PAGE_MARK_CLASS}{background:var(--oaci-mark-wash,#f8e5c7);color:#12161a;border-radius:2px;box-shadow:0 0 0 2px var(--oaci-mark-wash,#f8e5c7);text-decoration:underline;text-decoration-color:var(--oaci-mark-ink,#8a5a00);text-decoration-thickness:2px;text-underline-offset:2px}@media(forced-colors:active){.${PAGE_MARK_CLASS}{background:Mark;color:MarkText;box-shadow:none}}`;
19
+ var UNTOUCHABLE = /* @__PURE__ */ new Set(["TEXTAREA", "INPUT", "SELECT", "OPTION", "SCRIPT", "STYLE", "TEMPLATE", "NOSCRIPT"]);
20
+ var isSeparator = (run) => run.start_utf16 === 0 && run.end_utf16 === 0;
21
+ function slicesForRange(runs, start, end) {
22
+ const from = Math.max(0, start);
23
+ const to = end;
24
+ if (!Number.isFinite(from) || !Number.isFinite(to) || to <= from) return [];
25
+ const slices = [];
26
+ for (const run of runs) {
27
+ if (isSeparator(run)) continue;
28
+ if (run.visible_end_utf16 <= from || run.visible_start_utf16 >= to) continue;
29
+ const offset = Math.max(0, from - run.visible_start_utf16);
30
+ const finish = Math.min(run.text.length, to - run.visible_start_utf16);
31
+ const length = finish - offset;
32
+ if (length <= 0) continue;
33
+ slices.push({ path: [...run.node_path], offset, length, text: run.text.slice(offset, finish) });
34
+ }
35
+ return slices;
36
+ }
37
+ function resolvePath(root, path) {
38
+ let node = root;
39
+ for (const index of path) {
40
+ const children = node?.childNodes;
41
+ if (!children || index < 0 || index >= children.length) return null;
42
+ node = children[index];
43
+ }
44
+ return node ?? null;
45
+ }
46
+ function isWrappable(node, guard) {
47
+ let current = node.parentNode;
48
+ while (current) {
49
+ if (current === guard) return false;
50
+ const element = current;
51
+ const tag = typeof element.tagName === "string" ? element.tagName.toUpperCase() : "";
52
+ if (UNTOUCHABLE.has(tag)) return false;
53
+ if (tag.startsWith("ASTRO-DEV-TOOLBAR")) return false;
54
+ if (element.isContentEditable === true) return false;
55
+ if (typeof element.getAttribute === "function") {
56
+ const editable = element.getAttribute("contenteditable");
57
+ if (editable !== null && editable !== "false") return false;
58
+ }
59
+ current = current.parentNode;
60
+ }
61
+ return true;
62
+ }
63
+ function createPageHighlighter(options) {
64
+ const root = options.root;
65
+ const doc = options.document ?? root.ownerDocument ?? root;
66
+ const guard = options.guard ?? null;
67
+ const runs = options.runs;
68
+ let marks = [];
69
+ const ensureStyle = () => {
70
+ const head = doc.head ?? null;
71
+ if (!head || typeof head.querySelector !== "function") return;
72
+ if (head.querySelector(`#${STYLE_ID}`)) return;
73
+ const style = doc.createElement("style");
74
+ style.setAttribute("id", STYLE_ID);
75
+ style.textContent = PAGE_MARK_CSS;
76
+ head.appendChild(style);
77
+ };
78
+ const clear = () => {
79
+ for (const mark of marks.splice(0)) {
80
+ const parent = mark.parentNode;
81
+ if (!parent) continue;
82
+ parent.replaceChild(doc.createTextNode(mark.textContent ?? ""), mark);
83
+ if (typeof parent.normalize === "function") parent.normalize();
84
+ }
85
+ };
86
+ const scrollTo = (mark) => {
87
+ if (!mark) return;
88
+ const view = doc.defaultView ?? null;
89
+ const fallback = mark;
90
+ if (!view || typeof view.scrollBy !== "function" || typeof mark.getBoundingClientRect !== "function") {
91
+ if (typeof fallback.scrollIntoView === "function") fallback.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
92
+ return;
93
+ }
94
+ const height = view.innerHeight || 0;
95
+ const panel = options.avoid?.() ?? null;
96
+ const covering = panel && panel.width >= (view.innerWidth || 0) * 0.75 && panel.top <= 8;
97
+ const top = covering ? panel.bottom + 24 : 24;
98
+ const bottom = height - 24;
99
+ const rect = mark.getBoundingClientRect();
100
+ if (rect.top >= top && rect.bottom <= bottom) return;
101
+ const room = bottom - top;
102
+ const target = room > rect.height + 40 ? top + (room - rect.height) / 2 : top;
103
+ view.scrollBy({ top: rect.top - target, behavior: "auto" });
104
+ };
105
+ const show = (start, end, level) => {
106
+ clear();
107
+ const band = BAND[level] ?? "unclear";
108
+ const colours = BAND_COLOURS[band] ?? BAND_COLOURS.unclear;
109
+ const targets = [];
110
+ for (const slice of slicesForRange(runs, start, end)) {
111
+ const node = resolvePath(root, slice.path);
112
+ if (!node || node.nodeType !== 3) continue;
113
+ const text = node;
114
+ const value = text.nodeValue ?? "";
115
+ if (value.slice(slice.offset, slice.offset + slice.length) !== slice.text) continue;
116
+ if (!isWrappable(text, guard)) continue;
117
+ targets.push({ node: text, slice });
118
+ }
119
+ if (!targets.length) return 0;
120
+ ensureStyle();
121
+ for (const { node, slice } of targets) {
122
+ const parent = node.parentNode;
123
+ if (!parent) continue;
124
+ const value = node.nodeValue ?? "";
125
+ const before = value.slice(0, slice.offset);
126
+ const after = value.slice(slice.offset + slice.length);
127
+ const mark = doc.createElement("span");
128
+ mark.setAttribute("class", PAGE_MARK_CLASS);
129
+ mark.setAttribute("data-oaci-page-mark", String(band));
130
+ mark.setAttribute("style", `--oaci-mark-ink:${colours.ink};--oaci-mark-wash:${colours.wash}`);
131
+ mark.appendChild(doc.createTextNode(slice.text));
132
+ if (after) parent.insertBefore(doc.createTextNode(after), node.nextSibling);
133
+ parent.insertBefore(mark, node.nextSibling);
134
+ if (before) node.nodeValue = before;
135
+ else parent.removeChild(node);
136
+ marks.push(mark);
137
+ }
138
+ if (options.scroll !== false) scrollTo(marks[0]);
139
+ return marks.length;
140
+ };
141
+ return {
142
+ show,
143
+ clear,
144
+ get count() {
145
+ return marks.length;
146
+ }
147
+ };
148
+ }
149
+ export {
150
+ PAGE_MARK_CLASS,
151
+ PAGE_MARK_CSS,
152
+ createPageHighlighter,
153
+ resolvePath,
154
+ slicesForRange
155
+ };
@@ -0,0 +1,6 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ import { type ContentIntegrityAstroOptions } from './options.js';
3
+ export type { ContentIntegrityAstroOptions } from './options.js';
4
+ export { analyseHtml, visibleHtml } from './report.js';
5
+ export declare const APP_ID = "opace-ai-content-checker";
6
+ export default function contentIntegrity(userOptions?: ContentIntegrityAstroOptions): AstroIntegration;
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ import {
2
+ normaliseOptions
3
+ } from "./shared-MMXWTX2U.js";
4
+ import {
5
+ analyseHtml,
6
+ visibleHtml,
7
+ writeBuildReport
8
+ } from "./shared-EX54KI6H.js";
9
+
10
+ // src/index.ts
11
+ import { fileURLToPath } from "node:url";
12
+ var APP_ID = "opace-ai-content-checker";
13
+ function contentIntegrity(userOptions = {}) {
14
+ const options = normaliseOptions(userOptions);
15
+ return {
16
+ name: "@opacedev/astro-ai-content-checker",
17
+ hooks: {
18
+ "astro:config:setup": ({ addDevToolbarApp, command }) => {
19
+ if (!options.toolbar || command !== "dev") return;
20
+ addDevToolbarApp({
21
+ id: APP_ID,
22
+ name: "Opace AI Content Checker & Detector for Astro",
23
+ // A single-colour line glyph, so it sits with Astro's own white rail
24
+ // icons instead of dropping a colour tile into their row. The full
25
+ // colour product mark stays inside the panel masthead.
26
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3.2h6a1.5 1.5 0 0 1 1.5 1.5v.6a1.5 1.5 0 0 1-1.5 1.5H9a1.5 1.5 0 0 1-1.5-1.5v-.6A1.5 1.5 0 0 1 9 3.2Z"/><path d="M16.5 4.7h1.9A1.6 1.6 0 0 1 20 6.3v13.1a1.6 1.6 0 0 1-1.6 1.6H5.6A1.6 1.6 0 0 1 4 19.4V6.3a1.6 1.6 0 0 1 1.6-1.6h1.9"/><path d="M8 10.4h5"/><path d="m8.3 15.2 2.3 2.3 5.1-5.6"/></svg>',
27
+ entrypoint: fileURLToPath(new URL("./toolbar.js", import.meta.url))
28
+ });
29
+ },
30
+ "astro:build:done": async ({ dir, logger }) => {
31
+ if (options.buildCheck === false) return;
32
+ const evidence = await writeBuildReport(dir, options);
33
+ logger.info(`Hash-only Opace AI Content Checker & Detector report: ${evidence.hash}`);
34
+ }
35
+ }
36
+ };
37
+ }
38
+ export {
39
+ APP_ID,
40
+ analyseHtml,
41
+ contentIntegrity as default,
42
+ visibleHtml
43
+ };
@@ -0,0 +1,21 @@
1
+ export interface ContentIntegrityAstroOptions {
2
+ toolbar?: boolean;
3
+ buildCheck?: 'report' | false;
4
+ failOn?: Array<'protected_fact_changed'>;
5
+ localService?: false;
6
+ reportDirectory?: string;
7
+ include?: string[];
8
+ exclude?: string[];
9
+ maxCharacters?: number;
10
+ }
11
+ export interface NormalisedOptions {
12
+ toolbar: boolean;
13
+ buildCheck: 'report' | false;
14
+ failOn: Array<'protected_fact_changed'>;
15
+ localService: false;
16
+ reportDirectory: string;
17
+ include: string[];
18
+ exclude: string[];
19
+ maxCharacters: number;
20
+ }
21
+ export declare function normaliseOptions(value?: ContentIntegrityAstroOptions): NormalisedOptions;
@@ -0,0 +1,6 @@
1
+ import {
2
+ normaliseOptions
3
+ } from "./shared-MMXWTX2U.js";
4
+ export {
5
+ normaliseOptions
6
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The content-free JSON receipt.
3
+ *
4
+ * A receipt records what ran and what it found: hashes, counts, levels, scores,
5
+ * method identities, versions, routes and limitations. It never carries the
6
+ * page text. Section passages are replaced by their UTF-16 locators, and no
7
+ * page URL or route path is written, so a receipt can be attached to a ticket
8
+ * or an audit trail without moving a draft.
9
+ */
10
+ import type { CheckerResult } from '@opace/content-integrity-cycle5-browser';
11
+ export declare const RECEIPT_VERSION = "astro-toolbar-content-free/2.0.0";
12
+ /** Fails closed if a content-shaped field survives into a receipt. */
13
+ export declare function assertContentFree(value: unknown, path?: string): void;
14
+ /** Build the exact-result receipt with every content-bearing field removed. */
15
+ export declare function buildContentFreeReceipt(result: CheckerResult, generatedAt?: string): Record<string, unknown>;
@@ -0,0 +1,100 @@
1
+ // src/receipt.ts
2
+ var RECEIPT_VERSION = "astro-toolbar-content-free/2.0.0";
3
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["passage", "draft", "excerpt", "candidate", "source_text", "content_text", "route_path"]);
4
+ var CHECKED_KEYS = /* @__PURE__ */ new Set(["content", "text", "url", "href"]);
5
+ var POLICY_TOKEN = /^[A-Za-z0-9_.:+-]{1,40}$/u;
6
+ function assertContentFree(value, path = "receipt") {
7
+ if (Array.isArray(value)) {
8
+ value.forEach((item, index) => assertContentFree(item, `${path}[${index}]`));
9
+ return;
10
+ }
11
+ if (typeof value !== "object" || value === null) return;
12
+ for (const [key, item] of Object.entries(value)) {
13
+ const suspect = FORBIDDEN_KEYS.has(key) || CHECKED_KEYS.has(key) && !(typeof item === "string" && POLICY_TOKEN.test(item)) && item !== null && item !== void 0;
14
+ if (suspect) throw new Error(`Receipt field ${path}.${key} could carry page content.`);
15
+ assertContentFree(item, `${path}.${key}`);
16
+ }
17
+ }
18
+ function buildContentFreeReceipt(result, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
19
+ const ai = result.axes.ai_pattern;
20
+ const receipt = {
21
+ schema_version: result.schema_version,
22
+ contract_version: result.contract_version,
23
+ receipt_version: RECEIPT_VERSION,
24
+ result_id: result.result_id,
25
+ profile: result.profile,
26
+ surface: "Astro dev toolbar",
27
+ generated_at: result.generated_at,
28
+ written_at: generatedAt,
29
+ contains_content: false,
30
+ source: {
31
+ content_hash: result.source.content_hash,
32
+ normalised_hash: result.source.normalised_hash,
33
+ content_type: result.source.content_type,
34
+ language: result.source.language,
35
+ word_count: result.source.word_count,
36
+ character_count: result.source.character_count,
37
+ section_count: result.source.section_count
38
+ },
39
+ route: {
40
+ kind: result.route.kind,
41
+ location: result.route.location,
42
+ content_transfer: result.route.content_transfer,
43
+ privacy_route: result.route.privacy_route,
44
+ consent: result.route.consent,
45
+ retention: result.route.retention,
46
+ model: result.route.model
47
+ },
48
+ axes: {
49
+ ai_pattern: {
50
+ assessment_status: ai.assessment_status,
51
+ method_status: ai.method_status,
52
+ source: ai.source,
53
+ raw_score: ai.raw_score,
54
+ raw_margin: ai.raw_margin,
55
+ display_score: ai.display_score,
56
+ score_scale: ai.score_scale,
57
+ level: ai.level,
58
+ primary_display_threshold: ai.primary_display_threshold,
59
+ secondary_display_threshold: ai.secondary_display_threshold,
60
+ flagged: ai.flagged,
61
+ flag_reason: ai.flag_reason,
62
+ strongest_section_index: ai.strongest_section_index,
63
+ reason: ai.reason,
64
+ limitations: ai.limitations
65
+ },
66
+ text_integrity: result.axes.text_integrity,
67
+ editorial: result.axes.editorial
68
+ },
69
+ sections: result.sections.map((section) => ({
70
+ index: section.index,
71
+ start_utf16: section.start_utf16,
72
+ end_utf16: section.end_utf16,
73
+ word_count: section.word_count,
74
+ raw_score: section.raw_score,
75
+ raw_margin: section.raw_margin,
76
+ display_score: section.display_score,
77
+ level: section.level,
78
+ evidence: section.evidence.map((item) => ({ id: item.id, kind: item.kind, basis: item.basis ?? null }))
79
+ })),
80
+ methods: result.methods.map((method) => ({
81
+ id: method.id,
82
+ category: method.category,
83
+ provider_or_method: method.provider_or_method,
84
+ version: method.version,
85
+ status: method.status,
86
+ privacy_route: method.privacy_route,
87
+ limitations: method.limitations
88
+ })),
89
+ provenance: result.provenance,
90
+ abuse_controls: result.abuse_controls,
91
+ limitations: [...result.limitations, "No page text, page URL or route path is retained in this receipt."]
92
+ };
93
+ assertContentFree(receipt);
94
+ return receipt;
95
+ }
96
+ export {
97
+ RECEIPT_VERSION,
98
+ assertContentFree,
99
+ buildContentFreeReceipt
100
+ };
@@ -0,0 +1,55 @@
1
+ import type { NormalisedOptions } from './options.js';
2
+ declare const STABLE_TIME = "2000-01-01T00:00:00.000Z";
3
+ export declare function visibleHtml(html: string, maxCharacters?: number): string;
4
+ export interface HashOnlyRouteReport {
5
+ route_id: string;
6
+ source_hash: string;
7
+ word_count: number;
8
+ summary: Record<string, number>;
9
+ methods: Array<{
10
+ id: string;
11
+ version: string;
12
+ status: string;
13
+ limitations: string[];
14
+ }>;
15
+ protected_counts: Record<string, number>;
16
+ pattern_counts: Record<string, number>;
17
+ truncated: boolean;
18
+ }
19
+ export interface AstroIntegrityReport {
20
+ schema_version: '1.0';
21
+ contract_version: '1.0.0';
22
+ package_version: '0.3.1';
23
+ /** Build support, never the full checker. The interactive toolbar is the checker. */
24
+ profile: 'build_scan';
25
+ mode: 'report_only';
26
+ privacy_route: 'browser';
27
+ contains_content: false;
28
+ generated_at: typeof STABLE_TIME;
29
+ /** The three result axes, with the model axis honestly unassessed. */
30
+ axes: {
31
+ ai_pattern: {
32
+ assessment_status: 'not_assessed';
33
+ reason: string;
34
+ limitations: string[];
35
+ };
36
+ text_integrity: {
37
+ method_status: 'per_route';
38
+ reason: string;
39
+ };
40
+ editorial: {
41
+ method_status: 'per_route';
42
+ reason: string;
43
+ };
44
+ };
45
+ routes: HashOnlyRouteReport[];
46
+ limitations: string[];
47
+ }
48
+ export declare function shouldInclude(path: string, options: Pick<NormalisedOptions, 'include' | 'exclude'>): boolean;
49
+ export declare function analyseHtml(html: string, opaqueRoute: string, maxCharacters?: number): Promise<HashOnlyRouteReport>;
50
+ export declare function writeBuildReport(outputUrl: URL, options: NormalisedOptions): Promise<{
51
+ json: string;
52
+ html: string;
53
+ hash: string;
54
+ }>;
55
+ export {};
package/dist/report.js ADDED
@@ -0,0 +1,12 @@
1
+ import {
2
+ analyseHtml,
3
+ shouldInclude,
4
+ visibleHtml,
5
+ writeBuildReport
6
+ } from "./shared-EX54KI6H.js";
7
+ export {
8
+ analyseHtml,
9
+ shouldInclude,
10
+ visibleHtml,
11
+ writeBuildReport
12
+ };
@@ -0,0 +1,181 @@
1
+ // src/sections.ts
2
+ function stripLabel(number, total, level, score) {
3
+ return [`Section ${number} of ${total}`, level, score].filter(Boolean).join(" \xB7 ");
4
+ }
5
+ function neighbour(index, total, step) {
6
+ const next = index + step;
7
+ return next < 0 || next >= total ? null : next;
8
+ }
9
+ function tintStatus(marks) {
10
+ if (marks > 0) return `Shown on the page: ${marks} passage${marks === 1 ? "" : "s"} tinted in the band colour.`;
11
+ return "This passage could not be found on the page as it is now, so nothing was tinted. The evidence below is still the passage the model read.";
12
+ }
13
+ var NOOP = {
14
+ total: 0,
15
+ open: null,
16
+ show() {
17
+ },
18
+ close() {
19
+ },
20
+ toggled() {
21
+ },
22
+ destroy() {
23
+ }
24
+ };
25
+ function installSectionAccordion(root, options = {}) {
26
+ const strip = root.querySelector(".oaci-strip");
27
+ const list = strip?.querySelector(".oaci-strip__list");
28
+ const rows = list ? [...list.childNodes].filter((node) => node.nodeType === 1 && node.tagName === "LI") : [];
29
+ const dives = root.querySelector(".oaci-dives");
30
+ if (!strip || !list || !rows.length || !dives) return NOOP;
31
+ const doc = root.ownerDocument;
32
+ const total = rows.length;
33
+ const listeners = [];
34
+ const on = (node, type, handler) => {
35
+ node.addEventListener(type, handler);
36
+ listeners.push(() => node.removeEventListener(type, handler));
37
+ };
38
+ const intro = dives.querySelector(".oaci-dives__intro");
39
+ const head = strip.querySelector(".oaci-strip__head");
40
+ if (intro && head) head.appendChild(intro);
41
+ for (const dive of dives.querySelectorAll(".oaci-dive")) {
42
+ const index = Number(dive.getAttribute("data-oaci-section"));
43
+ const row = Number.isInteger(index) ? rows[index] : void 0;
44
+ if (!row) continue;
45
+ dive.setAttribute("class", `${dive.getAttribute("class") ?? ""} oacit-dive-inline`.trim());
46
+ row.appendChild(dive);
47
+ }
48
+ dives.remove();
49
+ const bars = rows.map((row) => row.querySelector(".oaci-strip__bar"));
50
+ const panels = rows.map((row) => row.querySelector(".oaci-dive"));
51
+ bars.forEach((bar, index) => {
52
+ bar?.setAttribute("aria-expanded", "false");
53
+ const panel = panels[index];
54
+ if (panel) panel.hidden = true;
55
+ });
56
+ const make = (tag, className, attributes = {}, text = "") => {
57
+ const node = doc.createElement(tag);
58
+ node.setAttribute("class", className);
59
+ for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, value);
60
+ if (text) node.appendChild(doc.createTextNode(text));
61
+ return node;
62
+ };
63
+ const nav = make("div", "oacit-secnav", { "data-oacit-secnav": "" });
64
+ nav.hidden = true;
65
+ const step = (direction, label, glyph) => {
66
+ const button = make("button", "oacit-secnav__step", {
67
+ type: "button",
68
+ "data-oacit-secnav-step": direction,
69
+ "aria-label": label
70
+ });
71
+ button.appendChild(make("span", "oacit-secnav__glyph", { "aria-hidden": "true" }, glyph));
72
+ return button;
73
+ };
74
+ const previous = step("-1", "Previous section", "\u2039");
75
+ const following = step("1", "Next section", "\u203A");
76
+ const now = make("p", "oacit-secnav__now", { "data-oacit-secnav-now": "" });
77
+ const closeButton = make("button", "oacit-secnav__close", { type: "button", "data-oacit-secnav-close": "" }, "Close");
78
+ const tint = make("p", "oacit-secnav__tint", { "data-oacit-secnav-tint": "", role: "status" });
79
+ for (const child of [previous, now, following, closeButton, tint]) nav.appendChild(child);
80
+ strip.insertBefore(nav, list);
81
+ const steps = [previous, following];
82
+ const offsetHost = options.offsetHost ?? options.scroller ?? null;
83
+ const heightOf = (node) => node && Number.isFinite(node.offsetHeight) ? node.offsetHeight : 0;
84
+ const measure = () => {
85
+ if (!offsetHost || typeof offsetHost.style?.setProperty !== "function") return;
86
+ offsetHost.style.setProperty("--oacit-head-h", `${heightOf(offsetHost.querySelector(".oacit-head"))}px`);
87
+ offsetHost.style.setProperty("--oacit-secnav-h", `${nav.hidden ? 0 : heightOf(nav)}px`);
88
+ };
89
+ let open = null;
90
+ const pin = (index) => {
91
+ const scroller = options.scroller;
92
+ const row = rows[index];
93
+ if (!scroller || !row) return;
94
+ if (typeof scroller.getBoundingClientRect !== "function" || typeof row.getBoundingClientRect !== "function") return;
95
+ const above = heightOf(scroller.querySelector(".oacit-head")) + (nav.hidden ? 0 : heightOf(nav));
96
+ const delta = row.getBoundingClientRect().top - scroller.getBoundingClientRect().top - above;
97
+ scroller.scrollTop += delta;
98
+ };
99
+ const paint = () => {
100
+ if (open === null) {
101
+ nav.hidden = true;
102
+ measure();
103
+ return;
104
+ }
105
+ const bar = bars[open];
106
+ const level = bar?.querySelector(".oaci-strip__band")?.textContent?.trim() ?? "";
107
+ const score = bar?.querySelector(".oaci-strip__score")?.textContent?.trim() ?? "";
108
+ now.textContent = stripLabel(open + 1, total, level, score);
109
+ for (const step2 of steps) {
110
+ const target = neighbour(open, total, Number(step2.getAttribute("data-oacit-secnav-step")));
111
+ step2.disabled = target === null;
112
+ }
113
+ nav.hidden = false;
114
+ measure();
115
+ };
116
+ const apply = (index) => {
117
+ open = index;
118
+ rows.forEach((row, position) => {
119
+ const expanded = position === index;
120
+ row.toggleAttribute("data-oacit-open", expanded);
121
+ bars[position]?.setAttribute("aria-expanded", String(expanded));
122
+ const panel = panels[position];
123
+ if (panel) panel.hidden = !expanded;
124
+ });
125
+ paint();
126
+ };
127
+ const show = (index) => {
128
+ if (index < 0 || index >= total) return;
129
+ apply(index);
130
+ tint.textContent = tintStatus(options.onOpen ? options.onOpen(index) : 0);
131
+ pin(index);
132
+ };
133
+ const close = () => {
134
+ apply(null);
135
+ tint.textContent = "";
136
+ options.onClose?.();
137
+ };
138
+ const toggled = (index, expanded) => {
139
+ if (!expanded) {
140
+ close();
141
+ return;
142
+ }
143
+ show(index);
144
+ };
145
+ for (const step2 of steps) {
146
+ on(step2, "click", () => {
147
+ if (open === null) return;
148
+ const target = neighbour(open, total, Number(step2.getAttribute("data-oacit-secnav-step")));
149
+ if (target === null) return;
150
+ show(target);
151
+ });
152
+ }
153
+ on(closeButton, "click", () => {
154
+ const returning = open;
155
+ close();
156
+ if (returning !== null) bars[returning]?.focus();
157
+ });
158
+ apply(null);
159
+ measure();
160
+ return {
161
+ get total() {
162
+ return total;
163
+ },
164
+ get open() {
165
+ return open;
166
+ },
167
+ show,
168
+ close,
169
+ toggled,
170
+ destroy() {
171
+ for (const remove of listeners.splice(0)) remove();
172
+ options.onClose?.();
173
+ }
174
+ };
175
+ }
176
+ export {
177
+ installSectionAccordion,
178
+ neighbour,
179
+ stripLabel,
180
+ tintStatus
181
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Sharing a reading without sharing the page.
3
+ *
4
+ * A share here is a summary: the level, the per-section scores already on
5
+ * screen, the word count, the date and the model version. No page text, no URL
6
+ * and no route path travels. The payload rides in the URL fragment, which a
7
+ * browser never sends to a server, and the honesty line travels with every
8
+ * summary so a level cannot be quoted without its limits.
9
+ *
10
+ * The wire shape matches the website checker's `#shared=` fragment exactly, so a
11
+ * summary produced in the toolbar opens as a read-only result on the product
12
+ * page. `v:1` fixes the level ordering forever.
13
+ */
14
+ import type { CheckerResult } from '@opace/content-integrity-cycle5-browser';
15
+ export declare const CHECKER_URL = "https://opace.agency/tools/ai/content-verification-integrity/checker/";
16
+ export declare const HONESTY_LINE = "No AI checker can prove who wrote a text \u2014 this is a pattern reading.";
17
+ /** Fixed by the wire format. Withheld readings produced no level and are never shared. */
18
+ declare const SHAREABLE_LEVELS: readonly ["signal-likely-human", "signal-unclear", "signal-potentially-ai", "signal-likely-ai", "signal-strongly-ai"];
19
+ export type ShareableLevel = (typeof SHAREABLE_LEVELS)[number];
20
+ export declare const LEVEL_NAMES: Readonly<Record<ShareableLevel, string>>;
21
+ export interface ShareSummary {
22
+ levelId: ShareableLevel;
23
+ /** `score` is the four-decimal wire value; `display` is the exact string the reading printed. */
24
+ sections: Array<{
25
+ index: number;
26
+ score: number;
27
+ display: string;
28
+ levelId: ShareableLevel;
29
+ }>;
30
+ display: string;
31
+ words: number;
32
+ date: string;
33
+ version: string;
34
+ }
35
+ /**
36
+ * Build a summary from the canonical content-free share payload the core
37
+ * already asserts carries no draft. The page text is deliberately not a
38
+ * parameter, so no excerpt can reach a share by accident.
39
+ */
40
+ export declare function buildShareSummary(result: CheckerResult): ShareSummary | undefined;
41
+ /** Encode a summary as the base64url payload the website's `#shared=` reader accepts. */
42
+ export declare function encodeShared(summary: ShareSummary): string;
43
+ /** The full shareable link: the canonical product page plus the content-free fragment. */
44
+ export declare const shareUrl: (summary: ShareSummary) => string;
45
+ /** The plain-text summary offered alongside the link. Counts and levels only. */
46
+ export declare function shareText(summary: ShareSummary): string;
47
+ export {};