@brandsystem/mcp 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +515 -0
  2. package/bin/brandsystem-mcp.mjs +2 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +20 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/brand-dir.d.ts +56 -0
  8. package/dist/lib/brand-dir.d.ts.map +1 -0
  9. package/dist/lib/brand-dir.js +270 -0
  10. package/dist/lib/brand-dir.js.map +1 -0
  11. package/dist/lib/color-namer.d.ts +28 -0
  12. package/dist/lib/color-namer.d.ts.map +1 -0
  13. package/dist/lib/color-namer.js +155 -0
  14. package/dist/lib/color-namer.js.map +1 -0
  15. package/dist/lib/confidence.d.ts +19 -0
  16. package/dist/lib/confidence.d.ts.map +1 -0
  17. package/dist/lib/confidence.js +66 -0
  18. package/dist/lib/confidence.js.map +1 -0
  19. package/dist/lib/content-scorer.d.ts +38 -0
  20. package/dist/lib/content-scorer.d.ts.map +1 -0
  21. package/dist/lib/content-scorer.js +571 -0
  22. package/dist/lib/content-scorer.js.map +1 -0
  23. package/dist/lib/css-parser.d.ts +45 -0
  24. package/dist/lib/css-parser.d.ts.map +1 -0
  25. package/dist/lib/css-parser.js +330 -0
  26. package/dist/lib/css-parser.js.map +1 -0
  27. package/dist/lib/dtcg-compiler.d.ts +7 -0
  28. package/dist/lib/dtcg-compiler.d.ts.map +1 -0
  29. package/dist/lib/dtcg-compiler.js +89 -0
  30. package/dist/lib/dtcg-compiler.js.map +1 -0
  31. package/dist/lib/interaction-policy-compiler.d.ts +40 -0
  32. package/dist/lib/interaction-policy-compiler.d.ts.map +1 -0
  33. package/dist/lib/interaction-policy-compiler.js +60 -0
  34. package/dist/lib/interaction-policy-compiler.js.map +1 -0
  35. package/dist/lib/logo-extractor.d.ts +49 -0
  36. package/dist/lib/logo-extractor.d.ts.map +1 -0
  37. package/dist/lib/logo-extractor.js +384 -0
  38. package/dist/lib/logo-extractor.js.map +1 -0
  39. package/dist/lib/report-html.d.ts +20 -0
  40. package/dist/lib/report-html.d.ts.map +1 -0
  41. package/dist/lib/report-html.js +938 -0
  42. package/dist/lib/report-html.js.map +1 -0
  43. package/dist/lib/response.d.ts +20 -0
  44. package/dist/lib/response.d.ts.map +1 -0
  45. package/dist/lib/response.js +54 -0
  46. package/dist/lib/response.js.map +1 -0
  47. package/dist/lib/runtime-compiler.d.ts +60 -0
  48. package/dist/lib/runtime-compiler.d.ts.map +1 -0
  49. package/dist/lib/runtime-compiler.js +96 -0
  50. package/dist/lib/runtime-compiler.js.map +1 -0
  51. package/dist/lib/svg-resolver.d.ts +21 -0
  52. package/dist/lib/svg-resolver.d.ts.map +1 -0
  53. package/dist/lib/svg-resolver.js +115 -0
  54. package/dist/lib/svg-resolver.js.map +1 -0
  55. package/dist/lib/url-validator.d.ts +11 -0
  56. package/dist/lib/url-validator.d.ts.map +1 -0
  57. package/dist/lib/url-validator.js +93 -0
  58. package/dist/lib/url-validator.js.map +1 -0
  59. package/dist/lib/version.d.ts +2 -0
  60. package/dist/lib/version.d.ts.map +1 -0
  61. package/dist/lib/version.js +19 -0
  62. package/dist/lib/version.js.map +1 -0
  63. package/dist/lib/vim-generator.d.ts +13 -0
  64. package/dist/lib/vim-generator.d.ts.map +1 -0
  65. package/dist/lib/vim-generator.js +718 -0
  66. package/dist/lib/vim-generator.js.map +1 -0
  67. package/dist/resources/brand-resources.d.ts +4 -0
  68. package/dist/resources/brand-resources.d.ts.map +1 -0
  69. package/dist/resources/brand-resources.js +34 -0
  70. package/dist/resources/brand-resources.js.map +1 -0
  71. package/dist/schemas/brand-config.d.ts +28 -0
  72. package/dist/schemas/brand-config.d.ts.map +1 -0
  73. package/dist/schemas/brand-config.js +11 -0
  74. package/dist/schemas/brand-config.js.map +1 -0
  75. package/dist/schemas/brand-runtime.d.ts +251 -0
  76. package/dist/schemas/brand-runtime.d.ts.map +1 -0
  77. package/dist/schemas/brand-runtime.js +54 -0
  78. package/dist/schemas/brand-runtime.js.map +1 -0
  79. package/dist/schemas/core-identity.d.ts +302 -0
  80. package/dist/schemas/core-identity.d.ts.map +1 -0
  81. package/dist/schemas/core-identity.js +51 -0
  82. package/dist/schemas/core-identity.js.map +1 -0
  83. package/dist/schemas/index.d.ts +11 -0
  84. package/dist/schemas/index.d.ts.map +1 -0
  85. package/dist/schemas/index.js +11 -0
  86. package/dist/schemas/index.js.map +1 -0
  87. package/dist/schemas/interaction-policy.d.ts +150 -0
  88. package/dist/schemas/interaction-policy.d.ts.map +1 -0
  89. package/dist/schemas/interaction-policy.js +34 -0
  90. package/dist/schemas/interaction-policy.js.map +1 -0
  91. package/dist/schemas/messaging.d.ts +776 -0
  92. package/dist/schemas/messaging.d.ts.map +1 -0
  93. package/dist/schemas/messaging.js +68 -0
  94. package/dist/schemas/messaging.js.map +1 -0
  95. package/dist/schemas/needs-clarification.d.ts +62 -0
  96. package/dist/schemas/needs-clarification.d.ts.map +1 -0
  97. package/dist/schemas/needs-clarification.js +13 -0
  98. package/dist/schemas/needs-clarification.js.map +1 -0
  99. package/dist/schemas/strategy.d.ts +537 -0
  100. package/dist/schemas/strategy.d.ts.map +1 -0
  101. package/dist/schemas/strategy.js +71 -0
  102. package/dist/schemas/strategy.js.map +1 -0
  103. package/dist/schemas/tokens.d.ts +35 -0
  104. package/dist/schemas/tokens.d.ts.map +1 -0
  105. package/dist/schemas/tokens.js +15 -0
  106. package/dist/schemas/tokens.js.map +1 -0
  107. package/dist/schemas/visual-identity.d.ts +224 -0
  108. package/dist/schemas/visual-identity.d.ts.map +1 -0
  109. package/dist/schemas/visual-identity.js +42 -0
  110. package/dist/schemas/visual-identity.js.map +1 -0
  111. package/dist/server.d.ts +3 -0
  112. package/dist/server.d.ts.map +1 -0
  113. package/dist/server.js +75 -0
  114. package/dist/server.js.map +1 -0
  115. package/dist/tools/brand-audit-content.d.ts +3 -0
  116. package/dist/tools/brand-audit-content.d.ts.map +1 -0
  117. package/dist/tools/brand-audit-content.js +116 -0
  118. package/dist/tools/brand-audit-content.js.map +1 -0
  119. package/dist/tools/brand-audit-drift.d.ts +3 -0
  120. package/dist/tools/brand-audit-drift.d.ts.map +1 -0
  121. package/dist/tools/brand-audit-drift.js +301 -0
  122. package/dist/tools/brand-audit-drift.js.map +1 -0
  123. package/dist/tools/brand-audit.d.ts +3 -0
  124. package/dist/tools/brand-audit.d.ts.map +1 -0
  125. package/dist/tools/brand-audit.js +129 -0
  126. package/dist/tools/brand-audit.js.map +1 -0
  127. package/dist/tools/brand-build-journey.d.ts +3 -0
  128. package/dist/tools/brand-build-journey.d.ts.map +1 -0
  129. package/dist/tools/brand-build-journey.js +312 -0
  130. package/dist/tools/brand-build-journey.js.map +1 -0
  131. package/dist/tools/brand-build-matrix.d.ts +3 -0
  132. package/dist/tools/brand-build-matrix.d.ts.map +1 -0
  133. package/dist/tools/brand-build-matrix.js +525 -0
  134. package/dist/tools/brand-build-matrix.js.map +1 -0
  135. package/dist/tools/brand-build-personas.d.ts +3 -0
  136. package/dist/tools/brand-build-personas.d.ts.map +1 -0
  137. package/dist/tools/brand-build-personas.js +436 -0
  138. package/dist/tools/brand-build-personas.js.map +1 -0
  139. package/dist/tools/brand-build-themes.d.ts +3 -0
  140. package/dist/tools/brand-build-themes.d.ts.map +1 -0
  141. package/dist/tools/brand-build-themes.js +476 -0
  142. package/dist/tools/brand-build-themes.js.map +1 -0
  143. package/dist/tools/brand-check-compliance.d.ts +3 -0
  144. package/dist/tools/brand-check-compliance.d.ts.map +1 -0
  145. package/dist/tools/brand-check-compliance.js +243 -0
  146. package/dist/tools/brand-check-compliance.js.map +1 -0
  147. package/dist/tools/brand-clarify.d.ts +21 -0
  148. package/dist/tools/brand-clarify.d.ts.map +1 -0
  149. package/dist/tools/brand-clarify.js +497 -0
  150. package/dist/tools/brand-clarify.js.map +1 -0
  151. package/dist/tools/brand-compile-messaging.d.ts +3 -0
  152. package/dist/tools/brand-compile-messaging.d.ts.map +1 -0
  153. package/dist/tools/brand-compile-messaging.js +759 -0
  154. package/dist/tools/brand-compile-messaging.js.map +1 -0
  155. package/dist/tools/brand-compile.d.ts +3 -0
  156. package/dist/tools/brand-compile.d.ts.map +1 -0
  157. package/dist/tools/brand-compile.js +182 -0
  158. package/dist/tools/brand-compile.js.map +1 -0
  159. package/dist/tools/brand-deepen-identity.d.ts +3 -0
  160. package/dist/tools/brand-deepen-identity.d.ts.map +1 -0
  161. package/dist/tools/brand-deepen-identity.js +483 -0
  162. package/dist/tools/brand-deepen-identity.js.map +1 -0
  163. package/dist/tools/brand-export.d.ts +17 -0
  164. package/dist/tools/brand-export.d.ts.map +1 -0
  165. package/dist/tools/brand-export.js +730 -0
  166. package/dist/tools/brand-export.js.map +1 -0
  167. package/dist/tools/brand-extract-figma.d.ts +3 -0
  168. package/dist/tools/brand-extract-figma.d.ts.map +1 -0
  169. package/dist/tools/brand-extract-figma.js +174 -0
  170. package/dist/tools/brand-extract-figma.js.map +1 -0
  171. package/dist/tools/brand-extract-messaging.d.ts +3 -0
  172. package/dist/tools/brand-extract-messaging.d.ts.map +1 -0
  173. package/dist/tools/brand-extract-messaging.js +620 -0
  174. package/dist/tools/brand-extract-messaging.js.map +1 -0
  175. package/dist/tools/brand-extract-web.d.ts +3 -0
  176. package/dist/tools/brand-extract-web.d.ts.map +1 -0
  177. package/dist/tools/brand-extract-web.js +477 -0
  178. package/dist/tools/brand-extract-web.js.map +1 -0
  179. package/dist/tools/brand-feedback.d.ts +3 -0
  180. package/dist/tools/brand-feedback.d.ts.map +1 -0
  181. package/dist/tools/brand-feedback.js +366 -0
  182. package/dist/tools/brand-feedback.js.map +1 -0
  183. package/dist/tools/brand-ingest-assets.d.ts +3 -0
  184. package/dist/tools/brand-ingest-assets.d.ts.map +1 -0
  185. package/dist/tools/brand-ingest-assets.js +233 -0
  186. package/dist/tools/brand-ingest-assets.js.map +1 -0
  187. package/dist/tools/brand-init.d.ts +3 -0
  188. package/dist/tools/brand-init.d.ts.map +1 -0
  189. package/dist/tools/brand-init.js +66 -0
  190. package/dist/tools/brand-init.js.map +1 -0
  191. package/dist/tools/brand-preflight.d.ts +3 -0
  192. package/dist/tools/brand-preflight.d.ts.map +1 -0
  193. package/dist/tools/brand-preflight.js +608 -0
  194. package/dist/tools/brand-preflight.js.map +1 -0
  195. package/dist/tools/brand-report.d.ts +3 -0
  196. package/dist/tools/brand-report.d.ts.map +1 -0
  197. package/dist/tools/brand-report.js +154 -0
  198. package/dist/tools/brand-report.js.map +1 -0
  199. package/dist/tools/brand-runtime.d.ts +3 -0
  200. package/dist/tools/brand-runtime.d.ts.map +1 -0
  201. package/dist/tools/brand-runtime.js +37 -0
  202. package/dist/tools/brand-runtime.js.map +1 -0
  203. package/dist/tools/brand-set-logo.d.ts +3 -0
  204. package/dist/tools/brand-set-logo.d.ts.map +1 -0
  205. package/dist/tools/brand-set-logo.js +170 -0
  206. package/dist/tools/brand-set-logo.js.map +1 -0
  207. package/dist/tools/brand-start.d.ts +3 -0
  208. package/dist/tools/brand-start.d.ts.map +1 -0
  209. package/dist/tools/brand-start.js +686 -0
  210. package/dist/tools/brand-start.js.map +1 -0
  211. package/dist/tools/brand-status.d.ts +3 -0
  212. package/dist/tools/brand-status.d.ts.map +1 -0
  213. package/dist/tools/brand-status.js +175 -0
  214. package/dist/tools/brand-status.js.map +1 -0
  215. package/dist/tools/brand-write.d.ts +3 -0
  216. package/dist/tools/brand-write.d.ts.map +1 -0
  217. package/dist/tools/brand-write.js +442 -0
  218. package/dist/tools/brand-write.js.map +1 -0
  219. package/dist/types/index.d.ts +331 -0
  220. package/dist/types/index.d.ts.map +1 -0
  221. package/dist/types/index.js +52 -0
  222. package/dist/types/index.js.map +1 -0
  223. package/package.json +60 -0
