agentlinkops 0.6.11 → 0.6.16

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 (311) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +2 -2
  3. package/.mcp.json +0 -0
  4. package/CHANGELOG.md +35 -0
  5. package/LICENSE +0 -0
  6. package/NOTICE +0 -0
  7. package/PACK-ARTIFACT.json +16 -0
  8. package/PACKS.json +591 -0
  9. package/README.md +34 -48
  10. package/bin/agentlinkops.mjs +0 -0
  11. package/bin/linktrail.mjs +0 -0
  12. package/cli/adapters/csv.js +0 -0
  13. package/cli/adapters/sqlite-crm.js +0 -0
  14. package/cli/adapters/suppliers.js +1 -1
  15. package/cli/admission.js +0 -0
  16. package/cli/agent-setup.js +101 -17
  17. package/cli/agentlinkops.mjs +0 -0
  18. package/cli/args.js +0 -0
  19. package/cli/campaign-bridge.js +325 -0
  20. package/cli/campaign-outcomes.js +107 -0
  21. package/cli/catalog.js +47686 -16398
  22. package/cli/check.js +40 -10
  23. package/cli/chronology.js +39 -0
  24. package/cli/citation.js +0 -0
  25. package/cli/client.js +2 -1
  26. package/cli/cloud-error.js +0 -0
  27. package/cli/commands.js +17 -8
  28. package/cli/config.js +0 -0
  29. package/cli/connection.js +92 -18
  30. package/cli/contact-adoption.js +91 -0
  31. package/cli/context-mcp.js +0 -0
  32. package/cli/context.js +13 -2
  33. package/cli/delivery.js +0 -0
  34. package/cli/disavow.js +0 -0
  35. package/cli/doctor.js +15 -4
  36. package/cli/env.js +0 -0
  37. package/cli/fleet.js +30 -19
  38. package/cli/gsc-browser.js +0 -0
  39. package/cli/import.js +7 -1
  40. package/cli/index-observations.js +0 -0
  41. package/cli/index-submissions.js +0 -0
  42. package/cli/key-store.js +115 -0
  43. package/cli/ledger.js +1 -0
  44. package/cli/lifecycle.js +0 -0
  45. package/cli/linktrail.mjs +0 -0
  46. package/cli/local-result.js +41 -6
  47. package/cli/main.js +85 -20
  48. package/cli/mcp-client.js +2 -2
  49. package/cli/migration.js +308 -0
  50. package/cli/mirror.js +0 -0
  51. package/cli/mix.js +0 -0
  52. package/cli/native-lock.js +0 -0
  53. package/cli/oauth-credentials.js +0 -0
  54. package/cli/oauth-flow.js +0 -0
  55. package/cli/placement-projection.js +106 -0
  56. package/cli/platforms.js +0 -0
  57. package/cli/prompt.js +25 -0
  58. package/cli/public-http.js +168 -0
  59. package/cli/receipt-performance.js +0 -0
  60. package/cli/receipts.js +0 -0
  61. package/cli/report.js +51 -19
  62. package/cli/setup-plan.js +0 -0
  63. package/cli/skill.js +0 -0
  64. package/cli/state.js +76 -29
  65. package/cli/status.js +14 -18
  66. package/cli/sync-lock.js +0 -0
  67. package/cli/sync.js +23 -4
  68. package/package.json +7 -8
  69. package/references/citation-panels.md +2 -0
  70. package/references/cli.md +2 -2
  71. package/references/disavow.md +1 -1
  72. package/references/discovery-handoff.md +1 -1
  73. package/references/local-crm-draft-handoff.md +1 -1
  74. package/references/portable-ledger-handoff.md +11 -10
  75. package/references/recipes/catalog.json +263 -7
  76. package/references/recipes/command-notes.md +5 -3
  77. package/references/recipes/internal-link-improvement.md +25 -0
  78. package/references/recipes/placement-reconciliation.md +53 -1
  79. package/references/recipes/qualified-campaign-handoff.md +0 -0
  80. package/references/recipes/site-context-brief.md +0 -0
  81. package/references/recipes/sourced-linkable-asset.md +0 -0
  82. package/references/reports.md +0 -0
  83. package/scripts/agentlinkops.py +0 -0
  84. package/scripts/disavow.py +0 -0
  85. package/scripts/linktrail.py +0 -0
  86. package/scripts/migrations/001_crm.sql +0 -0
  87. package/scripts/migrations/002_cloud_events.sql +0 -0
  88. package/scripts/migrations/003_disavow.sql +0 -0
  89. package/scripts/migrations/004_placement_costs.sql +0 -0
  90. package/scripts/outreach-crm-draft.py +0 -0
  91. package/scripts/portable-handoff.py +17 -4
  92. package/scripts/report.py +0 -0
  93. package/shared/admission-contract.js +3 -2
  94. package/shared/admission-report-contract.js +3 -2
  95. package/shared/agent-clients.js +0 -0
  96. package/shared/analytics-history-contract.js +15 -0
  97. package/shared/campaign-outcome-contract.js +12 -0
  98. package/shared/campaign-prospect-contract.js +23 -0
  99. package/shared/citation-evidence-contract.js +0 -0
  100. package/shared/classification-contract.js +54 -0
  101. package/shared/contact-record-contract.js +27 -0
  102. package/shared/disavow-contract.js +37 -9
  103. package/shared/exec-grammar.js +0 -0
  104. package/shared/index-observation-contract.js +5 -4
  105. package/shared/internal-links-contract.js +143 -0
  106. package/shared/lifecycle-contract.js +0 -0
  107. package/shared/lifecycle-money.js +0 -0
  108. package/shared/link-category-contract.js +35 -0
  109. package/shared/link-category.js +138 -0
  110. package/shared/link-labels.js +26 -0
  111. package/shared/link-summary-contract.js +17 -0
  112. package/shared/monitoring-chronology-export.js +57 -0
  113. package/shared/monitoring-read-contract.js +41 -0
  114. package/shared/observation-event.js +1 -1
  115. package/shared/onboarding.js +20 -0
  116. package/shared/opportunity-record.js +0 -0
  117. package/shared/response-contract.js +41 -5
  118. package/shared/search-connection-contract.js +2 -1
  119. package/shared/table-page-contract.js +13 -0
  120. package/skills/agentlinkops-assets/SKILL-DEPS.json +290 -0
  121. package/skills/agentlinkops-assets/SKILL.md +3 -1
  122. package/skills/agentlinkops-assets/agents/openai.yaml +1 -1
  123. package/skills/agentlinkops-assets/references/portable/references/cli.md +220 -0
  124. package/skills/agentlinkops-assets/references/portable/references/disavow.md +73 -0
  125. package/skills/agentlinkops-assets/references/portable/references/reports.md +53 -0
  126. package/skills/agentlinkops-assets/references/portable/scripts/agentlinkops.py +676 -0
  127. package/skills/agentlinkops-assets/references/portable/scripts/disavow.py +207 -0
  128. package/skills/agentlinkops-assets/references/portable/scripts/migrations/001_crm.sql +45 -0
  129. package/skills/agentlinkops-assets/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  130. package/skills/agentlinkops-assets/references/portable/scripts/migrations/003_disavow.sql +20 -0
  131. package/skills/agentlinkops-assets/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  132. package/skills/agentlinkops-assets/references/portable/scripts/report.py +113 -0
  133. package/skills/agentlinkops-assets/references/portable/templates/campaigns.v1.json +65 -0
  134. package/skills/agentlinkops-campaigns/SKILL-DEPS.json +336 -0
  135. package/skills/agentlinkops-campaigns/SKILL.md +7 -5
  136. package/skills/agentlinkops-campaigns/agents/openai.yaml +1 -1
  137. package/skills/agentlinkops-campaigns/references/first-campaign.md +31 -0
  138. package/skills/agentlinkops-campaigns/references/mailbox-campaign.md +46 -0
  139. package/skills/agentlinkops-campaigns/references/portable/references/cli.md +220 -0
  140. package/skills/agentlinkops-campaigns/references/portable/references/disavow.md +73 -0
  141. package/skills/agentlinkops-campaigns/references/portable/references/reports.md +53 -0
  142. package/skills/agentlinkops-campaigns/references/portable/scripts/agentlinkops.py +676 -0
  143. package/skills/agentlinkops-campaigns/references/portable/scripts/disavow.py +207 -0
  144. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/001_crm.sql +45 -0
  145. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  146. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/003_disavow.sql +20 -0
  147. package/skills/agentlinkops-campaigns/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  148. package/skills/agentlinkops-campaigns/references/portable/scripts/report.py +113 -0
  149. package/skills/agentlinkops-campaigns/references/portable/templates/campaigns.v1.json +65 -0
  150. package/skills/agentlinkops-connect/SKILL-DEPS.json +104 -0
  151. package/skills/agentlinkops-connect/SKILL.md +2 -0
  152. package/skills/agentlinkops-connect/agents/openai.yaml +1 -1
  153. package/skills/agentlinkops-connect/references/agentlinkops.md +164 -61
  154. package/skills/agentlinkops-connect/references/placement-workflows.md +19 -0
  155. package/skills/agentlinkops-crm/SKILL-DEPS.json +381 -0
  156. package/skills/agentlinkops-crm/SKILL.md +26 -9
  157. package/skills/agentlinkops-crm/agents/openai.yaml +1 -1
  158. package/skills/agentlinkops-crm/references/portable/references/cli.md +220 -0
  159. package/skills/agentlinkops-crm/references/portable/references/disavow.md +73 -0
  160. package/skills/agentlinkops-crm/references/portable/references/local-crm-draft-handoff.md +76 -0
  161. package/skills/agentlinkops-crm/references/portable/references/portable-ledger-handoff.md +51 -0
  162. package/skills/agentlinkops-crm/references/portable/references/reports.md +53 -0
  163. package/skills/agentlinkops-crm/references/portable/scripts/agentlinkops.py +676 -0
  164. package/skills/agentlinkops-crm/references/portable/scripts/disavow.py +207 -0
  165. package/skills/agentlinkops-crm/references/portable/scripts/migrations/001_crm.sql +45 -0
  166. package/skills/agentlinkops-crm/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  167. package/skills/agentlinkops-crm/references/portable/scripts/migrations/003_disavow.sql +20 -0
  168. package/skills/agentlinkops-crm/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  169. package/skills/agentlinkops-crm/references/portable/scripts/outreach-crm-draft.py +121 -0
  170. package/skills/agentlinkops-crm/references/portable/scripts/portable-handoff.py +197 -0
  171. package/skills/agentlinkops-crm/references/portable/scripts/report.py +113 -0
  172. package/skills/agentlinkops-crm/references/portable/templates/campaigns.v1.json +65 -0
  173. package/skills/agentlinkops-discovery/SKILL-DEPS.json +301 -0
  174. package/skills/agentlinkops-discovery/SKILL.md +4 -2
  175. package/skills/agentlinkops-discovery/references/portable/references/cli.md +220 -0
  176. package/skills/agentlinkops-discovery/references/portable/references/disavow.md +73 -0
  177. package/skills/agentlinkops-discovery/references/portable/references/discovery-handoff.md +32 -0
  178. package/skills/agentlinkops-discovery/references/portable/references/reports.md +53 -0
  179. package/skills/agentlinkops-discovery/references/portable/scripts/agentlinkops.py +676 -0
  180. package/skills/agentlinkops-discovery/references/portable/scripts/disavow.py +207 -0
  181. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/001_crm.sql +45 -0
  182. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/002_cloud_events.sql +13 -0
  183. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/003_disavow.sql +20 -0
  184. package/skills/agentlinkops-discovery/references/portable/scripts/migrations/004_placement_costs.sql +2 -0
  185. package/skills/agentlinkops-discovery/references/portable/scripts/report.py +113 -0
  186. package/skills/agentlinkops-discovery/references/portable/templates/campaigns.v1.json +65 -0
  187. package/skills/agentlinkops-discovery/references/saved-backlink-workflows.md +23 -0
  188. package/src/citations/adapter.js +0 -0
  189. package/src/citations/alert.js +0 -0
  190. package/src/citations/browser/chatgpt-citations.js +0 -0
  191. package/src/citations/browser/engine.js +0 -0
  192. package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +0 -0
  193. package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +0 -0
  194. package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +0 -0
  195. package/src/citations/browser/gen/core/providers/ai-overview/index.js +0 -0
  196. package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +0 -0
  197. package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +0 -0
  198. package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +0 -0
  199. package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +0 -0
  200. package/src/citations/browser/gen/core/providers/chatgpt/index.js +0 -0
  201. package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +0 -0
  202. package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +0 -0
  203. package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +0 -0
  204. package/src/citations/browser/gen/core/providers/claude/index.js +0 -0
  205. package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +0 -0
  206. package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +0 -0
  207. package/src/citations/browser/gen/core/providers/gemini/index.js +0 -0
  208. package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +0 -0
  209. package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +0 -0
  210. package/src/citations/browser/gen/core/providers/gemini/lib/session.js +0 -0
  211. package/src/citations/browser/gen/core/providers/index.js +0 -0
  212. package/src/citations/browser/gen/core/providers/perplexity/index.js +0 -0
  213. package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +0 -0
  214. package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +0 -0
  215. package/src/citations/browser/gen/lib/browser/camoufox.js +0 -0
  216. package/src/citations/browser/gen/lib/browser/display.js +0 -0
  217. package/src/citations/browser/gen/lib/browser/domOps.js +0 -0
  218. package/src/citations/browser/gen/lib/browser/humanBehavior.js +0 -0
  219. package/src/citations/browser/gen/lib/browser/navigate.js +0 -0
  220. package/src/citations/browser/gen/lib/extraction/sourceUtils.js +0 -0
  221. package/src/citations/browser/gen/lib/input/editor/clearInput.js +0 -0
  222. package/src/citations/browser/gen/lib/input/editor/findEditor.js +0 -0
  223. package/src/citations/browser/gen/lib/input/editor/promptInput.js +0 -0
  224. package/src/citations/browser/gen/lib/input/markdown/converter.js +0 -0
  225. package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +0 -0
  226. package/src/citations/browser/gen/lib/input/response/detectBotPage.js +0 -0
  227. package/src/citations/browser/gen/lib/input/response/isGenerating.js +0 -0
  228. package/src/citations/browser/gen/lib/input/response/waitForFinish.js +0 -0
  229. package/src/citations/browser/gen/lib/input/sources/findButton.js +0 -0
  230. package/src/citations/browser/gen/lib/input/sources/openPanel.js +0 -0
  231. package/src/citations/browser/gen/utils/agent-constants.js +0 -0
  232. package/src/citations/browser/providers.js +0 -0
  233. package/src/citations/browser/proxy-admission.js +0 -0
  234. package/src/citations/browser/shims/errors.js +0 -0
  235. package/src/citations/browser/shims/types.js +0 -0
  236. package/src/citations/browser/shims/utils.js +0 -0
  237. package/src/citations/contract.js +0 -0
  238. package/src/citations/egress-receipts.js +0 -0
  239. package/src/citations/evidence-integrity.js +0 -0
  240. package/src/citations/inventory.js +0 -0
  241. package/src/citations/login.js +0 -0
  242. package/src/citations/match.js +0 -0
  243. package/src/citations/panel-builder.js +0 -0
  244. package/src/citations/report.js +0 -0
  245. package/src/citations/runner.js +0 -0
  246. package/src/citations/stats.js +0 -0
  247. package/src/citations/sync.js +0 -0
  248. package/src/citations/watch.js +0 -0
  249. package/src/competitors/contract.js +17 -1
  250. package/src/competitors/domain-mix.js +0 -0
  251. package/src/context/ga4.js +0 -0
  252. package/src/context/gsc.js +0 -0
  253. package/src/context/site-profile.js +34 -121
  254. package/src/context/util.js +0 -0
  255. package/src/context-tool-registry.js +0 -0
  256. package/src/corpus/acquire.js +68 -32
  257. package/src/corpus/extract.js +147 -0
  258. package/src/corpus/frontier.js +151 -0
  259. package/src/corpus/seeds.js +0 -0
  260. package/src/discovery/contract.js +8 -0
  261. package/src/discovery/import.js +13 -1
  262. package/src/evidence-reference.js +0 -0
  263. package/src/index-observation.js +0 -0
  264. package/src/locator.js +0 -0
  265. package/src/mentions.js +193 -0
  266. package/src/page-acquisition/acquire.js +50 -0
  267. package/src/page-acquisition/bytes.js +138 -0
  268. package/src/page-acquisition/capture.js +11 -0
  269. package/src/page-acquisition/contracts.js +67 -0
  270. package/src/page-acquisition/documents.js +166 -0
  271. package/src/page-acquisition/http.js +307 -0
  272. package/src/page-acquisition/policy.js +20 -0
  273. package/src/page-acquisition/recovery.js +164 -0
  274. package/src/page-acquisition/render-failure.js +82 -0
  275. package/src/page-extraction/html.js +359 -0
  276. package/src/page-extraction/index.js +30 -0
  277. package/src/page-extraction/metadata.js +16 -0
  278. package/src/response-shaping.js +4 -3
  279. package/src/tool-views.js +23 -12
  280. package/src/verifier/fetch.js +4 -339
  281. package/src/verifier/index.js +81 -23
  282. package/src/verifier/observation-kind.js +0 -0
  283. package/src/verifier/page-quality.js +14 -0
  284. package/src/verifier/parser.js +2 -241
  285. package/src/verifier/readiness.js +64 -13
  286. package/src/verifier/render-evidence.js +25 -2
  287. package/src/verifier/robots.js +0 -0
  288. package/src/verifier/target.js +201 -0
  289. package/src/verifier/transition.js +18 -3
  290. package/src/verifier/url.js +31 -0
  291. package/src/watch-crawlability.js +144 -0
  292. package/templates/SITE.template.md +0 -0
  293. package/templates/campaigns.v1.json +0 -0
  294. package/skills/content-defingerprinting/SKILL.md +0 -29
  295. package/skills/content-defingerprinting/scripts/watermarks/README.md +0 -102
  296. package/skills/content-defingerprinting/scripts/watermarks/UPSTREAM.json +0 -26
  297. package/skills/content-defingerprinting/scripts/watermarks/revendor.sh +0 -52
  298. package/skills/content-defingerprinting/scripts/watermarks/vendor/LICENSE.upstream +0 -21
  299. package/skills/content-defingerprinting/scripts/watermarks/vendor/clean_text.py +0 -96
  300. package/skills/content-defingerprinting/scripts/watermarks/vendor/common.py +0 -432
  301. package/skills/content-defingerprinting/scripts/watermarks/vendor/inspect_text.py +0 -86
  302. package/skills/content-defingerprinting/scripts/watermarks/vendor/score_stylometry.py +0 -460
  303. package/skills/content-defingerprinting/scripts/watermarks/vendor/text_unicode.py +0 -651
  304. package/skills/content-defingerprinting/scripts/watermarks/verify-vendor.py +0 -27
  305. package/skills/content-defingerprinting/scripts/watermarks/wm-scan.py +0 -212
  306. package/skills/internal-linking-optimizer/SKILL.md +0 -56
  307. package/skills/internal-linking-optimizer/references/source-prompt.md +0 -76
  308. package/skills/pre-publish-review/SKILL.md +0 -179
  309. package/skills/source-cited-content-builder/SKILL.md +0 -56
  310. package/skills/source-cited-content-builder/references/source-prompt.md +0 -19
  311. package/skills/source-cited-humanizer/SKILL.md +0 -20
