Skip to content

AI-assisted knowledge graph auto-linking#291

Open
d-oit wants to merge 4 commits into
mainfrom
feat/ai-graph-linking-9896207610745298301
Open

AI-assisted knowledge graph auto-linking#291
d-oit wants to merge 4 commits into
mainfrom
feat/ai-graph-linking-9896207610745298301

Conversation

@d-oit

@d-oit d-oit commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Implemented AI-assisted knowledge graph auto-linking. This feature uses LLMs to analyze note content, extract named entities (people, orgs, tech, etc.), and suggest relationships between them. Users can review these suggestions through a dedicated dialog and selectively add them to the knowledge graph.

Key components:

  • src/lib/ai/entity-extractor.ts: LLM prompt and extraction logic.
  • src/lib/ai/graph-linker.ts: Service for applying entities/links to the DB with deduplication and metadata tagging.
  • src/features/ai/EntityReviewDialog.tsx: Review interface with checkboxes for entities and relationships.
  • src/features/editor/Editor.tsx: Integrated "AI Extract" button and auto-trigger logic after saving a note.
  • src/features/graph/GraphControls.tsx: New "Analyze All Notes" batch processing mode.

The implementation ensures that:

  • Entities are deduplicated by name.
  • Relationships are not duplicated if they already exist.
  • New entities store the sourceNoteId in their metadata for traceability.
  • Unit and integration tests cover the core extraction and linking logic.

Fixes #285


PR created automatically by Jules for task 9896207610745298301 started by @d-oit

Implemented LLM-powered entity extraction and relationship discovery from notes.
Added a review UI for users to selectively add extracted entities and edges to the graph.
Integrated extraction into the editor (manual and auto-trigger) and added batch analysis to graph controls.
Includes duplicate checking and backlinks to source notes in metadata.

Co-authored-by: d-oit <6849456+d-oit@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@deepsource-io

deepsource-io Bot commented Jun 8, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 89efe1b...eab8399 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Jun 9, 2026 8:21a.m. Review ↗
Python Jun 9, 2026 8:21a.m. Review ↗
Shell Jun 9, 2026 8:21a.m. Review ↗
SQL Jun 9, 2026 8:21a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@codacy-production

codacy-production Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Not up to standards ⛔

🔴 Issues 12 high · 6 medium

Alerts:
⚠ 18 issues (≤ 0 issues of at least minor severity)

Results:
18 new issues

Category Results
BestPractice 6 high
ErrorProne 6 medium
6 high

View in Codacy

🟢 Metrics 77 complexity · 5 duplication

Metric Results
Complexity 77
Duplication 5

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

};

return (
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && onClose()}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSX tree is too deeply nested. Found 5 levels of nesting


Nesting JSX elements too deeply can confuse developers reading the code. To make maintenance and refactoring easier, DeepSource recommends limiting the maximum JSX tree depth to 4.

