@domandigital/craft 0.6.0 → 0.7.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/dist/index.d.cts CHANGED
@@ -1048,6 +1048,24 @@ declare function runTell(tell: Tell, files: SourceFile[]): Hit[];
1048
1048
  * markers, and a test fails if the two differ.
1049
1049
  */
1050
1050
  declare function catalogueTable(): string;
1051
+ /**
1052
+ * The copy word and phrase lists, as portable JSON.
1053
+ *
1054
+ * These are the only part of the copy catalogue a non-TypeScript consumer can
1055
+ * use directly: a word list is data, a regex-shaped tell (contrastive
1056
+ * negation, the "No X" badge, em dash, emoji) is not something worth
1057
+ * re-deriving from a JSON export, so those stay TypeScript-only here.
1058
+ *
1059
+ * `claude-kit`'s `copy_check.py` is the reason this exists. It hardcoded its
1060
+ * own copies of these same lists rather than reading this package, and the
1061
+ * two drifted (documented in claude-kit's `house-style/copy-rules.md`, "Note
1062
+ * the enforcement gap"). `word-lists.json` is the file that ends the drift:
1063
+ * `install/sync-copy-wordlists.sh` on that side copies it in verbatim, same
1064
+ * shape as `sync-craft-standard.sh` does for `STANDARD.md`. Regenerate the
1065
+ * checked-in `word-lists.json` with `pnpm --filter @domandigital/craft run
1066
+ * docs` after changing any list in `tells/copy.ts`.
1067
+ */
1068
+ declare function wordListsJson(): string;
1051
1069
 
1052
1070
  /** Plain-text rendering of a report, for a terminal or a hook. */
1053
1071
 
@@ -1418,4 +1436,271 @@ declare function propose(input: ProposeInput): {
1418
1436
  direction: ArtDirection;
1419
1437
  };
1420
1438
 
