airail 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/airail.js +3 -0
- package/dist/cli/add.js +136 -0
- package/dist/cli/clear.js +87 -0
- package/dist/cli/colors.js +18 -0
- package/dist/cli/config.js +232 -0
- package/dist/cli/index.js +144 -0
- package/dist/cli/init.js +74 -0
- package/dist/cli/status.js +66 -0
- package/dist/cli/update.js +51 -0
- package/dist/index.js +4 -0
- package/dist/utils.js +243 -0
- package/package.json +39 -0
- package/skills/universal/code-quality/SKILL.md +37 -0
- package/skills/universal/code-review/SKILL.md +37 -0
- package/skills/universal/error-handling/SKILL.md +40 -0
- package/skills/universal/git-workflow/SKILL.md +37 -0
- package/skills/universal/performance/SKILL.md +31 -0
- package/skills/universal/security/SKILL.md +33 -0
- package/skills/universal/skill-add/SKILL.md +241 -0
- package/skills/universal/testing/SKILL.md +39 -0
- package/src/templates/CLAUDE.md +11 -0
- package/src/templates/agents/code-reviewer.md +58 -0
- package/src/templates/agents/project-manager.md +45 -0
- package/src/templates/commands/add-todo.md +24 -0
- package/src/templates/commands/check.md +42 -0
- package/src/templates/commands/crud.md +27 -0
- package/src/templates/commands/dev.md +21 -0
- package/src/templates/commands/init-docs.md +80 -0
- package/src/templates/commands/next.md +31 -0
- package/src/templates/commands/progress.md +38 -0
- package/src/templates/commands/start.md +30 -0
- package/src/templates/commands/update-status.md +22 -0
- package/src/templates/docs/README.md +36 -0
- package/src/templates/docs//345/211/215/347/253/257/345/274/200/345/217/221/346/214/207/345/215/227.md +59 -0
- package/src/templates/docs//345/220/216/347/253/257/345/274/200/345/217/221/346/214/207/345/215/227.md +104 -0
- package/src/templates/docs//345/267/245/345/205/267/347/261/273/344/275/277/347/224/250/346/214/207/345/215/227.md +80 -0
- package/src/templates/docs//346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/350/247/204/350/214/203.md +91 -0
- package/src/templates/docs//346/226/260/345/212/237/350/203/275/345/274/200/345/217/221/346/265/201/347/250/213.md +77 -0
- package/src/templates/docs//346/241/206/346/236/266/350/257/264/346/230/216.md +52 -0
- package/src/templates/hooks/guard.js +113 -0
- package/src/templates/hooks/inject.js +40 -0
- package/src/templates/hooks/skill-eval.js +48 -0
- package/src/templates/settings.json +24 -0
- package/src/templates/templates//345/276/205/345/212/236/346/270/205/345/215/225.md +22 -0
- package/src/templates/templates//351/234/200/346/261/202/346/226/207/346/241/243.md +47 -0
- package/src/templates/templates//351/241/271/347/233/256/347/212/266/346/200/201.md +50 -0
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.cmdInit = cmdInit;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const utils_1 = require("../utils");
|
|
40
|
+
const colors_1 = require("./colors");
|
|
41
|
+
async function cmdInit() {
|
|
42
|
+
const cwd = process.cwd();
|
|
43
|
+
const claudeDir = path.join(cwd, '.claude');
|
|
44
|
+
if (fs.existsSync(path.join(claudeDir, 'airail.json'))) {
|
|
45
|
+
console.log((0, colors_1.warn)('当前项目已初始化 airail。'));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log((0, colors_1.info)('正在初始化 airail...\n'));
|
|
49
|
+
const projectType = (0, utils_1.detectProjectType)(cwd);
|
|
50
|
+
if (projectType)
|
|
51
|
+
console.log((0, colors_1.cyan)(`检测到项目类型: ${projectType}\n`));
|
|
52
|
+
fs.mkdirSync(path.join(claudeDir, 'hooks'), { recursive: true });
|
|
53
|
+
fs.mkdirSync(path.join(claudeDir, 'skills'), { recursive: true });
|
|
54
|
+
(0, utils_1.copyHooks)(claudeDir);
|
|
55
|
+
(0, utils_1.copyBuiltinSkills)('universal', claudeDir);
|
|
56
|
+
(0, utils_1.copyTemplateDir)('commands', claudeDir);
|
|
57
|
+
(0, utils_1.copyTemplateDir)('agents', claudeDir);
|
|
58
|
+
(0, utils_1.copyTemplateDir)('templates', claudeDir);
|
|
59
|
+
(0, utils_1.copyTemplateDir)('docs', claudeDir);
|
|
60
|
+
(0, utils_1.writeClaude)(cwd);
|
|
61
|
+
(0, utils_1.writeSettings)(claudeDir);
|
|
62
|
+
const config = {
|
|
63
|
+
version: '0.1.0',
|
|
64
|
+
pack: null,
|
|
65
|
+
};
|
|
66
|
+
(0, utils_1.writeAirailJson)(claudeDir, config);
|
|
67
|
+
console.log((0, colors_1.ok)('核心 hooks 已安装'));
|
|
68
|
+
console.log((0, colors_1.ok)('通用技能已安装'));
|
|
69
|
+
console.log((0, colors_1.ok)('斜杠命令已安装'));
|
|
70
|
+
console.log((0, colors_1.ok)('文档模板已安装'));
|
|
71
|
+
console.log((0, colors_1.ok)('\nairail 初始化完成!'));
|
|
72
|
+
console.log((0, colors_1.warn)('提示:执行 airail add <git-url|路径> 安装规范包。'));
|
|
73
|
+
console.log((0, colors_1.warn)('提示:在 Claude Code 中输入 /init-docs 让 AI 分析项目代码并自动填充规范文档。'));
|
|
74
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.cmdStatus = cmdStatus;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const utils_1 = require("../utils");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const colors_1 = require("./colors");
|
|
41
|
+
async function cmdStatus() {
|
|
42
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
43
|
+
if (!fs.existsSync(path.join(claudeDir, 'airail.json'))) {
|
|
44
|
+
console.log((0, colors_1.warn)('未初始化,请先执行 "airail init"。'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const config = (0, utils_1.readAirailJson)(claudeDir);
|
|
48
|
+
console.log((0, colors_1.cyan)(`airail v${config.version}`));
|
|
49
|
+
// 只显示已安装的 skills
|
|
50
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
51
|
+
if (!fs.existsSync(skillsDir))
|
|
52
|
+
return;
|
|
53
|
+
const skills = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
54
|
+
.filter(e => e.isDirectory())
|
|
55
|
+
.map(e => e.name);
|
|
56
|
+
console.log((0, colors_1.cyan)(`\n已安装技能 (${skills.length}):`));
|
|
57
|
+
for (const s of skills) {
|
|
58
|
+
const skillFile = path.join(skillsDir, s, 'SKILL.md');
|
|
59
|
+
let desc = '';
|
|
60
|
+
if (fs.existsSync(skillFile)) {
|
|
61
|
+
const line = fs.readFileSync(skillFile, 'utf-8').split('\n').find(l => l.startsWith('description:'));
|
|
62
|
+
desc = line?.replace('description:', '').trim() || '';
|
|
63
|
+
}
|
|
64
|
+
console.log(` ${(0, colors_1.gray)(s.padEnd(28))} ${desc}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.cmdUpdate = cmdUpdate;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const utils_1 = require("../utils");
|
|
39
|
+
const colors_1 = require("./colors");
|
|
40
|
+
async function cmdUpdate() {
|
|
41
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
42
|
+
const config = (0, utils_1.requireAirailJson)(claudeDir);
|
|
43
|
+
if (!config.pack) {
|
|
44
|
+
console.error((0, colors_1.err)('未安装任何 pack,无需更新。'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
// 从 airail.json 读取 pack 来源信息(需要扩展 AirailJson 结构)
|
|
48
|
+
// 暂时简化:提示用户重新 add
|
|
49
|
+
console.error((0, colors_1.err)('update 功能开发中,请使用 airail add 重新安装。'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
package/dist/index.js
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PKG_ROOT = void 0;
|
|
37
|
+
exports.fetchText = fetchText;
|
|
38
|
+
exports.readAirailJson = readAirailJson;
|
|
39
|
+
exports.writeAirailJson = writeAirailJson;
|
|
40
|
+
exports.requireAirailJson = requireAirailJson;
|
|
41
|
+
exports.readRc = readRc;
|
|
42
|
+
exports.writeRc = writeRc;
|
|
43
|
+
exports.installPackFromDir = installPackFromDir;
|
|
44
|
+
exports.installSkillsFromDir = installSkillsFromDir;
|
|
45
|
+
exports.copyBuiltinSkills = copyBuiltinSkills;
|
|
46
|
+
exports.removeNonUniversalSkills = removeNonUniversalSkills;
|
|
47
|
+
exports.copyDir = copyDir;
|
|
48
|
+
exports.copyHooks = copyHooks;
|
|
49
|
+
exports.copyTemplateDir = copyTemplateDir;
|
|
50
|
+
exports.writeSettings = writeSettings;
|
|
51
|
+
exports.writeClaude = writeClaude;
|
|
52
|
+
exports.detectProjectType = detectProjectType;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const os = __importStar(require("os"));
|
|
56
|
+
const https = __importStar(require("https"));
|
|
57
|
+
const http = __importStar(require("http"));
|
|
58
|
+
exports.PKG_ROOT = path.join(__dirname, '..');
|
|
59
|
+
// ─── HTTP 工具 ────────────────────────────────────────────────────────────────
|
|
60
|
+
function fetchText(url, token) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const mod = url.startsWith('https') ? https : http;
|
|
63
|
+
const headers = { 'User-Agent': 'airail-cli' };
|
|
64
|
+
if (token)
|
|
65
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
66
|
+
mod.get(url, { headers }, (res) => {
|
|
67
|
+
if (res.statusCode === 401 || res.statusCode === 403) {
|
|
68
|
+
reject(new Error(`访问被拒绝 (${res.statusCode}),请检查 token 是否正确。`));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (res.statusCode === 404) {
|
|
72
|
+
reject(new Error(`资源不存在 (404): ${url}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (res.statusCode !== 200) {
|
|
76
|
+
reject(new Error(`请求失败 (${res.statusCode}): ${url}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const chunks = [];
|
|
80
|
+
res.on('data', (c) => chunks.push(c));
|
|
81
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
82
|
+
res.on('error', reject);
|
|
83
|
+
}).on('error', reject);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function readAirailJson(claudeDir) {
|
|
87
|
+
const p = path.join(claudeDir, 'airail.json');
|
|
88
|
+
return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
89
|
+
}
|
|
90
|
+
function writeAirailJson(claudeDir, data) {
|
|
91
|
+
fs.writeFileSync(path.join(claudeDir, 'airail.json'), JSON.stringify(data, null, 2));
|
|
92
|
+
}
|
|
93
|
+
function requireAirailJson(claudeDir) {
|
|
94
|
+
if (!fs.existsSync(path.join(claudeDir, 'airail.json'))) {
|
|
95
|
+
console.error('未初始化,请先执行 "airail init"。');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
return readAirailJson(claudeDir);
|
|
99
|
+
}
|
|
100
|
+
// ─── ~/.airailrc ──────────────────────────────────────────────────────────────
|
|
101
|
+
const RC_PATH = path.join(os.homedir(), '.airailrc');
|
|
102
|
+
function readRc() {
|
|
103
|
+
if (!fs.existsSync(RC_PATH))
|
|
104
|
+
return {};
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(fs.readFileSync(RC_PATH, 'utf-8'));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function writeRc(patch) {
|
|
113
|
+
const current = readRc();
|
|
114
|
+
fs.writeFileSync(RC_PATH, JSON.stringify({ ...current, ...patch }, null, 2));
|
|
115
|
+
}
|
|
116
|
+
// ─── pack 安装 ────────────────────────────────────────────────────────────────
|
|
117
|
+
/**
|
|
118
|
+
* 安装 pack:
|
|
119
|
+
* - skills/ → .claude/skills/(带 packName 前缀)
|
|
120
|
+
* - commands/agents/hooks/templates/ → .claude/ 对应目录(全量覆盖)
|
|
121
|
+
*/
|
|
122
|
+
function installPackFromDir(packDir, packName, claudeDir) {
|
|
123
|
+
// 删除旧 pack 的 skills
|
|
124
|
+
removeNonUniversalSkills(claudeDir);
|
|
125
|
+
// 安装新 pack 的 skills
|
|
126
|
+
installSkillsFromDir(packDir, packName, claudeDir);
|
|
127
|
+
// 覆盖 commands / agents / hooks / templates
|
|
128
|
+
for (const sub of ['commands', 'agents', 'hooks', 'templates']) {
|
|
129
|
+
const src = path.join(packDir, sub);
|
|
130
|
+
const dest = path.join(claudeDir, sub);
|
|
131
|
+
if (fs.existsSync(src)) {
|
|
132
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
133
|
+
copyDir(src, dest);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ─── skills ───────────────────────────────────────────────────────────────────
|
|
138
|
+
/** 将 pack/skills/ 平铺复制到 .claude/skills/(universal 不加前缀) */
|
|
139
|
+
function installSkillsFromDir(packDir, packName, claudeDir) {
|
|
140
|
+
const skillsSrc = path.join(packDir, 'skills');
|
|
141
|
+
if (!fs.existsSync(skillsSrc)) {
|
|
142
|
+
console.warn(`规范包 "${packName}" 中未找到 skills/ 目录。`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const skillsDest = path.join(claudeDir, 'skills');
|
|
146
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
147
|
+
for (const entry of fs.readdirSync(skillsSrc, { withFileTypes: true })) {
|
|
148
|
+
if (!entry.isDirectory())
|
|
149
|
+
continue;
|
|
150
|
+
const destName = packName === 'universal' ? entry.name : `${packName}-${entry.name}`;
|
|
151
|
+
copyDir(path.join(skillsSrc, entry.name), path.join(skillsDest, destName));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function copyBuiltinSkills(packName, claudeDir) {
|
|
155
|
+
const src = path.join(exports.PKG_ROOT, 'skills', packName);
|
|
156
|
+
if (!fs.existsSync(src)) {
|
|
157
|
+
console.warn(`内置规范包 "${packName}" 不存在。`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const skillsDest = path.join(claudeDir, 'skills');
|
|
161
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
162
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
163
|
+
if (!entry.isDirectory())
|
|
164
|
+
continue;
|
|
165
|
+
const destName = packName === 'universal' ? entry.name : `${packName}-${entry.name}`;
|
|
166
|
+
copyDir(path.join(src, entry.name), path.join(skillsDest, destName));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** 删除所有非 universal 的 skills(即带 - 前缀的) */
|
|
170
|
+
function removeNonUniversalSkills(claudeDir) {
|
|
171
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
172
|
+
if (!fs.existsSync(skillsDir))
|
|
173
|
+
return;
|
|
174
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
175
|
+
if (!entry.isDirectory())
|
|
176
|
+
continue;
|
|
177
|
+
if (entry.name.includes('-')) {
|
|
178
|
+
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// ─── 内置模板工具 ──────────────────────────────────────────────────────────────
|
|
183
|
+
function copyDir(src, dest) {
|
|
184
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
185
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
186
|
+
const s = path.join(src, entry.name);
|
|
187
|
+
const d = path.join(dest, entry.name);
|
|
188
|
+
entry.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function copyHooks(claudeDir) {
|
|
192
|
+
const src = path.join(exports.PKG_ROOT, 'src', 'templates', 'hooks');
|
|
193
|
+
const dest = path.join(claudeDir, 'hooks');
|
|
194
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
195
|
+
for (const f of fs.readdirSync(src)) {
|
|
196
|
+
fs.copyFileSync(path.join(src, f), path.join(dest, f));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** init 时复制模板目录(不覆盖已有文件) */
|
|
200
|
+
function copyTemplateDir(subDir, claudeDir) {
|
|
201
|
+
const src = path.join(exports.PKG_ROOT, 'src', 'templates', subDir);
|
|
202
|
+
if (!fs.existsSync(src))
|
|
203
|
+
return;
|
|
204
|
+
const dest = path.join(claudeDir, subDir);
|
|
205
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
206
|
+
for (const f of fs.readdirSync(src)) {
|
|
207
|
+
const destFile = path.join(dest, f);
|
|
208
|
+
if (!fs.existsSync(destFile))
|
|
209
|
+
fs.copyFileSync(path.join(src, f), destFile);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function writeSettings(claudeDir) {
|
|
213
|
+
const dest = path.join(claudeDir, 'settings.json');
|
|
214
|
+
if (fs.existsSync(dest))
|
|
215
|
+
return; // 不覆盖已有文件
|
|
216
|
+
const tpl = path.join(exports.PKG_ROOT, 'src', 'templates', 'settings.json');
|
|
217
|
+
fs.copyFileSync(tpl, dest);
|
|
218
|
+
}
|
|
219
|
+
function writeClaude(projectDir) {
|
|
220
|
+
const tpl = path.join(exports.PKG_ROOT, 'src', 'templates', 'CLAUDE.md');
|
|
221
|
+
const dest = path.join(projectDir, 'CLAUDE.md');
|
|
222
|
+
if (!fs.existsSync(dest))
|
|
223
|
+
fs.copyFileSync(tpl, dest);
|
|
224
|
+
}
|
|
225
|
+
function detectProjectType(cwd) {
|
|
226
|
+
if (fs.existsSync(path.join(cwd, 'pom.xml')))
|
|
227
|
+
return 'spring-boot';
|
|
228
|
+
if (fs.existsSync(path.join(cwd, 'go.mod')))
|
|
229
|
+
return 'go';
|
|
230
|
+
if (fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
231
|
+
try {
|
|
232
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
|
|
233
|
+
if (pkg.dependencies?.next)
|
|
234
|
+
return 'nextjs';
|
|
235
|
+
if (pkg.dependencies?.vue)
|
|
236
|
+
return 'vue';
|
|
237
|
+
if (pkg.dependencies?.react)
|
|
238
|
+
return 'react';
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "airail",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI coding assistant framework - enforce coding standards via Claude hooks & skills",
|
|
5
|
+
"bin": {
|
|
6
|
+
"airail": "./bin/airail.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"dist/",
|
|
12
|
+
"skills/",
|
|
13
|
+
"src/templates/"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"release": "node ../../scripts/release.js",
|
|
19
|
+
"release:dry": "node ../../scripts/release.js --dry"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@inquirer/prompts": "^7.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.4.0",
|
|
26
|
+
"@types/node": "^20.0.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"claude",
|
|
33
|
+
"ai",
|
|
34
|
+
"coding-standards",
|
|
35
|
+
"hooks",
|
|
36
|
+
"skills"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT"
|
|
39
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-quality
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
pack: universal
|
|
5
|
+
description: 通用代码质量规范,DRY/KISS/命名规范
|
|
6
|
+
triggers:
|
|
7
|
+
- 代码质量
|
|
8
|
+
- 重构
|
|
9
|
+
- 命名
|
|
10
|
+
- DRY
|
|
11
|
+
- 重复代码
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# 代码质量规范
|
|
15
|
+
|
|
16
|
+
## 核心原则
|
|
17
|
+
|
|
18
|
+
**KISS**:能简单就不复杂,三行能解决的不写十行。
|
|
19
|
+
|
|
20
|
+
**DRY**:发现重复代码立即提取,零容忍 copy-paste。
|
|
21
|
+
|
|
22
|
+
**YAGNI**:不为假设的未来需求写代码,只解决当前问题。
|
|
23
|
+
|
|
24
|
+
## 命名规范
|
|
25
|
+
|
|
26
|
+
- 变量/函数:描述"做什么",不描述"怎么做"
|
|
27
|
+
- 布尔值:用 `is/has/can/should` 前缀
|
|
28
|
+
- 函数:动词开头(`getUser`, `createOrder`, `validateInput`)
|
|
29
|
+
- 常量:全大写下划线(`MAX_RETRY_COUNT`)
|
|
30
|
+
|
|
31
|
+
## 禁止事项
|
|
32
|
+
|
|
33
|
+
- 禁止魔法数字,提取为命名常量
|
|
34
|
+
- 禁止注释掉的废弃代码,直接删除
|
|
35
|
+
- 禁止无意义的注释(`// 循环遍历数组`)
|
|
36
|
+
- 禁止超过 3 层的嵌套,提前 return 或提取函数
|
|
37
|
+
- 函数超过 30 行考虑拆分
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
pack: universal
|
|
5
|
+
description: 代码审查清单
|
|
6
|
+
triggers:
|
|
7
|
+
- code review
|
|
8
|
+
- 代码审查
|
|
9
|
+
- 检查代码
|
|
10
|
+
- review
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# 代码审查清单
|
|
14
|
+
|
|
15
|
+
## 功能正确性
|
|
16
|
+
|
|
17
|
+
- [ ] 逻辑是否正确,边界条件是否处理
|
|
18
|
+
- [ ] 错误路径是否有处理
|
|
19
|
+
- [ ] 是否有遗漏的业务场景
|
|
20
|
+
|
|
21
|
+
## 代码质量
|
|
22
|
+
|
|
23
|
+
- [ ] 有无重复代码(DRY)
|
|
24
|
+
- [ ] 命名是否清晰
|
|
25
|
+
- [ ] 函数是否单一职责
|
|
26
|
+
- [ ] 是否有不必要的复杂度
|
|
27
|
+
|
|
28
|
+
## 安全
|
|
29
|
+
|
|
30
|
+
- [ ] 用户输入是否验证
|
|
31
|
+
- [ ] 是否有硬编码密钥
|
|
32
|
+
- [ ] 权限控制是否正确
|
|
33
|
+
|
|
34
|
+
## 性能
|
|
35
|
+
|
|
36
|
+
- [ ] 是否有 N+1 查询
|
|
37
|
+
- [ ] 大数据量是否有分页
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-handling
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
pack: universal
|
|
5
|
+
description: 错误处理规范,异常捕获和错误传递
|
|
6
|
+
triggers:
|
|
7
|
+
- 错误处理
|
|
8
|
+
- 异常
|
|
9
|
+
- try catch
|
|
10
|
+
- error
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# 错误处理规范
|
|
14
|
+
|
|
15
|
+
## 核心原则
|
|
16
|
+
|
|
17
|
+
- 只在系统边界处理错误(HTTP 入口、外部 API 调用、文件 IO)
|
|
18
|
+
- 内部函数抛出错误,不要吞掉
|
|
19
|
+
- 错误信息要有上下文,不要只写 "error occurred"
|
|
20
|
+
|
|
21
|
+
## 模式
|
|
22
|
+
|
|
23
|
+
**不要吞掉错误:**
|
|
24
|
+
```js
|
|
25
|
+
// 错误
|
|
26
|
+
try { ... } catch (e) {}
|
|
27
|
+
|
|
28
|
+
// 正确
|
|
29
|
+
try { ... } catch (e) { throw new Error(`Failed to process order ${id}: ${e.message}`); }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**区分可恢复和不可恢复错误:**
|
|
33
|
+
- 可恢复(用户输入错误、网络超时):返回错误响应
|
|
34
|
+
- 不可恢复(数据库连接失败):让程序崩溃,由进程管理器重启
|
|
35
|
+
|
|
36
|
+
## 禁止事项
|
|
37
|
+
|
|
38
|
+
- 禁止空 catch 块
|
|
39
|
+
- 禁止对不可能发生的场景加错误处理(过度防御)
|
|
40
|
+
- 禁止在日志中打印完整堆栈到生产环境
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-workflow
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
pack: universal
|
|
5
|
+
description: Git 工作流规范,commit 格式和分支策略
|
|
6
|
+
triggers:
|
|
7
|
+
- git
|
|
8
|
+
- commit
|
|
9
|
+
- 分支
|
|
10
|
+
- 提交
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Git 工作流规范
|
|
14
|
+
|
|
15
|
+
## Commit 格式
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
<type>(<scope>): <description>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
类型:`feat` / `fix` / `refactor` / `docs` / `test` / `chore`
|
|
22
|
+
|
|
23
|
+
示例:
|
|
24
|
+
- `feat(auth): add JWT refresh token`
|
|
25
|
+
- `fix(order): correct total price calculation`
|
|
26
|
+
|
|
27
|
+
## 分支策略
|
|
28
|
+
|
|
29
|
+
- `main`:生产代码,禁止直接推送
|
|
30
|
+
- `feat/<name>`:新功能
|
|
31
|
+
- `fix/<name>`:Bug 修复
|
|
32
|
+
|
|
33
|
+
## 禁止事项
|
|
34
|
+
|
|
35
|
+
- 禁止 `git push --force` 到 main/master
|
|
36
|
+
- 禁止提交 `.env`、密钥、凭证文件
|
|
37
|
+
- 禁止在 commit message 中加 AI 署名
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: performance
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
pack: universal
|
|
5
|
+
description: 性能优化原则
|
|
6
|
+
triggers:
|
|
7
|
+
- 性能
|
|
8
|
+
- 慢查询
|
|
9
|
+
- 优化
|
|
10
|
+
- N+1
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# 性能优化规范
|
|
14
|
+
|
|
15
|
+
## 数据库
|
|
16
|
+
|
|
17
|
+
- 查询必须走索引,避免全表扫描
|
|
18
|
+
- 禁止 N+1 查询,用 JOIN 或批量查询
|
|
19
|
+
- 分页查询必须有 LIMIT,禁止无限制查询
|
|
20
|
+
|
|
21
|
+
## 缓存
|
|
22
|
+
|
|
23
|
+
- 热点数据加缓存,设置合理 TTL
|
|
24
|
+
- 缓存 key 要有命名空间,避免冲突
|
|
25
|
+
- 写操作后主动失效相关缓存
|
|
26
|
+
|
|
27
|
+
## 代码层面
|
|
28
|
+
|
|
29
|
+
- 循环内禁止数据库查询
|
|
30
|
+
- 大数据量用流式处理,不要一次性加载到内存
|
|
31
|
+
- 先测量再优化,不要凭感觉优化
|