@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/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  | Group | Status | Tools | Naming |
19
19
  | --- | --- | --- | --- |
20
20
  | **registry** | ✅ shipping | `list_components` / `get_component_meta` / `find_components` | unprefixed (historical exception, see ADR 0011 §5) |
21
- | **design** | ✅ shipping | `design_list_principles` / `design_get_tokens` / `design_list_patterns` / `design_get_pattern` / `design_get_brand` | `<group>_<verb>_<noun>` |
21
+ | **tokens** | ✅ shipping | `tokens_get` / `tokens_list` / `tokens_search` | `<group>_<verb>_<noun>` |
22
22
  | **skills** | ✅ shipping | `skills_list` / `skills_get` / `skills_find` | `<group>_<verb>_<noun>` |
23
23
  | **adr** | ✅ shipping | `adr_list` / `adr_get` / `adr_find` | `<group>_<verb>_<noun>` |
24
24
  | **scenario** | ⏸ v0.8 | `scenario_list` / `scenario_get_pack` | `<group>_<verb>_<noun>` |
package/dist/cli.js CHANGED
@@ -40,10 +40,15 @@ function resolveManifestPath(startDir = process.cwd()) {
40
40
  }
41
41
  throw new Error(
42
42
  [
43
- "Could not locate @teamix-evo/ui/manifest.json.",
44
- `Tried env TEAMIX_EVO_UI_MANIFEST + walking up from ${startDir}.`,
45
- "Either install @teamix-evo/ui in the consumer project, or set TEAMIX_EVO_UI_MANIFEST to an absolute path."
46
- ].join(" ")
43
+ "Could not locate @teamix-evo/ui/manifest.json \u2014 the UI registry is not installed in this project.",
44
+ "",
45
+ "Fix one of:",
46
+ ` 1. Install the package: pnpm add @teamix-evo/ui (or npm/yarn equivalent)`,
47
+ ` 2. Point env at an existing manifest: export TEAMIX_EVO_UI_MANIFEST=/abs/path/to/manifest.json`,
48
+ "",
49
+ `Searched: $TEAMIX_EVO_UI_MANIFEST, then walked up from ${startDir} looking for node_modules/@teamix-evo/ui/manifest.json (16 levels).`,
50
+ "Note: MCP currently has no online fallback \u2014 it does not fetch from the npm registry / unpkg."
51
+ ].join("\n")
47
52
  );
48
53
  }
