nablr — Engineering Audit Report
Engineering Audit · Functional / Logical / Technical

nablr — Audit Report

Repo my_projects/01/nablr · branch dev · v4.3.25 (pyproject) · 2026-07-28 · Remediation status updated 2026-08-02 — Waves 0–1 complete
~40k src LOC / 132 modules · ~27.5k test LOC / 121 test files · 34 MCP tools · 18 personas · 241 commits

Verdict

The product is real; the enforcement is not yet. nablr's core — 18 personas, a phase state machine, 12 scan targets built on genuine AST analysis (tree-sitter), per-story git-worktree isolation, licensing with trial flow — all exists and runs. The architecture direction is sound and several subsystems are genuinely well-crafted.

But the audit's central finding is systemic: the governance layer is largely advisory in practice. Nearly every gate that is supposed to block an LLM agent fails open, is self-certifiable, or is unreachable. A second systemic finding: nablr does not pass its own bar. Its own health scan reports 93 critical findings; 73 files exceed complexity 30 while the product enforces ≤15 on users; CI runs coverage with --cov-fail-under=0 and only on main, while the shipped protocol demands ≥85% on changed files.

None of this is fatal — the failure modes are specific, enumerable, and mostly cheap to fix. The P0 list below is 7 items.

Scorecard

Functional — does it do what it claims? Partial
Core loop works end-to-end. Headline claims inflated (19→18 personas, "40+"→~17 scanners); Jira sync doc says Shipped for reverted code; free-tier entry prompt is paywalled.
Logical — is the design sound? Weak enforcement
Good state-machine model; but gates fail open, artifacts self-certify, blockers/escalations are inert, phase-skip holes exist.
Technical — code quality Mixed
High type coverage, real AST engines, honest debt logging — alongside god modules, 1 atomic write in the codebase, no locking, 47 broad exception handlers.
Security posture Needs work
Air-gap architecture is a genuine strength. But no path confinement on MCP args, repo-supplied test commands executed verbatim, two shipped license bypasses.
Tests Real suite, hollow gates
1,548 mostly-real tests incl. strong e2e. Coverage gate disabled, full-suite report fabricates counts, generated tests are boilerplate.
Docs & requirements Heavy drift
public-docs tool page 100% stale; INDEX.md misparses; version split 3 ways (4.3.25 / tag v4.4.1 / changelog 4.3.12); foreign TD docs leaked in.

Finding 1 — Governance gates fail open Critical · logical

The product's promise is enforcement. Today most enforcement paths can be skipped, self-certified, or silently pass when their inputs are missing.

Finding 2 — Personas are never delivered Critical · functional

Finding 3 — Scanner correctness High · technical

Finding 4 — Security & input validation High

Finding 5 — State integrity High · technical

Finding 6 — CI & test enforcement High

Finding 7 — Claims vs. reality High · functional

ClaimRealityVerdict
19 expert personas18 (AVAILABLE_AGENTS); the 19th prompt is the entry pointInflated
40+ local scanners12 scan targets, ~17 real engines; own docs page says both "40+" and "10"Inflated
Rules/configs "Pro only"All 34 rule files + 11 YAMLs ship in the free wheel; free tier reads them locallyNot enforced
Free entry: "activate nablr"activate_nablr prompt is behind _require_paid() — free users get an upsellBroken
Jira sync "Shipped"Reverted; doc still says Shipped; 306-LOC export module survives with zero callersStale
Parallel orchestrationGate only, hardcoded 2 agents at PHASE_6; never dispatches (uncommitted epic plans the generalization)Partial
Codemap generationRuns, but output has 176 [Desc] placeholders → FEATURES.md degenerates to "2 features" for a 34-tool productHollow output
scan(target="compliance")Advertised in docstring + AGENTS.md; not in the dispatch — invalid target; 246-LOC dead implementationPhantom
14-day trial / licensingImplemented properly (mint, device dedup, offline grace)Real
Worktree isolation, browser E2E stepper, air-gap proofImplemented (browser fixed at bcf0fcd; was hardcoded-success before)Real
Docs & requirements drift (16 items)

Finding 8 — Code health, by your own tooling Medium

Ran scan(target="health") and scan(target="security") on src/nablr as part of this audit:

MetricResultNote
Critical / High / Medium93 / 107 / 1,638Product enforces "block on critical" for users
Files over complexity 30 (Tier-3 "full redesign")73Product mandates ≤15 per function; own worst: server.py CC 211, 1,793 LOC
except Exception without re-raise34 critical hitsOwn protocol forbids it; 29 handlers are body = pass
Security self-scanFAIL (network imports)Fails on its own allowlisted license client — allowlist matching bug
Dependency audit"0 dependencies"False — 10 runtime deps; parser missed its own pyproject format
Report placementWrote into src/nablr/docs/ & src/nablr/nablr-reports/Path-resolution bug pollutes shipped package tree

