alanbox 0.1.3 → 0.1.4

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 (56) hide show
  1. package/0boxer/AGENTS.md +3 -2
  2. package/0boxer/src/commands/AGENTS.md +2 -1
  3. package/0boxer/src/commands/install.js +47 -0
  4. package/1swarmer/AGENTS.md +5 -2
  5. package/1swarmer/src/AGENTS.md +4 -3
  6. package/1swarmer/src/args.js +7 -0
  7. package/1swarmer/src/cli.js +26 -0
  8. package/1swarmer/src/commands/AGENTS.md +3 -0
  9. package/1swarmer/src/commands/review-file.js +997 -0
  10. package/1swarmer/src/runner/AGENTS.md +1 -0
  11. package/1swarmer/src/runner/codex-runner.js +23 -3
  12. package/2designer/README.md +3 -0
  13. package/2designer/dist/{cdp-engine-JK2XVDHK.js → cdp-engine-4AIWSWXO.js} +2 -2
  14. package/2designer/dist/{cdp-engine-A5WTMTVF.js → cdp-engine-SG4K2BCX.js} +2 -2
  15. package/2designer/dist/{chunk-NQ3ASZUE.js → chunk-7X7PTLZH.js} +2 -2
  16. package/2designer/dist/{chunk-JVF26NXD.js → chunk-DPOWNFOH.js} +2 -2
  17. package/2designer/dist/{chunk-SKEIVBOU.js → chunk-ISUUIOO7.js} +1 -1
  18. package/2designer/dist/chunk-ISUUIOO7.js.map +1 -0
  19. package/2designer/dist/cli.js +494 -244
  20. package/2designer/dist/cli.js.map +1 -1
  21. package/2designer/dist/index.d.ts +7 -18
  22. package/2designer/dist/index.js +5 -198
  23. package/2designer/dist/index.js.map +1 -1
  24. package/2designer/dist/{playwright-engine-YBRDIUHF.js → playwright-engine-YXBY3KEN.js} +2 -2
  25. package/2designer/dist/{playwright-engine-3YKJOUNU.js → playwright-engine-YXGDTSZ5.js} +2 -2
  26. package/2designer/dist/tint-UD4CJ7S2.js +7 -0
  27. package/2designer/dist/{tint-I3FTT23O.js → tint-YN63MLVN.js} +1 -1
  28. package/2designer/dist/tint-YN63MLVN.js.map +1 -0
  29. package/4reporter/README.md +24 -0
  30. package/4reporter/dist/cli.js +464 -0
  31. package/4reporter/dist/cli.js.map +1 -0
  32. package/4reporter/dist/index.d.ts +108 -0
  33. package/4reporter/dist/index.js +445 -0
  34. package/4reporter/dist/index.js.map +1 -0
  35. package/4reporter/package.json +39 -0
  36. package/README.md +13 -5
  37. package/bin/reporter.js +11 -0
  38. package/cli.js +31 -6
  39. package/mcp/README.md +7 -1
  40. package/mcp/config.toml +4 -0
  41. package/package.json +8 -4
  42. package/skills/AGENTS.md +3 -3
  43. package/skills/aitool/SKILL.md +1 -1
  44. package/skills/desginer/SKILL.md +65 -45
  45. package/skills/swarmer/SKILL.md +37 -0
  46. package/2designer/LICENSE +0 -21
  47. package/2designer/dist/chunk-SKEIVBOU.js.map +0 -1
  48. package/2designer/dist/tint-I3FTT23O.js.map +0 -1
  49. package/2designer/dist/tint-RUSSUAWA.js +0 -7
  50. /package/2designer/dist/{cdp-engine-JK2XVDHK.js.map → cdp-engine-4AIWSWXO.js.map} +0 -0
  51. /package/2designer/dist/{cdp-engine-A5WTMTVF.js.map → cdp-engine-SG4K2BCX.js.map} +0 -0
  52. /package/2designer/dist/{chunk-NQ3ASZUE.js.map → chunk-7X7PTLZH.js.map} +0 -0
  53. /package/2designer/dist/{chunk-JVF26NXD.js.map → chunk-DPOWNFOH.js.map} +0 -0
  54. /package/2designer/dist/{playwright-engine-YBRDIUHF.js.map → playwright-engine-YXBY3KEN.js.map} +0 -0
  55. /package/2designer/dist/{playwright-engine-3YKJOUNU.js.map → playwright-engine-YXGDTSZ5.js.map} +0 -0
  56. /package/2designer/dist/{tint-RUSSUAWA.js.map → tint-UD4CJ7S2.js.map} +0 -0
