moluoxixi-ai-rules 0.2.11 → 0.2.13

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.
@@ -0,0 +1,95 @@
1
+ import fs from 'node:fs';
2
+ import { parseDocument, visit } from 'yaml';
3
+ import { requireRoleName } from './role-assets.js';
4
+ const installationFields = ['hosts', 'packages', 'assets', 'distribution', 'entrypoints', 'role_vendor', 'role_vendor_position'];
5
+ const metadataFields = ['schema_version', 'role_id', 'role_version', 'status', 'canonical_root', 'description', 'extends_roles', 'provides', 'installation'];
6
+ function requireRecord(value, location) {
7
+ if (!value || typeof value !== 'object' || Array.isArray(value))
8
+ throw new TypeError(`${location} must be an object`);
9
+ return value;
10
+ }
11
+ function requireFields(record, allowed, location) {
12
+ for (const field of Object.keys(record)) {
13
+ if (!allowed.includes(field))
14
+ throw new Error(`${location} has unsupported field "${field}"`);
15
+ }
16
+ }
17
+ export function requireRoleInheritance(value, location, schemaVersion = 1) {
18
+ if (!Array.isArray(value) || !value.every(role => typeof role === 'string'))
19
+ throw new TypeError(`${location} must be a string array`);
20
+ if (schemaVersion < 2 && value.length > 1)
21
+ throw new Error(`${location} supports at most one parent role (single inheritance); use schema_version: 2 for multiple parents`);
22
+ const seen = new Set();
23
+ for (const role of value) {
24
+ requireRoleName(role);
25
+ const key = role.toLowerCase();
26
+ if (seen.has(key))
27
+ throw new Error(`${location} declares duplicate parent role "${role}"`);
28
+ seen.add(key);
29
+ }
30
+ return [...value];
31
+ }
32
+ export function readRoleContract(contractPath) {
33
+ const location = `Role contract "${contractPath}"`;
34
+ const document = parseDocument(fs.readFileSync(contractPath, 'utf8'), {
35
+ merge: false,
36
+ prettyErrors: true,
37
+ strict: true,
38
+ uniqueKeys: true,
39
+ });
40
+ if (document.errors.length > 0)
41
+ throw new Error(`${location} is invalid: ${document.errors.map(error => error.message).join('; ')}`);
42
+ visit(document, {
43
+ Alias() {
44
+ throw new Error(`${location} must not use YAML aliases`);
45
+ },
46
+ });
47
+ const record = requireRecord(document.toJS({ maxAliasCount: 0 }), `${location} YAML mapping`);
48
+ const schemaVersion = record.schema_version === undefined ? 1 : record.schema_version;
49
+ if (schemaVersion !== 1 && schemaVersion !== 2)
50
+ throw new Error(`${location} schema_version must be 1 or 2`);
51
+ let provides = record;
52
+ let installation = record;
53
+ if (schemaVersion === 2) {
54
+ requireFields(record, metadataFields, location);
55
+ requireRoleName(record.role_id);
56
+ provides = requireRecord(record.provides, `${location} provides`);
57
+ requireFields(provides, ['capabilities'], `${location} provides`);
58
+ installation = requireRecord(record.installation === undefined ? {} : record.installation, `${location} installation`);
59
+ requireFields(installation, installationFields, `${location} installation`);
60
+ if (!Array.isArray(provides.capabilities))
61
+ throw new TypeError(`${location} provides.capabilities must be a list`);
62
+ if (installation.assets !== undefined) {
63
+ const assets = requireRecord(installation.assets, `${location} installation.assets`);
64
+ for (const [name, root] of Object.entries(assets)) {
65
+ if (typeof root !== 'string' || root.length === 0)
66
+ throw new TypeError(`${location} installation.assets.${name} must be a non-empty relative path`);
67
+ if (/^(?:[\\/]|[A-Za-z]:)/u.test(root) || root.replaceAll('\\', '/').split('/').includes('..'))
68
+ throw new Error(`${location} installation.assets.${name} must stay inside the role`);
69
+ }
70
+ }
71
+ }
72
+ else if (record.provides !== undefined || record.installation !== undefined) {
73
+ throw new Error(`${location} provides/installation require schema_version: 2`);
74
+ }
75
+ const roleVendorPosition = schemaVersion === 2 ? installation.role_vendor_position : installation.role_vendor_position ?? installation.roleVendorPosition;
76
+ if (roleVendorPosition !== undefined && roleVendorPosition !== 'before' && roleVendorPosition !== 'after')
77
+ throw new Error(`${location} field "role_vendor_position" must be "before" or "after"`);
78
+ return {
79
+ path: contractPath,
80
+ schemaVersion,
81
+ roleId: record.role_id,
82
+ canonicalRoot: record.canonical_root,
83
+ capabilities: provides.capabilities,
84
+ extendsRoles: requireRoleInheritance(schemaVersion === 2
85
+ ? record.extends_roles === undefined ? [] : record.extends_roles
86
+ : record.extends_roles ?? record.extendsRoles ?? [], `${location} field "extends_roles"`, schemaVersion),
87
+ hosts: installation.hosts,
88
+ packages: installation.packages,
89
+ assets: installation.assets,
90
+ distribution: installation.distribution,
91
+ entrypoints: installation.entrypoints,
92
+ roleVendor: installation.role_vendor ?? installation.roleVendor,
93
+ roleVendorPosition,
94
+ };
95
+ }
@@ -1,15 +1,11 @@
1
+ import type { RoleInheritanceEntry, RolePaths } from './types/roles.js';
2
+ export { requireRoleInheritance } from './role-contract.js';
3
+ export type { RolePaths } from './types/roles.js';
1
4
  export declare const DEFAULT_ROLE = "";
