Skip to content

fix: report learning citations precisely and clearly#141

Open
wenchanghan wants to merge 7 commits into
ReflexioAI:mainfrom
wenchanghan:codex/citation-labeling-precision
Open

fix: report learning citations precisely and clearly#141
wenchanghan wants to merge 7 commits into
ReflexioAI:mainfrom
wenchanghan:codex/citation-labeling-precision

Conversation

@wenchanghan

@wenchanghan wenchanghan commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Before

  • Normal coding-session markers and dashboard labels said "applied", which could be read as proof that a learning caused the response.
  • A model could decide that a learning passed the material-change test yet skip the optional-looking citation marker.
  • Citation resolution could award credit from ambiguous tags or duplicate a re-injected learning.
  • Dashboard views made injected and applied learnings hard to compare when zero was hidden.
  • The session-detail diagnostic exposed a technical "could not be verified" message that did not explain the customer impact.

Changed

  • Injection and normal coding sessions: retain the strict material-change plus counterfactual test. If one or more learnings pass both tests, the assistant is instructed to emit one final learning cited marker for exactly those learnings; otherwise it emits no marker. This is explicit attribution, not a causal-influence claim.
  • Compatibility: new markers and dashboard UI say cited; historic applied markers remain readable.
  • Resolution: credit only exact registry tags or canonical learning routes, and deduplicate re-injected aliases. Ambiguous, malformed, and fingerprint-mismatched markers never receive credit.
  • Dashboard: show N injected beside M cited, including zero. Tooltips explain that injected means available context, cited means verified explicit attribution, and zero cited does not prove zero influence.
  • Unconfirmed citations: in the session-detail dashboard only, show N citation(s) could not be tied to a learning injected in this session. The tooltip explains it is excluded from the cited count. This diagnostic is local-only: it does not change the normal coding response or enter the publish payload.

After

Normal coding sessions

  • A learning that does not pass the strict gate produces no marker.
  • A learning that does pass is more likely to be disclosed by the assistant, but the marker remains the assistant's self-report rather than proof of model influence.
  • Users do not see the dashboard-only unconfirmed-citation diagnostic in their coding response.

Dashboard

  • Injected = learning context was made available in the session.
  • Cited = the assistant explicitly named a learning and claude-smart verified that it was injected in that session.
  • Unconfirmed = the assistant named a learning, but claude-smart could not tie it to an injected learning in that session; it is never counted as cited.

Trust

  • The product never infers, appends, or manufactures a citation after the response.
  • Malformed, ambiguous, duplicate, and unconfirmed citations never inflate cited counts.
  • Citation metrics remain lower-bound attribution signals, not measurements of causal model influence.

Validation

  • uv run --project plugin pytest - 700 passed, 1 skipped
  • Dashboard npm run lint, npx tsc --noEmit, and npm run build
  • 143 focused citation, context-rendering, and host-event tests
  • Independent standards/spec reviews; one weaker OSC 8 instruction was corrected
  • Six synthetic, non-sensitive screenshots cover dashboard, sessions, verified citation, unconfirmed citation, Skills, and Preferences

Supersedes #140 (refiled from personal fork / wenchanghan).

Stop pre-building multi-item "copy this marker" instructions so models
are not nudged to over-cite every injected memory. Resolve unique rank
fingerprint drift so true markers still count, persist unresolved marker
ids locally for diagnostics (not on the publish wire), and show injected
vs applied counts in the dashboard.
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR changes citation terminology and metrics across the citation pipeline and dashboard, preserves unresolved citation IDs, adds injected-learning counts, and updates session, dashboard, skills, and preferences views to display cited-learning statistics.

Changes

Citation pipeline

