teamix-evo 0.7.0 → 0.8.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.
@@ -1,7 +1,6 @@
1
1
  // src/core/tokens-init.ts
2
2
  import * as path9 from "path";
3
3
  import * as fs6 from "fs/promises";
4
- import { createRequire as createRequire2 } from "module";
5
4
  import {
6
5
  loadTokensPackageManifest,
7
6
  getVariantEntry
@@ -97,6 +96,19 @@ import {
97
96
  validateSkillsLock,
98
97
  TokensPackLockSchema
99
98
  } from "@teamix-evo/registry";
99
+
100
+ // src/utils/error.ts
101
+ function getErrorMessage(err) {
102
+ if (err instanceof Error) return err.message;
103
+ if (typeof err === "string") return err;
104
+ try {
105
+ return JSON.stringify(err);
106
+ } catch {
107
+ return String(err);
108
+ }
109
+ }
110
+
111
+ // src/core/state.ts
100
112
  var TEAMIX_DIR = ".teamix-evo";
101
113
  var CONFIG_FILE = "config.json";
102
114
  var MANIFEST_FILE = "manifest.json";
@@ -120,7 +132,7 @@ async function readProjectConfig(projectRoot) {
120
132
  data = JSON.parse(raw);
121
133
  } catch (err) {
122
134
  throw new Error(
123
- `Corrupted config.json (${err.message}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior config.`
135
+ `Corrupted config.json (${getErrorMessage(err)}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior config.`
124
136
  );
125
137
  }
126
138
  const result = validateConfig(data);
@@ -145,7 +157,7 @@ async function readInstalledManifest(projectRoot) {
145
157
  data = JSON.parse(raw);
146
158
  } catch (err) {
147
159
  throw new Error(
148
- `Corrupted manifest.json (${err.message}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior install records.`
160
+ `Corrupted manifest.json (${getErrorMessage(err)}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior install records.`
149
161
  );
150
162
  }
151
163
  const result = validateInstalled(data);
@@ -173,9 +185,7 @@ async function readTokensLock(projectRoot) {
173
185
  }
174
186
  return parsed.data;
175
187
  } catch (err) {
176
- logger.warn(
177
- `Failed to parse tokens-lock.json: ${err.message}`
178
- );
188
+ logger.warn(`Failed to parse tokens-lock.json: ${getErrorMessage(err)}`);
179
189
  return null;
180
190
  }
181
191
  }
@@ -206,7 +216,7 @@ async function readSkillsLock(projectRoot) {
206
216
  return result.data;
207
217
  } catch (err) {
208
218
  logger.warn(
209
- `Failed to parse skills manifest.lock.json: ${err.message}`
219
+ `Failed to parse skills manifest.lock.json: ${getErrorMessage(err)}`
210
220
  );
211
221
  return null;
212
222
  }
@@ -221,6 +231,12 @@ async function writeSkillsLock(projectRoot, lock) {
221
231
  await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
222
232
  logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
223
233
  }
234
+ function findInstalledPackage(installed, packageName) {
235
+ if (!installed) return null;
236
+ const matches = installed.installed.filter((p) => p.package === packageName);
237
+ if (matches.length === 0) return null;
238
+ return matches[matches.length - 1] ?? null;
239
+ }
224
240
 
225
241
  // src/core/skills-client.ts
226
242
  import * as path3 from "path";
@@ -255,7 +271,9 @@ import * as path7 from "path";
255
271
  import * as fs5 from "fs/promises";
256
272
  import {
257
273
  replaceManagedRegion,
258
- hasManagedRegion
274
+ hasManagedRegion,
275
+ extractFrontmatter,
276
+ replaceFrontmatter
259
277
  } from "@teamix-evo/registry";
260
278
 
261
279
  // src/ide/QoderAdapter.ts
