teamix-evo 0.13.4 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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
  }
@@ -194,51 +197,61 @@ async function readTokensVariant(projectRoot) {
194
197
  const lock = await readTokensLock(projectRoot);
195
198
  return lock?.variant.name ?? null;
196
199
  }
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
200
  async function readSkillsLock(projectRoot) {
205
- const lockPath = path2.join(
206
- projectRoot,
207
- TEAMIX_DIR,
208
- SKILLS_DIR,
209
- SKILLS_LOCK_FILE
210
- );
201
+ const lockPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
211
202
  const raw = await readFileOrNull(lockPath);
212
203
  if (raw === null) return null;
213
204
  try {
214
205
  const data = JSON.parse(raw);
215
206
  const result = validateSkillsLock(data);
216
207
  if (!result.success) {
217
- logger.warn(`Invalid skills manifest.lock.json: ${result.error}`);
208
+ logger.warn(`Invalid skills-lock.json: ${result.error}`);
218
209
  return null;
219
210
  }
220
211
  return result.data;
221
212
  } catch (err) {
222
213
  logger.warn(
223
- `Failed to parse skills manifest.lock.json: ${getErrorMessage(err)}`
214
+ `Failed to parse skills-lock.json: ${getErrorMessage(err)}`
224
215
  );
225
216
  return null;
226
217
  }
227
218
  }
228
219
  async function writeSkillsLock(projectRoot, lock) {
229
- const lockPath = path2.join(
230
- projectRoot,
231
- TEAMIX_DIR,
232
- SKILLS_DIR,
233
- SKILLS_LOCK_FILE
234
- );
220
+ const lockPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
235
221
  await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
236
222
  logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
237
223
  }
224
+ async function migrateSkillsLockLocation(projectRoot) {
225
+ const newPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
226
+ try {
227
+ await fs2.access(newPath);
228
+ } catch {
229
+ for (const legacyRel of LEGACY_SKILLS_LOCK_PATHS) {
230
+ const legacyPath = path2.join(projectRoot, TEAMIX_DIR, legacyRel);
231
+ try {
232
+ await fs2.access(legacyPath);
233
+ await ensureDir(path2.dirname(newPath));
234
+ await fs2.rename(legacyPath, newPath);
235
+ logger.debug(`Migrated skills lock: ${legacyRel} \u2192 ${SKILLS_LOCK_FILE}`);
236
+ break;
237
+ } catch {
238
+ continue;
239
+ }
240
+ }
241
+ }
242
+ for (const staleDir of ["skills-source", "skills"]) {
243
+ const abs = path2.join(projectRoot, TEAMIX_DIR, staleDir);
244
+ try {
245
+ await fs2.rm(abs, { recursive: true, force: true });
246
+ logger.debug(`Cleaned up stale dir: .teamix-evo/${staleDir}/`);
247
+ } catch {
248
+ }
249
+ }
250
+ }
238
251
 
239
252
  // src/core/skills-client.ts
240
253
  import * as path3 from "path";
241
- import * as fs2 from "fs/promises";
254
+ import * as fs3 from "fs/promises";
242
255
  import { createRequire } from "module";
243
256
  import { loadSkillsPackageManifest } from "@teamix-evo/registry";
244
257
  var require2 = createRequire(import.meta.url);
