@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/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
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
260
|
-
"Either
|
|
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
|
|
545
|
-
"
|
|
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/
|
|
855
|
+
// src/groups/tokens.ts
|
|
796
856
|
import { z as z4 } from "zod";
|
|
797
857
|
|
|
798
|
-
// src/
|
|
799
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4
|
|
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
|
|
802
|
-
const envPath = process.env.
|
|
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"
|
|
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
|
|
817
|
-
const root = rootDir ??
|
|
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, "
|
|
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
|
|
862
|
-
if (existsSync4(
|
|
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 = "
|
|
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
|
|
897
|
-
function
|
|
898
|
-
|
|
899
|
-
|
|
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
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (
|
|
906
|
-
|
|
907
|
-
|
|
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
|
|
964
|
+
return out;
|
|
920
965
|
}
|
|
921
|
-
function
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
927
|
-
|
|
928
|
-
|
|
977
|
+
variant: loaded.variant,
|
|
978
|
+
tokens: flattenTokens(snapshot.base),
|
|
979
|
+
sources: snapshot.sources
|
|
929
980
|
};
|
|
930
981
|
}
|
|
931
|
-
function
|
|
932
|
-
const
|
|
933
|
-
const
|
|
934
|
-
if (!
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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
|
-
|
|
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/
|
|
1021
|
+
// src/groups/tokens.ts
|
|
948
1022
|
var EmptyInput = z4.object({});
|
|
949
|
-
var
|
|
950
|
-
|
|
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: "
|
|
955
|
-
description:
|
|
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: "
|
|
960
|
-
description: "
|
|
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: "
|
|
965
|
-
description:
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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: ["
|
|
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
|
|
990
|
-
function
|
|
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
|
|
993
|
-
if (cache === void 0) cache =
|
|
1060
|
+
function getLoaded() {
|
|
1061
|
+
if (cache === void 0) cache = loadTokens(opts.rootDir);
|
|
994
1062
|
return cache;
|
|
995
1063
|
}
|
|
996
1064
|
return {
|
|
997
|
-
name: "
|
|
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 =
|
|
1002
|
-
if (name === "
|
|
1069
|
+
const loaded = getLoaded();
|
|
1070
|
+
if (name === "tokens_get") {
|
|
1003
1071
|
EmptyInput.parse(args ?? {});
|
|
1004
1072
|
if (!loaded) {
|
|
1005
|
-
return jsonResult({
|
|
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 === "
|
|
1077
|
+
if (name === "tokens_list") {
|
|
1017
1078
|
EmptyInput.parse(args ?? {});
|
|
1018
1079
|
if (!loaded) {
|
|
1019
|
-
return jsonResult({
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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(
|
|
1087
|
+
return jsonResult(listTokens(loaded));
|
|
1047
1088
|
}
|
|
1048
|
-
if (name === "
|
|
1049
|
-
|
|
1089
|
+
if (name === "tokens_search") {
|
|
1090
|
+
const parsed = SearchInput.parse(args ?? {});
|
|
1050
1091
|
if (!loaded) {
|
|
1051
|
-
return jsonResult({
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|