@@ -339,6 +357,8 @@ async function loadTemplateFile(filePath) {
339
357
  // src/utils/path.ts
340
358
  import * as path6 from "path";
341
359
  import * as fs4 from "fs/promises";
360
+ import { createRequire as createRequire2 } from "module";
361
+ var require3 = createRequire2(import.meta.url);
342
362
  function resolveSourcePath(source, variantDir, packageRoot) {
343
363
  if (source.startsWith("_template/")) {
344
364
  return path6.join(packageRoot, source);
@@ -358,6 +378,10 @@ async function walkDir(dir) {
358
378
  }
359
379
  return files;
360
380
  }
381
+ function resolveTokensPackageRoot(packageName) {
382
+ const pkgJson = require3.resolve(`${packageName}/package.json`);
383
+ return path6.dirname(pkgJson);
384
+ }
361
385
 
362
386
  // src/core/skills-installer.ts
363
387
  async function installSkills(options) {
@@ -394,9 +418,9 @@ async function writeSkillSource(skill, options) {
394
418
  const { data, packageRoot, projectRoot } = options;
395
419
  const sourceAbs = path7.resolve(packageRoot, skill.source);
396
420
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
397
- const stat2 = await fs5.stat(sourceAbs);
421
+ const stat5 = await fs5.stat(sourceAbs);
398
422
  const records = [];
399
- if (stat2.isFile()) {
423
+ if (stat5.isFile()) {
400
424
  const targetFile = path7.join(targetDir, "SKILL.md");
401
425
  const content = await renderSkillContent(sourceAbs, skill, data);
402
426
  await writeFileSafe(targetFile, content);
@@ -463,6 +487,10 @@ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sou
463
487
  return existing;
464
488
  }
465
489
  let merged = existing;
490
+ const upstreamFm = extractFrontmatter(sourceContent);
491
+ if (upstreamFm) {
492
+ merged = replaceFrontmatter(merged, upstreamFm);
493
+ }
466
494
  for (const id of matchedRegions) {
467
495
  const newRegion = extractRegionBody(sourceContent, id);
468
496
  if (newRegion === null) continue;
@@ -524,11 +552,7 @@ async function updateSkills(options) {
524
552
  if (idFilter && !idFilter.has(skill.id)) continue;
525
553
  const skillIdes = skill.ides.filter((i) => ides.includes(i));
526
554
  if (skillIdes.length === 0) continue;
527
- const sourceRecords = await rewriteSkillSource(
528
- skill,
529
- options,
530
- summary
531
- );
555
+ const sourceRecords = await rewriteSkillSource(skill, options, summary);
532
556
  updated.push(...sourceRecords);
533
557
  for (const ide of skillIdes) {
534
558
  const mirrorRecords = await mirrorSkillToIde(
@@ -546,8 +570,8 @@ async function rewriteSkillSource(skill, options, summary) {
546
570
  const { data, packageRoot, projectRoot } = options;
547
571
  const sourceAbs = path7.resolve(packageRoot, skill.source);
548
572
  const targetDir = getSkillsSourceDir(projectRoot, skill.name);
549
- const stat2 = await fs5.stat(sourceAbs);
550
- if (!stat2.isFile()) {
573
+ const stat5 = await fs5.stat(sourceAbs);
574
+ if (!stat5.isFile()) {
551
575
  await ensureDir(targetDir);
552
576
  const entries = await walkDir(sourceAbs);
553
577
  const records = [];
@@ -618,6 +642,10 @@ async function rewriteSingleFile(args) {
618
642
  }
619
643
  const current = await readFileOrNull(targetFile);
620
644
  let merged = current ?? newContent;
645
+ const upstreamFm = extractFrontmatter(newContent);
646
+ if (upstreamFm) {
647
+ merged = replaceFrontmatter(merged, upstreamFm);
648
+ }
621
649
  for (const regionId of managedRegions ?? []) {
622
650
  const re = new RegExp(
623
651
  `<!-- teamix-evo:managed:start id="${escapeRegExp(
@@ -700,7 +728,7 @@ async function removeSkillFiles(records) {
700
728
  removed.push(r.target);
701
729
  } catch (err) {
702
730
  if (err.code !== "ENOENT") {
703
- logger.warn(`Failed to remove ${r.target}: ${err.message}`);
731
+ logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
704
732
  }
705
733
  }
706
734
  }
@@ -735,11 +763,14 @@ async function ensureMcpJson(projectRoot) {
735
763
  const mcpPath = path8.join(projectRoot, ".mcp.json");
736
764
  if (await fileExists(mcpPath)) return "exists";
737
765
  try {
738
- await writeFileSafe(mcpPath, JSON.stringify(MCP_JSON_CONTENT, null, 2) + "\n");
766
+ await writeFileSafe(
767
+ mcpPath,
768
+ JSON.stringify(MCP_JSON_CONTENT, null, 2) + "\n"
769
+ );
739
770
  logger.debug(`Wrote .mcp.json \u2192 ${mcpPath}`);
740
771
  return "created";
741
772
  } catch (err) {
742
- logger.warn(`Failed to write .mcp.json: ${err.message}`);
773
+ logger.warn(`Failed to write .mcp.json: ${getErrorMessage(err)}`);
743
774
  return "failed";
744
775
  }
745
776
  }
@@ -912,8 +943,8 @@ async function finalizeSkillsInstall(args) {
912
943
  onlyIds
913
944
  });
914
945
  const config = existingConfig ?? {
915
- $schema: "https://teamix-evo.dev/schema/config/v1.json",
916
- schemaVersion: 1,
946
+ $schema: "https://teamix-evo.dev/schema/config/v2.json",
947
+ schemaVersion: 2,
917
948
  ide: ideIdent,
918
949
  packages: {}
919
950
  };
@@ -996,7 +1027,6 @@ var CONSUMER_OVERRIDES_FILE = "tokens.overrides.css";
996
1027
  var EMPTY_OVERRIDES_TEMPLATE = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
997
1028
  /* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
998
1029
  `;
999
- var require3 = createRequire2(import.meta.url);
1000
1030
  async function runTokensInit(options) {
1001
1031
  const { projectRoot, variant, ide } = options;
1002
1032
  const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE;
@@ -1060,8 +1090,8 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
1060
1090
  JSON.stringify(lock, null, 2) + "\n"
1061
1091
  );
1062
1092
  const config = {
1063
- $schema: "https://teamix-evo.dev/schema/config/v1.json",
1064
- schemaVersion: 1,
1093
+ $schema: "https://teamix-evo.dev/schema/config/v2.json",
1094
+ schemaVersion: 2,
1065
1095
  ide: existingConfig?.ide ?? ide,
1066
1096
  packages: {
1067
1097
  ...existingConfig?.packages ?? {},
@@ -1114,7 +1144,9 @@ async function tryAutoInstallVariantSkills(args) {
1114
1144
  manifestSkillIds = new Set(manifest.skills.map((s) => s.id));
1115
1145
  } catch (err) {
1116
1146
  logger.warn(
1117
- `Skipping skills auto-install: could not load skills manifest (${err.message}).`
1147
+ `Skipping skills auto-install: could not load skills manifest (${getErrorMessage(
1148
+ err
1149
+ )}).`
1118
1150
  );
1119
1151
  return {
1120
1152
  attempted: [],
@@ -1164,7 +1196,7 @@ async function tryAutoInstallVariantSkills(args) {
1164
1196
  };
1165
1197
  } catch (err) {
1166
1198
  logger.warn(
1167
- `Skills auto-install failed (continuing): ${err.message}`
1199
+ `Skills auto-install failed (continuing): ${getErrorMessage(err)}`
1168
1200
  );
1169
1201
  return {
1170
1202
  attempted: desired,
@@ -1178,10 +1210,7 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1178
1210
  const sourceAbs = path9.join(packageRoot, fileRelToPackage);
1179
1211
  const base = path9.basename(fileRelToPackage);
1180
1212
  if (base === "theme.css") {
1181
- const targetRel = path9.posix.join(
1182
- CONSUMER_TOKENS_DIR,
1183
- CONSUMER_THEME_FILE
1184
- );
1213
+ const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
1185
1214
  const targetAbs = path9.join(projectRoot, targetRel);
1186
1215
  const content = await fs6.readFile(sourceAbs, "utf-8");
1187
1216
  await writeFileSafe(targetAbs, content);
@@ -1218,10 +1247,6 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
1218
1247
  }
1219
1248
  return null;
1220
1249
  }
1221
- function resolveTokensPackageRoot(packageName) {
1222
- const pkgJson = require3.resolve(`${packageName}/package.json`);
1223
- return path9.dirname(pkgJson);
1224
- }
1225
1250
  async function listTokenVariants(packageName = DEFAULT_TOKENS_PACKAGE, packageRoot) {
1226
1251
  const root = packageRoot ?? resolveTokensPackageRoot(packageName);
1227
1252
  const catalog = await loadTokensPackageManifest(root);
@@ -1438,8 +1463,8 @@ async function runUiInit(options) {
1438
1463
  const tsx = options.tsx ?? true;
1439
1464
  const rsc = options.rsc ?? false;
1440
1465
  const config = existingConfig ?? {
1441
- $schema: "https://teamix-evo.dev/schema/config/v1.json",
1442
- schemaVersion: 1,
1466
+ $schema: "https://teamix-evo.dev/schema/config/v2.json",
1467
+ schemaVersion: 2,
1443
1468
  ide: ideIdent,
1444
1469
  packages: {}
1445
1470
  };
@@ -1607,7 +1632,7 @@ async function removeUiFiles(records) {
1607
1632
  removed.push(r.target);
1608
1633
  } catch (err) {
1609
1634
  if (err.code !== "ENOENT") {
1610
- logger.warn(`Failed to remove ${r.target}: ${err.message}`);
1635
+ logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
1611
1636
  }
1612
1637
  }
1613
1638
  }
@@ -2009,120 +2034,2147 @@ async function patchPackageJsonScripts(projectRoot) {
2009
2034
  }
2010
2035
  }
2011
2036
 
2012
- // src/core/installer.ts
2013
- import * as path14 from "path";
2037
+ // src/core/agents-md.ts
2014
2038
  import * as fs10 from "fs/promises";
2015
- async function installResources(options) {
2016
- const { projectRoot, manifest, data, variantDir, packageRoot } = options;
2017
- const installedResources = [];
2018
- for (const resource of manifest.resources) {
2019
- logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
2020
- if (resource.recursive) {
2021
- const results = await installRecursiveResource(
2022
- resource,
2023
- projectRoot,
2024
- data,
2025
- variantDir,
2026
- packageRoot
2027
- );
2028
- installedResources.push(...results);
2029
- } else {
2030
- const result = await installSingleResource(
2031
- resource,
2032
- projectRoot,
2033
- data,
2034
- variantDir,
2035
- packageRoot
2039
+ import * as path14 from "path";
2040
+ async function runGenerateAgentsMd(options) {
2041
+ const { projectRoot, variant, skillIds } = options;
2042
+ const ordered = [...skillIds].sort(
2043
+ (a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
2044
+ );
2045
+ const sections = [];
2046
+ const missingSkillIds = [];
2047
+ for (const id of ordered) {
2048
+ const { section, missing } = await renderSkillSection(projectRoot, id);
2049
+ sections.push(section);
2050
+ if (missing) missingSkillIds.push(id);
2051
+ }
2052
+ const body = renderAgentsMd({ variant, sections });
2053
+ const target = path14.join(projectRoot, "AGENTS.md");
2054
+ await fs10.writeFile(target, body, "utf8");
2055
+ return {
2056
+ path: target,
2057
+ skillCount: ordered.length,
2058
+ missingSkillIds
2059
+ };
2060
+ }
2061
+ function bucketRank(id) {
2062
+ if (id.startsWith("teamix-evo-design-")) return 0;
2063
+ if (id.startsWith("teamix-evo-code-")) return 1;
2064
+ return 2;
2065
+ }
2066
+ async function renderSkillSection(projectRoot, skillId) {
2067
+ const skillPath = path14.join(
2068
+ projectRoot,
2069
+ ".teamix-evo",
2070
+ "skills",
2071
+ skillId,
2072
+ "SKILL.md"
2073
+ );
2074
+ const lines = [];
2075
+ lines.push(`### ${skillId}`);
2076
+ let parts = null;
2077
+ let missing = false;
2078
+ try {
2079
+ const raw = await fs10.readFile(skillPath, "utf8");
2080
+ parts = extractDescriptionParts(raw);
2081
+ } catch {
2082
+ missing = true;
2083
+ }
2084
+ if (parts?.capability) {
2085
+ lines.push(`- ${parts.capability}`);
2086
+ }
2087
+ lines.push(
2088
+ `- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
2089
+ );
2090
+ lines.push(
2091
+ `- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
2092
+ );
2093
+ if (parts?.coordinates) {
2094
+ lines.push(`- **Coordinates with**: ${parts.coordinates}`);
2095
+ }
2096
+ lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills/${skillId}/SKILL.md\``);
2097
+ return { section: lines.join("\n"), missing };
2098
+ }
2099
+ function renderAgentsMd(args) {
2100
+ const { variant, sections } = args;
2101
+ const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
2102
+ return `# AGENTS.md
2103
+
2104
+ > \u672C\u5DE5\u7A0B\u5DF2\u88C5\u914D Teamix Evo AI skills\u3002AI \u52A9\u624B\u5728\u4EE5\u4E0B\u573A\u666F\u4E0B**\u5FC5\u987B\u5148\u8BFB\u5BF9\u5E94 skill** \u518D\u52A8\u624B\u3002
2105
+ > \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C[ADR 0038](https://github.com/teamix-evo/teamix-evo/blob/main/docs/adr/0038-create-agents-md-skill-trigger-fallback.md)\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
2106
+
2107
+ ## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
2108
+
2109
+ ${skillBlock}
2110
+
2111
+ ## \u89E6\u53D1\u515C\u5E95\u89C4\u5219
2112
+
2113
+ - \u5199\u65B0 \`.tsx\` / \`.ts\` \u524D\uFF0C\u5BF9\u7167\u4E0A\u8FF0 TRIGGER \u5224\u5B9A\u662F\u5426\u547D\u4E2D
2114
+ - \u547D\u4E2D\u5219\u5148\u8BFB\u5BF9\u5E94 \`SKILL.md\`\uFF0C\u518D\u52A8\u624B\uFF1B\u4E8C\u8005\u540C\u65F6\u547D\u4E2D\u5219\u4E24\u4E2A\u90FD\u8BFB
2115
+ - \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
2116
+ - \u751F\u547D\u5468\u671F\u547D\u4EE4\uFF08\`init\` / \`update\` / \`add\`\uFF09\u8D70 \`teamix-evo-manage\`\uFF08\u5168\u5C40 skill\uFF0C\u672C\u6587\u4EF6\u4E0D\u5217\uFF09
2117
+
2118
+ > \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002
2119
+ `;
2120
+ }
2121
+ function extractDescriptionParts(fileContent) {
2122
+ const description = extractDescriptionBlock(fileContent);
2123
+ if (description == null) return null;
2124
+ const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
2125
+ let capability = "";
2126
+ for (const line of allLines) {
2127
+ if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
2128
+ capability = capability ? `${capability} ${line}` : line;
2129
+ }
2130
+ return {
2131
+ capability: capability.trim(),
2132
+ trigger: extractSection(description, "TRIGGER when:"),
2133
+ skip: extractSection(description, "SKIP:"),
2134
+ coordinates: extractSection(description, "Coordinates with:")
2135
+ };
2136
+ }
2137
+ function extractDescriptionBlock(fileContent) {
2138
+ const lines = fileContent.split("\n");
2139
+ if (lines[0]?.trim() !== "---") return null;
2140
+ let endIdx = -1;
2141
+ for (let i = 1; i < lines.length; i++) {
2142
+ if (lines[i]?.trim() === "---") {
2143
+ endIdx = i;
2144
+ break;
2145
+ }
2146
+ }
2147
+ if (endIdx === -1) return null;
2148
+ const fmLines = lines.slice(1, endIdx);
2149
+ let startIdx = -1;
2150
+ let inlineValue = null;
2151
+ let blockMode = "inline";
2152
+ for (let i = 0; i < fmLines.length; i++) {
2153
+ const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
2154
+ if (m) {
2155
+ startIdx = i;
2156
+ const indicator = (m[1] ?? "").trim();
2157
+ const rest = m[2] ?? "";
2158
+ if (indicator.startsWith("|")) blockMode = "literal";
2159
+ else if (indicator.startsWith(">")) blockMode = "folded";
2160
+ else {
2161
+ blockMode = "inline";
2162
+ inlineValue = rest;
2163
+ }
2164
+ break;
2165
+ }
2166
+ }
2167
+ if (startIdx === -1) return null;
2168
+ if (blockMode === "inline") {
2169
+ return inlineValue ?? "";
2170
+ }
2171
+ const body = [];
2172
+ let blockIndent = -1;
2173
+ for (let i = startIdx + 1; i < fmLines.length; i++) {
2174
+ const line = fmLines[i] ?? "";
2175
+ if (line.trim() === "") {
2176
+ body.push("");
2177
+ continue;
2178
+ }
2179
+ const indentMatch = line.match(/^(\s+)/);
2180
+ const indent = indentMatch ? indentMatch[1].length : 0;
2181
+ if (indent === 0) break;
2182
+ if (blockIndent === -1) blockIndent = indent;
2183
+ if (indent < blockIndent) break;
2184
+ body.push(line.slice(blockIndent));
2185
+ }
2186
+ while (body.length > 0 && body[body.length - 1] === "") body.pop();
2187
+ return body.join("\n");
2188
+ }
2189
+ function extractSection(description, marker) {
2190
+ const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
2191
+ const lines = description.split("\n");
2192
+ let inSection = false;
2193
+ const collected = [];
2194
+ for (const line of lines) {
2195
+ const trimmed = line.trim();
2196
+ const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
2197
+ if (!inSection && startsWithMarker) {
2198
+ inSection = true;
2199
+ collected.push(trimmed.slice(marker.length).trim());
2200
+ continue;
2201
+ }
2202
+ if (inSection) {
2203
+ const hitNextMarker = markers.some(
2204
+ (m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
2036
2205
  );
2037
- installedResources.push(result);
2206
+ if (hitNextMarker) break;
2207
+ if (trimmed === "") {
2208
+ if (collected[collected.length - 1] === "") break;
2209
+ collected.push("");
2210
+ continue;
2211
+ }
2212
+ collected.push(trimmed);
2213
+ }
2214
+ }
2215
+ if (!inSection) return null;
2216
+ const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
2217
+ return joined || null;
2218
+ }
2219
+
2220
+ // src/core/init-detect.ts
2221
+ import * as fs11 from "fs/promises";
2222
+ import * as path15 from "path";
2223
+ var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
2224
+ ".git",
2225
+ ".gitignore",
2226
+ ".gitattributes",
2227
+ ".gitkeep",
2228
+ ".DS_Store",
2229
+ "Thumbs.db",
2230
+ ".idea",
2231
+ ".vscode",
2232
+ ".qoder",
2233
+ ".claude",
2234
+ "README.md",
2235
+ "README",
2236
+ "README.txt",
2237
+ "LICENSE",
2238
+ "LICENSE.md",
2239
+ "LICENSE.txt"
2240
+ ]);
2241
+ async function detectProjectState(cwd) {
2242
+ const absCwd = path15.resolve(cwd);
2243
+ const teamixDir = getTeamixDir(absCwd);
2244
+ const hasTeamixDir = await fileExists(teamixDir);
2245
+ if (hasTeamixDir) {
2246
+ return {
2247
+ state: "teamix-evo-installed",
2248
+ cwd: absCwd,
2249
+ hasTeamixDir: true,
2250
+ hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
2251
+ significantEntries: []
2252
+ };
2253
+ }
2254
+ let entries;
2255
+ try {
2256
+ entries = await fs11.readdir(absCwd);
2257
+ } catch (err) {
2258
+ if (err.code === "ENOENT") {
2259
+ return {
2260
+ state: "empty",
2261
+ cwd: absCwd,
2262
+ hasTeamixDir: false,
2263
+ hasPackageJson: false,
2264
+ significantEntries: []
2265
+ };
2038
2266
  }
2267
+ throw err;
2268
+ }
2269
+ const significant = entries.filter((e) => !IGNORED_TOP_LEVEL.has(e));
2270
+ const hasPackageJson = entries.includes("package.json");
2271
+ if (significant.length === 0) {
2272
+ return {
2273
+ state: "empty",
2274
+ cwd: absCwd,
2275
+ hasTeamixDir: false,
2276
+ hasPackageJson: false,
2277
+ significantEntries: []
2278
+ };
2039
2279
  }
2040
2280
  return {
2041
- resources: installedResources,
2042
- count: installedResources.length
2281
+ state: "non-teamix-evo",
2282
+ cwd: absCwd,
2283
+ hasTeamixDir: false,
2284
+ hasPackageJson,
2285
+ significantEntries: significant.slice(0, 20).sort()
2043
2286
  };
2044
2287
  }
2045
- async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
2046
- const sourcePath = resolveSourcePath(
2047
- resource.source,
2048
- variantDir,
2049
- packageRoot
2050
- );
2051
- const targetPath = path14.join(projectRoot, resource.target);
2052
- let content;
2053
- if (resource.template) {
2054
- const templateContent = await loadTemplateFile(sourcePath);
2055
- content = renderTemplate(templateContent, data);
2056
- } else {
2057
- content = await fs10.readFile(sourcePath, "utf-8");
2288
+
2289
+ // src/core/init-conflicts.ts
2290
+ import * as crypto from "crypto";
2291
+ import * as fs12 from "fs/promises";
2292
+ import * as path16 from "path";
2293
+ var TAILWIND_CONFIG_CANDIDATES = [
2294
+ "tailwind.config.ts",
2295
+ "tailwind.config.js",
2296
+ "tailwind.config.cjs",
2297
+ "tailwind.config.mjs"
2298
+ ];
2299
+ var TOKENS_FILE_CANDIDATES = [
2300
+ "src/design-tokens.css",
2301
+ "src/styles/design-tokens.css",
2302
+ "src/styles/tokens.css"
2303
+ ];
2304
+ var TOKENS_DIR_CANDIDATES = ["tokens"];
2305
+ var INDEX_CSS_CANDIDATES = [
2306
+ "src/index.css",
2307
+ "src/main.css",
2308
+ "src/app.css",
2309
+ "src/styles/index.css",
2310
+ "src/styles/globals.css"
2311
+ ];
2312
+ var SHADCN_FILE_CANDIDATES = ["src/lib/utils.ts"];
2313
+ var SHADCN_DIR_CANDIDATES = ["src/components/ui"];
2314
+ async function isDir(target) {
2315
+ try {
2316
+ const stat5 = await fs12.stat(target);
2317
+ return stat5.isDirectory();
2318
+ } catch {
2319
+ return false;
2058
2320
  }
2059
- await writeFileSafe(targetPath, content);
2060
- const hash = computeHash(content);
2061
- logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
2321
+ }
2322
+ async function dirHasContent(target) {
2323
+ try {
2324
+ const entries = await fs12.readdir(target);
2325
+ return entries.length > 0;
2326
+ } catch {
2327
+ return false;
2328
+ }
2329
+ }
2330
+ function fingerprint(parts) {
2331
+ const hash = crypto.createHash("sha256");
2332
+ for (const p of [...parts].sort()) hash.update(p);
2333
+ return `sha256:${hash.digest("hex").slice(0, 16)}`;
2334
+ }
2335
+ async function readPackageJson(cwd) {
2336
+ const raw = await readFileOrNull(path16.join(cwd, "package.json"));
2337
+ if (raw === null) return null;
2338
+ try {
2339
+ return JSON.parse(raw);
2340
+ } catch {
2341
+ return null;
2342
+ }
2343
+ }
2344
+ function detectTailwindMajor(pkg) {
2345
+ if (!pkg) return null;
2346
+ const v = pkg.dependencies?.tailwindcss ?? pkg.devDependencies?.tailwindcss ?? null;
2347
+ if (!v) return null;
2348
+ const m = /(\d+)/.exec(v);
2349
+ if (!m) return null;
2350
+ const major = Number.parseInt(m[1], 10);
2351
+ if (major === 3) return 3;
2352
+ if (major === 4) return 4;
2353
+ return null;
2354
+ }
2355
+ async function detectAgentsMd(cwd) {
2356
+ const target = path16.join(cwd, "AGENTS.md");
2357
+ const content = await readFileOrNull(target);
2358
+ const exists = content !== null;
2062
2359
  return {
2063
- id: resource.id,
2064
- target: resource.target,
2065
- hash,
2066
- strategy: resource.updateStrategy
2360
+ key: "agents-md",
2361
+ exists,
2362
+ paths: exists ? ["AGENTS.md"] : [],
2363
+ fingerprint: exists ? fingerprint([content]) : void 0,
2364
+ recommendedStrategy: exists ? "merge-managed" : "overwrite",
2365
+ availableStrategies: ["merge-managed", "overwrite", "skip"]
2067
2366
  };
2068
2367
  }
2069
- async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
2070
- const sourcePath = resolveSourcePath(
2071
- resource.source,
2072
- variantDir,
2073
- packageRoot
2368
+ async function detectComponentsJson(cwd) {
2369
+ const target = path16.join(cwd, "components.json");
2370
+ const content = await readFileOrNull(target);
2371
+ const exists = content !== null;
2372
+ return {
2373
+ key: "components-json",
2374
+ exists,
2375
+ paths: exists ? ["components.json"] : [],
2376
+ fingerprint: exists ? fingerprint([content]) : void 0,
2377
+ recommendedStrategy: exists ? "diff-prompt" : "overwrite",
2378
+ availableStrategies: ["diff-prompt", "overwrite", "skip"]
2379
+ };
2380
+ }
2381
+ async function detectTailwindConfig(cwd) {
2382
+ const matched = [];
2383
+ const contents = [];
2384
+ for (const rel2 of TAILWIND_CONFIG_CANDIDATES) {
2385
+ const c = await readFileOrNull(path16.join(cwd, rel2));
2386
+ if (c !== null) {
2387
+ matched.push(rel2);
2388
+ contents.push(c);
2389
+ }
2390
+ }
2391
+ const pkg = await readPackageJson(cwd);
2392
+ const tailwindMajor = detectTailwindMajor(pkg);
2393
+ const exists = matched.length > 0;
2394
+ return {
2395
+ key: "tailwind-config",
2396
+ exists,
2397
+ paths: matched,
2398
+ fingerprint: exists ? fingerprint(contents) : void 0,
2399
+ recommendedStrategy: exists ? "backup-overwrite" : "overwrite",
2400
+ availableStrategies: ["backup-overwrite", "overwrite", "skip"],
2401
+ meta: { tailwindMajor }
2402
+ };
2403
+ }
2404
+ async function detectTokens(cwd) {
2405
+ const matched = [];
2406
+ const contents = [];
2407
+ for (const rel2 of TOKENS_FILE_CANDIDATES) {
2408
+ const c = await readFileOrNull(path16.join(cwd, rel2));
2409
+ if (c !== null) {
2410
+ matched.push(rel2);
2411
+ contents.push(c);
2412
+ }
2413
+ }
2414
+ for (const rel2 of TOKENS_DIR_CANDIDATES) {
2415
+ const abs = path16.join(cwd, rel2);
2416
+ if (await isDir(abs) && await dirHasContent(abs)) {
2417
+ matched.push(`${rel2}/`);
2418
+ }
2419
+ }
2420
+ const exists = matched.length > 0;
2421
+ return {
2422
+ key: "tokens",
2423
+ exists,
2424
+ paths: matched,
2425
+ fingerprint: contents.length > 0 ? fingerprint(contents) : void 0,
2426
+ recommendedStrategy: exists ? "migrate" : "overwrite",
2427
+ availableStrategies: ["migrate", "coexist", "overwrite", "skip"]
2428
+ };
2429
+ }
2430
+ async function detectIndexCss(cwd) {
2431
+ const matched = [];
2432
+ const contents = [];
2433
+ for (const rel2 of INDEX_CSS_CANDIDATES) {
2434
+ const c = await readFileOrNull(path16.join(cwd, rel2));
2435
+ if (c !== null) {
2436
+ matched.push(rel2);
2437
+ contents.push(c);
2438
+ }
2439
+ }
2440
+ const exists = matched.length > 0;
2441
+ return {
2442
+ key: "index-css",
2443
+ exists,
2444
+ paths: matched,
2445
+ fingerprint: exists ? fingerprint(contents) : void 0,
2446
+ recommendedStrategy: exists ? "append" : "overwrite",
2447
+ availableStrategies: ["append", "diff-prompt", "overwrite", "skip"]
2448
+ };
2449
+ }
2450
+ async function detectShadcnSource(cwd) {
2451
+ const matched = [];
2452
+ for (const rel2 of SHADCN_FILE_CANDIDATES) {
2453
+ if (await fileExists(path16.join(cwd, rel2))) matched.push(rel2);
2454
+ }
2455
+ for (const rel2 of SHADCN_DIR_CANDIDATES) {
2456
+ const abs = path16.join(cwd, rel2);
2457
+ if (await isDir(abs) && await dirHasContent(abs)) {
2458
+ matched.push(`${rel2}/`);
2459
+ }
2460
+ }
2461
+ let componentCount = 0;
2462
+ try {
2463
+ const uiDir = path16.join(cwd, "src/components/ui");
2464
+ if (await isDir(uiDir)) {
2465
+ const entries = await fs12.readdir(uiDir);
2466
+ componentCount = entries.filter(
2467
+ (e) => e.endsWith(".tsx") || e.endsWith(".ts")
2468
+ ).length;
2469
+ }
2470
+ } catch {
2471
+ }
2472
+ const exists = matched.length > 0;
2473
+ return {
2474
+ key: "shadcn-source",
2475
+ exists,
2476
+ paths: matched,
2477
+ recommendedStrategy: exists ? "skip-existing" : "overwrite",
2478
+ availableStrategies: ["skip-existing", "per-file-prompt", "overwrite"],
2479
+ meta: { componentCount }
2480
+ };
2481
+ }
2482
+ async function detectConflicts(cwd) {
2483
+ const absCwd = path16.resolve(cwd);
2484
+ const items = await Promise.all([
2485
+ detectAgentsMd(absCwd),
2486
+ detectComponentsJson(absCwd),
2487
+ detectTailwindConfig(absCwd),
2488
+ detectTokens(absCwd),
2489
+ detectIndexCss(absCwd),
2490
+ detectShadcnSource(absCwd)
2491
+ ]);
2492
+ return {
2493
+ cwd: absCwd,
2494
+ items,
2495
+ hasAnyConflict: items.some((i) => i.exists)
2496
+ };
2497
+ }
2498
+
2499
+ // src/core/legacy-tokens-migrate.ts
2500
+ import * as path17 from "path";
2501
+ import * as fs13 from "fs/promises";
2502
+ var CONSUMER_TOKENS_DIR2 = "tokens";
2503
+ var CONSUMER_OVERRIDES_FILE2 = "tokens.overrides.css";
2504
+ var EMPTY_OVERRIDES_TEMPLATE2 = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
2505
+ /* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
2506
+ `;
2507
+ function buildMigrationBanner(legacyRel, isoTimestamp) {
2508
+ return `/* === Migrated from ${legacyRel} on ${isoTimestamp} === */
2509
+ `;
2510
+ }
2511
+ function computeBackupRel(legacyRel, isoTimestamp) {
2512
+ const tsSafe = isoTimestamp.replace(/[:.]/g, "-");
2513
+ return path17.posix.join(
2514
+ ".teamix-evo",
2515
+ ".backups",
2516
+ `${legacyRel}.${tsSafe}.bak`
2074
2517
  );
2075
- const targetDir = path14.join(projectRoot, resource.target);
2076
- const results = [];
2077
- await ensureDir(targetDir);
2078
- const entries = await walkDir(sourcePath);
2079
- for (const entry of entries) {
2080
- const relPath = path14.relative(sourcePath, entry);
2081
- let targetFile = path14.join(targetDir, relPath);
2082
- if (resource.template && targetFile.endsWith(".hbs")) {
2083
- targetFile = targetFile.slice(0, -4);
2518
+ }
2519
+ async function migrateLegacyTokens(options) {
2520
+ const { projectRoot, legacyPaths, dryRun = false } = options;
2521
+ const seen = /* @__PURE__ */ new Set();
2522
+ const uniqueLegacy = [];
2523
+ for (const raw of legacyPaths) {
2524
+ const norm = raw.split(path17.sep).join("/");
2525
+ if (!seen.has(norm)) {
2526
+ seen.add(norm);
2527
+ uniqueLegacy.push(norm);
2084
2528
  }
2085
- let content;
2086
- if (resource.template && entry.endsWith(".hbs")) {
2087
- const templateContent = await loadTemplateFile(entry);
2088
- content = renderTemplate(templateContent, data);
2089
- } else {
2090
- content = await fs10.readFile(entry, "utf-8");
2529
+ }
2530
+ const overridesRel = path17.posix.join(
2531
+ CONSUMER_TOKENS_DIR2,
2532
+ CONSUMER_OVERRIDES_FILE2
2533
+ );
2534
+ const overridesAbs = path17.join(projectRoot, overridesRel);
2535
+ const existingOverrides = await readFileOrNull(overridesAbs);
2536
+ const overridesCreated = existingOverrides === null;
2537
+ const baseContent = existingOverrides === null ? EMPTY_OVERRIDES_TEMPLATE2 : existingOverrides;
2538
+ const migrated = [];
2539
+ const skipped = [];
2540
+ const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString();
2541
+ let appendedPayload = "";
2542
+ for (const legacyRel of uniqueLegacy) {
2543
+ const legacyAbs = path17.join(projectRoot, legacyRel);
2544
+ const content = await readFileOrNull(legacyAbs);
2545
+ if (content === null) {
2546
+ skipped.push({ from: legacyRel, reason: "not-found" });
2547
+ continue;
2091
2548
  }
2092
- await writeFileSafe(targetFile, content);
2093
- const hash = computeHash(content);
2094
- const targetRel = path14.relative(projectRoot, targetFile);
2095
- results.push({
2096
- id: `${resource.id}:${relPath}`,
2097
- target: targetRel,
2098
- hash,
2099
- strategy: resource.updateStrategy
2549
+ if (content.trim() === "") {
2550
+ skipped.push({ from: legacyRel, reason: "empty" });
2551
+ continue;
2552
+ }
2553
+ const banner = buildMigrationBanner(legacyRel, isoTimestamp);
2554
+ const separator = appendedPayload === "" && baseContent.endsWith("\n\n") ? "" : "\n";
2555
+ const block = `${separator}${banner}${content}${content.endsWith("\n") ? "" : "\n"}`;
2556
+ appendedPayload += block;
2557
+ const backupRel = dryRun ? null : computeBackupRel(legacyRel, isoTimestamp);
2558
+ migrated.push({
2559
+ from: legacyRel,
2560
+ backupTo: backupRel,
2561
+ bytesAppended: Buffer.byteLength(block, "utf-8")
2100
2562
  });
2101
- logger.debug(` Written: ${targetRel}`);
2102
2563
  }
2103
- return results;
2564
+ if (dryRun || migrated.length === 0) {
2565
+ return {
2566
+ migrated,
2567
+ skipped,
2568
+ overridesPath: overridesRel,
2569
+ overridesCreated: dryRun ? overridesCreated : false,
2570
+ dryRun
2571
+ };
2572
+ }
2573
+ await ensureDir(path17.dirname(overridesAbs));
2574
+ const merged = baseContent + appendedPayload;
2575
+ const tmp = overridesAbs + ".tmp";
2576
+ await fs13.writeFile(tmp, merged, "utf-8");
2577
+ await fs13.rename(tmp, overridesAbs);
2578
+ for (const entry of migrated) {
2579
+ const legacyAbs = path17.join(projectRoot, entry.from);
2580
+ const backupAbs = path17.join(projectRoot, entry.backupTo);
2581
+ await ensureDir(path17.dirname(backupAbs));
2582
+ const srcContent = await readFileOrNull(legacyAbs);
2583
+ if (srcContent === null) {
2584
+ logger.debug(
2585
+ `legacy-tokens-migrate: source disappeared before backup: ${entry.from}`
2586
+ );
2587
+ continue;
2588
+ }
2589
+ await fs13.writeFile(backupAbs, srcContent, "utf-8");
2590
+ if (await fileExists(legacyAbs)) {
2591
+ await fs13.unlink(legacyAbs);
2592
+ }
2593
+ logger.debug(
2594
+ `legacy-tokens-migrate: ${entry.from} \u2192 ${overridesRel} (backup: ${entry.backupTo})`
2595
+ );
2596
+ }
2597
+ return {
2598
+ migrated,
2599
+ skipped,
2600
+ overridesPath: overridesRel,
2601
+ overridesCreated,
2602
+ dryRun: false
2603
+ };
2104
2604
  }
2105
2605
 
2106
- // src/core/registry-client.ts
2107
- import * as path15 from "path";
2108
- import * as fs11 from "fs/promises";
2109
- import { createRequire as createRequire5 } from "module";
2110
- import { loadVariantManifest } from "@teamix-evo/registry";
2111
- var require6 = createRequire5(import.meta.url);
2112
- function resolvePackageRoot4(packageName) {
2606
+ // src/core/snapshot.ts
2607
+ import * as fs14 from "fs/promises";
2608
+ import * as path18 from "path";
2609
+ var TEAMIX_DIR2 = ".teamix-evo";
2610
+ var SNAPSHOTS_DIR = ".snapshots";
2611
+ var LOGS_DIR = "logs";
2612
+ var META_FILE = "_meta.json";
2613
+ var DEFAULT_KEEP = 5;
2614
+ function isoToFsSafe(iso) {
2615
+ return iso.replace(/[:.]/g, "-");
2616
+ }
2617
+ function fsSafeToIso(safe) {
2618
+ return safe.replace(
2619
+ /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})(Z)$/,
2620
+ "$1T$2:$3:$4.$5$6"
2621
+ );
2622
+ }
2623
+ async function createSnapshot(projectRoot, opts = {}) {
2624
+ const teamixDir = path18.join(projectRoot, TEAMIX_DIR2);
2625
+ try {
2626
+ const stat5 = await fs14.stat(teamixDir);
2627
+ if (!stat5.isDirectory()) return null;
2628
+ } catch (err) {
2629
+ if (err.code === "ENOENT") return null;
2630
+ throw err;
2631
+ }
2632
+ const isoTs = (/* @__PURE__ */ new Date()).toISOString();
2633
+ const ts = isoToFsSafe(isoTs);
2634
+ const snapshotRoot = path18.join(teamixDir, SNAPSHOTS_DIR);
2635
+ const target = path18.join(snapshotRoot, ts);
2636
+ await fs14.mkdir(target, { recursive: true });
2637
+ const entries = await fs14.readdir(teamixDir, { withFileTypes: true });
2638
+ for (const entry of entries) {
2639
+ if (entry.name === SNAPSHOTS_DIR) continue;
2640
+ if (entry.name === LOGS_DIR) continue;
2641
+ const src = path18.join(teamixDir, entry.name);
2642
+ const dst = path18.join(target, entry.name);
2643
+ await fs14.cp(src, dst, { recursive: true });
2644
+ }
2645
+ const meta = {
2646
+ ts: isoTs,
2647
+ reason: opts.reason ?? "manual"
2648
+ };
2649
+ await fs14.writeFile(
2650
+ path18.join(target, META_FILE),
2651
+ JSON.stringify(meta, null, 2) + "\n",
2652
+ "utf-8"
2653
+ );
2654
+ logger.debug(
2655
+ `Snapshot created \u2192 ${path18.relative(projectRoot, target)} (${meta.reason})`
2656
+ );
2657
+ const keep = opts.keep ?? DEFAULT_KEEP;
2658
+ await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
2659
+ return { ts, path: target };
2660
+ }
2661
+ async function listSnapshots(projectRoot) {
2662
+ const snapshotRoot = path18.join(projectRoot, TEAMIX_DIR2, SNAPSHOTS_DIR);
2663
+ let entries;
2664
+ try {
2665
+ entries = await fs14.readdir(snapshotRoot, { withFileTypes: true });
2666
+ } catch (err) {
2667
+ if (err.code === "ENOENT") return [];
2668
+ throw err;
2669
+ }
2670
+ const result = [];
2671
+ for (const entry of entries) {
2672
+ if (!entry.isDirectory()) continue;
2673
+ const dir = path18.join(snapshotRoot, entry.name);
2674
+ let isoTs = null;
2675
+ let reason = null;
2676
+ try {
2677
+ const raw = await fs14.readFile(path18.join(dir, META_FILE), "utf-8");
2678
+ const parsed = JSON.parse(raw);
2679
+ if (typeof parsed.ts === "string") isoTs = parsed.ts;
2680
+ if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
2681
+ parsed.reason
2682
+ )) {
2683
+ reason = parsed.reason;
2684
+ }
2685
+ } catch {
2686
+ isoTs = fsSafeToIso(entry.name);
2687
+ }
2688
+ result.push({ ts: entry.name, isoTs, reason, path: dir });
2689
+ }
2690
+ result.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0);
2691
+ return result;
2692
+ }
2693
+ async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
2694
+ if (keep < 0)
2695
+ throw new Error(`pruneSnapshots: keep must be >= 0, got ${keep}`);
2696
+ const snapshots = await listSnapshots(projectRoot);
2697
+ if (snapshots.length <= keep) return [];
2698
+ const tail = snapshots.slice(keep);
2699
+ const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
2700
+ const removed = [];
2701
+ for (const snap of toRemove) {
2702
+ await fs14.rm(snap.path, { recursive: true, force: true });
2703
+ removed.push(snap.ts);
2704
+ logger.debug(`Pruned snapshot ${snap.ts}`);
2705
+ }
2706
+ return removed.reverse();
2707
+ }
2708
+
2709
+ // src/core/project-init.ts
2710
+ var BASELINE_UI_ENTRIES = [
2711
+ "button",
2712
+ "button-group",
2713
+ "input",
2714
+ "form",
2715
+ "card",
2716
+ "collapsible",
2717
+ "dialog",
2718
+ "dropdown-menu",
2719
+ "tabs",
2720
+ "table",
2721
+ "sidebar",
2722
+ "page-shell",
2723
+ "page-header"
2724
+ ];
2725
+ var CRITICAL_STEPS = /* @__PURE__ */ new Set([
2726
+ "tokens",
2727
+ "skills",
2728
+ "ui-init"
2729
+ ]);
2730
+ var IMPLEMENTED_STRATEGIES = {
2731
+ "agents-md": ["merge-managed", "overwrite", "skip"],
2732
+ tokens: ["migrate", "overwrite", "skip"],
2733
+ "components-json": ["overwrite", "skip"],
2734
+ "shadcn-source": ["overwrite", "skip-existing", "skip"],
2735
+ "tailwind-config": ["skip"],
2736
+ "index-css": ["skip"]
2737
+ };
2738
+ function pickIde(ides) {
2739
+ return ides[0] ?? "qoder";
2740
+ }
2741
+ function strategyImplemented(key, strategy) {
2742
+ return IMPLEMENTED_STRATEGIES[key]?.includes(strategy) ?? false;
2743
+ }
2744
+ async function resolveUiEntries(options) {
2745
+ if (options.uiEntries && options.uiEntries.length > 0) {
2746
+ return [...options.uiEntries];
2747
+ }
2748
+ if (options.answers.uiSelection === "all") {
2749
+ const { manifest } = await loadUiData("@teamix-evo/ui");
2750
+ return manifest.entries.map((e) => e.id);
2751
+ }
2752
+ return [...BASELINE_UI_ENTRIES];
2753
+ }
2754
+ async function runProjectInit(options) {
2755
+ const { projectRoot, answers, dryRun = false, onStep } = options;
2756
+ const ide = pickIde(answers.ides);
2757
+ const steps = [];
2758
+ const pending = [];
2759
+ let snapshot = null;
2760
+ let snapshotError;
2761
+ if (!dryRun) {
2762
+ try {
2763
+ snapshot = await createSnapshot(projectRoot, { reason: "init" });
2764
+ } catch (err) {
2765
+ snapshotError = getErrorMessage(err);
2766
+ }
2767
+ }
2768
+ let aborted = false;
2769
+ const firstFailure = {
2770
+ value: null
2771
+ };
2772
+ function record(step) {
2773
+ steps.push(step);
2774
+ onStep?.(step);
2775
+ }
2776
+ function recordPending(key) {
2777
+ const strategy = answers.conflictDecisions[key];
2778
+ if (!strategy) return;
2779
+ if (strategyImplemented(key, strategy)) return;
2780
+ pending.push({
2781
+ key,
2782
+ strategy,
2783
+ reason: `Strategy "${strategy}" requires the managed-region engine (batch 4); recorded for follow-up.`
2784
+ });
2785
+ }
2786
+ function recordFailure(name, err) {
2787
+ const message = getErrorMessage(err);
2788
+ record({ name, status: "fail", detail: message });
2789
+ if (!firstFailure.value)
2790
+ firstFailure.value = { step: name, error: message };
2791
+ if (CRITICAL_STEPS.has(name)) aborted = true;
2792
+ }
2793
+ const tokensDecision = answers.conflictDecisions.tokens;
2794
+ const legacyTokensPaths = options.legacyTokensPaths ?? [];
2795
+ const wantMigrate = tokensDecision === "migrate" && legacyTokensPaths.length > 0;
2796
+ if (tokensDecision === "skip") {
2797
+ record({
2798
+ name: "tokens",
2799
+ status: "skip",
2800
+ detail: "conflict strategy = skip"
2801
+ });
2802
+ } else if (dryRun) {
2803
+ const planDetail = wantMigrate ? `runTokensInit(variant=${answers.variant}); migrateLegacyTokens(${legacyTokensPaths.length} file${legacyTokensPaths.length === 1 ? "" : "s"})` : `runTokensInit(variant=${answers.variant})`;
2804
+ record({
2805
+ name: "tokens",
2806
+ status: "planned",
2807
+ detail: planDetail
2808
+ });
2809
+ } else {
2810
+ try {
2811
+ const result = await runTokensInit({
2812
+ projectRoot,
2813
+ variant: answers.variant,
2814
+ ide
2815
+ });
2816
+ let detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
2817
+ if (wantMigrate) {
2818
+ try {
2819
+ const m = await migrateLegacyTokens({
2820
+ projectRoot,
2821
+ legacyPaths: legacyTokensPaths
2822
+ });
2823
+ detail += `; migrated ${m.migrated.length}/${legacyTokensPaths.length} legacy file${legacyTokensPaths.length === 1 ? "" : "s"} \u2192 ${m.overridesPath}`;
2824
+ if (m.skipped.length > 0) {
2825
+ detail += ` (skipped ${m.skipped.length})`;
2826
+ }
2827
+ } catch (err) {
2828
+ detail += `; migrate failed: ${getErrorMessage(err)}`;
2829
+ }
2830
+ }
2831
+ record({
2832
+ name: "tokens",
2833
+ status: "ok",
2834
+ detail
2835
+ });
2836
+ } catch (err) {
2837
+ recordFailure("tokens", err);
2838
+ }
2839
+ }
2840
+ recordPending("tokens");
2841
+ const codeSkillId = `teamix-evo-code-${answers.variant}`;
2842
+ if (dryRun) {
2843
+ record({
2844
+ name: "skills",
2845
+ status: "planned",
2846
+ detail: `runSkillsAdd(${codeSkillId})`
2847
+ });
2848
+ } else if (aborted) {
2849
+ record({
2850
+ name: "skills",
2851
+ status: "skip",
2852
+ detail: "aborted: earlier critical step failed"
2853
+ });
2854
+ } else {
2855
+ try {
2856
+ const result = await runSkillsAdd({
2857
+ projectRoot,
2858
+ names: [codeSkillId],
2859
+ ides: answers.ides,
2860
+ scope: answers.scope,
2861
+ ide
2862
+ });
2863
+ record({
2864
+ name: "skills",
2865
+ status: "ok",
2866
+ detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status
2867
+ });
2868
+ } catch (err) {
2869
+ recordFailure("skills", err);
2870
+ }
2871
+ }
2872
+ const agentsMdDecision = answers.conflictDecisions["agents-md"];
2873
+ const agentsMdSkillIds = [
2874
+ `teamix-evo-design-${answers.variant}`,
2875
+ codeSkillId
2876
+ ];
2877
+ if (agentsMdDecision === "skip") {
2878
+ record({
2879
+ name: "agents-md",
2880
+ status: "skip",
2881
+ detail: "conflict strategy = skip"
2882
+ });
2883
+ } else if (dryRun) {
2884
+ record({
2885
+ name: "agents-md",
2886
+ status: "planned",
2887
+ detail: `runGenerateAgentsMd(${agentsMdSkillIds.length} skills)`
2888
+ });
2889
+ } else {
2890
+ try {
2891
+ const result = await runGenerateAgentsMd({
2892
+ projectRoot,
2893
+ variant: answers.variant,
2894
+ skillIds: agentsMdSkillIds
2895
+ });
2896
+ record({
2897
+ name: "agents-md",
2898
+ status: "ok",
2899
+ detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`
2900
+ });
2901
+ } catch (err) {
2902
+ recordFailure("agents-md", err);
2903
+ }
2904
+ }
2905
+ recordPending("agents-md");
2906
+ const componentsJsonDecision = answers.conflictDecisions["components-json"];
2907
+ const shadcnDecision = answers.conflictDecisions["shadcn-source"];
2908
+ const skipUiInit = !answers.withUi || componentsJsonDecision === "skip";
2909
+ if (skipUiInit) {
2910
+ record({
2911
+ name: "ui-init",
2912
+ status: "skip",
2913
+ detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
2914
+ });
2915
+ record({
2916
+ name: "ui-add",
2917
+ status: "skip",
2918
+ detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
2919
+ });
2920
+ } else if (dryRun) {
2921
+ record({
2922
+ name: "ui-init",
2923
+ status: "planned",
2924
+ detail: "runUiInit()"
2925
+ });
2926
+ const entries = await resolveUiEntries(options).catch(() => [
2927
+ ...BASELINE_UI_ENTRIES
2928
+ ]);
2929
+ record({
2930
+ name: "ui-add",
2931
+ status: "planned",
2932
+ detail: `runUiAdd(${entries.length} entries: ${entries.slice(0, 3).join(", ")}${entries.length > 3 ? "\u2026" : ""})`
2933
+ });
2934
+ } else {
2935
+ if (aborted) {
2936
+ record({
2937
+ name: "ui-init",
2938
+ status: "skip",
2939
+ detail: "aborted: earlier critical step failed"
2940
+ });
2941
+ record({
2942
+ name: "ui-add",
2943
+ status: "skip",
2944
+ detail: "aborted: earlier critical step failed"
2945
+ });
2946
+ } else {
2947
+ let uiInitOk = false;
2948
+ try {
2949
+ const initResult = await runUiInit({ projectRoot, ide });
2950
+ record({
2951
+ name: "ui-init",
2952
+ status: "ok",
2953
+ detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
2954
+ });
2955
+ uiInitOk = true;
2956
+ } catch (err) {
2957
+ recordFailure("ui-init", err);
2958
+ }
2959
+ if (!uiInitOk) {
2960
+ record({
2961
+ name: "ui-add",
2962
+ status: "skip",
2963
+ detail: "aborted: ui-init failed"
2964
+ });
2965
+ } else if (shadcnDecision === "skip") {
2966
+ record({
2967
+ name: "ui-add",
2968
+ status: "skip",
2969
+ detail: "shadcn-source conflict strategy = skip"
2970
+ });
2971
+ } else {
2972
+ try {
2973
+ const entries = await resolveUiEntries(options);
2974
+ const addResult = await runUiAdd({
2975
+ projectRoot,
2976
+ ids: entries,
2977
+ // 'overwrite' strategy → overwrite=true; everything else (incl.
2978
+ // 'skip-existing' which is the default) → overwrite=false.
2979
+ overwrite: shadcnDecision === "overwrite"
2980
+ });
2981
+ record({
2982
+ name: "ui-add",
2983
+ status: "ok",
2984
+ detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`
2985
+ });
2986
+ } catch (err) {
2987
+ recordFailure("ui-add", err);
2988
+ }
2989
+ }
2990
+ }
2991
+ }
2992
+ recordPending("components-json");
2993
+ recordPending("shadcn-source");
2994
+ if (!answers.withLint) {
2995
+ record({
2996
+ name: "lint",
2997
+ status: "skip",
2998
+ detail: "withLint = false"
2999
+ });
3000
+ } else if (dryRun) {
3001
+ record({
3002
+ name: "lint",
3003
+ status: "planned",
3004
+ detail: "runLintInit()"
3005
+ });
3006
+ } else {
3007
+ try {
3008
+ const result = await runLintInit({
3009
+ projectRoot,
3010
+ skipInstall: options.skipInstall ?? false
3011
+ });
3012
+ record({
3013
+ name: "lint",
3014
+ status: "ok",
3015
+ detail: result.status === "installed" ? `eslint=${result.eslint}, stylelint=${result.stylelint}` : result.status
3016
+ });
3017
+ } catch (err) {
3018
+ recordFailure("lint", err);
3019
+ }
3020
+ }
3021
+ recordPending("tailwind-config");
3022
+ recordPending("index-css");
3023
+ const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
3024
+ const out = {
3025
+ status,
3026
+ steps,
3027
+ pendingConflictWork: pending,
3028
+ snapshot
3029
+ };
3030
+ if (snapshotError) out.snapshotError = snapshotError;
3031
+ if (firstFailure.value) {
3032
+ out.resumeHint = {
3033
+ failedAt: firstFailure.value.step,
3034
+ completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
3035
+ failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
3036
+ error: firstFailure.value.error,
3037
+ // Every sub-step is idempotent (returns `already-initialized` when its
3038
+ // state file already exists), so a plain re-run resumes from the
3039
+ // failed step. A richer --resume model lands with batch 3 (lock
3040
+ // snapshot + restore).
3041
+ resumeCommand: "teamix-evo init"
3042
+ };
3043
+ }
3044
+ return out;
3045
+ }
3046
+
3047
+ // src/core/project-update.ts
3048
+ import * as path24 from "path";
3049
+ import {
3050
+ loadTokensPackageManifest as loadTokensPackageManifest3,
3051
+ getVariantEntry as getVariantEntry3
3052
+ } from "@teamix-evo/registry";
3053
+
3054
+ // src/core/tokens-update.ts
3055
+ import * as path20 from "path";
3056
+ import * as fs15 from "fs/promises";
3057
+ import {
3058
+ loadTokensPackageManifest as loadTokensPackageManifest2,
3059
+ getVariantEntry as getVariantEntry2
3060
+ } from "@teamix-evo/registry";
3061
+
3062
+ // src/core/upgrade-hints.ts
3063
+ import * as path19 from "path";
3064
+ var TEAMIX_DIR3 = ".teamix-evo";
3065
+ var HINTS_DIR = ".upgrade-hints";
3066
+ function isoToFsSafe2(iso) {
3067
+ return iso.replace(/[:.]/g, "-");
3068
+ }
3069
+ async function writeTokensUpgradeHint(options) {
3070
+ if (options.renames.length === 0) return null;
3071
+ const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3072
+ const fsTs = isoToFsSafe2(isoTs);
3073
+ const filename = `tokens-${fsTs}.json`;
3074
+ const target = path19.join(
3075
+ options.projectRoot,
3076
+ TEAMIX_DIR3,
3077
+ HINTS_DIR,
3078
+ filename
3079
+ );
3080
+ const payload = {
3081
+ schemaVersion: 1,
3082
+ ts: isoTs,
3083
+ package: "tokens",
3084
+ trigger: options.trigger,
3085
+ fromVariant: options.fromVariant,
3086
+ toVariant: options.toVariant,
3087
+ fromVersion: options.fromVersion,
3088
+ toVersion: options.toVersion,
3089
+ renames: options.renames
3090
+ };
3091
+ await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
3092
+ return {
3093
+ path: target,
3094
+ ts: fsTs,
3095
+ renameCount: options.renames.length
3096
+ };
3097
+ }
3098
+ function selectApplicableRenames(renames, fromVersion, toVersion) {
3099
+ return renames.filter(
3100
+ (r) => compareSemver(r.sinceVersion, fromVersion) > 0 && compareSemver(r.sinceVersion, toVersion) <= 0
3101
+ ).sort((a, b) => compareSemver(a.sinceVersion, b.sinceVersion));
3102
+ }
3103
+ function compareSemver(a, b) {
3104
+ const [aMain = "", aRest = ""] = a.split("-", 2);
3105
+ const [bMain = "", bRest = ""] = b.split("-", 2);
3106
+ const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
3107
+ const bParts = bMain.split(".").map((n) => Number.parseInt(n, 10));
3108
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
3109
+ const ai = aParts[i] ?? 0;
3110
+ const bi = bParts[i] ?? 0;
3111
+ if (ai !== bi) return ai - bi;
3112
+ }
3113
+ if (aRest === "" && bRest !== "") return 1;
3114
+ if (aRest !== "" && bRest === "") return -1;
3115
+ return aRest.localeCompare(bRest, void 0, { numeric: true });
3116
+ }
3117
+
3118
+ // src/core/managed-merge.ts
3119
+ import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
3120
+ function mergeManagedRegions(upstreamContent, consumerContent) {
3121
+ let updated = consumerContent;
3122
+ const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
3123
+ let match;
3124
+ while ((match = re.exec(upstreamContent)) !== null) {
3125
+ const id = match[1];
3126
+ const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
3127
+ if (!hasManagedRegion2(updated, id)) {
3128
+ throw new Error(
3129
+ `Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
3130
+ );
3131
+ }
3132
+ updated = replaceManagedRegion2(updated, id, body);
3133
+ }
3134
+ return updated;
3135
+ }
3136
+
3137
+ // src/core/tokens-update.ts
3138
+ var DEFAULT_TOKENS_PACKAGE2 = "@teamix-evo/tokens";
3139
+ var CONSUMER_BASENAME_BY_UPSTREAM = {
3140
+ "theme.css": "tokens.theme.css",
3141
+ "overrides.css": "tokens.overrides.css"
3142
+ };
3143
+ async function runTokensUpdate(options) {
3144
+ const { projectRoot } = options;
3145
+ const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE2;
3146
+ const config = await readProjectConfig(projectRoot);
3147
+ if (!config?.packages?.tokens) {
3148
+ return { status: "not-initialized" };
3149
+ }
3150
+ const currentVariant = config.packages.tokens.variant;
3151
+ const currentVersion = config.packages.tokens.version;
3152
+ const packageRoot = options.packageRoot ?? resolveTokensPackageRoot(packageName);
3153
+ const catalog = await loadTokensPackageManifest2(packageRoot);
3154
+ const variantEntry = getVariantEntry2(catalog, currentVariant);
3155
+ if (!variantEntry) {
3156
+ throw new Error(
3157
+ `Currently installed variant "${currentVariant}" no longer exists in ${packageName}@${catalog.version}. Available: ${catalog.variants.map((v) => v.name).join(", ")}. Run \`npx teamix-evo@latest tokens uninstall\` then \`npx teamix-evo@latest tokens init <variant>\` to switch.`
3158
+ );
3159
+ }
3160
+ const upstreamByBasename = /* @__PURE__ */ new Map();
3161
+ for (const fileRel of variantEntry.files) {
3162
+ upstreamByBasename.set(
3163
+ path20.basename(fileRel),
3164
+ path20.join(packageRoot, fileRel)
3165
+ );
3166
+ }
3167
+ const prior = await readInstalledManifest(projectRoot) ?? {
3168
+ schemaVersion: 1,
3169
+ installed: []
3170
+ };
3171
+ const installedIdx = prior.installed.findIndex(
3172
+ (p) => p.package === packageName
3173
+ );
3174
+ const priorResources = installedIdx >= 0 ? prior.installed[installedIdx].resources : [];
3175
+ const rewritten = [];
3176
+ const managedReplaced = [];
3177
+ const preserved = [];
3178
+ const frozenDrift = [];
3179
+ const refreshedResources = [];
3180
+ for (const resource of priorResources) {
3181
+ const consumerAbs = path20.isAbsolute(resource.target) ? resource.target : path20.join(projectRoot, resource.target);
3182
+ const consumerBasename = path20.basename(resource.target);
3183
+ const upstreamBasename = lookupUpstreamBasename(consumerBasename);
3184
+ const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
3185
+ if (resource.strategy === "regenerable") {
3186
+ if (!upstreamAbs) {
3187
+ refreshedResources.push(resource);
3188
+ continue;
3189
+ }
3190
+ const content = await fs15.readFile(upstreamAbs, "utf-8");
3191
+ await writeFileSafe(consumerAbs, content);
3192
+ rewritten.push(resource.target);
3193
+ refreshedResources.push({
3194
+ ...resource,
3195
+ hash: computeHash(content)
3196
+ });
3197
+ continue;
3198
+ }
3199
+ if (resource.strategy === "managed") {
3200
+ if (!upstreamAbs || !await fileExists(consumerAbs)) {
3201
+ refreshedResources.push(resource);
3202
+ continue;
3203
+ }
3204
+ const upstreamContent = await fs15.readFile(upstreamAbs, "utf-8");
3205
+ const consumerContent = await fs15.readFile(consumerAbs, "utf-8");
3206
+ const merged = mergeManagedRegions(upstreamContent, consumerContent);
3207
+ if (merged !== consumerContent) {
3208
+ await writeFileSafe(consumerAbs, merged);
3209
+ managedReplaced.push(resource.target);
3210
+ }
3211
+ refreshedResources.push({
3212
+ ...resource,
3213
+ hash: computeHash(merged)
3214
+ });
3215
+ continue;
3216
+ }
3217
+ if (await fileExists(consumerAbs)) preserved.push(resource.target);
3218
+ if (upstreamAbs) {
3219
+ const upstreamContent = await fs15.readFile(upstreamAbs, "utf-8");
3220
+ const upstreamHash = computeHash(upstreamContent);
3221
+ if (resource.hash && upstreamHash !== resource.hash) {
3222
+ frozenDrift.push({
3223
+ target: resource.target,
3224
+ reason: "upstream-changed"
3225
+ });
3226
+ }
3227
+ }
3228
+ refreshedResources.push(resource);
3229
+ }
3230
+ if (variantEntry.version === currentVersion) {
3231
+ if (installedIdx >= 0) {
3232
+ prior.installed[installedIdx] = {
3233
+ ...prior.installed[installedIdx],
3234
+ resources: refreshedResources
3235
+ };
3236
+ await writeInstalledManifest(projectRoot, prior);
3237
+ }
3238
+ return {
3239
+ status: "up-to-date",
3240
+ packageName,
3241
+ variant: currentVariant,
3242
+ version: currentVersion,
3243
+ frozenDrift
3244
+ };
3245
+ }
3246
+ const lock = {
3247
+ schemaVersion: 1,
3248
+ variant: {
3249
+ name: variantEntry.name,
3250
+ displayName: variantEntry.displayName,
3251
+ version: variantEntry.version,
3252
+ from: packageName
3253
+ },
3254
+ packageVersion: catalog.version,
3255
+ linked: variantEntry.linked,
3256
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
3257
+ };
3258
+ await writeFileSafe(
3259
+ path20.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
3260
+ JSON.stringify(lock, null, 2) + "\n"
3261
+ );
3262
+ config.packages.tokens.version = variantEntry.version;
3263
+ await writeProjectConfig(projectRoot, config);
3264
+ if (installedIdx >= 0) {
3265
+ prior.installed[installedIdx] = {
3266
+ ...prior.installed[installedIdx],
3267
+ version: variantEntry.version,
3268
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
3269
+ resources: refreshedResources
3270
+ };
3271
+ await writeInstalledManifest(projectRoot, prior);
3272
+ }
3273
+ const renames = selectApplicableRenames(
3274
+ variantEntry.renames ?? [],
3275
+ currentVersion,
3276
+ variantEntry.version
3277
+ );
3278
+ let hintPath;
3279
+ if (renames.length > 0) {
3280
+ const hint = await writeTokensUpgradeHint({
3281
+ projectRoot,
3282
+ trigger: "update",
3283
+ fromVariant: currentVariant,
3284
+ toVariant: currentVariant,
3285
+ fromVersion: currentVersion,
3286
+ toVersion: variantEntry.version,
3287
+ renames
3288
+ });
3289
+ if (hint) hintPath = hint.path;
3290
+ }
3291
+ return {
3292
+ status: "updated",
3293
+ packageName,
3294
+ variant: currentVariant,
3295
+ from: currentVersion,
3296
+ to: variantEntry.version,
3297
+ rewritten,
3298
+ managedReplaced,
3299
+ preserved,
3300
+ frozenDrift,
3301
+ renames,
3302
+ ...hintPath ? { hintPath } : {}
3303
+ };
3304
+ }
3305
+ function lookupUpstreamBasename(consumerBasename) {
3306
+ for (const [upstream, consumer] of Object.entries(
3307
+ CONSUMER_BASENAME_BY_UPSTREAM
3308
+ )) {
3309
+ if (consumer === consumerBasename) return upstream;
3310
+ }
3311
+ return consumerBasename;
3312
+ }
3313
+
3314
+ // src/core/ui-upgrade-detector.ts
3315
+ import * as fs16 from "fs/promises";
3316
+ import * as path21 from "path";
3317
+ var PACKAGE_NAME = {
3318
+ ui: "@teamix-evo/ui",
3319
+ "biz-ui": "@teamix-evo/biz-ui"
3320
+ };
3321
+ var ALIAS_KEY = {
3322
+ ui: "components",
3323
+ "biz-ui": "business"
3324
+ };
3325
+ var COMPONENT_FILE_RE = /\.(tsx|ts)$/;
3326
+ var SKIP_FILENAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx"]);
3327
+ async function detectComponentLineage(options) {
3328
+ const { projectRoot, category } = options;
3329
+ const config = options.config ?? await readProjectConfig(projectRoot);
3330
+ const installed = options.installed ?? await readInstalledManifest(projectRoot);
3331
+ const installDir = resolveInstallDir(category, config);
3332
+ const installDirAbs = path21.join(projectRoot, installDir);
3333
+ const installDirExists = await directoryExists(installDirAbs);
3334
+ const hasComponentsJson = await fileExists(
3335
+ path21.join(projectRoot, "components.json")
3336
+ );
3337
+ const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
3338
+ const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
3339
+ const onDiskIds = installDirExists ? await listComponentIds(installDirAbs) : [];
3340
+ const registeredSet = new Set(registeredIds);
3341
+ const unregisteredIds = onDiskIds.filter((id) => !registeredSet.has(id)).sort();
3342
+ const lineage = classifyLineage({
3343
+ hasInstalled: installedPkg !== null,
3344
+ hasComponentsJson,
3345
+ onDiskIds,
3346
+ unregisteredIds
3347
+ });
3348
+ return {
3349
+ category,
3350
+ lineage,
3351
+ installDir,
3352
+ installDirExists,
3353
+ hasComponentsJson,
3354
+ registeredIds,
3355
+ unregisteredIds,
3356
+ installedVersion: installedPkg?.version ?? null,
3357
+ installedVariant: installedPkg?.variant ?? null
3358
+ };
3359
+ }
3360
+ function resolveInstallDir(category, config) {
3361
+ const aliasMap = config?.packages?.ui?.aliases ?? config?.packages?.["biz-ui"]?.aliases ?? DEFAULT_UI_ALIASES;
3362
+ const key = ALIAS_KEY[category];
3363
+ return aliasMap[key] ?? DEFAULT_UI_ALIASES[key];
3364
+ }
3365
+ function extractIds(pkg) {
3366
+ const ids = /* @__PURE__ */ new Set();
3367
+ for (const r of pkg.resources) {
3368
+ const colon = r.id.indexOf(":");
3369
+ ids.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
3370
+ }
3371
+ return [...ids];
3372
+ }
3373
+ async function directoryExists(p) {
3374
+ try {
3375
+ const stat5 = await fs16.stat(p);
3376
+ return stat5.isDirectory();
3377
+ } catch {
3378
+ return false;
3379
+ }
3380
+ }
3381
+ async function listComponentIds(installDirAbs) {
3382
+ const entries = await fs16.readdir(installDirAbs, { withFileTypes: true });
3383
+ const ids = [];
3384
+ for (const e of entries) {
3385
+ if (!e.isFile()) continue;
3386
+ if (SKIP_FILENAMES.has(e.name)) continue;
3387
+ if (!COMPONENT_FILE_RE.test(e.name)) continue;
3388
+ ids.push(e.name.replace(COMPONENT_FILE_RE, ""));
3389
+ }
3390
+ return ids.sort();
3391
+ }
3392
+ function classifyLineage(args) {
3393
+ const { hasInstalled, hasComponentsJson, onDiskIds, unregisteredIds } = args;
3394
+ if (hasInstalled) {
3395
+ return unregisteredIds.length === 0 ? "teamix-evo" : "mixed";
3396
+ }
3397
+ if (onDiskIds.length === 0) return "absent";
3398
+ return hasComponentsJson ? "shadcn-native" : "custom-only";
3399
+ }
3400
+
3401
+ // src/core/ui-upgrade.ts
3402
+ import * as path23 from "path";
3403
+ import { createRequire as createRequire5 } from "module";
3404
+ import {
3405
+ loadUiPackageManifest as loadUiPackageManifest3,
3406
+ loadVariantUiPackageManifest as loadVariantUiPackageManifest2
3407
+ } from "@teamix-evo/registry";
3408
+
3409
+ // src/core/ui-upgrade-staging.ts
3410
+ import * as path22 from "path";
3411
+ var TEAMIX_DIR4 = ".teamix-evo";
3412
+ var STAGING_DIR = ".upgrade-staging";
3413
+ var PACKAGE_NAME2 = {
3414
+ ui: "@teamix-evo/ui",
3415
+ "biz-ui": "@teamix-evo/biz-ui"
3416
+ };
3417
+ function isoToFsSafe3(iso) {
3418
+ return iso.replace(/[:.]/g, "-");
3419
+ }
3420
+ async function buildUiUpgradeStaging(options) {
3421
+ const { lineageReport, category } = options;
3422
+ if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
3423
+ return null;
3424
+ }
3425
+ const installed = options.installed ?? await readInstalledManifest(options.projectRoot);
3426
+ const installedPkg = findInstalledPackage(installed, PACKAGE_NAME2[category]);
3427
+ if (!installedPkg) return null;
3428
+ const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
3429
+ const fsTs = isoToFsSafe3(isoTs);
3430
+ const stagingDir = path22.join(
3431
+ options.projectRoot,
3432
+ TEAMIX_DIR4,
3433
+ STAGING_DIR,
3434
+ `${category}-${fsTs}`
3435
+ );
3436
+ const entryMap = new Map(
3437
+ options.manifest.entries.map((e) => [e.id, e])
3438
+ );
3439
+ const resByEntryId = collectResourcesByEntry(installedPkg.resources);
3440
+ const onlyIds = options.onlyIds && options.onlyIds.length > 0 ? new Set(options.onlyIds) : null;
3441
+ const entries = [];
3442
+ for (const id of lineageReport.registeredIds) {
3443
+ if (onlyIds && !onlyIds.has(id)) continue;
3444
+ const built = await processRegistered({
3445
+ id,
3446
+ entry: entryMap.get(id),
3447
+ resource: resByEntryId.get(id),
3448
+ packageRoot: options.packageRoot,
3449
+ entryPackageRoot: options.entryPackageRoot,
3450
+ aliases: options.aliases,
3451
+ stagingDir,
3452
+ projectRoot: options.projectRoot,
3453
+ category,
3454
+ sourceVersion: options.manifest.version
3455
+ });
3456
+ if (built) entries.push(built);
3457
+ }
3458
+ for (const id of lineageReport.unregisteredIds) {
3459
+ if (onlyIds && !onlyIds.has(id)) continue;
3460
+ const built = await processForeign({
3461
+ id,
3462
+ installDirAbs: path22.join(options.projectRoot, lineageReport.installDir),
3463
+ stagingDir,
3464
+ projectRoot: options.projectRoot,
3465
+ category
3466
+ });
3467
+ if (built) entries.push(built);
3468
+ }
3469
+ if (entries.length === 0) return null;
3470
+ const byRisk = aggregateByRisk(entries);
3471
+ const manifestOut = {
3472
+ schemaVersion: 1,
3473
+ ts: isoTs,
3474
+ package: category,
3475
+ trigger: options.trigger,
3476
+ variant: lineageReport.installedVariant ?? "_flat",
3477
+ fromVersion: lineageReport.installedVersion ?? "",
3478
+ toVersion: options.manifest.version,
3479
+ lineage: lineageReport.lineage,
3480
+ summary: { total: entries.length, byRisk },
3481
+ entries
3482
+ };
3483
+ await ensureDir(stagingDir);
3484
+ await writeFileSafe(
3485
+ path22.join(stagingDir, "meta.json"),
3486
+ JSON.stringify(manifestOut, null, 2) + "\n"
3487
+ );
3488
+ return { stagingDir, manifest: manifestOut };
3489
+ }
3490
+ async function processRegistered(args) {
3491
+ const { id, entry, resource, stagingDir, projectRoot, category } = args;
3492
+ if (!resource) return null;
3493
+ const currentSource = await readFileOrNull(resource.target);
3494
+ if (currentSource === null) {
3495
+ return buildBreakingEntry({
3496
+ id,
3497
+ category,
3498
+ resource,
3499
+ projectRoot,
3500
+ stagingDir,
3501
+ currentSource: "",
3502
+ hint: "installed file missing on disk"
3503
+ });
3504
+ }
3505
+ if (!entry) {
3506
+ return buildBreakingEntry({
3507
+ id,
3508
+ category,
3509
+ resource,
3510
+ projectRoot,
3511
+ stagingDir,
3512
+ currentSource,
3513
+ hint: "entry removed in upstream package"
3514
+ });
3515
+ }
3516
+ const file = entry.files[0];
3517
+ if (!file) return null;
3518
+ const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
3519
+ const sourceAbs = path22.resolve(rootForEntry, file.source);
3520
+ const raw = await readFileOrNull(sourceAbs);
3521
+ if (raw === null) {
3522
+ return null;
3523
+ }
3524
+ const incomingTransformed = rewriteImports(raw, args.aliases);
3525
+ const incomingHash = computeHash(incomingTransformed);
3526
+ const currentExt = path22.extname(resource.target) || ".tsx";
3527
+ const incomingExt = path22.extname(file.targetName) || currentExt;
3528
+ const currentRel = `${id}/current${currentExt}`;
3529
+ const incomingRel = `${id}/incoming${incomingExt}`;
3530
+ await writeFileSafe(path22.join(stagingDir, currentRel), currentSource);
3531
+ await writeFileSafe(path22.join(stagingDir, incomingRel), incomingTransformed);
3532
+ const diff = classifyRisk({
3533
+ currentHash: resource.hash,
3534
+ incomingHash,
3535
+ currentSource,
3536
+ incomingSource: incomingTransformed,
3537
+ multiFile: entry.files.length > 1
3538
+ });
3539
+ return {
3540
+ id,
3541
+ category,
3542
+ current: {
3543
+ target: path22.relative(projectRoot, resource.target),
3544
+ hash: resource.hash,
3545
+ sourceLineage: "teamix-evo"
3546
+ },
3547
+ incoming: {
3548
+ sourceVersion: args.sourceVersion,
3549
+ hash: incomingHash,
3550
+ relPath: incomingRel
3551
+ },
3552
+ diff
3553
+ };
3554
+ }
3555
+ async function processForeign(args) {
3556
+ const { id, installDirAbs, stagingDir, projectRoot, category } = args;
3557
+ const tsx = path22.join(installDirAbs, `${id}.tsx`);
3558
+ const ts = path22.join(installDirAbs, `${id}.ts`);
3559
+ const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
3560
+ if (!target) return null;
3561
+ const raw = await readFileOrNull(target);
3562
+ if (raw === null) return null;
3563
+ const ext = path22.extname(target);
3564
+ const currentRel = `${id}/current${ext}`;
3565
+ await writeFileSafe(path22.join(stagingDir, currentRel), raw);
3566
+ return {
3567
+ id,
3568
+ category,
3569
+ current: {
3570
+ target: path22.relative(projectRoot, target),
3571
+ hash: computeHash(raw),
3572
+ sourceLineage: "custom"
3573
+ },
3574
+ diff: {
3575
+ riskLevel: "foreign",
3576
+ hints: [
3577
+ "component is on disk but not registered in .teamix-evo/manifest.json",
3578
+ "AI should propose: (a) ignore, (b) re-register via teamix-evo ui add, or (c) remove"
3579
+ ],
3580
+ filesChangedCount: 0
3581
+ }
3582
+ };
3583
+ }
3584
+ async function buildBreakingEntry(args) {
3585
+ const ext = path22.extname(args.resource.target) || ".tsx";
3586
+ const currentRel = `${args.id}/current${ext}`;
3587
+ await writeFileSafe(
3588
+ path22.join(args.stagingDir, currentRel),
3589
+ args.currentSource
3590
+ );
3591
+ return {
3592
+ id: args.id,
3593
+ category: args.category,
3594
+ current: {
3595
+ target: path22.relative(args.projectRoot, args.resource.target),
3596
+ hash: args.resource.hash,
3597
+ sourceLineage: "teamix-evo"
3598
+ },
3599
+ diff: {
3600
+ riskLevel: "breaking",
3601
+ hints: [args.hint],
3602
+ filesChangedCount: 0
3603
+ }
3604
+ };
3605
+ }
3606
+ function classifyRisk(args) {
3607
+ if (args.currentHash === args.incomingHash) {
3608
+ return { riskLevel: "unchanged", hints: [], filesChangedCount: 0 };
3609
+ }
3610
+ const curExports = extractExportNames(args.currentSource);
3611
+ const newExports = extractExportNames(args.incomingSource);
3612
+ const removedExports = setDiff(curExports, newExports);
3613
+ const addedExports = setDiff(newExports, curExports);
3614
+ const curVariants = extractCvaVariantValues(args.currentSource);
3615
+ const newVariants = extractCvaVariantValues(args.incomingSource);
3616
+ const removedVariants = setDiff(curVariants, newVariants);
3617
+ const addedVariants = setDiff(newVariants, curVariants);
3618
+ const hints = [];
3619
+ for (const e of removedExports) hints.push(`removed export: ${e}`);
3620
+ for (const e of addedExports) hints.push(`new export: ${e}`);
3621
+ for (const v of removedVariants) hints.push(`removed cva variant: ${v}`);
3622
+ for (const v of addedVariants) hints.push(`new cva variant: ${v}`);
3623
+ if (args.multiFile) hints.push("multi-file entry; only first file staged");
3624
+ let riskLevel;
3625
+ if (removedExports.length > 0 || removedVariants.length > 0) {
3626
+ riskLevel = "risky";
3627
+ } else if (addedExports.length > 0 || addedVariants.length > 0 || args.multiFile) {
3628
+ riskLevel = "upgradable-medium";
3629
+ } else {
3630
+ riskLevel = "upgradable-low";
3631
+ }
3632
+ return { riskLevel, hints, filesChangedCount: 1 };
3633
+ }
3634
+ function extractExportNames(src) {
3635
+ const names = /* @__PURE__ */ new Set();
3636
+ const re = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/gm;
3637
+ let m;
3638
+ while ((m = re.exec(src)) !== null) {
3639
+ if (m[1]) names.add(m[1]);
3640
+ }
3641
+ for (const dm of src.matchAll(/^\s*export\s+default\s+(\w+)\s*;/gm)) {
3642
+ if (dm[1]) names.add(dm[1]);
3643
+ }
3644
+ return [...names];
3645
+ }
3646
+ function extractCvaVariantValues(src) {
3647
+ const block = extractVariantsBlock(src);
3648
+ if (block === null) return [];
3649
+ const names = /* @__PURE__ */ new Set();
3650
+ for (const groupBody of extractGroupBodies(block)) {
3651
+ for (const km of groupBody.matchAll(/^\s*(?:['"]?)(\w+)(?:['"]?)\s*:/gm)) {
3652
+ if (km[1]) names.add(km[1]);
3653
+ }
3654
+ }
3655
+ return [...names];
3656
+ }
3657
+ function extractVariantsBlock(src) {
3658
+ const idx = src.search(/\bvariants\s*:\s*\{/);
3659
+ if (idx < 0) return null;
3660
+ const open = src.indexOf("{", idx);
3661
+ if (open < 0) return null;
3662
+ let depth = 0;
3663
+ for (let i = open; i < src.length; i++) {
3664
+ const c = src[i];
3665
+ if (c === "{") depth++;
3666
+ else if (c === "}") {
3667
+ depth--;
3668
+ if (depth === 0) return src.slice(open + 1, i);
3669
+ }
3670
+ }
3671
+ return null;
3672
+ }
3673
+ function* extractGroupBodies(block) {
3674
+ const re = /(\w+)\s*:\s*\{/g;
3675
+ let m;
3676
+ while ((m = re.exec(block)) !== null) {
3677
+ const open = block.indexOf("{", m.index);
3678
+ if (open < 0) continue;
3679
+ let depth = 0;
3680
+ for (let i = open; i < block.length; i++) {
3681
+ const c = block[i];
3682
+ if (c === "{") depth++;
3683
+ else if (c === "}") {
3684
+ depth--;
3685
+ if (depth === 0) {
3686
+ yield block.slice(open + 1, i);
3687
+ re.lastIndex = i + 1;
3688
+ break;
3689
+ }
3690
+ }
3691
+ }
3692
+ }
3693
+ }
3694
+ function setDiff(a, b) {
3695
+ const bset = new Set(b);
3696
+ return a.filter((x) => !bset.has(x)).sort();
3697
+ }
3698
+ function collectResourcesByEntry(resources) {
3699
+ const out = /* @__PURE__ */ new Map();
3700
+ for (const r of resources) {
3701
+ const colon = r.id.indexOf(":");
3702
+ const eid = colon >= 0 ? r.id.slice(0, colon) : r.id;
3703
+ if (!out.has(eid)) out.set(eid, r);
3704
+ }
3705
+ return out;
3706
+ }
3707
+ function aggregateByRisk(entries) {
3708
+ const out = {};
3709
+ for (const e of entries) {
3710
+ const k = e.diff.riskLevel;
3711
+ out[k] = (out[k] ?? 0) + 1;
3712
+ }
3713
+ return out;
3714
+ }
3715
+
3716
+ // src/core/ui-upgrade.ts
3717
+ var nodeRequire = createRequire5(import.meta.url);
3718
+ function resolvePackageRoot4(packageName) {
3719
+ const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
3720
+ return path23.dirname(pkgJsonPath);
3721
+ }
3722
+ async function buildStaging(args) {
3723
+ const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
3724
+ if (category === "ui") {
3725
+ const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
3726
+ const manifest = await loadUiPackageManifest3(root);
3727
+ return buildUiUpgradeStaging({
3728
+ projectRoot,
3729
+ category,
3730
+ manifest,
3731
+ packageRoot: root,
3732
+ aliases,
3733
+ lineageReport,
3734
+ trigger,
3735
+ onlyIds
3736
+ });
3737
+ }
3738
+ const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
3739
+ const variant = lineageReport.installedVariant ?? "_flat";
3740
+ const variantDir = path23.join(bizRoot, "variants", variant);
3741
+ const variantManifest = await loadVariantUiPackageManifest2(variantDir);
3742
+ const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
3743
+ const uiManifest = await loadUiPackageManifest3(uiRoot);
3744
+ const entryPackageRoot = /* @__PURE__ */ new Map();
3745
+ const merged = [];
3746
+ for (const e of variantManifest.entries) {
3747
+ entryPackageRoot.set(e.id, variantDir);
3748
+ merged.push(e);
3749
+ }
3750
+ for (const e of uiManifest.entries) {
3751
+ if (entryPackageRoot.has(e.id)) continue;
3752
+ entryPackageRoot.set(e.id, uiRoot);
3753
+ merged.push(e);
3754
+ }
3755
+ const synthetic = {
3756
+ schemaVersion: 1,
3757
+ package: "ui",
3758
+ version: variantManifest.version,
3759
+ engines: variantManifest.engines,
3760
+ entries: merged
3761
+ };
3762
+ return buildUiUpgradeStaging({
3763
+ projectRoot,
3764
+ category,
3765
+ manifest: synthetic,
3766
+ packageRoot: variantDir,
3767
+ entryPackageRoot,
3768
+ aliases,
3769
+ lineageReport,
3770
+ trigger,
3771
+ onlyIds
3772
+ });
3773
+ }
3774
+
3775
+ // src/core/project-update.ts
3776
+ var DEFAULT_TOKENS_PACKAGE3 = "@teamix-evo/tokens";
3777
+ var DEFAULT_SKILLS_PACKAGE4 = "@teamix-evo/skills";
3778
+ var CRITICAL_STEPS2 = /* @__PURE__ */ new Set(["tokens"]);
3779
+ async function runProjectUpdate(options) {
3780
+ const { projectRoot, dryRun = false, onStep } = options;
3781
+ const tokensPackage = options.tokensPackageName ?? DEFAULT_TOKENS_PACKAGE3;
3782
+ const skillsPackage = options.skillsPackageName ?? DEFAULT_SKILLS_PACKAGE4;
3783
+ const config = await readProjectConfig(projectRoot);
3784
+ if (!config) {
3785
+ return { status: "not-initialized" };
3786
+ }
3787
+ let snapshot = null;
3788
+ let snapshotError;
3789
+ if (!dryRun) {
3790
+ try {
3791
+ snapshot = await createSnapshot(projectRoot, { reason: "update" });
3792
+ } catch (err) {
3793
+ snapshotError = getErrorMessage(err);
3794
+ }
3795
+ }
3796
+ const steps = [];
3797
+ let aborted = false;
3798
+ const firstFailure = { value: null };
3799
+ function record(step) {
3800
+ steps.push(step);
3801
+ onStep?.(step);
3802
+ }
3803
+ function recordFailure(name, err) {
3804
+ const message = getErrorMessage(err);
3805
+ record({ name, status: "fail", detail: message });
3806
+ if (!firstFailure.value) {
3807
+ firstFailure.value = { step: name, error: message };
3808
+ }
3809
+ if (CRITICAL_STEPS2.has(name)) aborted = true;
3810
+ }
3811
+ let anyChanged = false;
3812
+ if (!config.packages?.tokens) {
3813
+ record({
3814
+ name: "tokens",
3815
+ status: "skip",
3816
+ detail: "tokens not installed"
3817
+ });
3818
+ } else if (dryRun) {
3819
+ try {
3820
+ const plan = await planTokensUpdate(tokensPackage, config);
3821
+ record({ name: "tokens", status: "planned", detail: plan });
3822
+ } catch (err) {
3823
+ recordFailure("tokens", err);
3824
+ }
3825
+ } else {
3826
+ try {
3827
+ const result = await runTokensUpdate({
3828
+ projectRoot,
3829
+ packageName: tokensPackage
3830
+ });
3831
+ if (result.status === "not-initialized") {
3832
+ record({
3833
+ name: "tokens",
3834
+ status: "skip",
3835
+ detail: "tokens not installed"
3836
+ });
3837
+ } else if (result.status === "up-to-date") {
3838
+ const driftSuffix = result.frozenDrift.length > 0 ? `, frozen drift: ${result.frozenDrift.length}` : "";
3839
+ record({
3840
+ name: "tokens",
3841
+ status: "ok",
3842
+ detail: `up-to-date (${result.variant} v${result.version}${driftSuffix})`
3843
+ });
3844
+ } else {
3845
+ anyChanged = true;
3846
+ const extras = [];
3847
+ if (result.managedReplaced.length > 0)
3848
+ extras.push(`managed: ${result.managedReplaced.length}`);
3849
+ if (result.frozenDrift.length > 0)
3850
+ extras.push(`frozen drift: ${result.frozenDrift.length}`);
3851
+ const suffix = extras.length > 0 ? ` [${extras.join(", ")}]` : "";
3852
+ record({
3853
+ name: "tokens",
3854
+ status: "ok",
3855
+ detail: `${result.variant} v${result.from} \u2192 v${result.to}${suffix}`
3856
+ });
3857
+ }
3858
+ } catch (err) {
3859
+ recordFailure("tokens", err);
3860
+ }
3861
+ }
3862
+ if (!config.packages?.skills) {
3863
+ record({
3864
+ name: "skills",
3865
+ status: "skip",
3866
+ detail: "skills not installed"
3867
+ });
3868
+ } else if (aborted) {
3869
+ record({
3870
+ name: "skills",
3871
+ status: "skip",
3872
+ detail: "aborted: earlier critical step failed"
3873
+ });
3874
+ } else {
3875
+ try {
3876
+ const result = await runSkillsUpdate({
3877
+ projectRoot,
3878
+ dryRun,
3879
+ packageName: skillsPackage
3880
+ });
3881
+ if (result.status === "no-skills") {
3882
+ record({
3883
+ name: "skills",
3884
+ status: "skip",
3885
+ detail: "no skills locked"
3886
+ });
3887
+ } else if (result.status === "no-changes") {
3888
+ record({
3889
+ name: "skills",
3890
+ status: "ok",
3891
+ detail: `up-to-date (${result.checkedSkillIds.length} skill(s) at v${result.version})`
3892
+ });
3893
+ } else if (result.status === "dry-run") {
3894
+ const bumps = result.plan.filter((p) => p.action === "version-bump");
3895
+ const detail = bumps.length === 0 ? `up-to-date (${result.plan.length} skill(s) checked at v${result.currentVersion})` : `${bumps.length} skill(s) would update: ${result.currentVersion} \u2192 ${result.availableVersion}`;
3896
+ record({ name: "skills", status: "planned", detail });
3897
+ } else {
3898
+ anyChanged = true;
3899
+ const summary = result.updatedSkillIds.length > 0 ? `updated: ${result.updatedSkillIds.join(", ")} (v${result.version})` : `refreshed at v${result.version}`;
3900
+ record({ name: "skills", status: "ok", detail: summary });
3901
+ }
3902
+ } catch (err) {
3903
+ recordFailure("skills", err);
3904
+ }
3905
+ }
3906
+ await runComponentSourceStep("ui", {
3907
+ projectRoot,
3908
+ config,
3909
+ dryRun,
3910
+ record,
3911
+ recordFailure
3912
+ });
3913
+ await runComponentSourceStep("biz-ui", {
3914
+ projectRoot,
3915
+ config,
3916
+ dryRun,
3917
+ record,
3918
+ recordFailure
3919
+ });
3920
+ const out = (() => {
3921
+ if (firstFailure.value) {
3922
+ return {
3923
+ status: "partial",
3924
+ steps,
3925
+ resumeHint: {
3926
+ failedAt: firstFailure.value.step,
3927
+ completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
3928
+ failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
3929
+ error: firstFailure.value.error,
3930
+ resumeCommand: "teamix-evo update"
3931
+ },
3932
+ snapshot,
3933
+ ...snapshotError ? { snapshotError } : {}
3934
+ };
3935
+ }
3936
+ if (dryRun) return { status: "dry-run", steps, snapshot: null };
3937
+ if (anyChanged)
3938
+ return {
3939
+ status: "updated",
3940
+ steps,
3941
+ snapshot,
3942
+ ...snapshotError ? { snapshotError } : {}
3943
+ };
3944
+ return {
3945
+ status: "up-to-date",
3946
+ steps,
3947
+ snapshot,
3948
+ ...snapshotError ? { snapshotError } : {}
3949
+ };
3950
+ })();
3951
+ return out;
3952
+ }
3953
+ async function runComponentSourceStep(category, args) {
3954
+ const { projectRoot, config, dryRun, record, recordFailure } = args;
3955
+ const cfgKey = category === "ui" ? "ui" : "biz-ui";
3956
+ if (!config.packages?.[cfgKey]) {
3957
+ record({
3958
+ name: category,
3959
+ status: "skip",
3960
+ detail: `${category} not installed`
3961
+ });
3962
+ return;
3963
+ }
3964
+ const aliases = config.packages.ui?.aliases ?? config.packages["biz-ui"]?.aliases;
3965
+ if (!aliases) {
3966
+ record({
3967
+ name: category,
3968
+ status: "skip",
3969
+ detail: `${category} aliases not configured`
3970
+ });
3971
+ return;
3972
+ }
3973
+ let report;
3974
+ try {
3975
+ report = await detectComponentLineage({
3976
+ projectRoot,
3977
+ category,
3978
+ config
3979
+ });
3980
+ } catch (err) {
3981
+ record({
3982
+ name: category,
3983
+ status: "skip",
3984
+ detail: `lineage detect failed: ${getErrorMessage(err)}`
3985
+ });
3986
+ return;
3987
+ }
3988
+ if (report.lineage !== "teamix-evo" && report.lineage !== "mixed") {
3989
+ record({
3990
+ name: category,
3991
+ status: "skip",
3992
+ detail: `lineage=${report.lineage}; nothing to stage`
3993
+ });
3994
+ return;
3995
+ }
3996
+ if (dryRun) {
3997
+ const total = report.registeredIds.length + report.unregisteredIds.length;
3998
+ record({
3999
+ name: category,
4000
+ status: "planned",
4001
+ detail: total === 0 ? "no components to stage" : `${total} component(s) to stage (lineage=${report.lineage})`
4002
+ });
4003
+ return;
4004
+ }
4005
+ try {
4006
+ const built = await buildStaging({
4007
+ category,
4008
+ projectRoot,
4009
+ aliases,
4010
+ lineageReport: report,
4011
+ trigger: "update",
4012
+ onlyIds: []
4013
+ });
4014
+ if (built === null) {
4015
+ record({
4016
+ name: category,
4017
+ status: "skip",
4018
+ detail: "no entries to stage"
4019
+ });
4020
+ return;
4021
+ }
4022
+ const stagingRel = path24.relative(projectRoot, built.stagingDir);
4023
+ const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
4024
+ record({
4025
+ name: category,
4026
+ status: "ok",
4027
+ detail: `${built.manifest.summary.total} component(s) staged${summary ? ` (${summary})` : ""}, see ${stagingRel}`
4028
+ });
4029
+ } catch (err) {
4030
+ recordFailure(category, err);
4031
+ }
4032
+ }
4033
+ function summarizeStagingRisk(byRisk) {
4034
+ const order = [
4035
+ "risky",
4036
+ "breaking",
4037
+ "foreign",
4038
+ "upgradable-medium",
4039
+ "upgradable-low",
4040
+ "unchanged"
4041
+ ];
4042
+ const parts = [];
4043
+ for (const k of order) {
4044
+ const v = byRisk[k];
4045
+ if (v && v > 0) parts.push(`${v} ${k}`);
4046
+ }
4047
+ return parts.join(", ");
4048
+ }
4049
+ async function planTokensUpdate(tokensPackage, config) {
4050
+ const tokensCfg = config.packages?.tokens;
4051
+ if (!tokensCfg) return "tokens not installed";
4052
+ const packageRoot = resolveTokensPackageRoot(tokensPackage);
4053
+ const catalog = await loadTokensPackageManifest3(packageRoot);
4054
+ const variantEntry = getVariantEntry3(catalog, tokensCfg.variant);
4055
+ if (!variantEntry) {
4056
+ return `variant "${tokensCfg.variant}" no longer in ${tokensPackage}@${catalog.version} \u2014 uninstall + re-init to switch`;
4057
+ }
4058
+ if (variantEntry.version === tokensCfg.version) {
4059
+ return `up-to-date (${tokensCfg.variant} v${tokensCfg.version})`;
4060
+ }
4061
+ return `${tokensCfg.variant} v${tokensCfg.version} \u2192 v${variantEntry.version}`;
4062
+ }
4063
+
4064
+ // src/core/installer.ts
4065
+ import * as path25 from "path";
4066
+ import * as fs17 from "fs/promises";
4067
+ async function installResources(options) {
4068
+ const { projectRoot, manifest, data, variantDir, packageRoot } = options;
4069
+ const installedResources = [];
4070
+ for (const resource of manifest.resources) {
4071
+ logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
4072
+ if (resource.recursive) {
4073
+ const results = await installRecursiveResource(
4074
+ resource,
4075
+ projectRoot,
4076
+ data,
4077
+ variantDir,
4078
+ packageRoot
4079
+ );
4080
+ installedResources.push(...results);
4081
+ } else {
4082
+ const result = await installSingleResource(
4083
+ resource,
4084
+ projectRoot,
4085
+ data,
4086
+ variantDir,
4087
+ packageRoot
4088
+ );
4089
+ installedResources.push(result);
4090
+ }
4091
+ }
4092
+ return {
4093
+ resources: installedResources,
4094
+ count: installedResources.length
4095
+ };
4096
+ }
4097
+ async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
4098
+ const sourcePath = resolveSourcePath(
4099
+ resource.source,
4100
+ variantDir,
4101
+ packageRoot
4102
+ );
4103
+ const targetPath = path25.join(projectRoot, resource.target);
4104
+ let content;
4105
+ if (resource.template) {
4106
+ const templateContent = await loadTemplateFile(sourcePath);
4107
+ content = renderTemplate(templateContent, data);
4108
+ } else {
4109
+ content = await fs17.readFile(sourcePath, "utf-8");
4110
+ }
4111
+ await writeFileSafe(targetPath, content);
4112
+ const hash = computeHash(content);
4113
+ logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
4114
+ return {
4115
+ id: resource.id,
4116
+ target: resource.target,
4117
+ hash,
4118
+ strategy: resource.updateStrategy
4119
+ };
4120
+ }
4121
+ async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
4122
+ const sourcePath = resolveSourcePath(
4123
+ resource.source,
4124
+ variantDir,
4125
+ packageRoot
4126
+ );
4127
+ const targetDir = path25.join(projectRoot, resource.target);
4128
+ const results = [];
4129
+ await ensureDir(targetDir);
4130
+ const entries = await walkDir(sourcePath);
4131
+ for (const entry of entries) {
4132
+ const relPath = path25.relative(sourcePath, entry);
4133
+ let targetFile = path25.join(targetDir, relPath);
4134
+ if (resource.template && targetFile.endsWith(".hbs")) {
4135
+ targetFile = targetFile.slice(0, -4);
4136
+ }
4137
+ let content;
4138
+ if (resource.template && entry.endsWith(".hbs")) {
4139
+ const templateContent = await loadTemplateFile(entry);
4140
+ content = renderTemplate(templateContent, data);
4141
+ } else {
4142
+ content = await fs17.readFile(entry, "utf-8");
4143
+ }
4144
+ await writeFileSafe(targetFile, content);
4145
+ const hash = computeHash(content);
4146
+ const targetRel = path25.relative(projectRoot, targetFile);
4147
+ results.push({
4148
+ id: `${resource.id}:${relPath}`,
4149
+ target: targetRel,
4150
+ hash,
4151
+ strategy: resource.updateStrategy
4152
+ });
4153
+ logger.debug(` Written: ${targetRel}`);
4154
+ }
4155
+ return results;
4156
+ }
4157
+
4158
+ // src/core/registry-client.ts
4159
+ import * as path26 from "path";
4160
+ import * as fs18 from "fs/promises";
4161
+ import { createRequire as createRequire6 } from "module";
4162
+ import { loadVariantManifest } from "@teamix-evo/registry";
4163
+ var require6 = createRequire6(import.meta.url);
4164
+ function resolvePackageRoot5(packageName) {
2113
4165
  const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
2114
- return path15.dirname(pkgJsonPath);
4166
+ return path26.dirname(pkgJsonPath);
2115
4167
  }
2116
4168
  async function loadVariantData(packageName, variant) {
2117
- const packageRoot = resolvePackageRoot4(packageName);
2118
- const variantDir = path15.join(packageRoot, "library", variant);
4169
+ const packageRoot = resolvePackageRoot5(packageName);
4170
+ const variantDir = path26.join(packageRoot, "library", variant);
2119
4171
  logger.debug(`Resolved variant dir: ${variantDir}`);
2120
4172
  logger.debug(`Package root: ${packageRoot}`);
2121
4173
  const manifest = await loadVariantManifest(variantDir);
2122
4174
  let data = {};
2123
- const dataPath = path15.join(variantDir, "_data.json");
4175
+ const dataPath = path26.join(variantDir, "_data.json");
2124
4176
  try {
2125
- const raw = await fs11.readFile(dataPath, "utf-8");
4177
+ const raw = await fs18.readFile(dataPath, "utf-8");
2126
4178
  data = JSON.parse(raw);
2127
4179
  } catch (err) {
2128
4180
  if (err.code !== "ENOENT") {
@@ -2135,7 +4187,10 @@ async function loadVariantData(packageName, variant) {
2135
4187
  export {
2136
4188
  DEFAULT_UI_ALIASES,
2137
4189
  DEFAULT_UI_ICON_LIBRARY,
4190
+ detectConflicts,
4191
+ detectProjectState,
2138
4192
  ensureTeamixDir,
4193
+ extractDescriptionParts,
2139
4194
  getTeamixDir,
2140
4195
  installResources,
2141
4196
  installSkills,
@@ -2153,7 +4208,10 @@ export {
2153
4208
  removeSkillFiles,
2154
4209
  removeUiFiles,
2155
4210
  runBizUiAdd,
4211
+ runGenerateAgentsMd,
2156
4212
  runLintInit,
4213
+ runProjectInit,
4214
+ runProjectUpdate,
2157
4215
  runSkillsAdd,
2158
4216
  runSkillsInit,
2159
4217
  runSkillsUpdate,