teamix-evo 0.2.0 → 0.3.0

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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command15 } from "commander";
5
- import { createRequire as createRequire4 } from "module";
4
+ import { Command as Command24 } from "commander";
5
+ import { createRequire as createRequire5 } from "module";
6
6
 
7
7
  // src/commands/design/index.ts
8
- import { Command as Command5 } from "commander";
8
+ import { Command as Command6 } from "commander";
9
9
 
10
10
  // src/commands/design/init.ts
11
11
  import { Command } from "commander";
@@ -66,11 +66,19 @@ function detectIde() {
66
66
  return new QoderAdapter();
67
67
  }
68
68
 
69
- // src/core/registry-client.ts
70
- import * as path3 from "path";
69
+ // src/core/design-init.ts
70
+ import * as path9 from "path";
71
+ import * as fs6 from "fs/promises";
72
+ import { createRequire as createRequire2 } from "module";
73
+ import {
74
+ loadDesignPack,
75
+ loadDesignPackageManifest,
76
+ mergeDefaultAndVariant
77
+ } from "@teamix-evo/registry";
78
+
79
+ // src/utils/fs.ts
71
80
  import * as fs from "fs/promises";
72
- import { createRequire } from "module";
73
- import { loadVariantManifest } from "@teamix-evo/registry";
81
+ import * as path3 from "path";
74
82
 
75
83
  // src/utils/logger.ts
76
84
  import { red, yellow, cyan, green, gray } from "kolorist";
@@ -95,52 +103,20 @@ var logger = {
95
103
  }
96
104
  };
97
105
 
98
- // src/core/registry-client.ts
99
- var require2 = createRequire(import.meta.url);
100
- function resolvePackageRoot(packageName) {
101
- const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
102
- return path3.dirname(pkgJsonPath);
103
- }
104
- async function loadVariantData(packageName, variant) {
105
- const packageRoot = resolvePackageRoot(packageName);
106
- const variantDir = path3.join(packageRoot, "library", variant);
107
- logger.debug(`Resolved variant dir: ${variantDir}`);
108
- logger.debug(`Package root: ${packageRoot}`);
109
- const manifest = await loadVariantManifest(variantDir);
110
- let data = {};
111
- const dataPath = path3.join(variantDir, "_data.json");
112
- try {
113
- const raw = await fs.readFile(dataPath, "utf-8");
114
- data = JSON.parse(raw);
115
- } catch (err) {
116
- if (err.code !== "ENOENT") {
117
- throw err;
118
- }
119
- logger.debug(`No _data.json found at ${dataPath}, using empty data`);
120
- }
121
- return { manifest, data, variantDir, packageRoot };
122
- }
123
-
124
- // src/core/installer.ts
125
- import * as path6 from "path";
126
- import * as fs5 from "fs/promises";
127
-
128
106
  // src/utils/fs.ts
129
- import * as fs2 from "fs/promises";
130
- import * as path4 from "path";
131
107
  async function ensureDir(dir) {
132
- await fs2.mkdir(dir, { recursive: true });
108
+ await fs.mkdir(dir, { recursive: true });
133
109
  }
134
110
  async function writeFileSafe(filePath, content) {
135
- const dir = path4.dirname(filePath);
111
+ const dir = path3.dirname(filePath);
136
112
  await ensureDir(dir);
137
113
  const tmp = filePath + ".tmp";
138
- await fs2.writeFile(tmp, content, "utf-8");
139
- await fs2.rename(tmp, filePath);
114
+ await fs.writeFile(tmp, content, "utf-8");
115
+ await fs.rename(tmp, filePath);
140
116
  }
