teamix-evo 0.9.0 → 0.11.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,13 +363,14 @@ function resolveSourcePath(source, variantDir, packageRoot) {
369
363
  }
370
364
  return path6.join(variantDir, source);
371
365
  }
372
- async function walkDir(dir) {
366
+ async function walkDir(dir, skipDirs) {
373
367
  const files = [];
374
368
  const entries = await fs4.readdir(dir, { withFileTypes: true });
375
369
  for (const entry of entries) {
376
370
  const fullPath = path6.join(dir, entry.name);
377
371
  if (entry.isDirectory()) {
378
- files.push(...await walkDir(fullPath));
372
+ if (skipDirs && skipDirs.has(entry.name)) continue;
373
+ files.push(...await walkDir(fullPath, skipDirs));
379
374
  } else if (entry.isFile()) {
380
375
  files.push(fullPath);
381
376
  }
@@ -423,9 +418,9 @@ async function writeSkillSource(skill, options) {
423
418
  const { data, packageRoot, projectRoot } = options;
424
419
  const sourceAbs = path7.resolve(packageRoot, skill.source);
425
420
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
426
- const stat5 = await fs5.stat(sourceAbs);
421
+ const stat3 = await fs5.stat(sourceAbs);
427
422
  const records = [];
428
- if (stat5.isFile()) {
423
+ if (stat3.isFile()) {
429
424
  const targetFile = path7.join(targetDir, "SKILL.md");
430
425
  const content = await renderSkillContent(sourceAbs, skill, data);
431
426
  await writeFileSafe(targetFile, content);
@@ -575,8 +570,8 @@ async function rewriteSkillSource(skill, options, summary) {
575
570
  const { data, packageRoot, projectRoot } = options;
576
571
  const sourceAbs = path7.resolve(packageRoot, skill.source);
577
572
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
578
- const stat5 = await fs5.stat(sourceAbs);
579
- if (!stat5.isFile()) {
573
+ const stat3 = await fs5.stat(sourceAbs);
574
+ if (!stat3.isFile()) {
580
575
  await ensureDir(targetDir);
581
576
  const entries = await walkDir(sourceAbs);
582
577
  const records = [];
@@ -813,13 +808,13 @@ async function pruneEmptyIdeSkillDirs(args) {
813
808
  }
814
809
  for (const name of entries) {
815
810
  const dir = path7.join(skillsRoot, name);
816
- let stat5;
811
+ let stat3;
817
812
  try {
818
- stat5 = await fs5.stat(dir);
813
+ stat3 = await fs5.stat(dir);
819
814
  } catch {
820
815
  continue;
821
816
  }
822
- if (!stat5.isDirectory()) continue;
817
+ if (!stat3.isDirectory()) continue;
823
818
  let children;
824
819
  try {
825
820
  children = await fs5.readdir(dir);
@@ -942,10 +937,25 @@ async function runSkillsInit(options) {
942
937
  return { status: "already-initialized" };
943
938
  }
944
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
+ }
945
955
  return {
946
956
  status: "installed",
947
957
  packageName,
948
- version: existingSkillsCfg?.version ?? manifest.version,
958
+ version: manifest.version,
949
959
  ides,
950
960
  scope,
951
961
  skillCount: 0,
@@ -953,7 +963,8 @@ async function runSkillsInit(options) {
953
963
  resources: [],
954
964
  addedSkillIds: [],
955
965
  skippedSkillIds,
956
- outdatedSkills
966
+ autoUpdatedSkillIds,
967
+ outdatedSkills: []
957
968
  };
958
969
  }
959
970
  return finalizeSkillsInstall({
@@ -1014,10 +1025,25 @@ async function runSkillsAdd(options) {
1014
1025
  existing
1015
1026
  );
1016
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
+ }
1017
1043
  return {
1018
1044
  status: "installed",
1019
1045
  packageName,
1020
- version: existingSkillsCfg?.version ?? manifest.version,
1046
+ version: manifest.version,
1021
1047
  ides,
1022
1048
  scope,
1023
1049
  skillCount: 0,
@@ -1025,7 +1051,8 @@ async function runSkillsAdd(options) {
1025
1051
  resources: [],
1026
1052
  addedSkillIds: [],
1027
1053
  skippedSkillIds,
1028
- outdatedSkills
1054
+ autoUpdatedSkillIds,
1055
+ outdatedSkills: []
1029
1056
  };
1030
1057
  }
1031
1058
  return finalizeSkillsInstall({
@@ -1179,6 +1206,21 @@ async function finalizeSkillsInstall(args) {
1179
1206
  await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
1180
1207
  } catch {
1181
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
+ }
1182
1224
  return {
1183
1225
  status: "installed",
1184
1226
  packageName,
@@ -1190,7 +1232,8 @@ async function finalizeSkillsInstall(args) {
1190
1232
  resources: result.resources,
1191
1233
  addedSkillIds: onlyIds,
1192
1234
  skippedSkillIds,
1193
- outdatedSkills: outdatedSkills ?? []
1235
+ autoUpdatedSkillIds,
1236
+ outdatedSkills: []
1194
1237
  };
1195
1238
  }
1196
1239
  function mergeInstalledResources(existing, next) {
@@ -1200,6 +1243,66 @@ function mergeInstalledResources(existing, next) {
1200
1243
  for (const r of next) map.set(key(r), r);
1201
1244
  return [...map.values()];
1202
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
+ }
1203
1306
 
1204
1307
  // src/core/tokens-init.ts
1205
1308
  var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
@@ -1251,13 +1354,16 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1251
1354
  if (!await fileExists(overridesAbs)) {
1252
1355
  await writeFileSafe(overridesAbs, EMPTY_OVERRIDES_TEMPLATE);
1253
1356
  }
1254
- const overridesContent = await fs6.readFile(overridesAbs, "utf-8");
1255
- installed.push({
1256
- id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
1257
- target: path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
1258
- hash: computeHash(overridesContent),
1259
- strategy: "frozen"
1260
- });
1357
+ const overridesId = `tokens:${CONSUMER_OVERRIDES_FILE}`;
1358
+ if (!installed.some((r) => r.id === overridesId)) {
1359
+ const overridesContent = await fs6.readFile(overridesAbs, "utf-8");
1360
+ installed.push({
1361
+ id: overridesId,
1362
+ target: path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
1363
+ hash: computeHash(overridesContent),
1364
+ strategy: "frozen"
1365
+ });
1366
+ }
1261
1367
  const lock = {
1262
1368
  schemaVersion: 1,
1263
1369
  variant: {
@@ -1628,6 +1734,7 @@ var DEFAULT_UI_ALIASES = {
1628
1734
  utils: "src/lib/utils",
1629
1735
  lib: "src/lib",
1630
1736
  business: "src/components/business",
1737
+ blocks: "src/blocks",
1631
1738
  templates: "src/templates"
1632
1739
  };
1633
1740
  var DEFAULT_UI_ICON_LIBRARY = "lucide";
@@ -1645,6 +1752,7 @@ async function runUiInit(options) {
1645
1752
  utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
1646
1753
  lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
1647
1754
  business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
1755
+ blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks,
1648
1756
  templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
1649
1757
  };
1650
1758
  const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
@@ -1713,9 +1821,11 @@ var SOURCE_ROOT_TO_ALIAS_KEY = {
1713
1821
  components: "components",
1714
1822
  hooks: "hooks",
1715
1823
  utils: "utils",
1716
- lib: "lib"
1824
+ lib: "lib",
1825
+ blocks: "blocks"
1717
1826
  };
1718
- function rewriteImports(source, aliases) {
1827
+ function rewriteImports(source, aliases, opts) {
1828
+ const shouldFlatten = opts?.flatten !== false;
1719
1829
  return source.replace(
1720
1830
  /(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
1721
1831
  (full, quote, root, rest) => {
@@ -1723,8 +1833,8 @@ function rewriteImports(source, aliases) {
1723
1833
  if (!aliasKey) return full;
1724
1834
  const alias = aliases[aliasKey];
1725
1835
  const normalized = aliasToImportPath(alias);
1726
- const flatRest = flattenRestPath(rest);
1727
- return `${quote}${normalized}${flatRest}${quote}`;
1836
+ const resolvedRest = shouldFlatten ? flattenRestPath(rest) : rest ?? "";
1837
+ return `${quote}${normalized}${resolvedRest}${quote}`;
1728
1838
  }
1729
1839
  );
1730
1840
  }
@@ -1753,7 +1863,8 @@ async function installUiEntries(options) {
1753
1863
  entryPackageRoot,
1754
1864
  aliases,
1755
1865
  requested,
1756
- skipExisting = true
1866
+ skipExisting = true,
1867
+ flatten = true
1757
1868
  } = options;
1758
1869
  const orderedIds = resolveUiEntryOrder(manifest.entries, requested);
1759
1870
  const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
@@ -1780,7 +1891,7 @@ async function installUiEntries(options) {
1780
1891
  const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
1781
1892
  const sourceAbs = path11.resolve(rootForEntry, file.source);
1782
1893
  const raw = await fs8.readFile(sourceAbs, "utf-8");
1783
- const transformed = rewriteImports(raw, aliases);
1894
+ const transformed = rewriteImports(raw, aliases, { flatten });
1784
1895
  if (exists) {
1785
1896
  await backupFile(targetAbs, projectRoot);
1786
1897
  }
@@ -2208,6 +2319,33 @@ async function runLintInit(options) {
2208
2319
  wroteStylelint = true;
2209
2320
  }
2210
2321
  const packageJsonPatched = await patchPackageJsonScripts(projectRoot);
2322
+ let stylelintIgnoreFilesWarning = false;
2323
+ if (!stylelintNeedsWrite && stylelintTemplateExists) {
2324
+ try {
2325
+ const existingContent = fs9.readFileSync(stylelintConfigPath, "utf-8");
2326
+ const usesTeamixPreset = existingContent.includes("@teamix-evo/stylelint-config/presets/") || existingContent.includes("@teamix-evo/stylelint-config/preset/");
2327
+ const hasTokenIgnore = existingContent.includes("tokens.theme.css") && existingContent.includes("tokens.overrides.css");
2328
+ if (!usesTeamixPreset && !hasTokenIgnore) {
2329
+ stylelintIgnoreFilesWarning = true;
2330
+ logger.warn(
2331
+ [
2332
+ "\u68C0\u6D4B\u5230\u73B0\u6709 stylelint \u914D\u7F6E\u672A\u6392\u9664 token \u5B9A\u4E49\u6587\u4EF6\u3002",
2333
+ "\u5EFA\u8BAE\u5728 stylelint.config.cjs \u4E2D\u6DFB\u52A0 ignoreFiles:",
2334
+ "",
2335
+ " ignoreFiles: [",
2336
+ " '**/tokens.theme.css',",
2337
+ " '**/tokens.overrides.css',",
2338
+ " ]",
2339
+ "",
2340
+ "\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
2341
+ "",
2342
+ " extends: ['@teamix-evo/stylelint-config/presets/consumer']"
2343
+ ].join("\n")
2344
+ );
2345
+ }
2346
+ } catch {
2347
+ }
2348
+ }
2211
2349
  return {
2212
2350
  status: "installed",
2213
2351
  eslint: wroteEslint,
@@ -2216,7 +2354,8 @@ async function runLintInit(options) {
2216
2354
  stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
2217
2355
  eslintSkipped: eslintSkipRequested,
2218
2356
  stylelintSkipped: stylelintSkipRequested,
2219
- packageJsonPatched
2357
+ packageJsonPatched,
2358
+ stylelintIgnoreFilesWarning
2220
2359
  };
2221
2360
  }
2222
2361
  function detectPm(projectRoot) {
@@ -2364,7 +2503,7 @@ function renderManagedBlockBody(args) {
2364
2503
  return `# AGENTS.md
2365
2504
 
2366
2505
  > \u672C\u5DE5\u7A0B\u5DF2\u88C5\u914D Teamix Evo AI skills\u3002AI \u52A9\u624B\u5728\u4EE5\u4E0B\u573A\u666F\u4E0B**\u5FC5\u987B\u5148\u8BFB\u5BF9\u5E94 skill** \u518D\u52A8\u624B\u3002
2367
- > \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C[ADR 0038](https://github.com/teamix-evo/teamix-evo/blob/main/docs/adr/0038-create-agents-md-skill-trigger-fallback.md)\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
2506
+ > \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C\u9075\u5FAA ADR 0038\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
2368
2507
 
2369
2508
  ## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
2370
2509
 
@@ -2377,6 +2516,13 @@ ${skillBlock}
2377
2516
  - \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
2378
2517
  - \u751F\u547D\u5468\u671F\u547D\u4EE4\uFF08\`init\` / \`update\` / \`add\`\uFF09\u8D70 \`teamix-evo-manage\`\uFF08\u5168\u5C40 skill\uFF0C\u672C\u6587\u4EF6\u4E0D\u5217\uFF09
2379
2518
 
2519
+ ## UI \u7EC4\u4EF6\u9886\u5730\uFF08init \u540E\u7EA6\u675F\uFF09
2520
+
2521
+ - \`src/components/ui/\` \u7531 teamix-evo \u63A5\u7BA1\uFF0C\u7981\u6B62\u624B\u5DE5\u6216\u901A\u8FC7 shadcn CLI \u6DFB\u52A0\u65B0\u7EC4\u4EF6
2522
+ - \u5982\u9700\u65B0\u589E UI \u7EC4\u4EF6\uFF0C\u4F7F\u7528 \`npx teamix-evo ui add <id>\`
2523
+ - \`src/components/shadcn-ui/\` \u662F init \u524D legacy \u7EC4\u4EF6\u5F52\u6863\uFF0C\u53EA\u8BFB\uFF1B\u65B0\u4EE3\u7801\u4E0D\u5E94\u518D import
2524
+ - \u5347\u7EA7 legacy \u7EC4\u4EF6\uFF1A\u89E6\u53D1 teamix-evo-upgrade skill
2525
+
2380
2526
  > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
2381
2527
  }
2382
2528
  function wrapManagedBlock(body) {
@@ -2601,8 +2747,8 @@ var STYLELINT_CONFIG_CANDIDATES = [
2601
2747
  ];
2602
2748
  async function isDir(target) {
2603
2749
  try {
2604
- const stat5 = await fs12.stat(target);
2605
- return stat5.isDirectory();
2750
+ const stat3 = await fs12.stat(target);
2751
+ return stat3.isDirectory();
2606
2752
  } catch {
2607
2753
  return false;
2608
2754
  }
@@ -2826,400 +2972,172 @@ async function detectStylelintConfig(cwd) {
2826
2972
  };
2827
2973
  }
2828
2974
 
2829
- // src/core/legacy-tokens-migrate.ts
2830
- import * as path17 from "path";
2975
+ // src/core/deps-install.ts
2831
2976
  import * as fs13 from "fs/promises";
2832
- var CONSUMER_TOKENS_DIR2 = "tokens";
2833
- var CONSUMER_OVERRIDES_FILE2 = "tokens.overrides.css";
2834
- var EMPTY_OVERRIDES_TEMPLATE2 = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
2835
- /* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
2836
- `;
2837
- function buildMigrationBanner(legacyRel, isoTimestamp) {
2838
- return `/* === Migrated from ${legacyRel} on ${isoTimestamp} === */
2839
- `;
2840
- }
2841
- function computeBackupRel(legacyRel, isoTimestamp) {
2842
- const tsSafe = isoTimestamp.replace(/[:.]/g, "-");
2843
- return path17.posix.join(
2844
- ".teamix-evo",
2845
- ".backups",
2846
- `${legacyRel}.${tsSafe}.bak`
2847
- );
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";
2848
2989
  }
2849
- async function migrateLegacyTokens(options) {
2850
- const { projectRoot, legacyPaths, dryRun = false } = options;
2851
- const seen = /* @__PURE__ */ new Set();
2852
- const uniqueLegacy = [];
2853
- for (const raw of legacyPaths) {
2854
- const norm = raw.split(path17.sep).join("/");
2855
- if (!seen.has(norm)) {
2856
- seen.add(norm);
2857
- uniqueLegacy.push(norm);
2858
- }
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
+ }
3001
+ }
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
+ );
2859
3010
  }
2860
- const overridesRel = path17.posix.join(
2861
- CONSUMER_TOKENS_DIR2,
2862
- CONSUMER_OVERRIDES_FILE2
2863
- );
2864
- const overridesAbs = path17.join(projectRoot, overridesRel);
2865
- const existingOverrides = await readFileOrNull(overridesAbs);
2866
- const overridesCreated = existingOverrides === null;
2867
- const baseContent = existingOverrides === null ? EMPTY_OVERRIDES_TEMPLATE2 : existingOverrides;
2868
- const migrated = [];
2869
- const skipped = [];
2870
- const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString();
2871
- let appendedPayload = "";
2872
- for (const legacyRel of uniqueLegacy) {
2873
- const legacyAbs = path17.join(projectRoot, legacyRel);
2874
- const content = await readFileOrNull(legacyAbs);
2875
- if (content === null) {
2876
- skipped.push({ from: legacyRel, reason: "not-found" });
2877
- continue;
2878
- }
2879
- if (content.trim() === "") {
2880
- skipped.push({ from: legacyRel, reason: "empty" });
2881
- 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;
2882
3023
  }
2883
- const banner = buildMigrationBanner(legacyRel, isoTimestamp);
2884
- const separator = appendedPayload === "" && baseContent.endsWith("\n\n") ? "" : "\n";
2885
- const block = `${separator}${banner}${content}${content.endsWith("\n") ? "" : "\n"}`;
2886
- appendedPayload += block;
2887
- const backupRel = dryRun ? null : computeBackupRel(legacyRel, isoTimestamp);
2888
- migrated.push({
2889
- from: legacyRel,
2890
- backupTo: backupRel,
2891
- bytesAppended: Buffer.byteLength(block, "utf-8")
2892
- });
2893
3024
  }
2894
- if (dryRun || migrated.length === 0) {
2895
- return {
2896
- migrated,
2897
- skipped,
2898
- overridesPath: overridesRel,
2899
- overridesCreated: dryRun ? overridesCreated : false,
2900
- dryRun
2901
- };
2902
- }
2903
- await ensureDir(path17.dirname(overridesAbs));
2904
- const merged = baseContent + appendedPayload;
2905
- const tmp = overridesAbs + ".tmp";
2906
- await fs13.writeFile(tmp, merged, "utf-8");
2907
- await fs13.rename(tmp, overridesAbs);
2908
- for (const entry of migrated) {
2909
- const legacyAbs = path17.join(projectRoot, entry.from);
2910
- const backupAbs = path17.join(projectRoot, entry.backupTo);
2911
- await ensureDir(path17.dirname(backupAbs));
2912
- const srcContent = await readFileOrNull(legacyAbs);
2913
- if (srcContent === null) {
2914
- logger.debug(
2915
- `legacy-tokens-migrate: source disappeared before backup: ${entry.from}`
2916
- );
2917
- continue;
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;
2918
3029
  }
2919
- await fs13.writeFile(backupAbs, srcContent, "utf-8");
2920
- if (await fileExists(legacyAbs)) {
2921
- await fs13.unlink(legacyAbs);
2922
- }
2923
- logger.debug(
2924
- `legacy-tokens-migrate: ${entry.from} \u2192 ${overridesRel} (backup: ${entry.backupTo})`
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`
2925
3036
  );
2926
3037
  }
2927
- return {
2928
- migrated,
2929
- skipped,
2930
- overridesPath: overridesRel,
2931
- overridesCreated,
2932
- dryRun: false
2933
- };
2934
- }
2935
-
2936
- // src/core/snapshot.ts
2937
- import * as fs14 from "fs/promises";
2938
- import * as path18 from "path";
2939
- var TEAMIX_DIR2 = ".teamix-evo";
2940
- var SNAPSHOTS_DIR = ".snapshots";
2941
- var LOGS_DIR = "logs";
2942
- var META_FILE = "_meta.json";
2943
- var DEFAULT_KEEP = 5;
2944
- function isoToFsSafe(iso) {
2945
- return iso.replace(/[:.]/g, "-");
2946
- }
2947
- function fsSafeToIso(safe) {
2948
- return safe.replace(
2949
- /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})(Z)$/,
2950
- "$1T$2:$3:$4.$5$6"
2951
- );
2952
- }
2953
- async function createSnapshot(projectRoot, opts = {}) {
2954
- const teamixDir = path18.join(projectRoot, TEAMIX_DIR2);
2955
- try {
2956
- const stat5 = await fs14.stat(teamixDir);
2957
- if (!stat5.isDirectory()) return null;
2958
- } catch (err) {
2959
- if (err.code === "ENOENT") return null;
2960
- throw err;
2961
- }
2962
- const isoTs = (/* @__PURE__ */ new Date()).toISOString();
2963
- const ts = isoToFsSafe(isoTs);
2964
- const snapshotRoot = path18.join(teamixDir, SNAPSHOTS_DIR);
2965
- const target = path18.join(snapshotRoot, ts);
2966
- await fs14.mkdir(target, { recursive: true });
2967
- const entries = await fs14.readdir(teamixDir, { withFileTypes: true });
2968
- for (const entry of entries) {
2969
- if (entry.name === SNAPSHOTS_DIR) continue;
2970
- if (entry.name === LOGS_DIR) continue;
2971
- const src = path18.join(teamixDir, entry.name);
2972
- const dst = path18.join(target, entry.name);
2973
- await fs14.cp(src, dst, { recursive: true });
2974
- }
2975
- const meta = {
2976
- ts: isoTs,
2977
- reason: opts.reason ?? "manual"
2978
- };
2979
- await fs14.writeFile(
2980
- path18.join(target, META_FILE),
2981
- JSON.stringify(meta, null, 2) + "\n",
2982
- "utf-8"
2983
- );
2984
- logger.debug(
2985
- `Snapshot created \u2192 ${path18.relative(projectRoot, target)} (${meta.reason})`
2986
- );
2987
- const keep = opts.keep ?? DEFAULT_KEEP;
2988
- await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
2989
- return { ts, path: target };
2990
- }
2991
- async function listSnapshots(projectRoot) {
2992
- const snapshotRoot = path18.join(projectRoot, TEAMIX_DIR2, SNAPSHOTS_DIR);
2993
- let entries;
2994
- try {
2995
- entries = await fs14.readdir(snapshotRoot, { withFileTypes: true });
2996
- } catch (err) {
2997
- if (err.code === "ENOENT") return [];
2998
- throw err;
2999
- }
3000
- const result = [];
3001
- for (const entry of entries) {
3002
- if (!entry.isDirectory()) continue;
3003
- const dir = path18.join(snapshotRoot, entry.name);
3004
- let isoTs = null;
3005
- let reason = null;
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}...`);
3006
3043
  try {
3007
- const raw = await fs14.readFile(path18.join(dir, META_FILE), "utf-8");
3008
- const parsed = JSON.parse(raw);
3009
- if (typeof parsed.ts === "string") isoTs = parsed.ts;
3010
- if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
3011
- parsed.reason
3012
- )) {
3013
- reason = parsed.reason;
3014
- }
3015
- } catch {
3016
- isoTs = fsSafeToIso(entry.name);
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)}`
3050
+ );
3051
+ logger.warn(" please run install manually");
3017
3052
  }
3018
- result.push({ ts: entry.name, isoTs, reason, path: dir });
3019
3053
  }
3020
- result.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0);
3021
- return result;
3054
+ return { added, existed, installed, packageManager };
3022
3055
  }
3023
- async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
3024
- if (keep < 0)
3025
- throw new Error(`pruneSnapshots: keep must be >= 0, got ${keep}`);
3026
- const snapshots = await listSnapshots(projectRoot);
3027
- if (snapshots.length <= keep) return [];
3028
- const tail = snapshots.slice(keep);
3029
- const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
3030
- const removed = [];
3031
- for (const snap of toRemove) {
3032
- await fs14.rm(snap.path, { recursive: true, force: true });
3033
- removed.push(snap.ts);
3034
- logger.debug(`Pruned snapshot ${snap.ts}`);
3035
- }
3036
- return removed.reverse();
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
+ `;
3037
3092
  }
3038
3093
 
3039
3094
  // src/core/file-changes.ts
3040
- import * as fs15 from "fs/promises";
3041
- import * as path19 from "path";
3095
+ import * as fs14 from "fs/promises";
3096
+ import * as path18 from "path";
3042
3097
  function toRelativePosix(p, projectRoot) {
3043
3098
  let rel2 = p;
3044
- if (path19.isAbsolute(p)) {
3045
- rel2 = path19.relative(projectRoot, p);
3046
- }
3047
- return rel2.split(path19.sep).join("/");
3048
- }
3049
- async function listBackupOriginals(projectRoot) {
3050
- const backupsDir = path19.join(projectRoot, ".teamix-evo", ".backups");
3051
- const out = /* @__PURE__ */ new Set();
3052
- const stack = [backupsDir];
3053
- while (stack.length > 0) {
3054
- const dir = stack.pop();
3055
- let entries;
3056
- try {
3057
- entries = await fs15.readdir(dir, { withFileTypes: true });
3058
- } catch (err) {
3059
- if (err.code === "ENOENT") continue;
3060
- throw err;
3061
- }
3062
- for (const e of entries) {
3063
- const full = path19.join(dir, e.name);
3064
- if (e.isDirectory()) {
3065
- stack.push(full);
3066
- } else if (e.isFile() && e.name.endsWith(".bak")) {
3067
- const rel2 = path19.relative(backupsDir, full);
3068
- const original = stripBackupSuffix(rel2);
3069
- if (original) out.add(original.split(path19.sep).join("/"));
3070
- }
3071
- }
3072
- }
3073
- return out;
3074
- }
3075
- function stripBackupSuffix(rel2) {
3076
- const m = rel2.match(
3077
- /^(.+)\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.bak$/
3078
- );
3079
- return m?.[1] ?? null;
3080
- }
3081
- function diffBackupSet(before, after) {
3082
- const out = /* @__PURE__ */ new Set();
3083
- for (const p of after) {
3084
- if (!before.has(p)) out.add(p);
3099
+ if (path18.isAbsolute(p)) {
3100
+ rel2 = path18.relative(projectRoot, p);
3085
3101
  }
3086
- return out;
3102
+ return rel2.split(path18.sep).join("/");
3087
3103
  }
3088
3104
 
3089
3105
  // src/core/project-init.ts
3090
- var BASELINE_UI_ENTRIES = [
3091
- "button",
3092
- "button-group",
3093
- "input",
3094
- "form",
3095
- "card",
3096
- "collapsible",
3097
- "dialog",
3098
- "dropdown-menu",
3099
- "tabs",
3100
- "table",
3101
- "sidebar",
3102
- "page-shell",
3103
- "page-header"
3104
- ];
3106
+ import * as fsNode from "fs/promises";
3107
+ import * as path19 from "path";
3105
3108
  var CRITICAL_STEPS = /* @__PURE__ */ new Set([
3106
3109
  "tokens",
3107
3110
  "skills",
3108
3111
  "ui-init"
3109
3112
  ]);
3110
- var IMPLEMENTED_STRATEGIES = {
3111
- // 'agents-md': both 'merge-managed' (Phase 2.B — splice the
3112
- // teamix-evo-skills managed region) and 'overwrite' (full rewrite) write
3113
- // through `runGenerateAgentsMd`.
3114
- "agents-md": ["merge-managed", "overwrite", "skip"],
3115
- tokens: ["migrate", "overwrite", "skip"],
3116
- "components-json": ["overwrite", "skip"],
3117
- "shadcn-source": ["overwrite", "skip-existing", "skip"],
3118
- "tailwind-config": ["skip"],
3119
- "index-css": ["skip"],
3120
- // Phase 3.E: lint conflict strategies are honored end-to-end by
3121
- // `runLintInit` (backup user file + write template, optional AI-assist hint).
3122
- "eslint-config": ["merge", "backup-overwrite", "skip", "overwrite"],
3123
- "stylelint-config": ["merge", "backup-overwrite", "skip", "overwrite"]
3124
- };
3125
- function pickIde(ides) {
3126
- return ides[0] ?? "qoder";
3127
- }
3128
- function strategyImplemented(key, strategy) {
3129
- return IMPLEMENTED_STRATEGIES[key]?.includes(strategy) ?? false;
3130
- }
3131
- async function resolveUiEntries(options) {
3132
- if (options.uiEntries && options.uiEntries.length > 0) {
3133
- return [...options.uiEntries];
3134
- }
3135
- if (options.answers.uiSelection === "all") {
3136
- const { manifest } = await loadUiData("@teamix-evo/ui");
3137
- return manifest.entries.map((e) => e.id);
3138
- }
3139
- return [...BASELINE_UI_ENTRIES];
3140
- }
3141
- function deriveTokensChanges(result, projectRoot) {
3142
- if (result.status !== "installed") return [];
3143
- return result.resources.map((r) => ({
3144
- kind: "created",
3145
- path: toRelativePosix(r.target, projectRoot),
3146
- step: "tokens",
3147
- detail: r.strategy
3148
- }));
3149
- }
3150
- function deriveSkillsChanges(result, projectRoot) {
3151
- if (result.status !== "installed") return [];
3152
- return result.addedSkillIds.map((id) => ({
3153
- kind: "created",
3154
- path: `.teamix-evo/skills/${id}/SKILL.md`,
3155
- step: "skills",
3156
- detail: "skill installed (source mirror + IDE mirrors)"
3157
- }));
3158
- }
3159
- function deriveUiAddChanges(result, projectRoot) {
3160
- const out = [];
3161
- let remaining = result.written;
3162
- for (let i = result.resources.length - 1; i >= 0 && remaining > 0; i--) {
3163
- const r = result.resources[i];
3164
- out.unshift({
3165
- kind: "created",
3166
- path: toRelativePosix(r.target, projectRoot),
3167
- step: "ui-add",
3168
- detail: r.strategy
3169
- });
3170
- remaining--;
3171
- }
3172
- return out;
3173
- }
3174
- function deriveLintChanges(result) {
3175
- if (result.status !== "installed") return [];
3176
- const out = [];
3177
- if (result.eslint) {
3178
- out.push({
3179
- kind: "created",
3180
- path: "eslint.config.js",
3181
- step: "lint",
3182
- detail: "@teamix-evo/eslint-config consumer preset"
3183
- });
3184
- }
3185
- if (result.stylelint) {
3186
- out.push({
3187
- kind: "created",
3188
- path: "stylelint.config.cjs",
3189
- step: "lint",
3190
- detail: "@teamix-evo/stylelint-config consumer preset"
3191
- });
3192
- }
3193
- if (result.packageJsonPatched) {
3194
- out.push({
3195
- kind: "created",
3196
- path: "package.json",
3197
- step: "lint",
3198
- detail: 'scripts.lint / scripts["lint:css"]'
3199
- });
3200
- }
3201
- return out;
3202
- }
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
+ ];
3203
3127
  async function runProjectInit(options) {
3204
- const { projectRoot, answers, dryRun = false, onStep } = options;
3205
- const ide = pickIde(answers.ides);
3128
+ const {
3129
+ projectRoot,
3130
+ variant,
3131
+ ides,
3132
+ scope = "project",
3133
+ dryRun = false,
3134
+ onStep
3135
+ } = options;
3136
+ const ide = ides[0] ?? "qoder";
3206
3137
  const steps = [];
3207
- const pending = [];
3208
3138
  const allChanges = [];
3209
- const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
3210
- let snapshot = null;
3211
- let snapshotError;
3212
- if (!dryRun) {
3213
- try {
3214
- snapshot = await createSnapshot(projectRoot, { reason: "init" });
3215
- } catch (err) {
3216
- snapshotError = getErrorMessage(err);
3217
- }
3218
- }
3219
3139
  let aborted = false;
3220
- const firstFailure = {
3221
- value: null
3222
- };
3140
+ const firstFailure = { value: null };
3223
3141
  function record(step) {
3224
3142
  steps.push(step);
3225
3143
  onStep?.(step);
@@ -3227,16 +3145,6 @@ async function runProjectInit(options) {
3227
3145
  allChanges.push(...step.changes);
3228
3146
  }
3229
3147
  }
3230
- function recordPending(key) {
3231
- const strategy = answers.conflictDecisions[key];
3232
- if (!strategy) return;
3233
- if (strategyImplemented(key, strategy)) return;
3234
- pending.push({
3235
- key,
3236
- strategy,
3237
- reason: `Strategy "${strategy}" requires the managed-region engine (batch 4); recorded for follow-up.`
3238
- });
3239
- }
3240
3148
  function recordFailure(name, err) {
3241
3149
  const message = getErrorMessage(err);
3242
3150
  record({ name, status: "fail", detail: message });
@@ -3244,61 +3152,36 @@ async function runProjectInit(options) {
3244
3152
  firstFailure.value = { step: name, error: message };
3245
3153
  if (CRITICAL_STEPS.has(name)) aborted = true;
3246
3154
  }
3247
- const tokensDecision = answers.conflictDecisions.tokens;
3248
- const legacyTokensPaths = options.legacyTokensPaths ?? [];
3249
- const wantMigrate = tokensDecision === "migrate" && legacyTokensPaths.length > 0;
3250
- if (tokensDecision === "skip") {
3251
- record({
3252
- name: "tokens",
3253
- status: "skip",
3254
- detail: "conflict strategy = skip"
3255
- });
3256
- } else if (dryRun) {
3257
- const planDetail = wantMigrate ? `runTokensInit(variant=${answers.variant}); migrateLegacyTokens(${legacyTokensPaths.length} file${legacyTokensPaths.length === 1 ? "" : "s"})` : `runTokensInit(variant=${answers.variant})`;
3155
+ if (dryRun) {
3258
3156
  record({
3259
3157
  name: "tokens",
3260
3158
  status: "planned",
3261
- detail: planDetail
3159
+ detail: `runTokensInit(variant=${variant})`
3262
3160
  });
3263
3161
  } else {
3264
3162
  try {
3265
- const result = await runTokensInit({
3266
- projectRoot,
3267
- variant: answers.variant,
3268
- ide
3269
- });
3270
- let detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
3271
- if (wantMigrate) {
3272
- try {
3273
- const m = await migrateLegacyTokens({
3274
- projectRoot,
3275
- legacyPaths: legacyTokensPaths
3276
- });
3277
- detail += `; migrated ${m.migrated.length}/${legacyTokensPaths.length} legacy file${legacyTokensPaths.length === 1 ? "" : "s"} \u2192 ${m.overridesPath}`;
3278
- if (m.skipped.length > 0) {
3279
- detail += ` (skipped ${m.skipped.length})`;
3280
- }
3281
- } catch (err) {
3282
- detail += `; migrate failed: ${getErrorMessage(err)}`;
3283
- }
3284
- }
3163
+ const result = await runTokensInit({ projectRoot, variant, ide });
3164
+ const detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
3285
3165
  record({
3286
3166
  name: "tokens",
3287
3167
  status: "ok",
3288
3168
  detail,
3289
- changes: deriveTokensChanges(result, projectRoot)
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
+ })) : []
3290
3175
  });
3291
3176
  } catch (err) {
3292
3177
  recordFailure("tokens", err);
3293
3178
  }
3294
3179
  }
3295
- recordPending("tokens");
3296
- const codeSkillId = `teamix-evo-code-${answers.variant}`;
3297
3180
  if (dryRun) {
3298
3181
  record({
3299
3182
  name: "skills",
3300
3183
  status: "planned",
3301
- detail: `runSkillsAdd(${codeSkillId})`
3184
+ detail: `runSkillsInit(scope=${scope})`
3302
3185
  });
3303
3186
  } else if (aborted) {
3304
3187
  record({
@@ -3308,1465 +3191,346 @@ async function runProjectInit(options) {
3308
3191
  });
3309
3192
  } else {
3310
3193
  try {
3311
- const result = await runSkillsAdd({
3312
- projectRoot,
3313
- names: [codeSkillId],
3314
- ides: answers.ides,
3315
- scope: answers.scope,
3316
- ide
3317
- });
3318
- record({
3319
- name: "skills",
3320
- status: "ok",
3321
- detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
3322
- changes: deriveSkillsChanges(result, projectRoot)
3323
- });
3324
- } catch (err) {
3325
- recordFailure("skills", err);
3326
- }
3327
- }
3328
- const agentsMdDecision = answers.conflictDecisions["agents-md"];
3329
- const agentsMdSkillIds = [
3330
- `teamix-evo-design-${answers.variant}`,
3331
- codeSkillId
3332
- ];
3333
- if (agentsMdDecision === "skip") {
3334
- record({
3335
- name: "agents-md",
3336
- status: "skip",
3337
- detail: "conflict strategy = skip"
3338
- });
3339
- } else if (dryRun) {
3340
- record({
3341
- name: "agents-md",
3342
- status: "planned",
3343
- detail: `runGenerateAgentsMd(${agentsMdSkillIds.length} skills)`
3344
- });
3345
- } else {
3346
- try {
3347
- const result = await runGenerateAgentsMd({
3348
- projectRoot,
3349
- variant: answers.variant,
3350
- skillIds: agentsMdSkillIds,
3351
- // Phase 2.B: when the user picked `merge-managed` on conflict, only
3352
- // rewrite the `teamix-evo-skills` managed region so hand-written
3353
- // sections survive the regenerate step. `overwrite` keeps the
3354
- // historical full-rewrite default.
3355
- mode: agentsMdDecision === "merge-managed" ? "merge-managed" : "overwrite"
3356
- });
3357
- record({
3358
- name: "agents-md",
3359
- status: "ok",
3360
- detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`,
3361
- changes: [
3362
- {
3363
- kind: result.backedUp ? "modified" : "created",
3364
- path: toRelativePosix(result.path, projectRoot),
3365
- step: "agents-md",
3366
- detail: "skill-trigger fallback (ADR 0038)"
3367
- }
3368
- ]
3369
- });
3370
- } catch (err) {
3371
- recordFailure("agents-md", err);
3372
- }
3373
- }
3374
- recordPending("agents-md");
3375
- const componentsJsonDecision = answers.conflictDecisions["components-json"];
3376
- const shadcnDecision = answers.conflictDecisions["shadcn-source"];
3377
- const skipUiInit = !answers.withUi || componentsJsonDecision === "skip";
3378
- if (skipUiInit) {
3379
- record({
3380
- name: "ui-init",
3381
- status: "skip",
3382
- detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
3383
- });
3384
- record({
3385
- name: "ui-add",
3386
- status: "skip",
3387
- detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
3388
- });
3389
- } else if (dryRun) {
3390
- record({
3391
- name: "ui-init",
3392
- status: "planned",
3393
- detail: "runUiInit()"
3394
- });
3395
- const entries = await resolveUiEntries(options).catch(() => [
3396
- ...BASELINE_UI_ENTRIES
3397
- ]);
3398
- record({
3399
- name: "ui-add",
3400
- status: "planned",
3401
- detail: `runUiAdd(${entries.length} entries: ${entries.slice(0, 3).join(", ")}${entries.length > 3 ? "\u2026" : ""})`
3402
- });
3403
- } else {
3404
- if (aborted) {
3405
- record({
3406
- name: "ui-init",
3407
- status: "skip",
3408
- detail: "aborted: earlier critical step failed"
3409
- });
3410
- record({
3411
- name: "ui-add",
3412
- status: "skip",
3413
- detail: "aborted: earlier critical step failed"
3414
- });
3415
- } else {
3416
- let uiInitOk = false;
3417
- try {
3418
- const initResult = await runUiInit({ projectRoot, ide });
3419
- record({
3420
- name: "ui-init",
3421
- status: "ok",
3422
- detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
3423
- });
3424
- uiInitOk = true;
3425
- } catch (err) {
3426
- recordFailure("ui-init", err);
3427
- }
3428
- if (!uiInitOk) {
3429
- record({
3430
- name: "ui-add",
3431
- status: "skip",
3432
- detail: "aborted: ui-init failed"
3433
- });
3434
- } else if (shadcnDecision === "skip") {
3435
- record({
3436
- name: "ui-add",
3437
- status: "skip",
3438
- detail: "shadcn-source conflict strategy = skip"
3439
- });
3440
- } else {
3441
- try {
3442
- const entries = await resolveUiEntries(options);
3443
- const addResult = await runUiAdd({
3444
- projectRoot,
3445
- ids: entries,
3446
- // 'overwrite' strategy → overwrite=true; everything else (incl.
3447
- // 'skip-existing' which is the default) → overwrite=false.
3448
- overwrite: shadcnDecision === "overwrite"
3449
- });
3450
- record({
3451
- name: "ui-add",
3452
- status: "ok",
3453
- detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
3454
- changes: deriveUiAddChanges(addResult, projectRoot)
3455
- });
3456
- } catch (err) {
3457
- recordFailure("ui-add", err);
3458
- }
3459
- }
3460
- }
3461
- }
3462
- recordPending("components-json");
3463
- recordPending("shadcn-source");
3464
- if (!answers.withLint) {
3465
- record({
3466
- name: "lint",
3467
- status: "skip",
3468
- detail: "withLint = false"
3469
- });
3470
- } else if (dryRun) {
3471
- record({
3472
- name: "lint",
3473
- status: "planned",
3474
- detail: "runLintInit()"
3475
- });
3476
- } else {
3477
- try {
3478
- const eslintStrategy = answers.conflictDecisions["eslint-config"];
3479
- const stylelintStrategy = answers.conflictDecisions["stylelint-config"];
3480
- const eslintExistingPaths = options.legacyEslintPaths ?? [];
3481
- const stylelintExistingPaths = options.legacyStylelintPaths ?? [];
3482
- const result = await runLintInit({
3483
- projectRoot,
3484
- skipInstall: options.skipInstall ?? false,
3485
- eslintStrategy: eslintStrategy === "merge" || eslintStrategy === "backup-overwrite" || eslintStrategy === "skip" || eslintStrategy === "overwrite" ? eslintStrategy : "overwrite",
3486
- stylelintStrategy: stylelintStrategy === "merge" || stylelintStrategy === "backup-overwrite" || stylelintStrategy === "skip" || stylelintStrategy === "overwrite" ? stylelintStrategy : "overwrite",
3487
- eslintExistingPaths,
3488
- stylelintExistingPaths
3489
- });
3490
- const detailParts = [];
3491
- if (result.status === "installed") {
3492
- detailParts.push(
3493
- `eslint=${result.eslint}, stylelint=${result.stylelint}`
3494
- );
3495
- if (result.eslintMergeRequested) {
3496
- detailParts.push("eslint:AI-merge-pending");
3497
- }
3498
- if (result.stylelintMergeRequested) {
3499
- detailParts.push("stylelint:AI-merge-pending");
3500
- }
3501
- if (result.eslintSkipped) detailParts.push("eslint:skipped");
3502
- if (result.stylelintSkipped) detailParts.push("stylelint:skipped");
3503
- } else {
3504
- detailParts.push(result.status);
3505
- }
3506
- record({
3507
- name: "lint",
3508
- status: "ok",
3509
- detail: detailParts.join(" / "),
3510
- changes: deriveLintChanges(result)
3511
- });
3512
- } catch (err) {
3513
- recordFailure("lint", err);
3514
- }
3515
- }
3516
- recordPending("tailwind-config");
3517
- recordPending("index-css");
3518
- if (!dryRun) {
3519
- try {
3520
- const backupsAfter = await listBackupOriginals(projectRoot);
3521
- const newlyBackedUp = diffBackupSet(backupsBefore, backupsAfter);
3522
- if (newlyBackedUp.size > 0) {
3523
- for (const change of allChanges) {
3524
- if (change.kind === "created" && newlyBackedUp.has(change.path)) {
3525
- change.kind = "modified";
3526
- }
3527
- }
3528
- for (const rel2 of newlyBackedUp) {
3529
- allChanges.push({
3530
- kind: "backed-up",
3531
- path: rel2,
3532
- step: "backup",
3533
- detail: ".teamix-evo/.backups/<\u540C\u8DEF\u5F84>.<isoTs>.bak"
3534
- });
3535
- }
3536
- }
3537
- } catch {
3538
- }
3539
- }
3540
- const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
3541
- const out = {
3542
- status,
3543
- steps,
3544
- pendingConflictWork: pending,
3545
- changes: allChanges,
3546
- snapshot
3547
- };
3548
- if (snapshotError) out.snapshotError = snapshotError;
3549
- if (firstFailure.value) {
3550
- out.resumeHint = {
3551
- failedAt: firstFailure.value.step,
3552
- completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
3553
- failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
3554
- error: firstFailure.value.error,
3555
- // Every sub-step is idempotent (returns `already-initialized` when its
3556
- // state file already exists), so a plain re-run resumes from the
3557
- // failed step. A richer --resume model lands with batch 3 (lock
3558
- // snapshot + restore).
3559
- resumeCommand: "teamix-evo init"
3560
- };
3561
- }
3562
- return out;
3563
- }
3564
-
3565
- // src/core/project-update.ts
3566
- import * as path25 from "path";
3567
- import {
3568
- loadTokensPackageManifest as loadTokensPackageManifest3,
3569
- getVariantEntry as getVariantEntry3
3570
- } from "@teamix-evo/registry";
3571
-
3572
- // src/core/tokens-update.ts
3573
- import * as path21 from "path";
3574
- import * as fs16 from "fs/promises";
3575
- import {
3576
- loadTokensPackageManifest as loadTokensPackageManifest2,
3577
- getVariantEntry as getVariantEntry2
3578
- } from "@teamix-evo/registry";
3579
-
3580
- // src/core/upgrade-hints.ts
3581
- import * as path20 from "path";
3582
- var TEAMIX_DIR3 = ".teamix-evo";
3583
- var HINTS_DIR = ".upgrade-hints";
3584
- function isoToFsSafe2(iso) {
3585
- return iso.replace(/[:.]/g, "-");
3586
- }
3587
- async function writeTokensUpgradeHint(options) {
3588
- if (options.renames.length === 0) return null;
3589
- const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3590
- const fsTs = isoToFsSafe2(isoTs);
3591
- const filename = `tokens-${fsTs}.json`;
3592
- const target = path20.join(
3593
- options.projectRoot,
3594
- TEAMIX_DIR3,
3595
- HINTS_DIR,
3596
- filename
3597
- );
3598
- const payload = {
3599
- schemaVersion: 1,
3600
- ts: isoTs,
3601
- package: "tokens",
3602
- trigger: options.trigger,
3603
- fromVariant: options.fromVariant,
3604
- toVariant: options.toVariant,
3605
- fromVersion: options.fromVersion,
3606
- toVersion: options.toVersion,
3607
- renames: options.renames
3608
- };
3609
- await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
3610
- return {
3611
- path: target,
3612
- ts: fsTs,
3613
- renameCount: options.renames.length
3614
- };
3615
- }
3616
- function selectApplicableRenames(renames, fromVersion, toVersion) {
3617
- return renames.filter(
3618
- (r) => compareSemver2(r.sinceVersion, fromVersion) > 0 && compareSemver2(r.sinceVersion, toVersion) <= 0
3619
- ).sort((a, b) => compareSemver2(a.sinceVersion, b.sinceVersion));
3620
- }
3621
- function compareSemver2(a, b) {
3622
- const [aMain = "", aRest = ""] = a.split("-", 2);
3623
- const [bMain = "", bRest = ""] = b.split("-", 2);
3624
- const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
3625
- const bParts = bMain.split(".").map((n) => Number.parseInt(n, 10));
3626
- for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
3627
- const ai = aParts[i] ?? 0;
3628
- const bi = bParts[i] ?? 0;
3629
- if (ai !== bi) return ai - bi;
3630
- }
3631
- if (aRest === "" && bRest !== "") return 1;
3632
- if (aRest !== "" && bRest === "") return -1;
3633
- return aRest.localeCompare(bRest, void 0, { numeric: true });
3634
- }
3635
-
3636
- // src/core/managed-merge.ts
3637
- import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
3638
- function mergeManagedRegions(upstreamContent, consumerContent) {
3639
- let updated = consumerContent;
3640
- const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
3641
- let match;
3642
- while ((match = re.exec(upstreamContent)) !== null) {
3643
- const id = match[1];
3644
- const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
3645
- if (!hasManagedRegion3(updated, id)) {
3646
- throw new Error(
3647
- `Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
3648
- );
3649
- }
3650
- updated = replaceManagedRegion3(updated, id, body);
3651
- }
3652
- return updated;
3653
- }
3654
-
3655
- // src/core/tokens-update.ts
3656
- var DEFAULT_TOKENS_PACKAGE2 = "@teamix-evo/tokens";
3657
- var CONSUMER_BASENAME_BY_UPSTREAM = {
3658
- "theme.css": "tokens.theme.css",
3659
- "overrides.css": "tokens.overrides.css"
3660
- };
3661
- async function runTokensUpdate(options) {
3662
- const { projectRoot } = options;
3663
- const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE2;
3664
- const config = await readProjectConfig(projectRoot);
3665
- if (!config?.packages?.tokens) {
3666
- return { status: "not-initialized" };
3667
- }
3668
- const currentVariant = config.packages.tokens.variant;
3669
- const currentVersion = config.packages.tokens.version;
3670
- const packageRoot = options.packageRoot ?? resolveTokensPackageRoot(packageName);
3671
- const catalog = await loadTokensPackageManifest2(packageRoot);
3672
- const variantEntry = getVariantEntry2(catalog, currentVariant);
3673
- if (!variantEntry) {
3674
- throw new Error(
3675
- `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.`
3676
- );
3677
- }
3678
- const upstreamByBasename = /* @__PURE__ */ new Map();
3679
- for (const fileRel of variantEntry.files) {
3680
- upstreamByBasename.set(
3681
- path21.basename(fileRel),
3682
- path21.join(packageRoot, fileRel)
3683
- );
3684
- }
3685
- const prior = await readInstalledManifest(projectRoot) ?? {
3686
- schemaVersion: 1,
3687
- installed: []
3688
- };
3689
- const installedIdx = prior.installed.findIndex(
3690
- (p) => p.package === packageName
3691
- );
3692
- const priorResources = installedIdx >= 0 ? prior.installed[installedIdx].resources : [];
3693
- const rewritten = [];
3694
- const managedReplaced = [];
3695
- const preserved = [];
3696
- const frozenDrift = [];
3697
- const refreshedResources = [];
3698
- for (const resource of priorResources) {
3699
- const consumerAbs = path21.isAbsolute(resource.target) ? resource.target : path21.join(projectRoot, resource.target);
3700
- const consumerBasename = path21.basename(resource.target);
3701
- const upstreamBasename = lookupUpstreamBasename(consumerBasename);
3702
- const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
3703
- if (resource.strategy === "regenerable") {
3704
- if (!upstreamAbs) {
3705
- refreshedResources.push(resource);
3706
- continue;
3707
- }
3708
- const content = await fs16.readFile(upstreamAbs, "utf-8");
3709
- await writeFileSafe(consumerAbs, content);
3710
- rewritten.push(resource.target);
3711
- refreshedResources.push({
3712
- ...resource,
3713
- hash: computeHash(content)
3714
- });
3715
- continue;
3716
- }
3717
- if (resource.strategy === "managed") {
3718
- if (!upstreamAbs || !await fileExists(consumerAbs)) {
3719
- refreshedResources.push(resource);
3720
- continue;
3721
- }
3722
- const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
3723
- const consumerContent = await fs16.readFile(consumerAbs, "utf-8");
3724
- const merged = mergeManagedRegions(upstreamContent, consumerContent);
3725
- if (merged !== consumerContent) {
3726
- await writeFileSafe(consumerAbs, merged);
3727
- managedReplaced.push(resource.target);
3728
- }
3729
- refreshedResources.push({
3730
- ...resource,
3731
- hash: computeHash(merged)
3732
- });
3733
- continue;
3734
- }
3735
- if (await fileExists(consumerAbs)) preserved.push(resource.target);
3736
- if (upstreamAbs) {
3737
- const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
3738
- const upstreamHash = computeHash(upstreamContent);
3739
- if (resource.hash && upstreamHash !== resource.hash) {
3740
- frozenDrift.push({
3741
- target: resource.target,
3742
- reason: "upstream-changed"
3743
- });
3744
- }
3745
- }
3746
- refreshedResources.push(resource);
3747
- }
3748
- if (variantEntry.version === currentVersion) {
3749
- if (installedIdx >= 0) {
3750
- prior.installed[installedIdx] = {
3751
- ...prior.installed[installedIdx],
3752
- resources: refreshedResources
3753
- };
3754
- await writeInstalledManifest(projectRoot, prior);
3755
- }
3756
- return {
3757
- status: "up-to-date",
3758
- packageName,
3759
- variant: currentVariant,
3760
- version: currentVersion,
3761
- frozenDrift
3762
- };
3763
- }
3764
- const lock = {
3765
- schemaVersion: 1,
3766
- variant: {
3767
- name: variantEntry.name,
3768
- displayName: variantEntry.displayName,
3769
- version: variantEntry.version,
3770
- from: packageName
3771
- },
3772
- packageVersion: catalog.version,
3773
- linked: variantEntry.linked,
3774
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
3775
- };
3776
- await writeFileSafe(
3777
- path21.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
3778
- JSON.stringify(lock, null, 2) + "\n"
3779
- );
3780
- config.packages.tokens.version = variantEntry.version;
3781
- await writeProjectConfig(projectRoot, config);
3782
- if (installedIdx >= 0) {
3783
- prior.installed[installedIdx] = {
3784
- ...prior.installed[installedIdx],
3785
- version: variantEntry.version,
3786
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
3787
- resources: refreshedResources
3788
- };
3789
- await writeInstalledManifest(projectRoot, prior);
3790
- }
3791
- const renames = selectApplicableRenames(
3792
- variantEntry.renames ?? [],
3793
- currentVersion,
3794
- variantEntry.version
3795
- );
3796
- let hintPath;
3797
- if (renames.length > 0) {
3798
- const hint = await writeTokensUpgradeHint({
3799
- projectRoot,
3800
- trigger: "update",
3801
- fromVariant: currentVariant,
3802
- toVariant: currentVariant,
3803
- fromVersion: currentVersion,
3804
- toVersion: variantEntry.version,
3805
- renames
3806
- });
3807
- if (hint) hintPath = hint.path;
3808
- }
3809
- return {
3810
- status: "updated",
3811
- packageName,
3812
- variant: currentVariant,
3813
- from: currentVersion,
3814
- to: variantEntry.version,
3815
- rewritten,
3816
- managedReplaced,
3817
- preserved,
3818
- frozenDrift,
3819
- renames,
3820
- ...hintPath ? { hintPath } : {}
3821
- };
3822
- }
3823
- function lookupUpstreamBasename(consumerBasename) {
3824
- for (const [upstream, consumer] of Object.entries(
3825
- CONSUMER_BASENAME_BY_UPSTREAM
3826
- )) {
3827
- if (consumer === consumerBasename) return upstream;
3828
- }
3829
- return consumerBasename;
3830
- }
3831
-
3832
- // src/core/ui-upgrade-detector.ts
3833
- import * as fs17 from "fs/promises";
3834
- import * as path22 from "path";
3835
- var PACKAGE_NAME = {
3836
- ui: "@teamix-evo/ui",
3837
- "biz-ui": "@teamix-evo/biz-ui"
3838
- };
3839
- var ALIAS_KEY = {
3840
- ui: "components",
3841
- "biz-ui": "business"
3842
- };
3843
- var COMPONENT_FILE_RE = /\.(tsx|ts)$/;
3844
- var SKIP_FILENAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx"]);
3845
- async function detectComponentLineage(options) {
3846
- const { projectRoot, category } = options;
3847
- const config = options.config ?? await readProjectConfig(projectRoot);
3848
- const installed = options.installed ?? await readInstalledManifest(projectRoot);
3849
- const installDir = resolveInstallDir(category, config);
3850
- const installDirAbs = path22.join(projectRoot, installDir);
3851
- const installDirExists = await directoryExists(installDirAbs);
3852
- const hasComponentsJson = await fileExists(
3853
- path22.join(projectRoot, "components.json")
3854
- );
3855
- const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
3856
- const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
3857
- const onDiskIds = installDirExists ? await listComponentIds(installDirAbs) : [];
3858
- const registeredSet = new Set(registeredIds);
3859
- const unregisteredIds = onDiskIds.filter((id) => !registeredSet.has(id)).sort();
3860
- const lineage = classifyLineage({
3861
- hasInstalled: installedPkg !== null,
3862
- hasComponentsJson,
3863
- onDiskIds,
3864
- unregisteredIds
3865
- });
3866
- return {
3867
- category,
3868
- lineage,
3869
- installDir,
3870
- installDirExists,
3871
- hasComponentsJson,
3872
- registeredIds,
3873
- unregisteredIds,
3874
- installedVersion: installedPkg?.version ?? null,
3875
- installedVariant: installedPkg?.variant ?? null
3876
- };
3877
- }
3878
- function resolveInstallDir(category, config) {
3879
- const aliasMap = config?.packages?.ui?.aliases ?? config?.packages?.["biz-ui"]?.aliases ?? DEFAULT_UI_ALIASES;
3880
- const key = ALIAS_KEY[category];
3881
- return aliasMap[key] ?? DEFAULT_UI_ALIASES[key];
3882
- }
3883
- function extractIds(pkg) {
3884
- const ids = /* @__PURE__ */ new Set();
3885
- for (const r of pkg.resources) {
3886
- const colon = r.id.indexOf(":");
3887
- ids.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
3888
- }
3889
- return [...ids];
3890
- }
3891
- async function directoryExists(p) {
3892
- try {
3893
- const stat5 = await fs17.stat(p);
3894
- return stat5.isDirectory();
3895
- } catch {
3896
- return false;
3897
- }
3898
- }
3899
- async function listComponentIds(installDirAbs) {
3900
- const entries = await fs17.readdir(installDirAbs, { withFileTypes: true });
3901
- const ids = [];
3902
- for (const e of entries) {
3903
- if (!e.isFile()) continue;
3904
- if (SKIP_FILENAMES.has(e.name)) continue;
3905
- if (!COMPONENT_FILE_RE.test(e.name)) continue;
3906
- ids.push(e.name.replace(COMPONENT_FILE_RE, ""));
3907
- }
3908
- return ids.sort();
3909
- }
3910
- function classifyLineage(args) {
3911
- const { hasInstalled, hasComponentsJson, onDiskIds, unregisteredIds } = args;
3912
- if (hasInstalled) {
3913
- return unregisteredIds.length === 0 ? "teamix-evo" : "mixed";
3914
- }
3915
- if (onDiskIds.length === 0) return "absent";
3916
- return hasComponentsJson ? "shadcn-native" : "custom-only";
3917
- }
3918
-
3919
- // src/core/ui-upgrade.ts
3920
- import * as path24 from "path";
3921
- import { createRequire as createRequire5 } from "module";
3922
- import {
3923
- loadUiPackageManifest as loadUiPackageManifest3,
3924
- loadVariantUiPackageManifest as loadVariantUiPackageManifest2
3925
- } from "@teamix-evo/registry";
3926
-
3927
- // src/core/ui-upgrade-staging.ts
3928
- import * as path23 from "path";
3929
- var TEAMIX_DIR4 = ".teamix-evo";
3930
- var STAGING_DIR = ".upgrade-staging";
3931
- var PACKAGE_NAME2 = {
3932
- ui: "@teamix-evo/ui",
3933
- "biz-ui": "@teamix-evo/biz-ui"
3934
- };
3935
- function isoToFsSafe3(iso) {
3936
- return iso.replace(/[:.]/g, "-");
3937
- }
3938
- async function buildUiUpgradeStaging(options) {
3939
- const { lineageReport, category } = options;
3940
- if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
3941
- return null;
3942
- }
3943
- const installed = options.installed ?? await readInstalledManifest(options.projectRoot);
3944
- const installedPkg = findInstalledPackage(installed, PACKAGE_NAME2[category]);
3945
- if (!installedPkg) return null;
3946
- const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3947
- const fsTs = isoToFsSafe3(isoTs);
3948
- const stagingDir = path23.join(
3949
- options.projectRoot,
3950
- TEAMIX_DIR4,
3951
- STAGING_DIR,
3952
- `${category}-${fsTs}`
3953
- );
3954
- const entryMap = new Map(
3955
- options.manifest.entries.map((e) => [e.id, e])
3956
- );
3957
- const resByEntryId = collectResourcesByEntry(installedPkg.resources);
3958
- const onlyIds = options.onlyIds && options.onlyIds.length > 0 ? new Set(options.onlyIds) : null;
3959
- const entries = [];
3960
- for (const id of lineageReport.registeredIds) {
3961
- if (onlyIds && !onlyIds.has(id)) continue;
3962
- const built = await processRegistered({
3963
- id,
3964
- entry: entryMap.get(id),
3965
- resource: resByEntryId.get(id),
3966
- packageRoot: options.packageRoot,
3967
- entryPackageRoot: options.entryPackageRoot,
3968
- aliases: options.aliases,
3969
- stagingDir,
3970
- projectRoot: options.projectRoot,
3971
- category,
3972
- sourceVersion: options.manifest.version
3973
- });
3974
- if (built) entries.push(built);
3975
- }
3976
- for (const id of lineageReport.unregisteredIds) {
3977
- if (onlyIds && !onlyIds.has(id)) continue;
3978
- const built = await processForeign({
3979
- id,
3980
- installDirAbs: path23.join(options.projectRoot, lineageReport.installDir),
3981
- stagingDir,
3982
- projectRoot: options.projectRoot,
3983
- category
3984
- });
3985
- if (built) entries.push(built);
3986
- }
3987
- if (entries.length === 0) return null;
3988
- const byRisk = aggregateByRisk(entries);
3989
- const manifestOut = {
3990
- schemaVersion: 1,
3991
- ts: isoTs,
3992
- package: category,
3993
- trigger: options.trigger,
3994
- variant: lineageReport.installedVariant ?? "_flat",
3995
- fromVersion: lineageReport.installedVersion ?? "",
3996
- toVersion: options.manifest.version,
3997
- lineage: lineageReport.lineage,
3998
- summary: { total: entries.length, byRisk },
3999
- entries
4000
- };
4001
- await ensureDir(stagingDir);
4002
- await writeFileSafe(
4003
- path23.join(stagingDir, "meta.json"),
4004
- JSON.stringify(manifestOut, null, 2) + "\n"
4005
- );
4006
- return { stagingDir, manifest: manifestOut };
4007
- }
4008
- async function processRegistered(args) {
4009
- const { id, entry, resource, stagingDir, projectRoot, category } = args;
4010
- if (!resource) return null;
4011
- const currentSource = await readFileOrNull(resource.target);
4012
- if (currentSource === null) {
4013
- return buildBreakingEntry({
4014
- id,
4015
- category,
4016
- resource,
4017
- projectRoot,
4018
- stagingDir,
4019
- currentSource: "",
4020
- hint: "installed file missing on disk"
4021
- });
4022
- }
4023
- if (!entry) {
4024
- return buildBreakingEntry({
4025
- id,
4026
- category,
4027
- resource,
4028
- projectRoot,
4029
- stagingDir,
4030
- currentSource,
4031
- hint: "entry removed in upstream package"
4032
- });
4033
- }
4034
- const file = entry.files[0];
4035
- if (!file) return null;
4036
- const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
4037
- const sourceAbs = path23.resolve(rootForEntry, file.source);
4038
- const raw = await readFileOrNull(sourceAbs);
4039
- if (raw === null) {
4040
- return null;
4041
- }
4042
- const incomingTransformed = rewriteImports(raw, args.aliases);
4043
- const incomingHash = computeHash(incomingTransformed);
4044
- const currentExt = path23.extname(resource.target) || ".tsx";
4045
- const incomingExt = path23.extname(file.targetName) || currentExt;
4046
- const currentRel = `${id}/current${currentExt}`;
4047
- const incomingRel = `${id}/incoming${incomingExt}`;
4048
- await writeFileSafe(path23.join(stagingDir, currentRel), currentSource);
4049
- await writeFileSafe(path23.join(stagingDir, incomingRel), incomingTransformed);
4050
- const diff = classifyRisk({
4051
- currentHash: resource.hash,
4052
- incomingHash,
4053
- currentSource,
4054
- incomingSource: incomingTransformed,
4055
- multiFile: entry.files.length > 1
4056
- });
4057
- const promotion = derivePromotion({
4058
- currentSource,
4059
- incomingSource: incomingTransformed,
4060
- targetName: entry.files[0]?.targetName ?? `${id}.tsx`
4061
- });
4062
- return {
4063
- id,
4064
- category,
4065
- current: {
4066
- target: path23.relative(projectRoot, resource.target),
4067
- hash: resource.hash,
4068
- sourceLineage: "teamix-evo"
4069
- },
4070
- incoming: {
4071
- sourceVersion: args.sourceVersion,
4072
- hash: incomingHash,
4073
- relPath: incomingRel
4074
- },
4075
- diff,
4076
- promotion
4077
- };
4078
- }
4079
- async function processForeign(args) {
4080
- const { id, installDirAbs, stagingDir, projectRoot, category } = args;
4081
- const tsx = path23.join(installDirAbs, `${id}.tsx`);
4082
- const ts = path23.join(installDirAbs, `${id}.ts`);
4083
- const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
4084
- if (!target) return null;
4085
- const raw = await readFileOrNull(target);
4086
- if (raw === null) return null;
4087
- const ext = path23.extname(target);
4088
- const currentRel = `${id}/current${ext}`;
4089
- await writeFileSafe(path23.join(stagingDir, currentRel), raw);
4090
- return {
4091
- id,
4092
- category,
4093
- current: {
4094
- target: path23.relative(projectRoot, target),
4095
- hash: computeHash(raw),
4096
- sourceLineage: "custom"
4097
- },
4098
- diff: {
4099
- riskLevel: "foreign",
4100
- hints: [
4101
- "component is on disk but not registered in .teamix-evo/manifest.json",
4102
- "AI should propose: (a) ignore, (b) re-register via teamix-evo ui add, or (c) remove"
4103
- ],
4104
- filesChangedCount: 0
4105
- }
4106
- };
4107
- }
4108
- async function buildBreakingEntry(args) {
4109
- const ext = path23.extname(args.resource.target) || ".tsx";
4110
- const currentRel = `${args.id}/current${ext}`;
4111
- await writeFileSafe(
4112
- path23.join(args.stagingDir, currentRel),
4113
- args.currentSource
4114
- );
4115
- return {
4116
- id: args.id,
4117
- category: args.category,
4118
- current: {
4119
- target: path23.relative(args.projectRoot, args.resource.target),
4120
- hash: args.resource.hash,
4121
- sourceLineage: "teamix-evo"
4122
- },
4123
- diff: {
4124
- riskLevel: "breaking",
4125
- hints: [args.hint],
4126
- filesChangedCount: 0
4127
- }
4128
- };
4129
- }
4130
- function classifyRisk(args) {
4131
- if (args.currentHash === args.incomingHash) {
4132
- return { riskLevel: "unchanged", hints: [], filesChangedCount: 0 };
4133
- }
4134
- const curExports = extractExportNames(args.currentSource);
4135
- const newExports = extractExportNames(args.incomingSource);
4136
- const removedExports = setDiff(curExports, newExports);
4137
- const addedExports = setDiff(newExports, curExports);
4138
- const curVariants = extractCvaVariantValues(args.currentSource);
4139
- const newVariants = extractCvaVariantValues(args.incomingSource);
4140
- const removedVariants = setDiff(curVariants, newVariants);
4141
- const addedVariants = setDiff(newVariants, curVariants);
4142
- const hints = [];
4143
- for (const e of removedExports) hints.push(`removed export: ${e}`);
4144
- for (const e of addedExports) hints.push(`new export: ${e}`);
4145
- for (const v of removedVariants) hints.push(`removed cva variant: ${v}`);
4146
- for (const v of addedVariants) hints.push(`new cva variant: ${v}`);
4147
- if (args.multiFile) hints.push("multi-file entry; only first file staged");
4148
- let riskLevel;
4149
- if (removedExports.length > 0 || removedVariants.length > 0) {
4150
- riskLevel = "risky";
4151
- } else if (addedExports.length > 0 || addedVariants.length > 0 || args.multiFile) {
4152
- riskLevel = "upgradable-medium";
4153
- } else {
4154
- riskLevel = "upgradable-low";
4155
- }
4156
- return { riskLevel, hints, filesChangedCount: 1 };
4157
- }
4158
- function extractExportNames(src) {
4159
- const names = /* @__PURE__ */ new Set();
4160
- const re = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/gm;
4161
- let m;
4162
- while ((m = re.exec(src)) !== null) {
4163
- if (m[1]) names.add(m[1]);
4164
- }
4165
- for (const dm of src.matchAll(/^\s*export\s+default\s+(\w+)\s*;/gm)) {
4166
- if (dm[1]) names.add(dm[1]);
4167
- }
4168
- return [...names];
4169
- }
4170
- function extractCvaVariantValues(src) {
4171
- const block = extractVariantsBlock(src);
4172
- if (block === null) return [];
4173
- const names = /* @__PURE__ */ new Set();
4174
- for (const groupBody of extractGroupBodies(block)) {
4175
- for (const km of groupBody.matchAll(/^\s*(?:['"]?)(\w+)(?:['"]?)\s*:/gm)) {
4176
- if (km[1]) names.add(km[1]);
4177
- }
4178
- }
4179
- return [...names];
4180
- }
4181
- function extractVariantsBlock(src) {
4182
- const idx = src.search(/\bvariants\s*:\s*\{/);
4183
- if (idx < 0) return null;
4184
- const open = src.indexOf("{", idx);
4185
- if (open < 0) return null;
4186
- let depth = 0;
4187
- for (let i = open; i < src.length; i++) {
4188
- const c = src[i];
4189
- if (c === "{") depth++;
4190
- else if (c === "}") {
4191
- depth--;
4192
- if (depth === 0) return src.slice(open + 1, i);
4193
- }
4194
- }
4195
- return null;
4196
- }
4197
- function* extractGroupBodies(block) {
4198
- const re = /(\w+)\s*:\s*\{/g;
4199
- let m;
4200
- while ((m = re.exec(block)) !== null) {
4201
- const open = block.indexOf("{", m.index);
4202
- if (open < 0) continue;
4203
- let depth = 0;
4204
- for (let i = open; i < block.length; i++) {
4205
- const c = block[i];
4206
- if (c === "{") depth++;
4207
- else if (c === "}") {
4208
- depth--;
4209
- if (depth === 0) {
4210
- yield block.slice(open + 1, i);
4211
- re.lastIndex = i + 1;
4212
- break;
4213
- }
4214
- }
4215
- }
4216
- }
4217
- }
4218
- function setDiff(a, b) {
4219
- const bset = new Set(b);
4220
- return a.filter((x) => !bset.has(x)).sort();
4221
- }
4222
- function collectResourcesByEntry(resources) {
4223
- const out = /* @__PURE__ */ new Map();
4224
- for (const r of resources) {
4225
- const colon = r.id.indexOf(":");
4226
- const eid = colon >= 0 ? r.id.slice(0, colon) : r.id;
4227
- if (!out.has(eid)) out.set(eid, r);
4228
- }
4229
- return out;
4230
- }
4231
- function aggregateByRisk(entries) {
4232
- const out = {};
4233
- for (const e of entries) {
4234
- const k = e.diff.riskLevel;
4235
- out[k] = (out[k] ?? 0) + 1;
4236
- }
4237
- return out;
4238
- }
4239
- function derivePromotion(args) {
4240
- const fileType = classifyPromoteFileType(args.targetName, args.currentSource);
4241
- const featureVector = buildFeatureVector(
4242
- args.currentSource,
4243
- args.incomingSource
4244
- );
4245
- const { recommendedModes, confidence, reasons } = scorePromotionModes(
4246
- fileType,
4247
- featureVector
4248
- );
4249
- return { fileType, featureVector, recommendedModes, confidence, reasons };
4250
- }
4251
- function classifyPromoteFileType(targetName, src) {
4252
- if (targetName.endsWith(".d.ts")) return "type";
4253
- if (/^use-[a-z0-9-]+\.tsx?$/i.test(targetName)) return "hook";
4254
- const hasJsx = /<[A-Za-z][^>]*?>/.test(src);
4255
- const hasReactImport = /from ['"]react['"]/.test(src);
4256
- const hasProvider = /\.Provider\b/.test(src) || /createContext\s*[<(]/.test(src);
4257
- if (hasProvider && (hasJsx || hasReactImport)) return "provider";
4258
- if (hasJsx || /forwardRef\s*[<(]/.test(src)) return "component";
4259
- if (!hasJsx && !hasReactImport) return "util";
4260
- return "component";
4261
- }
4262
- function buildFeatureVector(current, incoming) {
4263
- const curExports = extractExportNames(current);
4264
- const newExports = extractExportNames(incoming);
4265
- const apiAdded = setDiff(curExports, newExports);
4266
- const apiRemoved = setDiff(newExports, curExports);
4267
- const curVariants = extractCvaVariantValues(current);
4268
- const newVariants = extractCvaVariantValues(incoming);
4269
- const cvaAdded = setDiff(curVariants, newVariants);
4270
- const cvaModified = [];
4271
- const sharedVariants = curVariants.filter((v) => newVariants.includes(v));
4272
- for (const v of sharedVariants) {
4273
- if (extractVariantBody(current, v) !== extractVariantBody(incoming, v)) {
4274
- cvaModified.push(v);
4275
- }
4276
- }
4277
- const curClass = extractClassNameLiterals(current);
4278
- const newClass = extractClassNameLiterals(incoming);
4279
- const classNameDiff = curClass !== newClass;
4280
- const curTokens = extractTokenRefs(current);
4281
- const newTokens = extractTokenRefs(incoming);
4282
- const tokenUsageDiff = curTokens.size !== newTokens.size || [...curTokens].some((t) => !newTokens.has(t));
4283
- const hasState = /\buseState\s*[<(]/.test(current);
4284
- const hasEffect = /\b(useEffect|useLayoutEffect|useMemo|useCallback)\s*[<(]/.test(current);
4285
- const curImports = extractImportSources(current);
4286
- const newImports = extractImportSources(incoming);
4287
- const hasExtraImports = [...curImports].some((src) => !newImports.has(src));
4288
- const tagSet = /* @__PURE__ */ new Set();
4289
- for (const m of current.matchAll(/<([A-Z]\w+)[\s/>]/g)) {
4290
- if (m[1]) tagSet.add(m[1]);
4291
- }
4292
- const atomicChildren = [...tagSet];
4293
- const isComposition = atomicChildren.length > 2;
4294
- const signatureChanged = extractDefaultParamList(current) !== extractDefaultParamList(incoming);
4295
- return {
4296
- apiDelta: { added: apiAdded, removed: apiRemoved, signatureChanged },
4297
- styleDelta: { classNameDiff, tokenUsageDiff },
4298
- logicDelta: { hasState, hasEffect, hasExtraImports },
4299
- cvaDelta: { addedVariants: cvaAdded, modifiedVariants: cvaModified },
4300
- structureDelta: { isComposition, atomicChildren }
4301
- };
4302
- }
4303
- function scorePromotionModes(fileType, fv) {
4304
- const reasons = [];
4305
- if (fileType === "hook" || fileType === "util" || fileType === "type") {
4306
- reasons.push(
4307
- `fileType=${fileType} \u2014 not a component, deferred to ManualReview`
4308
- );
4309
- return { recommendedModes: ["ManualReview"], confidence: 0.5, reasons };
4310
- }
4311
- const apiNoChange = fv.apiDelta.added.length === 0 && fv.apiDelta.removed.length === 0 && !fv.apiDelta.signatureChanged;
4312
- const logicMinimal = !fv.logicDelta.hasState && !fv.logicDelta.hasEffect && !fv.logicDelta.hasExtraImports;
4313
- if (fv.apiDelta.removed.length > 0 || fv.apiDelta.signatureChanged) {
4314
- reasons.push(
4315
- "signature changed or props removed \u2014 Coexist preserves user version"
4316
- );
4317
- return { recommendedModes: ["Coexist"], confidence: 0.85, reasons };
4318
- }
4319
- if (apiNoChange && logicMinimal && (fv.styleDelta.classNameDiff || fv.styleDelta.tokenUsageDiff) && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
4320
- reasons.push(
4321
- "only style / token differences \u2014 push to tokens.overrides.css"
4322
- );
4323
- return { recommendedModes: ["TokenOnly"], confidence: 0.8, reasons };
4324
- }
4325
- const modes = [];
4326
- let score = 0;
4327
- if (fv.cvaDelta.addedVariants.length > 0 || fv.cvaDelta.modifiedVariants.length > 0) {
4328
- modes.push("Variant");
4329
- reasons.push(
4330
- `cva variants delta: +${fv.cvaDelta.addedVariants.length} ~${fv.cvaDelta.modifiedVariants.length}`
4331
- );
4332
- score = Math.max(score, 0.7);
4333
- }
4334
- if (fv.logicDelta.hasState || fv.logicDelta.hasEffect || fv.logicDelta.hasExtraImports || fv.apiDelta.added.length > 0) {
4335
- modes.push("Wrapper");
4336
- reasons.push("user added state / effect / imports / props");
4337
- score = Math.max(score, 0.75);
4338
- }
4339
- if (apiNoChange && logicMinimal && !fv.styleDelta.classNameDiff && !fv.styleDelta.tokenUsageDiff && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
4340
- modes.push("Preset");
4341
- reasons.push("no API/logic delta \u2014 Preset captures default-prop tweaks");
4342
- score = Math.max(score, 0.6);
4343
- }
4344
- if (fv.structureDelta.isComposition) {
4345
- modes.push("Compose");
4346
- reasons.push(
4347
- `composition of ${fv.structureDelta.atomicChildren.length} atomic children`
4348
- );
4349
- score = Math.max(score, 0.65);
4350
- }
4351
- if (modes.length === 0) {
4352
- reasons.push("no axis crossed the 0.6 threshold");
4353
- return { recommendedModes: ["ManualReview"], confidence: 0.4, reasons };
4354
- }
4355
- return { recommendedModes: modes, confidence: score, reasons };
4356
- }
4357
- function extractVariantBody(src, key) {
4358
- const block = extractVariantsBlock(src);
4359
- if (block === null) return "";
4360
- const re = new RegExp(`(?:["']?${key}["']?)\\s*:\\s*\\{`);
4361
- const idx = block.search(re);
4362
- if (idx < 0) return "";
4363
- const open = block.indexOf("{", idx);
4364
- if (open < 0) return "";
4365
- let depth = 0;
4366
- for (let i = open; i < block.length; i++) {
4367
- const c = block[i];
4368
- if (c === "{") depth++;
4369
- else if (c === "}") {
4370
- depth--;
4371
- if (depth === 0) return block.slice(open + 1, i);
4372
- }
4373
- }
4374
- return "";
4375
- }
4376
- function extractClassNameLiterals(src) {
4377
- const out = [];
4378
- for (const m of src.matchAll(/className\s*=\s*["'`]([^"'`]*)["'`]/g)) {
4379
- if (m[1]) out.push(m[1]);
4380
- }
4381
- for (const m of src.matchAll(/\b(?:cn|clsx|cva)\s*\(/g)) {
4382
- const open = (m.index ?? 0) + m[0].length - 1;
4383
- let depth = 1;
4384
- let i = open + 1;
4385
- for (; i < src.length && depth > 0; i++) {
4386
- const c = src[i];
4387
- if (c === "(") depth++;
4388
- else if (c === ")") depth--;
4389
- }
4390
- const body = src.slice(open + 1, i - 1);
4391
- for (const lit of body.matchAll(/["'`]([^"'`]*)["'`]/g)) {
4392
- if (lit[1]) out.push(lit[1]);
4393
- }
4394
- }
4395
- return out.sort().join("|");
4396
- }
4397
- function extractTokenRefs(src) {
4398
- const out = /* @__PURE__ */ new Set();
4399
- for (const m of src.matchAll(/var\(--([a-z0-9-]+)\)/g)) {
4400
- if (m[1]) out.add(m[1]);
4401
- }
4402
- for (const m of src.matchAll(/--([a-z][a-z0-9-]*)\s*:/g)) {
4403
- if (m[1]) out.add(m[1]);
4404
- }
4405
- return out;
4406
- }
4407
- function extractImportSources(src) {
4408
- const out = /* @__PURE__ */ new Set();
4409
- for (const m of src.matchAll(/^\s*import\b[^'"]*['"]([^'"]+)['"]/gm)) {
4410
- if (m[1]) out.add(m[1]);
4411
- }
4412
- return out;
4413
- }
4414
- function extractDefaultParamList(src) {
4415
- const m = /export\s+default\s+(?:async\s+)?function\s+\w*\s*\(([^)]*)\)/.exec(src) ?? /export\s+default\s+(?:\([^)]*\)|\w+)\s*=>/.exec(src) ?? /(?:const|function)\s+\w+\s*=?\s*(?:\(([^)]*)\)|\w+)\s*(?:=>|\{)/.exec(src);
4416
- return m?.[1]?.replace(/\s+/g, " ").trim() ?? "";
4417
- }
4418
-
4419
- // src/core/ui-upgrade.ts
4420
- var nodeRequire = createRequire5(import.meta.url);
4421
- function resolvePackageRoot4(packageName) {
4422
- const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
4423
- return path24.dirname(pkgJsonPath);
4424
- }
4425
- async function buildStaging(args) {
4426
- const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
4427
- if (category === "ui") {
4428
- const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
4429
- const manifest = await loadUiPackageManifest3(root);
4430
- return buildUiUpgradeStaging({
4431
- projectRoot,
4432
- category,
4433
- manifest,
4434
- packageRoot: root,
4435
- aliases,
4436
- lineageReport,
4437
- trigger,
4438
- onlyIds
4439
- });
4440
- }
4441
- const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
4442
- const variant = lineageReport.installedVariant ?? "_flat";
4443
- const variantDir = path24.join(bizRoot, "variants", variant);
4444
- const variantManifest = await loadVariantUiPackageManifest2(variantDir);
4445
- const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
4446
- const uiManifest = await loadUiPackageManifest3(uiRoot);
4447
- const entryPackageRoot = /* @__PURE__ */ new Map();
4448
- const merged = [];
4449
- for (const e of variantManifest.entries) {
4450
- entryPackageRoot.set(e.id, variantDir);
4451
- merged.push(e);
4452
- }
4453
- for (const e of uiManifest.entries) {
4454
- if (entryPackageRoot.has(e.id)) continue;
4455
- entryPackageRoot.set(e.id, uiRoot);
4456
- merged.push(e);
4457
- }
4458
- const synthetic = {
4459
- schemaVersion: 1,
4460
- package: "ui",
4461
- version: variantManifest.version,
4462
- engines: variantManifest.engines,
4463
- entries: merged
4464
- };
4465
- return buildUiUpgradeStaging({
4466
- projectRoot,
4467
- category,
4468
- manifest: synthetic,
4469
- packageRoot: variantDir,
4470
- entryPackageRoot,
4471
- aliases,
4472
- lineageReport,
4473
- trigger,
4474
- onlyIds
4475
- });
4476
- }
4477
-
4478
- // src/core/project-update.ts
4479
- var DEFAULT_TOKENS_PACKAGE3 = "@teamix-evo/tokens";
4480
- var DEFAULT_SKILLS_PACKAGE4 = "@teamix-evo/skills";
4481
- var CRITICAL_STEPS2 = /* @__PURE__ */ new Set(["tokens"]);
4482
- async function runProjectUpdate(options) {
4483
- const { projectRoot, dryRun = false, onStep } = options;
4484
- const tokensPackage = options.tokensPackageName ?? DEFAULT_TOKENS_PACKAGE3;
4485
- const skillsPackage = options.skillsPackageName ?? DEFAULT_SKILLS_PACKAGE4;
4486
- const config = await readProjectConfig(projectRoot);
4487
- if (!config) {
4488
- return { status: "not-initialized" };
4489
- }
4490
- let snapshot = null;
4491
- let snapshotError;
4492
- if (!dryRun) {
4493
- try {
4494
- 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
+ }
4495
3210
  } catch (err) {
4496
- snapshotError = getErrorMessage(err);
4497
- }
4498
- }
4499
- const steps = [];
4500
- let aborted = false;
4501
- const firstFailure = { value: null };
4502
- function record(step) {
4503
- steps.push(step);
4504
- onStep?.(step);
4505
- }
4506
- function recordFailure(name, err) {
4507
- const message = getErrorMessage(err);
4508
- record({ name, status: "fail", detail: message });
4509
- if (!firstFailure.value) {
4510
- firstFailure.value = { step: name, error: message };
3211
+ recordFailure("skills", err);
4511
3212
  }
4512
- if (CRITICAL_STEPS2.has(name)) aborted = true;
4513
3213
  }
4514
- let anyChanged = false;
4515
- if (!config.packages?.tokens) {
3214
+ if (dryRun) {
4516
3215
  record({
4517
- name: "tokens",
4518
- status: "skip",
4519
- detail: "tokens not installed"
3216
+ name: "agents-md",
3217
+ status: "planned",
3218
+ detail: "runGenerateAgentsMd()"
4520
3219
  });
4521
- } else if (dryRun) {
3220
+ } else {
4522
3221
  try {
4523
- const plan = await planTokensUpdate(tokensPackage, config);
4524
- 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
+ });
4525
3244
  } catch (err) {
4526
- recordFailure("tokens", err);
3245
+ recordFailure("agents-md", err);
4527
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
+ });
4528
3256
  } else {
4529
3257
  try {
4530
- const result = await runTokensUpdate({
4531
- projectRoot,
4532
- 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
4533
3263
  });
4534
- if (result.status === "not-initialized") {
4535
- record({
4536
- name: "tokens",
4537
- status: "skip",
4538
- detail: "tokens not installed"
4539
- });
4540
- } else if (result.status === "up-to-date") {
4541
- const driftSuffix = result.frozenDrift.length > 0 ? `, frozen drift: ${result.frozenDrift.length}` : "";
4542
- record({
4543
- name: "tokens",
4544
- status: "ok",
4545
- detail: `up-to-date (${result.variant} v${result.version}${driftSuffix})`
4546
- });
4547
- } else {
4548
- anyChanged = true;
4549
- const extras = [];
4550
- if (result.managedReplaced.length > 0)
4551
- extras.push(`managed: ${result.managedReplaced.length}`);
4552
- if (result.frozenDrift.length > 0)
4553
- extras.push(`frozen drift: ${result.frozenDrift.length}`);
4554
- const suffix = extras.length > 0 ? ` [${extras.join(", ")}]` : "";
4555
- record({
4556
- name: "tokens",
4557
- status: "ok",
4558
- detail: `${result.variant} v${result.from} \u2192 v${result.to}${suffix}`
4559
- });
4560
- }
4561
3264
  } catch (err) {
4562
- recordFailure("tokens", err);
3265
+ recordFailure("ui-init", err);
4563
3266
  }
4564
3267
  }
4565
- if (!config.packages?.skills) {
3268
+ const collectedNpmDeps = {};
3269
+ if (dryRun) {
3270
+ const { manifest } = await loadUiData("@teamix-evo/ui").catch(() => ({
3271
+ manifest: { entries: [] }
3272
+ }));
4566
3273
  record({
4567
- name: "skills",
4568
- status: "skip",
4569
- detail: "skills not installed"
3274
+ name: "ui-add",
3275
+ status: "planned",
3276
+ detail: `runUiAdd(${manifest.entries.length} entries, --all)`
4570
3277
  });
4571
3278
  } else if (aborted) {
4572
3279
  record({
4573
- name: "skills",
3280
+ name: "ui-add",
4574
3281
  status: "skip",
4575
3282
  detail: "aborted: earlier critical step failed"
4576
3283
  });
4577
3284
  } else {
4578
3285
  try {
4579
- 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({
4580
3289
  projectRoot,
4581
- dryRun,
4582
- 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)`
3317
+ });
3318
+ } catch {
3319
+ record({
3320
+ name: "biz-ui-add",
3321
+ status: "planned",
3322
+ detail: `runBizUiAdd(variant=${variant})`
4583
3323
  });
4584
- if (result.status === "no-skills") {
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) {
4585
3335
  record({
4586
- name: "skills",
3336
+ name: "biz-ui-add",
4587
3337
  status: "skip",
4588
- 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
4589
3347
  });
4590
- } else if (result.status === "no-changes") {
4591
3348
  record({
4592
- name: "skills",
3349
+ name: "biz-ui-add",
4593
3350
  status: "ok",
4594
- 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)`
4595
3352
  });
4596
- } else if (result.status === "dry-run") {
4597
- const bumps = result.plan.filter((p) => p.action === "version-bump");
4598
- 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}`;
4599
- record({ name: "skills", status: "planned", detail });
4600
- } else {
4601
- anyChanged = true;
4602
- const summary = result.updatedSkillIds.length > 0 ? `updated: ${result.updatedSkillIds.join(", ")} (v${result.version})` : `refreshed at v${result.version}`;
4603
- record({ name: "skills", status: "ok", detail: summary });
3353
+ if (result.npmDependencies) {
3354
+ Object.assign(collectedNpmDeps, result.npmDependencies);
3355
+ }
4604
3356
  }
4605
3357
  } catch (err) {
4606
- recordFailure("skills", err);
3358
+ recordFailure("biz-ui-add", err);
4607
3359
  }
4608
3360
  }
4609
- await runComponentSourceStep("ui", {
4610
- projectRoot,
4611
- config,
4612
- dryRun,
4613
- record,
4614
- recordFailure
4615
- });
4616
- await runComponentSourceStep("biz-ui", {
4617
- projectRoot,
4618
- config,
4619
- dryRun,
4620
- record,
4621
- recordFailure
4622
- });
4623
- const out = (() => {
4624
- if (firstFailure.value) {
4625
- return {
4626
- status: "partial",
4627
- steps,
4628
- resumeHint: {
4629
- failedAt: firstFailure.value.step,
4630
- completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
4631
- failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
4632
- error: firstFailure.value.error,
4633
- resumeCommand: "teamix-evo update"
4634
- },
4635
- snapshot,
4636
- ...snapshotError ? { snapshotError } : {}
4637
- };
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
+ );
4638
3374
  }
4639
- if (dryRun) return { status: "dry-run", steps, snapshot: null };
4640
- if (anyChanged)
4641
- return {
4642
- status: "updated",
4643
- steps,
4644
- snapshot,
4645
- ...snapshotError ? { snapshotError } : {}
4646
- };
4647
- return {
4648
- status: "up-to-date",
4649
- steps,
4650
- snapshot,
4651
- ...snapshotError ? { snapshotError } : {}
4652
- };
4653
- })();
4654
- return out;
4655
- }
4656
- async function runComponentSourceStep(category, args) {
4657
- const { projectRoot, config, dryRun, record, recordFailure } = args;
4658
- const cfgKey = category === "ui" ? "ui" : "biz-ui";
4659
- if (!config.packages?.[cfgKey]) {
4660
- record({
4661
- name: category,
4662
- status: "skip",
4663
- detail: `${category} not installed`
4664
- });
4665
- return;
4666
- }
4667
- const aliases = config.packages.ui?.aliases ?? config.packages["biz-ui"]?.aliases;
4668
- if (!aliases) {
4669
- record({
4670
- name: category,
4671
- status: "skip",
4672
- detail: `${category} aliases not configured`
4673
- });
4674
- return;
4675
- }
4676
- let report;
4677
- try {
4678
- report = await detectComponentLineage({
4679
- projectRoot,
4680
- category,
4681
- config
4682
- });
4683
- } catch (err) {
4684
- record({
4685
- name: category,
4686
- status: "skip",
4687
- detail: `lineage detect failed: ${getErrorMessage(err)}`
4688
- });
4689
- return;
4690
3375
  }
4691
- if (report.lineage !== "teamix-evo" && report.lineage !== "mixed") {
4692
- record({
4693
- name: category,
4694
- status: "skip",
4695
- detail: `lineage=${report.lineage}; nothing to stage`
4696
- });
4697
- 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
+ }
4698
3406
  }
4699
3407
  if (dryRun) {
4700
- const total = report.registeredIds.length + report.unregisteredIds.length;
4701
3408
  record({
4702
- name: category,
3409
+ name: "gitignore",
4703
3410
  status: "planned",
4704
- detail: total === 0 ? "no components to stage" : `${total} component(s) to stage (lineage=${report.lineage})`
3411
+ detail: "append teamix-evo runtime rules"
4705
3412
  });
4706
- 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
+ }
4707
3472
  }
4708
- try {
4709
- const built = await buildStaging({
4710
- category,
4711
- projectRoot,
4712
- aliases,
4713
- lineageReport: report,
4714
- trigger: "update",
4715
- onlyIds: []
4716
- });
4717
- if (built === null) {
4718
- record({
4719
- name: category,
4720
- status: "skip",
4721
- detail: "no entries to stage"
4722
- });
4723
- 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)");
4724
3497
  }
4725
- const stagingRel = path25.relative(projectRoot, built.stagingDir);
4726
- const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
4727
- record({
4728
- name: category,
4729
- status: "ok",
4730
- detail: `${built.manifest.summary.total} component(s) staged${summary ? ` (${summary})` : ""}, see ${stagingRel}`
4731
- });
4732
- } catch (err) {
4733
- recordFailure(category, err);
4734
3498
  }
3499
+ return out;
4735
3500
  }
4736
- function summarizeStagingRisk(byRisk) {
4737
- const order = [
4738
- "risky",
4739
- "breaking",
4740
- "foreign",
4741
- "upgradable-medium",
4742
- "upgradable-low",
4743
- "unchanged"
4744
- ];
4745
- const parts = [];
4746
- for (const k of order) {
4747
- const v = byRisk[k];
4748
- if (v && v > 0) parts.push(`${v} ${k}`);
4749
- }
4750
- return parts.join(", ");
4751
- }
4752
- async function planTokensUpdate(tokensPackage, config) {
4753
- const tokensCfg = config.packages?.tokens;
4754
- if (!tokensCfg) return "tokens not installed";
4755
- const packageRoot = resolveTokensPackageRoot(tokensPackage);
4756
- const catalog = await loadTokensPackageManifest3(packageRoot);
4757
- const variantEntry = getVariantEntry3(catalog, tokensCfg.variant);
4758
- if (!variantEntry) {
4759
- 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
+ });
4760
3519
  }
4761
- if (variantEntry.version === tokensCfg.version) {
4762
- 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
+ });
4763
3527
  }
4764
- return `${tokensCfg.variant} v${tokensCfg.version} \u2192 v${variantEntry.version}`;
3528
+ return out;
4765
3529
  }
4766
3530
 
4767
3531
  // src/core/installer.ts
4768
- import * as path26 from "path";
4769
- import * as fs18 from "fs/promises";
3532
+ import * as path20 from "path";
3533
+ import * as fs15 from "fs/promises";
4770
3534
  async function installResources(options) {
4771
3535
  const { projectRoot, manifest, data, variantDir, packageRoot } = options;
4772
3536
  const installedResources = [];
@@ -4803,13 +3567,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
4803
3567
  variantDir,
4804
3568
  packageRoot
4805
3569
  );
4806
- const targetPath = path26.join(projectRoot, resource.target);
3570
+ const targetPath = path20.join(projectRoot, resource.target);
4807
3571
  let content;
4808
3572
  if (resource.template) {
4809
3573
  const templateContent = await loadTemplateFile(sourcePath);
4810
3574
  content = renderTemplate(templateContent, data);
4811
3575
  } else {
4812
- content = await fs18.readFile(sourcePath, "utf-8");
3576
+ content = await fs15.readFile(sourcePath, "utf-8");
4813
3577
  }
4814
3578
  await writeFileSafe(targetPath, content);
4815
3579
  const hash = computeHash(content);
@@ -4827,13 +3591,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
4827
3591
  variantDir,
4828
3592
  packageRoot
4829
3593
  );
4830
- const targetDir = path26.join(projectRoot, resource.target);
3594
+ const targetDir = path20.join(projectRoot, resource.target);
4831
3595
  const results = [];
4832
3596
  await ensureDir(targetDir);
4833
3597
  const entries = await walkDir(sourcePath);
4834
3598
  for (const entry of entries) {
4835
- const relPath = path26.relative(sourcePath, entry);
4836
- let targetFile = path26.join(targetDir, relPath);
3599
+ const relPath = path20.relative(sourcePath, entry);
3600
+ let targetFile = path20.join(targetDir, relPath);
4837
3601
  if (resource.template && targetFile.endsWith(".hbs")) {
4838
3602
  targetFile = targetFile.slice(0, -4);
4839
3603
  }
@@ -4842,11 +3606,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
4842
3606
  const templateContent = await loadTemplateFile(entry);
4843
3607
  content = renderTemplate(templateContent, data);
4844
3608
  } else {
4845
- content = await fs18.readFile(entry, "utf-8");
3609
+ content = await fs15.readFile(entry, "utf-8");
4846
3610
  }
4847
3611
  await writeFileSafe(targetFile, content);
4848
3612
  const hash = computeHash(content);
4849
- const targetRel = path26.relative(projectRoot, targetFile);
3613
+ const targetRel = path20.relative(projectRoot, targetFile);
4850
3614
  results.push({
4851
3615
  id: `${resource.id}:${relPath}`,
4852
3616
  target: targetRel,
@@ -4859,25 +3623,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
4859
3623
  }
4860
3624
 
4861
3625
  // src/core/registry-client.ts
4862
- import * as path27 from "path";
4863
- import * as fs19 from "fs/promises";
4864
- 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";
4865
3629
  import { loadVariantManifest } from "@teamix-evo/registry";
4866
- var require6 = createRequire6(import.meta.url);
4867
- function resolvePackageRoot5(packageName) {
3630
+ var require6 = createRequire5(import.meta.url);
3631
+ function resolvePackageRoot4(packageName) {
4868
3632
  const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
4869
- return path27.dirname(pkgJsonPath);
3633
+ return path21.dirname(pkgJsonPath);
4870
3634
  }
4871
3635
  async function loadVariantData(packageName, variant) {
4872
- const packageRoot = resolvePackageRoot5(packageName);
4873
- const variantDir = path27.join(packageRoot, "library", variant);
3636
+ const packageRoot = resolvePackageRoot4(packageName);
3637
+ const variantDir = path21.join(packageRoot, "library", variant);
4874
3638
  logger.debug(`Resolved variant dir: ${variantDir}`);
4875
3639
  logger.debug(`Package root: ${packageRoot}`);
4876
3640
  const manifest = await loadVariantManifest(variantDir);
4877
3641
  let data = {};
4878
- const dataPath = path27.join(variantDir, "_data.json");
3642
+ const dataPath = path21.join(variantDir, "_data.json");
4879
3643
  try {
4880
- const raw = await fs19.readFile(dataPath, "utf-8");
3644
+ const raw = await fs16.readFile(dataPath, "utf-8");
4881
3645
  data = JSON.parse(raw);
4882
3646
  } catch (err) {
4883
3647
  if (err.code !== "ENOENT") {
@@ -4914,7 +3678,6 @@ export {
4914
3678
  runGenerateAgentsMd,
4915
3679
  runLintInit,
4916
3680
  runProjectInit,
4917
- runProjectUpdate,
4918
3681
  runSkillsAdd,
4919
3682
  runSkillsInit,
4920
3683
  runSkillsUpdate,