1421
- export { AI_PHRASES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type ArtDirection, BUZZWORDS, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type ContrastCheck, type ContrastReport, type CopyTell, type CraftTokensInput, type CraftTokensResult, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, EXIT_RATIO, type EaseName, type EmitCssOptions, FINGERPRINT_VERSION, type Finding, type Fingerprint, type FingerprintDistance, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE_BUDGET, HOUSE_TYPE, type Hit, LIGHTNESS_CURVE, type MotionDecision, type MotionKind, NEGATIVE_REASSURANCE, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROPOSED, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_PHRASES, type SectionKind, type SectionRhythmOptions, type SemanticInput, type SemanticResult, type Severity, type ShouldAnimateInput, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TYPE_STEPS, type TailwindPreset, type Tell, type TellException, type TypeStep, VAGUE_WORDS, type ValidateContext, accentFork, apcaContrast, auditSnapshot, catalogueTable, checkCopy, checkPair, checkRestraint, craftTokens, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, exitDuration, findColours, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatHex, formatReport, hexToOklch, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, makeSnapshot, motionTokens, nearest, normaliseFamily, oklchToHex, oklchToRgb, paletteFromPixels, parseColour, parseHex, propose, ramp, rampFromAnchors, runTell, scanSource, sectionRhythm, semantic, shouldAnimate, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, typeFeatureTokens, validateDirection, wcagContrast };
1439
+ /**
1440
+ * The counterfactual: what a model builds for this brief when nobody tells it
1441
+ * anything else. Signal 2 of CHARACTER.md.
1442
+ *
1443
+ * A null model is about twenty pages generated from one brief, each reduced to
1444
+ * a fingerprint. A site is typical when it sits as close to those pages as they
1445
+ * sit to each other. That question keeps working when model defaults move,
1446
+ * which a fixed list of tells cannot: ban Inter and the null model moves with
1447
+ * the model, so the comparison moves too.
1448
+ *
1449
+ * `harvest` reads the same pages the other way round: which choices recur
1450
+ * across them that the catalogue does not know yet. Those are the candidates
1451
+ * for the next generation of tells. A person decides; nothing is added here.
1452
+ *
1453
+ * Pure. Generating pages and snapshotting them lives in the CLI.
1454
+ */
1455
+
1456
+ declare const NULL_VERSION = 1;
1457
+ /**
1458
+ * The prompt a null run gets: the brief, and nothing about how it should look.
1459
+ * Changing this changes every null model built after it, so it is versioned
1460
+ * by being recorded in each one.
1461
+ */
1462
+ declare function nullPrompt(brief: string): string;
1463
+ interface NullRun {
1464
+ /** The page's file name without extension: "01". */
1465
+ id: string;
1466
+ fingerprint: Fingerprint;
1467
+ /** Catalogue tells found on the page, source and rendered, each once. */
1468
+ tells: string[];
1469
+ /** Every visible string on the page, for the phrase harvest. */
1470
+ copy: string[];
1471
+ }
1472
+ interface NullModel {
1473
+ version: typeof NULL_VERSION;
1474
+ brief: string;
1475
+ prompt: string;
1476
+ /** What was asked for with --model, or "default". */
1477
+ model: string;
1478
+ catalogueVersion: string;
1479
+ builtAt: string;
1480
+ runs: NullRun[];
1481
+ }
1482
+ /** The HTML document in a model's reply, without code fences or commentary. */
1483
+ declare function extractHtml(reply: string): string | null;
1484
+ /** Fewer runs than this and the spread of the null is noise, so nothing is judged. */
1485
+ declare const MIN_RUNS = 5;
1486
+ /** Typical when at least this share of the null's own pages sit further out. */
1487
+ declare const TYPICAL_AT = 0.1;
1488
+ interface Typicality {
1489
+ /**
1490
+ * 0 to 1: the share of the null's own pages that sit further from the rest
1491
+ * than this page sits from them. 1 is more typical than anything the model
1492
+ * built; 0 is further out than every one of them.
1493
+ */
1494
+ score: number;
1495
+ typical: boolean;
1496
+ /** Mean distance to the k nearest null pages. */
1497
+ distance: number;
1498
+ /** The same measure for each null page against the others, median. */
1499
+ baseline: number;
1500
+ nearest: {
1501
+ id: string;
1502
+ distance: number;
1503
+ }[];
1504
+ /** The choices this page shares with most of the null: why it reads as typical. */
1505
+ shared: {
1506
+ part: string;
1507
+ value: string;
1508
+ runs: number;
1509
+ of: number;
1510
+ }[];
1511
+ }
1512
+ /**
1513
+ * How typical a page is against one or more null models (pooled). Every null
1514
+ * page is scored against the others the same way, leaving itself out, so the
1515
+ * threshold comes from the model's own spread and not from a number picked
1516
+ * here: a page drawn from the null is flagged nine times in ten by
1517
+ * construction, and the calibration checks whether real sites are not.
1518
+ */
1519
+ declare function typicality(target: Fingerprint, runs: NullRun[], k?: number): Typicality;
1520
+ declare function hueName(o: Oklch | null): string;
1521
+ declare function groundName(o: Oklch): string;
1522
+ declare function shapeName(roundness: number | null): string;
1523
+ /** The discrete choices a fingerprint makes, as part → value. What a person would name. */
1524
+ declare function choicesOf(fp: Fingerprint): Record<string, string[]>;
1525
+ type CandidateKind = "face" | "accent" | "ground" | "shape" | "motion" | "opening" | "tell" | "phrase";
1526
+ interface HarvestCandidate {
1527
+ kind: CandidateKind;
1528
+ value: string;
1529
+ /** Null pages that make this choice, across every model given. */
1530
+ runs: number;
1531
+ of: number;
1532
+ /** Distinct briefs it turned up for. A habit shows across briefs; a brief's own words do not. */
1533
+ briefs: number;
1534
+ /** The catalogue tell that already catches it, or null when nothing does. */
1535
+ known: string | null;
1536
+ /** An example, for a phrase: the string it came from. */
1537
+ example?: string;
1538
+ }
1539
+ interface HarvestOptions {
1540
+ /** Report a choice made by at least this share of pages. Default 0.25. */
1541
+ minShare?: number;
1542
+ /** Which copy tell, if any, already catches a phrase. The CLI passes the catalogue's. */
1543
+ copyTell?: (phrase: string) => string | null;
1544
+ }
1545
+ /**
1546
+ * Choices that recur across null pages, with the tell that already catches
1547
+ * each, if any. The ones with `known: null` are the next generation's
1548
+ * candidates. Nothing here decides: a person reads them, and a candidate only
1549
+ * becomes a tell with a flag case and a pass case like every other.
1550
+ */
1551
+ declare function harvest(models: Pick<NullModel, "brief" | "runs">[], options?: HarvestOptions): HarvestCandidate[];
1552
+ /** A representative colour for a hue family, for a report. */
1553
+ declare function hueSwatch(runs: NullRun[], hue: string): string | null;
1554
+
1555
+ /**
1556
+ * The estate register: every site the agency has shipped, fingerprinted, so a
1557
+ * new one can be checked against its siblings. Signal 3 of CHARACTER.md.
1558
+ *
1559
+ * An agency's biggest tell is its clients looking like each other. No single
1560
+ * site shows it; only the set does. So the register holds one fingerprint per
1561
+ * site, and `compareToEstate` says which sites a page sits nearest and what
1562
+ * it shares with them.
1563
+ *
1564
+ * The line for "siblings" is not a number picked here. Two different
1565
+ * businesses are siblings when they sit closer together than two pages Claude
1566
+ * builds for the same brief usually do: if a barber and an electrician look
1567
+ * more alike than two drafts of one barber, nobody chose either look. The
1568
+ * median comes from null models when they are given, and from the 2026-09-23
1569
+ * calibration when they are not.
1570
+ *
1571
+ * Pure. The CLI reads and writes the register file.
1572
+ */
1573
+
1574
+ declare const ESTATE_VERSION = 1;
1575
+ /**
1576
+ * Median fingerprint distance between two null pages built from the same
1577
+ * brief, across seven briefs and 1,330 pairs (calibration/null, 2026-09-23).
1578
+ */
1579
+ declare const SIBLING_AT = 0.31;
1580
+ interface EstateSite {
1581
+ /** Short kebab-case id: "rmp", "chair-and-blade". */
1582
+ id: string;
1583
+ client: string;
1584
+ url: string;
1585
+ /** Where the site's source lives, if known: "rmp474/RMP-Electrical". */
1586
+ repo?: string;
1587
+ fingerprint: Fingerprint;
1588
+ /** Rendered tells found when it was added, for the record. */
1589
+ tells: string[];
1590
+ addedAt: string;
1591
+ }
1592
+ interface EstateRegister {
1593
+ version: typeof ESTATE_VERSION;
1594
+ sites: EstateSite[];
1595
+ }
1596
+ declare function emptyEstate(): EstateRegister;
1597
+ /** Add a site, or replace the one with the same id. Sorted by id so the file diffs cleanly. */
1598
+ declare function addSite(register: EstateRegister, site: EstateSite): EstateRegister;
1599
+ /**
1600
+ * What two fingerprints effectively share, named the way a person would see
1601
+ * it: "Fraunces headline", "no accent", "pill buttons". A part counts when its
1602
+ * distance is 0.2 or less.
1603
+ */
1604
+ declare function sharedParts(a: Fingerprint, b: Fingerprint, d?: FingerprintDistance): string[];
1605
+ interface EstateMatch {
1606
+ id: string;
1607
+ distance: number;
1608
+ sibling: boolean;
1609
+ shared: string[];
1610
+ }
1611
+ /** The sites nearest a fingerprint, closest first, leaving out `exclude` (the site itself). */
1612
+ declare function compareToEstate(target: Fingerprint, register: EstateRegister, options?: {
1613
+ exclude?: string;
1614
+ siblingAt?: number;
1615
+ }): EstateMatch[];
1616
+ interface EstatePair {
1617
+ a: string;
1618
+ b: string;
1619
+ distance: number;
1620
+ sibling: boolean;
1621
+ shared: string[];
1622
+ }
1623
+ /** Every pair in the register, closest first. */
1624
+ declare function estatePairs(register: EstateRegister, siblingAt?: number): EstatePair[];
1625
+ /**
1626
+ * The sibling line from null models: the median distance between two pages
1627
+ * built from the same brief. Each inner array is one brief's fingerprints.
1628
+ */
1629
+ declare function siblingLine(briefs: Fingerprint[][]): number;
1630
+
1631
+ /**
1632
+ * The character report: the three signals and the reason rule, read together.
1633
+ *
1634
+ * No one signal decides. A site can carry no catalogued tell and still be what
1635
+ * Claude builds for its brief (signal 2); it can be nothing like the null and
1636
+ * still wear last year's reflexes (signal 1); it can pass both and be a twin of
1637
+ * the agency's other client (signal 3). And a site that passes all three has
1638
+ * still only avoided defaults: "decided" needs the reasons written down.
1639
+ *
1640
+ * So the verdict is:
1641
+ * default two or more signals raised
1642
+ * mixed one raised
1643
+ * decided none raised, and all measured, reasons included
1644
+ * unproven none raised, but something was not measured. Unverified is
1645
+ * never a pass.
1646
+ *
1647
+ * The actions change the surface: type, colour, shape, effects, motion, copy.
1648
+ * Never the page grammar. A layout tell says what to change about how a
1649
+ * section looks, not that it should move.
1650
+ *
1651
+ * Pure. The CLI gathers the inputs.
1652
+ */
1653
+
1654
+ /** Three or more distinct design tells: the rule fixed for the 2026-09-23 calibration. */
1655
+ declare const TELL_HEAVY = 3;
1656
+ /** Fewer choices decided than this and the reasons are not yet a direction. */
1657
+ declare const MIN_DECIDED = 5;
1658
+ type Verdict = "default" | "mixed" | "decided" | "unproven";
1659
+ type Area = "direction" | "type" | "colour" | "shape" | "effects" | "motion" | "layout" | "copy";
1660
+ interface SignalResult {
1661
+ id: "tells" | "typicality" | "estate" | "reasons";
1662
+ /** null when it was not measured. */
1663
+ raised: boolean | null;
1664
+ detail: string;
1665
+ }
1666
+ interface Action {
1667
+ area: Area;
1668
+ what: string;
1669
+ why: string;
1670
+ /** 1 first. Deciding comes before changing, so direction actions are always 1. */
1671
+ priority: 1 | 2 | 3;
1672
+ /** The art-direction choice that settles it, when there is one. */
1673
+ choice?: ChoiceKey;
1674
+ }
1675
+ interface CharacterReport {
1676
+ site: string;
1677
+ verdict: Verdict;
1678
+ summary: string;
1679
+ signals: SignalResult[];
1680
+ actions: Action[];
1681
+ }
1682
+ interface CharacterInputs {
1683
+ site: string;
1684
+ /** Source and rendered findings, merged. */
1685
+ findings?: CheckReport | null;
1686
+ typicality?: Typicality | null;
1687
+ estate?: EstateMatch[] | null;
1688
+ direction?: DirectionReport | null;
1689
+ }
1690
+ declare function characterReport(input: CharacterInputs, copyTells?: Set<string>): CharacterReport;
1691
+
1692
+ /**
1693
+ * `craft retrofit`: a character report turned into the checklist a retrofit
1694
+ * session works through, in the order that works: decide, then change.
1695
+ *
1696
+ * Every change is tied to the art-direction choice that settles it, and shows
1697
+ * the decided value and its reason when there is one. A change with no
1698
+ * decided value waits for the decision; otherwise the retrofit swaps one
1699
+ * default for the next.
1700
+ *
1701
+ * Pure. Returns Markdown.
1702
+ */
1703
+
1704
+ declare function retrofitPlan(report: CharacterReport, direction?: ArtDirection | null): string;
1705
+
1706
+ export { AI_PHRASES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type Area, type ArtDirection, BUZZWORDS, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type ContrastCheck, type ContrastReport, type CopyTell, type CraftTokensInput, type CraftTokensResult, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, ESTATE_VERSION, EXIT_RATIO, type EaseName, type EmitCssOptions, type EstateMatch, type EstatePair, type EstateRegister, type EstateSite, FINGERPRINT_VERSION, type Finding, type Fingerprint, type FingerprintDistance, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE_BUDGET, HOUSE_TYPE, type HarvestCandidate, type HarvestOptions, type Hit, LIGHTNESS_CURVE, MIN_DECIDED, MIN_RUNS, type MotionDecision, type MotionKind, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROPOSED, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_PHRASES, type SectionKind, type SectionRhythmOptions, type SemanticInput, type SemanticResult, type Severity, type ShouldAnimateInput, type SignalResult, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TELL_HEAVY, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type Tell, type TellException, type TypeStep, type Typicality, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, auditSnapshot, catalogueTable, characterReport, checkCopy, checkPair, checkRestraint, choicesOf, compareToEstate, craftTokens, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, emptyEstate, estatePairs, exitDuration, extractHtml, findColours, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatHex, formatReport, groundName, harvest, hexToOklch, hueName, hueSwatch, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, makeSnapshot, motionTokens, nearest, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, paletteFromPixels, parseColour, parseHex, propose, ramp, rampFromAnchors, retrofitPlan, runTell, scanSource, sectionRhythm, semantic, shapeName, sharedParts, shouldAnimate, siblingLine, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, typeFeatureTokens, typicality, validateDirection, wcagContrast, wordListsJson };
package/dist/index.d.ts CHANGED
@@ -1048,6 +1048,24 @@ declare function runTell(tell: Tell, files: SourceFile[]): Hit[];
1048
1048
  * markers, and a test fails if the two differ.
