teamix-evo 0.13.4 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  // src/core/tokens-init.ts
2
- import * as path9 from "path";
3
- import * as fs6 from "fs/promises";
2
+ import * as path8 from "path";
3
+ import * as fs7 from "fs/promises";
4
4
  import {
5
5
  loadTokensPackageManifest,
6
6
  getVariantEntry
@@ -96,6 +96,7 @@ import {
96
96
  validateSkillsLock,
97
97
  TokensPackLockSchema
98
98
  } from "@teamix-evo/registry";
99
+ import * as fs2 from "fs/promises";
99
100
 
100
101
  // src/utils/error.ts
101
102
  function getErrorMessage(err) {
@@ -113,9 +114,11 @@ var TEAMIX_DIR = ".teamix-evo";
113
114
  var CONFIG_FILE = "config.json";
114
115
  var MANIFEST_FILE = "manifest.json";
115
116
  var TOKENS_LOCK_FILE = "tokens-lock.json";
116
- var SKILLS_DIR = "skills-source";
117
- var LEGACY_SKILLS_DIR = "skills";
118
- var SKILLS_LOCK_FILE = "manifest.lock.json";
117
+ var SKILLS_LOCK_FILE = "skills-lock.json";
118
+ var LEGACY_SKILLS_LOCK_PATHS = [
119
+ path2.join("skills-source", "manifest.lock.json"),
120
+ path2.join("skills", "manifest.lock.json")
121
+ ];
119
122
  function getTeamixDir(projectRoot) {
120
123
  return path2.join(projectRoot, TEAMIX_DIR);
121
124
  }
@@ -162,12 +165,40 @@ async function readInstalledManifest(projectRoot) {
162
165
  );
163
166
  }
164
167
  const result = validateInstalled(data);
165
- if (!result.success) {
166
- throw new Error(
167
- `Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
168
+ if (result.success) {
169
+ return result.data;
170
+ }
171
+ const migrated = migrateManifest(data);
172
+ const retryResult = validateInstalled(migrated);
173
+ if (retryResult.success) {
174
+ logger.warn(
175
+ "Migrated legacy manifest.json \u2014 will be overwritten on next install."
168
176
  );
177
+ return retryResult.data;
169
178
  }
170
- return result.data;
179
+ throw new Error(
180
+ `Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
181
+ );
182
+ }
183
+ function migrateManifest(data) {
184
+ if (typeof data !== "object" || data === null) return data;
185
+ const obj = data;
186
+ if (!("schemaVersion" in obj)) obj.schemaVersion = 1;
187
+ if (!Array.isArray(obj.installed)) obj.installed = [];
188
+ for (const pkg of obj.installed) {
189
+ if (typeof pkg !== "object" || pkg === null) continue;
190
+ if (!pkg.variant) pkg.variant = "_flat";
191
+ if (!pkg.installedAt) pkg.installedAt = "";
192
+ if (!pkg.version) pkg.version = "0.0.0";
193
+ if (Array.isArray(pkg.resources)) {
194
+ for (const res of pkg.resources) {
195
+ if (typeof res !== "object" || res === null) continue;
196
+ if (!res.hash) res.hash = "sha256:legacy-unknown";
197
+ if (!res.strategy) res.strategy = "frozen";
198
+ }
199
+ }
200
+ }
201
+ return obj;
171
202
  }
172
203
  async function writeInstalledManifest(projectRoot, manifest) {
173
204
  const manifestPath = path2.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
@@ -194,51 +225,61 @@ async function readTokensVariant(projectRoot) {
194
225
  const lock = await readTokensLock(projectRoot);
195
226
  return lock?.variant.name ?? null;
196
227
  }
197
- function getSkillsSourceDir(projectRoot, skillName) {
198
- const base = path2.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
199
- return skillName ? path2.join(base, skillName) : base;
200
- }
201
- function getLegacySkillsSourceDir(projectRoot) {
202
- return path2.join(projectRoot, TEAMIX_DIR, LEGACY_SKILLS_DIR);
203
- }
204
228
  async function readSkillsLock(projectRoot) {
205
- const lockPath = path2.join(
206
- projectRoot,
207
- TEAMIX_DIR,
208
- SKILLS_DIR,
209
- SKILLS_LOCK_FILE
210
- );
229
+ const lockPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
211
230
  const raw = await readFileOrNull(lockPath);
212
231
  if (raw === null) return null;
213
232
  try {
214
233
  const data = JSON.parse(raw);
215
234
  const result = validateSkillsLock(data);
216
235
  if (!result.success) {
217
- logger.warn(`Invalid skills manifest.lock.json: ${result.error}`);
236
+ logger.warn(`Invalid skills-lock.json: ${result.error}`);
218
237
  return null;
219
238
  }
220
239
  return result.data;
221
240
  } catch (err) {
222
241
  logger.warn(
223
- `Failed to parse skills manifest.lock.json: ${getErrorMessage(err)}`
242
+ `Failed to parse skills-lock.json: ${getErrorMessage(err)}`
224
243
  );
225
244
  return null;
226
245
  }
227
246
  }
228
247
  async function writeSkillsLock(projectRoot, lock) {
229
- const lockPath = path2.join(
230
- projectRoot,
231
- TEAMIX_DIR,
232
- SKILLS_DIR,
233
- SKILLS_LOCK_FILE
234
- );
248
+ const lockPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
235
249
  await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
236
250
  logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
237
251
  }
252
+ async function migrateSkillsLockLocation(projectRoot) {
253
+ const newPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
254
+ try {
255
+ await fs2.access(newPath);
256
+ } catch {
257
+ for (const legacyRel of LEGACY_SKILLS_LOCK_PATHS) {
258
+ const legacyPath = path2.join(projectRoot, TEAMIX_DIR, legacyRel);
259
+ try {
260
+ await fs2.access(legacyPath);
261
+ await ensureDir(path2.dirname(newPath));
262
+ await fs2.rename(legacyPath, newPath);
263
+ logger.debug(`Migrated skills lock: ${legacyRel} \u2192 ${SKILLS_LOCK_FILE}`);
264
+ break;
265
+ } catch {
266
+ continue;
267
+ }
268
+ }
269
+ }
270
+ for (const staleDir of ["skills-source", "skills"]) {
271
+ const abs = path2.join(projectRoot, TEAMIX_DIR, staleDir);
272
+ try {
273
+ await fs2.rm(abs, { recursive: true, force: true });
274
+ logger.debug(`Cleaned up stale dir: .teamix-evo/${staleDir}/`);
275
+ } catch {
276
+ }
277
+ }
278
+ }
238
279
 
239
280
  // src/core/skills-client.ts
240
281
  import * as path3 from "path";
241
- import * as fs2 from "fs/promises";
282
+ import * as fs3 from "fs/promises";
242
283
  import { createRequire } from "module";
243
284
  import { loadSkillsPackageManifest } from "@teamix-evo/registry";
244
285
  var require2 = createRequire(import.meta.url);
@@ -253,7 +294,7 @@ async function loadSkillsData(packageName) {
253
294
  let data = {};
254
295
  const dataPath = path3.join(packageRoot, "_data.json");
255
296
  try {
256
- const raw = await fs2.readFile(dataPath, "utf-8");
297
+ const raw = await fs3.readFile(dataPath, "utf-8");
257
298
  data = JSON.parse(raw);
258
299
  } catch (err) {
259
300
  if (err.code !== "ENOENT") {
@@ -266,7 +307,7 @@ async function loadSkillsData(packageName) {
266
307
 
267
308
  // src/core/skills-installer.ts
268
309
  import * as path7 from "path";
269
- import * as fs5 from "fs/promises";
310
+ import * as fs6 from "fs/promises";
270
311
  import {
271
312
  replaceManagedRegion,
272
313
  hasManagedRegion,
@@ -326,7 +367,7 @@ function getAdapter(kind) {
326
367
 
327
368
  // src/utils/template.ts
328
369
  import Handlebars from "handlebars";
329
- import * as fs3 from "fs/promises";
370
+ import * as fs4 from "fs/promises";
330
371
  Handlebars.registerHelper("lowercase", (str) => {
331
372
  return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
332
373
  });
@@ -349,12 +390,12 @@ function renderTemplate(templateContent, data) {
349
390
  return compiled(data);
350
391
  }
351
392
  async function loadTemplateFile(filePath) {
352
- return fs3.readFile(filePath, "utf-8");
393
+ return fs4.readFile(filePath, "utf-8");
353
394
  }
354
395
 
355
396
  // src/utils/path.ts
356
397
  import * as path6 from "path";
357
- import * as fs4 from "fs/promises";
398
+ import * as fs5 from "fs/promises";
358
399
  import { createRequire as createRequire2 } from "module";
359
400
  var require3 = createRequire2(import.meta.url);
360
401
  function resolveSourcePath(source, variantDir, packageRoot) {
@@ -365,7 +406,7 @@ function resolveSourcePath(source, variantDir, packageRoot) {
365
406
  }
366
407
  async function walkDir(dir, skipDirs) {
367
408
  const files = [];
368
- const entries = await fs4.readdir(dir, { withFileTypes: true });
409
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
369
410
  for (const entry of entries) {
370
411
  const fullPath = path6.join(dir, entry.name);
371
412
  if (entry.isDirectory()) {
@@ -381,10 +422,13 @@ function resolveTokensPackageRoot(packageName) {
381
422
  const pkgJson = require3.resolve(`${packageName}/package.json`);
382
423
  return path6.dirname(pkgJson);
383
424
  }
425
+ function resolveResourceTarget(projectRoot, target) {
426
+ return path6.isAbsolute(target) ? target : path6.resolve(projectRoot, target);
427
+ }
384
428
 
385
429
  // src/core/skills-installer.ts
386
430
  async function installSkills(options) {
387
- await migrateLegacySkillsSourceDir(options.projectRoot);
431
+ await migrateSkillsLockLocation(options.projectRoot);
388
432
  const { manifest, ides, scope, onlyIds } = options;
389
433
  const installed = [];
390
434
  const targets = manifest.skills.filter(
@@ -400,75 +444,61 @@ async function installSkills(options) {
400
444
  );
401
445
  continue;
402
446
  }
403
- const sourceRecords = await writeSkillSource(skill, options);
404
- installed.push(...sourceRecords);
447
+ const rendered = await renderSkillFiles(skill, options);
405
448
  for (const ide of skillIdes) {
406
- const mirrorRecords = await mirrorSkillToIde(
449
+ const records = await writeRenderedToIde(
407
450
  skill,
451
+ rendered,
408
452
  ide,
409
453
  scope,
410
454
  options.projectRoot
411
455
  );
412
- installed.push(...mirrorRecords);
456
+ installed.push(...records);
413
457
  }
414
458
  }
415
459
  return { resources: installed, count: installed.length };
416
460
  }
417
- async function writeSkillSource(skill, options) {
418
- const { data, packageRoot, projectRoot } = options;
461
+ async function renderSkillFiles(skill, options) {
462
+ const { data, packageRoot } = options;
419
463
  const sourceAbs = path7.resolve(packageRoot, skill.source);
420
- const targetDir = getSkillsSourceDir(projectRoot, skill.name);
421
- const stat3 = await fs5.stat(sourceAbs);
422
- const records = [];
464
+ const stat3 = await fs6.stat(sourceAbs);
465
+ const rendered = /* @__PURE__ */ new Map();
423
466
  if (stat3.isFile()) {
424
- const targetFile = path7.join(targetDir, "SKILL.md");
425
467
  const content = await renderSkillContent(sourceAbs, skill, data);
426
- await writeFileSafe(targetFile, content);
427
- records.push(makeSourceRecord(skill, targetFile, content));
428
- logger.debug(` Wrote source: ${targetFile}`);
429
- return records;
468
+ rendered.set("SKILL.md", content);
469
+ return rendered;
430
470
  }
431
- await ensureDir(targetDir);
432
471
  const entries = await walkDir(sourceAbs);
433
472
  for (const entry of entries) {
434
- const rel2 = path7.relative(sourceAbs, entry);
435
- let targetFile = path7.join(targetDir, rel2);
436
- if (skill.template && targetFile.endsWith(".hbs")) {
437
- targetFile = targetFile.slice(0, -4);
473
+ let rel2 = path7.relative(sourceAbs, entry);
474
+ if (skill.template && rel2.endsWith(".hbs")) {
475
+ rel2 = rel2.slice(0, -4);
438
476
  }
439
- const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
440
- await writeFileSafe(targetFile, content);
441
- const relWritten = path7.relative(targetDir, targetFile);
442
- records.push(makeSourceRecord(skill, targetFile, content, relWritten));
443
- logger.debug(` Wrote source: ${targetFile}`);
477
+ const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs6.readFile(entry, "utf-8");
478
+ rendered.set(rel2, content);
444
479
  }
445
- return records;
480
+ return rendered;
446
481
  }
447
- async function mirrorSkillToIde(skill, ide, scope, projectRoot) {
448
- const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
482
+ async function writeRenderedToIde(skill, rendered, ide, scope, projectRoot) {
449
483
  const adapter = getAdapter(ide);
450
484
  const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
451
485
  const records = [];
452
- const sourceFiles = await walkDir(sourceDir);
453
486
  await ensureDir(targetDir);
454
- for (const src of sourceFiles) {
455
- const rel2 = path7.relative(sourceDir, src);
487
+ for (const [rel2, sourceContent] of rendered) {
456
488
  const targetFile = path7.join(targetDir, rel2);
457
- const sourceContent = await fs5.readFile(src, "utf-8");
458
489
  const writtenContent = await writeMirrorContent(
459
490
  targetFile,
460
491
  sourceContent,
461
- skill.managedRegions,
462
- src
492
+ skill.managedRegions
463
493
  );
464
494
  records.push(
465
- makeMirrorRecord(skill, targetFile, writtenContent, ide, scope, rel2)
495
+ makeMirrorRecord(skill, projectRoot, targetFile, writtenContent, ide, scope, rel2)
466
496
  );
467
- logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
497
+ logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
468
498
  }
469
499
  return records;
470
500
  }
471
- async function writeMirrorContent(targetFile, sourceContent, managedRegions, sourceFile) {
501
+ async function writeMirrorContent(targetFile, sourceContent, managedRegions) {
472
502
  const existing = await readFileOrNull(targetFile);
473
503
  if (existing === null) {
474
504
  await writeFileSafe(targetFile, sourceContent);
@@ -479,7 +509,7 @@ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sou
479
509
  if (matchedRegions.length === 0) {
480
510
  if (existing !== sourceContent) {
481
511
  logger.warn(
482
- `Mirror drift detected at ${targetFile} \u2014 overwriting from source. Edit ${sourceFile} (not the mirror) and re-run \`npx teamix-evo@latest skills sync\`.`
512
+ `Drift detected at ${targetFile} \u2014 overwriting from upstream. Re-run \`npx teamix-evo@latest skills sync\` after any manual edits.`
483
513
  );
484
514
  await writeFileSafe(targetFile, sourceContent);
485
515
  return sourceContent;
@@ -521,22 +551,13 @@ async function renderSkillContent(sourceAbs, skill, data) {
521
551
  const tpl = await loadTemplateFile(sourceAbs);
522
552
  return renderTemplate(tpl, { ...data, skill });
523
553
  }
524
- return fs5.readFile(sourceAbs, "utf-8");
525
- }
526
- function makeSourceRecord(skill, targetAbs, content, rel2) {
527
- const id = rel2 ? `${skill.id}:source:${rel2}` : `${skill.id}:source`;
528
- return {
529
- id,
530
- target: targetAbs,
531
- hash: computeHash(content),
532
- strategy: skill.updateStrategy
533
- };
554
+ return fs6.readFile(sourceAbs, "utf-8");
534
555
  }
535
- function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
556
+ function makeMirrorRecord(skill, projectRoot, targetAbs, content, ide, scope, rel2) {
536
557
  const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
537
558
  return {
538
559
  id,
539
- target: targetAbs,
560
+ target: path7.relative(projectRoot, targetAbs),
540
561
  hash: computeHash(content),
541
562
  strategy: skill.updateStrategy,
542
563
  ide,
@@ -552,64 +573,41 @@ async function updateSkills(options) {
552
573
  if (idFilter && !idFilter.has(skill.id)) continue;
553
574
  const skillIdes = skill.ides.filter((i) => ides.includes(i));
554
575
  if (skillIdes.length === 0) continue;
555
- const sourceRecords = await rewriteSkillSource(skill, options, summary);
556
- updated.push(...sourceRecords);
576
+ const rendered = await renderSkillFiles(skill, options);
557
577
  for (const ide of skillIdes) {
558
- const mirrorRecords = await mirrorSkillToIde(
578
+ const records = await updateRenderedInIde(
559
579
  skill,
580
+ rendered,
560
581
  ide,
561
582
  scope,
562
- projectRoot
583
+ projectRoot,
584
+ summary
563
585
  );
564
- updated.push(...mirrorRecords);
586
+ updated.push(...records);
565
587
  }
566
588
  }
567
589
  return { resources: updated, summary };
568
590
  }
569
- async function rewriteSkillSource(skill, options, summary) {
570
- const { data, packageRoot, projectRoot } = options;
571
- const sourceAbs = path7.resolve(packageRoot, skill.source);
572
- const targetDir = getSkillsSourceDir(projectRoot, skill.name);
573
- const stat3 = await fs5.stat(sourceAbs);
574
- if (!stat3.isFile()) {
575
- await ensureDir(targetDir);
576
- const entries = await walkDir(sourceAbs);
577
- const records = [];
578
- for (const entry of entries) {
579
- const rel2 = path7.relative(sourceAbs, entry);
580
- let targetFile2 = path7.join(targetDir, rel2);
581
- if (skill.template && targetFile2.endsWith(".hbs")) {
582
- targetFile2 = targetFile2.slice(0, -4);
583
- }
584
- const newContent2 = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
585
- const exists2 = await fileExists(targetFile2);
586
- const written2 = await rewriteSingleFile({
587
- targetFile: targetFile2,
588
- newContent: newContent2,
589
- exists: exists2,
590
- updateStrategy: skill.updateStrategy,
591
- managedRegions: skill.managedRegions,
592
- projectRoot,
593
- summary
594
- });
595
- const relWritten = path7.relative(targetDir, targetFile2);
596
- records.push(makeSourceRecord(skill, targetFile2, written2, relWritten));
597
- }
598
- return records;
591
+ async function updateRenderedInIde(skill, rendered, ide, scope, projectRoot, summary) {
592
+ const adapter = getAdapter(ide);
593
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
594
+ const records = [];
595
+ await ensureDir(targetDir);
596
+ for (const [rel2, newContent] of rendered) {
597
+ const targetFile = path7.join(targetDir, rel2);
598
+ const exists = await fileExists(targetFile);
599
+ const written = await rewriteSingleFile({
600
+ targetFile,
601
+ newContent,
602
+ exists,
603
+ updateStrategy: skill.updateStrategy,
604
+ managedRegions: skill.managedRegions,
605
+ projectRoot,
606
+ summary
607
+ });
608
+ records.push(makeMirrorRecord(skill, projectRoot, targetFile, written, ide, scope, rel2));
599
609
  }
600
- const targetFile = path7.join(targetDir, "SKILL.md");
601
- const newContent = await renderSkillContent(sourceAbs, skill, data);
602
- const exists = await fileExists(targetFile);
603
- const written = await rewriteSingleFile({
604
- targetFile,
605
- newContent,
606
- exists,
607
- updateStrategy: skill.updateStrategy,
608
- managedRegions: skill.managedRegions,
609
- projectRoot,
610
- summary
611
- });
612
- return [makeSourceRecord(skill, targetFile, written)];
610
+ return records;
613
611
  }
614
612
  async function rewriteSingleFile(args) {
615
613
  const {
@@ -676,41 +674,30 @@ async function rewriteSingleFile(args) {
676
674
  function escapeRegExp(str) {
677
675
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
678
676
  }
679
- async function syncSkillsToIdes(options) {
680
- await migrateLegacySkillsSourceDir(options.projectRoot);
681
- const { projectRoot, skills, ides, scope, onlyIds } = options;
677
+ async function reinstallSkillsToIdes(options) {
678
+ const { projectRoot, skills, manifest, data, packageRoot, ides, scope } = options;
682
679
  const out = [];
683
- const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
684
- for (const skill of targets) {
685
- const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
686
- if (!await fileExists(sourceDir)) {
687
- logger.warn(
688
- `Skill "${skill.id}" has no source at ${sourceDir}; skipped.`
689
- );
680
+ for (const skill of skills) {
681
+ const entry = manifest.skills.find((s) => s.id === skill.id);
682
+ if (!entry) {
683
+ logger.warn(`Skill "${skill.id}" not found in npm package manifest; skipped.`);
690
684
  continue;
691
685
  }
686
+ const rendered = await renderSkillFiles(entry, { data, packageRoot });
692
687
  for (const ide of ides) {
693
688
  const adapter = getAdapter(ide);
694
- const targetDir = adapter.getSkillTargetDir(
695
- skill.name,
696
- scope,
697
- projectRoot
698
- );
689
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
699
690
  await ensureDir(targetDir);
700
- const sourceFiles = await walkDir(sourceDir);
701
- for (const src of sourceFiles) {
702
- const rel2 = path7.relative(sourceDir, src);
691
+ for (const [rel2, sourceContent] of rendered) {
703
692
  const targetFile = path7.join(targetDir, rel2);
704
- const sourceContent = await fs5.readFile(src, "utf-8");
705
693
  const writtenContent = await writeMirrorContent(
706
694
  targetFile,
707
695
  sourceContent,
708
- skill.managedRegions,
709
- src
696
+ skill.managedRegions
710
697
  );
711
698
  out.push({
712
699
  id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
713
- target: targetFile,
700
+ target: path7.relative(projectRoot, targetFile),
714
701
  hash: computeHash(writtenContent),
715
702
  strategy: skill.updateStrategy,
716
703
  ide,
@@ -721,75 +708,6 @@ async function syncSkillsToIdes(options) {
721
708
  }
722
709
  return { resources: out, count: out.length };
723
710
  }
724
- async function migrateLegacySkillsSourceDir(projectRoot) {
725
- const legacyDir = getLegacySkillsSourceDir(projectRoot);
726
- const newDir = getSkillsSourceDir(projectRoot);
727
- let legacyExists = false;
728
- let newExists = false;
729
- try {
730
- legacyExists = (await fs5.stat(legacyDir)).isDirectory();
731
- } catch {
732
- legacyExists = false;
733
- }
734
- try {
735
- newExists = (await fs5.stat(newDir)).isDirectory();
736
- } catch {
737
- newExists = false;
738
- }
739
- if (!legacyExists) return;
740
- if (newExists) {
741
- logger.warn(
742
- `Detected stale legacy skills source dir at ${legacyDir} alongside ${newDir}; the new layout takes precedence \u2014 you can safely delete the legacy dir.`
743
- );
744
- return;
745
- }
746
- try {
747
- await fs5.rename(legacyDir, newDir);
748
- logger.info(
749
- `Migrated skills source dir: \`.teamix-evo/${LEGACY_SKILLS_DIR}/\` \u2192 \`.teamix-evo/skills-source/\``
750
- );
751
- } catch (err) {
752
- logger.warn(
753
- `Failed to rename legacy skills source dir (${getErrorMessage(
754
- err
755
- )}); leaving as-is. New skills will install under the new layout.`
756
- );
757
- return;
758
- }
759
- try {
760
- const manifest = await readInstalledManifest(projectRoot);
761
- if (!manifest) return;
762
- const legacyFragmentPosix = `/.teamix-evo/${LEGACY_SKILLS_DIR}/`;
763
- const newFragmentPosix = `/.teamix-evo/skills-source/`;
764
- const legacyFragmentNative = `${path7.sep}.teamix-evo${path7.sep}${LEGACY_SKILLS_DIR}${path7.sep}`;
765
- const newFragmentNative = `${path7.sep}.teamix-evo${path7.sep}skills-source${path7.sep}`;
766
- let touched = 0;
767
- for (const pkg of manifest.installed) {
768
- for (const r of pkg.resources) {
769
- if (typeof r.target !== "string") continue;
770
- const before = r.target;
771
- let after = before.replace(legacyFragmentPosix, newFragmentPosix);
772
- after = after.replace(legacyFragmentNative, newFragmentNative);
773
- if (after !== before) {
774
- r.target = after;
775
- touched += 1;
776
- }
777
- }
778
- }
779
- if (touched > 0) {
780
- await writeInstalledManifest(projectRoot, manifest);
781
- logger.debug(
782
- `Rewrote ${touched} manifest target(s) to the new skills-source path.`
783
- );
784
- }
785
- } catch (err) {
786
- logger.warn(
787
- `Migrated skills source dir but failed to update manifest paths (${getErrorMessage(
788
- err
789
- )}); manifest may still reference legacy paths.`
790
- );
791
- }
792
- }
793
711
  async function pruneEmptyIdeSkillDirs(args) {
794
712
  const removed = [];
795
713
  for (const ide of args.ides) {
@@ -802,7 +720,7 @@ async function pruneEmptyIdeSkillDirs(args) {
802
720
  const skillsRoot = path7.dirname(placeholderDir);
803
721
  let entries;
804
722
  try {
805
- entries = await fs5.readdir(skillsRoot);
723
+ entries = await fs6.readdir(skillsRoot);
806
724
  } catch {
807
725
  continue;
808
726
  }
@@ -810,21 +728,21 @@ async function pruneEmptyIdeSkillDirs(args) {
810
728
  const dir = path7.join(skillsRoot, name);
811
729
  let stat3;
812
730
  try {
813
- stat3 = await fs5.stat(dir);
731
+ stat3 = await fs6.stat(dir);
814
732
  } catch {
815
733
  continue;
816
734
  }
817
735
  if (!stat3.isDirectory()) continue;
818
736
  let children;
819
737
  try {
820
- children = await fs5.readdir(dir);
738
+ children = await fs6.readdir(dir);
821
739
  } catch {
822
740
  continue;
823
741
  }
824
742
  if (children.some((c) => c === "SKILL.md")) continue;
825
743
  if (children.length !== 0) continue;
826
744
  try {
827
- await fs5.rmdir(dir);
745
+ await fs6.rmdir(dir);
828
746
  removed.push(dir);
829
747
  logger.debug(`Pruned empty IDE skill dir: ${dir}`);
830
748
  } catch {
@@ -833,26 +751,29 @@ async function pruneEmptyIdeSkillDirs(args) {
833
751
  }
834
752
  return removed;
835
753
  }
836
- async function removeSkillFiles(records) {
754
+ async function removeSkillFiles(records, projectRoot) {
837
755
  const removed = [];
838
756
  for (const r of records) {
757
+ const abs = resolveResourceTarget(projectRoot, r.target);
839
758
  try {
840
- await fs5.unlink(r.target);
841
- removed.push(r.target);
759
+ await fs6.unlink(abs);
760
+ removed.push(abs);
842
761
  } catch (err) {
843
762
  if (err.code !== "ENOENT") {
844
- logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
763
+ logger.warn(`Failed to remove ${abs}: ${getErrorMessage(err)}`);
845
764
  }
846
765
  }
847
766
  }
848
- const startDirs = new Set(records.map((r) => path7.dirname(r.target)));
767
+ const startDirs = new Set(
768
+ records.map((r) => path7.dirname(resolveResourceTarget(projectRoot, r.target)))
769
+ );
849
770
  for (const startDir of startDirs) {
850
771
  let dir = startDir;
851
772
  for (let depth = 0; depth < 8; depth++) {
852
773
  try {
853
- const entries = await fs5.readdir(dir);
774
+ const entries = await fs6.readdir(dir);
854
775
  if (entries.length !== 0) break;
855
- await fs5.rmdir(dir);
776
+ await fs6.rmdir(dir);
856
777
  } catch {
857
778
  break;
858
779
  }
@@ -862,32 +783,6 @@ async function removeSkillFiles(records) {
862
783
  return removed;
863
784
  }
864
785
 
865
- // src/utils/mcp.ts
866
- import * as path8 from "path";
867
- var MCP_JSON_CONTENT = {
868
- mcpServers: {
869
- "teamix-evo": {
870
- command: "node",
871
- args: ["node_modules/@teamix-evo/mcp/dist/cli.js"]
872
- }
873
- }
874
- };
875
- async function ensureMcpJson(projectRoot) {
876
- const mcpPath = path8.join(projectRoot, ".mcp.json");
877
- if (await fileExists(mcpPath)) return "exists";
878
- try {
879
- await writeFileSafe(
880
- mcpPath,
881
- JSON.stringify(MCP_JSON_CONTENT, null, 2) + "\n"
882
- );
883
- logger.debug(`Wrote .mcp.json \u2192 ${mcpPath}`);
884
- return "created";
885
- } catch (err) {
886
- logger.warn(`Failed to write .mcp.json: ${getErrorMessage(err)}`);
887
- return "failed";
888
- }
889
- }
890
-
891
786
  // src/core/skills-add.ts
892
787
  var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
893
788
  var FLAT_VARIANT = "_flat";
@@ -1081,9 +976,15 @@ function partitionByVersion(ids, manifest, existing) {
1081
976
  onlyIds.push(name);
1082
977
  continue;
1083
978
  }
1084
- const installedVer = existing.lock?.skills?.[name]?.version;
979
+ const installedVer = existing.lock?.skills?.[name]?.version ?? existing.pkg?.version;
1085
980
  const latestVer = manifestById.get(name)?.version;
1086
- if (installedVer && latestVer && compareSemver(installedVer, latestVer) < 0) {
981
+ if (!installedVer || !latestVer) {
982
+ if (latestVer) {
983
+ outdatedSkills.push({ id: name, installed: "0.0.0", latest: latestVer });
984
+ } else {
985
+ skippedSkillIds.push(name);
986
+ }
987
+ } else if (compareSemver(installedVer, latestVer) < 0) {
1087
988
  outdatedSkills.push({
1088
989
  id: name,
1089
990
  installed: installedVer,
@@ -1201,7 +1102,6 @@ async function finalizeSkillsInstall(args) {
1201
1102
  };
1202
1103
  }
1203
1104
  await writeSkillsLock(projectRoot, lock);
1204
- await ensureMcpJson(projectRoot);
1205
1105
  try {
1206
1106
  await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
1207
1107
  } catch {
@@ -1346,7 +1246,7 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1346
1246
  const result = await installVariantFile(fileRel, packageRoot, projectRoot);
1347
1247
  if (result) installed.push(result);
1348
1248
  }
1349
- const overridesAbs = path9.join(
1249
+ const overridesAbs = path8.join(
1350
1250
  projectRoot,
1351
1251
  CONSUMER_TOKENS_DIR,
1352
1252
  CONSUMER_OVERRIDES_FILE
@@ -1356,10 +1256,10 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1356
1256
  }
1357
1257
  const overridesId = `tokens:${CONSUMER_OVERRIDES_FILE}`;
1358
1258
  if (!installed.some((r) => r.id === overridesId)) {
1359
- const overridesContent = await fs6.readFile(overridesAbs, "utf-8");
1259
+ const overridesContent = await fs7.readFile(overridesAbs, "utf-8");
1360
1260
  installed.push({
1361
1261
  id: overridesId,
1362
- target: path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
1262
+ target: path8.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
1363
1263
  hash: computeHash(overridesContent),
1364
1264
  strategy: "frozen"
1365
1265
  });
@@ -1377,7 +1277,7 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1377
1277
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
1378
1278
  };
1379
1279
  await writeFileSafe(
1380
- path9.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
1280
+ path8.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
1381
1281
  JSON.stringify(lock, null, 2) + "\n"
1382
1282
  );
1383
1283
  const config = {
@@ -1409,7 +1309,6 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1409
1309
  if (tokensIdx >= 0) prior.installed[tokensIdx] = tokensEntry;
1410
1310
  else prior.installed.push(tokensEntry);
1411
1311
  await writeInstalledManifest(projectRoot, prior);
1412
- await ensureMcpJson(projectRoot);
1413
1312
  const skills = await tryAutoInstallVariantSkills({
1414
1313
  projectRoot,
1415
1314
  variant,
@@ -1498,12 +1397,12 @@ async function tryAutoInstallVariantSkills(args) {
1498
1397
  }
1499
1398
  }
1500
1399
  async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1501
- const sourceAbs = path9.join(packageRoot, fileRelToPackage);
1502
- const base = path9.basename(fileRelToPackage);
1400
+ const sourceAbs = path8.join(packageRoot, fileRelToPackage);
1401
+ const base = path8.basename(fileRelToPackage);
1503
1402
  if (base === "theme.css") {
1504
- const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
1505
- const targetAbs = path9.join(projectRoot, targetRel);
1506
- const content = await fs6.readFile(sourceAbs, "utf-8");
1403
+ const targetRel = path8.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
1404
+ const targetAbs = path8.join(projectRoot, targetRel);
1405
+ const content = await fs7.readFile(sourceAbs, "utf-8");
1507
1406
  if (await fileExists(targetAbs)) {
1508
1407
  await backupFile(targetAbs, projectRoot);
1509
1408
  }
@@ -1516,13 +1415,13 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1516
1415
  };
1517
1416
  }
1518
1417
  if (base === "overrides.css" || base === "tokens.overrides.css") {
1519
- const targetRel = path9.posix.join(
1418
+ const targetRel = path8.posix.join(
1520
1419
  CONSUMER_TOKENS_DIR,
1521
1420
  CONSUMER_OVERRIDES_FILE
1522
1421
  );
1523
- const targetAbs = path9.join(projectRoot, targetRel);
1422
+ const targetAbs = path8.join(projectRoot, targetRel);
1524
1423
  if (await fileExists(targetAbs)) {
1525
- const existing = await fs6.readFile(targetAbs, "utf-8");
1424
+ const existing = await fs7.readFile(targetAbs, "utf-8");
1526
1425
  return {
1527
1426
  id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
1528
1427
  target: targetRel,
@@ -1530,7 +1429,7 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1530
1429
  strategy: "frozen"
1531
1430
  };
1532
1431
  }
1533
- const content = await fs6.readFile(sourceAbs, "utf-8");
1432
+ const content = await fs7.readFile(sourceAbs, "utf-8");
1534
1433
  await writeFileSafe(targetAbs, content);
1535
1434
  return {
1536
1435
  id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
@@ -1557,77 +1456,332 @@ async function listTokenVariants(packageName = DEFAULT_TOKENS_PACKAGE, packageRo
1557
1456
  };
1558
1457
  }
1559
1458
 
1560
- // src/core/skills-update.ts
1561
- var DEFAULT_SKILLS_PACKAGE3 = "@teamix-evo/skills";
1562
- var FLAT_VARIANT2 = "_flat";
1563
- async function runSkillsUpdate(options) {
1564
- const { projectRoot, names: requestedNames, dryRun } = options;
1565
- const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE3;
1459
+ // src/core/agents-md.ts
1460
+ import * as fs8 from "fs/promises";
1461
+ import * as path9 from "path";
1462
+ import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
1463
+ var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
1464
+ async function runGenerateAgentsMd(options) {
1465
+ const { projectRoot, variant, skillIds } = options;
1466
+ const mode = options.mode ?? "overwrite";
1566
1467
  const config = await readProjectConfig(projectRoot);
1567
- const skillsCfg = config?.packages?.skills;
1568
- if (!skillsCfg) {
1569
- return { status: "no-skills" };
1570
- }
1571
- const ides = skillsCfg.ides ?? ["qoder", "claude"];
1572
- const scope = skillsCfg.scope ?? "project";
1573
- const existingLock = await readSkillsLock(projectRoot);
1574
- if (!existingLock || Object.keys(existingLock.skills).length === 0) {
1575
- return { status: "no-skills" };
1468
+ const lock = await readSkillsLock(projectRoot);
1469
+ const ides = config?.packages?.skills?.ides ?? ["qoder", "claude"];
1470
+ const scope = config?.packages?.skills?.scope ?? lock?.skills[skillIds[0] ?? ""]?.scope ?? "project";
1471
+ const ordered = [...skillIds].sort(
1472
+ (a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
1473
+ );
1474
+ const sections = [];
1475
+ const missingSkillIds = [];
1476
+ for (const id of ordered) {
1477
+ const { section, missing } = await renderSkillSection(projectRoot, id, ides, scope);
1478
+ sections.push(section);
1479
+ if (missing) missingSkillIds.push(id);
1576
1480
  }
1577
- const { manifest, data, packageRoot } = await loadSkillsData(packageName);
1578
- const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
1579
- const lockIds = Object.keys(existingLock.skills);
1580
- const requestedSet = requestedNames ? new Set(requestedNames) : null;
1581
- if (requestedSet) {
1582
- const unknown = requestedNames.filter(
1583
- (n) => !lockIds.includes(n) && !manifestById.has(n)
1584
- );
1585
- if (unknown.length > 0) {
1586
- throw new Error(
1587
- `Unknown skill id(s): ${unknown.join(
1588
- ", "
1589
- )}. Available (installed): ${lockIds.join(", ") || "(none)"}.`
1590
- );
1481
+ const target = path9.join(projectRoot, "AGENTS.md");
1482
+ const targetExists = await fileExists(target);
1483
+ const fullTemplate = renderAgentsMd({ variant, sections });
1484
+ const managedBody = renderManagedBlockBody({ variant, sections });
1485
+ let outputContent;
1486
+ let merge;
1487
+ if (!targetExists) {
1488
+ outputContent = fullTemplate;
1489
+ merge = "created";
1490
+ } else {
1491
+ await backupFile(target, projectRoot);
1492
+ if (mode === "merge-managed") {
1493
+ const existing = await readFileOrNull(target) ?? "";
1494
+ if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
1495
+ outputContent = replaceManagedRegion2(
1496
+ existing,
1497
+ AGENTS_MD_MANAGED_ID,
1498
+ managedBody
1499
+ );
1500
+ merge = "managed-replaced";
1501
+ } else {
1502
+ const wrapped = wrapManagedBlock(managedBody);
1503
+ outputContent = `${wrapped}
1504
+
1505
+ ${PRECEDENCE_NOTICE}
1506
+
1507
+ ${existing.trimStart()}`;
1508
+ merge = "managed-prepended";
1509
+ }
1510
+ } else {
1511
+ outputContent = fullTemplate;
1512
+ merge = "overwritten";
1591
1513
  }
1592
1514
  }
1593
- const targetIds = [];
1594
- const skippedSkillIds = [];
1595
- for (const id of lockIds) {
1596
- if (requestedSet && !requestedSet.has(id)) continue;
1597
- const entry2 = manifestById.get(id);
1598
- if (!entry2) {
1599
- logger.debug(
1600
- `Skipping "${id}": no longer in upstream manifest. Use \`skills uninstall ${id}\` to remove.`
1601
- );
1602
- skippedSkillIds.push(id);
1603
- continue;
1604
- }
1605
- const effectiveScope = entry2.scope ?? "project";
1606
- if (effectiveScope !== scope) {
1607
- logger.debug(
1608
- `Skipping "${id}" (scope=${effectiveScope}): current install scope is "${scope}".`
1609
- );
1610
- skippedSkillIds.push(id);
1515
+ await fs8.writeFile(target, outputContent, "utf8");
1516
+ return {
1517
+ path: target,
1518
+ skillCount: ordered.length,
1519
+ missingSkillIds,
1520
+ backedUp: targetExists,
1521
+ merge
1522
+ };
1523
+ }
1524
+ function bucketRank(id) {
1525
+ if (id.startsWith("teamix-evo-design-")) return 0;
1526
+ if (id.startsWith("teamix-evo-code-")) return 1;
1527
+ return 2;
1528
+ }
1529
+ async function renderSkillSection(projectRoot, skillId, ides, scope) {
1530
+ const lines = [];
1531
+ lines.push(`### ${skillId}`);
1532
+ let parts = null;
1533
+ let missing = false;
1534
+ for (const ide of ides) {
1535
+ const adapter = getAdapter(ide);
1536
+ const skillPath = path9.join(
1537
+ adapter.getSkillTargetDir(skillId, scope, projectRoot),
1538
+ "SKILL.md"
1539
+ );
1540
+ try {
1541
+ const raw = await fs8.readFile(skillPath, "utf8");
1542
+ parts = extractDescriptionParts(raw);
1543
+ break;
1544
+ } catch {
1611
1545
  continue;
1612
1546
  }
1613
- targetIds.push(id);
1614
1547
  }
1615
- const allSame = targetIds.every((id) => {
1616
- const lockVer = existingLock.skills[id].version;
1617
- const manVer = manifestById.get(id).version;
1618
- return lockVer === manVer;
1619
- });
1620
- if (targetIds.length > 0 && allSame && !dryRun) {
1621
- return {
1622
- status: "no-changes",
1623
- packageName,
1624
- version: manifest.version,
1625
- checkedSkillIds: targetIds
1548
+ if (!parts) missing = true;
1549
+ if (parts?.capability) {
1550
+ lines.push(`- ${parts.capability}`);
1551
+ }
1552
+ lines.push(
1553
+ `- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
1554
+ );
1555
+ lines.push(
1556
+ `- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
1557
+ );
1558
+ if (parts?.coordinates) {
1559
+ lines.push(`- **Coordinates with**: ${parts.coordinates}`);
1560
+ }
1561
+ return { section: lines.join("\n"), missing };
1562
+ }
1563
+ function renderAgentsMd(args) {
1564
+ const { variant, sections } = args;
1565
+ const managedBody = renderManagedBlockBody({ variant, sections });
1566
+ const wrapped = wrapManagedBlock(managedBody);
1567
+ return `${wrapped}
1568
+
1569
+ ${PRECEDENCE_NOTICE}
1570
+ `;
1571
+ }
1572
+ function renderManagedBlockBody(args) {
1573
+ const { variant, sections } = args;
1574
+ const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
1575
+ return `# AGENTS.md
1576
+
1577
+ > AI \u52A9\u624B\uFF1A\u5199\u4EE3\u7801\u524D\u5148\u5BF9\u7167\u4E0B\u65B9 TRIGGER\uFF0C\u547D\u4E2D\u5219\u5FC5\u987B\u8BFB\u5BF9\u5E94 skill \u518D\u52A8\u624B\uFF1B\u4E0D\u786E\u5B9A\u65F6\u4ECD\u5EFA\u8BAE\u5FEB\u901F\u67E5\u9605\u76F8\u5173 skill \u4EE5\u786E\u8BA4\u662F\u5426\u6D89\u53CA\u89C4\u8303\u7EA6\u675F\u3002
1578
+ >
1579
+ > \u6FC0\u6D3B\u65B9\u5F0F\uFF1A\u5728\u5BF9\u8BDD\u4E2D\u76F4\u63A5\u63D0\u53CA skill \u540D\u79F0\uFF08\u5982 \`teamix-evo-design-${variant}\`\uFF09\uFF0CIDE \u4F1A\u81EA\u52A8\u52A0\u8F7D\u5176\u5B8C\u6574\u5185\u5BB9\u3002
1580
+
1581
+ ## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
1582
+
1583
+ ${skillBlock}
1584
+
1585
+ > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo@latest skills update\``;
1586
+ }
1587
+ function wrapManagedBlock(body) {
1588
+ return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
1589
+ ${body}
1590
+ <!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
1591
+ }
1592
+ var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
1593
+ > \u51B2\u7A81\u4EE5\u4E0A\u65B9\u7684 **Skills** \u7D22\u5F15\u4E3A\u51C6\uFF08\u4E0A\u6E38\u8DEF\u5F84\u4E0E TRIGGER/SKIP \u5951\u7EA6\uFF09\uFF1B\u9879\u76EE\u7279\u6709\u7684\u4EBA\u5DE5\u7EC6\u5219\u8BF7\u5199\u5728\u672C\u5904\u4EE5\u4E0B\u3001\u4E0D\u8981\u8986\u76D6\u4E0A\u65B9 managed \u533A\u57DF\u3002`;
1594
+ function extractDescriptionParts(fileContent) {
1595
+ const description = extractDescriptionBlock(fileContent);
1596
+ if (description == null) return null;
1597
+ const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
1598
+ let capability = "";
1599
+ for (const line of allLines) {
1600
+ if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
1601
+ capability = capability ? `${capability} ${line}` : line;
1602
+ }
1603
+ return {
1604
+ capability: capability.trim(),
1605
+ trigger: extractSection(description, "TRIGGER when:"),
1606
+ skip: extractSection(description, "SKIP:"),
1607
+ coordinates: extractSection(description, "Coordinates with:")
1608
+ };
1609
+ }
1610
+ function extractDescriptionBlock(fileContent) {
1611
+ const lines = fileContent.split("\n");
1612
+ if (lines[0]?.trim() !== "---") return null;
1613
+ let endIdx = -1;
1614
+ for (let i = 1; i < lines.length; i++) {
1615
+ if (lines[i]?.trim() === "---") {
1616
+ endIdx = i;
1617
+ break;
1618
+ }
1619
+ }
1620
+ if (endIdx === -1) return null;
1621
+ const fmLines = lines.slice(1, endIdx);
1622
+ let startIdx = -1;
1623
+ let inlineValue = null;
1624
+ let blockMode = "inline";
1625
+ for (let i = 0; i < fmLines.length; i++) {
1626
+ const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
1627
+ if (m) {
1628
+ startIdx = i;
1629
+ const indicator = (m[1] ?? "").trim();
1630
+ const rest = m[2] ?? "";
1631
+ if (indicator.startsWith("|")) blockMode = "literal";
1632
+ else if (indicator.startsWith(">")) blockMode = "folded";
1633
+ else {
1634
+ blockMode = "inline";
1635
+ inlineValue = rest;
1636
+ }
1637
+ break;
1638
+ }
1639
+ }
1640
+ if (startIdx === -1) return null;
1641
+ if (blockMode === "inline") {
1642
+ return inlineValue ?? "";
1643
+ }
1644
+ const body = [];
1645
+ let blockIndent = -1;
1646
+ for (let i = startIdx + 1; i < fmLines.length; i++) {
1647
+ const line = fmLines[i] ?? "";
1648
+ if (line.trim() === "") {
1649
+ body.push("");
1650
+ continue;
1651
+ }
1652
+ const indentMatch = line.match(/^(\s+)/);
1653
+ const indent = indentMatch ? indentMatch[1].length : 0;
1654
+ if (indent === 0) break;
1655
+ if (blockIndent === -1) blockIndent = indent;
1656
+ if (indent < blockIndent) break;
1657
+ body.push(line.slice(blockIndent));
1658
+ }
1659
+ while (body.length > 0 && body[body.length - 1] === "") body.pop();
1660
+ return body.join("\n");
1661
+ }
1662
+ function extractSection(description, marker) {
1663
+ const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
1664
+ const lines = description.split("\n");
1665
+ let inSection = false;
1666
+ const collected = [];
1667
+ for (const line of lines) {
1668
+ const trimmed = line.trim();
1669
+ const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
1670
+ if (!inSection && startsWithMarker) {
1671
+ inSection = true;
1672
+ collected.push(trimmed.slice(marker.length).trim());
1673
+ continue;
1674
+ }
1675
+ if (inSection) {
1676
+ const hitNextMarker = markers.some(
1677
+ (m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
1678
+ );
1679
+ if (hitNextMarker) break;
1680
+ if (trimmed === "") {
1681
+ if (collected[collected.length - 1] === "") break;
1682
+ collected.push("");
1683
+ continue;
1684
+ }
1685
+ collected.push(trimmed);
1686
+ }
1687
+ }
1688
+ if (!inSection) return null;
1689
+ const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
1690
+ return joined || null;
1691
+ }
1692
+
1693
+ // src/core/skills-update.ts
1694
+ var DEFAULT_SKILLS_PACKAGE3 = "@teamix-evo/skills";
1695
+ var FLAT_VARIANT2 = "_flat";
1696
+ async function runSkillsUpdate(options) {
1697
+ const { projectRoot, names: requestedNames, dryRun } = options;
1698
+ const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE3;
1699
+ const config = await readProjectConfig(projectRoot);
1700
+ const skillsCfg = config?.packages?.skills;
1701
+ if (!skillsCfg) {
1702
+ return { status: "no-skills" };
1703
+ }
1704
+ const ides = skillsCfg.ides ?? ["qoder", "claude"];
1705
+ const scope = skillsCfg.scope ?? "project";
1706
+ let existingLock = await readSkillsLock(projectRoot);
1707
+ if (!existingLock || Object.keys(existingLock.skills).length === 0) {
1708
+ const installedManifest2 = await readInstalledManifest(projectRoot);
1709
+ const pkg = installedManifest2?.installed.find(
1710
+ (p) => p.package === packageName
1711
+ );
1712
+ if (!pkg || pkg.resources.length === 0) {
1713
+ return { status: "no-skills" };
1714
+ }
1715
+ const derivedSkills = {};
1716
+ for (const r of pkg.resources) {
1717
+ const skillId = r.id.split(":")[0] ?? r.id;
1718
+ if (!derivedSkills[skillId]) {
1719
+ derivedSkills[skillId] = {
1720
+ version: pkg.version,
1721
+ from: packageName,
1722
+ installedAt: "",
1723
+ scope,
1724
+ mirroredTo: []
1725
+ };
1726
+ }
1727
+ }
1728
+ existingLock = { schemaVersion: 1, skills: derivedSkills };
1729
+ }
1730
+ const activeLock = existingLock;
1731
+ const { manifest, data, packageRoot } = await loadSkillsData(packageName);
1732
+ const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
1733
+ const lockIds = Object.keys(activeLock.skills);
1734
+ const requestedSet = requestedNames ? new Set(requestedNames) : null;
1735
+ if (requestedSet) {
1736
+ const unknown = requestedNames.filter(
1737
+ (n) => !lockIds.includes(n) && !manifestById.has(n)
1738
+ );
1739
+ if (unknown.length > 0) {
1740
+ throw new Error(
1741
+ `Unknown skill id(s): ${unknown.join(
1742
+ ", "
1743
+ )}. Available (installed): ${lockIds.join(", ") || "(none)"}.`
1744
+ );
1745
+ }
1746
+ }
1747
+ const targetIds = [];
1748
+ const skippedSkillIds = [];
1749
+ for (const id of lockIds) {
1750
+ if (requestedSet && !requestedSet.has(id)) continue;
1751
+ const entry2 = manifestById.get(id);
1752
+ if (!entry2) {
1753
+ logger.debug(
1754
+ `Skipping "${id}": no longer in upstream manifest. Use \`skills uninstall ${id}\` to remove.`
1755
+ );
1756
+ skippedSkillIds.push(id);
1757
+ continue;
1758
+ }
1759
+ const effectiveScope = entry2.scope ?? "project";
1760
+ if (effectiveScope !== scope) {
1761
+ logger.debug(
1762
+ `Skipping "${id}" (scope=${effectiveScope}): current install scope is "${scope}".`
1763
+ );
1764
+ skippedSkillIds.push(id);
1765
+ continue;
1766
+ }
1767
+ targetIds.push(id);
1768
+ }
1769
+ const allSame = targetIds.every((id) => {
1770
+ const lockVer = activeLock.skills[id].version;
1771
+ const manVer = manifestById.get(id).version;
1772
+ return lockVer === manVer;
1773
+ });
1774
+ if (targetIds.length > 0 && allSame && !dryRun) {
1775
+ return {
1776
+ status: "no-changes",
1777
+ packageName,
1778
+ version: manifest.version,
1779
+ checkedSkillIds: targetIds
1626
1780
  };
1627
1781
  }
1628
1782
  if (dryRun) {
1629
1783
  const plan = targetIds.map((id) => {
1630
- const lockVer = existingLock.skills[id].version;
1784
+ const lockVer = activeLock.skills[id].version;
1631
1785
  const entry2 = manifestById.get(id);
1632
1786
  const sameVersion = lockVer === entry2.version;
1633
1787
  return {
@@ -1699,7 +1853,7 @@ async function runSkillsUpdate(options) {
1699
1853
  await writeInstalledManifest(projectRoot, installedManifest);
1700
1854
  const lock = {
1701
1855
  schemaVersion: 1,
1702
- skills: { ...existingLock.skills }
1856
+ skills: { ...activeLock.skills }
1703
1857
  };
1704
1858
  for (const id of targetIds) {
1705
1859
  const skillDef = manifestById.get(id);
@@ -1714,6 +1868,24 @@ async function runSkillsUpdate(options) {
1714
1868
  };
1715
1869
  }
1716
1870
  await writeSkillsLock(projectRoot, lock);
1871
+ if (scope === "project") {
1872
+ try {
1873
+ const variant = await readTokensVariant(projectRoot);
1874
+ if (variant) {
1875
+ const projectSkillIds = Object.entries(lock.skills).filter(([, v]) => v.scope === "project").map(([id]) => id);
1876
+ await runGenerateAgentsMd({
1877
+ projectRoot,
1878
+ variant,
1879
+ skillIds: projectSkillIds,
1880
+ mode: "merge-managed"
1881
+ });
1882
+ }
1883
+ } catch (err) {
1884
+ logger.warn(
1885
+ `AGENTS.md \u5237\u65B0\u5931\u8D25\uFF08\u975E\u5173\u952E\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}`
1886
+ );
1887
+ }
1888
+ }
1717
1889
  return {
1718
1890
  status: "updated",
1719
1891
  packageName,
@@ -1734,8 +1906,7 @@ var DEFAULT_UI_ALIASES = {
1734
1906
  utils: "src/lib/utils",
1735
1907
  lib: "src/lib",
1736
1908
  business: "src/components/business",
1737
- blocks: "src/blocks",
1738
- templates: "src/templates"
1909
+ blocks: "src/blocks"
1739
1910
  };
1740
1911
  var DEFAULT_UI_ICON_LIBRARY = "lucide";
1741
1912
  async function runUiInit(options) {
@@ -1752,8 +1923,7 @@ async function runUiInit(options) {
1752
1923
  utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
1753
1924
  lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
1754
1925
  business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
1755
- blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks,
1756
- templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
1926
+ blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks
1757
1927
  };
1758
1928
  const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
1759
1929
  const tsx = options.tsx ?? true;
@@ -1773,7 +1943,6 @@ async function runUiInit(options) {
1773
1943
  rsc
1774
1944
  };
1775
1945
  await writeProjectConfig(projectRoot, config);
1776
- await ensureMcpJson(projectRoot);
1777
1946
  return {
1778
1947
  status: "installed",
1779
1948
  aliases,
@@ -1785,7 +1954,7 @@ async function runUiInit(options) {
1785
1954
 
1786
1955
  // src/core/ui-client.ts
1787
1956
  import * as path10 from "path";
1788
- import * as fs7 from "fs/promises";
1957
+ import * as fs9 from "fs/promises";
1789
1958
  import { createRequire as createRequire3 } from "module";
1790
1959
  import { loadUiPackageManifest } from "@teamix-evo/registry";
1791
1960
  var require4 = createRequire3(import.meta.url);
@@ -1800,7 +1969,7 @@ async function loadUiData(packageName) {
1800
1969
  let data = {};
1801
1970
  const dataPath = path10.join(packageRoot, "_data.json");
1802
1971
  try {
1803
- const raw = await fs7.readFile(dataPath, "utf-8");
1972
+ const raw = await fs9.readFile(dataPath, "utf-8");
1804
1973
  data = JSON.parse(raw);
1805
1974
  } catch (err) {
1806
1975
  if (err.code !== "ENOENT") {
@@ -1813,7 +1982,7 @@ async function loadUiData(packageName) {
1813
1982
 
1814
1983
  // src/core/ui-installer.ts
1815
1984
  import * as path11 from "path";
1816
- import * as fs8 from "fs/promises";
1985
+ import * as fs10 from "fs/promises";
1817
1986
  import { resolveUiEntryOrder } from "@teamix-evo/registry";
1818
1987
 
1819
1988
  // src/utils/transform-imports.ts
@@ -1870,8 +2039,14 @@ async function installUiEntries(options) {
1870
2039
  const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
1871
2040
  const resources = [];
1872
2041
  const npmDeps = {};
1873
- let written = 0;
2042
+ let created = 0;
2043
+ let overwritten = 0;
2044
+ let userModified = 0;
1874
2045
  let skipped = 0;
2046
+ const { priorResources } = options;
2047
+ const priorByTarget = new Map(
2048
+ (priorResources ?? []).map((r) => [r.target, r])
2049
+ );
1875
2050
  for (const id of orderedIds) {
1876
2051
  const entry = idToEntry.get(id);
1877
2052
  if (!entry) continue;
@@ -1890,17 +2065,43 @@ async function installUiEntries(options) {
1890
2065
  }
1891
2066
  const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
1892
2067
  const sourceAbs = path11.resolve(rootForEntry, file.source);
1893
- const raw = await fs8.readFile(sourceAbs, "utf-8");
2068
+ const raw = await fs10.readFile(sourceAbs, "utf-8");
1894
2069
  const transformed = rewriteImports(raw, aliases, { flatten });
1895
2070
  if (exists) {
2071
+ const current = await fs10.readFile(targetAbs, "utf-8");
2072
+ if (current === transformed) {
2073
+ logger.info(` skip (identical): ${rel(projectRoot, targetAbs)}`);
2074
+ skipped++;
2075
+ resources.push({
2076
+ id: `${entry.id}:${file.targetName}`,
2077
+ target: path11.relative(projectRoot, targetAbs),
2078
+ hash: computeHash(transformed),
2079
+ strategy: entry.updateStrategy ?? "frozen"
2080
+ });
2081
+ continue;
2082
+ }
2083
+ const relPath = path11.relative(projectRoot, targetAbs);
2084
+ const prior = priorByTarget.get(relPath);
2085
+ if (prior && prior.hash !== "sha256:legacy-unknown") {
2086
+ const onDiskHash = computeHash(current);
2087
+ if (onDiskHash !== prior.hash) {
2088
+ logger.warn(
2089
+ ` \u26A0 ${relPath} has local modifications (backed up)`
2090
+ );
2091
+ userModified++;
2092
+ }
2093
+ }
1896
2094
  await backupFile(targetAbs, projectRoot);
2095
+ overwritten++;
2096
+ logger.info(` overwrite: ${rel(projectRoot, targetAbs)}`);
2097
+ } else {
2098
+ created++;
2099
+ logger.info(` create: ${rel(projectRoot, targetAbs)}`);
1897
2100
  }
1898
2101
  await writeFileSafe(targetAbs, transformed);
1899
- written++;
1900
- logger.info(` write: ${rel(projectRoot, targetAbs)}`);
1901
2102
  resources.push({
1902
2103
  id: `${entry.id}:${file.targetName}`,
1903
- target: targetAbs,
2104
+ target: path11.relative(projectRoot, targetAbs),
1904
2105
  hash: computeHash(transformed),
1905
2106
  strategy: entry.updateStrategy ?? "frozen"
1906
2107
  });
@@ -1910,7 +2111,10 @@ async function installUiEntries(options) {
1910
2111
  orderedIds,
1911
2112
  resources,
1912
2113
  npmDependencies: npmDeps,
1913
- written,
2114
+ written: created + overwritten,
2115
+ created,
2116
+ overwritten,
2117
+ userModified,
1914
2118
  skipped
1915
2119
  };
1916
2120
  }
@@ -1926,23 +2130,26 @@ function resolveTargetPath(projectRoot, aliases, entry, file) {
1926
2130
  function rel(projectRoot, abs) {
1927
2131
  return path11.relative(projectRoot, abs);
1928
2132
  }
1929
- async function removeUiFiles(records) {
2133
+ async function removeUiFiles(records, projectRoot) {
1930
2134
  const removed = [];
1931
2135
  for (const r of records) {
2136
+ const abs = resolveResourceTarget(projectRoot, r.target);
1932
2137
  try {
1933
- await fs8.unlink(r.target);
1934
- removed.push(r.target);
2138
+ await fs10.unlink(abs);
2139
+ removed.push(abs);
1935
2140
  } catch (err) {
1936
2141
  if (err.code !== "ENOENT") {
1937
- logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
2142
+ logger.warn(`Failed to remove ${abs}: ${getErrorMessage(err)}`);
1938
2143
  }
1939
2144
  }
1940
2145
  }
1941
- const parents = new Set(records.map((r) => path11.dirname(r.target)));
2146
+ const parents = new Set(
2147
+ records.map((r) => path11.dirname(resolveResourceTarget(projectRoot, r.target)))
2148
+ );
1942
2149
  for (const dir of parents) {
1943
2150
  try {
1944
- const entries = await fs8.readdir(dir);
1945
- if (entries.length === 0) await fs8.rmdir(dir);
2151
+ const entries = await fs10.readdir(dir);
2152
+ if (entries.length === 0) await fs10.rmdir(dir);
1946
2153
  } catch {
1947
2154
  }
1948
2155
  }
@@ -1986,17 +2193,22 @@ async function runUiAdd(options) {
1986
2193
  `Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
1987
2194
  );
1988
2195
  }
2196
+ const existingManifest = await readInstalledManifest(
2197
+ projectRoot
2198
+ ) ?? { schemaVersion: 1, installed: [] };
2199
+ const priorPkg = existingManifest.installed.find(
2200
+ (p) => p.package === packageName
2201
+ );
1989
2202
  const result = await installUiEntries({
1990
2203
  projectRoot,
1991
2204
  manifest: effectiveManifest,
1992
2205
  packageRoot,
1993
2206
  aliases: uiCfg.aliases,
1994
2207
  requested: ids,
1995
- skipExisting: !overwrite
2208
+ skipExisting: !overwrite,
2209
+ priorResources: priorPkg?.resources
1996
2210
  });
1997
- const installed = await readInstalledManifest(
1998
- projectRoot
1999
- ) ?? { schemaVersion: 1, installed: [] };
2211
+ const installed = existingManifest;
2000
2212
  const idx = installed.installed.findIndex((p) => p.package === packageName);
2001
2213
  const prior = idx >= 0 ? installed.installed[idx] : null;
2002
2214
  const mergedResources = mergeResources(
@@ -2021,6 +2233,9 @@ async function runUiAdd(options) {
2021
2233
  packageName,
2022
2234
  orderedIds: result.orderedIds,
2023
2235
  written: result.written,
2236
+ created: result.created,
2237
+ overwritten: result.overwritten,
2238
+ userModified: result.userModified,
2024
2239
  skipped: result.skipped,
2025
2240
  npmDependencies: result.npmDependencies,
2026
2241
  resources: result.resources
@@ -2091,7 +2306,7 @@ async function runVariantUiAdd(packageName, options) {
2091
2306
  const uiCfg = config?.packages?.ui;
2092
2307
  if (!config || !uiCfg?.aliases) {
2093
2308
  throw new Error(
2094
- `UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business / templates).`
2309
+ `UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business / blocks).`
2095
2310
  );
2096
2311
  }
2097
2312
  const packageRoot = options.packageRoot ?? resolvePackageRoot3(fullPackageName);
@@ -2131,24 +2346,23 @@ async function runVariantUiAdd(packageName, options) {
2131
2346
  engines: variantManifest.engines,
2132
2347
  entries: mergedEntries
2133
2348
  };
2349
+ const installed = await readInstalledManifest(
2350
+ projectRoot
2351
+ ) ?? { schemaVersion: 1, installed: [] };
2352
+ const idx = installed.installed.findIndex(
2353
+ (p) => p.package === fullPackageName && p.variant === variant
2354
+ );
2355
+ const prior = idx >= 0 ? installed.installed[idx] : null;
2134
2356
  const result = await installUiEntries({
2135
2357
  projectRoot,
2136
2358
  manifest: adaptedManifest,
2137
2359
  packageRoot: variantDir,
2138
- // default for variant entries
2139
2360
  entryPackageRoot,
2140
- // ui entries resolve from uiPackageRoot
2141
2361
  aliases: uiCfg.aliases,
2142
2362
  requested: ids,
2143
- skipExisting: !overwrite
2363
+ skipExisting: !overwrite,
2364
+ priorResources: prior?.resources
2144
2365
  });
2145
- const installed = await readInstalledManifest(
2146
- projectRoot
2147
- ) ?? { schemaVersion: 1, installed: [] };
2148
- const idx = installed.installed.findIndex(
2149
- (p) => p.package === fullPackageName && p.variant === variant
2150
- );
2151
- const prior = idx >= 0 ? installed.installed[idx] : null;
2152
2366
  const mergedResources = mergeResources2(
2153
2367
  prior?.resources ?? [],
2154
2368
  result.resources
@@ -2168,6 +2382,9 @@ async function runVariantUiAdd(packageName, options) {
2168
2382
  variant,
2169
2383
  orderedIds: result.orderedIds,
2170
2384
  written: result.written,
2385
+ created: result.created,
2386
+ overwritten: result.overwritten,
2387
+ userModified: result.userModified,
2171
2388
  skipped: result.skipped,
2172
2389
  npmDependencies: result.npmDependencies,
2173
2390
  resources: result.resources
@@ -2182,9 +2399,6 @@ function mergeResources2(prior, next) {
2182
2399
  async function runBizUiAdd(options) {
2183
2400
  return runVariantUiAdd("biz-ui", options);
2184
2401
  }
2185
- async function runTemplatesAdd(options) {
2186
- return runVariantUiAdd("templates", options);
2187
- }
2188
2402
  async function listVariantUi(packageName, packageRoot) {
2189
2403
  const fullPackageName = `@teamix-evo/${packageName}`;
2190
2404
  const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
@@ -2202,9 +2416,6 @@ async function listVariantUi(packageName, packageRoot) {
2202
2416
  async function listBizUiVariants(packageRoot) {
2203
2417
  return listVariantUi("biz-ui", packageRoot);
2204
2418
  }
2205
- async function listTemplatesVariants(packageRoot) {
2206
- return listVariantUi("templates", packageRoot);
2207
- }
2208
2419
  async function listVariantUiEntries(packageName, variant, packageRoot) {
2209
2420
  const fullPackageName = `@teamix-evo/${packageName}`;
2210
2421
  const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
@@ -2232,13 +2443,10 @@ async function listVariantUiEntries(packageName, variant, packageRoot) {
2232
2443
  async function listBizUiEntries(variant, packageRoot) {
2233
2444
  return listVariantUiEntries("biz-ui", variant, packageRoot);
2234
2445
  }
2235
- async function listTemplatesEntries(variant, packageRoot) {
2236
- return listVariantUiEntries("templates", variant, packageRoot);
2237
- }
2238
2446
 
2239
2447
  // src/core/lint-init.ts
2240
2448
  import * as path13 from "path";
2241
- import * as fs9 from "fs";
2449
+ import * as fs11 from "fs";
2242
2450
  import { execa } from "execa";
2243
2451
  var ESLINT_CONFIG_CONTENT = `/**
2244
2452
  * teamix-evo consumer ESLint preset \u2014 9 token-discipline rules.
@@ -2330,7 +2538,7 @@ async function runLintInit(options) {
2330
2538
  let stylelintIgnoreFilesWarning = false;
2331
2539
  if (!stylelintNeedsWrite && stylelintTemplateExists) {
2332
2540
  try {
2333
- const existingContent = fs9.readFileSync(stylelintConfigPath, "utf-8");
2541
+ const existingContent = fs11.readFileSync(stylelintConfigPath, "utf-8");
2334
2542
  const usesTeamixPreset = existingContent.includes("@teamix-evo/stylelint-config/presets/") || existingContent.includes("@teamix-evo/stylelint-config/preset/");
2335
2543
  const hasTokenIgnore = existingContent.includes("tokens.theme.css") && existingContent.includes("tokens.overrides.css");
2336
2544
  if (!usesTeamixPreset && !hasTokenIgnore) {
@@ -2344,305 +2552,65 @@ async function runLintInit(options) {
2344
2552
  " '**/tokens.theme.css',",
2345
2553
  " '**/tokens.overrides.css',",
2346
2554
  " ]",
2347
- "",
2348
- "\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
2349
- "",
2350
- " extends: ['@teamix-evo/stylelint-config/presets/consumer']"
2351
- ].join("\n")
2352
- );
2353
- }
2354
- } catch {
2355
- }
2356
- }
2357
- return {
2358
- status: "installed",
2359
- eslint: wroteEslint,
2360
- stylelint: wroteStylelint,
2361
- eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
2362
- stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
2363
- eslintSkipped: eslintSkipRequested,
2364
- stylelintSkipped: stylelintSkipRequested,
2365
- packageJsonPatched,
2366
- stylelintIgnoreFilesWarning
2367
- };
2368
- }
2369
- function detectPm(projectRoot) {
2370
- if (fs9.existsSync(path13.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
2371
- if (fs9.existsSync(path13.join(projectRoot, "yarn.lock"))) return "yarn";
2372
- return "npm";
2373
- }
2374
- async function patchPackageJsonScripts(projectRoot) {
2375
- const pkgPath = path13.join(projectRoot, "package.json");
2376
- const raw = await readFileOrNull(pkgPath);
2377
- if (!raw) return false;
2378
- let pkg;
2379
- try {
2380
- pkg = JSON.parse(raw);
2381
- } catch {
2382
- return false;
2383
- }
2384
- const scripts = pkg.scripts ?? {};
2385
- let changed = false;
2386
- if (!scripts.lint) {
2387
- scripts.lint = "eslint src/";
2388
- changed = true;
2389
- }
2390
- if (!scripts["lint:css"]) {
2391
- scripts["lint:css"] = "stylelint 'src/**/*.css'";
2392
- changed = true;
2393
- }
2394
- if (changed) {
2395
- await backupFile(pkgPath, projectRoot);
2396
- pkg.scripts = scripts;
2397
- await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
2398
- logger.debug("Patched package.json scripts with lint / lint:css");
2399
- }
2400
- return changed;
2401
- }
2402
-
2403
- // src/core/agents-md.ts
2404
- import * as fs10 from "fs/promises";
2405
- import * as path14 from "path";
2406
- import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
2407
- var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
2408
- async function runGenerateAgentsMd(options) {
2409
- const { projectRoot, variant, skillIds } = options;
2410
- const mode = options.mode ?? "overwrite";
2411
- const ordered = [...skillIds].sort(
2412
- (a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
2413
- );
2414
- const sections = [];
2415
- const missingSkillIds = [];
2416
- for (const id of ordered) {
2417
- const { section, missing } = await renderSkillSection(projectRoot, id);
2418
- sections.push(section);
2419
- if (missing) missingSkillIds.push(id);
2420
- }
2421
- const target = path14.join(projectRoot, "AGENTS.md");
2422
- const targetExists = await fileExists(target);
2423
- const fullTemplate = renderAgentsMd({ variant, sections });
2424
- const managedBody = renderManagedBlockBody({ variant, sections });
2425
- let outputContent;
2426
- let merge;
2427
- if (!targetExists) {
2428
- outputContent = fullTemplate;
2429
- merge = "created";
2430
- } else {
2431
- await backupFile(target, projectRoot);
2432
- if (mode === "merge-managed") {
2433
- const existing = await readFileOrNull(target) ?? "";
2434
- if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
2435
- outputContent = replaceManagedRegion2(
2436
- existing,
2437
- AGENTS_MD_MANAGED_ID,
2438
- managedBody
2439
- );
2440
- merge = "managed-replaced";
2441
- } else {
2442
- const wrapped = wrapManagedBlock(managedBody);
2443
- outputContent = `${wrapped}
2444
-
2445
- ${PRECEDENCE_NOTICE}
2446
-
2447
- ${existing.trimStart()}`;
2448
- merge = "managed-prepended";
2449
- }
2450
- } else {
2451
- outputContent = fullTemplate;
2452
- merge = "overwritten";
2453
- }
2454
- }
2455
- await fs10.writeFile(target, outputContent, "utf8");
2456
- return {
2457
- path: target,
2458
- skillCount: ordered.length,
2459
- missingSkillIds,
2460
- backedUp: targetExists,
2461
- merge
2462
- };
2463
- }
2464
- function bucketRank(id) {
2465
- if (id.startsWith("teamix-evo-design-")) return 0;
2466
- if (id.startsWith("teamix-evo-code-")) return 1;
2467
- return 2;
2468
- }
2469
- async function renderSkillSection(projectRoot, skillId) {
2470
- const skillPath = path14.join(
2471
- getSkillsSourceDir(projectRoot, skillId),
2472
- "SKILL.md"
2473
- );
2474
- const lines = [];
2475
- lines.push(`### ${skillId}`);
2476
- let parts = null;
2477
- let missing = false;
2478
- try {
2479
- const raw = await fs10.readFile(skillPath, "utf8");
2480
- parts = extractDescriptionParts(raw);
2481
- } catch {
2482
- missing = true;
2483
- }
2484
- if (parts?.capability) {
2485
- lines.push(`- ${parts.capability}`);
2486
- }
2487
- lines.push(
2488
- `- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
2489
- );
2490
- lines.push(
2491
- `- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
2492
- );
2493
- if (parts?.coordinates) {
2494
- lines.push(`- **Coordinates with**: ${parts.coordinates}`);
2495
- }
2496
- lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills-source/${skillId}/SKILL.md\``);
2497
- return { section: lines.join("\n"), missing };
2498
- }
2499
- function renderAgentsMd(args) {
2500
- const { variant, sections } = args;
2501
- const managedBody = renderManagedBlockBody({ variant, sections });
2502
- const wrapped = wrapManagedBlock(managedBody);
2503
- return `${wrapped}
2504
-
2505
- ${PRECEDENCE_NOTICE}
2506
- `;
2507
- }
2508
- function renderManagedBlockBody(args) {
2509
- const { variant, sections } = args;
2510
- const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
2511
- return `# AGENTS.md
2512
-
2513
- > \u672C\u5DE5\u7A0B\u5DF2\u88C5\u914D Teamix Evo AI skills\u3002AI \u52A9\u624B\u5728\u4EE5\u4E0B\u573A\u666F\u4E0B**\u5FC5\u987B\u5148\u8BFB\u5BF9\u5E94 skill** \u518D\u52A8\u624B\u3002
2514
- > \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C\u9075\u5FAA ADR 0038\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
2515
-
2516
- ## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
2517
-
2518
- ${skillBlock}
2519
-
2520
- ## \u89E6\u53D1\u515C\u5E95\u89C4\u5219
2521
-
2522
- - \u5199\u65B0 \`.tsx\` / \`.ts\` \u524D\uFF0C\u5BF9\u7167\u4E0A\u8FF0 TRIGGER \u5224\u5B9A\u662F\u5426\u547D\u4E2D
2523
- - \u547D\u4E2D\u5219\u5148\u8BFB\u5BF9\u5E94 \`SKILL.md\`\uFF0C\u518D\u52A8\u624B\uFF1B\u4E8C\u8005\u540C\u65F6\u547D\u4E2D\u5219\u4E24\u4E2A\u90FD\u8BFB
2524
- - \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
2525
- - \u751F\u547D\u5468\u671F\u547D\u4EE4\uFF08\`init\` / \`update\` / \`add\`\uFF09\u8D70 \`teamix-evo-manage\`\uFF08\u5168\u5C40 skill\uFF0C\u672C\u6587\u4EF6\u4E0D\u5217\uFF09
2526
- - \u8FC1\u79FB\u573A\u666F\uFF08"\u4EE3\u7801\u8FC1\u79FB" / "\u6267\u884C\u8FC1\u79FB" / "\u65E7\u9879\u76EE\u8FC1\u79FB" / "\u91CD\u5EFA\u8001\u5DE5\u7A0B"\uFF09\u540C\u6837\u8D70 \`teamix-evo-manage\` \u573A\u666F 6
2527
-
2528
- ## UI \u7EC4\u4EF6\u9886\u5730\uFF08init \u540E\u7EA6\u675F\uFF09
2529
-
2530
- - \`src/components/ui/\` \u7531 teamix-evo \u63A5\u7BA1\uFF0C\u7981\u6B62\u624B\u5DE5\u6216\u901A\u8FC7 shadcn CLI \u6DFB\u52A0\u65B0\u7EC4\u4EF6
2531
- - \u5982\u9700\u65B0\u589E UI \u7EC4\u4EF6\uFF0C\u4F7F\u7528 \`npx teamix-evo ui add <id>\`
2532
- - \`src/components/shadcn-ui/\` \u662F init \u524D legacy \u7EC4\u4EF6\u5F52\u6863\uFF0C\u53EA\u8BFB\uFF1B\u65B0\u4EE3\u7801\u4E0D\u5E94\u518D import
2533
- - \u5347\u7EA7 legacy \u7EC4\u4EF6\uFF1A\u89E6\u53D1 teamix-evo-upgrade skill
2534
-
2535
- > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
2536
- }
2537
- function wrapManagedBlock(body) {
2538
- return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
2539
- ${body}
2540
- <!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
2541
- }
2542
- var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
2543
- > \u51B2\u7A81\u4EE5\u4E0A\u65B9\u7684 **Skills** \u7D22\u5F15\u4E3A\u51C6\uFF08\u4E0A\u6E38\u8DEF\u5F84\u4E0E TRIGGER/SKIP \u5951\u7EA6\uFF09\uFF1B\u9879\u76EE\u7279\u6709\u7684\u4EBA\u5DE5\u7EC6\u5219\u8BF7\u5199\u5728\u672C\u5904\u4EE5\u4E0B\u3001\u4E0D\u8981\u8986\u76D6\u4E0A\u65B9 managed \u533A\u57DF\u3002`;
2544
- function extractDescriptionParts(fileContent) {
2545
- const description = extractDescriptionBlock(fileContent);
2546
- if (description == null) return null;
2547
- const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
2548
- let capability = "";
2549
- for (const line of allLines) {
2550
- if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
2551
- capability = capability ? `${capability} ${line}` : line;
2552
- }
2553
- return {
2554
- capability: capability.trim(),
2555
- trigger: extractSection(description, "TRIGGER when:"),
2556
- skip: extractSection(description, "SKIP:"),
2557
- coordinates: extractSection(description, "Coordinates with:")
2558
- };
2559
- }
2560
- function extractDescriptionBlock(fileContent) {
2561
- const lines = fileContent.split("\n");
2562
- if (lines[0]?.trim() !== "---") return null;
2563
- let endIdx = -1;
2564
- for (let i = 1; i < lines.length; i++) {
2565
- if (lines[i]?.trim() === "---") {
2566
- endIdx = i;
2567
- break;
2568
- }
2569
- }
2570
- if (endIdx === -1) return null;
2571
- const fmLines = lines.slice(1, endIdx);
2572
- let startIdx = -1;
2573
- let inlineValue = null;
2574
- let blockMode = "inline";
2575
- for (let i = 0; i < fmLines.length; i++) {
2576
- const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
2577
- if (m) {
2578
- startIdx = i;
2579
- const indicator = (m[1] ?? "").trim();
2580
- const rest = m[2] ?? "";
2581
- if (indicator.startsWith("|")) blockMode = "literal";
2582
- else if (indicator.startsWith(">")) blockMode = "folded";
2583
- else {
2584
- blockMode = "inline";
2585
- inlineValue = rest;
2555
+ "",
2556
+ "\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
2557
+ "",
2558
+ " extends: ['@teamix-evo/stylelint-config/presets/consumer']"
2559
+ ].join("\n")
2560
+ );
2586
2561
  }
2587
- break;
2562
+ } catch {
2588
2563
  }
2589
2564
  }
2590
- if (startIdx === -1) return null;
2591
- if (blockMode === "inline") {
2592
- return inlineValue ?? "";
2565
+ return {
2566
+ status: "installed",
2567
+ eslint: wroteEslint,
2568
+ stylelint: wroteStylelint,
2569
+ eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
2570
+ stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
2571
+ eslintSkipped: eslintSkipRequested,
2572
+ stylelintSkipped: stylelintSkipRequested,
2573
+ packageJsonPatched,
2574
+ stylelintIgnoreFilesWarning
2575
+ };
2576
+ }
2577
+ function detectPm(projectRoot) {
2578
+ if (fs11.existsSync(path13.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
2579
+ if (fs11.existsSync(path13.join(projectRoot, "yarn.lock"))) return "yarn";
2580
+ return "npm";
2581
+ }
2582
+ async function patchPackageJsonScripts(projectRoot) {
2583
+ const pkgPath = path13.join(projectRoot, "package.json");
2584
+ const raw = await readFileOrNull(pkgPath);
2585
+ if (!raw) return false;
2586
+ let pkg;
2587
+ try {
2588
+ pkg = JSON.parse(raw);
2589
+ } catch {
2590
+ return false;
2593
2591
  }
2594
- const body = [];
2595
- let blockIndent = -1;
2596
- for (let i = startIdx + 1; i < fmLines.length; i++) {
2597
- const line = fmLines[i] ?? "";
2598
- if (line.trim() === "") {
2599
- body.push("");
2600
- continue;
2601
- }
2602
- const indentMatch = line.match(/^(\s+)/);
2603
- const indent = indentMatch ? indentMatch[1].length : 0;
2604
- if (indent === 0) break;
2605
- if (blockIndent === -1) blockIndent = indent;
2606
- if (indent < blockIndent) break;
2607
- body.push(line.slice(blockIndent));
2592
+ const scripts = pkg.scripts ?? {};
2593
+ let changed = false;
2594
+ if (!scripts.lint) {
2595
+ scripts.lint = "eslint src/";
2596
+ changed = true;
2608
2597
  }
2609
- while (body.length > 0 && body[body.length - 1] === "") body.pop();
2610
- return body.join("\n");
2611
- }
2612
- function extractSection(description, marker) {
2613
- const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
2614
- const lines = description.split("\n");
2615
- let inSection = false;
2616
- const collected = [];
2617
- for (const line of lines) {
2618
- const trimmed = line.trim();
2619
- const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
2620
- if (!inSection && startsWithMarker) {
2621
- inSection = true;
2622
- collected.push(trimmed.slice(marker.length).trim());
2623
- continue;
2624
- }
2625
- if (inSection) {
2626
- const hitNextMarker = markers.some(
2627
- (m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
2628
- );
2629
- if (hitNextMarker) break;
2630
- if (trimmed === "") {
2631
- if (collected[collected.length - 1] === "") break;
2632
- collected.push("");
2633
- continue;
2634
- }
2635
- collected.push(trimmed);
2636
- }
2598
+ if (!scripts["lint:css"]) {
2599
+ scripts["lint:css"] = "stylelint 'src/**/*.css'";
2600
+ changed = true;
2637
2601
  }
2638
- if (!inSection) return null;
2639
- const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
2640
- return joined || null;
2602
+ if (changed) {
2603
+ await backupFile(pkgPath, projectRoot);
2604
+ pkg.scripts = scripts;
2605
+ await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
2606
+ logger.debug("Patched package.json scripts with lint / lint:css");
2607
+ }
2608
+ return changed;
2641
2609
  }
2642
2610
 
2643
2611
  // src/core/init-detect.ts
2644
- import * as fs11 from "fs/promises";
2645
- import * as path15 from "path";
2612
+ import * as fs12 from "fs/promises";
2613
+ import * as path14 from "path";
2646
2614
  var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
2647
2615
  ".git",
2648
2616
  ".gitignore",
@@ -2662,7 +2630,7 @@ var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
2662
2630
  "LICENSE.txt"
2663
2631
  ]);
2664
2632
  async function detectProjectState(cwd) {
2665
- const absCwd = path15.resolve(cwd);
2633
+ const absCwd = path14.resolve(cwd);
2666
2634
  const teamixDir = getTeamixDir(absCwd);
2667
2635
  const hasTeamixDir = await fileExists(teamixDir);
2668
2636
  if (hasTeamixDir) {
@@ -2670,13 +2638,13 @@ async function detectProjectState(cwd) {
2670
2638
  state: "teamix-evo-installed",
2671
2639
  cwd: absCwd,
2672
2640
  hasTeamixDir: true,
2673
- hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
2641
+ hasPackageJson: await fileExists(path14.join(absCwd, "package.json")),
2674
2642
  significantEntries: []
2675
2643
  };
2676
2644
  }
2677
2645
  let entries;
2678
2646
  try {
2679
- entries = await fs11.readdir(absCwd);
2647
+ entries = await fs12.readdir(absCwd);
2680
2648
  } catch (err) {
2681
2649
  if (err.code === "ENOENT") {
2682
2650
  return {
@@ -2709,9 +2677,151 @@ async function detectProjectState(cwd) {
2709
2677
  };
2710
2678
  }
2711
2679
 
2680
+ // src/core/project-state.ts
2681
+ import * as fs13 from "fs/promises";
2682
+ import * as path15 from "path";
2683
+ var IGNORED_TOP_LEVEL2 = /* @__PURE__ */ new Set([
2684
+ ".git",
2685
+ ".gitignore",
2686
+ ".gitattributes",
2687
+ ".gitkeep",
2688
+ ".DS_Store",
2689
+ "Thumbs.db",
2690
+ ".idea",
2691
+ ".vscode",
2692
+ ".qoder",
2693
+ ".claude",
2694
+ "README.md",
2695
+ "README",
2696
+ "README.txt",
2697
+ "LICENSE",
2698
+ "LICENSE.md",
2699
+ "LICENSE.txt"
2700
+ ]);
2701
+ var SHADCN_TRAIT_KEYS = ["$schema", "style", "rsc", "tsx", "aliases"];
2702
+ async function isShadcnComponentsJson(filePath) {
2703
+ const content = await readFileOrNull(filePath);
2704
+ if (!content) return false;
2705
+ try {
2706
+ const json = JSON.parse(content);
2707
+ return SHADCN_TRAIT_KEYS.some((key) => key in json);
2708
+ } catch {
2709
+ return false;
2710
+ }
2711
+ }
2712
+ async function detectProjectState2(cwd) {
2713
+ const absCwd = path15.resolve(cwd);
2714
+ const teamixDir = getTeamixDir(absCwd);
2715
+ const hasTeamixDir = await fileExists(teamixDir);
2716
+ if (hasTeamixDir) {
2717
+ return {
2718
+ state: "teamix-evo",
2719
+ cwd: absCwd,
2720
+ hasTeamixDir: true,
2721
+ hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
2722
+ hasComponentsJson: false,
2723
+ significantEntries: []
2724
+ };
2725
+ }
2726
+ const componentsJsonPath = path15.join(absCwd, "components.json");
2727
+ const hasPackageJson = await fileExists(path15.join(absCwd, "package.json"));
2728
+ const hasComponentsJson = await isShadcnComponentsJson(componentsJsonPath);
2729
+ if (hasPackageJson && hasComponentsJson) {
2730
+ return {
2731
+ state: "shadcn",
2732
+ cwd: absCwd,
2733
+ hasTeamixDir: false,
2734
+ hasPackageJson: true,
2735
+ hasComponentsJson: true,
2736
+ significantEntries: []
2737
+ };
2738
+ }
2739
+ let entries;
2740
+ try {
2741
+ entries = await fs13.readdir(absCwd);
2742
+ } catch (err) {
2743
+ if (err.code === "ENOENT") {
2744
+ return {
2745
+ state: "empty",
2746
+ cwd: absCwd,
2747
+ hasTeamixDir: false,
2748
+ hasPackageJson: false,
2749
+ hasComponentsJson: false,
2750
+ significantEntries: []
2751
+ };
2752
+ }
2753
+ throw err;
2754
+ }
2755
+ const significant = entries.filter((e) => !IGNORED_TOP_LEVEL2.has(e));
2756
+ if (significant.length === 0) {
2757
+ return {
2758
+ state: "empty",
2759
+ cwd: absCwd,
2760
+ hasTeamixDir: false,
2761
+ hasPackageJson: false,
2762
+ hasComponentsJson: false,
2763
+ significantEntries: []
2764
+ };
2765
+ }
2766
+ return {
2767
+ state: "other",
2768
+ cwd: absCwd,
2769
+ hasTeamixDir: false,
2770
+ hasPackageJson,
2771
+ hasComponentsJson,
2772
+ significantEntries: significant.slice(0, 20).sort()
2773
+ };
2774
+ }
2775
+
2776
+ // src/core/command-guard.ts
2777
+ var COMMAND_LABELS = {
2778
+ init: "\u521D\u59CB\u5316 Teamix Evo \u5957\u4EF6",
2779
+ migrate: "shadcn \u9879\u76EE\u8FC1\u79FB\u4E3A Teamix Evo \u5957\u4EF6",
2780
+ update: "Teamix Evo \u5957\u4EF6\u66F4\u65B0"
2781
+ };
2782
+ var STATE_LABELS = {
2783
+ empty: "\u7A7A\u76EE\u5F55",
2784
+ shadcn: "shadcn \u5DE5\u7A0B",
2785
+ "teamix-evo": "Teamix Evo \u5DE5\u7A0B",
2786
+ other: "\u666E\u901A npm \u5DE5\u7A0B"
2787
+ };
2788
+ var VALID_STATES = {
2789
+ init: "empty",
2790
+ migrate: "shadcn",
2791
+ update: "teamix-evo"
2792
+ };
2793
+ var STATE_TO_COMMAND = {
2794
+ empty: "init",
2795
+ shadcn: "migrate",
2796
+ "teamix-evo": "update",
2797
+ other: null
2798
+ };
2799
+ function assertCommandPrecondition(command, state) {
2800
+ const expected = VALID_STATES[command];
2801
+ if (state === expected) return;
2802
+ const stateLabel = STATE_LABELS[state];
2803
+ const commandLabel = COMMAND_LABELS[command];
2804
+ logger.error(
2805
+ `\u5F53\u524D\u9879\u76EE\u72B6\u6001\u4E3A\u300C${stateLabel}\u300D\uFF0C\u65E0\u6CD5\u6267\u884C\u300C${commandLabel}\u300D\u3002`
2806
+ );
2807
+ const suggestion = STATE_TO_COMMAND[state];
2808
+ if (suggestion) {
2809
+ const suggestionLabel = COMMAND_LABELS[suggestion];
2810
+ logger.info(`\u5EFA\u8BAE\u4F7F\u7528\uFF1Ateamix-evo ${suggestion}\uFF08${suggestionLabel}\uFF09`);
2811
+ } else {
2812
+ logger.info(
2813
+ "\u5F53\u524D\u9879\u76EE\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u76F4\u63A5\u63A5\u5165\u3002"
2814
+ );
2815
+ logger.info(
2816
+ "\u5EFA\u8BAE\uFF1A\u5148\u7528 teamix-evo init \u521D\u59CB\u5316 Teamix Evo \u5957\u4EF6\uFF08\u7A7A\u76EE\u5F55\uFF09\uFF0C\u7136\u540E\u5BF9\u65E7\u9879\u76EE\u8FDB\u884C\u91CD\u6784\u3002"
2817
+ );
2818
+ }
2819
+ process.exitCode = 1;
2820
+ }
2821
+
2712
2822
  // src/core/init-conflicts.ts
2713
2823
  import * as crypto from "crypto";
2714
- import * as fs12 from "fs/promises";
2824
+ import * as fs14 from "fs/promises";
2715
2825
  import * as path16 from "path";
2716
2826
  var TAILWIND_CONFIG_CANDIDATES = [
2717
2827
  "tailwind.config.ts",
@@ -2756,7 +2866,7 @@ var STYLELINT_CONFIG_CANDIDATES = [
2756
2866
  ];
2757
2867
  async function isDir(target) {
2758
2868
  try {
2759
- const stat3 = await fs12.stat(target);
2869
+ const stat3 = await fs14.stat(target);
2760
2870
  return stat3.isDirectory();
2761
2871
  } catch {
2762
2872
  return false;
@@ -2764,7 +2874,7 @@ async function isDir(target) {
2764
2874
  }
2765
2875
  async function dirHasContent(target) {
2766
2876
  try {
2767
- const entries = await fs12.readdir(target);
2877
+ const entries = await fs14.readdir(target);
2768
2878
  return entries.length > 0;
2769
2879
  } catch {
2770
2880
  return false;
@@ -2905,7 +3015,7 @@ async function detectShadcnSource(cwd) {
2905
3015
  try {
2906
3016
  const uiDir = path16.join(cwd, "src/components/ui");
2907
3017
  if (await isDir(uiDir)) {
2908
- const entries = await fs12.readdir(uiDir);
3018
+ const entries = await fs14.readdir(uiDir);
2909
3019
  componentCount = entries.filter(
2910
3020
  (e) => e.endsWith(".tsx") || e.endsWith(".ts")
2911
3021
  ).length;
@@ -2981,19 +3091,97 @@ async function detectStylelintConfig(cwd) {
2981
3091
  };
2982
3092
  }
2983
3093
 
2984
- // src/core/deps-install.ts
2985
- import * as fs13 from "fs/promises";
3094
+ // src/core/meta-installer.ts
2986
3095
  import * as path17 from "path";
3096
+ import * as fs15 from "fs/promises";
3097
+ import { createRequire as createRequire5 } from "module";
3098
+ import {
3099
+ loadUiPackageManifest as loadUiPackageManifest3,
3100
+ loadVariantUiPackageManifest as loadVariantUiPackageManifest2
3101
+ } from "@teamix-evo/registry";
3102
+ var require6 = createRequire5(import.meta.url);
3103
+ var META_DIR = "meta";
3104
+ function resolvePackageRoot4(packageName) {
3105
+ const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
3106
+ return path17.dirname(pkgJsonPath);
3107
+ }
3108
+ function getMetaDir(projectRoot, category) {
3109
+ const base = path17.join(getTeamixDir(projectRoot), META_DIR);
3110
+ return category ? path17.join(base, category) : base;
3111
+ }
3112
+ async function landUiMeta(projectRoot) {
3113
+ const packageRoot = resolvePackageRoot4("@teamix-evo/ui");
3114
+ const manifest = await loadUiPackageManifest3(packageRoot);
3115
+ return landManifestMeta({
3116
+ projectRoot,
3117
+ packageRoot,
3118
+ manifest,
3119
+ category: "ui"
3120
+ });
3121
+ }
3122
+ async function landBizUiMeta(projectRoot, variant) {
3123
+ const packageRoot = resolvePackageRoot4("@teamix-evo/biz-ui");
3124
+ const variantRoot = path17.join(packageRoot, "variants", variant);
3125
+ const manifest = await loadVariantUiPackageManifest2(variantRoot);
3126
+ return landManifestMeta({
3127
+ projectRoot,
3128
+ packageRoot: variantRoot,
3129
+ manifest,
3130
+ category: "biz-ui"
3131
+ });
3132
+ }
3133
+ async function landManifestMeta(opts) {
3134
+ const { projectRoot, packageRoot, manifest, category } = opts;
3135
+ await ensureTeamixDir(projectRoot);
3136
+ const metaDir = getMetaDir(projectRoot, category);
3137
+ await ensureDir(metaDir);
3138
+ const manifestDest = path17.join(metaDir, "manifest.json");
3139
+ const manifestSrc = path17.join(packageRoot, "manifest.json");
3140
+ await fs15.copyFile(manifestSrc, manifestDest);
3141
+ logger.debug(`meta: copied manifest.json \u2192 ${manifestDest}`);
3142
+ let written = 0;
3143
+ let total = 0;
3144
+ for (const entry of manifest.entries) {
3145
+ if (!entry.meta) continue;
3146
+ total++;
3147
+ const srcPath = path17.join(packageRoot, entry.meta);
3148
+ const destPath = path17.join(metaDir, `${entry.id}.md`);
3149
+ try {
3150
+ await fs15.copyFile(srcPath, destPath);
3151
+ written++;
3152
+ logger.debug(`meta: ${entry.id} \u2192 ${destPath}`);
3153
+ } catch (err) {
3154
+ if (err.code === "ENOENT") {
3155
+ logger.warn(`meta: ${entry.id} \u2014 source not found: ${srcPath}`);
3156
+ } else {
3157
+ throw err;
3158
+ }
3159
+ }
3160
+ }
3161
+ logger.info(
3162
+ ` meta/${category}: ${written}/${total} meta files, manifest.json`
3163
+ );
3164
+ return {
3165
+ category,
3166
+ manifestWritten: true,
3167
+ metaFilesWritten: written,
3168
+ metaFilesTotal: total
3169
+ };
3170
+ }
3171
+
3172
+ // src/core/deps-install.ts
3173
+ import * as fs16 from "fs/promises";
3174
+ import * as path18 from "path";
2987
3175
  import { exec } from "child_process";
2988
3176
  import { promisify } from "util";
2989
3177
  var execAsync = promisify(exec);
2990
3178
  async function detectPackageManager(projectRoot) {
2991
- if (await fileExists(path17.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
2992
- if (await fileExists(path17.join(projectRoot, "pnpm-workspace.yaml")))
3179
+ if (await fileExists(path18.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3180
+ if (await fileExists(path18.join(projectRoot, "pnpm-workspace.yaml")))
2993
3181
  return "pnpm";
2994
- if (await fileExists(path17.join(projectRoot, "bun.lockb"))) return "bun";
2995
- if (await fileExists(path17.join(projectRoot, "bun.lock"))) return "bun";
2996
- if (await fileExists(path17.join(projectRoot, "yarn.lock"))) return "yarn";
3182
+ if (await fileExists(path18.join(projectRoot, "bun.lockb"))) return "bun";
3183
+ if (await fileExists(path18.join(projectRoot, "bun.lock"))) return "bun";
3184
+ if (await fileExists(path18.join(projectRoot, "yarn.lock"))) return "yarn";
2997
3185
  return "npm";
2998
3186
  }
2999
3187
  function getInstallCommand(pm) {
@@ -3010,7 +3198,7 @@ function getInstallCommand(pm) {
3010
3198
  }
3011
3199
  async function installProjectDeps(options) {
3012
3200
  const { projectRoot, npmDependencies, skipInstall = false } = options;
3013
- const pkgPath = path17.join(projectRoot, "package.json");
3201
+ const pkgPath = path18.join(projectRoot, "package.json");
3014
3202
  const raw = await readFileOrNull(pkgPath);
3015
3203
  if (!raw) {
3016
3204
  throw new Error(
@@ -3039,7 +3227,7 @@ async function installProjectDeps(options) {
3039
3227
  pkg.dependencies = Object.fromEntries(
3040
3228
  Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
3041
3229
  );
3042
- await fs13.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
3230
+ await fs16.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
3043
3231
  logger.info(
3044
3232
  ` patched package.json: +${Object.keys(added).length} dependencies`
3045
3233
  );
@@ -3101,19 +3289,19 @@ ${stepLines}
3101
3289
  }
3102
3290
 
3103
3291
  // src/core/file-changes.ts
3104
- import * as fs14 from "fs/promises";
3105
- import * as path18 from "path";
3292
+ import * as fs17 from "fs/promises";
3293
+ import * as path19 from "path";
3106
3294
  function toRelativePosix(p, projectRoot) {
3107
3295
  let rel2 = p;
3108
- if (path18.isAbsolute(p)) {
3109
- rel2 = path18.relative(projectRoot, p);
3296
+ if (path19.isAbsolute(p)) {
3297
+ rel2 = path19.relative(projectRoot, p);
3110
3298
  }
3111
- return rel2.split(path18.sep).join("/");
3299
+ return rel2.split(path19.sep).join("/");
3112
3300
  }
3113
3301
 
3114
3302
  // src/core/project-init.ts
3115
3303
  import * as fsNode from "fs/promises";
3116
- import * as path19 from "path";
3304
+ import * as path20 from "path";
3117
3305
  var CRITICAL_STEPS = /* @__PURE__ */ new Set([
3118
3306
  "tokens",
3119
3307
  "skills",
@@ -3124,7 +3312,6 @@ var GITIGNORE_MARKER_END = "# <<< teamix-evo:managed <<<";
3124
3312
  var GITIGNORE_RULES = [
3125
3313
  "# Runtime artifacts (regenerable / archives)",
3126
3314
  ".teamix-evo/.snapshots/",
3127
- ".teamix-evo/logs/",
3128
3315
  ".teamix-evo/.backups/",
3129
3316
  ".teamix-evo/.upgrade-staging/",
3130
3317
  ".teamix-evo/.upgrade-hints/"
@@ -3206,7 +3393,7 @@ async function runProjectInit(options) {
3206
3393
  detail: `${result.skillCount} skills, ${result.fileCount} files (added: ${result.addedSkillIds.join(", ") || "none"})`,
3207
3394
  changes: result.addedSkillIds.map((id) => ({
3208
3395
  kind: "created",
3209
- path: `.teamix-evo/skills/${id}/SKILL.md`,
3396
+ path: `.${ides[0] ?? "qoder"}/skills/${id}/SKILL.md`,
3210
3397
  step: "skills",
3211
3398
  detail: "skill installed"
3212
3399
  }))
@@ -3363,6 +3550,31 @@ async function runProjectInit(options) {
3363
3550
  recordFailure("biz-ui-add", err);
3364
3551
  }
3365
3552
  }
3553
+ if (dryRun) {
3554
+ record({
3555
+ name: "meta-landing",
3556
+ status: "planned",
3557
+ detail: `landUiMeta() + landBizUiMeta(${variant})`
3558
+ });
3559
+ } else if (aborted) {
3560
+ record({
3561
+ name: "meta-landing",
3562
+ status: "skip",
3563
+ detail: "aborted: earlier critical step failed"
3564
+ });
3565
+ } else {
3566
+ try {
3567
+ const uiResult = await landUiMeta(projectRoot);
3568
+ const bizResult = await landBizUiMeta(projectRoot, variant);
3569
+ record({
3570
+ name: "meta-landing",
3571
+ status: "ok",
3572
+ detail: `ui: ${uiResult.metaFilesWritten}/${uiResult.metaFilesTotal}, biz-ui: ${bizResult.metaFilesWritten}/${bizResult.metaFilesTotal}`
3573
+ });
3574
+ } catch (err) {
3575
+ recordFailure("meta-landing", err);
3576
+ }
3577
+ }
3366
3578
  if (!dryRun && !aborted && Object.keys(collectedNpmDeps).length > 0) {
3367
3579
  try {
3368
3580
  await installProjectDeps({
@@ -3430,7 +3642,7 @@ async function runProjectInit(options) {
3430
3642
  detail: "projectRoot does not exist"
3431
3643
  });
3432
3644
  } else {
3433
- const giPath = path19.join(projectRoot, ".gitignore");
3645
+ const giPath = path20.join(projectRoot, ".gitignore");
3434
3646
  let giContent = "";
3435
3647
  try {
3436
3648
  giContent = await fsNode.readFile(giPath, "utf-8");
@@ -3486,15 +3698,16 @@ async function runProjectInit(options) {
3486
3698
  resumeCommand: "teamix-evo init"
3487
3699
  };
3488
3700
  }
3489
- if (!dryRun) {
3701
+ const hasFailures = steps.some((s) => s.status === "fail" || s.status === "skip");
3702
+ if (!dryRun && hasFailures) {
3490
3703
  try {
3491
3704
  const checklistContent = renderInitChecklist({ variant, status, steps });
3492
- const checklistPath = path19.join(
3705
+ const checklistPath = path20.join(
3493
3706
  projectRoot,
3494
3707
  ".teamix-evo",
3495
3708
  "init-checklist.md"
3496
3709
  );
3497
- await fsNode.mkdir(path19.dirname(checklistPath), { recursive: true });
3710
+ await fsNode.mkdir(path20.dirname(checklistPath), { recursive: true });
3498
3711
  await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
3499
3712
  logger.info(" wrote .teamix-evo/init-checklist.md");
3500
3713
  } catch {
@@ -3534,8 +3747,8 @@ function deriveLintChanges(result) {
3534
3747
  }
3535
3748
 
3536
3749
  // src/core/installer.ts
3537
- import * as path20 from "path";
3538
- import * as fs15 from "fs/promises";
3750
+ import * as path21 from "path";
3751
+ import * as fs18 from "fs/promises";
3539
3752
  async function installResources(options) {
3540
3753
  const { projectRoot, manifest, data, variantDir, packageRoot } = options;
3541
3754
  const installedResources = [];
@@ -3572,13 +3785,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
3572
3785
  variantDir,
3573
3786
  packageRoot
3574
3787
  );
3575
- const targetPath = path20.join(projectRoot, resource.target);
3788
+ const targetPath = path21.join(projectRoot, resource.target);
3576
3789
  let content;
3577
3790
  if (resource.template) {
3578
3791
  const templateContent = await loadTemplateFile(sourcePath);
3579
3792
  content = renderTemplate(templateContent, data);
3580
3793
  } else {
3581
- content = await fs15.readFile(sourcePath, "utf-8");
3794
+ content = await fs18.readFile(sourcePath, "utf-8");
3582
3795
  }
3583
3796
  await writeFileSafe(targetPath, content);
3584
3797
  const hash = computeHash(content);
@@ -3596,13 +3809,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3596
3809
  variantDir,
3597
3810
  packageRoot
3598
3811
  );
3599
- const targetDir = path20.join(projectRoot, resource.target);
3812
+ const targetDir = path21.join(projectRoot, resource.target);
3600
3813
  const results = [];
3601
3814
  await ensureDir(targetDir);
3602
3815
  const entries = await walkDir(sourcePath);
3603
3816
  for (const entry of entries) {
3604
- const relPath = path20.relative(sourcePath, entry);
3605
- let targetFile = path20.join(targetDir, relPath);
3817
+ const relPath = path21.relative(sourcePath, entry);
3818
+ let targetFile = path21.join(targetDir, relPath);
3606
3819
  if (resource.template && targetFile.endsWith(".hbs")) {
3607
3820
  targetFile = targetFile.slice(0, -4);
3608
3821
  }
@@ -3611,11 +3824,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3611
3824
  const templateContent = await loadTemplateFile(entry);
3612
3825
  content = renderTemplate(templateContent, data);
3613
3826
  } else {
3614
- content = await fs15.readFile(entry, "utf-8");
3827
+ content = await fs18.readFile(entry, "utf-8");
3615
3828
  }
3616
3829
  await writeFileSafe(targetFile, content);
3617
3830
  const hash = computeHash(content);
3618
- const targetRel = path20.relative(projectRoot, targetFile);
3831
+ const targetRel = path21.relative(projectRoot, targetFile);
3619
3832
  results.push({
3620
3833
  id: `${resource.id}:${relPath}`,
3621
3834
  target: targetRel,
@@ -3628,25 +3841,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3628
3841
  }
3629
3842
 
3630
3843
  // src/core/registry-client.ts
3631
- import * as path21 from "path";
3632
- import * as fs16 from "fs/promises";
3633
- import { createRequire as createRequire5 } from "module";
3844
+ import * as path22 from "path";
3845
+ import * as fs19 from "fs/promises";
3846
+ import { createRequire as createRequire6 } from "module";
3634
3847
  import { loadVariantManifest } from "@teamix-evo/registry";
3635
- var require6 = createRequire5(import.meta.url);
3636
- function resolvePackageRoot4(packageName) {
3637
- const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
3638
- return path21.dirname(pkgJsonPath);
3848
+ var require7 = createRequire6(import.meta.url);
3849
+ function resolvePackageRoot5(packageName) {
3850
+ const pkgJsonPath = require7.resolve(`${packageName}/package.json`);
3851
+ return path22.dirname(pkgJsonPath);
3639
3852
  }
3640
3853
  async function loadVariantData(packageName, variant) {
3641
- const packageRoot = resolvePackageRoot4(packageName);
3642
- const variantDir = path21.join(packageRoot, "library", variant);
3854
+ const packageRoot = resolvePackageRoot5(packageName);
3855
+ const variantDir = path22.join(packageRoot, "library", variant);
3643
3856
  logger.debug(`Resolved variant dir: ${variantDir}`);
3644
3857
  logger.debug(`Package root: ${packageRoot}`);
3645
3858
  const manifest = await loadVariantManifest(variantDir);
3646
3859
  let data = {};
3647
- const dataPath = path21.join(variantDir, "_data.json");
3860
+ const dataPath = path22.join(variantDir, "_data.json");
3648
3861
  try {
3649
- const raw = await fs16.readFile(dataPath, "utf-8");
3862
+ const raw = await fs19.readFile(dataPath, "utf-8");
3650
3863
  data = JSON.parse(raw);
3651
3864
  } catch (err) {
3652
3865
  if (err.code !== "ENOENT") {
@@ -3659,8 +3872,10 @@ async function loadVariantData(packageName, variant) {
3659
3872
  export {
3660
3873
  DEFAULT_UI_ALIASES,
3661
3874
  DEFAULT_UI_ICON_LIBRARY,
3875
+ assertCommandPrecondition,
3662
3876
  detectConflicts,
3663
- detectProjectState,
3877
+ detectProjectState2 as detectProjectState,
3878
+ detectProjectState as detectProjectStateLegacy,
3664
3879
  ensureTeamixDir,
3665
3880
  extractDescriptionParts,
3666
3881
  getTeamixDir,
@@ -3669,14 +3884,13 @@ export {
3669
3884
  installUiEntries,
3670
3885
  listBizUiEntries,
3671
3886
  listBizUiVariants,
3672
- listTemplatesEntries,
3673
- listTemplatesVariants,
3674
3887
  listTokenVariants,
3675
3888
  loadSkillsData,
3676
3889
  loadUiData,
3677
3890
  loadVariantData,
3678
3891
  readInstalledManifest,
3679
3892
  readProjectConfig,
3893
+ reinstallSkillsToIdes,
3680
3894
  removeSkillFiles,
3681
3895
  removeUiFiles,
3682
3896
  runBizUiAdd,
@@ -3686,12 +3900,10 @@ export {
3686
3900
  runSkillsAdd,
3687
3901
  runSkillsInit,
3688
3902
  runSkillsUpdate,
3689
- runTemplatesAdd,
3690
3903
  runTokensInit,
3691
3904
  runUiAdd,
3692
3905
  runUiInit,
3693
3906
  runUiList,
3694
- syncSkillsToIdes,
3695
3907
  updateSkills,
3696
3908
  writeInstalledManifest,
3697
3909
  writeProjectConfig