@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.
- package/README.md +515 -0
- package/bin/brandsystem-mcp.mjs +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/brand-dir.d.ts +56 -0
- package/dist/lib/brand-dir.d.ts.map +1 -0
- package/dist/lib/brand-dir.js +270 -0
- package/dist/lib/brand-dir.js.map +1 -0
- package/dist/lib/color-namer.d.ts +28 -0
- package/dist/lib/color-namer.d.ts.map +1 -0
- package/dist/lib/color-namer.js +155 -0
- package/dist/lib/color-namer.js.map +1 -0
- package/dist/lib/confidence.d.ts +19 -0
- package/dist/lib/confidence.d.ts.map +1 -0
- package/dist/lib/confidence.js +66 -0
- package/dist/lib/confidence.js.map +1 -0
- package/dist/lib/content-scorer.d.ts +38 -0
- package/dist/lib/content-scorer.d.ts.map +1 -0
- package/dist/lib/content-scorer.js +571 -0
- package/dist/lib/content-scorer.js.map +1 -0
- package/dist/lib/css-parser.d.ts +45 -0
- package/dist/lib/css-parser.d.ts.map +1 -0
- package/dist/lib/css-parser.js +330 -0
- package/dist/lib/css-parser.js.map +1 -0
- package/dist/lib/dtcg-compiler.d.ts +7 -0
- package/dist/lib/dtcg-compiler.d.ts.map +1 -0
- package/dist/lib/dtcg-compiler.js +89 -0
- package/dist/lib/dtcg-compiler.js.map +1 -0
- package/dist/lib/interaction-policy-compiler.d.ts +40 -0
- package/dist/lib/interaction-policy-compiler.d.ts.map +1 -0
- package/dist/lib/interaction-policy-compiler.js +60 -0
- package/dist/lib/interaction-policy-compiler.js.map +1 -0
- package/dist/lib/logo-extractor.d.ts +49 -0
- package/dist/lib/logo-extractor.d.ts.map +1 -0
- package/dist/lib/logo-extractor.js +384 -0
- package/dist/lib/logo-extractor.js.map +1 -0
- package/dist/lib/report-html.d.ts +20 -0
- package/dist/lib/report-html.d.ts.map +1 -0
- package/dist/lib/report-html.js +938 -0
- package/dist/lib/report-html.js.map +1 -0
- package/dist/lib/response.d.ts +20 -0
- package/dist/lib/response.d.ts.map +1 -0
- package/dist/lib/response.js +54 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/lib/runtime-compiler.d.ts +60 -0
- package/dist/lib/runtime-compiler.d.ts.map +1 -0
- package/dist/lib/runtime-compiler.js +96 -0
- package/dist/lib/runtime-compiler.js.map +1 -0
- package/dist/lib/svg-resolver.d.ts +21 -0
- package/dist/lib/svg-resolver.d.ts.map +1 -0
- package/dist/lib/svg-resolver.js +115 -0
- package/dist/lib/svg-resolver.js.map +1 -0
- package/dist/lib/url-validator.d.ts +11 -0
- package/dist/lib/url-validator.d.ts.map +1 -0
- package/dist/lib/url-validator.js +93 -0
- package/dist/lib/url-validator.js.map +1 -0
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +19 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/lib/vim-generator.d.ts +13 -0
- package/dist/lib/vim-generator.d.ts.map +1 -0
- package/dist/lib/vim-generator.js +718 -0
- package/dist/lib/vim-generator.js.map +1 -0
- package/dist/resources/brand-resources.d.ts +4 -0
- package/dist/resources/brand-resources.d.ts.map +1 -0
- package/dist/resources/brand-resources.js +34 -0
- package/dist/resources/brand-resources.js.map +1 -0
- package/dist/schemas/brand-config.d.ts +28 -0
- package/dist/schemas/brand-config.d.ts.map +1 -0
- package/dist/schemas/brand-config.js +11 -0
- package/dist/schemas/brand-config.js.map +1 -0
- package/dist/schemas/brand-runtime.d.ts +251 -0
- package/dist/schemas/brand-runtime.d.ts.map +1 -0
- package/dist/schemas/brand-runtime.js +54 -0
- package/dist/schemas/brand-runtime.js.map +1 -0
- package/dist/schemas/core-identity.d.ts +302 -0
- package/dist/schemas/core-identity.d.ts.map +1 -0
- package/dist/schemas/core-identity.js +51 -0
- package/dist/schemas/core-identity.js.map +1 -0
- package/dist/schemas/index.d.ts +11 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +11 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/interaction-policy.d.ts +150 -0
- package/dist/schemas/interaction-policy.d.ts.map +1 -0
- package/dist/schemas/interaction-policy.js +34 -0
- package/dist/schemas/interaction-policy.js.map +1 -0
- package/dist/schemas/messaging.d.ts +776 -0
- package/dist/schemas/messaging.d.ts.map +1 -0
- package/dist/schemas/messaging.js +68 -0
- package/dist/schemas/messaging.js.map +1 -0
- package/dist/schemas/needs-clarification.d.ts +62 -0
- package/dist/schemas/needs-clarification.d.ts.map +1 -0
- package/dist/schemas/needs-clarification.js +13 -0
- package/dist/schemas/needs-clarification.js.map +1 -0
- package/dist/schemas/strategy.d.ts +537 -0
- package/dist/schemas/strategy.d.ts.map +1 -0
- package/dist/schemas/strategy.js +71 -0
- package/dist/schemas/strategy.js.map +1 -0
- package/dist/schemas/tokens.d.ts +35 -0
- package/dist/schemas/tokens.d.ts.map +1 -0
- package/dist/schemas/tokens.js +15 -0
- package/dist/schemas/tokens.js.map +1 -0
- package/dist/schemas/visual-identity.d.ts +224 -0
- package/dist/schemas/visual-identity.d.ts.map +1 -0
- package/dist/schemas/visual-identity.js +42 -0
- package/dist/schemas/visual-identity.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +75 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/brand-audit-content.d.ts +3 -0
- package/dist/tools/brand-audit-content.d.ts.map +1 -0
- package/dist/tools/brand-audit-content.js +116 -0
- package/dist/tools/brand-audit-content.js.map +1 -0
- package/dist/tools/brand-audit-drift.d.ts +3 -0
- package/dist/tools/brand-audit-drift.d.ts.map +1 -0
- package/dist/tools/brand-audit-drift.js +301 -0
- package/dist/tools/brand-audit-drift.js.map +1 -0
- package/dist/tools/brand-audit.d.ts +3 -0
- package/dist/tools/brand-audit.d.ts.map +1 -0
- package/dist/tools/brand-audit.js +129 -0
- package/dist/tools/brand-audit.js.map +1 -0
- package/dist/tools/brand-build-journey.d.ts +3 -0
- package/dist/tools/brand-build-journey.d.ts.map +1 -0
- package/dist/tools/brand-build-journey.js +312 -0
- package/dist/tools/brand-build-journey.js.map +1 -0
- package/dist/tools/brand-build-matrix.d.ts +3 -0
- package/dist/tools/brand-build-matrix.d.ts.map +1 -0
- package/dist/tools/brand-build-matrix.js +525 -0
- package/dist/tools/brand-build-matrix.js.map +1 -0
- package/dist/tools/brand-build-personas.d.ts +3 -0
- package/dist/tools/brand-build-personas.d.ts.map +1 -0
- package/dist/tools/brand-build-personas.js +436 -0
- package/dist/tools/brand-build-personas.js.map +1 -0
- package/dist/tools/brand-build-themes.d.ts +3 -0
- package/dist/tools/brand-build-themes.d.ts.map +1 -0
- package/dist/tools/brand-build-themes.js +476 -0
- package/dist/tools/brand-build-themes.js.map +1 -0
- package/dist/tools/brand-check-compliance.d.ts +3 -0
- package/dist/tools/brand-check-compliance.d.ts.map +1 -0
- package/dist/tools/brand-check-compliance.js +243 -0
- package/dist/tools/brand-check-compliance.js.map +1 -0
- package/dist/tools/brand-clarify.d.ts +21 -0
- package/dist/tools/brand-clarify.d.ts.map +1 -0
- package/dist/tools/brand-clarify.js +497 -0
- package/dist/tools/brand-clarify.js.map +1 -0
- package/dist/tools/brand-compile-messaging.d.ts +3 -0
- package/dist/tools/brand-compile-messaging.d.ts.map +1 -0
- package/dist/tools/brand-compile-messaging.js +759 -0
- package/dist/tools/brand-compile-messaging.js.map +1 -0
- package/dist/tools/brand-compile.d.ts +3 -0
- package/dist/tools/brand-compile.d.ts.map +1 -0
- package/dist/tools/brand-compile.js +182 -0
- package/dist/tools/brand-compile.js.map +1 -0
- package/dist/tools/brand-deepen-identity.d.ts +3 -0
- package/dist/tools/brand-deepen-identity.d.ts.map +1 -0
- package/dist/tools/brand-deepen-identity.js +483 -0
- package/dist/tools/brand-deepen-identity.js.map +1 -0
- package/dist/tools/brand-export.d.ts +17 -0
- package/dist/tools/brand-export.d.ts.map +1 -0
- package/dist/tools/brand-export.js +730 -0
- package/dist/tools/brand-export.js.map +1 -0
- package/dist/tools/brand-extract-figma.d.ts +3 -0
- package/dist/tools/brand-extract-figma.d.ts.map +1 -0
- package/dist/tools/brand-extract-figma.js +174 -0
- package/dist/tools/brand-extract-figma.js.map +1 -0
- package/dist/tools/brand-extract-messaging.d.ts +3 -0
- package/dist/tools/brand-extract-messaging.d.ts.map +1 -0
- package/dist/tools/brand-extract-messaging.js +620 -0
- package/dist/tools/brand-extract-messaging.js.map +1 -0
- package/dist/tools/brand-extract-web.d.ts +3 -0
- package/dist/tools/brand-extract-web.d.ts.map +1 -0
- package/dist/tools/brand-extract-web.js +477 -0
- package/dist/tools/brand-extract-web.js.map +1 -0
- package/dist/tools/brand-feedback.d.ts +3 -0
- package/dist/tools/brand-feedback.d.ts.map +1 -0
- package/dist/tools/brand-feedback.js +366 -0
- package/dist/tools/brand-feedback.js.map +1 -0
- package/dist/tools/brand-ingest-assets.d.ts +3 -0
- package/dist/tools/brand-ingest-assets.d.ts.map +1 -0
- package/dist/tools/brand-ingest-assets.js +233 -0
- package/dist/tools/brand-ingest-assets.js.map +1 -0
- package/dist/tools/brand-init.d.ts +3 -0
- package/dist/tools/brand-init.d.ts.map +1 -0
- package/dist/tools/brand-init.js +66 -0
- package/dist/tools/brand-init.js.map +1 -0
- package/dist/tools/brand-preflight.d.ts +3 -0
- package/dist/tools/brand-preflight.d.ts.map +1 -0
- package/dist/tools/brand-preflight.js +608 -0
- package/dist/tools/brand-preflight.js.map +1 -0
- package/dist/tools/brand-report.d.ts +3 -0
- package/dist/tools/brand-report.d.ts.map +1 -0
- package/dist/tools/brand-report.js +154 -0
- package/dist/tools/brand-report.js.map +1 -0
- package/dist/tools/brand-runtime.d.ts +3 -0
- package/dist/tools/brand-runtime.d.ts.map +1 -0
- package/dist/tools/brand-runtime.js +37 -0
- package/dist/tools/brand-runtime.js.map +1 -0
- package/dist/tools/brand-set-logo.d.ts +3 -0
- package/dist/tools/brand-set-logo.d.ts.map +1 -0
- package/dist/tools/brand-set-logo.js +170 -0
- package/dist/tools/brand-set-logo.js.map +1 -0
- package/dist/tools/brand-start.d.ts +3 -0
- package/dist/tools/brand-start.d.ts.map +1 -0
- package/dist/tools/brand-start.js +686 -0
- package/dist/tools/brand-start.js.map +1 -0
- package/dist/tools/brand-status.d.ts +3 -0
- package/dist/tools/brand-status.d.ts.map +1 -0
- package/dist/tools/brand-status.js +175 -0
- package/dist/tools/brand-status.js.map +1 -0
- package/dist/tools/brand-write.d.ts +3 -0
- package/dist/tools/brand-write.d.ts.map +1 -0
- package/dist/tools/brand-write.js +442 -0
- package/dist/tools/brand-write.js.map +1 -0
- package/dist/types/index.d.ts +331 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +52 -0
- package/dist/types/index.js.map +1 -0
- 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
|