1049
1049
  */
1050
1050
  declare function catalogueTable(): string;
1051
+ /**
1052
+ * The copy word and phrase lists, as portable JSON.
1053
+ *
1054
+ * These are the only part of the copy catalogue a non-TypeScript consumer can
1055
+ * use directly: a word list is data, a regex-shaped tell (contrastive
1056
+ * negation, the "No X" badge, em dash, emoji) is not something worth
1057
+ * re-deriving from a JSON export, so those stay TypeScript-only here.
1058
+ *
1059
+ * `claude-kit`'s `copy_check.py` is the reason this exists. It hardcoded its
1060
+ * own copies of these same lists rather than reading this package, and the
1061
+ * two drifted (documented in claude-kit's `house-style/copy-rules.md`, "Note
1062
+ * the enforcement gap"). `word-lists.json` is the file that ends the drift:
1063
+ * `install/sync-copy-wordlists.sh` on that side copies it in verbatim, same
1064
+ * shape as `sync-craft-standard.sh` does for `STANDARD.md`. Regenerate the
1065
+ * checked-in `word-lists.json` with `pnpm --filter @domandigital/craft run
1066
+ * docs` after changing any list in `tells/copy.ts`.
1067
+ */
1068
+ declare function wordListsJson(): string;
1051
1069
 
