diffity 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +11 -0
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/development.md +156 -0
- package/package.json +32 -0
- package/packages/cli/build.js +38 -0
- package/packages/cli/package.json +51 -0
- package/packages/cli/src/agent.ts +187 -0
- package/packages/cli/src/db.ts +58 -0
- package/packages/cli/src/index.ts +196 -0
- package/packages/cli/src/review-routes.ts +150 -0
- package/packages/cli/src/server.ts +370 -0
- package/packages/cli/src/session.ts +48 -0
- package/packages/cli/src/threads.ts +238 -0
- package/packages/cli/tsconfig.json +13 -0
- package/packages/git/package.json +24 -0
- package/packages/git/src/commits.ts +28 -0
- package/packages/git/src/diff.ts +97 -0
- package/packages/git/src/exec.ts +35 -0
- package/packages/git/src/index.ts +5 -0
- package/packages/git/src/repo.ts +63 -0
- package/packages/git/src/status.ts +9 -0
- package/packages/git/src/types.ts +12 -0
- package/packages/git/tsconfig.json +9 -0
- package/packages/parser/package.json +26 -0
- package/packages/parser/src/index.ts +12 -0
- package/packages/parser/src/parse.ts +299 -0
- package/packages/parser/src/types.ts +52 -0
- package/packages/parser/src/word-diff.ts +155 -0
- package/packages/parser/tests/fixtures/binary-deleted.diff +4 -0
- package/packages/parser/tests/fixtures/binary-file.diff +4 -0
- package/packages/parser/tests/fixtures/binary-modified.diff +3 -0
- package/packages/parser/tests/fixtures/copied-file.diff +12 -0
- package/packages/parser/tests/fixtures/deleted-file.diff +9 -0
- package/packages/parser/tests/fixtures/empty.diff +0 -0
- package/packages/parser/tests/fixtures/hunk-with-context.diff +12 -0
- package/packages/parser/tests/fixtures/mode-change-with-content.diff +10 -0
- package/packages/parser/tests/fixtures/mode-change.diff +3 -0
- package/packages/parser/tests/fixtures/multi-file.diff +22 -0
- package/packages/parser/tests/fixtures/new-file.diff +9 -0
- package/packages/parser/tests/fixtures/no-newline.diff +10 -0
- package/packages/parser/tests/fixtures/renamed-file.diff +12 -0
- package/packages/parser/tests/fixtures/single-file-additions.diff +11 -0
- package/packages/parser/tests/fixtures/single-file-deletions.diff +11 -0
- package/packages/parser/tests/fixtures/single-file-mixed.diff +15 -0
- package/packages/parser/tests/fixtures/single-file-multi-hunk.diff +22 -0
- package/packages/parser/tests/fixtures/spaces-in-path.diff +9 -0
- package/packages/parser/tests/fixtures/submodule.diff +7 -0
- package/packages/parser/tests/fixtures/unicode-content.diff +11 -0
- package/packages/parser/tests/parse.test.ts +312 -0
- package/packages/parser/tests/word-diff-integration.test.ts +52 -0
- package/packages/parser/tests/word-diff.test.ts +121 -0
- package/packages/parser/tsconfig.json +10 -0
- package/packages/skills/diffity-resolve/SKILL.md +55 -0
- package/packages/skills/diffity-review/SKILL.md +74 -0
- package/packages/skills/diffity-start/SKILL.md +25 -0
- package/packages/ui/index.html +13 -0
- package/packages/ui/package.json +35 -0
- package/packages/ui/public/brand.svg +12 -0
- package/packages/ui/public/favicon.svg +15 -0
- package/packages/ui/src/app.tsx +14 -0
- package/packages/ui/src/components/comment-bubble.tsx +78 -0
- package/packages/ui/src/components/comment-form-row.tsx +58 -0
- package/packages/ui/src/components/comment-form.tsx +78 -0
- package/packages/ui/src/components/comment-line-number.tsx +60 -0
- package/packages/ui/src/components/comment-thread.tsx +209 -0
- package/packages/ui/src/components/commit-list.tsx +100 -0
- package/packages/ui/src/components/dashboard.tsx +84 -0
- package/packages/ui/src/components/diff-line.tsx +90 -0
- package/packages/ui/src/components/diff-page.tsx +332 -0
- package/packages/ui/src/components/diff-stats.tsx +20 -0
- package/packages/ui/src/components/diff-view.tsx +278 -0
- package/packages/ui/src/components/expand-row.tsx +45 -0
- package/packages/ui/src/components/file-block.tsx +536 -0
- package/packages/ui/src/components/file-tree-item.tsx +84 -0
- package/packages/ui/src/components/file-tree.tsx +72 -0
- package/packages/ui/src/components/general-comments.tsx +174 -0
- package/packages/ui/src/components/hunk-block-split.tsx +357 -0
- package/packages/ui/src/components/hunk-block.tsx +161 -0
- package/packages/ui/src/components/hunk-header.tsx +144 -0
- package/packages/ui/src/components/hunk-with-gap.tsx +113 -0
- package/packages/ui/src/components/icons/arrow-down-icon.tsx +7 -0
- package/packages/ui/src/components/icons/arrow-up-icon.tsx +7 -0
- package/packages/ui/src/components/icons/check-circle-icon.tsx +8 -0
- package/packages/ui/src/components/icons/check-icon.tsx +9 -0
- package/packages/ui/src/components/icons/chevron-down-icon.tsx +11 -0
- package/packages/ui/src/components/icons/chevron-icon.tsx +20 -0
- package/packages/ui/src/components/icons/chevron-up-down-icon.tsx +7 -0
- package/packages/ui/src/components/icons/chevron-up-icon.tsx +11 -0
- package/packages/ui/src/components/icons/comment-icon.tsx +9 -0
- package/packages/ui/src/components/icons/copy-icon.tsx +10 -0
- package/packages/ui/src/components/icons/eye-icon.tsx +10 -0
- package/packages/ui/src/components/icons/eye-off-icon.tsx +12 -0
- package/packages/ui/src/components/icons/file-icon.tsx +7 -0
- package/packages/ui/src/components/icons/folder-icon.tsx +19 -0
- package/packages/ui/src/components/icons/git-branch-icon.tsx +13 -0
- package/packages/ui/src/components/icons/keyboard-icon.tsx +13 -0
- package/packages/ui/src/components/icons/moon-icon.tsx +9 -0
- package/packages/ui/src/components/icons/plus-icon.tsx +9 -0
- package/packages/ui/src/components/icons/search-icon.tsx +10 -0
- package/packages/ui/src/components/icons/sidebar-icon.tsx +10 -0
- package/packages/ui/src/components/icons/spinner.tsx +7 -0
- package/packages/ui/src/components/icons/split-view-icon.tsx +10 -0
- package/packages/ui/src/components/icons/sun-icon.tsx +17 -0
- package/packages/ui/src/components/icons/trash-icon.tsx +11 -0
- package/packages/ui/src/components/icons/undo-icon.tsx +9 -0
- package/packages/ui/src/components/icons/unified-view-icon.tsx +12 -0
- package/packages/ui/src/components/icons/x-icon.tsx +10 -0
- package/packages/ui/src/components/line-number-cell.tsx +18 -0
- package/packages/ui/src/components/markdown-content.tsx +139 -0
- package/packages/ui/src/components/orphaned-threads.tsx +80 -0
- package/packages/ui/src/components/overview-file-list.tsx +57 -0
- package/packages/ui/src/components/render-expansion-rows.tsx +47 -0
- package/packages/ui/src/components/shortcut-modal.tsx +93 -0
- package/packages/ui/src/components/sidebar.tsx +80 -0
- package/packages/ui/src/components/skeleton.tsx +9 -0
- package/packages/ui/src/components/stale-diff-banner.tsx +21 -0
- package/packages/ui/src/components/summary-bar.tsx +39 -0
- package/packages/ui/src/components/toolbar.tsx +246 -0
- package/packages/ui/src/components/ui/badge.tsx +17 -0
- package/packages/ui/src/components/ui/confirm-dialog.tsx +52 -0
- package/packages/ui/src/components/ui/icon-button.tsx +23 -0
- package/packages/ui/src/components/ui/status-badge.tsx +57 -0
- package/packages/ui/src/components/ui/thread-badge.tsx +35 -0
- package/packages/ui/src/components/word-diff.tsx +126 -0
- package/packages/ui/src/hooks/use-comment-actions.ts +97 -0
- package/packages/ui/src/hooks/use-commits.ts +12 -0
- package/packages/ui/src/hooks/use-copy.ts +18 -0
- package/packages/ui/src/hooks/use-diff-staleness.ts +58 -0
- package/packages/ui/src/hooks/use-diff.ts +12 -0
- package/packages/ui/src/hooks/use-highlighter.ts +190 -0
- package/packages/ui/src/hooks/use-info.ts +12 -0
- package/packages/ui/src/hooks/use-keyboard.ts +55 -0
- package/packages/ui/src/hooks/use-line-selection.ts +157 -0
- package/packages/ui/src/hooks/use-overview.ts +12 -0
- package/packages/ui/src/hooks/use-review-threads.ts +12 -0
- package/packages/ui/src/hooks/use-search-params.ts +26 -0
- package/packages/ui/src/hooks/use-theme.ts +34 -0
- package/packages/ui/src/hooks/use-thread-navigation.ts +43 -0
- package/packages/ui/src/lib/api.ts +232 -0
- package/packages/ui/src/lib/cn.ts +6 -0
- package/packages/ui/src/lib/context-expansion.ts +122 -0
- package/packages/ui/src/lib/diff-utils.ts +268 -0
- package/packages/ui/src/lib/dom-utils.ts +13 -0
- package/packages/ui/src/lib/file-tree.ts +122 -0
- package/packages/ui/src/lib/query-client.ts +10 -0
- package/packages/ui/src/lib/render-content.tsx +23 -0
- package/packages/ui/src/lib/syntax-token.ts +4 -0
- package/packages/ui/src/main.tsx +14 -0
- package/packages/ui/src/queries/commits.ts +9 -0
- package/packages/ui/src/queries/diff.ts +9 -0
- package/packages/ui/src/queries/file.ts +10 -0
- package/packages/ui/src/queries/info.ts +9 -0
- package/packages/ui/src/queries/overview.ts +9 -0
- package/packages/ui/src/styles/app.css +178 -0
- package/packages/ui/src/types/comment.ts +61 -0
- package/packages/ui/src/vite-env.d.ts +1 -0
- package/packages/ui/tests/context-expansion.test.ts +279 -0
- package/packages/ui/tests/diff-utils.test.ts +409 -0
- package/packages/ui/tsconfig.json +14 -0
- package/packages/ui/vite.config.ts +23 -0
- package/scripts/build-skills.ts +26 -0
- package/scripts/build.ts +15 -0
- package/scripts/dev.ts +32 -0
- package/scripts/lib/transformers/claude-code.ts +11 -0
- package/scripts/lib/transformers/codex.ts +17 -0
- package/scripts/lib/transformers/cursor.ts +17 -0
- package/scripts/lib/transformers/index.ts +3 -0
- package/scripts/lib/utils.ts +70 -0
- package/scripts/link-dev.ts +54 -0
- package/skills/diffity-resolve/SKILL.md +55 -0
- package/skills/diffity-review/SKILL.md +74 -0
- package/skills/diffity-start/SKILL.md +27 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { isGitRepo } from '@diffity/git';
|
|
10
|
+
import { startServer } from './server.js';
|
|
11
|
+
import { registerAgentCommands } from './agent.js';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const pkg = require('../package.json');
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name('diffity')
|
|
20
|
+
.description('GitHub-style git diff viewer in the browser')
|
|
21
|
+
.version(pkg.version)
|
|
22
|
+
.argument('[refs...]', 'Git refs to diff (e.g. HEAD~3, main, main..feature)')
|
|
23
|
+
.option('--port <port>', 'Port to use', '5391')
|
|
24
|
+
.option('--no-open', 'Do not open browser automatically')
|
|
25
|
+
.option('--quiet', 'Minimal terminal output')
|
|
26
|
+
.option('--dark', 'Open in dark mode (default: light)')
|
|
27
|
+
.option('--unified', 'Open in unified view (default: split)')
|
|
28
|
+
.addHelpText('after', `
|
|
29
|
+
Examples:
|
|
30
|
+
$ diffity Working tree changes
|
|
31
|
+
$ diffity HEAD~1 Last commit vs working tree
|
|
32
|
+
$ diffity HEAD~3 Last 3 commits vs working tree
|
|
33
|
+
$ diffity abc1234 Changes since a specific commit
|
|
34
|
+
$ diffity main..feature Compare branches
|
|
35
|
+
$ diffity main feature Same as main..feature
|
|
36
|
+
$ diffity v1.0.0..v2.0.0 Compare tags`)
|
|
37
|
+
.action(async (refs: string[], opts) => {
|
|
38
|
+
if (!isGitRepo()) {
|
|
39
|
+
console.error(pc.red('Error: Not a git repository'));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const diffArgs: string[] = [];
|
|
44
|
+
let description = '';
|
|
45
|
+
|
|
46
|
+
if (refs.length === 1) {
|
|
47
|
+
const ref = refs[0];
|
|
48
|
+
if (ref.includes('..')) {
|
|
49
|
+
diffArgs.push(ref);
|
|
50
|
+
description = ref;
|
|
51
|
+
} else {
|
|
52
|
+
diffArgs.push(ref);
|
|
53
|
+
description = `Changes from ${ref}`;
|
|
54
|
+
}
|
|
55
|
+
} else if (refs.length === 2) {
|
|
56
|
+
diffArgs.push(`${refs[0]}..${refs[1]}`);
|
|
57
|
+
description = `${refs[0]}..${refs[1]}`;
|
|
58
|
+
} else {
|
|
59
|
+
description = 'Unstaged changes';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const port = parseInt(opts.port, 10);
|
|
63
|
+
|
|
64
|
+
let effectiveRef: string;
|
|
65
|
+
if (refs.length > 0) {
|
|
66
|
+
effectiveRef = refs.length === 2 ? `${refs[0]}..${refs[1]}` : refs[0];
|
|
67
|
+
} else {
|
|
68
|
+
effectiveRef = 'work';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const { port: actualPort, close } = await startServer({ port, diffArgs, description, effectiveRef });
|
|
73
|
+
const urlParams = new URLSearchParams({ ref: effectiveRef });
|
|
74
|
+
if (opts.dark) {
|
|
75
|
+
urlParams.set('theme', 'dark');
|
|
76
|
+
}
|
|
77
|
+
if (opts.unified) {
|
|
78
|
+
urlParams.set('view', 'unified');
|
|
79
|
+
}
|
|
80
|
+
const url = `http://localhost:${actualPort}/?${urlParams.toString()}`;
|
|
81
|
+
|
|
82
|
+
if (!opts.quiet) {
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(pc.bold(' diffity'));
|
|
85
|
+
console.log(` ${pc.dim(description)}`);
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(` ${pc.green('→')} ${pc.cyan(url)}`);
|
|
88
|
+
console.log(` ${pc.dim('Press Ctrl+C to stop')}`);
|
|
89
|
+
console.log('');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
process.on('SIGINT', () => {
|
|
93
|
+
if (!opts.quiet) {
|
|
94
|
+
console.log(pc.dim('\n Shutting down...'));
|
|
95
|
+
}
|
|
96
|
+
close();
|
|
97
|
+
process.exit(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
process.on('SIGTERM', () => {
|
|
101
|
+
close();
|
|
102
|
+
process.exit(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (opts.open !== false) {
|
|
106
|
+
await open(url);
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(pc.red(`Failed to start server: ${err}`));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
program
|
|
115
|
+
.command('prune')
|
|
116
|
+
.description('Remove all diffity data (database, sessions) for all repos')
|
|
117
|
+
.action(() => {
|
|
118
|
+
const dir = join(homedir(), '.diffity');
|
|
119
|
+
if (!existsSync(dir)) {
|
|
120
|
+
console.log(pc.dim('Nothing to prune.'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
rmSync(dir, { recursive: true, force: true });
|
|
125
|
+
console.log(pc.green('Pruned all diffity data (~/.diffity).'));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
program
|
|
129
|
+
.command('update')
|
|
130
|
+
.description('Update diffity to the latest version')
|
|
131
|
+
.action(() => {
|
|
132
|
+
try {
|
|
133
|
+
const registry = execSync('npm view diffity version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
134
|
+
if (registry === pkg.version) {
|
|
135
|
+
console.log(pc.green(`Already on the latest version (${pkg.version}).`));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
console.log(`${pc.dim(`Current: ${pkg.version}`)} → ${pc.bold(registry)}`);
|
|
139
|
+
console.log(pc.dim('Updating...'));
|
|
140
|
+
execSync('npm install -g diffity@latest', { stdio: 'inherit' });
|
|
141
|
+
console.log(pc.green(`Updated to ${registry}.`));
|
|
142
|
+
} catch {
|
|
143
|
+
console.error(pc.red('Failed to update. Try running: npm install -g diffity@latest'));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
program
|
|
149
|
+
.command('doctor')
|
|
150
|
+
.description('Check that diffity can run correctly')
|
|
151
|
+
.action(() => {
|
|
152
|
+
let ok = true;
|
|
153
|
+
|
|
154
|
+
process.stdout.write(' git ');
|
|
155
|
+
try {
|
|
156
|
+
const gitVersion = execSync('git --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
157
|
+
console.log(pc.green(`✓ ${gitVersion}`));
|
|
158
|
+
} catch {
|
|
159
|
+
console.log(pc.red('✗ git not found'));
|
|
160
|
+
ok = false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
process.stdout.write(' git repo ');
|
|
164
|
+
if (isGitRepo()) {
|
|
165
|
+
console.log(pc.green('✓ inside a git repository'));
|
|
166
|
+
} else {
|
|
167
|
+
console.log(pc.yellow('- not inside a git repository'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
process.stdout.write(' node ');
|
|
171
|
+
console.log(pc.green(`✓ ${process.version}`));
|
|
172
|
+
|
|
173
|
+
process.stdout.write(' sqlite ');
|
|
174
|
+
try {
|
|
175
|
+
require('better-sqlite3');
|
|
176
|
+
console.log(pc.green('✓ better-sqlite3 loaded'));
|
|
177
|
+
} catch {
|
|
178
|
+
console.log(pc.red('✗ better-sqlite3 failed to load (native module issue)'));
|
|
179
|
+
ok = false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
process.stdout.write(' version ');
|
|
183
|
+
console.log(pc.green(`✓ diffity ${pkg.version}`));
|
|
184
|
+
|
|
185
|
+
console.log('');
|
|
186
|
+
if (ok) {
|
|
187
|
+
console.log(pc.green(' All checks passed.'));
|
|
188
|
+
} else {
|
|
189
|
+
console.log(pc.red(' Some checks failed. Fix the issues above and try again.'));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
registerAgentCommands(program);
|
|
195
|
+
|
|
196
|
+
program.parse();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import {
|
|
3
|
+
createThread,
|
|
4
|
+
getThreadsForSession,
|
|
5
|
+
addReply,
|
|
6
|
+
updateThreadStatus,
|
|
7
|
+
deleteThread,
|
|
8
|
+
deleteAllThreadsForSession,
|
|
9
|
+
deleteComment,
|
|
10
|
+
type ThreadStatus,
|
|
11
|
+
} from './threads.js';
|
|
12
|
+
import { getCurrentSession } from './session.js';
|
|
13
|
+
|
|
14
|
+
function sendJson(res: ServerResponse, data: unknown) {
|
|
15
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
16
|
+
res.end(JSON.stringify(data));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function sendError(res: ServerResponse, status: number, message: string) {
|
|
20
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
21
|
+
res.end(JSON.stringify({ error: message }));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const chunks: Buffer[] = [];
|
|
27
|
+
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
28
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
29
|
+
req.on('error', reject);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function handleReviewRoute(req: IncomingMessage, res: ServerResponse, pathname: string, url: URL): boolean {
|
|
34
|
+
if (pathname === '/api/sessions/current' && req.method === 'GET') {
|
|
35
|
+
const session = getCurrentSession();
|
|
36
|
+
sendJson(res, session);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (pathname === '/api/threads' && req.method === 'GET') {
|
|
41
|
+
const sid = url.searchParams.get('session');
|
|
42
|
+
if (!sid) {
|
|
43
|
+
sendError(res, 400, 'Missing session parameter');
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const status = url.searchParams.get('status') as ThreadStatus | null;
|
|
47
|
+
const threads = getThreadsForSession(sid, status || undefined);
|
|
48
|
+
sendJson(res, threads);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (pathname === '/api/threads' && req.method === 'DELETE') {
|
|
53
|
+
readBody(req).then((raw) => {
|
|
54
|
+
try {
|
|
55
|
+
const body = JSON.parse(raw);
|
|
56
|
+
const { sessionId: sid } = body;
|
|
57
|
+
if (!sid) {
|
|
58
|
+
sendError(res, 400, 'Missing sessionId');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
deleteAllThreadsForSession(sid);
|
|
62
|
+
sendJson(res, { ok: true });
|
|
63
|
+
} catch (err) {
|
|
64
|
+
sendError(res, 500, `Failed to delete all threads: ${err}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (pathname === '/api/threads' && req.method === 'POST') {
|
|
71
|
+
readBody(req).then((raw) => {
|
|
72
|
+
try {
|
|
73
|
+
const body = JSON.parse(raw);
|
|
74
|
+
const { sessionId: sid, filePath, side, startLine, endLine, body: commentBody, author, anchorContent } = body;
|
|
75
|
+
if (!sid || !filePath || !side || typeof startLine !== 'number' || typeof endLine !== 'number' || !commentBody || !author) {
|
|
76
|
+
sendError(res, 400, 'Missing required fields');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const thread = createThread(sid, filePath, side, startLine, endLine, commentBody, author, anchorContent);
|
|
80
|
+
sendJson(res, thread);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
sendError(res, 500, `Failed to create thread: ${err}`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const threadReplyMatch = pathname.match(/^\/api\/threads\/([^/]+)\/reply$/);
|
|
89
|
+
if (threadReplyMatch && req.method === 'POST') {
|
|
90
|
+
readBody(req).then((raw) => {
|
|
91
|
+
try {
|
|
92
|
+
const body = JSON.parse(raw);
|
|
93
|
+
const { body: commentBody, author } = body;
|
|
94
|
+
if (!commentBody || !author) {
|
|
95
|
+
sendError(res, 400, 'Missing body or author');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const comment = addReply(threadReplyMatch[1], commentBody, author);
|
|
99
|
+
sendJson(res, comment);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
sendError(res, 500, `Failed to add reply: ${err}`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const threadStatusMatch = pathname.match(/^\/api\/threads\/([^/]+)\/status$/);
|
|
108
|
+
if (threadStatusMatch && req.method === 'PATCH') {
|
|
109
|
+
readBody(req).then((raw) => {
|
|
110
|
+
try {
|
|
111
|
+
const body = JSON.parse(raw);
|
|
112
|
+
const { status, summary } = body;
|
|
113
|
+
if (!status) {
|
|
114
|
+
sendError(res, 400, 'Missing status');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const summaryAuthor = summary ? { name: 'System', type: 'user' as const } : undefined;
|
|
118
|
+
updateThreadStatus(threadStatusMatch[1], status, summary, summaryAuthor);
|
|
119
|
+
sendJson(res, { ok: true });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
sendError(res, 500, `Failed to update thread status: ${err}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const threadDeleteMatch = pathname.match(/^\/api\/threads\/([^/]+)$/);
|
|
128
|
+
if (threadDeleteMatch && req.method === 'DELETE') {
|
|
129
|
+
try {
|
|
130
|
+
deleteThread(threadDeleteMatch[1]);
|
|
131
|
+
sendJson(res, { ok: true });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
sendError(res, 500, `Failed to delete thread: ${err}`);
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const commentDeleteMatch = pathname.match(/^\/api\/comments\/([^/]+)$/);
|
|
139
|
+
if (commentDeleteMatch && req.method === 'DELETE') {
|
|
140
|
+
try {
|
|
141
|
+
deleteComment(commentDeleteMatch[1]);
|
|
142
|
+
sendJson(res, { ok: true });
|
|
143
|
+
} catch (err) {
|
|
144
|
+
sendError(res, 500, `Failed to delete comment: ${err}`);
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return false;
|
|
150
|
+
}
|