moluoxixi-ai-rules 0.2.0 → 0.2.2
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/dist/scripts/lib/tool.js
CHANGED
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { HOST_IDS, resolveHostId } from '../../constants/hosts.js';
|
|
5
5
|
import { ensureGlobalSkillLink, ensureInstallRoot, getDefaultInstallPaths, projectHostById, runSkillSetupCommands, } from './install.js';
|
|
6
6
|
import { resolveRoleManifestPath } from './roles.js';
|
|
7
|
-
import { rebuildVendorAssets } from './vendor-staging.js';
|
|
7
|
+
import { cleanupEmptyVendorSkillDirectories, rebuildVendorAssets } from './vendor-staging.js';
|
|
8
8
|
import { ensureVendorRepo, verifyVendorRepoRevision } from './vendor-sync.js';
|
|
9
9
|
import { loadVendorManifest } from './vendors.js';
|
|
10
10
|
import { verifyGlobalAgentSkills, verifyHost } from './verify.js';
|
|
@@ -105,6 +105,7 @@ export async function syncToHosts(options) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
cleanupEmptyVendorSkillDirectories(paths.moluoHome);
|
|
108
109
|
if (failedHosts.length > 0) {
|
|
109
110
|
throw new Error(`Host verification failed: ${failedHosts.join(', ')}`);
|
|
110
111
|
}
|
|
@@ -8,4 +8,5 @@ export interface RebuildVendorAssetsOptions {
|
|
|
8
8
|
role: string;
|
|
9
9
|
manifestPath: string;
|
|
10
10
|
}
|
|
11
|
+
export declare function cleanupEmptyVendorSkillDirectories(homeDir: string): void;
|
|
11
12
|
export declare function rebuildVendorAssets(options: RebuildVendorAssetsOptions): Promise<VendorAssetInventory>;
|
|
@@ -412,6 +412,71 @@ function rollbackManagedEntries(entries) {
|
|
|
412
412
|
}
|
|
413
413
|
return rollbackErrors;
|
|
414
414
|
}
|
|
415
|
+
function managedChildNames(currentRoot, nextRoot, label) {
|
|
416
|
+
const names = new Map();
|
|
417
|
+
for (const root of [currentRoot, nextRoot]) {
|
|
418
|
+
if (!fs.existsSync(root))
|
|
419
|
+
continue;
|
|
420
|
+
requirePlainDirectoryOrMissing(root, label);
|
|
421
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
422
|
+
if (!entry.isDirectory() && !entry.isFile())
|
|
423
|
+
throw new Error(`${label} has unsupported filesystem type: ${path.join(root, entry.name)}`);
|
|
424
|
+
const key = entry.name.toLowerCase();
|
|
425
|
+
const previous = names.get(key);
|
|
426
|
+
if (previous !== undefined && previous !== entry.name)
|
|
427
|
+
throw new Error(`${label} names differ only by case: ${previous}, ${entry.name}`);
|
|
428
|
+
names.set(key, entry.name);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return [...names.values()].sort(compareStable);
|
|
432
|
+
}
|
|
433
|
+
function isPlainDirectory(target) {
|
|
434
|
+
const stats = fs.lstatSync(target, { throwIfNoEntry: false });
|
|
435
|
+
return stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink();
|
|
436
|
+
}
|
|
437
|
+
function commitDirectoryContents(currentRoot, nextRoot, backupRoot, committedEntries, emptiedRoots) {
|
|
438
|
+
const childNames = managedChildNames(currentRoot, nextRoot, 'Managed vendor skill directory');
|
|
439
|
+
for (const name of childNames) {
|
|
440
|
+
const currentChild = path.join(currentRoot, name);
|
|
441
|
+
const nextChild = path.join(nextRoot, name);
|
|
442
|
+
const hasNextChild = fs.existsSync(nextChild);
|
|
443
|
+
if (isPlainDirectory(currentChild) && (!hasNextChild || isPlainDirectory(nextChild))) {
|
|
444
|
+
commitDirectoryContents(currentChild, nextChild, path.join(backupRoot, name), committedEntries, emptiedRoots);
|
|
445
|
+
if (!hasNextChild)
|
|
446
|
+
emptiedRoots.push(currentChild);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const entry = {
|
|
450
|
+
current: currentChild,
|
|
451
|
+
backup: path.join(backupRoot, name),
|
|
452
|
+
movedCurrent: false,
|
|
453
|
+
installedNext: false,
|
|
454
|
+
};
|
|
455
|
+
committedEntries.push(entry);
|
|
456
|
+
if (fs.existsSync(entry.current)) {
|
|
457
|
+
fs.mkdirSync(path.dirname(entry.backup), { recursive: true });
|
|
458
|
+
fs.renameSync(entry.current, entry.backup);
|
|
459
|
+
entry.movedCurrent = true;
|
|
460
|
+
}
|
|
461
|
+
if (fs.existsSync(nextChild)) {
|
|
462
|
+
fs.renameSync(nextChild, entry.current);
|
|
463
|
+
entry.installedNext = true;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function commitManagedChildren(spec, committedEntries, createdRoots, emptiedRoots) {
|
|
468
|
+
requirePlainDirectoryOrMissing(spec.current, 'Managed vendor skills root');
|
|
469
|
+
requirePlainDirectoryOrMissing(spec.next, 'Staged vendor skills root');
|
|
470
|
+
const childNames = managedChildNames(spec.current, spec.next, 'Managed vendor skill');
|
|
471
|
+
if (!fs.existsSync(spec.current)) {
|
|
472
|
+
if (!fs.existsSync(spec.next))
|
|
473
|
+
return;
|
|
474
|
+
fs.mkdirSync(spec.current, { recursive: true });
|
|
475
|
+
createdRoots.push(spec.current);
|
|
476
|
+
}
|
|
477
|
+
if (childNames.length > 0)
|
|
478
|
+
commitDirectoryContents(spec.current, spec.next, spec.backup, committedEntries, emptiedRoots);
|
|
479
|
+
}
|
|
415
480
|
async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role) {
|
|
416
481
|
const resolvedHome = path.resolve(homeDir);
|
|
417
482
|
const vendorRoot = path.join(resolvedHome, 'vendor');
|
|
@@ -429,6 +494,8 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
|
|
|
429
494
|
const nextRoot = path.join(nextWorkspace, 'vendor');
|
|
430
495
|
const backupRoot = fs.mkdtempSync(path.join(resolvedHome, '.airules-vendor-backup-'));
|
|
431
496
|
const committedEntries = [];
|
|
497
|
+
const createdRoots = [];
|
|
498
|
+
const emptiedRoots = [];
|
|
432
499
|
try {
|
|
433
500
|
fs.mkdirSync(nextRoot);
|
|
434
501
|
fs.cpSync(stagingRoot, nextRoot, { recursive: true, dereference: true });
|
|
@@ -436,6 +503,7 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
|
|
|
436
503
|
current: path.join(vendorRoot, name),
|
|
437
504
|
next: path.join(nextRoot, name),
|
|
438
505
|
backup: path.join(backupRoot, 'vendor', name),
|
|
506
|
+
preserveRoot: name === 'skills',
|
|
439
507
|
}));
|
|
440
508
|
if (roleStagingRoot !== undefined) {
|
|
441
509
|
const nextRole = path.join(nextWorkspace, 'roles', role);
|
|
@@ -448,6 +516,10 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
|
|
|
448
516
|
});
|
|
449
517
|
}
|
|
450
518
|
for (const spec of specs) {
|
|
519
|
+
if (spec.preserveRoot) {
|
|
520
|
+
commitManagedChildren(spec, committedEntries, createdRoots, emptiedRoots);
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
451
523
|
const entry = {
|
|
452
524
|
current: spec.current,
|
|
453
525
|
backup: spec.backup,
|
|
@@ -469,6 +541,14 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
|
|
|
469
541
|
}
|
|
470
542
|
catch (error) {
|
|
471
543
|
const rollbackErrors = rollbackManagedEntries(committedEntries);
|
|
544
|
+
for (const root of [...createdRoots].reverse()) {
|
|
545
|
+
try {
|
|
546
|
+
fs.rmdirSync(root);
|
|
547
|
+
}
|
|
548
|
+
catch (rollbackError) {
|
|
549
|
+
rollbackErrors.push(rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError)));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
472
552
|
removeBestEffort(nextWorkspace);
|
|
473
553
|
if (rollbackErrors.length > 0) {
|
|
474
554
|
throw new AggregateError([error, ...rollbackErrors], `Role and vendor staging commit failed and rollback did not restore every managed asset: ${String(error)}`);
|
|
@@ -476,9 +556,36 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
|
|
|
476
556
|
removeBestEffort(backupRoot);
|
|
477
557
|
throw new Error(`Role and vendor staging commit failed; previous managed assets restored: ${String(error)}`, { cause: error });
|
|
478
558
|
}
|
|
559
|
+
for (const root of emptiedRoots)
|
|
560
|
+
removeEmptyDirectoryBestEffort(root);
|
|
479
561
|
removeBestEffort(nextWorkspace);
|
|
480
562
|
removeBestEffort(backupRoot);
|
|
481
563
|
}
|
|
564
|
+
function removeEmptyDirectoryBestEffort(target) {
|
|
565
|
+
try {
|
|
566
|
+
fs.rmdirSync(target);
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
// Host junctions can keep an empty skill directory locked until projection finishes.
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function cleanupEmptyDirectoryTree(root) {
|
|
573
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
574
|
+
if (entry.isDirectory())
|
|
575
|
+
cleanupEmptyDirectoryTree(path.join(root, entry.name));
|
|
576
|
+
}
|
|
577
|
+
removeEmptyDirectoryBestEffort(root);
|
|
578
|
+
}
|
|
579
|
+
export function cleanupEmptyVendorSkillDirectories(homeDir) {
|
|
580
|
+
const skillsRoot = path.resolve(homeDir, 'vendor', 'skills');
|
|
581
|
+
if (!fs.existsSync(skillsRoot))
|
|
582
|
+
return;
|
|
583
|
+
requirePlainDirectoryOrMissing(skillsRoot, 'Managed vendor skills root');
|
|
584
|
+
for (const entry of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
|
|
585
|
+
if (entry.isDirectory())
|
|
586
|
+
cleanupEmptyDirectoryTree(path.join(skillsRoot, entry.name));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
482
589
|
export async function rebuildVendorAssets(options) {
|
|
483
590
|
const role = requireRoleName(options.role);
|
|
484
591
|
const manifest = await loadVendorManifest(options.manifestPath);
|
package/package.json
CHANGED