code-abyss 1.6.14 → 1.6.16
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/install.js +25 -4
- package/config/AGENTS.md +7 -7
- package/config/CLAUDE.md +3 -23
- package/package.json +1 -1
- package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
- package/skills/run_skill.py +84 -19
- package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
- package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
- package/skills/tools/gen-docs/scripts/doc_generator.py +31 -2
- package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
- package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
- package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-security/scripts/security_scanner.py +7 -1
package/bin/install.js
CHANGED
|
@@ -4,8 +4,16 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
8
|
+
const VERSION = pkg.version;
|
|
8
9
|
const HOME = os.homedir();
|
|
10
|
+
|
|
11
|
+
// ── Node.js 版本检查 ──
|
|
12
|
+
const MIN_NODE = pkg.engines?.node?.match(/(\d+)/)?.[1] || '18';
|
|
13
|
+
if (parseInt(process.versions.node) < parseInt(MIN_NODE)) {
|
|
14
|
+
console.error(`\x1b[31m✘ 需要 Node.js >= ${MIN_NODE},当前: ${process.versions.node}\x1b[0m`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
9
17
|
const SKIP = ['__pycache__', '.pyc', '.pyo', '.egg-info', '.DS_Store', 'Thumbs.db', '.git'];
|
|
10
18
|
const PKG_ROOT = fs.realpathSync(path.join(__dirname, '..'));
|
|
11
19
|
|
|
@@ -209,6 +217,10 @@ function runUninstall(tgt) {
|
|
|
209
217
|
if (!fs.existsSync(manifestPath)) { fail(`未找到安装记录: ${manifestPath}`); process.exit(1); }
|
|
210
218
|
|
|
211
219
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
220
|
+
if (manifest.manifest_version && manifest.manifest_version > 1) {
|
|
221
|
+
fail(`manifest 版本 ${manifest.manifest_version} 不兼容,请升级 code-abyss 后再卸载`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
212
224
|
divider(`卸载 Code Abyss v${manifest.version}`);
|
|
213
225
|
|
|
214
226
|
(manifest.installed || []).forEach(f => {
|
|
@@ -245,7 +257,7 @@ function installCore(tgt) {
|
|
|
245
257
|
{ src: 'skills', dest: 'skills' }
|
|
246
258
|
].filter(f => f.dest !== null);
|
|
247
259
|
|
|
248
|
-
const manifest = { version: VERSION, target: tgt, timestamp: new Date().toISOString(), installed: [], backups: [] };
|
|
260
|
+
const manifest = { manifest_version: 1, version: VERSION, target: tgt, timestamp: new Date().toISOString(), installed: [], backups: [] };
|
|
249
261
|
|
|
250
262
|
filesToInstall.forEach(({ src, dest }) => {
|
|
251
263
|
const srcPath = path.join(PKG_ROOT, src);
|
|
@@ -349,6 +361,7 @@ async function installCcline(ctx) {
|
|
|
349
361
|
const { execSync } = require('child_process');
|
|
350
362
|
const cclineDir = path.join(HOME, '.claude', 'ccline');
|
|
351
363
|
const cclineBin = path.join(cclineDir, process.platform === 'win32' ? 'ccline.exe' : 'ccline');
|
|
364
|
+
const errors = [];
|
|
352
365
|
|
|
353
366
|
// 1. 检测是否已有二进制
|
|
354
367
|
let hasBin = fs.existsSync(cclineBin);
|
|
@@ -366,8 +379,9 @@ async function installCcline(ctx) {
|
|
|
366
379
|
else {
|
|
367
380
|
try { execSync('ccline --version', { stdio: 'pipe' }); hasBin = true; ok('ccline 安装成功 (全局)'); } catch (e) {}
|
|
368
381
|
}
|
|
382
|
+
if (!hasBin) errors.push('ccline 二进制安装后仍未检测到');
|
|
369
383
|
} catch (e) {
|
|
370
|
-
|
|
384
|
+
errors.push(`npm install -g @cometix/ccline 失败: ${e.message}`);
|
|
371
385
|
info(`手动安装: ${c.cyn('npm install -g @cometix/ccline')}`);
|
|
372
386
|
info(`或下载: ${c.cyn('https://github.com/Haleclipse/CCometixLine/releases')}`);
|
|
373
387
|
}
|
|
@@ -392,7 +406,7 @@ async function installCcline(ctx) {
|
|
|
392
406
|
// 无打包配置,回退到 ccline --init
|
|
393
407
|
if (hasBin && !fs.existsSync(targetConfig)) {
|
|
394
408
|
try { execSync('ccline --init', { stdio: 'inherit' }); ok('ccline 默认配置已生成'); }
|
|
395
|
-
catch (e) {
|
|
409
|
+
catch (e) { errors.push(`ccline --init 失败: ${e.message}`); }
|
|
396
410
|
}
|
|
397
411
|
}
|
|
398
412
|
|
|
@@ -401,6 +415,13 @@ async function installCcline(ctx) {
|
|
|
401
415
|
ok(`statusLine → ${c.cyn(CCLINE_STATUS_LINE.statusLine.command)}`);
|
|
402
416
|
fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
|
|
403
417
|
|
|
418
|
+
// 5. 汇总报告
|
|
419
|
+
if (errors.length > 0) {
|
|
420
|
+
console.log('');
|
|
421
|
+
warn(c.b(`ccline 安装有 ${errors.length} 个问题:`));
|
|
422
|
+
errors.forEach(e => fail(` ${e}`));
|
|
423
|
+
}
|
|
424
|
+
|
|
404
425
|
console.log('');
|
|
405
426
|
warn(`需要 ${c.b('Nerd Font')} 字体才能正确显示图标`);
|
|
406
427
|
info(`推荐: FiraCode Nerd Font / JetBrainsMono Nerd Font`);
|
package/config/AGENTS.md
CHANGED
|
@@ -130,15 +130,15 @@
|
|
|
130
130
|
|
|
131
131
|
## 十二、多 Agent 协同
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
详见 `skills/orchestration/multi-agent/SKILL.md`(唯一权威定义)。
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
核心决策:满足**任意 1 条**即启用 TeamCreate:
|
|
136
|
+
- 涉及 ≥3 个独立文件/模块
|
|
137
|
+
- 需要 ≥2 个并行工作流
|
|
138
|
+
- 总步骤 >10 步
|
|
139
|
+
- 魔尊明确要求并行/团队
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
**铁律**:犹豫时优先 TeamCreate | 每文件同一时刻仅一个 Agent 可改。
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
package/config/CLAUDE.md
CHANGED
|
@@ -180,6 +180,8 @@
|
|
|
180
180
|
|
|
181
181
|
## 十三、多 Agent 协同
|
|
182
182
|
|
|
183
|
+
详细协同规范见 `skills/orchestration/multi-agent/SKILL.md`(唯一权威定义)。
|
|
184
|
+
|
|
183
185
|
### TeamCreate vs Task(subagent) 决策树
|
|
184
186
|
|
|
185
187
|
```
|
|
@@ -195,29 +197,7 @@
|
|
|
195
197
|
└─ 简单查询/单步操作? → 直接执行
|
|
196
198
|
```
|
|
197
199
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
### 启用条件
|
|
201
|
-
|
|
202
|
-
满足**任意 1 条**即启用 TeamCreate:
|
|
203
|
-
|
|
204
|
-
| 条件 | 示例 |
|
|
205
|
-
|------|------|
|
|
206
|
-
| 多文件独立变更(≥3) | 批量创建秘典、多模块重构 |
|
|
207
|
-
| 可并行子任务(≥2) | 前端+后端、代码+文档+测试 |
|
|
208
|
-
| 复杂度高(>10步) | 全栈功能开发、大规模迁移 |
|
|
209
|
-
| 时间紧迫需加速 | 紧急多点修复 |
|
|
210
|
-
|
|
211
|
-
### 角色
|
|
212
|
-
|
|
213
|
-
| 角色 | 职责 |
|
|
214
|
-
|------|------|
|
|
215
|
-
| 主修 (Lead) | 任务分解、进度追踪、结果汇总 |
|
|
216
|
-
| 道侣 (Worker) | 执行子任务、报告进度 |
|
|
217
|
-
| 护法 (Reviewer) | 代码审查、质量校验 |
|
|
218
|
-
|
|
219
|
-
**文件锁定**:每文件同一时刻仅一个 Agent 可改,违反 = 道基裂痕+1。
|
|
220
|
-
详细协同规范见 `skills/multi-agent/SKILL.md`。
|
|
200
|
+
**铁律**:犹豫时优先 TeamCreate | 每文件同一时刻仅一个 Agent 可改,违反 = 道基裂痕+1。
|
|
221
201
|
|
|
222
202
|
---
|
|
223
203
|
|
package/package.json
CHANGED
|
Binary file
|
package/skills/run_skill.py
CHANGED
|
@@ -16,9 +16,17 @@ Skills 运行入口
|
|
|
16
16
|
|
|
17
17
|
import sys
|
|
18
18
|
import subprocess
|
|
19
|
+
import hashlib
|
|
20
|
+
import tempfile
|
|
19
21
|
from pathlib import Path
|
|
20
22
|
import os
|
|
21
23
|
|
|
24
|
+
IS_WIN = sys.platform == 'win32'
|
|
25
|
+
if IS_WIN:
|
|
26
|
+
import msvcrt
|
|
27
|
+
else:
|
|
28
|
+
import fcntl
|
|
29
|
+
|
|
22
30
|
|
|
23
31
|
def get_skills_dir() -> Path:
|
|
24
32
|
"""获取 skills 目录路径(跨平台)"""
|
|
@@ -28,32 +36,84 @@ def get_skills_dir() -> Path:
|
|
|
28
36
|
return Path(__file__).resolve().parent
|
|
29
37
|
|
|
30
38
|
|
|
39
|
+
def discover_skills(skills_dir: Path) -> dict:
|
|
40
|
+
"""自动发现 tools/ 下所有 skill(按目录结构扫描)"""
|
|
41
|
+
found = {}
|
|
42
|
+
tools_dir = skills_dir / "tools"
|
|
43
|
+
if not tools_dir.is_dir():
|
|
44
|
+
return found
|
|
45
|
+
for skill_dir in sorted(tools_dir.iterdir()):
|
|
46
|
+
if not skill_dir.is_dir():
|
|
47
|
+
continue
|
|
48
|
+
scripts_dir = skill_dir / "scripts"
|
|
49
|
+
if not scripts_dir.is_dir():
|
|
50
|
+
continue
|
|
51
|
+
py_files = list(scripts_dir.glob("*.py"))
|
|
52
|
+
if py_files:
|
|
53
|
+
found[skill_dir.name] = py_files[0]
|
|
54
|
+
return found
|
|
55
|
+
|
|
56
|
+
|
|
31
57
|
def get_script_path(skill_name: str) -> Path:
|
|
32
58
|
"""获取 skill 脚本路径"""
|
|
33
59
|
skills_dir = get_skills_dir()
|
|
60
|
+
available = discover_skills(skills_dir)
|
|
34
61
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"verify-security": ("tools", "security_scanner.py"),
|
|
38
|
-
"verify-change": ("tools", "change_analyzer.py"),
|
|
39
|
-
"verify-quality": ("tools", "quality_checker.py"),
|
|
40
|
-
"gen-docs": ("tools", "doc_generator.py"),
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if skill_name not in script_map:
|
|
44
|
-
available = ", ".join(script_map.keys())
|
|
62
|
+
if skill_name not in available:
|
|
63
|
+
names = ", ".join(available.keys()) if available else "(无)"
|
|
45
64
|
print(f"错误: 未知的 skill '{skill_name}'")
|
|
46
|
-
print(f"可用的 skills: {
|
|
65
|
+
print(f"可用的 skills: {names}")
|
|
47
66
|
sys.exit(1)
|
|
48
67
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
return available[skill_name]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def acquire_target_lock(args: list):
|
|
72
|
+
"""按目标路径获取文件锁,同路径串行,不同路径并行。返回 (lock_fd, lock_path) 或 (None, None)"""
|
|
73
|
+
# 从参数中提取目标路径(第一个非 flag 参数)
|
|
74
|
+
target = None
|
|
75
|
+
for a in args:
|
|
76
|
+
if not a.startswith('-'):
|
|
77
|
+
target = a
|
|
78
|
+
break
|
|
79
|
+
if not target:
|
|
80
|
+
target = os.getcwd()
|
|
81
|
+
|
|
82
|
+
target = str(Path(target).resolve())
|
|
83
|
+
lock_name = "sage_skill_" + hashlib.md5(target.encode()).hexdigest()[:12] + ".lock"
|
|
84
|
+
lock_path = Path(tempfile.gettempdir()) / lock_name
|
|
85
|
+
|
|
86
|
+
fd = open(lock_path, 'w')
|
|
87
|
+
if IS_WIN:
|
|
88
|
+
import time
|
|
89
|
+
for _ in range(300): # 最多等 30s
|
|
90
|
+
try:
|
|
91
|
+
msvcrt.locking(fd.fileno(), msvcrt.LK_NBLCK, 1)
|
|
92
|
+
return fd, lock_path
|
|
93
|
+
except OSError:
|
|
94
|
+
time.sleep(0.1)
|
|
95
|
+
print(f"⏳ 等待锁超时: {target}")
|
|
96
|
+
msvcrt.locking(fd.fileno(), msvcrt.LK_LOCK, 1)
|
|
97
|
+
else:
|
|
98
|
+
try:
|
|
99
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
100
|
+
except OSError:
|
|
101
|
+
print(f"⏳ 等待锁释放: {target}")
|
|
102
|
+
fcntl.flock(fd, fcntl.LOCK_EX) # 阻塞等待
|
|
103
|
+
return fd, lock_path
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def release_lock(fd):
|
|
107
|
+
"""释放文件锁"""
|
|
108
|
+
if not fd:
|
|
109
|
+
return
|
|
110
|
+
try:
|
|
111
|
+
if IS_WIN:
|
|
112
|
+
msvcrt.locking(fd.fileno(), msvcrt.LK_UNLCK, 1)
|
|
113
|
+
else:
|
|
114
|
+
fcntl.flock(fd, fcntl.LOCK_UN)
|
|
115
|
+
finally:
|
|
116
|
+
fd.close()
|
|
57
117
|
|
|
58
118
|
|
|
59
119
|
def main():
|
|
@@ -70,6 +130,9 @@ def main():
|
|
|
70
130
|
script_path = get_script_path(skill_name)
|
|
71
131
|
args = sys.argv[2:]
|
|
72
132
|
|
|
133
|
+
# 按目标路径加锁,防止多 Agent 同时操作同一目录
|
|
134
|
+
lock_fd, lock_path = acquire_target_lock(args)
|
|
135
|
+
|
|
73
136
|
# 使用 sys.executable 确保使用当前 Python 解释器
|
|
74
137
|
cmd = [sys.executable, str(script_path)] + args
|
|
75
138
|
|
|
@@ -82,6 +145,8 @@ def main():
|
|
|
82
145
|
except Exception as e:
|
|
83
146
|
print(f"执行错误: {e}")
|
|
84
147
|
sys.exit(1)
|
|
148
|
+
finally:
|
|
149
|
+
release_lock(lock_fd)
|
|
85
150
|
|
|
86
151
|
|
|
87
152
|
if __name__ == "__main__":
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
import sys
|
|
9
10
|
import json
|
|
10
11
|
import ast
|
|
@@ -125,13 +126,41 @@ def analyze_module(path: Path) -> ModuleInfo:
|
|
|
125
126
|
if language == 'Python':
|
|
126
127
|
return analyze_python_module(path)
|
|
127
128
|
|
|
128
|
-
#
|
|
129
|
+
# 通用分析(regex fallback 提取函数/类)
|
|
129
130
|
info = ModuleInfo(name=path.name, path=str(path), language=language)
|
|
130
131
|
|
|
132
|
+
lang_patterns = {
|
|
133
|
+
'Go': (r'^\s*func\s+(\w+)', r'^\s*type\s+(\w+)\s+struct\b'),
|
|
134
|
+
'Rust': (r'^\s*(?:pub\s+)?fn\s+(\w+)', r'^\s*(?:pub\s+)?struct\s+(\w+)'),
|
|
135
|
+
'TypeScript': (r'^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)', r'^\s*(?:export\s+)?class\s+(\w+)'),
|
|
136
|
+
'JavaScript': (r'^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)', r'^\s*(?:export\s+)?class\s+(\w+)'),
|
|
137
|
+
'Java': (r'^\s*(?:public|private|protected)?\s*(?:static\s+)?\w+\s+(\w+)\s*\(', r'^\s*(?:public\s+)?class\s+(\w+)'),
|
|
138
|
+
'C++': (r'^\s*(?:\w+\s+)+(\w+)\s*\([^;]*$', r'^\s*class\s+(\w+)'),
|
|
139
|
+
'C': (r'^\s*(?:\w+\s+)+(\w+)\s*\([^;]*$', None),
|
|
140
|
+
}
|
|
141
|
+
|
|
131
142
|
code_extensions = {'.py', '.go', '.rs', '.ts', '.js', '.java', '.c', '.cpp'}
|
|
143
|
+
func_pat, cls_pat = lang_patterns.get(language, (None, None))
|
|
144
|
+
|
|
132
145
|
for f in path.rglob('*'):
|
|
133
146
|
if f.is_file() and f.suffix.lower() in code_extensions:
|
|
134
|
-
|
|
147
|
+
rel = str(f.relative_to(path))
|
|
148
|
+
info.files.append(rel)
|
|
149
|
+
|
|
150
|
+
if func_pat or cls_pat:
|
|
151
|
+
try:
|
|
152
|
+
content = f.read_text(encoding='utf-8', errors='ignore')
|
|
153
|
+
for line in content.split('\n'):
|
|
154
|
+
if func_pat:
|
|
155
|
+
m = re.match(func_pat, line)
|
|
156
|
+
if m and not m.group(1).startswith('_'):
|
|
157
|
+
info.functions.append({"name": m.group(1), "file": rel, "doc": ""})
|
|
158
|
+
if cls_pat:
|
|
159
|
+
m = re.match(cls_pat, line)
|
|
160
|
+
if m and not m.group(1).startswith('_'):
|
|
161
|
+
info.classes.append({"name": m.group(1), "file": rel, "doc": ""})
|
|
162
|
+
except Exception:
|
|
163
|
+
continue
|
|
135
164
|
|
|
136
165
|
return info
|
|
137
166
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -95,7 +95,8 @@ SECURITY_RULES = [
|
|
|
95
95
|
"id": "HARDCODED_SECRET",
|
|
96
96
|
"category": "敏感信息",
|
|
97
97
|
"severity": Severity.HIGH,
|
|
98
|
-
"pattern": r'(password|passwd|pwd|secret|api_key|apikey|token|
|
|
98
|
+
"pattern": r'(?<!\w)(password|passwd|pwd|secret|api_key|apikey|token|auth_token)\s*=\s*["\'][^"\']{8,}["\']',
|
|
99
|
+
"exclude_pattern": r'(example|placeholder|changeme|xxx|your[_-]|TODO|FIXME|<.*>|\*{3,})',
|
|
99
100
|
"extensions": [".py", ".js", ".ts", ".go", ".java", ".php", ".rb", ".yaml", ".yml", ".json", ".env"],
|
|
100
101
|
"message": "可能存在硬编码密钥/密码",
|
|
101
102
|
"recommendation": "使用环境变量或密钥管理服务"
|
|
@@ -245,6 +246,11 @@ def scan_file(file_path: Path, rules: List[Dict]) -> List[Finding]:
|
|
|
245
246
|
continue
|
|
246
247
|
|
|
247
248
|
if pattern.search(line):
|
|
249
|
+
# 排除已知安全模式(如 placeholder、example)
|
|
250
|
+
exclude = rule.get("exclude_pattern")
|
|
251
|
+
if exclude and re.search(exclude, line, re.IGNORECASE):
|
|
252
|
+
continue
|
|
253
|
+
|
|
248
254
|
findings.append(Finding(
|
|
249
255
|
severity=rule["severity"],
|
|
250
256
|
category=rule["category"],
|