Initial release: airsdb

This commit is contained in:
admin
2026-05-18 11:45:06 +08:00
commit f3464e0006
8 changed files with 1271 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
{
"name": "airsdb",
"version": "0.1.1",
"description": "Cppcheck static-analysis workflow for local and remote C/C++ code quality, security-relevant findings, exhaustive branch-analysis detail, AirDbg or AirDo handoff reports, and staticanalysis.md maintenance.",
"author": {
"name": "14816",
"email": "noreply@example.com",
"url": "https://airlongdian.fun/plugins/airsdb"
},
"homepage": "https://airlongdian.fun/plugins/airsdb",
"repository": "http://git.airlongdian.fun/admin/airsdb.git",
"license": "MIT",
"keywords": [
"airsdb",
"cppcheck",
"static-analysis",
"c",
"cpp",
"code-quality",
"security",
"airdbg",
"airdo"
],
"skills": "./skills/",
"interface": {
"displayName": "AirSDB",
"shortDescription": "Cppcheck static analysis with exhaustive local and remote reports",
"longDescription": "AirSDB helps Codex run cppcheck static analysis for C or C++ projects, detect or auto-configure cppcheck on first startup, run local scans using compile_commands.json when available, default local and remote scans to --check-level=exhaustive for maximum branch-analysis detail, run remote scans over SSH with remote cppcheck auto-configuration when possible, write detailed XML or JSON artifacts under AirPlan/state/airsdb/reports, maintain concise AirPlan/docs/staticanalysis.md reports for AI context, and hand static-analysis evidence to AirDbg and AirDo.",
"developerName": "14816",
"category": "Productivity",
"capabilities": [
"Interactive",
"Write"
],
"websiteURL": "https://airlongdian.fun/plugins/airsdb",
"privacyPolicyURL": "https://openai.com/policies/row-privacy-policy/",
"termsOfServiceURL": "https://openai.com/policies/row-terms-of-use/",
"defaultPrompt": [
"Use AirSDB to run cppcheck and capture detailed static-analysis evidence for a local C or C++ codebase.",
"Use AirSDB to prepare or run a remote cppcheck scan over SSH and bring the findings back into the project context.",
"Use AirSDB to provide static-analysis evidence to AirDbg or AirDo before we change C or C++ code."
],
"brandColor": "#0F766E",
"screenshots": []
}
}

77
commands/airsdb.md Normal file
View File

