@domandigital/craft 0.13.0 → 0.15.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/CHARACTER.md +272 -39
  3. package/COPY.md +5 -0
  4. package/README.md +19 -1
  5. package/art-direction.schema.json +428 -25
  6. package/dist/{audit-5TYDYRXV.js → audit-MTUC24XU.js} +3 -2
  7. package/dist/audit.cjs +674 -92
  8. package/dist/audit.d.cts +15 -1
  9. package/dist/audit.d.ts +15 -1
  10. package/dist/audit.js +674 -92
  11. package/dist/{chunk-IMB4V34S.js → chunk-2IQ5OXFR.js} +7 -7
  12. package/dist/{chunk-GMVXADJ6.js → chunk-LFNSB6W7.js} +711 -92
  13. package/dist/{chunk-3VTPI7BU.js → chunk-MUVFECFC.js} +74 -96
  14. package/dist/chunk-OQXTQQAU.js +27 -0
  15. package/dist/{chunk-ZLDA2SYB.js → chunk-OUQ25LY2.js} +47 -6
  16. package/dist/{chunk-K2BZN4GA.js → chunk-QU6Q2QZZ.js} +49 -14
  17. package/dist/chunk-QYBAVU3P.js +6 -0
  18. package/dist/{chunk-ESCVTKPN.js → chunk-RXXRXV3V.js} +9 -26
  19. package/dist/{chunk-CZDOMIVS.js → chunk-WUACK2HX.js} +2 -1
  20. package/dist/chunk-YDURAOYR.js +711 -0
  21. package/dist/cli-22TSWAX6.js +21 -0
  22. package/dist/cli-5ACKICBO.js +22 -0
  23. package/dist/{cli-DRKAMFAH.js → cli-7OZCYOQY.js} +89 -22
  24. package/dist/{cli-J5ECRCD4.js → cli-FCKHYAVZ.js} +40 -16
  25. package/dist/{cli-ILH7OO5G.js → cli-FLG3ZCAF.js} +6 -4
  26. package/dist/cli.js +3 -2
  27. package/dist/generate.js +6 -0
  28. package/dist/index.cjs +1618 -328
  29. package/dist/index.d.cts +619 -58
  30. package/dist/index.d.ts +619 -58
  31. package/dist/index.js +1555 -328
  32. package/dist/migrate-VIFAYIBM.js +9 -0
  33. package/package.json +1 -1
  34. package/word-lists.json +1 -1
  35. package/dist/chunk-ZZ5CKOJP.js +0 -295
  36. package/dist/cli-BKJMLD3H.js +0 -18
  37. package/dist/cli-N2IDXH7W.js +0 -20
@@ -0,0 +1,21 @@
1
+ import {
2
+ formatComponentPairs,
3
+ formatMatches,
4
+ formatPairs,
5
+ loadEstate,
6
+ runEstate
7
+ } from "./chunk-QU6Q2QZZ.js";
8
+ import "./chunk-YDURAOYR.js";
9
+ import "./chunk-RXXRXV3V.js";
10
+ import "./chunk-OQXTQQAU.js";
11
+ import "./chunk-MUVFECFC.js";
12
+ import "./chunk-WUACK2HX.js";
13
+ import "./chunk-LFNSB6W7.js";
14
+ import "./chunk-QYBAVU3P.js";
15
+ export {
16
+ formatComponentPairs,
17
+ formatMatches,
18
+ formatPairs,
19
+ loadEstate,
20
+ runEstate
21
+ };
@@ -0,0 +1,22 @@
1
+ import {
2
+ describeComponents,
3
+ describeFingerprint,
4
+ merge,
5
+ resolvePages,
6
+ runAudit
7
+ } from "./chunk-2IQ5OXFR.js";
8
+ import "./chunk-OUQ25LY2.js";
9
+ import "./chunk-JYNXGCYZ.js";
10
+ import "./chunk-RXXRXV3V.js";
11
+ import "./chunk-OQXTQQAU.js";
12
+ import "./chunk-MUVFECFC.js";
13
+ import "./chunk-WUACK2HX.js";
14
+ import "./chunk-LFNSB6W7.js";
15
+ import "./chunk-QYBAVU3P.js";
16
+ export {
17
+ describeComponents,
18
+ describeFingerprint,
19
+ merge,
20
+ resolvePages,
21
+ runAudit
22
+ };
@@ -1,17 +1,20 @@
1
- import {
2
- DIRECTION_VERSION,
3
- validateDirection
4
- } from "./chunk-ZZ5CKOJP.js";
5
1
  import {
6
2
  decodePng
7
3
  } from "./chunk-JYNXGCYZ.js";
