openspec-stat 1.2.0 → 1.3.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 (43) hide show
  1. package/README.md +28 -0
  2. package/README.zh-CN.md +28 -0
  3. package/dist/cjs/cli.js +12 -127
  4. package/dist/cjs/commands/init.d.ts +7 -0
  5. package/dist/cjs/commands/init.js +58 -0
  6. package/dist/cjs/commands/multi.d.ts +16 -0
  7. package/dist/cjs/commands/multi.js +172 -0
  8. package/dist/cjs/commands/single.d.ts +2 -0
  9. package/dist/cjs/commands/single.js +148 -0
  10. package/dist/cjs/formatters.d.ts +4 -4
  11. package/dist/cjs/formatters.js +128 -36
  12. package/dist/cjs/git-analyzer.d.ts +1 -0
  13. package/dist/cjs/git-analyzer.js +6 -0
  14. package/dist/cjs/i18n/locales/en.json +74 -1
  15. package/dist/cjs/i18n/locales/zh-CN.json +74 -1
  16. package/dist/cjs/multi/config-validator.d.ts +3 -0
  17. package/dist/cjs/multi/config-validator.js +130 -0
  18. package/dist/cjs/multi/config-wizard.d.ts +50 -0
  19. package/dist/cjs/multi/config-wizard.js +331 -0
  20. package/dist/cjs/multi/multi-repo-analyzer.d.ts +14 -0
  21. package/dist/cjs/multi/multi-repo-analyzer.js +210 -0
  22. package/dist/cjs/types.d.ts +46 -0
  23. package/dist/esm/cli.js +54 -139
  24. package/dist/esm/commands/init.d.ts +7 -0
  25. package/dist/esm/commands/init.js +49 -0
  26. package/dist/esm/commands/multi.d.ts +16 -0
  27. package/dist/esm/commands/multi.js +192 -0
  28. package/dist/esm/commands/single.d.ts +2 -0
  29. package/dist/esm/commands/single.js +162 -0
  30. package/dist/esm/formatters.d.ts +4 -4
  31. package/dist/esm/formatters.js +173 -52
  32. package/dist/esm/git-analyzer.d.ts +1 -0
  33. package/dist/esm/git-analyzer.js +104 -77
  34. package/dist/esm/i18n/locales/en.json +74 -1
  35. package/dist/esm/i18n/locales/zh-CN.json +74 -1
  36. package/dist/esm/multi/config-validator.d.ts +3 -0
  37. package/dist/esm/multi/config-validator.js +109 -0
  38. package/dist/esm/multi/config-wizard.d.ts +50 -0
  39. package/dist/esm/multi/config-wizard.js +535 -0
  40. package/dist/esm/multi/multi-repo-analyzer.d.ts +14 -0
  41. package/dist/esm/multi/multi-repo-analyzer.js +446 -0
  42. package/dist/esm/types.d.ts +46 -0
  43. package/package.json +1 -1
package/README.md CHANGED
@@ -18,6 +18,7 @@ A CLI tool for tracking team members' OpenSpec proposals and code changes in Git
18
18
  - ✅ Track only recently active members (default: 2 weeks)
19
19
  - ✅ Multiple output formats: Table, JSON, CSV, Markdown
20
20
  - ✅ Internationalization support: English and Chinese (简体中文)
21
+ - ✅ **🆕 Multi-repository mode (BETA)** - Analyze multiple local/remote repositories in one run
21
22
 
22
23
  ## Installation
23
24
 
@@ -49,6 +50,33 @@ openspec-stat
49
50
 
50
51
  This will track commits in the default time range (yesterday 20:00 ~ today 20:00).
51
52
 
