* 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>