49
54
  function loadManifest(path) {
@@ -86,33 +91,46 @@ function parseFrontmatter(text) {
86
91
 
87
92
  // src/groups/registry.ts
88
93
  var ListComponentsInput = z.object({
89
- status: z.enum(["stable", "experimental", "deprecated"]).optional()
94
+ status: z.enum(["stable", "experimental", "deprecated"]).optional(),
95
+ /**
96
+ * Include archived entries from `manifest.deprecatedEntries` (ADR 0028).
97
+ * Equivalent to `status: "deprecated"` when set alone, but composes with
98
+ * other filters: e.g. unset status + `includeDeprecated: true` returns
99
+ * active + deprecated together.
100
+ */
101
+ includeDeprecated: z.boolean().optional()
90
102
  });
91
103
  var GetComponentMetaInput = z.object({
92
104
  id: z.string().min(1)
93
105
  });
94
106
  var FindComponentsInput = z.object({
95
107
  query: z.string().min(1),
96
- limit: z.number().int().positive().max(100).optional()
108
+ limit: z.number().int().positive().max(100).optional(),
109
+ /** Include archived deprecated entries in matches (ADR 0028). */
110
+ includeDeprecated: z.boolean().optional()
97
111
  });
98
112
  var TOOLS = [
99
113
  {
100
114
  name: "list_components",
101
- 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.",
115
+ 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.',
102
116
  inputSchema: {
103
117
  type: "object",
104
118
  properties: {
105
119
  status: {
106
120
  type: "string",
107
121
  enum: ["stable", "experimental", "deprecated"],
108
- description: "Filter by maturity status."
122
+ description: "Filter active entries by maturity status. `deprecated` returns the archived `deprecatedEntries` list."
123
+ },
124
+ includeDeprecated: {
125
+ type: "boolean",
126
+ description: "When true, merge archived `deprecatedEntries` into the result (ADR 0028)."
109
127
  }
110
128
  }
111
129
  }
112
130
  },
113
131
  {
114
132
  name: "get_component_meta",
115
- 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.",
133
+ 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.",
116
134
  inputSchema: {
117
135
  type: "object",
118
136
  properties: {
@@ -126,7 +144,7 @@ var TOOLS = [
126
144
  },
127
145
  {
128
146
  name: "find_components",
129
- 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).',
147
+ 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).',
130
148
  inputSchema: {
131
149
  type: "object",
132
150
  properties: {
@@ -139,6 +157,10 @@ var TOOLS = [
139
157
  minimum: 1,
140
158
  maximum: 100,
141
159
  description: "Max matches to return (default 10)."
160
+ },
161
+ includeDeprecated: {
162
+ type: "boolean",
163
+ description: "When true, also search archived `deprecatedEntries` (ADR 0028)."
142
164
  }
143
165
  },
144
166
  required: ["query"]
@@ -158,6 +180,9 @@ function pickListEntry(entry) {
158
180
  registryDependencies: entry.registryDependencies ?? []
159
181
  };
160
182
  }
183
+ function pickArchivedEntry(entry) {
184
+ return { ...pickListEntry(entry), archived: true };
185
+ }
161
186
  function createRegistryGroup(opts = {}) {
162
187
  let cache = opts.loaded ?? null;
163
188
  function getManifest() {
@@ -172,30 +197,44 @@ function createRegistryGroup(opts = {}) {
172
197
  if (name === "list_components") {
173
198
  const input = ListComponentsInput.parse(args ?? {});
174
199
  const { manifest } = getManifest();
175
- const entries = manifest.entries.filter((e) => e.type === "component").filter((e) => input.status ? e.status === input.status : true).map(pickListEntry);
200
+ const archived = manifest.deprecatedEntries ?? [];
201
+ if (input.status === "deprecated") {
202
+ const entries = archived.filter((e) => e.type === "component").map(pickArchivedEntry);
203
+ return {
204
+ content: [{ type: "text", text: JSON.stringify(entries, null, 2) }]
205
+ };
206
+ }
207
+ const active = manifest.entries.filter((e) => e.type === "component").filter((e) => input.status ? e.status === input.status : true).map(pickListEntry);
208
+ const merged = input.includeDeprecated ? [
209
+ ...active,
210
+ ...archived.filter((e) => e.type === "component").map(pickArchivedEntry)
211
+ ] : active;
176
212
  return {
177
- content: [{ type: "text", text: JSON.stringify(entries, null, 2) }]
213
+ content: [{ type: "text", text: JSON.stringify(merged, null, 2) }]
178
214
  };
179
215
  }
180
216
  if (name === "get_component_meta") {
181
217
  const input = GetComponentMetaInput.parse(args);
182
218
  const { manifest, rootDir } = getManifest();
183
219
  const entry = manifest.entries.find((e) => e.id === input.id);
184
- if (!entry) {
220
+ const archived = entry ? null : (manifest.deprecatedEntries ?? []).find((e) => e.id === input.id) ?? null;
221
+ const found = entry ?? archived;
222
+ if (!found) {
185
223
  return {
186
224
  content: [
187
225
  {
188
226
  type: "text",
189
- text: `Component not found: ${input.id}. Use list_components to discover ids.`
227
+ text: `Component not found: ${input.id}. Use list_components to discover ids (pass includeDeprecated: true to widen the search).`
190
228
  }
191
229
  ],
192
230
  isError: true
193
231
  };
194
232
  }
195
- const meta = loadMeta(entry, rootDir);
233
+ const meta = loadMeta(found, rootDir);
196
234
  const payload = {
197
- entry,
198
- meta: meta ?? null
235
+ entry: found,
236
+ meta: meta ?? null,
237
+ archived: archived !== null
199
238
  };
200
239
  return {
201
240
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
@@ -206,9 +245,23 @@ function createRegistryGroup(opts = {}) {
206
245
  const limit = input.limit ?? 10;
207
246
  const q = input.query.toLowerCase();
208
247
  const { manifest } = getManifest();
209
- const matches = manifest.entries.filter((e) => e.type === "component").filter((e) => {
210
- return e.id.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q);
211
- }).slice(0, limit).map(pickListEntry);
248
+ const pool = input.includeDeprecated ? [
249
+ ...manifest.entries.map((e) => ({ entry: e, archived: false })),
250
+ ...(manifest.deprecatedEntries ?? []).map((e) => ({
251
+ entry: e,
252
+ archived: true
253
+ }))
254
+ ] : manifest.entries.map((e) => ({ entry: e, archived: false }));
255
+ const matches = pool.filter(({ entry }) => entry.type === "component").filter(({ entry, archived: a }) => {
256
+ if (!input.includeDeprecated && !a && entry.status === "deprecated") {
257
+ return false;
258
+ }
259
+ return true;
260
+ }).filter(({ entry }) => {
261
+ return entry.id.toLowerCase().includes(q) || entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q);
262
+ }).slice(0, limit).map(
263
+ ({ entry, archived: a }) => a ? pickArchivedEntry(entry) : pickListEntry(entry)
264
+ );
212
265
  return {
213
266
  content: [
214
267
  {
@@ -238,11 +291,18 @@ import { z as z2 } from "zod";
238
291
  // src/adr-loader.ts
239
292
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
240
293
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
294
+ import { fileURLToPath } from "url";
241
295
  function resolveAdrRoot(startDir = process.cwd()) {
242
296
  const envPath = process.env.TEAMIX_EVO_ADR_ROOT;
243
297
  if (envPath && existsSync2(envPath)) {
244
298
  return envPath;
245
299
  }
300
+ try {
301
+ const here = dirname2(fileURLToPath(import.meta.url));
302
+ const bundled = join2(here, "data", "adr");
303
+ if (existsSync2(bundled)) return bundled;
304
+ } catch {
305
+ }
246
306
  let dir = resolve2(startDir);
247
307
  for (let i = 0; i < 16; i++) {
248
308
  const candidates = [
@@ -259,8 +319,8 @@ function resolveAdrRoot(startDir = process.cwd()) {
259
319
  throw new Error(
260
320
  [
261
321
  "Could not locate ADR directory.",
262
- `Tried env TEAMIX_EVO_ADR_ROOT + walking up from ${startDir} for docs/adr/ or .teamix-evo/adr/.`,
263
- "Either run from a teamix-evo monorepo, or set TEAMIX_EVO_ADR_ROOT to an absolute path."
322
+ `Tried env TEAMIX_EVO_ADR_ROOT, mcp-bundled snapshot, and walked up from ${startDir} for docs/adr/ or .teamix-evo/adr/.`,
323
+ "Either install @teamix-evo/mcp (carries ADR snapshot) or set TEAMIX_EVO_ADR_ROOT to an absolute path."
264
324
  ].join(" ")
265
325
  );
266
326
  }
@@ -544,8 +604,8 @@ function resolveSkillsRoot(startDir = process.cwd()) {
544
604
  throw new Error(
545
605
  [
546
606
  "Could not locate @teamix-evo/skills root.",
547
- `Tried env TEAMIX_EVO_SKILLS_ROOT + walking up from ${startDir}.`,
548
- "Either install @teamix-evo/skills or run from a monorepo, or set TEAMIX_EVO_SKILLS_ROOT to an absolute path."
607
+ `Tried env TEAMIX_EVO_SKILLS_ROOT and walked up from ${startDir} for packages/skills/ or node_modules/@teamix-evo/skills/.`,
608
+ "Install @teamix-evo/skills as a devDependency in your project (the scaffold adds it automatically)."
549
609
  ].join(" ")
550
610
  );
551
611
  }
@@ -795,20 +855,20 @@ function createSkillsGroup(opts = {}) {
795
855
  };
796
856
  }
797
857
 
798
- // src/groups/design.ts
858
+ // src/groups/tokens.ts
799
859
  import { z as z4 } from "zod";
800
860
 
801
- // src/design-loader.ts
802
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
861
+ // src/tokens-loader.ts
862
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
803
863
  import { dirname as dirname4, join as join4, resolve as resolve4 } from "path";
804
- function resolveDesignRoot(startDir = process.cwd()) {
805
- const envPath = process.env.TEAMIX_EVO_DESIGN_ROOT;
864
+ function resolveTokensRoot(startDir = process.cwd()) {
865
+ const envPath = process.env.TEAMIX_EVO_TOKENS_ROOT;
806
866
  if (envPath && existsSync4(envPath)) {
807
867
  return envPath;
808
868
  }
809
869
  let dir = resolve4(startDir);
810
870
  for (let i = 0; i < 16; i++) {
811
- const candidate = join4(dir, ".teamix-evo", "design");
871
+ const candidate = join4(dir, ".teamix-evo");
812
872
  if (existsSync4(candidate)) return candidate;
813
873
  const parent = dirname4(dir);
814
874
  if (parent === dir) break;
@@ -816,11 +876,11 @@ function resolveDesignRoot(startDir = process.cwd()) {
816
876
  }
817
877
  return null;
818
878
  }
819
- function loadDesign(rootDir) {
820
- const root = rootDir ?? resolveDesignRoot();
879
+ function loadTokens(rootDir) {
880
+ const root = rootDir ?? resolveTokensRoot();
821
881
  if (!root) return null;
822
882
  let variant = null;
823
- const lockPath = join4(root, "pack.lock.json");
883
+ const lockPath = join4(root, "tokens-lock.json");
824
884
  if (existsSync4(lockPath)) {
825
885
  try {
826
886
  const raw = JSON.parse(readFileSync4(lockPath, "utf-8"));
@@ -831,47 +891,17 @@ function loadDesign(rootDir) {
831
891
  }
832
892
  return { rootDir: root, variant };
833
893
  }
834
- var PRINCIPLE_HEADING_RE = /^#{2,3}\s+(P\d+)\s*[·:]\s*(.+?)\s*$/;
835
- function readPrinciples(loaded) {
836
- const filePath = join4(loaded.rootDir, "philosophy", "principles.md");
837
- if (!existsSync4(filePath)) {
838
- return {
839
- principles: [],
840
- sourcePath: null,
841
- note: "philosophy/principles.md not found in this project. Run `teamix-evo design init <variant>` to install it."
842
- };
843
- }
844
- const text = readFileSync4(filePath, "utf-8");
845
- const lines = text.split(/\r?\n/);
846
- const out = [];
847
- for (let i = 0; i < lines.length; i++) {
848
- const m = lines[i].match(PRINCIPLE_HEADING_RE);
849
- if (!m) continue;
850
- let def = "";
851
- for (let j = i + 1; j < lines.length && j < i + 8; j++) {
852
- const candidate = lines[j].trim();
853
- if (!candidate) continue;
854
- if (/^#{1,6}\s/.test(candidate)) break;
855
- def = candidate.replace(/^[*_>\s-]+/, "").trim();
856
- break;
857
- }
858
- out.push({ id: m[1], name: m[2].trim(), oneLineDef: def });
859
- }
860
- return { principles: out, sourcePath: filePath };
861
- }
862
894
  function resolveTokensDir(loaded) {
863
895
  const projectRoot = dirname4(loaded.rootDir);
864
- const lifted = join4(projectRoot, "tokens");
865
- if (existsSync4(lifted)) return lifted;
866
- const legacy = join4(loaded.rootDir, "foundations", "tokens");
867
- if (existsSync4(legacy)) return legacy;
896
+ const rootTokens = join4(projectRoot, "tokens");
897
+ if (existsSync4(rootTokens)) return rootTokens;
868
898
  return null;
869
899
  }
870
900
  function readTokens(loaded) {
871
901
  const tokensDir = resolveTokensDir(loaded);
872
902
  const out = { sources: [] };
873
903
  if (!tokensDir) {
874
- out.note = ".teamix-evo/tokens/ not found in this project (and no legacy foundations/tokens/).";
904
+ out.note = "tokens/ not found at project root.";
875
905
  return out;
876
906
  }
877
907
  const tryJson = (rel) => {
@@ -891,169 +921,186 @@ function readTokens(loaded) {
891
921
  return readFileSync4(fp, "utf-8");
892
922
  };
893
923
  out.base = tryJson("base.tokens.json");
894
- out.semantic = tryJson("semantic.tokens.json");
895
924
  out.themeCss = tryText("tokens.theme.css");
896
925
  out.overridesCss = tryText("tokens.overrides.css");
897
926
  return out;
898
927
  }
899
- var VARIANT_PREFIX_RE = /^(cloud|opentrek|uni-manager|enterprise)-/i;
900
- function readPatternIndex(loaded) {
901
- const patternsDir = join4(loaded.rootDir, "patterns");
902
- if (!existsSync4(patternsDir)) return [];
928
+ var W3C_KEYS = /* @__PURE__ */ new Set(["$value", "$type", "$description", "$extensions"]);
929
+ function isPlainObject(v) {
930
+ return typeof v === "object" && v !== null && !Array.isArray(v);
931
+ }
932
+ function flattenTokens(base) {
933
+ if (!isPlainObject(base)) return [];
903
934
  const out = [];
904
- for (const name of readdirSync3(patternsDir)) {
905
- if (!name.endsWith(".md")) continue;
906
- const fp = join4(patternsDir, name);
907
- try {
908
- if (!statSync2(fp).isFile()) continue;
909
- } catch {
910
- continue;
935
+ for (const [category, group] of Object.entries(base)) {
936
+ if (category.startsWith("$")) continue;
937
+ if (!isPlainObject(group)) continue;
938
+ for (const [name, leaf] of Object.entries(group)) {
939
+ if (name.startsWith("$")) continue;
940
+ if (!isPlainObject(leaf)) continue;
941
+ const type = typeof leaf.$type === "string" ? leaf.$type : void 0;
942
+ const values = {};
943
+ let description = typeof leaf.$description === "string" ? leaf.$description : void 0;
944
+ if (typeof leaf.$value === "string") {
945
+ values.default = leaf.$value;
946
+ }
947
+ for (const [modeKey, modeVal] of Object.entries(leaf)) {
948
+ if (W3C_KEYS.has(modeKey)) continue;
949
+ if (!isPlainObject(modeVal)) continue;
950
+ if (typeof modeVal.$value === "string") {
951
+ values[modeKey] = modeVal.$value;
952
+ }
953
+ if (!description && typeof modeVal.$description === "string") {
954
+ description = modeVal.$description;
955
+ }
956
+ }
957
+ if (Object.keys(values).length === 0) continue;
958
+ out.push({
959
+ category,
960
+ name,
961
+ ...type ? { type } : {},
962
+ values,
963
+ ...description ? { description } : {}
964
+ });
911
965
  }
912
- const stem = name.replace(/\.md$/, "");
913
- const text = readFileSync4(fp, "utf-8");
914
- const titleMatch = text.match(/^#\s+(.+?)\s*$/m);
915
- out.push({
916
- id: stem,
917
- title: titleMatch ? titleMatch[1] : stem,
918
- sourcePath: fp,
919
- variantSpecific: VARIANT_PREFIX_RE.test(stem)
920
- });
921
966
  }
922
- return out.sort((a, b) => a.id.localeCompare(b.id));
967
+ return out;
923
968
  }
924
- function readPatternContent(loaded, id) {
925
- const stem = id.replace(/\.md$/, "");
926
- const fp = join4(loaded.rootDir, "patterns", `${stem}.md`);
927
- if (!existsSync4(fp)) return null;
969
+ function listTokens(loaded) {
970
+ const snapshot = readTokens(loaded);
971
+ if (!snapshot.base) {
972
+ return {
973
+ variant: loaded.variant,
974
+ tokens: [],
975
+ sources: snapshot.sources,
976
+ note: snapshot.note ?? "base.tokens.json not found."
977
+ };
978
+ }
928
979
  return {
929
- id: stem,
930
- sourcePath: fp,
931
- content: readFileSync4(fp, "utf-8")
980
+ variant: loaded.variant,
981
+ tokens: flattenTokens(snapshot.base),
982
+ sources: snapshot.sources
932
983
  };
933
984
  }
934
- function readBrand(loaded) {
935
- const brandDir = join4(loaded.rootDir, "brand");
936
- const out = {};
937
- if (!existsSync4(brandDir)) {
938
- out.note = "brand/ not present \u2014 this design variant does not ship brand-specific tone / voice / examples.";
939
- return out;
985
+ function searchTokens(loaded, query, limit = 20) {
986
+ const list = listTokens(loaded);
987
+ const q = query.trim().toLowerCase();
988
+ if (!q) {
989
+ return {
990
+ variant: list.variant,
991
+ query,
992
+ matches: [],
993
+ sources: list.sources,
994
+ note: "query is empty."
995
+ };
940
996
  }
941
- for (const key of ["tone", "voice", "examples"]) {
942
- const fp = join4(brandDir, `${key}.md`);
943
- if (existsSync4(fp)) {
944
- out[key] = { sourcePath: fp, content: readFileSync4(fp, "utf-8") };
945
- }
997
+ if (list.note && list.tokens.length === 0) {
998
+ return {
999
+ variant: list.variant,
1000
+ query,
1001
+ matches: [],
1002
+ sources: list.sources,
1003
+ note: list.note
1004
+ };
946
1005
  }
947
- return out;
1006
+ const hits = list.tokens.filter((t) => {
1007
+ if (t.category.toLowerCase().includes(q)) return true;
1008
+ if (t.name.toLowerCase().includes(q)) return true;
1009
+ if (t.description?.toLowerCase().includes(q)) return true;
1010
+ return Object.values(t.values).some((v) => v.toLowerCase().includes(q));
1011
+ });
1012
+ hits.sort((a, b) => {
1013
+ const c = a.category.localeCompare(b.category);
1014
+ return c !== 0 ? c : a.name.localeCompare(b.name);
1015
+ });
1016
+ return {
1017
+ variant: list.variant,
1018
+ query,
1019
+ matches: hits.slice(0, limit),
1020
+ sources: list.sources
1021
+ };
948
1022
  }
949
1023
 
950
- // src/groups/design.ts
1024
+ // src/groups/tokens.ts
951
1025
  var EmptyInput = z4.object({});
952
- var GetPatternInput = z4.object({
953
- id: z4.string().min(1)
1026
+ var SearchInput = z4.object({
1027
+ query: z4.string().min(1),
1028
+ limit: z4.number().int().min(1).max(100).optional()
954
1029
  });
955
1030
  var TOOLS4 = [
956
1031
  {
957
- name: "design_list_principles",
958
- 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.',
1032
+ name: "tokens_get",
1033
+ 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.",
959
1034
  inputSchema: { type: "object", properties: {} }
960
1035
  },
961
1036
  {
962
- name: "design_get_tokens",
963
- 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.",
1037
+ name: "tokens_list",
1038
+ 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.",
964
1039
  inputSchema: { type: "object", properties: {} }
965
1040
  },
966
1041
  {
967
- name: "design_list_patterns",
968
- 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.",
969
- inputSchema: { type: "object", properties: {} }
970
- },
971
- {
972
- name: "design_get_pattern",
973
- 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.",
1042
+ name: "tokens_search",
1043
+ 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.',
974
1044
  inputSchema: {
975
1045
  type: "object",
976
1046
  properties: {
977
- id: {
978
- type: "string",
979
- description: 'Pattern id \u2014 filename stem under `patterns/`, e.g. "page-types", "cloud-page-types".'
1047
+ query: { type: "string", description: "Free-text query." },
1048
+ limit: {
1049
+ type: "integer",
1050
+ minimum: 1,
1051
+ maximum: 100,
1052
+ description: "Max matches to return (default 20)."
980
1053
  }
981
1054
  },
982
- required: ["id"]
1055
+ required: ["query"]
983
1056
  }
984
- },
985
- {
986
- name: "design_get_brand",
987
- 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.",
988
- inputSchema: { type: "object", properties: {} }
989
1057
  }
990
1058
  ];
991
1059
  var TOOL_NAMES4 = new Set(TOOLS4.map((t) => t.name));
992
- var NO_DESIGN_NOTE = ".teamix-evo/design/ not found in this project. Run `npx teamix-evo design init <variant>` to install a design pack.";
993
- function createDesignGroup(opts = {}) {
1060
+ var NO_TOKENS_NOTE = "tokens/ directory not found in this project. Run `npx teamix-evo tokens init <variant>` to install design tokens.";
1061
+ function createTokensGroup(opts = {}) {
994
1062
  let cache = opts.loaded === void 0 ? void 0 : opts.loaded;
995
- function getDesign() {
996
- if (cache === void 0) cache = loadDesign(opts.rootDir);
1063
+ function getLoaded() {
1064
+ if (cache === void 0) cache = loadTokens(opts.rootDir);
997
1065
  return cache;
998
1066
  }
999
1067
  return {
1000
- name: "design",
1068
+ name: "tokens",
1001
1069
  tools: TOOLS4,
1002
1070
  async handle(name, args) {
1003
1071
  if (!TOOL_NAMES4.has(name)) return void 0;
1004
- const loaded = getDesign();
1005
- if (name === "design_list_principles") {
1072
+ const loaded = getLoaded();
1073
+ if (name === "tokens_get") {
1006
1074
  EmptyInput.parse(args ?? {});
1007
1075
  if (!loaded) {
1008
- return jsonResult({ principles: [], sourcePath: null, note: NO_DESIGN_NOTE });
1009
- }
1010
- return jsonResult(readPrinciples(loaded));
1011
- }
1012
- if (name === "design_get_tokens") {
1013
- EmptyInput.parse(args ?? {});
1014
- if (!loaded) {
1015
- return jsonResult({ sources: [], note: NO_DESIGN_NOTE });
1076
+ return jsonResult({ sources: [], note: NO_TOKENS_NOTE });
1016
1077
  }
1017
1078
  return jsonResult(readTokens(loaded));
1018
1079
  }
1019
- if (name === "design_list_patterns") {
1080
+ if (name === "tokens_list") {
1020
1081
  EmptyInput.parse(args ?? {});
1021
1082
  if (!loaded) {
1022
- return jsonResult({ patterns: [], note: NO_DESIGN_NOTE });
1023
- }
1024
- return jsonResult({
1025
- variant: loaded.variant,
1026
- patterns: readPatternIndex(loaded)
1027
- });
1028
- }
1029
- if (name === "design_get_pattern") {
1030
- const input = GetPatternInput.parse(args);
1031
- if (!loaded) {
1032
- return {
1033
- content: [{ type: "text", text: NO_DESIGN_NOTE }],
1034
- isError: true
1035
- };
1036
- }
1037
- const result = readPatternContent(loaded, input.id);
1038
- if (!result) {
1039
- return {
1040
- content: [
1041
- {
1042
- type: "text",
1043
- text: `Pattern not found: ${input.id}. Use design_list_patterns to discover available ids.`
1044
- }
1045
- ],
1046
- isError: true
1047
- };
1083
+ return jsonResult({
1084
+ variant: null,
1085
+ tokens: [],
1086
+ sources: [],
1087
+ note: NO_TOKENS_NOTE
1088
+ });
1048
1089
  }
1049
- return jsonResult(result);
1090
+ return jsonResult(listTokens(loaded));
1050
1091
  }
1051
- if (name === "design_get_brand") {
1052
- EmptyInput.parse(args ?? {});
1092
+ if (name === "tokens_search") {
1093
+ const parsed = SearchInput.parse(args ?? {});
1053
1094
  if (!loaded) {
1054
- return jsonResult({ note: NO_DESIGN_NOTE });
1095
+ return jsonResult({
1096
+ variant: null,
1097
+ query: parsed.query,
1098
+ matches: [],
1099
+ sources: [],
1100
+ note: NO_TOKENS_NOTE
1101
+ });
1055
1102
  }
1056
- return jsonResult(readBrand(loaded));
1103
+ return jsonResult(searchTokens(loaded, parsed.query, parsed.limit));
1057
1104
  }
1058
1105
  return void 0;
1059
1106
  }
@@ -1071,7 +1118,7 @@ function createServer(opts = {}) {
1071
1118
  createRegistryGroup(opts.registry),
1072
1119
  createAdrGroup(opts.adr),
1073
1120
  createSkillsGroup(opts.skills),
1074
- createDesignGroup(opts.design)
1121
+ createTokensGroup(opts.tokens)
1075
1122
  // Future: createScenarioGroup(opts.scenario), // ADR 0011, v0.8
1076
1123
  ];
1077
1124
  const seen = /* @__PURE__ */ new Set();