@@ -253,7 +266,7 @@ async function loadSkillsData(packageName) {
253
266
  let data = {};
254
267
  const dataPath = path3.join(packageRoot, "_data.json");
255
268
  try {
256
- const raw = await fs2.readFile(dataPath, "utf-8");
269
+ const raw = await fs3.readFile(dataPath, "utf-8");
257
270
  data = JSON.parse(raw);
258
271
  } catch (err) {
259
272
  if (err.code !== "ENOENT") {
@@ -266,7 +279,7 @@ async function loadSkillsData(packageName) {
266
279
 
267
280
  // src/core/skills-installer.ts
268
281
  import * as path7 from "path";
269
- import * as fs5 from "fs/promises";
282
+ import * as fs6 from "fs/promises";
270
283
  import {
271
284
  replaceManagedRegion,
272
285
  hasManagedRegion,
@@ -326,7 +339,7 @@ function getAdapter(kind) {
326
339
 
327
340
  // src/utils/template.ts
328
341
  import Handlebars from "handlebars";
329
- import * as fs3 from "fs/promises";
342
+ import * as fs4 from "fs/promises";
330
343
  Handlebars.registerHelper("lowercase", (str) => {
331
344
  return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
332
345
  });
@@ -349,12 +362,12 @@ function renderTemplate(templateContent, data) {
349
362
  return compiled(data);
350
363
  }
351
364
  async function loadTemplateFile(filePath) {
352
- return fs3.readFile(filePath, "utf-8");
365
+ return fs4.readFile(filePath, "utf-8");
353
366
  }
354
367
 
355
368
  // src/utils/path.ts
356
369
  import * as path6 from "path";
357
- import * as fs4 from "fs/promises";
370
+ import * as fs5 from "fs/promises";
358
371
  import { createRequire as createRequire2 } from "module";
359
372
  var require3 = createRequire2(import.meta.url);
360
373
  function resolveSourcePath(source, variantDir, packageRoot) {
@@ -365,7 +378,7 @@ function resolveSourcePath(source, variantDir, packageRoot) {
365
378
  }
366
379
  async function walkDir(dir, skipDirs) {
367
380
  const files = [];
368
- const entries = await fs4.readdir(dir, { withFileTypes: true });
381
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
369
382
  for (const entry of entries) {
370
383
  const fullPath = path6.join(dir, entry.name);
371
384
  if (entry.isDirectory()) {
@@ -381,10 +394,13 @@ function resolveTokensPackageRoot(packageName) {
381
394
  const pkgJson = require3.resolve(`${packageName}/package.json`);
382
395
  return path6.dirname(pkgJson);
383
396
  }
397
+ function resolveResourceTarget(projectRoot, target) {
398
+ return path6.isAbsolute(target) ? target : path6.resolve(projectRoot, target);
399
+ }
384
400
 
385
401
  // src/core/skills-installer.ts
386
402
  async function installSkills(options) {
387
- await migrateLegacySkillsSourceDir(options.projectRoot);
403
+ await migrateSkillsLockLocation(options.projectRoot);
388
404
  const { manifest, ides, scope, onlyIds } = options;
389
405
  const installed = [];
390
406
  const targets = manifest.skills.filter(
@@ -400,75 +416,61 @@ async function installSkills(options) {
400
416
  );
401
417
  continue;
402
418
  }
403
- const sourceRecords = await writeSkillSource(skill, options);
404
- installed.push(...sourceRecords);
419
+ const rendered = await renderSkillFiles(skill, options);
405
420
  for (const ide of skillIdes) {
406
- const mirrorRecords = await mirrorSkillToIde(
421
+ const records = await writeRenderedToIde(
407
422
  skill,
423
+ rendered,
408
424
  ide,
409
425
  scope,
410
426
  options.projectRoot
411
427
  );
412
- installed.push(...mirrorRecords);
428
+ installed.push(...records);
413
429
  }
414
430
  }
415
431
  return { resources: installed, count: installed.length };
416
432
  }
417
- async function writeSkillSource(skill, options) {
418
- const { data, packageRoot, projectRoot } = options;
433
+ async function renderSkillFiles(skill, options) {
434
+ const { data, packageRoot } = options;
419
435
  const sourceAbs = path7.resolve(packageRoot, skill.source);
420
- const targetDir = getSkillsSourceDir(projectRoot, skill.name);
421
- const stat3 = await fs5.stat(sourceAbs);
422
- const records = [];
436
+ const stat3 = await fs6.stat(sourceAbs);
437
+ const rendered = /* @__PURE__ */ new Map();
423
438
  if (stat3.isFile()) {
424
- const targetFile = path7.join(targetDir, "SKILL.md");
425
439
  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;
440
+ rendered.set("SKILL.md", content);
441
+ return rendered;
430
442
  }
431
- await ensureDir(targetDir);
432
443
  const entries = await walkDir(sourceAbs);
433
444
  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);
445
+ let rel2 = path7.relative(sourceAbs, entry);
446
+ if (skill.template && rel2.endsWith(".hbs")) {
447
+ rel2 = rel2.slice(0, -4);
438
448
  }
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}`);
449
+ const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs6.readFile(entry, "utf-8");
450
+ rendered.set(rel2, content);
444
451
  }
445
- return records;
452
+ return rendered;
446
453
  }
447
- async function mirrorSkillToIde(skill, ide, scope, projectRoot) {
448
- const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
454
+ async function writeRenderedToIde(skill, rendered, ide, scope, projectRoot) {
449
455
  const adapter = getAdapter(ide);
450
456
  const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
451
457
  const records = [];
452
- const sourceFiles = await walkDir(sourceDir);
453
458
  await ensureDir(targetDir);
454
- for (const src of sourceFiles) {
455
- const rel2 = path7.relative(sourceDir, src);
459
+ for (const [rel2, sourceContent] of rendered) {
456
460
  const targetFile = path7.join(targetDir, rel2);
457
- const sourceContent = await fs5.readFile(src, "utf-8");
458
461
  const writtenContent = await writeMirrorContent(
459
462
  targetFile,
460
463
  sourceContent,
461
- skill.managedRegions,
462
- src
464
+ skill.managedRegions
463
465
  );
464
466
  records.push(
465
- makeMirrorRecord(skill, targetFile, writtenContent, ide, scope, rel2)
467
+ makeMirrorRecord(skill, projectRoot, targetFile, writtenContent, ide, scope, rel2)
466
468
  );
467
- logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
469
+ logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
468
470
  }
469
471
  return records;
470
472
  }
471
- async function writeMirrorContent(targetFile, sourceContent, managedRegions, sourceFile) {
473
+ async function writeMirrorContent(targetFile, sourceContent, managedRegions) {
472
474
  const existing = await readFileOrNull(targetFile);
473
475
  if (existing === null) {
474
476
  await writeFileSafe(targetFile, sourceContent);
@@ -479,7 +481,7 @@ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sou
479
481
  if (matchedRegions.length === 0) {
480
482
  if (existing !== sourceContent) {
481
483
  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\`.`
484
+ `Drift detected at ${targetFile} \u2014 overwriting from upstream. Re-run \`npx teamix-evo@latest skills sync\` after any manual edits.`
483
485
  );
484
486
  await writeFileSafe(targetFile, sourceContent);
485
487
  return sourceContent;
@@ -521,22 +523,13 @@ async function renderSkillContent(sourceAbs, skill, data) {
521
523
  const tpl = await loadTemplateFile(sourceAbs);
522
524
  return renderTemplate(tpl, { ...data, skill });
523
525
  }
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
- };
526
+ return fs6.readFile(sourceAbs, "utf-8");
534
527
  }
535
- function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
528
+ function makeMirrorRecord(skill, projectRoot, targetAbs, content, ide, scope, rel2) {
536
529
  const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
537
530
  return {
538
531
  id,
539
- target: targetAbs,
532
+ target: path7.relative(projectRoot, targetAbs),
540
533
  hash: computeHash(content),
541
534
  strategy: skill.updateStrategy,
542
535
  ide,
@@ -552,64 +545,41 @@ async function updateSkills(options) {
552
545
  if (idFilter && !idFilter.has(skill.id)) continue;
553
546
  const skillIdes = skill.ides.filter((i) => ides.includes(i));
554
547
  if (skillIdes.length === 0) continue;
555
- const sourceRecords = await rewriteSkillSource(skill, options, summary);
556
- updated.push(...sourceRecords);
548
+ const rendered = await renderSkillFiles(skill, options);
557
549
  for (const ide of skillIdes) {
558
- const mirrorRecords = await mirrorSkillToIde(
550
+ const records = await updateRenderedInIde(
559
551
  skill,
552
+ rendered,
560
553
  ide,
561
554
  scope,
562
- projectRoot
555
+ projectRoot,
556
+ summary
563
557
  );
564
- updated.push(...mirrorRecords);
558
+ updated.push(...records);
565
559
  }
566
560
  }
567
561
  return { resources: updated, summary };
568
562
  }
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;
563
+ async function updateRenderedInIde(skill, rendered, ide, scope, projectRoot, summary) {
564
+ const adapter = getAdapter(ide);
565
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
566
+ const records = [];
567
+ await ensureDir(targetDir);
568
+ for (const [rel2, newContent] of rendered) {
569
+ const targetFile = path7.join(targetDir, rel2);
570
+ const exists = await fileExists(targetFile);
571
+ const written = await rewriteSingleFile({
572
+ targetFile,
573
+ newContent,
574
+ exists,
575
+ updateStrategy: skill.updateStrategy,
576
+ managedRegions: skill.managedRegions,
577
+ projectRoot,
578
+ summary
579
+ });
580
+ records.push(makeMirrorRecord(skill, projectRoot, targetFile, written, ide, scope, rel2));
599
581
  }
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)];
582
+ return records;
613
583
  }
614
584
  async function rewriteSingleFile(args) {
615
585
  const {
@@ -676,41 +646,30 @@ async function rewriteSingleFile(args) {
676
646
  function escapeRegExp(str) {
677
647
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
678
648
  }
679
- async function syncSkillsToIdes(options) {
680
- await migrateLegacySkillsSourceDir(options.projectRoot);
681
- const { projectRoot, skills, ides, scope, onlyIds } = options;
649
+ async function reinstallSkillsToIdes(options) {
650
+ const { projectRoot, skills, manifest, data, packageRoot, ides, scope } = options;
682
651
  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
- );
652
+ for (const skill of skills) {
653
+ const entry = manifest.skills.find((s) => s.id === skill.id);
654
+ if (!entry) {
655
+ logger.warn(`Skill "${skill.id}" not found in npm package manifest; skipped.`);
690
656
  continue;
691
657
  }
658
+ const rendered = await renderSkillFiles(entry, { data, packageRoot });
692
659
  for (const ide of ides) {
693
660
  const adapter = getAdapter(ide);
694
- const targetDir = adapter.getSkillTargetDir(
695
- skill.name,
696
- scope,
697
- projectRoot
698
- );
661
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
699
662
  await ensureDir(targetDir);
700
- const sourceFiles = await walkDir(sourceDir);
701
- for (const src of sourceFiles) {
702
- const rel2 = path7.relative(sourceDir, src);
663
+ for (const [rel2, sourceContent] of rendered) {
703
664
  const targetFile = path7.join(targetDir, rel2);
704
- const sourceContent = await fs5.readFile(src, "utf-8");
705
665
  const writtenContent = await writeMirrorContent(
706
666
  targetFile,
707
667
  sourceContent,
708
- skill.managedRegions,
709
- src
668
+ skill.managedRegions
710
669
  );
711
670
  out.push({
712
671
  id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
713
- target: targetFile,
672
+ target: path7.relative(projectRoot, targetFile),
714
673
  hash: computeHash(writtenContent),
715
674
  strategy: skill.updateStrategy,
716
675
  ide,
@@ -721,75 +680,6 @@ async function syncSkillsToIdes(options) {
721
680
  }
722
681
  return { resources: out, count: out.length };
723
682
  }
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
683
  async function pruneEmptyIdeSkillDirs(args) {
794
684
  const removed = [];
795
685
  for (const ide of args.ides) {
@@ -802,7 +692,7 @@ async function pruneEmptyIdeSkillDirs(args) {
802
692
  const skillsRoot = path7.dirname(placeholderDir);
803
693
  let entries;
804
694
  try {
805
- entries = await fs5.readdir(skillsRoot);
695
+ entries = await fs6.readdir(skillsRoot);
806
696
  } catch {
807
697
  continue;
808
698
  }
@@ -810,21 +700,21 @@ async function pruneEmptyIdeSkillDirs(args) {
810
700
  const dir = path7.join(skillsRoot, name);
811
701
  let stat3;
812
702
  try {
813
- stat3 = await fs5.stat(dir);
703
+ stat3 = await fs6.stat(dir);
814
704
  } catch {
815
705
  continue;
816
706
  }
817
707
  if (!stat3.isDirectory()) continue;
818
708
  let children;
819
709
  try {
820
- children = await fs5.readdir(dir);
710
+ children = await fs6.readdir(dir);
821
711
  } catch {
822
712
  continue;
823
713
  }
824
714
  if (children.some((c) => c === "SKILL.md")) continue;
825
715
  if (children.length !== 0) continue;
826
716
  try {
827
- await fs5.rmdir(dir);
717
+ await fs6.rmdir(dir);
828
718
  removed.push(dir);
829
719
  logger.debug(`Pruned empty IDE skill dir: ${dir}`);
830
720
  } catch {
@@ -833,26 +723,29 @@ async function pruneEmptyIdeSkillDirs(args) {
833
723
  }
834
724
  return removed;
835
725
  }
836
- async function removeSkillFiles(records) {
726
+ async function removeSkillFiles(records, projectRoot) {
837
727
  const removed = [];
838
728
  for (const r of records) {
729
+ const abs = resolveResourceTarget(projectRoot, r.target);
839
730
  try {
840
- await fs5.unlink(r.target);
841
- removed.push(r.target);
731
+ await fs6.unlink(abs);
732
+ removed.push(abs);
842
733
  } catch (err) {
843
734
  if (err.code !== "ENOENT") {
844
- logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
735
+ logger.warn(`Failed to remove ${abs}: ${getErrorMessage(err)}`);
845
736
  }
846
737
  }
847
738
  }
848
- const startDirs = new Set(records.map((r) => path7.dirname(r.target)));
739
+ const startDirs = new Set(
740
+ records.map((r) => path7.dirname(resolveResourceTarget(projectRoot, r.target)))
741
+ );
849
742
  for (const startDir of startDirs) {
850
743
  let dir = startDir;
851
744
  for (let depth = 0; depth < 8; depth++) {
852
745
  try {
853
- const entries = await fs5.readdir(dir);
746
+ const entries = await fs6.readdir(dir);
854
747
  if (entries.length !== 0) break;
855
- await fs5.rmdir(dir);
748
+ await fs6.rmdir(dir);
856
749
  } catch {
857
750
  break;
858
751
  }
@@ -862,32 +755,6 @@ async function removeSkillFiles(records) {
862
755
  return removed;
863
756
  }
864
757
 
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
758
  // src/core/skills-add.ts
892
759
  var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
893
760
  var FLAT_VARIANT = "_flat";
@@ -1201,7 +1068,6 @@ async function finalizeSkillsInstall(args) {
1201
1068
  };
1202
1069
  }
1203
1070
  await writeSkillsLock(projectRoot, lock);
1204
- await ensureMcpJson(projectRoot);
1205
1071
  try {
1206
1072
  await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
1207
1073
  } catch {
@@ -1346,7 +1212,7 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1346
1212
  const result = await installVariantFile(fileRel, packageRoot, projectRoot);
1347
1213
  if (result) installed.push(result);
1348
1214
  }
1349
- const overridesAbs = path9.join(
1215
+ const overridesAbs = path8.join(
1350
1216
  projectRoot,
1351
1217
  CONSUMER_TOKENS_DIR,
1352
1218
  CONSUMER_OVERRIDES_FILE
@@ -1356,10 +1222,10 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1356
1222
  }
1357
1223
  const overridesId = `tokens:${CONSUMER_OVERRIDES_FILE}`;
1358
1224
  if (!installed.some((r) => r.id === overridesId)) {
1359
- const overridesContent = await fs6.readFile(overridesAbs, "utf-8");
1225
+ const overridesContent = await fs7.readFile(overridesAbs, "utf-8");
1360
1226
  installed.push({
1361
1227
  id: overridesId,
1362
- target: path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
1228
+ target: path8.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
1363
1229
  hash: computeHash(overridesContent),
1364
1230
  strategy: "frozen"
1365
1231
  });
@@ -1377,7 +1243,7 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1377
1243
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
1378
1244
  };
1379
1245
  await writeFileSafe(
1380
- path9.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
1246
+ path8.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
1381
1247
  JSON.stringify(lock, null, 2) + "\n"
1382
1248
  );
1383
1249
  const config = {
@@ -1409,7 +1275,6 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1409
1275
  if (tokensIdx >= 0) prior.installed[tokensIdx] = tokensEntry;
1410
1276
  else prior.installed.push(tokensEntry);
1411
1277
  await writeInstalledManifest(projectRoot, prior);
1412
- await ensureMcpJson(projectRoot);
1413
1278
  const skills = await tryAutoInstallVariantSkills({
1414
1279
  projectRoot,
1415
1280
  variant,
@@ -1498,12 +1363,12 @@ async function tryAutoInstallVariantSkills(args) {
1498
1363
  }
1499
1364
  }
1500
1365
  async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1501
- const sourceAbs = path9.join(packageRoot, fileRelToPackage);
1502
- const base = path9.basename(fileRelToPackage);
1366
+ const sourceAbs = path8.join(packageRoot, fileRelToPackage);
1367
+ const base = path8.basename(fileRelToPackage);
1503
1368
  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");
1369
+ const targetRel = path8.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
1370
+ const targetAbs = path8.join(projectRoot, targetRel);
1371
+ const content = await fs7.readFile(sourceAbs, "utf-8");
1507
1372
  if (await fileExists(targetAbs)) {
1508
1373
  await backupFile(targetAbs, projectRoot);
1509
1374
  }
@@ -1516,13 +1381,13 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1516
1381
  };
1517
1382
  }
1518
1383
  if (base === "overrides.css" || base === "tokens.overrides.css") {
1519
- const targetRel = path9.posix.join(
1384
+ const targetRel = path8.posix.join(
1520
1385
  CONSUMER_TOKENS_DIR,
1521
1386
  CONSUMER_OVERRIDES_FILE
1522
1387
  );
1523
- const targetAbs = path9.join(projectRoot, targetRel);
1388
+ const targetAbs = path8.join(projectRoot, targetRel);
1524
1389
  if (await fileExists(targetAbs)) {
1525
- const existing = await fs6.readFile(targetAbs, "utf-8");
1390
+ const existing = await fs7.readFile(targetAbs, "utf-8");
1526
1391
  return {
1527
1392
  id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
1528
1393
  target: targetRel,
@@ -1530,7 +1395,7 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1530
1395
  strategy: "frozen"
1531
1396
  };
1532
1397
  }
1533
- const content = await fs6.readFile(sourceAbs, "utf-8");
1398
+ const content = await fs7.readFile(sourceAbs, "utf-8");
1534
1399
  await writeFileSafe(targetAbs, content);
1535
1400
  return {
1536
1401
  id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
@@ -1557,81 +1422,315 @@ async function listTokenVariants(packageName = DEFAULT_TOKENS_PACKAGE, packageRo
1557
1422
  };
1558
1423
  }
1559
1424
 
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;
1425
+ // src/core/agents-md.ts
1426
+ import * as fs8 from "fs/promises";
1427
+ import * as path9 from "path";
1428
+ import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
1429
+ var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
1430
+ async function runGenerateAgentsMd(options) {
1431
+ const { projectRoot, variant, skillIds } = options;
1432
+ const mode = options.mode ?? "overwrite";
1566
1433
  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" };
1434
+ const lock = await readSkillsLock(projectRoot);
1435
+ const ides = config?.packages?.skills?.ides ?? ["qoder", "claude"];
1436
+ const scope = config?.packages?.skills?.scope ?? lock?.skills[skillIds[0] ?? ""]?.scope ?? "project";
1437
+ const ordered = [...skillIds].sort(
1438
+ (a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
1439
+ );
1440
+ const sections = [];
1441
+ const missingSkillIds = [];
1442
+ for (const id of ordered) {
1443
+ const { section, missing } = await renderSkillSection(projectRoot, id, ides, scope);
1444
+ sections.push(section);
1445
+ if (missing) missingSkillIds.push(id);
1576
1446
  }
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
- );
1447
+ const target = path9.join(projectRoot, "AGENTS.md");
1448
+ const targetExists = await fileExists(target);
1449
+ const fullTemplate = renderAgentsMd({ variant, sections });
1450
+ const managedBody = renderManagedBlockBody({ variant, sections });
1451
+ let outputContent;
1452
+ let merge;
1453
+ if (!targetExists) {
1454
+ outputContent = fullTemplate;
1455
+ merge = "created";
1456
+ } else {
1457
+ await backupFile(target, projectRoot);
1458
+ if (mode === "merge-managed") {
1459
+ const existing = await readFileOrNull(target) ?? "";
1460
+ if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
1461
+ outputContent = replaceManagedRegion2(
1462
+ existing,
1463
+ AGENTS_MD_MANAGED_ID,
1464
+ managedBody
1465
+ );
1466
+ merge = "managed-replaced";
1467
+ } else {
1468
+ const wrapped = wrapManagedBlock(managedBody);
1469
+ outputContent = `${wrapped}
1470
+
1471
+ ${PRECEDENCE_NOTICE}
1472
+
1473
+ ${existing.trimStart()}`;
1474
+ merge = "managed-prepended";
1475
+ }
1476
+ } else {
1477
+ outputContent = fullTemplate;
1478
+ merge = "overwritten";
1591
1479
  }
1592
1480
  }
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);
1481
+ await fs8.writeFile(target, outputContent, "utf8");
1482
+ return {
1483
+ path: target,
1484
+ skillCount: ordered.length,
1485
+ missingSkillIds,
1486
+ backedUp: targetExists,
1487
+ merge
1488
+ };
1489
+ }
1490
+ function bucketRank(id) {
1491
+ if (id.startsWith("teamix-evo-design-")) return 0;
1492
+ if (id.startsWith("teamix-evo-code-")) return 1;
1493
+ return 2;
1494
+ }
1495
+ async function renderSkillSection(projectRoot, skillId, ides, scope) {
1496
+ const lines = [];
1497
+ lines.push(`### ${skillId}`);
1498
+ let parts = null;
1499
+ let missing = false;
1500
+ for (const ide of ides) {
1501
+ const adapter = getAdapter(ide);
1502
+ const skillPath = path9.join(
1503
+ adapter.getSkillTargetDir(skillId, scope, projectRoot),
1504
+ "SKILL.md"
1505
+ );
1506
+ try {
1507
+ const raw = await fs8.readFile(skillPath, "utf8");
1508
+ parts = extractDescriptionParts(raw);
1509
+ break;
1510
+ } catch {
1611
1511
  continue;
1612
1512
  }
1613
- targetIds.push(id);
1614
1513
  }
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
1626
- };
1514
+ if (!parts) missing = true;
1515
+ if (parts?.capability) {
1516
+ lines.push(`- ${parts.capability}`);
1627
1517
  }
1628
- if (dryRun) {
1629
- const plan = targetIds.map((id) => {
1630
- const lockVer = existingLock.skills[id].version;
1631
- const entry2 = manifestById.get(id);
1632
- const sameVersion = lockVer === entry2.version;
1633
- return {
1634
- id,
1518
+ lines.push(
1519
+ `- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
1520
+ );
1521
+ lines.push(
1522
+ `- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
1523
+ );
1524
+ if (parts?.coordinates) {
1525
+ lines.push(`- **Coordinates with**: ${parts.coordinates}`);
1526
+ }
1527
+ return { section: lines.join("\n"), missing };
1528
+ }
1529
+ function renderAgentsMd(args) {
1530
+ const { variant, sections } = args;
1531
+ const managedBody = renderManagedBlockBody({ variant, sections });
1532
+ const wrapped = wrapManagedBlock(managedBody);
1533
+ return `${wrapped}
1534
+
1535
+ ${PRECEDENCE_NOTICE}
1536
+ `;
1537
+ }
1538
+ function renderManagedBlockBody(args) {
1539
+ const { variant, sections } = args;
1540
+ const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
1541
+ return `# AGENTS.md
1542
+
1543
+ > 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
1544
+ >
1545
+ > \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
1546
+
1547
+ ## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
1548
+
1549
+ ${skillBlock}
1550
+
1551
+ > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo@latest skills update\``;
1552
+ }
1553
+ function wrapManagedBlock(body) {
1554
+ return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
1555
+ ${body}
1556
+ <!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
1557
+ }
1558
+ var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
1559
+ > \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`;
1560
+ function extractDescriptionParts(fileContent) {
1561
+ const description = extractDescriptionBlock(fileContent);
1562
+ if (description == null) return null;
1563
+ const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
1564
+ let capability = "";
1565
+ for (const line of allLines) {
1566
+ if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
1567
+ capability = capability ? `${capability} ${line}` : line;
1568
+ }
1569
+ return {
1570
+ capability: capability.trim(),
1571
+ trigger: extractSection(description, "TRIGGER when:"),
1572
+ skip: extractSection(description, "SKIP:"),
1573
+ coordinates: extractSection(description, "Coordinates with:")
1574
+ };
1575
+ }
1576
+ function extractDescriptionBlock(fileContent) {
1577
+ const lines = fileContent.split("\n");
1578
+ if (lines[0]?.trim() !== "---") return null;
1579
+ let endIdx = -1;
1580
+ for (let i = 1; i < lines.length; i++) {
1581
+ if (lines[i]?.trim() === "---") {
1582
+ endIdx = i;
1583
+ break;
1584
+ }
1585
+ }
1586
+ if (endIdx === -1) return null;
1587
+ const fmLines = lines.slice(1, endIdx);
1588
+ let startIdx = -1;
1589
+ let inlineValue = null;
1590
+ let blockMode = "inline";
1591
+ for (let i = 0; i < fmLines.length; i++) {
1592
+ const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
1593
+ if (m) {
1594
+ startIdx = i;
1595
+ const indicator = (m[1] ?? "").trim();
1596
+ const rest = m[2] ?? "";
1597
+ if (indicator.startsWith("|")) blockMode = "literal";
1598
+ else if (indicator.startsWith(">")) blockMode = "folded";
1599
+ else {
1600
+ blockMode = "inline";
1601
+ inlineValue = rest;
1602
+ }
1603
+ break;
1604
+ }
1605
+ }
1606
+ if (startIdx === -1) return null;
1607
+ if (blockMode === "inline") {
1608
+ return inlineValue ?? "";
1609
+ }
1610
+ const body = [];
1611
+ let blockIndent = -1;
1612
+ for (let i = startIdx + 1; i < fmLines.length; i++) {
1613
+ const line = fmLines[i] ?? "";
1614
+ if (line.trim() === "") {
1615
+ body.push("");
1616
+ continue;
1617
+ }
1618
+ const indentMatch = line.match(/^(\s+)/);
1619
+ const indent = indentMatch ? indentMatch[1].length : 0;
1620
+ if (indent === 0) break;
1621
+ if (blockIndent === -1) blockIndent = indent;
1622
+ if (indent < blockIndent) break;
1623
+ body.push(line.slice(blockIndent));
1624
+ }
1625
+ while (body.length > 0 && body[body.length - 1] === "") body.pop();
1626
+ return body.join("\n");
1627
+ }
1628
+ function extractSection(description, marker) {
1629
+ const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
1630
+ const lines = description.split("\n");
1631
+ let inSection = false;
1632
+ const collected = [];
1633
+ for (const line of lines) {
1634
+ const trimmed = line.trim();
1635
+ const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
1636
+ if (!inSection && startsWithMarker) {
1637
+ inSection = true;
1638
+ collected.push(trimmed.slice(marker.length).trim());
1639
+ continue;
1640
+ }
1641
+ if (inSection) {
1642
+ const hitNextMarker = markers.some(
1643
+ (m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
1644
+ );
1645
+ if (hitNextMarker) break;
1646
+ if (trimmed === "") {
1647
+ if (collected[collected.length - 1] === "") break;
1648
+ collected.push("");
1649
+ continue;
1650
+ }
1651
+ collected.push(trimmed);
1652
+ }
1653
+ }
1654
+ if (!inSection) return null;
1655
+ const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
1656
+ return joined || null;
1657
+ }
1658
+
1659
+ // src/core/skills-update.ts
1660
+ var DEFAULT_SKILLS_PACKAGE3 = "@teamix-evo/skills";
1661
+ var FLAT_VARIANT2 = "_flat";
1662
+ async function runSkillsUpdate(options) {
1663
+ const { projectRoot, names: requestedNames, dryRun } = options;
1664
+ const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE3;
1665
+ const config = await readProjectConfig(projectRoot);
1666
+ const skillsCfg = config?.packages?.skills;
1667
+ if (!skillsCfg) {
1668
+ return { status: "no-skills" };
1669
+ }
1670
+ const ides = skillsCfg.ides ?? ["qoder", "claude"];
1671
+ const scope = skillsCfg.scope ?? "project";
1672
+ const existingLock = await readSkillsLock(projectRoot);
1673
+ if (!existingLock || Object.keys(existingLock.skills).length === 0) {
1674
+ return { status: "no-skills" };
1675
+ }
1676
+ const { manifest, data, packageRoot } = await loadSkillsData(packageName);
1677
+ const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
1678
+ const lockIds = Object.keys(existingLock.skills);
1679
+ const requestedSet = requestedNames ? new Set(requestedNames) : null;
1680
+ if (requestedSet) {
1681
+ const unknown = requestedNames.filter(
1682
+ (n) => !lockIds.includes(n) && !manifestById.has(n)
1683
+ );
1684
+ if (unknown.length > 0) {
1685
+ throw new Error(
1686
+ `Unknown skill id(s): ${unknown.join(
1687
+ ", "
1688
+ )}. Available (installed): ${lockIds.join(", ") || "(none)"}.`
1689
+ );
1690
+ }
1691
+ }
1692
+ const targetIds = [];
1693
+ const skippedSkillIds = [];
1694
+ for (const id of lockIds) {
1695
+ if (requestedSet && !requestedSet.has(id)) continue;
1696
+ const entry2 = manifestById.get(id);
1697
+ if (!entry2) {
1698
+ logger.debug(
1699
+ `Skipping "${id}": no longer in upstream manifest. Use \`skills uninstall ${id}\` to remove.`
1700
+ );
1701
+ skippedSkillIds.push(id);
1702
+ continue;
1703
+ }
1704
+ const effectiveScope = entry2.scope ?? "project";
1705
+ if (effectiveScope !== scope) {
1706
+ logger.debug(
1707
+ `Skipping "${id}" (scope=${effectiveScope}): current install scope is "${scope}".`
1708
+ );
1709
+ skippedSkillIds.push(id);
1710
+ continue;
1711
+ }
1712
+ targetIds.push(id);
1713
+ }
1714
+ const allSame = targetIds.every((id) => {
1715
+ const lockVer = existingLock.skills[id].version;
1716
+ const manVer = manifestById.get(id).version;
1717
+ return lockVer === manVer;
1718
+ });
1719
+ if (targetIds.length > 0 && allSame && !dryRun) {
1720
+ return {
1721
+ status: "no-changes",
1722
+ packageName,
1723
+ version: manifest.version,
1724
+ checkedSkillIds: targetIds
1725
+ };
1726
+ }
1727
+ if (dryRun) {
1728
+ const plan = targetIds.map((id) => {
1729
+ const lockVer = existingLock.skills[id].version;
1730
+ const entry2 = manifestById.get(id);
1731
+ const sameVersion = lockVer === entry2.version;
1732
+ return {
1733
+ id,
1635
1734
  current: lockVer,
1636
1735
  next: entry2.version,
1637
1736
  strategy: entry2.updateStrategy ?? "managed",
@@ -1714,6 +1813,24 @@ async function runSkillsUpdate(options) {
1714
1813
  };
1715
1814
  }
1716
1815
  await writeSkillsLock(projectRoot, lock);
1816
+ if (scope === "project") {
1817
+ try {
1818
+ const variant = await readTokensVariant(projectRoot);
1819
+ if (variant) {
1820
+ const projectSkillIds = Object.entries(lock.skills).filter(([, v]) => v.scope === "project").map(([id]) => id);
1821
+ await runGenerateAgentsMd({
1822
+ projectRoot,
1823
+ variant,
1824
+ skillIds: projectSkillIds,
1825
+ mode: "merge-managed"
1826
+ });
1827
+ }
1828
+ } catch (err) {
1829
+ logger.warn(
1830
+ `AGENTS.md \u5237\u65B0\u5931\u8D25\uFF08\u975E\u5173\u952E\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}`
1831
+ );
1832
+ }
1833
+ }
1717
1834
  return {
1718
1835
  status: "updated",
1719
1836
  packageName,
@@ -1734,8 +1851,7 @@ var DEFAULT_UI_ALIASES = {
1734
1851
  utils: "src/lib/utils",
1735
1852
  lib: "src/lib",
1736
1853
  business: "src/components/business",
1737
- blocks: "src/blocks",
1738
- templates: "src/templates"
1854
+ blocks: "src/blocks"
1739
1855
  };
1740
1856
  var DEFAULT_UI_ICON_LIBRARY = "lucide";
1741
1857
  async function runUiInit(options) {
@@ -1752,8 +1868,7 @@ async function runUiInit(options) {
1752
1868
  utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
1753
1869
  lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
1754
1870
  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
1871
+ blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks
1757
1872
  };
1758
1873
  const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
1759
1874
  const tsx = options.tsx ?? true;
@@ -1773,7 +1888,6 @@ async function runUiInit(options) {
1773
1888
  rsc
1774
1889
  };
1775
1890
  await writeProjectConfig(projectRoot, config);
1776
- await ensureMcpJson(projectRoot);
1777
1891
  return {
1778
1892
  status: "installed",
1779
1893
  aliases,
@@ -1785,7 +1899,7 @@ async function runUiInit(options) {
1785
1899
 
1786
1900
  // src/core/ui-client.ts
1787
1901
  import * as path10 from "path";
1788
- import * as fs7 from "fs/promises";
1902
+ import * as fs9 from "fs/promises";
1789
1903
  import { createRequire as createRequire3 } from "module";
1790
1904
  import { loadUiPackageManifest } from "@teamix-evo/registry";
1791
1905
  var require4 = createRequire3(import.meta.url);
@@ -1800,7 +1914,7 @@ async function loadUiData(packageName) {
1800
1914
  let data = {};
1801
1915
  const dataPath = path10.join(packageRoot, "_data.json");
1802
1916
  try {
1803
- const raw = await fs7.readFile(dataPath, "utf-8");
1917
+ const raw = await fs9.readFile(dataPath, "utf-8");
1804
1918
  data = JSON.parse(raw);
1805
1919
  } catch (err) {
1806
1920
  if (err.code !== "ENOENT") {
@@ -1813,7 +1927,7 @@ async function loadUiData(packageName) {
1813
1927
 
1814
1928
  // src/core/ui-installer.ts
1815
1929
  import * as path11 from "path";
1816
- import * as fs8 from "fs/promises";
1930
+ import * as fs10 from "fs/promises";
1817
1931
  import { resolveUiEntryOrder } from "@teamix-evo/registry";
1818
1932
 
1819
1933
  // src/utils/transform-imports.ts
@@ -1890,9 +2004,21 @@ async function installUiEntries(options) {
1890
2004
  }
1891
2005
  const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
1892
2006
  const sourceAbs = path11.resolve(rootForEntry, file.source);
1893
- const raw = await fs8.readFile(sourceAbs, "utf-8");
2007
+ const raw = await fs10.readFile(sourceAbs, "utf-8");
1894
2008
  const transformed = rewriteImports(raw, aliases, { flatten });
1895
2009
  if (exists) {
2010
+ const current = await fs10.readFile(targetAbs, "utf-8");
2011
+ if (current === transformed) {
2012
+ logger.info(` skip (identical): ${rel(projectRoot, targetAbs)}`);
2013
+ skipped++;
2014
+ resources.push({
2015
+ id: `${entry.id}:${file.targetName}`,
2016
+ target: path11.relative(projectRoot, targetAbs),
2017
+ hash: computeHash(transformed),
2018
+ strategy: entry.updateStrategy ?? "frozen"
2019
+ });
2020
+ continue;
2021
+ }
1896
2022
  await backupFile(targetAbs, projectRoot);
1897
2023
  }
1898
2024
  await writeFileSafe(targetAbs, transformed);
@@ -1900,7 +2026,7 @@ async function installUiEntries(options) {
1900
2026
  logger.info(` write: ${rel(projectRoot, targetAbs)}`);
1901
2027
  resources.push({
1902
2028
  id: `${entry.id}:${file.targetName}`,
1903
- target: targetAbs,
2029
+ target: path11.relative(projectRoot, targetAbs),
1904
2030
  hash: computeHash(transformed),
1905
2031
  strategy: entry.updateStrategy ?? "frozen"
1906
2032
  });
@@ -1926,23 +2052,26 @@ function resolveTargetPath(projectRoot, aliases, entry, file) {
1926
2052
  function rel(projectRoot, abs) {
1927
2053
  return path11.relative(projectRoot, abs);
1928
2054
  }
1929
- async function removeUiFiles(records) {
2055
+ async function removeUiFiles(records, projectRoot) {
1930
2056
  const removed = [];
1931
2057
  for (const r of records) {
2058
+ const abs = resolveResourceTarget(projectRoot, r.target);
1932
2059
  try {
1933
- await fs8.unlink(r.target);
1934
- removed.push(r.target);
2060
+ await fs10.unlink(abs);
2061
+ removed.push(abs);
1935
2062
  } catch (err) {
1936
2063
  if (err.code !== "ENOENT") {
1937
- logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
2064
+ logger.warn(`Failed to remove ${abs}: ${getErrorMessage(err)}`);
1938
2065
  }
1939
2066
  }
1940
2067
  }
1941
- const parents = new Set(records.map((r) => path11.dirname(r.target)));
2068
+ const parents = new Set(
2069
+ records.map((r) => path11.dirname(resolveResourceTarget(projectRoot, r.target)))
2070
+ );
1942
2071
  for (const dir of parents) {
1943
2072
  try {
1944
- const entries = await fs8.readdir(dir);
1945
- if (entries.length === 0) await fs8.rmdir(dir);
2073
+ const entries = await fs10.readdir(dir);
2074
+ if (entries.length === 0) await fs10.rmdir(dir);
1946
2075
  } catch {
1947
2076
  }
1948
2077
  }
@@ -2091,7 +2220,7 @@ async function runVariantUiAdd(packageName, options) {
2091
2220
  const uiCfg = config?.packages?.ui;
2092
2221
  if (!config || !uiCfg?.aliases) {
2093
2222
  throw new Error(
2094
- `UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business / templates).`
2223
+ `UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business / blocks).`
2095
2224
  );
2096
2225
  }
2097
2226
  const packageRoot = options.packageRoot ?? resolvePackageRoot3(fullPackageName);
@@ -2182,9 +2311,6 @@ function mergeResources2(prior, next) {
2182
2311
  async function runBizUiAdd(options) {
2183
2312
  return runVariantUiAdd("biz-ui", options);
2184
2313
  }
2185
- async function runTemplatesAdd(options) {
2186
- return runVariantUiAdd("templates", options);
2187
- }
2188
2314
  async function listVariantUi(packageName, packageRoot) {
2189
2315
  const fullPackageName = `@teamix-evo/${packageName}`;
2190
2316
  const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
@@ -2202,9 +2328,6 @@ async function listVariantUi(packageName, packageRoot) {
2202
2328
  async function listBizUiVariants(packageRoot) {
2203
2329
  return listVariantUi("biz-ui", packageRoot);
2204
2330
  }
2205
- async function listTemplatesVariants(packageRoot) {
2206
- return listVariantUi("templates", packageRoot);
2207
- }
2208
2331
  async function listVariantUiEntries(packageName, variant, packageRoot) {
2209
2332
  const fullPackageName = `@teamix-evo/${packageName}`;
2210
2333
  const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
@@ -2232,13 +2355,10 @@ async function listVariantUiEntries(packageName, variant, packageRoot) {
2232
2355
  async function listBizUiEntries(variant, packageRoot) {
2233
2356
  return listVariantUiEntries("biz-ui", variant, packageRoot);
2234
2357
  }
2235
- async function listTemplatesEntries(variant, packageRoot) {
2236
- return listVariantUiEntries("templates", variant, packageRoot);
2237
- }
2238
2358
 
2239
2359
  // src/core/lint-init.ts
2240
2360
  import * as path13 from "path";
2241
- import * as fs9 from "fs";
2361
+ import * as fs11 from "fs";
2242
2362
  import { execa } from "execa";
2243
2363
  var ESLINT_CONFIG_CONTENT = `/**
2244
2364
  * teamix-evo consumer ESLint preset \u2014 9 token-discipline rules.
@@ -2330,7 +2450,7 @@ async function runLintInit(options) {
2330
2450
  let stylelintIgnoreFilesWarning = false;
2331
2451
  if (!stylelintNeedsWrite && stylelintTemplateExists) {
2332
2452
  try {
2333
- const existingContent = fs9.readFileSync(stylelintConfigPath, "utf-8");
2453
+ const existingContent = fs11.readFileSync(stylelintConfigPath, "utf-8");
2334
2454
  const usesTeamixPreset = existingContent.includes("@teamix-evo/stylelint-config/presets/") || existingContent.includes("@teamix-evo/stylelint-config/preset/");
2335
2455
  const hasTokenIgnore = existingContent.includes("tokens.theme.css") && existingContent.includes("tokens.overrides.css");
2336
2456
  if (!usesTeamixPreset && !hasTokenIgnore) {
@@ -2344,305 +2464,65 @@ async function runLintInit(options) {
2344
2464
  " '**/tokens.theme.css',",
2345
2465
  " '**/tokens.overrides.css',",
2346
2466
  " ]",
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;
2467
+ "",
2468
+ "\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
2469
+ "",
2470
+ " extends: ['@teamix-evo/stylelint-config/presets/consumer']"
2471
+ ].join("\n")
2472
+ );
2586
2473
  }
2587
- break;
2474
+ } catch {
2588
2475
  }
2589
2476
  }
2590
- if (startIdx === -1) return null;
2591
- if (blockMode === "inline") {
2592
- return inlineValue ?? "";
2477
+ return {
2478
+ status: "installed",
2479
+ eslint: wroteEslint,
2480
+ stylelint: wroteStylelint,
2481
+ eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
2482
+ stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
2483
+ eslintSkipped: eslintSkipRequested,
2484
+ stylelintSkipped: stylelintSkipRequested,
2485
+ packageJsonPatched,
2486
+ stylelintIgnoreFilesWarning
2487
+ };
2488
+ }
2489
+ function detectPm(projectRoot) {
2490
+ if (fs11.existsSync(path13.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
2491
+ if (fs11.existsSync(path13.join(projectRoot, "yarn.lock"))) return "yarn";
2492
+ return "npm";
2493
+ }
2494
+ async function patchPackageJsonScripts(projectRoot) {
2495
+ const pkgPath = path13.join(projectRoot, "package.json");
2496
+ const raw = await readFileOrNull(pkgPath);
2497
+ if (!raw) return false;
2498
+ let pkg;
2499
+ try {
2500
+ pkg = JSON.parse(raw);
2501
+ } catch {
2502
+ return false;
2593
2503
  }
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));
2504
+ const scripts = pkg.scripts ?? {};
2505
+ let changed = false;
2506
+ if (!scripts.lint) {
2507
+ scripts.lint = "eslint src/";
2508
+ changed = true;
2608
2509
  }
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
- }
2510
+ if (!scripts["lint:css"]) {
2511
+ scripts["lint:css"] = "stylelint 'src/**/*.css'";
2512
+ changed = true;
2637
2513
  }
2638
- if (!inSection) return null;
2639
- const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
2640
- return joined || null;
2514
+ if (changed) {
2515
+ await backupFile(pkgPath, projectRoot);
2516
+ pkg.scripts = scripts;
2517
+ await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
2518
+ logger.debug("Patched package.json scripts with lint / lint:css");
2519
+ }
2520
+ return changed;
2641
2521
  }
2642
2522
 
2643
2523
  // src/core/init-detect.ts
2644
- import * as fs11 from "fs/promises";
2645
- import * as path15 from "path";
2524
+ import * as fs12 from "fs/promises";
2525
+ import * as path14 from "path";
2646
2526
  var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
2647
2527
  ".git",
2648
2528
  ".gitignore",
@@ -2662,7 +2542,7 @@ var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
2662
2542
  "LICENSE.txt"
2663
2543
  ]);
2664
2544
  async function detectProjectState(cwd) {
2665
- const absCwd = path15.resolve(cwd);
2545
+ const absCwd = path14.resolve(cwd);
2666
2546
  const teamixDir = getTeamixDir(absCwd);
2667
2547
  const hasTeamixDir = await fileExists(teamixDir);
2668
2548
  if (hasTeamixDir) {
@@ -2670,13 +2550,13 @@ async function detectProjectState(cwd) {
2670
2550
  state: "teamix-evo-installed",
2671
2551
  cwd: absCwd,
2672
2552
  hasTeamixDir: true,
2673
- hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
2553
+ hasPackageJson: await fileExists(path14.join(absCwd, "package.json")),
2674
2554
  significantEntries: []
2675
2555
  };
2676
2556
  }
2677
2557
  let entries;
2678
2558
  try {
2679
- entries = await fs11.readdir(absCwd);
2559
+ entries = await fs12.readdir(absCwd);
2680
2560
  } catch (err) {
2681
2561
  if (err.code === "ENOENT") {
2682
2562
  return {
@@ -2709,9 +2589,151 @@ async function detectProjectState(cwd) {
2709
2589
  };
2710
2590
  }
2711
2591
 
2592
+ // src/core/project-state.ts
2593
+ import * as fs13 from "fs/promises";
2594
+ import * as path15 from "path";
2595
+ var IGNORED_TOP_LEVEL2 = /* @__PURE__ */ new Set([
2596
+ ".git",
2597
+ ".gitignore",
2598
+ ".gitattributes",
2599
+ ".gitkeep",
2600
+ ".DS_Store",
2601
+ "Thumbs.db",
2602
+ ".idea",
2603
+ ".vscode",
2604
+ ".qoder",
2605
+ ".claude",
2606
+ "README.md",
2607
+ "README",
2608
+ "README.txt",
2609
+ "LICENSE",
2610
+ "LICENSE.md",
2611
+ "LICENSE.txt"
2612
+ ]);
2613
+ var SHADCN_TRAIT_KEYS = ["$schema", "style", "rsc", "tsx", "aliases"];
2614
+ async function isShadcnComponentsJson(filePath) {
2615
+ const content = await readFileOrNull(filePath);
2616
+ if (!content) return false;
2617
+ try {
2618
+ const json = JSON.parse(content);
2619
+ return SHADCN_TRAIT_KEYS.some((key) => key in json);
2620
+ } catch {
2621
+ return false;
2622
+ }
2623
+ }
2624
+ async function detectProjectState2(cwd) {
2625
+ const absCwd = path15.resolve(cwd);
2626
+ const teamixDir = getTeamixDir(absCwd);
2627
+ const hasTeamixDir = await fileExists(teamixDir);
2628
+ if (hasTeamixDir) {
2629
+ return {
2630
+ state: "teamix-evo",
2631
+ cwd: absCwd,
2632
+ hasTeamixDir: true,
2633
+ hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
2634
+ hasComponentsJson: false,
2635
+ significantEntries: []
2636
+ };
2637
+ }
2638
+ const componentsJsonPath = path15.join(absCwd, "components.json");
2639
+ const hasPackageJson = await fileExists(path15.join(absCwd, "package.json"));
2640
+ const hasComponentsJson = await isShadcnComponentsJson(componentsJsonPath);
2641
+ if (hasPackageJson && hasComponentsJson) {
2642
+ return {
2643
+ state: "shadcn",
2644
+ cwd: absCwd,
2645
+ hasTeamixDir: false,
2646
+ hasPackageJson: true,
2647
+ hasComponentsJson: true,
2648
+ significantEntries: []
2649
+ };
2650
+ }
2651
+ let entries;
2652
+ try {
2653
+ entries = await fs13.readdir(absCwd);
2654
+ } catch (err) {
2655
+ if (err.code === "ENOENT") {
2656
+ return {
2657
+ state: "empty",
2658
+ cwd: absCwd,
2659
+ hasTeamixDir: false,
2660
+ hasPackageJson: false,
2661
+ hasComponentsJson: false,
2662
+ significantEntries: []
2663
+ };
2664
+ }
2665
+ throw err;
2666
+ }
2667
+ const significant = entries.filter((e) => !IGNORED_TOP_LEVEL2.has(e));
2668
+ if (significant.length === 0) {
2669
+ return {
2670
+ state: "empty",
2671
+ cwd: absCwd,
2672
+ hasTeamixDir: false,
2673
+ hasPackageJson: false,
2674
+ hasComponentsJson: false,
2675
+ significantEntries: []
2676
+ };
2677
+ }
2678
+ return {
2679
+ state: "other",
2680
+ cwd: absCwd,
2681
+ hasTeamixDir: false,
2682
+ hasPackageJson,
2683
+ hasComponentsJson,
2684
+ significantEntries: significant.slice(0, 20).sort()
2685
+ };
2686
+ }
2687
+
2688
+ // src/core/command-guard.ts
2689
+ var COMMAND_LABELS = {
2690
+ init: "\u521D\u59CB\u5316 Teamix Evo \u5957\u4EF6",
2691
+ migrate: "shadcn \u9879\u76EE\u8FC1\u79FB\u4E3A Teamix Evo \u5957\u4EF6",
2692
+ update: "Teamix Evo \u5957\u4EF6\u66F4\u65B0"
2693
+ };
2694
+ var STATE_LABELS = {
2695
+ empty: "\u7A7A\u76EE\u5F55",
2696
+ shadcn: "shadcn \u5DE5\u7A0B",
2697
+ "teamix-evo": "Teamix Evo \u5DE5\u7A0B",
2698
+ other: "\u666E\u901A npm \u5DE5\u7A0B"
2699
+ };
2700
+ var VALID_STATES = {
2701
+ init: "empty",
2702
+ migrate: "shadcn",
2703
+ update: "teamix-evo"
2704
+ };
2705
+ var STATE_TO_COMMAND = {
2706
+ empty: "init",
2707
+ shadcn: "migrate",
2708
+ "teamix-evo": "update",
2709
+ other: null
2710
+ };
2711
+ function assertCommandPrecondition(command, state) {
2712
+ const expected = VALID_STATES[command];
2713
+ if (state === expected) return;
2714
+ const stateLabel = STATE_LABELS[state];
2715
+ const commandLabel = COMMAND_LABELS[command];
2716
+ logger.error(
2717
+ `\u5F53\u524D\u9879\u76EE\u72B6\u6001\u4E3A\u300C${stateLabel}\u300D\uFF0C\u65E0\u6CD5\u6267\u884C\u300C${commandLabel}\u300D\u3002`
2718
+ );
2719
+ const suggestion = STATE_TO_COMMAND[state];
2720
+ if (suggestion) {
2721
+ const suggestionLabel = COMMAND_LABELS[suggestion];
2722
+ logger.info(`\u5EFA\u8BAE\u4F7F\u7528\uFF1Ateamix-evo ${suggestion}\uFF08${suggestionLabel}\uFF09`);
2723
+ } else {
2724
+ logger.info(
2725
+ "\u5F53\u524D\u9879\u76EE\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u76F4\u63A5\u63A5\u5165\u3002"
2726
+ );
2727
+ logger.info(
2728
+ "\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"
2729
+ );
2730
+ }
2731
+ process.exitCode = 1;
2732
+ }
2733
+
2712
2734
  // src/core/init-conflicts.ts
2713
2735
  import * as crypto from "crypto";
2714
- import * as fs12 from "fs/promises";
2736
+ import * as fs14 from "fs/promises";
2715
2737
  import * as path16 from "path";
2716
2738
  var TAILWIND_CONFIG_CANDIDATES = [
2717
2739
  "tailwind.config.ts",
@@ -2756,7 +2778,7 @@ var STYLELINT_CONFIG_CANDIDATES = [
2756
2778
  ];
2757
2779
  async function isDir(target) {
2758
2780
  try {
2759
- const stat3 = await fs12.stat(target);
2781
+ const stat3 = await fs14.stat(target);
2760
2782
  return stat3.isDirectory();
2761
2783
  } catch {
2762
2784
  return false;
@@ -2764,7 +2786,7 @@ async function isDir(target) {
2764
2786
  }
2765
2787
  async function dirHasContent(target) {
2766
2788
  try {
2767
- const entries = await fs12.readdir(target);
2789
+ const entries = await fs14.readdir(target);
2768
2790
  return entries.length > 0;
2769
2791
  } catch {
2770
2792
  return false;
@@ -2905,7 +2927,7 @@ async function detectShadcnSource(cwd) {
2905
2927
  try {
2906
2928
  const uiDir = path16.join(cwd, "src/components/ui");
2907
2929
  if (await isDir(uiDir)) {
2908
- const entries = await fs12.readdir(uiDir);
2930
+ const entries = await fs14.readdir(uiDir);
2909
2931
  componentCount = entries.filter(
2910
2932
  (e) => e.endsWith(".tsx") || e.endsWith(".ts")
2911
2933
  ).length;
@@ -2981,19 +3003,97 @@ async function detectStylelintConfig(cwd) {
2981
3003
  };
2982
3004
  }
2983
3005
 
2984
- // src/core/deps-install.ts
2985
- import * as fs13 from "fs/promises";
3006
+ // src/core/meta-installer.ts
2986
3007
  import * as path17 from "path";
3008
+ import * as fs15 from "fs/promises";
3009
+ import { createRequire as createRequire5 } from "module";
3010
+ import {
3011
+ loadUiPackageManifest as loadUiPackageManifest3,
3012
+ loadVariantUiPackageManifest as loadVariantUiPackageManifest2
3013
+ } from "@teamix-evo/registry";
3014
+ var require6 = createRequire5(import.meta.url);
3015
+ var META_DIR = "meta";
3016
+ function resolvePackageRoot4(packageName) {
3017
+ const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
3018
+ return path17.dirname(pkgJsonPath);
3019
+ }
3020
+ function getMetaDir(projectRoot, category) {
3021
+ const base = path17.join(getTeamixDir(projectRoot), META_DIR);
3022
+ return category ? path17.join(base, category) : base;
3023
+ }
3024
+ async function landUiMeta(projectRoot) {
3025
+ const packageRoot = resolvePackageRoot4("@teamix-evo/ui");
3026
+ const manifest = await loadUiPackageManifest3(packageRoot);
3027
+ return landManifestMeta({
3028
+ projectRoot,
3029
+ packageRoot,
3030
+ manifest,
3031
+ category: "ui"
3032
+ });
3033
+ }
3034
+ async function landBizUiMeta(projectRoot, variant) {
3035
+ const packageRoot = resolvePackageRoot4("@teamix-evo/biz-ui");
3036
+ const variantRoot = path17.join(packageRoot, "variants", variant);
3037
+ const manifest = await loadVariantUiPackageManifest2(variantRoot);
3038
+ return landManifestMeta({
3039
+ projectRoot,
3040
+ packageRoot: variantRoot,
3041
+ manifest,
3042
+ category: "biz-ui"
3043
+ });
3044
+ }
3045
+ async function landManifestMeta(opts) {
3046
+ const { projectRoot, packageRoot, manifest, category } = opts;
3047
+ await ensureTeamixDir(projectRoot);
3048
+ const metaDir = getMetaDir(projectRoot, category);
3049
+ await ensureDir(metaDir);
3050
+ const manifestDest = path17.join(metaDir, "manifest.json");
3051
+ const manifestSrc = path17.join(packageRoot, "manifest.json");
3052
+ await fs15.copyFile(manifestSrc, manifestDest);
3053
+ logger.debug(`meta: copied manifest.json \u2192 ${manifestDest}`);
3054
+ let written = 0;
3055
+ let total = 0;
3056
+ for (const entry of manifest.entries) {
3057
+ if (!entry.meta) continue;
3058
+ total++;
3059
+ const srcPath = path17.join(packageRoot, entry.meta);
3060
+ const destPath = path17.join(metaDir, `${entry.id}.md`);
3061
+ try {
3062
+ await fs15.copyFile(srcPath, destPath);
3063
+ written++;
3064
+ logger.debug(`meta: ${entry.id} \u2192 ${destPath}`);
3065
+ } catch (err) {
3066
+ if (err.code === "ENOENT") {
3067
+ logger.warn(`meta: ${entry.id} \u2014 source not found: ${srcPath}`);
3068
+ } else {
3069
+ throw err;
3070
+ }
3071
+ }
3072
+ }
3073
+ logger.info(
3074
+ ` meta/${category}: ${written}/${total} meta files, manifest.json`
3075
+ );
3076
+ return {
3077
+ category,
3078
+ manifestWritten: true,
3079
+ metaFilesWritten: written,
3080
+ metaFilesTotal: total
3081
+ };
3082
+ }
3083
+
3084
+ // src/core/deps-install.ts
3085
+ import * as fs16 from "fs/promises";
3086
+ import * as path18 from "path";
2987
3087
  import { exec } from "child_process";
2988
3088
  import { promisify } from "util";
2989
3089
  var execAsync = promisify(exec);
2990
3090
  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")))
3091
+ if (await fileExists(path18.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3092
+ if (await fileExists(path18.join(projectRoot, "pnpm-workspace.yaml")))
2993
3093
  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";
3094
+ if (await fileExists(path18.join(projectRoot, "bun.lockb"))) return "bun";
3095
+ if (await fileExists(path18.join(projectRoot, "bun.lock"))) return "bun";
3096
+ if (await fileExists(path18.join(projectRoot, "yarn.lock"))) return "yarn";
2997
3097
  return "npm";
2998
3098
  }
2999
3099
  function getInstallCommand(pm) {
@@ -3010,7 +3110,7 @@ function getInstallCommand(pm) {
3010
3110
  }
3011
3111
  async function installProjectDeps(options) {
3012
3112
  const { projectRoot, npmDependencies, skipInstall = false } = options;
3013
- const pkgPath = path17.join(projectRoot, "package.json");
3113
+ const pkgPath = path18.join(projectRoot, "package.json");
3014
3114
  const raw = await readFileOrNull(pkgPath);
3015
3115
  if (!raw) {
3016
3116
  throw new Error(
@@ -3039,7 +3139,7 @@ async function installProjectDeps(options) {
3039
3139
  pkg.dependencies = Object.fromEntries(
3040
3140
  Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
3041
3141
  );
3042
- await fs13.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
3142
+ await fs16.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
3043
3143
  logger.info(
3044
3144
  ` patched package.json: +${Object.keys(added).length} dependencies`
3045
3145
  );
@@ -3101,19 +3201,19 @@ ${stepLines}
3101
3201
  }
3102
3202
 
3103
3203
  // src/core/file-changes.ts
3104
- import * as fs14 from "fs/promises";
3105
- import * as path18 from "path";
3204
+ import * as fs17 from "fs/promises";
3205
+ import * as path19 from "path";
3106
3206
  function toRelativePosix(p, projectRoot) {
3107
3207
  let rel2 = p;
3108
- if (path18.isAbsolute(p)) {
3109
- rel2 = path18.relative(projectRoot, p);
3208
+ if (path19.isAbsolute(p)) {
3209
+ rel2 = path19.relative(projectRoot, p);
3110
3210
  }
3111
- return rel2.split(path18.sep).join("/");
3211
+ return rel2.split(path19.sep).join("/");
3112
3212
  }
3113
3213
 
3114
3214
  // src/core/project-init.ts
3115
3215
  import * as fsNode from "fs/promises";
3116
- import * as path19 from "path";
3216
+ import * as path20 from "path";
3117
3217
  var CRITICAL_STEPS = /* @__PURE__ */ new Set([
3118
3218
  "tokens",
3119
3219
  "skills",
@@ -3124,7 +3224,6 @@ var GITIGNORE_MARKER_END = "# <<< teamix-evo:managed <<<";
3124
3224
  var GITIGNORE_RULES = [
3125
3225
  "# Runtime artifacts (regenerable / archives)",
3126
3226
  ".teamix-evo/.snapshots/",
3127
- ".teamix-evo/logs/",
3128
3227
  ".teamix-evo/.backups/",
3129
3228
  ".teamix-evo/.upgrade-staging/",
3130
3229
  ".teamix-evo/.upgrade-hints/"
@@ -3206,7 +3305,7 @@ async function runProjectInit(options) {
3206
3305
  detail: `${result.skillCount} skills, ${result.fileCount} files (added: ${result.addedSkillIds.join(", ") || "none"})`,
3207
3306
  changes: result.addedSkillIds.map((id) => ({
3208
3307
  kind: "created",
3209
- path: `.teamix-evo/skills/${id}/SKILL.md`,
3308
+ path: `.${ides[0] ?? "qoder"}/skills/${id}/SKILL.md`,
3210
3309
  step: "skills",
3211
3310
  detail: "skill installed"
3212
3311
  }))
@@ -3363,6 +3462,31 @@ async function runProjectInit(options) {
3363
3462
  recordFailure("biz-ui-add", err);
3364
3463
  }
3365
3464
  }
3465
+ if (dryRun) {
3466
+ record({
3467
+ name: "meta-landing",
3468
+ status: "planned",
3469
+ detail: `landUiMeta() + landBizUiMeta(${variant})`
3470
+ });
3471
+ } else if (aborted) {
3472
+ record({
3473
+ name: "meta-landing",
3474
+ status: "skip",
3475
+ detail: "aborted: earlier critical step failed"
3476
+ });
3477
+ } else {
3478
+ try {
3479
+ const uiResult = await landUiMeta(projectRoot);
3480
+ const bizResult = await landBizUiMeta(projectRoot, variant);
3481
+ record({
3482
+ name: "meta-landing",
3483
+ status: "ok",
3484
+ detail: `ui: ${uiResult.metaFilesWritten}/${uiResult.metaFilesTotal}, biz-ui: ${bizResult.metaFilesWritten}/${bizResult.metaFilesTotal}`
3485
+ });
3486
+ } catch (err) {
3487
+ recordFailure("meta-landing", err);
3488
+ }
3489
+ }
3366
3490
  if (!dryRun && !aborted && Object.keys(collectedNpmDeps).length > 0) {
3367
3491
  try {
3368
3492
  await installProjectDeps({
@@ -3430,7 +3554,7 @@ async function runProjectInit(options) {
3430
3554
  detail: "projectRoot does not exist"
3431
3555
  });
3432
3556
  } else {
3433
- const giPath = path19.join(projectRoot, ".gitignore");
3557
+ const giPath = path20.join(projectRoot, ".gitignore");
3434
3558
  let giContent = "";
3435
3559
  try {
3436
3560
  giContent = await fsNode.readFile(giPath, "utf-8");
@@ -3486,15 +3610,16 @@ async function runProjectInit(options) {
3486
3610
  resumeCommand: "teamix-evo init"
3487
3611
  };
3488
3612
  }
3489
- if (!dryRun) {
3613
+ const hasFailures = steps.some((s) => s.status === "fail" || s.status === "skip");
3614
+ if (!dryRun && hasFailures) {
3490
3615
  try {
3491
3616
  const checklistContent = renderInitChecklist({ variant, status, steps });
3492
- const checklistPath = path19.join(
3617
+ const checklistPath = path20.join(
3493
3618
  projectRoot,
3494
3619
  ".teamix-evo",
3495
3620
  "init-checklist.md"
3496
3621
  );
3497
- await fsNode.mkdir(path19.dirname(checklistPath), { recursive: true });
3622
+ await fsNode.mkdir(path20.dirname(checklistPath), { recursive: true });
3498
3623
  await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
3499
3624
  logger.info(" wrote .teamix-evo/init-checklist.md");
3500
3625
  } catch {
@@ -3534,8 +3659,8 @@ function deriveLintChanges(result) {
3534
3659
  }
3535
3660
 
3536
3661
  // src/core/installer.ts
3537
- import * as path20 from "path";
3538
- import * as fs15 from "fs/promises";
3662
+ import * as path21 from "path";
3663
+ import * as fs18 from "fs/promises";
3539
3664
  async function installResources(options) {
3540
3665
  const { projectRoot, manifest, data, variantDir, packageRoot } = options;
3541
3666
  const installedResources = [];
@@ -3572,13 +3697,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
3572
3697
  variantDir,
3573
3698
  packageRoot
3574
3699
  );
3575
- const targetPath = path20.join(projectRoot, resource.target);
3700
+ const targetPath = path21.join(projectRoot, resource.target);
3576
3701
  let content;
3577
3702
  if (resource.template) {
3578
3703
  const templateContent = await loadTemplateFile(sourcePath);
3579
3704
  content = renderTemplate(templateContent, data);
3580
3705
  } else {
3581
- content = await fs15.readFile(sourcePath, "utf-8");
3706
+ content = await fs18.readFile(sourcePath, "utf-8");
3582
3707
  }
3583
3708
  await writeFileSafe(targetPath, content);
3584
3709
  const hash = computeHash(content);
@@ -3596,13 +3721,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3596
3721
  variantDir,
3597
3722
  packageRoot
3598
3723
  );
3599
- const targetDir = path20.join(projectRoot, resource.target);
3724
+ const targetDir = path21.join(projectRoot, resource.target);
3600
3725
  const results = [];
3601
3726
  await ensureDir(targetDir);
3602
3727
  const entries = await walkDir(sourcePath);
3603
3728
  for (const entry of entries) {
3604
- const relPath = path20.relative(sourcePath, entry);
3605
- let targetFile = path20.join(targetDir, relPath);
3729
+ const relPath = path21.relative(sourcePath, entry);
3730
+ let targetFile = path21.join(targetDir, relPath);
3606
3731
  if (resource.template && targetFile.endsWith(".hbs")) {
3607
3732
  targetFile = targetFile.slice(0, -4);
3608
3733
  }
@@ -3611,11 +3736,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3611
3736
  const templateContent = await loadTemplateFile(entry);
3612
3737
  content = renderTemplate(templateContent, data);
3613
3738
  } else {
3614
- content = await fs15.readFile(entry, "utf-8");
3739
+ content = await fs18.readFile(entry, "utf-8");
3615
3740
  }
3616
3741
  await writeFileSafe(targetFile, content);
3617
3742
  const hash = computeHash(content);
3618
- const targetRel = path20.relative(projectRoot, targetFile);
3743
+ const targetRel = path21.relative(projectRoot, targetFile);
3619
3744
  results.push({
3620
3745
  id: `${resource.id}:${relPath}`,
3621
3746
  target: targetRel,
@@ -3628,25 +3753,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3628
3753
  }
3629
3754
 
3630
3755
  // 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";
3756
+ import * as path22 from "path";
3757
+ import * as fs19 from "fs/promises";
3758
+ import { createRequire as createRequire6 } from "module";
3634
3759
  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);
3760
+ var require7 = createRequire6(import.meta.url);
3761
+ function resolvePackageRoot5(packageName) {
3762
+ const pkgJsonPath = require7.resolve(`${packageName}/package.json`);
3763
+ return path22.dirname(pkgJsonPath);
3639
3764
  }
3640
3765
  async function loadVariantData(packageName, variant) {
3641
- const packageRoot = resolvePackageRoot4(packageName);
3642
- const variantDir = path21.join(packageRoot, "library", variant);
3766
+ const packageRoot = resolvePackageRoot5(packageName);
3767
+ const variantDir = path22.join(packageRoot, "library", variant);
3643
3768
  logger.debug(`Resolved variant dir: ${variantDir}`);
3644
3769
  logger.debug(`Package root: ${packageRoot}`);
3645
3770
  const manifest = await loadVariantManifest(variantDir);
3646
3771
  let data = {};
3647
- const dataPath = path21.join(variantDir, "_data.json");
3772
+ const dataPath = path22.join(variantDir, "_data.json");
3648
3773
  try {
3649
- const raw = await fs16.readFile(dataPath, "utf-8");
3774
+ const raw = await fs19.readFile(dataPath, "utf-8");
3650
3775
  data = JSON.parse(raw);
3651
3776
  } catch (err) {
3652
3777
  if (err.code !== "ENOENT") {
@@ -3659,8 +3784,10 @@ async function loadVariantData(packageName, variant) {
3659
3784
  export {
3660
3785
  DEFAULT_UI_ALIASES,
3661
3786
  DEFAULT_UI_ICON_LIBRARY,
3787
+ assertCommandPrecondition,
3662
3788
  detectConflicts,
3663
- detectProjectState,
3789
+ detectProjectState2 as detectProjectState,
3790
+ detectProjectState as detectProjectStateLegacy,
3664
3791
  ensureTeamixDir,
3665
3792
  extractDescriptionParts,
3666
3793
  getTeamixDir,
@@ -3669,14 +3796,13 @@ export {
3669
3796
  installUiEntries,
3670
3797
  listBizUiEntries,
3671
3798
  listBizUiVariants,
3672
- listTemplatesEntries,
3673
- listTemplatesVariants,
3674
3799
  listTokenVariants,
3675
3800
  loadSkillsData,
3676
3801
  loadUiData,
3677
3802
  loadVariantData,
3678
3803
  readInstalledManifest,
3679
3804
  readProjectConfig,
3805
+ reinstallSkillsToIdes,
3680
3806
  removeSkillFiles,
3681
3807
  removeUiFiles,
3682
3808
  runBizUiAdd,
@@ -3686,12 +3812,10 @@ export {
3686
3812
  runSkillsAdd,
3687
3813
  runSkillsInit,
3688
3814
  runSkillsUpdate,
3689
- runTemplatesAdd,
3690
3815
  runTokensInit,
3691
3816
  runUiAdd,
3692
3817
  runUiInit,
3693
3818
  runUiList,
3694
- syncSkillsToIdes,
3695
3819
  updateSkills,
3696
3820
  writeInstalledManifest,
3697
3821
  writeProjectConfig