feat: add resolvedAt + filterByResolved for reflection items (#447)#483
feat: add resolvedAt + filterByResolved for reflection items (#447)#483jlin53882 wants to merge 4 commits intoCortexReach:masterfrom
Conversation
Closes CortexReach#447 Minimal fix: passive suppression only — no tool yet. Add resolvedAt, resolvedBy, resolutionNote fields to ReflectionItemMetadata. Filter resolved items in loadAgentReflectionSlicesFromEntries so both inherited-rules and derived-focus injection paths are suppressed.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec610e71fa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/reflection-store.ts
Outdated
| const invariantCandidates = buildInvariantCandidates(unresolvedItemRows, legacyRows); | ||
| const derivedCandidates = buildDerivedCandidates(unresolvedItemRows, legacyRows); |
There was a problem hiding this comment.
Prevent legacy fallback from reviving resolved reflection items
Passing unresolvedItemRows into buildInvariantCandidates/buildDerivedCandidates re-enables the legacy fallback path whenever all item rows for a slice are resolved. In that case itemCandidates.length becomes 0, so those helpers read from legacyRows, which still contain the original lines from memory-reflection entries and have no resolvedAt signal. In the default write path (which still stores combined legacy rows), resolved items can therefore reappear in recall output, defeating the suppression behavior introduced by this commit.
Useful? React with 👍 / 👎.
Closes CortexReach#447 Minimal fix: passive suppression only — no tool yet. Add resolvedAt, resolvedBy, resolutionNote fields to ReflectionItemMetadata. Filter resolved items in loadAgentReflectionSlicesFromEntries so both inherited-rules and derived-focus injection paths are suppressed.
回覆:Prevent legacy fallback from reviving resolved reflection items分析正確,感謝找到這個 edge case。 修正內容在 // Filter out resolved items — passive suppression for #447
const unresolvedItemRows = itemRows.filter(({ metadata }) => metadata.resolvedAt === undefined);
// If there were item rows but all are resolved, suppress to prevent legacy
// fallback from reviving them. If there were NO item rows at all, allow
// legacy fallback to run (backward compatibility for stores that predate
// itemized reflection rows).
const hasItemRows = itemRows.length > 0;
if (hasItemRows && unresolvedItemRows.length === 0) {
return { invariants: [], derived: [] };
}邏輯
新增測試第四個測試覆蓋 all-resolved + legacy rows 並存: 測試結果36/37 通過。唯一失敗的是預先存在的 |
|
This fix is too broad. In I reproduced it with:
Current result: So this avoids reviving resolved content through legacy fallback, but it also drops unrelated legacy advice. The new test covers the “don’t revive resolved text” case, but not this mixed-data case. |
…dvice revival (P1)
|
Review comment addressed. Here is a summary of the two fixes: Fix 1: Content-aware early-return guardThe simple
This correctly handles the P1 issue: in the default configuration ( Fix 2: Additional test coverage
Test results38 pass / 1 pre-existing fail (the failing test is unrelated to this PR) Commit: |
1 similar comment
|
Review comment addressed. Here is a summary of the two fixes: Fix 1: Content-aware early-return guardThe simple
This correctly handles the P1 issue: in the default configuration ( Fix 2: Additional test coverage
Test results38 pass / 1 pre-existing fail (the failing test is unrelated to this PR) Commit: |
AliceLJY
left a comment
There was a problem hiding this comment.
Review: PR #483 — resolvedAt + filterByResolved for reflection items (#447)
Clean, minimal implementation that solves the core problem from #447.
Codex P1 assessment — "Prevent legacy fallback from reviving resolved reflection items"
Valid concern, and the author has already addressed it in the current code. The Codex comment warned that passing unresolvedItemRows (which can be empty when all items are resolved) into buildInvariantCandidates/buildDerivedCandidates would trigger the legacy fallback path, reviving resolved content from memory-reflection entries.
The fix is present: the code builds resolvedInvariantTexts/resolvedDerivedTexts sets from resolved items, then checks whether legacy rows contain any content that is NOT a duplicate of resolved items. The shouldSuppress logic (lines ~285-292) returns early with empty slices when all item rows are resolved AND legacy rows only contain duplicates. This correctly prevents the legacy fallback from reviving resolved advice.
Code review findings
-
Schema addition is clean —
resolvedAt,resolvedBy,resolutionNoteare all optional fields onReflectionItemMetadata. No migration needed, backward compatible. -
Filter logic is correct —
resolvedAt === undefinedas the unresolved check works because the field is genuinely absent (not set tonullor0). Themetadatatype enforces this via the optional?modifier. -
Test coverage is thorough — 6 new test cases covering:
- Invariant suppression
- Derived suppression
- Unresolved pass-through
- All-resolved + no legacy → empty output
- Legacy duplicate suppression (the P1 fix)
- Legacy with unique content NOT suppressed alongside resolved items
-
Edge case handled well — The "does NOT suppress legacy rows when resolved items exist alongside unrelated legacy rows" test confirms that unique legacy advice still surfaces even when all item rows are resolved.
-
Minor note —
normalizeReflectionLineForAggregationis used for duplicate detection between resolved items and legacy rows. This is the right function since it strips noise before comparison, reducing false negatives.
LGTM. The memory_reflection_resolve tool (noted as follow-up) is correctly deferred.
Review SummaryUseful feature (64%) — stale reflection items polluting context for 45 days is a real UX problem. The implementation has a legacy fallback gap that needs fixing. Must Fix
Questions
Nice to Have
Fix the legacy fallback leak and confirm test CI coverage, then this is mergeable. |
Fix 1 回覆:Legacy Fallback Bug 已在
|
UpdateFollow-up issue 已開:#558 — feat: add memory_reflection_resolve tool
等待維護者回覆設計確認後再實作。 |
|
Hi, sorry to bother you! I've addressed all the items from your review:
Follow-up issue for The PR is ready for your re-review whenever you have time. Thanks! |
CI cli-smoke failure 分析(#483 附帶、非本次 PR 導致)失敗的測試:
判定:flaky test,非 regression
該測試在 master branch 上本身就有 race condition 問題(常見於 Node.js 20→24 过渡期的并发测试),這次失敗屬於預期外的 flaky test。 建議:排除 flaky test 後可以 merge,或由 maintainer 確認後忽略該 check。 |
…resolved item revival (P2) 當 Invariants 全 resolved、Derived 有 unique legacy content 時, shouldSuppress=false,buildInvariantCandidates 會走 legacy fallback 並返回所有 legacy invariants,revive 已 resolved 的 items。 修復:在 call site 對 legacyRows 做 per-section 過濾, 只傳有 unique invariant content 的 rows 進 buildInvariantCandidates, 只傳有 unique derived content 的 rows 進 buildDerivedCandidates。 新增 2 個測試: - invariants resolved + derived has unique legacy: only derived passes - derived resolved + invariants has unique legacy: only invariants passes
P2 Bug Fix — Per-Section Legacy Filtering發現的問題對抗式 Code Review(Codex)發現了一個 P2 Bug: Bug 情境:當 Section A(Invariants)全 resolved、Section B(Derived)有 unique legacy content 時:
修復內容在 call site( // 只傳有 unique invariant content 的 legacy rows
const invariantLegacyRows = legacyRows.filter(({ metadata }) =>
toStringArray(metadata.invariants).some(
(line) => !resolvedInvariantTexts.has(normalizeReflectionLineForAggregation(line))
)
);
// 只傳有 unique derived content 的 legacy rows
const derivedLegacyRows = legacyRows.filter(({ metadata }) =>
toStringArray(metadata.derived).some(
(line) => !resolvedDerivedTexts.has(normalizeReflectionLineForAggregation(line))
)
);
const invariantCandidates = buildInvariantCandidates(unresolvedItemRows, invariantLegacyRows);
const derivedCandidates = buildDerivedCandidates(unresolvedItemRows, derivedLegacyRows);新增測試
Codex 對抗式確認✅ Codex 確認:「I did not find a discrete regression or missed edge case in this fix.」 測試狀態
|
CI
|
… from being revived (P1+P2) Issue: CortexReach#483 ## P1: Active suppression when all items resolved + legacy duplicates resolved content - Extract resolvedItemRows (metadata.resolvedAt !== undefined) - Collect normalized resolved invariant/derived text into Sets - Check if legacy rows have unique content not in resolved sets - Suppress output early when: hasItemRows && unresolvedItemRows.length === 0 && (!hasLegacyRows || (!legacyHasUniqueInvariant && !legacyHasUniqueDerived)) ## P2: Per-section legacy filtering - invariantCandidates gets only legacy rows with unique invariant content - derivedCandidates gets only legacy rows with unique derived content - Prevents resolved items in section A from being revived when section B has unique legacy content ## Tests added (5 new) - returns empty slices when ALL invariant items are resolved AND NO legacy rows - suppresses legacy rows when all legacy content duplicates already-resolved items - invariants resolved + derived has unique legacy: only derived passes - derived resolved + invariants has unique legacy: only invariants passes - does NOT suppress legacy rows when resolved items exist alongside unrelated legacy rows
|
I have created a clean version of this PR with targeted diff: This new PR has clean diff:
The original PR #483 has 2307/2169 diff due to formatting issues. Please consider:
The new branch contains the same P1+P2 fixes with clean, surgical changes. |
更新已重建乾淨的 PR:#595 Diff 現在只有 +70/-2,2 個檔案(原本的 +1709/-1195 是因為包含太多歷史 commit)。 變更內容
|
狀態更新 (2026-04-13)
修復已搬到新 PR:#595
這個 PR 現在關閉,由 PR #595 繼續。