teamix-evo 0.7.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 +30 -24
- package/dist/core/index.d.ts +510 -3
- package/dist/core/index.js +2909 -148
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +6406 -1185
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/core/index.js
CHANGED
|
@@ -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,11 +96,25 @@ 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";
|
|
103
115
|
var TOKENS_LOCK_FILE = "tokens-lock.json";
|
|
104
|
-
var SKILLS_DIR = "skills";
|
|
116
|
+
var SKILLS_DIR = "skills-source";
|
|
117
|
+
var LEGACY_SKILLS_DIR = "skills";
|
|
105
118
|
var SKILLS_LOCK_FILE = "manifest.lock.json";
|
|
106
119
|
function getTeamixDir(projectRoot) {
|
|
107
120
|
return path2.join(projectRoot, TEAMIX_DIR);
|
|
@@ -120,7 +133,7 @@ async function readProjectConfig(projectRoot) {
|
|
|
120
133
|
data = JSON.parse(raw);
|
|
121
134
|
} catch (err) {
|
|
122
135
|
throw new Error(
|
|
123
|
-
`Corrupted config.json (${err
|
|
136
|
+
`Corrupted config.json (${getErrorMessage(err)}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior config.`
|
|
124
137
|
);
|
|
125
138
|
}
|
|
126
139
|
const result = validateConfig(data);
|
|
@@ -145,7 +158,7 @@ async function readInstalledManifest(projectRoot) {
|
|
|
145
158
|
data = JSON.parse(raw);
|
|
146
159
|
} catch (err) {
|
|
147
160
|
throw new Error(
|
|
148
|
-
`Corrupted manifest.json (${err
|
|
161
|
+
`Corrupted manifest.json (${getErrorMessage(err)}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior install records.`
|
|
149
162
|
);
|
|
150
163
|
}
|
|
151
164
|
const result = validateInstalled(data);
|
|
@@ -173,9 +186,7 @@ async function readTokensLock(projectRoot) {
|
|
|
173
186
|
}
|
|
174
187
|
return parsed.data;
|
|
175
188
|
} catch (err) {
|
|
176
|
-
logger.warn(
|
|
177
|
-
`Failed to parse tokens-lock.json: ${err.message}`
|
|
178
|
-
);
|
|
189
|
+
logger.warn(`Failed to parse tokens-lock.json: ${getErrorMessage(err)}`);
|
|
179
190
|
return null;
|
|
180
191
|
}
|
|
181
192
|
}
|
|
@@ -187,6 +198,9 @@ function getSkillsSourceDir(projectRoot, skillName) {
|
|
|
187
198
|
const base = path2.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
|
|
188
199
|
return skillName ? path2.join(base, skillName) : base;
|
|
189
200
|
}
|
|
201
|
+
function getLegacySkillsSourceDir(projectRoot) {
|
|
202
|
+
return path2.join(projectRoot, TEAMIX_DIR, LEGACY_SKILLS_DIR);
|
|
203
|
+
}
|
|
190
204
|
async function readSkillsLock(projectRoot) {
|
|
191
205
|
const lockPath = path2.join(
|
|
192
206
|
projectRoot,
|
|
@@ -206,7 +220,7 @@ async function readSkillsLock(projectRoot) {
|
|
|
206
220
|
return result.data;
|
|
207
221
|
} catch (err) {
|
|
208
222
|
logger.warn(
|
|
209
|
-
`Failed to parse skills manifest.lock.json: ${err
|
|
223
|
+
`Failed to parse skills manifest.lock.json: ${getErrorMessage(err)}`
|
|
210
224
|
);
|
|
211
225
|
return null;
|
|
212
226
|
}
|
|
@@ -221,6 +235,12 @@ async function writeSkillsLock(projectRoot, lock) {
|
|
|
221
235
|
await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
222
236
|
logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
|
|
223
237
|
}
|
|
238
|
+
function findInstalledPackage(installed, packageName) {
|
|
239
|
+
if (!installed) return null;
|
|
240
|
+
const matches = installed.installed.filter((p) => p.package === packageName);
|
|
241
|
+
if (matches.length === 0) return null;
|
|
242
|
+
return matches[matches.length - 1] ?? null;
|
|
243
|
+
}
|
|
224
244
|
|
|
225
245
|
// src/core/skills-client.ts
|
|
226
246
|
import * as path3 from "path";
|
|
@@ -255,7 +275,9 @@ import * as path7 from "path";
|
|
|
255
275
|
import * as fs5 from "fs/promises";
|
|
256
276
|
import {
|
|
257
277
|
replaceManagedRegion,
|
|
258
|
-
hasManagedRegion
|
|
278
|
+
hasManagedRegion,
|
|
279
|
+
extractFrontmatter,
|
|
280
|
+
replaceFrontmatter
|
|
259
281
|
} from "@teamix-evo/registry";
|
|
260
282
|
|
|
261
283
|
// src/ide/QoderAdapter.ts
|
|
@@ -339,6 +361,8 @@ async function loadTemplateFile(filePath) {
|
|
|
339
361
|
// src/utils/path.ts
|
|
340
362
|
import * as path6 from "path";
|
|
341
363
|
import * as fs4 from "fs/promises";
|
|
364
|
+
import { createRequire as createRequire2 } from "module";
|
|
365
|
+
var require3 = createRequire2(import.meta.url);
|
|
342
366
|
function resolveSourcePath(source, variantDir, packageRoot) {
|
|
343
367
|
if (source.startsWith("_template/")) {
|
|
344
368
|
return path6.join(packageRoot, source);
|
|
@@ -358,9 +382,14 @@ async function walkDir(dir) {
|
|
|
358
382
|
}
|
|
359
383
|
return files;
|
|
360
384
|
}
|
|
385
|
+
function resolveTokensPackageRoot(packageName) {
|
|
386
|
+
const pkgJson = require3.resolve(`${packageName}/package.json`);
|
|
387
|
+
return path6.dirname(pkgJson);
|
|
388
|
+
}
|
|
361
389
|
|
|
362
390
|
// src/core/skills-installer.ts
|
|
363
391
|
async function installSkills(options) {
|
|
392
|
+
await migrateLegacySkillsSourceDir(options.projectRoot);
|
|
364
393
|
const { manifest, ides, scope, onlyIds } = options;
|
|
365
394
|
const installed = [];
|
|
366
395
|
const targets = manifest.skills.filter(
|
|
@@ -394,9 +423,9 @@ async function writeSkillSource(skill, options) {
|
|
|
394
423
|
const { data, packageRoot, projectRoot } = options;
|
|
395
424
|
const sourceAbs = path7.resolve(packageRoot, skill.source);
|
|
396
425
|
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
397
|
-
const
|
|
426
|
+
const stat5 = await fs5.stat(sourceAbs);
|
|
398
427
|
const records = [];
|
|
399
|
-
if (
|
|
428
|
+
if (stat5.isFile()) {
|
|
400
429
|
const targetFile = path7.join(targetDir, "SKILL.md");
|
|
401
430
|
const content = await renderSkillContent(sourceAbs, skill, data);
|
|
402
431
|
await writeFileSafe(targetFile, content);
|
|
@@ -463,6 +492,10 @@ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sou
|
|
|
463
492
|
return existing;
|
|
464
493
|
}
|
|
465
494
|
let merged = existing;
|
|
495
|
+
const upstreamFm = extractFrontmatter(sourceContent);
|
|
496
|
+
if (upstreamFm) {
|
|
497
|
+
merged = replaceFrontmatter(merged, upstreamFm);
|
|
498
|
+
}
|
|
466
499
|
for (const id of matchedRegions) {
|
|
467
500
|
const newRegion = extractRegionBody(sourceContent, id);
|
|
468
501
|
if (newRegion === null) continue;
|
|
@@ -524,11 +557,7 @@ async function updateSkills(options) {
|
|
|
524
557
|
if (idFilter && !idFilter.has(skill.id)) continue;
|
|
525
558
|
const skillIdes = skill.ides.filter((i) => ides.includes(i));
|
|
526
559
|
if (skillIdes.length === 0) continue;
|
|
527
|
-
const sourceRecords = await rewriteSkillSource(
|
|
528
|
-
skill,
|
|
529
|
-
options,
|
|
530
|
-
summary
|
|
531
|
-
);
|
|
560
|
+
const sourceRecords = await rewriteSkillSource(skill, options, summary);
|
|
532
561
|
updated.push(...sourceRecords);
|
|
533
562
|
for (const ide of skillIdes) {
|
|
534
563
|
const mirrorRecords = await mirrorSkillToIde(
|
|
@@ -546,8 +575,8 @@ async function rewriteSkillSource(skill, options, summary) {
|
|
|
546
575
|
const { data, packageRoot, projectRoot } = options;
|
|
547
576
|
const sourceAbs = path7.resolve(packageRoot, skill.source);
|
|
548
577
|
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
549
|
-
const
|
|
550
|
-
if (!
|
|
578
|
+
const stat5 = await fs5.stat(sourceAbs);
|
|
579
|
+
if (!stat5.isFile()) {
|
|
551
580
|
await ensureDir(targetDir);
|
|
552
581
|
const entries = await walkDir(sourceAbs);
|
|
553
582
|
const records = [];
|
|
@@ -618,6 +647,10 @@ async function rewriteSingleFile(args) {
|
|
|
618
647
|
}
|
|
619
648
|
const current = await readFileOrNull(targetFile);
|
|
620
649
|
let merged = current ?? newContent;
|
|
650
|
+
const upstreamFm = extractFrontmatter(newContent);
|
|
651
|
+
if (upstreamFm) {
|
|
652
|
+
merged = replaceFrontmatter(merged, upstreamFm);
|
|
653
|
+
}
|
|
621
654
|
for (const regionId of managedRegions ?? []) {
|
|
622
655
|
const re = new RegExp(
|
|
623
656
|
`<!-- teamix-evo:managed:start id="${escapeRegExp(
|
|
@@ -649,6 +682,7 @@ function escapeRegExp(str) {
|
|
|
649
682
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
650
683
|
}
|
|
651
684
|
async function syncSkillsToIdes(options) {
|
|
685
|
+
await migrateLegacySkillsSourceDir(options.projectRoot);
|
|
652
686
|
const { projectRoot, skills, ides, scope, onlyIds } = options;
|
|
653
687
|
const out = [];
|
|
654
688
|
const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
|
|
@@ -692,6 +726,118 @@ async function syncSkillsToIdes(options) {
|
|
|
692
726
|
}
|
|
693
727
|
return { resources: out, count: out.length };
|
|
694
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
|
+
}
|
|
695
841
|
async function removeSkillFiles(records) {
|
|
696
842
|
const removed = [];
|
|
697
843
|
for (const r of records) {
|
|
@@ -700,7 +846,7 @@ async function removeSkillFiles(records) {
|
|
|
700
846
|
removed.push(r.target);
|
|
701
847
|
} catch (err) {
|
|
702
848
|
if (err.code !== "ENOENT") {
|
|
703
|
-
logger.warn(`Failed to remove ${r.target}: ${err
|
|
849
|
+
logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
|
|
704
850
|
}
|
|
705
851
|
}
|
|
706
852
|
}
|
|
@@ -735,11 +881,14 @@ async function ensureMcpJson(projectRoot) {
|
|
|
735
881
|
const mcpPath = path8.join(projectRoot, ".mcp.json");
|
|
736
882
|
if (await fileExists(mcpPath)) return "exists";
|
|
737
883
|
try {
|
|
738
|
-
await writeFileSafe(
|
|
884
|
+
await writeFileSafe(
|
|
885
|
+
mcpPath,
|
|
886
|
+
JSON.stringify(MCP_JSON_CONTENT, null, 2) + "\n"
|
|
887
|
+
);
|
|
739
888
|
logger.debug(`Wrote .mcp.json \u2192 ${mcpPath}`);
|
|
740
889
|
return "created";
|
|
741
890
|
} catch (err) {
|
|
742
|
-
logger.warn(`Failed to write .mcp.json: ${err
|
|
891
|
+
logger.warn(`Failed to write .mcp.json: ${getErrorMessage(err)}`);
|
|
743
892
|
return "failed";
|
|
744
893
|
}
|
|
745
894
|
}
|
|
@@ -784,13 +933,29 @@ async function runSkillsInit(options) {
|
|
|
784
933
|
}
|
|
785
934
|
return true;
|
|
786
935
|
}).map((s) => s.id);
|
|
787
|
-
const skippedSkillIds =
|
|
788
|
-
|
|
936
|
+
const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
|
|
937
|
+
candidateIds,
|
|
938
|
+
manifest,
|
|
939
|
+
existing
|
|
789
940
|
);
|
|
790
|
-
|
|
791
|
-
if (existingSkillsCfg && onlyIds.length === 0) {
|
|
941
|
+
if (existingSkillsCfg && onlyIds.length === 0 && outdatedSkills.length === 0) {
|
|
792
942
|
return { status: "already-initialized" };
|
|
793
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
|
+
}
|
|
794
959
|
return finalizeSkillsInstall({
|
|
795
960
|
projectRoot,
|
|
796
961
|
packageName,
|
|
@@ -802,6 +967,7 @@ async function runSkillsInit(options) {
|
|
|
802
967
|
scope,
|
|
803
968
|
onlyIds,
|
|
804
969
|
skippedSkillIds,
|
|
970
|
+
outdatedSkills,
|
|
805
971
|
existing,
|
|
806
972
|
existingConfig
|
|
807
973
|
});
|
|
@@ -842,10 +1008,11 @@ async function runSkillsAdd(options) {
|
|
|
842
1008
|
}
|
|
843
1009
|
}
|
|
844
1010
|
const existing = await readExistingState(projectRoot, packageName);
|
|
845
|
-
const skippedSkillIds =
|
|
846
|
-
|
|
1011
|
+
const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
|
|
1012
|
+
requestedNames,
|
|
1013
|
+
manifest,
|
|
1014
|
+
existing
|
|
847
1015
|
);
|
|
848
|
-
const onlyIds = requestedNames.filter((n) => !existing.skillIds.has(n));
|
|
849
1016
|
if (onlyIds.length === 0) {
|
|
850
1017
|
return {
|
|
851
1018
|
status: "installed",
|
|
@@ -857,7 +1024,8 @@ async function runSkillsAdd(options) {
|
|
|
857
1024
|
fileCount: 0,
|
|
858
1025
|
resources: [],
|
|
859
1026
|
addedSkillIds: [],
|
|
860
|
-
skippedSkillIds
|
|
1027
|
+
skippedSkillIds,
|
|
1028
|
+
outdatedSkills
|
|
861
1029
|
};
|
|
862
1030
|
}
|
|
863
1031
|
return finalizeSkillsInstall({
|
|
@@ -871,10 +1039,52 @@ async function runSkillsAdd(options) {
|
|
|
871
1039
|
scope,
|
|
872
1040
|
onlyIds,
|
|
873
1041
|
skippedSkillIds,
|
|
1042
|
+
outdatedSkills,
|
|
874
1043
|
existing,
|
|
875
1044
|
existingConfig
|
|
876
1045
|
});
|
|
877
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
|
+
}
|
|
878
1088
|
async function readExistingState(projectRoot, packageName) {
|
|
879
1089
|
const installed = await readInstalledManifest(projectRoot);
|
|
880
1090
|
const pkg = installed?.installed.find((p) => p.package === packageName);
|
|
@@ -899,6 +1109,7 @@ async function finalizeSkillsInstall(args) {
|
|
|
899
1109
|
scope,
|
|
900
1110
|
onlyIds,
|
|
901
1111
|
skippedSkillIds,
|
|
1112
|
+
outdatedSkills,
|
|
902
1113
|
existing,
|
|
903
1114
|
existingConfig
|
|
904
1115
|
} = args;
|
|
@@ -912,8 +1123,8 @@ async function finalizeSkillsInstall(args) {
|
|
|
912
1123
|
onlyIds
|
|
913
1124
|
});
|
|
914
1125
|
const config = existingConfig ?? {
|
|
915
|
-
$schema: "https://teamix-evo.dev/schema/config/
|
|
916
|
-
schemaVersion:
|
|
1126
|
+
$schema: "https://teamix-evo.dev/schema/config/v2.json",
|
|
1127
|
+
schemaVersion: 2,
|
|
917
1128
|
ide: ideIdent,
|
|
918
1129
|
packages: {}
|
|
919
1130
|
};
|
|
@@ -964,6 +1175,10 @@ async function finalizeSkillsInstall(args) {
|
|
|
964
1175
|
}
|
|
965
1176
|
await writeSkillsLock(projectRoot, lock);
|
|
966
1177
|
await ensureMcpJson(projectRoot);
|
|
1178
|
+
try {
|
|
1179
|
+
await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
967
1182
|
return {
|
|
968
1183
|
status: "installed",
|
|
969
1184
|
packageName,
|
|
@@ -974,7 +1189,8 @@ async function finalizeSkillsInstall(args) {
|
|
|
974
1189
|
fileCount: result.count,
|
|
975
1190
|
resources: result.resources,
|
|
976
1191
|
addedSkillIds: onlyIds,
|
|
977
|
-
skippedSkillIds
|
|
1192
|
+
skippedSkillIds,
|
|
1193
|
+
outdatedSkills: outdatedSkills ?? []
|
|
978
1194
|
};
|
|
979
1195
|
}
|
|
980
1196
|
function mergeInstalledResources(existing, next) {
|
|
@@ -996,7 +1212,6 @@ var CONSUMER_OVERRIDES_FILE = "tokens.overrides.css";
|
|
|
996
1212
|
var EMPTY_OVERRIDES_TEMPLATE = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
|
|
997
1213
|
/* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
|
|
998
1214
|
`;
|
|
999
|
-
var require3 = createRequire2(import.meta.url);
|
|
1000
1215
|
async function runTokensInit(options) {
|
|
1001
1216
|
const { projectRoot, variant, ide } = options;
|
|
1002
1217
|
const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE;
|
|
@@ -1060,8 +1275,8 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
|
1060
1275
|
JSON.stringify(lock, null, 2) + "\n"
|
|
1061
1276
|
);
|
|
1062
1277
|
const config = {
|
|
1063
|
-
$schema: "https://teamix-evo.dev/schema/config/
|
|
1064
|
-
schemaVersion:
|
|
1278
|
+
$schema: "https://teamix-evo.dev/schema/config/v2.json",
|
|
1279
|
+
schemaVersion: 2,
|
|
1065
1280
|
ide: existingConfig?.ide ?? ide,
|
|
1066
1281
|
packages: {
|
|
1067
1282
|
...existingConfig?.packages ?? {},
|
|
@@ -1114,7 +1329,9 @@ async function tryAutoInstallVariantSkills(args) {
|
|
|
1114
1329
|
manifestSkillIds = new Set(manifest.skills.map((s) => s.id));
|
|
1115
1330
|
} catch (err) {
|
|
1116
1331
|
logger.warn(
|
|
1117
|
-
`Skipping skills auto-install: could not load skills manifest (${
|
|
1332
|
+
`Skipping skills auto-install: could not load skills manifest (${getErrorMessage(
|
|
1333
|
+
err
|
|
1334
|
+
)}).`
|
|
1118
1335
|
);
|
|
1119
1336
|
return {
|
|
1120
1337
|
attempted: [],
|
|
@@ -1164,7 +1381,7 @@ async function tryAutoInstallVariantSkills(args) {
|
|
|
1164
1381
|
};
|
|
1165
1382
|
} catch (err) {
|
|
1166
1383
|
logger.warn(
|
|
1167
|
-
`Skills auto-install failed (continuing): ${err
|
|
1384
|
+
`Skills auto-install failed (continuing): ${getErrorMessage(err)}`
|
|
1168
1385
|
);
|
|
1169
1386
|
return {
|
|
1170
1387
|
attempted: desired,
|
|
@@ -1178,12 +1395,12 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1178
1395
|
const sourceAbs = path9.join(packageRoot, fileRelToPackage);
|
|
1179
1396
|
const base = path9.basename(fileRelToPackage);
|
|
1180
1397
|
if (base === "theme.css") {
|
|
1181
|
-
const targetRel = path9.posix.join(
|
|
1182
|
-
CONSUMER_TOKENS_DIR,
|
|
1183
|
-
CONSUMER_THEME_FILE
|
|
1184
|
-
);
|
|
1398
|
+
const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
|
|
1185
1399
|
const targetAbs = path9.join(projectRoot, targetRel);
|
|
1186
1400
|
const content = await fs6.readFile(sourceAbs, "utf-8");
|
|
1401
|
+
if (await fileExists(targetAbs)) {
|
|
1402
|
+
await backupFile(targetAbs, projectRoot);
|
|
1403
|
+
}
|
|
1187
1404
|
await writeFileSafe(targetAbs, content);
|
|
1188
1405
|
return {
|
|
1189
1406
|
id: `tokens:${CONSUMER_THEME_FILE}`,
|
|
@@ -1218,10 +1435,6 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1218
1435
|
}
|
|
1219
1436
|
return null;
|
|
1220
1437
|
}
|
|
1221
|
-
function resolveTokensPackageRoot(packageName) {
|
|
1222
|
-
const pkgJson = require3.resolve(`${packageName}/package.json`);
|
|
1223
|
-
return path9.dirname(pkgJson);
|
|
1224
|
-
}
|
|
1225
1438
|
async function listTokenVariants(packageName = DEFAULT_TOKENS_PACKAGE, packageRoot) {
|
|
1226
1439
|
const root = packageRoot ?? resolveTokensPackageRoot(packageName);
|
|
1227
1440
|
const catalog = await loadTokensPackageManifest(root);
|
|
@@ -1438,8 +1651,8 @@ async function runUiInit(options) {
|
|
|
1438
1651
|
const tsx = options.tsx ?? true;
|
|
1439
1652
|
const rsc = options.rsc ?? false;
|
|
1440
1653
|
const config = existingConfig ?? {
|
|
1441
|
-
$schema: "https://teamix-evo.dev/schema/config/
|
|
1442
|
-
schemaVersion:
|
|
1654
|
+
$schema: "https://teamix-evo.dev/schema/config/v2.json",
|
|
1655
|
+
schemaVersion: 2,
|
|
1443
1656
|
ide: ideIdent,
|
|
1444
1657
|
packages: {}
|
|
1445
1658
|
};
|
|
@@ -1568,6 +1781,9 @@ async function installUiEntries(options) {
|
|
|
1568
1781
|
const sourceAbs = path11.resolve(rootForEntry, file.source);
|
|
1569
1782
|
const raw = await fs8.readFile(sourceAbs, "utf-8");
|
|
1570
1783
|
const transformed = rewriteImports(raw, aliases);
|
|
1784
|
+
if (exists) {
|
|
1785
|
+
await backupFile(targetAbs, projectRoot);
|
|
1786
|
+
}
|
|
1571
1787
|
await writeFileSafe(targetAbs, transformed);
|
|
1572
1788
|
written++;
|
|
1573
1789
|
logger.info(` write: ${rel(projectRoot, targetAbs)}`);
|
|
@@ -1607,7 +1823,7 @@ async function removeUiFiles(records) {
|
|
|
1607
1823
|
removed.push(r.target);
|
|
1608
1824
|
} catch (err) {
|
|
1609
1825
|
if (err.code !== "ENOENT") {
|
|
1610
|
-
logger.warn(`Failed to remove ${r.target}: ${err
|
|
1826
|
+
logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
|
|
1611
1827
|
}
|
|
1612
1828
|
}
|
|
1613
1829
|
}
|
|
@@ -1938,18 +2154,29 @@ var ESLINT_DEPS = [
|
|
|
1938
2154
|
];
|
|
1939
2155
|
var STYLELINT_DEPS = ["@teamix-evo/stylelint-config", "stylelint"];
|
|
1940
2156
|
async function runLintInit(options) {
|
|
1941
|
-
const {
|
|
2157
|
+
const {
|
|
2158
|
+
projectRoot,
|
|
2159
|
+
skipInstall,
|
|
2160
|
+
eslintStrategy = "overwrite",
|
|
2161
|
+
stylelintStrategy = "overwrite",
|
|
2162
|
+
eslintExistingPaths = [],
|
|
2163
|
+
stylelintExistingPaths = []
|
|
2164
|
+
} = options;
|
|
1942
2165
|
const eslintConfigPath = path13.join(projectRoot, "eslint.config.js");
|
|
1943
2166
|
const stylelintConfigPath = path13.join(projectRoot, "stylelint.config.cjs");
|
|
1944
|
-
const
|
|
1945
|
-
const
|
|
1946
|
-
|
|
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) {
|
|
1947
2174
|
return { status: "already-initialized" };
|
|
1948
2175
|
}
|
|
1949
2176
|
if (!skipInstall) {
|
|
1950
2177
|
const depsToInstall = [
|
|
1951
|
-
...
|
|
1952
|
-
...
|
|
2178
|
+
...eslintNeedsWrite ? ESLINT_DEPS : [],
|
|
2179
|
+
...stylelintNeedsWrite ? STYLELINT_DEPS : []
|
|
1953
2180
|
];
|
|
1954
2181
|
if (depsToInstall.length > 0) {
|
|
1955
2182
|
const pm = detectPm(projectRoot);
|
|
@@ -1958,23 +2185,38 @@ async function runLintInit(options) {
|
|
|
1958
2185
|
await execa(pm, args, { cwd: projectRoot, stdio: "inherit" });
|
|
1959
2186
|
}
|
|
1960
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
|
+
}
|
|
1961
2198
|
let wroteEslint = false;
|
|
1962
2199
|
let wroteStylelint = false;
|
|
1963
|
-
if (
|
|
2200
|
+
if (eslintNeedsWrite) {
|
|
1964
2201
|
await writeFileSafe(eslintConfigPath, ESLINT_CONFIG_CONTENT);
|
|
1965
2202
|
logger.debug(`Wrote eslint.config.js \u2192 ${eslintConfigPath}`);
|
|
1966
2203
|
wroteEslint = true;
|
|
1967
2204
|
}
|
|
1968
|
-
if (
|
|
2205
|
+
if (stylelintNeedsWrite) {
|
|
1969
2206
|
await writeFileSafe(stylelintConfigPath, STYLELINT_CONFIG_CONTENT);
|
|
1970
2207
|
logger.debug(`Wrote stylelint.config.cjs \u2192 ${stylelintConfigPath}`);
|
|
1971
2208
|
wroteStylelint = true;
|
|
1972
2209
|
}
|
|
1973
|
-
await patchPackageJsonScripts(projectRoot);
|
|
2210
|
+
const packageJsonPatched = await patchPackageJsonScripts(projectRoot);
|
|
1974
2211
|
return {
|
|
1975
2212
|
status: "installed",
|
|
1976
2213
|
eslint: wroteEslint,
|
|
1977
|
-
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
|
|
1978
2220
|
};
|
|
1979
2221
|
}
|
|
1980
2222
|
function detectPm(projectRoot) {
|
|
@@ -1985,12 +2227,12 @@ function detectPm(projectRoot) {
|
|
|
1985
2227
|
async function patchPackageJsonScripts(projectRoot) {
|
|
1986
2228
|
const pkgPath = path13.join(projectRoot, "package.json");
|
|
1987
2229
|
const raw = await readFileOrNull(pkgPath);
|
|
1988
|
-
if (!raw) return;
|
|
2230
|
+
if (!raw) return false;
|
|
1989
2231
|
let pkg;
|
|
1990
2232
|
try {
|
|
1991
2233
|
pkg = JSON.parse(raw);
|
|
1992
2234
|
} catch {
|
|
1993
|
-
return;
|
|
2235
|
+
return false;
|
|
1994
2236
|
}
|
|
1995
2237
|
const scripts = pkg.scripts ?? {};
|
|
1996
2238
|
let changed = false;
|
|
@@ -2003,126 +2245,2639 @@ async function patchPackageJsonScripts(projectRoot) {
|
|
|
2003
2245
|
changed = true;
|
|
2004
2246
|
}
|
|
2005
2247
|
if (changed) {
|
|
2248
|
+
await backupFile(pkgPath, projectRoot);
|
|
2006
2249
|
pkg.scripts = scripts;
|
|
2007
2250
|
await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2008
2251
|
logger.debug("Patched package.json scripts with lint / lint:css");
|
|
2009
2252
|
}
|
|
2253
|
+
return changed;
|
|
2010
2254
|
}
|
|
2011
2255
|
|
|
2012
|
-
// src/core/
|
|
2013
|
-
import * as path14 from "path";
|
|
2256
|
+
// src/core/agents-md.ts
|
|
2014
2257
|
import * as fs10 from "fs/promises";
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
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";
|
|
2261
|
+
async function runGenerateAgentsMd(options) {
|
|
2262
|
+
const { projectRoot, variant, skillIds } = options;
|
|
2263
|
+
const mode = options.mode ?? "overwrite";
|
|
2264
|
+
const ordered = [...skillIds].sort(
|
|
2265
|
+
(a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
|
|
2266
|
+
);
|
|
2267
|
+
const sections = [];
|
|
2268
|
+
const missingSkillIds = [];
|
|
2269
|
+
for (const id of ordered) {
|
|
2270
|
+
const { section, missing } = await renderSkillSection(projectRoot, id);
|
|
2271
|
+
sections.push(section);
|
|
2272
|
+
if (missing) missingSkillIds.push(id);
|
|
2273
|
+
}
|
|
2274
|
+
const target = path14.join(projectRoot, "AGENTS.md");
|
|
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
|
+
}
|
|
2029
2303
|
} else {
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
projectRoot,
|
|
2033
|
-
data,
|
|
2034
|
-
variantDir,
|
|
2035
|
-
packageRoot
|
|
2036
|
-
);
|
|
2037
|
-
installedResources.push(result);
|
|
2304
|
+
outputContent = fullTemplate;
|
|
2305
|
+
merge = "overwritten";
|
|
2038
2306
|
}
|
|
2039
2307
|
}
|
|
2308
|
+
await fs10.writeFile(target, outputContent, "utf8");
|
|
2040
2309
|
return {
|
|
2041
|
-
|
|
2042
|
-
|
|
2310
|
+
path: target,
|
|
2311
|
+
skillCount: ordered.length,
|
|
2312
|
+
missingSkillIds,
|
|
2313
|
+
backedUp: targetExists,
|
|
2314
|
+
merge
|
|
2043
2315
|
};
|
|
2044
2316
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2317
|
+
function bucketRank(id) {
|
|
2318
|
+
if (id.startsWith("teamix-evo-design-")) return 0;
|
|
2319
|
+
if (id.startsWith("teamix-evo-code-")) return 1;
|
|
2320
|
+
return 2;
|
|
2321
|
+
}
|
|
2322
|
+
async function renderSkillSection(projectRoot, skillId) {
|
|
2323
|
+
const skillPath = path14.join(
|
|
2324
|
+
getSkillsSourceDir(projectRoot, skillId),
|
|
2325
|
+
"SKILL.md"
|
|
2050
2326
|
);
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2327
|
+
const lines = [];
|
|
2328
|
+
lines.push(`### ${skillId}`);
|
|
2329
|
+
let parts = null;
|
|
2330
|
+
let missing = false;
|
|
2331
|
+
try {
|
|
2332
|
+
const raw = await fs10.readFile(skillPath, "utf8");
|
|
2333
|
+
parts = extractDescriptionParts(raw);
|
|
2334
|
+
} catch {
|
|
2335
|
+
missing = true;
|
|
2336
|
+
}
|
|
2337
|
+
if (parts?.capability) {
|
|
2338
|
+
lines.push(`- ${parts.capability}`);
|
|
2339
|
+
}
|
|
2340
|
+
lines.push(
|
|
2341
|
+
`- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
|
|
2342
|
+
);
|
|
2343
|
+
lines.push(
|
|
2344
|
+
`- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
|
|
2345
|
+
);
|
|
2346
|
+
if (parts?.coordinates) {
|
|
2347
|
+
lines.push(`- **Coordinates with**: ${parts.coordinates}`);
|
|
2348
|
+
}
|
|
2349
|
+
lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills-source/${skillId}/SKILL.md\``);
|
|
2350
|
+
return { section: lines.join("\n"), missing };
|
|
2351
|
+
}
|
|
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) {
|
|
2362
|
+
const { variant, sections } = args;
|
|
2363
|
+
const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
|
|
2364
|
+
return `# AGENTS.md
|
|
2365
|
+
|
|
2366
|
+
> \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
|
|
2367
|
+
> \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
|
|
2368
|
+
|
|
2369
|
+
## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
|
|
2370
|
+
|
|
2371
|
+
${skillBlock}
|
|
2372
|
+
|
|
2373
|
+
## \u89E6\u53D1\u515C\u5E95\u89C4\u5219
|
|
2374
|
+
|
|
2375
|
+
- \u5199\u65B0 \`.tsx\` / \`.ts\` \u524D\uFF0C\u5BF9\u7167\u4E0A\u8FF0 TRIGGER \u5224\u5B9A\u662F\u5426\u547D\u4E2D
|
|
2376
|
+
- \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
|
|
2377
|
+
- \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
|
|
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
|
|
2379
|
+
|
|
2380
|
+
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
|
|
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`;
|
|
2389
|
+
function extractDescriptionParts(fileContent) {
|
|
2390
|
+
const description = extractDescriptionBlock(fileContent);
|
|
2391
|
+
if (description == null) return null;
|
|
2392
|
+
const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2393
|
+
let capability = "";
|
|
2394
|
+
for (const line of allLines) {
|
|
2395
|
+
if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
|
|
2396
|
+
capability = capability ? `${capability} ${line}` : line;
|
|
2058
2397
|
}
|
|
2059
|
-
await writeFileSafe(targetPath, content);
|
|
2060
|
-
const hash = computeHash(content);
|
|
2061
|
-
logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
|
|
2062
2398
|
return {
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2399
|
+
capability: capability.trim(),
|
|
2400
|
+
trigger: extractSection(description, "TRIGGER when:"),
|
|
2401
|
+
skip: extractSection(description, "SKIP:"),
|
|
2402
|
+
coordinates: extractSection(description, "Coordinates with:")
|
|
2067
2403
|
};
|
|
2068
2404
|
}
|
|
2069
|
-
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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);
|
|
2405
|
+
function extractDescriptionBlock(fileContent) {
|
|
2406
|
+
const lines = fileContent.split("\n");
|
|
2407
|
+
if (lines[0]?.trim() !== "---") return null;
|
|
2408
|
+
let endIdx = -1;
|
|
2409
|
+
for (let i = 1; i < lines.length; i++) {
|
|
2410
|
+
if (lines[i]?.trim() === "---") {
|
|
2411
|
+
endIdx = i;
|
|
2412
|
+
break;
|
|
2084
2413
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2414
|
+
}
|
|
2415
|
+
if (endIdx === -1) return null;
|
|
2416
|
+
const fmLines = lines.slice(1, endIdx);
|
|
2417
|
+
let startIdx = -1;
|
|
2418
|
+
let inlineValue = null;
|
|
2419
|
+
let blockMode = "inline";
|
|
2420
|
+
for (let i = 0; i < fmLines.length; i++) {
|
|
2421
|
+
const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
|
|
2422
|
+
if (m) {
|
|
2423
|
+
startIdx = i;
|
|
2424
|
+
const indicator = (m[1] ?? "").trim();
|
|
2425
|
+
const rest = m[2] ?? "";
|
|
2426
|
+
if (indicator.startsWith("|")) blockMode = "literal";
|
|
2427
|
+
else if (indicator.startsWith(">")) blockMode = "folded";
|
|
2428
|
+
else {
|
|
2429
|
+
blockMode = "inline";
|
|
2430
|
+
inlineValue = rest;
|
|
2431
|
+
}
|
|
2432
|
+
break;
|
|
2091
2433
|
}
|
|
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
|
|
2100
|
-
});
|
|
2101
|
-
logger.debug(` Written: ${targetRel}`);
|
|
2102
2434
|
}
|
|
2103
|
-
return
|
|
2435
|
+
if (startIdx === -1) return null;
|
|
2436
|
+
if (blockMode === "inline") {
|
|
2437
|
+
return inlineValue ?? "";
|
|
2438
|
+
}
|
|
2439
|
+
const body = [];
|
|
2440
|
+
let blockIndent = -1;
|
|
2441
|
+
for (let i = startIdx + 1; i < fmLines.length; i++) {
|
|
2442
|
+
const line = fmLines[i] ?? "";
|
|
2443
|
+
if (line.trim() === "") {
|
|
2444
|
+
body.push("");
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
const indentMatch = line.match(/^(\s+)/);
|
|
2448
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
2449
|
+
if (indent === 0) break;
|
|
2450
|
+
if (blockIndent === -1) blockIndent = indent;
|
|
2451
|
+
if (indent < blockIndent) break;
|
|
2452
|
+
body.push(line.slice(blockIndent));
|
|
2453
|
+
}
|
|
2454
|
+
while (body.length > 0 && body[body.length - 1] === "") body.pop();
|
|
2455
|
+
return body.join("\n");
|
|
2456
|
+
}
|
|
2457
|
+
function extractSection(description, marker) {
|
|
2458
|
+
const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
|
|
2459
|
+
const lines = description.split("\n");
|
|
2460
|
+
let inSection = false;
|
|
2461
|
+
const collected = [];
|
|
2462
|
+
for (const line of lines) {
|
|
2463
|
+
const trimmed = line.trim();
|
|
2464
|
+
const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
|
|
2465
|
+
if (!inSection && startsWithMarker) {
|
|
2466
|
+
inSection = true;
|
|
2467
|
+
collected.push(trimmed.slice(marker.length).trim());
|
|
2468
|
+
continue;
|
|
2469
|
+
}
|
|
2470
|
+
if (inSection) {
|
|
2471
|
+
const hitNextMarker = markers.some(
|
|
2472
|
+
(m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
|
|
2473
|
+
);
|
|
2474
|
+
if (hitNextMarker) break;
|
|
2475
|
+
if (trimmed === "") {
|
|
2476
|
+
if (collected[collected.length - 1] === "") break;
|
|
2477
|
+
collected.push("");
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
2480
|
+
collected.push(trimmed);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
if (!inSection) return null;
|
|
2484
|
+
const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
|
|
2485
|
+
return joined || null;
|
|
2104
2486
|
}
|
|
2105
2487
|
|
|
2106
|
-
// src/core/
|
|
2107
|
-
import * as path15 from "path";
|
|
2488
|
+
// src/core/init-detect.ts
|
|
2108
2489
|
import * as fs11 from "fs/promises";
|
|
2109
|
-
import
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2490
|
+
import * as path15 from "path";
|
|
2491
|
+
var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
2492
|
+
".git",
|
|
2493
|
+
".gitignore",
|
|
2494
|
+
".gitattributes",
|
|
2495
|
+
".gitkeep",
|
|
2496
|
+
".DS_Store",
|
|
2497
|
+
"Thumbs.db",
|
|
2498
|
+
".idea",
|
|
2499
|
+
".vscode",
|
|
2500
|
+
".qoder",
|
|
2501
|
+
".claude",
|
|
2502
|
+
"README.md",
|
|
2503
|
+
"README",
|
|
2504
|
+
"README.txt",
|
|
2505
|
+
"LICENSE",
|
|
2506
|
+
"LICENSE.md",
|
|
2507
|
+
"LICENSE.txt"
|
|
2508
|
+
]);
|
|
2509
|
+
async function detectProjectState(cwd) {
|
|
2510
|
+
const absCwd = path15.resolve(cwd);
|
|
2511
|
+
const teamixDir = getTeamixDir(absCwd);
|
|
2512
|
+
const hasTeamixDir = await fileExists(teamixDir);
|
|
2513
|
+
if (hasTeamixDir) {
|
|
2514
|
+
return {
|
|
2515
|
+
state: "teamix-evo-installed",
|
|
2516
|
+
cwd: absCwd,
|
|
2517
|
+
hasTeamixDir: true,
|
|
2518
|
+
hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
|
|
2519
|
+
significantEntries: []
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
let entries;
|
|
2523
|
+
try {
|
|
2524
|
+
entries = await fs11.readdir(absCwd);
|
|
2525
|
+
} catch (err) {
|
|
2526
|
+
if (err.code === "ENOENT") {
|
|
2527
|
+
return {
|
|
2528
|
+
state: "empty",
|
|
2529
|
+
cwd: absCwd,
|
|
2530
|
+
hasTeamixDir: false,
|
|
2531
|
+
hasPackageJson: false,
|
|
2532
|
+
significantEntries: []
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
throw err;
|
|
2536
|
+
}
|
|
2537
|
+
const significant = entries.filter((e) => !IGNORED_TOP_LEVEL.has(e));
|
|
2538
|
+
const hasPackageJson = entries.includes("package.json");
|
|
2539
|
+
if (significant.length === 0) {
|
|
2540
|
+
return {
|
|
2541
|
+
state: "empty",
|
|
2542
|
+
cwd: absCwd,
|
|
2543
|
+
hasTeamixDir: false,
|
|
2544
|
+
hasPackageJson: false,
|
|
2545
|
+
significantEntries: []
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
return {
|
|
2549
|
+
state: "non-teamix-evo",
|
|
2550
|
+
cwd: absCwd,
|
|
2551
|
+
hasTeamixDir: false,
|
|
2552
|
+
hasPackageJson,
|
|
2553
|
+
significantEntries: significant.slice(0, 20).sort()
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// src/core/init-conflicts.ts
|
|
2558
|
+
import * as crypto from "crypto";
|
|
2559
|
+
import * as fs12 from "fs/promises";
|
|
2560
|
+
import * as path16 from "path";
|
|
2561
|
+
var TAILWIND_CONFIG_CANDIDATES = [
|
|
2562
|
+
"tailwind.config.ts",
|
|
2563
|
+
"tailwind.config.js",
|
|
2564
|
+
"tailwind.config.cjs",
|
|
2565
|
+
"tailwind.config.mjs"
|
|
2566
|
+
];
|
|
2567
|
+
var TOKENS_FILE_CANDIDATES = [
|
|
2568
|
+
"src/design-tokens.css",
|
|
2569
|
+
"src/styles/design-tokens.css",
|
|
2570
|
+
"src/styles/tokens.css"
|
|
2571
|
+
];
|
|
2572
|
+
var TOKENS_DIR_CANDIDATES = ["tokens"];
|
|
2573
|
+
var INDEX_CSS_CANDIDATES = [
|
|
2574
|
+
"src/index.css",
|
|
2575
|
+
"src/main.css",
|
|
2576
|
+
"src/app.css",
|
|
2577
|
+
"src/styles/index.css",
|
|
2578
|
+
"src/styles/globals.css"
|
|
2579
|
+
];
|
|
2580
|
+
var SHADCN_FILE_CANDIDATES = ["src/lib/utils.ts"];
|
|
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
|
+
];
|
|
2602
|
+
async function isDir(target) {
|
|
2603
|
+
try {
|
|
2604
|
+
const stat5 = await fs12.stat(target);
|
|
2605
|
+
return stat5.isDirectory();
|
|
2606
|
+
} catch {
|
|
2607
|
+
return false;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
async function dirHasContent(target) {
|
|
2611
|
+
try {
|
|
2612
|
+
const entries = await fs12.readdir(target);
|
|
2613
|
+
return entries.length > 0;
|
|
2614
|
+
} catch {
|
|
2615
|
+
return false;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
function fingerprint(parts) {
|
|
2619
|
+
const hash = crypto.createHash("sha256");
|
|
2620
|
+
for (const p of [...parts].sort()) hash.update(p);
|
|
2621
|
+
return `sha256:${hash.digest("hex").slice(0, 16)}`;
|
|
2622
|
+
}
|
|
2623
|
+
async function readPackageJson(cwd) {
|
|
2624
|
+
const raw = await readFileOrNull(path16.join(cwd, "package.json"));
|
|
2625
|
+
if (raw === null) return null;
|
|
2626
|
+
try {
|
|
2627
|
+
return JSON.parse(raw);
|
|
2628
|
+
} catch {
|
|
2629
|
+
return null;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
function detectTailwindMajor(pkg) {
|
|
2633
|
+
if (!pkg) return null;
|
|
2634
|
+
const v = pkg.dependencies?.tailwindcss ?? pkg.devDependencies?.tailwindcss ?? null;
|
|
2635
|
+
if (!v) return null;
|
|
2636
|
+
const m = /(\d+)/.exec(v);
|
|
2637
|
+
if (!m) return null;
|
|
2638
|
+
const major = Number.parseInt(m[1], 10);
|
|
2639
|
+
if (major === 3) return 3;
|
|
2640
|
+
if (major === 4) return 4;
|
|
2641
|
+
return null;
|
|
2642
|
+
}
|
|
2643
|
+
async function detectAgentsMd(cwd) {
|
|
2644
|
+
const target = path16.join(cwd, "AGENTS.md");
|
|
2645
|
+
const content = await readFileOrNull(target);
|
|
2646
|
+
const exists = content !== null;
|
|
2647
|
+
return {
|
|
2648
|
+
key: "agents-md",
|
|
2649
|
+
exists,
|
|
2650
|
+
paths: exists ? ["AGENTS.md"] : [],
|
|
2651
|
+
fingerprint: exists ? fingerprint([content]) : void 0,
|
|
2652
|
+
recommendedStrategy: exists ? "merge-managed" : "overwrite",
|
|
2653
|
+
availableStrategies: ["merge-managed", "overwrite", "skip"]
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
async function detectComponentsJson(cwd) {
|
|
2657
|
+
const target = path16.join(cwd, "components.json");
|
|
2658
|
+
const content = await readFileOrNull(target);
|
|
2659
|
+
const exists = content !== null;
|
|
2660
|
+
return {
|
|
2661
|
+
key: "components-json",
|
|
2662
|
+
exists,
|
|
2663
|
+
paths: exists ? ["components.json"] : [],
|
|
2664
|
+
fingerprint: exists ? fingerprint([content]) : void 0,
|
|
2665
|
+
recommendedStrategy: exists ? "diff-prompt" : "overwrite",
|
|
2666
|
+
availableStrategies: ["diff-prompt", "overwrite", "skip"]
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
async function detectTailwindConfig(cwd) {
|
|
2670
|
+
const matched = [];
|
|
2671
|
+
const contents = [];
|
|
2672
|
+
for (const rel2 of TAILWIND_CONFIG_CANDIDATES) {
|
|
2673
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2674
|
+
if (c !== null) {
|
|
2675
|
+
matched.push(rel2);
|
|
2676
|
+
contents.push(c);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
const pkg = await readPackageJson(cwd);
|
|
2680
|
+
const tailwindMajor = detectTailwindMajor(pkg);
|
|
2681
|
+
const exists = matched.length > 0;
|
|
2682
|
+
return {
|
|
2683
|
+
key: "tailwind-config",
|
|
2684
|
+
exists,
|
|
2685
|
+
paths: matched,
|
|
2686
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2687
|
+
recommendedStrategy: exists ? "backup-overwrite" : "overwrite",
|
|
2688
|
+
availableStrategies: ["backup-overwrite", "overwrite", "skip"],
|
|
2689
|
+
meta: { tailwindMajor }
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
async function detectTokens(cwd) {
|
|
2693
|
+
const matched = [];
|
|
2694
|
+
const contents = [];
|
|
2695
|
+
for (const rel2 of TOKENS_FILE_CANDIDATES) {
|
|
2696
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2697
|
+
if (c !== null) {
|
|
2698
|
+
matched.push(rel2);
|
|
2699
|
+
contents.push(c);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
for (const rel2 of TOKENS_DIR_CANDIDATES) {
|
|
2703
|
+
const abs = path16.join(cwd, rel2);
|
|
2704
|
+
if (await isDir(abs) && await dirHasContent(abs)) {
|
|
2705
|
+
matched.push(`${rel2}/`);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
const exists = matched.length > 0;
|
|
2709
|
+
return {
|
|
2710
|
+
key: "tokens",
|
|
2711
|
+
exists,
|
|
2712
|
+
paths: matched,
|
|
2713
|
+
fingerprint: contents.length > 0 ? fingerprint(contents) : void 0,
|
|
2714
|
+
recommendedStrategy: exists ? "migrate" : "overwrite",
|
|
2715
|
+
availableStrategies: ["migrate", "coexist", "overwrite", "skip"]
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
async function detectIndexCss(cwd) {
|
|
2719
|
+
const matched = [];
|
|
2720
|
+
const contents = [];
|
|
2721
|
+
for (const rel2 of INDEX_CSS_CANDIDATES) {
|
|
2722
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2723
|
+
if (c !== null) {
|
|
2724
|
+
matched.push(rel2);
|
|
2725
|
+
contents.push(c);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
const exists = matched.length > 0;
|
|
2729
|
+
return {
|
|
2730
|
+
key: "index-css",
|
|
2731
|
+
exists,
|
|
2732
|
+
paths: matched,
|
|
2733
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2734
|
+
recommendedStrategy: exists ? "append" : "overwrite",
|
|
2735
|
+
availableStrategies: ["append", "diff-prompt", "overwrite", "skip"]
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
async function detectShadcnSource(cwd) {
|
|
2739
|
+
const matched = [];
|
|
2740
|
+
for (const rel2 of SHADCN_FILE_CANDIDATES) {
|
|
2741
|
+
if (await fileExists(path16.join(cwd, rel2))) matched.push(rel2);
|
|
2742
|
+
}
|
|
2743
|
+
for (const rel2 of SHADCN_DIR_CANDIDATES) {
|
|
2744
|
+
const abs = path16.join(cwd, rel2);
|
|
2745
|
+
if (await isDir(abs) && await dirHasContent(abs)) {
|
|
2746
|
+
matched.push(`${rel2}/`);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
let componentCount = 0;
|
|
2750
|
+
try {
|
|
2751
|
+
const uiDir = path16.join(cwd, "src/components/ui");
|
|
2752
|
+
if (await isDir(uiDir)) {
|
|
2753
|
+
const entries = await fs12.readdir(uiDir);
|
|
2754
|
+
componentCount = entries.filter(
|
|
2755
|
+
(e) => e.endsWith(".tsx") || e.endsWith(".ts")
|
|
2756
|
+
).length;
|
|
2757
|
+
}
|
|
2758
|
+
} catch {
|
|
2759
|
+
}
|
|
2760
|
+
const exists = matched.length > 0;
|
|
2761
|
+
return {
|
|
2762
|
+
key: "shadcn-source",
|
|
2763
|
+
exists,
|
|
2764
|
+
paths: matched,
|
|
2765
|
+
recommendedStrategy: exists ? "skip-existing" : "overwrite",
|
|
2766
|
+
availableStrategies: ["skip-existing", "per-file-prompt", "overwrite"],
|
|
2767
|
+
meta: { componentCount }
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
async function detectConflicts(cwd) {
|
|
2771
|
+
const absCwd = path16.resolve(cwd);
|
|
2772
|
+
const items = await Promise.all([
|
|
2773
|
+
detectAgentsMd(absCwd),
|
|
2774
|
+
detectComponentsJson(absCwd),
|
|
2775
|
+
detectTailwindConfig(absCwd),
|
|
2776
|
+
detectTokens(absCwd),
|
|
2777
|
+
detectIndexCss(absCwd),
|
|
2778
|
+
detectShadcnSource(absCwd),
|
|
2779
|
+
detectEslintConfig(absCwd),
|
|
2780
|
+
detectStylelintConfig(absCwd)
|
|
2781
|
+
]);
|
|
2782
|
+
return {
|
|
2783
|
+
cwd: absCwd,
|
|
2784
|
+
items,
|
|
2785
|
+
hasAnyConflict: items.some((i) => i.exists)
|
|
2786
|
+
};
|
|
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
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// src/core/legacy-tokens-migrate.ts
|
|
2830
|
+
import * as path17 from "path";
|
|
2831
|
+
import * as fs13 from "fs/promises";
|
|
2832
|
+
var CONSUMER_TOKENS_DIR2 = "tokens";
|
|
2833
|
+
var CONSUMER_OVERRIDES_FILE2 = "tokens.overrides.css";
|
|
2834
|
+
var EMPTY_OVERRIDES_TEMPLATE2 = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
|
|
2835
|
+
/* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
|
|
2836
|
+
`;
|
|
2837
|
+
function buildMigrationBanner(legacyRel, isoTimestamp) {
|
|
2838
|
+
return `/* === Migrated from ${legacyRel} on ${isoTimestamp} === */
|
|
2839
|
+
`;
|
|
2840
|
+
}
|
|
2841
|
+
function computeBackupRel(legacyRel, isoTimestamp) {
|
|
2842
|
+
const tsSafe = isoTimestamp.replace(/[:.]/g, "-");
|
|
2843
|
+
return path17.posix.join(
|
|
2844
|
+
".teamix-evo",
|
|
2845
|
+
".backups",
|
|
2846
|
+
`${legacyRel}.${tsSafe}.bak`
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
async function migrateLegacyTokens(options) {
|
|
2850
|
+
const { projectRoot, legacyPaths, dryRun = false } = options;
|
|
2851
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2852
|
+
const uniqueLegacy = [];
|
|
2853
|
+
for (const raw of legacyPaths) {
|
|
2854
|
+
const norm = raw.split(path17.sep).join("/");
|
|
2855
|
+
if (!seen.has(norm)) {
|
|
2856
|
+
seen.add(norm);
|
|
2857
|
+
uniqueLegacy.push(norm);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
const overridesRel = path17.posix.join(
|
|
2861
|
+
CONSUMER_TOKENS_DIR2,
|
|
2862
|
+
CONSUMER_OVERRIDES_FILE2
|
|
2863
|
+
);
|
|
2864
|
+
const overridesAbs = path17.join(projectRoot, overridesRel);
|
|
2865
|
+
const existingOverrides = await readFileOrNull(overridesAbs);
|
|
2866
|
+
const overridesCreated = existingOverrides === null;
|
|
2867
|
+
const baseContent = existingOverrides === null ? EMPTY_OVERRIDES_TEMPLATE2 : existingOverrides;
|
|
2868
|
+
const migrated = [];
|
|
2869
|
+
const skipped = [];
|
|
2870
|
+
const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2871
|
+
let appendedPayload = "";
|
|
2872
|
+
for (const legacyRel of uniqueLegacy) {
|
|
2873
|
+
const legacyAbs = path17.join(projectRoot, legacyRel);
|
|
2874
|
+
const content = await readFileOrNull(legacyAbs);
|
|
2875
|
+
if (content === null) {
|
|
2876
|
+
skipped.push({ from: legacyRel, reason: "not-found" });
|
|
2877
|
+
continue;
|
|
2878
|
+
}
|
|
2879
|
+
if (content.trim() === "") {
|
|
2880
|
+
skipped.push({ from: legacyRel, reason: "empty" });
|
|
2881
|
+
continue;
|
|
2882
|
+
}
|
|
2883
|
+
const banner = buildMigrationBanner(legacyRel, isoTimestamp);
|
|
2884
|
+
const separator = appendedPayload === "" && baseContent.endsWith("\n\n") ? "" : "\n";
|
|
2885
|
+
const block = `${separator}${banner}${content}${content.endsWith("\n") ? "" : "\n"}`;
|
|
2886
|
+
appendedPayload += block;
|
|
2887
|
+
const backupRel = dryRun ? null : computeBackupRel(legacyRel, isoTimestamp);
|
|
2888
|
+
migrated.push({
|
|
2889
|
+
from: legacyRel,
|
|
2890
|
+
backupTo: backupRel,
|
|
2891
|
+
bytesAppended: Buffer.byteLength(block, "utf-8")
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
if (dryRun || migrated.length === 0) {
|
|
2895
|
+
return {
|
|
2896
|
+
migrated,
|
|
2897
|
+
skipped,
|
|
2898
|
+
overridesPath: overridesRel,
|
|
2899
|
+
overridesCreated: dryRun ? overridesCreated : false,
|
|
2900
|
+
dryRun
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
await ensureDir(path17.dirname(overridesAbs));
|
|
2904
|
+
const merged = baseContent + appendedPayload;
|
|
2905
|
+
const tmp = overridesAbs + ".tmp";
|
|
2906
|
+
await fs13.writeFile(tmp, merged, "utf-8");
|
|
2907
|
+
await fs13.rename(tmp, overridesAbs);
|
|
2908
|
+
for (const entry of migrated) {
|
|
2909
|
+
const legacyAbs = path17.join(projectRoot, entry.from);
|
|
2910
|
+
const backupAbs = path17.join(projectRoot, entry.backupTo);
|
|
2911
|
+
await ensureDir(path17.dirname(backupAbs));
|
|
2912
|
+
const srcContent = await readFileOrNull(legacyAbs);
|
|
2913
|
+
if (srcContent === null) {
|
|
2914
|
+
logger.debug(
|
|
2915
|
+
`legacy-tokens-migrate: source disappeared before backup: ${entry.from}`
|
|
2916
|
+
);
|
|
2917
|
+
continue;
|
|
2918
|
+
}
|
|
2919
|
+
await fs13.writeFile(backupAbs, srcContent, "utf-8");
|
|
2920
|
+
if (await fileExists(legacyAbs)) {
|
|
2921
|
+
await fs13.unlink(legacyAbs);
|
|
2922
|
+
}
|
|
2923
|
+
logger.debug(
|
|
2924
|
+
`legacy-tokens-migrate: ${entry.from} \u2192 ${overridesRel} (backup: ${entry.backupTo})`
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
return {
|
|
2928
|
+
migrated,
|
|
2929
|
+
skipped,
|
|
2930
|
+
overridesPath: overridesRel,
|
|
2931
|
+
overridesCreated,
|
|
2932
|
+
dryRun: false
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// src/core/snapshot.ts
|
|
2937
|
+
import * as fs14 from "fs/promises";
|
|
2938
|
+
import * as path18 from "path";
|
|
2939
|
+
var TEAMIX_DIR2 = ".teamix-evo";
|
|
2940
|
+
var SNAPSHOTS_DIR = ".snapshots";
|
|
2941
|
+
var LOGS_DIR = "logs";
|
|
2942
|
+
var META_FILE = "_meta.json";
|
|
2943
|
+
var DEFAULT_KEEP = 5;
|
|
2944
|
+
function isoToFsSafe(iso) {
|
|
2945
|
+
return iso.replace(/[:.]/g, "-");
|
|
2946
|
+
}
|
|
2947
|
+
function fsSafeToIso(safe) {
|
|
2948
|
+
return safe.replace(
|
|
2949
|
+
/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})(Z)$/,
|
|
2950
|
+
"$1T$2:$3:$4.$5$6"
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
async function createSnapshot(projectRoot, opts = {}) {
|
|
2954
|
+
const teamixDir = path18.join(projectRoot, TEAMIX_DIR2);
|
|
2955
|
+
try {
|
|
2956
|
+
const stat5 = await fs14.stat(teamixDir);
|
|
2957
|
+
if (!stat5.isDirectory()) return null;
|
|
2958
|
+
} catch (err) {
|
|
2959
|
+
if (err.code === "ENOENT") return null;
|
|
2960
|
+
throw err;
|
|
2961
|
+
}
|
|
2962
|
+
const isoTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
2963
|
+
const ts = isoToFsSafe(isoTs);
|
|
2964
|
+
const snapshotRoot = path18.join(teamixDir, SNAPSHOTS_DIR);
|
|
2965
|
+
const target = path18.join(snapshotRoot, ts);
|
|
2966
|
+
await fs14.mkdir(target, { recursive: true });
|
|
2967
|
+
const entries = await fs14.readdir(teamixDir, { withFileTypes: true });
|
|
2968
|
+
for (const entry of entries) {
|
|
2969
|
+
if (entry.name === SNAPSHOTS_DIR) continue;
|
|
2970
|
+
if (entry.name === LOGS_DIR) continue;
|
|
2971
|
+
const src = path18.join(teamixDir, entry.name);
|
|
2972
|
+
const dst = path18.join(target, entry.name);
|
|
2973
|
+
await fs14.cp(src, dst, { recursive: true });
|
|
2974
|
+
}
|
|
2975
|
+
const meta = {
|
|
2976
|
+
ts: isoTs,
|
|
2977
|
+
reason: opts.reason ?? "manual"
|
|
2978
|
+
};
|
|
2979
|
+
await fs14.writeFile(
|
|
2980
|
+
path18.join(target, META_FILE),
|
|
2981
|
+
JSON.stringify(meta, null, 2) + "\n",
|
|
2982
|
+
"utf-8"
|
|
2983
|
+
);
|
|
2984
|
+
logger.debug(
|
|
2985
|
+
`Snapshot created \u2192 ${path18.relative(projectRoot, target)} (${meta.reason})`
|
|
2986
|
+
);
|
|
2987
|
+
const keep = opts.keep ?? DEFAULT_KEEP;
|
|
2988
|
+
await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
|
|
2989
|
+
return { ts, path: target };
|
|
2990
|
+
}
|
|
2991
|
+
async function listSnapshots(projectRoot) {
|
|
2992
|
+
const snapshotRoot = path18.join(projectRoot, TEAMIX_DIR2, SNAPSHOTS_DIR);
|
|
2993
|
+
let entries;
|
|
2994
|
+
try {
|
|
2995
|
+
entries = await fs14.readdir(snapshotRoot, { withFileTypes: true });
|
|
2996
|
+
} catch (err) {
|
|
2997
|
+
if (err.code === "ENOENT") return [];
|
|
2998
|
+
throw err;
|
|
2999
|
+
}
|
|
3000
|
+
const result = [];
|
|
3001
|
+
for (const entry of entries) {
|
|
3002
|
+
if (!entry.isDirectory()) continue;
|
|
3003
|
+
const dir = path18.join(snapshotRoot, entry.name);
|
|
3004
|
+
let isoTs = null;
|
|
3005
|
+
let reason = null;
|
|
3006
|
+
try {
|
|
3007
|
+
const raw = await fs14.readFile(path18.join(dir, META_FILE), "utf-8");
|
|
3008
|
+
const parsed = JSON.parse(raw);
|
|
3009
|
+
if (typeof parsed.ts === "string") isoTs = parsed.ts;
|
|
3010
|
+
if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
|
|
3011
|
+
parsed.reason
|
|
3012
|
+
)) {
|
|
3013
|
+
reason = parsed.reason;
|
|
3014
|
+
}
|
|
3015
|
+
} catch {
|
|
3016
|
+
isoTs = fsSafeToIso(entry.name);
|
|
3017
|
+
}
|
|
3018
|
+
result.push({ ts: entry.name, isoTs, reason, path: dir });
|
|
3019
|
+
}
|
|
3020
|
+
result.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0);
|
|
3021
|
+
return result;
|
|
3022
|
+
}
|
|
3023
|
+
async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
|
|
3024
|
+
if (keep < 0)
|
|
3025
|
+
throw new Error(`pruneSnapshots: keep must be >= 0, got ${keep}`);
|
|
3026
|
+
const snapshots = await listSnapshots(projectRoot);
|
|
3027
|
+
if (snapshots.length <= keep) return [];
|
|
3028
|
+
const tail = snapshots.slice(keep);
|
|
3029
|
+
const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
|
|
3030
|
+
const removed = [];
|
|
3031
|
+
for (const snap of toRemove) {
|
|
3032
|
+
await fs14.rm(snap.path, { recursive: true, force: true });
|
|
3033
|
+
removed.push(snap.ts);
|
|
3034
|
+
logger.debug(`Pruned snapshot ${snap.ts}`);
|
|
3035
|
+
}
|
|
3036
|
+
return removed.reverse();
|
|
3037
|
+
}
|
|
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
|
+
|
|
3089
|
+
// src/core/project-init.ts
|
|
3090
|
+
var BASELINE_UI_ENTRIES = [
|
|
3091
|
+
"button",
|
|
3092
|
+
"button-group",
|
|
3093
|
+
"input",
|
|
3094
|
+
"form",
|
|
3095
|
+
"card",
|
|
3096
|
+
"collapsible",
|
|
3097
|
+
"dialog",
|
|
3098
|
+
"dropdown-menu",
|
|
3099
|
+
"tabs",
|
|
3100
|
+
"table",
|
|
3101
|
+
"sidebar",
|
|
3102
|
+
"page-shell",
|
|
3103
|
+
"page-header"
|
|
3104
|
+
];
|
|
3105
|
+
var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
3106
|
+
"tokens",
|
|
3107
|
+
"skills",
|
|
3108
|
+
"ui-init"
|
|
3109
|
+
]);
|
|
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`.
|
|
3114
|
+
"agents-md": ["merge-managed", "overwrite", "skip"],
|
|
3115
|
+
tokens: ["migrate", "overwrite", "skip"],
|
|
3116
|
+
"components-json": ["overwrite", "skip"],
|
|
3117
|
+
"shadcn-source": ["overwrite", "skip-existing", "skip"],
|
|
3118
|
+
"tailwind-config": ["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"]
|
|
3124
|
+
};
|
|
3125
|
+
function pickIde(ides) {
|
|
3126
|
+
return ides[0] ?? "qoder";
|
|
3127
|
+
}
|
|
3128
|
+
function strategyImplemented(key, strategy) {
|
|
3129
|
+
return IMPLEMENTED_STRATEGIES[key]?.includes(strategy) ?? false;
|
|
3130
|
+
}
|
|
3131
|
+
async function resolveUiEntries(options) {
|
|
3132
|
+
if (options.uiEntries && options.uiEntries.length > 0) {
|
|
3133
|
+
return [...options.uiEntries];
|
|
3134
|
+
}
|
|
3135
|
+
if (options.answers.uiSelection === "all") {
|
|
3136
|
+
const { manifest } = await loadUiData("@teamix-evo/ui");
|
|
3137
|
+
return manifest.entries.map((e) => e.id);
|
|
3138
|
+
}
|
|
3139
|
+
return [...BASELINE_UI_ENTRIES];
|
|
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
|
+
}
|
|
3203
|
+
async function runProjectInit(options) {
|
|
3204
|
+
const { projectRoot, answers, dryRun = false, onStep } = options;
|
|
3205
|
+
const ide = pickIde(answers.ides);
|
|
3206
|
+
const steps = [];
|
|
3207
|
+
const pending = [];
|
|
3208
|
+
const allChanges = [];
|
|
3209
|
+
const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
|
|
3210
|
+
let snapshot = null;
|
|
3211
|
+
let snapshotError;
|
|
3212
|
+
if (!dryRun) {
|
|
3213
|
+
try {
|
|
3214
|
+
snapshot = await createSnapshot(projectRoot, { reason: "init" });
|
|
3215
|
+
} catch (err) {
|
|
3216
|
+
snapshotError = getErrorMessage(err);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
let aborted = false;
|
|
3220
|
+
const firstFailure = {
|
|
3221
|
+
value: null
|
|
3222
|
+
};
|
|
3223
|
+
function record(step) {
|
|
3224
|
+
steps.push(step);
|
|
3225
|
+
onStep?.(step);
|
|
3226
|
+
if (step.changes && step.changes.length > 0) {
|
|
3227
|
+
allChanges.push(...step.changes);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
function recordPending(key) {
|
|
3231
|
+
const strategy = answers.conflictDecisions[key];
|
|
3232
|
+
if (!strategy) return;
|
|
3233
|
+
if (strategyImplemented(key, strategy)) return;
|
|
3234
|
+
pending.push({
|
|
3235
|
+
key,
|
|
3236
|
+
strategy,
|
|
3237
|
+
reason: `Strategy "${strategy}" requires the managed-region engine (batch 4); recorded for follow-up.`
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
function recordFailure(name, err) {
|
|
3241
|
+
const message = getErrorMessage(err);
|
|
3242
|
+
record({ name, status: "fail", detail: message });
|
|
3243
|
+
if (!firstFailure.value)
|
|
3244
|
+
firstFailure.value = { step: name, error: message };
|
|
3245
|
+
if (CRITICAL_STEPS.has(name)) aborted = true;
|
|
3246
|
+
}
|
|
3247
|
+
const tokensDecision = answers.conflictDecisions.tokens;
|
|
3248
|
+
const legacyTokensPaths = options.legacyTokensPaths ?? [];
|
|
3249
|
+
const wantMigrate = tokensDecision === "migrate" && legacyTokensPaths.length > 0;
|
|
3250
|
+
if (tokensDecision === "skip") {
|
|
3251
|
+
record({
|
|
3252
|
+
name: "tokens",
|
|
3253
|
+
status: "skip",
|
|
3254
|
+
detail: "conflict strategy = skip"
|
|
3255
|
+
});
|
|
3256
|
+
} else if (dryRun) {
|
|
3257
|
+
const planDetail = wantMigrate ? `runTokensInit(variant=${answers.variant}); migrateLegacyTokens(${legacyTokensPaths.length} file${legacyTokensPaths.length === 1 ? "" : "s"})` : `runTokensInit(variant=${answers.variant})`;
|
|
3258
|
+
record({
|
|
3259
|
+
name: "tokens",
|
|
3260
|
+
status: "planned",
|
|
3261
|
+
detail: planDetail
|
|
3262
|
+
});
|
|
3263
|
+
} else {
|
|
3264
|
+
try {
|
|
3265
|
+
const result = await runTokensInit({
|
|
3266
|
+
projectRoot,
|
|
3267
|
+
variant: answers.variant,
|
|
3268
|
+
ide
|
|
3269
|
+
});
|
|
3270
|
+
let detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
|
|
3271
|
+
if (wantMigrate) {
|
|
3272
|
+
try {
|
|
3273
|
+
const m = await migrateLegacyTokens({
|
|
3274
|
+
projectRoot,
|
|
3275
|
+
legacyPaths: legacyTokensPaths
|
|
3276
|
+
});
|
|
3277
|
+
detail += `; migrated ${m.migrated.length}/${legacyTokensPaths.length} legacy file${legacyTokensPaths.length === 1 ? "" : "s"} \u2192 ${m.overridesPath}`;
|
|
3278
|
+
if (m.skipped.length > 0) {
|
|
3279
|
+
detail += ` (skipped ${m.skipped.length})`;
|
|
3280
|
+
}
|
|
3281
|
+
} catch (err) {
|
|
3282
|
+
detail += `; migrate failed: ${getErrorMessage(err)}`;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
record({
|
|
3286
|
+
name: "tokens",
|
|
3287
|
+
status: "ok",
|
|
3288
|
+
detail,
|
|
3289
|
+
changes: deriveTokensChanges(result, projectRoot)
|
|
3290
|
+
});
|
|
3291
|
+
} catch (err) {
|
|
3292
|
+
recordFailure("tokens", err);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
recordPending("tokens");
|
|
3296
|
+
const codeSkillId = `teamix-evo-code-${answers.variant}`;
|
|
3297
|
+
if (dryRun) {
|
|
3298
|
+
record({
|
|
3299
|
+
name: "skills",
|
|
3300
|
+
status: "planned",
|
|
3301
|
+
detail: `runSkillsAdd(${codeSkillId})`
|
|
3302
|
+
});
|
|
3303
|
+
} else if (aborted) {
|
|
3304
|
+
record({
|
|
3305
|
+
name: "skills",
|
|
3306
|
+
status: "skip",
|
|
3307
|
+
detail: "aborted: earlier critical step failed"
|
|
3308
|
+
});
|
|
3309
|
+
} else {
|
|
3310
|
+
try {
|
|
3311
|
+
const result = await runSkillsAdd({
|
|
3312
|
+
projectRoot,
|
|
3313
|
+
names: [codeSkillId],
|
|
3314
|
+
ides: answers.ides,
|
|
3315
|
+
scope: answers.scope,
|
|
3316
|
+
ide
|
|
3317
|
+
});
|
|
3318
|
+
record({
|
|
3319
|
+
name: "skills",
|
|
3320
|
+
status: "ok",
|
|
3321
|
+
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
|
|
3322
|
+
changes: deriveSkillsChanges(result, projectRoot)
|
|
3323
|
+
});
|
|
3324
|
+
} catch (err) {
|
|
3325
|
+
recordFailure("skills", err);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
const agentsMdDecision = answers.conflictDecisions["agents-md"];
|
|
3329
|
+
const agentsMdSkillIds = [
|
|
3330
|
+
`teamix-evo-design-${answers.variant}`,
|
|
3331
|
+
codeSkillId
|
|
3332
|
+
];
|
|
3333
|
+
if (agentsMdDecision === "skip") {
|
|
3334
|
+
record({
|
|
3335
|
+
name: "agents-md",
|
|
3336
|
+
status: "skip",
|
|
3337
|
+
detail: "conflict strategy = skip"
|
|
3338
|
+
});
|
|
3339
|
+
} else if (dryRun) {
|
|
3340
|
+
record({
|
|
3341
|
+
name: "agents-md",
|
|
3342
|
+
status: "planned",
|
|
3343
|
+
detail: `runGenerateAgentsMd(${agentsMdSkillIds.length} skills)`
|
|
3344
|
+
});
|
|
3345
|
+
} else {
|
|
3346
|
+
try {
|
|
3347
|
+
const result = await runGenerateAgentsMd({
|
|
3348
|
+
projectRoot,
|
|
3349
|
+
variant: answers.variant,
|
|
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"
|
|
3356
|
+
});
|
|
3357
|
+
record({
|
|
3358
|
+
name: "agents-md",
|
|
3359
|
+
status: "ok",
|
|
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
|
+
]
|
|
3369
|
+
});
|
|
3370
|
+
} catch (err) {
|
|
3371
|
+
recordFailure("agents-md", err);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
recordPending("agents-md");
|
|
3375
|
+
const componentsJsonDecision = answers.conflictDecisions["components-json"];
|
|
3376
|
+
const shadcnDecision = answers.conflictDecisions["shadcn-source"];
|
|
3377
|
+
const skipUiInit = !answers.withUi || componentsJsonDecision === "skip";
|
|
3378
|
+
if (skipUiInit) {
|
|
3379
|
+
record({
|
|
3380
|
+
name: "ui-init",
|
|
3381
|
+
status: "skip",
|
|
3382
|
+
detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
|
|
3383
|
+
});
|
|
3384
|
+
record({
|
|
3385
|
+
name: "ui-add",
|
|
3386
|
+
status: "skip",
|
|
3387
|
+
detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
|
|
3388
|
+
});
|
|
3389
|
+
} else if (dryRun) {
|
|
3390
|
+
record({
|
|
3391
|
+
name: "ui-init",
|
|
3392
|
+
status: "planned",
|
|
3393
|
+
detail: "runUiInit()"
|
|
3394
|
+
});
|
|
3395
|
+
const entries = await resolveUiEntries(options).catch(() => [
|
|
3396
|
+
...BASELINE_UI_ENTRIES
|
|
3397
|
+
]);
|
|
3398
|
+
record({
|
|
3399
|
+
name: "ui-add",
|
|
3400
|
+
status: "planned",
|
|
3401
|
+
detail: `runUiAdd(${entries.length} entries: ${entries.slice(0, 3).join(", ")}${entries.length > 3 ? "\u2026" : ""})`
|
|
3402
|
+
});
|
|
3403
|
+
} else {
|
|
3404
|
+
if (aborted) {
|
|
3405
|
+
record({
|
|
3406
|
+
name: "ui-init",
|
|
3407
|
+
status: "skip",
|
|
3408
|
+
detail: "aborted: earlier critical step failed"
|
|
3409
|
+
});
|
|
3410
|
+
record({
|
|
3411
|
+
name: "ui-add",
|
|
3412
|
+
status: "skip",
|
|
3413
|
+
detail: "aborted: earlier critical step failed"
|
|
3414
|
+
});
|
|
3415
|
+
} else {
|
|
3416
|
+
let uiInitOk = false;
|
|
3417
|
+
try {
|
|
3418
|
+
const initResult = await runUiInit({ projectRoot, ide });
|
|
3419
|
+
record({
|
|
3420
|
+
name: "ui-init",
|
|
3421
|
+
status: "ok",
|
|
3422
|
+
detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
|
|
3423
|
+
});
|
|
3424
|
+
uiInitOk = true;
|
|
3425
|
+
} catch (err) {
|
|
3426
|
+
recordFailure("ui-init", err);
|
|
3427
|
+
}
|
|
3428
|
+
if (!uiInitOk) {
|
|
3429
|
+
record({
|
|
3430
|
+
name: "ui-add",
|
|
3431
|
+
status: "skip",
|
|
3432
|
+
detail: "aborted: ui-init failed"
|
|
3433
|
+
});
|
|
3434
|
+
} else if (shadcnDecision === "skip") {
|
|
3435
|
+
record({
|
|
3436
|
+
name: "ui-add",
|
|
3437
|
+
status: "skip",
|
|
3438
|
+
detail: "shadcn-source conflict strategy = skip"
|
|
3439
|
+
});
|
|
3440
|
+
} else {
|
|
3441
|
+
try {
|
|
3442
|
+
const entries = await resolveUiEntries(options);
|
|
3443
|
+
const addResult = await runUiAdd({
|
|
3444
|
+
projectRoot,
|
|
3445
|
+
ids: entries,
|
|
3446
|
+
// 'overwrite' strategy → overwrite=true; everything else (incl.
|
|
3447
|
+
// 'skip-existing' which is the default) → overwrite=false.
|
|
3448
|
+
overwrite: shadcnDecision === "overwrite"
|
|
3449
|
+
});
|
|
3450
|
+
record({
|
|
3451
|
+
name: "ui-add",
|
|
3452
|
+
status: "ok",
|
|
3453
|
+
detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
|
|
3454
|
+
changes: deriveUiAddChanges(addResult, projectRoot)
|
|
3455
|
+
});
|
|
3456
|
+
} catch (err) {
|
|
3457
|
+
recordFailure("ui-add", err);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
recordPending("components-json");
|
|
3463
|
+
recordPending("shadcn-source");
|
|
3464
|
+
if (!answers.withLint) {
|
|
3465
|
+
record({
|
|
3466
|
+
name: "lint",
|
|
3467
|
+
status: "skip",
|
|
3468
|
+
detail: "withLint = false"
|
|
3469
|
+
});
|
|
3470
|
+
} else if (dryRun) {
|
|
3471
|
+
record({
|
|
3472
|
+
name: "lint",
|
|
3473
|
+
status: "planned",
|
|
3474
|
+
detail: "runLintInit()"
|
|
3475
|
+
});
|
|
3476
|
+
} else {
|
|
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 ?? [];
|
|
3482
|
+
const result = await runLintInit({
|
|
3483
|
+
projectRoot,
|
|
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
|
|
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
|
+
}
|
|
3506
|
+
record({
|
|
3507
|
+
name: "lint",
|
|
3508
|
+
status: "ok",
|
|
3509
|
+
detail: detailParts.join(" / "),
|
|
3510
|
+
changes: deriveLintChanges(result)
|
|
3511
|
+
});
|
|
3512
|
+
} catch (err) {
|
|
3513
|
+
recordFailure("lint", err);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
recordPending("tailwind-config");
|
|
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
|
+
}
|
|
3540
|
+
const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
|
|
3541
|
+
const out = {
|
|
3542
|
+
status,
|
|
3543
|
+
steps,
|
|
3544
|
+
pendingConflictWork: pending,
|
|
3545
|
+
changes: allChanges,
|
|
3546
|
+
snapshot
|
|
3547
|
+
};
|
|
3548
|
+
if (snapshotError) out.snapshotError = snapshotError;
|
|
3549
|
+
if (firstFailure.value) {
|
|
3550
|
+
out.resumeHint = {
|
|
3551
|
+
failedAt: firstFailure.value.step,
|
|
3552
|
+
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
3553
|
+
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
3554
|
+
error: firstFailure.value.error,
|
|
3555
|
+
// Every sub-step is idempotent (returns `already-initialized` when its
|
|
3556
|
+
// state file already exists), so a plain re-run resumes from the
|
|
3557
|
+
// failed step. A richer --resume model lands with batch 3 (lock
|
|
3558
|
+
// snapshot + restore).
|
|
3559
|
+
resumeCommand: "teamix-evo init"
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
return out;
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
// src/core/project-update.ts
|
|
3566
|
+
import * as path25 from "path";
|
|
3567
|
+
import {
|
|
3568
|
+
loadTokensPackageManifest as loadTokensPackageManifest3,
|
|
3569
|
+
getVariantEntry as getVariantEntry3
|
|
3570
|
+
} from "@teamix-evo/registry";
|
|
3571
|
+
|
|
3572
|
+
// src/core/tokens-update.ts
|
|
3573
|
+
import * as path21 from "path";
|
|
3574
|
+
import * as fs16 from "fs/promises";
|
|
3575
|
+
import {
|
|
3576
|
+
loadTokensPackageManifest as loadTokensPackageManifest2,
|
|
3577
|
+
getVariantEntry as getVariantEntry2
|
|
3578
|
+
} from "@teamix-evo/registry";
|
|
3579
|
+
|
|
3580
|
+
// src/core/upgrade-hints.ts
|
|
3581
|
+
import * as path20 from "path";
|
|
3582
|
+
var TEAMIX_DIR3 = ".teamix-evo";
|
|
3583
|
+
var HINTS_DIR = ".upgrade-hints";
|
|
3584
|
+
function isoToFsSafe2(iso) {
|
|
3585
|
+
return iso.replace(/[:.]/g, "-");
|
|
3586
|
+
}
|
|
3587
|
+
async function writeTokensUpgradeHint(options) {
|
|
3588
|
+
if (options.renames.length === 0) return null;
|
|
3589
|
+
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3590
|
+
const fsTs = isoToFsSafe2(isoTs);
|
|
3591
|
+
const filename = `tokens-${fsTs}.json`;
|
|
3592
|
+
const target = path20.join(
|
|
3593
|
+
options.projectRoot,
|
|
3594
|
+
TEAMIX_DIR3,
|
|
3595
|
+
HINTS_DIR,
|
|
3596
|
+
filename
|
|
3597
|
+
);
|
|
3598
|
+
const payload = {
|
|
3599
|
+
schemaVersion: 1,
|
|
3600
|
+
ts: isoTs,
|
|
3601
|
+
package: "tokens",
|
|
3602
|
+
trigger: options.trigger,
|
|
3603
|
+
fromVariant: options.fromVariant,
|
|
3604
|
+
toVariant: options.toVariant,
|
|
3605
|
+
fromVersion: options.fromVersion,
|
|
3606
|
+
toVersion: options.toVersion,
|
|
3607
|
+
renames: options.renames
|
|
3608
|
+
};
|
|
3609
|
+
await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
|
|
3610
|
+
return {
|
|
3611
|
+
path: target,
|
|
3612
|
+
ts: fsTs,
|
|
3613
|
+
renameCount: options.renames.length
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
function selectApplicableRenames(renames, fromVersion, toVersion) {
|
|
3617
|
+
return renames.filter(
|
|
3618
|
+
(r) => compareSemver2(r.sinceVersion, fromVersion) > 0 && compareSemver2(r.sinceVersion, toVersion) <= 0
|
|
3619
|
+
).sort((a, b) => compareSemver2(a.sinceVersion, b.sinceVersion));
|
|
3620
|
+
}
|
|
3621
|
+
function compareSemver2(a, b) {
|
|
3622
|
+
const [aMain = "", aRest = ""] = a.split("-", 2);
|
|
3623
|
+
const [bMain = "", bRest = ""] = b.split("-", 2);
|
|
3624
|
+
const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
|
|
3625
|
+
const bParts = bMain.split(".").map((n) => Number.parseInt(n, 10));
|
|
3626
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
3627
|
+
const ai = aParts[i] ?? 0;
|
|
3628
|
+
const bi = bParts[i] ?? 0;
|
|
3629
|
+
if (ai !== bi) return ai - bi;
|
|
3630
|
+
}
|
|
3631
|
+
if (aRest === "" && bRest !== "") return 1;
|
|
3632
|
+
if (aRest !== "" && bRest === "") return -1;
|
|
3633
|
+
return aRest.localeCompare(bRest, void 0, { numeric: true });
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
// src/core/managed-merge.ts
|
|
3637
|
+
import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
|
|
3638
|
+
function mergeManagedRegions(upstreamContent, consumerContent) {
|
|
3639
|
+
let updated = consumerContent;
|
|
3640
|
+
const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
|
|
3641
|
+
let match;
|
|
3642
|
+
while ((match = re.exec(upstreamContent)) !== null) {
|
|
3643
|
+
const id = match[1];
|
|
3644
|
+
const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
|
|
3645
|
+
if (!hasManagedRegion3(updated, id)) {
|
|
3646
|
+
throw new Error(
|
|
3647
|
+
`Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
|
|
3648
|
+
);
|
|
3649
|
+
}
|
|
3650
|
+
updated = replaceManagedRegion3(updated, id, body);
|
|
3651
|
+
}
|
|
3652
|
+
return updated;
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
// src/core/tokens-update.ts
|
|
3656
|
+
var DEFAULT_TOKENS_PACKAGE2 = "@teamix-evo/tokens";
|
|
3657
|
+
var CONSUMER_BASENAME_BY_UPSTREAM = {
|
|
3658
|
+
"theme.css": "tokens.theme.css",
|
|
3659
|
+
"overrides.css": "tokens.overrides.css"
|
|
3660
|
+
};
|
|
3661
|
+
async function runTokensUpdate(options) {
|
|
3662
|
+
const { projectRoot } = options;
|
|
3663
|
+
const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE2;
|
|
3664
|
+
const config = await readProjectConfig(projectRoot);
|
|
3665
|
+
if (!config?.packages?.tokens) {
|
|
3666
|
+
return { status: "not-initialized" };
|
|
3667
|
+
}
|
|
3668
|
+
const currentVariant = config.packages.tokens.variant;
|
|
3669
|
+
const currentVersion = config.packages.tokens.version;
|
|
3670
|
+
const packageRoot = options.packageRoot ?? resolveTokensPackageRoot(packageName);
|
|
3671
|
+
const catalog = await loadTokensPackageManifest2(packageRoot);
|
|
3672
|
+
const variantEntry = getVariantEntry2(catalog, currentVariant);
|
|
3673
|
+
if (!variantEntry) {
|
|
3674
|
+
throw new Error(
|
|
3675
|
+
`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.`
|
|
3676
|
+
);
|
|
3677
|
+
}
|
|
3678
|
+
const upstreamByBasename = /* @__PURE__ */ new Map();
|
|
3679
|
+
for (const fileRel of variantEntry.files) {
|
|
3680
|
+
upstreamByBasename.set(
|
|
3681
|
+
path21.basename(fileRel),
|
|
3682
|
+
path21.join(packageRoot, fileRel)
|
|
3683
|
+
);
|
|
3684
|
+
}
|
|
3685
|
+
const prior = await readInstalledManifest(projectRoot) ?? {
|
|
3686
|
+
schemaVersion: 1,
|
|
3687
|
+
installed: []
|
|
3688
|
+
};
|
|
3689
|
+
const installedIdx = prior.installed.findIndex(
|
|
3690
|
+
(p) => p.package === packageName
|
|
3691
|
+
);
|
|
3692
|
+
const priorResources = installedIdx >= 0 ? prior.installed[installedIdx].resources : [];
|
|
3693
|
+
const rewritten = [];
|
|
3694
|
+
const managedReplaced = [];
|
|
3695
|
+
const preserved = [];
|
|
3696
|
+
const frozenDrift = [];
|
|
3697
|
+
const refreshedResources = [];
|
|
3698
|
+
for (const resource of priorResources) {
|
|
3699
|
+
const consumerAbs = path21.isAbsolute(resource.target) ? resource.target : path21.join(projectRoot, resource.target);
|
|
3700
|
+
const consumerBasename = path21.basename(resource.target);
|
|
3701
|
+
const upstreamBasename = lookupUpstreamBasename(consumerBasename);
|
|
3702
|
+
const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
|
|
3703
|
+
if (resource.strategy === "regenerable") {
|
|
3704
|
+
if (!upstreamAbs) {
|
|
3705
|
+
refreshedResources.push(resource);
|
|
3706
|
+
continue;
|
|
3707
|
+
}
|
|
3708
|
+
const content = await fs16.readFile(upstreamAbs, "utf-8");
|
|
3709
|
+
await writeFileSafe(consumerAbs, content);
|
|
3710
|
+
rewritten.push(resource.target);
|
|
3711
|
+
refreshedResources.push({
|
|
3712
|
+
...resource,
|
|
3713
|
+
hash: computeHash(content)
|
|
3714
|
+
});
|
|
3715
|
+
continue;
|
|
3716
|
+
}
|
|
3717
|
+
if (resource.strategy === "managed") {
|
|
3718
|
+
if (!upstreamAbs || !await fileExists(consumerAbs)) {
|
|
3719
|
+
refreshedResources.push(resource);
|
|
3720
|
+
continue;
|
|
3721
|
+
}
|
|
3722
|
+
const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
|
|
3723
|
+
const consumerContent = await fs16.readFile(consumerAbs, "utf-8");
|
|
3724
|
+
const merged = mergeManagedRegions(upstreamContent, consumerContent);
|
|
3725
|
+
if (merged !== consumerContent) {
|
|
3726
|
+
await writeFileSafe(consumerAbs, merged);
|
|
3727
|
+
managedReplaced.push(resource.target);
|
|
3728
|
+
}
|
|
3729
|
+
refreshedResources.push({
|
|
3730
|
+
...resource,
|
|
3731
|
+
hash: computeHash(merged)
|
|
3732
|
+
});
|
|
3733
|
+
continue;
|
|
3734
|
+
}
|
|
3735
|
+
if (await fileExists(consumerAbs)) preserved.push(resource.target);
|
|
3736
|
+
if (upstreamAbs) {
|
|
3737
|
+
const upstreamContent = await fs16.readFile(upstreamAbs, "utf-8");
|
|
3738
|
+
const upstreamHash = computeHash(upstreamContent);
|
|
3739
|
+
if (resource.hash && upstreamHash !== resource.hash) {
|
|
3740
|
+
frozenDrift.push({
|
|
3741
|
+
target: resource.target,
|
|
3742
|
+
reason: "upstream-changed"
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
refreshedResources.push(resource);
|
|
3747
|
+
}
|
|
3748
|
+
if (variantEntry.version === currentVersion) {
|
|
3749
|
+
if (installedIdx >= 0) {
|
|
3750
|
+
prior.installed[installedIdx] = {
|
|
3751
|
+
...prior.installed[installedIdx],
|
|
3752
|
+
resources: refreshedResources
|
|
3753
|
+
};
|
|
3754
|
+
await writeInstalledManifest(projectRoot, prior);
|
|
3755
|
+
}
|
|
3756
|
+
return {
|
|
3757
|
+
status: "up-to-date",
|
|
3758
|
+
packageName,
|
|
3759
|
+
variant: currentVariant,
|
|
3760
|
+
version: currentVersion,
|
|
3761
|
+
frozenDrift
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3764
|
+
const lock = {
|
|
3765
|
+
schemaVersion: 1,
|
|
3766
|
+
variant: {
|
|
3767
|
+
name: variantEntry.name,
|
|
3768
|
+
displayName: variantEntry.displayName,
|
|
3769
|
+
version: variantEntry.version,
|
|
3770
|
+
from: packageName
|
|
3771
|
+
},
|
|
3772
|
+
packageVersion: catalog.version,
|
|
3773
|
+
linked: variantEntry.linked,
|
|
3774
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3775
|
+
};
|
|
3776
|
+
await writeFileSafe(
|
|
3777
|
+
path21.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
|
|
3778
|
+
JSON.stringify(lock, null, 2) + "\n"
|
|
3779
|
+
);
|
|
3780
|
+
config.packages.tokens.version = variantEntry.version;
|
|
3781
|
+
await writeProjectConfig(projectRoot, config);
|
|
3782
|
+
if (installedIdx >= 0) {
|
|
3783
|
+
prior.installed[installedIdx] = {
|
|
3784
|
+
...prior.installed[installedIdx],
|
|
3785
|
+
version: variantEntry.version,
|
|
3786
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3787
|
+
resources: refreshedResources
|
|
3788
|
+
};
|
|
3789
|
+
await writeInstalledManifest(projectRoot, prior);
|
|
3790
|
+
}
|
|
3791
|
+
const renames = selectApplicableRenames(
|
|
3792
|
+
variantEntry.renames ?? [],
|
|
3793
|
+
currentVersion,
|
|
3794
|
+
variantEntry.version
|
|
3795
|
+
);
|
|
3796
|
+
let hintPath;
|
|
3797
|
+
if (renames.length > 0) {
|
|
3798
|
+
const hint = await writeTokensUpgradeHint({
|
|
3799
|
+
projectRoot,
|
|
3800
|
+
trigger: "update",
|
|
3801
|
+
fromVariant: currentVariant,
|
|
3802
|
+
toVariant: currentVariant,
|
|
3803
|
+
fromVersion: currentVersion,
|
|
3804
|
+
toVersion: variantEntry.version,
|
|
3805
|
+
renames
|
|
3806
|
+
});
|
|
3807
|
+
if (hint) hintPath = hint.path;
|
|
3808
|
+
}
|
|
3809
|
+
return {
|
|
3810
|
+
status: "updated",
|
|
3811
|
+
packageName,
|
|
3812
|
+
variant: currentVariant,
|
|
3813
|
+
from: currentVersion,
|
|
3814
|
+
to: variantEntry.version,
|
|
3815
|
+
rewritten,
|
|
3816
|
+
managedReplaced,
|
|
3817
|
+
preserved,
|
|
3818
|
+
frozenDrift,
|
|
3819
|
+
renames,
|
|
3820
|
+
...hintPath ? { hintPath } : {}
|
|
3821
|
+
};
|
|
3822
|
+
}
|
|
3823
|
+
function lookupUpstreamBasename(consumerBasename) {
|
|
3824
|
+
for (const [upstream, consumer] of Object.entries(
|
|
3825
|
+
CONSUMER_BASENAME_BY_UPSTREAM
|
|
3826
|
+
)) {
|
|
3827
|
+
if (consumer === consumerBasename) return upstream;
|
|
3828
|
+
}
|
|
3829
|
+
return consumerBasename;
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
// src/core/ui-upgrade-detector.ts
|
|
3833
|
+
import * as fs17 from "fs/promises";
|
|
3834
|
+
import * as path22 from "path";
|
|
3835
|
+
var PACKAGE_NAME = {
|
|
3836
|
+
ui: "@teamix-evo/ui",
|
|
3837
|
+
"biz-ui": "@teamix-evo/biz-ui"
|
|
3838
|
+
};
|
|
3839
|
+
var ALIAS_KEY = {
|
|
3840
|
+
ui: "components",
|
|
3841
|
+
"biz-ui": "business"
|
|
3842
|
+
};
|
|
3843
|
+
var COMPONENT_FILE_RE = /\.(tsx|ts)$/;
|
|
3844
|
+
var SKIP_FILENAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx"]);
|
|
3845
|
+
async function detectComponentLineage(options) {
|
|
3846
|
+
const { projectRoot, category } = options;
|
|
3847
|
+
const config = options.config ?? await readProjectConfig(projectRoot);
|
|
3848
|
+
const installed = options.installed ?? await readInstalledManifest(projectRoot);
|
|
3849
|
+
const installDir = resolveInstallDir(category, config);
|
|
3850
|
+
const installDirAbs = path22.join(projectRoot, installDir);
|
|
3851
|
+
const installDirExists = await directoryExists(installDirAbs);
|
|
3852
|
+
const hasComponentsJson = await fileExists(
|
|
3853
|
+
path22.join(projectRoot, "components.json")
|
|
3854
|
+
);
|
|
3855
|
+
const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
|
|
3856
|
+
const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
|
|
3857
|
+
const onDiskIds = installDirExists ? await listComponentIds(installDirAbs) : [];
|
|
3858
|
+
const registeredSet = new Set(registeredIds);
|
|
3859
|
+
const unregisteredIds = onDiskIds.filter((id) => !registeredSet.has(id)).sort();
|
|
3860
|
+
const lineage = classifyLineage({
|
|
3861
|
+
hasInstalled: installedPkg !== null,
|
|
3862
|
+
hasComponentsJson,
|
|
3863
|
+
onDiskIds,
|
|
3864
|
+
unregisteredIds
|
|
3865
|
+
});
|
|
3866
|
+
return {
|
|
3867
|
+
category,
|
|
3868
|
+
lineage,
|
|
3869
|
+
installDir,
|
|
3870
|
+
installDirExists,
|
|
3871
|
+
hasComponentsJson,
|
|
3872
|
+
registeredIds,
|
|
3873
|
+
unregisteredIds,
|
|
3874
|
+
installedVersion: installedPkg?.version ?? null,
|
|
3875
|
+
installedVariant: installedPkg?.variant ?? null
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
function resolveInstallDir(category, config) {
|
|
3879
|
+
const aliasMap = config?.packages?.ui?.aliases ?? config?.packages?.["biz-ui"]?.aliases ?? DEFAULT_UI_ALIASES;
|
|
3880
|
+
const key = ALIAS_KEY[category];
|
|
3881
|
+
return aliasMap[key] ?? DEFAULT_UI_ALIASES[key];
|
|
3882
|
+
}
|
|
3883
|
+
function extractIds(pkg) {
|
|
3884
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3885
|
+
for (const r of pkg.resources) {
|
|
3886
|
+
const colon = r.id.indexOf(":");
|
|
3887
|
+
ids.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
|
|
3888
|
+
}
|
|
3889
|
+
return [...ids];
|
|
3890
|
+
}
|
|
3891
|
+
async function directoryExists(p) {
|
|
3892
|
+
try {
|
|
3893
|
+
const stat5 = await fs17.stat(p);
|
|
3894
|
+
return stat5.isDirectory();
|
|
3895
|
+
} catch {
|
|
3896
|
+
return false;
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
async function listComponentIds(installDirAbs) {
|
|
3900
|
+
const entries = await fs17.readdir(installDirAbs, { withFileTypes: true });
|
|
3901
|
+
const ids = [];
|
|
3902
|
+
for (const e of entries) {
|
|
3903
|
+
if (!e.isFile()) continue;
|
|
3904
|
+
if (SKIP_FILENAMES.has(e.name)) continue;
|
|
3905
|
+
if (!COMPONENT_FILE_RE.test(e.name)) continue;
|
|
3906
|
+
ids.push(e.name.replace(COMPONENT_FILE_RE, ""));
|
|
3907
|
+
}
|
|
3908
|
+
return ids.sort();
|
|
3909
|
+
}
|
|
3910
|
+
function classifyLineage(args) {
|
|
3911
|
+
const { hasInstalled, hasComponentsJson, onDiskIds, unregisteredIds } = args;
|
|
3912
|
+
if (hasInstalled) {
|
|
3913
|
+
return unregisteredIds.length === 0 ? "teamix-evo" : "mixed";
|
|
3914
|
+
}
|
|
3915
|
+
if (onDiskIds.length === 0) return "absent";
|
|
3916
|
+
return hasComponentsJson ? "shadcn-native" : "custom-only";
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
// src/core/ui-upgrade.ts
|
|
3920
|
+
import * as path24 from "path";
|
|
3921
|
+
import { createRequire as createRequire5 } from "module";
|
|
3922
|
+
import {
|
|
3923
|
+
loadUiPackageManifest as loadUiPackageManifest3,
|
|
3924
|
+
loadVariantUiPackageManifest as loadVariantUiPackageManifest2
|
|
3925
|
+
} from "@teamix-evo/registry";
|
|
3926
|
+
|
|
3927
|
+
// src/core/ui-upgrade-staging.ts
|
|
3928
|
+
import * as path23 from "path";
|
|
3929
|
+
var TEAMIX_DIR4 = ".teamix-evo";
|
|
3930
|
+
var STAGING_DIR = ".upgrade-staging";
|
|
3931
|
+
var PACKAGE_NAME2 = {
|
|
3932
|
+
ui: "@teamix-evo/ui",
|
|
3933
|
+
"biz-ui": "@teamix-evo/biz-ui"
|
|
3934
|
+
};
|
|
3935
|
+
function isoToFsSafe3(iso) {
|
|
3936
|
+
return iso.replace(/[:.]/g, "-");
|
|
3937
|
+
}
|
|
3938
|
+
async function buildUiUpgradeStaging(options) {
|
|
3939
|
+
const { lineageReport, category } = options;
|
|
3940
|
+
if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
|
|
3941
|
+
return null;
|
|
3942
|
+
}
|
|
3943
|
+
const installed = options.installed ?? await readInstalledManifest(options.projectRoot);
|
|
3944
|
+
const installedPkg = findInstalledPackage(installed, PACKAGE_NAME2[category]);
|
|
3945
|
+
if (!installedPkg) return null;
|
|
3946
|
+
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3947
|
+
const fsTs = isoToFsSafe3(isoTs);
|
|
3948
|
+
const stagingDir = path23.join(
|
|
3949
|
+
options.projectRoot,
|
|
3950
|
+
TEAMIX_DIR4,
|
|
3951
|
+
STAGING_DIR,
|
|
3952
|
+
`${category}-${fsTs}`
|
|
3953
|
+
);
|
|
3954
|
+
const entryMap = new Map(
|
|
3955
|
+
options.manifest.entries.map((e) => [e.id, e])
|
|
3956
|
+
);
|
|
3957
|
+
const resByEntryId = collectResourcesByEntry(installedPkg.resources);
|
|
3958
|
+
const onlyIds = options.onlyIds && options.onlyIds.length > 0 ? new Set(options.onlyIds) : null;
|
|
3959
|
+
const entries = [];
|
|
3960
|
+
for (const id of lineageReport.registeredIds) {
|
|
3961
|
+
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3962
|
+
const built = await processRegistered({
|
|
3963
|
+
id,
|
|
3964
|
+
entry: entryMap.get(id),
|
|
3965
|
+
resource: resByEntryId.get(id),
|
|
3966
|
+
packageRoot: options.packageRoot,
|
|
3967
|
+
entryPackageRoot: options.entryPackageRoot,
|
|
3968
|
+
aliases: options.aliases,
|
|
3969
|
+
stagingDir,
|
|
3970
|
+
projectRoot: options.projectRoot,
|
|
3971
|
+
category,
|
|
3972
|
+
sourceVersion: options.manifest.version
|
|
3973
|
+
});
|
|
3974
|
+
if (built) entries.push(built);
|
|
3975
|
+
}
|
|
3976
|
+
for (const id of lineageReport.unregisteredIds) {
|
|
3977
|
+
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3978
|
+
const built = await processForeign({
|
|
3979
|
+
id,
|
|
3980
|
+
installDirAbs: path23.join(options.projectRoot, lineageReport.installDir),
|
|
3981
|
+
stagingDir,
|
|
3982
|
+
projectRoot: options.projectRoot,
|
|
3983
|
+
category
|
|
3984
|
+
});
|
|
3985
|
+
if (built) entries.push(built);
|
|
3986
|
+
}
|
|
3987
|
+
if (entries.length === 0) return null;
|
|
3988
|
+
const byRisk = aggregateByRisk(entries);
|
|
3989
|
+
const manifestOut = {
|
|
3990
|
+
schemaVersion: 1,
|
|
3991
|
+
ts: isoTs,
|
|
3992
|
+
package: category,
|
|
3993
|
+
trigger: options.trigger,
|
|
3994
|
+
variant: lineageReport.installedVariant ?? "_flat",
|
|
3995
|
+
fromVersion: lineageReport.installedVersion ?? "",
|
|
3996
|
+
toVersion: options.manifest.version,
|
|
3997
|
+
lineage: lineageReport.lineage,
|
|
3998
|
+
summary: { total: entries.length, byRisk },
|
|
3999
|
+
entries
|
|
4000
|
+
};
|
|
4001
|
+
await ensureDir(stagingDir);
|
|
4002
|
+
await writeFileSafe(
|
|
4003
|
+
path23.join(stagingDir, "meta.json"),
|
|
4004
|
+
JSON.stringify(manifestOut, null, 2) + "\n"
|
|
4005
|
+
);
|
|
4006
|
+
return { stagingDir, manifest: manifestOut };
|
|
4007
|
+
}
|
|
4008
|
+
async function processRegistered(args) {
|
|
4009
|
+
const { id, entry, resource, stagingDir, projectRoot, category } = args;
|
|
4010
|
+
if (!resource) return null;
|
|
4011
|
+
const currentSource = await readFileOrNull(resource.target);
|
|
4012
|
+
if (currentSource === null) {
|
|
4013
|
+
return buildBreakingEntry({
|
|
4014
|
+
id,
|
|
4015
|
+
category,
|
|
4016
|
+
resource,
|
|
4017
|
+
projectRoot,
|
|
4018
|
+
stagingDir,
|
|
4019
|
+
currentSource: "",
|
|
4020
|
+
hint: "installed file missing on disk"
|
|
4021
|
+
});
|
|
4022
|
+
}
|
|
4023
|
+
if (!entry) {
|
|
4024
|
+
return buildBreakingEntry({
|
|
4025
|
+
id,
|
|
4026
|
+
category,
|
|
4027
|
+
resource,
|
|
4028
|
+
projectRoot,
|
|
4029
|
+
stagingDir,
|
|
4030
|
+
currentSource,
|
|
4031
|
+
hint: "entry removed in upstream package"
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
4034
|
+
const file = entry.files[0];
|
|
4035
|
+
if (!file) return null;
|
|
4036
|
+
const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
|
|
4037
|
+
const sourceAbs = path23.resolve(rootForEntry, file.source);
|
|
4038
|
+
const raw = await readFileOrNull(sourceAbs);
|
|
4039
|
+
if (raw === null) {
|
|
4040
|
+
return null;
|
|
4041
|
+
}
|
|
4042
|
+
const incomingTransformed = rewriteImports(raw, args.aliases);
|
|
4043
|
+
const incomingHash = computeHash(incomingTransformed);
|
|
4044
|
+
const currentExt = path23.extname(resource.target) || ".tsx";
|
|
4045
|
+
const incomingExt = path23.extname(file.targetName) || currentExt;
|
|
4046
|
+
const currentRel = `${id}/current${currentExt}`;
|
|
4047
|
+
const incomingRel = `${id}/incoming${incomingExt}`;
|
|
4048
|
+
await writeFileSafe(path23.join(stagingDir, currentRel), currentSource);
|
|
4049
|
+
await writeFileSafe(path23.join(stagingDir, incomingRel), incomingTransformed);
|
|
4050
|
+
const diff = classifyRisk({
|
|
4051
|
+
currentHash: resource.hash,
|
|
4052
|
+
incomingHash,
|
|
4053
|
+
currentSource,
|
|
4054
|
+
incomingSource: incomingTransformed,
|
|
4055
|
+
multiFile: entry.files.length > 1
|
|
4056
|
+
});
|
|
4057
|
+
const promotion = derivePromotion({
|
|
4058
|
+
currentSource,
|
|
4059
|
+
incomingSource: incomingTransformed,
|
|
4060
|
+
targetName: entry.files[0]?.targetName ?? `${id}.tsx`
|
|
4061
|
+
});
|
|
4062
|
+
return {
|
|
4063
|
+
id,
|
|
4064
|
+
category,
|
|
4065
|
+
current: {
|
|
4066
|
+
target: path23.relative(projectRoot, resource.target),
|
|
4067
|
+
hash: resource.hash,
|
|
4068
|
+
sourceLineage: "teamix-evo"
|
|
4069
|
+
},
|
|
4070
|
+
incoming: {
|
|
4071
|
+
sourceVersion: args.sourceVersion,
|
|
4072
|
+
hash: incomingHash,
|
|
4073
|
+
relPath: incomingRel
|
|
4074
|
+
},
|
|
4075
|
+
diff,
|
|
4076
|
+
promotion
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
async function processForeign(args) {
|
|
4080
|
+
const { id, installDirAbs, stagingDir, projectRoot, category } = args;
|
|
4081
|
+
const tsx = path23.join(installDirAbs, `${id}.tsx`);
|
|
4082
|
+
const ts = path23.join(installDirAbs, `${id}.ts`);
|
|
4083
|
+
const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
|
|
4084
|
+
if (!target) return null;
|
|
4085
|
+
const raw = await readFileOrNull(target);
|
|
4086
|
+
if (raw === null) return null;
|
|
4087
|
+
const ext = path23.extname(target);
|
|
4088
|
+
const currentRel = `${id}/current${ext}`;
|
|
4089
|
+
await writeFileSafe(path23.join(stagingDir, currentRel), raw);
|
|
4090
|
+
return {
|
|
4091
|
+
id,
|
|
4092
|
+
category,
|
|
4093
|
+
current: {
|
|
4094
|
+
target: path23.relative(projectRoot, target),
|
|
4095
|
+
hash: computeHash(raw),
|
|
4096
|
+
sourceLineage: "custom"
|
|
4097
|
+
},
|
|
4098
|
+
diff: {
|
|
4099
|
+
riskLevel: "foreign",
|
|
4100
|
+
hints: [
|
|
4101
|
+
"component is on disk but not registered in .teamix-evo/manifest.json",
|
|
4102
|
+
"AI should propose: (a) ignore, (b) re-register via teamix-evo ui add, or (c) remove"
|
|
4103
|
+
],
|
|
4104
|
+
filesChangedCount: 0
|
|
4105
|
+
}
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4108
|
+
async function buildBreakingEntry(args) {
|
|
4109
|
+
const ext = path23.extname(args.resource.target) || ".tsx";
|
|
4110
|
+
const currentRel = `${args.id}/current${ext}`;
|
|
4111
|
+
await writeFileSafe(
|
|
4112
|
+
path23.join(args.stagingDir, currentRel),
|
|
4113
|
+
args.currentSource
|
|
4114
|
+
);
|
|
4115
|
+
return {
|
|
4116
|
+
id: args.id,
|
|
4117
|
+
category: args.category,
|
|
4118
|
+
current: {
|
|
4119
|
+
target: path23.relative(args.projectRoot, args.resource.target),
|
|
4120
|
+
hash: args.resource.hash,
|
|
4121
|
+
sourceLineage: "teamix-evo"
|
|
4122
|
+
},
|
|
4123
|
+
diff: {
|
|
4124
|
+
riskLevel: "breaking",
|
|
4125
|
+
hints: [args.hint],
|
|
4126
|
+
filesChangedCount: 0
|
|
4127
|
+
}
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
function classifyRisk(args) {
|
|
4131
|
+
if (args.currentHash === args.incomingHash) {
|
|
4132
|
+
return { riskLevel: "unchanged", hints: [], filesChangedCount: 0 };
|
|
4133
|
+
}
|
|
4134
|
+
const curExports = extractExportNames(args.currentSource);
|
|
4135
|
+
const newExports = extractExportNames(args.incomingSource);
|
|
4136
|
+
const removedExports = setDiff(curExports, newExports);
|
|
4137
|
+
const addedExports = setDiff(newExports, curExports);
|
|
4138
|
+
const curVariants = extractCvaVariantValues(args.currentSource);
|
|
4139
|
+
const newVariants = extractCvaVariantValues(args.incomingSource);
|
|
4140
|
+
const removedVariants = setDiff(curVariants, newVariants);
|
|
4141
|
+
const addedVariants = setDiff(newVariants, curVariants);
|
|
4142
|
+
const hints = [];
|
|
4143
|
+
for (const e of removedExports) hints.push(`removed export: ${e}`);
|
|
4144
|
+
for (const e of addedExports) hints.push(`new export: ${e}`);
|
|
4145
|
+
for (const v of removedVariants) hints.push(`removed cva variant: ${v}`);
|
|
4146
|
+
for (const v of addedVariants) hints.push(`new cva variant: ${v}`);
|
|
4147
|
+
if (args.multiFile) hints.push("multi-file entry; only first file staged");
|
|
4148
|
+
let riskLevel;
|
|
4149
|
+
if (removedExports.length > 0 || removedVariants.length > 0) {
|
|
4150
|
+
riskLevel = "risky";
|
|
4151
|
+
} else if (addedExports.length > 0 || addedVariants.length > 0 || args.multiFile) {
|
|
4152
|
+
riskLevel = "upgradable-medium";
|
|
4153
|
+
} else {
|
|
4154
|
+
riskLevel = "upgradable-low";
|
|
4155
|
+
}
|
|
4156
|
+
return { riskLevel, hints, filesChangedCount: 1 };
|
|
4157
|
+
}
|
|
4158
|
+
function extractExportNames(src) {
|
|
4159
|
+
const names = /* @__PURE__ */ new Set();
|
|
4160
|
+
const re = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/gm;
|
|
4161
|
+
let m;
|
|
4162
|
+
while ((m = re.exec(src)) !== null) {
|
|
4163
|
+
if (m[1]) names.add(m[1]);
|
|
4164
|
+
}
|
|
4165
|
+
for (const dm of src.matchAll(/^\s*export\s+default\s+(\w+)\s*;/gm)) {
|
|
4166
|
+
if (dm[1]) names.add(dm[1]);
|
|
4167
|
+
}
|
|
4168
|
+
return [...names];
|
|
4169
|
+
}
|
|
4170
|
+
function extractCvaVariantValues(src) {
|
|
4171
|
+
const block = extractVariantsBlock(src);
|
|
4172
|
+
if (block === null) return [];
|
|
4173
|
+
const names = /* @__PURE__ */ new Set();
|
|
4174
|
+
for (const groupBody of extractGroupBodies(block)) {
|
|
4175
|
+
for (const km of groupBody.matchAll(/^\s*(?:['"]?)(\w+)(?:['"]?)\s*:/gm)) {
|
|
4176
|
+
if (km[1]) names.add(km[1]);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
return [...names];
|
|
4180
|
+
}
|
|
4181
|
+
function extractVariantsBlock(src) {
|
|
4182
|
+
const idx = src.search(/\bvariants\s*:\s*\{/);
|
|
4183
|
+
if (idx < 0) return null;
|
|
4184
|
+
const open = src.indexOf("{", idx);
|
|
4185
|
+
if (open < 0) return null;
|
|
4186
|
+
let depth = 0;
|
|
4187
|
+
for (let i = open; i < src.length; i++) {
|
|
4188
|
+
const c = src[i];
|
|
4189
|
+
if (c === "{") depth++;
|
|
4190
|
+
else if (c === "}") {
|
|
4191
|
+
depth--;
|
|
4192
|
+
if (depth === 0) return src.slice(open + 1, i);
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
return null;
|
|
4196
|
+
}
|
|
4197
|
+
function* extractGroupBodies(block) {
|
|
4198
|
+
const re = /(\w+)\s*:\s*\{/g;
|
|
4199
|
+
let m;
|
|
4200
|
+
while ((m = re.exec(block)) !== null) {
|
|
4201
|
+
const open = block.indexOf("{", m.index);
|
|
4202
|
+
if (open < 0) continue;
|
|
4203
|
+
let depth = 0;
|
|
4204
|
+
for (let i = open; i < block.length; i++) {
|
|
4205
|
+
const c = block[i];
|
|
4206
|
+
if (c === "{") depth++;
|
|
4207
|
+
else if (c === "}") {
|
|
4208
|
+
depth--;
|
|
4209
|
+
if (depth === 0) {
|
|
4210
|
+
yield block.slice(open + 1, i);
|
|
4211
|
+
re.lastIndex = i + 1;
|
|
4212
|
+
break;
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
function setDiff(a, b) {
|
|
4219
|
+
const bset = new Set(b);
|
|
4220
|
+
return a.filter((x) => !bset.has(x)).sort();
|
|
4221
|
+
}
|
|
4222
|
+
function collectResourcesByEntry(resources) {
|
|
4223
|
+
const out = /* @__PURE__ */ new Map();
|
|
4224
|
+
for (const r of resources) {
|
|
4225
|
+
const colon = r.id.indexOf(":");
|
|
4226
|
+
const eid = colon >= 0 ? r.id.slice(0, colon) : r.id;
|
|
4227
|
+
if (!out.has(eid)) out.set(eid, r);
|
|
4228
|
+
}
|
|
4229
|
+
return out;
|
|
4230
|
+
}
|
|
4231
|
+
function aggregateByRisk(entries) {
|
|
4232
|
+
const out = {};
|
|
4233
|
+
for (const e of entries) {
|
|
4234
|
+
const k = e.diff.riskLevel;
|
|
4235
|
+
out[k] = (out[k] ?? 0) + 1;
|
|
4236
|
+
}
|
|
4237
|
+
return out;
|
|
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
|
+
}
|
|
4418
|
+
|
|
4419
|
+
// src/core/ui-upgrade.ts
|
|
4420
|
+
var nodeRequire = createRequire5(import.meta.url);
|
|
4421
|
+
function resolvePackageRoot4(packageName) {
|
|
4422
|
+
const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
|
|
4423
|
+
return path24.dirname(pkgJsonPath);
|
|
4424
|
+
}
|
|
4425
|
+
async function buildStaging(args) {
|
|
4426
|
+
const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
|
|
4427
|
+
if (category === "ui") {
|
|
4428
|
+
const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
4429
|
+
const manifest = await loadUiPackageManifest3(root);
|
|
4430
|
+
return buildUiUpgradeStaging({
|
|
4431
|
+
projectRoot,
|
|
4432
|
+
category,
|
|
4433
|
+
manifest,
|
|
4434
|
+
packageRoot: root,
|
|
4435
|
+
aliases,
|
|
4436
|
+
lineageReport,
|
|
4437
|
+
trigger,
|
|
4438
|
+
onlyIds
|
|
4439
|
+
});
|
|
4440
|
+
}
|
|
4441
|
+
const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
|
|
4442
|
+
const variant = lineageReport.installedVariant ?? "_flat";
|
|
4443
|
+
const variantDir = path24.join(bizRoot, "variants", variant);
|
|
4444
|
+
const variantManifest = await loadVariantUiPackageManifest2(variantDir);
|
|
4445
|
+
const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
4446
|
+
const uiManifest = await loadUiPackageManifest3(uiRoot);
|
|
4447
|
+
const entryPackageRoot = /* @__PURE__ */ new Map();
|
|
4448
|
+
const merged = [];
|
|
4449
|
+
for (const e of variantManifest.entries) {
|
|
4450
|
+
entryPackageRoot.set(e.id, variantDir);
|
|
4451
|
+
merged.push(e);
|
|
4452
|
+
}
|
|
4453
|
+
for (const e of uiManifest.entries) {
|
|
4454
|
+
if (entryPackageRoot.has(e.id)) continue;
|
|
4455
|
+
entryPackageRoot.set(e.id, uiRoot);
|
|
4456
|
+
merged.push(e);
|
|
4457
|
+
}
|
|
4458
|
+
const synthetic = {
|
|
4459
|
+
schemaVersion: 1,
|
|
4460
|
+
package: "ui",
|
|
4461
|
+
version: variantManifest.version,
|
|
4462
|
+
engines: variantManifest.engines,
|
|
4463
|
+
entries: merged
|
|
4464
|
+
};
|
|
4465
|
+
return buildUiUpgradeStaging({
|
|
4466
|
+
projectRoot,
|
|
4467
|
+
category,
|
|
4468
|
+
manifest: synthetic,
|
|
4469
|
+
packageRoot: variantDir,
|
|
4470
|
+
entryPackageRoot,
|
|
4471
|
+
aliases,
|
|
4472
|
+
lineageReport,
|
|
4473
|
+
trigger,
|
|
4474
|
+
onlyIds
|
|
4475
|
+
});
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
// src/core/project-update.ts
|
|
4479
|
+
var DEFAULT_TOKENS_PACKAGE3 = "@teamix-evo/tokens";
|
|
4480
|
+
var DEFAULT_SKILLS_PACKAGE4 = "@teamix-evo/skills";
|
|
4481
|
+
var CRITICAL_STEPS2 = /* @__PURE__ */ new Set(["tokens"]);
|
|
4482
|
+
async function runProjectUpdate(options) {
|
|
4483
|
+
const { projectRoot, dryRun = false, onStep } = options;
|
|
4484
|
+
const tokensPackage = options.tokensPackageName ?? DEFAULT_TOKENS_PACKAGE3;
|
|
4485
|
+
const skillsPackage = options.skillsPackageName ?? DEFAULT_SKILLS_PACKAGE4;
|
|
4486
|
+
const config = await readProjectConfig(projectRoot);
|
|
4487
|
+
if (!config) {
|
|
4488
|
+
return { status: "not-initialized" };
|
|
4489
|
+
}
|
|
4490
|
+
let snapshot = null;
|
|
4491
|
+
let snapshotError;
|
|
4492
|
+
if (!dryRun) {
|
|
4493
|
+
try {
|
|
4494
|
+
snapshot = await createSnapshot(projectRoot, { reason: "update" });
|
|
4495
|
+
} catch (err) {
|
|
4496
|
+
snapshotError = getErrorMessage(err);
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
const steps = [];
|
|
4500
|
+
let aborted = false;
|
|
4501
|
+
const firstFailure = { value: null };
|
|
4502
|
+
function record(step) {
|
|
4503
|
+
steps.push(step);
|
|
4504
|
+
onStep?.(step);
|
|
4505
|
+
}
|
|
4506
|
+
function recordFailure(name, err) {
|
|
4507
|
+
const message = getErrorMessage(err);
|
|
4508
|
+
record({ name, status: "fail", detail: message });
|
|
4509
|
+
if (!firstFailure.value) {
|
|
4510
|
+
firstFailure.value = { step: name, error: message };
|
|
4511
|
+
}
|
|
4512
|
+
if (CRITICAL_STEPS2.has(name)) aborted = true;
|
|
4513
|
+
}
|
|
4514
|
+
let anyChanged = false;
|
|
4515
|
+
if (!config.packages?.tokens) {
|
|
4516
|
+
record({
|
|
4517
|
+
name: "tokens",
|
|
4518
|
+
status: "skip",
|
|
4519
|
+
detail: "tokens not installed"
|
|
4520
|
+
});
|
|
4521
|
+
} else if (dryRun) {
|
|
4522
|
+
try {
|
|
4523
|
+
const plan = await planTokensUpdate(tokensPackage, config);
|
|
4524
|
+
record({ name: "tokens", status: "planned", detail: plan });
|
|
4525
|
+
} catch (err) {
|
|
4526
|
+
recordFailure("tokens", err);
|
|
4527
|
+
}
|
|
4528
|
+
} else {
|
|
4529
|
+
try {
|
|
4530
|
+
const result = await runTokensUpdate({
|
|
4531
|
+
projectRoot,
|
|
4532
|
+
packageName: tokensPackage
|
|
4533
|
+
});
|
|
4534
|
+
if (result.status === "not-initialized") {
|
|
4535
|
+
record({
|
|
4536
|
+
name: "tokens",
|
|
4537
|
+
status: "skip",
|
|
4538
|
+
detail: "tokens not installed"
|
|
4539
|
+
});
|
|
4540
|
+
} else if (result.status === "up-to-date") {
|
|
4541
|
+
const driftSuffix = result.frozenDrift.length > 0 ? `, frozen drift: ${result.frozenDrift.length}` : "";
|
|
4542
|
+
record({
|
|
4543
|
+
name: "tokens",
|
|
4544
|
+
status: "ok",
|
|
4545
|
+
detail: `up-to-date (${result.variant} v${result.version}${driftSuffix})`
|
|
4546
|
+
});
|
|
4547
|
+
} else {
|
|
4548
|
+
anyChanged = true;
|
|
4549
|
+
const extras = [];
|
|
4550
|
+
if (result.managedReplaced.length > 0)
|
|
4551
|
+
extras.push(`managed: ${result.managedReplaced.length}`);
|
|
4552
|
+
if (result.frozenDrift.length > 0)
|
|
4553
|
+
extras.push(`frozen drift: ${result.frozenDrift.length}`);
|
|
4554
|
+
const suffix = extras.length > 0 ? ` [${extras.join(", ")}]` : "";
|
|
4555
|
+
record({
|
|
4556
|
+
name: "tokens",
|
|
4557
|
+
status: "ok",
|
|
4558
|
+
detail: `${result.variant} v${result.from} \u2192 v${result.to}${suffix}`
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
} catch (err) {
|
|
4562
|
+
recordFailure("tokens", err);
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
if (!config.packages?.skills) {
|
|
4566
|
+
record({
|
|
4567
|
+
name: "skills",
|
|
4568
|
+
status: "skip",
|
|
4569
|
+
detail: "skills not installed"
|
|
4570
|
+
});
|
|
4571
|
+
} else if (aborted) {
|
|
4572
|
+
record({
|
|
4573
|
+
name: "skills",
|
|
4574
|
+
status: "skip",
|
|
4575
|
+
detail: "aborted: earlier critical step failed"
|
|
4576
|
+
});
|
|
4577
|
+
} else {
|
|
4578
|
+
try {
|
|
4579
|
+
const result = await runSkillsUpdate({
|
|
4580
|
+
projectRoot,
|
|
4581
|
+
dryRun,
|
|
4582
|
+
packageName: skillsPackage
|
|
4583
|
+
});
|
|
4584
|
+
if (result.status === "no-skills") {
|
|
4585
|
+
record({
|
|
4586
|
+
name: "skills",
|
|
4587
|
+
status: "skip",
|
|
4588
|
+
detail: "no skills locked"
|
|
4589
|
+
});
|
|
4590
|
+
} else if (result.status === "no-changes") {
|
|
4591
|
+
record({
|
|
4592
|
+
name: "skills",
|
|
4593
|
+
status: "ok",
|
|
4594
|
+
detail: `up-to-date (${result.checkedSkillIds.length} skill(s) at v${result.version})`
|
|
4595
|
+
});
|
|
4596
|
+
} else if (result.status === "dry-run") {
|
|
4597
|
+
const bumps = result.plan.filter((p) => p.action === "version-bump");
|
|
4598
|
+
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}`;
|
|
4599
|
+
record({ name: "skills", status: "planned", detail });
|
|
4600
|
+
} else {
|
|
4601
|
+
anyChanged = true;
|
|
4602
|
+
const summary = result.updatedSkillIds.length > 0 ? `updated: ${result.updatedSkillIds.join(", ")} (v${result.version})` : `refreshed at v${result.version}`;
|
|
4603
|
+
record({ name: "skills", status: "ok", detail: summary });
|
|
4604
|
+
}
|
|
4605
|
+
} catch (err) {
|
|
4606
|
+
recordFailure("skills", err);
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
await runComponentSourceStep("ui", {
|
|
4610
|
+
projectRoot,
|
|
4611
|
+
config,
|
|
4612
|
+
dryRun,
|
|
4613
|
+
record,
|
|
4614
|
+
recordFailure
|
|
4615
|
+
});
|
|
4616
|
+
await runComponentSourceStep("biz-ui", {
|
|
4617
|
+
projectRoot,
|
|
4618
|
+
config,
|
|
4619
|
+
dryRun,
|
|
4620
|
+
record,
|
|
4621
|
+
recordFailure
|
|
4622
|
+
});
|
|
4623
|
+
const out = (() => {
|
|
4624
|
+
if (firstFailure.value) {
|
|
4625
|
+
return {
|
|
4626
|
+
status: "partial",
|
|
4627
|
+
steps,
|
|
4628
|
+
resumeHint: {
|
|
4629
|
+
failedAt: firstFailure.value.step,
|
|
4630
|
+
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
4631
|
+
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
4632
|
+
error: firstFailure.value.error,
|
|
4633
|
+
resumeCommand: "teamix-evo update"
|
|
4634
|
+
},
|
|
4635
|
+
snapshot,
|
|
4636
|
+
...snapshotError ? { snapshotError } : {}
|
|
4637
|
+
};
|
|
4638
|
+
}
|
|
4639
|
+
if (dryRun) return { status: "dry-run", steps, snapshot: null };
|
|
4640
|
+
if (anyChanged)
|
|
4641
|
+
return {
|
|
4642
|
+
status: "updated",
|
|
4643
|
+
steps,
|
|
4644
|
+
snapshot,
|
|
4645
|
+
...snapshotError ? { snapshotError } : {}
|
|
4646
|
+
};
|
|
4647
|
+
return {
|
|
4648
|
+
status: "up-to-date",
|
|
4649
|
+
steps,
|
|
4650
|
+
snapshot,
|
|
4651
|
+
...snapshotError ? { snapshotError } : {}
|
|
4652
|
+
};
|
|
4653
|
+
})();
|
|
4654
|
+
return out;
|
|
4655
|
+
}
|
|
4656
|
+
async function runComponentSourceStep(category, args) {
|
|
4657
|
+
const { projectRoot, config, dryRun, record, recordFailure } = args;
|
|
4658
|
+
const cfgKey = category === "ui" ? "ui" : "biz-ui";
|
|
4659
|
+
if (!config.packages?.[cfgKey]) {
|
|
4660
|
+
record({
|
|
4661
|
+
name: category,
|
|
4662
|
+
status: "skip",
|
|
4663
|
+
detail: `${category} not installed`
|
|
4664
|
+
});
|
|
4665
|
+
return;
|
|
4666
|
+
}
|
|
4667
|
+
const aliases = config.packages.ui?.aliases ?? config.packages["biz-ui"]?.aliases;
|
|
4668
|
+
if (!aliases) {
|
|
4669
|
+
record({
|
|
4670
|
+
name: category,
|
|
4671
|
+
status: "skip",
|
|
4672
|
+
detail: `${category} aliases not configured`
|
|
4673
|
+
});
|
|
4674
|
+
return;
|
|
4675
|
+
}
|
|
4676
|
+
let report;
|
|
4677
|
+
try {
|
|
4678
|
+
report = await detectComponentLineage({
|
|
4679
|
+
projectRoot,
|
|
4680
|
+
category,
|
|
4681
|
+
config
|
|
4682
|
+
});
|
|
4683
|
+
} catch (err) {
|
|
4684
|
+
record({
|
|
4685
|
+
name: category,
|
|
4686
|
+
status: "skip",
|
|
4687
|
+
detail: `lineage detect failed: ${getErrorMessage(err)}`
|
|
4688
|
+
});
|
|
4689
|
+
return;
|
|
4690
|
+
}
|
|
4691
|
+
if (report.lineage !== "teamix-evo" && report.lineage !== "mixed") {
|
|
4692
|
+
record({
|
|
4693
|
+
name: category,
|
|
4694
|
+
status: "skip",
|
|
4695
|
+
detail: `lineage=${report.lineage}; nothing to stage`
|
|
4696
|
+
});
|
|
4697
|
+
return;
|
|
4698
|
+
}
|
|
4699
|
+
if (dryRun) {
|
|
4700
|
+
const total = report.registeredIds.length + report.unregisteredIds.length;
|
|
4701
|
+
record({
|
|
4702
|
+
name: category,
|
|
4703
|
+
status: "planned",
|
|
4704
|
+
detail: total === 0 ? "no components to stage" : `${total} component(s) to stage (lineage=${report.lineage})`
|
|
4705
|
+
});
|
|
4706
|
+
return;
|
|
4707
|
+
}
|
|
4708
|
+
try {
|
|
4709
|
+
const built = await buildStaging({
|
|
4710
|
+
category,
|
|
4711
|
+
projectRoot,
|
|
4712
|
+
aliases,
|
|
4713
|
+
lineageReport: report,
|
|
4714
|
+
trigger: "update",
|
|
4715
|
+
onlyIds: []
|
|
4716
|
+
});
|
|
4717
|
+
if (built === null) {
|
|
4718
|
+
record({
|
|
4719
|
+
name: category,
|
|
4720
|
+
status: "skip",
|
|
4721
|
+
detail: "no entries to stage"
|
|
4722
|
+
});
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4725
|
+
const stagingRel = path25.relative(projectRoot, built.stagingDir);
|
|
4726
|
+
const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
|
|
4727
|
+
record({
|
|
4728
|
+
name: category,
|
|
4729
|
+
status: "ok",
|
|
4730
|
+
detail: `${built.manifest.summary.total} component(s) staged${summary ? ` (${summary})` : ""}, see ${stagingRel}`
|
|
4731
|
+
});
|
|
4732
|
+
} catch (err) {
|
|
4733
|
+
recordFailure(category, err);
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
function summarizeStagingRisk(byRisk) {
|
|
4737
|
+
const order = [
|
|
4738
|
+
"risky",
|
|
4739
|
+
"breaking",
|
|
4740
|
+
"foreign",
|
|
4741
|
+
"upgradable-medium",
|
|
4742
|
+
"upgradable-low",
|
|
4743
|
+
"unchanged"
|
|
4744
|
+
];
|
|
4745
|
+
const parts = [];
|
|
4746
|
+
for (const k of order) {
|
|
4747
|
+
const v = byRisk[k];
|
|
4748
|
+
if (v && v > 0) parts.push(`${v} ${k}`);
|
|
4749
|
+
}
|
|
4750
|
+
return parts.join(", ");
|
|
4751
|
+
}
|
|
4752
|
+
async function planTokensUpdate(tokensPackage, config) {
|
|
4753
|
+
const tokensCfg = config.packages?.tokens;
|
|
4754
|
+
if (!tokensCfg) return "tokens not installed";
|
|
4755
|
+
const packageRoot = resolveTokensPackageRoot(tokensPackage);
|
|
4756
|
+
const catalog = await loadTokensPackageManifest3(packageRoot);
|
|
4757
|
+
const variantEntry = getVariantEntry3(catalog, tokensCfg.variant);
|
|
4758
|
+
if (!variantEntry) {
|
|
4759
|
+
return `variant "${tokensCfg.variant}" no longer in ${tokensPackage}@${catalog.version} \u2014 uninstall + re-init to switch`;
|
|
4760
|
+
}
|
|
4761
|
+
if (variantEntry.version === tokensCfg.version) {
|
|
4762
|
+
return `up-to-date (${tokensCfg.variant} v${tokensCfg.version})`;
|
|
4763
|
+
}
|
|
4764
|
+
return `${tokensCfg.variant} v${tokensCfg.version} \u2192 v${variantEntry.version}`;
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
// src/core/installer.ts
|
|
4768
|
+
import * as path26 from "path";
|
|
4769
|
+
import * as fs18 from "fs/promises";
|
|
4770
|
+
async function installResources(options) {
|
|
4771
|
+
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
4772
|
+
const installedResources = [];
|
|
4773
|
+
for (const resource of manifest.resources) {
|
|
4774
|
+
logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
|
|
4775
|
+
if (resource.recursive) {
|
|
4776
|
+
const results = await installRecursiveResource(
|
|
4777
|
+
resource,
|
|
4778
|
+
projectRoot,
|
|
4779
|
+
data,
|
|
4780
|
+
variantDir,
|
|
4781
|
+
packageRoot
|
|
4782
|
+
);
|
|
4783
|
+
installedResources.push(...results);
|
|
4784
|
+
} else {
|
|
4785
|
+
const result = await installSingleResource(
|
|
4786
|
+
resource,
|
|
4787
|
+
projectRoot,
|
|
4788
|
+
data,
|
|
4789
|
+
variantDir,
|
|
4790
|
+
packageRoot
|
|
4791
|
+
);
|
|
4792
|
+
installedResources.push(result);
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
return {
|
|
4796
|
+
resources: installedResources,
|
|
4797
|
+
count: installedResources.length
|
|
4798
|
+
};
|
|
4799
|
+
}
|
|
4800
|
+
async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
4801
|
+
const sourcePath = resolveSourcePath(
|
|
4802
|
+
resource.source,
|
|
4803
|
+
variantDir,
|
|
4804
|
+
packageRoot
|
|
4805
|
+
);
|
|
4806
|
+
const targetPath = path26.join(projectRoot, resource.target);
|
|
4807
|
+
let content;
|
|
4808
|
+
if (resource.template) {
|
|
4809
|
+
const templateContent = await loadTemplateFile(sourcePath);
|
|
4810
|
+
content = renderTemplate(templateContent, data);
|
|
4811
|
+
} else {
|
|
4812
|
+
content = await fs18.readFile(sourcePath, "utf-8");
|
|
4813
|
+
}
|
|
4814
|
+
await writeFileSafe(targetPath, content);
|
|
4815
|
+
const hash = computeHash(content);
|
|
4816
|
+
logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
|
|
4817
|
+
return {
|
|
4818
|
+
id: resource.id,
|
|
4819
|
+
target: resource.target,
|
|
4820
|
+
hash,
|
|
4821
|
+
strategy: resource.updateStrategy
|
|
4822
|
+
};
|
|
4823
|
+
}
|
|
4824
|
+
async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
4825
|
+
const sourcePath = resolveSourcePath(
|
|
4826
|
+
resource.source,
|
|
4827
|
+
variantDir,
|
|
4828
|
+
packageRoot
|
|
4829
|
+
);
|
|
4830
|
+
const targetDir = path26.join(projectRoot, resource.target);
|
|
4831
|
+
const results = [];
|
|
4832
|
+
await ensureDir(targetDir);
|
|
4833
|
+
const entries = await walkDir(sourcePath);
|
|
4834
|
+
for (const entry of entries) {
|
|
4835
|
+
const relPath = path26.relative(sourcePath, entry);
|
|
4836
|
+
let targetFile = path26.join(targetDir, relPath);
|
|
4837
|
+
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
4838
|
+
targetFile = targetFile.slice(0, -4);
|
|
4839
|
+
}
|
|
4840
|
+
let content;
|
|
4841
|
+
if (resource.template && entry.endsWith(".hbs")) {
|
|
4842
|
+
const templateContent = await loadTemplateFile(entry);
|
|
4843
|
+
content = renderTemplate(templateContent, data);
|
|
4844
|
+
} else {
|
|
4845
|
+
content = await fs18.readFile(entry, "utf-8");
|
|
4846
|
+
}
|
|
4847
|
+
await writeFileSafe(targetFile, content);
|
|
4848
|
+
const hash = computeHash(content);
|
|
4849
|
+
const targetRel = path26.relative(projectRoot, targetFile);
|
|
4850
|
+
results.push({
|
|
4851
|
+
id: `${resource.id}:${relPath}`,
|
|
4852
|
+
target: targetRel,
|
|
4853
|
+
hash,
|
|
4854
|
+
strategy: resource.updateStrategy
|
|
4855
|
+
});
|
|
4856
|
+
logger.debug(` Written: ${targetRel}`);
|
|
4857
|
+
}
|
|
4858
|
+
return results;
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
// src/core/registry-client.ts
|
|
4862
|
+
import * as path27 from "path";
|
|
4863
|
+
import * as fs19 from "fs/promises";
|
|
4864
|
+
import { createRequire as createRequire6 } from "module";
|
|
4865
|
+
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
4866
|
+
var require6 = createRequire6(import.meta.url);
|
|
4867
|
+
function resolvePackageRoot5(packageName) {
|
|
2113
4868
|
const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
|
|
2114
|
-
return
|
|
4869
|
+
return path27.dirname(pkgJsonPath);
|
|
2115
4870
|
}
|
|
2116
4871
|
async function loadVariantData(packageName, variant) {
|
|
2117
|
-
const packageRoot =
|
|
2118
|
-
const variantDir =
|
|
4872
|
+
const packageRoot = resolvePackageRoot5(packageName);
|
|
4873
|
+
const variantDir = path27.join(packageRoot, "library", variant);
|
|
2119
4874
|
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
2120
4875
|
logger.debug(`Package root: ${packageRoot}`);
|
|
2121
4876
|
const manifest = await loadVariantManifest(variantDir);
|
|
2122
4877
|
let data = {};
|
|
2123
|
-
const dataPath =
|
|
4878
|
+
const dataPath = path27.join(variantDir, "_data.json");
|
|
2124
4879
|
try {
|
|
2125
|
-
const raw = await
|
|
4880
|
+
const raw = await fs19.readFile(dataPath, "utf-8");
|
|
2126
4881
|
data = JSON.parse(raw);
|
|
2127
4882
|
} catch (err) {
|
|
2128
4883
|
if (err.code !== "ENOENT") {
|
|
@@ -2135,7 +4890,10 @@ async function loadVariantData(packageName, variant) {
|
|
|
2135
4890
|
export {
|
|
2136
4891
|
DEFAULT_UI_ALIASES,
|
|
2137
4892
|
DEFAULT_UI_ICON_LIBRARY,
|
|
4893
|
+
detectConflicts,
|
|
4894
|
+
detectProjectState,
|
|
2138
4895
|
ensureTeamixDir,
|
|
4896
|
+
extractDescriptionParts,
|
|
2139
4897
|
getTeamixDir,
|
|
2140
4898
|
installResources,
|
|
2141
4899
|
installSkills,
|
|
@@ -2153,7 +4911,10 @@ export {
|
|
|
2153
4911
|
removeSkillFiles,
|
|
2154
4912
|
removeUiFiles,
|
|
2155
4913
|
runBizUiAdd,
|
|
4914
|
+
runGenerateAgentsMd,
|
|
2156
4915
|
runLintInit,
|
|
4916
|
+
runProjectInit,
|
|
4917
|
+
runProjectUpdate,
|
|
2157
4918
|
runSkillsAdd,
|
|
2158
4919
|
runSkillsInit,
|
|
2159
4920
|
runSkillsUpdate,
|