@teamix-evo/mcp 0.3.0 → 0.4.2

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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +225 -178
  3. package/dist/cli.js.map +1 -1
  4. package/dist/data/adr/0001-three-layer-alignment.md +54 -0
  5. package/dist/data/adr/0002-package-naming.md +50 -0
  6. package/dist/data/adr/0003-update-strategy-tri-state.md +62 -0
  7. package/dist/data/adr/0004-cli-command-structure.md +61 -0
  8. package/dist/data/adr/0005-ui-no-variant.md +67 -0
  9. package/dist/data/adr/0006-ui-upgrade-no-baseline.md +67 -0
  10. package/dist/data/adr/0007-governance-docs-at-root.md +62 -0
  11. package/dist/data/adr/0008-eslint-visual-rules-warn-baseline.md +110 -0
  12. package/dist/data/adr/0009-registry-mcp-protocol-layer.md +87 -0
  13. package/dist/data/adr/0010-design-default-and-variants.md +319 -0
  14. package/dist/data/adr/0011-mcp-single-package-multi-group.md +169 -0
  15. package/dist/data/adr/0012-lint-shared-core.md +215 -0
  16. package/dist/data/adr/0013-skills-source-mirror.md +154 -0
  17. package/dist/data/adr/0014-ui-biz-ui-templates-tier.md +274 -0
  18. package/dist/data/adr/0015-skill-description-trigger-contract.md +122 -0
  19. package/dist/data/adr/0016-design-md-brand-charter.md +93 -0
  20. package/dist/data/adr/0017-mcp-design-group.md +107 -0
  21. package/dist/data/adr/0018-ai-context-routing.md +112 -0
  22. package/dist/data/adr/0019-project-upgrade-flow.md +156 -0
  23. package/dist/data/adr/0020-design-to-tokens-skill-fusion.md +139 -0
  24. package/dist/data/adr/0021-semantic-color-api-unification.md +99 -0
  25. package/dist/data/adr/0022-preferences-css-boundary.md +75 -0
  26. package/dist/data/adr/0023-cursor-pointer-explicit-in-component-source.md +70 -0
  27. package/dist/data/adr/0024-scoped-css-radix-state-conflict.md +99 -0
  28. package/dist/data/adr/0025-component-props-explicit-declaration.md +144 -0
  29. package/dist/data/adr/0026-component-level-token-alias.md +107 -0
  30. package/dist/data/adr/0027-component-visual-token-alignment.md +127 -0
  31. package/dist/data/adr/0028-ui-component-categorization.md +111 -0
  32. package/dist/data/adr/0029-input-split-and-prefix-suffix-removal.md +62 -0
  33. package/dist/data/adr/0030-skill-uni-manager-uplift.md +56 -0
  34. package/dist/data/adr/0031-skill-templates-decoupling.md +77 -0
  35. package/dist/data/adr/0032-opentrek-v4.1-brand-token-alignment.md +129 -0
  36. package/dist/data/adr/0033-entry-skill-global-only-scope.md +64 -0
  37. package/dist/data/adr/0034-skills-cli-verb-alignment.md +61 -0
  38. package/dist/data/adr/0035-skills-update-scope-and-lock-gates.md +69 -0
  39. package/dist/data/adr/README.md +75 -0
  40. package/dist/data/adr/_template.md +36 -0
  41. package/dist/index.d.ts +64 -59
  42. package/dist/index.js +232 -186
  43. package/dist/index.js.map +1 -1
  44. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -37,10 +37,15 @@ function resolveManifestPath(startDir = process.cwd()) {
37
37
  }
38
38
  throw new Error(
39
39
  [
40
- "Could not locate @teamix-evo/ui/manifest.json.",
41
- `Tried env TEAMIX_EVO_UI_MANIFEST + walking up from ${startDir}.`,
42
- "Either install @teamix-evo/ui in the consumer project, or set TEAMIX_EVO_UI_MANIFEST to an absolute path."
43
- ].join(" ")
40
+ "Could not locate @teamix-evo/ui/manifest.json \u2014 the UI registry is not installed in this project.",
41
+ "",
42
+ "Fix one of:",
43
+ ` 1. Install the package: pnpm add @teamix-evo/ui (or npm/yarn equivalent)`,
44
+ ` 2. Point env at an existing manifest: export TEAMIX_EVO_UI_MANIFEST=/abs/path/to/manifest.json`,
45
+ "",
46
+ `Searched: $TEAMIX_EVO_UI_MANIFEST, then walked up from ${startDir} looking for node_modules/@teamix-evo/ui/manifest.json (16 levels).`,
47
+ "Note: MCP currently has no online fallback \u2014 it does not fetch from the npm registry / unpkg."
48
+ ].join("\n")
44
49
  );