@@ -0,0 +1,445 @@
1
+ // src/git.ts
2
+ import { execFileSync } from "child_process";
3
+ import { existsSync, statSync } from "fs";
4
+ import { basename, dirname, isAbsolute, relative, resolve } from "path";
5
+ var RECORD_SEPARATOR = "";
6
+ var FIELD_SEPARATOR = "";
7
+ function findGitRoot(inputPath) {
8
+ const absolutePath = resolve(inputPath);
9
+ if (!existsSync(absolutePath)) {
10
+ throw new Error(`path does not exist: ${absolutePath}`);
11
+ }
12
+ const startPath = statSync(absolutePath).isDirectory() ? absolutePath : dirname(absolutePath);
13
+ const root = runGit(startPath, ["rev-parse", "--show-toplevel"]).trim();
14
+ if (!root) {
15
+ throw new Error(`not a Git repository: ${absolutePath}`);
16
+ }
17
+ return root;
18
+ }
19
+ function getRepoInfo(root) {
20
+ const branch = runGit(root, ["branch", "--show-current"], { allowFailure: true }).trim() || `detached:${runGit(root, ["rev-parse", "--short", "HEAD"], { allowFailure: true }).trim()}` || "unknown";
21
+ const remote = runGit(root, ["config", "--get", "remote.origin.url"], { allowFailure: true }).trim();
22
+ return {
23
+ root,
24
+ name: basename(root),
25
+ branch,
26
+ remote: remote || void 0
27
+ };
28
+ }
29
+ function scanWeeklyWork(options) {
30
+ const root = findGitRoot(options.targetPath);
31
+ const scopedPath = options.wholeRepo ? void 0 : getScopedPath(root, options.targetPath);
32
+ const userAliases = parseUserAliases(options.user);
33
+ const commits = readCommits(root, {
34
+ since: options.since,
35
+ until: options.until,
36
+ includeAllRefs: options.includeAllRefs ?? true,
37
+ scopedPath
38
+ }).filter((commit) => matchesUser(commit, userAliases));
39
+ return {
40
+ repo: getRepoInfo(root),
41
+ target: {
42
+ inputPath: resolve(options.targetPath),
43
+ scopedPath
44
+ },
45
+ user: options.user,
46
+ userAliases,
47
+ range: {
48
+ since: options.since.toISOString(),
49
+ until: options.until.toISOString()
50
+ },
51
+ commits,
52
+ uncommitted: options.includeUncommitted ? readUncommittedSummary(root, scopedPath) : void 0
53
+ };
54
+ }
55
+ function readCommits(root, options) {
56
+ const args = ["log"];
57
+ if (options.includeAllRefs) args.push("--all");
58
+ args.push(
59
+ `--since=${options.since.toISOString()}`,
60
+ `--until=${options.until.toISOString()}`,
61
+ "--date=iso-strict",
62
+ "--numstat",
63
+ `--format=${RECORD_SEPARATOR}%H${FIELD_SEPARATOR}%an${FIELD_SEPARATOR}%ae${FIELD_SEPARATOR}%cn${FIELD_SEPARATOR}%ce${FIELD_SEPARATOR}%aI${FIELD_SEPARATOR}%s`
64
+ );
65
+ if (options.scopedPath) {
66
+ args.push("--", options.scopedPath);
67
+ }
68
+ return parseGitLog(runGit(root, args));
69
+ }
70
+ function parseGitLog(raw) {
71
+ const commits = [];
72
+ const seen = /* @__PURE__ */ new Set();
73
+ for (const block of raw.split(RECORD_SEPARATOR)) {
74
+ const trimmedBlock = block.trim();
75
+ if (!trimmedBlock) continue;
76
+ const lines = trimmedBlock.split(/\r?\n/);
77
+ const fields = (lines.shift() ?? "").split(FIELD_SEPARATOR);
78
+ if (fields.length < 7) continue;
79
+ const [hash, authorName, authorEmail, committerName, committerEmail, date, subject] = fields;
80
+ if (!hash || seen.has(hash)) continue;
81
+ seen.add(hash);
82
+ const files = lines.map((line) => parseNumstatLine(line)).filter((file) => Boolean(file));
83
+ const insertions = files.reduce((sum, file) => sum + file.insertions, 0);
84
+ const deletions = files.reduce((sum, file) => sum + file.deletions, 0);
85
+ commits.push({
86
+ hash,
87
+ shortHash: hash.slice(0, 7),
88
+ authorName,
89
+ authorEmail,
90
+ committerName,
91
+ committerEmail,
92
+ date,
93
+ subject: subject || "(no subject)",
94
+ files,
95
+ insertions,
96
+ deletions
97
+ });
98
+ }
99
+ return commits;
100
+ }
101
+ function parseUserAliases(user) {
102
+ const rawAliases = Array.isArray(user) ? user : user.split(",");
103
+ const aliases = rawAliases.map((alias) => alias.trim().toLowerCase()).filter(Boolean);
104
+ if (!aliases.length) {
105
+ throw new Error("--user must include at least one Git name or email alias");
106
+ }
107
+ return aliases;
108
+ }
109
+ function matchesUser(commit, user) {
110
+ const aliases = parseUserAliases(user);
111
+ const haystack = [
112
+ commit.authorName,
113
+ commit.authorEmail,
114
+ commit.committerName,
115
+ commit.committerEmail
116
+ ].join(" ").toLowerCase();
117
+ return aliases.some((alias) => haystack.includes(alias));
118
+ }
119
+ function parseNumstatLine(line) {
120
+ if (!line.trim()) return void 0;
121
+ const [insertionsRaw, deletionsRaw, ...pathParts] = line.split(" ");
122
+ const filePath = pathParts.join(" ").trim();
123
+ if (!filePath) return void 0;
124
+ const binary = insertionsRaw === "-" || deletionsRaw === "-";
125
+ return {
126
+ path: filePath,
127
+ insertions: binary ? 0 : Number.parseInt(insertionsRaw, 10) || 0,
128
+ deletions: binary ? 0 : Number.parseInt(deletionsRaw, 10) || 0,
129
+ binary
130
+ };
131
+ }
132
+ function readUncommittedSummary(root, scopedPath) {
133
+ const pathArgs = scopedPath ? ["--", scopedPath] : [];
134
+ const status = runGit(root, ["status", "--porcelain=v1", ...pathArgs], { allowFailure: true });
135
+ const files = status.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean).map((line) => ({
136
+ status: line.slice(0, 2).trim() || "??",
137
+ path: line.slice(3).trim()
138
+ }));
139
+ const shortstats = [
140
+ runGit(root, ["diff", "--cached", "--shortstat", ...pathArgs], { allowFailure: true }).trim(),
141
+ runGit(root, ["diff", "--shortstat", ...pathArgs], { allowFailure: true }).trim()
142
+ ].filter(Boolean);
143
+ return {
144
+ files,
145
+ shortstat: shortstats.join("; ") || void 0
146
+ };
147
+ }
148
+ function getScopedPath(root, inputPath) {
149
+ const rootPath = resolve(root);
150
+ const absoluteInput = resolve(inputPath);
151
+ const scopedPath = relative(rootPath, absoluteInput);
152
+ if (!scopedPath) return void 0;
153
+ if (scopedPath.startsWith("..") || isAbsolute(scopedPath)) return void 0;
154
+ return scopedPath.replace(/\\/g, "/");
155
+ }
156
+ function runGit(cwd, args, options = {}) {
157
+ try {
158
+ return execFileSync("git", ["-C", cwd, ...args], {
159
+ encoding: "utf8",
160
+ maxBuffer: 20 * 1024 * 1024,
161
+ stdio: ["ignore", "pipe", "pipe"]
162
+ });
163
+ } catch (error) {
164
+ if (options.allowFailure) return "";
165
+ const gitError = error;
166
+ const stderr = gitError.stderr?.toString().trim();
167
+ const detail = stderr || gitError.message || "unknown git error";
168
+ throw new Error(`git ${args.join(" ")} failed in ${cwd}: ${detail}`);
169
+ }
170
+ }
171
+
172
+ // src/report.ts
173
+ var CATEGORY_ORDER = [
174
+ { id: "feature", label: "\u529F\u80FD\u5F00\u53D1" },
175
+ { id: "fix", label: "\u95EE\u9898\u4FEE\u590D" },
176
+ { id: "refactor", label: "\u91CD\u6784\u7EF4\u62A4" },
177
+ { id: "test", label: "\u6D4B\u8BD5\u9A8C\u8BC1" },
178
+ { id: "docs", label: "\u6587\u6863\u8BF4\u660E" },
179
+ { id: "config", label: "\u914D\u7F6E\u6784\u5EFA" },
180
+ { id: "other", label: "\u5176\u4ED6\u5DE5\u4F5C" }
181
+ ];
182
+ function summarizeWeeklyScan(scan) {
183
+ const categoryMap = /* @__PURE__ */ new Map();
184
+ const fileMap = /* @__PURE__ */ new Map();
185
+ const followUps = /* @__PURE__ */ new Set();
186
+ for (const commit of scan.commits) {
187
+ const category = classifyCommit(commit);
188
+ const commits = categoryMap.get(category) ?? [];
189
+ commits.push(commit);
190
+ categoryMap.set(category, commits);
191
+ if (/\b(todo|fixme|wip)\b|待|临时|暂/i.test(commit.subject)) {
192
+ followUps.add(cleanSubject(commit.subject));
193
+ }
194
+ for (const file of commit.files) {
195
+ const current = fileMap.get(file.path) ?? {
196
+ path: file.path,
197
+ commits: 0,
198
+ insertions: 0,
199
+ deletions: 0
200
+ };
201
+ current.commits += 1;
202
+ current.insertions += file.insertions;
203
+ current.deletions += file.deletions;
204
+ fileMap.set(file.path, current);
205
+ }
206
+ }
207
+ return {
208
+ commitCount: scan.commits.length,
209
+ fileCount: fileMap.size,
210
+ insertions: sumCommits(scan.commits, "insertions"),
211
+ deletions: sumCommits(scan.commits, "deletions"),
212
+ categories: CATEGORY_ORDER.map((category) => ({
213
+ ...category,
214
+ commits: categoryMap.get(category.id) ?? []
215
+ })).filter((category) => category.commits.length > 0),
216
+ topFiles: [...fileMap.values()].sort((a, b) => b.insertions + b.deletions + b.commits - (a.insertions + a.deletions + a.commits)).slice(0, 8),
217
+ followUps: [...followUps].slice(0, 8)
218
+ };
219
+ }
220
+ function classifyCommit(commit) {
221
+ const subject = commit.subject.toLowerCase();
222
+ const files = commit.files.map((file) => file.path.toLowerCase());
223
+ if (/(fix|bug|hotfix|修复|问题|错误|缺陷)/i.test(subject)) return "fix";
224
+ if (/(test|spec|vitest|jest|测试|验证)/i.test(subject) || files.some(isTestFile)) return "test";
225
+ if (/(docs?|readme|文档|说明)/i.test(subject) || files.every(isDocFile)) return "docs";
226
+ if (/(refactor|cleanup|chore|重构|整理|维护)/i.test(subject)) return "refactor";
227
+ if (/(build|ci|config|deps?|package|配置|构建|依赖)/i.test(subject) || files.some(isConfigFile)) {
228
+ return "config";
229
+ }
230
+ if (/(feat|feature|add|init|实现|新增|添加|完成|支持)/i.test(subject)) return "feature";
231
+ return "other";
232
+ }
233
+ function formatWeeklyReport(scan, summary = summarizeWeeklyScan(scan)) {
234
+ const lines = [];
235
+ const since = formatDate(scan.range.since);
236
+ const until = formatDate(scan.range.until);
237
+ lines.push(`# \u5468\u62A5\uFF1A${since.date} \u81F3 ${until.date}`);
238
+ lines.push("");
239
+ lines.push(`- \u4ED3\u5E93\uFF1A${scan.repo.name}`);
240
+ lines.push(`- \u8DEF\u5F84\uFF1A${scan.repo.root}`);
241
+ lines.push(`- \u626B\u63CF\u8303\u56F4\uFF1A${scan.target.scopedPath ?? "\u6574\u4E2A\u4ED3\u5E93"}`);
242
+ lines.push(`- \u5206\u652F\uFF1A${scan.repo.branch}`);
243
+ lines.push(`- \u7528\u6237\uFF1A${scan.user}`);
244
+ lines.push(`- \u65F6\u95F4\u8303\u56F4\uFF1A${since.full} - ${until.full}`);
245
+ lines.push(`- \u63D0\u4EA4\u7EDF\u8BA1\uFF1A${summary.commitCount} \u4E2A\u63D0\u4EA4\uFF0C${summary.fileCount} \u4E2A\u6587\u4EF6\uFF0C+${summary.insertions} / -${summary.deletions}`);
246
+ if (scan.repo.remote) {
247
+ lines.push(`- \u8FDC\u7AEF\uFF1A${scan.repo.remote}`);
248
+ }
249
+ lines.push("");
250
+ if (summary.commitCount === 0) {
251
+ lines.push("## \u672C\u5468\u5DE5\u4F5C");
252
+ lines.push("");
253
+ lines.push("\u672C\u5468\u5728\u5F53\u524D\u4ED3\u5E93\u548C\u65F6\u95F4\u8303\u56F4\u5185\u6CA1\u6709\u626B\u63CF\u5230\u5339\u914D\u7528\u6237\u7684\u63D0\u4EA4\u3002\u8BF7\u786E\u8BA4 `--user` \u662F\u5426\u4E0E Git author/committer \u7684 name \u6216 email \u4E00\u81F4\uFF0C\u6216\u4F7F\u7528 `--since` / `--until` \u8C03\u6574\u8303\u56F4\u3002");
254
+ return lines.join("\n");
255
+ }
256
+ lines.push("## \u672C\u5468\u91CD\u70B9");
257
+ lines.push("");
258
+ for (const category of summary.categories) {
259
+ const subjects = category.commits.slice(0, 3).map((commit) => cleanSubject(commit.subject));
260
+ const suffix = category.commits.length > subjects.length ? ` \u7B49 ${category.commits.length} \u9879` : "";
261
+ lines.push(`- ${category.label}\uFF1A${subjects.join("\uFF1B")}${suffix}`);
262
+ }
263
+ lines.push("");
264
+ lines.push("## \u5177\u4F53\u5DE5\u4F5C");
265
+ lines.push("");
266
+ for (const category of summary.categories) {
267
+ lines.push(`### ${category.label}`);
268
+ for (const commit of category.commits.slice(0, 10)) {
269
+ lines.push(formatCommitLine(commit));
270
+ }
271
+ if (category.commits.length > 10) {
272
+ lines.push(`- \u5176\u4F59 ${category.commits.length - 10} \u4E2A\u63D0\u4EA4\u5DF2\u7701\u7565\u3002`);
273
+ }
274
+ lines.push("");
275
+ }
276
+ if (summary.topFiles.length > 0) {
277
+ lines.push("## \u91CD\u70B9\u53D8\u66F4\u6587\u4EF6");
278
+ lines.push("");
279
+ for (const file of summary.topFiles) {
280
+ lines.push(`- ${file.path}\uFF1A${file.commits} \u6B21\u63D0\u4EA4\uFF0C+${file.insertions} / -${file.deletions}`);
281
+ }
282
+ lines.push("");
283
+ }
284
+ lines.push("## \u98CE\u9669\u4E0E\u5F85\u8DDF\u8FDB");
285
+ lines.push("");
286
+ if (summary.followUps.length > 0) {
287
+ for (const item of summary.followUps) {
288
+ lines.push(`- ${item}`);
289
+ }
290
+ } else {
291
+ lines.push("- \u6682\u672A\u4ECE\u63D0\u4EA4\u4FE1\u606F\u4E2D\u8BC6\u522B\u51FA\u660E\u786E\u963B\u585E\u6216\u5F85\u529E\u3002");
292
+ }
293
+ if (scan.uncommitted?.files.length) {
294
+ lines.push("");
295
+ lines.push("## \u672A\u63D0\u4EA4\u53D8\u66F4");
296
+ lines.push("");
297
+ lines.push("\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u5DE5\u4F5C\u533A\u72B6\u6001\uFF0C\u672A\u6309\u7528\u6237\u540D\u5F52\u56E0\uFF1A");
298
+ for (const file of scan.uncommitted.files.slice(0, 12)) {
299
+ lines.push(`- ${file.status} ${file.path}`);
300
+ }
301
+ if (scan.uncommitted.files.length > 12) {
302
+ lines.push(`- \u5176\u4F59 ${scan.uncommitted.files.length - 12} \u4E2A\u6587\u4EF6\u5DF2\u7701\u7565\u3002`);
303
+ }
304
+ if (scan.uncommitted.shortstat) {
305
+ lines.push(`- Diff \u6458\u8981\uFF1A${scan.uncommitted.shortstat}`);
306
+ }
307
+ }
308
+ return lines.join("\n").trimEnd();
309
+ }
310
+ function formatCommitLine(commit) {
311
+ const files = summarizeFiles(commit.files);
312
+ return `- ${cleanSubject(commit.subject)}\uFF08${commit.shortHash}\uFF0C${files}\uFF0C+${commit.insertions} / -${commit.deletions}\uFF09`;
313
+ }
314
+ function summarizeFiles(files) {
315
+ if (files.length === 0) return "\u65E0\u6587\u4EF6\u7EDF\u8BA1";
316
+ const preview = files.slice(0, 2).map((file) => file.path).join("\u3001");
317
+ if (files.length > 2) return `${preview} \u7B49 ${files.length} \u4E2A\u6587\u4EF6`;
318
+ return `${preview}\uFF0C${files.length} \u4E2A\u6587\u4EF6`;
319
+ }
320
+ function cleanSubject(subject) {
321
+ return subject.replace(/^(feat|fix|docs|chore|refactor|test|style|perf|build|ci)(\([^)]+\))?:\s*/i, "").trim() || "(\u65E0\u63D0\u4EA4\u8BF4\u660E)";
322
+ }
323
+ function formatDate(value) {
324
+ const date = new Date(value);
325
+ const year = date.getFullYear();
326
+ const month = pad(date.getMonth() + 1);
327
+ const day = pad(date.getDate());
328
+ const hour = pad(date.getHours());
329
+ const minute = pad(date.getMinutes());
330
+ return {
331
+ date: `${year}-${month}-${day}`,
332
+ full: `${year}-${month}-${day} ${hour}:${minute}`
333
+ };
334
+ }
335
+ function pad(value) {
336
+ return String(value).padStart(2, "0");
337
+ }
338
+ function sumCommits(commits, field) {
339
+ return commits.reduce((sum, commit) => sum + commit[field], 0);
340
+ }
341
+ function isTestFile(file) {
342
+ return /(^|\/)(__tests__|test|tests|spec)\//.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file);
343
+ }
344
+ function isDocFile(file) {
345
+ return /\.(md|mdx|txt|rst)$/i.test(file);
346
+ }
347
+ function isConfigFile(file) {
348
+ return /(^|\/)(package\.json|pnpm-lock\.yaml|tsconfig\.json|vite\.config|vitest\.config|tsup\.config|\.github\/|config\/)/i.test(file);
349
+ }
350
+
351
+ // src/commands/weekly.ts
352
+ import { mkdirSync, writeFileSync } from "fs";
353
+ import { dirname as dirname2, join, resolve as resolve2 } from "path";
354
+ var DEFAULT_REPORT_DIR = "C:\\Users\\lenovo\\Desktop\\all-project\\.tmp\\reporter";
355
+ function getLocalWeekRange(now = /* @__PURE__ */ new Date()) {
356
+ const since = new Date(now);
357
+ since.setHours(0, 0, 0, 0);
358
+ const day = since.getDay();
359
+ const diffToMonday = day === 0 ? -6 : 1 - day;
360
+ since.setDate(since.getDate() + diffToMonday);
361
+ return { since, until: now };
362
+ }
363
+ function buildWeeklyOptions(raw, now = /* @__PURE__ */ new Date()) {
364
+ const targetPath = raw.path ?? raw.repo ?? raw.address;
365
+ if (!targetPath) {
366
+ throw new Error("--path is required");
367
+ }
368
+ if (!raw.user) {
369
+ throw new Error("--user is required");
370
+ }
371
+ const defaultRange = getLocalWeekRange(now);
372
+ const since = parseDateOption(raw.since, "start") ?? defaultRange.since;
373
+ const until = parseDateOption(raw.until, "end") ?? defaultRange.until;
374
+ if (since.getTime() > until.getTime()) {
375
+ throw new Error("--since must be earlier than --until");
376
+ }
377
+ const format = normalizeFormat(raw.format);
378
+ const explicitOutput = Boolean(raw.output);
379
+ const output = explicitOutput ? resolve2(String(raw.output)) : buildDefaultOutputPath(targetPath, raw.user, since, format);
380
+ return {
381
+ targetPath: String(targetPath),
382
+ user: String(raw.user),
383
+ since,
384
+ until,
385
+ includeAllRefs: !raw.currentBranch,
386
+ wholeRepo: Boolean(raw.wholeRepo),
387
+ includeUncommitted: Boolean(raw.includeUncommitted),
388
+ format,
389
+ output,
390
+ explicitOutput
391
+ };
392
+ }
393
+ function renderWeeklyOutput(scan, format) {
394
+ const summary = summarizeWeeklyScan(scan);
395
+ if (format === "json") {
396
+ return JSON.stringify({ scan, summary }, null, 2);
397
+ }
398
+ return formatWeeklyReport(scan, summary);
399
+ }
400
+ function buildDefaultOutputPath(targetPath, user, since, format) {
401
+ const ext = format === "json" ? "json" : "md";
402
+ const date = formatDateForFile(since);
403
+ const userSlug = slugify(user) || "user";
404
+ const targetSlug = slugify(targetPath.split(/[\\/]/).filter(Boolean).pop() ?? "repo") || "repo";
405
+ return join(DEFAULT_REPORT_DIR, `${date}-${targetSlug}-${userSlug}.${ext}`);
406
+ }
407
+ function parseDateOption(value, boundary) {
408
+ if (value == null || value === "") return void 0;
409
+ const raw = String(value).trim();
410
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(raw);
411
+ const date = dateOnly ? /* @__PURE__ */ new Date(`${raw}T${boundary === "start" ? "00:00:00" : "23:59:59"}`) : new Date(raw);
412
+ if (Number.isNaN(date.getTime())) {
413
+ throw new Error(`invalid date: ${raw}`);
414
+ }
415
+ return date;
416
+ }
417
+ function normalizeFormat(value) {
418
+ const format = String(value ?? "markdown").toLowerCase();
419
+ if (format === "markdown" || format === "json") return format;
420
+ throw new Error("--format must be markdown or json");
421
+ }
422
+ function formatDateForFile(date) {
423
+ const year = date.getFullYear();
424
+ const month = String(date.getMonth() + 1).padStart(2, "0");
425
+ const day = String(date.getDate()).padStart(2, "0");
426
+ return `${year}-${month}-${day}`;
427
+ }
428
+ function slugify(value) {
429
+ return value.trim().replace(/@/g, "-at-").replace(/[^a-zA-Z0-9\u4e00-\u9fa5._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
430
+ }
431
+ export {
432
+ buildWeeklyOptions,
433
+ classifyCommit,
434
+ findGitRoot,
435
+ formatWeeklyReport,
436
+ getLocalWeekRange,
437
+ getRepoInfo,
438
+ matchesUser,
439
+ parseGitLog,
440
+ parseUserAliases,
441
+ renderWeeklyOutput,
442
+ scanWeeklyWork,
443
+ summarizeWeeklyScan
444
+ };
445
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/git.ts","../src/report.ts","../src/commands/weekly.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport { existsSync, statSync } from 'node:fs'\nimport { basename, dirname, isAbsolute, relative, resolve } from 'node:path'\n\nconst RECORD_SEPARATOR = '\\x1e'\nconst FIELD_SEPARATOR = '\\x1f'\n\nexport interface GitFileStat {\n path: string\n insertions: number\n deletions: number\n binary: boolean\n}\n\nexport interface GitCommit {\n hash: string\n shortHash: string\n authorName: string\n authorEmail: string\n committerName: string\n committerEmail: string\n date: string\n subject: string\n files: GitFileStat[]\n insertions: number\n deletions: number\n}\n\nexport interface RepoInfo {\n root: string\n name: string\n branch: string\n remote?: string\n}\n\nexport interface UncommittedSummary {\n files: Array<{ status: string; path: string }>\n shortstat?: string\n}\n\nexport interface WeeklyScan {\n repo: RepoInfo\n target: {\n inputPath: string\n scopedPath?: string\n }\n user: string\n userAliases: string[]\n range: {\n since: string\n until: string\n }\n commits: GitCommit[]\n uncommitted?: UncommittedSummary\n}\n\nexport interface WeeklyScanOptions {\n targetPath: string\n user: string\n since: Date\n until: Date\n includeAllRefs?: boolean\n wholeRepo?: boolean\n includeUncommitted?: boolean\n}\n\ninterface RunGitOptions {\n allowFailure?: boolean\n}\n\nexport function findGitRoot(inputPath: string): string {\n const absolutePath = resolve(inputPath)\n if (!existsSync(absolutePath)) {\n throw new Error(`path does not exist: ${absolutePath}`)\n }\n\n const startPath = statSync(absolutePath).isDirectory() ? absolutePath : dirname(absolutePath)\n const root = runGit(startPath, ['rev-parse', '--show-toplevel']).trim()\n if (!root) {\n throw new Error(`not a Git repository: ${absolutePath}`)\n }\n return root\n}\n\nexport function getRepoInfo(root: string): RepoInfo {\n const branch = runGit(root, ['branch', '--show-current'], { allowFailure: true }).trim()\n || `detached:${runGit(root, ['rev-parse', '--short', 'HEAD'], { allowFailure: true }).trim()}`\n || 'unknown'\n const remote = runGit(root, ['config', '--get', 'remote.origin.url'], { allowFailure: true }).trim()\n\n return {\n root,\n name: basename(root),\n branch,\n remote: remote || undefined,\n }\n}\n\nexport function scanWeeklyWork(options: WeeklyScanOptions): WeeklyScan {\n const root = findGitRoot(options.targetPath)\n const scopedPath = options.wholeRepo ? undefined : getScopedPath(root, options.targetPath)\n const userAliases = parseUserAliases(options.user)\n const commits = readCommits(root, {\n since: options.since,\n until: options.until,\n includeAllRefs: options.includeAllRefs ?? true,\n scopedPath,\n }).filter(commit => matchesUser(commit, userAliases))\n\n return {\n repo: getRepoInfo(root),\n target: {\n inputPath: resolve(options.targetPath),\n scopedPath,\n },\n user: options.user,\n userAliases,\n range: {\n since: options.since.toISOString(),\n until: options.until.toISOString(),\n },\n commits,\n uncommitted: options.includeUncommitted ? readUncommittedSummary(root, scopedPath) : undefined,\n }\n}\n\nexport function readCommits(\n root: string,\n options: { since: Date; until: Date; includeAllRefs: boolean; scopedPath?: string },\n): GitCommit[] {\n const args = ['log']\n if (options.includeAllRefs) args.push('--all')\n args.push(\n `--since=${options.since.toISOString()}`,\n `--until=${options.until.toISOString()}`,\n '--date=iso-strict',\n '--numstat',\n `--format=${RECORD_SEPARATOR}%H${FIELD_SEPARATOR}%an${FIELD_SEPARATOR}%ae${FIELD_SEPARATOR}%cn${FIELD_SEPARATOR}%ce${FIELD_SEPARATOR}%aI${FIELD_SEPARATOR}%s`,\n )\n if (options.scopedPath) {\n args.push('--', options.scopedPath)\n }\n\n return parseGitLog(runGit(root, args))\n}\n\nexport function parseGitLog(raw: string): GitCommit[] {\n const commits: GitCommit[] = []\n const seen = new Set<string>()\n\n for (const block of raw.split(RECORD_SEPARATOR)) {\n const trimmedBlock = block.trim()\n if (!trimmedBlock) continue\n\n const lines = trimmedBlock.split(/\\r?\\n/)\n const fields = (lines.shift() ?? '').split(FIELD_SEPARATOR)\n if (fields.length < 7) continue\n\n const [hash, authorName, authorEmail, committerName, committerEmail, date, subject] = fields\n if (!hash || seen.has(hash)) continue\n seen.add(hash)\n\n const files = lines\n .map(line => parseNumstatLine(line))\n .filter((file): file is GitFileStat => Boolean(file))\n const insertions = files.reduce((sum, file) => sum + file.insertions, 0)\n const deletions = files.reduce((sum, file) => sum + file.deletions, 0)\n\n commits.push({\n hash,\n shortHash: hash.slice(0, 7),\n authorName,\n authorEmail,\n committerName,\n committerEmail,\n date,\n subject: subject || '(no subject)',\n files,\n insertions,\n deletions,\n })\n }\n\n return commits\n}\n\nexport function parseUserAliases(user: string | string[]): string[] {\n const rawAliases = Array.isArray(user) ? user : user.split(',')\n const aliases = rawAliases\n .map(alias => alias.trim().toLowerCase())\n .filter(Boolean)\n\n if (!aliases.length) {\n throw new Error('--user must include at least one Git name or email alias')\n }\n\n return aliases\n}\n\nexport function matchesUser(commit: GitCommit, user: string | string[]): boolean {\n const aliases = parseUserAliases(user)\n const haystack = [\n commit.authorName,\n commit.authorEmail,\n commit.committerName,\n commit.committerEmail,\n ].join(' ').toLowerCase()\n\n return aliases.some(alias => haystack.includes(alias))\n}\n\nfunction parseNumstatLine(line: string): GitFileStat | undefined {\n if (!line.trim()) return undefined\n const [insertionsRaw, deletionsRaw, ...pathParts] = line.split('\\t')\n const filePath = pathParts.join('\\t').trim()\n if (!filePath) return undefined\n\n const binary = insertionsRaw === '-' || deletionsRaw === '-'\n return {\n path: filePath,\n insertions: binary ? 0 : Number.parseInt(insertionsRaw, 10) || 0,\n deletions: binary ? 0 : Number.parseInt(deletionsRaw, 10) || 0,\n binary,\n }\n}\n\nfunction readUncommittedSummary(root: string, scopedPath?: string): UncommittedSummary {\n const pathArgs = scopedPath ? ['--', scopedPath] : []\n const status = runGit(root, ['status', '--porcelain=v1', ...pathArgs], { allowFailure: true })\n const files = status\n .split(/\\r?\\n/)\n .map(line => line.trimEnd())\n .filter(Boolean)\n .map(line => ({\n status: line.slice(0, 2).trim() || '??',\n path: line.slice(3).trim(),\n }))\n\n const shortstats = [\n runGit(root, ['diff', '--cached', '--shortstat', ...pathArgs], { allowFailure: true }).trim(),\n runGit(root, ['diff', '--shortstat', ...pathArgs], { allowFailure: true }).trim(),\n ].filter(Boolean)\n\n return {\n files,\n shortstat: shortstats.join('; ') || undefined,\n }\n}\n\nfunction getScopedPath(root: string, inputPath: string): string | undefined {\n const rootPath = resolve(root)\n const absoluteInput = resolve(inputPath)\n const scopedPath = relative(rootPath, absoluteInput)\n if (!scopedPath) return undefined\n if (scopedPath.startsWith('..') || isAbsolute(scopedPath)) return undefined\n\n return scopedPath.replace(/\\\\/g, '/')\n}\n\nfunction runGit(cwd: string, args: string[], options: RunGitOptions = {}): string {\n try {\n return execFileSync('git', ['-C', cwd, ...args], {\n encoding: 'utf8',\n maxBuffer: 20 * 1024 * 1024,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n } catch (error) {\n if (options.allowFailure) return ''\n\n const gitError = error as { stderr?: Buffer | string; message?: string }\n const stderr = gitError.stderr?.toString().trim()\n const detail = stderr || gitError.message || 'unknown git error'\n throw new Error(`git ${args.join(' ')} failed in ${cwd}: ${detail}`)\n }\n}\n","import type { GitCommit, GitFileStat, WeeklyScan } from './git.js'\n\nexport interface CategorySummary {\n id: string\n label: string\n commits: GitCommit[]\n}\n\nexport interface FileSummary {\n path: string\n commits: number\n insertions: number\n deletions: number\n}\n\nexport interface WeeklySummary {\n commitCount: number\n fileCount: number\n insertions: number\n deletions: number\n categories: CategorySummary[]\n topFiles: FileSummary[]\n followUps: string[]\n}\n\nconst CATEGORY_ORDER = [\n { id: 'feature', label: '功能开发' },\n { id: 'fix', label: '问题修复' },\n { id: 'refactor', label: '重构维护' },\n { id: 'test', label: '测试验证' },\n { id: 'docs', label: '文档说明' },\n { id: 'config', label: '配置构建' },\n { id: 'other', label: '其他工作' },\n]\n\nexport function summarizeWeeklyScan(scan: WeeklyScan): WeeklySummary {\n const categoryMap = new Map<string, GitCommit[]>()\n const fileMap = new Map<string, FileSummary>()\n const followUps = new Set<string>()\n\n for (const commit of scan.commits) {\n const category = classifyCommit(commit)\n const commits = categoryMap.get(category) ?? []\n commits.push(commit)\n categoryMap.set(category, commits)\n\n if (/\\b(todo|fixme|wip)\\b|待|临时|暂/i.test(commit.subject)) {\n followUps.add(cleanSubject(commit.subject))\n }\n\n for (const file of commit.files) {\n const current = fileMap.get(file.path) ?? {\n path: file.path,\n commits: 0,\n insertions: 0,\n deletions: 0,\n }\n current.commits += 1\n current.insertions += file.insertions\n current.deletions += file.deletions\n fileMap.set(file.path, current)\n }\n }\n\n return {\n commitCount: scan.commits.length,\n fileCount: fileMap.size,\n insertions: sumCommits(scan.commits, 'insertions'),\n deletions: sumCommits(scan.commits, 'deletions'),\n categories: CATEGORY_ORDER\n .map(category => ({\n ...category,\n commits: categoryMap.get(category.id) ?? [],\n }))\n .filter(category => category.commits.length > 0),\n topFiles: [...fileMap.values()]\n .sort((a, b) => (b.insertions + b.deletions + b.commits) - (a.insertions + a.deletions + a.commits))\n .slice(0, 8),\n followUps: [...followUps].slice(0, 8),\n }\n}\n\nexport function classifyCommit(commit: GitCommit): string {\n const subject = commit.subject.toLowerCase()\n const files = commit.files.map(file => file.path.toLowerCase())\n\n if (/(fix|bug|hotfix|修复|问题|错误|缺陷)/i.test(subject)) return 'fix'\n if (/(test|spec|vitest|jest|测试|验证)/i.test(subject) || files.some(isTestFile)) return 'test'\n if (/(docs?|readme|文档|说明)/i.test(subject) || files.every(isDocFile)) return 'docs'\n if (/(refactor|cleanup|chore|重构|整理|维护)/i.test(subject)) return 'refactor'\n if (\n /(build|ci|config|deps?|package|配置|构建|依赖)/i.test(subject)\n || files.some(isConfigFile)\n ) {\n return 'config'\n }\n if (/(feat|feature|add|init|实现|新增|添加|完成|支持)/i.test(subject)) return 'feature'\n\n return 'other'\n}\n\nexport function formatWeeklyReport(scan: WeeklyScan, summary = summarizeWeeklyScan(scan)): string {\n const lines: string[] = []\n const since = formatDate(scan.range.since)\n const until = formatDate(scan.range.until)\n\n lines.push(`# 周报:${since.date} 至 ${until.date}`)\n lines.push('')\n lines.push(`- 仓库:${scan.repo.name}`)\n lines.push(`- 路径:${scan.repo.root}`)\n lines.push(`- 扫描范围:${scan.target.scopedPath ?? '整个仓库'}`)\n lines.push(`- 分支:${scan.repo.branch}`)\n lines.push(`- 用户:${scan.user}`)\n lines.push(`- 时间范围:${since.full} - ${until.full}`)\n lines.push(`- 提交统计:${summary.commitCount} 个提交,${summary.fileCount} 个文件,+${summary.insertions} / -${summary.deletions}`)\n\n if (scan.repo.remote) {\n lines.push(`- 远端:${scan.repo.remote}`)\n }\n\n lines.push('')\n\n if (summary.commitCount === 0) {\n lines.push('## 本周工作')\n lines.push('')\n lines.push('本周在当前仓库和时间范围内没有扫描到匹配用户的提交。请确认 `--user` 是否与 Git author/committer 的 name 或 email 一致,或使用 `--since` / `--until` 调整范围。')\n return lines.join('\\n')\n }\n\n lines.push('## 本周重点')\n lines.push('')\n for (const category of summary.categories) {\n const subjects = category.commits.slice(0, 3).map(commit => cleanSubject(commit.subject))\n const suffix = category.commits.length > subjects.length ? ` 等 ${category.commits.length} 项` : ''\n lines.push(`- ${category.label}:${subjects.join(';')}${suffix}`)\n }\n\n lines.push('')\n lines.push('## 具体工作')\n lines.push('')\n for (const category of summary.categories) {\n lines.push(`### ${category.label}`)\n for (const commit of category.commits.slice(0, 10)) {\n lines.push(formatCommitLine(commit))\n }\n if (category.commits.length > 10) {\n lines.push(`- 其余 ${category.commits.length - 10} 个提交已省略。`)\n }\n lines.push('')\n }\n\n if (summary.topFiles.length > 0) {\n lines.push('## 重点变更文件')\n lines.push('')\n for (const file of summary.topFiles) {\n lines.push(`- ${file.path}:${file.commits} 次提交,+${file.insertions} / -${file.deletions}`)\n }\n lines.push('')\n }\n\n lines.push('## 风险与待跟进')\n lines.push('')\n if (summary.followUps.length > 0) {\n for (const item of summary.followUps) {\n lines.push(`- ${item}`)\n }\n } else {\n lines.push('- 暂未从提交信息中识别出明确阻塞或待办。')\n }\n\n if (scan.uncommitted?.files.length) {\n lines.push('')\n lines.push('## 未提交变更')\n lines.push('')\n lines.push('以下内容来自工作区状态,未按用户名归因:')\n for (const file of scan.uncommitted.files.slice(0, 12)) {\n lines.push(`- ${file.status} ${file.path}`)\n }\n if (scan.uncommitted.files.length > 12) {\n lines.push(`- 其余 ${scan.uncommitted.files.length - 12} 个文件已省略。`)\n }\n if (scan.uncommitted.shortstat) {\n lines.push(`- Diff 摘要:${scan.uncommitted.shortstat}`)\n }\n }\n\n return lines.join('\\n').trimEnd()\n}\n\nfunction formatCommitLine(commit: GitCommit): string {\n const files = summarizeFiles(commit.files)\n return `- ${cleanSubject(commit.subject)}(${commit.shortHash},${files},+${commit.insertions} / -${commit.deletions})`\n}\n\nfunction summarizeFiles(files: GitFileStat[]): string {\n if (files.length === 0) return '无文件统计'\n const preview = files.slice(0, 2).map(file => file.path).join('、')\n if (files.length > 2) return `${preview} 等 ${files.length} 个文件`\n return `${preview},${files.length} 个文件`\n}\n\nfunction cleanSubject(subject: string): string {\n return subject\n .replace(/^(feat|fix|docs|chore|refactor|test|style|perf|build|ci)(\\([^)]+\\))?:\\s*/i, '')\n .trim()\n || '(无提交说明)'\n}\n\nfunction formatDate(value: string): { date: string; full: string } {\n const date = new Date(value)\n const year = date.getFullYear()\n const month = pad(date.getMonth() + 1)\n const day = pad(date.getDate())\n const hour = pad(date.getHours())\n const minute = pad(date.getMinutes())\n\n return {\n date: `${year}-${month}-${day}`,\n full: `${year}-${month}-${day} ${hour}:${minute}`,\n }\n}\n\nfunction pad(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nfunction sumCommits(commits: GitCommit[], field: 'insertions' | 'deletions'): number {\n return commits.reduce((sum, commit) => sum + commit[field], 0)\n}\n\nfunction isTestFile(file: string): boolean {\n return /(^|\\/)(__tests__|test|tests|spec)\\//.test(file) || /\\.(test|spec)\\.[cm]?[jt]sx?$/.test(file)\n}\n\nfunction isDocFile(file: string): boolean {\n return /\\.(md|mdx|txt|rst)$/i.test(file)\n}\n\nfunction isConfigFile(file: string): boolean {\n return /(^|\\/)(package\\.json|pnpm-lock\\.yaml|tsconfig\\.json|vite\\.config|vitest\\.config|tsup\\.config|\\.github\\/|config\\/)/i.test(file)\n}\n","import { mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { formatWeeklyReport, summarizeWeeklyScan } from '../report.js'\nimport { scanWeeklyWork, type WeeklyScan } from '../git.js'\n\nexport type WeeklyOutputFormat = 'markdown' | 'json'\n\nexport const DEFAULT_REPORT_DIR = 'C:\\\\Users\\\\lenovo\\\\Desktop\\\\all-project\\\\.tmp\\\\reporter'\n\nexport interface WeeklyCommandOptions {\n targetPath: string\n user: string\n since: Date\n until: Date\n includeAllRefs: boolean\n wholeRepo: boolean\n includeUncommitted: boolean\n format: WeeklyOutputFormat\n output: string\n explicitOutput: boolean\n}\n\nexport function getLocalWeekRange(now = new Date()): { since: Date; until: Date } {\n const since = new Date(now)\n since.setHours(0, 0, 0, 0)\n const day = since.getDay()\n const diffToMonday = day === 0 ? -6 : 1 - day\n since.setDate(since.getDate() + diffToMonday)\n\n return { since, until: now }\n}\n\nexport function buildWeeklyOptions(raw: Record<string, any>, now = new Date()): WeeklyCommandOptions {\n const targetPath = raw.path ?? raw.repo ?? raw.address\n if (!targetPath) {\n throw new Error('--path is required')\n }\n if (!raw.user) {\n throw new Error('--user is required')\n }\n\n const defaultRange = getLocalWeekRange(now)\n const since = parseDateOption(raw.since, 'start') ?? defaultRange.since\n const until = parseDateOption(raw.until, 'end') ?? defaultRange.until\n\n if (since.getTime() > until.getTime()) {\n throw new Error('--since must be earlier than --until')\n }\n\n const format = normalizeFormat(raw.format)\n const explicitOutput = Boolean(raw.output)\n const output = explicitOutput\n ? resolve(String(raw.output))\n : buildDefaultOutputPath(targetPath, raw.user, since, format)\n\n return {\n targetPath: String(targetPath),\n user: String(raw.user),\n since,\n until,\n includeAllRefs: !raw.currentBranch,\n wholeRepo: Boolean(raw.wholeRepo),\n includeUncommitted: Boolean(raw.includeUncommitted),\n format,\n output,\n explicitOutput,\n }\n}\n\nexport function renderWeeklyOutput(scan: WeeklyScan, format: WeeklyOutputFormat): string {\n const summary = summarizeWeeklyScan(scan)\n if (format === 'json') {\n return JSON.stringify({ scan, summary }, null, 2)\n }\n return formatWeeklyReport(scan, summary)\n}\n\nexport async function weekly(raw: Record<string, any>): Promise<void> {\n const options = buildWeeklyOptions(raw)\n const scan = scanWeeklyWork(options)\n const rendered = renderWeeklyOutput(scan, options.format)\n\n mkdirSync(dirname(options.output), { recursive: true })\n writeFileSync(options.output, rendered, 'utf8')\n console.log(`weekly report written to ${options.output}`)\n}\n\nexport function buildDefaultOutputPath(\n targetPath: string,\n user: string,\n since: Date,\n format: WeeklyOutputFormat,\n): string {\n const ext = format === 'json' ? 'json' : 'md'\n const date = formatDateForFile(since)\n const userSlug = slugify(user) || 'user'\n const targetSlug = slugify(targetPath.split(/[\\\\/]/).filter(Boolean).pop() ?? 'repo') || 'repo'\n\n return join(DEFAULT_REPORT_DIR, `${date}-${targetSlug}-${userSlug}.${ext}`)\n}\n\nfunction parseDateOption(value: unknown, boundary: 'start' | 'end'): Date | undefined {\n if (value == null || value === '') return undefined\n\n const raw = String(value).trim()\n const dateOnly = /^\\d{4}-\\d{2}-\\d{2}$/.test(raw)\n const date = dateOnly\n ? new Date(`${raw}T${boundary === 'start' ? '00:00:00' : '23:59:59'}`)\n : new Date(raw)\n\n if (Number.isNaN(date.getTime())) {\n throw new Error(`invalid date: ${raw}`)\n }\n\n return date\n}\n\nfunction normalizeFormat(value: unknown): WeeklyOutputFormat {\n const format = String(value ?? 'markdown').toLowerCase()\n if (format === 'markdown' || format === 'json') return format\n throw new Error('--format must be markdown or json')\n}\n\nfunction formatDateForFile(date: Date): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n\nfunction slugify(value: string): string {\n return value\n .trim()\n .replace(/@/g, '-at-')\n .replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5._-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80)\n}\n"],"mappings":"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","names":["dirname","resolve","resolve"]}
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "reporter",
3
+ "version": "0.1.0",
4
+ "description": "Git weekly report CLI for AI agents.",
5
+ "type": "module",
6
+ "bin": {
7
+ "reporter": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "lint": "tsc --noEmit"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "keywords": [
22
+ "git",
23
+ "weekly-report",
24
+ "report",
25
+ "ai"
26
+ ],
27
+ "license": "MIT",
28
+ "packageManager": "pnpm@10.28.2",
29
+ "dependencies": {
30
+ "commander": "^14.0.3"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.5.2",
34
+ "tsup": "^8.5.1",
35
+ "typescript": "^5.8.3",
36
+ "vitest": "^4.1.4"
37
+ }
38
+ }
39
+
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # alanbox
2
2
 
3
- `alanbox` is a local AI-tool command package. It routes package resource commands to `0boxer`, worker orchestration to `1swarmer`, and runtime UI measurement to `2designer`.
3
+ `alanbox` is a local AI-tool command package. It routes package resource commands to `0boxer`, worker orchestration to `1swarmer`, runtime UI measurement to `2designer`, and Git weekly report generation to `4reporter`.
4
4
 
5
5
  The stable worker workflow is still `swarmer` / `swarm` from `1swarmer`.
6
6
 
@@ -22,15 +22,23 @@ alanbox install --target claude
22
22
  alanbox install --target both
23
23
  alanbox designer measure --url "http://localhost:3000" --selector ".dialog"
24
24
  designer screenshot --url "http://localhost:3000" --output page.png
25
+ designer changelist --design design.png --runtime runtime.png --output changes.json --annotated changes.png
26
+ reporter weekly --path "C:\path\to\repo-or-file" --user "Your Git Name" --output weekly.md
27
+ alanbox reporter weekly --path "C:\path\to\repo-or-file" --user "you@example.com"
25
28
  swarmer --namespace run-name --worker "codex:reviewer:检查当前改动"
26
29
  swarmer --namespace run-name --worker "claude:reviewer:检查当前改动"
30
+ swarmer review-file "C:\path\to\file-or-dir"
31
+ swarmer review-file "C:\path\to\file-or-dir" --review-kind improvements
27
32
  ```
28
33
 
29
- `alanbox init` and `alanbox update` prompt you to choose Codex, Claude, or both. The package also exposes `swarmer` and `designer` as direct bins, so those flows do not need the `alanbox` prefix.
34
+ `alanbox init` and `alanbox update` prompt you to choose Codex, Claude, or both. The package also exposes `swarmer`, `designer`, and `reporter` as direct bins, so those flows do not need the `alanbox` prefix.
30
35
 
31
36
  ## Notes
32
37
 
33
- - `init`, `update`, and `install` explicitly copy bundled skills, hooks, scripts, and MCP resource placeholders to Codex and/or Claude homes.
34
- - `cli.js` is the package-level command router. Keep package resource command parsing inside `0boxer/src/cli.js`, worker parsing inside `1swarmer/src/cli.js`, and UI parsing inside `2designer/dist/cli.js`.
35
- - `aibox/skills` is the package-level skill installation source. It can include `2designer` workflow guidance. `designer` is now bundled as a direct bin and as `alanbox designer ...`. `3apiflower` getapi is not exposed by this npm package yet.
38
+ - `init`, `update`, and `install` explicitly copy bundled skills, hooks, scripts, and MCP resources to Codex and/or Claude homes. Codex installs also append the bundled `mcp/config.toml` Playwright MCP entry when `mcp_servers.playwright` is missing.
39
+ - `cli.js` is the package-level command router. Keep package resource command parsing inside `0boxer/src/cli.js`, worker parsing inside `1swarmer/src/cli.js`, UI parsing inside `2designer/dist/cli.js`, and Git weekly report parsing inside `4reporter/dist/cli.js`.
40
+ - `aibox/skills` is the package-level skill installation source. It can include `2designer` workflow guidance. `designer` is now bundled as a direct bin and as `alanbox designer ...`. `reporter` is bundled as a direct bin and as `alanbox reporter ...`. `3apiflower` is maintained as a separate local `alan-utils` API flow and is not exposed by this npm package yet.
41
+ - `reporter weekly --path <file-or-repo> --user <name-or-email>` scans local Git history for the current week by default and writes a Chinese Markdown weekly report; use `--since`, `--until`, `--current-branch`, `--format json`, and `--include-uncommitted` for narrower runs.
36
42
  - `swarmer` is a compatibility alias for `swarm`.
43
+ - `swarmer review-file <file-or-dir>` first asks Codex and Claude to write separate initial Markdown sidecars such as `*.codex.initial.md` and `*.claude.initial.md`, then merges `[codex√]` and `[claude√]` findings into one consensus-chain Markdown report under `C:\Users\lenovo\Desktop\all-project\.tmp\swarm\<yyyy-mm-dd-HHmmss>-<hash>.md` when that workspace exists. Items use tags such as `[codex√]`, `[codex√claude❌]`, and `[codex√claude❌codex√]`; unresolved `❌` items keep being reviewed and finally end with `√`.
44
+ - Use `--phase initial|cross-check|all`, `--report-file <path>`, `--report-dir <path>`, `--providers codex,claude`, and `--review-kind defects|improvements` to resume, split long review runs, or switch between blocker-bug review and actionable-improvement review.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Direct reporter bin bundled through alanbox.
5
+ */
6
+
7
+ require('../cli').runReporter().catch((error) => {
8
+ console.error(error.message || error);
9
+ process.exitCode = error.exitCode || 1;
10
+ });
11
+