8
4
  import {
9
- readSnapshot
10
- } from "./chunk-CZDOMIVS.js";
5
+ DIRECTION_VERSION,
6
+ PAGE_TYPES,
7
+ PROPOSED,
8
+ researchPrompt,
9
+ validateDirection
10
+ } from "./chunk-YDURAOYR.js";
11
11
  import {
12
12
  parseFlags,
13
13
  toJson
14
- } from "./chunk-3VTPI7BU.js";
14
+ } from "./chunk-MUVFECFC.js";
15
+ import {
16
+ readSnapshot
17
+ } from "./chunk-WUACK2HX.js";
15
18
  import {
16
19
  deltaEOk,
17
20
  fingerprint,
@@ -21,7 +24,8 @@ import {
21
24
  isCream,
22
25
  oklchToRgb,
23
26
  parseColour
24
- } from "./chunk-GMVXADJ6.js";
27
+ } from "./chunk-LFNSB6W7.js";
28
+ import "./chunk-QYBAVU3P.js";
25
29
 
26
30
  // src/direction/cli.ts
27
31
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
@@ -43,11 +47,31 @@ function initDirection(input) {
43
47
  body: empty(fp.body.family),
44
48
  ...fp.roundness !== null ? { shape: empty(fp.roundness >= 0.45 ? "pill buttons" : fp.roundness <= 0.05 ? "square corners" : `corners at ${Math.round(fp.roundness * 100)}% of button height`) } : {}
45
49
  } : {};
