teamix-evo 0.10.1 → 0.12.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.
@@ -235,12 +235,6 @@ async function writeSkillsLock(projectRoot, lock) {
235
235
  await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
236
236
  logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
237
237
  }
238
- function findInstalledPackage(installed, packageName) {
239
- if (!installed) return null;
240
- const matches = installed.installed.filter((p) => p.package === packageName);
241
- if (matches.length === 0) return null;
242
- return matches[matches.length - 1] ?? null;
243
- }
244
238
 
245
239
  // src/core/skills-client.ts
246
240
  import * as path3 from "path";
@@ -369,12 +363,6 @@ function resolveSourcePath(source, variantDir, packageRoot) {
369
363
  }
370
364
  return path6.join(variantDir, source);
371
365
  }
372
- var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
373
- "node_modules",
374
- "dist",
375
- "build",
376
- ".teamix-evo"
377
- ]);
378
366
  async function walkDir(dir, skipDirs) {
379
367
  const files = [];
380
368
  const entries = await fs4.readdir(dir, { withFileTypes: true });
@@ -430,9 +418,9 @@ async function writeSkillSource(skill, options) {
430
418
  const { data, packageRoot, projectRoot } = options;
431
419
  const sourceAbs = path7.resolve(packageRoot, skill.source);
432
420
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
433
- const stat5 = await fs5.stat(sourceAbs);
421
+ const stat3 = await fs5.stat(sourceAbs);
434
422
  const records = [];
435
- if (stat5.isFile()) {
423
+ if (stat3.isFile()) {
436
424
  const targetFile = path7.join(targetDir, "SKILL.md");
437
425
  const content = await renderSkillContent(sourceAbs, skill, data);
438
426
  await writeFileSafe(targetFile, content);
@@ -582,8 +570,8 @@ async function rewriteSkillSource(skill, options, summary) {
582
570
  const { data, packageRoot, projectRoot } = options;
583
571
  const sourceAbs = path7.resolve(packageRoot, skill.source);
584
572
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
585
- const stat5 = await fs5.stat(sourceAbs);
586
- if (!stat5.isFile()) {
573
+ const stat3 = await fs5.stat(sourceAbs);
574
+ if (!stat3.isFile()) {
587
575
  await ensureDir(targetDir);
588
576
  const entries = await walkDir(sourceAbs);
589
577
  const records = [];
@@ -820,13 +808,13 @@ async function pruneEmptyIdeSkillDirs(args) {
820
808
  }
821
809
  for (const name of entries) {
822
810
  const dir = path7.join(skillsRoot, name);
823
- let stat5;
811
+ let stat3;
824
812
  try {
825
- stat5 = await fs5.stat(dir);
813
+ stat3 = await fs5.stat(dir);
826
814
  } catch {
827
815
  continue;
828
816
  }
829
- if (!stat5.isDirectory()) continue;
817
+ if (!stat3.isDirectory()) continue;
830
818
  let children;
831
819
  try {
832
820
  children = await fs5.readdir(dir);
@@ -949,10 +937,25 @@ async function runSkillsInit(options) {
949
937
  return { status: "already-initialized" };
950
938
  }
951
939
  if (onlyIds.length === 0) {
940
+ let autoUpdatedSkillIds = [];
941
+ if (outdatedSkills.length > 0) {
942
+ autoUpdatedSkillIds = await autoUpgradeOutdatedSkills({
943
+ projectRoot,
944
+ packageName,
945
+ manifest,
946
+ data,
947
+ packageRoot,
948
+ ides,
949
+ scope,
950
+ outdatedSkills,
951
+ existing,
952
+ existingConfig
953
+ });
954
+ }
952
955
  return {
953
956
  status: "installed",
954
957
  packageName,
955
- version: existingSkillsCfg?.version ?? manifest.version,
958
+ version: manifest.version,
956
959
  ides,
957
960
  scope,
958
961
  skillCount: 0,
@@ -960,7 +963,8 @@ async function runSkillsInit(options) {
960
963
  resources: [],
961
964
  addedSkillIds: [],
962
965
  skippedSkillIds,
963
- outdatedSkills
966
+ autoUpdatedSkillIds,
967
+ outdatedSkills: []
964
968
  };
965
969
  }
966
970
  return finalizeSkillsInstall({
@@ -1021,10 +1025,25 @@ async function runSkillsAdd(options) {
1021
1025
  existing
1022
1026
  );
1023
1027
  if (onlyIds.length === 0) {
1028
+ let autoUpdatedSkillIds = [];
1029
+ if (outdatedSkills.length > 0) {
1030
+ autoUpdatedSkillIds = await autoUpgradeOutdatedSkills({
1031
+ projectRoot,
1032
+ packageName,
1033
+ manifest,
1034
+ data,
1035
+ packageRoot,
1036
+ ides,
1037
+ scope,
1038
+ outdatedSkills,
1039
+ existing,
1040
+ existingConfig
1041
+ });
1042
+ }
1024
1043
  return {
1025
1044
  status: "installed",
1026
1045
  packageName,
1027
- version: existingSkillsCfg?.version ?? manifest.version,
1046
+ version: manifest.version,
1028
1047
  ides,
1029
1048
  scope,
1030
1049
  skillCount: 0,
@@ -1032,7 +1051,8 @@ async function runSkillsAdd(options) {
1032
1051
  resources: [],
1033
1052
  addedSkillIds: [],
1034
1053
  skippedSkillIds,
1035
- outdatedSkills
1054
+ autoUpdatedSkillIds,
1055
+ outdatedSkills: []
1036
1056
  };
1037
1057
  }
1038
1058
  return finalizeSkillsInstall({
@@ -1186,6 +1206,21 @@ async function finalizeSkillsInstall(args) {
1186
1206
  await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
1187
1207
  } catch {
1188
1208
  }
1209
+ let autoUpdatedSkillIds = [];
1210
+ if (outdatedSkills && outdatedSkills.length > 0) {
1211
+ autoUpdatedSkillIds = await autoUpgradeOutdatedSkills({
1212
+ projectRoot,
1213
+ packageName,
1214
+ manifest,
1215
+ data,
1216
+ packageRoot,
1217
+ ides,
1218
+ scope,
1219
+ outdatedSkills,
1220
+ existing,
1221
+ existingConfig: existingConfig ?? config
1222
+ });
1223
+ }
1189
1224
  return {
1190
1225
  status: "installed",
1191
1226
  packageName,
@@ -1197,7 +1232,8 @@ async function finalizeSkillsInstall(args) {
1197
1232
  resources: result.resources,
1198
1233
  addedSkillIds: onlyIds,
1199
1234
  skippedSkillIds,
1200
- outdatedSkills: outdatedSkills ?? []
1235
+ autoUpdatedSkillIds,
1236
+ outdatedSkills: []
1201
1237
  };
1202
1238
  }
1203
1239
  function mergeInstalledResources(existing, next) {
@@ -1207,6 +1243,66 @@ function mergeInstalledResources(existing, next) {
1207
1243
  for (const r of next) map.set(key(r), r);
1208
1244
  return [...map.values()];
1209
1245
  }
1246
+ async function autoUpgradeOutdatedSkills(args) {
1247
+ const {
1248
+ projectRoot,
1249
+ packageName,
1250
+ manifest,
1251
+ data,
1252
+ packageRoot,
1253
+ ides,
1254
+ scope,
1255
+ outdatedSkills,
1256
+ existing
1257
+ } = args;
1258
+ const targetIds = outdatedSkills.map((o) => o.id);
1259
+ await updateSkills({
1260
+ projectRoot,
1261
+ manifest,
1262
+ data,
1263
+ packageRoot,
1264
+ ides,
1265
+ scope,
1266
+ onlyIds: targetIds
1267
+ });
1268
+ const lock = existing.lock ?? {
1269
+ schemaVersion: 1,
1270
+ skills: {}
1271
+ };
1272
+ const installedAt = (/* @__PURE__ */ new Date()).toISOString();
1273
+ const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
1274
+ for (const id of targetIds) {
1275
+ const skillDef = manifestById.get(id);
1276
+ if (!skillDef) continue;
1277
+ const mirroredTo = skillDef.ides.filter((i) => ides.includes(i));
1278
+ lock.skills[id] = {
1279
+ version: skillDef.version,
1280
+ from: packageName,
1281
+ installedAt,
1282
+ scope,
1283
+ mirroredTo
1284
+ };
1285
+ }
1286
+ await writeSkillsLock(projectRoot, lock);
1287
+ const installedManifest = await readInstalledManifest(projectRoot) ?? {
1288
+ schemaVersion: 1,
1289
+ installed: []
1290
+ };
1291
+ const idx = installedManifest.installed.findIndex(
1292
+ (p) => p.package === packageName
1293
+ );
1294
+ if (idx >= 0) {
1295
+ installedManifest.installed[idx].version = manifest.version;
1296
+ installedManifest.installed[idx].installedAt = installedAt;
1297
+ }
1298
+ await writeInstalledManifest(projectRoot, installedManifest);
1299
+ logger.debug(
1300
+ `Auto-upgraded ${targetIds.length} outdated skill(s): ${targetIds.join(
1301
+ ", "
1302
+ )}`
1303
+ );
1304
+ return targetIds;
1305
+ }
1210
1306
 
1211
1307
  // src/core/tokens-init.ts
1212
1308
  var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
@@ -1638,6 +1734,7 @@ var DEFAULT_UI_ALIASES = {
1638
1734
  utils: "src/lib/utils",
1639
1735
  lib: "src/lib",
1640
1736
  business: "src/components/business",
1737
+ blocks: "src/blocks",
1641
1738
  templates: "src/templates"
1642
1739
  };
1643
1740
  var DEFAULT_UI_ICON_LIBRARY = "lucide";
@@ -1655,6 +1752,7 @@ async function runUiInit(options) {
1655
1752
  utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
1656
1753
  lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
1657
1754
  business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
1755
+ blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks,
1658
1756
  templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
1659
1757
  };
1660
1758
  const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
@@ -1723,9 +1821,11 @@ var SOURCE_ROOT_TO_ALIAS_KEY = {
1723
1821
  components: "components",
1724
1822
  hooks: "hooks",
1725
1823
  utils: "utils",
1726
- lib: "lib"
1824
+ lib: "lib",
1825
+ blocks: "blocks"
1727
1826
  };
1728
- function rewriteImports(source, aliases) {
1827
+ function rewriteImports(source, aliases, opts) {
1828
+ const shouldFlatten = opts?.flatten !== false;
1729
1829
  return source.replace(
1730
1830
  /(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
1731
1831
  (full, quote, root, rest) => {
@@ -1733,8 +1833,8 @@ function rewriteImports(source, aliases) {
1733
1833
  if (!aliasKey) return full;
1734
1834
  const alias = aliases[aliasKey];
1735
1835
  const normalized = aliasToImportPath(alias);
1736
- const flatRest = flattenRestPath(rest);
1737
- return `${quote}${normalized}${flatRest}${quote}`;
1836
+ const resolvedRest = shouldFlatten ? flattenRestPath(rest) : rest ?? "";
1837
+ return `${quote}${normalized}${resolvedRest}${quote}`;
1738
1838
  }
1739
1839
  );
1740
1840
  }
@@ -1763,7 +1863,8 @@ async function installUiEntries(options) {
1763
1863
  entryPackageRoot,
1764
1864
  aliases,
1765
1865
  requested,
1766
- skipExisting = true
1866
+ skipExisting = true,
1867
+ flatten = true
1767
1868
  } = options;
1768
1869
  const orderedIds = resolveUiEntryOrder(manifest.entries, requested);
1769
1870
  const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
@@ -1790,7 +1891,7 @@ async function installUiEntries(options) {
1790
1891
  const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
1791
1892
  const sourceAbs = path11.resolve(rootForEntry, file.source);
1792
1893
  const raw = await fs8.readFile(sourceAbs, "utf-8");
1793
- const transformed = rewriteImports(raw, aliases);
1894
+ const transformed = rewriteImports(raw, aliases, { flatten });
1794
1895
  if (exists) {
1795
1896
  await backupFile(targetAbs, projectRoot);
1796
1897
  }
@@ -2646,8 +2747,8 @@ var STYLELINT_CONFIG_CANDIDATES = [
2646
2747
  ];
2647
2748
  async function isDir(target) {
2648
2749
  try {
2649
- const stat5 = await fs12.stat(target);
2650
- return stat5.isDirectory();
2750
+ const stat3 = await fs12.stat(target);
2751
+ return stat3.isDirectory();
2651
2752
  } catch {
2652
2753
  return false;
2653
2754
  }
@@ -2871,2762 +2972,565 @@ async function detectStylelintConfig(cwd) {
2871
2972
  };
2872
2973
  }
2873
2974
 
2874
- // src/core/legacy-tokens-migrate.ts
2875
- import * as path17 from "path";
2975
+ // src/core/deps-install.ts
2876
2976
  import * as fs13 from "fs/promises";
2877
- var CONSUMER_TOKENS_DIR2 = "tokens";
2878
- var CONSUMER_OVERRIDES_FILE2 = "tokens.overrides.css";
2879
- var EMPTY_OVERRIDES_TEMPLATE2 = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
2880
- /* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
2881
- `;
2882
- function buildMigrationBanner(legacyRel, isoTimestamp) {
2883
- return `/* === Migrated from ${legacyRel} on ${isoTimestamp} === */
2884
- `;
2977
+ import * as path17 from "path";
2978
+ import { exec } from "child_process";
2979
+ import { promisify } from "util";
2980
+ var execAsync = promisify(exec);
2981
+ async function detectPackageManager(projectRoot) {
2982
+ if (await fileExists(path17.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
2983
+ if (await fileExists(path17.join(projectRoot, "pnpm-workspace.yaml")))
2984
+ return "pnpm";
2985
+ if (await fileExists(path17.join(projectRoot, "bun.lockb"))) return "bun";
2986
+ if (await fileExists(path17.join(projectRoot, "bun.lock"))) return "bun";
2987
+ if (await fileExists(path17.join(projectRoot, "yarn.lock"))) return "yarn";
2988
+ return "npm";
2885
2989
  }
2886
- function computeBackupRel(legacyRel, isoTimestamp) {
2887
- const tsSafe = isoTimestamp.replace(/[:.]/g, "-");
2888
- return path17.posix.join(
2889
- ".teamix-evo",
2890
- ".backups",
2891
- `${legacyRel}.${tsSafe}.bak`
2892
- );
2990
+ function getInstallCommand(pm) {
2991
+ switch (pm) {
2992
+ case "pnpm":
2993
+ return "pnpm install";
2994
+ case "yarn":
2995
+ return "yarn install";
2996
+ case "bun":
2997
+ return "bun install";
2998
+ case "npm":
2999
+ return "npm install";
3000
+ }
2893
3001
  }
2894
- async function migrateLegacyTokens(options) {
2895
- const { projectRoot, legacyPaths, dryRun = false } = options;
2896
- const seen = /* @__PURE__ */ new Set();
2897
- const uniqueLegacy = [];
2898
- for (const raw of legacyPaths) {
2899
- const norm = raw.split(path17.sep).join("/");
2900
- if (!seen.has(norm)) {
2901
- seen.add(norm);
2902
- uniqueLegacy.push(norm);
2903
- }
3002
+ async function installProjectDeps(options) {
3003
+ const { projectRoot, npmDependencies, skipInstall = false } = options;
3004
+ const pkgPath = path17.join(projectRoot, "package.json");
3005
+ const raw = await readFileOrNull(pkgPath);
3006
+ if (!raw) {
3007
+ throw new Error(
3008
+ `package.json not found at ${projectRoot}. Cannot install dependencies.`
3009
+ );
2904
3010
  }
2905
- const overridesRel = path17.posix.join(
2906
- CONSUMER_TOKENS_DIR2,
2907
- CONSUMER_OVERRIDES_FILE2
2908
- );
2909
- const overridesAbs = path17.join(projectRoot, overridesRel);
2910
- const existingOverrides = await readFileOrNull(overridesAbs);
2911
- const overridesCreated = existingOverrides === null;
2912
- const baseContent = existingOverrides === null ? EMPTY_OVERRIDES_TEMPLATE2 : existingOverrides;
2913
- const migrated = [];
2914
- const skipped = [];
2915
- const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString();
2916
- let appendedPayload = "";
2917
- for (const legacyRel of uniqueLegacy) {
2918
- const legacyAbs = path17.join(projectRoot, legacyRel);
2919
- const content = await readFileOrNull(legacyAbs);
2920
- if (content === null) {
2921
- skipped.push({ from: legacyRel, reason: "not-found" });
2922
- continue;
2923
- }
2924
- if (content.trim() === "") {
2925
- skipped.push({ from: legacyRel, reason: "empty" });
2926
- continue;
3011
+ const pkg = JSON.parse(raw);
3012
+ const existingDeps = {
3013
+ ...pkg.dependencies,
3014
+ ...pkg.devDependencies
3015
+ };
3016
+ const added = {};
3017
+ const existed = {};
3018
+ for (const [name, range] of Object.entries(npmDependencies)) {
3019
+ if (existingDeps[name]) {
3020
+ existed[name] = existingDeps[name];
3021
+ } else {
3022
+ added[name] = range;
2927
3023
  }
2928
- const banner = buildMigrationBanner(legacyRel, isoTimestamp);
2929
- const separator = appendedPayload === "" && baseContent.endsWith("\n\n") ? "" : "\n";
2930
- const block = `${separator}${banner}${content}${content.endsWith("\n") ? "" : "\n"}`;
2931
- appendedPayload += block;
2932
- const backupRel = dryRun ? null : computeBackupRel(legacyRel, isoTimestamp);
2933
- migrated.push({
2934
- from: legacyRel,
2935
- backupTo: backupRel,
2936
- bytesAppended: Buffer.byteLength(block, "utf-8")
2937
- });
2938
3024
  }
2939
- if (dryRun || migrated.length === 0) {
2940
- return {
2941
- migrated,
2942
- skipped,
2943
- overridesPath: overridesRel,
2944
- overridesCreated: dryRun ? overridesCreated : false,
2945
- dryRun
2946
- };
3025
+ if (Object.keys(added).length > 0) {
3026
+ if (!pkg.dependencies) pkg.dependencies = {};
3027
+ for (const [name, range] of Object.entries(added)) {
3028
+ pkg.dependencies[name] = range;
3029
+ }
3030
+ pkg.dependencies = Object.fromEntries(
3031
+ Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
3032
+ );
3033
+ await fs13.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
3034
+ logger.info(
3035
+ ` patched package.json: +${Object.keys(added).length} dependencies`
3036
+ );
2947
3037
  }
2948
- await ensureDir(path17.dirname(overridesAbs));
2949
- const merged = baseContent + appendedPayload;
2950
- const tmp = overridesAbs + ".tmp";
2951
- await fs13.writeFile(tmp, merged, "utf-8");
2952
- await fs13.rename(tmp, overridesAbs);
2953
- for (const entry of migrated) {
2954
- const legacyAbs = path17.join(projectRoot, entry.from);
2955
- const backupAbs = path17.join(projectRoot, entry.backupTo);
2956
- await ensureDir(path17.dirname(backupAbs));
2957
- const srcContent = await readFileOrNull(legacyAbs);
2958
- if (srcContent === null) {
2959
- logger.debug(
2960
- `legacy-tokens-migrate: source disappeared before backup: ${entry.from}`
3038
+ const packageManager = await detectPackageManager(projectRoot);
3039
+ let installed = false;
3040
+ if (!skipInstall && Object.keys(added).length > 0) {
3041
+ const cmd = getInstallCommand(packageManager);
3042
+ logger.info(` running ${cmd}...`);
3043
+ try {
3044
+ await execAsync(cmd, { cwd: projectRoot, timeout: 12e4 });
3045
+ installed = true;
3046
+ logger.info(" install complete");
3047
+ } catch (err) {
3048
+ logger.warn(
3049
+ ` install failed: ${err instanceof Error ? err.message : String(err)}`
2961
3050
  );
2962
- continue;
2963
- }
2964
- await fs13.writeFile(backupAbs, srcContent, "utf-8");
2965
- if (await fileExists(legacyAbs)) {
2966
- await fs13.unlink(legacyAbs);
3051
+ logger.warn(" please run install manually");
2967
3052
  }
2968
- logger.debug(
2969
- `legacy-tokens-migrate: ${entry.from} \u2192 ${overridesRel} (backup: ${entry.backupTo})`
2970
- );
2971
3053
  }
2972
- return {
2973
- migrated,
2974
- skipped,
2975
- overridesPath: overridesRel,
2976
- overridesCreated,
2977
- dryRun: false
2978
- };
3054
+ return { added, existed, installed, packageManager };
3055
+ }
3056
+
3057
+ // src/core/init-checklist-template.ts
3058
+ var STEP_LABELS = {
3059
+ tokens: "tokens \u2014 design tokens \u5B89\u88C5",
3060
+ skills: "skills \u2014 AI skills \u6279\u91CF\u81EA\u4E3E",
3061
+ "agents-md": "agents-md \u2014 AGENTS.md \u751F\u6210",
3062
+ "ui-init": "ui-init \u2014 UI \u914D\u7F6E\u521D\u59CB\u5316",
3063
+ "ui-add": "ui-add \u2014 UI \u7EC4\u4EF6\u5168\u91CF\u5B89\u88C5",
3064
+ "biz-ui-add": "biz-ui-add \u2014 \u4E1A\u52A1\u7EC4\u4EF6\u5168\u91CF\u5B89\u88C5",
3065
+ lint: "lint \u2014 ESLint + Stylelint \u914D\u7F6E",
3066
+ gitignore: "gitignore \u2014 .gitignore \u8FFD\u52A0"
3067
+ };
3068
+ function renderInitChecklist(args) {
3069
+ const { variant, status, steps } = args;
3070
+ const ts = args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
3071
+ const stepLines = steps.map((s) => {
3072
+ const label = STEP_LABELS[s.name] ?? s.name;
3073
+ const checked = s.status === "ok" ? "x" : " ";
3074
+ const suffix = s.status === "ok" ? "" : s.status === "skip" ? `\uFF08\u8DF3\u8FC7${s.detail ? "\uFF1A" + s.detail : ""}\uFF09` : s.status === "fail" ? `\uFF08\u5931\u8D25\uFF1A${s.detail ?? "unknown"}\uFF09` : "\uFF08\u8BA1\u5212\u4E2D\uFF09";
3075
+ return `- [${checked}] ${label}${suffix}`;
3076
+ }).join("\n");
3077
+ return `# teamix-evo init \u5B89\u88C5\u6458\u8981
3078
+
3079
+ > \u7531 \`teamix-evo init\` \u81EA\u52A8\u751F\u6210 \xB7 ${ts}
3080
+ > variant: ${variant} \xB7 status: ${status}
3081
+
3082
+ ## \u5B89\u88C5\u6B65\u9AA4
3083
+
3084
+ ${stepLines}
3085
+
3086
+ ## \u5907\u6CE8
3087
+
3088
+ - \u5931\u8D25\u6216\u8DF3\u8FC7\u7684\u6B65\u9AA4\u4E0D\u5F71\u54CD\u540E\u7EED\u6B65\u9AA4\u72EC\u7ACB\u6267\u884C
3089
+ - \u4FEE\u590D\u540E\u91CD\u8DD1 \`teamix-evo init\`\uFF08\u6BCF\u6B65\u5E42\u7B49\uFF0C\u81EA\u52A8\u8DF3\u8FC7\u5DF2\u5B8C\u6210\u9879\uFF09
3090
+ - \u5982\u9700\u5168\u91CF\u8986\u76D6\uFF1A\`teamix-evo init --force\`
3091
+ `;
2979
3092
  }
2980
3093
 
2981
- // src/core/ui-conflict-detector.ts
3094
+ // src/core/file-changes.ts
2982
3095
  import * as fs14 from "fs/promises";
2983
3096
  import * as path18 from "path";
2984
- function looksLikeShadcnOriginal(content) {
2985
- if (content.includes("data-slot=")) return false;
2986
- if (content.includes("@teamix-evo")) return false;
2987
- if (content.includes("teamix-evo:managed")) return false;
2988
- return true;
2989
- }
2990
- async function detectUiConflicts(options) {
2991
- const { projectRoot, aliases, manifest } = options;
2992
- const conflicts = [];
2993
- const conflictDirSet = /* @__PURE__ */ new Set();
2994
- let totalChecked = 0;
2995
- for (const entry of manifest.entries) {
2996
- for (const file of entry.files) {
2997
- totalChecked++;
2998
- const aliasDir = aliases[file.targetAlias];
2999
- if (!aliasDir) continue;
3000
- const targetAbs = path18.join(projectRoot, aliasDir, file.targetName);
3001
- const exists = await fileExists(targetAbs);
3002
- if (!exists) continue;
3003
- let isShadcnOriginal = true;
3004
- try {
3005
- const content = await fs14.readFile(targetAbs, "utf-8");
3006
- isShadcnOriginal = looksLikeShadcnOriginal(content);
3007
- } catch {
3008
- }
3009
- const relativePath = path18.relative(projectRoot, targetAbs).replace(/\\/g, "/");
3010
- conflicts.push({
3011
- id: entry.id,
3012
- targetPath: targetAbs,
3013
- relativePath,
3014
- isShadcnOriginal
3015
- });
3016
- conflictDirSet.add(aliasDir);
3017
- }
3097
+ function toRelativePosix(p, projectRoot) {
3098
+ let rel2 = p;
3099
+ if (path18.isAbsolute(p)) {
3100
+ rel2 = path18.relative(projectRoot, p);
3018
3101
  }
3019
- return {
3020
- conflictEntries: conflicts,
3021
- unconflictedTargets: totalChecked - conflicts.length,
3022
- totalEntries: totalChecked,
3023
- shouldBlock: conflicts.length > 0,
3024
- conflictDirs: [...conflictDirSet]
3025
- };
3102
+ return rel2.split(path18.sep).join("/");
3026
3103
  }
3027
3104
 
3028
- // src/core/ui-isolate.ts
3029
- import * as fs15 from "fs/promises";
3105
+ // src/core/project-init.ts
3106
+ import * as fsNode from "fs/promises";
3030
3107
  import * as path19 from "path";
3031
- var IGNORE_DIR_NAMES = DEFAULT_SKIP_DIRS;
3032
- async function runUiIsolate(options) {
3033
- const { projectRoot, aliases } = options;
3034
- const componentsDir = path19.join(projectRoot, aliases.components);
3035
- const legacyDir = path19.join(
3108
+ var CRITICAL_STEPS = /* @__PURE__ */ new Set([
3109
+ "tokens",
3110
+ "skills",
3111
+ "ui-init"
3112
+ ]);
3113
+ var GITIGNORE_MARKER_START = "# >>> teamix-evo:managed >>>";
3114
+ var GITIGNORE_MARKER_END = "# <<< teamix-evo:managed <<<";
3115
+ var GITIGNORE_RULES = [
3116
+ "# IDE skill mirrors (regenerable via `teamix-evo skills sync`; ADR 0013)",
3117
+ ".qoder/skills/",
3118
+ ".claude/skills/",
3119
+ "",
3120
+ "# Runtime artifacts (regenerable / archives)",
3121
+ ".teamix-evo/.snapshots/",
3122
+ ".teamix-evo/logs/",
3123
+ ".teamix-evo/.backups/",
3124
+ ".teamix-evo/.upgrade-staging/",
3125
+ ".teamix-evo/.upgrade-hints/"
3126
+ ];
3127
+ async function runProjectInit(options) {
3128
+ const {
3036
3129
  projectRoot,
3037
- aliases.components.replace(/\/ui\/?$/, "/shadcn-ui")
3038
- );
3039
- const movedFiles = [];
3040
- const backedUpFiles = [];
3041
- let componentsJsonRemoved = false;
3042
- if (await fileExists(componentsDir)) {
3043
- await ensureDir(legacyDir);
3044
- const UI_EXTENSIONS = /* @__PURE__ */ new Set([
3045
- ".ts",
3046
- ".tsx",
3047
- ".js",
3048
- ".jsx",
3049
- ".css",
3050
- ".json",
3051
- ".md",
3052
- ".mdx"
3053
- ]);
3054
- const allUiFiles = await walkDir(componentsDir);
3055
- const uiFiles = allUiFiles.filter(
3056
- (f) => UI_EXTENSIONS.has(path19.extname(f))
3057
- );
3058
- for (const srcFile of uiFiles) {
3059
- const relFromUi = path19.relative(componentsDir, srcFile);
3060
- const destFile = path19.join(legacyDir, relFromUi);
3061
- await ensureDir(path19.dirname(destFile));
3062
- const content = await fs15.readFile(srcFile, "utf-8");
3063
- await fs15.writeFile(destFile, content, "utf-8");
3064
- try {
3065
- await fs15.unlink(srcFile);
3066
- } catch {
3067
- logger.warn(`Could not remove: ${srcFile}`);
3068
- }
3069
- const fromRel = path19.relative(projectRoot, srcFile).replace(/\\/g, "/");
3070
- const toRel = path19.relative(projectRoot, destFile).replace(/\\/g, "/");
3071
- movedFiles.push({ from: fromRel, to: toRel });
3130
+ variant,
3131
+ ides,
3132
+ scope = "project",
3133
+ dryRun = false,
3134
+ onStep
3135
+ } = options;
3136
+ const ide = ides[0] ?? "qoder";
3137
+ const steps = [];
3138
+ const allChanges = [];
3139
+ let aborted = false;
3140
+ const firstFailure = { value: null };
3141
+ function record(step) {
3142
+ steps.push(step);
3143
+ onStep?.(step);
3144
+ if (step.changes && step.changes.length > 0) {
3145
+ allChanges.push(...step.changes);
3072
3146
  }
3073
- await pruneEmptyDirs(componentsDir);
3074
- logger.info(
3075
- ` moved ${movedFiles.length} files \u2192 ${path19.relative(
3076
- projectRoot,
3077
- legacyDir
3078
- )}/`
3079
- );
3080
3147
  }
3081
- const importRewrites = /* @__PURE__ */ new Map();
3082
- const srcDir = path19.join(projectRoot, "src");
3083
- if (await fileExists(srcDir)) {
3084
- const SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mdx"]);
3085
- const allScanFiles = await walkDir(srcDir, DEFAULT_SKIP_DIRS);
3086
- const scanFiles = allScanFiles.filter((f) => {
3087
- const rel2 = path19.relative(projectRoot, f);
3088
- const segments = rel2.split(path19.sep);
3089
- if (segments.some((s) => IGNORE_DIR_NAMES.has(s))) return false;
3090
- return SCAN_EXTENSIONS.has(path19.extname(f));
3091
- });
3092
- const oldAlias = aliases.components;
3093
- const newAlias = oldAlias.replace(/\/ui\/?$/, "/shadcn-ui");
3094
- const oldWithoutSrc = oldAlias.replace(/^src\//, "");
3095
- const newWithoutSrc = newAlias.replace(/^src\//, "");
3096
- const dirName = path19.basename(oldAlias);
3097
- const newDirName = path19.basename(newAlias);
3098
- for (const file of scanFiles) {
3099
- const content = await fs15.readFile(file, "utf-8");
3100
- let modified = content;
3101
- let count = 0;
3102
- modified = modified.replace(
3103
- new RegExp(`(['"'\`])@/${escapeRegExp2(oldWithoutSrc)}/`, "g"),
3104
- (match, quote) => {
3105
- count++;
3106
- return `${quote}@/${newWithoutSrc}/`;
3107
- }
3108
- );
3109
- modified = modified.replace(
3110
- new RegExp(`(['"'\`])~/${escapeRegExp2(oldWithoutSrc)}/`, "g"),
3111
- (match, quote) => {
3112
- count++;
3113
- return `${quote}~/${newWithoutSrc}/`;
3114
- }
3115
- );
3116
- modified = modified.replace(
3117
- new RegExp(
3118
- `(from\\s+['"'\`](?:\\.\\.?\\/)+(?:[\\w.-]+\\/)*)${escapeRegExp2(
3119
- dirName
3120
- )}/`,
3121
- "g"
3122
- ),
3123
- (match) => {
3124
- count++;
3125
- return match.slice(0, -dirName.length - 1) + `${newDirName}/`;
3126
- }
3127
- );
3128
- if (count > 0) {
3129
- await fs15.writeFile(file, modified, "utf-8");
3130
- const relPath = path19.relative(projectRoot, file).replace(/\\/g, "/");
3131
- importRewrites.set(relPath, count);
3132
- }
3133
- }
3134
- logger.info(` rewrote imports in ${importRewrites.size} files`);
3148
+ function recordFailure(name, err) {
3149
+ const message = getErrorMessage(err);
3150
+ record({ name, status: "fail", detail: message });
3151
+ if (!firstFailure.value)
3152
+ firstFailure.value = { step: name, error: message };
3153
+ if (CRITICAL_STEPS.has(name)) aborted = true;
3135
3154
  }
3136
- const componentsJsonPath = path19.join(projectRoot, "components.json");
3137
- if (await fileExists(componentsJsonPath)) {
3138
- await backupFile(componentsJsonPath, projectRoot);
3139
- backedUpFiles.push("components.json");
3155
+ if (dryRun) {
3156
+ record({
3157
+ name: "tokens",
3158
+ status: "planned",
3159
+ detail: `runTokensInit(variant=${variant})`
3160
+ });
3161
+ } else {
3140
3162
  try {
3141
- await fs15.unlink(componentsJsonPath);
3142
- componentsJsonRemoved = true;
3143
- logger.info(" backed up and removed components.json");
3144
- } catch {
3145
- logger.warn(" could not remove components.json");
3163
+ const result = await runTokensInit({ projectRoot, variant, ide });
3164
+ const detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
3165
+ record({
3166
+ name: "tokens",
3167
+ status: "ok",
3168
+ detail,
3169
+ changes: result.status === "installed" ? result.resources.map((r) => ({
3170
+ kind: "created",
3171
+ path: toRelativePosix(r.target, projectRoot),
3172
+ step: "tokens",
3173
+ detail: r.strategy
3174
+ })) : []
3175
+ });
3176
+ } catch (err) {
3177
+ recordFailure("tokens", err);
3146
3178
  }
3147
3179
  }
3148
- const libBackupTargets = [
3149
- path19.join(projectRoot, aliases.utils + ".ts"),
3150
- path19.join(projectRoot, aliases.lib, "color.ts")
3151
- ];
3152
- const hooksDir = path19.join(projectRoot, aliases.hooks);
3153
- if (await fileExists(hooksDir)) {
3154
- const allHookFiles = await walkDir(hooksDir, DEFAULT_SKIP_DIRS);
3155
- const hookFiles = allHookFiles.filter(
3156
- (f) => path19.extname(f) === ".ts" || path19.extname(f) === ".tsx"
3157
- );
3158
- libBackupTargets.push(...hookFiles);
3159
- }
3160
- for (const target of libBackupTargets) {
3161
- if (await fileExists(target)) {
3162
- await backupFile(target, projectRoot);
3163
- backedUpFiles.push(
3164
- path19.relative(projectRoot, target).replace(/\\/g, "/")
3165
- );
3166
- }
3167
- }
3168
- return {
3169
- movedFiles,
3170
- importRewrites,
3171
- componentsJsonRemoved,
3172
- backedUpFiles
3173
- };
3174
- }
3175
- function escapeRegExp2(s) {
3176
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3177
- }
3178
- async function pruneEmptyDirs(dir) {
3179
- try {
3180
- const entries = await fs15.readdir(dir, { withFileTypes: true });
3181
- for (const entry of entries) {
3182
- if (entry.isDirectory()) {
3183
- await pruneEmptyDirs(path19.join(dir, entry.name));
3184
- }
3185
- }
3186
- const remaining = await fs15.readdir(dir);
3187
- if (remaining.length === 0) {
3188
- await fs15.rmdir(dir);
3189
- }
3190
- } catch {
3191
- }
3192
- }
3193
-
3194
- // src/core/ui-upgrade.ts
3195
- import * as path22 from "path";
3196
- import { createRequire as createRequire5 } from "module";
3197
- import {
3198
- loadUiPackageManifest as loadUiPackageManifest3,
3199
- loadVariantUiPackageManifest as loadVariantUiPackageManifest2
3200
- } from "@teamix-evo/registry";
3201
-
3202
- // src/core/ui-upgrade-detector.ts
3203
- import * as fs16 from "fs/promises";
3204
- import * as path20 from "path";
3205
- var PACKAGE_NAME = {
3206
- ui: "@teamix-evo/ui",
3207
- "biz-ui": "@teamix-evo/biz-ui"
3208
- };
3209
- var ALIAS_KEY = {
3210
- ui: "components",
3211
- "biz-ui": "business"
3212
- };
3213
- var COMPONENT_FILE_RE = /\.(tsx|ts)$/;
3214
- var SKIP_FILENAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx"]);
3215
- async function detectComponentLineage(options) {
3216
- const { projectRoot, category } = options;
3217
- const config = options.config ?? await readProjectConfig(projectRoot);
3218
- const installed = options.installed ?? await readInstalledManifest(projectRoot);
3219
- const installDir = resolveInstallDir(category, config);
3220
- const installDirAbs = path20.join(projectRoot, installDir);
3221
- const installDirExists = await directoryExists(installDirAbs);
3222
- const hasComponentsJson = await fileExists(
3223
- path20.join(projectRoot, "components.json")
3224
- );
3225
- const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
3226
- const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
3227
- const onDiskIds = installDirExists ? await listComponentIds(installDirAbs) : [];
3228
- const registeredSet = new Set(registeredIds);
3229
- const unregisteredIds = onDiskIds.filter((id) => !registeredSet.has(id)).sort();
3230
- const lineage = classifyLineage({
3231
- hasInstalled: installedPkg !== null,
3232
- hasComponentsJson,
3233
- onDiskIds,
3234
- unregisteredIds
3235
- });
3236
- return {
3237
- category,
3238
- lineage,
3239
- installDir,
3240
- installDirExists,
3241
- hasComponentsJson,
3242
- registeredIds,
3243
- unregisteredIds,
3244
- installedVersion: installedPkg?.version ?? null,
3245
- installedVariant: installedPkg?.variant ?? null
3246
- };
3247
- }
3248
- function resolveInstallDir(category, config) {
3249
- const aliasMap = config?.packages?.ui?.aliases ?? config?.packages?.["biz-ui"]?.aliases ?? DEFAULT_UI_ALIASES;
3250
- const key = ALIAS_KEY[category];
3251
- return aliasMap[key] ?? DEFAULT_UI_ALIASES[key];
3252
- }
3253
- function extractIds(pkg) {
3254
- const ids = /* @__PURE__ */ new Set();
3255
- for (const r of pkg.resources) {
3256
- const colon = r.id.indexOf(":");
3257
- ids.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
3258
- }
3259
- return [...ids];
3260
- }
3261
- async function directoryExists(p) {
3262
- try {
3263
- const stat5 = await fs16.stat(p);
3264
- return stat5.isDirectory();
3265
- } catch {
3266
- return false;
3267
- }
3268
- }
3269
- async function listComponentIds(installDirAbs) {
3270
- const entries = await fs16.readdir(installDirAbs, { withFileTypes: true });
3271
- const ids = [];
3272
- for (const e of entries) {
3273
- if (!e.isFile()) continue;
3274
- if (SKIP_FILENAMES.has(e.name)) continue;
3275
- if (!COMPONENT_FILE_RE.test(e.name)) continue;
3276
- ids.push(e.name.replace(COMPONENT_FILE_RE, ""));
3277
- }
3278
- return ids.sort();
3279
- }
3280
- function classifyLineage(args) {
3281
- const { hasInstalled, hasComponentsJson, onDiskIds, unregisteredIds } = args;
3282
- if (hasInstalled) {
3283
- return unregisteredIds.length === 0 ? "teamix-evo" : "mixed";
3284
- }
3285
- if (onDiskIds.length === 0) return "absent";
3286
- return hasComponentsJson ? "shadcn-native" : "custom-only";
3287
- }
3288
-
3289
- // src/core/ui-upgrade-staging.ts
3290
- import * as path21 from "path";
3291
- var TEAMIX_DIR2 = ".teamix-evo";
3292
- var STAGING_DIR = ".upgrade-staging";
3293
- var PACKAGE_NAME2 = {
3294
- ui: "@teamix-evo/ui",
3295
- "biz-ui": "@teamix-evo/biz-ui"
3296
- };
3297
- function isoToFsSafe(iso) {
3298
- return iso.replace(/[:.]/g, "-");
3299
- }
3300
- async function buildUiUpgradeStaging(options) {
3301
- const { lineageReport, category } = options;
3302
- if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
3303
- return null;
3304
- }
3305
- const installed = options.installed ?? await readInstalledManifest(options.projectRoot);
3306
- const installedPkg = findInstalledPackage(installed, PACKAGE_NAME2[category]);
3307
- if (!installedPkg) return null;
3308
- const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3309
- const fsTs = isoToFsSafe(isoTs);
3310
- const stagingDir = path21.join(
3311
- options.projectRoot,
3312
- TEAMIX_DIR2,
3313
- STAGING_DIR,
3314
- `${category}-${fsTs}`
3315
- );
3316
- const entryMap = new Map(
3317
- options.manifest.entries.map((e) => [e.id, e])
3318
- );
3319
- const resByEntryId = collectResourcesByEntry(installedPkg.resources);
3320
- const onlyIds = options.onlyIds && options.onlyIds.length > 0 ? new Set(options.onlyIds) : null;
3321
- const entries = [];
3322
- for (const id of lineageReport.registeredIds) {
3323
- if (onlyIds && !onlyIds.has(id)) continue;
3324
- const built = await processRegistered({
3325
- id,
3326
- entry: entryMap.get(id),
3327
- resource: resByEntryId.get(id),
3328
- packageRoot: options.packageRoot,
3329
- entryPackageRoot: options.entryPackageRoot,
3330
- aliases: options.aliases,
3331
- stagingDir,
3332
- projectRoot: options.projectRoot,
3333
- category,
3334
- sourceVersion: options.manifest.version
3335
- });
3336
- if (built) entries.push(built);
3337
- }
3338
- for (const id of lineageReport.unregisteredIds) {
3339
- if (onlyIds && !onlyIds.has(id)) continue;
3340
- const built = await processForeign({
3341
- id,
3342
- installDirAbs: path21.join(options.projectRoot, lineageReport.installDir),
3343
- stagingDir,
3344
- projectRoot: options.projectRoot,
3345
- category
3346
- });
3347
- if (built) entries.push(built);
3348
- }
3349
- if (entries.length === 0) return null;
3350
- const byRisk = aggregateByRisk(entries);
3351
- const manifestOut = {
3352
- schemaVersion: 1,
3353
- ts: isoTs,
3354
- package: category,
3355
- trigger: options.trigger,
3356
- variant: lineageReport.installedVariant ?? "_flat",
3357
- fromVersion: lineageReport.installedVersion ?? "",
3358
- toVersion: options.manifest.version,
3359
- lineage: lineageReport.lineage,
3360
- summary: { total: entries.length, byRisk },
3361
- entries
3362
- };
3363
- await ensureDir(stagingDir);
3364
- await writeFileSafe(
3365
- path21.join(stagingDir, "meta.json"),
3366
- JSON.stringify(manifestOut, null, 2) + "\n"
3367
- );
3368
- return { stagingDir, manifest: manifestOut };
3369
- }
3370
- async function processRegistered(args) {
3371
- const { id, entry, resource, stagingDir, projectRoot, category } = args;
3372
- if (!resource) return null;
3373
- const currentSource = await readFileOrNull(resource.target);
3374
- if (currentSource === null) {
3375
- return buildBreakingEntry({
3376
- id,
3377
- category,
3378
- resource,
3379
- projectRoot,
3380
- stagingDir,
3381
- currentSource: "",
3382
- hint: "installed file missing on disk"
3383
- });
3384
- }
3385
- if (!entry) {
3386
- return buildBreakingEntry({
3387
- id,
3388
- category,
3389
- resource,
3390
- projectRoot,
3391
- stagingDir,
3392
- currentSource,
3393
- hint: "entry removed in upstream package"
3394
- });
3395
- }
3396
- const file = entry.files[0];
3397
- if (!file) return null;
3398
- const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
3399
- const sourceAbs = path21.resolve(rootForEntry, file.source);
3400
- const raw = await readFileOrNull(sourceAbs);
3401
- if (raw === null) {
3402
- return null;
3403
- }
3404
- const incomingTransformed = rewriteImports(raw, args.aliases);
3405
- const incomingHash = computeHash(incomingTransformed);
3406
- const currentExt = path21.extname(resource.target) || ".tsx";
3407
- const incomingExt = path21.extname(file.targetName) || currentExt;
3408
- const currentRel = `${id}/current${currentExt}`;
3409
- const incomingRel = `${id}/incoming${incomingExt}`;
3410
- await writeFileSafe(path21.join(stagingDir, currentRel), currentSource);
3411
- await writeFileSafe(path21.join(stagingDir, incomingRel), incomingTransformed);
3412
- const diff = classifyRisk({
3413
- currentHash: resource.hash,
3414
- incomingHash,
3415
- currentSource,
3416
- incomingSource: incomingTransformed,
3417
- multiFile: entry.files.length > 1
3418
- });
3419
- const promotion = derivePromotion({
3420
- currentSource,
3421
- incomingSource: incomingTransformed,
3422
- targetName: entry.files[0]?.targetName ?? `${id}.tsx`
3423
- });
3424
- return {
3425
- id,
3426
- category,
3427
- current: {
3428
- target: path21.relative(projectRoot, resource.target),
3429
- hash: resource.hash,
3430
- sourceLineage: "teamix-evo"
3431
- },
3432
- incoming: {
3433
- sourceVersion: args.sourceVersion,
3434
- hash: incomingHash,
3435
- relPath: incomingRel
3436
- },
3437
- diff,
3438
- promotion
3439
- };
3440
- }
3441
- async function processForeign(args) {
3442
- const { id, installDirAbs, stagingDir, projectRoot, category } = args;
3443
- const tsx = path21.join(installDirAbs, `${id}.tsx`);
3444
- const ts = path21.join(installDirAbs, `${id}.ts`);
3445
- const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
3446
- if (!target) return null;
3447
- const raw = await readFileOrNull(target);
3448
- if (raw === null) return null;
3449
- const ext = path21.extname(target);
3450
- const currentRel = `${id}/current${ext}`;
3451
- await writeFileSafe(path21.join(stagingDir, currentRel), raw);
3452
- return {
3453
- id,
3454
- category,
3455
- current: {
3456
- target: path21.relative(projectRoot, target),
3457
- hash: computeHash(raw),
3458
- sourceLineage: "custom"
3459
- },
3460
- diff: {
3461
- riskLevel: "foreign",
3462
- hints: [
3463
- "component is on disk but not registered in .teamix-evo/manifest.json",
3464
- "AI should propose: (a) ignore, (b) re-register via teamix-evo ui add, or (c) remove"
3465
- ],
3466
- filesChangedCount: 0
3467
- }
3468
- };
3469
- }
3470
- async function buildBreakingEntry(args) {
3471
- const ext = path21.extname(args.resource.target) || ".tsx";
3472
- const currentRel = `${args.id}/current${ext}`;
3473
- await writeFileSafe(
3474
- path21.join(args.stagingDir, currentRel),
3475
- args.currentSource
3476
- );
3477
- return {
3478
- id: args.id,
3479
- category: args.category,
3480
- current: {
3481
- target: path21.relative(args.projectRoot, args.resource.target),
3482
- hash: args.resource.hash,
3483
- sourceLineage: "teamix-evo"
3484
- },
3485
- diff: {
3486
- riskLevel: "breaking",
3487
- hints: [args.hint],
3488
- filesChangedCount: 0
3489
- }
3490
- };
3491
- }
3492
- function classifyRisk(args) {
3493
- if (args.currentHash === args.incomingHash) {
3494
- return { riskLevel: "unchanged", hints: [], filesChangedCount: 0 };
3495
- }
3496
- const curExports = extractExportNames(args.currentSource);
3497
- const newExports = extractExportNames(args.incomingSource);
3498
- const removedExports = setDiff(curExports, newExports);
3499
- const addedExports = setDiff(newExports, curExports);
3500
- const curVariants = extractCvaVariantValues(args.currentSource);
3501
- const newVariants = extractCvaVariantValues(args.incomingSource);
3502
- const removedVariants = setDiff(curVariants, newVariants);
3503
- const addedVariants = setDiff(newVariants, curVariants);
3504
- const hints = [];
3505
- for (const e of removedExports) hints.push(`removed export: ${e}`);
3506
- for (const e of addedExports) hints.push(`new export: ${e}`);
3507
- for (const v of removedVariants) hints.push(`removed cva variant: ${v}`);
3508
- for (const v of addedVariants) hints.push(`new cva variant: ${v}`);
3509
- if (args.multiFile) hints.push("multi-file entry; only first file staged");
3510
- let riskLevel;
3511
- if (removedExports.length > 0 || removedVariants.length > 0) {
3512
- riskLevel = "risky";
3513
- } else if (addedExports.length > 0 || addedVariants.length > 0 || args.multiFile) {
3514
- riskLevel = "upgradable-medium";
3515
- } else {
3516
- riskLevel = "upgradable-low";
3517
- }
3518
- return { riskLevel, hints, filesChangedCount: 1 };
3519
- }
3520
- function extractExportNames(src) {
3521
- const names = /* @__PURE__ */ new Set();
3522
- const re = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/gm;
3523
- let m;
3524
- while ((m = re.exec(src)) !== null) {
3525
- if (m[1]) names.add(m[1]);
3526
- }
3527
- for (const dm of src.matchAll(/^\s*export\s+default\s+(\w+)\s*;/gm)) {
3528
- if (dm[1]) names.add(dm[1]);
3529
- }
3530
- return [...names];
3531
- }
3532
- function extractCvaVariantValues(src) {
3533
- const block = extractVariantsBlock(src);
3534
- if (block === null) return [];
3535
- const names = /* @__PURE__ */ new Set();
3536
- for (const groupBody of extractGroupBodies(block)) {
3537
- for (const km of groupBody.matchAll(/^\s*(?:['"]?)(\w+)(?:['"]?)\s*:/gm)) {
3538
- if (km[1]) names.add(km[1]);
3539
- }
3540
- }
3541
- return [...names];
3542
- }
3543
- function extractVariantsBlock(src) {
3544
- const idx = src.search(/\bvariants\s*:\s*\{/);
3545
- if (idx < 0) return null;
3546
- const open = src.indexOf("{", idx);
3547
- if (open < 0) return null;
3548
- let depth = 0;
3549
- for (let i = open; i < src.length; i++) {
3550
- const c = src[i];
3551
- if (c === "{") depth++;
3552
- else if (c === "}") {
3553
- depth--;
3554
- if (depth === 0) return src.slice(open + 1, i);
3555
- }
3556
- }
3557
- return null;
3558
- }
3559
- function* extractGroupBodies(block) {
3560
- const re = /(\w+)\s*:\s*\{/g;
3561
- let m;
3562
- while ((m = re.exec(block)) !== null) {
3563
- const open = block.indexOf("{", m.index);
3564
- if (open < 0) continue;
3565
- let depth = 0;
3566
- for (let i = open; i < block.length; i++) {
3567
- const c = block[i];
3568
- if (c === "{") depth++;
3569
- else if (c === "}") {
3570
- depth--;
3571
- if (depth === 0) {
3572
- yield block.slice(open + 1, i);
3573
- re.lastIndex = i + 1;
3574
- break;
3575
- }
3576
- }
3577
- }
3578
- }
3579
- }
3580
- function setDiff(a, b) {
3581
- const bset = new Set(b);
3582
- return a.filter((x) => !bset.has(x)).sort();
3583
- }
3584
- function collectResourcesByEntry(resources) {
3585
- const out = /* @__PURE__ */ new Map();
3586
- for (const r of resources) {
3587
- const colon = r.id.indexOf(":");
3588
- const eid = colon >= 0 ? r.id.slice(0, colon) : r.id;
3589
- if (!out.has(eid)) out.set(eid, r);
3590
- }
3591
- return out;
3592
- }
3593
- function aggregateByRisk(entries) {
3594
- const out = {};
3595
- for (const e of entries) {
3596
- const k = e.diff.riskLevel;
3597
- out[k] = (out[k] ?? 0) + 1;
3598
- }
3599
- return out;
3600
- }
3601
- function derivePromotion(args) {
3602
- const fileType = classifyPromoteFileType(args.targetName, args.currentSource);
3603
- const featureVector = buildFeatureVector(
3604
- args.currentSource,
3605
- args.incomingSource
3606
- );
3607
- const { recommendedModes, confidence, reasons } = scorePromotionModes(
3608
- fileType,
3609
- featureVector
3610
- );
3611
- return { fileType, featureVector, recommendedModes, confidence, reasons };
3612
- }
3613
- function classifyPromoteFileType(targetName, src) {
3614
- if (targetName.endsWith(".d.ts")) return "type";
3615
- if (/^use-[a-z0-9-]+\.tsx?$/i.test(targetName)) return "hook";
3616
- const hasJsx = /<[A-Za-z][^>]*?>/.test(src);
3617
- const hasReactImport = /from ['"]react['"]/.test(src);
3618
- const hasProvider = /\.Provider\b/.test(src) || /createContext\s*[<(]/.test(src);
3619
- if (hasProvider && (hasJsx || hasReactImport)) return "provider";
3620
- if (hasJsx || /forwardRef\s*[<(]/.test(src)) return "component";
3621
- if (!hasJsx && !hasReactImport) return "util";
3622
- return "component";
3623
- }
3624
- function buildFeatureVector(current, incoming) {
3625
- const curExports = extractExportNames(current);
3626
- const newExports = extractExportNames(incoming);
3627
- const apiAdded = setDiff(curExports, newExports);
3628
- const apiRemoved = setDiff(newExports, curExports);
3629
- const curVariants = extractCvaVariantValues(current);
3630
- const newVariants = extractCvaVariantValues(incoming);
3631
- const cvaAdded = setDiff(curVariants, newVariants);
3632
- const cvaModified = [];
3633
- const sharedVariants = curVariants.filter((v) => newVariants.includes(v));
3634
- for (const v of sharedVariants) {
3635
- if (extractVariantBody(current, v) !== extractVariantBody(incoming, v)) {
3636
- cvaModified.push(v);
3637
- }
3638
- }
3639
- const curClass = extractClassNameLiterals(current);
3640
- const newClass = extractClassNameLiterals(incoming);
3641
- const classNameDiff = curClass !== newClass;
3642
- const curTokens = extractTokenRefs(current);
3643
- const newTokens = extractTokenRefs(incoming);
3644
- const tokenUsageDiff = curTokens.size !== newTokens.size || [...curTokens].some((t) => !newTokens.has(t));
3645
- const hasState = /\buseState\s*[<(]/.test(current);
3646
- const hasEffect = /\b(useEffect|useLayoutEffect|useMemo|useCallback)\s*[<(]/.test(current);
3647
- const curImports = extractImportSources(current);
3648
- const newImports = extractImportSources(incoming);
3649
- const hasExtraImports = [...curImports].some((src) => !newImports.has(src));
3650
- const tagSet = /* @__PURE__ */ new Set();
3651
- for (const m of current.matchAll(/<([A-Z]\w+)[\s/>]/g)) {
3652
- if (m[1]) tagSet.add(m[1]);
3653
- }
3654
- const atomicChildren = [...tagSet];
3655
- const isComposition = atomicChildren.length > 2;
3656
- const signatureChanged = extractDefaultParamList(current) !== extractDefaultParamList(incoming);
3657
- return {
3658
- apiDelta: { added: apiAdded, removed: apiRemoved, signatureChanged },
3659
- styleDelta: { classNameDiff, tokenUsageDiff },
3660
- logicDelta: { hasState, hasEffect, hasExtraImports },
3661
- cvaDelta: { addedVariants: cvaAdded, modifiedVariants: cvaModified },
3662
- structureDelta: { isComposition, atomicChildren }
3663
- };
3664
- }
3665
- function scorePromotionModes(fileType, fv) {
3666
- const reasons = [];
3667
- if (fileType === "hook" || fileType === "util" || fileType === "type") {
3668
- reasons.push(
3669
- `fileType=${fileType} \u2014 not a component, deferred to ManualReview`
3670
- );
3671
- return { recommendedModes: ["ManualReview"], confidence: 0.5, reasons };
3672
- }
3673
- const apiNoChange = fv.apiDelta.added.length === 0 && fv.apiDelta.removed.length === 0 && !fv.apiDelta.signatureChanged;
3674
- const logicMinimal = !fv.logicDelta.hasState && !fv.logicDelta.hasEffect && !fv.logicDelta.hasExtraImports;
3675
- if (fv.apiDelta.removed.length > 0 || fv.apiDelta.signatureChanged) {
3676
- reasons.push(
3677
- "signature changed or props removed \u2014 Coexist preserves user version"
3678
- );
3679
- return { recommendedModes: ["Coexist"], confidence: 0.85, reasons };
3680
- }
3681
- if (apiNoChange && logicMinimal && (fv.styleDelta.classNameDiff || fv.styleDelta.tokenUsageDiff) && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
3682
- reasons.push(
3683
- "only style / token differences \u2014 push to tokens.overrides.css"
3684
- );
3685
- return { recommendedModes: ["TokenOnly"], confidence: 0.8, reasons };
3686
- }
3687
- const modes = [];
3688
- let score = 0;
3689
- if (fv.cvaDelta.addedVariants.length > 0 || fv.cvaDelta.modifiedVariants.length > 0) {
3690
- modes.push("Variant");
3691
- reasons.push(
3692
- `cva variants delta: +${fv.cvaDelta.addedVariants.length} ~${fv.cvaDelta.modifiedVariants.length}`
3693
- );
3694
- score = Math.max(score, 0.7);
3695
- }
3696
- if (fv.logicDelta.hasState || fv.logicDelta.hasEffect || fv.logicDelta.hasExtraImports || fv.apiDelta.added.length > 0) {
3697
- modes.push("Wrapper");
3698
- reasons.push("user added state / effect / imports / props");
3699
- score = Math.max(score, 0.75);
3700
- }
3701
- if (apiNoChange && logicMinimal && !fv.styleDelta.classNameDiff && !fv.styleDelta.tokenUsageDiff && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
3702
- modes.push("Preset");
3703
- reasons.push("no API/logic delta \u2014 Preset captures default-prop tweaks");
3704
- score = Math.max(score, 0.6);
3705
- }
3706
- if (fv.structureDelta.isComposition) {
3707
- modes.push("Compose");
3708
- reasons.push(
3709
- `composition of ${fv.structureDelta.atomicChildren.length} atomic children`
3710
- );
3711
- score = Math.max(score, 0.65);
3712
- }
3713
- if (modes.length === 0) {
3714
- reasons.push("no axis crossed the 0.6 threshold");
3715
- return { recommendedModes: ["ManualReview"], confidence: 0.4, reasons };
3716
- }
3717
- return { recommendedModes: modes, confidence: score, reasons };
3718
- }
3719
- function extractVariantBody(src, key) {
3720
- const block = extractVariantsBlock(src);
3721
- if (block === null) return "";
3722
- const re = new RegExp(`(?:["']?${key}["']?)\\s*:\\s*\\{`);
3723
- const idx = block.search(re);
3724
- if (idx < 0) return "";
3725
- const open = block.indexOf("{", idx);
3726
- if (open < 0) return "";
3727
- let depth = 0;
3728
- for (let i = open; i < block.length; i++) {
3729
- const c = block[i];
3730
- if (c === "{") depth++;
3731
- else if (c === "}") {
3732
- depth--;
3733
- if (depth === 0) return block.slice(open + 1, i);
3734
- }
3735
- }
3736
- return "";
3737
- }
3738
- function extractClassNameLiterals(src) {
3739
- const out = [];
3740
- for (const m of src.matchAll(/className\s*=\s*["'`]([^"'`]*)["'`]/g)) {
3741
- if (m[1]) out.push(m[1]);
3742
- }
3743
- for (const m of src.matchAll(/\b(?:cn|clsx|cva)\s*\(/g)) {
3744
- const open = (m.index ?? 0) + m[0].length - 1;
3745
- let depth = 1;
3746
- let i = open + 1;
3747
- for (; i < src.length && depth > 0; i++) {
3748
- const c = src[i];
3749
- if (c === "(") depth++;
3750
- else if (c === ")") depth--;
3751
- }
3752
- const body = src.slice(open + 1, i - 1);
3753
- for (const lit of body.matchAll(/["'`]([^"'`]*)["'`]/g)) {
3754
- if (lit[1]) out.push(lit[1]);
3755
- }
3756
- }
3757
- return out.sort().join("|");
3758
- }
3759
- function extractTokenRefs(src) {
3760
- const out = /* @__PURE__ */ new Set();
3761
- for (const m of src.matchAll(/var\(--([a-z0-9-]+)\)/g)) {
3762
- if (m[1]) out.add(m[1]);
3763
- }
3764
- for (const m of src.matchAll(/--([a-z][a-z0-9-]*)\s*:/g)) {
3765
- if (m[1]) out.add(m[1]);
3766
- }
3767
- return out;
3768
- }
3769
- function extractImportSources(src) {
3770
- const out = /* @__PURE__ */ new Set();
3771
- for (const m of src.matchAll(/^\s*import\b[^'"]*['"]([^'"]+)['"]/gm)) {
3772
- if (m[1]) out.add(m[1]);
3773
- }
3774
- return out;
3775
- }
3776
- function extractDefaultParamList(src) {
3777
- 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);
3778
- return m?.[1]?.replace(/\s+/g, " ").trim() ?? "";
3779
- }
3780
-
3781
- // src/core/ui-upgrade.ts
3782
- var nodeRequire = createRequire5(import.meta.url);
3783
- function resolvePackageRoot4(packageName) {
3784
- const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
3785
- return path22.dirname(pkgJsonPath);
3786
- }
3787
- async function runUiUpgrade(options) {
3788
- const { projectRoot, category, ids = [], trigger } = options;
3789
- const config = await readProjectConfig(projectRoot);
3790
- if (!config) return { status: "not-initialized" };
3791
- const cfgKey = category === "ui" ? "ui" : "biz-ui";
3792
- if (!config.packages?.[cfgKey]) {
3793
- return { status: "not-installed", detail: `${category} not installed` };
3794
- }
3795
- const aliases = config.packages.ui?.aliases ?? config.packages["biz-ui"]?.aliases;
3796
- if (!aliases) {
3797
- return {
3798
- status: "not-installed",
3799
- detail: `${category} aliases not configured (run \`teamix-evo ui init\` first)`
3800
- };
3801
- }
3802
- const installed = await readInstalledManifest(projectRoot);
3803
- const lineageReport = await detectComponentLineage({
3804
- projectRoot,
3805
- category,
3806
- config,
3807
- installed
3808
- });
3809
- if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
3810
- return {
3811
- status: "skipped",
3812
- detail: `lineage=${lineageReport.lineage}; nothing to stage`,
3813
- lineage: lineageReport.lineage
3814
- };
3815
- }
3816
- if (ids.length > 0) {
3817
- const knownIds = /* @__PURE__ */ new Set([
3818
- ...lineageReport.registeredIds,
3819
- ...lineageReport.unregisteredIds
3820
- ]);
3821
- const unknown = ids.filter((id) => !knownIds.has(id));
3822
- if (unknown.length > 0) {
3823
- throw new Error(
3824
- `Unknown ${category} component id(s): ${unknown.map((s) => `"${s}"`).join(
3825
- ", "
3826
- )}. Hint: \`teamix-evo ${category} list\` shows available ids.`
3827
- );
3828
- }
3829
- }
3830
- const built = await buildStaging({
3831
- category,
3832
- projectRoot,
3833
- aliases,
3834
- lineageReport,
3835
- trigger,
3836
- onlyIds: ids,
3837
- uiPackageRoot: options.uiPackageRoot,
3838
- bizUiPackageRoot: options.bizUiPackageRoot
3839
- });
3840
- if (built === null) {
3841
- return {
3842
- status: "skipped",
3843
- detail: "no entries to stage",
3844
- lineage: lineageReport.lineage
3845
- };
3846
- }
3847
- return {
3848
- status: "staged",
3849
- stagingDir: built.stagingDir,
3850
- manifest: built.manifest
3851
- };
3852
- }
3853
- async function buildStaging(args) {
3854
- const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
3855
- if (category === "ui") {
3856
- const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
3857
- const manifest = await loadUiPackageManifest3(root);
3858
- return buildUiUpgradeStaging({
3859
- projectRoot,
3860
- category,
3861
- manifest,
3862
- packageRoot: root,
3863
- aliases,
3864
- lineageReport,
3865
- trigger,
3866
- onlyIds
3867
- });
3868
- }
3869
- const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
3870
- const variant = lineageReport.installedVariant ?? "_flat";
3871
- const variantDir = path22.join(bizRoot, "variants", variant);
3872
- const variantManifest = await loadVariantUiPackageManifest2(variantDir);
3873
- const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
3874
- const uiManifest = await loadUiPackageManifest3(uiRoot);
3875
- const entryPackageRoot = /* @__PURE__ */ new Map();
3876
- const merged = [];
3877
- for (const e of variantManifest.entries) {
3878
- entryPackageRoot.set(e.id, variantDir);
3879
- merged.push(e);
3880
- }
3881
- for (const e of uiManifest.entries) {
3882
- if (entryPackageRoot.has(e.id)) continue;
3883
- entryPackageRoot.set(e.id, uiRoot);
3884
- merged.push(e);
3885
- }
3886
- const synthetic = {
3887
- schemaVersion: 1,
3888
- package: "ui",
3889
- version: variantManifest.version,
3890
- engines: variantManifest.engines,
3891
- entries: merged
3892
- };
3893
- return buildUiUpgradeStaging({
3894
- projectRoot,
3895
- category,
3896
- manifest: synthetic,
3897
- packageRoot: variantDir,
3898
- entryPackageRoot,
3899
- aliases,
3900
- lineageReport,
3901
- trigger,
3902
- onlyIds
3903
- });
3904
- }
3905
-
3906
- // src/core/deps-install.ts
3907
- import * as fs17 from "fs/promises";
3908
- import * as path23 from "path";
3909
- import { exec } from "child_process";
3910
- import { promisify } from "util";
3911
- var execAsync = promisify(exec);
3912
- async function detectPackageManager(projectRoot) {
3913
- if (await fileExists(path23.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3914
- if (await fileExists(path23.join(projectRoot, "pnpm-workspace.yaml")))
3915
- return "pnpm";
3916
- if (await fileExists(path23.join(projectRoot, "bun.lockb"))) return "bun";
3917
- if (await fileExists(path23.join(projectRoot, "bun.lock"))) return "bun";
3918
- if (await fileExists(path23.join(projectRoot, "yarn.lock"))) return "yarn";
3919
- return "npm";
3920
- }
3921
- function getInstallCommand(pm) {
3922
- switch (pm) {
3923
- case "pnpm":
3924
- return "pnpm install";
3925
- case "yarn":
3926
- return "yarn install";
3927
- case "bun":
3928
- return "bun install";
3929
- case "npm":
3930
- return "npm install";
3931
- }
3932
- }
3933
- async function installProjectDeps(options) {
3934
- const { projectRoot, npmDependencies, skipInstall = false } = options;
3935
- const pkgPath = path23.join(projectRoot, "package.json");
3936
- const raw = await readFileOrNull(pkgPath);
3937
- if (!raw) {
3938
- throw new Error(
3939
- `package.json not found at ${projectRoot}. Cannot install dependencies.`
3940
- );
3941
- }
3942
- const pkg = JSON.parse(raw);
3943
- const existingDeps = {
3944
- ...pkg.dependencies,
3945
- ...pkg.devDependencies
3946
- };
3947
- const added = {};
3948
- const existed = {};
3949
- for (const [name, range] of Object.entries(npmDependencies)) {
3950
- if (existingDeps[name]) {
3951
- existed[name] = existingDeps[name];
3952
- } else {
3953
- added[name] = range;
3954
- }
3955
- }
3956
- if (Object.keys(added).length > 0) {
3957
- if (!pkg.dependencies) pkg.dependencies = {};
3958
- for (const [name, range] of Object.entries(added)) {
3959
- pkg.dependencies[name] = range;
3960
- }
3961
- pkg.dependencies = Object.fromEntries(
3962
- Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
3963
- );
3964
- await fs17.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
3965
- logger.info(
3966
- ` patched package.json: +${Object.keys(added).length} dependencies`
3967
- );
3968
- }
3969
- const packageManager = await detectPackageManager(projectRoot);
3970
- let installed = false;
3971
- if (!skipInstall && Object.keys(added).length > 0) {
3972
- const cmd = getInstallCommand(packageManager);
3973
- logger.info(` running ${cmd}...`);
3974
- try {
3975
- await execAsync(cmd, { cwd: projectRoot, timeout: 12e4 });
3976
- installed = true;
3977
- logger.info(" install complete");
3978
- } catch (err) {
3979
- logger.warn(
3980
- ` install failed: ${err instanceof Error ? err.message : String(err)}`
3981
- );
3982
- logger.warn(" please run install manually");
3983
- }
3984
- }
3985
- return { added, existed, installed, packageManager };
3986
- }
3987
-
3988
- // src/core/ui-migration-plan-template.ts
3989
- import * as fs18 from "fs/promises";
3990
- import * as path24 from "path";
3991
- async function generateMigrationPlan(options) {
3992
- const { projectRoot, strategy, isolateResult, addResult, residualImports } = options;
3993
- const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3994
- const planDir = path24.join(projectRoot, ".qoder", "plans");
3995
- await ensureDir(planDir);
3996
- const planPath = path24.join(planDir, "teamix-evo-ui-migration.md");
3997
- const movedSection = isolateResult.movedFiles.length > 0 ? isolateResult.movedFiles.map((f) => `| \`${f.from}\` | \`${f.to}\` |`).join("\n") : "| _(\u65E0\u642C\u8FC1)_ | |";
3998
- const installedSection = addResult.orderedIds.map((id) => `- \`${id}\``).join("\n");
3999
- const rewriteCount = isolateResult.importRewrites.size;
4000
- const rewriteSection = rewriteCount > 0 ? [...isolateResult.importRewrites.entries()].map(([file, count]) => `- \`${file}\`: ${count} \u5904`).join("\n") : "_(\u65E0 import \u91CD\u5199)_";
4001
- const strategyLabel = strategy === "isolate-progressive" ? "\u9694\u79BB + \u6E10\u8FDB\u8FC1\u79FB\uFF08Path A\uFF09" : strategy === "isolate-aggressive" ? "\u9694\u79BB + \u4E3B\u52A8\u5347\u7EA7\uFF08Path C\uFF09" : "\u8DF3\u8FC7\u5DF2\u6709\u6587\u4EF6";
4002
- const residualSection = residualImports != null && residualImports > 0 ? `
4003
- ## \u6B8B\u7559 import \u6E05\u5355
4004
-
4005
- \u53D1\u73B0 ${residualImports} \u5904\u6B8B\u7559\u5F15\u7528\uFF0C\u9700\u4EBA\u5DE5\u4FEE\u590D\u3002\u8BE6\u89C1 lint \u8F93\u51FA\u4E2D\u7684 \`no-legacy-shadcn-import\` \u8B66\u544A\u3002
4006
- ` : "";
4007
- const nextSteps = strategy === "isolate-aggressive" ? `1. \u67E5\u770B \`.teamix-evo/.upgrade-staging/\` \u4E2D\u7684\u5347\u7EA7 staging
4008
- 2. \u89E6\u53D1 \`teamix-evo-upgrade\` skill \u8FDB\u884C\u5206\u6279\u66FF\u6362
4009
- 3. \u9010\u6B65\u5C06 \`shadcn-ui/\` \u4E2D\u7684\u7EC4\u4EF6\u66FF\u6362\u4E3A \`ui/\` \u7248\u672C
4010
- 4. \u66FF\u6362\u5B8C\u6210\u540E\u5220\u9664 \`shadcn-ui/\` \u76EE\u5F55
4011
- 5. \u8FD0\u884C \`npx teamix-evo tokens treat\` \u8FDB\u884C token \u6CBB\u7406` : `1. \u9010\u6B65\u5C06\u4E1A\u52A1\u4EE3\u7801\u4E2D\u7684 \`@/components/shadcn-ui/...\` \u66FF\u6362\u4E3A \`@/components/ui/...\`
4012
- 2. ESLint \`no-legacy-shadcn-import\` \u89C4\u5219\u4F1A\u63D0\u793A\u54EA\u4E9B\u6587\u4EF6\u8FD8\u5728\u5F15\u7528\u65E7\u7EC4\u4EF6
4013
- 3. \u5168\u90E8\u66FF\u6362\u5B8C\u6210\u540E\u5220\u9664 \`src/components/shadcn-ui/\` \u76EE\u5F55
4014
- 4. \u8FD0\u884C \`npx teamix-evo tokens treat\` \u8FDB\u884C token \u6CBB\u7406`;
4015
- const content = `# teamix-evo UI \u8FC1\u79FB\u8BA1\u5212
4016
-
4017
- > \u751F\u6210\u65F6\u95F4: ${now}
4018
- > \u8FC1\u79FB\u7B56\u7565: ${strategyLabel}
4019
-
4020
- ## \u642C\u8FC1\u8BB0\u5F55
4021
-
4022
- | \u539F\u8DEF\u5F84 | \u65B0\u8DEF\u5F84 |
4023
- | ------ | ------ |
4024
- ${movedSection}
4025
-
4026
- ## \u5DF2\u5B89\u88C5 teamix-evo \u7EC4\u4EF6\uFF08${addResult.orderedIds.length} \u4E2A\uFF09
4027
-
4028
- ${installedSection}
4029
-
4030
- ## Import \u91CD\u5199\u8BB0\u5F55\uFF08${rewriteCount} \u4E2A\u6587\u4EF6\uFF09
4031
-
4032
- ${rewriteSection}
4033
-
4034
- ## components.json
4035
-
4036
- ${isolateResult.componentsJsonRemoved ? "\u5DF2\u5907\u4EFD\u81F3 `.teamix-evo/.backups/` \u5E76\u5220\u9664\u539F\u6587\u4EF6\u3002" : "\u672A\u53D1\u73B0 components.json\u3002"}
4037
-
4038
- ## \u5907\u4EFD\u6587\u4EF6
4039
-
4040
- ${isolateResult.backedUpFiles.length > 0 ? isolateResult.backedUpFiles.map((f) => `- \`${f}\``).join("\n") : "_(\u65E0\u5907\u4EFD)_"}
4041
- ${residualSection}
4042
- ## \u540E\u7EED\u6B65\u9AA4
4043
-
4044
- ${nextSteps}
4045
-
4046
- ## npm \u4F9D\u8D56
4047
-
4048
- \u5DF2\u81EA\u52A8\u5B89\u88C5\u7684\u4F9D\u8D56:
4049
- ${Object.entries(addResult.npmDependencies).length > 0 ? Object.entries(addResult.npmDependencies).map(([name, version]) => `- \`${name}@${version}\``).join("\n") : "_(\u65E0\u989D\u5916\u4F9D\u8D56)_"}
4050
- `;
4051
- await fs18.writeFile(planPath, content, "utf-8");
4052
- return planPath;
4053
- }
4054
-
4055
- // src/core/residual-import-detector.ts
4056
- import * as fs19 from "fs/promises";
4057
- import * as path25 from "path";
4058
- var LEGACY_PATTERNS = [
4059
- /['"`]@\/components\/shadcn-ui\//,
4060
- /['"`]~\/components\/shadcn-ui\//,
4061
- /['"`]\.\.?\/.*shadcn-ui\//,
4062
- /from\s+['"].*shadcn-ui\//
4063
- ];
4064
- async function detectResidualImports(projectRoot) {
4065
- const srcDir = path25.join(projectRoot, "src");
4066
- const SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
4067
- ".ts",
4068
- ".tsx",
4069
- ".js",
4070
- ".jsx",
4071
- ".mdx",
4072
- ".css",
4073
- ".scss"
4074
- ]);
4075
- let filesScanned = 0;
4076
- const entries = [];
4077
- const affectedFileSet = /* @__PURE__ */ new Set();
4078
- if (await fileExists(srcDir)) {
4079
- const allFiles = await walkDir(srcDir, DEFAULT_SKIP_DIRS);
4080
- const scanFiles = allFiles.filter(
4081
- (f) => SCAN_EXTENSIONS.has(path25.extname(f))
4082
- );
4083
- for (const file of scanFiles) {
4084
- filesScanned++;
4085
- const content = await fs19.readFile(file, "utf-8");
4086
- const lines = content.split("\n");
4087
- const relPath = path25.relative(projectRoot, file).replace(/\\/g, "/");
4088
- for (let i = 0; i < lines.length; i++) {
4089
- const line = lines[i];
4090
- for (const pattern of LEGACY_PATTERNS) {
4091
- if (pattern.test(line)) {
4092
- entries.push({
4093
- file: relPath,
4094
- line: i + 1,
4095
- content: line.trim()
4096
- });
4097
- affectedFileSet.add(relPath);
4098
- break;
4099
- }
4100
- }
4101
- }
4102
- }
4103
- }
4104
- const rootConfigFiles = [
4105
- "vite.config.ts",
4106
- "vite.config.js",
4107
- "next.config.ts",
4108
- "next.config.js",
4109
- "tsconfig.json",
4110
- ".storybook/main.ts",
4111
- ".storybook/main.js"
4112
- ];
4113
- for (const configFile of rootConfigFiles) {
4114
- const configPath = path25.join(projectRoot, configFile);
4115
- if (await fileExists(configPath)) {
4116
- filesScanned++;
4117
- const content = await fs19.readFile(configPath, "utf-8");
4118
- const lines = content.split("\n");
4119
- for (let i = 0; i < lines.length; i++) {
4120
- const line = lines[i];
4121
- for (const pattern of LEGACY_PATTERNS) {
4122
- if (pattern.test(line)) {
4123
- entries.push({
4124
- file: configFile,
4125
- line: i + 1,
4126
- content: line.trim()
4127
- });
4128
- affectedFileSet.add(configFile);
4129
- break;
4130
- }
4131
- }
4132
- }
4133
- }
4134
- }
4135
- return {
4136
- filesScanned,
4137
- entries,
4138
- affectedFiles: affectedFileSet.size
4139
- };
4140
- }
4141
-
4142
- // src/core/init-checklist-template.ts
4143
- var STEP_LABELS = {
4144
- tokens: "tokens \u2014 design tokens \u5B89\u88C5",
4145
- skills: "skills \u2014 AI skills \u5B89\u88C5",
4146
- "agents-md": "agents-md \u2014 AGENTS.md \u751F\u6210",
4147
- "ui-init": "ui-init \u2014 UI \u914D\u7F6E\u521D\u59CB\u5316",
4148
- "ui-add": "ui-add \u2014 UI \u7EC4\u4EF6\u5B89\u88C5",
4149
- lint: "lint \u2014 ESLint + Stylelint \u914D\u7F6E",
4150
- gitignore: "gitignore \u2014 .gitignore \u8FFD\u52A0"
4151
- };
4152
- function renderInitChecklist(args) {
4153
- const { variant, status, steps } = args;
4154
- const ts = args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
4155
- const phaseA = steps.map((s) => {
4156
- const label = STEP_LABELS[s.name] ?? s.name;
4157
- const checked = s.status === "ok" ? "x" : " ";
4158
- const suffix = s.status === "ok" ? "" : s.status === "skip" ? "\uFF08\u8DF3\u8FC7\uFF09" : s.status === "fail" ? `\uFF08\u5931\u8D25\uFF1A${s.detail ?? "unknown"}\uFF09` : "\uFF08\u8BA1\u5212\u4E2D\uFF09";
4159
- return `- [${checked}] ${label}${suffix}`;
4160
- }).join("\n");
4161
- return `# teamix-evo init \u68C0\u67E5\u6E05\u5355
4162
-
4163
- > \u7531 \`teamix-evo init\` \u81EA\u52A8\u751F\u6210 \xB7 ${ts}
4164
- > variant: ${variant} \xB7 status: ${status}
4165
-
4166
- ## Phase A \xB7 CLI \u843D\u5730\uFF08\u81EA\u52A8\u5B8C\u6210\uFF09
4167
-
4168
- ${phaseA}
4169
-
4170
- ## Phase B \xB7 AI \u4E32\u573A\uFF086 \u6B65\u95ED\u73AF\uFF09
4171
-
4172
- > \u4EE5\u4E0B\u6B65\u9AA4\u7531\u5BA2\u6237\u4FA7 AI \u5728 IDE \u4E2D\u9010\u6B65\u6267\u884C\u3002\u26A0\uFE0F \u6807\u8BB0\u7684\u6B65\u9AA4\u4E3A HARD GATE\uFF0C
4173
- > \u5FC5\u987B\u6267\u884C\u5B8C\u6BD5\u5E76\u5F81\u5F97\u7528\u6237\u786E\u8BA4\u540E\u624D\u80FD\u7EE7\u7EED\uFF08\u53C2\u89C1 manage SKILL.md\u300CHARD GATE \u534F\u8BAE\u300D\uFF09\u3002
4174
-
4175
- - [ ] \u26A0\uFE0F HARD GATE\uFF1Aadopt \u2014 \`npx teamix-evo ui add --adopt\`
4176
- - [ ] \u26A0\uFE0F HARD GATE\uFF1Aupgrade staging \u2014 \`npx teamix-evo ui upgrade\`\uFF08+ \`biz-ui upgrade\`\uFF09
4177
- - [ ] \u26A0\uFE0F HARD GATE\uFF1Atokens audit \u2014 \`npx teamix-evo tokens audit\`
4178
- - [ ] AGENTS.md \u56DE\u586B \u2014 \u5F52\u7C7B\u9879\u76EE\u7279\u6709\u89C4\u5219\uFF0C\u585E\u56DE managed \u533A\u57DF\u4E4B\u524D
4179
- - [ ] lint \u2014 \`npx teamix-evo lint init -y\` + \`pnpm lint\`
4180
- - [ ] \u26A0\uFE0F HARD GATE\uFF1Atoken \u6CBB\u7406 \u2014 \u89E6\u53D1 \`teamix-evo-upgrade\` skill Part C\uFF08Token treatment pipeline\uFF09
4181
-
4182
- ## \u5907\u6CE8
4183
-
4184
- - Phase A \u4E2D\u5931\u8D25\u6216\u8DF3\u8FC7\u7684\u6B65\u9AA4\u4E0D\u5F71\u54CD Phase B \u542F\u52A8\uFF0C\u4F46\u5EFA\u8BAE\u5148\u4FEE\u590D\u518D\u7EE7\u7EED
4185
- - \u4FEE\u590D\u540E\u91CD\u8DD1 \`teamix-evo init\`\uFF08\u6BCF\u6B65\u5E42\u7B49\uFF0C\u81EA\u52A8\u8DF3\u8FC7\u5DF2\u5B8C\u6210\u9879\uFF09
4186
- - \u6062\u590D\u5230\u6267\u884C\u524D\u72B6\u6001\uFF1A\`teamix-evo restore --list\` \u2192 \`teamix-evo restore <ts>\`
4187
- `;
4188
- }
4189
-
4190
- // src/core/snapshot.ts
4191
- import * as fs20 from "fs/promises";
4192
- import * as path26 from "path";
4193
- var TEAMIX_DIR3 = ".teamix-evo";
4194
- var SNAPSHOTS_DIR = ".snapshots";
4195
- var LOGS_DIR = "logs";
4196
- var META_FILE = "_meta.json";
4197
- var DEFAULT_KEEP = 5;
4198
- function isoToFsSafe2(iso) {
4199
- return iso.replace(/[:.]/g, "-");
4200
- }
4201
- function fsSafeToIso(safe) {
4202
- return safe.replace(
4203
- /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})(Z)$/,
4204
- "$1T$2:$3:$4.$5$6"
4205
- );
4206
- }
4207
- async function createSnapshot(projectRoot, opts = {}) {
4208
- const teamixDir = path26.join(projectRoot, TEAMIX_DIR3);
4209
- try {
4210
- const stat5 = await fs20.stat(teamixDir);
4211
- if (!stat5.isDirectory()) return null;
4212
- } catch (err) {
4213
- if (err.code === "ENOENT") return null;
4214
- throw err;
4215
- }
4216
- const isoTs = (/* @__PURE__ */ new Date()).toISOString();
4217
- const ts = isoToFsSafe2(isoTs);
4218
- const snapshotRoot = path26.join(teamixDir, SNAPSHOTS_DIR);
4219
- const target = path26.join(snapshotRoot, ts);
4220
- await fs20.mkdir(target, { recursive: true });
4221
- const entries = await fs20.readdir(teamixDir, { withFileTypes: true });
4222
- for (const entry of entries) {
4223
- if (entry.name === SNAPSHOTS_DIR) continue;
4224
- if (entry.name === LOGS_DIR) continue;
4225
- const src = path26.join(teamixDir, entry.name);
4226
- const dst = path26.join(target, entry.name);
4227
- await fs20.cp(src, dst, { recursive: true });
4228
- }
4229
- const meta = {
4230
- ts: isoTs,
4231
- reason: opts.reason ?? "manual"
4232
- };
4233
- await fs20.writeFile(
4234
- path26.join(target, META_FILE),
4235
- JSON.stringify(meta, null, 2) + "\n",
4236
- "utf-8"
4237
- );
4238
- logger.debug(
4239
- `Snapshot created \u2192 ${path26.relative(projectRoot, target)} (${meta.reason})`
4240
- );
4241
- const keep = opts.keep ?? DEFAULT_KEEP;
4242
- await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
4243
- return { ts, path: target };
4244
- }
4245
- async function listSnapshots(projectRoot) {
4246
- const snapshotRoot = path26.join(projectRoot, TEAMIX_DIR3, SNAPSHOTS_DIR);
4247
- let entries;
4248
- try {
4249
- entries = await fs20.readdir(snapshotRoot, { withFileTypes: true });
4250
- } catch (err) {
4251
- if (err.code === "ENOENT") return [];
4252
- throw err;
4253
- }
4254
- const result = [];
4255
- for (const entry of entries) {
4256
- if (!entry.isDirectory()) continue;
4257
- const dir = path26.join(snapshotRoot, entry.name);
4258
- let isoTs = null;
4259
- let reason = null;
4260
- try {
4261
- const raw = await fs20.readFile(path26.join(dir, META_FILE), "utf-8");
4262
- const parsed = JSON.parse(raw);
4263
- if (typeof parsed.ts === "string") isoTs = parsed.ts;
4264
- if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
4265
- parsed.reason
4266
- )) {
4267
- reason = parsed.reason;
4268
- }
4269
- } catch {
4270
- isoTs = fsSafeToIso(entry.name);
4271
- }
4272
- result.push({ ts: entry.name, isoTs, reason, path: dir });
4273
- }
4274
- result.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0);
4275
- return result;
4276
- }
4277
- async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
4278
- if (keep < 0)
4279
- throw new Error(`pruneSnapshots: keep must be >= 0, got ${keep}`);
4280
- const snapshots = await listSnapshots(projectRoot);
4281
- if (snapshots.length <= keep) return [];
4282
- const tail = snapshots.slice(keep);
4283
- const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
4284
- const removed = [];
4285
- for (const snap of toRemove) {
4286
- await fs20.rm(snap.path, { recursive: true, force: true });
4287
- removed.push(snap.ts);
4288
- logger.debug(`Pruned snapshot ${snap.ts}`);
4289
- }
4290
- return removed.reverse();
4291
- }
4292
-
4293
- // src/core/file-changes.ts
4294
- import * as fs21 from "fs/promises";
4295
- import * as path27 from "path";
4296
- function toRelativePosix(p, projectRoot) {
4297
- let rel2 = p;
4298
- if (path27.isAbsolute(p)) {
4299
- rel2 = path27.relative(projectRoot, p);
4300
- }
4301
- return rel2.split(path27.sep).join("/");
4302
- }
4303
- async function listBackupOriginals(projectRoot) {
4304
- const backupsDir = path27.join(projectRoot, ".teamix-evo", ".backups");
4305
- const out = /* @__PURE__ */ new Set();
4306
- const stack = [backupsDir];
4307
- while (stack.length > 0) {
4308
- const dir = stack.pop();
4309
- let entries;
4310
- try {
4311
- entries = await fs21.readdir(dir, { withFileTypes: true });
4312
- } catch (err) {
4313
- if (err.code === "ENOENT") continue;
4314
- throw err;
4315
- }
4316
- for (const e of entries) {
4317
- const full = path27.join(dir, e.name);
4318
- if (e.isDirectory()) {
4319
- stack.push(full);
4320
- } else if (e.isFile() && e.name.endsWith(".bak")) {
4321
- const rel2 = path27.relative(backupsDir, full);
4322
- const original = stripBackupSuffix(rel2);
4323
- if (original) out.add(original.split(path27.sep).join("/"));
4324
- }
4325
- }
4326
- }
4327
- return out;
4328
- }
4329
- function stripBackupSuffix(rel2) {
4330
- const m = rel2.match(
4331
- /^(.+)\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.bak$/
4332
- );
4333
- return m?.[1] ?? null;
4334
- }
4335
- function diffBackupSet(before, after) {
4336
- const out = /* @__PURE__ */ new Set();
4337
- for (const p of after) {
4338
- if (!before.has(p)) out.add(p);
4339
- }
4340
- return out;
4341
- }
4342
-
4343
- // src/core/project-init.ts
4344
- import * as fsNode from "fs/promises";
4345
- import * as path28 from "path";
4346
- var BASELINE_UI_ENTRIES = [
4347
- "button",
4348
- "button-group",
4349
- "input",
4350
- "form",
4351
- "card",
4352
- "collapsible",
4353
- "dialog",
4354
- "dropdown-menu",
4355
- "tabs",
4356
- "table",
4357
- "sidebar",
4358
- "page-shell",
4359
- "page-header"
4360
- ];
4361
- var CRITICAL_STEPS = /* @__PURE__ */ new Set([
4362
- "tokens",
4363
- "skills",
4364
- "ui-init"
4365
- ]);
4366
- var IMPLEMENTED_STRATEGIES = {
4367
- // 'agents-md': both 'merge-managed' (Phase 2.B — splice the
4368
- // teamix-evo-skills managed region) and 'overwrite' (full rewrite) write
4369
- // through `runGenerateAgentsMd`.
4370
- "agents-md": ["merge-managed", "overwrite", "skip"],
4371
- tokens: ["migrate", "overwrite", "skip"],
4372
- "components-json": ["overwrite", "skip"],
4373
- "shadcn-source": ["overwrite", "skip-existing", "skip"],
4374
- "tailwind-config": ["skip"],
4375
- "index-css": ["skip"],
4376
- // Phase 3.E: lint conflict strategies are honored end-to-end by
4377
- // `runLintInit` (backup user file + write template, optional AI-assist hint).
4378
- "eslint-config": ["merge", "backup-overwrite", "skip", "overwrite"],
4379
- "stylelint-config": ["merge", "backup-overwrite", "skip", "overwrite"]
4380
- };
4381
- function pickIde(ides) {
4382
- return ides[0] ?? "qoder";
4383
- }
4384
- function strategyImplemented(key, strategy) {
4385
- return IMPLEMENTED_STRATEGIES[key]?.includes(strategy) ?? false;
4386
- }
4387
- async function resolveUiEntries(options) {
4388
- if (options.uiEntries && options.uiEntries.length > 0) {
4389
- return [...options.uiEntries];
4390
- }
4391
- if (options.answers.uiSelection === "all") {
4392
- const { manifest } = await loadUiData("@teamix-evo/ui");
4393
- return manifest.entries.map((e) => e.id);
4394
- }
4395
- return [...BASELINE_UI_ENTRIES];
4396
- }
4397
- function deriveTokensChanges(result, projectRoot) {
4398
- if (result.status !== "installed") return [];
4399
- return result.resources.map((r) => ({
4400
- kind: "created",
4401
- path: toRelativePosix(r.target, projectRoot),
4402
- step: "tokens",
4403
- detail: r.strategy
4404
- }));
4405
- }
4406
- function deriveSkillsChanges(result, projectRoot) {
4407
- if (result.status !== "installed") return [];
4408
- return result.addedSkillIds.map((id) => ({
4409
- kind: "created",
4410
- path: `.teamix-evo/skills/${id}/SKILL.md`,
4411
- step: "skills",
4412
- detail: "skill installed (source mirror + IDE mirrors)"
4413
- }));
4414
- }
4415
- function deriveUiAddChanges(result, projectRoot) {
4416
- const out = [];
4417
- let remaining = result.written;
4418
- for (let i = result.resources.length - 1; i >= 0 && remaining > 0; i--) {
4419
- const r = result.resources[i];
4420
- out.unshift({
4421
- kind: "created",
4422
- path: toRelativePosix(r.target, projectRoot),
4423
- step: "ui-add",
4424
- detail: r.strategy
4425
- });
4426
- remaining--;
4427
- }
4428
- return out;
4429
- }
4430
- function deriveLintChanges(result) {
4431
- if (result.status !== "installed") return [];
4432
- const out = [];
4433
- if (result.eslint) {
4434
- out.push({
4435
- kind: "created",
4436
- path: "eslint.config.js",
4437
- step: "lint",
4438
- detail: "@teamix-evo/eslint-config consumer preset"
4439
- });
4440
- }
4441
- if (result.stylelint) {
4442
- out.push({
4443
- kind: "created",
4444
- path: "stylelint.config.cjs",
4445
- step: "lint",
4446
- detail: "@teamix-evo/stylelint-config consumer preset"
4447
- });
4448
- }
4449
- if (result.packageJsonPatched) {
4450
- out.push({
4451
- kind: "created",
4452
- path: "package.json",
4453
- step: "lint",
4454
- detail: 'scripts.lint / scripts["lint:css"]'
4455
- });
4456
- }
4457
- return out;
4458
- }
4459
- async function runProjectInit(options) {
4460
- const { projectRoot, answers, dryRun = false, onStep } = options;
4461
- const ide = pickIde(answers.ides);
4462
- const steps = [];
4463
- const pending = [];
4464
- const allChanges = [];
4465
- const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
4466
- let snapshot = null;
4467
- let snapshotError;
4468
- if (!dryRun) {
4469
- try {
4470
- snapshot = await createSnapshot(projectRoot, { reason: "init" });
4471
- } catch (err) {
4472
- snapshotError = getErrorMessage(err);
4473
- }
4474
- }
4475
- let aborted = false;
4476
- const firstFailure = {
4477
- value: null
4478
- };
4479
- function record(step) {
4480
- steps.push(step);
4481
- onStep?.(step);
4482
- if (step.changes && step.changes.length > 0) {
4483
- allChanges.push(...step.changes);
4484
- }
4485
- }
4486
- function recordPending(key) {
4487
- const strategy = answers.conflictDecisions[key];
4488
- if (!strategy) return;
4489
- if (strategyImplemented(key, strategy)) return;
4490
- pending.push({
4491
- key,
4492
- strategy,
4493
- reason: `Strategy "${strategy}" requires the managed-region engine (batch 4); recorded for follow-up.`
4494
- });
4495
- }
4496
- function recordFailure(name, err) {
4497
- const message = getErrorMessage(err);
4498
- record({ name, status: "fail", detail: message });
4499
- if (!firstFailure.value)
4500
- firstFailure.value = { step: name, error: message };
4501
- if (CRITICAL_STEPS.has(name)) aborted = true;
4502
- }
4503
- const tokensDecision = answers.conflictDecisions.tokens;
4504
- const legacyTokensPaths = options.legacyTokensPaths ?? [];
4505
- const wantMigrate = tokensDecision === "migrate" && legacyTokensPaths.length > 0;
4506
- if (tokensDecision === "skip") {
4507
- record({
4508
- name: "tokens",
4509
- status: "skip",
4510
- detail: "conflict strategy = skip"
4511
- });
4512
- } else if (dryRun) {
4513
- const planDetail = wantMigrate ? `runTokensInit(variant=${answers.variant}); migrateLegacyTokens(${legacyTokensPaths.length} file${legacyTokensPaths.length === 1 ? "" : "s"})` : `runTokensInit(variant=${answers.variant})`;
4514
- record({
4515
- name: "tokens",
4516
- status: "planned",
4517
- detail: planDetail
4518
- });
4519
- } else {
4520
- try {
4521
- const result = await runTokensInit({
4522
- projectRoot,
4523
- variant: answers.variant,
4524
- ide
4525
- });
4526
- let detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
4527
- if (wantMigrate) {
4528
- try {
4529
- const m = await migrateLegacyTokens({
4530
- projectRoot,
4531
- legacyPaths: legacyTokensPaths
4532
- });
4533
- detail += `; migrated ${m.migrated.length}/${legacyTokensPaths.length} legacy file${legacyTokensPaths.length === 1 ? "" : "s"} \u2192 ${m.overridesPath}`;
4534
- if (m.skipped.length > 0) {
4535
- detail += ` (skipped ${m.skipped.length})`;
4536
- }
4537
- } catch (err) {
4538
- detail += `; migrate failed: ${getErrorMessage(err)}`;
4539
- }
4540
- }
4541
- record({
4542
- name: "tokens",
4543
- status: "ok",
4544
- detail,
4545
- changes: deriveTokensChanges(result, projectRoot)
4546
- });
4547
- } catch (err) {
4548
- recordFailure("tokens", err);
4549
- }
4550
- }
4551
- recordPending("tokens");
4552
- const codeSkillId = `teamix-evo-code-${answers.variant}`;
4553
- if (dryRun) {
4554
- record({
4555
- name: "skills",
4556
- status: "planned",
4557
- detail: `runSkillsAdd(${codeSkillId})`
4558
- });
4559
- } else if (aborted) {
4560
- record({
4561
- name: "skills",
4562
- status: "skip",
4563
- detail: "aborted: earlier critical step failed"
4564
- });
4565
- } else {
4566
- try {
4567
- const result = await runSkillsAdd({
4568
- projectRoot,
4569
- names: [codeSkillId],
4570
- ides: answers.ides,
4571
- scope: answers.scope,
4572
- ide
4573
- });
4574
- record({
4575
- name: "skills",
4576
- status: "ok",
4577
- detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
4578
- changes: deriveSkillsChanges(result, projectRoot)
4579
- });
4580
- } catch (err) {
4581
- recordFailure("skills", err);
4582
- }
4583
- }
4584
- const agentsMdDecision = answers.conflictDecisions["agents-md"];
4585
- const agentsMdSkillIds = [
4586
- `teamix-evo-design-${answers.variant}`,
4587
- codeSkillId
4588
- ];
4589
- if (agentsMdDecision === "skip") {
4590
- record({
4591
- name: "agents-md",
4592
- status: "skip",
4593
- detail: "conflict strategy = skip"
4594
- });
4595
- } else if (dryRun) {
4596
- record({
4597
- name: "agents-md",
4598
- status: "planned",
4599
- detail: `runGenerateAgentsMd(${agentsMdSkillIds.length} skills)`
4600
- });
4601
- } else {
4602
- try {
4603
- const result = await runGenerateAgentsMd({
4604
- projectRoot,
4605
- variant: answers.variant,
4606
- skillIds: agentsMdSkillIds,
4607
- // Phase 2.B: when the user picked `merge-managed` on conflict, only
4608
- // rewrite the `teamix-evo-skills` managed region so hand-written
4609
- // sections survive the regenerate step. `overwrite` keeps the
4610
- // historical full-rewrite default.
4611
- mode: agentsMdDecision === "merge-managed" ? "merge-managed" : "overwrite"
4612
- });
4613
- record({
4614
- name: "agents-md",
4615
- status: "ok",
4616
- detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`,
4617
- changes: [
4618
- {
4619
- kind: result.backedUp ? "modified" : "created",
4620
- path: toRelativePosix(result.path, projectRoot),
4621
- step: "agents-md",
4622
- detail: "skill-trigger fallback (ADR 0038)"
4623
- }
4624
- ]
4625
- });
4626
- } catch (err) {
4627
- recordFailure("agents-md", err);
4628
- }
4629
- }
4630
- recordPending("agents-md");
4631
- const componentsJsonDecision = answers.conflictDecisions["components-json"];
4632
- const shadcnDecision = answers.conflictDecisions["shadcn-source"];
4633
- const skipUiInit = !answers.withUi || componentsJsonDecision === "skip";
4634
- let uiDecisionRequired;
4635
- if (skipUiInit) {
4636
- record({
4637
- name: "ui-init",
4638
- status: "skip",
4639
- detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
4640
- });
4641
- record({
4642
- name: "ui-add",
4643
- status: "skip",
4644
- detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
4645
- });
4646
- } else if (dryRun) {
4647
- record({
4648
- name: "ui-init",
4649
- status: "planned",
4650
- detail: "runUiInit()"
4651
- });
4652
- const entries = await resolveUiEntries(options).catch(() => [
4653
- ...BASELINE_UI_ENTRIES
4654
- ]);
3180
+ if (dryRun) {
4655
3181
  record({
4656
- name: "ui-add",
3182
+ name: "skills",
4657
3183
  status: "planned",
4658
- detail: `runUiAdd(${entries.length} entries: ${entries.slice(0, 3).join(", ")}${entries.length > 3 ? "\u2026" : ""})`
3184
+ detail: `runSkillsInit(scope=${scope})`
4659
3185
  });
4660
- } else {
4661
- if (aborted) {
4662
- record({
4663
- name: "ui-init",
4664
- status: "skip",
4665
- detail: "aborted: earlier critical step failed"
4666
- });
4667
- record({
4668
- name: "ui-add",
4669
- status: "skip",
4670
- detail: "aborted: earlier critical step failed"
4671
- });
4672
- } else {
4673
- let uiInitOk = false;
4674
- try {
4675
- const initResult = await runUiInit({ projectRoot, ide });
4676
- record({
4677
- name: "ui-init",
4678
- status: "ok",
4679
- detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
4680
- });
4681
- uiInitOk = true;
4682
- } catch (err) {
4683
- recordFailure("ui-init", err);
4684
- }
4685
- if (!uiInitOk) {
4686
- record({
4687
- name: "ui-add",
4688
- status: "skip",
4689
- detail: "aborted: ui-init failed"
4690
- });
4691
- } else if (shadcnDecision === "skip") {
4692
- record({
4693
- name: "ui-add",
4694
- status: "skip",
4695
- detail: "shadcn-source conflict strategy = skip"
4696
- });
4697
- } else {
4698
- try {
4699
- const entries = await resolveUiEntries(options);
4700
- const { manifest } = await loadUiData("@teamix-evo/ui");
4701
- const existingConfig = await readProjectConfig(projectRoot).catch(
4702
- () => null
4703
- );
4704
- const aliases = existingConfig?.packages?.ui?.aliases ?? DEFAULT_UI_ALIASES;
4705
- const conflictReport = await detectUiConflicts({
4706
- projectRoot,
4707
- aliases,
4708
- manifest
4709
- });
4710
- if (conflictReport.shouldBlock) {
4711
- let effectiveStrategy = options.uiConflictStrategy;
4712
- if (!effectiveStrategy) {
4713
- if (options.nonInteractive) {
4714
- effectiveStrategy = "isolate-progressive";
4715
- } else {
4716
- uiDecisionRequired = {
4717
- report: conflictReport,
4718
- options: [
4719
- {
4720
- strategy: "isolate-progressive",
4721
- label: "\u9694\u79BB + \u6E10\u8FDB\u8FC1\u79FB\uFF08\u63A8\u8350\uFF09",
4722
- description: "\u5C06\u73B0\u6709 UI \u7EC4\u4EF6\u642C\u8FC1\u5230 shadcn-ui/ \u76EE\u5F55\uFF0C\u81EA\u52A8\u91CD\u5199 import \u8DEF\u5F84\uFF0C\u843D\u5730\u5168\u65B0 teamix-evo \u7EC4\u4EF6\u5230 ui/\uFF0C\u6309\u81EA\u5DF1\u8282\u594F\u9010\u6B65\u8FC1\u79FB\u3002"
4723
- },
4724
- {
4725
- strategy: "isolate-aggressive",
4726
- label: "\u9694\u79BB + \u4E3B\u52A8\u5347\u7EA7",
4727
- description: "\u5728\u6E10\u8FDB\u8FC1\u79FB\u57FA\u7840\u4E0A\uFF0C\u7ACB\u5373\u751F\u6210\u5347\u7EA7 staging\uFF0C\u7531 teamix-evo-upgrade skill \u5F15\u5BFC\u5206\u6279\u66FF\u6362\u3002"
4728
- },
4729
- {
4730
- strategy: "frozen-skip",
4731
- label: "\u8DF3\u8FC7\u5DF2\u6709\u6587\u4EF6\uFF08\u65E7\u6A21\u5F0F\uFF09",
4732
- description: "\u4FDD\u6301\u73B0\u6709\u884C\u4E3A\uFF1A\u5DF2\u6709\u7EC4\u4EF6\u6587\u4EF6\u4E0D\u8986\u76D6\uFF0C\u65B0\u7EC4\u4EF6\u6B63\u5E38\u5B89\u88C5\u3002\u4E0D\u63A8\u8350 \u2014\u2014 \u53EF\u80FD\u5BFC\u81F4\u98CE\u683C\u6DF7\u7528\u3002"
4733
- }
4734
- ]
4735
- };
4736
- record({
4737
- name: "ui-add",
4738
- status: "skip",
4739
- detail: `UI conflict detected (${conflictReport.conflictEntries.length} files), awaiting user decision`
4740
- });
4741
- effectiveStrategy = void 0;
4742
- }
4743
- }
4744
- if (effectiveStrategy) {
4745
- if (effectiveStrategy === "frozen-skip") {
4746
- const addResult = await runUiAdd({
4747
- projectRoot,
4748
- ids: entries,
4749
- overwrite: shadcnDecision === "overwrite"
4750
- });
4751
- record({
4752
- name: "ui-add",
4753
- status: "ok",
4754
- detail: `${addResult.orderedIds.length} entries [frozen-skip] (${addResult.written} written, ${addResult.skipped} skipped)`,
4755
- changes: deriveUiAddChanges(addResult, projectRoot)
4756
- });
4757
- } else {
4758
- const isolateResult = await runUiIsolate({
4759
- projectRoot,
4760
- aliases,
4761
- conflictReport
4762
- });
4763
- logger.info(
4764
- ` isolated ${isolateResult.movedFiles.length} files, rewrote imports in ${isolateResult.importRewrites.size} files`
4765
- );
4766
- const addResult = await runUiAdd({
4767
- projectRoot,
4768
- ids: entries,
4769
- overwrite: true
4770
- // after isolation, ui/ is empty → safe to write
4771
- });
4772
- let stagingDetail = "";
4773
- if (effectiveStrategy === "isolate-aggressive") {
4774
- try {
4775
- const upgradeResult = await runUiUpgrade({
4776
- projectRoot,
4777
- category: "ui",
4778
- trigger: "ui-upgrade"
4779
- });
4780
- if (upgradeResult.status === "staged") {
4781
- stagingDetail = `; upgrade staging generated (${upgradeResult.manifest.entries.length} entries)`;
4782
- }
4783
- } catch (upgradeErr) {
4784
- stagingDetail = `; upgrade staging failed: ${getErrorMessage(
4785
- upgradeErr
4786
- )}`;
4787
- }
4788
- }
4789
- record({
4790
- name: "ui-add",
4791
- status: "ok",
4792
- detail: `${addResult.orderedIds.length} entries [${effectiveStrategy}] (${addResult.written} written, ${addResult.skipped} skipped; isolated ${isolateResult.movedFiles.length} files${stagingDetail})`,
4793
- changes: deriveUiAddChanges(addResult, projectRoot)
4794
- });
4795
- try {
4796
- const deps = addResult.npmDependencies;
4797
- if (deps && Object.keys(deps).length > 0) {
4798
- await installProjectDeps({
4799
- projectRoot,
4800
- npmDependencies: deps,
4801
- skipInstall: options.skipInstall
4802
- });
4803
- }
4804
- } catch (depsErr) {
4805
- logger.warn(
4806
- ` deps install failed (non-fatal): ${getErrorMessage(
4807
- depsErr
4808
- )}`
4809
- );
4810
- }
4811
- let residualCount = 0;
4812
- try {
4813
- const residualReport = await detectResidualImports(
4814
- projectRoot
4815
- );
4816
- residualCount = residualReport.entries.length;
4817
- if (residualCount > 0) {
4818
- logger.warn(
4819
- ` \u26A0\uFE0F ${residualCount} residual import(s) to shadcn-ui/ detected \u2014 see lint warnings`
4820
- );
4821
- }
4822
- } catch {
4823
- }
4824
- try {
4825
- const planPath = await generateMigrationPlan({
4826
- projectRoot,
4827
- strategy: effectiveStrategy,
4828
- isolateResult,
4829
- addResult,
4830
- residualImports: residualCount
4831
- });
4832
- logger.info(
4833
- ` wrote migration plan: ${path28.relative(
4834
- projectRoot,
4835
- planPath
4836
- )}`
4837
- );
4838
- } catch {
4839
- }
4840
- }
4841
- }
4842
- } else {
4843
- const addResult = await runUiAdd({
4844
- projectRoot,
4845
- ids: entries,
4846
- overwrite: shadcnDecision === "overwrite"
4847
- });
4848
- record({
4849
- name: "ui-add",
4850
- status: "ok",
4851
- detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
4852
- changes: deriveUiAddChanges(addResult, projectRoot)
4853
- });
4854
- try {
4855
- const deps = addResult.npmDependencies;
4856
- if (deps && Object.keys(deps).length > 0) {
4857
- await installProjectDeps({
4858
- projectRoot,
4859
- npmDependencies: deps,
4860
- skipInstall: options.skipInstall
4861
- });
4862
- }
4863
- } catch (depsErr) {
4864
- logger.warn(
4865
- ` deps install failed (non-fatal): ${getErrorMessage(
4866
- depsErr
4867
- )}`
4868
- );
4869
- }
4870
- }
4871
- } catch (err) {
4872
- recordFailure("ui-add", err);
4873
- }
4874
- }
4875
- }
4876
- }
4877
- recordPending("components-json");
4878
- recordPending("shadcn-source");
4879
- if (!answers.withLint) {
3186
+ } else if (aborted) {
4880
3187
  record({
4881
- name: "lint",
3188
+ name: "skills",
4882
3189
  status: "skip",
4883
- detail: "withLint = false"
4884
- });
4885
- } else if (dryRun) {
4886
- record({
4887
- name: "lint",
4888
- status: "planned",
4889
- detail: "runLintInit()"
4890
- });
4891
- } else {
4892
- try {
4893
- const eslintStrategy = answers.conflictDecisions["eslint-config"];
4894
- const stylelintStrategy = answers.conflictDecisions["stylelint-config"];
4895
- const eslintExistingPaths = options.legacyEslintPaths ?? [];
4896
- const stylelintExistingPaths = options.legacyStylelintPaths ?? [];
4897
- const result = await runLintInit({
4898
- projectRoot,
4899
- skipInstall: options.skipInstall ?? false,
4900
- eslintStrategy: eslintStrategy === "merge" || eslintStrategy === "backup-overwrite" || eslintStrategy === "skip" || eslintStrategy === "overwrite" ? eslintStrategy : "overwrite",
4901
- stylelintStrategy: stylelintStrategy === "merge" || stylelintStrategy === "backup-overwrite" || stylelintStrategy === "skip" || stylelintStrategy === "overwrite" ? stylelintStrategy : "overwrite",
4902
- eslintExistingPaths,
4903
- stylelintExistingPaths
4904
- });
4905
- const detailParts = [];
4906
- if (result.status === "installed") {
4907
- detailParts.push(
4908
- `eslint=${result.eslint}, stylelint=${result.stylelint}`
4909
- );
4910
- if (result.eslintMergeRequested) {
4911
- detailParts.push("eslint:AI-merge-pending");
4912
- }
4913
- if (result.stylelintMergeRequested) {
4914
- detailParts.push("stylelint:AI-merge-pending");
4915
- }
4916
- if (result.eslintSkipped) detailParts.push("eslint:skipped");
4917
- if (result.stylelintSkipped) detailParts.push("stylelint:skipped");
4918
- if (result.stylelintIgnoreFilesWarning) {
4919
- detailParts.push("stylelint:ignoreFiles-warning");
4920
- }
4921
- } else {
4922
- detailParts.push(result.status);
4923
- }
4924
- record({
4925
- name: "lint",
4926
- status: "ok",
4927
- detail: detailParts.join(" / "),
4928
- changes: deriveLintChanges(result)
4929
- });
4930
- } catch (err) {
4931
- recordFailure("lint", err);
4932
- }
4933
- }
4934
- recordPending("tailwind-config");
4935
- recordPending("index-css");
4936
- const GITIGNORE_MARKER_START = "# >>> teamix-evo:managed >>>";
4937
- const GITIGNORE_MARKER_END = "# <<< teamix-evo:managed <<<";
4938
- const GITIGNORE_RULES = [
4939
- ".teamix-evo/.backups/",
4940
- ".teamix-evo/.upgrade-staging/",
4941
- ".teamix-evo/.upgrade-hints/"
4942
- ];
4943
- if (dryRun) {
4944
- record({
4945
- name: "gitignore",
4946
- status: "planned",
4947
- detail: "append teamix-evo runtime artifact rules"
4948
- });
4949
- } else {
4950
- try {
4951
- try {
4952
- await fsNode.access(projectRoot);
4953
- } catch {
4954
- record({
4955
- name: "gitignore",
4956
- status: "skip",
4957
- detail: "projectRoot does not exist"
4958
- });
4959
- throw { __skip: true };
4960
- }
4961
- const giPath = path28.join(projectRoot, ".gitignore");
4962
- let giContent = "";
4963
- try {
4964
- giContent = await fsNode.readFile(giPath, "utf-8");
4965
- } catch {
4966
- }
4967
- if (giContent.includes(GITIGNORE_MARKER_START)) {
4968
- record({
4969
- name: "gitignore",
4970
- status: "skip",
4971
- detail: "teamix-evo markers already present"
4972
- });
4973
- } else {
4974
- const block = [
4975
- "",
4976
- GITIGNORE_MARKER_START,
4977
- ...GITIGNORE_RULES,
4978
- GITIGNORE_MARKER_END,
4979
- ""
4980
- ].join("\n");
4981
- const separator = giContent.length > 0 && !giContent.endsWith("\n") ? "\n" : "";
4982
- await fsNode.writeFile(giPath, giContent + separator + block, "utf-8");
4983
- record({
4984
- name: "gitignore",
4985
- status: "ok",
4986
- detail: GITIGNORE_RULES.length + " rules appended",
4987
- changes: [
4988
- {
4989
- kind: "modified",
4990
- path: ".gitignore",
4991
- step: "gitignore",
4992
- detail: "teamix-evo runtime artifact rules"
4993
- }
4994
- ]
4995
- });
4996
- }
4997
- } catch (err) {
4998
- if (err && typeof err === "object" && "__skip" in err) {
4999
- } else {
5000
- recordFailure("gitignore", err);
5001
- }
5002
- }
5003
- }
5004
- if (!dryRun) {
5005
- try {
5006
- const backupsAfter = await listBackupOriginals(projectRoot);
5007
- const newlyBackedUp = diffBackupSet(backupsBefore, backupsAfter);
5008
- if (newlyBackedUp.size > 0) {
5009
- for (const change of allChanges) {
5010
- if (change.kind === "created" && newlyBackedUp.has(change.path)) {
5011
- change.kind = "modified";
5012
- }
5013
- }
5014
- for (const rel2 of newlyBackedUp) {
5015
- allChanges.push({
5016
- kind: "backed-up",
5017
- path: rel2,
5018
- step: "backup",
5019
- detail: ".teamix-evo/.backups/<\u540C\u8DEF\u5F84>.<isoTs>.bak"
5020
- });
5021
- }
5022
- }
5023
- } catch {
5024
- }
5025
- }
5026
- const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
5027
- const out = {
5028
- status,
5029
- steps,
5030
- pendingConflictWork: pending,
5031
- changes: allChanges,
5032
- snapshot
5033
- };
5034
- if (snapshotError) out.snapshotError = snapshotError;
5035
- if (uiDecisionRequired) out.uiDecisionRequired = uiDecisionRequired;
5036
- if (firstFailure.value) {
5037
- out.resumeHint = {
5038
- failedAt: firstFailure.value.step,
5039
- completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
5040
- failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
5041
- error: firstFailure.value.error,
5042
- // Every sub-step is idempotent (returns `already-initialized` when its
5043
- // state file already exists), so a plain re-run resumes from the
5044
- // failed step. A richer --resume model lands with batch 3 (lock
5045
- // snapshot + restore).
5046
- resumeCommand: "teamix-evo init"
5047
- };
5048
- }
5049
- if (!dryRun) {
5050
- try {
5051
- const checklistContent = renderInitChecklist({
5052
- variant: answers.variant,
5053
- status,
5054
- steps
5055
- });
5056
- const checklistPath = path28.join(
5057
- projectRoot,
5058
- ".teamix-evo",
5059
- "init-checklist.md"
5060
- );
5061
- await fsNode.mkdir(path28.dirname(checklistPath), { recursive: true });
5062
- await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
5063
- logger.info(" wrote .teamix-evo/init-checklist.md");
5064
- } catch {
5065
- logger.warn(" failed to write init-checklist.md (non-fatal)");
5066
- }
5067
- }
5068
- return out;
5069
- }
5070
-
5071
- // src/core/project-update.ts
5072
- import * as path31 from "path";
5073
- import {
5074
- loadTokensPackageManifest as loadTokensPackageManifest3,
5075
- getVariantEntry as getVariantEntry3
5076
- } from "@teamix-evo/registry";
5077
-
5078
- // src/core/tokens-update.ts
5079
- import * as path30 from "path";
5080
- import * as fs22 from "fs/promises";
5081
- import {
5082
- loadTokensPackageManifest as loadTokensPackageManifest2,
5083
- getVariantEntry as getVariantEntry2
5084
- } from "@teamix-evo/registry";
5085
-
5086
- // src/core/upgrade-hints.ts
5087
- import * as path29 from "path";
5088
- var TEAMIX_DIR4 = ".teamix-evo";
5089
- var HINTS_DIR = ".upgrade-hints";
5090
- function isoToFsSafe3(iso) {
5091
- return iso.replace(/[:.]/g, "-");
5092
- }
5093
- async function writeTokensUpgradeHint(options) {
5094
- if (options.renames.length === 0) return null;
5095
- const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
5096
- const fsTs = isoToFsSafe3(isoTs);
5097
- const filename = `tokens-${fsTs}.json`;
5098
- const target = path29.join(
5099
- options.projectRoot,
5100
- TEAMIX_DIR4,
5101
- HINTS_DIR,
5102
- filename
5103
- );
5104
- const payload = {
5105
- schemaVersion: 1,
5106
- ts: isoTs,
5107
- package: "tokens",
5108
- trigger: options.trigger,
5109
- fromVariant: options.fromVariant,
5110
- toVariant: options.toVariant,
5111
- fromVersion: options.fromVersion,
5112
- toVersion: options.toVersion,
5113
- renames: options.renames
5114
- };
5115
- await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
5116
- return {
5117
- path: target,
5118
- ts: fsTs,
5119
- renameCount: options.renames.length
5120
- };
5121
- }
5122
- function selectApplicableRenames(renames, fromVersion, toVersion) {
5123
- return renames.filter(
5124
- (r) => compareSemver2(r.sinceVersion, fromVersion) > 0 && compareSemver2(r.sinceVersion, toVersion) <= 0
5125
- ).sort((a, b) => compareSemver2(a.sinceVersion, b.sinceVersion));
5126
- }
5127
- function compareSemver2(a, b) {
5128
- const [aMain = "", aRest = ""] = a.split("-", 2);
5129
- const [bMain = "", bRest = ""] = b.split("-", 2);
5130
- const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
5131
- const bParts = bMain.split(".").map((n) => Number.parseInt(n, 10));
5132
- for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
5133
- const ai = aParts[i] ?? 0;
5134
- const bi = bParts[i] ?? 0;
5135
- if (ai !== bi) return ai - bi;
5136
- }
5137
- if (aRest === "" && bRest !== "") return 1;
5138
- if (aRest !== "" && bRest === "") return -1;
5139
- return aRest.localeCompare(bRest, void 0, { numeric: true });
5140
- }
5141
-
5142
- // src/core/managed-merge.ts
5143
- import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
5144
- function mergeManagedRegions(upstreamContent, consumerContent) {
5145
- let updated = consumerContent;
5146
- const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
5147
- let match;
5148
- while ((match = re.exec(upstreamContent)) !== null) {
5149
- const id = match[1];
5150
- const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
5151
- if (!hasManagedRegion3(updated, id)) {
5152
- throw new Error(
5153
- `Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
5154
- );
5155
- }
5156
- updated = replaceManagedRegion3(updated, id, body);
5157
- }
5158
- return updated;
5159
- }
5160
-
5161
- // src/core/tokens-update.ts
5162
- var DEFAULT_TOKENS_PACKAGE2 = "@teamix-evo/tokens";
5163
- var CONSUMER_BASENAME_BY_UPSTREAM = {
5164
- "theme.css": "tokens.theme.css",
5165
- "overrides.css": "tokens.overrides.css"
5166
- };
5167
- async function runTokensUpdate(options) {
5168
- const { projectRoot } = options;
5169
- const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE2;
5170
- const config = await readProjectConfig(projectRoot);
5171
- if (!config?.packages?.tokens) {
5172
- return { status: "not-initialized" };
5173
- }
5174
- const currentVariant = config.packages.tokens.variant;
5175
- const currentVersion = config.packages.tokens.version;
5176
- const packageRoot = options.packageRoot ?? resolveTokensPackageRoot(packageName);
5177
- const catalog = await loadTokensPackageManifest2(packageRoot);
5178
- const variantEntry = getVariantEntry2(catalog, currentVariant);
5179
- if (!variantEntry) {
5180
- throw new Error(
5181
- `Currently installed variant "${currentVariant}" no longer exists in ${packageName}@${catalog.version}. Available: ${catalog.variants.map((v) => v.name).join(", ")}. Run \`npx teamix-evo@latest tokens uninstall\` then \`npx teamix-evo@latest tokens init <variant>\` to switch.`
5182
- );
5183
- }
5184
- const upstreamByBasename = /* @__PURE__ */ new Map();
5185
- for (const fileRel of variantEntry.files) {
5186
- upstreamByBasename.set(
5187
- path30.basename(fileRel),
5188
- path30.join(packageRoot, fileRel)
5189
- );
5190
- }
5191
- const prior = await readInstalledManifest(projectRoot) ?? {
5192
- schemaVersion: 1,
5193
- installed: []
5194
- };
5195
- const installedIdx = prior.installed.findIndex(
5196
- (p) => p.package === packageName
5197
- );
5198
- const priorResources = installedIdx >= 0 ? prior.installed[installedIdx].resources : [];
5199
- const rewritten = [];
5200
- const managedReplaced = [];
5201
- const preserved = [];
5202
- const frozenDrift = [];
5203
- const refreshedResources = [];
5204
- for (const resource of priorResources) {
5205
- const consumerAbs = path30.isAbsolute(resource.target) ? resource.target : path30.join(projectRoot, resource.target);
5206
- const consumerBasename = path30.basename(resource.target);
5207
- const upstreamBasename = lookupUpstreamBasename(consumerBasename);
5208
- const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
5209
- if (resource.strategy === "regenerable") {
5210
- if (!upstreamAbs) {
5211
- refreshedResources.push(resource);
5212
- continue;
5213
- }
5214
- const content = await fs22.readFile(upstreamAbs, "utf-8");
5215
- await writeFileSafe(consumerAbs, content);
5216
- rewritten.push(resource.target);
5217
- refreshedResources.push({
5218
- ...resource,
5219
- hash: computeHash(content)
5220
- });
5221
- continue;
5222
- }
5223
- if (resource.strategy === "managed") {
5224
- if (!upstreamAbs || !await fileExists(consumerAbs)) {
5225
- refreshedResources.push(resource);
5226
- continue;
5227
- }
5228
- const upstreamContent = await fs22.readFile(upstreamAbs, "utf-8");
5229
- const consumerContent = await fs22.readFile(consumerAbs, "utf-8");
5230
- const merged = mergeManagedRegions(upstreamContent, consumerContent);
5231
- if (merged !== consumerContent) {
5232
- await writeFileSafe(consumerAbs, merged);
5233
- managedReplaced.push(resource.target);
5234
- }
5235
- refreshedResources.push({
5236
- ...resource,
5237
- hash: computeHash(merged)
5238
- });
5239
- continue;
5240
- }
5241
- if (await fileExists(consumerAbs)) preserved.push(resource.target);
5242
- if (upstreamAbs) {
5243
- const upstreamContent = await fs22.readFile(upstreamAbs, "utf-8");
5244
- const upstreamHash = computeHash(upstreamContent);
5245
- if (resource.hash && upstreamHash !== resource.hash) {
5246
- frozenDrift.push({
5247
- target: resource.target,
5248
- reason: "upstream-changed"
5249
- });
5250
- }
5251
- }
5252
- refreshedResources.push(resource);
5253
- }
5254
- if (variantEntry.version === currentVersion) {
5255
- if (installedIdx >= 0) {
5256
- prior.installed[installedIdx] = {
5257
- ...prior.installed[installedIdx],
5258
- resources: refreshedResources
5259
- };
5260
- await writeInstalledManifest(projectRoot, prior);
5261
- }
5262
- return {
5263
- status: "up-to-date",
5264
- packageName,
5265
- variant: currentVariant,
5266
- version: currentVersion,
5267
- frozenDrift
5268
- };
5269
- }
5270
- const lock = {
5271
- schemaVersion: 1,
5272
- variant: {
5273
- name: variantEntry.name,
5274
- displayName: variantEntry.displayName,
5275
- version: variantEntry.version,
5276
- from: packageName
5277
- },
5278
- packageVersion: catalog.version,
5279
- linked: variantEntry.linked,
5280
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
5281
- };
5282
- await writeFileSafe(
5283
- path30.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
5284
- JSON.stringify(lock, null, 2) + "\n"
5285
- );
5286
- config.packages.tokens.version = variantEntry.version;
5287
- await writeProjectConfig(projectRoot, config);
5288
- if (installedIdx >= 0) {
5289
- prior.installed[installedIdx] = {
5290
- ...prior.installed[installedIdx],
5291
- version: variantEntry.version,
5292
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
5293
- resources: refreshedResources
5294
- };
5295
- await writeInstalledManifest(projectRoot, prior);
5296
- }
5297
- const renames = selectApplicableRenames(
5298
- variantEntry.renames ?? [],
5299
- currentVersion,
5300
- variantEntry.version
5301
- );
5302
- let hintPath;
5303
- if (renames.length > 0) {
5304
- const hint = await writeTokensUpgradeHint({
5305
- projectRoot,
5306
- trigger: "update",
5307
- fromVariant: currentVariant,
5308
- toVariant: currentVariant,
5309
- fromVersion: currentVersion,
5310
- toVersion: variantEntry.version,
5311
- renames
5312
- });
5313
- if (hint) hintPath = hint.path;
5314
- }
5315
- return {
5316
- status: "updated",
5317
- packageName,
5318
- variant: currentVariant,
5319
- from: currentVersion,
5320
- to: variantEntry.version,
5321
- rewritten,
5322
- managedReplaced,
5323
- preserved,
5324
- frozenDrift,
5325
- renames,
5326
- ...hintPath ? { hintPath } : {}
5327
- };
5328
- }
5329
- function lookupUpstreamBasename(consumerBasename) {
5330
- for (const [upstream, consumer] of Object.entries(
5331
- CONSUMER_BASENAME_BY_UPSTREAM
5332
- )) {
5333
- if (consumer === consumerBasename) return upstream;
5334
- }
5335
- return consumerBasename;
5336
- }
5337
-
5338
- // src/core/project-update.ts
5339
- var DEFAULT_TOKENS_PACKAGE3 = "@teamix-evo/tokens";
5340
- var DEFAULT_SKILLS_PACKAGE4 = "@teamix-evo/skills";
5341
- var CRITICAL_STEPS2 = /* @__PURE__ */ new Set(["tokens"]);
5342
- async function runProjectUpdate(options) {
5343
- const { projectRoot, dryRun = false, onStep } = options;
5344
- const tokensPackage = options.tokensPackageName ?? DEFAULT_TOKENS_PACKAGE3;
5345
- const skillsPackage = options.skillsPackageName ?? DEFAULT_SKILLS_PACKAGE4;
5346
- const config = await readProjectConfig(projectRoot);
5347
- if (!config) {
5348
- return { status: "not-initialized" };
5349
- }
5350
- let snapshot = null;
5351
- let snapshotError;
5352
- if (!dryRun) {
3190
+ detail: "aborted: earlier critical step failed"
3191
+ });
3192
+ } else {
5353
3193
  try {
5354
- snapshot = await createSnapshot(projectRoot, { reason: "update" });
3194
+ const result = await runSkillsInit({ projectRoot, ides, scope, ide });
3195
+ if (result.status === "already-initialized") {
3196
+ record({ name: "skills", status: "ok", detail: "already-initialized" });
3197
+ } else {
3198
+ record({
3199
+ name: "skills",
3200
+ status: "ok",
3201
+ detail: `${result.skillCount} skills, ${result.fileCount} files (added: ${result.addedSkillIds.join(", ") || "none"})`,
3202
+ changes: result.addedSkillIds.map((id) => ({
3203
+ kind: "created",
3204
+ path: `.teamix-evo/skills/${id}/SKILL.md`,
3205
+ step: "skills",
3206
+ detail: "skill installed"
3207
+ }))
3208
+ });
3209
+ }
5355
3210
  } catch (err) {
5356
- snapshotError = getErrorMessage(err);
5357
- }
5358
- }
5359
- const steps = [];
5360
- let aborted = false;
5361
- const firstFailure = { value: null };
5362
- function record(step) {
5363
- steps.push(step);
5364
- onStep?.(step);
5365
- }
5366
- function recordFailure(name, err) {
5367
- const message = getErrorMessage(err);
5368
- record({ name, status: "fail", detail: message });
5369
- if (!firstFailure.value) {
5370
- firstFailure.value = { step: name, error: message };
3211
+ recordFailure("skills", err);
5371
3212
  }
5372
- if (CRITICAL_STEPS2.has(name)) aborted = true;
5373
3213
  }
5374
- let anyChanged = false;
5375
- if (!config.packages?.tokens) {
3214
+ if (dryRun) {
5376
3215
  record({
5377
- name: "tokens",
5378
- status: "skip",
5379
- detail: "tokens not installed"
3216
+ name: "agents-md",
3217
+ status: "planned",
3218
+ detail: "runGenerateAgentsMd()"
5380
3219
  });
5381
- } else if (dryRun) {
3220
+ } else {
5382
3221
  try {
5383
- const plan = await planTokensUpdate(tokensPackage, config);
5384
- record({ name: "tokens", status: "planned", detail: plan });
3222
+ const result = await runGenerateAgentsMd({
3223
+ projectRoot,
3224
+ variant,
3225
+ skillIds: [
3226
+ `teamix-evo-design-${variant}`,
3227
+ `teamix-evo-code-${variant}`
3228
+ ],
3229
+ mode: "merge-managed"
3230
+ });
3231
+ record({
3232
+ name: "agents-md",
3233
+ status: "ok",
3234
+ detail: `${result.skillCount} skill index`,
3235
+ changes: [
3236
+ {
3237
+ kind: result.backedUp ? "modified" : "created",
3238
+ path: toRelativePosix(result.path, projectRoot),
3239
+ step: "agents-md",
3240
+ detail: "skill-trigger fallback (ADR 0038)"
3241
+ }
3242
+ ]
3243
+ });
5385
3244
  } catch (err) {
5386
- recordFailure("tokens", err);
3245
+ recordFailure("agents-md", err);
5387
3246
  }
3247
+ }
3248
+ if (dryRun) {
3249
+ record({ name: "ui-init", status: "planned", detail: "runUiInit()" });
3250
+ } else if (aborted) {
3251
+ record({
3252
+ name: "ui-init",
3253
+ status: "skip",
3254
+ detail: "aborted: earlier critical step failed"
3255
+ });
5388
3256
  } else {
5389
3257
  try {
5390
- const result = await runTokensUpdate({
5391
- projectRoot,
5392
- packageName: tokensPackage
3258
+ const initResult = await runUiInit({ projectRoot, ide });
3259
+ record({
3260
+ name: "ui-init",
3261
+ status: "ok",
3262
+ detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
5393
3263
  });
5394
- if (result.status === "not-initialized") {
5395
- record({
5396
- name: "tokens",
5397
- status: "skip",
5398
- detail: "tokens not installed"
5399
- });
5400
- } else if (result.status === "up-to-date") {
5401
- const driftSuffix = result.frozenDrift.length > 0 ? `, frozen drift: ${result.frozenDrift.length}` : "";
5402
- record({
5403
- name: "tokens",
5404
- status: "ok",
5405
- detail: `up-to-date (${result.variant} v${result.version}${driftSuffix})`
5406
- });
5407
- } else {
5408
- anyChanged = true;
5409
- const extras = [];
5410
- if (result.managedReplaced.length > 0)
5411
- extras.push(`managed: ${result.managedReplaced.length}`);
5412
- if (result.frozenDrift.length > 0)
5413
- extras.push(`frozen drift: ${result.frozenDrift.length}`);
5414
- const suffix = extras.length > 0 ? ` [${extras.join(", ")}]` : "";
5415
- record({
5416
- name: "tokens",
5417
- status: "ok",
5418
- detail: `${result.variant} v${result.from} \u2192 v${result.to}${suffix}`
5419
- });
5420
- }
5421
3264
  } catch (err) {
5422
- recordFailure("tokens", err);
3265
+ recordFailure("ui-init", err);
5423
3266
  }
5424
3267
  }
5425
- if (!config.packages?.skills) {
3268
+ const collectedNpmDeps = {};
3269
+ if (dryRun) {
3270
+ const { manifest } = await loadUiData("@teamix-evo/ui").catch(() => ({
3271
+ manifest: { entries: [] }
3272
+ }));
5426
3273
  record({
5427
- name: "skills",
5428
- status: "skip",
5429
- detail: "skills not installed"
3274
+ name: "ui-add",
3275
+ status: "planned",
3276
+ detail: `runUiAdd(${manifest.entries.length} entries, --all)`
5430
3277
  });
5431
3278
  } else if (aborted) {
5432
3279
  record({
5433
- name: "skills",
3280
+ name: "ui-add",
5434
3281
  status: "skip",
5435
3282
  detail: "aborted: earlier critical step failed"
5436
3283
  });
5437
3284
  } else {
5438
3285
  try {
5439
- const result = await runSkillsUpdate({
3286
+ const { manifest } = await loadUiData("@teamix-evo/ui");
3287
+ const allIds = manifest.entries.map((e) => e.id);
3288
+ const addResult = await runUiAdd({
5440
3289
  projectRoot,
5441
- dryRun,
5442
- packageName: skillsPackage
3290
+ ids: allIds,
3291
+ overwrite: true
3292
+ });
3293
+ const writtenResources = addResult.written > 0 ? addResult.resources.slice(-addResult.written) : [];
3294
+ record({
3295
+ name: "ui-add",
3296
+ status: "ok",
3297
+ detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
3298
+ changes: writtenResources.map((r) => ({
3299
+ kind: "created",
3300
+ path: toRelativePosix(r.target, projectRoot),
3301
+ step: "ui-add",
3302
+ detail: r.strategy
3303
+ }))
3304
+ });
3305
+ Object.assign(collectedNpmDeps, addResult.npmDependencies);
3306
+ } catch (err) {
3307
+ recordFailure("ui-add", err);
3308
+ }
3309
+ }
3310
+ if (dryRun) {
3311
+ try {
3312
+ const listing = await listBizUiEntries(variant);
3313
+ record({
3314
+ name: "biz-ui-add",
3315
+ status: "planned",
3316
+ detail: `runBizUiAdd(variant=${variant}, ${listing.entries.length} entries)`
5443
3317
  });
5444
- if (result.status === "no-skills") {
3318
+ } catch {
3319
+ record({
3320
+ name: "biz-ui-add",
3321
+ status: "planned",
3322
+ detail: `runBizUiAdd(variant=${variant})`
3323
+ });
3324
+ }
3325
+ } else if (aborted) {
3326
+ record({
3327
+ name: "biz-ui-add",
3328
+ status: "skip",
3329
+ detail: "aborted: earlier critical step failed"
3330
+ });
3331
+ } else {
3332
+ try {
3333
+ const listing = await listBizUiEntries(variant);
3334
+ if (listing.entries.length === 0) {
5445
3335
  record({
5446
- name: "skills",
3336
+ name: "biz-ui-add",
5447
3337
  status: "skip",
5448
- detail: "no skills locked"
3338
+ detail: `no biz-ui entries for variant "${variant}"`
3339
+ });
3340
+ } else {
3341
+ const allIds = listing.entries.map((e) => e.id);
3342
+ const result = await runBizUiAdd({
3343
+ projectRoot,
3344
+ variant,
3345
+ ids: allIds,
3346
+ overwrite: true
5449
3347
  });
5450
- } else if (result.status === "no-changes") {
5451
3348
  record({
5452
- name: "skills",
3349
+ name: "biz-ui-add",
5453
3350
  status: "ok",
5454
- detail: `up-to-date (${result.checkedSkillIds.length} skill(s) at v${result.version})`
3351
+ detail: `${result.orderedIds.length} entries (${result.written} written, ${result.skipped} skipped)`
5455
3352
  });
5456
- } else if (result.status === "dry-run") {
5457
- const bumps = result.plan.filter((p) => p.action === "version-bump");
5458
- const detail = bumps.length === 0 ? `up-to-date (${result.plan.length} skill(s) checked at v${result.currentVersion})` : `${bumps.length} skill(s) would update: ${result.currentVersion} \u2192 ${result.availableVersion}`;
5459
- record({ name: "skills", status: "planned", detail });
5460
- } else {
5461
- anyChanged = true;
5462
- const summary = result.updatedSkillIds.length > 0 ? `updated: ${result.updatedSkillIds.join(", ")} (v${result.version})` : `refreshed at v${result.version}`;
5463
- record({ name: "skills", status: "ok", detail: summary });
3353
+ if (result.npmDependencies) {
3354
+ Object.assign(collectedNpmDeps, result.npmDependencies);
3355
+ }
5464
3356
  }
5465
3357
  } catch (err) {
5466
- recordFailure("skills", err);
3358
+ recordFailure("biz-ui-add", err);
5467
3359
  }
5468
3360
  }
5469
- await runComponentSourceStep("ui", {
5470
- projectRoot,
5471
- config,
5472
- dryRun,
5473
- record,
5474
- recordFailure
5475
- });
5476
- await runComponentSourceStep("biz-ui", {
5477
- projectRoot,
5478
- config,
5479
- dryRun,
5480
- record,
5481
- recordFailure
5482
- });
5483
- const out = (() => {
5484
- if (firstFailure.value) {
5485
- return {
5486
- status: "partial",
5487
- steps,
5488
- resumeHint: {
5489
- failedAt: firstFailure.value.step,
5490
- completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
5491
- failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
5492
- error: firstFailure.value.error,
5493
- resumeCommand: "teamix-evo update"
5494
- },
5495
- snapshot,
5496
- ...snapshotError ? { snapshotError } : {}
5497
- };
3361
+ if (!dryRun && !aborted && Object.keys(collectedNpmDeps).length > 0) {
3362
+ try {
3363
+ await installProjectDeps({
3364
+ projectRoot,
3365
+ npmDependencies: collectedNpmDeps,
3366
+ skipInstall: options.skipInstall ?? false
3367
+ });
3368
+ } catch (err) {
3369
+ logger.warn(
3370
+ `\u5B89\u88C5 ui/biz-ui \u4F20\u9012\u4F9D\u8D56\u5931\u8D25\uFF1A${getErrorMessage(
3371
+ err
3372
+ )}\uFF08\u53EF\u624B\u52A8\u8FD0\u884C \`npm install\` \u91CD\u8BD5\uFF09`
3373
+ );
5498
3374
  }
5499
- if (dryRun) return { status: "dry-run", steps, snapshot: null };
5500
- if (anyChanged)
5501
- return {
5502
- status: "updated",
5503
- steps,
5504
- snapshot,
5505
- ...snapshotError ? { snapshotError } : {}
5506
- };
5507
- return {
5508
- status: "up-to-date",
5509
- steps,
5510
- snapshot,
5511
- ...snapshotError ? { snapshotError } : {}
5512
- };
5513
- })();
5514
- return out;
5515
- }
5516
- async function runComponentSourceStep(category, args) {
5517
- const { projectRoot, config, dryRun, record, recordFailure } = args;
5518
- const cfgKey = category === "ui" ? "ui" : "biz-ui";
5519
- if (!config.packages?.[cfgKey]) {
5520
- record({
5521
- name: category,
5522
- status: "skip",
5523
- detail: `${category} not installed`
5524
- });
5525
- return;
5526
3375
  }
5527
- const aliases = config.packages.ui?.aliases ?? config.packages["biz-ui"]?.aliases;
5528
- if (!aliases) {
5529
- record({
5530
- name: category,
5531
- status: "skip",
5532
- detail: `${category} aliases not configured`
5533
- });
5534
- return;
5535
- }
5536
- let report;
5537
- try {
5538
- report = await detectComponentLineage({
5539
- projectRoot,
5540
- category,
5541
- config
5542
- });
5543
- } catch (err) {
5544
- record({
5545
- name: category,
5546
- status: "skip",
5547
- detail: `lineage detect failed: ${getErrorMessage(err)}`
5548
- });
5549
- return;
5550
- }
5551
- if (report.lineage !== "teamix-evo" && report.lineage !== "mixed") {
5552
- record({
5553
- name: category,
5554
- status: "skip",
5555
- detail: `lineage=${report.lineage}; nothing to stage`
5556
- });
5557
- return;
3376
+ if (dryRun) {
3377
+ record({ name: "lint", status: "planned", detail: "runLintInit()" });
3378
+ } else {
3379
+ try {
3380
+ const result = await runLintInit({
3381
+ projectRoot,
3382
+ skipInstall: options.skipInstall ?? false,
3383
+ eslintStrategy: "overwrite",
3384
+ stylelintStrategy: "overwrite",
3385
+ eslintExistingPaths: [],
3386
+ stylelintExistingPaths: []
3387
+ });
3388
+ const detailParts = [];
3389
+ if (result.status === "installed") {
3390
+ detailParts.push(
3391
+ `eslint=${result.eslint}, stylelint=${result.stylelint}`
3392
+ );
3393
+ if (result.packageJsonPatched) detailParts.push("package.json patched");
3394
+ } else {
3395
+ detailParts.push(result.status);
3396
+ }
3397
+ record({
3398
+ name: "lint",
3399
+ status: "ok",
3400
+ detail: detailParts.join(" / "),
3401
+ changes: deriveLintChanges(result)
3402
+ });
3403
+ } catch (err) {
3404
+ recordFailure("lint", err);
3405
+ }
5558
3406
  }
5559
3407
  if (dryRun) {
5560
- const total = report.registeredIds.length + report.unregisteredIds.length;
5561
3408
  record({
5562
- name: category,
3409
+ name: "gitignore",
5563
3410
  status: "planned",
5564
- detail: total === 0 ? "no components to stage" : `${total} component(s) to stage (lineage=${report.lineage})`
3411
+ detail: "append teamix-evo runtime rules"
5565
3412
  });
5566
- return;
3413
+ } else {
3414
+ try {
3415
+ let projectRootExists = true;
3416
+ try {
3417
+ await fsNode.access(projectRoot);
3418
+ } catch {
3419
+ projectRootExists = false;
3420
+ }
3421
+ if (!projectRootExists) {
3422
+ record({
3423
+ name: "gitignore",
3424
+ status: "skip",
3425
+ detail: "projectRoot does not exist"
3426
+ });
3427
+ } else {
3428
+ const giPath = path19.join(projectRoot, ".gitignore");
3429
+ let giContent = "";
3430
+ try {
3431
+ giContent = await fsNode.readFile(giPath, "utf-8");
3432
+ } catch {
3433
+ }
3434
+ if (giContent.includes(GITIGNORE_MARKER_START)) {
3435
+ record({
3436
+ name: "gitignore",
3437
+ status: "skip",
3438
+ detail: "teamix-evo markers already present"
3439
+ });
3440
+ } else {
3441
+ const block = [
3442
+ "",
3443
+ GITIGNORE_MARKER_START,
3444
+ ...GITIGNORE_RULES,
3445
+ GITIGNORE_MARKER_END,
3446
+ ""
3447
+ ].join("\n");
3448
+ const separator = giContent.length > 0 && !giContent.endsWith("\n") ? "\n" : "";
3449
+ await fsNode.writeFile(
3450
+ giPath,
3451
+ giContent + separator + block,
3452
+ "utf-8"
3453
+ );
3454
+ record({
3455
+ name: "gitignore",
3456
+ status: "ok",
3457
+ detail: `${GITIGNORE_RULES.filter((r) => r && !r.startsWith("#")).length} rules appended`,
3458
+ changes: [
3459
+ {
3460
+ kind: "modified",
3461
+ path: ".gitignore",
3462
+ step: "gitignore",
3463
+ detail: "teamix-evo runtime artifact rules"
3464
+ }
3465
+ ]
3466
+ });
3467
+ }
3468
+ }
3469
+ } catch (err) {
3470
+ recordFailure("gitignore", err);
3471
+ }
5567
3472
  }
5568
- try {
5569
- const built = await buildStaging({
5570
- category,
5571
- projectRoot,
5572
- aliases,
5573
- lineageReport: report,
5574
- trigger: "update",
5575
- onlyIds: []
5576
- });
5577
- if (built === null) {
5578
- record({
5579
- name: category,
5580
- status: "skip",
5581
- detail: "no entries to stage"
5582
- });
5583
- return;
3473
+ const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
3474
+ const out = { status, steps, changes: allChanges };
3475
+ if (firstFailure.value) {
3476
+ out.resumeHint = {
3477
+ failedAt: firstFailure.value.step,
3478
+ completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
3479
+ failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
3480
+ error: firstFailure.value.error,
3481
+ resumeCommand: "teamix-evo init"
3482
+ };
3483
+ }
3484
+ if (!dryRun) {
3485
+ try {
3486
+ const checklistContent = renderInitChecklist({ variant, status, steps });
3487
+ const checklistPath = path19.join(
3488
+ projectRoot,
3489
+ ".teamix-evo",
3490
+ "init-checklist.md"
3491
+ );
3492
+ await fsNode.mkdir(path19.dirname(checklistPath), { recursive: true });
3493
+ await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
3494
+ logger.info(" wrote .teamix-evo/init-checklist.md");
3495
+ } catch {
3496
+ logger.warn(" failed to write init-checklist.md (non-fatal)");
5584
3497
  }
5585
- const stagingRel = path31.relative(projectRoot, built.stagingDir);
5586
- const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
5587
- record({
5588
- name: category,
5589
- status: "ok",
5590
- detail: `${built.manifest.summary.total} component(s) staged${summary ? ` (${summary})` : ""}, see ${stagingRel}`
5591
- });
5592
- } catch (err) {
5593
- recordFailure(category, err);
5594
3498
  }
3499
+ return out;
5595
3500
  }
5596
- function summarizeStagingRisk(byRisk) {
5597
- const order = [
5598
- "risky",
5599
- "breaking",
5600
- "foreign",
5601
- "upgradable-medium",
5602
- "upgradable-low",
5603
- "unchanged"
5604
- ];
5605
- const parts = [];
5606
- for (const k of order) {
5607
- const v = byRisk[k];
5608
- if (v && v > 0) parts.push(`${v} ${k}`);
5609
- }
5610
- return parts.join(", ");
5611
- }
5612
- async function planTokensUpdate(tokensPackage, config) {
5613
- const tokensCfg = config.packages?.tokens;
5614
- if (!tokensCfg) return "tokens not installed";
5615
- const packageRoot = resolveTokensPackageRoot(tokensPackage);
5616
- const catalog = await loadTokensPackageManifest3(packageRoot);
5617
- const variantEntry = getVariantEntry3(catalog, tokensCfg.variant);
5618
- if (!variantEntry) {
5619
- return `variant "${tokensCfg.variant}" no longer in ${tokensPackage}@${catalog.version} \u2014 uninstall + re-init to switch`;
3501
+ function deriveLintChanges(result) {
3502
+ if (result.status !== "installed") return [];
3503
+ const out = [];
3504
+ if (result.eslint) {
3505
+ out.push({
3506
+ kind: "created",
3507
+ path: "eslint.config.js",
3508
+ step: "lint",
3509
+ detail: "@teamix-evo/eslint-config"
3510
+ });
3511
+ }
3512
+ if (result.stylelint) {
3513
+ out.push({
3514
+ kind: "created",
3515
+ path: "stylelint.config.cjs",
3516
+ step: "lint",
3517
+ detail: "@teamix-evo/stylelint-config"
3518
+ });
5620
3519
  }
5621
- if (variantEntry.version === tokensCfg.version) {
5622
- return `up-to-date (${tokensCfg.variant} v${tokensCfg.version})`;
3520
+ if (result.packageJsonPatched) {
3521
+ out.push({
3522
+ kind: "created",
3523
+ path: "package.json",
3524
+ step: "lint",
3525
+ detail: 'scripts.lint / scripts["lint:css"]'
3526
+ });
5623
3527
  }
5624
- return `${tokensCfg.variant} v${tokensCfg.version} \u2192 v${variantEntry.version}`;
3528
+ return out;
5625
3529
  }
5626
3530
 
5627
3531
  // src/core/installer.ts
5628
- import * as path32 from "path";
5629
- import * as fs23 from "fs/promises";
3532
+ import * as path20 from "path";
3533
+ import * as fs15 from "fs/promises";
5630
3534
  async function installResources(options) {
5631
3535
  const { projectRoot, manifest, data, variantDir, packageRoot } = options;
5632
3536
  const installedResources = [];
@@ -5663,13 +3567,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
5663
3567
  variantDir,
5664
3568
  packageRoot
5665
3569
  );
5666
- const targetPath = path32.join(projectRoot, resource.target);
3570
+ const targetPath = path20.join(projectRoot, resource.target);
5667
3571
  let content;
5668
3572
  if (resource.template) {
5669
3573
  const templateContent = await loadTemplateFile(sourcePath);
5670
3574
  content = renderTemplate(templateContent, data);
5671
3575
  } else {
5672
- content = await fs23.readFile(sourcePath, "utf-8");
3576
+ content = await fs15.readFile(sourcePath, "utf-8");
5673
3577
  }
5674
3578
  await writeFileSafe(targetPath, content);
5675
3579
  const hash = computeHash(content);
@@ -5687,13 +3591,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
5687
3591
  variantDir,
5688
3592
  packageRoot
5689
3593
  );
5690
- const targetDir = path32.join(projectRoot, resource.target);
3594
+ const targetDir = path20.join(projectRoot, resource.target);
5691
3595
  const results = [];
5692
3596
  await ensureDir(targetDir);
5693
3597
  const entries = await walkDir(sourcePath);
5694
3598
  for (const entry of entries) {
5695
- const relPath = path32.relative(sourcePath, entry);
5696
- let targetFile = path32.join(targetDir, relPath);
3599
+ const relPath = path20.relative(sourcePath, entry);
3600
+ let targetFile = path20.join(targetDir, relPath);
5697
3601
  if (resource.template && targetFile.endsWith(".hbs")) {
5698
3602
  targetFile = targetFile.slice(0, -4);
5699
3603
  }
@@ -5702,11 +3606,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
5702
3606
  const templateContent = await loadTemplateFile(entry);
5703
3607
  content = renderTemplate(templateContent, data);
5704
3608
  } else {
5705
- content = await fs23.readFile(entry, "utf-8");
3609
+ content = await fs15.readFile(entry, "utf-8");
5706
3610
  }
5707
3611
  await writeFileSafe(targetFile, content);
5708
3612
  const hash = computeHash(content);
5709
- const targetRel = path32.relative(projectRoot, targetFile);
3613
+ const targetRel = path20.relative(projectRoot, targetFile);
5710
3614
  results.push({
5711
3615
  id: `${resource.id}:${relPath}`,
5712
3616
  target: targetRel,
@@ -5719,25 +3623,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
5719
3623
  }
5720
3624
 
5721
3625
  // src/core/registry-client.ts
5722
- import * as path33 from "path";
5723
- import * as fs24 from "fs/promises";
5724
- import { createRequire as createRequire6 } from "module";
3626
+ import * as path21 from "path";
3627
+ import * as fs16 from "fs/promises";
3628
+ import { createRequire as createRequire5 } from "module";
5725
3629
  import { loadVariantManifest } from "@teamix-evo/registry";
5726
- var require6 = createRequire6(import.meta.url);
5727
- function resolvePackageRoot5(packageName) {
3630
+ var require6 = createRequire5(import.meta.url);
3631
+ function resolvePackageRoot4(packageName) {
5728
3632
  const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
5729
- return path33.dirname(pkgJsonPath);
3633
+ return path21.dirname(pkgJsonPath);
5730
3634
  }
5731
3635
  async function loadVariantData(packageName, variant) {
5732
- const packageRoot = resolvePackageRoot5(packageName);
5733
- const variantDir = path33.join(packageRoot, "library", variant);
3636
+ const packageRoot = resolvePackageRoot4(packageName);
3637
+ const variantDir = path21.join(packageRoot, "library", variant);
5734
3638
  logger.debug(`Resolved variant dir: ${variantDir}`);
5735
3639
  logger.debug(`Package root: ${packageRoot}`);
5736
3640
  const manifest = await loadVariantManifest(variantDir);
5737
3641
  let data = {};
5738
- const dataPath = path33.join(variantDir, "_data.json");
3642
+ const dataPath = path21.join(variantDir, "_data.json");
5739
3643
  try {
5740
- const raw = await fs24.readFile(dataPath, "utf-8");
3644
+ const raw = await fs16.readFile(dataPath, "utf-8");
5741
3645
  data = JSON.parse(raw);
5742
3646
  } catch (err) {
5743
3647
  if (err.code !== "ENOENT") {
@@ -5774,7 +3678,6 @@ export {
5774
3678
  runGenerateAgentsMd,
5775
3679
  runLintInit,
5776
3680
  runProjectInit,
5777
- runProjectUpdate,
5778
3681
  runSkillsAdd,
5779
3682
  runSkillsInit,
5780
3683
  runSkillsUpdate,