1611 Commits

Author SHA1 Message Date
Dragan Spiridonov c6742345dd fix(hooks): migrate qe_trajectories.metadata_json in place — unblocks v3.10.5 publish
The ADR-101 provenance insert named metadata_json unconditionally; stores
created by older versions (and the minimal fixtures in
post-task-promotion/post-task-tokens-saved tests) lack the column, so the
INSERT threw and aborted the whole Stream B learning transaction — caught
by the npm-publish test gate (run 27275586477), masked locally by the
documented native-module artifact.

- PRAGMA-check + ALTER TABLE ADD COLUMN metadata_json (fail-soft) before
  the insert — also heals real-world legacy stores
- canonical-DDL ensure block is now fail-soft too, so an unexpected legacy
  shape can never kill the transaction; the inserts surface real failures

Verified against the exact failing scenario via a Linux-native harness:
old-shape DB -> column migrated, trajectory row written,
experience_applications.tokens_saved = 68 (the precise value the failing
test asserts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v3.10.5
2026-06-10 12:31:09 +00:00
Dragan Spiridonov 429e91d53f Merge pull request #521 from proffesor-for-testing/working-may
chore(release): v3.10.5 — Fable 5 parity: CLI fleet fix, prompt caching, adversarial QCSD reviews, qe-arena
2026-06-10 14:15:21 +02:00
Dragan Spiridonov a483d36239 chore(release): bump version to v3.10.5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:31:24 +00:00
Dragan Spiridonov 07326b9802 feat(arena): ADR-104 qe-arena Phase 1 — competitive test-strategy tournaments
aqe arena run: seeded tournaments where test strategies compete on REAL
fitness — built-in first-order operator mutants (string/comment/arrow-safe
enumeration) executed per strategy via node --test, real line coverage
from --experimental-test-coverage, and a deterministic suite-cost term.
Tournament emits a Wolfram-style pairwise competitive array; --evolve
hill-climbs the winner by seeded group flips with real re-evaluation.
Runs persist fail-soft to memory.db kv_store (namespace 'arena');
aqe arena list reads them back. Self-contained: no core changes, no new
dependencies; fixtures/arena-demo ships 4 test groups of varying kill
power over a branch-dense pricing module.

Reproducibility contract: same seed + fixture => byte-identical envelope
(verified E2E by diffing two runs), with informational.runtimesMs the
only excluded field. Honest finding during verification: the original
measured-runtime rank penalty was jitter-dominated at fixture scale and
flipped rankings between identical-seed runs — replaced with the
deterministic suite-size ratio (documented in ADR-104).

Phase 2 (documented): LLM-generated strategies frozen as artifacts,
winners distilled into qe-test-architect priors; MCP arena_run
registration (CLI and MCP share runArena() — parity by construction).

10 tests green (incl. real-engine reproducibility); tsc + eslint clean.

Part of #520 (improvement 7/7 — initiative complete).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:48:37 +00:00
Dragan Spiridonov e04f05742f feat(qcsd): ADR-102 deterministic workflow orchestration with adversarial verification
qcsd-development-swarm now runs as a deterministic workflow script
(.claude/workflows/qcsd-development-review.js) instead of prompt-driven
Task-tool fan-out:

- pipeline: one finder per quality dimension (TDD adherence, complexity,
  coverage gaps; qe agents as agentType) -> 3 blind adversarial refuters
  per finding -> deterministic synthesis, no agent editorializes the report
- Loki-mode constraints (ADR-074) encoded in refuter prompts: blind review
  (bare claim + evidence only), anti-sycophancy (attack the claim;
  uncertainty defaults to refuted), 3 distinct lenses
- majority kill (>= ceil(N/2) refuted votes); killed findings retained
  with refutations for audit; output is finding-verdict@1 envelopes
  (ADR-103)
- SKILL.md v1.1.0: execution.primary workflow, Task-tool fallback
  documented (reports marked verification: none)

Validation run (wf_254f1271-022): 3 dimensions over src/, 48 agents,
15 raw findings -> 14 confirmed, 1 killed by unanimous refutation of a
factually-wrong claim — the adversarial gate demonstrably filtering a
false positive. Several confirmed findings target code added earlier in
this initiative (honest signal, queued as follow-ups).

Part of #520 (improvement 5/7, Tier 3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:32:09 +00:00
Dragan Spiridonov dc6fce5e70 feat(contracts): ADR-103 structured verdict handoffs — RiskDecision et al
Versioned, additive-only verdict envelopes for agent/MCP handoffs:
RiskDecision (quality gates), FindingVerdict (review findings with
adversarial refutations), CoverageGap (risk-weighted gaps).

- src/contracts/verdicts.ts: dependency-free validators (source of
  truth) + mirrored draft-07 JSON Schemas; no zod/ajv runtime deps
  (light-install posture preserved)
- quality_assess MCP boundary now attaches a validated riskDecision
  envelope derived from the gate outcome (approve/block, escalate when
  indeterminate) — additive, existing payload unchanged; direct and
  wrapped handlers share the same mapToResult so MCP parity is inherent
- schemas/*.schema.json published via scripts/generate-verdict-schemas.mjs
  for external ajv validation and workflow agent({schema}) enforcement
  (consumed by improvement 5)

17 contract tests (goldens validate; mutations rejected with field-level
errors; builder always emits valid envelopes). E2E: envelope emitted by
the compiled boundary builder validates with stock ajv against the
published schema; mutated sample rejected. tsc + eslint clean.

Part of #520 (improvement 6/7, Tier 3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:15:07 +00:00
Dragan Spiridonov f2a350ba7f feat(agents): ADR-101 nested-subagent readiness — provenance, probe, validation
Anthropic announced nested subagent support (depth=5) on 2026-06-09; the
runtime currently strips Task at parent->child spawn (upstream denylist,
empirically confirmed by our committed probe: level=1 status=NO_AGENT_TOOL,
matching ruflo ADR-147). This lands the readiness infrastructure that is
independently useful today and activates with zero changes when the gate
lifts:

- aqe hooks post-task gains --parent-agent-id / --depth (validated:
  identifier charset, integer 0 <= d <= 32; depth=0 survives as 0);
  provenance persisted to qe_trajectories.metadata_json (stage 1 — no
  schema migration) and forwarded into ReasoningBank feedback so learning
  can segment patterns per hierarchy level
- TaskExecution gains optional parentAgentId/depth (additive types)
- scripts/probe-nested-spawn-depth.mjs: single-shot empirical probe,
  committed transcript under docs/probes/ — re-run after every
  `claude update`; TASK_TOOL_LIVE is the signal for follow-up phases
- fresh-project fix found by the E2E: persistTaskOutcome assumed
  captured_experiences/experience_applications exist, but they are
  middleware-created — Stream B silently no-oped on fresh installs;
  canonical DDL now ensured in the hook path

Deliberately deferred (documented in ADR-101 P1): tools: frontmatter
retrofit on shipped qe-*.md agents — Claude Code frontmatter is a
restrictive allowlist and would sever inherited MCP tool access;
needs maintainer sign-off + wildcard verification first.

8 unit tests green; E2E on a fresh temp project verified persistence,
rejections, and depth=0 boundary. tsc + eslint clean.

Part of #520 (improvement 4/7, Tier 2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:06:05 +00:00
Dragan Spiridonov d106ea7e39 chore(lint): drop unused catch bindings in command-hooks (pre-existing)
Two fail-open catch blocks bound `error` without using it — flagged by
unused-imports/no-unused-vars when linting the ADR-100 change; the
bindings predate it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:52:05 +00:00
Dragan Spiridonov 2d5126a053 feat(hooks): ADR-100 tool-loop circuit breaker — warn at 3, block at 5
Breaks runaway retry loops: when the same Bash command fails consecutively
(normalized signature, cross-process via a small JSON sidecar under
.agentic-qe/), pre-command warns at 3 failures and emits a BLOCK
recommendation with a recovery hint at 5. Advisory by default;
AQE_STRICT_TOOL_LOOP=1 upgrades the block to a PreToolUse deny. A
successful run resets the breaker; an open breaker grants a half-open
probe after 2 minutes; stale entries pruned after 30 minutes.

Mirrors the ADR-064 domain-circuit-breaker semantics at command level.
Sidecar instead of memory.db: pre-command deliberately runs without a DB
open, and each hook call is a fresh process so in-process state is
impossible. Fail-open everywhere (corrupt state file => allow).

11 unit tests green; E2E verified with real aqe hooks pre/post-command:
warn after 3, BLOCK after 5, strict-mode deny envelope, reset on success.

Part of #520 (improvement 3/7, Tier 1 complete).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:48:37 +00:00
Dragan Spiridonov 9c54a70656 feat(llm): ADR-088 amendment — wire enableCache to ephemeral cache breakpoints
The enableCache flag existed since ADR-011 but was never read: every
advisor/LLM call paid full input price. Now:

- ClaudeProvider.generate() sends `system` as a content-block array with
  cache_control {type: ephemeral} when enableCache (default on); plain
  string when disabled. Anthropic-native provider only (ADR-092 — other
  providers untouched).
- cache_creation_input_tokens / cache_read_input_tokens surfaced into
  TokenUsage.cacheCreationTokens/cacheReadTokens; totalTokens now counts
  the full prompt (input_tokens alone is the uncached remainder).
- CostTracker bills cache reads at 0.1x and writes at 1.25x input rate
  instead of ignoring them.

5 new provider tests assert the serialized request shape (cache_control
present when enabled, absent when disabled, no system field when no
prompt) and the usage mapping. 64/64 LLM tests green, tsc clean.

Part of #520 (improvement 2/7, Tier 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:41:49 +00:00
Dragan Spiridonov 8ff39f2c6e feat(learning): ADR-099 reasoning-tag scrub before distillation/embedding
Fable 5-era models emit large extended-thinking blocks inside task results
and step actions; embedding them verbatim contaminates the 384-dim pattern
vectors in memory.db and degrades HNSW retrieval.

- new scrubReasoningBlocks()/scrubReasoningDeep() (src/shared/reasoning-scrub.ts):
  boundary-gated — only well-formed <think|thinking|reasoning|
  REASONING_SCRATCHPAD>...</> pairs are removed; prose mentioning a tag
  survives; clean input returned byte-identical
- applied at ingestion: TrajectoryTracker startTrajectory task,
  recordStep action/result.data/result.error
- applied at distillation: ExperienceReplay.storeExperience task/strategy/
  keyActions (insert + embedding text)
- applied at flow-forward: both init-time embedding backfills (legacy rows
  cleaned as they get embedded — no memory.db rewrite) and retrieveSimilar
  query embeddings (symmetric with stored vectors)
- 15 unit tests; E2E verified against a temp DB: trajectory + experience
  rows persisted with all scratchpad blocks stripped

Part of #520 (improvement 1/7, Tier 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:35:25 +00:00
Dragan Spiridonov a76fbf98e1 fix(cli): bridge CLI auto-init into MCP fleet state + dev-env fixes
- aqe memory/agent/task/team/pipeline CLI commands always failed with
  "Fleet not initialized": autoInitialize() built kernel+queen in its own
  CLIContext while the shared MCP handlers guard on the core-handlers
  fleet-state singleton. New adoptExternalFleet() bridges the CLI's
  components into that state (idempotent, never clobbers an existing
  fleet), restoring MCP-CLI parity. Verified E2E: store/get/delete green.
- devcontainer: node_modules named-volume overlay + npm ci in postCreate
  so container installs Linux natives without touching the host's macOS
  binaries (takes effect on next rebuild; devcontainer users only)
- docs/setup: drop stale ruflo@3.5.18 pins (CLAUDE.md quick setup,
  install-tools.sh) in favor of ruflo@3 + global `ruflo mcp start`
- docs: add Fable 5 / ruflo-parity improvement plan (tracked in #520)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:22:55 +00:00
Dragan Spiridonov e177bfcf58 benchmarks 2026-06-08 16:53:27 +00:00
Dragan Spiridonov 50936bebbe Merge pull request #518 from proffesor-for-testing/fix/rvf-project-root-resolution-vitest
fix(rvf): resolve project root via static import, not require() (#516 follow-up)
v3.10.4
2026-06-08 18:11:13 +02:00
Dragan Spiridonov dc0ac612e2 fix(rvf): resolve project root via static import, not require() (#516 follow-up)
The v3.10.4 publish failed: rvf-pattern-store.test.ts expected RvfPatternStore
but got PatternStore. createPatternStore's #516 change used
`require('../kernel/unified-memory.js')` to get findProjectRoot; esbuild inlines
that in the shipped bundle, but under vitest (the CI publish gate) it throws
"Cannot find module", so the whole RVF block fell through to the plain store —
i.e. RVF pattern store was silently disabled under the test runner.

Fix: extract findProjectRoot/clearProjectRootCache into a lightweight,
sqlite-free module (src/kernel/project-root.ts) and import it STATICALLY from
the RVF hot paths. unified-memory.ts now imports + re-exports them, so existing
`import { findProjectRoot } from '.../unified-memory.js'` sites are unchanged.
A static import avoids both the vitest require-resolution failure and dragging
unified-memory's eager better-sqlite3 load into these modules.

- src/kernel/project-root.ts: new module (fs/path only)
- src/kernel/unified-memory.ts: import + re-export (internal callers keep a binding)
- src/learning/pattern-store.ts: static import; drop require()
- src/integrations/ruvector/shared-rvf-adapter.ts: same (latent same bug)
- rvf-pattern-store.test.ts: pin AQE_PROJECT_ROOT to a hermetic temp root with
  .agentic-qe so the RVF branch is taken deterministically (was relying on a
  prior test creating ./.agentic-qe in cwd)

Verified locally (native RVF available): rvf-pattern-store 16/16,
find-project-root 4/4, 181 non-sqlite ruvector tests pass, typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:10:16 +00:00
Dragan Spiridonov 584fb7efab Merge pull request #517 from proffesor-for-testing/fix/issues-513-516-and-local-dev-env
chore(release): v3.10.4
2026-06-08 17:48:26 +02:00
Dragan Spiridonov d4fba4b9fc chore(release): bump version to v3.10.4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:49:13 +00:00
Dragan Spiridonov 07873d8c6b fix(mcp): exit orphaned stdio server on stdin EOF instead of busy-looping (#513)
When the parent (e.g. a Claude Code session) exited, aqe-mcp was reparented
to PID 1 and busy-looped at 70-98% CPU, ignoring SIGTERM (only kill -9 worked).

Root cause: on stdin EOF the stdio transport fired its ERROR handler, which
the protocol server routes to its reconnect path. Reconnect re-attached
readline to the already-ended stdin, immediately re-emitted 'close', and
span close → error → reconnect → close at high CPU. The async SIGTERM
handler, starved by the spin and able to hang on an unresolved await, never
reached process.exit — hence "SIGTERM ignored". Non-unref'd worker/daemon
timers meant nothing made the orphan exit on its own either.

Fix (defense-in-depth):
- stdio transport: stdin EOF is now TERMINAL — fire a new onClose handler,
  never the error handler, so the reconnect spin can't start. reconnect()
  is also a no-op on an ended stream.
- entry.ts: single idempotent shutdown() shared by SIGINT/SIGTERM and stdin
  EOF, with an unref()'d 3s watchdog guaranteeing exit even if a graceful
  step hangs. A non-TTY stdin 'end'/'close' now triggers shutdown so an
  orphan exits when its parent dies.
- unref() the daemon health-check, worker tick, quality-daemon tick, and
  git-watcher poll timers so they can't keep an orphaned event loop alive.

Verified: real spawned aqe-mcp now exits ~31ms after stdin EOF (code 0),
vs. lingering indefinitely before. Adds stdio-eof.test.ts covering the
terminal-close behavior and no-reconnect-on-ended-stream guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 12:00:28 +00:00
Dragan Spiridonov 83e8f762a6 fix(memory): resolve project learning to its own .agentic-qe/ (#516)
Two root-resolution defects caused a project's learning to be written
outside its own .agentic-qe/.

Defect 1 — findProjectRoot() preferred the TOPMOST .agentic-qe, so an
ancestor store (e.g. ~/.agentic-qe, created by any `aqe` run from $HOME)
hijacked every descendant project. Now prefers the NEAREST .agentic-qe,
mirroring the existing .git nearest-wins logic in the same walk.

Defect 2 — the RVF substrate hardcoded a cwd-relative '.agentic-qe',
ignoring findProjectRoot(). A subfolder cwd (vendored builds, background
workers, `aqe code index docs/`) scattered stray patterns.rvf/brain.rvf
stores. All three sites now resolve via AQE_PROJECT_ROOT ?? findProjectRoot():
- shared-rvf-adapter.ts (relative dataDir anchored to project root;
  explicit absolute args still honored)
- shared-rvf-dual-writer.ts (brain.rvf)
- pattern-store.ts (patterns.rvf + existence gate)

Adds find-project-root.test.ts: nearest-wins over ancestor store, env
override precedence, .git fallback, and .agentic-qe over ancestor .git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:48:02 +00:00
Dragan Spiridonov dc5dc14ebd feat(learning): use local Nagual instance + add local LLM judge
Switch the cross-project knowledge hub from the cloud endpoint to a
locally-runnable Nagual instance, and add an optional local LLM judge
for scoring pattern quality.

- nagual-client.ts: REST client for a local `nagual serve` instance
  (NAGUAL_URL, default http://localhost:3333); search/recordOutcome/
  promotePattern are all fire-and-forget safe.
- local-judge-client.ts: Ollama-compatible judge (NAGUAL_JUDGE_URL/MODEL)
  blending 0.7*outcome + 0.3*judge_score, with an ambiguous-zone review flag.
- task-hooks.ts: pre-task surfaces Nagual cross-project context for
  complex tasks in relevant QE domains (best-effort, gated on env + length).
- settings.json: add NAGUAL_JUDGE_URL/MODEL; fix invalid mcp allow-rule
  globs (mcp__ruflo__* / mcp__claude-flow__*) and add Bash(rm -rf:*) deny.
- docs: de-reference the cloud hub in favor of a local instance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:44:22 +00:00
Dragan Spiridonov 6ddb4b75be Merge pull request #515 from proffesor-for-testing/dependabot/npm_and_yarn/npm_and_yarn-0a9c170602
chore(deps-dev): bump hono from 4.12.18 to 4.12.23 in the npm_and_yarn group across 1 directory
2026-06-05 08:14:14 +02:00
dependabot[bot] 812a5a6a83 chore(deps-dev): bump hono in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [hono](https://github.com/honojs/hono).


Updates `hono` from 4.12.18 to 4.12.23
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.18...v4.12.23)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.23
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 20:51:33 +00:00
Dragan Spiridonov c8e6f3bc2c test(init-gate): recalibrate self-dogfood snapshot for working TS code-intelligence
The #511 fix makes init's code-intelligence pre-scan actually parse AQE's own
TypeScript source (tree-sitter, no typescript dep) and persist the graph, so the
self-dogfood init now produces ~35k KG entries (was ~27k) and takes ~182s (was
~60s). Bumped expectedKgEntries 27000->34000, expectedElapsedSec 60->80 (soft cap
240s), timeoutSec 240->300. Follow-up: batch KG persistence writes to cut init time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v3.10.3
2026-06-03 13:59:29 +00:00
Dragan Spiridonov 7fb48160f3 Merge pull request #514 from proffesor-for-testing/fix/issue-511-code-deps-persistence
chore(release): v3.10.3
2026-06-03 15:44:25 +02:00
Dragan Spiridonov 7790a8c001 fix(deps): bump eslint-plugin-unused-imports to v4 (supports @typescript-eslint 8)
CI npm ci failed with ERESOLVE — v3.2.0 peers @typescript-eslint/eslint-plugin
6-7, but the project is on v8. v4.x peers ^8 (and keeps ESLint 8 support), so it
resolves cleanly. Worked locally only because it was installed with
--legacy-peer-deps; npm ci is strict.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:22:35 +00:00
Dragan Spiridonov 0f09a06363 chore(release): bump version to v3.10.3
Code-intelligence TS/JS via bundled tree-sitter WASM (no typescript dep, #511) +
fix for MCP domain tools hanging on fresh install. Also fixes the release skill's
repo path (agentic-qe-new -> agentic-qe).
2026-06-03 13:16:48 +00:00
Dragan Spiridonov b9df2f9a58 fix(parsers): rebuild python/java/c#/rust/swift WASM grammars for web-tree-sitter 0.26
The committed grammars for these 5 languages were built with the pre-0.25
emscripten "side module" format and fail to load with web-tree-sitter 0.26
(getDylinkMetadata error) — the multi-language parser was silently falling back
to regex for ALL of them. Rebuilt with tree-sitter-cli 0.26.9 (wasi-sdk) via
scripts/build-grammars.sh.

Verified: the WASM parser test suite now loads every grammar with ZERO
"Failed to load WASM grammar" fallbacks (was one per parse); 117 parser tests pass.

Also: build-grammars.sh installs grammar source packages with --legacy-peer-deps
(they declare conflicting tree-sitter binding peers, irrelevant since we only
consume their C source) so `build-grammars.sh all` resolves cleanly.
2026-06-03 13:01:43 +00:00
Dragan Spiridonov c3173a88ad feat(code-intelligence): parse TS/JS via bundled tree-sitter WASM — no typescript dependency (#511)
Code-intelligence (index/deps/impact/search) required the multi-megabyte
`typescript` compiler at runtime; without it every TS/JS file errored
("TypeScript is required for code analysis") and produced 0 nodes/edges — i.e.
the feature was broken out of the box for the entire JS/TS ecosystem.

Bundle tree-sitter WASM grammars for typescript/tsx/javascript and extract
entities/imports from them instead:

- assets/grammars/tree-sitter-{typescript,tsx,javascript}.wasm (shipped via
  package `files: assets/**`).
- treesitter-ts-extractor.ts: extracts functions (incl. async arrows), classes +
  methods (with visibility), interfaces, and imports (incl. require()).
- knowledge-graph.ts: TS/JS extraction now prefers tree-sitter (typescript-free),
  falling back to the TypeScript compiler API only if tree-sitter is unavailable
  AND `typescript` happens to be installed.
- tree-sitter-wasm-parser.ts: export loadWasmGrammar() reusing the proven WASM
  init + grammar-path resolution.

Built with tree-sitter-cli 0.26.9 (matching web-tree-sitter ~0.26) via the new
scripts/build-grammars.sh (docker + emscripten/emsdk; reproducible — byte
identical across runs). Grammars built with the pre-0.25 emscripten dylink format
do NOT load with web-tree-sitter 0.26 (getDylinkMetadata error), which is why the
build pins the toolchain.

Verified on a real fresh project with NO typescript installed: code index → 8
nodes / 7 edges, deps → 2 connecting edges, search → 3 results. 232
code-intelligence tests + new extractor unit tests pass.

Note: the pre-existing assets/grammars/*.wasm for python/java/c#/rust/swift were
built with the old toolchain and currently fail to load with web-tree-sitter
0.26 (silently falling back to regex). They can be rebuilt with
`scripts/build-grammars.sh all` — tracked as a follow-up.
2026-06-03 12:51:10 +00:00
Dragan Spiridonov c981d84f5d fix(mcp): bound advisory routing with a timeout so a stalled learning engine can't hang domain tools
Defense-in-depth after the QEReasoningBank init recursion. Pattern search + model
routing in createDomainHandler are optional hints, so they must never block a
tool call. Wrap routeDomainTask() in withTimeout (ROUTE_ADVISORY_TIMEOUT_MS,
default 5s, env-overridable): on timeout the tool proceeds with null routing
(mapToPayload already tolerates null). Also cache the learning engine only AFTER
a successful init, so a failed/slow init no longer serves a half-initialized
engine on the next call.

Adds unit tests for withTimeout (fallback on stall, real value when fast, fallback
on reject). MCP code_index still returns normally.
2026-06-03 12:10:20 +00:00
Dragan Spiridonov d6b19d087c fix(learning): stop QEReasoningBank.initialize() recursing → MCP domain tools hung on fresh install
Root cause: initialize() seeds pretrained patterns via loadPretrainedPatterns() →
storePattern(), and storePattern() ensure-inits with
`if (!this.initialized) await this.initialize()`. Because `this.initialized = true`
was set AFTER seeding, the seed re-entered initialize() without bound — an async
self-recursion that pinned a CPU core and never returned.

Impact: EVERY MCP domain tool (code_index, quality_assess, defect_predict, …)
routes through the advisory pattern-search path (createDomainHandler →
routeDomainTask → searchPatternsForTask → getLearningEngine → AQELearningEngine →
QEReasoningBank.initialize). On a fresh install (patterns seeded for the first
time) that path recursed forever, so all MCP domain tools hung indefinitely while
the CLI — which calls domain services directly — worked. The repo and in-memory
unit tests never hit it (already seeded / bulk-load path).

Fix: set `this.initialized = true` BEFORE loadPretrainedPatterns()/seeding. The
core stores (patternStore + SQLite) are already up by then, so storePattern()
short-circuits instead of recursing.

Verified against a real tarball install: MCP code_index ∞-hang → 0.5s
(6 symbols / 5 relations); quality_assess / defect_predict 0.8s; returns cleanly
(0/0/0) when typescript is absent too. Adds a red-green regression test pinning
the init-order invariant.
2026-06-03 12:04:37 +00:00
Dragan Spiridonov 80522aa460 chore(lint): clear remaining mechanical lint + document intentional violations
Real fixes:
- prefer-as-const (3): provider `type` literal fields use `as const`
- prefer-const (1): merge split let-declaration in work-stealing timer
- no-empty (2): annotate best-effort cleanup catch blocks
- no-unused-expressions (1): convert side-effecting ternary to if/else

Documented-intentional (eslint-disable-next-line with rationale):
- no-this-alias (4): self captured for returned handler context
- no-constant-condition (4): heap sift-down / polling / tokenizer loops break internally
- no-unused-expressions (1): chaos-engineer CPU busy-loop
- no-empty-object-type (1): marker config interface

Lint problems: 489 -> 472. Typecheck, build, and 2154 coordination/routing tests pass.

Remaining 472 (406 err + 66 warn) are judgment-heavy, intentionally left for a
reviewed pass: 327 dead locals/assigned vars (may be WIP/exports), 66 explicit-any
(typing work), 42 require-imports (some intentional dynamic), 37 useless-escape
(eslint declined to autofix — regex-semantics risk).
2026-06-03 09:39:02 +00:00
Dragan Spiridonov b53f0fd520 chore(lint): wrap case-clause declarations in blocks (no-case-declarations)
AST-positioned codemod (TypeScript compiler API): wrapped 46 case/default clauses
that declare const/let/function/class in `{ }` blocks across 20 files, clearing
all 60 no-case-declarations errors. Pure block-scoping — semantically identical.
Verified: typecheck, build, and 4319 unit/integration tests pass.

Lint problems: 549 -> 489.
2026-06-03 09:32:32 +00:00
Dragan Spiridonov 447ea1df24 chore(lint): document 4 intentional rule violations (control-regex, async-executor)
Inspected the genuine "bug-smell" lint hits — none are defects:
- postgres-writer NUL stripping (2x no-control-regex): required, PostgreSQL JSONB
  rejects the NUL code point.
- http-server / websocket-transport async Promise executors (2x): both already
  handle errors (server.on('error', reject) / try-catch -> reject). Refactoring
  critical startup/upgrade paths for the rule isn't worth the risk.
Added eslint-disable-next-line with rationale on each.
2026-06-03 09:26:42 +00:00
Dragan Spiridonov 94b026e2d6 chore(lint): auto-remove dead imports via eslint-plugin-unused-imports
Add eslint-plugin-unused-imports (devDependency) and switch unused-vars handling:
- unused-imports/no-unused-imports (error, autofixable) removes dead imports
- unused-imports/no-unused-vars (error, report-only) keeps dead locals visible
- @typescript-eslint/no-unused-vars disabled to avoid double-reporting

Ran the autofix across the tree: removed 478 unused imports in 251 files.
Lint problems: 1031 -> 553. No bare side-effect imports (`import 'x'`) were
removed. Verified: typecheck passes, build passes, 4507 unit/integration tests
pass. No behavioral change — purely dead-import removal.
2026-06-03 09:21:38 +00:00
Dragan Spiridonov bec5a57f1c chore(lint): ignore src/_archived, align bounded-context count, apply safe autofixes
Repo-health cleanup from an external code audit:

- eslint: ignore src/_archived (already excluded by tsconfig, so linting it only
  produced 6 parserOptions.project errors).
- Align the architecture claim: src/index.ts and docs said "12"/"6" bounded
  contexts; there are 13 domain dirs (matching package.json). Updated src/index.ts
  (comment + ARCHITECTURE const) and the docs list to the actual 13.
- Apply stock `eslint --fix` (prefer-const, prefer-as-const, no-useless-escape,
  no-regex-spaces) across 24 files.

Lint problems: 1072 -> 1031. Typecheck and build pass; no behavioral changes.
2026-06-03 09:13:16 +00:00
Dragan Spiridonov cc5220c5a0 fix(code-intelligence): pin KG to a fixed namespace + evict stale file cache (#511)
Resolves the two remaining gaps from the audit.

Namespace divergence (CLI↔MCP parity):
KnowledgeGraphService persisted nodes/edges under the backend's DEFAULT
namespace — 'qe-kernel' for the CLI kernel, 'mcp-tools' for the MCP shared
backend — so a graph indexed by one tool was invisible to the other, and the
init existence check (`SELECT ... FROM kv_store WHERE namespace =
'code-intelligence:kg'`) always returned 0 because the data was elsewhere.

Now every KG kv op passes an explicit `{ namespace: 'code-intelligence:kg' }`
(via a single `nsOpts` getter so reads and writes can't drift apart — issue
#491 Bug 2). The two external readers that searched the default namespace by key
prefix (impact-analyzer test discovery, test-generator KG-context) now read that
namespace too. Verified: both `aqe code index` and the MCP `code_index` tool
land KG rows in `code-intelligence:kg`; deps/search still work; the init
`hasCodeIntelligenceIndex` check now finds the index.

FileReader stale content on incremental re-index:
indexFile() now evicts the file from the FileReader content cache before reading
it. The reader caches content for 5 min, so a re-index in a long-lived process
(the MCP server) could otherwise extract from pre-edit text. The two reads
within indexFile (entities + imports) still share one cache fill, so they stay
consistent. The incremental regression test now re-indexes on the SAME instance,
exercising both removeFileFromGraph and the cache eviction.

Adds a regression test asserting KG data is stored under 'code-intelligence:kg'
regardless of the backend's default namespace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:59:47 +00:00
Dragan Spiridonov 26e724b24e fix(code-intelligence): warn on unsupported langs, prune incremental stale edges, fix MCP code_index counts (#511)
Addresses the gaps a devil's-advocate audit surfaced in the #511 fixes:

1. Fail-loud warning (issue request #2): `index` now records how many files
   were indexed under a language with no dependency extractor and returns them
   as `IndexResult.warnings` (logged + printed by the CLI), so a downstream
   0-edge `deps` result is explained rather than silently misleading.

2. Incremental stale state: `indexFile(..., incremental)` now calls
   removeFileFromGraph() to delete a file's prior node/entity/outgoing-edge
   keys before re-indexing, so `index --incremental` prunes removed imports and
   renamed entities instead of leaving phantom dependencies. (Note: within a
   single long-lived process the FileReader content cache can still serve stale
   text; the CLI re-indexes in a fresh process so this is not user-visible.)

3. MCP parity: the `index-code` task handler emitted `nodesCreated`/`edgesCreated`
   but the MCP result mapper reads `symbolsExtracted`/`relationsFound`, so
   `code_index` always reported 0/0. Map the KG counts to those fields and pass
   `warnings` through. Verified via a real MCP protocol call: filesIndexed 2,
   symbolsExtracted 5, relationsFound 4 (was 0/0).

Adds regression tests: unsupported-language warning, incremental stale-edge
removal (fresh-instance, mirroring the CLI), and the MCP field mapping.

Known limitation (documented, not changed here): CLI persists the graph under
the 'qe-kernel' namespace and MCP under 'mcp-tools', so a graph indexed by one
tool is not read by the other. MCP exposes only `code_index` (no deps/search),
so this has no MCP-internal reader; reconciling the namespaces is entangled with
contradictory init/test-generation expectations and needs its own change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:47:57 +00:00
Dragan Spiridonov d7b82ff6e2 fix(code-intelligence): make aqe code search return results — content namespace round-trip (#511)
indexCode() stored retrievable content with a {namespace} store option, but
the key is already fully namespace-qualified and every read path
(semanticSearch/findSimilar/exact/fuzzy) plus the storeVector() embedding write
use the default namespace. On a real backend that honors the option, the
content written with {namespace} could not be read back without it (issue #491
Bug 2), so each matched vector's content fetch returned undefined and the result
was silently dropped — search returned 0 results for obviously-present terms.

Fix: drop the redundant {namespace} store option on the content write so it
round-trips with the reads.

Verified via CLI: `code search "calculator compute"` now returns the indexed
files (was 0). Adds a regression test using a namespace-honoring mock backend
(the existing mock ignored the option, which is why it never caught this); the
test fails with the option reintroduced and passes without it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:08:36 +00:00
Dragan Spiridonov 8a0217e9aa fix(code-intelligence): persist KG so aqe code deps returns edges on TS/JS repos (#511)
`aqe code index` and `aqe code deps`/`search` run as separate CLI processes,
but the knowledge graph stored nodes/edges in-memory only ("cache-only"), so a
fresh `deps` process always saw an empty graph and reported 0 edges / degree-0
nodes — even though `index` reported edges created. This affected every
language; TS was incidental (the issue's "missing TS parser" theory is a red
herring — the TS Compiler API path is wired and does extract).

Fixes:
- Persist nodes/edges to memory.db in storeNode/createEdge; getEdges hydrates a
  correct flat edge cache (the old source-keyed index could not serve
  incoming/both lookups); clear() deletes persisted KG :node:/:edge: keys only.
- resolveImportPath(): resolve relative import specifiers to real files with
  extension probing so file->file import edges connect (were dangling at
  pathToNodeId('./math')).
- traverseDependencies: restrict to true dependency edge types
  (import/call/extends/implements) and recurse directed-forward only, which
  removes phantom A->B->A cycles surfaced once edges actually existed.

Verified via CLI: index -> 6 edges; fresh-process deps -> 2 connecting edges,
0 cycles (was 0 edges). Adds cross-instance regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:01:03 +00:00
Dragan Spiridonov b46b1cf855 Merge pull request #512 from proffesor-for-testing/working-april
chore(release): v3.10.2
v3.10.2
2026-06-02 16:02:10 +02:00
Dragan Spiridonov f96159683f feat(init): ship the hook shim and wire aqe init to install + use it
Per request: aqe init now installs the cross-platform Node hook shim into each
user project and routes hooks through it (instead of `npx agentic-qe hooks`).

- package.json: add `.claude/hooks` to `files` so aqe-hook.cjs ships in the npm
  tarball (verified via `npm pack --dry-run`).
- 07-hooks.ts: new installHookShim() copies aqe-hook.cjs into the project's
  .claude/hooks/ (source: node_modules/agentic-qe, then the package's own copy
  resolved relative to the compiled module — works for npx too).
- 07-hooks.ts + init-wizard-hooks.ts: generated hooks now run
  `node "${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/aqe-hook.cjs" <cmd> ...`
  (README/help examples left as `npx agentic-qe hooks` for manual use).
- aqe-hook.cjs: added an npx fallback (`npx -y --prefer-offline agentic-qe`)
  so npx-only installs with no project-local bundle still work; disable with
  AQE_HOOK_NPX=0 (tests use this for determinism). Header/scope updated (now a
  shipped, init-copied file, not repo-internal).

Migration: isAqeHookEntry already matches the new `aqe-hook.cjs` command (via
the `\baqe\b` pattern) AND the old `npx agentic-qe hooks` form, so re-running
`aqe init` replaces a user's existing hooks with the shim form — no duplicates.

Verified end-to-end (real `aqe init --auto` in a temp project):
  - aqe-hook.cjs copied into .claude/hooks/ ("Installed aqe-hook.cjs (copied)")
  - all 11 generated hooks use `node .../aqe-hook.cjs <cmd>` (0 npx commands)
  - post-task retains the #508 fix (--agent/--success true/--description)
  - isAqeHookEntry matches new+old forms, not unrelated user hooks
  - shim 7/7, settings-merge 17/17, task-hooks 5/5 tests pass; typecheck clean
2026-06-02 13:45:19 +00:00
Dragan Spiridonov 1d0cbd626e refactor(hooks): rewrite hook shim as cross-platform Node (.cjs), drop POSIX .sh
A .sh shim is the least portable option — it forces a .cjs Windows twin and a
node_modules/.bin shell-wrapper spawn. Node is already a hard dependency of AQE
(CLI is Node >=20), so a single .cjs runs identically on Windows/macOS/Linux.

aqe-hook.cjs resolves a project-local bundle and runs it via process.execPath
(this node) — no shell, no .bin/.cmd wrapper:
  1. node_modules/agentic-qe/dist/cli/bundle.js  (installed dep — user case)
  2. dist/cli/bundle.js                            (this source repo)
  else no-op (exit 0). Swallows stderr; emits only the top-level JSON object.

Repo .claude/settings.json rewired: all 10 bundle hooks now invoke
. Deleted
aqe-hook.sh. Tests rewritten to run the .cjs via node; 7/7 pass incl. installed-
dep precedence + stderr-swallow. This removes the Windows blocker that stood
against ever shipping the shim to users.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 13:36:16 +00:00
Dragan Spiridonov e81b56d411 fix(hooks): make aqe-hook.sh portable (resolve project-local AQE, not hardcoded dist)
The shim hardcoded ${PROJECT}/dist/cli/bundle.js, which only exists in this
source repo — if ever copied into an installed project it would silently no-op.
Now resolves a PROJECT-LOCAL runner in priority order:
  1. node_modules/.bin/aqe   (installed project — the user case)
  2. dist/cli/bundle.js       (this source repo)
  else no-op (exit 0, never block a turn)
No global-PATH / npx fallback by design (version drift + npx-per-fire crash).

Clarified in the header that the shim is REPO-INTERNAL: it is not in the npm
`files` list and `aqe init` does not copy it — user hooks are generated as
`npx agentic-qe hooks <cmd>` (cross-platform, npx-friendly). Portability here is
defense-in-depth, not a shipping decision.

Adds a regression test for branch-1 resolution + precedence (node_modules/.bin/aqe
preferred over dist). 6/6 shim tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 13:26:14 +00:00
Dragan Spiridonov e84a911d1f chore(release): bump version to v3.10.2
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 12:27:08 +00:00
Dragan Spiridonov c61f19ab8b fix(hooks): dogfood the #508 fix in the repo's own .claude/settings.json
The repo's own PostToolUse post-task hook still carried the broken
'--success --json' (no --agent) form (the #5 shim rewired it but kept the
args). The parser fix makes bare --success parse true, but it still recorded
agent='unknown'. Updated post-task to pass --agent/--success true/--description
and post-command to explicit --success true, matching the corrected init
templates. (post-edit's bare --success is correct — that option is a flag.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:38:27 +00:00
Dragan Spiridonov 2ecc377241 fix(dream): kernel DreamScheduler reconciles dream-scheduler:hook-state per cycle (#509)
Reported by @ciprianmelian. ADR-094 moved dream cycles to the kernel-side
DreamScheduler, but the dream-scheduler:hook-state kv row that SessionStart
reads is only INCREMENTED by the post-task hook (experienceCount++) and was
never reset by the entity that actually dreams. Confirmed in source: the kernel
scheduler had ZERO references to that key (it persists its own
'dream-scheduler:state' instead). Result: SessionStart perpetually showed
lastDreamTime:null and a pendingExperiences counter that only grew (e.g.
11 -> 1747 -> 6489), reading like a stuck scheduler while the daemon dreamt fine.

Fix: after a successful executeDream cycle, reconcileHookState() stamps the
truth on the SAME key + DEFAULT namespace the hook path uses:
{ lastDreamTime: now, experienceCount: 0, totalDreamsThisSession: prev+1,
  sessionStartTime: preserved }. Best-effort (never breaks a dream cycle). Key
is a local literal mirroring DREAM_STATE_KEY to avoid a learning->cli layering
import.

Verified: new unit test seeds the stale row (experienceCount: 2720,
lastDreamTime: null), triggers a dream, asserts the row becomes
experienceCount:0, lastDreamTime: <iso string>, totalDreamsThisSession:1,
sessionStartTime preserved. 39 dream-scheduler tests pass; typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:34:40 +00:00
Dragan Spiridonov 201f5e8a95 fix(hooks): post-task --success eats --json -> every trajectory success=0/agent=unknown (#508)
Reported by @ciprianmelian. The aqe-init-generated PostToolUse hook ran
`post-task --task-id ... --success --json`, but --success was declared
`<bool>` (value REQUIRED), so Commander consumed --json as the success value:
options.success='--json' -> parsed false -> EVERY qe_trajectories /
captured_experiences row got success=0, the --json branch was swallowed, and no
--agent meant agent='unknown'. Net: Task trajectories were unpromotable
(distillation groups by agent/domain/success), so pattern count stayed pinned
near zero. (Confirmed in source; same class as the learning gaps #510 hardened.)

Fix (two layers):
1. Parser hardening — `--success <bool>` -> `--success [bool]` (optional value)
   in task-hooks/command-hooks/routing-hooks, so a bare --success yields boolean
   true and never eats the next token. Backward compatible: --success true/false
   and the 'true' default all still parse identically.
2. Generated templates (07-hooks.ts + init-wizard-hooks.ts): post-task now emits
   `--agent "$TOOL_INPUT_subagent_type" --success true --description
   "$TOOL_INPUT_prompt" --json` (real agent attribution + explicit success +
   pre-task bridge), and post-command emits explicit `--success true`.

Verified: isolated Commander proof — OLD <bool>: success="--json" (false);
NEW [bool]: success=true, --json fires. Plus 3 regression tests driving the EXACT
broken argv through registerTaskHooks: bare --success -> success=true, explicit
--success false -> false, --agent flows through. 5/5 task-hook tests pass
(incl. existing #449); typecheck clean. (Full post-task CLI run OOMs in Codespace
per CLAUDE.md — the parsing layer is the bug and is proven.)

NOTE for existing installs: re-run `aqe init` (or hand-edit the PostToolUse hook
in .claude/settings.json) to pick up the corrected command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:30:59 +00:00
Dragan Spiridonov 84fb9ca65d docs(adr): ADR-098 learning-store & routing hardening + mark 2 side-findings fixed
ADR-098 documents the durable behavioural decisions from the ruflo adoption
pass (#510): domain-score de-dilution (#2), EWC++ importance-weighted forgetting
(#6), contradiction suppression (#7), the two pre-existing bug fixes
(consecutive_failures, MCP -32601), and the explicit NEGLECTED decisions —
ruflo's Beta-priors (#2) and RaBitQ speedup (#8) did not transfer, and
multi-bundle packaging is deferred. AQE WH(Y) format, Accepted.

Marks the consecutive_failures (ae7c8b71) and MCP error-code (9b189115)
side-findings FIXED in the plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:06:24 +00:00
Dragan Spiridonov 9b1891157b fix(mcp): return proper JSON-RPC error code for unknown tool/method (#510 side-finding)
Pre-existing bug surfaced by the qe-devils-advocate audit and live-reproduced:
handleToolsCall (and unknown-method / double-initialize) threw a PLAIN OBJECT
{ code: -32601, message }. The server safety net checked err instanceof Error
(false for a plain object) and surfaced it as a content payload
'Internal error: [object Object]', losing the -32601 code/message an MCP client
expects. The tool was correctly never executed; only the error shape was wrong.

Fix (matches existing OAuthProviderError convention): add McpError extends Error
{ code } + isJsonRpcCodedError guard in transport/stdio.ts (guard accepts both
McpError and the legacy plain {code,message} so nothing regresses). Convert the
three plain-object throws in protocol-server.ts to McpError; the safety net
re-throws coded errors (real protocol errors) while still swallowing genuinely
unexpected errors to keep the connection alive; the transport catch honors
err.code, falling back to INTERNAL_ERROR. Successful/known-tool calls unaffected.

Verified (real server boot via scripts/audit-mcp-tool-parity.mjs):
  before: unknown tool rejected with 'Internal error: [object Object]'
  after:  unknown tool rejected with 'JSON-RPC error code=-32601'
Parity PASS (86 tools), typecheck clean, real .agentic-qe/aqe.rvf unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:05:25 +00:00
Dragan Spiridonov ae7c8b7105 fix(learning): surface consecutive_failures so the 'failures' deprecation branch fires (#510 side-finding)
Pre-existing bug surfaced by the qe-devils-advocate audit and confirmed:
checkDeprecation() reads (pattern as PatternWithDeprecation).consecutive_failures
to trigger the 'failures' deprecation reason, but rowToPattern() built the
QEPattern WITHOUT that column (and without deprecated_at), so getPattern()
always surfaced undefined->0 and the 'failures' branch NEVER fired — a pattern
that kept failing was never deprecated for failures.

Fix (surgical): rowToPattern now surfaces consecutive_failures and deprecated_at
(added to the PatternRow interface; returns a typed PatternWithDeprecation). No
thresholds or deprecation logic changed.

Verified: new tests assert a pattern with consecutive_failures==3 (== default
threshold), not stale, confidence above min, deprecates with reason 'failures';
below threshold does not. Regression-proven (reverting rowToPattern fails the
new test). 12 EWC tests pass; typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:04:57 +00:00