2
5
  export declare const COMMON_ROLE = "";
3
- export interface RolePaths {
4
- role: string;
5
- roleRoot: string;
6
- roleManifest: string;
7
- constantsDir?: string;
8
- constantsFile?: string;
9
- }
10
6
  export declare function requireRolePaths(repoRoot: string, roleValue: unknown): RolePaths;
11
7
  export declare function roleOverlayOrder(repoRoot: string, roleValue?: unknown): Promise<string[]>;
12
- export declare function requireRoleInheritance(value: unknown, location: string): string[];
8
+ export declare function resolveRoleInheritance(repoRoot: string, roleValue?: unknown): Promise<RoleInheritanceEntry[]>;
13
9
  export declare function resolveRoleManifestPath(repoRoot: string, roleValue: unknown, options?: {
14
10
  preferDist?: boolean;
15
11
  }): string;
@@ -1,9 +1,10 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
- import { parseDocument } from 'yaml';
5
4
  import { isPathInside } from './canonical-path.js';
6
5
  import { requireRoleName } from './role-assets.js';
6
+ import { readRoleContract, requireRoleInheritance } from './role-contract.js';
7
+ export { requireRoleInheritance } from './role-contract.js';
7
8
  export const DEFAULT_ROLE = '';
8
9
  export const COMMON_ROLE = '';
