Skip to content

Commit 28944ee

Browse files
committed
fix: restore Cognee dashboard recall and packaged launch
1 parent b3f9f47 commit 28944ee

11 files changed

Lines changed: 607 additions & 66 deletions

electron/main.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ if (smokeDebugPort) {
2323

2424
// Opt-in sandbox bypass for Linux kernels that reject the bundled chrome-sandbox
2525
// helper (some Arch/Manjaro and SELinux-enforcing systems). Default off.
26-
if (process.platform === "linux" && app.isPackaged && process.env.OVERCODE_NO_SANDBOX === "1") {
26+
const useLinuxPackagedNoSandbox =
27+
process.platform === "linux" && app.isPackaged && process.env.OVERCODE_NO_SANDBOX === "1";
28+
29+
if (useLinuxPackagedNoSandbox) {
2730
app.commandLine.appendSwitch("no-sandbox");
2831
}
2932

@@ -68,7 +71,7 @@ function createWindow() {
6871
preload: path.join(__dirname, "preload.js"),
6972
contextIsolation: true,
7073
nodeIntegration: false,
71-
sandbox: true,
74+
sandbox: !useLinuxPackagedNoSandbox,
7275
},
7376
});
7477

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2+
import { X } from "@phosphor-icons/react";
3+
import type { RecalledCogneeWorkflowMemory } from "../lib/cognee-workflow-runtime";
4+
import {
5+
extractCogneeMemoryHighlights,
6+
extractCogneeMemoryReferences,
7+
} from "../lib/cognee-workflow-memory";
8+
import { AIProviderLogo } from "./AIProviderLogo";
9+
import "./MemoryRecallModal.css";
10+
11+
interface Props {
12+
repoName: string;
13+
memory: RecalledCogneeWorkflowMemory;
14+
onClose: () => void;
15+
}
16+
17+
const EXIT_MS = 140;
18+
const MAX_MEMORY_SUMMARY_CHARS = 700;
19+
20+
export function RepoMemoryRecallModal({ repoName, memory, onClose }: Props) {
21+
const closeRef = useRef<HTMLButtonElement | null>(null);
22+
const [closing, setClosing] = useState(false);
23+
24+
const requestClose = useCallback(() => {
25+
setClosing(true);
26+
window.setTimeout(onClose, EXIT_MS);
27+
}, [onClose]);
28+
29+
useEffect(() => {
30+
closeRef.current?.focus();
31+
function onKey(event: KeyboardEvent) {
32+
if (event.key === "Escape") requestClose();
33+
}
34+
window.addEventListener("keydown", onKey);
35+
return () => window.removeEventListener("keydown", onKey);
36+
}, [requestClose]);
37+
38+
const memories = useMemo(
39+
() =>
40+
memory.items.map((item) => {
41+
const highlights = extractCogneeMemoryHighlights(item.summary);
42+
return {
43+
id: item.id,
44+
title: cleanText(item.title) || "Recalled memory",
45+
summary: boundText(cleanText(highlights[0] ?? item.summary), MAX_MEMORY_SUMMARY_CHARS),
46+
references: extractCogneeMemoryReferences(item).slice(0, 6),
47+
};
48+
}),
49+
[memory.items],
50+
);
51+
const highlights = useMemo(() => extractCogneeMemoryHighlights(memory.context), [memory.context]);
52+
const strongestSignal = cleanText(highlights[0] ?? memories[0]?.summary ?? memory.summary);
53+
const closingClass = closing ? " is-closing" : "";
54+
55+
return (
56+
<>
57+
<div
58+
className={`memory-modal-backdrop${closingClass}`}
59+
onClick={requestClose}
60+
aria-hidden="true"
61+
/>
62+
<div
63+
className={`memory-modal${closingClass}`}
64+
role="dialog"
65+
aria-modal="true"
66+
aria-label={`Cognee memory for ${repoName}`}
67+
>
68+
<header className="memory-modal-header">
69+
<AIProviderLogo providerId="cognee" size="md" decorative />
70+
<div className="memory-modal-titleblock">
71+
<span className="memory-modal-title">Cognee repo memory</span>
72+
<small>
73+
{memory.itemCount} memor{memory.itemCount === 1 ? "y" : "ies"} recalled for {repoName}
74+
</small>
75+
</div>
76+
<button
77+
ref={closeRef}
78+
type="button"
79+
className="memory-modal-close"
80+
onClick={requestClose}
81+
aria-label="Close memory view"
82+
title="Close"
83+
>
84+
<X size={14} weight="bold" />
85+
</button>
86+
</header>
87+
88+
<div className="memory-modal-body">
89+
<section className="memory-modal-hero" aria-label="Repository memory summary">
90+
<div>
91+
<span className="memory-modal-kicker">Repo signal</span>
92+
<p>{strongestSignal || "Cognee returned matching memory for this repository."}</p>
93+
</div>
94+
<div className="memory-modal-coverage">
95+
<span>{memory.itemCount}</span>
96+
<small>repo memories</small>
97+
</div>
98+
</section>
99+
100+
<section className="memory-modal-panel" aria-label="Recalled memory">
101+
<div className="memory-modal-section-head">
102+
<span className="memory-modal-kicker">Recalled memory</span>
103+
<small>repo-scoped</small>
104+
</div>
105+
<ul className="memory-modal-memory-list">
106+
{memories.map((item) => (
107+
<li key={item.id}>
108+
<div className="memory-modal-memory-head">
109+
<span>{item.title}</span>
110+
<small>{repoName}</small>
111+
</div>
112+
<p>{item.summary}</p>
113+
{item.references.length > 0 && (
114+
<div className="memory-modal-reference-row">
115+
{item.references.map((reference) => (
116+
<code key={reference}>{reference}</code>
117+
))}
118+
</div>
119+
)}
120+
</li>
121+
))}
122+
</ul>
123+
</section>
124+
125+
{memory.references.length > 0 && (
126+
<section className="memory-modal-panel" aria-label="All references">
127+
<span className="memory-modal-kicker">References</span>
128+
<div className="memory-modal-reference-row">
129+
{memory.references.map((reference) => (
130+
<code key={reference}>{reference}</code>
131+
))}
132+
</div>
133+
</section>
134+
)}
135+
</div>
136+
137+
<footer className="memory-modal-footer">
138+
Recalled live from Cognee Cloud. Overcode filters memories by repository before
139+
showing this view, so stronger memories from other workspaces do not leak into
140+
this repo context.
141+
</footer>
142+
</div>
143+
</>
144+
);
145+
}
146+
147+
function cleanText(value: string): string {
148+
return value
149+
.replace(/__node_content_(?:start|end)__/g, " ")
150+
.replace(/\bMemory\s+(?:cognee:)?[\w:-]+:\s*/gi, " ")
151+
.replace(/\bNodes?:\s*/gi, " ")
152+
.replace(/\bNode:\s*/gi, " ")
153+
.replace(/\s+/g, " ")
154+
.trim();
155+
}
156+
157+
function boundText(value: string, maxChars: number): string {
158+
if (value.length <= maxChars) return value;
159+
return `${value.slice(0, Math.max(0, maxChars - 3)).trim()}...`;
160+
}

