teamix-evo 0.14.0 → 0.14.1

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.
@@ -314,8 +314,14 @@ interface RunUiAddResult {
314
314
  packageName: string;
315
315
  /** Registered ids in install order (deps + requested). */
316
316
  orderedIds: string[];
317
- /** Number of files written. */
317
+ /** Number of files written (= created + overwritten). */
318
318
  written: number;
319
+ /** Number of files newly created. */
320
+ created: number;
321
+ /** Number of existing files overwritten. */
322
+ overwritten: number;
323
+ /** Number of overwritten files that had local modifications. */
324
+ userModified: number;
319
325
  /** Number of files skipped due to frozen + exists. */
320
326
  skipped: number;
321
327
  /** Aggregate npm dependencies of the installed entries. */
@@ -390,6 +396,9 @@ interface RunVariantUiAddResult {
390
396
  variant: string;
391
397
  orderedIds: string[];
392
398
  written: number;
399
+ created: number;
400
+ overwritten: number;
401
+ userModified: number;
393
402
  skipped: number;
394
403
  npmDependencies: Record<string, string>;
395
404
  resources: InstalledResource[];
@@ -883,6 +892,8 @@ interface UiInstallOptions {
883
892
  skipExisting?: boolean;
884
893
  /** When false, preserve directory structure in import paths (skip flattenRestPath). Defaults to true. */
885
894
  flatten?: boolean;
895
+ /** Prior manifest resources for the same package — used to detect user-modified files before overwrite. */
896
+ priorResources?: InstalledResource[];
886
897
  }
887
898
  interface UiInstallResult {
888
899
  /** Ordered list of entry ids that were processed (deps + requested) */
@@ -891,8 +902,14 @@ interface UiInstallResult {
891
902
  resources: InstalledResource[];
892
903
  /** Aggregate npm dependencies across the installed entries */
893
904
  npmDependencies: Record<string, string>;
894
- /** Number of files written */
905
+ /** Number of files written (= created + overwritten, kept for backwards compat) */
895
906
  written: number;
907
+ /** Number of files newly created (target did not exist) */
908
+ created: number;
909
+ /** Number of existing files overwritten */
910
+ overwritten: number;
911
+ /** Number of overwritten files that had local modifications vs manifest hash */
912
+ userModified: number;
896
913
  /** Number of files skipped because they already exist (frozen) */
897
914
  skipped: number;
898
915
  }
@@ -165,12 +165,40 @@ async function readInstalledManifest(projectRoot) {
165
165
  );
166
166
  }
167
167
  const result = validateInstalled(data);
168
- if (!result.success) {
169
- throw new Error(
170
- `Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
168
+ if (result.success) {
169
+ return result.data;
170
+ }
171
+ const migrated = migrateManifest(data);
172
+ const retryResult = validateInstalled(migrated);
173
+ if (retryResult.success) {
174
+ logger.warn(
175
+ "Migrated legacy manifest.json \u2014 will be overwritten on next install."
171
176
  );
177
+ return retryResult.data;
172
178
  }
173
- return result.data;
179
+ throw new Error(
180
+ `Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
181
+ );
182
+ }
183
+ function migrateManifest(data) {
184
+ if (typeof data !== "object" || data === null) return data;
185
+ const obj = data;
186
+ if (!("schemaVersion" in obj)) obj.schemaVersion = 1;
187
+ if (!Array.isArray(obj.installed)) obj.installed = [];
188
+ for (const pkg of obj.installed) {
189
+ if (typeof pkg !== "object" || pkg === null) continue;
190
+ if (!pkg.variant) pkg.variant = "_flat";
191
+ if (!pkg.installedAt) pkg.installedAt = "";
192
+ if (!pkg.version) pkg.version = "0.0.0";
193
+ if (Array.isArray(pkg.resources)) {
194
+ for (const res of pkg.resources) {
195
+ if (typeof res !== "object" || res === null) continue;
196
+ if (!res.hash) res.hash = "sha256:legacy-unknown";
197
+ if (!res.strategy) res.strategy = "frozen";
198
+ }
199
+ }
200
+ }
201
+ return obj;
174
202
  }
175
203
  async function writeInstalledManifest(projectRoot, manifest) {
176
204
  const manifestPath = path2.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
@@ -948,9 +976,15 @@ function partitionByVersion(ids, manifest, existing) {
948
976
  onlyIds.push(name);
949
977
  continue;
950
978
  }
951
- const installedVer = existing.lock?.skills?.[name]?.version;
979
+ const installedVer = existing.lock?.skills?.[name]?.version ?? existing.pkg?.version;
952
980
  const latestVer = manifestById.get(name)?.version;
953
- if (installedVer && latestVer && compareSemver(installedVer, latestVer) < 0) {
981
+ if (!installedVer || !latestVer) {
982
+ if (latestVer) {
983
+ outdatedSkills.push({ id: name, installed: "0.0.0", latest: latestVer });
984
+ } else {
985
+ skippedSkillIds.push(name);
986
+ }
987
+ } else if (compareSemver(installedVer, latestVer) < 0) {
954
988
  outdatedSkills.push({
955
989
  id: name,
956
990
  installed: installedVer,
@@ -1669,13 +1703,34 @@ async function runSkillsUpdate(options) {
1669
1703
  }
1670
1704
  const ides = skillsCfg.ides ?? ["qoder", "claude"];
1671
1705
  const scope = skillsCfg.scope ?? "project";
1672
- const existingLock = await readSkillsLock(projectRoot);
1706
+ let existingLock = await readSkillsLock(projectRoot);
1673
1707
  if (!existingLock || Object.keys(existingLock.skills).length === 0) {
1674
- return { status: "no-skills" };
1708
+ const installedManifest2 = await readInstalledManifest(projectRoot);
1709
+ const pkg = installedManifest2?.installed.find(
1710
+ (p) => p.package === packageName
1711
+ );
1712
+ if (!pkg || pkg.resources.length === 0) {
1713
+ return { status: "no-skills" };
1714
+ }
1715
+ const derivedSkills = {};
1716
+ for (const r of pkg.resources) {
1717
+ const skillId = r.id.split(":")[0] ?? r.id;
1718
+ if (!derivedSkills[skillId]) {
1719
+ derivedSkills[skillId] = {
1720
+ version: pkg.version,
1721
+ from: packageName,
1722
+ installedAt: "",
1723
+ scope,
1724
+ mirroredTo: []
1725
+ };
1726
+ }
1727
+ }
1728
+ existingLock = { schemaVersion: 1, skills: derivedSkills };
1675
1729
  }
1730
+ const activeLock = existingLock;
1676
1731
  const { manifest, data, packageRoot } = await loadSkillsData(packageName);
1677
1732
  const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
1678
- const lockIds = Object.keys(existingLock.skills);
1733
+ const lockIds = Object.keys(activeLock.skills);
1679
1734
  const requestedSet = requestedNames ? new Set(requestedNames) : null;
1680
1735
  if (requestedSet) {
1681
1736
  const unknown = requestedNames.filter(
@@ -1712,7 +1767,7 @@ async function runSkillsUpdate(options) {
1712
1767
  targetIds.push(id);
1713
1768
  }
1714
1769
  const allSame = targetIds.every((id) => {
1715
- const lockVer = existingLock.skills[id].version;
1770
+ const lockVer = activeLock.skills[id].version;
1716
1771
  const manVer = manifestById.get(id).version;
1717
1772
  return lockVer === manVer;
1718
1773
  });
@@ -1726,7 +1781,7 @@ async function runSkillsUpdate(options) {
1726
1781
  }
1727
1782
  if (dryRun) {
1728
1783
  const plan = targetIds.map((id) => {
1729
- const lockVer = existingLock.skills[id].version;
1784
+ const lockVer = activeLock.skills[id].version;
1730
1785
  const entry2 = manifestById.get(id);
1731
1786
  const sameVersion = lockVer === entry2.version;
1732
1787
  return {
@@ -1798,7 +1853,7 @@ async function runSkillsUpdate(options) {
1798
1853
  await writeInstalledManifest(projectRoot, installedManifest);
1799
1854
  const lock = {
1800
1855
  schemaVersion: 1,
1801
- skills: { ...existingLock.skills }
1856
+ skills: { ...activeLock.skills }
1802
1857
  };
1803
1858
  for (const id of targetIds) {
1804
1859
  const skillDef = manifestById.get(id);
@@ -1984,8 +2039,14 @@ async function installUiEntries(options) {
1984
2039
  const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
1985
2040
  const resources = [];
1986
2041
  const npmDeps = {};
1987
- let written = 0;
2042
+ let created = 0;
2043
+ let overwritten = 0;
2044
+ let userModified = 0;
1988
2045
  let skipped = 0;
2046
+ const { priorResources } = options;
2047
+ const priorByTarget = new Map(
2048
+ (priorResources ?? []).map((r) => [r.target, r])
2049
+ );
1989
2050
  for (const id of orderedIds) {
1990
2051
  const entry = idToEntry.get(id);
1991
2052
  if (!entry) continue;
@@ -2019,11 +2080,25 @@ async function installUiEntries(options) {
2019
2080
  });
2020
2081
  continue;
2021
2082
  }
2083
+ const relPath = path11.relative(projectRoot, targetAbs);
2084
+ const prior = priorByTarget.get(relPath);
2085
+ if (prior && prior.hash !== "sha256:legacy-unknown") {
2086
+ const onDiskHash = computeHash(current);
2087
+ if (onDiskHash !== prior.hash) {
2088
+ logger.warn(
2089
+ ` \u26A0 ${relPath} has local modifications (backed up)`
2090
+ );
2091
+ userModified++;
2092
+ }
2093
+ }
2022
2094
  await backupFile(targetAbs, projectRoot);
2095
+ overwritten++;
2096
+ logger.info(` overwrite: ${rel(projectRoot, targetAbs)}`);
2097
+ } else {
2098
+ created++;
2099
+ logger.info(` create: ${rel(projectRoot, targetAbs)}`);
2023
2100
  }
2024
2101
  await writeFileSafe(targetAbs, transformed);
2025
- written++;
2026
- logger.info(` write: ${rel(projectRoot, targetAbs)}`);
2027
2102
  resources.push({
2028
2103
  id: `${entry.id}:${file.targetName}`,
2029
2104
  target: path11.relative(projectRoot, targetAbs),
@@ -2036,7 +2111,10 @@ async function installUiEntries(options) {
2036
2111
  orderedIds,
2037
2112
  resources,
2038
2113
  npmDependencies: npmDeps,
2039
- written,
2114
+ written: created + overwritten,
2115
+ created,
2116
+ overwritten,
2117
+ userModified,
2040
2118
  skipped
2041
2119
  };
2042
2120
  }
@@ -2115,17 +2193,22 @@ async function runUiAdd(options) {
2115
2193
  `Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
2116
2194
  );
2117
2195
  }
2196
+ const existingManifest = await readInstalledManifest(
2197
+ projectRoot
2198
+ ) ?? { schemaVersion: 1, installed: [] };
2199
+ const priorPkg = existingManifest.installed.find(
2200
+ (p) => p.package === packageName
2201
+ );
2118
2202
  const result = await installUiEntries({
2119
2203
  projectRoot,
2120
2204
  manifest: effectiveManifest,
2121
2205
  packageRoot,
2122
2206
  aliases: uiCfg.aliases,
2123
2207
  requested: ids,
2124
- skipExisting: !overwrite
2208
+ skipExisting: !overwrite,
2209
+ priorResources: priorPkg?.resources
2125
2210
  });
2126
- const installed = await readInstalledManifest(
2127
- projectRoot
2128
- ) ?? { schemaVersion: 1, installed: [] };
2211
+ const installed = existingManifest;
2129
2212
  const idx = installed.installed.findIndex((p) => p.package === packageName);
2130
2213
  const prior = idx >= 0 ? installed.installed[idx] : null;
2131
2214
  const mergedResources = mergeResources(
@@ -2150,6 +2233,9 @@ async function runUiAdd(options) {
2150
2233
  packageName,
2151
2234
  orderedIds: result.orderedIds,
2152
2235
  written: result.written,
2236
+ created: result.created,
2237
+ overwritten: result.overwritten,
2238
+ userModified: result.userModified,
2153
2239
  skipped: result.skipped,
2154
2240
  npmDependencies: result.npmDependencies,
2155
2241
  resources: result.resources
@@ -2260,24 +2346,23 @@ async function runVariantUiAdd(packageName, options) {
2260
2346
  engines: variantManifest.engines,
2261
2347
  entries: mergedEntries
2262
2348
  };
2349
+ const installed = await readInstalledManifest(
2350
+ projectRoot
2351
+ ) ?? { schemaVersion: 1, installed: [] };
2352
+ const idx = installed.installed.findIndex(
2353
+ (p) => p.package === fullPackageName && p.variant === variant
2354
+ );
2355
+ const prior = idx >= 0 ? installed.installed[idx] : null;
2263
2356
  const result = await installUiEntries({
2264
2357
  projectRoot,
2265
2358
  manifest: adaptedManifest,
2266
2359
  packageRoot: variantDir,
2267
- // default for variant entries
2268
2360
  entryPackageRoot,
2269
- // ui entries resolve from uiPackageRoot
2270
2361
  aliases: uiCfg.aliases,
2271
2362
  requested: ids,
2272
- skipExisting: !overwrite
2363
+ skipExisting: !overwrite,
2364
+ priorResources: prior?.resources
2273
2365
  });
2274
- const installed = await readInstalledManifest(
2275
- projectRoot
2276
- ) ?? { schemaVersion: 1, installed: [] };
2277
- const idx = installed.installed.findIndex(
2278
- (p) => p.package === fullPackageName && p.variant === variant
2279
- );
2280
- const prior = idx >= 0 ? installed.installed[idx] : null;
2281
2366
  const mergedResources = mergeResources2(
2282
2367
  prior?.resources ?? [],
2283
2368
  result.resources
@@ -2297,6 +2382,9 @@ async function runVariantUiAdd(packageName, options) {
2297
2382
  variant,
2298
2383
  orderedIds: result.orderedIds,
2299
2384
  written: result.written,
2385
+ created: result.created,
2386
+ overwritten: result.overwritten,
2387
+ userModified: result.userModified,
2300
2388
  skipped: result.skipped,
2301
2389
  npmDependencies: result.npmDependencies,
2302
2390
  resources: result.resources