53
+ ### Multi-Repository Mode (BETA)
54
+
55
+ ⚠️ **Experimental Feature**: Multi-repository mode allows analyzing multiple repositories (local or remote) in a single run.
56
+
57
+ ```bash
58
+ # Initialize multi-repo configuration (interactive wizard)
59
+ openspec-stat init --multi
60
+
61
+ # Run multi-repo analysis
62
+ openspec-stat multi -c .openspec-stats.multi.json
63
+
64
+ # Run with detailed contributor statistics
65
+ openspec-stat multi -c .openspec-stats.multi.json --show-contributors
66
+
67
+ # Generate template
68
+ openspec-stat init --template multi -o config.json
69
+ ```
70
+
71
+ **Perfect for team managers who:**
72
+ - Have local access to backend repos but not frontend repos
73
+ - Need to track contributions across multiple repositories
74
+ - Want combined statistics without running multiple commands
75
+
76
+ **Note**: By default, multi-repo mode only shows aggregated statistics to avoid information overload. Use `--show-contributors` to see detailed statistics for each contributor.
77
+
78
+ See [Multi-Repository Guide](./MULTI_REPO_GUIDE.md) for detailed documentation.
79
+
52
80
  ### Command Line Options
53
81
 
54
82
  ```bash
package/README.zh-CN.md CHANGED
@@ -18,6 +18,7 @@
18
18
  - ✅ 仅追踪最近活跃的成员(默认:2 周)
19
19
  - ✅ 多种输出格式:表格、JSON、CSV、Markdown
20
20
  - ✅ 国际化支持:英文和中文
21
+ - ✅ **🆕 多仓库模式(BETA)** - 一次运行分析多个本地/远程仓库
21
22
 
22
23
  ## 安装
23
24
 
@@ -49,6 +50,33 @@ openspec-stat
49
50
 
50
51
  这将追踪默认时间范围内的提交(昨天 20:00 ~ 今天 20:00)。
51
52
 
53
+ ### 多仓库模式(BETA)
54
+
55
+ ⚠️ **实验性功能**:多仓库模式允许在单次运行中分析多个仓库(本地或远程)。
56
+
57
+ ```bash
58
+ # 初始化多仓库配置(交互式向导)
59
+ openspec-stat init --multi
60
+
61
+ # 运行多仓库分析
62
+ openspec-stat multi -c .openspec-stats.multi.json
63
+
64
+ # 运行并显示详细的贡献者统计
65
+ openspec-stat multi -c .openspec-stats.multi.json --show-contributors
66
+
67
+ # 生成配置模板
68
+ openspec-stat init --template multi -o config.json
69
+ ```
70
+
71
+ **适用于以下场景的团队管理者:**
72
+ - 拥有后端仓库的本地访问权限,但没有前端仓库
73
+ - 需要跨多个仓库追踪贡献情况
74
+ - 希望获得合并统计结果,而无需运行多个命令
75
+
76
+ **注意**:默认情况下,多仓库模式仅显示聚合统计信息,以避免信息过载。使用 `--show-contributors` 可查看每个贡献者的详细统计信息。
77
+
78
+ 详细文档请参阅 [多仓库模式指南](./MULTI_REPO_GUIDE.md)。
79
+
52
80
  ### 命令行选项
53
81
 
54
82
  ```bash
package/dist/cjs/cli.js CHANGED
@@ -1,134 +1,19 @@
1
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
2
 
25
3
  // src/cli.ts
26
4
  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");
5
+ var import_single = require("./commands/single.js");
6
+ var import_multi = require("./commands/multi.js");
7
+ var import_init = require("./commands/init.js");
35
8
  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)(config.defaultSinceHours, config.defaultUntilHours).since;
