mirror of
https://github.com/bmad-code-org/BMAD-METHOD.git
synced 2026-09-01 15:09:07 +08:00
feat: add Polytoken installer support
This commit is contained in:
@@ -1400,6 +1400,67 @@ async function runTests() {
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Suite 28b: Polytoken Native Skills
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 28b: Polytoken Native Skills${colors.reset}\n`);
|
||||
|
||||
let tempProjectDir28b;
|
||||
let installedBmadDir28b;
|
||||
try {
|
||||
clearCache();
|
||||
const platformCodes28b = await loadPlatformCodes();
|
||||
const polytokenInstaller = platformCodes28b.platforms.polytoken?.installer;
|
||||
|
||||
assert(polytokenInstaller?.target_dir === '.polytoken/skills', 'Polytoken target_dir uses its project-local native skills path');
|
||||
assert(!polytokenInstaller?.global_target_dir, 'Polytoken does not claim an undocumented global skills path');
|
||||
|
||||
tempProjectDir28b = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-polytoken-test-'));
|
||||
installedBmadDir28b = await createTestBmadFixture();
|
||||
|
||||
const ideManager28b = new IdeManager();
|
||||
await ideManager28b.ensureInitialized();
|
||||
|
||||
assert(
|
||||
ideManager28b.getAvailableIdes().some((ide) => ide.value === 'polytoken'),
|
||||
'Polytoken appears in available IDEs list',
|
||||
);
|
||||
|
||||
const detectedBefore28b = await ideManager28b.detectInstalledIdes(tempProjectDir28b);
|
||||
assert(!detectedBefore28b.includes('polytoken'), 'Polytoken is not detected before install');
|
||||
|
||||
const result28b = await ideManager28b.setup('polytoken', tempProjectDir28b, installedBmadDir28b, {
|
||||
silent: true,
|
||||
selectedModules: ['bmm'],
|
||||
});
|
||||
assert(result28b.success === true, 'Polytoken setup succeeds against temp project');
|
||||
|
||||
const detectedAfter28b = await ideManager28b.detectInstalledIdes(tempProjectDir28b);
|
||||
assert(detectedAfter28b.includes('polytoken'), 'Polytoken is detected after install');
|
||||
|
||||
const skillFile28b = path.join(tempProjectDir28b, '.polytoken', 'skills', 'bmad-master', 'SKILL.md');
|
||||
assert(await fs.pathExists(skillFile28b), 'Polytoken install writes SKILL.md directory output');
|
||||
|
||||
const skillContent28b = await fs.readFile(skillFile28b, 'utf8');
|
||||
const frontmatter28b = skillContent28b.match(/^---\n([\s\S]*?)\n---\n?/);
|
||||
assert(frontmatter28b, 'Polytoken SKILL.md contains valid frontmatter delimiters');
|
||||
assert(/^description:\s*\S.+$/m.test(frontmatter28b?.[1] || ''), 'Polytoken skill has a non-empty description');
|
||||
assert(skillContent28b.includes('agent-activation'), 'Polytoken skill preserves its activation instructions');
|
||||
|
||||
const reinstall28b = await ideManager28b.setup('polytoken', tempProjectDir28b, installedBmadDir28b, {
|
||||
silent: true,
|
||||
selectedModules: ['bmm'],
|
||||
});
|
||||
assert(reinstall28b.success === true, 'Polytoken reinstall succeeds over existing skills');
|
||||
} catch (error) {
|
||||
assert(false, 'Polytoken native skills test succeeds', error.message);
|
||||
} finally {
|
||||
if (tempProjectDir28b) await fs.remove(tempProjectDir28b).catch(() => {});
|
||||
if (installedBmadDir28b) await fs.remove(path.dirname(installedBmadDir28b)).catch(() => {});
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Suite 29: Unified Skill Scanner — collectSkills
|
||||
// ============================================================
|
||||
|
||||
@@ -281,6 +281,12 @@ platforms:
|
||||
target_dir: .agents/skills
|
||||
global_target_dir: ~/.agents/skills
|
||||
|
||||
polytoken:
|
||||
name: "Polytoken"
|
||||
preferred: false
|
||||
installer:
|
||||
target_dir: .polytoken/skills
|
||||
|
||||
qoder:
|
||||
name: "Qoder"
|
||||
preferred: false
|
||||
|
||||
@@ -266,6 +266,19 @@ async function autocompleteMultiselect(options) {
|
||||
const filterFn = options.filter ?? defaultAutocompleteFilter;
|
||||
const lockedSet = new Set(options.lockedValues || []);
|
||||
|
||||
if (typeof core.AutocompletePrompt !== 'function') {
|
||||
const initialValues = [...new Set([...(options.initialValues || []), ...(options.lockedValues || [])])];
|
||||
const result = await clack.multiselect({
|
||||
message: options.message,
|
||||
options: options.options,
|
||||
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
||||
required: options.required || false,
|
||||
maxItems: options.maxItems,
|
||||
});
|
||||
await handleCancel(result);
|
||||
return [...new Set([...result, ...(options.lockedValues || [])])];
|
||||
}
|
||||
|
||||
const prompt = new core.AutocompletePrompt({
|
||||
options: options.options,
|
||||
multiple: true,
|
||||
@@ -558,7 +571,17 @@ async function cancel(message = 'Operation cancelled') {
|
||||
*/
|
||||
async function box(content, title, options) {
|
||||
const clack = await getClack();
|
||||
clack.box(content, title, options);
|
||||
|
||||
// @clack/prompts does not currently expose box(), despite some versions of
|
||||
// its documentation and ecosystem examples referring to one. Keep the
|
||||
// wrapper forward-compatible while falling back to Clack's supported boxed
|
||||
// note renderer for current releases.
|
||||
if (typeof clack.box === 'function') {
|
||||
clack.box(content, title, options);
|
||||
return;
|
||||
}
|
||||
|
||||
clack.note(content, title);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,7 +596,8 @@ async function box(content, title, options) {
|
||||
*/
|
||||
async function autocomplete(options) {
|
||||
const clack = await getClack();
|
||||
const result = await clack.autocomplete(options);
|
||||
const prompt = typeof clack.autocomplete === 'function' ? clack.autocomplete : clack.select;
|
||||
const result = await prompt(options);
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
@@ -654,6 +678,20 @@ function listDirectoryOptions(input, options) {
|
||||
*/
|
||||
async function directory(options) {
|
||||
const core = await getClackCore();
|
||||
|
||||
if (typeof core.AutocompletePrompt !== 'function') {
|
||||
const clack = await getClack();
|
||||
const result = await clack.text({
|
||||
message: options.message,
|
||||
placeholder: options.placeholder,
|
||||
initialValue: options.default,
|
||||
defaultValue: options.default,
|
||||
validate: options.validate,
|
||||
});
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
const color = await getPicocolors();
|
||||
const tabCompletion = {
|
||||
prefix: '',
|
||||
|
||||
Reference in New Issue
Block a user