src/lib/ai-features.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,49 @@ describe("repo brief structured generation", () => {
190190
);
191191
});
192192

193+
it("strips marker comments from the local evidence-backed brief purpose", async () => {
194+
const thin = briefEnvelope(
195+
{
196+
purpose: "Provide a concise overview to get developers up to speed quickly.",
197+
keyModules: [],
198+
recentActivity: [],
199+
onboardingPath: ["Clone repo"],
200+
notableRisks: [],
201+
},
202+
["Repository facts were garbled; details may be incomplete."],
203+
);
204+
mockIpc.callAIModel
205+
.mockResolvedValueOnce(JSON.stringify(thin))
206+
.mockResolvedValueOnce(JSON.stringify(thin));
207+
208+
const { getRepoBriefStructured } = await import("./ai-features");
209+
const result = await getRepoBriefStructured({
210+
repoId: "repo-synth",
211+
repoName: "synth-x",
212+
branch: "master",
213+
tree: [
214+
"contracts/strategy.sol",
215+
"dashboard/src/App.tsx",
216+
"data/routes.json",
217+
"docs/README.md",
218+
"src/index.ts",
219+
"video/demo.mp4",
220+
],
221+
readme: [
222+
"# Murmur <!-- MARKEE:START:0x253e91dcc7bd56e3695348c3bb0bc9febf6f01b5 -->",
223+
"<!-- MARKEE:END -->",
224+
].join("\n"),
225+
recentCommits: ["Add dashboard route", "Update contracts"],
226+
changedFiles: ["contracts/strategy.sol"],
227+
});
228+
229+
expect(result.data.purpose).toContain("Murmur repository");
230+
expect(result.data.purpose).toContain("contracts");
231+
expect(result.data.purpose).not.toContain("MARKEE");
232+
expect(result.data.purpose).not.toContain("<!--");
233+
expect(result.data.purpose).not.toContain("# Murmur");
234+
});
235+
193236
it("keeps provider failure reasons on the local fallback", async () => {
194237
mockIpc.callAIModel.mockRejectedValue(
195238
new Error("Error invoking remote method 'ai:complete': Error: OpenRouter returned 429"),

src/lib/ai-features.ts

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const MAX_TREE_ITEMS = 80;
3737
const MAX_LIST_ITEMS = 80;
3838
const MAX_PROMPT_CHARS = 14_000;
3939
const MAX_MEMORY_CONTEXT_CHARS = 4_000;
40-
const REPO_BRIEF_CACHE_VERSION = "brief:v5";
40+
const REPO_BRIEF_CACHE_VERSION = "brief:v6";
4141

4242
const IMPACT_SYSTEM_PROMPT =
4343
"Analyze this code diff for a developer. Return only valid JSON with this shape: {\"schemaVersion\":1,\"feature\":\"impact\",\"summary\":\"1 short sentence\",\"confidence\":\"low|medium|high\",\"warnings\":[\"...\"],\"data\":{\"intent\":\"...\",\"modules\":[{\"name\":\"...\",\"paths\":[\"...\"],\"changeType\":\"added|modified|removed|mixed\"}],\"risks\":[{\"severity\":\"low|medium|high\",\"area\":\"...\",\"reason\":\"...\",\"files\":[\"...\"]}],\"checks\":[{\"command\":\"optional command\",\"reason\":\"...\"}],\"recommendation\":\"...\"}}. No markdown fences.";
@@ -1234,7 +1234,10 @@ function buildRepoBriefPrompt(payload: BriefPayload): string | null {
12341234
payload.changedFiles?.filter(Boolean) ?? [],
12351235
MAX_LIST_ITEMS,
12361236
);
1237-
const readme = truncateText(payload.readme?.trim() ?? "", MAX_README_CHARS);
1237+
const readme = truncateText(
1238+
sanitizeRepositoryEvidenceText(payload.readme?.trim() ?? ""),
1239+
MAX_README_CHARS,
1240+
);
12381241

12391242
const hasRealData =
12401243
tree.length > 0 ||
@@ -1503,9 +1506,7 @@ function buildLocalRepoBriefData(payload: BriefPayload): RepoBriefData {
15031506
]);
15041507
const readme = summarizeReadme(payload.readme ?? "");
15051508

1506-
const purpose = readme
1507-
? readme
1508-
: "Purpose is not explicit in the available README/package data. Start by inspecting the top-level files and primary source directories.";
1509+
const purpose = buildLocalRepoBriefPurpose(payload, readme, tree);
15091510

15101511
return {
15111512
purpose,
@@ -1532,6 +1533,30 @@ function buildLocalRepoBriefData(payload: BriefPayload): RepoBriefData {
15321533
};
15331534
}
15341535

1536+
function buildLocalRepoBriefPurpose(
1537+
payload: BriefPayload,
1538+
readme: string,
1539+
tree: string[],
1540+
): string {
1541+
if (readme.length >= 40) return readme;
1542+
1543+
const label = cleanRepositoryLabel(readme || payload.repoName || payload.repoId);
1544+
const modules = tree
1545+
.map((item) => item.replace(/\/+$/, ""))
1546+
.filter(Boolean)
1547+
.slice(0, 6);
1548+
if (label && modules.length > 0) {
1549+
return `${label} repository. Local evidence includes ${formatNaturalList(modules)}.`;
1550+
}
1551+
if (label) {
1552+
return `${label} repository. Purpose is not explicit in the available README/package data.`;
1553+
}
1554+
if (modules.length > 0) {
1555+
return `Purpose is not explicit in README/package data. Local evidence includes ${formatNaturalList(modules)}.`;
1556+
}
1557+
return "Purpose is not explicit in the available README/package data. Start by inspecting the top-level files and primary source directories.";
1558+
}
1559+
15351560
function buildLocalPRReviewData(detail: PullRequestDetail): PRReviewData {
15361561
const failing = detail.checks.filter((check) => check.conclusion === "failure").length;
15371562
const pending = detail.checks.filter((check) => !check.conclusion || check.status !== "completed").length;
@@ -1947,23 +1972,65 @@ function groupPathsByTopLevel(paths: string[]): Array<[string, string[]]> {
19471972
}
19481973

19491974
function summarizeReadme(value: string): string {
1950-
const lines = value
1975+
const lines = sanitizeRepositoryEvidenceText(value)
19511976
.split("\n")
1952-
.map((line) => line.trim())
1953-
.filter(Boolean)
1954-
.filter((line) => !/^(name|version|description|scripts|dependencies|devDependencies):/i.test(line));
1955-
const heading = lines.find((line) => /^#\s+/.test(line));
1977+
.map((line) => ({
1978+
raw: line.trim(),
1979+
clean: cleanReadmeLine(line),
1980+
}))
1981+
.filter((line) => line.clean)
1982+
.filter(
1983+
(line) =>
1984+
!/^(name|version|description|scripts|dependencies|devDependencies):/i.test(line.clean),
1985+
);
1986+
const heading = lines.find((line) => /^#\s+/.test(line.raw))?.clean;
19561987
const prose = lines.find(
19571988
(line) =>
1958-
!line.startsWith("#") &&
1959-
!line.startsWith("[") &&
1960-
!line.startsWith("!") &&
1961-
!/^<\/?(p|img|picture|source|h1|h2|h3|strong|em|div|span)\b/i.test(line) &&
1962-
!/<img\b/i.test(line) &&
1963-
!/shields\.io|badge/i.test(line) &&
1964-
line.length > 30,
1989+
!line.raw.startsWith("#") &&
1990+
!line.raw.startsWith("[") &&
1991+
!line.raw.startsWith("!") &&
1992+
!/^<\/?(p|img|picture|source|h1|h2|h3|strong|em|div|span)\b/i.test(line.raw) &&
1993+
!/<img\b/i.test(line.raw) &&
1994+
!/shields\.io|badge/i.test(line.raw) &&
1995+
line.clean.length > 30,
19651996
);
1966-
return truncateText([heading, prose].filter(Boolean).join(" "), 700);
1997+
return truncateText([heading, prose?.clean].filter(Boolean).join(" "), 700);
1998+
}
1999+
2000+
function sanitizeRepositoryEvidenceText(value: string): string {
2001+
return value
2002+
.replace(/<!--[\s\S]*?-->/g, " ")
2003+
.replace(/\bMARKEE:(?:START|END):[A-Za-z0-9:_-]+\b/gi, " ")
2004+
.split("\n")
2005+
.map((line) => line.replace(/\s+/g, " ").trim())
2006+
.filter(Boolean)
2007+
.join("\n");
2008+
}
2009+
2010+
function cleanReadmeLine(value: string): string {
2011+
return value
2012+
.replace(/^#{1,6}\s+/, "")
2013+
.replace(/^[-*]\s+/, "")
2014+
.replace(/!\[[^\]]*]\([^)]*\)/g, " ")
2015+
.replace(/\[([^\]]+)]\([^)]*\)/g, "$1")
2016+
.replace(/`([^`]+)`/g, "$1")
2017+
.replace(/\s+/g, " ")
2018+
.trim();
2019+
}
2020+
2021+
function cleanRepositoryLabel(value: string | undefined): string {
2022+
return (value ?? "")
2023+
.replace(/^#{1,6}\s+/, "")
2024+
.replace(/<!--[\s\S]*?-->/g, " ")
2025+
.replace(/\bMARKEE:(?:START|END):[A-Za-z0-9:_-]+\b/gi, " ")
2026+
.replace(/\s+/g, " ")
2027+
.trim();
2028+
}
2029+
2030+
function formatNaturalList(items: string[]): string {
2031+
if (items.length <= 1) return items[0] ?? "";
2032+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
2033+
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
19672034
}
19682035

19692036
function formatList(items: string[], emptyText: string): string {

0 commit comments

Comments
 (0)