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.
Files changed (57) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/CONTRIBUTING.md +4 -3
  3. package/DEVELOPMENT.md +84 -16
  4. package/README.md +71 -5
  5. package/dist/api.js +58 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/cli.js +454 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/csv.js +64 -0
  10. package/dist/csv.js.map +1 -0
  11. package/dist/format.js +25 -0
  12. package/dist/format.js.map +1 -0
  13. package/dist/gist-router.js +153 -0
  14. package/dist/gist-router.js.map +1 -0
  15. package/dist/gists.js +130 -0
  16. package/dist/gists.js.map +1 -0
  17. package/dist/git.js +67 -0
  18. package/dist/git.js.map +1 -0
  19. package/dist/gitignore.js +34 -0
  20. package/dist/gitignore.js.map +1 -0
  21. package/dist/linkcheck.js +310 -0
  22. package/dist/linkcheck.js.map +1 -0
  23. package/dist/markdown.js +493 -0
  24. package/dist/markdown.js.map +1 -0
  25. package/dist/net.js +10 -0
  26. package/dist/net.js.map +1 -0
  27. package/dist/paths.js +59 -0
  28. package/dist/paths.js.map +1 -0
  29. package/dist/reload.js +55 -0
  30. package/dist/reload.js.map +1 -0
  31. package/dist/repo-context.js +80 -0
  32. package/dist/repo-context.js.map +1 -0
  33. package/dist/repo-router.js +810 -0
  34. package/dist/repo-router.js.map +1 -0
  35. package/dist/review-cli.js +228 -0
  36. package/dist/review-cli.js.map +1 -0
  37. package/dist/server.js +122 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/session.js +86 -0
  40. package/dist/session.js.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/views.js +780 -0
  44. package/dist/views.js.map +1 -0
  45. package/package.json +20 -9
  46. package/public/app.css +113 -0
  47. package/public/app.js +26 -3
  48. package/public/gist.js +60 -0
  49. package/public/review.js +9 -6
  50. package/public/session.js +61 -0
  51. package/src/cli.js +0 -91
  52. package/src/gitignore.js +0 -34
  53. package/src/linkcheck.js +0 -312
  54. package/src/markdown.js +0 -518
  55. package/src/review-cli.js +0 -245
  56. package/src/server.js +0 -1126
  57. 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
- }