teamix-evo 0.3.1 → 0.4.1

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/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command24 } from "commander";
5
- import { createRequire as createRequire5 } from "module";
4
+ import { Command as Command29 } from "commander";
5
+ import { createRequire as createRequire7 } from "module";
6
6
 
7
- // src/commands/design/index.ts
7
+ // src/commands/tokens/index.ts
8
8
  import { Command as Command6 } from "commander";
9
9
 
10
- // src/commands/design/init.ts
10
+ // src/commands/tokens/init.ts
11
11
  import { Command } from "commander";
12
12
 
13
13
  // src/ide/QoderAdapter.ts
@@ -66,14 +66,13 @@ function detectIde() {
66
66
  return new QoderAdapter();
67
67
  }
68
68
 
69
- // src/core/design-init.ts
69
+ // src/core/tokens-init.ts
70
70
  import * as path9 from "path";
71
71
  import * as fs6 from "fs/promises";
72
72
  import { createRequire as createRequire2 } from "module";
73
73
  import {
74
- loadDesignPack,
75
- loadDesignPackageManifest,
76
- mergeDefaultAndVariant
74
+ loadTokensPackageManifest,
75
+ getVariantEntry
77
76
  } from "@teamix-evo/registry";
78
77
 
79
78
  // src/utils/fs.ts
@@ -158,71 +157,22 @@ function computeHash(content) {
158
157
  return `sha256:${hash}`;
159
158
  }
160
159
 
161
- // src/core/design-pack-classify.ts
162
- import * as path4 from "path";
163
- var TEAMIX_DIR = ".teamix-evo/design";
164
- var TOKENS_DIR = ".teamix-evo/tokens";
165
- var TOKENS_PACK_PREFIX = "foundations/tokens/";
166
- var ROOT_MANAGED_FILES = {
167
- "DESIGN.md": { target: "DESIGN.md", managedRegions: ["core"] },
168
- "AGENTS.md": { target: "AGENTS.md", managedRegions: ["teamix-evo"] },
169
- "CLAUDE.md": { target: "CLAUDE.md", managedRegions: ["teamix-evo"] }
170
- };
171
- var FROZEN_FILES = /* @__PURE__ */ new Set([
172
- "foundations/tokens/tokens.overrides.css"
173
- ]);
174
- function classifyPackFile(relPath) {
175
- if (path4.basename(relPath) === "README.md") {
176
- return null;
177
- }
178
- const rootManaged = ROOT_MANAGED_FILES[relPath];
179
- if (rootManaged) {
180
- return {
181
- target: rootManaged.target,
182
- strategy: "managed",
183
- managedRegions: rootManaged.managedRegions,
184
- isFrozen: false
185
- };
186
- }
187
- if (relPath.startsWith(TOKENS_PACK_PREFIX)) {
188
- const rel2 = relPath.slice(TOKENS_PACK_PREFIX.length);
189
- const target = path4.posix.join(TOKENS_DIR, rel2);
190
- if (FROZEN_FILES.has(relPath)) {
191
- return { target, strategy: "frozen", isFrozen: true };
192
- }
193
- return { target, strategy: "regenerable", isFrozen: false };
194
- }
195
- if (FROZEN_FILES.has(relPath)) {
196
- return {
197
- target: path4.posix.join(TEAMIX_DIR, relPath),
198
- strategy: "frozen",
199
- isFrozen: true
200
- };
201
- }
202
- return {
203
- target: path4.posix.join(TEAMIX_DIR, relPath),
204
- strategy: "regenerable",
205
- isFrozen: false
206
- };
207
- }
208
-
209
160
  // src/core/state.ts
210
- import * as path5 from "path";
161
+ import * as path4 from "path";
211
162
  import {
212
163
  validateConfig,
213
164
  validateInstalled,
214
165
  validateSkillsLock,
215
- DesignPackLockSchema
166
+ TokensPackLockSchema
216
167
  } from "@teamix-evo/registry";
217
- var TEAMIX_DIR2 = ".teamix-evo";
168
+ var TEAMIX_DIR = ".teamix-evo";
218
169
  var CONFIG_FILE = "config.json";
219
170
  var MANIFEST_FILE = "manifest.json";
220
- var DESIGN_DIR = "design";
221
- var DESIGN_LOCK_FILE = "pack.lock.json";
171
+ var TOKENS_LOCK_FILE = "tokens-lock.json";
222
172
  var SKILLS_DIR = "skills";
223
173
  var SKILLS_LOCK_FILE = "manifest.lock.json";
224
174
  function getTeamixDir(projectRoot) {
225
- return path5.join(projectRoot, TEAMIX_DIR2);
175
+ return path4.join(projectRoot, TEAMIX_DIR);
226
176
  }