</h4>
<div className="entity-list" style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{result.entities.map((entity, idx) => (
<label key={idx} style={{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do not use Array index in keys


When rendering a list of items in React, it is necessary to pass a "key" prop.
This key is used by React to identify which items have changed, are added, or are removed and should be stable.
It is not recommended to use the index of an element as key because it doesn't uniquely identify the element.
When elements are added/removed from an array, the index of an element may change, which will result in unnecessary re-renders.

{result.relationships.map((rel, idx) => {
const key = `${rel.from}->${rel.to}`;
return (
<label key={idx} style={{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do not use Array index in keys


When rendering a list of items in React, it is necessary to pass a "key" prop.
This key is used by React to identify which items have changed, are added, or are removed and should be stable.
It is not recommended to use the index of an element as key because it doesn't uniquely identify the element.
When elements are added/removed from an array, the index of an element may change, which will result in unnecessary re-renders.

const [isExtracting, setIsExtracting] = useState(false);
const [extractionResult, setExtractionResult] = useState<EntityExtractionResult | null>(null);
const [showExtractionReview, setShowExtractionReview] = useState(false);
const [extractionSourceId, setExtractionSourceId] = useState<string | undefined>(undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remove redundant `undefined` from function call


When an argument is omitted from a function call, it will default to undefined. It is therefore redundant to explicitly pass an undefined literal as the last argument.

setSourceUrl(e.target.value);
}, []);

const handleExtractEntities = useCallback(async (entityId?: string, forceContent?: string) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Function has a cyclomatic complexity of 10 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

Comment on lines +7 to +71
export async function applyEntitiesToGraph(
result: EntityExtractionResult,
repository: IRepository,
selectedEntities: string[],
selectedRelationships: string[],
sourceNoteId?: string
): Promise<void> {
const nameToId = new Map<string, string>();

// 1. Process selected entities
for (const entity of result.entities) {
if (!selectedEntities.includes(entity.name)) continue;

let existing = await repository.getEntityByName(entity.name);
if (!existing) {
// Create new entity if it doesn't exist
const created = await repository.createEntity({
name: entity.name,
type: entity.type,
description: entity.description,
metadata: {
sourceNoteId,
aiExtracted: true
}
});
nameToId.set(entity.name, created.id!);
} else {
nameToId.set(entity.name, existing.id!);
}
}

// 2. Fetch existing links once if doing batch to avoid N+1 check?
// For now, simple check for each relationship
const existingLinks = await repository.getAllLinks();

for (const rel of result.relationships) {
const relKey = `${rel.from}->${rel.to}`;
if (!selectedRelationships.includes(relKey)) continue;

// Resolve IDs for the from and to entities
const fromId = nameToId.get(rel.from) || (await repository.getEntityByName(rel.from))?.id;
const toId = nameToId.get(rel.to) || (await repository.getEntityByName(rel.to))?.id;

if (fromId && toId) {
// Check if link already exists to avoid duplicates
const exists = existingLinks.some(l =>
l.source_id === fromId &&
l.target_id === toId &&
l.relation === rel.label
);

if (!exists) {
await repository.createLink({
source_id: fromId,
target_id: toId,
relation: rel.label,
metadata: {
sourceNoteId,
aiExtracted: true
}
});
}
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable


It is considered a best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. Global variables created from a script can produce name collisions with global variables created from another script, which will usually lead to runtime errors or unexpected behavior. It is mostly useful for browser scripts.

/**
* Applies the selected entities and relationships from an AI extraction result to the graph.
*/
export async function applyEntitiesToGraph(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

`applyEntitiesToGraph` has a cyclomatic complexity of 11 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

Comment thread src/lib/ai/graph-linker.ts Outdated
aiExtracted: true
}
});
nameToId.set(entity.name, created.id!);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

});
nameToId.set(entity.name, created.id!);
} else {
nameToId.set(entity.name, existing.id!);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forbidden non-null assertion


Using non-null assertions cancels out the benefits of strict null-checking, and introduces the possibility of runtime errors. Avoid non-null assertions unless absolutely necessary. If you still need to use one, write a skipcq comment to explain why it is safe.

Implemented LLM-powered entity extraction and relationship discovery from notes.
Added a review UI for users to selectively add extracted entities and edges to the graph.
Integrated extraction into the editor (manual and auto-trigger) and added batch analysis to graph controls.
Includes duplicate checking and backlinks to source notes in metadata.

Fixed CI failures:
- Added Sparkles and X icons to lucide-react mock in tests.
- Fixed missing imports and initialized repository in GraphControls.tsx and Editor.tsx.
- Resolved lint errors (unused vars, explicit any, prefer-const).

Co-authored-by: d-oit <6849456+d-oit@users.noreply.github.com>
import { useMediaQuery } from '../../hooks/useMediaQuery';
import { MEDIA_QUERIES } from '../../lib/constants';
import type { GraphSnapshot } from '../../lib/validation';
import { repository, type GraphSnapshotDiff } from '../../db/repository';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

'repository' is defined but never used


Unused variables are generally considered a code smell and should be avoided.

import { applyEntitiesToGraph } from '../graph-linker';
import { IRepository } from '../../../db/repository/types';
import { EntityExtractionResult } from '../entity-extractor';
import { Entity } from '../../../lib/validation';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Useless path segments for "../../../lib/validation", should be "../../validation"


Unnecessarily complex import statements can be simplified. Complex imports usually result in confusing code. This usually happens as a result of refactoring.

it('creates new entities and links if they do not exist', async () => {
const mockRepository: Partial<IRepository> = {
getEntityByName: vi.fn().mockResolvedValue(null),
createEntity: vi.fn().mockImplementation((e: Omit<Entity, 'id' | 'created_at' | 'updated_at'>) => Promise.resolve({ ...e, id: 'new-id-' + e.name, rowid: 1 })),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected string concatenation


In ES2015 (ES6), we can use template literals instead of string concatenation.

Implemented LLM-powered entity extraction and relationship discovery from notes.
Added a review UI for users to selectively add extracted entities and edges to the graph.
Integrated extraction into the editor (manual and auto-trigger) and added batch analysis to graph controls.
Includes duplicate checking and backlinks to source notes in metadata.

Fixed CI failures:
- Added Sparkles and X icons to lucide-react mock in tests.
- Fixed missing imports and initialized repository in GraphControls.tsx and Editor.tsx.
- Fixed GraphControls.test.tsx to use renderWithDb.
- Resolved lint errors (unused vars, explicit any, prefer-const).

Co-authored-by: d-oit <6849456+d-oit@users.noreply.github.com>

it('triggers focus mode toggle', () => {
render(<GraphControls {...defaultProps} hasSelection={true} />);
renderWithDb(<GraphControls {...defaultProps} hasSelection={true} />, { repository });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Value must be omitted for boolean attribute `hasSelection`


When using a boolean attribute in JSX, you can set the attribute value to true or omit the value. This helps to keep consistency in code.

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

Labels

config tests Related to automated/manual tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: AI-assisted knowledge graph auto-linking — entity extraction and edge suggestion from notes

1 participant