openspec-stat 1.0.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.
Files changed (47) hide show
  1. package/README.md +234 -0
  2. package/README.zh-CN.md +234 -0
  3. package/dist/cjs/branch-selector.d.ts +7 -0
  4. package/dist/cjs/branch-selector.js +124 -0
  5. package/dist/cjs/cli.d.ts +2 -0
  6. package/dist/cjs/cli.js +151 -0
  7. package/dist/cjs/config.d.ts +3 -0
  8. package/dist/cjs/config.js +66 -0
  9. package/dist/cjs/formatters.d.ts +7 -0
  10. package/dist/cjs/formatters.js +222 -0
  11. package/dist/cjs/git-analyzer.d.ts +10 -0
  12. package/dist/cjs/git-analyzer.js +170 -0
  13. package/dist/cjs/i18n/index.d.ts +7 -0
  14. package/dist/cjs/i18n/index.js +84 -0
  15. package/dist/cjs/i18n/locales/en.json +72 -0
  16. package/dist/cjs/i18n/locales/zh-CN.json +72 -0
  17. package/dist/cjs/index.d.ts +6 -0
  18. package/dist/cjs/index.js +50 -0
  19. package/dist/cjs/stats-aggregator.d.ts +7 -0
  20. package/dist/cjs/stats-aggregator.js +117 -0
  21. package/dist/cjs/time-utils.d.ts +6 -0
  22. package/dist/cjs/time-utils.js +55 -0
  23. package/dist/cjs/types.d.ts +77 -0
  24. package/dist/cjs/types.js +17 -0
  25. package/dist/esm/branch-selector.d.ts +7 -0
  26. package/dist/esm/branch-selector.js +218 -0
  27. package/dist/esm/cli.d.ts +2 -0
  28. package/dist/esm/cli.js +157 -0
  29. package/dist/esm/config.d.ts +3 -0
  30. package/dist/esm/config.js +78 -0
  31. package/dist/esm/formatters.d.ts +7 -0
  32. package/dist/esm/formatters.js +207 -0
  33. package/dist/esm/git-analyzer.d.ts +10 -0
  34. package/dist/esm/git-analyzer.js +335 -0
  35. package/dist/esm/i18n/index.d.ts +7 -0
  36. package/dist/esm/i18n/index.js +49 -0
  37. package/dist/esm/i18n/locales/en.json +72 -0
  38. package/dist/esm/i18n/locales/zh-CN.json +72 -0
  39. package/dist/esm/index.d.ts +6 -0
  40. package/dist/esm/index.js +6 -0
  41. package/dist/esm/stats-aggregator.d.ts +7 -0
  42. package/dist/esm/stats-aggregator.js +149 -0
  43. package/dist/esm/time-utils.d.ts +6 -0
  44. package/dist/esm/time-utils.js +31 -0
  45. package/dist/esm/types.d.ts +77 -0
  46. package/dist/esm/types.js +1 -0
  47. package/package.json +57 -0
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/cli.ts
26
+ var import_commander = require("commander");
27
+ var import_chalk = __toESM(require("chalk"));
28
+ var import_config = require("./config.js");
29
+ var import_git_analyzer = require("./git-analyzer.js");
30
+ var import_stats_aggregator = require("./stats-aggregator.js");
31
+ var import_formatters = require("./formatters.js");
32
+ var import_time_utils = require("./time-utils.js");
33
+ var import_branch_selector = require("./branch-selector.js");
34
+ var import_i18n = require("./i18n/index.js");
35
+ var program = new import_commander.Command();
36
+ program.name("openspec-stat").description("Track team members' OpenSpec proposals and code changes in Git repositories").version("0.0.1").option("-r, --repo <path>", "Repository path", ".").option("-b, --branches <branches>", "Branch list, comma-separated").option("--no-interactive", "Disable interactive branch selection").option("-s, --since <datetime>", "Start time (default: yesterday 20:00)").option("-u, --until <datetime>", "End time (default: today 20:00)").option("-a, --author <name>", "Filter by specific author").option("--json", "Output in JSON format").option("--csv", "Output in CSV format").option("--markdown", "Output in Markdown format").option("-c, --config <path>", "Configuration file path").option("-v, --verbose", "Verbose output mode").option("-l, --lang <language>", "Language for output (en, zh-CN)", "en").action(async (options) => {
37
+ try {
38
+ (0, import_i18n.initI18n)(options.lang);
39
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.config")));
40
+ const config = await (0, import_config.loadConfig)(options.config, options.repo);
41
+ let since;
42
+ let until;
43
+ if (options.since || options.until) {
44
+ since = options.since ? (0, import_time_utils.parseDateTime)(options.since) : (0, import_time_utils.getDefaultTimeRange)(
45
+ config.defaultSinceHours,
46
+ config.defaultUntilHours
47
+ ).since;
48
+ until = options.until ? (0, import_time_utils.parseDateTime)(options.until) : (0, import_time_utils.getDefaultTimeRange)(
49
+ config.defaultSinceHours,
50
+ config.defaultUntilHours
51
+ ).until;
52
+ } else {
53
+ const defaultRange = (0, import_time_utils.getDefaultTimeRange)(
54
+ config.defaultSinceHours,
55
+ config.defaultUntilHours
56
+ );
57
+ since = defaultRange.since;
58
+ until = defaultRange.until;
59
+ }
60
+ let branches;
61
+ if (options.branches) {
62
+ branches = (0, import_time_utils.parseBranches)(options.branches);
63
+ } else if (options.interactive !== false) {
64
+ branches = await (0, import_branch_selector.selectBranches)(options.repo, config.defaultBranches);
65
+ } else {
66
+ branches = config.defaultBranches || [];
67
+ }
68
+ console.log(
69
+ import_chalk.default.blue(
70
+ (0, import_i18n.t)("info.timeRange", {
71
+ since: since.toLocaleString(),
72
+ until: until.toLocaleString()
73
+ })
74
+ )
75
+ );
76
+ console.log(import_chalk.default.blue((0, import_i18n.t)("info.branches", {
77
+ branches: branches.join(", ") || (0, import_i18n.t)("info.allBranches")
78
+ })));
79
+ const analyzer = new import_git_analyzer.GitAnalyzer(options.repo, config);
80
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.activeUsers")));
81
+ const activeAuthors = await analyzer.getActiveAuthors(
82
+ config.activeUserWeeks || 2
83
+ );
84
+ if (options.verbose) {
85
+ console.log(
86
+ import_chalk.default.gray(
87
+ (0, import_i18n.t)("info.activeUsers", {
88
+ weeks: String(config.activeUserWeeks || 2),
89
+ users: Array.from(activeAuthors).join(", ")
90
+ })
91
+ )
92
+ );
93
+ }
94
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.analyzing")));
95
+ const commits = await analyzer.getCommits(since, until, branches);
96
+ if (commits.length === 0) {
97
+ console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noCommits")));
98
+ return;
99
+ }
100
+ console.log(import_chalk.default.blue((0, import_i18n.t)("info.foundCommits", { count: String(commits.length) })));
101
+ const analyses = [];
102
+ for (let i = 0; i < commits.length; i++) {
103
+ const commit = commits[i];
104
+ if (options.verbose && i % 10 === 0) {
105
+ console.log(
106
+ import_chalk.default.gray((0, import_i18n.t)("info.analysisProgress", {
107
+ current: String(i + 1),
108
+ total: String(commits.length)
109
+ }))
110
+ );
111
+ }
112
+ const analysis = await analyzer.analyzeCommit(commit);
113
+ if (analysis) {
114
+ analyses.push(analysis);
115
+ }
116
+ }
117
+ if (analyses.length === 0) {
118
+ console.log(
119
+ import_chalk.default.yellow((0, import_i18n.t)("warning.noQualifyingCommits"))
120
+ );
121
+ return;
122
+ }
123
+ console.log(
124
+ import_chalk.default.blue(
125
+ (0, import_i18n.t)("info.qualifyingCommits", { count: String(analyses.length) })
126
+ )
127
+ );
128
+ const aggregator = new import_stats_aggregator.StatsAggregator(config, activeAuthors);
129
+ const result = aggregator.aggregate(
130
+ analyses,
131
+ since,
132
+ until,
133
+ branches,
134
+ options.author
135
+ );
136
+ const formatter = new import_formatters.OutputFormatter();
137
+ if (options.json) {
138
+ console.log(formatter.formatJSON(result));
139
+ } else if (options.csv) {
140
+ console.log(formatter.formatCSV(result));
141
+ } else if (options.markdown) {
142
+ console.log(formatter.formatMarkdown(result));
143
+ } else {
144
+ console.log(formatter.formatTable(result, options.verbose));
145
+ }
146
+ } catch (error) {
147
+ console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
148
+ process.exit(1);
149
+ }
150
+ });
151
+ program.parse();
@@ -0,0 +1,3 @@
1
+ import { Config } from './types.js';
2
+ export declare function loadConfig(configPath?: string, repoPath?: string): Promise<Config>;
3
+ export declare function normalizeAuthor(author: string, mapping?: Record<string, string>): string;
@@ -0,0 +1,66 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/config.ts
20
+ var config_exports = {};
21
+ __export(config_exports, {
22
+ loadConfig: () => loadConfig,
23
+ normalizeAuthor: () => normalizeAuthor
24
+ });
25
+ module.exports = __toCommonJS(config_exports);
26
+ var import_fs = require("fs");
27
+ var import_path = require("path");
28
+ var DEFAULT_CONFIG = {
29
+ defaultBranches: ["origin/master"],
30
+ defaultSinceHours: -30,
31
+ defaultUntilHours: 20,
32
+ authorMapping: {},
33
+ openspecDir: "openspec/",
34
+ excludeExtensions: [".md", ".txt", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp"],
35
+ activeUserWeeks: 2
36
+ };
37
+ async function loadConfig(configPath, repoPath = ".") {
38
+ let config = { ...DEFAULT_CONFIG };
39
+ const searchPaths = configPath ? [configPath] : [
40
+ (0, import_path.resolve)(repoPath, ".openspec-stats.json"),
41
+ (0, import_path.resolve)(repoPath, "openspec-stats.config.json"),
42
+ (0, import_path.resolve)(process.cwd(), ".openspec-stats.json"),
43
+ (0, import_path.resolve)(process.cwd(), "openspec-stats.config.json")
44
+ ];
45
+ for (const path of searchPaths) {
46
+ if ((0, import_fs.existsSync)(path)) {
47
+ try {
48
+ const fileContent = (0, import_fs.readFileSync)(path, "utf-8");
49
+ const userConfig = JSON.parse(fileContent);
50
+ config = { ...config, ...userConfig };
51
+ break;
52
+ } catch (error) {
53
+ console.warn(`Failed to load config from ${path}:`, error);
54
+ }
55
+ }
56
+ }
57
+ return config;
58
+ }
59
+ function normalizeAuthor(author, mapping = {}) {
60
+ return mapping[author] || author;
61
+ }
62
+ // Annotate the CommonJS export names for ESM import in node:
63
+ 0 && (module.exports = {
64
+ loadConfig,
65
+ normalizeAuthor
66
+ });
@@ -0,0 +1,7 @@
1
+ import { StatsResult } from './types.js';
2
+ export declare class OutputFormatter {
3
+ formatTable(result: StatsResult, verbose?: boolean): string;
4
+ formatJSON(result: StatsResult): string;
5
+ formatCSV(result: StatsResult): string;
6
+ formatMarkdown(result: StatsResult): string;
7
+ }
@@ -0,0 +1,222 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/formatters.ts
30
+ var formatters_exports = {};
31
+ __export(formatters_exports, {
32
+ OutputFormatter: () => OutputFormatter
33
+ });
34
+ module.exports = __toCommonJS(formatters_exports);
35
+ var import_cli_table3 = __toESM(require("cli-table3"));
36
+ var import_chalk = __toESM(require("chalk"));
37
+ var import_i18n = require("./i18n/index.js");
38
+ var OutputFormatter = class {
39
+ formatTable(result, verbose = false) {
40
+ let output = "";
41
+ output += import_chalk.default.bold((0, import_i18n.t)("output.title"));
42
+ output += import_chalk.default.gray(
43
+ (0, import_i18n.t)("output.timeRange", {
44
+ since: result.timeRange.since.toLocaleString("zh-CN", { hour12: false }),
45
+ until: result.timeRange.until.toLocaleString("zh-CN", { hour12: false })
46
+ })
47
+ );
48
+ output += import_chalk.default.gray((0, import_i18n.t)("output.branches", { branches: result.branches.join(", ") }));
49
+ output += import_chalk.default.gray((0, import_i18n.t)("output.totalCommits", { count: String(result.totalCommits) }));
50
+ const sortedAuthors = Array.from(result.authors.values()).sort(
51
+ (a, b) => b.commits - a.commits
52
+ );
53
+ for (const stats of sortedAuthors) {
54
+ output += import_chalk.default.bold.cyan(`
55
+ ${stats.author}
56
+ `);
57
+ if (stats.branchStats && stats.branchStats.size > 0) {
58
+ const branchTable = new import_cli_table3.default({
59
+ head: [
60
+ import_chalk.default.cyan((0, import_i18n.t)("table.branch")),
61
+ import_chalk.default.cyan((0, import_i18n.t)("table.commits")),
62
+ import_chalk.default.cyan((0, import_i18n.t)("table.proposals")),
63
+ import_chalk.default.cyan((0, import_i18n.t)("table.codeFiles")),
64
+ import_chalk.default.cyan((0, import_i18n.t)("table.additions")),
65
+ import_chalk.default.cyan((0, import_i18n.t)("table.deletions")),
66
+ import_chalk.default.cyan((0, import_i18n.t)("table.netChanges"))
67
+ ],
68
+ style: {
69
+ head: [],
70
+ border: []
71
+ }
72
+ });
73
+ const sortedBranches = Array.from(stats.branchStats.values()).sort(
74
+ (a, b) => b.commits - a.commits
75
+ );
76
+ for (const branchStat of sortedBranches) {
77
+ branchTable.push([
78
+ branchStat.branch,
79
+ branchStat.commits.toString(),
80
+ branchStat.openspecProposals.size.toString(),
81
+ branchStat.codeFilesChanged.toString(),
82
+ import_chalk.default.green(`+${branchStat.additions}`),
83
+ import_chalk.default.red(`-${branchStat.deletions}`),
84
+ branchStat.netChanges >= 0 ? import_chalk.default.green(`+${branchStat.netChanges}`) : import_chalk.default.red(`${branchStat.netChanges}`)
85
+ ]);
86
+ }
87
+ branchTable.push([
88
+ import_chalk.default.bold.yellow((0, import_i18n.t)("table.totalDeduplicated")),
89
+ import_chalk.default.bold(stats.commits.toString()),
90
+ import_chalk.default.bold(stats.openspecProposals.size.toString()),
91
+ import_chalk.default.bold(stats.codeFilesChanged.toString()),
92
+ import_chalk.default.bold.green(`+${stats.additions}`),
93
+ import_chalk.default.bold.red(`-${stats.deletions}`),
94
+ stats.netChanges >= 0 ? import_chalk.default.bold.green(`+${stats.netChanges}`) : import_chalk.default.bold.red(`${stats.netChanges}`)
95
+ ]);
96
+ output += branchTable.toString() + "\n";
97
+ } else {
98
+ const simpleTable = new import_cli_table3.default({
99
+ head: [
100
+ import_chalk.default.cyan((0, import_i18n.t)("table.period")),
101
+ import_chalk.default.cyan((0, import_i18n.t)("table.commits")),
102
+ import_chalk.default.cyan((0, import_i18n.t)("table.proposals")),
103
+ import_chalk.default.cyan((0, import_i18n.t)("table.codeFiles")),
104
+ import_chalk.default.cyan((0, import_i18n.t)("table.additions")),
105
+ import_chalk.default.cyan((0, import_i18n.t)("table.deletions")),
106
+ import_chalk.default.cyan((0, import_i18n.t)("table.netChanges"))
107
+ ],
108
+ style: {
109
+ head: [],
110
+ border: []
111
+ }
112
+ });
113
+ simpleTable.push([
114
+ stats.statisticsPeriod || "-",
115
+ stats.commits.toString(),
116
+ stats.openspecProposals.size.toString(),
117
+ stats.codeFilesChanged.toString(),
118
+ import_chalk.default.green(`+${stats.additions}`),
119
+ import_chalk.default.red(`-${stats.deletions}`),
120
+ stats.netChanges >= 0 ? import_chalk.default.green(`+${stats.netChanges}`) : import_chalk.default.red(`${stats.netChanges}`)
121
+ ]);
122
+ output += simpleTable.toString() + "\n";
123
+ }
124
+ if (verbose && stats.openspecProposals.size > 0) {
125
+ output += import_chalk.default.gray((0, import_i18n.t)("output.proposals", { proposals: Array.from(stats.openspecProposals).join(", ") }));
126
+ }
127
+ }
128
+ return output;
129
+ }
130
+ formatJSON(result) {
131
+ const data = {
132
+ timeRange: {
133
+ since: result.timeRange.since.toISOString(),
134
+ until: result.timeRange.until.toISOString()
135
+ },
136
+ branches: result.branches,
137
+ totalCommits: result.totalCommits,
138
+ authors: Array.from(result.authors.values()).map((stats) => {
139
+ var _a;
140
+ return {
141
+ author: stats.author,
142
+ commits: stats.commits,
143
+ openspecProposals: Array.from(stats.openspecProposals),
144
+ proposalCount: stats.openspecProposals.size,
145
+ codeFilesChanged: stats.codeFilesChanged,
146
+ additions: stats.additions,
147
+ deletions: stats.deletions,
148
+ netChanges: stats.netChanges,
149
+ lastCommitDate: (_a = stats.lastCommitDate) == null ? void 0 : _a.toISOString()
150
+ };
151
+ })
152
+ };
153
+ return JSON.stringify(data, null, 2);
154
+ }
155
+ formatCSV(result) {
156
+ var _a;
157
+ const rows = [];
158
+ rows.push(
159
+ `${(0, import_i18n.t)("table.author")},${(0, import_i18n.t)("table.period")},${(0, import_i18n.t)("table.commits")},${(0, import_i18n.t)("table.proposalsCount")},${(0, import_i18n.t)("table.proposalsList")},${(0, import_i18n.t)("table.codeFiles")},${(0, import_i18n.t)("table.additions")},${(0, import_i18n.t)("table.deletions")},${(0, import_i18n.t)("table.netChanges")},${(0, import_i18n.t)("table.lastCommitDate")}`
160
+ );
161
+ const sortedAuthors = Array.from(result.authors.values()).sort(
162
+ (a, b) => b.commits - a.commits
163
+ );
164
+ for (const stats of sortedAuthors) {
165
+ const proposals = Array.from(stats.openspecProposals).join(";");
166
+ rows.push(
167
+ [
168
+ stats.author,
169
+ stats.statisticsPeriod || "-",
170
+ stats.commits,
171
+ stats.openspecProposals.size,
172
+ `"${proposals}"`,
173
+ stats.codeFilesChanged,
174
+ stats.additions,
175
+ stats.deletions,
176
+ stats.netChanges,
177
+ ((_a = stats.lastCommitDate) == null ? void 0 : _a.toISOString()) || ""
178
+ ].join(",")
179
+ );
180
+ }
181
+ return rows.join("\n");
182
+ }
183
+ formatMarkdown(result) {
184
+ let md = "";
185
+ md += (0, import_i18n.t)("markdown.title");
186
+ md += (0, import_i18n.t)("markdown.timeRange", {
187
+ since: result.timeRange.since.toLocaleString("zh-CN", { hour12: false }),
188
+ until: result.timeRange.until.toLocaleString("zh-CN", { hour12: false })
189
+ });
190
+ md += (0, import_i18n.t)("markdown.branches", { branches: result.branches.join(", ") });
191
+ md += (0, import_i18n.t)("markdown.totalCommits", { count: String(result.totalCommits) });
192
+ md += (0, import_i18n.t)("markdown.statistics");
193
+ md += `| ${(0, import_i18n.t)("table.author")} | ${(0, import_i18n.t)("table.period")} | ${(0, import_i18n.t)("table.commits")} | ${(0, import_i18n.t)("table.proposals")} | ${(0, import_i18n.t)("table.codeFiles")} | ${(0, import_i18n.t)("table.additions")} | ${(0, import_i18n.t)("table.deletions")} | ${(0, import_i18n.t)("table.netChanges")} |
194
+ `;
195
+ md += "|--------|--------|---------|-----------|------------|-----------|-----------|-------------|\n";
196
+ const sortedAuthors = Array.from(result.authors.values()).sort(
197
+ (a, b) => b.commits - a.commits
198
+ );
199
+ for (const stats of sortedAuthors) {
200
+ md += `| ${stats.author} | ${stats.statisticsPeriod || "-"} | ${stats.commits} | ${stats.openspecProposals.size} | ${stats.codeFilesChanged} | +${stats.additions} | -${stats.deletions} | ${stats.netChanges >= 0 ? "+" : ""}${stats.netChanges} |
201
+ `;
202
+ }
203
+ md += (0, import_i18n.t)("markdown.proposalDetails");
204
+ for (const stats of sortedAuthors) {
205
+ if (stats.openspecProposals.size > 0) {
206
+ md += `### ${stats.author}
207
+
208
+ `;
209
+ for (const proposal of Array.from(stats.openspecProposals)) {
210
+ md += `- ${proposal}
211
+ `;
212
+ }
213
+ md += "\n";
214
+ }
215
+ }
216
+ return md;
217
+ }
218
+ };
219
+ // Annotate the CommonJS export names for ESM import in node:
220
+ 0 && (module.exports = {
221
+ OutputFormatter
222
+ });
@@ -0,0 +1,10 @@
1
+ import { CommitInfo, CommitAnalysis, Config } from './types.js';
2
+ export declare class GitAnalyzer {
3
+ private git;
4
+ private config;
5
+ constructor(repoPath: string, config: Config);
6
+ getCommits(since: Date, until: Date, branches: string[]): Promise<CommitInfo[]>;
7
+ getCommitBranches(commitHash: string, targetBranches: string[]): Promise<string[]>;
8
+ analyzeCommit(commit: CommitInfo): Promise<CommitAnalysis | null>;
9
+ getActiveAuthors(weeks?: number): Promise<Set<string>>;
10
+ }
@@ -0,0 +1,170 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/git-analyzer.ts
30
+ var git_analyzer_exports = {};
31
+ __export(git_analyzer_exports, {
32
+ GitAnalyzer: () => GitAnalyzer
33
+ });
34
+ module.exports = __toCommonJS(git_analyzer_exports);
35
+ var import_simple_git = __toESM(require("simple-git"));
36
+ var import_config = require("./config.js");
37
+ var GitAnalyzer = class {
38
+ constructor(repoPath, config) {
39
+ this.git = (0, import_simple_git.default)(repoPath);
40
+ this.config = config;
41
+ }
42
+ async getCommits(since, until, branches) {
43
+ const sinceStr = since.toISOString();
44
+ const untilStr = until.toISOString();
45
+ const logOptions = {
46
+ "--since": sinceStr,
47
+ "--until": untilStr,
48
+ "--all": null
49
+ };
50
+ if (branches.length > 0) {
51
+ for (const branch of branches) {
52
+ logOptions[`--remotes=${branch}`] = null;
53
+ }
54
+ }
55
+ const log = await this.git.log(logOptions);
56
+ const commits = [];
57
+ for (const commit of log.all) {
58
+ const commitBranches = await this.getCommitBranches(commit.hash, branches);
59
+ commits.push({
60
+ hash: commit.hash,
61
+ author: commit.author_name,
62
+ email: commit.author_email,
63
+ date: new Date(commit.date),
64
+ message: commit.message,
65
+ branches: commitBranches
66
+ });
67
+ }
68
+ return commits;
69
+ }
70
+ async getCommitBranches(commitHash, targetBranches) {
71
+ try {
72
+ const result = await this.git.raw(["branch", "-r", "--contains", commitHash]);
73
+ const allBranches = result.split("\n").map((b) => b.trim()).filter((b) => b && !b.includes("HEAD"));
74
+ if (targetBranches.length === 0) {
75
+ return allBranches;
76
+ }
77
+ return allBranches.filter(
78
+ (branch) => targetBranches.some((target) => branch === target || branch.includes(target))
79
+ );
80
+ } catch (error) {
81
+ return [];
82
+ }
83
+ }
84
+ async analyzeCommit(commit) {
85
+ try {
86
+ const show = await this.git.show([
87
+ "--numstat",
88
+ "--format=",
89
+ commit.hash
90
+ ]);
91
+ const lines = show.split("\n").filter((line) => line.trim());
92
+ const fileChanges = [];
93
+ const openspecProposals = /* @__PURE__ */ new Set();
94
+ let hasCodeChanges = false;
95
+ const openspecDir = this.config.openspecDir || "openspec/";
96
+ const excludeExts = this.config.excludeExtensions || [];
97
+ for (const line of lines) {
98
+ const parts = line.split(/\s+/);
99
+ if (parts.length < 3)
100
+ continue;
101
+ const [addStr, delStr, ...pathParts] = parts;
102
+ const path = pathParts.join(" ");
103
+ const additions = addStr === "-" ? 0 : parseInt(addStr, 10);
104
+ const deletions = delStr === "-" ? 0 : parseInt(delStr, 10);
105
+ if (path.startsWith(openspecDir)) {
106
+ const proposalMatch = path.match(
107
+ new RegExp(`^${openspecDir}changes/([^/]+)`)
108
+ );
109
+ if (proposalMatch) {
110
+ openspecProposals.add(proposalMatch[1]);
111
+ }
112
+ }
113
+ const isExcluded = excludeExts.some((ext) => path.endsWith(ext));
114
+ const isInOpenspec = path.startsWith(openspecDir);
115
+ if (!isExcluded && !isInOpenspec) {
116
+ hasCodeChanges = true;
117
+ fileChanges.push({
118
+ path,
119
+ additions,
120
+ deletions,
121
+ status: "M"
122
+ });
123
+ }
124
+ }
125
+ if (openspecProposals.size > 0 && hasCodeChanges) {
126
+ const totalAdditions = fileChanges.reduce(
127
+ (sum, f) => sum + f.additions,
128
+ 0
129
+ );
130
+ const totalDeletions = fileChanges.reduce(
131
+ (sum, f) => sum + f.deletions,
132
+ 0
133
+ );
134
+ return {
135
+ commit,
136
+ openspecProposals,
137
+ codeFiles: fileChanges,
138
+ totalAdditions,
139
+ totalDeletions,
140
+ netChanges: totalAdditions - totalDeletions
141
+ };
142
+ }
143
+ return null;
144
+ } catch (error) {
145
+ console.error(`Error analyzing commit ${commit.hash}:`, error);
146
+ return null;
147
+ }
148
+ }
149
+ async getActiveAuthors(weeks = 2) {
150
+ const since = /* @__PURE__ */ new Date();
151
+ since.setDate(since.getDate() - weeks * 7);
152
+ const log = await this.git.log({
153
+ "--since": since.toISOString(),
154
+ "--all": null
155
+ });
156
+ const authors = /* @__PURE__ */ new Set();
157
+ for (const commit of log.all) {
158
+ const normalizedAuthor = (0, import_config.normalizeAuthor)(
159
+ commit.author_name,
160
+ this.config.authorMapping
161
+ );
162
+ authors.add(normalizedAuthor);
163
+ }
164
+ return authors;
165
+ }
166
+ };
167
+ // Annotate the CommonJS export names for ESM import in node:
168
+ 0 && (module.exports = {
169
+ GitAnalyzer
170
+ });
@@ -0,0 +1,7 @@
1
+ type Language = 'en' | 'zh-CN';
2
+ type TranslationKey = string;
3
+ export declare function setLanguage(lang: Language): void;
4
+ export declare function getLanguage(): Language;
5
+ export declare function t(key: TranslationKey, params?: Record<string, string | number>): string;
6
+ export declare function initI18n(lang?: string): void;
7
+ export {};