45
- until = options.until ? (0, import_time_utils.parseDateTime)(options.until) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).until;
46
- } else {
47
- const defaultRange = (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours);
48
- since = defaultRange.since;
49
- until = defaultRange.until;
50
- }
51
- let branches;
52
- if (options.branches) {
53
- branches = (0, import_time_utils.parseBranches)(options.branches);
54
- } else if (options.interactive !== false) {
55
- branches = await (0, import_branch_selector.selectBranches)(options.repo, config.defaultBranches);
56
- } else {
57
- branches = config.defaultBranches || [];
58
- }
59
- console.log(
60
- import_chalk.default.blue(
61
- (0, import_i18n.t)("info.timeRange", {
62
- since: since.toLocaleString(),
63
- until: until.toLocaleString()
64
- })
65
- )
66
- );
67
- console.log(
68
- import_chalk.default.blue(
69
- (0, import_i18n.t)("info.branches", {
70
- branches: branches.join(", ") || (0, import_i18n.t)("info.allBranches")
71
- })
72
- )
73
- );
74
- const analyzer = new import_git_analyzer.GitAnalyzer(options.repo, config);
75
- console.log(import_chalk.default.blue((0, import_i18n.t)("loading.activeUsers")));
76
- const activeAuthors = await analyzer.getActiveAuthors(config.activeUserWeeks || 2);
77
- if (options.verbose) {
78
- console.log(
79
- import_chalk.default.gray(
80
- (0, import_i18n.t)("info.activeUsers", {
81
- weeks: String(config.activeUserWeeks || 2),
82
- users: Array.from(activeAuthors).join(", ")
83
- })
84
- )
85
- );
86
- }
87
- console.log(import_chalk.default.blue((0, import_i18n.t)("loading.analyzing")));
88
- const commits = await analyzer.getCommits(since, until, branches);
89
- if (commits.length === 0) {
90
- console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noCommits")));
91
- return;
92
- }
93
- console.log(import_chalk.default.blue((0, import_i18n.t)("info.foundCommits", { count: String(commits.length) })));
94
- const analyses = [];
95
- for (let i = 0; i < commits.length; i++) {
96
- const commit = commits[i];
97
- if (options.verbose && i % 10 === 0) {
98
- console.log(
99
- import_chalk.default.gray(
100
- (0, import_i18n.t)("info.analysisProgress", {
101
- current: String(i + 1),
102
- total: String(commits.length)
103
- })
104
- )
105
- );
106
- }
107
- const analysis = await analyzer.analyzeCommit(commit);
108
- if (analysis) {
109
- analyses.push(analysis);
110
- }
111
- }
112
- if (analyses.length === 0) {
113
- console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noQualifyingCommits")));
114
- return;
115
- }
116
- console.log(import_chalk.default.blue((0, import_i18n.t)("info.qualifyingCommits", { count: String(analyses.length) })));
117
- const aggregator = new import_stats_aggregator.StatsAggregator(config, activeAuthors);
118
- const result = aggregator.aggregate(analyses, since, until, branches, options.author);
119
- const formatter = new import_formatters.OutputFormatter();
120
- if (options.json) {
121
- console.log(formatter.formatJSON(result));
122
- } else if (options.csv) {
123
- console.log(formatter.formatCSV(result));
124
- } else if (options.markdown) {
125
- console.log(formatter.formatMarkdown(result));
126
- } else {
127
- console.log(formatter.formatTable(result, options.verbose));
128
- }
129
- } catch (error) {
130
- console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
131
- process.exit(1);
132
- }
9
+ program.name("openspec-stat").description("Track team members' OpenSpec proposals and code changes in Git repositories").version("1.3.1").enablePositionalOptions().passThroughOptions();
10
+ program.argument("[repo]", "Repository path", ".").option("-r, --repo <path>", "Repository path (alternative)", ".").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").option("--no-fetch", "Skip fetching remote branches").action(async (repo, options) => {
11
+ await (0, import_single.runSingleRepoCommand)({ ...options, repo: repo || options.repo || "." });
12
+ });
13
+ program.command("multi").description("Multi-repository analysis mode (BETA)").option("-c, --config <path>", "Configuration file path", ".openspec-stats.multi.json").option("-s, --since <datetime>", "Override start time").option("-u, --until <datetime>", "Override end time").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("-v, --verbose", "Verbose output mode").option("-l, --lang <language>", "Language (en, zh-CN)", "en").option("--no-cleanup", "Do not cleanup temporary directories").option("--show-contributors", "Show detailed contributor statistics (default: only show summary)").option("--no-fetch", "Skip fetching remote branches for local repositories").action(async (options) => {
14
+ await (0, import_multi.runMultiRepoCommand)(options);
15
+ });
16
+ program.command("init").description("Initialize configuration file").option("--multi", "Create multi-repository configuration (interactive)").option("--template <type>", "Generate template (single|multi)").option("-o, --output <path>", "Output file path").action(async (options) => {
17
+ await (0, import_init.runInitCommand)(options);
133
18
  });
