@domandigital/craft 0.20.0 → 0.20.1
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/CHARACTER.md +4 -2
- package/COPY.md +3 -1
- package/dist/{audit-43PC4LMS.js → audit-BN2GSFKT.js} +3 -3
- package/dist/audit.cjs +80 -5
- package/dist/audit.js +80 -5
- package/dist/{chunk-4LJFZH2I.js → chunk-2OBDCIVW.js} +2 -2
- package/dist/{chunk-LZVTN2KI.js → chunk-6NXPDCUO.js} +14 -14
- package/dist/{chunk-CZ7KT452.js → chunk-7DQDF3VL.js} +76 -4
- package/dist/{chunk-RFU5CIST.js → chunk-FQYKKKHI.js} +5 -5
- package/dist/{chunk-3CD3DTEI.js → chunk-GOPHZI6O.js} +4 -4
- package/dist/{chunk-H4UF2I7V.js → chunk-IZ6GTU54.js} +5 -5
- package/dist/{chunk-WK2WMWTH.js → chunk-JGWOA4LI.js} +5 -2
- package/dist/{chunk-VDUIIWG7.js → chunk-YOZLPNJS.js} +1 -1
- package/dist/{cli-73JCUXAV.js → cli-DVYLDCEA.js} +5 -5
- package/dist/{cli-3IAIKGCP.js → cli-HUN5ZTH5.js} +6 -6
- package/dist/{cli-EOXIJOJN.js → cli-IREKGVSM.js} +6 -6
- package/dist/{cli-YHS2VPWQ.js → cli-LSJI7QRD.js} +3 -3
- package/dist/{cli-YUVATZD5.js → cli-MKZJMZUJ.js} +5 -5
- package/dist/{cli-EDQNTWDD.js → cli-NTNUZUFP.js} +4 -4
- package/dist/{cli-5DONIRMR.js → cli-VFKKFRB4.js} +9 -9
- package/dist/{cli-VPARW6FY.js → cli-ZXDGVDYU.js} +1 -1
- package/dist/cli.js +3 -3
- package/dist/index.cjs +80 -5
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +80 -5
- package/dist/{viewports-cli-RYV4LDQK.js → viewports-cli-C4VXMPKK.js} +3 -3
- package/package.json +1 -1
- package/word-lists.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @domandigital/craft
|
|
2
2
|
|
|
3
|
+
## 0.20.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e8ec697: `review-phrase` reports "clear" and "clearly" (catalogue 2026.10.1). "A clear next step" and "clear pricing" promise clarity instead of giving it, so every use is now printed for a person to read. It never blocks: the house decision of 14 October 2026 is review first, and a move to the blocking tier waits until the existing uses across the estate are read. Review-tier phrases that end in a letter now match whole words only, so "clearance" and "clearer" pass, and "great questions" is no longer reported where "great question" is.
|
|
8
|
+
- a1f2b6b: Two new review-tier copy tells (catalogue 2026.10.2). `sales-tagline` reports advert lines such as "so your budget goes where it counts", "real results" and "pages that convert". `outcome-promise` reports a hoped-for result written as if delivered: "we'll turn that into enquiries", "once the first areas are working", "competitors you can outdo", "exactly what convinces". Ordinary statements of the work ("we'll rewrite your rewiring page", "once the fuse board is fitted") pass. Both print for review and never block, following the house rule that a new tell starts in review.
|
|
9
|
+
|
|
3
10
|
## 0.20.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/CHARACTER.md
CHANGED
|
@@ -1005,7 +1005,7 @@ Generated from the package. Run `pnpm --filter @domandigital/craft run docs`
|
|
|
1005
1005
|
after changing an entry; a test fails until you do.
|
|
1006
1006
|
|
|
1007
1007
|
<!-- craft:catalogue:start -->
|
|
1008
|
-
Catalogue version `2026.
|
|
1008
|
+
Catalogue version `2026.10.2`, 70 tells.
|
|
1009
1009
|
|
|
1010
1010
|
| Id | Gen | Surface | Severity | Tell | Why it is a default |
|
|
1011
1011
|
| --- | --- | --- | --- | --- | --- |
|
|
@@ -1039,7 +1039,9 @@ Catalogue version `2026.09.12`, 68 tells.
|
|
|
1039
1039
|
| `buzzword` | 1 | copy | warn | Buzzword | Cutting-edge, world-class, reach out, turnkey: corporate filler older than any model, and still the first thing a model writes about a business it knows nothing about. |
|
|
1040
1040
|
| `negative-reassurance` | 1 | copy | warn | Negative reassurance | 'No hidden fees', 'no surprises', 'never locked in' reassure by naming the fear, and plant it in a reader who did not have it. |
|
|
1041
1041
|
| `vague-word` | 1 | copy | warn | Vague word | Innovative, scalable, end-to-end, solutions: each can be true, and each is used where the writer had nothing specific to say. |
|
|
1042
|
-
| `review-phrase` | 1 | copy | warn | Review-tier phrase | Truly, genuinely, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking. |
|
|
1042
|
+
| `review-phrase` | 1 | copy | warn | Review-tier phrase | Truly, genuinely, clear, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking. |
|
|
1043
|
+
| `sales-tagline` | 1 | copy | warn | Sales tagline | 'So your budget goes where it counts', 'Real results', 'pages that convert': advert lines that sound like a pitch and say nothing a reader can check. |
|
|
1044
|
+
| `outcome-promise` | 1 | copy | warn | Outcome promise | 'We'll turn that into enquiries', 'once the first areas are working', 'competitors you can outdo': a hoped-for result written as if it were delivered. Rankings and enquiries depend on things nobody controls. |
|
|
1043
1045
|
| `hollow-imperative` | 1 | copy | warn | Hollow imperative | 'Discover', 'Experience', 'Transform your' as the opening verb of a heading or button is a call to action with nothing in it. |
|
|
1044
1046
|
| `rhetorical-opener` | 1 | copy | warn | Rhetorical question opener | 'Looking for...?', 'Tired of...?', 'Ready to...?' opens with the model guessing at the reader instead of telling them something. |
|
|
1045
1047
|
| `em-dash` | 2 | copy | warn | Em dash | Generated copy leans on the em dash to join clauses. House copy does not use it. |
|
package/COPY.md
CHANGED
|
@@ -86,7 +86,9 @@ person reading decides which instance earns its place.
|
|
|
86
86
|
|
|
87
87
|
| Tell | What it catches |
|
|
88
88
|
| --- | --- |
|
|
89
|
-
| `review-phrase` | truly, genuinely, deeply, ultimately, great question, absolutely, one-size-fits-all, and superlative over-claiming: "the single most", "by far the most", "hands down the", "arguably the best", "bar none" |
|
|
89
|
+
| `review-phrase` | truly, genuinely, deeply, ultimately, clear and clearly ("a clear next step", "clear pricing": say what the reader actually gets), great question, absolutely, one-size-fits-all, and superlative over-claiming: "the single most", "by far the most", "hands down the", "arguably the best", "bar none" |
|
|
90
|
+
| `sales-tagline` | advert lines that say nothing a reader can check: "so your budget goes where it counts", "real results", "results that matter", "work harder for you", "pages that convert" |
|
|
91
|
+
| `outcome-promise` | a hoped-for result written as delivered: "we'll turn that into enquiries", "once the first areas are working", "competitors you can outdo", "exactly what convinces", "guaranteed rankings", "you'll get more". Promise the work; measure and report the results |
|
|
90
92
|
| `ai-vocabulary` | tapestry, testament, realm, elevate, nestled, meticulous, unparalleled, moreover; and underscore, showcasing, pivotal, measured 10 to 14 times over-used in post-2022 text |
|
|
91
93
|
| `stock-phrase` | to the next level, one-stop shop, we pride ourselves, passionate about, peace of mind, "so here's", a journey used as a metaphor |
|
|
92
94
|
| `hollow-imperative` | Discover, Experience, Transform, Elevate as the opening verb of a heading or button |
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
snapshotPage,
|
|
10
10
|
snapshotUrl,
|
|
11
11
|
snapshotUrls
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YOZLPNJS.js";
|
|
13
13
|
import "./chunk-JYNXGCYZ.js";
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-JGWOA4LI.js";
|
|
15
|
+
import "./chunk-7DQDF3VL.js";
|
|
16
16
|
import "./chunk-QYBAVU3P.js";
|
|
17
17
|
export {
|
|
18
18
|
PROVENANCE_BUDGET_MS,
|
package/dist/audit.cjs
CHANGED
|
@@ -218,6 +218,8 @@ var REVIEW_PHRASES = [
|
|
|
218
218
|
"truly",
|
|
219
219
|
"genuinely",
|
|
220
220
|
"deeply",
|
|
221
|
+
"clear",
|
|
222
|
+
"clearly",
|
|
221
223
|
"ultimately",
|
|
222
224
|
"one-size-fits-all",
|
|
223
225
|
"one size fits all",
|
|
@@ -228,6 +230,33 @@ var REVIEW_PHRASES = [
|
|
|
228
230
|
"arguably the best",
|
|
229
231
|
"bar none"
|
|
230
232
|
];
|
|
233
|
+
var SALES_TAGLINES = [
|
|
234
|
+
/\bwhere it (?:really |truly )?(?:counts|matters)(?: most)?\b/i,
|
|
235
|
+
/\breal,? (?:measurable |tangible )?results\b/i,
|
|
236
|
+
/\bresults that (?:matter|count|speak|last)\b/i,
|
|
237
|
+
/\bresults you can (?:see|trust|measure)\b/i,
|
|
238
|
+
/\bwork(?:s|ing)? harder for you\b/i,
|
|
239
|
+
/\bmake (?:every|your) (?:penny|pound) count\b/i,
|
|
240
|
+
/\bevery (?:penny|pound) (?:counts|works)\b/i,
|
|
241
|
+
/\bbang for your buck\b/i,
|
|
242
|
+
/\bstand out from the (?:crowd|competition)\b/i,
|
|
243
|
+
/\b(?:pages?|websites?|sites?|copy|content|ads?) that (?:actually |really )?(?:converts?|sells?)\b/i
|
|
244
|
+
];
|
|
245
|
+
var SALES_RESULTS = "(?:enquiries|leads|customers|clients|sales|jobs|bookings|revenue|calls|traffic|profit)";
|
|
246
|
+
var OUTCOME_PROMISES = [
|
|
247
|
+
// "We'll turn more of that into rewiring enquiries"
|
|
248
|
+
new RegExp(`\\bturn(?:s|ing)?\\b[^.!?\\n]{0,50}?\\binto\\s+(?:more\\s+|real\\s+|paying\\s+)?(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i"),
|
|
249
|
+
// "once the first areas are working"
|
|
250
|
+
/\bonce\b[^.!?\n]{0,40}?\b(?:is|are|starts?|begins?)\s+(?:working|paying off|performing|ranking|delivering|bringing in)\b/i,
|
|
251
|
+
// "competitors you can outdo"
|
|
252
|
+
/\b(?:competitors?|competition|rivals?)\b[^.!?\n]{0,25}?\b(?:you|we)\s*(?:can|will|could|['’]ll)\s+(?:easily\s+)?(?:outdo|beat|outrank|overtake|outperform|outshine)\b/i,
|
|
253
|
+
/\b(?:outdo|outrank|overtake|outperform|outshine|beat)\s+(?:your|the|local|bigger)\s+(?:[\w-]+\s+)?(?:competitors|competition|rivals)\b/i,
|
|
254
|
+
// "exactly what convinces someone spending £8k"
|
|
255
|
+
/\bexactly what\b[^.!?\n]{0,15}?\b(?:convinces|persuades|wins|sells)\b/i,
|
|
256
|
+
new RegExp(`\\bguarantee[sd]?\\b[^.!?\\n]{0,25}?\\b(?:results|rankings?|first page|page one|top spot|${SALES_RESULTS.slice(3, -1)})\\b`, "i"),
|
|
257
|
+
/\byou(?:['’]ll| will)\s+(?:get|see|win|receive|start getting)\s+more\b/i,
|
|
258
|
+
new RegExp(`\\b(?:will|we['\u2019]ll)\\s+(?:(?:bring|drive|generate|get)\\s+you|bring|drive|generate)\\s+more\\s+(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i")
|
|
259
|
+
];
|
|
231
260
|
var NEGATION = [
|
|
232
261
|
// "It's not just a haircut, it's an experience."
|
|
233
262
|
/\b(?:it['’]?s|it is|this is|this isn['’]t|we['’]re|we are|they['’]re)\s+(?:not|more than)\s+(?:just|only|merely|simply)?\s*[^.!?\n]{1,60}?[,;.—-]\s*(?:it['’]?s|it is|this is|we['’]re|we are|they['’]re)\b/i,
|
|
@@ -413,12 +442,55 @@ var COPY_TELLS = [
|
|
|
413
442
|
generation: 1,
|
|
414
443
|
severity: "warn",
|
|
415
444
|
surface: "copy",
|
|
416
|
-
why: "Truly, genuinely, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
445
|
+
why: "Truly, genuinely, clear, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
417
446
|
fix: "Keep the one that lands hardest and cut the rest. 'What is genuinely excellent' reads better as 'what is excellent'.",
|
|
418
|
-
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1")).join("|")})`, "i")], quoted),
|
|
447
|
+
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1").replace(/(\w)$/, "$1\\b")).join("|")})`, "i")], quoted),
|
|
448
|
+
fixtures: {
|
|
449
|
+
flag: [f("content/home.md", "We are truly proud of it."), f("content/home.md", "You leave with a clear next step.")],
|
|
450
|
+
pass: [f("content/home.md", "We have fitted 400 boilers since 2009."), f("content/home.md", "Keep a safe clearance around the meter.")]
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: "sales-tagline",
|
|
455
|
+
name: "Sales tagline",
|
|
456
|
+
generation: 1,
|
|
457
|
+
severity: "warn",
|
|
458
|
+
surface: "copy",
|
|
459
|
+
why: "'So your budget goes where it counts', 'Real results', 'pages that convert': advert lines that sound like a pitch and say nothing a reader can check.",
|
|
460
|
+
fix: "Say what you will actually do and why. 'So your budget goes where it counts' becomes 'Oxford first, because that is where you want the work'.",
|
|
461
|
+
detect: phrases(SALES_TAGLINES, quoted),
|
|
462
|
+
fixtures: {
|
|
463
|
+
flag: [
|
|
464
|
+
f("content/home.md", "Strongest opportunities first, so your budget goes where it counts."),
|
|
465
|
+
f("content/home.md", "A clearer online presence. More enquiries. Real results.")
|
|
466
|
+
],
|
|
467
|
+
pass: [
|
|
468
|
+
f("content/home.md", "We start with Oxford, then expand based on search demand and results."),
|
|
469
|
+
f("content/home.md", "We report the results every month.")
|
|
470
|
+
]
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: "outcome-promise",
|
|
475
|
+
name: "Outcome promise",
|
|
476
|
+
generation: 1,
|
|
477
|
+
severity: "warn",
|
|
478
|
+
surface: "copy",
|
|
479
|
+
why: "'We'll turn that into enquiries', 'once the first areas are working', 'competitors you can outdo': a hoped-for result written as if it were delivered. Rankings and enquiries depend on things nobody controls.",
|
|
480
|
+
fix: "Promise the work, not the outcome. 'We'll turn that into enquiries' becomes 'We'll rewrite the page around the questions homeowners ask'. Results are measured and reported, never promised.",
|
|
481
|
+
detect: phrases(OUTCOME_PROMISES, quoted),
|
|
419
482
|
fixtures: {
|
|
420
|
-
flag: [
|
|
421
|
-
|
|
483
|
+
flag: [
|
|
484
|
+
f("content/home.md", "We'll turn more of that into rewiring enquiries."),
|
|
485
|
+
f("content/home.md", "We add new towns once the first areas are working."),
|
|
486
|
+
f("content/home.md", "We target the competitors you can outdo."),
|
|
487
|
+
f("content/home.md", "We write exactly what convinces someone spending \xA38k.")
|
|
488
|
+
],
|
|
489
|
+
pass: [
|
|
490
|
+
f("content/home.md", "We'll rewrite your rewiring page around homeowners' questions."),
|
|
491
|
+
f("content/home.md", "We record every rewiring enquiry, quotation and won job."),
|
|
492
|
+
f("content/home.md", "Once the fuse board is fitted, we test every circuit.")
|
|
493
|
+
]
|
|
422
494
|
}
|
|
423
495
|
},
|
|
424
496
|
{
|
|
@@ -2996,6 +3068,9 @@ var HOUSE = {
|
|
|
2996
3068
|
// on 2026-09-24 after its estate hits were read (ROADMAP.md): 15 of 15 real.
|
|
2997
3069
|
"chatbot-residue": { tier: "block", label: "chatbot residue. A chat reply was pasted in: remove it, and source or cut the claim it backed" },
|
|
2998
3070
|
"review-phrase": { tier: "review", label: "review tier" },
|
|
3071
|
+
// New 2026-10-14 (founder): review first, like every new tell, until estate hits are read.
|
|
3072
|
+
"sales-tagline": { tier: "review", label: "sales tagline, say what you will actually do" },
|
|
3073
|
+
"outcome-promise": { tier: "review", label: "outcome promise, promise the work, not the result" },
|
|
2999
3074
|
"ai-vocabulary": { tier: "review", label: "AI vocabulary" },
|
|
3000
3075
|
"stock-phrase": { tier: "review", label: "stock phrase" },
|
|
3001
3076
|
"hollow-imperative": { tier: "review", label: "hollow imperative" },
|
|
@@ -3737,7 +3812,7 @@ var IMAGERY_TELLS = [
|
|
|
3737
3812
|
];
|
|
3738
3813
|
|
|
3739
3814
|
// src/character/check.ts
|
|
3740
|
-
var CATALOGUE_VERSION = "2026.
|
|
3815
|
+
var CATALOGUE_VERSION = "2026.10.2";
|
|
3741
3816
|
var CATALOGUE = [
|
|
3742
3817
|
...[...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map((t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t),
|
|
3743
3818
|
...SPECIFICITY_COPY_TELLS,
|
package/dist/audit.js
CHANGED
|
@@ -183,6 +183,8 @@ var REVIEW_PHRASES = [
|
|
|
183
183
|
"truly",
|
|
184
184
|
"genuinely",
|
|
185
185
|
"deeply",
|
|
186
|
+
"clear",
|
|
187
|
+
"clearly",
|
|
186
188
|
"ultimately",
|
|
187
189
|
"one-size-fits-all",
|
|
188
190
|
"one size fits all",
|
|
@@ -193,6 +195,33 @@ var REVIEW_PHRASES = [
|
|
|
193
195
|
"arguably the best",
|
|
194
196
|
"bar none"
|
|
195
197
|
];
|
|
198
|
+
var SALES_TAGLINES = [
|
|
199
|
+
/\bwhere it (?:really |truly )?(?:counts|matters)(?: most)?\b/i,
|
|
200
|
+
/\breal,? (?:measurable |tangible )?results\b/i,
|
|
201
|
+
/\bresults that (?:matter|count|speak|last)\b/i,
|
|
202
|
+
/\bresults you can (?:see|trust|measure)\b/i,
|
|
203
|
+
/\bwork(?:s|ing)? harder for you\b/i,
|
|
204
|
+
/\bmake (?:every|your) (?:penny|pound) count\b/i,
|
|
205
|
+
/\bevery (?:penny|pound) (?:counts|works)\b/i,
|
|
206
|
+
/\bbang for your buck\b/i,
|
|
207
|
+
/\bstand out from the (?:crowd|competition)\b/i,
|
|
208
|
+
/\b(?:pages?|websites?|sites?|copy|content|ads?) that (?:actually |really )?(?:converts?|sells?)\b/i
|
|
209
|
+
];
|
|
210
|
+
var SALES_RESULTS = "(?:enquiries|leads|customers|clients|sales|jobs|bookings|revenue|calls|traffic|profit)";
|
|
211
|
+
var OUTCOME_PROMISES = [
|
|
212
|
+
// "We'll turn more of that into rewiring enquiries"
|
|
213
|
+
new RegExp(`\\bturn(?:s|ing)?\\b[^.!?\\n]{0,50}?\\binto\\s+(?:more\\s+|real\\s+|paying\\s+)?(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i"),
|
|
214
|
+
// "once the first areas are working"
|
|
215
|
+
/\bonce\b[^.!?\n]{0,40}?\b(?:is|are|starts?|begins?)\s+(?:working|paying off|performing|ranking|delivering|bringing in)\b/i,
|
|
216
|
+
// "competitors you can outdo"
|
|
217
|
+
/\b(?:competitors?|competition|rivals?)\b[^.!?\n]{0,25}?\b(?:you|we)\s*(?:can|will|could|['’]ll)\s+(?:easily\s+)?(?:outdo|beat|outrank|overtake|outperform|outshine)\b/i,
|
|
218
|
+
/\b(?:outdo|outrank|overtake|outperform|outshine|beat)\s+(?:your|the|local|bigger)\s+(?:[\w-]+\s+)?(?:competitors|competition|rivals)\b/i,
|
|
219
|
+
// "exactly what convinces someone spending £8k"
|
|
220
|
+
/\bexactly what\b[^.!?\n]{0,15}?\b(?:convinces|persuades|wins|sells)\b/i,
|
|
221
|
+
new RegExp(`\\bguarantee[sd]?\\b[^.!?\\n]{0,25}?\\b(?:results|rankings?|first page|page one|top spot|${SALES_RESULTS.slice(3, -1)})\\b`, "i"),
|
|
222
|
+
/\byou(?:['’]ll| will)\s+(?:get|see|win|receive|start getting)\s+more\b/i,
|
|
223
|
+
new RegExp(`\\b(?:will|we['\u2019]ll)\\s+(?:(?:bring|drive|generate|get)\\s+you|bring|drive|generate)\\s+more\\s+(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i")
|
|
224
|
+
];
|
|
196
225
|
var NEGATION = [
|
|
197
226
|
// "It's not just a haircut, it's an experience."
|
|
198
227
|
/\b(?:it['’]?s|it is|this is|this isn['’]t|we['’]re|we are|they['’]re)\s+(?:not|more than)\s+(?:just|only|merely|simply)?\s*[^.!?\n]{1,60}?[,;.—-]\s*(?:it['’]?s|it is|this is|we['’]re|we are|they['’]re)\b/i,
|
|
@@ -378,12 +407,55 @@ var COPY_TELLS = [
|
|
|
378
407
|
generation: 1,
|
|
379
408
|
severity: "warn",
|
|
380
409
|
surface: "copy",
|
|
381
|
-
why: "Truly, genuinely, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
410
|
+
why: "Truly, genuinely, clear, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
382
411
|
fix: "Keep the one that lands hardest and cut the rest. 'What is genuinely excellent' reads better as 'what is excellent'.",
|
|
383
|
-
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1")).join("|")})`, "i")], quoted),
|
|
412
|
+
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1").replace(/(\w)$/, "$1\\b")).join("|")})`, "i")], quoted),
|
|
413
|
+
fixtures: {
|
|
414
|
+
flag: [f("content/home.md", "We are truly proud of it."), f("content/home.md", "You leave with a clear next step.")],
|
|
415
|
+
pass: [f("content/home.md", "We have fitted 400 boilers since 2009."), f("content/home.md", "Keep a safe clearance around the meter.")]
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: "sales-tagline",
|
|
420
|
+
name: "Sales tagline",
|
|
421
|
+
generation: 1,
|
|
422
|
+
severity: "warn",
|
|
423
|
+
surface: "copy",
|
|
424
|
+
why: "'So your budget goes where it counts', 'Real results', 'pages that convert': advert lines that sound like a pitch and say nothing a reader can check.",
|
|
425
|
+
fix: "Say what you will actually do and why. 'So your budget goes where it counts' becomes 'Oxford first, because that is where you want the work'.",
|
|
426
|
+
detect: phrases(SALES_TAGLINES, quoted),
|
|
427
|
+
fixtures: {
|
|
428
|
+
flag: [
|
|
429
|
+
f("content/home.md", "Strongest opportunities first, so your budget goes where it counts."),
|
|
430
|
+
f("content/home.md", "A clearer online presence. More enquiries. Real results.")
|
|
431
|
+
],
|
|
432
|
+
pass: [
|
|
433
|
+
f("content/home.md", "We start with Oxford, then expand based on search demand and results."),
|
|
434
|
+
f("content/home.md", "We report the results every month.")
|
|
435
|
+
]
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: "outcome-promise",
|
|
440
|
+
name: "Outcome promise",
|
|
441
|
+
generation: 1,
|
|
442
|
+
severity: "warn",
|
|
443
|
+
surface: "copy",
|
|
444
|
+
why: "'We'll turn that into enquiries', 'once the first areas are working', 'competitors you can outdo': a hoped-for result written as if it were delivered. Rankings and enquiries depend on things nobody controls.",
|
|
445
|
+
fix: "Promise the work, not the outcome. 'We'll turn that into enquiries' becomes 'We'll rewrite the page around the questions homeowners ask'. Results are measured and reported, never promised.",
|
|
446
|
+
detect: phrases(OUTCOME_PROMISES, quoted),
|
|
384
447
|
fixtures: {
|
|
385
|
-
flag: [
|
|
386
|
-
|
|
448
|
+
flag: [
|
|
449
|
+
f("content/home.md", "We'll turn more of that into rewiring enquiries."),
|
|
450
|
+
f("content/home.md", "We add new towns once the first areas are working."),
|
|
451
|
+
f("content/home.md", "We target the competitors you can outdo."),
|
|
452
|
+
f("content/home.md", "We write exactly what convinces someone spending \xA38k.")
|
|
453
|
+
],
|
|
454
|
+
pass: [
|
|
455
|
+
f("content/home.md", "We'll rewrite your rewiring page around homeowners' questions."),
|
|
456
|
+
f("content/home.md", "We record every rewiring enquiry, quotation and won job."),
|
|
457
|
+
f("content/home.md", "Once the fuse board is fitted, we test every circuit.")
|
|
458
|
+
]
|
|
387
459
|
}
|
|
388
460
|
},
|
|
389
461
|
{
|
|
@@ -2961,6 +3033,9 @@ var HOUSE = {
|
|
|
2961
3033
|
// on 2026-09-24 after its estate hits were read (ROADMAP.md): 15 of 15 real.
|
|
2962
3034
|
"chatbot-residue": { tier: "block", label: "chatbot residue. A chat reply was pasted in: remove it, and source or cut the claim it backed" },
|
|
2963
3035
|
"review-phrase": { tier: "review", label: "review tier" },
|
|
3036
|
+
// New 2026-10-14 (founder): review first, like every new tell, until estate hits are read.
|
|
3037
|
+
"sales-tagline": { tier: "review", label: "sales tagline, say what you will actually do" },
|
|
3038
|
+
"outcome-promise": { tier: "review", label: "outcome promise, promise the work, not the result" },
|
|
2964
3039
|
"ai-vocabulary": { tier: "review", label: "AI vocabulary" },
|
|
2965
3040
|
"stock-phrase": { tier: "review", label: "stock phrase" },
|
|
2966
3041
|
"hollow-imperative": { tier: "review", label: "hollow imperative" },
|
|
@@ -3702,7 +3777,7 @@ var IMAGERY_TELLS = [
|
|
|
3702
3777
|
];
|
|
3703
3778
|
|
|
3704
3779
|
// src/character/check.ts
|
|
3705
|
-
var CATALOGUE_VERSION = "2026.
|
|
3780
|
+
var CATALOGUE_VERSION = "2026.10.2";
|
|
3706
3781
|
var CATALOGUE = [
|
|
3707
3782
|
...[...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map((t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t),
|
|
3708
3783
|
...SPECIFICITY_COPY_TELLS,
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
levenshtein,
|
|
11
11
|
normaliseFamily,
|
|
12
12
|
parseColour
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-JGWOA4LI.js";
|
|
14
14
|
import {
|
|
15
15
|
TRADE_NOUNS
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-7DQDF3VL.js";
|
|
17
17
|
|
|
18
18
|
// src/direction/types.ts
|
|
19
19
|
var DIRECTION_VERSION = 2;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
holdHouseBlocks,
|
|
11
11
|
houseRule,
|
|
12
12
|
scanSource
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-JGWOA4LI.js";
|
|
14
14
|
import {
|
|
15
15
|
compareCompetitor,
|
|
16
16
|
compareFacts,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
pageCopy,
|
|
22
22
|
protectedFacts,
|
|
23
23
|
visibleText
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-7DQDF3VL.js";
|
|
25
25
|
|
|
26
26
|
// src/character/config.ts
|
|
27
27
|
var DEFAULT_IGNORE = [".claude", ".agents", ".cursor"];
|
|
@@ -620,7 +620,7 @@ async function pageSnapshot(target, cwd) {
|
|
|
620
620
|
return readSnapshot(JSON.parse(readFileSync(local, "utf8")), target);
|
|
621
621
|
}
|
|
622
622
|
if (!/^https?:\/\//.test(target)) throw new Error(`${target} is neither a URL nor a snapshot file`);
|
|
623
|
-
const { snapshotUrl } = await import("./audit-
|
|
623
|
+
const { snapshotUrl } = await import("./audit-BN2GSFKT.js");
|
|
624
624
|
return snapshotUrl(target);
|
|
625
625
|
}
|
|
626
626
|
async function briefFor(flags, cwd) {
|
|
@@ -631,7 +631,7 @@ async function briefFor(flags, cwd) {
|
|
|
631
631
|
if (typeof brief === "string" && brief.trim()) return brief;
|
|
632
632
|
} else if (flags.direction) throw new Error(`no such file: ${flags.direction}`);
|
|
633
633
|
if (flags.null) {
|
|
634
|
-
const { loadNull } = await import("./cli-
|
|
634
|
+
const { loadNull } = await import("./cli-NTNUZUFP.js");
|
|
635
635
|
return loadNull(flags.null, cwd).brief;
|
|
636
636
|
}
|
|
637
637
|
return void 0;
|
|
@@ -657,37 +657,37 @@ async function compareWithCompetitor(flags, io) {
|
|
|
657
657
|
function run(argv, io) {
|
|
658
658
|
const [command, ...rest] = argv;
|
|
659
659
|
if (command === "direction") {
|
|
660
|
-
return import("./cli-
|
|
660
|
+
return import("./cli-MKZJMZUJ.js").then((m) => m.runDirection(rest, io));
|
|
661
661
|
}
|
|
662
662
|
if (command === "report" || command === "retrofit") {
|
|
663
|
-
return import("./cli-
|
|
663
|
+
return import("./cli-VFKKFRB4.js").then((m) => m.runReport(command, rest, io));
|
|
664
664
|
}
|
|
665
665
|
if (command === "register") {
|
|
666
|
-
return import("./cli-
|
|
666
|
+
return import("./cli-ZXDGVDYU.js").then((m) => m.runRegister(rest, io));
|
|
667
667
|
}
|
|
668
668
|
if (command === "estate") {
|
|
669
|
-
return import("./cli-
|
|
669
|
+
return import("./cli-HUN5ZTH5.js").then((m) => m.runEstate(rest, io));
|
|
670
670
|
}
|
|
671
671
|
if (command === "null") {
|
|
672
|
-
return import("./cli-
|
|
672
|
+
return import("./cli-NTNUZUFP.js").then((m) => m.runNull(rest, io));
|
|
673
673
|
}
|
|
674
674
|
if (command === "calibrate") {
|
|
675
|
-
return import("./cli-
|
|
675
|
+
return import("./cli-DVYLDCEA.js").then((m) => m.runCalibrate(rest, io));
|
|
676
676
|
}
|
|
677
677
|
if (command === "tells" && rest[0] === "harvest") {
|
|
678
|
-
return import("./cli-
|
|
678
|
+
return import("./cli-NTNUZUFP.js").then((m) => m.runHarvest(rest, io)).catch((error) => {
|
|
679
679
|
io.err(`craft: ${error.message}`);
|
|
680
680
|
return 2;
|
|
681
681
|
});
|
|
682
682
|
}
|
|
683
683
|
if (command === "phone-first") {
|
|
684
|
-
return import("./cli-
|
|
684
|
+
return import("./cli-LSJI7QRD.js").then((m) => m.runPhoneFirst(rest, io));
|
|
685
685
|
}
|
|
686
686
|
if (command === "viewports") {
|
|
687
|
-
return import("./viewports-cli-
|
|
687
|
+
return import("./viewports-cli-C4VXMPKK.js").then((m) => m.runViewports(rest, io));
|
|
688
688
|
}
|
|
689
689
|
if (command === "snapshot" || command === "audit") {
|
|
690
|
-
return import("./cli-
|
|
690
|
+
return import("./cli-IREKGVSM.js").then((m) => m.runAudit(command, rest, io));
|
|
691
691
|
}
|
|
692
692
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
693
693
|
io.out(HELP);
|
|
@@ -606,6 +606,8 @@ var REVIEW_PHRASES = [
|
|
|
606
606
|
"truly",
|
|
607
607
|
"genuinely",
|
|
608
608
|
"deeply",
|
|
609
|
+
"clear",
|
|
610
|
+
"clearly",
|
|
609
611
|
"ultimately",
|
|
610
612
|
"one-size-fits-all",
|
|
611
613
|
"one size fits all",
|
|
@@ -616,6 +618,33 @@ var REVIEW_PHRASES = [
|
|
|
616
618
|
"arguably the best",
|
|
617
619
|
"bar none"
|
|
618
620
|
];
|
|
621
|
+
var SALES_TAGLINES = [
|
|
622
|
+
/\bwhere it (?:really |truly )?(?:counts|matters)(?: most)?\b/i,
|
|
623
|
+
/\breal,? (?:measurable |tangible )?results\b/i,
|
|
624
|
+
/\bresults that (?:matter|count|speak|last)\b/i,
|
|
625
|
+
/\bresults you can (?:see|trust|measure)\b/i,
|
|
626
|
+
/\bwork(?:s|ing)? harder for you\b/i,
|
|
627
|
+
/\bmake (?:every|your) (?:penny|pound) count\b/i,
|
|
628
|
+
/\bevery (?:penny|pound) (?:counts|works)\b/i,
|
|
629
|
+
/\bbang for your buck\b/i,
|
|
630
|
+
/\bstand out from the (?:crowd|competition)\b/i,
|
|
631
|
+
/\b(?:pages?|websites?|sites?|copy|content|ads?) that (?:actually |really )?(?:converts?|sells?)\b/i
|
|
632
|
+
];
|
|
633
|
+
var SALES_RESULTS = "(?:enquiries|leads|customers|clients|sales|jobs|bookings|revenue|calls|traffic|profit)";
|
|
634
|
+
var OUTCOME_PROMISES = [
|
|
635
|
+
// "We'll turn more of that into rewiring enquiries"
|
|
636
|
+
new RegExp(`\\bturn(?:s|ing)?\\b[^.!?\\n]{0,50}?\\binto\\s+(?:more\\s+|real\\s+|paying\\s+)?(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i"),
|
|
637
|
+
// "once the first areas are working"
|
|
638
|
+
/\bonce\b[^.!?\n]{0,40}?\b(?:is|are|starts?|begins?)\s+(?:working|paying off|performing|ranking|delivering|bringing in)\b/i,
|
|
639
|
+
// "competitors you can outdo"
|
|
640
|
+
/\b(?:competitors?|competition|rivals?)\b[^.!?\n]{0,25}?\b(?:you|we)\s*(?:can|will|could|['’]ll)\s+(?:easily\s+)?(?:outdo|beat|outrank|overtake|outperform|outshine)\b/i,
|
|
641
|
+
/\b(?:outdo|outrank|overtake|outperform|outshine|beat)\s+(?:your|the|local|bigger)\s+(?:[\w-]+\s+)?(?:competitors|competition|rivals)\b/i,
|
|
642
|
+
// "exactly what convinces someone spending £8k"
|
|
643
|
+
/\bexactly what\b[^.!?\n]{0,15}?\b(?:convinces|persuades|wins|sells)\b/i,
|
|
644
|
+
new RegExp(`\\bguarantee[sd]?\\b[^.!?\\n]{0,25}?\\b(?:results|rankings?|first page|page one|top spot|${SALES_RESULTS.slice(3, -1)})\\b`, "i"),
|
|
645
|
+
/\byou(?:['’]ll| will)\s+(?:get|see|win|receive|start getting)\s+more\b/i,
|
|
646
|
+
new RegExp(`\\b(?:will|we['\u2019]ll)\\s+(?:(?:bring|drive|generate|get)\\s+you|bring|drive|generate)\\s+more\\s+(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i")
|
|
647
|
+
];
|
|
619
648
|
var NEGATION = [
|
|
620
649
|
// "It's not just a haircut, it's an experience."
|
|
621
650
|
/\b(?:it['’]?s|it is|this is|this isn['’]t|we['’]re|we are|they['’]re)\s+(?:not|more than)\s+(?:just|only|merely|simply)?\s*[^.!?\n]{1,60}?[,;.—-]\s*(?:it['’]?s|it is|this is|we['’]re|we are|they['’]re)\b/i,
|
|
@@ -801,12 +830,55 @@ var COPY_TELLS = [
|
|
|
801
830
|
generation: 1,
|
|
802
831
|
severity: "warn",
|
|
803
832
|
surface: "copy",
|
|
804
|
-
why: "Truly, genuinely, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
833
|
+
why: "Truly, genuinely, clear, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
805
834
|
fix: "Keep the one that lands hardest and cut the rest. 'What is genuinely excellent' reads better as 'what is excellent'.",
|
|
806
|
-
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1")).join("|")})`, "i")], quoted),
|
|
835
|
+
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1").replace(/(\w)$/, "$1\\b")).join("|")})`, "i")], quoted),
|
|
836
|
+
fixtures: {
|
|
837
|
+
flag: [f("content/home.md", "We are truly proud of it."), f("content/home.md", "You leave with a clear next step.")],
|
|
838
|
+
pass: [f("content/home.md", "We have fitted 400 boilers since 2009."), f("content/home.md", "Keep a safe clearance around the meter.")]
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
id: "sales-tagline",
|
|
843
|
+
name: "Sales tagline",
|
|
844
|
+
generation: 1,
|
|
845
|
+
severity: "warn",
|
|
846
|
+
surface: "copy",
|
|
847
|
+
why: "'So your budget goes where it counts', 'Real results', 'pages that convert': advert lines that sound like a pitch and say nothing a reader can check.",
|
|
848
|
+
fix: "Say what you will actually do and why. 'So your budget goes where it counts' becomes 'Oxford first, because that is where you want the work'.",
|
|
849
|
+
detect: phrases(SALES_TAGLINES, quoted),
|
|
850
|
+
fixtures: {
|
|
851
|
+
flag: [
|
|
852
|
+
f("content/home.md", "Strongest opportunities first, so your budget goes where it counts."),
|
|
853
|
+
f("content/home.md", "A clearer online presence. More enquiries. Real results.")
|
|
854
|
+
],
|
|
855
|
+
pass: [
|
|
856
|
+
f("content/home.md", "We start with Oxford, then expand based on search demand and results."),
|
|
857
|
+
f("content/home.md", "We report the results every month.")
|
|
858
|
+
]
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
id: "outcome-promise",
|
|
863
|
+
name: "Outcome promise",
|
|
864
|
+
generation: 1,
|
|
865
|
+
severity: "warn",
|
|
866
|
+
surface: "copy",
|
|
867
|
+
why: "'We'll turn that into enquiries', 'once the first areas are working', 'competitors you can outdo': a hoped-for result written as if it were delivered. Rankings and enquiries depend on things nobody controls.",
|
|
868
|
+
fix: "Promise the work, not the outcome. 'We'll turn that into enquiries' becomes 'We'll rewrite the page around the questions homeowners ask'. Results are measured and reported, never promised.",
|
|
869
|
+
detect: phrases(OUTCOME_PROMISES, quoted),
|
|
807
870
|
fixtures: {
|
|
808
|
-
flag: [
|
|
809
|
-
|
|
871
|
+
flag: [
|
|
872
|
+
f("content/home.md", "We'll turn more of that into rewiring enquiries."),
|
|
873
|
+
f("content/home.md", "We add new towns once the first areas are working."),
|
|
874
|
+
f("content/home.md", "We target the competitors you can outdo."),
|
|
875
|
+
f("content/home.md", "We write exactly what convinces someone spending \xA38k.")
|
|
876
|
+
],
|
|
877
|
+
pass: [
|
|
878
|
+
f("content/home.md", "We'll rewrite your rewiring page around homeowners' questions."),
|
|
879
|
+
f("content/home.md", "We record every rewiring enquiry, quotation and won job."),
|
|
880
|
+
f("content/home.md", "Once the fuse board is fitted, we test every circuit.")
|
|
881
|
+
]
|
|
810
882
|
}
|
|
811
883
|
},
|
|
812
884
|
{
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
formatDirectionMatches,
|
|
5
5
|
summariseDirection,
|
|
6
6
|
validateDirection
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2OBDCIVW.js";
|
|
8
8
|
import {
|
|
9
9
|
loadNull
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-IZ6GTU54.js";
|
|
11
11
|
import {
|
|
12
12
|
parseFlags
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6NXPDCUO.js";
|
|
14
14
|
import {
|
|
15
15
|
toJson
|
|
16
16
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
rhythmOf,
|
|
31
31
|
shapeName,
|
|
32
32
|
temperatureName
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-JGWOA4LI.js";
|
|
34
34
|
|
|
35
35
|
// src/estate/cli.ts
|
|
36
36
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -133,7 +133,7 @@ async function snapshotOf(target, cwd) {
|
|
|
133
133
|
return snap;
|
|
134
134
|
}
|
|
135
135
|
if (!/^https?:\/\//.test(target)) throw new Error(`${target} is neither a URL, a snapshot file, nor a site in the register`);
|
|
136
|
-
const { snapshotUrl } = await import("./audit-
|
|
136
|
+
const { snapshotUrl } = await import("./audit-BN2GSFKT.js");
|
|
137
137
|
return snapshotUrl(target);
|
|
138
138
|
}
|
|
139
139
|
var line = (distance, sibling, shared) => `${distance.toFixed(2)}${sibling ? " SIBLING" : ""}${shared.length ? ` shares ${shared.join(", ")}` : ""}`;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
snapshotUrl,
|
|
3
3
|
snapshotUrls
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YOZLPNJS.js";
|
|
5
5
|
import {
|
|
6
6
|
describeTypicality,
|
|
7
7
|
loadNull
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IZ6GTU54.js";
|
|
9
9
|
import {
|
|
10
10
|
SOURCE_FILE,
|
|
11
11
|
finish,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
parseFlags,
|
|
15
15
|
prepareReport,
|
|
16
16
|
readPaths
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-6NXPDCUO.js";
|
|
18
18
|
import {
|
|
19
19
|
toJson
|
|
20
20
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
rhythmOf,
|
|
29
29
|
scanSource,
|
|
30
30
|
typicality
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JGWOA4LI.js";
|
|
32
32
|
|
|
33
33
|
// src/audit/cli.ts
|
|
34
34
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-OQXTQQAU.js";
|
|
4
4
|
import {
|
|
5
5
|
parseFlags
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-6NXPDCUO.js";
|
|
7
7
|
import {
|
|
8
8
|
toJson
|
|
9
9
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
hueSwatch,
|
|
23
23
|
nullPrompt,
|
|
24
24
|
scanSource
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-JGWOA4LI.js";
|
|
26
26
|
import {
|
|
27
27
|
extractStrings
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-7DQDF3VL.js";
|
|
29
29
|
|
|
30
30
|
// src/null/cli.ts
|
|
31
31
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
@@ -120,7 +120,7 @@ async function build(flags, io) {
|
|
|
120
120
|
const toShoot = ids.filter((id) => existsSync(join(pages, `${id}.html`)) && !existsSync(join(shots, `${id}.json`)));
|
|
121
121
|
if (toShoot.length) {
|
|
122
122
|
io.out(`craft null: snapshotting ${toShoot.length} page${toShoot.length === 1 ? "" : "s"}`);
|
|
123
|
-
const { snapshotUrls } = await import("./audit-
|
|
123
|
+
const { snapshotUrls } = await import("./audit-BN2GSFKT.js");
|
|
124
124
|
const results = await snapshotUrls(toShoot.map((id) => pathToFileURL(join(pages, `${id}.html`)).href));
|
|
125
125
|
results.forEach((r, i) => {
|
|
126
126
|
const id = toShoot[i];
|
|
@@ -241,7 +241,7 @@ async function importPages(flags, io) {
|
|
|
241
241
|
const toShoot = pages.filter((p) => !existsSync(join(shots, `${p.id}.json`)));
|
|
242
242
|
if (toShoot.length) {
|
|
243
243
|
io.out(`craft null: snapshotting ${toShoot.length} page${toShoot.length === 1 ? "" : "s"} from ${flags.builder}`);
|
|
244
|
-
const { snapshotUrls } = await import("./audit-
|
|
244
|
+
const { snapshotUrls } = await import("./audit-BN2GSFKT.js");
|
|
245
245
|
const servers = /* @__PURE__ */ new Map();
|
|
246
246
|
try {
|
|
247
247
|
for (const p of toShoot) if (!servers.has(p.root)) servers.set(p.root, await serveDir(p.root));
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
parseFile,
|
|
10
10
|
sentencesOf,
|
|
11
11
|
specificity
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7DQDF3VL.js";
|
|
13
13
|
import {
|
|
14
14
|
SNAPSHOT_VERSION
|
|
15
15
|
} from "./chunk-QYBAVU3P.js";
|
|
@@ -2425,6 +2425,9 @@ var HOUSE = {
|
|
|
2425
2425
|
// on 2026-09-24 after its estate hits were read (ROADMAP.md): 15 of 15 real.
|
|
2426
2426
|
"chatbot-residue": { tier: "block", label: "chatbot residue. A chat reply was pasted in: remove it, and source or cut the claim it backed" },
|
|
2427
2427
|
"review-phrase": { tier: "review", label: "review tier" },
|
|
2428
|
+
// New 2026-10-14 (founder): review first, like every new tell, until estate hits are read.
|
|
2429
|
+
"sales-tagline": { tier: "review", label: "sales tagline, say what you will actually do" },
|
|
2430
|
+
"outcome-promise": { tier: "review", label: "outcome promise, promise the work, not the result" },
|
|
2428
2431
|
"ai-vocabulary": { tier: "review", label: "AI vocabulary" },
|
|
2429
2432
|
"stock-phrase": { tier: "review", label: "stock phrase" },
|
|
2430
2433
|
"hollow-imperative": { tier: "review", label: "hollow imperative" },
|
|
@@ -3020,7 +3023,7 @@ var IMAGERY_TELLS = [
|
|
|
3020
3023
|
];
|
|
3021
3024
|
|
|
3022
3025
|
// src/character/check.ts
|
|
3023
|
-
var CATALOGUE_VERSION = "2026.
|
|
3026
|
+
var CATALOGUE_VERSION = "2026.10.2";
|
|
3024
3027
|
var CATALOGUE = [
|
|
3025
3028
|
...[...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map((t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t),
|
|
3026
3029
|
...SPECIFICITY_COPY_TELLS,
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
expandNullTargets,
|
|
6
6
|
loadNull
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IZ6GTU54.js";
|
|
8
8
|
import "./chunk-OQXTQQAU.js";
|
|
9
9
|
import {
|
|
10
10
|
parseFlags
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-6NXPDCUO.js";
|
|
12
12
|
import {
|
|
13
13
|
toJson
|
|
14
14
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
fingerprint,
|
|
22
22
|
scanSource,
|
|
23
23
|
typicality
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-JGWOA4LI.js";
|
|
25
|
+
import "./chunk-7DQDF3VL.js";
|
|
26
26
|
import {
|
|
27
27
|
SNAPSHOT_VERSION
|
|
28
28
|
} from "./chunk-QYBAVU3P.js";
|
|
@@ -188,7 +188,7 @@ async function runCalibrate(args, io) {
|
|
|
188
188
|
if (toTake.length) {
|
|
189
189
|
io.err(`craft calibrate: taking ${toTake.length} snapshot${toTake.length === 1 ? "" : "s"}`);
|
|
190
190
|
try {
|
|
191
|
-
const { snapshotUrls } = await import("./audit-
|
|
191
|
+
const { snapshotUrls } = await import("./audit-BN2GSFKT.js");
|
|
192
192
|
const results = await snapshotUrls(toTake.map((t) => t.url));
|
|
193
193
|
results.forEach((r, i) => {
|
|
194
194
|
const { entry, keep } = toTake[i];
|
|
@@ -4,15 +4,15 @@ import {
|
|
|
4
4
|
formatPairs,
|
|
5
5
|
loadEstate,
|
|
6
6
|
runEstate
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-FQYKKKHI.js";
|
|
8
|
+
import "./chunk-2OBDCIVW.js";
|
|
9
|
+
import "./chunk-IZ6GTU54.js";
|
|
10
10
|
import "./chunk-OQXTQQAU.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-6NXPDCUO.js";
|
|
12
12
|
import "./chunk-QIEDBL4J.js";
|
|
13
13
|
import "./chunk-WUACK2HX.js";
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-JGWOA4LI.js";
|
|
15
|
+
import "./chunk-7DQDF3VL.js";
|
|
16
16
|
import "./chunk-QYBAVU3P.js";
|
|
17
17
|
export {
|
|
18
18
|
formatComponentPairs,
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
merge,
|
|
5
5
|
resolvePages,
|
|
6
6
|
runAudit
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-GOPHZI6O.js";
|
|
8
|
+
import "./chunk-YOZLPNJS.js";
|
|
9
9
|
import "./chunk-JYNXGCYZ.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-IZ6GTU54.js";
|
|
11
11
|
import "./chunk-OQXTQQAU.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-6NXPDCUO.js";
|
|
13
13
|
import "./chunk-QIEDBL4J.js";
|
|
14
14
|
import "./chunk-WUACK2HX.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-JGWOA4LI.js";
|
|
16
|
+
import "./chunk-7DQDF3VL.js";
|
|
17
17
|
import "./chunk-QYBAVU3P.js";
|
|
18
18
|
export {
|
|
19
19
|
describeComponents,
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
SOURCE_FILE,
|
|
11
11
|
readPaths
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-6NXPDCUO.js";
|
|
13
13
|
import {
|
|
14
14
|
toJson
|
|
15
15
|
} from "./chunk-QIEDBL4J.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-JGWOA4LI.js";
|
|
17
|
+
import "./chunk-7DQDF3VL.js";
|
|
18
18
|
import "./chunk-QYBAVU3P.js";
|
|
19
19
|
|
|
20
20
|
// src/phone/cli.ts
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
PROPOSED,
|
|
8
8
|
researchPrompt,
|
|
9
9
|
validateDirection
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2OBDCIVW.js";
|
|
11
11
|
import {
|
|
12
12
|
parseFlags
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6NXPDCUO.js";
|
|
14
14
|
import {
|
|
15
15
|
toJson
|
|
16
16
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -26,8 +26,8 @@ import {
|
|
|
26
26
|
isCream,
|
|
27
27
|
oklchToRgb,
|
|
28
28
|
parseColour
|
|
29
|
-
} from "./chunk-
|
|
30
|
-
import "./chunk-
|
|
29
|
+
} from "./chunk-JGWOA4LI.js";
|
|
30
|
+
import "./chunk-7DQDF3VL.js";
|
|
31
31
|
import "./chunk-QYBAVU3P.js";
|
|
32
32
|
|
|
33
33
|
// src/direction/cli.ts
|
|
@@ -208,7 +208,7 @@ var readJson = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
|
208
208
|
async function snapshotFrom(target, cwd) {
|
|
209
209
|
if (!target) return void 0;
|
|
210
210
|
if (/^https?:\/\//.test(target)) {
|
|
211
|
-
const { snapshotUrl } = await import("./audit-
|
|
211
|
+
const { snapshotUrl } = await import("./audit-BN2GSFKT.js");
|
|
212
212
|
return snapshotUrl(target);
|
|
213
213
|
}
|
|
214
214
|
return readSnapshot(readJson(resolve(cwd, target)), target);
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
runHarvest,
|
|
10
10
|
runNull,
|
|
11
11
|
serveDir
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IZ6GTU54.js";
|
|
13
13
|
import "./chunk-OQXTQQAU.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-6NXPDCUO.js";
|
|
15
15
|
import "./chunk-QIEDBL4J.js";
|
|
16
16
|
import "./chunk-WUACK2HX.js";
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-JGWOA4LI.js";
|
|
18
|
+
import "./chunk-7DQDF3VL.js";
|
|
19
19
|
import "./chunk-QYBAVU3P.js";
|
|
20
20
|
export {
|
|
21
21
|
describeTypicality,
|
|
@@ -3,21 +3,21 @@ import {
|
|
|
3
3
|
} from "./chunk-OKVHWXYV.js";
|
|
4
4
|
import {
|
|
5
5
|
merge
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-GOPHZI6O.js";
|
|
7
|
+
import "./chunk-YOZLPNJS.js";
|
|
8
8
|
import "./chunk-JYNXGCYZ.js";
|
|
9
9
|
import {
|
|
10
10
|
SIBLING_AT,
|
|
11
11
|
compareToEstate,
|
|
12
12
|
loadEstate
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-FQYKKKHI.js";
|
|
14
14
|
import {
|
|
15
15
|
PAGE_TYPES,
|
|
16
16
|
validateDirection
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2OBDCIVW.js";
|
|
18
18
|
import {
|
|
19
19
|
loadNull
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-IZ6GTU54.js";
|
|
21
21
|
import "./chunk-OQXTQQAU.js";
|
|
22
22
|
import {
|
|
23
23
|
COPY_FILE,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
loadExceptions,
|
|
29
29
|
parseFlags,
|
|
30
30
|
readPaths
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-6NXPDCUO.js";
|
|
32
32
|
import {
|
|
33
33
|
toJson
|
|
34
34
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -42,8 +42,8 @@ import {
|
|
|
42
42
|
fingerprint,
|
|
43
43
|
scanSource,
|
|
44
44
|
typicality
|
|
45
|
-
} from "./chunk-
|
|
46
|
-
import "./chunk-
|
|
45
|
+
} from "./chunk-JGWOA4LI.js";
|
|
46
|
+
import "./chunk-7DQDF3VL.js";
|
|
47
47
|
import "./chunk-QYBAVU3P.js";
|
|
48
48
|
|
|
49
49
|
// src/report/cli.ts
|
|
@@ -126,7 +126,7 @@ async function runReport(command, args, io) {
|
|
|
126
126
|
if (/\.json$/i.test(target2) && existsSync(local)) {
|
|
127
127
|
snap = readSnapshot(JSON.parse(readFileSync(local, "utf8")), target2);
|
|
128
128
|
} else {
|
|
129
|
-
const { snapshotUrl } = await import("./audit-
|
|
129
|
+
const { snapshotUrl } = await import("./audit-BN2GSFKT.js");
|
|
130
130
|
snap = await snapshotUrl(target2);
|
|
131
131
|
}
|
|
132
132
|
const fp = fingerprint(snap);
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
run
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6NXPDCUO.js";
|
|
5
5
|
import "./chunk-QIEDBL4J.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-JGWOA4LI.js";
|
|
7
|
+
import "./chunk-7DQDF3VL.js";
|
|
8
8
|
import "./chunk-QYBAVU3P.js";
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
package/dist/index.cjs
CHANGED
|
@@ -2003,6 +2003,8 @@ var REVIEW_PHRASES = [
|
|
|
2003
2003
|
"truly",
|
|
2004
2004
|
"genuinely",
|
|
2005
2005
|
"deeply",
|
|
2006
|
+
"clear",
|
|
2007
|
+
"clearly",
|
|
2006
2008
|
"ultimately",
|
|
2007
2009
|
"one-size-fits-all",
|
|
2008
2010
|
"one size fits all",
|
|
@@ -2013,6 +2015,33 @@ var REVIEW_PHRASES = [
|
|
|
2013
2015
|
"arguably the best",
|
|
2014
2016
|
"bar none"
|
|
2015
2017
|
];
|
|
2018
|
+
var SALES_TAGLINES = [
|
|
2019
|
+
/\bwhere it (?:really |truly )?(?:counts|matters)(?: most)?\b/i,
|
|
2020
|
+
/\breal,? (?:measurable |tangible )?results\b/i,
|
|
2021
|
+
/\bresults that (?:matter|count|speak|last)\b/i,
|
|
2022
|
+
/\bresults you can (?:see|trust|measure)\b/i,
|
|
2023
|
+
/\bwork(?:s|ing)? harder for you\b/i,
|
|
2024
|
+
/\bmake (?:every|your) (?:penny|pound) count\b/i,
|
|
2025
|
+
/\bevery (?:penny|pound) (?:counts|works)\b/i,
|
|
2026
|
+
/\bbang for your buck\b/i,
|
|
2027
|
+
/\bstand out from the (?:crowd|competition)\b/i,
|
|
2028
|
+
/\b(?:pages?|websites?|sites?|copy|content|ads?) that (?:actually |really )?(?:converts?|sells?)\b/i
|
|
2029
|
+
];
|
|
2030
|
+
var SALES_RESULTS = "(?:enquiries|leads|customers|clients|sales|jobs|bookings|revenue|calls|traffic|profit)";
|
|
2031
|
+
var OUTCOME_PROMISES = [
|
|
2032
|
+
// "We'll turn more of that into rewiring enquiries"
|
|
2033
|
+
new RegExp(`\\bturn(?:s|ing)?\\b[^.!?\\n]{0,50}?\\binto\\s+(?:more\\s+|real\\s+|paying\\s+)?(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i"),
|
|
2034
|
+
// "once the first areas are working"
|
|
2035
|
+
/\bonce\b[^.!?\n]{0,40}?\b(?:is|are|starts?|begins?)\s+(?:working|paying off|performing|ranking|delivering|bringing in)\b/i,
|
|
2036
|
+
// "competitors you can outdo"
|
|
2037
|
+
/\b(?:competitors?|competition|rivals?)\b[^.!?\n]{0,25}?\b(?:you|we)\s*(?:can|will|could|['’]ll)\s+(?:easily\s+)?(?:outdo|beat|outrank|overtake|outperform|outshine)\b/i,
|
|
2038
|
+
/\b(?:outdo|outrank|overtake|outperform|outshine|beat)\s+(?:your|the|local|bigger)\s+(?:[\w-]+\s+)?(?:competitors|competition|rivals)\b/i,
|
|
2039
|
+
// "exactly what convinces someone spending £8k"
|
|
2040
|
+
/\bexactly what\b[^.!?\n]{0,15}?\b(?:convinces|persuades|wins|sells)\b/i,
|
|
2041
|
+
new RegExp(`\\bguarantee[sd]?\\b[^.!?\\n]{0,25}?\\b(?:results|rankings?|first page|page one|top spot|${SALES_RESULTS.slice(3, -1)})\\b`, "i"),
|
|
2042
|
+
/\byou(?:['’]ll| will)\s+(?:get|see|win|receive|start getting)\s+more\b/i,
|
|
2043
|
+
new RegExp(`\\b(?:will|we['\u2019]ll)\\s+(?:(?:bring|drive|generate|get)\\s+you|bring|drive|generate)\\s+more\\s+(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i")
|
|
2044
|
+
];
|
|
2016
2045
|
var NEGATION = [
|
|
2017
2046
|
// "It's not just a haircut, it's an experience."
|
|
2018
2047
|
/\b(?:it['’]?s|it is|this is|this isn['’]t|we['’]re|we are|they['’]re)\s+(?:not|more than)\s+(?:just|only|merely|simply)?\s*[^.!?\n]{1,60}?[,;.—-]\s*(?:it['’]?s|it is|this is|we['’]re|we are|they['’]re)\b/i,
|
|
@@ -2198,12 +2227,55 @@ var COPY_TELLS = [
|
|
|
2198
2227
|
generation: 1,
|
|
2199
2228
|
severity: "warn",
|
|
2200
2229
|
surface: "copy",
|
|
2201
|
-
why: "Truly, genuinely, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
2230
|
+
why: "Truly, genuinely, clear, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
2202
2231
|
fix: "Keep the one that lands hardest and cut the rest. 'What is genuinely excellent' reads better as 'what is excellent'.",
|
|
2203
|
-
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1")).join("|")})`, "i")], quoted),
|
|
2232
|
+
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1").replace(/(\w)$/, "$1\\b")).join("|")})`, "i")], quoted),
|
|
2233
|
+
fixtures: {
|
|
2234
|
+
flag: [f("content/home.md", "We are truly proud of it."), f("content/home.md", "You leave with a clear next step.")],
|
|
2235
|
+
pass: [f("content/home.md", "We have fitted 400 boilers since 2009."), f("content/home.md", "Keep a safe clearance around the meter.")]
|
|
2236
|
+
}
|
|
2237
|
+
},
|
|
2238
|
+
{
|
|
2239
|
+
id: "sales-tagline",
|
|
2240
|
+
name: "Sales tagline",
|
|
2241
|
+
generation: 1,
|
|
2242
|
+
severity: "warn",
|
|
2243
|
+
surface: "copy",
|
|
2244
|
+
why: "'So your budget goes where it counts', 'Real results', 'pages that convert': advert lines that sound like a pitch and say nothing a reader can check.",
|
|
2245
|
+
fix: "Say what you will actually do and why. 'So your budget goes where it counts' becomes 'Oxford first, because that is where you want the work'.",
|
|
2246
|
+
detect: phrases(SALES_TAGLINES, quoted),
|
|
2247
|
+
fixtures: {
|
|
2248
|
+
flag: [
|
|
2249
|
+
f("content/home.md", "Strongest opportunities first, so your budget goes where it counts."),
|
|
2250
|
+
f("content/home.md", "A clearer online presence. More enquiries. Real results.")
|
|
2251
|
+
],
|
|
2252
|
+
pass: [
|
|
2253
|
+
f("content/home.md", "We start with Oxford, then expand based on search demand and results."),
|
|
2254
|
+
f("content/home.md", "We report the results every month.")
|
|
2255
|
+
]
|
|
2256
|
+
}
|
|
2257
|
+
},
|
|
2258
|
+
{
|
|
2259
|
+
id: "outcome-promise",
|
|
2260
|
+
name: "Outcome promise",
|
|
2261
|
+
generation: 1,
|
|
2262
|
+
severity: "warn",
|
|
2263
|
+
surface: "copy",
|
|
2264
|
+
why: "'We'll turn that into enquiries', 'once the first areas are working', 'competitors you can outdo': a hoped-for result written as if it were delivered. Rankings and enquiries depend on things nobody controls.",
|
|
2265
|
+
fix: "Promise the work, not the outcome. 'We'll turn that into enquiries' becomes 'We'll rewrite the page around the questions homeowners ask'. Results are measured and reported, never promised.",
|
|
2266
|
+
detect: phrases(OUTCOME_PROMISES, quoted),
|
|
2204
2267
|
fixtures: {
|
|
2205
|
-
flag: [
|
|
2206
|
-
|
|
2268
|
+
flag: [
|
|
2269
|
+
f("content/home.md", "We'll turn more of that into rewiring enquiries."),
|
|
2270
|
+
f("content/home.md", "We add new towns once the first areas are working."),
|
|
2271
|
+
f("content/home.md", "We target the competitors you can outdo."),
|
|
2272
|
+
f("content/home.md", "We write exactly what convinces someone spending \xA38k.")
|
|
2273
|
+
],
|
|
2274
|
+
pass: [
|
|
2275
|
+
f("content/home.md", "We'll rewrite your rewiring page around homeowners' questions."),
|
|
2276
|
+
f("content/home.md", "We record every rewiring enquiry, quotation and won job."),
|
|
2277
|
+
f("content/home.md", "Once the fuse board is fitted, we test every circuit.")
|
|
2278
|
+
]
|
|
2207
2279
|
}
|
|
2208
2280
|
},
|
|
2209
2281
|
{
|
|
@@ -5118,6 +5190,9 @@ var HOUSE = {
|
|
|
5118
5190
|
// on 2026-09-24 after its estate hits were read (ROADMAP.md): 15 of 15 real.
|
|
5119
5191
|
"chatbot-residue": { tier: "block", label: "chatbot residue. A chat reply was pasted in: remove it, and source or cut the claim it backed" },
|
|
5120
5192
|
"review-phrase": { tier: "review", label: "review tier" },
|
|
5193
|
+
// New 2026-10-14 (founder): review first, like every new tell, until estate hits are read.
|
|
5194
|
+
"sales-tagline": { tier: "review", label: "sales tagline, say what you will actually do" },
|
|
5195
|
+
"outcome-promise": { tier: "review", label: "outcome promise, promise the work, not the result" },
|
|
5121
5196
|
"ai-vocabulary": { tier: "review", label: "AI vocabulary" },
|
|
5122
5197
|
"stock-phrase": { tier: "review", label: "stock phrase" },
|
|
5123
5198
|
"hollow-imperative": { tier: "review", label: "hollow imperative" },
|
|
@@ -6323,7 +6398,7 @@ var IMAGERY_TELLS = [
|
|
|
6323
6398
|
];
|
|
6324
6399
|
|
|
6325
6400
|
// src/character/check.ts
|
|
6326
|
-
var CATALOGUE_VERSION = "2026.
|
|
6401
|
+
var CATALOGUE_VERSION = "2026.10.2";
|
|
6327
6402
|
var CATALOGUE = [
|
|
6328
6403
|
...[...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map((t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t),
|
|
6329
6404
|
...SPECIFICITY_COPY_TELLS,
|
package/dist/index.d.cts
CHANGED
|
@@ -1252,7 +1252,7 @@ interface CheckReport {
|
|
|
1252
1252
|
* Bumped whenever an entry is added, removed or its detection changes, so a
|
|
1253
1253
|
* report can say which list it was judged against.
|
|
1254
1254
|
*/
|
|
1255
|
-
declare const CATALOGUE_VERSION = "2026.
|
|
1255
|
+
declare const CATALOGUE_VERSION = "2026.10.2";
|
|
1256
1256
|
declare const CATALOGUE: readonly Tell[];
|
|
1257
1257
|
declare function tellById(id: string): Tell | undefined;
|
|
1258
1258
|
/**
|
|
@@ -3013,7 +3013,7 @@ declare const NEGATIVE_REASSURANCE: readonly ["no risk", "never locked in", "not
|
|
|
3013
3013
|
/** Banned only when vague. Reported for review, never blocking. */
|
|
3014
3014
|
declare const VAGUE_WORDS: readonly ["innovative", "scalable", "end-to-end", "streamline", "solutions"];
|
|
3015
3015
|
/** The review tier of copy-rules.md: normal English once, a tell when stacked. */
|
|
3016
|
-
declare const REVIEW_PHRASES: readonly ["great question", "of course!", "absolutely!", "absolutely,", "truly", "genuinely", "deeply", "ultimately", "one-size-fits-all", "one size fits all", "let me know if you'd", "the single most", "by far the most", "hands down the", "arguably the best", "bar none"];
|
|
3016
|
+
declare const REVIEW_PHRASES: readonly ["great question", "of course!", "absolutely!", "absolutely,", "truly", "genuinely", "deeply", "clear", "clearly", "ultimately", "one-size-fits-all", "one size fits all", "let me know if you'd", "the single most", "by far the most", "hands down the", "arguably the best", "bar none"];
|
|
3017
3017
|
|
|
3018
3018
|
/**
|
|
3019
3019
|
* The reason rule, one standard for every layer of an art direction.
|
package/dist/index.d.ts
CHANGED
|
@@ -1252,7 +1252,7 @@ interface CheckReport {
|
|
|
1252
1252
|
* Bumped whenever an entry is added, removed or its detection changes, so a
|
|
1253
1253
|
* report can say which list it was judged against.
|
|
1254
1254
|
*/
|
|
1255
|
-
declare const CATALOGUE_VERSION = "2026.
|
|
1255
|
+
declare const CATALOGUE_VERSION = "2026.10.2";
|
|
1256
1256
|
declare const CATALOGUE: readonly Tell[];
|
|
1257
1257
|
declare function tellById(id: string): Tell | undefined;
|
|
1258
1258
|
/**
|
|
@@ -3013,7 +3013,7 @@ declare const NEGATIVE_REASSURANCE: readonly ["no risk", "never locked in", "not
|
|
|
3013
3013
|
/** Banned only when vague. Reported for review, never blocking. */
|
|
3014
3014
|
declare const VAGUE_WORDS: readonly ["innovative", "scalable", "end-to-end", "streamline", "solutions"];
|
|
3015
3015
|
/** The review tier of copy-rules.md: normal English once, a tell when stacked. */
|
|
3016
|
-
declare const REVIEW_PHRASES: readonly ["great question", "of course!", "absolutely!", "absolutely,", "truly", "genuinely", "deeply", "ultimately", "one-size-fits-all", "one size fits all", "let me know if you'd", "the single most", "by far the most", "hands down the", "arguably the best", "bar none"];
|
|
3016
|
+
declare const REVIEW_PHRASES: readonly ["great question", "of course!", "absolutely!", "absolutely,", "truly", "genuinely", "deeply", "clear", "clearly", "ultimately", "one-size-fits-all", "one size fits all", "let me know if you'd", "the single most", "by far the most", "hands down the", "arguably the best", "bar none"];
|
|
3017
3017
|
|
|
3018
3018
|
/**
|
|
3019
3019
|
* The reason rule, one standard for every layer of an art direction.
|
package/dist/index.js
CHANGED
|
@@ -1701,6 +1701,8 @@ var REVIEW_PHRASES = [
|
|
|
1701
1701
|
"truly",
|
|
1702
1702
|
"genuinely",
|
|
1703
1703
|
"deeply",
|
|
1704
|
+
"clear",
|
|
1705
|
+
"clearly",
|
|
1704
1706
|
"ultimately",
|
|
1705
1707
|
"one-size-fits-all",
|
|
1706
1708
|
"one size fits all",
|
|
@@ -1711,6 +1713,33 @@ var REVIEW_PHRASES = [
|
|
|
1711
1713
|
"arguably the best",
|
|
1712
1714
|
"bar none"
|
|
1713
1715
|
];
|
|
1716
|
+
var SALES_TAGLINES = [
|
|
1717
|
+
/\bwhere it (?:really |truly )?(?:counts|matters)(?: most)?\b/i,
|
|
1718
|
+
/\breal,? (?:measurable |tangible )?results\b/i,
|
|
1719
|
+
/\bresults that (?:matter|count|speak|last)\b/i,
|
|
1720
|
+
/\bresults you can (?:see|trust|measure)\b/i,
|
|
1721
|
+
/\bwork(?:s|ing)? harder for you\b/i,
|
|
1722
|
+
/\bmake (?:every|your) (?:penny|pound) count\b/i,
|
|
1723
|
+
/\bevery (?:penny|pound) (?:counts|works)\b/i,
|
|
1724
|
+
/\bbang for your buck\b/i,
|
|
1725
|
+
/\bstand out from the (?:crowd|competition)\b/i,
|
|
1726
|
+
/\b(?:pages?|websites?|sites?|copy|content|ads?) that (?:actually |really )?(?:converts?|sells?)\b/i
|
|
1727
|
+
];
|
|
1728
|
+
var SALES_RESULTS = "(?:enquiries|leads|customers|clients|sales|jobs|bookings|revenue|calls|traffic|profit)";
|
|
1729
|
+
var OUTCOME_PROMISES = [
|
|
1730
|
+
// "We'll turn more of that into rewiring enquiries"
|
|
1731
|
+
new RegExp(`\\bturn(?:s|ing)?\\b[^.!?\\n]{0,50}?\\binto\\s+(?:more\\s+|real\\s+|paying\\s+)?(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i"),
|
|
1732
|
+
// "once the first areas are working"
|
|
1733
|
+
/\bonce\b[^.!?\n]{0,40}?\b(?:is|are|starts?|begins?)\s+(?:working|paying off|performing|ranking|delivering|bringing in)\b/i,
|
|
1734
|
+
// "competitors you can outdo"
|
|
1735
|
+
/\b(?:competitors?|competition|rivals?)\b[^.!?\n]{0,25}?\b(?:you|we)\s*(?:can|will|could|['’]ll)\s+(?:easily\s+)?(?:outdo|beat|outrank|overtake|outperform|outshine)\b/i,
|
|
1736
|
+
/\b(?:outdo|outrank|overtake|outperform|outshine|beat)\s+(?:your|the|local|bigger)\s+(?:[\w-]+\s+)?(?:competitors|competition|rivals)\b/i,
|
|
1737
|
+
// "exactly what convinces someone spending £8k"
|
|
1738
|
+
/\bexactly what\b[^.!?\n]{0,15}?\b(?:convinces|persuades|wins|sells)\b/i,
|
|
1739
|
+
new RegExp(`\\bguarantee[sd]?\\b[^.!?\\n]{0,25}?\\b(?:results|rankings?|first page|page one|top spot|${SALES_RESULTS.slice(3, -1)})\\b`, "i"),
|
|
1740
|
+
/\byou(?:['’]ll| will)\s+(?:get|see|win|receive|start getting)\s+more\b/i,
|
|
1741
|
+
new RegExp(`\\b(?:will|we['\u2019]ll)\\s+(?:(?:bring|drive|generate|get)\\s+you|bring|drive|generate)\\s+more\\s+(?:[\\w-]+\\s+)?${SALES_RESULTS}\\b`, "i")
|
|
1742
|
+
];
|
|
1714
1743
|
var NEGATION = [
|
|
1715
1744
|
// "It's not just a haircut, it's an experience."
|
|
1716
1745
|
/\b(?:it['’]?s|it is|this is|this isn['’]t|we['’]re|we are|they['’]re)\s+(?:not|more than)\s+(?:just|only|merely|simply)?\s*[^.!?\n]{1,60}?[,;.—-]\s*(?:it['’]?s|it is|this is|we['’]re|we are|they['’]re)\b/i,
|
|
@@ -1896,12 +1925,55 @@ var COPY_TELLS = [
|
|
|
1896
1925
|
generation: 1,
|
|
1897
1926
|
severity: "warn",
|
|
1898
1927
|
surface: "copy",
|
|
1899
|
-
why: "Truly, genuinely, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
1928
|
+
why: "Truly, genuinely, clear, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking.",
|
|
1900
1929
|
fix: "Keep the one that lands hardest and cut the rest. 'What is genuinely excellent' reads better as 'what is excellent'.",
|
|
1901
|
-
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1")).join("|")})`, "i")], quoted),
|
|
1930
|
+
detect: phrases([new RegExp(`(?:^|\\b)(?:${[...REVIEW_PHRASES].sort((a, b) => b.length - a.length).map((p) => escape(p).replace(/^(\w)/, "\\b$1").replace(/(\w)$/, "$1\\b")).join("|")})`, "i")], quoted),
|
|
1931
|
+
fixtures: {
|
|
1932
|
+
flag: [f("content/home.md", "We are truly proud of it."), f("content/home.md", "You leave with a clear next step.")],
|
|
1933
|
+
pass: [f("content/home.md", "We have fitted 400 boilers since 2009."), f("content/home.md", "Keep a safe clearance around the meter.")]
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
{
|
|
1937
|
+
id: "sales-tagline",
|
|
1938
|
+
name: "Sales tagline",
|
|
1939
|
+
generation: 1,
|
|
1940
|
+
severity: "warn",
|
|
1941
|
+
surface: "copy",
|
|
1942
|
+
why: "'So your budget goes where it counts', 'Real results', 'pages that convert': advert lines that sound like a pitch and say nothing a reader can check.",
|
|
1943
|
+
fix: "Say what you will actually do and why. 'So your budget goes where it counts' becomes 'Oxford first, because that is where you want the work'.",
|
|
1944
|
+
detect: phrases(SALES_TAGLINES, quoted),
|
|
1945
|
+
fixtures: {
|
|
1946
|
+
flag: [
|
|
1947
|
+
f("content/home.md", "Strongest opportunities first, so your budget goes where it counts."),
|
|
1948
|
+
f("content/home.md", "A clearer online presence. More enquiries. Real results.")
|
|
1949
|
+
],
|
|
1950
|
+
pass: [
|
|
1951
|
+
f("content/home.md", "We start with Oxford, then expand based on search demand and results."),
|
|
1952
|
+
f("content/home.md", "We report the results every month.")
|
|
1953
|
+
]
|
|
1954
|
+
}
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
id: "outcome-promise",
|
|
1958
|
+
name: "Outcome promise",
|
|
1959
|
+
generation: 1,
|
|
1960
|
+
severity: "warn",
|
|
1961
|
+
surface: "copy",
|
|
1962
|
+
why: "'We'll turn that into enquiries', 'once the first areas are working', 'competitors you can outdo': a hoped-for result written as if it were delivered. Rankings and enquiries depend on things nobody controls.",
|
|
1963
|
+
fix: "Promise the work, not the outcome. 'We'll turn that into enquiries' becomes 'We'll rewrite the page around the questions homeowners ask'. Results are measured and reported, never promised.",
|
|
1964
|
+
detect: phrases(OUTCOME_PROMISES, quoted),
|
|
1902
1965
|
fixtures: {
|
|
1903
|
-
flag: [
|
|
1904
|
-
|
|
1966
|
+
flag: [
|
|
1967
|
+
f("content/home.md", "We'll turn more of that into rewiring enquiries."),
|
|
1968
|
+
f("content/home.md", "We add new towns once the first areas are working."),
|
|
1969
|
+
f("content/home.md", "We target the competitors you can outdo."),
|
|
1970
|
+
f("content/home.md", "We write exactly what convinces someone spending \xA38k.")
|
|
1971
|
+
],
|
|
1972
|
+
pass: [
|
|
1973
|
+
f("content/home.md", "We'll rewrite your rewiring page around homeowners' questions."),
|
|
1974
|
+
f("content/home.md", "We record every rewiring enquiry, quotation and won job."),
|
|
1975
|
+
f("content/home.md", "Once the fuse board is fitted, we test every circuit.")
|
|
1976
|
+
]
|
|
1905
1977
|
}
|
|
1906
1978
|
},
|
|
1907
1979
|
{
|
|
@@ -4816,6 +4888,9 @@ var HOUSE = {
|
|
|
4816
4888
|
// on 2026-09-24 after its estate hits were read (ROADMAP.md): 15 of 15 real.
|
|
4817
4889
|
"chatbot-residue": { tier: "block", label: "chatbot residue. A chat reply was pasted in: remove it, and source or cut the claim it backed" },
|
|
4818
4890
|
"review-phrase": { tier: "review", label: "review tier" },
|
|
4891
|
+
// New 2026-10-14 (founder): review first, like every new tell, until estate hits are read.
|
|
4892
|
+
"sales-tagline": { tier: "review", label: "sales tagline, say what you will actually do" },
|
|
4893
|
+
"outcome-promise": { tier: "review", label: "outcome promise, promise the work, not the result" },
|
|
4819
4894
|
"ai-vocabulary": { tier: "review", label: "AI vocabulary" },
|
|
4820
4895
|
"stock-phrase": { tier: "review", label: "stock phrase" },
|
|
4821
4896
|
"hollow-imperative": { tier: "review", label: "hollow imperative" },
|
|
@@ -6021,7 +6096,7 @@ var IMAGERY_TELLS = [
|
|
|
6021
6096
|
];
|
|
6022
6097
|
|
|
6023
6098
|
// src/character/check.ts
|
|
6024
|
-
var CATALOGUE_VERSION = "2026.
|
|
6099
|
+
var CATALOGUE_VERSION = "2026.10.2";
|
|
6025
6100
|
var CATALOGUE = [
|
|
6026
6101
|
...[...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map((t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t),
|
|
6027
6102
|
...SPECIFICITY_COPY_TELLS,
|
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
} from "./chunk-IJUZ7RV4.js";
|
|
6
6
|
import {
|
|
7
7
|
loadPlaywright
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YOZLPNJS.js";
|
|
9
9
|
import "./chunk-JYNXGCYZ.js";
|
|
10
10
|
import {
|
|
11
11
|
toJson
|
|
12
12
|
} from "./chunk-QIEDBL4J.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-JGWOA4LI.js";
|
|
14
|
+
import "./chunk-7DQDF3VL.js";
|
|
15
15
|
import "./chunk-QYBAVU3P.js";
|
|
16
16
|
|
|
17
17
|
// src/phone/viewports-cli.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domandigital/craft",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Doman Digital house craft standard as numbers: OKLCh colour ramps that snap to existing anchors, derived semantic tokens with measured WCAG and APCA contrast, one shared motion vocabulary for CSS and JS, and a base stylesheet. Zero dependencies.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/word-lists.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"catalogueVersion": "2026.
|
|
2
|
+
"catalogueVersion": "2026.10.2",
|
|
3
3
|
"lists": {
|
|
4
4
|
"aiWords": [
|
|
5
5
|
"tapestry",
|
|
@@ -150,6 +150,8 @@
|
|
|
150
150
|
"truly",
|
|
151
151
|
"genuinely",
|
|
152
152
|
"deeply",
|
|
153
|
+
"clear",
|
|
154
|
+
"clearly",
|
|
153
155
|
"ultimately",
|
|
154
156
|
"one-size-fits-all",
|
|
155
157
|
"one size fits all",
|