moluoxixi-ai-rules 0.2.0 → 0.2.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.
@@ -412,6 +412,54 @@ function rollbackManagedEntries(entries) {
412
412
  }
413
413
  return rollbackErrors;
414
414
  }
415
+ function managedChildNames(currentRoot, nextRoot) {
416
+ const names = new Map();
417
+ for (const root of [currentRoot, nextRoot]) {
418
+ if (!fs.existsSync(root))
419
+ continue;
420
+ requirePlainDirectoryOrMissing(root, 'Managed vendor skills root');
421
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
422
+ if (!entry.isDirectory() && !entry.isFile())
423
+ throw new Error(`Managed vendor skill 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(`Managed vendor skill 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 commitManagedChildren(spec, committedEntries, createdRoots) {
434
+ requirePlainDirectoryOrMissing(spec.current, 'Managed vendor skills root');
435
+ requirePlainDirectoryOrMissing(spec.next, 'Staged vendor skills root');
436
+ const childNames = managedChildNames(spec.current, spec.next);
437
+ if (!fs.existsSync(spec.current)) {
438
+ if (!fs.existsSync(spec.next))
439
+ return;
440
+ fs.mkdirSync(spec.current, { recursive: true });
441
+ createdRoots.push(spec.current);
442
+ }
443
+ for (const name of childNames) {
444
+ const entry = {
445
+ current: path.join(spec.current, name),
446
+ backup: path.join(spec.backup, name),
447
+ movedCurrent: false,
448
+ installedNext: false,
449
+ };
450
+ committedEntries.push(entry);
451
+ if (fs.existsSync(entry.current)) {
452
+ fs.mkdirSync(path.dirname(entry.backup), { recursive: true });
453
+ fs.renameSync(entry.current, entry.backup);
454
+ entry.movedCurrent = true;
455
+ }
456
+ const nextChild = path.join(spec.next, name);
457
+ if (fs.existsSync(nextChild)) {
458
+ fs.renameSync(nextChild, entry.current);
459
+ entry.installedNext = true;
460
+ }
461
+ }
462
+ }
415
463
  async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role) {
416
464
  const resolvedHome = path.resolve(homeDir);
417
465
  const vendorRoot = path.join(resolvedHome, 'vendor');
@@ -429,6 +477,7 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
429
477
  const nextRoot = path.join(nextWorkspace, 'vendor');
430
478
  const backupRoot = fs.mkdtempSync(path.join(resolvedHome, '.airules-vendor-backup-'));
431
479
  const committedEntries = [];
480
+ const createdRoots = [];
432
481
  try {
433
482
  fs.mkdirSync(nextRoot);
434
483
  fs.cpSync(stagingRoot, nextRoot, { recursive: true, dereference: true });
@@ -436,6 +485,7 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
436
485
  current: path.join(vendorRoot, name),
437
486
  next: path.join(nextRoot, name),
438
487
  backup: path.join(backupRoot, 'vendor', name),
488
+ preserveRoot: name === 'skills',
439
489
  }));
440
490
  if (roleStagingRoot !== undefined) {
441
491
  const nextRole = path.join(nextWorkspace, 'roles', role);
@@ -448,6 +498,10 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
448
498
  });
449
499
  }
450
500
  for (const spec of specs) {
501
+ if (spec.preserveRoot) {
502
+ commitManagedChildren(spec, committedEntries, createdRoots);
503
+ continue;
504
+ }
451
505
  const entry = {
452
506
  current: spec.current,
453
507
  backup: spec.backup,
@@ -469,6 +523,14 @@ async function commitManagedEntries(stagingRoot, roleStagingRoot, homeDir, role)
469
523
  }
470
524
  catch (error) {
471
525
  const rollbackErrors = rollbackManagedEntries(committedEntries);
526
+ for (const root of [...createdRoots].reverse()) {
527
+ try {
528
+ fs.rmdirSync(root);
529
+ }
530
+ catch (rollbackError) {
531
+ rollbackErrors.push(rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError)));
532
+ }
533
+ }
472
534
  removeBestEffort(nextWorkspace);
473
535
  if (rollbackErrors.length > 0) {
474
536
  throw new AggregateError([error, ...rollbackErrors], `Role and vendor staging commit failed and rollback did not restore every managed asset: ${String(error)}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "moluoxixi-ai-rules",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "description": "Remote AI role asset distribution for supported hosts.",
6
6
  "license": "MIT",
7
7
  "repository": "https://github.com/moluoxixi/AIRules",