50
+ const part = () => ({ value: "", evidence: [] });
46
51
  return {
47
52
  $schema: "https://unpkg.com/@domandigital/craft/art-direction.schema.json",
48
53
  version: DIRECTION_VERSION,
49
54
  client: input.client ?? "",
50
55
  brief: input.brief ?? "",
56
+ // Empty on purpose, like the reasons: the first validate lists the job and the structure as undecided.
57
+ job: {
58
+ statement: { verb: "", object: "", context: "" },
59
+ functional: part(),
60
+ emotional: part(),
61
+ social: part(),
62
+ consideration: { value: "low", because: "", evidence: [] },
63
+ objections: [],
64
+ language: [],
65
+ evidence: []
66
+ },
67
+ hierarchy: {
68
+ home: {
69
+ primaryAction: { value: "", positions: [], because: "" },
70
+ order: fp?.sections?.length ? fp.sections.map((s) => ({ role: s.role, because: "" })) : [],
71
+ lead: { value: "", because: "" },
72
+ journey: []
73
+ }
74
+ },
51
75
  sources: [],
52
76
  choices,
53
77
  ...input.exceptions?.length ? { exceptions: input.exceptions } : {}
@@ -55,7 +79,6 @@ function initDirection(input) {
55
79
  }
56
80
 
57
81
  // src/direction/propose.ts
58
- var PROPOSED = "PROPOSED:";
59
82
  function srgbToLinear(c) {
60
83
  return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
61
84
  }
@@ -117,7 +140,13 @@ function accentCandidates(input) {
117
140
  const parsed = parseColour(c);
118
141
  if (!parsed || !chromatic(parsed.oklch)) continue;
119
142
  const o = parsed.oklch;
120
- const siblings = (input.estate ?? []).map((e) => ({ id: e.id, d: e.fingerprint.accent ? deltaEOk(o, e.fingerprint.accent) : 1 }));
143
+ const siblings = [
144
+ ...(input.estate ?? []).map((e) => ({ id: e.id, d: e.fingerprint.accent ? deltaEOk(o, e.fingerprint.accent) : 1 })),
145
+ ...(input.declared ?? []).flatMap((e) => {
146
+ const p = parseColour(e.accent);
147
+ return p ? [{ id: `${e.id} (declared)`, d: deltaEOk(o, p.oklch) }] : [];
148
+ })
149
+ ];
121
150
  const nearest = siblings.sort((a, b) => a.d - b.d)[0];
122
151
  const penalty = isAiViolet(o) ? 1 : 0;
123
152
  out.push({
@@ -173,15 +202,26 @@ function propose(input) {
173
202
 
174
203
  // src/direction/cli.ts
175
204
  var readJson = (path) => JSON.parse(readFileSync(path, "utf8"));
176
- function fingerprintFrom(path, cwd) {
177
- return path ? fingerprint(readJson(resolve(cwd, path))) : void 0;
205
+ async function snapshotFrom(target, cwd) {
206
+ if (!target) return void 0;
207
+ if (/^https?:\/\//.test(target)) {
208
+ const { snapshotUrl } = await import("./audit-MTUC24XU.js");
209
+ return snapshotUrl(target);
210
+ }
211
+ return readSnapshot(readJson(resolve(cwd, target)), target);
178
212
  }
213
+ function declaredFrom(path, cwd) {
214
+ if (!path || !path.endsWith(".json")) return [];
215
+ const register = readJson(resolve(cwd, path));
216
+ return register.sites ?? [];
217
+ }
218
+ var TEXT_SOURCE = /\.(?:txt|md|markdown|csv|json|html?)$/i;
179
219
  function estateFrom(dir, cwd) {
180
220
  if (!dir) return [];
181
221
  const root = resolve(cwd, dir);
182
222
  if (root.endsWith(".json")) {
183
223
  const register = readJson(root);
184
- return (register.sites ?? []).map((s) => ({ id: s.id, fingerprint: s.fingerprint }));
224
+ return (register.sites ?? []).filter((s) => s.fingerprint).map((s) => ({ id: s.id, fingerprint: s.fingerprint }));
185
225
  }
186
226
  return readdirSync(root).filter((f) => f.endsWith(".json")).flatMap((f) => {
187
227
  try {
@@ -192,23 +232,33 @@ function estateFrom(dir, cwd) {
192
232
  }
193
233
  });
194
234
  }
195
- function runDirection(args, io) {
235
+ async function runDirection(args, io) {
196
236
  try {
197
237
  const flags = parseFlags(args);
198
238
  if (typeof flags === "string") throw new Error(flags);
199
239
  const [sub] = flags.positional;
200
240
  const file = resolve(io.cwd, flags.direction ?? "art-direction.json");
201
- const current = fingerprintFrom(flags.snapshot, io.cwd);
241
+ if (sub === "research") {
242
+ const base = existsSync(file) ? readJson(file) : {};
243
+ const brief = flags.brief ?? base.brief;
244
+ if (!brief || brief.trim().split(/\s+/).length < 5) throw new Error('usage: craft direction research --brief "<who, where, what they do>" [--visual], or run it next to an art-direction.json with a brief');
245
+ io.out(researchPrompt({ brief, client: flags.client ?? base.client, visual: flags.visual }));
246
+ return 0;
247
+ }
248
+ const page = flags.page ?? "home";
249
+ if (!PAGE_TYPES.includes(page)) throw new Error(`--page takes one of ${PAGE_TYPES.join(", ")}`);
250
+ const snapshot = await snapshotFrom(flags.snapshot, io.cwd);
251
+ const current = snapshot ? fingerprint(snapshot) : void 0;
202
252
  if (sub === "init") {
203
253
  const out = resolve(io.cwd, flags.out ?? "art-direction.json");
204
254
  if (existsSync(out) && !flags.out) {
205
255
  const existing = readJson(out);
206
256
  if (existing.version !== void 0) throw new Error(`${out} already records choices. Pass --out to write elsewhere.`);
207
- const next = initDirection({ client: flags.client, current, exceptions: existing.exceptions });
257
+ const next = initDirection({ client: flags.client, brief: flags.brief, current, exceptions: existing.exceptions });
208
258
  writeFileSync(out, `${JSON.stringify(next, null, 2)}
209
259
  `);
210
260
  } else {
211
- writeFileSync(out, `${JSON.stringify(initDirection({ client: flags.client, current }), null, 2)}
261
+ writeFileSync(out, `${JSON.stringify(initDirection({ client: flags.client, brief: flags.brief, current }), null, 2)}
212
262
  `);
213
263
  }
214
264
  io.out(`craft direction: wrote ${out}. Add the sources and a reason for every choice, then run craft direction validate.`);
@@ -216,14 +266,30 @@ function runDirection(args, io) {
216
266
  }
217
267
  if (sub === "validate") {
218
268
  if (!existsSync(file)) throw new Error(`no ${file}. Run craft direction init.`);
219
- const report = validateDirection(readJson(file), {
269
+ const direction = readJson(file);
270
+ const estate = declaredFrom(flags.estate, io.cwd);
271
+ const report = validateDirection(direction, {
220
272
  fingerprint: current,
221
- pathExists: (p) => existsSync(resolve(dirname(file), p))
273
+ snapshot,
274
+ page,
275
+ strict: flags.strict,
276
+ pathExists: (p) => existsSync(resolve(dirname(file), p)),
277
+ readSource: (s) => {
278
+ if (!s.path || /^https?:/.test(s.path) || !TEXT_SOURCE.test(s.path)) return void 0;
279
+ const path = resolve(dirname(file), s.path);
280
+ return existsSync(path) ? readFileSync(path, "utf8") : void 0;
281
+ },
282
+ estate,
283
+ estateId: estate.find((s) => s.client && s.client === direction.client)?.id
222
284
  });
223
285
  if (flags.json) io.out(toJson(report));
224
286
  else {
225
287
  for (const p of report.problems) io.out(` ${p.severity === "error" ? "error" : "warn "} ${p.at || "(file)"} ${p.message}`);
226
- io.out(`craft direction: ${report.valid ? "valid" : "not valid"}, ${report.decided} of 7 choices decided with a reason.`);
288
+ const l = report.layers;
289
+ const pages = l.hierarchy.map((h) => `${h.page} ${h.decided} of ${h.total}`).join(", ");
290
+ io.out(
291
+ `craft direction: ${report.valid ? "valid" : "not valid"}. Job ${l.job ? "decided" : "not decided"}; hierarchy ${pages || "not decided"}; tokens ${l.tokens.decided} of ${l.tokens.total}.${l.complete ? " Every layer decided." : ""}`
292
+ );
227
293
  }
228
294
  return report.valid ? 0 : 1;
229
295
  }
@@ -242,7 +308,8 @@ function runDirection(args, io) {
242
308
  return s;
243
309
  }
244
310
  });
245
- const result = propose({ client: base.client, brief: base.brief, sources, current, estate: estateFrom(flags.estate, io.cwd) });
311
+ const declared = declaredFrom(flags.estate, io.cwd).flatMap((s) => s.direction?.accent && s.client !== base.client ? [{ id: s.id, accent: s.direction.accent }] : []);
312
+ const result = propose({ client: base.client, brief: base.brief, sources, current, estate: estateFrom(flags.estate, io.cwd), declared });
246
313
  const merged = { ...base, sources, choices: { ...result.direction.choices, ...stripUndecided(base.choices) }, ...base.exceptions ? { exceptions: base.exceptions } : {} };
247
314
  const text = `${JSON.stringify(merged, null, 2)}
248
315
  `;
@@ -253,7 +320,7 @@ function runDirection(args, io) {
253
320
  }
254
321
  return 0;
255
322
  }
256
- throw new Error("usage: craft direction init | validate | propose");
323
+ throw new Error("usage: craft direction init | validate | propose | research");
257
324
  } catch (error) {
258
325
  io.err(`craft: ${error.message}`);
259
326
  return 2;
@@ -1,22 +1,21 @@
1
- import {
2
- validateDirection
3
- } from "./chunk-ZZ5CKOJP.js";
4
1
  import {
5
2
  merge
6
- } from "./chunk-IMB4V34S.js";
7
- import "./chunk-ZLDA2SYB.js";
3
+ } from "./chunk-2IQ5OXFR.js";
4
+ import "./chunk-OUQ25LY2.js";
8
5
  import "./chunk-JYNXGCYZ.js";
9
6
  import {
10
7
  SIBLING_AT,
11
8
  compareToEstate,
12
9
  loadEstate
13
- } from "./chunk-K2BZN4GA.js";
10
+ } from "./chunk-QU6Q2QZZ.js";
14
11
  import {
15
- loadNull
16
- } from "./chunk-ESCVTKPN.js";
12
+ PAGE_TYPES,
13
+ validateDirection
14
+ } from "./chunk-YDURAOYR.js";
17
15
  import {
18
- readSnapshot
19
- } from "./chunk-CZDOMIVS.js";
16
+ loadNull
17
+ } from "./chunk-RXXRXV3V.js";
18
+ import "./chunk-OQXTQQAU.js";
20
19
  import {
21
20
  COPY_FILE,
22
21
  NOT_COPY_DIR,
@@ -27,7 +26,10 @@ import {
27
26
  parseFlags,
28
27
  readPaths,
29
28
  toJson
30
- } from "./chunk-3VTPI7BU.js";
29
+ } from "./chunk-MUVFECFC.js";
30
+ import {
31
+ readSnapshot
32
+ } from "./chunk-WUACK2HX.js";
31
33
  import {
32
34
  CATALOGUE,
33
35
  auditSnapshot,
@@ -35,7 +37,8 @@ import {
35
37
  fingerprint,
36
38
  scanSource,
37
39
  typicality
38
- } from "./chunk-GMVXADJ6.js";
40
+ } from "./chunk-LFNSB6W7.js";
41
+ import "./chunk-QYBAVU3P.js";
39
42
 
40
43
  // src/report/cli.ts
41
44
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -140,8 +143,16 @@ function characterReport(input, copyTells = /* @__PURE__ */ new Set()) {
140
143
  }
141
144
  if (input.direction) {
142
145
  const d = input.direction;
143
- const raised2 = !d.valid || d.decided < MIN_DECIDED;
144
- signals.push({ id: "reasons", raised: raised2, detail: `${d.decided} of 7 choices decided with a reason${d.valid ? "" : ", art-direction.json has errors"}` });
146
+ const layers = d.layers;
147
+ const raised2 = !d.valid || d.decided < MIN_DECIDED || !layers.complete;
148
+ const pages = layers.hierarchy.map((p) => `${p.page} ${p.decided} of ${p.total}`).join(", ");
149
+ const detail = [
150
+ `job ${layers.job ? "decided" : "not decided"}`,
151
+ `hierarchy ${pages || "not decided"}`,
152
+ `${d.decided} of 7 choices decided with a reason`
153
+ ].join("; ");
154
+ signals.push({ id: "reasons", raised: raised2, detail: `${detail}${d.valid ? "" : ", art-direction.json has errors"}` });
155
+ if (!layers.job) actions.push({ area: "direction", what: "Write the job map: what the visitor is trying to get done, from reviews, enquiries and conversations. Run craft direction research for the questions.", why: "structure and look are decided from the job", priority: 1 });
145
156
  for (const p of d.problems) {
146
157
  const key = p.at.match(/^choices\.(\w+)/)?.[1];
147
158
  if (p.severity === "error" || p.message === "not decided yet") {
@@ -257,12 +268,14 @@ async function runReport(command, args, io) {
257
268
  if (typeof flags === "string") throw new Error(flags);
258
269
  const target2 = flags.positional[0];
259
270
  if (!target2) throw new Error(USAGE);
271
+ const page = flags.page ?? "home";
272
+ if (!PAGE_TYPES.includes(page)) throw new Error(`--page takes one of ${PAGE_TYPES.join(", ")}`);
260
273
  const local = resolve(io.cwd, target2);
261
274
  let snap;
262
275
  if (/\.json$/i.test(target2) && existsSync(local)) {
263
276
  snap = readSnapshot(JSON.parse(readFileSync(local, "utf8")), target2);
264
277
  } else {
265
- const { snapshotUrl } = await import("./audit-5TYDYRXV.js");
278
+ const { snapshotUrl } = await import("./audit-MTUC24XU.js");
266
279
  snap = await snapshotUrl(target2);
267
280
  }
268
281
  const fp = fingerprint(snap);
@@ -289,7 +302,18 @@ async function runReport(command, args, io) {
289
302
  let directionReport = null;
290
303
  if (existsSync(directionFile)) {
291
304
  direction = JSON.parse(readFileSync(directionFile, "utf8"));
292
- directionReport = validateDirection(direction, { fingerprint: fp, pathExists: (p) => existsSync(resolve(dirname(directionFile), p)) });
305
+ directionReport = validateDirection(direction, {
306
+ fingerprint: fp,
307
+ snapshot: snap,
308
+ page,
309
+ strict: flags.strict,
310
+ pathExists: (p) => existsSync(resolve(dirname(directionFile), p)),
311
+ readSource: (s) => {
312
+ if (!s.path || /^https?:/.test(s.path) || !/\.(?:txt|md|markdown|csv|json|html?)$/i.test(s.path)) return void 0;
313
+ const path = resolve(dirname(directionFile), s.path);
314
+ return existsSync(path) ? readFileSync(path, "utf8") : void 0;
315
+ }
316
+ });
293
317
  } else if (flags.direction) {
294
318
  throw new Error(`no such file: ${flags.direction}`);
295
319
  }
@@ -6,10 +6,12 @@ import {
6
6
  nullPath,
7
7
  runHarvest,
8
8
  runNull
9
- } from "./chunk-ESCVTKPN.js";
10
- import "./chunk-CZDOMIVS.js";
11
- import "./chunk-3VTPI7BU.js";
12
- import "./chunk-GMVXADJ6.js";
9
+ } from "./chunk-RXXRXV3V.js";
10
+ import "./chunk-OQXTQQAU.js";
11
+ import "./chunk-MUVFECFC.js";
12
+ import "./chunk-WUACK2HX.js";
13
+ import "./chunk-LFNSB6W7.js";
14
+ import "./chunk-QYBAVU3P.js";
13
15
  export {
14
16
  describeTypicality,
15
17
  formatHarvest,
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-3VTPI7BU.js";
5
- import "./chunk-GMVXADJ6.js";
4
+ } from "./chunk-MUVFECFC.js";
5
+ import "./chunk-LFNSB6W7.js";
6
+ import "./chunk-QYBAVU3P.js";
6
7
 
7
8
  // src/cli.ts
8
9
  process.stdout.on("error", (error) => {
@@ -0,0 +1,6 @@
1
+ import {
2
+ generate
3
+ } from "./chunk-OQXTQQAU.js";
4
+ export {
5
+ generate
6
+ };