moluoxixi-ai-rules 0.2.4 → 0.2.5

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.
@@ -1,5 +1,4 @@
1
1
  import type { VendorRepo } from '../../../scripts/lib/vendors.js';
2
2
  export declare const extendsRoles: string[];
3
3
  export declare const hosts = "all";
4
- export declare const capabilities: readonly ["engineering", "productivity"];
5
- export declare const vendors: VendorRepo[];
4
+ export declare const roleVendor: VendorRepo;
@@ -1,11 +1,7 @@
1
- import { composeCapabilities } from '../../../capabilities/index.js';
2
1
  export const extendsRoles = [];
3
2
  export const hosts = 'all';
4
- export const capabilities = [
5
- 'engineering',
6
- 'productivity',
7
- ];
8
- const roleVendor = {
3
+ // 角色能力(capabilities)与 role_vendor_position 声明在 roles/matt/role.yaml 中,由 loadVendorManifest 组合成 vendors。
4
+ export const roleVendor = {
9
5
  name: 'matt-role',
10
6
  source: 'https://github.com/moluoxixi/AIRules.git',
11
7
  projections: [
@@ -15,7 +11,3 @@ const roleVendor = {
15
11
  },
16
12
  ],
17
13
  };
18
- export const vendors = composeCapabilities(capabilities, {
19
- roleVendor,
20
- roleVendorPosition: 'after',
21
- });
@@ -2,5 +2,4 @@ import type { RolePackageConfig, VendorRepo } from '../../../scripts/lib/vendors
2
2
  export declare const extendsRoles: string[];
3
3
  export declare const hosts = "all";
4
4
  export declare const packages: RolePackageConfig[];
5
- export declare const capabilities: readonly ["common", "coding", "productivity", "frontend"];
6
- export declare const vendors: VendorRepo[];
5
+ export declare const roleVendor: VendorRepo;
@@ -1,4 +1,3 @@
1
- import { composeCapabilities } from '../../../capabilities/index.js';
2
1
  export const extendsRoles = [];
3
2
  export const hosts = 'all';
4
3
  export const packages = [
@@ -15,13 +14,8 @@ export const packages = [
15
14
  },
16
15
  },
17
16
  ];
