REPOSITORY
via/asw-reference-ros2-kit Private
[참조 MVP] A-SW ROS2 참조 Kit — ROS2 토픽과 유사한 메시지 봉투로 인지→계획→제어 연결 방법을 보여주는 참조 Kit입니다.
https://git.agrithing.ai/via/asw-reference-ros2-kit.git 277c3f0 feat: adopt cpp TOOLING SDV reference implementation
d085d43 ci: add language-specific static and dynamic analysis
b7d2f98 docs: define component scope and interface contract
17e12a8 ci: verify contract tests and evidence package
0061666 feat: add executable reference MVP
9526ba0 docs: define component scope and interface contract
1 items · main
·/ tools/quality_scan.py
python · 4.8 KB · e7c3d31 Download
from __future__ import annotationsimport argparseimport jsonimport subprocessfrom pathlib import PathROOT = Path(__file__).resolve().parents[1]LANGUAGE = "cpp"STATIC_ADAPTER = "g++-warning-policy"DYNAMIC_ADAPTER = "g++-test-scenario"COMPILER = "g++"STANDARD = "c++17"EXTENSION = "cpp"BANNED = [["NATIVE-GETS","gets("],["NATIVE-STRCPY","strcpy("],["NATIVE-SPRINTF","sprintf("],["NATIVE-SYSTEM","system("]]def write_result(path: str, payload: dict) -> None: destination = ROOT / path destination.parent.mkdir(parents=True, exist_ok=True) destination.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")def run(command: list[str]) -> dict: completed = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False) return {"command": " ".join(command), "exitCode": completed.returncode, "stdout": completed.stdout[-4000:], "stderr": completed.stderr[-4000:]}def source_files() -> list[Path]: patterns = ["*.ts", "*.mjs"] if LANGUAGE == "typescript" else [f"*.{EXTENSION}", "*.h", "*.hpp"] files: set[Path] = set() for pattern in patterns: files.update((ROOT / "src").rglob(pattern)) if (ROOT / "include").exists(): files.update((ROOT / "include").rglob(pattern)) return sorted(files)def static_analysis() -> dict: issues: list[dict] = [] files = source_files() lines = 0 for path in files: source = path.read_text(encoding="utf-8") lines += len(source.splitlines()) for rule, token in BANNED: if token in source: issues.append({"rule": rule, "severity": "HIGH", "path": str(path.relative_to(ROOT)), "line": source[:source.index(token)].count("\n") + 1, "message": f"forbidden token: {token}"}) if LANGUAGE == "typescript": command = run(["node", "--check", "src/component.mjs"]) else: command = run([COMPILER, f"-std={STANDARD}", "-Wall", "-Wextra", "-Wpedantic", "-Werror", "-Iinclude", "-fsyntax-only", f"src/component.{EXTENSION}"]) if command["exitCode"] != 0: issues.append({"rule": "COMPILE-SYNTAX", "severity": "CRITICAL", "path": f"src/component.{EXTENSION}", "line": 0, "message": command["stderr"][-1000:]}) blocking = [issue for issue in issues if issue["severity"] in {"CRITICAL", "HIGH"}] return {"schemaVersion": "asw.quality-result/v1", "kind": "static-analysis", "adapter": STATIC_ADAPTER, "language": LANGUAGE, "state": "PASSED" if not blocking else "FAILED", "metrics": {"files": len(files), "lines": lines, "blockingIssues": len(blocking), "warnings": len(issues) - len(blocking)}, "issues": issues, "commands": [command]}def dynamic_analysis() -> dict: output = ROOT / "dist" / "component_test" output.parent.mkdir(parents=True, exist_ok=True) if LANGUAGE == "typescript": commands = [run(["node", "--test", "tests/component.test.mjs"])] else: compile_command = [COMPILER, f"-std={STANDARD}", "-Wall", "-Wextra", "-Wpedantic", "-Werror", "-Iinclude", f"src/component.{EXTENSION}", f"tests/component_test.{EXTENSION}", "-o", str(output)] commands = [run(compile_command)] if commands[0]["exitCode"] == 0: commands.append(run([str(output)])) passed = sum(item["exitCode"] == 0 for item in commands) rate = passed / len(commands) if commands else 0 return {"schemaVersion": "asw.quality-result/v1", "kind": "dynamic-analysis", "adapter": DYNAMIC_ADAPTER, "language": LANGUAGE, "state": "PASSED" if rate == 1 else "FAILED", "metrics": {"testCases": 1, "commands": len(commands), "passedCommands": passed, "passRate": rate}, "commands": commands}def summary() -> dict: static = json.loads((ROOT / "dist/quality/static-analysis.json").read_text(encoding="utf-8")) dynamic = json.loads((ROOT / "dist/quality/dynamic-analysis.json").read_text(encoding="utf-8")) return {"schemaVersion": "asw.quality-summary/v1", "language": LANGUAGE, "state": "PASSED" if static["state"] == dynamic["state"] == "PASSED" else "FAILED", "results": {"staticAnalysis": static["state"], "dynamicAnalysis": dynamic["state"]}, "metrics": {"blockingIssues": static["metrics"]["blockingIssues"], "testCases": dynamic["metrics"]["testCases"], "passRate": dynamic["metrics"]["passRate"]}}parser = argparse.ArgumentParser()parser.add_argument("mode", choices=["static", "dynamic", "summary"])parser.add_argument("--output", required=True)arguments = parser.parse_args()result = static_analysis() if arguments.mode == "static" else dynamic_analysis() if arguments.mode == "dynamic" else summary()write_result(arguments.output, result)print(json.dumps({"kind": result.get("kind", "summary"), "state": result["state"], "metrics": result.get("metrics", {})}, ensure_ascii=False))raise SystemExit(0 if result["state"] == "PASSED" else 1)