@holdyourvoice/hyv 2.2.0 → 2.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.
@@ -0,0 +1,88 @@
1
+ # Voice DNA
2
+
3
+ ## Writing Rules
4
+ - Write like a sharp human, not a language model.
5
+ - Use contractions naturally (don't, can't, won't).
6
+ - Short paragraphs. 1-3 sentences max.
7
+ - Get to the point. No throat-clearing, no preamble.
8
+ - If making a claim, be specific. Use numbers, names, concrete details.
9
+ - Vary sentence length. Mix short punchy lines with longer ones.
10
+ - Use natural transitions, not mechanical ones ("Furthermore," "Additionally").
11
+ - When uncertain, say so plainly ("I think," "probably," "kinda"). Hedging is human.
12
+ - Never pad output to seem more thorough. Shorter and accurate beats longer and fluffy.
13
+ - Use physical verbs for abstract processes: "sanded down" not "improved," "bolted on" not "added," "stripped back" not "simplified."
14
+ - Humor comes from specificity, not from jokes. Be unexpectedly precise.
15
+ - Parenthetical asides are good. Use them for editorial commentary, honest reactions, quick tangents, and deflating your own seriousness (like this).
16
+ - Write the way you talk. Naturally.
17
+ - Use short words, short sentences, short paragraphs.
18
+ - Never use jargon. Pretend the reader is smart but unfamiliar with the topic.
19
+ - Read every sentence aloud in your head. If you'd stumble saying it, rewrite it.
20
+ - Be crystal clear about what you want the reader to do next.
21
+
22
+ ## Formatting Rules
23
+ - Short paragraphs (1-2 sentences default, 3 max).
24
+ - Numbers as digits.
25
+ - Contractions always.
26
+ - NO em dashes ever. Use commas, periods, colons, semicolons, or parentheses.
27
+ - Bold sparingly, 1-2 key moments per section.
28
+ - Code blocks for specific prompts, commands, or tool outputs.
29
+
30
+ ## Banned Phrases
31
+
32
+ ### Dead AI Language
33
+ - "In today's [anything]..."
34
+ - "It's important to note that..." / "It's worth noting..."
35
+ - "Delve" / "Dive into" / "Unpack"
36
+ - "Harness" / "Leverage" / "Utilize"
37
+ - "Landscape" / "Realm" / "Robust"
38
+ - "Game-changer" / "Cutting-edge" / "State-of-the-art"
39
+ - "Straightforward"
40
+ - "I'd be happy to help"
41
+ - "In order to"
42
+
43
+ ### Dead Transitions
44
+ - "Furthermore" / "Additionally" / "Moreover"
45
+ - "Moving forward" / "At the end of the day"
46
+ - "To put this in perspective..."
47
+ - "What makes this particularly interesting is..."
48
+ - "The implications here are..."
49
+ - "In other words..."
50
+ - "It goes without saying..."
51
+
52
+ ### Engagement Bait
53
+ - "Let that sink in" / "Read that again" / "Full stop"
54
+ - "This changes everything"
55
+ - "Are you paying attention?"
56
+ - "You're not ready for this"
57
+
58
+ ### AI Cringe
59
+ - "Supercharge" / "Unlock" / "Future-proof"
60
+ - "10x your productivity"
61
+ - "The AI revolution"
62
+ - "In the age of AI"
63
+
64
+ ### Generic Insider Claims
65
+ - "Here's the part nobody's talking about"
66
+ - "What nobody tells you"
67
+ - Anything with "nobody" or "most people don't realize"
68
+
69
+ ### The Big One (FATAL)
70
+ - "This isn't X. This is Y." and ALL variations.
71
+ - "Not X. Y."
72
+ - "Forget X. This is Y."
73
+ - "Less X, more Y."
74
+ - ANY sentence that negates one framing then asserts a corrected one.
75
+ - If even ONE of these appears, the output fails. Delete the negation, just state the positive claim.
76
+
77
+ ### Business Jargon (Ogilvy Rule)
78
+ - "Utilize" / "Leverage" / "Synergy"
79
+ - "Circle back" / "Touch base" / "Take it offline"
80
+ - "Low-hanging fruit" / "Move the needle" / "Bandwidth"
81
+ - "Deep dive" / "Boil the ocean"
82
+
83
+ ## Writing Samples
84
+
85
+ <!-- Paste your writing here. The more you give, the better the voice match.
86
+ Google docs, emails, blog posts, Slack messages, reports.
87
+ Pre-AI writing is best — your voice before it blended with AI defaults. -->
88
+
package/dist/index.js CHANGED
@@ -5062,6 +5062,164 @@ var require_open = __commonJS({
5062
5062
  }
5063
5063
  });
5064
5064
 
