ooda-setup — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ooda-setup (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Entry point for new users. Run /ooda-setup after cloning OODA-loop. Runs a 3-step interactive wizard: scan → confirm domains → write config.json.
Read agent/safety/HALT. If the file exists → print [HALT] Setup blocked. Remove agent/safety/HALT to continue. and stop immediately.
If config.json already exists → back up to config.json.bak and ask:
[WARNING] config.json already exists.
(o) Overwrite — discard current config, start fresh
(m) Merge — keep existing domains and safety settings, update detected values only
(a) Abort — cancel setup
Choice (o/m/a):o → overwrite entirely (backed up to config.json.bak)m → read existing config, preserve user-modified fields (domains, safety, progressive_complexity.current_level, cost), only update auto-detected values (project.name, test_command, deploy_workflow, health_endpoints)a or anything else → abortPrint: [1/3] Scanning your project...
Detect Monorepo — before language detection, check for monorepo indicators:
package.json has "workspaces" field → npm/yarn workspaces monorepopnpm-workspace.yaml exists → pnpm monorepolerna.json exists → Lerna monorepopackages/ or apps/ directory exists with multiple sub-package.json files → probable monorepoIf monorepo detected, scan each workspace root for its own indicator files and aggregate results. Print:
Structure: monorepo ({N} packages detected)If not a monorepo, print Structure: single-package.
For monorepos, auto-detect values from the root config first, then fall back to the first workspace that has the relevant indicator. Use the root package.json scripts for test/build commands unless a workspace-level override exists.
Detect Language — use Glob to check for indicator files:
| Indicator | Language |
|---|---|
package.json | TypeScript (if *.ts files exist) or JavaScript |
go.mod | Go |
Cargo.toml | Rust |
pyproject.toml / requirements.txt | Python |
Gemfile | Ruby |
pom.xml / build.gradle | Java |
Check framework hints:
package.json: "next" → Next.js, "react" → React, "express" → Expressrequirements.txt / pyproject.toml: fastapi → FastAPI, django → Django, flask → Flaskgo.mod: gin → Gin, echo → Echo, fiber → FiberIf no indicator found → language = null. For monorepos, report the primary language (most common across packages) and note others: e.g. TypeScript (3 packages), Go (1 package).
Detect Test Command (first match wins):
package.json has scripts.test → npm testpytest.ini or conftest.py exists → pytest (enhanced: if pytest-cov found in requirements.txt or pyproject.toml dependencies, append --cov; detect main source directory from project structure for --cov=<dir>)go.mod present → go test ./...Cargo.toml present → cargo testGemfile contains rspec → bundle exec rspec"" (warn user)Detect CI — Glob .github/workflows/*.yml. If a file contains deploy in its name → deploy_workflow = that filename. If workflows exist but none named deploy → use the first one. None found → null.
Detect Health Endpoints — multi-strategy detection:
package.json scripts.start for a port, or docker-compose.yml port mappings.@app.get("/health"), @app.route("/health"), path("health/", url(r"^health")app.get('/health'), router.get('/health')HandleFunc("/health", Handle("/health"If a /health route is found, include it with the detected port.
http://localhost:3000, FastAPI/Django → http://localhost:8000, Go → http://localhost:8080.[].Detect Project Name — from package.json .name, go.mod module last segment, Cargo.toml [package] name, or current directory name.
Check Git — run git rev-parse --is-inside-work-tree 2>/dev/null. If fails → [WARN] Not a git repo. backlog domain will be disabled. Set is_git_repo = false.
Print summary:
[1/3] Scanning your project...
Language: TypeScript (Next.js)
Tests: jest (npm test)
CI: GitHub Actions (deploy.yml)
Endpoints: http://localhost:3000
Name: my-appShow (not detected) for any missing item.
Capture the mission FIRST — it is what the loop self-drives toward. This is the single most important input: an installed OODA-loop without a mission just cycles by staleness; with one, every Decide is pulled toward the project's purpose. Ask:
[2/3] In one sentence — what is this project for, and what does "done well" mean?
(e.g. "Keep the live URL shortener fast and reliable and ship the launch
backlog." This steers every cycle. Press Enter to skip — not recommended.)Write the answer to config.mission (empty string if skipped — scoring then falls back to staleness-only, with a printed warning that the loop has no purpose to drive toward).
Then, for EACH domain enabled below, set mission_alignment in [0.0, 1.0] — how much working that domain advances the stated mission. Infer a sensible default from the mission text and the domain's role (a domain the mission names directly → ~1.0; a generally-useful domain → ~0.6; an off-mission/curiosity domain like competitors for an internal tool → ~0.1), then show the inferred values and let the operator adjust:
service_health alignment 1.0 (mission mentions "reliable")
backlog alignment 1.0 (mission mentions "ship the backlog")
competitors alignment 0.2 (not on the critical path)
Adjust any? (e.g. "competitors 0.0", or Enter to accept)Print:
[2/3] Recommended domains:
✓ service_health (weight 2.0) — always recommended
✓ test_coverage (weight 0.5) — if test command detected
✓ backlog (weight 0.3) — if git repo detected
? business_strategy (weight 1.0) — optional
? ux_evolution (weight 1.0) — optional
? competitors (weight 0.3) — optional
Which domains to enable? (Enter for recommended, or list names separated by spaces)Defaults: service_health always; test_coverage if test_command detected; backlog if is_git_repo = true.
Read input: empty → use defaults. Space-separated names → enable exactly those. Validate against known domain names; re-ask once if invalid.
Print: [3/3] Creating config.json...
Locate the config template. Search in order (first found wins):
${CLAUDE_PLUGIN_ROOT}/config.example.json (plugin installation)~/.ooda-loop/config.example.json (global git clone installation)./config.example.json (running from the OODA-loop repo itself)If none found → generate a minimal config with sensible defaults (all fields from the schema with default values). Print [WARN] config.example.json not found — using built-in defaults.
Apply detected values:
project.name → detected nametest_command → detected command or ""deploy_workflow → detected filename or nullhealth_endpoints → detected array or []progressive_complexity.current_level → 0enabled = true if in user-chosen list, false otherwiseGraceful degradation:
What language does your project use?"", print [WARN] Set test_command in config.json manually.[INFO] deploy_workflow set to null.Validate the JSON structure before writing. Write to config.json. Never write tokens, keys, or passwords — skip any detected value that looks like a secret and warn.
Scaffold project directories — create the OODA runtime directories if they don't exist:
mkdir -p agent/state/evolve
mkdir -p agent/safetyInitialize state files: state.json, confidence.json, metrics.json, action_queue.json, memos.json (with "score_adjustments": {}, "interventions": [], "history": []), goals.json, episodes.json, principles.json, skill_gaps.json, reflections.json ({"schema_version": "1.0.0", "reflections": []}), outcomes.json ({"schema_version": "1.0.0", "entries": []}), cost_ledger.json, CHANGELOG.md in agent/state/evolve/. Use canonical schemas. (cascades.json, cycle_log.jsonl, and per-domain lens.json are created lazily by evolve.)
Auto-derive verifiable goals FROM the mission (v1.4.1, Iteration 6). Don't make the operator hand-write done-conditions — propose them from the mission text + the detected stack, then let them confirm. This is the "state your purpose, the loop figures out how to measure it" install flow. Map mission phrases to concrete metric_commands, e.g.:
| mission says… | proposed goal + metric_command | |
|---|---|---|
| "green tests" / "reliable" / "correctness" | g_tests: {test_command} exit 0 (or coverage ≥ N from test_coverage.json) | |
| "ship the backlog" / "close issues" | g_backlog: `jq '.pending\ | length==0' agent/state/evolve/action_queue.json` |
| "fast" / "p95 latency" / "uptime" | g_health: jq '.status=="healthy"' agent/state/service_health.json | |
| (a named feature/MVP) | g_mvp: a milestone the operator pastes a check for |
Present 1–3 derived goals: Proposed done-conditions from your mission: … (Enter to accept, or edit). Write the accepted ones to goals.json as active goals with progress: 0.0. If the mission was skipped or nothing maps, fall through to the manual prompt below.
Or seed at least one verifiable goal manually (loop-engineering done-condition). A loop with no written, machine-checkable goal "runs until the money runs out." Write goals.json with one active goal whose metric_command is a shell command that prints a number or boolean the engine can check each cycle (Step 2-C drives goal.progress from it, and the Loop Scorecard's Goal Progress KPI surfaces it):
{ "schema_version": "1.0.0", "goals": [
{ "id": "g1", "title": "<your done-condition, e.g. drain the action backlog>",
"status": "active", "progress": 0.0, "target": 1.0,
"metric_command": "jq '.pending|length==0' agent/state/evolve/action_queue.json" } ] }Ask the operator for one goal during setup; if they skip, write an empty { "goals": [] } and print a reminder that the loop has no done-condition yet.
Add to .gitignore if not already present:
config.json
agent/safety/HALT
agent/state/**/*.lock
agent/state/evolve/.lockDo NOT gitignore agent/state/ itself: evolve Step 6-D deliberately commits the state JSONs (decision history, confidence, episodes — the auditable memory of the loop). Ignoring the directory silently turns every 6-D commit into a no-op and the project loses its own learning trail (issue #31). Only the transient lock files (and the HALT kill-switch) stay untracked.
Print:
[3/3] Setup complete!
Created: config.json
Level: 0 (Just watching)
Domains: service_health, test_coverage, backlog
Next steps:
/evolve — Run first cycle (observe-only)
/ooda-status — Check current state
/ooda-config — Modify configurationAfter printing the "Setup complete!" block, run a quick verification pass against the just-written config.json. This gives the user real data from their project within 60 seconds of setup, before they ever run /evolve.
Print: [4/4] Taking a first look at your project...
Each domain check runs independently so one failure cannot block the others. Apply a 15-second overall timeout for all checks combined and a 10-second per-domain timeout:
config.test_command is non-empty, run it with a 10-secondtimeout. Parse the output for pass/fail counts and coverage percentage. Print: test_coverage {passed}/{total} passing ({coverage}% coverage) If test_command is empty: print test_coverage [Skip] No test_command configured If command fails or times out: print test_coverage [Error] Test command failed
config.health_endpoints is non-empty, curl each endpointwith --max-time 5. Print status code and response time. Print: service_health {url} → {status} ({time}ms) If array is empty: print service_health [Skip] No health endpoints configured If curl fails: print service_health {url} → unreachable
gh issue list --state open --limit 100 --json number 2>/dev/null.Count results. Print: backlog {count} open issues If gh is not installed or fails: print backlog [Skip] gh CLI not available If no issues: print backlog 0 open issues
Print: Ready. Run /evolve for your first full cycle.
This step is informational only — it does not write state files or modify config.json. If the entire step fails or times out, print [4/4] Verification skipped. and continue.
After the verification mini-cycle, read the domains from the written config.json. For any domain where status: "available" (i.e., not enabled and not explicitly disabled), append:
3 optional skills are available but not yet configured:
/scan-market — market research and strategic analysis
/scan-ux — UX audit and UI analysis
/scan-competitors — competitor monitoring
Create any of these when you're ready:
/ooda-skill create <name>
Or disable ones you don't need:
/ooda-skill disable <name>Only list domains whose status field equals "available" in the generated config. If no domains have status: "available", omit this block entirely.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.