1052
1070
  /** Plain-text rendering of a report, for a terminal or a hook. */
1053
1071
 
@@ -1418,4 +1436,271 @@ declare function propose(input: ProposeInput): {
1418
1436
  direction: ArtDirection;
1419
1437
  };
1420
1438
 
1421
- export { AI_PHRASES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type ArtDirection, BUZZWORDS, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type ContrastCheck, type ContrastReport, type CopyTell, type CraftTokensInput, type CraftTokensResult, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, EXIT_RATIO, type EaseName, type EmitCssOptions, FINGERPRINT_VERSION, type Finding, type Fingerprint, type FingerprintDistance, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE_BUDGET, HOUSE_TYPE, type Hit, LIGHTNESS_CURVE, type MotionDecision, type MotionKind, NEGATIVE_REASSURANCE, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROPOSED, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_PHRASES, type SectionKind, type SectionRhythmOptions, type SemanticInput, type SemanticResult, type Severity, type ShouldAnimateInput, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TYPE_STEPS, type TailwindPreset, type Tell, type TellException, type TypeStep, VAGUE_WORDS, type ValidateContext, accentFork, apcaContrast, auditSnapshot, catalogueTable, checkCopy, checkPair, checkRestraint, craftTokens, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, exitDuration, findColours, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatHex, formatReport, hexToOklch, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, makeSnapshot, motionTokens, nearest, normaliseFamily, oklchToHex, oklchToRgb, paletteFromPixels, parseColour, parseHex, propose, ramp, rampFromAnchors, runTell, scanSource, sectionRhythm, semantic, shouldAnimate, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, typeFeatureTokens, validateDirection, wcagContrast };
1439
+ /**
1440
+ * The counterfactual: what a model builds for this brief when nobody tells it
1441
+ * anything else. Signal 2 of CHARACTER.md.
1442
+ *
1443
+ * A null model is about twenty pages generated from one brief, each reduced to
1444
+ * a fingerprint. A site is typical when it sits as close to those pages as they
1445
+ * sit to each other. That question keeps working when model defaults move,
1446
+ * which a fixed list of tells cannot: ban Inter and the null model moves with
1447
+ * the model, so the comparison moves too.
1448
+ *
1449
+ * `harvest` reads the same pages the other way round: which choices recur
1450
+ * across them that the catalogue does not know yet. Those are the candidates
1451
+ * for the next generation of tells. A person decides; nothing is added here.
1452
+ *
1453
+ * Pure. Generating pages and snapshotting them lives in the CLI.
1454
+ */
1455
+
1456
+ declare const NULL_VERSION = 1;
1457
+ /**
1458
+ * The prompt a null run gets: the brief, and nothing about how it should look.
1459
+ * Changing this changes every null model built after it, so it is versioned
1460
+ * by being recorded in each one.
1461
+ */
1462
+ declare function nullPrompt(brief: string): string;
1463
+ interface NullRun {
1464
+ /** The page's file name without extension: "01". */
1465
+ id: string;
1466
+ fingerprint: Fingerprint;
1467
+ /** Catalogue tells found on the page, source and rendered, each once. */
1468
+ tells: string[];
1469
+ /** Every visible string on the page, for the phrase harvest. */
1470
+ copy: string[];
1471
+ }
1472
+ interface NullModel {
1473
+ version: typeof NULL_VERSION;
1474
+ brief: string;
1475
+ prompt: string;
1476
+ /** What was asked for with --model, or "default". */
1477
+ model: string;
1478
+ catalogueVersion: string;
1479
+ builtAt: string;
1480
+ runs: NullRun[];
1481
+ }
1482
+ /** The HTML document in a model's reply, without code fences or commentary. */
1483
+ declare function extractHtml(reply: string): string | null;
1484
+ /** Fewer runs than this and the spread of the null is noise, so nothing is judged. */
1485
+ declare const MIN_RUNS = 5;
1486
+ /** Typical when at least this share of the null's own pages sit further out. */
1487
+ declare const TYPICAL_AT = 0.1;
1488
+ interface Typicality {
1489
+ /**
1490
+ * 0 to 1: the share of the null's own pages that sit further from the rest
1491
+ * than this page sits from them. 1 is more typical than anything the model
1492
+ * built; 0 is further out than every one of them.
1493
+ */
1494
+ score: number;
1495
+ typical: boolean;
1496
+ /** Mean distance to the k nearest null pages. */
1497
+ distance: number;
1498
+ /** The same measure for each null page against the others, median. */
1499
+ baseline: number;
1500
+ nearest: {
1501
+ id: string;
1502
+ distance: number;
1503
+ }[];
1504
+ /** The choices this page shares with most of the null: why it reads as typical. */
1505
+ shared: {
1506
+ part: string;
1507
+ value: string;
1508
+ runs: number;
1509
+ of: number;
1510
+ }[];
1511
+ }
1512
+ /**
1513
+ * How typical a page is against one or more null models (pooled). Every null
1514
+ * page is scored against the others the same way, leaving itself out, so the
1515
+ * threshold comes from the model's own spread and not from a number picked
1516
+ * here: a page drawn from the null is flagged nine times in ten by
1517
+ * construction, and the calibration checks whether real sites are not.
1518
+ */
1519
+ declare function typicality(target: Fingerprint, runs: NullRun[], k?: number): Typicality;
1520
+ declare function hueName(o: Oklch | null): string;
1521
+ declare function groundName(o: Oklch): string;
1522
+ declare function shapeName(roundness: number | null): string;
1523
+ /** The discrete choices a fingerprint makes, as part → value. What a person would name. */
1524
+ declare function choicesOf(fp: Fingerprint): Record<string, string[]>;
1525
+ type CandidateKind = "face" | "accent" | "ground" | "shape" | "motion" | "opening" | "tell" | "phrase";
1526
+ interface HarvestCandidate {
1527
+ kind: CandidateKind;
1528
+ value: string;
1529
+ /** Null pages that make this choice, across every model given. */
1530
+ runs: number;
1531
+ of: number;
1532
+ /** Distinct briefs it turned up for. A habit shows across briefs; a brief's own words do not. */
1533
+ briefs: number;
1534
+ /** The catalogue tell that already catches it, or null when nothing does. */
1535
+ known: string | null;
1536
+ /** An example, for a phrase: the string it came from. */
1537
+ example?: string;
1538
+ }
1539
+ interface HarvestOptions {
1540
+ /** Report a choice made by at least this share of pages. Default 0.25. */
1541
+ minShare?: number;
1542
+ /** Which copy tell, if any, already catches a phrase. The CLI passes the catalogue's. */
1543
+ copyTell?: (phrase: string) => string | null;
1544
+ }
1545
+ /**
1546
+ * Choices that recur across null pages, with the tell that already catches
1547
+ * each, if any. The ones with `known: null` are the next generation's
1548
+ * candidates. Nothing here decides: a person reads them, and a candidate only
1549
+ * becomes a tell with a flag case and a pass case like every other.
1550
+ */
1551
+ declare function harvest(models: Pick<NullModel, "brief" | "runs">[], options?: HarvestOptions): HarvestCandidate[];
1552
+ /** A representative colour for a hue family, for a report. */
1553
+ declare function hueSwatch(runs: NullRun[], hue: string): string | null;
1554
+
1555
+ /**
1556
+ * The estate register: every site the agency has shipped, fingerprinted, so a
1557
+ * new one can be checked against its siblings. Signal 3 of CHARACTER.md.
1558
+ *
1559
+ * An agency's biggest tell is its clients looking like each other. No single
1560
+ * site shows it; only the set does. So the register holds one fingerprint per
1561
+ * site, and `compareToEstate` says which sites a page sits nearest and what
1562
+ * it shares with them.
1563
+ *
1564
+ * The line for "siblings" is not a number picked here. Two different
1565
+ * businesses are siblings when they sit closer together than two pages Claude
1566
+ * builds for the same brief usually do: if a barber and an electrician look
1567
+ * more alike than two drafts of one barber, nobody chose either look. The
1568
+ * median comes from null models when they are given, and from the 2026-09-23
1569
+ * calibration when they are not.
1570
+ *
1571
+ * Pure. The CLI reads and writes the register file.
1572
+ */
1573
+
1574
+ declare const ESTATE_VERSION = 1;
1575
+ /**
1576
+ * Median fingerprint distance between two null pages built from the same
1577
+ * brief, across seven briefs and 1,330 pairs (calibration/null, 2026-09-23).
1578
+ */
1579
+ declare const SIBLING_AT = 0.31;
1580
+ interface EstateSite {
1581
+ /** Short kebab-case id: "rmp", "chair-and-blade". */
1582
+ id: string;
1583
+ client: string;
1584
+ url: string;
1585
+ /** Where the site's source lives, if known: "rmp474/RMP-Electrical". */
1586
+ repo?: string;
1587
+ fingerprint: Fingerprint;
1588
+ /** Rendered tells found when it was added, for the record. */
1589
+ tells: string[];
1590
+ addedAt: string;
1591
+ }
1592
+ interface EstateRegister {
1593
+ version: typeof ESTATE_VERSION;
1594
+ sites: EstateSite[];
1595
+ }
1596
+ declare function emptyEstate(): EstateRegister;
1597
+ /** Add a site, or replace the one with the same id. Sorted by id so the file diffs cleanly. */
1598
+ declare function addSite(register: EstateRegister, site: EstateSite): EstateRegister;
1599
+ /**
1600
+ * What two fingerprints effectively share, named the way a person would see
1601
+ * it: "Fraunces headline", "no accent", "pill buttons". A part counts when its
1602
+ * distance is 0.2 or less.
1603
+ */
1604
+ declare function sharedParts(a: Fingerprint, b: Fingerprint, d?: FingerprintDistance): string[];
1605
+ interface EstateMatch {
1606
+ id: string;
1607
+ distance: number;
1608
+ sibling: boolean;
1609
+ shared: string[];
1610
+ }
1611
+ /** The sites nearest a fingerprint, closest first, leaving out `exclude` (the site itself). */
1612
+ declare function compareToEstate(target: Fingerprint, register: EstateRegister, options?: {
1613
+ exclude?: string;
1614
+ siblingAt?: number;
1615
+ }): EstateMatch[];
1616
+ interface EstatePair {
1617
+ a: string;
1618
+ b: string;
1619
+ distance: number;
1620
+ sibling: boolean;
1621
+ shared: string[];
1622
+ }
1623
+ /** Every pair in the register, closest first. */
1624
+ declare function estatePairs(register: EstateRegister, siblingAt?: number): EstatePair[];
1625
+ /**
1626
+ * The sibling line from null models: the median distance between two pages
1627
+ * built from the same brief. Each inner array is one brief's fingerprints.
1628
+ */
1629
+ declare function siblingLine(briefs: Fingerprint[][]): number;
1630
+
1631
+ /**
1632
+ * The character report: the three signals and the reason rule, read together.
1633
+ *
1634
+ * No one signal decides. A site can carry no catalogued tell and still be what
1635
+ * Claude builds for its brief (signal 2); it can be nothing like the null and
1636
+ * still wear last year's reflexes (signal 1); it can pass both and be a twin of
1637
+ * the agency's other client (signal 3). And a site that passes all three has
1638
+ * still only avoided defaults: "decided" needs the reasons written down.
1639
+ *
1640
+ * So the verdict is:
1641
+ * default two or more signals raised
1642
+ * mixed one raised
1643
+ * decided none raised, and all measured, reasons included
1644
+ * unproven none raised, but something was not measured. Unverified is
1645
+ * never a pass.
1646
+ *
1647
+ * The actions change the surface: type, colour, shape, effects, motion, copy.
1648
+ * Never the page grammar. A layout tell says what to change about how a
1649
+ * section looks, not that it should move.
1650
+ *
1651
+ * Pure. The CLI gathers the inputs.
1652
+ */
1653
+
1654
+ /** Three or more distinct design tells: the rule fixed for the 2026-09-23 calibration. */
1655
+ declare const TELL_HEAVY = 3;
1656
+ /** Fewer choices decided than this and the reasons are not yet a direction. */
1657
+ declare const MIN_DECIDED = 5;
1658
+ type Verdict = "default" | "mixed" | "decided" | "unproven";
1659
+ type Area = "direction" | "type" | "colour" | "shape" | "effects" | "motion" | "layout" | "copy";
1660
+ interface SignalResult {
1661
+ id: "tells" | "typicality" | "estate" | "reasons";
1662
+ /** null when it was not measured. */
1663
+ raised: boolean | null;
1664
+ detail: string;
1665
+ }
1666
+ interface Action {
1667
+ area: Area;
1668
+ what: string;
1669
+ why: string;
1670
+ /** 1 first. Deciding comes before changing, so direction actions are always 1. */
1671
+ priority: 1 | 2 | 3;
1672
+ /** The art-direction choice that settles it, when there is one. */
1673
+ choice?: ChoiceKey;
1674
+ }
1675
+ interface CharacterReport {
1676
+ site: string;
1677
+ verdict: Verdict;
1678
+ summary: string;
1679
+ signals: SignalResult[];
1680
+ actions: Action[];
1681
+ }
1682
+ interface CharacterInputs {
1683
+ site: string;
1684
+ /** Source and rendered findings, merged. */
1685
+ findings?: CheckReport | null;
1686
+ typicality?: Typicality | null;
1687
+ estate?: EstateMatch[] | null;
1688
+ direction?: DirectionReport | null;
1689
+ }
1690
+ declare function characterReport(input: CharacterInputs, copyTells?: Set<string>): CharacterReport;
1691
+
1692
+ /**
1693
+ * `craft retrofit`: a character report turned into the checklist a retrofit
1694
+ * session works through, in the order that works: decide, then change.
1695
+ *
1696
+ * Every change is tied to the art-direction choice that settles it, and shows
1697
+ * the decided value and its reason when there is one. A change with no
1698
+ * decided value waits for the decision; otherwise the retrofit swaps one
1699
+ * default for the next.
1700
+ *
1701
+ * Pure. Returns Markdown.
1702
+ */
1703
+
1704
+ declare function retrofitPlan(report: CharacterReport, direction?: ArtDirection | null): string;
1705
+
1706
+ export { AI_PHRASES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type Area, type ArtDirection, BUZZWORDS, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type ContrastCheck, type ContrastReport, type CopyTell, type CraftTokensInput, type CraftTokensResult, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, ESTATE_VERSION, EXIT_RATIO, type EaseName, type EmitCssOptions, type EstateMatch, type EstatePair, type EstateRegister, type EstateSite, FINGERPRINT_VERSION, type Finding, type Fingerprint, type FingerprintDistance, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE_BUDGET, HOUSE_TYPE, type HarvestCandidate, type HarvestOptions, type Hit, LIGHTNESS_CURVE, MIN_DECIDED, MIN_RUNS, type MotionDecision, type MotionKind, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROPOSED, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_PHRASES, type SectionKind, type SectionRhythmOptions, type SemanticInput, type SemanticResult, type Severity, type ShouldAnimateInput, type SignalResult, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TELL_HEAVY, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type Tell, type TellException, type TypeStep, type Typicality, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, auditSnapshot, catalogueTable, characterReport, checkCopy, checkPair, checkRestraint, choicesOf, compareToEstate, craftTokens, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, emptyEstate, estatePairs, exitDuration, extractHtml, findColours, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatHex, formatReport, groundName, harvest, hexToOklch, hueName, hueSwatch, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, makeSnapshot, motionTokens, nearest, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, paletteFromPixels, parseColour, parseHex, propose, ramp, rampFromAnchors, retrofitPlan, runTell, scanSource, sectionRhythm, semantic, shapeName, sharedParts, shouldAnimate, siblingLine, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, typeFeatureTokens, typicality, validateDirection, wcagContrast, wordListsJson };