@@ -0,0 +1,77 @@
---
description: Enter, inspect, run, or remote-run AirSDB cppcheck static analysis with exhaustive branch checking
argument-hint: [enter|setup|status|scan|command|remote-setup|remote-status|remote-command|remote-scan|exit]
allowed-tools: [Read, Glob, Grep, Bash, Write, Edit]
---
# /airsdb
控制当前工作区的 AirSDB 静态分析模式。
用户传入参数:`$ARGUMENTS`
- `enter` 或空参数:进入 AirSDB 模式,初始化 `AirPlan/docs/staticanalysis.md` 并检测 cppcheck。
- `setup`:检测/自动配置本机 cppcheck。
- `status`:检查 AirSDB 状态和本机 cppcheck不自动安装。
- `command`:生成本机 cppcheck 命令,不实际执行。
- `scan`:运行本机 cppcheck生成 XML/JSON 并更新 `AirPlan/docs/staticanalysis.md`
- `remote-setup`:检查远程 SSH 和远端 cppcheck缺失时自动尝试配置。
- `remote-status`:只检查远程配置和工具状态,不自动安装。
- `remote-command`:生成远程 cppcheck 命令,不实际执行。
- `remote-scan`:通过 SSH 在远端运行 cppcheck拉回报告并更新 `AirPlan/docs/staticanalysis.md`
- `exit`:退出 AirSDB 模式。
## 执行步骤
1. 解析 `$ARGUMENTS`,默认动作为 `enter`
2. `enter` / `setup` / `status` / `exit` 时运行:
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_mode.py" --mode <enter|setup|status|exit> --project .
```
如果 `python` 不存在,尝试 `py``python3` 或用户提供的 Python 绝对路径。
3. 本机 `command` / `scan` 时运行:
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_cppcheck.py" --project . --action <command|scan>
```
本机 scan 会优先使用 `compile_commands.json`,否则扫描当前项目并排除常见噪声目录;默认带 `--check-level=exhaustive` 以提供尽可能详细的分支分析信息。结果写入 `AirPlan/state/airsdb/reports/`,摘要写入 `AirPlan/docs/staticanalysis.md`
4. 如果 `$ARGUMENTS` 包含 `remote``ssh``远程`或用户说明目标代码在远程设备、测试机、服务器、VM、容器宿主机或 SSH 主机上,优先运行远程 helper
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_remote_device.py" --project . --action <setup|status|command|scan>
```
首次缺少远程配置时helper 会生成 `AirPlan/state/airsdb/remote-device.env.example` 并提示设置 `AIRSDB_REMOTE_SSH_TARGET``AIRSDB_REMOTE_PROJECT`。有 SSH 目标后helper 会探测远端 cppcheck缺失时自动尝试安装只使用非交互式 `sudo -n`,无法自动配置时停止并提示用户。远程 scan 同样默认带 `--check-level=exhaustive`
5. 每次 scan 后必须检查并维护:
- `AirPlan/docs/staticanalysis.md`
- `AirPlan/state/airsdb/reports/<timestamp>-cppcheck.xml`
- `AirPlan/state/airsdb/reports/<timestamp>-cppcheck.json`
- 必要时更新 `AGENTS.md`、ADR、C4 module
## 输出文案
进入模式:
```text
AirSDB 模式已开启:已初始化或检查 `AirPlan/docs/staticanalysis.md`,并检测 cppcheck。可运行本机 scan也可在远程目标上使用 remote device helper 自动检测/配置 cppcheck本机和远程 scan 默认使用 `--check-level=exhaustive`。
```
扫描完成:
```text
AirSDB 静态分析完成XML/JSON 报告已写入 `AirPlan/state/airsdb/reports/`,简短 AI 上下文已追加到 `AirPlan/docs/staticanalysis.md`,可交给 AirDbg 或 AirDo 使用。
```
状态检查:
```text
AirSDB 状态:<enabled|disabled>
cppcheck<ok|blocked>
关键文件:逐项列出 ok/missing。
```

320
scripts/airsdb_cppcheck.py Normal file
View File

@@ -0,0 +1,320 @@
#!/usr/bin/env python3
"""Run local cppcheck scans and maintain AirSDB reports."""
from __future__ import annotations
import argparse
import json
import os
import shlex
import subprocess
import sys
import xml.etree.ElementTree as ET
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional
from airsdb_mode import ensure_files, read_env, setup_cppcheck, stamp
DEFAULT_ENABLE = "warning,style,performance,portability,information"
DEFAULT_CHECK_LEVEL = "exhaustive"
DEFAULT_EXCLUDES = [
".git",
".airsdb",
"node_modules",
"build",
"cmake-build-debug",
"cmake-build-release",
"dist",
"out",
"vendor",
"third_party",
"external",
]
def iso() -> str:
return datetime.now(timezone.utc).isoformat()
def rel(project: Path, path: str) -> str:
if not path:
return ""
try:
return str(Path(path).resolve().relative_to(project))
except Exception:
return path
def find_project_file(project: Path, configured: str = "") -> Optional[Path]:
candidates = []
if configured:
candidates.append(Path(configured))
candidates.extend(
[
project / "compile_commands.json",
project / "build" / "compile_commands.json",
project / "cmake-build-debug" / "compile_commands.json",
project / "cmake-build-release" / "compile_commands.json",
]
)
for candidate in candidates:
p = candidate if candidate.is_absolute() else project / candidate
if p.exists():
return p.resolve()
return None
def build_args(
project: Path,
tool: str,
project_file: str = "",
target: str = "",
enable: str = DEFAULT_ENABLE,
check_level: str = DEFAULT_CHECK_LEVEL,
jobs: int = 1,
std: str = "",
extra: Optional[List[str]] = None,
) -> List[str]:
build_dir = project / "AirPlan" / "state" / "airsdb" / "cppcheck-build"
build_dir.mkdir(parents=True, exist_ok=True)
args = [
tool,
f"--enable={enable}",
f"--check-level={check_level}",
"--inconclusive",
"--inline-suppr",
"--quiet",
"--xml",
"--xml-version=2",
f"--cppcheck-build-dir={build_dir}",
]
if jobs > 1:
args.append(f"-j{jobs}")
if std:
args.append(f"--std={std}")
for item in extra or []:
if item:
args.extend(shlex.split(item, posix=(os.name != "nt")))
pf = find_project_file(project, project_file)
if pf:
args.append(f"--project={pf}")
else:
for ignored in DEFAULT_EXCLUDES:
args.append(f"-i{ignored}")
args.append(target or ".")
return args
def command_text(args: List[str]) -> str:
if os.name == "nt":
return subprocess.list2cmdline(args)
return shlex.join(args)
def extract_xml(text: str) -> str:
start = text.find("<?xml")
if start >= 0:
return text[start:]
start = text.find("<results")
if start >= 0:
return text[start:]
return text
def parse_cppcheck_xml(xml_text: str, project: Path) -> Dict[str, Any]:
xml_text = extract_xml(xml_text)
findings: List[Dict[str, Any]] = []
counts: Counter[str] = Counter()
if not xml_text.strip():
return {"counts": {}, "findings": [], "parseError": "empty_xml"}
try:
root = ET.fromstring(xml_text)
except ET.ParseError as exc:
return {"counts": {}, "findings": [], "parseError": str(exc)}
for error in root.findall(".//error"):
severity = error.attrib.get("severity", "unknown")
counts[severity] += 1
locations = []
for loc in error.findall("location"):
locations.append(
{
"file": rel(project, loc.attrib.get("file", "")),
"line": loc.attrib.get("line", ""),
"info": loc.attrib.get("info", ""),
}
)
findings.append(
{
"severity": severity,
"id": error.attrib.get("id", ""),
"msg": error.attrib.get("msg", ""),
"cwe": error.attrib.get("cwe", ""),
"locations": locations,
}
)
return {"counts": dict(counts), "findings": findings, "parseError": ""}
def top_findings(findings: List[Dict[str, Any]], limit: int = 12) -> List[str]:
rank = {"error": 0, "warning": 1, "performance": 2, "portability": 3, "style": 4, "information": 5}
def key(item: Dict[str, Any]) -> tuple[int, str]:
return (rank.get(item.get("severity", ""), 9), item.get("id", ""))
lines = []
for item in sorted(findings, key=key)[:limit]:
loc = item.get("locations") or [{}]
first = loc[0]
where = first.get("file", "")
if first.get("line"):
where += f":{first['line']}"
cwe = f" CWE-{item['cwe']}" if item.get("cwe") else ""
lines.append(f"[{item.get('severity')}:{item.get('id')}{cwe}] {where} {item.get('msg')}".strip())
return lines
def append_staticanalysis(project: Path, data: Dict[str, Any]) -> None:
ensure_files(project)
path = project / "AirPlan" / "docs" / "staticanalysis.md"
counts = data.get("counts") or {}
counts_text = ", ".join(f"{k}={v}" for k, v in sorted(counts.items())) or "none"
tops = data.get("topFindings") or []
top_text = "\n".join(f" - {line}" for line in tops) if tops else " - none"
result = "failed" if data.get("returnCode", 0) not in [0, None] else ("findings" if data.get("findingCount", 0) else "ok")
entry = (
f"\n### {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: AirSDB cppcheck\n\n"
f"- Target: {data.get('target', 'local')}\n"
f"- Tool: `{data.get('tool', '')}` {data.get('version', '')}\n"
f"- Command: `{data.get('command', '')}`\n"
f"- Result: {result}; returnCode={data.get('returnCode', '')}\n"
f"- Counts: {counts_text}\n"
f"- Reports: `{data.get('xmlReport', '')}`, `{data.get('jsonReport', '')}`\n"
f"- Top findings:\n{top_text}\n"
f"- AirDbg/AirDo handoff: {data.get('handoff', 'Review top findings before code-level repair or todo validation.')}\n"
f"- Residual risk: cppcheck is static analysis and may miss runtime, integration, configuration, or dependency issues.\n"
)
path.open("a", encoding="utf-8", newline="\n").write(entry)
def write_reports(project: Path, data: Dict[str, Any], xml_text: str, prefix: str = "cppcheck") -> Dict[str, Any]:
reports = project / "AirPlan" / "state" / "airsdb" / "reports"
reports.mkdir(parents=True, exist_ok=True)
ts = stamp()
xml_path = reports / f"{ts}-{prefix}.xml"
json_path = reports / f"{ts}-{prefix}.json"
xml_path.write_text(xml_text, encoding="utf-8", newline="\n")
data["xmlReport"] = str(xml_path)
data["jsonReport"] = str(json_path)
json_path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n")
return data
def run_scan(args: argparse.Namespace) -> int:
project = Path(args.project).expanduser().resolve()
ensure_files(project)
setup = setup_cppcheck(project, auto=not args.no_auto_configure)
if setup.get("status") != "ok":
print(f"airsdb_scan_status=blocked")
for key, value in setup.items():
print(f"{key}={value}")
return 2
tool = setup["tool"]
env = read_env(project / "AirPlan" / "state" / "airsdb" / "tool.env")
extra = list(args.extra or [])
if env.get("AIRSDB_CPPCHECK_OPTIONS"):
extra.append(env["AIRSDB_CPPCHECK_OPTIONS"])
cmd = build_args(project, tool, args.project_file, args.target, args.enable, args.check_level, args.jobs, args.std, extra)
if args.action == "command":
print("airsdb_command_status=ok")
print(f"command={command_text(cmd)}")
return 0
try:
run = subprocess.run(cmd, cwd=project, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=args.timeout)
except subprocess.TimeoutExpired as exc:
data = {
"target": "local",
"status": "timeout",
"tool": tool,
"version": setup.get("version", ""),
"checkLevel": args.check_level,
"command": command_text(cmd),
"returnCode": "timeout",
"stdout": exc.stdout or "",
"stderr": exc.stderr or "",
"capturedAt": iso(),
"counts": {},
"findings": [],
"findingCount": 0,
"topFindings": [],
}
data = write_reports(project, data, str(exc.stderr or ""), "cppcheck-timeout")
append_staticanalysis(project, data)
print("airsdb_scan_status=timeout")
print(f"jsonReport={data['jsonReport']}")
return 1
xml_text = extract_xml(run.stderr or "")
parsed = parse_cppcheck_xml(xml_text, project)
findings = parsed.get("findings", [])
data = {
"target": "local",
"status": "ok" if run.returncode == 0 else "failed",
"tool": tool,
"version": setup.get("version", ""),
"checkLevel": args.check_level,
"command": command_text(cmd),
"returnCode": run.returncode,
"stdout": run.stdout.strip(),
"stderrPreview": (run.stderr or "")[:2000],
"capturedAt": iso(),
"counts": parsed.get("counts", {}),
"findings": findings,
"findingCount": len(findings),
"topFindings": top_findings(findings, args.limit),
"parseError": parsed.get("parseError", ""),
}
data = write_reports(project, data, xml_text, "cppcheck")
append_staticanalysis(project, data)
print(f"airsdb_scan_status={data['status']}")
print(f"findingCount={data['findingCount']}")
print(f"xmlReport={data['xmlReport']}")
print(f"jsonReport={data['jsonReport']}")
return 0 if run.returncode == 0 else 1
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Run local AirSDB cppcheck analysis.")
parser.add_argument("--project", default=".")
parser.add_argument("--action", choices=["setup", "status", "command", "scan"], default="scan")
parser.add_argument("--project-file", default="")
parser.add_argument("--target", default="")
parser.add_argument("--enable", default=DEFAULT_ENABLE)
parser.add_argument("--check-level", default=DEFAULT_CHECK_LEVEL)
parser.add_argument("--std", default="")
parser.add_argument("--jobs", type=int, default=1)
parser.add_argument("--timeout", type=int, default=900)
parser.add_argument("--limit", type=int, default=12)
parser.add_argument("--extra", action="append", default=[])
parser.add_argument("--no-auto-configure", action="store_true")
return parser.parse_args()
def main() -> None:
args = parse_args()
project = Path(args.project).expanduser().resolve()
if args.action in ["setup", "status"]:
ensure_files(project)
setup = setup_cppcheck(project, auto=(args.action == "setup" and not args.no_auto_configure))
print(f"airsdb_cppcheck_status={setup.get('status', 'unknown')}")
for key, value in setup.items():
if key != "status":
print(f"{key}={value}")
raise SystemExit(0 if setup.get("status") == "ok" else 2)
raise SystemExit(run_scan(args))
if __name__ == "__main__":
main()

318
scripts/airsdb_mode.py Normal file
View File

@@ -0,0 +1,318 @@
#!/usr/bin/env python3
"""Bootstrap AirSDB static-analysis context and cppcheck tooling."""
from __future__ import annotations
import argparse
import json
import os
import platform
import shutil
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Optional, Tuple
MARKER_BEGIN = "<!-- AIRSDB:BEGIN -->"
MARKER_END = "<!-- AIRSDB:END -->"
def now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
def stamp() -> str:
return datetime.now().strftime("%Y%m%d-%H%M%S")
def read_env(path: Path) -> Dict[str, str]:
vals: Dict[str, str] = {}
if path.exists():
for raw in path.read_text(encoding="utf-8-sig").splitlines():
line = raw.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
vals[key.strip()] = value.strip().strip('"').strip("'")
for key in ["AIRSDB_CPPCHECK", "AIRSDB_CPPCHECK_OPTIONS"]:
if os.environ.get(key):
vals[key] = os.environ[key]
return vals
def write_env(path: Path, updates: Dict[str, str]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
old = path.read_text(encoding="utf-8") if path.exists() else ""
keys = set(updates)
lines = []
for raw in old.splitlines():
key = raw.split("=", 1)[0].strip() if "=" in raw and not raw.strip().startswith("#") else None
if key not in keys:
lines.append(raw)
if lines and lines[-1].strip():
lines.append("")
for key, value in updates.items():
if value:
lines.append(f"{key}={value}")
path.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8", newline="\n")
def ensure_files(project: Path) -> None:
air = project / "AirPlan" / "state" / "airsdb"
air.mkdir(parents=True, exist_ok=True)
example = air / "tool.env.example"
if not example.exists():
example.write_text(
"# AirSDB local cppcheck configuration\n"
"AIRSDB_CPPCHECK=cppcheck\n"
"AIRSDB_CPPCHECK_OPTIONS=\n"
"# AirSDB defaults to --check-level=exhaustive for maximum branch analysis detail.\n",
encoding="utf-8",
newline="\n",
)
gitignore = air / ".gitignore"
old = gitignore.read_text(encoding="utf-8") if gitignore.exists() else ""
for item in ["tool.env", "remote-device.env", "reports/", "cppcheck-build/"]:
if item not in old.splitlines():
old = (old.rstrip() + f"\n{item}\n").lstrip()
gitignore.write_text(old, encoding="utf-8", newline="\n")
static = project / "AirPlan" / "docs" / "staticanalysis.md"
if not static.exists():
static.write_text(
"# Static Analysis\n\n"
"Short AI-context reports for AirSDB cppcheck runs. Keep entries concise.\n\n"
"## Entry Template\n\n"
"### YYYY-MM-DD HH:MM:SS: AirSDB cppcheck\n\n"
"- Target: TODO\n"
"- Tool: TODO\n"
"- Command: TODO\n"
"- Result: TODO\n"
"- Counts: TODO\n"
"- Reports: TODO\n"
"- Top findings: TODO\n"
"- AirDbg/AirDo handoff: TODO\n"
"- Residual risk: TODO\n",
encoding="utf-8",
newline="\n",
)
def airsdb_agents_block() -> str:
return f"""{MARKER_BEGIN}
## AirSDB Static Analysis Workflow
1. Use AirSDB for `/airsdb` sessions that run cppcheck static analysis for C/C++ code quality, security-relevant defects, and debugging evidence.
2. On first entry, detect cppcheck; if missing, auto-configure it with the best available package manager or stop with install instructions.
3. Maintain `AirPlan/docs/staticanalysis.md` as the short AI-context report for AirDbg and AirDo. Keep detailed XML/JSON artifacts under `AirPlan/state/airsdb/reports/`.
4. Prefer `compile_commands.json` when available; otherwise scan the narrowest useful source tree with common generated/vendor directories excluded.
5. Default local and remote cppcheck scans to `--check-level=exhaustive` so AirSDB provides the most detailed branch-analysis evidence it can.
6. For local analysis, use `airsdb_cppcheck.py`; for remote targets over SSH, use `airsdb_remote_device.py` before local analysis.
7. When remote cppcheck is missing, the remote helper may auto-configure it with non-interactive package-manager commands; if sudo/password or unsupported OS blocks it, stop and record the blocker.
8. Record command, target, report paths, counts, top findings, AirDbg/AirDo handoff, and residual risk in `AirPlan/docs/staticanalysis.md`.
9. Update ADR/C4 only when static analysis tooling becomes a durable project boundary or changes implementation decisions.
{MARKER_END}
"""
def upsert_agents_md(path: Path) -> str:
block = airsdb_agents_block().rstrip() + "\n"
if path.exists():
original = path.read_text(encoding="utf-8")
existed = True
else:
original = "# AGENTS.md\n\n"
existed = False
begin = original.find(MARKER_BEGIN)
end = original.find(MARKER_END)
if begin >= 0 and end > begin:
end += len(MARKER_END)
updated = original[:begin].rstrip() + "\n\n" + block + original[end:].lstrip()
status = "updated"
else:
updated = original.rstrip() + "\n\n" + block
status = "updated" if existed else "created"
path.write_text(updated, encoding="utf-8", newline="\n")
return status
def run_args(args: list[str], timeout: int = 600) -> subprocess.CompletedProcess[str]:
return subprocess.run(args, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout)
def run_shell(cmd: str, timeout: int = 600) -> subprocess.CompletedProcess[str]:
return subprocess.run(cmd, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout)
def cppcheck_version(tool: str) -> str:
try:
result = run_args([tool, "--version"], timeout=20)
except Exception:
return ""
text = (result.stdout or result.stderr).strip()
return text.splitlines()[0] if text else ""
def detect_cppcheck(project: Path) -> Tuple[Optional[str], str]:
env = read_env(project / "AirPlan" / "state" / "airsdb" / "tool.env")
candidates = []
configured = env.get("AIRSDB_CPPCHECK", "").strip()
if configured:
candidates.append(configured)
candidates.append("cppcheck")
if platform.system().lower() == "windows":
candidates.extend(
[
r"C:\Program Files\Cppcheck\cppcheck.exe",
r"C:\Program Files (x86)\Cppcheck\cppcheck.exe",
]
)
for candidate in candidates:
path = shutil.which(candidate) or candidate
if Path(path).exists() or shutil.which(path):
version = cppcheck_version(path)
if version:
return path, version
return None, ""
def install_cppcheck(project: Path) -> Dict[str, str]:
system = platform.system().lower()
attempts = []
if system == "windows":
package_commands = [
["winget", "install", "--id", "Cppcheck.Cppcheck", "-e", "--accept-source-agreements", "--accept-package-agreements", "--silent"],
["choco", "install", "cppcheck", "-y"],
["scoop", "install", "cppcheck"],
]
for cmd in package_commands:
exe = shutil.which(cmd[0])
if not exe:
continue
run = run_args([exe] + cmd[1:], timeout=1200)
attempts.append({"command": " ".join(cmd), "returncode": str(run.returncode)})
tool, version = detect_cppcheck(project)
if tool:
write_env(project / "AirPlan" / "state" / "airsdb" / "tool.env", {"AIRSDB_CPPCHECK": tool})
return {"status": "ok", "tool": tool, "version": version, "installer": cmd[0], "attempts": json.dumps(attempts)}
return {
"status": "blocked",
"reason": "cppcheck_not_found",
"hint": "Install Cppcheck from https://cppcheck.sourceforge.io/ or configure AIRSDB_CPPCHECK.",
"attempts": json.dumps(attempts),
}
commands = [
"if command -v apt-get >/dev/null 2>&1; then sudo -n apt-get update && sudo -n apt-get install -y cppcheck; "
"elif command -v dnf >/dev/null 2>&1; then sudo -n dnf install -y cppcheck; "
"elif command -v yum >/dev/null 2>&1; then sudo -n yum install -y cppcheck; "
"elif command -v apk >/dev/null 2>&1; then sudo -n apk add cppcheck; "
"elif command -v pacman >/dev/null 2>&1; then sudo -n pacman -Sy --noconfirm cppcheck; "
"elif command -v brew >/dev/null 2>&1; then brew install cppcheck; "
"elif command -v port >/dev/null 2>&1; then sudo -n port install cppcheck; "
"else exit 42; fi"
]
run = run_shell(commands[0], timeout=1200)
attempts.append({"command": "system-package-manager", "returncode": str(run.returncode)})
tool, version = detect_cppcheck(project)
if tool:
write_env(project / "AirPlan" / "state" / "airsdb" / "tool.env", {"AIRSDB_CPPCHECK": tool})
return {"status": "ok", "tool": tool, "version": version, "installer": "system-package-manager", "attempts": json.dumps(attempts)}
return {
"status": "blocked",
"reason": "cppcheck_not_found",
"hint": "Install cppcheck with the OS package manager or configure AIRSDB_CPPCHECK.",
"attempts": json.dumps(attempts),
}
def setup_cppcheck(project: Path, auto: bool = True) -> Dict[str, str]:
ensure_files(project)
tool, version = detect_cppcheck(project)
if tool:
write_env(project / "AirPlan" / "state" / "airsdb" / "tool.env", {"AIRSDB_CPPCHECK": tool})
return {"status": "ok", "tool": tool, "version": version, "autoConfigure": "skipped"}
if not auto:
return {"status": "blocked", "reason": "cppcheck_not_found", "example": str(project / "AirPlan" / "state" / "airsdb" / "tool.env.example")}
return install_cppcheck(project)
def artifact_map(project: Path) -> Dict[str, Path]:
return {
"AGENTS.md": project / "AirPlan" / "AGENTS.md",
"staticanalysis": project / "AirPlan" / "docs" / "staticanalysis.md",
"state": project / "AirPlan" / "state" / "airsdb" / "state.json",
"tool_env": project / "AirPlan" / "state" / "airsdb" / "tool.env",
}
def write_state(path: Path, enabled: bool, project: Path, tool_info: Dict[str, str]) -> None:
artifacts = artifact_map(project)
payload = {
"enabled": enabled,
"updatedAt": now_iso(),
"projectRoot": str(project),
"cppcheck": tool_info,
"artifactHealth": {name: p.exists() for name, p in artifacts.items() if name != "state"},
}
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n")
def enter_mode(project: Path, auto: bool = True) -> Tuple[str, Dict[str, str]]:
ensure_files(project)
results = {"AGENTS.md": upsert_agents_md(project / "AirPlan" / "AGENTS.md"), "staticanalysis": "exists"}
tool_info = setup_cppcheck(project, auto=auto)
for key, value in tool_info.items():
results[f"cppcheck_{key}"] = value
write_state(project / "AirPlan" / "state" / "airsdb" / "state.json", True, project, tool_info)
return "enabled", results
def exit_mode(project: Path) -> Tuple[str, Dict[str, str]]:
ensure_files(project)
tool_info = setup_cppcheck(project, auto=False)
write_state(project / "AirPlan" / "state" / "airsdb" / "state.json", False, project, tool_info)
return "disabled", {}
def status_mode(project: Path) -> Tuple[str, Dict[str, str]]:
ensure_files(project)
state = project / "AirPlan" / "state" / "airsdb" / "state.json"
enabled = False
if state.exists():
try:
enabled = bool(json.loads(state.read_text(encoding="utf-8")).get("enabled"))
except json.JSONDecodeError:
enabled = False
tool_info = setup_cppcheck(project, auto=False)
results = {name: ("ok" if path.exists() else "missing") for name, path in artifact_map(project).items() if name != "state"}
for key, value in tool_info.items():
results[f"cppcheck_{key}"] = value
return ("enabled" if enabled else "disabled"), results
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Manage AirSDB static-analysis artifacts.")
parser.add_argument("--mode", choices=["enter", "setup", "exit", "status"], default="enter")
parser.add_argument("--project", default=".")
parser.add_argument("--no-auto-configure", action="store_true")
return parser.parse_args()
def main() -> None:
args = parse_args()
project = Path(args.project).expanduser().resolve()
if args.mode in ["enter", "setup"]:
mode_state, result = enter_mode(project, auto=not args.no_auto_configure)
elif args.mode == "exit":
mode_state, result = exit_mode(project)
else:
mode_state, result = status_mode(project)
print(f"airsdb_mode={mode_state}")
print(f"project_root={project}")
for key, value in result.items():
print(f"{key}={value}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""Run AirSDB cppcheck on a remote SSH target."""
from __future__ import annotations
import argparse
import base64
import json
import os
import shlex
import shutil
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List
from airsdb_cppcheck import append_staticanalysis, parse_cppcheck_xml, top_findings, write_reports
from airsdb_mode import ensure_files, stamp
DEFAULT_ENABLE = "warning,style,performance,portability,information"
DEFAULT_CHECK_LEVEL = "exhaustive"
def iso() -> str:
return datetime.now(timezone.utc).isoformat()
def q(value: str) -> str:
return shlex.quote(str(value))
def split(value: str) -> List[str]:
return shlex.split(value, posix=True) if value.strip() else []
def read_env(path: Path) -> Dict[str, str]:
vals: Dict[str, str] = {}
if path.exists():
for raw in path.read_text(encoding="utf-8-sig").splitlines():
line = raw.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
vals[key.strip()] = value.strip().strip('"').strip("'")
for key in [
"AIRSDB_REMOTE_SSH_TARGET",
"AIRSDB_REMOTE_SSH_PORT",
"AIRSDB_REMOTE_SSH_OPTIONS",
"AIRSDB_REMOTE_WORKDIR",
"AIRSDB_REMOTE_PROJECT",
"AIRSDB_REMOTE_CPPCHECK",
]:
if os.environ.get(key):
vals[key] = os.environ[key]
vals.setdefault("AIRSDB_REMOTE_SSH_PORT", "22")
vals.setdefault("AIRSDB_REMOTE_CPPCHECK", "auto")
return vals
def write_env(path: Path, updates: Dict[str, str]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
old = path.read_text(encoding="utf-8") if path.exists() else ""
keys = set(updates)
lines = []
for raw in old.splitlines():
key = raw.split("=", 1)[0].strip() if "=" in raw and not raw.strip().startswith("#") else None
if key not in keys:
lines.append(raw)
if lines and lines[-1].strip():
lines.append("")
for key, value in updates.items():
if value:
lines.append(f"{key}={value}")
path.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8", newline="\n")
def ensure_remote_files(project: Path) -> None:
ensure_files(project)
air = project / "AirPlan" / "state" / "airsdb"
example = air / "remote-device.env.example"
if not example.exists():
example.write_text(
"# AirSDB remote cppcheck configuration\n"
"AIRSDB_REMOTE_SSH_TARGET=user@host\n"
"AIRSDB_REMOTE_SSH_PORT=22\n"
"AIRSDB_REMOTE_SSH_OPTIONS=\n"
"AIRSDB_REMOTE_WORKDIR=\n"
"AIRSDB_REMOTE_PROJECT=/path/to/remote/project\n"
"AIRSDB_REMOTE_CPPCHECK=auto\n",
encoding="utf-8",
newline="\n",
)
def ssh_args(env: Dict[str, str], cmd: str) -> List[str]:
target = env.get("AIRSDB_REMOTE_SSH_TARGET", "")
if not target:
raise SystemExit("AIRSDB_REMOTE_SSH_TARGET is required")
args = [shutil.which("ssh") or "ssh"]
port = env.get("AIRSDB_REMOTE_SSH_PORT", "22")
if port:
args += ["-p", port]
args += split(env.get("AIRSDB_REMOTE_SSH_OPTIONS", ""))
return args + [target, cmd]
def run(env: Dict[str, str], cmd: str, timeout: int = 60) -> subprocess.CompletedProcess[str]:
return subprocess.run(ssh_args(env, cmd), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout)
def remote_home(env: Dict[str, str]) -> str:
result = run(env, 'printf %s "$HOME"', 15)
return result.stdout.strip() if result.returncode == 0 else ""
def workdir(env: Dict[str, str]) -> str:
if env.get("AIRSDB_REMOTE_WORKDIR"):
return env["AIRSDB_REMOTE_WORKDIR"]
home = remote_home(env)
return (home.rstrip("/") + "/.airsdb") if home else ".airsdb"
def detect_tool(env: Dict[str, str]) -> str:
tool = env.get("AIRSDB_REMOTE_CPPCHECK", "auto").strip()
if tool and tool != "auto":
result = run(env, f"command -v {q(tool)} >/dev/null 2>&1 && command -v {q(tool)} || test -x {q(tool)} && printf %s {q(tool)}", 20)
return result.stdout.strip().splitlines()[-1] if result.returncode == 0 and result.stdout.strip() else ""
result = run(env, 'command -v cppcheck >/dev/null 2>&1 && command -v cppcheck', 20)
return result.stdout.strip().splitlines()[-1] if result.returncode == 0 and result.stdout.strip() else ""
def remote_version(env: Dict[str, str], tool: str) -> str:
result = run(env, f"{q(tool)} --version", 20)
text = (result.stdout or result.stderr).strip()
return text.splitlines()[0] if result.returncode == 0 and text else ""
def install_tool(env: Dict[str, str]) -> subprocess.CompletedProcess[str]:
script = """set -e
if command -v cppcheck >/dev/null 2>&1; then exit 0; fi
if [ "$(id -u 2>/dev/null || echo 1)" = "0" ]; then SUDO=""; else SUDO="sudo -n"; fi
if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update && $SUDO apt-get install -y cppcheck;
elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y cppcheck;
elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y cppcheck;
elif command -v apk >/dev/null 2>&1; then $SUDO apk add cppcheck;
elif command -v pacman >/dev/null 2>&1; then $SUDO pacman -Sy --noconfirm cppcheck;
elif command -v brew >/dev/null 2>&1; then brew install cppcheck;
elif command -v port >/dev/null 2>&1; then $SUDO port install cppcheck;
else exit 42; fi"""
return run(env, script, 1200)
def setup(project: Path, env: Dict[str, str], auto: bool = True) -> Dict[str, str]:
if not env.get("AIRSDB_REMOTE_SSH_TARGET"):
return {"status": "blocked", "reason": "missing_remote_target", "example": str(project / "AirPlan" / "state" / "airsdb" / "remote-device.env.example")}
if not shutil.which("ssh"):
return {"status": "blocked", "reason": "ssh_not_found"}
probe = run(env, "printf ok", 20)
if probe.returncode:
return {"status": "blocked", "reason": "ssh_probe_failed", "stderr": probe.stderr.strip()}
wd = workdir(env)
mk = run(env, f"mkdir -p {q(wd)} {q(wd.rstrip('/') + '/reports')} {q(wd.rstrip('/') + '/cppcheck-build')}", 30)
if mk.returncode:
return {"status": "blocked", "reason": "remote_workdir_failed", "stderr": mk.stderr.strip()}
tool = detect_tool(env)
install = "skipped"
if not tool and auto:
inst = install_tool(env)
install = "ok" if inst.returncode == 0 else f"failed:{inst.returncode}"
tool = detect_tool(env)
updates = {"AIRSDB_REMOTE_WORKDIR": wd}
if tool:
updates["AIRSDB_REMOTE_CPPCHECK"] = tool
write_env(project / "AirPlan" / "state" / "airsdb" / "remote-device.env", updates)
version = remote_version(env, tool) if tool else ""
return {
"status": "ok" if tool else "blocked",
"target": env.get("AIRSDB_REMOTE_SSH_TARGET", ""),
"workdir": wd,
"tool": tool,
"version": version,
"autoConfigure": install,
"hint": "" if tool else "Install cppcheck on the remote target or set AIRSDB_REMOTE_CPPCHECK.",
}
def scan_cmd(
env: Dict[str, str],
tool: str,
remote_project: str,
remote_xml: str,
enable: str,
check_level: str,
std: str,
extra: List[str],
) -> str:
build_dir = workdir(env).rstrip("/") + "/cppcheck-build"
report_dir = remote_xml.rsplit("/", 1)[0] if "/" in remote_xml else "."
options = [
q(tool),
f"--enable={q(enable)}",
f"--check-level={q(check_level)}",
"--inconclusive",
"--inline-suppr",
"--quiet",
"--xml",
"--xml-version=2",
f"--cppcheck-build-dir={q(build_dir)}",
]
if std:
options.append(f"--std={q(std)}")
for item in extra:
if item:
options.append(item)
return (
f"cd {q(remote_project)} && mkdir -p {q(report_dir)} {q(build_dir)} && "
"if [ -f compile_commands.json ]; then AIRSDB_PROJECT_ARG='--project=compile_commands.json'; "
"elif [ -f build/compile_commands.json ]; then AIRSDB_PROJECT_ARG='--project=build/compile_commands.json'; "
"elif [ -f cmake-build-debug/compile_commands.json ]; then AIRSDB_PROJECT_ARG='--project=cmake-build-debug/compile_commands.json'; "
"elif [ -f cmake-build-release/compile_commands.json ]; then AIRSDB_PROJECT_ARG='--project=cmake-build-release/compile_commands.json'; "
"else AIRSDB_PROJECT_ARG='.'; fi; "
+ " ".join(options)
+ f" $AIRSDB_PROJECT_ARG 2> {q(remote_xml)}; AIRSDB_RC=$?; "
'printf "airsdb_cppcheck_rc=%s\\n" "$AIRSDB_RC"; '
'printf "airsdb_project_arg=%s\\n" "$AIRSDB_PROJECT_ARG"; '
"exit 0"
)
def emit(prefix: str, data: Dict[str, object]) -> None:
print(f"{prefix}_status={data.get('status', 'unknown')}")
for key, value in data.items():
if key != "status":
print(f"{key}={json.dumps(value, ensure_ascii=False) if isinstance(value, (dict, list)) else value}")
def parse_stdout_value(stdout: str, key: str) -> str:
prefix = f"{key}="
for line in stdout.splitlines():
if line.startswith(prefix):
return line[len(prefix) :].strip()
return ""
def main() -> None:
parser = argparse.ArgumentParser(description="Run AirSDB cppcheck on a remote SSH target.")
parser.add_argument("--project", default=".")
parser.add_argument("--action", choices=["setup", "status", "command", "scan"], default="setup")
parser.add_argument("--remote-project", default="")
parser.add_argument("--enable", default=DEFAULT_ENABLE)
parser.add_argument("--check-level", default=DEFAULT_CHECK_LEVEL)
parser.add_argument("--std", default="")
parser.add_argument("--timeout", type=int, default=900)
parser.add_argument("--limit", type=int, default=12)
parser.add_argument("--extra", action="append", default=[])
parser.add_argument("--no-auto-configure", action="store_true")
args = parser.parse_args()
project = Path(args.project).expanduser().resolve()
ensure_remote_files(project)
env = read_env(project / "AirPlan" / "state" / "airsdb" / "remote-device.env")
info = setup(project, env, auto=(args.action != "status" and not args.no_auto_configure))
if args.action in ["setup", "status"] or info.get("status") != "ok":
emit("airsdb_remote", info)
raise SystemExit(0 if info.get("status") == "ok" else 2)
remote_project = args.remote_project or env.get("AIRSDB_REMOTE_PROJECT", "")
if not remote_project:
emit("airsdb_remote", {"status": "blocked", "reason": "missing_remote_project", "example": str(project / "AirPlan" / "state" / "airsdb" / "remote-device.env.example")})
raise SystemExit(2)
remote_xml = info["workdir"].rstrip("/") + "/reports/" + stamp() + "-airsdb-remote-cppcheck.xml"
cmd = scan_cmd(env, str(info["tool"]), remote_project, remote_xml, args.enable, args.check_level, args.std, args.extra)
full_command = shlex.join(ssh_args(env, cmd))
if args.action == "command":
emit("airsdb_remote", {"status": "ok", "target": info["target"], "remoteProject": remote_project, "remoteXml": remote_xml, "command": full_command})
return
run_result = run(env, cmd, args.timeout)
rc_text = parse_stdout_value(run_result.stdout, "airsdb_cppcheck_rc")
cppcheck_rc = int(rc_text) if rc_text.isdigit() else run_result.returncode
b64 = run(env, f"base64 < {q(remote_xml)}", 120)
xml_text = ""
if b64.returncode == 0 and b64.stdout.strip():
xml_text = base64.b64decode("".join(b64.stdout.split())).decode("utf-8", errors="replace")
parsed = parse_cppcheck_xml(xml_text, project)
findings = parsed.get("findings", [])
data = {
"target": f"remote:{info['target']}",
"remoteProject": remote_project,
"status": "ok" if run_result.returncode == 0 else "failed",
"tool": info.get("tool", ""),
"version": info.get("version", ""),
"checkLevel": args.check_level,
"command": full_command,
"returnCode": cppcheck_rc,
"sshReturnCode": run_result.returncode,
"stdout": run_result.stdout.strip(),
"stderr": run_result.stderr.strip(),
"remoteXml": remote_xml,
"capturedAt": iso(),
"counts": parsed.get("counts", {}),
"findings": findings,
"findingCount": len(findings),
"topFindings": top_findings(findings, args.limit),
"parseError": parsed.get("parseError", ""),
}
data = write_reports(project, data, xml_text, "remote-cppcheck")
append_staticanalysis(project, data)
emit("airsdb_remote", data)
raise SystemExit(0 if run_result.returncode == 0 else 1)
if __name__ == "__main__":
main()

166
skills/airsdb/SKILL.md Normal file
View File

@@ -0,0 +1,166 @@
---
name: airsdb
description: Cppcheck static-analysis workflow for C/C++ projects. Use when the user invokes /airsdb, asks to run static analysis, evaluate code quality or security with cppcheck, generate a short AI-context static-analysis report for AirDbg or AirDo, diagnose issues that need static analysis, maintain AirPlan/docs/staticanalysis.md, or run local/remote cppcheck over SSH. On first startup detect cppcheck and auto-install or auto-configure it when missing; default local and remote scans to `--check-level=exhaustive` for maximum branch-analysis detail; and call the AirSDB remote device helper when remote cppcheck is needed.
---
# AirSDB
## 核心约束
- 全程使用中文与用户交流,命令、路径、工具名、告警 id、CWE 保持原文。
- `/airsdb` 专用于 C/C++ 静态分析、代码质量/安全性初筛、cppcheck 证据收集,以及给 AirDbg/AirDo 提供简短 AI 上下文报告。
- 第一次进入必须检测 `cppcheck`。本机缺失时自动尝试用包管理器安装或配置;无法自动安装时停止并提示官方下载页或 `AIRSDB_CPPCHECK`
- 必须创建或维护 `AirPlan/docs/staticanalysis.md`。它只写简短摘要,详细 XML/JSON 产物放在 `AirPlan/state/airsdb/reports/`
- 本机分析使用 `../../scripts/airsdb_cppcheck.py`
- 远程设备、测试机、VM、容器宿主机、服务器或 SSH 主机上的静态分析,先使用 `../../scripts/airsdb_remote_device.py`;远端缺少 `cppcheck` 时允许脚本自动配置,无法无密码 `sudo` 或无包管理器时停止并提示用户。
- 优先使用 `compile_commands.json`;没有时只扫描最窄可行目录,并排除 `.git``AirPlan/state/airsdb``build``node_modules``vendor``third_party` 等常见噪声目录。
- 默认使用 `--check-level=exhaustive`,尽可能提供详细分支分析信息,避免出现 `normalCheckLevelMaxBranches` 这类因分支分析深度受限造成的信息缺口;只有用户明确要求降级时才改。
- Cppcheck 是静态分析,不等同于编译、测试或安全审计;结论要写成“证据/线索”,不要夸大。
- 如果需要 cppcheck 安装和命令细节,读取 [references/cppcheck-notes.md](references/cppcheck-notes.md)。
## 启动与环境检测
进入 `/airsdb` 时运行:
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_mode.py" --mode enter --project .
```
如果当前环境没有 `python`,尝试 `py``python3` 或用户提供的 Python 绝对路径。
脚本会:
- 初始化 `AirPlan/state/airsdb/``AirPlan/state/airsdb/tool.env.example``AirPlan/state/airsdb/.gitignore`
- 创建或维护 `AirPlan/docs/staticanalysis.md`
-`AirPlan/AGENTS.md` 中维护 AirSDB 标记块。
- 检测 `AIRSDB_CPPCHECK``AirPlan/state/airsdb/tool.env`、PATH 和常见 Windows 安装路径。
- 找不到 `cppcheck` 时自动尝试安装:
- Windows`winget``choco``scoop`
- Linux/macOS`apt-get``dnf``yum``apk``pacman``brew``port`
`AirPlan/state/airsdb/tool.env` 是本机路径配置,由 `AirPlan/state/airsdb/.gitignore` 忽略,不应提交。
## 本机分析
检查或安装 cppcheck
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_cppcheck.py" --project . --action setup
```
只生成命令:
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_cppcheck.py" --project . --action command
```
执行扫描:
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_cppcheck.py" --project . --action scan --timeout 900
```
常用参数:
- `--project-file build/compile_commands.json`:指定编译数据库。
- `--target src`:没有编译数据库时限制扫描目录。
- `--enable warning,style,performance,portability,information`:默认检查集合。
- `--check-level exhaustive`:默认详细分支分析级别。
- `--std c++17`:指定 C/C++ 标准。
- `--extra "--suppress=missingIncludeSystem"`:追加 cppcheck 参数。
扫描后必须确认:
- `AirPlan/state/airsdb/reports/<timestamp>-cppcheck.xml`
- `AirPlan/state/airsdb/reports/<timestamp>-cppcheck.json`
- `AirPlan/docs/staticanalysis.md` 已追加简短报告
## 远程设备分析
当目标代码或复现场景在远程设备上时,不要先跑本机 cppcheck。先运行
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_remote_device.py" --project . --action setup
```
首次运行会生成 `AirPlan/state/airsdb/remote-device.env.example`。将连接信息写入 `AirPlan/state/airsdb/remote-device.env` 或当前环境变量:
- `AIRSDB_REMOTE_SSH_TARGET=user@host`
- `AIRSDB_REMOTE_SSH_PORT=22`
- `AIRSDB_REMOTE_SSH_OPTIONS=`
- `AIRSDB_REMOTE_WORKDIR=`
- `AIRSDB_REMOTE_PROJECT=/path/to/remote/project`
- `AIRSDB_REMOTE_CPPCHECK=auto`
远程 helper 行为:
- 检查本机 `ssh`、远程连通性、远程工作目录。
- 探测远端 `cppcheck`
- 缺失时自动尝试用远端包管理器安装 `cppcheck`,只使用非交互式 `sudo -n`;需要密码、管理员确认或无支持包管理器时停止并提示用户。
- 在远端项目目录运行 cppcheck把 XML 拉回本机 `AirPlan/state/airsdb/reports/` 并更新本机 `AirPlan/docs/staticanalysis.md`
远程命令:
```bash
python "$HOME/plugins/airsdb/scripts/airsdb_remote_device.py" --project . --action command
python "$HOME/plugins/airsdb/scripts/airsdb_remote_device.py" --project . --action scan --timeout 900
```
## 与 AirDbg 协作
AirDbg 调试中遇到以下情况时调用 AirSDB
- 需要用静态分析辅助定位崩溃、内存错误、未初始化变量、空指针、越界、危险转换、资源释放或 CWE 线索。
- 需要在修复前后比较 cppcheck 结果。
- 需要给根因分析提供短报告,而不是完整 XML 噪声。
AirSDB 给 AirDbg 的交接必须写入 `AirPlan/docs/staticanalysis.md`
- 命令和目标
- XML/JSON 报告路径
- severity/id/CWE 计数
- Top findings
- 哪些 findings 与当前 bug 相关
- 剩余风险
## 与 AirDo 协作
AirDo 执行 `AirPlan/todo.md` 时可以调用 AirSDB 做验收或排障:
- todo 要求静态分析、质量检查、安全性初筛或 C/C++ 代码风险评估。
- 验证失败但需要 cppcheck 辅助定位。
- 远程设备上的实现需要远端 cppcheck 证据。
AirDo 仍然拥有 `AirPlan/todo.md` 进度。调用 AirSDB 后,把命令、报告路径、结论和剩余风险写回当前 todo 项。
## staticanalysis.md 维护
每次 AirSDB 扫描至少追加:
- Targetlocal 或 remote target
- Toolcppcheck 路径和版本
- Command实际命令
- Resultok / findings / failed
- Counts各 severity 数量
- ReportsXML/JSON 路径
- Top findings最多 12 条,含 severity、id、CWE、文件行号、摘要
- AirDbg/AirDo handoff当前任务如何使用这些结果
- Residual risk静态分析未覆盖的风险
不要把完整 XML、长日志或大段 cppcheck 输出塞进 `staticanalysis.md`
## AGENTS / ADR / C4
- 发现稳定可复用的 AirSDB 命令、远程设备配置、过滤策略、suppressions 或质量门槛时,更新 `AGENTS.md`
- 如果静态分析成为长期测试/调试边界或影响模块边界、质量策略、安全策略、CI 策略,更新 C4 module 并新增或修订 ADR。
- 如果只是一次临时扫描,只维护 `staticanalysis.md` 即可。
## 完成输出
本轮结束时用中文简洁汇报:
- 使用本机还是远程 cppcheck。
- cppcheck 是否可用,是否发生自动配置。
- 报告路径和 `staticanalysis.md` 是否更新。
- 发现数量和最重要的 3-5 条线索。
- 是否建议交给 AirDbg 修复,或交给 AirDo 写回 todo 验收。

