feat(intelligence): add Graph Completion Hints (Adamic-Adar triad closure)#3002
feat(intelligence): add Graph Completion Hints (Adamic-Adar triad closure)#3002aashir-athar wants to merge 1 commit into
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (22)
✅ Files skipped from review due to trivial changes (12)
🚧 Files skipped from review as they are similar to previous changes (10)
📝 WalkthroughWalkthroughThis PR implements the Triad Closure feature—a graph-completion algorithm that identifies suggested edges using Adamic–Adar scoring over open wedges. It adds the deterministic ChangesTriad Closure Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
app/src/components/intelligence/TriadClosureTab.tsx (1)
43-45: ⚡ Quick winUse
async/awaitfor the namespace bootstrap.This
.then()/.catch()chain conflicts with the repo’s TS/TSX guideline. Fold it into an async helper inside the effect so the bootstrap path matches the rest of the file.♻️ Guideline-aligned rewrite
- loadNamespaces() - .then(setNamespaces) - .catch(() => setNamespaces([])); + void (async () => { + try { + setNamespaces(await loadNamespaces()); + } catch { + setNamespaces([]); + } + })();As per coding guidelines: "Always use async/await for promises instead of
.then()chains in TypeScript".🤖 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 `@app/src/components/intelligence/TriadClosureTab.tsx` around lines 43 - 45, The promise chain using loadNamespaces().then(setNamespaces).catch(() => setNamespaces([])) should be replaced with an async helper inside the same effect: create an async function (e.g., async function bootstrapNamespaces()) that awaits loadNamespaces(), calls setNamespaces with the result, and uses try/catch to call setNamespaces([]) on error; then invoke that helper from the effect. Refer to loadNamespaces and setNamespaces when implementing the await/try/catch flow so the bootstrap matches the file’s async/await style.
🤖 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 `@app/src/components/intelligence/TriadClosureTab.tsx`:
- Around line 24-38: The load function currently always calls setLoading(true)
and setError(null) synchronously, which causes an unwanted mount-time state
reset; modify load (the useCallback named load that uses
latestRequestId.current, setLoading, setError, setResult) to accept an optional
flag (e.g., initialLoad or skipReset) and when that flag is true skip the
initial synchronous setLoading(true)/setError(null) calls and also avoid
toggling loading in finally for that invocation; update the useEffect mount call
to invoke load('', true) (or equivalent) so the initial mount reuses existing
defaults while all other calls keep the existing behaviour and requestId
cancellation checks.
In `@app/src/lib/memory/triadClosure.ts`:
- Around line 76-79: The limit handling in TriadClosureOptions is inconsistent:
negative limits are documented as clamped to 0 but the code uses rawLimit <= 0
to treat negatives as "unlimited". In the TriadClosure logic (look for rawLimit
usage where you normalize options and any code near the previous change around
lines 103-105), change the normalization so that rawLimit === 0 is the only
sentinel for "unlimited", and if rawLimit < 0 clamp it to 0 (or otherwise return
an empty slice/fail closed) before using it; replace checks like rawLimit <= 0
with a two-way branch: if rawLimit === 0 => unlimited, else if rawLimit < 0 =>
set limit = 0, else limit = rawLimit.
In `@app/src/services/api/triadClosureApi.ts`:
- Around line 14-19: The log call in loadTriadClosure currently prints the raw
namespace (PII) — change it to use a stable bracketed prefix and a redacted
correlation field instead: do not log namespace verbatim; compute a redacted
scope/correlation (e.g., a fixed prefix like "[triad-closure]" plus a masked or
hashed namespace ID or an opaque request/correlation id) and pass that into the
log call along with relations.length; update the log invocation in
loadTriadClosure (which calls memoryGraphQuery) to emit something like a
bracketed prefix and the redacted correlation field instead of namespace to
avoid leaking PII.
---
Nitpick comments:
In `@app/src/components/intelligence/TriadClosureTab.tsx`:
- Around line 43-45: The promise chain using
loadNamespaces().then(setNamespaces).catch(() => setNamespaces([])) should be
replaced with an async helper inside the same effect: create an async function
(e.g., async function bootstrapNamespaces()) that awaits loadNamespaces(), calls
setNamespaces with the result, and uses try/catch to call setNamespaces([]) on
error; then invoke that helper from the effect. Refer to loadNamespaces and
setNamespaces when implementing the await/try/catch flow so the bootstrap
matches the file’s async/await style.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc119219-83d7-4de3-aa7b-540e2af79dc8
📒 Files selected for processing (23)
app/src/components/intelligence/TriadClosurePanel.test.tsxapp/src/components/intelligence/TriadClosurePanel.tsxapp/src/components/intelligence/TriadClosureTab.test.tsxapp/src/components/intelligence/TriadClosureTab.tsxapp/src/lib/i18n/chunks/ar-1.tsapp/src/lib/i18n/chunks/bn-1.tsapp/src/lib/i18n/chunks/de-1.tsapp/src/lib/i18n/chunks/en-1.tsapp/src/lib/i18n/chunks/es-1.tsapp/src/lib/i18n/chunks/fr-1.tsapp/src/lib/i18n/chunks/hi-1.tsapp/src/lib/i18n/chunks/id-1.tsapp/src/lib/i18n/chunks/it-1.tsapp/src/lib/i18n/chunks/ko-1.tsapp/src/lib/i18n/chunks/pt-1.tsapp/src/lib/i18n/chunks/ru-1.tsapp/src/lib/i18n/chunks/zh-CN-1.tsapp/src/lib/i18n/en.tsapp/src/lib/memory/triadClosure.test.tsapp/src/lib/memory/triadClosure.tsapp/src/pages/Intelligence.tsxapp/src/services/api/triadClosureApi.test.tsapp/src/services/api/triadClosureApi.ts
| const load = useCallback(async (ns: string) => { | ||
| const requestId = (latestRequestId.current += 1); | ||
| setLoading(true); | ||
| setError(null); | ||
| try { | ||
| const next = await loadTriadClosure(ns || undefined); | ||
| if (requestId !== latestRequestId.current) return; | ||
| setResult(next); | ||
| } catch (err) { | ||
| if (requestId !== latestRequestId.current) return; | ||
| setError(err instanceof Error ? err.message : String(err)); | ||
| } finally { | ||
| if (requestId === latestRequestId.current) setLoading(false); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Keep the mount effect from synchronously resetting local state.
void load('') still executes setLoading(true) and setError(null) before the first await, so the initial useEffect is indirectly doing the synchronous state reset pattern this repo has been avoiding. Please special-case the mount path so the effect can reuse the existing loading/error defaults without calling those setters first.
♻️ One minimal way to avoid the mount-time reset
- const load = useCallback(async (ns: string) => {
+ const load = useCallback(async (ns: string, resetState = true) => {
const requestId = (latestRequestId.current += 1);
- setLoading(true);
- setError(null);
+ if (resetState) {
+ setLoading(true);
+ setError(null);
+ }
try {
const next = await loadTriadClosure(ns || undefined);
if (requestId !== latestRequestId.current) return;
setResult(next);
@@
- void load('');
+ void load('', false);
}, [load]);Based on learnings: "In React components, do not perform synchronous setState (or other state-updating calls) directly inside useEffect bodies."
Also applies to: 40-47
🤖 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 `@app/src/components/intelligence/TriadClosureTab.tsx` around lines 24 - 38,
The load function currently always calls setLoading(true) and setError(null)
synchronously, which causes an unwanted mount-time state reset; modify load (the
useCallback named load that uses latestRequestId.current, setLoading, setError,
setResult) to accept an optional flag (e.g., initialLoad or skipReset) and when
that flag is true skip the initial synchronous setLoading(true)/setError(null)
calls and also avoid toggling loading in finally for that invocation; update the
useEffect mount call to invoke load('', true) (or equivalent) so the initial
mount reuses existing defaults while all other calls keep the existing behaviour
and requestId cancellation checks.
| export interface TriadClosureOptions { | ||
| minSupport?: number; // default 2 | ||
| limit?: number; // default 500 (pass 0 for unlimited; negative is clamped to 0) | ||
| } |
There was a problem hiding this comment.
Make negative limit handling match the public contract.
TriadClosureOptions documents negative limits as clamped, but rawLimit <= 0 currently turns -1 into an unlimited result set. That makes a bad caller value expand the payload instead of failing closed or returning an empty slice.
♻️ One way to normalize negatives separately from the `0 = unlimited` sentinel
- const rawLimit = options?.limit ?? DEFAULT_LIMIT;
- const limit = rawLimit <= 0 ? Number.POSITIVE_INFINITY : Math.floor(rawLimit);
+ const rawLimit = Math.floor(options?.limit ?? DEFAULT_LIMIT);
+ const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : Math.max(0, rawLimit);Also applies to: 103-105
🤖 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 `@app/src/lib/memory/triadClosure.ts` around lines 76 - 79, The limit handling
in TriadClosureOptions is inconsistent: negative limits are documented as
clamped to 0 but the code uses rawLimit <= 0 to treat negatives as "unlimited".
In the TriadClosure logic (look for rawLimit usage where you normalize options
and any code near the previous change around lines 103-105), change the
normalization so that rawLimit === 0 is the only sentinel for "unlimited", and
if rawLimit < 0 clamp it to 0 (or otherwise return an empty slice/fail closed)
before using it; replace checks like rawLimit <= 0 with a two-way branch: if
rawLimit === 0 => unlimited, else if rawLimit < 0 => set limit = 0, else limit =
rawLimit.
| const log = debug('triad-closure:api'); | ||
|
|
||
| /** Fetch graph relations for a namespace (or all) and compute closure hints. */ | ||
| export async function loadTriadClosure(namespace?: string): Promise<TriadClosureResult> { | ||
| const relations = await memoryGraphQuery(namespace); | ||
| log('loadTriadClosure namespace=%s relations=%d', namespace ?? '(all)', relations.length); |
There was a problem hiding this comment.
Redact namespace before logging.
namespace values here can carry user identifiers, so logging them verbatim leaks PII into debug output. Please switch to a stable bracketed prefix and log only a redacted scope/correlation field instead.
🛡️ Safer logging shape
-const log = debug('triad-closure:api');
+const log = debug('[rpc] triadClosureApi');
...
- log('loadTriadClosure namespace=%s relations=%d', namespace ?? '(all)', relations.length);
+ log(
+ 'method=loadTriadClosure namespaceScope=%s relations=%d',
+ namespace ? 'single' : 'all',
+ relations.length
+ );As per coding guidelines **/*.{rs,ts,tsx}: Use stable grep-friendly log prefixes ([domain], [rpc], [ui-flow]) and correlation fields (request IDs, method names, entity IDs). Never log secrets or full PII in debug logs; redact sensitive information.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const log = debug('triad-closure:api'); | |
| /** Fetch graph relations for a namespace (or all) and compute closure hints. */ | |
| export async function loadTriadClosure(namespace?: string): Promise<TriadClosureResult> { | |
| const relations = await memoryGraphQuery(namespace); | |
| log('loadTriadClosure namespace=%s relations=%d', namespace ?? '(all)', relations.length); | |
| const log = debug('[rpc] triadClosureApi'); | |
| /** Fetch graph relations for a namespace (or all) and compute closure hints. */ | |
| export async function loadTriadClosure(namespace?: string): Promise<TriadClosureResult> { | |
| const relations = await memoryGraphQuery(namespace); | |
| log( | |
| 'method=loadTriadClosure namespaceScope=%s relations=%d', | |
| namespace ? 'single' : 'all', | |
| relations.length | |
| ); |
🤖 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 `@app/src/services/api/triadClosureApi.ts` around lines 14 - 19, The log call
in loadTriadClosure currently prints the raw namespace (PII) — change it to use
a stable bracketed prefix and a redacted correlation field instead: do not log
namespace verbatim; compute a redacted scope/correlation (e.g., a fixed prefix
like "[triad-closure]" plus a masked or hashed namespace ID or an opaque
request/correlation id) and pass that into the log call along with
relations.length; update the log invocation in loadTriadClosure (which calls
memoryGraphQuery) to emit something like a bracketed prefix and the redacted
correlation field instead of namespace to avoid leaking PII.
b032c37 to
a50b4cf
Compare
|
@aashir-athar Strong, consistent work across the intelligence series — each module's logic is clean (e.g. #2967's export serializers are RFC-4180 correct, with malformed-row drops and finite-number guards). One structural issue spanning the whole series, raising it here as the anchor rather than on all ~22 PRs: Every PR in the wave edits the same three shared points:
Because they all append to the same regions, the PRs mutually conflict — only one can merge before the rest go Suggested restructure:
That removes the cross-PR conflicts and lets them merge in any order. Happy to fast-track reviews once they're restructured. |
a50b4cf to
8393441
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 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 `@app/src/lib/i18n/es.ts`:
- Around line 4215-4236: The Spanish locale currently contains English strings
for the new triad-closure keys; replace the English values with proper Spanish
translations for each key added (e.g., 'triadClosure.title',
'triadClosure.intro', 'triadClosure.loading', 'triadClosure.errorPrefix',
'triadClosure.retry', 'triadClosure.empty', 'triadClosure.emptyHint',
'triadClosure.namespaceLabel', 'triadClosure.namespaceAll',
'triadClosure.metricHints', 'triadClosure.metricCandidates',
'triadClosure.metricSupport', 'triadClosure.summaryCaption',
'triadClosure.truncatedBadge', 'triadClosure.truncatedTitle',
'triadClosure.noCandidates', 'triadClosure.allFiltered',
'triadClosure.rankedHeading', 'triadClosure.suggestEdgeTo',
'triadClosure.viaPrefix', 'triadClosure.extraIntermediaries', and
'memory.tab.completion') with accurate, natural Spanish copy (including
accents/punctuation) that preserves meaning and placeholders like {nodes},
{edges}, {count}, and {n}. Ensure translations fit UI context and
pluralization/placeholder usage matches the original intent.
In `@app/src/lib/i18n/fr.ts`:
- Around line 4232-4253: Replace the English strings for the newly added i18n
keys (all keys beginning with 'triadClosure.' shown and 'memory.tab.completion')
with proper French translations, preserving interpolation tokens like {nodes},
{edges}, {count}, and {n} and keeping punctuation/typography (e.g., em dashes,
ellipses) intact; update values for 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries' and 'memory.tab.completion' with accurate,
idiomatic French strings. Ensure translations match the original meaning
(technical terms like "Adamíc–Adar score" may be left or explained in French)
and run a quick lint/format check after editing.
In `@app/src/lib/i18n/id.ts`:
- Around line 4155-4176: The new triadClosure.* keys are still in English;
replace each English string for 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix', and
'triadClosure.extraIntermediaries' with correct Indonesian translations
(preserve interpolation placeholders like {nodes}, {edges}, {count}, {n} and any
punctuation or technical terms such as "Adamic–Adar" if you decide to keep
them), so the id.ts locale file is a real Indonesian localization rather than
English literals.
In `@app/src/lib/i18n/it.ts`:
- Around line 4210-4231: The Italian locale contains untranslated English
strings for the triad-closure keys and memory.tab.completion; update the values
for the following keys in it.ts: 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries' and 'memory.tab.completion' with proper
Italian translations; preserve placeholders like {nodes}, {edges}, {count}, {n}
and punctuation/typographic characters (e.g., em dashes, ellipses), and ensure
translations are idiomatic and grammatically correct for UI display.
In `@app/src/lib/i18n/ko.ts`:
- Around line 4108-4129: The new triadClosure.* i18n entries in ko.ts are still
in English; replace each English string for keys triadClosure.title,
triadClosure.intro, triadClosure.loading, triadClosure.errorPrefix,
triadClosure.retry, triadClosure.empty, triadClosure.emptyHint,
triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries (and memory.tab.completion if applicable) with
correct Korean translations consistent with the meanings in the English en.ts
additions; ensure phrasing/placeholders (e.g., {nodes},{edges},{count},{n}) are
preserved and verify punctuation/ellipsis matches Korean conventions so the
locale displays proper Korean text for the Graph Completion Hints feature.
In `@app/src/lib/i18n/pl.ts`:
- Around line 4211-4232: Replace the English placeholder values in pl.ts for all
newly added i18n keys (e.g. 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries', and 'memory.tab.completion') with correct
Polish translations; ensure the translations preserve placeholders like {nodes},
{edges}, {count}, and {n} and maintain punctuation and capitalization consistent
with other Polish locale entries, then run a quick lint/i18n parity check to
confirm no English strings remain.
In `@app/src/lib/i18n/pt.ts`:
- Around line 4210-4231: The new i18n entries (triadClosure.title,
triadClosure.intro, triadClosure.loading, triadClosure.errorPrefix,
triadClosure.retry, triadClosure.empty, triadClosure.emptyHint,
triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries, and memory.tab.completion) are left in
English; replace each English string in app/src/lib/i18n/pt.ts with accurate
Portuguese translations (not English) matching the meaning and tone of the
original UI text, preserving placeholders like {nodes}, {edges}, {count}, and
{n} exactly and keeping punctuation/ellipsis where appropriate so the keys
(e.g., triadClosure.intro, triadClosure.summaryCaption) render correctly for
Portuguese users.
In `@app/src/lib/i18n/ru.ts`:
- Around line 4179-4200: The listed i18n entries (triadClosure.title,
triadClosure.intro, triadClosure.loading, triadClosure.errorPrefix,
triadClosure.retry, triadClosure.empty, triadClosure.emptyHint,
triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries and memory.tab.completion) are still in
English; replace each string value with a correct Russian translation
(preserving placeholders like {nodes}, {edges}, {count}, {n} and punctuation) so
Russian locale users see localized UI text. Ensure translations are natural,
concise, and respect existing formatting and placeholders when updating the
corresponding keys (e.g., triadClosure.intro, triadClosure.summaryCaption,
triadClosure.allFiltered).
In `@app/src/lib/i18n/zh-CN.ts`:
- Around line 3941-3962: The triadClosure i18n entries in zh-CN.ts currently
contain English placeholders; update all triadClosure.* keys (e.g.,
'triadClosure.title', 'triadClosure.intro', 'triadClosure.loading',
'triadClosure.errorPrefix', 'triadClosure.retry', 'triadClosure.empty',
'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries', plus 'memory.tab.completion') with proper
Simplified Chinese translations (replace English text with Chinese), preserving
any placeholders like {nodes}, {edges}, {count}, {n} and punctuation/ellipsis
semantics; ensure translations are accurate, concise, and idiomatic for zh-CN
users.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 017bb3c3-3273-42e8-967a-9fc65d5525c2
📒 Files selected for processing (23)
app/src/components/intelligence/TriadClosurePanel.test.tsxapp/src/components/intelligence/TriadClosurePanel.tsxapp/src/components/intelligence/TriadClosureTab.test.tsxapp/src/components/intelligence/TriadClosureTab.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/lib/memory/triadClosure.test.tsapp/src/lib/memory/triadClosure.tsapp/src/pages/Intelligence.tsxapp/src/services/api/triadClosureApi.test.tsapp/src/services/api/triadClosureApi.ts
✅ Files skipped from review due to trivial changes (5)
- app/src/lib/i18n/ar.ts
- app/src/lib/i18n/hi.ts
- app/src/lib/i18n/bn.ts
- app/src/lib/i18n/de.ts
- app/src/lib/i18n/en.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- app/src/services/api/triadClosureApi.test.ts
- app/src/services/api/triadClosureApi.ts
- app/src/pages/Intelligence.tsx
- app/src/components/intelligence/TriadClosureTab.tsx
- app/src/components/intelligence/TriadClosureTab.test.tsx
- app/src/lib/memory/triadClosure.ts
- app/src/lib/memory/triadClosure.test.ts
- app/src/components/intelligence/TriadClosurePanel.tsx
- app/src/components/intelligence/TriadClosurePanel.test.tsx
| 'triadClosure.title': 'Graph Completion Hints', | ||
| 'triadClosure.intro': "Every other lens measures relations that ALREADY exist. This one surfaces what's MISSING: for every ordered pair (A, C) with no direct edge but several shared intermediaries A→B→C, propose creating A→C. Hints are ranked by the Adamic–Adar score — low-degree intermediaries weigh more, because a B that only knows A and C is much stronger evidence than a mega-hub that knows everyone.", | ||
| 'triadClosure.loading': 'Computing closure hints…', | ||
| 'triadClosure.errorPrefix': 'Could not load the graph:', | ||
| 'triadClosure.retry': 'Retry', | ||
| 'triadClosure.empty': 'No knowledge graph yet.', | ||
| 'triadClosure.emptyHint': 'As the assistant records relations about you, suggested closing edges will surface here.', | ||
| 'triadClosure.namespaceLabel': 'Namespace', | ||
| 'triadClosure.namespaceAll': 'All namespaces', | ||
| 'triadClosure.metricHints': 'Suggested edges', | ||
| 'triadClosure.metricCandidates': 'Candidate pairs', | ||
| 'triadClosure.metricSupport': 'Minimum support', | ||
| 'triadClosure.summaryCaption': '{nodes} entities · {edges} directed edges', | ||
| 'triadClosure.truncatedBadge': 'truncated', | ||
| 'triadClosure.truncatedTitle': 'A hub-heavy source node hit the per-source wedge cap — some hints may be missing for that source.', | ||
| 'triadClosure.noCandidates': 'No open triads — the graph has no wedges to close.', | ||
| 'triadClosure.allFiltered': '{count} candidate pairs filtered out by support floor — every closing wedge was single-intermediary. Lower minSupport to see them.', | ||
| 'triadClosure.rankedHeading': 'Suggested edges to consider', | ||
| 'triadClosure.suggestEdgeTo': 'suggest edge to', | ||
| 'triadClosure.viaPrefix': 'via', | ||
| 'triadClosure.extraIntermediaries': '+{n} more', | ||
| 'memory.tab.completion': 'Completion', |
There was a problem hiding this comment.
Italian locale includes English strings for new triad-closure keys
Lines 4210-4231 are user-visible Italian locale entries but remain in English (including memory.tab.completion). Please replace them with proper Italian translations to meet locale parity and avoid mixed-language UI.
🌐 Suggested translation patch
- 'triadClosure.title': 'Graph Completion Hints',
+ 'triadClosure.title': 'Suggerimenti di completamento del grafo',
- 'triadClosure.intro': "Every other lens measures relations that ALREADY exist. This one surfaces what's MISSING: for every ordered pair (A, C) with no direct edge but several shared intermediaries A→B→C, propose creating A→C. Hints are ranked by the Adamic–Adar score — low-degree intermediaries weigh more, because a B that only knows A and C is much stronger evidence than a mega-hub that knows everyone.",
+ 'triadClosure.intro': "Le altre viste misurano relazioni che ESISTONO GIÀ. Questa evidenzia ciò che MANCA: per ogni coppia ordinata (A, C) senza arco diretto ma con intermediari condivisi A→B→C, propone la creazione di A→C. I suggerimenti sono ordinati con il punteggio Adamic–Adar — gli intermediari con basso grado pesano di più, perché un B che conosce solo A e C è un indizio molto più forte di un mega-hub collegato a tutti.",
- 'triadClosure.loading': 'Computing closure hints…',
+ 'triadClosure.loading': 'Calcolo dei suggerimenti di chiusura…',
- 'triadClosure.errorPrefix': 'Could not load the graph:',
+ 'triadClosure.errorPrefix': 'Impossibile caricare il grafo:',
- 'triadClosure.retry': 'Retry',
+ 'triadClosure.retry': 'Riprova',
- 'triadClosure.empty': 'No knowledge graph yet.',
+ 'triadClosure.empty': 'Nessun grafo della conoscenza al momento.',
- 'triadClosure.emptyHint': 'As the assistant records relations about you, suggested closing edges will surface here.',
+ 'triadClosure.emptyHint': 'Man mano che l’assistente registra relazioni su di te, qui compariranno i possibili archi di chiusura.',
- 'triadClosure.namespaceLabel': 'Namespace',
+ 'triadClosure.namespaceLabel': 'Spazio dei nomi',
- 'triadClosure.namespaceAll': 'All namespaces',
+ 'triadClosure.namespaceAll': 'Tutti gli spazi dei nomi',
- 'triadClosure.metricHints': 'Suggested edges',
+ 'triadClosure.metricHints': 'Archi suggeriti',
- 'triadClosure.metricCandidates': 'Candidate pairs',
+ 'triadClosure.metricCandidates': 'Coppie candidate',
- 'triadClosure.metricSupport': 'Minimum support',
+ 'triadClosure.metricSupport': 'Supporto minimo',
- 'triadClosure.summaryCaption': '{nodes} entities · {edges} directed edges',
+ 'triadClosure.summaryCaption': '{nodes} entità · {edges} archi diretti',
- 'triadClosure.truncatedBadge': 'truncated',
+ 'triadClosure.truncatedBadge': 'troncato',
- 'triadClosure.truncatedTitle': 'A hub-heavy source node hit the per-source wedge cap — some hints may be missing for that source.',
+ 'triadClosure.truncatedTitle': 'Un nodo sorgente con molti hub ha raggiunto il limite di wedge per sorgente: alcuni suggerimenti potrebbero mancare per quella sorgente.',
- 'triadClosure.noCandidates': 'No open triads — the graph has no wedges to close.',
+ 'triadClosure.noCandidates': 'Nessuna triade aperta: il grafo non ha wedge da chiudere.',
- 'triadClosure.allFiltered': '{count} candidate pairs filtered out by support floor — every closing wedge was single-intermediary. Lower minSupport to see them.',
+ 'triadClosure.allFiltered': '{count} coppie candidate filtrate dalla soglia minima di supporto: ogni wedge di chiusura aveva un solo intermediario. Riduci minSupport per visualizzarle.',
- 'triadClosure.rankedHeading': 'Suggested edges to consider',
+ 'triadClosure.rankedHeading': 'Archi suggeriti da valutare',
- 'triadClosure.suggestEdgeTo': 'suggest edge to',
+ 'triadClosure.suggestEdgeTo': 'suggerisci arco verso',
- 'triadClosure.viaPrefix': 'via',
+ 'triadClosure.viaPrefix': 'tramite',
- 'triadClosure.extraIntermediaries': '+{n} more',
+ 'triadClosure.extraIntermediaries': '+{n} altri',
- 'memory.tab.completion': 'Completion',
+ 'memory.tab.completion': 'Completamento',As per coding guidelines: “When adding or changing i18n keys in app/src/lib/i18n/en.ts, add the same key with a real, correct translation (not English) to every non-English locale file…”.
🤖 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 `@app/src/lib/i18n/it.ts` around lines 4210 - 4231, The Italian locale contains
untranslated English strings for the triad-closure keys and
memory.tab.completion; update the values for the following keys in it.ts:
'triadClosure.title', 'triadClosure.intro', 'triadClosure.loading',
'triadClosure.errorPrefix', 'triadClosure.retry', 'triadClosure.empty',
'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries' and 'memory.tab.completion' with proper
Italian translations; preserve placeholders like {nodes}, {edges}, {count}, {n}
and punctuation/typographic characters (e.g., em dashes, ellipses), and ensure
translations are idiomatic and grammatically correct for UI display.
| 'triadClosure.title': 'Graph Completion Hints', | ||
| 'triadClosure.intro': "Every other lens measures relations that ALREADY exist. This one surfaces what's MISSING: for every ordered pair (A, C) with no direct edge but several shared intermediaries A→B→C, propose creating A→C. Hints are ranked by the Adamic–Adar score — low-degree intermediaries weigh more, because a B that only knows A and C is much stronger evidence than a mega-hub that knows everyone.", | ||
| 'triadClosure.loading': 'Computing closure hints…', | ||
| 'triadClosure.errorPrefix': 'Could not load the graph:', | ||
| 'triadClosure.retry': 'Retry', | ||
| 'triadClosure.empty': 'No knowledge graph yet.', | ||
| 'triadClosure.emptyHint': 'As the assistant records relations about you, suggested closing edges will surface here.', | ||
| 'triadClosure.namespaceLabel': 'Namespace', | ||
| 'triadClosure.namespaceAll': 'All namespaces', | ||
| 'triadClosure.metricHints': 'Suggested edges', | ||
| 'triadClosure.metricCandidates': 'Candidate pairs', | ||
| 'triadClosure.metricSupport': 'Minimum support', | ||
| 'triadClosure.summaryCaption': '{nodes} entities · {edges} directed edges', | ||
| 'triadClosure.truncatedBadge': 'truncated', | ||
| 'triadClosure.truncatedTitle': 'A hub-heavy source node hit the per-source wedge cap — some hints may be missing for that source.', | ||
| 'triadClosure.noCandidates': 'No open triads — the graph has no wedges to close.', | ||
| 'triadClosure.allFiltered': '{count} candidate pairs filtered out by support floor — every closing wedge was single-intermediary. Lower minSupport to see them.', | ||
| 'triadClosure.rankedHeading': 'Suggested edges to consider', | ||
| 'triadClosure.suggestEdgeTo': 'suggest edge to', | ||
| 'triadClosure.viaPrefix': 'via', | ||
| 'triadClosure.extraIntermediaries': '+{n} more', | ||
| 'memory.tab.completion': 'Completion', |
There was a problem hiding this comment.
Translate new triadClosure.* and memory.tab.completion strings into Russian.
These newly added Russian-locale entries are still English, so Russian users will see untranslated UI text in the Completion tab.
💡 Suggested direction
- 'triadClosure.title': 'Graph Completion Hints',
+ 'triadClosure.title': 'Подсказки дополнения графа',
...
- 'triadClosure.retry': 'Retry',
+ 'triadClosure.retry': 'Повторить',
...
- 'memory.tab.completion': 'Completion',
+ 'memory.tab.completion': 'Дополнение',Please apply this to all added triadClosure.* keys in this hunk.
As per coding guidelines, "When adding or changing i18n keys in app/src/lib/i18n/en.ts, add the same key with a real, correct translation (not English) to every non-English locale file."
🤖 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 `@app/src/lib/i18n/ru.ts` around lines 4179 - 4200, The listed i18n entries
(triadClosure.title, triadClosure.intro, triadClosure.loading,
triadClosure.errorPrefix, triadClosure.retry, triadClosure.empty,
triadClosure.emptyHint, triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries and memory.tab.completion) are still in
English; replace each string value with a correct Russian translation
(preserving placeholders like {nodes}, {edges}, {count}, {n} and punctuation) so
Russian locale users see localized UI text. Ensure translations are natural,
concise, and respect existing formatting and placeholders when updating the
corresponding keys (e.g., triadClosure.intro, triadClosure.summaryCaption,
triadClosure.allFiltered).
8393441 to
7d22ca3
Compare
…sure) A new read-only "Completion" tab. Every other lens measures relations that ALREADY exist; this is the first to surface what's MISSING. For every ordered (A, C) pair with no direct edge but several shared intermediaries A→B→C, propose creating A→C — ranked by the Adamic–Adar score: score(A, C) = Σ_B 1 / log(1 + deg(B)) Low-degree intermediaries weigh more, formalising the intuition that a B who only knows A and C is much stronger evidence than a mega-hub. Picked by the loop-22 judge-panel design workflow (4 fresh angles × 3 judges + synthesis): weighted score 7.87, beating Knowledge Cadence (7.78), Attention Quadrants, and Chunk Corroboration Atlas. Engine (pure, deterministic — no React/RPC/clock/RNG): - Predicate-AGNOSTIC: a direct A→C edge under ANY predicate suppresses the hint (cleanest "no link exists" semantics). - Self-loops dropped; parallel edges collapsed. - Per-pair score is summed over intermediaries in canonical sorted order so the float is byte-identical across input permutations. - Pair key uses JSON.stringify([s, o]) (no NUL separator) so the control-char scan stays at zero. - Default minSupport=2, limit=500, per-source wedge ceiling 200k with `truncated:true` flag surfaced in the UI. - candidatePairCount exposes the pre-filter count so users understand when minSupport ate every wedge. Adds ZERO new core surface: composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/ presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales; aria-hidden on the decorative → glyph + sr-only "suggest edge to" for screen readers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7d22ca3 to
578cedf
Compare
Summary
Adds a new read-only "Completion" tab — the first lens in the suite to surface what's MISSING from the graph instead of measuring what's present. For every ordered pair
(A, C)with no direct edge but several shared intermediariesA→B→C, propose creatingA→C, ranked by the Adamic–Adar scoreΣ_B 1 / log(1 + deg(B)). Low-degree intermediaries weigh more — formalising the intuition that a B who only knows A and C is much stronger structural evidence than a mega-hub who knows everyone.Provenance & design discipline
Picked by the loop-22 judge-panel design workflow (4 fresh angles — cadence / chunks / inference / quadrants — × 3 independent judges + synthesis). Weighted score 7.87 / 10, beating Knowledge Cadence (7.78), Attention Quadrants (lowest user-value), and Chunk Corroboration Atlas (overlaps too much with #19 Document Provenance). All 5 synthesis-stage refinements applied:
JSON.stringify([A, C])pair key (not NUL separator — dodges the control-char scan flag from MEMORY.md).limit: 500to cap output payload.MAX_WEDGES_PER_A = 200_000withtruncated:trueflag.1 + deg(B)shift (vs textbookdeg(B)) — keeps log finite & positive at the boundary.Engine design (pure, deterministic)
score DESC, support DESC, subject ASC, object ASC— a total order.candidatePairCountexposes the pre-filter count so the UI can explain "1 candidate filtered by support floor" instead of just showing an empty list.Zero new core surface
Composes the already-shipped
memoryGraphQuery/memoryListNamespaceswrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales;aria-hiddenon the decorative→glyph paired with ansr-only"suggest edge to" so screen readers announce the relationship semantically.Test plan
vitest— 27 tests (engine: empty/EMPTY_RESULT shape, default-filtered single-wedge, minSupport=1 exposes it, two-intermediary Adamic-Adar exact, hub-degree vs low-degree weighting, direct-edge suppression (any predicate), reverse-direction does NOT suppress, self-loop drop, parallel-edge collapse, malformed-row drop, no case-folding, byte-identical across permutations, total-order sort with tied scores, limit cap — plus api facade, panel metric tiles + worklist + intermediary chips + no-candidates / all-filtered captions, container load/namespace-requery/error)tsc --noEmit— cleaneslint— 0 errors (1 accepted load-on-mount warning)prettier --check— clean🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes