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.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command37 } from "commander";
4
+ import { Command as Command39 } from "commander";
5
5
  import { createRequire as createRequire8 } from "module";
6
6
 
7
7
  // src/commands/tokens/index.ts
8
- import { Command as Command6 } from "commander";
8
+ import { Command as Command7 } from "commander";
9
9
 
10
10
  // src/commands/tokens/init.ts
11
11
  import { Command } from "commander";
@@ -181,7 +181,8 @@ var TEAMIX_DIR = ".teamix-evo";
181
181
  var CONFIG_FILE = "config.json";
182
182
  var MANIFEST_FILE = "manifest.json";
183
183
  var TOKENS_LOCK_FILE = "tokens-lock.json";
184
- var SKILLS_DIR = "skills";
184
+ var SKILLS_DIR = "skills-source";
185
+ var LEGACY_SKILLS_DIR = "skills";
185
186
  var SKILLS_LOCK_FILE = "manifest.lock.json";
186
187
  function getTeamixDir(projectRoot) {
187
188
  return path4.join(projectRoot, TEAMIX_DIR);
@@ -265,6 +266,9 @@ function getSkillsSourceDir(projectRoot, skillName) {
265
266
  const base = path4.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
266
267
  return skillName ? path4.join(base, skillName) : base;
267
268
  }
269
+ function getLegacySkillsSourceDir(projectRoot) {
270
+ return path4.join(projectRoot, TEAMIX_DIR, LEGACY_SKILLS_DIR);
271
+ }
268
272
  async function readSkillsLock(projectRoot) {
269
273
  const lockPath = path4.join(
270
274
  projectRoot,
@@ -397,6 +401,7 @@ function resolveTokensPackageRoot(packageName) {
397
401
 
398
402
  // src/core/skills-installer.ts
399
403
  async function installSkills(options) {
404
+ await migrateLegacySkillsSourceDir(options.projectRoot);
400
405
  const { manifest, ides, scope, onlyIds } = options;
401
406
  const installed = [];
402
407
  const targets = manifest.skills.filter(
@@ -689,6 +694,7 @@ function escapeRegExp(str) {
689
694
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
690
695
  }
691
696
  async function syncSkillsToIdes(options) {
697
+ await migrateLegacySkillsSourceDir(options.projectRoot);
692
698
  const { projectRoot, skills, ides, scope, onlyIds } = options;
693
699
  const out = [];
694
700
  const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
@@ -732,6 +738,118 @@ async function syncSkillsToIdes(options) {
732
738
  }
733
739
  return { resources: out, count: out.length };
734
740
  }
741
+ async function migrateLegacySkillsSourceDir(projectRoot) {
742
+ const legacyDir = getLegacySkillsSourceDir(projectRoot);
743
+ const newDir = getSkillsSourceDir(projectRoot);
744
+ let legacyExists = false;
745
+ let newExists = false;
746
+ try {
747
+ legacyExists = (await fs5.stat(legacyDir)).isDirectory();
748
+ } catch {
749
+ legacyExists = false;
750
+ }
751
+ try {
752
+ newExists = (await fs5.stat(newDir)).isDirectory();
753
+ } catch {
754
+ newExists = false;
755
+ }
756
+ if (!legacyExists) return;
757
+ if (newExists) {
758
+ logger.warn(
759
+ `Detected stale legacy skills source dir at ${legacyDir} alongside ${newDir}; the new layout takes precedence \u2014 you can safely delete the legacy dir.`
760
+ );
761
+ return;
762
+ }
763
+ try {
764
+ await fs5.rename(legacyDir, newDir);
765
+ logger.info(
766
+ `Migrated skills source dir: \`.teamix-evo/${LEGACY_SKILLS_DIR}/\` \u2192 \`.teamix-evo/skills-source/\``
767
+ );
768
+ } catch (err) {
769
+ logger.warn(
770
+ `Failed to rename legacy skills source dir (${getErrorMessage(
771
+ err
772
+ )}); leaving as-is. New skills will install under the new layout.`
773
+ );
774
+ return;
775
+ }
776
+ try {
777
+ const manifest = await readInstalledManifest(projectRoot);
778
+ if (!manifest) return;
779
+ const legacyFragmentPosix = `/.teamix-evo/${LEGACY_SKILLS_DIR}/`;
780
+ const newFragmentPosix = `/.teamix-evo/skills-source/`;
781
+ const legacyFragmentNative = `${path7.sep}.teamix-evo${path7.sep}${LEGACY_SKILLS_DIR}${path7.sep}`;
782
+ const newFragmentNative = `${path7.sep}.teamix-evo${path7.sep}skills-source${path7.sep}`;
783
+ let touched = 0;
784
+ for (const pkg of manifest.installed) {
785
+ for (const r of pkg.resources) {
786
+ if (typeof r.target !== "string") continue;
787
+ const before = r.target;
788
+ let after = before.replace(legacyFragmentPosix, newFragmentPosix);
789
+ after = after.replace(legacyFragmentNative, newFragmentNative);
790
+ if (after !== before) {
791
+ r.target = after;
792
+ touched += 1;
793
+ }
794
+ }
795
+ }
796
+ if (touched > 0) {
797
+ await writeInstalledManifest(projectRoot, manifest);
798
+ logger.debug(
799
+ `Rewrote ${touched} manifest target(s) to the new skills-source path.`
800
+ );
801
+ }
802
+ } catch (err) {
803
+ logger.warn(
804
+ `Migrated skills source dir but failed to update manifest paths (${getErrorMessage(
805
+ err
806
+ )}); manifest may still reference legacy paths.`
807
+ );
808
+ }
809
+ }
810
+ async function pruneEmptyIdeSkillDirs(args) {
811
+ const removed = [];
812
+ for (const ide of args.ides) {
813
+ const adapter = getAdapter(ide);
814
+ const placeholderDir = adapter.getSkillTargetDir(
815
+ "__placeholder__",
816
+ args.scope,
817
+ args.projectRoot
818
+ );
819
+ const skillsRoot = path7.dirname(placeholderDir);
820
+ let entries;
821
+ try {
822
+ entries = await fs5.readdir(skillsRoot);
823
+ } catch {
824
+ continue;
825
+ }
826
+ for (const name of entries) {
827
+ const dir = path7.join(skillsRoot, name);
828
+ let stat7;
829
+ try {
830
+ stat7 = await fs5.stat(dir);
831
+ } catch {
832
+ continue;
833
+ }
834
+ if (!stat7.isDirectory()) continue;
835
+ let children;
836
+ try {
837
+ children = await fs5.readdir(dir);
838
+ } catch {
839
+ continue;
840
+ }
841
+ if (children.some((c) => c === "SKILL.md")) continue;
842
+ if (children.length !== 0) continue;
843
+ try {
844
+ await fs5.rmdir(dir);
845
+ removed.push(dir);
846
+ logger.debug(`Pruned empty IDE skill dir: ${dir}`);
847
+ } catch {
848
+ }
849
+ }
850
+ }
851
+ return removed;
852
+ }
735
853
  async function removeSkillFiles(records) {
736
854
  const removed = [];
737
855
  for (const r of records) {
@@ -827,13 +945,29 @@ async function runSkillsInit(options) {
827
945
  }
828
946
  return true;
829
947
  }).map((s) => s.id);
830
- const skippedSkillIds = candidateIds.filter(
831
- (id) => existing.skillIds.has(id)
948
+ const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
949
+ candidateIds,
950
+ manifest,
951
+ existing
832
952
  );
833
- const onlyIds = candidateIds.filter((id) => !existing.skillIds.has(id));
834
- if (existingSkillsCfg && onlyIds.length === 0) {
953
+ if (existingSkillsCfg && onlyIds.length === 0 && outdatedSkills.length === 0) {
835
954
  return { status: "already-initialized" };
836
955
  }
956
+ if (onlyIds.length === 0) {
957
+ return {
958
+ status: "installed",
959
+ packageName,
960
+ version: existingSkillsCfg?.version ?? manifest.version,
961
+ ides,
962
+ scope,
963
+ skillCount: 0,
964
+ fileCount: 0,
965
+ resources: [],
966
+ addedSkillIds: [],
967
+ skippedSkillIds,
968
+ outdatedSkills
969
+ };
970
+ }
837
971
  return finalizeSkillsInstall({
838
972
  projectRoot,
839
973
  packageName,
@@ -845,6 +979,7 @@ async function runSkillsInit(options) {
845
979
  scope,
846
980
  onlyIds,
847
981
  skippedSkillIds,
982
+ outdatedSkills,
848
983
  existing,
849
984
  existingConfig
850
985
  });
@@ -885,10 +1020,11 @@ async function runSkillsAdd(options) {
885
1020
  }
886
1021
  }
887
1022
  const existing = await readExistingState(projectRoot, packageName);
888
- const skippedSkillIds = requestedNames.filter(
889
- (n) => existing.skillIds.has(n)
1023
+ const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
1024
+ requestedNames,
1025
+ manifest,
1026
+ existing
890
1027
  );
891
- const onlyIds = requestedNames.filter((n) => !existing.skillIds.has(n));
892
1028
  if (onlyIds.length === 0) {
893
1029
  return {
894
1030
  status: "installed",
@@ -900,7 +1036,8 @@ async function runSkillsAdd(options) {
900
1036
  fileCount: 0,
901
1037
  resources: [],
902
1038
  addedSkillIds: [],
903
- skippedSkillIds
1039
+ skippedSkillIds,
1040
+ outdatedSkills
904
1041
  };
905
1042
  }
906
1043
  return finalizeSkillsInstall({
@@ -914,10 +1051,52 @@ async function runSkillsAdd(options) {
914
1051
  scope,
915
1052
  onlyIds,
916
1053
  skippedSkillIds,
1054
+ outdatedSkills,
917
1055
  existing,
918
1056
  existingConfig
919
1057
  });
920
1058
  }
1059
+ function partitionByVersion(ids, manifest, existing) {
1060
+ const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
1061
+ const onlyIds = [];
1062
+ const skippedSkillIds = [];
1063
+ const outdatedSkills = [];
1064
+ for (const name of ids) {
1065
+ if (!existing.skillIds.has(name)) {
1066
+ onlyIds.push(name);
1067
+ continue;
1068
+ }
1069
+ const installedVer = existing.lock?.skills?.[name]?.version;
1070
+ const latestVer = manifestById.get(name)?.version;
1071
+ if (installedVer && latestVer && compareSemver(installedVer, latestVer) < 0) {
1072
+ outdatedSkills.push({
1073
+ id: name,
1074
+ installed: installedVer,
1075
+ latest: latestVer
1076
+ });
1077
+ } else {
1078
+ skippedSkillIds.push(name);
1079
+ }
1080
+ }
1081
+ return { onlyIds, skippedSkillIds, outdatedSkills };
1082
+ }
1083
+ function parseSemverTriple(v) {
1084
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
1085
+ if (!m) return null;
1086
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
1087
+ }
1088
+ function compareSemver(a, b) {
1089
+ const pa = parseSemverTriple(a);
1090
+ const pb = parseSemverTriple(b);
1091
+ if (!pa || !pb) {
1092
+ if (a === b) return 0;
1093
+ return a < b ? -1 : 1;
1094
+ }
1095
+ for (let i = 0; i < 3; i++) {
1096
+ if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
1097
+ }
1098
+ return 0;
1099
+ }
921
1100
  async function readExistingState(projectRoot, packageName) {
922
1101
  const installed = await readInstalledManifest(projectRoot);
923
1102
  const pkg = installed?.installed.find((p2) => p2.package === packageName);
@@ -942,6 +1121,7 @@ async function finalizeSkillsInstall(args) {
942
1121
  scope,
943
1122
  onlyIds,
944
1123
  skippedSkillIds,
1124
+ outdatedSkills,
945
1125
  existing,
946
1126
  existingConfig
947
1127
  } = args;
@@ -1007,6 +1187,10 @@ async function finalizeSkillsInstall(args) {
1007
1187
  }
1008
1188
  await writeSkillsLock(projectRoot, lock);
1009
1189
  await ensureMcpJson(projectRoot);
1190
+ try {
1191
+ await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
1192
+ } catch {
1193
+ }
1010
1194
  return {
1011
1195
  status: "installed",
1012
1196
  packageName,
@@ -1017,7 +1201,8 @@ async function finalizeSkillsInstall(args) {
1017
1201
  fileCount: result.count,
1018
1202
  resources: result.resources,
1019
1203
  addedSkillIds: onlyIds,
1020
- skippedSkillIds
1204
+ skippedSkillIds,
1205
+ outdatedSkills: outdatedSkills ?? []
1021
1206
  };
1022
1207
  }
1023
1208
  function mergeInstalledResources(existing, next) {
@@ -1225,6 +1410,9 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1225
1410
  const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
1226
1411
  const targetAbs = path9.join(projectRoot, targetRel);
1227
1412
  const content = await fs6.readFile(sourceAbs, "utf-8");
1413
+ if (await fileExists(targetAbs)) {
1414
+ await backupFile(targetAbs, projectRoot);
1415
+ }
1228
1416
  await writeFileSafe(targetAbs, content);
1229
1417
  return {
1230
1418
  id: `tokens:${CONSUMER_THEME_FILE}`,
@@ -1434,10 +1622,10 @@ async function writeTokensUpgradeHint(options) {
1434
1622
  }
1435
1623
  function selectApplicableRenames(renames, fromVersion, toVersion) {
1436
1624
  return renames.filter(
1437
- (r) => compareSemver(r.sinceVersion, fromVersion) > 0 && compareSemver(r.sinceVersion, toVersion) <= 0
1438
- ).sort((a, b) => compareSemver(a.sinceVersion, b.sinceVersion));
1625
+ (r) => compareSemver2(r.sinceVersion, fromVersion) > 0 && compareSemver2(r.sinceVersion, toVersion) <= 0
1626
+ ).sort((a, b) => compareSemver2(a.sinceVersion, b.sinceVersion));
1439
1627
  }
1440
- function compareSemver(a, b) {
1628
+ function compareSemver2(a, b) {
1441
1629
  const [aMain = "", aRest = ""] = a.split("-", 2);
1442
1630
  const [bMain = "", bRest = ""] = b.split("-", 2);
1443
1631
  const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
@@ -1876,19 +2064,243 @@ var uninstallCommand = new Command5("uninstall").description(
1876
2064
  }
1877
2065
  });
1878
2066
 
2067
+ // src/commands/tokens/audit.ts
2068
+ import { Command as Command6 } from "commander";
2069
+
2070
+ // src/core/tokens-audit.ts
2071
+ import * as fs10 from "fs/promises";
2072
+ import * as path14 from "path";
2073
+ var THEME_REL = "tokens/tokens.theme.css";
2074
+ var OVERRIDES_REL = "tokens/tokens.overrides.css";
2075
+ async function runTokensAudit(options) {
2076
+ const themePath = path14.join(options.projectRoot, THEME_REL);
2077
+ const overridesPath = path14.join(options.projectRoot, OVERRIDES_REL);
2078
+ const themeSrc = await safeRead(themePath);
2079
+ if (themeSrc === null) {
2080
+ return { status: "no-theme", themePath, overridesPath };
2081
+ }
2082
+ const overridesSrc = await safeRead(overridesPath);
2083
+ if (overridesSrc === null) {
2084
+ return { status: "no-overrides", themePath, overridesPath };
2085
+ }
2086
+ const themeTokens = parseCssVariables(themeSrc);
2087
+ const overrideTokens = parseCssVariables(overridesSrc);
2088
+ const themeIndex = /* @__PURE__ */ new Map();
2089
+ for (const tok of themeTokens) themeIndex.set(tok.name, tok);
2090
+ const entries = [];
2091
+ for (const tok of overrideTokens) {
2092
+ entries.push(classifyOverride(tok, themeIndex));
2093
+ }
2094
+ const summary = {
2095
+ redundant: 0,
2096
+ kept: 0,
2097
+ migrate: 0,
2098
+ custom: 0
2099
+ };
2100
+ for (const entry of entries) summary[entry.category] += 1;
2101
+ return {
2102
+ status: "audited",
2103
+ themePath,
2104
+ overridesPath,
2105
+ totalOverrides: entries.length,
2106
+ entries,
2107
+ summary
2108
+ };
2109
+ }
2110
+ async function safeRead(filePath) {
2111
+ try {
2112
+ return await fs10.readFile(filePath, "utf-8");
2113
+ } catch {
2114
+ return null;
2115
+ }
2116
+ }
2117
+ function parseCssVariables(source) {
2118
+ const stripped = source.replace(
2119
+ /\/\*[\s\S]*?\*\//g,
2120
+ (m) => m.replace(/[^\n]/g, " ")
2121
+ );
2122
+ const out = [];
2123
+ const lines = stripped.split("\n");
2124
+ for (let i = 0; i < lines.length; i += 1) {
2125
+ const lineRaw = lines[i] ?? "";
2126
+ const match = /^\s*(--[A-Za-z0-9_-]+)\s*:\s*([^;]+?)\s*;/.exec(lineRaw);
2127
+ if (!match) continue;
2128
+ const name = match[1];
2129
+ const value = match[2];
2130
+ if (!name || !value) continue;
2131
+ out.push({ name, value: value.trim(), line: i + 1 });
2132
+ }
2133
+ return out;
2134
+ }
2135
+ function classifyOverride(override, themeIndex) {
2136
+ const direct = themeIndex.get(override.name);
2137
+ if (direct) {
2138
+ const equal = areValuesSemanticallyEqual(override.value, direct.value);
2139
+ return {
2140
+ name: override.name,
2141
+ value: override.value,
2142
+ line: override.line,
2143
+ category: equal ? "redundant" : "kept",
2144
+ themeName: direct.name,
2145
+ themeValue: direct.value,
2146
+ reason: equal ? `theme already declares ${direct.name} with an equivalent value \u2014 safe to delete` : `genuinely differs from theme value \u2014 kept as user override`
2147
+ };
2148
+ }
2149
+ if (!override.name.startsWith("--color-")) {
2150
+ const v4Name = `--color-${override.name.slice(2)}`;
2151
+ const v4Match = themeIndex.get(v4Name);
2152
+ if (v4Match) {
2153
+ const wrapped = wrapAsHslIfBare(override.value);
2154
+ const equal = areValuesSemanticallyEqual(wrapped, v4Match.value);
2155
+ return {
2156
+ name: override.name,
2157
+ value: override.value,
2158
+ line: override.line,
2159
+ category: "migrate",
2160
+ themeName: v4Match.name,
2161
+ themeValue: v4Match.value,
2162
+ compareCategory: equal ? "redundant" : "kept",
2163
+ reason: equal ? `legacy v3 shape; theme has ${v4Match.name} with equivalent value \u2014 rewrite as v4 (or delete)` : `legacy v3 shape; rewrite as v4 \`${v4Name}: hsl(...)\` and keep override`
2164
+ };
2165
+ }
2166
+ }
2167
+ return {
2168
+ name: override.name,
2169
+ value: override.value,
2170
+ line: override.line,
2171
+ category: "custom",
2172
+ reason: `no matching theme variable \u2014 project-specific extension`
2173
+ };
2174
+ }
2175
+ function areValuesSemanticallyEqual(a, b) {
2176
+ return normaliseCssValue(a) === normaliseCssValue(b);
2177
+ }
2178
+ function normaliseCssValue(value) {
2179
+ let v = value.trim().toLowerCase();
2180
+ v = v.replace(/\s+/g, " ");
2181
+ v = v.replace(/\s*([(),/])\s*/g, "$1");
2182
+ v = v.replace(/(\d+\.\d*?)0+(?=[^0-9]|$)/g, "$1").replace(/\.(?=[^0-9]|$)/g, "");
2183
+ return v;
2184
+ }
2185
+ function wrapAsHslIfBare(value) {
2186
+ const trimmed = value.trim();
2187
+ if (/^[a-z-]+\s*\(/i.test(trimmed)) return trimmed;
2188
+ const triplet = /^-?\d+(?:\.\d+)?\s+\d+(?:\.\d+)?%\s+\d+(?:\.\d+)?%(?:\s*\/\s*\S+)?$/;
2189
+ if (triplet.test(trimmed)) {
2190
+ return `hsl(${trimmed})`;
2191
+ }
2192
+ return trimmed;
2193
+ }
2194
+ function formatAuditEntry(entry) {
2195
+ const head = ` ${entry.name}: ${entry.value};`;
2196
+ switch (entry.category) {
2197
+ case "redundant":
2198
+ return `${head}
2199
+ \u2192 REDUNDANT \u2014 ${entry.reason}`;
2200
+ case "kept":
2201
+ return `${head}
2202
+ \u2192 KEEP \u2014 ${entry.reason}`;
2203
+ case "migrate": {
2204
+ const sub = entry.compareCategory ? ` (currently ${entry.compareCategory})` : "";
2205
+ return `${head}
2206
+ \u2192 MIGRATE${sub} \u2014 ${entry.reason}`;
2207
+ }
2208
+ case "custom":
2209
+ return `${head}
2210
+ \u2192 CUSTOM \u2014 ${entry.reason}`;
2211
+ }
2212
+ }
2213
+ function renderAuditSummary(result) {
2214
+ const lines = [];
2215
+ lines.push(`tokens audit \xB7 ${result.totalOverrides} override(s) inspected`);
2216
+ lines.push(
2217
+ ` redundant=${result.summary.redundant} kept=${result.summary.kept} migrate=${result.summary.migrate} custom=${result.summary.custom}`
2218
+ );
2219
+ for (const cat of ["redundant", "migrate", "custom", "kept"]) {
2220
+ const subset = result.entries.filter((e) => e.category === cat);
2221
+ if (subset.length === 0) continue;
2222
+ lines.push("");
2223
+ lines.push(`[${cat.toUpperCase()}] (${subset.length})`);
2224
+ for (const entry of subset) lines.push(formatAuditEntry(entry));
2225
+ }
2226
+ return lines.join("\n");
2227
+ }
2228
+
2229
+ // src/commands/tokens/audit.ts
2230
+ var auditCommand = new Command6("audit").description(
2231
+ "\u5BA1\u8BA1 tokens/tokens.overrides.css\uFF1A\u4E0E\u53D8\u4F53 theme.css \u5BF9\u7167\uFF0C\u5206\u7C7B\u8F93\u51FA\u5197\u4F59/\u4FDD\u7559/\u8FC1\u79FB/\u9879\u76EE\u7279\u6709"
2232
+ ).option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u7ED3\u679C\uFF08\u4FBF\u4E8E skill \u6D88\u8D39\uFF09").option(
2233
+ "--filter <category>",
2234
+ "\u4EC5\u663E\u793A\u6307\u5B9A bucket\uFF08redundant|kept|migrate|custom\uFF09\uFF0C\u53EF\u9017\u53F7\u5206\u9694"
2235
+ ).action(async (opts) => {
2236
+ try {
2237
+ const ide = detectIde();
2238
+ const projectRoot = ide.getProjectRoot();
2239
+ const result = await runTokensAudit({ projectRoot });
2240
+ if (result.status === "no-theme") {
2241
+ logger.error(
2242
+ `No tokens.theme.css found at ${result.themePath}. Run \`tokens init <variant>\` first.`
2243
+ );
2244
+ process.exitCode = 1;
2245
+ return;
2246
+ }
2247
+ if (result.status === "no-overrides") {
2248
+ logger.info(
2249
+ `No tokens.overrides.css at ${result.overridesPath}. Nothing to audit.`
2250
+ );
2251
+ return;
2252
+ }
2253
+ if (result.status !== "audited") {
2254
+ return;
2255
+ }
2256
+ const filterSet = parseFilter(opts.filter);
2257
+ const filtered = filterSet ? {
2258
+ ...result,
2259
+ entries: result.entries.filter((e) => filterSet.has(e.category))
2260
+ } : result;
2261
+ if (opts.json) {
2262
+ process.stdout.write(JSON.stringify(filtered, null, 2) + "\n");
2263
+ return;
2264
+ }
2265
+ logger.info(renderAuditSummary(filtered));
2266
+ logger.info("");
2267
+ logger.info(
2268
+ "\u63D0\u793A\uFF1A\u6B64\u547D\u4EE4\u53EA\u8BFB\uFF0C\u4E0D\u4F1A\u4FEE\u6539 overrides.css\uFF1B\u6309\u4E0A\u9762\u7684 REDUNDANT / MIGRATE \u5206\u7C7B\u81EA\u884C\u88C1\u526A\u3002"
2269
+ );
2270
+ } catch (err) {
2271
+ logger.error(`Failed to audit tokens: ${getErrorMessage(err)}`);
2272
+ process.exitCode = 1;
2273
+ }
2274
+ });
2275
+ function parseFilter(raw) {
2276
+ if (!raw) return null;
2277
+ const valid = ["redundant", "kept", "migrate", "custom"];
2278
+ const out = /* @__PURE__ */ new Set();
2279
+ for (const part of raw.split(",").map((s) => s.trim().toLowerCase())) {
2280
+ if (!part) continue;
2281
+ if (valid.includes(part)) {
2282
+ out.add(part);
2283
+ }
2284
+ }
2285
+ return out.size > 0 ? out : null;
2286
+ }
2287
+
1879
2288
  // src/commands/tokens/index.ts
1880
- var tokensCommand = new Command6("tokens").description("\u7BA1\u7406 design tokens(\u53D8\u4F53\u7EA7 theme.css \u7B49)");
2289
+ var tokensCommand = new Command7("tokens").description(
2290
+ "\u7BA1\u7406 design tokens(\u53D8\u4F53\u7EA7 theme.css \u7B49)"
2291
+ );
1881
2292
  tokensCommand.addCommand(initCommand);
1882
2293
  tokensCommand.addCommand(updateCommand);
1883
2294
  tokensCommand.addCommand(listCommand);
1884
2295
  tokensCommand.addCommand(listVariantsCommand);
1885
2296
  tokensCommand.addCommand(uninstallCommand);
2297
+ tokensCommand.addCommand(auditCommand);
1886
2298
 
1887
2299
  // src/commands/skills/index.ts
1888
- import { Command as Command14 } from "commander";
2300
+ import { Command as Command15 } from "commander";
1889
2301
 
1890
2302
  // src/commands/skills/init.ts
1891
- import { Command as Command7 } from "commander";
2303
+ import { Command as Command8 } from "commander";
1892
2304
  import * as prompts2 from "@clack/prompts";
1893
2305
 
1894
2306
  // src/utils/cancelled.ts
@@ -1921,7 +2333,7 @@ function parseScope(input) {
1921
2333
  }
1922
2334
 
1923
2335
  // src/commands/skills/init.ts
1924
- var initCommand2 = new Command7("init").description(
2336
+ var initCommand2 = new Command8("init").description(
1925
2337
  "\u81EA\u4E3E teamix-evo skills\uFF08\u6309 tokens variant + scope \u5168\u88C5\u7B26\u5408\u6761\u4EF6\u7684 skill\uFF1Bscope \u4E3A global-only \u7684 entry skill \u81EA\u52A8\u8DF3\u8FC7 \u2014 ADR 0033\uFF09"
1926
2338
  ).option("--ide <list>", '\u9017\u53F7\u5206\u9694\u7684 IDE \u5217\u8868\uFF0C\u5982 "qoder,claude"').option("--scope <scope>", "project | global\uFF08\u9ED8\u8BA4 project\uFF09").option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").action(async (opts) => {
1927
2339
  try {
@@ -1969,6 +2381,13 @@ var initCommand2 = new Command7("init").description(
1969
2381
  );
1970
2382
  }
1971
2383
  logger.info(` Files: ${result.fileCount}`);
2384
+ if (result.outdatedSkills.length > 0) {
2385
+ const outdatedDesc = result.outdatedSkills.map((o) => `${o.id}@${o.installed} \u2192 ${o.latest}`).join(", ");
2386
+ const outdatedIds = result.outdatedSkills.map((o) => o.id).join(" ");
2387
+ logger.warn(
2388
+ `\u90E8\u5206\u6280\u80FD\u5DF2\u88C5\u4F46\u6709\u53EF\u7528\u66F4\u65B0\uFF1A${outdatedDesc}\u3002\u8FD0\u884C "npx teamix-evo@latest skills update ${outdatedIds}" \u5347\u7EA7\u3002`
2389
+ );
2390
+ }
1972
2391
  logger.info("");
1973
2392
  logger.info(
1974
2393
  'Run "npx teamix-evo@latest skills list" to see installed skills.'
@@ -2021,9 +2440,9 @@ async function resolveIdesAndScope(args) {
2021
2440
  }
2022
2441
 
2023
2442
  // src/commands/skills/add.ts
2024
- import { Command as Command8 } from "commander";
2443
+ import { Command as Command9 } from "commander";
2025
2444
  import * as prompts3 from "@clack/prompts";
2026
- var addCommand = new Command8("add").description(
2445
+ var addCommand = new Command9("add").description(
2027
2446
  "\u589E\u91CF\u6DFB\u52A0 teamix-evo skills\uFF08\u5FC5\u987B\u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A skill id\uFF1B\u81EA\u4E3E\u8BF7\u7528 `skills init`\uFF09"
2028
2447
  ).argument("<names...>", "\u81F3\u5C11\u4E00\u4E2A skill id\uFF08\u589E\u91CF\u88C5\uFF09").option("--ide <list>", '\u9017\u53F7\u5206\u9694\u7684 IDE \u5217\u8868\uFF0C\u5982 "qoder,claude"').option(
2029
2448
  "--scope <scope>",
@@ -2060,26 +2479,45 @@ var addCommand = new Command8("add").description(
2060
2479
  ide: ide.name,
2061
2480
  names
2062
2481
  });
2063
- if (result.addedSkillIds.length === 0 && result.skippedSkillIds.length > 0) {
2064
- logger.warn(
2065
- `\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u6DFB\u52A0\uFF1A${result.skippedSkillIds.join(
2066
- ", "
2067
- )}\u3002\u5982\u9700\u5237\u65B0\u5185\u5BB9\u8BF7\u8FD0\u884C "npx teamix-evo@latest skills update"\u3002`
2068
- );
2482
+ const hasAdded = result.addedSkillIds.length > 0;
2483
+ const hasSkipped = result.skippedSkillIds.length > 0;
2484
+ const hasOutdated = result.outdatedSkills.length > 0;
2485
+ const outdatedDesc = result.outdatedSkills.map((o) => `${o.id}@${o.installed} \u2192 ${o.latest}`).join(", ");
2486
+ const outdatedIds = result.outdatedSkills.map((o) => o.id).join(" ");
2487
+ if (!hasAdded) {
2488
+ if (hasOutdated) {
2489
+ logger.warn(
2490
+ `\u5DF2\u5B89\u88C5\u4F46\u6709\u53EF\u7528\u66F4\u65B0\uFF1A${outdatedDesc}\u3002\u8FD0\u884C "npx teamix-evo@latest skills update ${outdatedIds}" \u5347\u7EA7\u3002`
2491
+ );
2492
+ }
2493
+ if (hasSkipped) {
2494
+ if (hasOutdated) {
2495
+ logger.info(`\u5176\u4F59\u5DF2\u662F\u6700\u65B0\uFF1A${result.skippedSkillIds.join(", ")}`);
2496
+ } else {
2497
+ logger.warn(
2498
+ `\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u6DFB\u52A0\uFF1A${result.skippedSkillIds.join(
2499
+ ", "
2500
+ )}\u3002\u5982\u9700\u5237\u65B0\u5185\u5BB9\u8BF7\u8FD0\u884C "npx teamix-evo@latest skills update"\u3002`
2501
+ );
2502
+ }
2503
+ }
2069
2504
  return;
2070
2505
  }
2071
2506
  logger.success(`Skills added: ${result.skillCount} skill(s)`);
2072
2507
  logger.info(` IDEs: ${result.ides.join(", ")}`);
2073
2508
  logger.info(` Scope: ${result.scope}`);
2074
- if (result.addedSkillIds.length > 0) {
2075
- logger.info(` Added: ${result.addedSkillIds.join(", ")}`);
2076
- }
2077
- if (result.skippedSkillIds.length > 0) {
2509
+ logger.info(` Added: ${result.addedSkillIds.join(", ")}`);
2510
+ if (hasSkipped) {
2078
2511
  logger.info(
2079
2512
  ` Skipped: ${result.skippedSkillIds.join(", ")} (already added)`
2080
2513
  );
2081
2514
  }
2082
2515
  logger.info(` Files: ${result.fileCount}`);
2516
+ if (hasOutdated) {
2517
+ logger.warn(
2518
+ `\u90E8\u5206\u6280\u80FD\u5DF2\u88C5\u4F46\u6709\u53EF\u7528\u66F4\u65B0\uFF1A${outdatedDesc}\u3002\u8FD0\u884C "npx teamix-evo@latest skills update ${outdatedIds}" \u5347\u7EA7\u3002`
2519
+ );
2520
+ }
2083
2521
  logger.info("");
2084
2522
  logger.info(
2085
2523
  'Run "npx teamix-evo@latest skills list" to see installed skills.'
@@ -2144,9 +2582,9 @@ async function resolveIdesAndScope2(args) {
2144
2582
  }
2145
2583
 
2146
2584
  // src/commands/skills/list.ts
2147
- import { Command as Command9 } from "commander";
2585
+ import { Command as Command10 } from "commander";
2148
2586
  var SKILLS_PACKAGE = "@teamix-evo/skills";
2149
- var listCommand2 = new Command9("list").alias("ls").description(
2587
+ var listCommand2 = new Command10("list").alias("ls").description(
2150
2588
  "\u5217\u51FA teamix-evo skills\uFF08\u9ED8\u8BA4\u5C55\u793A\u5168\u90E8 skill \u5E76\u6807\u6CE8\u5DF2\u88C5/\u672A\u88C5\uFF1B--installed \u4EC5\u770B\u5DF2\u88C5\uFF09"
2151
2589
  ).option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 skill\uFF08\u9690\u85CF\u672A\u5B89\u88C5\u9879\uFF09").action(async (opts) => {
2152
2590
  try {
@@ -2242,7 +2680,7 @@ function printInstalledHeader(cfg, installedAt) {
2242
2680
  }
2243
2681
 
2244
2682
  // src/commands/skills/update.ts
2245
- import { Command as Command10 } from "commander";
2683
+ import { Command as Command11 } from "commander";
2246
2684
  import { createRequire as createRequire3 } from "module";
2247
2685
 
2248
2686
  // src/core/skills-update.ts
@@ -2418,7 +2856,7 @@ async function runSkillsUpdate(options) {
2418
2856
  // src/commands/skills/update.ts
2419
2857
  var require4 = createRequire3(import.meta.url);
2420
2858
  var SKILLS_PACKAGE2 = "@teamix-evo/skills";
2421
- var updateCommand2 = new Command10("update").description(
2859
+ var updateCommand2 = new Command11("update").description(
2422
2860
  "\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 teamix-evo skills\uFF08\u4EC5\u5347\u7EA7 lock \u5DF2\u8BB0\u5F55\u4E14 scope \u5339\u914D\u7684 skill \u2014 ADR 0035\uFF09"
2423
2861
  ).argument("[names...]", "\u53EF\u9009\uFF1A\u4EC5\u5347\u7EA7\u6307\u5B9A skill id\uFF1B\u7701\u7565\u5219\u5347\u7EA7\u5168\u90E8\u5DF2\u88C5").option("--dry-run", "\u9884\u89C8\u53D8\u66F4\uFF0C\u4E0D\u5199\u76D8").option(
2424
2862
  "--scope <scope>",
@@ -2541,12 +2979,12 @@ async function printVersionBanner() {
2541
2979
  }
2542
2980
 
2543
2981
  // src/commands/skills/uninstall.ts
2544
- import { Command as Command11 } from "commander";
2982
+ import { Command as Command12 } from "commander";
2545
2983
  import * as prompts4 from "@clack/prompts";
2546
- import * as path14 from "path";
2547
- import * as fs10 from "fs/promises";
2984
+ import * as path15 from "path";
2985
+ import * as fs11 from "fs/promises";
2548
2986
  var SKILLS_PACKAGE3 = "@teamix-evo/skills";
2549
- var uninstallCommand2 = new Command11("uninstall").description(
2987
+ var uninstallCommand2 = new Command12("uninstall").description(
2550
2988
  "\u5378\u8F7D\u5DF2\u5B89\u88C5\u7684 teamix-evo skills\uFF1B\u4E0D\u4F20 ids \u5219\u5378\u8F7D\u6574\u5305\uFF0C\u4F20 ids \u5219\u6309 skill \u5220\u9664"
2551
2989
  ).argument("[ids...]", "\u53EF\u9009\uFF1A\u4EC5\u5378\u8F7D\u6307\u5B9A skill id\uFF1B\u7701\u7565\u5219\u5378\u8F7D\u6574\u4E2A skills \u5305").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").action(async (ids, opts) => {
2552
2990
  try {
@@ -2611,7 +3049,7 @@ async function runFullUninstall(args) {
2611
3049
  const skillsRoot = getSkillsSourceDir(projectRoot);
2612
3050
  const sourceSkillNames = await listSkillSourceNames(skillsRoot);
2613
3051
  try {
2614
- await fs10.rm(skillsRoot, { recursive: true, force: true });
3052
+ await fs11.rm(skillsRoot, { recursive: true, force: true });
2615
3053
  logger.debug(`Removed source dir ${skillsRoot}`);
2616
3054
  } catch (err) {
2617
3055
  logger.warn(`Failed to remove ${skillsRoot}: ${getErrorMessage(err)}`);
@@ -2637,10 +3075,10 @@ async function runFullUninstall(args) {
2637
3075
  await writeProjectConfig(projectRoot, config);
2638
3076
  logger.success(`Uninstalled ${SKILLS_PACKAGE3}`);
2639
3077
  logger.info(` Removed: ${removed.length} files`);
2640
- logger.info(` Source: ${path14.relative(projectRoot, skillsRoot)} (cleaned)`);
3078
+ logger.info(` Source: ${path15.relative(projectRoot, skillsRoot)} (cleaned)`);
2641
3079
  if (cleanedMirrorDirs.length > 0) {
2642
3080
  logger.info(
2643
- ` Mirrors: ${cleanedMirrorDirs.map((d) => path14.relative(projectRoot, d)).join(", ")} (cleaned)`
3081
+ ` Mirrors: ${cleanedMirrorDirs.map((d) => path15.relative(projectRoot, d)).join(", ")} (cleaned)`
2644
3082
  );
2645
3083
  }
2646
3084
  }
@@ -2678,7 +3116,7 @@ async function runPartialUninstall(args) {
2678
3116
  for (const id of matched) {
2679
3117
  const dir = getSkillsSourceDir(projectRoot, id);
2680
3118
  try {
2681
- await fs10.rm(dir, { recursive: true, force: true });
3119
+ await fs11.rm(dir, { recursive: true, force: true });
2682
3120
  logger.debug(`Removed source dir ${dir}`);
2683
3121
  } catch (err) {
2684
3122
  logger.warn(`Failed to remove ${dir}: ${getErrorMessage(err)}`);
@@ -2704,7 +3142,7 @@ async function runPartialUninstall(args) {
2704
3142
  logger.info(` Files: ${removed.length}`);
2705
3143
  if (cleanedMirrorDirs.length > 0) {
2706
3144
  logger.info(
2707
- ` Mirrors: ${cleanedMirrorDirs.map((d) => path14.relative(projectRoot, d)).join(", ")} (cleaned)`
3145
+ ` Mirrors: ${cleanedMirrorDirs.map((d) => path15.relative(projectRoot, d)).join(", ")} (cleaned)`
2708
3146
  );
2709
3147
  }
2710
3148
  if (missing.length > 0) {
@@ -2713,7 +3151,7 @@ async function runPartialUninstall(args) {
2713
3151
  }
2714
3152
  async function listSkillSourceNames(skillsRoot) {
2715
3153
  try {
2716
- const entries = await fs10.readdir(skillsRoot, { withFileTypes: true });
3154
+ const entries = await fs11.readdir(skillsRoot, { withFileTypes: true });
2717
3155
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
2718
3156
  } catch {
2719
3157
  return [];
@@ -2737,13 +3175,13 @@ async function removeMirrorDirs(projectRoot, scope, ides, skillNames) {
2737
3175
  const dir = adapter.getSkillTargetDir(name, targetScope, projectRoot);
2738
3176
  let existed = true;
2739
3177
  try {
2740
- await fs10.access(dir);
3178
+ await fs11.access(dir);
2741
3179
  } catch {
2742
3180
  existed = false;
2743
3181
  }
2744
3182
  if (!existed) continue;
2745
3183
  try {
2746
- await fs10.rm(dir, { recursive: true, force: true });
3184
+ await fs11.rm(dir, { recursive: true, force: true });
2747
3185
  removed.push(dir);
2748
3186
  logger.debug(`Removed mirror dir ${dir}`);
2749
3187
  } catch (err) {
@@ -2751,13 +3189,13 @@ async function removeMirrorDirs(projectRoot, scope, ides, skillNames) {
2751
3189
  }
2752
3190
  }
2753
3191
  if (removed.length > 0) {
2754
- const skillsParent = path14.dirname(
3192
+ const skillsParent = path15.dirname(
2755
3193
  adapter.getSkillTargetDir("placeholder", targetScope, projectRoot)
2756
3194
  );
2757
3195
  try {
2758
- const remaining = await fs10.readdir(skillsParent);
3196
+ const remaining = await fs11.readdir(skillsParent);
2759
3197
  if (remaining.length === 0) {
2760
- await fs10.rmdir(skillsParent);
3198
+ await fs11.rmdir(skillsParent);
2761
3199
  }
2762
3200
  } catch {
2763
3201
  }
@@ -2783,18 +3221,18 @@ function dedupe(values) {
2783
3221
  }
2784
3222
 
2785
3223
  // src/commands/skills/sync.ts
2786
- import { Command as Command12 } from "commander";
3224
+ import { Command as Command13 } from "commander";
2787
3225
 
2788
3226
  // src/core/skills-sync.ts
2789
- import * as path15 from "path";
2790
- import * as fs11 from "fs/promises";
3227
+ import * as path16 from "path";
3228
+ import * as fs12 from "fs/promises";
2791
3229
  import { createRequire as createRequire4 } from "module";
2792
3230
  import { loadSkillsPackageManifest as loadSkillsPackageManifest2 } from "@teamix-evo/registry";
2793
3231
  var require5 = createRequire4(import.meta.url);
2794
3232
  async function readSkillMetaFromUpstream(skillId) {
2795
3233
  try {
2796
3234
  const pkgJson = require5.resolve("@teamix-evo/skills/package.json");
2797
- const packageRoot = path15.dirname(pkgJson);
3235
+ const packageRoot = path16.dirname(pkgJson);
2798
3236
  const manifest = await loadSkillsPackageManifest2(packageRoot);
2799
3237
  const entry = manifest.skills.find((s) => s.id === skillId);
2800
3238
  if (!entry) return null;
@@ -2874,7 +3312,7 @@ async function runSkillsSync(options) {
2874
3312
  }
2875
3313
  async function dirExists(p2) {
2876
3314
  try {
2877
- const stat7 = await fs11.stat(p2);
3315
+ const stat7 = await fs12.stat(p2);
2878
3316
  return stat7.isDirectory();
2879
3317
  } catch {
2880
3318
  return false;
@@ -2892,8 +3330,8 @@ async function refreshMirrorRecords(projectRoot, newMirrorRecords) {
2892
3330
  }
2893
3331
 
2894
3332
  // src/commands/skills/sync.ts
2895
- var syncCommand = new Command12("sync").description(
2896
- "\u628A .teamix-evo/skills/ \u4E0B\u7684\u6E90\u91CD\u65B0\u955C\u50CF\u5230 IDE \u8DEF\u5F84\uFF08.qoder / .claude\uFF09"
3333
+ var syncCommand = new Command13("sync").description(
3334
+ "\u628A .teamix-evo/skills-source/ \u4E0B\u7684\u6E90\u91CD\u65B0\u955C\u50CF\u5230 IDE \u8DEF\u5F84\uFF08.qoder / .claude\uFF09"
2897
3335
  ).argument(
2898
3336
  "[names...]",
2899
3337
  "\u53EF\u9009\uFF1A\u4EC5\u540C\u6B65\u6307\u5B9A skill id\uFF1B\u7701\u7565\u5219\u540C\u6B65\u5168\u90E8\u5DF2\u8BB0\u5F55\u5728 lock \u5185\u7684 skill"
@@ -2918,7 +3356,7 @@ var syncCommand = new Command12("sync").description(
2918
3356
  });
2919
3357
  if (result.status === "no-skills") {
2920
3358
  logger.info(
2921
- "No skills recorded in .teamix-evo/skills/manifest.lock.json. Nothing to sync."
3359
+ "No skills recorded in .teamix-evo/skills-source/manifest.lock.json. Nothing to sync."
2922
3360
  );
2923
3361
  return;
2924
3362
  }
@@ -2943,11 +3381,11 @@ var syncCommand = new Command12("sync").description(
2943
3381
  });
2944
3382
 
2945
3383
  // src/commands/skills/doctor.ts
2946
- import { Command as Command13 } from "commander";
3384
+ import { Command as Command14 } from "commander";
2947
3385
 
2948
3386
  // src/core/skills-doctor.ts
2949
- import * as path16 from "path";
2950
- import * as fs12 from "fs/promises";
3387
+ import * as path17 from "path";
3388
+ import * as fs13 from "fs/promises";
2951
3389
  async function runSkillsDoctor(options) {
2952
3390
  const { projectRoot } = options;
2953
3391
  const lock = await readSkillsLock(projectRoot);
@@ -2969,8 +3407,8 @@ async function runSkillsDoctor(options) {
2969
3407
  const sourceFiles = await walkDir(sourceDir);
2970
3408
  const sourceContents = /* @__PURE__ */ new Map();
2971
3409
  for (const f of sourceFiles) {
2972
- const rel2 = path16.relative(sourceDir, f);
2973
- sourceContents.set(rel2, await fs12.readFile(f, "utf-8"));
3410
+ const rel2 = path17.relative(sourceDir, f);
3411
+ sourceContents.set(rel2, await fs13.readFile(f, "utf-8"));
2974
3412
  }
2975
3413
  for (const ide of entry.mirroredTo) {
2976
3414
  const adapter = getAdapter(ide);
@@ -2991,7 +3429,7 @@ async function runSkillsDoctor(options) {
2991
3429
  continue;
2992
3430
  }
2993
3431
  for (const [rel2, sourceContent] of sourceContents.entries()) {
2994
- const mirrorFile = path16.join(mirrorDir, rel2);
3432
+ const mirrorFile = path17.join(mirrorDir, rel2);
2995
3433
  if (!await fileExists(mirrorFile)) {
2996
3434
  findings.push({
2997
3435
  kind: "missing-mirror",
@@ -3003,7 +3441,7 @@ async function runSkillsDoctor(options) {
3003
3441
  });
3004
3442
  continue;
3005
3443
  }
3006
- const mirrorContent = await fs12.readFile(mirrorFile, "utf-8");
3444
+ const mirrorContent = await fs13.readFile(mirrorFile, "utf-8");
3007
3445
  if (computeHash(mirrorContent) !== computeHash(sourceContent)) {
3008
3446
  findings.push({
3009
3447
  kind: "mirror-drift",
@@ -3024,7 +3462,7 @@ async function runSkillsDoctor(options) {
3024
3462
  }
3025
3463
  async function dirExists2(p2) {
3026
3464
  try {
3027
- const stat7 = await fs12.stat(p2);
3465
+ const stat7 = await fs13.stat(p2);
3028
3466
  return stat7.isDirectory();
3029
3467
  } catch {
3030
3468
  return false;
@@ -3032,7 +3470,9 @@ async function dirExists2(p2) {
3032
3470
  }
3033
3471
 
3034
3472
  // src/commands/skills/doctor.ts
3035
- var doctorCommand = new Command13("doctor").description("\u68C0\u67E5 .teamix-evo/skills/ \u6E90\u4E0E IDE \u955C\u50CF\u662F\u5426\u6F02\u79FB\uFF1B\u63D0\u793A\u5982\u4F55\u4FEE\u590D").action(async () => {
3473
+ var doctorCommand = new Command14("doctor").description(
3474
+ "\u68C0\u67E5 .teamix-evo/skills-source/ \u6E90\u4E0E IDE \u955C\u50CF\u662F\u5426\u6F02\u79FB\uFF1B\u63D0\u793A\u5982\u4F55\u4FEE\u590D"
3475
+ ).action(async () => {
3036
3476
  try {
3037
3477
  const ide = detectIde();
3038
3478
  const cwd = ide.getProjectRoot();
@@ -3069,7 +3509,7 @@ var doctorCommand = new Command13("doctor").description("\u68C0\u67E5 .teamix-ev
3069
3509
  });
3070
3510
 
3071
3511
  // src/commands/skills/index.ts
3072
- var skillsCommand = new Command14("skills").description(
3512
+ var skillsCommand = new Command15("skills").description(
3073
3513
  "\u7BA1\u7406 teamix-evo skills\uFF08\u5411 AI IDE \u6CE8\u5165\u6280\u80FD\uFF1Bsource-mirror \u6A21\u578B\u89C1 ADR 0013\uFF09"
3074
3514
  );
3075
3515
  skillsCommand.addCommand(initCommand2);
@@ -3081,10 +3521,10 @@ skillsCommand.addCommand(doctorCommand);
3081
3521
  skillsCommand.addCommand(uninstallCommand2);
3082
3522
 
3083
3523
  // src/commands/ui/index.ts
3084
- import { Command as Command19 } from "commander";
3524
+ import { Command as Command21 } from "commander";
3085
3525
 
3086
3526
  // src/commands/ui/init.ts
3087
- import { Command as Command15 } from "commander";
3527
+ import { Command as Command16 } from "commander";
3088
3528
  import * as prompts5 from "@clack/prompts";
3089
3529
 
3090
3530
  // src/core/ui-init.ts
@@ -3142,7 +3582,7 @@ async function runUiInit(options) {
3142
3582
  }
3143
3583
 
3144
3584
  // src/commands/ui/init.ts
3145
- var initCommand3 = new Command15("init").description(
3585
+ var initCommand3 = new Command16("init").description(
3146
3586
  "\u521D\u59CB\u5316 teamix-evo ui \u914D\u7F6E\uFF08\u8BE2\u95EE aliases / iconLibrary / tsx / rsc\uFF09"
3147
3587
  ).option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").option(
3148
3588
  "--components <path>",
@@ -3261,26 +3701,26 @@ async function resolveConfig(opts) {
3261
3701
  }
3262
3702
 
3263
3703
  // src/commands/ui/add.ts
3264
- import { Command as Command16 } from "commander";
3704
+ import { Command as Command17 } from "commander";
3265
3705
 
3266
3706
  // src/core/ui-client.ts
3267
- import * as path17 from "path";
3268
- import * as fs13 from "fs/promises";
3707
+ import * as path18 from "path";
3708
+ import * as fs14 from "fs/promises";
3269
3709
  import { createRequire as createRequire5 } from "module";
3270
3710
  import { loadUiPackageManifest } from "@teamix-evo/registry";
3271
3711
  var require6 = createRequire5(import.meta.url);
3272
3712
  function resolvePackageRoot2(packageName) {
3273
3713
  const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
3274
- return path17.dirname(pkgJsonPath);
3714
+ return path18.dirname(pkgJsonPath);
3275
3715
  }
3276
3716
  async function loadUiData(packageName) {
3277
3717
  const packageRoot = resolvePackageRoot2(packageName);
3278
3718
  logger.debug(`Resolved ui package root: ${packageRoot}`);
3279
3719
  const manifest = await loadUiPackageManifest(packageRoot);
3280
3720
  let data = {};
3281
- const dataPath = path17.join(packageRoot, "_data.json");
3721
+ const dataPath = path18.join(packageRoot, "_data.json");
3282
3722
  try {
3283
- const raw = await fs13.readFile(dataPath, "utf-8");
3723
+ const raw = await fs14.readFile(dataPath, "utf-8");
3284
3724
  data = JSON.parse(raw);
3285
3725
  } catch (err) {
3286
3726
  if (err.code !== "ENOENT") {
@@ -3292,8 +3732,8 @@ async function loadUiData(packageName) {
3292
3732
  }
3293
3733
 
3294
3734
  // src/core/ui-installer.ts
3295
- import * as path18 from "path";
3296
- import * as fs14 from "fs/promises";
3735
+ import * as path19 from "path";
3736
+ import * as fs15 from "fs/promises";
3297
3737
  import { resolveUiEntryOrder } from "@teamix-evo/registry";
3298
3738
 
3299
3739
  // src/utils/transform-imports.ts
@@ -3366,9 +3806,12 @@ async function installUiEntries(options) {
3366
3806
  continue;
3367
3807
  }
3368
3808
  const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
3369
- const sourceAbs = path18.resolve(rootForEntry, file.source);
3370
- const raw = await fs14.readFile(sourceAbs, "utf-8");
3809
+ const sourceAbs = path19.resolve(rootForEntry, file.source);
3810
+ const raw = await fs15.readFile(sourceAbs, "utf-8");
3371
3811
  const transformed = rewriteImports(raw, aliases);
3812
+ if (exists) {
3813
+ await backupFile(targetAbs, projectRoot);
3814
+ }
3372
3815
  await writeFileSafe(targetAbs, transformed);
3373
3816
  written++;
3374
3817
  logger.info(` write: ${rel(projectRoot, targetAbs)}`);
@@ -3395,10 +3838,10 @@ function resolveTargetPath(projectRoot, aliases, entry, file) {
3395
3838
  `Entry "${entry.id}" requires alias "${file.targetAlias}" but it is not configured.`
3396
3839
  );
3397
3840
  }
3398
- return path18.join(projectRoot, aliasDir, file.targetName);
3841
+ return path19.join(projectRoot, aliasDir, file.targetName);
3399
3842
  }
3400
3843
  function rel(projectRoot, abs) {
3401
- return path18.relative(projectRoot, abs);
3844
+ return path19.relative(projectRoot, abs);
3402
3845
  }
3403
3846
 
3404
3847
  // src/core/ui-add.ts
@@ -3485,30 +3928,271 @@ function mergeResources(prior, next) {
3485
3928
  return Array.from(merged.values());
3486
3929
  }
3487
3930
 
3488
- // src/commands/ui/add.ts
3489
- var addCommand2 = new Command16("add").description(
3490
- "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A ui entry\uFF08\u6309 id\uFF0C\u81EA\u52A8\u5C55\u5F00 registryDependencies\uFF09"
3491
- ).argument("<ids...>", 'entry id \u5217\u8868\uFF0C\u5982 "button" "dialog"').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6\uFF08\u7ED5\u8FC7 frozen \u8DF3\u8FC7\uFF09").option(
3492
- "--include-deprecated",
3493
- "\u5141\u8BB8\u5B89\u88C5\u5DF2\u5F52\u6863\u7684 deprecated entry\uFF08\u4EC5\u8FC1\u79FB / \u5BA1\u8BA1\u573A\u666F\uFF0CADR 0028\uFF09"
3494
- ).action(
3495
- async (ids, opts) => {
3496
- try {
3497
- const ide = detectIde();
3498
- const projectRoot = ide.getProjectRoot();
3499
- logger.info(`Installing entries: ${ids.join(", ")}`);
3500
- const result = await runUiAdd({
3501
- projectRoot,
3502
- ids,
3503
- overwrite: opts.overwrite,
3504
- includeDeprecated: opts.includeDeprecated
3505
- });
3506
- logger.success(
3507
- `UI add complete: ${result.written} written, ${result.skipped} skipped.`
3508
- );
3509
- logger.info("");
3510
- logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
3511
- const npmDeps = Object.entries(result.npmDependencies);
3931
+ // src/core/ui-adopt.ts
3932
+ import * as path20 from "path";
3933
+ import * as fs16 from "fs/promises";
3934
+ var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
3935
+ async function runUiAdopt(options) {
3936
+ const { projectRoot, dryRun = false } = options;
3937
+ const packageName = options.packageName ?? DEFAULT_UI_PACKAGE2;
3938
+ const config = await readProjectConfig(projectRoot);
3939
+ const uiCfg = config?.packages?.ui;
3940
+ if (!config || !uiCfg?.aliases) {
3941
+ throw new Error(
3942
+ "UI not initialized. Run `runUiInit` (or `teamix-evo ui init`) first."
3943
+ );
3944
+ }
3945
+ const { manifest, packageRoot } = await loadUiData(packageName);
3946
+ const aliases = uiCfg.aliases;
3947
+ const adopted = [];
3948
+ const foreign = [];
3949
+ const hooks = [];
3950
+ const utils = [];
3951
+ const types = [];
3952
+ const idToEntry = /* @__PURE__ */ new Map();
3953
+ for (const e of manifest.deprecatedEntries ?? []) idToEntry.set(e.id, e);
3954
+ for (const e of manifest.entries) idToEntry.set(e.id, e);
3955
+ const aliasRoots = [
3956
+ { abs: path20.join(projectRoot, aliases.components), bucket: "components" },
3957
+ { abs: path20.join(projectRoot, aliases.hooks), bucket: "hooks" },
3958
+ { abs: path20.join(projectRoot, aliases.utils), bucket: "utils" },
3959
+ { abs: path20.join(projectRoot, aliases.business), bucket: "components" }
3960
+ ];
3961
+ if (aliases.lib && aliases.lib !== aliases.utils) {
3962
+ aliasRoots.push({
3963
+ abs: path20.join(projectRoot, aliases.lib),
3964
+ bucket: "utils"
3965
+ });
3966
+ }
3967
+ for (const root of aliasRoots) {
3968
+ const files = await listSourceFiles(root.abs);
3969
+ for (const abs of files) {
3970
+ const rel2 = toPosixRel(projectRoot, abs);
3971
+ const content = await fs16.readFile(abs, "utf-8");
3972
+ const fileType = classifyFile(abs, content);
3973
+ const id = inferEntryId(abs);
3974
+ if (fileType === "type") {
3975
+ types.push({ rel: rel2, fileType, target: abs, reason: "no-registry-match" });
3976
+ continue;
3977
+ }
3978
+ if (fileType === "hook" || root.bucket === "hooks") {
3979
+ hooks.push({
3980
+ rel: rel2,
3981
+ fileType: "hook",
3982
+ target: abs,
3983
+ reason: "no-registry-match"
3984
+ });
3985
+ continue;
3986
+ }
3987
+ if (fileType === "util" || root.bucket === "utils") {
3988
+ utils.push({
3989
+ rel: rel2,
3990
+ fileType: "util",
3991
+ target: abs,
3992
+ reason: "no-registry-match"
3993
+ });
3994
+ continue;
3995
+ }
3996
+ const entry = id ? idToEntry.get(id) : void 0;
3997
+ if (!entry) {
3998
+ foreign.push({
3999
+ rel: rel2,
4000
+ fileType: fileType === "unknown" ? "component" : fileType,
4001
+ target: abs,
4002
+ reason: "no-registry-match"
4003
+ });
4004
+ continue;
4005
+ }
4006
+ const upstream = await readUpstreamContent(
4007
+ packageRoot,
4008
+ entry,
4009
+ aliases
4010
+ ).catch((err) => {
4011
+ logger.debug(
4012
+ `Failed to read upstream for ${entry.id}: ${err.message}`
4013
+ );
4014
+ return null;
4015
+ });
4016
+ const lineage = upstream === null ? "detected" : normalize(upstream) === normalize(content) ? "teamix-evo" : "custom";
4017
+ adopted.push({
4018
+ id: entry.id,
4019
+ target: abs,
4020
+ rel: rel2,
4021
+ fileType: fileType === "unknown" ? "component" : fileType,
4022
+ sourceLineage: lineage,
4023
+ hash: computeHash(content)
4024
+ });
4025
+ }
4026
+ }
4027
+ if (!dryRun && adopted.length > 0) {
4028
+ await mergeIntoInstalledManifest({
4029
+ projectRoot,
4030
+ packageName,
4031
+ manifest,
4032
+ adopted
4033
+ });
4034
+ }
4035
+ return {
4036
+ packageName,
4037
+ adopted,
4038
+ foreign,
4039
+ hooks,
4040
+ utils,
4041
+ types,
4042
+ dryRun
4043
+ };
4044
+ }
4045
+ async function listSourceFiles(dir) {
4046
+ const out = [];
4047
+ const stack = [dir];
4048
+ while (stack.length > 0) {
4049
+ const current = stack.pop();
4050
+ let entries;
4051
+ try {
4052
+ entries = await fs16.readdir(current, { withFileTypes: true });
4053
+ } catch (err) {
4054
+ if (err.code === "ENOENT") continue;
4055
+ throw err;
4056
+ }
4057
+ for (const e of entries) {
4058
+ const abs = path20.join(current, e.name);
4059
+ if (e.isDirectory()) {
4060
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
4061
+ stack.push(abs);
4062
+ continue;
4063
+ }
4064
+ if (!e.isFile()) continue;
4065
+ if (/\.(ts|tsx)$/.test(e.name)) out.push(abs);
4066
+ }
4067
+ }
4068
+ return out;
4069
+ }
4070
+ function classifyFile(abs, content) {
4071
+ const base = path20.basename(abs);
4072
+ if (base.endsWith(".d.ts")) return "type";
4073
+ if (/^use-[a-z0-9-]+\.tsx?$/i.test(base)) return "hook";
4074
+ const hasJsx = /<[A-Za-z][^>]*?>/.test(content);
4075
+ const hasReactImport = /from ['"]react['"]/.test(content);
4076
+ const hasForwardRef = /forwardRef\s*[<(]/.test(content);
4077
+ const hasProvider = /\.Provider\b/.test(content) || /createContext\s*[<(]/.test(content);
4078
+ if (hasProvider && (hasJsx || hasReactImport)) return "provider";
4079
+ if (hasJsx || hasForwardRef) return "component";
4080
+ if (hasReactImport && /export\s+(const|function|default)/.test(content))
4081
+ return "component";
4082
+ const exportsValue = /export\s+(const|function|class|default|let|var)\b/.test(
4083
+ content
4084
+ );
4085
+ const exportsType = /export\s+(type|interface)\b/.test(content) || /^\s*type\s+/m.test(content);
4086
+ if (!exportsValue && exportsType) return "type";
4087
+ if (base.startsWith("use-")) return "hook";
4088
+ if (/\b(use[A-Z]\w+)\s*\(/.test(content) && !hasJsx) {
4089
+ return "util";
4090
+ }
4091
+ if (!hasJsx && !hasReactImport) return "util";
4092
+ return "unknown";
4093
+ }
4094
+ function inferEntryId(abs) {
4095
+ const base = path20.basename(abs).replace(/\.(tsx?|d\.ts)$/i, "");
4096
+ if (!base) return null;
4097
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(base)) return null;
4098
+ if (base.startsWith("use-")) return null;
4099
+ return base;
4100
+ }
4101
+ async function readUpstreamContent(packageRoot, entry, aliases) {
4102
+ const file = entry.files[0];
4103
+ if (!file) return null;
4104
+ const sourceAbs = path20.resolve(packageRoot, file.source);
4105
+ let raw;
4106
+ try {
4107
+ raw = await fs16.readFile(sourceAbs, "utf-8");
4108
+ } catch {
4109
+ return null;
4110
+ }
4111
+ return rewriteImports(raw, aliases);
4112
+ }
4113
+ function normalize(s) {
4114
+ return s.replace(/\r\n/g, "\n").trim();
4115
+ }
4116
+ function toPosixRel(projectRoot, abs) {
4117
+ return path20.relative(projectRoot, abs).split(path20.sep).join("/");
4118
+ }
4119
+ async function mergeIntoInstalledManifest(args) {
4120
+ const { projectRoot, packageName, manifest, adopted } = args;
4121
+ const installed = await readInstalledManifest(
4122
+ projectRoot
4123
+ ) ?? { schemaVersion: 1, installed: [] };
4124
+ const idx = installed.installed.findIndex((p2) => p2.package === packageName);
4125
+ const prior = idx >= 0 ? installed.installed[idx] : null;
4126
+ const newResources = adopted.map((a) => {
4127
+ const targetName = path20.basename(a.target);
4128
+ return {
4129
+ id: `${a.id}:${targetName}`,
4130
+ target: a.target,
4131
+ hash: a.hash,
4132
+ strategy: "frozen",
4133
+ sourceLineage: a.sourceLineage
4134
+ };
4135
+ });
4136
+ const merged = mergeResources2(prior?.resources ?? [], newResources);
4137
+ const entry = {
4138
+ package: packageName,
4139
+ variant: "_flat",
4140
+ version: manifest.version,
4141
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4142
+ resources: merged
4143
+ };
4144
+ if (idx >= 0) installed.installed[idx] = entry;
4145
+ else installed.installed.push(entry);
4146
+ await writeInstalledManifest(projectRoot, installed);
4147
+ }
4148
+ function mergeResources2(prior, next) {
4149
+ const merged = /* @__PURE__ */ new Map();
4150
+ for (const r of prior) merged.set(r.id, r);
4151
+ for (const r of next) merged.set(r.id, r);
4152
+ return Array.from(merged.values());
4153
+ }
4154
+
4155
+ // src/commands/ui/add.ts
4156
+ var addCommand2 = new Command17("add").description(
4157
+ "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A ui entry\uFF08\u6309 id\uFF0C\u81EA\u52A8\u5C55\u5F00 registryDependencies\uFF09"
4158
+ ).argument("[ids...]", 'entry id \u5217\u8868\uFF0C\u5982 "button" "dialog"').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6\uFF08\u7ED5\u8FC7 frozen \u8DF3\u8FC7\uFF09").option(
4159
+ "--include-deprecated",
4160
+ "\u5141\u8BB8\u5B89\u88C5\u5DF2\u5F52\u6863\u7684 deprecated entry\uFF08\u4EC5\u8FC1\u79FB / \u5BA1\u8BA1\u573A\u666F\uFF0CADR 0028\uFF09"
4161
+ ).option("--adopt", "\u626B\u63CF\u9879\u76EE\u73B0\u6709 UI \u6E90\u7801\u7EB3\u7BA1\u5230 manifest\uFF0C\u4E0D\u5199\u5165\u4EFB\u4F55\u6587\u4EF6\u5185\u5BB9").option(
4162
+ "--dry-run",
4163
+ "\u4EC5\u626B\u63CF + \u8F93\u51FA\u62A5\u544A\uFF0C\u4E0D\u4FEE\u6539 manifest\uFF08\u4EC5\u4E0E --adopt \u540C\u7528\uFF09"
4164
+ ).action(
4165
+ async (ids, opts) => {
4166
+ try {
4167
+ const ide = detectIde();
4168
+ const projectRoot = ide.getProjectRoot();
4169
+ if (opts.adopt) {
4170
+ await runAdoptCli(projectRoot, opts.dryRun ?? false);
4171
+ return;
4172
+ }
4173
+ if (opts.dryRun) {
4174
+ logger.warn(
4175
+ "`--dry-run` only takes effect together with `--adopt`; ignoring for plain ui add."
4176
+ );
4177
+ }
4178
+ if (!ids || ids.length === 0) {
4179
+ throw new Error(
4180
+ "At least one entry id must be provided (or pass `--adopt`)."
4181
+ );
4182
+ }
4183
+ logger.info(`Installing entries: ${ids.join(", ")}`);
4184
+ const result = await runUiAdd({
4185
+ projectRoot,
4186
+ ids,
4187
+ overwrite: opts.overwrite,
4188
+ includeDeprecated: opts.includeDeprecated
4189
+ });
4190
+ logger.success(
4191
+ `UI add complete: ${result.written} written, ${result.skipped} skipped.`
4192
+ );
4193
+ logger.info("");
4194
+ logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
4195
+ const npmDeps = Object.entries(result.npmDependencies);
3512
4196
  if (npmDeps.length > 0) {
3513
4197
  logger.info("");
3514
4198
  logger.info("Install npm dependencies in your project:");
@@ -3530,15 +4214,51 @@ var addCommand2 = new Command16("add").description(
3530
4214
  }
3531
4215
  }
3532
4216
  );
4217
+ async function runAdoptCli(projectRoot, dryRun) {
4218
+ logger.info(
4219
+ dryRun ? "Adopt scan (dry-run) \u2014 no manifest changes will be written." : "Adopting existing UI source files into manifest..."
4220
+ );
4221
+ const result = await runUiAdopt({ projectRoot, dryRun });
4222
+ logger.success(
4223
+ `UI adopt complete: ${result.adopted.length} adopted, ${result.foreign.length} foreign, ${result.hooks.length} hooks, ${result.utils.length} utils, ${result.types.length} types.`
4224
+ );
4225
+ if (result.adopted.length > 0) {
4226
+ logger.info("");
4227
+ logger.info("Adopted (matched registry id):");
4228
+ for (const a of result.adopted) {
4229
+ logger.info(` \u2022 ${a.id} \u2190 ${a.rel} [${a.sourceLineage}]`);
4230
+ }
4231
+ }
4232
+ if (result.foreign.length > 0) {
4233
+ logger.info("");
4234
+ logger.info("Foreign (no registry match \u2014 keep as-is):");
4235
+ for (const f of result.foreign) logger.info(` \u2022 ${f.rel}`);
4236
+ }
4237
+ if (result.hooks.length > 0) {
4238
+ logger.info("");
4239
+ logger.info("Hooks:");
4240
+ for (const h of result.hooks) logger.info(` \u2022 ${h.rel}`);
4241
+ }
4242
+ if (result.utils.length > 0) {
4243
+ logger.info("");
4244
+ logger.info("Utils:");
4245
+ for (const u of result.utils) logger.info(` \u2022 ${u.rel}`);
4246
+ }
4247
+ if (result.types.length > 0) {
4248
+ logger.info("");
4249
+ logger.info("Type-only modules:");
4250
+ for (const t of result.types) logger.info(` \u2022 ${t.rel}`);
4251
+ }
4252
+ }
3533
4253
 
3534
4254
  // src/commands/ui/list.ts
3535
- import { Command as Command17 } from "commander";
4255
+ import { Command as Command18 } from "commander";
3536
4256
 
3537
4257
  // src/core/ui-list.ts
3538
- var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
4258
+ var DEFAULT_UI_PACKAGE3 = "@teamix-evo/ui";
3539
4259
  async function runUiList(options) {
3540
4260
  const { projectRoot, installedOnly, includeDeprecated } = options;
3541
- const packageName = options.packageName ?? DEFAULT_UI_PACKAGE2;
4261
+ const packageName = options.packageName ?? DEFAULT_UI_PACKAGE3;
3542
4262
  const { manifest } = await loadUiData(packageName);
3543
4263
  const installedManifest = await readInstalledManifest(projectRoot);
3544
4264
  const installedIds = /* @__PURE__ */ new Set();
@@ -3570,7 +4290,7 @@ async function runUiList(options) {
3570
4290
  }
3571
4291
 
3572
4292
  // src/commands/ui/list.ts
3573
- var listCommand3 = new Command17("list").description("\u5217\u51FA @teamix-evo/ui \u7684\u6240\u6709 entry \u53CA\u5DF2\u5B89\u88C5\u72B6\u6001").option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 entry").option(
4293
+ var listCommand3 = new Command18("list").description("\u5217\u51FA @teamix-evo/ui \u7684\u6240\u6709 entry \u53CA\u5DF2\u5B89\u88C5\u72B6\u6001").option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 entry").option(
3574
4294
  "--include-deprecated",
3575
4295
  "\u540C\u65F6\u5217\u51FA\u5DF2\u5F52\u6863\u7684 deprecated entry\uFF08\u9ED8\u8BA4\u9690\u85CF\uFF0CADR 0028\uFF09"
3576
4296
  ).action(
@@ -3622,10 +4342,10 @@ var listCommand3 = new Command17("list").description("\u5217\u51FA @teamix-evo/u
3622
4342
  );
3623
4343
 
3624
4344
  // src/commands/_upgrade-command-factory.ts
3625
- import { Command as Command18 } from "commander";
4345
+ import { Command as Command19 } from "commander";
3626
4346
 
3627
4347
  // src/core/ui-upgrade.ts
3628
- import * as path21 from "path";
4348
+ import * as path23 from "path";
3629
4349
  import { createRequire as createRequire6 } from "module";
3630
4350
  import {
3631
4351
  loadUiPackageManifest as loadUiPackageManifest2,
@@ -3633,8 +4353,8 @@ import {
3633
4353
  } from "@teamix-evo/registry";
3634
4354
 
3635
4355
  // src/core/ui-upgrade-detector.ts
3636
- import * as fs15 from "fs/promises";
3637
- import * as path19 from "path";
4356
+ import * as fs17 from "fs/promises";
4357
+ import * as path21 from "path";
3638
4358
  var PACKAGE_NAME = {
3639
4359
  ui: "@teamix-evo/ui",
3640
4360
  "biz-ui": "@teamix-evo/biz-ui"
@@ -3650,10 +4370,10 @@ async function detectComponentLineage(options) {
3650
4370
  const config = options.config ?? await readProjectConfig(projectRoot);
3651
4371
  const installed = options.installed ?? await readInstalledManifest(projectRoot);
3652
4372
  const installDir = resolveInstallDir(category, config);
3653
- const installDirAbs = path19.join(projectRoot, installDir);
4373
+ const installDirAbs = path21.join(projectRoot, installDir);
3654
4374
  const installDirExists = await directoryExists(installDirAbs);
3655
4375
  const hasComponentsJson = await fileExists(
3656
- path19.join(projectRoot, "components.json")
4376
+ path21.join(projectRoot, "components.json")
3657
4377
  );
3658
4378
  const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
3659
4379
  const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
@@ -3693,14 +4413,14 @@ function extractIds(pkg) {
3693
4413
  }
3694
4414
  async function directoryExists(p2) {
3695
4415
  try {
3696
- const stat7 = await fs15.stat(p2);
4416
+ const stat7 = await fs17.stat(p2);
3697
4417
  return stat7.isDirectory();
3698
4418
  } catch {
3699
4419
  return false;
3700
4420
  }
3701
4421
  }
3702
4422
  async function listComponentIds(installDirAbs) {
3703
- const entries = await fs15.readdir(installDirAbs, { withFileTypes: true });
4423
+ const entries = await fs17.readdir(installDirAbs, { withFileTypes: true });
3704
4424
  const ids = [];
3705
4425
  for (const e of entries) {
3706
4426
  if (!e.isFile()) continue;
@@ -3720,7 +4440,7 @@ function classifyLineage(args) {
3720
4440
  }
3721
4441
 
3722
4442
  // src/core/ui-upgrade-staging.ts
3723
- import * as path20 from "path";
4443
+ import * as path22 from "path";
3724
4444
  var TEAMIX_DIR4 = ".teamix-evo";
3725
4445
  var STAGING_DIR = ".upgrade-staging";
3726
4446
  var PACKAGE_NAME2 = {
@@ -3740,7 +4460,7 @@ async function buildUiUpgradeStaging(options) {
3740
4460
  if (!installedPkg) return null;
3741
4461
  const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3742
4462
  const fsTs = isoToFsSafe2(isoTs);
3743
- const stagingDir = path20.join(
4463
+ const stagingDir = path22.join(
3744
4464
  options.projectRoot,
3745
4465
  TEAMIX_DIR4,
3746
4466
  STAGING_DIR,
@@ -3772,7 +4492,7 @@ async function buildUiUpgradeStaging(options) {
3772
4492
  if (onlyIds && !onlyIds.has(id)) continue;
3773
4493
  const built = await processForeign({
3774
4494
  id,
3775
- installDirAbs: path20.join(options.projectRoot, lineageReport.installDir),
4495
+ installDirAbs: path22.join(options.projectRoot, lineageReport.installDir),
3776
4496
  stagingDir,
3777
4497
  projectRoot: options.projectRoot,
3778
4498
  category
@@ -3795,7 +4515,7 @@ async function buildUiUpgradeStaging(options) {
3795
4515
  };
3796
4516
  await ensureDir(stagingDir);
3797
4517
  await writeFileSafe(
3798
- path20.join(stagingDir, "meta.json"),
4518
+ path22.join(stagingDir, "meta.json"),
3799
4519
  JSON.stringify(manifestOut, null, 2) + "\n"
3800
4520
  );
3801
4521
  return { stagingDir, manifest: manifestOut };
@@ -3829,19 +4549,19 @@ async function processRegistered(args) {
3829
4549
  const file = entry.files[0];
3830
4550
  if (!file) return null;
3831
4551
  const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
3832
- const sourceAbs = path20.resolve(rootForEntry, file.source);
4552
+ const sourceAbs = path22.resolve(rootForEntry, file.source);
3833
4553
  const raw = await readFileOrNull(sourceAbs);
3834
4554
  if (raw === null) {
3835
4555
  return null;
3836
4556
  }
3837
4557
  const incomingTransformed = rewriteImports(raw, args.aliases);
3838
4558
  const incomingHash = computeHash(incomingTransformed);
3839
- const currentExt = path20.extname(resource.target) || ".tsx";
3840
- const incomingExt = path20.extname(file.targetName) || currentExt;
4559
+ const currentExt = path22.extname(resource.target) || ".tsx";
4560
+ const incomingExt = path22.extname(file.targetName) || currentExt;
3841
4561
  const currentRel = `${id}/current${currentExt}`;
3842
4562
  const incomingRel = `${id}/incoming${incomingExt}`;
3843
- await writeFileSafe(path20.join(stagingDir, currentRel), currentSource);
3844
- await writeFileSafe(path20.join(stagingDir, incomingRel), incomingTransformed);
4563
+ await writeFileSafe(path22.join(stagingDir, currentRel), currentSource);
4564
+ await writeFileSafe(path22.join(stagingDir, incomingRel), incomingTransformed);
3845
4565
  const diff = classifyRisk({
3846
4566
  currentHash: resource.hash,
3847
4567
  incomingHash,
@@ -3849,11 +4569,16 @@ async function processRegistered(args) {
3849
4569
  incomingSource: incomingTransformed,
3850
4570
  multiFile: entry.files.length > 1
3851
4571
  });
4572
+ const promotion = derivePromotion({
4573
+ currentSource,
4574
+ incomingSource: incomingTransformed,
4575
+ targetName: entry.files[0]?.targetName ?? `${id}.tsx`
4576
+ });
3852
4577
  return {
3853
4578
  id,
3854
4579
  category,
3855
4580
  current: {
3856
- target: path20.relative(projectRoot, resource.target),
4581
+ target: path22.relative(projectRoot, resource.target),
3857
4582
  hash: resource.hash,
3858
4583
  sourceLineage: "teamix-evo"
3859
4584
  },
@@ -3862,25 +4587,26 @@ async function processRegistered(args) {
3862
4587
  hash: incomingHash,
3863
4588
  relPath: incomingRel
3864
4589
  },
3865
- diff
4590
+ diff,
4591
+ promotion
3866
4592
  };
3867
4593
  }
3868
4594
  async function processForeign(args) {
3869
4595
  const { id, installDirAbs, stagingDir, projectRoot, category } = args;
3870
- const tsx = path20.join(installDirAbs, `${id}.tsx`);
3871
- const ts = path20.join(installDirAbs, `${id}.ts`);
4596
+ const tsx = path22.join(installDirAbs, `${id}.tsx`);
4597
+ const ts = path22.join(installDirAbs, `${id}.ts`);
3872
4598
  const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
3873
4599
  if (!target) return null;
3874
4600
  const raw = await readFileOrNull(target);
3875
4601
  if (raw === null) return null;
3876
- const ext = path20.extname(target);
4602
+ const ext = path22.extname(target);
3877
4603
  const currentRel = `${id}/current${ext}`;
3878
- await writeFileSafe(path20.join(stagingDir, currentRel), raw);
4604
+ await writeFileSafe(path22.join(stagingDir, currentRel), raw);
3879
4605
  return {
3880
4606
  id,
3881
4607
  category,
3882
4608
  current: {
3883
- target: path20.relative(projectRoot, target),
4609
+ target: path22.relative(projectRoot, target),
3884
4610
  hash: computeHash(raw),
3885
4611
  sourceLineage: "custom"
3886
4612
  },
@@ -3895,17 +4621,17 @@ async function processForeign(args) {
3895
4621
  };
3896
4622
  }
3897
4623
  async function buildBreakingEntry(args) {
3898
- const ext = path20.extname(args.resource.target) || ".tsx";
4624
+ const ext = path22.extname(args.resource.target) || ".tsx";
3899
4625
  const currentRel = `${args.id}/current${ext}`;
3900
4626
  await writeFileSafe(
3901
- path20.join(args.stagingDir, currentRel),
4627
+ path22.join(args.stagingDir, currentRel),
3902
4628
  args.currentSource
3903
4629
  );
3904
4630
  return {
3905
4631
  id: args.id,
3906
4632
  category: args.category,
3907
4633
  current: {
3908
- target: path20.relative(args.projectRoot, args.resource.target),
4634
+ target: path22.relative(args.projectRoot, args.resource.target),
3909
4635
  hash: args.resource.hash,
3910
4636
  sourceLineage: "teamix-evo"
3911
4637
  },
@@ -4025,12 +4751,191 @@ function aggregateByRisk(entries) {
4025
4751
  }
4026
4752
  return out;
4027
4753
  }
4754
+ function derivePromotion(args) {
4755
+ const fileType = classifyPromoteFileType(args.targetName, args.currentSource);
4756
+ const featureVector = buildFeatureVector(
4757
+ args.currentSource,
4758
+ args.incomingSource
4759
+ );
4760
+ const { recommendedModes, confidence, reasons } = scorePromotionModes(
4761
+ fileType,
4762
+ featureVector
4763
+ );
4764
+ return { fileType, featureVector, recommendedModes, confidence, reasons };
4765
+ }
4766
+ function classifyPromoteFileType(targetName, src) {
4767
+ if (targetName.endsWith(".d.ts")) return "type";
4768
+ if (/^use-[a-z0-9-]+\.tsx?$/i.test(targetName)) return "hook";
4769
+ const hasJsx = /<[A-Za-z][^>]*?>/.test(src);
4770
+ const hasReactImport = /from ['"]react['"]/.test(src);
4771
+ const hasProvider = /\.Provider\b/.test(src) || /createContext\s*[<(]/.test(src);
4772
+ if (hasProvider && (hasJsx || hasReactImport)) return "provider";
4773
+ if (hasJsx || /forwardRef\s*[<(]/.test(src)) return "component";
4774
+ if (!hasJsx && !hasReactImport) return "util";
4775
+ return "component";
4776
+ }
4777
+ function buildFeatureVector(current, incoming) {
4778
+ const curExports = extractExportNames(current);
4779
+ const newExports = extractExportNames(incoming);
4780
+ const apiAdded = setDiff(curExports, newExports);
4781
+ const apiRemoved = setDiff(newExports, curExports);
4782
+ const curVariants = extractCvaVariantValues(current);
4783
+ const newVariants = extractCvaVariantValues(incoming);
4784
+ const cvaAdded = setDiff(curVariants, newVariants);
4785
+ const cvaModified = [];
4786
+ const sharedVariants = curVariants.filter((v) => newVariants.includes(v));
4787
+ for (const v of sharedVariants) {
4788
+ if (extractVariantBody(current, v) !== extractVariantBody(incoming, v)) {
4789
+ cvaModified.push(v);
4790
+ }
4791
+ }
4792
+ const curClass = extractClassNameLiterals(current);
4793
+ const newClass = extractClassNameLiterals(incoming);
4794
+ const classNameDiff = curClass !== newClass;
4795
+ const curTokens = extractTokenRefs(current);
4796
+ const newTokens = extractTokenRefs(incoming);
4797
+ const tokenUsageDiff = curTokens.size !== newTokens.size || [...curTokens].some((t) => !newTokens.has(t));
4798
+ const hasState = /\buseState\s*[<(]/.test(current);
4799
+ const hasEffect = /\b(useEffect|useLayoutEffect|useMemo|useCallback)\s*[<(]/.test(current);
4800
+ const curImports = extractImportSources(current);
4801
+ const newImports = extractImportSources(incoming);
4802
+ const hasExtraImports = [...curImports].some((src) => !newImports.has(src));
4803
+ const tagSet = /* @__PURE__ */ new Set();
4804
+ for (const m of current.matchAll(/<([A-Z]\w+)[\s/>]/g)) {
4805
+ if (m[1]) tagSet.add(m[1]);
4806
+ }
4807
+ const atomicChildren = [...tagSet];
4808
+ const isComposition = atomicChildren.length > 2;
4809
+ const signatureChanged = extractDefaultParamList(current) !== extractDefaultParamList(incoming);
4810
+ return {
4811
+ apiDelta: { added: apiAdded, removed: apiRemoved, signatureChanged },
4812
+ styleDelta: { classNameDiff, tokenUsageDiff },
4813
+ logicDelta: { hasState, hasEffect, hasExtraImports },
4814
+ cvaDelta: { addedVariants: cvaAdded, modifiedVariants: cvaModified },
4815
+ structureDelta: { isComposition, atomicChildren }
4816
+ };
4817
+ }
4818
+ function scorePromotionModes(fileType, fv) {
4819
+ const reasons = [];
4820
+ if (fileType === "hook" || fileType === "util" || fileType === "type") {
4821
+ reasons.push(
4822
+ `fileType=${fileType} \u2014 not a component, deferred to ManualReview`
4823
+ );
4824
+ return { recommendedModes: ["ManualReview"], confidence: 0.5, reasons };
4825
+ }
4826
+ const apiNoChange = fv.apiDelta.added.length === 0 && fv.apiDelta.removed.length === 0 && !fv.apiDelta.signatureChanged;
4827
+ const logicMinimal = !fv.logicDelta.hasState && !fv.logicDelta.hasEffect && !fv.logicDelta.hasExtraImports;
4828
+ if (fv.apiDelta.removed.length > 0 || fv.apiDelta.signatureChanged) {
4829
+ reasons.push(
4830
+ "signature changed or props removed \u2014 Coexist preserves user version"
4831
+ );
4832
+ return { recommendedModes: ["Coexist"], confidence: 0.85, reasons };
4833
+ }
4834
+ if (apiNoChange && logicMinimal && (fv.styleDelta.classNameDiff || fv.styleDelta.tokenUsageDiff) && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
4835
+ reasons.push(
4836
+ "only style / token differences \u2014 push to tokens.overrides.css"
4837
+ );
4838
+ return { recommendedModes: ["TokenOnly"], confidence: 0.8, reasons };
4839
+ }
4840
+ const modes = [];
4841
+ let score = 0;
4842
+ if (fv.cvaDelta.addedVariants.length > 0 || fv.cvaDelta.modifiedVariants.length > 0) {
4843
+ modes.push("Variant");
4844
+ reasons.push(
4845
+ `cva variants delta: +${fv.cvaDelta.addedVariants.length} ~${fv.cvaDelta.modifiedVariants.length}`
4846
+ );
4847
+ score = Math.max(score, 0.7);
4848
+ }
4849
+ if (fv.logicDelta.hasState || fv.logicDelta.hasEffect || fv.logicDelta.hasExtraImports || fv.apiDelta.added.length > 0) {
4850
+ modes.push("Wrapper");
4851
+ reasons.push("user added state / effect / imports / props");
4852
+ score = Math.max(score, 0.75);
4853
+ }
4854
+ if (apiNoChange && logicMinimal && !fv.styleDelta.classNameDiff && !fv.styleDelta.tokenUsageDiff && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
4855
+ modes.push("Preset");
4856
+ reasons.push("no API/logic delta \u2014 Preset captures default-prop tweaks");
4857
+ score = Math.max(score, 0.6);
4858
+ }
4859
+ if (fv.structureDelta.isComposition) {
4860
+ modes.push("Compose");
4861
+ reasons.push(
4862
+ `composition of ${fv.structureDelta.atomicChildren.length} atomic children`
4863
+ );
4864
+ score = Math.max(score, 0.65);
4865
+ }
4866
+ if (modes.length === 0) {
4867
+ reasons.push("no axis crossed the 0.6 threshold");
4868
+ return { recommendedModes: ["ManualReview"], confidence: 0.4, reasons };
4869
+ }
4870
+ return { recommendedModes: modes, confidence: score, reasons };
4871
+ }
4872
+ function extractVariantBody(src, key) {
4873
+ const block = extractVariantsBlock(src);
4874
+ if (block === null) return "";
4875
+ const re = new RegExp(`(?:["']?${key}["']?)\\s*:\\s*\\{`);
4876
+ const idx = block.search(re);
4877
+ if (idx < 0) return "";
4878
+ const open = block.indexOf("{", idx);
4879
+ if (open < 0) return "";
4880
+ let depth = 0;
4881
+ for (let i = open; i < block.length; i++) {
4882
+ const c = block[i];
4883
+ if (c === "{") depth++;
4884
+ else if (c === "}") {
4885
+ depth--;
4886
+ if (depth === 0) return block.slice(open + 1, i);
4887
+ }
4888
+ }
4889
+ return "";
4890
+ }
4891
+ function extractClassNameLiterals(src) {
4892
+ const out = [];
4893
+ for (const m of src.matchAll(/className\s*=\s*["'`]([^"'`]*)["'`]/g)) {
4894
+ if (m[1]) out.push(m[1]);
4895
+ }
4896
+ for (const m of src.matchAll(/\b(?:cn|clsx|cva)\s*\(/g)) {
4897
+ const open = (m.index ?? 0) + m[0].length - 1;
4898
+ let depth = 1;
4899
+ let i = open + 1;
4900
+ for (; i < src.length && depth > 0; i++) {
4901
+ const c = src[i];
4902
+ if (c === "(") depth++;
4903
+ else if (c === ")") depth--;
4904
+ }
4905
+ const body = src.slice(open + 1, i - 1);
4906
+ for (const lit of body.matchAll(/["'`]([^"'`]*)["'`]/g)) {
4907
+ if (lit[1]) out.push(lit[1]);
4908
+ }
4909
+ }
4910
+ return out.sort().join("|");
4911
+ }
4912
+ function extractTokenRefs(src) {
4913
+ const out = /* @__PURE__ */ new Set();
4914
+ for (const m of src.matchAll(/var\(--([a-z0-9-]+)\)/g)) {
4915
+ if (m[1]) out.add(m[1]);
4916
+ }
4917
+ for (const m of src.matchAll(/--([a-z][a-z0-9-]*)\s*:/g)) {
4918
+ if (m[1]) out.add(m[1]);
4919
+ }
4920
+ return out;
4921
+ }
4922
+ function extractImportSources(src) {
4923
+ const out = /* @__PURE__ */ new Set();
4924
+ for (const m of src.matchAll(/^\s*import\b[^'"]*['"]([^'"]+)['"]/gm)) {
4925
+ if (m[1]) out.add(m[1]);
4926
+ }
4927
+ return out;
4928
+ }
4929
+ function extractDefaultParamList(src) {
4930
+ 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);
4931
+ return m?.[1]?.replace(/\s+/g, " ").trim() ?? "";
4932
+ }
4028
4933
 
4029
4934
  // src/core/ui-upgrade.ts
4030
4935
  var nodeRequire = createRequire6(import.meta.url);
4031
4936
  function resolvePackageRoot3(packageName) {
4032
4937
  const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
4033
- return path21.dirname(pkgJsonPath);
4938
+ return path23.dirname(pkgJsonPath);
4034
4939
  }
4035
4940
  async function runUiUpgrade(options) {
4036
4941
  const { projectRoot, category, ids = [], trigger } = options;
@@ -4116,7 +5021,7 @@ async function buildStaging(args) {
4116
5021
  }
4117
5022
  const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot3("@teamix-evo/biz-ui");
4118
5023
  const variant = lineageReport.installedVariant ?? "_flat";
4119
- const variantDir = path21.join(bizRoot, "variants", variant);
5024
+ const variantDir = path23.join(bizRoot, "variants", variant);
4120
5025
  const variantManifest = await loadVariantUiPackageManifest(variantDir);
4121
5026
  const uiRoot = args.uiPackageRoot ?? resolvePackageRoot3("@teamix-evo/ui");
4122
5027
  const uiManifest = await loadUiPackageManifest2(uiRoot);
@@ -4168,7 +5073,7 @@ var META = {
4168
5073
  };
4169
5074
  function makeUpgradeCommand(category) {
4170
5075
  const meta = META[category];
4171
- return new Command18("upgrade").description(
5076
+ return new Command19("upgrade").description(
4172
5077
  `\u4E3A ${category} \u7EC4\u4EF6\u751F\u6210\u5347\u7EA7 staging\uFF08\u4E0D\u5199 src\uFF0C\u9700\u901A\u8FC7 teamix-evo-upgrade skill \u5E94\u7528\uFF09`
4173
5078
  ).argument(
4174
5079
  "[ids...]",
@@ -4230,36 +5135,865 @@ function makeUpgradeCommand(category) {
4230
5135
  return;
4231
5136
  }
4232
5137
  }
4233
- } catch (err) {
5138
+ } catch (err) {
5139
+ logger.error(
5140
+ `Failed to build ${category} staging: ${getErrorMessage(err)}`
5141
+ );
5142
+ logger.debug(err.stack ?? "");
5143
+ process.exitCode = 1;
5144
+ }
5145
+ });
5146
+ }
5147
+
5148
+ // src/commands/ui/upgrade.ts
5149
+ var upgradeCommand = makeUpgradeCommand("ui");
5150
+
5151
+ // src/commands/ui/promote.ts
5152
+ import { Command as Command20 } from "commander";
5153
+
5154
+ // src/core/ui-promote.ts
5155
+ import * as fs19 from "fs/promises";
5156
+ import * as path25 from "path";
5157
+
5158
+ // src/core/ui-promote-imports.ts
5159
+ import * as fs18 from "fs/promises";
5160
+ import * as path24 from "path";
5161
+ var SOURCE_FILE_EXT = /\.(tsx?|jsx?|mts|cts)$/;
5162
+ var STATIC_IMPORT_FROM = /^(\s*(?:import|export)\s[\s\S]*?from\s+['"])([^'"\n]+)(['"])/;
5163
+ var SIDE_EFFECT_IMPORT = /^(\s*import\s+['"])([^'"\n]+)(['"])/;
5164
+ var DYNAMIC_IMPORT_STR = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
5165
+ var DYNAMIC_IMPORT_TPL = /\bimport\s*\(\s*`/g;
5166
+ async function rewriteProjectImports(projectRoot, specs, options = {}) {
5167
+ if (specs.length === 0) return { rewritten: [], skipped: [] };
5168
+ const scanRoot = options.scanRoot ?? path24.join(projectRoot, "src");
5169
+ const dryRun = options.dryRun ?? false;
5170
+ const backup = options.backup ?? true;
5171
+ const files = await collectTsFiles(scanRoot);
5172
+ const rewritten = [];
5173
+ const skipped = [];
5174
+ for (const file of files) {
5175
+ const raw = await readFileOrNull(file);
5176
+ if (raw === null) continue;
5177
+ const rel2 = toRel(projectRoot, file);
5178
+ const lines = raw.split("\n");
5179
+ let changed = false;
5180
+ for (let i = 0; i < lines.length; i++) {
5181
+ const orig = lines[i] ?? "";
5182
+ const staticMatch = orig.match(STATIC_IMPORT_FROM);
5183
+ if (staticMatch) {
5184
+ const whole = staticMatch[0];
5185
+ const prefix = staticMatch[1] ?? "";
5186
+ const spec = staticMatch[2] ?? "";
5187
+ const quote2 = staticMatch[3] ?? "";
5188
+ const replaced = applySpecs(spec, specs);
5189
+ if (replaced !== null) {
5190
+ if (/^\s*export\s/.test(orig)) {
5191
+ skipped.push({
5192
+ file: rel2,
5193
+ line: i + 1,
5194
+ reason: "aliased-export",
5195
+ snippet: orig.trim()
5196
+ });
5197
+ continue;
5198
+ }
5199
+ lines[i] = `${prefix}${replaced}${quote2}${orig.slice(whole.length)}`;
5200
+ changed = true;
5201
+ }
5202
+ continue;
5203
+ }
5204
+ const sideMatch = orig.match(SIDE_EFFECT_IMPORT);
5205
+ if (sideMatch) {
5206
+ const whole = sideMatch[0];
5207
+ const prefix = sideMatch[1] ?? "";
5208
+ const spec = sideMatch[2] ?? "";
5209
+ const quote2 = sideMatch[3] ?? "";
5210
+ const replaced = applySpecs(spec, specs);
5211
+ if (replaced !== null) {
5212
+ lines[i] = `${prefix}${replaced}${quote2}${orig.slice(whole.length)}`;
5213
+ changed = true;
5214
+ }
5215
+ continue;
5216
+ }
5217
+ for (const m of orig.matchAll(DYNAMIC_IMPORT_STR)) {
5218
+ const target = m[1] ?? "";
5219
+ if (specs.some((s) => isAffected(target, s.oldPath))) {
5220
+ skipped.push({
5221
+ file: rel2,
5222
+ line: i + 1,
5223
+ reason: "dynamic-import",
5224
+ snippet: orig.trim()
5225
+ });
5226
+ }
5227
+ }
5228
+ if (DYNAMIC_IMPORT_TPL.test(orig)) {
5229
+ skipped.push({
5230
+ file: rel2,
5231
+ line: i + 1,
5232
+ reason: "string-template",
5233
+ snippet: orig.trim()
5234
+ });
5235
+ DYNAMIC_IMPORT_TPL.lastIndex = 0;
5236
+ }
5237
+ }
5238
+ if (changed) {
5239
+ const updated = lines.join("\n");
5240
+ if (!dryRun) {
5241
+ if (backup) await backupFile(file, projectRoot);
5242
+ await writeFileSafe(file, updated);
5243
+ }
5244
+ rewritten.push(rel2);
5245
+ }
5246
+ }
5247
+ return { rewritten, skipped };
5248
+ }
5249
+ function applySpecs(target, specs) {
5250
+ for (const s of specs) {
5251
+ if (target === s.oldPath) return s.newPath;
5252
+ if (target.startsWith(s.oldPath + "/")) {
5253
+ return s.newPath + target.slice(s.oldPath.length);
5254
+ }
5255
+ }
5256
+ return null;
5257
+ }
5258
+ function isAffected(target, oldPath) {
5259
+ return target === oldPath || target.startsWith(oldPath + "/");
5260
+ }
5261
+ async function collectTsFiles(root) {
5262
+ const out = [];
5263
+ async function walk(dir) {
5264
+ let entries;
5265
+ try {
5266
+ entries = await fs18.readdir(dir, { withFileTypes: true });
5267
+ } catch (err) {
5268
+ if (err.code === "ENOENT") return;
5269
+ throw err;
5270
+ }
5271
+ for (const e of entries) {
5272
+ if (e.name.startsWith(".") || e.name === "node_modules" || e.name === "dist") {
5273
+ continue;
5274
+ }
5275
+ const full = path24.join(dir, e.name);
5276
+ if (e.isDirectory()) {
5277
+ await walk(full);
5278
+ } else if (e.isFile() && SOURCE_FILE_EXT.test(e.name)) {
5279
+ out.push(full);
5280
+ }
5281
+ }
5282
+ }
5283
+ await walk(root);
5284
+ return out;
5285
+ }
5286
+ function toRel(projectRoot, abs) {
5287
+ return path24.relative(projectRoot, abs).split(path24.sep).join("/");
5288
+ }
5289
+
5290
+ // src/core/ui-promote.ts
5291
+ var TEAMIX_DIR5 = ".teamix-evo";
5292
+ var STAGING_DIR2 = ".upgrade-staging";
5293
+ async function runUiPromote(options) {
5294
+ const { projectRoot } = options;
5295
+ const dryRun = options.dryRun ?? false;
5296
+ const promotions = [];
5297
+ const fileChanges = [];
5298
+ const skipped = [];
5299
+ const rewritten = [];
5300
+ const config = await readProjectConfig(projectRoot);
5301
+ if (!config?.packages?.ui?.aliases) {
5302
+ return {
5303
+ status: "not-initialized",
5304
+ promotions,
5305
+ fileChanges,
5306
+ importRewrite: { rewritten, skipped }
5307
+ };
5308
+ }
5309
+ const aliases = config.packages.ui.aliases;
5310
+ const stagingDir = options.stagingDir ?? await findLatestUiStagingDir(projectRoot);
5311
+ if (!stagingDir) {
5312
+ return {
5313
+ status: "no-staging",
5314
+ promotions,
5315
+ fileChanges,
5316
+ importRewrite: { rewritten, skipped }
5317
+ };
5318
+ }
5319
+ const meta = await readStagingMeta(stagingDir);
5320
+ if (!meta || meta.entries.length === 0) {
5321
+ return {
5322
+ status: "no-entries",
5323
+ stagingDir,
5324
+ promotions,
5325
+ fileChanges,
5326
+ importRewrite: { rewritten, skipped }
5327
+ };
5328
+ }
5329
+ const onlyIds = options.ids && options.ids.length > 0 ? new Set(options.ids) : null;
5330
+ const targetEntries = onlyIds ? meta.entries.filter((e) => onlyIds.has(e.id)) : meta.entries;
5331
+ const newResources = [];
5332
+ for (const entry of targetEntries) {
5333
+ const decision = decideModes(entry, options.modesOverride);
5334
+ try {
5335
+ const outcome = await promoteEntry({
5336
+ projectRoot,
5337
+ stagingDir,
5338
+ entry,
5339
+ modes: decision,
5340
+ aliases,
5341
+ keepOriginalNames: options.keepOriginalNames ?? false,
5342
+ dryRun
5343
+ });
5344
+ promotions.push(outcome.promotion);
5345
+ fileChanges.push(...outcome.fileChanges);
5346
+ newResources.push(...outcome.newResources);
5347
+ if (decision.includes("Coexist") && options.migrateImports && outcome.coexistRewriteSpec) {
5348
+ const r = await rewriteProjectImports(
5349
+ projectRoot,
5350
+ [outcome.coexistRewriteSpec],
5351
+ { dryRun, backup: true }
5352
+ );
5353
+ rewritten.push(...r.rewritten);
5354
+ skipped.push(...r.skipped);
5355
+ for (const rel2 of r.rewritten) {
5356
+ fileChanges.push({
5357
+ kind: "modified",
5358
+ path: rel2,
5359
+ step: "ui-promote-imports"
5360
+ });
5361
+ }
5362
+ }
5363
+ } catch (err) {
5364
+ logger.error(`Promotion failed for ${entry.id}: ${getErrorMessage(err)}`);
5365
+ promotions.push({
5366
+ id: entry.id,
5367
+ modes: decision,
5368
+ status: "skipped",
5369
+ outputs: [],
5370
+ reason: getErrorMessage(err)
5371
+ });
5372
+ }
5373
+ }
5374
+ if (!dryRun && newResources.length > 0) {
5375
+ await mergeIntoInstalledManifest2(projectRoot, newResources);
5376
+ }
5377
+ return {
5378
+ status: "promoted",
5379
+ stagingDir,
5380
+ promotions,
5381
+ fileChanges,
5382
+ importRewrite: { rewritten, skipped }
5383
+ };
5384
+ }
5385
+ async function promoteEntry(args) {
5386
+ const { entry, modes, projectRoot, stagingDir, aliases, dryRun } = args;
5387
+ const fileChanges = [];
5388
+ const newResources = [];
5389
+ let coexistRewriteSpec;
5390
+ if (modes.includes("ManualReview")) {
5391
+ return {
5392
+ promotion: {
5393
+ id: entry.id,
5394
+ modes,
5395
+ status: "manual-review",
5396
+ outputs: [],
5397
+ reason: "Confidence below threshold or fileType non-component \u2014 see staging meta.json"
5398
+ },
5399
+ fileChanges,
5400
+ newResources
5401
+ };
5402
+ }
5403
+ if (modes.includes("TokenOnly")) {
5404
+ return {
5405
+ promotion: {
5406
+ id: entry.id,
5407
+ modes,
5408
+ status: "token-only",
5409
+ outputs: [],
5410
+ reason: "\u5DEE\u5F02\u4EC5\u9650 className/token \u2014 \u7F16\u8F91 tokens/tokens.overrides.css \u800C\u975E\u521B\u5EFA biz-ui \u6587\u4EF6"
5411
+ },
5412
+ fileChanges,
5413
+ newResources
5414
+ };
5415
+ }
5416
+ const currentSrc = await readStagedFile(stagingDir, entry, "current");
5417
+ const incomingSrc = await readStagedFile(stagingDir, entry, "incoming");
5418
+ const businessDir = path25.join(projectRoot, aliases.business);
5419
+ const uiDir = path25.join(projectRoot, aliases.components);
5420
+ const businessRel = aliases.business;
5421
+ const uiRel = aliases.components;
5422
+ if (modes.includes("Coexist")) {
5423
+ if (!currentSrc) {
5424
+ throw new Error("Coexist requires a current.tsx in staging");
5425
+ }
5426
+ if (!incomingSrc) {
5427
+ throw new Error("Coexist requires an incoming.tsx in staging");
5428
+ }
5429
+ const legacyId = `legacy-${entry.id}`;
5430
+ const legacyAbs = path25.join(businessDir, `${legacyId}.tsx`);
5431
+ const uiAbs = path25.join(uiDir, `${entry.id}.tsx`);
5432
+ if (await fileExists(uiAbs)) {
5433
+ if (!dryRun) await backupFile(uiAbs, projectRoot);
5434
+ fileChanges.push({
5435
+ kind: "backed-up",
5436
+ path: posix2(path25.relative(projectRoot, uiAbs)),
5437
+ step: "ui-promote",
5438
+ detail: "pre-coexist user version"
5439
+ });
5440
+ }
5441
+ const renamed = args.keepOriginalNames ? currentSrc : prefixExportsLegacy(currentSrc, entry.id);
5442
+ if (!dryRun) {
5443
+ await ensureDir(businessDir);
5444
+ await writeFileSafe(legacyAbs, renamed);
5445
+ }
5446
+ fileChanges.push({
5447
+ kind: "created",
5448
+ path: posix2(path25.join(businessRel, `${legacyId}.tsx`)),
5449
+ step: "ui-promote",
5450
+ detail: "Coexist legacy snapshot"
5451
+ });
5452
+ newResources.push({
5453
+ id: `${legacyId}:${legacyId}.tsx`,
5454
+ target: legacyAbs,
5455
+ hash: computeHash(renamed),
5456
+ strategy: "frozen"
5457
+ });
5458
+ if (!dryRun) await writeFileSafe(uiAbs, incomingSrc);
5459
+ fileChanges.push({
5460
+ kind: "modified",
5461
+ path: posix2(path25.join(uiRel, `${entry.id}.tsx`)),
5462
+ step: "ui-promote",
5463
+ detail: "Coexist upstream restore"
5464
+ });
5465
+ coexistRewriteSpec = {
5466
+ oldPath: importPathFor(uiRel, entry.id),
5467
+ newPath: importPathFor(businessRel, legacyId)
5468
+ };
5469
+ return {
5470
+ promotion: {
5471
+ id: entry.id,
5472
+ modes,
5473
+ status: "promoted",
5474
+ outputs: [
5475
+ posix2(path25.join(businessRel, `${legacyId}.tsx`)),
5476
+ posix2(path25.join(uiRel, `${entry.id}.tsx`))
5477
+ ],
5478
+ reason: "\u53CC\u8F68\uFF1Aupstream \u88C5\u56DE ui/\u3001\u7528\u6237\u7248\u6539\u540D\u8FDB business/legacy-"
5479
+ },
5480
+ fileChanges,
5481
+ newResources,
5482
+ coexistRewriteSpec
5483
+ };
5484
+ }
5485
+ if (modes.includes("Fork")) {
5486
+ if (!currentSrc) throw new Error("Fork requires a current.tsx in staging");
5487
+ const forkAbs = path25.join(businessDir, `${entry.id}.tsx`);
5488
+ if (await fileExists(forkAbs)) {
5489
+ if (!dryRun) await backupFile(forkAbs, projectRoot);
5490
+ fileChanges.push({
5491
+ kind: "backed-up",
5492
+ path: posix2(path25.relative(projectRoot, forkAbs)),
5493
+ step: "ui-promote",
5494
+ detail: "pre-fork existing business file"
5495
+ });
5496
+ }
5497
+ if (!dryRun) {
5498
+ await ensureDir(businessDir);
5499
+ await writeFileSafe(forkAbs, currentSrc);
5500
+ }
5501
+ fileChanges.push({
5502
+ kind: "created",
5503
+ path: posix2(path25.join(businessRel, `${entry.id}.tsx`)),
5504
+ step: "ui-promote",
5505
+ detail: "Fork \u2014 full custom copy"
5506
+ });
5507
+ newResources.push({
5508
+ id: `${entry.id}:${entry.id}.tsx`,
5509
+ target: forkAbs,
5510
+ hash: computeHash(currentSrc),
5511
+ strategy: "frozen"
5512
+ });
5513
+ return {
5514
+ promotion: {
5515
+ id: entry.id,
5516
+ modes,
5517
+ status: "promoted",
5518
+ outputs: [posix2(path25.join(businessRel, `${entry.id}.tsx`))],
5519
+ reason: "Fork\uFF1A\u5B8C\u6574\u4FDD\u7559\u7528\u6237\u5B9E\u73B0"
5520
+ },
5521
+ fileChanges,
5522
+ newResources
5523
+ };
5524
+ }
5525
+ const businessAbs = path25.join(businessDir, `${entry.id}.tsx`);
5526
+ const composedSource = composeBusinessFile({
5527
+ id: entry.id,
5528
+ modes,
5529
+ aliases,
5530
+ entry,
5531
+ incomingSource: incomingSrc
5532
+ });
5533
+ if (await fileExists(businessAbs)) {
5534
+ if (!dryRun) await backupFile(businessAbs, projectRoot);
5535
+ fileChanges.push({
5536
+ kind: "backed-up",
5537
+ path: posix2(path25.relative(projectRoot, businessAbs)),
5538
+ step: "ui-promote",
5539
+ detail: "pre-promote business file"
5540
+ });
5541
+ }
5542
+ if (!dryRun) {
5543
+ await ensureDir(businessDir);
5544
+ await writeFileSafe(businessAbs, composedSource);
5545
+ }
5546
+ fileChanges.push({
5547
+ kind: "created",
5548
+ path: posix2(path25.join(businessRel, `${entry.id}.tsx`)),
5549
+ step: "ui-promote",
5550
+ detail: `modes: ${modes.join("+")}`
5551
+ });
5552
+ newResources.push({
5553
+ id: `${entry.id}:${entry.id}.tsx`,
5554
+ target: businessAbs,
5555
+ hash: computeHash(composedSource),
5556
+ strategy: "frozen"
5557
+ });
5558
+ return {
5559
+ promotion: {
5560
+ id: entry.id,
5561
+ modes,
5562
+ status: "promoted",
5563
+ outputs: [posix2(path25.join(businessRel, `${entry.id}.tsx`))],
5564
+ reason: `modes: ${modes.join("+")} \u5DF2\u751F\u6210 business/${entry.id}.tsx`
5565
+ },
5566
+ fileChanges,
5567
+ newResources
5568
+ };
5569
+ }
5570
+ function composeBusinessFile(args) {
5571
+ const { id, modes, aliases, entry } = args;
5572
+ const componentName = pascalCase(id);
5573
+ const upstreamImport = importPathFor(aliases.components, id);
5574
+ const hasVariant = modes.includes("Variant");
5575
+ const hasWrapper = modes.includes("Wrapper");
5576
+ const hasPreset = modes.includes("Preset");
5577
+ const hasCompose = modes.includes("Compose");
5578
+ const banner = renderBanner({ id, modes, entry });
5579
+ const importLines = [
5580
+ `import * as React from 'react';`,
5581
+ `import { ${componentName} as Base${componentName}, type ${componentName}Props as Base${componentName}Props } from '${upstreamImport}';`
5582
+ ];
5583
+ if (hasCompose && entry.promotion?.featureVector?.structureDelta?.atomicChildren) {
5584
+ const children = entry.promotion.featureVector.structureDelta.atomicChildren.filter((c) => c !== componentName).slice(0, 4);
5585
+ for (const child of children) {
5586
+ importLines.push(
5587
+ `// TODO: confirm import path for atomic child <${child} />`
5588
+ );
5589
+ }
5590
+ }
5591
+ const body = [];
5592
+ body.push(
5593
+ `export interface ${componentName}Props extends Base${componentName}Props {`
5594
+ );
5595
+ if (hasWrapper) {
5596
+ body.push(
5597
+ ` // TODO(Wrapper): add business-only props (e.g. loading?: boolean, confirm?: () => void)`
5598
+ );
5599
+ }
5600
+ body.push(`}`);
5601
+ body.push("");
5602
+ if (hasVariant) {
5603
+ body.push(
5604
+ `// TODO(Variant): extend cva variants from upstream \u2014 when upstream`
5605
+ );
5606
+ body.push(
5607
+ `// exports \`${id}Variants\`, import + spread it here; when not, copy the`
5608
+ );
5609
+ body.push(
5610
+ `// configuration into this file and annotate \`SOURCE_OF_TRUTH\`.`
5611
+ );
5612
+ body.push("");
5613
+ }
5614
+ if (hasPreset) {
5615
+ body.push(
5616
+ `export const ${componentName}: React.FC<${componentName}Props> = ({`
5617
+ );
5618
+ body.push(
5619
+ ` // TODO(Preset): fill defaults migrated from your previous user version,`
5620
+ );
5621
+ body.push(
5622
+ ` // e.g. \`size = 'md'\`, \`variant = 'primary'\`.`
5623
+ );
5624
+ body.push(` ...props`);
5625
+ body.push(`}) => {`);
5626
+ } else {
5627
+ body.push(
5628
+ `export const ${componentName}: React.FC<${componentName}Props> = (props) => {`
5629
+ );
5630
+ }
5631
+ if (hasWrapper) {
5632
+ body.push(
5633
+ ` // TODO(Wrapper): pull business state / effects out of props before rendering.`
5634
+ );
5635
+ }
5636
+ if (hasCompose) {
5637
+ body.push(
5638
+ ` // TODO(Compose): assemble atomic children \u2014 ${entry.promotion?.featureVector?.structureDelta?.atomicChildren?.join(
5639
+ ", "
5640
+ ) ?? "..."}`
5641
+ );
5642
+ }
5643
+ body.push(` return <Base${componentName} {...props} />;`);
5644
+ body.push(`};`);
5645
+ return [banner, "", importLines.join("\n"), "", body.join("\n"), ""].join(
5646
+ "\n"
5647
+ );
5648
+ }
5649
+ function renderBanner(args) {
5650
+ const reasons = args.entry.promotion?.reasons ?? [];
5651
+ const lines = [
5652
+ `/**`,
5653
+ ` * Generated by \`teamix-evo ui promote-to-biz\` (Init landing plan \xA7C.3).`,
5654
+ ` *`,
5655
+ ` * id : ${args.id}`,
5656
+ ` * modes : ${args.modes.join(" + ")}`,
5657
+ ` * note : \u4E1A\u52A1\u5B9A\u5236\u5C42\uFF1B\u4E0A\u6E38 ui/${args.id}.tsx \u4ECD\u662F SOURCE_OF_TRUTH\uFF0C\u8BF7\u907F\u514D\u5728\u6B64\u518D\u6B21\u590D\u5236\u5176\u5185\u90E8\u7EC6\u8282\u3002`
5658
+ ];
5659
+ if (reasons.length > 0) {
5660
+ lines.push(" *");
5661
+ lines.push(" * \u63A8\u8350\u7406\u7531\uFF1A");
5662
+ for (const r of reasons) lines.push(` * \u2022 ${r}`);
5663
+ }
5664
+ lines.push(" */");
5665
+ return lines.join("\n");
5666
+ }
5667
+ async function findLatestUiStagingDir(projectRoot) {
5668
+ const base = path25.join(projectRoot, TEAMIX_DIR5, STAGING_DIR2);
5669
+ let entries;
5670
+ try {
5671
+ entries = await fs19.readdir(base, { withFileTypes: true });
5672
+ } catch (err) {
5673
+ if (err.code === "ENOENT") return null;
5674
+ throw err;
5675
+ }
5676
+ const candidates = entries.filter((e) => e.isDirectory() && e.name.startsWith("ui-")).map((e) => e.name).sort();
5677
+ const last = candidates[candidates.length - 1];
5678
+ return last ? path25.join(base, last) : null;
5679
+ }
5680
+ async function readStagingMeta(stagingDir) {
5681
+ const raw = await readFileOrNull(path25.join(stagingDir, "meta.json"));
5682
+ if (raw === null) return null;
5683
+ try {
5684
+ return JSON.parse(raw);
5685
+ } catch {
5686
+ return null;
5687
+ }
5688
+ }
5689
+ async function readStagedFile(stagingDir, entry, kind) {
5690
+ const ext = path25.extname(entry.current.target) || ".tsx";
5691
+ const direct = path25.join(stagingDir, entry.id, `${kind}${ext}`);
5692
+ const directRead = await readFileOrNull(direct);
5693
+ if (directRead !== null) return directRead;
5694
+ if (kind === "incoming" && entry.incoming?.relPath) {
5695
+ return readFileOrNull(path25.join(stagingDir, entry.incoming.relPath));
5696
+ }
5697
+ return null;
5698
+ }
5699
+ function decideModes(entry, override) {
5700
+ if (override && override.length > 0) return override;
5701
+ const recommended = entry.promotion?.recommendedModes;
5702
+ if (recommended && recommended.length > 0) return recommended;
5703
+ return ["ManualReview"];
5704
+ }
5705
+ async function mergeIntoInstalledManifest2(projectRoot, newResources) {
5706
+ const installed = await readInstalledManifest(
5707
+ projectRoot
5708
+ ) ?? { schemaVersion: 1, installed: [] };
5709
+ const idx = installed.installed.findIndex(
5710
+ (p2) => p2.package === "@teamix-evo/biz-ui-promoted"
5711
+ );
5712
+ const prior = idx >= 0 ? installed.installed[idx] : void 0;
5713
+ const merged = /* @__PURE__ */ new Map();
5714
+ for (const r of prior?.resources ?? []) merged.set(r.id, r);
5715
+ for (const r of newResources) merged.set(r.id, r);
5716
+ const next = {
5717
+ package: "@teamix-evo/biz-ui-promoted",
5718
+ variant: "_promoted",
5719
+ version: "0.0.0",
5720
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
5721
+ resources: Array.from(merged.values())
5722
+ };
5723
+ if (idx >= 0) installed.installed[idx] = next;
5724
+ else installed.installed.push(next);
5725
+ await writeInstalledManifest(projectRoot, installed);
5726
+ }
5727
+ function prefixExportsLegacy(source, id) {
5728
+ const stem = pascalCase(id);
5729
+ const stemRe = new RegExp(`\\b${stem}([A-Z]\\w*)?\\b`, "g");
5730
+ let out = source;
5731
+ out = out.replace(
5732
+ new RegExp(
5733
+ `(\\bexport\\s+(?:const|let|var|function|class|interface|type|enum)\\s+)(${stem}[A-Z]?\\w*)\\b`,
5734
+ "g"
5735
+ ),
5736
+ (_full, prefix, name) => `${prefix}Legacy${name}`
5737
+ );
5738
+ out = out.replace(/export\s*\{([^}]+)\}/g, (full, body) => {
5739
+ const replacedBody = body.replace(stemRe, (m) => `Legacy${m}`);
5740
+ return `export {${replacedBody}}`;
5741
+ });
5742
+ out = out.replace(
5743
+ new RegExp(`(\\bexport\\s+default\\s+)(${stem}[A-Z]?\\w*)(\\s*;)`, "g"),
5744
+ (_full, prefix, name, suffix) => `${prefix}Legacy${name}${suffix}`
5745
+ );
5746
+ out = out.replace(
5747
+ new RegExp(
5748
+ `(\\bexport\\s+default\\s+(?:function|class)\\s+)(${stem}[A-Z]?\\w*)\\b`,
5749
+ "g"
5750
+ ),
5751
+ (_full, prefix, name) => `${prefix}Legacy${name}`
5752
+ );
5753
+ return out;
5754
+ }
5755
+ function pascalCase(id) {
5756
+ return id.split(/[-_]/g).filter(Boolean).map((p2) => p2[0].toUpperCase() + p2.slice(1)).join("");
5757
+ }
5758
+ function importPathFor(aliasDir, id) {
5759
+ const trimmed = aliasDir.replace(/^\.\//, "").replace(/\/$/, "");
5760
+ const root = trimmed.startsWith("src/") ? `@/${trimmed.slice("src/".length)}` : `@/${trimmed}`;
5761
+ return `${root}/${id}`;
5762
+ }
5763
+ function posix2(p2) {
5764
+ return p2.split(path25.sep).join("/");
5765
+ }
5766
+
5767
+ // src/core/file-changes.ts
5768
+ import * as fs20 from "fs/promises";
5769
+ import * as path26 from "path";
5770
+ function toRelativePosix(p2, projectRoot) {
5771
+ let rel2 = p2;
5772
+ if (path26.isAbsolute(p2)) {
5773
+ rel2 = path26.relative(projectRoot, p2);
5774
+ }
5775
+ return rel2.split(path26.sep).join("/");
5776
+ }
5777
+ async function listBackupOriginals(projectRoot) {
5778
+ const backupsDir = path26.join(projectRoot, ".teamix-evo", ".backups");
5779
+ const out = /* @__PURE__ */ new Set();
5780
+ const stack = [backupsDir];
5781
+ while (stack.length > 0) {
5782
+ const dir = stack.pop();
5783
+ let entries;
5784
+ try {
5785
+ entries = await fs20.readdir(dir, { withFileTypes: true });
5786
+ } catch (err) {
5787
+ if (err.code === "ENOENT") continue;
5788
+ throw err;
5789
+ }
5790
+ for (const e of entries) {
5791
+ const full = path26.join(dir, e.name);
5792
+ if (e.isDirectory()) {
5793
+ stack.push(full);
5794
+ } else if (e.isFile() && e.name.endsWith(".bak")) {
5795
+ const rel2 = path26.relative(backupsDir, full);
5796
+ const original = stripBackupSuffix(rel2);
5797
+ if (original) out.add(original.split(path26.sep).join("/"));
5798
+ }
5799
+ }
5800
+ }
5801
+ return out;
5802
+ }
5803
+ function stripBackupSuffix(rel2) {
5804
+ const m = rel2.match(
5805
+ /^(.+)\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.bak$/
5806
+ );
5807
+ return m?.[1] ?? null;
5808
+ }
5809
+ function diffBackupSet(before, after) {
5810
+ const out = /* @__PURE__ */ new Set();
5811
+ for (const p2 of after) {
5812
+ if (!before.has(p2)) out.add(p2);
5813
+ }
5814
+ return out;
5815
+ }
5816
+ function formatFileChangeSummary(changes) {
5817
+ if (changes.length === 0) return [];
5818
+ const buckets = {
5819
+ created: /* @__PURE__ */ new Map(),
5820
+ modified: /* @__PURE__ */ new Map(),
5821
+ "backed-up": /* @__PURE__ */ new Map(),
5822
+ deleted: /* @__PURE__ */ new Map()
5823
+ };
5824
+ for (const c of changes) {
5825
+ const existing = buckets[c.kind].get(c.path);
5826
+ if (!existing) buckets[c.kind].set(c.path, c);
5827
+ }
5828
+ const order = [
5829
+ ["created", "\u{1F195} \u65B0\u5EFA"],
5830
+ ["modified", "\u270F\uFE0F \u4FEE\u6539"],
5831
+ ["backed-up", "\u{1F4BE} \u5DF2\u5907\u4EFD"],
5832
+ ["deleted", "\u{1F5D1} \u5220\u9664"]
5833
+ ];
5834
+ const lines = [];
5835
+ for (const [kind, label] of order) {
5836
+ const bucket = buckets[kind];
5837
+ if (bucket.size === 0) continue;
5838
+ lines.push(`${label}\uFF08${bucket.size}\uFF09\uFF1A`);
5839
+ const sorted = [...bucket.values()].sort(
5840
+ (a, b) => a.path.localeCompare(b.path)
5841
+ );
5842
+ for (const c of sorted) {
5843
+ const detail = c.detail ? ` \u2014 ${c.detail}` : "";
5844
+ lines.push(` \u2022 ${c.path} [${c.step}]${detail}`);
5845
+ }
5846
+ }
5847
+ return lines;
5848
+ }
5849
+
5850
+ // src/commands/ui/promote.ts
5851
+ var VALID_MODES = [
5852
+ "Coexist",
5853
+ "Preset",
5854
+ "Wrapper",
5855
+ "Compose",
5856
+ "Variant",
5857
+ "Fork",
5858
+ "TokenOnly",
5859
+ "ManualReview"
5860
+ ];
5861
+ var promoteCommand = new Command20("promote-to-biz").description(
5862
+ "\u5C06 staging \u4E2D\u7684\u7EC4\u4EF6\u6309 8 \u6A21\u5F0F promote \u5230 src/components/business/\uFF08Init \u843D\u5730\u8BA1\u5212 \xA7C.3\uFF09"
5863
+ ).argument("[ids...]", "\u53EF\u9009 entry id \u5217\u8868\uFF1B\u7701\u7565\u65F6\u5904\u7406 staging \u4E2D\u5168\u90E8\u6761\u76EE").option(
5864
+ "--modes <list>",
5865
+ '\u9017\u53F7\u5206\u9694\u7684\u6A21\u5F0F\u5217\u8868\uFF08\u8986\u76D6 staging \u63A8\u8350\uFF09\uFF0C\u5982 "Wrapper,Preset"'
5866
+ ).option(
5867
+ "--migrate-imports",
5868
+ "Coexist \u6A21\u5F0F\u65F6\u540C\u6B65\u91CD\u5199 src/** \u4E2D ui/<id> import \u4E3A legacy-<id>"
5869
+ ).option(
5870
+ "--keep-original-names",
5871
+ "Coexist \u6A21\u5F0F\u8DF3\u8FC7 Foo \u2192 LegacyFoo \u91CD\u547D\u540D\uFF08\u9ED8\u8BA4\u4F1A\u91CD\u547D\u540D\uFF09"
5872
+ ).option("--dry-run", "\u53EA\u8F93\u51FA\u53D8\u66F4\u8BA1\u5212\uFF0C\u4E0D\u5199\u5165\u4EFB\u4F55\u6587\u4EF6").option(
5873
+ "--staging-dir <path>",
5874
+ "\u6307\u5B9A staging \u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528 .teamix-evo/.upgrade-staging/ui-* \u4E2D\u6700\u65B0\u7684\uFF09"
5875
+ ).action(async (ids, opts) => {
5876
+ try {
5877
+ const ide = detectIde();
5878
+ const projectRoot = ide.getProjectRoot();
5879
+ const modesOverride = parseModes(opts.modes);
5880
+ const result = await runUiPromote({
5881
+ projectRoot,
5882
+ ids: ids ?? [],
5883
+ modesOverride,
5884
+ migrateImports: opts.migrateImports ?? false,
5885
+ keepOriginalNames: opts.keepOriginalNames ?? false,
5886
+ dryRun: opts.dryRun ?? false,
5887
+ stagingDir: opts.stagingDir
5888
+ });
5889
+ renderResult(result, opts.dryRun ?? false);
5890
+ } catch (err) {
5891
+ logger.error(`promote-to-biz failed: ${getErrorMessage(err)}`);
5892
+ logger.debug(err instanceof Error ? err.stack ?? "" : "");
5893
+ process.exitCode = 1;
5894
+ }
5895
+ });
5896
+ function parseModes(raw) {
5897
+ if (!raw || raw.trim().length === 0) return void 0;
5898
+ const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
5899
+ const out = [];
5900
+ for (const p2 of parts) {
5901
+ const match = VALID_MODES.find((m) => m.toLowerCase() === p2.toLowerCase());
5902
+ if (!match) {
5903
+ throw new Error(`Unknown mode "${p2}". Valid: ${VALID_MODES.join(", ")}`);
5904
+ }
5905
+ out.push(match);
5906
+ }
5907
+ return out;
5908
+ }
5909
+ function renderResult(result, dryRun) {
5910
+ switch (result.status) {
5911
+ case "not-initialized":
5912
+ logger.error("UI not initialized. Run `npx teamix-evo ui init` first.");
5913
+ process.exitCode = 1;
5914
+ return;
5915
+ case "no-staging":
4234
5916
  logger.error(
4235
- `Failed to build ${category} staging: ${getErrorMessage(err)}`
5917
+ "No `ui-*` staging dir found under `.teamix-evo/.upgrade-staging/`. Run `teamix-evo ui upgrade` first."
4236
5918
  );
4237
- logger.debug(err.stack ?? "");
4238
5919
  process.exitCode = 1;
5920
+ return;
5921
+ case "no-entries":
5922
+ logger.warn(`Staging dir is empty: ${result.stagingDir}`);
5923
+ return;
5924
+ case "promoted":
5925
+ break;
5926
+ }
5927
+ const promoted = result.promotions.filter((p2) => p2.status === "promoted");
5928
+ const manual = result.promotions.filter((p2) => p2.status === "manual-review");
5929
+ const tokenOnly = result.promotions.filter((p2) => p2.status === "token-only");
5930
+ const failed = result.promotions.filter((p2) => p2.status === "skipped");
5931
+ logger.success(
5932
+ `${dryRun ? "[dry-run] " : ""}promote-to-biz complete: ${promoted.length} promoted, ${manual.length} manual-review, ${tokenOnly.length} token-only, ${failed.length} skipped.`
5933
+ );
5934
+ logger.info("");
5935
+ if (promoted.length > 0) {
5936
+ logger.info("Promoted:");
5937
+ for (const p2 of promoted) {
5938
+ logger.info(` \u2022 ${p2.id} [${p2.modes.join("+")}]`);
5939
+ for (const out of p2.outputs) logger.info(` \u2192 ${out}`);
5940
+ if (p2.reason) logger.info(` ${p2.reason}`);
4239
5941
  }
4240
- });
5942
+ logger.info("");
5943
+ }
5944
+ if (manual.length > 0) {
5945
+ logger.info("Manual review needed (no file written):");
5946
+ for (const p2 of manual) logger.info(` \u2022 ${p2.id} \u2014 ${p2.reason ?? ""}`);
5947
+ logger.info("");
5948
+ }
5949
+ if (tokenOnly.length > 0) {
5950
+ logger.info("Token-only (edit tokens.overrides.css instead):");
5951
+ for (const p2 of tokenOnly) logger.info(` \u2022 ${p2.id}`);
5952
+ logger.info("");
5953
+ }
5954
+ if (failed.length > 0) {
5955
+ logger.warn("Failed:");
5956
+ for (const p2 of failed) logger.warn(` \u2022 ${p2.id} \u2014 ${p2.reason ?? ""}`);
5957
+ logger.info("");
5958
+ }
5959
+ if (result.importRewrite.rewritten.length > 0) {
5960
+ logger.info(
5961
+ `Import rewrite: ${result.importRewrite.rewritten.length} file(s) updated under src/**.`
5962
+ );
5963
+ }
5964
+ if (result.importRewrite.skipped.length > 0) {
5965
+ logger.warn("Manual import fix needed (could not be rewritten safely):");
5966
+ for (const s of result.importRewrite.skipped) {
5967
+ logger.warn(` \u2022 ${s.file}:${s.line} [${s.reason}] \u2014 ${s.snippet}`);
5968
+ }
5969
+ logger.info("");
5970
+ }
5971
+ if (result.fileChanges.length > 0) {
5972
+ logger.info("\u6587\u4EF6\u53D8\u66F4\uFF1A");
5973
+ for (const line of formatFileChangeSummary(result.fileChanges)) {
5974
+ logger.info(` ${line}`);
5975
+ }
5976
+ }
4241
5977
  }
4242
5978
 
4243
- // src/commands/ui/upgrade.ts
4244
- var upgradeCommand = makeUpgradeCommand("ui");
4245
-
4246
5979
  // src/commands/ui/index.ts
4247
- var uiCommand = new Command19("ui").description(
5980
+ var uiCommand = new Command21("ui").description(
4248
5981
  "\u7BA1\u7406 teamix-evo ui \u7EC4\u4EF6\uFF08\u6E90\u7801\u6CE8\u5165\u5F0F\u5B89\u88C5\uFF0Cshadcn \u98CE\u683C\uFF09"
4249
5982
  );
4250
5983
  uiCommand.addCommand(initCommand3);
4251
5984
  uiCommand.addCommand(addCommand2);
4252
5985
  uiCommand.addCommand(listCommand3);
4253
5986
  uiCommand.addCommand(upgradeCommand);
5987
+ uiCommand.addCommand(promoteCommand);
4254
5988
 
4255
5989
  // src/commands/biz-ui/index.ts
4256
- import { Command as Command23 } from "commander";
5990
+ import { Command as Command25 } from "commander";
4257
5991
 
4258
5992
  // src/commands/biz-ui/add.ts
4259
- import { Command as Command20 } from "commander";
5993
+ import { Command as Command22 } from "commander";
4260
5994
 
4261
5995
  // src/core/variant-ui-add.ts
4262
- import * as path22 from "path";
5996
+ import * as path27 from "path";
4263
5997
  import { createRequire as createRequire7 } from "module";
4264
5998
  import {
4265
5999
  loadUiPackageManifest as loadUiPackageManifest3,
@@ -4269,7 +6003,7 @@ import {
4269
6003
  var require7 = createRequire7(import.meta.url);
4270
6004
  function resolvePackageRoot4(packageName) {
4271
6005
  const pkgJsonPath = require7.resolve(`${packageName}/package.json`);
4272
- return path22.dirname(pkgJsonPath);
6006
+ return path27.dirname(pkgJsonPath);
4273
6007
  }
4274
6008
  async function runVariantUiAdd(packageName, options) {
4275
6009
  const { projectRoot, variant, ids, overwrite } = options;
@@ -4292,7 +6026,7 @@ async function runVariantUiAdd(packageName, options) {
4292
6026
  `Variant "${variant}" not found in ${fullPackageName}. Known variants: ${known}. Hint: \`teamix-evo ${packageName} list-variants\` shows all.`
4293
6027
  );
4294
6028
  }
4295
- const variantDir = path22.join(packageRoot, "variants", variant);
6029
+ const variantDir = path27.join(packageRoot, "variants", variant);
4296
6030
  const variantManifest = await loadVariantUiPackageManifest2(variantDir);
4297
6031
  const knownIds = new Set(variantManifest.entries.map((e) => e.id));
4298
6032
  const unknown = ids.filter((id) => !knownIds.has(id));
@@ -4339,7 +6073,7 @@ async function runVariantUiAdd(packageName, options) {
4339
6073
  (p2) => p2.package === fullPackageName && p2.variant === variant
4340
6074
  );
4341
6075
  const prior = idx >= 0 ? installed.installed[idx] : null;
4342
- const mergedResources = mergeResources2(
6076
+ const mergedResources = mergeResources3(
4343
6077
  prior?.resources ?? [],
4344
6078
  result.resources
4345
6079
  );
@@ -4363,7 +6097,7 @@ async function runVariantUiAdd(packageName, options) {
4363
6097
  resources: result.resources
4364
6098
  };
4365
6099
  }
4366
- function mergeResources2(prior, next) {
6100
+ function mergeResources3(prior, next) {
4367
6101
  const merged = /* @__PURE__ */ new Map();
4368
6102
  for (const r of prior) merged.set(r.id, r);
4369
6103
  for (const r of next) merged.set(r.id, r);
@@ -4405,7 +6139,7 @@ async function listVariantUiEntries(packageName, variant, packageRoot) {
4405
6139
  `Variant "${variant}" not found in ${fullPackageName}. Known: ${known}.`
4406
6140
  );
4407
6141
  }
4408
- const variantDir = path22.join(root, "variants", variant);
6142
+ const variantDir = path27.join(root, "variants", variant);
4409
6143
  const variantManifest = await loadVariantUiPackageManifest2(variantDir);
4410
6144
  return {
4411
6145
  packageName: fullPackageName,
@@ -4427,7 +6161,7 @@ async function listTemplatesEntries(variant, packageRoot) {
4427
6161
  }
4428
6162
 
4429
6163
  // src/commands/biz-ui/add.ts
4430
- var addCommand3 = new Command20("add").description(
6164
+ var addCommand3 = new Command22("add").description(
4431
6165
  "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A\u4E1A\u52A1 UI \u7EC4\u4EF6(\u6309 id,\u81EA\u52A8\u5C55\u5F00 ui \u5305\u7684 registryDependencies)"
4432
6166
  ).argument("<ids...>", '\u7EC4\u4EF6 id \u5217\u8868,\u5982 "tenant-switcher" "org-picker"').option("--variant <name>", '\u53D8\u4F53 id(\u5FC5\u586B,\u5982 "opentrek"\u3001"uni-manager")').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6").action(
4433
6167
  async (ids, opts) => {
@@ -4478,8 +6212,8 @@ var addCommand3 = new Command20("add").description(
4478
6212
  );
4479
6213
 
4480
6214
  // src/commands/biz-ui/list.ts
4481
- import { Command as Command21 } from "commander";
4482
- var listCommand4 = new Command21("list").description("\u5217\u51FA\u6307\u5B9A\u53D8\u4F53\u4E0B\u7684 biz-ui entries").requiredOption("--variant <name>", "\u53D8\u4F53\u540D\uFF08\u5982 opentrek / uni-manager\uFF09").action(async (opts) => {
6215
+ import { Command as Command23 } from "commander";
6216
+ var listCommand4 = new Command23("list").description("\u5217\u51FA\u6307\u5B9A\u53D8\u4F53\u4E0B\u7684 biz-ui entries").requiredOption("--variant <name>", "\u53D8\u4F53\u540D\uFF08\u5982 opentrek / uni-manager\uFF09").action(async (opts) => {
4483
6217
  try {
4484
6218
  const result = await listBizUiEntries(opts.variant);
4485
6219
  logger.info(`${result.packageName}#${result.variant} entries:`);
@@ -4504,8 +6238,8 @@ var listCommand4 = new Command21("list").description("\u5217\u51FA\u6307\u5B9A\u
4504
6238
  });
4505
6239
 
4506
6240
  // src/commands/biz-ui/list-variants.ts
4507
- import { Command as Command22 } from "commander";
4508
- var listVariantsCommand2 = new Command22("list-variants").description("\u5217\u51FA @teamix-evo/biz-ui \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u4E1A\u52A1\u53D8\u4F53").action(async () => {
6241
+ import { Command as Command24 } from "commander";
6242
+ var listVariantsCommand2 = new Command24("list-variants").description("\u5217\u51FA @teamix-evo/biz-ui \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u4E1A\u52A1\u53D8\u4F53").action(async () => {
4509
6243
  try {
4510
6244
  const result = await listBizUiVariants();
4511
6245
  logger.info(`Available biz-ui variants in ${result.packageName}:`);
@@ -4532,7 +6266,7 @@ var listVariantsCommand2 = new Command22("list-variants").description("\u5217\u5
4532
6266
  var upgradeCommand2 = makeUpgradeCommand("biz-ui");
4533
6267
 
4534
6268
  // src/commands/biz-ui/index.ts
4535
- var bizUiCommand = new Command23("biz-ui").description(
6269
+ var bizUiCommand = new Command25("biz-ui").description(
4536
6270
  "\u7BA1\u7406\u4E1A\u52A1 UI \u7EC4\u4EF6(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / templates \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
4537
6271
  );
4538
6272
  bizUiCommand.addCommand(addCommand3);
@@ -4541,11 +6275,11 @@ bizUiCommand.addCommand(listVariantsCommand2);
4541
6275
  bizUiCommand.addCommand(upgradeCommand2);
4542
6276
 
4543
6277
  // src/commands/templates/index.ts
4544
- import { Command as Command27 } from "commander";
6278
+ import { Command as Command29 } from "commander";
4545
6279
 
4546
6280
  // src/commands/templates/add.ts
4547
- import { Command as Command24 } from "commander";
4548
- var addCommand4 = new Command24("add").description(
6281
+ import { Command as Command26 } from "commander";
6282
+ var addCommand4 = new Command26("add").description(
4549
6283
  "\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A\u9875\u9762\u6A21\u677F(\u6309 id,\u81EA\u52A8\u5C55\u5F00 ui \u5305\u7684 registryDependencies)"
4550
6284
  ).argument("<ids...>", '\u6A21\u677F id \u5217\u8868,\u5982 "list-detail-page"').option("--variant <name>", '\u53D8\u4F53 id(\u5FC5\u586B,\u5982 "opentrek"\u3001"uni-manager")').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6").action(
4551
6285
  async (ids, opts) => {
@@ -4596,8 +6330,8 @@ var addCommand4 = new Command24("add").description(
4596
6330
  );
4597
6331
 
4598
6332
  // src/commands/templates/list.ts
4599
- import { Command as Command25 } from "commander";
4600
- var listCommand5 = new Command25("list").description("\u5217\u51FA\u6307\u5B9A\u53D8\u4F53\u4E0B\u7684 templates entries").requiredOption("--variant <name>", "\u53D8\u4F53\u540D\uFF08\u5982 opentrek / uni-manager\uFF09").action(async (opts) => {
6333
+ import { Command as Command27 } from "commander";
6334
+ var listCommand5 = new Command27("list").description("\u5217\u51FA\u6307\u5B9A\u53D8\u4F53\u4E0B\u7684 templates entries").requiredOption("--variant <name>", "\u53D8\u4F53\u540D\uFF08\u5982 opentrek / uni-manager\uFF09").action(async (opts) => {
4601
6335
  try {
4602
6336
  const result = await listTemplatesEntries(opts.variant);
4603
6337
  logger.info(`${result.packageName}#${result.variant} entries:`);
@@ -4622,8 +6356,8 @@ var listCommand5 = new Command25("list").description("\u5217\u51FA\u6307\u5B9A\u
4622
6356
  });
4623
6357
 
4624
6358
  // src/commands/templates/list-variants.ts
4625
- import { Command as Command26 } from "commander";
4626
- var listVariantsCommand3 = new Command26("list-variants").description("\u5217\u51FA @teamix-evo/templates \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u9875\u9762\u6A21\u677F\u53D8\u4F53").action(async () => {
6359
+ import { Command as Command28 } from "commander";
6360
+ var listVariantsCommand3 = new Command28("list-variants").description("\u5217\u51FA @teamix-evo/templates \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u9875\u9762\u6A21\u677F\u53D8\u4F53").action(async () => {
4627
6361
  try {
4628
6362
  const result = await listTemplatesVariants();
4629
6363
  logger.info(`Available templates variants in ${result.packageName}:`);
@@ -4647,7 +6381,7 @@ var listVariantsCommand3 = new Command26("list-variants").description("\u5217\u5
4647
6381
  });
4648
6382
 
4649
6383
  // src/commands/templates/index.ts
4650
- var templatesCommand = new Command27("templates").description(
6384
+ var templatesCommand = new Command29("templates").description(
4651
6385
  "\u7BA1\u7406\u9875\u9762\u6A21\u677F(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / biz-ui \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
4652
6386
  );
4653
6387
  templatesCommand.addCommand(addCommand4);
@@ -4655,16 +6389,16 @@ templatesCommand.addCommand(listCommand5);
4655
6389
  templatesCommand.addCommand(listVariantsCommand3);
4656
6390
 
4657
6391
  // src/commands/logs/index.ts
4658
- import { Command as Command30 } from "commander";
6392
+ import { Command as Command32 } from "commander";
4659
6393
 
4660
6394
  // src/commands/logs/analyze.ts
4661
- import { Command as Command28 } from "commander";
6395
+ import { Command as Command30 } from "commander";
4662
6396
  import { existsSync as existsSync2 } from "fs";
4663
- import { resolve as resolve4, join as join22 } from "path";
6397
+ import { resolve as resolve5, join as join27 } from "path";
4664
6398
 
4665
6399
  // src/commands/logs/_io.ts
4666
6400
  import { readFileSync, readdirSync, statSync } from "fs";
4667
- import { join as join21 } from "path";
6401
+ import { join as join26 } from "path";
4668
6402
  var DATE_DIR_RE = /^\d{4}-\d{2}-\d{2}$/;
4669
6403
  function parseIntOrUndef(v) {
4670
6404
  if (v === void 0) return void 0;
@@ -4676,7 +6410,7 @@ function readRecords(baseDir, dayLimit) {
4676
6410
  const selected = dayLimit !== void 0 ? dayDirs.slice(0, dayLimit) : dayDirs;
4677
6411
  const records = [];
4678
6412
  for (const day of selected) {
4679
- const dayPath = join21(baseDir, day);
6413
+ const dayPath = join26(baseDir, day);
4680
6414
  let entries;
4681
6415
  try {
4682
6416
  entries = readdirSync(dayPath);
@@ -4685,7 +6419,7 @@ function readRecords(baseDir, dayLimit) {
4685
6419
  }
4686
6420
  for (const entry of entries) {
4687
6421
  if (!entry.endsWith(".jsonl")) continue;
4688
- const fp = join21(dayPath, entry);
6422
+ const fp = join26(dayPath, entry);
4689
6423
  try {
4690
6424
  if (!statSync(fp).isFile()) continue;
4691
6425
  } catch {
@@ -4705,14 +6439,14 @@ function readRecords(baseDir, dayLimit) {
4705
6439
  }
4706
6440
 
4707
6441
  // src/commands/logs/analyze.ts
4708
- var logsAnalyzeCommand = new Command28("analyze").description(
6442
+ var logsAnalyzeCommand = new Command30("analyze").description(
4709
6443
  "\u6C47\u603B vibe-logger \u8F93\u51FA (.teamix-evo/logs/ai/**/*.jsonl) \u2014 \u5DE5\u5177 / \u5305\u6807\u7B7E / MCP \u8C03\u7528\u9891\u7387,\u8F85\u52A9\u751F\u6001\u4F18\u5316"
4710
6444
  ).option("--dir <path>", "log \u76EE\u5F55 (\u9ED8\u8BA4 <project>/.teamix-evo/logs/ai)").option(
4711
6445
  "--days <n>",
4712
6446
  "\u53EA\u770B\u6700\u8FD1 N \u5929\u7684\u76EE\u5F55 (\u9ED8\u8BA4\u5168\u90E8;\u6309\u76EE\u5F55\u540D YYYY-MM-DD \u6BD4\u5BF9,\u4E0D\u89E3\u6790\u8BB0\u5F55 ts)"
4713
6447
  ).option("--top <n>", "\u6BCF\u4E2A\u6392\u884C\u5C55\u793A\u524D N \u9879 (\u9ED8\u8BA4 10)", "10").option("--json", "\u4EE5 JSON \u8F93\u51FA (CI/\u5DE5\u5177\u53CB\u597D)").action((opts) => {
4714
- const baseDir = resolve4(
4715
- opts.dir ?? join22(process.cwd(), ".teamix-evo", "logs", "ai")
6448
+ const baseDir = resolve5(
6449
+ opts.dir ?? join27(process.cwd(), ".teamix-evo", "logs", "ai")
4716
6450
  );
4717
6451
  if (!existsSync2(baseDir)) {
4718
6452
  logger.warn(`No log directory at ${baseDir}.`);
@@ -4854,14 +6588,14 @@ function pad(n, width) {
4854
6588
  }
4855
6589
 
4856
6590
  // src/commands/logs/trace.ts
4857
- import { Command as Command29 } from "commander";
6591
+ import { Command as Command31 } from "commander";
4858
6592
  import { existsSync as existsSync3 } from "fs";
4859
- import { resolve as resolve5, join as join23 } from "path";
4860
- var logsTraceCommand = new Command29("trace").description(
6593
+ import { resolve as resolve6, join as join28 } from "path";
6594
+ var logsTraceCommand = new Command31("trace").description(
4861
6595
  "\u6309\u4F1A\u8BDD\u8FD8\u539F AI \u8C03\u7528\u94FE\u8DEF:\u4ECE\u7528\u6237 prompt \u8D77\u59CB,\u4E32\u8054\u540E\u7EED PreToolUse/PostToolUse \u76F4\u5230\u4E0B\u4E00\u4E2A prompt \u6216 Stop"
4862
6596
  ).option("--prompt <keyword>", "\u6309\u7528\u6237\u8F93\u5165\u5173\u952E\u5B57\u8FC7\u6EE4 (\u5B50\u4E32\u5339\u914D,\u4E0D\u533A\u5206\u5927\u5C0F\u5199)").option("--session <id>", "\u6307\u5B9A\u4F1A\u8BDD ID (\u524D\u7F00\u5339\u914D)").option("--days <n>", "\u53EA\u770B\u6700\u8FD1 N \u5929\u7684\u76EE\u5F55 (\u9ED8\u8BA4 7)", "7").option("--dir <path>", "log \u76EE\u5F55 (\u9ED8\u8BA4 <project>/.teamix-evo/logs/ai)").option("--json", "\u4EE5 JSON \u8F93\u51FA (CI/\u5DE5\u5177\u53CB\u597D)").action((opts) => {
4863
- const baseDir = resolve5(
4864
- opts.dir ?? join23(process.cwd(), ".teamix-evo", "logs", "ai")
6597
+ const baseDir = resolve6(
6598
+ opts.dir ?? join28(process.cwd(), ".teamix-evo", "logs", "ai")
4865
6599
  );
4866
6600
  if (!existsSync3(baseDir)) {
4867
6601
  logger.warn(`No log directory at ${baseDir}.`);
@@ -5030,22 +6764,22 @@ function padLeft(s, n) {
5030
6764
  }
5031
6765
 
5032
6766
  // src/commands/logs/index.ts
5033
- var logsCommand = new Command30("logs").description(
6767
+ var logsCommand = new Command32("logs").description(
5034
6768
  "\u67E5\u8BE2 vibe-logger \u8F93\u51FA (.teamix-evo/logs/ai/**/*.jsonl) \u2014 AI \u8C03\u7528\u94FE\u5206\u6790"
5035
6769
  );
5036
6770
  logsCommand.addCommand(logsAnalyzeCommand);
5037
6771
  logsCommand.addCommand(logsTraceCommand);
5038
6772
 
5039
6773
  // src/commands/lint/index.ts
5040
- import { Command as Command32 } from "commander";
6774
+ import { Command as Command34 } from "commander";
5041
6775
 
5042
6776
  // src/commands/lint/init.ts
5043
- import { Command as Command31 } from "commander";
6777
+ import { Command as Command33 } from "commander";
5044
6778
  import * as prompts6 from "@clack/prompts";
5045
6779
 
5046
6780
  // src/core/lint-init.ts
5047
- import * as path23 from "path";
5048
- import * as fs16 from "fs";
6781
+ import * as path28 from "path";
6782
+ import * as fs21 from "fs";
5049
6783
  import { execa } from "execa";
5050
6784
  var ESLINT_CONFIG_CONTENT = `/**
5051
6785
  * teamix-evo consumer ESLint preset \u2014 9 token-discipline rules.
@@ -5072,18 +6806,29 @@ var ESLINT_DEPS = [
5072
6806
  ];
5073
6807
  var STYLELINT_DEPS = ["@teamix-evo/stylelint-config", "stylelint"];
5074
6808
  async function runLintInit(options) {
5075
- const { projectRoot, skipInstall } = options;
5076
- const eslintConfigPath = path23.join(projectRoot, "eslint.config.js");
5077
- const stylelintConfigPath = path23.join(projectRoot, "stylelint.config.cjs");
5078
- const eslintExists = await fileExists(eslintConfigPath);
5079
- const stylelintExists = await fileExists(stylelintConfigPath);
5080
- if (eslintExists && stylelintExists) {
6809
+ const {
6810
+ projectRoot,
6811
+ skipInstall,
6812
+ eslintStrategy = "overwrite",
6813
+ stylelintStrategy = "overwrite",
6814
+ eslintExistingPaths = [],
6815
+ stylelintExistingPaths = []
6816
+ } = options;
6817
+ const eslintConfigPath = path28.join(projectRoot, "eslint.config.js");
6818
+ const stylelintConfigPath = path28.join(projectRoot, "stylelint.config.cjs");
6819
+ const eslintTemplateExists = await fileExists(eslintConfigPath);
6820
+ const stylelintTemplateExists = await fileExists(stylelintConfigPath);
6821
+ const eslintSkipRequested = eslintStrategy === "skip" && eslintExistingPaths.length > 0;
6822
+ const stylelintSkipRequested = stylelintStrategy === "skip" && stylelintExistingPaths.length > 0;
6823
+ const eslintNeedsWrite = !eslintTemplateExists && !eslintSkipRequested;
6824
+ const stylelintNeedsWrite = !stylelintTemplateExists && !stylelintSkipRequested;
6825
+ if (!eslintNeedsWrite && !stylelintNeedsWrite) {
5081
6826
  return { status: "already-initialized" };
5082
6827
  }
5083
6828
  if (!skipInstall) {
5084
6829
  const depsToInstall = [
5085
- ...eslintExists ? [] : ESLINT_DEPS,
5086
- ...stylelintExists ? [] : STYLELINT_DEPS
6830
+ ...eslintNeedsWrite ? ESLINT_DEPS : [],
6831
+ ...stylelintNeedsWrite ? STYLELINT_DEPS : []
5087
6832
  ];
5088
6833
  if (depsToInstall.length > 0) {
5089
6834
  const pm = detectPm(projectRoot);
@@ -5092,39 +6837,54 @@ async function runLintInit(options) {
5092
6837
  await execa(pm, args, { cwd: projectRoot, stdio: "inherit" });
5093
6838
  }
5094
6839
  }
6840
+ if (eslintNeedsWrite && eslintExistingPaths.length > 0) {
6841
+ for (const rel2 of eslintExistingPaths) {
6842
+ await backupFile(path28.join(projectRoot, rel2), projectRoot);
6843
+ }
6844
+ }
6845
+ if (stylelintNeedsWrite && stylelintExistingPaths.length > 0) {
6846
+ for (const rel2 of stylelintExistingPaths) {
6847
+ await backupFile(path28.join(projectRoot, rel2), projectRoot);
6848
+ }
6849
+ }
5095
6850
  let wroteEslint = false;
5096
6851
  let wroteStylelint = false;
5097
- if (!eslintExists) {
6852
+ if (eslintNeedsWrite) {
5098
6853
  await writeFileSafe(eslintConfigPath, ESLINT_CONFIG_CONTENT);
5099
6854
  logger.debug(`Wrote eslint.config.js \u2192 ${eslintConfigPath}`);
5100
6855
  wroteEslint = true;
5101
6856
  }
5102
- if (!stylelintExists) {
6857
+ if (stylelintNeedsWrite) {
5103
6858
  await writeFileSafe(stylelintConfigPath, STYLELINT_CONFIG_CONTENT);
5104
6859
  logger.debug(`Wrote stylelint.config.cjs \u2192 ${stylelintConfigPath}`);
5105
6860
  wroteStylelint = true;
5106
6861
  }
5107
- await patchPackageJsonScripts(projectRoot);
6862
+ const packageJsonPatched = await patchPackageJsonScripts(projectRoot);
5108
6863
  return {
5109
6864
  status: "installed",
5110
6865
  eslint: wroteEslint,
5111
- stylelint: wroteStylelint
6866
+ stylelint: wroteStylelint,
6867
+ eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
6868
+ stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
6869
+ eslintSkipped: eslintSkipRequested,
6870
+ stylelintSkipped: stylelintSkipRequested,
6871
+ packageJsonPatched
5112
6872
  };
5113
6873
  }
5114
6874
  function detectPm(projectRoot) {
5115
- if (fs16.existsSync(path23.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
5116
- if (fs16.existsSync(path23.join(projectRoot, "yarn.lock"))) return "yarn";
6875
+ if (fs21.existsSync(path28.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
6876
+ if (fs21.existsSync(path28.join(projectRoot, "yarn.lock"))) return "yarn";
5117
6877
  return "npm";
5118
6878
  }
5119
6879
  async function patchPackageJsonScripts(projectRoot) {
5120
- const pkgPath = path23.join(projectRoot, "package.json");
6880
+ const pkgPath = path28.join(projectRoot, "package.json");
5121
6881
  const raw = await readFileOrNull(pkgPath);
5122
- if (!raw) return;
6882
+ if (!raw) return false;
5123
6883
  let pkg;
5124
6884
  try {
5125
6885
  pkg = JSON.parse(raw);
5126
6886
  } catch {
5127
- return;
6887
+ return false;
5128
6888
  }
5129
6889
  const scripts = pkg.scripts ?? {};
5130
6890
  let changed = false;
@@ -5137,14 +6897,16 @@ async function patchPackageJsonScripts(projectRoot) {
5137
6897
  changed = true;
5138
6898
  }
5139
6899
  if (changed) {
6900
+ await backupFile(pkgPath, projectRoot);
5140
6901
  pkg.scripts = scripts;
5141
6902
  await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
5142
6903
  logger.debug("Patched package.json scripts with lint / lint:css");
5143
6904
  }
6905
+ return changed;
5144
6906
  }
5145
6907
 
5146
6908
  // src/commands/lint/init.ts
5147
- var initCommand4 = new Command31("init").description(
6909
+ var initCommand4 = new Command33("init").description(
5148
6910
  "\u521D\u59CB\u5316 ESLint + Stylelint \u5DE5\u7A0B\u89C4\u8303\uFF08\u5B89\u88C5\u4F9D\u8D56 + \u751F\u6210\u914D\u7F6E\u6587\u4EF6 + \u6CE8\u5165 scripts\uFF09"
5149
6911
  ).option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4\uFF0C\u76F4\u63A5\u6267\u884C").action(async (opts) => {
5150
6912
  try {
@@ -5192,18 +6954,18 @@ var initCommand4 = new Command31("init").description(
5192
6954
  });
5193
6955
 
5194
6956
  // src/commands/lint/index.ts
5195
- var lintCommand = new Command32("lint").description(
6957
+ var lintCommand = new Command34("lint").description(
5196
6958
  "\u7BA1\u7406\u5DE5\u7A0B\u89C4\u8303\uFF08ESLint + Stylelint token-discipline \u89C4\u5219\u96C6\uFF09"
5197
6959
  );
5198
6960
  lintCommand.addCommand(initCommand4);
5199
6961
 
5200
6962
  // src/commands/init/index.ts
5201
- import { Command as Command33 } from "commander";
5202
- import * as path29 from "path";
6963
+ import { Command as Command35 } from "commander";
6964
+ import * as path35 from "path";
5203
6965
 
5204
6966
  // src/core/init-detect.ts
5205
- import * as fs17 from "fs/promises";
5206
- import * as path24 from "path";
6967
+ import * as fs22 from "fs/promises";
6968
+ import * as path29 from "path";
5207
6969
  var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
5208
6970
  ".git",
5209
6971
  ".gitignore",
@@ -5223,7 +6985,7 @@ var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
5223
6985
  "LICENSE.txt"
5224
6986
  ]);
5225
6987
  async function detectProjectState(cwd) {
5226
- const absCwd = path24.resolve(cwd);
6988
+ const absCwd = path29.resolve(cwd);
5227
6989
  const teamixDir = getTeamixDir(absCwd);
5228
6990
  const hasTeamixDir = await fileExists(teamixDir);
5229
6991
  if (hasTeamixDir) {
@@ -5231,13 +6993,13 @@ async function detectProjectState(cwd) {
5231
6993
  state: "teamix-evo-installed",
5232
6994
  cwd: absCwd,
5233
6995
  hasTeamixDir: true,
5234
- hasPackageJson: await fileExists(path24.join(absCwd, "package.json")),
6996
+ hasPackageJson: await fileExists(path29.join(absCwd, "package.json")),
5235
6997
  significantEntries: []
5236
6998
  };
5237
6999
  }
5238
7000
  let entries;
5239
7001
  try {
5240
- entries = await fs17.readdir(absCwd);
7002
+ entries = await fs22.readdir(absCwd);
5241
7003
  } catch (err) {
5242
7004
  if (err.code === "ENOENT") {
5243
7005
  return {
@@ -5272,8 +7034,8 @@ async function detectProjectState(cwd) {
5272
7034
 
5273
7035
  // src/core/init-conflicts.ts
5274
7036
  import * as crypto from "crypto";
5275
- import * as fs18 from "fs/promises";
5276
- import * as path25 from "path";
7037
+ import * as fs23 from "fs/promises";
7038
+ import * as path30 from "path";
5277
7039
  var TAILWIND_CONFIG_CANDIDATES = [
5278
7040
  "tailwind.config.ts",
5279
7041
  "tailwind.config.js",
@@ -5295,9 +7057,29 @@ var INDEX_CSS_CANDIDATES = [
5295
7057
  ];
5296
7058
  var SHADCN_FILE_CANDIDATES = ["src/lib/utils.ts"];
5297
7059
  var SHADCN_DIR_CANDIDATES = ["src/components/ui"];
7060
+ var ESLINT_CONFIG_CANDIDATES = [
7061
+ ".eslintrc.cjs",
7062
+ ".eslintrc.js",
7063
+ ".eslintrc.json",
7064
+ ".eslintrc.yml",
7065
+ "eslint.config.js",
7066
+ "eslint.config.cjs",
7067
+ "eslint.config.mjs",
7068
+ "eslint.config.ts"
7069
+ ];
7070
+ var STYLELINT_CONFIG_CANDIDATES = [
7071
+ ".stylelintrc.cjs",
7072
+ ".stylelintrc.js",
7073
+ ".stylelintrc.json",
7074
+ ".stylelintrc.yml",
7075
+ "stylelint.config.cjs",
7076
+ "stylelint.config.js",
7077
+ "stylelint.config.mjs",
7078
+ "stylelint.config.ts"
7079
+ ];
5298
7080
  async function isDir(target) {
5299
7081
  try {
5300
- const stat7 = await fs18.stat(target);
7082
+ const stat7 = await fs23.stat(target);
5301
7083
  return stat7.isDirectory();
5302
7084
  } catch {
5303
7085
  return false;
@@ -5305,7 +7087,7 @@ async function isDir(target) {
5305
7087
  }
5306
7088
  async function dirHasContent(target) {
5307
7089
  try {
5308
- const entries = await fs18.readdir(target);
7090
+ const entries = await fs23.readdir(target);
5309
7091
  return entries.length > 0;
5310
7092
  } catch {
5311
7093
  return false;
@@ -5317,7 +7099,7 @@ function fingerprint(parts) {
5317
7099
  return `sha256:${hash.digest("hex").slice(0, 16)}`;
5318
7100
  }
5319
7101
  async function readPackageJson(cwd) {
5320
- const raw = await readFileOrNull(path25.join(cwd, "package.json"));
7102
+ const raw = await readFileOrNull(path30.join(cwd, "package.json"));
5321
7103
  if (raw === null) return null;
5322
7104
  try {
5323
7105
  return JSON.parse(raw);
@@ -5337,7 +7119,7 @@ function detectTailwindMajor(pkg) {
5337
7119
  return null;
5338
7120
  }
5339
7121
  async function detectAgentsMd(cwd) {
5340
- const target = path25.join(cwd, "AGENTS.md");
7122
+ const target = path30.join(cwd, "AGENTS.md");
5341
7123
  const content = await readFileOrNull(target);
5342
7124
  const exists = content !== null;
5343
7125
  return {
@@ -5350,7 +7132,7 @@ async function detectAgentsMd(cwd) {
5350
7132
  };
5351
7133
  }
5352
7134
  async function detectComponentsJson(cwd) {
5353
- const target = path25.join(cwd, "components.json");
7135
+ const target = path30.join(cwd, "components.json");
5354
7136
  const content = await readFileOrNull(target);
5355
7137
  const exists = content !== null;
5356
7138
  return {
@@ -5366,7 +7148,7 @@ async function detectTailwindConfig(cwd) {
5366
7148
  const matched = [];
5367
7149
  const contents = [];
5368
7150
  for (const rel2 of TAILWIND_CONFIG_CANDIDATES) {
5369
- const c = await readFileOrNull(path25.join(cwd, rel2));
7151
+ const c = await readFileOrNull(path30.join(cwd, rel2));
5370
7152
  if (c !== null) {
5371
7153
  matched.push(rel2);
5372
7154
  contents.push(c);
@@ -5389,14 +7171,14 @@ async function detectTokens(cwd) {
5389
7171
  const matched = [];
5390
7172
  const contents = [];
5391
7173
  for (const rel2 of TOKENS_FILE_CANDIDATES) {
5392
- const c = await readFileOrNull(path25.join(cwd, rel2));
7174
+ const c = await readFileOrNull(path30.join(cwd, rel2));
5393
7175
  if (c !== null) {
5394
7176
  matched.push(rel2);
5395
7177
  contents.push(c);
5396
7178
  }
5397
7179
  }
5398
7180
  for (const rel2 of TOKENS_DIR_CANDIDATES) {
5399
- const abs = path25.join(cwd, rel2);
7181
+ const abs = path30.join(cwd, rel2);
5400
7182
  if (await isDir(abs) && await dirHasContent(abs)) {
5401
7183
  matched.push(`${rel2}/`);
5402
7184
  }
@@ -5415,7 +7197,7 @@ async function detectIndexCss(cwd) {
5415
7197
  const matched = [];
5416
7198
  const contents = [];
5417
7199
  for (const rel2 of INDEX_CSS_CANDIDATES) {
5418
- const c = await readFileOrNull(path25.join(cwd, rel2));
7200
+ const c = await readFileOrNull(path30.join(cwd, rel2));
5419
7201
  if (c !== null) {
5420
7202
  matched.push(rel2);
5421
7203
  contents.push(c);
@@ -5434,19 +7216,19 @@ async function detectIndexCss(cwd) {
5434
7216
  async function detectShadcnSource(cwd) {
5435
7217
  const matched = [];
5436
7218
  for (const rel2 of SHADCN_FILE_CANDIDATES) {
5437
- if (await fileExists(path25.join(cwd, rel2))) matched.push(rel2);
7219
+ if (await fileExists(path30.join(cwd, rel2))) matched.push(rel2);
5438
7220
  }
5439
7221
  for (const rel2 of SHADCN_DIR_CANDIDATES) {
5440
- const abs = path25.join(cwd, rel2);
7222
+ const abs = path30.join(cwd, rel2);
5441
7223
  if (await isDir(abs) && await dirHasContent(abs)) {
5442
7224
  matched.push(`${rel2}/`);
5443
7225
  }
5444
7226
  }
5445
7227
  let componentCount = 0;
5446
7228
  try {
5447
- const uiDir = path25.join(cwd, "src/components/ui");
7229
+ const uiDir = path30.join(cwd, "src/components/ui");
5448
7230
  if (await isDir(uiDir)) {
5449
- const entries = await fs18.readdir(uiDir);
7231
+ const entries = await fs23.readdir(uiDir);
5450
7232
  componentCount = entries.filter(
5451
7233
  (e) => e.endsWith(".tsx") || e.endsWith(".ts")
5452
7234
  ).length;
@@ -5464,14 +7246,16 @@ async function detectShadcnSource(cwd) {
5464
7246
  };
5465
7247
  }
5466
7248
  async function detectConflicts(cwd) {
5467
- const absCwd = path25.resolve(cwd);
7249
+ const absCwd = path30.resolve(cwd);
5468
7250
  const items = await Promise.all([
5469
7251
  detectAgentsMd(absCwd),
5470
7252
  detectComponentsJson(absCwd),
5471
7253
  detectTailwindConfig(absCwd),
5472
7254
  detectTokens(absCwd),
5473
7255
  detectIndexCss(absCwd),
5474
- detectShadcnSource(absCwd)
7256
+ detectShadcnSource(absCwd),
7257
+ detectEslintConfig(absCwd),
7258
+ detectStylelintConfig(absCwd)
5475
7259
  ]);
5476
7260
  return {
5477
7261
  cwd: absCwd,
@@ -5479,10 +7263,50 @@ async function detectConflicts(cwd) {
5479
7263
  hasAnyConflict: items.some((i) => i.exists)
5480
7264
  };
5481
7265
  }
7266
+ async function detectEslintConfig(cwd) {
7267
+ const matched = [];
7268
+ const contents = [];
7269
+ for (const rel2 of ESLINT_CONFIG_CANDIDATES) {
7270
+ const c = await readFileOrNull(path30.join(cwd, rel2));
7271
+ if (c !== null) {
7272
+ matched.push(rel2);
7273
+ contents.push(c);
7274
+ }
7275
+ }
7276
+ const exists = matched.length > 0;
7277
+ return {
7278
+ key: "eslint-config",
7279
+ exists,
7280
+ paths: matched,
7281
+ fingerprint: exists ? fingerprint(contents) : void 0,
7282
+ recommendedStrategy: exists ? "merge" : "overwrite",
7283
+ availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
7284
+ };
7285
+ }
7286
+ async function detectStylelintConfig(cwd) {
7287
+ const matched = [];
7288
+ const contents = [];
7289
+ for (const rel2 of STYLELINT_CONFIG_CANDIDATES) {
7290
+ const c = await readFileOrNull(path30.join(cwd, rel2));
7291
+ if (c !== null) {
7292
+ matched.push(rel2);
7293
+ contents.push(c);
7294
+ }
7295
+ }
7296
+ const exists = matched.length > 0;
7297
+ return {
7298
+ key: "stylelint-config",
7299
+ exists,
7300
+ paths: matched,
7301
+ fingerprint: exists ? fingerprint(contents) : void 0,
7302
+ recommendedStrategy: exists ? "merge" : "overwrite",
7303
+ availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
7304
+ };
7305
+ }
5482
7306
 
5483
7307
  // src/core/legacy-tokens-migrate.ts
5484
- import * as path26 from "path";
5485
- import * as fs19 from "fs/promises";
7308
+ import * as path31 from "path";
7309
+ import * as fs24 from "fs/promises";
5486
7310
  var CONSUMER_TOKENS_DIR2 = "tokens";
5487
7311
  var CONSUMER_OVERRIDES_FILE2 = "tokens.overrides.css";
5488
7312
  var EMPTY_OVERRIDES_TEMPLATE2 = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
@@ -5494,7 +7318,7 @@ function buildMigrationBanner(legacyRel, isoTimestamp) {
5494
7318
  }
5495
7319
  function computeBackupRel(legacyRel, isoTimestamp) {
5496
7320
  const tsSafe = isoTimestamp.replace(/[:.]/g, "-");
5497
- return path26.posix.join(
7321
+ return path31.posix.join(
5498
7322
  ".teamix-evo",
5499
7323
  ".backups",
5500
7324
  `${legacyRel}.${tsSafe}.bak`
@@ -5505,17 +7329,17 @@ async function migrateLegacyTokens(options) {
5505
7329
  const seen = /* @__PURE__ */ new Set();
5506
7330
  const uniqueLegacy = [];
5507
7331
  for (const raw of legacyPaths) {
5508
- const norm = raw.split(path26.sep).join("/");
7332
+ const norm = raw.split(path31.sep).join("/");
5509
7333
  if (!seen.has(norm)) {
5510
7334
  seen.add(norm);
5511
7335
  uniqueLegacy.push(norm);
5512
7336
  }
5513
7337
  }
5514
- const overridesRel = path26.posix.join(
7338
+ const overridesRel = path31.posix.join(
5515
7339
  CONSUMER_TOKENS_DIR2,
5516
7340
  CONSUMER_OVERRIDES_FILE2
5517
7341
  );
5518
- const overridesAbs = path26.join(projectRoot, overridesRel);
7342
+ const overridesAbs = path31.join(projectRoot, overridesRel);
5519
7343
  const existingOverrides = await readFileOrNull(overridesAbs);
5520
7344
  const overridesCreated = existingOverrides === null;
5521
7345
  const baseContent = existingOverrides === null ? EMPTY_OVERRIDES_TEMPLATE2 : existingOverrides;
@@ -5524,7 +7348,7 @@ async function migrateLegacyTokens(options) {
5524
7348
  const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString();
5525
7349
  let appendedPayload = "";
5526
7350
  for (const legacyRel of uniqueLegacy) {
5527
- const legacyAbs = path26.join(projectRoot, legacyRel);
7351
+ const legacyAbs = path31.join(projectRoot, legacyRel);
5528
7352
  const content = await readFileOrNull(legacyAbs);
5529
7353
  if (content === null) {
5530
7354
  skipped.push({ from: legacyRel, reason: "not-found" });
@@ -5554,15 +7378,15 @@ async function migrateLegacyTokens(options) {
5554
7378
  dryRun
5555
7379
  };
5556
7380
  }
5557
- await ensureDir(path26.dirname(overridesAbs));
7381
+ await ensureDir(path31.dirname(overridesAbs));
5558
7382
  const merged = baseContent + appendedPayload;
5559
7383
  const tmp = overridesAbs + ".tmp";
5560
- await fs19.writeFile(tmp, merged, "utf-8");
5561
- await fs19.rename(tmp, overridesAbs);
7384
+ await fs24.writeFile(tmp, merged, "utf-8");
7385
+ await fs24.rename(tmp, overridesAbs);
5562
7386
  for (const entry of migrated) {
5563
- const legacyAbs = path26.join(projectRoot, entry.from);
5564
- const backupAbs = path26.join(projectRoot, entry.backupTo);
5565
- await ensureDir(path26.dirname(backupAbs));
7387
+ const legacyAbs = path31.join(projectRoot, entry.from);
7388
+ const backupAbs = path31.join(projectRoot, entry.backupTo);
7389
+ await ensureDir(path31.dirname(backupAbs));
5566
7390
  const srcContent = await readFileOrNull(legacyAbs);
5567
7391
  if (srcContent === null) {
5568
7392
  logger.debug(
@@ -5570,9 +7394,9 @@ async function migrateLegacyTokens(options) {
5570
7394
  );
5571
7395
  continue;
5572
7396
  }
5573
- await fs19.writeFile(backupAbs, srcContent, "utf-8");
7397
+ await fs24.writeFile(backupAbs, srcContent, "utf-8");
5574
7398
  if (await fileExists(legacyAbs)) {
5575
- await fs19.unlink(legacyAbs);
7399
+ await fs24.unlink(legacyAbs);
5576
7400
  }
5577
7401
  logger.debug(
5578
7402
  `legacy-tokens-migrate: ${entry.from} \u2192 ${overridesRel} (backup: ${entry.backupTo})`
@@ -5588,10 +7412,13 @@ async function migrateLegacyTokens(options) {
5588
7412
  }
5589
7413
 
5590
7414
  // src/core/agents-md.ts
5591
- import * as fs20 from "fs/promises";
5592
- import * as path27 from "path";
7415
+ import * as fs25 from "fs/promises";
7416
+ import * as path32 from "path";
7417
+ import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
7418
+ var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
5593
7419
  async function runGenerateAgentsMd(options) {
5594
7420
  const { projectRoot, variant, skillIds } = options;
7421
+ const mode = options.mode ?? "overwrite";
5595
7422
  const ordered = [...skillIds].sort(
5596
7423
  (a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
5597
7424
  );
@@ -5602,13 +7429,47 @@ async function runGenerateAgentsMd(options) {
5602
7429
  sections.push(section);
5603
7430
  if (missing) missingSkillIds.push(id);
5604
7431
  }
5605
- const body = renderAgentsMd({ variant, sections });
5606
- const target = path27.join(projectRoot, "AGENTS.md");
5607
- await fs20.writeFile(target, body, "utf8");
7432
+ const target = path32.join(projectRoot, "AGENTS.md");
7433
+ const targetExists = await fileExists(target);
7434
+ const fullTemplate = renderAgentsMd({ variant, sections });
7435
+ const managedBody = renderManagedBlockBody({ variant, sections });
7436
+ let outputContent;
7437
+ let merge;
7438
+ if (!targetExists) {
7439
+ outputContent = fullTemplate;
7440
+ merge = "created";
7441
+ } else {
7442
+ await backupFile(target, projectRoot);
7443
+ if (mode === "merge-managed") {
7444
+ const existing = await readFileOrNull(target) ?? "";
7445
+ if (hasManagedRegion3(existing, AGENTS_MD_MANAGED_ID)) {
7446
+ outputContent = replaceManagedRegion3(
7447
+ existing,
7448
+ AGENTS_MD_MANAGED_ID,
7449
+ managedBody
7450
+ );
7451
+ merge = "managed-replaced";
7452
+ } else {
7453
+ const wrapped = wrapManagedBlock(managedBody);
7454
+ outputContent = `${wrapped}
7455
+
7456
+ ${PRECEDENCE_NOTICE}
7457
+
7458
+ ${existing.trimStart()}`;
7459
+ merge = "managed-prepended";
7460
+ }
7461
+ } else {
7462
+ outputContent = fullTemplate;
7463
+ merge = "overwritten";
7464
+ }
7465
+ }
7466
+ await fs25.writeFile(target, outputContent, "utf8");
5608
7467
  return {
5609
7468
  path: target,
5610
7469
  skillCount: ordered.length,
5611
- missingSkillIds
7470
+ missingSkillIds,
7471
+ backedUp: targetExists,
7472
+ merge
5612
7473
  };
5613
7474
  }
5614
7475
  function bucketRank(id) {
@@ -5617,11 +7478,8 @@ function bucketRank(id) {
5617
7478
  return 2;
5618
7479
  }
5619
7480
  async function renderSkillSection(projectRoot, skillId) {
5620
- const skillPath = path27.join(
5621
- projectRoot,
5622
- ".teamix-evo",
5623
- "skills",
5624
- skillId,
7481
+ const skillPath = path32.join(
7482
+ getSkillsSourceDir(projectRoot, skillId),
5625
7483
  "SKILL.md"
5626
7484
  );
5627
7485
  const lines = [];
@@ -5629,7 +7487,7 @@ async function renderSkillSection(projectRoot, skillId) {
5629
7487
  let parts = null;
5630
7488
  let missing = false;
5631
7489
  try {
5632
- const raw = await fs20.readFile(skillPath, "utf8");
7490
+ const raw = await fs25.readFile(skillPath, "utf8");
5633
7491
  parts = extractDescriptionParts(raw);
5634
7492
  } catch {
5635
7493
  missing = true;
@@ -5646,10 +7504,19 @@ async function renderSkillSection(projectRoot, skillId) {
5646
7504
  if (parts?.coordinates) {
5647
7505
  lines.push(`- **Coordinates with**: ${parts.coordinates}`);
5648
7506
  }
5649
- lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills/${skillId}/SKILL.md\``);
7507
+ lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills-source/${skillId}/SKILL.md\``);
5650
7508
  return { section: lines.join("\n"), missing };
5651
7509
  }
5652
7510
  function renderAgentsMd(args) {
7511
+ const { variant, sections } = args;
7512
+ const managedBody = renderManagedBlockBody({ variant, sections });
7513
+ const wrapped = wrapManagedBlock(managedBody);
7514
+ return `${wrapped}
7515
+
7516
+ ${PRECEDENCE_NOTICE}
7517
+ `;
7518
+ }
7519
+ function renderManagedBlockBody(args) {
5653
7520
  const { variant, sections } = args;
5654
7521
  const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
5655
7522
  return `# AGENTS.md
@@ -5668,9 +7535,15 @@ ${skillBlock}
5668
7535
  - \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
5669
7536
  - \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
5670
7537
 
5671
- > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002
5672
- `;
7538
+ > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
7539
+ }
7540
+ function wrapManagedBlock(body) {
7541
+ return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
7542
+ ${body}
7543
+ <!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
5673
7544
  }
7545
+ var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
7546
+ > \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`;
5674
7547
  function extractDescriptionParts(fileContent) {
5675
7548
  const description = extractDescriptionBlock(fileContent);
5676
7549
  if (description == null) return null;
@@ -5771,9 +7644,9 @@ function extractSection(description, marker) {
5771
7644
  }
5772
7645
 
5773
7646
  // src/core/snapshot.ts
5774
- import * as fs21 from "fs/promises";
5775
- import * as path28 from "path";
5776
- var TEAMIX_DIR5 = ".teamix-evo";
7647
+ import * as fs26 from "fs/promises";
7648
+ import * as path33 from "path";
7649
+ var TEAMIX_DIR6 = ".teamix-evo";
5777
7650
  var SNAPSHOTS_DIR = ".snapshots";
5778
7651
  var LOGS_DIR = "logs";
5779
7652
  var META_FILE = "_meta.json";
@@ -5788,9 +7661,9 @@ function fsSafeToIso(safe) {
5788
7661
  );
5789
7662
  }
5790
7663
  async function createSnapshot(projectRoot, opts = {}) {
5791
- const teamixDir = path28.join(projectRoot, TEAMIX_DIR5);
7664
+ const teamixDir = path33.join(projectRoot, TEAMIX_DIR6);
5792
7665
  try {
5793
- const stat7 = await fs21.stat(teamixDir);
7666
+ const stat7 = await fs26.stat(teamixDir);
5794
7667
  if (!stat7.isDirectory()) return null;
5795
7668
  } catch (err) {
5796
7669
  if (err.code === "ENOENT") return null;
@@ -5798,38 +7671,38 @@ async function createSnapshot(projectRoot, opts = {}) {
5798
7671
  }
5799
7672
  const isoTs = (/* @__PURE__ */ new Date()).toISOString();
5800
7673
  const ts = isoToFsSafe3(isoTs);
5801
- const snapshotRoot = path28.join(teamixDir, SNAPSHOTS_DIR);
5802
- const target = path28.join(snapshotRoot, ts);
5803
- await fs21.mkdir(target, { recursive: true });
5804
- const entries = await fs21.readdir(teamixDir, { withFileTypes: true });
7674
+ const snapshotRoot = path33.join(teamixDir, SNAPSHOTS_DIR);
7675
+ const target = path33.join(snapshotRoot, ts);
7676
+ await fs26.mkdir(target, { recursive: true });
7677
+ const entries = await fs26.readdir(teamixDir, { withFileTypes: true });
5805
7678
  for (const entry of entries) {
5806
7679
  if (entry.name === SNAPSHOTS_DIR) continue;
5807
7680
  if (entry.name === LOGS_DIR) continue;
5808
- const src = path28.join(teamixDir, entry.name);
5809
- const dst = path28.join(target, entry.name);
5810
- await fs21.cp(src, dst, { recursive: true });
7681
+ const src = path33.join(teamixDir, entry.name);
7682
+ const dst = path33.join(target, entry.name);
7683
+ await fs26.cp(src, dst, { recursive: true });
5811
7684
  }
5812
7685
  const meta = {
5813
7686
  ts: isoTs,
5814
7687
  reason: opts.reason ?? "manual"
5815
7688
  };
5816
- await fs21.writeFile(
5817
- path28.join(target, META_FILE),
7689
+ await fs26.writeFile(
7690
+ path33.join(target, META_FILE),
5818
7691
  JSON.stringify(meta, null, 2) + "\n",
5819
7692
  "utf-8"
5820
7693
  );
5821
7694
  logger.debug(
5822
- `Snapshot created \u2192 ${path28.relative(projectRoot, target)} (${meta.reason})`
7695
+ `Snapshot created \u2192 ${path33.relative(projectRoot, target)} (${meta.reason})`
5823
7696
  );
5824
7697
  const keep = opts.keep ?? DEFAULT_KEEP;
5825
7698
  await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
5826
7699
  return { ts, path: target };
5827
7700
  }
5828
7701
  async function listSnapshots(projectRoot) {
5829
- const snapshotRoot = path28.join(projectRoot, TEAMIX_DIR5, SNAPSHOTS_DIR);
7702
+ const snapshotRoot = path33.join(projectRoot, TEAMIX_DIR6, SNAPSHOTS_DIR);
5830
7703
  let entries;
5831
7704
  try {
5832
- entries = await fs21.readdir(snapshotRoot, { withFileTypes: true });
7705
+ entries = await fs26.readdir(snapshotRoot, { withFileTypes: true });
5833
7706
  } catch (err) {
5834
7707
  if (err.code === "ENOENT") return [];
5835
7708
  throw err;
@@ -5837,11 +7710,11 @@ async function listSnapshots(projectRoot) {
5837
7710
  const result = [];
5838
7711
  for (const entry of entries) {
5839
7712
  if (!entry.isDirectory()) continue;
5840
- const dir = path28.join(snapshotRoot, entry.name);
7713
+ const dir = path33.join(snapshotRoot, entry.name);
5841
7714
  let isoTs = null;
5842
7715
  let reason = null;
5843
7716
  try {
5844
- const raw = await fs21.readFile(path28.join(dir, META_FILE), "utf-8");
7717
+ const raw = await fs26.readFile(path33.join(dir, META_FILE), "utf-8");
5845
7718
  const parsed = JSON.parse(raw);
5846
7719
  if (typeof parsed.ts === "string") isoTs = parsed.ts;
5847
7720
  if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
@@ -5866,17 +7739,17 @@ async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
5866
7739
  const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
5867
7740
  const removed = [];
5868
7741
  for (const snap of toRemove) {
5869
- await fs21.rm(snap.path, { recursive: true, force: true });
7742
+ await fs26.rm(snap.path, { recursive: true, force: true });
5870
7743
  removed.push(snap.ts);
5871
7744
  logger.debug(`Pruned snapshot ${snap.ts}`);
5872
7745
  }
5873
7746
  return removed.reverse();
5874
7747
  }
5875
7748
  async function restoreSnapshot(projectRoot, ts) {
5876
- const snapshotRoot = path28.join(projectRoot, TEAMIX_DIR5, SNAPSHOTS_DIR);
5877
- const target = path28.join(snapshotRoot, ts);
7749
+ const snapshotRoot = path33.join(projectRoot, TEAMIX_DIR6, SNAPSHOTS_DIR);
7750
+ const target = path33.join(snapshotRoot, ts);
5878
7751
  try {
5879
- const stat7 = await fs21.stat(target);
7752
+ const stat7 = await fs26.stat(target);
5880
7753
  if (!stat7.isDirectory()) {
5881
7754
  throw new Error(`Snapshot path is not a directory: ${target}`);
5882
7755
  }
@@ -5889,22 +7762,22 @@ async function restoreSnapshot(projectRoot, ts) {
5889
7762
  throw err;
5890
7763
  }
5891
7764
  await createSnapshot(projectRoot, { reason: "restore", protectedTs: ts });
5892
- const teamixDir = path28.join(projectRoot, TEAMIX_DIR5);
5893
- const live = await fs21.readdir(teamixDir, { withFileTypes: true });
7765
+ const teamixDir = path33.join(projectRoot, TEAMIX_DIR6);
7766
+ const live = await fs26.readdir(teamixDir, { withFileTypes: true });
5894
7767
  for (const entry of live) {
5895
7768
  if (entry.name === SNAPSHOTS_DIR) continue;
5896
7769
  if (entry.name === LOGS_DIR) continue;
5897
- await fs21.rm(path28.join(teamixDir, entry.name), {
7770
+ await fs26.rm(path33.join(teamixDir, entry.name), {
5898
7771
  recursive: true,
5899
7772
  force: true
5900
7773
  });
5901
7774
  }
5902
- const snapshotEntries = await fs21.readdir(target, { withFileTypes: true });
7775
+ const snapshotEntries = await fs26.readdir(target, { withFileTypes: true });
5903
7776
  for (const entry of snapshotEntries) {
5904
7777
  if (entry.name === META_FILE) continue;
5905
- const src = path28.join(target, entry.name);
5906
- const dst = path28.join(teamixDir, entry.name);
5907
- await fs21.cp(src, dst, { recursive: true });
7778
+ const src = path33.join(target, entry.name);
7779
+ const dst = path33.join(teamixDir, entry.name);
7780
+ await fs26.cp(src, dst, { recursive: true });
5908
7781
  }
5909
7782
  logger.debug(`Restored .teamix-evo/ from snapshot ${ts}`);
5910
7783
  }
@@ -5931,12 +7804,19 @@ var CRITICAL_STEPS = /* @__PURE__ */ new Set([
5931
7804
  "ui-init"
5932
7805
  ]);
5933
7806
  var IMPLEMENTED_STRATEGIES = {
7807
+ // 'agents-md': both 'merge-managed' (Phase 2.B — splice the
7808
+ // teamix-evo-skills managed region) and 'overwrite' (full rewrite) write
7809
+ // through `runGenerateAgentsMd`.
5934
7810
  "agents-md": ["merge-managed", "overwrite", "skip"],
5935
7811
  tokens: ["migrate", "overwrite", "skip"],
5936
7812
  "components-json": ["overwrite", "skip"],
5937
7813
  "shadcn-source": ["overwrite", "skip-existing", "skip"],
5938
7814
  "tailwind-config": ["skip"],
5939
- "index-css": ["skip"]
7815
+ "index-css": ["skip"],
7816
+ // Phase 3.E: lint conflict strategies are honored end-to-end by
7817
+ // `runLintInit` (backup user file + write template, optional AI-assist hint).
7818
+ "eslint-config": ["merge", "backup-overwrite", "skip", "overwrite"],
7819
+ "stylelint-config": ["merge", "backup-overwrite", "skip", "overwrite"]
5940
7820
  };
5941
7821
  function pickIde(ides) {
5942
7822
  return ides[0] ?? "qoder";
@@ -5954,11 +7834,75 @@ async function resolveUiEntries(options) {
5954
7834
  }
5955
7835
  return [...BASELINE_UI_ENTRIES];
5956
7836
  }
7837
+ function deriveTokensChanges(result, projectRoot) {
7838
+ if (result.status !== "installed") return [];
7839
+ return result.resources.map((r) => ({
7840
+ kind: "created",
7841
+ path: toRelativePosix(r.target, projectRoot),
7842
+ step: "tokens",
7843
+ detail: r.strategy
7844
+ }));
7845
+ }
7846
+ function deriveSkillsChanges(result, projectRoot) {
7847
+ if (result.status !== "installed") return [];
7848
+ return result.addedSkillIds.map((id) => ({
7849
+ kind: "created",
7850
+ path: `.teamix-evo/skills/${id}/SKILL.md`,
7851
+ step: "skills",
7852
+ detail: "skill installed (source mirror + IDE mirrors)"
7853
+ }));
7854
+ }
7855
+ function deriveUiAddChanges(result, projectRoot) {
7856
+ const out = [];
7857
+ let remaining = result.written;
7858
+ for (let i = result.resources.length - 1; i >= 0 && remaining > 0; i--) {
7859
+ const r = result.resources[i];
7860
+ out.unshift({
7861
+ kind: "created",
7862
+ path: toRelativePosix(r.target, projectRoot),
7863
+ step: "ui-add",
7864
+ detail: r.strategy
7865
+ });
7866
+ remaining--;
7867
+ }
7868
+ return out;
7869
+ }
7870
+ function deriveLintChanges(result) {
7871
+ if (result.status !== "installed") return [];
7872
+ const out = [];
7873
+ if (result.eslint) {
7874
+ out.push({
7875
+ kind: "created",
7876
+ path: "eslint.config.js",
7877
+ step: "lint",
7878
+ detail: "@teamix-evo/eslint-config consumer preset"
7879
+ });
7880
+ }
7881
+ if (result.stylelint) {
7882
+ out.push({
7883
+ kind: "created",
7884
+ path: "stylelint.config.cjs",
7885
+ step: "lint",
7886
+ detail: "@teamix-evo/stylelint-config consumer preset"
7887
+ });
7888
+ }
7889
+ if (result.packageJsonPatched) {
7890
+ out.push({
7891
+ kind: "created",
7892
+ path: "package.json",
7893
+ step: "lint",
7894
+ detail: 'scripts.lint / scripts["lint:css"]'
7895
+ });
7896
+ }
7897
+ return out;
7898
+ }
5957
7899
  async function runProjectInit(options) {
5958
7900
  const { projectRoot, answers, dryRun = false, onStep } = options;
5959
7901
  const ide = pickIde(answers.ides);
5960
7902
  const steps = [];
5961
7903
  const pending = [];
7904
+ const allChanges = [];
7905
+ const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
5962
7906
  let snapshot = null;
5963
7907
  let snapshotError;
5964
7908
  if (!dryRun) {
@@ -5975,6 +7919,9 @@ async function runProjectInit(options) {
5975
7919
  function record(step) {
5976
7920
  steps.push(step);
5977
7921
  onStep?.(step);
7922
+ if (step.changes && step.changes.length > 0) {
7923
+ allChanges.push(...step.changes);
7924
+ }
5978
7925
  }
5979
7926
  function recordPending(key) {
5980
7927
  const strategy = answers.conflictDecisions[key];
@@ -6034,7 +7981,8 @@ async function runProjectInit(options) {
6034
7981
  record({
6035
7982
  name: "tokens",
6036
7983
  status: "ok",
6037
- detail
7984
+ detail,
7985
+ changes: deriveTokensChanges(result, projectRoot)
6038
7986
  });
6039
7987
  } catch (err) {
6040
7988
  recordFailure("tokens", err);
@@ -6066,7 +8014,8 @@ async function runProjectInit(options) {
6066
8014
  record({
6067
8015
  name: "skills",
6068
8016
  status: "ok",
6069
- detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status
8017
+ detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
8018
+ changes: deriveSkillsChanges(result, projectRoot)
6070
8019
  });
6071
8020
  } catch (err) {
6072
8021
  recordFailure("skills", err);
@@ -6094,12 +8043,25 @@ async function runProjectInit(options) {
6094
8043
  const result = await runGenerateAgentsMd({
6095
8044
  projectRoot,
6096
8045
  variant: answers.variant,
6097
- skillIds: agentsMdSkillIds
8046
+ skillIds: agentsMdSkillIds,
8047
+ // Phase 2.B: when the user picked `merge-managed` on conflict, only
8048
+ // rewrite the `teamix-evo-skills` managed region so hand-written
8049
+ // sections survive the regenerate step. `overwrite` keeps the
8050
+ // historical full-rewrite default.
8051
+ mode: agentsMdDecision === "merge-managed" ? "merge-managed" : "overwrite"
6098
8052
  });
6099
8053
  record({
6100
8054
  name: "agents-md",
6101
8055
  status: "ok",
6102
- detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`
8056
+ detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`,
8057
+ changes: [
8058
+ {
8059
+ kind: result.backedUp ? "modified" : "created",
8060
+ path: toRelativePosix(result.path, projectRoot),
8061
+ step: "agents-md",
8062
+ detail: "skill-trigger fallback (ADR 0038)"
8063
+ }
8064
+ ]
6103
8065
  });
6104
8066
  } catch (err) {
6105
8067
  recordFailure("agents-md", err);
@@ -6184,7 +8146,8 @@ async function runProjectInit(options) {
6184
8146
  record({
6185
8147
  name: "ui-add",
6186
8148
  status: "ok",
6187
- detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`
8149
+ detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
8150
+ changes: deriveUiAddChanges(addResult, projectRoot)
6188
8151
  });
6189
8152
  } catch (err) {
6190
8153
  recordFailure("ui-add", err);
@@ -6208,14 +8171,39 @@ async function runProjectInit(options) {
6208
8171
  });
6209
8172
  } else {
6210
8173
  try {
8174
+ const eslintStrategy = answers.conflictDecisions["eslint-config"];
8175
+ const stylelintStrategy = answers.conflictDecisions["stylelint-config"];
8176
+ const eslintExistingPaths = options.legacyEslintPaths ?? [];
8177
+ const stylelintExistingPaths = options.legacyStylelintPaths ?? [];
6211
8178
  const result = await runLintInit({
6212
8179
  projectRoot,
6213
- skipInstall: options.skipInstall ?? false
8180
+ skipInstall: options.skipInstall ?? false,
8181
+ eslintStrategy: eslintStrategy === "merge" || eslintStrategy === "backup-overwrite" || eslintStrategy === "skip" || eslintStrategy === "overwrite" ? eslintStrategy : "overwrite",
8182
+ stylelintStrategy: stylelintStrategy === "merge" || stylelintStrategy === "backup-overwrite" || stylelintStrategy === "skip" || stylelintStrategy === "overwrite" ? stylelintStrategy : "overwrite",
8183
+ eslintExistingPaths,
8184
+ stylelintExistingPaths
6214
8185
  });
8186
+ const detailParts = [];
8187
+ if (result.status === "installed") {
8188
+ detailParts.push(
8189
+ `eslint=${result.eslint}, stylelint=${result.stylelint}`
8190
+ );
8191
+ if (result.eslintMergeRequested) {
8192
+ detailParts.push("eslint:AI-merge-pending");
8193
+ }
8194
+ if (result.stylelintMergeRequested) {
8195
+ detailParts.push("stylelint:AI-merge-pending");
8196
+ }
8197
+ if (result.eslintSkipped) detailParts.push("eslint:skipped");
8198
+ if (result.stylelintSkipped) detailParts.push("stylelint:skipped");
8199
+ } else {
8200
+ detailParts.push(result.status);
8201
+ }
6215
8202
  record({
6216
8203
  name: "lint",
6217
8204
  status: "ok",
6218
- detail: result.status === "installed" ? `eslint=${result.eslint}, stylelint=${result.stylelint}` : result.status
8205
+ detail: detailParts.join(" / "),
8206
+ changes: deriveLintChanges(result)
6219
8207
  });
6220
8208
  } catch (err) {
6221
8209
  recordFailure("lint", err);
@@ -6223,11 +8211,34 @@ async function runProjectInit(options) {
6223
8211
  }
6224
8212
  recordPending("tailwind-config");
6225
8213
  recordPending("index-css");
8214
+ if (!dryRun) {
8215
+ try {
8216
+ const backupsAfter = await listBackupOriginals(projectRoot);
8217
+ const newlyBackedUp = diffBackupSet(backupsBefore, backupsAfter);
8218
+ if (newlyBackedUp.size > 0) {
8219
+ for (const change of allChanges) {
8220
+ if (change.kind === "created" && newlyBackedUp.has(change.path)) {
8221
+ change.kind = "modified";
8222
+ }
8223
+ }
8224
+ for (const rel2 of newlyBackedUp) {
8225
+ allChanges.push({
8226
+ kind: "backed-up",
8227
+ path: rel2,
8228
+ step: "backup",
8229
+ detail: ".teamix-evo/.backups/<\u540C\u8DEF\u5F84>.<isoTs>.bak"
8230
+ });
8231
+ }
8232
+ }
8233
+ } catch {
8234
+ }
8235
+ }
6226
8236
  const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
6227
8237
  const out = {
6228
8238
  status,
6229
8239
  steps,
6230
8240
  pendingConflictWork: pending,
8241
+ changes: allChanges,
6231
8242
  snapshot
6232
8243
  };
6233
8244
  if (snapshotError) out.snapshotError = snapshotError;
@@ -6247,6 +8258,72 @@ async function runProjectInit(options) {
6247
8258
  return out;
6248
8259
  }
6249
8260
 
8261
+ // src/core/post-init-guidance.ts
8262
+ import * as path34 from "path";
8263
+ var HEADING = "Next steps:";
8264
+ async function buildPostInitGuidance(options) {
8265
+ const { projectRoot, withLint, withUi } = options;
8266
+ let foreignComponents = null;
8267
+ if (withUi) {
8268
+ try {
8269
+ const result = await runUiAdopt({ projectRoot, dryRun: true });
8270
+ foreignComponents = result.foreign.length;
8271
+ } catch {
8272
+ foreignComponents = null;
8273
+ }
8274
+ }
8275
+ let overridesVariables = null;
8276
+ try {
8277
+ const overridesAbs = path34.join(
8278
+ projectRoot,
8279
+ "tokens",
8280
+ "tokens.overrides.css"
8281
+ );
8282
+ const raw = await readFileOrNull(overridesAbs);
8283
+ if (raw !== null) {
8284
+ overridesVariables = countCssCustomProperties(raw);
8285
+ }
8286
+ } catch {
8287
+ overridesVariables = null;
8288
+ }
8289
+ const lines = [];
8290
+ lines.push("\u2022 \u542F\u52A8 dev server \u9A8C\u8BC1\uFF1A`npm run dev`");
8291
+ if (withLint) {
8292
+ lines.push("\u2022 lint \u68C0\u67E5\uFF1A`npm run lint` / `npm run lint:css`");
8293
+ }
8294
+ if (withUi) {
8295
+ if (foreignComponents !== null && foreignComponents > 0) {
8296
+ lines.push(
8297
+ `\u2022 \u68C0\u6D4B\u5230 ${foreignComponents} \u4E2A foreign UI \u7EC4\u4EF6\uFF08\u7528\u6237\u81EA\u6709\uFF0C\u4E0D\u5728 registry\uFF09\u2014 \u53EF\u6267\u884C \`teamix-evo ui add --adopt\` \u590D\u6838\u7EB3\u7BA1\u72B6\u6001`
8298
+ );
8299
+ } else if (foreignComponents === 0) {
8300
+ lines.push(
8301
+ "\u2022 UI \u63A5\u7BA1\u5B8C\u6210\uFF1A\u5F53\u524D src/components/ui/ \u5DF2\u5168\u90E8\u6CE8\u518C\u5230 manifest"
8302
+ );
8303
+ }
8304
+ }
8305
+ if (overridesVariables !== null && overridesVariables > 0) {
8306
+ lines.push(
8307
+ `\u2022 tokens.overrides.css \u542B ${overridesVariables} \u4E2A CSS \u53D8\u91CF \u2014 \u540E\u7EED\u53EF\u901A\u8FC7 \`teamix-evo tokens audit\` \u5BA1\u8BA1\u5197\u4F59 / \u63A8\u8350\u8FC1\u79FB\uFF08Phase 4.C.5\uFF09`
8308
+ );
8309
+ }
8310
+ lines.push(
8311
+ "\u2022 \u8BA9 AI \u63A5\u529B\uFF1A\u5728 IDE \u5185\u89E6\u53D1 `teamix-evo-manage` skill\uFF0C\u6309 adopt \u2192 upgrade \u2192 tokens audit \u2192 AGENTS.md \u56DE\u586B \u2192 lint 5 \u6B65\u8D70\u5B8C\u95ED\u73AF"
8312
+ );
8313
+ return {
8314
+ stats: { foreignComponents, overridesVariables },
8315
+ lines
8316
+ };
8317
+ }
8318
+ var POST_INIT_HEADING = HEADING;
8319
+ function countCssCustomProperties(content) {
8320
+ const seen = /* @__PURE__ */ new Set();
8321
+ for (const m of content.matchAll(/^\s*(--[a-z][a-z0-9-]*)\s*:/gim)) {
8322
+ if (m[1]) seen.add(m[1]);
8323
+ }
8324
+ return seen.size;
8325
+ }
8326
+
6250
8327
  // src/commands/init/wizard.ts
6251
8328
  import * as p from "@clack/prompts";
6252
8329
  var FRIENDLY_KEY = {
@@ -6255,7 +8332,9 @@ var FRIENDLY_KEY = {
6255
8332
  "tailwind-config": "tailwind.config.*",
6256
8333
  tokens: "tokens / src/design-tokens.css",
6257
8334
  "index-css": "src/index.css",
6258
- "shadcn-source": "src/lib/utils.ts + src/components/ui/"
8335
+ "shadcn-source": "src/lib/utils.ts + src/components/ui/",
8336
+ "eslint-config": "eslint.config.* / .eslintrc.*",
8337
+ "stylelint-config": "stylelint.config.* / .stylelintrc.*"
6259
8338
  };
6260
8339
  var STRATEGY_LABEL = {
6261
8340
  overwrite: "\u8986\u76D6\uFF08\u5199\u5165\u65B0\u6587\u4EF6\uFF09",
@@ -6267,7 +8346,8 @@ var STRATEGY_LABEL = {
6267
8346
  coexist: "\u5171\u5B58\uFF08\u4EC5\u5199 overrides\uFF0C\u4E0D\u8986\u76D6\u73B0\u6709\uFF09",
6268
8347
  append: "\u8FFD\u52A0 @import \u884C + managed \u6807\u8BB0\uFF08\u63A8\u8350\uFF09",
6269
8348
  "skip-existing": "\u8DF3\u8FC7\u540C\u540D\u6587\u4EF6\uFF0C\u4EC5\u88C5\u65B0\u589E\uFF08\u63A8\u8350\uFF09",
6270
- "per-file-prompt": "\u9010\u6587\u4EF6\u8BE2\u95EE"
8349
+ "per-file-prompt": "\u9010\u6587\u4EF6\u8BE2\u95EE",
8350
+ merge: "\u5907\u4EFD + \u5199\u6A21\u677F\uFF0C\u8D77 AI \u52A9\u624B\u624B\u5408\u5E76\uFF08\u63A8\u8350\uFF09"
6271
8351
  };
6272
8352
  function strategyLabel(s) {
6273
8353
  return STRATEGY_LABEL[s];
@@ -6446,10 +8526,10 @@ var STEP_ICON = {
6446
8526
  };
6447
8527
 
6448
8528
  // src/commands/init/index.ts
6449
- var initCommand5 = new Command33("init").description(
8529
+ var initCommand5 = new Command35("init").description(
6450
8530
  "\u628A teamix-evo AI \u5957\u4EF6\u63A5\u5165\u5DF2\u6709\u5DE5\u7A0B\uFF08\u666E\u901A\u7248\u63A5\u5165\uFF1A\u68C0\u6D4B\u51B2\u7A81 \u2192 \u5F15\u5BFC wizard \u2192 \u9759\u9ED8\u843D\u5730\uFF09"
6451
8531
  ).option("--cwd <dir>", "\u6307\u5B9A\u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\uFF1A\u5F53\u524D\u76EE\u5F55\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u4EA4\u4E92\uFF0C\u5168\u90E8\u4F7F\u7528\u63A8\u8350\u9ED8\u8BA4\u503C").option("--dry-run", "\u53EA\u8F93\u51FA\u6267\u884C\u8BA1\u5212\uFF0C\u4E0D\u5199\u4EFB\u4F55\u6587\u4EF6").option("--variant <name>", "tokens variant\uFF08\u8986\u76D6 wizard \u8BE2\u95EE\uFF09").action(async (opts) => {
6452
- const cwd = path29.resolve(opts.cwd ?? process.cwd());
8532
+ const cwd = path35.resolve(opts.cwd ?? process.cwd());
6453
8533
  try {
6454
8534
  const state = await detectProjectState(cwd);
6455
8535
  if (state.state === "empty") {
@@ -6496,11 +8576,15 @@ var initCommand5 = new Command33("init").description(
6496
8576
  logger.info("\u{1F4CB} dry-run \u6A21\u5F0F\uFF1A\u4EE5\u4E0B\u8BA1\u5212\u4E0D\u4F1A\u5199\u5165\u4EFB\u4F55\u6587\u4EF6");
6497
8577
  }
6498
8578
  const legacyTokensPaths = (conflicts.items.find((it) => it.key === "tokens")?.paths ?? []).filter((p2) => !p2.startsWith("tokens/"));
8579
+ const legacyEslintPaths = conflicts.items.find((it) => it.key === "eslint-config")?.paths ?? [];
8580
+ const legacyStylelintPaths = conflicts.items.find((it) => it.key === "stylelint-config")?.paths ?? [];
6499
8581
  const result = await runProjectInit({
6500
8582
  projectRoot: cwd,
6501
8583
  answers,
6502
8584
  dryRun,
6503
8585
  legacyTokensPaths,
8586
+ legacyEslintPaths,
8587
+ legacyStylelintPaths,
6504
8588
  onStep: (step) => {
6505
8589
  const icon = STEP_ICON[step.status];
6506
8590
  const detail = step.detail ? ` \u2014 ${step.detail}` : "";
@@ -6552,12 +8636,23 @@ var initCommand5 = new Command33("init").description(
6552
8636
  logger.info(` \u2022 ${w.key} \u2192 ${w.strategy}`);
6553
8637
  }
6554
8638
  }
8639
+ if (result.status !== "dry-run" && result.changes.length > 0) {
8640
+ logger.info("");
8641
+ logger.info("\u672C\u6B21 init \u6587\u4EF6\u53D8\u66F4\u6E05\u5355\uFF1A");
8642
+ for (const line of formatFileChangeSummary(result.changes)) {
8643
+ logger.info(line);
8644
+ }
8645
+ }
6555
8646
  if (result.status === "installed") {
8647
+ const guidance = await buildPostInitGuidance({
8648
+ projectRoot: cwd,
8649
+ withLint: answers.withLint,
8650
+ withUi: answers.withUi
8651
+ });
6556
8652
  logger.info("");
6557
- logger.info("Next steps:");
6558
- logger.info(" \u2022 \u542F\u52A8 dev server \u9A8C\u8BC1\uFF1A`npm run dev`");
6559
- if (answers.withLint) {
6560
- logger.info(" \u2022 lint \u68C0\u67E5\uFF1A`npm run lint` / `npm run lint:css`");
8653
+ logger.info(POST_INIT_HEADING);
8654
+ for (const line of guidance.lines) {
8655
+ logger.info(` ${line}`);
6561
8656
  }
6562
8657
  }
6563
8658
  } catch (err) {
@@ -6573,11 +8668,11 @@ var initCommand5 = new Command33("init").description(
6573
8668
  });
6574
8669
 
6575
8670
  // src/commands/update/index.ts
6576
- import { Command as Command34 } from "commander";
6577
- import * as path31 from "path";
8671
+ import { Command as Command36 } from "commander";
8672
+ import * as path37 from "path";
6578
8673
 
6579
8674
  // src/core/project-update.ts
6580
- import * as path30 from "path";
8675
+ import * as path36 from "path";
6581
8676
  import {
6582
8677
  loadTokensPackageManifest as loadTokensPackageManifest3,
6583
8678
  getVariantEntry as getVariantEntry3
@@ -6828,7 +8923,7 @@ async function runComponentSourceStep(category, args) {
6828
8923
  });
6829
8924
  return;
6830
8925
  }
6831
- const stagingRel = path30.relative(projectRoot, built.stagingDir);
8926
+ const stagingRel = path36.relative(projectRoot, built.stagingDir);
6832
8927
  const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
6833
8928
  record({
6834
8929
  name: category,
@@ -6871,10 +8966,10 @@ async function planTokensUpdate(tokensPackage, config) {
6871
8966
  }
6872
8967
 
6873
8968
  // src/commands/update/index.ts
6874
- var updateCommand3 = new Command34("update").description(
8969
+ var updateCommand3 = new Command36("update").description(
6875
8970
  "\u4E00\u952E\u5347\u7EA7 teamix-evo \u5DF2\u88C5\u8D44\u6E90\uFF08regenerable \u8986\u76D6\u3001frozen \u4FDD\u7559\u3001managed \u5408\u5E76 \u2014 ADR 0003 \u4E09\u6001\uFF09"
6876
8971
  ).option("--cwd <dir>", "\u6307\u5B9A\u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\uFF1A\u5F53\u524D\u76EE\u5F55\uFF09").option("--dry-run", "\u53EA\u8F93\u51FA\u5347\u7EA7\u8BA1\u5212\uFF0C\u4E0D\u5199\u4EFB\u4F55\u6587\u4EF6").action(async (opts) => {
6877
- const cwd = path31.resolve(opts.cwd ?? process.cwd());
8972
+ const cwd = path37.resolve(opts.cwd ?? process.cwd());
6878
8973
  const dryRun = opts.dryRun ?? false;
6879
8974
  try {
6880
8975
  if (dryRun) {
@@ -6956,14 +9051,14 @@ var updateCommand3 = new Command34("update").description(
6956
9051
  });
6957
9052
 
6958
9053
  // src/commands/restore/index.ts
6959
- import { Command as Command35 } from "commander";
6960
- import * as path32 from "path";
9054
+ import { Command as Command37 } from "commander";
9055
+ import * as path38 from "path";
6961
9056
  import * as prompts7 from "@clack/prompts";
6962
9057
  function createRestoreCommand() {
6963
- return new Command35("restore").description(
9058
+ return new Command37("restore").description(
6964
9059
  "\u56DE\u6EDA .teamix-evo/ \u5230\u6307\u5B9A snapshot\uFF08init/update \u5931\u8D25\u65F6\u4F7F\u7528 \u2014 ADR 0019 \xA72\uFF09"
6965
9060
  ).argument("[ts]", "\u76EE\u6807 snapshot \u65F6\u95F4\u6233\uFF08\u5982 2026-06-11T20-59-03-000Z\uFF09").option("--list", "\u5217\u51FA\u53EF\u7528 snapshot\uFF08\u4E0D\u505A\u5B9E\u9645\u56DE\u6EDA\uFF09").option("--cwd <dir>", "\u6307\u5B9A\u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\uFF1A\u5F53\u524D\u76EE\u5F55\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u4E8C\u6B21\u786E\u8BA4\uFF08destructive\uFF0C\u8BF7\u786E\u8BA4\u65E0\u8BEF\uFF09").action(async (ts, opts) => {
6966
- const cwd = path32.resolve(opts.cwd ?? process.cwd());
9061
+ const cwd = path38.resolve(opts.cwd ?? process.cwd());
6967
9062
  try {
6968
9063
  const snapshots = await listSnapshots(cwd);
6969
9064
  if (opts.list) {
@@ -7059,13 +9154,13 @@ function printSnapshotTable(snapshots) {
7059
9154
  }
7060
9155
 
7061
9156
  // src/commands/switch/index.ts
7062
- import { Command as Command36 } from "commander";
7063
- import * as path34 from "path";
9157
+ import { Command as Command38 } from "commander";
9158
+ import * as path40 from "path";
7064
9159
  import * as prompts8 from "@clack/prompts";
7065
9160
 
7066
9161
  // src/core/variant-switch.ts
7067
- import * as path33 from "path";
7068
- import * as fs22 from "fs/promises";
9162
+ import * as path39 from "path";
9163
+ import * as fs27 from "fs/promises";
7069
9164
  import {
7070
9165
  loadTokensPackageManifest as loadTokensPackageManifest4,
7071
9166
  getVariantEntry as getVariantEntry4
@@ -7099,8 +9194,8 @@ async function runVariantSwitch(options) {
7099
9194
  const upstreamByBasename = /* @__PURE__ */ new Map();
7100
9195
  for (const fileRel of variantEntry.files) {
7101
9196
  upstreamByBasename.set(
7102
- path33.basename(fileRel),
7103
- path33.join(packageRoot, fileRel)
9197
+ path39.basename(fileRel),
9198
+ path39.join(packageRoot, fileRel)
7104
9199
  );
7105
9200
  }
7106
9201
  const prior = await readInstalledManifest(projectRoot) ?? {
@@ -7114,7 +9209,7 @@ async function runVariantSwitch(options) {
7114
9209
  const priorVersion = installedIdx >= 0 ? prior.installed[installedIdx].version : "0.0.0";
7115
9210
  const changes = [];
7116
9211
  for (const resource of priorResources) {
7117
- const consumerBasename = path33.basename(resource.target);
9212
+ const consumerBasename = path39.basename(resource.target);
7118
9213
  const upstreamBasename = lookupUpstreamBasename2(consumerBasename);
7119
9214
  const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
7120
9215
  if (resource.strategy === "frozen") {
@@ -7135,7 +9230,7 @@ async function runVariantSwitch(options) {
7135
9230
  });
7136
9231
  continue;
7137
9232
  }
7138
- const upstreamContent = await fs22.readFile(upstreamAbs, "utf-8");
9233
+ const upstreamContent = await fs27.readFile(upstreamAbs, "utf-8");
7139
9234
  if (resource.strategy === "regenerable") {
7140
9235
  const newHash = computeHash(upstreamContent);
7141
9236
  if (newHash === resource.hash) {
@@ -7188,12 +9283,12 @@ async function runVariantSwitch(options) {
7188
9283
  }
7189
9284
  const refreshedResources = [];
7190
9285
  for (const resource of priorResources) {
7191
- const consumerAbs = path33.isAbsolute(resource.target) ? resource.target : path33.join(projectRoot, resource.target);
7192
- const consumerBasename = path33.basename(resource.target);
9286
+ const consumerAbs = path39.isAbsolute(resource.target) ? resource.target : path39.join(projectRoot, resource.target);
9287
+ const consumerBasename = path39.basename(resource.target);
7193
9288
  const upstreamBasename = lookupUpstreamBasename2(consumerBasename);
7194
9289
  const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
7195
9290
  if (resource.strategy === "regenerable" && upstreamAbs) {
7196
- const upstreamContent = await fs22.readFile(upstreamAbs, "utf-8");
9291
+ const upstreamContent = await fs27.readFile(upstreamAbs, "utf-8");
7197
9292
  await writeFileSafe(consumerAbs, upstreamContent);
7198
9293
  logger.debug(`[variant-switch] rewrite regenerable: ${resource.target}`);
7199
9294
  refreshedResources.push({
@@ -7203,8 +9298,8 @@ async function runVariantSwitch(options) {
7203
9298
  continue;
7204
9299
  }
7205
9300
  if (resource.strategy === "managed" && upstreamAbs && await fileExists(consumerAbs)) {
7206
- const upstreamContent = await fs22.readFile(upstreamAbs, "utf-8");
7207
- const consumerContent = await fs22.readFile(consumerAbs, "utf-8");
9301
+ const upstreamContent = await fs27.readFile(upstreamAbs, "utf-8");
9302
+ const consumerContent = await fs27.readFile(consumerAbs, "utf-8");
7208
9303
  const merged = mergeManagedRegions(upstreamContent, consumerContent);
7209
9304
  if (merged !== consumerContent) {
7210
9305
  await writeFileSafe(consumerAbs, merged);
@@ -7235,7 +9330,7 @@ async function runVariantSwitch(options) {
7235
9330
  installedAt: now
7236
9331
  };
7237
9332
  await writeFileSafe(
7238
- path33.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
9333
+ path39.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
7239
9334
  JSON.stringify(lock, null, 2) + "\n"
7240
9335
  );
7241
9336
  logger.debug(
@@ -7332,10 +9427,10 @@ var KIND_LABEL = {
7332
9427
  unchanged: "unchanged"
7333
9428
  };
7334
9429
  function createSwitchCommand() {
7335
- return new Command36("switch").description(
9430
+ return new Command38("switch").description(
7336
9431
  "variant \u5207\u6362\uFF1A\u5148\u5C55\u793A file-level diff\uFF0C--apply \u624D\u771F\u5199\uFF08ADR 0019 \xA7D3\uFF09"
7337
9432
  ).argument("<new-variant>", "\u76EE\u6807 variant id\uFF08\u5982 opentrek / uni-manager\uFF09").option("--cwd <dir>", "\u6307\u5B9A\u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\uFF1A\u5F53\u524D\u76EE\u5F55\uFF09").option("--apply", "\u771F\u6B63\u6267\u884C\u5207\u6362\uFF08\u9ED8\u8BA4 dry-run\uFF0C\u4EC5\u5C55\u793A\u8BA1\u5212\uFF09").option("-y, --yes", "\u8DF3\u8FC7 --apply \u4E8C\u6B21\u786E\u8BA4\uFF08destructive\uFF0C\u8BF7\u786E\u8BA4\u65E0\u8BEF\uFF09").action(async (newVariant, opts) => {
7338
- const cwd = path34.resolve(opts.cwd ?? process.cwd());
9433
+ const cwd = path40.resolve(opts.cwd ?? process.cwd());
7339
9434
  const apply = opts.apply ?? false;
7340
9435
  try {
7341
9436
  const plan = await runVariantSwitch({
@@ -7451,7 +9546,7 @@ function printChangePlan(from, to, toVersion, changes) {
7451
9546
  // src/index.ts
7452
9547
  var require8 = createRequire8(import.meta.url);
7453
9548
  var { version } = require8("../package.json");
7454
- var program = new Command37();
9549
+ var program = new Command39();
7455
9550
  program.name("teamix-evo").description("Where ideas evolve. \u2014 AI Coding \u5957\u4EF6").version(version);
7456
9551
  program.addCommand(tokensCommand);
7457
9552
  program.addCommand(skillsCommand);