View File

@@ -0,0 +1,3 @@
name: airsdb
short_description: Cppcheck static analysis with exhaustive local/remote reports
default_prompt: "使用 AirSDB 运行本机或远程 cppcheck 静态分析,默认启用 --check-level=exhaustive生成简短 staticanalysis.md 报告并交给 AirDbg/AirDo 使用。"

View File

@@ -0,0 +1,27 @@
# Cppcheck Notes
Use this only when AirSDB needs cppcheck install or command details.
## Sources
- Official open-source download page: https://cppcheck.sourceforge.io/
- Official repository package notes: https://github.com/danmar/cppcheck
- Official manual: https://cppcheck.sourceforge.io/manual.html
- User-provided Chinese guide: https://www.zeeklog.com/cppcheckzhong-ji-zhi-nan-cong-ling-kai-shi-zhang-wo-c-c-jing-tai-dai-ma-fen-xi
- User-provided download reference: http://cppcheck.net/#download
## Install Notes
- The official page lists current open-source releases and package-manager examples.
- Windows official installer is linked from the Cppcheck open-source page.
- Package managers can be convenient but may lag behind official releases.
- AirSDB auto-configures with package managers first because it must be non-interactive for agent workflows.
## Command Notes
- Prefer `cppcheck --project=compile_commands.json` when the project has a compilation database.
- Generate a CMake compilation database with `cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .` when appropriate.
- Use `--xml --xml-version=2` for machine-readable reports.
- Use `--cppcheck-build-dir=<path>` for incremental analysis and better whole-program analysis.
- Use `-i<path>` to skip generated/vendor directories.
- Use suppressions instead of deleting warnings when a finding is a known false positive.