teamix-evo 0.16.0 → 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
  }
@@ -3827,9 +3853,438 @@ function deriveLintChanges(result) {
3827
3853
  return out;
3828
3854
  }
3829
3855
 
3830
- // src/core/installer.ts
3831
- import * as path22 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
3832
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";
3833
4288
  async function installResources(options) {
3834
4289
  const { projectRoot, manifest, data, variantDir, packageRoot } = options;
3835
4290
  const installedResources = [];
@@ -3866,13 +4321,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
3866
4321
  variantDir,
3867
4322
  packageRoot
3868
4323
  );
3869
- const targetPath = path22.join(projectRoot, resource.target);
4324
+ const targetPath = path24.join(projectRoot, resource.target);
3870
4325
  let content;
3871
4326
  if (resource.template) {
3872
4327
  const templateContent = await loadTemplateFile(sourcePath);
3873
4328
  content = renderTemplate(templateContent, data);
3874
4329
  } else {
3875
- content = await fs19.readFile(sourcePath, "utf-8");
4330
+ content = await fs20.readFile(sourcePath, "utf-8");
3876
4331
  }
3877
4332
  await writeFileSafe(targetPath, content);
3878
4333
  const hash = computeHash(content);
@@ -3890,13 +4345,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3890
4345
  variantDir,
3891
4346
  packageRoot
3892
4347
  );
3893
- const targetDir = path22.join(projectRoot, resource.target);
4348
+ const targetDir = path24.join(projectRoot, resource.target);
3894
4349
  const results = [];
3895
4350
  await ensureDir(targetDir);
3896
4351
  const entries = await walkDir(sourcePath);
3897
4352
  for (const entry of entries) {
3898
- const relPath = path22.relative(sourcePath, entry);
3899
- let targetFile = path22.join(targetDir, relPath);
4353
+ const relPath = path24.relative(sourcePath, entry);
4354
+ let targetFile = path24.join(targetDir, relPath);
3900
4355
  if (resource.template && targetFile.endsWith(".hbs")) {
3901
4356
  targetFile = targetFile.slice(0, -4);
3902
4357
  }
@@ -3905,11 +4360,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3905
4360
  const templateContent = await loadTemplateFile(entry);
3906
4361
  content = renderTemplate(templateContent, data);
3907
4362
  } else {
3908
- content = await fs19.readFile(entry, "utf-8");
4363
+ content = await fs20.readFile(entry, "utf-8");
3909
4364
  }
3910
4365
  await writeFileSafe(targetFile, content);
3911
4366
  const hash = computeHash(content);
3912
- const targetRel = path22.relative(projectRoot, targetFile);
4367
+ const targetRel = path24.relative(projectRoot, targetFile);
3913
4368
  results.push({
3914
4369
  id: `${resource.id}:${relPath}`,
3915
4370
  target: targetRel,
@@ -3922,25 +4377,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
3922
4377
  }
3923
4378
 
3924
4379
  // src/core/registry-client.ts
3925
- import * as path23 from "path";
3926
- import * as fs20 from "fs/promises";
4380
+ import * as path25 from "path";
4381
+ import * as fs21 from "fs/promises";
3927
4382
  import { createRequire as createRequire6 } from "module";
3928
4383
  import { loadVariantManifest } from "@teamix-evo/registry";
3929
4384
  var require7 = createRequire6(import.meta.url);
3930
4385
  function resolvePackageRoot5(packageName) {
3931
4386
  const pkgJsonPath = require7.resolve(`${packageName}/package.json`);
3932
- return path23.dirname(pkgJsonPath);
4387
+ return path25.dirname(pkgJsonPath);
3933
4388
  }
3934
4389
  async function loadVariantData(packageName, variant) {
3935
4390
  const packageRoot = resolvePackageRoot5(packageName);
3936
- const variantDir = path23.join(packageRoot, "library", variant);
4391
+ const variantDir = path25.join(packageRoot, "library", variant);
3937
4392
  logger.debug(`Resolved variant dir: ${variantDir}`);
3938
4393
  logger.debug(`Package root: ${packageRoot}`);
3939
4394
  const manifest = await loadVariantManifest(variantDir);
3940
4395
  let data = {};
3941
- const dataPath = path23.join(variantDir, "_data.json");
4396
+ const dataPath = path25.join(variantDir, "_data.json");
3942
4397
  try {
3943
- const raw = await fs20.readFile(dataPath, "utf-8");
4398
+ const raw = await fs21.readFile(dataPath, "utf-8");
3944
4399
  data = JSON.parse(raw);
3945
4400
  } catch (err) {
3946
4401
  if (err.code !== "ENOENT") {
@@ -3976,6 +4431,7 @@ export {
3976
4431
  removeUiFiles,
3977
4432
  runBizUiAdd,
3978
4433
  runGenerateAgentsMd,
4434
+ runGraftInit,
3979
4435
  runLintInit,
3980
4436
  runProjectInit,
3981
4437
  runSkillsAdd,