teamix-evo 0.8.0 → 0.9.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.
- package/README.md +2 -0
- package/dist/core/index.d.ts +155 -10
- package/dist/core/index.js +808 -105
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +2431 -336
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/core/index.js
CHANGED
|
@@ -113,7 +113,8 @@ var TEAMIX_DIR = ".teamix-evo";
|
|
|
113
113
|
var CONFIG_FILE = "config.json";
|
|
114
114
|
var MANIFEST_FILE = "manifest.json";
|
|
115
115
|
var TOKENS_LOCK_FILE = "tokens-lock.json";
|
|
116
|
-
var SKILLS_DIR = "skills";
|
|
116
|
+
var SKILLS_DIR = "skills-source";
|
|
117
|
+
var LEGACY_SKILLS_DIR = "skills";
|
|
117
118
|
var SKILLS_LOCK_FILE = "manifest.lock.json";
|
|
118
119
|
function getTeamixDir(projectRoot) {
|
|
119
120
|
return path2.join(projectRoot, TEAMIX_DIR);
|
|
@@ -197,6 +198,9 @@ function getSkillsSourceDir(projectRoot, skillName) {
|
|
|
197
198
|
const base = path2.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
|
|
198
199
|
return skillName ? path2.join(base, skillName) : base;
|
|
199
200
|
}
|
|
201
|
+
function getLegacySkillsSourceDir(projectRoot) {
|
|
202
|
+
return path2.join(projectRoot, TEAMIX_DIR, LEGACY_SKILLS_DIR);
|
|
203
|
+
}
|
|
200
204
|
async function readSkillsLock(projectRoot) {
|
|
201
205
|
const lockPath = path2.join(
|
|
202
206
|
projectRoot,
|
|
@@ -385,6 +389,7 @@ function resolveTokensPackageRoot(packageName) {
|
|
|
385
389
|
|
|
386
390
|
// src/core/skills-installer.ts
|
|
387
391
|
async function installSkills(options) {
|
|
392
|
+
await migrateLegacySkillsSourceDir(options.projectRoot);
|
|
388
393
|
const { manifest, ides, scope, onlyIds } = options;
|
|
389
394
|
const installed = [];
|
|
390
395
|
const targets = manifest.skills.filter(
|
|
@@ -677,6 +682,7 @@ function escapeRegExp(str) {
|
|
|
677
682
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
678
683
|
}
|
|
679
684
|
async function syncSkillsToIdes(options) {
|
|
685
|
+
await migrateLegacySkillsSourceDir(options.projectRoot);
|
|
680
686
|
const { projectRoot, skills, ides, scope, onlyIds } = options;
|
|
681
687
|
const out = [];
|
|
682
688
|
const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
|
|
@@ -720,6 +726,118 @@ async function syncSkillsToIdes(options) {
|
|
|
720
726
|
}
|
|
721
727
|
return { resources: out, count: out.length };
|
|
722
728
|
}
|
|
729
|
+
async function migrateLegacySkillsSourceDir(projectRoot) {
|
|
730
|
+
const legacyDir = getLegacySkillsSourceDir(projectRoot);
|
|
731
|
+
const newDir = getSkillsSourceDir(projectRoot);
|
|
732
|
+
let legacyExists = false;
|
|
733
|
+
let newExists = false;
|
|
734
|
+
try {
|
|
735
|
+
legacyExists = (await fs5.stat(legacyDir)).isDirectory();
|
|
736
|
+
} catch {
|
|
737
|
+
legacyExists = false;
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
newExists = (await fs5.stat(newDir)).isDirectory();
|
|
741
|
+
} catch {
|
|
742
|
+
newExists = false;
|
|
743
|
+
}
|
|
744
|
+
if (!legacyExists) return;
|
|
745
|
+
if (newExists) {
|
|
746
|
+
logger.warn(
|
|
747
|
+
`Detected stale legacy skills source dir at ${legacyDir} alongside ${newDir}; the new layout takes precedence \u2014 you can safely delete the legacy dir.`
|
|
748
|
+
);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
try {
|
|
752
|
+
await fs5.rename(legacyDir, newDir);
|
|
753
|
+
logger.info(
|
|
754
|
+
`Migrated skills source dir: \`.teamix-evo/${LEGACY_SKILLS_DIR}/\` \u2192 \`.teamix-evo/skills-source/\``
|
|
755
|
+
);
|
|
756
|
+
} catch (err) {
|
|
757
|
+
logger.warn(
|
|
758
|
+
`Failed to rename legacy skills source dir (${getErrorMessage(
|
|
759
|
+
err
|
|
760
|
+
)}); leaving as-is. New skills will install under the new layout.`
|
|
761
|
+
);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
const manifest = await readInstalledManifest(projectRoot);
|
|
766
|
+
if (!manifest) return;
|
|
767
|
+
const legacyFragmentPosix = `/.teamix-evo/${LEGACY_SKILLS_DIR}/`;
|
|
768
|
+
const newFragmentPosix = `/.teamix-evo/skills-source/`;
|
|
769
|
+
const legacyFragmentNative = `${path7.sep}.teamix-evo${path7.sep}${LEGACY_SKILLS_DIR}${path7.sep}`;
|
|
770
|
+
const newFragmentNative = `${path7.sep}.teamix-evo${path7.sep}skills-source${path7.sep}`;
|
|
771
|
+
let touched = 0;
|
|
772
|
+
for (const pkg of manifest.installed) {
|
|
773
|
+
for (const r of pkg.resources) {
|
|
774
|
+
if (typeof r.target !== "string") continue;
|
|
775
|
+
const before = r.target;
|
|
776
|
+
let after = before.replace(legacyFragmentPosix, newFragmentPosix);
|
|
777
|
+
after = after.replace(legacyFragmentNative, newFragmentNative);
|
|
778
|
+
if (after !== before) {
|
|
779
|
+
r.target = after;
|
|
780
|
+
touched += 1;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (touched > 0) {
|
|
785
|
+
await writeInstalledManifest(projectRoot, manifest);
|
|
786
|
+
logger.debug(
|
|
787
|
+
`Rewrote ${touched} manifest target(s) to the new skills-source path.`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
} catch (err) {
|
|
791
|
+
logger.warn(
|
|
792
|
+
`Migrated skills source dir but failed to update manifest paths (${getErrorMessage(
|
|
793
|
+
err
|
|
794
|
+
)}); manifest may still reference legacy paths.`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
async function pruneEmptyIdeSkillDirs(args) {
|
|
799
|
+
const removed = [];
|
|
800
|
+
for (const ide of args.ides) {
|
|
801
|
+
const adapter = getAdapter(ide);
|
|
802
|
+
const placeholderDir = adapter.getSkillTargetDir(
|
|
803
|
+
"__placeholder__",
|
|
804
|
+
args.scope,
|
|
805
|
+
args.projectRoot
|
|
806
|
+
);
|
|
807
|
+
const skillsRoot = path7.dirname(placeholderDir);
|
|
808
|
+
let entries;
|
|
809
|
+
try {
|
|
810
|
+
entries = await fs5.readdir(skillsRoot);
|
|
811
|
+
} catch {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
for (const name of entries) {
|
|
815
|
+
const dir = path7.join(skillsRoot, name);
|
|
816
|
+
let stat5;
|
|
817
|
+
try {
|
|
818
|
+
stat5 = await fs5.stat(dir);
|
|
819
|
+
} catch {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
if (!stat5.isDirectory()) continue;
|
|
823
|
+
let children;
|
|
824
|
+
try {
|
|
825
|
+
children = await fs5.readdir(dir);
|
|
826
|
+
} catch {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (children.some((c) => c === "SKILL.md")) continue;
|
|
830
|
+
if (children.length !== 0) continue;
|
|
831
|
+
try {
|
|
832
|
+
await fs5.rmdir(dir);
|
|
833
|
+
removed.push(dir);
|
|
834
|
+
logger.debug(`Pruned empty IDE skill dir: ${dir}`);
|
|
835
|
+
} catch {
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return removed;
|
|
840
|
+
}
|
|
723
841
|
async function removeSkillFiles(records) {
|
|
724
842
|
const removed = [];
|
|
725
843
|
for (const r of records) {
|
|
@@ -815,13 +933,29 @@ async function runSkillsInit(options) {
|
|
|
815
933
|
}
|
|
816
934
|
return true;
|
|
817
935
|
}).map((s) => s.id);
|
|
818
|
-
const skippedSkillIds =
|
|
819
|
-
|
|
936
|
+
const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
|
|
937
|
+
candidateIds,
|
|
938
|
+
manifest,
|
|
939
|
+
existing
|
|
820
940
|
);
|
|
821
|
-
|
|
822
|
-
if (existingSkillsCfg && onlyIds.length === 0) {
|
|
941
|
+
if (existingSkillsCfg && onlyIds.length === 0 && outdatedSkills.length === 0) {
|
|
823
942
|
return { status: "already-initialized" };
|
|
824
943
|
}
|
|
944
|
+
if (onlyIds.length === 0) {
|
|
945
|
+
return {
|
|
946
|
+
status: "installed",
|
|
947
|
+
packageName,
|
|
948
|
+
version: existingSkillsCfg?.version ?? manifest.version,
|
|
949
|
+
ides,
|
|
950
|
+
scope,
|
|
951
|
+
skillCount: 0,
|
|
952
|
+
fileCount: 0,
|
|
953
|
+
resources: [],
|
|
954
|
+
addedSkillIds: [],
|
|
955
|
+
skippedSkillIds,
|
|
956
|
+
outdatedSkills
|
|
957
|
+
};
|
|
958
|
+
}
|
|
825
959
|
return finalizeSkillsInstall({
|
|
826
960
|
projectRoot,
|
|
827
961
|
packageName,
|
|
@@ -833,6 +967,7 @@ async function runSkillsInit(options) {
|
|
|
833
967
|
scope,
|
|
834
968
|
onlyIds,
|
|
835
969
|
skippedSkillIds,
|
|
970
|
+
outdatedSkills,
|
|
836
971
|
existing,
|
|
837
972
|
existingConfig
|
|
838
973
|
});
|
|
@@ -873,10 +1008,11 @@ async function runSkillsAdd(options) {
|
|
|
873
1008
|
}
|
|
874
1009
|
}
|
|
875
1010
|
const existing = await readExistingState(projectRoot, packageName);
|
|
876
|
-
const skippedSkillIds =
|
|
877
|
-
|
|
1011
|
+
const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
|
|
1012
|
+
requestedNames,
|
|
1013
|
+
manifest,
|
|
1014
|
+
existing
|
|
878
1015
|
);
|
|
879
|
-
const onlyIds = requestedNames.filter((n) => !existing.skillIds.has(n));
|
|
880
1016
|
if (onlyIds.length === 0) {
|
|
881
1017
|
return {
|
|
882
1018
|
status: "installed",
|
|
@@ -888,7 +1024,8 @@ async function runSkillsAdd(options) {
|
|
|
888
1024
|
fileCount: 0,
|
|
889
1025
|
resources: [],
|
|
890
1026
|
addedSkillIds: [],
|
|
891
|
-
skippedSkillIds
|
|
1027
|
+
skippedSkillIds,
|
|
1028
|
+
outdatedSkills
|
|
892
1029
|
};
|
|
893
1030
|
}
|
|
894
1031
|
return finalizeSkillsInstall({
|
|
@@ -902,10 +1039,52 @@ async function runSkillsAdd(options) {
|
|
|
902
1039
|
scope,
|
|
903
1040
|
onlyIds,
|
|
904
1041
|
skippedSkillIds,
|
|
1042
|
+
outdatedSkills,
|
|
905
1043
|
existing,
|
|
906
1044
|
existingConfig
|
|
907
1045
|
});
|
|
908
1046
|
}
|
|
1047
|
+
function partitionByVersion(ids, manifest, existing) {
|
|
1048
|
+
const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
|
|
1049
|
+
const onlyIds = [];
|
|
1050
|
+
const skippedSkillIds = [];
|
|
1051
|
+
const outdatedSkills = [];
|
|
1052
|
+
for (const name of ids) {
|
|
1053
|
+
if (!existing.skillIds.has(name)) {
|
|
1054
|
+
onlyIds.push(name);
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
const installedVer = existing.lock?.skills?.[name]?.version;
|
|
1058
|
+
const latestVer = manifestById.get(name)?.version;
|
|
1059
|
+
if (installedVer && latestVer && compareSemver(installedVer, latestVer) < 0) {
|
|
1060
|
+
outdatedSkills.push({
|
|
1061
|
+
id: name,
|
|
1062
|
+
installed: installedVer,
|
|
1063
|
+
latest: latestVer
|
|
1064
|
+
});
|
|
1065
|
+
} else {
|
|
1066
|
+
skippedSkillIds.push(name);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return { onlyIds, skippedSkillIds, outdatedSkills };
|
|
1070
|
+
}
|
|
1071
|
+
function parseSemverTriple(v) {
|
|
1072
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
1073
|
+
if (!m) return null;
|
|
1074
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
1075
|
+
}
|
|
1076
|
+
function compareSemver(a, b) {
|
|
1077
|
+
const pa = parseSemverTriple(a);
|
|
1078
|
+
const pb = parseSemverTriple(b);
|
|
1079
|
+
if (!pa || !pb) {
|
|
1080
|
+
if (a === b) return 0;
|
|
1081
|
+
return a < b ? -1 : 1;
|
|
1082
|
+
}
|
|
1083
|
+
for (let i = 0; i < 3; i++) {
|
|
1084
|
+
if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
|
|
1085
|
+
}
|
|
1086
|
+
return 0;
|
|
1087
|
+
}
|
|
909
1088
|
async function readExistingState(projectRoot, packageName) {
|
|
910
1089
|
const installed = await readInstalledManifest(projectRoot);
|
|
911
1090
|
const pkg = installed?.installed.find((p) => p.package === packageName);
|
|
@@ -930,6 +1109,7 @@ async function finalizeSkillsInstall(args) {
|
|
|
930
1109
|
scope,
|
|
931
1110
|
onlyIds,
|
|
932
1111
|
skippedSkillIds,
|
|
1112
|
+
outdatedSkills,
|
|
933
1113
|
existing,
|
|
934
1114
|
existingConfig
|
|
935
1115
|
} = args;
|
|
@@ -995,6 +1175,10 @@ async function finalizeSkillsInstall(args) {
|
|
|
995
1175
|
}
|
|
996
1176
|
await writeSkillsLock(projectRoot, lock);
|
|
997
1177
|
await ensureMcpJson(projectRoot);
|
|
1178
|
+
try {
|
|
1179
|
+
await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
998
1182
|
return {
|
|
999
1183
|
status: "installed",
|
|
1000
1184
|
packageName,
|
|
@@ -1005,7 +1189,8 @@ async function finalizeSkillsInstall(args) {
|
|
|
1005
1189
|
fileCount: result.count,
|
|
1006
1190
|
resources: result.resources,
|
|
1007
1191
|
addedSkillIds: onlyIds,
|
|
1008
|
-
skippedSkillIds
|
|
1192
|
+
skippedSkillIds,
|
|
1193
|
+
outdatedSkills: outdatedSkills ?? []
|
|
1009
1194
|
};
|
|
1010
1195
|
}
|
|
1011
1196
|
function mergeInstalledResources(existing, next) {
|
|
@@ -1213,6 +1398,9 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1213
1398
|
const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
|
|
1214
1399
|
const targetAbs = path9.join(projectRoot, targetRel);
|
|
1215
1400
|
const content = await fs6.readFile(sourceAbs, "utf-8");
|
|
1401
|
+
if (await fileExists(targetAbs)) {
|
|
1402
|
+
await backupFile(targetAbs, projectRoot);
|
|
1403
|
+
}
|
|
1216
1404
|
await writeFileSafe(targetAbs, content);
|
|
1217
1405
|
return {
|
|
1218
1406
|
id: `tokens:${CONSUMER_THEME_FILE}`,
|
|
@@ -1593,6 +1781,9 @@ async function installUiEntries(options) {
|
|
|
1593
1781
|
const sourceAbs = path11.resolve(rootForEntry, file.source);
|
|
1594
1782
|
const raw = await fs8.readFile(sourceAbs, "utf-8");
|
|
1595
1783
|
const transformed = rewriteImports(raw, aliases);
|
|
1784
|
+
if (exists) {
|
|
1785
|
+
await backupFile(targetAbs, projectRoot);
|
|
1786
|
+
}
|
|
1596
1787
|
await writeFileSafe(targetAbs, transformed);
|
|
1597
1788
|
written++;
|
|
1598
1789
|
logger.info(` write: ${rel(projectRoot, targetAbs)}`);
|
|
@@ -1963,18 +2154,29 @@ var ESLINT_DEPS = [
|
|
|
1963
2154
|
];
|
|
1964
2155
|
var STYLELINT_DEPS = ["@teamix-evo/stylelint-config", "stylelint"];
|
|
1965
2156
|
async function runLintInit(options) {
|
|
1966
|
-
const {
|
|
2157
|
+
const {
|
|
2158
|
+
projectRoot,
|
|
2159
|
+
skipInstall,
|
|
2160
|
+
eslintStrategy = "overwrite",
|
|
2161
|
+
stylelintStrategy = "overwrite",
|
|
2162
|
+
eslintExistingPaths = [],
|
|
2163
|
+
stylelintExistingPaths = []
|
|
2164
|
+
} = options;
|
|
1967
2165
|
const eslintConfigPath = path13.join(projectRoot, "eslint.config.js");
|
|
1968
2166
|
const stylelintConfigPath = path13.join(projectRoot, "stylelint.config.cjs");
|
|
1969
|
-
const
|
|
1970
|
-
const
|
|
1971
|
-
|
|
2167
|
+
const eslintTemplateExists = await fileExists(eslintConfigPath);
|
|
2168
|
+
const stylelintTemplateExists = await fileExists(stylelintConfigPath);
|
|
2169
|
+
const eslintSkipRequested = eslintStrategy === "skip" && eslintExistingPaths.length > 0;
|
|
2170
|
+
const stylelintSkipRequested = stylelintStrategy === "skip" && stylelintExistingPaths.length > 0;
|
|
2171
|
+
const eslintNeedsWrite = !eslintTemplateExists && !eslintSkipRequested;
|
|
2172
|
+
const stylelintNeedsWrite = !stylelintTemplateExists && !stylelintSkipRequested;
|
|
2173
|
+
if (!eslintNeedsWrite && !stylelintNeedsWrite) {
|
|
1972
2174
|
return { status: "already-initialized" };
|
|
1973
2175
|
}
|
|
1974
2176
|
if (!skipInstall) {
|
|
1975
2177
|
const depsToInstall = [
|
|
1976
|
-
...
|
|
1977
|
-
...
|
|
2178
|
+
...eslintNeedsWrite ? ESLINT_DEPS : [],
|
|
2179
|
+
...stylelintNeedsWrite ? STYLELINT_DEPS : []
|
|
1978
2180
|
];
|
|
1979
2181
|
if (depsToInstall.length > 0) {
|
|
1980
2182
|
const pm = detectPm(projectRoot);
|
|
@@ -1983,23 +2185,38 @@ async function runLintInit(options) {
|
|
|
1983
2185
|
await execa(pm, args, { cwd: projectRoot, stdio: "inherit" });
|
|
1984
2186
|
}
|
|
1985
2187
|
}
|
|
2188
|
+
if (eslintNeedsWrite && eslintExistingPaths.length > 0) {
|
|
2189
|
+
for (const rel2 of eslintExistingPaths) {
|
|
2190
|
+
await backupFile(path13.join(projectRoot, rel2), projectRoot);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
if (stylelintNeedsWrite && stylelintExistingPaths.length > 0) {
|
|
2194
|
+
for (const rel2 of stylelintExistingPaths) {
|
|
2195
|
+
await backupFile(path13.join(projectRoot, rel2), projectRoot);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
1986
2198
|
let wroteEslint = false;
|
|
1987
2199
|
let wroteStylelint = false;
|
|
1988
|
-
if (
|
|
2200
|
+
if (eslintNeedsWrite) {
|
|
1989
2201
|
await writeFileSafe(eslintConfigPath, ESLINT_CONFIG_CONTENT);
|
|
1990
2202
|
logger.debug(`Wrote eslint.config.js \u2192 ${eslintConfigPath}`);
|
|
1991
2203
|
wroteEslint = true;
|
|
1992
2204
|
}
|
|
1993
|
-
if (
|
|
2205
|
+
if (stylelintNeedsWrite) {
|
|
1994
2206
|
await writeFileSafe(stylelintConfigPath, STYLELINT_CONFIG_CONTENT);
|
|
1995
2207
|
logger.debug(`Wrote stylelint.config.cjs \u2192 ${stylelintConfigPath}`);
|
|
1996
2208
|
wroteStylelint = true;
|
|
1997
2209
|
}
|
|
1998
|
-
await patchPackageJsonScripts(projectRoot);
|
|
2210
|
+
const packageJsonPatched = await patchPackageJsonScripts(projectRoot);
|
|
1999
2211
|
return {
|
|
2000
2212
|
status: "installed",
|
|
2001
2213
|
eslint: wroteEslint,
|
|
2002
|
-
stylelint: wroteStylelint
|
|
2214
|
+
stylelint: wroteStylelint,
|
|
2215
|
+
eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
|
|
2216
|
+
stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
|
|
2217
|
+
eslintSkipped: eslintSkipRequested,
|
|
2218
|
+
stylelintSkipped: stylelintSkipRequested,
|
|
2219
|
+
packageJsonPatched
|
|
2003
2220
|
};
|
|
2004
2221
|
}
|
|
2005
2222
|
function detectPm(projectRoot) {
|
|
@@ -2010,12 +2227,12 @@ function detectPm(projectRoot) {
|
|
|
2010
2227
|
async function patchPackageJsonScripts(projectRoot) {
|
|
2011
2228
|
const pkgPath = path13.join(projectRoot, "package.json");
|
|
2012
2229
|
const raw = await readFileOrNull(pkgPath);
|
|
2013
|
-
if (!raw) return;
|
|
2230
|
+
if (!raw) return false;
|
|
2014
2231
|
let pkg;
|
|
2015
2232
|
try {
|
|
2016
2233
|
pkg = JSON.parse(raw);
|
|
2017
2234
|
} catch {
|
|
2018
|
-
return;
|
|
2235
|
+
return false;
|
|
2019
2236
|
}
|
|
2020
2237
|
const scripts = pkg.scripts ?? {};
|
|
2021
2238
|
let changed = false;
|
|
@@ -2028,17 +2245,22 @@ async function patchPackageJsonScripts(projectRoot) {
|
|
|
2028
2245
|
changed = true;
|
|
2029
2246
|
}
|
|
2030
2247
|
if (changed) {
|
|
2248
|
+
await backupFile(pkgPath, projectRoot);
|
|
2031
2249
|
pkg.scripts = scripts;
|
|
2032
2250
|
await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2033
2251
|
logger.debug("Patched package.json scripts with lint / lint:css");
|
|
2034
2252
|
}
|
|
2253
|
+
return changed;
|
|
2035
2254
|
}
|
|
2036
2255
|
|
|
2037
2256
|
// src/core/agents-md.ts
|
|
2038
2257
|
import * as fs10 from "fs/promises";
|
|
2039
2258
|
import * as path14 from "path";
|
|
2259
|
+
import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
|
|
2260
|
+
var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
|
|
2040
2261
|
async function runGenerateAgentsMd(options) {
|
|
2041
2262
|
const { projectRoot, variant, skillIds } = options;
|
|
2263
|
+
const mode = options.mode ?? "overwrite";
|
|
2042
2264
|
const ordered = [...skillIds].sort(
|
|
2043
2265
|
(a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
|
|
2044
2266
|
);
|
|
@@ -2049,13 +2271,47 @@ async function runGenerateAgentsMd(options) {
|
|
|
2049
2271
|
sections.push(section);
|
|
2050
2272
|
if (missing) missingSkillIds.push(id);
|
|
2051
2273
|
}
|
|
2052
|
-
const body = renderAgentsMd({ variant, sections });
|
|
2053
2274
|
const target = path14.join(projectRoot, "AGENTS.md");
|
|
2054
|
-
await
|
|
2275
|
+
const targetExists = await fileExists(target);
|
|
2276
|
+
const fullTemplate = renderAgentsMd({ variant, sections });
|
|
2277
|
+
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
2278
|
+
let outputContent;
|
|
2279
|
+
let merge;
|
|
2280
|
+
if (!targetExists) {
|
|
2281
|
+
outputContent = fullTemplate;
|
|
2282
|
+
merge = "created";
|
|
2283
|
+
} else {
|
|
2284
|
+
await backupFile(target, projectRoot);
|
|
2285
|
+
if (mode === "merge-managed") {
|
|
2286
|
+
const existing = await readFileOrNull(target) ?? "";
|
|
2287
|
+
if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
|
|
2288
|
+
outputContent = replaceManagedRegion2(
|
|
2289
|
+
existing,
|
|
2290
|
+
AGENTS_MD_MANAGED_ID,
|
|
2291
|
+
managedBody
|
|
2292
|
+
);
|
|
2293
|
+
merge = "managed-replaced";
|
|
2294
|
+
} else {
|
|
2295
|
+
const wrapped = wrapManagedBlock(managedBody);
|
|
2296
|
+
outputContent = `${wrapped}
|
|
2297
|
+
|
|
2298
|
+
${PRECEDENCE_NOTICE}
|
|
2299
|
+
|
|
2300
|
+
${existing.trimStart()}`;
|
|
2301
|
+
merge = "managed-prepended";
|
|
2302
|
+
}
|
|
2303
|
+
} else {
|
|
2304
|
+
outputContent = fullTemplate;
|
|
2305
|
+
merge = "overwritten";
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
await fs10.writeFile(target, outputContent, "utf8");
|
|
2055
2309
|
return {
|
|
2056
2310
|
path: target,
|
|
2057
2311
|
skillCount: ordered.length,
|
|
2058
|
-
missingSkillIds
|
|
2312
|
+
missingSkillIds,
|
|
2313
|
+
backedUp: targetExists,
|
|
2314
|
+
merge
|
|
2059
2315
|
};
|
|
2060
2316
|
}
|
|
2061
2317
|
function bucketRank(id) {
|
|
@@ -2065,10 +2321,7 @@ function bucketRank(id) {
|
|
|
2065
2321
|
}
|
|
2066
2322
|
async function renderSkillSection(projectRoot, skillId) {
|
|
2067
2323
|
const skillPath = path14.join(
|
|
2068
|
-
projectRoot,
|
|
2069
|
-
".teamix-evo",
|
|
2070
|
-
"skills",
|
|
2071
|
-
skillId,
|
|
2324
|
+
getSkillsSourceDir(projectRoot, skillId),
|
|
2072
2325
|
"SKILL.md"
|
|
2073
2326
|
);
|
|
2074
2327
|
const lines = [];
|
|
@@ -2093,10 +2346,19 @@ async function renderSkillSection(projectRoot, skillId) {
|
|
|
2093
2346
|
if (parts?.coordinates) {
|
|
2094
2347
|
lines.push(`- **Coordinates with**: ${parts.coordinates}`);
|
|
2095
2348
|
}
|
|
2096
|
-
lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills/${skillId}/SKILL.md\``);
|
|
2349
|
+
lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills-source/${skillId}/SKILL.md\``);
|
|
2097
2350
|
return { section: lines.join("\n"), missing };
|
|
2098
2351
|
}
|
|
2099
2352
|
function renderAgentsMd(args) {
|
|
2353
|
+
const { variant, sections } = args;
|
|
2354
|
+
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
2355
|
+
const wrapped = wrapManagedBlock(managedBody);
|
|
2356
|
+
return `${wrapped}
|
|
2357
|
+
|
|
2358
|
+
${PRECEDENCE_NOTICE}
|
|
2359
|
+
`;
|
|
2360
|
+
}
|
|
2361
|
+
function renderManagedBlockBody(args) {
|
|
2100
2362
|
const { variant, sections } = args;
|
|
2101
2363
|
const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
|
|
2102
2364
|
return `# AGENTS.md
|
|
@@ -2115,9 +2377,15 @@ ${skillBlock}
|
|
|
2115
2377
|
- \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
|
|
2116
2378
|
- \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
2379
|
|
|
2118
|
-
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002
|
|
2119
|
-
`;
|
|
2380
|
+
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
|
|
2120
2381
|
}
|
|
2382
|
+
function wrapManagedBlock(body) {
|
|
2383
|
+
return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
|
|
2384
|
+
${body}
|
|
2385
|
+
<!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
|
|
2386
|
+
}
|
|
2387
|
+
var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
|
|
2388
|
+
> \u51B2\u7A81\u4EE5\u4E0A\u65B9\u7684 **Skills** \u7D22\u5F15\u4E3A\u51C6\uFF08\u4E0A\u6E38\u8DEF\u5F84\u4E0E TRIGGER/SKIP \u5951\u7EA6\uFF09\uFF1B\u9879\u76EE\u7279\u6709\u7684\u4EBA\u5DE5\u7EC6\u5219\u8BF7\u5199\u5728\u672C\u5904\u4EE5\u4E0B\u3001\u4E0D\u8981\u8986\u76D6\u4E0A\u65B9 managed \u533A\u57DF\u3002`;
|
|
2121
2389
|
function extractDescriptionParts(fileContent) {
|
|
2122
2390
|
const description = extractDescriptionBlock(fileContent);
|
|
2123
2391
|
if (description == null) return null;
|
|
@@ -2311,6 +2579,26 @@ var INDEX_CSS_CANDIDATES = [
|
|
|
2311
2579
|
];
|
|
2312
2580
|
var SHADCN_FILE_CANDIDATES = ["src/lib/utils.ts"];
|
|
2313
2581
|
var SHADCN_DIR_CANDIDATES = ["src/components/ui"];
|
|
2582
|
+
var ESLINT_CONFIG_CANDIDATES = [
|
|
2583
|
+
".eslintrc.cjs",
|
|
2584
|
+
".eslintrc.js",
|
|
2585
|
+
".eslintrc.json",
|
|
2586
|
+
".eslintrc.yml",
|
|
2587
|
+
"eslint.config.js",
|
|
2588
|
+
"eslint.config.cjs",
|
|
2589
|
+
"eslint.config.mjs",
|
|
2590
|
+
"eslint.config.ts"
|
|
2591
|
+
];
|
|
2592
|
+
var STYLELINT_CONFIG_CANDIDATES = [
|
|
2593
|
+
".stylelintrc.cjs",
|
|
2594
|
+
".stylelintrc.js",
|
|
2595
|
+
".stylelintrc.json",
|
|
2596
|
+
".stylelintrc.yml",
|
|
2597
|
+
"stylelint.config.cjs",
|
|
2598
|
+
"stylelint.config.js",
|
|
2599
|
+
"stylelint.config.mjs",
|
|
2600
|
+
"stylelint.config.ts"
|
|
2601
|
+
];
|
|
2314
2602
|
async function isDir(target) {
|
|
2315
2603
|
try {
|
|
2316
2604
|
const stat5 = await fs12.stat(target);
|
|
@@ -2487,7 +2775,9 @@ async function detectConflicts(cwd) {
|
|
|
2487
2775
|
detectTailwindConfig(absCwd),
|
|
2488
2776
|
detectTokens(absCwd),
|
|
2489
2777
|
detectIndexCss(absCwd),
|
|
2490
|
-
detectShadcnSource(absCwd)
|
|
2778
|
+
detectShadcnSource(absCwd),
|
|
2779
|
+
detectEslintConfig(absCwd),
|
|
2780
|
+
detectStylelintConfig(absCwd)
|
|
2491
2781
|
]);
|
|
2492
2782
|
return {
|
|
2493
2783
|
cwd: absCwd,
|
|
@@ -2495,6 +2785,46 @@ async function detectConflicts(cwd) {
|
|
|
2495
2785
|
hasAnyConflict: items.some((i) => i.exists)
|
|
2496
2786
|
};
|
|
2497
2787
|
}
|
|
2788
|
+
async function detectEslintConfig(cwd) {
|
|
2789
|
+
const matched = [];
|
|
2790
|
+
const contents = [];
|
|
2791
|
+
for (const rel2 of ESLINT_CONFIG_CANDIDATES) {
|
|
2792
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2793
|
+
if (c !== null) {
|
|
2794
|
+
matched.push(rel2);
|
|
2795
|
+
contents.push(c);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
const exists = matched.length > 0;
|
|
2799
|
+
return {
|
|
2800
|
+
key: "eslint-config",
|
|
2801
|
+
exists,
|
|
2802
|
+
paths: matched,
|
|
2803
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2804
|
+
recommendedStrategy: exists ? "merge" : "overwrite",
|
|
2805
|
+
availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
async function detectStylelintConfig(cwd) {
|
|
2809
|
+
const matched = [];
|
|
2810
|
+
const contents = [];
|
|
2811
|
+
for (const rel2 of STYLELINT_CONFIG_CANDIDATES) {
|
|
2812
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2813
|
+
if (c !== null) {
|
|
2814
|
+
matched.push(rel2);
|
|
2815
|
+
contents.push(c);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
const exists = matched.length > 0;
|
|
2819
|
+
return {
|
|
2820
|
+
key: "stylelint-config",
|
|
2821
|
+
exists,
|
|
2822
|
+
paths: matched,
|
|
2823
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2824
|
+
recommendedStrategy: exists ? "merge" : "overwrite",
|
|
2825
|
+
availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2498
2828
|
|
|
2499
2829
|
// src/core/legacy-tokens-migrate.ts
|
|
2500
2830
|
import * as path17 from "path";
|
|
@@ -2706,6 +3036,56 @@ async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
|
|
|
2706
3036
|
return removed.reverse();
|
|
2707
3037
|
}
|
|
2708
3038
|
|
|
3039
|
+
// src/core/file-changes.ts
|
|
3040
|
+
import * as fs15 from "fs/promises";
|
|
3041
|
+
import * as path19 from "path";
|
|
3042
|
+
function toRelativePosix(p, projectRoot) {
|
|
3043
|
+
let rel2 = p;
|
|
3044
|
+
if (path19.isAbsolute(p)) {
|
|
3045
|
+
rel2 = path19.relative(projectRoot, p);
|
|
3046
|
+
}
|
|
3047
|
+
return rel2.split(path19.sep).join("/");
|
|
3048
|
+
}
|
|
3049
|
+
async function listBackupOriginals(projectRoot) {
|
|
3050
|
+
const backupsDir = path19.join(projectRoot, ".teamix-evo", ".backups");
|
|
3051
|
+
const out = /* @__PURE__ */ new Set();
|
|
3052
|
+
const stack = [backupsDir];
|
|
3053
|
+
while (stack.length > 0) {
|
|
3054
|
+
const dir = stack.pop();
|
|
3055
|
+
let entries;
|
|
3056
|
+
try {
|
|
3057
|
+
entries = await fs15.readdir(dir, { withFileTypes: true });
|
|
3058
|
+
} catch (err) {
|
|
3059
|
+
if (err.code === "ENOENT") continue;
|
|
3060
|
+
throw err;
|
|
3061
|
+
}
|
|
3062
|
+
for (const e of entries) {
|
|
3063
|
+
const full = path19.join(dir, e.name);
|
|
3064
|
+
if (e.isDirectory()) {
|
|
3065
|
+
stack.push(full);
|
|
3066
|
+
} else if (e.isFile() && e.name.endsWith(".bak")) {
|
|
3067
|
+
const rel2 = path19.relative(backupsDir, full);
|
|
3068
|
+
const original = stripBackupSuffix(rel2);
|
|
3069
|
+
if (original) out.add(original.split(path19.sep).join("/"));
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
return out;
|
|
3074
|
+
}
|
|
3075
|
+
function stripBackupSuffix(rel2) {
|
|
3076
|
+
const m = rel2.match(
|
|
3077
|
+
/^(.+)\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.bak$/
|
|
3078
|
+
);
|
|
3079
|
+
return m?.[1] ?? null;
|
|
3080
|
+
}
|
|
3081
|
+
function diffBackupSet(before, after) {
|
|
3082
|
+
const out = /* @__PURE__ */ new Set();
|
|
3083
|
+
for (const p of after) {
|
|
3084
|
+
if (!before.has(p)) out.add(p);
|
|
3085
|
+
}
|
|
3086
|
+
return out;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
2709
3089
|
// src/core/project-init.ts
|
|
2710
3090
|
var BASELINE_UI_ENTRIES = [
|
|
2711
3091
|
"button",
|
|
@@ -2728,12 +3108,19 @@ var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
|
2728
3108
|
"ui-init"
|
|
2729
3109
|
]);
|
|
2730
3110
|
var IMPLEMENTED_STRATEGIES = {
|
|
3111
|
+
// 'agents-md': both 'merge-managed' (Phase 2.B — splice the
|
|
3112
|
+
// teamix-evo-skills managed region) and 'overwrite' (full rewrite) write
|
|
3113
|
+
// through `runGenerateAgentsMd`.
|
|
2731
3114
|
"agents-md": ["merge-managed", "overwrite", "skip"],
|
|
2732
3115
|
tokens: ["migrate", "overwrite", "skip"],
|
|
2733
3116
|
"components-json": ["overwrite", "skip"],
|
|
2734
3117
|
"shadcn-source": ["overwrite", "skip-existing", "skip"],
|
|
2735
3118
|
"tailwind-config": ["skip"],
|
|
2736
|
-
"index-css": ["skip"]
|
|
3119
|
+
"index-css": ["skip"],
|
|
3120
|
+
// Phase 3.E: lint conflict strategies are honored end-to-end by
|
|
3121
|
+
// `runLintInit` (backup user file + write template, optional AI-assist hint).
|
|
3122
|
+
"eslint-config": ["merge", "backup-overwrite", "skip", "overwrite"],
|
|
3123
|
+
"stylelint-config": ["merge", "backup-overwrite", "skip", "overwrite"]
|
|
2737
3124
|
};
|
|
2738
3125
|
function pickIde(ides) {
|
|
2739
3126
|
return ides[0] ?? "qoder";
|
|
@@ -2751,11 +3138,75 @@ async function resolveUiEntries(options) {
|
|
|
2751
3138
|
}
|
|
2752
3139
|
return [...BASELINE_UI_ENTRIES];
|
|
2753
3140
|
}
|
|
3141
|
+
function deriveTokensChanges(result, projectRoot) {
|
|
3142
|
+
if (result.status !== "installed") return [];
|
|
3143
|
+
return result.resources.map((r) => ({
|
|
3144
|
+
kind: "created",
|
|
3145
|
+
path: toRelativePosix(r.target, projectRoot),
|
|
3146
|
+
step: "tokens",
|
|
3147
|
+
detail: r.strategy
|
|
3148
|
+
}));
|
|
3149
|
+
}
|
|
3150
|
+
function deriveSkillsChanges(result, projectRoot) {
|
|
3151
|
+
if (result.status !== "installed") return [];
|
|
3152
|
+
return result.addedSkillIds.map((id) => ({
|
|
3153
|
+
kind: "created",
|
|
3154
|
+
path: `.teamix-evo/skills/${id}/SKILL.md`,
|
|
3155
|
+
step: "skills",
|
|
3156
|
+
detail: "skill installed (source mirror + IDE mirrors)"
|
|
3157
|
+
}));
|
|
3158
|
+
}
|
|
3159
|
+
function deriveUiAddChanges(result, projectRoot) {
|
|
3160
|
+
const out = [];
|
|
3161
|
+
let remaining = result.written;
|
|
3162
|
+
for (let i = result.resources.length - 1; i >= 0 && remaining > 0; i--) {
|
|
3163
|
+
const r = result.resources[i];
|
|
3164
|
+
out.unshift({
|
|
3165
|
+
kind: "created",
|
|
3166
|
+
path: toRelativePosix(r.target, projectRoot),
|
|
3167
|
+
step: "ui-add",
|
|
3168
|
+
detail: r.strategy
|
|
3169
|
+
});
|
|
3170
|
+
remaining--;
|
|
3171
|
+
}
|
|
3172
|
+
return out;
|
|
3173
|
+
}
|
|
3174
|
+
function deriveLintChanges(result) {
|
|
3175
|
+
if (result.status !== "installed") return [];
|
|
3176
|
+
const out = [];
|
|
3177
|
+
if (result.eslint) {
|
|
3178
|
+
out.push({
|
|
3179
|
+
kind: "created",
|
|
3180
|
+
path: "eslint.config.js",
|
|
3181
|
+
step: "lint",
|
|
3182
|
+
detail: "@teamix-evo/eslint-config consumer preset"
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
if (result.stylelint) {
|
|
3186
|
+
out.push({
|
|
3187
|
+
kind: "created",
|
|
3188
|
+
path: "stylelint.config.cjs",
|
|
3189
|
+
step: "lint",
|
|
3190
|
+
detail: "@teamix-evo/stylelint-config consumer preset"
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
if (result.packageJsonPatched) {
|
|
3194
|
+
out.push({
|
|
3195
|
+
kind: "created",
|
|
3196
|
+
path: "package.json",
|
|
3197
|
+
step: "lint",
|
|
3198
|
+
detail: 'scripts.lint / scripts["lint:css"]'
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
return out;
|
|
3202
|
+
}
|
|
2754
3203
|
async function runProjectInit(options) {
|
|
2755
3204
|
const { projectRoot, answers, dryRun = false, onStep } = options;
|
|
2756
3205
|
const ide = pickIde(answers.ides);
|
|
2757
3206
|
const steps = [];
|
|
2758
3207
|
const pending = [];
|
|
3208
|
+
const allChanges = [];
|
|
3209
|
+
const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
|
|
2759
3210
|
let snapshot = null;
|
|
2760
3211
|
let snapshotError;
|
|
2761
3212
|
if (!dryRun) {
|
|
@@ -2772,6 +3223,9 @@ async function runProjectInit(options) {
|
|
|
2772
3223
|
function record(step) {
|
|
2773
3224
|
steps.push(step);
|
|
2774
3225
|
onStep?.(step);
|
|
3226
|
+
if (step.changes && step.changes.length > 0) {
|
|
3227
|
+
allChanges.push(...step.changes);
|
|
3228
|
+
}
|
|
2775
3229
|
}
|
|
2776
3230
|
function recordPending(key) {
|
|
2777
3231
|
const strategy = answers.conflictDecisions[key];
|
|
@@ -2831,7 +3285,8 @@ async function runProjectInit(options) {
|
|
|
2831
3285
|
record({
|
|
2832
3286
|
name: "tokens",
|
|
2833
3287
|
status: "ok",
|
|
2834
|
-
detail
|
|
3288
|
+
detail,
|
|
3289
|
+
changes: deriveTokensChanges(result, projectRoot)
|
|
2835
3290
|
});
|
|
2836
3291
|
} catch (err) {
|
|
2837
3292
|
recordFailure("tokens", err);
|
|
@@ -2863,7 +3318,8 @@ async function runProjectInit(options) {
|
|
|
2863
3318
|
record({
|
|
2864
3319
|
name: "skills",
|
|
2865
3320
|
status: "ok",
|
|
2866
|
-
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status
|
|
3321
|
+
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
|
|
3322
|
+
changes: deriveSkillsChanges(result, projectRoot)
|
|
2867
3323
|
});
|
|
2868
3324
|
} catch (err) {
|
|
2869
3325
|
recordFailure("skills", err);
|
|
@@ -2891,12 +3347,25 @@ async function runProjectInit(options) {
|
|
|
2891
3347
|
const result = await runGenerateAgentsMd({
|
|
2892
3348
|
projectRoot,
|
|
2893
3349
|
variant: answers.variant,
|
|
2894
|
-
skillIds: agentsMdSkillIds
|
|
3350
|
+
skillIds: agentsMdSkillIds,
|
|
3351
|
+
// Phase 2.B: when the user picked `merge-managed` on conflict, only
|
|
3352
|
+
// rewrite the `teamix-evo-skills` managed region so hand-written
|
|
3353
|
+
// sections survive the regenerate step. `overwrite` keeps the
|
|
3354
|
+
// historical full-rewrite default.
|
|
3355
|
+
mode: agentsMdDecision === "merge-managed" ? "merge-managed" : "overwrite"
|
|
2895
3356
|
});
|
|
2896
3357
|
record({
|
|
2897
3358
|
name: "agents-md",
|
|
2898
3359
|
status: "ok",
|
|
2899
|
-
detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}
|
|
3360
|
+
detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`,
|
|
3361
|
+
changes: [
|
|
3362
|
+
{
|
|
3363
|
+
kind: result.backedUp ? "modified" : "created",
|
|
3364
|
+
path: toRelativePosix(result.path, projectRoot),
|
|
3365
|
+
step: "agents-md",
|
|
3366
|
+
detail: "skill-trigger fallback (ADR 0038)"
|
|
3367
|
+
}
|
|
3368
|
+
]
|
|
2900
3369
|
});
|
|
2901
3370
|
} catch (err) {
|
|
2902
3371
|
recordFailure("agents-md", err);
|
|
@@ -2981,7 +3450,8 @@ async function runProjectInit(options) {
|
|
|
2981
3450
|
record({
|
|
2982
3451
|
name: "ui-add",
|
|
2983
3452
|
status: "ok",
|
|
2984
|
-
detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)
|
|
3453
|
+
detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
|
|
3454
|
+
changes: deriveUiAddChanges(addResult, projectRoot)
|
|
2985
3455
|
});
|
|
2986
3456
|
} catch (err) {
|
|
2987
3457
|
recordFailure("ui-add", err);
|
|
@@ -3005,14 +3475,39 @@ async function runProjectInit(options) {
|
|
|
3005
3475
|
});
|
|
3006
3476
|
} else {
|
|
3007
3477
|
try {
|
|
3478
|
+
const eslintStrategy = answers.conflictDecisions["eslint-config"];
|
|
3479
|
+
const stylelintStrategy = answers.conflictDecisions["stylelint-config"];
|
|
3480
|
+
const eslintExistingPaths = options.legacyEslintPaths ?? [];
|
|
3481
|
+
const stylelintExistingPaths = options.legacyStylelintPaths ?? [];
|
|
3008
3482
|
const result = await runLintInit({
|
|
3009
3483
|
projectRoot,
|
|
3010
|
-
skipInstall: options.skipInstall ?? false
|
|
3484
|
+
skipInstall: options.skipInstall ?? false,
|
|
3485
|
+
eslintStrategy: eslintStrategy === "merge" || eslintStrategy === "backup-overwrite" || eslintStrategy === "skip" || eslintStrategy === "overwrite" ? eslintStrategy : "overwrite",
|
|
3486
|
+
stylelintStrategy: stylelintStrategy === "merge" || stylelintStrategy === "backup-overwrite" || stylelintStrategy === "skip" || stylelintStrategy === "overwrite" ? stylelintStrategy : "overwrite",
|
|
3487
|
+
eslintExistingPaths,
|
|
3488
|
+
stylelintExistingPaths
|
|
3011
3489
|
});
|
|
3490
|
+
const detailParts = [];
|
|
3491
|
+
if (result.status === "installed") {
|
|
3492
|
+
detailParts.push(
|
|
3493
|
+
`eslint=${result.eslint}, stylelint=${result.stylelint}`
|
|
3494
|
+
);
|
|
3495
|
+
if (result.eslintMergeRequested) {
|
|
3496
|
+
detailParts.push("eslint:AI-merge-pending");
|
|
3497
|
+
}
|
|
3498
|
+
if (result.stylelintMergeRequested) {
|
|
3499
|
+
detailParts.push("stylelint:AI-merge-pending");
|
|
3500
|
+
}
|
|
3501
|
+
if (result.eslintSkipped) detailParts.push("eslint:skipped");
|
|
3502
|
+
if (result.stylelintSkipped) detailParts.push("stylelint:skipped");
|
|
3503
|
+
} else {
|
|
3504
|
+
detailParts.push(result.status);
|
|
3505
|
+
}
|
|
3012
3506
|
record({
|
|
3013
3507
|
name: "lint",
|
|
3014
3508
|
status: "ok",
|
|
3015
|
-
detail:
|
|
3509
|
+
detail: detailParts.join(" / "),
|
|
3510
|
+
changes: deriveLintChanges(result)
|
|
3016
3511
|
});
|
|
3017
3512
|
} catch (err) {
|
|
3018
3513
|
recordFailure("lint", err);
|
|
@@ -3020,11 +3515,34 @@ async function runProjectInit(options) {
|
|
|
3020
3515
|
}
|
|
3021
3516
|
recordPending("tailwind-config");
|
|
3022
3517
|
recordPending("index-css");
|
|
3518
|
+
if (!dryRun) {
|
|
3519
|
+
try {
|
|
3520
|
+
const backupsAfter = await listBackupOriginals(projectRoot);
|
|
3521
|
+
const newlyBackedUp = diffBackupSet(backupsBefore, backupsAfter);
|
|
3522
|
+
if (newlyBackedUp.size > 0) {
|
|
3523
|
+
for (const change of allChanges) {
|
|
3524
|
+
if (change.kind === "created" && newlyBackedUp.has(change.path)) {
|
|
3525
|
+
change.kind = "modified";
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
for (const rel2 of newlyBackedUp) {
|
|
3529
|
+
allChanges.push({
|
|
3530
|
+
kind: "backed-up",
|
|
3531
|
+
path: rel2,
|
|
3532
|
+
step: "backup",
|
|
3533
|
+
detail: ".teamix-evo/.backups/<\u540C\u8DEF\u5F84>.<isoTs>.bak"
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
} catch {
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3023
3540
|
const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
|
|
3024
3541
|
const out = {
|
|
3025
3542
|
status,
|
|
3026
3543
|
steps,
|
|
3027
3544
|
pendingConflictWork: pending,
|
|
3545
|
+
changes: allChanges,
|
|
3028
3546
|
snapshot
|
|
3029
3547
|
};
|
|
3030
3548
|
if (snapshotError) out.snapshotError = snapshotError;
|
|
@@ -3045,22 +3563,22 @@ async function runProjectInit(options) {
|
|
|
3045
3563
|
}
|
|
3046
3564
|
|
|
3047
3565
|
// src/core/project-update.ts
|
|
3048
|
-
import * as
|
|
3566
|
+
import * as path25 from "path";
|
|
3049
3567
|
import {
|
|
3050
3568
|
loadTokensPackageManifest as loadTokensPackageManifest3,
|
|
3051
3569
|
getVariantEntry as getVariantEntry3
|
|
3052
3570
|
} from "@teamix-evo/registry";
|
|
3053
3571
|
|
|
3054
3572
|
// src/core/tokens-update.ts
|
|
3055
|
-
import * as
|
|
3056
|
-
import * as
|
|
3573
|
+
import * as path21 from "path";
|
|
3574
|
+
import * as fs16 from "fs/promises";
|
|
3057
3575
|
import {
|
|
3058
3576
|
loadTokensPackageManifest as loadTokensPackageManifest2,
|
|
3059
3577
|
getVariantEntry as getVariantEntry2
|
|
3060
3578
|
} from "@teamix-evo/registry";
|
|
3061
3579
|
|
|
3062
3580
|
// src/core/upgrade-hints.ts
|
|
3063
|
-
import * as
|
|
3581
|
+
import * as path20 from "path";
|
|
3064
3582
|
var TEAMIX_DIR3 = ".teamix-evo";
|
|
3065
3583
|
var HINTS_DIR = ".upgrade-hints";
|
|
3066
3584
|
function isoToFsSafe2(iso) {
|
|
@@ -3071,7 +3589,7 @@ async function writeTokensUpgradeHint(options) {
|
|
|
3071
3589
|
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3072
3590
|
const fsTs = isoToFsSafe2(isoTs);
|
|
3073
3591
|
const filename = `tokens-${fsTs}.json`;
|
|
3074
|
-
const target =
|
|
3592
|
+
const target = path20.join(
|
|
3075
3593
|
options.projectRoot,
|
|
3076
3594
|
TEAMIX_DIR3,
|
|
3077
3595
|
HINTS_DIR,
|
|
@@ -3097,10 +3615,10 @@ async function writeTokensUpgradeHint(options) {
|
|
|
3097
3615
|
}
|
|
3098
3616
|
function selectApplicableRenames(renames, fromVersion, toVersion) {
|
|
3099
3617
|
return renames.filter(
|
|
3100
|
-
(r) =>
|
|
3101
|
-
).sort((a, b) =>
|
|
3618
|
+
(r) => compareSemver2(r.sinceVersion, fromVersion) > 0 && compareSemver2(r.sinceVersion, toVersion) <= 0
|
|
3619
|
+
).sort((a, b) => compareSemver2(a.sinceVersion, b.sinceVersion));
|
|
3102
3620
|
}
|
|
3103
|
-
function
|
|
3621
|
+
function compareSemver2(a, b) {
|
|
3104
3622
|
const [aMain = "", aRest = ""] = a.split("-", 2);
|
|
3105
3623
|
const [bMain = "", bRest = ""] = b.split("-", 2);
|
|
3106
3624
|
const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
|
|
@@ -3116,7 +3634,7 @@ function compareSemver(a, b) {
|
|
|
3116
3634
|
}
|
|
3117
3635
|
|
|
3118
3636
|
// src/core/managed-merge.ts
|
|
3119
|
-
import { hasManagedRegion as
|
|
3637
|
+
import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
|
|
3120
3638
|
function mergeManagedRegions(upstreamContent, consumerContent) {
|
|
3121
3639
|
let updated = consumerContent;
|
|
3122
3640
|
const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
|
|
@@ -3124,12 +3642,12 @@ function mergeManagedRegions(upstreamContent, consumerContent) {
|
|
|
3124
3642
|
while ((match = re.exec(upstreamContent)) !== null) {
|
|
3125
3643
|
const id = match[1];
|
|
3126
3644
|
const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
|
|
3127
|
-
if (!
|
|
3645
|
+
if (!hasManagedRegion3(updated, id)) {
|
|
3128
3646
|
throw new Error(
|
|
3129
3647
|
`Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
|
|
3130
3648
|
);
|
|
3131
3649
|
}
|
|
3132
|
-
updated =
|
|
3650
|
+
updated = replaceManagedRegion3(updated, id, body);
|
|
3133
3651
|
}
|
|
3134
3652
|
return updated;
|
|
3135
3653
|
}
|
|
@@ -3160,8 +3678,8 @@ async function runTokensUpdate(options) {
|
|
|
3160
3678
|
const upstreamByBasename = /* @__PURE__ */ new Map();
|
|
3161
3679
|
for (const fileRel of variantEntry.files) {
|
|
3162
3680
|
upstreamByBasename.set(
|
|
3163
|
-
|
|
3164
|
-
|
|
3681
|
+
path21.basename(fileRel),
|
|
3682
|
+
path21.join(packageRoot, fileRel)
|
|
3165
3683
|
);
|
|
3166
3684
|
}
|
|
3167
3685
|
const prior = await readInstalledManifest(projectRoot) ?? {
|
|
@@ -3178,8 +3696,8 @@ async function runTokensUpdate(options) {
|
|
|
3178
3696
|
const frozenDrift = [];
|
|
3179
3697
|
const refreshedResources = [];
|
|
3180
3698
|
for (const resource of priorResources) {
|
|
3181
|
-
const consumerAbs =
|
|
3182
|
-
const consumerBasename =
|
|
3699
|
+
const consumerAbs = path21.isAbsolute(resource.target) ? resource.target : path21.join(projectRoot, resource.target);
|
|
3700
|
+
const consumerBasename = path21.basename(resource.target);
|
|
3183
3701
|
const upstreamBasename = lookupUpstreamBasename(consumerBasename);
|
|
3184
3702
|
const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
|
|
3185
3703
|
if (resource.strategy === "regenerable") {
|
|
@@ -3187,7 +3705,7 @@ async function runTokensUpdate(options) {
|
|
|
3187
3705
|
refreshedResources.push(resource);
|
|
3188
3706
|
continue;
|
|
3189
3707
|
}
|
|
3190
|
-
const content = await
|
|
3708
|
+
const content = await fs16.readFile(upstreamAbs, "utf-8");
|
|
3191
3709
|
await writeFileSafe(consumerAbs, content);
|
|
3192
3710
|
rewritten.push(resource.target);
|
|
3193
3711
|
refreshedResources.push({
|
|
@@ -3201,8 +3719,8 @@ async function runTokensUpdate(options) {
|
|
|
3201
3719
|
refreshedResources.push(resource);
|
|
3202
3720
|
continue;
|
|
3203
3721
|
}
|
|
3204
|
-
const upstreamContent = await
|
|
3205
|
-
const consumerContent = await
|
|
3722
|
+
const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
|
|
3723
|
+
const consumerContent = await fs16.readFile(consumerAbs, "utf-8");
|
|
3206
3724
|
const merged = mergeManagedRegions(upstreamContent, consumerContent);
|
|
3207
3725
|
if (merged !== consumerContent) {
|
|
3208
3726
|
await writeFileSafe(consumerAbs, merged);
|
|
@@ -3216,7 +3734,7 @@ async function runTokensUpdate(options) {
|
|
|
3216
3734
|
}
|
|
3217
3735
|
if (await fileExists(consumerAbs)) preserved.push(resource.target);
|
|
3218
3736
|
if (upstreamAbs) {
|
|
3219
|
-
const upstreamContent = await
|
|
3737
|
+
const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
|
|
3220
3738
|
const upstreamHash = computeHash(upstreamContent);
|
|
3221
3739
|
if (resource.hash && upstreamHash !== resource.hash) {
|
|
3222
3740
|
frozenDrift.push({
|
|
@@ -3256,7 +3774,7 @@ async function runTokensUpdate(options) {
|
|
|
3256
3774
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3257
3775
|
};
|
|
3258
3776
|
await writeFileSafe(
|
|
3259
|
-
|
|
3777
|
+
path21.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
|
|
3260
3778
|
JSON.stringify(lock, null, 2) + "\n"
|
|
3261
3779
|
);
|
|
3262
3780
|
config.packages.tokens.version = variantEntry.version;
|
|
@@ -3312,8 +3830,8 @@ function lookupUpstreamBasename(consumerBasename) {
|
|
|
3312
3830
|
}
|
|
3313
3831
|
|
|
3314
3832
|
// src/core/ui-upgrade-detector.ts
|
|
3315
|
-
import * as
|
|
3316
|
-
import * as
|
|
3833
|
+
import * as fs17 from "fs/promises";
|
|
3834
|
+
import * as path22 from "path";
|
|
3317
3835
|
var PACKAGE_NAME = {
|
|
3318
3836
|
ui: "@teamix-evo/ui",
|
|
3319
3837
|
"biz-ui": "@teamix-evo/biz-ui"
|
|
@@ -3329,10 +3847,10 @@ async function detectComponentLineage(options) {
|
|
|
3329
3847
|
const config = options.config ?? await readProjectConfig(projectRoot);
|
|
3330
3848
|
const installed = options.installed ?? await readInstalledManifest(projectRoot);
|
|
3331
3849
|
const installDir = resolveInstallDir(category, config);
|
|
3332
|
-
const installDirAbs =
|
|
3850
|
+
const installDirAbs = path22.join(projectRoot, installDir);
|
|
3333
3851
|
const installDirExists = await directoryExists(installDirAbs);
|
|
3334
3852
|
const hasComponentsJson = await fileExists(
|
|
3335
|
-
|
|
3853
|
+
path22.join(projectRoot, "components.json")
|
|
3336
3854
|
);
|
|
3337
3855
|
const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
|
|
3338
3856
|
const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
|
|
@@ -3372,14 +3890,14 @@ function extractIds(pkg) {
|
|
|
3372
3890
|
}
|
|
3373
3891
|
async function directoryExists(p) {
|
|
3374
3892
|
try {
|
|
3375
|
-
const stat5 = await
|
|
3893
|
+
const stat5 = await fs17.stat(p);
|
|
3376
3894
|
return stat5.isDirectory();
|
|
3377
3895
|
} catch {
|
|
3378
3896
|
return false;
|
|
3379
3897
|
}
|
|
3380
3898
|
}
|
|
3381
3899
|
async function listComponentIds(installDirAbs) {
|
|
3382
|
-
const entries = await
|
|
3900
|
+
const entries = await fs17.readdir(installDirAbs, { withFileTypes: true });
|
|
3383
3901
|
const ids = [];
|
|
3384
3902
|
for (const e of entries) {
|
|
3385
3903
|
if (!e.isFile()) continue;
|
|
@@ -3399,7 +3917,7 @@ function classifyLineage(args) {
|
|
|
3399
3917
|
}
|
|
3400
3918
|
|
|
3401
3919
|
// src/core/ui-upgrade.ts
|
|
3402
|
-
import * as
|
|
3920
|
+
import * as path24 from "path";
|
|
3403
3921
|
import { createRequire as createRequire5 } from "module";
|
|
3404
3922
|
import {
|
|
3405
3923
|
loadUiPackageManifest as loadUiPackageManifest3,
|
|
@@ -3407,7 +3925,7 @@ import {
|
|
|
3407
3925
|
} from "@teamix-evo/registry";
|
|
3408
3926
|
|
|
3409
3927
|
// src/core/ui-upgrade-staging.ts
|
|
3410
|
-
import * as
|
|
3928
|
+
import * as path23 from "path";
|
|
3411
3929
|
var TEAMIX_DIR4 = ".teamix-evo";
|
|
3412
3930
|
var STAGING_DIR = ".upgrade-staging";
|
|
3413
3931
|
var PACKAGE_NAME2 = {
|
|
@@ -3427,7 +3945,7 @@ async function buildUiUpgradeStaging(options) {
|
|
|
3427
3945
|
if (!installedPkg) return null;
|
|
3428
3946
|
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3429
3947
|
const fsTs = isoToFsSafe3(isoTs);
|
|
3430
|
-
const stagingDir =
|
|
3948
|
+
const stagingDir = path23.join(
|
|
3431
3949
|
options.projectRoot,
|
|
3432
3950
|
TEAMIX_DIR4,
|
|
3433
3951
|
STAGING_DIR,
|
|
@@ -3459,7 +3977,7 @@ async function buildUiUpgradeStaging(options) {
|
|
|
3459
3977
|
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3460
3978
|
const built = await processForeign({
|
|
3461
3979
|
id,
|
|
3462
|
-
installDirAbs:
|
|
3980
|
+
installDirAbs: path23.join(options.projectRoot, lineageReport.installDir),
|
|
3463
3981
|
stagingDir,
|
|
3464
3982
|
projectRoot: options.projectRoot,
|
|
3465
3983
|
category
|
|
@@ -3482,7 +4000,7 @@ async function buildUiUpgradeStaging(options) {
|
|
|
3482
4000
|
};
|
|
3483
4001
|
await ensureDir(stagingDir);
|
|
3484
4002
|
await writeFileSafe(
|
|
3485
|
-
|
|
4003
|
+
path23.join(stagingDir, "meta.json"),
|
|
3486
4004
|
JSON.stringify(manifestOut, null, 2) + "\n"
|
|
3487
4005
|
);
|
|
3488
4006
|
return { stagingDir, manifest: manifestOut };
|
|
@@ -3516,19 +4034,19 @@ async function processRegistered(args) {
|
|
|
3516
4034
|
const file = entry.files[0];
|
|
3517
4035
|
if (!file) return null;
|
|
3518
4036
|
const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
|
|
3519
|
-
const sourceAbs =
|
|
4037
|
+
const sourceAbs = path23.resolve(rootForEntry, file.source);
|
|
3520
4038
|
const raw = await readFileOrNull(sourceAbs);
|
|
3521
4039
|
if (raw === null) {
|
|
3522
4040
|
return null;
|
|
3523
4041
|
}
|
|
3524
4042
|
const incomingTransformed = rewriteImports(raw, args.aliases);
|
|
3525
4043
|
const incomingHash = computeHash(incomingTransformed);
|
|
3526
|
-
const currentExt =
|
|
3527
|
-
const incomingExt =
|
|
4044
|
+
const currentExt = path23.extname(resource.target) || ".tsx";
|
|
4045
|
+
const incomingExt = path23.extname(file.targetName) || currentExt;
|
|
3528
4046
|
const currentRel = `${id}/current${currentExt}`;
|
|
3529
4047
|
const incomingRel = `${id}/incoming${incomingExt}`;
|
|
3530
|
-
await writeFileSafe(
|
|
3531
|
-
await writeFileSafe(
|
|
4048
|
+
await writeFileSafe(path23.join(stagingDir, currentRel), currentSource);
|
|
4049
|
+
await writeFileSafe(path23.join(stagingDir, incomingRel), incomingTransformed);
|
|
3532
4050
|
const diff = classifyRisk({
|
|
3533
4051
|
currentHash: resource.hash,
|
|
3534
4052
|
incomingHash,
|
|
@@ -3536,11 +4054,16 @@ async function processRegistered(args) {
|
|
|
3536
4054
|
incomingSource: incomingTransformed,
|
|
3537
4055
|
multiFile: entry.files.length > 1
|
|
3538
4056
|
});
|
|
4057
|
+
const promotion = derivePromotion({
|
|
4058
|
+
currentSource,
|
|
4059
|
+
incomingSource: incomingTransformed,
|
|
4060
|
+
targetName: entry.files[0]?.targetName ?? `${id}.tsx`
|
|
4061
|
+
});
|
|
3539
4062
|
return {
|
|
3540
4063
|
id,
|
|
3541
4064
|
category,
|
|
3542
4065
|
current: {
|
|
3543
|
-
target:
|
|
4066
|
+
target: path23.relative(projectRoot, resource.target),
|
|
3544
4067
|
hash: resource.hash,
|
|
3545
4068
|
sourceLineage: "teamix-evo"
|
|
3546
4069
|
},
|
|
@@ -3549,25 +4072,26 @@ async function processRegistered(args) {
|
|
|
3549
4072
|
hash: incomingHash,
|
|
3550
4073
|
relPath: incomingRel
|
|
3551
4074
|
},
|
|
3552
|
-
diff
|
|
4075
|
+
diff,
|
|
4076
|
+
promotion
|
|
3553
4077
|
};
|
|
3554
4078
|
}
|
|
3555
4079
|
async function processForeign(args) {
|
|
3556
4080
|
const { id, installDirAbs, stagingDir, projectRoot, category } = args;
|
|
3557
|
-
const tsx =
|
|
3558
|
-
const ts =
|
|
4081
|
+
const tsx = path23.join(installDirAbs, `${id}.tsx`);
|
|
4082
|
+
const ts = path23.join(installDirAbs, `${id}.ts`);
|
|
3559
4083
|
const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
|
|
3560
4084
|
if (!target) return null;
|
|
3561
4085
|
const raw = await readFileOrNull(target);
|
|
3562
4086
|
if (raw === null) return null;
|
|
3563
|
-
const ext =
|
|
4087
|
+
const ext = path23.extname(target);
|
|
3564
4088
|
const currentRel = `${id}/current${ext}`;
|
|
3565
|
-
await writeFileSafe(
|
|
4089
|
+
await writeFileSafe(path23.join(stagingDir, currentRel), raw);
|
|
3566
4090
|
return {
|
|
3567
4091
|
id,
|
|
3568
4092
|
category,
|
|
3569
4093
|
current: {
|
|
3570
|
-
target:
|
|
4094
|
+
target: path23.relative(projectRoot, target),
|
|
3571
4095
|
hash: computeHash(raw),
|
|
3572
4096
|
sourceLineage: "custom"
|
|
3573
4097
|
},
|
|
@@ -3582,17 +4106,17 @@ async function processForeign(args) {
|
|
|
3582
4106
|
};
|
|
3583
4107
|
}
|
|
3584
4108
|
async function buildBreakingEntry(args) {
|
|
3585
|
-
const ext =
|
|
4109
|
+
const ext = path23.extname(args.resource.target) || ".tsx";
|
|
3586
4110
|
const currentRel = `${args.id}/current${ext}`;
|
|
3587
4111
|
await writeFileSafe(
|
|
3588
|
-
|
|
4112
|
+
path23.join(args.stagingDir, currentRel),
|
|
3589
4113
|
args.currentSource
|
|
3590
4114
|
);
|
|
3591
4115
|
return {
|
|
3592
4116
|
id: args.id,
|
|
3593
4117
|
category: args.category,
|
|
3594
4118
|
current: {
|
|
3595
|
-
target:
|
|
4119
|
+
target: path23.relative(args.projectRoot, args.resource.target),
|
|
3596
4120
|
hash: args.resource.hash,
|
|
3597
4121
|
sourceLineage: "teamix-evo"
|
|
3598
4122
|
},
|
|
@@ -3712,12 +4236,191 @@ function aggregateByRisk(entries) {
|
|
|
3712
4236
|
}
|
|
3713
4237
|
return out;
|
|
3714
4238
|
}
|
|
4239
|
+
function derivePromotion(args) {
|
|
4240
|
+
const fileType = classifyPromoteFileType(args.targetName, args.currentSource);
|
|
4241
|
+
const featureVector = buildFeatureVector(
|
|
4242
|
+
args.currentSource,
|
|
4243
|
+
args.incomingSource
|
|
4244
|
+
);
|
|
4245
|
+
const { recommendedModes, confidence, reasons } = scorePromotionModes(
|
|
4246
|
+
fileType,
|
|
4247
|
+
featureVector
|
|
4248
|
+
);
|
|
4249
|
+
return { fileType, featureVector, recommendedModes, confidence, reasons };
|
|
4250
|
+
}
|
|
4251
|
+
function classifyPromoteFileType(targetName, src) {
|
|
4252
|
+
if (targetName.endsWith(".d.ts")) return "type";
|
|
4253
|
+
if (/^use-[a-z0-9-]+\.tsx?$/i.test(targetName)) return "hook";
|
|
4254
|
+
const hasJsx = /<[A-Za-z][^>]*?>/.test(src);
|
|
4255
|
+
const hasReactImport = /from ['"]react['"]/.test(src);
|
|
4256
|
+
const hasProvider = /\.Provider\b/.test(src) || /createContext\s*[<(]/.test(src);
|
|
4257
|
+
if (hasProvider && (hasJsx || hasReactImport)) return "provider";
|
|
4258
|
+
if (hasJsx || /forwardRef\s*[<(]/.test(src)) return "component";
|
|
4259
|
+
if (!hasJsx && !hasReactImport) return "util";
|
|
4260
|
+
return "component";
|
|
4261
|
+
}
|
|
4262
|
+
function buildFeatureVector(current, incoming) {
|
|
4263
|
+
const curExports = extractExportNames(current);
|
|
4264
|
+
const newExports = extractExportNames(incoming);
|
|
4265
|
+
const apiAdded = setDiff(curExports, newExports);
|
|
4266
|
+
const apiRemoved = setDiff(newExports, curExports);
|
|
4267
|
+
const curVariants = extractCvaVariantValues(current);
|
|
4268
|
+
const newVariants = extractCvaVariantValues(incoming);
|
|
4269
|
+
const cvaAdded = setDiff(curVariants, newVariants);
|
|
4270
|
+
const cvaModified = [];
|
|
4271
|
+
const sharedVariants = curVariants.filter((v) => newVariants.includes(v));
|
|
4272
|
+
for (const v of sharedVariants) {
|
|
4273
|
+
if (extractVariantBody(current, v) !== extractVariantBody(incoming, v)) {
|
|
4274
|
+
cvaModified.push(v);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
const curClass = extractClassNameLiterals(current);
|
|
4278
|
+
const newClass = extractClassNameLiterals(incoming);
|
|
4279
|
+
const classNameDiff = curClass !== newClass;
|
|
4280
|
+
const curTokens = extractTokenRefs(current);
|
|
4281
|
+
const newTokens = extractTokenRefs(incoming);
|
|
4282
|
+
const tokenUsageDiff = curTokens.size !== newTokens.size || [...curTokens].some((t) => !newTokens.has(t));
|
|
4283
|
+
const hasState = /\buseState\s*[<(]/.test(current);
|
|
4284
|
+
const hasEffect = /\b(useEffect|useLayoutEffect|useMemo|useCallback)\s*[<(]/.test(current);
|
|
4285
|
+
const curImports = extractImportSources(current);
|
|
4286
|
+
const newImports = extractImportSources(incoming);
|
|
4287
|
+
const hasExtraImports = [...curImports].some((src) => !newImports.has(src));
|
|
4288
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
4289
|
+
for (const m of current.matchAll(/<([A-Z]\w+)[\s/>]/g)) {
|
|
4290
|
+
if (m[1]) tagSet.add(m[1]);
|
|
4291
|
+
}
|
|
4292
|
+
const atomicChildren = [...tagSet];
|
|
4293
|
+
const isComposition = atomicChildren.length > 2;
|
|
4294
|
+
const signatureChanged = extractDefaultParamList(current) !== extractDefaultParamList(incoming);
|
|
4295
|
+
return {
|
|
4296
|
+
apiDelta: { added: apiAdded, removed: apiRemoved, signatureChanged },
|
|
4297
|
+
styleDelta: { classNameDiff, tokenUsageDiff },
|
|
4298
|
+
logicDelta: { hasState, hasEffect, hasExtraImports },
|
|
4299
|
+
cvaDelta: { addedVariants: cvaAdded, modifiedVariants: cvaModified },
|
|
4300
|
+
structureDelta: { isComposition, atomicChildren }
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
function scorePromotionModes(fileType, fv) {
|
|
4304
|
+
const reasons = [];
|
|
4305
|
+
if (fileType === "hook" || fileType === "util" || fileType === "type") {
|
|
4306
|
+
reasons.push(
|
|
4307
|
+
`fileType=${fileType} \u2014 not a component, deferred to ManualReview`
|
|
4308
|
+
);
|
|
4309
|
+
return { recommendedModes: ["ManualReview"], confidence: 0.5, reasons };
|
|
4310
|
+
}
|
|
4311
|
+
const apiNoChange = fv.apiDelta.added.length === 0 && fv.apiDelta.removed.length === 0 && !fv.apiDelta.signatureChanged;
|
|
4312
|
+
const logicMinimal = !fv.logicDelta.hasState && !fv.logicDelta.hasEffect && !fv.logicDelta.hasExtraImports;
|
|
4313
|
+
if (fv.apiDelta.removed.length > 0 || fv.apiDelta.signatureChanged) {
|
|
4314
|
+
reasons.push(
|
|
4315
|
+
"signature changed or props removed \u2014 Coexist preserves user version"
|
|
4316
|
+
);
|
|
4317
|
+
return { recommendedModes: ["Coexist"], confidence: 0.85, reasons };
|
|
4318
|
+
}
|
|
4319
|
+
if (apiNoChange && logicMinimal && (fv.styleDelta.classNameDiff || fv.styleDelta.tokenUsageDiff) && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
|
|
4320
|
+
reasons.push(
|
|
4321
|
+
"only style / token differences \u2014 push to tokens.overrides.css"
|
|
4322
|
+
);
|
|
4323
|
+
return { recommendedModes: ["TokenOnly"], confidence: 0.8, reasons };
|
|
4324
|
+
}
|
|
4325
|
+
const modes = [];
|
|
4326
|
+
let score = 0;
|
|
4327
|
+
if (fv.cvaDelta.addedVariants.length > 0 || fv.cvaDelta.modifiedVariants.length > 0) {
|
|
4328
|
+
modes.push("Variant");
|
|
4329
|
+
reasons.push(
|
|
4330
|
+
`cva variants delta: +${fv.cvaDelta.addedVariants.length} ~${fv.cvaDelta.modifiedVariants.length}`
|
|
4331
|
+
);
|
|
4332
|
+
score = Math.max(score, 0.7);
|
|
4333
|
+
}
|
|
4334
|
+
if (fv.logicDelta.hasState || fv.logicDelta.hasEffect || fv.logicDelta.hasExtraImports || fv.apiDelta.added.length > 0) {
|
|
4335
|
+
modes.push("Wrapper");
|
|
4336
|
+
reasons.push("user added state / effect / imports / props");
|
|
4337
|
+
score = Math.max(score, 0.75);
|
|
4338
|
+
}
|
|
4339
|
+
if (apiNoChange && logicMinimal && !fv.styleDelta.classNameDiff && !fv.styleDelta.tokenUsageDiff && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
|
|
4340
|
+
modes.push("Preset");
|
|
4341
|
+
reasons.push("no API/logic delta \u2014 Preset captures default-prop tweaks");
|
|
4342
|
+
score = Math.max(score, 0.6);
|
|
4343
|
+
}
|
|
4344
|
+
if (fv.structureDelta.isComposition) {
|
|
4345
|
+
modes.push("Compose");
|
|
4346
|
+
reasons.push(
|
|
4347
|
+
`composition of ${fv.structureDelta.atomicChildren.length} atomic children`
|
|
4348
|
+
);
|
|
4349
|
+
score = Math.max(score, 0.65);
|
|
4350
|
+
}
|
|
4351
|
+
if (modes.length === 0) {
|
|
4352
|
+
reasons.push("no axis crossed the 0.6 threshold");
|
|
4353
|
+
return { recommendedModes: ["ManualReview"], confidence: 0.4, reasons };
|
|
4354
|
+
}
|
|
4355
|
+
return { recommendedModes: modes, confidence: score, reasons };
|
|
4356
|
+
}
|
|
4357
|
+
function extractVariantBody(src, key) {
|
|
4358
|
+
const block = extractVariantsBlock(src);
|
|
4359
|
+
if (block === null) return "";
|
|
4360
|
+
const re = new RegExp(`(?:["']?${key}["']?)\\s*:\\s*\\{`);
|
|
4361
|
+
const idx = block.search(re);
|
|
4362
|
+
if (idx < 0) return "";
|
|
4363
|
+
const open = block.indexOf("{", idx);
|
|
4364
|
+
if (open < 0) return "";
|
|
4365
|
+
let depth = 0;
|
|
4366
|
+
for (let i = open; i < block.length; i++) {
|
|
4367
|
+
const c = block[i];
|
|
4368
|
+
if (c === "{") depth++;
|
|
4369
|
+
else if (c === "}") {
|
|
4370
|
+
depth--;
|
|
4371
|
+
if (depth === 0) return block.slice(open + 1, i);
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
return "";
|
|
4375
|
+
}
|
|
4376
|
+
function extractClassNameLiterals(src) {
|
|
4377
|
+
const out = [];
|
|
4378
|
+
for (const m of src.matchAll(/className\s*=\s*["'`]([^"'`]*)["'`]/g)) {
|
|
4379
|
+
if (m[1]) out.push(m[1]);
|
|
4380
|
+
}
|
|
4381
|
+
for (const m of src.matchAll(/\b(?:cn|clsx|cva)\s*\(/g)) {
|
|
4382
|
+
const open = (m.index ?? 0) + m[0].length - 1;
|
|
4383
|
+
let depth = 1;
|
|
4384
|
+
let i = open + 1;
|
|
4385
|
+
for (; i < src.length && depth > 0; i++) {
|
|
4386
|
+
const c = src[i];
|
|
4387
|
+
if (c === "(") depth++;
|
|
4388
|
+
else if (c === ")") depth--;
|
|
4389
|
+
}
|
|
4390
|
+
const body = src.slice(open + 1, i - 1);
|
|
4391
|
+
for (const lit of body.matchAll(/["'`]([^"'`]*)["'`]/g)) {
|
|
4392
|
+
if (lit[1]) out.push(lit[1]);
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
return out.sort().join("|");
|
|
4396
|
+
}
|
|
4397
|
+
function extractTokenRefs(src) {
|
|
4398
|
+
const out = /* @__PURE__ */ new Set();
|
|
4399
|
+
for (const m of src.matchAll(/var\(--([a-z0-9-]+)\)/g)) {
|
|
4400
|
+
if (m[1]) out.add(m[1]);
|
|
4401
|
+
}
|
|
4402
|
+
for (const m of src.matchAll(/--([a-z][a-z0-9-]*)\s*:/g)) {
|
|
4403
|
+
if (m[1]) out.add(m[1]);
|
|
4404
|
+
}
|
|
4405
|
+
return out;
|
|
4406
|
+
}
|
|
4407
|
+
function extractImportSources(src) {
|
|
4408
|
+
const out = /* @__PURE__ */ new Set();
|
|
4409
|
+
for (const m of src.matchAll(/^\s*import\b[^'"]*['"]([^'"]+)['"]/gm)) {
|
|
4410
|
+
if (m[1]) out.add(m[1]);
|
|
4411
|
+
}
|
|
4412
|
+
return out;
|
|
4413
|
+
}
|
|
4414
|
+
function extractDefaultParamList(src) {
|
|
4415
|
+
const m = /export\s+default\s+(?:async\s+)?function\s+\w*\s*\(([^)]*)\)/.exec(src) ?? /export\s+default\s+(?:\([^)]*\)|\w+)\s*=>/.exec(src) ?? /(?:const|function)\s+\w+\s*=?\s*(?:\(([^)]*)\)|\w+)\s*(?:=>|\{)/.exec(src);
|
|
4416
|
+
return m?.[1]?.replace(/\s+/g, " ").trim() ?? "";
|
|
4417
|
+
}
|
|
3715
4418
|
|
|
3716
4419
|
// src/core/ui-upgrade.ts
|
|
3717
4420
|
var nodeRequire = createRequire5(import.meta.url);
|
|
3718
4421
|
function resolvePackageRoot4(packageName) {
|
|
3719
4422
|
const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
|
|
3720
|
-
return
|
|
4423
|
+
return path24.dirname(pkgJsonPath);
|
|
3721
4424
|
}
|
|
3722
4425
|
async function buildStaging(args) {
|
|
3723
4426
|
const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
|
|
@@ -3737,7 +4440,7 @@ async function buildStaging(args) {
|
|
|
3737
4440
|
}
|
|
3738
4441
|
const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
|
|
3739
4442
|
const variant = lineageReport.installedVariant ?? "_flat";
|
|
3740
|
-
const variantDir =
|
|
4443
|
+
const variantDir = path24.join(bizRoot, "variants", variant);
|
|
3741
4444
|
const variantManifest = await loadVariantUiPackageManifest2(variantDir);
|
|
3742
4445
|
const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
3743
4446
|
const uiManifest = await loadUiPackageManifest3(uiRoot);
|
|
@@ -4019,7 +4722,7 @@ async function runComponentSourceStep(category, args) {
|
|
|
4019
4722
|
});
|
|
4020
4723
|
return;
|
|
4021
4724
|
}
|
|
4022
|
-
const stagingRel =
|
|
4725
|
+
const stagingRel = path25.relative(projectRoot, built.stagingDir);
|
|
4023
4726
|
const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
|
|
4024
4727
|
record({
|
|
4025
4728
|
name: category,
|
|
@@ -4062,8 +4765,8 @@ async function planTokensUpdate(tokensPackage, config) {
|
|
|
4062
4765
|
}
|
|
4063
4766
|
|
|
4064
4767
|
// src/core/installer.ts
|
|
4065
|
-
import * as
|
|
4066
|
-
import * as
|
|
4768
|
+
import * as path26 from "path";
|
|
4769
|
+
import * as fs18 from "fs/promises";
|
|
4067
4770
|
async function installResources(options) {
|
|
4068
4771
|
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
4069
4772
|
const installedResources = [];
|
|
@@ -4100,13 +4803,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
|
|
|
4100
4803
|
variantDir,
|
|
4101
4804
|
packageRoot
|
|
4102
4805
|
);
|
|
4103
|
-
const targetPath =
|
|
4806
|
+
const targetPath = path26.join(projectRoot, resource.target);
|
|
4104
4807
|
let content;
|
|
4105
4808
|
if (resource.template) {
|
|
4106
4809
|
const templateContent = await loadTemplateFile(sourcePath);
|
|
4107
4810
|
content = renderTemplate(templateContent, data);
|
|
4108
4811
|
} else {
|
|
4109
|
-
content = await
|
|
4812
|
+
content = await fs18.readFile(sourcePath, "utf-8");
|
|
4110
4813
|
}
|
|
4111
4814
|
await writeFileSafe(targetPath, content);
|
|
4112
4815
|
const hash = computeHash(content);
|
|
@@ -4124,13 +4827,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4124
4827
|
variantDir,
|
|
4125
4828
|
packageRoot
|
|
4126
4829
|
);
|
|
4127
|
-
const targetDir =
|
|
4830
|
+
const targetDir = path26.join(projectRoot, resource.target);
|
|
4128
4831
|
const results = [];
|
|
4129
4832
|
await ensureDir(targetDir);
|
|
4130
4833
|
const entries = await walkDir(sourcePath);
|
|
4131
4834
|
for (const entry of entries) {
|
|
4132
|
-
const relPath =
|
|
4133
|
-
let targetFile =
|
|
4835
|
+
const relPath = path26.relative(sourcePath, entry);
|
|
4836
|
+
let targetFile = path26.join(targetDir, relPath);
|
|
4134
4837
|
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
4135
4838
|
targetFile = targetFile.slice(0, -4);
|
|
4136
4839
|
}
|
|
@@ -4139,11 +4842,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4139
4842
|
const templateContent = await loadTemplateFile(entry);
|
|
4140
4843
|
content = renderTemplate(templateContent, data);
|
|
4141
4844
|
} else {
|
|
4142
|
-
content = await
|
|
4845
|
+
content = await fs18.readFile(entry, "utf-8");
|
|
4143
4846
|
}
|
|
4144
4847
|
await writeFileSafe(targetFile, content);
|
|
4145
4848
|
const hash = computeHash(content);
|
|
4146
|
-
const targetRel =
|
|
4849
|
+
const targetRel = path26.relative(projectRoot, targetFile);
|
|
4147
4850
|
results.push({
|
|
4148
4851
|
id: `${resource.id}:${relPath}`,
|
|
4149
4852
|
target: targetRel,
|
|
@@ -4156,25 +4859,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4156
4859
|
}
|
|
4157
4860
|
|
|
4158
4861
|
// src/core/registry-client.ts
|
|
4159
|
-
import * as
|
|
4160
|
-
import * as
|
|
4862
|
+
import * as path27 from "path";
|
|
4863
|
+
import * as fs19 from "fs/promises";
|
|
4161
4864
|
import { createRequire as createRequire6 } from "module";
|
|
4162
4865
|
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
4163
4866
|
var require6 = createRequire6(import.meta.url);
|
|
4164
4867
|
function resolvePackageRoot5(packageName) {
|
|
4165
4868
|
const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
|
|
4166
|
-
return
|
|
4869
|
+
return path27.dirname(pkgJsonPath);
|
|
4167
4870
|
}
|
|
4168
4871
|
async function loadVariantData(packageName, variant) {
|
|
4169
4872
|
const packageRoot = resolvePackageRoot5(packageName);
|
|
4170
|
-
const variantDir =
|
|
4873
|
+
const variantDir = path27.join(packageRoot, "library", variant);
|
|
4171
4874
|
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
4172
4875
|
logger.debug(`Package root: ${packageRoot}`);
|
|
4173
4876
|
const manifest = await loadVariantManifest(variantDir);
|
|
4174
4877
|
let data = {};
|
|
4175
|
-
const dataPath =
|
|
4878
|
+
const dataPath = path27.join(variantDir, "_data.json");
|
|
4176
4879
|
try {
|
|
4177
|
-
const raw = await
|
|
4880
|
+
const raw = await fs19.readFile(dataPath, "utf-8");
|
|
4178
4881
|
data = JSON.parse(raw);
|
|
4179
4882
|
} catch (err) {
|
|
4180
4883
|
if (err.code !== "ENOENT") {
|