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.
@@ -1,8 +1,16 @@
1
- // src/core/registry-client.ts
2
- import * as path from "path";
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
3
12
  import * as fs from "fs/promises";
4
- import { createRequire } from "module";
5
- import { loadVariantManifest } from "@teamix-evo/registry";
13
+ import * as path from "path";
6
14
 
7
15
  // src/utils/logger.ts
8
16
  import { red, yellow, cyan, green, gray } from "kolorist";
@@ -27,52 +35,20 @@ var logger = {
27
35
  }
28
36
  };
29
37
 
30
- // src/core/registry-client.ts
31
- var require2 = createRequire(import.meta.url);
32
- function resolvePackageRoot(packageName) {
33
- const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
34
- return path.dirname(pkgJsonPath);
35
- }
36
- async function loadVariantData(packageName, variant) {
37
- const packageRoot = resolvePackageRoot(packageName);
38
- const variantDir = path.join(packageRoot, "library", variant);
39
- logger.debug(`Resolved variant dir: ${variantDir}`);
40
- logger.debug(`Package root: ${packageRoot}`);
41
- const manifest = await loadVariantManifest(variantDir);
42
- let data = {};
43
- const dataPath = path.join(variantDir, "_data.json");
44
- try {
45
- const raw = await fs.readFile(dataPath, "utf-8");
46
- data = JSON.parse(raw);
47
- } catch (err) {
48
- if (err.code !== "ENOENT") {
49
- throw err;
50
- }
51
- logger.debug(`No _data.json found at ${dataPath}, using empty data`);
52
- }
53
- return { manifest, data, variantDir, packageRoot };
54
- }
55
-
56
- // src/core/installer.ts
57
- import * as path4 from "path";
58
- import * as fs5 from "fs/promises";
59
-
60
38
  // src/utils/fs.ts
61
- import * as fs2 from "fs/promises";
62
- import * as path2 from "path";
63
39
  async function ensureDir(dir) {
64
- await fs2.mkdir(dir, { recursive: true });
40
+ await fs.mkdir(dir, { recursive: true });
65
41
  }
66
42
  async function writeFileSafe(filePath, content) {
67
- const dir = path2.dirname(filePath);
43
+ const dir = path.dirname(filePath);
68
44
  await ensureDir(dir);
69
45
  const tmp = filePath + ".tmp";
70
- await fs2.writeFile(tmp, content, "utf-8");
71
- await fs2.rename(tmp, filePath);
46
+ await fs.writeFile(tmp, content, "utf-8");
47
+ await fs.rename(tmp, filePath);
72
48
  }
