@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.
- package/README.md +1 -1
- package/dist/cli.js +225 -178
- package/dist/cli.js.map +1 -1
- package/dist/data/adr/0001-three-layer-alignment.md +54 -0
- package/dist/data/adr/0002-package-naming.md +50 -0
- package/dist/data/adr/0003-update-strategy-tri-state.md +62 -0
- package/dist/data/adr/0004-cli-command-structure.md +61 -0
- package/dist/data/adr/0005-ui-no-variant.md +67 -0
- package/dist/data/adr/0006-ui-upgrade-no-baseline.md +67 -0
- package/dist/data/adr/0007-governance-docs-at-root.md +62 -0
- package/dist/data/adr/0008-eslint-visual-rules-warn-baseline.md +110 -0
- package/dist/data/adr/0009-registry-mcp-protocol-layer.md +87 -0
- package/dist/data/adr/0010-design-default-and-variants.md +319 -0
- package/dist/data/adr/0011-mcp-single-package-multi-group.md +169 -0
- package/dist/data/adr/0012-lint-shared-core.md +215 -0
- package/dist/data/adr/0013-skills-source-mirror.md +154 -0
- package/dist/data/adr/0014-ui-biz-ui-templates-tier.md +274 -0
- package/dist/data/adr/0015-skill-description-trigger-contract.md +122 -0
- package/dist/data/adr/0016-design-md-brand-charter.md +93 -0
- package/dist/data/adr/0017-mcp-design-group.md +107 -0
- package/dist/data/adr/0018-ai-context-routing.md +112 -0
- package/dist/data/adr/0019-project-upgrade-flow.md +156 -0
- package/dist/data/adr/0020-design-to-tokens-skill-fusion.md +139 -0
- package/dist/data/adr/0021-semantic-color-api-unification.md +99 -0
- package/dist/data/adr/0022-preferences-css-boundary.md +75 -0
- package/dist/data/adr/0023-cursor-pointer-explicit-in-component-source.md +70 -0
- package/dist/data/adr/0024-scoped-css-radix-state-conflict.md +99 -0
- package/dist/data/adr/0025-component-props-explicit-declaration.md +144 -0
- package/dist/data/adr/0026-component-level-token-alias.md +107 -0
- package/dist/data/adr/0027-component-visual-token-alignment.md +127 -0
- package/dist/data/adr/0028-ui-component-categorization.md +111 -0
- package/dist/data/adr/0029-input-split-and-prefix-suffix-removal.md +62 -0
- package/dist/data/adr/0030-skill-uni-manager-uplift.md +56 -0
- package/dist/data/adr/0031-skill-templates-decoupling.md +77 -0
- package/dist/data/adr/0032-opentrek-v4.1-brand-token-alignment.md +129 -0
- package/dist/data/adr/0033-entry-skill-global-only-scope.md +64 -0
- package/dist/data/adr/0034-skills-cli-verb-alignment.md +61 -0
- package/dist/data/adr/0035-skills-update-scope-and-lock-gates.md +69 -0
- package/dist/data/adr/README.md +75 -0
- package/dist/data/adr/_template.md +36 -0
- package/dist/index.d.ts +64 -59
- package/dist/index.js +232 -186
- package/dist/index.js.map +1 -1
- 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
|
-
| **
|
|
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
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
263
|
-
"Either
|
|
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
|
|
548
|
-
"
|
|
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/
|
|
858
|
+
// src/groups/tokens.ts
|
|
799
859
|
import { z as z4 } from "zod";
|
|
800
860
|
|
|
801
|
-
// src/
|
|
802
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4
|
|
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
|
|
805
|
-
const envPath = process.env.
|
|
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"
|
|
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
|
|
820
|
-
const root = rootDir ??
|
|
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, "
|
|
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
|
|
865
|
-
if (existsSync4(
|
|
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 = "
|
|
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
|
|
900
|
-
function
|
|
901
|
-
|
|
902
|
-
|
|
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
|
|
905
|
-
if (
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
if (
|
|
909
|
-
|
|
910
|
-
|
|
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
|
|
967
|
+
return out;
|
|
923
968
|
}
|
|
924
|
-
function
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
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
|
-
|
|
930
|
-
|
|
931
|
-
|
|
980
|
+
variant: loaded.variant,
|
|
981
|
+
tokens: flattenTokens(snapshot.base),
|
|
982
|
+
sources: snapshot.sources
|
|
932
983
|
};
|
|
933
984
|
}
|
|
934
|
-
function
|
|
935
|
-
const
|
|
936
|
-
const
|
|
937
|
-
if (!
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
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/
|
|
1024
|
+
// src/groups/tokens.ts
|
|
951
1025
|
var EmptyInput = z4.object({});
|
|
952
|
-
var
|
|
953
|
-
|
|
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: "
|
|
958
|
-
description:
|
|
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: "
|
|
963
|
-
description: "
|
|
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: "
|
|
968
|
-
description:
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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: ["
|
|
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
|
|
993
|
-
function
|
|
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
|
|
996
|
-
if (cache === void 0) cache =
|
|
1063
|
+
function getLoaded() {
|
|
1064
|
+
if (cache === void 0) cache = loadTokens(opts.rootDir);
|
|
997
1065
|
return cache;
|
|
998
1066
|
}
|
|
999
1067
|
return {
|
|
1000
|
-
name: "
|
|
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 =
|
|
1005
|
-
if (name === "
|
|
1072
|
+
const loaded = getLoaded();
|
|
1073
|
+
if (name === "tokens_get") {
|
|
1006
1074
|
EmptyInput.parse(args ?? {});
|
|
1007
1075
|
if (!loaded) {
|
|
1008
|
-
return jsonResult({
|
|
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 === "
|
|
1080
|
+
if (name === "tokens_list") {
|
|
1020
1081
|
EmptyInput.parse(args ?? {});
|
|
1021
1082
|
if (!loaded) {
|
|
1022
|
-
return jsonResult({
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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(
|
|
1090
|
+
return jsonResult(listTokens(loaded));
|
|
1050
1091
|
}
|
|
1051
|
-
if (name === "
|
|
1052
|
-
|
|
1092
|
+
if (name === "tokens_search") {
|
|
1093
|
+
const parsed = SearchInput.parse(args ?? {});
|
|
1053
1094
|
if (!loaded) {
|
|
1054
|
-
return jsonResult({
|
|
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(
|
|
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
|
-
|
|
1121
|
+
createTokensGroup(opts.tokens)
|
|
1075
1122
|
// Future: createScenarioGroup(opts.scenario), // ADR 0011, v0.8
|
|
1076
1123
|
];
|
|
1077
1124
|
const seen = /* @__PURE__ */ new Set();
|