@domandigital/craft 0.7.0 → 0.8.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.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  parseFlags
3
- } from "./chunk-YYC4EGER.js";
3
+ } from "./chunk-X65RDW65.js";
4
4
  import {
5
5
  fingerprint,
6
6
  fingerprintDistance
7
- } from "./chunk-Q54JSNC5.js";
7
+ } from "./chunk-OGSYVCMU.js";
8
8
  import {
9
9
  CATALOGUE_VERSION,
10
10
  REFLEX_FONTS_1,
@@ -18,7 +18,7 @@ import {
18
18
  isCream,
19
19
  oklchToRgb,
20
20
  scanSource
21
- } from "./chunk-3AP4PJMX.js";
21
+ } from "./chunk-CZHRNPDD.js";
22
22
 
23
23
  // src/null/cli.ts
24
24
  import { spawn } from "child_process";
@@ -352,7 +352,7 @@ async function build(flags, io) {
352
352
  const toShoot = ids.filter((id) => existsSync(join(pages, `${id}.html`)) && !existsSync(join(shots, `${id}.json`)));
353
353
  if (toShoot.length) {
354
354
  io.out(`craft null: snapshotting ${toShoot.length} page${toShoot.length === 1 ? "" : "s"}`);
355
- const { snapshotUrls } = await import("./audit-YNODYB3U.js");
355
+ const { snapshotUrls } = await import("./audit-W57YDZ2K.js");
356
356
  const results = await snapshotUrls(toShoot.map((id) => pathToFileURL(join(pages, `${id}.html`)).href));
357
357
  results.forEach((r, i) => {
358
358
  const id = toShoot[i];
@@ -3,7 +3,7 @@ import {
3
3
  fontClass,
4
4
  normaliseFamily,
5
5
  parseColour
6
- } from "./chunk-3AP4PJMX.js";
6
+ } from "./chunk-CZHRNPDD.js";
7
7
 
8
8
  // src/fingerprint/index.ts
9
9
  var FINGERPRINT_VERSION = 1;
@@ -3,18 +3,18 @@ import {
3
3
  hueName,
4
4
  loadNull,
5
5
  shapeName
6
- } from "./chunk-GV3QOTQU.js";
6
+ } from "./chunk-K253ZXB3.js";
7
7
  import {
8
8
  parseFlags
9
- } from "./chunk-YYC4EGER.js";
9
+ } from "./chunk-X65RDW65.js";
10
10
  import {
11
11
  fingerprint,
12
12
  fingerprintDistance
13
- } from "./chunk-Q54JSNC5.js";
13
+ } from "./chunk-OGSYVCMU.js";
14
14
  import {
15
15
  SNAPSHOT_VERSION,
16
16
  auditSnapshot
17
- } from "./chunk-3AP4PJMX.js";
17
+ } from "./chunk-CZHRNPDD.js";
18
18
 
19
19
  // src/estate/cli.ts
20
20
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -92,7 +92,7 @@ async function snapshotOf(target, cwd) {
92
92
  return snap;
93
93
  }
94
94
  if (!/^https?:\/\//.test(target)) throw new Error(`${target} is neither a URL, a snapshot file, nor a site in the register`);
95
- const { snapshotUrl } = await import("./audit-YNODYB3U.js");
95
+ const { snapshotUrl } = await import("./audit-W57YDZ2K.js");
96
96
  return snapshotUrl(target);
97
97
  }
98
98
  var line = (distance, sibling, shared) => `${distance.toFixed(2)}${sibling ? " SIBLING" : ""}${shared.length ? ` shares ${shared.join(", ")}` : ""}`;
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  fingerprint
3
- } from "./chunk-Q54JSNC5.js";
3
+ } from "./chunk-OGSYVCMU.js";
4
4
  import {
5
5
  SNAPSHOT_VERSION,
6
6
  auditSnapshot
7
- } from "./chunk-3AP4PJMX.js";
7
+ } from "./chunk-CZHRNPDD.js";
8
8
 
