Skip to content

Commit 057024e

Browse files
authored
feat(ide-sync): add Kimi Code as supported IDE target (#683)
Adds Kimi Code as a skills-first IDE sync target and hardens generated Kimi skill output after review. Validated locally with lint, typecheck, sync checks, manifest validation, targeted IDE-sync tests, schema test, and full Jest suite; GitHub CI checks passed on the final head commit before admin merge.
1 parent fb312dd commit 057024e

23 files changed

Lines changed: 6718 additions & 33 deletions

File tree

.aiox-core/core-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ ideSync:
334334
enabled: true
335335
path: .antigravity/rules/agents
336336
format: cursor-style
337+
kimi:
338+
enabled: true
339+
path: .kimi/skills
340+
format: kimi-skill
341+
fallbackSources:
342+
- .codex/agents
337343
redirects: {}
338344
validation:
339345
strictMode: true

.aiox-core/core/config/schemas/framework-config.schema.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,15 @@
117117
"enabled": { "type": "boolean" },
118118
"path": { "type": "string" },
119119
"skillsPath": { "type": "string" },
120-
"format": { "type": "string", "enum": ["full-markdown-yaml", "condensed-rules", "cursor-style"] }
120+
"fallbackSources": {
121+
"type": "array",
122+
"items": { "type": "string" },
123+
"description": "Fallback source directories used when the primary IDE sync source has no matching definitions"
124+
},
125+
"format": {
126+
"type": "string",
127+
"enum": ["full-markdown-yaml", "condensed-rules", "cursor-style", "kimi-skill"]
128+
}
121129
},
122130
"additionalProperties": false
123131
}

.aiox-core/framework-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ ide_sync_system:
144144
enabled: true
145145
path: ".antigravity/rules/agents"
146146
format: "cursor-style"
147+
kimi:
148+
enabled: true
149+
path: ".kimi/skills"
150+
format: "kimi-skill"
151+
fallbackSources:
152+
- ".codex/agents"
147153
redirects: {}
148154
validation:
149155
strict_mode: true

.aiox-core/infrastructure/scripts/ide-sync/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ Each IDE has a specific format for agent files:
140140
| GitHub Copilot | Full markdown with YAML | `.md` |
141141
| Cursor | Condensed MDC rules | `.mdc` |
142142
| Antigravity | Cursor-style | `.md` |
143+
| Kimi | Skill directory | `SKILL.md` |
143144

144145
Platform-specific checks:
145146

@@ -181,7 +182,9 @@ alwaysApply: false
181182
└── transformers/
182183
├── claude-code.js # Claude Code format
183184
├── cursor.js # Cursor format
184-
└── antigravity.js # Antigravity format
185+
├── antigravity.js # Antigravity format
186+
├── github-copilot.js # GitHub Copilot format
187+
└── kimi.js # Kimi skill format
185188
```
186189

187190
## Performance

.aiox-core/infrastructure/scripts/ide-sync/index.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const claudeCodeTransformer = require('./transformers/claude-code');
3131
const cursorTransformer = require('./transformers/cursor');
3232
const antigravityTransformer = require('./transformers/antigravity');
3333
const githubCopilotTransformer = require('./transformers/github-copilot');
34+
const kimiTransformer = require(path.resolve(__dirname, 'transformers', 'kimi'));
3435

3536
// ANSI colors for output
3637
const colors = {
@@ -88,6 +89,12 @@ function loadConfig(projectRoot) {
8889
path: '.antigravity/rules/agents',
8990
format: 'cursor-style',
9091
},
92+
kimi: {
93+
enabled: true,
94+
path: '.kimi/skills',
95+
format: 'kimi-skill',
96+
fallbackSources: ['.codex/agents'],
97+
},
9198
},
9299
redirects: {
93100
'aiox-developer': 'aiox-master',
@@ -129,6 +136,7 @@ function getTransformer(format) {
129136
'condensed-rules': cursorTransformer,
130137
'cursor-style': antigravityTransformer,
131138
'github-copilot': githubCopilotTransformer,
139+
'kimi-skill': kimiTransformer,
132140
};
133141

134142
return transformers[format] || claudeCodeTransformer;
@@ -142,6 +150,14 @@ function transformPrimaryContent(transformer, agent, ideName) {
142150
return transformer.transform(agent);
143151
}
144152

153+
function isPathInside(rootDir, candidatePath) {
154+
const relativePath = path.relative(rootDir, candidatePath);
155+
return relativePath !== '' &&
156+
relativePath !== '..' &&
157+
!relativePath.startsWith(`..${path.sep}`) &&
158+
!path.isAbsolute(relativePath);
159+
}
160+
145161
/**
146162
* Sync agents to a specific IDE
147163
* @param {object[]} agents - Parsed agent data
@@ -194,7 +210,25 @@ function syncIde(agents, ideConfig, ideName, projectRoot, options) {
194210
try {
195211
const content = transformPrimaryContent(transformer, agent, ideName);
196212
const filename = transformer.getFilename(agent);
197-
const targetPath = path.join(result.targetDir, filename);
213+
214+
// Kimi format uses subdirectories per skill: <skill-id>/SKILL.md
215+
let targetPath;
216+
if (ideConfig.format === 'kimi-skill' && transformer.getDirname) {
217+
const targetRoot = path.resolve(result.targetDir);
218+
const dirname = transformer.getDirname(agent);
219+
const skillDir = path.resolve(targetRoot, dirname);
220+
targetPath = path.resolve(skillDir, filename);
221+
222+
if (!isPathInside(targetRoot, skillDir) || !isPathInside(targetRoot, targetPath)) {
223+
throw new Error(`Unsafe Kimi output path for agent '${agent.id}'`);
224+
}
225+
226+
if (!options.dryRun) {
227+
fs.ensureDirSync(skillDir);
228+
}
229+
} else {
230+
targetPath = path.join(result.targetDir, filename);
231+
}
198232

199233
if (!options.dryRun) {
200234
fs.writeFileSync(targetPath, content, 'utf8');
@@ -409,7 +443,11 @@ async function commandValidate(options) {
409443
try {
410444
const content = transformPrimaryContent(transformer, agent, ideName);
411445
const filename = transformer.getFilename(agent);
412-
expectedFiles.push({ filename, content });
446+
// Kimi format stores each skill in <skill-id>/SKILL.md — record nested path
447+
const relPath = ideConfig.format === 'kimi-skill' && transformer.getDirname
448+
? path.join(transformer.getDirname(agent), filename)
449+
: filename;
450+
expectedFiles.push({ filename: relPath, content });
413451
} catch (error) {
414452
// Skip agents that fail to transform
415453
}

0 commit comments

Comments
 (0)