Layer / File(s) Summary
Citation resolution and marker instructions
plugin/src/claude_smart/events/stop.py, plugin/src/claude_smart/cs_cite.py, plugin/src/claude_smart/context_format.py, tests/*
Citation markers use “learning cited” wording, qualifying-item guidance is updated, resolved citations are deduplicated, and unresolved IDs are retained in assistant records.
Session contracts and citation statistics
plugin/dashboard/lib/types.ts, plugin/dashboard/lib/session-reader.ts, plugin/src/claude_smart/state.py
Session records expose unresolved citations, citation counts, cited-learning counts, and unique injected-learning counts. Rule statistics use citation counters and timestamps.
Cited statistics and badges
plugin/dashboard/app/api/rules/cited/route.ts, plugin/dashboard/app/dashboard/page.tsx, plugin/dashboard/app/skills/page.tsx, plugin/dashboard/app/preferences/page.tsx, plugin/dashboard/components/common/*
Dashboard data loading, sorting, labels, and badges switch from applied metrics to cited metrics and add injected-learning indicators.
Session citation diagnostics
plugin/dashboard/app/sessions/page.tsx, plugin/dashboard/app/sessions/[sessionId]/page.tsx
Session views show injected and cited badges and render warnings for unresolved citations.
Citation behavior documentation
README.md
The citation marker example and attribution explanation are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Assistant
  participant StopHook
  participant SessionReader
  participant Dashboard
  Assistant->>StopHook: emit citation marker
  StopHook->>SessionReader: store cited_items and unresolved_citations
  SessionReader->>Dashboard: provide citation and injection metrics
  Dashboard-->>Dashboard: render cited and unresolved-citation status
Loading

Possibly related PRs

Suggested reviewers: yyiilluu, yilu331

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main change: improving learning citation precision and reporting.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/src/claude_smart/events/stop.py (1)

299-321: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Deduplicate the resolved registry entry, not only the raw citation token.

Different aliases such as an exact ID and a stale fingerprint can resolve to the same entry, producing duplicate cited_items and inflated applied counts. Normalize raw tokens and track the resolved entry’s canonical ID.

Proposed fix
-    seen: set[str] = set()
+    seen_citations: set[str] = set()
+    seen_entries: set[str] = set()
     resolved: list[dict[str, Any]] = []
     unresolved: list[str] = []
     for cid in cited_ids:
-        if cid in seen:
+        normalized_cid = cid.casefold()
+        if normalized_cid in seen_citations:
             continue
-        seen.add(cid)
+        seen_citations.add(normalized_cid)
         entry = _registry_entry_for_citation(registry, cid)
         if not entry:
             unresolved.append(cid)
             continue
+        canonical_id = str(entry.get("id") or cid).casefold()
+        if canonical_id in seen_entries:
+            continue
+        seen_entries.add(canonical_id)
         item: dict[str, Any] = {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/src/claude_smart/events/stop.py` around lines 299 - 321, Update the
citation loop in the stop-event resolution logic to normalize each raw token
before lookup and deduplicate using the resolved registry entry’s canonical ID,
rather than only the raw citation token. Ensure aliases such as an exact ID and
stale fingerprint produce one resolved item and do not inflate applied counts,
while preserving unresolved-token handling.
🧹 Nitpick comments (1)
plugin/dashboard/lib/session-reader.ts (1)

438-449: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Parallelize I/O for reading session and injected data.

The addition of await countUniqueInjected(sessionId) inside this for loop introduces a second sequential file read per session. To improve dashboard load times without risking file-descriptor limits (which could happen if we parallelized all sessions at once), you can read the two files for each session concurrently.

⚡ Proposed refactor
-    const records = await readJsonl(fullPath).catch(() => []);
+    const [records, injectedLearningCount] = await Promise.all([
+      readJsonl(fullPath).catch(() => []),
+      countUniqueInjected(sessionId),
+    ]);
     const {
       turns,
       publishedUpTo,
       learningInteractionCount,
       lastTs,
       firstTs,
       preview,
       host,
       requestIds,
     } = foldTurns(records);
-    const injectedLearningCount = await countUniqueInjected(sessionId);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/dashboard/lib/session-reader.ts` around lines 438 - 449, Within the
session-processing loop, start the session JSONL read and
countUniqueInjected(sessionId) concurrently, then await both results together
before calling foldTurns. Preserve the existing fallback of [] for readJsonl
failures and keep concurrency scoped to each session rather than parallelizing
across sessions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/src/claude_smart/context_format.py`:
- Around line 353-357: Update the available-memory label in the context
formatting flow around marker_parts so it does not call every linked entry
“Qualifying.” Use neutral wording that reflects these are linked titles pending
the model’s relevance decision, while preserving the existing joined list and
punctuation.

---

Outside diff comments:
In `@plugin/src/claude_smart/events/stop.py`:
- Around line 299-321: Update the citation loop in the stop-event resolution
logic to normalize each raw token before lookup and deduplicate using the
resolved registry entry’s canonical ID, rather than only the raw citation token.
Ensure aliases such as an exact ID and stale fingerprint produce one resolved
item and do not inflate applied counts, while preserving unresolved-token
handling.

---

Nitpick comments:
In `@plugin/dashboard/lib/session-reader.ts`:
- Around line 438-449: Within the session-processing loop, start the session
JSONL read and countUniqueInjected(sessionId) concurrently, then await both
results together before calling foldTurns. Preserve the existing fallback of []
for readJsonl failures and keep concurrency scoped to each session rather than
parallelizing across sessions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 96617689-7578-47ae-9a25-835615163504

📥 Commits

Reviewing files that changed from the base of the PR and between 045d8b3 and ac57191.

📒 Files selected for processing (11)
  • plugin/dashboard/app/dashboard/page.tsx
  • plugin/dashboard/app/sessions/[sessionId]/page.tsx
  • plugin/dashboard/app/sessions/page.tsx
  • plugin/dashboard/components/common/injected-badge.tsx
  • plugin/dashboard/lib/session-reader.ts
  • plugin/dashboard/lib/types.ts
  • plugin/src/claude_smart/context_format.py
  • plugin/src/claude_smart/events/stop.py
  • plugin/src/claude_smart/state.py
  • tests/test_context_format.py
  • tests/test_events.py

Comment thread plugin/src/claude_smart/context_format.py
@wenchanghan

Copy link
Copy Markdown
Contributor Author

Code Review — PR #141

Reviewed axes: Standards (layer correctness, code smells, optimizations) and Spec (fidelity to stated goals: more truthful citations, no new false positives, dashboard observability).


Standards

Layers — clean. context_format.py stays in the prompt layer, stop.py in the resolution layer, state.py's wire exclusion is the right place to strip diagnostic fields, dashboard changes are purely display. No cross-layer bleeding.

seen.add(cid) move in stop.py — correct. Old code skipped seen.add for unresolvable ids, so the same unresolved token could be appended multiple times to unresolved. The new placement deduplicates before lookup, which is strictly better.

Truthfulness logic — sound. The len(candidates) == 1 guard in the rank fallback correctly trades a small false-negative risk (rare unresolved drift case) for zero false positives. Consistent with "FP is worse than FN."


Spec

All four spec requirements are implemented. One scope addition, two correctness/performance issues:


⚠️ Bug — bare rank id can grant unearned credit (contradicts "never invent credit" invariant)

File: plugin/src/claude_smart/events/stop.py

_RANK_ID_RE makes the fingerprint group optional:

_RANK_ID_RE = re.compile(r"^(?P<rank>[ps]\d+)(?:-(?P<fp>[a-z0-9]{1,8}))?$", re.I)

If a model emits a bare rank like s4 (no dash, no fingerprint), this regex matches, and the candidate search key.lower().startswith("s4-") can return one unique entry and resolve it — granting applied credit for a token that was never a proper fingerprinted citation.

The spec says: "we never invent credit" and "ambiguous or empty matches stay unresolved." A bare s4 is not a valid fingerprinted id and should be rejected before entering the fuzzy path.

Fix: require the fp group to be present before running the rank-prefix candidate search:

match = _RANK_ID_RE.fullmatch(citation.strip())
if not match or not match.group("fp"):   # require fingerprint
    return None

⚠️ Performance regression — countUniqueInjected is awaited serially per session in listSessions()

File: plugin/dashboard/lib/session-reader.ts

// Inside the for...of loop over entries:
const injectedLearningCount = await countUniqueInjected(sessionId);

Each iteration does a sequential async file read. With N sessions this is O(N) sequential disk reads added on top of the existing O(N) .jsonl reads — doubling list-page I/O. The existing listSessions implementation is already fully sequential, but this makes it noticeably slower for users with many sessions.

Fix: collect session ids first, then fan out:

const injectedCounts = await Promise.all(
  sessionIds.map((id) => countUniqueInjected(id))
);

(Or collect session ids + their full paths in the first loop pass, then Promise.all the count reads.)


📝 Minor — lowered dict rebuilt per citation in _registry_entry_for_citation

File: plugin/src/claude_smart/events/stop.py

lowered = {key.lower(): value for key, value in registry.items()}

This is O(|registry|) and called once per element of cited_ids. In practice both are small, so no production impact — but it's a needless allocation per call. Pre-computing lowered once in _resolve_cited_items and passing it down would be cleaner.


ℹ️ Scope note — UnresolvedCitationsRow not in spec

File: plugin/dashboard/app/sessions/[sessionId]/page.tsx

The spec says "persist unresolved marker ids locally for diagnostics" — it does not explicitly call for surfacing them in the per-turn dashboard view. UnresolvedCitationsRow is a sensible diagnostic addition, but it's unasked-for scope. No action required; flagging for awareness.


Summary

Axis Findings Worst issue
Standards 1 perf smell + 1 minor inefficiency Serial countUniqueInjected in list loop
Spec 1 bug + 1 scope note Bare rank id bypasses FP guard

The bare-rank-id bug is the one to fix before merge — it directly contradicts the PR's core correctness invariant. The serial read regression is worth addressing now since the sessions list is already the slowest page.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/dashboard/lib/session-reader.ts (1)

450-485: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Parallelize session file reads.

Currently, listSessions() iterates over the directory entries sequentially, waiting for each session's file reads to complete before starting the next. As noted in the PR objectives, mapping the entries to promises and awaiting them concurrently via Promise.all() will significantly improve the dashboard's load performance.

⚡ Proposed fix
-  const summaries: SessionSummary[] = [];
-  for (const entry of entries) {
-    if (!entry.endsWith(".jsonl")) continue;
-    if (entry.endsWith(".injected.jsonl")) continue;
-    const fullPath = path.join(dir, entry);
-    const sessionId = entry.replace(/\.jsonl$/, "");
-    const [records, injectedLearningCount] = await Promise.all([
-      readJsonl(fullPath).catch(() => []),
-      countUniqueInjected(sessionId),
-    ]);
-    const {
-      turns,
-      publishedUpTo,
-      learningInteractionCount,
-      appliedLearningCount,
-      lastTs,
-      firstTs,
-      preview,
-      host,
-      requestIds,
-    } = foldTurns(records);
-    summaries.push({
-      session_id: sessionId,
-      turn_count: turns.length,
-      learning_interaction_count: learningInteractionCount,
-      applied_learning_count: appliedLearningCount,
-      injected_learning_count: injectedLearningCount,
-      last_activity: lastTs,
-      first_activity: firstTs,
-      published_up_to: publishedUpTo,
-      preview,
-      source: "local",
-      host,
-      request_ids: requestIds,
-    });
-  }
+  const sessionPromises = entries
+    .filter((entry) => entry.endsWith(".jsonl") && !entry.endsWith(".injected.jsonl"))
+    .map(async (entry): Promise<SessionSummary> => {
+      const fullPath = path.join(dir, entry);
+      const sessionId = entry.replace(/\.jsonl$/, "");
+      const [records, injectedLearningCount] = await Promise.all([
+        readJsonl(fullPath).catch(() => []),
+        countUniqueInjected(sessionId),
+      ]);
+      const {
+        turns,
+        publishedUpTo,
+        learningInteractionCount,
+        appliedLearningCount,
+        lastTs,
+        firstTs,
+        preview,
+        host,
+        requestIds,
+      } = foldTurns(records);
+      return {
+        session_id: sessionId,
+        turn_count: turns.length,
+        learning_interaction_count: learningInteractionCount,
+        applied_learning_count: appliedLearningCount,
+        injected_learning_count: injectedLearningCount,
+        last_activity: lastTs,
+        first_activity: firstTs,
+        published_up_to: publishedUpTo,
+        preview,
+        source: "local",
+        host,
+        request_ids: requestIds,
+      };
+    });
+
+  const summaries = await Promise.all(sessionPromises);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/dashboard/lib/session-reader.ts` around lines 450 - 485, Update
listSessions() to map eligible session entries to promises and await them with
Promise.all(), so readJsonl() and countUniqueInjected() for all sessions run
concurrently. Preserve the existing filtering, foldTurns() processing, and
SessionSummary fields, excluding invalid or injected entries as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@plugin/dashboard/lib/session-reader.ts`:
- Around line 450-485: Update listSessions() to map eligible session entries to
promises and await them with Promise.all(), so readJsonl() and
countUniqueInjected() for all sessions run concurrently. Preserve the existing
filtering, foldTurns() processing, and SessionSummary fields, excluding invalid
or injected entries as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cc57564-3bb0-4b10-9e71-a9c75635a292

📥 Commits

Reviewing files that changed from the base of the PR and between ac57191 and b28d9e1.

📒 Files selected for processing (10)
  • plugin/dashboard/app/dashboard/page.tsx
  • plugin/dashboard/app/sessions/[sessionId]/page.tsx
  • plugin/dashboard/app/sessions/page.tsx
  • plugin/dashboard/components/common/learnings-badge.tsx
  • plugin/dashboard/lib/session-reader.ts
  • plugin/dashboard/lib/types.ts
  • plugin/src/claude_smart/context_format.py
  • plugin/src/claude_smart/events/stop.py
  • tests/test_context_format.py
  • tests/test_events.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • plugin/dashboard/app/sessions/page.tsx
  • plugin/dashboard/app/sessions/[sessionId]/page.tsx
  • plugin/dashboard/app/dashboard/page.tsx
  • plugin/dashboard/lib/types.ts
  • plugin/src/claude_smart/context_format.py
  • tests/test_context_format.py

@wenchanghan

Copy link
Copy Markdown
Contributor Author
Synthetic sessions list Synthetic session detail

Follow-up fixes are ready in b28d9e1.

  • Exact registry tags or canonical routes are the only sources of applied credit; unknown, bare, and fingerprint-mismatched markers stay unresolved.
  • Rank and route aliases, including a re-injected learning at a new rank, collapse to one cited learning.
  • Session badges compare unique injected vs. unique applied learnings; the dashboard-wide statistic remains turn-scoped.

Validation: uv run --project plugin pytest (700 passed, 1 skipped); dashboard typecheck, lint, and production build passed.

These two screenshots use synthetic local data only. They show a re-ranked learning collapsing to 2 injected / 2 applied, and an unresolved marker displayed separately.

@wenchanghan

wenchanghan commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Updated synthetic dashboard evidence

  • The sessions list now keeps the meaningful zero visible: 8 injected beside 0 learnings applied.
  • Citation marker not linked means the assistant emitted a marker that did not match this session’s injected registry. The mismatch is retained as a local diagnostic and is not counted as applied.

Both screenshots use synthetic local data only.

Session detail showing an unlinked citation marker and zero verified applications Sessions list showing eight injected learnings and zero verified applications

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
plugin/dashboard/lib/session-reader.ts (1)

450-485: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Read sessions concurrently to avoid N+1 file I/O latency.

Both listSessions and listCitedRules iterate over the session entries sequentially, awaiting each file read before moving to the next. For larger numbers of sessions, this sequential processing significantly increases dashboard load times. You can use Promise.all to read all valid entries concurrently before aggregating them.

  • plugin/dashboard/lib/session-reader.ts#L450-L485: Replace the for loop in listSessions with await Promise.all(entries.filter(...).map(async (entry) => ...)).
  • plugin/dashboard/lib/session-reader.ts#L242-L293: Apply the same parallelization pattern in listCitedRules to concurrently fetch the records arrays, then flat-map or reduce them into the stats map.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/dashboard/lib/session-reader.ts` around lines 450 - 485, Parallelize
session processing in listSessions by replacing its sequential loop with
Promise.all over valid entries, preserving each entry’s filtering, record
reading, counting, folding, and summary construction. Apply the same concurrent
record-fetch pattern in listCitedRules, then aggregate the fetched results into
the existing stats map. Affected sites: plugin/dashboard/lib/session-reader.ts
lines 450-485 (update listSessions); plugin/dashboard/lib/session-reader.ts
lines 242-293 (update listCitedRules).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@plugin/dashboard/lib/session-reader.ts`:
- Around line 450-485: Parallelize session processing in listSessions by
replacing its sequential loop with Promise.all over valid entries, preserving
each entry’s filtering, record reading, counting, folding, and summary
construction. Apply the same concurrent record-fetch pattern in listCitedRules,
then aggregate the fetched results into the existing stats map. Affected sites:
plugin/dashboard/lib/session-reader.ts lines 450-485 (update listSessions);
plugin/dashboard/lib/session-reader.ts lines 242-293 (update listCitedRules).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c110e82-b6c8-4fb1-84a1-3c3f96111112

📥 Commits

Reviewing files that changed from the base of the PR and between bc5ffec and 69a8420.

📒 Files selected for processing (18)
  • README.md
  • plugin/dashboard/app/api/rules/cited/route.ts
  • plugin/dashboard/app/dashboard/page.tsx
  • plugin/dashboard/app/preferences/page.tsx
  • plugin/dashboard/app/sessions/[sessionId]/page.tsx
  • plugin/dashboard/app/sessions/page.tsx
  • plugin/dashboard/app/skills/page.tsx
  • plugin/dashboard/components/common/cited-learnings-badge.tsx
  • plugin/dashboard/components/common/injected-badge.tsx
  • plugin/dashboard/components/common/learning-citation-badge.tsx
  • plugin/dashboard/lib/session-reader.ts
  • plugin/dashboard/lib/types.ts
  • plugin/src/claude_smart/cs_cite.py
  • tests/host_learning_harness.py
  • tests/test_codex_support.py
  • tests/test_context_format.py
  • tests/test_cs_cite.py
  • tests/test_events.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_context_format.py

@wenchanghan wenchanghan changed the title fix: make citation labels more precise and observable fix: report learning citations precisely and clearly Jul 18, 2026
@wenchanghan

Copy link
Copy Markdown
Contributor Author

Synthetic UI proof for this PR — no customer or real-session data:

  1. Dashboard overview — cited response and cited-learning counts
  2. Sessions list — injected/cited shown, including zero citations
  3. Session detail — verified cited learnings
  4. Session detail — citation not tied to an injected learning (excluded from cited count)
  5. Skills — cited-learning badge
  6. Preferences — cited-learning badge
pr141-dashboard-cited pr141-sessions-list-cited pr141-session-detail-cited pr141-session-detail-unconfirmed-citation pr141-skills-cited pr141-preferences-cited

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant