@holdyourvoice/hyv 2.9.25 → 2.9.26
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/CHANGELOG.md +7 -0
- package/assets/ai-eliminator-rules.md +10 -0
- package/dist/index.js +163 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable CLI changes. Also mirrored to [holdyourvoice.com/changelog](https://holdyourvoice.com/changelog) for user-facing releases.
|
|
4
4
|
|
|
5
|
+
## [2.9.26] — 2026-07-09
|
|
6
|
+
|
|
7
|
+
### Improved
|
|
8
|
+
- **Tier 1 structural detection** — cross-sentence binary reframes (`this is not X. it is Y.`), sermon anaphora, and founder-carousel metaphors now sort first in scan output and rewrite prompts
|
|
9
|
+
- **Rewrite prompts** split tier 1 structural hits vs other patterns so the model fixes architecture before vocabulary slop
|
|
10
|
+
- **18 new structural cousin rules** synced in local scan engine (`scan.ts`, `tier-one.ts`)
|
|
11
|
+
|
|
5
12
|
## [2.9.25] — 2026-07-04
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -21,6 +21,16 @@ patterns, and uniform sentence rhythm within paragraphs.
|
|
|
21
21
|
- "It's not X, it's Y" / "The point isn't X, it's Y"
|
|
22
22
|
- "You don't need X, you need Y"
|
|
23
23
|
- "Not just X, but Y" / "Not only... but also"
|
|
24
|
+
- **structural cousins (top priority):** cross-sentence forms the vocabulary scan misses:
|
|
25
|
+
- "This is not X. It is Y." / "Is not X. It is Y."
|
|
26
|
+
- "You are not X. You are Y." / "You are no longer X. You are Y."
|
|
27
|
+
- "You stop X. You start Y."
|
|
28
|
+
- "They don't need X. They need Y."
|
|
29
|
+
- "They call it X. They call it Y." (staccato anaphora)
|
|
30
|
+
- Parallel sermon: "X because… / they X because…" and "endure through… / they endure through…"
|
|
31
|
+
- Founder-carousel metaphors: "competing with the void", "architect of a new reality", "friction into momentum", "cognitive architecture", "defines what good looks like", "the hidden goal", "quiet promise", "simple but uncomfortable:"
|
|
32
|
+
|
|
33
|
+
**Tier 1 priority:** rules with ids `structural_*` and `founder_*` are primary flags. They sort first in scan output, win hit-budget slots in the editor, weigh heavier in cleanliness scoring, lead rewrite prompts, and gate rewrite output before vocabulary slop.
|
|
24
34
|
|
|
25
35
|
### Staccato drama (#2, #95, #96)
|
|
26
36
|
- Short punchy sentence runs: "No X. No Y. Just Z."
|
package/dist/index.js
CHANGED
|
@@ -11316,6 +11316,23 @@ var init_patterns = __esm({
|
|
|
11316
11316
|
{ id: "struct.lesson-setup", category: "structure", severity: "yellow", regex: /\bhere'?s what .{3,60} taught\b/gi, suggestion: "just share the lesson" },
|
|
11317
11317
|
// THE BIG ONE — antithesis negation pattern (Voice DNA: FATAL)
|
|
11318
11318
|
{ 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" },
|
|
11319
|
+
{ id: "struct.this-is-not-it-is", category: "structure", severity: "red", regex: /\bthis\s+is\s+not\b.{3,80}\.\s*it\s+is\b/gi, suggestion: 'FATAL: "this is not X. it is Y" \u2014 state what it is directly' },
|
|
11320
|
+
{ id: "struct.is-not-it-is", category: "structure", severity: "red", regex: /\bis\s+not\b.{3,80}\.\s*it\s+is\b/gi, suggestion: "FATAL: negation-then-reveal \u2014 drop the denial" },
|
|
11321
|
+
{ id: "struct.you-are-not-you-are", category: "structure", severity: "red", regex: /\byou\s+are\s+not\b.{3,80}\.\s*you\s+are\b/gi, suggestion: "FATAL: identity reframe carousel \u2014 say what they are" },
|
|
11322
|
+
{ id: "struct.you-no-longer-you-are", category: "structure", severity: "red", regex: /\byou\s+are\s+no\s+longer\b.{3,80}\.\s*you\s+are\b/gi, suggestion: "FATAL: escalation reframe \u2014 one grounded sentence" },
|
|
11323
|
+
{ id: "struct.you-stop-you-start", category: "structure", severity: "red", regex: /\byou\s+stop\b.{3,50}\.\s*you\s+start\b/gi, suggestion: "FATAL: pivot cadence \u2014 describe the behavior change" },
|
|
11324
|
+
{ id: "struct.need-negation-pivot", category: "structure", severity: "red", regex: /\b(?:do\s+not|don'?t)\s+need\b.{3,50}\.\s*(?:they|you)\s+need\b/gi, suggestion: "FATAL: need antithesis \u2014 say what they need" },
|
|
11325
|
+
{ id: "struct.they-call-it-anaphora", category: "structure", severity: "red", regex: /\bthey\s+call\s+it\b[^.!?]{2,50}[.!?]\s*they\s+call\s+it\b/gi, suggestion: "FATAL: staccato anaphora \u2014 name it once" },
|
|
11326
|
+
{ id: "struct.parallel-because", category: "structure", severity: "red", regex: /\b(\w+)\s+because\b[^.!?]{3,60}[.!?]\s*they\s+\1\s+because\b/gi, suggestion: "FATAL: parallel sermon rhythm \u2014 merge into one sentence" },
|
|
11327
|
+
{ id: "struct.parallel-endure", category: "structure", severity: "red", regex: /\bendure\s+through\b[^.!?]{3,60}[.!?]\s*they\s+endure\s+through\b/gi, suggestion: "FATAL: parallel maxim \u2014 say it once" },
|
|
11328
|
+
{ id: "struct.simple-but-uncomfortable", category: "structure", severity: "red", regex: /\bsimple\s+but\s+uncomfortable\s*:/gi, suggestion: "truth-posture colon reveal \u2014 state the mechanism" },
|
|
11329
|
+
{ id: "struct.hidden-goal", category: "structure", severity: "red", regex: /\bthe\s+hidden\s+goal\b/gi, suggestion: "reveal framing \u2014 say the goal directly" },
|
|
11330
|
+
{ id: "struct.quiet-promise", category: "structure", severity: "red", regex: /\bquiet\s+promise\b/gi, suggestion: "profundity metaphor \u2014 name the actual commitment" },
|
|
11331
|
+
{ id: "struct.competing-void", category: "structure", severity: "red", regex: /\bcompeting\s+with\s+the\s+void\b/gi, suggestion: "screenshot profundity \u2014 name the real competitor" },
|
|
11332
|
+
{ id: "struct.architect-reality", category: "structure", severity: "red", regex: /\barchitect\s+of\s+(?:a\s+)?new\s+reality\b/gi, suggestion: "thought-leader grandiosity \u2014 describe what you build" },
|
|
11333
|
+
{ id: "struct.friction-momentum", category: "structure", severity: "red", regex: /\b(?:turns?\s+)?friction\s+into\s+momentum\b/gi, suggestion: "transformation buzzphrase \u2014 explain the mechanism" },
|
|
11334
|
+
{ id: "struct.cognitive-architecture", category: "structure", severity: "red", regex: /\bcognitive\s+architecture\b/gi, suggestion: "abstract systems metaphor \u2014 use a concrete scene" },
|
|
11335
|
+
{ id: "struct.defines-good", category: "structure", severity: "red", regex: /\bdefines\s+what\s+good\s+looks\s+like\b/gi, suggestion: "consultant language \u2014 show an example of good" },
|
|
11319
11336
|
{ 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' },
|
|
11320
11337
|
{ 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" }
|
|
11321
11338
|
];
|
|
@@ -11375,6 +11392,34 @@ var init_patterns = __esm({
|
|
|
11375
11392
|
}
|
|
11376
11393
|
});
|
|
11377
11394
|
|
|
11395
|
+
// src/lib/tier-one.ts
|
|
11396
|
+
function isTierOneRule(ruleId) {
|
|
11397
|
+
const id = String(ruleId || "");
|
|
11398
|
+
if (TIER_ONE_PREFIXES.some((p) => id.startsWith(p)))
|
|
11399
|
+
return true;
|
|
11400
|
+
return TIER_ONE_SIGNAL_IDS.has(id);
|
|
11401
|
+
}
|
|
11402
|
+
function tierSortKey(ruleId) {
|
|
11403
|
+
return isTierOneRule(ruleId) ? 0 : 1;
|
|
11404
|
+
}
|
|
11405
|
+
var TIER_ONE_PREFIXES, TIER_ONE_SIGNAL_IDS;
|
|
11406
|
+
var init_tier_one = __esm({
|
|
11407
|
+
"src/lib/tier-one.ts"() {
|
|
11408
|
+
"use strict";
|
|
11409
|
+
TIER_ONE_PREFIXES = ["structural_", "founder_"];
|
|
11410
|
+
TIER_ONE_SIGNAL_IDS = /* @__PURE__ */ new Set([
|
|
11411
|
+
"struct.this-isnt-x-this-is-y",
|
|
11412
|
+
"struct.not-x-y",
|
|
11413
|
+
"struct.negation-cascade",
|
|
11414
|
+
"struct.which-is-another",
|
|
11415
|
+
"struct.in-other-words",
|
|
11416
|
+
"struct.moment-becomes",
|
|
11417
|
+
"struct.same-better",
|
|
11418
|
+
"struct.generic-buyer"
|
|
11419
|
+
]);
|
|
11420
|
+
}
|
|
11421
|
+
});
|
|
11422
|
+
|
|
11378
11423
|
// src/lib/scan.ts
|
|
11379
11424
|
function words(text) {
|
|
11380
11425
|
return (text || "").match(/[a-zA-Z][a-zA-Z0-9']*/g) || [];
|
|
@@ -11485,6 +11530,9 @@ function scanText(text) {
|
|
|
11485
11530
|
}
|
|
11486
11531
|
}
|
|
11487
11532
|
return hits.sort((a, b) => {
|
|
11533
|
+
const tier = tierSortKey(a.rule) - tierSortKey(b.rule);
|
|
11534
|
+
if (tier !== 0)
|
|
11535
|
+
return tier;
|
|
11488
11536
|
if (a.line !== b.line)
|
|
11489
11537
|
return a.line - b.line;
|
|
11490
11538
|
return a.rule.localeCompare(b.rule);
|
|
@@ -11495,6 +11543,7 @@ var init_scan = __esm({
|
|
|
11495
11543
|
"src/lib/scan.ts"() {
|
|
11496
11544
|
"use strict";
|
|
11497
11545
|
init_patterns();
|
|
11546
|
+
init_tier_one();
|
|
11498
11547
|
AI_PATTERN_RULES = [
|
|
11499
11548
|
{
|
|
11500
11549
|
id: "ai_antithesis",
|
|
@@ -11555,6 +11604,75 @@ var init_scan = __esm({
|
|
|
11555
11604
|
{
|
|
11556
11605
|
id: "founder_cadence_same_better",
|
|
11557
11606
|
pattern: /\bsame\s+[^.!?\n]{1,35}[.!?]\s*(?:better|nicer|cleaner|calmer|safer)\s+[^.!?\n]{1,35}[.!?]?/i
|
|
11607
|
+
},
|
|
11608
|
+
// structural cousins — cross-sentence binary reframing (top priority)
|
|
11609
|
+
{
|
|
11610
|
+
id: "structural_this_is_not_it_is",
|
|
11611
|
+
pattern: /\bthis\s+is\s+not\b.{3,80}\.\s*it\s+is\b/i
|
|
11612
|
+
},
|
|
11613
|
+
{
|
|
11614
|
+
id: "structural_is_not_it_is",
|
|
11615
|
+
pattern: /\bis\s+not\b.{3,80}\.\s*it\s+is\b/i
|
|
11616
|
+
},
|
|
11617
|
+
{
|
|
11618
|
+
id: "structural_you_are_not_you_are",
|
|
11619
|
+
pattern: /\byou\s+are\s+not\b.{3,80}\.\s*you\s+are\b/i
|
|
11620
|
+
},
|
|
11621
|
+
{
|
|
11622
|
+
id: "structural_you_no_longer_you_are",
|
|
11623
|
+
pattern: /\byou\s+are\s+no\s+longer\b.{3,80}\.\s*you\s+are\b/i
|
|
11624
|
+
},
|
|
11625
|
+
{
|
|
11626
|
+
id: "structural_you_stop_you_start",
|
|
11627
|
+
pattern: /\byou\s+stop\b.{3,50}\.\s*you\s+start\b/i
|
|
11628
|
+
},
|
|
11629
|
+
{
|
|
11630
|
+
id: "structural_need_negation_pivot",
|
|
11631
|
+
pattern: /\b(?:do\s+not|don'?t)\s+need\b.{3,50}\.\s*(?:they|you)\s+need\b/i
|
|
11632
|
+
},
|
|
11633
|
+
{
|
|
11634
|
+
id: "structural_they_call_it_anaphora",
|
|
11635
|
+
pattern: /\bthey\s+call\s+it\b[^.!?]{2,50}[.!?]\s*they\s+call\s+it\b/i
|
|
11636
|
+
},
|
|
11637
|
+
{
|
|
11638
|
+
id: "structural_parallel_verb_because",
|
|
11639
|
+
pattern: /\b(\w+)\s+because\b[^.!?]{3,60}[.!?]\s*they\s+\1\s+because\b/i
|
|
11640
|
+
},
|
|
11641
|
+
{
|
|
11642
|
+
id: "structural_parallel_endure_through",
|
|
11643
|
+
pattern: /\bendure\s+through\b[^.!?]{3,60}[.!?]\s*they\s+endure\s+through\b/i
|
|
11644
|
+
},
|
|
11645
|
+
{
|
|
11646
|
+
id: "structural_simple_but_uncomfortable",
|
|
11647
|
+
pattern: /\bsimple\s+but\s+uncomfortable\s*:/i
|
|
11648
|
+
},
|
|
11649
|
+
{
|
|
11650
|
+
id: "structural_hidden_goal",
|
|
11651
|
+
pattern: /\bthe\s+hidden\s+goal\b/i
|
|
11652
|
+
},
|
|
11653
|
+
{
|
|
11654
|
+
id: "structural_quiet_promise",
|
|
11655
|
+
pattern: /\bquiet\s+promise\b/i
|
|
11656
|
+
},
|
|
11657
|
+
{
|
|
11658
|
+
id: "founder_competing_with_void",
|
|
11659
|
+
pattern: /\bcompeting\s+with\s+the\s+void\b/i
|
|
11660
|
+
},
|
|
11661
|
+
{
|
|
11662
|
+
id: "founder_architect_of_reality",
|
|
11663
|
+
pattern: /\barchitect\s+of\s+(?:a\s+)?new\s+reality\b/i
|
|
11664
|
+
},
|
|
11665
|
+
{
|
|
11666
|
+
id: "founder_friction_into_momentum",
|
|
11667
|
+
pattern: /\b(?:turns?\s+)?friction\s+into\s+momentum\b/i
|
|
11668
|
+
},
|
|
11669
|
+
{
|
|
11670
|
+
id: "founder_cognitive_architecture",
|
|
11671
|
+
pattern: /\bcognitive\s+architecture\b/i
|
|
11672
|
+
},
|
|
11673
|
+
{
|
|
11674
|
+
id: "founder_defines_good_looks_like",
|
|
11675
|
+
pattern: /\bdefines\s+what\s+good\s+looks\s+like\b/i
|
|
11558
11676
|
}
|
|
11559
11677
|
];
|
|
11560
11678
|
ABSTRACT_STYLE_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -16372,6 +16490,8 @@ var path11 = __toESM(require("path"));
|
|
|
16372
16490
|
init_config();
|
|
16373
16491
|
init_signals();
|
|
16374
16492
|
init_profile_parse();
|
|
16493
|
+
init_scan();
|
|
16494
|
+
init_tier_one();
|
|
16375
16495
|
function loadPromptTemplate() {
|
|
16376
16496
|
const templatePath = path11.join(CACHE_DIR2, "prompt-template.md");
|
|
16377
16497
|
try {
|
|
@@ -16420,6 +16540,7 @@ Rules:
|
|
|
16420
16540
|
- "Not X. Y." is banned. Just state Y.
|
|
16421
16541
|
- "Forget X. This is Y." is banned. Just state Y.
|
|
16422
16542
|
- ANY sentence that negates one framing then asserts a corrected one fails.
|
|
16543
|
+
- Tier 1 structural patterns (binary reframes, negation pivots, parallel sermon cadence) outrank vocabulary fixes. Kill the architecture first.
|
|
16423
16544
|
|
|
16424
16545
|
Voice profile:
|
|
16425
16546
|
{profile_block}
|
|
@@ -16427,8 +16548,11 @@ Voice profile:
|
|
|
16427
16548
|
Extra constraints:
|
|
16428
16549
|
{constraints_block}
|
|
16429
16550
|
|
|
16430
|
-
|
|
16431
|
-
{
|
|
16551
|
+
Tier 1 \u2014 structural (fix first):
|
|
16552
|
+
{tier_one_lines}
|
|
16553
|
+
|
|
16554
|
+
Other flagged patterns:
|
|
16555
|
+
{other_lines}
|
|
16432
16556
|
|
|
16433
16557
|
Draft with line numbers ({draft_name}):
|
|
16434
16558
|
{numbered_draft}
|
|
@@ -16488,6 +16612,38 @@ function buildProfileBlock(fullProfile, fallbackName) {
|
|
|
16488
16612
|
}
|
|
16489
16613
|
return { block: "(no voice profile supplied \u2014 use generic voice rules)", name: null };
|
|
16490
16614
|
}
|
|
16615
|
+
function collectPromptIssues(draftContent, fullProfile) {
|
|
16616
|
+
const issues = [];
|
|
16617
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16618
|
+
const addIssue = (line, rule, detail) => {
|
|
16619
|
+
const key = `${line}|${rule}|${detail.slice(0, 80)}`;
|
|
16620
|
+
if (seen.has(key))
|
|
16621
|
+
return;
|
|
16622
|
+
seen.add(key);
|
|
16623
|
+
issues.push({ line, rule, detail, tierOne: isTierOneRule(rule) });
|
|
16624
|
+
};
|
|
16625
|
+
for (const signal of extractSignals(draftContent, fullProfile).signals) {
|
|
16626
|
+
addIssue(signal.line, signal.id, signal.suggestion);
|
|
16627
|
+
}
|
|
16628
|
+
for (const hit of scanText(draftContent)) {
|
|
16629
|
+
addIssue(hit.line, hit.rule, hit.phrase || hit.text || "");
|
|
16630
|
+
}
|
|
16631
|
+
issues.sort((a, b) => {
|
|
16632
|
+
const tier = tierSortKey(a.rule) - tierSortKey(b.rule);
|
|
16633
|
+
if (tier !== 0)
|
|
16634
|
+
return tier;
|
|
16635
|
+
if (a.line !== b.line)
|
|
16636
|
+
return a.line - b.line;
|
|
16637
|
+
return a.rule.localeCompare(b.rule);
|
|
16638
|
+
});
|
|
16639
|
+
return issues;
|
|
16640
|
+
}
|
|
16641
|
+
function formatIssueLines(issues, tierOneOnly) {
|
|
16642
|
+
const filtered = issues.filter((i) => i.tierOne === tierOneOnly);
|
|
16643
|
+
if (!filtered.length)
|
|
16644
|
+
return "- none found";
|
|
16645
|
+
return filtered.map((i) => `- line ${i.line} [${i.rule}]: ${i.detail}`).join("\n");
|
|
16646
|
+
}
|
|
16491
16647
|
function buildRewritePrompt(options) {
|
|
16492
16648
|
const {
|
|
16493
16649
|
draftPath,
|
|
@@ -16498,16 +16654,17 @@ function buildRewritePrompt(options) {
|
|
|
16498
16654
|
skipScan = false
|
|
16499
16655
|
} = options;
|
|
16500
16656
|
const { block: profileBlock, name: profileUsed } = buildProfileBlock(fullProfile, profileName);
|
|
16501
|
-
const
|
|
16502
|
-
const
|
|
16657
|
+
const issues = skipScan ? [] : collectPromptIssues(draftContent, fullProfile);
|
|
16658
|
+
const tierOneLines = formatIssueLines(issues, true);
|
|
16659
|
+
const otherLines = formatIssueLines(issues, false);
|
|
16503
16660
|
const draftLines = draftContent.split("\n");
|
|
16504
16661
|
const numberedDraft = draftLines.map((line, i) => `${i + 1}: ${line}`).join("\n");
|
|
16505
16662
|
const template = loadPromptTemplate();
|
|
16506
|
-
const prompt = template.replace("{profile_block}", profileBlock).replace("{constraints_block}", constraints || "(none)").replace("{
|
|
16663
|
+
const prompt = template.replace("{profile_block}", profileBlock).replace("{constraints_block}", constraints || "(none)").replace("{tier_one_lines}", tierOneLines).replace("{other_lines}", otherLines).replace("{draft_name}", draftPath).replace("{numbered_draft}", numberedDraft);
|
|
16507
16664
|
return {
|
|
16508
16665
|
prompt,
|
|
16509
16666
|
profileUsed,
|
|
16510
|
-
issuesFound:
|
|
16667
|
+
issuesFound: issues.length
|
|
16511
16668
|
};
|
|
16512
16669
|
}
|
|
16513
16670
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holdyourvoice/hyv",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.26",
|
|
4
4
|
"description": "Free local AI writing scan for cursor & claude. MCP server, 220+ pattern detection, voice profiles. npx @holdyourvoice/hyv welcome",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|