141
117
  async function readFileOrNull(filePath) {
142
118
  try {
143
- return await fs2.readFile(filePath, "utf-8");
119
+ return await fs.readFile(filePath, "utf-8");
144
120
  } catch (err) {
145
121
  if (err.code === "ENOENT") {
146
122
  return null;
@@ -154,21 +130,21 @@ async function backupFile(filePath, projectRoot) {
154
130
  logger.debug(`Skip backup: ${filePath} does not exist`);
155
131
  return;
156
132
  }
157
- const rel2 = path4.relative(projectRoot, filePath);
133
+ const rel2 = path3.relative(projectRoot, filePath);
158
134
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
159
- const backupPath = path4.join(
135
+ const backupPath = path3.join(
160
136
  projectRoot,
161
137
  ".teamix-evo",
162
138
  ".backups",
163
139
  `${rel2}.${timestamp}.bak`
164
140
  );
165
- await ensureDir(path4.dirname(backupPath));
166
- await fs2.writeFile(backupPath, content, "utf-8");
167
- logger.debug(`Backed up ${rel2} \u2192 ${path4.relative(projectRoot, backupPath)}`);
141
+ await ensureDir(path3.dirname(backupPath));
142
+ await fs.writeFile(backupPath, content, "utf-8");
143
+ logger.debug(`Backed up ${rel2} \u2192 ${path3.relative(projectRoot, backupPath)}`);
168
144
  }
169
145
  async function fileExists(filePath) {
170
146
  try {
171
- await fs2.access(filePath);
147
+ await fs.access(filePath);
172
148
  return true;
173
149
  } catch {
174
150
  return false;
@@ -182,157 +158,71 @@ function computeHash(content) {
182
158
  return `sha256:${hash}`;
183
159
  }
184
160
 
185
- // src/utils/template.ts
186
- import Handlebars from "handlebars";
187
- import * as fs3 from "fs/promises";
188
- Handlebars.registerHelper("lowercase", (str) => {
189
- return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
190
- });
191
- var compiledCache = /* @__PURE__ */ new Map();
192
- var MAX_CACHE_SIZE = 64;
193
- function getCompiledTemplate(templateContent) {
194
- let compiled = compiledCache.get(templateContent);
195
- if (!compiled) {
196
- if (compiledCache.size >= MAX_CACHE_SIZE) {
197
- const firstKey = compiledCache.keys().next().value;
198
- compiledCache.delete(firstKey);
199
- }
200
- compiled = Handlebars.compile(templateContent, { noEscape: true });
201
- compiledCache.set(templateContent, compiled);
202
- }
203
- return compiled;
204
- }
205
- function renderTemplate(templateContent, data) {
206
- const compiled = getCompiledTemplate(templateContent);
207
- return compiled(data);
208
- }
209
- async function loadTemplateFile(filePath) {
210
- return fs3.readFile(filePath, "utf-8");
211
- }
212
-
213
- // src/utils/path.ts
214
- import * as path5 from "path";
215
- import * as fs4 from "fs/promises";
216
- function resolveSourcePath(source, variantDir, packageRoot) {
217
- if (source.startsWith("_template/")) {
218
- return path5.join(packageRoot, source);
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;
219
177
  }
220
- return path5.join(variantDir, source);
221
- }
222
- async function walkDir(dir) {
223
- const files = [];
224
- const entries = await fs4.readdir(dir, { withFileTypes: true });
225
- for (const entry of entries) {
226
- const fullPath = path5.join(dir, entry.name);
227
- if (entry.isDirectory()) {
228
- files.push(...await walkDir(fullPath));
229
- } else if (entry.isFile()) {
230
- files.push(fullPath);
231
- }
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
+ };
232
186
  }
233
- return files;
234
- }
235
-
236
- // src/core/installer.ts
237
- async function installResources(options) {
238
- const { projectRoot, manifest, data, variantDir, packageRoot } = options;
239
- const installedResources = [];
240
- for (const resource of manifest.resources) {
241
- logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
242
- if (resource.recursive) {
243
- const results = await installRecursiveResource(
244
- resource,
245
- projectRoot,
246
- data,
247
- variantDir,
248
- packageRoot
249
- );
250
- installedResources.push(...results);
251
- } else {
252
- const result = await installSingleResource(
253
- resource,
254
- projectRoot,
255
- data,
256
- variantDir,
257
- packageRoot
258
- );
259
- installedResources.push(result);
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 };
260
192
  }
193
+ return { target, strategy: "regenerable", isFrozen: false };
261
194
  }
262
- return {
263
- resources: installedResources,
264
- count: installedResources.length
265
- };
266
- }
267
- async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
268
- const sourcePath = resolveSourcePath(
269
- resource.source,
270
- variantDir,
271
- packageRoot
272
- );
273
- const targetPath = path6.join(projectRoot, resource.target);
274
- let content;
275
- if (resource.template) {
276
- const templateContent = await loadTemplateFile(sourcePath);
277
- content = renderTemplate(templateContent, data);
278
- } else {
279
- content = await fs5.readFile(sourcePath, "utf-8");
195
+ if (FROZEN_FILES.has(relPath)) {
196
+ return {
197
+ target: path4.posix.join(TEAMIX_DIR, relPath),
198
+ strategy: "frozen",
199
+ isFrozen: true
200
+ };
280
201
  }
281
- await writeFileSafe(targetPath, content);
282
- const hash = computeHash(content);
283
- logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
284
202
  return {
285
- id: resource.id,
286
- target: resource.target,
287
- hash,
288
- strategy: resource.updateStrategy
203
+ target: path4.posix.join(TEAMIX_DIR, relPath),
204
+ strategy: "regenerable",
205
+ isFrozen: false
289
206
  };
290
207
  }
291
- async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
292
- const sourcePath = resolveSourcePath(
293
- resource.source,
294
- variantDir,
295
- packageRoot
296
- );
297
- const targetDir = path6.join(projectRoot, resource.target);
298
- const results = [];
299
- await ensureDir(targetDir);
300
- const entries = await walkDir(sourcePath);
301
- for (const entry of entries) {
302
- const relPath = path6.relative(sourcePath, entry);
303
- let targetFile = path6.join(targetDir, relPath);
304
- if (resource.template && targetFile.endsWith(".hbs")) {
305
- targetFile = targetFile.slice(0, -4);
306
- }
307
- let content;
308
- if (resource.template && entry.endsWith(".hbs")) {
309
- const templateContent = await loadTemplateFile(entry);
310
- content = renderTemplate(templateContent, data);
311
- } else {
312
- content = await fs5.readFile(entry, "utf-8");
313
- }
314
- await writeFileSafe(targetFile, content);
315
- const hash = computeHash(content);
316
- const targetRel = path6.relative(projectRoot, targetFile);
317
- results.push({
318
- id: `${resource.id}:${relPath}`,
319
- target: targetRel,
320
- hash,
321
- strategy: resource.updateStrategy
322
- });
323
- logger.debug(` Written: ${targetRel}`);
324
- }
325
- return results;
326
- }
327
208
 
328
209
  // src/core/state.ts
329
- import * as path7 from "path";
330
- import { validateConfig, validateInstalled } from "@teamix-evo/registry";
331
- var TEAMIX_DIR = ".teamix-evo";
210
+ import * as path5 from "path";
211
+ import {
212
+ validateConfig,
213
+ validateInstalled,
214
+ validateSkillsLock,
215
+ DesignPackLockSchema
216
+ } from "@teamix-evo/registry";
217
+ var TEAMIX_DIR2 = ".teamix-evo";
332
218
  var CONFIG_FILE = "config.json";
333
219
  var MANIFEST_FILE = "manifest.json";
220
+ var DESIGN_DIR = "design";
221
+ var DESIGN_LOCK_FILE = "pack.lock.json";
222
+ var SKILLS_DIR = "skills";
223
+ var SKILLS_LOCK_FILE = "manifest.lock.json";
334
224
  function getTeamixDir(projectRoot) {
335
- return path7.join(projectRoot, TEAMIX_DIR);
225
+ return path5.join(projectRoot, TEAMIX_DIR2);
336
226
  }
337
227
  async function ensureTeamixDir(projectRoot) {
338
228
  const dir = getTeamixDir(projectRoot);
@@ -340,7 +230,7 @@ async function ensureTeamixDir(projectRoot) {
340
230
  return dir;
341
231
  }
342
232
  async function readProjectConfig(projectRoot) {
343
- const configPath = path7.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
233
+ const configPath = path5.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
344
234
  const raw = await readFileOrNull(configPath);
345
235
  if (raw === null) return null;
346
236
  try {
@@ -357,12 +247,12 @@ async function readProjectConfig(projectRoot) {
357
247
  }
358
248
  }
359
249
  async function writeProjectConfig(projectRoot, config) {
360
- const configPath = path7.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
250
+ const configPath = path5.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
361
251
  await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
362
252
  logger.debug(`Wrote config \u2192 ${configPath}`);
363
253
  }
364
254
  async function readInstalledManifest(projectRoot) {
365
- const manifestPath = path7.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
255
+ const manifestPath = path5.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
366
256
  const raw = await readFileOrNull(manifestPath);
367
257
  if (raw === null) return null;
368
258
  try {
@@ -379,559 +269,94 @@ async function readInstalledManifest(projectRoot) {
379
269
  }
380
270
  }
381
271
  async function writeInstalledManifest(projectRoot, manifest) {
382
- const manifestPath = path7.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
272
+ const manifestPath = path5.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
383
273
  await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
384
274
  logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
385
275
  }
386
-
387
- // src/core/design-init.ts
388
- var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
389
- async function runDesignInit(options) {
390
- const { projectRoot, variant, tailwind, ide } = options;
391
- const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
392
- await ensureTeamixDir(projectRoot);
393
- const existingConfig = await readProjectConfig(projectRoot);
394
- if (existingConfig?.packages?.design) {
395
- return {
396
- status: "already-initialized",
397
- existingVariant: existingConfig.packages.design.variant
398
- };
399
- }
400
- const { manifest, data, variantDir, packageRoot } = await loadVariantData(
401
- packageName,
402
- variant
403
- );
404
- const result = await installResources({
276
+ async function readDesignPackLock(projectRoot) {
277
+ const lockPath = path5.join(
405
278
  projectRoot,
406
- manifest,
407
- data,
408
- variantDir,
409
- packageRoot
410
- });
411
- const config = {
412
- $schema: "https://teamix-evo.dev/schema/config/v1.json",
413
- schemaVersion: 1,
414
- ide,
415
- packages: {
416
- design: {
417
- variant,
418
- version: manifest.version,
419
- tailwind
420
- }
421
- }
422
- };
423
- await writeProjectConfig(projectRoot, config);
424
- const installedManifest = {
425
- schemaVersion: 1,
426
- installed: [
427
- {
428
- package: packageName,
429
- variant,
430
- version: manifest.version,
431
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
432
- resources: result.resources
433
- }
434
- ]
435
- };
436
- await writeInstalledManifest(projectRoot, installedManifest);
437
- return {
438
- status: "installed",
439
- packageName,
440
- variant,
441
- version: manifest.version,
442
- tailwind,
443
- count: result.count,
444
- resources: result.resources
445
- };
446
- }
447
-
448
- // src/commands/design/init.ts
449
- var DEFAULT_VARIANT = "opentrek";
450
- var DEFAULT_TAILWIND = "v4";
451
- function normalizeTailwind(input) {
452
- if (input === void 0) return DEFAULT_TAILWIND;
453
- const lower = input.toLowerCase();
454
- if (lower === "v3" || lower === "3") return "v3";
455
- if (lower === "v4" || lower === "4") return "v4";
456
- throw new Error(
457
- `Invalid --tailwind value: "${input}". Expected "v3" or "v4".`
279
+ TEAMIX_DIR2,
280
+ DESIGN_DIR,
281
+ DESIGN_LOCK_FILE
458
282
  );
459
- }
460
- var initCommand = new Command("init").description("\u521D\u59CB\u5316\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90").argument("[variant]", "\u8BBE\u8BA1\u53D8\u4F53\u540D\u79F0", DEFAULT_VARIANT).option(
461
- "--tailwind <version>",
462
- "\u9879\u76EE\u4F7F\u7528\u7684 Tailwind CSS \u4E3B\u7248\u672C\uFF08v3 | v4\uFF09",
463
- DEFAULT_TAILWIND
464
- ).action(async (variant, opts) => {
283
+ const raw = await readFileOrNull(lockPath);
284
+ if (raw === null) return null;
465
285
  try {
466
- const tailwind = normalizeTailwind(opts.tailwind);
467
- const ide = detectIde();
468
- const projectRoot = ide.getProjectRoot();
469
- logger.info(
470
- `Initializing design system: variant="${variant}", tailwind="${tailwind}"`
471
- );
472
- logger.debug(`Project root: ${projectRoot}`);
473
- logger.debug(`IDE: ${ide.name}`);
474
- logger.info(`Loading variant "${variant}"...`);
475
- logger.info("Installing resources...");
476
- const result = await runDesignInit({
477
- projectRoot,
478
- variant,
479
- tailwind,
480
- ide: ide.name
481
- });
482
- if (result.status === "already-initialized") {
483
- logger.warn(
484
- `Design system already initialized (variant: ${result.existingVariant}). Use "teamix-evo design update" to update.`
485
- );
486
- return;
286
+ const parsed = DesignPackLockSchema.safeParse(JSON.parse(raw));
287
+ if (!parsed.success) {
288
+ logger.warn(`Invalid design pack.lock.json: ${parsed.error.message}`);
289
+ return null;
487
290
  }
488
- logger.success(
489
- `Design system initialized: ${result.packageName} v${result.version}`
490
- );
491
- logger.info(` Variant: ${result.variant}`);
492
- logger.info(` Tailwind: ${result.tailwind}`);
493
- logger.info(` Resources: ${result.count} files installed`);
494
- logger.info("");
495
- logger.info('Run "teamix-evo design update" to update resources later.');
291
+ return parsed.data;
496
292
  } catch (err) {
497
- logger.error(`Failed to initialize: ${err.message}`);
498
- logger.debug(err.stack ?? "");
499
- process.exitCode = 1;
293
+ logger.warn(
294
+ `Failed to parse design pack.lock.json: ${err.message}`
295
+ );
296
+ return null;
500
297
  }
501
- });
502
-
503
- // src/commands/design/update.ts
504
- import { Command as Command2 } from "commander";
505
-
506
- // src/core/updater.ts
507
- import * as path8 from "path";
508
- import * as fs6 from "fs/promises";
509
- import { getUpdateAction, replaceManagedRegion } from "@teamix-evo/registry";
510
- async function updateResources(options) {
511
- const {
298
+ }
299
+ async function readDesignVariant(projectRoot) {
300
+ const lock = await readDesignPackLock(projectRoot);
301
+ return lock?.variant.name ?? null;
302
+ }
303
+ function getSkillsSourceDir(projectRoot, skillName) {
304
+ const base = path5.join(projectRoot, TEAMIX_DIR2, SKILLS_DIR);
305
+ return skillName ? path5.join(base, skillName) : base;
306
+ }
307
+ async function readSkillsLock(projectRoot) {
308
+ const lockPath = path5.join(
512
309
  projectRoot,
513
- manifest,
514
- data,
515
- variantDir,
516
- packageRoot,
517
- installedManifest,
518
- packageName
519
- } = options;
520
- const updatedResources = [];
521
- const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
522
- const installedPkg = installedManifest.installed.find(
523
- (p) => p.package === packageName && p.variant === manifest.variant
310
+ TEAMIX_DIR2,
311
+ SKILLS_DIR,
312
+ SKILLS_LOCK_FILE
524
313
  );
525
- const installedMap = /* @__PURE__ */ new Map();
526
- if (installedPkg) {
527
- for (const res of installedPkg.resources) {
528
- installedMap.set(res.id, res);
529
- }
530
- }
531
- for (const resource of manifest.resources) {
532
- if (resource.recursive) {
533
- const results = await updateRecursiveResource(
534
- resource,
535
- projectRoot,
536
- data,
537
- variantDir,
538
- packageRoot,
539
- installedMap,
540
- summary
541
- );
542
- updatedResources.push(...results);
543
- } else {
544
- const result = await updateSingleResource(
545
- resource,
546
- projectRoot,
547
- data,
548
- variantDir,
549
- packageRoot,
550
- installedMap,
551
- summary
552
- );
553
- updatedResources.push(result);
314
+ const raw = await readFileOrNull(lockPath);
315
+ if (raw === null) return null;
316
+ try {
317
+ const data = JSON.parse(raw);
318
+ const result = validateSkillsLock(data);
319
+ if (!result.success) {
320
+ logger.warn(`Invalid skills manifest.lock.json: ${result.error}`);
321
+ return null;
554
322
  }
323
+ return result.data;
324
+ } catch (err) {
325
+ logger.warn(
326
+ `Failed to parse skills manifest.lock.json: ${err.message}`
327
+ );
328
+ return null;
555
329
  }
556
- return { resources: updatedResources, summary };
557
330
  }
558
- async function updateSingleResource(resource, projectRoot, data, variantDir, packageRoot, installedMap, summary) {
559
- const targetPath = path8.join(projectRoot, resource.target);
560
- const exists = await fileExists(targetPath);
561
- const installed = installedMap.get(resource.id);
562
- const sourcePath = resolveSourcePath(
563
- resource.source,
564
- variantDir,
565
- packageRoot
331
+ async function writeSkillsLock(projectRoot, lock) {
332
+ const lockPath = path5.join(
333
+ projectRoot,
334
+ TEAMIX_DIR2,
335
+ SKILLS_DIR,
336
+ SKILLS_LOCK_FILE
566
337
  );
567
- let newContent;
568
- if (resource.template) {
569
- const templateContent = await loadTemplateFile(sourcePath);
570
- newContent = renderTemplate(templateContent, data);
571
- } else {
572
- newContent = await fs6.readFile(sourcePath, "utf-8");
573
- }
574
- const newHash = computeHash(newContent);
575
- const action = getUpdateAction(resource.updateStrategy, {
576
- exists,
577
- hash: newHash,
578
- currentHash: installed?.hash
579
- });
580
- switch (action) {
581
- case "skip": {
582
- logger.debug(` Skip: ${resource.target} (${resource.updateStrategy})`);
583
- summary.skipped++;
584
- return installed ?? {
585
- id: resource.id,
586
- target: resource.target,
587
- hash: newHash,
588
- strategy: resource.updateStrategy
589
- };
590
- }
591
- case "overwrite": {
592
- if (exists) {
593
- await backupFile(targetPath, projectRoot);
594
- summary.overwritten++;
595
- } else {
596
- summary.created++;
597
- }
598
- await writeFileSafe(targetPath, newContent);
599
- logger.debug(` ${exists ? "Overwrite" : "Create"}: ${resource.target}`);
600
- return {
601
- id: resource.id,
602
- target: resource.target,
603
- hash: newHash,
604
- strategy: resource.updateStrategy
605
- };
606
- }
607
- case "managed-update": {
608
- const currentContent = await readFileOrNull(targetPath);
609
- if (currentContent === null) {
610
- await writeFileSafe(targetPath, newContent);
611
- summary.created++;
612
- return {
613
- id: resource.id,
614
- target: resource.target,
615
- hash: computeHash(newContent),
616
- strategy: resource.updateStrategy
617
- };
618
- }
619
- let updatedContent = currentContent;
620
- const regionIds = resource.managedRegions ?? [];
621
- for (const regionId of regionIds) {
622
- const regionPattern = new RegExp(
623
- `<!-- teamix-evo:managed:start id="${escapeRegExp(
624
- regionId
625
- )}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp(
626
- regionId
627
- )}" -->`
628
- );
629
- const match = newContent.match(regionPattern);
630
- if (match) {
631
- const regionContent = match[1].replace(/^\n/, "").replace(/\n$/, "");
632
- try {
633
- updatedContent = replaceManagedRegion(
634
- updatedContent,
635
- regionId,
636
- regionContent
637
- );
638
- } catch {
639
- logger.warn(
640
- `Managed region "${regionId}" not found in ${resource.target}. Skipping region.`
641
- );
642
- }
643
- }
644
- }
645
- await backupFile(targetPath, projectRoot);
646
- await writeFileSafe(targetPath, updatedContent);
647
- summary.managed++;
648
- return {
649
- id: resource.id,
650
- target: resource.target,
651
- hash: computeHash(updatedContent),
652
- strategy: resource.updateStrategy
653
- };
654
- }
655
- default:
656
- summary.skipped++;
657
- return installed ?? {
658
- id: resource.id,
659
- target: resource.target,
660
- hash: newHash,
661
- strategy: resource.updateStrategy
662
- };
663
- }
338
+ await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
339
+ logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
664
340
  }
665
- async function updateRecursiveResource(resource, projectRoot, data, variantDir, packageRoot, installedMap, summary) {
666
- const sourcePath = resolveSourcePath(
667
- resource.source,
668
- variantDir,
669
- packageRoot
670
- );
671
- const targetDir = path8.join(projectRoot, resource.target);
672
- const results = [];
673
- if (resource.updateStrategy === "frozen") {
674
- const anyInstalled = [...installedMap.keys()].some(
675
- (k) => k.startsWith(`${resource.id}:`)
676
- );
677
- if (anyInstalled) {
678
- summary.skipped++;
679
- for (const [id, res] of installedMap) {
680
- if (id.startsWith(`${resource.id}:`)) {
681
- results.push(res);
682
- }
683
- }
684
- return results;
685
- }
686
- }
687
- await ensureDir(targetDir);
688
- const entries = await walkDir(sourcePath);
689
- for (const entry of entries) {
690
- const relPath = path8.relative(sourcePath, entry);
691
- let targetFile = path8.join(targetDir, relPath);
692
- if (resource.template && targetFile.endsWith(".hbs")) {
693
- targetFile = targetFile.slice(0, -4);
694
- }
695
- let content;
696
- if (resource.template && entry.endsWith(".hbs")) {
697
- const templateContent = await loadTemplateFile(entry);
698
- content = renderTemplate(templateContent, data);
699
- } else {
700
- content = await fs6.readFile(entry, "utf-8");
701
- }
702
- await writeFileSafe(targetFile, content);
703
- const hash = computeHash(content);
704
- const targetRel = path8.relative(projectRoot, targetFile);
705
- results.push({
706
- id: `${resource.id}:${relPath}`,
707
- target: targetRel,
708
- hash,
709
- strategy: resource.updateStrategy
710
- });
711
- summary.overwritten++;
712
- }
713
- return results;
714
- }
715
- function escapeRegExp(str) {
716
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
717
- }
718
-
719
- // src/commands/design/update.ts
720
- var DESIGN_PACKAGE = "@teamix-evo/design";
721
- var updateCommand = new Command2("update").description("\u66F4\u65B0\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90").action(async () => {
722
- try {
723
- const ide = detectIde();
724
- const projectRoot = ide.getProjectRoot();
725
- logger.info("Updating design system resources...");
726
- const config = await readProjectConfig(projectRoot);
727
- if (!config?.packages?.design) {
728
- logger.error(
729
- 'Design system not initialized. Run "teamix-evo design init" first.'
730
- );
731
- process.exitCode = 1;
732
- return;
733
- }
734
- const { variant, version: currentVersion } = config.packages.design;
735
- const installedManifest = await readInstalledManifest(projectRoot);
736
- if (!installedManifest) {
737
- logger.error(
738
- 'No installed manifest found. Try re-initializing with "teamix-evo design init".'
739
- );
740
- process.exitCode = 1;
741
- return;
742
- }
743
- logger.info(`Loading variant "${variant}" from ${DESIGN_PACKAGE}...`);
744
- const { manifest, data, variantDir, packageRoot } = await loadVariantData(DESIGN_PACKAGE, variant);
745
- logger.info(
746
- `Current: v${currentVersion} \u2192 Available: v${manifest.version}`
747
- );
748
- const result = await updateResources({
749
- projectRoot,
750
- manifest,
751
- data,
752
- variantDir,
753
- packageRoot,
754
- installedManifest,
755
- packageName: DESIGN_PACKAGE
756
- });
757
- config.packages.design.version = manifest.version;
758
- await writeProjectConfig(projectRoot, config);
759
- const updatedManifest = { ...installedManifest };
760
- const pkgIdx = updatedManifest.installed.findIndex(
761
- (p) => p.package === DESIGN_PACKAGE && p.variant === variant
762
- );
763
- const pkgEntry = {
764
- package: DESIGN_PACKAGE,
765
- variant,
766
- version: manifest.version,
767
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
768
- resources: result.resources
769
- };
770
- if (pkgIdx >= 0) {
771
- updatedManifest.installed[pkgIdx] = pkgEntry;
772
- } else {
773
- updatedManifest.installed.push(pkgEntry);
774
- }
775
- await writeInstalledManifest(projectRoot, updatedManifest);
776
- const { summary } = result;
777
- logger.success(
778
- `Design system updated to v${manifest.version}`
779
- );
780
- logger.info(` Created: ${summary.created}`);
781
- logger.info(` Overwritten: ${summary.overwritten}`);
782
- logger.info(` Managed: ${summary.managed}`);
783
- logger.info(` Skipped: ${summary.skipped}`);
784
- } catch (err) {
785
- logger.error(`Failed to update: ${err.message}`);
786
- logger.debug(err.stack ?? "");
787
- process.exitCode = 1;
788
- }
789
- });
790
-
791
- // src/commands/design/list.ts
792
- import { Command as Command3 } from "commander";
793
- var listCommand = new Command3("list").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684\u8BBE\u8BA1\u53D8\u4F53").action(async () => {
794
- try {
795
- const ide = detectIde();
796
- const projectRoot = ide.getProjectRoot();
797
- const config = await readProjectConfig(projectRoot);
798
- if (!config?.packages?.design) {
799
- logger.info("No design system installed.");
800
- logger.info('Run "teamix-evo design init [variant]" to get started.');
801
- return;
802
- }
803
- const { variant, version: version2 } = config.packages.design;
804
- logger.info("Installed design system:");
805
- logger.info(` Package: @teamix-evo/design`);
806
- logger.info(` Variant: ${variant}`);
807
- logger.info(` Version: ${version2}`);
808
- logger.info(` IDE: ${config.ide}`);
809
- const manifest = await readInstalledManifest(projectRoot);
810
- if (manifest) {
811
- const pkg = manifest.installed.find(
812
- (p) => p.package === "@teamix-evo/design" && p.variant === variant
813
- );
814
- if (pkg) {
815
- logger.info(` Resources: ${pkg.resources.length} files`);
816
- logger.info(
817
- ` Installed: ${new Date(pkg.installedAt).toLocaleString()}`
818
- );
819
- }
820
- }
821
- } catch (err) {
822
- logger.error(`Failed to list: ${err.message}`);
823
- process.exitCode = 1;
824
- }
825
- });
826
-
827
- // src/commands/design/uninstall.ts
828
- import { Command as Command4 } from "commander";
829
- import * as fs7 from "fs/promises";
830
- import * as path9 from "path";
831
- import * as prompts from "@clack/prompts";
832
- var DESIGN_PACKAGE2 = "@teamix-evo/design";
833
- var uninstallCommand = new Command4("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(
834
- "--keep-files",
835
- "\u4EC5\u6E05\u7406 .teamix-evo \u4E2D\u7684\u8BB0\u8D26\u4FE1\u606F\uFF0C\u4E0D\u5220\u9664\u5DF2\u843D\u5730\u8D44\u6E90\u6587\u4EF6"
836
- ).action(async (opts) => {
837
- try {
838
- const ide = detectIde();
839
- const projectRoot = ide.getProjectRoot();
840
- const config = await readProjectConfig(projectRoot);
841
- if (!config?.packages?.design) {
842
- logger.info("Design system is not installed. Nothing to do.");
843
- return;
844
- }
845
- const installedManifest = await readInstalledManifest(projectRoot);
846
- const pkg = installedManifest?.installed.find(
847
- (p) => p.package === DESIGN_PACKAGE2
848
- );
849
- const resources = pkg?.resources ?? [];
850
- const removable = opts.keepFiles ? [] : resources.filter((r) => r.strategy !== "managed");
851
- const kept = resources.length - removable.length;
852
- logger.info(
853
- `Will remove ${removable.length} file(s); keep ${kept} managed file(s).`
854
- );
855
- if (!opts.yes) {
856
- const confirm4 = await prompts.confirm({
857
- message: "\u786E\u8BA4\u5378\u8F7D\u8BBE\u8BA1\u4F53\u7CFB\uFF1F",
858
- initialValue: false
859
- });
860
- if (prompts.isCancel(confirm4) || !confirm4) {
861
- logger.info("Cancelled.");
862
- return;
863
- }
864
- }
865
- let removed = 0;
866
- for (const r of removable) {
867
- const target = path9.isAbsolute(r.target) ? r.target : path9.join(projectRoot, r.target);
868
- try {
869
- await fs7.unlink(target);
870
- removed++;
871
- } catch (err) {
872
- if (err.code !== "ENOENT") {
873
- logger.warn(
874
- `Failed to remove ${target}: ${err.message}`
875
- );
876
- }
877
- }
878
- }
879
- if (installedManifest) {
880
- installedManifest.installed = installedManifest.installed.filter(
881
- (p) => p.package !== DESIGN_PACKAGE2
882
- );
883
- await writeInstalledManifest(projectRoot, installedManifest);
884
- }
885
- delete config.packages.design;
886
- await writeProjectConfig(projectRoot, config);
887
- logger.success(`Uninstalled ${DESIGN_PACKAGE2}`);
888
- logger.info(` Removed: ${removed} files`);
889
- if (kept > 0) {
890
- logger.info(
891
- ` Kept: ${kept} managed files (you may delete manually)`
892
- );
893
- }
894
- } catch (err) {
895
- logger.error(`Failed to uninstall: ${err.message}`);
896
- logger.debug(err.stack ?? "");
897
- process.exitCode = 1;
898
- }
899
- });
900
-
901
- // src/commands/design/index.ts
902
- var designCommand = new Command5("design").description(
903
- "\u7BA1\u7406\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90"
904
- );
905
- designCommand.addCommand(initCommand);
906
- designCommand.addCommand(updateCommand);
907
- designCommand.addCommand(listCommand);
908
- designCommand.addCommand(uninstallCommand);
909
-
910
- // src/commands/skills/index.ts
911
- import { Command as Command10 } from "commander";
912
-
913
- // src/commands/skills/add.ts
914
- import { Command as Command6 } from "commander";
915
- import * as prompts2 from "@clack/prompts";
916
341
 
917
342
  // src/core/skills-client.ts
918
- import * as path10 from "path";
919
- import * as fs8 from "fs/promises";
920
- import { createRequire as createRequire2 } from "module";
343
+ import * as path6 from "path";
344
+ import * as fs2 from "fs/promises";
345
+ import { createRequire } from "module";
921
346
  import { loadSkillsPackageManifest } from "@teamix-evo/registry";
922
- var require3 = createRequire2(import.meta.url);
923
- function resolvePackageRoot2(packageName) {
924
- const pkgJsonPath = require3.resolve(`${packageName}/package.json`);
925
- return path10.dirname(pkgJsonPath);
347
+ var require2 = createRequire(import.meta.url);
348
+ function resolvePackageRoot(packageName) {
349
+ const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
350
+ return path6.dirname(pkgJsonPath);
926
351
  }
927
352
  async function loadSkillsData(packageName) {
928
- const packageRoot = resolvePackageRoot2(packageName);
353
+ const packageRoot = resolvePackageRoot(packageName);
929
354
  logger.debug(`Resolved skills package root: ${packageRoot}`);
930
355
  const manifest = await loadSkillsPackageManifest(packageRoot);
931
356
  let data = {};
932
- const dataPath = path10.join(packageRoot, "_data.json");
357
+ const dataPath = path6.join(packageRoot, "_data.json");
933
358
  try {
934
- const raw = await fs8.readFile(dataPath, "utf-8");
359
+ const raw = await fs2.readFile(dataPath, "utf-8");
935
360
  data = JSON.parse(raw);
936
361
  } catch (err) {
937
362
  if (err.code !== "ENOENT") {
@@ -943,9 +368,56 @@ async function loadSkillsData(packageName) {
943
368
  }
944
369
 
945
370
  // src/core/skills-installer.ts
946
- import * as path11 from "path";
947
- import * as fs9 from "fs/promises";
948
- import { replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
371
+ import * as path8 from "path";
372
+ import * as fs5 from "fs/promises";
373
+ import { replaceManagedRegion } from "@teamix-evo/registry";
374
+
375
+ // src/utils/template.ts
376
+ import Handlebars from "handlebars";
377
+ import * as fs3 from "fs/promises";
378
+ Handlebars.registerHelper("lowercase", (str) => {
379
+ return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
380
+ });
381
+ var compiledCache = /* @__PURE__ */ new Map();
382
+ var MAX_CACHE_SIZE = 64;
383
+ function getCompiledTemplate(templateContent) {
384
+ let compiled = compiledCache.get(templateContent);
385
+ if (!compiled) {
386
+ if (compiledCache.size >= MAX_CACHE_SIZE) {
387
+ const firstKey = compiledCache.keys().next().value;
388
+ compiledCache.delete(firstKey);
389
+ }
390
+ compiled = Handlebars.compile(templateContent, { noEscape: true });
391
+ compiledCache.set(templateContent, compiled);
392
+ }
393
+ return compiled;
394
+ }
395
+ function renderTemplate(templateContent, data) {
396
+ const compiled = getCompiledTemplate(templateContent);
397
+ return compiled(data);
398
+ }
399
+ async function loadTemplateFile(filePath) {
400
+ return fs3.readFile(filePath, "utf-8");
401
+ }
402
+
403
+ // src/utils/path.ts
404
+ import * as path7 from "path";
405
+ import * as fs4 from "fs/promises";
406
+ async function walkDir(dir) {
407
+ const files = [];
408
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
409
+ for (const entry of entries) {
410
+ const fullPath = path7.join(dir, entry.name);
411
+ if (entry.isDirectory()) {
412
+ files.push(...await walkDir(fullPath));
413
+ } else if (entry.isFile()) {
414
+ files.push(fullPath);
415
+ }
416
+ }
417
+ return files;
418
+ }
419
+
420
+ // src/core/skills-installer.ts
949
421
  async function installSkills(options) {
950
422
  const { manifest, ides, scope, onlyIds } = options;
951
423
  const installed = [];
@@ -962,60 +434,85 @@ async function installSkills(options) {
962
434
  );
963
435
  continue;
964
436
  }
437
+ const sourceRecords = await writeSkillSource(skill, options);
438
+ installed.push(...sourceRecords);
965
439
  for (const ide of skillIdes) {
966
- const result = await installSkillForIde(skill, ide, scope, options);
967
- installed.push(...result);
440
+ const mirrorRecords = await mirrorSkillToIde(
441
+ skill,
442
+ ide,
443
+ scope,
444
+ options.projectRoot
445
+ );
446
+ installed.push(...mirrorRecords);
968
447
  }
969
448
  }
970
449
  return { resources: installed, count: installed.length };
971
450
  }
972
- async function installSkillForIde(skill, ide, scope, options) {
451
+ async function writeSkillSource(skill, options) {
973
452
  const { data, packageRoot, projectRoot } = options;
974
- const adapter = getAdapter(ide);
975
- const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
976
- const sourceAbs = path11.resolve(packageRoot, skill.source);
977
- const stat2 = await fs9.stat(sourceAbs);
978
- const results = [];
979
- if (stat2.isFile()) {
980
- const targetFile = path11.join(targetDir, "SKILL.md");
453
+ const sourceAbs = path8.resolve(packageRoot, skill.source);
454
+ const targetDir = getSkillsSourceDir(projectRoot, skill.name);
455
+ const stat4 = await fs5.stat(sourceAbs);
456
+ const records = [];
457
+ if (stat4.isFile()) {
458
+ const targetFile = path8.join(targetDir, "SKILL.md");
981
459
  const content = await renderSkillContent(sourceAbs, skill, data);
982
460
  await writeFileSafe(targetFile, content);
983
- results.push(makeInstalledRecord(skill, targetFile, content, ide, scope));
984
- logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
985
- return results;
461
+ records.push(makeSourceRecord(skill, targetFile, content));
462
+ logger.debug(` Wrote source: ${targetFile}`);
463
+ return records;
986
464
  }
987
465
  await ensureDir(targetDir);
988
466
  const entries = await walkDir(sourceAbs);
989
467
  for (const entry of entries) {
990
- const rel2 = path11.relative(sourceAbs, entry);
991
- let targetFile = path11.join(targetDir, rel2);
468
+ const rel2 = path8.relative(sourceAbs, entry);
469
+ let targetFile = path8.join(targetDir, rel2);
992
470
  if (skill.template && targetFile.endsWith(".hbs")) {
993
471
  targetFile = targetFile.slice(0, -4);
994
472
  }
995
- let content;
996
- if (skill.template && entry.endsWith(".hbs")) {
997
- const tpl = await loadTemplateFile(entry);
998
- content = renderTemplate(tpl, { ...data, skill });
999
- } else {
1000
- content = await fs9.readFile(entry, "utf-8");
1001
- }
473
+ const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
1002
474
  await writeFileSafe(targetFile, content);
1003
- results.push(
1004
- makeInstalledRecord(skill, targetFile, content, ide, scope, rel2)
1005
- );
1006
- logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
1007
- }
1008
- return results;
1009
- }
1010
- async function renderSkillContent(sourceAbs, skill, data) {
1011
- if (skill.template ?? sourceAbs.endsWith(".hbs")) {
1012
- const tpl = await loadTemplateFile(sourceAbs);
1013
- return renderTemplate(tpl, { ...data, skill });
475
+ const relWritten = path8.relative(targetDir, targetFile);
476
+ records.push(makeSourceRecord(skill, targetFile, content, relWritten));
477
+ logger.debug(` Wrote source: ${targetFile}`);
1014
478
  }
1015
- return fs9.readFile(sourceAbs, "utf-8");
479
+ return records;
1016
480
  }
1017
- function makeInstalledRecord(skill, targetAbs, content, ide, scope, rel2) {
1018
- const id = rel2 ? `${skill.id}:${rel2}` : skill.id;
481
+ async function mirrorSkillToIde(skill, ide, scope, projectRoot) {
482
+ const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
483
+ const adapter = getAdapter(ide);
484
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
485
+ const records = [];
486
+ const sourceFiles = await walkDir(sourceDir);
487
+ await ensureDir(targetDir);
488
+ 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));
494
+ logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
495
+ }
496
+ return records;
497
+ }
498
+ async function renderSkillContent(sourceAbs, skill, data) {
499
+ if (skill.template ?? sourceAbs.endsWith(".hbs")) {
500
+ const tpl = await loadTemplateFile(sourceAbs);
501
+ return renderTemplate(tpl, { ...data, skill });
502
+ }
503
+ return fs5.readFile(sourceAbs, "utf-8");
504
+ }
505
+ function makeSourceRecord(skill, targetAbs, content, rel2) {
506
+ const id = rel2 ? `${skill.id}:source:${rel2}` : `${skill.id}:source`;
507
+ return {
508
+ id,
509
+ target: targetAbs,
510
+ hash: computeHash(content),
511
+ strategy: skill.updateStrategy
512
+ };
513
+ }
514
+ function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
515
+ const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
1019
516
  return {
1020
517
  id,
1021
518
  target: targetAbs,
@@ -1026,55 +523,46 @@ function makeInstalledRecord(skill, targetAbs, content, ide, scope, rel2) {
1026
523
  };
1027
524
  }
1028
525
  async function updateSkills(options) {
1029
- const { manifest, ides, scope, installed, projectRoot, data, packageRoot } = options;
526
+ const { manifest, ides, scope, projectRoot } = options;
1030
527
  const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
1031
528
  const updated = [];
1032
- const installedMap = /* @__PURE__ */ new Map();
1033
- for (const r of installed) {
1034
- installedMap.set(installedKey(r), r);
1035
- }
1036
529
  for (const skill of manifest.skills) {
1037
530
  const skillIdes = skill.ides.filter((i) => ides.includes(i));
531
+ if (skillIdes.length === 0) continue;
532
+ const sourceRecords = await rewriteSkillSource(
533
+ skill,
534
+ options,
535
+ summary
536
+ );
537
+ updated.push(...sourceRecords);
1038
538
  for (const ide of skillIdes) {
1039
- const records = await updateSkillForIde(
539
+ const mirrorRecords = await mirrorSkillToIde(
1040
540
  skill,
1041
541
  ide,
1042
542
  scope,
1043
- data,
1044
- packageRoot,
1045
- projectRoot,
1046
- installedMap,
1047
- summary
543
+ projectRoot
1048
544
  );
1049
- updated.push(...records);
545
+ updated.push(...mirrorRecords);
1050
546
  }
1051
547
  }
1052
548
  return { resources: updated, summary };
1053
549
  }
1054
- async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRoot, installedMap, summary) {
1055
- const adapter = getAdapter(ide);
1056
- const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
1057
- const sourceAbs = path11.resolve(packageRoot, skill.source);
1058
- const stat2 = await fs9.stat(sourceAbs);
1059
- const records = [];
1060
- if (!stat2.isFile()) {
1061
- const entries = await walkDir(sourceAbs);
550
+ async function rewriteSkillSource(skill, options, summary) {
551
+ const { data, packageRoot, projectRoot } = options;
552
+ const sourceAbs = path8.resolve(packageRoot, skill.source);
553
+ const targetDir = getSkillsSourceDir(projectRoot, skill.name);
554
+ const stat4 = await fs5.stat(sourceAbs);
555
+ if (!stat4.isFile()) {
1062
556
  await ensureDir(targetDir);
557
+ const entries = await walkDir(sourceAbs);
558
+ const records = [];
1063
559
  for (const entry of entries) {
1064
- const rel2 = path11.relative(sourceAbs, entry);
1065
- let targetFile2 = path11.join(targetDir, rel2);
560
+ const rel2 = path8.relative(sourceAbs, entry);
561
+ let targetFile2 = path8.join(targetDir, rel2);
1066
562
  if (skill.template && targetFile2.endsWith(".hbs")) {
1067
563
  targetFile2 = targetFile2.slice(0, -4);
1068
564
  }
1069
- let content;
1070
- if (skill.template && entry.endsWith(".hbs")) {
1071
- content = renderTemplate(await loadTemplateFile(entry), {
1072
- ...data,
1073
- skill
1074
- });
1075
- } else {
1076
- content = await fs9.readFile(entry, "utf-8");
1077
- }
565
+ const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
1078
566
  const exists2 = await fileExists(targetFile2);
1079
567
  if (exists2) {
1080
568
  await backupFile(targetFile2, projectRoot);
@@ -1083,30 +571,23 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
1083
571
  summary.created++;
1084
572
  }
1085
573
  await writeFileSafe(targetFile2, content);
1086
- records.push(
1087
- makeInstalledRecord(skill, targetFile2, content, ide, scope, rel2)
1088
- );
574
+ const relWritten = path8.relative(targetDir, targetFile2);
575
+ records.push(makeSourceRecord(skill, targetFile2, content, relWritten));
1089
576
  }
1090
577
  return records;
1091
578
  }
1092
- const targetFile = path11.join(targetDir, "SKILL.md");
579
+ const targetFile = path8.join(targetDir, "SKILL.md");
1093
580
  const newContent = await renderSkillContent(sourceAbs, skill, data);
1094
581
  const exists = await fileExists(targetFile);
1095
- const installedKeyStr = `${skill.id}|${ide}|${scope}`;
1096
- const prior = installedMap.get(installedKeyStr);
1097
582
  if (skill.updateStrategy === "frozen") {
1098
583
  if (exists) {
1099
584
  summary.skipped++;
1100
- return [
1101
- prior ?? makeInstalledRecord(skill, targetFile, newContent, ide, scope)
1102
- ];
585
+ const current2 = await readFileOrNull(targetFile) ?? newContent;
586
+ return [makeSourceRecord(skill, targetFile, current2)];
1103
587
  }
1104
588
  await writeFileSafe(targetFile, newContent);
1105
589
  summary.created++;
1106
- records.push(
1107
- makeInstalledRecord(skill, targetFile, newContent, ide, scope)
1108
- );
1109
- return records;
590
+ return [makeSourceRecord(skill, targetFile, newContent)];
1110
591
  }
1111
592
  if (skill.updateStrategy === "regenerable" || !exists) {
1112
593
  if (exists) {
@@ -1116,19 +597,15 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
1116
597
  summary.created++;
1117
598
  }
1118
599
  await writeFileSafe(targetFile, newContent);
1119
- records.push(
1120
- makeInstalledRecord(skill, targetFile, newContent, ide, scope)
1121
- );
1122
- return records;
600
+ return [makeSourceRecord(skill, targetFile, newContent)];
1123
601
  }
1124
602
  const current = await readFileOrNull(targetFile);
1125
- let updated = current ?? newContent;
1126
- const regionIds = skill.managedRegions ?? [];
1127
- for (const regionId of regionIds) {
603
+ let merged = current ?? newContent;
604
+ for (const regionId of skill.managedRegions ?? []) {
1128
605
  const re = new RegExp(
1129
- `<!-- teamix-evo:managed:start id="${escapeRegExp2(
606
+ `<!-- teamix-evo:managed:start id="${escapeRegExp(
1130
607
  regionId
1131
- )}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp2(
608
+ )}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp(
1132
609
  regionId
1133
610
  )}" -->`
1134
611
  );
@@ -1136,7 +613,7 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
1136
613
  if (match) {
1137
614
  const region = match[1].replace(/^\n/, "").replace(/\n$/, "");
1138
615
  try {
1139
- updated = replaceManagedRegion2(updated, regionId, region);
616
+ merged = replaceManagedRegion(merged, regionId, region);
1140
617
  } catch {
1141
618
  logger.warn(
1142
619
  `Managed region "${regionId}" not found in ${targetFile}. Skipped.`
@@ -1145,22 +622,57 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
1145
622
  }
1146
623
  }
1147
624
  await backupFile(targetFile, projectRoot);
1148
- await writeFileSafe(targetFile, updated);
625
+ await writeFileSafe(targetFile, merged);
1149
626
  summary.managed++;
1150
- records.push(makeInstalledRecord(skill, targetFile, updated, ide, scope));
1151
- return records;
627
+ return [makeSourceRecord(skill, targetFile, merged)];
1152
628
  }
1153
- function installedKey(r) {
1154
- return `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
1155
- }
1156
- function escapeRegExp2(str) {
629
+ function escapeRegExp(str) {
1157
630
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1158
631
  }
632
+ async function syncSkillsToIdes(options) {
633
+ const { projectRoot, skills, ides, scope, onlyIds } = options;
634
+ const out = [];
635
+ const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
636
+ for (const skill of targets) {
637
+ const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
638
+ if (!await fileExists(sourceDir)) {
639
+ logger.warn(
640
+ `Skill "${skill.id}" has no source at ${sourceDir}; skipped.`
641
+ );
642
+ continue;
643
+ }
644
+ for (const ide of ides) {
645
+ const adapter = getAdapter(ide);
646
+ const targetDir = adapter.getSkillTargetDir(
647
+ skill.name,
648
+ scope,
649
+ projectRoot
650
+ );
651
+ await ensureDir(targetDir);
652
+ const sourceFiles = await walkDir(sourceDir);
653
+ 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);
658
+ out.push({
659
+ id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
660
+ target: targetFile,
661
+ hash: computeHash(content),
662
+ strategy: skill.updateStrategy,
663
+ ide,
664
+ scope
665
+ });
666
+ }
667
+ }
668
+ }
669
+ return { resources: out, count: out.length };
670
+ }
1159
671
  async function removeSkillFiles(records) {
1160
672
  const removed = [];
1161
673
  for (const r of records) {
1162
674
  try {
1163
- await fs9.unlink(r.target);
675
+ await fs5.unlink(r.target);
1164
676
  removed.push(r.target);
1165
677
  } catch (err) {
1166
678
  if (err.code !== "ENOENT") {
@@ -1168,11 +680,11 @@ async function removeSkillFiles(records) {
1168
680
  }
1169
681
  }
1170
682
  }
1171
- const parents = new Set(records.map((r) => path11.dirname(r.target)));
683
+ const parents = new Set(records.map((r) => path8.dirname(r.target)));
1172
684
  for (const dir of parents) {
1173
685
  try {
1174
- const entries = await fs9.readdir(dir);
1175
- if (entries.length === 0) await fs9.rmdir(dir);
686
+ const entries = await fs5.readdir(dir);
687
+ if (entries.length === 0) await fs5.rmdir(dir);
1176
688
  } catch {
1177
689
  }
1178
690
  }
@@ -1202,6 +714,7 @@ async function runSkillsAdd(options) {
1202
714
  throw new Error("Scope must be specified (project | global).");
1203
715
  }
1204
716
  const { manifest, data, packageRoot } = await loadSkillsData(packageName);
717
+ const currentDesignVariant = await readDesignVariant(projectRoot);
1205
718
  if (isIncremental) {
1206
719
  const known = new Set(manifest.skills.map((s) => s.id));
1207
720
  const unknown = requestedNames.filter((n) => !known.has(n));
@@ -1212,102 +725,605 @@ async function runSkillsAdd(options) {
1212
725
  );
1213
726
  }
1214
727
  }
1215
- const existingInstalled = await readInstalledManifest(projectRoot);
1216
- const existingPkg = existingInstalled?.installed.find(
1217
- (p) => p.package === packageName
1218
- );
1219
- const existingSkillIds = new Set(
1220
- (existingPkg?.resources ?? []).map((r) => r.id.split(":")[0])
1221
- );
1222
- let onlyIds;
1223
- let skippedSkillIds;
1224
- if (isIncremental) {
1225
- skippedSkillIds = requestedNames.filter((n) => existingSkillIds.has(n));
1226
- onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
1227
- } else {
1228
- skippedSkillIds = [];
1229
- onlyIds = manifest.skills.map((s) => s.id);
1230
- }
1231
- if (isIncremental && onlyIds.length === 0) {
1232
- return {
1233
- status: "installed",
1234
- packageName,
1235
- version: existingSkillsCfg?.version ?? manifest.version,
1236
- ides,
1237
- scope,
1238
- skillCount: 0,
1239
- fileCount: 0,
1240
- resources: [],
1241
- addedSkillIds: [],
1242
- skippedSkillIds
1243
- };
1244
- }
1245
- const result = await installSkills({
1246
- projectRoot,
1247
- manifest,
1248
- data,
1249
- packageRoot,
1250
- ides,
1251
- scope,
1252
- onlyIds
1253
- });
1254
- const config = existingConfig ?? {
1255
- $schema: "https://teamix-evo.dev/schema/config/v1.json",
1256
- schemaVersion: 1,
1257
- ide: ideIdent,
1258
- packages: {}
1259
- };
1260
- config.packages.skills = {
1261
- variant: FLAT_VARIANT,
1262
- version: manifest.version,
1263
- ides,
1264
- scope
1265
- };
1266
- await writeProjectConfig(projectRoot, config);
1267
- const installedManifest = existingInstalled ?? {
1268
- schemaVersion: 1,
1269
- installed: []
1270
- };
1271
- const idx = installedManifest.installed.findIndex(
1272
- (p) => p.package === packageName
1273
- );
1274
- const mergedResources = mergeInstalledResources(
1275
- existingPkg?.resources ?? [],
1276
- result.resources
1277
- );
1278
- const entry = {
1279
- package: packageName,
1280
- variant: FLAT_VARIANT,
1281
- version: manifest.version,
1282
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1283
- resources: mergedResources
1284
- };
1285
- if (idx >= 0) installedManifest.installed[idx] = entry;
1286
- else installedManifest.installed.push(entry);
1287
- await writeInstalledManifest(projectRoot, installedManifest);
1288
- return {
1289
- status: "installed",
1290
- packageName,
1291
- version: manifest.version,
1292
- ides,
1293
- scope,
1294
- skillCount: onlyIds.length,
1295
- fileCount: result.count,
1296
- resources: result.resources,
1297
- addedSkillIds: onlyIds,
1298
- skippedSkillIds
1299
- };
728
+ const existingInstalled = await readInstalledManifest(projectRoot);
729
+ const existingPkg = existingInstalled?.installed.find(
730
+ (p) => p.package === packageName
731
+ );
732
+ const existingLock = await readSkillsLock(projectRoot);
733
+ const existingSkillIds = /* @__PURE__ */ new Set([
734
+ ...Object.keys(existingLock?.skills ?? {}),
735
+ // Legacy fallback: pre-ADR-0013 installs only had manifest.json. Derive
736
+ // skill ids by stripping the trailing :source / :sub-file suffix.
737
+ ...(existingPkg?.resources ?? []).map((r) => r.id.split(":")[0])
738
+ ]);
739
+ let onlyIds;
740
+ let skippedSkillIds;
741
+ if (isIncremental) {
742
+ skippedSkillIds = requestedNames.filter((n) => existingSkillIds.has(n));
743
+ onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
744
+ } else {
745
+ skippedSkillIds = [];
746
+ onlyIds = manifest.skills.filter((s) => {
747
+ if (!s.variant) return true;
748
+ if (!currentDesignVariant) {
749
+ logger.debug(
750
+ `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): no design pack installed; will be picked up when "design init" runs.`
751
+ );
752
+ return false;
753
+ }
754
+ if (s.variant !== currentDesignVariant) {
755
+ logger.debug(
756
+ `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): current design variant is "${currentDesignVariant}".`
757
+ );
758
+ return false;
759
+ }
760
+ return true;
761
+ }).map((s) => s.id);
762
+ }
763
+ if (isIncremental && onlyIds.length === 0) {
764
+ return {
765
+ status: "installed",
766
+ packageName,
767
+ version: existingSkillsCfg?.version ?? manifest.version,
768
+ ides,
769
+ scope,
770
+ skillCount: 0,
771
+ fileCount: 0,
772
+ resources: [],
773
+ addedSkillIds: [],
774
+ skippedSkillIds
775
+ };
776
+ }
777
+ const result = await installSkills({
778
+ projectRoot,
779
+ manifest,
780
+ data,
781
+ packageRoot,
782
+ ides,
783
+ scope,
784
+ onlyIds
785
+ });
786
+ const config = existingConfig ?? {
787
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
788
+ schemaVersion: 1,
789
+ ide: ideIdent,
790
+ packages: {}
791
+ };
792
+ config.packages.skills = {
793
+ variant: FLAT_VARIANT,
794
+ version: manifest.version,
795
+ ides,
796
+ scope
797
+ };
798
+ await writeProjectConfig(projectRoot, config);
799
+ const installedAt = (/* @__PURE__ */ new Date()).toISOString();
800
+ const installedManifest = existingInstalled ?? {
801
+ schemaVersion: 1,
802
+ installed: []
803
+ };
804
+ const idx = installedManifest.installed.findIndex(
805
+ (p) => p.package === packageName
806
+ );
807
+ const mergedResources = mergeInstalledResources(
808
+ existingPkg?.resources ?? [],
809
+ result.resources
810
+ );
811
+ const entry = {
812
+ package: packageName,
813
+ variant: FLAT_VARIANT,
814
+ version: manifest.version,
815
+ installedAt,
816
+ resources: mergedResources
817
+ };
818
+ if (idx >= 0) installedManifest.installed[idx] = entry;
819
+ else installedManifest.installed.push(entry);
820
+ await writeInstalledManifest(projectRoot, installedManifest);
821
+ const lock = existingLock ?? {
822
+ schemaVersion: 1,
823
+ skills: {}
824
+ };
825
+ for (const skillId of onlyIds) {
826
+ const skillDef = manifest.skills.find((s) => s.id === skillId);
827
+ if (!skillDef) continue;
828
+ const mirroredTo = skillDef.ides.filter((i) => ides.includes(i));
829
+ lock.skills[skillId] = {
830
+ version: skillDef.version,
831
+ from: packageName,
832
+ installedAt,
833
+ scope,
834
+ mirroredTo
835
+ };
836
+ }
837
+ await writeSkillsLock(projectRoot, lock);
838
+ return {
839
+ status: "installed",
840
+ packageName,
841
+ version: manifest.version,
842
+ ides,
843
+ scope,
844
+ skillCount: onlyIds.length,
845
+ fileCount: result.count,
846
+ resources: result.resources,
847
+ addedSkillIds: onlyIds,
848
+ skippedSkillIds
849
+ };
850
+ }
851
+ function mergeInstalledResources(existing, next) {
852
+ const map = /* @__PURE__ */ new Map();
853
+ const key = (r) => `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
854
+ for (const r of existing) map.set(key(r), r);
855
+ for (const r of next) map.set(key(r), r);
856
+ return [...map.values()];
857
+ }
858
+
859
+ // src/core/design-init.ts
860
+ var BASELINE_DESIGN_RULES_SKILL = "teamix-evo-design-rules";
861
+ var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
862
+ var DEFAULT_AUTO_SKILL_IDES = ["qoder", "claude"];
863
+ var DEFAULT_AUTO_SKILL_SCOPE = "project";
864
+ var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
865
+ var require3 = createRequire2(import.meta.url);
866
+ async function runDesignInit(options) {
867
+ const { projectRoot, variant, ide } = options;
868
+ const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
869
+ 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);
880
+ if (!variantEntry) {
881
+ const known = catalog.variants.map((v) => v.name).join(", ");
882
+ throw new Error(
883
+ `Design variant not found: "${variant}". Known variants: ${known || "(none)"}. Hint: run "teamix-evo design list-variants" to see all.`
884
+ );
885
+ }
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);
891
+ const installed = [];
892
+ for (const file of merge.files) {
893
+ const result = await installPackFile(file, projectRoot);
894
+ if (result) installed.push(result);
895
+ }
896
+ const lock = {
897
+ schemaVersion: 1,
898
+ default: { version: defaultPack.version, from: packageName },
899
+ variant: {
900
+ name: variantPack.name,
901
+ displayName: variantPack.displayName,
902
+ version: variantPack.version,
903
+ from: packageName
904
+ },
905
+ linked: variantPack.linked,
906
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
907
+ };
908
+ await writeFileSafe(
909
+ path9.join(projectRoot, ".teamix-evo", "design", "pack.lock.json"),
910
+ JSON.stringify(lock, null, 2) + "\n"
911
+ );
912
+ const config = {
913
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
914
+ schemaVersion: 1,
915
+ ide,
916
+ packages: {
917
+ design: {
918
+ variant,
919
+ version: variantPack.version,
920
+ tailwind: "v4"
921
+ }
922
+ }
923
+ };
924
+ await writeProjectConfig(projectRoot, config);
925
+ const installedManifest = {
926
+ 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
+ ]
936
+ };
937
+ await writeInstalledManifest(projectRoot, installedManifest);
938
+ const skills = await tryAutoInstallVariantSkills({
939
+ projectRoot,
940
+ variant,
941
+ ide
942
+ });
943
+ return {
944
+ status: "installed",
945
+ packageName,
946
+ variant,
947
+ version: variantPack.version,
948
+ count: installed.length,
949
+ resources: installed,
950
+ merge: {
951
+ overrides: merge.overrides,
952
+ variantAdds: merge.variantAdds,
953
+ defaultPassThrough: merge.defaultPassThrough
954
+ },
955
+ skills
956
+ };
957
+ }
958
+ async function tryAutoInstallVariantSkills(args) {
959
+ const { projectRoot, variant, ide } = args;
960
+ const variantSkillId = `${BASELINE_DESIGN_RULES_SKILL}-${variant}`;
961
+ const desired = [BASELINE_DESIGN_RULES_SKILL, variantSkillId];
962
+ let manifestSkillIds;
963
+ try {
964
+ const { manifest } = await loadSkillsData(DEFAULT_SKILLS_PACKAGE2);
965
+ manifestSkillIds = new Set(manifest.skills.map((s) => s.id));
966
+ } catch (err) {
967
+ logger.warn(
968
+ `Skipping skills auto-install: could not load skills manifest (${err.message}).`
969
+ );
970
+ return {
971
+ attempted: [],
972
+ addedSkillIds: [],
973
+ skippedSkillIds: [],
974
+ missing: desired
975
+ };
976
+ }
977
+ const present = desired.filter((id) => manifestSkillIds.has(id));
978
+ const missing = desired.filter((id) => !manifestSkillIds.has(id));
979
+ if (missing.length > 0) {
980
+ logger.warn(
981
+ `Skills auto-install: not found in manifest, skipping: ${missing.join(", ")}.`
982
+ );
983
+ }
984
+ if (present.length === 0) {
985
+ return {
986
+ attempted: desired,
987
+ addedSkillIds: [],
988
+ skippedSkillIds: [],
989
+ missing
990
+ };
991
+ }
992
+ try {
993
+ const result = await runSkillsAdd({
994
+ projectRoot,
995
+ names: present,
996
+ ides: DEFAULT_AUTO_SKILL_IDES,
997
+ scope: DEFAULT_AUTO_SKILL_SCOPE,
998
+ ide
999
+ });
1000
+ if (result.status !== "installed") {
1001
+ return {
1002
+ attempted: desired,
1003
+ addedSkillIds: [],
1004
+ skippedSkillIds: present,
1005
+ missing
1006
+ };
1007
+ }
1008
+ return {
1009
+ attempted: desired,
1010
+ addedSkillIds: result.addedSkillIds,
1011
+ skippedSkillIds: result.skippedSkillIds,
1012
+ missing
1013
+ };
1014
+ } catch (err) {
1015
+ logger.warn(
1016
+ `Skills auto-install failed (continuing): ${err.message}`
1017
+ );
1018
+ return {
1019
+ attempted: desired,
1020
+ addedSkillIds: [],
1021
+ skippedSkillIds: [],
1022
+ missing
1023
+ };
1024
+ }
1025
+ }
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");
1032
+ return {
1033
+ id: `pack:${file.relPath}`,
1034
+ target: cls.target,
1035
+ hash: computeHash(existing),
1036
+ strategy: cls.strategy
1037
+ };
1038
+ }
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
+ };
1047
+ }
1048
+ function resolveDesignPackageRoot(packageName) {
1049
+ const pkgJson = require3.resolve(`${packageName}/package.json`);
1050
+ return path9.dirname(pkgJson);
1051
+ }
1052
+ async function listDesignVariants(packageName = DEFAULT_DESIGN_PACKAGE, packageRoot) {
1053
+ const root = packageRoot ?? resolveDesignPackageRoot(packageName);
1054
+ const catalog = await loadDesignPackageManifest(root);
1055
+ return {
1056
+ packageName,
1057
+ defaultDescription: catalog.default.description,
1058
+ variants: catalog.variants.map((v) => ({
1059
+ name: v.name,
1060
+ displayName: v.displayName,
1061
+ version: v.version,
1062
+ description: v.description,
1063
+ linked: v.linked
1064
+ }))
1065
+ };
1066
+ }
1067
+
1068
+ // src/commands/design/init.ts
1069
+ 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
+ try {
1071
+ const ide = detectIde();
1072
+ const projectRoot = ide.getProjectRoot();
1073
+ logger.info(`Initializing design system: variant="${variant}"`);
1074
+ logger.debug(`Project root: ${projectRoot}`);
1075
+ logger.debug(`IDE: ${ide.name}`);
1076
+ logger.info(`Loading variant "${variant}"...`);
1077
+ logger.info("Installing resources...");
1078
+ const result = await runDesignInit({
1079
+ projectRoot,
1080
+ variant,
1081
+ ide: ide.name
1082
+ });
1083
+ if (result.status === "already-initialized") {
1084
+ logger.warn(
1085
+ `Design system already initialized (variant: ${result.existingVariant}). Use "teamix-evo design update" to update.`
1086
+ );
1087
+ return;
1088
+ }
1089
+ logger.success(
1090
+ `Design system initialized: ${result.packageName} (${result.variant} v${result.version})`
1091
+ );
1092
+ logger.info(` Variant: ${result.variant}`);
1093
+ logger.info(` Tailwind: v4`);
1094
+ 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
+ if (result.skills) {
1099
+ const { addedSkillIds, skippedSkillIds, missing } = result.skills;
1100
+ if (addedSkillIds.length > 0) {
1101
+ logger.info(
1102
+ ` Skills: auto-installed ${addedSkillIds.join(", ")}`
1103
+ );
1104
+ }
1105
+ if (skippedSkillIds.length > 0) {
1106
+ logger.info(
1107
+ ` Skills: already installed (skipped) ${skippedSkillIds.join(", ")}`
1108
+ );
1109
+ }
1110
+ if (missing.length > 0) {
1111
+ logger.info(
1112
+ ` Skills: not in manifest (skipped) ${missing.join(", ")}`
1113
+ );
1114
+ }
1115
+ }
1116
+ logger.info("");
1117
+ logger.info('Run "teamix-evo design update" to update resources later.');
1118
+ logger.info(
1119
+ 'Run "teamix-evo design list-variants" to see all available variants.'
1120
+ );
1121
+ } catch (err) {
1122
+ logger.error(`Failed to initialize: ${err.message}`);
1123
+ logger.debug(err.stack ?? "");
1124
+ process.exitCode = 1;
1125
+ }
1126
+ });
1127
+
1128
+ // src/commands/design/update.ts
1129
+ 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)."
1133
+ );
1134
+ logger.info(
1135
+ "Workaround: clean `.teamix-evo/design/` and re-run `teamix-evo design init <variant>`."
1136
+ );
1137
+ logger.info(
1138
+ "Tracking: PLAN \xA712.6 v0.7 \u2014 semantic upgrade flow + tokens.overrides.css preservation."
1139
+ );
1140
+ process.exitCode = 0;
1141
+ });
1142
+
1143
+ // src/commands/design/list.ts
1144
+ import { Command as Command3 } from "commander";
1145
+ var listCommand = new Command3("list").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684\u8BBE\u8BA1\u53D8\u4F53").action(async () => {
1146
+ try {
1147
+ const ide = detectIde();
1148
+ const projectRoot = ide.getProjectRoot();
1149
+ 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.');
1153
+ return;
1154
+ }
1155
+ const { variant, version: version2 } = config.packages.design;
1156
+ logger.info("Installed design system:");
1157
+ logger.info(` Package: @teamix-evo/design`);
1158
+ logger.info(` Variant: ${variant}`);
1159
+ logger.info(` Version: ${version2}`);
1160
+ logger.info(` IDE: ${config.ide}`);
1161
+ const manifest = await readInstalledManifest(projectRoot);
1162
+ if (manifest) {
1163
+ const pkg = manifest.installed.find(
1164
+ (p) => p.package === "@teamix-evo/design" && p.variant === variant
1165
+ );
1166
+ if (pkg) {
1167
+ logger.info(` Resources: ${pkg.resources.length} files`);
1168
+ logger.info(
1169
+ ` Installed: ${new Date(pkg.installedAt).toLocaleString()}`
1170
+ );
1171
+ }
1172
+ }
1173
+ } catch (err) {
1174
+ logger.error(`Failed to list: ${err.message}`);
1175
+ process.exitCode = 1;
1176
+ }
1177
+ });
1178
+
1179
+ // src/commands/design/list-variants.ts
1180
+ 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 () => {
1182
+ 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
+ }
1190
+ logger.info("");
1191
+ if (result.variants.length === 0) {
1192
+ logger.info(" (no variants beyond default)");
1193
+ return;
1194
+ }
1195
+ for (const v of result.variants) {
1196
+ logger.info(` ${v.name} (${v.displayName}) \u2014 v${v.version}`);
1197
+ if (v.description) logger.info(` ${v.description}`);
1198
+ if (v.linked) {
1199
+ const links = [];
1200
+ if (v.linked["biz-ui"]) links.push(`biz-ui: ${v.linked["biz-ui"]}`);
1201
+ if (v.linked.templates) links.push(`templates: ${v.linked.templates}`);
1202
+ if (links.length) logger.info(` linked: ${links.join(" / ")}`);
1203
+ }
1204
+ logger.info("");
1205
+ }
1206
+ logger.info("Install a variant: teamix-evo design init <name>");
1207
+ } catch (err) {
1208
+ logger.error(`Failed to list variants: ${err.message}`);
1209
+ logger.debug(err.stack ?? "");
1210
+ process.exitCode = 1;
1211
+ }
1212
+ });
1213
+
1214
+ // src/commands/design/uninstall.ts
1215
+ import { Command as Command5 } from "commander";
1216
+ import * as fs7 from "fs/promises";
1217
+ import * as path10 from "path";
1218
+ 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(
1221
+ "--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"
1223
+ ).action(async (opts) => {
1224
+ try {
1225
+ const ide = detectIde();
1226
+ const projectRoot = ide.getProjectRoot();
1227
+ const config = await readProjectConfig(projectRoot);
1228
+ if (!config?.packages?.design) {
1229
+ logger.info("Design system is not installed. Nothing to do.");
1230
+ return;
1231
+ }
1232
+ const installedManifest = await readInstalledManifest(projectRoot);
1233
+ const pkg = installedManifest?.installed.find(
1234
+ (p) => p.package === DESIGN_PACKAGE
1235
+ );
1236
+ const resources = pkg?.resources ?? [];
1237
+ const removable = opts.keepFiles ? [] : resources.filter((r) => r.strategy !== "managed");
1238
+ const kept = resources.length - removable.length;
1239
+ logger.info(
1240
+ `Will remove ${removable.length} file(s); keep ${kept} managed file(s).`
1241
+ );
1242
+ if (!opts.yes) {
1243
+ const confirm4 = await prompts.confirm({
1244
+ message: "\u786E\u8BA4\u5378\u8F7D\u8BBE\u8BA1\u4F53\u7CFB\uFF1F",
1245
+ initialValue: false
1246
+ });
1247
+ if (prompts.isCancel(confirm4) || !confirm4) {
1248
+ logger.info("Cancelled.");
1249
+ return;
1250
+ }
1251
+ }
1252
+ let removed = 0;
1253
+ for (const r of removable) {
1254
+ const target = path10.isAbsolute(r.target) ? r.target : path10.join(projectRoot, r.target);
1255
+ try {
1256
+ await fs7.unlink(target);
1257
+ removed++;
1258
+ } catch (err) {
1259
+ if (err.code !== "ENOENT") {
1260
+ logger.warn(
1261
+ `Failed to remove ${target}: ${err.message}`
1262
+ );
1263
+ }
1264
+ }
1265
+ }
1266
+ if (installedManifest) {
1267
+ installedManifest.installed = installedManifest.installed.filter(
1268
+ (p) => p.package !== DESIGN_PACKAGE
1269
+ );
1270
+ await writeInstalledManifest(projectRoot, installedManifest);
1271
+ }
1272
+ delete config.packages.design;
1273
+ await writeProjectConfig(projectRoot, config);
1274
+ logger.success(`Uninstalled ${DESIGN_PACKAGE}`);
1275
+ logger.info(` Removed: ${removed} files`);
1276
+ if (kept > 0) {
1277
+ logger.info(
1278
+ ` Kept: ${kept} managed files (you may delete manually)`
1279
+ );
1280
+ }
1281
+ } catch (err) {
1282
+ logger.error(`Failed to uninstall: ${err.message}`);
1283
+ logger.debug(err.stack ?? "");
1284
+ process.exitCode = 1;
1285
+ }
1286
+ });
1287
+
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);
1297
+
1298
+ // src/commands/skills/index.ts
1299
+ import { Command as Command13 } from "commander";
1300
+
1301
+ // src/commands/skills/add.ts
1302
+ import { Command as Command7 } from "commander";
1303
+ 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);
1300
1315
  }
1301
- function mergeInstalledResources(existing, next) {
1302
- const map = /* @__PURE__ */ new Map();
1303
- const key = (r) => `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
1304
- for (const r of existing) map.set(key(r), r);
1305
- for (const r of next) map.set(key(r), r);
1306
- return [...map.values()];
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;
1307
1323
  }
1308
1324
 
1309
1325
  // src/commands/skills/add.ts
1310
- var addCommand = new Command6("add").description(
1326
+ var addCommand = new Command7("add").description(
1311
1327
  "\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"
1312
1328
  ).argument(
1313
1329
  "[names...]",
@@ -1318,13 +1334,18 @@ var addCommand = new Command6("add").description(
1318
1334
  ).option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").action(async (names, opts) => {
1319
1335
  try {
1320
1336
  const ide = detectIde();
1321
- const projectRoot = ide.getProjectRoot();
1337
+ const cwd = ide.getProjectRoot();
1322
1338
  const isIncremental = names.length > 0;
1323
1339
  const { ides, scope } = await resolveIdesAndScope({
1324
1340
  opts,
1325
- projectRoot,
1341
+ projectRoot: cwd,
1326
1342
  isIncremental
1327
1343
  });
1344
+ let projectRoot = cwd;
1345
+ if (scope === "global" && !isTeamixEvoProject(cwd)) {
1346
+ projectRoot = await ensureGlobalMetaRoot();
1347
+ logger.info(`Global skill install \u2014 meta root: ${projectRoot}`);
1348
+ }
1328
1349
  logger.info(
1329
1350
  isIncremental ? `Adding skills [${names.join(",")}]: ides=[${ides.join(
1330
1351
  ","
@@ -1439,9 +1460,9 @@ function parseScope(input) {
1439
1460
  }
1440
1461
 
1441
1462
  // src/commands/skills/list.ts
1442
- import { Command as Command7 } from "commander";
1463
+ import { Command as Command8 } from "commander";
1443
1464
  var SKILLS_PACKAGE = "@teamix-evo/skills";
1444
- var listCommand2 = new Command7("list").alias("ls").description(
1465
+ var listCommand2 = new Command8("list").alias("ls").description(
1445
1466
  "\u5217\u51FA teamix-evo skills\uFF08\u9ED8\u8BA4\u5C55\u793A\u5168\u90E8 skill \u5E76\u6807\u6CE8\u5DF2\u88C5/\u672A\u88C5\uFF1B--installed \u4EC5\u770B\u5DF2\u88C5\uFF09"
1446
1467
  ).option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 skill\uFF08\u9690\u85CF\u672A\u5B89\u88C5\u9879\uFF09").action(async (opts) => {
1447
1468
  try {
@@ -1521,10 +1542,10 @@ function printInstalledHeader(cfg, installedAt) {
1521
1542
  }
1522
1543
 
1523
1544
  // src/commands/skills/update.ts
1524
- import { Command as Command8 } from "commander";
1545
+ import { Command as Command9 } from "commander";
1525
1546
  var SKILLS_PACKAGE2 = "@teamix-evo/skills";
1526
1547
  var FLAT_VARIANT2 = "_flat";
1527
- var updateCommand2 = new Command8("update").description("\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 teamix-evo skills").action(async () => {
1548
+ var updateCommand2 = new Command9("update").description("\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 teamix-evo skills").action(async () => {
1528
1549
  try {
1529
1550
  const ide = detectIde();
1530
1551
  const projectRoot = ide.getProjectRoot();
@@ -1550,10 +1571,6 @@ var updateCommand2 = new Command8("update").description("\u66F4\u65B0\u5DF2\u5B8
1550
1571
  const { manifest, data, packageRoot } = await loadSkillsData(
1551
1572
  SKILLS_PACKAGE2
1552
1573
  );
1553
- const pkgInstalled = installedManifest.installed.find(
1554
- (p) => p.package === SKILLS_PACKAGE2
1555
- );
1556
- const installedResources = pkgInstalled?.resources ?? [];
1557
1574
  logger.info(
1558
1575
  `Current: v${skillsEntry.version} \u2192 Available: v${manifest.version}`
1559
1576
  );
@@ -1563,101 +1580,416 @@ var updateCommand2 = new Command8("update").description("\u66F4\u65B0\u5DF2\u5B8
1563
1580
  data,
1564
1581
  packageRoot,
1565
1582
  ides,
1566
- scope,
1567
- installed: installedResources
1583
+ scope
1568
1584
  });
1569
1585
  config.packages.skills.version = manifest.version;
1570
1586
  await writeProjectConfig(projectRoot, config);
1587
+ const installedAt = (/* @__PURE__ */ new Date()).toISOString();
1571
1588
  const idx = installedManifest.installed.findIndex(
1572
1589
  (p) => p.package === SKILLS_PACKAGE2
1573
1590
  );
1574
- const entry = {
1575
- package: SKILLS_PACKAGE2,
1576
- variant: FLAT_VARIANT2,
1577
- version: manifest.version,
1578
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1579
- resources: result.resources
1580
- };
1581
- if (idx >= 0) installedManifest.installed[idx] = entry;
1582
- else installedManifest.installed.push(entry);
1583
- await writeInstalledManifest(projectRoot, installedManifest);
1584
- const { summary } = result;
1585
- logger.success(`Skills updated to v${manifest.version}`);
1586
- logger.info(` Created: ${summary.created}`);
1587
- logger.info(` Overwritten: ${summary.overwritten}`);
1588
- logger.info(` Managed: ${summary.managed}`);
1589
- logger.info(` Skipped: ${summary.skipped}`);
1591
+ const entry = {
1592
+ package: SKILLS_PACKAGE2,
1593
+ variant: FLAT_VARIANT2,
1594
+ version: manifest.version,
1595
+ installedAt,
1596
+ resources: result.resources
1597
+ };
1598
+ if (idx >= 0) installedManifest.installed[idx] = entry;
1599
+ else installedManifest.installed.push(entry);
1600
+ await writeInstalledManifest(projectRoot, installedManifest);
1601
+ const existingLock = await readSkillsLock(projectRoot) ?? {
1602
+ schemaVersion: 1,
1603
+ skills: {}
1604
+ };
1605
+ const lock = {
1606
+ schemaVersion: 1,
1607
+ skills: { ...existingLock.skills }
1608
+ };
1609
+ for (const skill of manifest.skills) {
1610
+ const mirroredTo = skill.ides.filter((i) => ides.includes(i));
1611
+ if (mirroredTo.length === 0) continue;
1612
+ lock.skills[skill.id] = {
1613
+ version: skill.version,
1614
+ from: SKILLS_PACKAGE2,
1615
+ installedAt,
1616
+ scope,
1617
+ mirroredTo
1618
+ };
1619
+ }
1620
+ await writeSkillsLock(projectRoot, lock);
1621
+ const { summary } = result;
1622
+ logger.success(`Skills updated to v${manifest.version}`);
1623
+ logger.info(` Created: ${summary.created}`);
1624
+ logger.info(` Overwritten: ${summary.overwritten}`);
1625
+ logger.info(` Managed: ${summary.managed}`);
1626
+ logger.info(` Skipped: ${summary.skipped}`);
1627
+ } catch (err) {
1628
+ logger.error(`Failed to update skills: ${err.message}`);
1629
+ logger.debug(err.stack ?? "");
1630
+ process.exitCode = 1;
1631
+ }
1632
+ });
1633
+
1634
+ // src/commands/skills/uninstall.ts
1635
+ import { Command as Command10 } from "commander";
1636
+ import * as prompts3 from "@clack/prompts";
1637
+ import * as path12 from "path";
1638
+ import * as fs9 from "fs/promises";
1639
+ var SKILLS_PACKAGE3 = "@teamix-evo/skills";
1640
+ var uninstallCommand2 = new Command10("uninstall").description("\u5378\u8F7D\u5DF2\u5B89\u88C5\u7684 teamix-evo skills\uFF08\u5220\u9664\u6CE8\u5165\u5230 IDE \u7684\u6280\u80FD\u6587\u4EF6\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").action(async (opts) => {
1641
+ try {
1642
+ const ide = detectIde();
1643
+ const projectRoot = ide.getProjectRoot();
1644
+ const config = await readProjectConfig(projectRoot);
1645
+ if (!config?.packages?.skills) {
1646
+ logger.info("Skills are not installed. Nothing to do.");
1647
+ return;
1648
+ }
1649
+ const installedManifest = await readInstalledManifest(projectRoot);
1650
+ const pkg = installedManifest?.installed.find(
1651
+ (p) => p.package === SKILLS_PACKAGE3
1652
+ );
1653
+ const resources = pkg?.resources ?? [];
1654
+ logger.info(
1655
+ `Will remove ${resources.length} skill file(s) installed by ${SKILLS_PACKAGE3}.`
1656
+ );
1657
+ if (!opts.yes) {
1658
+ const confirm4 = await prompts3.confirm({
1659
+ message: "\u786E\u8BA4\u5378\u8F7D\uFF1F\u6B64\u64CD\u4F5C\u4F1A\u5220\u9664\u4E0A\u8FF0\u6587\u4EF6\u3002",
1660
+ initialValue: false
1661
+ });
1662
+ if (prompts3.isCancel(confirm4) || !confirm4) {
1663
+ logger.info("Cancelled.");
1664
+ return;
1665
+ }
1666
+ }
1667
+ const removed = await removeSkillFiles(resources);
1668
+ logger.debug(`Removed ${removed.length} files`);
1669
+ const skillsRoot = getSkillsSourceDir(projectRoot);
1670
+ try {
1671
+ await fs9.rm(skillsRoot, { recursive: true, force: true });
1672
+ logger.debug(`Removed source dir ${skillsRoot}`);
1673
+ } catch (err) {
1674
+ logger.warn(
1675
+ `Failed to remove ${skillsRoot}: ${err.message}`
1676
+ );
1677
+ }
1678
+ if (installedManifest && pkg) {
1679
+ installedManifest.installed = installedManifest.installed.filter(
1680
+ (p) => p.package !== SKILLS_PACKAGE3
1681
+ );
1682
+ await writeInstalledManifest(projectRoot, installedManifest);
1683
+ }
1684
+ delete config.packages.skills;
1685
+ await writeProjectConfig(projectRoot, config);
1686
+ logger.success(`Uninstalled ${SKILLS_PACKAGE3}`);
1687
+ logger.info(` Removed: ${removed.length} files`);
1688
+ logger.info(` Source: ${path12.relative(projectRoot, skillsRoot)} (cleaned)`);
1689
+ } catch (err) {
1690
+ logger.error(`Failed to uninstall: ${err.message}`);
1691
+ logger.debug(err.stack ?? "");
1692
+ process.exitCode = 1;
1693
+ }
1694
+ });
1695
+
1696
+ // src/commands/skills/sync.ts
1697
+ import { Command as Command11 } from "commander";
1698
+
1699
+ // src/core/skills-sync.ts
1700
+ import * as fs10 from "fs/promises";
1701
+ var SKILLS_PACKAGE_DEFAULT = "@teamix-evo/skills";
1702
+ async function runSkillsSync(options) {
1703
+ const { projectRoot, names } = options;
1704
+ const lock = await readSkillsLock(projectRoot);
1705
+ if (!lock || Object.keys(lock.skills).length === 0) {
1706
+ return {
1707
+ status: "no-skills",
1708
+ syncedSkillIds: [],
1709
+ fileCount: 0,
1710
+ resources: [],
1711
+ missingSourceIds: []
1712
+ };
1713
+ }
1714
+ const skillIds = Object.keys(lock.skills);
1715
+ const targets = names ? skillIds.filter((id) => names.includes(id)) : skillIds;
1716
+ const allResources = [];
1717
+ const synced = [];
1718
+ const missing = [];
1719
+ for (const skillId of targets) {
1720
+ const lockEntry = lock.skills[skillId];
1721
+ if (!lockEntry) continue;
1722
+ const sourceDir = getSkillsSourceDir(projectRoot, skillId);
1723
+ if (!await dirExists(sourceDir)) {
1724
+ logger.warn(`Skill "${skillId}" has no source at ${sourceDir}; skipped.`);
1725
+ missing.push(skillId);
1726
+ continue;
1727
+ }
1728
+ const ides = options.ides ?? lockEntry.mirroredTo;
1729
+ const scope = options.scope ?? lockEntry.scope;
1730
+ if (ides.length === 0) {
1731
+ logger.warn(`Skill "${skillId}" has no IDE mirror targets; skipped.`);
1732
+ continue;
1733
+ }
1734
+ const result = await syncSkillsToIdes({
1735
+ projectRoot,
1736
+ skills: [
1737
+ {
1738
+ id: skillId,
1739
+ name: skillId,
1740
+ updateStrategy: "regenerable"
1741
+ }
1742
+ ],
1743
+ ides,
1744
+ scope
1745
+ });
1746
+ allResources.push(...result.resources);
1747
+ synced.push(skillId);
1748
+ lock.skills[skillId] = {
1749
+ ...lockEntry,
1750
+ mirroredTo: ides,
1751
+ scope,
1752
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1753
+ };
1754
+ }
1755
+ await writeSkillsLock(projectRoot, lock);
1756
+ await refreshMirrorRecords(projectRoot, allResources);
1757
+ return {
1758
+ status: "synced",
1759
+ syncedSkillIds: synced,
1760
+ fileCount: allResources.length,
1761
+ resources: allResources,
1762
+ missingSourceIds: missing
1763
+ };
1764
+ }
1765
+ async function dirExists(p) {
1766
+ try {
1767
+ const stat4 = await fs10.stat(p);
1768
+ return stat4.isDirectory();
1769
+ } catch {
1770
+ return false;
1771
+ }
1772
+ }
1773
+ async function refreshMirrorRecords(projectRoot, newMirrorRecords) {
1774
+ const installed = await readInstalledManifest(projectRoot);
1775
+ if (!installed) return;
1776
+ const pkg = installed.installed.find((p) => p.package === SKILLS_PACKAGE_DEFAULT);
1777
+ if (!pkg) return;
1778
+ const sourceOnly = pkg.resources.filter((r) => r.ide === void 0);
1779
+ pkg.resources = [...sourceOnly, ...newMirrorRecords];
1780
+ pkg.installedAt = (/* @__PURE__ */ new Date()).toISOString();
1781
+ await writeInstalledManifest(projectRoot, installed);
1782
+ }
1783
+
1784
+ // src/commands/skills/sync.ts
1785
+ var syncCommand = new Command11("sync").description(
1786
+ "\u628A .teamix-evo/skills/ \u4E0B\u7684\u6E90\u91CD\u65B0\u955C\u50CF\u5230 IDE \u8DEF\u5F84\uFF08.qoder / .claude\uFF09"
1787
+ ).argument(
1788
+ "[names...]",
1789
+ "\u53EF\u9009\uFF1A\u4EC5\u540C\u6B65\u6307\u5B9A skill id\uFF1B\u7701\u7565\u5219\u540C\u6B65\u5168\u90E8\u5DF2\u8BB0\u5F55\u5728 lock \u5185\u7684 skill"
1790
+ ).option(
1791
+ "--ide <list>",
1792
+ "\u9017\u53F7\u5206\u9694\u7684 IDE \u5217\u8868\uFF08\u8986\u76D6 lock \u4E2D\u8BB0\u5F55\u7684 mirroredTo\uFF09"
1793
+ ).option(
1794
+ "--scope <scope>",
1795
+ "project | global\uFF08\u8986\u76D6 lock \u4E2D\u8BB0\u5F55\u7684 scope\uFF09"
1796
+ ).action(async (names, opts) => {
1797
+ try {
1798
+ const ide = detectIde();
1799
+ const projectRoot = ide.getProjectRoot();
1800
+ const ides = opts.ide ? parseIdeList2(opts.ide) : void 0;
1801
+ const scope = opts.scope ? parseScope2(opts.scope) : void 0;
1802
+ const result = await runSkillsSync({
1803
+ projectRoot,
1804
+ ides,
1805
+ scope,
1806
+ names: names.length > 0 ? names : void 0
1807
+ });
1808
+ if (result.status === "no-skills") {
1809
+ logger.info(
1810
+ "No skills recorded in .teamix-evo/skills/manifest.lock.json. Nothing to sync."
1811
+ );
1812
+ return;
1813
+ }
1814
+ logger.success(
1815
+ `Synced ${result.syncedSkillIds.length} skill(s) \u2192 ${result.fileCount} file(s)`
1816
+ );
1817
+ if (result.syncedSkillIds.length > 0) {
1818
+ logger.info(` Skills: ${result.syncedSkillIds.join(", ")}`);
1819
+ }
1820
+ if (result.missingSourceIds.length > 0) {
1821
+ logger.warn(
1822
+ ` Missing source: ${result.missingSourceIds.join(
1823
+ ", "
1824
+ )} (run "skills add <id>" to (re)install)`
1825
+ );
1826
+ }
1590
1827
  } catch (err) {
1591
- logger.error(`Failed to update skills: ${err.message}`);
1828
+ logger.error(`Failed to sync skills: ${err.message}`);
1592
1829
  logger.debug(err.stack ?? "");
1593
1830
  process.exitCode = 1;
1594
1831
  }
1595
1832
  });
1833
+ function parseIdeList2(input) {
1834
+ const parts = input.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
1835
+ const result = [];
1836
+ for (const p of parts) {
1837
+ if (p === "qoder" || p === "claude") {
1838
+ if (!result.includes(p)) result.push(p);
1839
+ } else {
1840
+ throw new Error(
1841
+ `Unknown IDE: "${p}". Expected one of: ${ALL_IDE_KINDS.join(", ")}.`
1842
+ );
1843
+ }
1844
+ }
1845
+ return result;
1846
+ }
1847
+ function parseScope2(input) {
1848
+ const v = input.toLowerCase();
1849
+ if (v === "project" || v === "global") return v;
1850
+ throw new Error(`Invalid --scope: "${input}". Expected project | global.`);
1851
+ }
1596
1852
 
1597
- // src/commands/skills/uninstall.ts
1598
- import { Command as Command9 } from "commander";
1599
- import * as prompts3 from "@clack/prompts";
1600
- var SKILLS_PACKAGE3 = "@teamix-evo/skills";
1601
- var uninstallCommand2 = new Command9("uninstall").description("\u5378\u8F7D\u5DF2\u5B89\u88C5\u7684 teamix-evo skills\uFF08\u5220\u9664\u6CE8\u5165\u5230 IDE \u7684\u6280\u80FD\u6587\u4EF6\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").action(async (opts) => {
1853
+ // src/commands/skills/doctor.ts
1854
+ import { Command as Command12 } from "commander";
1855
+
1856
+ // src/core/skills-doctor.ts
1857
+ import * as path13 from "path";
1858
+ import * as fs11 from "fs/promises";
1859
+ async function runSkillsDoctor(options) {
1860
+ const { projectRoot } = options;
1861
+ const lock = await readSkillsLock(projectRoot);
1862
+ if (!lock || Object.keys(lock.skills).length === 0) {
1863
+ return { status: "no-skills", findings: [] };
1864
+ }
1865
+ const findings = [];
1866
+ for (const [skillId, entry] of Object.entries(lock.skills)) {
1867
+ const sourceDir = getSkillsSourceDir(projectRoot, skillId);
1868
+ if (!await dirExists2(sourceDir)) {
1869
+ findings.push({
1870
+ kind: "missing-source",
1871
+ skillId,
1872
+ path: sourceDir,
1873
+ detail: 'Run "teamix-evo skills add" to reinstall.'
1874
+ });
1875
+ continue;
1876
+ }
1877
+ const sourceFiles = await walkDir(sourceDir);
1878
+ const sourceContents = /* @__PURE__ */ new Map();
1879
+ for (const f of sourceFiles) {
1880
+ const rel2 = path13.relative(sourceDir, f);
1881
+ sourceContents.set(rel2, await fs11.readFile(f, "utf-8"));
1882
+ }
1883
+ for (const ide of entry.mirroredTo) {
1884
+ const adapter = getAdapter(ide);
1885
+ const mirrorDir = adapter.getSkillTargetDir(
1886
+ skillId,
1887
+ entry.scope,
1888
+ projectRoot
1889
+ );
1890
+ if (!await dirExists2(mirrorDir)) {
1891
+ findings.push({
1892
+ kind: "missing-mirror",
1893
+ skillId,
1894
+ ide,
1895
+ scope: entry.scope,
1896
+ path: mirrorDir,
1897
+ detail: 'Run "teamix-evo skills sync" to re-mirror.'
1898
+ });
1899
+ continue;
1900
+ }
1901
+ for (const [rel2, sourceContent] of sourceContents.entries()) {
1902
+ const mirrorFile = path13.join(mirrorDir, rel2);
1903
+ if (!await fileExists(mirrorFile)) {
1904
+ findings.push({
1905
+ kind: "missing-mirror",
1906
+ skillId,
1907
+ ide,
1908
+ scope: entry.scope,
1909
+ path: mirrorFile,
1910
+ detail: 'Run "teamix-evo skills sync" to re-mirror.'
1911
+ });
1912
+ continue;
1913
+ }
1914
+ const mirrorContent = await fs11.readFile(mirrorFile, "utf-8");
1915
+ if (computeHash(mirrorContent) !== computeHash(sourceContent)) {
1916
+ findings.push({
1917
+ kind: "mirror-drift",
1918
+ skillId,
1919
+ ide,
1920
+ scope: entry.scope,
1921
+ path: mirrorFile,
1922
+ detail: 'Mirror differs from source. Re-run "teamix-evo skills sync" to overwrite.'
1923
+ });
1924
+ }
1925
+ }
1926
+ }
1927
+ }
1928
+ return {
1929
+ status: findings.length === 0 ? "clean" : "drift",
1930
+ findings
1931
+ };
1932
+ }
1933
+ async function dirExists2(p) {
1934
+ try {
1935
+ const stat4 = await fs11.stat(p);
1936
+ return stat4.isDirectory();
1937
+ } catch {
1938
+ return false;
1939
+ }
1940
+ }
1941
+
1942
+ // src/commands/skills/doctor.ts
1943
+ var doctorCommand = new Command12("doctor").description(
1944
+ "\u68C0\u67E5 .teamix-evo/skills/ \u6E90\u4E0E IDE \u955C\u50CF\u662F\u5426\u6F02\u79FB\uFF1B\u63D0\u793A\u5982\u4F55\u4FEE\u590D"
1945
+ ).action(async () => {
1602
1946
  try {
1603
1947
  const ide = detectIde();
1604
1948
  const projectRoot = ide.getProjectRoot();
1605
- const config = await readProjectConfig(projectRoot);
1606
- if (!config?.packages?.skills) {
1607
- logger.info("Skills are not installed. Nothing to do.");
1949
+ const result = await runSkillsDoctor({ projectRoot });
1950
+ if (result.status === "no-skills") {
1951
+ logger.info(
1952
+ 'No skills recorded. Run "teamix-evo skills add" first.'
1953
+ );
1608
1954
  return;
1609
1955
  }
1610
- const installedManifest = await readInstalledManifest(projectRoot);
1611
- const pkg = installedManifest?.installed.find(
1612
- (p) => p.package === SKILLS_PACKAGE3
1613
- );
1614
- const resources = pkg?.resources ?? [];
1615
- logger.info(
1616
- `Will remove ${resources.length} skill file(s) installed by ${SKILLS_PACKAGE3}.`
1617
- );
1618
- if (!opts.yes) {
1619
- const confirm4 = await prompts3.confirm({
1620
- message: "\u786E\u8BA4\u5378\u8F7D\uFF1F\u6B64\u64CD\u4F5C\u4F1A\u5220\u9664\u4E0A\u8FF0\u6587\u4EF6\u3002",
1621
- initialValue: false
1622
- });
1623
- if (prompts3.isCancel(confirm4) || !confirm4) {
1624
- logger.info("Cancelled.");
1625
- return;
1626
- }
1956
+ if (result.status === "clean") {
1957
+ logger.success("Skills are in sync. No drift detected.");
1958
+ return;
1627
1959
  }
1628
- const removed = await removeSkillFiles(resources);
1629
- logger.debug(`Removed ${removed.length} files`);
1630
- if (installedManifest && pkg) {
1631
- installedManifest.installed = installedManifest.installed.filter(
1632
- (p) => p.package !== SKILLS_PACKAGE3
1633
- );
1634
- await writeInstalledManifest(projectRoot, installedManifest);
1960
+ logger.warn(
1961
+ `Found ${result.findings.length} drift issue(s). Run "teamix-evo skills sync" to repair mirrors.`
1962
+ );
1963
+ for (const f of result.findings) {
1964
+ const idePart = f.ide ? ` [${f.ide}]` : "";
1965
+ logger.info(` - ${f.kind}${idePart}: ${f.skillId}`);
1966
+ logger.info(` ${f.path}`);
1967
+ if (f.detail) logger.info(` ${f.detail}`);
1635
1968
  }
1636
- delete config.packages.skills;
1637
- await writeProjectConfig(projectRoot, config);
1638
- logger.success(`Uninstalled ${SKILLS_PACKAGE3}`);
1639
- logger.info(` Removed: ${removed.length} files`);
1969
+ process.exitCode = 1;
1640
1970
  } catch (err) {
1641
- logger.error(`Failed to uninstall: ${err.message}`);
1971
+ logger.error(`Failed to run doctor: ${err.message}`);
1642
1972
  logger.debug(err.stack ?? "");
1643
1973
  process.exitCode = 1;
1644
1974
  }
1645
1975
  });
1646
1976
 
1647
1977
  // src/commands/skills/index.ts
1648
- var skillsCommand = new Command10("skills").description(
1649
- "\u7BA1\u7406 teamix-evo skills\uFF08\u5411 AI IDE \u6CE8\u5165\u6280\u80FD\uFF09"
1978
+ var skillsCommand = new Command13("skills").description(
1979
+ "\u7BA1\u7406 teamix-evo skills\uFF08\u5411 AI IDE \u6CE8\u5165\u6280\u80FD\uFF1Bsource-mirror \u6A21\u578B\u89C1 ADR 0013\uFF09"
1650
1980
  );
1651
1981
  skillsCommand.addCommand(addCommand);
1652
1982
  skillsCommand.addCommand(listCommand2);
1653
1983
  skillsCommand.addCommand(updateCommand2);
1984
+ skillsCommand.addCommand(syncCommand);
1985
+ skillsCommand.addCommand(doctorCommand);
1654
1986
  skillsCommand.addCommand(uninstallCommand2);
1655
1987
 
1656
1988
  // src/commands/ui/index.ts
1657
- import { Command as Command14 } from "commander";
1989
+ import { Command as Command17 } from "commander";
1658
1990
 
1659
1991
  // src/commands/ui/init.ts
1660
- import { Command as Command11 } from "commander";
1992
+ import { Command as Command14 } from "commander";
1661
1993
  import * as prompts4 from "@clack/prompts";
1662
1994
 
1663
1995
  // src/core/ui-init.ts
@@ -1665,7 +1997,9 @@ var DEFAULT_UI_ALIASES = {
1665
1997
  components: "src/components/ui",
1666
1998
  hooks: "src/hooks",
1667
1999
  utils: "src/lib/utils",
1668
- lib: "src/lib"
2000
+ lib: "src/lib",
2001
+ business: "src/components/business",
2002
+ templates: "src/templates"
1669
2003
  };
1670
2004
  var DEFAULT_UI_ICON_LIBRARY = "lucide";
1671
2005
  async function runUiInit(options) {
@@ -1680,7 +2014,9 @@ async function runUiInit(options) {
1680
2014
  components: options.aliases?.components ?? DEFAULT_UI_ALIASES.components,
1681
2015
  hooks: options.aliases?.hooks ?? DEFAULT_UI_ALIASES.hooks,
1682
2016
  utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
1683
- lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib
2017
+ lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
2018
+ business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
2019
+ templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
1684
2020
  };
1685
2021
  const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
1686
2022
  const tsx = options.tsx ?? true;
@@ -1710,7 +2046,7 @@ async function runUiInit(options) {
1710
2046
  }
1711
2047
 
1712
2048
  // src/commands/ui/init.ts
1713
- var initCommand2 = new Command11("init").description(
2049
+ var initCommand2 = new Command14("init").description(
1714
2050
  "\u521D\u59CB\u5316 teamix-evo ui \u914D\u7F6E\uFF08\u8BE2\u95EE aliases / iconLibrary / tsx / rsc\uFF09"
1715
2051
  ).option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").option(
1716
2052
  "--components <path>",
@@ -1757,7 +2093,9 @@ async function resolveConfig(opts) {
1757
2093
  components: opts.components ?? DEFAULT_UI_ALIASES.components,
1758
2094
  hooks: opts.hooks ?? DEFAULT_UI_ALIASES.hooks,
1759
2095
  utils: opts.utils ?? DEFAULT_UI_ALIASES.utils,
1760
- lib: opts.lib ?? DEFAULT_UI_ALIASES.lib
2096
+ lib: opts.lib ?? DEFAULT_UI_ALIASES.lib,
2097
+ business: DEFAULT_UI_ALIASES.business,
2098
+ templates: DEFAULT_UI_ALIASES.templates
1761
2099
  },
1762
2100
  iconLibrary: opts.iconLibrary ?? "lucide",
1763
2101
  tsx: opts.tsx ?? true,
@@ -1800,7 +2138,14 @@ async function resolveConfig(opts) {
1800
2138
  });
1801
2139
  if (prompts4.isCancel(rscAns)) throw new Error("Cancelled by user.");
1802
2140
  return {
1803
- aliases: { components, hooks, utils, lib },
2141
+ aliases: {
2142
+ components,
2143
+ hooks,
2144
+ utils,
2145
+ lib,
2146
+ business: DEFAULT_UI_ALIASES.business,
2147
+ templates: DEFAULT_UI_ALIASES.templates
2148
+ },
1804
2149
  iconLibrary,
1805
2150
  tsx: tsxAns,
1806
2151
  rsc: rscAns
@@ -1808,26 +2153,26 @@ async function resolveConfig(opts) {
1808
2153
  }
1809
2154
 
1810
2155
  // src/commands/ui/add.ts
1811
- import { Command as Command12 } from "commander";
2156
+ import { Command as Command15 } from "commander";
1812
2157
 
1813
2158
  // src/core/ui-client.ts
1814
- import * as path12 from "path";
1815
- import * as fs10 from "fs/promises";
2159
+ import * as path14 from "path";
2160
+ import * as fs12 from "fs/promises";
1816
2161
  import { createRequire as createRequire3 } from "module";
1817
2162
  import { loadUiPackageManifest } from "@teamix-evo/registry";
1818
2163
  var require4 = createRequire3(import.meta.url);
1819
- function resolvePackageRoot3(packageName) {
2164
+ function resolvePackageRoot2(packageName) {
1820
2165
  const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
1821
- return path12.dirname(pkgJsonPath);
2166
+ return path14.dirname(pkgJsonPath);
1822
2167
  }
1823
2168
  async function loadUiData(packageName) {
1824
- const packageRoot = resolvePackageRoot3(packageName);
2169
+ const packageRoot = resolvePackageRoot2(packageName);
1825
2170
  logger.debug(`Resolved ui package root: ${packageRoot}`);
1826
2171
  const manifest = await loadUiPackageManifest(packageRoot);
1827
2172
  let data = {};
1828
- const dataPath = path12.join(packageRoot, "_data.json");
2173
+ const dataPath = path14.join(packageRoot, "_data.json");
1829
2174
  try {
1830
- const raw = await fs10.readFile(dataPath, "utf-8");
2175
+ const raw = await fs12.readFile(dataPath, "utf-8");
1831
2176
  data = JSON.parse(raw);
1832
2177
  } catch (err) {
1833
2178
  if (err.code !== "ENOENT") {
@@ -1839,8 +2184,8 @@ async function loadUiData(packageName) {
1839
2184
  }
1840
2185
 
1841
2186
  // src/core/ui-installer.ts
1842
- import * as path13 from "path";
1843
- import * as fs11 from "fs/promises";
2187
+ import * as path15 from "path";
2188
+ import * as fs13 from "fs/promises";
1844
2189
  import { resolveUiEntryOrder } from "@teamix-evo/registry";
1845
2190
 
1846
2191
  // src/utils/transform-imports.ts
@@ -1858,10 +2203,19 @@ function rewriteImports(source, aliases) {
1858
2203
  if (!aliasKey) return full;
1859
2204
  const alias = aliases[aliasKey];
1860
2205
  const normalized = aliasToImportPath(alias);
1861
- return `${quote}${normalized}${rest ?? ""}${quote}`;
2206
+ const flatRest = flattenRestPath(rest);
2207
+ return `${quote}${normalized}${flatRest}${quote}`;
1862
2208
  }
1863
2209
  );
1864
2210
  }
2211
+ function flattenRestPath(rest) {
2212
+ if (!rest) return "";
2213
+ const segments = rest.split("/");
2214
+ if (segments.length === 3) {
2215
+ return `/${segments[2]}`;
2216
+ }
2217
+ return rest;
2218
+ }
1865
2219
  function aliasToImportPath(alias) {
1866
2220
  const trimmed = alias.replace(/^\.\//, "").replace(/\/$/, "");
1867
2221
  if (trimmed.startsWith("src/")) {
@@ -1904,8 +2258,8 @@ async function installUiEntries(options) {
1904
2258
  skipped++;
1905
2259
  continue;
1906
2260
  }
1907
- const sourceAbs = path13.resolve(packageRoot, file.source);
1908
- const raw = await fs11.readFile(sourceAbs, "utf-8");
2261
+ const sourceAbs = path15.resolve(packageRoot, file.source);
2262
+ const raw = await fs13.readFile(sourceAbs, "utf-8");
1909
2263
  const transformed = rewriteImports(raw, aliases);
1910
2264
  await writeFileSafe(targetAbs, transformed);
1911
2265
  written++;
@@ -1918,9 +2272,9 @@ async function installUiEntries(options) {
1918
2272
  });
1919
2273
  }
1920
2274
  if (entry.meta) {
1921
- const metaSourceAbs = path13.resolve(packageRoot, entry.meta);
1922
- const metaContent = await fs11.readFile(metaSourceAbs, "utf-8");
1923
- const metaTargetAbs = path13.join(
2275
+ const metaSourceAbs = path15.resolve(packageRoot, entry.meta);
2276
+ const metaContent = await fs13.readFile(metaSourceAbs, "utf-8");
2277
+ const metaTargetAbs = path15.join(
1924
2278
  projectRoot,
1925
2279
  DESIGN_COMPONENTS_DIR,
1926
2280
  `${entry.id}.meta.md`
@@ -1952,10 +2306,10 @@ function resolveTargetPath(projectRoot, aliases, entry, file) {
1952
2306
  `Entry "${entry.id}" requires alias "${file.targetAlias}" but it is not configured.`
1953
2307
  );
1954
2308
  }
1955
- return path13.join(projectRoot, aliasDir, file.targetName);
2309
+ return path15.join(projectRoot, aliasDir, file.targetName);
1956
2310
  }
1957
2311
  function rel(projectRoot, abs) {
1958
- return path13.relative(projectRoot, abs);
2312
+ return path15.relative(projectRoot, abs);
1959
2313
  }
1960
2314
 
1961
2315
  // src/core/ui-add.ts
@@ -2030,7 +2384,7 @@ function mergeResources(prior, next) {
2030
2384
  }
2031
2385
 
2032
2386
  // src/commands/ui/add.ts
2033
- var addCommand2 = new Command12("add").description(
2387
+ var addCommand2 = new Command15("add").description(
2034
2388
  "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A ui entry\uFF08\u6309 id\uFF0C\u81EA\u52A8\u5C55\u5F00 registryDependencies\uFF09"
2035
2389
  ).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) => {
2036
2390
  try {
@@ -2074,7 +2428,7 @@ var addCommand2 = new Command12("add").description(
2074
2428
  });
2075
2429
 
2076
2430
  // src/commands/ui/list.ts
2077
- import { Command as Command13 } from "commander";
2431
+ import { Command as Command16 } from "commander";
2078
2432
 
2079
2433
  // src/core/ui-list.ts
2080
2434
  var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
@@ -2106,7 +2460,7 @@ async function runUiList(options) {
2106
2460
  }
2107
2461
 
2108
2462
  // src/commands/ui/list.ts
2109
- var listCommand3 = new Command13("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) => {
2463
+ 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) => {
2110
2464
  try {
2111
2465
  const ide = detectIde();
2112
2466
  const projectRoot = ide.getProjectRoot();
@@ -2152,20 +2506,301 @@ var listCommand3 = new Command13("list").description("\u5217\u51FA @teamix-evo/u
2152
2506
  });
2153
2507
 
2154
2508
  // src/commands/ui/index.ts
2155
- var uiCommand = new Command14("ui").description(
2509
+ var uiCommand = new Command17("ui").description(
2156
2510
  "\u7BA1\u7406 teamix-evo ui \u7EC4\u4EF6\uFF08\u6E90\u7801\u6CE8\u5165\u5F0F\u5B89\u88C5\uFF0Cshadcn \u98CE\u683C\uFF09"
2157
2511
  );
2158
2512
  uiCommand.addCommand(initCommand2);
2159
2513
  uiCommand.addCommand(addCommand2);
2160
2514
  uiCommand.addCommand(listCommand3);
2161
2515
 
2162
- // src/index.ts
2516
+ // src/commands/biz-ui/index.ts
2517
+ import { Command as Command20 } from "commander";
2518
+
2519
+ // src/commands/biz-ui/add.ts
2520
+ import { Command as Command18 } from "commander";
2521
+
2522
+ // src/core/variant-ui-add.ts
2523
+ import * as path16 from "path";
2524
+ import { createRequire as createRequire4 } from "module";
2525
+ import {
2526
+ loadVariantUiPackageCatalog,
2527
+ loadVariantUiPackageManifest
2528
+ } from "@teamix-evo/registry";
2163
2529
  var require5 = createRequire4(import.meta.url);
2164
- var { version } = require5("../package.json");
2165
- var program = new Command15();
2530
+ function resolvePackageRoot3(packageName) {
2531
+ const pkgJsonPath = require5.resolve(`${packageName}/package.json`);
2532
+ return path16.dirname(pkgJsonPath);
2533
+ }
2534
+ async function runVariantUiAdd(packageName, options) {
2535
+ const { projectRoot, variant, ids, overwrite } = options;
2536
+ const fullPackageName = options.packageName ?? `@teamix-evo/${packageName}`;
2537
+ if (ids.length === 0) {
2538
+ throw new Error("At least one entry id must be provided.");
2539
+ }
2540
+ const config = await readProjectConfig(projectRoot);
2541
+ const uiCfg = config?.packages?.ui;
2542
+ if (!config || !uiCfg?.aliases) {
2543
+ throw new Error(
2544
+ `UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business / templates).`
2545
+ );
2546
+ }
2547
+ const packageRoot = options.packageRoot ?? resolvePackageRoot3(fullPackageName);
2548
+ const catalog = await loadVariantUiPackageCatalog(packageRoot);
2549
+ if (!catalog.variants.some((v) => v.name === variant)) {
2550
+ const known = catalog.variants.map((v) => v.name).join(", ");
2551
+ throw new Error(
2552
+ `Variant "${variant}" not found in ${fullPackageName}. Known variants: ${known}. Hint: \`teamix-evo ${packageName} list-variants\` shows all.`
2553
+ );
2554
+ }
2555
+ const variantDir = path16.join(packageRoot, "variants", variant);
2556
+ const variantManifest = await loadVariantUiPackageManifest(variantDir);
2557
+ const knownIds = new Set(variantManifest.entries.map((e) => e.id));
2558
+ const unknown = ids.filter((id) => !knownIds.has(id));
2559
+ if (unknown.length > 0) {
2560
+ throw new Error(
2561
+ `Unknown entry id(s) in ${packageName}#${variant}: ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ${packageName} list --variant ${variant}\` to see options.`
2562
+ );
2563
+ }
2564
+ const adaptedManifest = {
2565
+ schemaVersion: 1,
2566
+ package: "ui",
2567
+ version: variantManifest.version,
2568
+ engines: variantManifest.engines,
2569
+ entries: variantManifest.entries
2570
+ };
2571
+ const result = await installUiEntries({
2572
+ projectRoot,
2573
+ manifest: adaptedManifest,
2574
+ packageRoot: variantDir,
2575
+ // sources resolved relative to variant dir
2576
+ aliases: uiCfg.aliases,
2577
+ requested: ids,
2578
+ skipExisting: !overwrite
2579
+ });
2580
+ const installed = await readInstalledManifest(
2581
+ projectRoot
2582
+ ) ?? { schemaVersion: 1, installed: [] };
2583
+ const idx = installed.installed.findIndex(
2584
+ (p) => p.package === fullPackageName && p.variant === variant
2585
+ );
2586
+ const prior = idx >= 0 ? installed.installed[idx] : null;
2587
+ const mergedResources = mergeResources2(
2588
+ prior?.resources ?? [],
2589
+ result.resources
2590
+ );
2591
+ const entry = {
2592
+ package: fullPackageName,
2593
+ variant,
2594
+ version: variantManifest.version,
2595
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
2596
+ resources: mergedResources
2597
+ };
2598
+ if (idx >= 0) installed.installed[idx] = entry;
2599
+ else installed.installed.push(entry);
2600
+ await writeInstalledManifest(projectRoot, installed);
2601
+ return {
2602
+ packageName: fullPackageName,
2603
+ variant,
2604
+ orderedIds: result.orderedIds,
2605
+ written: result.written,
2606
+ skipped: result.skipped,
2607
+ metaFiles: result.metaFiles,
2608
+ npmDependencies: result.npmDependencies,
2609
+ resources: result.resources
2610
+ };
2611
+ }
2612
+ function mergeResources2(prior, next) {
2613
+ const merged = /* @__PURE__ */ new Map();
2614
+ for (const r of prior) merged.set(r.id, r);
2615
+ for (const r of next) merged.set(r.id, r);
2616
+ return Array.from(merged.values());
2617
+ }
2618
+ async function runBizUiAdd(options) {
2619
+ return runVariantUiAdd("biz-ui", options);
2620
+ }
2621
+ async function runTemplatesAdd(options) {
2622
+ return runVariantUiAdd("templates", options);
2623
+ }
2624
+ async function listVariantUi(packageName, packageRoot) {
2625
+ const fullPackageName = `@teamix-evo/${packageName}`;
2626
+ const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
2627
+ const catalog = await loadVariantUiPackageCatalog(root);
2628
+ return {
2629
+ packageName: fullPackageName,
2630
+ variants: catalog.variants.map((v) => ({
2631
+ name: v.name,
2632
+ displayName: v.displayName,
2633
+ version: v.version,
2634
+ description: v.description
2635
+ }))
2636
+ };
2637
+ }
2638
+ async function listBizUiVariants(packageRoot) {
2639
+ return listVariantUi("biz-ui", packageRoot);
2640
+ }
2641
+ async function listTemplatesVariants(packageRoot) {
2642
+ return listVariantUi("templates", packageRoot);
2643
+ }
2644
+
2645
+ // src/commands/biz-ui/add.ts
2646
+ var addCommand3 = new Command18("add").description(
2647
+ "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A\u4E1A\u52A1 UI \u7EC4\u4EF6(\u6309 id,\u81EA\u52A8\u5C55\u5F00 ui \u5305\u7684 registryDependencies)"
2648
+ ).argument("<ids...>", '\u7EC4\u4EF6 id \u5217\u8868,\u5982 "tenant-switcher" "org-picker"').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(
2649
+ async (ids, opts) => {
2650
+ try {
2651
+ if (!opts.variant) {
2652
+ throw new Error(
2653
+ "--variant <name> is required. Run `teamix-evo biz-ui list-variants` to see available variants."
2654
+ );
2655
+ }
2656
+ const ide = detectIde();
2657
+ const projectRoot = ide.getProjectRoot();
2658
+ logger.info(
2659
+ `Installing biz-ui entries from variant "${opts.variant}": ${ids.join(", ")}`
2660
+ );
2661
+ const result = await runBizUiAdd({
2662
+ projectRoot,
2663
+ variant: opts.variant,
2664
+ ids,
2665
+ overwrite: opts.overwrite
2666
+ });
2667
+ logger.success(
2668
+ `biz-ui add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
2669
+ );
2670
+ logger.info("");
2671
+ logger.info(`Variant: ${result.variant}`);
2672
+ logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
2673
+ const npmDeps = Object.entries(result.npmDependencies);
2674
+ if (npmDeps.length > 0) {
2675
+ logger.info("");
2676
+ logger.info("Install npm dependencies in your project:");
2677
+ const installCmd = npmDeps.map(([name, range]) => `${name}@${range}`).join(" ");
2678
+ logger.info(` pnpm add ${installCmd}`);
2679
+ }
2680
+ } catch (err) {
2681
+ logger.error(`Failed: ${err.message}`);
2682
+ logger.debug(err.stack ?? "");
2683
+ process.exitCode = 1;
2684
+ }
2685
+ }
2686
+ );
2687
+
2688
+ // src/commands/biz-ui/list-variants.ts
2689
+ import { Command as Command19 } from "commander";
2690
+ 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 () => {
2691
+ try {
2692
+ const result = await listBizUiVariants();
2693
+ logger.info(`Available biz-ui variants in ${result.packageName}:`);
2694
+ logger.info("");
2695
+ if (result.variants.length === 0) {
2696
+ logger.info(" (no variants yet)");
2697
+ return;
2698
+ }
2699
+ for (const v of result.variants) {
2700
+ logger.info(` ${v.name} (${v.displayName}) \u2014 v${v.version}`);
2701
+ if (v.description) logger.info(` ${v.description}`);
2702
+ logger.info("");
2703
+ }
2704
+ logger.info("Install from a variant: teamix-evo biz-ui add <id> --variant <name>");
2705
+ } catch (err) {
2706
+ logger.error(`Failed: ${err.message}`);
2707
+ process.exitCode = 1;
2708
+ }
2709
+ });
2710
+
2711
+ // src/commands/biz-ui/index.ts
2712
+ var bizUiCommand = new Command20("biz-ui").description(
2713
+ "\u7BA1\u7406\u4E1A\u52A1 UI \u7EC4\u4EF6(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / templates \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
2714
+ );
2715
+ bizUiCommand.addCommand(addCommand3);
2716
+ bizUiCommand.addCommand(listVariantsCommand2);
2717
+
2718
+ // src/commands/templates/index.ts
2719
+ import { Command as Command23 } from "commander";
2720
+
2721
+ // src/commands/templates/add.ts
2722
+ import { Command as Command21 } from "commander";
2723
+ var addCommand4 = new Command21("add").description(
2724
+ "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A\u9875\u9762\u6A21\u677F(\u6309 id,\u81EA\u52A8\u5C55\u5F00 ui \u5305\u7684 registryDependencies)"
2725
+ ).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(
2726
+ async (ids, opts) => {
2727
+ try {
2728
+ if (!opts.variant) {
2729
+ throw new Error(
2730
+ "--variant <name> is required. Run `teamix-evo templates list-variants` to see available variants."
2731
+ );
2732
+ }
2733
+ const ide = detectIde();
2734
+ const projectRoot = ide.getProjectRoot();
2735
+ logger.info(
2736
+ `Installing templates from variant "${opts.variant}": ${ids.join(", ")}`
2737
+ );
2738
+ const result = await runTemplatesAdd({
2739
+ projectRoot,
2740
+ variant: opts.variant,
2741
+ ids,
2742
+ overwrite: opts.overwrite
2743
+ });
2744
+ logger.success(
2745
+ `templates add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
2746
+ );
2747
+ logger.info("");
2748
+ logger.info(`Variant: ${result.variant}`);
2749
+ logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
2750
+ const npmDeps = Object.entries(result.npmDependencies);
2751
+ if (npmDeps.length > 0) {
2752
+ logger.info("");
2753
+ logger.info("Install npm dependencies in your project:");
2754
+ const installCmd = npmDeps.map(([name, range]) => `${name}@${range}`).join(" ");
2755
+ logger.info(` pnpm add ${installCmd}`);
2756
+ }
2757
+ } catch (err) {
2758
+ logger.error(`Failed: ${err.message}`);
2759
+ logger.debug(err.stack ?? "");
2760
+ process.exitCode = 1;
2761
+ }
2762
+ }
2763
+ );
2764
+
2765
+ // src/commands/templates/list-variants.ts
2766
+ import { Command as Command22 } from "commander";
2767
+ 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 () => {
2768
+ try {
2769
+ const result = await listTemplatesVariants();
2770
+ logger.info(`Available templates variants in ${result.packageName}:`);
2771
+ logger.info("");
2772
+ if (result.variants.length === 0) {
2773
+ logger.info(" (no variants yet)");
2774
+ return;
2775
+ }
2776
+ for (const v of result.variants) {
2777
+ logger.info(` ${v.name} (${v.displayName}) \u2014 v${v.version}`);
2778
+ if (v.description) logger.info(` ${v.description}`);
2779
+ logger.info("");
2780
+ }
2781
+ logger.info("Install from a variant: teamix-evo templates add <id> --variant <name>");
2782
+ } catch (err) {
2783
+ logger.error(`Failed: ${err.message}`);
2784
+ process.exitCode = 1;
2785
+ }
2786
+ });
2787
+
2788
+ // src/commands/templates/index.ts
2789
+ var templatesCommand = new Command23("templates").description(
2790
+ "\u7BA1\u7406\u9875\u9762\u6A21\u677F(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / biz-ui \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
2791
+ );
2792
+ templatesCommand.addCommand(addCommand4);
2793
+ templatesCommand.addCommand(listVariantsCommand3);
2794
+
2795
+ // src/index.ts
2796
+ var require6 = createRequire5(import.meta.url);
2797
+ var { version } = require6("../package.json");
2798
+ var program = new Command24();
2166
2799
  program.name("teamix-evo").description("Where ideas evolve. \u2014 AI Coding \u5957\u4EF6").version(version);
2167
2800
  program.addCommand(designCommand);
2168
2801
  program.addCommand(skillsCommand);
2169
2802
  program.addCommand(uiCommand);
2803
+ program.addCommand(bizUiCommand);
2804
+ program.addCommand(templatesCommand);
2170
2805
  program.parse();
2171
2806
  //# sourceMappingURL=index.js.map