Skip to content

Commit ba17fb2

Browse files
heqikaicursoragent
andcommitted
feat(wiki): graph-grounded docs, Ask, and desktop v0.2.0
Add Wiki generation and citation-backed Q&A from structural snapshots, dev UI proxy on :3847, performance hardening (snapshot singleflight, parallel compare/trace), and bundle wiki-engine into the desktop runtime. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 17cd649 commit ba17fb2

45 files changed

Lines changed: 6852 additions & 728 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
**Local-first, commit-aware structural code intelligence** — built on [CodeGraph](https://github.com/colbymchenry/codegraph).
77

8-
CodeDelta shows how a codebase’s **structure** changes between commits: symbols, dependency edges, blast radius, and review-oriented summaries. **Trace View** helps narrow down which commit may have introduced a behavior change, with evidence you can verify in **Delta View**. **Panorama** visualizes call-flow trees at a single commit (or a structural diff overlay between two commits).
8+
CodeDelta shows how a codebase’s **structure** changes between commits: symbols, dependency edges, blast radius, and review-oriented summaries. **Trace View** helps narrow down which commit may have introduced a behavior change, with evidence you can verify in **Delta View**. **Panorama** visualizes call-flow trees at a single commit (or a structural diff overlay between two commits). **Wiki** generates graph-grounded documentation per commit — module pages, Mermaid architecture diagrams derived from real call/import edges, and a citation-backed “Ask this repo” chat.
99

1010
This repository is a fork: the **CodeGraph** engine lives under [`src/`](src/) (CLI + MCP + tree-sitter graph). The **CodeDelta** app lives under [`packages/`](packages/) and [`apps/web/`](apps/web/) (import, timeline, delta, trace, settings UI).
1111

@@ -61,15 +61,25 @@ Describe a bug, behavior change, or question in natural language:
6161

6262
**Without any LLM configured**, Trace still returns candidates, evidence, and impact radius (evidence-first, no invented facts).
6363

64+
### Wiki (graph-grounded docs + Ask)
65+
66+
Generate a per-commit wiki from the structural snapshot — inspired by DeepWiki, but grounded in the deterministic CodeGraph graph instead of text-chunk RAG:
67+
68+
- **TOC planned deterministically** from the graph: overview, architecture, one page per top module, plus routes/components when present
69+
- **Mermaid diagrams serialized from real edges** (module import graph, call flows) — never invented by the model
70+
- **Per-page citations** to symbols with file/line ranges; symbol citations deep-link into **Panorama**
71+
- **Ask this repo** — conversational Q&A over the commit; retrieval is lexical scoring + graph traversal (no embeddings, no vector DB), answers cite the evidence whitelist
72+
- **Works without any LLM**: structural pages, tables, and diagrams are always generated; configure a provider to add narrated prose and richer answers
73+
- Cached under `.codedelta/wiki/` per commit + wiki version; generation runs as a background job with progress
74+
6475
### Commit timeline & import
6576

6677
- Import a public GitHub repo (`owner/repo` or URL) or a **local git path**
67-
- Browse commits; open Delta, Trace, or Panorama from the timeline
78+
- Browse commits; open Delta, Trace, Panorama, or Wiki from the timeline
6879

6980
## What CodeDelta is not
7081

7182
- **Not a generic Git GUI** — no merge UI or branch workflow
72-
- **Not a CodeWiki / doc generator** — no long-form auto-docs for the whole repo
7383
- **Not a line-diff-first tool** — structural delta is the product; text diff supports review
7484
- **Not a replacement for Understand Anything** — no interactive whole-repo onboarding graph or LLM tours
7585

@@ -85,13 +95,14 @@ npm run build:codedelta
8595
npm run dev:codedelta
8696
```
8797

88-
Open [http://localhost:5173](http://localhost:5173).
98+
Open [http://localhost:3847](http://localhost:3847) (dev mode proxies the Vite UI to the API port; [http://localhost:5173](http://localhost:5173) also works).
8999

90100
1. **Import** a repository (GitHub URL or local path)
91101
2. **Commit Timeline** — pick a branch and browse history
92102
3. **Delta View** — choose `Base (before)` and `Head (after)`, then compare
93103
4. **Trace View** — describe an issue; review candidates and open Delta to verify
94104
5. **Panorama** — pick branch/commit and explore call trees; drill down from any entry or route
105+
6. **Wiki** — pick a commit, generate the wiki, browse pages, and ask questions with cited answers
95106

96107
## UI walkthrough
97108

@@ -125,10 +136,16 @@ API: [http://localhost:3847](http://localhost:3847)
125136
| `POST /api/repos/:id/panorama/enrich` | Optional LLM labels for panorama nodes |
126137
| `GET /api/repos/:id/diff?base=&head=&file=` | Unified diff for one file |
127138
| `POST /api/repos/:id/trace` | Trace question → candidates + evidence |
139+
| `POST /api/repos/:id/wiki/generate?commit=` | Start wiki generation (background job) |
140+
| `GET /api/repos/:id/wiki/status?commit=` | Generation state + progress |
141+
| `GET /api/repos/:id/wiki/toc?commit=` | Wiki table of contents |
142+
| `GET /api/repos/:id/wiki/page?commit=&section=` | One wiki page (markdown + citations) |
143+
| `GET /api/repos/:id/wiki/asset?commit=&path=` | README / wiki image at commit (git show) |
144+
| `POST /api/repos/:id/wiki/ask` | Question → cited answer over the commit graph |
128145
| `GET /api/settings/provider` | Current LLM provider settings |
129146
| `GET /api/settings/provider/codex-status` | Local Codex CLI login status |
130147

131-
## Configure Codex for Trace View (optional)
148+
## Configure Codex for Trace View & Wiki (optional)
132149

133150
CodeDelta can reuse your **existing Codex CLI login** — no API key pasted into the web UI.
134151

@@ -153,7 +170,7 @@ This creates or updates `~/.codex/auth.json` (ChatGPT OAuth). You can override t
153170

154171
Open **Trace View**, enter a concrete question (file paths, symbols, or config names help), and click **Run trace**.
155172

156-
Deterministic results always appear; if Codex is configured, the model may refine the narrative. Model output is **non-authoritative** — evidence and Delta verification are the source of truth.
173+
Deterministic results always appear; if Codex is configured, the model may refine the narrative. Model output is **non-authoritative** — evidence and Delta verification are the source of truth. The same provider also powers **Wiki** page narration and **Ask** answers; without it, both fall back to deterministic structural output.
157174

158175
### Codex troubleshooting
159176

@@ -174,6 +191,7 @@ Deterministic results always appear; if Codex is configured, the model may refin
174191
| `.codedelta/repos/<id>/` | Cloned or referenced repositories |
175192
| `.codedelta/registry.json` | Import registry |
176193
| `.codedelta/snapshots/<repoId>/<hash>/<analyzerVersion>/` | Per-commit structural snapshots |
194+
| `.codedelta/wiki/<repoId>/<hash>/<wikiVersion>/` | Generated wiki (toc, pages, meta) |
177195
| `.codedelta/settings.json` | Provider settings |
178196

179197
Snapshots are built **lazily** on compare/trace — full history is not pre-indexed.
@@ -213,8 +231,9 @@ packages/
213231
codedelta-impact-score/
214232
codedelta-delta-summary/
215233
codedelta-trace-engine/
234+
codedelta-wiki-engine/ # Wiki TOC/pages/Mermaid + Ask retrieval
216235
codedelta-provider-runtime/
217-
apps/web/ # React UI (Delta, Trace, Panorama)
236+
apps/web/ # React UI (Delta, Trace, Panorama, Wiki)
218237
apps/desktop/ # macOS desktop shell (Tauri 2)
219238
```
220239

@@ -233,13 +252,13 @@ Roadmap and deferred work: [docs/codedelta/ROADMAP.md](docs/codedelta/ROADMAP.md
233252

234253
CodeDelta ships **desktop apps** ([`apps/desktop/`](apps/desktop/)) — Tauri 2 shells that bundle Node 22 (for CodeGraph’s `node:sqlite`) and the API server. End users do not need a separate Node install.
235254

236-
**Version** is read from `apps/desktop/src-tauri/tauri.conf.json` (currently `0.1.0`). macOS and Windows installers publish to the same GitHub Release: `codedelta-desktop-v0.1.0`.
255+
**Version** is read from `apps/desktop/src-tauri/tauri.conf.json` (currently `0.2.0`). macOS and Windows installers publish to the same GitHub Release: `codedelta-desktop-v0.2.0`.
237256

238257
### Download
239258

240259
| Platform | File | Notes |
241260
|----------|------|-------|
242-
| **macOS** (Apple Silicon) | [GitHub Releases](https://github.com/ingeniousfrog/CodeDelta/releases/tag/codedelta-desktop-v0.1.0)`CodeDelta_*_aarch64.dmg` | Unsigned; right-click → Open if blocked |
261+
| **macOS** (Apple Silicon) | [GitHub Releases](https://github.com/ingeniousfrog/CodeDelta/releases/tag/codedelta-desktop-v0.2.0)`CodeDelta_*_aarch64.dmg` | Unsigned; right-click → Open if blocked |
243262
| **Windows** (x64) | Same release → `CodeDelta_*_x64-setup.exe` | NSIS installer |
244263
| macOS mirror | [百度网盘](https://pan.baidu.com/s/1FQxOgNHyvU1Y5EB34RpogQ?pwd=frog) · 提取码: `frog` | |
245264

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { execFileSync } from 'child_process';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import request from 'supertest';
6+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7+
import { createApp } from '../../packages/codedelta-server/src';
8+
9+
function run(cmd: string, cwd: string): void {
10+
execFileSync('sh', ['-c', cmd], { cwd, stdio: 'pipe' });
11+
}
12+
13+
async function waitForWikiReady(
14+
app: ReturnType<typeof createApp>['app'],
15+
repoId: string,
16+
commit: string,
17+
timeoutMs = 120_000,
18+
): Promise<Record<string, unknown>> {
19+
const deadline = Date.now() + timeoutMs;
20+
for (;;) {
21+
const res = await request(app).get(`/api/repos/${repoId}/wiki/status?commit=${commit}`);
22+
expect(res.status).toBe(200);
23+
if (res.body.state === 'ready') return res.body;
24+
if (res.body.state === 'error') {
25+
throw new Error(`wiki generation failed: ${res.body.error}`);
26+
}
27+
if (Date.now() > deadline) throw new Error('timed out waiting for wiki generation');
28+
await new Promise((resolve) => setTimeout(resolve, 200));
29+
}
30+
}
31+
32+
describe('codedelta-server wiki (none provider, deterministic path)', () => {
33+
let tmpDir: string;
34+
let cacheRoot: string;
35+
36+
beforeEach(() => {
37+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codedelta-wiki-'));
38+
cacheRoot = path.join(tmpDir, '.codedelta');
39+
run('git init -b main', tmpDir);
40+
run('git config user.email "test@example.com"', tmpDir);
41+
run('git config user.name "Test User"', tmpDir);
42+
fs.writeFileSync(path.join(tmpDir, 'README.md'), '# wiki demo\n\n![badge](docs/badge.png)\n\nDemo repository for wiki tests.\n');
43+
fs.mkdirSync(path.join(tmpDir, 'docs'), { recursive: true });
44+
// 1x1 PNG
45+
fs.writeFileSync(
46+
path.join(tmpDir, 'docs', 'badge.png'),
47+
Buffer.from(
48+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==',
49+
'base64',
50+
),
51+
);
52+
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
53+
fs.writeFileSync(
54+
path.join(tmpDir, 'src', 'auth.ts'),
55+
[
56+
'export function login(user: string): boolean {',
57+
' return validate(user);',
58+
'}',
59+
'',
60+
'export function validate(user: string): boolean {',
61+
' return user.length > 0;',
62+
'}',
63+
'',
64+
].join('\n'),
65+
);
66+
fs.writeFileSync(
67+
path.join(tmpDir, 'src', 'server.ts'),
68+
[
69+
"import { login } from './auth';",
70+
'',
71+
'export function handleRequest(user: string): string {',
72+
" return login(user) ? 'ok' : 'denied';",
73+
'}',
74+
'',
75+
].join('\n'),
76+
);
77+
run('git add . && git commit -m "initial commit"', tmpDir);
78+
});
79+
80+
afterEach(() => {
81+
fs.rmSync(tmpDir, { recursive: true, force: true });
82+
});
83+
84+
it('generates a wiki, serves toc/pages, and answers ask deterministically', async () => {
85+
const { app } = createApp({ cacheRoot });
86+
const importRes = await request(app).post('/api/repos/import').send({ source: 'local', input: tmpDir });
87+
expect(importRes.status).toBe(201);
88+
const repoId = importRes.body.id as string;
89+
const commit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: tmpDir, encoding: 'utf8' }).trim();
90+
91+
// Status before generation.
92+
const absent = await request(app).get(`/api/repos/${repoId}/wiki/status?commit=${commit}`);
93+
expect(absent.status).toBe(200);
94+
expect(absent.body.state).toBe('absent');
95+
96+
// TOC before generation → 404 guidance.
97+
const earlyToc = await request(app).get(`/api/repos/${repoId}/wiki/toc?commit=${commit}`);
98+
expect(earlyToc.status).toBe(404);
99+
100+
// Kick off generation (background job).
101+
const gen = await request(app).post(`/api/repos/${repoId}/wiki/generate?commit=${commit}`);
102+
expect([200, 202]).toContain(gen.status);
103+
104+
const ready = await waitForWikiReady(app, repoId, commit);
105+
expect(ready.llmUsed).toBe(false);
106+
107+
// Re-generate on a ready wiki is a no-op.
108+
const regen = await request(app).post(`/api/repos/${repoId}/wiki/generate?commit=${commit}`);
109+
expect(regen.status).toBe(200);
110+
expect(regen.body.status).toBe('ready');
111+
112+
// TOC: overview + architecture first, then module sections.
113+
const toc = await request(app).get(`/api/repos/${repoId}/wiki/toc?commit=${commit}`);
114+
expect(toc.status).toBe(200);
115+
const sections = toc.body.sections as Array<{ id: string; kind: string }>;
116+
expect(sections[0].id).toBe('overview');
117+
expect(sections[1].id).toBe('architecture');
118+
expect(sections.length).toBeGreaterThanOrEqual(2);
119+
120+
// Overview page: markdown with README excerpt, citations array present.
121+
const overview = await request(app).get(
122+
`/api/repos/${repoId}/wiki/page?commit=${commit}&section=overview`,
123+
);
124+
expect(overview.status).toBe(200);
125+
expect(overview.body.markdown).toContain('# Overview');
126+
expect(overview.body.markdown).toContain('/wiki/asset?');
127+
expect(overview.body.markdown).toContain(encodeURIComponent('docs/badge.png'));
128+
expect(overview.body.markdown).toContain('Demo repository for wiki tests.');
129+
expect(Array.isArray(overview.body.citations)).toBe(true);
130+
131+
const asset = await request(app).get(
132+
`/api/repos/${repoId}/wiki/asset?commit=${commit}&path=${encodeURIComponent('docs/badge.png')}`,
133+
);
134+
expect(asset.status).toBe(200);
135+
expect(asset.headers['content-type']).toMatch(/image\/png/);
136+
expect(asset.body.length).toBeGreaterThan(0);
137+
138+
// Every TOC section has a retrievable page.
139+
for (const section of sections) {
140+
const page = await request(app).get(
141+
`/api/repos/${repoId}/wiki/page?commit=${commit}&section=${section.id}`,
142+
);
143+
expect(page.status).toBe(200);
144+
expect(typeof page.body.markdown).toBe('string');
145+
expect(page.body.markdown.length).toBeGreaterThan(0);
146+
}
147+
148+
// Unknown section → 404.
149+
const missing = await request(app).get(
150+
`/api/repos/${repoId}/wiki/page?commit=${commit}&section=nope`,
151+
);
152+
expect(missing.status).toBe(404);
153+
154+
// Ask without provider: deterministic answer grounded in matched symbols.
155+
const ask = await request(app)
156+
.post(`/api/repos/${repoId}/wiki/ask`)
157+
.send({ commit, question: 'how does login validate the user?' });
158+
expect(ask.status).toBe(200);
159+
expect(ask.body.provider.used).toBe(false);
160+
expect(ask.body.answer).toContain('login');
161+
expect(Array.isArray(ask.body.citations)).toBe(true);
162+
expect(Array.isArray(ask.body.evidence)).toBe(true);
163+
expect(ask.body.evidence.length).toBeGreaterThan(0);
164+
165+
// Ask validation errors.
166+
const noQuestion = await request(app).post(`/api/repos/${repoId}/wiki/ask`).send({ commit });
167+
expect(noQuestion.status).toBe(400);
168+
const noCommit = await request(app)
169+
.post(`/api/repos/${repoId}/wiki/ask`)
170+
.send({ question: 'anything' });
171+
expect(noCommit.status).toBe(400);
172+
}, 180_000);
173+
174+
it('rejects generate without commit and unknown repo', async () => {
175+
const { app } = createApp({ cacheRoot });
176+
const importRes = await request(app).post('/api/repos/import').send({ source: 'local', input: tmpDir });
177+
const repoId = importRes.body.id as string;
178+
179+
const noCommit = await request(app).post(`/api/repos/${repoId}/wiki/generate`);
180+
expect(noCommit.status).toBe(400);
181+
182+
const badRepo = await request(app).post(`/api/repos/does-not-exist/wiki/generate?commit=abc`);
183+
expect(badRepo.status).toBe(404);
184+
});
185+
});

apps/desktop/src-tauri/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ pub fn run() {
1212
}
1313
}))
1414
.setup(|app| {
15-
if let Some(hint) = server::port_in_use_hint() {
16-
eprintln!("{hint}");
17-
}
1815
if let Err(err) = server::start(app.handle()) {
16+
if let Some(hint) = server::port_in_use_hint() {
17+
eprintln!("{hint}");
18+
}
1919
eprintln!("CodeDelta startup failed: {err}");
2020
return Err(err.into());
2121
}

apps/desktop/src-tauri/src/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ pub fn start(app: &AppHandle) -> Result<(), String> {
150150
}
151151

152152
// Production must spawn the bundled server. Do not attach to a dev API on :3847
153-
// (e.g. `npm run dev:desktop`) — it serves API only and shows "Cannot GET /".
153+
// unless it serves the UI (CODEDELTA_STATIC_DIR or CODEDELTA_DEV_UI_URL).
154154
if health_ok() {
155155
let hint = port_in_use_hint().unwrap_or_default();
156156
return Err(format!(

apps/desktop/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "CodeDelta",
4-
"version": "0.1.0",
4+
"version": "0.2.0",
55
"identifier": "com.codedelta.desktop",
66
"build": {
77
"beforeDevCommand": "",

apps/web/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010
},
1111
"dependencies": {
1212
"@xyflow/react": "^12.6.0",
13+
"mermaid": "^11.15.0",
1314
"react": "^19.0.0",
1415
"react-dom": "^19.0.0",
15-
"react-router-dom": "^7.1.1"
16+
"react-markdown": "^10.1.0",
17+
"react-router-dom": "^7.1.1",
18+
"rehype-raw": "^7.0.0",
19+
"rehype-sanitize": "^6.0.0",
20+
"remark-gfm": "^4.0.1"
1621
},
1722
"devDependencies": {
1823
"@types/react": "^19.0.2",

0 commit comments

Comments
 (0)