9
10
  export function requireRolePaths(repoRoot, roleValue) {
@@ -58,33 +59,44 @@ export function requireRolePaths(repoRoot, roleValue) {
58
59
  return { role, roleRoot, roleManifest, constantsDir, constantsFile };
59
60
  }
60
61
  export async function roleOverlayOrder(repoRoot, roleValue = DEFAULT_ROLE) {
62
+ return (await resolveRoleInheritance(repoRoot, roleValue)).map(entry => entry.role);
63
+ }
64
+ export async function resolveRoleInheritance(repoRoot, roleValue = DEFAULT_ROLE) {
61
65
  if (roleValue === '') {
62
66
  return [];
63
67
  }
64
68
  const role = requireRoleName(roleValue);
65
69
  const orderedRoles = [];
66
70
  const visitingRoles = new Set();
67
- let roleName = role;
68
- while (roleName !== undefined) {
69
- requireRolePaths(repoRoot, roleName);
71
+ const visitedRoles = new Set();
72
+ async function visit(roleName, inheritancePath) {
70
73
  if (visitingRoles.has(roleName)) {
71
- throw new Error(`AIRules role inheritance cycle detected at "${roleName}"`);
74
+ throw new Error(`AIRules role inheritance cycle detected: ${inheritancePath.join(' → ')}`);
72
75
  }
76
+ if (visitedRoles.has(roleName))
77
+ return;
78
+ const { roleManifest: manifestPath } = requireRolePaths(repoRoot, roleName);
73
79
  visitingRoles.add(roleName);
74
- orderedRoles.push(roleName);
75
- const parents = await loadRoleExtendsRoles(repoRoot, roleName);
76
- roleName = parents[0];
77
- }
78
- return orderedRoles.reverse();
79
- }
80
- export function requireRoleInheritance(value, location) {
81
- if (!Array.isArray(value) || !value.every(roleName => typeof roleName === 'string')) {
82
- throw new TypeError(`${location} must be a string array`);
83
- }
84
- if (value.length > 1) {
85
- throw new Error(`${location} supports at most one parent role (single inheritance)`);
80
+ const contract = path.basename(manifestPath).toLowerCase() === 'role.yaml' ? readRoleContract(manifestPath) : undefined;
81
+ if (contract?.roleId !== undefined && contract.roleId !== roleName)
82
+ throw new Error(`Role contract "${manifestPath}" role_id must equal "${roleName}" (${inheritancePath.join(' → ')})`);
83
+ if (contract?.canonicalRoot !== undefined && contract.canonicalRoot !== `roles/${roleName}`)
84
+ throw new Error(`Role contract "${manifestPath}" canonical_root must equal roles/${roleName} (${inheritancePath.join(' → ')})`);
85
+ const parents = contract?.extendsRoles ?? await loadLegacyRoleExtendsRoles(manifestPath);
86
+ for (const parent of parents) {
87
+ try {
88
+ await visit(parent, [...inheritancePath, parent]);
89
+ }
90
+ catch (error) {
91
+ throw new Error(`${String(error)} (inheritance: ${[...inheritancePath, parent].join(' → ')})`, { cause: error });
92
+ }
93
+ }
94
+ visitingRoles.delete(roleName);
95
+ visitedRoles.add(roleName);
96
+ orderedRoles.push({ role: roleName, manifestPath, inheritancePath, contract });
86
97
  }
87
- return value;
98
+ await visit(role, [role]);
99
+ return orderedRoles;
88
100
  }
89
101
  export function resolveRoleManifestPath(repoRoot, roleValue, options = {}) {
90
102
  if (roleValue === '') {
@@ -134,30 +146,11 @@ function resolveManifestCandidate(repoRoot, requestedRoleRoot, manifestPath) {
134
146
  requireInsideRoot(roleRoot, resolvedManifest, 'role manifest', 'role root');
135
147
  return resolvedManifest;
136
148
  }
137
- async function loadRoleExtendsRoles(repoRoot, role) {
138
- const manifestPath = resolveRoleManifestPath(repoRoot, role);
139
- if (path.basename(manifestPath).toLowerCase() === 'role.yaml') {
140
- const document = parseDocument(fs.readFileSync(manifestPath, 'utf8'), {
141
- merge: false,
142
- prettyErrors: true,
143
- strict: true,
144
- uniqueKeys: true,
145
- });
146
- if (document.errors.length > 0) {
147
- throw new Error(`Role contract "${manifestPath}" is invalid: ${document.errors.map(error => error.message).join('; ')}`);
148
- }
149
- const value = document.toJS({ maxAliasCount: 0 });
150
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
151
- throw new TypeError(`Role contract "${manifestPath}" must be a YAML mapping`);
152
- }
153
- const record = value;
154
- const extendsRoles = record.extends_roles ?? record.extendsRoles ?? [];
155
- return requireRoleInheritance(extendsRoles, `roles/${role}/role.yaml field "extends_roles"`);
156
- }
149
+ async function loadLegacyRoleExtendsRoles(manifestPath) {
157
150
  const manifestUrl = pathToFileURL(path.resolve(manifestPath)).href;
158
151
  const module = await import(manifestUrl);
159
152
  const extendsRoles = module.extendsRoles ?? module.default?.extendsRoles ?? [];
160
- return requireRoleInheritance(extendsRoles, `roles/${role}/constants/skills.ts export "extendsRoles"`);
153
+ return requireRoleInheritance(extendsRoles, `Role manifest "${manifestPath}" export "extendsRoles"`);
161
154
  }
162
155
  function requireInsideRoot(root, target, field, rootLabel) {
163
156
  if (!isPathInside(root, target)) {
@@ -53,7 +53,6 @@ async function syncVendorsIfNeeded(paths, skipVendors, manifest) {
53
53
  for (const vendor of Object.values(manifest.vendors)) {
54
54
  ensureVendorRepo(paths.moluoHome, vendor);
55
55
  }
56
- runSkillSetupCommands(manifest, paths.moluoHome);
57
56
  }
58
57
  else {
59
58
  for (const vendor of Object.values(manifest.vendors)) {
@@ -72,13 +71,15 @@ async function syncVendorStaging(paths, skipVendors, manifest) {
72
71
  moluoHome: paths.moluoHome,
73
72
  repoRoot: paths.repoRoot,
74
73
  };
75
- ensureInstallRoot(installPaths);
76
74
  await syncVendorsIfNeeded(paths, skipVendors, manifest);
77
75
  await rebuildVendorAssets({
78
76
  homeDir: paths.moluoHome,
79
77
  role: paths.role,
80
78
  manifestPath: paths.manifestPath,
79
+ manifest,
80
+ beforeCommit: skipVendors ? undefined : () => runSkillSetupCommands(manifest, paths.moluoHome),
81
81
  });
82
+ ensureInstallRoot(installPaths);
82
83
  ensureGlobalSkillLink(installPaths);
83
84
  }
84
85
  export async function syncToHosts(options) {
@@ -8,8 +8,16 @@ export interface CapabilityDefinition {
8
8
  export interface CapabilitySelection {
9
9
  definition: CapabilityDefinition;
10
10
  name: string;
11
+ origin?: string;
11
12
  }
12
13
  export interface ComposeCapabilitiesOptions {
13
14
  roleVendor: VendorRepo;
14
15
  roleVendorPosition?: 'before' | 'after';
16
+ capabilityOrigins?: Record<string, string>;
17
+ roleOrigin?: string;
18
+ }
19
+ export interface CapabilityVendorEntry {
20
+ vendor: VendorRepo;
21
+ origin: string;
22
+ projectionOrigins?: string[][];
15
23
  }
@@ -70,16 +70,8 @@ export interface VendorManifest {
70
70
  packages?: RolePackageConfig[];
71
71
  version: number;
72
72
  vendors: Record<string, Vendor>;
73
- }
74
- export interface RoleContract {
75
- path: string;
76
- value: Record<string, unknown>;
77
- }
78
- export interface RoleContractData {
79
- hosts?: unknown;
80
- packages?: unknown;
81
- capabilities?: unknown;
82
- extendsRoles?: unknown;
83
- roleVendor?: unknown;
84
- roleVendorPosition?: 'before' | 'after';
73
+ origins?: Record<string, {
74
+ vendor: string[];
75
+ links: string[][];
76
+ }>;
85
77
  }
@@ -0,0 +1,13 @@
1
+ import type { SetupCommand } from './manifest.js';
2
+ export interface McpCatalog {
3
+ servers: Record<string, Record<string, unknown>>;
4
+ serverSetup: Record<string, SetupCommand[]>;
5
+ setup: SetupCommand[];
6
+ }
7
+ export interface McpServerDefinition {
8
+ server: unknown;
9
+ setup: SetupCommand[];
10
+ }
11
+ export interface McpServerOwner extends McpServerDefinition {
12
+ owner: string;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ export interface RolePaths {
2
+ role: string;
3
+ roleRoot: string;
4
+ roleManifest: string;
5
+ constantsDir?: string;
6
+ constantsFile?: string;
7
+ }
8
+ /** Normalized fields; installation values always belong to this declaration. */
9
+ export interface RoleContractData {
10
+ path: string;
11
+ schemaVersion: 1 | 2;
12
+ roleId?: unknown;
13
+ canonicalRoot?: unknown;
14
+ capabilities?: unknown;
15
+ extendsRoles: string[];
16
+ hosts?: unknown;
17
+ packages?: unknown;
18
+ assets?: unknown;
19
+ distribution?: unknown;
20
+ entrypoints?: unknown;
21
+ roleVendor?: unknown;
22
+ roleVendorPosition?: 'before' | 'after';
23
+ }
24
+ export interface RoleInheritanceEntry {
25
+ role: string;
26
+ manifestPath: string;
27
+ inheritancePath: string[];
28
+ contract?: RoleContractData;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import type { SetupCommand, VendorLink, VendorManifest } from './manifest.js';
2
+ export interface VendorAssetInventory {
3
+ role: string;
4
+ roleRoot?: string;
5
+ skills: string[];
6
+ }
7
+ export interface RebuildVendorAssetsOptions {
8
+ homeDir: string;
9
+ role: string;
10
+ manifestPath: string;
11
+ manifest?: VendorManifest;
12
+ beforeCommit?: () => void | Promise<void>;
13
+ }
14
+ export interface PlannedAsset {
15
+ vendorId: string;
16
+ kind: VendorLink['kind'];
17
+ source: string;
18
+ target: string;
19
+ setup: SetupCommand[];
20
+ origin: string;
21
+ }
22
+ export interface VendorStagingPlan {
23
+ assets: PlannedAsset[];
24
+ roleSource?: string;
25
+ roleVendorId?: string;
26
+ roleMcpPath?: string;
27
+ roleSetup?: SetupCommand[];
28
+ roleOrigin?: string;
29
+ }
30
+ export interface MaterializedPlan {
31
+ buildRoot: string;
32
+ stagingRoot: string;
33
+ roleStagingRoot?: string;
34
+ }
35
+ export type SourceKind = 'file' | 'directory';
36
+ export interface CanonicalRoleAssetRoots {
37
+ skills: string;
38
+ mcp: string;
39
+ }
40
+ export interface ManagedEntryCommit {
41
+ current: string;
42
+ backup: string;
43
+ movedCurrent: boolean;
44
+ installedNext: boolean;
45
+ }
46
+ export interface ManagedEntrySpec {
47
+ current: string;
48
+ next: string;
49
+ backup: string;
50
+ preserveRoot?: boolean;
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,12 +1,4 @@
1
- export interface VendorAssetInventory {
2
- role: string;
3
- roleRoot?: string;
4
- skills: string[];
5
- }
6
- export interface RebuildVendorAssetsOptions {
7
- homeDir: string;
8
- role: string;
9
- manifestPath: string;
10
- }
1
+ import type { RebuildVendorAssetsOptions, VendorAssetInventory } from './types/vendor-staging.js';
2
+ export type { RebuildVendorAssetsOptions, VendorAssetInventory } from './types/vendor-staging.js';
11
3
  export declare function cleanupEmptyVendorSkillDirectories(homeDir: string): void;
12
4
  export declare function rebuildVendorAssets(options: RebuildVendorAssetsOptions): Promise<VendorAssetInventory>;