@softspark/ai-toolkit 1.3.13 → 1.3.15
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/CHANGELOG.md +34 -0
- package/LICENSE +1 -1
- package/README.md +59 -18
- package/app/ARCHITECTURE.md +2 -1
- package/app/agents/backend-specialist.md +8 -0
- package/app/agents/code-reviewer.md +9 -0
- package/app/agents/database-architect.md +8 -0
- package/app/agents/debugger.md +8 -0
- package/app/agents/devops-implementer.md +8 -0
- package/app/agents/documenter.md +8 -0
- package/app/agents/frontend-specialist.md +8 -0
- package/app/agents/performance-optimizer.md +8 -0
- package/app/agents/security-auditor.md +25 -1
- package/app/agents/test-engineer.md +9 -0
- package/app/skills/analyze/SKILL.md +15 -0
- package/app/skills/api-patterns/SKILL.md +10 -0
- package/app/skills/ci-cd-patterns/SKILL.md +10 -0
- package/app/skills/clean-code/SKILL.md +10 -0
- package/app/skills/cve-scan/SKILL.md +134 -0
- package/app/skills/cve-scan/scripts/cve_scan.py +412 -0
- package/app/skills/database-patterns/SKILL.md +10 -0
- package/app/skills/debug/SKILL.md +16 -0
- package/app/skills/docs/SKILL.md +16 -0
- package/app/skills/git-mastery/SKILL.md +10 -0
- package/app/skills/onboard/SKILL.md +15 -0
- package/app/skills/performance-profiling/SKILL.md +10 -0
- package/app/skills/plan/SKILL.md +16 -0
- package/app/skills/refactor/SKILL.md +16 -0
- package/app/skills/review/SKILL.md +58 -3
- package/app/skills/security-patterns/SKILL.md +10 -0
- package/app/skills/tdd/SKILL.md +6 -0
- package/app/skills/testing-patterns/SKILL.md +10 -0
- package/app/skills/workflow/SKILL.md +3 -3
- package/kb/reference/architecture-overview.md +20 -3
- package/kb/reference/skills-catalog.md +60 -3
- package/llms-full.txt +81 -6
- package/manifest.json +3 -3
- package/package.json +2 -2
- package/scripts/check_deps.py +52 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CVE dependency scanner — detect ecosystems and run native audit tools.
|
|
3
|
+
|
|
4
|
+
Stdlib only. No external dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
ECOSYSTEMS = {
|
|
15
|
+
"npm": {
|
|
16
|
+
"manifests": ["package.json"],
|
|
17
|
+
"locks": ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
|
|
18
|
+
"tool": "npm",
|
|
19
|
+
"audit_cmd": ["npm", "audit", "--json"],
|
|
20
|
+
"fix_cmd": ["npm", "audit", "fix"],
|
|
21
|
+
"install_hint": "npm is bundled with Node.js",
|
|
22
|
+
},
|
|
23
|
+
"pip": {
|
|
24
|
+
"manifests": ["requirements.txt", "pyproject.toml", "setup.py", "setup.cfg"],
|
|
25
|
+
"locks": ["requirements.txt"],
|
|
26
|
+
"tool": "pip-audit",
|
|
27
|
+
"audit_cmd": ["pip-audit", "--format=json", "--output=-"],
|
|
28
|
+
"fix_cmd": ["pip-audit", "--fix"],
|
|
29
|
+
"install_hint": "pip install pip-audit",
|
|
30
|
+
},
|
|
31
|
+
"composer": {
|
|
32
|
+
"manifests": ["composer.json"],
|
|
33
|
+
"locks": ["composer.lock"],
|
|
34
|
+
"tool": "composer",
|
|
35
|
+
"audit_cmd": ["composer", "audit", "--format=json"],
|
|
36
|
+
"fix_cmd": ["composer", "update"],
|
|
37
|
+
"install_hint": "https://getcomposer.org/download/",
|
|
38
|
+
},
|
|
39
|
+
"cargo": {
|
|
40
|
+
"manifests": ["Cargo.toml"],
|
|
41
|
+
"locks": ["Cargo.lock"],
|
|
42
|
+
"tool": "cargo-audit",
|
|
43
|
+
"audit_cmd": ["cargo", "audit", "--json"],
|
|
44
|
+
"fix_cmd": ["cargo", "audit", "fix"],
|
|
45
|
+
"install_hint": "cargo install cargo-audit",
|
|
46
|
+
},
|
|
47
|
+
"go": {
|
|
48
|
+
"manifests": ["go.mod"],
|
|
49
|
+
"locks": ["go.sum"],
|
|
50
|
+
"tool": "govulncheck",
|
|
51
|
+
"audit_cmd": ["govulncheck", "-json", "./..."],
|
|
52
|
+
"fix_cmd": ["go", "get", "-u", "./..."],
|
|
53
|
+
"install_hint": "go install golang.org/x/vuln/cmd/govulncheck@latest",
|
|
54
|
+
},
|
|
55
|
+
"ruby": {
|
|
56
|
+
"manifests": ["Gemfile"],
|
|
57
|
+
"locks": ["Gemfile.lock"],
|
|
58
|
+
"tool": "bundle-audit",
|
|
59
|
+
"audit_cmd": ["bundle-audit", "check", "--format=json"],
|
|
60
|
+
"fix_cmd": ["bundle-audit", "update"],
|
|
61
|
+
"install_hint": "gem install bundler-audit",
|
|
62
|
+
},
|
|
63
|
+
"dart": {
|
|
64
|
+
"manifests": ["pubspec.yaml"],
|
|
65
|
+
"locks": ["pubspec.lock"],
|
|
66
|
+
"tool": "dart",
|
|
67
|
+
"audit_cmd": ["dart", "pub", "outdated", "--json"],
|
|
68
|
+
"fix_cmd": ["dart", "pub", "upgrade"],
|
|
69
|
+
"install_hint": "https://dart.dev/get-dart",
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def detect_ecosystems(root: Path) -> list[dict]:
|
|
75
|
+
"""Detect which package ecosystems are present in the project."""
|
|
76
|
+
found = []
|
|
77
|
+
for name, eco in ECOSYSTEMS.items():
|
|
78
|
+
detected_files = []
|
|
79
|
+
has_lock = False
|
|
80
|
+
for m in eco["manifests"]:
|
|
81
|
+
if (root / m).exists():
|
|
82
|
+
detected_files.append(m)
|
|
83
|
+
for m in eco["locks"]:
|
|
84
|
+
if (root / m).exists():
|
|
85
|
+
detected_files.append(m)
|
|
86
|
+
has_lock = True
|
|
87
|
+
# Deduplicate (requirements.txt is both manifest and lock)
|
|
88
|
+
detected_files = list(dict.fromkeys(detected_files))
|
|
89
|
+
if detected_files:
|
|
90
|
+
tool_path = shutil.which(eco["tool"])
|
|
91
|
+
found.append({
|
|
92
|
+
"name": name,
|
|
93
|
+
"files": detected_files,
|
|
94
|
+
"has_lock": has_lock,
|
|
95
|
+
"tool": eco["tool"],
|
|
96
|
+
"tool_available": tool_path is not None,
|
|
97
|
+
"install_hint": eco["install_hint"],
|
|
98
|
+
"audit_cmd": eco["audit_cmd"],
|
|
99
|
+
"fix_cmd": eco["fix_cmd"],
|
|
100
|
+
})
|
|
101
|
+
return found
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def run_audit(eco: dict, root: Path, fix: bool = False) -> dict:
|
|
105
|
+
"""Run the native audit command for an ecosystem."""
|
|
106
|
+
cmd = eco["fix_cmd"] if fix else eco["audit_cmd"]
|
|
107
|
+
result = {
|
|
108
|
+
"ecosystem": eco["name"],
|
|
109
|
+
"tool": eco["tool"],
|
|
110
|
+
"command": " ".join(cmd),
|
|
111
|
+
"success": False,
|
|
112
|
+
"raw_output": "",
|
|
113
|
+
"error": "",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if not eco["tool_available"]:
|
|
117
|
+
result["error"] = f"Tool '{eco['tool']}' not installed. Install: {eco['install_hint']}"
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
if not eco.get("has_lock", True) and eco["name"] in ("npm",):
|
|
121
|
+
result["warning"] = "No lock file — run `npm install` first"
|
|
122
|
+
result["success"] = True
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
proc = subprocess.run(
|
|
127
|
+
cmd,
|
|
128
|
+
cwd=str(root),
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
timeout=120,
|
|
132
|
+
)
|
|
133
|
+
result["raw_output"] = proc.stdout
|
|
134
|
+
result["error"] = proc.stderr if proc.returncode not in (0, 1) else ""
|
|
135
|
+
# npm audit returns 1 when vulnerabilities found — that's not an error
|
|
136
|
+
result["success"] = True
|
|
137
|
+
except FileNotFoundError:
|
|
138
|
+
result["error"] = f"Tool '{eco['tool']}' not found in PATH"
|
|
139
|
+
except subprocess.TimeoutExpired:
|
|
140
|
+
result["error"] = f"Audit command timed out after 120s"
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def parse_npm_audit(raw: str) -> list[dict]:
|
|
146
|
+
"""Parse npm audit JSON output into unified findings."""
|
|
147
|
+
findings = []
|
|
148
|
+
try:
|
|
149
|
+
data = json.loads(raw)
|
|
150
|
+
except (json.JSONDecodeError, ValueError):
|
|
151
|
+
return findings
|
|
152
|
+
|
|
153
|
+
vulns = data.get("vulnerabilities", {})
|
|
154
|
+
for pkg_name, info in vulns.items():
|
|
155
|
+
for via in info.get("via", []):
|
|
156
|
+
if isinstance(via, dict):
|
|
157
|
+
severity = via.get("severity", "unknown").upper()
|
|
158
|
+
findings.append({
|
|
159
|
+
"severity": severity,
|
|
160
|
+
"package": pkg_name,
|
|
161
|
+
"installed": info.get("range", "unknown"),
|
|
162
|
+
"cve": via.get("cve", [via.get("url", "N/A")]),
|
|
163
|
+
"title": via.get("title", "Unknown"),
|
|
164
|
+
"url": via.get("url", ""),
|
|
165
|
+
"fixed_in": info.get("fixAvailable", {}).get("version", "unknown") if isinstance(info.get("fixAvailable"), dict) else "unknown",
|
|
166
|
+
})
|
|
167
|
+
return findings
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def parse_pip_audit(raw: str) -> list[dict]:
|
|
171
|
+
"""Parse pip-audit JSON output into unified findings."""
|
|
172
|
+
findings = []
|
|
173
|
+
try:
|
|
174
|
+
data = json.loads(raw)
|
|
175
|
+
except (json.JSONDecodeError, ValueError):
|
|
176
|
+
return findings
|
|
177
|
+
|
|
178
|
+
for dep in data.get("dependencies", []):
|
|
179
|
+
for vuln in dep.get("vulns", []):
|
|
180
|
+
vuln_id = vuln.get("id", "N/A")
|
|
181
|
+
aliases = vuln.get("aliases", [])
|
|
182
|
+
# Use GHSA link if available, otherwise OSV
|
|
183
|
+
url = ""
|
|
184
|
+
for alias in aliases:
|
|
185
|
+
if alias.startswith("GHSA-"):
|
|
186
|
+
url = f"https://github.com/advisories/{alias}"
|
|
187
|
+
break
|
|
188
|
+
if not url:
|
|
189
|
+
url = f"https://osv.dev/vulnerability/{vuln_id}"
|
|
190
|
+
|
|
191
|
+
fix_versions = vuln.get("fix_versions", [])
|
|
192
|
+
# Determine severity from CVE ID prefix heuristic — pip-audit
|
|
193
|
+
# doesn't provide severity directly, so we mark as HIGH by default
|
|
194
|
+
severity = "HIGH"
|
|
195
|
+
|
|
196
|
+
findings.append({
|
|
197
|
+
"severity": severity,
|
|
198
|
+
"package": f"{dep['name']}@{dep['version']}",
|
|
199
|
+
"installed": dep.get("version", "unknown"),
|
|
200
|
+
"cve": vuln_id,
|
|
201
|
+
"title": vuln.get("description", "")[:120] + ("..." if len(vuln.get("description", "")) > 120 else ""),
|
|
202
|
+
"url": url,
|
|
203
|
+
"fixed_in": ", ".join(fix_versions) if fix_versions else "unknown",
|
|
204
|
+
})
|
|
205
|
+
return findings
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def parse_cargo_audit(raw: str) -> list[dict]:
|
|
209
|
+
"""Parse cargo audit JSON output into unified findings."""
|
|
210
|
+
findings = []
|
|
211
|
+
try:
|
|
212
|
+
data = json.loads(raw)
|
|
213
|
+
except (json.JSONDecodeError, ValueError):
|
|
214
|
+
return findings
|
|
215
|
+
|
|
216
|
+
for vuln in data.get("vulnerabilities", {}).get("list", []):
|
|
217
|
+
advisory = vuln.get("advisory", {})
|
|
218
|
+
pkg = vuln.get("package", {})
|
|
219
|
+
findings.append({
|
|
220
|
+
"severity": "HIGH",
|
|
221
|
+
"package": f"{pkg.get('name', 'unknown')}@{pkg.get('version', 'unknown')}",
|
|
222
|
+
"installed": pkg.get("version", "unknown"),
|
|
223
|
+
"cve": advisory.get("id", "N/A"),
|
|
224
|
+
"title": advisory.get("title", "Unknown"),
|
|
225
|
+
"url": advisory.get("url", ""),
|
|
226
|
+
"fixed_in": ", ".join(vuln.get("versions", {}).get("patched", [])) or "unknown",
|
|
227
|
+
})
|
|
228
|
+
return findings
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _table(headers: list[str], rows: list[list[str]]) -> list[str]:
|
|
232
|
+
"""Render an aligned plain-text table with box-drawing borders."""
|
|
233
|
+
widths = [len(h) for h in headers]
|
|
234
|
+
for row in rows:
|
|
235
|
+
for i, cell in enumerate(row):
|
|
236
|
+
widths[i] = max(widths[i], len(cell))
|
|
237
|
+
|
|
238
|
+
sep = "├" + "┼".join("─" * (w + 2) for w in widths) + "┤"
|
|
239
|
+
top = "┌" + "┬".join("─" * (w + 2) for w in widths) + "┐"
|
|
240
|
+
bot = "└" + "┴".join("─" * (w + 2) for w in widths) + "┘"
|
|
241
|
+
|
|
242
|
+
def fmt_row(cells: list[str]) -> str:
|
|
243
|
+
parts = []
|
|
244
|
+
for i, cell in enumerate(cells):
|
|
245
|
+
parts.append(f" {cell:<{widths[i]}} ")
|
|
246
|
+
return "│" + "│".join(parts) + "│"
|
|
247
|
+
|
|
248
|
+
lines = [top, fmt_row(headers), sep]
|
|
249
|
+
for row in rows:
|
|
250
|
+
lines.append(fmt_row(row))
|
|
251
|
+
lines.append(bot)
|
|
252
|
+
return lines
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def format_text_report(ecosystems: list[dict], results: list[dict], all_findings: list[dict]) -> str:
|
|
256
|
+
"""Format findings as a terminal-friendly report."""
|
|
257
|
+
lines = ["", "═══ CVE Scan Report ═══", ""]
|
|
258
|
+
|
|
259
|
+
# Ecosystems detected
|
|
260
|
+
eco_rows = []
|
|
261
|
+
for eco in ecosystems:
|
|
262
|
+
status = "✓" if eco["tool_available"] else "⚠ not installed"
|
|
263
|
+
files = ", ".join(eco["files"])
|
|
264
|
+
eco_rows.append([eco["name"], files, eco["tool"], status])
|
|
265
|
+
lines.extend(_table(["Ecosystem", "Files", "Tool", "Status"], eco_rows))
|
|
266
|
+
lines.append("")
|
|
267
|
+
|
|
268
|
+
# Summary
|
|
269
|
+
severity_counts = {"CRITICAL": 0, "HIGH": 0, "MODERATE": 0, "MEDIUM": 0, "LOW": 0}
|
|
270
|
+
for f in all_findings:
|
|
271
|
+
sev = f["severity"].upper()
|
|
272
|
+
if sev in severity_counts:
|
|
273
|
+
severity_counts[sev] += 1
|
|
274
|
+
else:
|
|
275
|
+
severity_counts[sev] = severity_counts.get(sev, 0) + 1
|
|
276
|
+
|
|
277
|
+
# Merge MODERATE into MEDIUM for display
|
|
278
|
+
severity_counts["MEDIUM"] += severity_counts.pop("MODERATE", 0)
|
|
279
|
+
|
|
280
|
+
total = sum(severity_counts.values())
|
|
281
|
+
summary_rows = []
|
|
282
|
+
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
|
|
283
|
+
if severity_counts.get(sev, 0) > 0:
|
|
284
|
+
summary_rows.append([sev, str(severity_counts[sev])])
|
|
285
|
+
summary_rows.append(["TOTAL", str(total)])
|
|
286
|
+
lines.extend(_table(["Severity", "Count"], summary_rows))
|
|
287
|
+
lines.append("")
|
|
288
|
+
|
|
289
|
+
if not all_findings:
|
|
290
|
+
lines.append("✓ No known vulnerabilities found.")
|
|
291
|
+
lines.append("")
|
|
292
|
+
|
|
293
|
+
# Findings by severity
|
|
294
|
+
if all_findings:
|
|
295
|
+
severity_order = {"CRITICAL": 0, "HIGH": 1, "MODERATE": 2, "MEDIUM": 2, "LOW": 3}
|
|
296
|
+
sorted_findings = sorted(all_findings, key=lambda f: severity_order.get(f["severity"].upper(), 9))
|
|
297
|
+
|
|
298
|
+
finding_rows = []
|
|
299
|
+
for f in sorted_findings:
|
|
300
|
+
sev = f["severity"].upper()
|
|
301
|
+
if sev == "MODERATE":
|
|
302
|
+
sev = "MEDIUM"
|
|
303
|
+
cve = f["cve"] if isinstance(f["cve"], str) else ", ".join(f["cve"]) if f["cve"] else "N/A"
|
|
304
|
+
title = f["title"][:60] + ("..." if len(f["title"]) > 60 else "")
|
|
305
|
+
finding_rows.append([sev, f["package"], cve, f["fixed_in"], title])
|
|
306
|
+
|
|
307
|
+
lines.extend(_table(["Severity", "Package", "CVE", "Fix", "Title"], finding_rows))
|
|
308
|
+
lines.append("")
|
|
309
|
+
|
|
310
|
+
# Detailed advisory links
|
|
311
|
+
lines.append("Advisory links:")
|
|
312
|
+
for f in sorted_findings:
|
|
313
|
+
if f.get("url"):
|
|
314
|
+
cve = f["cve"] if isinstance(f["cve"], str) else ", ".join(f["cve"]) if f["cve"] else "N/A"
|
|
315
|
+
lines.append(f" {cve}: {f['url']}")
|
|
316
|
+
lines.append("")
|
|
317
|
+
|
|
318
|
+
# Install hints for missing tools
|
|
319
|
+
missing = [e for e in ecosystems if not e["tool_available"]]
|
|
320
|
+
if missing:
|
|
321
|
+
lines.append("Missing tools:")
|
|
322
|
+
for eco in missing:
|
|
323
|
+
lines.append(f" {eco['name']}: install with: {eco['install_hint']}")
|
|
324
|
+
lines.append("")
|
|
325
|
+
|
|
326
|
+
# Warnings (non-fatal issues like missing lock files)
|
|
327
|
+
warnings = [r for r in results if r.get("warning")]
|
|
328
|
+
if warnings:
|
|
329
|
+
for r in warnings:
|
|
330
|
+
lines.append(f"⚠ {r['ecosystem']}: {r['warning']}")
|
|
331
|
+
lines.append("")
|
|
332
|
+
|
|
333
|
+
# Errors (fatal issues)
|
|
334
|
+
errors = [r for r in results if r.get("error")]
|
|
335
|
+
if errors:
|
|
336
|
+
for r in errors:
|
|
337
|
+
lines.append(f"✗ {r['ecosystem']}: {r['error']}")
|
|
338
|
+
lines.append("")
|
|
339
|
+
|
|
340
|
+
return "\n".join(lines)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def main():
|
|
344
|
+
import argparse
|
|
345
|
+
|
|
346
|
+
parser = argparse.ArgumentParser(description="CVE dependency scanner")
|
|
347
|
+
parser.add_argument("--json", action="store_true", help="JSON output")
|
|
348
|
+
parser.add_argument("--fix", action="store_true", help="Auto-fix vulnerabilities")
|
|
349
|
+
parser.add_argument("--ecosystem", type=str, help="Scan specific ecosystem only")
|
|
350
|
+
parser.add_argument("path", nargs="?", default=".", help="Project root path")
|
|
351
|
+
args = parser.parse_args()
|
|
352
|
+
|
|
353
|
+
root = Path(args.path).resolve()
|
|
354
|
+
if not root.is_dir():
|
|
355
|
+
print(f"Error: {root} is not a directory", file=sys.stderr)
|
|
356
|
+
sys.exit(1)
|
|
357
|
+
|
|
358
|
+
# Detect
|
|
359
|
+
ecosystems = detect_ecosystems(root)
|
|
360
|
+
if args.ecosystem:
|
|
361
|
+
ecosystems = [e for e in ecosystems if e["name"] == args.ecosystem]
|
|
362
|
+
|
|
363
|
+
if not ecosystems:
|
|
364
|
+
msg = "No supported package ecosystems detected."
|
|
365
|
+
if args.json:
|
|
366
|
+
print(json.dumps({"ecosystems": [], "findings": [], "message": msg}))
|
|
367
|
+
else:
|
|
368
|
+
print(msg)
|
|
369
|
+
sys.exit(0)
|
|
370
|
+
|
|
371
|
+
# Run audits
|
|
372
|
+
results = []
|
|
373
|
+
all_findings = []
|
|
374
|
+
for eco in ecosystems:
|
|
375
|
+
result = run_audit(eco, root, fix=args.fix)
|
|
376
|
+
results.append(result)
|
|
377
|
+
|
|
378
|
+
# Parse results with ecosystem-specific parsers
|
|
379
|
+
if result["success"] and result["raw_output"]:
|
|
380
|
+
parsers = {
|
|
381
|
+
"npm": parse_npm_audit,
|
|
382
|
+
"pip": parse_pip_audit,
|
|
383
|
+
"cargo": parse_cargo_audit,
|
|
384
|
+
}
|
|
385
|
+
parser = parsers.get(eco["name"])
|
|
386
|
+
if parser:
|
|
387
|
+
findings = parser(result["raw_output"])
|
|
388
|
+
for f in findings:
|
|
389
|
+
f["ecosystem"] = eco["name"]
|
|
390
|
+
all_findings.extend(findings)
|
|
391
|
+
|
|
392
|
+
# Output
|
|
393
|
+
if args.json:
|
|
394
|
+
output = {
|
|
395
|
+
"ecosystems": [{"name": e["name"], "files": e["files"], "tool_available": e["tool_available"]} for e in ecosystems],
|
|
396
|
+
"results": results,
|
|
397
|
+
"findings": all_findings,
|
|
398
|
+
"total_findings": len(all_findings),
|
|
399
|
+
}
|
|
400
|
+
print(json.dumps(output, indent=2))
|
|
401
|
+
else:
|
|
402
|
+
report = format_text_report(ecosystems, results, all_findings)
|
|
403
|
+
print(report)
|
|
404
|
+
|
|
405
|
+
# Exit code: non-zero if CRITICAL or HIGH found
|
|
406
|
+
high_or_critical = [f for f in all_findings if f["severity"].upper() in ("CRITICAL", "HIGH")]
|
|
407
|
+
if high_or_critical:
|
|
408
|
+
sys.exit(1)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
if __name__ == "__main__":
|
|
412
|
+
main()
|
|
@@ -296,3 +296,13 @@ client.create_payload_index(
|
|
|
296
296
|
| COSINE | Text embeddings | Yes |
|
|
297
297
|
| EUCLID | Image embeddings | No |
|
|
298
298
|
| DOT | When vectors pre-normalized | Yes |
|
|
299
|
+
|
|
300
|
+
## Common Rationalizations
|
|
301
|
+
|
|
302
|
+
| Excuse | Why It's Wrong |
|
|
303
|
+
|--------|----------------|
|
|
304
|
+
| "We'll add indexes later when it's slow" | Missing indexes on production tables cause outages, not slowdowns — index from design |
|
|
305
|
+
| "The ORM handles performance" | ORMs generate queries, they don't optimize them — always check the query plan |
|
|
306
|
+
| "NoSQL is faster" | NoSQL trades consistency for speed — if you need joins, use a relational DB |
|
|
307
|
+
| "We don't need migrations, we'll update the schema directly" | Direct schema changes are irreversible and untestable — migrations are the safety net |
|
|
308
|
+
| "One big table is simpler" | Denormalization without measurement creates update anomalies — normalize first, denormalize with data |
|
|
@@ -144,6 +144,16 @@ Have them talk to each other to challenge each other's theories.
|
|
|
144
144
|
Report consensus when done.
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
## Common Rationalizations
|
|
148
|
+
|
|
149
|
+
| Excuse | Why It's Wrong |
|
|
150
|
+
|--------|----------------|
|
|
151
|
+
| "It works on my machine" | Environment differences are the #1 cause of production bugs — reproduce in prod-like env |
|
|
152
|
+
| "It must be a library bug" | 95% of the time it's your code — exhaust local hypotheses first |
|
|
153
|
+
| "I'll just add more logging and wait" | Passive debugging wastes hours — form a hypothesis and test it actively |
|
|
154
|
+
| "The error message says X, so it must be X" | Error messages often describe symptoms, not root causes — trace the full chain |
|
|
155
|
+
| "It only happens sometimes, probably a fluke" | Intermittent bugs are race conditions or state leaks — they get worse, not better |
|
|
156
|
+
|
|
147
157
|
## Debug Checklist
|
|
148
158
|
|
|
149
159
|
- [ ] Identified error/symptom
|
|
@@ -152,3 +162,9 @@ Report consensus when done.
|
|
|
152
162
|
- [ ] Reproduced issue
|
|
153
163
|
- [ ] Formed hypothesis
|
|
154
164
|
- [ ] Tested fix
|
|
165
|
+
|
|
166
|
+
## Related Skills
|
|
167
|
+
- Bug fixed? → `/review` to verify the fix quality
|
|
168
|
+
- Need a regression test? → `/tdd` to write it test-first
|
|
169
|
+
- Performance issue? → `/analyze --type=complexity` for hotspot analysis
|
|
170
|
+
- Incident in production? → `/workflow incident-response` for full response
|
package/app/skills/docs/SKILL.md
CHANGED
|
@@ -113,6 +113,16 @@ Proposed
|
|
|
113
113
|
- [ ] Commit changes
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
## Common Rationalizations
|
|
117
|
+
|
|
118
|
+
| Excuse | Why It's Wrong |
|
|
119
|
+
|--------|----------------|
|
|
120
|
+
| "The code is self-documenting" | Code shows how, not why — decisions, constraints, and context need prose |
|
|
121
|
+
| "Nobody reads docs anyway" | People don't read bad docs — good docs are the first thing consulted |
|
|
122
|
+
| "I'll document it when it's stable" | Unstable code needs docs most — document intent so others can contribute |
|
|
123
|
+
| "Comments get stale" | That's an argument for maintaining docs, not skipping them |
|
|
124
|
+
| "The tests are the documentation" | Tests verify behavior but don't explain architecture, trade-offs, or setup |
|
|
125
|
+
|
|
116
126
|
## Configuration
|
|
117
127
|
|
|
118
128
|
Documentation settings in:
|
|
@@ -143,3 +153,9 @@ Create an agent team for documentation:
|
|
|
143
153
|
- Teammate 3 (documenter): "Generate README sections: installation, usage, API reference." Use Opus.
|
|
144
154
|
Teammates should NOT overlap — each owns their assigned scope.
|
|
145
155
|
```
|
|
156
|
+
|
|
157
|
+
## Related Skills
|
|
158
|
+
- Documenting an architecture decision? → `/council` for multi-perspective analysis first
|
|
159
|
+
- Need to explore the codebase? → `/explore` to understand structure before documenting
|
|
160
|
+
- Writing a PRD? → `/write-a-prd` for structured product requirements
|
|
161
|
+
- Auditing existing docs? → `/analyze` for coverage gaps
|
|
@@ -68,3 +68,13 @@ git cherry-pick --continue
|
|
|
68
68
|
- `perf:` Performance improvement
|
|
69
69
|
- `test:` Adding missing tests
|
|
70
70
|
- `chore:` Build process/auxiliary tools
|
|
71
|
+
|
|
72
|
+
## Common Rationalizations
|
|
73
|
+
|
|
74
|
+
| Excuse | Why It's Wrong |
|
|
75
|
+
|--------|----------------|
|
|
76
|
+
| "I'll clean up commits later" | Later means never — write clean commits as you go |
|
|
77
|
+
| "Force push is fine on my branch" | Others may have fetched your branch — use --force-with-lease |
|
|
78
|
+
| "One big commit is simpler" | Big commits are impossible to review, bisect, or revert — keep them atomic |
|
|
79
|
+
| "Merge conflicts mean someone else's problem" | Conflicts mean you diverged too long — rebase frequently to stay aligned |
|
|
80
|
+
| "Commit messages don't matter" | Messages are documentation — future you needs to understand why, not just what |
|
|
@@ -17,6 +17,21 @@ Guide the user through setting up the ai-toolkit in their project, including con
|
|
|
17
17
|
|
|
18
18
|
## Setup Steps
|
|
19
19
|
|
|
20
|
+
### Step 0: Intent Capture Interview
|
|
21
|
+
|
|
22
|
+
Before setting up tooling, understand the project's undocumented context. Ask the developer these questions (adapt based on what the codebase scan reveals):
|
|
23
|
+
|
|
24
|
+
1. **What's the one thing a new contributor always gets wrong?** — This reveals the biggest documentation gap
|
|
25
|
+
2. **Are there files or directories that should NOT be modified?** — Identifies protected areas (legacy, generated, vendor)
|
|
26
|
+
3. **What's the deployment model?** — Monolith, microservices, serverless, edge — shapes which agents and skills are most relevant
|
|
27
|
+
4. **Are there non-obvious constraints?** — Compliance requirements, performance budgets, browser support matrix
|
|
28
|
+
5. **What's the team's review culture?** — Strict PR reviews, trunk-based, pair programming — configures `/review` behavior
|
|
29
|
+
|
|
30
|
+
Use answers to:
|
|
31
|
+
- Customize the generated `CLAUDE.md` with project-specific warnings and conventions
|
|
32
|
+
- Select the right `--profile` (minimal/standard/strict) automatically
|
|
33
|
+
- Pre-configure relevant language rules
|
|
34
|
+
|
|
20
35
|
### Step 1: Prerequisites Check
|
|
21
36
|
- [ ] Claude Code CLI installed
|
|
22
37
|
- [ ] ai-toolkit repository cloned
|
|
@@ -57,3 +57,13 @@ Always measure -> change -> measure.
|
|
|
57
57
|
2. **Algorithm**: (O(n²) -> O(n log n))
|
|
58
58
|
3. **Memory**: (Allocation churn, GC pressure)
|
|
59
59
|
4. **Micro-optimization**: (Loop unrolling, etc.) - *Smallest Gains*
|
|
60
|
+
|
|
61
|
+
## Common Rationalizations
|
|
62
|
+
|
|
63
|
+
| Excuse | Why It's Wrong |
|
|
64
|
+
|--------|----------------|
|
|
65
|
+
| "It feels slow, let me optimize this function" | Feelings aren't data — profile first, then optimize the actual bottleneck |
|
|
66
|
+
| "We should optimize everything" | Premature optimization is the root of all evil — focus on the critical path |
|
|
67
|
+
| "Caching will fix it" | Caching masks problems and adds complexity — fix the root cause first |
|
|
68
|
+
| "It's fast enough in dev" | Dev has 1 user — production has thousands and cold caches |
|
|
69
|
+
| "We'll optimize later" | Performance debt compounds — a 100ms regression per sprint = 5s in a year |
|
package/app/skills/plan/SKILL.md
CHANGED
|
@@ -95,6 +95,16 @@ During planning:
|
|
|
95
95
|
- NO code writing
|
|
96
96
|
- NO file creation (except plan)
|
|
97
97
|
|
|
98
|
+
## Common Rationalizations
|
|
99
|
+
|
|
100
|
+
| Excuse | Why It's Wrong |
|
|
101
|
+
|--------|----------------|
|
|
102
|
+
| "We already know what to build" | Assumed requirements lead to rework — validate assumptions explicitly |
|
|
103
|
+
| "Planning is wasted time, just start coding" | Unplanned work has 3-5x more rework — 30 min planning saves days |
|
|
104
|
+
| "The requirements will change anyway" | Plans adapt — without one, you can't assess impact of changes |
|
|
105
|
+
| "It's a small feature, no plan needed" | Small features in complex systems have hidden dependencies — map them |
|
|
106
|
+
| "We'll figure it out as we go" | Discovery without structure leads to scope creep and missed edge cases |
|
|
107
|
+
|
|
98
108
|
## Next Steps
|
|
99
109
|
|
|
100
110
|
After plan approval:
|
|
@@ -108,3 +118,9 @@ Before planning:
|
|
|
108
118
|
smart_query("project template: {type}")
|
|
109
119
|
hybrid_search_kb("architecture {pattern}")
|
|
110
120
|
```
|
|
121
|
+
|
|
122
|
+
## Related Skills
|
|
123
|
+
- Plan approved? → `/orchestrate` or `/workflow` to execute with agents
|
|
124
|
+
- Need requirements first? → `/write-a-prd` for structured product requirements
|
|
125
|
+
- Want to stress-test the plan? → `/grill-me` for Socratic questioning
|
|
126
|
+
- Ready to break into issues? → `/prd-to-plan` → `/triage-issue`
|
|
@@ -81,6 +81,16 @@ Before executing:
|
|
|
81
81
|
- [ ] Tests passing
|
|
82
82
|
- [ ] Backup created
|
|
83
83
|
|
|
84
|
+
## Common Rationalizations
|
|
85
|
+
|
|
86
|
+
| Excuse | Why It's Wrong |
|
|
87
|
+
|--------|----------------|
|
|
88
|
+
| "It works, don't touch it" | Working code that's hard to maintain slows every future change |
|
|
89
|
+
| "We'll refactor it later" | Later never comes — refactor when the pain is fresh and context is loaded |
|
|
90
|
+
| "It's too risky to change" | That's exactly why it needs refactoring — risk compounds with complexity |
|
|
91
|
+
| "Just one more hack won't hurt" | Each hack makes the next one easier to justify — break the cycle now |
|
|
92
|
+
| "We need to rewrite from scratch" | Incremental refactoring is safer and delivers value continuously |
|
|
93
|
+
|
|
84
94
|
## READ BEFORE WRITE
|
|
85
95
|
|
|
86
96
|
This command analyzes and plans first.
|
|
@@ -122,3 +132,9 @@ Create an agent team for refactoring:
|
|
|
122
132
|
- Teammate 2 (backend-specialist): "Implement the refactoring changes identified by the reviewer." Use Opus.
|
|
123
133
|
Teammate 1 completes first, then Teammate 2 acts on the plan.
|
|
124
134
|
```
|
|
135
|
+
|
|
136
|
+
## Related Skills
|
|
137
|
+
- Need a safe refactor plan? → `/refactor-plan` for incremental steps as GitHub RFC
|
|
138
|
+
- Want to validate architecture? → `/analyze` for code quality metrics
|
|
139
|
+
- Need tests before refactoring? → `/tdd` to build safety net first
|
|
140
|
+
- Architecture decision needed? → `/council` for multi-perspective evaluation
|
|
@@ -117,17 +117,40 @@ After all reviewers complete:
|
|
|
117
117
|
- **Lines Added**: [+count]
|
|
118
118
|
- **Lines Removed**: [-count]
|
|
119
119
|
- **Issues Found**: [count]
|
|
120
|
+
- **Overall Confidence**: [1-10] — how confident the reviewer is in the assessment
|
|
120
121
|
|
|
121
122
|
### Findings
|
|
122
123
|
|
|
123
124
|
#### Critical
|
|
124
125
|
- **[file:line]**: [issue]
|
|
125
|
-
- [
|
|
126
|
+
- Severity: critical | Confidence: [1-10]
|
|
127
|
+
- Evidence: [specific code reference and reasoning]
|
|
126
128
|
- Suggested fix: [code]
|
|
127
129
|
|
|
128
|
-
####
|
|
130
|
+
#### Major
|
|
131
|
+
- **[file:line]**: [issue]
|
|
132
|
+
- Severity: major | Confidence: [1-10]
|
|
133
|
+
- Evidence: [specific code reference and reasoning]
|
|
134
|
+
- Suggested fix: [code]
|
|
135
|
+
|
|
136
|
+
#### Minor
|
|
137
|
+
- **[file:line]**: [issue]
|
|
138
|
+
- Severity: minor | Confidence: [1-10]
|
|
139
|
+
- Evidence: [line number + reasoning]
|
|
140
|
+
|
|
141
|
+
#### Nit
|
|
129
142
|
- **[file:line]**: [suggestion]
|
|
130
|
-
- [
|
|
143
|
+
- Severity: nit | Confidence: [1-10]
|
|
144
|
+
|
|
145
|
+
### Confidence Guide
|
|
146
|
+
|
|
147
|
+
| Score | Meaning |
|
|
148
|
+
|-------|---------|
|
|
149
|
+
| 9-10 | Certain — verified via code, tests, or documentation |
|
|
150
|
+
| 7-8 | High — strong evidence, minor assumptions |
|
|
151
|
+
| 5-6 | Medium — plausible issue, needs author confirmation |
|
|
152
|
+
| 3-4 | Low — speculative, based on patterns not proof |
|
|
153
|
+
| 1-2 | Guess — flag for discussion, don't block on this |
|
|
131
154
|
|
|
132
155
|
### Positive Notes
|
|
133
156
|
- [What's good about the code]
|
|
@@ -136,6 +159,38 @@ After all reviewers complete:
|
|
|
136
159
|
[APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION]
|
|
137
160
|
```
|
|
138
161
|
|
|
162
|
+
## Common Rationalizations
|
|
163
|
+
|
|
164
|
+
| Excuse | Why It's Wrong |
|
|
165
|
+
|--------|----------------|
|
|
166
|
+
| "Small change, quick scan is enough" | Small changes introduce subtle bugs — apply consistent review regardless of size |
|
|
167
|
+
| "Tests pass, so the code is correct" | Tests validate specific scenarios, not all behaviors — verify missing coverage |
|
|
168
|
+
| "It's just a refactor, no need for deep review" | Refactors change invariants — verify behavior preservation, not just compilation |
|
|
169
|
+
| "The author is senior, they know what they're doing" | Seniority doesn't prevent mistakes — review the code, not the person |
|
|
170
|
+
| "We're in a hurry, ship it" | Rushed reviews create tech debt that costs 10x more to fix later |
|
|
171
|
+
|
|
172
|
+
## Self-Evaluation (LLM-as-Judge)
|
|
173
|
+
|
|
174
|
+
After completing the review, perform a self-evaluation pass:
|
|
175
|
+
|
|
176
|
+
### Check for Blind Spots
|
|
177
|
+
1. **Did I verify, or assume?** — For each finding, confirm you read the actual code (not inferred from context)
|
|
178
|
+
2. **Did I miss the inverse?** — If you flagged X as a problem, did you check if NOT doing X is also a problem elsewhere?
|
|
179
|
+
3. **Did I anchor on the first issue?** — Review whether early findings biased you toward similar patterns, missing different issue classes
|
|
180
|
+
4. **Did I check the unhappy path?** — Error handling, edge cases, failure modes — not just the golden path
|
|
181
|
+
5. **Did I flag uncertainty?** — Findings with confidence < 6 should be clearly marked as "needs author input"
|
|
182
|
+
|
|
183
|
+
### Calibrate Confidence
|
|
184
|
+
- If all findings are confidence 7+, you may be overconfident — re-examine the weakest finding
|
|
185
|
+
- If any finding lacks a file:line reference, downgrade it or remove it
|
|
186
|
+
- If you found zero issues, state what you specifically checked (not "looks good")
|
|
187
|
+
|
|
139
188
|
## READ-ONLY
|
|
140
189
|
|
|
141
190
|
This skill only analyzes. It does NOT modify any files.
|
|
191
|
+
|
|
192
|
+
## Related Skills
|
|
193
|
+
- Issues found? → `/debug` to trace root causes
|
|
194
|
+
- Missing tests? → `/tdd` to add test-first coverage
|
|
195
|
+
- Security findings? → `/cve-scan` for dependency vulnerabilities
|
|
196
|
+
- Architecture concerns? → `/analyze` for deeper code quality metrics
|