Other structural items: 19 latent import cycles broken by 166 function-local imports (33 in server.py); utils/ depends upward on session/workflow/tools; a leaf tool imports nablr.server and spawns a daemon thread from inside a getter (context_graph.py:830); orchestration decisions made by regex-parsing scanner markdown (server.py:321-324, :477if "❌" in result); 148 logger.info calls unreachable at the configured WARNING level; dead dispatch enums duplicating the Literal unions; stale hardcoded validator_version="3.13.2"; PyYAML used on a core path but declared dev-only.

Functional gaps (claimed or implied, not present)

Addendum (2026-07-28) — Token efficiency & First-Pass Yield Post-audit analysis

Question examined: how efficient is nablr in token terms — measured not as raw count but as outcome per token. Answer: token cost is moderate and bounded; token efficiency is poor, and the waste is caused by reliability failures, not verbose prompts. Fixing the Finding 1 gates saves more tokens than any prompt trimming.

Metric: First-Pass Yield (FPY)

North star: FPY — % of phases (or stories) that pass their gate on the first attempt with no retry, no rework loop, no fix-flow traced back later. Efficiency = FPY ÷ cost (nablr-controlled tokens + cycle time); concretely, cost-per-first-pass-phase. Token figures are an honest lower-bound proxy — an MCP server cannot see client-side reasoning tokens.

Cost side (measured, chars/4)

SurfaceCostNote
AGENTS.md (always-on)~3.5k tokReasonable
Persona payloads2.0k–8.9k (developer max, mean ~5k)Lean — but currently ~0 delivered (Finding 2)
Rule library49.2k across 34 filesLoaded per-role only — good design
Health report / TECH_DEBT / INDEX3.4k / 7.1k / 3.9kFine
scan(debt, summary=false)Unbounded (659 rows)Worst single offender

A full 10-phase story ≈ 30–60k nablr-controlled tokens once personas are actually delivered — the intended, acceptable price of governance.

Outcome side (day-zero baseline from live logs)

Synthesis: tokens-per-completed-story is effectively unbounded — the completions denominator is zero in current state. The leak is rework multiplying how many times the same tokens get spent. Efficiency-ordered levers: (1) Wave 0–1 gate fixes — the biggest token saver; (2) deliver personas on activation; (3) cap scan(debt) output; (4) prompt trimming — smallest lever.

Tracked by STORY-FPY-METRICS-001 (EPIC-TEAM-SCALE-001, commit 2c5e160; Jira NABLR-5 under epic NABLR-4): enum outcomes + persisted retries, per-payload token ledger, generate_efficiency_reportnablr-reports/efficiency/, today's numbers committed as the day-zero baseline.

Prioritized recommendations

P0 — the product's promise depends on these

  1. Fail closed. *_unresolved gaps must block, not pass (validators/__init__.py:240-248); route all story-path lookups through resolve_story_file_path.
  2. Close the phase-skip hole. validate_and_advance gets adjacency + artifact checks; remove or gate log(event="artifact") self-certification behind a verified whitelist, and log every grant.
  3. Fix the severity mapping (emoji → enum) so critical debt is critical again — one small function, restores the entire remediation trigger chain.
  4. Confine paths. Validate IDs (pattern= on pydantic fields), resolve every directory/file_path arg against the project root, refuse escapes; stop honoring repo-supplied extra_args without an allowlist.
  5. Turn CI on: run on dev + PRs, set a real --cov-fail-under, add diff-cover for the 85%-changed-files bar, add pyright, wire the test-smell scanner in, and parse real pytest counts into pass_summary (require total > 0 in evaluate_gate).
  6. Make state writes atomic + locked — copy the full_suite_runner pattern (tmp + os.replace, lock) into state_machine._save_state and friends; treat JSONDecodeError as corruption, not absence.
  7. Deliver personas on activationagent(activate) should return the persona prompt (or AGENTS.md should mandate the prompt endpoint); fix _detect_tech_stack to use the session root.

