repoview 0.5.1 → 0.6.1
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/CHANGELOG.md +87 -0
- package/CONTRIBUTING.md +4 -3
- package/DEVELOPMENT.md +84 -16
- package/README.md +71 -5
- package/dist/api.js +58 -0
- package/dist/api.js.map +1 -0
- package/dist/cli.js +454 -0
- package/dist/cli.js.map +1 -0
- package/dist/csv.js +64 -0
- package/dist/csv.js.map +1 -0
- package/dist/format.js +25 -0
- package/dist/format.js.map +1 -0
- package/dist/gist-router.js +153 -0
- package/dist/gist-router.js.map +1 -0
- package/dist/gists.js +130 -0
- package/dist/gists.js.map +1 -0
- package/dist/git.js +67 -0
- package/dist/git.js.map +1 -0
- package/dist/gitignore.js +34 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/linkcheck.js +310 -0
- package/dist/linkcheck.js.map +1 -0
- package/dist/markdown.js +493 -0
- package/dist/markdown.js.map +1 -0
- package/dist/net.js +10 -0
- package/dist/net.js.map +1 -0
- package/dist/paths.js +59 -0
- package/dist/paths.js.map +1 -0
- package/dist/reload.js +55 -0
- package/dist/reload.js.map +1 -0
- package/dist/repo-context.js +80 -0
- package/dist/repo-context.js.map +1 -0
- package/dist/repo-router.js +810 -0
- package/dist/repo-router.js.map +1 -0
- package/dist/review-cli.js +228 -0
- package/dist/review-cli.js.map +1 -0
- package/dist/server.js +122 -0
- package/dist/server.js.map +1 -0
- package/dist/session.js +86 -0
- package/dist/session.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/views.js +780 -0
- package/dist/views.js.map +1 -0
- package/package.json +20 -9
- package/public/app.css +113 -0
- package/public/app.js +26 -3
- package/public/gist.js +60 -0
- package/public/review.js +9 -6
- package/public/session.js +61 -0
- package/src/cli.js +0 -91
- package/src/gitignore.js +0 -34
- package/src/linkcheck.js +0 -312
- package/src/markdown.js +0 -518
- package/src/review-cli.js +0 -245
- package/src/server.js +0 -1126
- package/src/views.js +0 -657
package/src/review-cli.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
function generateThreadId(title) {
|
|
5
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
6
|
-
const slug = title
|
|
7
|
-
.toLowerCase()
|
|
8
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
9
|
-
.replace(/^-|-$/g, "")
|
|
10
|
-
.slice(0, 50);
|
|
11
|
-
return `${date}-${slug}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function getNextMessageId(existingIds) {
|
|
15
|
-
let max = 0;
|
|
16
|
-
for (const id of existingIds) {
|
|
17
|
-
const n = parseInt(id, 10);
|
|
18
|
-
if (n > max) max = n;
|
|
19
|
-
}
|
|
20
|
-
return String(max + 1).padStart(3, "0");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function reviewNew({ title, reviewDir }) {
|
|
24
|
-
if (!title) {
|
|
25
|
-
process.stderr.write("Error: --title is required\n");
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const id = generateThreadId(title);
|
|
30
|
-
const threadDir = path.join(reviewDir, id);
|
|
31
|
-
const messagesDir = path.join(threadDir, "messages");
|
|
32
|
-
|
|
33
|
-
await fs.mkdir(messagesDir, { recursive: true });
|
|
34
|
-
|
|
35
|
-
const now = new Date().toISOString();
|
|
36
|
-
const thread = {
|
|
37
|
-
id,
|
|
38
|
-
title,
|
|
39
|
-
createdAt: now,
|
|
40
|
-
lastActivityAt: now,
|
|
41
|
-
readUntil: null,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
await fs.writeFile(path.join(threadDir, "thread.json"), JSON.stringify(thread, null, 2) + "\n");
|
|
45
|
-
await fs.writeFile(path.join(threadDir, "comments.json"), JSON.stringify({ comments: [] }, null, 2) + "\n");
|
|
46
|
-
|
|
47
|
-
process.stdout.write(id + "\n");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function reviewPost({ threadId, role, body, file, reviewDir }) {
|
|
51
|
-
if (!threadId) {
|
|
52
|
-
process.stderr.write("Error: thread-id is required\n");
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const threadDir = path.join(reviewDir, threadId);
|
|
57
|
-
const messagesDir = path.join(threadDir, "messages");
|
|
58
|
-
const threadFile = path.join(threadDir, "thread.json");
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
await fs.stat(threadFile);
|
|
62
|
-
} catch {
|
|
63
|
-
process.stderr.write(`Error: thread "${threadId}" not found\n`);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let messageBody = body || "";
|
|
68
|
-
if (file) {
|
|
69
|
-
messageBody = await fs.readFile(file, "utf8");
|
|
70
|
-
} else if (!body) {
|
|
71
|
-
// Read from stdin
|
|
72
|
-
const chunks = [];
|
|
73
|
-
for await (const chunk of process.stdin) {
|
|
74
|
-
chunks.push(chunk);
|
|
75
|
-
}
|
|
76
|
-
messageBody = Buffer.concat(chunks).toString("utf8");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!messageBody.trim()) {
|
|
80
|
-
process.stderr.write("Error: message body is empty\n");
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Find next message ID
|
|
85
|
-
let entries = [];
|
|
86
|
-
try {
|
|
87
|
-
entries = await fs.readdir(messagesDir);
|
|
88
|
-
} catch {
|
|
89
|
-
await fs.mkdir(messagesDir, { recursive: true });
|
|
90
|
-
}
|
|
91
|
-
const existingIds = entries.filter((e) => e.endsWith(".json")).map((e) => e.replace(".json", ""));
|
|
92
|
-
const nextId = getNextMessageId(existingIds);
|
|
93
|
-
|
|
94
|
-
const now = new Date().toISOString();
|
|
95
|
-
const messageRole = role || "agent";
|
|
96
|
-
const format = messageRole === "agent" ? "markdown" : "text";
|
|
97
|
-
|
|
98
|
-
const message = {
|
|
99
|
-
id: nextId,
|
|
100
|
-
role: messageRole,
|
|
101
|
-
format,
|
|
102
|
-
body: messageBody,
|
|
103
|
-
createdAt: now,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
await fs.writeFile(path.join(messagesDir, `${nextId}.json`), JSON.stringify(message, null, 2) + "\n");
|
|
107
|
-
|
|
108
|
-
// Update lastActivityAt in thread.json
|
|
109
|
-
const thread = JSON.parse(await fs.readFile(threadFile, "utf8"));
|
|
110
|
-
thread.lastActivityAt = now;
|
|
111
|
-
await fs.writeFile(threadFile, JSON.stringify(thread, null, 2) + "\n");
|
|
112
|
-
|
|
113
|
-
process.stdout.write(nextId + "\n");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function reviewRead({ threadId, reviewDir }) {
|
|
117
|
-
if (!threadId) {
|
|
118
|
-
process.stderr.write("Error: thread-id is required\n");
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const threadDir = path.join(reviewDir, threadId);
|
|
123
|
-
const threadFile = path.join(threadDir, "thread.json");
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
await fs.stat(threadFile);
|
|
127
|
-
} catch {
|
|
128
|
-
process.stderr.write(`Error: thread "${threadId}" not found\n`);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const thread = JSON.parse(await fs.readFile(threadFile, "utf8"));
|
|
133
|
-
|
|
134
|
-
const messagesDir = path.join(threadDir, "messages");
|
|
135
|
-
let messageFiles = [];
|
|
136
|
-
try {
|
|
137
|
-
messageFiles = (await fs.readdir(messagesDir)).filter((f) => f.endsWith(".json")).sort();
|
|
138
|
-
} catch {
|
|
139
|
-
// no messages yet
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const messages = [];
|
|
143
|
-
for (const f of messageFiles) {
|
|
144
|
-
const msg = JSON.parse(await fs.readFile(path.join(messagesDir, f), "utf8"));
|
|
145
|
-
messages.push(msg);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let comments = { comments: [] };
|
|
149
|
-
try {
|
|
150
|
-
comments = JSON.parse(await fs.readFile(path.join(threadDir, "comments.json"), "utf8"));
|
|
151
|
-
} catch {
|
|
152
|
-
// no comments file
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const result = { thread, messages, comments: comments.comments };
|
|
156
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function reviewList({ reviewDir }) {
|
|
160
|
-
let entries = [];
|
|
161
|
-
try {
|
|
162
|
-
entries = await fs.readdir(reviewDir, { withFileTypes: true });
|
|
163
|
-
} catch {
|
|
164
|
-
process.stdout.write("[]\n");
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const threads = [];
|
|
169
|
-
for (const entry of entries) {
|
|
170
|
-
if (!entry.isDirectory()) continue;
|
|
171
|
-
const threadFile = path.join(reviewDir, entry.name, "thread.json");
|
|
172
|
-
try {
|
|
173
|
-
const thread = JSON.parse(await fs.readFile(threadFile, "utf8"));
|
|
174
|
-
// Count messages
|
|
175
|
-
let messageCount = 0;
|
|
176
|
-
try {
|
|
177
|
-
const msgs = await fs.readdir(path.join(reviewDir, entry.name, "messages"));
|
|
178
|
-
messageCount = msgs.filter((f) => f.endsWith(".json")).length;
|
|
179
|
-
} catch {
|
|
180
|
-
// no messages
|
|
181
|
-
}
|
|
182
|
-
threads.push({ ...thread, messageCount });
|
|
183
|
-
} catch {
|
|
184
|
-
// skip dirs without thread.json
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Sort by lastActivityAt, newest first
|
|
189
|
-
threads.sort((a, b) => new Date(b.lastActivityAt) - new Date(a.lastActivityAt));
|
|
190
|
-
|
|
191
|
-
process.stdout.write(JSON.stringify(threads, null, 2) + "\n");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export async function handleReviewCommand(argv, repoRoot) {
|
|
195
|
-
const subcommand = argv[0];
|
|
196
|
-
const args = argv.slice(1);
|
|
197
|
-
|
|
198
|
-
// Parse --review-dir flag
|
|
199
|
-
let reviewDir = path.join(repoRoot, ".repoview", "reviews");
|
|
200
|
-
const rest = [];
|
|
201
|
-
for (let i = 0; i < args.length; i++) {
|
|
202
|
-
if (args[i] === "--review-dir") {
|
|
203
|
-
reviewDir = args[++i];
|
|
204
|
-
} else {
|
|
205
|
-
rest.push(args[i]);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Parse subcommand-specific flags
|
|
210
|
-
const flags = {};
|
|
211
|
-
const positional = [];
|
|
212
|
-
for (let i = 0; i < rest.length; i++) {
|
|
213
|
-
const v = rest[i];
|
|
214
|
-
if (v === "--title") flags.title = rest[++i];
|
|
215
|
-
else if (v === "--role") flags.role = rest[++i];
|
|
216
|
-
else if (v === "--body") flags.body = rest[++i];
|
|
217
|
-
else if (v === "--file") flags.file = rest[++i];
|
|
218
|
-
else positional.push(v);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
switch (subcommand) {
|
|
222
|
-
case "new":
|
|
223
|
-
await reviewNew({ title: flags.title, reviewDir });
|
|
224
|
-
break;
|
|
225
|
-
case "post":
|
|
226
|
-
await reviewPost({
|
|
227
|
-
threadId: positional[0],
|
|
228
|
-
role: flags.role,
|
|
229
|
-
body: flags.body,
|
|
230
|
-
file: flags.file,
|
|
231
|
-
reviewDir,
|
|
232
|
-
});
|
|
233
|
-
break;
|
|
234
|
-
case "read":
|
|
235
|
-
await reviewRead({ threadId: positional[0], reviewDir });
|
|
236
|
-
break;
|
|
237
|
-
case "list":
|
|
238
|
-
await reviewList({ reviewDir });
|
|
239
|
-
break;
|
|
240
|
-
default:
|
|
241
|
-
process.stderr.write(`Unknown review subcommand: ${subcommand}\n`);
|
|
242
|
-
process.stderr.write("Usage: repoview review <new|post|read|list> [options]\n");
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
}
|