teamix-evo 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -180,12 +180,40 @@ async function readInstalledManifest(projectRoot) {
180
180
  );
181
181
  }
182
182
  const result = validateInstalled(data);
183
- if (!result.success) {
184
- throw new Error(
185
- `Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
183
+ if (result.success) {
184
+ return result.data;
185
+ }
186
+ const migrated = migrateManifest(data);
187
+ const retryResult = validateInstalled(migrated);
188
+ if (retryResult.success) {
189
+ logger.warn(
190
+ "Migrated legacy manifest.json \u2014 will be overwritten on next install."
186
191
  );
192
+ return retryResult.data;
187
193
  }
188
- return result.data;
194
+ throw new Error(
195
+ `Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
196
+ );
197
+ }
198
+ function migrateManifest(data) {
199
+ if (typeof data !== "object" || data === null) return data;
200
+ const obj = data;
201
+ if (!("schemaVersion" in obj)) obj.schemaVersion = 1;
202
+ if (!Array.isArray(obj.installed)) obj.installed = [];
203
+ for (const pkg of obj.installed) {
204
+ if (typeof pkg !== "object" || pkg === null) continue;
205
+ if (!pkg.variant) pkg.variant = "_flat";
206
+ if (!pkg.installedAt) pkg.installedAt = "";
207
+ if (!pkg.version) pkg.version = "0.0.0";
208
+ if (Array.isArray(pkg.resources)) {
209
+ for (const res of pkg.resources) {
210
+ if (typeof res !== "object" || res === null) continue;
211
+ if (!res.hash) res.hash = "sha256:legacy-unknown";
212
+ if (!res.strategy) res.strategy = "frozen";
213
+ }
214
+ }
215
+ }
216
+ return obj;
189
217
  }
190
218
  async function writeInstalledManifest(projectRoot, manifest) {
191
219
  const manifestPath = path4.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
@@ -1296,9 +1324,15 @@ function partitionByVersion(ids, manifest, existing) {
1296
1324
  onlyIds.push(name);
1297
1325
  continue;
1298
1326
  }
1299
- const installedVer = existing.lock?.skills?.[name]?.version;
1327
+ const installedVer = existing.lock?.skills?.[name]?.version ?? existing.pkg?.version;
1300
1328
  const latestVer = manifestById.get(name)?.version;
1301
- if (installedVer && latestVer && compareSemver(installedVer, latestVer) < 0) {
1329
+ if (!installedVer || !latestVer) {
1330
+ if (latestVer) {
1331
+ outdatedSkills.push({ id: name, installed: "0.0.0", latest: latestVer });
1332
+ } else {
1333
+ skippedSkillIds.push(name);
1334
+ }
1335
+ } else if (compareSemver(installedVer, latestVer) < 0) {
1302
1336
  outdatedSkills.push({
1303
1337
  id: name,
1304
1338
  installed: installedVer,
@@ -3877,18 +3911,24 @@ init_state();
3877
3911
  var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
3878
3912
  async function runGenerateAgentsMd(options) {
3879
3913
  const { projectRoot, variant, skillIds } = options;
3914
+ const globalSkillIds = options.globalSkillIds ?? [];
3880
3915
  const mode = options.mode ?? "overwrite";
3881
3916
  const config = await readProjectConfig(projectRoot);
3882
3917
  const lock = await readSkillsLock(projectRoot);
3883
3918
  const ides = config?.packages?.skills?.ides ?? ["qoder", "claude"];
3884
3919
  const scope = config?.packages?.skills?.scope ?? lock?.skills[skillIds[0] ?? ""]?.scope ?? "project";
3885
- const ordered = [...skillIds].sort(
3920
+ const skillScopeMap = /* @__PURE__ */ new Map();
3921
+ for (const id of skillIds) skillScopeMap.set(id, scope);
3922
+ for (const id of globalSkillIds) skillScopeMap.set(id, "global");
3923
+ const allSkillIds = [...skillIds, ...globalSkillIds];
3924
+ const ordered = allSkillIds.sort(
3886
3925
  (a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
3887
3926
  );
3888
3927
  const sections = [];
3889
3928
  const missingSkillIds = [];
3890
3929
  for (const id of ordered) {
3891
- const { section, missing } = await renderSkillSection(projectRoot, id, ides, scope);
3930
+ const skillScope = skillScopeMap.get(id) ?? scope;
3931
+ const { section, missing } = await renderSkillSection(projectRoot, id, ides, skillScope);
3892
3932
  sections.push(section);
3893
3933
  if (missing) missingSkillIds.push(id);
3894
3934
  }
@@ -3972,6 +4012,12 @@ async function renderSkillSection(projectRoot, skillId, ides, scope) {
3972
4012
  if (parts?.coordinates) {
3973
4013
  lines.push(`- **Coordinates with**: ${parts.coordinates}`);
3974
4014
  }
4015
+ if (scope === "global") {
4016
+ lines.push("");
4017
+ lines.push(
4018
+ "> **\u5168\u5C40\u6280\u80FD** \u2014 \u5B89\u88C5\u5728\u5168\u5C40\u800C\u975E\u9879\u76EE\u5185\uFF0C\u4E0D\u968F\u4EE3\u7801\u4ED3\u5E93\u5206\u53D1\u3002AI \u89E6\u53D1\u65F6\u5E94\u5148\u786E\u8BA4\u672C\u6280\u80FD\u53EF\u7528\uFF1B\u82E5\u4E0D\u53EF\u7528\uFF0C\u5F15\u5BFC\u7528\u6237\u6267\u884C\u5168\u5C40\u5B89\u88C5\u3002"
4019
+ );
4020
+ }
3975
4021
  return { section: lines.join("\n"), missing };
3976
4022
  }
3977
4023
  function renderAgentsMd(args) {
@@ -4118,13 +4164,34 @@ async function runSkillsUpdate(options) {
4118
4164
  }
4119
4165
  const ides = skillsCfg.ides ?? ["qoder", "claude"];
4120
4166
  const scope = skillsCfg.scope ?? "project";
4121
- const existingLock = await readSkillsLock(projectRoot);
4167
+ let existingLock = await readSkillsLock(projectRoot);
4122
4168
  if (!existingLock || Object.keys(existingLock.skills).length === 0) {
4123
- return { status: "no-skills" };
4169
+ const installedManifest2 = await readInstalledManifest(projectRoot);
4170
+ const pkg = installedManifest2?.installed.find(
4171
+ (p2) => p2.package === packageName
4172
+ );
4173
+ if (!pkg || pkg.resources.length === 0) {
4174
+ return { status: "no-skills" };
4175
+ }
4176
+ const derivedSkills = {};
4177
+ for (const r of pkg.resources) {
4178
+ const skillId = r.id.split(":")[0] ?? r.id;
4179
+ if (!derivedSkills[skillId]) {
4180
+ derivedSkills[skillId] = {
4181
+ version: pkg.version,
4182
+ from: packageName,
4183
+ installedAt: "",
4184
+ scope,
4185
+ mirroredTo: []
4186
+ };
4187
+ }
4188
+ }
4189
+ existingLock = { schemaVersion: 1, skills: derivedSkills };
4124
4190
  }
4191
+ const activeLock = existingLock;
4125
4192
  const { manifest, data, packageRoot } = await loadSkillsData(packageName);
4126
4193
  const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
4127
- const lockIds = Object.keys(existingLock.skills);
4194
+ const lockIds = Object.keys(activeLock.skills);
4128
4195
  const requestedSet = requestedNames ? new Set(requestedNames) : null;
4129
4196
  if (requestedSet) {
4130
4197
  const unknown = requestedNames.filter(
@@ -4161,7 +4228,7 @@ async function runSkillsUpdate(options) {
4161
4228
  targetIds.push(id);
4162
4229
  }
4163
4230
  const allSame = targetIds.every((id) => {
4164
- const lockVer = existingLock.skills[id].version;
4231
+ const lockVer = activeLock.skills[id].version;
4165
4232
  const manVer = manifestById.get(id).version;
4166
4233
  return lockVer === manVer;
4167
4234
  });
@@ -4175,7 +4242,7 @@ async function runSkillsUpdate(options) {
4175
4242
  }
4176
4243
  if (dryRun) {
4177
4244
  const plan = targetIds.map((id) => {
4178
- const lockVer = existingLock.skills[id].version;
4245
+ const lockVer = activeLock.skills[id].version;
4179
4246
  const entry2 = manifestById.get(id);
4180
4247
  const sameVersion = lockVer === entry2.version;
4181
4248
  return {
@@ -4247,7 +4314,7 @@ async function runSkillsUpdate(options) {
4247
4314
  await writeInstalledManifest(projectRoot, installedManifest);
4248
4315
  const lock = {
4249
4316
  schemaVersion: 1,
4250
- skills: { ...existingLock.skills }
4317
+ skills: { ...activeLock.skills }
4251
4318
  };
4252
4319
  for (const id of targetIds) {
4253
4320
  const skillDef = manifestById.get(id);
@@ -4267,10 +4334,12 @@ async function runSkillsUpdate(options) {
4267
4334
  const variant = await readTokensVariant(projectRoot);
4268
4335
  if (variant) {
4269
4336
  const projectSkillIds = Object.entries(lock.skills).filter(([, v]) => v.scope === "project").map(([id]) => id);
4337
+ const globalSkillIds = Object.entries(lock.skills).filter(([, v]) => v.scope === "global").map(([id]) => id);
4270
4338
  await runGenerateAgentsMd({
4271
4339
  projectRoot,
4272
4340
  variant,
4273
4341
  skillIds: projectSkillIds,
4342
+ globalSkillIds,
4274
4343
  mode: "merge-managed"
4275
4344
  });
4276
4345
  }
@@ -4431,11 +4500,14 @@ init_logger();
4431
4500
  var SKILLS_PACKAGE3 = "@teamix-evo/skills";
4432
4501
  var uninstallCommand2 = new Command17("uninstall").description(
4433
4502
  "\u5378\u8F7D\u5DF2\u5B89\u88C5\u7684 teamix-evo skills\uFF1B\u4E0D\u4F20 ids \u5219\u5378\u8F7D\u6574\u5305\uFF0C\u4F20 ids \u5219\u6309 skill \u5220\u9664"
4434
- ).argument("[ids...]", "\u53EF\u9009\uFF1A\u4EC5\u5378\u8F7D\u6307\u5B9A skill id\uFF1B\u7701\u7565\u5219\u5378\u8F7D\u6574\u4E2A skills \u5305").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").action(async (ids, opts) => {
4503
+ ).argument("[ids...]", "\u53EF\u9009\uFF1A\u4EC5\u5378\u8F7D\u6307\u5B9A skill id\uFF1B\u7701\u7565\u5219\u5378\u8F7D\u6574\u4E2A skills \u5305").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").option(
4504
+ "--scope <scope>",
4505
+ "project | global\uFF08\u9ED8\u8BA4\u6309 cwd \u63A8\u65AD\uFF1Acwd \u662F\u9879\u76EE\u2192project\uFF1B\u5426\u5219 fallback \u5230\u5168\u5C40\uFF09"
4506
+ ).action(async (ids, opts) => {
4435
4507
  try {
4436
4508
  const ide = detectIde();
4437
4509
  const cwd = ide.getProjectRoot();
4438
- const projectRoot = resolveSkillsMaintenanceRoot(cwd);
4510
+ const projectRoot = resolveUninstallRoot(cwd, opts.scope);
4439
4511
  if (projectRoot !== cwd) {
4440
4512
  logger.info(`Using global skills meta root: ${projectRoot}`);
4441
4513
  }
@@ -4637,6 +4709,22 @@ function groupBySkillId(records) {
4637
4709
  function dedupe(values) {
4638
4710
  return Array.from(new Set(values));
4639
4711
  }
4712
+ function resolveUninstallRoot(cwd, scopeFlag) {
4713
+ if (scopeFlag === void 0) {
4714
+ return resolveSkillsMaintenanceRoot(cwd);
4715
+ }
4716
+ const scope = parseScope(scopeFlag);
4717
+ if (scope === "global") {
4718
+ const globalRoot = getGlobalMetaRoot();
4719
+ if (!isTeamixEvoProject(globalRoot)) {
4720
+ throw new Error(
4721
+ `No global skills installed at ${globalRoot}. Nothing to uninstall.`
4722
+ );
4723
+ }
4724
+ return globalRoot;
4725
+ }
4726
+ return cwd;
4727
+ }
4640
4728
 
4641
4729
  // src/commands/skills/sync.ts
4642
4730
  import { Command as Command18 } from "commander";
@@ -5157,6 +5245,10 @@ async function loadUiData(packageName) {
5157
5245
  const packageRoot = resolvePackageRoot2(packageName);
5158
5246
  logger.debug(`Resolved ui package root: ${packageRoot}`);
5159
5247
  const manifest = await loadUiPackageManifest(packageRoot);
5248
+ const pkgJson = JSON.parse(
5249
+ await fs18.readFile(path23.join(packageRoot, "package.json"), "utf-8")
5250
+ );
5251
+ manifest.version = pkgJson.version;
5160
5252
  let data = {};
5161
5253
  const dataPath = path23.join(packageRoot, "_data.json");
5162
5254
  try {
@@ -5233,8 +5325,14 @@ async function installUiEntries(options) {
5233
5325
  const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
5234
5326
  const resources = [];
5235
5327
  const npmDeps = {};
5236
- let written = 0;
5328
+ let created = 0;
5329
+ let overwritten = 0;
5330
+ let userModified = 0;
5237
5331
  let skipped = 0;
5332
+ const { priorResources } = options;
5333
+ const priorByTarget = new Map(
5334
+ (priorResources ?? []).map((r) => [r.target, r])
5335
+ );
5238
5336
  for (const id of orderedIds) {
5239
5337
  const entry = idToEntry.get(id);
5240
5338
  if (!entry) continue;
@@ -5268,11 +5366,25 @@ async function installUiEntries(options) {
5268
5366
  });
5269
5367
  continue;
5270
5368
  }
5369
+ const relPath = path24.relative(projectRoot, targetAbs);
5370
+ const prior = priorByTarget.get(relPath);
5371
+ if (prior && prior.hash !== "sha256:legacy-unknown") {
5372
+ const onDiskHash = computeHash(current);
5373
+ if (onDiskHash !== prior.hash) {
5374
+ logger.warn(
5375
+ ` \u26A0 ${relPath} has local modifications (backed up)`
5376
+ );
5377
+ userModified++;
5378
+ }
5379
+ }
5271
5380
  await backupFile(targetAbs, projectRoot);
5381
+ overwritten++;
5382
+ logger.info(` overwrite: ${rel(projectRoot, targetAbs)}`);
5383
+ } else {
5384
+ created++;
5385
+ logger.info(` create: ${rel(projectRoot, targetAbs)}`);
5272
5386
  }
5273
5387
  await writeFileSafe(targetAbs, transformed);
5274
- written++;
5275
- logger.info(` write: ${rel(projectRoot, targetAbs)}`);
5276
5388
  resources.push({
5277
5389
  id: `${entry.id}:${file.targetName}`,
5278
5390
  target: path24.relative(projectRoot, targetAbs),
@@ -5285,7 +5397,10 @@ async function installUiEntries(options) {
5285
5397
  orderedIds,
5286
5398
  resources,
5287
5399
  npmDependencies: npmDeps,
5288
- written,
5400
+ written: created + overwritten,
5401
+ created,
5402
+ overwritten,
5403
+ userModified,
5289
5404
  skipped
5290
5405
  };
5291
5406
  }
@@ -5340,17 +5455,22 @@ async function runUiAdd(options) {
5340
5455
  `Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
5341
5456
  );
5342
5457
  }
5458
+ const existingManifest = await readInstalledManifest(
5459
+ projectRoot
5460
+ ) ?? { schemaVersion: 1, installed: [] };
5461
+ const priorPkg = existingManifest.installed.find(
5462
+ (p2) => p2.package === packageName
5463
+ );
5343
5464
  const result = await installUiEntries({
5344
5465
  projectRoot,
5345
5466
  manifest: effectiveManifest,
5346
5467
  packageRoot,
5347
5468
  aliases: uiCfg.aliases,
5348
5469
  requested: ids,
5349
- skipExisting: !overwrite
5470
+ skipExisting: !overwrite,
5471
+ priorResources: priorPkg?.resources
5350
5472
  });
5351
- const installed = await readInstalledManifest(
5352
- projectRoot
5353
- ) ?? { schemaVersion: 1, installed: [] };
5473
+ const installed = existingManifest;
5354
5474
  const idx = installed.installed.findIndex((p2) => p2.package === packageName);
5355
5475
  const prior = idx >= 0 ? installed.installed[idx] : null;
5356
5476
  const mergedResources = mergeResources(
@@ -5375,6 +5495,9 @@ async function runUiAdd(options) {
5375
5495
  packageName,
5376
5496
  orderedIds: result.orderedIds,
5377
5497
  written: result.written,
5498
+ created: result.created,
5499
+ overwritten: result.overwritten,
5500
+ userModified: result.userModified,
5378
5501
  skipped: result.skipped,
5379
5502
  npmDependencies: result.npmDependencies,
5380
5503
  resources: result.resources
@@ -5738,8 +5861,13 @@ var addCommand2 = new Command22("add").description(
5738
5861
  logger.warn("meta sync failed (non-fatal)");
5739
5862
  }
5740
5863
  logger.success(
5741
- `UI add complete: ${result.written} written, ${result.skipped} skipped.`
5864
+ `UI add complete: ${result.created} created, ${result.overwritten} updated, ${result.skipped} skipped.`
5742
5865
  );
5866
+ if (result.userModified > 0) {
5867
+ logger.warn(
5868
+ `${result.userModified} file(s) had local modifications (backed up to .teamix-evo/.backups/)`
5869
+ );
5870
+ }
5743
5871
  logger.info("");
5744
5872
  logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
5745
5873
  const npmDeps = Object.entries(result.npmDependencies);
@@ -5900,6 +6028,7 @@ import { Command as Command24 } from "commander";
5900
6028
  // src/core/ui-upgrade.ts
5901
6029
  init_state();
5902
6030
  import * as path29 from "path";
6031
+ import * as fs23 from "fs/promises";
5903
6032
  import { createRequire as createRequire7 } from "module";
5904
6033
  import {
5905
6034
  loadUiPackageManifest as loadUiPackageManifest3,
@@ -6071,6 +6200,9 @@ async function buildUiUpgradeStaging(options) {
6071
6200
  }
6072
6201
  if (entries.length === 0) return null;
6073
6202
  const byRisk = aggregateByRisk(entries);
6203
+ const localModifiedCount = entries.filter(
6204
+ (e) => e.current.localModified
6205
+ ).length;
6074
6206
  const manifestOut = {
6075
6207
  schemaVersion: 1,
6076
6208
  ts: isoTs,
@@ -6080,7 +6212,11 @@ async function buildUiUpgradeStaging(options) {
6080
6212
  fromVersion: lineageReport.installedVersion ?? "",
6081
6213
  toVersion: options.manifest.version,
6082
6214
  lineage: lineageReport.lineage,
6083
- summary: { total: entries.length, byRisk },
6215
+ summary: {
6216
+ total: entries.length,
6217
+ byRisk,
6218
+ ...localModifiedCount > 0 ? { localModifiedCount } : {}
6219
+ },
6084
6220
  entries
6085
6221
  };
6086
6222
  await ensureDir(stagingDir);
@@ -6096,6 +6232,8 @@ async function processRegistered(args) {
6096
6232
  const currentSource = await readFileOrNull(
6097
6233
  resolveResourceTarget(projectRoot, resource.target)
6098
6234
  );
6235
+ const onDiskHash = currentSource !== null ? computeHash(currentSource) : null;
6236
+ const localModified = onDiskHash !== null && resource.hash !== "sha256:legacy-unknown" && onDiskHash !== resource.hash;
6099
6237
  if (currentSource === null) {
6100
6238
  return buildBreakingEntry({
6101
6239
  id,
@@ -6141,6 +6279,16 @@ async function processRegistered(args) {
6141
6279
  incomingSource: incomingTransformed,
6142
6280
  multiFile: entry.files.length > 1
6143
6281
  });
6282
+ if (localModified && diff.riskLevel !== "unchanged") {
6283
+ if (diff.riskLevel === "upgradable-low") {
6284
+ diff.riskLevel = "upgradable-medium";
6285
+ } else if (diff.riskLevel === "upgradable-medium") {
6286
+ diff.riskLevel = "risky";
6287
+ }
6288
+ diff.hints.push(
6289
+ "\u26A0 file has local modifications since installation \u2014 manual merge recommended"
6290
+ );
6291
+ }
6144
6292
  const promotion = derivePromotion({
6145
6293
  currentSource,
6146
6294
  incomingSource: incomingTransformed,
@@ -6155,7 +6303,9 @@ async function processRegistered(args) {
6155
6303
  resolveResourceTarget(projectRoot, resource.target)
6156
6304
  ),
6157
6305
  hash: resource.hash,
6158
- sourceLineage: "teamix-evo"
6306
+ sourceLineage: "teamix-evo",
6307
+ onDiskHash,
6308
+ localModified
6159
6309
  },
6160
6310
  incoming: {
6161
6311
  sourceVersion: args.sourceVersion,
@@ -6586,6 +6736,7 @@ async function buildStaging(args) {
6586
6736
  if (category === "blocks") {
6587
6737
  const root = args.blocksPackageRoot ?? resolvePackageRoot4("@teamix-evo/blocks");
6588
6738
  const manifest = await loadUiPackageManifest3(root);
6739
+ manifest.version = await readPkgVersion(root);
6589
6740
  return buildUiUpgradeStaging({
6590
6741
  projectRoot,
6591
6742
  category,
@@ -6600,6 +6751,7 @@ async function buildStaging(args) {
6600
6751
  if (category === "ui") {
6601
6752
  const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
6602
6753
  const manifest = await loadUiPackageManifest3(root);
6754
+ manifest.version = await readPkgVersion(root);
6603
6755
  return buildUiUpgradeStaging({
6604
6756
  projectRoot,
6605
6757
  category,
@@ -6631,7 +6783,7 @@ async function buildStaging(args) {
6631
6783
  const synthetic = {
6632
6784
  schemaVersion: 1,
6633
6785
  package: "ui",
6634
- version: variantManifest.version,
6786
+ version: await readPkgVersion(bizRoot),
6635
6787
  engines: variantManifest.engines,
6636
6788
  entries: merged
6637
6789
  };
@@ -6647,6 +6799,13 @@ async function buildStaging(args) {
6647
6799
  onlyIds
6648
6800
  });
6649
6801
  }
6802
+ async function readPkgVersion(packageRoot) {
6803
+ const raw = await fs23.readFile(
6804
+ path29.join(packageRoot, "package.json"),
6805
+ "utf-8"
6806
+ );
6807
+ return JSON.parse(raw).version;
6808
+ }
6650
6809
 
6651
6810
  // src/commands/_upgrade-command-factory.ts
6652
6811
  init_logger();
@@ -6687,7 +6846,7 @@ function makeUpgradeCommand(category) {
6687
6846
  `CLI \u4E0D\u4F1A\u81EA\u52A8\u5199\u5165 ${meta.installDirHint}\uFF08ADR 0019 frozen \u8FB9\u754C\uFF09\u3002`
6688
6847
  );
6689
6848
  logger.error(
6690
- `\u8BF7\u8FD0\u884C \`teamix-evo ${category} upgrade\` \u751F\u6210 staging\uFF0C\u518D\u8BA9 AI \u8C03\u7528 teamix-evo-manage skill \u9010\u6587\u4EF6\u5E94\u7528\u3002`
6849
+ `\u8BF7\u8FD0\u884C \`teamix-evo ${category} update\` \u751F\u6210 staging\uFF0C\u518D\u8BA9 AI \u8C03\u7528 teamix-evo-manage skill \u9010\u6587\u4EF6\u5E94\u7528\u3002`
6691
6850
  );
6692
6851
  process.exitCode = 1;
6693
6852
  return;
@@ -6740,6 +6899,11 @@ function makeUpgradeCommand(category) {
6740
6899
  } catch {
6741
6900
  logger.warn(" meta sync failed (non-fatal)");
6742
6901
  }
6902
+ if (manifest.summary.localModifiedCount) {
6903
+ logger.warn(
6904
+ ` \u26A0 ${manifest.summary.localModifiedCount} file(s) have local modifications \u2014 AI will prompt for manual merge`
6905
+ );
6906
+ }
6743
6907
  logger.info("");
6744
6908
  logger.info(
6745
6909
  "Next: \u8BA9 AI \u8C03\u7528 teamix-evo-manage skill\uFF0C\u6309 risk \u5206\u6279 review & apply\u3002"
@@ -6765,7 +6929,7 @@ import { Command as Command25 } from "commander";
6765
6929
 
6766
6930
  // src/core/ui-promote.ts
6767
6931
  init_fs();
6768
- import * as fs24 from "fs/promises";
6932
+ import * as fs25 from "fs/promises";
6769
6933
  import * as path31 from "path";
6770
6934
  init_logger();
6771
6935
  init_error();
@@ -6773,7 +6937,7 @@ init_state();
6773
6937
 
6774
6938
  // src/core/ui-promote-imports.ts
6775
6939
  init_fs();
6776
- import * as fs23 from "fs/promises";
6940
+ import * as fs24 from "fs/promises";
6777
6941
  import * as path30 from "path";
6778
6942
  var SOURCE_FILE_EXT = /\.(tsx?|jsx?|mts|cts)$/;
6779
6943
  var STATIC_IMPORT_FROM = /^(\s*(?:import|export)\s[\s\S]*?from\s+['"])([^'"\n]+)(['"])/;
@@ -6880,7 +7044,7 @@ async function collectTsFiles(root) {
6880
7044
  async function walk(dir) {
6881
7045
  let entries;
6882
7046
  try {
6883
- entries = await fs23.readdir(dir, { withFileTypes: true });
7047
+ entries = await fs24.readdir(dir, { withFileTypes: true });
6884
7048
  } catch (err) {
6885
7049
  if (err.code === "ENOENT") return;
6886
7050
  throw err;
@@ -7285,7 +7449,7 @@ async function findLatestUiStagingDir(projectRoot) {
7285
7449
  const base = path31.join(projectRoot, TEAMIX_DIR5, STAGING_DIR2);
7286
7450
  let entries;
7287
7451
  try {
7288
- entries = await fs24.readdir(base, { withFileTypes: true });
7452
+ entries = await fs25.readdir(base, { withFileTypes: true });
7289
7453
  } catch (err) {
7290
7454
  if (err.code === "ENOENT") return null;
7291
7455
  throw err;
@@ -7382,7 +7546,7 @@ function posix2(p2) {
7382
7546
  }
7383
7547
 
7384
7548
  // src/core/file-changes.ts
7385
- import * as fs25 from "fs/promises";
7549
+ import * as fs26 from "fs/promises";
7386
7550
  import * as path32 from "path";
7387
7551
  function toRelativePosix(p2, projectRoot) {
7388
7552
  let rel2 = p2;
@@ -7574,6 +7738,7 @@ import { Command as Command27 } from "commander";
7574
7738
 
7575
7739
  // src/core/variant-ui-add.ts
7576
7740
  import * as path33 from "path";
7741
+ import * as fs27 from "fs/promises";
7577
7742
  import { createRequire as createRequire8 } from "module";
7578
7743
  import {
7579
7744
  loadUiPackageManifest as loadUiPackageManifest4,
@@ -7609,6 +7774,10 @@ async function runVariantUiAdd(packageName, options) {
7609
7774
  }
7610
7775
  const variantDir = path33.join(packageRoot, "variants", variant);
7611
7776
  const variantManifest = await loadVariantUiPackageManifest3(variantDir);
7777
+ const pkgJson = JSON.parse(
7778
+ await fs27.readFile(path33.join(packageRoot, "package.json"), "utf-8")
7779
+ );
7780
+ const packageVersion = pkgJson.version;
7612
7781
  const knownIds = new Set(variantManifest.entries.map((e) => e.id));
7613
7782
  const unknown = ids.filter((id) => !knownIds.has(id));
7614
7783
  if (unknown.length > 0) {
@@ -7632,28 +7801,27 @@ async function runVariantUiAdd(packageName, options) {
7632
7801
  const adaptedManifest = {
7633
7802
  schemaVersion: 1,
7634
7803
  package: "ui",
7635
- version: variantManifest.version,
7804
+ version: packageVersion,
7636
7805
  engines: variantManifest.engines,
7637
7806
  entries: mergedEntries
7638
7807
  };
7808
+ const installed = await readInstalledManifest(
7809
+ projectRoot
7810
+ ) ?? { schemaVersion: 1, installed: [] };
7811
+ const idx = installed.installed.findIndex(
7812
+ (p2) => p2.package === fullPackageName && p2.variant === variant
7813
+ );
7814
+ const prior = idx >= 0 ? installed.installed[idx] : null;
7639
7815
  const result = await installUiEntries({
7640
7816
  projectRoot,
7641
7817
  manifest: adaptedManifest,
7642
7818
  packageRoot: variantDir,
7643
- // default for variant entries
7644
7819
  entryPackageRoot,
7645
- // ui entries resolve from uiPackageRoot
7646
7820
  aliases: uiCfg.aliases,
7647
7821
  requested: ids,
7648
- skipExisting: !overwrite
7822
+ skipExisting: !overwrite,
7823
+ priorResources: prior?.resources
7649
7824
  });
7650
- const installed = await readInstalledManifest(
7651
- projectRoot
7652
- ) ?? { schemaVersion: 1, installed: [] };
7653
- const idx = installed.installed.findIndex(
7654
- (p2) => p2.package === fullPackageName && p2.variant === variant
7655
- );
7656
- const prior = idx >= 0 ? installed.installed[idx] : null;
7657
7825
  const mergedResources = mergeResources3(
7658
7826
  prior?.resources ?? [],
7659
7827
  result.resources
@@ -7661,7 +7829,7 @@ async function runVariantUiAdd(packageName, options) {
7661
7829
  const entry = {
7662
7830
  package: fullPackageName,
7663
7831
  variant,
7664
- version: variantManifest.version,
7832
+ version: packageVersion,
7665
7833
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
7666
7834
  resources: mergedResources
7667
7835
  };
@@ -7673,6 +7841,9 @@ async function runVariantUiAdd(packageName, options) {
7673
7841
  variant,
7674
7842
  orderedIds: result.orderedIds,
7675
7843
  written: result.written,
7844
+ created: result.created,
7845
+ overwritten: result.overwritten,
7846
+ userModified: result.userModified,
7676
7847
  skipped: result.skipped,
7677
7848
  npmDependencies: result.npmDependencies,
7678
7849
  resources: result.resources
@@ -7772,8 +7943,13 @@ var addCommand3 = new Command27("add").description(
7772
7943
  logger.warn("meta sync failed (non-fatal)");
7773
7944
  }
7774
7945
  logger.success(
7775
- `biz-ui add complete: ${result.written} written, ${result.skipped} skipped.`
7946
+ `biz-ui add complete: ${result.created} created, ${result.overwritten} updated, ${result.skipped} skipped.`
7776
7947
  );
7948
+ if (result.userModified > 0) {
7949
+ logger.warn(
7950
+ `${result.userModified} file(s) had local modifications (backed up to .teamix-evo/.backups/)`
7951
+ );
7952
+ }
7777
7953
  logger.info("");
7778
7954
  logger.info(`Variant: ${result.variant}`);
7779
7955
  logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
@@ -7889,6 +8065,11 @@ async function runBlocksAdd(options) {
7889
8065
  `Unknown block id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo blocks list\` to see options.`
7890
8066
  );
7891
8067
  }
8068
+ const installed = await readInstalledManifest(
8069
+ projectRoot
8070
+ ) ?? { schemaVersion: 1, installed: [] };
8071
+ const idx = installed.installed.findIndex((p2) => p2.package === packageName);
8072
+ const prior = idx >= 0 ? installed.installed[idx] : null;
7892
8073
  const result = await installUiEntries({
7893
8074
  projectRoot,
7894
8075
  manifest,
@@ -7896,13 +8077,9 @@ async function runBlocksAdd(options) {
7896
8077
  aliases: uiCfg.aliases,
7897
8078
  requested: ids,
7898
8079
  skipExisting: !overwrite,
7899
- flatten: false
8080
+ flatten: false,
8081
+ priorResources: prior?.resources
7900
8082
  });
7901
- const installed = await readInstalledManifest(
7902
- projectRoot
7903
- ) ?? { schemaVersion: 1, installed: [] };
7904
- const idx = installed.installed.findIndex((p2) => p2.package === packageName);
7905
- const prior = idx >= 0 ? installed.installed[idx] : null;
7906
8083
  const mergedResources = mergeResources4(
7907
8084
  prior?.resources ?? [],
7908
8085
  result.resources
@@ -7921,6 +8098,9 @@ async function runBlocksAdd(options) {
7921
8098
  packageName,
7922
8099
  orderedIds: result.orderedIds,
7923
8100
  written: result.written,
8101
+ created: result.created,
8102
+ overwritten: result.overwritten,
8103
+ userModified: result.userModified,
7924
8104
  skipped: result.skipped,
7925
8105
  npmDependencies: result.npmDependencies,
7926
8106
  resources: result.resources
@@ -7948,8 +8128,13 @@ var addCommand4 = new Command31("add").description("\u5B89\u88C5\u4E00\u4E2A\u62
7948
8128
  overwrite: opts.overwrite
7949
8129
  });
7950
8130
  logger.success(
7951
- `Blocks add complete: ${result.written} written, ${result.skipped} skipped.`
8131
+ `Blocks add complete: ${result.created} created, ${result.overwritten} updated, ${result.skipped} skipped.`
7952
8132
  );
8133
+ if (result.userModified > 0) {
8134
+ logger.warn(
8135
+ `${result.userModified} file(s) had local modifications (backed up to .teamix-evo/.backups/)`
8136
+ );
8137
+ }
7953
8138
  logger.info("");
7954
8139
  logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
7955
8140
  const npmDeps = Object.entries(result.npmDependencies);
@@ -8047,7 +8232,7 @@ import * as prompts6 from "@clack/prompts";
8047
8232
  init_fs();
8048
8233
  init_logger();
8049
8234
  import * as path34 from "path";
8050
- import * as fs26 from "fs";
8235
+ import * as fs28 from "fs";
8051
8236
  import { execa } from "execa";
8052
8237
  var ESLINT_CONFIG_CONTENT = `/**
8053
8238
  * teamix-evo consumer ESLint preset \u2014 9 token-discipline rules.
@@ -8139,7 +8324,7 @@ async function runLintInit(options) {
8139
8324
  let stylelintIgnoreFilesWarning = false;
8140
8325
  if (!stylelintNeedsWrite && stylelintTemplateExists) {
8141
8326
  try {
8142
- const existingContent = fs26.readFileSync(stylelintConfigPath, "utf-8");
8327
+ const existingContent = fs28.readFileSync(stylelintConfigPath, "utf-8");
8143
8328
  const usesTeamixPreset = existingContent.includes("@teamix-evo/stylelint-config/presets/") || existingContent.includes("@teamix-evo/stylelint-config/preset/");
8144
8329
  const hasTokenIgnore = existingContent.includes("tokens.theme.css") && existingContent.includes("tokens.overrides.css");
8145
8330
  if (!usesTeamixPreset && !hasTokenIgnore) {
@@ -8176,8 +8361,8 @@ async function runLintInit(options) {
8176
8361
  };
8177
8362
  }
8178
8363
  function detectPm(projectRoot) {
8179
- if (fs26.existsSync(path34.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
8180
- if (fs26.existsSync(path34.join(projectRoot, "yarn.lock"))) return "yarn";
8364
+ if (fs28.existsSync(path34.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
8365
+ if (fs28.existsSync(path34.join(projectRoot, "yarn.lock"))) return "yarn";
8181
8366
  return "npm";
8182
8367
  }
8183
8368
  async function patchPackageJsonScripts(projectRoot) {
@@ -8273,7 +8458,7 @@ import { execa as execa2 } from "execa";
8273
8458
  // src/core/project-state.ts
8274
8459
  init_fs();
8275
8460
  init_state();
8276
- import * as fs27 from "fs/promises";
8461
+ import * as fs29 from "fs/promises";
8277
8462
  import * as path35 from "path";
8278
8463
  var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
8279
8464
  ".git",
@@ -8333,7 +8518,7 @@ async function detectProjectState(cwd) {
8333
8518
  }
8334
8519
  let entries;
8335
8520
  try {
8336
- entries = await fs27.readdir(absCwd);
8521
+ entries = await fs29.readdir(absCwd);
8337
8522
  } catch (err) {
8338
8523
  if (err.code === "ENOENT") {
8339
8524
  return {
@@ -8418,7 +8603,7 @@ function assertCommandPrecondition(command, state) {
8418
8603
  // src/core/deps-install.ts
8419
8604
  init_fs();
8420
8605
  init_logger();
8421
- import * as fs28 from "fs/promises";
8606
+ import * as fs30 from "fs/promises";
8422
8607
  import * as path36 from "path";
8423
8608
  import { exec as exec4 } from "child_process";
8424
8609
  import { promisify as promisify4 } from "util";
@@ -8475,7 +8660,7 @@ async function installProjectDeps(options) {
8475
8660
  pkg.dependencies = Object.fromEntries(
8476
8661
  Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
8477
8662
  );
8478
- await fs28.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
8663
+ await fs30.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
8479
8664
  logger.info(
8480
8665
  ` patched package.json: +${Object.keys(added).length} dependencies`
8481
8666
  );
@@ -8657,6 +8842,7 @@ async function runProjectInit(options) {
8657
8842
  `teamix-evo-design-${variant}`,
8658
8843
  `teamix-evo-code-${variant}`
8659
8844
  ],
8845
+ globalSkillIds: ["teamix-evo-manage"],
8660
8846
  mode: "merge-managed"
8661
8847
  });
8662
8848
  record({
@@ -9443,7 +9629,7 @@ import * as prompts7 from "@clack/prompts";
9443
9629
 
9444
9630
  // src/core/snapshot.ts
9445
9631
  init_logger();
9446
- import * as fs29 from "fs/promises";
9632
+ import * as fs31 from "fs/promises";
9447
9633
  import * as path42 from "path";
9448
9634
  var TEAMIX_DIR6 = ".teamix-evo";
9449
9635
  var SNAPSHOTS_DIR = ".snapshots";
@@ -9461,7 +9647,7 @@ function fsSafeToIso(safe) {
9461
9647
  async function createSnapshot(projectRoot, opts = {}) {
9462
9648
  const teamixDir = path42.join(projectRoot, TEAMIX_DIR6);
9463
9649
  try {
9464
- const stat5 = await fs29.stat(teamixDir);
9650
+ const stat5 = await fs31.stat(teamixDir);
9465
9651
  if (!stat5.isDirectory()) return null;
9466
9652
  } catch (err) {
9467
9653
  if (err.code === "ENOENT") return null;
@@ -9471,19 +9657,19 @@ async function createSnapshot(projectRoot, opts = {}) {
9471
9657
  const ts = isoToFsSafe3(isoTs);
9472
9658
  const snapshotRoot = path42.join(teamixDir, SNAPSHOTS_DIR);
9473
9659
  const target = path42.join(snapshotRoot, ts);
9474
- await fs29.mkdir(target, { recursive: true });
9475
- const entries = await fs29.readdir(teamixDir, { withFileTypes: true });
9660
+ await fs31.mkdir(target, { recursive: true });
9661
+ const entries = await fs31.readdir(teamixDir, { withFileTypes: true });
9476
9662
  for (const entry of entries) {
9477
9663
  if (entry.name === SNAPSHOTS_DIR) continue;
9478
9664
  const src = path42.join(teamixDir, entry.name);
9479
9665
  const dst = path42.join(target, entry.name);
9480
- await fs29.cp(src, dst, { recursive: true });
9666
+ await fs31.cp(src, dst, { recursive: true });
9481
9667
  }
9482
9668
  const meta = {
9483
9669
  ts: isoTs,
9484
9670
  reason: opts.reason ?? "manual"
9485
9671
  };
9486
- await fs29.writeFile(
9672
+ await fs31.writeFile(
9487
9673
  path42.join(target, META_FILE),
9488
9674
  JSON.stringify(meta, null, 2) + "\n",
9489
9675
  "utf-8"
@@ -9499,7 +9685,7 @@ async function listSnapshots(projectRoot) {
9499
9685
  const snapshotRoot = path42.join(projectRoot, TEAMIX_DIR6, SNAPSHOTS_DIR);
9500
9686
  let entries;
9501
9687
  try {
9502
- entries = await fs29.readdir(snapshotRoot, { withFileTypes: true });
9688
+ entries = await fs31.readdir(snapshotRoot, { withFileTypes: true });
9503
9689
  } catch (err) {
9504
9690
  if (err.code === "ENOENT") return [];
9505
9691
  throw err;
@@ -9511,7 +9697,7 @@ async function listSnapshots(projectRoot) {
9511
9697
  let isoTs = null;
9512
9698
  let reason = null;
9513
9699
  try {
9514
- const raw = await fs29.readFile(path42.join(dir, META_FILE), "utf-8");
9700
+ const raw = await fs31.readFile(path42.join(dir, META_FILE), "utf-8");
9515
9701
  const parsed = JSON.parse(raw);
9516
9702
  if (typeof parsed.ts === "string") isoTs = parsed.ts;
9517
9703
  if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
@@ -9536,7 +9722,7 @@ async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
9536
9722
  const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
9537
9723
  const removed = [];
9538
9724
  for (const snap of toRemove) {
9539
- await fs29.rm(snap.path, { recursive: true, force: true });
9725
+ await fs31.rm(snap.path, { recursive: true, force: true });
9540
9726
  removed.push(snap.ts);
9541
9727
  logger.debug(`Pruned snapshot ${snap.ts}`);
9542
9728
  }
@@ -9546,7 +9732,7 @@ async function restoreSnapshot(projectRoot, ts) {
9546
9732
  const snapshotRoot = path42.join(projectRoot, TEAMIX_DIR6, SNAPSHOTS_DIR);
9547
9733
  const target = path42.join(snapshotRoot, ts);
9548
9734
  try {
9549
- const stat5 = await fs29.stat(target);
9735
+ const stat5 = await fs31.stat(target);
9550
9736
  if (!stat5.isDirectory()) {
9551
9737
  throw new Error(`Snapshot path is not a directory: ${target}`);
9552
9738
  }
@@ -9560,20 +9746,20 @@ async function restoreSnapshot(projectRoot, ts) {
9560
9746
  }
9561
9747
  await createSnapshot(projectRoot, { reason: "restore", protectedTs: ts });
9562
9748
  const teamixDir = path42.join(projectRoot, TEAMIX_DIR6);
9563
- const live = await fs29.readdir(teamixDir, { withFileTypes: true });
9749
+ const live = await fs31.readdir(teamixDir, { withFileTypes: true });
9564
9750
  for (const entry of live) {
9565
9751
  if (entry.name === SNAPSHOTS_DIR) continue;
9566
- await fs29.rm(path42.join(teamixDir, entry.name), {
9752
+ await fs31.rm(path42.join(teamixDir, entry.name), {
9567
9753
  recursive: true,
9568
9754
  force: true
9569
9755
  });
9570
9756
  }
9571
- const snapshotEntries = await fs29.readdir(target, { withFileTypes: true });
9757
+ const snapshotEntries = await fs31.readdir(target, { withFileTypes: true });
9572
9758
  for (const entry of snapshotEntries) {
9573
9759
  if (entry.name === META_FILE) continue;
9574
9760
  const src = path42.join(target, entry.name);
9575
9761
  const dst = path42.join(teamixDir, entry.name);
9576
- await fs29.cp(src, dst, { recursive: true });
9762
+ await fs31.cp(src, dst, { recursive: true });
9577
9763
  }
9578
9764
  logger.debug(`Restored .teamix-evo/ from snapshot ${ts}`);
9579
9765
  }
@@ -9688,7 +9874,7 @@ import * as prompts8 from "@clack/prompts";
9688
9874
  // src/core/variant-switch.ts
9689
9875
  init_fs();
9690
9876
  import * as path44 from "path";
9691
- import * as fs30 from "fs/promises";
9877
+ import * as fs32 from "fs/promises";
9692
9878
  import {
9693
9879
  loadTokensPackageManifest as loadTokensPackageManifest3,
9694
9880
  getVariantEntry as getVariantEntry3
@@ -9761,7 +9947,7 @@ async function runVariantSwitch(options) {
9761
9947
  });
9762
9948
  continue;
9763
9949
  }
9764
- const upstreamContent = await fs30.readFile(upstreamAbs, "utf-8");
9950
+ const upstreamContent = await fs32.readFile(upstreamAbs, "utf-8");
9765
9951
  if (resource.strategy === "regenerable") {
9766
9952
  const newHash = computeHash(upstreamContent);
9767
9953
  if (newHash === resource.hash) {
@@ -9819,7 +10005,7 @@ async function runVariantSwitch(options) {
9819
10005
  const upstreamBasename = lookupUpstreamBasename2(consumerBasename);
9820
10006
  const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
9821
10007
  if (resource.strategy === "regenerable" && upstreamAbs) {
9822
- const upstreamContent = await fs30.readFile(upstreamAbs, "utf-8");
10008
+ const upstreamContent = await fs32.readFile(upstreamAbs, "utf-8");
9823
10009
  await writeFileSafe(consumerAbs, upstreamContent);
9824
10010
  logger.debug(`[variant-switch] rewrite regenerable: ${resource.target}`);
9825
10011
  refreshedResources.push({
@@ -9829,8 +10015,8 @@ async function runVariantSwitch(options) {
9829
10015
  continue;
9830
10016
  }
9831
10017
  if (resource.strategy === "managed" && upstreamAbs && await fileExists(consumerAbs)) {
9832
- const upstreamContent = await fs30.readFile(upstreamAbs, "utf-8");
9833
- const consumerContent = await fs30.readFile(consumerAbs, "utf-8");
10018
+ const upstreamContent = await fs32.readFile(upstreamAbs, "utf-8");
10019
+ const consumerContent = await fs32.readFile(consumerAbs, "utf-8");
9834
10020
  const merged = mergeManagedRegions(upstreamContent, consumerContent);
9835
10021
  if (merged !== consumerContent) {
9836
10022
  await writeFileSafe(consumerAbs, merged);
@@ -9927,10 +10113,12 @@ async function runVariantSwitch(options) {
9927
10113
  const lock2 = await readSkillsLock(projectRoot);
9928
10114
  if (lock2) {
9929
10115
  const projectSkillIds = Object.entries(lock2.skills).filter(([, v]) => v.scope === "project").map(([id]) => id);
10116
+ const globalSkillIds = Object.entries(lock2.skills).filter(([, v]) => v.scope === "global").map(([id]) => id);
9930
10117
  await runGenerateAgentsMd({
9931
10118
  projectRoot,
9932
10119
  variant: newVariant,
9933
10120
  skillIds: projectSkillIds,
10121
+ globalSkillIds,
9934
10122
  mode: "merge-managed"
9935
10123
  });
9936
10124
  }