synthesisui 0.16.93 → 0.16.95
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/commands/import.js +35 -3
- package/dist/commands/mcp.js +83 -0
- package/dist/doctor/sketch.js +72 -2
- package/dist/doctor/transcribe.js +29 -4
- package/dist/progress.js +16 -0
- package/dist/skill-import.js +226 -156
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -23,7 +23,7 @@ import { buildTable } from "../doctor/tokens.js";
|
|
|
23
23
|
import { readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
|
|
24
24
|
import { transcribeVariants } from "../doctor/variant-read.js";
|
|
25
25
|
import { body, paint, section } from "../output.js";
|
|
26
|
-
import { startProgress } from "../progress.js";
|
|
26
|
+
import { phase, startProgress } from "../progress.js";
|
|
27
27
|
import { detectStack, resolveDeps } from "../stack.js";
|
|
28
28
|
import { walk, walkAll } from "./doctor.js";
|
|
29
29
|
/**
|
|
@@ -396,7 +396,30 @@ export async function takeCensus(root, opts) {
|
|
|
396
396
|
// component's own look, and everything below it is a part this slice does
|
|
397
397
|
// not attempt.
|
|
398
398
|
if (found.length > 0) {
|
|
399
|
-
|
|
399
|
+
/**
|
|
400
|
+
* EVERY COMPONENT THE FILE DEFINES, not just the first.
|
|
401
|
+
*
|
|
402
|
+
* One file in a real library defines two - `RadioCard` and
|
|
403
|
+
* `RadioCardGroup` - and the second arrived with an empty base while its
|
|
404
|
+
* `flex flex-col gap-3` sat six lines below the first component's return
|
|
405
|
+
* (audit, dono, 01/08). The file-level readings (the CSS module, the
|
|
406
|
+
* variant tables) belong to the PRIMARY definition; the root classes and
|
|
407
|
+
* the root tag are per component, scoped by name.
|
|
408
|
+
*/
|
|
409
|
+
for (const extra of found.slice(1)) {
|
|
410
|
+
const et = transcribe(rootClasses(src, extra.name), declaredValues);
|
|
411
|
+
const etag = rootTag(src, extra.name);
|
|
412
|
+
const esize = Object.keys(et.base).length +
|
|
413
|
+
Object.keys(et.dark).length +
|
|
414
|
+
Object.keys(et.states).length;
|
|
415
|
+
if (esize > 0 || etag) {
|
|
416
|
+
looks[extra.name] = {
|
|
417
|
+
...et,
|
|
418
|
+
...(etag ? { rootTag: etag } : {}),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const t = transcribe(rootClasses(src, found[0].name), declaredValues);
|
|
400
423
|
/**
|
|
401
424
|
* THE VARIANT STYLES, FOLDED IN.
|
|
402
425
|
*
|
|
@@ -480,7 +503,7 @@ export async function takeCensus(root, opts) {
|
|
|
480
503
|
Object.keys(states).length +
|
|
481
504
|
Object.keys(parts).length +
|
|
482
505
|
layers.length;
|
|
483
|
-
const tag = rootTag(src);
|
|
506
|
+
const tag = rootTag(src, found[0].name);
|
|
484
507
|
/**
|
|
485
508
|
* THE SKETCH travels with the look, so the reading phase never opens this
|
|
486
509
|
* file again: naming parts is a sentence over data the census already holds,
|
|
@@ -1774,9 +1797,17 @@ export async function runImport(opts) {
|
|
|
1774
1797
|
* Resolved here rather than at measure time because the reading arrives
|
|
1775
1798
|
* AFTER the census was taken - this is the first moment both halves exist.
|
|
1776
1799
|
*/
|
|
1800
|
+
phase(2, 3, "Resolving the reading against the census");
|
|
1777
1801
|
await resolveReadParts(census, root);
|
|
1778
1802
|
}
|
|
1779
1803
|
else {
|
|
1804
|
+
/**
|
|
1805
|
+
* THE MACRO STEP. Three lines for a run, not thirty: a person watching wants
|
|
1806
|
+
* to know which phase they are in and how far into it, and everything else -
|
|
1807
|
+
* every file opened, every candidate ruled out - is the machine talking to
|
|
1808
|
+
* itself (dono, 01/08).
|
|
1809
|
+
*/
|
|
1810
|
+
phase(1, 3, "Measuring the repository");
|
|
1780
1811
|
console.log(section("Reading your project"));
|
|
1781
1812
|
census = await takeCensus(readFrom, {
|
|
1782
1813
|
...(usage.length > 0 ? { usage } : {}),
|
|
@@ -1859,6 +1890,7 @@ export async function runImport(opts) {
|
|
|
1859
1890
|
console.log("");
|
|
1860
1891
|
return;
|
|
1861
1892
|
}
|
|
1893
|
+
phase(3, 3, "Sending v1 and the v2 proposal");
|
|
1862
1894
|
const res = await fetch(`${base}/api/onboarding/import`, {
|
|
1863
1895
|
method: "POST",
|
|
1864
1896
|
headers: {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -117,6 +117,28 @@ const TOOLS = [
|
|
|
117
117
|
required: ["recipe"],
|
|
118
118
|
},
|
|
119
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: "validate_recipes",
|
|
122
|
+
description: "The SAME question as validate_recipe, asked once for a whole reading. Send every recipe you are about to write and get one report back: which would arrive whole, and for the rest what would not survive, by name. Validating 35 components one at a time is 35 round trips and none of them thinking - this is one. Prefer it whenever you have more than one recipe in hand.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
recipes: {
|
|
127
|
+
type: "array",
|
|
128
|
+
description: "Every candidate, in the document's own shape: [{ name, recipe }].",
|
|
129
|
+
items: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
name: { type: "string" },
|
|
133
|
+
recipe: { type: "object" },
|
|
134
|
+
},
|
|
135
|
+
required: ["recipe"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
required: ["recipes"],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
120
142
|
{
|
|
121
143
|
name: "request_component",
|
|
122
144
|
description: "File a component request when nothing in the catalogue covers what you need. This is the OTHER HALF of the refusal rule: you already say which entry you considered and why it did not fit - said in chat, that reasoning evaporates; filed here, it becomes the queue the system's author works from. File it at the moment you build the workaround, while the reasoning is still yours.",
|
|
@@ -333,6 +355,63 @@ async function validateRecipe(name, recipe) {
|
|
|
333
355
|
lines.push("", "Write this into `_synthesisui/not-expressed.md` - what we cannot hold is the list", "the system's author works from, and said only in chat it evaporates.");
|
|
334
356
|
return lines.join("\n");
|
|
335
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* A WHOLE READING, VALIDATED IN ONE QUESTION.
|
|
360
|
+
*
|
|
361
|
+
* `validate_recipe` is right and it is one round trip per component: a reader
|
|
362
|
+
* with 35 anatomies in hand spent 35 turns asking the same question, and that
|
|
363
|
+
* was the largest single block of wall-clock in a real import with none of it
|
|
364
|
+
* thinking (dono, 01/08). Every answer is independent, so batching costs nothing
|
|
365
|
+
* in fidelity - the server runs the same judgment per recipe.
|
|
366
|
+
*
|
|
367
|
+
* The report leads with the count that decides what to do next, then names only
|
|
368
|
+
* what would NOT survive. A list of 30 "this one is fine" lines is a wall the
|
|
369
|
+
* reader has to skim to find the two that matter.
|
|
370
|
+
*/
|
|
371
|
+
async function validateRecipes(entries) {
|
|
372
|
+
if (entries.length === 0)
|
|
373
|
+
return "Send at least one recipe: { recipes: [{ name, recipe }] }.";
|
|
374
|
+
const answer = await askCatalogue("validate", { recipes: entries });
|
|
375
|
+
if (!answer.ok)
|
|
376
|
+
return answer.because;
|
|
377
|
+
const body = answer.body;
|
|
378
|
+
/**
|
|
379
|
+
* A SERVER THAT DOES NOT KNOW THE BATCH still has to answer usefully - a
|
|
380
|
+
* published CLI meeting an older host must degrade to the singular rather than
|
|
381
|
+
* print a shape error. One round trip per recipe is slower and it is correct.
|
|
382
|
+
*/
|
|
383
|
+
if (!body.batch || !Array.isArray(body.results)) {
|
|
384
|
+
const one = [];
|
|
385
|
+
for (const e of entries)
|
|
386
|
+
one.push(await validateRecipe(String(e.name ?? "this component"), e.recipe));
|
|
387
|
+
return `This host does not answer batches, so each was asked on its own.\n\n${one.join("\n\n")}`;
|
|
388
|
+
}
|
|
389
|
+
const results = body.results;
|
|
390
|
+
const bad = results.filter((r) => r.ok !== true);
|
|
391
|
+
const lines = [
|
|
392
|
+
`${results.length} recipes validated - ${results.length - bad.length} would arrive whole.`,
|
|
393
|
+
];
|
|
394
|
+
if (bad.length === 0) {
|
|
395
|
+
lines.push("", "Nothing to fix. Send them.");
|
|
396
|
+
return lines.join("\n");
|
|
397
|
+
}
|
|
398
|
+
lines.push("", `${bad.length} would NOT arrive whole:`);
|
|
399
|
+
for (const r of bad) {
|
|
400
|
+
lines.push("", `## ${r.name ?? "this component"}`);
|
|
401
|
+
for (const x of r.rejected ?? [])
|
|
402
|
+
lines.push(` REFUSED ${x.at}: ${x.why}`);
|
|
403
|
+
for (const l of r.lost ?? [])
|
|
404
|
+
lines.push(` DROPPED BY THE COMPILER ${l}`);
|
|
405
|
+
for (const m of r.missing ?? [])
|
|
406
|
+
lines.push(` BELOW THE FLOOR ${m}`);
|
|
407
|
+
if (r.deferred)
|
|
408
|
+
lines.push(` floor deferred: ${r.deferred}`);
|
|
409
|
+
for (const c of r.checklist ?? [])
|
|
410
|
+
lines.push(` - go look for: ${c}`);
|
|
411
|
+
}
|
|
412
|
+
lines.push("", "Write these into `_synthesisui/not-expressed.md` - what we cannot hold is the", "list the system's author works from, and said only in chat it evaporates.");
|
|
413
|
+
return lines.join("\n");
|
|
414
|
+
}
|
|
336
415
|
/**
|
|
337
416
|
* The catalogue's door, with the session the device login already granted.
|
|
338
417
|
*
|
|
@@ -585,6 +664,10 @@ async function callTool(root, name, args) {
|
|
|
585
664
|
return text(await recipeVocabulary());
|
|
586
665
|
case "validate_recipe":
|
|
587
666
|
return text(await validateRecipe(String(args.name ?? "this component"), args.recipe));
|
|
667
|
+
case "validate_recipes":
|
|
668
|
+
return text(await validateRecipes(Array.isArray(args.recipes)
|
|
669
|
+
? args.recipes
|
|
670
|
+
: []));
|
|
588
671
|
case "request_component": {
|
|
589
672
|
const r = await fileRequest(root, {
|
|
590
673
|
kind: "component",
|
package/dist/doctor/sketch.js
CHANGED
|
@@ -19,10 +19,75 @@
|
|
|
19
19
|
* same in an atomic library, a feature-foldered app, or a single flat directory.
|
|
20
20
|
*/
|
|
21
21
|
import { scanTags } from "./css-modules.js";
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Past this a component is a page in disguise, and the gate already said no.
|
|
24
|
+
*
|
|
25
|
+
* 60 was a guess and it cost a real read: measured across the owner's library,
|
|
26
|
+
* exactly ONE component overflows it - `TextEditor`, 67 nodes - and the seven it
|
|
27
|
+
* lost included `EditorContent`, the editable region that is the whole point of
|
|
28
|
+
* the component. So the skill opened TextEditor.tsx by hand at line 353, which
|
|
29
|
+
* is the "a file read whose content the census already carries is a pipeline
|
|
30
|
+
* optimization failure" rule breaking on our own cap (not-expressed.md, dono,
|
|
31
|
+
* 01/08).
|
|
32
|
+
*
|
|
33
|
+
* At 150 nothing in that library truncates (largest after TextEditor: 43), and
|
|
34
|
+
* 200 buys nothing over 150 - so 150 is the measured ceiling, not a rounder
|
|
35
|
+
* guess. The gate upstream is what keeps a page out; this only keeps a big
|
|
36
|
+
* COMPONENT whole.
|
|
37
|
+
*/
|
|
38
|
+
const MAX_NODES = 150;
|
|
24
39
|
/** `className="..."` or className={"..."} / {`...`} with no interpolation. */
|
|
25
40
|
const CLASS_ATTR = /className=\{?["'`]([^"'`{}$]*)["'`]\}?/;
|
|
41
|
+
/**
|
|
42
|
+
* `className={cn("a b", "c", cond && "d", record[x])}` - the STATIC strings.
|
|
43
|
+
*
|
|
44
|
+
* The plain-attr regex above sees a call expression and reads nothing, and
|
|
45
|
+
* 19 of 36 components in a real library style exactly this way - so their
|
|
46
|
+
* elements arrived in the sketch with no classes at all, and the Tooltip's
|
|
47
|
+
* popup carried none when the base had to be transcribed by hand
|
|
48
|
+
* (not-expressed.md, dono, 01/08). The always-on string arguments ARE the
|
|
49
|
+
* element's resting classes; the conditional halves are variants, which the
|
|
50
|
+
* variant readers already read - so only TOP-LEVEL string literals count.
|
|
51
|
+
* Template literals with `${}` and computed values stay out: a class we
|
|
52
|
+
* cannot see in full is a class we would be guessing at.
|
|
53
|
+
*
|
|
54
|
+
* THE NAMESPACE COUNTS. Measured on the real library: 64 calls are a bare
|
|
55
|
+
* `cn(`, 11 are `twUtils.cn(` and 2 are `clsx(` - and the first version of this
|
|
56
|
+
* regex read only the bare form, so a Modal's whole surface (its fill, border,
|
|
57
|
+
* radius and padding) stayed invisible while its siblings read fine
|
|
58
|
+
* (e2e, dono, 01/08).
|
|
59
|
+
*/
|
|
60
|
+
const CLASS_CALL = /className=\{\s*(?:[A-Za-z_$][\w$]*\.)?(?:cn|clsx|classNames|cx|twMerge|twJoin|classcat)\(/;
|
|
61
|
+
function staticCallClasses(body) {
|
|
62
|
+
const call = CLASS_CALL.exec(body);
|
|
63
|
+
if (!call)
|
|
64
|
+
return null;
|
|
65
|
+
const start = call.index + call[0].length;
|
|
66
|
+
// Walk the call's arguments at depth 0, collecting bare string literals.
|
|
67
|
+
let depth = 1;
|
|
68
|
+
let argStart = start;
|
|
69
|
+
const args = [];
|
|
70
|
+
for (let i = start; i < body.length && depth > 0; i++) {
|
|
71
|
+
const ch = body[i];
|
|
72
|
+
if (ch === "(" || ch === "{" || ch === "[")
|
|
73
|
+
depth += 1;
|
|
74
|
+
else if (ch === ")" || ch === "}" || ch === "]") {
|
|
75
|
+
depth -= 1;
|
|
76
|
+
if (depth === 0)
|
|
77
|
+
args.push(body.slice(argStart, i));
|
|
78
|
+
}
|
|
79
|
+
else if (ch === "," && depth === 1) {
|
|
80
|
+
args.push(body.slice(argStart, i));
|
|
81
|
+
argStart = i + 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const classes = args
|
|
85
|
+
.map((a) => a.trim())
|
|
86
|
+
.filter((a) => /^(["'`]).*\1$/.test(a) && !a.includes("${"))
|
|
87
|
+
.map((a) => a.slice(1, -1).trim())
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
return classes.length > 0 ? classes.join(" ") : null;
|
|
90
|
+
}
|
|
26
91
|
/**
|
|
27
92
|
* The sketch of one component file.
|
|
28
93
|
*
|
|
@@ -47,6 +112,11 @@ export function sketchOf(source) {
|
|
|
47
112
|
const cls = CLASS_ATTR.exec(event.body);
|
|
48
113
|
if (cls?.[1].trim())
|
|
49
114
|
node.classes = cls[1].trim();
|
|
115
|
+
else {
|
|
116
|
+
const called = staticCallClasses(event.body);
|
|
117
|
+
if (called)
|
|
118
|
+
node.classes = called;
|
|
119
|
+
}
|
|
50
120
|
// Literal text: the slice between this open and the next event, when it is prose.
|
|
51
121
|
const next = events[i + 1];
|
|
52
122
|
if (next) {
|
|
@@ -1090,15 +1090,40 @@ export function transcribe(classes, declared) {
|
|
|
1090
1090
|
* which is still a signal, just a weaker one, so it travels as written and the
|
|
1091
1091
|
* platform decides what to do with it.
|
|
1092
1092
|
*/
|
|
1093
|
-
|
|
1094
|
-
|
|
1093
|
+
/**
|
|
1094
|
+
* WHERE *THIS* COMPONENT'S `return (` IS.
|
|
1095
|
+
*
|
|
1096
|
+
* Both readers below took the FIRST `return (` in the file, which is right for
|
|
1097
|
+
* the 34 files that define one component and wrong for the one that defines
|
|
1098
|
+
* two: `RadioCard.tsx` exports `RadioCard` and `RadioCardGroup`, and the group
|
|
1099
|
+
* - a real `flex flex-col gap-3` - arrived with an empty base and the screen
|
|
1100
|
+
* said "Not written yet" over it (audit, dono, 01/08).
|
|
1101
|
+
*
|
|
1102
|
+
* Scoped by NAME when a name is given: find that declaration, then its return.
|
|
1103
|
+
* Absent or unfound, the old behaviour stands exactly - which keeps every
|
|
1104
|
+
* single-component file reading byte-identically.
|
|
1105
|
+
*/
|
|
1106
|
+
function returnAt(source, name) {
|
|
1107
|
+
if (name) {
|
|
1108
|
+
const decl = new RegExp(`(?:function|const|let|var)\\s+${name}\\b|${name}\\s*[:=]\\s*(?:function|\\()`).exec(source);
|
|
1109
|
+
if (decl) {
|
|
1110
|
+
const from = source.slice(decl.index);
|
|
1111
|
+
const ret = from.search(/return\s*\(/);
|
|
1112
|
+
if (ret !== -1)
|
|
1113
|
+
return decl.index + ret;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return source.search(/return\s*\(/);
|
|
1117
|
+
}
|
|
1118
|
+
export function rootTag(source, name) {
|
|
1119
|
+
const ret = returnAt(source, name);
|
|
1095
1120
|
if (ret === -1)
|
|
1096
1121
|
return null;
|
|
1097
1122
|
const m = /<([A-Za-z][A-Za-z0-9.]*)/.exec(source.slice(ret));
|
|
1098
1123
|
return m ? m[1] : null;
|
|
1099
1124
|
}
|
|
1100
|
-
export function rootClasses(source) {
|
|
1101
|
-
const ret = source
|
|
1125
|
+
export function rootClasses(source, name) {
|
|
1126
|
+
const ret = returnAt(source, name);
|
|
1102
1127
|
if (ret === -1)
|
|
1103
1128
|
return [];
|
|
1104
1129
|
const open = source.indexOf("<", ret);
|
package/dist/progress.js
CHANGED
|
@@ -17,6 +17,22 @@ const BAR_WIDTH = 24;
|
|
|
17
17
|
/** Every this-many items, the non-TTY path prints one line. Enough to see movement in
|
|
18
18
|
* a piped log without turning it into the log. */
|
|
19
19
|
const MILESTONE_EVERY = 25;
|
|
20
|
+
/**
|
|
21
|
+
* THE MACRO STEP - "2/3 Validating recipes", on its own line, once.
|
|
22
|
+
*
|
|
23
|
+
* A run that prints every grep it ran spends the person's attention on
|
|
24
|
+
* mechanics, and one that prints nothing for minutes is indistinguishable from
|
|
25
|
+
* one that hung (dono, 01/08). Three lines for three phases is the whole
|
|
26
|
+
* contract: the bar underneath says how far into THIS phase it is, and nothing
|
|
27
|
+
* else gets a line.
|
|
28
|
+
*
|
|
29
|
+
* Written to stderr on purpose. A phase banner is progress, not output: a caller
|
|
30
|
+
* piping stdout to a file - or the skill reading a census off it - gets the data
|
|
31
|
+
* clean, and a person at the terminal still sees both.
|
|
32
|
+
*/
|
|
33
|
+
export function phase(n, of, title, write = (s) => process.stderr.write(s)) {
|
|
34
|
+
write(`\n${n}/${of} ${title}\n`);
|
|
35
|
+
}
|
|
20
36
|
/** A bar that reads as a bar at any width. Two glyphs, no colour: this line is
|
|
21
37
|
* overwritten dozens of times a second and anything fancier flickers. */
|
|
22
38
|
function bar(done, total) {
|
package/dist/skill-import.js
CHANGED
|
@@ -64,7 +64,9 @@ cannot expire on a host that never issued it. That is how a full read ended in a
|
|
|
64
64
|
twice (dono, 31/07).
|
|
65
65
|
|
|
66
66
|
So if the file says \`"registry": "http://localhost:3000"\`, every command in this skill carries
|
|
67
|
-
\`--registry http://localhost:3000\`. If it says production, pass nothing.
|
|
67
|
+
\`--registry http://localhost:3000\`. If it says production, pass nothing. **Read that file ONCE**
|
|
68
|
+
and carry the answer for the whole run - re-reading it before each command is a round trip that
|
|
69
|
+
answers a question already answered. The CLI now refuses a
|
|
68
70
|
mismatch instead of letting the server reject it, but the point is not to reach that.
|
|
69
71
|
|
|
70
72
|
**Do this silently.** Which host you are pointed at and which flags follow from it is plumbing,
|
|
@@ -143,7 +145,196 @@ The census still lands at \`<root>/_synthesisui/census.json\` and records both w
|
|
|
143
145
|
and where the evidence came from, so you always send the same path and there is never a second
|
|
144
146
|
file to pick between. Nothing is sent by \`--dry\`. Read the file.
|
|
145
147
|
|
|
146
|
-
### 2.
|
|
148
|
+
### 2. Walk them through the decisions - ALL of them, before any reading
|
|
149
|
+
|
|
150
|
+
**Every question this skill asks is answerable from the census you just took.** Source, name,
|
|
151
|
+
primary, theme, canvas, our-kit-or-theirs, architecture - the numbers behind all seven are in
|
|
152
|
+
the file on their disk, and none of them needs the anatomy.
|
|
153
|
+
|
|
154
|
+
They used to be asked AFTER the reading, and that put six hundred lines of work between the
|
|
155
|
+
person's first answer and their second (dono, 01/08). A person who answers seven questions in
|
|
156
|
+
ninety seconds and then watches a bar has had a good four minutes; the same person interrupted
|
|
157
|
+
twice in the middle of a silence has had a bad twenty.
|
|
158
|
+
|
|
159
|
+
So: ask everything here, in one pass, and **do not come back**. If something you learn during
|
|
160
|
+
the reading contradicts an answer, you do not reopen the question - you say so in the report at
|
|
161
|
+
the end and let them change it there.
|
|
162
|
+
|
|
163
|
+
Seven decisions are theirs. **Ask them as separate questions with selectable options** - use your
|
|
164
|
+
question tool, one call per decision, so they pick instead of reading a wall and composing a
|
|
165
|
+
reply. A single block containing everything is a report, and a report gets read, not answered.
|
|
166
|
+
|
|
167
|
+
Every question carries **your recommendation first, marked as such**, and the evidence for it in
|
|
168
|
+
one line. They are choosing, not auditing you.
|
|
169
|
+
|
|
170
|
+
**2a. Source** - the folder you measure the system out of.
|
|
171
|
+
|
|
172
|
+
Options are the candidates the CLI already ranked, each with what it would produce, plus the
|
|
173
|
+
whole repo, plus *somewhere else* for a path they type:
|
|
174
|
+
|
|
175
|
+
\`\`\`
|
|
176
|
+
Where should the system come from?
|
|
177
|
+
|
|
178
|
+
packages/ui (recommended) 114 files · 47% of its values already named ·
|
|
179
|
+
92 tokens of their own · every app imports it
|
|
180
|
+
the whole repo 2797 files · 36% · the average of one dark app
|
|
181
|
+
and two light ones, which is a diagnosis and
|
|
182
|
+
not a system
|
|
183
|
+
apps/web-dashboard the heaviest consumer, if you want one app's
|
|
184
|
+
vocabulary rather than the shared one
|
|
185
|
+
somewhere else a path you name
|
|
186
|
+
\`\`\`
|
|
187
|
+
|
|
188
|
+
If they name a path you cannot use, say which of the two it is and offer the list again:
|
|
189
|
+
|
|
190
|
+
- **it does not exist**, or holds no files you can read
|
|
191
|
+
- **it holds no design values** - no colours, no radii, no tokens. A folder of server code or
|
|
192
|
+
config is not a design system, and measuring it produces a system of nothing.
|
|
193
|
+
|
|
194
|
+
**2b. Name** - suggest theirs, and let Enter take it:
|
|
195
|
+
|
|
196
|
+
\`\`\`
|
|
197
|
+
What should it be called? (SignalUI)
|
|
198
|
+
\`\`\`
|
|
199
|
+
|
|
200
|
+
Say where the suggestion came from - \`Introduction.mdx\`, the package name, the folder. A name
|
|
201
|
+
they recognise is worth more than a clever one. The slug is derived once and never changes, so
|
|
202
|
+
this is the moment.
|
|
203
|
+
|
|
204
|
+
**2c. Primary** - and explain the role before offering the choice, because *primary* is our word
|
|
205
|
+
and not necessarily theirs:
|
|
206
|
+
|
|
207
|
+
\`\`\`
|
|
208
|
+
Primary is the colour that carries action - buttons, links, focus, the thing
|
|
209
|
+
you want pressed. Everything else in the system is measured for contrast
|
|
210
|
+
against it.
|
|
211
|
+
|
|
212
|
+
#059aed ocean-500 (recommended) your ColorPalette.mdx calls Ocean the
|
|
213
|
+
"Primary branding scale"
|
|
214
|
+
#1a4ed8 royal-blue-500 your docs label this one secondary
|
|
215
|
+
#ec4899 vivid-pink-500 half of your signature AI gradient
|
|
216
|
+
#4A90E2 blue-500 the most-painted blue in the folder,
|
|
217
|
+
but it sits after your own
|
|
218
|
+
/* End - Signal UI */ marker
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
Always name the hex AND the token, and always give the reason a value is *not* recommended when
|
|
222
|
+
frequency would have picked it. That last row is the whole point of asking.
|
|
223
|
+
|
|
224
|
+
**2d. Default theme** - which face it opens in:
|
|
225
|
+
|
|
226
|
+
\`\`\`
|
|
227
|
+
dark (recommended) data-theme="dark" in the root layout, next-themes
|
|
228
|
+
defaultTheme="dark", enableSystem={false}
|
|
229
|
+
light the other one, also real here
|
|
230
|
+
\`\`\`
|
|
231
|
+
|
|
232
|
+
**Two is all there is** - a SynthesisUI system carries one palette and one alternate, and the
|
|
233
|
+
schema is \`"light" | "dark"\`. So this is not "which themes do you support", it is "which one do
|
|
234
|
+
docs and installs open in". If both are real, both get built from their own tokens; this picks
|
|
235
|
+
the face.
|
|
236
|
+
|
|
237
|
+
**2e. Canvas** - confirm, do not offer a list. The page is measured, not chosen:
|
|
238
|
+
|
|
239
|
+
\`\`\`
|
|
240
|
+
canvas #222326 darkgray-500 - what DashboardLayout paints on <main>
|
|
241
|
+
(your sidebar sits a step deeper at darkgray-700)
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
Say it and let them object. Offering five near-blacks to pick between is asking someone to
|
|
245
|
+
re-decide something their code already decided.
|
|
246
|
+
|
|
247
|
+
**2f. Our kit, or theirs alone** - ask this ONLY when they already have a component library,
|
|
248
|
+
which is when \`--scope\` pointed at one and the CLI said so. An app with no components of its own
|
|
249
|
+
has nothing to weigh and should never see the question.
|
|
250
|
+
|
|
251
|
+
\`\`\`
|
|
252
|
+
Every system born here comes with 45 components of ours - button, card, input,
|
|
253
|
+
table, the rest of a working toolkit. You already have 37.
|
|
254
|
+
|
|
255
|
+
add ours to yours (recommended) 68 components, and the 23 governed by your
|
|
256
|
+
own rules are the 23 you wrote
|
|
257
|
+
mine alone 37 components, all of them yours. Our
|
|
258
|
+
layouts that need a component you do not
|
|
259
|
+
have stay out too. Add any of ours later
|
|
260
|
+
from the library, one at a time
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
Send it as \`"standards": "none"\` in the reading when they choose the second, and leave the field
|
|
264
|
+
out when they choose the first. **Recommend adding ours**, and say why in one line: a component of
|
|
265
|
+
ours costs nothing until something composes it, and having a \`table\` the day somebody needs a
|
|
266
|
+
table is worth more than a shorter list.
|
|
267
|
+
|
|
268
|
+
Two things to be straight about, because both are true and neither is obvious:
|
|
269
|
+
|
|
270
|
+
- **the foundations come from the seed either way.** Their palette, their scales, their type - all
|
|
271
|
+
theirs - but the seven type slots, the easing and the neutral floor arrive from ours, because
|
|
272
|
+
the alternative to a type scale is no components at all rather than their components.
|
|
273
|
+
- **our layouts are written in our recipes.** A landing that composes \`button\`, \`card\` and
|
|
274
|
+
\`badge\` needs those to exist. Choosing *mine alone* keeps the layouts whose recipes they
|
|
275
|
+
happen to have and drops the rest - a layout pointing at a recipe nobody has renders as holes.
|
|
276
|
+
|
|
277
|
+
**2g. Which architecture has priority** - ask only when the CLI reported more than
|
|
278
|
+
one, which it does under "How this project is organised".
|
|
279
|
+
|
|
280
|
+
A project having two shapes is not a project with a mistake: a library organised
|
|
281
|
+
atomically inside a monorepo whose apps are organised by feature is two true answers.
|
|
282
|
+
Say that before you ask, or the question reads as an accusation.
|
|
283
|
+
|
|
284
|
+
\`\`\`
|
|
285
|
+
Two shapes, and neither is wrong:
|
|
286
|
+
|
|
287
|
+
atomic under packages/ui (recommended) 114 files - atoms/molecules/organisms,
|
|
288
|
+
and this is where the system comes from
|
|
289
|
+
feature under apps/web-dashboard 900 files - features/, where the app
|
|
290
|
+
consumes it
|
|
291
|
+
\`\`\`
|
|
292
|
+
|
|
293
|
+
The one they pick becomes a rule that reaches \`CLAUDE.md\`, so **every prompt after this
|
|
294
|
+
knows where a new component goes** - not what the folders are called, but which rung or
|
|
295
|
+
which feature a new file belongs to. Recommend the one the system came from.
|
|
296
|
+
|
|
297
|
+
**2h. Fonts** - do not ask at all. Report and move on:
|
|
298
|
+
|
|
299
|
+
\`\`\`
|
|
300
|
+
fonts Figtree for both display and body - the only family declared
|
|
301
|
+
(--font-sans, and next/font Figtree in all three root layouts)
|
|
302
|
+
\`\`\`
|
|
303
|
+
|
|
304
|
+
If a project genuinely declares two families, say which is which and why you paired them that
|
|
305
|
+
way. If it declares one, there is nothing to decide.
|
|
306
|
+
|
|
307
|
+
**A note on colour swatches.** You cannot paint a hex in this conversation, so the token name
|
|
308
|
+
carries the weight - \`ocean-500\` tells someone more than a square would. When they run
|
|
309
|
+
\`npx synthesisui import\` themselves in a terminal, the CLI paints them.
|
|
310
|
+
|
|
311
|
+
### 3. Read what arithmetic cannot - and do it in ONE uninterrupted run
|
|
312
|
+
|
|
313
|
+
Every answer is in hand, so nothing below needs the person. **Do not ask, do not confirm, do
|
|
314
|
+
not narrate the mechanics.** Three lines on screen for the whole phase:
|
|
315
|
+
|
|
316
|
+
\`\`\`
|
|
317
|
+
1/3 Measuring the repository... (step 1, already done)
|
|
318
|
+
2/3 Naming the anatomy and validating... (this step)
|
|
319
|
+
3/3 Sending v1 and v2... (step 4)
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
**Narrate findings, never mechanics.** A grep, a glob, a file you opened and ruled out, a draft
|
|
323
|
+
you rewrote - none of those is a finding, and a run that prints them reads as a machine talking
|
|
324
|
+
to itself. *"Their docs call Ocean the primary branding scale"* is a finding and earns its line.
|
|
325
|
+
|
|
326
|
+
**The census answers first, always.** Before opening any file, check whether \`signals\`, \`sketch\`,
|
|
327
|
+
\`declared\` or \`coverage\` already hold it - they usually do, and a file read whose content the
|
|
328
|
+
census carries is the failure this pipeline keeps paying for. The sketch now runs to 150 nodes,
|
|
329
|
+
so a big component arrives WHOLE: \`TextEditor\` used to lose its editable region at node 60 and
|
|
330
|
+
that single truncation is what forced a hand-read of the file (dono, 01/08).
|
|
331
|
+
|
|
332
|
+
**Validate in ONE call, at the end.** Build every recipe in memory first, then send them all to
|
|
333
|
+
\`validate_recipes\` - one round trip for the whole reading instead of one per component. A reader
|
|
334
|
+
with 35 anatomies spent 35 turns asking the same question, and none of that was thinking. Only
|
|
335
|
+
after the batch answers do you fix what it named and, if you fixed anything, ask once more.
|
|
336
|
+
|
|
337
|
+
|
|
147
338
|
|
|
148
339
|
Open the project and answer the questions below. Then add a \`reading\` object to
|
|
149
340
|
\`_synthesisui/census.json\`:
|
|
@@ -591,6 +782,16 @@ a recipe before these tools existed, and four separate readings were lost in sil
|
|
|
591
782
|
\`dark:\` inside a variant, a state the compiler cannot spell, a part name with a dot in it,
|
|
592
783
|
a layer with an empty style. Each of those was correct work that arrived as nothing.
|
|
593
784
|
|
|
785
|
+
**In ONE call.** Build every recipe in memory, then send the whole reading to
|
|
786
|
+
\`validate_recipes\` - one round trip for 35 components instead of 35. \`validate_recipe\`
|
|
787
|
+
(singular) is for the one recipe you rewrote after the batch named it; reaching for it in a
|
|
788
|
+
loop is the shape this optimisation exists to kill (dono, 01/08).
|
|
789
|
+
|
|
790
|
+
\`\`\`
|
|
791
|
+
validate_recipes({ recipes: [{ name, recipe }, …] }) the whole reading, one question
|
|
792
|
+
validate_recipe(name, recipe) the one you just fixed
|
|
793
|
+
\`\`\`
|
|
794
|
+
|
|
594
795
|
\`validate_recipe\` never answers yes or no. It answers with what would not survive:
|
|
595
796
|
|
|
596
797
|
\`\`\`
|
|
@@ -748,7 +949,16 @@ scale and lost the sentence (dono, 01/08).
|
|
|
748
949
|
|
|
749
950
|
When their docs (a \`Geometry.mdx\`, a design-tokens page, comments beside the \`@theme\`)
|
|
750
951
|
state what a step is FOR, send it as system-wide rules - \`applies: []\`, \`fact: true\`, the
|
|
751
|
-
doc as evidence
|
|
952
|
+
doc as evidence.
|
|
953
|
+
|
|
954
|
+
**One doc pass, and only for what the census cannot hold.** A scale's INTENT is prose, so it
|
|
955
|
+
genuinely needs a human sentence somebody wrote - but a scale's VALUES, its token names, the
|
|
956
|
+
theme, the class convention and the architecture are all measured and sitting in \`declared\`,
|
|
957
|
+
\`signals\` and \`conventions\`. Opening a docs page to confirm a number the census already
|
|
958
|
+
carries costs a turn and answers nothing. Read the docs ONCE, for the sentences; never in a
|
|
959
|
+
loop, and never to check arithmetic.
|
|
960
|
+
|
|
961
|
+
Send it like this:
|
|
752
962
|
|
|
753
963
|
\`\`\`
|
|
754
964
|
this system's spacing has exactly three steps - sm for close relationships inside a
|
|
@@ -786,157 +996,9 @@ The older flat form - \`"parts": [{ "name": "label", "classes": "..." }]\` - sti
|
|
|
786
996
|
styles correctly. It just carries no shape, so the preview lays the parts out in a row and the
|
|
787
997
|
spec view has no nesting to draw.
|
|
788
998
|
|
|
789
|
-
###
|
|
790
|
-
|
|
791
|
-
Six decisions are theirs. **Ask them as separate questions with selectable options** - use your
|
|
792
|
-
question tool, one call per decision, so they pick instead of reading a wall and composing a
|
|
793
|
-
reply. A single block containing everything is a report, and a report gets read, not answered.
|
|
794
|
-
|
|
795
|
-
Every question carries **your recommendation first, marked as such**, and the evidence for it in
|
|
796
|
-
one line. They are choosing, not auditing you.
|
|
797
|
-
|
|
798
|
-
**3a. Source** - the folder you measure the system out of.
|
|
799
|
-
|
|
800
|
-
Options are the candidates the CLI already ranked, each with what it would produce, plus the
|
|
801
|
-
whole repo, plus *somewhere else* for a path they type:
|
|
802
|
-
|
|
803
|
-
\`\`\`
|
|
804
|
-
Where should the system come from?
|
|
805
|
-
|
|
806
|
-
packages/ui (recommended) 114 files · 47% of its values already named ·
|
|
807
|
-
92 tokens of their own · every app imports it
|
|
808
|
-
the whole repo 2797 files · 36% · the average of one dark app
|
|
809
|
-
and two light ones, which is a diagnosis and
|
|
810
|
-
not a system
|
|
811
|
-
apps/web-dashboard the heaviest consumer, if you want one app's
|
|
812
|
-
vocabulary rather than the shared one
|
|
813
|
-
somewhere else a path you name
|
|
814
|
-
\`\`\`
|
|
815
|
-
|
|
816
|
-
If they name a path you cannot use, say which of the two it is and offer the list again:
|
|
817
|
-
|
|
818
|
-
- **it does not exist**, or holds no files you can read
|
|
819
|
-
- **it holds no design values** - no colours, no radii, no tokens. A folder of server code or
|
|
820
|
-
config is not a design system, and measuring it produces a system of nothing.
|
|
821
|
-
|
|
822
|
-
**3b. Name** - suggest theirs, and let Enter take it:
|
|
823
|
-
|
|
824
|
-
\`\`\`
|
|
825
|
-
What should it be called? (SignalUI)
|
|
826
|
-
\`\`\`
|
|
827
|
-
|
|
828
|
-
Say where the suggestion came from - \`Introduction.mdx\`, the package name, the folder. A name
|
|
829
|
-
they recognise is worth more than a clever one. The slug is derived once and never changes, so
|
|
830
|
-
this is the moment.
|
|
831
|
-
|
|
832
|
-
**3c. Primary** - and explain the role before offering the choice, because *primary* is our word
|
|
833
|
-
and not necessarily theirs:
|
|
834
|
-
|
|
835
|
-
\`\`\`
|
|
836
|
-
Primary is the colour that carries action - buttons, links, focus, the thing
|
|
837
|
-
you want pressed. Everything else in the system is measured for contrast
|
|
838
|
-
against it.
|
|
839
|
-
|
|
840
|
-
#059aed ocean-500 (recommended) your ColorPalette.mdx calls Ocean the
|
|
841
|
-
"Primary branding scale"
|
|
842
|
-
#1a4ed8 royal-blue-500 your docs label this one secondary
|
|
843
|
-
#ec4899 vivid-pink-500 half of your signature AI gradient
|
|
844
|
-
#4A90E2 blue-500 the most-painted blue in the folder,
|
|
845
|
-
but it sits after your own
|
|
846
|
-
/* End - Signal UI */ marker
|
|
847
|
-
\`\`\`
|
|
848
|
-
|
|
849
|
-
Always name the hex AND the token, and always give the reason a value is *not* recommended when
|
|
850
|
-
frequency would have picked it. That last row is the whole point of asking.
|
|
851
|
-
|
|
852
|
-
**3d. Default theme** - which face it opens in:
|
|
853
|
-
|
|
854
|
-
\`\`\`
|
|
855
|
-
dark (recommended) data-theme="dark" in the root layout, next-themes
|
|
856
|
-
defaultTheme="dark", enableSystem={false}
|
|
857
|
-
light the other one, also real here
|
|
858
|
-
\`\`\`
|
|
859
|
-
|
|
860
|
-
**Two is all there is** - a SynthesisUI system carries one palette and one alternate, and the
|
|
861
|
-
schema is \`"light" | "dark"\`. So this is not "which themes do you support", it is "which one do
|
|
862
|
-
docs and installs open in". If both are real, both get built from their own tokens; this picks
|
|
863
|
-
the face.
|
|
864
|
-
|
|
865
|
-
**3e. Canvas** - confirm, do not offer a list. The page is measured, not chosen:
|
|
866
|
-
|
|
867
|
-
\`\`\`
|
|
868
|
-
canvas #222326 darkgray-500 - what DashboardLayout paints on <main>
|
|
869
|
-
(your sidebar sits a step deeper at darkgray-700)
|
|
870
|
-
\`\`\`
|
|
871
|
-
|
|
872
|
-
Say it and let them object. Offering five near-blacks to pick between is asking someone to
|
|
873
|
-
re-decide something their code already decided.
|
|
874
|
-
|
|
875
|
-
**3e-bis. Our kit, or theirs alone** - ask this ONLY when they already have a component library,
|
|
876
|
-
which is when \`--scope\` pointed at one and the CLI said so. An app with no components of its own
|
|
877
|
-
has nothing to weigh and should never see the question.
|
|
878
|
-
|
|
879
|
-
\`\`\`
|
|
880
|
-
Every system born here comes with 45 components of ours - button, card, input,
|
|
881
|
-
table, the rest of a working toolkit. You already have 37.
|
|
882
|
-
|
|
883
|
-
add ours to yours (recommended) 68 components, and the 23 governed by your
|
|
884
|
-
own rules are the 23 you wrote
|
|
885
|
-
mine alone 37 components, all of them yours. Our
|
|
886
|
-
layouts that need a component you do not
|
|
887
|
-
have stay out too. Add any of ours later
|
|
888
|
-
from the library, one at a time
|
|
889
|
-
\`\`\`
|
|
890
|
-
|
|
891
|
-
Send it as \`"standards": "none"\` in the reading when they choose the second, and leave the field
|
|
892
|
-
out when they choose the first. **Recommend adding ours**, and say why in one line: a component of
|
|
893
|
-
ours costs nothing until something composes it, and having a \`table\` the day somebody needs a
|
|
894
|
-
table is worth more than a shorter list.
|
|
895
|
-
|
|
896
|
-
Two things to be straight about, because both are true and neither is obvious:
|
|
897
|
-
|
|
898
|
-
- **the foundations come from the seed either way.** Their palette, their scales, their type - all
|
|
899
|
-
theirs - but the seven type slots, the easing and the neutral floor arrive from ours, because
|
|
900
|
-
the alternative to a type scale is no components at all rather than their components.
|
|
901
|
-
- **our layouts are written in our recipes.** A landing that composes \`button\`, \`card\` and
|
|
902
|
-
\`badge\` needs those to exist. Choosing *mine alone* keeps the layouts whose recipes they
|
|
903
|
-
happen to have and drops the rest - a layout pointing at a recipe nobody has renders as holes.
|
|
904
|
-
|
|
905
|
-
**3e-ter. Which architecture has priority** - ask only when the CLI reported more than
|
|
906
|
-
one, which it does under "How this project is organised".
|
|
907
|
-
|
|
908
|
-
A project having two shapes is not a project with a mistake: a library organised
|
|
909
|
-
atomically inside a monorepo whose apps are organised by feature is two true answers.
|
|
910
|
-
Say that before you ask, or the question reads as an accusation.
|
|
911
|
-
|
|
912
|
-
\`\`\`
|
|
913
|
-
Two shapes, and neither is wrong:
|
|
914
|
-
|
|
915
|
-
atomic under packages/ui (recommended) 114 files - atoms/molecules/organisms,
|
|
916
|
-
and this is where the system comes from
|
|
917
|
-
feature under apps/web-dashboard 900 files - features/, where the app
|
|
918
|
-
consumes it
|
|
919
|
-
\`\`\`
|
|
920
|
-
|
|
921
|
-
The one they pick becomes a rule that reaches \`CLAUDE.md\`, so **every prompt after this
|
|
922
|
-
knows where a new component goes** - not what the folders are called, but which rung or
|
|
923
|
-
which feature a new file belongs to. Recommend the one the system came from.
|
|
924
|
-
|
|
925
|
-
**3f. Fonts** - do not ask at all. Report and move on:
|
|
926
|
-
|
|
927
|
-
\`\`\`
|
|
928
|
-
fonts Figtree for both display and body - the only family declared
|
|
929
|
-
(--font-sans, and next/font Figtree in all three root layouts)
|
|
930
|
-
\`\`\`
|
|
931
|
-
|
|
932
|
-
If a project genuinely declares two families, say which is which and why you paired them that
|
|
933
|
-
way. If it declares one, there is nothing to decide.
|
|
934
|
-
|
|
935
|
-
**A note on colour swatches.** You cannot paint a hex in this conversation, so the token name
|
|
936
|
-
carries the weight - \`ocean-500\` tells someone more than a square would. When they run
|
|
937
|
-
\`npx synthesisui import\` themselves in a terminal, the CLI paints them.
|
|
999
|
+
### 4. Send it
|
|
938
1000
|
|
|
939
|
-
|
|
1001
|
+
Everything is answered and everything is validated:
|
|
940
1002
|
|
|
941
1003
|
\`\`\`
|
|
942
1004
|
npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--registry <from step 0>]
|
|
@@ -947,13 +1009,13 @@ npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--
|
|
|
947
1009
|
Pass every answer explicitly - \`--name\`, \`--scope\` and \`--usage\` if they changed either, and the reading rewritten
|
|
948
1010
|
with their primary and their theme. The CLI cannot ask anything when you are the one running it:
|
|
949
1011
|
it has no terminal, so its own prompts are skipped by design. **Whatever you did not ask, nobody
|
|
950
|
-
asked.** Skip
|
|
1012
|
+
asked.** Skip 2a and the first time they learn where their system came from is when they open
|
|
951
1013
|
it.
|
|
952
1014
|
|
|
953
1015
|
If it refuses, it says which of the three it is: no session, an expired one, or a token issued
|
|
954
1016
|
by a different host. All are recoverable from the census on disk; none need a re-measure.
|
|
955
1017
|
|
|
956
|
-
###
|
|
1018
|
+
### 5. Tell them exactly what they have, and what is theirs to decide
|
|
957
1019
|
|
|
958
1020
|
The import publishes **v1** as a faithful baseline and opens a **v2 draft** holding the
|
|
959
1021
|
proposal. Say all of this in your own words - do not just print the link:
|
|
@@ -984,7 +1046,7 @@ three biggest items so they know whether it is worth opening now.
|
|
|
984
1046
|
found in their code, a token that is not ramp-shaped, a name the document refused. Read them
|
|
985
1047
|
and pass on the ones that matter.
|
|
986
1048
|
|
|
987
|
-
###
|
|
1049
|
+
### 6. Close the loop in their repo
|
|
988
1050
|
|
|
989
1051
|
The system is only worth something once it governs the code it came from:
|
|
990
1052
|
|
|
@@ -1022,6 +1084,14 @@ The user should end up with a system that reads like theirs and not like ours:
|
|
|
1022
1084
|
- **Do not guess \`themes\` to be helpful.** Omitting it falls back to counting rungs, which is
|
|
1023
1085
|
a guess the product labels as a guess. A wrong confident answer does not get labelled.
|
|
1024
1086
|
- **Do not apologise for the empty contracts.** They are the design. Explain them.
|
|
1087
|
+
- **Do not come back with a question once step 3 has started.** Everything answerable was
|
|
1088
|
+
answered in step 2. An interruption in the middle of a silent phase costs the person the
|
|
1089
|
+
whole phase, because they now have to hold what you were doing in their head.
|
|
1090
|
+
- **Do not narrate the mechanics.** No grep, no glob, no "let me check whether X exists", no
|
|
1091
|
+
draft you rewrote. Findings earn a line; the machine talking to itself does not.
|
|
1092
|
+
- **Do not open a file to confirm something the census already carries.** \`signals\`, \`sketch\`,
|
|
1093
|
+
\`declared\`, \`conventions\` and \`coverage\` are the answer to almost every "let me just check".
|
|
1094
|
+
- **Do not validate in a loop.** One \`validate_recipes\` for the whole reading.
|
|
1025
1095
|
|
|
1026
1096
|
## Not yet true
|
|
1027
1097
|
|
package/package.json
CHANGED