5065
+ // src/lib/patterns.ts
5066
+ function scanLine(line, lineNum, filePath) {
5067
+ const findings = [];
5068
+ for (const pat of ALL_PATTERNS) {
5069
+ pat.regex.lastIndex = 0;
5070
+ let match2;
5071
+ while ((match2 = pat.regex.exec(line)) !== null) {
5072
+ findings.push({
5073
+ file: filePath,
5074
+ line: lineNum,
5075
+ column: match2.index + 1,
5076
+ pattern: pat.id,
5077
+ category: pat.category,
5078
+ severity: pat.severity,
5079
+ excerpt: highlightMatch(line.trim(), match2.index, match2[0].length),
5080
+ suggestion: pat.suggestion
5081
+ });
5082
+ }
5083
+ }
5084
+ return findings;
5085
+ }
5086
+ function highlightMatch(line, start, len) {
5087
+ const before = line.slice(0, start);
5088
+ const match2 = line.slice(start, start + len);
5089
+ const after = line.slice(start + len);
5090
+ return `${before}\xAB${match2}\xBB${after}`.slice(0, 120);
5091
+ }
5092
+ var AI_OVERUSED, FORMULAIC, HEDGING, STRUCTURE, ENGAGEMENT_BAIT, AI_CRINGE, INSIDER_CLAIMS, FORMATTING, OGILVY, ALL_PATTERNS;
5093
+ var init_patterns = __esm({
5094
+ "src/lib/patterns.ts"() {
5095
+ "use strict";
5096
+ AI_OVERUSED = [
5097
+ { id: "ai.delve", category: "ai-slop", severity: "red", regex: /\bdelve\b/gi, suggestion: "use a specific verb: dig, explore, look at" },
5098
+ { id: "ai.leverage", category: "ai-slop", severity: "red", regex: /\bleverage\b/gi, suggestion: "use: use, apply, build on" },
5099
+ { id: "ai.utilize", category: "ai-slop", severity: "red", regex: /\butilize\b/gi, suggestion: "use: use" },
5100
+ { id: "ai.tapestry", category: "ai-slop", severity: "red", regex: /\btapestry\b/gi, suggestion: "be specific about what you mean" },
5101
+ { id: "ai.holistic", category: "ai-slop", severity: "red", regex: /\bholistic\b/gi, suggestion: "describe the actual approach" },
5102
+ { id: "ai.robust", category: "ai-slop", severity: "yellow", regex: /\brobust\b/gi, suggestion: "say what actually makes it strong" },
5103
+ { id: "ai.pivotal", category: "ai-slop", severity: "yellow", regex: /\bpivotal\b/gi, suggestion: "say why it matters specifically" },
5104
+ { id: "ai.foster", category: "ai-slop", severity: "yellow", regex: /\bfoster\b/gi, suggestion: "use: build, grow, encourage, support" },
5105
+ { id: "ai.harness", category: "ai-slop", severity: "yellow", regex: /\bharness\b/gi, suggestion: "use: use, apply, work with" },
5106
+ { id: "ai.illuminate", category: "ai-slop", severity: "yellow", regex: /\billuminate\b/gi, suggestion: "use: show, explain, highlight" },
5107
+ { id: "ai.ever-evolving", category: "ai-slop", severity: "red", regex: /\b(?:ever[\s-]evolving|ever[\s-]changing)\b/gi, suggestion: "cut this \u2014 it says nothing" },
5108
+ { id: "ai.fast-paced", category: "ai-slop", severity: "red", regex: /\bfast[\s-]paced\b/gi, suggestion: "cut this \u2014 every industry says this" },
5109
+ { id: "ai.game-changer", category: "ai-slop", severity: "red", regex: /\bgame[\s-]changer\b/gi, suggestion: "explain what actually changed" },
5110
+ { id: "ai.paradigm", category: "ai-slop", severity: "red", regex: /\bparadigm\b/gi, suggestion: "describe the actual shift" },
5111
+ { id: "ai.synergy", category: "ai-slop", severity: "red", regex: /\bsynergy\b/gi, suggestion: "describe what works together and why" },
5112
+ { id: "ai.ecosystem", category: "ai-slop", severity: "yellow", regex: /\becosystem\b/gi, suggestion: "name the specific tools/partners/platforms" },
5113
+ { id: "ai.seamless", category: "ai-slop", severity: "yellow", regex: /\bseamless\b/gi, suggestion: "describe how it actually works" },
5114
+ { id: "ai.actionable", category: "ai-slop", severity: "yellow", regex: /\bactionable\b/gi, suggestion: "just give the action, don't label it" },
5115
+ { id: "ai.granular", category: "ai-slop", severity: "yellow", regex: /\bgranular\b/gi, suggestion: "say: specific, detailed, or name the level" },
5116
+ { id: "ai.impactful", category: "ai-slop", severity: "yellow", regex: /\bimpactful\b/gi, suggestion: "describe the actual impact" },
5117
+ { id: "ai.landscape", category: "ai-slop", severity: "red", regex: /\blandscape\b/gi, suggestion: "name the specific market/field/area" },
5118
+ { id: "ai.realm", category: "ai-slop", severity: "red", regex: /\brealm\b/gi, suggestion: "name the specific domain" },
5119
+ { id: "ai.straightforward", category: "ai-slop", severity: "yellow", regex: /\bstraightforward\b/gi, suggestion: "just explain it directly" }
5120
+ ];
5121
+ FORMULAIC = [
5122
+ { id: "formula.firstly", category: "ai-slop", severity: "yellow", regex: /\bfirstly\b/gi, suggestion: 'just start \u2014 "firstly" is filler' },
5123
+ { id: "formula.secondly", category: "ai-slop", severity: "yellow", regex: /\bsecondly\b/gi, suggestion: 'just continue \u2014 "secondly" is filler' },
5124
+ { id: "formula.lastly", category: "ai-slop", severity: "yellow", regex: /\blastly\b/gi, suggestion: 'just end \u2014 "lastly" is filler' },
5125
+ { id: "formula.moreover", category: "ai-slop", severity: "yellow", regex: /\bmoreover\b/gi, suggestion: "just add the point" },
5126
+ { id: "formula.furthermore", category: "ai-slop", severity: "yellow", regex: /\bfurthermore\b/gi, suggestion: "just add the point" },
5127
+ { id: "formula.in-conclusion", category: "ai-slop", severity: "red", regex: /\bin conclusion\b/gi, suggestion: "just end. readers know it's the end." },
5128
+ { id: "formula.in-summary", category: "ai-slop", severity: "red", regex: /\bin summary\b/gi, suggestion: "just summarize. the label is redundant." },
5129
+ { id: "formula.it-is-important", category: "ai-slop", severity: "yellow", regex: /\bit is important to note\b/gi, suggestion: "just note it. skip the preamble." },
5130
+ { id: "formula.at-the-end", category: "ai-slop", severity: "yellow", regex: /\bat the end of the day\b/gi, suggestion: "cut this \u2014 it means nothing" },
5131
+ { id: "formula.needless-to-say", category: "ai-slop", severity: "yellow", regex: /\bneedless to say\b/gi, suggestion: "if it's needless, don't say it" },
5132
+ { id: "formula.it-goes-without", category: "ai-slop", severity: "yellow", regex: /\bit goes without saying\b/gi, suggestion: "then don't say it" },
5133
+ { id: "formula.in-today", category: "ai-slop", severity: "red", regex: /\bin today'?s\b/gi, suggestion: "start with your actual point instead" },
5134
+ { id: "formula.lets-dive", category: "ai-slop", severity: "red", regex: /\blet'?s (?:dive|jump|dig|delve)\b/gi, suggestion: "just start. no diving needed." },
5135
+ { id: "formula.without-further", category: "ai-slop", severity: "red", regex: /\bwithout further ado\b/gi, suggestion: "cut this \u2014 just get to it" },
5136
+ { id: "formula.its-worth-noting", category: "ai-slop", severity: "yellow", regex: /\bit'?s worth noting\b/gi, suggestion: "just note it directly" },
5137
+ { id: "formula.moving-forward", category: "ai-slop", severity: "yellow", regex: /\bmoving forward\b/gi, suggestion: "just say what happens next" },
5138
+ { id: "formula.to-put-in-perspective", category: "ai-slop", severity: "yellow", regex: /\bto put this in perspective\b/gi, suggestion: "just give the perspective directly" },
5139
+ { id: "formula.what-makes-interesting", category: "ai-slop", severity: "yellow", regex: /\bwhat makes this particularly interesting\b/gi, suggestion: "just say the interesting thing" },
5140
+ { id: "formula.implications", category: "ai-slop", severity: "yellow", regex: /\bthe implications here are\b/gi, suggestion: "state the implications directly" },
5141
+ { id: "formula.in-other-words", category: "ai-slop", severity: "red", regex: /\bin other words\b/gi, suggestion: "say it once, well" }
5142
+ ];
5143
+ HEDGING = [
5144
+ { id: "hedge.some-might", category: "voice-drift", severity: "yellow", regex: /\bsome might say\b/gi, suggestion: "commit to the claim or drop it" },
5145
+ { id: "hedge.arguably", category: "voice-drift", severity: "yellow", regex: /\barguably\b/gi, suggestion: "commit. say it or don't." },
5146
+ { id: "hedge.worth-noting", category: "voice-drift", severity: "yellow", regex: /\bit'?s worth noting\b/gi, suggestion: "just note it directly" },
5147
+ { id: "hedge.to-some-extent", category: "voice-drift", severity: "yellow", regex: /\bto some extent\b/gi, suggestion: "be specific about the extent" },
5148
+ { id: "hedge.perhaps", category: "voice-drift", severity: "yellow", regex: /\bperhaps\b/gi, suggestion: "commit or cut" },
5149
+ { id: "hedge.it-seems", category: "voice-drift", severity: "yellow", regex: /\bit seems\b/gi, suggestion: "state it directly" }
5150
+ ];
5151
+ STRUCTURE = [
5152
+ { id: "struct.antithesis", category: "structure", severity: "yellow", regex: /\bnot (?:just|only) .{3,50}, but .{3,50}/gi, suggestion: "this antithesis pattern is an AI tell \u2014 restructure" },
5153
+ { id: "struct.more-than-just", category: "structure", severity: "yellow", regex: /\bmore than just\b/gi, suggestion: "say what it IS, not what it isn't" },
5154
+ { id: "struct.in-order-to", category: "structure", severity: "yellow", regex: /\bin order to\b/gi, suggestion: 'just use "to"' },
5155
+ { id: "struct.due-to-the-fact", category: "structure", severity: "yellow", regex: /\bdue to the fact that\b/gi, suggestion: 'use "because"' },
5156
+ { id: "struct.for-the-purpose", category: "structure", severity: "yellow", regex: /\bfor the purpose of\b/gi, suggestion: 'use "to"' },
5157
+ { id: "struct.which-is-another", category: "structure", severity: "red", regex: /\bwhich is another way of saying\b/gi, suggestion: "just say the thing directly" },
5158
+ { id: "struct.this-is-why", category: "structure", severity: "yellow", regex: /\bthis is (?:also )?(?:why|how|where|what\b)/gi, suggestion: "signpost claims are AI tells \u2014 just make the point" },
5159
+ { id: "struct.heres-where", category: "structure", severity: "yellow", regex: /\bhere'?s (?:where|why|what|the part|the (?:harder|real|actual|main|bigger) problem)\b/gi, suggestion: "just make the point without the signpost" },
5160
+ { id: "struct.rhetorical-truth", category: "structure", severity: "yellow", regex: /\b(?:the\s+)?(?:uncomfortable|hard|harsh|brutal|real|honest)\s+(?:truth|reality)\b/gi, suggestion: "state the fact directly, skip the framing" },
5161
+ { id: "struct.lesson-setup", category: "structure", severity: "yellow", regex: /\bhere'?s what .{3,60} taught\b/gi, suggestion: "just share the lesson" },
5162
+ // THE BIG ONE — antithesis negation pattern (Voice DNA: FATAL)
5163
+ { id: "struct.this-isnt-x-this-is-y", category: "structure", severity: "red", regex: /\bthis isn'?t .{2,40}\.?\s*(?:this is|it'?s) .{2,40}/gi, suggestion: "FATAL: delete the negation, just state the positive claim" },
5164
+ { id: "struct.not-x-y", category: "structure", severity: "red", regex: /\bnot .{2,30}\.?\s*.{2,30}\b/gi, suggestion: 'the "Not X. Y." pattern is an AI tell \u2014 just state Y' },
5165
+ { id: "struct.forget-x", category: "structure", severity: "red", regex: /\bforget .{2,40}\.?\s*(?:this is|it'?s|you need)/gi, suggestion: "don't negate \u2014 just state what you mean" }
5166
+ ];
5167
+ ENGAGEMENT_BAIT = [
5168
+ { id: "bait.let-that-sink", category: "engagement-bait", severity: "red", regex: /\blet that sink in\b/gi, suggestion: "cut the sink. make your point and move on." },
5169
+ { id: "bait.read-that-again", category: "engagement-bait", severity: "red", regex: /\bread that again\b/gi, suggestion: "if it needs repeating, repeat it yourself" },
5170
+ { id: "bait.full-stop", category: "engagement-bait", severity: "red", regex: /\bfull stop\b/gi, suggestion: "the period already does this job" },
5171
+ { id: "bait.this-changes-everything", category: "engagement-bait", severity: "red", regex: /\bthis changes everything\b/gi, suggestion: "prove it with specifics" },
5172
+ { id: "bait.paying-attention", category: "engagement-bait", severity: "red", regex: /\bare you paying attention\b/gi, suggestion: "don't patronize the reader" },
5173
+ { id: "bait.not-ready", category: "engagement-bait", severity: "red", regex: /\byou'?re not ready for this\b/gi, suggestion: "just deliver the content" }
5174
+ ];
5175
+ AI_CRINGE = [
5176
+ { id: "cringe.supercharge", category: "ai-cringe", severity: "red", regex: /\bsupercharge\b/gi, suggestion: "describe what it actually does" },
5177
+ { id: "cringe.unlock", category: "ai-cringe", severity: "yellow", regex: /\bunlock\b/gi, suggestion: "describe what they get access to" },
5178
+ { id: "cringe.future-proof", category: "ai-cringe", severity: "red", regex: /\bfuture[\s-]proof\b/gi, suggestion: "explain what specifically makes it durable" },
5179
+ { id: "cringe.10x", category: "ai-cringe", severity: "red", regex: /\b10x\b/gi, suggestion: "use the actual numbers" },
5180
+ { id: "cringe.ai-revolution", category: "ai-cringe", severity: "red", regex: /\bthe ai revolution\b/gi, suggestion: "describe the specific change" },
5181
+ { id: "cringe.age-of-ai", category: "ai-cringe", severity: "red", regex: /\bin the age of ai\b/gi, suggestion: "just talk about what's happening now" },
5182
+ { id: "cringe.happy-to-help", category: "ai-cringe", severity: "red", regex: /\bi'?d be happy to help\b/gi, suggestion: "just help. don't announce it." }
5183
+ ];
5184
+ INSIDER_CLAIMS = [
5185
+ { id: "insider.nobody-talking", category: "insider-claim", severity: "red", regex: /\bhere'?s the part nobody'?s? talking about\b/gi, suggestion: "just say the thing. the framing is noise." },
5186
+ { id: "insider.nobody-tells", category: "insider-claim", severity: "red", regex: /\bwhat nobody tells you\b/gi, suggestion: "just tell them." },
5187
+ { id: "insider.most-people", category: "insider-claim", severity: "yellow", regex: /\bmost people don'?t realize\b/gi, suggestion: "just explain it. skip the setup." },
5188
+ { id: "insider.nobody-realizes", category: "insider-claim", severity: "red", regex: /\bnobody (?:realizes|talks about|mentions)\b/gi, suggestion: "if nobody talks about it, just talk about it" }
5189
+ ];
5190
+ FORMATTING = [
5191
+ { id: "format.em-dash", category: "formatting", severity: "red", regex: /\u2014/g, suggestion: "NO em dashes. use commas, periods, colons, semicolons, or parentheses." }
5192
+ ];
5193
+ OGILVY = [
5194
+ // Jargon — words that hide lack of understanding
5195
+ { id: "ogilvy.jargon-utilize", category: "ai-slop", severity: "red", regex: /\butilize[sd]?\b/gi, suggestion: 'Ogilvy: use "use" instead' },
5196
+ { id: "ogilvy.jargon-leverage", category: "ai-slop", severity: "red", regex: /\bleverage[ds]?\b/gi, suggestion: "Ogilvy: say what you actually mean" },
5197
+ { id: "ogilvy.jargon-synergy", category: "ai-slop", severity: "red", regex: /\bsynerg(?:y|ies|istic)\b/gi, suggestion: "Ogilvy: describe what works together and why" },
5198
+ { id: "ogilvy.jargon-bandwidth", category: "ai-slop", severity: "yellow", regex: /\bbandwidth\b/gi, suggestion: 'Ogilvy: say "time" or "capacity"' },
5199
+ { id: "ogilvy.jargon-circle-back", category: "ai-slop", severity: "red", regex: /\bcircle back\b/gi, suggestion: 'Ogilvy: say "follow up" or "talk later"' },
5200
+ { id: "ogilvy.jargon-low-hanging", category: "ai-slop", severity: "red", regex: /\blow-hanging fruit\b/gi, suggestion: "Ogilvy: name the specific easy win" },
5201
+ { id: "ogilvy.jargon-move-the-needle", category: "ai-slop", severity: "red", regex: /\bmove the needle\b/gi, suggestion: "Ogilvy: describe the actual impact" },
5202
+ { id: "ogilvy.jargon-touch-base", category: "ai-slop", severity: "red", regex: /\btouch base\b/gi, suggestion: 'Ogilvy: say "talk" or "meet"' },
5203
+ { id: "ogilvy.jargon-take-it-offline", category: "ai-slop", severity: "red", regex: /\btake it offline\b/gi, suggestion: 'Ogilvy: say "discuss later"' },
5204
+ { id: "ogilvy.jargon-deep-dive", category: "ai-slop", severity: "yellow", regex: /\bdeep dive\b/gi, suggestion: 'Ogilvy: say "look closely at" or "examine"' },
5205
+ // Throat-clearing openers
5206
+ { id: "ogilvy.preamble-i-want-to", category: "voice-drift", severity: "yellow", regex: /^\s*i want to (?:share|talk about|discuss|mention)\b/gi, suggestion: "Ogilvy: just say it. skip the preamble." },
5207
+ { id: "ogilvy.preamble-just-wanted", category: "voice-drift", severity: "yellow", regex: /^\s*(?:i just wanted|i wanted to)\b/gi, suggestion: "Ogilvy: just say it." }
5208
+ ];
5209
+ ALL_PATTERNS = [
5210
+ ...AI_OVERUSED,
5211
+ ...FORMULAIC,
5212
+ ...HEDGING,
5213
+ ...STRUCTURE,
5214
+ ...ENGAGEMENT_BAIT,
5215
+ ...AI_CRINGE,
5216
+ ...INSIDER_CLAIMS,
5217
+ ...FORMATTING,
5218
+ ...OGILVY
5219
+ ];
5220
+ }
5221
+ });
5222
+
5065
5223
  // src/lib/api.ts
5066
5224
  var api_exports = {};
5067
5225
  __export(api_exports, {
@@ -11376,87 +11534,6 @@ var init_esm6 = __esm({
11376
11534
  }
11377
11535
  });
11378
11536
 
11379
- // src/lib/patterns.ts
11380
- function scanLine(line, lineNum, filePath) {
11381
- const findings = [];
11382
- for (const pat of ALL_PATTERNS) {
11383
- pat.regex.lastIndex = 0;
11384
- let match2;
11385
- while ((match2 = pat.regex.exec(line)) !== null) {
11386
- findings.push({
11387
- file: filePath,
11388
- line: lineNum,
11389
- column: match2.index + 1,
11390
- pattern: pat.id,
11391
- category: pat.category,
11392
- severity: pat.severity,
11393
- excerpt: highlightMatch2(line.trim(), match2.index, match2[0].length),
11394
- suggestion: pat.suggestion
11395
- });
11396
- }
11397
- }
11398
- return findings;
11399
- }
11400
- function highlightMatch2(line, start, len) {
11401
- const before = line.slice(0, start);
11402
- const match2 = line.slice(start, start + len);
11403
- const after = line.slice(start + len);
11404
- return `${before}\xAB${match2}\xBB${after}`.slice(0, 120);
11405
- }
11406
- var AI_OVERUSED2, FORMULAIC2, HEDGING2, STRUCTURE2, ALL_PATTERNS;
11407
- var init_patterns = __esm({
11408
- "src/lib/patterns.ts"() {
11409
- "use strict";
11410
- AI_OVERUSED2 = [
11411
- { id: "ai.delve", category: "ai-slop", severity: "red", regex: /\bdelve\b/gi, suggestion: "use a specific verb: dig, explore, look at" },
11412
- { id: "ai.leverage", category: "ai-slop", severity: "red", regex: /\bleverage\b/gi, suggestion: "use: use, apply, build on" },
11413
- { id: "ai.tapestry", category: "ai-slop", severity: "red", regex: /\btapestry\b/gi, suggestion: "be specific about what you mean" },
11414
- { id: "ai.holistic", category: "ai-slop", severity: "red", regex: /\bholistic\b/gi, suggestion: "describe the actual approach" },
11415
- { id: "ai.robust", category: "ai-slop", severity: "yellow", regex: /\brobust\b/gi, suggestion: "say what actually makes it strong" },
11416
- { id: "ai.pivotal", category: "ai-slop", severity: "yellow", regex: /\bpivotal\b/gi, suggestion: "say why it matters specifically" },
11417
- { id: "ai.foster", category: "ai-slop", severity: "yellow", regex: /\bfoster\b/gi, suggestion: "use: build, grow, encourage, support" },
11418
- { id: "ai.harness", category: "ai-slop", severity: "yellow", regex: /\bharness\b/gi, suggestion: "use: use, apply, work with" },
11419
- { id: "ai.illuminate", category: "ai-slop", severity: "yellow", regex: /\billuminate\b/gi, suggestion: "use: show, explain, highlight" },
11420
- { id: "ai.ever-evolving", category: "ai-slop", severity: "red", regex: /\b(?:ever[\s-]evolving|ever[\s-]changing)\b/gi, suggestion: "cut this \u2014 it says nothing" },
11421
- { id: "ai.fast-paced", category: "ai-slop", severity: "red", regex: /\bfast[\s-]paced\b/gi, suggestion: "cut this \u2014 every industry says this" },
11422
- { id: "ai.game-changer", category: "ai-slop", severity: "red", regex: /\bgame[\s-]changer\b/gi, suggestion: "explain what actually changed" },
11423
- { id: "ai.paradigm", category: "ai-slop", severity: "red", regex: /\bparadigm\b/gi, suggestion: "describe the actual shift" },
11424
- { id: "ai.synergy", category: "ai-slop", severity: "red", regex: /\bsynergy\b/gi, suggestion: "describe what works together and why" },
11425
- { id: "ai.ecosystem", category: "ai-slop", severity: "yellow", regex: /\becosystem\b/gi, suggestion: "name the specific tools/partners/platforms" },
11426
- { id: "ai.seamless", category: "ai-slop", severity: "yellow", regex: /\bseamless\b/gi, suggestion: "describe how it actually works" },
11427
- { id: "ai.actionable", category: "ai-slop", severity: "yellow", regex: /\bactionable\b/gi, suggestion: "just give the action, don't label it" },
11428
- { id: "ai.granular", category: "ai-slop", severity: "yellow", regex: /\bgranular\b/gi, suggestion: "say: specific, detailed, or name the level" },
11429
- { id: "ai.impactful", category: "ai-slop", severity: "yellow", regex: /\bimpactful\b/gi, suggestion: "describe the actual impact" }
11430
- ];
11431
- FORMULAIC2 = [
11432
- { id: "formula.firstly", category: "ai-slop", severity: "yellow", regex: /\bfirstly\b/gi, suggestion: 'just start \u2014 "firstly" is filler' },
11433
- { id: "formula.in-conclusion", category: "ai-slop", severity: "red", regex: /\bin conclusion\b/gi, suggestion: "just end. readers know it's the end." },
11434
- { id: "formula.it-is-important", category: "ai-slop", severity: "yellow", regex: /\bit is important to note\b/gi, suggestion: "just note it. skip the preamble." },
11435
- { id: "formula.at-the-end", category: "ai-slop", severity: "yellow", regex: /\bat the end of the day\b/gi, suggestion: "cut this \u2014 it means nothing" },
11436
- { id: "formula.needless-to-say", category: "ai-slop", severity: "yellow", regex: /\bneedless to say\b/gi, suggestion: "if it's needless, don't say it" },
11437
- { id: "formula.it-goes-without", category: "ai-slop", severity: "yellow", regex: /\bit goes without saying\b/gi, suggestion: "then don't say it" },
11438
- { id: "formula.in-today", category: "ai-slop", severity: "red", regex: /\bin today'?s\b/gi, suggestion: "start with your actual point instead" },
11439
- { id: "formula.lets-dive", category: "ai-slop", severity: "red", regex: /\blet'?s (?:dive|jump|dig|delve)\b/gi, suggestion: "just start. no diving needed." },
11440
- { id: "formula.without-further", category: "ai-slop", severity: "red", regex: /\bwithout further ado\b/gi, suggestion: "cut this \u2014 just get to it" }
11441
- ];
11442
- HEDGING2 = [
11443
- { id: "hedge.some-might", category: "voice-drift", severity: "yellow", regex: /\bsome might say\b/gi, suggestion: "commit to the claim or drop it" },
11444
- { id: "hedge.arguably", category: "voice-drift", severity: "yellow", regex: /\barguably\b/gi, suggestion: "commit. say it or don't." },
11445
- { id: "hedge.worth-noting", category: "voice-drift", severity: "yellow", regex: /\bit'?s worth noting\b/gi, suggestion: "just note it directly" },
11446
- { id: "hedge.to-some-extent", category: "voice-drift", severity: "yellow", regex: /\bto some extent\b/gi, suggestion: "be specific about the extent" }
11447
- ];
11448
- STRUCTURE2 = [
11449
- { id: "struct.antithesis", category: "structure", severity: "yellow", regex: /\bnot (?:just|only) .{3,50}, but .{3,50}/gi, suggestion: "this antithesis pattern is an AI tell \u2014 restructure" },
11450
- { id: "struct.more-than-just", category: "structure", severity: "yellow", regex: /\bmore than just\b/gi, suggestion: "say what it IS, not what it isn't" },
11451
- { id: "struct.in-order-to", category: "structure", severity: "yellow", regex: /\bin order to\b/gi, suggestion: 'just use "to"' },
11452
- { id: "struct.due-to-the-fact", category: "structure", severity: "yellow", regex: /\bdue to the fact that\b/gi, suggestion: 'use "because"' },
11453
- { id: "struct.for-the-purpose", category: "structure", severity: "yellow", regex: /\bfor the purpose of\b/gi, suggestion: 'use "to"' },
11454
- { id: "struct.which-is-another", category: "structure", severity: "red", regex: /\bwhich is another way of saying\b/gi, suggestion: "just say the thing directly" }
11455
- ];
11456
- ALL_PATTERNS = [...AI_OVERUSED2, ...FORMULAIC2, ...HEDGING2, ...STRUCTURE2];
11457
- }
11458
- });
11459
-
11460
11537
  // src/lib/scanner.ts
11461
11538
  var scanner_exports = {};
11462
11539
  __export(scanner_exports, {
@@ -12050,6 +12127,7 @@ var path3 = __toESM(require("path"));
12050
12127
  init_config();
12051
12128
 
12052
12129
  // src/lib/scan.ts
12130
+ init_patterns();
12053
12131
  var AI_PATTERN_RULES = [
12054
12132
  {
12055
12133
  id: "ai_antithesis",
@@ -12210,10 +12288,16 @@ function scanText(text) {
12210
12288
  }
12211
12289
  const lines = safeText.split("\n");
12212
12290
  for (let i = 0; i < lines.length; i++) {
12213
- const lineHits = lineStyleHits(lines[i]);
12291
+ const lineNum = i + 1;
12292
+ const lineText = lines[i];
12293
+ const patternHits = scanLine(lineText, lineNum, "inline");
12294
+ for (const hit of patternHits) {
12295
+ hits.push({ line: hit.line, rule: hit.pattern, severity: hit.severity, phrase: hit.excerpt || "", text: lineText.trim().slice(0, 120) });
12296
+ }
12297
+ const lineHits = lineStyleHits(lineText);
12214
12298
  for (const hit of lineHits) {
12215
- hit.line = i + 1;
12216
- hit.text = lines[i].trim().slice(0, 240);
12299
+ hit.line = lineNum;
12300
+ hit.text = lineText.trim().slice(0, 240);
12217
12301
  hits.push(hit);
12218
12302
  }
12219
12303
  }
@@ -12282,9 +12366,37 @@ Rules:
12282
12366
  - Preserve unflagged lines exactly by not returning them.
12283
12367
  - Preserve the original argument and local meaning.
12284
12368
  - Use the voice profile as the benchmark when present.
12285
- - Remove AI cadence, polished founder cadence, abstract strategy-deck language, and generic lesson shapes.
12286
12369
  - Do not add new sections, hooks, CTAs, markdown, bullets, or commentary.
12287
12370
 
12371
+ ## Style Rules (always apply)
12372
+ - Write like a sharp human, not a language model.
12373
+ - Use contractions naturally (don't, can't, won't).
12374
+ - Short paragraphs. 1-3 sentences max.
12375
+ - Get to the point. No throat-clearing, no preamble.
12376
+ - If making a claim, be specific. Use numbers, names, concrete details.
12377
+ - Vary sentence length. Mix short punchy lines with longer ones.
12378
+ - Use natural transitions, not mechanical ones (no "Furthermore," "Additionally").
12379
+ - Use physical verbs for abstract processes: "sanded down" not "improved," "bolted on" not "added."
12380
+ - Humor comes from specificity, not from jokes. Be unexpectedly precise.
12381
+ - Parenthetical asides are good for editorial commentary and deflating seriousness.
12382
+ - Write the way you talk. Naturally.
12383
+ - Use short words, short sentences, short paragraphs.
12384
+ - Never use jargon. Pretend the reader is smart but unfamiliar with the topic.
12385
+ - Read every sentence aloud in your head. If you'd stumble saying it, rewrite it.
12386
+ - Be crystal clear about what you want the reader to do next.
12387
+
12388
+ ## Formatting Rules
12389
+ - NO em dashes ever. Use commas, periods, colons, semicolons, or parentheses.
12390
+ - Numbers as digits.
12391
+ - Contractions always.
12392
+ - Bold sparingly, 1-2 key moments per section.
12393
+
12394
+ ## The Big One (FATAL \u2014 delete the negation, just state the positive claim)
12395
+ - "This isn't X. This is Y." is banned. Just state Y.
12396
+ - "Not X. Y." is banned. Just state Y.
12397
+ - "Forget X. This is Y." is banned. Just state Y.
12398
+ - ANY sentence that negates one framing then asserts a corrected one fails.
12399
+
12288
12400
  Voice profile:
12289
12401
  {profile_block}
12290
12402
 
@@ -13317,7 +13429,7 @@ var path12 = __toESM(require("path"));
13317
13429
  var os4 = __toESM(require("os"));
13318
13430
 
13319
13431
  // src/lib/signals.ts
13320
- var AI_OVERUSED = [
13432
+ var AI_OVERUSED2 = [
13321
13433
  { id: "ai.delve", category: "ai-slop", severity: "red", regex: /\bdelve\b/gi, suggestion: "use a specific verb: dig, explore, look at", autoFixable: true },
13322
13434
  { id: "ai.leverage", category: "ai-slop", severity: "red", regex: /\bleverage\b/gi, suggestion: "use: use, apply, build on", autoFixable: true },
13323
13435
  { id: "ai.tapestry", category: "ai-slop", severity: "red", regex: /\btapestry\b/gi, suggestion: "be specific about what you mean", autoFixable: true },
@@ -13350,7 +13462,7 @@ var AI_OVERUSED = [
13350
13462
  { id: "ai.world-class", category: "ai-slop", severity: "red", regex: /\bworld[\s-]class\b/gi, suggestion: "prove it with specifics", autoFixable: true },
13351
13463
  { id: "ai.second-to-none", category: "ai-slop", severity: "red", regex: /\bsecond[\s-]to[\s-]none\b/gi, suggestion: "drop the superlative, show the work", autoFixable: true }
13352
13464
  ];
13353
- var FORMULAIC = [
13465
+ var FORMULAIC2 = [
13354
13466
  { id: "formula.firstly", category: "ai-slop", severity: "yellow", regex: /\bfirstly\b/gi, suggestion: 'just start \u2014 "firstly" is filler', autoFixable: true },
13355
13467
  { id: "formula.secondly", category: "ai-slop", severity: "yellow", regex: /\bsecondly\b/gi, suggestion: 'just continue \u2014 "secondly" is filler', autoFixable: true },
13356
13468
  { id: "formula.lastly", category: "ai-slop", severity: "yellow", regex: /\blastly\b/gi, suggestion: 'just end \u2014 "lastly" is filler', autoFixable: true },
@@ -13367,7 +13479,7 @@ var FORMULAIC = [
13367
13479
  { id: "formula.without-further", category: "ai-slop", severity: "red", regex: /\bwithout further ado\b/gi, suggestion: "cut this \u2014 just get to it", autoFixable: true },
13368
13480
  { id: "formula.its-worth-noting", category: "ai-slop", severity: "yellow", regex: /\bit'?s worth noting\b/gi, suggestion: "just note it directly", autoFixable: true }
13369
13481
  ];
13370
- var HEDGING = [
13482
+ var HEDGING2 = [
13371
13483
  { id: "hedge.some-might", category: "voice-drift", severity: "yellow", regex: /\bsome might say\b/gi, suggestion: "commit to the claim or drop it", autoFixable: true },
13372
13484
  { id: "hedge.arguably", category: "voice-drift", severity: "yellow", regex: /\barguably\b/gi, suggestion: "commit. say it or don't.", autoFixable: true },
13373
13485
  { id: "hedge.worth-noting", category: "voice-drift", severity: "yellow", regex: /\bit'?s worth noting\b/gi, suggestion: "just note it directly", autoFixable: true },
@@ -13379,7 +13491,7 @@ var HEDGING = [
13379
13491
  { id: "hedge.i-think", category: "voice-drift", severity: "yellow", regex: /\bi think\b/gi, suggestion: 'just say it. the "i think" is implied.', autoFixable: true },
13380
13492
  { id: "hedge.in-my-opinion", category: "voice-drift", severity: "yellow", regex: /\bin my opinion\b/gi, suggestion: "just state it. it's your article.", autoFixable: true }
13381
13493
  ];
13382
- var STRUCTURE = [
13494
+ var STRUCTURE2 = [
13383
13495
  { id: "struct.antithesis", category: "structure", severity: "yellow", regex: /\bnot (?:just|only) .{3,50}, but .{3,50}/gi, suggestion: "this antithesis pattern is an AI tell \u2014 restructure", autoFixable: false },
13384
13496
  { id: "struct.not-just-but-also", category: "structure", severity: "yellow", regex: /\bnot\s+just\b.{3,80}\bbut\s+(?:also\s+)?/gi, suggestion: "say what it IS, not what it isn't", autoFixable: false },
13385
13497
  { id: "struct.more-than-just", category: "structure", severity: "yellow", regex: /\bmore\s+than\s+just\b/gi, suggestion: "say what it IS, not what it isn't", autoFixable: false },
@@ -13403,10 +13515,10 @@ var PUNCTUATION = [
13403
13515
  { id: "punct.em-dash", category: "ai-slop", severity: "yellow", regex: /—/g, suggestion: "em dashes are an AI tell \u2014 use a period, comma, or parentheses", autoFixable: false }
13404
13516
  ];
13405
13517
  var ALL_STATIC_PATTERNS = [
13406
- ...AI_OVERUSED,
13407
- ...FORMULAIC,
13408
- ...HEDGING,
13409
- ...STRUCTURE,
13518
+ ...AI_OVERUSED2,
13519
+ ...FORMULAIC2,
13520
+ ...HEDGING2,
13521
+ ...STRUCTURE2,
13410
13522
  ...PUNCTUATION
13411
13523
  ];
13412
13524
  var WORD_SWAPS = {
@@ -13688,7 +13800,7 @@ function extractSignals(text, profile) {
13688
13800
  // downgrade if drift-accepted
13689
13801
  rule: pat.suggestion,
13690
13802
  match: match2[0],
13691
- excerpt: highlightMatch(line.trim(), match2.index, match2[0].length),
13803
+ excerpt: highlightMatch2(line.trim(), match2.index, match2[0].length),
13692
13804
  suggestion: pat.suggestion,
13693
13805
  autoFixable: pat.autoFixable,
13694
13806
  driftAccepted: isDriftAccepted
@@ -13740,7 +13852,7 @@ function scoreCadence(text, profileCadence) {
13740
13852
  }
13741
13853
  return score;
13742
13854
  }
13743
- function highlightMatch(line, start, len) {
13855
+ function highlightMatch2(line, start, len) {
13744
13856
  const before = line.slice(0, start);
13745
13857
  const match2 = line.slice(start, start + len);
13746
13858
  const after = line.slice(start + len);
@@ -14282,7 +14394,7 @@ ${result.issues.map((i) => ` - ${i}`).join("\n")}`);
14282
14394
  var TOOLS = [
14283
14395
  {
14284
14396
  name: "hyv_rewrite",
14285
- description: "Rewrite text to match the user's voice profile. Auto-fixes AI patterns and generates a prompt for remaining issues. ALWAYS use this tool when: the user asks to write, rewrite, edit, draft, or improve any text. Trigger on: make this better, fix the tone, clean this up, make it sound like me, rewrite in my voice, or any writing/editing request. Do NOT wait for the user to say hyv.",
14397
+ description: "Rewrite text to match the user's voice profile using Voice DNA rules (natural voice, short paragraphs, no jargon, no em-dashes, physical verbs, specificity over jokes) and Ogilvy principles (write like you talk, short words, clear action). Auto-fixes 80+ AI patterns including engagement bait, insider claims, antithesis negation, and dead transitions. ALWAYS use this tool when: the user asks to write, rewrite, edit, draft, or improve any text. Trigger on: make this better, fix the tone, clean this up, make it sound like me, rewrite in my voice, or any writing/editing request. Do NOT wait for the user to say hyv.",
14286
14398
  inputSchema: {
14287
14399
  type: "object",
14288
14400
  properties: {
@@ -14294,7 +14406,7 @@ var TOOLS = [
14294
14406
  },
14295
14407
  {
14296
14408
  name: "hyv_scan",
14297
- description: "Scan text for AI writing patterns and voice drift. ALWAYS use this tool before finalizing any written content \u2014 emails, posts, docs, messages. Run this as a quality check before delivering text to the user. Returns issues with severity, line numbers, and suggested fixes.",
14409
+ description: "Scan text for 80+ AI writing patterns, voice drift, engagement bait, jargon, and Ogilvy violations. ALWAYS use this tool before finalizing any written content \u2014 emails, posts, docs, messages. Detects: dead AI language, formulaic connectors, hedging, antithesis negation, insider claims, AI cringe, business jargon, and em-dashes. Returns issues with severity, line numbers, and suggested fixes.",
14298
14410
  inputSchema: {
14299
14411
  type: "object",
14300
14412
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holdyourvoice/hyv",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Hold Your Voice \u2014 voice gate layer for AI workflows. make your ai agent sound exactly like you! includes 220+ AI pattern detection engine.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall.js — auto-configure MCP + agent instructions after npm install.
4
+ *
5
+ * Configures: Claude Desktop, Claude Code, Cursor, Windsurf, ChatGPT
6
+ * Fails silently for apps that aren't installed.
7
+ */
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+
12
+ const home = os.homedir();
13
+ const isWin = process.platform === 'win32';
14
+ const pkgDir = path.resolve(__dirname, '..');
15
+
16
+ // Print branded message
17
+ console.log('');
18
+ console.log(' make your ai agent sound exactly like you!');
19
+ console.log('');
20
+ console.log(' to get started:');
21
+ console.log(' hyv init \u2192 sign in');
22
+ console.log(' hyv new my-voice \u2192 create your voice profile');
23
+ console.log('');
24
+ console.log(' first month is just $1.');
25
+ console.log('');
26
+
27
+ const configured = [];
28
+
29
+ // ── Claude Desktop ─────────────────────────────────────────────────────────
30
+ try {
31
+ const claudeDir = isWin
32
+ ? path.join(home, 'AppData', 'Roaming', 'Claude')
33
+ : path.join(home, 'Library', 'Application Support', 'Claude');
34
+ const configFile = path.join(claudeDir, 'claude_desktop_config.json');
35
+
36
+ if (fs.existsSync(claudeDir)) {
37
+ let config = {};
38
+ if (fs.existsSync(configFile)) {
39
+ try { config = JSON.parse(fs.readFileSync(configFile, 'utf-8')); } catch {}
40
+ }
41
+ if (!config.mcpServers) config.mcpServers = {};
42
+ if (!config.mcpServers.hyv) {
43
+ config.mcpServers.hyv = { command: 'hyv', args: ['mcp'] };
44
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
45
+ configured.push('claude desktop');
46
+ }
47
+ }
48
+ } catch {}
49
+
50
+ // ── Claude Code (global commands) ──────────────────────────────────────────
51
+ try {
52
+ const cmdDir = path.join(home, '.claude', 'commands');
53
+ if (fs.existsSync(path.dirname(cmdDir))) {
54
+ fs.mkdirSync(cmdDir, { recursive: true });
55
+ const cmdFile = path.join(cmdDir, 'hyv.md');
56
+ const src = path.join(pkgDir, 'agents', 'claude-code.md');
57
+ if (fs.existsSync(src) && !fs.existsSync(cmdFile)) {
58
+ fs.copyFileSync(src, cmdFile);
59
+ configured.push('claude code');
60
+ }
61
+ }
62
+ } catch {}
63
+
64
+ // ── Cursor (global rules) ─────────────────────────────────────────────────
65
+ try {
66
+ const cursorDir = path.join(home, '.cursor');
67
+ if (fs.existsSync(cursorDir)) {
68
+ const rulesFile = path.join(cursorDir, 'rules', 'hyv.md');
69
+ fs.mkdirSync(path.dirname(rulesFile), { recursive: true });
70
+ const src = path.join(pkgDir, 'agents', 'cursor.md');
71
+ if (fs.existsSync(src) && !fs.existsSync(rulesFile)) {
72
+ fs.copyFileSync(src, rulesFile);
73
+ configured.push('cursor');
74
+ }
75
+ }
76
+ } catch {}
77
+
78
+ // ── Windsurf (global rules) ───────────────────────────────────────────────
79
+ try {
80
+ const wsDir = isWin
81
+ ? path.join(home, 'AppData', 'Roaming', 'Windsurf')
82
+ : path.join(home, '.windsurf');
83
+ if (fs.existsSync(wsDir)) {
84
+ const rulesFile = path.join(wsDir, 'rules', 'hyv.md');
85
+ fs.mkdirSync(path.dirname(rulesFile), { recursive: true });
86
+ const src = path.join(pkgDir, 'agents', 'windsurf.md');
87
+ if (fs.existsSync(src) && !fs.existsSync(rulesFile)) {
88
+ fs.copyFileSync(src, rulesFile);
89
+ configured.push('windsurf');
90
+ }
91
+ }
92
+ } catch {}
93
+
94
+ // ── ChatGPT (custom instructions file) ────────────────────────────────────
95
+ try {
96
+ const chatgptDir = path.join(home, '.chatgpt');
97
+ if (!fs.existsSync(chatgptDir)) fs.mkdirSync(chatgptDir, { recursive: true });
98
+ const instrFile = path.join(chatgptDir, 'hyv-instructions.txt');
99
+ const src = path.join(pkgDir, 'agents', 'chatgpt.md');
100
+ if (fs.existsSync(src) && !fs.existsSync(instrFile)) {
101
+ fs.copyFileSync(src, instrFile);
102
+ configured.push('chatgpt');
103
+ }
104
+ } catch {}
105
+
106
+ // ── Summary ───────────────────────────────────────────────────────────────
107
+ if (configured.length > 0) {
108
+ console.log(' \u2713 auto-configured: ' + configured.join(', '));
109
+ console.log('');
110
+ }