73
49
  async function readFileOrNull(filePath) {
74
50
  try {
75
- return await fs2.readFile(filePath, "utf-8");
51
+ return await fs.readFile(filePath, "utf-8");
76
52
  } catch (err) {
77
53
  if (err.code === "ENOENT") {
78
54
  return null;
@@ -86,21 +62,21 @@ async function backupFile(filePath, projectRoot) {
86
62
  logger.debug(`Skip backup: ${filePath} does not exist`);
87
63
  return;
88
64
  }
89
- const rel2 = path2.relative(projectRoot, filePath);
65
+ const rel2 = path.relative(projectRoot, filePath);
90
66
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
91
- const backupPath = path2.join(
67
+ const backupPath = path.join(
92
68
  projectRoot,
93
69
  ".teamix-evo",
94
70
  ".backups",
95
71
  `${rel2}.${timestamp}.bak`
96
72
  );
97
- await ensureDir(path2.dirname(backupPath));
98
- await fs2.writeFile(backupPath, content, "utf-8");
99
- logger.debug(`Backed up ${rel2} \u2192 ${path2.relative(projectRoot, backupPath)}`);
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)}`);
100
76
  }
101
77
  async function fileExists(filePath) {
102
78
  try {
103
- await fs2.access(filePath);
79
+ await fs.access(filePath);
104
80
  return true;
105
81
  } catch {
106
82
  return false;
@@ -114,157 +90,71 @@ function computeHash(content) {
114
90
  return `sha256:${hash}`;
115
91
  }
116
92
 
117
- // src/utils/template.ts
118
- import Handlebars from "handlebars";
119
- import * as fs3 from "fs/promises";
120
- Handlebars.registerHelper("lowercase", (str) => {
121
- return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
122
- });
123
- var compiledCache = /* @__PURE__ */ new Map();
124
- var MAX_CACHE_SIZE = 64;
125
- function getCompiledTemplate(templateContent) {
126
- let compiled = compiledCache.get(templateContent);
127
- if (!compiled) {
128
- if (compiledCache.size >= MAX_CACHE_SIZE) {
129
- const firstKey = compiledCache.keys().next().value;
130
- compiledCache.delete(firstKey);
131
- }
132
- compiled = Handlebars.compile(templateContent, { noEscape: true });
133
- compiledCache.set(templateContent, compiled);
134
- }
135
- return compiled;
136
- }
137
- function renderTemplate(templateContent, data) {
138
- const compiled = getCompiledTemplate(templateContent);
139
- return compiled(data);
140
- }
141
- async function loadTemplateFile(filePath) {
142
- return fs3.readFile(filePath, "utf-8");
143
- }
144
-
145
- // src/utils/path.ts
146
- import * as path3 from "path";
147
- import * as fs4 from "fs/promises";
148
- function resolveSourcePath(source, variantDir, packageRoot) {
149
- if (source.startsWith("_template/")) {
150
- return path3.join(packageRoot, source);
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;
151
109
  }
152
- return path3.join(variantDir, source);
153
- }
154
- async function walkDir(dir) {
155
- const files = [];
156
- const entries = await fs4.readdir(dir, { withFileTypes: true });
157
- for (const entry of entries) {
158
- const fullPath = path3.join(dir, entry.name);
159
- if (entry.isDirectory()) {
160
- files.push(...await walkDir(fullPath));
161
- } else if (entry.isFile()) {
162
- files.push(fullPath);
163
- }
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
+ };
164
118
  }
165
- return files;
166
- }
167
-
168
- // src/core/installer.ts
169
- async function installResources(options) {
170
- const { projectRoot, manifest, data, variantDir, packageRoot } = options;
171
- const installedResources = [];
172
- for (const resource of manifest.resources) {
173
- logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
174
- if (resource.recursive) {
175
- const results = await installRecursiveResource(
176
- resource,
177
- projectRoot,
178
- data,
179
- variantDir,
180
- packageRoot
181
- );
182
- installedResources.push(...results);
183
- } else {
184
- const result = await installSingleResource(
185
- resource,
186
- projectRoot,
187
- data,
188
- variantDir,
189
- packageRoot
190
- );
191
- installedResources.push(result);
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 };
192
124
  }
125
+ return { target, strategy: "regenerable", isFrozen: false };
193
126
  }
194
- return {
195
- resources: installedResources,
196
- count: installedResources.length
197
- };
198
- }
199
- async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
200
- const sourcePath = resolveSourcePath(
201
- resource.source,
202
- variantDir,
203
- packageRoot
204
- );
205
- const targetPath = path4.join(projectRoot, resource.target);
206
- let content;
207
- if (resource.template) {
208
- const templateContent = await loadTemplateFile(sourcePath);
209
- content = renderTemplate(templateContent, data);
210
- } else {
211
- content = await fs5.readFile(sourcePath, "utf-8");
127
+ if (FROZEN_FILES.has(relPath)) {
128
+ return {
129
+ target: path2.posix.join(TEAMIX_DIR, relPath),
130
+ strategy: "frozen",
131
+ isFrozen: true
132
+ };
212
133
  }
213
- await writeFileSafe(targetPath, content);
214
- const hash = computeHash(content);
215
- logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
216
134
  return {
217
- id: resource.id,
218
- target: resource.target,
219
- hash,
220
- strategy: resource.updateStrategy
135
+ target: path2.posix.join(TEAMIX_DIR, relPath),
136
+ strategy: "regenerable",
137
+ isFrozen: false
221
138
  };
222
139
  }
223
- async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
224
- const sourcePath = resolveSourcePath(
225
- resource.source,
226
- variantDir,
227
- packageRoot
228
- );
229
- const targetDir = path4.join(projectRoot, resource.target);
230
- const results = [];
231
- await ensureDir(targetDir);
232
- const entries = await walkDir(sourcePath);
233
- for (const entry of entries) {
234
- const relPath = path4.relative(sourcePath, entry);
235
- let targetFile = path4.join(targetDir, relPath);
236
- if (resource.template && targetFile.endsWith(".hbs")) {
237
- targetFile = targetFile.slice(0, -4);
238
- }
239
- let content;
240
- if (resource.template && entry.endsWith(".hbs")) {
241
- const templateContent = await loadTemplateFile(entry);
242
- content = renderTemplate(templateContent, data);
243
- } else {
244
- content = await fs5.readFile(entry, "utf-8");
245
- }
246
- await writeFileSafe(targetFile, content);
247
- const hash = computeHash(content);
248
- const targetRel = path4.relative(projectRoot, targetFile);
249
- results.push({
250
- id: `${resource.id}:${relPath}`,
251
- target: targetRel,
252
- hash,
253
- strategy: resource.updateStrategy
254
- });
255
- logger.debug(` Written: ${targetRel}`);
256
- }
257
- return results;
258
- }
259
140
 
260
141
  // src/core/state.ts
261
- import * as path5 from "path";
262
- import { validateConfig, validateInstalled } from "@teamix-evo/registry";
263
- var TEAMIX_DIR = ".teamix-evo";
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";
264
150
  var CONFIG_FILE = "config.json";
265
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";
266
156
  function getTeamixDir(projectRoot) {
267
- return path5.join(projectRoot, TEAMIX_DIR);
157
+ return path3.join(projectRoot, TEAMIX_DIR2);
268
158
  }
269
159
  async function ensureTeamixDir(projectRoot) {
270
160
  const dir = getTeamixDir(projectRoot);
@@ -272,7 +162,7 @@ async function ensureTeamixDir(projectRoot) {
272
162
  return dir;
273
163
  }
274
164
  async function readProjectConfig(projectRoot) {
275
- const configPath = path5.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
165
+ const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
276
166
  const raw = await readFileOrNull(configPath);
277
167
  if (raw === null) return null;
278
168
  try {
@@ -289,12 +179,12 @@ async function readProjectConfig(projectRoot) {
289
179
  }
290
180
  }
291
181
  async function writeProjectConfig(projectRoot, config) {
292
- const configPath = path5.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
182
+ const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
293
183
  await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
294
184
  logger.debug(`Wrote config \u2192 ${configPath}`);
295
185
  }
296
186
  async function readInstalledManifest(projectRoot) {
297
- const manifestPath = path5.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
187
+ const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
298
188
  const raw = await readFileOrNull(manifestPath);
299
189
  if (raw === null) return null;
300
190
  try {
@@ -311,90 +201,94 @@ async function readInstalledManifest(projectRoot) {
311
201
  }
312
202
  }
313
203
  async function writeInstalledManifest(projectRoot, manifest) {
314
- const manifestPath = path5.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
204
+ const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
315
205
  await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
316
206
  logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
317
207
  }
318
-
319
- // src/core/design-init.ts
320
- var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
321
- async function runDesignInit(options) {
322
- const { projectRoot, variant, tailwind, ide } = options;
323
- const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
324
- await ensureTeamixDir(projectRoot);
325
- const existingConfig = await readProjectConfig(projectRoot);
326
- if (existingConfig?.packages?.design) {
327
- return {
328
- status: "already-initialized",
329
- existingVariant: existingConfig.packages.design.variant
330
- };
331
- }
332
- const { manifest, data, variantDir, packageRoot } = await loadVariantData(
333
- packageName,
334
- variant
208
+ async function readDesignPackLock(projectRoot) {
209
+ const lockPath = path3.join(
210
+ projectRoot,
211
+ TEAMIX_DIR2,
212
+ DESIGN_DIR,
213
+ DESIGN_LOCK_FILE
335
214
  );
336
- const result = await installResources({
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(
337
241
  projectRoot,
338
- manifest,
339
- data,
340
- variantDir,
341
- packageRoot
342
- });
343
- const config = {
344
- $schema: "https://teamix-evo.dev/schema/config/v1.json",
345
- schemaVersion: 1,
346
- ide,
347
- packages: {
348
- design: {
349
- variant,
350
- version: manifest.version,
351
- tailwind
352
- }
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;
353
254
  }
354
- };
355
- await writeProjectConfig(projectRoot, config);
356
- const installedManifest = {
357
- schemaVersion: 1,
358
- installed: [
359
- {
360
- package: packageName,
361
- variant,
362
- version: manifest.version,
363
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
364
- resources: result.resources
365
- }
366
- ]
367
- };
368
- await writeInstalledManifest(projectRoot, installedManifest);
369
- return {
370
- status: "installed",
371
- packageName,
372
- variant,
373
- version: manifest.version,
374
- tailwind,
375
- count: result.count,
376
- resources: result.resources
377
- };
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}`);
378
272
  }