9
9
  // src/audit/collect.ts
10
10
  function collectInPage() {
@@ -6,7 +6,7 @@ import {
6
6
  isCream,
7
7
  normaliseFamily,
8
8
  parseColour
9
- } from "./chunk-3AP4PJMX.js";
9
+ } from "./chunk-CZHRNPDD.js";
10
10
 
11
11
  // src/direction/types.ts
12
12
  var DIRECTION_VERSION = 1;
@@ -1,16 +1,101 @@
1
1
  import {
2
2
  CATALOGUE,
3
3
  CATALOGUE_VERSION,
4
+ applyHouseGate,
4
5
  checkCopy,
6
+ extractCopy,
7
+ extractStrings,
5
8
  fileKind,
9
+ houseRule,
6
10
  scanSource
7
- } from "./chunk-3AP4PJMX.js";
11
+ } from "./chunk-CZHRNPDD.js";
8
12
 
9
13
  // src/character/cli.ts
10
14
  import { execFileSync } from "child_process";
11
15
  import { existsSync, readFileSync, readdirSync, statSync } from "fs";
12
16
  import { join, relative, resolve } from "path";
13
17
 
18
+ // src/character/facts.ts
19
+ var MONTH = "(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)";
20
+ var PATTERNS = [
21
+ ["url", /\bhttps?:\/\/[^\s<>"'`)\]]+|\bwww\.[^\s<>"'`)\]]+/gi],
22
+ ["email", /\b[\w.+-]+@[\w-]+(?:\.[\w-]+)+\b/g],
23
+ ["phone", /(?:\+44\s?\(?0?\)?\s?|\b0)\d(?:[\s-]?\d){8,9}\b/g],
24
+ ["postcode", /\b[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}\b/g],
25
+ ["money", /[£$€]\s?\d[\d,]*(?:\.\d{1,2})?(?:\s?(?:k|m|bn)\b)?|\b\d[\d,]*(?:\.\d{1,2})?\s?(?:pounds|GBP)\b/gi],
26
+ ["date", new RegExp(`\\b\\d{1,2}(?:st|nd|rd|th)?\\s+${MONTH}\\b(?:\\s+\\d{4})?|\\b${MONTH}\\s+\\d{1,2}(?:st|nd|rd|th)?\\b(?:,?\\s+\\d{4})?|\\b${MONTH}\\s+\\d{4}\\b|\\b\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}\\b|\\b(?:19|20)\\d{2}\\b`, "g")],
27
+ ["time", /\b\d{1,2}(?:[:.]\d{2})?\s?(?:am|pm)\b|\b\d{1,2}:\d{2}\b/gi],
28
+ ["number", /\b\d[\d,]*(?:\.\d+)?%?|\b\d+(?:\.\d+)?\s?(?:per ?cent)\b/gi]
29
+ ];
30
+ var NAME = /[A-Z][a-zA-Z’'&-]*[a-zA-Z](?:\s+(?:of|and|&|the)?\s*[A-Z][a-zA-Z’'&-]*[a-zA-Z])*/g;
31
+ var normalise = (kind, value) => {
32
+ const v = value.trim();
33
+ if (kind === "phone") return v.replace(/[^\d+]/g, "").replace(/^\+44\(?0?\)?/, "0").replace(/^\+44/, "0");
34
+ if (kind === "money" || kind === "number") return v.replace(/[\s,]/g, "").toLowerCase();
35
+ if (kind === "postcode") return v.replace(/\s/g, "").toUpperCase();
36
+ if (kind === "url") return v.replace(/[.,;:]+$/, "").toLowerCase();
37
+ return v.replace(/\s+/g, " ").toLowerCase();
38
+ };
39
+ function visibleText(file) {
40
+ const prose = /\.(md|markdown|mdx|txt)$/i.test(file.path);
41
+ return (prose ? extractCopy(file) : extractStrings(file)).map((b) => b.text).join("\n");
42
+ }
43
+ function protectedFacts(text) {
44
+ const facts = [];
45
+ let rest = text;
46
+ const mask = (start, length) => {
47
+ rest = rest.slice(0, start) + " ".repeat(length) + rest.slice(start + length);
48
+ };
49
+ for (const [kind, pattern] of PATTERNS) {
50
+ for (const m of [...rest.matchAll(pattern)]) {
51
+ const value = m[0].replace(/[.,;:]+$/, "");
52
+ facts.push({ kind, value, key: `${kind}:${normalise(kind, value)}` });
53
+ mask(m.index ?? 0, m[0].length);
54
+ }
55
+ }
56
+ const lines = rest.split("\n");
57
+ for (const line of lines) {
58
+ if (/^\s{0,3}#{1,6}\s/.test(line)) continue;
59
+ for (const m of line.matchAll(NAME)) {
60
+ const at = m.index ?? 0;
61
+ const before = line.slice(0, at).trimEnd();
62
+ const opensSentence = before === "" || /[.!?:"“(>*-]$/.test(before) || /^\s*(?:[-*+]|\d+\.)$/.test(before);
63
+ const word = m[0];
64
+ const acronym = /^[A-Z]{2,}$/.test(word);
65
+ if (opensSentence && !acronym && !/\s/.test(word)) continue;
66
+ if (word === "I") continue;
67
+ facts.push({ kind: "name", value: word, key: `name:${normalise("name", word)}` });
68
+ }
69
+ }
70
+ return facts;
71
+ }
72
+ var unique = (facts) => [...new Map(facts.map((f) => [f.key, f])).values()];
73
+ function compareFacts(before, after) {
74
+ const a = unique(protectedFacts(before));
75
+ const b = unique(protectedFacts(after));
76
+ const inA = new Set(a.map((f) => f.key));
77
+ const inB = new Set(b.map((f) => f.key));
78
+ return {
79
+ before: a.length,
80
+ after: b.length,
81
+ lost: a.filter((f) => !inB.has(f.key)),
82
+ added: b.filter((f) => !inA.has(f.key))
83
+ };
84
+ }
85
+ function formatComparison(result, beforePath, afterPath) {
86
+ const lines = [`craft copy compare: ${beforePath} -> ${afterPath}`, ` ${result.before} protected facts before, ${result.after} after`];
87
+ if (result.lost.length) {
88
+ lines.push("", ` Lost (${result.lost.length}): restore each, or say why it went`);
89
+ for (const f of result.lost) lines.push(` ${f.kind.padEnd(9)} ${f.value}`);
90
+ }
91
+ if (result.added.length) {
92
+ lines.push("", ` Added (${result.added.length}): each needs a source, or it is invented`);
93
+ for (const f of result.added) lines.push(` ${f.kind.padEnd(9)} ${f.value}`);
94
+ }
95
+ if (!result.lost.length && !result.added.length) lines.push("", " Every fact kept, nothing added.");
96
+ return lines.join("\n");
97
+ }
98
+
14
99
  // src/character/format.ts
15
100
  function formatReport(report, title) {
16
101
  const lines = [];
@@ -54,7 +139,8 @@ var HELP = `craft: find the AI look and say what to do instead.
54
139
 
55
140
  Usage
56
141
  craft scan [paths...] [--staged] [--json] [--strict] [--direction <file>]
57
- craft copy <paths...> [--json] [--strict] [--direction <file>]
142
+ craft copy <paths...> [--gate] [--json] [--strict] [--direction <file>]
143
+ craft copy compare <before> <after> [--json]
58
144
  craft tells list [--json]
59
145
  craft tells harvest <null.json | dir>... [--share 0.25] [--json]
60
146
  craft snapshot <url> [--out <file>] [--width <px>] [--height <px>]
@@ -71,6 +157,11 @@ Usage
71
157
  scan Markup, component code and stylesheets (source and compiled CSS).
72
158
  --staged reads the git index, for a pre-commit hook.
73
159
  copy The prose in Markdown, markup and content files.
160
+ --gate applies the house copy policy: the blocking tier of COPY.md
161
+ fails the run. What copy-check and the pre-commit hook run.
162
+ compare lists the protected facts (prices, numbers, dates, times,
163
+ phones, emails, links, postcodes, names) a rewrite lost or added.
164
+ Exit 1 if any: restore it, source it, or say why.
74
165
  tells The catalogue this build judges against. harvest reads null models
75
166
  for choices the model keeps making that the catalogue does not know.
76
167
  snapshot Render a page in a browser and save what it looks like, as JSON.
@@ -114,12 +205,13 @@ var VALUE_FLAGS = {
114
205
  "--id": "id"
115
206
  };
116
207
  function parseFlags(args) {
117
- const flags = { positional: [], json: false, strict: false, staged: false };
208
+ const flags = { positional: [], json: false, strict: false, staged: false, gate: false };
118
209
  for (let i = 0; i < args.length; i += 1) {
119
210
  const a = args[i];
120
211
  if (a === "--json") flags.json = true;
121
212
  else if (a === "--strict") flags.strict = true;
122
213
  else if (a === "--staged") flags.staged = true;
214
+ else if (a === "--gate") flags.gate = true;
123
215
  else if (a in VALUE_FLAGS) {
124
216
  const value = args[i + 1];
125
217
  i += 1;
@@ -182,25 +274,25 @@ var NOT_COPY_DIR = (name) => name.startsWith("_");
182
274
  function run(argv, io) {
183
275
  const [command, ...rest] = argv;
184
276
  if (command === "direction") {
185
- return import("./cli-6Y5E32FS.js").then((m) => m.runDirection(rest, io));
277
+ return import("./cli-KA3JDNDH.js").then((m) => m.runDirection(rest, io));
186
278
  }
187
279
  if (command === "report" || command === "retrofit") {
188
- return import("./cli-TM2HWYN7.js").then((m) => m.runReport(command, rest, io));
280
+ return import("./cli-LZ7R6KJM.js").then((m) => m.runReport(command, rest, io));
189
281
  }
190
282
  if (command === "estate") {
191
- return import("./cli-IQT6QZ6U.js").then((m) => m.runEstate(rest, io));
283
+ return import("./cli-SHLNRHK6.js").then((m) => m.runEstate(rest, io));
192
284
  }
193
285
  if (command === "null") {
194
- return import("./cli-MW3JAGX3.js").then((m) => m.runNull(rest, io));
286
+ return import("./cli-F7ONSKZS.js").then((m) => m.runNull(rest, io));
195
287
  }
196
288
  if (command === "tells" && rest[0] === "harvest") {
197
- return import("./cli-MW3JAGX3.js").then((m) => m.runHarvest(rest, io)).catch((error) => {
289
+ return import("./cli-F7ONSKZS.js").then((m) => m.runHarvest(rest, io)).catch((error) => {
198
290
  io.err(`craft: ${error.message}`);
199
291
  return 2;
200
292
  });
201
293
  }
202
294
  if (command === "snapshot" || command === "audit") {
203
- return import("./cli-PD43TWX7.js").then((m) => m.runAudit(command, rest, io));
295
+ return import("./cli-FKJOMOOF.js").then((m) => m.runAudit(command, rest, io));
204
296
  }
205
297
  if (!command || command === "help" || command === "--help" || command === "-h") {
206
298
  io.out(HELP);
@@ -212,7 +304,16 @@ function run(argv, io) {
212
304
  if (typeof flags === "string") throw new Error(flags);
213
305
  if (flags.positional[0] !== "list") throw new Error("usage: craft tells list [--json] | craft tells harvest <null.json>...");
214
306
  if (flags.json) {
215
- const data = CATALOGUE.map(({ id, name, generation, surface, severity, why, fix }) => ({ id, name, generation, surface, severity, why, fix }));
307
+ const data = CATALOGUE.map(({ id, name, generation, surface, severity, why, fix }) => ({
308
+ id,
309
+ name,
310
+ generation,
311
+ surface,
312
+ severity,
313
+ why,
314
+ fix,
315
+ ...surface === "copy" ? { house: houseRule(id, name).tier, houseLabel: houseRule(id, name).label } : {}
316
+ }));
216
317
  io.out(JSON.stringify({ version: CATALOGUE_VERSION, tells: data }, null, 2));
217
318
  } else {
218
319
  io.out(`Catalogue ${CATALOGUE_VERSION}`);
@@ -220,6 +321,19 @@ function run(argv, io) {
220
321
  }
221
322
  return 0;
222
323
  }
324
+ if (command === "copy" && rest[0] === "compare") {
325
+ const flags = parseFlags(rest.slice(1));
326
+ if (typeof flags === "string") throw new Error(flags);
327
+ if (flags.positional.length !== 2) throw new Error("usage: craft copy compare <before> <after> [--json]");
328
+ const [before, after] = flags.positional.map((p) => {
329
+ const abs = resolve(io.cwd, p);
330
+ if (!existsSync(abs)) throw new Error(`no such path: ${p}`);
331
+ return visibleText({ path: p, text: readFileSync(abs, "utf8") });
332
+ });
333
+ const result = compareFacts(before, after);
334
+ io.out(flags.json ? JSON.stringify(result, null, 2) : formatComparison(result, flags.positional[0], flags.positional[1]));
335
+ return result.lost.length || result.added.length ? 1 : 0;
336
+ }
223
337
  if (command === "scan" || command === "copy") {
224
338
  const flags = parseFlags(rest);
225
339
  if (typeof flags === "string") throw new Error(flags);
@@ -227,7 +341,8 @@ function run(argv, io) {
227
341
  if (command === "copy" && flags.positional.length === 0 && !flags.staged) throw new Error("usage: craft copy <paths...>");
228
342
  const files = flags.staged ? readStaged(io.cwd, wanted) : readPaths(flags.positional.length > 0 ? flags.positional : ["."], io.cwd, wanted, command === "copy" ? NOT_COPY_DIR : void 0);
229
343
  const exceptions = loadExceptions(flags, io.cwd);
230
- const report = command === "scan" ? scanSource(files, { exceptions }) : checkCopy(files, { exceptions });
344
+ let report = command === "scan" ? scanSource(files, { exceptions }) : checkCopy(files, { exceptions });
345
+ if (command === "copy" && flags.gate) report = applyHouseGate(report);
231
346
  return finish(report, flags, command === "scan" ? "craft scan" : "craft copy", io);
232
347
  }
233
348
  throw new Error(`unknown command "${command}". Run craft --help.`);
@@ -6,10 +6,10 @@ import {
6
6
  nullPath,
7
7
  runHarvest,
8
8
  runNull
9
- } from "./chunk-GV3QOTQU.js";
10
- import "./chunk-YYC4EGER.js";
11
- import "./chunk-Q54JSNC5.js";
12
- import "./chunk-3AP4PJMX.js";
9
+ } from "./chunk-K253ZXB3.js";
10
+ import "./chunk-X65RDW65.js";
11
+ import "./chunk-OGSYVCMU.js";
12
+ import "./chunk-CZHRNPDD.js";
13
13
  export {
14
14
  describeTypicality,
15
15
  formatHarvest,
@@ -0,0 +1,15 @@
1
+ import {
2
+ describeFingerprint,
3
+ merge,
4
+ runAudit
5
+ } from "./chunk-35OX6C4H.js";
6
+ import "./chunk-Q4MN3OXF.js";
7
+ import "./chunk-K253ZXB3.js";
8
+ import "./chunk-X65RDW65.js";
9
+ import "./chunk-OGSYVCMU.js";
10
+ import "./chunk-CZHRNPDD.js";
11
+ export {
12
+ describeFingerprint,
13
+ merge,
14
+ runAudit
15
+ };
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  DIRECTION_VERSION,
3
3
  validateDirection
4
- } from "./chunk-J2BOAIHJ.js";
4
+ } from "./chunk-ULYKASLA.js";
5
5
  import {
6
6
  parseFlags
7
- } from "./chunk-YYC4EGER.js";
7
+ } from "./chunk-X65RDW65.js";
8
8
  import {
9
9
  fingerprint
10
- } from "./chunk-Q54JSNC5.js";
10
+ } from "./chunk-OGSYVCMU.js";
11
11
  import {
12
12
  deltaEOk,
13
13
  formatHex,
@@ -16,7 +16,7 @@ import {
16
16
  isCream,
17
17
  oklchToRgb,
18
18
  parseColour
19
- } from "./chunk-3AP4PJMX.js";
19
+ } from "./chunk-CZHRNPDD.js";
20
20
 
21
21
  // src/direction/cli.ts
22
22
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  validateDirection
3
- } from "./chunk-J2BOAIHJ.js";
3
+ } from "./chunk-ULYKASLA.js";
4
4
  import {
5
5
  merge
6
- } from "./chunk-IQTZWNEK.js";
7
- import "./chunk-OA7SGPN4.js";
6
+ } from "./chunk-35OX6C4H.js";
7
+ import "./chunk-Q4MN3OXF.js";
8
8
  import {
9
9
  SIBLING_AT,
10
10
  compareToEstate,
11
11
  loadEstate
12
- } from "./chunk-CU5TZHOV.js";
12
+ } from "./chunk-OVOH7KZY.js";
13
13
  import {
14
14
  loadNull,
15
15
  typicality
16
- } from "./chunk-GV3QOTQU.js";
16
+ } from "./chunk-K253ZXB3.js";
17
17
  import {
18
18
  COPY_FILE,
19
19
  NOT_COPY_DIR,
@@ -21,17 +21,17 @@ import {
21
21
  loadExceptions,
22
22
  parseFlags,
23
23
  readPaths
24
- } from "./chunk-YYC4EGER.js";
24
+ } from "./chunk-X65RDW65.js";
25
25
  import {
26
26
  fingerprint
27
- } from "./chunk-Q54JSNC5.js";
27
+ } from "./chunk-OGSYVCMU.js";
28
28
  import {
29
29
  CATALOGUE,
30
30
  SNAPSHOT_VERSION,
31
31
  auditSnapshot,
32
32
  checkCopy,
33
33
  scanSource
34
- } from "./chunk-3AP4PJMX.js";
34
+ } from "./chunk-CZHRNPDD.js";
35
35
 
36
36
  // src/report/cli.ts
37
37
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -259,7 +259,7 @@ async function runReport(command, args, io) {
259
259
  snap = JSON.parse(readFileSync(local, "utf8"));
260
260
  if (snap.version !== SNAPSHOT_VERSION) throw new Error(`${target2} is not a snapshot`);
261
261
  } else {
262
- const { snapshotUrl } = await import("./audit-YNODYB3U.js");
262
+ const { snapshotUrl } = await import("./audit-W57YDZ2K.js");
263
263
  snap = await snapshotUrl(target2);
264
264
  }
265
265
  const fp = fingerprint(snap);
@@ -0,0 +1,16 @@
1
+ import {
2
+ formatMatches,
3
+ formatPairs,
4
+ loadEstate,
5
+ runEstate
6
+ } from "./chunk-OVOH7KZY.js";
7
+ import "./chunk-K253ZXB3.js";
8
+ import "./chunk-X65RDW65.js";
9
+ import "./chunk-OGSYVCMU.js";
10
+ import "./chunk-CZHRNPDD.js";
11
+ export {
12
+ formatMatches,
13
+ formatPairs,
14
+ loadEstate,
15
+ runEstate
16
+ };
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-YYC4EGER.js";
5
- import "./chunk-3AP4PJMX.js";
4
+ } from "./chunk-X65RDW65.js";
5
+ import "./chunk-CZHRNPDD.js";
6
6
 
7
7
  // src/cli.ts
8
8
  process.stdout.on("error", (error) => {