teamix-evo 0.15.6 → 0.17.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.
@@ -461,9 +461,9 @@ async function installSkills(options) {
461
461
  async function renderSkillFiles(skill, options) {
462
462
  const { data, packageRoot } = options;
463
463
  const sourceAbs = path7.resolve(packageRoot, skill.source);
464
- const stat3 = await fs6.stat(sourceAbs);
464
+ const stat4 = await fs6.stat(sourceAbs);
465
465
  const rendered = /* @__PURE__ */ new Map();
466
- if (stat3.isFile()) {
466
+ if (stat4.isFile()) {
467
467
  const content = await renderSkillContent(sourceAbs, skill, data);
468
468
  rendered.set("SKILL.md", content);
469
469
  return rendered;
@@ -726,13 +726,13 @@ async function pruneEmptyIdeSkillDirs(args) {
726
726
  }
727
727
  for (const name of entries) {
728
728
  const dir = path7.join(skillsRoot, name);
729
- let stat3;
729
+ let stat4;
730
730
  try {
731
- stat3 = await fs6.stat(dir);
731
+ stat4 = await fs6.stat(dir);
732
732
  } catch {
733
733
  continue;
734
734
  }
735
- if (!stat3.isDirectory()) continue;
735
+ if (!stat4.isDirectory()) continue;
736
736
  let children;
737
737
  try {
738
738
  children = await fs6.readdir(dir);
@@ -2789,13 +2789,44 @@ async function detectProjectState2(cwd) {
2789
2789
  significantEntries: []
2790
2790
  };
2791
2791
  }
2792
+ let legacyLib = "unknown";
2793
+ let buildTool = "unknown";
2794
+ let allDeps = {};
2795
+ if (hasPackageJson) {
2796
+ const pkgRaw = await readFileOrNull(path15.join(absCwd, "package.json"));
2797
+ if (pkgRaw) {
2798
+ try {
2799
+ const pkg = JSON.parse(pkgRaw);
2800
+ allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2801
+ } catch {
2802
+ }
2803
+ }
2804
+ }
2805
+ if ("@alifd/next" in allDeps || "@ali/teamix-material" in allDeps) {
2806
+ legacyLib = "fusion";
2807
+ } else if ("antd" in allDeps) {
2808
+ legacyLib = "antd";
2809
+ } else if ("element-ui" in allDeps || "element-plus" in allDeps) {
2810
+ legacyLib = "element";
2811
+ }
2812
+ if (await fileExists(path15.join(absCwd, "build.json"))) {
2813
+ buildTool = "ice";
2814
+ } else if (await fileExists(path15.join(absCwd, "vite.config.ts")) || await fileExists(path15.join(absCwd, "vite.config.js")) || await fileExists(path15.join(absCwd, "vite.config.mts"))) {
2815
+ buildTool = "vite";
2816
+ } else if (await fileExists(path15.join(absCwd, "webpack.config.js")) || await fileExists(path15.join(absCwd, "webpack.config.ts"))) {
2817
+ buildTool = "webpack";
2818
+ } else if ("umi" in allDeps || "@umijs/max" in allDeps) {
2819
+ buildTool = "umi";
2820
+ }
2792
2821
  return {
2793
2822
  state: "other",
2794
2823
  cwd: absCwd,
2795
2824
  hasTeamixDir: false,
2796
2825
  hasPackageJson,
2797
2826
  hasComponentsJson,
2798
- significantEntries: significant.slice(0, 20).sort()
2827
+ significantEntries: significant.slice(0, 20).sort(),
2828
+ legacyLib,
2829
+ buildTool
2799
2830
  };
2800
2831
  }
2801
2832
 
@@ -2803,7 +2834,8 @@ async function detectProjectState2(cwd) {
2803
2834
  var COMMAND_LABELS = {
2804
2835
  init: "\u521D\u59CB\u5316 Teamix Evo \u5957\u4EF6",
2805
2836
  migrate: "shadcn \u9879\u76EE\u8FC1\u79FB\u4E3A Teamix Evo \u5957\u4EF6",
2806
- update: "Teamix Evo \u5957\u4EF6\u66F4\u65B0"
2837
+ update: "Teamix Evo \u5957\u4EF6\u66F4\u65B0",
2838
+ graft: "\u53E0\u52A0 Teamix Evo \u80FD\u529B"
2807
2839
  };
2808
2840
  var STATE_LABELS = {
2809
2841
  empty: "\u7A7A\u76EE\u5F55",
@@ -2814,13 +2846,14 @@ var STATE_LABELS = {
2814
2846
  var VALID_STATES = {
2815
2847
  init: "empty",
2816
2848
  migrate: "shadcn",
2817
- update: "teamix-evo"
2849
+ update: "teamix-evo",
2850
+ graft: "other"
2818
2851
  };
2819
2852
  var STATE_TO_COMMAND = {
2820
2853
  empty: "init",
2821
2854
  shadcn: "migrate",
2822
2855
  "teamix-evo": "update",
2823
- other: null
2856
+ other: "graft"
2824
2857
  };
2825
2858
  function assertCommandPrecondition(command, state) {
2826
2859
  const expected = VALID_STATES[command];
@@ -2834,13 +2867,6 @@ function assertCommandPrecondition(command, state) {
2834
2867
  if (suggestion) {
2835
2868
  const suggestionLabel = COMMAND_LABELS[suggestion];
2836
2869
  logger.info(`\u5EFA\u8BAE\u4F7F\u7528\uFF1Ateamix-evo ${suggestion}\uFF08${suggestionLabel}\uFF09`);
2837
- } else {
2838
- logger.info(
2839
- "\u5F53\u524D\u9879\u76EE\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u76F4\u63A5\u63A5\u5165\u3002"
2840
- );
2841
- logger.info(
2842
- "\u5EFA\u8BAE\uFF1A\u5148\u7528 teamix-evo init \u521D\u59CB\u5316 Teamix Evo \u5957\u4EF6\uFF08\u7A7A\u76EE\u5F55\uFF09\uFF0C\u7136\u540E\u5BF9\u65E7\u9879\u76EE\u8FDB\u884C\u91CD\u6784\u3002"
2843
- );
2844
2870
  }
2845
2871
  process.exitCode = 1;
2846
2872
  }
@@ -2892,8 +2918,8 @@ var STYLELINT_CONFIG_CANDIDATES = [
2892
2918
  ];
2893
2919
  async function isDir(target) {
2894
2920
  try {
2895
- const stat3 = await fs15.stat(target);
2896
- return stat3.isDirectory();
2921
+ const stat4 = await fs15.stat(target);
2922
+ return stat4.isDirectory();
2897
2923
  } catch {
2898
2924
  return false;
2899
2925
  }
@@ -3117,8 +3143,30 @@ async function detectStylelintConfig(cwd) {
3117
3143
  };
3118
3144
  }
3119
3145
 
3120
- // src/core/meta-installer.ts
3146
+ // src/core/html-theme-patch.ts
3121
3147
  import * as path17 from "path";
3148
+ async function patchHtmlDataTheme(projectRoot, variant) {
3149
+ const htmlPath = path17.join(projectRoot, "index.html");
3150
+ const content = await readFileOrNull(htmlPath);
3151
+ if (content === null) {
3152
+ return { status: "not-found", htmlPath };
3153
+ }
3154
+ if (/data-theme\s*=/.test(content)) {
3155
+ return { status: "already-set", htmlPath };
3156
+ }
3157
+ const patched = content.replace(
3158
+ /<html([^>]*)>/,
3159
+ `<html$1 data-theme="${variant}">`
3160
+ );
3161
+ if (patched === content) {
3162
+ return { status: "not-found", htmlPath };
3163
+ }
3164
+ await writeFileSafe(htmlPath, patched);
3165
+ return { status: "patched", htmlPath };
3166
+ }
3167
+
3168
+ // src/core/meta-installer.ts
3169
+ import * as path18 from "path";
3122
3170
  import * as fs16 from "fs/promises";
3123
3171
  import { createRequire as createRequire5 } from "module";
3124
3172
  import {
@@ -3129,11 +3177,11 @@ var require6 = createRequire5(import.meta.url);
3129
3177
  var META_DIR = "meta";
3130
3178
  function resolvePackageRoot4(packageName) {
3131
3179
  const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
3132
- return path17.dirname(pkgJsonPath);
3180
+ return path18.dirname(pkgJsonPath);
3133
3181
  }
3134
3182
  function getMetaDir(projectRoot, category) {
3135
- const base = path17.join(getTeamixDir(projectRoot), META_DIR);
3136
- return category ? path17.join(base, category) : base;
3183
+ const base = path18.join(getTeamixDir(projectRoot), META_DIR);
3184
+ return category ? path18.join(base, category) : base;
3137
3185
  }
3138
3186
  async function landUiMeta(projectRoot) {
3139
3187
  const packageRoot = resolvePackageRoot4("@teamix-evo/ui");
@@ -3147,7 +3195,7 @@ async function landUiMeta(projectRoot) {
3147
3195
  }
3148
3196
  async function landBizUiMeta(projectRoot, variant) {
3149
3197
  const packageRoot = resolvePackageRoot4("@teamix-evo/biz-ui");
3150
- const variantRoot = path17.join(packageRoot, "variants", variant);
3198
+ const variantRoot = path18.join(packageRoot, "variants", variant);
3151
3199
  const manifest = await loadVariantUiPackageManifest2(variantRoot);
3152
3200
  return landManifestMeta({
3153
3201
  projectRoot,
@@ -3161,8 +3209,8 @@ async function landManifestMeta(opts) {
3161
3209
  await ensureTeamixDir(projectRoot);
3162
3210
  const metaDir = getMetaDir(projectRoot, category);
3163
3211
  await ensureDir(metaDir);
3164
- const manifestDest = path17.join(metaDir, "manifest.json");
3165
- const manifestSrc = path17.join(packageRoot, "manifest.json");
3212
+ const manifestDest = path18.join(metaDir, "manifest.json");
3213
+ const manifestSrc = path18.join(packageRoot, "manifest.json");
3166
3214
  await fs16.copyFile(manifestSrc, manifestDest);
3167
3215
  logger.debug(`meta: copied manifest.json \u2192 ${manifestDest}`);
3168
3216
  let written = 0;
@@ -3170,8 +3218,8 @@ async function landManifestMeta(opts) {
3170
3218
  for (const entry of manifest.entries) {
3171
3219
  if (!entry.meta) continue;
3172
3220
  total++;
3173
- const srcPath = path17.join(packageRoot, entry.meta);
3174
- const destPath = path17.join(metaDir, `${entry.id}.md`);
3221
+ const srcPath = path18.join(packageRoot, entry.meta);
3222
+ const destPath = path18.join(metaDir, `${entry.id}.md`);
3175
3223
  try {
3176
3224
  await fs16.copyFile(srcPath, destPath);
3177
3225
  written++;
@@ -3197,17 +3245,17 @@ async function landManifestMeta(opts) {
3197
3245
 
3198
3246
  // src/core/deps-install.ts
3199
3247
  import * as fs17 from "fs/promises";
3200
- import * as path18 from "path";
3248
+ import * as path19 from "path";
3201
3249
  import { exec } from "child_process";
3202
3250
  import { promisify } from "util";
3203
3251
  var execAsync = promisify(exec);
3204
3252
  async function detectPackageManager(projectRoot) {
3205
- if (await fileExists(path18.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3206
- if (await fileExists(path18.join(projectRoot, "pnpm-workspace.yaml")))
3253
+ if (await fileExists(path19.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3254
+ if (await fileExists(path19.join(projectRoot, "pnpm-workspace.yaml")))
3207
3255
  return "pnpm";
3208
- if (await fileExists(path18.join(projectRoot, "bun.lockb"))) return "bun";
3209
- if (await fileExists(path18.join(projectRoot, "bun.lock"))) return "bun";
3210
- if (await fileExists(path18.join(projectRoot, "yarn.lock"))) return "yarn";
3256
+ if (await fileExists(path19.join(projectRoot, "bun.lockb"))) return "bun";
3257
+ if (await fileExists(path19.join(projectRoot, "bun.lock"))) return "bun";
3258
+ if (await fileExists(path19.join(projectRoot, "yarn.lock"))) return "yarn";
3211
3259
  return "npm";
3212
3260
  }
3213
3261
  function getInstallCommand(pm) {
@@ -3224,7 +3272,7 @@ function getInstallCommand(pm) {
3224
3272
  }
3225
3273
  async function installProjectDeps(options) {
3226
3274
  const { projectRoot, npmDependencies, skipInstall = false } = options;
3227
- const pkgPath = path18.join(projectRoot, "package.json");
3275
+ const pkgPath = path19.join(projectRoot, "package.json");
3228
3276
  const raw = await readFileOrNull(pkgPath);
3229
3277
  if (!raw) {
3230
3278
  throw new Error(
@@ -3316,18 +3364,18 @@ ${stepLines}
3316
3364
 
3317
3365
  // src/core/file-changes.ts
3318
3366
  import * as fs18 from "fs/promises";
3319
- import * as path19 from "path";
3367
+ import * as path20 from "path";
3320
3368
  function toRelativePosix(p, projectRoot) {
3321
3369
  let rel2 = p;
3322
- if (path19.isAbsolute(p)) {
3323
- rel2 = path19.relative(projectRoot, p);
3370
+ if (path20.isAbsolute(p)) {
3371
+ rel2 = path20.relative(projectRoot, p);
3324
3372
  }
3325
- return rel2.split(path19.sep).join("/");
3373
+ return rel2.split(path20.sep).join("/");
3326
3374
  }
3327
3375
 
3328
3376
  // src/core/project-init.ts
3329
3377
  import * as fsNode from "fs/promises";
3330
- import * as path20 from "path";
3378
+ import * as path21 from "path";
3331
3379
  var CRITICAL_STEPS = /* @__PURE__ */ new Set([
3332
3380
  "tokens",
3333
3381
  "skills",
@@ -3395,6 +3443,38 @@ async function runProjectInit(options) {
3395
3443
  recordFailure("tokens", err);
3396
3444
  }
3397
3445
  }
3446
+ if (dryRun) {
3447
+ record({
3448
+ name: "html-theme",
3449
+ status: "planned",
3450
+ detail: `patchHtmlDataTheme(variant=${variant})`
3451
+ });
3452
+ } else if (aborted) {
3453
+ record({
3454
+ name: "html-theme",
3455
+ status: "skip",
3456
+ detail: "aborted: earlier critical step failed"
3457
+ });
3458
+ } else {
3459
+ try {
3460
+ const htmlResult = await patchHtmlDataTheme(projectRoot, variant);
3461
+ record({
3462
+ name: "html-theme",
3463
+ status: "ok",
3464
+ detail: htmlResult.status,
3465
+ changes: htmlResult.status === "patched" ? [
3466
+ {
3467
+ kind: "modified",
3468
+ path: "index.html",
3469
+ step: "html-theme",
3470
+ detail: `data-theme="${variant}"`
3471
+ }
3472
+ ] : []
3473
+ });
3474
+ } catch (err) {
3475
+ recordFailure("html-theme", err);
3476
+ }
3477
+ }
3398
3478
  if (dryRun) {
3399
3479
  record({
3400
3480
  name: "skills",
@@ -3669,7 +3749,7 @@ async function runProjectInit(options) {
3669
3749
  detail: "projectRoot does not exist"
3670
3750
  });
3671
3751
  } else {
3672
- const giPath = path20.join(projectRoot, ".gitignore");
3752
+ const giPath = path21.join(projectRoot, ".gitignore");
3673
3753
  let giContent = "";
3674
3754
  try {
3675
3755
  giContent = await fsNode.readFile(giPath, "utf-8");
@@ -3729,12 +3809,12 @@ async function runProjectInit(options) {
3729
3809
  if (!dryRun && hasFailures) {
3730
3810
  try {
3731
3811
  const checklistContent = renderInitChecklist({ variant, status, steps });
3732
- const checklistPath = path20.join(
3812
+ const checklistPath = path21.join(
3733
3813
  projectRoot,
3734
3814
  ".teamix-evo",
3735
3815
  "init-checklist.md"
3736
3816
  );
3737
- await fsNode.mkdir(path20.dirname(checklistPath), { recursive: true });
3817
+ await fsNode.mkdir(path21.dirname(checklistPath), { recursive: true });
3738
3818
  await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
3739
3819
  logger.info(" wrote .teamix-evo/init-checklist.md");
3740
3820
  } catch {
@@ -3773,9 +3853,438 @@ function deriveLintChanges(result) {
3773
3853
  return out;
3774
3854
  }
3775
3855
 
3776
- // src/core/installer.ts
3777
- import * as path21 from "path";
3856
+ // src/core/graft-init.ts
3857
+ import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
3858
+
3859
+ // src/core/snapshot.ts
3778
3860
  import * as fs19 from "fs/promises";
3861
+ import * as path22 from "path";
3862
+ var TEAMIX_DIR2 = ".teamix-evo";
3863
+ var SNAPSHOTS_DIR = ".snapshots";
3864
+ var META_FILE = "_meta.json";
3865
+ var DEFAULT_KEEP = 5;
3866
+ var SNAPSHOT_REASONS = ["init", "update", "switch", "restore", "graft", "manual"];
3867
+ function isoToFsSafe(iso) {
3868
+ return iso.replace(/[:.]/g, "-");
3869
+ }
3870
+ function fsSafeToIso(safe) {
3871
+ return safe.replace(
3872
+ /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})(Z)$/,
3873
+ "$1T$2:$3:$4.$5$6"
3874
+ );
3875
+ }
3876
+ async function createSnapshot(projectRoot, opts = {}) {
3877
+ const teamixDir = path22.join(projectRoot, TEAMIX_DIR2);
3878
+ try {
3879
+ const stat4 = await fs19.stat(teamixDir);
3880
+ if (!stat4.isDirectory()) return null;
3881
+ } catch (err) {
3882
+ if (err.code === "ENOENT") return null;
3883
+ throw err;
3884
+ }
3885
+ const isoTs = (/* @__PURE__ */ new Date()).toISOString();
3886
+ const ts = isoToFsSafe(isoTs);
3887
+ const snapshotRoot = path22.join(teamixDir, SNAPSHOTS_DIR);
3888
+ const target = path22.join(snapshotRoot, ts);
3889
+ await fs19.mkdir(target, { recursive: true });
3890
+ const entries = await fs19.readdir(teamixDir, { withFileTypes: true });
3891
+ for (const entry of entries) {
3892
+ if (entry.name === SNAPSHOTS_DIR) continue;
3893
+ const src = path22.join(teamixDir, entry.name);
3894
+ const dst = path22.join(target, entry.name);
3895
+ await fs19.cp(src, dst, { recursive: true });
3896
+ }
3897
+ const meta = {
3898
+ ts: isoTs,
3899
+ reason: opts.reason ?? "manual"
3900
+ };
3901
+ await fs19.writeFile(
3902
+ path22.join(target, META_FILE),
3903
+ JSON.stringify(meta, null, 2) + "\n",
3904
+ "utf-8"
3905
+ );
3906
+ logger.debug(
3907
+ `Snapshot created \u2192 ${path22.relative(projectRoot, target)} (${meta.reason})`
3908
+ );
3909
+ const keep = opts.keep ?? DEFAULT_KEEP;
3910
+ await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
3911
+ return { ts, path: target };
3912
+ }
3913
+ async function listSnapshots(projectRoot) {
3914
+ const snapshotRoot = path22.join(projectRoot, TEAMIX_DIR2, SNAPSHOTS_DIR);
3915
+ let entries;
3916
+ try {
3917
+ entries = await fs19.readdir(snapshotRoot, { withFileTypes: true });
3918
+ } catch (err) {
3919
+ if (err.code === "ENOENT") return [];
3920
+ throw err;
3921
+ }
3922
+ const result = [];
3923
+ for (const entry of entries) {
3924
+ if (!entry.isDirectory()) continue;
3925
+ const dir = path22.join(snapshotRoot, entry.name);
3926
+ let isoTs = null;
3927
+ let reason = null;
3928
+ try {
3929
+ const raw = await fs19.readFile(path22.join(dir, META_FILE), "utf-8");
3930
+ const parsed = JSON.parse(raw);
3931
+ if (typeof parsed.ts === "string") isoTs = parsed.ts;
3932
+ if (typeof parsed.reason === "string" && SNAPSHOT_REASONS.includes(
3933
+ parsed.reason
3934
+ )) {
3935
+ reason = parsed.reason;
3936
+ }
3937
+ } catch {
3938
+ isoTs = fsSafeToIso(entry.name);
3939
+ }
3940
+ result.push({ ts: entry.name, isoTs, reason, path: dir });
3941
+ }
3942
+ result.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0);
3943
+ return result;
3944
+ }
3945
+ async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
3946
+ if (keep < 0)
3947
+ throw new Error(`pruneSnapshots: keep must be >= 0, got ${keep}`);
3948
+ const snapshots = await listSnapshots(projectRoot);
3949
+ if (snapshots.length <= keep) return [];
3950
+ const tail = snapshots.slice(keep);
3951
+ const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
3952
+ const removed = [];
3953
+ for (const snap of toRemove) {
3954
+ await fs19.rm(snap.path, { recursive: true, force: true });
3955
+ removed.push(snap.ts);
3956
+ logger.debug(`Pruned snapshot ${snap.ts}`);
3957
+ }
3958
+ return removed.reverse();
3959
+ }
3960
+
3961
+ // src/core/graft-init.ts
3962
+ import * as fsNode2 from "fs/promises";
3963
+ import * as path23 from "path";
3964
+ var CRITICAL_STEPS2 = /* @__PURE__ */ new Set([
3965
+ "ensure-teamix-dir",
3966
+ "tokens",
3967
+ "ui-init",
3968
+ "skills"
3969
+ ]);
3970
+ var AGENTS_MD_GRAFT_MANAGED_ID = "teamix-evo-graft";
3971
+ var GITIGNORE_MARKER_START2 = "# >>> teamix-evo:managed >>>";
3972
+ var GITIGNORE_MARKER_END2 = "# <<< teamix-evo:managed <<<";
3973
+ var GITIGNORE_RULES2 = [
3974
+ "# Runtime artifacts (regenerable / archives)",
3975
+ ".teamix-evo/.snapshots/",
3976
+ ".teamix-evo/.backups/",
3977
+ ".teamix-evo/.upgrade-staging/",
3978
+ ".teamix-evo/.upgrade-hints/"
3979
+ ];
3980
+ var LEGACY_LIB_META = {
3981
+ fusion: {
3982
+ displayName: "Fusion Design",
3983
+ imports: ["teamix/ui", "@alifd/next", "@ali/teamix-material"]
3984
+ },
3985
+ antd: {
3986
+ displayName: "Ant Design",
3987
+ imports: ["antd", "@ant-design/*"]
3988
+ },
3989
+ element: {
3990
+ displayName: "Element UI",
3991
+ imports: ["element-ui", "element-plus"]
3992
+ }
3993
+ };
3994
+ async function runGraftInit(options) {
3995
+ const {
3996
+ projectRoot,
3997
+ variant,
3998
+ legacyLib,
3999
+ onStep
4000
+ } = options;
4001
+ const ide = options.ide ?? "qoder";
4002
+ const ides = options.ides ?? ["qoder", "claude"];
4003
+ const scope = options.scope ?? "project";
4004
+ const steps = [];
4005
+ let aborted = false;
4006
+ function record(step) {
4007
+ steps.push(step);
4008
+ onStep?.(step);
4009
+ }
4010
+ function recordFailure(name, err) {
4011
+ const message = getErrorMessage(err);
4012
+ record({ name, status: "fail", detail: message });
4013
+ if (CRITICAL_STEPS2.has(name)) aborted = true;
4014
+ }
4015
+ try {
4016
+ await ensureTeamixDir(projectRoot);
4017
+ record({ name: "ensure-teamix-dir", status: "ok" });
4018
+ } catch (err) {
4019
+ recordFailure("ensure-teamix-dir", err);
4020
+ }
4021
+ if (!aborted) {
4022
+ try {
4023
+ const snap = await createSnapshot(projectRoot, { reason: "graft" });
4024
+ record({
4025
+ name: "snapshot",
4026
+ status: "ok",
4027
+ detail: snap ? `snapshot ${snap.ts}` : "empty .teamix-evo/ \u2014 nothing to snapshot"
4028
+ });
4029
+ } catch (err) {
4030
+ recordFailure("snapshot", err);
4031
+ }
4032
+ } else {
4033
+ record({ name: "snapshot", status: "skip", detail: "aborted: earlier critical step failed" });
4034
+ }
4035
+ if (!aborted) {
4036
+ try {
4037
+ const result = await runTokensInit({ projectRoot, variant, ide });
4038
+ const detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
4039
+ record({ name: "tokens", status: "ok", detail });
4040
+ } catch (err) {
4041
+ recordFailure("tokens", err);
4042
+ }
4043
+ } else {
4044
+ record({ name: "tokens", status: "skip", detail: "aborted: earlier critical step failed" });
4045
+ }
4046
+ if (!aborted) {
4047
+ try {
4048
+ const initResult = await runUiInit({ projectRoot, ide });
4049
+ record({
4050
+ name: "ui-init",
4051
+ status: "ok",
4052
+ detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
4053
+ });
4054
+ } catch (err) {
4055
+ recordFailure("ui-init", err);
4056
+ }
4057
+ } else {
4058
+ record({ name: "ui-init", status: "skip", detail: "aborted: earlier critical step failed" });
4059
+ }
4060
+ const collectedNpmDeps = {};
4061
+ if (!aborted) {
4062
+ try {
4063
+ const { manifest } = await loadUiData("@teamix-evo/ui");
4064
+ const allIds = manifest.entries.map((e) => e.id);
4065
+ const addResult = await runUiAdd({
4066
+ projectRoot,
4067
+ ids: allIds,
4068
+ overwrite: true
4069
+ });
4070
+ record({
4071
+ name: "ui-add",
4072
+ status: "ok",
4073
+ detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`
4074
+ });
4075
+ Object.assign(collectedNpmDeps, addResult.npmDependencies);
4076
+ } catch (err) {
4077
+ recordFailure("ui-add", err);
4078
+ }
4079
+ } else {
4080
+ record({ name: "ui-add", status: "skip", detail: "aborted: earlier critical step failed" });
4081
+ }
4082
+ if (!aborted) {
4083
+ try {
4084
+ const listing = await listBizUiEntries(variant);
4085
+ if (listing.entries.length === 0) {
4086
+ record({
4087
+ name: "biz-ui-add",
4088
+ status: "skip",
4089
+ detail: `no biz-ui entries for variant "${variant}"`
4090
+ });
4091
+ } else {
4092
+ const allIds = listing.entries.map((e) => e.id);
4093
+ const result = await runBizUiAdd({
4094
+ projectRoot,
4095
+ variant,
4096
+ ids: allIds,
4097
+ overwrite: true
4098
+ });
4099
+ record({
4100
+ name: "biz-ui-add",
4101
+ status: "ok",
4102
+ detail: `${result.orderedIds.length} entries (${result.written} written, ${result.skipped} skipped)`
4103
+ });
4104
+ if (result.npmDependencies) {
4105
+ Object.assign(collectedNpmDeps, result.npmDependencies);
4106
+ }
4107
+ }
4108
+ } catch (err) {
4109
+ recordFailure("biz-ui-add", err);
4110
+ }
4111
+ } else {
4112
+ record({ name: "biz-ui-add", status: "skip", detail: "aborted: earlier critical step failed" });
4113
+ }
4114
+ if (!aborted && Object.keys(collectedNpmDeps).length > 0) {
4115
+ try {
4116
+ await installProjectDeps({
4117
+ projectRoot,
4118
+ npmDependencies: collectedNpmDeps,
4119
+ skipInstall: options.skipInstall ?? false
4120
+ });
4121
+ } catch (err) {
4122
+ logger.warn(
4123
+ `\u5B89\u88C5 ui/biz-ui \u4F20\u9012\u4F9D\u8D56\u5931\u8D25\uFF1A${getErrorMessage(err)}\uFF08\u53EF\u624B\u52A8\u8FD0\u884C \`npm install\` \u91CD\u8BD5\uFF09`
4124
+ );
4125
+ }
4126
+ }
4127
+ if (!aborted) {
4128
+ try {
4129
+ const result = await runSkillsInit({ projectRoot, ides, scope, ide });
4130
+ if (result.status === "already-initialized") {
4131
+ record({ name: "skills", status: "ok", detail: "already-initialized" });
4132
+ } else {
4133
+ record({
4134
+ name: "skills",
4135
+ status: "ok",
4136
+ detail: `${result.skillCount} skills, ${result.fileCount} files`
4137
+ });
4138
+ }
4139
+ } catch (err) {
4140
+ recordFailure("skills", err);
4141
+ }
4142
+ } else {
4143
+ record({ name: "skills", status: "skip", detail: "aborted: earlier critical step failed" });
4144
+ }
4145
+ if (!aborted) {
4146
+ try {
4147
+ const result = await runGenerateAgentsMd({
4148
+ projectRoot,
4149
+ variant,
4150
+ skillIds: [
4151
+ `teamix-evo-design-${variant}`,
4152
+ `teamix-evo-code-${variant}`
4153
+ ],
4154
+ globalSkillIds: ["teamix-evo-manage"],
4155
+ mode: "merge-managed"
4156
+ });
4157
+ record({
4158
+ name: "agents-md",
4159
+ status: "ok",
4160
+ detail: `${result.skillCount} skill index (${result.merge})`
4161
+ });
4162
+ } catch (err) {
4163
+ recordFailure("agents-md", err);
4164
+ }
4165
+ } else {
4166
+ record({ name: "agents-md", status: "skip", detail: "aborted" });
4167
+ }
4168
+ if (!aborted) {
4169
+ try {
4170
+ const agentsMdPath = path23.join(projectRoot, "AGENTS.md");
4171
+ const existing = await readFileOrNull(agentsMdPath) ?? "";
4172
+ const meta = LEGACY_LIB_META[legacyLib] ?? {
4173
+ displayName: legacyLib,
4174
+ imports: [legacyLib]
4175
+ };
4176
+ const importsStr = meta.imports.map((i) => `\`${i}\``).join("\u3001");
4177
+ const graftBody = renderGraftManagedBody(meta.displayName, importsStr);
4178
+ let output;
4179
+ if (hasManagedRegion3(existing, AGENTS_MD_GRAFT_MANAGED_ID)) {
4180
+ output = replaceManagedRegion3(
4181
+ existing,
4182
+ AGENTS_MD_GRAFT_MANAGED_ID,
4183
+ graftBody
4184
+ );
4185
+ } else {
4186
+ const wrapped = wrapGraftManagedBlock(graftBody);
4187
+ output = existing.trimEnd() + "\n\n" + wrapped + "\n";
4188
+ }
4189
+ await fsNode2.writeFile(agentsMdPath, output, "utf-8");
4190
+ record({ name: "agents-md-graft", status: "ok", detail: `dual-stack rules for ${legacyLib}` });
4191
+ } catch (err) {
4192
+ recordFailure("agents-md-graft", err);
4193
+ }
4194
+ } else {
4195
+ record({ name: "agents-md-graft", status: "skip", detail: "aborted" });
4196
+ }
4197
+ if (!aborted) {
4198
+ try {
4199
+ const uiResult = await landUiMeta(projectRoot);
4200
+ const bizResult = await landBizUiMeta(projectRoot, variant);
4201
+ record({
4202
+ name: "meta-landing",
4203
+ status: "ok",
4204
+ detail: `ui: ${uiResult.metaFilesWritten}/${uiResult.metaFilesTotal}, biz-ui: ${bizResult.metaFilesWritten}/${bizResult.metaFilesTotal}`
4205
+ });
4206
+ } catch (err) {
4207
+ recordFailure("meta-landing", err);
4208
+ }
4209
+ } else {
4210
+ record({ name: "meta-landing", status: "skip", detail: "aborted" });
4211
+ }
4212
+ if (!aborted) {
4213
+ try {
4214
+ const config = await readProjectConfig(projectRoot);
4215
+ if (config) {
4216
+ config.mode = "graft";
4217
+ config.legacyLib = legacyLib;
4218
+ await writeProjectConfig(projectRoot, config);
4219
+ record({ name: "config-mode", status: "ok", detail: `mode=graft, legacyLib=${legacyLib}` });
4220
+ } else {
4221
+ record({ name: "config-mode", status: "skip", detail: "no config.json found to patch" });
4222
+ }
4223
+ } catch (err) {
4224
+ recordFailure("config-mode", err);
4225
+ }
4226
+ } else {
4227
+ record({ name: "config-mode", status: "skip", detail: "aborted" });
4228
+ }
4229
+ if (!aborted) {
4230
+ try {
4231
+ const giPath = path23.join(projectRoot, ".gitignore");
4232
+ let giContent = "";
4233
+ try {
4234
+ giContent = await fsNode2.readFile(giPath, "utf-8");
4235
+ } catch {
4236
+ }
4237
+ if (giContent.includes(GITIGNORE_MARKER_START2)) {
4238
+ record({ name: "gitignore", status: "skip", detail: "teamix-evo markers already present" });
4239
+ } else {
4240
+ const block = [
4241
+ "",
4242
+ GITIGNORE_MARKER_START2,
4243
+ ...GITIGNORE_RULES2,
4244
+ GITIGNORE_MARKER_END2,
4245
+ ""
4246
+ ].join("\n");
4247
+ const separator = giContent.length > 0 && !giContent.endsWith("\n") ? "\n" : "";
4248
+ await fsNode2.writeFile(giPath, giContent + separator + block, "utf-8");
4249
+ record({
4250
+ name: "gitignore",
4251
+ status: "ok",
4252
+ detail: `${GITIGNORE_RULES2.filter((r) => r && !r.startsWith("#")).length} rules appended`
4253
+ });
4254
+ }
4255
+ } catch (err) {
4256
+ recordFailure("gitignore", err);
4257
+ }
4258
+ } else {
4259
+ record({ name: "gitignore", status: "skip", detail: "aborted" });
4260
+ }
4261
+ const status = steps.some((s) => s.status === "fail") ? "partial" : "success";
4262
+ return { status, steps };
4263
+ }
4264
+ function renderGraftManagedBody(legacyLibName, legacyImports) {
4265
+ return `## \u53CC\u6808\u7814\u53D1\u89C4\u5219
4266
+
4267
+ \u672C\u9879\u76EE\u540C\u65F6\u5B58\u5728 ${legacyLibName}\uFF08\u65E7\u6808\uFF09\u548C teamix-evo\uFF08\u65B0\u6808\uFF09\u4E24\u5957\u6280\u672F\u6808\u3002
4268
+
4269
+ ### \u5224\u65AD\u89C4\u5219\u2014\u2014\u901A\u8FC7 import \u8BED\u53E5\u8BC6\u522B
4270
+ - \u6587\u4EF6\u4E2D import \u4E86 ${legacyImports} \u2192 **\u65E7\u6808**\uFF0C\u6309 ${legacyLibName} \u89C4\u8303\u7814\u53D1\uFF0C**\u8DF3\u8FC7** teamix-evo \u7684 design / code skill
4271
+ - \u6587\u4EF6\u4E2D import \u4E86 \`@/components/ui\`\u3001\u4F7F\u7528 Tailwind class \u2192 **\u65B0\u6808**\uFF0C\u6B63\u5E38\u89E6\u53D1 teamix-evo design / code skill
4272
+ - \u65B0\u5EFA\u9875\u9762 \u2192 \u9ED8\u8BA4\u4F7F\u7528\u65B0\u6808\uFF08teamix-evo\uFF09
4273
+
4274
+ ### \u7981\u6B62\u4E8B\u9879
4275
+ - \u7981\u6B62\u5728\u65E7\u6808\u6587\u4EF6\u4E2D\u5F15\u5165 \`@/components/ui\` \u6216 Tailwind class
4276
+ - \u7981\u6B62\u5728\u65B0\u6808\u6587\u4EF6\u4E2D\u5F15\u5165 ${legacyImports}
4277
+ - \u7981\u6B62\u5728\u540C\u4E00\u6587\u4EF6\u4E2D\u6DF7\u7528\u4E24\u5957\u7EC4\u4EF6\u5E93`;
4278
+ }
4279
+ function wrapGraftManagedBlock(body) {
4280
+ return `<!-- teamix-evo:managed:start id="${AGENTS_MD_GRAFT_MANAGED_ID}" -->
4281
+ ${body}
4282
+ <!-- teamix-evo:managed:end id="${AGENTS_MD_GRAFT_MANAGED_ID}" -->`;
4283
+ }
4284
+
4285
+ // src/core/installer.ts
4286
+ import * as path24 from "path";
4287
+ import * as fs20 from "fs/promises";
3779
4288
  async function installResources(options) {
3780
4289
  const { projectRoot, manifest, data, variantDir, packageRoot } = options;
3781
4290
  const installedResources = [];
@@ -3812,13 +4321,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
3812
4321
  variantDir,
3813
4322
  packageRoot
3814
4323
  );
3815
- const targetPath = path21.join(projectRoot, resource.target);
4324
+ const targetPath = path24.join(projectRoot, resource.target);
3816
4325
  let content;
3817
4326
  if (resource.template) {
3818
4327
  const templateContent = await loadTemplateFile(sourcePath);
3819
4328
  content = renderTemplate(templateContent, data);
3820
4329
  } else {
3821
- content = await fs19.readFile(sourcePath, "utf-8");
4330
+ content = await fs20.readFile(sourcePath, "utf-8");
3822
4331
  }
3823
4332
  await writeFileSafe(targetPath, content);
3824
4333
  const hash = computeHash(content);
@@ -3836,13 +4345,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3836
4345
  variantDir,
3837
4346
  packageRoot
3838
4347
  );
3839
- const targetDir = path21.join(projectRoot, resource.target);
4348
+ const targetDir = path24.join(projectRoot, resource.target);
3840
4349
  const results = [];
3841
4350
  await ensureDir(targetDir);
3842
4351
  const entries = await walkDir(sourcePath);
3843
4352
  for (const entry of entries) {
3844
- const relPath = path21.relative(sourcePath, entry);
3845
- let targetFile = path21.join(targetDir, relPath);
4353
+ const relPath = path24.relative(sourcePath, entry);
4354
+ let targetFile = path24.join(targetDir, relPath);
3846
4355
  if (resource.template && targetFile.endsWith(".hbs")) {
3847
4356
  targetFile = targetFile.slice(0, -4);
3848
4357
  }
@@ -3851,11 +4360,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3851
4360
  const templateContent = await loadTemplateFile(entry);
3852
4361
  content = renderTemplate(templateContent, data);
3853
4362
  } else {
3854
- content = await fs19.readFile(entry, "utf-8");
4363
+ content = await fs20.readFile(entry, "utf-8");
3855
4364
  }
3856
4365
  await writeFileSafe(targetFile, content);
3857
4366
  const hash = computeHash(content);
3858
- const targetRel = path21.relative(projectRoot, targetFile);
4367
+ const targetRel = path24.relative(projectRoot, targetFile);
3859
4368
  results.push({
3860
4369
  id: `${resource.id}:${relPath}`,
3861
4370
  target: targetRel,
@@ -3868,25 +4377,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3868
4377
  }
3869
4378
 
3870
4379
  // src/core/registry-client.ts
3871
- import * as path22 from "path";
3872
- import * as fs20 from "fs/promises";
4380
+ import * as path25 from "path";
4381
+ import * as fs21 from "fs/promises";
3873
4382
  import { createRequire as createRequire6 } from "module";
3874
4383
  import { loadVariantManifest } from "@teamix-evo/registry";
3875
4384
  var require7 = createRequire6(import.meta.url);
3876
4385
  function resolvePackageRoot5(packageName) {
3877
4386
  const pkgJsonPath = require7.resolve(`${packageName}/package.json`);
3878
- return path22.dirname(pkgJsonPath);
4387
+ return path25.dirname(pkgJsonPath);
3879
4388
  }
3880
4389
  async function loadVariantData(packageName, variant) {
3881
4390
  const packageRoot = resolvePackageRoot5(packageName);
3882
- const variantDir = path22.join(packageRoot, "library", variant);
4391
+ const variantDir = path25.join(packageRoot, "library", variant);
3883
4392
  logger.debug(`Resolved variant dir: ${variantDir}`);
3884
4393
  logger.debug(`Package root: ${packageRoot}`);
3885
4394
  const manifest = await loadVariantManifest(variantDir);
3886
4395
  let data = {};
3887
- const dataPath = path22.join(variantDir, "_data.json");
4396
+ const dataPath = path25.join(variantDir, "_data.json");
3888
4397
  try {
3889
- const raw = await fs20.readFile(dataPath, "utf-8");
4398
+ const raw = await fs21.readFile(dataPath, "utf-8");
3890
4399
  data = JSON.parse(raw);
3891
4400
  } catch (err) {
3892
4401
  if (err.code !== "ENOENT") {
@@ -3922,6 +4431,7 @@ export {
3922
4431
  removeUiFiles,
3923
4432
  runBizUiAdd,
3924
4433
  runGenerateAgentsMd,
4434
+ runGraftInit,
3925
4435
  runLintInit,
3926
4436
  runProjectInit,
3927
4437
  runSkillsAdd,