`LearningConsolidationWorker.getDomainPatterns()` mapped
`lastSeen: p.lastUsedAt`, but `lastUsedAt` arrives from the
`learning:pattern:*` kv as an ISO string (not a Date) after the JSON
round-trip. The two downstream consumers — `consolidatePatterns` and
`pruneIneffectivePatterns` — both call `.getTime()` on it, throwing
`TypeError: lastSeen.getTime is not a function` every cycle on installs
with non-empty pattern kv.
This was dead code until v3.9.32: #491 Bug 1+2 starved the pattern store
so `collectPatterns()` always threw `No learning patterns to consolidate
yet` before reaching `consolidatePatterns`. Fixing the #491 chain made
this line reachable for the first time and the same `Date`-lost-across-
kv-JSON-round-trip class of bug as #491 Bug 3 surfaced. Other call sites
were hardened in v3.9.32 (`pattern-store.ts:1301-1304`,
`pattern-lifecycle.ts:592-594`, `sqlite-persistence.ts:891`); these two
were missed because they were unreachable at the time.
Fix: rehydrate at the kv-read boundary — `lastSeen: new Date(p.lastUsedAt)`.
`new Date(x)` is safe for `Date | string | null | undefined` (yields
Invalid Date — whose `.getTime()` returns `NaN` rather than throwing — on
null-ish input). One-line change covers both downstream consumers.
Test: `learning-consolidation-date-coercion.test.ts` exercises the full
post-mining half of the loop (the gap Jordi flagged in his release-gate
suggestion). Seeds a `learning:pattern:*` entry with ISO-string
`lastUsedAt`, runs the worker through `getDomainPatterns` →
`consolidatePatterns` → `pruneIneffectivePatterns` → full `doExecute`,
and asserts:
1. The seam rehydrates `lastSeen` to a real Date.
2. `consolidatePatterns` completes without throwing.
3. The recent-high-performer filter math actually counts the pattern
(not just "doesn't throw" — catches silent-suppression regressions).
4. `pruneIneffectivePatterns` completes and produces the stale finding.
5. Full `doExecute` reaches `Learning consolidation complete` and
`recordLoopHealth(success:true)` — the user-visible signals.
Verified pre-fix with 5/5 failing tests showing the exact error from
the issue; post-fix with 5/5 green, full learning-consolidation suite
(36 tests) green, and `tsc --noEmit` clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #491 — `aqe learning loop-health` permanently showed
`LearningConsolidationWorker ○ never-ran` even when the worker
executed every cycle. Root cause: in `doExecute`, the
`recordLoopHealth(success:true)` ping sat *after* `collectPatterns()`,
which throws on installs with nothing to consolidate (the common case
for fresh users). The error propagated up before liveness was ever
recorded, so the dashboard had no idea the worker was alive.
Fix: wrap the worker body in try/catch/finally — the same shape
CapturedExperienceBridge.drainSafe already uses (which is why the
bridge correctly shows `live` and the worker did not). The `finally`
emits `recordLoopHealth` with `success:true` on a clean tick and
`success:false + error` on a throw, then the catch rethrows so
BaseWorker's retry/error tracking sees the failure unchanged. The
liveness write is itself wrapped in try/catch so a memory-backend
hiccup can't shadow the worker's real error.
Liveness contract test:
tests/unit/workers/workers/learning-consolidation-liveness.test.ts
pins three behaviors:
1. Throw inside collectPatterns → recordLoopHealth still called
with `{success:false, error}` (THIS is what regressed before).
2. Successful tick → recordLoopHealth called with `{success:true}`.
3. recordLoopHealth itself throwing must not shadow the original
worker error.
Verified red→green: with the try/finally reverted, assertion (1)
fails with "expected number of calls: 1, received: 0" — exactly the
"never-ran" symptom from the issue. With the fix in place, 3/3 pass
in ~19ms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The producer side of the learning-optimization domain pipeline had no
auto-trigger for `mineExperiences`. After v3.9.29's CapturedExperienceBridge
landed, `learning:experience:*` kv populates correctly — but the chain
stopped there because `mineExperiences` was only callable via four manual
MCP tools or an explicit `task_submit`. Nothing scheduled it.
Verified consumer survey before choosing the fix shape:
- `learning:pattern:*` kv writers/readers are all scoped to the
learning-optimization domain + coordination layer
- `qe_patterns` table is the qualified multi-domain catalog with
distinct schema (tier, quality_score, embedding, deprecated_at,
token tracking, successful_uses) — NOT a duplicate of the kv
- The two stores represent sequentially different lifecycle phases:
kv = candidate working memory, table = qualified catalog
- Deprecating the kv would have wide cross-domain blast radius;
auto-triggering `mineExperiences` keeps the boundary intact
Implementation:
- new private `mineExperiencesPerDomain` step in
`runContinuousLearningLoop` (called as Step 7 after lifecycle stats)
- iterates ALL_DOMAINS, reads per-domain cursor from
`learning:consolidation-cursor:{domain}` kv, calls the canonical
`LearningCoordinatorService.mineExperiences(domain, timeRange)`
via `LearningOptimizationExtendedAPI.getLearningService()`
- advances cursor ONLY on `experienceCount > 0` — failures and
empty windows leave the cursor untouched so the next 30-min tick
retries the same window with new experiences
- clamps future/corrupted cursors to the default 1-day lookback
- per-domain try/catch isolates failures (one bad domain doesn't
block the others)
- graceful no-op when the learning-optimization API is unavailable
Why direct service call instead of `runLearningCycle`:
`runLearningCycle` also spawns a learning agent, runs strategy
optimization, and creates knowledge. Any of those can fail and block
mining. The direct service call is the minimum sufficient producer.
Why cursor at all:
`learnPattern` uses `uuidv4()` for pattern IDs and `Date.now()` for
names, so re-mining the same experiences would create duplicate
`learning:pattern:*` rows. The cursor is essential for idempotency
across worker ticks.
7 new tests cover: per-domain invocation, cursor advance on success,
no-advance on failure, throw isolation, cursor reuse as next window
start, future-cursor clamping, and graceful no-op when the API is
unavailable.
463/463 unit tests pass across workers + CLI suites; build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Coverage data never reached quality assessment because of fragmented key
conventions, fabricated fallback values, and missing --coverage flags in
test commands. This commit fixes the entire coverage data pipeline:
- Remove fabricated coverage (up to 95%) when no real data exists
- Unify 5 orphaned key conventions into consistent coverage:latest,
coverage:previous, and coverage:file:{path} keys
- Add --coverage flags to vitest/jest test commands
- Read coverage from disk (coverage-summary.json) when not in stdout
- Store per-file coverage via memory.set() (not storeVector) so
quality-analyzer can read it
- Rotate coverage:previous only in coverage-analyzer (canonical writer)
to prevent double-rotation race conditions
- Fix defect-predictor and defect-investigation orphaned key reads
- Replace hardcoded fake data in coverage-tracker worker with real reads
- Use safeJsonParse and path sanitization in disk coverage reader
- Report coverage: -1 (not 0) when no data available to avoid
false 25-point quality score penalty
Closes#385
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
125 test files had beforeEach/beforeAll but no afterEach/afterAll,
leaking state between tests and causing order-dependent failures.
Added cleanup to 104 files (skipped 4 archived, 16 import-only):
- 2 critical DB handle leaks (witness-chain): added db.close()
- 7 high-risk service files: added vi.restoreAllMocks()
- 7 beforeAll files: added matching afterAll
- 88 medium/low risk: added afterEach with appropriate cleanup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add YAML deterministic pipeline loader and registry (Imp-9) for token-free workflow execution
- Add validation pipeline skill and MCP handler (BMAD-003) with 13-step requirements validation
- Add cross-phase signal handlers for QCSD feedback loops (store, query, agent_complete, phase events)
- Add heartbeat scheduler worker (Imp-10) for token-free background maintenance every 30 minutes
- Add defect intelligence and requirements traceability context sources
- Register all new MCP tools in protocol-server and server
- Update skills manifests, tests, and documentation
chore(release): bump version to v3.7.19
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>