Skip to content

Commit 13d7eeb

Browse files
committed
feat(claude): add skills-first agent activation [ACORE-SKILLS]
1 parent 94842cd commit 13d7eeb

40 files changed

Lines changed: 6012 additions & 5071 deletions

.aiox-core/core-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ ideSync:
312312
claude-code:
313313
enabled: true
314314
path: .claude/commands/AIOX/agents
315+
skillsPath: .claude/skills
315316
format: full-markdown-yaml
316317
codex:
317318
enabled: true

.aiox-core/core/doctor/checks/ide-sync.js

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
22
* Doctor Check: IDE Sync
33
*
4-
* Validates agents in .claude/commands/AIOX/agents/ match
5-
* .aiox-core/development/agents/ (count and names).
4+
* Validates Claude agent skills and legacy command files match
5+
* .aiox-core/development/agents/ during the skills-first transition.
66
*
77
* @module aiox-core/doctor/checks/ide-sync
88
* @story INS-4.1
@@ -15,7 +15,8 @@ const name = 'ide-sync';
1515

1616
async function run(context) {
1717
const agentsSourceDir = path.join(context.projectRoot, '.aiox-core', 'development', 'agents');
18-
const agentsIdeDir = path.join(context.projectRoot, '.claude', 'commands', 'AIOX', 'agents');
18+
const agentsCommandDir = path.join(context.projectRoot, '.claude', 'commands', 'AIOX', 'agents');
19+
const agentsSkillDir = path.join(context.projectRoot, '.claude', 'skills', 'AIOX', 'agents');
1920

2021
if (!fs.existsSync(agentsSourceDir)) {
2122
return {
@@ -26,16 +27,7 @@ async function run(context) {
2627
};
2728
}
2829

29-
if (!fs.existsSync(agentsIdeDir)) {
30-
return {
31-
check: name,
32-
status: 'WARN',
33-
message: 'IDE agents directory not found (.claude/commands/AIOX/agents/)',
34-
fixCommand: 'npx aiox-core install --force',
35-
};
36-
}
37-
38-
let sourceAgents, ideFiles;
30+
let sourceAgents;
3931
try {
4032
sourceAgents = fs.readdirSync(agentsSourceDir)
4133
.filter((f) => f.endsWith('.md'))
@@ -49,35 +41,43 @@ async function run(context) {
4941
};
5042
}
5143

52-
try {
53-
ideFiles = fs.readdirSync(agentsIdeDir)
54-
.filter((f) => f.endsWith('.md'));
55-
} catch (_err) {
44+
const commandAgents = fs.existsSync(agentsCommandDir)
45+
? fs.readdirSync(agentsCommandDir)
46+
.filter((f) => f.endsWith('.md'))
47+
.map((f) => f.replace('.md', ''))
48+
: [];
49+
const skillAgents = fs.existsSync(agentsSkillDir)
50+
? fs.readdirSync(agentsSkillDir, { withFileTypes: true })
51+
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(agentsSkillDir, entry.name, 'SKILL.md')))
52+
.map((entry) => entry.name)
53+
: [];
54+
55+
const sourceCount = sourceAgents.length;
56+
const commandCount = commandAgents.length;
57+
const skillCount = skillAgents.length;
58+
59+
if (skillCount !== sourceCount) {
5660
return {
5761
check: name,
5862
status: 'WARN',
59-
message: 'Cannot read IDE agents directory',
63+
message: `Claude skills have ${skillCount} agents, source has ${sourceCount}`,
6064
fixCommand: 'npx aiox-core install --force',
6165
};
6266
}
6367

64-
const ideAgents = ideFiles.map((f) => f.replace('.md', ''));
65-
const sourceCount = sourceAgents.length;
66-
const ideCount = ideAgents.length;
67-
68-
if (sourceCount === ideCount) {
68+
if (commandCount === sourceCount) {
6969
return {
7070
check: name,
7171
status: 'PASS',
72-
message: `${ideCount}/${sourceCount} agents synced`,
72+
message: `${skillCount}/${sourceCount} Claude skills synced; ${commandCount}/${sourceCount} legacy commands synced`,
7373
fixCommand: null,
7474
};
7575
}
7676

7777
return {
7878
check: name,
7979
status: 'WARN',
80-
message: `IDE has ${ideCount} agents, source has ${sourceCount}`,
80+
message: `${skillCount}/${sourceCount} Claude skills synced; legacy commands have ${commandCount}/${sourceCount}`,
8181
fixCommand: 'npx aiox-core install --force',
8282
};
8383
}
Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/**
22
* Doctor Check: Skills Count
33
*
4-
* Counts skill directories in .claude/skills/ that contain SKILL.md.
5-
* PASS: >=7, WARN: 1-6, FAIL: 0 or directory missing.
4+
* Counts SKILL.md files in .claude/skills/ recursively.
5+
* PASS: >=7 total skills and all AIOX agent skills present.
6+
* WARN: skills exist but AIOX agent skills are incomplete.
7+
* FAIL: 0 or directory missing.
68
*
79
* @module aiox-core/doctor/checks/skills-count
810
* @story INS-4.8
@@ -13,8 +15,58 @@ const fs = require('fs');
1315

1416
const name = 'skills-count';
1517

18+
function countSkillFiles(dir) {
19+
let count = 0;
20+
let entries;
21+
22+
try {
23+
entries = fs.readdirSync(dir, { withFileTypes: true });
24+
} catch {
25+
return 0;
26+
}
27+
28+
for (const entry of entries) {
29+
const fullPath = path.join(dir, entry.name);
30+
if (entry.isDirectory()) {
31+
count += countSkillFiles(fullPath);
32+
} else if (entry.isFile() && entry.name === 'SKILL.md') {
33+
count++;
34+
}
35+
}
36+
37+
return count;
38+
}
39+
40+
function countAgentSkillFiles(agentSkillsDir) {
41+
let entries;
42+
43+
try {
44+
entries = fs.readdirSync(agentSkillsDir, { withFileTypes: true });
45+
} catch {
46+
return 0;
47+
}
48+
49+
return entries.filter((entry) => (
50+
entry.isDirectory() && fs.existsSync(path.join(agentSkillsDir, entry.name, 'SKILL.md'))
51+
)).length;
52+
}
53+
54+
function countSourceAgents(sourceAgentsDir) {
55+
let entries;
56+
57+
try {
58+
entries = fs.readdirSync(sourceAgentsDir, { withFileTypes: true });
59+
} catch {
60+
return 0;
61+
}
62+
63+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith('.md')).length;
64+
}
65+
1666
async function run(context) {
1767
const skillsDir = path.join(context.projectRoot, '.claude', 'skills');
68+
const agentSkillsDir = path.join(skillsDir, 'AIOX', 'agents');
69+
const sourceAgentsDir = path.join(context.projectRoot, '.aiox-core', 'development', 'agents');
1870

1971
if (!fs.existsSync(skillsDir)) {
2072
return {
@@ -25,29 +77,24 @@ async function run(context) {
2577
};
2678
}
2779

28-
let entries;
29-
try {
30-
entries = fs.readdirSync(skillsDir, { withFileTypes: true });
31-
} catch {
80+
const count = countSkillFiles(skillsDir);
81+
const agentSkillCount = countAgentSkillFiles(agentSkillsDir);
82+
const sourceAgentCount = countSourceAgents(sourceAgentsDir);
83+
84+
if (count === 0) {
3285
return {
3386
check: name,
3487
status: 'FAIL',
35-
message: 'Cannot read skills directory',
88+
message: 'No skills found (expected >=7)',
3689
fixCommand: 'npx aiox-core install --force',
3790
};
3891
}
3992

40-
const skills = entries.filter(
41-
(d) => d.isDirectory() && fs.existsSync(path.join(skillsDir, d.name, 'SKILL.md')),
42-
);
43-
44-
const count = skills.length;
45-
46-
if (count === 0) {
93+
if (sourceAgentCount > 0 && agentSkillCount !== sourceAgentCount) {
4794
return {
4895
check: name,
49-
status: 'FAIL',
50-
message: 'No skills found (expected >=7)',
96+
status: 'WARN',
97+
message: `${count} skills found, but AIOX agent skills are incomplete (${agentSkillCount}/${sourceAgentCount})`,
5198
fixCommand: 'npx aiox-core install --force',
5299
};
53100
}
@@ -56,17 +103,23 @@ async function run(context) {
56103
return {
57104
check: name,
58105
status: 'PASS',
59-
message: `${count} skills found`,
106+
message: `${count} skills found (${agentSkillCount}/${sourceAgentCount} AIOX agent skills)`,
60107
fixCommand: null,
61108
};
62109
}
63110

64111
return {
65112
check: name,
66113
status: 'WARN',
67-
message: `Only ${count}/7 skills found`,
114+
message: `Only ${count}/7 skills found (${agentSkillCount}/${sourceAgentCount} AIOX agent skills)`,
68115
fixCommand: 'npx aiox-core install --force',
69116
};
70117
}
71118

72-
module.exports = { name, run };
119+
module.exports = {
120+
name,
121+
run,
122+
countSkillFiles,
123+
countAgentSkillFiles,
124+
countSourceAgents,
125+
};

.aiox-core/framework-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ ide_sync_system:
122122
claude-code:
123123
enabled: true
124124
path: ".claude/commands/AIOX/agents"
125+
skillsPath: ".claude/skills"
125126
format: "full-markdown-yaml"
126127
codex:
127128
enabled: true

.aiox-core/infrastructure/scripts/codex-skills-sync/validate.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const path = require('path');
77
const { parseAllAgents } = require('../ide-sync/agent-parser');
88
const { getSkillId } = require('./index');
99

10+
const GENERATED_MARKER = '<!-- AIOX-CODEX-LOCAL-SKILLS: generated -->';
11+
1012
function getDefaultOptions() {
1113
const projectRoot = process.cwd();
1214
return {
@@ -59,14 +61,32 @@ function validateSkillContent(content, expected) {
5961
return issues;
6062
}
6163

64+
function extractGeneratedSquadSource(content) {
65+
const match = String(content || '').match(/`(squads\/[^`]+\/agents\/[^`]+\.md)`/);
66+
return match ? match[1] : '';
67+
}
68+
69+
function isGeneratedSquadSkill(content, projectRoot) {
70+
if (!String(content || '').includes(GENERATED_MARKER)) {
71+
return false;
72+
}
73+
74+
const sourcePath = extractGeneratedSquadSource(content);
75+
if (!sourcePath) {
76+
return false;
77+
}
78+
79+
return fs.existsSync(path.join(projectRoot, sourcePath));
80+
}
81+
6282
function validateCodexSkills(options = {}) {
6383
const resolved = { ...getDefaultOptions(), ...options };
6484
const errors = [];
6585
const warnings = [];
6686

6787
if (!fs.existsSync(resolved.skillsDir)) {
6888
errors.push(`Skills directory not found: ${resolved.skillsDir}`);
69-
return { ok: false, checked: 0, expected: 0, errors, warnings, missing: [], orphaned: [] };
89+
return { ok: false, checked: 0, expected: 0, errors, warnings, missing: [], orphaned: [], ignored: [] };
7090
}
7191

7292
const agents = parseAllAgents(resolved.sourceDir).filter(isParsableAgent);
@@ -100,12 +120,26 @@ function validateCodexSkills(options = {}) {
100120

101121
const expectedIds = new Set(expected.map(item => item.skillId));
102122
const orphaned = [];
123+
const ignored = [];
103124
if (resolved.strict) {
104125
const dirs = fs.readdirSync(resolved.skillsDir, { withFileTypes: true })
105126
.filter(entry => entry.isDirectory() && entry.name.startsWith('aiox-'))
106127
.map(entry => entry.name);
107128
for (const dir of dirs) {
108129
if (!expectedIds.has(dir)) {
130+
const skillPath = path.join(resolved.skillsDir, dir, 'SKILL.md');
131+
let content = '';
132+
try {
133+
content = fs.readFileSync(skillPath, 'utf8');
134+
} catch (_error) {
135+
content = '';
136+
}
137+
138+
if (isGeneratedSquadSkill(content, resolved.projectRoot)) {
139+
ignored.push(dir);
140+
continue;
141+
}
142+
109143
orphaned.push(dir);
110144
errors.push(`Orphaned skill directory: ${path.join(path.relative(resolved.projectRoot, resolved.skillsDir), dir)}`);
111145
}
@@ -124,6 +158,7 @@ function validateCodexSkills(options = {}) {
124158
warnings,
125159
missing,
126160
orphaned,
161+
ignored,
127162
};
128163
}
129164

@@ -167,6 +202,8 @@ if (require.main === module) {
167202
module.exports = {
168203
validateCodexSkills,
169204
validateSkillContent,
205+
extractGeneratedSquadSource,
206+
isGeneratedSquadSkill,
170207
parseArgs,
171208
getDefaultOptions,
172209
};

0 commit comments

Comments
 (0)