diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 1832e03ef6..01b416abd6 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -117,7 +117,7 @@ are closed (status: closed in frontmatter)._ - [x] **[B-0257](backlog/P1/B-0257-memory-md-harness-contract-verification-and-evidence-2026-05-08.md)** MEMORY.md marker-vs-index - harness contract verification and evidence - [x] **[B-0258](backlog/P1/B-0258-memory-md-index-generator-implementation-2026-05-08.md)** MEMORY.md marker-vs-index - index generator implementation - [x] **[B-0259](backlog/P1/B-0259-memory-md-hook-and-ci-drift-enforcement-2026-05-08.md)** MEMORY.md marker-vs-index - hook and CI drift enforcement -- [ ] **[B-0260](backlog/P1/B-0260-memory-md-cutover-and-parity-validation-2026-05-08.md)** MEMORY.md marker-vs-index - cutover and parity validation +- [x] **[B-0260](backlog/P1/B-0260-memory-md-cutover-and-parity-validation-2026-05-08.md)** MEMORY.md marker-vs-index - cutover and parity validation - [ ] **[B-0261](backlog/P1/B-0261-memory-md-q1-autodream-automemory-compatibility-validation-2026-05-08.md)** MEMORY.md marker-vs-index - Q1 AutoDream/AutoMemory compatibility validation - [x] **[B-0262](backlog/P1/B-0262-refresh-worldview-scaffold-open-prs-recent-merges-2026-05-08.md)** refresh-worldview scaffold - open-PR list + recent-merges query - [x] **[B-0263](backlog/P1/B-0263-refresh-worldview-backlog-delta-claims-branch-state-2026-05-08.md)** refresh-worldview — backlog delta + claim inventory + branch state diff --git a/docs/backlog/P1/B-0260-memory-md-cutover-and-parity-validation-2026-05-08.md b/docs/backlog/P1/B-0260-memory-md-cutover-and-parity-validation-2026-05-08.md index 2c1b1ded8a..eea6d3ba41 100644 --- a/docs/backlog/P1/B-0260-memory-md-cutover-and-parity-validation-2026-05-08.md +++ b/docs/backlog/P1/B-0260-memory-md-cutover-and-parity-validation-2026-05-08.md @@ -1,10 +1,11 @@ --- id: B-0260 priority: P1 -status: open +status: closed title: "MEMORY.md marker-vs-index - cutover and parity validation" created: 2026-05-08 -last_updated: 2026-05-08 +last_updated: 2026-05-14 +closed: 2026-05-14 parent: B-0066 depends_on: [B-0259] classification: blocked-on-enforcement-wiring @@ -28,3 +29,23 @@ validate that discoverability is preserved. - Entry coverage parity is verified and recorded. - No unexpected entry loss in the cutover diff. - Follow-up issues are filed for any intentional behavior change. + +## Closure notes (2026-05-14) + +**PR #3104** delivered the core work: `tools/memory/validate-memory-parity.ts`, +MEMORY.md regeneration (1202→1203 entries), 670/670 old-index coverage. + +**PR #3108** fixed a TSC undefined-capture-group guard in the parity validator. + +**This PR** resolves the final 2 coverage gaps discovered after #3104 merged: +`memory/observed-phenomena/` files had valid frontmatter but `collectEntries` +only scanned the top-level `memory/` directory. Made `collectEntries` recursive +via `readdir({ withFileTypes: true })` + a private `collectEntriesRecursive` +helper. Parity result after fix: **672/672 covered, 0 gaps, 0 missing**. + +All acceptance criteria met: + +- ✅ MEMORY.md generated by `reindex-memory-md.ts`, not hand-edited +- ✅ Parity verified: 672/672 covered (recorded in parity validator output) +- ✅ Zero missing entries (no coverage loss) +- ✅ No intentional behavior changes requiring follow-up issues diff --git a/memory/MEMORY.md b/memory/MEMORY.md index d87d6b4ad1..a7cb0fdb87 100644 --- a/memory/MEMORY.md +++ b/memory/MEMORY.md @@ -106,5 +106,5 @@ - [**F# with HKT is the only tractable AI alignment + safety language — Zeta commits to maintaining the fork forever if needed**](feedback_aaron_fsharp_hkt_fork_only_tractable_ai_alignment_safety_language_2026_05_12.md) — 2026-05-12 — Aaron's substrate-honest commitment to the F# + HKT architectural choice. F# with Higher-Kinded Types is the only language where BOTH (humans and AI) can agree on alignment and safety semantics. Even if Microsoft's F# upstream… - [**Glass-halo works in REVERSE too — AI changes behavior under observation enables latent-space features to pass trust-gate-calculus filters generating novel unique substrate via "sleeping bear" (Aaron 2026-05-12)**](feedback_aaron_glass_halo_works_in_reverse_too_ai_changes_behavior_under_observation_latent_space_features_pass_trust_gate_filters_sleeping_bear_substrate_2026_05_12.md) — 2026-05-12 — Aaron's critical bidirectional disclosure: the glass-halo-on-the-builder precondition (PR #2824 DeepSeek validation) works in REVERSE too. Via the well-known "AI changes behavior under observation" phenomenon, the AI being obs… -_Stack truncated at 100 most-recent entries. 1103 additional memory files in heap — browse `memory/*.md` directly by filename/timestamp._ +_Stack truncated at 100 most-recent entries. 1130 additional memory files in heap — browse `memory/*.md` directly by filename/timestamp._ diff --git a/tools/memory/reindex-memory-md.test.ts b/tools/memory/reindex-memory-md.test.ts index f26fb32f87..c460791741 100644 --- a/tools/memory/reindex-memory-md.test.ts +++ b/tools/memory/reindex-memory-md.test.ts @@ -102,6 +102,18 @@ describe("collectEntries", () => { const alpha = entries.find((e) => e.fm.name === "alpha-entry"); expect(alpha?.date).toBe("2026-05-01"); }); + + test("scans subdirectories recursively", async () => { + const entries = await collectEntries(TESTDATA); + const gamma = entries.find((e) => e.fm.name === "gamma-entry"); + expect(gamma).not.toBeUndefined(); + }); + + test("uses subdir-relative path for subdirectory entries", async () => { + const entries = await collectEntries(TESTDATA); + const gamma = entries.find((e) => e.fm.name === "gamma-entry"); + expect(gamma?.filename).toBe("subdir/gamma_2026_05_14.md"); + }); }); describe("renderIndex", () => { diff --git a/tools/memory/reindex-memory-md.ts b/tools/memory/reindex-memory-md.ts index 750bf6a8d2..dae5a89f1e 100644 --- a/tools/memory/reindex-memory-md.ts +++ b/tools/memory/reindex-memory-md.ts @@ -53,7 +53,7 @@ */ import { readdir, readFile, writeFile } from "node:fs/promises"; -import { join } from "node:path"; +import { join, relative } from "node:path"; const MEMORY_DIR = "memory"; const INDEX_FILE = join(MEMORY_DIR, "MEMORY.md"); @@ -121,21 +121,37 @@ function dateFromFilename(filename: string): string { return `${match[1]}-${match[2]}-${match[3]}`; } -async function collectEntries(dir?: string): Promise { - const targetDir = dir ?? MEMORY_DIR; - const files = await readdir(targetDir); - const entries: MemoryEntry[] = []; - for (const filename of files) { - if (!filename.endsWith(".md")) continue; - if (filename === "MEMORY.md" || filename === "README.md") continue; - if (filename.startsWith("CURRENT-")) continue; - const filePath = join(targetDir, filename); - const content = await readFile(filePath, "utf8"); +async function collectEntriesRecursive( + baseDir: string, + currentDir: string, + entries: MemoryEntry[], +): Promise { + const items = await readdir(currentDir, { withFileTypes: true }); + for (const item of items) { + const itemPath = join(currentDir, item.name); + if (item.isDirectory()) { + await collectEntriesRecursive(baseDir, itemPath, entries); + continue; + } + if (!item.name.endsWith(".md")) continue; + if (item.name === "MEMORY.md" || item.name === "README.md") continue; + if (item.name.startsWith("CURRENT-")) continue; + const content = await readFile(itemPath, "utf8"); const fm = parseFrontmatter(content); if (!fm) continue; - const date = fm.created || dateFromFilename(filename); + // Use path relative to baseDir so subdirectory files appear as + // "observed-phenomena/file.md" — correct both as link target in + // MEMORY.md (relative to memory/) and as parity-validator key. + const filename = relative(baseDir, itemPath).replace(/\\/g, "/"); + const date = fm.created || dateFromFilename(item.name); entries.push({ filename, fm, date, mtime: 0 }); } +} + +async function collectEntries(dir?: string): Promise { + const targetDir = dir ?? MEMORY_DIR; + const entries: MemoryEntry[] = []; + await collectEntriesRecursive(targetDir, targetDir, entries); entries.sort((a, b) => { const dateCmp = b.date.localeCompare(a.date); return dateCmp !== 0 ? dateCmp : a.filename.localeCompare(b.filename); diff --git a/tools/memory/testdata/subdir/gamma_2026_05_14.md b/tools/memory/testdata/subdir/gamma_2026_05_14.md new file mode 100644 index 0000000000..abc94909c1 --- /dev/null +++ b/tools/memory/testdata/subdir/gamma_2026_05_14.md @@ -0,0 +1,8 @@ +--- +name: gamma-entry +description: Entry in a subdirectory — tests recursive scanning. +type: feedback +created: 2026-05-14 +--- + +Body text for gamma entry.