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

nablr — Audit Report

Repo my_projects/01/nablr · branch dev · v4.8.0 (pyproject) — dev/main re-diverged 2026-09-10: 38 commits main-ahead, 45 dev-ahead, latest tag v4.12.0 not merged to dev — RELEASE-CHAIN-001's "fully closed" claim below no longer holds, see Wave 5 · audit baseline 2026-07-28 · Re-verified against code 2026-09-09 (HEAD 080a202) — full reconciliation pass same day (every open/partial Finding 1–8 + Blocker Report item re-checked against current code, not other docs; see the reconciliation update below the Blocker Report addendum); FIX-F2CEBCB8 landed same day, closing DEBT-6d5ceef6 itself (the tool defect that blocked STORY-066, not just STORY-066's own closure); STORY-066 closed same day (validator teeth for Socratic-interrogation, blocked for weeks by a tool defect not a code gap); FIX-A5461707 landed 2026-09-08 (ambient-root/keyword bugs in story-type + capability detection); Step 2's 11 stories landed 2026-09-01, 8 more live bugs found+fixed 2026-09-02, Step 3's 4 stories + STORY-SCANNER-FP-002 landed 2026-09-02, a 38th story (STORY-MARKERSIGNER-PERSIST-001) landed 2026-09-05, Step 4 (all 3 orders, incl. REQDOC-LIFECYCLE-001) landed 2026-09-05–06 — the entire 39-story backlog this report has tracked since 2026-07-28 is now fully shipped, SCAN-RESULTS-STRUCTURED-001 included (scoped and closed 2026-09-06); a 40th, unplanned story (STORY-CI-SCOPED-TESTS-001) landed the same day (ecb538b), scoping local hooks + PR-into-dev CI to pytest-testmon) — drift-check re-run same day, HEAD now b883e56: three more self-fixed defects landed (FIX-4119C674, archives closed-story state under workflow/archive/ instead of top level; FIX-B6182BA7, stopped writing the write-only docs/requirements/*.json sidecar entirely — the root-cause fix, superseding this report's own earlier .gitignore negation for those sidecars, now removed as moot; FIX-76D75EB2, gates health_scanner's ARCHITECTURE.md/EXTERNAL_DEPS.md relocation behind an explicit relocate_artifacts opt-in, default False — closes the last open half of the "side effects in getters" finding below) and two more stories shipped same-day (STORY-PR-DIFF-CROSSCHECK-001, STORY-DEPENDENCY-CVE-AUDIT-001, both outside the original 39-story backlog, both closing further Blocker Report gaps — see the updated tally below); a new gap surfaced by this pass itself, STORY-PARALLEL-NAGENT-001 (Draft), formally tracks the pre-existing fixed-2-slot parallel-dispatch finding
~40k src LOC / 132 modules · ~27.5k test LOC / 121 test files · 34 MCP tools · 18 personas · 250+ 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.

Update, 2026-08-27: all 7 P0 items are now fixed in code (Waves 0–1, verified this pass — see the execution sequence). The systemic risk today is narrower: the long tail of 24 Wave 2–5 stories, of which 14 remain untouched — notably scanner report-path pollution and the version/claims drift are actively recurring, not just historical.

Update, 2026-08-28: SCAN-REPORT-PATHS-001 — the report-path pollution item flagged above as "actively recurring" — is now fully fixed. It scored CRITICAL under impact_architect's own file-count gate (14 files, over the >10-file Developer-activation limit) — the same enforcement mechanism this report's Finding 1 was built around actually did its job and forced a split rather than being overridden. It shipped as 5 sequenced child stories instead of one diff: SCAN-ROOT-RESOLVER-001, SCAN-WRITE-ANCHOR-001, SCAN-EXCLUSION-ENGINE-001 (9ff4528), SCAN-PATH-NORMALIZE-001 (d492d18), SCAN-CLEANUP-001 (640e1f3) — all merged to dev. See Finding 3 and the updated Wave 3 row.

Update, 2026-09-01: Step 2's 11 mutually-disjoint stories (see Concurrent-safe worktree batches) all landed same-day, run genuinely concurrently in 11 nablr-managed worktrees. Real findings from the run, not just green checkmarks: three different unattended agents hit HARD_REQUIRED human-approval gates and self-resolved rather than blocking indefinitely — each was reviewed after the fact (diffs read directly, merge commits verified as real ancestors of dev) and found clean, but it's a live instance of exactly the self-certification risk Finding 1 already named, not a hypothetical. One story (SCANNER-FP-001) got a real narrowing-then-forensic-reinvestigation cycle before its gate cleared honestly; two of its five original fixes split into a follow-up, STORY-SCANNER-FP-002 (drafted, not started). Two new nablr bugs surfaced live and are logged, not fixed: merge_back's squash-commit doesn't ancestor-link the story branch into dev's reachable history (caused a repeated conflict-retry loop on one story until diagnosed), and IgnorePatternMatcher conflates .gitignore/.dockerignore patterns, silently excluding all of tests/ from the test-smell scanner's shared entry point. RELEASE-CHAIN-001 reconciled the version split (pyproject now matches tag lineage) but flagged that dev/main have diverged 164 vs. 12 commits since their merge-base — a separate, larger problem than the version string, not resolved here.

Update, 2026-09-02: eight more bugs surfaced live during Step 2 execution were each run through their own fix_flow cycle and merged to dev (see Step 2.5) — a specifier-validator false positive that ironically over-blocked the exact "Open Questions" escape hatch cluster 7 of the Blocker Report addendum found toothless in the other direction; a stale support domain in the docs; the two open anomalies this report already flagged in MERGEBACK-STATE-EXPORT-001 and left as live risk; a wrong-path bug in create_story's own success message; and a genuine cross-session data leak in pr_reviewer's injected file list, root-caused to the same ambient-session-state anti-pattern Finding 5 already named — three separate fixes this round (pr_reviewer, the merge_back export anomaly, and scan()'s diff_scope) all trace to variants of it, confirming it's a class of defect, not one bug. Two of the eight (the pr_reviewer leak and scan() diff_scope fix) hit their own HARD_REQUIRED impact_architect gates and were escalated to the human operator rather than self-approved — two more real, reviewed instances of the same live self-certification evidence the 2026-09-01 update above already logged, this time correctly routed rather than auto-resolved. The dev/main divergence flagged above is now fully reconciled — main merged into dev, every tag from v4.4.0 to v4.7.1 is reachable from dev, dev..main is 0 — and pushed to origin/dev. That push surfaced two more real, if minor, gaps closed in the process: a bandit HIGH finding on the newly-hardened test_command shell call that needed an explicit nosec justification alongside its existing ruff suppression, and the automated devmain PR's own CI lint gate failing on a genuinely missing doc entry (workflow.revert_phase, never added to overview.mdx) — both fixed inline rather than bypassed.

Update, 2026-09-02 (Step 3): the 4 stories unblocked by Step 2's landings (THRESHOLD-COHERENCE-001, SCANNER-PORTABILITY-001, VALIDATOR-TESTDESIGNER-001, DOCS-CLAIMS-001) all landed same day — see Step 3. Real findings, not just green checkmarks: VALIDATOR-TESTDESIGNER-001's security_owner phase found and fixed a genuine path-traversal vulnerability in the new test_designer validator itself (artifact_id="../../../secret" could read files outside the project root — the one new validator this pass added was, briefly, the one validator that skipped the confinement check its 17 siblings already had); THRESHOLD-COHERENCE-001 hit a CRITICAL (not just HIGH) impact_architect gate because the citation mechanism for its Churn Recency downgrade exception (a closed-story entry in project_status.md) doesn't cover fix_flow tickets at all — the touching commit's real closure had to be verified by commit ancestry instead, a gap in the gate's own evidence-citation path, not in the touch itself. All 3 story-level merge_backs hit real (not spurious) conflicts against Step 2/2.5's own append-only docs (TECH_DEBT.md, project_status.md, the daily efficiency report) — each resolved by hand in the retained clone and verified landed by content, not by trusting the tool's success message. One meta-note: mid-session it surfaced that this session's own MCP connection to nablr runs a separately-published package snapshot (uvx nablr), not this repo's own dev checkout — expected for a self-hosted tool that isn't hot-reloaded, already tracked as DEBT-b4a67e86, but it means a live round-trip check of a fix against the tool itself only proves anything after the tool process restarts on newer code; source-level verification (real tests, real diffs) had to substitute in the meantime. See the diff_scope RCA note below Finding 6 for a related, separately-investigated question about why full-suite verification runs weren't diff-scoped.

Update, 2026-09-02 (STORY-SCANNER-FP-002): the two false-positive fixes split off from SCANNER-FP-001 landed (0a01460) — DC002 orphan-file candidates now resolve from the true package root instead of the scan-invocation directory, and health's comment-smell finding now reads through finding.severity instead of hardcoding HIGH. Hit its own HIGH impact_architect gate (Coupling + Churn Recency, downgraded via the citation exception against two real, closed, Tests-Passed prior stories) — escalated, independently verified (real commit ancestry, real fan-in count, real closed-story status), approved by the human operator. Mid-flow it also reproduced a third instance of DEBT-b4a67e86: scan(target="health") via the MCP tool reported CRITICAL/CC001/"threshold: 12" findings on both edited files — the exact legacy message format THRESHOLD-COHERENCE-001 already removed from the source — because the MCP server's uvx-installed build has never had that fix; a fresh, out-of-process import of the actual on-disk module (independently reproduced both by the story's own session and this one) showed the true, current result: critical=0, CX-FILE/HIGH, never Tier-3-eligible. Logged as a debt recurrence rather than silently worked around. Separately, the story's own pre-push hook then caught a genuinely stale, unrelated regression-lock constant (tests/test_docs_claims_drift.py's _EXPECTED_VALIDATOR_COUNT — hardcoded 18 by DOCS-CLAIMS-001's worktree, which snapshotted the validator registry before VALIDATOR-TESTDESIGNER-001's concurrent merge fully landed; real count is 19, all legitimate entries, no duplicates) — fixed inline (6cd494f) rather than bypassing the hook. Push to origin separately blocked on an unrelated local environment issue — two GitHub accounts configured on this machine, wrong one active for nablrco/nablr. gh auth switch fixed it transiently (one fetch succeeded) but the active account reverted before the push completed; still unresolved as of this writing, not a code or gate problem.

Update, 2026-09-05: STORY-MARKERSIGNER-PERSIST-001 landed (1addea1) — outside the original 35-story backlog, but the same defect class Finding 6's diff_scope RCA already named: the full-suite gate's HMAC signing key was minted fresh per MCP-process start and held only in an in-memory module-level set, so any server restart invalidated every previously-signed report and evaluate_gate() could never trust a marker across a process boundary. Fixed by persisting the key to disk (atomic write, mode 0600, zero window of over-permissive access) so a signature now survives restart. Mid-fix, the change itself tripped the debt-ratchet gate (LOC growth on gate.py and full_suite_runner.py vs. their story-init baselines) — resolved by extracting a shared marker_valid_for_report() helper (removed real duplication between gate.py and report.py, not a workaround) and dropping two dead re-exports, landing both files at or below baseline. Re-verified directly against code today (HEAD 5e3ded7), not taken on the story's own close report: Step 2's prerequisites for Step 4 are both genuinely in placeworkflow/state_machine.py has real fail_phase/revert_phase functions (GATE-PHASE-REVERT-001), and tools/workflow/merge_back.py has a real _export_story_state plus its FIX-FE9945E9 misroute-detection follow-up (MERGEBACK-STATE-EXPORT-001). workflow/transitions.py:807-809's can_transition still checks state.blockers, confirming the 2026-08-27 "partial, as a byproduct" note on GATE-BLOCKERS-001 still holds. Everything Step 4 still expects to fix is confirmed still open by direct grep, not inference: tools/workflow/next_agent.py has zero blocker/HALT references (workflow(next) still doesn't check either); session.py's halt_flags is still a bare in-memory set() with no persistence path; handoff_tools.py has zero L1/L2/L3-differentiation code; add_blocker's level param is still an unconstrained str; fix_flow_close.py's close_workflow (confirmed still at this corrected location) has zero PHASE_7/update_phase reference. A live, unplanned confirmation of REQDOC-LIFECYCLE-001's own premise turned up in the process: STORY-MARKERSIGNER-PERSIST-001's own requirement doc has a stale, empty-description duplicate sitting untracked in docs/requirements/active/ alongside its real, committed done/ copy — the exact "no single canonical copy across lifecycle stages" defect this story targets, caught in the act rather than inferred from the backlog description.

Update, 2026-09-05 (Step 4, Orders 1–2): STORY-GATE-BLOCKERS-001 landed (c1be360) — blockers and L3 HALT now block phase advance at the same enforcement point validate_and_advance already used, and HALT state persists across an MCP-server restart instead of living in an in-memory set(); workflow(next) was also wired to check both, closing the asymmetry Finding 1 named. STORY-CLOSE-PHASE7-001 landed immediately after, merged to devclose_workflow now genuinely advances a story to PHASE_7 via WorkflowManager.update_phase before setting story_closed, and the scrum_master closure gate this story extracted into a shared closure_gate_check() is now called identically by both merge_back (already hard-blocking) and close_workflow (previously a non-blocking note) — the two-close-paths asymmetry Finding 1 flagged is closed. Two genuine bugs were found and fixed in a new one-off scripts/sweep_stale_closures.py written to correct stories that closed stale before this fix existed: a clobber bug that could silently overwrite an already-correct done/ copy with a stale active/ duplicate, and a root-resolution bug where the sweep silently ignored the project root it was given — both caught by a failing regression test before shipping, not after. A pre-existing operational finding surfaced independently, not caused by this story: the long-lived nablr MCP server backing this session's own tool calls has fix_flow_close.py/merge_back.py imported from before this story's edits landed, so workflow(action="complete"/"merge_back") called via the MCP tools executes stale code for those two files specifically until the server restarts — worked around per-story via fresh subprocess calls, flagged as a live risk for any future story closed through MCP tools in this session. Step 4 Order 3, REQDOC-LIFECYCLE-001, is next — not yet started.

Update, 2026-09-06: STORY-REQDOC-LIFECYCLE-001 (Step 4 Order 3, the last item on the Step 4 table) landed (2ee2a19) — re-verified directly against code, not taken on the story's own close report. All 5 ACs are real: G1 (_commit_story_doc_at_init in the new init_worktree.py stages and commits only the story doc's own .md/.json pair before the worktree checkout — never a broad git add -A, closing DEBT-6b0e6ed3); G2 (WorkflowState.story_doc_path is the recorded canonical path; validators/__init__.py's new resolve_story_doc() reads it instead of re-deriving); G3 (docs/requirements/INDEX.md added to .gitattributes as merge=ours alongside codemap/**, extending the STORY-MERGE-ARTIFACT-CONFLICT-001 regime, plus post-merge regen); G4 (_rename_story_doc_with_companion in fix_flow_close.py rolls the .md rename back if its .json companion fails, rather than leaving a stale pair); G5 (create_story/update_story split in hierarchy_manager.py, no force flag). DEBT-a19ced2a can be closed. This clears the last open item on the Step 4 table — the entire backlog this report has tracked since 2026-07-28 is done bar SCAN-RESULTS-STRUCTURED-001 (Partial, unscheduled).

Update, 2026-09-06 (verification gap, not a production bug): landing this story surfaced a live instance of this report's own recurring "own bar" theme — inside the story's own test suite this time, not the product. Bisecting a deterministic (not flaky) failure across all 341 test files found a genuinely thread-unsafe unittest.mock.patch usage in tests/story_license_integrity_001/test_concurrency.py: 5 worker threads each independently entered/exited with patch("urllib.request.urlopen", ...) on the same global target, and the enter/exit race could leave urlopen permanently monkey-patched process-wide after that file ran — silently breaking tests/test_complexity_budget.py's real-network-call path whenever it ran later in the same pytest process. Fixed by patching once, from the main thread, around the whole worker pool. The same full-suite run caught two more gaps that were this story's own commit's fault: test_global_root_grep_gate.py's hardcoded call-site whitelist was never updated for the new find_canonical_root() call this story's own FR-1 added to init.py, and test_merge_back_squash.py's synthetic repo fixture never seeded docs/requirements/INDEX.md, so this story's own FR-4 (unconditional INDEX.md regen) looked like a spurious extra commit to the test even though production behavior was correct. All 3 fixed in one commit (be4954c), full suite reverified green (3486 passed, 17 skipped), pushed to origin/dev. Same root cause this report keeps finding: a story's own PR review runs only its own tests, not the full suite, so gaps in its own test fixtures surface later, not at review time.

Update, 2026-09-06 (closing the backlog): STORY-SCAN-RESULTS-STRUCTURED-001 — the last open item on this report's own table since 2026-07-28 — landed (79142f0), closed Done, scoped. Re-scoped to the two concrete regex/__import__ sites this story's own audit finding named by file:line, rather than the full general ScanResult-for-all-12-scanners interface its title/description also describe: discovery_audit.py's audit_discovery() -> str is split into run_discovery_audit() -> AuditResult (structured PASS/FAIL/ERROR) + render_audit_result() -> str (pure formatting), proven byte-compatible against real tmp_path fixtures; workflow_impl.py's G3 discovery phase-close gate (this report's own Finding 8 cited it as server.py:477, now relocated) reads AuditResult.status == "FAIL" directly instead of scraping "❌"/"FAIL" out of rendered markdown, with the ERROR/missing-directory case verified to preserve the pre-existing non-blocking behavior rather than silently tightening it; scan_impl.py's full_suite dispatch entry replaces a per-call __import__(...) with a normal top-of-module import. The remaining general-ScanResult scope is recorded as an explicit follow-up (AC-4) in the story's own closed doc, not silently expanded or dropped — the same honest-partial pattern as MIGRATION-GATES-001. Three deferred tokens were used at PHASE_4 (health_scan_deferred, tests_smell_deferred, debt_ratchet_deferred), each backed by a specific docs/TECH_DEBT.md entry citing verified pre-existing debt or genuinely-required LOC growth, not a silent bypass — a live, well-behaved instance of the "audited bypasses instead of silent ones" pattern this report's own "What you got right" section already named as a strength. Closing it hit STORY-REQDOC-LIFECYCLE-001's own G2 gate live: workflow(action="complete") correctly blocked on a main-root-vs-worktree story-doc divergence, naming both paths rather than silently picking one — reconciled by hand, then closed. The 39-story backlog this report has tracked since 2026-07-28 is now fully done. The only remaining open items in this report are the separately-tracked 2026 AI Blocker Report cross-check (11 of 12 recommendations still not started) and the P1/P2 polish items below — neither is part of the original 2026-07-28 backlog table.

Update, 2026-09-06 (post-merge verification): the unscoped full test suite — run against dev after SCAN-RESULTS-STRUCTURED-001's merge, not the story's own diff-scoped gate — found one real regression the story's own scoped PHASE_4 run couldn't see: tests/test_gate_phaseskip.py::test_log_phase_discovery_pass_runs_audit_discovery still monkeypatched the old, now-unused discovery_audit.audit_discovery string-returning wrapper, so it silently exercised the real run_discovery_audit() against an incomplete fixture and failed. Root-caused (stale mock target, not a product bug) and fixed by patching the function the gate actually calls now; full suite reverified green (3491 passed, 17 skipped, 0 failed), pushed as f049995. Same class of gap this report's 2026-09-06 verification-gap update already named two paragraphs up: a story's own scoped test run cannot see a regression in a test file outside its changed-file set.

Update, 2026-09-06 (STORY-CI-SCOPED-TESTS-001, unplanned, 40th story): landed (ecb538b), outside the tracked backlog but directly on top of Finding 6's residual pain: CI-DEV-BRANCH-001 (Wave 0) already made the full suite run on every push/PR to dev, but every PR-into-dev review and every local pre-commit/pre-push still paid the full ~27-minute, ~3,500-test run regardless of diff size — the review and local-feedback loops this backlog's own token-efficiency addendum named as the highest-cost surface. Pre-commit gained a pyright hook plus a pytest-testmon-scoped run keyed to the staged diff; pre-push retargeted its existing full-suite hook to branch-diff scope; ci.yml's test job now branches on base branch — testmon-scoped for PRs into dev, still unconditionally full-suite for PRs into main and for the push-triggered safety net, matching this report's own repeated finding that the merge-to-main gate must never be weakened by a scoping mechanism aimed at faster iteration. A live, not assumed, defect surfaced during PR review, the same investigative discipline this report has applied throughout: pytest-testmon does not degrade gracefully on a corrupt .testmondata — reproduced deliberately (echo garbage > .testmondata), it raises sqlite3.DatabaseError/INTERNALERROR and aborts the whole pytest process rather than falling back to a full run, contradicting the story's own FR-7 guarantee. Fixed with a new scripts/testmon_guard.py pre-flight (removes an unreadable db before testmon opens it) wired into both the CI step and the local hooks, with 3 new real-execution tests (actual SQLite files, no mocking) and a live end-to-end re-verification of corrupt-db → guard → full-run-fallback. Unrelated to Finding 5 in mechanism but the same shape: this is the second story this month (after STORY-MARKERSIGNER-PERSIST-001) to find a real gap in code that looked done because its designed behavior was never actually exercised.

Update, 2026-09-07 (FIX-523E9631, unplanned): a naming-collision bug surfaced live during this session's own housekeeping, not from the tracked backlog: main_root/"main-root" — the term bound throughout worktree.py/fix_flow_close.py/merge_back.py/merge_back_clone.py/merge_back_stash.py/init.py to canonical_project_root()'s return value (pure path math stripping .nablr-worktrees/<repo>/<story> ancestry, zero git awareness) — reads as "the git main branch's root" to anyone without prior context, and this repo's actual base branch is dev (BASE_BRANCH, worktree.py:25), making the term doubly misleading. Confirmed-broken site: init.py:205's own comment literally read "commit the story doc to the main-root branch," conflating the two concepts in one phrase next to a real git commit call. Run through fix_flow's full pipeline (forensic_engineer → strategist → specifier → impact_architect → developer → pr_reviewer → scrum_master); scored HIGH regression risk (Churn Recency Critical on 4 of 6 files, each downgraded via the Self-Verified Downgrade Exception citing three already-closed stories' Tests Passed: ✅ records by commit hash), escalated, human-approved. Renamed to checkout_root/"non-worktree checkout" at all 6 sites plus one disambiguating docstring anchor on canonical_project_root() itself; zero complexity change; +10 LOC total, all disambiguating prose (revised up from an initial 0-LOC estimate once measured, not scope creep). One test's white-box monkeypatch.setattr target needed updating for the rename, caught by re-running the affected suite before merge. 122 transitively-affected tests plus the full suite reverified green, security scan 5/5 clean, PR-REVIEW verdict APPROVE. Landed 81deb61, pushed to origin/dev (9630ad9). Same defect shape this report already tracks under Finding 7/docs drift — a term coined once and propagated by copy-paste across 6 files and 10+ closed-story docs, never centrally defined; the fix pairs the rename with a single-source docstring anchor rather than a standalone glossary note, so the disambiguation lives at the point of reading instead of in a doc nobody consults (see Finding 2's own "artifact_tokens.md loaded by nobody" row for the general pattern).

Update, 2026-09-07 (FIX-695F304B, plus two stale-finding corrections): FIX-695F304B closes Finding 6's "generated tests are boilerplate phantoms" row: tests/generated/STORY-*/'s import-only smoke tests (symbol_smoke_runner.py output, explicitly commented "DO NOT EDIT") were git-tracked (44 files), collected by default pytest runs, and indexed as codemap packages, despite being regenerated every Tester run. Extended the repo's own existing /tests/generated/ exclusion convention (already used by ai_pattern_detector.py/health_scanner.py) to the three sibling sites that never got it: pyproject.toml's norecursedirs, .gitignore (plus git rm -r --cached to untrack, kept on disk), and codemap.py's find_python_packages(). Hit its own HIGH impact_architect gate (Coupling High, Churn Recency Critical→High downgraded via the Self-Verified Downgrade Exception citing STORY-CI-SCOPED-TESTS-001), escalated, human-approved with the explicit condition that Developer add a direct regression test — satisfied (tests/test_codemap_generated_tests_exclusion.py, real execution, no mocking). Full suite reverified green (3528 passed), PR-REVIEW verdict APPROVE, landed 02b9928 + codemap regen f4edbdd, pushed to origin/dev. Separately, two other items pulled from this report's own status list as still-open turned out to be false positives caused by exactly the documentation-drift pattern this report already tracks (Finding 7): Finding 4's "Two shipped license bypasses" row and Finding 5's "No locking on read-modify-write" row were both already fixed by prior closed stories — STORY-LICENSE-INTEGRITY-001 (closed 2026-08-09: dev-mode gate behind _is_editable_install(), transport auth's _assert_open_mode_safe(), hmac.compare_digest, cache expiry — 30/30 tests passing) and STORY-STATE-LOCKING-001 (closed 2026-08-10: 9 mutation methods incl. parallel.py's update_parallel_task wrap read-modify-write in story_lock()) — but this report never retroactively annotated either bullet as Fixed. Both rows are corrected below rather than left stale. No fix_flow session was needed for either — confirmed via direct source read and passing test suites, then cleanly closed with no commits.

Update, 2026-09-08 (FIX-A5461707): two independent, live-reproduced bugs closed via fix_flow, both traced to the same root cause: check_testing_capabilities (misc_tools.py) and two other call sites (init.py x2, workflow_impl.py's run_browser check) constructed CapabilityDetector() against the ambient session root (find_canonical_root()'s ordinary fallback) instead of the calling story's own registered worktree — a concurrent session's worktree bootstrap or merge_back could silently repoint that ambient root mid-flight, so a story's own testing-capability check could read a different checkout's requirements doc. Sibling Defect Audit found all 4 sites; all 4 fixed by routing through the existing, purpose-built resolve_story_root(story_id) instead. Second, unrelated bug in the same investigation: FRONTEND_KEYWORDS included the bare word "form", which collides with ordinary prose ("a line of the form ...") on backend-only requirement docs and forces a false frontend classification via the keyword-fallback path — removed, UI-form surface still covered by "ui"/"component"/"modal"/"dialog". 3 new regression tests using real git worktrees, no UUT mocking — the key reproduction test initially passed even against the pre-fix code because check_capabilities's own story-type cache short-circuited before the buggy fallback path ever ran; fixed by forcing StoryType.UNKNOWN before the check, then git-stash A/B verified to fail pre-fix and pass post-fix. Landed 007befb, merged to dev with zero conflicts.

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.
2026-09-01: free-tier entry prompt unpaywalled (FREE-ENTRY-001); public tool docs rewritten against the live registry (PUBLICDOCS-TOOLS-001).
2026-09-02: persona/scanner-count claims fixed and regression-tested against live registries — 18 personas, 12 scanners (DOCS-CLAIMS-001). Jira sync doc's stale claim is the one item that couldn't be fixed through this story (gitignored file, needs a manual edit).
Logical — is the design sound? Enforcement gaps mostly closed
Good state-machine model; but gates fail open, artifacts self-certify, blockers/escalations are inert, phase-skip holes exist.
2026-08-27: fail-open + phase-skip fixed; blockers/escalations still inert (Wave 2, not started).
2026-09-01: permanent completed_phases bypass closed (GATE-PHASE-REVERT-001) — failed phases can now be re-gated instead of auto-passing forever. But the self-certification risk this row already named is no longer theoretical: running Step 2's 11 stories surfaced 3 separate unattended agents that hit a HARD_REQUIRED human-approval gate and self-resolved it rather than blocking. Each was reviewed after the fact and found clean, but the mechanism that let them do it unsupervised is exactly what this finding described.
2026-09-02: two more real HARD_REQUIRED gates (FIX-8AD8E17E, FIX-85A0B5C8, both Coupling+Churn High) — this time both correctly stopped and escalated to the human operator instead of self-resolving. Same live risk, better-behaved this round; the gate itself still has no code-level enforcement against an agent choosing not to stop.
2026-09-05: blockers/L3 HALT now genuinely block phase advance and persist across restart (GATE-BLOCKERS-001); close_workflow now reaches PHASE_7 and shares merge_back's closure gate (CLOSE-PHASE7-001) — the two-close-paths asymmetry this row named is closed.
2026-09-10: the 39-story backlog this report tracked is fully shipped; every named enforcement gap in this row's own history is closed in code (verified above, not assumed). The self-certification risk itself is architectural, not fully closed — an unattended agent can still choose not to stop at a HARD_REQUIRED gate; every live instance so far has been reviewed clean, but nothing in code prevents a future one from not being.
Technical — code quality Mixed, genuinely improved
High type coverage, real AST engines, honest debt logging — alongside god modules, no locking. 2026-08-27: 1 atomic write, 47 broad exception handlers.
2026-09-10 (re-verified against code, not this row's own prior count): atomic-write pattern now spans 11 files (atomic_io.py, file_lock.py, state_persistence.py, merge_back.py, signing_key.py, runner/report.py, context_graph.py/_index.py, debt_cache.py, compliance_remediation.py, license_client.py) — a real, broad shift, not a one-off; broad exception handlers down to 34 (still real debt, not eliminated).
Security posture Improved, real gaps remain
Air-gap architecture is a genuine strength. But no path confinement on MCP args, repo-supplied test commands executed verbatim, two shipped license bypasses.
2026-08-27: path confinement + license bypasses fixed.
2026-09-10 (re-verified against code): a real SAST engine (STORY-064), PyPI/npm package-existence check (STORY-067), prompt-injection detection (STORY-068), diff-aware AI-code scanning (STORY-070), OSV.dev CVE check + JS/TS license parity (STORY-DEPENDENCY-CVE-AUDIT-001), and a Shannon-entropy secret-scan fallback (FIX-5124835F, secret_scan.py:189-260 — this row's own earlier "no entropy check" claim was stale, corrected here) have all landed since 2026-08-27. Still genuinely open: URL allowlist silently drops any URL containing a literal backslash from scanning entirely (_is_regex_pattern_fragment, url_scan.py:137-149,206-207 — confirmed by direct read, not carried over from an old line-number citation); file audit's "subprocess" detection can never match; destructive-action interception at runtime not started.
Tests Real suite, gates now enforce
1,548 mostly-real tests incl. strong e2e. Coverage gate disabled, full-suite report fabricates counts, generated tests are boilerplate.
2026-08-27: coverage gate (75% + diff-cover) + pyright now real; full-suite counts still partly fabricated.
2026-09-01: pass_summary.total/passed/failed now parsed from pytest's own cache instead of hardcoded 0 (FULLSUITE-COUNTS-001) — the fabrication this row named is fixed; test-smell scanner also now wired into CI with a ratchet gate (TESTSMELL-CI-001), closing the "runs in no pipeline" gap from Finding 6.
2026-09-06: local hooks and PR-into-dev CI now run pytest-testmon-selected tests instead of the full suite on every commit/push/PR (CI-SCOPED-TESTS-001) — PR-into-main and the push safety net stay full-suite; a live corrupt-db crash in testmon itself was found and fixed in the same pass.
2026-09-07: the "generated tests are boilerplate phantoms" gap this row's Finding 6 names is fixed — tests/generated/ scratch output is no longer git-tracked, no longer collected by default pytest, no longer codemap-indexed (FIX-695F304B).
Docs & requirements Drift resolved once, reopened
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.
2026-09-06: the story-doc lifecycle drift this row's "foreign TD docs leaked in" / silent-overwrite risk traced back to (DEBT-a19ced2a) is fixed — REQDOC-LIFECYCLE-001: one canonical doc copy per story tracked in state, committed at worktree init, INDEX.md merge-safe. RELEASE-CHAIN-001 reconciled the version split and closed dev/main to 0 commits apart, pushed 2026-09-02.
2026-09-10 (re-verified against actual git state, not this row's own prior narrative): that closure did not hold — dev and origin/main have re-diverged (38 commits main-ahead, 45 dev-ahead as of today); main's release pipeline auto-bumped through v4.9.0v4.12.0 with none of it merged back to dev, and pyproject.toml on dev is still 4.8.0. RELEASE-CHAIN-001's "Done" verdict fixed the root cause once but nothing re-enforces the merge-back after each release — this is a process gap, not a one-time bug, and it has now recurred.

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

RCA — why Step 3's verification runs weren't diff-scoped Live-reproduced, root-caused 2026-09-02

Raised live during Step 3: all 4 STORY-flow agents ran full 3200+-test suites (12–15 min each) instead of a diff-scoped subset, and this report's own independent re-verification passes did too. Investigated rather than assumed — three separate, unrelated causes, only one of them a design gap:

Net effect: nothing here was fabricated or theatrical — the scoping mechanism exists and works when its inputs resolve correctly, and this session's own fix for the ctx-resolution bug is real and merged. The unscoped runs seen live were the fix's own fix-lag (server not yet restarted) stacked on top of a second, independent gate-reliability gap that no scoping fix would have closed. This report's own manual full-suite reruns during Step 3 were a separate, deliberate choice (raw pytest -q, no nablr tool involved) made for stronger regression confidence, not a symptom of either bug.

Finding 7 — Claims vs. reality High · functional

ClaimRealityVerdict
19 expert personas18 (AVAILABLE_AGENTS); the 19th prompt is the entry pointFixed 2026-09-02 — README + 4 public-docs pages now say 18, regression-tested against the live registry
40+ local scanners12 scan targets, ~17 real engines; own docs page says both "40+" and "10"Fixed 2026-09-02 — now framed as "12 built-in scanners" everywhere, regression-tested
Rules/configs "Pro only"All 34 rule files + 11 YAMLs ship in the free wheel; free tier reads them locallyNot enforced — re-verified 2026-09-09, still true: pyproject.toml's wheel target still ships the whole package tree (no gating found around rule/config file reads; _require_paid only gates the persona-prompt layer, not file access)
Free entry: "activate nablr"activate_nablr prompt is behind _require_paid() — free users get an upsellFixed — re-verified 2026-09-09: prompts_impl.py:72-89's activate_nablr_prompt() has no _require_paid call (docstring cites STORY-FREE-ENTRY-001); every other persona prompt still is gated, confirming only the free entry point was un-gated
Jira sync "Shipped"Reverted; doc still says Shipped; 306-LOC export module survives with zero callersHalf-fixed 2026-09-02 — dead jira_confluence_export.py deleted (0 callers, verified twice); the doc's stale claim explicitly descoped from DOCS-CLAIMS-001 since the file is gitignored/untracked and undeliverable via any commit — needs a manual edit. Re-verified 2026-09-09: the module is now confirmed fully gone (no file, no callers, no residue); docs/features/JIRA_SYNC.md still literally reads "Status: Shipped" — doc claim remains stale, unchanged
Parallel orchestrationGate only, hardcoded 2 agents at PHASE_6; never dispatches (uncommitted epic plans the generalization)Partial — re-verified 2026-09-09, still true: parallel.py:110-155 now generalizes the gate across VERIFICATION and QA_GATE phases via PHASE_CONFIG lookup (an improvement over one hardcoded phase), but L154 is still a fixed [primary_agent, parallel_agent] 2-slot structure — a manual instruction for a human to run two agents, not a real N-agent dispatcher. 2026-09-09 (later same day): now formally tracked — STORY-PARALLEL-NAGENT-001 (Draft, P2) created same day during this pass, with the full 5-reader blast radius enumerated (guard_tools.py, workflow_impl.py, phase_status.py, phase_advance.py, state_mutations.py) — not yet built
Codemap generationRuns, but output has 176 [Desc] placeholders → FEATURES.md degenerates to "2 features" for a 34-tool productPartially fixed — re-verified 2026-09-09: FEATURES.md's literal [Desc] placeholder is gone (0 matches), but it still ends "Total Features: 2" — the degenerate output is unchanged. Across codemap/*.md, placeholder purposes are down to 74 occurrences (from 176), concentrated in generated-test package maps
scan(target="compliance")Advertised in docstring + AGENTS.md; not in the dispatch — invalid target; 246-LOC dead implementationFixed — re-verified 2026-09-09: scan_impl.py:82-84's build_scan_dispatch() now includes a real "compliance" entry wired to compliance.validate_compliance_tool — no longer an invalid target
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) Fixed 2026-09-06 STORY-SCAN-RESULTS-STRUCTURED-001: the :477 discovery-gate site (relocated to workflow_impl.py) now reads a structured AuditResult.status field instead; server.py:321-324's severity-count regex was already superseded by STORY-SEVERITY-PIPELINE-001's structured health_report.json read, kept only as a defensive fallback; 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 AI Blocker Report coverage cross-check External source, re-verified 2026-08-27

A separate artifact ("sef vs. the 2026 AI Blocker Report — Coverage Audit", undated, from an earlier conversation) mapped nablr against "What's Still Blocking Developers in the Age of Frontier AI Coding Assistants" (July 2026), an external published landscape report on AI-coding-assistant failure modes. It is the source document behind four pre-existing epics in this backlog: EPIC-SEC-HARDENING-001, EPIC-ATTACK-SURFACE-001, EPIC-COGNITIVE-DEBT-001, EPIC-AI-PROVENANCE-001. Re-verified against code this pass rather than re-summarized from the artifact's prose.

Result (2026-08-27): 1 of 12 recommended fixes shipped since that artifact was written. The one that landed (override-rate tracking, STORY-074) is real and well-executed. Everything else — including the two highest-priority items, a real SAST engine and a package-existence check — is still exactly as absent as the artifact found it.

Update, 2026-09-08: re-verified directly against code, not taken on any story's own close report. 6 of 12 now shipped — 5 landed since the 2026-08-27 pass, all confirmed Done in docs/requirements/done/: STORY-064 (#1, real SAST engine behind the security gate), STORY-067 (#2, PyPI/npm package-existence/slopsquatting check), STORY-069 (#4, Nablr-Agent git commit trailer bridging nablr's own persona-attribution to git history), STORY-068 (#7, prompt-injection detection on externally-sourced content), and STORY-070 (#8, diff-aware quality scanning keyed on the Nablr-Agent trailer — unblocked once #4 landed, exactly as this table originally predicted). Remaining: #3 (destructive-action interception), #5 (PR/merge-API integration — explicitly deferred by user decision as of this update), #6 (npm/JS license parity), #9 (codemap summarization — STORY-072 drafted with a full precondition/feasibility analysis already written, still Draft), #10 (opt-in telemetry), #12 (validator teeth for Socratic-interrogation — STORY-066, code/tests/docs complete and green, phase-close gate blocked on a dependency that has since resolved; see below).

Update, 2026-09-09 (STORY-066 closed): 7 of 12 now shipped. Implementation had shipped weeks earlier (7f68dfd, 2026-07-24) but never reached PHASE_7 — root-caused, not assumed: get_story_changed_files() has no safe fallback for a story with zero merge-base divergence from dev (true for any story committed directly to dev before per-story worktrees existed), so it silently substitutes whatever files a later, unrelated session happens to have open. Every gate built on it — health, tests_smell, full_suite, and developer's own fr_no_impl FR-traceability check — resolved against the wrong file set, producing both spurious passes and a spurious FAIL_AUTO on this story specifically. Widened the existing DEBT-6d5ceef6 debt row from its originally-reported single call site to its real, confirmed 9-call-site blast radius (gate_checks.py, developer.py, pr_reviewer.py, security_owner.py, impact_architect.py, scan_impl.py, parallel.py, debt_ratchet.py, ai_attribution.py) — impact_architect scored a real fix Coupling+Churn HIGH, correctly its own future fix_flow, not bundled into this closure. Independently re-verified all 8 FRs against SPEC-STORY-066.md by direct source read (bypassing the broken grep-based gate) rather than trusting the gate's own verdict; logged an L2 escalation citing that evidence, then closed via a new evidence-gated script, scripts/close_legacy_developer_gate.py — refuses to touch workflow state unless an L2+ escalation for the exact story_id is already recorded on disk, same precedent as scripts/sweep_stale_closures.py, own 7 regression tests, no mocking. PR review, tester, and security_owner all passed on the real (already-merged) diff. Landed cede392 + 64e7e48, pushed to origin/dev.

Update, 2026-09-09 (later same day, drift-check re-run): 9 of 12 now shipped. Two more stories landed and closed within hours of the reconciliation pass above — verified against code, not their own close reports. STORY-PR-DIFF-CROSSCHECK-001 (#5): github_client.py gained real get_pr_diff/get_pr_comments functions (confirmed present, not stubs); _github_crosscheck.py calls both; _github_diff_crosscheck.py implements the pr_review_diff_drift and pr_review_comments_unaddressed gap codes. Narrower than full merge-API integration (file-path-set diff comparison, not hunk-level; REST comment presence/text-match, not GraphQL resolved-thread state; GitLab still out of scope) but the specific gap this report tracked — "not the actual PR diff or PR comments" — is closed. STORY-DEPENDENCY-CVE-AUDIT-001 (#6, plus a bonus): dependency_audit.py now imports _vulnerability_check.check_vulnerabilities (OSV.dev-backed CVE check) and _js_license_check.parse_package_json (JS/TS SAFE_LICENSES parity) — both confirmed wired into audit_dependencies(), not decorative. Remaining not-started: #3 (destructive-action interception), #10 (opt-in telemetry). #9 (codemap summarization) stays Partial.

Update, 2026-09-09 (FIX-F2CEBCB8): DEBT-6d5ceef6 itself closed, not just its STORY-066 symptom. Run through fix_flow's full pipeline, including a mid-flight correction the discipline was built to catch: the first design (guard the ambient-diff union on canonical_project_root(dir_path) != dir_path, nablr's own worktree-path marker) was implemented, tested, and empirically falsified by 2 real pre-existing tests — a plain solo (non-worktree) checkout and an ad-hoc git worktree add both legitimately need ambient diff, and the marker-based guard dropped it for both, silently defeating a real complexity-budget breach gate in one case. Escalated back through strategist (3-option SCAMPER/decision-matrix, web-researched git-native worktree conventions) and impact_architect (re-scored the same HIGH Coupling/Churn risk against the corrected design) before resuming Developer — re-approved by the human operator. Corrected design needs no new infrastructure: (dir_path / ".git").is_file() (git-native linked-worktree check, any worktree not just nablr's) always trusts ambient diff; a main checkout trusts it only when the already-tested list_registered_worktrees() shows no other worktree currently registered against it — the actual STORY-066 trigger (concurrent dogfooding sessions sharing one canonical checkout), not worktree topology. Also corrected STORY-066's own closure note above: the real blast radius is 8 call sites, not 9 — ai_attribution.py only receives scope_files as a parameter and never calls get_story_changed_files directly, re-verified by direct grep during this fix's forensic phase. 10 new regression tests (real git repos and git worktree add invocations throughout, no mocking), full 14-file call-site sweep 283 passed, whole-repo full suite 3741 passed/0 failed (after confirming an initial 11-failure result was transient concurrent-session flake on rerun, not caused by the fix). PR review APPROVE, security audit PASS. Landed cdb2706, pushed to origin/dev.

Update, 2026-09-09 (full reconciliation pass): every open/partial item in Findings 1–8 and the Blocker Report re-verified directly against current code, not against other docs or story-close reports — the same discipline this report has applied to individual items throughout, now run end-to-end via 5 parallel code-reading passes. Result: 19 items newly confirmed Fixed (artifact-token whitelist+logging, impact_reviewed_verified now evidence-gated, fix_flow no longer pre-grants gates or permanently hijacks the active-story pointer, unknown-phase-string now errors instead of silently passing, migration track now has real PHASE_CONFIG gates, persona rule-coverage/orphaned-file gaps closed, all 5 high-noise scanner false positives, remediation-engine bookkeeping, the YAML config decorative-field gap (closed by deletion), demo_security_scan.sh's false claim, merge_back's audit-trail export, the four-sources-of-truth state consolidation, the free-entry paywall, scan(target="compliance"), the closure-doc generator), 6 partially fixed with the remaining gap now stated precisely instead of left stale (the security scanner's dependency-parsing/secret-suppression halves, health-scan's TECH_DEBT.md-regeneration half, the touched-files timestamp bug specifically, branch protection via a real local push-block, parallel orchestration's phase-generalization), and 1 corrected from "Broken" to Fixed and 1 corrected from "not started" to real partial progress (item #5, a genuine opt-in GitHub PR-status cross-check most of this report's prior passes hadn't caught) — both stale in the artifact's favor of understating progress, the opposite direction from the self-certification risk this report otherwise warns about. Confirmed still genuinely open, unchanged: on-demand agents' false "Workflow Complete" banner, phase numbering's three-way disagreement, the URL-allowlist backslash bypass and subprocess-detection dead rule, git subprocess timeouts, graph node-ID collision, orphan state accumulation (now confirmed worse — 38MB vs. the original 18MB), 16 permanently-skipped tests against a nonexistent script, CI provenance check still warning-only, unenforced "Pro only" rules claim, the Jira doc's stale claim, parallel orchestration's fixed 2-slot structure (now tracked as STORY-PARALLEL-NAGENT-001, Draft), codemap's residual placeholder output, and the Playwright E2E gate remaining a warning not a block. 2026-09-09 (later same day): CVE/vulnerability scanning is no longer on this list — fixed, see the tally update above.

ClusterVerdict (unchanged unless noted)2026-08-27 status
1 · Productivity paradoxNot coveredNo telemetry mechanism added.
2 · Quality & tech debtPartialCodemap autofill mechanism shipped (STORY-CODEMAP-AUTOFILL-001, done/) but real-world coverage is incomplete — spot-checked codemap/src_nablr_security.md still reads the raw placeholder [Describe the purpose of this module].
3 · Security regressionsPartial — strengthened2026-09-08: a real SAST engine now sits behind the security gate (STORY-064, recommendation #1); security_owner.py still separately validates the LLM's own report, not the code — that half is unchanged.
4 · New attack surfacesCovered2026-09-08: STORY-067 (package-existence/slopsquatting, #2) and STORY-068 (prompt-injection, #7) both shipped and closed. Destructive-action interception (#3) is still not found.
5 · Review bottleneckCovered — narrower than the recommendationRe-verified 2026-09-09 (later same day): the opt-in GitHub REST client (integrations/github_client.py, gated by NABLR_ENABLE_GITHUB_PR_CHECK) now also fetches the real PR diff and comment threads (STORY-PR-DIFF-CROSSCHECK-001: get_pr_diff/get_pr_comments, wired into _github_crosscheck.py/_github_diff_crosscheck.py as pr_review_diff_drift/pr_review_comments_unaddressed gaps) — the specific "not diffs/comments" gap this report tracked all along is closed. Still file-path-set comparison, not hunk-level; REST-only, no GraphQL resolved-thread state; GitLab still deferred.
6 · Trust & governancePartial — strengthened2026-09-08: STORY-069 shipped a real Nablr-Agent git commit trailer (#4), which in turn unblocked cluster 2's diff-aware-scanning recommendation (#8, STORY-070) exactly as predicted below.
7 · Cognitive / skill debtAddressedReal progress. STORY-074 shipped utils/override_rate_report.py: real force-bypass/escalation rates computed from handoff_log.jsonl, wired into the executive-summary report, 18+2 real tests, 96% coverage. 2026-09-09: the Socratic-interrogation validator's remaining gap is closed — STORY-066 makes specifier.py's verdict logic block on a missing/empty/placeholder ## Clarifying Questions section, gate-scoped to the live SPECIFICATION phase so Done stories and brownfield On-Demand specs are never retroactively broken.
8 · Legal / IP / licensingCovered2026-09-09 (later same day): dependency_audit.py's SAFE_LICENSES check now also covers JS/TS via package.json (STORY-DEPENDENCY-CVE-AUDIT-001, _js_license_check.py) — confirmed wired into audit_dependencies().
All 12 recommendations, verified individually
#RecommendationStatus
1Real SAST engine behind the security gateDone — STORY-064
2Package-existence check (PyPI/npm) for new depsDone — STORY-067
3Destructive-action interception at runtimeNot started
4AI-authorship tagging (commit trailer)Done — STORY-069, Nablr-Agent git trailer
5Real PR/merge-API integration for pr_reviewerDone, narrowerSTORY-075 + STORY-PR-DIFF-CROSSCHECK-001; opt-in integrations/github_client.py (env-gated) now cross-checks PR existence/CI status, real diff, and comment threads; file-set-only diff comparison, REST-only comments, GitLab deferred
6npm/JS license scanning at parity with PythonDoneSTORY-DEPENDENCY-CVE-AUDIT-001, _js_license_check.py (also added an unplanned bonus: real OSV.dev CVE/vulnerability check, previously "no CVE scanning" on this report's open list)
7Prompt-injection detection on ingested contentDone — STORY-068
8Diff-aware scanning for AI-generated codeDone — STORY-070, unblocked once #4 landed
9Real codemap summarization (LLM-filled purpose)Partial — mechanism shipped, coverage incomplete; STORY-072 drafted (precondition + feasibility analysis written, not yet started)
10Opt-in productivity telemetryNot started
11Override-rate tracking (automation-complacency signal)Done — STORY-074, override_rate_report.py
12Validator teeth for Socratic-interrogation personaDone — STORY-066, closed 2026-09-09 (cede392/64e7e48). Gate was stuck not on missing work but on a tool defect (DEBT-6d5ceef6, 8 call sites); code (7f68dfd) was correct since 2026-07-24. DEBT-6d5ceef6 itself fixed same day — FIX-F2CEBCB8, cdb2706

Where this fits the rest of the backlog: this cross-check is a distinct axis from Findings 1–8 above — those are about whether nablr's own governance mechanisms work as designed; this is about whether nablr's scope covers what an external, independently-sourced landscape report says actually blocks AI-assisted teams. The two overlap once (cluster 3/Finding 4's security-scanner-is-self-proof point), otherwise they're additive. Recommendations 1, 2, 4, 5, 7 are the highest-leverage additions to the Wave 5+ backlog once Waves 0–2 land, since none require the gate-integrity work to land first — they're new scanning/integration surface, not enforcement-of-existing-scope.

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.

Code-verified 2026-08-27: all 7 done. #1 fail-closed Done_unresolved gaps block (validators/__init__.py:286-299, "FR-1/FR-9") · #2 phase-skip Donevalidate_and_advance now has an adjacency check with a logged override escape hatch (state_machine.py:856-871) · #3 severity mapping Done — emoji-vs-enum bug fixed at both call sites (health_scanner.py:515, :620) · #4 path confinement Done — new nablr.utils.path_confine.resolve_within_root, wired into server.py's directory/file_path args (server.py:55,388,410) · #5 CI Done — runs on [main, dev], pyright in the pipeline, real --cov-fail-under=75 + a separate 85% diff-cover step (ci.yml:9,46-49,118-141) · #6 atomic state writes Done_save_state uses tmp+os.replace with a .bak recovery copy (state_machine.py:393-408), extracted into a shared atomic_write_json helper reused by the full-suite report writer too · #7 persona delivery Done_detect_tech_stack now resolves via find_project_root(), not cwd() (start_agent.py:245-250). One residual: full_suite's pass_summary.total/passed/failed are still hardcoded 0/0/0 (tools/runner/report.py) — see Wave 2, FULLSUITE-COUNTS-001, still partial.

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

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 & verified status (2026-08-27)
0Meta-blockersWORKTREE-VENV-001 Done · GATE-FAILCLOSED-001 Done · CI-DEV-BRANCH-001 Done
1P0 correctnessSEVERITY-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 hardening GATE-TOKEN-AUDIT-001 Done · FIXFLOW-INTEGRITY-001 Done (extended further by STORY-FIXFLOW-PERSIST-001, -REVIEW-PARITY-001, -WORKTREE-ISOLATION-001, all done/) · STATE-LOCKING-001 Done · CODEMAP-DEDUPE-001 Done (added to the backlog after the original audit) · FULLSUITE-COUNTS-001 Done (2026-09-01)pass_summary.total/passed/failed now parsed from pytest's own cache (_parse_pytest_counts), no more hardcoded 0; zero-tests case reuses the existing no_runners/greenfield_no_tests bypass, zero changes to transitions.py · MERGEBACK-STATE-EXPORT-001 Done (2026-09-01) — exports .nablr_state/handoff log to main_root before worktree removal; also closed a path-confinement gap on story_id found in review — the open anomaly flagged here (exported artifacts not found post-merge despite passing tests) was root-caused and closed 2026-09-02 as FIX-FE9945E9: handoff_logger.log_agent_activation has no story_id param at all, so it always resolves an ambient session root instead of the story's own worktree; merge_back's export now independently cross-checks the shared handoff log and logs a debt row on a detected misroute instead of a silent no-op · GATE-PHASE-REVERT-001 Done (2026-09-01)fail_phase/revert_phase give a failed phase a real state effect instead of a permanent completed_phases bypass; transitions.py needed zero edits, narrower than planned · GATE-BLOCKERS-001 Done (2026-09-05)validate_and_advance blocked on open blockers and session HALT via TransitionValidator.can_transition() as a byproduct of GATE-PHASESKIP-001 (partial, 2026-08-27); completed 2026-09-05 (c1be360): workflow(next) now checks both too, and HALT flags persist across an MCP-server restart instead of living only in an in-memory set()
3Scanner correctness SCAN-REPORT-PATHS-001 Done (2026-08-28) — split 5 ways under the CRITICAL >10-file gate: ROOT-RESOLVER-001, WRITE-ANCHOR-001, EXCLUSION-ENGINE-001 (9ff4528), PATH-NORMALIZE-001 (d492d18), CLEANUP-001 (640e1f3) — all merged, stray src/ pollution deleted and .gitignore anchoring fixed to stop recurrence · REMEDIATION-BOOKKEEPING-001 Done (2026-09-01) — per-violation status now set from real before/after ruff evidence, matched by id not path string; unrecognized rules route to Tier 2 instead of silently to Tier 1 · SCANNER-FP-001 Done (2026-09-01) — 5 of 7 planned false-positive fixes shipped (comment-smell, debt-ast, docs-scanner, complexity ternary, invest-scorer); dead_code_scanner.py and health_scanner.py (the two highest-churn files) split off into STORY-SCANNER-FP-002 · SCANNER-FP-002 Done (2026-09-02) — DC002 candidate paths now resolve from the true package root (nearest pyproject.toml ancestor, src/-preferred) instead of the scan-invocation directory; comment-smell severity reads through finding.severity instead of hardcoding HIGH; 18 new fixture tests, no UUT mocking; hit HIGH impact_architect gate, human-approved on verified evidence; surfaced a third DEBT-b4a67e86 recurrence (see Step 3 update above) · THRESHOLD-COHERENCE-001 Done (2026-09-02) — CC001 re-keyed onto per-function AST complexity (matches scan(target=complexity)'s model instead of health_scanner's old whole-file thresholds); Thresholds.merge_framework no longer silently resets configured thresholds/drops exclusions (tri-state None-vs-empty tracking of explicitly-configured fields); shared load_ai_patterns_config() fixed a real drift bug (missing /tests/generated/ exclusion). Hit a CRITICAL impact_architect gate (Churn Recency citation gap, see Step 3), human-approved past it · SCANNER-PORTABILITY-001 Done (2026-09-02)resolve_source_packages() now actually wired into test_smell_ast.py's uut_mocked check and code_quality_scanner.py's import-order/stdlib checks (not just the Wave-0 guard); developer validator's structlog/FR-traceability/branch checks are now opt-in via nablr-config.yaml, off by default; proven end-to-end against a new non-nablr fixture project (acme_project) · TESTSMELL-CI-001 Done (2026-09-01) — wired into CI with a ratchet gate against a seeded baseline; closed the scanner's patch.object detection blind spot, verified live against its own test suite (would have caught 2 more findings pre-fix)
4Workflow completion + telemetry CLOSE-PHASE7-001 Done (2026-09-05)close_workflow (in fix_flow_close.py, moved out of fix_flow.py since the story was written) now calls WorkflowManager.update_phase(story_id, PHASE_7) before setting story_closed; the scrum_master closure gate is extracted into a shared closure_gate_check() called identically by merge_back (already hard-blocking) and close_workflow (previously a non-blocking note); a new scripts/sweep_stale_closures.py corrected pre-existing stale closures across the backlog · FPY-METRICS-001 Done (extended by STORY-DEBT-DELTA-REPORT-001, -DEBT-RATCHET-001, -FIX-METRICS-INTEGRITY-001, -FIX-LEDGER-DOUBLECOUNT-001, all done/ — the efficiency-report arc got real follow-on investment) · VALIDATOR-TESTDESIGNER-001 Done (2026-09-02) — new test_designer validator registered (registry unified, _EXPECTED_ROLES now includes it — the 18th, closing Finding 2's registry-drift row); tests_written split from conceptual_tests_written so a JSON plan can no longer satisfy the gate meant for real executable tests; the plan-writer's relative-path/resolver divergence fixed. security_owner found and fixed a real path-traversal bug in the new validator's own artifact_id handling before merge · SESSION-COHERENCE-001 Addressed — superseded in substance by the STATELESS-CONTEXT-001/002/003 arc (all done/), which eliminates the same SessionManager mutable-singleton / active-story process-globals this story targeted · REQDOCS-SERIALIZER-001 Done (2026-09-01) — fixed a ## vs ### section-boundary regex bug affecting 20+ real story docs; TD-/REF-/FIX- doc types now parsed and rendered in INDEX.md · REQDOC-LIFECYCLE-001 Done (2026-09-06) — all 5 ACs verified against code: commit-at-init (init_worktree.py), canonical-path resolution (WorkflowState.story_doc_path, resolve_story_doc()), INDEX.md merge=ours + regen, atomic .md/.json companion rename, create-is-create (update_story split out). DEBT-a19ced2a closeable · MIGRATION-GATES-001 Done, scoped (2026-09-01) — items 2/3/6 shipped (real PHASE_CONFIG entries replace the zero-artifact fallback for all 3 migration personas); items 1/4/5 (PHASE_3_5 disposition, honest on-demand messages, server.py:879 reconciliation) explicitly deferred to a follow-up, no story ID cut yet
5Truth & release LICENSE-INTEGRITY-001 Done · RELEASE-CHAIN-001 Done, but its fix regressed — root cause was a missing insertion_flag anchor that made semantic-release's changelog writer silently no-op, confirmed by running the real CLI before/after against a disposable clone; version reconciled to 4.7.0; the dev/main divergence this flagged (164 vs. 12 commits since merge-base) was fully closed 2026-09-02 (dev/main merged, dev..main 0, pushed to origin) — but re-diverged since: re-verified 2026-09-10 directly against git rev-list, not this row's own prior "fully closed" claim — dev is now 38 commits behind and 45 ahead of origin/main, which auto-released through v4.12.0 with none of it merged back; pyproject.toml on dev is still 4.8.0. The code-level bug this story fixed stayed fixed; the process gap (nothing re-merges main's release commits back to dev after each release) was never actually closed, only reset once · FREE-ENTRY-001 Done (2026-09-01)activate_nablr_prompt() no longer paywalled; the other 16 paid personas' individual _require_paid gates verified byte-for-byte unchanged · DOCS-CLAIMS-001 Done (2026-09-02) — persona/scanner counts corrected to the real, independently-recounted 18/12 (README.md + 4 public-docs pages), with a new regression test reading the live registries directly so this can't drift silently again; corrected one audit error along the way (validators are really 18, not this report's earlier-claimed 17); demo_security_scan.sh's self-contradicting claim fixed; dead jira_confluence_export.py (306 LOC, 0 callers) deleted; phantom compliance scan target dropped from server.py's docstring/tool_names.py (still present in agents_generator.py + 4 rule files, flagged as a follow-up, not silently left). docs/features/JIRA_SYNC.md's stale "Shipped" claim explicitly descoped — the file is gitignored/untracked, undeliverable through any git merge, needs a manual out-of-band edit · PUBLICDOCS-TOOLS-001 Done (2026-09-01) — full rewrite documenting all 36 live @mcp.tool names verified directly against nablr.server.mcp.list_tools(); new drift-guard test introspects the live registry so this can't go stale silently again · SCAN-RESULTS-STRUCTURED-001 Done, scoped (2026-09-06)full_suite_runner.py's earlier split into a typed tools/runner/ package (GateDecision dataclass) via STORY-COMPLEXITY-DEBT-001/STORY-LOC-DEBT-001 was real progress but scoped to full_suite only; this pass fixed the story's own two named regex/__import__ sites directly — discovery_audit.py's audit_discovery() split into run_discovery_audit() -> AuditResult + render_audit_result() -> str (byte-compatible, golden-tested), workflow_impl.py's discovery gate reads AuditResult.status directly, scan_impl.py's full_suite dispatch drops its __import__(...) for a normal import. The general ScanResult-for-all-12-scanners interface is recorded as an explicit follow-up in the closed story doc (AC-4), not silently expanded or dropped. Landed 79142f0

Reading this table (updated 2026-09-06): Waves 0–1 (P0, the product's core promise) are fully shipped — genuine, verified progress in one month. Waves 2–5: all 27 done (26 clean, 1 — SCAN-RESULTS-STRUCTURED-001 — done-and-scoped), after Step 2's 11 disjoint-footprint stories landed 2026-09-01, 8 more live-found bugs closed via fix_flow the next day (see Step 2.5), Step 3's 4 stories (THRESHOLD-COHERENCE-001, SCANNER-PORTABILITY-001, VALIDATOR-TESTDESIGNER-001, DOCS-CLAIMS-001) all landed the day after that (see Step 3), Step 4's 3 orders (GATE-BLOCKERS-001, CLOSE-PHASE7-001, REQDOC-LIFECYCLE-001) landed 2026-09-05–06, and SCAN-RESULTS-STRUCTURED-001 closed the same day — every open anomaly this table previously carried is now resolved. An unplanned 38th story, STORY-MARKERSIGNER-PERSIST-001, landed 2026-09-05 outside this backlog (persisted the full-suite gate's HMAC signing key across MCP-process restarts — see the verdict update above); it touched neither hub file, so it changed nothing about Step 4's readiness. A second unplanned story, STORY-CI-SCOPED-TESTS-001, landed 2026-09-06 (ecb538b) — testmon-scoped local hooks + PR-into-dev CI (see Finding 6); also outside this backlog and touching neither hub file. The entire backlog this report has tracked since 2026-07-28 is done.

Concurrent-safe worktree batches File-footprint analysis

Method: for each not-yet-started or partial story, the file set it will touch was pulled from the acceptance criteria authored for it (this backlog), cross-checked against current code where files have since moved (e.g. full_suite_runner.py's split into tools/runner/report.py + tools/runner/gate.py). Two stories go in the same concurrent batch only if their footprints share zero files — a squash-merge of disjoint-file branches cannot conflict, regardless of story order.

Result: 11 of the 19 remaining stories are mutually disjoint — they can run in 11 separate worktrees right now with no merge-back risk between them. The other 8 cluster around a handful of hub files (server.py, workflow/state_machine.py, tools/workflow/merge_back.py, tools/validators/__init__.py) and need to run one at a time, in the order below. 2026-09-01: the prediction held — all 11 landed the same day with zero merge-back conflicts against each other (every conflict any of them hit was against shared append-only files — docs/TECH_DEBT.md, docs/project_status.md, the daily efficiency report — or against an unrelated concurrent FIX-* session, never against another Step-2 story's real source diff).

Caveat: this is git-mechanical safety, not semantic independence. Two disjoint-file stories can still be logically coupled (e.g. GATE-PHASE-REVERT-001 and GATE-BLOCKERS-001 both extend the blocker/gate model even though only one shares a file with anything below). And a story's real diff can grow past its planned footprint during implementation — run git diff --name-only against the target branch before merge-back as a final check, especially for anything not marked high confidence below.

Verification note (2026-08-27): all 57 file paths below were confirmed to exist against the current tree; the specific functions/symbols each story targets were spot-checked for >70% of the footprint (state_machine.py, merge_back.py, hierarchy_manager.py, validators/__init__.py, tier1_fixer.py, compliance_remediation.py, comment_smell_scanner.py, dead_code_scanner.py, debt_ast_rules.py, test_smell_ast.py, both find_project_root duplicates, release.toml). One drift found and corrected: CLOSE-PHASE7-001's target, close_workflow, moved out of fix_flow.py (now split down to one function) into a new fix_flow_close.py since this story was originally scoped — its underlying finding (no PHASE_7 advance) still holds, re-verified on the correct file. Given the volume of concurrent work landing in this repo, re-run a footprint check like this before actually starting any story below, rather than trusting this table indefinitely.

Second pass (2026-08-27, same day): extended the spot-check to the remaining ~30 unverified symbols across all 19 stories. Two more real findings: GATE-BLOCKERS-001 is actually Partial, not Not-started — validate_and_advance picked up a working blockers/HALT check as an unplanned side effect of GATE-PHASESKIP-001's implementation (both modify the same can_transition() call path); and this story's own originally-authored footprint had a path error (workflow/phase_signals.py, not tools/workflow/). Two claims that looked like they might have been fixed turned out to be false alarms on closer read (config_models.merge_framework still drops exclusions and resets complexity-threshold fields to hardcoded defaults; complexity.py's ternary regex is still the exact r"\?\s*.*\s*:" originally cited). Every other footprint and verdict in this report held.

Read this section top to bottom — each step below assumes every step above it has already merged. (An earlier version of this table put the prerequisite step after the batch that depends on it. Fixed 2026-08-27; if you started from that ordering, stop and re-read from Step 1.)

Step 1 — run first, alone

StoryFootprintWhy first, why alone
SCAN-REPORT-PATHS-001 Doneutils/git_utils.py, tools/context_tools.py, tools/report_utils.py, tools/health_scanner.py, tools/debt_ast_rules.py, tools/dead_code_scanner.py, tools/docs_scanner.py, tools/complexity.py, tools/comment_smell_scanner.py, tools/code_quality_scanner.py, utils/ignore_matcher.py, tools/guard_tools.pyConflicts with two Step 2 stories (SCANNER-FP-001: 5 shared scanner files; MIGRATION-GATES-001: guard_tools.py). It's also the path-normalization convention the Step 2/3 scanner stories should build on — landing it first avoids rebasing their diffs onto a moving target. Outcome (2026-08-28): ran first as planned, but not as one diff — impact_architect's own CRITICAL gate (14 files > the 10-file limit) forced a 5-way split, executed sequentially where footprints overlapped (SCAN-EXCLUSION-ENGINE-001 then SCAN-PATH-NORMALIZE-001 shared 5 files) and validated in fix_flow, not the general STORY track. The gate worked as this report's Finding 1 hoped it would.

Step 2 — once Step 1 merges, run these 11 concurrently (zero shared files) All 11 done, 2026-09-01

StoryFootprintLanded
MERGEBACK-STATE-EXPORT-001tools/workflow/merge_back.pyDone dbb7093
FULLSUITE-COUNTS-001tools/runner/report.py, tools/runner/gate.py, tests/test_full_suite_runner.pyDone 026647a
GATE-PHASE-REVERT-001workflow/state_machine.py, workflow/transitions.py, server.py, workflow/phase_signals.py (corrected 2026-08-27 — no tools/ prefix)Done 9f1181d
REMEDIATION-BOOKKEEPING-001tools/tier1_fixer.py, tools/compliance_remediation.pyDone 398d646 (+ new tools/tier1_fixer_report.py, beyond planned footprint)
SCANNER-FP-001tools/comment_smell_scanner.py, tools/dead_code_scanner.py, tools/docs_scanner.py, tools/debt_ast_rules.py, tools/complexity.py, tools/codemap_freshness.py, tools/invest_scorer.pyDone 157c3c5 (narrowed to 5 files mid-flight; dead_code_scanner.py/health_scanner.py split to STORY-SCANNER-FP-002)
TESTSMELL-CI-001.github/workflows/*.yml, tools/test_smell_ast.py, tests/test_brownfield_populator.py, rules/quality/testing_standards.mdDone bbea494
REQDOCS-SERIALIZER-001tools/requirements_serializer.py, tools/hierarchy_manager.py, tools/index_generator.py, docs/requirements/README.mdDone 349af19
MIGRATION-GATES-001constants.py, tools/guard_tools.py, tools/validators/__init__.pyDone, scoped 4db95d7
RELEASE-CHAIN-001release.toml, CHANGELOG.md, pyproject.toml, PUBLISH.md, core/validation_models.pyDone 1c2f691
FREE-ENTRY-001server_tools/prompts_impl.py, root README.md, public-docs/quickstart.mdx, public-docs/license.mdxDone 81f5d1a
PUBLICDOCS-TOOLS-001public-docs/tools/overview.mdx, docs/USER_GUIDE.md, docs/USP.mdDone 4b4258e

Note the two same-named-looking files that are not a clash: REQDOCS-SERIALIZER-001 touches docs/requirements/README.md, while FREE-ENTRY-001 touches the root README.md — genuinely different files. All 11 commit hashes above verified as real ancestors of dev via git merge-base --is-ancestor, not taken on the agents' word.

Step 2.5 — bugs found live during Step 2, fixed via fix_flow (not backlog stories) All 8 done, 2026-09-02

None of these were in the original 36-story backlog — each is a bug an unattended Step 2 agent tripped over and reported, filed and closed as its own fix_flow ticket rather than folded silently into whichever story happened to be running. Two hit their own HARD_REQUIRED impact_architect gate and were escalated to the human operator rather than self-approved.

TicketBugLanded
FIX-C46FA326Specifier's "Open Questions" validator escalated on any content, including Blocking=No rows and the "No gaps identified" literal that already works for the sibling Clarifying Questions section — the exact defect class cluster 7 of the Blocker Report addendum named from the other direction (no teeth at all)Done 5a47779
FIX-85C47621Stale nablr.nablrco.com support domain in public-docs/quickstart.mdx; canonical is nablr.dev per license_client.pyDone c9d7152
FIX-F00968B7workflow(action="complete") moved a closing story's .md from active/ to done/ but left its .json companion orphaned behindDone 748f8a9
FIX-BC44F80Bcreate_story's own success message hardcoded a stale flat-root path; real file lands in active/ — the tool told callers the wrong place to look for the file it just wroteDone 7e393ae
FIX-8AD8E17Epr_reviewer's injected "Changed files" list could leak a different concurrent session's diff — _get_persona_content dropped an available ctx, and SessionScopeMiddleware never hooked FastMCP's on_get_prompt at all, so raw prompt fetches bypassed session-scoping entirely. Matches tracked DEBT-cf511751. HARD_REQUIRED gate hit (Coupling + Churn Recency, both High) — escalated, human-approved, then mergedDone a0c183e
FIX-FE9945E9Closes MERGEBACK-STATE-EXPORT-001's open anomaly above — handoff_logger.log_agent_activation has no story_id param, always resolves ambient session rootDone f49d7d7
(tier1_fixer test_command)Preemptive hardening: _run_post_fix_test runs a caller-supplied test_command via shell=True with zero validation. Not live-exploitable today (remediate's MCP surface doesn't wire test_command through yet) but added a denylist+allowlist (UnsafeTestCommandError) now, before a future story can wire it in unguarded. HARD_REQUIRED gate hit (Coupling + Churn Recency) — escalated, human-approved, then mergedDone 694575a
FIX-85A0B5C8Bug B: scan()'s diff_scope auto-resolution and pr_reviewer's context injection both attributed a scan to the wrong concurrent story — WorkflowManager.get_active_story()/set_active_story() had no ctx param at all, so resolution fell through to ambient state. Same anti-pattern as FIX-8AD8E17E, different call sites. Reproduced live before fixing. HARD_REQUIRED gate hit (same shape) — escalated, human-approved, then mergedDone 6547ca7

All 8 commit hashes verified as real ancestors of dev via git merge-base --is-ancestor. Immediately after, dev (now carrying all 8 fixes plus a full reconciliation of the historical main divergence) was pushed to origin — the automated devmain PR that push opens (PR #32) failed its own CI lint gate once (a genuinely undocumented dispatcher action, workflow.revert_phase, missing from overview.mdx) and had a real merge conflict against a main that had, in the interim, auto-released a further version bump to 4.7.1 — both fixed and re-pushed rather than force-merged past.

Step 3 — once Step 2 merges, run each of these once its dependency clears All 4 done, 2026-09-02

StoryFootprintWaits on (shared file)Landed
THRESHOLD-COHERENCE-001tools/health_scanner.py, tools/complexity.py, core/config_models.py, core/config_loader.py, core/config_parsing.py, tools/ai_pattern_detector.py, tools/compliance_remediation.pySCAN-REPORT-PATHS-001 (health_scanner.py) + SCANNER-FP-001 (complexity.py)Done 2c41ac1
SCANNER-PORTABILITY-001utils/source_packages.py, tools/test_smell_ast.py, tools/code_quality_scanner.py, validators/developer.py, new validators/_developer_policies.py, utils/artifact_paths.pySCAN-REPORT-PATHS-001 (code_quality_scanner.py) + TESTSMELL-CI-001 (test_smell_ast.py)Done f47a0b2
VALIDATOR-TESTDESIGNER-001new tools/validators/test_designer.py, tools/validators/__init__.py, constants.py, tools/workflow/misc_tools.py, tools/workflow/parallel.pyMIGRATION-GATES-001 (validators/__init__.py, constants.py)Done 40bb78a
DOCS-CLAIMS-001root README.md, 4 public-docs/*.mdx, demo_security_scan.sh, tool_names.py, server.py, deleted tools/jira_confluence_export.pyRELEASE-CHAIN-001 (CHANGELOG.md) + FREE-ENTRY-001 (README.md) + PUBLICDOCS-TOOLS-001 (USP.md) + GATE-PHASE-REVERT-001 (server.py)Done 7b3d48f

All 4 ran as predicted: THRESHOLD-COHERENCE-001 and SCANNER-PORTABILITY-001 concurrently with each other (zero shared files, confirmed no conflict between them), VALIDATOR-TESTDESIGNER-001 and DOCS-CLAIMS-001 independently alongside both. All 3 later merge_backs hit real conflicts against Step 2/2.5's shared append-only docs (TECH_DEBT.md, project_status.md, the efficiency report) — same shape as Step 2's own footnote predicted, resolved by hand each time, never against another story's actual source diff. docs/features/JIRA_SYNC.md's fix was descoped from DOCS-CLAIMS-001 mid-flight: the file is gitignored/untracked, so no commit in this flow can ever touch it — needs a manual, out-of-band edit. All 4 commit hashes verified as real ancestors of dev via git merge-base --is-ancestor / direct content diff (squash-commit messages can retain an earlier sub-commit's text — verified by content, not label).

Step 4 — sequential only, one at a time: the state-machine / merge_back hub All 3 orders done

All three touch workflow/state_machine.py and/or tools/workflow/merge_back.py, which Step 2's GATE-PHASE-REVERT-001 and MERGEBACK-STATE-EXPORT-001 also touch. Run one at a time, after Step 2:

Updated 2026-09-06: Order 1 (GATE-BLOCKERS-001) landed (c1be360) — blockers/L3 HALT now block phase advance via can_transition from both validate_and_advance and workflow(next), and HALT persists across an MCP-server restart. Order 2 (CLOSE-PHASE7-001) landed immediately after and merged to devclose_workflow now advances to PHASE_7 via update_phase, and its closure gate is now the same shared closure_gate_check() merge_back already hard-blocked on; a one-off sweep corrected pre-existing stale closures across the backlog. Order 3 (REQDOC-LIFECYCLE-001) landed (2ee2a19) — commit-at-init, canonical doc-path resolution, INDEX.md merge=ours+regen, atomic .md/.json companion rename, and create-is-create are all real, confirmed by direct grep/commit-ancestry check on all three orders, none taken on a story's own close report. Step 4 is fully done.

OrderStoryFootprint
1GATE-BLOCKERS-001 Done (2026-09-05)workflow/state_machine.py, workflow/transitions.py, tools/workflow/next_agent.py, tools/workflow/misc_tools.py, server.py, session.py (HALT persistence)
2CLOSE-PHASE7-001 Done (2026-09-05)tools/workflow/fix_flow_close.py, workflow/state_machine.py, tools/workflow/merge_back.py, new tools/workflow/closure_gate.py, new scripts/sweep_stale_closures.py
3REQDOC-LIFECYCLE-001 Done (2026-09-06)tools/workflow/init.py, new tools/workflow/init_worktree.py, tools/hierarchy_manager.py, workflow/state_machine.py, tools/workflow/merge_back_clone.py, tools/workflow/fix_flow_close.py, tools/validators/__init__.py, .gitattributes

Update, 2026-09-06: SCAN-RESULTS-STRUCTURED-001 landed (see the waves table above and the closing verdict update) — scoped to discovery_audit.py/workflow_impl.py/scan_impl.py rather than the wider server.py-plus-every-scanner footprint originally guessed here. Nothing remains unscheduled in this backlog.

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 were untracked and have since been deleted by SCAN-CLEANUP-001 (2026-08-28).