379
273
 
380
274
  // src/core/skills-client.ts
381
- import * as path6 from "path";
382
- import * as fs6 from "fs/promises";
383
- import { createRequire as createRequire2 } from "module";
275
+ import * as path4 from "path";
276
+ import * as fs2 from "fs/promises";
277
+ import { createRequire } from "module";
384
278
  import { loadSkillsPackageManifest } from "@teamix-evo/registry";
385
- var require3 = createRequire2(import.meta.url);
386
- function resolvePackageRoot2(packageName) {
387
- const pkgJsonPath = require3.resolve(`${packageName}/package.json`);
388
- return path6.dirname(pkgJsonPath);
279
+ var require2 = createRequire(import.meta.url);
280
+ function resolvePackageRoot(packageName) {
281
+ const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
282
+ return path4.dirname(pkgJsonPath);
389
283
  }
390
284
  async function loadSkillsData(packageName) {
391
- const packageRoot = resolvePackageRoot2(packageName);
285
+ const packageRoot = resolvePackageRoot(packageName);
392
286
  logger.debug(`Resolved skills package root: ${packageRoot}`);
393
287
  const manifest = await loadSkillsPackageManifest(packageRoot);
394
288
  let data = {};
395
- const dataPath = path6.join(packageRoot, "_data.json");
289
+ const dataPath = path4.join(packageRoot, "_data.json");
396
290
  try {
397
- const raw = await fs6.readFile(dataPath, "utf-8");
291
+ const raw = await fs2.readFile(dataPath, "utf-8");
398
292
  data = JSON.parse(raw);
399
293
  } catch (err) {
400
294
  if (err.code !== "ENOENT") {
@@ -406,13 +300,13 @@ async function loadSkillsData(packageName) {
406
300
  }
407
301
 
408
302
  // src/core/skills-installer.ts
409
- import * as path9 from "path";
410
- import * as fs7 from "fs/promises";
303
+ import * as path8 from "path";
304
+ import * as fs5 from "fs/promises";
411
305
  import { replaceManagedRegion } from "@teamix-evo/registry";
412
306
 
413
307
  // src/ide/QoderAdapter.ts
414
308
  import * as os from "os";
415
- import * as path7 from "path";
309
+ import * as path5 from "path";
416
310
  var QoderAdapter = class {
417
311
  kind = "qoder";
418
312
  name = "qoder";
@@ -423,14 +317,14 @@ var QoderAdapter = class {
423
317
  return true;
424
318
  }
425
319
  getSkillTargetDir(skillName, scope, projectRoot) {
426
- const base = scope === "global" ? path7.join(os.homedir(), ".qoder") : path7.join(projectRoot ?? this.getProjectRoot(), ".qoder");
427
- return path7.join(base, "skills", skillName);
320
+ const base = scope === "global" ? path5.join(os.homedir(), ".qoder") : path5.join(projectRoot ?? this.getProjectRoot(), ".qoder");
321
+ return path5.join(base, "skills", skillName);
428
322
  }
429
323
  };
430
324
 
431
325
  // src/ide/ClaudeAdapter.ts
432
326
  import * as os2 from "os";
433
- import * as path8 from "path";
327
+ import * as path6 from "path";
434
328
  var ClaudeAdapter = class {
435
329
  kind = "claude";
436
330
  name = "claude";
@@ -441,8 +335,8 @@ var ClaudeAdapter = class {
441
335
  return Boolean(process.env.CLAUDECODE);
442
336
  }
443
337
  getSkillTargetDir(skillName, scope, projectRoot) {
444
- const base = scope === "global" ? path8.join(os2.homedir(), ".claude") : path8.join(projectRoot ?? this.getProjectRoot(), ".claude");
445
- return path8.join(base, "skills", skillName);
338
+ const base = scope === "global" ? path6.join(os2.homedir(), ".claude") : path6.join(projectRoot ?? this.getProjectRoot(), ".claude");
339
+ return path6.join(base, "skills", skillName);
446
340
  }
447
341
  };
448
342
 
@@ -460,6 +354,57 @@ function getAdapter(kind) {
460
354
  }
461
355
  }
462
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
+
463
408
  // src/core/skills-installer.ts
464
409
  async function installSkills(options) {
465
410
  const { manifest, ides, scope, onlyIds } = options;
@@ -477,60 +422,85 @@ async function installSkills(options) {
477
422
  );
478
423
  continue;
479
424
  }
425
+ const sourceRecords = await writeSkillSource(skill, options);
426
+ installed.push(...sourceRecords);
480
427
  for (const ide of skillIdes) {
481
- const result = await installSkillForIde(skill, ide, scope, options);
482
- installed.push(...result);
428
+ const mirrorRecords = await mirrorSkillToIde(
429
+ skill,
430
+ ide,
431
+ scope,
432
+ options.projectRoot
433
+ );
434
+ installed.push(...mirrorRecords);
483
435
  }
484
436
  }
485
437
  return { resources: installed, count: installed.length };
486
438
  }
487
- async function installSkillForIde(skill, ide, scope, options) {
439
+ async function writeSkillSource(skill, options) {
488
440
  const { data, packageRoot, projectRoot } = options;
489
- const adapter = getAdapter(ide);
490
- const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
491
- const sourceAbs = path9.resolve(packageRoot, skill.source);
492
- const stat2 = await fs7.stat(sourceAbs);
493
- const results = [];
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 = [];
494
445
  if (stat2.isFile()) {
495
- const targetFile = path9.join(targetDir, "SKILL.md");
446
+ const targetFile = path8.join(targetDir, "SKILL.md");
496
447
  const content = await renderSkillContent(sourceAbs, skill, data);
497
448
  await writeFileSafe(targetFile, content);
498
- results.push(makeInstalledRecord(skill, targetFile, content, ide, scope));
499
- logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
500
- return results;
449
+ records.push(makeSourceRecord(skill, targetFile, content));
450
+ logger.debug(` Wrote source: ${targetFile}`);
451
+ return records;
501
452
  }
502
453
  await ensureDir(targetDir);
503
454
  const entries = await walkDir(sourceAbs);
504
455
  for (const entry of entries) {
505
- const rel2 = path9.relative(sourceAbs, entry);
506
- let targetFile = path9.join(targetDir, rel2);
456
+ const rel2 = path8.relative(sourceAbs, entry);
457
+ let targetFile = path8.join(targetDir, rel2);
507
458
  if (skill.template && targetFile.endsWith(".hbs")) {
508
459
  targetFile = targetFile.slice(0, -4);
509
460
  }
510
- let content;
511
- if (skill.template && entry.endsWith(".hbs")) {
512
- const tpl = await loadTemplateFile(entry);
513
- content = renderTemplate(tpl, { ...data, skill });
514
- } else {
515
- content = await fs7.readFile(entry, "utf-8");
516
- }
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");
517
480
  await writeFileSafe(targetFile, content);
518
- results.push(
519
- makeInstalledRecord(skill, targetFile, content, ide, scope, rel2)
520
- );
521
- logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
481
+ records.push(makeMirrorRecord(skill, targetFile, content, ide, scope, rel2));
482
+ logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
522
483
  }
523
- return results;
484
+ return records;
524
485
  }
525
486
  async function renderSkillContent(sourceAbs, skill, data) {
526
487
  if (skill.template ?? sourceAbs.endsWith(".hbs")) {
527
488
  const tpl = await loadTemplateFile(sourceAbs);
528
489
  return renderTemplate(tpl, { ...data, skill });
529
490
  }
530
- return fs7.readFile(sourceAbs, "utf-8");
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
+ };
531
501
  }
532
- function makeInstalledRecord(skill, targetAbs, content, ide, scope, rel2) {
533
- const id = rel2 ? `${skill.id}:${rel2}` : skill.id;
502
+ function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
503
+ const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
534
504
  return {
535
505
  id,
536
506
  target: targetAbs,
@@ -541,55 +511,46 @@ function makeInstalledRecord(skill, targetAbs, content, ide, scope, rel2) {
541
511
  };
542
512
  }
543
513
  async function updateSkills(options) {
544
- const { manifest, ides, scope, installed, projectRoot, data, packageRoot } = options;
514
+ const { manifest, ides, scope, projectRoot } = options;
545
515
  const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
546
516
  const updated = [];
547
- const installedMap = /* @__PURE__ */ new Map();
548
- for (const r of installed) {
549
- installedMap.set(installedKey(r), r);
550
- }
551
517
  for (const skill of manifest.skills) {
552
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);
553
526
  for (const ide of skillIdes) {
554
- const records = await updateSkillForIde(
527
+ const mirrorRecords = await mirrorSkillToIde(
555
528
  skill,
556
529
  ide,
557
530
  scope,
558
- data,
559
- packageRoot,
560
- projectRoot,
561
- installedMap,
562
- summary
531
+ projectRoot
563
532
  );
564
- updated.push(...records);
533
+ updated.push(...mirrorRecords);
565
534
  }
566
535
  }
567
536
  return { resources: updated, summary };
568
537
  }
569
- async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRoot, installedMap, summary) {
570
- const adapter = getAdapter(ide);
571
- const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
572
- const sourceAbs = path9.resolve(packageRoot, skill.source);
573
- const stat2 = await fs7.stat(sourceAbs);
574
- const records = [];
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);
575
543
  if (!stat2.isFile()) {
576
- const entries = await walkDir(sourceAbs);
577
544
  await ensureDir(targetDir);
545
+ const entries = await walkDir(sourceAbs);
546
+ const records = [];
578
547
  for (const entry of entries) {
579
- const rel2 = path9.relative(sourceAbs, entry);
580
- let targetFile2 = path9.join(targetDir, rel2);
548
+ const rel2 = path8.relative(sourceAbs, entry);
549
+ let targetFile2 = path8.join(targetDir, rel2);
581
550
  if (skill.template && targetFile2.endsWith(".hbs")) {
582
551
  targetFile2 = targetFile2.slice(0, -4);
583
552
  }
584
- let content;
585
- if (skill.template && entry.endsWith(".hbs")) {
586
- content = renderTemplate(await loadTemplateFile(entry), {
587
- ...data,
588
- skill
589
- });
590
- } else {
591
- content = await fs7.readFile(entry, "utf-8");
592
- }
553
+ const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
593
554
  const exists2 = await fileExists(targetFile2);
594
555
  if (exists2) {
595
556
  await backupFile(targetFile2, projectRoot);
@@ -598,30 +559,23 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
598
559
  summary.created++;
599
560
  }
600
561
  await writeFileSafe(targetFile2, content);
601
- records.push(
602
- makeInstalledRecord(skill, targetFile2, content, ide, scope, rel2)
603
- );
562
+ const relWritten = path8.relative(targetDir, targetFile2);
563
+ records.push(makeSourceRecord(skill, targetFile2, content, relWritten));
604
564
  }
605
565
  return records;
606
566
  }
607
- const targetFile = path9.join(targetDir, "SKILL.md");
567
+ const targetFile = path8.join(targetDir, "SKILL.md");
608
568
  const newContent = await renderSkillContent(sourceAbs, skill, data);
609
569
  const exists = await fileExists(targetFile);
610
- const installedKeyStr = `${skill.id}|${ide}|${scope}`;
611
- const prior = installedMap.get(installedKeyStr);
612
570
  if (skill.updateStrategy === "frozen") {
613
571
  if (exists) {
614
572
  summary.skipped++;
615
- return [
616
- prior ?? makeInstalledRecord(skill, targetFile, newContent, ide, scope)
617
- ];
573
+ const current2 = await readFileOrNull(targetFile) ?? newContent;
574
+ return [makeSourceRecord(skill, targetFile, current2)];
618
575
  }
619
576
  await writeFileSafe(targetFile, newContent);
620
577
  summary.created++;
621
- records.push(
622
- makeInstalledRecord(skill, targetFile, newContent, ide, scope)
623
- );
624
- return records;
578
+ return [makeSourceRecord(skill, targetFile, newContent)];
625
579
  }
626
580
  if (skill.updateStrategy === "regenerable" || !exists) {
627
581
  if (exists) {
@@ -631,15 +585,11 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
631
585
  summary.created++;
632
586
  }
633
587
  await writeFileSafe(targetFile, newContent);
634
- records.push(
635
- makeInstalledRecord(skill, targetFile, newContent, ide, scope)
636
- );
637
- return records;
588
+ return [makeSourceRecord(skill, targetFile, newContent)];
638
589
  }
639
590
  const current = await readFileOrNull(targetFile);
640
- let updated = current ?? newContent;
641
- const regionIds = skill.managedRegions ?? [];
642
- for (const regionId of regionIds) {
591
+ let merged = current ?? newContent;
592
+ for (const regionId of skill.managedRegions ?? []) {
643
593
  const re = new RegExp(
644
594
  `<!-- teamix-evo:managed:start id="${escapeRegExp(
645
595
  regionId
@@ -651,7 +601,7 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
651
601
  if (match) {
652
602
  const region = match[1].replace(/^\n/, "").replace(/\n$/, "");
653
603
  try {
654
- updated = replaceManagedRegion(updated, regionId, region);
604
+ merged = replaceManagedRegion(merged, regionId, region);
655
605
  } catch {
656
606
  logger.warn(
657
607
  `Managed region "${regionId}" not found in ${targetFile}. Skipped.`
@@ -660,22 +610,57 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
660
610
  }
661
611
  }
662
612
  await backupFile(targetFile, projectRoot);
663
- await writeFileSafe(targetFile, updated);
613
+ await writeFileSafe(targetFile, merged);
664
614
  summary.managed++;
665
- records.push(makeInstalledRecord(skill, targetFile, updated, ide, scope));
666
- return records;
667
- }
668
- function installedKey(r) {
669
- return `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
615
+ return [makeSourceRecord(skill, targetFile, merged)];
670
616
  }
671
617
  function escapeRegExp(str) {
672
618
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
673
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
+ }
674
659
  async function removeSkillFiles(records) {
675
660
  const removed = [];
676
661
  for (const r of records) {
677
662
  try {
678
- await fs7.unlink(r.target);
663
+ await fs5.unlink(r.target);
679
664
  removed.push(r.target);
680
665
  } catch (err) {
681
666
  if (err.code !== "ENOENT") {
@@ -683,11 +668,11 @@ async function removeSkillFiles(records) {
683
668
  }
684
669
  }
685
670
  }
686
- const parents = new Set(records.map((r) => path9.dirname(r.target)));
671
+ const parents = new Set(records.map((r) => path8.dirname(r.target)));
687
672
  for (const dir of parents) {
688
673
  try {
689
- const entries = await fs7.readdir(dir);
690
- if (entries.length === 0) await fs7.rmdir(dir);
674
+ const entries = await fs5.readdir(dir);
675
+ if (entries.length === 0) await fs5.rmdir(dir);
691
676
  } catch {
692
677
  }
693
678
  }
@@ -717,6 +702,7 @@ async function runSkillsAdd(options) {
717
702
  throw new Error("Scope must be specified (project | global).");
718
703
  }
719
704
  const { manifest, data, packageRoot } = await loadSkillsData(packageName);
705
+ const currentDesignVariant = await readDesignVariant(projectRoot);
720
706
  if (isIncremental) {
721
707
  const known = new Set(manifest.skills.map((s) => s.id));
722
708
  const unknown = requestedNames.filter((n) => !known.has(n));
@@ -731,9 +717,13 @@ async function runSkillsAdd(options) {
731
717
  const existingPkg = existingInstalled?.installed.find(
732
718
  (p) => p.package === packageName
733
719
  );
734
- const existingSkillIds = new Set(
735
- (existingPkg?.resources ?? []).map((r) => r.id.split(":")[0])
736
- );
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
+ ]);
737
727
  let onlyIds;
738
728
  let skippedSkillIds;
739
729
  if (isIncremental) {
@@ -741,7 +731,22 @@ async function runSkillsAdd(options) {
741
731
  onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
742
732
  } else {
743
733
  skippedSkillIds = [];
744
- onlyIds = manifest.skills.map((s) => s.id);
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);
745
750
  }
746
751
  if (isIncremental && onlyIds.length === 0) {
747
752
  return {
@@ -779,6 +784,7 @@ async function runSkillsAdd(options) {
779
784
  scope
780
785
  };
781
786
  await writeProjectConfig(projectRoot, config);
787
+ const installedAt = (/* @__PURE__ */ new Date()).toISOString();
782
788
  const installedManifest = existingInstalled ?? {
783
789
  schemaVersion: 1,
784
790
  installed: []
@@ -794,12 +800,29 @@ async function runSkillsAdd(options) {
794
800
  package: packageName,
795
801
  variant: FLAT_VARIANT,
796
802
  version: manifest.version,
797
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
803
+ installedAt,
798
804
  resources: mergedResources
799
805
  };
800
806
  if (idx >= 0) installedManifest.installed[idx] = entry;
801
807
  else installedManifest.installed.push(entry);
802
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);
803
826
  return {
804
827
  status: "installed",
805
828
  packageName,
@@ -821,12 +844,208 @@ function mergeInstalledResources(existing, next) {
821
844
  return [...map.values()];
822
845
  }
823
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
+
824
1041
  // src/core/ui-init.ts
825
1042
  var DEFAULT_UI_ALIASES = {
826
1043
  components: "src/components/ui",
827
1044
  hooks: "src/hooks",
828
1045
  utils: "src/lib/utils",
829
- lib: "src/lib"
1046
+ lib: "src/lib",
1047
+ business: "src/components/business",
1048
+ templates: "src/templates"
830
1049
  };
831
1050
  var DEFAULT_UI_ICON_LIBRARY = "lucide";
832
1051
  async function runUiInit(options) {
@@ -841,7 +1060,9 @@ async function runUiInit(options) {
841
1060
  components: options.aliases?.components ?? DEFAULT_UI_ALIASES.components,
842
1061
  hooks: options.aliases?.hooks ?? DEFAULT_UI_ALIASES.hooks,
843
1062
  utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
844
- lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib
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
845
1066
  };
846
1067
  const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
847
1068
  const tsx = options.tsx ?? true;
@@ -872,22 +1093,22 @@ async function runUiInit(options) {
872
1093
 
873
1094
  // src/core/ui-client.ts
874
1095
  import * as path10 from "path";
875
- import * as fs8 from "fs/promises";
1096
+ import * as fs7 from "fs/promises";
876
1097
  import { createRequire as createRequire3 } from "module";
877
1098
  import { loadUiPackageManifest } from "@teamix-evo/registry";
878
1099
  var require4 = createRequire3(import.meta.url);
879
- function resolvePackageRoot3(packageName) {
1100
+ function resolvePackageRoot2(packageName) {
880
1101
  const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
881
1102
  return path10.dirname(pkgJsonPath);
882
1103
  }
883
1104
  async function loadUiData(packageName) {
884
- const packageRoot = resolvePackageRoot3(packageName);
1105
+ const packageRoot = resolvePackageRoot2(packageName);
885
1106
  logger.debug(`Resolved ui package root: ${packageRoot}`);
886
1107
  const manifest = await loadUiPackageManifest(packageRoot);
887
1108
  let data = {};
888
1109
  const dataPath = path10.join(packageRoot, "_data.json");
889
1110
  try {
890
- const raw = await fs8.readFile(dataPath, "utf-8");
1111
+ const raw = await fs7.readFile(dataPath, "utf-8");
891
1112
  data = JSON.parse(raw);
892
1113
  } catch (err) {
893
1114
  if (err.code !== "ENOENT") {
@@ -900,7 +1121,7 @@ async function loadUiData(packageName) {
900
1121
 
901
1122
  // src/core/ui-installer.ts
902
1123
  import * as path11 from "path";
903
- import * as fs9 from "fs/promises";
1124
+ import * as fs8 from "fs/promises";
904
1125
  import { resolveUiEntryOrder } from "@teamix-evo/registry";
905
1126
 
906
1127
  // src/utils/transform-imports.ts
@@ -918,10 +1139,19 @@ function rewriteImports(source, aliases) {
918
1139
  if (!aliasKey) return full;
919
1140
  const alias = aliases[aliasKey];
920
1141
  const normalized = aliasToImportPath(alias);
921
- return `${quote}${normalized}${rest ?? ""}${quote}`;
1142
+ const flatRest = flattenRestPath(rest);
1143
+ return `${quote}${normalized}${flatRest}${quote}`;
922
1144
  }
923
1145
  );
924
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
+ }
925
1155
  function aliasToImportPath(alias) {
926
1156
  const trimmed = alias.replace(/^\.\//, "").replace(/\/$/, "");
927
1157
  if (trimmed.startsWith("src/")) {
@@ -965,7 +1195,7 @@ async function installUiEntries(options) {
965
1195
  continue;
966
1196
  }
967
1197
  const sourceAbs = path11.resolve(packageRoot, file.source);
968
- const raw = await fs9.readFile(sourceAbs, "utf-8");
1198
+ const raw = await fs8.readFile(sourceAbs, "utf-8");
969
1199
  const transformed = rewriteImports(raw, aliases);
970
1200
  await writeFileSafe(targetAbs, transformed);
971
1201
  written++;
@@ -979,7 +1209,7 @@ async function installUiEntries(options) {
979
1209
  }
980
1210
  if (entry.meta) {
981
1211
  const metaSourceAbs = path11.resolve(packageRoot, entry.meta);
982
- const metaContent = await fs9.readFile(metaSourceAbs, "utf-8");
1212
+ const metaContent = await fs8.readFile(metaSourceAbs, "utf-8");
983
1213
  const metaTargetAbs = path11.join(
984
1214
  projectRoot,
985
1215
  DESIGN_COMPONENTS_DIR,
@@ -1021,7 +1251,7 @@ async function removeUiFiles(records) {
1021
1251
  const removed = [];
1022
1252
  for (const r of records) {
1023
1253
  try {
1024
- await fs9.unlink(r.target);
1254
+ await fs8.unlink(r.target);
1025
1255
  removed.push(r.target);
1026
1256
  } catch (err) {
1027
1257
  if (err.code !== "ENOENT") {
@@ -1032,8 +1262,8 @@ async function removeUiFiles(records) {
1032
1262
  const parents = new Set(records.map((r) => path11.dirname(r.target)));
1033
1263
  for (const dir of parents) {
1034
1264
  try {
1035
- const entries = await fs9.readdir(dir);
1036
- if (entries.length === 0) await fs9.rmdir(dir);
1265
+ const entries = await fs8.readdir(dir);
1266
+ if (entries.length === 0) await fs8.rmdir(dir);
1037
1267
  } catch {
1038
1268
  }
1039
1269
  }
@@ -1139,6 +1369,130 @@ async function runUiList(options) {
1139
1369
  entries
1140
1370
  };
1141
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
+ }
1142
1496
  export {
1143
1497
  DEFAULT_UI_ALIASES,
1144
1498
  DEFAULT_UI_ICON_LIBRARY,
@@ -1159,6 +1513,7 @@ export {
1159
1513
  runUiAdd,
1160
1514
  runUiInit,
1161
1515
  runUiList,
1516
+ syncSkillsToIdes,
1162
1517
  updateSkills,
1163
1518
  writeInstalledManifest,
1164
1519
  writeProjectConfig