@@ -1,102 +0,0 @@
1
- # Stage 2b — the invisible-character scan
2
-
3
- **Required on every page, every time, before it ships.** It is part of stage 2
4
- (humanize), and it is the half of stage 2 that a human reviewer physically cannot do.
5
-
6
- ## What it is
7
-
8
- A vendored copy of the Layer A engine from
9
- [watermarks-remover](https://github.com/guillaumemeyer/watermarks-remover) (MIT),
10
- pinned in `UPSTREAM.json`, plus `wm-scan.py`, our wrapper that walks content roots
11
- and reports line numbers.
12
-
13
- ```bash
14
- python3 wm-scan.py <path> [<path> ...] # report, exit 1 on findings
15
- python3 wm-scan.py --fix <path> # clean in place, then re-verify
16
- python3 wm-scan.py --json <path> # for a gate
17
- python3 verify-vendor.py # prove the engine is the pinned upstream one
18
- ./revendor.sh [ref] # refresh from upstream and re-pin
19
- ```
20
-
21
- ## Why it is required rather than recommended
22
-
23
- Because it catches a defect class that survives every other check we have. These
24
- characters are invisible in the editor, invisible in review, invisible in the
25
- rendered page, and they ride in on ordinary copy-paste from PDFs, Word and model
26
- output. What they break is everything that reads our prose as text:
27
-
28
- **A zero-width space inside "affidavit" means the banned-phrase scan, the citation
29
- audits and every `grep` in the repo stop seeing the word.** The page looks fine and
30
- the checks that are supposed to guard it have quietly stopped applying.
31
-
32
- It is not a theoretical risk. First run across the estate, tax-sale, trucking,
33
- coverage and proxy repos, 2026-08-18:
34
-
35
- | repo | finding |
36
- |---|---|
37
- | settled | a soft hyphen inside `affi[U+00AD]davit` in rendered prose, plus 4 more in Missouri state data |
38
- | Tax Sale Atlas | Wingdings/PUA glyphs (`U+F0B7`, `U+F073`) in 5 counties' `sale.json`, including inside an `evidence` string |
39
- | covered-weight | 1,200+ PUA glyphs across the archived payer-policy extractions |
40
- | haulhandbook, proxy-compare | clean |
41
-
42
- ## The archive rule, which is not negotiable
43
-
44
- **`--fix` refuses to touch anything under `sources/`, `snapshots/`, `evidence/`,
45
- `_sources/`, `price-history/` or `raw/`.**
46
-
47
- That tree is the evidence every `_source` and `verified_quote` resolves against. The
48
- marks in it are glyph artifacts of a document *somebody else published*, and they are
49
- part of what we recorded. Rewriting the archive so a scan goes green is precisely the
50
- tampering the provenance chain exists to detect — one edited file and one re-forged
51
- hash is the whole covered-weight story.
52
-
53
- So: **the archive is reported and left alone. The QUOTE lifted out of it is the thing
54
- to check.** A hit inside a quote field is a provenance question before it is a
55
- formatting one: open the source, see what the character is, and decide. A PDF bullet
56
- glyph rendered into a quote is noise the reader never saw and can be dropped; a real
57
- character cannot.
58
-
59
- ## The load-bearing rule (found by auditing, 2026-08-18)
60
-
61
- **`--fix` also holds back any file where the mark sits on a line carrying regex-replacement
62
- syntax**, tagging it `[LOAD-BEARING? regex literal on that line - review by hand]`.
63
-
64
- The estate-wide audit found three of these, and every one is a **sanitizer written with the
65
- literal characters it strips**:
66
-
67
- ```js
68
- .replace(/[<zwsp><zwnj><zwj><bom>]+/g, '') covered-weight extract-ehb-chart-rows.mjs:127
69
- .replace(/&nbsp;| |<zwsp>/g, " ") proxy-compare scraper/vendors/proxyscrape.ts:112
70
- .replace(/<nbsp>/g, " ") proxy-compare scraper/vendors/rayobyte.ts:67
71
- ```
72
-
73
- Auto-cleaning those empties the character class and silently breaks the guard, letting through
74
- exactly the contamination it existed to catch. **A blanket `--fix` across the estate would have
75
- broken three sanitizers in two repos** — the scanner would have disabled its own counterparts.
76
-
77
- This is the argument for classify-before-fix in one example. A hit is not a defect; a hit is a
78
- question. The three answers that mean "do not touch" are: it is archived evidence, it is a
79
- sourced quote, or it is load-bearing.
80
-
81
- ## What we deliberately do NOT run
82
-
83
- The upstream project does more than this, and the rest of it is off by decision, not
84
- by oversight.
85
-
86
- | layer | status | why |
87
- |---|---|---|
88
- | **A** — invisible Unicode, exotic spaces | **required** | pure contamination, deterministic, zero judgement needed |
89
- | **Files** — EXIF / XMP on our own generated covers | available | harmless hygiene; our covers are generated by our own script, so there is little to strip |
90
- | **C2PA manifests** | **detect, never strip** | C2PA is a provenance record. On one of our own covers it is a signal that something upstream generated an image with AI — on sites that say "never an AI image", that is a question to answer, not noise to delete |
91
- | **B** — rewriting text to defeat statistical watermarks | **opt-in, off** | our humanize stage already rewrites AI-pattern prose on quality grounds. Making "defeat provenance watermarking" an explicit standing step across five YMYL properties is a decision for the owner, not a default |
92
- | **Pixels** — CtrlRegen image watermark removal | **not installed** | needs a model backend, and it has no use on sites that generate their own covers. Its actual use is stripping watermarks off images we did not make |
93
-
94
- If any of those need turning on, that is a conversation, and the reason belongs in
95
- this file next to the change.
96
-
97
- ## Keeping it honest
98
-
99
- `vendor/` is byte-identical to upstream and must stay that way — `verify-vendor.py`
100
- proves it, and `revendor.sh` is how it changes. Do not edit it in place. The last time
101
- a copied source of truth got edited locally in this estate it became a house rule
102
- nobody agreed to, sitting in a file that looked authoritative.
@@ -1,26 +0,0 @@
1
- {
2
- "upstream": "https://github.com/guillaumemeyer/watermarks-remover",
3
- "license": "MIT",
4
- "pinned_commit": "8318d4df79de18a8c18c21eecb4b796192f34805",
5
- "pinned_commit_date": "2026-08-18T18:28:19-07:00",
6
- "vendored_on": "2026-08-18",
7
- "vendored_from": "service/scripts/",
8
- "modified": false,
9
- "layers_vendored": [
10
- "A: invisible Unicode / space homoglyphs (deterministic)"
11
- ],
12
- "layers_deliberately_not_vendored": [
13
- "B: statistical token-sampling watermark removal by rewriting (opt-in, needs a decision)",
14
- "Files: C2PA / EXIF / XMP stripping (we DETECT and report C2PA, we do not strip it)",
15
- "Pixels: CtrlRegen image watermark removal (not used; our covers are our own generated assets)"
16
- ],
17
- "note": "Files are byte-identical to upstream. Re-vendor with revendor.sh; never edit in place.",
18
- "sha256": {
19
- "LICENSE.upstream": "c70d55b5722484a6be261f755b5bdfd5335244d0389436eb116aa8357243347c",
20
- "clean_text.py": "8cde7a3c75c06687f7403f85364eda3380ace4c0ab5e5eb7d1f89f9e0c50ce04",
21
- "common.py": "311d78fb23351d56b9a2601a7af3b8d5bf834e2e55289796e1f18184a3b9f49d",
22
- "inspect_text.py": "a61e5c524f72de8122750114f09973cec826641d798f417d811c7e5649218479",
23
- "score_stylometry.py": "cd3dae134d5f641120218fd525afd523d4a0bfb52d4d73c0e92dc04e3bcc6d59",
24
- "text_unicode.py": "cd21c5700184a6bb4a43089f546531a2692bf140413954ea9b93f4a67f1f3a5e"
25
- }
26
- }
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Re-vendor the watermarks-remover Layer A engine from upstream.
3
- #
4
- # ./revendor.sh [ref] # default: main
5
- #
6
- # The vendored files are byte-identical to upstream and MUST stay that way. If a
7
- # behaviour needs changing, change it in wm-scan.py (ours) or send a PR upstream.
8
- # Editing vendor/ in place produces a house rule nobody agreed to, in a file that
9
- # looks like somebody else's source of truth. That has happened here before with a
10
- # banned-phrase list.
11
- set -euo pipefail
12
-
13
- REF="${1:-main}"
14
- HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
- REPO="https://github.com/guillaumemeyer/watermarks-remover"
16
- FILES=(common.py text_unicode.py inspect_text.py clean_text.py score_stylometry.py)
17
- TMP="$(mktemp -d)"
18
- trap 'rm -rf "$TMP"' EXIT
19
-
20
- echo "Fetching $REPO @ $REF ..."
21
- git clone --quiet --depth 1 --branch "$REF" "$REPO" "$TMP/src" 2>/dev/null \
22
- || git clone --quiet "$REPO" "$TMP/src"
23
- git -C "$TMP/src" checkout --quiet "$REF" 2>/dev/null || true
24
-
25
- for f in "${FILES[@]}"; do
26
- cp "$TMP/src/service/scripts/$f" "$HERE/vendor/$f"
27
- done
28
- cp "$TMP/src/LICENSE" "$HERE/vendor/LICENSE.upstream"
29
-
30
- SHA="$(git -C "$TMP/src" rev-parse HEAD)"
31
- DATE="$(git -C "$TMP/src" log -1 --format=%cI)"
32
- TODAY="$(date +%Y-%m-%d)"
33
-
34
- python3 - "$HERE" "$SHA" "$DATE" "$TODAY" <<'PY'
35
- import hashlib, io, json, os, sys
36
- here, sha, date, today = sys.argv[1:5]
37
- m = json.load(io.open(os.path.join(here, 'UPSTREAM.json'), encoding='utf-8'))
38
- files = {}
39
- for f in sorted(os.listdir(os.path.join(here, 'vendor'))):
40
- if f.startswith('._'): # AppleDouble shadows on the exFAT volumes
41
- continue
42
- files[f] = hashlib.sha256(io.open(os.path.join(here, 'vendor', f), 'rb').read()).hexdigest()
43
- changed = [f for f, h in files.items() if m['sha256'].get(f) != h]
44
- m.update(pinned_commit=sha, pinned_commit_date=date, vendored_on=today, sha256=files)
45
- io.open(os.path.join(here, 'UPSTREAM.json'), 'w', encoding='utf-8').write(json.dumps(m, indent=2) + "\n")
46
- print("pinned %s (%s)" % (sha[:12], date))
47
- print("changed: %s" % (", ".join(changed) if changed else "nothing"))
48
- PY
49
-
50
- echo
51
- echo "Now re-run the scan on a repo you know the answer for, before trusting it:"
52
- echo " python3 $HERE/wm-scan.py <a path with a known hit>"
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 watermarks-remover contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Strip invisible Unicode / normalize space homoglyphs (Layer A)."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import json
8
- import sys
9
- from pathlib import Path
10
-
11
- sys.path.insert(0, str(Path(__file__).resolve().parent))
12
-
13
- from common import (
14
- backup_path,
15
- cleaned_path,
16
- eprint,
17
- read_text_input,
18
- write_text_output,
19
- )
20
- from text_unicode import clean_text
21
-
22
-
23
- def main() -> int:
24
- p = argparse.ArgumentParser(description=__doc__)
25
- p.add_argument("path", nargs="?", default="-", help="Input text file, or - for stdin")
26
- p.add_argument("-o", "--output", help="Output path (default: stdout or *.cleaned.*)")
27
- p.add_argument("--nfkc", action="store_true", help="Apply Unicode NFKC after scrub")
28
- p.add_argument(
29
- "--aggressive-homoglyphs",
30
- action="store_true",
31
- help="Map Cyrillic/fullwidth Latin confusables to ASCII Latin",
32
- )
33
- p.add_argument(
34
- "--no-normalize-spaces",
35
- action="store_true",
36
- help="Do not rewrite exotic spaces to U+0020",
37
- )
38
- p.add_argument(
39
- "--strip-emoji-glue",
40
- action="store_true",
41
- help="Paranoid: strip all load-bearing invisibles too (emoji glue, script joiners, flag tags, same-script fillers/selectors, orthographic Cf)",
42
- )
43
- p.add_argument(
44
- "--strip-bidi",
45
- action="store_true",
46
- help="Also strip legitimate RTL/LTR directional marks and isolates",
47
- )
48
- p.add_argument("--stats", action="store_true", help="Print stats JSON to stderr")
49
- p.add_argument(
50
- "--force-text",
51
- action="store_true",
52
- help="Clean even when the input looks like a binary container "
53
- "(this rewrites the bytes and will corrupt the file)",
54
- )
55
- p.add_argument(
56
- "--in-place",
57
- action="store_true",
58
- help="Overwrite input file (creates .bak backup)",
59
- )
60
- args = p.parse_args()
61
-
62
- text = read_text_input(args.path, allow_binary=args.force_text)
63
- cleaned, stats = clean_text(
64
- text,
65
- nfkc=args.nfkc,
66
- aggressive_homoglyphs=args.aggressive_homoglyphs,
67
- normalize_spaces=not args.no_normalize_spaces,
68
- strip_emoji_glue=args.strip_emoji_glue,
69
- strip_bidi=args.strip_bidi,
70
- )
71
-
72
- out = args.output
73
- if args.in_place:
74
- if args.path in (None, "-"):
75
- eprint("--in-place requires a file path")
76
- return 2
77
- src = Path(args.path)
78
- backup_path(src) # side effect: keep a .bak of the original before overwriting
79
- out = str(src)
80
- elif out is None and args.path not in (None, "-"):
81
- out = str(cleaned_path(Path(args.path)))
82
-
83
- write_text_output(cleaned, out)
84
-
85
- if args.stats:
86
- eprint(json.dumps(stats, indent=2, ensure_ascii=False))
87
- else:
88
- eprint(
89
- f"removed={stats['removed_count']} replaced={stats['replaced_count']} "
90
- f"len {stats['input_length']}->{stats['output_length']}"
91
- )
92
- return 0
93
-
94
-
95
- if __name__ == "__main__":
96
- raise SystemExit(main())