teamix-evo 0.13.4 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -4
- package/dist/core/index.d.ts +68 -70
- package/dist/core/index.js +943 -731
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +1993 -2130
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
package/dist/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/core/tokens-init.ts
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as path8 from "path";
|
|
3
|
+
import * as fs7 from "fs/promises";
|
|
4
4
|
import {
|
|
5
5
|
loadTokensPackageManifest,
|
|
6
6
|
getVariantEntry
|
|
@@ -96,6 +96,7 @@ import {
|
|
|
96
96
|
validateSkillsLock,
|
|
97
97
|
TokensPackLockSchema
|
|
98
98
|
} from "@teamix-evo/registry";
|
|
99
|
+
import * as fs2 from "fs/promises";
|
|
99
100
|
|
|
100
101
|
// src/utils/error.ts
|
|
101
102
|
function getErrorMessage(err) {
|
|
@@ -113,9 +114,11 @@ var TEAMIX_DIR = ".teamix-evo";
|
|
|
113
114
|
var CONFIG_FILE = "config.json";
|
|
114
115
|
var MANIFEST_FILE = "manifest.json";
|
|
115
116
|
var TOKENS_LOCK_FILE = "tokens-lock.json";
|
|
116
|
-
var
|
|
117
|
-
var
|
|
118
|
-
|
|
117
|
+
var SKILLS_LOCK_FILE = "skills-lock.json";
|
|
118
|
+
var LEGACY_SKILLS_LOCK_PATHS = [
|
|
119
|
+
path2.join("skills-source", "manifest.lock.json"),
|
|
120
|
+
path2.join("skills", "manifest.lock.json")
|
|
121
|
+
];
|
|
119
122
|
function getTeamixDir(projectRoot) {
|
|
120
123
|
return path2.join(projectRoot, TEAMIX_DIR);
|
|
121
124
|
}
|
|
@@ -162,12 +165,40 @@ async function readInstalledManifest(projectRoot) {
|
|
|
162
165
|
);
|
|
163
166
|
}
|
|
164
167
|
const result = validateInstalled(data);
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
if (result.success) {
|
|
169
|
+
return result.data;
|
|
170
|
+
}
|
|
171
|
+
const migrated = migrateManifest(data);
|
|
172
|
+
const retryResult = validateInstalled(migrated);
|
|
173
|
+
if (retryResult.success) {
|
|
174
|
+
logger.warn(
|
|
175
|
+
"Migrated legacy manifest.json \u2014 will be overwritten on next install."
|
|
168
176
|
);
|
|
177
|
+
return retryResult.data;
|
|
169
178
|
}
|
|
170
|
-
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Invalid manifest.json schema: ${result.error}. Fix the file manually or remove it to start fresh.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
function migrateManifest(data) {
|
|
184
|
+
if (typeof data !== "object" || data === null) return data;
|
|
185
|
+
const obj = data;
|
|
186
|
+
if (!("schemaVersion" in obj)) obj.schemaVersion = 1;
|
|
187
|
+
if (!Array.isArray(obj.installed)) obj.installed = [];
|
|
188
|
+
for (const pkg of obj.installed) {
|
|
189
|
+
if (typeof pkg !== "object" || pkg === null) continue;
|
|
190
|
+
if (!pkg.variant) pkg.variant = "_flat";
|
|
191
|
+
if (!pkg.installedAt) pkg.installedAt = "";
|
|
192
|
+
if (!pkg.version) pkg.version = "0.0.0";
|
|
193
|
+
if (Array.isArray(pkg.resources)) {
|
|
194
|
+
for (const res of pkg.resources) {
|
|
195
|
+
if (typeof res !== "object" || res === null) continue;
|
|
196
|
+
if (!res.hash) res.hash = "sha256:legacy-unknown";
|
|
197
|
+
if (!res.strategy) res.strategy = "frozen";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return obj;
|
|
171
202
|
}
|
|
172
203
|
async function writeInstalledManifest(projectRoot, manifest) {
|
|
173
204
|
const manifestPath = path2.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
|
|
@@ -194,51 +225,61 @@ async function readTokensVariant(projectRoot) {
|
|
|
194
225
|
const lock = await readTokensLock(projectRoot);
|
|
195
226
|
return lock?.variant.name ?? null;
|
|
196
227
|
}
|
|
197
|
-
function getSkillsSourceDir(projectRoot, skillName) {
|
|
198
|
-
const base = path2.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
|
|
199
|
-
return skillName ? path2.join(base, skillName) : base;
|
|
200
|
-
}
|
|
201
|
-
function getLegacySkillsSourceDir(projectRoot) {
|
|
202
|
-
return path2.join(projectRoot, TEAMIX_DIR, LEGACY_SKILLS_DIR);
|
|
203
|
-
}
|
|
204
228
|
async function readSkillsLock(projectRoot) {
|
|
205
|
-
const lockPath = path2.join(
|
|
206
|
-
projectRoot,
|
|
207
|
-
TEAMIX_DIR,
|
|
208
|
-
SKILLS_DIR,
|
|
209
|
-
SKILLS_LOCK_FILE
|
|
210
|
-
);
|
|
229
|
+
const lockPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
|
|
211
230
|
const raw = await readFileOrNull(lockPath);
|
|
212
231
|
if (raw === null) return null;
|
|
213
232
|
try {
|
|
214
233
|
const data = JSON.parse(raw);
|
|
215
234
|
const result = validateSkillsLock(data);
|
|
216
235
|
if (!result.success) {
|
|
217
|
-
logger.warn(`Invalid skills
|
|
236
|
+
logger.warn(`Invalid skills-lock.json: ${result.error}`);
|
|
218
237
|
return null;
|
|
219
238
|
}
|
|
220
239
|
return result.data;
|
|
221
240
|
} catch (err) {
|
|
222
241
|
logger.warn(
|
|
223
|
-
`Failed to parse skills
|
|
242
|
+
`Failed to parse skills-lock.json: ${getErrorMessage(err)}`
|
|
224
243
|
);
|
|
225
244
|
return null;
|
|
226
245
|
}
|
|
227
246
|
}
|
|
228
247
|
async function writeSkillsLock(projectRoot, lock) {
|
|
229
|
-
const lockPath = path2.join(
|
|
230
|
-
projectRoot,
|
|
231
|
-
TEAMIX_DIR,
|
|
232
|
-
SKILLS_DIR,
|
|
233
|
-
SKILLS_LOCK_FILE
|
|
234
|
-
);
|
|
248
|
+
const lockPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
|
|
235
249
|
await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
236
250
|
logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
|
|
237
251
|
}
|
|
252
|
+
async function migrateSkillsLockLocation(projectRoot) {
|
|
253
|
+
const newPath = path2.join(projectRoot, TEAMIX_DIR, SKILLS_LOCK_FILE);
|
|
254
|
+
try {
|
|
255
|
+
await fs2.access(newPath);
|
|
256
|
+
} catch {
|
|
257
|
+
for (const legacyRel of LEGACY_SKILLS_LOCK_PATHS) {
|
|
258
|
+
const legacyPath = path2.join(projectRoot, TEAMIX_DIR, legacyRel);
|
|
259
|
+
try {
|
|
260
|
+
await fs2.access(legacyPath);
|
|
261
|
+
await ensureDir(path2.dirname(newPath));
|
|
262
|
+
await fs2.rename(legacyPath, newPath);
|
|
263
|
+
logger.debug(`Migrated skills lock: ${legacyRel} \u2192 ${SKILLS_LOCK_FILE}`);
|
|
264
|
+
break;
|
|
265
|
+
} catch {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const staleDir of ["skills-source", "skills"]) {
|
|
271
|
+
const abs = path2.join(projectRoot, TEAMIX_DIR, staleDir);
|
|
272
|
+
try {
|
|
273
|
+
await fs2.rm(abs, { recursive: true, force: true });
|
|
274
|
+
logger.debug(`Cleaned up stale dir: .teamix-evo/${staleDir}/`);
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
238
279
|
|
|
239
280
|
// src/core/skills-client.ts
|
|
240
281
|
import * as path3 from "path";
|
|
241
|
-
import * as
|
|
282
|
+
import * as fs3 from "fs/promises";
|
|
242
283
|
import { createRequire } from "module";
|
|
243
284
|
import { loadSkillsPackageManifest } from "@teamix-evo/registry";
|
|
244
285
|
var require2 = createRequire(import.meta.url);
|
|
@@ -253,7 +294,7 @@ async function loadSkillsData(packageName) {
|
|
|
253
294
|
let data = {};
|
|
254
295
|
const dataPath = path3.join(packageRoot, "_data.json");
|
|
255
296
|
try {
|
|
256
|
-
const raw = await
|
|
297
|
+
const raw = await fs3.readFile(dataPath, "utf-8");
|
|
257
298
|
data = JSON.parse(raw);
|
|
258
299
|
} catch (err) {
|
|
259
300
|
if (err.code !== "ENOENT") {
|
|
@@ -266,7 +307,7 @@ async function loadSkillsData(packageName) {
|
|
|
266
307
|
|
|
267
308
|
// src/core/skills-installer.ts
|
|
268
309
|
import * as path7 from "path";
|
|
269
|
-
import * as
|
|
310
|
+
import * as fs6 from "fs/promises";
|
|
270
311
|
import {
|
|
271
312
|
replaceManagedRegion,
|
|
272
313
|
hasManagedRegion,
|
|
@@ -326,7 +367,7 @@ function getAdapter(kind) {
|
|
|
326
367
|
|
|
327
368
|
// src/utils/template.ts
|
|
328
369
|
import Handlebars from "handlebars";
|
|
329
|
-
import * as
|
|
370
|
+
import * as fs4 from "fs/promises";
|
|
330
371
|
Handlebars.registerHelper("lowercase", (str) => {
|
|
331
372
|
return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
|
|
332
373
|
});
|
|
@@ -349,12 +390,12 @@ function renderTemplate(templateContent, data) {
|
|
|
349
390
|
return compiled(data);
|
|
350
391
|
}
|
|
351
392
|
async function loadTemplateFile(filePath) {
|
|
352
|
-
return
|
|
393
|
+
return fs4.readFile(filePath, "utf-8");
|
|
353
394
|
}
|
|
354
395
|
|
|
355
396
|
// src/utils/path.ts
|
|
356
397
|
import * as path6 from "path";
|
|
357
|
-
import * as
|
|
398
|
+
import * as fs5 from "fs/promises";
|
|
358
399
|
import { createRequire as createRequire2 } from "module";
|
|
359
400
|
var require3 = createRequire2(import.meta.url);
|
|
360
401
|
function resolveSourcePath(source, variantDir, packageRoot) {
|
|
@@ -365,7 +406,7 @@ function resolveSourcePath(source, variantDir, packageRoot) {
|
|
|
365
406
|
}
|
|
366
407
|
async function walkDir(dir, skipDirs) {
|
|
367
408
|
const files = [];
|
|
368
|
-
const entries = await
|
|
409
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
369
410
|
for (const entry of entries) {
|
|
370
411
|
const fullPath = path6.join(dir, entry.name);
|
|
371
412
|
if (entry.isDirectory()) {
|
|
@@ -381,10 +422,13 @@ function resolveTokensPackageRoot(packageName) {
|
|
|
381
422
|
const pkgJson = require3.resolve(`${packageName}/package.json`);
|
|
382
423
|
return path6.dirname(pkgJson);
|
|
383
424
|
}
|
|
425
|
+
function resolveResourceTarget(projectRoot, target) {
|
|
426
|
+
return path6.isAbsolute(target) ? target : path6.resolve(projectRoot, target);
|
|
427
|
+
}
|
|
384
428
|
|
|
385
429
|
// src/core/skills-installer.ts
|
|
386
430
|
async function installSkills(options) {
|
|
387
|
-
await
|
|
431
|
+
await migrateSkillsLockLocation(options.projectRoot);
|
|
388
432
|
const { manifest, ides, scope, onlyIds } = options;
|
|
389
433
|
const installed = [];
|
|
390
434
|
const targets = manifest.skills.filter(
|
|
@@ -400,75 +444,61 @@ async function installSkills(options) {
|
|
|
400
444
|
);
|
|
401
445
|
continue;
|
|
402
446
|
}
|
|
403
|
-
const
|
|
404
|
-
installed.push(...sourceRecords);
|
|
447
|
+
const rendered = await renderSkillFiles(skill, options);
|
|
405
448
|
for (const ide of skillIdes) {
|
|
406
|
-
const
|
|
449
|
+
const records = await writeRenderedToIde(
|
|
407
450
|
skill,
|
|
451
|
+
rendered,
|
|
408
452
|
ide,
|
|
409
453
|
scope,
|
|
410
454
|
options.projectRoot
|
|
411
455
|
);
|
|
412
|
-
installed.push(...
|
|
456
|
+
installed.push(...records);
|
|
413
457
|
}
|
|
414
458
|
}
|
|
415
459
|
return { resources: installed, count: installed.length };
|
|
416
460
|
}
|
|
417
|
-
async function
|
|
418
|
-
const { data, packageRoot
|
|
461
|
+
async function renderSkillFiles(skill, options) {
|
|
462
|
+
const { data, packageRoot } = options;
|
|
419
463
|
const sourceAbs = path7.resolve(packageRoot, skill.source);
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
const records = [];
|
|
464
|
+
const stat3 = await fs6.stat(sourceAbs);
|
|
465
|
+
const rendered = /* @__PURE__ */ new Map();
|
|
423
466
|
if (stat3.isFile()) {
|
|
424
|
-
const targetFile = path7.join(targetDir, "SKILL.md");
|
|
425
467
|
const content = await renderSkillContent(sourceAbs, skill, data);
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
logger.debug(` Wrote source: ${targetFile}`);
|
|
429
|
-
return records;
|
|
468
|
+
rendered.set("SKILL.md", content);
|
|
469
|
+
return rendered;
|
|
430
470
|
}
|
|
431
|
-
await ensureDir(targetDir);
|
|
432
471
|
const entries = await walkDir(sourceAbs);
|
|
433
472
|
for (const entry of entries) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
targetFile = targetFile.slice(0, -4);
|
|
473
|
+
let rel2 = path7.relative(sourceAbs, entry);
|
|
474
|
+
if (skill.template && rel2.endsWith(".hbs")) {
|
|
475
|
+
rel2 = rel2.slice(0, -4);
|
|
438
476
|
}
|
|
439
|
-
const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await
|
|
440
|
-
|
|
441
|
-
const relWritten = path7.relative(targetDir, targetFile);
|
|
442
|
-
records.push(makeSourceRecord(skill, targetFile, content, relWritten));
|
|
443
|
-
logger.debug(` Wrote source: ${targetFile}`);
|
|
477
|
+
const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs6.readFile(entry, "utf-8");
|
|
478
|
+
rendered.set(rel2, content);
|
|
444
479
|
}
|
|
445
|
-
return
|
|
480
|
+
return rendered;
|
|
446
481
|
}
|
|
447
|
-
async function
|
|
448
|
-
const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
482
|
+
async function writeRenderedToIde(skill, rendered, ide, scope, projectRoot) {
|
|
449
483
|
const adapter = getAdapter(ide);
|
|
450
484
|
const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
|
|
451
485
|
const records = [];
|
|
452
|
-
const sourceFiles = await walkDir(sourceDir);
|
|
453
486
|
await ensureDir(targetDir);
|
|
454
|
-
for (const
|
|
455
|
-
const rel2 = path7.relative(sourceDir, src);
|
|
487
|
+
for (const [rel2, sourceContent] of rendered) {
|
|
456
488
|
const targetFile = path7.join(targetDir, rel2);
|
|
457
|
-
const sourceContent = await fs5.readFile(src, "utf-8");
|
|
458
489
|
const writtenContent = await writeMirrorContent(
|
|
459
490
|
targetFile,
|
|
460
491
|
sourceContent,
|
|
461
|
-
skill.managedRegions
|
|
462
|
-
src
|
|
492
|
+
skill.managedRegions
|
|
463
493
|
);
|
|
464
494
|
records.push(
|
|
465
|
-
makeMirrorRecord(skill, targetFile, writtenContent, ide, scope, rel2)
|
|
495
|
+
makeMirrorRecord(skill, projectRoot, targetFile, writtenContent, ide, scope, rel2)
|
|
466
496
|
);
|
|
467
|
-
logger.debug(`
|
|
497
|
+
logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
|
|
468
498
|
}
|
|
469
499
|
return records;
|
|
470
500
|
}
|
|
471
|
-
async function writeMirrorContent(targetFile, sourceContent, managedRegions
|
|
501
|
+
async function writeMirrorContent(targetFile, sourceContent, managedRegions) {
|
|
472
502
|
const existing = await readFileOrNull(targetFile);
|
|
473
503
|
if (existing === null) {
|
|
474
504
|
await writeFileSafe(targetFile, sourceContent);
|
|
@@ -479,7 +509,7 @@ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sou
|
|
|
479
509
|
if (matchedRegions.length === 0) {
|
|
480
510
|
if (existing !== sourceContent) {
|
|
481
511
|
logger.warn(
|
|
482
|
-
`
|
|
512
|
+
`Drift detected at ${targetFile} \u2014 overwriting from upstream. Re-run \`npx teamix-evo@latest skills sync\` after any manual edits.`
|
|
483
513
|
);
|
|
484
514
|
await writeFileSafe(targetFile, sourceContent);
|
|
485
515
|
return sourceContent;
|
|
@@ -521,22 +551,13 @@ async function renderSkillContent(sourceAbs, skill, data) {
|
|
|
521
551
|
const tpl = await loadTemplateFile(sourceAbs);
|
|
522
552
|
return renderTemplate(tpl, { ...data, skill });
|
|
523
553
|
}
|
|
524
|
-
return
|
|
525
|
-
}
|
|
526
|
-
function makeSourceRecord(skill, targetAbs, content, rel2) {
|
|
527
|
-
const id = rel2 ? `${skill.id}:source:${rel2}` : `${skill.id}:source`;
|
|
528
|
-
return {
|
|
529
|
-
id,
|
|
530
|
-
target: targetAbs,
|
|
531
|
-
hash: computeHash(content),
|
|
532
|
-
strategy: skill.updateStrategy
|
|
533
|
-
};
|
|
554
|
+
return fs6.readFile(sourceAbs, "utf-8");
|
|
534
555
|
}
|
|
535
|
-
function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
|
|
556
|
+
function makeMirrorRecord(skill, projectRoot, targetAbs, content, ide, scope, rel2) {
|
|
536
557
|
const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
|
|
537
558
|
return {
|
|
538
559
|
id,
|
|
539
|
-
target: targetAbs,
|
|
560
|
+
target: path7.relative(projectRoot, targetAbs),
|
|
540
561
|
hash: computeHash(content),
|
|
541
562
|
strategy: skill.updateStrategy,
|
|
542
563
|
ide,
|
|
@@ -552,64 +573,41 @@ async function updateSkills(options) {
|
|
|
552
573
|
if (idFilter && !idFilter.has(skill.id)) continue;
|
|
553
574
|
const skillIdes = skill.ides.filter((i) => ides.includes(i));
|
|
554
575
|
if (skillIdes.length === 0) continue;
|
|
555
|
-
const
|
|
556
|
-
updated.push(...sourceRecords);
|
|
576
|
+
const rendered = await renderSkillFiles(skill, options);
|
|
557
577
|
for (const ide of skillIdes) {
|
|
558
|
-
const
|
|
578
|
+
const records = await updateRenderedInIde(
|
|
559
579
|
skill,
|
|
580
|
+
rendered,
|
|
560
581
|
ide,
|
|
561
582
|
scope,
|
|
562
|
-
projectRoot
|
|
583
|
+
projectRoot,
|
|
584
|
+
summary
|
|
563
585
|
);
|
|
564
|
-
updated.push(...
|
|
586
|
+
updated.push(...records);
|
|
565
587
|
}
|
|
566
588
|
}
|
|
567
589
|
return { resources: updated, summary };
|
|
568
590
|
}
|
|
569
|
-
async function
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
targetFile: targetFile2,
|
|
588
|
-
newContent: newContent2,
|
|
589
|
-
exists: exists2,
|
|
590
|
-
updateStrategy: skill.updateStrategy,
|
|
591
|
-
managedRegions: skill.managedRegions,
|
|
592
|
-
projectRoot,
|
|
593
|
-
summary
|
|
594
|
-
});
|
|
595
|
-
const relWritten = path7.relative(targetDir, targetFile2);
|
|
596
|
-
records.push(makeSourceRecord(skill, targetFile2, written2, relWritten));
|
|
597
|
-
}
|
|
598
|
-
return records;
|
|
591
|
+
async function updateRenderedInIde(skill, rendered, ide, scope, projectRoot, summary) {
|
|
592
|
+
const adapter = getAdapter(ide);
|
|
593
|
+
const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
|
|
594
|
+
const records = [];
|
|
595
|
+
await ensureDir(targetDir);
|
|
596
|
+
for (const [rel2, newContent] of rendered) {
|
|
597
|
+
const targetFile = path7.join(targetDir, rel2);
|
|
598
|
+
const exists = await fileExists(targetFile);
|
|
599
|
+
const written = await rewriteSingleFile({
|
|
600
|
+
targetFile,
|
|
601
|
+
newContent,
|
|
602
|
+
exists,
|
|
603
|
+
updateStrategy: skill.updateStrategy,
|
|
604
|
+
managedRegions: skill.managedRegions,
|
|
605
|
+
projectRoot,
|
|
606
|
+
summary
|
|
607
|
+
});
|
|
608
|
+
records.push(makeMirrorRecord(skill, projectRoot, targetFile, written, ide, scope, rel2));
|
|
599
609
|
}
|
|
600
|
-
|
|
601
|
-
const newContent = await renderSkillContent(sourceAbs, skill, data);
|
|
602
|
-
const exists = await fileExists(targetFile);
|
|
603
|
-
const written = await rewriteSingleFile({
|
|
604
|
-
targetFile,
|
|
605
|
-
newContent,
|
|
606
|
-
exists,
|
|
607
|
-
updateStrategy: skill.updateStrategy,
|
|
608
|
-
managedRegions: skill.managedRegions,
|
|
609
|
-
projectRoot,
|
|
610
|
-
summary
|
|
611
|
-
});
|
|
612
|
-
return [makeSourceRecord(skill, targetFile, written)];
|
|
610
|
+
return records;
|
|
613
611
|
}
|
|
614
612
|
async function rewriteSingleFile(args) {
|
|
615
613
|
const {
|
|
@@ -676,41 +674,30 @@ async function rewriteSingleFile(args) {
|
|
|
676
674
|
function escapeRegExp(str) {
|
|
677
675
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
678
676
|
}
|
|
679
|
-
async function
|
|
680
|
-
|
|
681
|
-
const { projectRoot, skills, ides, scope, onlyIds } = options;
|
|
677
|
+
async function reinstallSkillsToIdes(options) {
|
|
678
|
+
const { projectRoot, skills, manifest, data, packageRoot, ides, scope } = options;
|
|
682
679
|
const out = [];
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
logger.warn(
|
|
688
|
-
`Skill "${skill.id}" has no source at ${sourceDir}; skipped.`
|
|
689
|
-
);
|
|
680
|
+
for (const skill of skills) {
|
|
681
|
+
const entry = manifest.skills.find((s) => s.id === skill.id);
|
|
682
|
+
if (!entry) {
|
|
683
|
+
logger.warn(`Skill "${skill.id}" not found in npm package manifest; skipped.`);
|
|
690
684
|
continue;
|
|
691
685
|
}
|
|
686
|
+
const rendered = await renderSkillFiles(entry, { data, packageRoot });
|
|
692
687
|
for (const ide of ides) {
|
|
693
688
|
const adapter = getAdapter(ide);
|
|
694
|
-
const targetDir = adapter.getSkillTargetDir(
|
|
695
|
-
skill.name,
|
|
696
|
-
scope,
|
|
697
|
-
projectRoot
|
|
698
|
-
);
|
|
689
|
+
const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
|
|
699
690
|
await ensureDir(targetDir);
|
|
700
|
-
const
|
|
701
|
-
for (const src of sourceFiles) {
|
|
702
|
-
const rel2 = path7.relative(sourceDir, src);
|
|
691
|
+
for (const [rel2, sourceContent] of rendered) {
|
|
703
692
|
const targetFile = path7.join(targetDir, rel2);
|
|
704
|
-
const sourceContent = await fs5.readFile(src, "utf-8");
|
|
705
693
|
const writtenContent = await writeMirrorContent(
|
|
706
694
|
targetFile,
|
|
707
695
|
sourceContent,
|
|
708
|
-
skill.managedRegions
|
|
709
|
-
src
|
|
696
|
+
skill.managedRegions
|
|
710
697
|
);
|
|
711
698
|
out.push({
|
|
712
699
|
id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
|
|
713
|
-
target: targetFile,
|
|
700
|
+
target: path7.relative(projectRoot, targetFile),
|
|
714
701
|
hash: computeHash(writtenContent),
|
|
715
702
|
strategy: skill.updateStrategy,
|
|
716
703
|
ide,
|
|
@@ -721,75 +708,6 @@ async function syncSkillsToIdes(options) {
|
|
|
721
708
|
}
|
|
722
709
|
return { resources: out, count: out.length };
|
|
723
710
|
}
|
|
724
|
-
async function migrateLegacySkillsSourceDir(projectRoot) {
|
|
725
|
-
const legacyDir = getLegacySkillsSourceDir(projectRoot);
|
|
726
|
-
const newDir = getSkillsSourceDir(projectRoot);
|
|
727
|
-
let legacyExists = false;
|
|
728
|
-
let newExists = false;
|
|
729
|
-
try {
|
|
730
|
-
legacyExists = (await fs5.stat(legacyDir)).isDirectory();
|
|
731
|
-
} catch {
|
|
732
|
-
legacyExists = false;
|
|
733
|
-
}
|
|
734
|
-
try {
|
|
735
|
-
newExists = (await fs5.stat(newDir)).isDirectory();
|
|
736
|
-
} catch {
|
|
737
|
-
newExists = false;
|
|
738
|
-
}
|
|
739
|
-
if (!legacyExists) return;
|
|
740
|
-
if (newExists) {
|
|
741
|
-
logger.warn(
|
|
742
|
-
`Detected stale legacy skills source dir at ${legacyDir} alongside ${newDir}; the new layout takes precedence \u2014 you can safely delete the legacy dir.`
|
|
743
|
-
);
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
try {
|
|
747
|
-
await fs5.rename(legacyDir, newDir);
|
|
748
|
-
logger.info(
|
|
749
|
-
`Migrated skills source dir: \`.teamix-evo/${LEGACY_SKILLS_DIR}/\` \u2192 \`.teamix-evo/skills-source/\``
|
|
750
|
-
);
|
|
751
|
-
} catch (err) {
|
|
752
|
-
logger.warn(
|
|
753
|
-
`Failed to rename legacy skills source dir (${getErrorMessage(
|
|
754
|
-
err
|
|
755
|
-
)}); leaving as-is. New skills will install under the new layout.`
|
|
756
|
-
);
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
const manifest = await readInstalledManifest(projectRoot);
|
|
761
|
-
if (!manifest) return;
|
|
762
|
-
const legacyFragmentPosix = `/.teamix-evo/${LEGACY_SKILLS_DIR}/`;
|
|
763
|
-
const newFragmentPosix = `/.teamix-evo/skills-source/`;
|
|
764
|
-
const legacyFragmentNative = `${path7.sep}.teamix-evo${path7.sep}${LEGACY_SKILLS_DIR}${path7.sep}`;
|
|
765
|
-
const newFragmentNative = `${path7.sep}.teamix-evo${path7.sep}skills-source${path7.sep}`;
|
|
766
|
-
let touched = 0;
|
|
767
|
-
for (const pkg of manifest.installed) {
|
|
768
|
-
for (const r of pkg.resources) {
|
|
769
|
-
if (typeof r.target !== "string") continue;
|
|
770
|
-
const before = r.target;
|
|
771
|
-
let after = before.replace(legacyFragmentPosix, newFragmentPosix);
|
|
772
|
-
after = after.replace(legacyFragmentNative, newFragmentNative);
|
|
773
|
-
if (after !== before) {
|
|
774
|
-
r.target = after;
|
|
775
|
-
touched += 1;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
if (touched > 0) {
|
|
780
|
-
await writeInstalledManifest(projectRoot, manifest);
|
|
781
|
-
logger.debug(
|
|
782
|
-
`Rewrote ${touched} manifest target(s) to the new skills-source path.`
|
|
783
|
-
);
|
|
784
|
-
}
|
|
785
|
-
} catch (err) {
|
|
786
|
-
logger.warn(
|
|
787
|
-
`Migrated skills source dir but failed to update manifest paths (${getErrorMessage(
|
|
788
|
-
err
|
|
789
|
-
)}); manifest may still reference legacy paths.`
|
|
790
|
-
);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
711
|
async function pruneEmptyIdeSkillDirs(args) {
|
|
794
712
|
const removed = [];
|
|
795
713
|
for (const ide of args.ides) {
|
|
@@ -802,7 +720,7 @@ async function pruneEmptyIdeSkillDirs(args) {
|
|
|
802
720
|
const skillsRoot = path7.dirname(placeholderDir);
|
|
803
721
|
let entries;
|
|
804
722
|
try {
|
|
805
|
-
entries = await
|
|
723
|
+
entries = await fs6.readdir(skillsRoot);
|
|
806
724
|
} catch {
|
|
807
725
|
continue;
|
|
808
726
|
}
|
|
@@ -810,21 +728,21 @@ async function pruneEmptyIdeSkillDirs(args) {
|
|
|
810
728
|
const dir = path7.join(skillsRoot, name);
|
|
811
729
|
let stat3;
|
|
812
730
|
try {
|
|
813
|
-
stat3 = await
|
|
731
|
+
stat3 = await fs6.stat(dir);
|
|
814
732
|
} catch {
|
|
815
733
|
continue;
|
|
816
734
|
}
|
|
817
735
|
if (!stat3.isDirectory()) continue;
|
|
818
736
|
let children;
|
|
819
737
|
try {
|
|
820
|
-
children = await
|
|
738
|
+
children = await fs6.readdir(dir);
|
|
821
739
|
} catch {
|
|
822
740
|
continue;
|
|
823
741
|
}
|
|
824
742
|
if (children.some((c) => c === "SKILL.md")) continue;
|
|
825
743
|
if (children.length !== 0) continue;
|
|
826
744
|
try {
|
|
827
|
-
await
|
|
745
|
+
await fs6.rmdir(dir);
|
|
828
746
|
removed.push(dir);
|
|
829
747
|
logger.debug(`Pruned empty IDE skill dir: ${dir}`);
|
|
830
748
|
} catch {
|
|
@@ -833,26 +751,29 @@ async function pruneEmptyIdeSkillDirs(args) {
|
|
|
833
751
|
}
|
|
834
752
|
return removed;
|
|
835
753
|
}
|
|
836
|
-
async function removeSkillFiles(records) {
|
|
754
|
+
async function removeSkillFiles(records, projectRoot) {
|
|
837
755
|
const removed = [];
|
|
838
756
|
for (const r of records) {
|
|
757
|
+
const abs = resolveResourceTarget(projectRoot, r.target);
|
|
839
758
|
try {
|
|
840
|
-
await
|
|
841
|
-
removed.push(
|
|
759
|
+
await fs6.unlink(abs);
|
|
760
|
+
removed.push(abs);
|
|
842
761
|
} catch (err) {
|
|
843
762
|
if (err.code !== "ENOENT") {
|
|
844
|
-
logger.warn(`Failed to remove ${
|
|
763
|
+
logger.warn(`Failed to remove ${abs}: ${getErrorMessage(err)}`);
|
|
845
764
|
}
|
|
846
765
|
}
|
|
847
766
|
}
|
|
848
|
-
const startDirs = new Set(
|
|
767
|
+
const startDirs = new Set(
|
|
768
|
+
records.map((r) => path7.dirname(resolveResourceTarget(projectRoot, r.target)))
|
|
769
|
+
);
|
|
849
770
|
for (const startDir of startDirs) {
|
|
850
771
|
let dir = startDir;
|
|
851
772
|
for (let depth = 0; depth < 8; depth++) {
|
|
852
773
|
try {
|
|
853
|
-
const entries = await
|
|
774
|
+
const entries = await fs6.readdir(dir);
|
|
854
775
|
if (entries.length !== 0) break;
|
|
855
|
-
await
|
|
776
|
+
await fs6.rmdir(dir);
|
|
856
777
|
} catch {
|
|
857
778
|
break;
|
|
858
779
|
}
|
|
@@ -862,32 +783,6 @@ async function removeSkillFiles(records) {
|
|
|
862
783
|
return removed;
|
|
863
784
|
}
|
|
864
785
|
|
|
865
|
-
// src/utils/mcp.ts
|
|
866
|
-
import * as path8 from "path";
|
|
867
|
-
var MCP_JSON_CONTENT = {
|
|
868
|
-
mcpServers: {
|
|
869
|
-
"teamix-evo": {
|
|
870
|
-
command: "node",
|
|
871
|
-
args: ["node_modules/@teamix-evo/mcp/dist/cli.js"]
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
async function ensureMcpJson(projectRoot) {
|
|
876
|
-
const mcpPath = path8.join(projectRoot, ".mcp.json");
|
|
877
|
-
if (await fileExists(mcpPath)) return "exists";
|
|
878
|
-
try {
|
|
879
|
-
await writeFileSafe(
|
|
880
|
-
mcpPath,
|
|
881
|
-
JSON.stringify(MCP_JSON_CONTENT, null, 2) + "\n"
|
|
882
|
-
);
|
|
883
|
-
logger.debug(`Wrote .mcp.json \u2192 ${mcpPath}`);
|
|
884
|
-
return "created";
|
|
885
|
-
} catch (err) {
|
|
886
|
-
logger.warn(`Failed to write .mcp.json: ${getErrorMessage(err)}`);
|
|
887
|
-
return "failed";
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
786
|
// src/core/skills-add.ts
|
|
892
787
|
var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
|
|
893
788
|
var FLAT_VARIANT = "_flat";
|
|
@@ -1081,9 +976,15 @@ function partitionByVersion(ids, manifest, existing) {
|
|
|
1081
976
|
onlyIds.push(name);
|
|
1082
977
|
continue;
|
|
1083
978
|
}
|
|
1084
|
-
const installedVer = existing.lock?.skills?.[name]?.version;
|
|
979
|
+
const installedVer = existing.lock?.skills?.[name]?.version ?? existing.pkg?.version;
|
|
1085
980
|
const latestVer = manifestById.get(name)?.version;
|
|
1086
|
-
if (installedVer
|
|
981
|
+
if (!installedVer || !latestVer) {
|
|
982
|
+
if (latestVer) {
|
|
983
|
+
outdatedSkills.push({ id: name, installed: "0.0.0", latest: latestVer });
|
|
984
|
+
} else {
|
|
985
|
+
skippedSkillIds.push(name);
|
|
986
|
+
}
|
|
987
|
+
} else if (compareSemver(installedVer, latestVer) < 0) {
|
|
1087
988
|
outdatedSkills.push({
|
|
1088
989
|
id: name,
|
|
1089
990
|
installed: installedVer,
|
|
@@ -1201,7 +1102,6 @@ async function finalizeSkillsInstall(args) {
|
|
|
1201
1102
|
};
|
|
1202
1103
|
}
|
|
1203
1104
|
await writeSkillsLock(projectRoot, lock);
|
|
1204
|
-
await ensureMcpJson(projectRoot);
|
|
1205
1105
|
try {
|
|
1206
1106
|
await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
|
|
1207
1107
|
} catch {
|
|
@@ -1346,7 +1246,7 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
|
1346
1246
|
const result = await installVariantFile(fileRel, packageRoot, projectRoot);
|
|
1347
1247
|
if (result) installed.push(result);
|
|
1348
1248
|
}
|
|
1349
|
-
const overridesAbs =
|
|
1249
|
+
const overridesAbs = path8.join(
|
|
1350
1250
|
projectRoot,
|
|
1351
1251
|
CONSUMER_TOKENS_DIR,
|
|
1352
1252
|
CONSUMER_OVERRIDES_FILE
|
|
@@ -1356,10 +1256,10 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
|
1356
1256
|
}
|
|
1357
1257
|
const overridesId = `tokens:${CONSUMER_OVERRIDES_FILE}`;
|
|
1358
1258
|
if (!installed.some((r) => r.id === overridesId)) {
|
|
1359
|
-
const overridesContent = await
|
|
1259
|
+
const overridesContent = await fs7.readFile(overridesAbs, "utf-8");
|
|
1360
1260
|
installed.push({
|
|
1361
1261
|
id: overridesId,
|
|
1362
|
-
target:
|
|
1262
|
+
target: path8.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
|
|
1363
1263
|
hash: computeHash(overridesContent),
|
|
1364
1264
|
strategy: "frozen"
|
|
1365
1265
|
});
|
|
@@ -1377,7 +1277,7 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
|
1377
1277
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1378
1278
|
};
|
|
1379
1279
|
await writeFileSafe(
|
|
1380
|
-
|
|
1280
|
+
path8.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
|
|
1381
1281
|
JSON.stringify(lock, null, 2) + "\n"
|
|
1382
1282
|
);
|
|
1383
1283
|
const config = {
|
|
@@ -1409,7 +1309,6 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
|
1409
1309
|
if (tokensIdx >= 0) prior.installed[tokensIdx] = tokensEntry;
|
|
1410
1310
|
else prior.installed.push(tokensEntry);
|
|
1411
1311
|
await writeInstalledManifest(projectRoot, prior);
|
|
1412
|
-
await ensureMcpJson(projectRoot);
|
|
1413
1312
|
const skills = await tryAutoInstallVariantSkills({
|
|
1414
1313
|
projectRoot,
|
|
1415
1314
|
variant,
|
|
@@ -1498,12 +1397,12 @@ async function tryAutoInstallVariantSkills(args) {
|
|
|
1498
1397
|
}
|
|
1499
1398
|
}
|
|
1500
1399
|
async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
1501
|
-
const sourceAbs =
|
|
1502
|
-
const base =
|
|
1400
|
+
const sourceAbs = path8.join(packageRoot, fileRelToPackage);
|
|
1401
|
+
const base = path8.basename(fileRelToPackage);
|
|
1503
1402
|
if (base === "theme.css") {
|
|
1504
|
-
const targetRel =
|
|
1505
|
-
const targetAbs =
|
|
1506
|
-
const content = await
|
|
1403
|
+
const targetRel = path8.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
|
|
1404
|
+
const targetAbs = path8.join(projectRoot, targetRel);
|
|
1405
|
+
const content = await fs7.readFile(sourceAbs, "utf-8");
|
|
1507
1406
|
if (await fileExists(targetAbs)) {
|
|
1508
1407
|
await backupFile(targetAbs, projectRoot);
|
|
1509
1408
|
}
|
|
@@ -1516,13 +1415,13 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1516
1415
|
};
|
|
1517
1416
|
}
|
|
1518
1417
|
if (base === "overrides.css" || base === "tokens.overrides.css") {
|
|
1519
|
-
const targetRel =
|
|
1418
|
+
const targetRel = path8.posix.join(
|
|
1520
1419
|
CONSUMER_TOKENS_DIR,
|
|
1521
1420
|
CONSUMER_OVERRIDES_FILE
|
|
1522
1421
|
);
|
|
1523
|
-
const targetAbs =
|
|
1422
|
+
const targetAbs = path8.join(projectRoot, targetRel);
|
|
1524
1423
|
if (await fileExists(targetAbs)) {
|
|
1525
|
-
const existing = await
|
|
1424
|
+
const existing = await fs7.readFile(targetAbs, "utf-8");
|
|
1526
1425
|
return {
|
|
1527
1426
|
id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
|
|
1528
1427
|
target: targetRel,
|
|
@@ -1530,7 +1429,7 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1530
1429
|
strategy: "frozen"
|
|
1531
1430
|
};
|
|
1532
1431
|
}
|
|
1533
|
-
const content = await
|
|
1432
|
+
const content = await fs7.readFile(sourceAbs, "utf-8");
|
|
1534
1433
|
await writeFileSafe(targetAbs, content);
|
|
1535
1434
|
return {
|
|
1536
1435
|
id: `tokens:${CONSUMER_OVERRIDES_FILE}`,
|
|
@@ -1557,77 +1456,332 @@ async function listTokenVariants(packageName = DEFAULT_TOKENS_PACKAGE, packageRo
|
|
|
1557
1456
|
};
|
|
1558
1457
|
}
|
|
1559
1458
|
|
|
1560
|
-
// src/core/
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1459
|
+
// src/core/agents-md.ts
|
|
1460
|
+
import * as fs8 from "fs/promises";
|
|
1461
|
+
import * as path9 from "path";
|
|
1462
|
+
import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
|
|
1463
|
+
var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
|
|
1464
|
+
async function runGenerateAgentsMd(options) {
|
|
1465
|
+
const { projectRoot, variant, skillIds } = options;
|
|
1466
|
+
const mode = options.mode ?? "overwrite";
|
|
1566
1467
|
const config = await readProjectConfig(projectRoot);
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
const
|
|
1574
|
-
|
|
1575
|
-
|
|
1468
|
+
const lock = await readSkillsLock(projectRoot);
|
|
1469
|
+
const ides = config?.packages?.skills?.ides ?? ["qoder", "claude"];
|
|
1470
|
+
const scope = config?.packages?.skills?.scope ?? lock?.skills[skillIds[0] ?? ""]?.scope ?? "project";
|
|
1471
|
+
const ordered = [...skillIds].sort(
|
|
1472
|
+
(a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
|
|
1473
|
+
);
|
|
1474
|
+
const sections = [];
|
|
1475
|
+
const missingSkillIds = [];
|
|
1476
|
+
for (const id of ordered) {
|
|
1477
|
+
const { section, missing } = await renderSkillSection(projectRoot, id, ides, scope);
|
|
1478
|
+
sections.push(section);
|
|
1479
|
+
if (missing) missingSkillIds.push(id);
|
|
1576
1480
|
}
|
|
1577
|
-
const
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
)
|
|
1481
|
+
const target = path9.join(projectRoot, "AGENTS.md");
|
|
1482
|
+
const targetExists = await fileExists(target);
|
|
1483
|
+
const fullTemplate = renderAgentsMd({ variant, sections });
|
|
1484
|
+
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
1485
|
+
let outputContent;
|
|
1486
|
+
let merge;
|
|
1487
|
+
if (!targetExists) {
|
|
1488
|
+
outputContent = fullTemplate;
|
|
1489
|
+
merge = "created";
|
|
1490
|
+
} else {
|
|
1491
|
+
await backupFile(target, projectRoot);
|
|
1492
|
+
if (mode === "merge-managed") {
|
|
1493
|
+
const existing = await readFileOrNull(target) ?? "";
|
|
1494
|
+
if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
|
|
1495
|
+
outputContent = replaceManagedRegion2(
|
|
1496
|
+
existing,
|
|
1497
|
+
AGENTS_MD_MANAGED_ID,
|
|
1498
|
+
managedBody
|
|
1499
|
+
);
|
|
1500
|
+
merge = "managed-replaced";
|
|
1501
|
+
} else {
|
|
1502
|
+
const wrapped = wrapManagedBlock(managedBody);
|
|
1503
|
+
outputContent = `${wrapped}
|
|
1504
|
+
|
|
1505
|
+
${PRECEDENCE_NOTICE}
|
|
1506
|
+
|
|
1507
|
+
${existing.trimStart()}`;
|
|
1508
|
+
merge = "managed-prepended";
|
|
1509
|
+
}
|
|
1510
|
+
} else {
|
|
1511
|
+
outputContent = fullTemplate;
|
|
1512
|
+
merge = "overwritten";
|
|
1591
1513
|
}
|
|
1592
1514
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1515
|
+
await fs8.writeFile(target, outputContent, "utf8");
|
|
1516
|
+
return {
|
|
1517
|
+
path: target,
|
|
1518
|
+
skillCount: ordered.length,
|
|
1519
|
+
missingSkillIds,
|
|
1520
|
+
backedUp: targetExists,
|
|
1521
|
+
merge
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
function bucketRank(id) {
|
|
1525
|
+
if (id.startsWith("teamix-evo-design-")) return 0;
|
|
1526
|
+
if (id.startsWith("teamix-evo-code-")) return 1;
|
|
1527
|
+
return 2;
|
|
1528
|
+
}
|
|
1529
|
+
async function renderSkillSection(projectRoot, skillId, ides, scope) {
|
|
1530
|
+
const lines = [];
|
|
1531
|
+
lines.push(`### ${skillId}`);
|
|
1532
|
+
let parts = null;
|
|
1533
|
+
let missing = false;
|
|
1534
|
+
for (const ide of ides) {
|
|
1535
|
+
const adapter = getAdapter(ide);
|
|
1536
|
+
const skillPath = path9.join(
|
|
1537
|
+
adapter.getSkillTargetDir(skillId, scope, projectRoot),
|
|
1538
|
+
"SKILL.md"
|
|
1539
|
+
);
|
|
1540
|
+
try {
|
|
1541
|
+
const raw = await fs8.readFile(skillPath, "utf8");
|
|
1542
|
+
parts = extractDescriptionParts(raw);
|
|
1543
|
+
break;
|
|
1544
|
+
} catch {
|
|
1611
1545
|
continue;
|
|
1612
1546
|
}
|
|
1613
|
-
targetIds.push(id);
|
|
1614
1547
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1548
|
+
if (!parts) missing = true;
|
|
1549
|
+
if (parts?.capability) {
|
|
1550
|
+
lines.push(`- ${parts.capability}`);
|
|
1551
|
+
}
|
|
1552
|
+
lines.push(
|
|
1553
|
+
`- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
|
|
1554
|
+
);
|
|
1555
|
+
lines.push(
|
|
1556
|
+
`- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
|
|
1557
|
+
);
|
|
1558
|
+
if (parts?.coordinates) {
|
|
1559
|
+
lines.push(`- **Coordinates with**: ${parts.coordinates}`);
|
|
1560
|
+
}
|
|
1561
|
+
return { section: lines.join("\n"), missing };
|
|
1562
|
+
}
|
|
1563
|
+
function renderAgentsMd(args) {
|
|
1564
|
+
const { variant, sections } = args;
|
|
1565
|
+
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
1566
|
+
const wrapped = wrapManagedBlock(managedBody);
|
|
1567
|
+
return `${wrapped}
|
|
1568
|
+
|
|
1569
|
+
${PRECEDENCE_NOTICE}
|
|
1570
|
+
`;
|
|
1571
|
+
}
|
|
1572
|
+
function renderManagedBlockBody(args) {
|
|
1573
|
+
const { variant, sections } = args;
|
|
1574
|
+
const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
|
|
1575
|
+
return `# AGENTS.md
|
|
1576
|
+
|
|
1577
|
+
> AI \u52A9\u624B\uFF1A\u5199\u4EE3\u7801\u524D\u5148\u5BF9\u7167\u4E0B\u65B9 TRIGGER\uFF0C\u547D\u4E2D\u5219\u5FC5\u987B\u8BFB\u5BF9\u5E94 skill \u518D\u52A8\u624B\uFF1B\u4E0D\u786E\u5B9A\u65F6\u4ECD\u5EFA\u8BAE\u5FEB\u901F\u67E5\u9605\u76F8\u5173 skill \u4EE5\u786E\u8BA4\u662F\u5426\u6D89\u53CA\u89C4\u8303\u7EA6\u675F\u3002
|
|
1578
|
+
>
|
|
1579
|
+
> \u6FC0\u6D3B\u65B9\u5F0F\uFF1A\u5728\u5BF9\u8BDD\u4E2D\u76F4\u63A5\u63D0\u53CA skill \u540D\u79F0\uFF08\u5982 \`teamix-evo-design-${variant}\`\uFF09\uFF0CIDE \u4F1A\u81EA\u52A8\u52A0\u8F7D\u5176\u5B8C\u6574\u5185\u5BB9\u3002
|
|
1580
|
+
|
|
1581
|
+
## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
|
|
1582
|
+
|
|
1583
|
+
${skillBlock}
|
|
1584
|
+
|
|
1585
|
+
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo@latest skills update\``;
|
|
1586
|
+
}
|
|
1587
|
+
function wrapManagedBlock(body) {
|
|
1588
|
+
return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
|
|
1589
|
+
${body}
|
|
1590
|
+
<!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
|
|
1591
|
+
}
|
|
1592
|
+
var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
|
|
1593
|
+
> \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`;
|
|
1594
|
+
function extractDescriptionParts(fileContent) {
|
|
1595
|
+
const description = extractDescriptionBlock(fileContent);
|
|
1596
|
+
if (description == null) return null;
|
|
1597
|
+
const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1598
|
+
let capability = "";
|
|
1599
|
+
for (const line of allLines) {
|
|
1600
|
+
if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
|
|
1601
|
+
capability = capability ? `${capability} ${line}` : line;
|
|
1602
|
+
}
|
|
1603
|
+
return {
|
|
1604
|
+
capability: capability.trim(),
|
|
1605
|
+
trigger: extractSection(description, "TRIGGER when:"),
|
|
1606
|
+
skip: extractSection(description, "SKIP:"),
|
|
1607
|
+
coordinates: extractSection(description, "Coordinates with:")
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function extractDescriptionBlock(fileContent) {
|
|
1611
|
+
const lines = fileContent.split("\n");
|
|
1612
|
+
if (lines[0]?.trim() !== "---") return null;
|
|
1613
|
+
let endIdx = -1;
|
|
1614
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1615
|
+
if (lines[i]?.trim() === "---") {
|
|
1616
|
+
endIdx = i;
|
|
1617
|
+
break;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
if (endIdx === -1) return null;
|
|
1621
|
+
const fmLines = lines.slice(1, endIdx);
|
|
1622
|
+
let startIdx = -1;
|
|
1623
|
+
let inlineValue = null;
|
|
1624
|
+
let blockMode = "inline";
|
|
1625
|
+
for (let i = 0; i < fmLines.length; i++) {
|
|
1626
|
+
const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
|
|
1627
|
+
if (m) {
|
|
1628
|
+
startIdx = i;
|
|
1629
|
+
const indicator = (m[1] ?? "").trim();
|
|
1630
|
+
const rest = m[2] ?? "";
|
|
1631
|
+
if (indicator.startsWith("|")) blockMode = "literal";
|
|
1632
|
+
else if (indicator.startsWith(">")) blockMode = "folded";
|
|
1633
|
+
else {
|
|
1634
|
+
blockMode = "inline";
|
|
1635
|
+
inlineValue = rest;
|
|
1636
|
+
}
|
|
1637
|
+
break;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (startIdx === -1) return null;
|
|
1641
|
+
if (blockMode === "inline") {
|
|
1642
|
+
return inlineValue ?? "";
|
|
1643
|
+
}
|
|
1644
|
+
const body = [];
|
|
1645
|
+
let blockIndent = -1;
|
|
1646
|
+
for (let i = startIdx + 1; i < fmLines.length; i++) {
|
|
1647
|
+
const line = fmLines[i] ?? "";
|
|
1648
|
+
if (line.trim() === "") {
|
|
1649
|
+
body.push("");
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
const indentMatch = line.match(/^(\s+)/);
|
|
1653
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
1654
|
+
if (indent === 0) break;
|
|
1655
|
+
if (blockIndent === -1) blockIndent = indent;
|
|
1656
|
+
if (indent < blockIndent) break;
|
|
1657
|
+
body.push(line.slice(blockIndent));
|
|
1658
|
+
}
|
|
1659
|
+
while (body.length > 0 && body[body.length - 1] === "") body.pop();
|
|
1660
|
+
return body.join("\n");
|
|
1661
|
+
}
|
|
1662
|
+
function extractSection(description, marker) {
|
|
1663
|
+
const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
|
|
1664
|
+
const lines = description.split("\n");
|
|
1665
|
+
let inSection = false;
|
|
1666
|
+
const collected = [];
|
|
1667
|
+
for (const line of lines) {
|
|
1668
|
+
const trimmed = line.trim();
|
|
1669
|
+
const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
|
|
1670
|
+
if (!inSection && startsWithMarker) {
|
|
1671
|
+
inSection = true;
|
|
1672
|
+
collected.push(trimmed.slice(marker.length).trim());
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (inSection) {
|
|
1676
|
+
const hitNextMarker = markers.some(
|
|
1677
|
+
(m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
|
|
1678
|
+
);
|
|
1679
|
+
if (hitNextMarker) break;
|
|
1680
|
+
if (trimmed === "") {
|
|
1681
|
+
if (collected[collected.length - 1] === "") break;
|
|
1682
|
+
collected.push("");
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
collected.push(trimmed);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (!inSection) return null;
|
|
1689
|
+
const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
|
|
1690
|
+
return joined || null;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// src/core/skills-update.ts
|
|
1694
|
+
var DEFAULT_SKILLS_PACKAGE3 = "@teamix-evo/skills";
|
|
1695
|
+
var FLAT_VARIANT2 = "_flat";
|
|
1696
|
+
async function runSkillsUpdate(options) {
|
|
1697
|
+
const { projectRoot, names: requestedNames, dryRun } = options;
|
|
1698
|
+
const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE3;
|
|
1699
|
+
const config = await readProjectConfig(projectRoot);
|
|
1700
|
+
const skillsCfg = config?.packages?.skills;
|
|
1701
|
+
if (!skillsCfg) {
|
|
1702
|
+
return { status: "no-skills" };
|
|
1703
|
+
}
|
|
1704
|
+
const ides = skillsCfg.ides ?? ["qoder", "claude"];
|
|
1705
|
+
const scope = skillsCfg.scope ?? "project";
|
|
1706
|
+
let existingLock = await readSkillsLock(projectRoot);
|
|
1707
|
+
if (!existingLock || Object.keys(existingLock.skills).length === 0) {
|
|
1708
|
+
const installedManifest2 = await readInstalledManifest(projectRoot);
|
|
1709
|
+
const pkg = installedManifest2?.installed.find(
|
|
1710
|
+
(p) => p.package === packageName
|
|
1711
|
+
);
|
|
1712
|
+
if (!pkg || pkg.resources.length === 0) {
|
|
1713
|
+
return { status: "no-skills" };
|
|
1714
|
+
}
|
|
1715
|
+
const derivedSkills = {};
|
|
1716
|
+
for (const r of pkg.resources) {
|
|
1717
|
+
const skillId = r.id.split(":")[0] ?? r.id;
|
|
1718
|
+
if (!derivedSkills[skillId]) {
|
|
1719
|
+
derivedSkills[skillId] = {
|
|
1720
|
+
version: pkg.version,
|
|
1721
|
+
from: packageName,
|
|
1722
|
+
installedAt: "",
|
|
1723
|
+
scope,
|
|
1724
|
+
mirroredTo: []
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
existingLock = { schemaVersion: 1, skills: derivedSkills };
|
|
1729
|
+
}
|
|
1730
|
+
const activeLock = existingLock;
|
|
1731
|
+
const { manifest, data, packageRoot } = await loadSkillsData(packageName);
|
|
1732
|
+
const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
|
|
1733
|
+
const lockIds = Object.keys(activeLock.skills);
|
|
1734
|
+
const requestedSet = requestedNames ? new Set(requestedNames) : null;
|
|
1735
|
+
if (requestedSet) {
|
|
1736
|
+
const unknown = requestedNames.filter(
|
|
1737
|
+
(n) => !lockIds.includes(n) && !manifestById.has(n)
|
|
1738
|
+
);
|
|
1739
|
+
if (unknown.length > 0) {
|
|
1740
|
+
throw new Error(
|
|
1741
|
+
`Unknown skill id(s): ${unknown.join(
|
|
1742
|
+
", "
|
|
1743
|
+
)}. Available (installed): ${lockIds.join(", ") || "(none)"}.`
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
const targetIds = [];
|
|
1748
|
+
const skippedSkillIds = [];
|
|
1749
|
+
for (const id of lockIds) {
|
|
1750
|
+
if (requestedSet && !requestedSet.has(id)) continue;
|
|
1751
|
+
const entry2 = manifestById.get(id);
|
|
1752
|
+
if (!entry2) {
|
|
1753
|
+
logger.debug(
|
|
1754
|
+
`Skipping "${id}": no longer in upstream manifest. Use \`skills uninstall ${id}\` to remove.`
|
|
1755
|
+
);
|
|
1756
|
+
skippedSkillIds.push(id);
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
const effectiveScope = entry2.scope ?? "project";
|
|
1760
|
+
if (effectiveScope !== scope) {
|
|
1761
|
+
logger.debug(
|
|
1762
|
+
`Skipping "${id}" (scope=${effectiveScope}): current install scope is "${scope}".`
|
|
1763
|
+
);
|
|
1764
|
+
skippedSkillIds.push(id);
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
targetIds.push(id);
|
|
1768
|
+
}
|
|
1769
|
+
const allSame = targetIds.every((id) => {
|
|
1770
|
+
const lockVer = activeLock.skills[id].version;
|
|
1771
|
+
const manVer = manifestById.get(id).version;
|
|
1772
|
+
return lockVer === manVer;
|
|
1773
|
+
});
|
|
1774
|
+
if (targetIds.length > 0 && allSame && !dryRun) {
|
|
1775
|
+
return {
|
|
1776
|
+
status: "no-changes",
|
|
1777
|
+
packageName,
|
|
1778
|
+
version: manifest.version,
|
|
1779
|
+
checkedSkillIds: targetIds
|
|
1626
1780
|
};
|
|
1627
1781
|
}
|
|
1628
1782
|
if (dryRun) {
|
|
1629
1783
|
const plan = targetIds.map((id) => {
|
|
1630
|
-
const lockVer =
|
|
1784
|
+
const lockVer = activeLock.skills[id].version;
|
|
1631
1785
|
const entry2 = manifestById.get(id);
|
|
1632
1786
|
const sameVersion = lockVer === entry2.version;
|
|
1633
1787
|
return {
|
|
@@ -1699,7 +1853,7 @@ async function runSkillsUpdate(options) {
|
|
|
1699
1853
|
await writeInstalledManifest(projectRoot, installedManifest);
|
|
1700
1854
|
const lock = {
|
|
1701
1855
|
schemaVersion: 1,
|
|
1702
|
-
skills: { ...
|
|
1856
|
+
skills: { ...activeLock.skills }
|
|
1703
1857
|
};
|
|
1704
1858
|
for (const id of targetIds) {
|
|
1705
1859
|
const skillDef = manifestById.get(id);
|
|
@@ -1714,6 +1868,24 @@ async function runSkillsUpdate(options) {
|
|
|
1714
1868
|
};
|
|
1715
1869
|
}
|
|
1716
1870
|
await writeSkillsLock(projectRoot, lock);
|
|
1871
|
+
if (scope === "project") {
|
|
1872
|
+
try {
|
|
1873
|
+
const variant = await readTokensVariant(projectRoot);
|
|
1874
|
+
if (variant) {
|
|
1875
|
+
const projectSkillIds = Object.entries(lock.skills).filter(([, v]) => v.scope === "project").map(([id]) => id);
|
|
1876
|
+
await runGenerateAgentsMd({
|
|
1877
|
+
projectRoot,
|
|
1878
|
+
variant,
|
|
1879
|
+
skillIds: projectSkillIds,
|
|
1880
|
+
mode: "merge-managed"
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
} catch (err) {
|
|
1884
|
+
logger.warn(
|
|
1885
|
+
`AGENTS.md \u5237\u65B0\u5931\u8D25\uFF08\u975E\u5173\u952E\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}`
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1717
1889
|
return {
|
|
1718
1890
|
status: "updated",
|
|
1719
1891
|
packageName,
|
|
@@ -1734,8 +1906,7 @@ var DEFAULT_UI_ALIASES = {
|
|
|
1734
1906
|
utils: "src/lib/utils",
|
|
1735
1907
|
lib: "src/lib",
|
|
1736
1908
|
business: "src/components/business",
|
|
1737
|
-
blocks: "src/blocks"
|
|
1738
|
-
templates: "src/templates"
|
|
1909
|
+
blocks: "src/blocks"
|
|
1739
1910
|
};
|
|
1740
1911
|
var DEFAULT_UI_ICON_LIBRARY = "lucide";
|
|
1741
1912
|
async function runUiInit(options) {
|
|
@@ -1752,8 +1923,7 @@ async function runUiInit(options) {
|
|
|
1752
1923
|
utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
|
|
1753
1924
|
lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
|
|
1754
1925
|
business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
|
|
1755
|
-
blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks
|
|
1756
|
-
templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
|
|
1926
|
+
blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks
|
|
1757
1927
|
};
|
|
1758
1928
|
const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
|
|
1759
1929
|
const tsx = options.tsx ?? true;
|
|
@@ -1773,7 +1943,6 @@ async function runUiInit(options) {
|
|
|
1773
1943
|
rsc
|
|
1774
1944
|
};
|
|
1775
1945
|
await writeProjectConfig(projectRoot, config);
|
|
1776
|
-
await ensureMcpJson(projectRoot);
|
|
1777
1946
|
return {
|
|
1778
1947
|
status: "installed",
|
|
1779
1948
|
aliases,
|
|
@@ -1785,7 +1954,7 @@ async function runUiInit(options) {
|
|
|
1785
1954
|
|
|
1786
1955
|
// src/core/ui-client.ts
|
|
1787
1956
|
import * as path10 from "path";
|
|
1788
|
-
import * as
|
|
1957
|
+
import * as fs9 from "fs/promises";
|
|
1789
1958
|
import { createRequire as createRequire3 } from "module";
|
|
1790
1959
|
import { loadUiPackageManifest } from "@teamix-evo/registry";
|
|
1791
1960
|
var require4 = createRequire3(import.meta.url);
|
|
@@ -1800,7 +1969,7 @@ async function loadUiData(packageName) {
|
|
|
1800
1969
|
let data = {};
|
|
1801
1970
|
const dataPath = path10.join(packageRoot, "_data.json");
|
|
1802
1971
|
try {
|
|
1803
|
-
const raw = await
|
|
1972
|
+
const raw = await fs9.readFile(dataPath, "utf-8");
|
|
1804
1973
|
data = JSON.parse(raw);
|
|
1805
1974
|
} catch (err) {
|
|
1806
1975
|
if (err.code !== "ENOENT") {
|
|
@@ -1813,7 +1982,7 @@ async function loadUiData(packageName) {
|
|
|
1813
1982
|
|
|
1814
1983
|
// src/core/ui-installer.ts
|
|
1815
1984
|
import * as path11 from "path";
|
|
1816
|
-
import * as
|
|
1985
|
+
import * as fs10 from "fs/promises";
|
|
1817
1986
|
import { resolveUiEntryOrder } from "@teamix-evo/registry";
|
|
1818
1987
|
|
|
1819
1988
|
// src/utils/transform-imports.ts
|
|
@@ -1870,8 +2039,14 @@ async function installUiEntries(options) {
|
|
|
1870
2039
|
const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
|
|
1871
2040
|
const resources = [];
|
|
1872
2041
|
const npmDeps = {};
|
|
1873
|
-
let
|
|
2042
|
+
let created = 0;
|
|
2043
|
+
let overwritten = 0;
|
|
2044
|
+
let userModified = 0;
|
|
1874
2045
|
let skipped = 0;
|
|
2046
|
+
const { priorResources } = options;
|
|
2047
|
+
const priorByTarget = new Map(
|
|
2048
|
+
(priorResources ?? []).map((r) => [r.target, r])
|
|
2049
|
+
);
|
|
1875
2050
|
for (const id of orderedIds) {
|
|
1876
2051
|
const entry = idToEntry.get(id);
|
|
1877
2052
|
if (!entry) continue;
|
|
@@ -1890,17 +2065,43 @@ async function installUiEntries(options) {
|
|
|
1890
2065
|
}
|
|
1891
2066
|
const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
|
|
1892
2067
|
const sourceAbs = path11.resolve(rootForEntry, file.source);
|
|
1893
|
-
const raw = await
|
|
2068
|
+
const raw = await fs10.readFile(sourceAbs, "utf-8");
|
|
1894
2069
|
const transformed = rewriteImports(raw, aliases, { flatten });
|
|
1895
2070
|
if (exists) {
|
|
2071
|
+
const current = await fs10.readFile(targetAbs, "utf-8");
|
|
2072
|
+
if (current === transformed) {
|
|
2073
|
+
logger.info(` skip (identical): ${rel(projectRoot, targetAbs)}`);
|
|
2074
|
+
skipped++;
|
|
2075
|
+
resources.push({
|
|
2076
|
+
id: `${entry.id}:${file.targetName}`,
|
|
2077
|
+
target: path11.relative(projectRoot, targetAbs),
|
|
2078
|
+
hash: computeHash(transformed),
|
|
2079
|
+
strategy: entry.updateStrategy ?? "frozen"
|
|
2080
|
+
});
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
const relPath = path11.relative(projectRoot, targetAbs);
|
|
2084
|
+
const prior = priorByTarget.get(relPath);
|
|
2085
|
+
if (prior && prior.hash !== "sha256:legacy-unknown") {
|
|
2086
|
+
const onDiskHash = computeHash(current);
|
|
2087
|
+
if (onDiskHash !== prior.hash) {
|
|
2088
|
+
logger.warn(
|
|
2089
|
+
` \u26A0 ${relPath} has local modifications (backed up)`
|
|
2090
|
+
);
|
|
2091
|
+
userModified++;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
1896
2094
|
await backupFile(targetAbs, projectRoot);
|
|
2095
|
+
overwritten++;
|
|
2096
|
+
logger.info(` overwrite: ${rel(projectRoot, targetAbs)}`);
|
|
2097
|
+
} else {
|
|
2098
|
+
created++;
|
|
2099
|
+
logger.info(` create: ${rel(projectRoot, targetAbs)}`);
|
|
1897
2100
|
}
|
|
1898
2101
|
await writeFileSafe(targetAbs, transformed);
|
|
1899
|
-
written++;
|
|
1900
|
-
logger.info(` write: ${rel(projectRoot, targetAbs)}`);
|
|
1901
2102
|
resources.push({
|
|
1902
2103
|
id: `${entry.id}:${file.targetName}`,
|
|
1903
|
-
target: targetAbs,
|
|
2104
|
+
target: path11.relative(projectRoot, targetAbs),
|
|
1904
2105
|
hash: computeHash(transformed),
|
|
1905
2106
|
strategy: entry.updateStrategy ?? "frozen"
|
|
1906
2107
|
});
|
|
@@ -1910,7 +2111,10 @@ async function installUiEntries(options) {
|
|
|
1910
2111
|
orderedIds,
|
|
1911
2112
|
resources,
|
|
1912
2113
|
npmDependencies: npmDeps,
|
|
1913
|
-
written,
|
|
2114
|
+
written: created + overwritten,
|
|
2115
|
+
created,
|
|
2116
|
+
overwritten,
|
|
2117
|
+
userModified,
|
|
1914
2118
|
skipped
|
|
1915
2119
|
};
|
|
1916
2120
|
}
|
|
@@ -1926,23 +2130,26 @@ function resolveTargetPath(projectRoot, aliases, entry, file) {
|
|
|
1926
2130
|
function rel(projectRoot, abs) {
|
|
1927
2131
|
return path11.relative(projectRoot, abs);
|
|
1928
2132
|
}
|
|
1929
|
-
async function removeUiFiles(records) {
|
|
2133
|
+
async function removeUiFiles(records, projectRoot) {
|
|
1930
2134
|
const removed = [];
|
|
1931
2135
|
for (const r of records) {
|
|
2136
|
+
const abs = resolveResourceTarget(projectRoot, r.target);
|
|
1932
2137
|
try {
|
|
1933
|
-
await
|
|
1934
|
-
removed.push(
|
|
2138
|
+
await fs10.unlink(abs);
|
|
2139
|
+
removed.push(abs);
|
|
1935
2140
|
} catch (err) {
|
|
1936
2141
|
if (err.code !== "ENOENT") {
|
|
1937
|
-
logger.warn(`Failed to remove ${
|
|
2142
|
+
logger.warn(`Failed to remove ${abs}: ${getErrorMessage(err)}`);
|
|
1938
2143
|
}
|
|
1939
2144
|
}
|
|
1940
2145
|
}
|
|
1941
|
-
const parents = new Set(
|
|
2146
|
+
const parents = new Set(
|
|
2147
|
+
records.map((r) => path11.dirname(resolveResourceTarget(projectRoot, r.target)))
|
|
2148
|
+
);
|
|
1942
2149
|
for (const dir of parents) {
|
|
1943
2150
|
try {
|
|
1944
|
-
const entries = await
|
|
1945
|
-
if (entries.length === 0) await
|
|
2151
|
+
const entries = await fs10.readdir(dir);
|
|
2152
|
+
if (entries.length === 0) await fs10.rmdir(dir);
|
|
1946
2153
|
} catch {
|
|
1947
2154
|
}
|
|
1948
2155
|
}
|
|
@@ -1986,17 +2193,22 @@ async function runUiAdd(options) {
|
|
|
1986
2193
|
`Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
|
|
1987
2194
|
);
|
|
1988
2195
|
}
|
|
2196
|
+
const existingManifest = await readInstalledManifest(
|
|
2197
|
+
projectRoot
|
|
2198
|
+
) ?? { schemaVersion: 1, installed: [] };
|
|
2199
|
+
const priorPkg = existingManifest.installed.find(
|
|
2200
|
+
(p) => p.package === packageName
|
|
2201
|
+
);
|
|
1989
2202
|
const result = await installUiEntries({
|
|
1990
2203
|
projectRoot,
|
|
1991
2204
|
manifest: effectiveManifest,
|
|
1992
2205
|
packageRoot,
|
|
1993
2206
|
aliases: uiCfg.aliases,
|
|
1994
2207
|
requested: ids,
|
|
1995
|
-
skipExisting: !overwrite
|
|
2208
|
+
skipExisting: !overwrite,
|
|
2209
|
+
priorResources: priorPkg?.resources
|
|
1996
2210
|
});
|
|
1997
|
-
const installed =
|
|
1998
|
-
projectRoot
|
|
1999
|
-
) ?? { schemaVersion: 1, installed: [] };
|
|
2211
|
+
const installed = existingManifest;
|
|
2000
2212
|
const idx = installed.installed.findIndex((p) => p.package === packageName);
|
|
2001
2213
|
const prior = idx >= 0 ? installed.installed[idx] : null;
|
|
2002
2214
|
const mergedResources = mergeResources(
|
|
@@ -2021,6 +2233,9 @@ async function runUiAdd(options) {
|
|
|
2021
2233
|
packageName,
|
|
2022
2234
|
orderedIds: result.orderedIds,
|
|
2023
2235
|
written: result.written,
|
|
2236
|
+
created: result.created,
|
|
2237
|
+
overwritten: result.overwritten,
|
|
2238
|
+
userModified: result.userModified,
|
|
2024
2239
|
skipped: result.skipped,
|
|
2025
2240
|
npmDependencies: result.npmDependencies,
|
|
2026
2241
|
resources: result.resources
|
|
@@ -2091,7 +2306,7 @@ async function runVariantUiAdd(packageName, options) {
|
|
|
2091
2306
|
const uiCfg = config?.packages?.ui;
|
|
2092
2307
|
if (!config || !uiCfg?.aliases) {
|
|
2093
2308
|
throw new Error(
|
|
2094
|
-
`UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business /
|
|
2309
|
+
`UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business / blocks).`
|
|
2095
2310
|
);
|
|
2096
2311
|
}
|
|
2097
2312
|
const packageRoot = options.packageRoot ?? resolvePackageRoot3(fullPackageName);
|
|
@@ -2131,24 +2346,23 @@ async function runVariantUiAdd(packageName, options) {
|
|
|
2131
2346
|
engines: variantManifest.engines,
|
|
2132
2347
|
entries: mergedEntries
|
|
2133
2348
|
};
|
|
2349
|
+
const installed = await readInstalledManifest(
|
|
2350
|
+
projectRoot
|
|
2351
|
+
) ?? { schemaVersion: 1, installed: [] };
|
|
2352
|
+
const idx = installed.installed.findIndex(
|
|
2353
|
+
(p) => p.package === fullPackageName && p.variant === variant
|
|
2354
|
+
);
|
|
2355
|
+
const prior = idx >= 0 ? installed.installed[idx] : null;
|
|
2134
2356
|
const result = await installUiEntries({
|
|
2135
2357
|
projectRoot,
|
|
2136
2358
|
manifest: adaptedManifest,
|
|
2137
2359
|
packageRoot: variantDir,
|
|
2138
|
-
// default for variant entries
|
|
2139
2360
|
entryPackageRoot,
|
|
2140
|
-
// ui entries resolve from uiPackageRoot
|
|
2141
2361
|
aliases: uiCfg.aliases,
|
|
2142
2362
|
requested: ids,
|
|
2143
|
-
skipExisting: !overwrite
|
|
2363
|
+
skipExisting: !overwrite,
|
|
2364
|
+
priorResources: prior?.resources
|
|
2144
2365
|
});
|
|
2145
|
-
const installed = await readInstalledManifest(
|
|
2146
|
-
projectRoot
|
|
2147
|
-
) ?? { schemaVersion: 1, installed: [] };
|
|
2148
|
-
const idx = installed.installed.findIndex(
|
|
2149
|
-
(p) => p.package === fullPackageName && p.variant === variant
|
|
2150
|
-
);
|
|
2151
|
-
const prior = idx >= 0 ? installed.installed[idx] : null;
|
|
2152
2366
|
const mergedResources = mergeResources2(
|
|
2153
2367
|
prior?.resources ?? [],
|
|
2154
2368
|
result.resources
|
|
@@ -2168,6 +2382,9 @@ async function runVariantUiAdd(packageName, options) {
|
|
|
2168
2382
|
variant,
|
|
2169
2383
|
orderedIds: result.orderedIds,
|
|
2170
2384
|
written: result.written,
|
|
2385
|
+
created: result.created,
|
|
2386
|
+
overwritten: result.overwritten,
|
|
2387
|
+
userModified: result.userModified,
|
|
2171
2388
|
skipped: result.skipped,
|
|
2172
2389
|
npmDependencies: result.npmDependencies,
|
|
2173
2390
|
resources: result.resources
|
|
@@ -2182,9 +2399,6 @@ function mergeResources2(prior, next) {
|
|
|
2182
2399
|
async function runBizUiAdd(options) {
|
|
2183
2400
|
return runVariantUiAdd("biz-ui", options);
|
|
2184
2401
|
}
|
|
2185
|
-
async function runTemplatesAdd(options) {
|
|
2186
|
-
return runVariantUiAdd("templates", options);
|
|
2187
|
-
}
|
|
2188
2402
|
async function listVariantUi(packageName, packageRoot) {
|
|
2189
2403
|
const fullPackageName = `@teamix-evo/${packageName}`;
|
|
2190
2404
|
const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
|
|
@@ -2202,9 +2416,6 @@ async function listVariantUi(packageName, packageRoot) {
|
|
|
2202
2416
|
async function listBizUiVariants(packageRoot) {
|
|
2203
2417
|
return listVariantUi("biz-ui", packageRoot);
|
|
2204
2418
|
}
|
|
2205
|
-
async function listTemplatesVariants(packageRoot) {
|
|
2206
|
-
return listVariantUi("templates", packageRoot);
|
|
2207
|
-
}
|
|
2208
2419
|
async function listVariantUiEntries(packageName, variant, packageRoot) {
|
|
2209
2420
|
const fullPackageName = `@teamix-evo/${packageName}`;
|
|
2210
2421
|
const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
|
|
@@ -2232,13 +2443,10 @@ async function listVariantUiEntries(packageName, variant, packageRoot) {
|
|
|
2232
2443
|
async function listBizUiEntries(variant, packageRoot) {
|
|
2233
2444
|
return listVariantUiEntries("biz-ui", variant, packageRoot);
|
|
2234
2445
|
}
|
|
2235
|
-
async function listTemplatesEntries(variant, packageRoot) {
|
|
2236
|
-
return listVariantUiEntries("templates", variant, packageRoot);
|
|
2237
|
-
}
|
|
2238
2446
|
|
|
2239
2447
|
// src/core/lint-init.ts
|
|
2240
2448
|
import * as path13 from "path";
|
|
2241
|
-
import * as
|
|
2449
|
+
import * as fs11 from "fs";
|
|
2242
2450
|
import { execa } from "execa";
|
|
2243
2451
|
var ESLINT_CONFIG_CONTENT = `/**
|
|
2244
2452
|
* teamix-evo consumer ESLint preset \u2014 9 token-discipline rules.
|
|
@@ -2330,7 +2538,7 @@ async function runLintInit(options) {
|
|
|
2330
2538
|
let stylelintIgnoreFilesWarning = false;
|
|
2331
2539
|
if (!stylelintNeedsWrite && stylelintTemplateExists) {
|
|
2332
2540
|
try {
|
|
2333
|
-
const existingContent =
|
|
2541
|
+
const existingContent = fs11.readFileSync(stylelintConfigPath, "utf-8");
|
|
2334
2542
|
const usesTeamixPreset = existingContent.includes("@teamix-evo/stylelint-config/presets/") || existingContent.includes("@teamix-evo/stylelint-config/preset/");
|
|
2335
2543
|
const hasTokenIgnore = existingContent.includes("tokens.theme.css") && existingContent.includes("tokens.overrides.css");
|
|
2336
2544
|
if (!usesTeamixPreset && !hasTokenIgnore) {
|
|
@@ -2344,305 +2552,65 @@ async function runLintInit(options) {
|
|
|
2344
2552
|
" '**/tokens.theme.css',",
|
|
2345
2553
|
" '**/tokens.overrides.css',",
|
|
2346
2554
|
" ]",
|
|
2347
|
-
"",
|
|
2348
|
-
"\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
|
|
2349
|
-
"",
|
|
2350
|
-
" extends: ['@teamix-evo/stylelint-config/presets/consumer']"
|
|
2351
|
-
].join("\n")
|
|
2352
|
-
);
|
|
2353
|
-
}
|
|
2354
|
-
} catch {
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
return {
|
|
2358
|
-
status: "installed",
|
|
2359
|
-
eslint: wroteEslint,
|
|
2360
|
-
stylelint: wroteStylelint,
|
|
2361
|
-
eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
|
|
2362
|
-
stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
|
|
2363
|
-
eslintSkipped: eslintSkipRequested,
|
|
2364
|
-
stylelintSkipped: stylelintSkipRequested,
|
|
2365
|
-
packageJsonPatched,
|
|
2366
|
-
stylelintIgnoreFilesWarning
|
|
2367
|
-
};
|
|
2368
|
-
}
|
|
2369
|
-
function detectPm(projectRoot) {
|
|
2370
|
-
if (fs9.existsSync(path13.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
2371
|
-
if (fs9.existsSync(path13.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
2372
|
-
return "npm";
|
|
2373
|
-
}
|
|
2374
|
-
async function patchPackageJsonScripts(projectRoot) {
|
|
2375
|
-
const pkgPath = path13.join(projectRoot, "package.json");
|
|
2376
|
-
const raw = await readFileOrNull(pkgPath);
|
|
2377
|
-
if (!raw) return false;
|
|
2378
|
-
let pkg;
|
|
2379
|
-
try {
|
|
2380
|
-
pkg = JSON.parse(raw);
|
|
2381
|
-
} catch {
|
|
2382
|
-
return false;
|
|
2383
|
-
}
|
|
2384
|
-
const scripts = pkg.scripts ?? {};
|
|
2385
|
-
let changed = false;
|
|
2386
|
-
if (!scripts.lint) {
|
|
2387
|
-
scripts.lint = "eslint src/";
|
|
2388
|
-
changed = true;
|
|
2389
|
-
}
|
|
2390
|
-
if (!scripts["lint:css"]) {
|
|
2391
|
-
scripts["lint:css"] = "stylelint 'src/**/*.css'";
|
|
2392
|
-
changed = true;
|
|
2393
|
-
}
|
|
2394
|
-
if (changed) {
|
|
2395
|
-
await backupFile(pkgPath, projectRoot);
|
|
2396
|
-
pkg.scripts = scripts;
|
|
2397
|
-
await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2398
|
-
logger.debug("Patched package.json scripts with lint / lint:css");
|
|
2399
|
-
}
|
|
2400
|
-
return changed;
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
// src/core/agents-md.ts
|
|
2404
|
-
import * as fs10 from "fs/promises";
|
|
2405
|
-
import * as path14 from "path";
|
|
2406
|
-
import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
|
|
2407
|
-
var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
|
|
2408
|
-
async function runGenerateAgentsMd(options) {
|
|
2409
|
-
const { projectRoot, variant, skillIds } = options;
|
|
2410
|
-
const mode = options.mode ?? "overwrite";
|
|
2411
|
-
const ordered = [...skillIds].sort(
|
|
2412
|
-
(a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
|
|
2413
|
-
);
|
|
2414
|
-
const sections = [];
|
|
2415
|
-
const missingSkillIds = [];
|
|
2416
|
-
for (const id of ordered) {
|
|
2417
|
-
const { section, missing } = await renderSkillSection(projectRoot, id);
|
|
2418
|
-
sections.push(section);
|
|
2419
|
-
if (missing) missingSkillIds.push(id);
|
|
2420
|
-
}
|
|
2421
|
-
const target = path14.join(projectRoot, "AGENTS.md");
|
|
2422
|
-
const targetExists = await fileExists(target);
|
|
2423
|
-
const fullTemplate = renderAgentsMd({ variant, sections });
|
|
2424
|
-
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
2425
|
-
let outputContent;
|
|
2426
|
-
let merge;
|
|
2427
|
-
if (!targetExists) {
|
|
2428
|
-
outputContent = fullTemplate;
|
|
2429
|
-
merge = "created";
|
|
2430
|
-
} else {
|
|
2431
|
-
await backupFile(target, projectRoot);
|
|
2432
|
-
if (mode === "merge-managed") {
|
|
2433
|
-
const existing = await readFileOrNull(target) ?? "";
|
|
2434
|
-
if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
|
|
2435
|
-
outputContent = replaceManagedRegion2(
|
|
2436
|
-
existing,
|
|
2437
|
-
AGENTS_MD_MANAGED_ID,
|
|
2438
|
-
managedBody
|
|
2439
|
-
);
|
|
2440
|
-
merge = "managed-replaced";
|
|
2441
|
-
} else {
|
|
2442
|
-
const wrapped = wrapManagedBlock(managedBody);
|
|
2443
|
-
outputContent = `${wrapped}
|
|
2444
|
-
|
|
2445
|
-
${PRECEDENCE_NOTICE}
|
|
2446
|
-
|
|
2447
|
-
${existing.trimStart()}`;
|
|
2448
|
-
merge = "managed-prepended";
|
|
2449
|
-
}
|
|
2450
|
-
} else {
|
|
2451
|
-
outputContent = fullTemplate;
|
|
2452
|
-
merge = "overwritten";
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
2455
|
-
await fs10.writeFile(target, outputContent, "utf8");
|
|
2456
|
-
return {
|
|
2457
|
-
path: target,
|
|
2458
|
-
skillCount: ordered.length,
|
|
2459
|
-
missingSkillIds,
|
|
2460
|
-
backedUp: targetExists,
|
|
2461
|
-
merge
|
|
2462
|
-
};
|
|
2463
|
-
}
|
|
2464
|
-
function bucketRank(id) {
|
|
2465
|
-
if (id.startsWith("teamix-evo-design-")) return 0;
|
|
2466
|
-
if (id.startsWith("teamix-evo-code-")) return 1;
|
|
2467
|
-
return 2;
|
|
2468
|
-
}
|
|
2469
|
-
async function renderSkillSection(projectRoot, skillId) {
|
|
2470
|
-
const skillPath = path14.join(
|
|
2471
|
-
getSkillsSourceDir(projectRoot, skillId),
|
|
2472
|
-
"SKILL.md"
|
|
2473
|
-
);
|
|
2474
|
-
const lines = [];
|
|
2475
|
-
lines.push(`### ${skillId}`);
|
|
2476
|
-
let parts = null;
|
|
2477
|
-
let missing = false;
|
|
2478
|
-
try {
|
|
2479
|
-
const raw = await fs10.readFile(skillPath, "utf8");
|
|
2480
|
-
parts = extractDescriptionParts(raw);
|
|
2481
|
-
} catch {
|
|
2482
|
-
missing = true;
|
|
2483
|
-
}
|
|
2484
|
-
if (parts?.capability) {
|
|
2485
|
-
lines.push(`- ${parts.capability}`);
|
|
2486
|
-
}
|
|
2487
|
-
lines.push(
|
|
2488
|
-
`- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
|
|
2489
|
-
);
|
|
2490
|
-
lines.push(
|
|
2491
|
-
`- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
|
|
2492
|
-
);
|
|
2493
|
-
if (parts?.coordinates) {
|
|
2494
|
-
lines.push(`- **Coordinates with**: ${parts.coordinates}`);
|
|
2495
|
-
}
|
|
2496
|
-
lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills-source/${skillId}/SKILL.md\``);
|
|
2497
|
-
return { section: lines.join("\n"), missing };
|
|
2498
|
-
}
|
|
2499
|
-
function renderAgentsMd(args) {
|
|
2500
|
-
const { variant, sections } = args;
|
|
2501
|
-
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
2502
|
-
const wrapped = wrapManagedBlock(managedBody);
|
|
2503
|
-
return `${wrapped}
|
|
2504
|
-
|
|
2505
|
-
${PRECEDENCE_NOTICE}
|
|
2506
|
-
`;
|
|
2507
|
-
}
|
|
2508
|
-
function renderManagedBlockBody(args) {
|
|
2509
|
-
const { variant, sections } = args;
|
|
2510
|
-
const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
|
|
2511
|
-
return `# AGENTS.md
|
|
2512
|
-
|
|
2513
|
-
> \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
|
|
2514
|
-
> \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C\u9075\u5FAA ADR 0038\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
|
|
2515
|
-
|
|
2516
|
-
## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
|
|
2517
|
-
|
|
2518
|
-
${skillBlock}
|
|
2519
|
-
|
|
2520
|
-
## \u89E6\u53D1\u515C\u5E95\u89C4\u5219
|
|
2521
|
-
|
|
2522
|
-
- \u5199\u65B0 \`.tsx\` / \`.ts\` \u524D\uFF0C\u5BF9\u7167\u4E0A\u8FF0 TRIGGER \u5224\u5B9A\u662F\u5426\u547D\u4E2D
|
|
2523
|
-
- \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
|
|
2524
|
-
- \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
|
|
2525
|
-
- \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
|
|
2526
|
-
- \u8FC1\u79FB\u573A\u666F\uFF08"\u4EE3\u7801\u8FC1\u79FB" / "\u6267\u884C\u8FC1\u79FB" / "\u65E7\u9879\u76EE\u8FC1\u79FB" / "\u91CD\u5EFA\u8001\u5DE5\u7A0B"\uFF09\u540C\u6837\u8D70 \`teamix-evo-manage\` \u573A\u666F 6
|
|
2527
|
-
|
|
2528
|
-
## UI \u7EC4\u4EF6\u9886\u5730\uFF08init \u540E\u7EA6\u675F\uFF09
|
|
2529
|
-
|
|
2530
|
-
- \`src/components/ui/\` \u7531 teamix-evo \u63A5\u7BA1\uFF0C\u7981\u6B62\u624B\u5DE5\u6216\u901A\u8FC7 shadcn CLI \u6DFB\u52A0\u65B0\u7EC4\u4EF6
|
|
2531
|
-
- \u5982\u9700\u65B0\u589E UI \u7EC4\u4EF6\uFF0C\u4F7F\u7528 \`npx teamix-evo ui add <id>\`
|
|
2532
|
-
- \`src/components/shadcn-ui/\` \u662F init \u524D legacy \u7EC4\u4EF6\u5F52\u6863\uFF0C\u53EA\u8BFB\uFF1B\u65B0\u4EE3\u7801\u4E0D\u5E94\u518D import
|
|
2533
|
-
- \u5347\u7EA7 legacy \u7EC4\u4EF6\uFF1A\u89E6\u53D1 teamix-evo-upgrade skill
|
|
2534
|
-
|
|
2535
|
-
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
|
|
2536
|
-
}
|
|
2537
|
-
function wrapManagedBlock(body) {
|
|
2538
|
-
return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
|
|
2539
|
-
${body}
|
|
2540
|
-
<!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
|
|
2541
|
-
}
|
|
2542
|
-
var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
|
|
2543
|
-
> \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`;
|
|
2544
|
-
function extractDescriptionParts(fileContent) {
|
|
2545
|
-
const description = extractDescriptionBlock(fileContent);
|
|
2546
|
-
if (description == null) return null;
|
|
2547
|
-
const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2548
|
-
let capability = "";
|
|
2549
|
-
for (const line of allLines) {
|
|
2550
|
-
if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
|
|
2551
|
-
capability = capability ? `${capability} ${line}` : line;
|
|
2552
|
-
}
|
|
2553
|
-
return {
|
|
2554
|
-
capability: capability.trim(),
|
|
2555
|
-
trigger: extractSection(description, "TRIGGER when:"),
|
|
2556
|
-
skip: extractSection(description, "SKIP:"),
|
|
2557
|
-
coordinates: extractSection(description, "Coordinates with:")
|
|
2558
|
-
};
|
|
2559
|
-
}
|
|
2560
|
-
function extractDescriptionBlock(fileContent) {
|
|
2561
|
-
const lines = fileContent.split("\n");
|
|
2562
|
-
if (lines[0]?.trim() !== "---") return null;
|
|
2563
|
-
let endIdx = -1;
|
|
2564
|
-
for (let i = 1; i < lines.length; i++) {
|
|
2565
|
-
if (lines[i]?.trim() === "---") {
|
|
2566
|
-
endIdx = i;
|
|
2567
|
-
break;
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
if (endIdx === -1) return null;
|
|
2571
|
-
const fmLines = lines.slice(1, endIdx);
|
|
2572
|
-
let startIdx = -1;
|
|
2573
|
-
let inlineValue = null;
|
|
2574
|
-
let blockMode = "inline";
|
|
2575
|
-
for (let i = 0; i < fmLines.length; i++) {
|
|
2576
|
-
const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
|
|
2577
|
-
if (m) {
|
|
2578
|
-
startIdx = i;
|
|
2579
|
-
const indicator = (m[1] ?? "").trim();
|
|
2580
|
-
const rest = m[2] ?? "";
|
|
2581
|
-
if (indicator.startsWith("|")) blockMode = "literal";
|
|
2582
|
-
else if (indicator.startsWith(">")) blockMode = "folded";
|
|
2583
|
-
else {
|
|
2584
|
-
blockMode = "inline";
|
|
2585
|
-
inlineValue = rest;
|
|
2555
|
+
"",
|
|
2556
|
+
"\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
|
|
2557
|
+
"",
|
|
2558
|
+
" extends: ['@teamix-evo/stylelint-config/presets/consumer']"
|
|
2559
|
+
].join("\n")
|
|
2560
|
+
);
|
|
2586
2561
|
}
|
|
2587
|
-
|
|
2562
|
+
} catch {
|
|
2588
2563
|
}
|
|
2589
2564
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2565
|
+
return {
|
|
2566
|
+
status: "installed",
|
|
2567
|
+
eslint: wroteEslint,
|
|
2568
|
+
stylelint: wroteStylelint,
|
|
2569
|
+
eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
|
|
2570
|
+
stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
|
|
2571
|
+
eslintSkipped: eslintSkipRequested,
|
|
2572
|
+
stylelintSkipped: stylelintSkipRequested,
|
|
2573
|
+
packageJsonPatched,
|
|
2574
|
+
stylelintIgnoreFilesWarning
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
function detectPm(projectRoot) {
|
|
2578
|
+
if (fs11.existsSync(path13.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
2579
|
+
if (fs11.existsSync(path13.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
2580
|
+
return "npm";
|
|
2581
|
+
}
|
|
2582
|
+
async function patchPackageJsonScripts(projectRoot) {
|
|
2583
|
+
const pkgPath = path13.join(projectRoot, "package.json");
|
|
2584
|
+
const raw = await readFileOrNull(pkgPath);
|
|
2585
|
+
if (!raw) return false;
|
|
2586
|
+
let pkg;
|
|
2587
|
+
try {
|
|
2588
|
+
pkg = JSON.parse(raw);
|
|
2589
|
+
} catch {
|
|
2590
|
+
return false;
|
|
2593
2591
|
}
|
|
2594
|
-
const
|
|
2595
|
-
let
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
body.push("");
|
|
2600
|
-
continue;
|
|
2601
|
-
}
|
|
2602
|
-
const indentMatch = line.match(/^(\s+)/);
|
|
2603
|
-
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
2604
|
-
if (indent === 0) break;
|
|
2605
|
-
if (blockIndent === -1) blockIndent = indent;
|
|
2606
|
-
if (indent < blockIndent) break;
|
|
2607
|
-
body.push(line.slice(blockIndent));
|
|
2592
|
+
const scripts = pkg.scripts ?? {};
|
|
2593
|
+
let changed = false;
|
|
2594
|
+
if (!scripts.lint) {
|
|
2595
|
+
scripts.lint = "eslint src/";
|
|
2596
|
+
changed = true;
|
|
2608
2597
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
function extractSection(description, marker) {
|
|
2613
|
-
const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
|
|
2614
|
-
const lines = description.split("\n");
|
|
2615
|
-
let inSection = false;
|
|
2616
|
-
const collected = [];
|
|
2617
|
-
for (const line of lines) {
|
|
2618
|
-
const trimmed = line.trim();
|
|
2619
|
-
const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
|
|
2620
|
-
if (!inSection && startsWithMarker) {
|
|
2621
|
-
inSection = true;
|
|
2622
|
-
collected.push(trimmed.slice(marker.length).trim());
|
|
2623
|
-
continue;
|
|
2624
|
-
}
|
|
2625
|
-
if (inSection) {
|
|
2626
|
-
const hitNextMarker = markers.some(
|
|
2627
|
-
(m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
|
|
2628
|
-
);
|
|
2629
|
-
if (hitNextMarker) break;
|
|
2630
|
-
if (trimmed === "") {
|
|
2631
|
-
if (collected[collected.length - 1] === "") break;
|
|
2632
|
-
collected.push("");
|
|
2633
|
-
continue;
|
|
2634
|
-
}
|
|
2635
|
-
collected.push(trimmed);
|
|
2636
|
-
}
|
|
2598
|
+
if (!scripts["lint:css"]) {
|
|
2599
|
+
scripts["lint:css"] = "stylelint 'src/**/*.css'";
|
|
2600
|
+
changed = true;
|
|
2637
2601
|
}
|
|
2638
|
-
if (
|
|
2639
|
-
|
|
2640
|
-
|
|
2602
|
+
if (changed) {
|
|
2603
|
+
await backupFile(pkgPath, projectRoot);
|
|
2604
|
+
pkg.scripts = scripts;
|
|
2605
|
+
await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2606
|
+
logger.debug("Patched package.json scripts with lint / lint:css");
|
|
2607
|
+
}
|
|
2608
|
+
return changed;
|
|
2641
2609
|
}
|
|
2642
2610
|
|
|
2643
2611
|
// src/core/init-detect.ts
|
|
2644
|
-
import * as
|
|
2645
|
-
import * as
|
|
2612
|
+
import * as fs12 from "fs/promises";
|
|
2613
|
+
import * as path14 from "path";
|
|
2646
2614
|
var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
2647
2615
|
".git",
|
|
2648
2616
|
".gitignore",
|
|
@@ -2662,7 +2630,7 @@ var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
|
2662
2630
|
"LICENSE.txt"
|
|
2663
2631
|
]);
|
|
2664
2632
|
async function detectProjectState(cwd) {
|
|
2665
|
-
const absCwd =
|
|
2633
|
+
const absCwd = path14.resolve(cwd);
|
|
2666
2634
|
const teamixDir = getTeamixDir(absCwd);
|
|
2667
2635
|
const hasTeamixDir = await fileExists(teamixDir);
|
|
2668
2636
|
if (hasTeamixDir) {
|
|
@@ -2670,13 +2638,13 @@ async function detectProjectState(cwd) {
|
|
|
2670
2638
|
state: "teamix-evo-installed",
|
|
2671
2639
|
cwd: absCwd,
|
|
2672
2640
|
hasTeamixDir: true,
|
|
2673
|
-
hasPackageJson: await fileExists(
|
|
2641
|
+
hasPackageJson: await fileExists(path14.join(absCwd, "package.json")),
|
|
2674
2642
|
significantEntries: []
|
|
2675
2643
|
};
|
|
2676
2644
|
}
|
|
2677
2645
|
let entries;
|
|
2678
2646
|
try {
|
|
2679
|
-
entries = await
|
|
2647
|
+
entries = await fs12.readdir(absCwd);
|
|
2680
2648
|
} catch (err) {
|
|
2681
2649
|
if (err.code === "ENOENT") {
|
|
2682
2650
|
return {
|
|
@@ -2709,9 +2677,151 @@ async function detectProjectState(cwd) {
|
|
|
2709
2677
|
};
|
|
2710
2678
|
}
|
|
2711
2679
|
|
|
2680
|
+
// src/core/project-state.ts
|
|
2681
|
+
import * as fs13 from "fs/promises";
|
|
2682
|
+
import * as path15 from "path";
|
|
2683
|
+
var IGNORED_TOP_LEVEL2 = /* @__PURE__ */ new Set([
|
|
2684
|
+
".git",
|
|
2685
|
+
".gitignore",
|
|
2686
|
+
".gitattributes",
|
|
2687
|
+
".gitkeep",
|
|
2688
|
+
".DS_Store",
|
|
2689
|
+
"Thumbs.db",
|
|
2690
|
+
".idea",
|
|
2691
|
+
".vscode",
|
|
2692
|
+
".qoder",
|
|
2693
|
+
".claude",
|
|
2694
|
+
"README.md",
|
|
2695
|
+
"README",
|
|
2696
|
+
"README.txt",
|
|
2697
|
+
"LICENSE",
|
|
2698
|
+
"LICENSE.md",
|
|
2699
|
+
"LICENSE.txt"
|
|
2700
|
+
]);
|
|
2701
|
+
var SHADCN_TRAIT_KEYS = ["$schema", "style", "rsc", "tsx", "aliases"];
|
|
2702
|
+
async function isShadcnComponentsJson(filePath) {
|
|
2703
|
+
const content = await readFileOrNull(filePath);
|
|
2704
|
+
if (!content) return false;
|
|
2705
|
+
try {
|
|
2706
|
+
const json = JSON.parse(content);
|
|
2707
|
+
return SHADCN_TRAIT_KEYS.some((key) => key in json);
|
|
2708
|
+
} catch {
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
async function detectProjectState2(cwd) {
|
|
2713
|
+
const absCwd = path15.resolve(cwd);
|
|
2714
|
+
const teamixDir = getTeamixDir(absCwd);
|
|
2715
|
+
const hasTeamixDir = await fileExists(teamixDir);
|
|
2716
|
+
if (hasTeamixDir) {
|
|
2717
|
+
return {
|
|
2718
|
+
state: "teamix-evo",
|
|
2719
|
+
cwd: absCwd,
|
|
2720
|
+
hasTeamixDir: true,
|
|
2721
|
+
hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
|
|
2722
|
+
hasComponentsJson: false,
|
|
2723
|
+
significantEntries: []
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
const componentsJsonPath = path15.join(absCwd, "components.json");
|
|
2727
|
+
const hasPackageJson = await fileExists(path15.join(absCwd, "package.json"));
|
|
2728
|
+
const hasComponentsJson = await isShadcnComponentsJson(componentsJsonPath);
|
|
2729
|
+
if (hasPackageJson && hasComponentsJson) {
|
|
2730
|
+
return {
|
|
2731
|
+
state: "shadcn",
|
|
2732
|
+
cwd: absCwd,
|
|
2733
|
+
hasTeamixDir: false,
|
|
2734
|
+
hasPackageJson: true,
|
|
2735
|
+
hasComponentsJson: true,
|
|
2736
|
+
significantEntries: []
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
let entries;
|
|
2740
|
+
try {
|
|
2741
|
+
entries = await fs13.readdir(absCwd);
|
|
2742
|
+
} catch (err) {
|
|
2743
|
+
if (err.code === "ENOENT") {
|
|
2744
|
+
return {
|
|
2745
|
+
state: "empty",
|
|
2746
|
+
cwd: absCwd,
|
|
2747
|
+
hasTeamixDir: false,
|
|
2748
|
+
hasPackageJson: false,
|
|
2749
|
+
hasComponentsJson: false,
|
|
2750
|
+
significantEntries: []
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
throw err;
|
|
2754
|
+
}
|
|
2755
|
+
const significant = entries.filter((e) => !IGNORED_TOP_LEVEL2.has(e));
|
|
2756
|
+
if (significant.length === 0) {
|
|
2757
|
+
return {
|
|
2758
|
+
state: "empty",
|
|
2759
|
+
cwd: absCwd,
|
|
2760
|
+
hasTeamixDir: false,
|
|
2761
|
+
hasPackageJson: false,
|
|
2762
|
+
hasComponentsJson: false,
|
|
2763
|
+
significantEntries: []
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
return {
|
|
2767
|
+
state: "other",
|
|
2768
|
+
cwd: absCwd,
|
|
2769
|
+
hasTeamixDir: false,
|
|
2770
|
+
hasPackageJson,
|
|
2771
|
+
hasComponentsJson,
|
|
2772
|
+
significantEntries: significant.slice(0, 20).sort()
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// src/core/command-guard.ts
|
|
2777
|
+
var COMMAND_LABELS = {
|
|
2778
|
+
init: "\u521D\u59CB\u5316 Teamix Evo \u5957\u4EF6",
|
|
2779
|
+
migrate: "shadcn \u9879\u76EE\u8FC1\u79FB\u4E3A Teamix Evo \u5957\u4EF6",
|
|
2780
|
+
update: "Teamix Evo \u5957\u4EF6\u66F4\u65B0"
|
|
2781
|
+
};
|
|
2782
|
+
var STATE_LABELS = {
|
|
2783
|
+
empty: "\u7A7A\u76EE\u5F55",
|
|
2784
|
+
shadcn: "shadcn \u5DE5\u7A0B",
|
|
2785
|
+
"teamix-evo": "Teamix Evo \u5DE5\u7A0B",
|
|
2786
|
+
other: "\u666E\u901A npm \u5DE5\u7A0B"
|
|
2787
|
+
};
|
|
2788
|
+
var VALID_STATES = {
|
|
2789
|
+
init: "empty",
|
|
2790
|
+
migrate: "shadcn",
|
|
2791
|
+
update: "teamix-evo"
|
|
2792
|
+
};
|
|
2793
|
+
var STATE_TO_COMMAND = {
|
|
2794
|
+
empty: "init",
|
|
2795
|
+
shadcn: "migrate",
|
|
2796
|
+
"teamix-evo": "update",
|
|
2797
|
+
other: null
|
|
2798
|
+
};
|
|
2799
|
+
function assertCommandPrecondition(command, state) {
|
|
2800
|
+
const expected = VALID_STATES[command];
|
|
2801
|
+
if (state === expected) return;
|
|
2802
|
+
const stateLabel = STATE_LABELS[state];
|
|
2803
|
+
const commandLabel = COMMAND_LABELS[command];
|
|
2804
|
+
logger.error(
|
|
2805
|
+
`\u5F53\u524D\u9879\u76EE\u72B6\u6001\u4E3A\u300C${stateLabel}\u300D\uFF0C\u65E0\u6CD5\u6267\u884C\u300C${commandLabel}\u300D\u3002`
|
|
2806
|
+
);
|
|
2807
|
+
const suggestion = STATE_TO_COMMAND[state];
|
|
2808
|
+
if (suggestion) {
|
|
2809
|
+
const suggestionLabel = COMMAND_LABELS[suggestion];
|
|
2810
|
+
logger.info(`\u5EFA\u8BAE\u4F7F\u7528\uFF1Ateamix-evo ${suggestion}\uFF08${suggestionLabel}\uFF09`);
|
|
2811
|
+
} else {
|
|
2812
|
+
logger.info(
|
|
2813
|
+
"\u5F53\u524D\u9879\u76EE\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u76F4\u63A5\u63A5\u5165\u3002"
|
|
2814
|
+
);
|
|
2815
|
+
logger.info(
|
|
2816
|
+
"\u5EFA\u8BAE\uFF1A\u5148\u7528 teamix-evo init \u521D\u59CB\u5316 Teamix Evo \u5957\u4EF6\uFF08\u7A7A\u76EE\u5F55\uFF09\uFF0C\u7136\u540E\u5BF9\u65E7\u9879\u76EE\u8FDB\u884C\u91CD\u6784\u3002"
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
process.exitCode = 1;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2712
2822
|
// src/core/init-conflicts.ts
|
|
2713
2823
|
import * as crypto from "crypto";
|
|
2714
|
-
import * as
|
|
2824
|
+
import * as fs14 from "fs/promises";
|
|
2715
2825
|
import * as path16 from "path";
|
|
2716
2826
|
var TAILWIND_CONFIG_CANDIDATES = [
|
|
2717
2827
|
"tailwind.config.ts",
|
|
@@ -2756,7 +2866,7 @@ var STYLELINT_CONFIG_CANDIDATES = [
|
|
|
2756
2866
|
];
|
|
2757
2867
|
async function isDir(target) {
|
|
2758
2868
|
try {
|
|
2759
|
-
const stat3 = await
|
|
2869
|
+
const stat3 = await fs14.stat(target);
|
|
2760
2870
|
return stat3.isDirectory();
|
|
2761
2871
|
} catch {
|
|
2762
2872
|
return false;
|
|
@@ -2764,7 +2874,7 @@ async function isDir(target) {
|
|
|
2764
2874
|
}
|
|
2765
2875
|
async function dirHasContent(target) {
|
|
2766
2876
|
try {
|
|
2767
|
-
const entries = await
|
|
2877
|
+
const entries = await fs14.readdir(target);
|
|
2768
2878
|
return entries.length > 0;
|
|
2769
2879
|
} catch {
|
|
2770
2880
|
return false;
|
|
@@ -2905,7 +3015,7 @@ async function detectShadcnSource(cwd) {
|
|
|
2905
3015
|
try {
|
|
2906
3016
|
const uiDir = path16.join(cwd, "src/components/ui");
|
|
2907
3017
|
if (await isDir(uiDir)) {
|
|
2908
|
-
const entries = await
|
|
3018
|
+
const entries = await fs14.readdir(uiDir);
|
|
2909
3019
|
componentCount = entries.filter(
|
|
2910
3020
|
(e) => e.endsWith(".tsx") || e.endsWith(".ts")
|
|
2911
3021
|
).length;
|
|
@@ -2981,19 +3091,97 @@ async function detectStylelintConfig(cwd) {
|
|
|
2981
3091
|
};
|
|
2982
3092
|
}
|
|
2983
3093
|
|
|
2984
|
-
// src/core/
|
|
2985
|
-
import * as fs13 from "fs/promises";
|
|
3094
|
+
// src/core/meta-installer.ts
|
|
2986
3095
|
import * as path17 from "path";
|
|
3096
|
+
import * as fs15 from "fs/promises";
|
|
3097
|
+
import { createRequire as createRequire5 } from "module";
|
|
3098
|
+
import {
|
|
3099
|
+
loadUiPackageManifest as loadUiPackageManifest3,
|
|
3100
|
+
loadVariantUiPackageManifest as loadVariantUiPackageManifest2
|
|
3101
|
+
} from "@teamix-evo/registry";
|
|
3102
|
+
var require6 = createRequire5(import.meta.url);
|
|
3103
|
+
var META_DIR = "meta";
|
|
3104
|
+
function resolvePackageRoot4(packageName) {
|
|
3105
|
+
const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
|
|
3106
|
+
return path17.dirname(pkgJsonPath);
|
|
3107
|
+
}
|
|
3108
|
+
function getMetaDir(projectRoot, category) {
|
|
3109
|
+
const base = path17.join(getTeamixDir(projectRoot), META_DIR);
|
|
3110
|
+
return category ? path17.join(base, category) : base;
|
|
3111
|
+
}
|
|
3112
|
+
async function landUiMeta(projectRoot) {
|
|
3113
|
+
const packageRoot = resolvePackageRoot4("@teamix-evo/ui");
|
|
3114
|
+
const manifest = await loadUiPackageManifest3(packageRoot);
|
|
3115
|
+
return landManifestMeta({
|
|
3116
|
+
projectRoot,
|
|
3117
|
+
packageRoot,
|
|
3118
|
+
manifest,
|
|
3119
|
+
category: "ui"
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3122
|
+
async function landBizUiMeta(projectRoot, variant) {
|
|
3123
|
+
const packageRoot = resolvePackageRoot4("@teamix-evo/biz-ui");
|
|
3124
|
+
const variantRoot = path17.join(packageRoot, "variants", variant);
|
|
3125
|
+
const manifest = await loadVariantUiPackageManifest2(variantRoot);
|
|
3126
|
+
return landManifestMeta({
|
|
3127
|
+
projectRoot,
|
|
3128
|
+
packageRoot: variantRoot,
|
|
3129
|
+
manifest,
|
|
3130
|
+
category: "biz-ui"
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
async function landManifestMeta(opts) {
|
|
3134
|
+
const { projectRoot, packageRoot, manifest, category } = opts;
|
|
3135
|
+
await ensureTeamixDir(projectRoot);
|
|
3136
|
+
const metaDir = getMetaDir(projectRoot, category);
|
|
3137
|
+
await ensureDir(metaDir);
|
|
3138
|
+
const manifestDest = path17.join(metaDir, "manifest.json");
|
|
3139
|
+
const manifestSrc = path17.join(packageRoot, "manifest.json");
|
|
3140
|
+
await fs15.copyFile(manifestSrc, manifestDest);
|
|
3141
|
+
logger.debug(`meta: copied manifest.json \u2192 ${manifestDest}`);
|
|
3142
|
+
let written = 0;
|
|
3143
|
+
let total = 0;
|
|
3144
|
+
for (const entry of manifest.entries) {
|
|
3145
|
+
if (!entry.meta) continue;
|
|
3146
|
+
total++;
|
|
3147
|
+
const srcPath = path17.join(packageRoot, entry.meta);
|
|
3148
|
+
const destPath = path17.join(metaDir, `${entry.id}.md`);
|
|
3149
|
+
try {
|
|
3150
|
+
await fs15.copyFile(srcPath, destPath);
|
|
3151
|
+
written++;
|
|
3152
|
+
logger.debug(`meta: ${entry.id} \u2192 ${destPath}`);
|
|
3153
|
+
} catch (err) {
|
|
3154
|
+
if (err.code === "ENOENT") {
|
|
3155
|
+
logger.warn(`meta: ${entry.id} \u2014 source not found: ${srcPath}`);
|
|
3156
|
+
} else {
|
|
3157
|
+
throw err;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
logger.info(
|
|
3162
|
+
` meta/${category}: ${written}/${total} meta files, manifest.json`
|
|
3163
|
+
);
|
|
3164
|
+
return {
|
|
3165
|
+
category,
|
|
3166
|
+
manifestWritten: true,
|
|
3167
|
+
metaFilesWritten: written,
|
|
3168
|
+
metaFilesTotal: total
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// src/core/deps-install.ts
|
|
3173
|
+
import * as fs16 from "fs/promises";
|
|
3174
|
+
import * as path18 from "path";
|
|
2987
3175
|
import { exec } from "child_process";
|
|
2988
3176
|
import { promisify } from "util";
|
|
2989
3177
|
var execAsync = promisify(exec);
|
|
2990
3178
|
async function detectPackageManager(projectRoot) {
|
|
2991
|
-
if (await fileExists(
|
|
2992
|
-
if (await fileExists(
|
|
3179
|
+
if (await fileExists(path18.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3180
|
+
if (await fileExists(path18.join(projectRoot, "pnpm-workspace.yaml")))
|
|
2993
3181
|
return "pnpm";
|
|
2994
|
-
if (await fileExists(
|
|
2995
|
-
if (await fileExists(
|
|
2996
|
-
if (await fileExists(
|
|
3182
|
+
if (await fileExists(path18.join(projectRoot, "bun.lockb"))) return "bun";
|
|
3183
|
+
if (await fileExists(path18.join(projectRoot, "bun.lock"))) return "bun";
|
|
3184
|
+
if (await fileExists(path18.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
2997
3185
|
return "npm";
|
|
2998
3186
|
}
|
|
2999
3187
|
function getInstallCommand(pm) {
|
|
@@ -3010,7 +3198,7 @@ function getInstallCommand(pm) {
|
|
|
3010
3198
|
}
|
|
3011
3199
|
async function installProjectDeps(options) {
|
|
3012
3200
|
const { projectRoot, npmDependencies, skipInstall = false } = options;
|
|
3013
|
-
const pkgPath =
|
|
3201
|
+
const pkgPath = path18.join(projectRoot, "package.json");
|
|
3014
3202
|
const raw = await readFileOrNull(pkgPath);
|
|
3015
3203
|
if (!raw) {
|
|
3016
3204
|
throw new Error(
|
|
@@ -3039,7 +3227,7 @@ async function installProjectDeps(options) {
|
|
|
3039
3227
|
pkg.dependencies = Object.fromEntries(
|
|
3040
3228
|
Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
3041
3229
|
);
|
|
3042
|
-
await
|
|
3230
|
+
await fs16.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3043
3231
|
logger.info(
|
|
3044
3232
|
` patched package.json: +${Object.keys(added).length} dependencies`
|
|
3045
3233
|
);
|
|
@@ -3101,19 +3289,19 @@ ${stepLines}
|
|
|
3101
3289
|
}
|
|
3102
3290
|
|
|
3103
3291
|
// src/core/file-changes.ts
|
|
3104
|
-
import * as
|
|
3105
|
-
import * as
|
|
3292
|
+
import * as fs17 from "fs/promises";
|
|
3293
|
+
import * as path19 from "path";
|
|
3106
3294
|
function toRelativePosix(p, projectRoot) {
|
|
3107
3295
|
let rel2 = p;
|
|
3108
|
-
if (
|
|
3109
|
-
rel2 =
|
|
3296
|
+
if (path19.isAbsolute(p)) {
|
|
3297
|
+
rel2 = path19.relative(projectRoot, p);
|
|
3110
3298
|
}
|
|
3111
|
-
return rel2.split(
|
|
3299
|
+
return rel2.split(path19.sep).join("/");
|
|
3112
3300
|
}
|
|
3113
3301
|
|
|
3114
3302
|
// src/core/project-init.ts
|
|
3115
3303
|
import * as fsNode from "fs/promises";
|
|
3116
|
-
import * as
|
|
3304
|
+
import * as path20 from "path";
|
|
3117
3305
|
var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
3118
3306
|
"tokens",
|
|
3119
3307
|
"skills",
|
|
@@ -3124,7 +3312,6 @@ var GITIGNORE_MARKER_END = "# <<< teamix-evo:managed <<<";
|
|
|
3124
3312
|
var GITIGNORE_RULES = [
|
|
3125
3313
|
"# Runtime artifacts (regenerable / archives)",
|
|
3126
3314
|
".teamix-evo/.snapshots/",
|
|
3127
|
-
".teamix-evo/logs/",
|
|
3128
3315
|
".teamix-evo/.backups/",
|
|
3129
3316
|
".teamix-evo/.upgrade-staging/",
|
|
3130
3317
|
".teamix-evo/.upgrade-hints/"
|
|
@@ -3206,7 +3393,7 @@ async function runProjectInit(options) {
|
|
|
3206
3393
|
detail: `${result.skillCount} skills, ${result.fileCount} files (added: ${result.addedSkillIds.join(", ") || "none"})`,
|
|
3207
3394
|
changes: result.addedSkillIds.map((id) => ({
|
|
3208
3395
|
kind: "created",
|
|
3209
|
-
path:
|
|
3396
|
+
path: `.${ides[0] ?? "qoder"}/skills/${id}/SKILL.md`,
|
|
3210
3397
|
step: "skills",
|
|
3211
3398
|
detail: "skill installed"
|
|
3212
3399
|
}))
|
|
@@ -3363,6 +3550,31 @@ async function runProjectInit(options) {
|
|
|
3363
3550
|
recordFailure("biz-ui-add", err);
|
|
3364
3551
|
}
|
|
3365
3552
|
}
|
|
3553
|
+
if (dryRun) {
|
|
3554
|
+
record({
|
|
3555
|
+
name: "meta-landing",
|
|
3556
|
+
status: "planned",
|
|
3557
|
+
detail: `landUiMeta() + landBizUiMeta(${variant})`
|
|
3558
|
+
});
|
|
3559
|
+
} else if (aborted) {
|
|
3560
|
+
record({
|
|
3561
|
+
name: "meta-landing",
|
|
3562
|
+
status: "skip",
|
|
3563
|
+
detail: "aborted: earlier critical step failed"
|
|
3564
|
+
});
|
|
3565
|
+
} else {
|
|
3566
|
+
try {
|
|
3567
|
+
const uiResult = await landUiMeta(projectRoot);
|
|
3568
|
+
const bizResult = await landBizUiMeta(projectRoot, variant);
|
|
3569
|
+
record({
|
|
3570
|
+
name: "meta-landing",
|
|
3571
|
+
status: "ok",
|
|
3572
|
+
detail: `ui: ${uiResult.metaFilesWritten}/${uiResult.metaFilesTotal}, biz-ui: ${bizResult.metaFilesWritten}/${bizResult.metaFilesTotal}`
|
|
3573
|
+
});
|
|
3574
|
+
} catch (err) {
|
|
3575
|
+
recordFailure("meta-landing", err);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3366
3578
|
if (!dryRun && !aborted && Object.keys(collectedNpmDeps).length > 0) {
|
|
3367
3579
|
try {
|
|
3368
3580
|
await installProjectDeps({
|
|
@@ -3430,7 +3642,7 @@ async function runProjectInit(options) {
|
|
|
3430
3642
|
detail: "projectRoot does not exist"
|
|
3431
3643
|
});
|
|
3432
3644
|
} else {
|
|
3433
|
-
const giPath =
|
|
3645
|
+
const giPath = path20.join(projectRoot, ".gitignore");
|
|
3434
3646
|
let giContent = "";
|
|
3435
3647
|
try {
|
|
3436
3648
|
giContent = await fsNode.readFile(giPath, "utf-8");
|
|
@@ -3486,15 +3698,16 @@ async function runProjectInit(options) {
|
|
|
3486
3698
|
resumeCommand: "teamix-evo init"
|
|
3487
3699
|
};
|
|
3488
3700
|
}
|
|
3489
|
-
|
|
3701
|
+
const hasFailures = steps.some((s) => s.status === "fail" || s.status === "skip");
|
|
3702
|
+
if (!dryRun && hasFailures) {
|
|
3490
3703
|
try {
|
|
3491
3704
|
const checklistContent = renderInitChecklist({ variant, status, steps });
|
|
3492
|
-
const checklistPath =
|
|
3705
|
+
const checklistPath = path20.join(
|
|
3493
3706
|
projectRoot,
|
|
3494
3707
|
".teamix-evo",
|
|
3495
3708
|
"init-checklist.md"
|
|
3496
3709
|
);
|
|
3497
|
-
await fsNode.mkdir(
|
|
3710
|
+
await fsNode.mkdir(path20.dirname(checklistPath), { recursive: true });
|
|
3498
3711
|
await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
|
|
3499
3712
|
logger.info(" wrote .teamix-evo/init-checklist.md");
|
|
3500
3713
|
} catch {
|
|
@@ -3534,8 +3747,8 @@ function deriveLintChanges(result) {
|
|
|
3534
3747
|
}
|
|
3535
3748
|
|
|
3536
3749
|
// src/core/installer.ts
|
|
3537
|
-
import * as
|
|
3538
|
-
import * as
|
|
3750
|
+
import * as path21 from "path";
|
|
3751
|
+
import * as fs18 from "fs/promises";
|
|
3539
3752
|
async function installResources(options) {
|
|
3540
3753
|
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
3541
3754
|
const installedResources = [];
|
|
@@ -3572,13 +3785,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
|
|
|
3572
3785
|
variantDir,
|
|
3573
3786
|
packageRoot
|
|
3574
3787
|
);
|
|
3575
|
-
const targetPath =
|
|
3788
|
+
const targetPath = path21.join(projectRoot, resource.target);
|
|
3576
3789
|
let content;
|
|
3577
3790
|
if (resource.template) {
|
|
3578
3791
|
const templateContent = await loadTemplateFile(sourcePath);
|
|
3579
3792
|
content = renderTemplate(templateContent, data);
|
|
3580
3793
|
} else {
|
|
3581
|
-
content = await
|
|
3794
|
+
content = await fs18.readFile(sourcePath, "utf-8");
|
|
3582
3795
|
}
|
|
3583
3796
|
await writeFileSafe(targetPath, content);
|
|
3584
3797
|
const hash = computeHash(content);
|
|
@@ -3596,13 +3809,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
3596
3809
|
variantDir,
|
|
3597
3810
|
packageRoot
|
|
3598
3811
|
);
|
|
3599
|
-
const targetDir =
|
|
3812
|
+
const targetDir = path21.join(projectRoot, resource.target);
|
|
3600
3813
|
const results = [];
|
|
3601
3814
|
await ensureDir(targetDir);
|
|
3602
3815
|
const entries = await walkDir(sourcePath);
|
|
3603
3816
|
for (const entry of entries) {
|
|
3604
|
-
const relPath =
|
|
3605
|
-
let targetFile =
|
|
3817
|
+
const relPath = path21.relative(sourcePath, entry);
|
|
3818
|
+
let targetFile = path21.join(targetDir, relPath);
|
|
3606
3819
|
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
3607
3820
|
targetFile = targetFile.slice(0, -4);
|
|
3608
3821
|
}
|
|
@@ -3611,11 +3824,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
3611
3824
|
const templateContent = await loadTemplateFile(entry);
|
|
3612
3825
|
content = renderTemplate(templateContent, data);
|
|
3613
3826
|
} else {
|
|
3614
|
-
content = await
|
|
3827
|
+
content = await fs18.readFile(entry, "utf-8");
|
|
3615
3828
|
}
|
|
3616
3829
|
await writeFileSafe(targetFile, content);
|
|
3617
3830
|
const hash = computeHash(content);
|
|
3618
|
-
const targetRel =
|
|
3831
|
+
const targetRel = path21.relative(projectRoot, targetFile);
|
|
3619
3832
|
results.push({
|
|
3620
3833
|
id: `${resource.id}:${relPath}`,
|
|
3621
3834
|
target: targetRel,
|
|
@@ -3628,25 +3841,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
3628
3841
|
}
|
|
3629
3842
|
|
|
3630
3843
|
// src/core/registry-client.ts
|
|
3631
|
-
import * as
|
|
3632
|
-
import * as
|
|
3633
|
-
import { createRequire as
|
|
3844
|
+
import * as path22 from "path";
|
|
3845
|
+
import * as fs19 from "fs/promises";
|
|
3846
|
+
import { createRequire as createRequire6 } from "module";
|
|
3634
3847
|
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
3635
|
-
var
|
|
3636
|
-
function
|
|
3637
|
-
const pkgJsonPath =
|
|
3638
|
-
return
|
|
3848
|
+
var require7 = createRequire6(import.meta.url);
|
|
3849
|
+
function resolvePackageRoot5(packageName) {
|
|
3850
|
+
const pkgJsonPath = require7.resolve(`${packageName}/package.json`);
|
|
3851
|
+
return path22.dirname(pkgJsonPath);
|
|
3639
3852
|
}
|
|
3640
3853
|
async function loadVariantData(packageName, variant) {
|
|
3641
|
-
const packageRoot =
|
|
3642
|
-
const variantDir =
|
|
3854
|
+
const packageRoot = resolvePackageRoot5(packageName);
|
|
3855
|
+
const variantDir = path22.join(packageRoot, "library", variant);
|
|
3643
3856
|
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
3644
3857
|
logger.debug(`Package root: ${packageRoot}`);
|
|
3645
3858
|
const manifest = await loadVariantManifest(variantDir);
|
|
3646
3859
|
let data = {};
|
|
3647
|
-
const dataPath =
|
|
3860
|
+
const dataPath = path22.join(variantDir, "_data.json");
|
|
3648
3861
|
try {
|
|
3649
|
-
const raw = await
|
|
3862
|
+
const raw = await fs19.readFile(dataPath, "utf-8");
|
|
3650
3863
|
data = JSON.parse(raw);
|
|
3651
3864
|
} catch (err) {
|
|
3652
3865
|
if (err.code !== "ENOENT") {
|
|
@@ -3659,8 +3872,10 @@ async function loadVariantData(packageName, variant) {
|
|
|
3659
3872
|
export {
|
|
3660
3873
|
DEFAULT_UI_ALIASES,
|
|
3661
3874
|
DEFAULT_UI_ICON_LIBRARY,
|
|
3875
|
+
assertCommandPrecondition,
|
|
3662
3876
|
detectConflicts,
|
|
3663
|
-
detectProjectState,
|
|
3877
|
+
detectProjectState2 as detectProjectState,
|
|
3878
|
+
detectProjectState as detectProjectStateLegacy,
|
|
3664
3879
|
ensureTeamixDir,
|
|
3665
3880
|
extractDescriptionParts,
|
|
3666
3881
|
getTeamixDir,
|
|
@@ -3669,14 +3884,13 @@ export {
|
|
|
3669
3884
|
installUiEntries,
|
|
3670
3885
|
listBizUiEntries,
|
|
3671
3886
|
listBizUiVariants,
|
|
3672
|
-
listTemplatesEntries,
|
|
3673
|
-
listTemplatesVariants,
|
|
3674
3887
|
listTokenVariants,
|
|
3675
3888
|
loadSkillsData,
|
|
3676
3889
|
loadUiData,
|
|
3677
3890
|
loadVariantData,
|
|
3678
3891
|
readInstalledManifest,
|
|
3679
3892
|
readProjectConfig,
|
|
3893
|
+
reinstallSkillsToIdes,
|
|
3680
3894
|
removeSkillFiles,
|
|
3681
3895
|
removeUiFiles,
|
|
3682
3896
|
runBizUiAdd,
|
|
@@ -3686,12 +3900,10 @@ export {
|
|
|
3686
3900
|
runSkillsAdd,
|
|
3687
3901
|
runSkillsInit,
|
|
3688
3902
|
runSkillsUpdate,
|
|
3689
|
-
runTemplatesAdd,
|
|
3690
3903
|
runTokensInit,
|
|
3691
3904
|
runUiAdd,
|
|
3692
3905
|
runUiInit,
|
|
3693
3906
|
runUiList,
|
|
3694
|
-
syncSkillsToIdes,
|
|
3695
3907
|
updateSkills,
|
|
3696
3908
|
writeInstalledManifest,
|
|
3697
3909
|
writeProjectConfig
|