P1 — trust and hygiene

  1. Export .nablr_state/ + handoff log before merge_back removes the worktree; restore the active-story pointer after fix-flow.
  2. Make blockers/L3 actually halt (persist HALT flags; check blockers in suggest_next_agent); add revert_phase so failed phases can re-gate.
  3. Fix the claims: 18 personas, honest scanner count, unpaywall activate_nablr (it's your funnel), delete or ship jira_confluence_export, remove the phantom compliance target, fix demo_security_scan.sh.
  4. Rewrite public-docs/tools/overview.mdx against the real tool surface; fix INDEX serializer regexes; repair the release chain (changelog insertion marker; re-point tags at reachable commits).
  5. Decompose server.py (registration vs orchestration vs watcher lifecycle) and give scanners a structured result type — stop regex-parsing your own markdown for control flow.
  6. Remove the tracked/stray artifacts from the wheel (src/nablr/codemap/, stray report dirs); gitignore sef-reports/ or delete it.
  7. Gate the migration personas (add M-phase PHASE_CONFIG entries) and add the test_designer validator.
  8. Delete the foreign TD-011…015 docs; sweep active/ statuses (the close protocol works — Finding 1's closure bug is why they're stale).

P2 — polish

  1. False-positive pass on comment-smell, DC002, TS ternary regex, arg-count self/cls; unify the five exclusion lists behind IgnorePatternMatcher.
  2. Parameterize house rules (source packages, branch names, structlog/FR checks) so scanners work on customer repos.
  3. Fill codemap purposes (planned STORY-072) so FEATURES.md stops reporting 2 features; regenerate AGENTS.md from code.
  4. Timestamps → UTC-aware everywhere; git subprocess timeouts; graph node IDs with microseconds; expire the offline paid-asset cache.
  5. Reduce the worst complexity offenders behind the P0 fixes (server.py, health_scanner.py, transitions.py resolve_artifact → token-resolver registry).
  6. Ship artifact_tokens.md into the personas that need it — it's the manual for your own gates and nobody reads it.

Execution sequence — 35 stories in 6 waves

Done marks stories already implemented. Wave 0 complete (2026-07-28): WORKTREE-VENV-001 (ed48e8e), GATE-FAILCLOSED-001 (220db52), CI-DEV-BRANCH-001 (c452af5). Wave 1 complete (verified 2026-08-02): SEVERITY-PIPELINE-001 (45e21f5), GATE-PHASESKIP-001 (c1bf456), STATE-ATOMIC-001 (PR #4, 06ed98e), PATH-CONFINE-001 (6675c74, PR #5), COVERAGE-GATE-001 (97c4670, PR #11), PERSONA-INJECT-001 (8f27090, PR #10).

Principle: fix the tools you'll use to build everything else first, then correctness, then polish. Backlog committed as 5 new epics + stories linked into 7 existing epics (a8de8d4, 2c5e160).

WaveThemeStories (order)
0Meta-blockers — the workflow itself is broken for dogfoodingWORKTREE-VENV-001 Done · GATE-FAILCLOSED-001 Done · CI-DEV-BRANCH-001 Done
1P0 correctness (parallelizable)SEVERITY-PIPELINE-001 Done · GATE-PHASESKIP-001 Done · STATE-ATOMIC-001 Done · PATH-CONFINE-001 Done · COVERAGE-GATE-001 Done · PERSONA-INJECT-001 Done
2Gate + state hardeningGATE-TOKEN-AUDIT-001 → FIXFLOW-INTEGRITY-001 · STATE-LOCKING-001 · MERGEBACK-STATE-EXPORT-001 · FULLSUITE-COUNTS-001 · GATE-PHASE-REVERT-001 · GATE-BLOCKERS-001 · CODEMAP-DEDUPE-001
3Scanner correctnessSCAN-REPORT-PATHS-001 → REMEDIATION-BOOKKEEPING-001 · SCANNER-FP-001 · THRESHOLD-COHERENCE-001 · SCANNER-PORTABILITY-001 · TESTSMELL-CI-001
4Workflow completion + telemetryCLOSE-PHASE7-001 → FPY-METRICS-001 · VALIDATOR-TESTDESIGNER-001 · SESSION-COHERENCE-001 · REQDOCS-SERIALIZER-001 + REQDOC-LIFECYCLE-001 (paired: parser/placement vs lifecycle moves; Jira NABLR-6) · MIGRATION-GATES-001
5Truth & releaseLICENSE-INTEGRITY-001 + RELEASE-CHAIN-001 (both before next PyPI publish) · FREE-ENTRY-001 · DOCS-CLAIMS-001 · PUBLICDOCS-TOOLS-001 · SCAN-RESULTS-STRUCTURED-001 (last — pure refactor, safest under waves 0–2 protection)

FPY-METRICS-001 placement rationale: Wave 4, immediately after CLOSE-PHASE7-001 — its outcome data is only meaningful once gates fail closed (Wave 0) and closures actually reach PHASE_7. The day-zero baseline is already captured, so nothing is lost by waiting. Overrides: pull LICENSE-INTEGRITY + RELEASE-CHAIN into Wave 1 if a release is imminent; Wave 3 has zero file overlap with Waves 1–2, so a second contributor can run it in parallel.

Method & caveats

Five parallel deep-read audits (architecture/core, scanners/validators, workflow engine/personas, test suite/CI, docs-vs-claims) over the full source tree, plus dogfooding nablr's own MCP scan(health) and scan(security) against src/nablr. Read-only — no code changed; the auto-started remediation session REM-8332E323 was left untouched.

Caveats: dev HEAD moved during the audit (e1a21d3bcf0fcd, a concurrent session's browser-stepper fix — findings reflect bcf0fcd where noted). Three story worktrees are live under .nablr-worktrees/. Line numbers are accurate as of scan time and may drift. Dogfood scan wrote reports into src/nablr/… (itself Finding 3's path bug) — those files are untracked and safe to delete.