feat(coding): add bundled plugin package selection

This commit is contained in:
2026-08-27 15:08:28 +08:00
parent 2ab1c51a24
commit 422150d4fa
14 changed files with 2188 additions and 15 deletions

View File

@@ -1,5 +1,6 @@
import { readFile, readdir } from 'node:fs/promises';
import path from 'node:path';
import { DATA_SERVICE_PLUGIN_DEFINITION } from '../../shared/coding-plugins';
import {
BUNDLED_CODING_SKILL_IDS,
type BundledCodingSkillId,
@@ -26,6 +27,13 @@ const MAKELORE_COMMANDS: readonly ProductCodingCommand[] = [
const COMMAND_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
export interface ProductCodingPluginSkillSource {
id: string;
directory: string;
/** Package-relative Skill entry; defaults to `SKILL.md`. */
entryPath?: string;
}
function frontmatterScalar(content: string, key: string): string | undefined {
const block = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
if (!block) return undefined;
@@ -39,17 +47,41 @@ function frontmatterScalar(content: string, key: string): string | undefined {
return value || undefined;
}
function selectedSkillIds(value: readonly string[]): Set<BundledCodingSkillId> {
const allowed = new Set<string>(BUNDLED_CODING_SKILL_IDS);
const selected = new Set<BundledCodingSkillId>();
function defaultPluginSkillSources(bundledSkillsDir: string): ProductCodingPluginSkillSource[] {
const skillId = DATA_SERVICE_PLUGIN_DEFINITION.skills[0]?.id ?? 'data-service';
return [{
id: skillId,
directory: path.join(
path.dirname(path.resolve(bundledSkillsDir)),
'coding-plugins',
'data-service',
'skills',
'data-service',
),
}];
}
function selectedSkillIds(
value: readonly string[],
allIds: readonly string[],
): Set<string> {
const allowed = new Set<string>(allIds);
const selected = new Set<string>();
for (const raw of value) {
const id = raw.trim();
if (!allowed.has(id)) throw new Error(`Unknown bundled coding skill: ${id}`);
selected.add(id as BundledCodingSkillId);
selected.add(id);
}
return selected;
}
function productSkillId(id: string): BundledCodingSkillId {
// ProductCodingSkill predates package-owned Skill ids. The registry is
// the trusted projection boundary, so the runtime value may contain a
// package Skill while the shared contract is migrated by the caller.
return id as BundledCodingSkillId;
}
async function listSkillEntries(
directory: string,
relative = '',
@@ -71,13 +103,26 @@ async function listSkillEntries(
export async function listProductCodingSkills(
bundledSkillsDir: string,
selectedIds: readonly string[] = [],
pluginSkillSources: readonly ProductCodingPluginSkillSource[] = defaultPluginSkillSources(bundledSkillsDir),
): Promise<ProductCodingSkill[]> {
const selected = selectedSkillIds(selectedIds);
return await Promise.all(BUNDLED_CODING_SKILL_IDS.map(async (id) => {
const location = path.join(bundledSkillsDir, id);
const content = await readFile(path.join(location, 'SKILL.md'), 'utf8');
return {
const pluginIds = pluginSkillSources.map(({ id }) => id);
const allIds = [...BUNDLED_CODING_SKILL_IDS, ...pluginIds];
if (new Set(allIds).size !== allIds.length) {
throw new Error('Duplicate coding skill identifier');
}
const selected = selectedSkillIds(selectedIds, allIds);
const sources: ProductCodingPluginSkillSource[] = [
...BUNDLED_CODING_SKILL_IDS.map((id) => ({
id,
directory: path.join(bundledSkillsDir, id),
})),
...pluginSkillSources,
];
return await Promise.all(sources.map(async ({ id, directory, entryPath }) => {
const location = path.resolve(directory);
const content = await readFile(path.join(location, entryPath ?? 'SKILL.md'), 'utf8');
return {
id: productSkillId(id),
name: frontmatterScalar(content, 'name') ?? id,
description: frontmatterScalar(content, 'description') ?? '',
selected: selected.has(id),