@koda-sl/baker-cli 0.274.0-dev.bbba3d7fc → 0.274.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/{chunk-VWLNPPC6.js → chunk-36KVUYVL.js} +4 -4
- package/dist/chunk-36KVUYVL.js.map +1 -0
- package/dist/cli.js +253 -63
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-VWLNPPC6.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-36KVUYVL.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -12459,22 +12459,7 @@ var ANALYTICS_EVENT_TYPES = [
|
|
|
12459
12459
|
* know who this was" unanswerable.
|
|
12460
12460
|
*/
|
|
12461
12461
|
"identify",
|
|
12462
|
-
/**
|
|
12463
|
-
* A named event: an escape hatch a page defines, or one a client's own server
|
|
12464
|
-
* posted to `POST /v1/events`.
|
|
12465
|
-
*
|
|
12466
|
-
* One type for both, deliberately. There was briefly a `server_event`, and
|
|
12467
|
-
* the type was the mistake: every report that shows named events whitelists
|
|
12468
|
-
* `custom`, so a server-sent "Deal won" was structurally invisible on the
|
|
12469
|
-
* Page events card, in Top pages and in the events catalogue — the three
|
|
12470
|
-
* screens somebody would look at after being told it had arrived. GA4 makes
|
|
12471
|
-
* the same call for the same reason: a Measurement Protocol event is just an
|
|
12472
|
-
* event, and the transport is a property.
|
|
12473
|
-
*
|
|
12474
|
-
* Which collector wrote it is {@link ANALYTICS_SOURCES} — `client` for a
|
|
12475
|
-
* page, `ingest` for a third party's server — and every report that needs to
|
|
12476
|
-
* tell them apart filters on that.
|
|
12477
|
-
*/
|
|
12462
|
+
/** Escape hatch for a page-specific event. */
|
|
12478
12463
|
"custom"
|
|
12479
12464
|
];
|
|
12480
12465
|
var ANALYTICS_DEVICE_TYPES = ["desktop", "mobile", "tablet", "bot", "unknown"];
|
|
@@ -12997,19 +12982,6 @@ var analyticsQueryRequestSchema = z25.object({
|
|
|
12997
12982
|
* superset and the right default for "how is paid doing".
|
|
12998
12983
|
*/
|
|
12999
12984
|
adPlatform: z25.enum(AD_PLATFORMS).optional(),
|
|
13000
|
-
/**
|
|
13001
|
-
* Read only what one collector produced: `site` for everything Baker
|
|
13002
|
-
* measured itself, `systems` for what a client's own server posted to
|
|
13003
|
-
* `POST /v1/events`.
|
|
13004
|
-
*
|
|
13005
|
-
* Omit it for all of it, which is the default and the honest superset.
|
|
13006
|
-
* Only the reads where the split is a question somebody asks honour it —
|
|
13007
|
-
* the totals, the trend, the two page-event reads, the conversion
|
|
13008
|
-
* breakdown and the events catalogue. A session-scoped report cannot: an
|
|
13009
|
-
* ingest row carries no `session_id`, deliberately, so there is no visit
|
|
13010
|
-
* for it to be counted in.
|
|
13011
|
-
*/
|
|
13012
|
-
eventOrigin: z25.enum(["site", "systems"]).optional(),
|
|
13013
12985
|
/** Return the long breakdowns and the per-day trend. */
|
|
13014
12986
|
full: z25.boolean().optional(),
|
|
13015
12987
|
/**
|
|
@@ -13282,7 +13254,6 @@ var analyticsTagRowSchema = z25.object({
|
|
|
13282
13254
|
/** Converting visits per entrance. */
|
|
13283
13255
|
conversionRate: z25.number().min(0).max(1).nullable()
|
|
13284
13256
|
});
|
|
13285
|
-
var ANALYTICS_EVENT_ORIGINS = ["site", "systems", "both"];
|
|
13286
13257
|
var analyticsCustomEventRowSchema = z25.object({
|
|
13287
13258
|
eventName: z25.string(),
|
|
13288
13259
|
events: z25.number().int().nonnegative(),
|
|
@@ -13298,9 +13269,7 @@ var analyticsCustomEventRowSchema = z25.object({
|
|
|
13298
13269
|
*/
|
|
13299
13270
|
propertyKeys: z25.array(z25.string()),
|
|
13300
13271
|
valueTotal: z25.number(),
|
|
13301
|
-
landings: z25.array(z25.string())
|
|
13302
|
-
/** Which collector produced this name — and `both` when more than one did. */
|
|
13303
|
-
origin: z25.enum(ANALYTICS_EVENT_ORIGINS)
|
|
13272
|
+
landings: z25.array(z25.string())
|
|
13304
13273
|
});
|
|
13305
13274
|
var analyticsEventBreakdownRowSchema = z25.object({
|
|
13306
13275
|
value: z25.string(),
|
|
@@ -13635,16 +13604,6 @@ var analyticsConversionRowSchema = z25.object({
|
|
|
13635
13604
|
var analyticsEventCatalogRowSchema = z25.object({
|
|
13636
13605
|
/** `form:…` | `submit:…` | `page:…` | `exit:…` — parse with `parseEventKey`. */
|
|
13637
13606
|
eventKey: z25.string(),
|
|
13638
|
-
/**
|
|
13639
|
-
* Which collector produced this key — and `both` when more than one did.
|
|
13640
|
-
*
|
|
13641
|
-
* On the row rather than in the key, which is the whole shape of the change
|
|
13642
|
-
* that removed the `server:` family: a picker can say where an event came
|
|
13643
|
-
* from without a filter round-trip, and a report can still separate the two
|
|
13644
|
-
* populations at any grain, but nothing downstream has to learn a second
|
|
13645
|
-
* vocabulary to count one.
|
|
13646
|
-
*/
|
|
13647
|
-
origin: z25.enum(ANALYTICS_EVENT_ORIGINS),
|
|
13648
13607
|
events: z25.number(),
|
|
13649
13608
|
sessions: z25.number(),
|
|
13650
13609
|
/** The Form node's kind, for a `form:` key. Empty otherwise. */
|
|
@@ -43331,7 +43290,7 @@ Full guide: __tooling__/docs/tools/baker/images.md`
|
|
|
43331
43290
|
import { defineCommand as defineCommand168 } from "citty";
|
|
43332
43291
|
|
|
43333
43292
|
// src/commands/landing/critique.ts
|
|
43334
|
-
import { readdir as readdir9, stat as stat6 } from "fs/promises";
|
|
43293
|
+
import { readdir as readdir9, readFile as readFile22, stat as stat6 } from "fs/promises";
|
|
43335
43294
|
import path28 from "path";
|
|
43336
43295
|
import { defineCommand as defineCommand158 } from "citty";
|
|
43337
43296
|
|
|
@@ -43461,6 +43420,29 @@ var COPY_TELL_GATES = {
|
|
|
43461
43420
|
"inline-header-list": { minCount: 3, severity: "advisory" },
|
|
43462
43421
|
emoji: { minCount: 3, severity: "advisory" }
|
|
43463
43422
|
};
|
|
43423
|
+
var CONCESSION_PATTERNS = [
|
|
43424
|
+
// Cedes superiority to somebody else.
|
|
43425
|
+
/\b(?:mejor|peor)(?:es)? que (?:el |la |lo |los |las )?nuestr[oa]s?\b/g,
|
|
43426
|
+
/\bmas [a-z]+ que (?:el |la |lo |los |las )?nuestr[oa]s?\b/g,
|
|
43427
|
+
/\b(?:melhor|pior)(?:es)? que (?:o |a |os |as )?noss[oa]s?\b/g,
|
|
43428
|
+
/\bmais [a-z]+ que (?:o |a |os |as )?noss[oa]s?\b/g,
|
|
43429
|
+
/\bbetter than (?:ours|our own)\b/g,
|
|
43430
|
+
/\b(?:ellos|ellas) lo hacen mejor\b/g,
|
|
43431
|
+
/\beles fazem melhor\b/g,
|
|
43432
|
+
/\bthey do it better\b/g,
|
|
43433
|
+
/\bnos (?:ganan|superan|adelantan) en\b/g,
|
|
43434
|
+
/\bnos (?:ganham|superam) em\b/g,
|
|
43435
|
+
// Denies the client leads.
|
|
43436
|
+
/\b(?:hoy |actualmente )?no somos nosotros\b/g,
|
|
43437
|
+
/\b(?:hoje |atualmente )?nao somos nos\b/g,
|
|
43438
|
+
/\bno somos (?:el|la|los|las) (?:mejor|mas) [a-z]+/g,
|
|
43439
|
+
/\bno somos (?:el|la|los|las) (?:mejores|mejor)\b/g,
|
|
43440
|
+
/\bnao somos (?:o|a|os|as) (?:melhor|melhores|mais) ?[a-z]*/g,
|
|
43441
|
+
/\bwe(?:'re| are) not the (?:best|most|leading|strongest)\b/g,
|
|
43442
|
+
/\b(?:it|that)(?:'s| is) not us\b/g
|
|
43443
|
+
];
|
|
43444
|
+
var PRICE_CONCESSION_CONTEXT = /barat|econom|precio|preco|cheap|price|low ?cost|coste|custo|\bcar[oa]s?\b|\bexpensive\b/;
|
|
43445
|
+
var CONCESSION_NEGATION_CONTEXT = /\b(?:ningun[oa]?s?|nadie|nada|no hay|no existe|nenhum[oa]?s?|ninguem|nao ha|nao existe|nothing|nobody|no one|none|there is no)\b/;
|
|
43464
43446
|
var AUTONOMY_CLAIMS = [
|
|
43465
43447
|
"fully autonomous",
|
|
43466
43448
|
"completely autonomous",
|
|
@@ -43540,6 +43522,16 @@ var RULE_META = {
|
|
|
43540
43522
|
severity: "block",
|
|
43541
43523
|
note: "An <img> with an empty or placeholder src ships a broken image. Source a real asset via `baker images`."
|
|
43542
43524
|
},
|
|
43525
|
+
"competitor-concession": {
|
|
43526
|
+
family: "integrity",
|
|
43527
|
+
severity: "block",
|
|
43528
|
+
note: "This page ranks somebody else above the client, or says the client does not lead. A paid landing answers an objection by changing the axis, never by conceding it. Rewrite the claim around what this product does that the alternative structurally cannot (`references/sections/faq.md`)."
|
|
43529
|
+
},
|
|
43530
|
+
"competitor-named-in-comparison": {
|
|
43531
|
+
family: "integrity",
|
|
43532
|
+
severity: "warn",
|
|
43533
|
+
note: "A competitor from the vault is named in a comparative sentence outside a comparison table. Naming a rival on paid traffic sells their brand and is the client's decision, not yours \u2014 cite the capability, not the company."
|
|
43534
|
+
},
|
|
43543
43535
|
"side-tab": {
|
|
43544
43536
|
family: "borders_depth",
|
|
43545
43537
|
severity: "warn",
|
|
@@ -43897,6 +43889,40 @@ var LINE_MATCHERS = [
|
|
|
43897
43889
|
function includesPhraseAtWordStart(haystack, phrase) {
|
|
43898
43890
|
return new RegExp(`\\b${phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`).test(haystack);
|
|
43899
43891
|
}
|
|
43892
|
+
function foldForCopy(text2) {
|
|
43893
|
+
return text2.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
43894
|
+
}
|
|
43895
|
+
function detectConcessions(text2) {
|
|
43896
|
+
const folded = foldForCopy(text2);
|
|
43897
|
+
const out = [];
|
|
43898
|
+
for (const pattern of CONCESSION_PATTERNS) {
|
|
43899
|
+
for (const m of folded.matchAll(pattern)) {
|
|
43900
|
+
const index = m.index ?? 0;
|
|
43901
|
+
if (PRICE_CONCESSION_CONTEXT.test(sentenceAround(folded, index))) continue;
|
|
43902
|
+
if (CONCESSION_NEGATION_CONTEXT.test(clauseBefore(folded, index))) continue;
|
|
43903
|
+
out.push({
|
|
43904
|
+
index,
|
|
43905
|
+
sample: text2.slice(index, index + 70).replace(/\s+/g, " ").trim()
|
|
43906
|
+
});
|
|
43907
|
+
}
|
|
43908
|
+
}
|
|
43909
|
+
return out;
|
|
43910
|
+
}
|
|
43911
|
+
function sentenceAround(text2, index) {
|
|
43912
|
+
const rest = text2.slice(index).search(/[.!?\n]/);
|
|
43913
|
+
return text2.slice(sentenceStart(text2, index), rest === -1 ? text2.length : index + rest);
|
|
43914
|
+
}
|
|
43915
|
+
function clauseBefore(text2, index) {
|
|
43916
|
+
const prefix = text2.slice(sentenceStart(text2, index), index);
|
|
43917
|
+
const lastBreak = Math.max(prefix.lastIndexOf(","), prefix.lastIndexOf(";"), prefix.lastIndexOf(":"));
|
|
43918
|
+
return prefix.slice(lastBreak + 1);
|
|
43919
|
+
}
|
|
43920
|
+
function sentenceStart(text2, index) {
|
|
43921
|
+
for (let i = index; i > 0; i--) {
|
|
43922
|
+
if (/[.!?\n]/.test(text2[i - 1] ?? "")) return i;
|
|
43923
|
+
}
|
|
43924
|
+
return 0;
|
|
43925
|
+
}
|
|
43900
43926
|
function stripHtmlToText(html) {
|
|
43901
43927
|
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
|
|
43902
43928
|
}
|
|
@@ -43908,17 +43934,76 @@ function stripHtmlPreservingHeadings(html) {
|
|
|
43908
43934
|
`
|
|
43909
43935
|
).replace(/<\/(?:p|div|section|li|tr|br)>|<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n");
|
|
43910
43936
|
}
|
|
43937
|
+
function isProseLiteral(value) {
|
|
43938
|
+
const text2 = value.trim();
|
|
43939
|
+
if (text2.length < 12) return false;
|
|
43940
|
+
if (/^(?:https?:\/\/|\/|\.{1,2}\/|#|@|[a-z]+:)/i.test(text2)) return false;
|
|
43941
|
+
if (!new RegExp("\\p{L}", "u").test(text2)) return false;
|
|
43942
|
+
const words2 = text2.split(/\s+/).filter(Boolean);
|
|
43943
|
+
if (words2.length < 4) return false;
|
|
43944
|
+
const classy = words2.filter((w) => /^-?[a-z][a-z0-9]*(?:[-.:/][a-z0-9.[\]%-]+)+$/.test(w)).length;
|
|
43945
|
+
if (classy >= 3 && classy / words2.length > 0.5) return false;
|
|
43946
|
+
return true;
|
|
43947
|
+
}
|
|
43911
43948
|
function blankAstroFrontmatter(text2) {
|
|
43912
|
-
|
|
43949
|
+
const cut = frontmatterLength(text2);
|
|
43950
|
+
if (cut === 0) return text2;
|
|
43951
|
+
const head = text2.slice(0, cut);
|
|
43952
|
+
const kept = head.split("").map((ch) => ch === "\n" ? "\n" : " ");
|
|
43953
|
+
for (const literal of stringLiterals(head)) {
|
|
43954
|
+
if (!isProseLiteral(literal.value)) continue;
|
|
43955
|
+
for (let i = 0; i < literal.value.length; i++) {
|
|
43956
|
+
const at = literal.start + i;
|
|
43957
|
+
if (head[at] !== "\n") kept[at] = head[at] ?? " ";
|
|
43958
|
+
}
|
|
43959
|
+
}
|
|
43960
|
+
return kept.join("") + text2.slice(cut);
|
|
43961
|
+
}
|
|
43962
|
+
function frontmatterLength(text2) {
|
|
43963
|
+
if (!text2.startsWith("---")) return 0;
|
|
43913
43964
|
const lines = text2.split("\n");
|
|
43914
|
-
if (!/^---\s*$/.test(lines[0] ?? "")) return
|
|
43915
|
-
|
|
43916
|
-
|
|
43917
|
-
|
|
43918
|
-
|
|
43965
|
+
if (!/^---\s*$/.test(lines[0] ?? "")) return 0;
|
|
43966
|
+
const end = lines.findIndex((line, i) => i > 0 && /^---\s*$/.test(line));
|
|
43967
|
+
return end === -1 ? 0 : lines.slice(0, end + 1).join("\n").length;
|
|
43968
|
+
}
|
|
43969
|
+
function stringLiterals(source) {
|
|
43970
|
+
const out = [];
|
|
43971
|
+
for (let i = 0; i < source.length; i++) {
|
|
43972
|
+
const commentEnd = skipComment(source, i);
|
|
43973
|
+
if (commentEnd !== -1) {
|
|
43974
|
+
i = commentEnd;
|
|
43975
|
+
continue;
|
|
43919
43976
|
}
|
|
43977
|
+
const quote = source[i];
|
|
43978
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") continue;
|
|
43979
|
+
const close = closingQuote(source, i + 1, quote);
|
|
43980
|
+
if (close === -1) break;
|
|
43981
|
+
out.push({ start: i + 1, value: source.slice(i + 1, close) });
|
|
43982
|
+
i = close;
|
|
43920
43983
|
}
|
|
43921
|
-
return
|
|
43984
|
+
return out;
|
|
43985
|
+
}
|
|
43986
|
+
function skipComment(source, i) {
|
|
43987
|
+
if (source[i] !== "/") return -1;
|
|
43988
|
+
if (source[i + 1] === "/") {
|
|
43989
|
+
const nl = source.indexOf("\n", i);
|
|
43990
|
+
return nl === -1 ? source.length : nl;
|
|
43991
|
+
}
|
|
43992
|
+
if (source[i + 1] === "*") {
|
|
43993
|
+
const close = source.indexOf("*/", i + 2);
|
|
43994
|
+
return close === -1 ? source.length : close + 1;
|
|
43995
|
+
}
|
|
43996
|
+
return -1;
|
|
43997
|
+
}
|
|
43998
|
+
function closingQuote(source, from, quote) {
|
|
43999
|
+
for (let j = from; j < source.length; j++) {
|
|
44000
|
+
if (source[j] === "\\") {
|
|
44001
|
+
j++;
|
|
44002
|
+
continue;
|
|
44003
|
+
}
|
|
44004
|
+
if (source[j] === quote) return j;
|
|
44005
|
+
}
|
|
44006
|
+
return -1;
|
|
43922
44007
|
}
|
|
43923
44008
|
var ANALYZERS = [
|
|
43924
44009
|
// Overused primary font (font-family declarations + Google Fonts URLs). Fires
|
|
@@ -44109,6 +44194,23 @@ var ANALYZERS = [
|
|
|
44109
44194
|
return out;
|
|
44110
44195
|
}
|
|
44111
44196
|
},
|
|
44197
|
+
// Positioning concessions. File-scope (fires at 1, and the line is what the
|
|
44198
|
+
// agent needs to fix it) and read from the visible copy only.
|
|
44199
|
+
{
|
|
44200
|
+
scope: "file",
|
|
44201
|
+
run: (text2, file) => {
|
|
44202
|
+
const visible = stripHtmlPreservingHeadings(text2);
|
|
44203
|
+
return detectConcessions(visible).map(({ sample: sample2 }) => {
|
|
44204
|
+
const at = text2.indexOf(sample2.slice(0, 24));
|
|
44205
|
+
return {
|
|
44206
|
+
id: "competitor-concession",
|
|
44207
|
+
snippet: `"${sample2}"`,
|
|
44208
|
+
file,
|
|
44209
|
+
...at >= 0 ? { line: lineOf(text2, at) } : {}
|
|
44210
|
+
};
|
|
44211
|
+
});
|
|
44212
|
+
}
|
|
44213
|
+
},
|
|
44112
44214
|
// Gradient text in authored CSS. A rule block spans lines, so this scans a
|
|
44113
44215
|
// ±6-line window around each `background-clip: text` instead of one line
|
|
44114
44216
|
// (the Tailwind class form stays a line matcher above).
|
|
@@ -44202,6 +44304,72 @@ function lineOf(text2, index) {
|
|
|
44202
44304
|
for (let i = 0; i < index && i < text2.length; i++) if (text2[i] === "\n") line++;
|
|
44203
44305
|
return line;
|
|
44204
44306
|
}
|
|
44307
|
+
var COMPARISON_MARKERS = /\b(?:vs|versus|frente a|comparad|compara|mejor|mejores|peor|peores|alternativa|melhor|melhores|pior|piores|compared|better|worse|alternative|than)\b/;
|
|
44308
|
+
function tableRanges(text2) {
|
|
44309
|
+
const out = [];
|
|
44310
|
+
for (const m of text2.matchAll(/<table\b[\s\S]*?<\/table>/gi)) {
|
|
44311
|
+
const start = m.index ?? 0;
|
|
44312
|
+
out.push([start, start + m[0].length]);
|
|
44313
|
+
}
|
|
44314
|
+
return out;
|
|
44315
|
+
}
|
|
44316
|
+
function detectCompetitorMentions(sources, competitors) {
|
|
44317
|
+
const names = distinctCompetitorNames(competitors);
|
|
44318
|
+
if (names.length === 0) return [];
|
|
44319
|
+
const out = [];
|
|
44320
|
+
for (const source of sources) out.push(...mentionsInSource(source, names));
|
|
44321
|
+
return dedupe(out);
|
|
44322
|
+
}
|
|
44323
|
+
function distinctCompetitorNames(competitors) {
|
|
44324
|
+
const byFold = /* @__PURE__ */ new Map();
|
|
44325
|
+
for (const raw of competitors) {
|
|
44326
|
+
if (typeof raw !== "string") continue;
|
|
44327
|
+
const name = raw.trim();
|
|
44328
|
+
if (name.length < 4) continue;
|
|
44329
|
+
const key = foldForCopy(name);
|
|
44330
|
+
if (!byFold.has(key) || new RegExp("\\p{Lu}", "u").test(name)) byFold.set(key, name);
|
|
44331
|
+
}
|
|
44332
|
+
return [...byFold.values()];
|
|
44333
|
+
}
|
|
44334
|
+
function mentionsInSource(source, names) {
|
|
44335
|
+
const text2 = blankAstroFrontmatter(source.text);
|
|
44336
|
+
const visible = blankHtmlKeepingOffsets(text2);
|
|
44337
|
+
const tables = tableRanges(text2);
|
|
44338
|
+
const out = [];
|
|
44339
|
+
for (const name of names) {
|
|
44340
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44341
|
+
for (const m of visible.matchAll(new RegExp(`\\b${escapeRegExp(name)}\\b`, "gi"))) {
|
|
44342
|
+
const at = m.index ?? 0;
|
|
44343
|
+
if (tables.some(([from, to]) => at >= from && at < to)) continue;
|
|
44344
|
+
const start = sentenceStart(visible, at);
|
|
44345
|
+
if (seen.has(start)) continue;
|
|
44346
|
+
const sentence = sentenceAround(visible, at).replace(/\s+/g, " ").trim();
|
|
44347
|
+
if (!COMPARISON_MARKERS.test(foldForCopy(sentence))) continue;
|
|
44348
|
+
seen.add(start);
|
|
44349
|
+
out.push({
|
|
44350
|
+
id: "competitor-named-in-comparison",
|
|
44351
|
+
snippet: `"${sentence.slice(0, 70)}" names ${name}`,
|
|
44352
|
+
file: source.path,
|
|
44353
|
+
line: lineOf(text2, at)
|
|
44354
|
+
});
|
|
44355
|
+
}
|
|
44356
|
+
}
|
|
44357
|
+
return out;
|
|
44358
|
+
}
|
|
44359
|
+
function blankHtmlKeepingOffsets(text2) {
|
|
44360
|
+
const out = text2.split("");
|
|
44361
|
+
const pattern = /<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>|<!--[\s\S]*?-->|<[^>]+>/gi;
|
|
44362
|
+
for (const m of text2.matchAll(pattern)) {
|
|
44363
|
+
const start = m.index ?? 0;
|
|
44364
|
+
for (let i = start; i < start + m[0].length && i < out.length; i++) {
|
|
44365
|
+
if (out[i] !== "\n") out[i] = " ";
|
|
44366
|
+
}
|
|
44367
|
+
}
|
|
44368
|
+
return out.join("");
|
|
44369
|
+
}
|
|
44370
|
+
function escapeRegExp(value) {
|
|
44371
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44372
|
+
}
|
|
44205
44373
|
function detectSource(source) {
|
|
44206
44374
|
const file = source.path;
|
|
44207
44375
|
const text2 = blankAstroFrontmatter(source.text);
|
|
@@ -44261,6 +44429,7 @@ function critiqueLanding(input) {
|
|
|
44261
44429
|
const raws = [];
|
|
44262
44430
|
for (const source of sources) raws.push(...detectSource(source));
|
|
44263
44431
|
raws.push(...detectPage(sources));
|
|
44432
|
+
raws.push(...detectCompetitorMentions(sources, input.competitors ?? []));
|
|
44264
44433
|
for (const raw of raws) {
|
|
44265
44434
|
const meta = RULE_META[raw.id];
|
|
44266
44435
|
if (!meta) continue;
|
|
@@ -44301,7 +44470,7 @@ function describeCounts(findings) {
|
|
|
44301
44470
|
// src/commands/landing/snapshot.ts
|
|
44302
44471
|
import { mkdir as mkdir8, rename as rename2, writeFile as writeFile12 } from "fs/promises";
|
|
44303
44472
|
import path26 from "path";
|
|
44304
|
-
var CRITIC_VERSION = "
|
|
44473
|
+
var CRITIC_VERSION = "3";
|
|
44305
44474
|
function critiqueCacheDir(projectRoot) {
|
|
44306
44475
|
return path26.join(projectRoot, ".cache", "landing-critique");
|
|
44307
44476
|
}
|
|
@@ -44375,7 +44544,7 @@ async function walkAstro(dir) {
|
|
|
44375
44544
|
// src/commands/landing/critique.ts
|
|
44376
44545
|
registerSchema({
|
|
44377
44546
|
command: "landing.critique",
|
|
44378
|
-
description: "Start here: `baker landing critique my-landing` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images\u2026) tiered block/warn/advisory, and records the critique the publish quality gate requires.",
|
|
44547
|
+
description: "Start here: `baker landing critique my-landing` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images\u2026) plus positioning integrity (copy that ranks a competitor above the client, a rival named in a comparison) tiered block/warn/advisory, and records the critique the publish quality gate requires.",
|
|
44379
44548
|
args: {
|
|
44380
44549
|
slug: {
|
|
44381
44550
|
type: "string",
|
|
@@ -44405,7 +44574,7 @@ function fail5(code, message, fix) {
|
|
|
44405
44574
|
var critiqueCommand2 = defineCommand158({
|
|
44406
44575
|
meta: {
|
|
44407
44576
|
name: "critique",
|
|
44408
|
-
description: "Start here: `baker landing critique <slug>` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI design tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md as the allowlist. Also records the critique that publishing requires \u2014 run it before finishing a landing."
|
|
44577
|
+
description: "Start here: `baker landing critique <slug>` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI design tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) and positioning integrity (a concession ranking a competitor above the client, a rival named in a comparison) tiered block/warn/advisory, respecting the client's BRAND.md as the allowlist. Also records the critique that publishing requires \u2014 run it before finishing a landing."
|
|
44409
44578
|
},
|
|
44410
44579
|
args: {
|
|
44411
44580
|
slug: {
|
|
@@ -44433,7 +44602,8 @@ var critiqueCommand2 = defineCommand158({
|
|
|
44433
44602
|
}
|
|
44434
44603
|
}
|
|
44435
44604
|
const brand = await loadBrandTokens(projectRoot);
|
|
44436
|
-
const
|
|
44605
|
+
const competitors = await readCompetitorNames(projectRoot);
|
|
44606
|
+
const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand, competitors)));
|
|
44437
44607
|
const landings = results.map(({ slug, report }) => ({
|
|
44438
44608
|
slug,
|
|
44439
44609
|
overall: report.overall,
|
|
@@ -44464,10 +44634,10 @@ var critiqueCommand2 = defineCommand158({
|
|
|
44464
44634
|
);
|
|
44465
44635
|
}
|
|
44466
44636
|
});
|
|
44467
|
-
async function critiqueOne(projectRoot, slug, brand) {
|
|
44637
|
+
async function critiqueOne(projectRoot, slug, brand, competitors) {
|
|
44468
44638
|
const landingDir = path28.resolve(projectRoot, "src", "pages", slug);
|
|
44469
44639
|
const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
|
|
44470
|
-
const report = critiqueLanding({ slug, sources, brand });
|
|
44640
|
+
const report = critiqueLanding({ slug, sources, brand, competitors });
|
|
44471
44641
|
let snapshotFailed = false;
|
|
44472
44642
|
try {
|
|
44473
44643
|
await writeCritiqueSnapshot(projectRoot, {
|
|
@@ -44483,6 +44653,26 @@ async function critiqueOne(projectRoot, slug, brand) {
|
|
|
44483
44653
|
}
|
|
44484
44654
|
return { slug, report, snapshotFailed };
|
|
44485
44655
|
}
|
|
44656
|
+
async function readCompetitorNames(projectRoot) {
|
|
44657
|
+
const dir = path28.join(projectRoot, "src", "content", "competitors");
|
|
44658
|
+
let entries;
|
|
44659
|
+
try {
|
|
44660
|
+
entries = (await readdir9(dir)).filter((f) => f.endsWith(".md"));
|
|
44661
|
+
} catch {
|
|
44662
|
+
return [];
|
|
44663
|
+
}
|
|
44664
|
+
const names = [];
|
|
44665
|
+
for (const entry of entries) {
|
|
44666
|
+
names.push(path28.basename(entry, ".md").replace(/[-_]+/g, " "));
|
|
44667
|
+
try {
|
|
44668
|
+
const head = (await readFile22(path28.join(dir, entry), "utf8")).slice(0, 2e3);
|
|
44669
|
+
const titled = /^\s*(?:title|name)\s*:\s*["']?([^"'\n]+)["']?\s*$/im.exec(head);
|
|
44670
|
+
if (titled?.[1]) names.push(titled[1].trim());
|
|
44671
|
+
} catch {
|
|
44672
|
+
}
|
|
44673
|
+
}
|
|
44674
|
+
return names;
|
|
44675
|
+
}
|
|
44486
44676
|
async function listLandingSlugs(projectRoot) {
|
|
44487
44677
|
try {
|
|
44488
44678
|
const entries = await readdir9(path28.join(projectRoot, "src", "pages"), { withFileTypes: true });
|
|
@@ -47686,7 +47876,7 @@ Start here: \`baker landing critique <slug>\` after building or editing a landin
|
|
|
47686
47876
|
|
|
47687
47877
|
Subcommands:
|
|
47688
47878
|
baker landing inspiration \u2014 reference library of real landing-page sections; search it during research, BEFORE writing the Direction Contract. Inspiration only: take the mechanism, never the words.
|
|
47689
|
-
baker landing critique <slug> \u2014 deterministic design-quality critic (advisory): flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md. Records the critique the publish quality gate requires \u2014 run it before finishing a landing.`
|
|
47879
|
+
baker landing critique <slug> \u2014 deterministic design-quality critic (advisory): flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) and positioning integrity (copy ranking a competitor above the client) tiered block/warn/advisory, respecting the client's BRAND.md. Records the critique the publish quality gate requires \u2014 run it before finishing a landing.`
|
|
47690
47880
|
},
|
|
47691
47881
|
subCommands: {
|
|
47692
47882
|
inspiration: inspirationCommand,
|
|
@@ -49430,7 +49620,7 @@ var listCommand15 = defineCommand185({
|
|
|
49430
49620
|
});
|
|
49431
49621
|
|
|
49432
49622
|
// src/commands/scheduled-actions/templates.ts
|
|
49433
|
-
import { readFile as
|
|
49623
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
49434
49624
|
import path36 from "path";
|
|
49435
49625
|
import { defineCommand as defineCommand186 } from "citty";
|
|
49436
49626
|
registerSchema({
|
|
@@ -49534,7 +49724,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
|
|
|
49534
49724
|
}
|
|
49535
49725
|
if (save.length > 0) {
|
|
49536
49726
|
const briefFile = flag("brief-file");
|
|
49537
|
-
const brief = briefFile.length > 0 ? await
|
|
49727
|
+
const brief = briefFile.length > 0 ? await readFile23(path36.resolve(briefFile), "utf8") : flag("brief");
|
|
49538
49728
|
if (brief.trim().length === 0) {
|
|
49539
49729
|
failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
|
|
49540
49730
|
}
|
|
@@ -52717,7 +52907,7 @@ var searchCommand4 = defineCommand213({
|
|
|
52717
52907
|
var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
52718
52908
|
|
|
52719
52909
|
// src/commands/videos/upload.ts
|
|
52720
|
-
import { readFile as
|
|
52910
|
+
import { readFile as readFile24, stat as stat8 } from "fs/promises";
|
|
52721
52911
|
import { basename as basename3, extname as extname4 } from "path";
|
|
52722
52912
|
import { defineCommand as defineCommand214 } from "citty";
|
|
52723
52913
|
var MIME_MAP = {
|
|
@@ -52814,7 +53004,7 @@ var uploadCommand2 = defineCommand214({
|
|
|
52814
53004
|
originalFilename,
|
|
52815
53005
|
descriptionContext
|
|
52816
53006
|
});
|
|
52817
|
-
const fileBuffer = await
|
|
53007
|
+
const fileBuffer = await readFile24(filePath);
|
|
52818
53008
|
const uploadResponse = await fetch(uploadUrl, {
|
|
52819
53009
|
method: "PUT",
|
|
52820
53010
|
headers: { "Content-Type": contentType },
|