18
- export const capabilities = [
19
- 'common',
20
- 'coding',
21
- 'productivity',
22
- 'frontend',
23
- ];
24
- const roleVendor = {
17
+ // 角色能力(capabilities)声明在 roles/moluoxixi/role.yaml 中,由 loadVendorManifest 组合成 vendors。
18
+ export const roleVendor = {
25
19
  name: 'moluoxixi',
26
20
  source: 'https://github.com/moluoxixi/AIRules.git',
27
21
  projections: [
@@ -31,4 +25,3 @@ const roleVendor = {
31
25
  },
32
26
  ],
33
27
  };
34
- export const vendors = composeCapabilities(capabilities, { roleVendor });
@@ -1,5 +1,4 @@
1
1
  import type { VendorRepo } from '../../../scripts/lib/vendors.js';
2
2
  export declare const extendsRoles: string[];
3
3
  export declare const hosts = "all";
4
- export declare const capabilities: readonly ["common", "coding", "productivity", "frontend"];
5
- export declare const vendors: VendorRepo[];
4
+ export declare const roleVendor: VendorRepo;
@@ -1,13 +1,7 @@
1
- import { composeCapabilities } from '../../../capabilities/index.js';
2
1
  export const extendsRoles = [];
3
2
  export const hosts = 'all';
4
- export const capabilities = [
5
- 'common',
6
- 'coding',
7
- 'productivity',
8
- 'frontend',
9
- ];
10
- const roleVendor = {
3
+ // 角色能力(capabilities)声明在 roles/trellis/role.yaml 中,由 loadVendorManifest 组合成 vendors。
4
+ export const roleVendor = {
11
5
  name: 'trellis',
12
6
  source: 'https://github.com/moluoxixi/AIRules.git',
13
7
  setup: [
@@ -23,4 +17,3 @@ const roleVendor = {
23
17
  },
24
18
  ],
25
19
  };
26
- export const vendors = composeCapabilities(capabilities, { roleVendor });
@@ -1,5 +1,8 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
3
  import { fileURLToPath, pathToFileURL, URL } from 'node:url';
4
+ import { parseDocument } from 'yaml';
5
+ import { CAPABILITY_NAMES, composeCapabilities } from '../../capabilities/index.js';
3
6
  import { HOST_IDS } from '../../constants/hosts.js';
4
7
  import { flattenedSkillName, flattenedVendorSkillTarget } from './skill-projection.js';
5
8
  const vendorNamePattern = /^[A-Za-z0-9][\w-]*$/u;
@@ -204,9 +207,12 @@ export function walkVendorTree(node, namespaceParts, vendors) {
204
207
  export async function loadVendorManifest(manifestPath) {
205
208
  const manifestUrl = pathToFileURL(path.resolve(manifestPath)).href;
206
209
  const module = await import(manifestUrl);
207
- const vendorTree = module.vendors ?? module.default?.vendors ?? module.default;
210
+ const roleVendor = module.roleVendor ?? module.default?.roleVendor;
211
+ const vendorTree = roleVendor === undefined
212
+ ? module.vendors ?? module.default?.vendors ?? module.default
213
+ : composeRoleVendorTree(manifestPath, roleVendor);
208
214
  if (!vendorTree || typeof vendorTree !== 'object') {
209
- throw new Error(`Vendor manifest "${manifestPath}" must export a "vendors" object`);
215
+ throw new Error(`Vendor manifest "${manifestPath}" must export a "vendors" object or a "roleVendor" definition`);
210
216
  }
211
217
  const vendors = {};
212
218
  walkVendorTree(vendorTree, [], vendors);
@@ -219,6 +225,76 @@ export async function loadVendorManifest(manifestPath) {
219
225
  vendors,
220
226
  };
221
227
  }
228
+ /**
229
+ * 定位清单对应的 role.yaml 角色契约。
230
+ * 源码与 checkout 布局下契约位于清单的角色根目录;
231
+ * dist 布局(dist/roles/<role>/constants/skills.js)下回退到包根的 roles/<role>/role.yaml。
232
+ */
233
+ function resolveRoleContractPath(manifestPath) {
234
+ const resolvedManifest = path.resolve(manifestPath);
235
+ const roleRoot = path.dirname(path.dirname(resolvedManifest));
236
+ const sourceContract = path.join(roleRoot, 'role.yaml');
237
+ if (fs.existsSync(sourceContract))
238
+ return sourceContract;
239
+ const rolesDir = path.dirname(roleRoot);
240
+ const distRoot = path.dirname(rolesDir);
241
+ if (path.basename(rolesDir) === 'roles' && path.basename(distRoot) === 'dist') {
242
+ const packagedContract = path.join(path.dirname(distRoot), 'roles', path.basename(roleRoot), 'role.yaml');
243
+ if (fs.existsSync(packagedContract))
244
+ return packagedContract;
245
+ }
246
+ throw new Error(`Vendor manifest "${manifestPath}" exports "roleVendor" but its role.yaml contract is missing: ${sourceContract}`);
247
+ }
248
+ function loadRoleCapabilityContract(manifestPath) {
249
+ const contractPath = resolveRoleContractPath(manifestPath);
250
+ const document = parseDocument(fs.readFileSync(contractPath, 'utf8'), {
251
+ merge: false,
252
+ prettyErrors: true,
253
+ strict: true,
254
+ uniqueKeys: true,
255
+ });
256
+ if (document.errors.length > 0) {
257
+ throw new Error(`Role contract "${contractPath}" is invalid: ${document.errors.map(error => error.message).join('; ')}`);
258
+ }
259
+ const contract = document.toJS({ maxAliasCount: 0 });
260
+ if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {
261
+ throw new Error(`Role contract "${contractPath}" must be a YAML mapping`);
262
+ }
263
+ const { capabilities, role_vendor_position: roleVendorPosition } = contract;
264
+ if (!Array.isArray(capabilities) || capabilities.length === 0) {
265
+ throw new Error(`Role contract "${contractPath}" must declare a non-empty "capabilities" list`);
266
+ }
267
+ const seen = new Set();
268
+ for (const capability of capabilities) {
269
+ if (typeof capability !== 'string' || !CAPABILITY_NAMES.includes(capability)) {
270
+ throw new Error(`Role contract "${contractPath}" references unknown capability "${String(capability)}"`);
271
+ }
272
+ if (seen.has(capability)) {
273
+ throw new Error(`Role contract "${contractPath}" declares duplicate capability "${capability}"`);
274
+ }
275
+ seen.add(capability);
276
+ }
277
+ if (roleVendorPosition !== undefined && roleVendorPosition !== 'before' && roleVendorPosition !== 'after') {
278
+ throw new Error(`Role contract "${contractPath}" field "role_vendor_position" must be "before" or "after"`);
279
+ }
280
+ return {
281
+ capabilities: capabilities,
282
+ ...(roleVendorPosition === undefined ? {} : { roleVendorPosition }),
283
+ };
284
+ }
285
+ /**
286
+ * 依据 role.yaml 声明的 capabilities,把清单导出的 roleVendor 组合成完整 vendors 列表。
287
+ */
288
+ function composeRoleVendorTree(manifestPath, roleVendor) {
289
+ if (!isVendorEntry(roleVendor)) {
290
+ throw new Error(`Vendor manifest "${manifestPath}" export "roleVendor" must be a vendor definition with name and source`);
291
+ }
292
+ const contract = loadRoleCapabilityContract(manifestPath);
293
+ return composeCapabilities(contract.capabilities, {
294
+ roleVendor: roleVendor,
295
+ ...(contract.roleVendorPosition === undefined ? {} : { roleVendorPosition: contract.roleVendorPosition }),
296
+ });
297
+ }
222
298
  function normalizeRolePackages(value, manifestPath) {
223
299
  if (value === undefined)
224
300
  return [];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "moluoxixi-ai-rules",
3
3
  "type": "module",
4
- "version": "0.2.4",
4
+ "version": "0.2.5",
5
5
  "description": "Remote AI role asset distribution for supported hosts.",
6
6
  "license": "MIT",
7
7
  "repository": "https://github.com/moluoxixi/AIRules",
@@ -23,7 +23,6 @@
23
23
  "README.md",
24
24
  "bin",
25
25
  "dist",
26
- "docs",
27
26
  "roles/*/constants/**",
28
27
  "roles/*/role.yaml"
29
28
  ],
@@ -1,17 +1,11 @@
1
- import type { CapabilityName } from '../../../capabilities/index.js'
2
1
  import type { VendorRepo } from '../../../scripts/lib/vendors.js'
3
- import { composeCapabilities } from '../../../capabilities/index.js'
4
2
 
5
3
  export const extendsRoles: string[] = []
6
4
 
7
5
  export const hosts = 'all'
8
6
 
9
- export const capabilities = [
10
- 'engineering',
11
- 'productivity',
12
- ] as const satisfies readonly CapabilityName[]
13
-
14
- const roleVendor: VendorRepo = {
7
+ // 角色能力(capabilities)与 role_vendor_position 声明在 roles/matt/role.yaml 中,由 loadVendorManifest 组合成 vendors。
8
+ export const roleVendor: VendorRepo = {
15
9
  name: 'matt-role',
16
10
  source: 'https://github.com/moluoxixi/AIRules.git',
17
11
  projections: [
@@ -21,8 +15,3 @@ const roleVendor: VendorRepo = {
21
15
  },
22
16
  ],
23
17
  }
24
-
25
- export const vendors: VendorRepo[] = composeCapabilities(capabilities, {
26
- roleVendor,
27
- roleVendorPosition: 'after',
28
- })
@@ -6,6 +6,10 @@ canonical_root: roles/matt
6
6
  description: Installs Matt Pocock's engineering and productivity skills through AIRules.
7
7
  assets:
8
8
  skills: skills
9
+ capabilities:
10
+ - engineering
11
+ - productivity
12
+ role_vendor_position: after
9
13
  distribution:
10
14
  bootstrap_manifest: constants/skills.ts
11
15
  full_role_path_required: true
@@ -1,6 +1,4 @@
1
- import type { CapabilityName } from '../../../capabilities/index.js'
2
1
  import type { RolePackageConfig, VendorRepo } from '../../../scripts/lib/vendors.js'
3
- import { composeCapabilities } from '../../../capabilities/index.js'
4
2
 
5
3
  export const extendsRoles: string[] = []
6
4
 
@@ -21,14 +19,8 @@ export const packages: RolePackageConfig[] = [
21
19
  },
22
20
  ]
23
21
 
24
- export const capabilities = [
25
- 'common',
26
- 'coding',
27
- 'productivity',
28
- 'frontend',
29
- ] as const satisfies readonly CapabilityName[]
30
-
31
- const roleVendor: VendorRepo = {
22
+ // 角色能力(capabilities)声明在 roles/moluoxixi/role.yaml 中,由 loadVendorManifest 组合成 vendors。
23
+ export const roleVendor: VendorRepo = {
32
24
  name: 'moluoxixi',
33
25
  source: 'https://github.com/moluoxixi/AIRules.git',
34
26
  projections: [
@@ -38,5 +30,3 @@ const roleVendor: VendorRepo = {
38
30
  },
39
31
  ],
40
32
  }
41
-
42
- export const vendors: VendorRepo[] = composeCapabilities(capabilities, { roleVendor })
@@ -8,6 +8,11 @@ assets:
8
8
  skills: skills
9
9
  mcp: mcp
10
10
  packages: packages
11
+ capabilities:
12
+ - common
13
+ - coding
14
+ - productivity
15
+ - frontend
11
16
  distribution:
12
17
  bootstrap_manifest: constants/skills.ts
13
18
  full_role_path_required: true
@@ -1,19 +1,11 @@
1
- import type { CapabilityName } from '../../../capabilities/index.js'
2
1
  import type { VendorRepo } from '../../../scripts/lib/vendors.js'
3
- import { composeCapabilities } from '../../../capabilities/index.js'
4
2
 
5
3
  export const extendsRoles: string[] = []
6
4
 
7
5
  export const hosts = 'all'
8
6
 
9
- export const capabilities = [
10
- 'common',
11
- 'coding',
12
- 'productivity',
13
- 'frontend',
14
- ] as const satisfies readonly CapabilityName[]
15
-
16
- const roleVendor: VendorRepo = {
7
+ // 角色能力(capabilities)声明在 roles/trellis/role.yaml 中,由 loadVendorManifest 组合成 vendors。
8
+ export const roleVendor: VendorRepo = {
17
9
  name: 'trellis',
18
10
  source: 'https://github.com/moluoxixi/AIRules.git',
19
11
  setup: [
@@ -29,5 +21,3 @@ const roleVendor: VendorRepo = {
29
21
  },
30
22
  ],
31
23
  }
32
-
33
- export const vendors: VendorRepo[] = composeCapabilities(capabilities, { roleVendor })
@@ -7,6 +7,11 @@ description: Installs Trellis, shared coding and frontend capabilities, native p
7
7
  assets:
8
8
  skills: skills
9
9
  mcp: mcp
10
+ capabilities:
11
+ - common
12
+ - coding
13
+ - productivity
14
+ - frontend
10
15
  distribution:
11
16
  bootstrap_manifest: constants/skills.ts
12
17
  full_role_path_required: true