45
50
  }
46
51
  function loadManifest(path) {
@@ -83,33 +88,46 @@ function parseFrontmatter(text) {
83
88
 
84
89
  // src/groups/registry.ts
85
90
  var ListComponentsInput = z.object({
86
- status: z.enum(["stable", "experimental", "deprecated"]).optional()
91
+ status: z.enum(["stable", "experimental", "deprecated"]).optional(),
92
+ /**
93
+ * Include archived entries from `manifest.deprecatedEntries` (ADR 0028).
94
+ * Equivalent to `status: "deprecated"` when set alone, but composes with
95
+ * other filters: e.g. unset status + `includeDeprecated: true` returns
96
+ * active + deprecated together.
97
+ */
98
+ includeDeprecated: z.boolean().optional()
87
99
  });
88
100
  var GetComponentMetaInput = z.object({
89
101
  id: z.string().min(1)
90
102
  });
91
103
  var FindComponentsInput = z.object({
92
104
  query: z.string().min(1),
93
- limit: z.number().int().positive().max(100).optional()
105
+ limit: z.number().int().positive().max(100).optional(),
106
+ /** Include archived deprecated entries in matches (ADR 0028). */
107
+ includeDeprecated: z.boolean().optional()
94
108
  });
95
109
  var TOOLS = [
96
110
  {
97
111
  name: "list_components",
98
- description: "List all UI components in the @teamix-evo/ui registry. Optionally filter by status (stable / experimental / deprecated). Returns id, name, description, status, registryDependencies \u2014 small enough for the model to scan whole.",
112
+ description: 'List UI components in the @teamix-evo/ui registry. By default excludes archived `deprecatedEntries` (ADR 0028) \u2014 pass `includeDeprecated: true` (or `status: "deprecated"`) to inspect them, e.g. for upgrade audits. Returns id, name, description, status, registryDependencies \u2014 small enough for the model to scan whole.',
99
113
  inputSchema: {
100
114
  type: "object",
101
115
  properties: {
102
116
  status: {
103
117
  type: "string",
104
118
  enum: ["stable", "experimental", "deprecated"],
105
- description: "Filter by maturity status."
119
+ description: "Filter active entries by maturity status. `deprecated` returns the archived `deprecatedEntries` list."
120
+ },
121
+ includeDeprecated: {
122
+ type: "boolean",
123
+ description: "When true, merge archived `deprecatedEntries` into the result (ADR 0028)."
106
124
  }
107
125
  }
108
126
  }
109
127
  },
110
128
  {
111
129
  name: "get_component_meta",
112
- description: "Fetch the full registry entry + parsed meta.md for a single component by id. Returns props schema reference, registryDependencies, npm dependencies, AI generation rules, and the component description.",
130
+ description: "Fetch the full registry entry + parsed meta.md for a single component by id. Searches both active `entries` and archived `deprecatedEntries`; deprecated hits are flagged via `archived: true` in the payload. Returns props schema reference, registryDependencies, npm dependencies, AI generation rules, and the component description.",
113
131
  inputSchema: {
114
132
  type: "object",
115
133
  properties: {
@@ -123,7 +141,7 @@ var TOOLS = [
123
141
  },
124
142
  {
125
143
  name: "find_components",
126
- description: 'Substring match over component id / name / description. Use when you need a component but don\'t know its exact id (e.g. "find a component supporting async search and pagination"). Returns up to `limit` matches (default 10). Note: substring match is a v0.1 implementation; semantic search is planned for v0.7 (see ADR 0009).',
144
+ description: 'Substring match over component id / name / description. Use when you need a component but don\'t know its exact id (e.g. "find a component supporting async search and pagination"). Excludes archived `deprecatedEntries` by default \u2014 pass `includeDeprecated: true` to widen the search (ADR 0028). Returns up to `limit` matches (default 10). Note: substring match is a v0.1 implementation; semantic search is planned for v0.7 (see ADR 0009).',
127
145
  inputSchema: {
128
146
  type: "object",
129
147
  properties: {
@@ -136,6 +154,10 @@ var TOOLS = [
136
154
  minimum: 1,
137
155
  maximum: 100,
138
156
  description: "Max matches to return (default 10)."
157
+ },
158
+ includeDeprecated: {
159
+ type: "boolean",
160
+ description: "When true, also search archived `deprecatedEntries` (ADR 0028)."
139
161
  }
140
162
  },
141
163
  required: ["query"]
@@ -155,6 +177,9 @@ function pickListEntry(entry) {
155
177
  registryDependencies: entry.registryDependencies ?? []
156
178
  };
157
179
  }
180
+ function pickArchivedEntry(entry) {
181
+ return { ...pickListEntry(entry), archived: true };
182
+ }
158
183
  function createRegistryGroup(opts = {}) {
159
184
  let cache = opts.loaded ?? null;
160
185
  function getManifest() {
@@ -169,30 +194,44 @@ function createRegistryGroup(opts = {}) {
169
194
  if (name === "list_components") {
170
195
  const input = ListComponentsInput.parse(args ?? {});
171
196
  const { manifest } = getManifest();
172
- const entries = manifest.entries.filter((e) => e.type === "component").filter((e) => input.status ? e.status === input.status : true).map(pickListEntry);
197
+ const archived = manifest.deprecatedEntries ?? [];
198
+ if (input.status === "deprecated") {
199
+ const entries = archived.filter((e) => e.type === "component").map(pickArchivedEntry);
200
+ return {
201
+ content: [{ type: "text", text: JSON.stringify(entries, null, 2) }]
202
+ };
203
+ }
204
+ const active = manifest.entries.filter((e) => e.type === "component").filter((e) => input.status ? e.status === input.status : true).map(pickListEntry);
205
+ const merged = input.includeDeprecated ? [
206
+ ...active,
207
+ ...archived.filter((e) => e.type === "component").map(pickArchivedEntry)
208
+ ] : active;
173
209
  return {
174
- content: [{ type: "text", text: JSON.stringify(entries, null, 2) }]
210
+ content: [{ type: "text", text: JSON.stringify(merged, null, 2) }]
175
211
  };
176
212
  }
177
213
  if (name === "get_component_meta") {
178
214
  const input = GetComponentMetaInput.parse(args);
179
215
  const { manifest, rootDir } = getManifest();
180
216
  const entry = manifest.entries.find((e) => e.id === input.id);
181
- if (!entry) {
217
+ const archived = entry ? null : (manifest.deprecatedEntries ?? []).find((e) => e.id === input.id) ?? null;
218
+ const found = entry ?? archived;
219
+ if (!found) {
182
220
  return {
183
221
  content: [
184
222
  {
185
223
  type: "text",
186
- text: `Component not found: ${input.id}. Use list_components to discover ids.`
224
+ text: `Component not found: ${input.id}. Use list_components to discover ids (pass includeDeprecated: true to widen the search).`
187
225
  }
188
226
  ],
189
227
  isError: true
190
228
  };
191
229
  }
192
- const meta = loadMeta(entry, rootDir);
230
+ const meta = loadMeta(found, rootDir);
193
231
  const payload = {
194
- entry,
195
- meta: meta ?? null
232
+ entry: found,
233
+ meta: meta ?? null,
234
+ archived: archived !== null
196
235
  };
197
236
  return {
198
237
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
@@ -203,9 +242,23 @@ function createRegistryGroup(opts = {}) {
203
242
  const limit = input.limit ?? 10;
204
243
  const q = input.query.toLowerCase();
205
244
  const { manifest } = getManifest();
206
- const matches = manifest.entries.filter((e) => e.type === "component").filter((e) => {
207
- return e.id.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q);
208
- }).slice(0, limit).map(pickListEntry);
245
+ const pool = input.includeDeprecated ? [
246
+ ...manifest.entries.map((e) => ({ entry: e, archived: false })),
247
+ ...(manifest.deprecatedEntries ?? []).map((e) => ({
248
+ entry: e,
249
+ archived: true
250
+ }))
251
+ ] : manifest.entries.map((e) => ({ entry: e, archived: false }));
252
+ const matches = pool.filter(({ entry }) => entry.type === "component").filter(({ entry, archived: a }) => {
253
+ if (!input.includeDeprecated && !a && entry.status === "deprecated") {
254
+ return false;
255
+ }
256
+ return true;
257
+ }).filter(({ entry }) => {
258
+ return entry.id.toLowerCase().includes(q) || entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q);
259
+ }).slice(0, limit).map(
260
+ ({ entry, archived: a }) => a ? pickArchivedEntry(entry) : pickListEntry(entry)
261
+ );
209
262
  return {
210
263
  content: [
211
264
  {
@@ -235,11 +288,18 @@ import { z as z2 } from "zod";
235
288
  // src/adr-loader.ts
236
289
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
237
290
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
291
+ import { fileURLToPath } from "url";
238
292
  function resolveAdrRoot(startDir = process.cwd()) {
239
293
  const envPath = process.env.TEAMIX_EVO_ADR_ROOT;
240
294
  if (envPath && existsSync2(envPath)) {
241
295
  return envPath;
242
296
  }
297
+ try {
298
+ const here = dirname2(fileURLToPath(import.meta.url));
299
+ const bundled = join2(here, "data", "adr");
300
+ if (existsSync2(bundled)) return bundled;
301
+ } catch {
302
+ }
243
303
  let dir = resolve2(startDir);
244
304
  for (let i = 0; i < 16; i++) {
245
305
  const candidates = [
@@ -256,8 +316,8 @@ function resolveAdrRoot(startDir = process.cwd()) {
256
316
  throw new Error(
257
317
  [
258
318
  "Could not locate ADR directory.",
259
- `Tried env TEAMIX_EVO_ADR_ROOT + walking up from ${startDir} for docs/adr/ or .teamix-evo/adr/.`,
260
- "Either run from a teamix-evo monorepo, or set TEAMIX_EVO_ADR_ROOT to an absolute path."
319
+ `Tried env TEAMIX_EVO_ADR_ROOT, mcp-bundled snapshot, and walked up from ${startDir} for docs/adr/ or .teamix-evo/adr/.`,
320
+ "Either install @teamix-evo/mcp (carries ADR snapshot) or set TEAMIX_EVO_ADR_ROOT to an absolute path."
261
321
  ].join(" ")
262
322
  );
263
323
  }
@@ -541,8 +601,8 @@ function resolveSkillsRoot(startDir = process.cwd()) {
541
601
  throw new Error(
542
602
  [
543
603
  "Could not locate @teamix-evo/skills root.",
544
- `Tried env TEAMIX_EVO_SKILLS_ROOT + walking up from ${startDir}.`,
545
- "Either install @teamix-evo/skills or run from a monorepo, or set TEAMIX_EVO_SKILLS_ROOT to an absolute path."
604
+ `Tried env TEAMIX_EVO_SKILLS_ROOT and walked up from ${startDir} for packages/skills/ or node_modules/@teamix-evo/skills/.`,
605
+ "Install @teamix-evo/skills as a devDependency in your project (the scaffold adds it automatically)."
546
606
  ].join(" ")
547
607
  );
548
608
  }
@@ -792,20 +852,20 @@ function createSkillsGroup(opts = {}) {
792
852
  };
793
853
  }
794
854
 
795
- // src/groups/design.ts
855
+ // src/groups/tokens.ts
796
856
  import { z as z4 } from "zod";
797
857
 
798
- // src/design-loader.ts
799
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
858
+ // src/tokens-loader.ts
859
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
800
860
  import { dirname as dirname4, join as join4, resolve as resolve4 } from "path";
801
- function resolveDesignRoot(startDir = process.cwd()) {
802
- const envPath = process.env.TEAMIX_EVO_DESIGN_ROOT;
861
+ function resolveTokensRoot(startDir = process.cwd()) {
862
+ const envPath = process.env.TEAMIX_EVO_TOKENS_ROOT;
803
863
  if (envPath && existsSync4(envPath)) {
804
864
  return envPath;
805
865
  }
806
866
  let dir = resolve4(startDir);
807
867
  for (let i = 0; i < 16; i++) {
808
- const candidate = join4(dir, ".teamix-evo", "design");
868
+ const candidate = join4(dir, ".teamix-evo");
809
869
  if (existsSync4(candidate)) return candidate;
810
870
  const parent = dirname4(dir);
811
871
  if (parent === dir) break;
@@ -813,11 +873,11 @@ function resolveDesignRoot(startDir = process.cwd()) {
813
873
  }
814
874
  return null;
815
875
  }
816
- function loadDesign(rootDir) {
817
- const root = rootDir ?? resolveDesignRoot();
876
+ function loadTokens(rootDir) {
877
+ const root = rootDir ?? resolveTokensRoot();
818
878
  if (!root) return null;
819
879
  let variant = null;
820
- const lockPath = join4(root, "pack.lock.json");
880
+ const lockPath = join4(root, "tokens-lock.json");
821
881
  if (existsSync4(lockPath)) {
822
882
  try {
823
883
  const raw = JSON.parse(readFileSync4(lockPath, "utf-8"));
@@ -828,47 +888,17 @@ function loadDesign(rootDir) {
828
888
  }
829
889
  return { rootDir: root, variant };
830
890
  }
831
- var PRINCIPLE_HEADING_RE = /^#{2,3}\s+(P\d+)\s*[·:]\s*(.+?)\s*$/;
832
- function readPrinciples(loaded) {
833
- const filePath = join4(loaded.rootDir, "philosophy", "principles.md");
834
- if (!existsSync4(filePath)) {
835
- return {
836
- principles: [],
837
- sourcePath: null,
838
- note: "philosophy/principles.md not found in this project. Run `teamix-evo design init <variant>` to install it."
839
- };
840
- }
841
- const text = readFileSync4(filePath, "utf-8");
842
- const lines = text.split(/\r?\n/);
843
- const out = [];
844
- for (let i = 0; i < lines.length; i++) {
845
- const m = lines[i].match(PRINCIPLE_HEADING_RE);
846
- if (!m) continue;
847
- let def = "";
848
- for (let j = i + 1; j < lines.length && j < i + 8; j++) {
849
- const candidate = lines[j].trim();
850
- if (!candidate) continue;
851
- if (/^#{1,6}\s/.test(candidate)) break;
852
- def = candidate.replace(/^[*_>\s-]+/, "").trim();
853
- break;
854
- }
855
- out.push({ id: m[1], name: m[2].trim(), oneLineDef: def });
856
- }
857
- return { principles: out, sourcePath: filePath };
858
- }
859
891
  function resolveTokensDir(loaded) {
860
892
  const projectRoot = dirname4(loaded.rootDir);
861
- const lifted = join4(projectRoot, "tokens");
862
- if (existsSync4(lifted)) return lifted;
863
- const legacy = join4(loaded.rootDir, "foundations", "tokens");
864
- if (existsSync4(legacy)) return legacy;
893
+ const rootTokens = join4(projectRoot, "tokens");
894
+ if (existsSync4(rootTokens)) return rootTokens;
865
895
  return null;
866
896
  }
867
897
  function readTokens(loaded) {
868
898
  const tokensDir = resolveTokensDir(loaded);
869
899
  const out = { sources: [] };
870
900
  if (!tokensDir) {
871
- out.note = ".teamix-evo/tokens/ not found in this project (and no legacy foundations/tokens/).";
901
+ out.note = "tokens/ not found at project root.";
872
902
  return out;
873
903
  }
874
904
  const tryJson = (rel) => {
@@ -888,169 +918,186 @@ function readTokens(loaded) {
888
918
  return readFileSync4(fp, "utf-8");
889
919
  };
890
920
  out.base = tryJson("base.tokens.json");
891
- out.semantic = tryJson("semantic.tokens.json");
892
921
  out.themeCss = tryText("tokens.theme.css");
893
922
  out.overridesCss = tryText("tokens.overrides.css");
894
923
  return out;
895
924
  }
896
- var VARIANT_PREFIX_RE = /^(cloud|opentrek|uni-manager|enterprise)-/i;
897
- function readPatternIndex(loaded) {
898
- const patternsDir = join4(loaded.rootDir, "patterns");
899
- if (!existsSync4(patternsDir)) return [];
925
+ var W3C_KEYS = /* @__PURE__ */ new Set(["$value", "$type", "$description", "$extensions"]);
926
+ function isPlainObject(v) {
927
+ return typeof v === "object" && v !== null && !Array.isArray(v);
928
+ }
929
+ function flattenTokens(base) {
930
+ if (!isPlainObject(base)) return [];
900
931
  const out = [];
901
- for (const name of readdirSync3(patternsDir)) {
902
- if (!name.endsWith(".md")) continue;
903
- const fp = join4(patternsDir, name);
904
- try {
905
- if (!statSync2(fp).isFile()) continue;
906
- } catch {
907
- continue;
932
+ for (const [category, group] of Object.entries(base)) {
933
+ if (category.startsWith("$")) continue;
934
+ if (!isPlainObject(group)) continue;
935
+ for (const [name, leaf] of Object.entries(group)) {
936
+ if (name.startsWith("$")) continue;
937
+ if (!isPlainObject(leaf)) continue;
938
+ const type = typeof leaf.$type === "string" ? leaf.$type : void 0;
939
+ const values = {};
940
+ let description = typeof leaf.$description === "string" ? leaf.$description : void 0;
941
+ if (typeof leaf.$value === "string") {
942
+ values.default = leaf.$value;
943
+ }
944
+ for (const [modeKey, modeVal] of Object.entries(leaf)) {
945
+ if (W3C_KEYS.has(modeKey)) continue;
946
+ if (!isPlainObject(modeVal)) continue;
947
+ if (typeof modeVal.$value === "string") {
948
+ values[modeKey] = modeVal.$value;
949
+ }
950
+ if (!description && typeof modeVal.$description === "string") {
951
+ description = modeVal.$description;
952
+ }
953
+ }
954
+ if (Object.keys(values).length === 0) continue;
955
+ out.push({
956
+ category,
957
+ name,
958
+ ...type ? { type } : {},
959
+ values,
960
+ ...description ? { description } : {}
961
+ });
908
962
  }
909
- const stem = name.replace(/\.md$/, "");
910
- const text = readFileSync4(fp, "utf-8");
911
- const titleMatch = text.match(/^#\s+(.+?)\s*$/m);
912
- out.push({
913
- id: stem,
914
- title: titleMatch ? titleMatch[1] : stem,
915
- sourcePath: fp,
916
- variantSpecific: VARIANT_PREFIX_RE.test(stem)
917
- });
918
963
  }
919
- return out.sort((a, b) => a.id.localeCompare(b.id));
964
+ return out;
920
965
  }
921
- function readPatternContent(loaded, id) {
922
- const stem = id.replace(/\.md$/, "");
923
- const fp = join4(loaded.rootDir, "patterns", `${stem}.md`);
924
- if (!existsSync4(fp)) return null;
966
+ function listTokens(loaded) {
967
+ const snapshot = readTokens(loaded);
968
+ if (!snapshot.base) {
969
+ return {
970
+ variant: loaded.variant,
971
+ tokens: [],
972
+ sources: snapshot.sources,
973
+ note: snapshot.note ?? "base.tokens.json not found."
974
+ };
975
+ }
925
976
  return {
926
- id: stem,
927
- sourcePath: fp,
928
- content: readFileSync4(fp, "utf-8")
977
+ variant: loaded.variant,
978
+ tokens: flattenTokens(snapshot.base),
979
+ sources: snapshot.sources
929
980
  };
930
981
  }
931
- function readBrand(loaded) {
932
- const brandDir = join4(loaded.rootDir, "brand");
933
- const out = {};
934
- if (!existsSync4(brandDir)) {
935
- out.note = "brand/ not present \u2014 this design variant does not ship brand-specific tone / voice / examples.";
936
- return out;
982
+ function searchTokens(loaded, query, limit = 20) {
983
+ const list = listTokens(loaded);
984
+ const q = query.trim().toLowerCase();
985
+ if (!q) {
986
+ return {
987
+ variant: list.variant,
988
+ query,
989
+ matches: [],
990
+ sources: list.sources,
991
+ note: "query is empty."
992
+ };
937
993
  }
938
- for (const key of ["tone", "voice", "examples"]) {
939
- const fp = join4(brandDir, `${key}.md`);
940
- if (existsSync4(fp)) {
941
- out[key] = { sourcePath: fp, content: readFileSync4(fp, "utf-8") };
942
- }
994
+ if (list.note && list.tokens.length === 0) {
995
+ return {
996
+ variant: list.variant,
997
+ query,
998
+ matches: [],
999
+ sources: list.sources,
1000
+ note: list.note
1001
+ };
943
1002
  }
944
- return out;
1003
+ const hits = list.tokens.filter((t) => {
1004
+ if (t.category.toLowerCase().includes(q)) return true;
1005
+ if (t.name.toLowerCase().includes(q)) return true;
1006
+ if (t.description?.toLowerCase().includes(q)) return true;
1007
+ return Object.values(t.values).some((v) => v.toLowerCase().includes(q));
1008
+ });
1009
+ hits.sort((a, b) => {
1010
+ const c = a.category.localeCompare(b.category);
1011
+ return c !== 0 ? c : a.name.localeCompare(b.name);
1012
+ });
1013
+ return {
1014
+ variant: list.variant,
1015
+ query,
1016
+ matches: hits.slice(0, limit),
1017
+ sources: list.sources
1018
+ };
945
1019
  }
946
1020
 
947
- // src/groups/design.ts
1021
+ // src/groups/tokens.ts
948
1022
  var EmptyInput = z4.object({});
949
- var GetPatternInput = z4.object({
950
- id: z4.string().min(1)
1023
+ var SearchInput = z4.object({
1024
+ query: z4.string().min(1),
1025
+ limit: z4.number().int().min(1).max(100).optional()
951
1026
  });
952
1027
  var TOOLS4 = [
953
1028
  {
954
- name: "design_list_principles",
955
- description: 'List the design principles defined by the consumer project\'s installed design variant. Reads `.teamix-evo/design/philosophy/principles.md` and parses headings of the form `## P1 \xB7 Name`. Returns id, display name, and a one-line definition for each. Use this when the user asks about "the four principles", "design philosophy", or to ground a design review in the variant\'s stated values. Returns `{ principles: [], note: "..." }` if the project hasn\'t installed a design pack.',
1029
+ name: "tokens_get",
1030
+ description: "Fetch the consumer project's design tokens \u2014 parsed JSON from `tokens/base.tokens.json`, plus raw text of `tokens.theme.css` (variant theme) and `tokens.overrides.css` (user-owned overrides) when present. Use when AI needs to know what semantic colors / spacing / radii are available before writing component styles. JSON is for introspection; CSS is for copy-paste. Each result lists `sources` so you can cite paths.",
956
1031
  inputSchema: { type: "object", properties: {} }
957
1032
  },
958
1033
  {
959
- name: "design_get_tokens",
960
- description: "Fetch the consumer project's design tokens \u2014 parsed JSON from `.teamix-evo/tokens/base.tokens.json` and `semantic.tokens.json`, plus raw text of `tokens.theme.css` (variant theme) and `tokens.overrides.css` (user-owned overrides) when present. Use when AI needs to know what semantic colors / spacing / radii are available before writing component styles. JSON is for introspection; CSS is for copy-paste. Each tool result lists `sources` so you can cite paths.",
1034
+ name: "tokens_list",
1035
+ description: "List ALL design tokens as a flat array of entries `{ category, name, type, values, description }`. Categories come from top-level keys of base.tokens.json (e.g. `color`, `radius`, `spacing`). Each entry exposes resolved values keyed by mode (e.g. `light` / `dark` / `default`). Use this BEFORE writing styles to discover what semantic names are available \u2014 much cheaper than parsing the whole CSS file. Use `tokens_search` instead when you have a keyword.",
961
1036
  inputSchema: { type: "object", properties: {} }
962
1037
  },
963
1038
  {
964
- name: "design_list_patterns",
965
- description: "Index every pattern in `.teamix-evo/design/patterns/*.md` (page types, journeys, flows, plus variant-specific markers like `cloud-*`, `opentrek-*`, `uni-manager-*`). Returns `{ id, title, sourcePath, variantSpecific }` for each. Use this before `design_get_pattern` to discover which patterns exist. When both a baseline (`page-types`) and a variant-specific (`cloud-page-types`) entry are available, prefer the variant-specific one for projects that match.",
966
- inputSchema: { type: "object", properties: {} }
967
- },
968
- {
969
- name: "design_get_pattern",
970
- description: "Fetch the full markdown body of one pattern by id (filename stem, e.g. `page-types` or `cloud-page-types`). Use after `design_list_patterns` discovered the id. Returns the raw markdown plus `sourcePath`. Returns isError if the id does not exist \u2014 call `design_list_patterns` first to discover available ids.",
1039
+ name: "tokens_search",
1040
+ description: 'Substring match across token category / name / description / values (case-insensitive). Use when you remember a phrase ("primary" / "destructive" / "card-gap") but not the exact path. Returns up to `limit` matches (default 20). Combine with `tokens_get` if you need raw CSS text after locating an entry.',
971
1041
  inputSchema: {
972
1042
  type: "object",
973
1043
  properties: {
974
- id: {
975
- type: "string",
976
- description: 'Pattern id \u2014 filename stem under `patterns/`, e.g. "page-types", "cloud-page-types".'
1044
+ query: { type: "string", description: "Free-text query." },
1045
+ limit: {
1046
+ type: "integer",
1047
+ minimum: 1,
1048
+ maximum: 100,
1049
+ description: "Max matches to return (default 20)."
977
1050
  }
978
1051
  },
979
- required: ["id"]
1052
+ required: ["query"]
980
1053
  }
981
- },
982
- {
983
- name: "design_get_brand",
984
- description: "Fetch the consumer project's variant-specific brand voice \u2014 `brand/tone.md`, `brand/voice.md`, `brand/examples.md`. Each is optional; the response contains only the files that exist. Some variants (like `_template` or `default`) ship no brand content \u2014 that's not an error, the response simply lists no entries. Use this when generating or reviewing user-facing copy, error messages, empty states, or onboarding text where brand voice matters.",
985
- inputSchema: { type: "object", properties: {} }
986
1054
  }
987
1055
  ];
988
1056
  var TOOL_NAMES4 = new Set(TOOLS4.map((t) => t.name));
989
- var NO_DESIGN_NOTE = ".teamix-evo/design/ not found in this project. Run `npx teamix-evo design init <variant>` to install a design pack.";
990
- function createDesignGroup(opts = {}) {
1057
+ var NO_TOKENS_NOTE = "tokens/ directory not found in this project. Run `npx teamix-evo tokens init <variant>` to install design tokens.";
1058
+ function createTokensGroup(opts = {}) {
991
1059
  let cache = opts.loaded === void 0 ? void 0 : opts.loaded;
992
- function getDesign() {
993
- if (cache === void 0) cache = loadDesign(opts.rootDir);
1060
+ function getLoaded() {
1061
+ if (cache === void 0) cache = loadTokens(opts.rootDir);
994
1062
  return cache;
995
1063
  }
996
1064
  return {
997
- name: "design",
1065
+ name: "tokens",
998
1066
  tools: TOOLS4,
999
1067
  async handle(name, args) {
1000
1068
  if (!TOOL_NAMES4.has(name)) return void 0;
1001
- const loaded = getDesign();
1002
- if (name === "design_list_principles") {
1069
+ const loaded = getLoaded();
1070
+ if (name === "tokens_get") {
1003
1071
  EmptyInput.parse(args ?? {});
1004
1072
  if (!loaded) {
1005
- return jsonResult({ principles: [], sourcePath: null, note: NO_DESIGN_NOTE });
1006
- }
1007
- return jsonResult(readPrinciples(loaded));
1008
- }
1009
- if (name === "design_get_tokens") {
1010
- EmptyInput.parse(args ?? {});
1011
- if (!loaded) {
1012
- return jsonResult({ sources: [], note: NO_DESIGN_NOTE });
1073
+ return jsonResult({ sources: [], note: NO_TOKENS_NOTE });
1013
1074
  }
1014
1075
  return jsonResult(readTokens(loaded));
1015
1076
  }
1016
- if (name === "design_list_patterns") {
1077
+ if (name === "tokens_list") {
1017
1078
  EmptyInput.parse(args ?? {});
1018
1079
  if (!loaded) {
1019
- return jsonResult({ patterns: [], note: NO_DESIGN_NOTE });
1020
- }
1021
- return jsonResult({
1022
- variant: loaded.variant,
1023
- patterns: readPatternIndex(loaded)
1024
- });
1025
- }
1026
- if (name === "design_get_pattern") {
1027
- const input = GetPatternInput.parse(args);
1028
- if (!loaded) {
1029
- return {
1030
- content: [{ type: "text", text: NO_DESIGN_NOTE }],
1031
- isError: true
1032
- };
1033
- }
1034
- const result = readPatternContent(loaded, input.id);
1035
- if (!result) {
1036
- return {
1037
- content: [
1038
- {
1039
- type: "text",
1040
- text: `Pattern not found: ${input.id}. Use design_list_patterns to discover available ids.`
1041
- }
1042
- ],
1043
- isError: true
1044
- };
1080
+ return jsonResult({
1081
+ variant: null,
1082
+ tokens: [],
1083
+ sources: [],
1084
+ note: NO_TOKENS_NOTE
1085
+ });
1045
1086
  }
1046
- return jsonResult(result);
1087
+ return jsonResult(listTokens(loaded));
1047
1088
  }
1048
- if (name === "design_get_brand") {
1049
- EmptyInput.parse(args ?? {});
1089
+ if (name === "tokens_search") {
1090
+ const parsed = SearchInput.parse(args ?? {});
1050
1091
  if (!loaded) {
1051
- return jsonResult({ note: NO_DESIGN_NOTE });
1092
+ return jsonResult({
1093
+ variant: null,
1094
+ query: parsed.query,
1095
+ matches: [],
1096
+ sources: [],
1097
+ note: NO_TOKENS_NOTE
1098
+ });
1052
1099
  }
1053
- return jsonResult(readBrand(loaded));
1100
+ return jsonResult(searchTokens(loaded, parsed.query, parsed.limit));
1054
1101
  }
1055
1102
  return void 0;
1056
1103
  }
@@ -1068,7 +1115,7 @@ function createServer(opts = {}) {
1068
1115
  createRegistryGroup(opts.registry),
1069
1116
  createAdrGroup(opts.adr),
1070
1117
  createSkillsGroup(opts.skills),
1071
- createDesignGroup(opts.design)
1118
+ createTokensGroup(opts.tokens)
1072
1119
  // Future: createScenarioGroup(opts.scenario), // ADR 0011, v0.8
1073
1120
  ];
1074
1121
  const seen = /* @__PURE__ */ new Set();
@@ -1115,27 +1162,26 @@ function createServer(opts = {}) {
1115
1162
  }
1116
1163
  export {
1117
1164
  createAdrGroup,
1118
- createDesignGroup,
1119
1165
  createRegistryGroup,
1120
1166
  createServer,
1121
1167
  createSkillsGroup,
1168
+ createTokensGroup,
1122
1169
  findAdr,
1123
1170
  findSkill,
1171
+ flattenTokens,
1172
+ listTokens,
1124
1173
  loadAdrContent,
1125
1174
  loadAdrIndex,
1126
- loadDesign,
1127
1175
  loadManifest,
1128
1176
  loadMeta,
1129
1177
  loadSkillContent,
1130
1178
  loadSkillsManifest,
1131
- readBrand,
1132
- readPatternContent,
1133
- readPatternIndex,
1134
- readPrinciples,
1179
+ loadTokens,
1135
1180
  readTokens,
1136
1181
  resolveAdrRoot,
1137
- resolveDesignRoot,
1138
1182
  resolveManifestPath,
1139
- resolveSkillsRoot
1183
+ resolveSkillsRoot,
1184
+ resolveTokensRoot,
1185
+ searchTokens
1140
1186
  };
1141
1187
  //# sourceMappingURL=index.js.map