openspec-stat 1.3.2 → 1.3.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.
- package/README.md +5 -0
- package/README.zh-CN.md +5 -0
- package/dist/cjs/branch-selector.js +5 -1
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/formatters.js +34 -4
- package/dist/cjs/i18n/locales/en.json +3 -0
- package/dist/cjs/i18n/locales/zh-CN.json +3 -0
- package/dist/cjs/stats-aggregator.js +13 -5
- package/dist/cjs/types.d.ts +2 -0
- package/dist/esm/branch-selector.js +9 -5
- package/dist/esm/cli.js +1 -1
- package/dist/esm/formatters.js +57 -16
- package/dist/esm/i18n/locales/en.json +3 -0
- package/dist/esm/i18n/locales/zh-CN.json +3 -0
- package/dist/esm/stats-aggregator.js +13 -5
- package/dist/esm/types.d.ts +2 -0
- package/package.json +17 -24
package/README.md
CHANGED
|
@@ -275,6 +275,11 @@ pnpm build
|
|
|
275
275
|
node dist/esm/cli.js
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
+
## Contributing & Release Process
|
|
279
|
+
|
|
280
|
+
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, branching, and pull-request expectations.
|
|
281
|
+
- See [RELEASE.md](./RELEASE.md) for the Changesets-driven versioning and publishing workflow.
|
|
282
|
+
|
|
278
283
|
## LICENSE
|
|
279
284
|
|
|
280
285
|
MIT
|
package/README.zh-CN.md
CHANGED
|
@@ -102,6 +102,10 @@ async function selectBranches(repoPath, defaultBranches) {
|
|
|
102
102
|
choices: choices.slice(0, -1),
|
|
103
103
|
pageSize: 15
|
|
104
104
|
});
|
|
105
|
+
if (selected.length === 0) {
|
|
106
|
+
console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noBranchesSelected")));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
105
109
|
if (selected.length > 0) {
|
|
106
110
|
console.log(import_chalk.default.green((0, import_i18n.t)("branch.selected")));
|
|
107
111
|
selected.forEach((branch) => {
|
|
@@ -115,7 +119,7 @@ async function selectBranches(repoPath, defaultBranches) {
|
|
|
115
119
|
const customBranches = customInput.split(",").map((b) => b.trim()).filter((b) => b);
|
|
116
120
|
return [...selected.filter((b) => b !== "__custom__"), ...customBranches];
|
|
117
121
|
}
|
|
118
|
-
return selected
|
|
122
|
+
return selected;
|
|
119
123
|
}
|
|
120
124
|
// Annotate the CommonJS export names for ESM import in node:
|
|
121
125
|
0 && (module.exports = {
|
package/dist/cjs/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ var import_single = require("./commands/single.js");
|
|
|
6
6
|
var import_multi = require("./commands/multi.js");
|
|
7
7
|
var import_init = require("./commands/init.js");
|
|
8
8
|
var program = new import_commander.Command();
|
|
9
|
-
program.name("openspec-stat").description("Track team members' OpenSpec proposals and code changes in Git repositories").version("1.3.
|
|
9
|
+
program.name("openspec-stat").description("Track team members' OpenSpec proposals and code changes in Git repositories").version("1.3.4").enablePositionalOptions().passThroughOptions();
|
|
10
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
11
|
await (0, import_single.runSingleRepoCommand)({ ...options, repo: repo || options.repo || "." });
|
|
12
12
|
});
|
package/dist/cjs/formatters.js
CHANGED
|
@@ -69,8 +69,9 @@ ${(0, import_i18n.t)("output.proposalSummary")}
|
|
|
69
69
|
const sortedProposals = Array.from(result.proposals.values()).sort((a, b) => b.netChanges - a.netChanges);
|
|
70
70
|
for (const proposalStats of sortedProposals) {
|
|
71
71
|
const contributors = Array.from(proposalStats.contributors).join(", ");
|
|
72
|
+
const proposalName = proposalStats.multiProposalCommits > 0 ? `${proposalStats.proposal} ${import_chalk.default.yellow("⚠")}` : proposalStats.proposal;
|
|
72
73
|
proposalTable.push([
|
|
73
|
-
|
|
74
|
+
proposalName,
|
|
74
75
|
proposalStats.commits.toString(),
|
|
75
76
|
contributors,
|
|
76
77
|
proposalStats.codeFilesChanged.toString(),
|
|
@@ -80,13 +81,24 @@ ${(0, import_i18n.t)("output.proposalSummary")}
|
|
|
80
81
|
]);
|
|
81
82
|
}
|
|
82
83
|
output += proposalTable.toString() + "\n";
|
|
84
|
+
const hasMultiProposalCommits = Array.from(result.proposals.values()).some((p) => p.multiProposalCommits > 0);
|
|
85
|
+
if (hasMultiProposalCommits) {
|
|
86
|
+
output += import_chalk.default.yellow(`
|
|
87
|
+
⚠ ${(0, import_i18n.t)("output.multiProposalWarning")}
|
|
88
|
+
`);
|
|
89
|
+
const affectedProposals = Array.from(result.proposals.values()).filter((p) => p.multiProposalCommits > 0).map(
|
|
90
|
+
(p) => ` • ${p.proposal}: ${p.multiProposalCommits}/${p.commits} commits ${(0, import_i18n.t)("output.sharedWithOthers")}`
|
|
91
|
+
).join("\n");
|
|
92
|
+
output += import_chalk.default.gray(affectedProposals + "\n");
|
|
93
|
+
}
|
|
83
94
|
const totalProposals = result.proposals.size;
|
|
84
95
|
const totalProposalCommits = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.commits, 0);
|
|
85
96
|
const totalProposalFiles = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.codeFilesChanged, 0);
|
|
86
97
|
const totalProposalAdditions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.additions, 0);
|
|
87
98
|
const totalProposalDeletions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.deletions, 0);
|
|
88
99
|
const totalProposalNetChanges = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.netChanges, 0);
|
|
89
|
-
output += import_chalk.default.
|
|
100
|
+
output += import_chalk.default.cyan("─".repeat(80)) + "\n";
|
|
101
|
+
output += import_chalk.default.bold.cyan(
|
|
90
102
|
(0, import_i18n.t)("output.proposalTotal", {
|
|
91
103
|
count: totalProposals.toString(),
|
|
92
104
|
commits: totalProposalCommits.toString(),
|
|
@@ -96,6 +108,7 @@ ${(0, import_i18n.t)("output.proposalSummary")}
|
|
|
96
108
|
netChanges: totalProposalNetChanges.toString()
|
|
97
109
|
})
|
|
98
110
|
);
|
|
111
|
+
output += import_chalk.default.cyan("─".repeat(80)) + "\n";
|
|
99
112
|
}
|
|
100
113
|
output += import_chalk.default.bold.cyan(`
|
|
101
114
|
${(0, import_i18n.t)("output.authorSummary")}
|
|
@@ -231,7 +244,10 @@ ${stats.author}
|
|
|
231
244
|
codeFilesChanged: stats.codeFilesChanged,
|
|
232
245
|
additions: stats.additions,
|
|
233
246
|
deletions: stats.deletions,
|
|
234
|
-
netChanges: stats.netChanges
|
|
247
|
+
netChanges: stats.netChanges,
|
|
248
|
+
multiProposalCommits: stats.multiProposalCommits,
|
|
249
|
+
hasSharedCommits: stats.multiProposalCommits > 0,
|
|
250
|
+
sharedCommitHashes: Array.from(stats.sharedCommitHashes)
|
|
235
251
|
})),
|
|
236
252
|
summary: {
|
|
237
253
|
totalProposals: result.proposals.size,
|
|
@@ -375,7 +391,8 @@ ${stats.author}
|
|
|
375
391
|
const sortedProposals = Array.from(result.proposals.values()).sort((a, b) => b.netChanges - a.netChanges);
|
|
376
392
|
for (const stats of sortedProposals) {
|
|
377
393
|
const contributors = Array.from(stats.contributors).join(", ");
|
|
378
|
-
|
|
394
|
+
const proposalName = stats.multiProposalCommits > 0 ? `${stats.proposal} ⚠️` : stats.proposal;
|
|
395
|
+
md += `| ${proposalName} | ${stats.commits} | ${contributors} | ${stats.codeFilesChanged} | +${stats.additions} | -${stats.deletions} | ${stats.netChanges >= 0 ? "+" : ""}${stats.netChanges} |
|
|
379
396
|
`;
|
|
380
397
|
}
|
|
381
398
|
const totalProposals = result.proposals.size;
|
|
@@ -399,6 +416,19 @@ ${stats.author}
|
|
|
399
416
|
`;
|
|
400
417
|
md += `- ${(0, import_i18n.t)("table.netChanges")}: ${totalProposalNetChanges >= 0 ? "+" : ""}${totalProposalNetChanges}
|
|
401
418
|
`;
|
|
419
|
+
const hasMultiProposalCommits = Array.from(result.proposals.values()).some((p) => p.multiProposalCommits > 0);
|
|
420
|
+
if (hasMultiProposalCommits) {
|
|
421
|
+
md += `
|
|
422
|
+
> ⚠️ **${(0, import_i18n.t)("output.multiProposalWarning")}**
|
|
423
|
+
>
|
|
424
|
+
`;
|
|
425
|
+
const affectedProposals = Array.from(result.proposals.values()).filter((p) => p.multiProposalCommits > 0);
|
|
426
|
+
for (const p of affectedProposals) {
|
|
427
|
+
md += `> - ${p.proposal}: ${p.multiProposalCommits}/${p.commits} commits ${(0, import_i18n.t)("output.sharedWithOthers")}
|
|
428
|
+
`;
|
|
429
|
+
}
|
|
430
|
+
md += "\n";
|
|
431
|
+
}
|
|
402
432
|
md += `
|
|
403
433
|
## ${(0, import_i18n.t)("output.authorSummary")}
|
|
404
434
|
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"warning.noCommits": "⚠️ No commits found matching the criteria",
|
|
30
30
|
"warning.noQualifyingCommits": "⚠️ No commits found containing both OpenSpec proposals and code changes",
|
|
31
31
|
"warning.noBranches": "⚠️ No remote branches found",
|
|
32
|
+
"warning.noBranchesSelected": "⚠️ No branches selected. Please select at least one branch to analyze.",
|
|
32
33
|
|
|
33
34
|
"error.prefix": "❌ Error:",
|
|
34
35
|
|
|
@@ -52,6 +53,8 @@
|
|
|
52
53
|
"output.proposalSummary": "📋 Proposal Summary (by proposal)",
|
|
53
54
|
"output.proposalTotal": " 📊 Total: {{count}} proposals | {{commits}} commits | {{files}} files | +{{additions}}/-{{deletions}} lines (net: {{netChanges}})\n",
|
|
54
55
|
"output.proposalTotalLabel": "Proposal Summary Total",
|
|
56
|
+
"output.multiProposalWarning": "Note: Some proposals contain commits shared with other proposals. Code changes have been evenly distributed among proposals.",
|
|
57
|
+
"output.sharedWithOthers": "shared with other proposals",
|
|
55
58
|
"output.authorSummary": "👥 Author Summary (by contributor)",
|
|
56
59
|
"output.contributorHint": "💡 Use --show-contributors to see detailed statistics for each contributor",
|
|
57
60
|
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"warning.noCommits": "⚠️ 未找到符合条件的提交",
|
|
30
30
|
"warning.noQualifyingCommits": "⚠️ 未找到同时包含 OpenSpec 提案和代码变更的提交",
|
|
31
31
|
"warning.noBranches": "⚠️ 未找到远程分支",
|
|
32
|
+
"warning.noBranchesSelected": "⚠️ 未选择任何分支。请至少选择一个分支进行分析。",
|
|
32
33
|
|
|
33
34
|
"error.prefix": "❌ 错误:",
|
|
34
35
|
|
|
@@ -52,6 +53,8 @@
|
|
|
52
53
|
"output.proposalSummary": "📋 提案汇总(按提案统计)",
|
|
53
54
|
"output.proposalTotal": " 📊 总计:{{count}} 个提案 | {{commits}} 次提交 | {{files}} 个文件 | +{{additions}}/-{{deletions}} 行(净变更:{{netChanges}})\n",
|
|
54
55
|
"output.proposalTotalLabel": "提案汇总总计",
|
|
56
|
+
"output.multiProposalWarning": "注意:部分提案包含多提案共享的 commit,代码变更已按提案数量平均分配",
|
|
57
|
+
"output.sharedWithOthers": "与其他提案共享",
|
|
55
58
|
"output.authorSummary": "👥 作者汇总(按贡献者统计)",
|
|
56
59
|
"output.contributorHint": "💡 使用 --show-contributors 选项可查看每个贡献者的详细统计信息",
|
|
57
60
|
|
|
@@ -70,7 +70,9 @@ var StatsAggregator = class {
|
|
|
70
70
|
additions: 0,
|
|
71
71
|
deletions: 0,
|
|
72
72
|
netChanges: 0,
|
|
73
|
-
commitHashes: /* @__PURE__ */ new Set()
|
|
73
|
+
commitHashes: /* @__PURE__ */ new Set(),
|
|
74
|
+
multiProposalCommits: 0,
|
|
75
|
+
sharedCommitHashes: /* @__PURE__ */ new Set()
|
|
74
76
|
};
|
|
75
77
|
proposalStatsMap.set(proposal, proposalStats);
|
|
76
78
|
}
|
|
@@ -78,10 +80,16 @@ var StatsAggregator = class {
|
|
|
78
80
|
proposalStats.commitHashes.add(analysis.commit.hash);
|
|
79
81
|
proposalStats.commits++;
|
|
80
82
|
proposalStats.contributors.add(normalizedAuthor);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const proposalCount = analysis.openspecProposals.size;
|
|
84
|
+
const isMultiProposal = proposalCount > 1;
|
|
85
|
+
if (isMultiProposal) {
|
|
86
|
+
proposalStats.multiProposalCommits++;
|
|
87
|
+
proposalStats.sharedCommitHashes.add(analysis.commit.hash);
|
|
88
|
+
}
|
|
89
|
+
proposalStats.codeFilesChanged += Math.floor(analysis.codeFiles.length / proposalCount);
|
|
90
|
+
proposalStats.additions += Math.floor(analysis.totalAdditions / proposalCount);
|
|
91
|
+
proposalStats.deletions += Math.floor(analysis.totalDeletions / proposalCount);
|
|
92
|
+
proposalStats.netChanges += Math.floor(analysis.netChanges / proposalCount);
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
if (!stats.lastCommitDate || analysis.commit.date > stats.lastCommitDate) {
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -182,6 +182,10 @@ function _selectBranches() {
|
|
|
182
182
|
});
|
|
183
183
|
case 21:
|
|
184
184
|
selected = _context2.sent;
|
|
185
|
+
if (selected.length === 0) {
|
|
186
|
+
console.log(chalk.yellow(t('warning.noBranchesSelected')));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
185
189
|
if (selected.length > 0) {
|
|
186
190
|
console.log(chalk.green(t('branch.selected')));
|
|
187
191
|
selected.forEach(function (branch) {
|
|
@@ -189,14 +193,14 @@ function _selectBranches() {
|
|
|
189
193
|
});
|
|
190
194
|
}
|
|
191
195
|
if (!selected.includes('__custom__')) {
|
|
192
|
-
_context2.next =
|
|
196
|
+
_context2.next = 30;
|
|
193
197
|
break;
|
|
194
198
|
}
|
|
195
|
-
_context2.next =
|
|
199
|
+
_context2.next = 27;
|
|
196
200
|
return input({
|
|
197
201
|
message: t('branch.additionalInput')
|
|
198
202
|
});
|
|
199
|
-
case
|
|
203
|
+
case 27:
|
|
200
204
|
_customInput = _context2.sent;
|
|
201
205
|
customBranches = _customInput.split(',').map(function (b) {
|
|
202
206
|
return b.trim();
|
|
@@ -206,9 +210,9 @@ function _selectBranches() {
|
|
|
206
210
|
return _context2.abrupt("return", [].concat(_toConsumableArray(selected.filter(function (b) {
|
|
207
211
|
return b !== '__custom__';
|
|
208
212
|
})), _toConsumableArray(customBranches)));
|
|
209
|
-
case 29:
|
|
210
|
-
return _context2.abrupt("return", selected.length > 0 ? selected : defaultBranches || []);
|
|
211
213
|
case 30:
|
|
214
|
+
return _context2.abrupt("return", selected);
|
|
215
|
+
case 31:
|
|
212
216
|
case "end":
|
|
213
217
|
return _context2.stop();
|
|
214
218
|
}
|
package/dist/esm/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { runSingleRepoCommand } from "./commands/single.js";
|
|
|
13
13
|
import { runMultiRepoCommand } from "./commands/multi.js";
|
|
14
14
|
import { runInitCommand } from "./commands/init.js";
|
|
15
15
|
var program = new Command();
|
|
16
|
-
program.name('openspec-stat').description("Track team members' OpenSpec proposals and code changes in Git repositories").version("1.3.
|
|
16
|
+
program.name('openspec-stat').description("Track team members' OpenSpec proposals and code changes in Git repositories").version("1.3.4").enablePositionalOptions().passThroughOptions();
|
|
17
17
|
|
|
18
18
|
// Default command for single-repository mode (for backward compatibility)
|
|
19
19
|
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( /*#__PURE__*/function () {
|
package/dist/esm/formatters.js
CHANGED
|
@@ -55,7 +55,8 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
55
55
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
56
56
|
var proposalStats = _step.value;
|
|
57
57
|
var contributors = Array.from(proposalStats.contributors).join(', ');
|
|
58
|
-
|
|
58
|
+
var proposalName = proposalStats.multiProposalCommits > 0 ? "".concat(proposalStats.proposal, " ").concat(chalk.yellow('⚠')) : proposalStats.proposal;
|
|
59
|
+
proposalTable.push([proposalName, proposalStats.commits.toString(), contributors, proposalStats.codeFilesChanged.toString(), chalk.green("+".concat(proposalStats.additions)), chalk.red("-".concat(proposalStats.deletions)), proposalStats.netChanges >= 0 ? chalk.green("+".concat(proposalStats.netChanges)) : chalk.red("".concat(proposalStats.netChanges))]);
|
|
59
60
|
}
|
|
60
61
|
} catch (err) {
|
|
61
62
|
_iterator.e(err);
|
|
@@ -63,6 +64,18 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
63
64
|
_iterator.f();
|
|
64
65
|
}
|
|
65
66
|
output += proposalTable.toString() + '\n';
|
|
67
|
+
var hasMultiProposalCommits = Array.from(result.proposals.values()).some(function (p) {
|
|
68
|
+
return p.multiProposalCommits > 0;
|
|
69
|
+
});
|
|
70
|
+
if (hasMultiProposalCommits) {
|
|
71
|
+
output += chalk.yellow("\n\u26A0 ".concat(t('output.multiProposalWarning'), "\n"));
|
|
72
|
+
var affectedProposals = Array.from(result.proposals.values()).filter(function (p) {
|
|
73
|
+
return p.multiProposalCommits > 0;
|
|
74
|
+
}).map(function (p) {
|
|
75
|
+
return " \u2022 ".concat(p.proposal, ": ").concat(p.multiProposalCommits, "/").concat(p.commits, " commits ").concat(t('output.sharedWithOthers'));
|
|
76
|
+
}).join('\n');
|
|
77
|
+
output += chalk.gray(affectedProposals + '\n');
|
|
78
|
+
}
|
|
66
79
|
|
|
67
80
|
// Proposal summary totals
|
|
68
81
|
var totalProposals = result.proposals.size;
|
|
@@ -81,7 +94,8 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
81
94
|
var totalProposalNetChanges = Array.from(result.proposals.values()).reduce(function (sum, p) {
|
|
82
95
|
return sum + p.netChanges;
|
|
83
96
|
}, 0);
|
|
84
|
-
output += chalk.
|
|
97
|
+
output += chalk.cyan('─'.repeat(80)) + '\n';
|
|
98
|
+
output += chalk.bold.cyan(t('output.proposalTotal', {
|
|
85
99
|
count: totalProposals.toString(),
|
|
86
100
|
commits: totalProposalCommits.toString(),
|
|
87
101
|
files: totalProposalFiles.toString(),
|
|
@@ -89,6 +103,7 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
89
103
|
deletions: totalProposalDeletions.toString(),
|
|
90
104
|
netChanges: totalProposalNetChanges.toString()
|
|
91
105
|
}));
|
|
106
|
+
output += chalk.cyan('─'.repeat(80)) + '\n';
|
|
92
107
|
}
|
|
93
108
|
output += chalk.bold.cyan("\n".concat(t('output.authorSummary'), "\n"));
|
|
94
109
|
var sortedAuthors = Array.from(result.authors.values()).sort(function (a, b) {
|
|
@@ -207,7 +222,10 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
207
222
|
codeFilesChanged: stats.codeFilesChanged,
|
|
208
223
|
additions: stats.additions,
|
|
209
224
|
deletions: stats.deletions,
|
|
210
|
-
netChanges: stats.netChanges
|
|
225
|
+
netChanges: stats.netChanges,
|
|
226
|
+
multiProposalCommits: stats.multiProposalCommits,
|
|
227
|
+
hasSharedCommits: stats.multiProposalCommits > 0,
|
|
228
|
+
sharedCommitHashes: Array.from(stats.sharedCommitHashes)
|
|
211
229
|
};
|
|
212
230
|
}),
|
|
213
231
|
summary: {
|
|
@@ -405,7 +423,8 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
405
423
|
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
406
424
|
var _stats3 = _step6.value;
|
|
407
425
|
var contributors = Array.from(_stats3.contributors).join(', ');
|
|
408
|
-
|
|
426
|
+
var proposalName = _stats3.multiProposalCommits > 0 ? "".concat(_stats3.proposal, " \u26A0\uFE0F") : _stats3.proposal;
|
|
427
|
+
md += "| ".concat(proposalName, " | ").concat(_stats3.commits, " | ").concat(contributors, " | ").concat(_stats3.codeFilesChanged, " | +").concat(_stats3.additions, " | -").concat(_stats3.deletions, " | ").concat(_stats3.netChanges >= 0 ? '+' : '').concat(_stats3.netChanges, " |\n");
|
|
409
428
|
}
|
|
410
429
|
|
|
411
430
|
// Proposal totals
|
|
@@ -437,6 +456,28 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
437
456
|
md += "- ".concat(t('table.additions'), ": +").concat(totalProposalAdditions, "\n");
|
|
438
457
|
md += "- ".concat(t('table.deletions'), ": -").concat(totalProposalDeletions, "\n");
|
|
439
458
|
md += "- ".concat(t('table.netChanges'), ": ").concat(totalProposalNetChanges >= 0 ? '+' : '').concat(totalProposalNetChanges, "\n");
|
|
459
|
+
var hasMultiProposalCommits = Array.from(result.proposals.values()).some(function (p) {
|
|
460
|
+
return p.multiProposalCommits > 0;
|
|
461
|
+
});
|
|
462
|
+
if (hasMultiProposalCommits) {
|
|
463
|
+
md += "\n> \u26A0\uFE0F **".concat(t('output.multiProposalWarning'), "**\n>\n");
|
|
464
|
+
var affectedProposals = Array.from(result.proposals.values()).filter(function (p) {
|
|
465
|
+
return p.multiProposalCommits > 0;
|
|
466
|
+
});
|
|
467
|
+
var _iterator7 = _createForOfIteratorHelper(affectedProposals),
|
|
468
|
+
_step7;
|
|
469
|
+
try {
|
|
470
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
471
|
+
var p = _step7.value;
|
|
472
|
+
md += "> - ".concat(p.proposal, ": ").concat(p.multiProposalCommits, "/").concat(p.commits, " commits ").concat(t('output.sharedWithOthers'), "\n");
|
|
473
|
+
}
|
|
474
|
+
} catch (err) {
|
|
475
|
+
_iterator7.e(err);
|
|
476
|
+
} finally {
|
|
477
|
+
_iterator7.f();
|
|
478
|
+
}
|
|
479
|
+
md += '\n';
|
|
480
|
+
}
|
|
440
481
|
|
|
441
482
|
// Author summary
|
|
442
483
|
md += "\n## ".concat(t('output.authorSummary'), "\n\n");
|
|
@@ -446,24 +487,24 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
446
487
|
if (showContributors) {
|
|
447
488
|
md += "| ".concat(t('table.author'), " | ").concat(t('table.period'), " | ").concat(t('table.commits'), " | ").concat(t('table.proposals'), " | ").concat(t('table.codeFiles'), " | ").concat(t('table.additions'), " | ").concat(t('table.deletions'), " | ").concat(t('table.netChanges'), " |\n");
|
|
448
489
|
md += '|--------|--------|---------|-----------|------------|-----------|-----------|-------------|\n';
|
|
449
|
-
var
|
|
450
|
-
|
|
490
|
+
var _iterator8 = _createForOfIteratorHelper(sortedAuthors),
|
|
491
|
+
_step8;
|
|
451
492
|
try {
|
|
452
|
-
for (
|
|
453
|
-
var stats =
|
|
493
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
494
|
+
var stats = _step8.value;
|
|
454
495
|
md += "| ".concat(stats.author, " | ").concat(stats.statisticsPeriod || '-', " | ").concat(stats.commits, " | ").concat(stats.openspecProposals.size, " | ").concat(stats.codeFilesChanged, " | +").concat(stats.additions, " | -").concat(stats.deletions, " | ").concat(stats.netChanges >= 0 ? '+' : '').concat(stats.netChanges, " |\n");
|
|
455
496
|
}
|
|
456
497
|
} catch (err) {
|
|
457
|
-
|
|
498
|
+
_iterator8.e(err);
|
|
458
499
|
} finally {
|
|
459
|
-
|
|
500
|
+
_iterator8.f();
|
|
460
501
|
}
|
|
461
502
|
md += t('markdown.proposalDetails');
|
|
462
|
-
var
|
|
463
|
-
|
|
503
|
+
var _iterator9 = _createForOfIteratorHelper(sortedAuthors),
|
|
504
|
+
_step9;
|
|
464
505
|
try {
|
|
465
|
-
for (
|
|
466
|
-
var _stats2 =
|
|
506
|
+
for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
|
|
507
|
+
var _stats2 = _step9.value;
|
|
467
508
|
if (_stats2.openspecProposals.size > 0) {
|
|
468
509
|
md += "### ".concat(_stats2.author, "\n\n");
|
|
469
510
|
for (var _i = 0, _Array$from = Array.from(_stats2.openspecProposals); _i < _Array$from.length; _i++) {
|
|
@@ -474,9 +515,9 @@ export var OutputFormatter = /*#__PURE__*/function () {
|
|
|
474
515
|
}
|
|
475
516
|
}
|
|
476
517
|
} catch (err) {
|
|
477
|
-
|
|
518
|
+
_iterator9.e(err);
|
|
478
519
|
} finally {
|
|
479
|
-
|
|
520
|
+
_iterator9.f();
|
|
480
521
|
}
|
|
481
522
|
} else {
|
|
482
523
|
md += "| ".concat(t('table.contributors'), " | ").concat(t('table.commits'), " | ").concat(t('table.proposals'), " | ").concat(t('table.codeFiles'), " | ").concat(t('table.additions'), " | ").concat(t('table.deletions'), " | ").concat(t('table.netChanges'), " |\n");
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"warning.noCommits": "⚠️ No commits found matching the criteria",
|
|
30
30
|
"warning.noQualifyingCommits": "⚠️ No commits found containing both OpenSpec proposals and code changes",
|
|
31
31
|
"warning.noBranches": "⚠️ No remote branches found",
|
|
32
|
+
"warning.noBranchesSelected": "⚠️ No branches selected. Please select at least one branch to analyze.",
|
|
32
33
|
|
|
33
34
|
"error.prefix": "❌ Error:",
|
|
34
35
|
|
|
@@ -52,6 +53,8 @@
|
|
|
52
53
|
"output.proposalSummary": "📋 Proposal Summary (by proposal)",
|
|
53
54
|
"output.proposalTotal": " 📊 Total: {{count}} proposals | {{commits}} commits | {{files}} files | +{{additions}}/-{{deletions}} lines (net: {{netChanges}})\n",
|
|
54
55
|
"output.proposalTotalLabel": "Proposal Summary Total",
|
|
56
|
+
"output.multiProposalWarning": "Note: Some proposals contain commits shared with other proposals. Code changes have been evenly distributed among proposals.",
|
|
57
|
+
"output.sharedWithOthers": "shared with other proposals",
|
|
55
58
|
"output.authorSummary": "👥 Author Summary (by contributor)",
|
|
56
59
|
"output.contributorHint": "💡 Use --show-contributors to see detailed statistics for each contributor",
|
|
57
60
|
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"warning.noCommits": "⚠️ 未找到符合条件的提交",
|
|
30
30
|
"warning.noQualifyingCommits": "⚠️ 未找到同时包含 OpenSpec 提案和代码变更的提交",
|
|
31
31
|
"warning.noBranches": "⚠️ 未找到远程分支",
|
|
32
|
+
"warning.noBranchesSelected": "⚠️ 未选择任何分支。请至少选择一个分支进行分析。",
|
|
32
33
|
|
|
33
34
|
"error.prefix": "❌ 错误:",
|
|
34
35
|
|
|
@@ -52,6 +53,8 @@
|
|
|
52
53
|
"output.proposalSummary": "📋 提案汇总(按提案统计)",
|
|
53
54
|
"output.proposalTotal": " 📊 总计:{{count}} 个提案 | {{commits}} 次提交 | {{files}} 个文件 | +{{additions}}/-{{deletions}} 行(净变更:{{netChanges}})\n",
|
|
54
55
|
"output.proposalTotalLabel": "提案汇总总计",
|
|
56
|
+
"output.multiProposalWarning": "注意:部分提案包含多提案共享的 commit,代码变更已按提案数量平均分配",
|
|
57
|
+
"output.sharedWithOthers": "与其他提案共享",
|
|
55
58
|
"output.authorSummary": "👥 作者汇总(按贡献者统计)",
|
|
56
59
|
"output.contributorHint": "💡 使用 --show-contributors 选项可查看每个贡献者的详细统计信息",
|
|
57
60
|
|
|
@@ -71,7 +71,9 @@ export var StatsAggregator = /*#__PURE__*/function () {
|
|
|
71
71
|
additions: 0,
|
|
72
72
|
deletions: 0,
|
|
73
73
|
netChanges: 0,
|
|
74
|
-
commitHashes: new Set()
|
|
74
|
+
commitHashes: new Set(),
|
|
75
|
+
multiProposalCommits: 0,
|
|
76
|
+
sharedCommitHashes: new Set()
|
|
75
77
|
};
|
|
76
78
|
proposalStatsMap.set(_proposal, proposalStats);
|
|
77
79
|
}
|
|
@@ -81,10 +83,16 @@ export var StatsAggregator = /*#__PURE__*/function () {
|
|
|
81
83
|
proposalStats.commitHashes.add(analysis.commit.hash);
|
|
82
84
|
proposalStats.commits++;
|
|
83
85
|
proposalStats.contributors.add(normalizedAuthor);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
var proposalCount = analysis.openspecProposals.size;
|
|
87
|
+
var isMultiProposal = proposalCount > 1;
|
|
88
|
+
if (isMultiProposal) {
|
|
89
|
+
proposalStats.multiProposalCommits++;
|
|
90
|
+
proposalStats.sharedCommitHashes.add(analysis.commit.hash);
|
|
91
|
+
}
|
|
92
|
+
proposalStats.codeFilesChanged += Math.floor(analysis.codeFiles.length / proposalCount);
|
|
93
|
+
proposalStats.additions += Math.floor(analysis.totalAdditions / proposalCount);
|
|
94
|
+
proposalStats.deletions += Math.floor(analysis.totalDeletions / proposalCount);
|
|
95
|
+
proposalStats.netChanges += Math.floor(analysis.netChanges / proposalCount);
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
} catch (err) {
|
package/dist/esm/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,35 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openspec-stat",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Track team members' OpenSpec proposals and code changes in Git repositories",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"types": "dist/cjs/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"packageManager": "pnpm@10.12.1",
|
|
9
8
|
"bin": {
|
|
10
9
|
"openspec-stat": "dist/esm/cli.js"
|
|
11
10
|
},
|
|
12
|
-
"scripts": {
|
|
13
|
-
"dev": "father dev",
|
|
14
|
-
"build": "father build",
|
|
15
|
-
"build:deps": "father prebundle",
|
|
16
|
-
"prepublishOnly": "father doctor && npm run build",
|
|
17
|
-
"test:cli": "node dist/esm/cli.js --help",
|
|
18
|
-
"link:global": "npm link",
|
|
19
|
-
"link:local": "pnpm run build && chmod +x dist/esm/cli.js && npm link .",
|
|
20
|
-
"unlink": "npm unlink -g openspec-stat",
|
|
21
|
-
"lint": "eslint src --ext .ts,.tsx",
|
|
22
|
-
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
23
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
24
|
-
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
25
|
-
"prepare": "husky",
|
|
26
|
-
"changeset": "changeset",
|
|
27
|
-
"changeset:add": "changeset add",
|
|
28
|
-
"version": "changeset version && git add . && git commit -m 'chore: version packages'",
|
|
29
|
-
"release": "pnpm run release:version && pnpm run release:tag",
|
|
30
|
-
"release:version": "pnpm changeset version && git add . && git commit -m 'chore: version packages' && git push origin main",
|
|
31
|
-
"release:tag": "git tag v$(node -p \"require('./package.json').version\") && git push origin v$(node -p \"require('./package.json').version\")"
|
|
32
|
-
},
|
|
33
11
|
"keywords": [
|
|
34
12
|
"openspec",
|
|
35
13
|
"git",
|
|
@@ -85,5 +63,20 @@
|
|
|
85
63
|
"commander": "^14.0.2",
|
|
86
64
|
"date-fns": "^4.1.0",
|
|
87
65
|
"simple-git": "^3.30.0"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"dev": "father dev",
|
|
69
|
+
"build": "father build",
|
|
70
|
+
"build:deps": "father prebundle",
|
|
71
|
+
"test:cli": "node dist/esm/cli.js --help",
|
|
72
|
+
"link:global": "npm link",
|
|
73
|
+
"link:local": "pnpm run build && chmod +x dist/esm/cli.js && npm link .",
|
|
74
|
+
"unlink": "npm unlink -g openspec-stat",
|
|
75
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
76
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
77
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
78
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
79
|
+
"changeset": "changeset",
|
|
80
|
+
"changeset:add": "changeset add"
|
|
88
81
|
}
|
|
89
|
-
}
|
|
82
|
+
}
|