Commit Graph

4 Commits

Author SHA1 Message Date
rUv f9537a240b fix(ruflo-adr): skip .brain when walking for ADRs (#2911) (#2994)
* fix(ruflo-adr): skip .brain when walking for ADRs (#2911)

ruvnet-brain clones ~50 external repos under .brain/repo/clones/,
many carrying their own docs/adr/. Without .brain in SKIP_DIRS,
findAdrs() picked up hundreds of foreign ADRs alongside the
project's own — and since .brain sorts before docs, the project's
ADRs landed dead last in the walk order.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruflo-adr): smoke step 22 should not hard-code the test-file count

Was `-eq 3`, breaking every time a new *.test.mjs file is added to
__tests__/ even when the new tests themselves pass. `-ge 3` keeps the
"regression tests exist and run" guarantee without re-breaking on
growth — caught by this PR's own new test file (skip-brain-dir-2911).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-08-12 15:31:06 -04:00
rUv 401e02d511 fix: complete reports and consistent initialization for v3.32.37 (#2851)
* fix(metaharness): preserve readiness verdict payloads

* test(metaharness): cover blocked genome verdicts

* fix(adr): parse bullet metadata and relationships (#2659)

* fix(adr): align adr-create with AgentDB schema (#2651)

* fix(adr): make index updates idempotent (#2660)

* fix(memory): bound session-end graph consolidation (#2628)

* fix(memory): align active row visibility (#2652)

* fix(memory): honor database path during init

* fix(hooks): keep all shim fallback tags aligned

* fix(codex): omit unbacked full-template skills

* fix(init): generate complete native dual projects

* test(memory): isolate path and legacy-row regressions

* chore(release): prepare v3.32.37
2026-07-29 15:40:17 -04:00
rUv 810b13dcd6 fix: tracker-sweep 2026-07-26 (v3.32.10) — 9 bugs + promo seed + follow-ups (#2788)
* fix: tracker sweep 2026-07-26 — 9 bug fixes + promo seed

Closes: #2770 #2774 #2776 #2777 #2781 #2782 #2785 #2786 (Codex follow-up)
Refs:   #2775 (memory upsert semantics)

- Statusline promo row blank on new installs — restored cold-start local
  seed pool that ADR-311 had emptied. Remote Cognitum-served pool
  (funnel.ruv.io/v1/messages) remains authoritative via
  eligibleMessagesFromPools (remote wins by id); seed only fills the
  ≤ 60s cold-start window before first successful remote fetch.
- #2777 — ruflo init no longer imports the entire ruvnet/ruflo repo
  (97 MB, 384 SKILL.md) into .agents/skills/ruflo/. Materializes single
  platform SKILL.md; detects "bloated" prior install and re-materializes.
- #2781 — ruflo-adr adr-index no longer silently drops ADR data: status
  regex accepts "- **Status**: proposed"; single-line and wrapped
  relation lines parse fully; CLI_CORE=1 warns and unifies namespace.
- #2770 — Windows: browser-session MCP tools + two init execFileSync npx
  sites now set shell: process.platform === 'win32' so cmd.exe resolves
  npx.cmd. POSIX behavior unchanged.
- #2782 — WorkerDaemon.saveState + autopilot-state.saveState + appendLog
  no longer race on shared .tmp filename; all three call writeFileAtomic
  (pid + timestamp + random-suffixed temp).
- #2785 — ruflo hooks post-task accepts --task/-t and --store-results
  flags matching CLAUDE.md-documented usage; routing outcomes finally
  persist to the namespace hooks_metrics reads.
- #2786 — AgentDB no longer silently fails to initialize under
  CLAUDE_FLOW_ENCRYPT_AT_REST=1. New getAgentDbPath() returns
  agentdb-memory.db in the same directory as memory.db, so the
  ControllerRegistry native better-sqlite3 opens a distinct file from
  the sql.js CRUD writer's encrypted memory.db.
- #2776 — Statusline security STALE/IN_PROGRESS branches reachable via
  local overlay recomputing freshness on every render from
  .claude/security-scans/scan-*.json. Env: RUFLO_SCAN_STALE_HOURS (24),
  RUFLO_SCAN_PENDING_CAP_MIN (30). STALE renders dim gray.
- #2774 — Codex MCP generator registers dedicated stdio server binary
  claude-flow-mcp instead of the management CLI ruflo mcp start that
  never answers initialize. All 7 wrong-command sites fixed
  (initializer.ts + 6 template sites in generators/config-toml.ts + 2
  in migrations/index.ts + generators.test.ts:653 assertion).
- #2775 — Memory store to existing key no longer dead-ends:
  bridgeStoreEntry uses INSERT ... ON CONFLICT with tombstone
  auto-resurrect; UNIQUE returns typed error instead of null (no more
  misleading demotion into #2735 guard); bridgeDeleteEntry runs
  wal_checkpoint(PASSIVE); CLI memory store --upsert default
  materialized locally (parser.applyDefaults bug).
- Stale ruflo-rebrand assertion in codex/tests/generators.test.ts:192
  updated Co-Authored-By: claude-flow → ruflo-bot.

- #2786 fix-2/3 (hooks_metrics reads dead .claude-flow/memory/store.json;
  bridgeRecordFeedback calls non-existent agentdb API) — architectural
  rewire, needs separate PR.
- #2775 defect #3 root cause (parser.ts applyDefaults doesn't apply
  subcommand-declared defaults) — flagged by agent, other subcommands
  affected too.
- #2775 memory-tools.ts MCP tool schema still defaults upsert:false
  (parity fix flagged).

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(parser): applyDefaults now walks subcommand + command options (#2775 follow-up)

Previously only `this.globalOptions` were walked. Any subcommand-declared
`default: <value>` silently dropped and the action handler received
`undefined`. The immediate victim was `memory store --upsert` (per #2775),
but the bug is generic — every subcommand that relies on a per-flag
default was affected.

Fix: applyDefaults now accepts optional command + subcommand, walks all
three layers narrow-to-broad (subcommand > command > global), and the
first layer to supply a value wins (because it only writes when the flag
is `undefined`, so earlier writers stick).

Parser tests: 52/52 pass. Build: tsc exit 0.

Refs: #2775 (root cause behind the CLI --upsert workaround already in
`commands/memory.ts`; the workaround is now redundant but harmless).

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(mcp): memory_store defaults upsert=true for CLI parity (#2775 follow-up)

The CLI `memory store` command defaults `--upsert=true` (issue #2594, fix
c36cb4d66) so `store → delete → store` on the same (namespace, key)
doesn't trip the `UNIQUE(namespace, key)` constraint against a
soft-deleted row. The `memory_store` MCP tool schema still defaulted
`upsert: false`, so every agent-facing store-to-existing-key hit the
strict-insert path unless the caller explicitly passed `upsert: true` —
different behavior than the CLI documented.

Fix: schema description now states default true, handler reads
`input.upsert !== false` (only explicit `upsert: false` opts out).
Symmetric with the CLI-side workaround in `commands/memory.ts`.

Refs: #2775 (MCP parity follow-up flagged by the sweep agent).

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(changelog): re-scope tracker sweep to 3.32.10 + reflect final commit set

- Rename header 3.28.1 → 3.32.10 (patch on the actual current 3.32.9;
  the original 3.28.1 heading was left over from the stale-base draft
  before the rebase onto origin/main).
- Remove the #2774 bullet from the "Fixed" list — that fix was reverted
  during rebase (upstream d20f1323b superseded it and the reporter's
  diagnosis appears incorrect). Move #2774 to a new "Investigated"
  subsection with the analysis and a note recommending closure.
- Fold the #2775 parser follow-up (parser.ts:applyDefaults walks
  subcommand + command options) and the MCP parity follow-up
  (memory_store default upsert=true) into the #2775 bullet, since
  they're the same defect surfacing at three layers.
- Drop the "stale rebrand test assertion" bullet — that assertion is
  now on origin/main already; no notable delta in this PR.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(hooks): dual-write routing decisions to JSON store for metrics reader (#2786 fix-2)

Previously `hooks_post-task --store-results` wrote routing outcomes to
AgentDB namespace `patterns` via `storeFn`, but the sync reader
`getIntelligenceStatsFromMemory()` only reads
`.claude-flow/memory/store.json`. Result: the "Pattern Learning" and
"Agent Routing" numbers in `hooks_metrics` stayed at zero even when
routing decisions were being recorded.

Fix: after the AgentDB write, mirror the same entry into the JSON store
with `metadata.type = 'routing-decision'` so the reader's filter
picks it up. AgentDB stays authoritative for cross-session retrieval;
the JSON store is the counter surface the sync reader depends on. Both
writes are wrapped in try/catch so a failure at either layer doesn't
break `hooks_post-task`.

Same shape as the existing dual-write pattern for `hooks_post-command`
at line 910-931. Keeps `getIntelligenceStatsFromMemory` synchronous
(would require async ripple through 3 callers otherwise).

Refs: #2786 fix-2 (flagged as architectural by the sweep agent — turns
out to be one bounded additive write, not a rewire).

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(parser): regression coverage for command + subcommand defaults (#2775)

Three new tests in the existing `defaults` describe block:
- command-level `option.default` applies to a flagless invocation
- subcommand-level `option.default` applies to a subcommand invocation
  (this is the exact shape that trapped `memory store --upsert`)
- explicit `--no-upsert` still overrides the default:true

Guards against a re-regression of parser.ts:applyDefaults collapsing
back to only walking `globalOptions`.

52/52 → 55/55 parser tests pass.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(hooks): regression guard for #2786 fix-2 JSON dual-write

New test file `hooks-post-task-routing-dual-write-2786.test.ts` runs
`hooksPostTask.handler` in a temp cwd and asserts that when called with
`storeDecisions=true`:

  - `.claude-flow/memory/store.json` is created
  - It contains a `routing-decision:<taskId>` entry
  - The entry's shape matches what `getIntelligenceStatsFromMemory()`
    filters on (`key.includes('routing') || metadata.type === 'routing-decision'`)
  - `metadata.confidence` maps from `quality` (used to compute avgConfidence)
  - `namespace` is `patterns` (parity with the AgentDB write)

Second test guards backwards compat: when `storeDecisions` is omitted,
no routing-decision entry appears in the JSON store.

Bridge is mocked (the AgentDB path's real behavior is covered elsewhere).

2/2 pass.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(funnel): regression coverage for cold-start seed pool

New test file `funnel-messages-seed-pool.test.ts` asserts the seven
invariants of the 2026-07-26 cold-start pool restoration:
  - MESSAGES is non-empty
  - contains ≥1 disclosure so the disclosure gate can unlock cold
  - every disclosure carries the exact ' · manage: ruflo settings' tail
    (ADR-301 invariant)
  - every seed message passes isValidMessage() (schema / host allowlist
    / control-char strip / 80-col cap)
  - every URL is on the exact-host allowlist (no third-party leaks)
  - contains ≥1 educational (4-in-5 rotation slots)
  - all ids are unique (rotation dedups on id)

7/7 pass. Guards against a re-empty of MESSAGES that would blank the
statusline promo row again on new installs.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(funnel): disclosure gate now consults local seed pool (E2E follow-up)

E2E validation of the 2026-07-26 sweep exposed a gap: on true cold
start (no remote cache, no network), the promo row stayed blank
because `selectDisclosureMessage` only read `getRemoteMessages()` —
my local seed disclosure in messages.ts never reached the disclosure
gate, so the gate stayed `never_seen` forever.

Fix: `getDisclosureMessagePool` now merges local `MESSAGES` with the
remote pool via the same `eligibleMessagesFromPools` helper rotation
uses (remote wins by id). This mirrors the design of the rotation
selector — the seed is a cold-start bootstrap, remote takes over as
soon as its cache populates.

Verified end-to-end: cleared funnel cache + pointed
`RUFLO_FUNNEL_MESSAGES_ENDPOINT` at an unreachable host; statusline
render emits the local disclosure ("Ruflo shows occasional tips and
sponsor notes here · manage: ruflo settings") — exactly the string
seeded in messages.ts (`local.disclosure.v1`).

Refs: 2026-07-26 tracker sweep, promo row cold-start.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-26 15:05:33 -07:00
rUv dc015986dc feat(adr): reconcile deleted ADRs — hard-delete primitive + drop-and-rebuild reindex (#2668)
* feat(adr): reconcile deleted ADRs — hard-delete primitive + drop-and-rebuild reindex

Closes #2666. `adr-index` can only add/upsert; deleting an ADR file (or a
relation line from a surviving one) left an orphan row in `adr-patterns`/
`adr-edges` forever, and `adr-verify` certified the result as healthy since
an orphan has no dangling ref and forms no cycle.

Root cause traced into @claude-flow/cli itself, not just this plugin:
`memory delete` only ever soft-deletes (status='deleted'), so the row keeps
occupying its UNIQUE(namespace, key) slot and blocks a later non-upsert
re-store (#2652). `memory cleanup` reaps by age/TTL/quality, not by
"does the source still exist". Neither the AgentDB v3 bridge nor the sql.js
fallback exposed any hard, namespace-scoped delete anywhere in the CLI/MCP
surface. Separately, import.mjs/verify.mjs's spawnSync calls never passed
`cwd`, so ADR_ROOT scanned the right files but could write to the wrong
`.swarm/memory.db`.

CLI (@claude-flow/cli):
- memory-bridge.ts: bridgePurgeNamespace() — real DELETE FROM memory_entries
  WHERE namespace = ? against the live bridge SQLite handle, routed through
  the existing MutationGuard/AttestationLog hooks.
- memory-initializer.ts: purgeNamespace() (bridge-then-sql.js-fallback, same
  shape as deleteEntry) + withMemoryDbLock() (new <dbPath>.lock O_EXCL
  advisory lock, same stale-takeover pattern as global-ai-budget.ts —
  protects against a second concurrent purge/delete on the same file; does
  not close #2621, which needs every writer to adopt the same lock).
- commands/memory.ts: `memory purge --namespace <ns> --force [--dry-run]` —
  requires --namespace explicitly (no default), confirms unless --force.
- package.json: sql.js was already imported directly by
  memory-initializer.ts's fallback path but never declared as a dependency
  — worked by accident under npm's loose hoisting, silently broken under
  this repo's strict pnpm workspace (confirmed: unmodified deleteEntry/
  storeEntry fail identically). Declared explicitly.

Plugin (ruflo-adr):
- scripts/reindex.mjs (new) — purge both namespaces via `memory purge`,
  rescan every ADR currently on disk, store fresh, then re-list and assert
  the count equals files-on-disk (a storedRecords tally can't see a
  concurrent-writer clobber of the purge; a fresh recount can). Exits
  non-zero on failure. Full-namespace wipe rather than a selective orphan
  diff, since adr-edges keys always carry a fresh timestamp-rand suffix and
  are never deduplicated across imports — a selective pass would still
  leave duplicate edges accumulating for ADRs that do still exist.
- scripts/lib/parse-adrs.mjs (new) — findAdrs/parseAdr and sub-parsers
  extracted from import.mjs so reindex.mjs doesn't duplicate ~150 lines of
  dual-format parsing; import.mjs shrank 322 -> 156 lines, same dry-run
  output verified before/after (530 ADRs, same status/relation counts).
- import.mjs/verify.mjs: every npx subprocess call now passes `cwd: ROOT`.
- New `/adr-reindex` skill; adr-index/adr-verify SKILL.md cross-link it.
- ADR-0002 records the decision. plugin.json 0.3.0 -> 0.4.0.

Verified end-to-end against a scratch repo + local CLI build: 2 ADRs
indexed, one deleted from disk, re-run adr-index -> orphan survives
(bug reproduced exactly as filed), run adr-reindex -> orphan gone,
post-condition OK, exit 0.

New tests: __tests__/memory-purge-namespace-2666.test.ts (8 cases) —
namespace-scoped hard delete, cross-namespace isolation, the specific
#2652 regression (non-upsert re-store of a purged key must succeed),
invalid-namespace rejection, lock acquire/release/stale-takeover/
serialization. smoke.sh: 15 -> 21 checks.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): restore sql.js lockfile entry lost during local drift verification

A `git stash` + reinstall done while investigating unrelated pre-existing
pnpm-lock.yaml drift (verifying it wasn't caused by this branch) ran with
@claude-flow/cli/package.json temporarily reverted, regenerating
v3/pnpm-lock.yaml without the sql.js entry the prior commit added.
`git stash pop` restored package.json but not the lockfile state written
while the stash was active, so the previous commit's lockfile silently
lost that entry — CI's `pnpm install --frozen-lockfile` correctly caught
the drift (ERR_PNPM_OUTDATED_LOCKFILE). Re-ran `pnpm install` with
package.json in its correct state; frozen-lockfile install now passes
clean locally, matching CI's exact step.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-14 10:51:11 -04:00