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 dev→main 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 place — workflow/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 dev — close_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.
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.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.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.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).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.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.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.tests/generated/ scratch output is no longer git-tracked, no longer collected by default pytest, no longer codemap-indexed (FIX-695F304B).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.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.0–v4.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.scripts/airgap_proof.py, security scan) is a real differentiator — worth keeping honest (see Finding 4).ref:allow) with justification comments.utils/worktree.py + merge_back with conflict-safe git reset --merge; three live worktrees confirm real use.transitions.py:366, guard_tools.py:102) — deliberate anti-drift design. Report freshness (2h health, signed full-suite markers) is enforced.*_deferred tokens write TECH_DEBT rows; force=true logs an L2 escalation and cannot bypass impact_reviewed_*.validate(role="__health__")), retry counter keyed on artifact hash.full_suite_runner.py — the codebase's only atomic write, locked signer, correct double-checked singleton, subprocess timeouts. This is the pattern the rest of the state layer should copy.noqa carries a TECH_DEBT: + story ID; TECH_DEBT.md logs real, reproduced flaws against the project itself (e.g. DEBT-1a5fe7ac, DEBT-ec74396b).Literal action unions, pydantic report models, only 18 type: ignore in ~40k LOC.tmp_path; test_e2e_sdlc_flow.py drives the actual MCP tools through all 8 phases and asserts real gate refusals; several tests are named regression locks for specific prior bugs.The product's promise is enforcement. Today most enforcement paths can be skipped, self-certified, or silently pass when their inputs are missing.
workflow(action="validate_and_advance") accepts any next_phase with no adjacency or artifact check (state_machine.py:582-695). PHASE_0 → PHASE_7 in one call. Already bit you: DEBT-ec74396b. Fixed STORY-GATE-PHASESKIP-001 (c1bf456): validate_and_advance enforces legal phase transitions._unresolved returns allowed=True (validators/__init__.py:240-248) — and every validator returns exactly that when it can't find its file. No artifact ⇒ no validation ⇒ pass. The strictest-looking layer is the weakest. Fixed STORY-GATE-FAILCLOSED-001 (220db52): _unresolved gaps now block with remediation text; only no_project_root stays fail-open; gate exceptions in log(event="phase") also fail closed.docs/requirements/{id}.md, but create_story writes to active/ (hierarchy_manager.py:25-31). Every story created through the supported path silently skips validation via the fail-open above. resolve_story_file_path exists but has only 3 call sites. Fixed e09f005 + STORY-GATE-FAILCLOSED-001: validators route through the shared resolver (flat/active/done/archive), locked by both-paths parity tests; platform_engineer now reads the real report path and timestamp key.log(event="artifact") sets any token true with no whitelist, no verification — including impact_reviewed_provisional, a HARD_REQUIRED gate (misc_tools.py:158-183). And the early return at server.py:428-431 means these grants never reach the handoff log: 0 artifact events in 1,027 log lines. Fixed — re-verified against code 2026-09-09: _gate_whitelist/_classify_artifact_token (misc_tools.py:256-304) now reject any token outside a phase's verified_produces list as "rejected"; every grant/block attempt is logged via handoff_logger.log_artifact_grant (misc_tools.py:346-386, dispatched from server.py:286-289) — the silent-early-return bypass no longer exists.impact_reviewed_verified is both HARD_REQUIRED and a PHASE_4 auto-signal — set true unconditionally on developer phase-close with no evidence check (phase_signals.py:44-50). Fixed — re-verified 2026-09-09: no longer a phase_signals entry at all (constants.py:438-445, explicitly excluded per STORY-GATE-TOKEN-AUDIT-001 FR-8); resolved read-time only by _resolve_impact_reviewed_verified (artifact_resolution.py:137-165), true only when both the CHANGE doc exists AND full_suite_passed is independently true — never phase-close self-certification.completed_phases is a permanent bypass. Once a phase is in the list, all its artifact checks short-circuit true forever (transitions.py:479-482); nothing removes entries, and log(event="phase", outcome="failed") has zero state effect. Rework loops auto-pass health/test/full-suite gates.add_blocker appends to state; the only consumer is can_transition, which production never calls. L1/L2/L3 write identical log rows; "HALT" is display text — nothing prevents the next tool call. HALT flags live in an in-memory set, gone on restart (session.py:86-92). Fixed STORY-GATE-BLOCKERS-001 (c1be360): can_transition is now called from both validate_and_advance and workflow(next); HALT persists across restart.start_fix_flow pre-grants 5 gate artifacts including impact review, omits impact_architect from its injected sequence, and permanently hijacks active.json (never restored — live state shows FIX-611BC079 as active). AGENTS.md's claim that skipping impact review "is blocked at PR review" has no enforcing code. Fixed — re-verified 2026-09-09: fix_flow_start.py:170-186 no longer pre-grants impact-review artifacts (comment cites STORY-FIXFLOW-INTEGRITY-001); impact_architect is step 4 of the injected sequence (fix_flow_start.py:269-274); the prior active story is snapshotted at start and restored on completion (fix_flow_start.py:117, fix_flow_complete.py:135-198) — confirmed live by this session's own FIX-F2CEBCB8, whose impact_architect gate genuinely blocked Developer twice mid-flow until satisfied.log(event="phase", phase="implementation") (name instead of key) resolves no phase → skips produce-gates and validators, reports success (server.py:438-449). Fixed — re-verified 2026-09-09: handle_log_phase_event (workflow_impl.py:110-148) now returns an explicit Status.ERROR ("Unrecognized phase... Refusing to report success") for an unresolvable phase string on outcome="passed", instead of silently skipping the gate.close_workflow sets story_closed but never advances to PHASE_7 — closed stories report in-flight forever; 55 of 77 active/ stories still say Draft for shipped work. Fixed STORY-CLOSE-PHASE7-001 (2026-09-05): close_workflow now calls update_phase(..., PHASE_7); a one-off sweep (scripts/sweep_stale_closures.py) corrected the pre-existing stale closures.migration_planner/parity_tester/cutover_engineer exist as personas but have no PHASE_CONFIG entries → activation with zero artifact requirements. Fixed — re-verified 2026-09-09: constants.py:523-556 now has full PHASE_M1/PHASE_M3/PHASE_M4 entries with primary_agent, required_artifacts, and next_phase chaining migration_planner → parity_tester → cutover_engineer.next_agent.py:189-191).agent(action="activate") injects no persona. It returns a generic "EXECUTION CONTRACT" string and never calls any get_*_prompt() (server.py:859-962). Personas exist only as MCP prompt endpoints, which AGENTS.md never tells the client to invoke. Following your own documented protocol activates a role with zero rule content. The only path that injects a persona is fix-flow. Fixed STORY-PERSONA-INJECT-001 (8f27090, PR #10): agent(activate) now delivers the persona._detect_tech_stack() uses Path.cwd() — the MCP client's launch dir, not the project root — then strips rule sections based on it (start_agent.py:124-140). Frontend/backend rules are dropped or kept essentially at random. Fixed (same story): tech-stack detection uses the project root; orphan rule files shipped into personas.core_protocol.md missing from 8/18 personas (incl. architect, scrum_master, PM); 3 rule files loaded by nobody — including artifact_tokens.md, the doc that explains how to satisfy every gate token. Fixed — re-verified 2026-09-09: start_agent.py now loads core_protocol.md for architect (L629), scrum_master (L759), product_manager (L604), and 14 others — only strategist omits it, with an explicit documented exception (on-demand utility agent). artifact_tokens.md is loaded by developer, scrum_master, pr_reviewer, and impact_architect — no longer orphaned.test_designer). Phase numbering disagrees across three sources (QA_GATE is PHASE_2 but runs after PHASE_3). Fixed (validator coverage half) VALIDATOR-TESTDESIGNER-001 (2026-09-02): test_designer registered, registry unified so the loader and _EXPECTED_ROLES can't drift apart again — validators now genuinely 18/18. AGENTS.md's agent-count text and the phase-numbering disagreement are untouched. Update, re-verified 2026-09-09: agent-count drift is also now closed — AGENTS.md lists 18, AVAILABLE_AGENTS (constants.py:60-78) has 18, both now match. Phase numbering is still genuinely disagreeing: Phase enum (constants.py:317-320) sets QA_GATE = "PHASE_2"/DESIGN = "PHASE_3", but PHASE_CONFIG chains DESIGN's next_phase to QA_GATE — i.e. "PHASE_2" genuinely runs after "PHASE_3" — and AGENTS.md compounds it by labeling architect "Phase 2" and qa_lead/test_designer "Phase 3", the reverse of the enum. Not fixed."🔴 Critical"); the health scanner maps severity.lower() against plain names → never matches → every debt finding downgrades to MEDIUM (health_scanner.py:504-510). Confirmed live: all 659 debt findings in your own report are medium, including 45 bare-excepts configured critical. Debt can never trigger auto-remediation. Fixed STORY-SEVERITY-PIPELINE-001 (45e21f5): one severity vocabulary end-to-end; emoji-vs-name mapping bug removed in health + debt scanners.scan(target="complexity") uses 80/150 and reports the same files OK. Per-function complexity for Python "isn't tracked yet" (complexity.py:259) despite being the advertised metric. Fixed THRESHOLD-COHERENCE-001 (2026-09-02): CC001 re-keyed onto the same per-function AST model scan(target=complexity) already used; file-aggregate reporting split into its own CX-FILE code instead of conflating the two.dead_code_scanner.py:429-468); TS ternary regex matches every optional property; docs scanner flags Markdown headings like "# Get started" as code-echo comments; arg-count rule counts self → every method "missing 1 type hint". Fixed — all 5 — re-verified against code 2026-09-09: comment-smell severity now defaults to MEDIUM, reads through finding.severity (comment_smell_scanner.py:65, sole caller doesn't override); DC002 now resolves candidates via _resolve_package_root() walking to the true package ancestor (dead_code_scanner.py:743-782); TS ternary regex is now r"\?(?!:)(?!\.)[^?:]*:", excluding ?:/?. (complexity.py:299); docs scanner's code-echo check is now restricted to code-file extensions, excluding .md/.yaml (docs_scanner.py:94,247-248); arg-count rule now strips a leading self/cls before counting (debt_ast_rules.py:343-345).PROJECT_SOURCE_PACKAGES=("nablr",) means UUT-mock detection can never fire in any customer project; import-order treats only nablr.* as local; validators hard-require structlog imports and literal FR-\d+ tokens in arbitrary user repos; branch name dev hardcoded in developer validator. Fixed SCANNER-PORTABILITY-001 (2026-09-02): resolve_source_packages() wired into both the UUT-mock check and import-order/stdlib check; developer validator's house-rule checks (structlog, FR-traceability, branch convergence) now opt-in via nablr-config.yaml, off by default; proven end-to-end against a fixture project with a different package name/layout.tier1_fixer.py:78-99); no backup/dirty-check/post-fix test run; completion always logs outcome="passed". Fixed — re-verified 2026-09-09: violations now matched by violation_ids/fixed_violation_ids, never by re-comparing path strings (tier1_fixer.py:159-163); _remap_path refuses candidates outside project_root and skips ambiguous same-basename matches (L226-263); a dirty worktree is refused unless allow_dirty=True (L701-729), with an optional post-fix test_command run; outcome is now computed conditionally, not hardcoded (compliance_remediation.py:889,1144).build/dist/target; only 2 scanners consult the gitignore matcher. Fixed STORY-SCAN-EXCLUSION-ENGINE-001 (9ff4528): IgnorePatternMatcher gains default excludes and becomes the sole engine across 8 sites; segment matching replaces substring (src/builder/ no longer wrongly skipped); compiles once per scan instead of once per file. 4 unrelated security/*.py exclusion sets deliberately deferred (logged as debt) to keep this fix under the same >10-file gate that split its parent story.ast_node/condition parsed and never consumed; config/ai_patterns.yaml never read; framework merge silently resets complexity thresholds to hardcoded values and drops exclusions (config_models.py:141-167). Fixed (the merge half) THRESHOLD-COHERENCE-001 (2026-09-02): tri-state None-vs-empty-frozenset tracking of explicitly-configured fields, so a real user override is never silently overwritten by defaults; load_ai_patterns_config() now actually wired into the AI-pattern detector and health scanner's test-smell check. ast_node/condition consumption is untouched. Update, re-verified 2026-09-09: the decorative half is now also closed — resolved by deletion, not wiring: ast_node/condition fields are removed entirely from config_models.py:22-24, with a comment confirming they were "parsed since inception but never consumed."src/nablr wrote reports and TECH_DEBT into src/nablr/nablr-reports/ and src/nablr/docs/ — inside the shipped package tree. Multiple stray nablr-reports//codemap/ dirs already litter src/. Fixed STORY-SCAN-REPORT-PATHS-001, split 5 ways (impact_architect CRITICAL gate, 14-file diff over the >10-file limit): a single resolver anchors writes at true root regardless of scan scope (SCAN-ROOT-RESOLVER-001, SCAN-WRITE-ANCHOR-001); sub-scanner finding paths normalize to project-root-relative before aggregation, removing two basename-fallback hacks (SCAN-PATH-NORMALIZE-001, d492d18); 11 stray artifact dirs/files deleted under src/ and the .gitignore anchoring bug that let them recur is fixed (SCAN-CLEANUP-001, 640e1f3).scan/remediate/full_suite accept arbitrary absolute paths. A prompt-injected assistant can point the secret scanner at ~/.ssh (masked prefixes written to a report), run tier-1 fixes (file mutation) anywhere, or run full_suite — which executes npm test/gradlew/mvn/cargo in any directory, with repo-supplied nablr-config.yaml extra_args appended verbatim to argv. Fixed STORY-PATH-CONFINE-001 (6675c74, PR #5): MCP tool inputs confined to project root (utils/path_confine.py), full_suite argv hardened.story_id et al. interpolate straight into file paths (f"{story_id}.json"); pydantic fields have no pattern=. A ../-bearing ID writes outside the state dir. Fixed (same story): IDs validated.NABLR_DEV_MODE=1 returns paid status before any check (license_client.py:420-421); transport auth defaults to a verifier accepting every token (auth.py:108-116). Key comparison non-constant-time; paid asset cache never expires offline. Fixed STORY-LICENSE-INTEGRITY-001 (closed 2026-08-09): dev-mode gate now behind _is_editable_install(), not a bare env check; transport auth's _assert_open_mode_safe() raises unless NABLR_INSECURE_AUTH=1 is explicitly set; key comparison uses hmac.compare_digest; cache expiry enforced. Re-verified 2026-09-07 (this row was pulled as still-open from this report's own status list, then found already fixed on direct source read; 30/30 story tests passing) — this bullet was never retroactively annotated until now.dependencies = [ line (reported 0 deps for this very repo), has no CVE check; file audit's "subprocess" detection can never match. Report titles itself "nablr Security Audit" for any project. Partially fixed — re-verified against code 2026-09-09: dependency parsing is fixed (_parse_pyproject, dependency_audit.py:66-89, now correctly returns this repo's real 12 deps, not 0); secret-scan's blanket "line contains test" suppression narrowed to a specific test[_-]?key pattern (secret_scan.py:93). Fixed 2026-09-09 (later same day): CVE/OSV check now real (_vulnerability_check.py, OSV.dev-backed, wired into audit_dependencies() via STORY-DEPENDENCY-CVE-AUDIT-001) — verified by direct read, not the story's own close report; JS/TS license parity also landed same story (_js_license_check.py, package.json-aware). Still genuinely open: URL allowlist's backslash bypass is unchanged (url_scan.py:135-139 still auto-whitelists any URL containing \); file audit's "subprocess" entry still can never match (_get_func_name returns "run"/"Popen", never the literal string "subprocess", file_audit.py:80-88); the "no entropy check" claim above is now stale — a Shannon-entropy fallback exists (FIX-5124835F, secret_scan.py:189-260), verified 2026-09-10 by direct read.get_nablr_state_dir() edits the user's .vscode/settings.json and .cursorignore; a health scan mutates the scanned repo (writes TECH_DEBT.md, relocates docs). Partially fixed — re-verified 2026-09-09: get_nablr_state_dir() still writes .vscode/settings.json/.cursorignore on first creation (git_utils.py:586-631, unchanged) — open. But health scan no longer regenerates TECH_DEBT.md wholesale (health_scanner.py:1440-1444 hardcodes an empty status, per FIX-9CBDE4D8) — Fixed 2026-09-09 (later same day): the ARCHITECTURE.md/EXTERNAL_DEPS.md relocation is now gated behind an explicit relocate_artifacts parameter, default False (FIX-76D75EB2, health_scanner.py:1375-1466) — a health scan is read-only by default now, closing the last open half of this finding. Introduced two low/medium debt rows in the process (DEBT-a9d3adfa lint, DEBT-22a9902d pre-existing scan_health() LOC-over-threshold, both logged not silently absorbed).demo_security_scan.sh claims "no network calls" then instructs the customer to run the exact grep that disproves it (urllib in license_client.py). README's "exactly one category of outbound call" is the honest framing — use it everywhere. Fixed — re-verified 2026-09-09: script text no longer claims zero network calls; reframed to the honest "exactly one category of outbound call" claim (STORY-DOCS-CLAIMS-001 FR-4/FR-5), grep instructions now expect exactly one hit (license_client.py) instead of implying zero.None = "no such story", and re-init dead-ends in FileExistsError → story permanently unopenable (state_machine.py:239-265, init.py:303). Worse: a corrupted story file makes the scrum-master close-out gate pass (requirements fall back to []). Fixed STORY-STATE-ATOMIC-001 (PR #4, 06ed98e): atomic write/read for state layer.tester/security_owner completions lose updates (parallel.py:165); story_lock exists but guards 2 of ~9 mutation sites, and its own dict insertion races. Fixed STORY-STATE-LOCKING-001 (closed 2026-08-10): 9 mutation methods incl. parallel.py's update_parallel_task now wrap their full read-modify-write in SessionManager.get().story_lock(story_id); registry insertion itself made race-free via a _registry_lock. Re-verified 2026-09-07 (this row was pulled as still-open from this report's own status list, then found already fixed on direct source read) — never retroactively annotated until now.merge_back deletes the audit trail. Worktree removal takes .nablr_state/ and handoff_log.jsonl with it — phase history vanishes at merge with no export (merge_back.py:206). Fixed — re-verified 2026-09-09: _export_story_state() (merge_back.py:141-181) copies the story's workflow state and appends the handoff log to the canonical checkout before worktree removal, and aborts removal on export failure (call site, L519-525).DEBT-1d576de9 — cross-worktree corruption already logged). Fixed (largely) — re-verified 2026-09-09: session.py:86-99 confirms activation state moved to story-scoped disk records (STORY-STATELESS-CONTEXT-003); no module-level active_story/ACTIVE_STORY global remains anywhere in the codebase. Only documented, intentional in-memory remnants stay in SessionState (a default-root shim, fallback agent, migration toggle, persona-hash cache) — not the four independent sources originally found.touched_files_since_story_init by the UTC offset; second-resolution graph node IDs collide; git subprocesses have no timeouts (a credential prompt hangs the MCP handler). Partially fixed — re-verified 2026-09-09: the specific touched_files_since_story_init bug no longer reproduces (WorkflowState.__post_init__ and the reader both use naive-local time consistently, state_models.py:106-111, git_utils.py:906) — but a broader naive/UTC mix persists elsewhere (context_graph.py/handoff_logger.py use UTC-aware). Node-ID collision is unchanged: context_impl.py:49 still builds IDs from second-resolution strftime, no microseconds/uuid. Git subprocess timeouts are unchanged: all 12 subprocess.run calls in git_utils.py still lack timeout=..nablr_state/; 15MB unignored sef-reports/. Still open, worse — re-verified 2026-09-09: .nablr_state/ is now 38MB (up from 18MB) with 145 workflow FIX-state files vs 13 fix_flow session files; sef-reports/ still has no dedicated .gitignore rule (only incidentally caught by a generic *.json pattern). Orphan accumulation has not been addressed and has grown.ci.yml triggers on main only; the active branch is dev; story-branch PRs into dev match nothing. Tests first run at the batched dev→main PR, long after stories are declared Done. Fixed STORY-CI-DEV-BRANCH-001 (c452af5): push+PR triggers on [main, dev]; paths-ignore narrowed to pure prose; lint/security/test parallel with build gating on all three.--cov-fail-under=0 in a job named "Test & Coverage" (ci.yml:93), against a stated bar of ≥85% on changed files. No coverage config exists anywhere; coverage.xml is produced and discarded. The 85%-on-changed-files bar is currently uncomputable (no diff-cover). Fixed CI-DEV-BRANCH-001 uploads coverage.xml every run; STORY-COVERAGE-GATE-001 (97c4670, PR #11) sets a real floor (--cov-fail-under=75, ratchet-only policy) and adds diff-cover for the changed-files bar.pass_summary total/passed/failed are hardcoded 0 literals (full_suite_runner.py:689-693); evaluate_gate checks signature/freshness/exit codes only. A run collecting nothing passes. The shape-lock test asserts only that the key exists.tests/generated/ are assert symbol is not None (6 of 7 for the same symbol), regenerated by design, untracked in git and invisible to CI — yet counted in local green runs and already indexed in codemap. Fixed FIX-695F304B (2026-09-07): pyproject.toml's norecursedirs excludes tests/generated from default pytest collection (symbol_smoke_runner.py's own direct invocation against it is unaffected); .gitignore entry + git rm -r --cached untracks the 44 files that had accumulated (kept on disk); codemap.py's find_python_packages() gains a matching /tests/generated/ substring exclusion so these directories are never indexed as packages.scripts/propagate.sh, which doesn't exist — the "17 skipped" your status reports record as green. Two real red tests sit in .pytest_cache/lastfailed.patch.object blind spot — the strongest UUT-mock violation in your own suite (test_brownfield_populator.py:31-75) is invisible to it.bandit --exit-zero discards MEDIUM/LOW Fixed (CI-DEV-BRANCH-001: scripts/ci_bandit_gate.py — HIGH/CRITICAL fail, MEDIUM annotated; STORY-CI-SCOPED-TESTS-001, 2026-09-06: pyright now runs in pre-commit too, whole-repo, ~7s). No branch protection; provenance check is a warning, not a block. Partially fixed — re-verified 2026-09-09: native GitHub branch protection/rulesets are genuinely unusable (plan-gated, needs GitHub Team — see STORY-BRANCH-RULESET-001's own closed doc); as a substitute, scripts/block_main_push.sh is wired into the pre-push hook and does actually block direct pushes to main (verified live, PR #2/#80). The CI provenance check itself is confirmed still open — ci.yml's verify-merge-provenance job still only emits ::warning:: on a non-merge-commit HEAD, never fails the build.CI-DEV-BRANCH-001 put the full suite on the right trigger (Wave 0) but never scoped it to the change. Fixed STORY-CI-SCOPED-TESTS-001 (ecb538b, 2026-09-06, unplanned): pre-commit/pre-push and the PR-into-dev CI job now run only pytest-testmon-selected tests; PR-into-main and the push-triggered job stay unconditionally full-suite, preserving the merge-to-main invariant this report has repeatedly flagged as the one gate that must never be weakened. Live-reproduced during PR review, not assumed: pytest-testmon crashes (sqlite3.DatabaseError/INTERNALERROR) rather than degrading on a corrupt .testmondata, contradicting its own designed fallback — fixed with a new scripts/testmon_guard.py pre-flight, wired into both CI and local hooks, verified live end-to-end.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:
run_full_suite() (full_suite_runner.py:183-227) genuinely narrows pytest to changed files + direct importers when given a file list (_apply_scope_to_runners, landed 42dedb5, over a month before this session — not new, not half-built). But whether that file list ever gets computed depends on _resolve_effective_diff_scope() (scan_impl.py:131-163) correctly resolving the caller's active story. Before today's FIX-85A0B5C8, that resolver preferred stale ambient session state over the caller's own explicit ctx — so for a STORY-flow subagent it silently resolved to "no active story" → diff_scope=False → scope_files=None → unscoped full run, every time. tests/test_scan_diff_scope_ctx_precedence.py reproduces exactly this failure mode.DEBT-b4a67e86, tracked before this session) — it keeps executing whatever code was in memory at process start until restart. So even after FIX-85A0B5C8 merged to dev, the still-running server kept serving the pre-fix resolver to all 4 Step-3 agents the same day — a live instance of the same version-skew this report already flags for the uvx nablr-vs-checked-out-source split (see the 2026-09-02 Step 3 verdict update above).full_suite gate is separately, structurally unable to trust an out-of-process run — unrelated to scoping. evaluate_gate() (gate.py:82-117) requires a report "signed" by an in-memory marker in a module-level set() (report.py:36-60), populated only inside the same process's own run_full_suite() call. A raw pytest process (spawned via Bash, by an agent or by this report's own re-verification passes) can never register into that set and so can never pass the gate, regardless of whether it was diff-scoped. DEBT-f750b0a6 already documented the marker not surviving across MCP tool-call boundaries even for legitimate in-tool calls; DEBT-24cd66f0 documented whole-repo full_suite calls timing out outright (exit 124). This — not the scoping bug above — is why agents kept logging full_suite_deferred/full_suite_deferred_tool_report_unreliable (DEBT-560f061d) and fell back to raw pytest, which is inherently unscoped by construction (a bare CLI invocation with no path filter), independent of nablr's own diff_scope feature entirely.filter_files_to_scope(), e.g. health_scanner.py:454-458) — but the existing test coverage (test_scan_diff_scope.py) only asserts on report content, not on which files were actually walked, so it proves the narrowing landed for the cases exercised today (FIX-85A0B5C8's own scenario) without proving every sub-check is scoped. Worth a dedicated coverage pass, not re-architecture.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.
| Claim | Reality | Verdict |
|---|---|---|
| 19 expert personas | 18 (AVAILABLE_AGENTS); the 19th prompt is the entry point | Fixed 2026-09-02 — README + 4 public-docs pages now say 18, regression-tested against the live registry |
| 40+ local scanners | 12 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 locally | Not 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 upsell | Fixed — 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 callers | Half-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 orchestration | Gate 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 generation | Runs, but output has 176 [Desc] placeholders → FEATURES.md degenerates to "2 features" for a 34-tool product | Partially 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 implementation | Fixed — 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 / licensing | Implemented properly (mint, device dedup, offline grace) | Real |
| Worktree isolation, browser E2E stepper, air-gap proof | Implemented (browser fixed at bcf0fcd; was hardcoded-success before) | Real |
public-docs/tools/overview.mdx: all 18 documented tool names are pre-consolidation — none exist (scan_health → scan(target="health"), activate_key → set_license_key, …). remediate, agent, workflow, validate, browser etc. publicly undocumented.release.toml changelog mode=update with no insertion flag and no marker in CHANGELOG.md → semantic-release silently never updates it.INDEX.md: claims 83 stories (actual 104), 6 epics (actual 11); status buckets include **|:** 11 from the fallback regex matching legacy metadata tables (requirements_serializer.py:137); 14 stories render "(untitled)"; TD/REF/FIX docs invisible to the generator.docs/requirements/TD-011…TD-015 belong to a different product (education app: mindmaps, tutors, EPIC-19/20) — cross-project write consistent with the stale-root bug in DEBT-1d576de9.active/ (STORY-011×3, BRANCH-RULESET, CI-MINUTES); archive/ never created despite the Done story specifying it; 22 md/json companion pairs split across directories; all 21 new uncommitted docs written to flat root, ignoring the subfolder convention.docs/requirements/README.md 7 months stale; USER_GUIDE.md references renamed tools; CHANGELOG headed "SEF Agents" (already fixed upstream, confirmed 2026-09-02); README badge said Python 3.13+ vs requires-python >=3.12 Fixed 2026-09-02 (DOCS-CLAIMS-001); quickstart pointed to nablr.nablrco.com while everything else says nablr.dev Fixed 2026-08-28 (FIX-85C47621); dist/ holds builds from two identities (nablr, ref_agents).| corrupts its own columns (the exact bug the row describes); manual registry severities invisible in the auto-rollup below it.Ran scan(target="health") and scan(target="security") on src/nablr as part of this audit:
| Metric | Result | Note |
|---|---|---|
| Critical / High / Medium | 93 / 107 / 1,638 | Product enforces "block on critical" for users |
| Files over complexity 30 (Tier-3 "full redesign") | 73 | Product mandates ≤15 per function; own worst: server.py CC 211, 1,793 LOC |
except Exception without re-raise | 34 critical hits | Own protocol forbids it; 29 handlers are body = pass |
| Security self-scan | FAIL (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 placement | Wrote 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, :477 — if "❌" 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.
scan(target="regression") is filename-keyword matching wired to no gate.test_designer validator; conceptual-test plan sets tests_written three phases before executable tests exist (misc_tools.py:301). Fixed 2026-09-02 VALIDATOR-TESTDESIGNER-001: validator added and registered; token split into tests_written (real tests) vs. conceptual_tests_written (the plan) so the early-satisfaction gap is closed.closure_gate.py:75-119 now defines generate_closure_doc(), auto-writing docs/closure/{story_id}-CLOSURE.md from workflow state (never overwrites a hand-written doc), called from fix_flow_close.py:389; docs/closure/ now has 12 files, up from 2. Currently wired only from the fix-flow close path, not the main STORY workflow close — a narrower fix than "generator for all stories," but the mechanism is real, not vaporware.parallel.py:197-219) — contradicts the tester persona's "will be BLOCKED".guardian-ci.mdx markets the provenance story before the git-trailer bridge exists.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.
| Cluster | Verdict (unchanged unless noted) | 2026-08-27 status |
|---|---|---|
| 1 · Productivity paradox | Not covered | No telemetry mechanism added. |
| 2 · Quality & tech debt | Partial | Codemap 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 regressions | Partial — strengthened | 2026-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 surfaces | Covered | 2026-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 bottleneck | Covered — narrower than the recommendation | Re-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 & governance | Partial — strengthened | 2026-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 debt | Addressed | Real 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 / licensing | Covered | 2026-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(). |
| # | Recommendation | Status |
|---|---|---|
| 1 | Real SAST engine behind the security gate | Done — STORY-064 |
| 2 | Package-existence check (PyPI/npm) for new deps | Done — STORY-067 |
| 3 | Destructive-action interception at runtime | Not started |
| 4 | AI-authorship tagging (commit trailer) | Done — STORY-069, Nablr-Agent git trailer |
| 5 | Real PR/merge-API integration for pr_reviewer | Done, narrower — STORY-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 |
| 6 | npm/JS license scanning at parity with Python | Done — STORY-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) |
| 7 | Prompt-injection detection on ingested content | Done — STORY-068 |
| 8 | Diff-aware scanning for AI-generated code | Done — STORY-070, unblocked once #4 landed |
| 9 | Real codemap summarization (LLM-filled purpose) | Partial — mechanism shipped, coverage incomplete; STORY-072 drafted (precondition + feasibility analysis written, not yet started) |
| 10 | Opt-in productivity telemetry | Not started |
| 11 | Override-rate tracking (automation-complacency signal) | Done — STORY-074, override_rate_report.py |
| 12 | Validator teeth for Socratic-interrogation persona | Done — 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.
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.
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.
| Surface | Cost | Note |
|---|---|---|
| AGENTS.md (always-on) | ~3.5k tok | Reasonable |
| Persona payloads | 2.0k–8.9k (developer max, mean ~5k) | Lean — but currently ~0 delivered (Finding 2) |
| Rule library | 49.2k across 34 files | Loaded per-role only — good design |
| Health report / TECH_DEBT / INDEX | 3.4k / 7.1k / 3.9k | Fine |
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.
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_report → nablr-reports/efficiency/, today's numbers committed as the day-zero baseline.
*_unresolved gaps must block, not pass (validators/__init__.py:240-248); route all story-path lookups through resolve_story_file_path.validate_and_advance gets adjacency + artifact checks; remove or gate log(event="artifact") self-certification behind a verified whitelist, and log every grant.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.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).full_suite_runner pattern (tmp + os.replace, lock) into state_machine._save_state and friends; treat JSONDecodeError as corruption, not absence.agent(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 Done — validate_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.
.nablr_state/ + handoff log before merge_back removes the worktree; restore the active-story pointer after fix-flow.suggest_next_agent); add revert_phase so failed phases can re-gate.activate_nablr (it's your funnel), delete or ship jira_confluence_export, remove the phantom compliance target, fix demo_security_scan.sh.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).server.py (registration vs orchestration vs watcher lifecycle) and give scanners a structured result type — stop regex-parsing your own markdown for control flow.src/nablr/codemap/, stray report dirs); gitignore sef-reports/ or delete it.test_designer validator.active/ statuses (the close protocol works — Finding 1's closure bug is why they're stale).IgnorePatternMatcher.server.py, health_scanner.py, transitions.py resolve_artifact → token-resolver registry).artifact_tokens.md into the personas that need it — it's the manual for your own gates and nobody reads it.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).
| Wave | Theme | Stories & verified status (2026-08-27) |
|---|---|---|
| 0 | Meta-blockers | WORKTREE-VENV-001 Done · GATE-FAILCLOSED-001 Done · CI-DEV-BRANCH-001 Done |
| 1 | P0 correctness | SEVERITY-PIPELINE-001 Done · GATE-PHASESKIP-001 Done · STATE-ATOMIC-001 Done · PATH-CONFINE-001 Done · COVERAGE-GATE-001 Done · PERSONA-INJECT-001 Done |
| 2 | Gate + 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()
|
| 3 | Scanner 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)
|
| 4 | Workflow 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
|
| 5 | Truth & 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.
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.)
| Story | Footprint | Why first, why alone |
|---|---|---|
SCAN-REPORT-PATHS-001 Done | utils/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.py | Conflicts 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. |
| Story | Footprint | Landed |
|---|---|---|
MERGEBACK-STATE-EXPORT-001 | tools/workflow/merge_back.py | Done dbb7093 |
FULLSUITE-COUNTS-001 | tools/runner/report.py, tools/runner/gate.py, tests/test_full_suite_runner.py | Done 026647a |
GATE-PHASE-REVERT-001 | workflow/state_machine.py, workflow/transitions.py, server.py, workflow/phase_signals.py (corrected 2026-08-27 — no tools/ prefix) | Done 9f1181d |
REMEDIATION-BOOKKEEPING-001 | tools/tier1_fixer.py, tools/compliance_remediation.py | Done 398d646 (+ new tools/tier1_fixer_report.py, beyond planned footprint) |
SCANNER-FP-001 | tools/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.py | Done 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.md | Done bbea494 |
REQDOCS-SERIALIZER-001 | tools/requirements_serializer.py, tools/hierarchy_manager.py, tools/index_generator.py, docs/requirements/README.md | Done 349af19 |
MIGRATION-GATES-001 | constants.py, tools/guard_tools.py, tools/validators/__init__.py | Done, scoped 4db95d7 |
RELEASE-CHAIN-001 | release.toml, CHANGELOG.md, pyproject.toml, PUBLISH.md, core/validation_models.py | Done 1c2f691 |
FREE-ENTRY-001 | server_tools/prompts_impl.py, root README.md, public-docs/quickstart.mdx, public-docs/license.mdx | Done 81f5d1a |
PUBLICDOCS-TOOLS-001 | public-docs/tools/overview.mdx, docs/USER_GUIDE.md, docs/USP.md | Done 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.
fix_flow (not backlog stories) All 8 done, 2026-09-02None 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.
| Ticket | Bug | Landed |
|---|---|---|
FIX-C46FA326 | Specifier'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-85C47621 | Stale nablr.nablrco.com support domain in public-docs/quickstart.mdx; canonical is nablr.dev per license_client.py | Done c9d7152 |
FIX-F00968B7 | workflow(action="complete") moved a closing story's .md from active/ to done/ but left its .json companion orphaned behind | Done 748f8a9 |
FIX-BC44F80B | create_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 wrote | Done 7e393ae |
FIX-8AD8E17E | pr_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 merged | Done a0c183e |
FIX-FE9945E9 | Closes MERGEBACK-STATE-EXPORT-001's open anomaly above — handoff_logger.log_agent_activation has no story_id param, always resolves ambient session root | Done 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 merged | Done 694575a |
FIX-85A0B5C8 | Bug 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 merged | Done 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 dev→main 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.
| Story | Footprint | Waits on (shared file) | Landed |
|---|---|---|---|
THRESHOLD-COHERENCE-001 | tools/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.py | SCAN-REPORT-PATHS-001 (health_scanner.py) + SCANNER-FP-001 (complexity.py) | Done 2c41ac1 |
SCANNER-PORTABILITY-001 | utils/source_packages.py, tools/test_smell_ast.py, tools/code_quality_scanner.py, validators/developer.py, new validators/_developer_policies.py, utils/artifact_paths.py | SCAN-REPORT-PATHS-001 (code_quality_scanner.py) + TESTSMELL-CI-001 (test_smell_ast.py) | Done f47a0b2 |
VALIDATOR-TESTDESIGNER-001 | new tools/validators/test_designer.py, tools/validators/__init__.py, constants.py, tools/workflow/misc_tools.py, tools/workflow/parallel.py | MIGRATION-GATES-001 (validators/__init__.py, constants.py) | Done 40bb78a |
DOCS-CLAIMS-001 | root README.md, 4 public-docs/*.mdx, demo_security_scan.sh, tool_names.py, server.py, deleted tools/jira_confluence_export.py | RELEASE-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).
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 dev — close_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.
| Order | Story | Footprint |
|---|---|---|
| 1 | GATE-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) |
| 2 | CLOSE-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 |
| 3 | REQDOC-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.
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 (e1a21d3 → bcf0fcd, 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).