@@ -0,0 +1,620 @@
1
+ import { z } from "zod";
2
+ import * as cheerio from "cheerio";
3
+ import { BrandDir } from "../lib/brand-dir.js";
4
+ import { buildResponse, safeParseParams } from "../lib/response.js";
5
+ import { getVersion } from "../lib/version.js";
6
+ import { ERROR_CODES } from "../types/index.js";
7
+ // ─── Parameters ──────────────────────────────────────────────────────────────
8
+ const paramsShape = {
9
+ url: z.string().url().describe("Primary website URL to audit (typically the homepage, e.g. 'https://acme.com')"),
10
+ pages: z
11
+ .string()
12
+ .optional()
13
+ .describe("JSON array of additional page URLs to include (e.g. '[\"https://acme.com/about\", \"https://acme.com/services\"]'). Analyzes up to 10 pages."),
14
+ };
15
+ const ParamsSchema = z.object(paramsShape);
16
+ // ─── Stop words (filtered from vocabulary frequency) ─────────────────────────
17
+ const STOP_WORDS = new Set([
18
+ // ── English stop words ──────────────────────────────────────────────────────
19
+ "a", "about", "above", "after", "again", "against", "all", "am", "an", "and",
20
+ "any", "are", "aren't", "as", "at", "be", "because", "been", "before",
21
+ "being", "below", "between", "both", "but", "by", "can", "can't", "cannot",
22
+ "could", "couldn't", "did", "didn't", "do", "does", "doesn't", "doing",
23
+ "don't", "down", "during", "each", "few", "for", "from", "further", "get",
24
+ "got", "had", "hadn't", "has", "hasn't", "have", "haven't", "having", "he",
25
+ "he'd", "he'll", "he's", "her", "here", "here's", "hers", "herself", "him",
26
+ "himself", "his", "how", "how's", "i", "i'd", "i'll", "i'm", "i've", "if",
27
+ "in", "into", "is", "isn't", "it", "it's", "its", "itself", "just", "let",
28
+ "let's", "like", "ll", "me", "might", "more", "most", "mustn't", "my",
29
+ "myself", "no", "nor", "not", "of", "off", "on", "once", "only", "or",
30
+ "other", "ought", "our", "ours", "ourselves", "out", "over", "own", "re",
31
+ "s", "same", "shan't", "she", "she'd", "she'll", "she's", "should",
32
+ "shouldn't", "so", "some", "such", "t", "than", "that", "that's", "the",
33
+ "their", "theirs", "them", "themselves", "then", "there", "there's", "these",
34
+ "they", "they'd", "they'll", "they're", "they've", "this", "those",
35
+ "through", "to", "too", "under", "until", "up", "us", "ve", "very", "was",
36
+ "wasn't", "we", "we'd", "we'll", "we're", "we've", "were", "weren't",
37
+ "what", "what's", "when", "when's", "where", "where's", "which", "while",
38
+ "who", "who's", "whom", "why", "why's", "will", "with", "won't", "would",
39
+ "wouldn't", "you", "you'd", "you'll", "you're", "you've", "your", "yours",
40
+ "yourself", "yourselves", "also", "an", "another", "back", "even", "go",
41
+ "going", "know", "make", "much", "new", "now", "one", "really", "right",
42
+ "see", "still", "take", "thing", "think", "two", "use", "want", "way",
43
+ "well", "work", "year", "d", "m", "re", "ve", "ll",
44
+ // ── JavaScript keywords / patterns (web artifact noise) ─────────────────────
45
+ "function", "const", "var", "let", "return", "class", "export", "import",
46
+ "default", "async", "await", "typeof", "undefined", "null", "true", "false",
47
+ "this", "new", "void", "delete", "throw", "catch", "finally", "switch",
48
+ "case", "break", "continue", "else", "instanceof", "constructor", "prototype",
49
+ "arguments", "module", "require", "window", "document", "console", "error",
50
+ "object", "array", "string", "number", "boolean", "symbol",
51
+ // ── CSS / HTML artifacts ────────────────────────────────────────────────────
52
+ "width", "height", "display", "none", "block", "flex", "grid", "margin",
53
+ "padding", "border", "color", "font", "size", "style", "background",
54
+ "position", "absolute", "relative", "fixed", "overflow", "hidden", "visible",
55
+ "auto", "inherit", "initial", "important", "hover", "active", "focus",
56
+ "opacity", "transition", "transform", "animation", "index", "container",
57
+ "wrapper", "section", "header", "footer", "main", "article", "button",
58
+ "input", "image", "link", "content", "currentindex", "maxindex", "previndex",
59
+ "nextindex", "slideto", "arialabel", "classname", "onclick", "onchange",
60
+ "onload", "queryselector", "addeventlistener", "setinterval", "settimeout",
61
+ "innerhtml", "textcontent", "appendchild", "createelement", "getelementbyid",
62
+ "parentnode", "childnodes",
63
+ // ── Common web framework terms ──────────────────────────────────────────────
64
+ "component", "props", "state", "render", "mount", "unmount", "usestate",
65
+ "useeffect", "useref", "memo", "callback", "dispatch", "reducer", "context",
66
+ "provider", "consumer", "router", "route", "navigate", "params", "query",
67
+ "middleware", "handler", "controller", "endpoint", "schema", "config",
68
+ "utils", "helpers", "types", "interfaces",
69
+ ]);
70
+ // ─── Overused / generic marketing words ──────────────────────────────────────
71
+ const OVERUSED_WORDS = new Set([
72
+ "solution", "solutions", "leverage", "innovative", "innovation",
73
+ "best-in-class", "cutting-edge", "world-class", "next-generation",
74
+ "synergy", "synergies", "disruptive", "disrupt", "paradigm",
75
+ "holistic", "seamless", "seamlessly", "robust", "scalable",
76
+ "optimize", "optimization", "streamline", "streamlined",
77
+ "revolutionary", "game-changing", "state-of-the-art", "turnkey",
78
+ "bleeding-edge", "empower", "empowering", "enable", "enabling",
79
+ "unlock", "unlocking", "transform", "transformative", "transformation",
80
+ "elevate", "elevating",
81
+ ]);
82
+ // ─── AI-ism patterns ─────────────────────────────────────────────────────────
83
+ const AI_ISM_PATTERNS = [
84
+ "in today's",
85
+ "it's worth noting",
86
+ "at the end of the day",
87
+ "this is a testament to",
88
+ "let's dive in",
89
+ "here's the thing",
90
+ "the reality is",
91
+ "is not just",
92
+ "it's not just",
93
+ "— it's",
94
+ "whether you're",
95
+ "in an era of",
96
+ "in the world of",
97
+ "at its core",
98
+ "when it comes to",
99
+ "it goes without saying",
100
+ "needless to say",
101
+ "look no further",
102
+ "stands as a",
103
+ "serves as a",
104
+ "plays a crucial role",
105
+ "navigating the",
106
+ "landscape",
107
+ "ever-evolving",
108
+ "ever-changing",
109
+ "game-changer",
110
+ "take it to the next level",
111
+ "deep dive",
112
+ "delve",
113
+ "moreover",
114
+ "furthermore",
115
+ "in conclusion",
116
+ "comprehensive",
117
+ ];
118
+ // ─── Hedging words ───────────────────────────────────────────────────────────
119
+ const HEDGING_WORDS = [
120
+ "can", "may", "might", "could", "potentially", "help", "helps",
121
+ "possible", "possibly", "perhaps", "likely", "tend", "tends",
122
+ "generally", "typically", "often", "sometimes", "arguably",
123
+ ];
124
+ // ─── Superlative / claim indicators ──────────────────────────────────────────
125
+ const SUPERLATIVE_PATTERNS = [
126
+ /\b(leading|best|top|first|only|largest|fastest|most|premier|number[- ]?one|#1|no\.?\s?1)\b/i,
127
+ ];
128
+ // ─── Passive voice pattern ───────────────────────────────────────────────────
129
+ const PASSIVE_PATTERN = /\b(is|are|was|were|been|being|be)\s+\w+ed\b/i;
130
+ // ─── Industry jargon detection (common B2B / tech / marketing) ───────────────
131
+ const JARGON_TERMS = new Set([
132
+ "roi", "kpi", "saas", "b2b", "b2c", "api", "crm", "erp", "mvp",
133
+ "pipeline", "funnel", "touchpoint", "touchpoints", "omnichannel",
134
+ "stakeholder", "stakeholders", "deliverable", "deliverables",
135
+ "bandwidth", "cadence", "ecosystem", "integration", "integrations",
136
+ "onboarding", "offboarding", "upskill", "upskilling", "whitepaper",
137
+ "thought-leadership", "go-to-market", "end-to-end", "full-stack",
138
+ "analytics", "metric", "metrics", "segmentation", "personalization",
139
+ "attribution", "lifecycle", "retention", "churn", "arpu", "ltv",
140
+ "cac", "conversion", "conversions", "engagement",
141
+ ]);
142
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
143
+ function extractTextContent($) {
144
+ // Remove script, style, nav, footer, header elements to focus on body copy
145
+ $("script, style, noscript, svg, iframe").remove();
146
+ // Get text from semantic content areas first, then body
147
+ const contentSelectors = ["main", "article", "[role='main']"];
148
+ let text = "";
149
+ for (const sel of contentSelectors) {
150
+ const el = $(sel);
151
+ if (el.length) {
152
+ text += el.text() + "\n";
153
+ }
154
+ }
155
+ // If no semantic content areas found, use body
156
+ if (!text.trim()) {
157
+ text = $("body").text() || $.text();
158
+ }
159
+ // Clean up: collapse whitespace, preserve paragraph breaks
160
+ return text
161
+ .replace(/[\t ]+/g, " ")
162
+ .replace(/\n\s*\n/g, "\n\n")
163
+ .replace(/\n{3,}/g, "\n\n")
164
+ .trim();
165
+ }
166
+ function splitSentences(text) {
167
+ return text
168
+ .split(/[.!?]+/)
169
+ .map((s) => s.trim())
170
+ .filter((s) => s.length > 5 && s.split(/\s+/).length >= 3);
171
+ }
172
+ function tokenize(text) {
173
+ return text
174
+ .toLowerCase()
175
+ .replace(/[^a-z0-9'-]/g, " ")
176
+ .split(/\s+/)
177
+ .filter((w) => w.length > 1);
178
+ }
179
+ function countOccurrences(text, pattern) {
180
+ const lower = text.toLowerCase();
181
+ const target = pattern.toLowerCase();
182
+ let count = 0;
183
+ let idx = 0;
184
+ while ((idx = lower.indexOf(target, idx)) !== -1) {
185
+ count++;
186
+ idx += target.length;
187
+ }
188
+ return count;
189
+ }
190
+ // ─── Analysis functions ──────────────────────────────────────────────────────
191
+ function analyzeVoiceFingerprint(text, sentences, words) {
192
+ // Formality (1-10): based on contractions, avg word length, colloquialisms
193
+ const contractionCount = (text.match(/\b\w+n't\b|\b\w+'re\b|\b\w+'ve\b|\b\w+'ll\b|\b\w+'s\b|\b\w+'d\b/gi) || []).length;
194
+ const avgWordLength = words.reduce((sum, w) => sum + w.length, 0) / (words.length || 1);
195
+ const contractionRatio = contractionCount / (sentences.length || 1);
196
+ // High contractions + short words = informal; low contractions + long words = formal
197
+ let formality = 5;
198
+ if (avgWordLength > 6)
199
+ formality += 2;
200
+ else if (avgWordLength > 5)
201
+ formality += 1;
202
+ if (avgWordLength < 4.5)
203
+ formality -= 1;
204
+ if (contractionRatio < 0.1)
205
+ formality += 1;
206
+ if (contractionRatio > 0.4)
207
+ formality -= 2;
208
+ else if (contractionRatio > 0.2)
209
+ formality -= 1;
210
+ formality = Math.max(1, Math.min(10, formality));
211
+ // Jargon density
212
+ const jargonWords = words.filter((w) => JARGON_TERMS.has(w));
213
+ const jargonDensity = words.length > 0 ? (jargonWords.length / words.length) : 0;
214
+ const jargonStr = `${(jargonDensity * 100).toFixed(1)}% (${jargonWords.length} jargon terms / ${words.length} words)`;
215
+ // Average sentence length
216
+ const avgSentenceLength = sentences.length > 0
217
+ ? Math.round(sentences.reduce((sum, s) => sum + s.split(/\s+/).length, 0) / sentences.length)
218
+ : 0;
219
+ // Active vs passive voice
220
+ let passiveCount = 0;
221
+ for (const sentence of sentences) {
222
+ if (PASSIVE_PATTERN.test(sentence)) {
223
+ passiveCount++;
224
+ }
225
+ }
226
+ const activePct = sentences.length > 0
227
+ ? Math.round(((sentences.length - passiveCount) / sentences.length) * 100)
228
+ : 0;
229
+ // Hedging frequency
230
+ let hedgeCount = 0;
231
+ for (const hw of HEDGING_WORDS) {
232
+ hedgeCount += countOccurrences(text, ` ${hw} `);
233
+ }
234
+ const hedgeStr = `${hedgeCount} hedging instances across ${sentences.length} sentences (${sentences.length > 0 ? (hedgeCount / sentences.length * 100).toFixed(1) : 0}%)`;
235
+ // Person detection
236
+ const weCount = countOccurrences(text, " we ");
237
+ const iCount = countOccurrences(text, " i ");
238
+ const youCount = countOccurrences(text, " you ");
239
+ const theyCount = countOccurrences(text, " they ");
240
+ const personCounts = {
241
+ "we (first-person plural)": weCount,
242
+ "I (first-person singular)": iCount,
243
+ "you (second-person)": youCount,
244
+ "they (third-person)": theyCount,
245
+ };
246
+ const toneByChannel = {};
247
+ const dominant = Object.entries(personCounts).sort((a, b) => b[1] - a[1]);
248
+ toneByChannel["dominant_person"] = dominant[0]?.[1] > 0
249
+ ? `${dominant[0][0]}: ${dominant[0][1]} occurrences`
250
+ : "no clear dominant person";
251
+ toneByChannel["person_breakdown"] = dominant.map(([k, v]) => `${k}: ${v}`).join(", ");
252
+ return {
253
+ formality,
254
+ jargon_density: jargonStr,
255
+ avg_sentence_length: avgSentenceLength,
256
+ active_voice_pct: activePct,
257
+ hedging_frequency: hedgeStr,
258
+ tone_by_channel: toneByChannel,
259
+ };
260
+ }
261
+ function analyzeVocabulary(words) {
262
+ const freq = new Map();
263
+ for (const w of words) {
264
+ if (STOP_WORDS.has(w) || w.length < 3)
265
+ continue;
266
+ freq.set(w, (freq.get(w) || 0) + 1);
267
+ }
268
+ const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 30);
269
+ return sorted.map(([term, count]) => {
270
+ let assessment;
271
+ if (OVERUSED_WORDS.has(term)) {
272
+ assessment = "overused-generic";
273
+ }
274
+ else if (JARGON_TERMS.has(term)) {
275
+ assessment = "industry-jargon";
276
+ }
277
+ else if (count >= 5) {
278
+ assessment = "potentially-distinctive";
279
+ }
280
+ else {
281
+ assessment = "neutral";
282
+ }
283
+ return { term, count, assessment };
284
+ });
285
+ }
286
+ function analyzeClaims(sentences) {
287
+ const explicit = [];
288
+ const implicit = [];
289
+ const contradictions = [];
290
+ // Stat-based claims: sentences with numbers
291
+ const statPattern = /\b\d[\d,]*\+?\b|\b\d+x\b|\b\d+%\b/i;
292
+ const seenStatClaims = new Set();
293
+ for (const sentence of sentences) {
294
+ // Explicit claims with numbers
295
+ if (statPattern.test(sentence)) {
296
+ const normalized = sentence.slice(0, 80);
297
+ if (!seenStatClaims.has(normalized)) {
298
+ seenStatClaims.add(normalized);
299
+ const issues = [];
300
+ // Check if there's a source/citation nearby
301
+ if (!/source|according|study|report|survey|research|data/i.test(sentence)) {
302
+ issues.push("no source cited");
303
+ }
304
+ explicit.push({
305
+ claim: sentence.length > 120 ? sentence.slice(0, 120) + "..." : sentence,
306
+ frequency: 1,
307
+ issues,
308
+ });
309
+ }
310
+ }
311
+ // Superlative claims
312
+ for (const pattern of SUPERLATIVE_PATTERNS) {
313
+ if (pattern.test(sentence)) {
314
+ const match = sentence.match(pattern);
315
+ implicit.push({
316
+ claim: sentence.length > 120 ? sentence.slice(0, 120) + "..." : sentence,
317
+ evidence: `Uses superlative "${match?.[1] || "unknown"}"`,
318
+ status: "unqualified",
319
+ });
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ // Simple contradiction detection: look for opposing claims
325
+ const hasLeading = sentences.some((s) => /\b(leading|leader|#1|number one)\b/i.test(s));
326
+ const hasNew = sentences.some((s) => /\b(new|startup|emerging|young)\b/i.test(s));
327
+ if (hasLeading && hasNew) {
328
+ contradictions.push("Claims 'leading' position while also positioning as 'new/emerging' — clarify which framing is primary");
329
+ }
330
+ const hasSimple = sentences.some((s) => /\b(simple|easy|effortless)\b/i.test(s));
331
+ const hasComprehensive = sentences.some((s) => /\b(comprehensive|full-suite|complete|everything)\b/i.test(s));
332
+ if (hasSimple && hasComprehensive) {
333
+ contradictions.push("Claims both 'simple/easy' and 'comprehensive/complete' — these can feel contradictory without careful framing");
334
+ }
335
+ return { explicit, implicit, contradictions };
336
+ }
337
+ function analyzeAiIsms(text) {
338
+ const results = [];
339
+ for (const pattern of AI_ISM_PATTERNS) {
340
+ const count = countOccurrences(text, pattern);
341
+ if (count > 0) {
342
+ results.push({ pattern, count });
343
+ }
344
+ }
345
+ return results.sort((a, b) => b.count - a.count);
346
+ }
347
+ function analyzeGaps(text, sentences, vocab, pageTexts) {
348
+ const gaps = [];
349
+ // Check for stated brand perspective (mission/vision/why)
350
+ const hasPerspective = /\b(mission|vision|purpose|believe|why we|what we stand for)\b/i.test(text);
351
+ if (!hasPerspective) {
352
+ gaps.push("No clear brand perspective found (mission, vision, or belief statement)");
353
+ }
354
+ // Check for enemy/tension
355
+ const hasTension = /\b(problem|challenge|broken|wrong|unlike|instead of|tired of|frustrated)\b/i.test(text);
356
+ if (!hasTension) {
357
+ gaps.push("No clear 'enemy' or tension identified — brand lacks something to push against");
358
+ }
359
+ // Check for anchor vocabulary (distinctive repeated terms)
360
+ const distinctiveTerms = vocab.filter((v) => v.assessment === "potentially-distinctive");
361
+ if (distinctiveTerms.length < 3) {
362
+ gaps.push("Weak anchor vocabulary — fewer than 3 distinctive, frequently-used terms found");
363
+ }
364
+ // Check for forbidden vocabulary awareness
365
+ const overusedCount = vocab.filter((v) => v.assessment === "overused-generic").length;
366
+ if (overusedCount >= 3) {
367
+ gaps.push(`${overusedCount} overused generic marketing terms detected — needs a 'never say' list`);
368
+ }
369
+ // Check voice consistency across pages (if multiple pages analyzed)
370
+ if (pageTexts.length > 1) {
371
+ const pageSentenceLengths = pageTexts.map((pt) => {
372
+ const ss = splitSentences(pt);
373
+ return ss.length > 0
374
+ ? ss.reduce((sum, s) => sum + s.split(/\s+/).length, 0) / ss.length
375
+ : 0;
376
+ });
377
+ const maxLen = Math.max(...pageSentenceLengths);
378
+ const minLen = Math.min(...pageSentenceLengths.filter((l) => l > 0));
379
+ if (maxLen > 0 && minLen > 0 && maxLen / minLen > 1.8) {
380
+ gaps.push("Voice inconsistency across pages — sentence length varies significantly (possible multiple writers without a style guide)");
381
+ }
382
+ }
383
+ // Check for social proof
384
+ const hasSocialProof = /\b(testimonial|review|client|customer|partner|trusted by|used by)\b/i.test(text);
385
+ if (!hasSocialProof) {
386
+ gaps.push("No social proof language detected (testimonials, client references, trust signals)");
387
+ }
388
+ return gaps;
389
+ }
390
+ // ─── Markdown report generator ───────────────────────────────────────────────
391
+ function generateAuditMarkdown(audit, aiIsms, urls) {
392
+ const lines = [];
393
+ lines.push("# Messaging Audit Report");
394
+ lines.push("");
395
+ lines.push(`**Pages analyzed:** ${urls.join(", ")}`);
396
+ lines.push(`**Generated:** ${new Date().toISOString().split("T")[0]}`);
397
+ lines.push("");
398
+ // Voice Fingerprint
399
+ lines.push("## Voice Fingerprint");
400
+ lines.push("");
401
+ lines.push(`| Metric | Value |`);
402
+ lines.push(`| ------ | ----- |`);
403
+ lines.push(`| Formality | ${audit.voice_fingerprint.formality}/10 |`);
404
+ lines.push(`| Jargon density | ${audit.voice_fingerprint.jargon_density} |`);
405
+ lines.push(`| Avg sentence length | ${audit.voice_fingerprint.avg_sentence_length} words |`);
406
+ lines.push(`| Active voice | ${audit.voice_fingerprint.active_voice_pct}% |`);
407
+ lines.push(`| Hedging frequency | ${audit.voice_fingerprint.hedging_frequency} |`);
408
+ lines.push(`| Dominant person | ${audit.voice_fingerprint.tone_by_channel["dominant_person"] || "n/a"} |`);
409
+ lines.push(`| Person breakdown | ${audit.voice_fingerprint.tone_by_channel["person_breakdown"] || "n/a"} |`);
410
+ lines.push("");
411
+ // Vocabulary
412
+ lines.push("## Top Vocabulary");
413
+ lines.push("");
414
+ lines.push(`| Term | Count | Assessment |`);
415
+ lines.push(`| ---- | ----- | ---------- |`);
416
+ for (const v of audit.vocabulary_frequency) {
417
+ lines.push(`| ${v.term} | ${v.count} | ${v.assessment} |`);
418
+ }
419
+ lines.push("");
420
+ // Overused callout
421
+ const overused = audit.vocabulary_frequency.filter((v) => v.assessment === "overused-generic");
422
+ if (overused.length > 0) {
423
+ lines.push("### Overused Generic Terms");
424
+ lines.push("");
425
+ for (const o of overused) {
426
+ lines.push(`- **${o.term}** (${o.count}x) — consider replacing with brand-specific language`);
427
+ }
428
+ lines.push("");
429
+ }
430
+ // Claims
431
+ lines.push("## Claims Analysis");
432
+ lines.push("");
433
+ if (audit.claims.explicit.length > 0) {
434
+ lines.push("### Explicit Claims (with data/numbers)");
435
+ lines.push("");
436
+ for (const c of audit.claims.explicit) {
437
+ const issueStr = c.issues.length > 0 ? ` — **Issues:** ${c.issues.join(", ")}` : "";
438
+ lines.push(`- "${c.claim}"${issueStr}`);
439
+ }
440
+ lines.push("");
441
+ }
442
+ if (audit.claims.implicit.length > 0) {
443
+ lines.push("### Superlative / Implicit Claims");
444
+ lines.push("");
445
+ for (const c of audit.claims.implicit) {
446
+ lines.push(`- "${c.claim}" — ${c.evidence} (${c.status})`);
447
+ }
448
+ lines.push("");
449
+ }
450
+ if (audit.claims.contradictions.length > 0) {
451
+ lines.push("### Contradictions");
452
+ lines.push("");
453
+ for (const c of audit.claims.contradictions) {
454
+ lines.push(`- ${c}`);
455
+ }
456
+ lines.push("");
457
+ }
458
+ // AI-isms
459
+ if (aiIsms.length > 0) {
460
+ lines.push("## AI-ism Detection");
461
+ lines.push("");
462
+ lines.push("Patterns commonly associated with AI-generated or template copy:");
463
+ lines.push("");
464
+ for (const a of aiIsms) {
465
+ lines.push(`- "${a.pattern}" — ${a.count}x`);
466
+ }
467
+ lines.push("");
468
+ }
469
+ // Gaps
470
+ if (audit.gaps.length > 0) {
471
+ lines.push("## Gaps");
472
+ lines.push("");
473
+ for (const g of audit.gaps) {
474
+ lines.push(`- ${g}`);
475
+ }
476
+ lines.push("");
477
+ }
478
+ return lines.join("\n");
479
+ }
480
+ // ─── Main handler ────────────────────────────────────────────────────────────
481
+ async function handler(input) {
482
+ const brandDir = new BrandDir(process.cwd());
483
+ if (!(await brandDir.exists())) {
484
+ return buildResponse({
485
+ what_happened: "No .brand/ directory found",
486
+ next_steps: ["Run brand_init first to create the brand system"],
487
+ data: { error: ERROR_CODES.NOT_INITIALIZED },
488
+ });
489
+ }
490
+ if (!input.url.startsWith("http://") && !input.url.startsWith("https://")) {
491
+ return buildResponse({
492
+ what_happened: "Only http:// and https:// URLs are supported",
493
+ next_steps: ["Provide a URL starting with https://"],
494
+ data: { error: ERROR_CODES.INVALID_PROTOCOL },
495
+ });
496
+ }
497
+ // Build URL list
498
+ const urls = [input.url];
499
+ if (input.pages) {
500
+ try {
501
+ const parsedPages = JSON.parse(input.pages);
502
+ if (Array.isArray(parsedPages)) {
503
+ const validUrls = parsedPages.filter(u => u.startsWith("http://") || u.startsWith("https://"));
504
+ urls.push(...validUrls);
505
+ }
506
+ }
507
+ catch {
508
+ return buildResponse({
509
+ what_happened: "Invalid pages parameter — must be a JSON array of URL strings",
510
+ next_steps: ["Fix the pages parameter and try again"],
511
+ data: { error: ERROR_CODES.INVALID_PAGES_PARAM },
512
+ });
513
+ }
514
+ }
515
+ // Fetch all pages in parallel
516
+ const allUrls = urls.slice(0, 10);
517
+ const fetchResults = await Promise.allSettled(allUrls.map(async (pageUrl) => {
518
+ const resp = await fetch(pageUrl, {
519
+ signal: AbortSignal.timeout(15000),
520
+ headers: { "User-Agent": `brandsystem-mcp/${getVersion()}` },
521
+ });
522
+ if (!resp.ok)
523
+ return { url: pageUrl, text: "" };
524
+ const html = await resp.text();
525
+ const $ = cheerio.load(html);
526
+ const text = extractTextContent($);
527
+ return { url: pageUrl, text };
528
+ }));
529
+ const pageTexts = [];
530
+ const fetchedUrls = [];
531
+ for (const result of fetchResults) {
532
+ if (result.status === "fulfilled" && result.value.text.length > 50) {
533
+ pageTexts.push(result.value.text);
534
+ fetchedUrls.push(result.value.url);
535
+ }
536
+ }
537
+ if (pageTexts.length === 0) {
538
+ return buildResponse({
539
+ what_happened: `Failed to extract text content from any of the ${urls.length} URLs`,
540
+ next_steps: [
541
+ "Check the URLs are correct and publicly accessible (not behind a login)",
542
+ "Make sure the pages have text content (not just images or JavaScript-rendered content)",
543
+ "Try a different page on the same domain (e.g. /about or /services)",
544
+ "If this keeps happening, run brand_feedback to report the issue.",
545
+ ],
546
+ data: { error: ERROR_CODES.NO_CONTENT, urls },
547
+ });
548
+ }
549
+ // Combine all text for analysis
550
+ const allText = pageTexts.join("\n\n");
551
+ const sentences = splitSentences(allText);
552
+ const words = tokenize(allText);
553
+ // Run analyses
554
+ const voiceFingerprint = analyzeVoiceFingerprint(allText, sentences, words);
555
+ const vocabularyFrequency = analyzeVocabulary(words);
556
+ const claims = analyzeClaims(sentences);
557
+ const aiIsms = analyzeAiIsms(allText);
558
+ const gaps = analyzeGaps(allText, sentences, vocabularyFrequency, pageTexts);
559
+ const audit = {
560
+ voice_fingerprint: voiceFingerprint,
561
+ vocabulary_frequency: vocabularyFrequency,
562
+ claims,
563
+ gaps,
564
+ };
565
+ // Generate and write the markdown report
566
+ const markdown = generateAuditMarkdown(audit, aiIsms, fetchedUrls);
567
+ await brandDir.writeMarkdown("messaging-audit.md", markdown);
568
+ // Build summary for response
569
+ const topDistinctive = vocabularyFrequency
570
+ .filter((v) => v.assessment === "potentially-distinctive")
571
+ .slice(0, 5)
572
+ .map((v) => v.term);
573
+ const topOverused = vocabularyFrequency
574
+ .filter((v) => v.assessment === "overused-generic")
575
+ .slice(0, 5)
576
+ .map((v) => v.term);
577
+ return buildResponse({
578
+ what_happened: `Analyzed messaging across ${fetchedUrls.length} page(s) from ${input.url}`,
579
+ next_steps: [
580
+ "Present the key findings to the user: voice fingerprint scores, top vocabulary patterns, any contradictions or claims issues, and gaps",
581
+ "Then run brand_compile_messaging to define how the brand *should* sound",
582
+ ],
583
+ data: {
584
+ pages_analyzed: fetchedUrls.length,
585
+ total_sentences: sentences.length,
586
+ total_words: words.length,
587
+ voice_fingerprint: {
588
+ formality: `${voiceFingerprint.formality}/10`,
589
+ avg_sentence_length: `${voiceFingerprint.avg_sentence_length} words`,
590
+ active_voice: `${voiceFingerprint.active_voice_pct}%`,
591
+ dominant_person: voiceFingerprint.tone_by_channel["dominant_person"] || "n/a",
592
+ },
593
+ vocabulary: {
594
+ distinctive_terms: topDistinctive,
595
+ overused_terms: topOverused,
596
+ },
597
+ claims_summary: {
598
+ explicit_count: claims.explicit.length,
599
+ superlative_count: claims.implicit.length,
600
+ contradictions: claims.contradictions,
601
+ },
602
+ ai_isms_found: aiIsms.length,
603
+ gaps: gaps,
604
+ report_file: ".brand/messaging-audit.md",
605
+ conversation_guide: {
606
+ instruction: "Present the key findings to the user: voice fingerprint scores, top vocabulary patterns, any contradictions or claims issues, and gaps. Then say: 'This is how your brand sounds today. Now let's define how it *should* sound. I'll walk you through defining your perspective, voice, and brand story.' Then run brand_compile_messaging.",
607
+ },
608
+ },
609
+ });
610
+ }
611
+ // ─── Registration ────────────────────────────────────────────────────────────
612
+ export function register(server) {
613
+ server.tool("brand_extract_messaging", "Audit how a brand currently sounds on its website. Analyzes voice fingerprint (formality, jargon density, active voice %, hedging), vocabulary frequency (distinctive vs generic terms), claims (explicit with data, superlative/unqualified), AI-ism detection, and messaging gaps. Writes .brand/messaging-audit.md. Use when the user wants to understand their current brand voice before defining how it should sound. Returns structured analysis with scores and actionable findings.", paramsShape, async (args) => {
614
+ const parsed = safeParseParams(ParamsSchema, args);
615
+ if (!parsed.success)
616
+ return parsed.response;
617
+ return handler(parsed.data);
618
+ });
619
+ }
620
+ //# sourceMappingURL=brand-extract-messaging.js.map