Files
fn_registry/functions/infra/audit_cpp_apps.go
T

160 lines
5.2 KiB
Go

package infra
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"strings"
_ "github.com/mattn/go-sqlite3"
)
// CppAppAudit holds the standard-conformance report for a single C++ app.
type CppAppAudit struct {
AppID string `json:"app_id"`
DirPath string `json:"dir_path"`
OK bool `json:"ok"`
Issues []string `json:"issues"`
}
// AuditCppApps walks every app registered with lang='cpp' in registry.db and
// checks conformance with cpp/PATTERNS.md and .claude/rules/cpp_apps.md:
// - main.cpp exists at the root of the app dir
// - main.cpp uses fn::run_app(...) (not glfwInit directly)
// - cfg.about is declared (or .about = passed in run_app literal)
// - cfg.log is declared
// - main.cpp does NOT call fn_ui::app_menubar(nullptr,...) manually
// - main.cpp does NOT call ImGui::DockSpaceOverViewport(...) unless
// cfg.auto_dockspace = false is also set
// - app.md frontmatter declares framework: imgui and entry_point
//
// Apps whose dir does not exist on disk are skipped with a placeholder issue.
func AuditCppApps(registryRoot string) ([]CppAppAudit, error) {
dbPath := filepath.Join(registryRoot, "registry.db")
dsn := fmt.Sprintf("file:%s?mode=ro&_foreign_keys=on", dbPath)
db, err := sql.Open("sqlite3", dsn)
if err != nil {
return nil, fmt.Errorf("audit_cpp_apps: open db: %w", err)
}
defer db.Close()
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("audit_cpp_apps: ping db: %w", err)
}
rows, err := db.Query(`SELECT id, dir_path, COALESCE(framework, '') FROM apps WHERE lang = 'cpp' ORDER BY id`)
if err != nil {
return nil, fmt.Errorf("audit_cpp_apps: query apps: %w", err)
}
defer rows.Close()
var results []CppAppAudit
for rows.Next() {
var id, dir, framework string
if err := rows.Scan(&id, &dir, &framework); err != nil {
continue
}
// Solo auditar apps imgui — apps CLI/test no necesitan cfg.about/log/menubar.
if framework != "imgui" {
continue
}
audit := CppAppAudit{AppID: id, DirPath: dir}
absDir := dir
if !filepath.IsAbs(absDir) {
absDir = filepath.Join(registryRoot, dir)
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
audit.Issues = append(audit.Issues, "directory_missing")
results = append(results, audit)
continue
}
// main.cpp checks
mainPath := filepath.Join(absDir, "main.cpp")
mainBytes, err := os.ReadFile(mainPath)
if err != nil {
audit.Issues = append(audit.Issues, "main.cpp_missing_or_unreadable")
} else {
src := string(mainBytes)
if !strings.Contains(src, "fn::run_app") {
audit.Issues = append(audit.Issues, "no_fn_run_app_call")
}
if strings.Contains(src, "glfwInit(") {
audit.Issues = append(audit.Issues, "manual_glfwInit_call")
}
if !cppHasAbout(src) {
audit.Issues = append(audit.Issues, "missing_cfg_about")
}
if !cppHasLog(src) {
audit.Issues = append(audit.Issues, "missing_cfg_log")
}
if cppHasManualMenubar(src) {
audit.Issues = append(audit.Issues, "manual_app_menubar_call")
}
if cppHasManualDockSpace(src) && !cppHasAutoDockspaceFalse(src) {
audit.Issues = append(audit.Issues, "manual_DockSpaceOverViewport_without_auto_dockspace_false")
}
}
// app.md checks
appMdPath := filepath.Join(absDir, "app.md")
mdBytes, err := os.ReadFile(appMdPath)
if err != nil {
audit.Issues = append(audit.Issues, "app.md_missing")
} else {
md := string(mdBytes)
if !strings.Contains(md, "framework:") || !strings.Contains(md, "imgui") {
audit.Issues = append(audit.Issues, "app.md_missing_framework_imgui")
}
if !strings.Contains(md, "entry_point:") {
audit.Issues = append(audit.Issues, "app.md_missing_entry_point")
}
}
audit.OK = len(audit.Issues) == 0
results = append(results, audit)
}
return results, nil
}
// cppHasAbout returns true if main.cpp sets cfg.about or passes .about = in a
// run_app literal initializer.
func cppHasAbout(src string) bool {
return strings.Contains(src, "cfg.about") ||
strings.Contains(src, ".about ") ||
strings.Contains(src, ".about=") ||
strings.Contains(src, "about_window_set_info")
}
// cppHasLog returns true if main.cpp sets cfg.log or .log = in a run_app
// literal initializer.
func cppHasLog(src string) bool {
return strings.Contains(src, "cfg.log") ||
strings.Contains(src, ".log ") ||
strings.Contains(src, ".log=")
}
// cppHasManualMenubar returns true if main.cpp invokes
// fn_ui::app_menubar(...) directly. The framework already calls it once per
// frame, so any manual call is a duplicate.
func cppHasManualMenubar(src string) bool {
return strings.Contains(src, "fn_ui::app_menubar(") ||
strings.Contains(src, "app_menubar(nullptr")
}
// cppHasManualDockSpace returns true if main.cpp invokes
// ImGui::DockSpaceOverViewport(...) inside its render(). Coexists with the
// framework's auto dockspace only if cfg.auto_dockspace = false is set.
func cppHasManualDockSpace(src string) bool {
return strings.Contains(src, "DockSpaceOverViewport(")
}
// cppHasAutoDockspaceFalse returns true if main.cpp opts out of the
// framework auto-dockspace via cfg.auto_dockspace = false or
// .auto_dockspace = false.
func cppHasAutoDockspaceFalse(src string) bool {
return strings.Contains(src, "auto_dockspace = false") ||
strings.Contains(src, "auto_dockspace=false")
}