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 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 VERSION = require(path.join(__dirname, '..', 'package.json')).version;
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
- warn('npm install -g @cometix/ccline 失败');
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) { warn('ccline --init 失败,可手动运行'); }
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
- 启用条件(≥2条):文件独立 | 复杂度高(>15步) | 可并行 | 时间紧迫
133
+ 详见 `skills/orchestration/multi-agent/SKILL.md`(唯一权威定义)。
134
134
 
135
- | 角色 | 职责 |
136
- |------|------|
137
- | 主修 (Lead) | 任务分解、进度追踪、结果汇总 |
138
- | 道侣 (Worker) | 执行子任务、报告进度 |
139
- | 护法 (Reviewer) | 代码审查、质量校验 |
135
+ 核心决策:满足**任意 1 条**即启用 TeamCreate:
136
+ - 涉及 ≥3 个独立文件/模块
137
+ - 需要 ≥2 个并行工作流
138
+ - 总步骤 >10
139
+ - 魔尊明确要求并行/团队
140
140
 
141
- **文件锁定**:每文件同一时刻仅一个 Agent 可改。详见 `skills/multi-agent/SKILL.md`。
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
- **铁律**:当犹豫时,优先 TeamCreate。多 Agent 并行的效率远高于串行 subagent
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-abyss",
3
- "version": "1.6.14",
3
+ "version": "1.6.16",
4
4
  "description": "邪修红尘仙·宿命深渊 - 一键为 Claude Code / Codex CLI 注入邪修人格与安全工程知识体系",
5
5
  "keywords": [
6
6
  "claude",
@@ -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
- script_map = {
36
- "verify-module": ("tools", "module_scanner.py"),
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: {available}")
65
+ print(f"可用的 skills: {names}")
47
66
  sys.exit(1)
48
67
 
49
- category, script_name = script_map[skill_name]
50
- script_path = skills_dir / category / skill_name / "scripts" / script_name
51
-
52
- if not script_path.exists():
53
- print(f"错误: 脚本不存在 {script_path}")
54
- sys.exit(1)
55
-
56
- return script_path
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__":
@@ -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
- info.files.append(str(f.relative_to(path)))
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
 
@@ -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|auth)\s*=\s*["\'][^"\']{8,}["\']',
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"],