227
177
  async function ensureTeamixDir(projectRoot) {
228
178
  const dir = getTeamixDir(projectRoot);
@@ -230,84 +180,85 @@ async function ensureTeamixDir(projectRoot) {
230
180
  return dir;
231
181
  }
232
182
  async function readProjectConfig(projectRoot) {
233
- const configPath = path5.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
183
+ const configPath = path4.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
234
184
  const raw = await readFileOrNull(configPath);
235
185
  if (raw === null) return null;
186
+ let data;
236
187
  try {
237
- const data = JSON.parse(raw);
238
- const result = validateConfig(data);
239
- if (!result.success) {
240
- logger.warn(`Invalid config.json: ${result.error}`);
241
- return null;
242
- }
243
- return result.data;
188
+ data = JSON.parse(raw);
244
189
  } catch (err) {
245
- logger.warn(`Failed to parse config.json: ${err.message}`);
246
- return null;
190
+ throw new Error(
191
+ `Corrupted config.json (${err.message}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior config.`
192
+ );
193
+ }
194
+ const result = validateConfig(data);
195
+ if (!result.success) {
196
+ throw new Error(
197
+ `Invalid config.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
198
+ );
247
199
  }
200
+ return result.data;
248
201
  }
249
202
  async function writeProjectConfig(projectRoot, config) {
250
- const configPath = path5.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
203
+ const configPath = path4.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
251
204
  await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
252
205
  logger.debug(`Wrote config \u2192 ${configPath}`);
253
206
  }
254
207
  async function readInstalledManifest(projectRoot) {
255
- const manifestPath = path5.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
208
+ const manifestPath = path4.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
256
209
  const raw = await readFileOrNull(manifestPath);
257
210
  if (raw === null) return null;
211
+ let data;
258
212
  try {
259
- const data = JSON.parse(raw);
260
- const result = validateInstalled(data);
261
- if (!result.success) {
262
- logger.warn(`Invalid manifest.json: ${result.error}`);
263
- return null;
264
- }
265
- return result.data;
213
+ data = JSON.parse(raw);
266
214
  } catch (err) {
267
- logger.warn(`Failed to parse manifest.json: ${err.message}`);
268
- return null;
215
+ throw new Error(
216
+ `Corrupted manifest.json (${err.message}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior install records.`
217
+ );
218
+ }
219
+ const result = validateInstalled(data);
220
+ if (!result.success) {
221
+ throw new Error(
222
+ `Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
223
+ );
269
224
  }
225
+ return result.data;
270
226
  }
271
227
  async function writeInstalledManifest(projectRoot, manifest) {
272
- const manifestPath = path5.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
228
+ const manifestPath = path4.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
273
229
  await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
274
230
  logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
275
231
  }
276
- async function readDesignPackLock(projectRoot) {
277
- const lockPath = path5.join(
278
- projectRoot,
279
- TEAMIX_DIR2,
280
- DESIGN_DIR,
281
- DESIGN_LOCK_FILE
282
- );
232
+ async function readTokensLock(projectRoot) {
233
+ const lockPath = path4.join(projectRoot, TEAMIX_DIR, TOKENS_LOCK_FILE);
283
234
  const raw = await readFileOrNull(lockPath);
284
235
  if (raw === null) return null;
285
236
  try {
286
- const parsed = DesignPackLockSchema.safeParse(JSON.parse(raw));
237
+ const parsed = TokensPackLockSchema.safeParse(JSON.parse(raw));
287
238
  if (!parsed.success) {
288
- logger.warn(`Invalid design pack.lock.json: ${parsed.error.message}`);
239
+ logger.warn(`Invalid tokens-lock.json: ${parsed.error.message}`);
289
240
  return null;
290
241
  }
291
242
  return parsed.data;
292
243
  } catch (err) {
293
244
  logger.warn(
294
- `Failed to parse design pack.lock.json: ${err.message}`
245
+ `Failed to parse tokens-lock.json: ${err.message}`
295
246
  );
296
247
  return null;
297
248
  }
298
249
  }
299
- async function readDesignVariant(projectRoot) {
300
- const lock = await readDesignPackLock(projectRoot);
250
+ async function readTokensVariant(projectRoot) {
251
+ const lock = await readTokensLock(projectRoot);
301
252
  return lock?.variant.name ?? null;
302
253
  }
303
254
  function getSkillsSourceDir(projectRoot, skillName) {
304
- const base = path5.join(projectRoot, TEAMIX_DIR2, SKILLS_DIR);
305
- return skillName ? path5.join(base, skillName) : base;
255
+ const base = path4.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
256
+ return skillName ? path4.join(base, skillName) : base;
306
257
  }
307
258
  async function readSkillsLock(projectRoot) {
308
- const lockPath = path5.join(
259
+ const lockPath = path4.join(
309
260
  projectRoot,
310
- TEAMIX_DIR2,
261
+ TEAMIX_DIR,
311
262
  SKILLS_DIR,
312
263
  SKILLS_LOCK_FILE
313
264
  );
@@ -329,9 +280,9 @@ async function readSkillsLock(projectRoot) {
329
280
  }
330
281
  }
331
282
  async function writeSkillsLock(projectRoot, lock) {
332
- const lockPath = path5.join(
283
+ const lockPath = path4.join(
333
284
  projectRoot,
334
- TEAMIX_DIR2,
285
+ TEAMIX_DIR,
335
286
  SKILLS_DIR,
336
287
  SKILLS_LOCK_FILE
337
288
  );
@@ -340,21 +291,21 @@ async function writeSkillsLock(projectRoot, lock) {
340
291
  }
341
292
 
342
293
  // src/core/skills-client.ts
343
- import * as path6 from "path";
294
+ import * as path5 from "path";
344
295
  import * as fs2 from "fs/promises";
345
296
  import { createRequire } from "module";
346
297
  import { loadSkillsPackageManifest } from "@teamix-evo/registry";
347
298
  var require2 = createRequire(import.meta.url);
348
299
  function resolvePackageRoot(packageName) {
349
300
  const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
350
- return path6.dirname(pkgJsonPath);
301
+ return path5.dirname(pkgJsonPath);
351
302
  }
352
303
  async function loadSkillsData(packageName) {
353
304
  const packageRoot = resolvePackageRoot(packageName);
354
305
  logger.debug(`Resolved skills package root: ${packageRoot}`);
355
306
  const manifest = await loadSkillsPackageManifest(packageRoot);
356
307
  let data = {};
357
- const dataPath = path6.join(packageRoot, "_data.json");
308
+ const dataPath = path5.join(packageRoot, "_data.json");
358
309
  try {
359
310
  const raw = await fs2.readFile(dataPath, "utf-8");
360
311
  data = JSON.parse(raw);
@@ -368,9 +319,12 @@ async function loadSkillsData(packageName) {
368
319
  }
369
320
 
370
321
  // src/core/skills-installer.ts
371
- import * as path8 from "path";
322
+ import * as path7 from "path";
372
323
  import * as fs5 from "fs/promises";
373
- import { replaceManagedRegion } from "@teamix-evo/registry";
324
+ import {
325
+ replaceManagedRegion,
326
+ hasManagedRegion
327
+ } from "@teamix-evo/registry";
374
328
 
375
329
  // src/utils/template.ts
376
330
  import Handlebars from "handlebars";
@@ -401,13 +355,13 @@ async function loadTemplateFile(filePath) {
401
355
  }
402
356
 
403
357
  // src/utils/path.ts
404
- import * as path7 from "path";
358
+ import * as path6 from "path";
405
359
  import * as fs4 from "fs/promises";
406
360
  async function walkDir(dir) {
407
361
  const files = [];
408
362
  const entries = await fs4.readdir(dir, { withFileTypes: true });
409
363
  for (const entry of entries) {
410
- const fullPath = path7.join(dir, entry.name);
364
+ const fullPath = path6.join(dir, entry.name);
411
365
  if (entry.isDirectory()) {
412
366
  files.push(...await walkDir(fullPath));
413
367
  } else if (entry.isFile()) {
@@ -450,12 +404,12 @@ async function installSkills(options) {
450
404
  }
451
405
  async function writeSkillSource(skill, options) {
452
406
  const { data, packageRoot, projectRoot } = options;
453
- const sourceAbs = path8.resolve(packageRoot, skill.source);
407
+ const sourceAbs = path7.resolve(packageRoot, skill.source);
454
408
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
455
409
  const stat4 = await fs5.stat(sourceAbs);
456
410
  const records = [];
457
411
  if (stat4.isFile()) {
458
- const targetFile = path8.join(targetDir, "SKILL.md");
412
+ const targetFile = path7.join(targetDir, "SKILL.md");
459
413
  const content = await renderSkillContent(sourceAbs, skill, data);
460
414
  await writeFileSafe(targetFile, content);
461
415
  records.push(makeSourceRecord(skill, targetFile, content));
@@ -465,14 +419,14 @@ async function writeSkillSource(skill, options) {
465
419
  await ensureDir(targetDir);
466
420
  const entries = await walkDir(sourceAbs);
467
421
  for (const entry of entries) {
468
- const rel2 = path8.relative(sourceAbs, entry);
469
- let targetFile = path8.join(targetDir, rel2);
422
+ const rel2 = path7.relative(sourceAbs, entry);
423
+ let targetFile = path7.join(targetDir, rel2);
470
424
  if (skill.template && targetFile.endsWith(".hbs")) {
471
425
  targetFile = targetFile.slice(0, -4);
472
426
  }
473
427
  const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
474
428
  await writeFileSafe(targetFile, content);
475
- const relWritten = path8.relative(targetDir, targetFile);
429
+ const relWritten = path7.relative(targetDir, targetFile);
476
430
  records.push(makeSourceRecord(skill, targetFile, content, relWritten));
477
431
  logger.debug(` Wrote source: ${targetFile}`);
478
432
  }
@@ -486,15 +440,66 @@ async function mirrorSkillToIde(skill, ide, scope, projectRoot) {
486
440
  const sourceFiles = await walkDir(sourceDir);
487
441
  await ensureDir(targetDir);
488
442
  for (const src of sourceFiles) {
489
- const rel2 = path8.relative(sourceDir, src);
490
- const targetFile = path8.join(targetDir, rel2);
491
- const content = await fs5.readFile(src, "utf-8");
492
- await writeFileSafe(targetFile, content);
493
- records.push(makeMirrorRecord(skill, targetFile, content, ide, scope, rel2));
443
+ const rel2 = path7.relative(sourceDir, src);
444
+ const targetFile = path7.join(targetDir, rel2);
445
+ const sourceContent = await fs5.readFile(src, "utf-8");
446
+ const writtenContent = await writeMirrorContent(
447
+ targetFile,
448
+ sourceContent,
449
+ skill.managedRegions,
450
+ src
451
+ );
452
+ records.push(
453
+ makeMirrorRecord(skill, targetFile, writtenContent, ide, scope, rel2)
454
+ );
494
455
  logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
495
456
  }
496
457
  return records;
497
458
  }
459
+ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sourceFile) {
460
+ const existing = await readFileOrNull(targetFile);
461
+ if (existing === null) {
462
+ await writeFileSafe(targetFile, sourceContent);
463
+ return sourceContent;
464
+ }
465
+ const regions = managedRegions ?? [];
466
+ const matchedRegions = regions.filter((id) => hasManagedRegion(existing, id));
467
+ if (matchedRegions.length === 0) {
468
+ if (existing !== sourceContent) {
469
+ logger.warn(
470
+ `Mirror drift detected at ${targetFile} \u2014 overwriting from source. Edit ${sourceFile} (not the mirror) and re-run \`teamix-evo skills sync\`.`
471
+ );
472
+ await writeFileSafe(targetFile, sourceContent);
473
+ return sourceContent;
474
+ }
475
+ return existing;
476
+ }
477
+ let merged = existing;
478
+ for (const id of matchedRegions) {
479
+ const newRegion = extractRegionBody(sourceContent, id);
480
+ if (newRegion === null) continue;
481
+ try {
482
+ merged = replaceManagedRegion(merged, id, newRegion);
483
+ } catch {
484
+ }
485
+ }
486
+ if (merged !== existing) {
487
+ await writeFileSafe(targetFile, merged);
488
+ }
489
+ return merged;
490
+ }
491
+ function extractRegionBody(content, id) {
492
+ const re = new RegExp(
493
+ `<!-- teamix-evo:managed:start id="${escapeRegExp(
494
+ id
495
+ )}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp(
496
+ id
497
+ )}" -->`
498
+ );
499
+ const m = content.match(re);
500
+ if (!m) return null;
501
+ return m[1].replace(/^\n/, "").replace(/\n$/, "");
502
+ }
498
503
  async function renderSkillContent(sourceAbs, skill, data) {
499
504
  if (skill.template ?? sourceAbs.endsWith(".hbs")) {
500
505
  const tpl = await loadTemplateFile(sourceAbs);
@@ -549,7 +554,7 @@ async function updateSkills(options) {
549
554
  }
550
555
  async function rewriteSkillSource(skill, options, summary) {
551
556
  const { data, packageRoot, projectRoot } = options;
552
- const sourceAbs = path8.resolve(packageRoot, skill.source);
557
+ const sourceAbs = path7.resolve(packageRoot, skill.source);
553
558
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
554
559
  const stat4 = await fs5.stat(sourceAbs);
555
560
  if (!stat4.isFile()) {
@@ -557,8 +562,8 @@ async function rewriteSkillSource(skill, options, summary) {
557
562
  const entries = await walkDir(sourceAbs);
558
563
  const records = [];
559
564
  for (const entry of entries) {
560
- const rel2 = path8.relative(sourceAbs, entry);
561
- let targetFile2 = path8.join(targetDir, rel2);
565
+ const rel2 = path7.relative(sourceAbs, entry);
566
+ let targetFile2 = path7.join(targetDir, rel2);
562
567
  if (skill.template && targetFile2.endsWith(".hbs")) {
563
568
  targetFile2 = targetFile2.slice(0, -4);
564
569
  }
@@ -571,12 +576,12 @@ async function rewriteSkillSource(skill, options, summary) {
571
576
  summary.created++;
572
577
  }
573
578
  await writeFileSafe(targetFile2, content);
574
- const relWritten = path8.relative(targetDir, targetFile2);
579
+ const relWritten = path7.relative(targetDir, targetFile2);
575
580
  records.push(makeSourceRecord(skill, targetFile2, content, relWritten));
576
581
  }
577
582
  return records;
578
583
  }
579
- const targetFile = path8.join(targetDir, "SKILL.md");
584
+ const targetFile = path7.join(targetDir, "SKILL.md");
580
585
  const newContent = await renderSkillContent(sourceAbs, skill, data);
581
586
  const exists = await fileExists(targetFile);
582
587
  if (skill.updateStrategy === "frozen") {
@@ -651,14 +656,19 @@ async function syncSkillsToIdes(options) {
651
656
  await ensureDir(targetDir);
652
657
  const sourceFiles = await walkDir(sourceDir);
653
658
  for (const src of sourceFiles) {
654
- const rel2 = path8.relative(sourceDir, src);
655
- const targetFile = path8.join(targetDir, rel2);
656
- const content = await fs5.readFile(src, "utf-8");
657
- await writeFileSafe(targetFile, content);
659
+ const rel2 = path7.relative(sourceDir, src);
660
+ const targetFile = path7.join(targetDir, rel2);
661
+ const sourceContent = await fs5.readFile(src, "utf-8");
662
+ const writtenContent = await writeMirrorContent(
663
+ targetFile,
664
+ sourceContent,
665
+ skill.managedRegions,
666
+ src
667
+ );
658
668
  out.push({
659
669
  id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
660
670
  target: targetFile,
661
- hash: computeHash(content),
671
+ hash: computeHash(writtenContent),
662
672
  strategy: skill.updateStrategy,
663
673
  ide,
664
674
  scope
@@ -680,17 +690,46 @@ async function removeSkillFiles(records) {
680
690
  }
681
691
  }
682
692
  }
683
- const parents = new Set(records.map((r) => path8.dirname(r.target)));
684
- for (const dir of parents) {
685
- try {
686
- const entries = await fs5.readdir(dir);
687
- if (entries.length === 0) await fs5.rmdir(dir);
688
- } catch {
693
+ const startDirs = new Set(records.map((r) => path7.dirname(r.target)));
694
+ for (const startDir of startDirs) {
695
+ let dir = startDir;
696
+ for (let depth = 0; depth < 8; depth++) {
697
+ try {
698
+ const entries = await fs5.readdir(dir);
699
+ if (entries.length !== 0) break;
700
+ await fs5.rmdir(dir);
701
+ } catch {
702
+ break;
703
+ }
704
+ dir = path7.dirname(dir);
689
705
  }
690
706
  }
691
707
  return removed;
692
708
  }
693
709
 
710
+ // src/utils/mcp.ts
711
+ import * as path8 from "path";
712
+ var MCP_JSON_CONTENT = {
713
+ mcpServers: {
714
+ "teamix-evo": {
715
+ command: "npx",
716
+ args: ["-y", "@teamix-evo/mcp"]
717
+ }
718
+ }
719
+ };
720
+ async function ensureMcpJson(projectRoot) {
721
+ const mcpPath = path8.join(projectRoot, ".mcp.json");
722
+ if (await fileExists(mcpPath)) return "exists";
723
+ try {
724
+ await writeFileSafe(mcpPath, JSON.stringify(MCP_JSON_CONTENT, null, 2) + "\n");
725
+ logger.debug(`Wrote .mcp.json \u2192 ${mcpPath}`);
726
+ return "created";
727
+ } catch (err) {
728
+ logger.warn(`Failed to write .mcp.json: ${err.message}`);
729
+ return "failed";
730
+ }
731
+ }
732
+
694
733
  // src/core/skills-add.ts
695
734
  var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
696
735
  var FLAT_VARIANT = "_flat";
@@ -702,9 +741,6 @@ async function runSkillsAdd(options) {
702
741
  await ensureTeamixDir(projectRoot);
703
742
  const existingConfig = await readProjectConfig(projectRoot);
704
743
  const existingSkillsCfg = existingConfig?.packages?.skills;
705
- if (!isIncremental && existingSkillsCfg) {
706
- return { status: "already-added" };
707
- }
708
744
  const ides = options.ides && options.ides.length > 0 ? [...options.ides] : existingSkillsCfg?.ides ? [...existingSkillsCfg.ides] : [];
709
745
  const scope = options.scope ?? existingSkillsCfg?.scope;
710
746
  if (ides.length === 0) {
@@ -714,7 +750,7 @@ async function runSkillsAdd(options) {
714
750
  throw new Error("Scope must be specified (project | global).");
715
751
  }
716
752
  const { manifest, data, packageRoot } = await loadSkillsData(packageName);
717
- const currentDesignVariant = await readDesignVariant(projectRoot);
753
+ const currentTokensVariant = await readTokensVariant(projectRoot);
718
754
  if (isIncremental) {
719
755
  const known = new Set(manifest.skills.map((s) => s.id));
720
756
  const unknown = requestedNames.filter((n) => !known.has(n));
@@ -742,23 +778,27 @@ async function runSkillsAdd(options) {
742
778
  skippedSkillIds = requestedNames.filter((n) => existingSkillIds.has(n));
743
779
  onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
744
780
  } else {
745
- skippedSkillIds = [];
746
- onlyIds = manifest.skills.filter((s) => {
781
+ const candidateIds = manifest.skills.filter((s) => {
747
782
  if (!s.variant) return true;
748
- if (!currentDesignVariant) {
783
+ if (!currentTokensVariant) {
749
784
  logger.debug(
750
- `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): no design pack installed; will be picked up when "design init" runs.`
785
+ `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): no tokens variant installed; will be picked up when "tokens init" runs.`
751
786
  );
752
787
  return false;
753
788
  }
754
- if (s.variant !== currentDesignVariant) {
789
+ if (s.variant !== currentTokensVariant) {
755
790
  logger.debug(
756
- `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): current design variant is "${currentDesignVariant}".`
791
+ `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): current tokens variant is "${currentTokensVariant}".`
757
792
  );
758
793
  return false;
759
794
  }
760
795
  return true;
761
796
  }).map((s) => s.id);
797
+ skippedSkillIds = candidateIds.filter((id) => existingSkillIds.has(id));
798
+ onlyIds = candidateIds.filter((id) => !existingSkillIds.has(id));
799
+ }
800
+ if (!isIncremental && existingSkillsCfg && onlyIds.length === 0) {
801
+ return { status: "already-added" };
762
802
  }
763
803
  if (isIncremental && onlyIds.length === 0) {
764
804
  return {
@@ -835,6 +875,7 @@ async function runSkillsAdd(options) {
835
875
  };
836
876
  }
837
877
  await writeSkillsLock(projectRoot, lock);
878
+ await ensureMcpJson(projectRoot);
838
879
  return {
839
880
  status: "installed",
840
881
  packageName,
@@ -856,85 +897,110 @@ function mergeInstalledResources(existing, next) {
856
897
  return [...map.values()];
857
898
  }
858
899
 
859
- // src/core/design-init.ts
860
- var BASELINE_DESIGN_RULES_SKILL = "teamix-evo-design-rules";
900
+ // src/core/tokens-init.ts
861
901
  var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
862
902
  var DEFAULT_AUTO_SKILL_IDES = ["qoder", "claude"];
863
903
  var DEFAULT_AUTO_SKILL_SCOPE = "project";
864
- var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
904
+ var DEFAULT_TOKENS_PACKAGE = "@teamix-evo/tokens";
905
+ var CONSUMER_TOKENS_DIR = "tokens";
906
+ var CONSUMER_THEME_FILE = "tokens.theme.css";
907
+ var CONSUMER_OVERRIDES_FILE = "tokens.overrides.css";
908
+ var EMPTY_OVERRIDES_TEMPLATE = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
909
+ /* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
910
+ `;
865
911
  var require3 = createRequire2(import.meta.url);
866
- async function runDesignInit(options) {
912
+ async function runTokensInit(options) {
867
913
  const { projectRoot, variant, ide } = options;
868
- const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
914
+ const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE;
869
915
  await ensureTeamixDir(projectRoot);
870
- const existingConfig = await readProjectConfig(projectRoot);
871
- if (existingConfig?.packages?.design) {
872
- return {
873
- status: "already-initialized",
874
- existingVariant: existingConfig.packages.design.variant
875
- };
876
- }
877
- const packageRoot = options.packageRoot ?? resolveDesignPackageRoot(packageName);
878
- const catalog = await loadDesignPackageManifest(packageRoot);
879
- const variantEntry = catalog.variants.find((v) => v.name === variant);
916
+ const packageRoot = options.packageRoot ?? resolveTokensPackageRoot(packageName);
917
+ const catalog = await loadTokensPackageManifest(packageRoot);
918
+ const variantEntry = getVariantEntry(catalog, variant);
880
919
  if (!variantEntry) {
881
920
  const known = catalog.variants.map((v) => v.name).join(", ");
882
921
  throw new Error(
883
- `Design variant not found: "${variant}". Known variants: ${known || "(none)"}. Hint: run "teamix-evo design list-variants" to see all.`
922
+ `Unknown variant "${variant}". Available variants: ${known || "(none)"}.
923
+ Run \`teamix-evo tokens list-variants\` to see all options.`
884
924
  );
885
925
  }
886
- const defaultDir = path9.join(packageRoot, "default");
887
- const variantDir = path9.join(packageRoot, "variants", variant);
888
- const defaultPack = await loadDesignPack(defaultDir);
889
- const variantPack = await loadDesignPack(variantDir);
890
- const merge = await mergeDefaultAndVariant(defaultDir, variantDir);
926
+ const existingConfig = await readProjectConfig(projectRoot);
927
+ if (existingConfig?.packages?.tokens) {
928
+ const existingVariant = existingConfig.packages.tokens.variant;
929
+ if (existingVariant === variant) {
930
+ return { status: "already-initialized", existingVariant };
931
+ }
932
+ return {
933
+ status: "variant-mismatch",
934
+ existingVariant,
935
+ requestedVariant: variant
936
+ };
937
+ }
891
938
  const installed = [];
892
- for (const file of merge.files) {
893
- const result = await installPackFile(file, projectRoot);
939
+ for (const fileRel of variantEntry.files) {
940
+ const result = await installVariantFile(fileRel, packageRoot, projectRoot);
894
941
  if (result) installed.push(result);
895
942
  }
943
+ const overridesAbs = path9.join(
944
+ projectRoot,
945
+ CONSUMER_TOKENS_DIR,
946
+ CONSUMER_OVERRIDES_FILE
947
+ );
948
+ if (!await fileExists(overridesAbs)) {
949
+ await writeFileSafe(overridesAbs, EMPTY_OVERRIDES_TEMPLATE);
950
+ }
951
+ const overridesContent = await fs6.readFile(overridesAbs, "utf-8");
952
+ installed.push({
953
+ id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
954
+ target: path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
955
+ hash: computeHash(overridesContent),
956
+ strategy: "frozen"
957
+ });
896
958
  const lock = {
897
959
  schemaVersion: 1,
898
- default: { version: defaultPack.version, from: packageName },
899
960
  variant: {
900
- name: variantPack.name,
901
- displayName: variantPack.displayName,
902
- version: variantPack.version,
961
+ name: variantEntry.name,
962
+ displayName: variantEntry.displayName,
963
+ version: variantEntry.version,
903
964
  from: packageName
904
965
  },
905
- linked: variantPack.linked,
966
+ packageVersion: catalog.version,
967
+ linked: variantEntry.linked,
906
968
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
907
969
  };
908
970
  await writeFileSafe(
909
- path9.join(projectRoot, ".teamix-evo", "design", "pack.lock.json"),
971
+ path9.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
910
972
  JSON.stringify(lock, null, 2) + "\n"
911
973
  );
912
974
  const config = {
913
975
  $schema: "https://teamix-evo.dev/schema/config/v1.json",
914
976
  schemaVersion: 1,
915
- ide,
977
+ ide: existingConfig?.ide ?? ide,
916
978
  packages: {
917
- design: {
979
+ ...existingConfig?.packages ?? {},
980
+ tokens: {
918
981
  variant,
919
- version: variantPack.version,
982
+ version: variantEntry.version,
920
983
  tailwind: "v4"
921
984
  }
922
985
  }
923
986
  };
924
987
  await writeProjectConfig(projectRoot, config);
925
- const installedManifest = {
988
+ const prior = await readInstalledManifest(projectRoot) ?? {
926
989
  schemaVersion: 1,
927
- installed: [
928
- {
929
- package: packageName,
930
- variant,
931
- version: variantPack.version,
932
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
933
- resources: installed
934
- }
935
- ]
990
+ installed: []
936
991
  };
937
- await writeInstalledManifest(projectRoot, installedManifest);
992
+ const tokensIdx = prior.installed.findIndex((p) => p.package === packageName);
993
+ const tokensEntry = {
994
+ package: packageName,
995
+ variant,
996
+ version: variantEntry.version,
997
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
998
+ resources: installed
999
+ };
1000
+ if (tokensIdx >= 0) prior.installed[tokensIdx] = tokensEntry;
1001
+ else prior.installed.push(tokensEntry);
1002
+ await writeInstalledManifest(projectRoot, prior);
1003
+ await ensureMcpJson(projectRoot);
938
1004
  const skills = await tryAutoInstallVariantSkills({
939
1005
  projectRoot,
940
1006
  variant,
@@ -944,21 +1010,16 @@ async function runDesignInit(options) {
944
1010
  status: "installed",
945
1011
  packageName,
946
1012
  variant,
947
- version: variantPack.version,
1013
+ version: variantEntry.version,
948
1014
  count: installed.length,
949
1015
  resources: installed,
950
- merge: {
951
- overrides: merge.overrides,
952
- variantAdds: merge.variantAdds,
953
- defaultPassThrough: merge.defaultPassThrough
954
- },
955
1016
  skills
956
1017
  };
957
1018
  }
958
1019
  async function tryAutoInstallVariantSkills(args) {
959
1020
  const { projectRoot, variant, ide } = args;
960
- const variantSkillId = `${BASELINE_DESIGN_RULES_SKILL}-${variant}`;
961
- const desired = [BASELINE_DESIGN_RULES_SKILL, variantSkillId];
1021
+ const variantSkillId = `teamix-evo-design-${variant}`;
1022
+ const desired = [variantSkillId];
962
1023
  let manifestSkillIds;
963
1024
  try {
964
1025
  const { manifest } = await loadSkillsData(DEFAULT_SKILLS_PACKAGE2);
@@ -978,7 +1039,9 @@ async function tryAutoInstallVariantSkills(args) {
978
1039
  const missing = desired.filter((id) => !manifestSkillIds.has(id));
979
1040
  if (missing.length > 0) {
980
1041
  logger.warn(
981
- `Skills auto-install: not found in manifest, skipping: ${missing.join(", ")}.`
1042
+ `Skills auto-install: not found in manifest, skipping: ${missing.join(
1043
+ ", "
1044
+ )}.`
982
1045
  );
983
1046
  }
984
1047
  if (present.length === 0) {
@@ -1023,38 +1086,60 @@ async function tryAutoInstallVariantSkills(args) {
1023
1086
  };
1024
1087
  }
1025
1088
  }
1026
- async function installPackFile(file, projectRoot) {
1027
- const cls = classifyPackFile(file.relPath);
1028
- if (cls === null) return null;
1029
- const targetAbs = path9.join(projectRoot, cls.target);
1030
- if (cls.isFrozen && await fileExists(targetAbs)) {
1031
- const existing = await fs6.readFile(targetAbs, "utf-8");
1089
+ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1090
+ const sourceAbs = path9.join(packageRoot, fileRelToPackage);
1091
+ const base = path9.basename(fileRelToPackage);
1092
+ if (base === "theme.css") {
1093
+ const targetRel = path9.posix.join(
1094
+ CONSUMER_TOKENS_DIR,
1095
+ CONSUMER_THEME_FILE
1096
+ );
1097
+ const targetAbs = path9.join(projectRoot, targetRel);
1098
+ const content = await fs6.readFile(sourceAbs, "utf-8");
1099
+ await writeFileSafe(targetAbs, content);
1032
1100
  return {
1033
- id: `pack:${file.relPath}`,
1034
- target: cls.target,
1035
- hash: computeHash(existing),
1036
- strategy: cls.strategy
1101
+ id: `tokens:${CONSUMER_THEME_FILE}`,
1102
+ target: targetRel,
1103
+ hash: computeHash(content),
1104
+ strategy: "regenerable"
1037
1105
  };
1038
1106
  }
1039
- const content = await fs6.readFile(file.sourcePath, "utf-8");
1040
- await writeFileSafe(targetAbs, content);
1041
- return {
1042
- id: `pack:${file.relPath}`,
1043
- target: cls.target,
1044
- hash: computeHash(content),
1045
- strategy: cls.strategy
1046
- };
1107
+ if (base === "overrides.css" || base === "tokens.overrides.css") {
1108
+ const targetRel = path9.posix.join(
1109
+ CONSUMER_TOKENS_DIR,
1110
+ CONSUMER_OVERRIDES_FILE
1111
+ );
1112
+ const targetAbs = path9.join(projectRoot, targetRel);
1113
+ if (await fileExists(targetAbs)) {
1114
+ const existing = await fs6.readFile(targetAbs, "utf-8");
1115
+ return {
1116
+ id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
1117
+ target: targetRel,
1118
+ hash: computeHash(existing),
1119
+ strategy: "frozen"
1120
+ };
1121
+ }
1122
+ const content = await fs6.readFile(sourceAbs, "utf-8");
1123
+ await writeFileSafe(targetAbs, content);
1124
+ return {
1125
+ id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
1126
+ target: targetRel,
1127
+ hash: computeHash(content),
1128
+ strategy: "frozen"
1129
+ };
1130
+ }
1131
+ return null;
1047
1132
  }
1048
- function resolveDesignPackageRoot(packageName) {
1133
+ function resolveTokensPackageRoot(packageName) {
1049
1134
  const pkgJson = require3.resolve(`${packageName}/package.json`);
1050
1135
  return path9.dirname(pkgJson);
1051
1136
  }
1052
- async function listDesignVariants(packageName = DEFAULT_DESIGN_PACKAGE, packageRoot) {
1053
- const root = packageRoot ?? resolveDesignPackageRoot(packageName);
1054
- const catalog = await loadDesignPackageManifest(root);
1137
+ async function listTokenVariants(packageName = DEFAULT_TOKENS_PACKAGE, packageRoot) {
1138
+ const root = packageRoot ?? resolveTokensPackageRoot(packageName);
1139
+ const catalog = await loadTokensPackageManifest(root);
1055
1140
  return {
1056
1141
  packageName,
1057
- defaultDescription: catalog.default.description,
1142
+ packageVersion: catalog.version,
1058
1143
  variants: catalog.variants.map((v) => ({
1059
1144
  name: v.name,
1060
1145
  displayName: v.displayName,
@@ -1065,25 +1150,73 @@ async function listDesignVariants(packageName = DEFAULT_DESIGN_PACKAGE, packageR
1065
1150
  };
1066
1151
  }
1067
1152
 
1068
- // src/commands/design/init.ts
1153
+ // src/utils/global-root.ts
1154
+ import { existsSync } from "fs";
1155
+ import * as fs7 from "fs/promises";
1156
+ import * as os3 from "os";
1157
+ import * as path10 from "path";
1158
+ var GLOBAL_META_DIR = ".teamix-evo-global";
1159
+ var TEAMIX_DIR2 = ".teamix-evo";
1160
+ var CONFIG_FILE2 = "config.json";
1161
+ function getGlobalMetaRoot() {
1162
+ return path10.join(os3.homedir(), GLOBAL_META_DIR);
1163
+ }
1164
+ function isTeamixEvoProject(dir) {
1165
+ return existsSync(path10.join(dir, TEAMIX_DIR2, CONFIG_FILE2));
1166
+ }
1167
+ async function ensureGlobalMetaRoot() {
1168
+ const root = getGlobalMetaRoot();
1169
+ await fs7.mkdir(root, { recursive: true });
1170
+ return root;
1171
+ }
1172
+ function hasPackageJson(projectRoot) {
1173
+ return existsSync(path10.join(projectRoot, "package.json"));
1174
+ }
1175
+ function resolveSkillsMaintenanceRoot(cwd) {
1176
+ if (isTeamixEvoProject(cwd)) return cwd;
1177
+ const globalRoot = getGlobalMetaRoot();
1178
+ if (isTeamixEvoProject(globalRoot)) return globalRoot;
1179
+ return cwd;
1180
+ }
1181
+
1182
+ // src/commands/tokens/init.ts
1069
1183
  var initCommand = new Command("init").description("\u521D\u59CB\u5316\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90\uFF08\u5FC5\u987B\u663E\u5F0F\u6307\u5B9A\u4E1A\u52A1\u53D8\u4F53\uFF09").argument("<variant>", '\u4E1A\u52A1\u53D8\u4F53\u540D\u79F0\uFF08\u5982 "opentrek"\u3001"uni-manager"\uFF09').action(async (variant) => {
1070
1184
  try {
1071
1185
  const ide = detectIde();
1072
1186
  const projectRoot = ide.getProjectRoot();
1187
+ if (!hasPackageJson(projectRoot)) {
1188
+ logger.error(
1189
+ "No package.json found in current directory. Please run this command in a valid project root."
1190
+ );
1191
+ process.exit(1);
1192
+ }
1073
1193
  logger.info(`Initializing design system: variant="${variant}"`);
1074
1194
  logger.debug(`Project root: ${projectRoot}`);
1075
1195
  logger.debug(`IDE: ${ide.name}`);
1076
1196
  logger.info(`Loading variant "${variant}"...`);
1077
1197
  logger.info("Installing resources...");
1078
- const result = await runDesignInit({
1198
+ const result = await runTokensInit({
1079
1199
  projectRoot,
1080
1200
  variant,
1081
1201
  ide: ide.name
1082
1202
  });
1083
1203
  if (result.status === "already-initialized") {
1084
- logger.warn(
1085
- `Design system already initialized (variant: ${result.existingVariant}). Use "teamix-evo design update" to update.`
1204
+ logger.info(
1205
+ `Design system already initialized (variant: ${result.existingVariant}). Use "teamix-evo tokens update" to refresh resources.`
1206
+ );
1207
+ return;
1208
+ }
1209
+ if (result.status === "variant-mismatch") {
1210
+ logger.error(
1211
+ `Cannot switch tokens variant in place. Currently installed: "${result.existingVariant}"; requested: "${result.requestedVariant}".`
1212
+ );
1213
+ logger.info("To switch variants:");
1214
+ logger.info(" 1. teamix-evo tokens uninstall --yes");
1215
+ logger.info(` 2. teamix-evo tokens init ${result.requestedVariant}`);
1216
+ logger.info(
1217
+ "Note: tokens.overrides.css (frozen) is preserved across uninstall/init by default."
1086
1218
  );
1219
+ process.exitCode = 1;
1087
1220
  return;
1088
1221
  }
1089
1222
  logger.success(
@@ -1092,9 +1225,6 @@ var initCommand = new Command("init").description("\u521D\u59CB\u5316\u8BBE\u8BA
1092
1225
  logger.info(` Variant: ${result.variant}`);
1093
1226
  logger.info(` Tailwind: v4`);
1094
1227
  logger.info(` Resources: ${result.count} files installed`);
1095
- logger.info(
1096
- ` Merge: ${result.merge.overrides.length} overrides / ${result.merge.variantAdds.length} variant-only / ${result.merge.defaultPassThrough.length} default-passthrough`
1097
- );
1098
1228
  if (result.skills) {
1099
1229
  const { addedSkillIds, skippedSkillIds, missing } = result.skills;
1100
1230
  if (addedSkillIds.length > 0) {
@@ -1114,9 +1244,9 @@ var initCommand = new Command("init").description("\u521D\u59CB\u5316\u8BBE\u8BA
1114
1244
  }
1115
1245
  }
1116
1246
  logger.info("");
1117
- logger.info('Run "teamix-evo design update" to update resources later.');
1247
+ logger.info('Run "teamix-evo tokens update" to update resources later.');
1118
1248
  logger.info(
1119
- 'Run "teamix-evo design list-variants" to see all available variants.'
1249
+ 'Run "teamix-evo tokens list-variants" to see all available variants.'
1120
1250
  );
1121
1251
  } catch (err) {
1122
1252
  logger.error(`Failed to initialize: ${err.message}`);
@@ -1125,43 +1255,191 @@ var initCommand = new Command("init").description("\u521D\u59CB\u5316\u8BBE\u8BA
1125
1255
  }
1126
1256
  });
1127
1257
 
1128
- // src/commands/design/update.ts
1258
+ // src/commands/tokens/update.ts
1129
1259
  import { Command as Command2 } from "commander";
1130
- var updateCommand = new Command2("update").description("(v0.7) \u66F4\u65B0\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90 \u2014 \u5F53\u524D\u672A\u5B9E\u73B0,\u89C1 ADR 0010 / PLAN \xA712.6").action(() => {
1131
- logger.warn(
1132
- "design update is not yet implemented for the design pack model (default + variants/, ADR 0010)."
1260
+
1261
+ // src/core/tokens-update.ts
1262
+ import * as path11 from "path";
1263
+ import * as fs8 from "fs/promises";
1264
+ import { createRequire as createRequire3 } from "module";
1265
+ import {
1266
+ loadTokensPackageManifest as loadTokensPackageManifest2,
1267
+ getVariantEntry as getVariantEntry2
1268
+ } from "@teamix-evo/registry";
1269
+ var DEFAULT_TOKENS_PACKAGE2 = "@teamix-evo/tokens";
1270
+ var CONSUMER_TOKENS_DIR2 = "tokens";
1271
+ var CONSUMER_THEME_FILE2 = "tokens.theme.css";
1272
+ var require4 = createRequire3(import.meta.url);
1273
+ async function runTokensUpdate(options) {
1274
+ const { projectRoot } = options;
1275
+ const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE2;
1276
+ const config = await readProjectConfig(projectRoot);
1277
+ if (!config?.packages?.tokens) {
1278
+ return { status: "not-initialized" };
1279
+ }
1280
+ const currentVariant = config.packages.tokens.variant;
1281
+ const currentVersion = config.packages.tokens.version;
1282
+ const packageRoot = options.packageRoot ?? resolveTokensPackageRoot2(packageName);
1283
+ const catalog = await loadTokensPackageManifest2(packageRoot);
1284
+ const variantEntry = getVariantEntry2(catalog, currentVariant);
1285
+ if (!variantEntry) {
1286
+ throw new Error(
1287
+ `Currently installed variant "${currentVariant}" no longer exists in ${packageName}@${catalog.version}. Available: ${catalog.variants.map((v) => v.name).join(", ")}. Run \`teamix-evo tokens uninstall\` then \`teamix-evo tokens init <variant>\` to switch.`
1288
+ );
1289
+ }
1290
+ if (variantEntry.version === currentVersion) {
1291
+ await rewriteRegenerableFiles(variantEntry.files, packageRoot, projectRoot);
1292
+ return {
1293
+ status: "up-to-date",
1294
+ packageName,
1295
+ variant: currentVariant,
1296
+ version: currentVersion
1297
+ };
1298
+ }
1299
+ const rewritten = await rewriteRegenerableFiles(
1300
+ variantEntry.files,
1301
+ packageRoot,
1302
+ projectRoot
1133
1303
  );
1134
- logger.info(
1135
- "Workaround: clean `.teamix-evo/design/` and re-run `teamix-evo design init <variant>`."
1304
+ const preserved = [];
1305
+ const overridesAbs = path11.join(
1306
+ projectRoot,
1307
+ CONSUMER_TOKENS_DIR2,
1308
+ "tokens.overrides.css"
1136
1309
  );
1137
- logger.info(
1138
- "Tracking: PLAN \xA712.6 v0.7 \u2014 semantic upgrade flow + tokens.overrides.css preservation."
1310
+ if (await fileExists(overridesAbs)) preserved.push("tokens.overrides.css");
1311
+ const lock = {
1312
+ schemaVersion: 1,
1313
+ variant: {
1314
+ name: variantEntry.name,
1315
+ displayName: variantEntry.displayName,
1316
+ version: variantEntry.version,
1317
+ from: packageName
1318
+ },
1319
+ packageVersion: catalog.version,
1320
+ linked: variantEntry.linked,
1321
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1322
+ };
1323
+ await writeFileSafe(
1324
+ path11.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
1325
+ JSON.stringify(lock, null, 2) + "\n"
1139
1326
  );
1140
- process.exitCode = 0;
1327
+ config.packages.tokens.version = variantEntry.version;
1328
+ await writeProjectConfig(projectRoot, config);
1329
+ const prior = await readInstalledManifest(projectRoot) ?? {
1330
+ schemaVersion: 1,
1331
+ installed: []
1332
+ };
1333
+ const idx = prior.installed.findIndex((p) => p.package === packageName);
1334
+ if (idx >= 0) {
1335
+ prior.installed[idx].version = variantEntry.version;
1336
+ prior.installed[idx].installedAt = (/* @__PURE__ */ new Date()).toISOString();
1337
+ for (const r of prior.installed[idx].resources) {
1338
+ if (r.strategy === "regenerable") {
1339
+ const abs = path11.isAbsolute(r.target) ? r.target : path11.join(projectRoot, r.target);
1340
+ if (await fileExists(abs)) {
1341
+ r.hash = computeHash(await fs8.readFile(abs, "utf-8"));
1342
+ }
1343
+ }
1344
+ }
1345
+ await writeInstalledManifest(projectRoot, prior);
1346
+ }
1347
+ return {
1348
+ status: "updated",
1349
+ packageName,
1350
+ variant: currentVariant,
1351
+ from: currentVersion,
1352
+ to: variantEntry.version,
1353
+ rewritten,
1354
+ preserved
1355
+ };
1356
+ }
1357
+ async function rewriteRegenerableFiles(files, packageRoot, projectRoot) {
1358
+ const written = [];
1359
+ for (const fileRel of files) {
1360
+ const base = path11.basename(fileRel);
1361
+ if (base !== "theme.css") continue;
1362
+ const sourceAbs = path11.join(packageRoot, fileRel);
1363
+ const targetAbs = path11.join(
1364
+ projectRoot,
1365
+ CONSUMER_TOKENS_DIR2,
1366
+ CONSUMER_THEME_FILE2
1367
+ );
1368
+ const content = await fs8.readFile(sourceAbs, "utf-8");
1369
+ await writeFileSafe(targetAbs, content);
1370
+ written.push(CONSUMER_THEME_FILE2);
1371
+ }
1372
+ return written;
1373
+ }
1374
+ function resolveTokensPackageRoot2(packageName) {
1375
+ const pkgJson = require4.resolve(`${packageName}/package.json`);
1376
+ return path11.dirname(pkgJson);
1377
+ }
1378
+
1379
+ // src/commands/tokens/update.ts
1380
+ var updateCommand = new Command2("update").description(
1381
+ "\u5237\u65B0 design tokens\uFF08regenerable \u8986\u76D6\u3001frozen \u4FDD\u7559 \u2014 ADR 0003 \u4E09\u6001\uFF09"
1382
+ ).action(async () => {
1383
+ try {
1384
+ const ide = detectIde();
1385
+ const projectRoot = ide.getProjectRoot();
1386
+ const result = await runTokensUpdate({ projectRoot });
1387
+ if (result.status === "not-initialized") {
1388
+ logger.error(
1389
+ "No tokens variant installed. Run `teamix-evo tokens init <variant>` first."
1390
+ );
1391
+ process.exitCode = 1;
1392
+ return;
1393
+ }
1394
+ if (result.status === "up-to-date") {
1395
+ logger.success(
1396
+ `Already at latest: ${result.packageName} (${result.variant} v${result.version})`
1397
+ );
1398
+ logger.info(
1399
+ " Regenerable files refreshed in case of manual edits."
1400
+ );
1401
+ return;
1402
+ }
1403
+ logger.success(
1404
+ `Updated ${result.packageName} (${result.variant}): v${result.from} \u2192 v${result.to}`
1405
+ );
1406
+ if (result.rewritten.length > 0) {
1407
+ logger.info(` Rewrote: ${result.rewritten.join(", ")}`);
1408
+ }
1409
+ if (result.preserved.length > 0) {
1410
+ logger.info(
1411
+ ` Preserved: ${result.preserved.join(", ")} (frozen \u2014 your customizations kept)`
1412
+ );
1413
+ }
1414
+ } catch (err) {
1415
+ logger.error(`Failed to update tokens: ${err.message}`);
1416
+ logger.debug(err.stack ?? "");
1417
+ process.exitCode = 1;
1418
+ }
1141
1419
  });
1142
1420
 
1143
- // src/commands/design/list.ts
1421
+ // src/commands/tokens/list.ts
1144
1422
  import { Command as Command3 } from "commander";
1145
1423
  var listCommand = new Command3("list").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684\u8BBE\u8BA1\u53D8\u4F53").action(async () => {
1146
1424
  try {
1147
1425
  const ide = detectIde();
1148
1426
  const projectRoot = ide.getProjectRoot();
1149
1427
  const config = await readProjectConfig(projectRoot);
1150
- if (!config?.packages?.design) {
1151
- logger.info("No design system installed.");
1152
- logger.info('Run "teamix-evo design init [variant]" to get started.');
1428
+ if (!config?.packages?.tokens) {
1429
+ logger.info("No tokens variant installed.");
1430
+ logger.info('Run "teamix-evo tokens init [variant]" to get started.');
1153
1431
  return;
1154
1432
  }
1155
- const { variant, version: version2 } = config.packages.design;
1156
- logger.info("Installed design system:");
1157
- logger.info(` Package: @teamix-evo/design`);
1433
+ const { variant, version: version2 } = config.packages.tokens;
1434
+ logger.info("Installed tokens variant:");
1435
+ logger.info(` Package: @teamix-evo/tokens`);
1158
1436
  logger.info(` Variant: ${variant}`);
1159
1437
  logger.info(` Version: ${version2}`);
1160
1438
  logger.info(` IDE: ${config.ide}`);
1161
1439
  const manifest = await readInstalledManifest(projectRoot);
1162
1440
  if (manifest) {
1163
1441
  const pkg = manifest.installed.find(
1164
- (p) => p.package === "@teamix-evo/design" && p.variant === variant
1442
+ (p) => p.package === "@teamix-evo/tokens" && p.variant === variant
1165
1443
  );
1166
1444
  if (pkg) {
1167
1445
  logger.info(` Resources: ${pkg.resources.length} files`);
@@ -1176,20 +1454,17 @@ var listCommand = new Command3("list").description("\u5217\u51FA\u5DF2\u5B89\u88
1176
1454
  }
1177
1455
  });
1178
1456
 
1179
- // src/commands/design/list-variants.ts
1457
+ // src/commands/tokens/list-variants.ts
1180
1458
  import { Command as Command4 } from "commander";
1181
- var listVariantsCommand = new Command4("list-variants").description("\u5217\u51FA @teamix-evo/design \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u53D8\u4F53").action(async () => {
1459
+ var listVariantsCommand = new Command4("list-variants").description("\u5217\u51FA @teamix-evo/tokens \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u53D8\u4F53").action(async () => {
1182
1460
  try {
1183
- const result = await listDesignVariants();
1184
- logger.info(`Available design variants in ${result.packageName}:`);
1185
- logger.info("");
1186
- logger.info(" default \u2014 B \u7AEF\u901A\u7528\u57FA\u7EBF(\u59CB\u7EC8\u5185\u7F6E,\u65E0\u9700\u9009\u62E9)");
1187
- if (result.defaultDescription) {
1188
- logger.info(` ${result.defaultDescription}`);
1189
- }
1461
+ const result = await listTokenVariants();
1462
+ logger.info(
1463
+ `Available variants in ${result.packageName} v${result.packageVersion}:`
1464
+ );
1190
1465
  logger.info("");
1191
1466
  if (result.variants.length === 0) {
1192
- logger.info(" (no variants beyond default)");
1467
+ logger.info(" (no variants advertised in manifest.json)");
1193
1468
  return;
1194
1469
  }
1195
1470
  for (const v of result.variants) {
@@ -1198,12 +1473,13 @@ var listVariantsCommand = new Command4("list-variants").description("\u5217\u51F
1198
1473
  if (v.linked) {
1199
1474
  const links = [];
1200
1475
  if (v.linked["biz-ui"]) links.push(`biz-ui: ${v.linked["biz-ui"]}`);
1201
- if (v.linked.templates) links.push(`templates: ${v.linked.templates}`);
1476
+ if (v.linked.templates)
1477
+ links.push(`templates: ${v.linked.templates}`);
1202
1478
  if (links.length) logger.info(` linked: ${links.join(" / ")}`);
1203
1479
  }
1204
1480
  logger.info("");
1205
1481
  }
1206
- logger.info("Install a variant: teamix-evo design init <name>");
1482
+ logger.info("Install a variant: teamix-evo tokens init <name>");
1207
1483
  } catch (err) {
1208
1484
  logger.error(`Failed to list variants: ${err.message}`);
1209
1485
  logger.debug(err.stack ?? "");
@@ -1211,37 +1487,42 @@ var listVariantsCommand = new Command4("list-variants").description("\u5217\u51F
1211
1487
  }
1212
1488
  });
1213
1489
 
1214
- // src/commands/design/uninstall.ts
1490
+ // src/commands/tokens/uninstall.ts
1215
1491
  import { Command as Command5 } from "commander";
1216
- import * as fs7 from "fs/promises";
1217
- import * as path10 from "path";
1492
+ import * as fs9 from "fs/promises";
1493
+ import * as path12 from "path";
1218
1494
  import * as prompts from "@clack/prompts";
1219
- var DESIGN_PACKAGE = "@teamix-evo/design";
1220
- var uninstallCommand = new Command5("uninstall").description("\u5378\u8F7D\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90\uFF08\u9ED8\u8BA4\u4F1A\u5220\u9664 frozen / regenerable \u8D44\u6E90\u6587\u4EF6\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").option(
1495
+ var TOKENS_PACKAGE = "@teamix-evo/tokens";
1496
+ var uninstallCommand = new Command5("uninstall").description(
1497
+ "\u5378\u8F7D\u5DF2\u88C5\u7684 tokens \u53D8\u4F53\uFF08\u9ED8\u8BA4\u4FDD\u7559 frozen \u6587\u4EF6\u5982 tokens.overrides.css\uFF1B--purge \u624D\u4E00\u5E76\u5220\uFF09"
1498
+ ).option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").option(
1221
1499
  "--keep-files",
1222
- "\u4EC5\u6E05\u7406 .teamix-evo \u4E2D\u7684\u8BB0\u8D26\u4FE1\u606F\uFF0C\u4E0D\u5220\u9664\u5DF2\u843D\u5730\u8D44\u6E90\u6587\u4EF6"
1500
+ "\u4EC5\u6E05\u7406 .teamix-evo \u4E2D\u7684\u8BB0\u8D26\u4FE1\u606F\uFF0C\u4E0D\u5220\u9664\u4EFB\u4F55\u843D\u5730\u8D44\u6E90\u6587\u4EF6"
1501
+ ).option(
1502
+ "--purge",
1503
+ "\u540C\u65F6\u5220\u9664 frozen \u6587\u4EF6\uFF08\u9ED8\u8BA4 frozen / managed \u90FD\u4FDD\u7559 \u2014 ADR 0003\uFF09"
1223
1504
  ).action(async (opts) => {
1224
1505
  try {
1225
1506
  const ide = detectIde();
1226
1507
  const projectRoot = ide.getProjectRoot();
1227
1508
  const config = await readProjectConfig(projectRoot);
1228
- if (!config?.packages?.design) {
1229
- logger.info("Design system is not installed. Nothing to do.");
1509
+ if (!config?.packages?.tokens) {
1510
+ logger.info("No tokens variant installed. Nothing to do.");
1230
1511
  return;
1231
1512
  }
1232
1513
  const installedManifest = await readInstalledManifest(projectRoot);
1233
1514
  const pkg = installedManifest?.installed.find(
1234
- (p) => p.package === DESIGN_PACKAGE
1515
+ (p) => p.package === TOKENS_PACKAGE
1235
1516
  );
1236
1517
  const resources = pkg?.resources ?? [];
1237
- const removable = opts.keepFiles ? [] : resources.filter((r) => r.strategy !== "managed");
1518
+ const removable = opts.keepFiles ? [] : opts.purge ? resources.filter((r) => r.strategy !== "managed") : resources.filter((r) => r.strategy === "regenerable");
1238
1519
  const kept = resources.length - removable.length;
1239
1520
  logger.info(
1240
1521
  `Will remove ${removable.length} file(s); keep ${kept} managed file(s).`
1241
1522
  );
1242
1523
  if (!opts.yes) {
1243
1524
  const confirm4 = await prompts.confirm({
1244
- message: "\u786E\u8BA4\u5378\u8F7D\u8BBE\u8BA1\u4F53\u7CFB\uFF1F",
1525
+ message: "\u786E\u8BA4\u5378\u8F7D tokens \u53D8\u4F53\uFF1F",
1245
1526
  initialValue: false
1246
1527
  });
1247
1528
  if (prompts.isCancel(confirm4) || !confirm4) {
@@ -1251,9 +1532,9 @@ var uninstallCommand = new Command5("uninstall").description("\u5378\u8F7D\u8BBE
1251
1532
  }
1252
1533
  let removed = 0;
1253
1534
  for (const r of removable) {
1254
- const target = path10.isAbsolute(r.target) ? r.target : path10.join(projectRoot, r.target);
1535
+ const target = path12.isAbsolute(r.target) ? r.target : path12.join(projectRoot, r.target);
1255
1536
  try {
1256
- await fs7.unlink(target);
1537
+ await fs9.unlink(target);
1257
1538
  removed++;
1258
1539
  } catch (err) {
1259
1540
  if (err.code !== "ENOENT") {
@@ -1265,18 +1546,31 @@ var uninstallCommand = new Command5("uninstall").description("\u5378\u8F7D\u8BBE
1265
1546
  }
1266
1547
  if (installedManifest) {
1267
1548
  installedManifest.installed = installedManifest.installed.filter(
1268
- (p) => p.package !== DESIGN_PACKAGE
1549
+ (p) => p.package !== TOKENS_PACKAGE
1269
1550
  );
1270
1551
  await writeInstalledManifest(projectRoot, installedManifest);
1271
1552
  }
1272
- delete config.packages.design;
1553
+ delete config.packages.tokens;
1273
1554
  await writeProjectConfig(projectRoot, config);
1274
- logger.success(`Uninstalled ${DESIGN_PACKAGE}`);
1555
+ const lockPath = path12.join(
1556
+ projectRoot,
1557
+ ".teamix-evo",
1558
+ "tokens-lock.json"
1559
+ );
1560
+ try {
1561
+ await fs9.unlink(lockPath);
1562
+ } catch (err) {
1563
+ if (err.code !== "ENOENT") {
1564
+ logger.warn(
1565
+ `Failed to remove tokens-lock.json: ${err.message}`
1566
+ );
1567
+ }
1568
+ }
1569
+ logger.success(`Uninstalled ${TOKENS_PACKAGE}`);
1275
1570
  logger.info(` Removed: ${removed} files`);
1276
1571
  if (kept > 0) {
1277
- logger.info(
1278
- ` Kept: ${kept} managed files (you may delete manually)`
1279
- );
1572
+ const note = opts.purge ? "managed (you may delete manually)" : "frozen / managed (preserved \u2014 ADR 0003; --purge to force)";
1573
+ logger.info(` Kept: ${kept} files \u2014 ${note}`);
1280
1574
  }
1281
1575
  } catch (err) {
1282
1576
  logger.error(`Failed to uninstall: ${err.message}`);
@@ -1285,15 +1579,13 @@ var uninstallCommand = new Command5("uninstall").description("\u5378\u8F7D\u8BBE
1285
1579
  }
1286
1580
  });
1287
1581
 
1288
- // src/commands/design/index.ts
1289
- var designCommand = new Command6("design").description(
1290
- "\u7BA1\u7406\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90"
1291
- );
1292
- designCommand.addCommand(initCommand);
1293
- designCommand.addCommand(updateCommand);
1294
- designCommand.addCommand(listCommand);
1295
- designCommand.addCommand(listVariantsCommand);
1296
- designCommand.addCommand(uninstallCommand);
1582
+ // src/commands/tokens/index.ts
1583
+ var tokensCommand = new Command6("tokens").description("\u7BA1\u7406 design tokens(\u53D8\u4F53\u7EA7 theme.css \u7B49)");
1584
+ tokensCommand.addCommand(initCommand);
1585
+ tokensCommand.addCommand(updateCommand);
1586
+ tokensCommand.addCommand(listCommand);
1587
+ tokensCommand.addCommand(listVariantsCommand);
1588
+ tokensCommand.addCommand(uninstallCommand);
1297
1589
 
1298
1590
  // src/commands/skills/index.ts
1299
1591
  import { Command as Command13 } from "commander";
@@ -1301,34 +1593,6 @@ import { Command as Command13 } from "commander";
1301
1593
  // src/commands/skills/add.ts
1302
1594
  import { Command as Command7 } from "commander";
1303
1595
  import * as prompts2 from "@clack/prompts";
1304
-
1305
- // src/utils/global-root.ts
1306
- import { existsSync } from "fs";
1307
- import * as fs8 from "fs/promises";
1308
- import * as os3 from "os";
1309
- import * as path11 from "path";
1310
- var GLOBAL_META_DIR = ".teamix-evo-global";
1311
- var TEAMIX_DIR3 = ".teamix-evo";
1312
- var CONFIG_FILE2 = "config.json";
1313
- function getGlobalMetaRoot() {
1314
- return path11.join(os3.homedir(), GLOBAL_META_DIR);
1315
- }
1316
- function isTeamixEvoProject(dir) {
1317
- return existsSync(path11.join(dir, TEAMIX_DIR3, CONFIG_FILE2));
1318
- }
1319
- async function ensureGlobalMetaRoot() {
1320
- const root = getGlobalMetaRoot();
1321
- await fs8.mkdir(root, { recursive: true });
1322
- return root;
1323
- }
1324
- function resolveSkillsMaintenanceRoot(cwd) {
1325
- if (isTeamixEvoProject(cwd)) return cwd;
1326
- const globalRoot = getGlobalMetaRoot();
1327
- if (isTeamixEvoProject(globalRoot)) return globalRoot;
1328
- return cwd;
1329
- }
1330
-
1331
- // src/commands/skills/add.ts
1332
1596
  var addCommand = new Command7("add").description(
1333
1597
  "\u5411\u9879\u76EE\uFF08\u6216\u5168\u5C40 IDE \u914D\u7F6E\uFF09\u6DFB\u52A0 teamix-evo skills\uFF1B\u4E0D\u4F20 names \u5219\u6DFB\u52A0 manifest \u5185\u5168\u90E8 skill"
1334
1598
  ).argument(
@@ -1351,6 +1615,11 @@ var addCommand = new Command7("add").description(
1351
1615
  if (scope === "global" && !isTeamixEvoProject(cwd)) {
1352
1616
  projectRoot = await ensureGlobalMetaRoot();
1353
1617
  logger.info(`Global skill install \u2014 meta root: ${projectRoot}`);
1618
+ } else if (scope !== "global" && !hasPackageJson(cwd)) {
1619
+ logger.error(
1620
+ "No package.json found in current directory. Please run this command in a valid project root."
1621
+ );
1622
+ process.exit(1);
1354
1623
  }
1355
1624
  logger.info(
1356
1625
  isIncremental ? `Adding skills [${names.join(",")}]: ides=[${ides.join(
@@ -1483,11 +1752,17 @@ var listCommand2 = new Command8("list").alias("ls").description(
1483
1752
  const pkg = installedManifest?.installed.find(
1484
1753
  (p) => p.package === SKILLS_PACKAGE
1485
1754
  );
1755
+ const skillsLock = await readSkillsLock(projectRoot);
1486
1756
  const installedBySkill = /* @__PURE__ */ new Map();
1487
1757
  for (const r of pkg?.resources ?? []) {
1488
1758
  const skillId = r.id.split(":")[0];
1489
1759
  installedBySkill.set(skillId, (installedBySkill.get(skillId) ?? 0) + 1);
1490
1760
  }
1761
+ for (const skillId of Object.keys(skillsLock?.skills ?? {})) {
1762
+ if (!installedBySkill.has(skillId)) {
1763
+ installedBySkill.set(skillId, 1);
1764
+ }
1765
+ }
1491
1766
  if (opts.installed) {
1492
1767
  if (!config?.packages?.skills || !pkg) {
1493
1768
  logger.info("No skills installed.");
@@ -1510,9 +1785,13 @@ var listCommand2 = new Command8("list").alias("ls").description(
1510
1785
  const skills = [...manifest.skills].sort(
1511
1786
  (a, b) => a.id.localeCompare(b.id)
1512
1787
  );
1513
- const isInstalled = !!config?.packages?.skills && !!pkg;
1514
- if (isInstalled) {
1788
+ const isInstalled = installedBySkill.size > 0;
1789
+ if (isInstalled && config?.packages?.skills && pkg) {
1515
1790
  printInstalledHeader(config.packages.skills, pkg.installedAt);
1791
+ } else if (isInstalled) {
1792
+ logger.info(
1793
+ `Installed (${installedBySkill.size} skill(s)) \u2014 config or manifest record missing; run "teamix-evo skills doctor" to repair.`
1794
+ );
1516
1795
  } else {
1517
1796
  logger.info("Skills package not yet added.");
1518
1797
  logger.info(
@@ -1648,8 +1927,8 @@ var updateCommand2 = new Command9("update").description("\u66F4\u65B0\u5DF2\u5B8
1648
1927
  // src/commands/skills/uninstall.ts
1649
1928
  import { Command as Command10 } from "commander";
1650
1929
  import * as prompts3 from "@clack/prompts";
1651
- import * as path12 from "path";
1652
- import * as fs9 from "fs/promises";
1930
+ import * as path13 from "path";
1931
+ import * as fs10 from "fs/promises";
1653
1932
  var SKILLS_PACKAGE3 = "@teamix-evo/skills";
1654
1933
  var uninstallCommand2 = new Command10("uninstall").description(
1655
1934
  "\u5378\u8F7D\u5DF2\u5B89\u88C5\u7684 teamix-evo skills\uFF1B\u4E0D\u4F20 ids \u5219\u5378\u8F7D\u6574\u5305\uFF0C\u4F20 ids \u5219\u6309 skill \u5220\u9664"
@@ -1717,14 +1996,26 @@ async function runFullUninstall(args) {
1717
1996
  const removed = await removeSkillFiles(resources);
1718
1997
  logger.debug(`Removed ${removed.length} files`);
1719
1998
  const skillsRoot = getSkillsSourceDir(projectRoot);
1999
+ const sourceSkillNames = await listSkillSourceNames(skillsRoot);
1720
2000
  try {
1721
- await fs9.rm(skillsRoot, { recursive: true, force: true });
2001
+ await fs10.rm(skillsRoot, { recursive: true, force: true });
1722
2002
  logger.debug(`Removed source dir ${skillsRoot}`);
1723
2003
  } catch (err) {
1724
2004
  logger.warn(
1725
2005
  `Failed to remove ${skillsRoot}: ${err.message}`
1726
2006
  );
1727
2007
  }
2008
+ const skillNames = collectSkillNames({
2009
+ fromSources: sourceSkillNames,
2010
+ fromConfig: config,
2011
+ fromResources: resources
2012
+ });
2013
+ const cleanedMirrorDirs = await removeMirrorDirs(
2014
+ projectRoot,
2015
+ config?.packages?.skills?.scope,
2016
+ config?.packages?.skills?.ides,
2017
+ skillNames
2018
+ );
1728
2019
  if (installedManifest && pkg) {
1729
2020
  installedManifest.installed = installedManifest.installed.filter(
1730
2021
  (p) => p.package !== SKILLS_PACKAGE3
@@ -1735,7 +2026,12 @@ async function runFullUninstall(args) {
1735
2026
  await writeProjectConfig(projectRoot, config);
1736
2027
  logger.success(`Uninstalled ${SKILLS_PACKAGE3}`);
1737
2028
  logger.info(` Removed: ${removed.length} files`);
1738
- logger.info(` Source: ${path12.relative(projectRoot, skillsRoot)} (cleaned)`);
2029
+ logger.info(` Source: ${path13.relative(projectRoot, skillsRoot)} (cleaned)`);
2030
+ if (cleanedMirrorDirs.length > 0) {
2031
+ logger.info(
2032
+ ` Mirrors: ${cleanedMirrorDirs.map((d) => path13.relative(projectRoot, d)).join(", ")} (cleaned)`
2033
+ );
2034
+ }
1739
2035
  }
1740
2036
  async function runPartialUninstall(args) {
1741
2037
  const { projectRoot, installedManifest, pkg, resources, ids, opts } = args;
@@ -1771,12 +2067,19 @@ async function runPartialUninstall(args) {
1771
2067
  for (const id of matched) {
1772
2068
  const dir = getSkillsSourceDir(projectRoot, id);
1773
2069
  try {
1774
- await fs9.rm(dir, { recursive: true, force: true });
2070
+ await fs10.rm(dir, { recursive: true, force: true });
1775
2071
  logger.debug(`Removed source dir ${dir}`);
1776
2072
  } catch (err) {
1777
2073
  logger.warn(`Failed to remove ${dir}: ${err.message}`);
1778
2074
  }
1779
2075
  }
2076
+ const config = await readProjectConfig(projectRoot);
2077
+ const cleanedMirrorDirs = await removeMirrorDirs(
2078
+ projectRoot,
2079
+ config?.packages?.skills?.scope,
2080
+ config?.packages?.skills?.ides,
2081
+ matched
2082
+ );
1780
2083
  const lock = await readSkillsLock(projectRoot);
1781
2084
  if (lock) {
1782
2085
  for (const id of matched) delete lock.skills[id];
@@ -1790,10 +2093,69 @@ async function runPartialUninstall(args) {
1790
2093
  `Removed ${matched.length} skill(s): ${matched.join(", ")}`
1791
2094
  );
1792
2095
  logger.info(` Files: ${removed.length}`);
2096
+ if (cleanedMirrorDirs.length > 0) {
2097
+ logger.info(
2098
+ ` Mirrors: ${cleanedMirrorDirs.map((d) => path13.relative(projectRoot, d)).join(", ")} (cleaned)`
2099
+ );
2100
+ }
1793
2101
  if (missing.length > 0) {
1794
2102
  logger.info(` Skipped: ${missing.join(", ")} (not installed)`);
1795
2103
  }
1796
2104
  }
2105
+ async function listSkillSourceNames(skillsRoot) {
2106
+ try {
2107
+ const entries = await fs10.readdir(skillsRoot, { withFileTypes: true });
2108
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
2109
+ } catch {
2110
+ return [];
2111
+ }
2112
+ }
2113
+ function collectSkillNames(args) {
2114
+ const set = new Set(args.fromSources);
2115
+ for (const r of args.fromResources) {
2116
+ set.add(r.id.split(":")[0]);
2117
+ }
2118
+ return [...set];
2119
+ }
2120
+ async function removeMirrorDirs(projectRoot, scope, ides, skillNames) {
2121
+ if (skillNames.length === 0) return [];
2122
+ const targetIdes = ides && ides.length > 0 ? ides.filter((i) => i === "qoder" || i === "claude") : [...ALL_IDE_KINDS];
2123
+ const targetScope = scope === "global" ? "global" : "project";
2124
+ const removed = [];
2125
+ for (const ide of targetIdes) {
2126
+ const adapter = getAdapter(ide);
2127
+ for (const name of skillNames) {
2128
+ const dir = adapter.getSkillTargetDir(name, targetScope, projectRoot);
2129
+ let existed = true;
2130
+ try {
2131
+ await fs10.access(dir);
2132
+ } catch {
2133
+ existed = false;
2134
+ }
2135
+ if (!existed) continue;
2136
+ try {
2137
+ await fs10.rm(dir, { recursive: true, force: true });
2138
+ removed.push(dir);
2139
+ logger.debug(`Removed mirror dir ${dir}`);
2140
+ } catch (err) {
2141
+ logger.warn(`Failed to remove ${dir}: ${err.message}`);
2142
+ }
2143
+ }
2144
+ if (removed.length > 0) {
2145
+ const skillsParent = path13.dirname(
2146
+ adapter.getSkillTargetDir("placeholder", targetScope, projectRoot)
2147
+ );
2148
+ try {
2149
+ const remaining = await fs10.readdir(skillsParent);
2150
+ if (remaining.length === 0) {
2151
+ await fs10.rmdir(skillsParent);
2152
+ }
2153
+ } catch {
2154
+ }
2155
+ }
2156
+ }
2157
+ return removed;
2158
+ }
1797
2159
  function skillIdOf(r) {
1798
2160
  return r.id.split(":")[0];
1799
2161
  }
@@ -1815,7 +2177,26 @@ function dedupe(values) {
1815
2177
  import { Command as Command11 } from "commander";
1816
2178
 
1817
2179
  // src/core/skills-sync.ts
1818
- import * as fs10 from "fs/promises";
2180
+ import * as path14 from "path";
2181
+ import * as fs11 from "fs/promises";
2182
+ import { createRequire as createRequire4 } from "module";
2183
+ import { loadSkillsPackageManifest as loadSkillsPackageManifest2 } from "@teamix-evo/registry";
2184
+ var require5 = createRequire4(import.meta.url);
2185
+ async function readSkillMetaFromUpstream(skillId) {
2186
+ try {
2187
+ const pkgJson = require5.resolve("@teamix-evo/skills/package.json");
2188
+ const packageRoot = path14.dirname(pkgJson);
2189
+ const manifest = await loadSkillsPackageManifest2(packageRoot);
2190
+ const entry = manifest.skills.find((s) => s.id === skillId);
2191
+ if (!entry) return null;
2192
+ return {
2193
+ updateStrategy: entry.updateStrategy,
2194
+ managedRegions: entry.managedRegions
2195
+ };
2196
+ } catch {
2197
+ return null;
2198
+ }
2199
+ }
1819
2200
  var SKILLS_PACKAGE_DEFAULT = "@teamix-evo/skills";
1820
2201
  async function runSkillsSync(options) {
1821
2202
  const { projectRoot, names } = options;
@@ -1849,13 +2230,15 @@ async function runSkillsSync(options) {
1849
2230
  logger.warn(`Skill "${skillId}" has no IDE mirror targets; skipped.`);
1850
2231
  continue;
1851
2232
  }
2233
+ const upstreamMeta = await readSkillMetaFromUpstream(skillId);
1852
2234
  const result = await syncSkillsToIdes({
1853
2235
  projectRoot,
1854
2236
  skills: [
1855
2237
  {
1856
2238
  id: skillId,
1857
2239
  name: skillId,
1858
- updateStrategy: "regenerable"
2240
+ updateStrategy: upstreamMeta?.updateStrategy ?? "regenerable",
2241
+ managedRegions: upstreamMeta?.managedRegions
1859
2242
  }
1860
2243
  ],
1861
2244
  ides,
@@ -1882,7 +2265,7 @@ async function runSkillsSync(options) {
1882
2265
  }
1883
2266
  async function dirExists(p) {
1884
2267
  try {
1885
- const stat4 = await fs10.stat(p);
2268
+ const stat4 = await fs11.stat(p);
1886
2269
  return stat4.isDirectory();
1887
2270
  } catch {
1888
2271
  return false;
@@ -1976,8 +2359,8 @@ function parseScope2(input) {
1976
2359
  import { Command as Command12 } from "commander";
1977
2360
 
1978
2361
  // src/core/skills-doctor.ts
1979
- import * as path13 from "path";
1980
- import * as fs11 from "fs/promises";
2362
+ import * as path15 from "path";
2363
+ import * as fs12 from "fs/promises";
1981
2364
  async function runSkillsDoctor(options) {
1982
2365
  const { projectRoot } = options;
1983
2366
  const lock = await readSkillsLock(projectRoot);
@@ -1999,8 +2382,8 @@ async function runSkillsDoctor(options) {
1999
2382
  const sourceFiles = await walkDir(sourceDir);
2000
2383
  const sourceContents = /* @__PURE__ */ new Map();
2001
2384
  for (const f of sourceFiles) {
2002
- const rel2 = path13.relative(sourceDir, f);
2003
- sourceContents.set(rel2, await fs11.readFile(f, "utf-8"));
2385
+ const rel2 = path15.relative(sourceDir, f);
2386
+ sourceContents.set(rel2, await fs12.readFile(f, "utf-8"));
2004
2387
  }
2005
2388
  for (const ide of entry.mirroredTo) {
2006
2389
  const adapter = getAdapter(ide);
@@ -2021,7 +2404,7 @@ async function runSkillsDoctor(options) {
2021
2404
  continue;
2022
2405
  }
2023
2406
  for (const [rel2, sourceContent] of sourceContents.entries()) {
2024
- const mirrorFile = path13.join(mirrorDir, rel2);
2407
+ const mirrorFile = path15.join(mirrorDir, rel2);
2025
2408
  if (!await fileExists(mirrorFile)) {
2026
2409
  findings.push({
2027
2410
  kind: "missing-mirror",
@@ -2033,7 +2416,7 @@ async function runSkillsDoctor(options) {
2033
2416
  });
2034
2417
  continue;
2035
2418
  }
2036
- const mirrorContent = await fs11.readFile(mirrorFile, "utf-8");
2419
+ const mirrorContent = await fs12.readFile(mirrorFile, "utf-8");
2037
2420
  if (computeHash(mirrorContent) !== computeHash(sourceContent)) {
2038
2421
  findings.push({
2039
2422
  kind: "mirror-drift",
@@ -2054,7 +2437,7 @@ async function runSkillsDoctor(options) {
2054
2437
  }
2055
2438
  async function dirExists2(p) {
2056
2439
  try {
2057
- const stat4 = await fs11.stat(p);
2440
+ const stat4 = await fs12.stat(p);
2058
2441
  return stat4.isDirectory();
2059
2442
  } catch {
2060
2443
  return false;
@@ -2162,6 +2545,7 @@ async function runUiInit(options) {
2162
2545
  rsc
2163
2546
  };
2164
2547
  await writeProjectConfig(projectRoot, config);
2548
+ await ensureMcpJson(projectRoot);
2165
2549
  return {
2166
2550
  status: "installed",
2167
2551
  aliases,
@@ -2182,6 +2566,12 @@ var initCommand2 = new Command14("init").description(
2182
2566
  try {
2183
2567
  const ide = detectIde();
2184
2568
  const projectRoot = ide.getProjectRoot();
2569
+ if (!hasPackageJson(projectRoot)) {
2570
+ logger.error(
2571
+ "No package.json found in current directory. Please run this command in a valid project root."
2572
+ );
2573
+ process.exit(1);
2574
+ }
2185
2575
  const cfg = await resolveConfig(opts);
2186
2576
  const result = await runUiInit({
2187
2577
  projectRoot,
@@ -2282,23 +2672,23 @@ async function resolveConfig(opts) {
2282
2672
  import { Command as Command15 } from "commander";
2283
2673
 
2284
2674
  // src/core/ui-client.ts
2285
- import * as path14 from "path";
2286
- import * as fs12 from "fs/promises";
2287
- import { createRequire as createRequire3 } from "module";
2675
+ import * as path16 from "path";
2676
+ import * as fs13 from "fs/promises";
2677
+ import { createRequire as createRequire5 } from "module";
2288
2678
  import { loadUiPackageManifest } from "@teamix-evo/registry";
2289
- var require4 = createRequire3(import.meta.url);
2679
+ var require6 = createRequire5(import.meta.url);
2290
2680
  function resolvePackageRoot2(packageName) {
2291
- const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
2292
- return path14.dirname(pkgJsonPath);
2681
+ const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
2682
+ return path16.dirname(pkgJsonPath);
2293
2683
  }
2294
2684
  async function loadUiData(packageName) {
2295
2685
  const packageRoot = resolvePackageRoot2(packageName);
2296
2686
  logger.debug(`Resolved ui package root: ${packageRoot}`);
2297
2687
  const manifest = await loadUiPackageManifest(packageRoot);
2298
2688
  let data = {};
2299
- const dataPath = path14.join(packageRoot, "_data.json");
2689
+ const dataPath = path16.join(packageRoot, "_data.json");
2300
2690
  try {
2301
- const raw = await fs12.readFile(dataPath, "utf-8");
2691
+ const raw = await fs13.readFile(dataPath, "utf-8");
2302
2692
  data = JSON.parse(raw);
2303
2693
  } catch (err) {
2304
2694
  if (err.code !== "ENOENT") {
@@ -2310,8 +2700,8 @@ async function loadUiData(packageName) {
2310
2700
  }
2311
2701
 
2312
2702
  // src/core/ui-installer.ts
2313
- import * as path15 from "path";
2314
- import * as fs13 from "fs/promises";
2703
+ import * as path17 from "path";
2704
+ import * as fs14 from "fs/promises";
2315
2705
  import { resolveUiEntryOrder } from "@teamix-evo/registry";
2316
2706
 
2317
2707
  // src/utils/transform-imports.ts
@@ -2324,13 +2714,13 @@ var SOURCE_ROOT_TO_ALIAS_KEY = {
2324
2714
  function rewriteImports(source, aliases) {
2325
2715
  return source.replace(
2326
2716
  /(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
2327
- (full, quote, root, rest) => {
2717
+ (full, quote2, root, rest) => {
2328
2718
  const aliasKey = SOURCE_ROOT_TO_ALIAS_KEY[root];
2329
2719
  if (!aliasKey) return full;
2330
2720
  const alias = aliases[aliasKey];
2331
2721
  const normalized = aliasToImportPath(alias);
2332
2722
  const flatRest = flattenRestPath(rest);
2333
- return `${quote}${normalized}${flatRest}${quote}`;
2723
+ return `${quote2}${normalized}${flatRest}${quote2}`;
2334
2724
  }
2335
2725
  );
2336
2726
  }
@@ -2351,12 +2741,12 @@ function aliasToImportPath(alias) {
2351
2741
  }
2352
2742
 
2353
2743
  // src/core/ui-installer.ts
2354
- var DESIGN_COMPONENTS_DIR = ".teamix-evo/design/components";
2355
2744
  async function installUiEntries(options) {
2356
2745
  const {
2357
2746
  projectRoot,
2358
2747
  manifest,
2359
2748
  packageRoot,
2749
+ entryPackageRoot,
2360
2750
  aliases,
2361
2751
  requested,
2362
2752
  skipExisting = true
@@ -2365,7 +2755,6 @@ async function installUiEntries(options) {
2365
2755
  const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
2366
2756
  const resources = [];
2367
2757
  const npmDeps = {};
2368
- const metaFiles = [];
2369
2758
  let written = 0;
2370
2759
  let skipped = 0;
2371
2760
  for (const id of orderedIds) {
@@ -2384,8 +2773,9 @@ async function installUiEntries(options) {
2384
2773
  skipped++;
2385
2774
  continue;
2386
2775
  }
2387
- const sourceAbs = path15.resolve(packageRoot, file.source);
2388
- const raw = await fs13.readFile(sourceAbs, "utf-8");
2776
+ const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
2777
+ const sourceAbs = path17.resolve(rootForEntry, file.source);
2778
+ const raw = await fs14.readFile(sourceAbs, "utf-8");
2389
2779
  const transformed = rewriteImports(raw, aliases);
2390
2780
  await writeFileSafe(targetAbs, transformed);
2391
2781
  written++;
@@ -2397,32 +2787,13 @@ async function installUiEntries(options) {
2397
2787
  strategy: entry.updateStrategy ?? "frozen"
2398
2788
  });
2399
2789
  }
2400
- if (entry.meta) {
2401
- const metaSourceAbs = path15.resolve(packageRoot, entry.meta);
2402
- const metaContent = await fs13.readFile(metaSourceAbs, "utf-8");
2403
- const metaTargetAbs = path15.join(
2404
- projectRoot,
2405
- DESIGN_COMPONENTS_DIR,
2406
- `${entry.id}.meta.md`
2407
- );
2408
- await writeFileSafe(metaTargetAbs, metaContent);
2409
- metaFiles.push(metaTargetAbs);
2410
- resources.push({
2411
- id: `${entry.id}:meta`,
2412
- target: metaTargetAbs,
2413
- hash: computeHash(metaContent),
2414
- strategy: "regenerable"
2415
- });
2416
- logger.info(` meta: ${rel(projectRoot, metaTargetAbs)}`);
2417
- }
2418
2790
  }
2419
2791
  return {
2420
2792
  orderedIds,
2421
2793
  resources,
2422
2794
  npmDependencies: npmDeps,
2423
2795
  written,
2424
- skipped,
2425
- metaFiles
2796
+ skipped
2426
2797
  };
2427
2798
  }
2428
2799
  function resolveTargetPath(projectRoot, aliases, entry, file) {
@@ -2432,16 +2803,16 @@ function resolveTargetPath(projectRoot, aliases, entry, file) {
2432
2803
  `Entry "${entry.id}" requires alias "${file.targetAlias}" but it is not configured.`
2433
2804
  );
2434
2805
  }
2435
- return path15.join(projectRoot, aliasDir, file.targetName);
2806
+ return path17.join(projectRoot, aliasDir, file.targetName);
2436
2807
  }
2437
2808
  function rel(projectRoot, abs) {
2438
- return path15.relative(projectRoot, abs);
2809
+ return path17.relative(projectRoot, abs);
2439
2810
  }
2440
2811
 
2441
2812
  // src/core/ui-add.ts
2442
2813
  var DEFAULT_UI_PACKAGE = "@teamix-evo/ui";
2443
2814
  async function runUiAdd(options) {
2444
- const { projectRoot, ids, overwrite } = options;
2815
+ const { projectRoot, ids, overwrite, includeDeprecated } = options;
2445
2816
  const packageName = options.packageName ?? DEFAULT_UI_PACKAGE;
2446
2817
  if (ids.length === 0) {
2447
2818
  throw new Error("At least one entry id must be provided.");
@@ -2454,7 +2825,21 @@ async function runUiAdd(options) {
2454
2825
  );
2455
2826
  }
2456
2827
  const { manifest, packageRoot } = await loadUiData(packageName);
2457
- const knownIds = new Set(manifest.entries.map((e) => e.id));
2828
+ const archived = manifest.deprecatedEntries ?? [];
2829
+ const archivedIds = new Set(archived.map((e) => e.id));
2830
+ const activeIds = new Set(manifest.entries.map((e) => e.id));
2831
+ const requestedDeprecated = ids.filter((id) => archivedIds.has(id));
2832
+ if (requestedDeprecated.length > 0 && !includeDeprecated) {
2833
+ const list = requestedDeprecated.map((s) => `"${s}"`).join(", ");
2834
+ throw new Error(
2835
+ `Refusing to install deprecated entr${requestedDeprecated.length === 1 ? "y" : "ies"} ${list}. These entries are archived and not part of the active distribution (ADR 0028). Pass \`--include-deprecated\` to override (e.g. for migration tooling).`
2836
+ );
2837
+ }
2838
+ const effectiveManifest = includeDeprecated ? { ...manifest, entries: [...manifest.entries, ...archived] } : manifest;
2839
+ const knownIds = /* @__PURE__ */ new Set([
2840
+ ...activeIds,
2841
+ ...includeDeprecated ? archivedIds : []
2842
+ ]);
2458
2843
  const unknown = ids.filter((id) => !knownIds.has(id));
2459
2844
  if (unknown.length > 0) {
2460
2845
  throw new Error(
@@ -2463,7 +2848,7 @@ async function runUiAdd(options) {
2463
2848
  }
2464
2849
  const result = await installUiEntries({
2465
2850
  projectRoot,
2466
- manifest,
2851
+ manifest: effectiveManifest,
2467
2852
  packageRoot,
2468
2853
  aliases: uiCfg.aliases,
2469
2854
  requested: ids,
@@ -2497,7 +2882,6 @@ async function runUiAdd(options) {
2497
2882
  orderedIds: result.orderedIds,
2498
2883
  written: result.written,
2499
2884
  skipped: result.skipped,
2500
- metaFiles: result.metaFiles,
2501
2885
  npmDependencies: result.npmDependencies,
2502
2886
  resources: result.resources
2503
2887
  };
@@ -2512,46 +2896,48 @@ function mergeResources(prior, next) {
2512
2896
  // src/commands/ui/add.ts
2513
2897
  var addCommand2 = new Command15("add").description(
2514
2898
  "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A ui entry\uFF08\u6309 id\uFF0C\u81EA\u52A8\u5C55\u5F00 registryDependencies\uFF09"
2515
- ).argument("<ids...>", 'entry id \u5217\u8868\uFF0C\u5982 "button" "dialog"').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6\uFF08\u7ED5\u8FC7 frozen \u8DF3\u8FC7\uFF09").action(async (ids, opts) => {
2516
- try {
2517
- const ide = detectIde();
2518
- const projectRoot = ide.getProjectRoot();
2519
- logger.info(`Installing entries: ${ids.join(", ")}`);
2520
- const result = await runUiAdd({
2521
- projectRoot,
2522
- ids,
2523
- overwrite: opts.overwrite
2524
- });
2525
- logger.success(
2526
- `UI add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
2527
- );
2528
- logger.info("");
2529
- logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
2530
- const npmDeps = Object.entries(result.npmDependencies);
2531
- if (npmDeps.length > 0) {
2532
- logger.info("");
2533
- logger.info("Install npm dependencies in your project:");
2534
- const installCmd = npmDeps.map(([name, range]) => `${name}@${range}`).join(" ");
2535
- logger.info(` pnpm add ${installCmd}`);
2536
- logger.info(` # or: npm install ${installCmd}`);
2537
- }
2538
- if (result.metaFiles.length > 0) {
2539
- logger.info("");
2540
- logger.info(
2541
- "Component meta dropped under .teamix-evo/design/components/ (AI-readable)."
2899
+ ).argument("<ids...>", 'entry id \u5217\u8868\uFF0C\u5982 "button" "dialog"').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6\uFF08\u7ED5\u8FC7 frozen \u8DF3\u8FC7\uFF09").option(
2900
+ "--include-deprecated",
2901
+ "\u5141\u8BB8\u5B89\u88C5\u5DF2\u5F52\u6863\u7684 deprecated entry\uFF08\u4EC5\u8FC1\u79FB / \u5BA1\u8BA1\u573A\u666F\uFF0CADR 0028\uFF09"
2902
+ ).action(
2903
+ async (ids, opts) => {
2904
+ try {
2905
+ const ide = detectIde();
2906
+ const projectRoot = ide.getProjectRoot();
2907
+ logger.info(`Installing entries: ${ids.join(", ")}`);
2908
+ const result = await runUiAdd({
2909
+ projectRoot,
2910
+ ids,
2911
+ overwrite: opts.overwrite,
2912
+ includeDeprecated: opts.includeDeprecated
2913
+ });
2914
+ logger.success(
2915
+ `UI add complete: ${result.written} written, ${result.skipped} skipped.`
2542
2916
  );
2917
+ logger.info("");
2918
+ logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
2919
+ const npmDeps = Object.entries(result.npmDependencies);
2920
+ if (npmDeps.length > 0) {
2921
+ logger.info("");
2922
+ logger.info("Install npm dependencies in your project:");
2923
+ const installCmd = npmDeps.map(([name, range]) => `${name}@${range}`).join(" ");
2924
+ logger.info(` pnpm add ${installCmd}`);
2925
+ logger.info(` # or: npm install ${installCmd}`);
2926
+ }
2927
+ } catch (err) {
2928
+ const message = err.message;
2929
+ if (message.startsWith("UI not initialized")) {
2930
+ logger.error(
2931
+ "UI not initialized. Run `npx teamix-evo ui init` first."
2932
+ );
2933
+ } else {
2934
+ logger.error(`Failed to add ui entries: ${message}`);
2935
+ }
2936
+ logger.debug(err.stack ?? "");
2937
+ process.exitCode = 1;
2543
2938
  }
2544
- } catch (err) {
2545
- const message = err.message;
2546
- if (message.startsWith("UI not initialized")) {
2547
- logger.error("UI not initialized. Run `npx teamix-evo ui init` first.");
2548
- } else {
2549
- logger.error(`Failed to add ui entries: ${message}`);
2550
- }
2551
- logger.debug(err.stack ?? "");
2552
- process.exitCode = 1;
2553
2939
  }
2554
- });
2940
+ );
2555
2941
 
2556
2942
  // src/commands/ui/list.ts
2557
2943
  import { Command as Command16 } from "commander";
@@ -2559,7 +2945,7 @@ import { Command as Command16 } from "commander";
2559
2945
  // src/core/ui-list.ts
2560
2946
  var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
2561
2947
  async function runUiList(options) {
2562
- const { projectRoot, installedOnly } = options;
2948
+ const { projectRoot, installedOnly, includeDeprecated } = options;
2563
2949
  const packageName = options.packageName ?? DEFAULT_UI_PACKAGE2;
2564
2950
  const { manifest } = await loadUiData(packageName);
2565
2951
  const installedManifest = await readInstalledManifest(projectRoot);
@@ -2571,65 +2957,77 @@ async function runUiList(options) {
2571
2957
  const colon = r.id.indexOf(":");
2572
2958
  installedIds.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
2573
2959
  }
2574
- const entries = manifest.entries.filter((e) => !installedOnly || installedIds.has(e.id)).map((e) => ({
2575
- id: e.id,
2576
- type: e.type,
2577
- description: e.description,
2578
- installed: installedIds.has(e.id)
2960
+ const archived = manifest.deprecatedEntries ?? [];
2961
+ const pool = includeDeprecated ? [
2962
+ ...manifest.entries.map((e) => ({ entry: e, deprecated: false })),
2963
+ ...archived.map((e) => ({ entry: e, deprecated: true }))
2964
+ ] : manifest.entries.map((e) => ({ entry: e, deprecated: false }));
2965
+ const entries = pool.filter(({ entry }) => !installedOnly || installedIds.has(entry.id)).map(({ entry, deprecated }) => ({
2966
+ id: entry.id,
2967
+ type: entry.type,
2968
+ description: entry.description,
2969
+ installed: installedIds.has(entry.id),
2970
+ deprecated
2579
2971
  }));
2580
2972
  return {
2581
2973
  packageName,
2582
- total: manifest.entries.length,
2974
+ total: manifest.entries.length + (includeDeprecated ? archived.length : 0),
2583
2975
  installedCount: installedIds.size,
2584
2976
  entries
2585
2977
  };
2586
2978
  }
2587
2979
 
2588
2980
  // src/commands/ui/list.ts
2589
- var listCommand3 = new Command16("list").description("\u5217\u51FA @teamix-evo/ui \u7684\u6240\u6709 entry \u53CA\u5DF2\u5B89\u88C5\u72B6\u6001").option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 entry").action(async (opts) => {
2590
- try {
2591
- const ide = detectIde();
2592
- const projectRoot = ide.getProjectRoot();
2593
- const { entries, total, installedCount } = await runUiList({
2594
- projectRoot,
2595
- installedOnly: opts.installed
2596
- });
2597
- if (entries.length === 0) {
2981
+ var listCommand3 = new Command16("list").description("\u5217\u51FA @teamix-evo/ui \u7684\u6240\u6709 entry \u53CA\u5DF2\u5B89\u88C5\u72B6\u6001").option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 entry").option(
2982
+ "--include-deprecated",
2983
+ "\u540C\u65F6\u5217\u51FA\u5DF2\u5F52\u6863\u7684 deprecated entry\uFF08\u9ED8\u8BA4\u9690\u85CF\uFF0CADR 0028\uFF09"
2984
+ ).action(
2985
+ async (opts) => {
2986
+ try {
2987
+ const ide = detectIde();
2988
+ const projectRoot = ide.getProjectRoot();
2989
+ const { entries, total, installedCount } = await runUiList({
2990
+ projectRoot,
2991
+ installedOnly: opts.installed,
2992
+ includeDeprecated: opts.includeDeprecated
2993
+ });
2994
+ if (entries.length === 0) {
2995
+ logger.info(
2996
+ opts.installed ? "No ui entries installed." : "No ui entries available."
2997
+ );
2998
+ return;
2999
+ }
3000
+ const idWidth = Math.max(...entries.map((e) => e.id.length), 4);
3001
+ const typeWidth = Math.max(...entries.map((e) => e.type.length), 4);
2598
3002
  logger.info(
2599
- opts.installed ? "No ui entries installed." : "No ui entries available."
3003
+ `${"ID".padEnd(idWidth)} ${"TYPE".padEnd(
3004
+ typeWidth
3005
+ )} STATUS DESCRIPTION`
2600
3006
  );
2601
- return;
2602
- }
2603
- const idWidth = Math.max(...entries.map((e) => e.id.length), 4);
2604
- const typeWidth = Math.max(...entries.map((e) => e.type.length), 4);
2605
- logger.info(
2606
- `${"ID".padEnd(idWidth)} ${"TYPE".padEnd(
2607
- typeWidth
2608
- )} STATUS DESCRIPTION`
2609
- );
2610
- logger.info(
2611
- `${"\u2500".repeat(idWidth)} ${"\u2500".repeat(
2612
- typeWidth
2613
- )} \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
2614
- );
2615
- for (const e of entries) {
2616
- const status = e.installed ? "INSTALLED" : "\u2013";
2617
3007
  logger.info(
2618
- `${e.id.padEnd(idWidth)} ${e.type.padEnd(
3008
+ `${"\u2500".repeat(idWidth)} ${"\u2500".repeat(
2619
3009
  typeWidth
2620
- )} ${status.padEnd(7)} ${e.description}`
3010
+ )} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
2621
3011
  );
3012
+ for (const e of entries) {
3013
+ const status = e.deprecated ? e.installed ? "INST/DEPR" : "DEPRECATED" : e.installed ? "INSTALLED" : "\u2013";
3014
+ logger.info(
3015
+ `${e.id.padEnd(idWidth)} ${e.type.padEnd(
3016
+ typeWidth
3017
+ )} ${status.padEnd(10)} ${e.description}`
3018
+ );
3019
+ }
3020
+ logger.info("");
3021
+ logger.info(
3022
+ `Total: ${total} entr${total === 1 ? "y" : "ies"}, ${installedCount} installed.`
3023
+ );
3024
+ } catch (err) {
3025
+ logger.error(`Failed to list ui entries: ${err.message}`);
3026
+ logger.debug(err.stack ?? "");
3027
+ process.exitCode = 1;
2622
3028
  }
2623
- logger.info("");
2624
- logger.info(
2625
- `Total: ${total} entr${total === 1 ? "y" : "ies"}, ${installedCount} installed.`
2626
- );
2627
- } catch (err) {
2628
- logger.error(`Failed to list ui entries: ${err.message}`);
2629
- logger.debug(err.stack ?? "");
2630
- process.exitCode = 1;
2631
3029
  }
2632
- });
3030
+ );
2633
3031
 
2634
3032
  // src/commands/ui/index.ts
2635
3033
  var uiCommand = new Command17("ui").description(
@@ -2640,22 +3038,23 @@ uiCommand.addCommand(addCommand2);
2640
3038
  uiCommand.addCommand(listCommand3);
2641
3039
 
2642
3040
  // src/commands/biz-ui/index.ts
2643
- import { Command as Command20 } from "commander";
3041
+ import { Command as Command21 } from "commander";
2644
3042
 
2645
3043
  // src/commands/biz-ui/add.ts
2646
3044
  import { Command as Command18 } from "commander";
2647
3045
 
2648
3046
  // src/core/variant-ui-add.ts
2649
- import * as path16 from "path";
2650
- import { createRequire as createRequire4 } from "module";
3047
+ import * as path18 from "path";
3048
+ import { createRequire as createRequire6 } from "module";
2651
3049
  import {
3050
+ loadUiPackageManifest as loadUiPackageManifest2,
2652
3051
  loadVariantUiPackageCatalog,
2653
3052
  loadVariantUiPackageManifest
2654
3053
  } from "@teamix-evo/registry";
2655
- var require5 = createRequire4(import.meta.url);
3054
+ var require7 = createRequire6(import.meta.url);
2656
3055
  function resolvePackageRoot3(packageName) {
2657
- const pkgJsonPath = require5.resolve(`${packageName}/package.json`);
2658
- return path16.dirname(pkgJsonPath);
3056
+ const pkgJsonPath = require7.resolve(`${packageName}/package.json`);
3057
+ return path18.dirname(pkgJsonPath);
2659
3058
  }
2660
3059
  async function runVariantUiAdd(packageName, options) {
2661
3060
  const { projectRoot, variant, ids, overwrite } = options;
@@ -2678,27 +3077,42 @@ async function runVariantUiAdd(packageName, options) {
2678
3077
  `Variant "${variant}" not found in ${fullPackageName}. Known variants: ${known}. Hint: \`teamix-evo ${packageName} list-variants\` shows all.`
2679
3078
  );
2680
3079
  }
2681
- const variantDir = path16.join(packageRoot, "variants", variant);
3080
+ const variantDir = path18.join(packageRoot, "variants", variant);
2682
3081
  const variantManifest = await loadVariantUiPackageManifest(variantDir);
2683
3082
  const knownIds = new Set(variantManifest.entries.map((e) => e.id));
2684
3083
  const unknown = ids.filter((id) => !knownIds.has(id));
2685
3084
  if (unknown.length > 0) {
2686
3085
  throw new Error(
2687
- `Unknown entry id(s) in ${packageName}#${variant}: ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ${packageName} list --variant ${variant}\` to see options.`
3086
+ `Unknown entry id(s) in ${packageName}#${variant}: ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ${packageName} list-variants\` to see this package's variants, or \`teamix-evo ${packageName} list --variant ${variant}\` for its entries.`
2688
3087
  );
2689
3088
  }
3089
+ const uiPackageRoot = resolvePackageRoot3("@teamix-evo/ui");
3090
+ const uiManifest = await loadUiPackageManifest2(uiPackageRoot);
3091
+ const entryPackageRoot = /* @__PURE__ */ new Map();
3092
+ const mergedEntries = [];
3093
+ for (const e of variantManifest.entries) {
3094
+ entryPackageRoot.set(e.id, variantDir);
3095
+ mergedEntries.push(e);
3096
+ }
3097
+ for (const e of uiManifest.entries) {
3098
+ if (entryPackageRoot.has(e.id)) continue;
3099
+ entryPackageRoot.set(e.id, uiPackageRoot);
3100
+ mergedEntries.push(e);
3101
+ }
2690
3102
  const adaptedManifest = {
2691
3103
  schemaVersion: 1,
2692
3104
  package: "ui",
2693
3105
  version: variantManifest.version,
2694
3106
  engines: variantManifest.engines,
2695
- entries: variantManifest.entries
3107
+ entries: mergedEntries
2696
3108
  };
2697
3109
  const result = await installUiEntries({
2698
3110
  projectRoot,
2699
3111
  manifest: adaptedManifest,
2700
3112
  packageRoot: variantDir,
2701
- // sources resolved relative to variant dir
3113
+ // default for variant entries
3114
+ entryPackageRoot,
3115
+ // ui entries resolve from uiPackageRoot
2702
3116
  aliases: uiCfg.aliases,
2703
3117
  requested: ids,
2704
3118
  skipExisting: !overwrite
@@ -2730,7 +3144,6 @@ async function runVariantUiAdd(packageName, options) {
2730
3144
  orderedIds: result.orderedIds,
2731
3145
  written: result.written,
2732
3146
  skipped: result.skipped,
2733
- metaFiles: result.metaFiles,
2734
3147
  npmDependencies: result.npmDependencies,
2735
3148
  resources: result.resources
2736
3149
  };
@@ -2767,6 +3180,36 @@ async function listBizUiVariants(packageRoot) {
2767
3180
  async function listTemplatesVariants(packageRoot) {
2768
3181
  return listVariantUi("templates", packageRoot);
2769
3182
  }
3183
+ async function listVariantUiEntries(packageName, variant, packageRoot) {
3184
+ const fullPackageName = `@teamix-evo/${packageName}`;
3185
+ const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
3186
+ const catalog = await loadVariantUiPackageCatalog(root);
3187
+ if (!catalog.variants.some((v) => v.name === variant)) {
3188
+ const known = catalog.variants.map((v) => v.name).join(", ");
3189
+ throw new Error(
3190
+ `Variant "${variant}" not found in ${fullPackageName}. Known: ${known}.`
3191
+ );
3192
+ }
3193
+ const variantDir = path18.join(root, "variants", variant);
3194
+ const variantManifest = await loadVariantUiPackageManifest(variantDir);
3195
+ return {
3196
+ packageName: fullPackageName,
3197
+ variant,
3198
+ entries: variantManifest.entries.map((e) => ({
3199
+ id: e.id,
3200
+ name: e.name,
3201
+ type: e.type,
3202
+ description: e.description,
3203
+ registryDependencies: e.registryDependencies ?? []
3204
+ }))
3205
+ };
3206
+ }
3207
+ async function listBizUiEntries(variant, packageRoot) {
3208
+ return listVariantUiEntries("biz-ui", variant, packageRoot);
3209
+ }
3210
+ async function listTemplatesEntries(variant, packageRoot) {
3211
+ return listVariantUiEntries("templates", variant, packageRoot);
3212
+ }
2770
3213
 
2771
3214
  // src/commands/biz-ui/add.ts
2772
3215
  var addCommand3 = new Command18("add").description(
@@ -2781,6 +3224,12 @@ var addCommand3 = new Command18("add").description(
2781
3224
  }
2782
3225
  const ide = detectIde();
2783
3226
  const projectRoot = ide.getProjectRoot();
3227
+ const projectVariant = await readTokensVariant(projectRoot);
3228
+ if (projectVariant && projectVariant !== opts.variant) {
3229
+ throw new Error(
3230
+ `Variant mismatch \u2014 project is locked to "${projectVariant}" but you requested "${opts.variant}". Fix: run \`teamix-evo tokens uninstall -y\` then \`teamix-evo tokens init ${opts.variant}\` to switch, or re-run with \`--variant ${projectVariant}\` to install for the current variant.`
3231
+ );
3232
+ }
2784
3233
  logger.info(
2785
3234
  `Installing biz-ui entries from variant "${opts.variant}": ${ids.join(", ")}`
2786
3235
  );
@@ -2791,7 +3240,7 @@ var addCommand3 = new Command18("add").description(
2791
3240
  overwrite: opts.overwrite
2792
3241
  });
2793
3242
  logger.success(
2794
- `biz-ui add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
3243
+ `biz-ui add complete: ${result.written} written, ${result.skipped} skipped.`
2795
3244
  );
2796
3245
  logger.info("");
2797
3246
  logger.info(`Variant: ${result.variant}`);
@@ -2811,9 +3260,35 @@ var addCommand3 = new Command18("add").description(
2811
3260
  }
2812
3261
  );
2813
3262
 
2814
- // src/commands/biz-ui/list-variants.ts
3263
+ // src/commands/biz-ui/list.ts
2815
3264
  import { Command as Command19 } from "commander";
2816
- var listVariantsCommand2 = new Command19("list-variants").description("\u5217\u51FA @teamix-evo/biz-ui \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u4E1A\u52A1\u53D8\u4F53").action(async () => {
3265
+ var listCommand4 = new Command19("list").description("\u5217\u51FA\u6307\u5B9A\u53D8\u4F53\u4E0B\u7684 biz-ui entries").requiredOption("--variant <name>", "\u53D8\u4F53\u540D\uFF08\u5982 opentrek / uni-manager\uFF09").action(async (opts) => {
3266
+ try {
3267
+ const result = await listBizUiEntries(opts.variant);
3268
+ logger.info(`${result.packageName}#${result.variant} entries:`);
3269
+ logger.info("");
3270
+ if (result.entries.length === 0) {
3271
+ logger.info(" (no entries \u2014 variant skeleton, awaiting components)");
3272
+ return;
3273
+ }
3274
+ for (const e of result.entries) {
3275
+ const deps = e.registryDependencies.length ? ` [deps: ${e.registryDependencies.join(", ")}]` : "";
3276
+ logger.info(` ${e.id} (${e.type})${deps}`);
3277
+ if (e.description) logger.info(` ${e.description}`);
3278
+ logger.info("");
3279
+ }
3280
+ logger.info(
3281
+ `Install: teamix-evo biz-ui add <id> --variant ${result.variant}`
3282
+ );
3283
+ } catch (err) {
3284
+ logger.error(`Failed: ${err.message}`);
3285
+ process.exitCode = 1;
3286
+ }
3287
+ });
3288
+
3289
+ // src/commands/biz-ui/list-variants.ts
3290
+ import { Command as Command20 } from "commander";
3291
+ var listVariantsCommand2 = new Command20("list-variants").description("\u5217\u51FA @teamix-evo/biz-ui \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u4E1A\u52A1\u53D8\u4F53").action(async () => {
2817
3292
  try {
2818
3293
  const result = await listBizUiVariants();
2819
3294
  logger.info(`Available biz-ui variants in ${result.packageName}:`);
@@ -2835,18 +3310,19 @@ var listVariantsCommand2 = new Command19("list-variants").description("\u5217\u5
2835
3310
  });
2836
3311
 
2837
3312
  // src/commands/biz-ui/index.ts
2838
- var bizUiCommand = new Command20("biz-ui").description(
3313
+ var bizUiCommand = new Command21("biz-ui").description(
2839
3314
  "\u7BA1\u7406\u4E1A\u52A1 UI \u7EC4\u4EF6(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / templates \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
2840
3315
  );
2841
3316
  bizUiCommand.addCommand(addCommand3);
3317
+ bizUiCommand.addCommand(listCommand4);
2842
3318
  bizUiCommand.addCommand(listVariantsCommand2);
2843
3319
 
2844
3320
  // src/commands/templates/index.ts
2845
- import { Command as Command23 } from "commander";
3321
+ import { Command as Command25 } from "commander";
2846
3322
 
2847
3323
  // src/commands/templates/add.ts
2848
- import { Command as Command21 } from "commander";
2849
- var addCommand4 = new Command21("add").description(
3324
+ import { Command as Command22 } from "commander";
3325
+ var addCommand4 = new Command22("add").description(
2850
3326
  "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A\u9875\u9762\u6A21\u677F(\u6309 id,\u81EA\u52A8\u5C55\u5F00 ui \u5305\u7684 registryDependencies)"
2851
3327
  ).argument("<ids...>", '\u6A21\u677F id \u5217\u8868,\u5982 "list-detail-page"').option("--variant <name>", '\u53D8\u4F53 id(\u5FC5\u586B,\u5982 "opentrek"\u3001"uni-manager")').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6").action(
2852
3328
  async (ids, opts) => {
@@ -2858,6 +3334,12 @@ var addCommand4 = new Command21("add").description(
2858
3334
  }
2859
3335
  const ide = detectIde();
2860
3336
  const projectRoot = ide.getProjectRoot();
3337
+ const projectVariant = await readTokensVariant(projectRoot);
3338
+ if (projectVariant && projectVariant !== opts.variant) {
3339
+ throw new Error(
3340
+ `Variant mismatch \u2014 project is locked to "${projectVariant}" but you requested "${opts.variant}". Fix: run \`teamix-evo tokens uninstall -y\` then \`teamix-evo tokens init ${opts.variant}\` to switch, or re-run with \`--variant ${projectVariant}\` to install for the current variant.`
3341
+ );
3342
+ }
2861
3343
  logger.info(
2862
3344
  `Installing templates from variant "${opts.variant}": ${ids.join(", ")}`
2863
3345
  );
@@ -2868,7 +3350,7 @@ var addCommand4 = new Command21("add").description(
2868
3350
  overwrite: opts.overwrite
2869
3351
  });
2870
3352
  logger.success(
2871
- `templates add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
3353
+ `templates add complete: ${result.written} written, ${result.skipped} skipped.`
2872
3354
  );
2873
3355
  logger.info("");
2874
3356
  logger.info(`Variant: ${result.variant}`);
@@ -2888,9 +3370,35 @@ var addCommand4 = new Command21("add").description(
2888
3370
  }
2889
3371
  );
2890
3372
 
3373
+ // src/commands/templates/list.ts
3374
+ import { Command as Command23 } from "commander";
3375
+ var listCommand5 = new Command23("list").description("\u5217\u51FA\u6307\u5B9A\u53D8\u4F53\u4E0B\u7684 templates entries").requiredOption("--variant <name>", "\u53D8\u4F53\u540D\uFF08\u5982 opentrek / uni-manager\uFF09").action(async (opts) => {
3376
+ try {
3377
+ const result = await listTemplatesEntries(opts.variant);
3378
+ logger.info(`${result.packageName}#${result.variant} entries:`);
3379
+ logger.info("");
3380
+ if (result.entries.length === 0) {
3381
+ logger.info(" (no entries \u2014 variant skeleton, awaiting templates)");
3382
+ return;
3383
+ }
3384
+ for (const e of result.entries) {
3385
+ const deps = e.registryDependencies.length ? ` [deps: ${e.registryDependencies.join(", ")}]` : "";
3386
+ logger.info(` ${e.id} (${e.type})${deps}`);
3387
+ if (e.description) logger.info(` ${e.description}`);
3388
+ logger.info("");
3389
+ }
3390
+ logger.info(
3391
+ `Install: teamix-evo templates add <id> --variant ${result.variant}`
3392
+ );
3393
+ } catch (err) {
3394
+ logger.error(`Failed: ${err.message}`);
3395
+ process.exitCode = 1;
3396
+ }
3397
+ });
3398
+
2891
3399
  // src/commands/templates/list-variants.ts
2892
- import { Command as Command22 } from "commander";
2893
- var listVariantsCommand3 = new Command22("list-variants").description("\u5217\u51FA @teamix-evo/templates \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u9875\u9762\u6A21\u677F\u53D8\u4F53").action(async () => {
3400
+ import { Command as Command24 } from "commander";
3401
+ var listVariantsCommand3 = new Command24("list-variants").description("\u5217\u51FA @teamix-evo/templates \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u9875\u9762\u6A21\u677F\u53D8\u4F53").action(async () => {
2894
3402
  try {
2895
3403
  const result = await listTemplatesVariants();
2896
3404
  logger.info(`Available templates variants in ${result.packageName}:`);
@@ -2912,21 +3420,427 @@ var listVariantsCommand3 = new Command22("list-variants").description("\u5217\u5
2912
3420
  });
2913
3421
 
2914
3422
  // src/commands/templates/index.ts
2915
- var templatesCommand = new Command23("templates").description(
3423
+ var templatesCommand = new Command25("templates").description(
2916
3424
  "\u7BA1\u7406\u9875\u9762\u6A21\u677F(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / biz-ui \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
2917
3425
  );
2918
3426
  templatesCommand.addCommand(addCommand4);
3427
+ templatesCommand.addCommand(listCommand5);
2919
3428
  templatesCommand.addCommand(listVariantsCommand3);
2920
3429
 
3430
+ // src/commands/logs/index.ts
3431
+ import { Command as Command28 } from "commander";
3432
+
3433
+ // src/commands/logs/analyze.ts
3434
+ import { Command as Command26 } from "commander";
3435
+ import { readFileSync, readdirSync, existsSync as existsSync2, statSync } from "fs";
3436
+ import { resolve as resolve3, join as join17 } from "path";
3437
+ var DATE_DIR_RE = /^\d{4}-\d{2}-\d{2}$/;
3438
+ var logsAnalyzeCommand = new Command26("analyze").description(
3439
+ "\u6C47\u603B vibe-logger \u8F93\u51FA (.log/ai/**/*.jsonl) \u2014 \u5DE5\u5177 / \u5305\u6807\u7B7E / MCP \u8C03\u7528\u9891\u7387,\u8F85\u52A9\u751F\u6001\u4F18\u5316"
3440
+ ).option("--dir <path>", "log \u76EE\u5F55 (\u9ED8\u8BA4 <project>/.log/ai)").option(
3441
+ "--days <n>",
3442
+ "\u53EA\u770B\u6700\u8FD1 N \u5929\u7684\u76EE\u5F55 (\u9ED8\u8BA4\u5168\u90E8;\u6309\u76EE\u5F55\u540D YYYY-MM-DD \u6BD4\u5BF9,\u4E0D\u89E3\u6790\u8BB0\u5F55 ts)"
3443
+ ).option("--top <n>", "\u6BCF\u4E2A\u6392\u884C\u5C55\u793A\u524D N \u9879 (\u9ED8\u8BA4 10)", "10").option("--json", "\u4EE5 JSON \u8F93\u51FA (CI/\u5DE5\u5177\u53CB\u597D)").action((opts) => {
3444
+ const baseDir = resolve3(opts.dir ?? join17(process.cwd(), ".log", "ai"));
3445
+ if (!existsSync2(baseDir)) {
3446
+ logger.warn(`No log directory at ${baseDir}.`);
3447
+ logger.info(
3448
+ "\u8FD0\u884C vibe-logger hook \u89E6\u53D1\u540E\u4F1A\u5728\u6B64\u76EE\u5F55\u751F\u6210 JSONL \u2014 \u89C1 .claude/scripts/vibe-logger.mjs"
3449
+ );
3450
+ return;
3451
+ }
3452
+ const dayLimit = parseIntOrUndef(opts.days);
3453
+ const top = parseIntOrUndef(opts.top) ?? 10;
3454
+ const dayDirs = readdirSync(baseDir, { withFileTypes: true }).filter((e) => e.isDirectory() && DATE_DIR_RE.test(e.name)).map((e) => e.name).sort().reverse();
3455
+ const selected = dayLimit !== void 0 ? dayDirs.slice(0, dayLimit) : dayDirs;
3456
+ const records = [];
3457
+ for (const day of selected) {
3458
+ const dayPath = join17(baseDir, day);
3459
+ let entries;
3460
+ try {
3461
+ entries = readdirSync(dayPath);
3462
+ } catch {
3463
+ continue;
3464
+ }
3465
+ for (const entry of entries) {
3466
+ if (!entry.endsWith(".jsonl")) continue;
3467
+ const fp = join17(dayPath, entry);
3468
+ try {
3469
+ if (!statSync(fp).isFile()) continue;
3470
+ } catch {
3471
+ continue;
3472
+ }
3473
+ const text2 = readFileSync(fp, "utf8");
3474
+ for (const line of text2.split("\n")) {
3475
+ if (!line.trim()) continue;
3476
+ try {
3477
+ records.push(JSON.parse(line));
3478
+ } catch {
3479
+ }
3480
+ }
3481
+ }
3482
+ }
3483
+ const report = buildReport(records, top);
3484
+ if (opts.json) {
3485
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
3486
+ return;
3487
+ }
3488
+ printReport(baseDir, selected.length, report);
3489
+ });
3490
+ function buildReport(records, top) {
3491
+ const byAgent = {};
3492
+ const byEvent = {};
3493
+ const tools = /* @__PURE__ */ new Map();
3494
+ const tags = /* @__PURE__ */ new Map();
3495
+ const mcp = /* @__PURE__ */ new Map();
3496
+ const sessions = /* @__PURE__ */ new Set();
3497
+ const filesTouched = /* @__PURE__ */ new Set();
3498
+ const errors = [];
3499
+ let from = null;
3500
+ let to = null;
3501
+ for (const r of records) {
3502
+ if (!from || r.ts < from) from = r.ts;
3503
+ if (!to || r.ts > to) to = r.ts;
3504
+ byAgent[r.agent] = (byAgent[r.agent] ?? 0) + 1;
3505
+ byEvent[r.event] = (byEvent[r.event] ?? 0) + 1;
3506
+ sessions.add(`${r.agent}::${r.session}`);
3507
+ if (r.file) filesTouched.add(r.file);
3508
+ if (r.tool) {
3509
+ tools.set(r.tool, (tools.get(r.tool) ?? 0) + 1);
3510
+ if (r.tool.startsWith("mcp__")) {
3511
+ const name = r.tool.replace(/^mcp__[^_]+__/, "");
3512
+ const slot = mcp.get(name) ?? { count: 0, keys: /* @__PURE__ */ new Set() };
3513
+ slot.count += 1;
3514
+ for (const k of r.mcpArgs ?? []) slot.keys.add(k);
3515
+ mcp.set(name, slot);
3516
+ }
3517
+ }
3518
+ for (const tag of r.tags ?? []) {
3519
+ tags.set(tag, (tags.get(tag) ?? 0) + 1);
3520
+ }
3521
+ if (r.error) {
3522
+ errors.push({
3523
+ ts: r.ts,
3524
+ tool: r.tool,
3525
+ file: r.file,
3526
+ session: r.session
3527
+ });
3528
+ }
3529
+ }
3530
+ const topTools = sortAndSlice(tools, top).map(([tool, count]) => ({
3531
+ tool,
3532
+ count
3533
+ }));
3534
+ const topTags = sortAndSlice(tags, top).map(([tag, count]) => ({
3535
+ tag,
3536
+ count
3537
+ }));
3538
+ const topMcp = [...mcp.entries()].sort((a, b) => b[1].count - a[1].count).slice(0, top).map(([name, v]) => ({
3539
+ name,
3540
+ count: v.count,
3541
+ argKeys: [...v.keys].sort()
3542
+ }));
3543
+ const recentErrors = errors.slice(-10);
3544
+ return {
3545
+ range: { from, to },
3546
+ totals: {
3547
+ records: records.length,
3548
+ sessions: sessions.size,
3549
+ errors: errors.length,
3550
+ files: filesTouched.size
3551
+ },
3552
+ byAgent,
3553
+ byEvent,
3554
+ topTools,
3555
+ topTags,
3556
+ topMcp,
3557
+ recentErrors
3558
+ };
3559
+ }
3560
+ function sortAndSlice(m, top) {
3561
+ return [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, top);
3562
+ }
3563
+ function printReport(baseDir, dayCount, r) {
3564
+ logger.info(`vibe-logger \u5206\u6790\u62A5\u544A`);
3565
+ logger.info(` \u76EE\u5F55: ${baseDir}`);
3566
+ logger.info(` \u5929\u6570: ${dayCount}`);
3567
+ logger.info(` \u65F6\u95F4: ${r.range.from ?? "-"} \u2192 ${r.range.to ?? "-"}`);
3568
+ logger.info("");
3569
+ logger.info(
3570
+ `Totals records=${r.totals.records} sessions=${r.totals.sessions} errors=${r.totals.errors} files=${r.totals.files}`
3571
+ );
3572
+ logger.info(`By agent ${formatKv(r.byAgent)}`);
3573
+ logger.info(`By event ${formatKv(r.byEvent)}`);
3574
+ logger.info("");
3575
+ logger.info("Top tools:");
3576
+ for (const { tool, count } of r.topTools) {
3577
+ logger.info(` ${pad(count, 5)} ${tool}`);
3578
+ }
3579
+ if (r.topTags.length) {
3580
+ logger.info("");
3581
+ logger.info("Top package tags (which package the AI touched):");
3582
+ for (const { tag, count } of r.topTags) {
3583
+ logger.info(` ${pad(count, 5)} ${tag}`);
3584
+ }
3585
+ }
3586
+ if (r.topMcp.length) {
3587
+ logger.info("");
3588
+ logger.info("Top MCP tools:");
3589
+ for (const { name, count, argKeys } of r.topMcp) {
3590
+ const keys = argKeys.length ? ` args: ${argKeys.join(", ")}` : "";
3591
+ logger.info(` ${pad(count, 5)} ${name}${keys}`);
3592
+ }
3593
+ }
3594
+ if (r.recentErrors.length) {
3595
+ logger.info("");
3596
+ logger.info(`Recent errors (last ${r.recentErrors.length}):`);
3597
+ for (const e of r.recentErrors) {
3598
+ logger.info(
3599
+ ` ${e.ts} session=${e.session} tool=${e.tool ?? "-"} file=${e.file ?? "-"}`
3600
+ );
3601
+ }
3602
+ }
3603
+ }
3604
+ function formatKv(o) {
3605
+ return Object.entries(o).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}=${v}`).join(" ");
3606
+ }
3607
+ function pad(n, width) {
3608
+ const s = String(n);
3609
+ return s.length >= width ? s : " ".repeat(width - s.length) + s;
3610
+ }
3611
+ function parseIntOrUndef(v) {
3612
+ if (v === void 0) return void 0;
3613
+ const n = Number.parseInt(v, 10);
3614
+ return Number.isFinite(n) && n > 0 ? n : void 0;
3615
+ }
3616
+
3617
+ // src/commands/logs/trace.ts
3618
+ import { Command as Command27 } from "commander";
3619
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync3, statSync as statSync2 } from "fs";
3620
+ import { resolve as resolve4, join as join18 } from "path";
3621
+ var DATE_DIR_RE2 = /^\d{4}-\d{2}-\d{2}$/;
3622
+ var logsTraceCommand = new Command27("trace").description(
3623
+ "\u6309\u4F1A\u8BDD\u8FD8\u539F AI \u8C03\u7528\u94FE\u8DEF:\u4ECE\u7528\u6237 prompt \u8D77\u59CB,\u4E32\u8054\u540E\u7EED PreToolUse/PostToolUse \u76F4\u5230\u4E0B\u4E00\u4E2A prompt \u6216 Stop"
3624
+ ).option("--prompt <keyword>", "\u6309\u7528\u6237\u8F93\u5165\u5173\u952E\u5B57\u8FC7\u6EE4 (\u5B50\u4E32\u5339\u914D,\u4E0D\u533A\u5206\u5927\u5C0F\u5199)").option("--session <id>", "\u6307\u5B9A\u4F1A\u8BDD ID (\u524D\u7F00\u5339\u914D)").option("--days <n>", "\u53EA\u770B\u6700\u8FD1 N \u5929\u7684\u76EE\u5F55 (\u9ED8\u8BA4 7)", "7").option("--dir <path>", "log \u76EE\u5F55 (\u9ED8\u8BA4 <project>/.log/ai)").option("--json", "\u4EE5 JSON \u8F93\u51FA (CI/\u5DE5\u5177\u53CB\u597D)").action((opts) => {
3625
+ const baseDir = resolve4(opts.dir ?? join18(process.cwd(), ".log", "ai"));
3626
+ if (!existsSync3(baseDir)) {
3627
+ logger.warn(`No log directory at ${baseDir}.`);
3628
+ logger.info(
3629
+ "\u8FD0\u884C vibe-logger hook \u89E6\u53D1\u540E\u4F1A\u5728\u6B64\u76EE\u5F55\u751F\u6210 JSONL \u2014 \u89C1 .claude/scripts/vibe-logger.mjs"
3630
+ );
3631
+ return;
3632
+ }
3633
+ const dayLimit = parseIntOrUndef2(opts.days) ?? 7;
3634
+ const records = readRecords(baseDir, dayLimit);
3635
+ const report = buildTrace(records, {
3636
+ prompt: opts.prompt,
3637
+ session: opts.session
3638
+ });
3639
+ if (opts.json) {
3640
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
3641
+ return;
3642
+ }
3643
+ printTrace(baseDir, report);
3644
+ });
3645
+ function readRecords(baseDir, dayLimit) {
3646
+ const dayDirs = readdirSync2(baseDir, { withFileTypes: true }).filter((e) => e.isDirectory() && DATE_DIR_RE2.test(e.name)).map((e) => e.name).sort().reverse();
3647
+ const selected = dayDirs.slice(0, dayLimit);
3648
+ const records = [];
3649
+ for (const day of selected) {
3650
+ const dayPath = join18(baseDir, day);
3651
+ let entries;
3652
+ try {
3653
+ entries = readdirSync2(dayPath);
3654
+ } catch {
3655
+ continue;
3656
+ }
3657
+ for (const entry of entries) {
3658
+ if (!entry.endsWith(".jsonl")) continue;
3659
+ const fp = join18(dayPath, entry);
3660
+ try {
3661
+ if (!statSync2(fp).isFile()) continue;
3662
+ } catch {
3663
+ continue;
3664
+ }
3665
+ const text2 = readFileSync2(fp, "utf8");
3666
+ for (const line of text2.split("\n")) {
3667
+ if (!line.trim()) continue;
3668
+ try {
3669
+ records.push(JSON.parse(line));
3670
+ } catch {
3671
+ }
3672
+ }
3673
+ }
3674
+ }
3675
+ return records;
3676
+ }
3677
+ function buildTrace(records, filter = {}) {
3678
+ const groups = /* @__PURE__ */ new Map();
3679
+ for (const r of records) {
3680
+ if (!r.session) continue;
3681
+ const arr = groups.get(r.session) ?? [];
3682
+ arr.push(r);
3683
+ groups.set(r.session, arr);
3684
+ }
3685
+ const promptNeedle = filter.prompt?.toLowerCase();
3686
+ const sessionPrefix = filter.session;
3687
+ const sessions = [];
3688
+ for (const [sessionId, items] of groups.entries()) {
3689
+ if (sessionPrefix && !sessionId.startsWith(sessionPrefix)) continue;
3690
+ items.sort((a, b) => a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0);
3691
+ const chains = [];
3692
+ let current = null;
3693
+ let pending = /* @__PURE__ */ new Map();
3694
+ const closeCurrent = () => {
3695
+ if (current) chains.push(current);
3696
+ current = null;
3697
+ pending = /* @__PURE__ */ new Map();
3698
+ };
3699
+ for (const r of items) {
3700
+ if (r.event === "UserPromptSubmit") {
3701
+ if (current) closeCurrent();
3702
+ current = { prompt: r.prompt ?? "", promptTs: r.ts, steps: [] };
3703
+ pending = /* @__PURE__ */ new Map();
3704
+ continue;
3705
+ }
3706
+ if (r.event === "Stop") {
3707
+ if (current) closeCurrent();
3708
+ continue;
3709
+ }
3710
+ if (!current) continue;
3711
+ if (r.event === "PreToolUse") {
3712
+ const idx = current.steps.length;
3713
+ current.steps.push({
3714
+ ts: r.ts,
3715
+ event: "PreToolUse",
3716
+ tool: r.tool,
3717
+ file: r.file,
3718
+ tags: r.tags,
3719
+ duration_ms: r.duration_ms,
3720
+ error: r.error ?? false
3721
+ });
3722
+ if (r.tool) {
3723
+ const list = pending.get(r.tool) ?? [];
3724
+ list.push(idx);
3725
+ pending.set(r.tool, list);
3726
+ }
3727
+ } else if (r.event === "PostToolUse") {
3728
+ const list = r.tool ? pending.get(r.tool) : void 0;
3729
+ if (list && list.length > 0) {
3730
+ const idx = list.shift();
3731
+ if (list.length === 0 && r.tool) pending.delete(r.tool);
3732
+ const step = current.steps[idx];
3733
+ if (r.duration_ms !== void 0) step.duration_ms = r.duration_ms;
3734
+ if (r.error !== void 0) step.error = r.error;
3735
+ if (!step.file && r.file) step.file = r.file;
3736
+ if ((!step.tags || step.tags.length === 0) && r.tags)
3737
+ step.tags = r.tags;
3738
+ } else {
3739
+ current.steps.push({
3740
+ ts: r.ts,
3741
+ event: "PostToolUse",
3742
+ tool: r.tool,
3743
+ file: r.file,
3744
+ tags: r.tags,
3745
+ duration_ms: r.duration_ms,
3746
+ error: r.error ?? false
3747
+ });
3748
+ }
3749
+ }
3750
+ }
3751
+ if (current) closeCurrent();
3752
+ const filteredChains = promptNeedle ? chains.filter((c) => c.prompt.toLowerCase().includes(promptNeedle)) : chains;
3753
+ if (filteredChains.length === 0) continue;
3754
+ sessions.push({
3755
+ id: sessionId,
3756
+ agent: items[0]?.agent ?? "unknown",
3757
+ chains: filteredChains
3758
+ });
3759
+ }
3760
+ sessions.sort((a, b) => {
3761
+ const ta = a.chains[0]?.promptTs ?? "";
3762
+ const tb = b.chains[0]?.promptTs ?? "";
3763
+ return tb.localeCompare(ta);
3764
+ });
3765
+ return { sessions };
3766
+ }
3767
+ function printTrace(baseDir, r) {
3768
+ logger.info(`vibe-logger \u94FE\u8DEF\u8FFD\u8E2A`);
3769
+ logger.info(` \u76EE\u5F55: ${baseDir}`);
3770
+ logger.info(` \u4F1A\u8BDD: ${r.sessions.length}`);
3771
+ logger.info("");
3772
+ if (r.sessions.length === 0) {
3773
+ logger.info("\u672A\u627E\u5230\u5339\u914D\u7684\u4F1A\u8BDD/\u63D0\u793A\u3002");
3774
+ return;
3775
+ }
3776
+ for (const s of r.sessions) {
3777
+ logger.info(`\u2501\u2501\u2501 Session: ${s.id} (${s.agent}) \u2501\u2501\u2501`);
3778
+ logger.info("");
3779
+ for (const c of s.chains) {
3780
+ logger.info(
3781
+ `[${formatTime(c.promptTs)}] \u{1F4AC} Prompt: ${quote(truncate(c.prompt, 200))}`
3782
+ );
3783
+ for (const step of c.steps) {
3784
+ logger.info(` ${formatStep(step)}`);
3785
+ }
3786
+ logger.info("");
3787
+ }
3788
+ }
3789
+ }
3790
+ function formatStep(s) {
3791
+ const ts = `[${formatTime(s.ts)}]`;
3792
+ const isEdit = s.tool === "Edit" || s.tool === "Write" || s.tool === "MultiEdit" || s.tool === "create_file" || s.tool === "search_replace";
3793
+ const icon = isEdit ? "\u{1F4DD}" : "\u{1F527}";
3794
+ const head = isEdit && s.file ? `${s.tool ?? "?"} \u2192 ${s.file}` : s.tool ?? s.event ?? "?";
3795
+ const dur = s.duration_ms !== void 0 ? `${s.duration_ms}ms` : "";
3796
+ const tags = s.tags && s.tags.length ? `[${s.tags.join(",")}]` : "";
3797
+ const err = s.error ? "\u274C" : "";
3798
+ return [ts, icon, padRight(head, 40), padLeft(dur, 6), tags, err].filter((x) => x !== "").join(" ");
3799
+ }
3800
+ function formatTime(ts) {
3801
+ const d = new Date(ts);
3802
+ if (Number.isNaN(d.getTime())) return ts;
3803
+ const hh = String(d.getUTCHours()).padStart(2, "0");
3804
+ const mm = String(d.getUTCMinutes()).padStart(2, "0");
3805
+ const ss = String(d.getUTCSeconds()).padStart(2, "0");
3806
+ return `${hh}:${mm}:${ss}`;
3807
+ }
3808
+ function quote(s) {
3809
+ return `"${s.replace(/"/g, '\\"')}"`;
3810
+ }
3811
+ function truncate(s, n) {
3812
+ if (s.length <= n) return s;
3813
+ return s.slice(0, n) + "\u2026";
3814
+ }
3815
+ function padRight(s, n) {
3816
+ return s.length >= n ? s : s + " ".repeat(n - s.length);
3817
+ }
3818
+ function padLeft(s, n) {
3819
+ return s.length >= n ? s : " ".repeat(n - s.length) + s;
3820
+ }
3821
+ function parseIntOrUndef2(v) {
3822
+ if (v === void 0) return void 0;
3823
+ const n = Number.parseInt(v, 10);
3824
+ return Number.isFinite(n) && n > 0 ? n : void 0;
3825
+ }
3826
+
3827
+ // src/commands/logs/index.ts
3828
+ var logsCommand = new Command28("logs").description(
3829
+ "\u67E5\u8BE2 vibe-logger \u8F93\u51FA (.log/ai/**/*.jsonl) \u2014 AI \u8C03\u7528\u94FE\u5206\u6790"
3830
+ );
3831
+ logsCommand.addCommand(logsAnalyzeCommand);
3832
+ logsCommand.addCommand(logsTraceCommand);
3833
+
2921
3834
  // src/index.ts
2922
- var require6 = createRequire5(import.meta.url);
2923
- var { version } = require6("../package.json");
2924
- var program = new Command24();
3835
+ var require8 = createRequire7(import.meta.url);
3836
+ var { version } = require8("../package.json");
3837
+ var program = new Command29();
2925
3838
  program.name("teamix-evo").description("Where ideas evolve. \u2014 AI Coding \u5957\u4EF6").version(version);
2926
- program.addCommand(designCommand);
3839
+ program.addCommand(tokensCommand);
2927
3840
  program.addCommand(skillsCommand);
2928
3841
  program.addCommand(uiCommand);
2929
3842
  program.addCommand(bizUiCommand);
2930
3843
  program.addCommand(templatesCommand);
3844
+ program.addCommand(logsCommand);
2931
3845
  program.parse();
2932
3846
  //# sourceMappingURL=index.js.map