synthesisui 0.16.60 → 0.16.62

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.
@@ -637,11 +637,11 @@ async function sayIfWorkspace(root) {
637
637
  console.log("");
638
638
  if (shared.length > 0) {
639
639
  console.log(body("The shape that works, if your vocabulary is shared:"));
640
- console.log(body(` ${paint.strong(`synthesisui import --dir ${shared[0]}`)} ${paint.faint("the system comes from here")}`));
640
+ console.log(body(` ${paint.strong(`synthesisui import --scope ${shared[0]}`)} ${paint.faint("the system comes from here")}`));
641
641
  }
642
642
  else {
643
643
  console.log(body("The shape that works:"));
644
- console.log(body(` ${paint.strong(`synthesisui import --dir ${apps[0]}`)} ${paint.faint("one app, one vocabulary")}`));
644
+ console.log(body(` ${paint.strong(`synthesisui import --scope ${apps[0]}`)} ${paint.faint("one app, one vocabulary")}`));
645
645
  }
646
646
  for (const app of apps.slice(0, 3)) {
647
647
  console.log(body(` ${paint.dim(`synthesisui doctor ${app}`)} ${paint.faint("how far this one is from it")}`));
@@ -863,6 +863,16 @@ function sayReach(c) {
863
863
  }
864
864
  export async function runImport(opts) {
865
865
  const root = opts.root ?? process.cwd();
866
+ /**
867
+ * WHAT TO READ, as opposed to WHERE TO WRITE. In a monorepo the design
868
+ * decisions live in one package and the governance belongs at the root, and
869
+ * conflating the two meant every re-measure scattered another census.
870
+ */
871
+ const scope = opts.scope
872
+ ?.trim()
873
+ .replace(/^\.\/+/, "")
874
+ .replace(/\/+$/, "");
875
+ const readFrom = scope ? join(root, scope) : root;
866
876
  // A census handed to us (an agent annotated it) is sent as-is; the numbers
867
877
  // inside were still ours to begin with.
868
878
  let census;
@@ -889,15 +899,20 @@ export async function runImport(opts) {
889
899
  }
890
900
  else {
891
901
  console.log(section("Reading your project"));
892
- census = await takeCensus(root);
902
+ census = await takeCensus(readFrom);
903
+ if (scope)
904
+ census.scope = scope;
893
905
  }
894
906
  summarize(census);
895
- if (!opts.census)
907
+ // Only when nobody has scoped yet. Telling someone to scope to the folder
908
+ // they just scoped to reads as the tool not having noticed.
909
+ if (!opts.census && !scope)
896
910
  await sayIfWorkspace(root);
897
- // A census handed to us is written BACK TO ITSELF. Deriving the path from the
898
- // root instead planted a second census at the repo root when the real one
899
- // lived in `packages/ui/_synthesisui/` - two files, silently disagreeing
900
- // about which import happened (dono, 31/07).
911
+ // A census handed to us is written BACK TO ITSELF; one we took lands at the
912
+ // ROOT, whatever it measured. Everything else in `_synthesisui/` is anchored
913
+ // there - config, `ds/<slug>/`, the hook's marker - and the census was the
914
+ // only artefact that travelled with `--dir`, which is how two of them ended
915
+ // up disagreeing about which import happened (dono, 31/07).
901
916
  const out = opts.census ?? join(root, "_synthesisui", "census.json");
902
917
  await mkdir(dirname(out), { recursive: true });
903
918
  await writeFile(out, `${JSON.stringify(census, null, 2)}\n`, "utf8");
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ Usage - deterministic, FREE:
41
41
 
42
42
  Usage - governance (deterministic, FREE):
43
43
  synthesisui adopt [--write] turn the design system you ALREADY have into a contract
44
- synthesisui import [--dry] [--name <n>] read the app you already have and make it a system
44
+ synthesisui import [--dry] [--scope <p>] read the app you already have and make it a system
45
45
  your agent follows - without touching your CSS
46
46
  synthesisui connect wire your agent: the check as an editor hook, the system
47
47
  as MCP tools, and a contract that stops repeating itself
@@ -66,6 +66,7 @@ Options:
66
66
  --version <n> install a specific version (default: latest)
67
67
  --ds <slug> init: bring this DS in right away · generate: target DS (default: installed)
68
68
  --name <name> preferred component name for generate
69
+ --scope <path> import: measure only this folder; the census still lands at the root
69
70
  --scheme <s> dark|light - which end of your ladder is the default
70
71
  --target <t> template/init target: next | general (default: next)
71
72
  --pages-dir <dir> init: folder for generated pages (default: app)
@@ -162,6 +163,8 @@ async function main() {
162
163
  name: typeof flags.name === "string" ? flags.name : undefined,
163
164
  // Only these two words. Anything else is a typo that would silently
164
165
  // invert a system, so it falls through to being measured and asked.
166
+ // WHAT to read. `--dir` stays WHERE the project is, in every command.
167
+ scope: typeof flags.scope === "string" ? flags.scope : undefined,
165
168
  scheme: flags.scheme === "dark" || flags.scheme === "light"
166
169
  ? flags.scheme
167
170
  : undefined,
@@ -67,19 +67,61 @@ So if the file says \`"registry": "http://localhost:3000"\`, every command in th
67
67
  \`--registry http://localhost:3000\`. If it says production, pass nothing. The CLI now refuses a
68
68
  mismatch instead of letting the server reject it, but the point is not to reach that.
69
69
 
70
+ **Do this silently.** Which host you are pointed at and which flags follow from it is plumbing,
71
+ and narrating it - *"registry is production, so no --registry flag needed"* - spends the
72
+ person's attention on a decision that was never theirs (dono, 01/08). Say something only when
73
+ there is nothing to say to: no token, or the wrong host.
74
+
75
+ ## How to narrate
76
+
77
+ The person cares about **what you found in their code** and **what you are about to create**.
78
+ They do not care which flag you passed, which grep you ran, or what you checked and ruled out.
79
+
80
+ - Report findings, not mechanics. *"Their docs call Ocean the primary branding scale"* earns
81
+ its line. *"Let me check whether packages/ui exists"* does not.
82
+ - Every decision you make on their behalf belongs in **one** confirmation at the end, not
83
+ scattered through the work as fait accompli.
84
+ - Never end on a wall of prose that happens to contain a question. If you need an answer, the
85
+ last thing on screen is the question.
86
+
70
87
  ### 1. Measure
71
88
 
89
+ **Always run from the project root, and always twice in a monorepo.** The root is where the
90
+ governance lives - \`_synthesisui/config.json\`, \`ds/<slug>/\`, and the census. One root, one
91
+ census; re-measuring overwrites it rather than leaving copies to send the wrong one.
92
+
93
+ First, unscoped, to find out what kind of repo this is:
94
+
72
95
  \`\`\`
73
- npx synthesisui import --dry --dir <the folder that holds the design decisions>
96
+ npx synthesisui import --dry
74
97
  \`\`\`
75
98
 
76
- In a monorepo that folder is almost never the repo root. Look for the package every app
77
- imports from - \`packages/ui\`, \`packages/design-system\`, \`libs/shared-ui\`. The CLI names the
78
- candidates and ranks them by how many tokens they declare; if it printed a workspace summary,
79
- read it before choosing. Scoping to the wrong folder measures the wrong thing, and the score
80
- it reports will be meaninglessly low.
99
+ If it is a single app, that census is the one. If it holds several projects, the CLI says so
100
+ and **names the candidates itself**, ranked by how many tokens each declares:
81
101
 
82
- This writes \`_synthesisui/census.json\` and **sends nothing**. Read that file.
102
+ \`\`\`
103
+ This root holds several projects
104
+ 3 apps (apps/web-admin, apps/web-dashboard, apps/web-review) - and this census
105
+ is the average of all of them. That is a fine DIAGNOSIS and a poor system: a
106
+ light app and a dark one average into a palette that is neither.
107
+
108
+ synthesisui import --scope packages/ui the system comes from here
109
+ \`\`\`
110
+
111
+ Then measure again, narrowed:
112
+
113
+ \`\`\`
114
+ npx synthesisui import --dry --scope packages/ui
115
+ \`\`\`
116
+
117
+ **Do not import the average.** Three apps measured together produce a palette that belongs to
118
+ none of them, and it will look plausible - a real monorepo reported 36% coverage across 2797
119
+ files at the root and 47% across 114 in its shared package. The second number is the system;
120
+ the first is a diagnosis.
121
+
122
+ \`--scope\` narrows what is READ. The census still lands at \`<root>/_synthesisui/census.json\`
123
+ and records what it measured, so you always send the same path and there is never a second
124
+ file to pick between. Nothing is sent by \`--dry\`. Read the file.
83
125
 
84
126
  ### 2. Read what arithmetic cannot
85
127
 
@@ -124,21 +166,43 @@ about which one is the page. Read a screen and see.
124
166
  **\`fonts\` and \`concept\`** - the voice, and one paragraph on what this product is. The concept
125
167
  feeds every recommendation downstream, so a real one beats a generic one by a wide margin.
126
168
 
127
- ### 3. Send it
169
+ ### 3. Confirm once, then send
170
+
171
+ You have made three decisions on their behalf by now - **where** you measured, **what** it will
172
+ be called, and **which values** carry which meaning. Put all three in one place and ask. Not a
173
+ report that contains a question at the bottom: a question.
174
+
175
+ Offer the alternatives you actually considered, so choosing is a choice and not a veto:
176
+
177
+ \`\`\`
178
+ I'd create a system from packages/ui, called SignalUI, opening dark.
179
+
180
+ from packages/ui 114 files, 47% of its values already named
181
+ (apps/web-dashboard, apps/web-review and apps/web-admin
182
+ are the consumers; measuring all of them gave 36%, which
183
+ is the average of a light app and two dark ones)
184
+ called SignalUI their own name, from Introduction.mdx
185
+ brand #059aed their docs call Ocean the primary scale
186
+ opens dark data-theme="dark" in the root layout
187
+
188
+ This creates it on your account. Go ahead, or change any of these?
189
+ \`\`\`
190
+
191
+ Then, and only after they answer:
128
192
 
129
193
  \`\`\`
130
- npx synthesisui import --census _synthesisui/census.json --name "<their name for it>" [--registry <the one from step 0>]
194
+ npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--registry <from step 0>]
131
195
  \`\`\`
132
196
 
133
- **One real write, and it creates a system on their account.** Say what you are about to send
134
- and what it will be called before you run it.
197
+ **This is the one real write.** Everything before it is on their disk and costs nothing to redo.
135
198
 
136
- The CLI asks for a name unless \`--name\` is passed. It does **not** ask about the scheme when
137
- your reading already answered - a second question would only be a chance to contradict you.
199
+ The CLI asks for a name unless \`--name\` is passed, and it does **not** ask about the scheme when
200
+ your reading already answered - a second question would only be a chance to contradict you. So
201
+ if you skip the confirmation above, nobody is asked about the scope at all, and the first time
202
+ they learn where their system came from is when they open it.
138
203
 
139
- If it refuses, it will tell you which of the two problems it is: no session, an expired one, or
140
- a token issued by a different host than the one you are sending to. All three are recoverable
141
- from the census already on disk; none of them need a re-measure.
204
+ If it refuses, it says which of the three it is: no session, an expired one, or a token issued
205
+ by a different host. All are recoverable from the census on disk; none need a re-measure.
142
206
 
143
207
  ### 4. Tell them exactly what they have, and what is theirs to decide
144
208
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.60",
3
+ "version": "0.16.62",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {