teamix-evo 0.1.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.
@@ -0,0 +1,1521 @@
1
+ // src/core/design-init.ts
2
+ import * as path9 from "path";
3
+ import * as fs6 from "fs/promises";
4
+ import { createRequire as createRequire2 } from "module";
5
+ import {
6
+ loadDesignPack,
7
+ loadDesignPackageManifest,
8
+ mergeDefaultAndVariant
9
+ } from "@teamix-evo/registry";
10
+
11
+ // src/utils/fs.ts
12
+ import * as fs from "fs/promises";
13
+ import * as path from "path";
14
+
15
+ // src/utils/logger.ts
16
+ import { red, yellow, cyan, green, gray } from "kolorist";
17
+ var isDebug = process.env.TEAMIX_DEBUG === "1";
18
+ var logger = {
19
+ info(msg) {
20
+ console.log(cyan("\u2139"), msg);
21
+ },
22
+ warn(msg) {
23
+ console.warn(yellow("\u26A0"), msg);
24
+ },
25
+ error(msg) {
26
+ console.error(red("\u2716"), msg);
27
+ },
28
+ success(msg) {
29
+ console.log(green("\u2714"), msg);
30
+ },
31
+ debug(msg) {
32
+ if (isDebug) {
33
+ console.log(gray("\u22A1"), gray(msg));
34
+ }
35
+ }
36
+ };
37
+
38
+ // src/utils/fs.ts
39
+ async function ensureDir(dir) {
40
+ await fs.mkdir(dir, { recursive: true });
41
+ }
42
+ async function writeFileSafe(filePath, content) {
43
+ const dir = path.dirname(filePath);
44
+ await ensureDir(dir);
45
+ const tmp = filePath + ".tmp";
46
+ await fs.writeFile(tmp, content, "utf-8");
47
+ await fs.rename(tmp, filePath);
48
+ }
49
+ async function readFileOrNull(filePath) {
50
+ try {
51
+ return await fs.readFile(filePath, "utf-8");
52
+ } catch (err) {
53
+ if (err.code === "ENOENT") {
54
+ return null;
55
+ }
56
+ throw err;
57
+ }
58
+ }
59
+ async function backupFile(filePath, projectRoot) {
60
+ const content = await readFileOrNull(filePath);
61
+ if (content === null) {
62
+ logger.debug(`Skip backup: ${filePath} does not exist`);
63
+ return;
64
+ }
65
+ const rel2 = path.relative(projectRoot, filePath);
66
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
67
+ const backupPath = path.join(
68
+ projectRoot,
69
+ ".teamix-evo",
70
+ ".backups",
71
+ `${rel2}.${timestamp}.bak`
72
+ );
73
+ await ensureDir(path.dirname(backupPath));
74
+ await fs.writeFile(backupPath, content, "utf-8");
75
+ logger.debug(`Backed up ${rel2} \u2192 ${path.relative(projectRoot, backupPath)}`);
76
+ }
77
+ async function fileExists(filePath) {
78
+ try {
79
+ await fs.access(filePath);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ // src/utils/hash.ts
87
+ import { createHash } from "crypto";
88
+ function computeHash(content) {
89
+ const hash = createHash("sha256").update(content, "utf-8").digest("hex");
90
+ return `sha256:${hash}`;
91
+ }
92
+
93
+ // src/core/design-pack-classify.ts
94
+ import * as path2 from "path";
95
+ var TEAMIX_DIR = ".teamix-evo/design";
96
+ var TOKENS_DIR = ".teamix-evo/tokens";
97
+ var TOKENS_PACK_PREFIX = "foundations/tokens/";
98
+ var ROOT_MANAGED_FILES = {
99
+ "DESIGN.md": { target: "DESIGN.md", managedRegions: ["core"] },
100
+ "AGENTS.md": { target: "AGENTS.md", managedRegions: ["teamix-evo"] },
101
+ "CLAUDE.md": { target: "CLAUDE.md", managedRegions: ["teamix-evo"] }
102
+ };
103
+ var FROZEN_FILES = /* @__PURE__ */ new Set([
104
+ "foundations/tokens/tokens.overrides.css"
105
+ ]);
106
+ function classifyPackFile(relPath) {
107
+ if (path2.basename(relPath) === "README.md") {
108
+ return null;
109
+ }
110
+ const rootManaged = ROOT_MANAGED_FILES[relPath];
111
+ if (rootManaged) {
112
+ return {
113
+ target: rootManaged.target,
114
+ strategy: "managed",
115
+ managedRegions: rootManaged.managedRegions,
116
+ isFrozen: false
117
+ };
118
+ }
119
+ if (relPath.startsWith(TOKENS_PACK_PREFIX)) {
120
+ const rel2 = relPath.slice(TOKENS_PACK_PREFIX.length);
121
+ const target = path2.posix.join(TOKENS_DIR, rel2);
122
+ if (FROZEN_FILES.has(relPath)) {
123
+ return { target, strategy: "frozen", isFrozen: true };
124
+ }
125
+ return { target, strategy: "regenerable", isFrozen: false };
126
+ }
127
+ if (FROZEN_FILES.has(relPath)) {
128
+ return {
129
+ target: path2.posix.join(TEAMIX_DIR, relPath),
130
+ strategy: "frozen",
131
+ isFrozen: true
132
+ };
133
+ }
134
+ return {
135
+ target: path2.posix.join(TEAMIX_DIR, relPath),
136
+ strategy: "regenerable",
137
+ isFrozen: false
138
+ };
139
+ }
140
+
141
+ // src/core/state.ts
142
+ import * as path3 from "path";
143
+ import {
144
+ validateConfig,
145
+ validateInstalled,
146
+ validateSkillsLock,
147
+ DesignPackLockSchema
148
+ } from "@teamix-evo/registry";
149
+ var TEAMIX_DIR2 = ".teamix-evo";
150
+ var CONFIG_FILE = "config.json";
151
+ var MANIFEST_FILE = "manifest.json";
152
+ var DESIGN_DIR = "design";
153
+ var DESIGN_LOCK_FILE = "pack.lock.json";
154
+ var SKILLS_DIR = "skills";
155
+ var SKILLS_LOCK_FILE = "manifest.lock.json";
156
+ function getTeamixDir(projectRoot) {
157
+ return path3.join(projectRoot, TEAMIX_DIR2);
158
+ }
159
+ async function ensureTeamixDir(projectRoot) {
160
+ const dir = getTeamixDir(projectRoot);
161
+ await ensureDir(dir);
162
+ return dir;
163
+ }
164
+ async function readProjectConfig(projectRoot) {
165
+ const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
166
+ const raw = await readFileOrNull(configPath);
167
+ if (raw === null) return null;
168
+ try {
169
+ const data = JSON.parse(raw);
170
+ const result = validateConfig(data);
171
+ if (!result.success) {
172
+ logger.warn(`Invalid config.json: ${result.error}`);
173
+ return null;
174
+ }
175
+ return result.data;
176
+ } catch (err) {
177
+ logger.warn(`Failed to parse config.json: ${err.message}`);
178
+ return null;
179
+ }
180
+ }
181
+ async function writeProjectConfig(projectRoot, config) {
182
+ const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
183
+ await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
184
+ logger.debug(`Wrote config \u2192 ${configPath}`);
185
+ }
186
+ async function readInstalledManifest(projectRoot) {
187
+ const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
188
+ const raw = await readFileOrNull(manifestPath);
189
+ if (raw === null) return null;
190
+ try {
191
+ const data = JSON.parse(raw);
192
+ const result = validateInstalled(data);
193
+ if (!result.success) {
194
+ logger.warn(`Invalid manifest.json: ${result.error}`);
195
+ return null;
196
+ }
197
+ return result.data;
198
+ } catch (err) {
199
+ logger.warn(`Failed to parse manifest.json: ${err.message}`);
200
+ return null;
201
+ }
202
+ }
203
+ async function writeInstalledManifest(projectRoot, manifest) {
204
+ const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
205
+ await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
206
+ logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
207
+ }
208
+ async function readDesignPackLock(projectRoot) {
209
+ const lockPath = path3.join(
210
+ projectRoot,
211
+ TEAMIX_DIR2,
212
+ DESIGN_DIR,
213
+ DESIGN_LOCK_FILE
214
+ );
215
+ const raw = await readFileOrNull(lockPath);
216
+ if (raw === null) return null;
217
+ try {
218
+ const parsed = DesignPackLockSchema.safeParse(JSON.parse(raw));
219
+ if (!parsed.success) {
220
+ logger.warn(`Invalid design pack.lock.json: ${parsed.error.message}`);
221
+ return null;
222
+ }
223
+ return parsed.data;
224
+ } catch (err) {
225
+ logger.warn(
226
+ `Failed to parse design pack.lock.json: ${err.message}`
227
+ );
228
+ return null;
229
+ }
230
+ }
231
+ async function readDesignVariant(projectRoot) {
232
+ const lock = await readDesignPackLock(projectRoot);
233
+ return lock?.variant.name ?? null;
234
+ }
235
+ function getSkillsSourceDir(projectRoot, skillName) {
236
+ const base = path3.join(projectRoot, TEAMIX_DIR2, SKILLS_DIR);
237
+ return skillName ? path3.join(base, skillName) : base;
238
+ }
239
+ async function readSkillsLock(projectRoot) {
240
+ const lockPath = path3.join(
241
+ projectRoot,
242
+ TEAMIX_DIR2,
243
+ SKILLS_DIR,
244
+ SKILLS_LOCK_FILE
245
+ );
246
+ const raw = await readFileOrNull(lockPath);
247
+ if (raw === null) return null;
248
+ try {
249
+ const data = JSON.parse(raw);
250
+ const result = validateSkillsLock(data);
251
+ if (!result.success) {
252
+ logger.warn(`Invalid skills manifest.lock.json: ${result.error}`);
253
+ return null;
254
+ }
255
+ return result.data;
256
+ } catch (err) {
257
+ logger.warn(
258
+ `Failed to parse skills manifest.lock.json: ${err.message}`
259
+ );
260
+ return null;
261
+ }
262
+ }
263
+ async function writeSkillsLock(projectRoot, lock) {
264
+ const lockPath = path3.join(
265
+ projectRoot,
266
+ TEAMIX_DIR2,
267
+ SKILLS_DIR,
268
+ SKILLS_LOCK_FILE
269
+ );
270
+ await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
271
+ logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
272
+ }
273
+
274
+ // src/core/skills-client.ts
275
+ import * as path4 from "path";
276
+ import * as fs2 from "fs/promises";
277
+ import { createRequire } from "module";
278
+ import { loadSkillsPackageManifest } from "@teamix-evo/registry";
279
+ var require2 = createRequire(import.meta.url);
280
+ function resolvePackageRoot(packageName) {
281
+ const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
282
+ return path4.dirname(pkgJsonPath);
283
+ }
284
+ async function loadSkillsData(packageName) {
285
+ const packageRoot = resolvePackageRoot(packageName);
286
+ logger.debug(`Resolved skills package root: ${packageRoot}`);
287
+ const manifest = await loadSkillsPackageManifest(packageRoot);
288
+ let data = {};
289
+ const dataPath = path4.join(packageRoot, "_data.json");
290
+ try {
291
+ const raw = await fs2.readFile(dataPath, "utf-8");
292
+ data = JSON.parse(raw);
293
+ } catch (err) {
294
+ if (err.code !== "ENOENT") {
295
+ throw err;
296
+ }
297
+ logger.debug(`No _data.json found at ${dataPath}, using empty data`);
298
+ }
299
+ return { manifest, data, packageRoot };
300
+ }
301
+
302
+ // src/core/skills-installer.ts
303
+ import * as path8 from "path";
304
+ import * as fs5 from "fs/promises";
305
+ import { replaceManagedRegion } from "@teamix-evo/registry";
306
+
307
+ // src/ide/QoderAdapter.ts
308
+ import * as os from "os";
309
+ import * as path5 from "path";
310
+ var QoderAdapter = class {
311
+ kind = "qoder";
312
+ name = "qoder";
313
+ getProjectRoot() {
314
+ return process.cwd();
315
+ }
316
+ detectIde() {
317
+ return true;
318
+ }
319
+ getSkillTargetDir(skillName, scope, projectRoot) {
320
+ const base = scope === "global" ? path5.join(os.homedir(), ".qoder") : path5.join(projectRoot ?? this.getProjectRoot(), ".qoder");
321
+ return path5.join(base, "skills", skillName);
322
+ }
323
+ };
324
+
325
+ // src/ide/ClaudeAdapter.ts
326
+ import * as os2 from "os";
327
+ import * as path6 from "path";
328
+ var ClaudeAdapter = class {
329
+ kind = "claude";
330
+ name = "claude";
331
+ getProjectRoot() {
332
+ return process.cwd();
333
+ }
334
+ detectIde() {
335
+ return Boolean(process.env.CLAUDECODE);
336
+ }
337
+ getSkillTargetDir(skillName, scope, projectRoot) {
338
+ const base = scope === "global" ? path6.join(os2.homedir(), ".claude") : path6.join(projectRoot ?? this.getProjectRoot(), ".claude");
339
+ return path6.join(base, "skills", skillName);
340
+ }
341
+ };
342
+
343
+ // src/ide/index.ts
344
+ function getAdapter(kind) {
345
+ switch (kind) {
346
+ case "qoder":
347
+ return new QoderAdapter();
348
+ case "claude":
349
+ return new ClaudeAdapter();
350
+ default: {
351
+ const _exhaustive = kind;
352
+ throw new Error(`Unsupported IDE kind: ${_exhaustive}`);
353
+ }
354
+ }
355
+ }
356
+
357
+ // src/utils/template.ts
358
+ import Handlebars from "handlebars";
359
+ import * as fs3 from "fs/promises";
360
+ Handlebars.registerHelper("lowercase", (str) => {
361
+ return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
362
+ });
363
+ var compiledCache = /* @__PURE__ */ new Map();
364
+ var MAX_CACHE_SIZE = 64;
365
+ function getCompiledTemplate(templateContent) {
366
+ let compiled = compiledCache.get(templateContent);
367
+ if (!compiled) {
368
+ if (compiledCache.size >= MAX_CACHE_SIZE) {
369
+ const firstKey = compiledCache.keys().next().value;
370
+ compiledCache.delete(firstKey);
371
+ }
372
+ compiled = Handlebars.compile(templateContent, { noEscape: true });
373
+ compiledCache.set(templateContent, compiled);
374
+ }
375
+ return compiled;
376
+ }
377
+ function renderTemplate(templateContent, data) {
378
+ const compiled = getCompiledTemplate(templateContent);
379
+ return compiled(data);
380
+ }
381
+ async function loadTemplateFile(filePath) {
382
+ return fs3.readFile(filePath, "utf-8");
383
+ }
384
+
385
+ // src/utils/path.ts
386
+ import * as path7 from "path";
387
+ import * as fs4 from "fs/promises";
388
+ function resolveSourcePath(source, variantDir, packageRoot) {
389
+ if (source.startsWith("_template/")) {
390
+ return path7.join(packageRoot, source);
391
+ }
392
+ return path7.join(variantDir, source);
393
+ }
394
+ async function walkDir(dir) {
395
+ const files = [];
396
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
397
+ for (const entry of entries) {
398
+ const fullPath = path7.join(dir, entry.name);
399
+ if (entry.isDirectory()) {
400
+ files.push(...await walkDir(fullPath));
401
+ } else if (entry.isFile()) {
402
+ files.push(fullPath);
403
+ }
404
+ }
405
+ return files;
406
+ }
407
+
408
+ // src/core/skills-installer.ts
409
+ async function installSkills(options) {
410
+ const { manifest, ides, scope, onlyIds } = options;
411
+ const installed = [];
412
+ const targets = manifest.skills.filter(
413
+ (s) => !onlyIds || onlyIds.includes(s.id)
414
+ );
415
+ for (const skill of targets) {
416
+ const skillIdes = skill.ides.filter((i) => ides.includes(i));
417
+ if (skillIdes.length === 0) {
418
+ logger.warn(
419
+ `Skill "${skill.name}" supports [${skill.ides.join(
420
+ ","
421
+ )}], no overlap with [${ides.join(",")}]; skipped.`
422
+ );
423
+ continue;
424
+ }
425
+ const sourceRecords = await writeSkillSource(skill, options);
426
+ installed.push(...sourceRecords);
427
+ for (const ide of skillIdes) {
428
+ const mirrorRecords = await mirrorSkillToIde(
429
+ skill,
430
+ ide,
431
+ scope,
432
+ options.projectRoot
433
+ );
434
+ installed.push(...mirrorRecords);
435
+ }
436
+ }
437
+ return { resources: installed, count: installed.length };
438
+ }
439
+ async function writeSkillSource(skill, options) {
440
+ const { data, packageRoot, projectRoot } = options;
441
+ const sourceAbs = path8.resolve(packageRoot, skill.source);
442
+ const targetDir = getSkillsSourceDir(projectRoot, skill.name);
443
+ const stat2 = await fs5.stat(sourceAbs);
444
+ const records = [];
445
+ if (stat2.isFile()) {
446
+ const targetFile = path8.join(targetDir, "SKILL.md");
447
+ const content = await renderSkillContent(sourceAbs, skill, data);
448
+ await writeFileSafe(targetFile, content);
449
+ records.push(makeSourceRecord(skill, targetFile, content));
450
+ logger.debug(` Wrote source: ${targetFile}`);
451
+ return records;
452
+ }
453
+ await ensureDir(targetDir);
454
+ const entries = await walkDir(sourceAbs);
455
+ for (const entry of entries) {
456
+ const rel2 = path8.relative(sourceAbs, entry);
457
+ let targetFile = path8.join(targetDir, rel2);
458
+ if (skill.template && targetFile.endsWith(".hbs")) {
459
+ targetFile = targetFile.slice(0, -4);
460
+ }
461
+ const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
462
+ await writeFileSafe(targetFile, content);
463
+ const relWritten = path8.relative(targetDir, targetFile);
464
+ records.push(makeSourceRecord(skill, targetFile, content, relWritten));
465
+ logger.debug(` Wrote source: ${targetFile}`);
466
+ }
467
+ return records;
468
+ }
469
+ async function mirrorSkillToIde(skill, ide, scope, projectRoot) {
470
+ const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
471
+ const adapter = getAdapter(ide);
472
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
473
+ const records = [];
474
+ const sourceFiles = await walkDir(sourceDir);
475
+ await ensureDir(targetDir);
476
+ for (const src of sourceFiles) {
477
+ const rel2 = path8.relative(sourceDir, src);
478
+ const targetFile = path8.join(targetDir, rel2);
479
+ const content = await fs5.readFile(src, "utf-8");
480
+ await writeFileSafe(targetFile, content);
481
+ records.push(makeMirrorRecord(skill, targetFile, content, ide, scope, rel2));
482
+ logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
483
+ }
484
+ return records;
485
+ }
486
+ async function renderSkillContent(sourceAbs, skill, data) {
487
+ if (skill.template ?? sourceAbs.endsWith(".hbs")) {
488
+ const tpl = await loadTemplateFile(sourceAbs);
489
+ return renderTemplate(tpl, { ...data, skill });
490
+ }
491
+ return fs5.readFile(sourceAbs, "utf-8");
492
+ }
493
+ function makeSourceRecord(skill, targetAbs, content, rel2) {
494
+ const id = rel2 ? `${skill.id}:source:${rel2}` : `${skill.id}:source`;
495
+ return {
496
+ id,
497
+ target: targetAbs,
498
+ hash: computeHash(content),
499
+ strategy: skill.updateStrategy
500
+ };
501
+ }
502
+ function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
503
+ const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
504
+ return {
505
+ id,
506
+ target: targetAbs,
507
+ hash: computeHash(content),
508
+ strategy: skill.updateStrategy,
509
+ ide,
510
+ scope
511
+ };
512
+ }
513
+ async function updateSkills(options) {
514
+ const { manifest, ides, scope, projectRoot } = options;
515
+ const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
516
+ const updated = [];
517
+ for (const skill of manifest.skills) {
518
+ const skillIdes = skill.ides.filter((i) => ides.includes(i));
519
+ if (skillIdes.length === 0) continue;
520
+ const sourceRecords = await rewriteSkillSource(
521
+ skill,
522
+ options,
523
+ summary
524
+ );
525
+ updated.push(...sourceRecords);
526
+ for (const ide of skillIdes) {
527
+ const mirrorRecords = await mirrorSkillToIde(
528
+ skill,
529
+ ide,
530
+ scope,
531
+ projectRoot
532
+ );
533
+ updated.push(...mirrorRecords);
534
+ }
535
+ }
536
+ return { resources: updated, summary };
537
+ }
538
+ async function rewriteSkillSource(skill, options, summary) {
539
+ const { data, packageRoot, projectRoot } = options;
540
+ const sourceAbs = path8.resolve(packageRoot, skill.source);
541
+ const targetDir = getSkillsSourceDir(projectRoot, skill.name);
542
+ const stat2 = await fs5.stat(sourceAbs);
543
+ if (!stat2.isFile()) {
544
+ await ensureDir(targetDir);
545
+ const entries = await walkDir(sourceAbs);
546
+ const records = [];
547
+ for (const entry of entries) {
548
+ const rel2 = path8.relative(sourceAbs, entry);
549
+ let targetFile2 = path8.join(targetDir, rel2);
550
+ if (skill.template && targetFile2.endsWith(".hbs")) {
551
+ targetFile2 = targetFile2.slice(0, -4);
552
+ }
553
+ const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
554
+ const exists2 = await fileExists(targetFile2);
555
+ if (exists2) {
556
+ await backupFile(targetFile2, projectRoot);
557
+ summary.overwritten++;
558
+ } else {
559
+ summary.created++;
560
+ }
561
+ await writeFileSafe(targetFile2, content);
562
+ const relWritten = path8.relative(targetDir, targetFile2);
563
+ records.push(makeSourceRecord(skill, targetFile2, content, relWritten));
564
+ }
565
+ return records;
566
+ }
567
+ const targetFile = path8.join(targetDir, "SKILL.md");
568
+ const newContent = await renderSkillContent(sourceAbs, skill, data);
569
+ const exists = await fileExists(targetFile);
570
+ if (skill.updateStrategy === "frozen") {
571
+ if (exists) {
572
+ summary.skipped++;
573
+ const current2 = await readFileOrNull(targetFile) ?? newContent;
574
+ return [makeSourceRecord(skill, targetFile, current2)];
575
+ }
576
+ await writeFileSafe(targetFile, newContent);
577
+ summary.created++;
578
+ return [makeSourceRecord(skill, targetFile, newContent)];
579
+ }
580
+ if (skill.updateStrategy === "regenerable" || !exists) {
581
+ if (exists) {
582
+ await backupFile(targetFile, projectRoot);
583
+ summary.overwritten++;
584
+ } else {
585
+ summary.created++;
586
+ }
587
+ await writeFileSafe(targetFile, newContent);
588
+ return [makeSourceRecord(skill, targetFile, newContent)];
589
+ }
590
+ const current = await readFileOrNull(targetFile);
591
+ let merged = current ?? newContent;
592
+ for (const regionId of skill.managedRegions ?? []) {
593
+ const re = new RegExp(
594
+ `<!-- teamix-evo:managed:start id="${escapeRegExp(
595
+ regionId
596
+ )}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp(
597
+ regionId
598
+ )}" -->`
599
+ );
600
+ const match = newContent.match(re);
601
+ if (match) {
602
+ const region = match[1].replace(/^\n/, "").replace(/\n$/, "");
603
+ try {
604
+ merged = replaceManagedRegion(merged, regionId, region);
605
+ } catch {
606
+ logger.warn(
607
+ `Managed region "${regionId}" not found in ${targetFile}. Skipped.`
608
+ );
609
+ }
610
+ }
611
+ }
612
+ await backupFile(targetFile, projectRoot);
613
+ await writeFileSafe(targetFile, merged);
614
+ summary.managed++;
615
+ return [makeSourceRecord(skill, targetFile, merged)];
616
+ }
617
+ function escapeRegExp(str) {
618
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
619
+ }
620
+ async function syncSkillsToIdes(options) {
621
+ const { projectRoot, skills, ides, scope, onlyIds } = options;
622
+ const out = [];
623
+ const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
624
+ for (const skill of targets) {
625
+ const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
626
+ if (!await fileExists(sourceDir)) {
627
+ logger.warn(
628
+ `Skill "${skill.id}" has no source at ${sourceDir}; skipped.`
629
+ );
630
+ continue;
631
+ }
632
+ for (const ide of ides) {
633
+ const adapter = getAdapter(ide);
634
+ const targetDir = adapter.getSkillTargetDir(
635
+ skill.name,
636
+ scope,
637
+ projectRoot
638
+ );
639
+ await ensureDir(targetDir);
640
+ const sourceFiles = await walkDir(sourceDir);
641
+ for (const src of sourceFiles) {
642
+ const rel2 = path8.relative(sourceDir, src);
643
+ const targetFile = path8.join(targetDir, rel2);
644
+ const content = await fs5.readFile(src, "utf-8");
645
+ await writeFileSafe(targetFile, content);
646
+ out.push({
647
+ id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
648
+ target: targetFile,
649
+ hash: computeHash(content),
650
+ strategy: skill.updateStrategy,
651
+ ide,
652
+ scope
653
+ });
654
+ }
655
+ }
656
+ }
657
+ return { resources: out, count: out.length };
658
+ }
659
+ async function removeSkillFiles(records) {
660
+ const removed = [];
661
+ for (const r of records) {
662
+ try {
663
+ await fs5.unlink(r.target);
664
+ removed.push(r.target);
665
+ } catch (err) {
666
+ if (err.code !== "ENOENT") {
667
+ logger.warn(`Failed to remove ${r.target}: ${err.message}`);
668
+ }
669
+ }
670
+ }
671
+ const parents = new Set(records.map((r) => path8.dirname(r.target)));
672
+ for (const dir of parents) {
673
+ try {
674
+ const entries = await fs5.readdir(dir);
675
+ if (entries.length === 0) await fs5.rmdir(dir);
676
+ } catch {
677
+ }
678
+ }
679
+ return removed;
680
+ }
681
+
682
+ // src/core/skills-add.ts
683
+ var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
684
+ var FLAT_VARIANT = "_flat";
685
+ async function runSkillsAdd(options) {
686
+ const { projectRoot, names: requestedNames } = options;
687
+ const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE;
688
+ const ideIdent = options.ide ?? "qoder";
689
+ const isIncremental = !!requestedNames && requestedNames.length > 0;
690
+ await ensureTeamixDir(projectRoot);
691
+ const existingConfig = await readProjectConfig(projectRoot);
692
+ const existingSkillsCfg = existingConfig?.packages?.skills;
693
+ if (!isIncremental && existingSkillsCfg) {
694
+ return { status: "already-added" };
695
+ }
696
+ const ides = options.ides && options.ides.length > 0 ? [...options.ides] : existingSkillsCfg?.ides ? [...existingSkillsCfg.ides] : [];
697
+ const scope = options.scope ?? existingSkillsCfg?.scope;
698
+ if (ides.length === 0) {
699
+ throw new Error("At least one IDE must be selected.");
700
+ }
701
+ if (!scope) {
702
+ throw new Error("Scope must be specified (project | global).");
703
+ }
704
+ const { manifest, data, packageRoot } = await loadSkillsData(packageName);
705
+ const currentDesignVariant = await readDesignVariant(projectRoot);
706
+ if (isIncremental) {
707
+ const known = new Set(manifest.skills.map((s) => s.id));
708
+ const unknown = requestedNames.filter((n) => !known.has(n));
709
+ if (unknown.length > 0) {
710
+ const available = [...known].join(", ");
711
+ throw new Error(
712
+ `Unknown skill id(s): ${unknown.join(", ")}. Available: ${available || "(none)"}.`
713
+ );
714
+ }
715
+ }
716
+ const existingInstalled = await readInstalledManifest(projectRoot);
717
+ const existingPkg = existingInstalled?.installed.find(
718
+ (p) => p.package === packageName
719
+ );
720
+ const existingLock = await readSkillsLock(projectRoot);
721
+ const existingSkillIds = /* @__PURE__ */ new Set([
722
+ ...Object.keys(existingLock?.skills ?? {}),
723
+ // Legacy fallback: pre-ADR-0013 installs only had manifest.json. Derive
724
+ // skill ids by stripping the trailing :source / :sub-file suffix.
725
+ ...(existingPkg?.resources ?? []).map((r) => r.id.split(":")[0])
726
+ ]);
727
+ let onlyIds;
728
+ let skippedSkillIds;
729
+ if (isIncremental) {
730
+ skippedSkillIds = requestedNames.filter((n) => existingSkillIds.has(n));
731
+ onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
732
+ } else {
733
+ skippedSkillIds = [];
734
+ onlyIds = manifest.skills.filter((s) => {
735
+ if (!s.variant) return true;
736
+ if (!currentDesignVariant) {
737
+ logger.debug(
738
+ `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): no design pack installed; will be picked up when "design init" runs.`
739
+ );
740
+ return false;
741
+ }
742
+ if (s.variant !== currentDesignVariant) {
743
+ logger.debug(
744
+ `Skipping variant-bound skill "${s.id}" (variant=${s.variant}): current design variant is "${currentDesignVariant}".`
745
+ );
746
+ return false;
747
+ }
748
+ return true;
749
+ }).map((s) => s.id);
750
+ }
751
+ if (isIncremental && onlyIds.length === 0) {
752
+ return {
753
+ status: "installed",
754
+ packageName,
755
+ version: existingSkillsCfg?.version ?? manifest.version,
756
+ ides,
757
+ scope,
758
+ skillCount: 0,
759
+ fileCount: 0,
760
+ resources: [],
761
+ addedSkillIds: [],
762
+ skippedSkillIds
763
+ };
764
+ }
765
+ const result = await installSkills({
766
+ projectRoot,
767
+ manifest,
768
+ data,
769
+ packageRoot,
770
+ ides,
771
+ scope,
772
+ onlyIds
773
+ });
774
+ const config = existingConfig ?? {
775
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
776
+ schemaVersion: 1,
777
+ ide: ideIdent,
778
+ packages: {}
779
+ };
780
+ config.packages.skills = {
781
+ variant: FLAT_VARIANT,
782
+ version: manifest.version,
783
+ ides,
784
+ scope
785
+ };
786
+ await writeProjectConfig(projectRoot, config);
787
+ const installedAt = (/* @__PURE__ */ new Date()).toISOString();
788
+ const installedManifest = existingInstalled ?? {
789
+ schemaVersion: 1,
790
+ installed: []
791
+ };
792
+ const idx = installedManifest.installed.findIndex(
793
+ (p) => p.package === packageName
794
+ );
795
+ const mergedResources = mergeInstalledResources(
796
+ existingPkg?.resources ?? [],
797
+ result.resources
798
+ );
799
+ const entry = {
800
+ package: packageName,
801
+ variant: FLAT_VARIANT,
802
+ version: manifest.version,
803
+ installedAt,
804
+ resources: mergedResources
805
+ };
806
+ if (idx >= 0) installedManifest.installed[idx] = entry;
807
+ else installedManifest.installed.push(entry);
808
+ await writeInstalledManifest(projectRoot, installedManifest);
809
+ const lock = existingLock ?? {
810
+ schemaVersion: 1,
811
+ skills: {}
812
+ };
813
+ for (const skillId of onlyIds) {
814
+ const skillDef = manifest.skills.find((s) => s.id === skillId);
815
+ if (!skillDef) continue;
816
+ const mirroredTo = skillDef.ides.filter((i) => ides.includes(i));
817
+ lock.skills[skillId] = {
818
+ version: skillDef.version,
819
+ from: packageName,
820
+ installedAt,
821
+ scope,
822
+ mirroredTo
823
+ };
824
+ }
825
+ await writeSkillsLock(projectRoot, lock);
826
+ return {
827
+ status: "installed",
828
+ packageName,
829
+ version: manifest.version,
830
+ ides,
831
+ scope,
832
+ skillCount: onlyIds.length,
833
+ fileCount: result.count,
834
+ resources: result.resources,
835
+ addedSkillIds: onlyIds,
836
+ skippedSkillIds
837
+ };
838
+ }
839
+ function mergeInstalledResources(existing, next) {
840
+ const map = /* @__PURE__ */ new Map();
841
+ const key = (r) => `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
842
+ for (const r of existing) map.set(key(r), r);
843
+ for (const r of next) map.set(key(r), r);
844
+ return [...map.values()];
845
+ }
846
+
847
+ // src/core/design-init.ts
848
+ var BASELINE_DESIGN_RULES_SKILL = "teamix-evo-design-rules";
849
+ var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
850
+ var DEFAULT_AUTO_SKILL_IDES = ["qoder", "claude"];
851
+ var DEFAULT_AUTO_SKILL_SCOPE = "project";
852
+ var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
853
+ var require3 = createRequire2(import.meta.url);
854
+ async function runDesignInit(options) {
855
+ const { projectRoot, variant, ide } = options;
856
+ const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
857
+ await ensureTeamixDir(projectRoot);
858
+ const existingConfig = await readProjectConfig(projectRoot);
859
+ if (existingConfig?.packages?.design) {
860
+ return {
861
+ status: "already-initialized",
862
+ existingVariant: existingConfig.packages.design.variant
863
+ };
864
+ }
865
+ const packageRoot = options.packageRoot ?? resolveDesignPackageRoot(packageName);
866
+ const catalog = await loadDesignPackageManifest(packageRoot);
867
+ const variantEntry = catalog.variants.find((v) => v.name === variant);
868
+ if (!variantEntry) {
869
+ const known = catalog.variants.map((v) => v.name).join(", ");
870
+ throw new Error(
871
+ `Design variant not found: "${variant}". Known variants: ${known || "(none)"}. Hint: run "teamix-evo design list-variants" to see all.`
872
+ );
873
+ }
874
+ const defaultDir = path9.join(packageRoot, "default");
875
+ const variantDir = path9.join(packageRoot, "variants", variant);
876
+ const defaultPack = await loadDesignPack(defaultDir);
877
+ const variantPack = await loadDesignPack(variantDir);
878
+ const merge = await mergeDefaultAndVariant(defaultDir, variantDir);
879
+ const installed = [];
880
+ for (const file of merge.files) {
881
+ const result = await installPackFile(file, projectRoot);
882
+ if (result) installed.push(result);
883
+ }
884
+ const lock = {
885
+ schemaVersion: 1,
886
+ default: { version: defaultPack.version, from: packageName },
887
+ variant: {
888
+ name: variantPack.name,
889
+ displayName: variantPack.displayName,
890
+ version: variantPack.version,
891
+ from: packageName
892
+ },
893
+ linked: variantPack.linked,
894
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
895
+ };
896
+ await writeFileSafe(
897
+ path9.join(projectRoot, ".teamix-evo", "design", "pack.lock.json"),
898
+ JSON.stringify(lock, null, 2) + "\n"
899
+ );
900
+ const config = {
901
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
902
+ schemaVersion: 1,
903
+ ide,
904
+ packages: {
905
+ design: {
906
+ variant,
907
+ version: variantPack.version,
908
+ tailwind: "v4"
909
+ }
910
+ }
911
+ };
912
+ await writeProjectConfig(projectRoot, config);
913
+ const installedManifest = {
914
+ schemaVersion: 1,
915
+ installed: [
916
+ {
917
+ package: packageName,
918
+ variant,
919
+ version: variantPack.version,
920
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
921
+ resources: installed
922
+ }
923
+ ]
924
+ };
925
+ await writeInstalledManifest(projectRoot, installedManifest);
926
+ const skills = await tryAutoInstallVariantSkills({
927
+ projectRoot,
928
+ variant,
929
+ ide
930
+ });
931
+ return {
932
+ status: "installed",
933
+ packageName,
934
+ variant,
935
+ version: variantPack.version,
936
+ count: installed.length,
937
+ resources: installed,
938
+ merge: {
939
+ overrides: merge.overrides,
940
+ variantAdds: merge.variantAdds,
941
+ defaultPassThrough: merge.defaultPassThrough
942
+ },
943
+ skills
944
+ };
945
+ }
946
+ async function tryAutoInstallVariantSkills(args) {
947
+ const { projectRoot, variant, ide } = args;
948
+ const variantSkillId = `${BASELINE_DESIGN_RULES_SKILL}-${variant}`;
949
+ const desired = [BASELINE_DESIGN_RULES_SKILL, variantSkillId];
950
+ let manifestSkillIds;
951
+ try {
952
+ const { manifest } = await loadSkillsData(DEFAULT_SKILLS_PACKAGE2);
953
+ manifestSkillIds = new Set(manifest.skills.map((s) => s.id));
954
+ } catch (err) {
955
+ logger.warn(
956
+ `Skipping skills auto-install: could not load skills manifest (${err.message}).`
957
+ );
958
+ return {
959
+ attempted: [],
960
+ addedSkillIds: [],
961
+ skippedSkillIds: [],
962
+ missing: desired
963
+ };
964
+ }
965
+ const present = desired.filter((id) => manifestSkillIds.has(id));
966
+ const missing = desired.filter((id) => !manifestSkillIds.has(id));
967
+ if (missing.length > 0) {
968
+ logger.warn(
969
+ `Skills auto-install: not found in manifest, skipping: ${missing.join(", ")}.`
970
+ );
971
+ }
972
+ if (present.length === 0) {
973
+ return {
974
+ attempted: desired,
975
+ addedSkillIds: [],
976
+ skippedSkillIds: [],
977
+ missing
978
+ };
979
+ }
980
+ try {
981
+ const result = await runSkillsAdd({
982
+ projectRoot,
983
+ names: present,
984
+ ides: DEFAULT_AUTO_SKILL_IDES,
985
+ scope: DEFAULT_AUTO_SKILL_SCOPE,
986
+ ide
987
+ });
988
+ if (result.status !== "installed") {
989
+ return {
990
+ attempted: desired,
991
+ addedSkillIds: [],
992
+ skippedSkillIds: present,
993
+ missing
994
+ };
995
+ }
996
+ return {
997
+ attempted: desired,
998
+ addedSkillIds: result.addedSkillIds,
999
+ skippedSkillIds: result.skippedSkillIds,
1000
+ missing
1001
+ };
1002
+ } catch (err) {
1003
+ logger.warn(
1004
+ `Skills auto-install failed (continuing): ${err.message}`
1005
+ );
1006
+ return {
1007
+ attempted: desired,
1008
+ addedSkillIds: [],
1009
+ skippedSkillIds: [],
1010
+ missing
1011
+ };
1012
+ }
1013
+ }
1014
+ async function installPackFile(file, projectRoot) {
1015
+ const cls = classifyPackFile(file.relPath);
1016
+ if (cls === null) return null;
1017
+ const targetAbs = path9.join(projectRoot, cls.target);
1018
+ if (cls.isFrozen && await fileExists(targetAbs)) {
1019
+ const existing = await fs6.readFile(targetAbs, "utf-8");
1020
+ return {
1021
+ id: `pack:${file.relPath}`,
1022
+ target: cls.target,
1023
+ hash: computeHash(existing),
1024
+ strategy: cls.strategy
1025
+ };
1026
+ }
1027
+ const content = await fs6.readFile(file.sourcePath, "utf-8");
1028
+ await writeFileSafe(targetAbs, content);
1029
+ return {
1030
+ id: `pack:${file.relPath}`,
1031
+ target: cls.target,
1032
+ hash: computeHash(content),
1033
+ strategy: cls.strategy
1034
+ };
1035
+ }
1036
+ function resolveDesignPackageRoot(packageName) {
1037
+ const pkgJson = require3.resolve(`${packageName}/package.json`);
1038
+ return path9.dirname(pkgJson);
1039
+ }
1040
+
1041
+ // src/core/ui-init.ts
1042
+ var DEFAULT_UI_ALIASES = {
1043
+ components: "src/components/ui",
1044
+ hooks: "src/hooks",
1045
+ utils: "src/lib/utils",
1046
+ lib: "src/lib",
1047
+ business: "src/components/business",
1048
+ templates: "src/templates"
1049
+ };
1050
+ var DEFAULT_UI_ICON_LIBRARY = "lucide";
1051
+ async function runUiInit(options) {
1052
+ const { projectRoot } = options;
1053
+ const ideIdent = options.ide ?? "qoder";
1054
+ await ensureTeamixDir(projectRoot);
1055
+ const existingConfig = await readProjectConfig(projectRoot);
1056
+ if (existingConfig?.packages?.ui) {
1057
+ return { status: "already-initialized" };
1058
+ }
1059
+ const aliases = {
1060
+ components: options.aliases?.components ?? DEFAULT_UI_ALIASES.components,
1061
+ hooks: options.aliases?.hooks ?? DEFAULT_UI_ALIASES.hooks,
1062
+ utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
1063
+ lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
1064
+ business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
1065
+ templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
1066
+ };
1067
+ const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
1068
+ const tsx = options.tsx ?? true;
1069
+ const rsc = options.rsc ?? false;
1070
+ const config = existingConfig ?? {
1071
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
1072
+ schemaVersion: 1,
1073
+ ide: ideIdent,
1074
+ packages: {}
1075
+ };
1076
+ config.packages.ui = {
1077
+ variant: "_flat",
1078
+ version: "0.0.0",
1079
+ aliases,
1080
+ iconLibrary,
1081
+ tsx,
1082
+ rsc
1083
+ };
1084
+ await writeProjectConfig(projectRoot, config);
1085
+ return {
1086
+ status: "installed",
1087
+ aliases,
1088
+ iconLibrary,
1089
+ tsx,
1090
+ rsc
1091
+ };
1092
+ }
1093
+
1094
+ // src/core/ui-client.ts
1095
+ import * as path10 from "path";
1096
+ import * as fs7 from "fs/promises";
1097
+ import { createRequire as createRequire3 } from "module";
1098
+ import { loadUiPackageManifest } from "@teamix-evo/registry";
1099
+ var require4 = createRequire3(import.meta.url);
1100
+ function resolvePackageRoot2(packageName) {
1101
+ const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
1102
+ return path10.dirname(pkgJsonPath);
1103
+ }
1104
+ async function loadUiData(packageName) {
1105
+ const packageRoot = resolvePackageRoot2(packageName);
1106
+ logger.debug(`Resolved ui package root: ${packageRoot}`);
1107
+ const manifest = await loadUiPackageManifest(packageRoot);
1108
+ let data = {};
1109
+ const dataPath = path10.join(packageRoot, "_data.json");
1110
+ try {
1111
+ const raw = await fs7.readFile(dataPath, "utf-8");
1112
+ data = JSON.parse(raw);
1113
+ } catch (err) {
1114
+ if (err.code !== "ENOENT") {
1115
+ throw err;
1116
+ }
1117
+ logger.debug(`No _data.json found at ${dataPath}, using empty data`);
1118
+ }
1119
+ return { manifest, data, packageRoot };
1120
+ }
1121
+
1122
+ // src/core/ui-installer.ts
1123
+ import * as path11 from "path";
1124
+ import * as fs8 from "fs/promises";
1125
+ import { resolveUiEntryOrder } from "@teamix-evo/registry";
1126
+
1127
+ // src/utils/transform-imports.ts
1128
+ var SOURCE_ROOT_TO_ALIAS_KEY = {
1129
+ components: "components",
1130
+ hooks: "hooks",
1131
+ utils: "utils",
1132
+ lib: "lib"
1133
+ };
1134
+ function rewriteImports(source, aliases) {
1135
+ return source.replace(
1136
+ /(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
1137
+ (full, quote, root, rest) => {
1138
+ const aliasKey = SOURCE_ROOT_TO_ALIAS_KEY[root];
1139
+ if (!aliasKey) return full;
1140
+ const alias = aliases[aliasKey];
1141
+ const normalized = aliasToImportPath(alias);
1142
+ const flatRest = flattenRestPath(rest);
1143
+ return `${quote}${normalized}${flatRest}${quote}`;
1144
+ }
1145
+ );
1146
+ }
1147
+ function flattenRestPath(rest) {
1148
+ if (!rest) return "";
1149
+ const segments = rest.split("/");
1150
+ if (segments.length === 3) {
1151
+ return `/${segments[2]}`;
1152
+ }
1153
+ return rest;
1154
+ }
1155
+ function aliasToImportPath(alias) {
1156
+ const trimmed = alias.replace(/^\.\//, "").replace(/\/$/, "");
1157
+ if (trimmed.startsWith("src/")) {
1158
+ return `@/${trimmed.slice("src/".length)}`;
1159
+ }
1160
+ return `@/${trimmed}`;
1161
+ }
1162
+
1163
+ // src/core/ui-installer.ts
1164
+ var DESIGN_COMPONENTS_DIR = ".teamix-evo/design/components";
1165
+ async function installUiEntries(options) {
1166
+ const {
1167
+ projectRoot,
1168
+ manifest,
1169
+ packageRoot,
1170
+ aliases,
1171
+ requested,
1172
+ skipExisting = true
1173
+ } = options;
1174
+ const orderedIds = resolveUiEntryOrder(manifest.entries, requested);
1175
+ const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
1176
+ const resources = [];
1177
+ const npmDeps = {};
1178
+ const metaFiles = [];
1179
+ let written = 0;
1180
+ let skipped = 0;
1181
+ for (const id of orderedIds) {
1182
+ const entry = idToEntry.get(id);
1183
+ if (!entry) continue;
1184
+ if (entry.dependencies) {
1185
+ for (const [name, range] of Object.entries(entry.dependencies)) {
1186
+ npmDeps[name] = range;
1187
+ }
1188
+ }
1189
+ for (const file of entry.files) {
1190
+ const targetAbs = resolveTargetPath(projectRoot, aliases, entry, file);
1191
+ const exists = await fileExists(targetAbs);
1192
+ if (exists && skipExisting && (entry.updateStrategy ?? "frozen") === "frozen") {
1193
+ logger.info(` skip (frozen, exists): ${rel(projectRoot, targetAbs)}`);
1194
+ skipped++;
1195
+ continue;
1196
+ }
1197
+ const sourceAbs = path11.resolve(packageRoot, file.source);
1198
+ const raw = await fs8.readFile(sourceAbs, "utf-8");
1199
+ const transformed = rewriteImports(raw, aliases);
1200
+ await writeFileSafe(targetAbs, transformed);
1201
+ written++;
1202
+ logger.info(` write: ${rel(projectRoot, targetAbs)}`);
1203
+ resources.push({
1204
+ id: `${entry.id}:${file.targetName}`,
1205
+ target: targetAbs,
1206
+ hash: computeHash(transformed),
1207
+ strategy: entry.updateStrategy ?? "frozen"
1208
+ });
1209
+ }
1210
+ if (entry.meta) {
1211
+ const metaSourceAbs = path11.resolve(packageRoot, entry.meta);
1212
+ const metaContent = await fs8.readFile(metaSourceAbs, "utf-8");
1213
+ const metaTargetAbs = path11.join(
1214
+ projectRoot,
1215
+ DESIGN_COMPONENTS_DIR,
1216
+ `${entry.id}.meta.md`
1217
+ );
1218
+ await writeFileSafe(metaTargetAbs, metaContent);
1219
+ metaFiles.push(metaTargetAbs);
1220
+ resources.push({
1221
+ id: `${entry.id}:meta`,
1222
+ target: metaTargetAbs,
1223
+ hash: computeHash(metaContent),
1224
+ strategy: "regenerable"
1225
+ });
1226
+ logger.info(` meta: ${rel(projectRoot, metaTargetAbs)}`);
1227
+ }
1228
+ }
1229
+ return {
1230
+ orderedIds,
1231
+ resources,
1232
+ npmDependencies: npmDeps,
1233
+ written,
1234
+ skipped,
1235
+ metaFiles
1236
+ };
1237
+ }
1238
+ function resolveTargetPath(projectRoot, aliases, entry, file) {
1239
+ const aliasDir = aliases[file.targetAlias];
1240
+ if (!aliasDir) {
1241
+ throw new Error(
1242
+ `Entry "${entry.id}" requires alias "${file.targetAlias}" but it is not configured.`
1243
+ );
1244
+ }
1245
+ return path11.join(projectRoot, aliasDir, file.targetName);
1246
+ }
1247
+ function rel(projectRoot, abs) {
1248
+ return path11.relative(projectRoot, abs);
1249
+ }
1250
+ async function removeUiFiles(records) {
1251
+ const removed = [];
1252
+ for (const r of records) {
1253
+ try {
1254
+ await fs8.unlink(r.target);
1255
+ removed.push(r.target);
1256
+ } catch (err) {
1257
+ if (err.code !== "ENOENT") {
1258
+ logger.warn(`Failed to remove ${r.target}: ${err.message}`);
1259
+ }
1260
+ }
1261
+ }
1262
+ const parents = new Set(records.map((r) => path11.dirname(r.target)));
1263
+ for (const dir of parents) {
1264
+ try {
1265
+ const entries = await fs8.readdir(dir);
1266
+ if (entries.length === 0) await fs8.rmdir(dir);
1267
+ } catch {
1268
+ }
1269
+ }
1270
+ return removed;
1271
+ }
1272
+
1273
+ // src/core/ui-add.ts
1274
+ var DEFAULT_UI_PACKAGE = "@teamix-evo/ui";
1275
+ async function runUiAdd(options) {
1276
+ const { projectRoot, ids, overwrite } = options;
1277
+ const packageName = options.packageName ?? DEFAULT_UI_PACKAGE;
1278
+ if (ids.length === 0) {
1279
+ throw new Error("At least one entry id must be provided.");
1280
+ }
1281
+ const config = await readProjectConfig(projectRoot);
1282
+ const uiCfg = config?.packages?.ui;
1283
+ if (!config || !uiCfg?.aliases) {
1284
+ throw new Error(
1285
+ "UI not initialized. Run `runUiInit` (or `teamix-evo ui init`) first."
1286
+ );
1287
+ }
1288
+ const { manifest, packageRoot } = await loadUiData(packageName);
1289
+ const knownIds = new Set(manifest.entries.map((e) => e.id));
1290
+ const unknown = ids.filter((id) => !knownIds.has(id));
1291
+ if (unknown.length > 0) {
1292
+ throw new Error(
1293
+ `Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
1294
+ );
1295
+ }
1296
+ const result = await installUiEntries({
1297
+ projectRoot,
1298
+ manifest,
1299
+ packageRoot,
1300
+ aliases: uiCfg.aliases,
1301
+ requested: ids,
1302
+ skipExisting: !overwrite
1303
+ });
1304
+ const installed = await readInstalledManifest(
1305
+ projectRoot
1306
+ ) ?? { schemaVersion: 1, installed: [] };
1307
+ const idx = installed.installed.findIndex((p) => p.package === packageName);
1308
+ const prior = idx >= 0 ? installed.installed[idx] : null;
1309
+ const mergedResources = mergeResources(
1310
+ prior?.resources ?? [],
1311
+ result.resources
1312
+ );
1313
+ const entry = {
1314
+ package: packageName,
1315
+ variant: "_flat",
1316
+ version: manifest.version,
1317
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1318
+ resources: mergedResources
1319
+ };
1320
+ if (idx >= 0) installed.installed[idx] = entry;
1321
+ else installed.installed.push(entry);
1322
+ await writeInstalledManifest(projectRoot, installed);
1323
+ if (uiCfg.version !== manifest.version) {
1324
+ uiCfg.version = manifest.version;
1325
+ await writeProjectConfig(projectRoot, config);
1326
+ }
1327
+ return {
1328
+ packageName,
1329
+ orderedIds: result.orderedIds,
1330
+ written: result.written,
1331
+ skipped: result.skipped,
1332
+ metaFiles: result.metaFiles,
1333
+ npmDependencies: result.npmDependencies,
1334
+ resources: result.resources
1335
+ };
1336
+ }
1337
+ function mergeResources(prior, next) {
1338
+ const merged = /* @__PURE__ */ new Map();
1339
+ for (const r of prior) merged.set(r.id, r);
1340
+ for (const r of next) merged.set(r.id, r);
1341
+ return Array.from(merged.values());
1342
+ }
1343
+
1344
+ // src/core/ui-list.ts
1345
+ var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
1346
+ async function runUiList(options) {
1347
+ const { projectRoot, installedOnly } = options;
1348
+ const packageName = options.packageName ?? DEFAULT_UI_PACKAGE2;
1349
+ const { manifest } = await loadUiData(packageName);
1350
+ const installedManifest = await readInstalledManifest(projectRoot);
1351
+ const installedIds = /* @__PURE__ */ new Set();
1352
+ const uiPkg = installedManifest?.installed.find(
1353
+ (p) => p.package === packageName
1354
+ );
1355
+ for (const r of uiPkg?.resources ?? []) {
1356
+ const colon = r.id.indexOf(":");
1357
+ installedIds.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
1358
+ }
1359
+ const entries = manifest.entries.filter((e) => !installedOnly || installedIds.has(e.id)).map((e) => ({
1360
+ id: e.id,
1361
+ type: e.type,
1362
+ description: e.description,
1363
+ installed: installedIds.has(e.id)
1364
+ }));
1365
+ return {
1366
+ packageName,
1367
+ total: manifest.entries.length,
1368
+ installedCount: installedIds.size,
1369
+ entries
1370
+ };
1371
+ }
1372
+
1373
+ // src/core/installer.ts
1374
+ import * as path12 from "path";
1375
+ import * as fs9 from "fs/promises";
1376
+ async function installResources(options) {
1377
+ const { projectRoot, manifest, data, variantDir, packageRoot } = options;
1378
+ const installedResources = [];
1379
+ for (const resource of manifest.resources) {
1380
+ logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
1381
+ if (resource.recursive) {
1382
+ const results = await installRecursiveResource(
1383
+ resource,
1384
+ projectRoot,
1385
+ data,
1386
+ variantDir,
1387
+ packageRoot
1388
+ );
1389
+ installedResources.push(...results);
1390
+ } else {
1391
+ const result = await installSingleResource(
1392
+ resource,
1393
+ projectRoot,
1394
+ data,
1395
+ variantDir,
1396
+ packageRoot
1397
+ );
1398
+ installedResources.push(result);
1399
+ }
1400
+ }
1401
+ return {
1402
+ resources: installedResources,
1403
+ count: installedResources.length
1404
+ };
1405
+ }
1406
+ async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
1407
+ const sourcePath = resolveSourcePath(
1408
+ resource.source,
1409
+ variantDir,
1410
+ packageRoot
1411
+ );
1412
+ const targetPath = path12.join(projectRoot, resource.target);
1413
+ let content;
1414
+ if (resource.template) {
1415
+ const templateContent = await loadTemplateFile(sourcePath);
1416
+ content = renderTemplate(templateContent, data);
1417
+ } else {
1418
+ content = await fs9.readFile(sourcePath, "utf-8");
1419
+ }
1420
+ await writeFileSafe(targetPath, content);
1421
+ const hash = computeHash(content);
1422
+ logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
1423
+ return {
1424
+ id: resource.id,
1425
+ target: resource.target,
1426
+ hash,
1427
+ strategy: resource.updateStrategy
1428
+ };
1429
+ }
1430
+ async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
1431
+ const sourcePath = resolveSourcePath(
1432
+ resource.source,
1433
+ variantDir,
1434
+ packageRoot
1435
+ );
1436
+ const targetDir = path12.join(projectRoot, resource.target);
1437
+ const results = [];
1438
+ await ensureDir(targetDir);
1439
+ const entries = await walkDir(sourcePath);
1440
+ for (const entry of entries) {
1441
+ const relPath = path12.relative(sourcePath, entry);
1442
+ let targetFile = path12.join(targetDir, relPath);
1443
+ if (resource.template && targetFile.endsWith(".hbs")) {
1444
+ targetFile = targetFile.slice(0, -4);
1445
+ }
1446
+ let content;
1447
+ if (resource.template && entry.endsWith(".hbs")) {
1448
+ const templateContent = await loadTemplateFile(entry);
1449
+ content = renderTemplate(templateContent, data);
1450
+ } else {
1451
+ content = await fs9.readFile(entry, "utf-8");
1452
+ }
1453
+ await writeFileSafe(targetFile, content);
1454
+ const hash = computeHash(content);
1455
+ const targetRel = path12.relative(projectRoot, targetFile);
1456
+ results.push({
1457
+ id: `${resource.id}:${relPath}`,
1458
+ target: targetRel,
1459
+ hash,
1460
+ strategy: resource.updateStrategy
1461
+ });
1462
+ logger.debug(` Written: ${targetRel}`);
1463
+ }
1464
+ return results;
1465
+ }
1466
+
1467
+ // src/core/registry-client.ts
1468
+ import * as path13 from "path";
1469
+ import * as fs10 from "fs/promises";
1470
+ import { createRequire as createRequire4 } from "module";
1471
+ import { loadVariantManifest } from "@teamix-evo/registry";
1472
+ var require5 = createRequire4(import.meta.url);
1473
+ function resolvePackageRoot3(packageName) {
1474
+ const pkgJsonPath = require5.resolve(`${packageName}/package.json`);
1475
+ return path13.dirname(pkgJsonPath);
1476
+ }
1477
+ async function loadVariantData(packageName, variant) {
1478
+ const packageRoot = resolvePackageRoot3(packageName);
1479
+ const variantDir = path13.join(packageRoot, "library", variant);
1480
+ logger.debug(`Resolved variant dir: ${variantDir}`);
1481
+ logger.debug(`Package root: ${packageRoot}`);
1482
+ const manifest = await loadVariantManifest(variantDir);
1483
+ let data = {};
1484
+ const dataPath = path13.join(variantDir, "_data.json");
1485
+ try {
1486
+ const raw = await fs10.readFile(dataPath, "utf-8");
1487
+ data = JSON.parse(raw);
1488
+ } catch (err) {
1489
+ if (err.code !== "ENOENT") {
1490
+ throw err;
1491
+ }
1492
+ logger.debug(`No _data.json found at ${dataPath}, using empty data`);
1493
+ }
1494
+ return { manifest, data, variantDir, packageRoot };
1495
+ }
1496
+ export {
1497
+ DEFAULT_UI_ALIASES,
1498
+ DEFAULT_UI_ICON_LIBRARY,
1499
+ ensureTeamixDir,
1500
+ getTeamixDir,
1501
+ installResources,
1502
+ installSkills,
1503
+ installUiEntries,
1504
+ loadSkillsData,
1505
+ loadUiData,
1506
+ loadVariantData,
1507
+ readInstalledManifest,
1508
+ readProjectConfig,
1509
+ removeSkillFiles,
1510
+ removeUiFiles,
1511
+ runDesignInit,
1512
+ runSkillsAdd,
1513
+ runUiAdd,
1514
+ runUiInit,
1515
+ runUiList,
1516
+ syncSkillsToIdes,
1517
+ updateSkills,
1518
+ writeInstalledManifest,
1519
+ writeProjectConfig
1520
+ };
1521
+ //# sourceMappingURL=index.js.map