134
19
  program.parse();
@@ -0,0 +1,7 @@
1
+ interface InitOptions {
2
+ multi?: boolean;
3
+ template?: 'single' | 'multi';
4
+ output?: string;
5
+ }
6
+ export declare function runInitCommand(options: InitOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,58 @@
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/commands/init.ts
30
+ var init_exports = {};
31
+ __export(init_exports, {
32
+ runInitCommand: () => runInitCommand
33
+ });
34
+ module.exports = __toCommonJS(init_exports);
35
+ var import_chalk = __toESM(require("chalk"));
36
+ var import_fs = require("fs");
37
+ var import_i18n = require("../i18n/index.js");
38
+ var import_config_wizard = require("../multi/config-wizard.js");
39
+ async function runInitCommand(options) {
40
+ try {
41
+ if (options.template) {
42
+ const template = options.template === "multi" ? import_config_wizard.MULTI_REPO_TEMPLATE : import_config_wizard.SINGLE_REPO_TEMPLATE;
43
+ const outputPath = options.output || `.openspec-stats.${options.template}.json`;
44
+ (0, import_fs.writeFileSync)(outputPath, JSON.stringify(template, null, 2));
45
+ console.log(import_chalk.default.green((0, import_i18n.t)("init.templateCreated", { path: outputPath })));
46
+ console.log(import_chalk.default.gray((0, import_i18n.t)("init.templateEdit")));
47
+ return;
48
+ }
49
+ await (0, import_config_wizard.runConfigWizard)(options.multi || false);
50
+ } catch (error) {
51
+ console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
52
+ process.exit(1);
53
+ }
54
+ }
55
+ // Annotate the CommonJS export names for ESM import in node:
56
+ 0 && (module.exports = {
57
+ runInitCommand
58
+ });
@@ -0,0 +1,16 @@
1
+ interface MultiCommandOptions {
2
+ config: string;
3
+ since?: string;
4
+ until?: string;
5
+ author?: string;
6
+ json?: boolean;
7
+ csv?: boolean;
8
+ markdown?: boolean;
9
+ verbose?: boolean;
10
+ lang?: string;
11
+ cleanup?: boolean;
12
+ showContributors?: boolean;
13
+ noFetch?: boolean;
14
+ }
15
+ export declare function runMultiRepoCommand(options: MultiCommandOptions): Promise<void>;
16
+ export {};
@@ -0,0 +1,172 @@
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/commands/multi.ts
30
+ var multi_exports = {};
31
+ __export(multi_exports, {
32
+ runMultiRepoCommand: () => runMultiRepoCommand
33
+ });
34
+ module.exports = __toCommonJS(multi_exports);
35
+ var import_chalk = __toESM(require("chalk"));
36
+ var import_fs = require("fs");
37
+ var import_path = require("path");
38
+ var import_i18n = require("../i18n/index.js");
39
+ var import_multi_repo_analyzer = require("../multi/multi-repo-analyzer.js");
40
+ var import_config_validator = require("../multi/config-validator.js");
41
+ var import_stats_aggregator = require("../stats-aggregator.js");
42
+ var import_formatters = require("../formatters.js");
43
+ var import_time_utils = require("../time-utils.js");
44
+ async function runMultiRepoCommand(options) {
45
+ try {
46
+ (0, import_i18n.initI18n)(options.lang || "en");
47
+ console.log(import_chalk.default.yellow.bold((0, import_i18n.t)("multi.beta.warning")));
48
+ console.log(import_chalk.default.gray((0, import_i18n.t)("multi.beta.feedback")));
49
+ console.log();
50
+ console.log(import_chalk.default.blue((0, import_i18n.t)("multi.loading.config")));
51
+ const configPath = (0, import_path.resolve)(process.cwd(), options.config);
52
+ if (!(0, import_fs.existsSync)(configPath)) {
53
+ throw new Error(`Configuration file not found: ${configPath}`);
54
+ }
55
+ const rawConfig = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
56
+ const config = (0, import_config_validator.validateAndFillDefaults)(rawConfig);
57
+ if (options.verbose) {
58
+ (0, import_config_validator.printConfigSummary)(config);
59
+ }
60
+ let since;
61
+ let until;
62
+ if (options.since || options.until) {
63
+ since = options.since ? (0, import_time_utils.parseDateTime)(options.since) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).since;
64
+ until = options.until ? (0, import_time_utils.parseDateTime)(options.until) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).until;
65
+ } else {
66
+ const defaultRange = (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours);
67
+ since = defaultRange.since;
68
+ until = defaultRange.until;
69
+ }
70
+ console.log(
71
+ import_chalk.default.blue(
72
+ (0, import_i18n.t)("info.timeRange", {
73
+ since: since.toLocaleString(),
74
+ until: until.toLocaleString()
75
+ })
76
+ )
77
+ );
78
+ const analyzer = new import_multi_repo_analyzer.MultiRepoAnalyzer(config);
79
+ analyzer.registerCleanupHandlers();
80
+ if (options.cleanup === false) {
81
+ config.remoteCache = config.remoteCache || {
82
+ dir: "/tmp/openspec-stat-cache",
83
+ autoCleanup: false,
84
+ cleanupOnComplete: false,
85
+ cleanupOnError: false
86
+ };
87
+ config.remoteCache.cleanupOnComplete = false;
88
+ }
89
+ if (options.noFetch) {
90
+ config.autoFetch = false;
91
+ }
92
+ const repoResults = await analyzer.analyzeAll(since, until);
93
+ const successResults = repoResults.filter((r) => r.success);
94
+ const failedResults = repoResults.filter((r) => !r.success);
95
+ console.log(
96
+ import_chalk.default.blue(
97
+ (0, import_i18n.t)("multi.summary.title") + (0, import_i18n.t)("multi.summary.repos", {
98
+ total: String(repoResults.length),
99
+ success: String(successResults.length),
100
+ failed: String(failedResults.length)
101
+ })
102
+ )
103
+ );
104
+ if (failedResults.length > 0) {
105
+ console.log(import_chalk.default.yellow("\nFailed repositories:"));
106
+ failedResults.forEach((r) => {
107
+ console.log(import_chalk.default.red(` - ${r.repository}: ${r.error}`));
108
+ });
109
+ console.log();
110
+ }
111
+ const allAnalyses = successResults.flatMap((r) => r.analyses);
112
+ if (allAnalyses.length === 0) {
113
+ console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noQualifyingCommits")));
114
+ return;
115
+ }
116
+ console.log(import_chalk.default.blue((0, import_i18n.t)("info.qualifyingCommits", { count: String(allAnalyses.length) })));
117
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.activeUsers")));
118
+ const activeAuthors = await getActiveAuthorsFromMultiRepo(config, repoResults);
119
+ if (options.verbose && activeAuthors.size > 0) {
120
+ console.log(
121
+ import_chalk.default.gray(
122
+ (0, import_i18n.t)("info.activeUsers", {
123
+ weeks: String(config.activeUserWeeks || 2),
124
+ users: Array.from(activeAuthors).join(", ")
125
+ })
126
+ )
127
+ );
128
+ }
129
+ const aggregator = new import_stats_aggregator.StatsAggregator(config, activeAuthors);
130
+ const allBranches = [
131
+ ...new Set(
132
+ repoResults.flatMap((r) => {
133
+ var _a, _b;
134
+ return ((_b = (_a = config.repositories) == null ? void 0 : _a.find((repo) => repo.name === r.repository)) == null ? void 0 : _b.branches) || [];
135
+ })
136
+ )
137
+ ];
138
+ const result = aggregator.aggregate(allAnalyses, since, until, allBranches, options.author);
139
+ const formatter = new import_formatters.OutputFormatter();
140
+ const showContributors = options.showContributors || false;
141
+ if (options.json) {
142
+ console.log(formatter.formatJSON(result, showContributors));
143
+ } else if (options.csv) {
144
+ console.log(formatter.formatCSV(result, showContributors));
145
+ } else if (options.markdown) {
146
+ console.log(formatter.formatMarkdown(result, showContributors));
147
+ } else {
148
+ console.log(formatter.formatTable(result, options.verbose, showContributors));
149
+ }
150
+ } catch (error) {
151
+ console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
152
+ process.exit(1);
153
+ }
154
+ }
155
+ async function getActiveAuthorsFromMultiRepo(config, repoResults) {
156
+ const allAuthors = /* @__PURE__ */ new Set();
157
+ for (const result of repoResults) {
158
+ if (result.success && result.analyses) {
159
+ result.analyses.forEach((analysis) => {
160
+ var _a;
161
+ if ((_a = analysis.commit) == null ? void 0 : _a.author) {
162
+ allAuthors.add(analysis.commit.author);
163
+ }
164
+ });
165
+ }
166
+ }
167
+ return allAuthors;
168
+ }
169
+ // Annotate the CommonJS export names for ESM import in node:
170
+ 0 && (module.exports = {
171
+ runMultiRepoCommand
172
+ });
@@ -0,0 +1,2 @@
1
+ import { CliOptions } from '../types.js';
2
+ export declare function runSingleRepoCommand(options: CliOptions): Promise<void>;
@@ -0,0 +1,148 @@
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/commands/single.ts
30
+ var single_exports = {};
31
+ __export(single_exports, {
32
+ runSingleRepoCommand: () => runSingleRepoCommand
33
+ });
34
+ module.exports = __toCommonJS(single_exports);
35
+ var import_chalk = __toESM(require("chalk"));
36
+ var import_config = require("../config.js");
37
+ var import_git_analyzer = require("../git-analyzer.js");
38
+ var import_stats_aggregator = require("../stats-aggregator.js");
39
+ var import_formatters = require("../formatters.js");
40
+ var import_time_utils = require("../time-utils.js");
41
+ var import_branch_selector = require("../branch-selector.js");
42
+ var import_i18n = require("../i18n/index.js");
43
+ async function runSingleRepoCommand(options) {
44
+ try {
45
+ (0, import_i18n.initI18n)(options.lang);
46
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.config")));
47
+ const config = await (0, import_config.loadConfig)(options.config, options.repo);
48
+ let since;
49
+ let until;
50
+ if (options.since || options.until) {
51
+ since = options.since ? (0, import_time_utils.parseDateTime)(options.since) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).since;
52
+ until = options.until ? (0, import_time_utils.parseDateTime)(options.until) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).until;
53
+ } else {
54
+ const defaultRange = (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours);
55
+ since = defaultRange.since;
56
+ until = defaultRange.until;
57
+ }
58
+ let branches;
59
+ if (options.branches) {
60
+ branches = (0, import_time_utils.parseBranches)(options.branches);
61
+ } else if (options.interactive !== false) {
62
+ branches = await (0, import_branch_selector.selectBranches)(options.repo, config.defaultBranches);
63
+ } else {
64
+ branches = config.defaultBranches || [];
65
+ }
66
+ console.log(
67
+ import_chalk.default.blue(
68
+ (0, import_i18n.t)("info.timeRange", {
69
+ since: since.toLocaleString(),
70
+ until: until.toLocaleString()
71
+ })
72
+ )
73
+ );
74
+ console.log(
75
+ import_chalk.default.blue(
76
+ (0, import_i18n.t)("info.branches", {
77
+ branches: branches.join(", ") || (0, import_i18n.t)("info.allBranches")
78
+ })
79
+ )
80
+ );
81
+ const analyzer = new import_git_analyzer.GitAnalyzer(options.repo, config);
82
+ if (!options.noFetch && config.autoFetch !== false) {
83
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.fetching")));
84
+ await analyzer.fetchRemote();
85
+ }
86
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.activeUsers")));
87
+ const activeAuthors = await analyzer.getActiveAuthors(config.activeUserWeeks || 2);
88
+ if (options.verbose) {
89
+ console.log(
90
+ import_chalk.default.gray(
91
+ (0, import_i18n.t)("info.activeUsers", {
92
+ weeks: String(config.activeUserWeeks || 2),
93
+ users: Array.from(activeAuthors).join(", ")
94
+ })
95
+ )
96
+ );
97
+ }
98
+ console.log(import_chalk.default.blue((0, import_i18n.t)("loading.analyzing")));
99
+ const commits = await analyzer.getCommits(since, until, branches);
100
+ if (commits.length === 0) {
101
+ console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noCommits")));
102
+ return;
103
+ }
104
+ console.log(import_chalk.default.blue((0, import_i18n.t)("info.foundCommits", { count: String(commits.length) })));
105
+ const analyses = [];
106
+ for (let i = 0; i < commits.length; i++) {
107
+ const commit = commits[i];
108
+ if (options.verbose && i % 10 === 0) {
109
+ console.log(
110
+ import_chalk.default.gray(
111
+ (0, import_i18n.t)("info.analysisProgress", {
112
+ current: String(i + 1),
113
+ total: String(commits.length)
114
+ })
115
+ )
116
+ );
117
+ }
118
+ const analysis = await analyzer.analyzeCommit(commit);
119
+ if (analysis) {
120
+ analyses.push(analysis);
121
+ }
122
+ }
123
+ if (analyses.length === 0) {
124
+ console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noQualifyingCommits")));
125
+ return;
126
+ }
127
+ console.log(import_chalk.default.blue((0, import_i18n.t)("info.qualifyingCommits", { count: String(analyses.length) })));
128
+ const aggregator = new import_stats_aggregator.StatsAggregator(config, activeAuthors);
129
+ const result = aggregator.aggregate(analyses, since, until, branches, options.author);
130
+ const formatter = new import_formatters.OutputFormatter();
131
+ if (options.json) {
132
+ console.log(formatter.formatJSON(result));
133
+ } else if (options.csv) {
134
+ console.log(formatter.formatCSV(result));
135
+ } else if (options.markdown) {
136
+ console.log(formatter.formatMarkdown(result));
137
+ } else {
138
+ console.log(formatter.formatTable(result, options.verbose));
139
+ }
140
+ } catch (error) {
141
+ console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
142
+ process.exit(1);
143
+ }
144
+ }
145
+ // Annotate the CommonJS export names for ESM import in node:
146
+ 0 && (module.exports = {
147
+ runSingleRepoCommand
148
+ });
@@ -1,7 +1,7 @@
1
1
  import { StatsResult } from './types.js';
2
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;
3
+ formatTable(result: StatsResult, verbose?: boolean, showContributors?: boolean): string;
4
+ formatJSON(result: StatsResult, showContributors?: boolean): string;
5
+ formatCSV(result: StatsResult, showContributors?: boolean): string;
6
+ formatMarkdown(result: StatsResult, showContributors?: boolean): string;
7
7
  }