teamix-evo 0.8.0 → 0.9.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.
@@ -113,7 +113,8 @@ var TEAMIX_DIR = ".teamix-evo";
113
113
  var CONFIG_FILE = "config.json";
114
114
  var MANIFEST_FILE = "manifest.json";
115
115
  var TOKENS_LOCK_FILE = "tokens-lock.json";
116
- var SKILLS_DIR = "skills";
116
+ var SKILLS_DIR = "skills-source";
117
+ var LEGACY_SKILLS_DIR = "skills";
117
118
  var SKILLS_LOCK_FILE = "manifest.lock.json";
118
119
  function getTeamixDir(projectRoot) {
119
120
  return path2.join(projectRoot, TEAMIX_DIR);
@@ -197,6 +198,9 @@ function getSkillsSourceDir(projectRoot, skillName) {
197
198
  const base = path2.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
198
199
  return skillName ? path2.join(base, skillName) : base;
199
200
  }
201
+ function getLegacySkillsSourceDir(projectRoot) {
202
+ return path2.join(projectRoot, TEAMIX_DIR, LEGACY_SKILLS_DIR);
203
+ }
200
204
  async function readSkillsLock(projectRoot) {
201
205
  const lockPath = path2.join(
202
206
  projectRoot,
@@ -385,6 +389,7 @@ function resolveTokensPackageRoot(packageName) {
385
389
 
386
390
  // src/core/skills-installer.ts
387
391
  async function installSkills(options) {
392
+ await migrateLegacySkillsSourceDir(options.projectRoot);
388
393
  const { manifest, ides, scope, onlyIds } = options;
389
394
  const installed = [];
390
395
  const targets = manifest.skills.filter(
@@ -677,6 +682,7 @@ function escapeRegExp(str) {
677
682
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
678
683
  }
679
684
  async function syncSkillsToIdes(options) {
685
+ await migrateLegacySkillsSourceDir(options.projectRoot);
680
686
  const { projectRoot, skills, ides, scope, onlyIds } = options;
681
687
  const out = [];
682
688
  const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
@@ -720,6 +726,118 @@ async function syncSkillsToIdes(options) {
720
726
  }
721
727
  return { resources: out, count: out.length };
722
728
  }
729
+ async function migrateLegacySkillsSourceDir(projectRoot) {
730
+ const legacyDir = getLegacySkillsSourceDir(projectRoot);
731
+ const newDir = getSkillsSourceDir(projectRoot);
732
+ let legacyExists = false;
733
+ let newExists = false;
734
+ try {
735
+ legacyExists = (await fs5.stat(legacyDir)).isDirectory();
736
+ } catch {
737
+ legacyExists = false;
738
+ }
739
+ try {
740
+ newExists = (await fs5.stat(newDir)).isDirectory();
741
+ } catch {
742
+ newExists = false;
743
+ }
744
+ if (!legacyExists) return;
745
+ if (newExists) {
746
+ logger.warn(
747
+ `Detected stale legacy skills source dir at ${legacyDir} alongside ${newDir}; the new layout takes precedence \u2014 you can safely delete the legacy dir.`
748
+ );
749
+ return;
750
+ }
751
+ try {
752
+ await fs5.rename(legacyDir, newDir);
753
+ logger.info(
754
+ `Migrated skills source dir: \`.teamix-evo/${LEGACY_SKILLS_DIR}/\` \u2192 \`.teamix-evo/skills-source/\``
755
+ );
756
+ } catch (err) {
757
+ logger.warn(
758
+ `Failed to rename legacy skills source dir (${getErrorMessage(
759
+ err
760
+ )}); leaving as-is. New skills will install under the new layout.`
761
+ );
762
+ return;
763
+ }
764
+ try {
765
+ const manifest = await readInstalledManifest(projectRoot);
766
+ if (!manifest) return;
767
+ const legacyFragmentPosix = `/.teamix-evo/${LEGACY_SKILLS_DIR}/`;
768
+ const newFragmentPosix = `/.teamix-evo/skills-source/`;
769
+ const legacyFragmentNative = `${path7.sep}.teamix-evo${path7.sep}${LEGACY_SKILLS_DIR}${path7.sep}`;
770
+ const newFragmentNative = `${path7.sep}.teamix-evo${path7.sep}skills-source${path7.sep}`;
771
+ let touched = 0;
772
+ for (const pkg of manifest.installed) {
773
+ for (const r of pkg.resources) {
774
+ if (typeof r.target !== "string") continue;
775
+ const before = r.target;
776
+ let after = before.replace(legacyFragmentPosix, newFragmentPosix);
777
+ after = after.replace(legacyFragmentNative, newFragmentNative);
778
+ if (after !== before) {
779
+ r.target = after;
780
+ touched += 1;
781
+ }
782
+ }
783
+ }
784
+ if (touched > 0) {
785
+ await writeInstalledManifest(projectRoot, manifest);
786
+ logger.debug(
787
+ `Rewrote ${touched} manifest target(s) to the new skills-source path.`
788
+ );
789
+ }
790
+ } catch (err) {
791
+ logger.warn(
792
+ `Migrated skills source dir but failed to update manifest paths (${getErrorMessage(
793
+ err
794
+ )}); manifest may still reference legacy paths.`
795
+ );
796
+ }
797
+ }
798
+ async function pruneEmptyIdeSkillDirs(args) {
799
+ const removed = [];
800
+ for (const ide of args.ides) {
801
+ const adapter = getAdapter(ide);
802
+ const placeholderDir = adapter.getSkillTargetDir(
803
+ "__placeholder__",
804
+ args.scope,
805
+ args.projectRoot
806
+ );
807
+ const skillsRoot = path7.dirname(placeholderDir);
808
+ let entries;
809
+ try {
810
+ entries = await fs5.readdir(skillsRoot);
811
+ } catch {
812
+ continue;
813
+ }
814
+ for (const name of entries) {
815
+ const dir = path7.join(skillsRoot, name);
816
+ let stat5;
817
+ try {
818
+ stat5 = await fs5.stat(dir);
819
+ } catch {
820
+ continue;
821
+ }
822
+ if (!stat5.isDirectory()) continue;
823
+ let children;
824
+ try {
825
+ children = await fs5.readdir(dir);
826
+ } catch {
827
+ continue;
828
+ }
829
+ if (children.some((c) => c === "SKILL.md")) continue;
830
+ if (children.length !== 0) continue;
831
+ try {
832
+ await fs5.rmdir(dir);
833
+ removed.push(dir);
834
+ logger.debug(`Pruned empty IDE skill dir: ${dir}`);
835
+ } catch {
836
+ }
837
+ }
838
+ }
839
+ return removed;
840
+ }
723
841
  async function removeSkillFiles(records) {
724
842
  const removed = [];
725
843
  for (const r of records) {
@@ -815,13 +933,29 @@ async function runSkillsInit(options) {
815
933
  }
816
934
  return true;
817
935
  }).map((s) => s.id);
818
- const skippedSkillIds = candidateIds.filter(
819
- (id) => existing.skillIds.has(id)
936
+ const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
937
+ candidateIds,
938
+ manifest,
939
+ existing
820
940
  );
821
- const onlyIds = candidateIds.filter((id) => !existing.skillIds.has(id));
822
- if (existingSkillsCfg && onlyIds.length === 0) {
941
+ if (existingSkillsCfg && onlyIds.length === 0 && outdatedSkills.length === 0) {
823
942
  return { status: "already-initialized" };
824
943
  }
944
+ if (onlyIds.length === 0) {
945
+ return {
946
+ status: "installed",
947
+ packageName,
948
+ version: existingSkillsCfg?.version ?? manifest.version,
949
+ ides,
950
+ scope,
951
+ skillCount: 0,
952
+ fileCount: 0,
953
+ resources: [],
954
+ addedSkillIds: [],
955
+ skippedSkillIds,
956
+ outdatedSkills
957
+ };
958
+ }
825
959
  return finalizeSkillsInstall({
826
960
  projectRoot,
827
961
  packageName,
@@ -833,6 +967,7 @@ async function runSkillsInit(options) {
833
967
  scope,
834
968
  onlyIds,
835
969
  skippedSkillIds,
970
+ outdatedSkills,
836
971
  existing,
837
972
  existingConfig
838
973
  });
@@ -873,10 +1008,11 @@ async function runSkillsAdd(options) {
873
1008
  }
874
1009
  }
875
1010
  const existing = await readExistingState(projectRoot, packageName);
876
- const skippedSkillIds = requestedNames.filter(
877
- (n) => existing.skillIds.has(n)
1011
+ const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
1012
+ requestedNames,
1013
+ manifest,
1014
+ existing
878
1015
  );
879
- const onlyIds = requestedNames.filter((n) => !existing.skillIds.has(n));
880
1016
  if (onlyIds.length === 0) {
881
1017
  return {
882
1018
  status: "installed",
@@ -888,7 +1024,8 @@ async function runSkillsAdd(options) {
888
1024
  fileCount: 0,
889
1025
  resources: [],
890
1026
  addedSkillIds: [],
891
- skippedSkillIds
1027
+ skippedSkillIds,
1028
+ outdatedSkills
892
1029
  };
893
1030
  }
894
1031
  return finalizeSkillsInstall({
@@ -902,10 +1039,52 @@ async function runSkillsAdd(options) {
902
1039
  scope,
903
1040
  onlyIds,
904
1041
  skippedSkillIds,
1042
+ outdatedSkills,
905
1043
  existing,
906
1044
  existingConfig
907
1045
  });
908
1046
  }
1047
+ function partitionByVersion(ids, manifest, existing) {
1048
+ const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
1049
+ const onlyIds = [];
1050
+ const skippedSkillIds = [];
1051
+ const outdatedSkills = [];
1052
+ for (const name of ids) {
1053
+ if (!existing.skillIds.has(name)) {
1054
+ onlyIds.push(name);
1055
+ continue;
1056
+ }
1057
+ const installedVer = existing.lock?.skills?.[name]?.version;
1058
+ const latestVer = manifestById.get(name)?.version;
1059
+ if (installedVer && latestVer && compareSemver(installedVer, latestVer) < 0) {
1060
+ outdatedSkills.push({
1061
+ id: name,
1062
+ installed: installedVer,
1063
+ latest: latestVer
1064
+ });
1065
+ } else {
1066
+ skippedSkillIds.push(name);
1067
+ }
1068
+ }
1069
+ return { onlyIds, skippedSkillIds, outdatedSkills };
1070
+ }
1071
+ function parseSemverTriple(v) {
1072
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
1073
+ if (!m) return null;
1074
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
1075
+ }
1076
+ function compareSemver(a, b) {
1077
+ const pa = parseSemverTriple(a);
1078
+ const pb = parseSemverTriple(b);
1079
+ if (!pa || !pb) {
1080
+ if (a === b) return 0;
1081
+ return a < b ? -1 : 1;
1082
+ }
1083
+ for (let i = 0; i < 3; i++) {
1084
+ if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
1085
+ }
1086
+ return 0;
1087
+ }
909
1088
  async function readExistingState(projectRoot, packageName) {
910
1089
  const installed = await readInstalledManifest(projectRoot);
911
1090
  const pkg = installed?.installed.find((p) => p.package === packageName);
@@ -930,6 +1109,7 @@ async function finalizeSkillsInstall(args) {
930
1109
  scope,
931
1110
  onlyIds,
932
1111
  skippedSkillIds,
1112
+ outdatedSkills,
933
1113
  existing,
934
1114
  existingConfig
935
1115
  } = args;
@@ -995,6 +1175,10 @@ async function finalizeSkillsInstall(args) {
995
1175
  }
996
1176
  await writeSkillsLock(projectRoot, lock);
997
1177
  await ensureMcpJson(projectRoot);
1178
+ try {
1179
+ await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
1180
+ } catch {
1181
+ }
998
1182
  return {
999
1183
  status: "installed",
1000
1184
  packageName,
@@ -1005,7 +1189,8 @@ async function finalizeSkillsInstall(args) {
1005
1189
  fileCount: result.count,
1006
1190
  resources: result.resources,
1007
1191
  addedSkillIds: onlyIds,
1008
- skippedSkillIds
1192
+ skippedSkillIds,
1193
+ outdatedSkills: outdatedSkills ?? []
1009
1194
  };
1010
1195
  }
1011
1196
  function mergeInstalledResources(existing, next) {
@@ -1213,6 +1398,9 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1213
1398
  const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
1214
1399
  const targetAbs = path9.join(projectRoot, targetRel);
1215
1400
  const content = await fs6.readFile(sourceAbs, "utf-8");
1401
+ if (await fileExists(targetAbs)) {
1402
+ await backupFile(targetAbs, projectRoot);
1403
+ }
1216
1404
  await writeFileSafe(targetAbs, content);
1217
1405
  return {
1218
1406
  id: `tokens:${CONSUMER_THEME_FILE}`,
@@ -1593,6 +1781,9 @@ async function installUiEntries(options) {
1593
1781
  const sourceAbs = path11.resolve(rootForEntry, file.source);
1594
1782
  const raw = await fs8.readFile(sourceAbs, "utf-8");
1595
1783
  const transformed = rewriteImports(raw, aliases);
1784
+ if (exists) {
1785
+ await backupFile(targetAbs, projectRoot);
1786
+ }
1596
1787
  await writeFileSafe(targetAbs, transformed);
1597
1788
  written++;
1598
1789
  logger.info(` write: ${rel(projectRoot, targetAbs)}`);
@@ -1963,18 +2154,29 @@ var ESLINT_DEPS = [
1963
2154
  ];
1964
2155
  var STYLELINT_DEPS = ["@teamix-evo/stylelint-config", "stylelint"];
1965
2156
  async function runLintInit(options) {
1966
- const { projectRoot, skipInstall } = options;
2157
+ const {
2158
+ projectRoot,
2159
+ skipInstall,
2160
+ eslintStrategy = "overwrite",
2161
+ stylelintStrategy = "overwrite",
2162
+ eslintExistingPaths = [],
2163
+ stylelintExistingPaths = []
2164
+ } = options;
1967
2165
  const eslintConfigPath = path13.join(projectRoot, "eslint.config.js");
1968
2166
  const stylelintConfigPath = path13.join(projectRoot, "stylelint.config.cjs");
1969
- const eslintExists = await fileExists(eslintConfigPath);
1970
- const stylelintExists = await fileExists(stylelintConfigPath);
1971
- if (eslintExists && stylelintExists) {
2167
+ const eslintTemplateExists = await fileExists(eslintConfigPath);
2168
+ const stylelintTemplateExists = await fileExists(stylelintConfigPath);
2169
+ const eslintSkipRequested = eslintStrategy === "skip" && eslintExistingPaths.length > 0;
2170
+ const stylelintSkipRequested = stylelintStrategy === "skip" && stylelintExistingPaths.length > 0;
2171
+ const eslintNeedsWrite = !eslintTemplateExists && !eslintSkipRequested;
2172
+ const stylelintNeedsWrite = !stylelintTemplateExists && !stylelintSkipRequested;
2173
+ if (!eslintNeedsWrite && !stylelintNeedsWrite) {
1972
2174
  return { status: "already-initialized" };
1973
2175
  }
1974
2176
  if (!skipInstall) {
1975
2177
  const depsToInstall = [
1976
- ...eslintExists ? [] : ESLINT_DEPS,
1977
- ...stylelintExists ? [] : STYLELINT_DEPS
2178
+ ...eslintNeedsWrite ? ESLINT_DEPS : [],
2179
+ ...stylelintNeedsWrite ? STYLELINT_DEPS : []
1978
2180
  ];
1979
2181
  if (depsToInstall.length > 0) {
1980
2182
  const pm = detectPm(projectRoot);
@@ -1983,23 +2185,38 @@ async function runLintInit(options) {
1983
2185
  await execa(pm, args, { cwd: projectRoot, stdio: "inherit" });
1984
2186
  }
1985
2187
  }
2188
+ if (eslintNeedsWrite && eslintExistingPaths.length > 0) {
2189
+ for (const rel2 of eslintExistingPaths) {
2190
+ await backupFile(path13.join(projectRoot, rel2), projectRoot);
2191
+ }
2192
+ }
2193
+ if (stylelintNeedsWrite && stylelintExistingPaths.length > 0) {
2194
+ for (const rel2 of stylelintExistingPaths) {
2195
+ await backupFile(path13.join(projectRoot, rel2), projectRoot);
2196
+ }
2197
+ }
1986
2198
  let wroteEslint = false;
1987
2199
  let wroteStylelint = false;
1988
- if (!eslintExists) {
2200
+ if (eslintNeedsWrite) {
1989
2201
  await writeFileSafe(eslintConfigPath, ESLINT_CONFIG_CONTENT);
1990
2202
  logger.debug(`Wrote eslint.config.js \u2192 ${eslintConfigPath}`);
1991
2203
  wroteEslint = true;
1992
2204
  }
1993
- if (!stylelintExists) {
2205
+ if (stylelintNeedsWrite) {
1994
2206
  await writeFileSafe(stylelintConfigPath, STYLELINT_CONFIG_CONTENT);
1995
2207
  logger.debug(`Wrote stylelint.config.cjs \u2192 ${stylelintConfigPath}`);
1996
2208
  wroteStylelint = true;
1997
2209
  }
1998
- await patchPackageJsonScripts(projectRoot);
2210
+ const packageJsonPatched = await patchPackageJsonScripts(projectRoot);
1999
2211
  return {
2000
2212
  status: "installed",
2001
2213
  eslint: wroteEslint,
2002
- stylelint: wroteStylelint
2214
+ stylelint: wroteStylelint,
2215
+ eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
2216
+ stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
2217
+ eslintSkipped: eslintSkipRequested,
2218
+ stylelintSkipped: stylelintSkipRequested,
2219
+ packageJsonPatched
2003
2220
  };
2004
2221
  }
2005
2222
  function detectPm(projectRoot) {
@@ -2010,12 +2227,12 @@ function detectPm(projectRoot) {
2010
2227
  async function patchPackageJsonScripts(projectRoot) {
2011
2228
  const pkgPath = path13.join(projectRoot, "package.json");
2012
2229
  const raw = await readFileOrNull(pkgPath);
2013
- if (!raw) return;
2230
+ if (!raw) return false;
2014
2231
  let pkg;
2015
2232
  try {
2016
2233
  pkg = JSON.parse(raw);
2017
2234
  } catch {
2018
- return;
2235
+ return false;
2019
2236
  }
2020
2237
  const scripts = pkg.scripts ?? {};
2021
2238
  let changed = false;
@@ -2028,17 +2245,22 @@ async function patchPackageJsonScripts(projectRoot) {
2028
2245
  changed = true;
2029
2246
  }
2030
2247
  if (changed) {
2248
+ await backupFile(pkgPath, projectRoot);
2031
2249
  pkg.scripts = scripts;
2032
2250
  await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
2033
2251
  logger.debug("Patched package.json scripts with lint / lint:css");
2034
2252
  }
2253
+ return changed;
2035
2254
  }
2036
2255
 
2037
2256
  // src/core/agents-md.ts
2038
2257
  import * as fs10 from "fs/promises";
2039
2258
  import * as path14 from "path";
2259
+ import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
2260
+ var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
2040
2261
  async function runGenerateAgentsMd(options) {
2041
2262
  const { projectRoot, variant, skillIds } = options;
2263
+ const mode = options.mode ?? "overwrite";
2042
2264
  const ordered = [...skillIds].sort(
2043
2265
  (a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
2044
2266
  );
@@ -2049,13 +2271,47 @@ async function runGenerateAgentsMd(options) {
2049
2271
  sections.push(section);
2050
2272
  if (missing) missingSkillIds.push(id);
2051
2273
  }
2052
- const body = renderAgentsMd({ variant, sections });
2053
2274
  const target = path14.join(projectRoot, "AGENTS.md");
2054
- await fs10.writeFile(target, body, "utf8");
2275
+ const targetExists = await fileExists(target);
2276
+ const fullTemplate = renderAgentsMd({ variant, sections });
2277
+ const managedBody = renderManagedBlockBody({ variant, sections });
2278
+ let outputContent;
2279
+ let merge;
2280
+ if (!targetExists) {
2281
+ outputContent = fullTemplate;
2282
+ merge = "created";
2283
+ } else {
2284
+ await backupFile(target, projectRoot);
2285
+ if (mode === "merge-managed") {
2286
+ const existing = await readFileOrNull(target) ?? "";
2287
+ if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
2288
+ outputContent = replaceManagedRegion2(
2289
+ existing,
2290
+ AGENTS_MD_MANAGED_ID,
2291
+ managedBody
2292
+ );
2293
+ merge = "managed-replaced";
2294
+ } else {
2295
+ const wrapped = wrapManagedBlock(managedBody);
2296
+ outputContent = `${wrapped}
2297
+
2298
+ ${PRECEDENCE_NOTICE}
2299
+
2300
+ ${existing.trimStart()}`;
2301
+ merge = "managed-prepended";
2302
+ }
2303
+ } else {
2304
+ outputContent = fullTemplate;
2305
+ merge = "overwritten";
2306
+ }
2307
+ }
2308
+ await fs10.writeFile(target, outputContent, "utf8");
2055
2309
  return {
2056
2310
  path: target,
2057
2311
  skillCount: ordered.length,
2058
- missingSkillIds
2312
+ missingSkillIds,
2313
+ backedUp: targetExists,
2314
+ merge
2059
2315
  };
2060
2316
  }
2061
2317
  function bucketRank(id) {
@@ -2065,10 +2321,7 @@ function bucketRank(id) {
2065
2321
  }
2066
2322
  async function renderSkillSection(projectRoot, skillId) {
2067
2323
  const skillPath = path14.join(
2068
- projectRoot,
2069
- ".teamix-evo",
2070
- "skills",
2071
- skillId,
2324
+ getSkillsSourceDir(projectRoot, skillId),
2072
2325
  "SKILL.md"
2073
2326
  );
2074
2327
  const lines = [];
@@ -2093,10 +2346,19 @@ async function renderSkillSection(projectRoot, skillId) {
2093
2346
  if (parts?.coordinates) {
2094
2347
  lines.push(`- **Coordinates with**: ${parts.coordinates}`);
2095
2348
  }
2096
- lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills/${skillId}/SKILL.md\``);
2349
+ lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills-source/${skillId}/SKILL.md\``);
2097
2350
  return { section: lines.join("\n"), missing };
2098
2351
  }
2099
2352
  function renderAgentsMd(args) {
2353
+ const { variant, sections } = args;
2354
+ const managedBody = renderManagedBlockBody({ variant, sections });
2355
+ const wrapped = wrapManagedBlock(managedBody);
2356
+ return `${wrapped}
2357
+
2358
+ ${PRECEDENCE_NOTICE}
2359
+ `;
2360
+ }
2361
+ function renderManagedBlockBody(args) {
2100
2362
  const { variant, sections } = args;
2101
2363
  const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
2102
2364
  return `# AGENTS.md
@@ -2115,9 +2377,15 @@ ${skillBlock}
2115
2377
  - \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
2116
2378
  - \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
2117
2379
 
2118
- > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002
2119
- `;
2380
+ > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
2120
2381
  }
2382
+ function wrapManagedBlock(body) {
2383
+ return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
2384
+ ${body}
2385
+ <!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
2386
+ }
2387
+ var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
2388
+ > \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`;
2121
2389
  function extractDescriptionParts(fileContent) {
2122
2390
  const description = extractDescriptionBlock(fileContent);
2123
2391
  if (description == null) return null;
@@ -2311,6 +2579,26 @@ var INDEX_CSS_CANDIDATES = [
2311
2579
  ];
2312
2580
  var SHADCN_FILE_CANDIDATES = ["src/lib/utils.ts"];
2313
2581
  var SHADCN_DIR_CANDIDATES = ["src/components/ui"];
2582
+ var ESLINT_CONFIG_CANDIDATES = [
2583
+ ".eslintrc.cjs",
2584
+ ".eslintrc.js",
2585
+ ".eslintrc.json",
2586
+ ".eslintrc.yml",
2587
+ "eslint.config.js",
2588
+ "eslint.config.cjs",
2589
+ "eslint.config.mjs",
2590
+ "eslint.config.ts"
2591
+ ];
2592
+ var STYLELINT_CONFIG_CANDIDATES = [
2593
+ ".stylelintrc.cjs",
2594
+ ".stylelintrc.js",
2595
+ ".stylelintrc.json",
2596
+ ".stylelintrc.yml",
2597
+ "stylelint.config.cjs",
2598
+ "stylelint.config.js",
2599
+ "stylelint.config.mjs",
2600
+ "stylelint.config.ts"
2601
+ ];
2314
2602
  async function isDir(target) {
2315
2603
  try {
2316
2604
  const stat5 = await fs12.stat(target);
@@ -2487,7 +2775,9 @@ async function detectConflicts(cwd) {
2487
2775
  detectTailwindConfig(absCwd),
2488
2776
  detectTokens(absCwd),
2489
2777
  detectIndexCss(absCwd),
2490
- detectShadcnSource(absCwd)
2778
+ detectShadcnSource(absCwd),
2779
+ detectEslintConfig(absCwd),
2780
+ detectStylelintConfig(absCwd)
2491
2781
  ]);
2492
2782
  return {
2493
2783
  cwd: absCwd,
@@ -2495,6 +2785,46 @@ async function detectConflicts(cwd) {
2495
2785
  hasAnyConflict: items.some((i) => i.exists)
2496
2786
  };
2497
2787
  }
2788
+ async function detectEslintConfig(cwd) {
2789
+ const matched = [];
2790
+ const contents = [];
2791
+ for (const rel2 of ESLINT_CONFIG_CANDIDATES) {
2792
+ const c = await readFileOrNull(path16.join(cwd, rel2));
2793
+ if (c !== null) {
2794
+ matched.push(rel2);
2795
+ contents.push(c);
2796
+ }
2797
+ }
2798
+ const exists = matched.length > 0;
2799
+ return {
2800
+ key: "eslint-config",
2801
+ exists,
2802
+ paths: matched,
2803
+ fingerprint: exists ? fingerprint(contents) : void 0,
2804
+ recommendedStrategy: exists ? "merge" : "overwrite",
2805
+ availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
2806
+ };
2807
+ }
2808
+ async function detectStylelintConfig(cwd) {
2809
+ const matched = [];
2810
+ const contents = [];
2811
+ for (const rel2 of STYLELINT_CONFIG_CANDIDATES) {
2812
+ const c = await readFileOrNull(path16.join(cwd, rel2));
2813
+ if (c !== null) {
2814
+ matched.push(rel2);
2815
+ contents.push(c);
2816
+ }
2817
+ }
2818
+ const exists = matched.length > 0;
2819
+ return {
2820
+ key: "stylelint-config",
2821
+ exists,
2822
+ paths: matched,
2823
+ fingerprint: exists ? fingerprint(contents) : void 0,
2824
+ recommendedStrategy: exists ? "merge" : "overwrite",
2825
+ availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
2826
+ };
2827
+ }
2498
2828
 
2499
2829
  // src/core/legacy-tokens-migrate.ts
2500
2830
  import * as path17 from "path";
@@ -2706,6 +3036,56 @@ async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
2706
3036
  return removed.reverse();
2707
3037
  }
2708
3038
 
3039
+ // src/core/file-changes.ts
3040
+ import * as fs15 from "fs/promises";
3041
+ import * as path19 from "path";
3042
+ function toRelativePosix(p, projectRoot) {
3043
+ let rel2 = p;
3044
+ if (path19.isAbsolute(p)) {
3045
+ rel2 = path19.relative(projectRoot, p);
3046
+ }
3047
+ return rel2.split(path19.sep).join("/");
3048
+ }
3049
+ async function listBackupOriginals(projectRoot) {
3050
+ const backupsDir = path19.join(projectRoot, ".teamix-evo", ".backups");
3051
+ const out = /* @__PURE__ */ new Set();
3052
+ const stack = [backupsDir];
3053
+ while (stack.length > 0) {
3054
+ const dir = stack.pop();
3055
+ let entries;
3056
+ try {
3057
+ entries = await fs15.readdir(dir, { withFileTypes: true });
3058
+ } catch (err) {
3059
+ if (err.code === "ENOENT") continue;
3060
+ throw err;
3061
+ }
3062
+ for (const e of entries) {
3063
+ const full = path19.join(dir, e.name);
3064
+ if (e.isDirectory()) {
3065
+ stack.push(full);
3066
+ } else if (e.isFile() && e.name.endsWith(".bak")) {
3067
+ const rel2 = path19.relative(backupsDir, full);
3068
+ const original = stripBackupSuffix(rel2);
3069
+ if (original) out.add(original.split(path19.sep).join("/"));
3070
+ }
3071
+ }
3072
+ }
3073
+ return out;
3074
+ }
3075
+ function stripBackupSuffix(rel2) {
3076
+ const m = rel2.match(
3077
+ /^(.+)\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.bak$/
3078
+ );
3079
+ return m?.[1] ?? null;
3080
+ }
3081
+ function diffBackupSet(before, after) {
3082
+ const out = /* @__PURE__ */ new Set();
3083
+ for (const p of after) {
3084
+ if (!before.has(p)) out.add(p);
3085
+ }
3086
+ return out;
3087
+ }
3088
+
2709
3089
  // src/core/project-init.ts
2710
3090
  var BASELINE_UI_ENTRIES = [
2711
3091
  "button",
@@ -2728,12 +3108,19 @@ var CRITICAL_STEPS = /* @__PURE__ */ new Set([
2728
3108
  "ui-init"
2729
3109
  ]);
2730
3110
  var IMPLEMENTED_STRATEGIES = {
3111
+ // 'agents-md': both 'merge-managed' (Phase 2.B — splice the
3112
+ // teamix-evo-skills managed region) and 'overwrite' (full rewrite) write
3113
+ // through `runGenerateAgentsMd`.
2731
3114
  "agents-md": ["merge-managed", "overwrite", "skip"],
2732
3115
  tokens: ["migrate", "overwrite", "skip"],
2733
3116
  "components-json": ["overwrite", "skip"],
2734
3117
  "shadcn-source": ["overwrite", "skip-existing", "skip"],
2735
3118
  "tailwind-config": ["skip"],
2736
- "index-css": ["skip"]
3119
+ "index-css": ["skip"],
3120
+ // Phase 3.E: lint conflict strategies are honored end-to-end by
3121
+ // `runLintInit` (backup user file + write template, optional AI-assist hint).
3122
+ "eslint-config": ["merge", "backup-overwrite", "skip", "overwrite"],
3123
+ "stylelint-config": ["merge", "backup-overwrite", "skip", "overwrite"]
2737
3124
  };
2738
3125
  function pickIde(ides) {
2739
3126
  return ides[0] ?? "qoder";
@@ -2751,11 +3138,75 @@ async function resolveUiEntries(options) {
2751
3138
  }
2752
3139
  return [...BASELINE_UI_ENTRIES];
2753
3140
  }
3141
+ function deriveTokensChanges(result, projectRoot) {
3142
+ if (result.status !== "installed") return [];
3143
+ return result.resources.map((r) => ({
3144
+ kind: "created",
3145
+ path: toRelativePosix(r.target, projectRoot),
3146
+ step: "tokens",
3147
+ detail: r.strategy
3148
+ }));
3149
+ }
3150
+ function deriveSkillsChanges(result, projectRoot) {
3151
+ if (result.status !== "installed") return [];
3152
+ return result.addedSkillIds.map((id) => ({
3153
+ kind: "created",
3154
+ path: `.teamix-evo/skills/${id}/SKILL.md`,
3155
+ step: "skills",
3156
+ detail: "skill installed (source mirror + IDE mirrors)"
3157
+ }));
3158
+ }
3159
+ function deriveUiAddChanges(result, projectRoot) {
3160
+ const out = [];
3161
+ let remaining = result.written;
3162
+ for (let i = result.resources.length - 1; i >= 0 && remaining > 0; i--) {
3163
+ const r = result.resources[i];
3164
+ out.unshift({
3165
+ kind: "created",
3166
+ path: toRelativePosix(r.target, projectRoot),
3167
+ step: "ui-add",
3168
+ detail: r.strategy
3169
+ });
3170
+ remaining--;
3171
+ }
3172
+ return out;
3173
+ }
3174
+ function deriveLintChanges(result) {
3175
+ if (result.status !== "installed") return [];
3176
+ const out = [];
3177
+ if (result.eslint) {
3178
+ out.push({
3179
+ kind: "created",
3180
+ path: "eslint.config.js",
3181
+ step: "lint",
3182
+ detail: "@teamix-evo/eslint-config consumer preset"
3183
+ });
3184
+ }
3185
+ if (result.stylelint) {
3186
+ out.push({
3187
+ kind: "created",
3188
+ path: "stylelint.config.cjs",
3189
+ step: "lint",
3190
+ detail: "@teamix-evo/stylelint-config consumer preset"
3191
+ });
3192
+ }
3193
+ if (result.packageJsonPatched) {
3194
+ out.push({
3195
+ kind: "created",
3196
+ path: "package.json",
3197
+ step: "lint",
3198
+ detail: 'scripts.lint / scripts["lint:css"]'
3199
+ });
3200
+ }
3201
+ return out;
3202
+ }
2754
3203
  async function runProjectInit(options) {
2755
3204
  const { projectRoot, answers, dryRun = false, onStep } = options;
2756
3205
  const ide = pickIde(answers.ides);
2757
3206
  const steps = [];
2758
3207
  const pending = [];
3208
+ const allChanges = [];
3209
+ const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
2759
3210
  let snapshot = null;
2760
3211
  let snapshotError;
2761
3212
  if (!dryRun) {
@@ -2772,6 +3223,9 @@ async function runProjectInit(options) {
2772
3223
  function record(step) {
2773
3224
  steps.push(step);
2774
3225
  onStep?.(step);
3226
+ if (step.changes && step.changes.length > 0) {
3227
+ allChanges.push(...step.changes);
3228
+ }
2775
3229
  }
2776
3230
  function recordPending(key) {
2777
3231
  const strategy = answers.conflictDecisions[key];
@@ -2831,7 +3285,8 @@ async function runProjectInit(options) {
2831
3285
  record({
2832
3286
  name: "tokens",
2833
3287
  status: "ok",
2834
- detail
3288
+ detail,
3289
+ changes: deriveTokensChanges(result, projectRoot)
2835
3290
  });
2836
3291
  } catch (err) {
2837
3292
  recordFailure("tokens", err);
@@ -2863,7 +3318,8 @@ async function runProjectInit(options) {
2863
3318
  record({
2864
3319
  name: "skills",
2865
3320
  status: "ok",
2866
- detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status
3321
+ detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
3322
+ changes: deriveSkillsChanges(result, projectRoot)
2867
3323
  });
2868
3324
  } catch (err) {
2869
3325
  recordFailure("skills", err);
@@ -2891,12 +3347,25 @@ async function runProjectInit(options) {
2891
3347
  const result = await runGenerateAgentsMd({
2892
3348
  projectRoot,
2893
3349
  variant: answers.variant,
2894
- skillIds: agentsMdSkillIds
3350
+ skillIds: agentsMdSkillIds,
3351
+ // Phase 2.B: when the user picked `merge-managed` on conflict, only
3352
+ // rewrite the `teamix-evo-skills` managed region so hand-written
3353
+ // sections survive the regenerate step. `overwrite` keeps the
3354
+ // historical full-rewrite default.
3355
+ mode: agentsMdDecision === "merge-managed" ? "merge-managed" : "overwrite"
2895
3356
  });
2896
3357
  record({
2897
3358
  name: "agents-md",
2898
3359
  status: "ok",
2899
- detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`
3360
+ detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`,
3361
+ changes: [
3362
+ {
3363
+ kind: result.backedUp ? "modified" : "created",
3364
+ path: toRelativePosix(result.path, projectRoot),
3365
+ step: "agents-md",
3366
+ detail: "skill-trigger fallback (ADR 0038)"
3367
+ }
3368
+ ]
2900
3369
  });
2901
3370
  } catch (err) {
2902
3371
  recordFailure("agents-md", err);
@@ -2981,7 +3450,8 @@ async function runProjectInit(options) {
2981
3450
  record({
2982
3451
  name: "ui-add",
2983
3452
  status: "ok",
2984
- detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`
3453
+ detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
3454
+ changes: deriveUiAddChanges(addResult, projectRoot)
2985
3455
  });
2986
3456
  } catch (err) {
2987
3457
  recordFailure("ui-add", err);
@@ -3005,14 +3475,39 @@ async function runProjectInit(options) {
3005
3475
  });
3006
3476
  } else {
3007
3477
  try {
3478
+ const eslintStrategy = answers.conflictDecisions["eslint-config"];
3479
+ const stylelintStrategy = answers.conflictDecisions["stylelint-config"];
3480
+ const eslintExistingPaths = options.legacyEslintPaths ?? [];
3481
+ const stylelintExistingPaths = options.legacyStylelintPaths ?? [];
3008
3482
  const result = await runLintInit({
3009
3483
  projectRoot,
3010
- skipInstall: options.skipInstall ?? false
3484
+ skipInstall: options.skipInstall ?? false,
3485
+ eslintStrategy: eslintStrategy === "merge" || eslintStrategy === "backup-overwrite" || eslintStrategy === "skip" || eslintStrategy === "overwrite" ? eslintStrategy : "overwrite",
3486
+ stylelintStrategy: stylelintStrategy === "merge" || stylelintStrategy === "backup-overwrite" || stylelintStrategy === "skip" || stylelintStrategy === "overwrite" ? stylelintStrategy : "overwrite",
3487
+ eslintExistingPaths,
3488
+ stylelintExistingPaths
3011
3489
  });
3490
+ const detailParts = [];
3491
+ if (result.status === "installed") {
3492
+ detailParts.push(
3493
+ `eslint=${result.eslint}, stylelint=${result.stylelint}`
3494
+ );
3495
+ if (result.eslintMergeRequested) {
3496
+ detailParts.push("eslint:AI-merge-pending");
3497
+ }
3498
+ if (result.stylelintMergeRequested) {
3499
+ detailParts.push("stylelint:AI-merge-pending");
3500
+ }
3501
+ if (result.eslintSkipped) detailParts.push("eslint:skipped");
3502
+ if (result.stylelintSkipped) detailParts.push("stylelint:skipped");
3503
+ } else {
3504
+ detailParts.push(result.status);
3505
+ }
3012
3506
  record({
3013
3507
  name: "lint",
3014
3508
  status: "ok",
3015
- detail: result.status === "installed" ? `eslint=${result.eslint}, stylelint=${result.stylelint}` : result.status
3509
+ detail: detailParts.join(" / "),
3510
+ changes: deriveLintChanges(result)
3016
3511
  });
3017
3512
  } catch (err) {
3018
3513
  recordFailure("lint", err);
@@ -3020,11 +3515,34 @@ async function runProjectInit(options) {
3020
3515
  }
3021
3516
  recordPending("tailwind-config");
3022
3517
  recordPending("index-css");
3518
+ if (!dryRun) {
3519
+ try {
3520
+ const backupsAfter = await listBackupOriginals(projectRoot);
3521
+ const newlyBackedUp = diffBackupSet(backupsBefore, backupsAfter);
3522
+ if (newlyBackedUp.size > 0) {
3523
+ for (const change of allChanges) {
3524
+ if (change.kind === "created" && newlyBackedUp.has(change.path)) {
3525
+ change.kind = "modified";
3526
+ }
3527
+ }
3528
+ for (const rel2 of newlyBackedUp) {
3529
+ allChanges.push({
3530
+ kind: "backed-up",
3531
+ path: rel2,
3532
+ step: "backup",
3533
+ detail: ".teamix-evo/.backups/<\u540C\u8DEF\u5F84>.<isoTs>.bak"
3534
+ });
3535
+ }
3536
+ }
3537
+ } catch {
3538
+ }
3539
+ }
3023
3540
  const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
3024
3541
  const out = {
3025
3542
  status,
3026
3543
  steps,
3027
3544
  pendingConflictWork: pending,
3545
+ changes: allChanges,
3028
3546
  snapshot
3029
3547
  };
3030
3548
  if (snapshotError) out.snapshotError = snapshotError;
@@ -3045,22 +3563,22 @@ async function runProjectInit(options) {
3045
3563
  }
3046
3564
 
3047
3565
  // src/core/project-update.ts
3048
- import * as path24 from "path";
3566
+ import * as path25 from "path";
3049
3567
  import {
3050
3568
  loadTokensPackageManifest as loadTokensPackageManifest3,
3051
3569
  getVariantEntry as getVariantEntry3
3052
3570
  } from "@teamix-evo/registry";
3053
3571
 
3054
3572
  // src/core/tokens-update.ts
3055
- import * as path20 from "path";
3056
- import * as fs15 from "fs/promises";
3573
+ import * as path21 from "path";
3574
+ import * as fs16 from "fs/promises";
3057
3575
  import {
3058
3576
  loadTokensPackageManifest as loadTokensPackageManifest2,
3059
3577
  getVariantEntry as getVariantEntry2
3060
3578
  } from "@teamix-evo/registry";
3061
3579
 
3062
3580
  // src/core/upgrade-hints.ts
3063
- import * as path19 from "path";
3581
+ import * as path20 from "path";
3064
3582
  var TEAMIX_DIR3 = ".teamix-evo";
3065
3583
  var HINTS_DIR = ".upgrade-hints";
3066
3584
  function isoToFsSafe2(iso) {
@@ -3071,7 +3589,7 @@ async function writeTokensUpgradeHint(options) {
3071
3589
  const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3072
3590
  const fsTs = isoToFsSafe2(isoTs);
3073
3591
  const filename = `tokens-${fsTs}.json`;
3074
- const target = path19.join(
3592
+ const target = path20.join(
3075
3593
  options.projectRoot,
3076
3594
  TEAMIX_DIR3,
3077
3595
  HINTS_DIR,
@@ -3097,10 +3615,10 @@ async function writeTokensUpgradeHint(options) {
3097
3615
  }
3098
3616
  function selectApplicableRenames(renames, fromVersion, toVersion) {
3099
3617
  return renames.filter(
3100
- (r) => compareSemver(r.sinceVersion, fromVersion) > 0 && compareSemver(r.sinceVersion, toVersion) <= 0
3101
- ).sort((a, b) => compareSemver(a.sinceVersion, b.sinceVersion));
3618
+ (r) => compareSemver2(r.sinceVersion, fromVersion) > 0 && compareSemver2(r.sinceVersion, toVersion) <= 0
3619
+ ).sort((a, b) => compareSemver2(a.sinceVersion, b.sinceVersion));
3102
3620
  }
3103
- function compareSemver(a, b) {
3621
+ function compareSemver2(a, b) {
3104
3622
  const [aMain = "", aRest = ""] = a.split("-", 2);
3105
3623
  const [bMain = "", bRest = ""] = b.split("-", 2);
3106
3624
  const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
@@ -3116,7 +3634,7 @@ function compareSemver(a, b) {
3116
3634
  }
3117
3635
 
3118
3636
  // src/core/managed-merge.ts
3119
- import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
3637
+ import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
3120
3638
  function mergeManagedRegions(upstreamContent, consumerContent) {
3121
3639
  let updated = consumerContent;
3122
3640
  const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
@@ -3124,12 +3642,12 @@ function mergeManagedRegions(upstreamContent, consumerContent) {
3124
3642
  while ((match = re.exec(upstreamContent)) !== null) {
3125
3643
  const id = match[1];
3126
3644
  const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
3127
- if (!hasManagedRegion2(updated, id)) {
3645
+ if (!hasManagedRegion3(updated, id)) {
3128
3646
  throw new Error(
3129
3647
  `Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
3130
3648
  );
3131
3649
  }
3132
- updated = replaceManagedRegion2(updated, id, body);
3650
+ updated = replaceManagedRegion3(updated, id, body);
3133
3651
  }
3134
3652
  return updated;
3135
3653
  }
@@ -3160,8 +3678,8 @@ async function runTokensUpdate(options) {
3160
3678
  const upstreamByBasename = /* @__PURE__ */ new Map();
3161
3679
  for (const fileRel of variantEntry.files) {
3162
3680
  upstreamByBasename.set(
3163
- path20.basename(fileRel),
3164
- path20.join(packageRoot, fileRel)
3681
+ path21.basename(fileRel),
3682
+ path21.join(packageRoot, fileRel)
3165
3683
  );
3166
3684
  }
3167
3685
  const prior = await readInstalledManifest(projectRoot) ?? {
@@ -3178,8 +3696,8 @@ async function runTokensUpdate(options) {
3178
3696
  const frozenDrift = [];
3179
3697
  const refreshedResources = [];
3180
3698
  for (const resource of priorResources) {
3181
- const consumerAbs = path20.isAbsolute(resource.target) ? resource.target : path20.join(projectRoot, resource.target);
3182
- const consumerBasename = path20.basename(resource.target);
3699
+ const consumerAbs = path21.isAbsolute(resource.target) ? resource.target : path21.join(projectRoot, resource.target);
3700
+ const consumerBasename = path21.basename(resource.target);
3183
3701
  const upstreamBasename = lookupUpstreamBasename(consumerBasename);
3184
3702
  const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
3185
3703
  if (resource.strategy === "regenerable") {
@@ -3187,7 +3705,7 @@ async function runTokensUpdate(options) {
3187
3705
  refreshedResources.push(resource);
3188
3706
  continue;
3189
3707
  }
3190
- const content = await fs15.readFile(upstreamAbs, "utf-8");
3708
+ const content = await fs16.readFile(upstreamAbs, "utf-8");
3191
3709
  await writeFileSafe(consumerAbs, content);
3192
3710
  rewritten.push(resource.target);
3193
3711
  refreshedResources.push({
@@ -3201,8 +3719,8 @@ async function runTokensUpdate(options) {
3201
3719
  refreshedResources.push(resource);
3202
3720
  continue;
3203
3721
  }
3204
- const upstreamContent = await fs15.readFile(upstreamAbs, "utf-8");
3205
- const consumerContent = await fs15.readFile(consumerAbs, "utf-8");
3722
+ const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
3723
+ const consumerContent = await fs16.readFile(consumerAbs, "utf-8");
3206
3724
  const merged = mergeManagedRegions(upstreamContent, consumerContent);
3207
3725
  if (merged !== consumerContent) {
3208
3726
  await writeFileSafe(consumerAbs, merged);
@@ -3216,7 +3734,7 @@ async function runTokensUpdate(options) {
3216
3734
  }
3217
3735
  if (await fileExists(consumerAbs)) preserved.push(resource.target);
3218
3736
  if (upstreamAbs) {
3219
- const upstreamContent = await fs15.readFile(upstreamAbs, "utf-8");
3737
+ const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
3220
3738
  const upstreamHash = computeHash(upstreamContent);
3221
3739
  if (resource.hash && upstreamHash !== resource.hash) {
3222
3740
  frozenDrift.push({
@@ -3256,7 +3774,7 @@ async function runTokensUpdate(options) {
3256
3774
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
3257
3775
  };
3258
3776
  await writeFileSafe(
3259
- path20.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
3777
+ path21.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
3260
3778
  JSON.stringify(lock, null, 2) + "\n"
3261
3779
  );
3262
3780
  config.packages.tokens.version = variantEntry.version;
@@ -3312,8 +3830,8 @@ function lookupUpstreamBasename(consumerBasename) {
3312
3830
  }
3313
3831
 
3314
3832
  // src/core/ui-upgrade-detector.ts
3315
- import * as fs16 from "fs/promises";
3316
- import * as path21 from "path";
3833
+ import * as fs17 from "fs/promises";
3834
+ import * as path22 from "path";
3317
3835
  var PACKAGE_NAME = {
3318
3836
  ui: "@teamix-evo/ui",
3319
3837
  "biz-ui": "@teamix-evo/biz-ui"
@@ -3329,10 +3847,10 @@ async function detectComponentLineage(options) {
3329
3847
  const config = options.config ?? await readProjectConfig(projectRoot);
3330
3848
  const installed = options.installed ?? await readInstalledManifest(projectRoot);
3331
3849
  const installDir = resolveInstallDir(category, config);
3332
- const installDirAbs = path21.join(projectRoot, installDir);
3850
+ const installDirAbs = path22.join(projectRoot, installDir);
3333
3851
  const installDirExists = await directoryExists(installDirAbs);
3334
3852
  const hasComponentsJson = await fileExists(
3335
- path21.join(projectRoot, "components.json")
3853
+ path22.join(projectRoot, "components.json")
3336
3854
  );
3337
3855
  const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
3338
3856
  const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
@@ -3372,14 +3890,14 @@ function extractIds(pkg) {
3372
3890
  }
3373
3891
  async function directoryExists(p) {
3374
3892
  try {
3375
- const stat5 = await fs16.stat(p);
3893
+ const stat5 = await fs17.stat(p);
3376
3894
  return stat5.isDirectory();
3377
3895
  } catch {
3378
3896
  return false;
3379
3897
  }
3380
3898
  }
3381
3899
  async function listComponentIds(installDirAbs) {
3382
- const entries = await fs16.readdir(installDirAbs, { withFileTypes: true });
3900
+ const entries = await fs17.readdir(installDirAbs, { withFileTypes: true });
3383
3901
  const ids = [];
3384
3902
  for (const e of entries) {
3385
3903
  if (!e.isFile()) continue;
@@ -3399,7 +3917,7 @@ function classifyLineage(args) {
3399
3917
  }
3400
3918
 
3401
3919
  // src/core/ui-upgrade.ts
3402
- import * as path23 from "path";
3920
+ import * as path24 from "path";
3403
3921
  import { createRequire as createRequire5 } from "module";
3404
3922
  import {
3405
3923
  loadUiPackageManifest as loadUiPackageManifest3,
@@ -3407,7 +3925,7 @@ import {
3407
3925
  } from "@teamix-evo/registry";
3408
3926
 
3409
3927
  // src/core/ui-upgrade-staging.ts
3410
- import * as path22 from "path";
3928
+ import * as path23 from "path";
3411
3929
  var TEAMIX_DIR4 = ".teamix-evo";
3412
3930
  var STAGING_DIR = ".upgrade-staging";
3413
3931
  var PACKAGE_NAME2 = {
@@ -3427,7 +3945,7 @@ async function buildUiUpgradeStaging(options) {
3427
3945
  if (!installedPkg) return null;
3428
3946
  const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3429
3947
  const fsTs = isoToFsSafe3(isoTs);
3430
- const stagingDir = path22.join(
3948
+ const stagingDir = path23.join(
3431
3949
  options.projectRoot,
3432
3950
  TEAMIX_DIR4,
3433
3951
  STAGING_DIR,
@@ -3459,7 +3977,7 @@ async function buildUiUpgradeStaging(options) {
3459
3977
  if (onlyIds && !onlyIds.has(id)) continue;
3460
3978
  const built = await processForeign({
3461
3979
  id,
3462
- installDirAbs: path22.join(options.projectRoot, lineageReport.installDir),
3980
+ installDirAbs: path23.join(options.projectRoot, lineageReport.installDir),
3463
3981
  stagingDir,
3464
3982
  projectRoot: options.projectRoot,
3465
3983
  category
@@ -3482,7 +4000,7 @@ async function buildUiUpgradeStaging(options) {
3482
4000
  };
3483
4001
  await ensureDir(stagingDir);
3484
4002
  await writeFileSafe(
3485
- path22.join(stagingDir, "meta.json"),
4003
+ path23.join(stagingDir, "meta.json"),
3486
4004
  JSON.stringify(manifestOut, null, 2) + "\n"
3487
4005
  );
3488
4006
  return { stagingDir, manifest: manifestOut };
@@ -3516,19 +4034,19 @@ async function processRegistered(args) {
3516
4034
  const file = entry.files[0];
3517
4035
  if (!file) return null;
3518
4036
  const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
3519
- const sourceAbs = path22.resolve(rootForEntry, file.source);
4037
+ const sourceAbs = path23.resolve(rootForEntry, file.source);
3520
4038
  const raw = await readFileOrNull(sourceAbs);
3521
4039
  if (raw === null) {
3522
4040
  return null;
3523
4041
  }
3524
4042
  const incomingTransformed = rewriteImports(raw, args.aliases);
3525
4043
  const incomingHash = computeHash(incomingTransformed);
3526
- const currentExt = path22.extname(resource.target) || ".tsx";
3527
- const incomingExt = path22.extname(file.targetName) || currentExt;
4044
+ const currentExt = path23.extname(resource.target) || ".tsx";
4045
+ const incomingExt = path23.extname(file.targetName) || currentExt;
3528
4046
  const currentRel = `${id}/current${currentExt}`;
3529
4047
  const incomingRel = `${id}/incoming${incomingExt}`;
3530
- await writeFileSafe(path22.join(stagingDir, currentRel), currentSource);
3531
- await writeFileSafe(path22.join(stagingDir, incomingRel), incomingTransformed);
4048
+ await writeFileSafe(path23.join(stagingDir, currentRel), currentSource);
4049
+ await writeFileSafe(path23.join(stagingDir, incomingRel), incomingTransformed);
3532
4050
  const diff = classifyRisk({
3533
4051
  currentHash: resource.hash,
3534
4052
  incomingHash,
@@ -3536,11 +4054,16 @@ async function processRegistered(args) {
3536
4054
  incomingSource: incomingTransformed,
3537
4055
  multiFile: entry.files.length > 1
3538
4056
  });
4057
+ const promotion = derivePromotion({
4058
+ currentSource,
4059
+ incomingSource: incomingTransformed,
4060
+ targetName: entry.files[0]?.targetName ?? `${id}.tsx`
4061
+ });
3539
4062
  return {
3540
4063
  id,
3541
4064
  category,
3542
4065
  current: {
3543
- target: path22.relative(projectRoot, resource.target),
4066
+ target: path23.relative(projectRoot, resource.target),
3544
4067
  hash: resource.hash,
3545
4068
  sourceLineage: "teamix-evo"
3546
4069
  },
@@ -3549,25 +4072,26 @@ async function processRegistered(args) {
3549
4072
  hash: incomingHash,
3550
4073
  relPath: incomingRel
3551
4074
  },
3552
- diff
4075
+ diff,
4076
+ promotion
3553
4077
  };
3554
4078
  }
3555
4079
  async function processForeign(args) {
3556
4080
  const { id, installDirAbs, stagingDir, projectRoot, category } = args;
3557
- const tsx = path22.join(installDirAbs, `${id}.tsx`);
3558
- const ts = path22.join(installDirAbs, `${id}.ts`);
4081
+ const tsx = path23.join(installDirAbs, `${id}.tsx`);
4082
+ const ts = path23.join(installDirAbs, `${id}.ts`);
3559
4083
  const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
3560
4084
  if (!target) return null;
3561
4085
  const raw = await readFileOrNull(target);
3562
4086
  if (raw === null) return null;
3563
- const ext = path22.extname(target);
4087
+ const ext = path23.extname(target);
3564
4088
  const currentRel = `${id}/current${ext}`;
3565
- await writeFileSafe(path22.join(stagingDir, currentRel), raw);
4089
+ await writeFileSafe(path23.join(stagingDir, currentRel), raw);
3566
4090
  return {
3567
4091
  id,
3568
4092
  category,
3569
4093
  current: {
3570
- target: path22.relative(projectRoot, target),
4094
+ target: path23.relative(projectRoot, target),
3571
4095
  hash: computeHash(raw),
3572
4096
  sourceLineage: "custom"
3573
4097
  },
@@ -3582,17 +4106,17 @@ async function processForeign(args) {
3582
4106
  };
3583
4107
  }
3584
4108
  async function buildBreakingEntry(args) {
3585
- const ext = path22.extname(args.resource.target) || ".tsx";
4109
+ const ext = path23.extname(args.resource.target) || ".tsx";
3586
4110
  const currentRel = `${args.id}/current${ext}`;
3587
4111
  await writeFileSafe(
3588
- path22.join(args.stagingDir, currentRel),
4112
+ path23.join(args.stagingDir, currentRel),
3589
4113
  args.currentSource
3590
4114
  );
3591
4115
  return {
3592
4116
  id: args.id,
3593
4117
  category: args.category,
3594
4118
  current: {
3595
- target: path22.relative(args.projectRoot, args.resource.target),
4119
+ target: path23.relative(args.projectRoot, args.resource.target),
3596
4120
  hash: args.resource.hash,
3597
4121
  sourceLineage: "teamix-evo"
3598
4122
  },
@@ -3712,12 +4236,191 @@ function aggregateByRisk(entries) {
3712
4236
  }
3713
4237
  return out;
3714
4238
  }
4239
+ function derivePromotion(args) {
4240
+ const fileType = classifyPromoteFileType(args.targetName, args.currentSource);
4241
+ const featureVector = buildFeatureVector(
4242
+ args.currentSource,
4243
+ args.incomingSource
4244
+ );
4245
+ const { recommendedModes, confidence, reasons } = scorePromotionModes(
4246
+ fileType,
4247
+ featureVector
4248
+ );
4249
+ return { fileType, featureVector, recommendedModes, confidence, reasons };
4250
+ }
4251
+ function classifyPromoteFileType(targetName, src) {
4252
+ if (targetName.endsWith(".d.ts")) return "type";
4253
+ if (/^use-[a-z0-9-]+\.tsx?$/i.test(targetName)) return "hook";
4254
+ const hasJsx = /<[A-Za-z][^>]*?>/.test(src);
4255
+ const hasReactImport = /from ['"]react['"]/.test(src);
4256
+ const hasProvider = /\.Provider\b/.test(src) || /createContext\s*[<(]/.test(src);
4257
+ if (hasProvider && (hasJsx || hasReactImport)) return "provider";
4258
+ if (hasJsx || /forwardRef\s*[<(]/.test(src)) return "component";
4259
+ if (!hasJsx && !hasReactImport) return "util";
4260
+ return "component";
4261
+ }
4262
+ function buildFeatureVector(current, incoming) {
4263
+ const curExports = extractExportNames(current);
4264
+ const newExports = extractExportNames(incoming);
4265
+ const apiAdded = setDiff(curExports, newExports);
4266
+ const apiRemoved = setDiff(newExports, curExports);
4267
+ const curVariants = extractCvaVariantValues(current);
4268
+ const newVariants = extractCvaVariantValues(incoming);
4269
+ const cvaAdded = setDiff(curVariants, newVariants);
4270
+ const cvaModified = [];
4271
+ const sharedVariants = curVariants.filter((v) => newVariants.includes(v));
4272
+ for (const v of sharedVariants) {
4273
+ if (extractVariantBody(current, v) !== extractVariantBody(incoming, v)) {
4274
+ cvaModified.push(v);
4275
+ }
4276
+ }
4277
+ const curClass = extractClassNameLiterals(current);
4278
+ const newClass = extractClassNameLiterals(incoming);
4279
+ const classNameDiff = curClass !== newClass;
4280
+ const curTokens = extractTokenRefs(current);
4281
+ const newTokens = extractTokenRefs(incoming);
4282
+ const tokenUsageDiff = curTokens.size !== newTokens.size || [...curTokens].some((t) => !newTokens.has(t));
4283
+ const hasState = /\buseState\s*[<(]/.test(current);
4284
+ const hasEffect = /\b(useEffect|useLayoutEffect|useMemo|useCallback)\s*[<(]/.test(current);
4285
+ const curImports = extractImportSources(current);
4286
+ const newImports = extractImportSources(incoming);
4287
+ const hasExtraImports = [...curImports].some((src) => !newImports.has(src));
4288
+ const tagSet = /* @__PURE__ */ new Set();
4289
+ for (const m of current.matchAll(/<([A-Z]\w+)[\s/>]/g)) {
4290
+ if (m[1]) tagSet.add(m[1]);
4291
+ }
4292
+ const atomicChildren = [...tagSet];
4293
+ const isComposition = atomicChildren.length > 2;
4294
+ const signatureChanged = extractDefaultParamList(current) !== extractDefaultParamList(incoming);
4295
+ return {
4296
+ apiDelta: { added: apiAdded, removed: apiRemoved, signatureChanged },
4297
+ styleDelta: { classNameDiff, tokenUsageDiff },
4298
+ logicDelta: { hasState, hasEffect, hasExtraImports },
4299
+ cvaDelta: { addedVariants: cvaAdded, modifiedVariants: cvaModified },
4300
+ structureDelta: { isComposition, atomicChildren }
4301
+ };
4302
+ }
4303
+ function scorePromotionModes(fileType, fv) {
4304
+ const reasons = [];
4305
+ if (fileType === "hook" || fileType === "util" || fileType === "type") {
4306
+ reasons.push(
4307
+ `fileType=${fileType} \u2014 not a component, deferred to ManualReview`
4308
+ );
4309
+ return { recommendedModes: ["ManualReview"], confidence: 0.5, reasons };
4310
+ }
4311
+ const apiNoChange = fv.apiDelta.added.length === 0 && fv.apiDelta.removed.length === 0 && !fv.apiDelta.signatureChanged;
4312
+ const logicMinimal = !fv.logicDelta.hasState && !fv.logicDelta.hasEffect && !fv.logicDelta.hasExtraImports;
4313
+ if (fv.apiDelta.removed.length > 0 || fv.apiDelta.signatureChanged) {
4314
+ reasons.push(
4315
+ "signature changed or props removed \u2014 Coexist preserves user version"
4316
+ );
4317
+ return { recommendedModes: ["Coexist"], confidence: 0.85, reasons };
4318
+ }
4319
+ if (apiNoChange && logicMinimal && (fv.styleDelta.classNameDiff || fv.styleDelta.tokenUsageDiff) && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
4320
+ reasons.push(
4321
+ "only style / token differences \u2014 push to tokens.overrides.css"
4322
+ );
4323
+ return { recommendedModes: ["TokenOnly"], confidence: 0.8, reasons };
4324
+ }
4325
+ const modes = [];
4326
+ let score = 0;
4327
+ if (fv.cvaDelta.addedVariants.length > 0 || fv.cvaDelta.modifiedVariants.length > 0) {
4328
+ modes.push("Variant");
4329
+ reasons.push(
4330
+ `cva variants delta: +${fv.cvaDelta.addedVariants.length} ~${fv.cvaDelta.modifiedVariants.length}`
4331
+ );
4332
+ score = Math.max(score, 0.7);
4333
+ }
4334
+ if (fv.logicDelta.hasState || fv.logicDelta.hasEffect || fv.logicDelta.hasExtraImports || fv.apiDelta.added.length > 0) {
4335
+ modes.push("Wrapper");
4336
+ reasons.push("user added state / effect / imports / props");
4337
+ score = Math.max(score, 0.75);
4338
+ }
4339
+ if (apiNoChange && logicMinimal && !fv.styleDelta.classNameDiff && !fv.styleDelta.tokenUsageDiff && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
4340
+ modes.push("Preset");
4341
+ reasons.push("no API/logic delta \u2014 Preset captures default-prop tweaks");
4342
+ score = Math.max(score, 0.6);
4343
+ }
4344
+ if (fv.structureDelta.isComposition) {
4345
+ modes.push("Compose");
4346
+ reasons.push(
4347
+ `composition of ${fv.structureDelta.atomicChildren.length} atomic children`
4348
+ );
4349
+ score = Math.max(score, 0.65);
4350
+ }
4351
+ if (modes.length === 0) {
4352
+ reasons.push("no axis crossed the 0.6 threshold");
4353
+ return { recommendedModes: ["ManualReview"], confidence: 0.4, reasons };
4354
+ }
4355
+ return { recommendedModes: modes, confidence: score, reasons };
4356
+ }
4357
+ function extractVariantBody(src, key) {
4358
+ const block = extractVariantsBlock(src);
4359
+ if (block === null) return "";
4360
+ const re = new RegExp(`(?:["']?${key}["']?)\\s*:\\s*\\{`);
4361
+ const idx = block.search(re);
4362
+ if (idx < 0) return "";
4363
+ const open = block.indexOf("{", idx);
4364
+ if (open < 0) return "";
4365
+ let depth = 0;
4366
+ for (let i = open; i < block.length; i++) {
4367
+ const c = block[i];
4368
+ if (c === "{") depth++;
4369
+ else if (c === "}") {
4370
+ depth--;
4371
+ if (depth === 0) return block.slice(open + 1, i);
4372
+ }
4373
+ }
4374
+ return "";
4375
+ }
4376
+ function extractClassNameLiterals(src) {
4377
+ const out = [];
4378
+ for (const m of src.matchAll(/className\s*=\s*["'`]([^"'`]*)["'`]/g)) {
4379
+ if (m[1]) out.push(m[1]);
4380
+ }
4381
+ for (const m of src.matchAll(/\b(?:cn|clsx|cva)\s*\(/g)) {
4382
+ const open = (m.index ?? 0) + m[0].length - 1;
4383
+ let depth = 1;
4384
+ let i = open + 1;
4385
+ for (; i < src.length && depth > 0; i++) {
4386
+ const c = src[i];
4387
+ if (c === "(") depth++;
4388
+ else if (c === ")") depth--;
4389
+ }
4390
+ const body = src.slice(open + 1, i - 1);
4391
+ for (const lit of body.matchAll(/["'`]([^"'`]*)["'`]/g)) {
4392
+ if (lit[1]) out.push(lit[1]);
4393
+ }
4394
+ }
4395
+ return out.sort().join("|");
4396
+ }
4397
+ function extractTokenRefs(src) {
4398
+ const out = /* @__PURE__ */ new Set();
4399
+ for (const m of src.matchAll(/var\(--([a-z0-9-]+)\)/g)) {
4400
+ if (m[1]) out.add(m[1]);
4401
+ }
4402
+ for (const m of src.matchAll(/--([a-z][a-z0-9-]*)\s*:/g)) {
4403
+ if (m[1]) out.add(m[1]);
4404
+ }
4405
+ return out;
4406
+ }
4407
+ function extractImportSources(src) {
4408
+ const out = /* @__PURE__ */ new Set();
4409
+ for (const m of src.matchAll(/^\s*import\b[^'"]*['"]([^'"]+)['"]/gm)) {
4410
+ if (m[1]) out.add(m[1]);
4411
+ }
4412
+ return out;
4413
+ }
4414
+ function extractDefaultParamList(src) {
4415
+ const m = /export\s+default\s+(?:async\s+)?function\s+\w*\s*\(([^)]*)\)/.exec(src) ?? /export\s+default\s+(?:\([^)]*\)|\w+)\s*=>/.exec(src) ?? /(?:const|function)\s+\w+\s*=?\s*(?:\(([^)]*)\)|\w+)\s*(?:=>|\{)/.exec(src);
4416
+ return m?.[1]?.replace(/\s+/g, " ").trim() ?? "";
4417
+ }
3715
4418
 
3716
4419
  // src/core/ui-upgrade.ts
3717
4420
  var nodeRequire = createRequire5(import.meta.url);
3718
4421
  function resolvePackageRoot4(packageName) {
3719
4422
  const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
3720
- return path23.dirname(pkgJsonPath);
4423
+ return path24.dirname(pkgJsonPath);
3721
4424
  }
3722
4425
  async function buildStaging(args) {
3723
4426
  const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
@@ -3737,7 +4440,7 @@ async function buildStaging(args) {
3737
4440
  }
3738
4441
  const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
3739
4442
  const variant = lineageReport.installedVariant ?? "_flat";
3740
- const variantDir = path23.join(bizRoot, "variants", variant);
4443
+ const variantDir = path24.join(bizRoot, "variants", variant);
3741
4444
  const variantManifest = await loadVariantUiPackageManifest2(variantDir);
3742
4445
  const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
3743
4446
  const uiManifest = await loadUiPackageManifest3(uiRoot);
@@ -4019,7 +4722,7 @@ async function runComponentSourceStep(category, args) {
4019
4722
  });
4020
4723
  return;
4021
4724
  }
4022
- const stagingRel = path24.relative(projectRoot, built.stagingDir);
4725
+ const stagingRel = path25.relative(projectRoot, built.stagingDir);
4023
4726
  const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
4024
4727
  record({
4025
4728
  name: category,
@@ -4062,8 +4765,8 @@ async function planTokensUpdate(tokensPackage, config) {
4062
4765
  }
4063
4766
 
4064
4767
  // src/core/installer.ts
4065
- import * as path25 from "path";
4066
- import * as fs17 from "fs/promises";
4768
+ import * as path26 from "path";
4769
+ import * as fs18 from "fs/promises";
4067
4770
  async function installResources(options) {
4068
4771
  const { projectRoot, manifest, data, variantDir, packageRoot } = options;
4069
4772
  const installedResources = [];
@@ -4100,13 +4803,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
4100
4803
  variantDir,
4101
4804
  packageRoot
4102
4805
  );
4103
- const targetPath = path25.join(projectRoot, resource.target);
4806
+ const targetPath = path26.join(projectRoot, resource.target);
4104
4807
  let content;
4105
4808
  if (resource.template) {
4106
4809
  const templateContent = await loadTemplateFile(sourcePath);
4107
4810
  content = renderTemplate(templateContent, data);
4108
4811
  } else {
4109
- content = await fs17.readFile(sourcePath, "utf-8");
4812
+ content = await fs18.readFile(sourcePath, "utf-8");
4110
4813
  }
4111
4814
  await writeFileSafe(targetPath, content);
4112
4815
  const hash = computeHash(content);
@@ -4124,13 +4827,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
4124
4827
  variantDir,
4125
4828
  packageRoot
4126
4829
  );
4127
- const targetDir = path25.join(projectRoot, resource.target);
4830
+ const targetDir = path26.join(projectRoot, resource.target);
4128
4831
  const results = [];
4129
4832
  await ensureDir(targetDir);
4130
4833
  const entries = await walkDir(sourcePath);
4131
4834
  for (const entry of entries) {
4132
- const relPath = path25.relative(sourcePath, entry);
4133
- let targetFile = path25.join(targetDir, relPath);
4835
+ const relPath = path26.relative(sourcePath, entry);
4836
+ let targetFile = path26.join(targetDir, relPath);
4134
4837
  if (resource.template && targetFile.endsWith(".hbs")) {
4135
4838
  targetFile = targetFile.slice(0, -4);
4136
4839
  }
@@ -4139,11 +4842,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
4139
4842
  const templateContent = await loadTemplateFile(entry);
4140
4843
  content = renderTemplate(templateContent, data);
4141
4844
  } else {
4142
- content = await fs17.readFile(entry, "utf-8");
4845
+ content = await fs18.readFile(entry, "utf-8");
4143
4846
  }
4144
4847
  await writeFileSafe(targetFile, content);
4145
4848
  const hash = computeHash(content);
4146
- const targetRel = path25.relative(projectRoot, targetFile);
4849
+ const targetRel = path26.relative(projectRoot, targetFile);
4147
4850
  results.push({
4148
4851
  id: `${resource.id}:${relPath}`,
4149
4852
  target: targetRel,
@@ -4156,25 +4859,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
4156
4859
  }
4157
4860
 
4158
4861
  // src/core/registry-client.ts
4159
- import * as path26 from "path";
4160
- import * as fs18 from "fs/promises";
4862
+ import * as path27 from "path";
4863
+ import * as fs19 from "fs/promises";
4161
4864
  import { createRequire as createRequire6 } from "module";
4162
4865
  import { loadVariantManifest } from "@teamix-evo/registry";
4163
4866
  var require6 = createRequire6(import.meta.url);
4164
4867
  function resolvePackageRoot5(packageName) {
4165
4868
  const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
4166
- return path26.dirname(pkgJsonPath);
4869
+ return path27.dirname(pkgJsonPath);
4167
4870
  }
4168
4871
  async function loadVariantData(packageName, variant) {
4169
4872
  const packageRoot = resolvePackageRoot5(packageName);
4170
- const variantDir = path26.join(packageRoot, "library", variant);
4873
+ const variantDir = path27.join(packageRoot, "library", variant);
4171
4874
  logger.debug(`Resolved variant dir: ${variantDir}`);
4172
4875
  logger.debug(`Package root: ${packageRoot}`);
4173
4876
  const manifest = await loadVariantManifest(variantDir);
4174
4877
  let data = {};
4175
- const dataPath = path26.join(variantDir, "_data.json");
4878
+ const dataPath = path27.join(variantDir, "_data.json");
4176
4879
  try {
4177
- const raw = await fs18.readFile(dataPath, "utf-8");
4880
+ const raw = await fs19.readFile(dataPath, "utf-8");
4178
4881
  data = JSON.parse(raw);
4179
4882
  } catch (err) {
4180
4883
  if (err.code !== "ENOENT") {