Compare commits

...
Author SHA1 Message Date
chirag-nighut 12ee6b100b Merge branch 'fix/dynamic-skill-discovery' of ssh://github.com/cnighut/GitNexus into fix/dynamic-skill-discovery 2026-03-13 23:17:18 +05:30
chirag-nighut 3d4a787ad4 fix(cli): use glob for skill discovery in setup
Switch skill source discovery to glob patterns for flat and directory layouts, removing redundant per-skill stat checks and simplifying install flow.

Made-with: Cursor
2026-03-13 23:12:26 +05:30
Chirag Nighut 23d3765a8d Merge branch 'main' into fix/dynamic-skill-discovery 2026-03-13 22:48:16 +05:30
chirag-nighut 824b22ba1a Merge remote-tracking branch 'origin/main' into fix/dynamic-skill-discovery 2026-03-13 22:47:08 +05:30
chirag-nighut 1d9a9c476d fix(cli): dynamically discover and install agent skills
This fixes an issue where the `SKILL_NAMES` array was hardcoded, preventing newly added skills from being installed when running `gitnexus setup`.

Fixes #269

Made-with: Cursor
2026-03-13 16:28:25 +05:30
+27 -15
View File
@@ -10,6 +10,7 @@ import fs from 'fs/promises';
import path from 'path';
import os from 'os';
import { fileURLToPath } from 'url';
import { glob } from 'glob';
import { getGlobalDir } from '../storage/repo-manager.js';
const __filename = fileURLToPath(import.meta.url);
@@ -240,8 +241,6 @@ async function setupOpenCode(result: SetupResult): Promise<void> {
// ─── Skill Installation ───────────────────────────────────────────
const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impact-analysis', 'gitnexus-refactoring', 'gitnexus-guide', 'gitnexus-cli'];
/**
* Install GitNexus skills to a target directory.
* Each skill is installed as {targetDir}/gitnexus-{skillName}/SKILL.md
@@ -255,25 +254,38 @@ async function installSkillsTo(targetDir: string): Promise<string[]> {
const installed: string[] = [];
const skillsRoot = path.join(__dirname, '..', '..', 'skills');
for (const skillName of SKILL_NAMES) {
let flatFiles: string[] = [];
let dirSkillFiles: string[] = [];
try {
[flatFiles, dirSkillFiles] = await Promise.all([
glob('*.md', { cwd: skillsRoot }),
glob('*/SKILL.md', { cwd: skillsRoot }),
]);
} catch {
return [];
}
const skillSources = new Map<string, { isDirectory: boolean }>();
for (const relPath of dirSkillFiles) {
skillSources.set(path.dirname(relPath), { isDirectory: true });
}
for (const relPath of flatFiles) {
const skillName = path.basename(relPath, '.md');
if (!skillSources.has(skillName)) {
skillSources.set(skillName, { isDirectory: false });
}
}
for (const [skillName, source] of skillSources) {
const skillDir = path.join(targetDir, skillName);
try {
// Try directory-based skill first (skills/{name}/SKILL.md)
const dirSource = path.join(skillsRoot, skillName);
const dirSkillFile = path.join(dirSource, 'SKILL.md');
let isDirectory = false;
try {
const stat = await fs.stat(dirSource);
isDirectory = stat.isDirectory();
} catch { /* not a directory */ }
if (isDirectory) {
if (source.isDirectory) {
const dirSource = path.join(skillsRoot, skillName);
await copyDirRecursive(dirSource, skillDir);
installed.push(skillName);
} else {
// Fall back to flat file (skills/{name}.md)
const flatSource = path.join(skillsRoot, `${skillName}.md`);
const content = await fs.readFile(flatSource, 'utf-8');
await fs.mkdir(skillDir, { recursive: true });