@donativo/roux 0.1.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 (95) hide show
  1. package/README.md +335 -0
  2. package/dist/analysis/age.d.ts +3 -0
  3. package/dist/analysis/age.js +32 -0
  4. package/dist/analysis/age.js.map +1 -0
  5. package/dist/analysis/authors.d.ts +3 -0
  6. package/dist/analysis/authors.js +18 -0
  7. package/dist/analysis/authors.js.map +1 -0
  8. package/dist/analysis/churn.d.ts +5 -0
  9. package/dist/analysis/churn.js +39 -0
  10. package/dist/analysis/churn.js.map +1 -0
  11. package/dist/analysis/communication.d.ts +3 -0
  12. package/dist/analysis/communication.js +44 -0
  13. package/dist/analysis/communication.js.map +1 -0
  14. package/dist/analysis/coupling.d.ts +4 -0
  15. package/dist/analysis/coupling.js +84 -0
  16. package/dist/analysis/coupling.js.map +1 -0
  17. package/dist/analysis/entity-effort.d.ts +3 -0
  18. package/dist/analysis/entity-effort.js +36 -0
  19. package/dist/analysis/entity-effort.js.map +1 -0
  20. package/dist/analysis/entity-ownership.d.ts +3 -0
  21. package/dist/analysis/entity-ownership.js +35 -0
  22. package/dist/analysis/entity-ownership.js.map +1 -0
  23. package/dist/analysis/fragmentation.d.ts +3 -0
  24. package/dist/analysis/fragmentation.js +53 -0
  25. package/dist/analysis/fragmentation.js.map +1 -0
  26. package/dist/analysis/identity.d.ts +3 -0
  27. package/dist/analysis/identity.js +12 -0
  28. package/dist/analysis/identity.js.map +1 -0
  29. package/dist/analysis/index.d.ts +4 -0
  30. package/dist/analysis/index.js +59 -0
  31. package/dist/analysis/index.js.map +1 -0
  32. package/dist/analysis/main-dev-by-revs.d.ts +3 -0
  33. package/dist/analysis/main-dev-by-revs.js +36 -0
  34. package/dist/analysis/main-dev-by-revs.js.map +1 -0
  35. package/dist/analysis/main-dev.d.ts +3 -0
  36. package/dist/analysis/main-dev.js +38 -0
  37. package/dist/analysis/main-dev.js.map +1 -0
  38. package/dist/analysis/messages.d.ts +3 -0
  39. package/dist/analysis/messages.js +27 -0
  40. package/dist/analysis/messages.js.map +1 -0
  41. package/dist/analysis/refactoring-main-dev.d.ts +3 -0
  42. package/dist/analysis/refactoring-main-dev.js +37 -0
  43. package/dist/analysis/refactoring-main-dev.js.map +1 -0
  44. package/dist/analysis/revisions.d.ts +3 -0
  45. package/dist/analysis/revisions.js +16 -0
  46. package/dist/analysis/revisions.js.map +1 -0
  47. package/dist/analysis/summary.d.ts +3 -0
  48. package/dist/analysis/summary.js +12 -0
  49. package/dist/analysis/summary.js.map +1 -0
  50. package/dist/analysis/types.d.ts +18 -0
  51. package/dist/analysis/types.js +8 -0
  52. package/dist/analysis/types.js.map +1 -0
  53. package/dist/app.d.ts +12 -0
  54. package/dist/app.js +54 -0
  55. package/dist/app.js.map +1 -0
  56. package/dist/cli.d.ts +1 -0
  57. package/dist/cli.js +79 -0
  58. package/dist/cli.js.map +1 -0
  59. package/dist/git.d.ts +7 -0
  60. package/dist/git.js +24 -0
  61. package/dist/git.js.map +1 -0
  62. package/dist/index.d.ts +8 -0
  63. package/dist/index.js +6 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/output/csv.d.ts +1 -0
  66. package/dist/output/csv.js +19 -0
  67. package/dist/output/csv.js.map +1 -0
  68. package/dist/output/json.d.ts +1 -0
  69. package/dist/output/json.js +4 -0
  70. package/dist/output/json.js.map +1 -0
  71. package/dist/parsers/git.d.ts +2 -0
  72. package/dist/parsers/git.js +41 -0
  73. package/dist/parsers/git.js.map +1 -0
  74. package/dist/parsers/git2.d.ts +2 -0
  75. package/dist/parsers/git2.js +72 -0
  76. package/dist/parsers/git2.js.map +1 -0
  77. package/dist/parsers/index.d.ts +7 -0
  78. package/dist/parsers/index.js +7 -0
  79. package/dist/parsers/index.js.map +1 -0
  80. package/dist/parsers/types.d.ts +9 -0
  81. package/dist/parsers/types.js +2 -0
  82. package/dist/parsers/types.js.map +1 -0
  83. package/dist/transforms/grouper.d.ts +11 -0
  84. package/dist/transforms/grouper.js +57 -0
  85. package/dist/transforms/grouper.js.map +1 -0
  86. package/dist/transforms/team-mapper.d.ts +7 -0
  87. package/dist/transforms/team-mapper.js +51 -0
  88. package/dist/transforms/team-mapper.js.map +1 -0
  89. package/dist/transforms/temporal-grouper.d.ts +2 -0
  90. package/dist/transforms/temporal-grouper.js +42 -0
  91. package/dist/transforms/temporal-grouper.js.map +1 -0
  92. package/dist/utils/dataset.d.ts +2 -0
  93. package/dist/utils/dataset.js +34 -0
  94. package/dist/utils/dataset.js.map +1 -0
  95. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ # Roux
2
+
3
+ Mine and analyze git history to understand how your codebase evolves — find hotspots, hidden coupling, ownership patterns, and team dynamics from commit data alone.
4
+
5
+ 挖掘和分析 git 历史,理解代码库的演变 — 仅凭 commit 数据就能发现热点、隐式耦合、代码所有权和团队协作模式。
6
+
7
+ Roux provides 18 analyses covering code hotspots, coupling, churn, ownership, and team collaboration. Written in TypeScript, it starts in milliseconds and installs with `npm install`. Based on the analysis methods from [code-maat](https://github.com/adamtornhill/code-maat), with fully compatible output.
8
+
9
+ Roux 提供 18 种分析,覆盖代码热点、耦合、变动、所有权和团队协作。TypeScript 编写,毫秒级启动,`npm install` 即可使用。基于 [code-maat](https://github.com/adamtornhill/code-maat) 的分析方法,输出兼容。
10
+
11
+ ## Quick Start / 快速开始
12
+
13
+ ```bash
14
+ # Analyze the current git repository / 分析当前 git 仓库
15
+ npx roux summary
16
+ npx roux coupling
17
+ npx roux main-dev
18
+
19
+ # Analyze last year only / 只分析最近一年
20
+ npx roux coupling --after 2025-01-01
21
+
22
+ # Between two releases / 两个版本之间
23
+ npx roux coupling --rev v1.0..v2.0
24
+
25
+ # Date range / 日期范围
26
+ npx roux revisions --after 2024-01-01 --before 2025-01-01
27
+
28
+ # Analyze a specific log file / 分析指定的日志文件
29
+ npx roux revisions -l git.log
30
+
31
+ # Generate a log file first, then analyze / 先生成日志文件,再分析
32
+ git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN--%s' --no-renames > git.log
33
+ npx roux hotspot -l git.log
34
+ ```
35
+
36
+ ## Installation / 安装
37
+
38
+ ```bash
39
+ npm install roux
40
+ ```
41
+
42
+ Or run directly / 或直接运行:
43
+
44
+ ```bash
45
+ npx roux <analysis> [options]
46
+ ```
47
+
48
+ ## Analyses / 分析类型
49
+
50
+ Roux supports 18 analyses:
51
+
52
+ Roux 支持 18 种分析:
53
+
54
+ | Analysis / 分析 | Description / 说明 | Key Output / 关键输出 |
55
+ |------------------------|---------------------------------------------------------------|--------------------------------|
56
+ | `summary` | Repository statistics / 仓库统计 | commits, entities, authors |
57
+ | `revisions` | Most modified files / 修改最多的文件 | entity, n-revs |
58
+ | `authors` | Author diversity per file / 每个文件的作者多样性 | entity, n-authors, n-revs |
59
+ | `coupling` | Files that change together / 经常一起修改的文件 | entity, coupled, degree |
60
+ | `soc` | Sum of coupling per file / 每个文件的耦合度总和 | entity, soc |
61
+ | `abs-churn` | Daily code churn / 每日代码变动量 | date, added, deleted, commits |
62
+ | `entity-churn` | Code churn per file / 每个文件的代码变动量 | entity, added, deleted |
63
+ | `author-churn` | Code churn per author / 每个作者的代码变动量 | author, added, deleted |
64
+ | `age` | Months since last change / 距最后修改的月数 | entity, age-months |
65
+ | `entity-ownership` | Who owns what (by LOC) / 代码所有权(按代码行数) | entity, author, added, deleted |
66
+ | `main-dev` | Primary developer (by lines added) / 主要开发者(按新增行数) | entity, main-dev, ownership |
67
+ | `main-dev-by-revs` | Primary developer (by commits) / 主要开发者(按提交次数) | entity, main-dev, ownership |
68
+ | `refactoring-main-dev` | Top refactorer (by lines deleted) / 主要重构者(按删除行数) | entity, main-dev, ownership |
69
+ | `entity-effort` | Effort distribution / 工作量分布 | entity, author, author-revs |
70
+ | `fragmentation` | Knowledge fragmentation / 知识碎片化 | entity, fractal-value |
71
+ | `communication` | Developer collaboration / 开发者协作关系 | author, peer, strength |
72
+ | `messages` | Commit message pattern matching / 提交信息模式匹配 | entity, matches |
73
+ | `identity` | Raw modification data / 原始修改数据 | all fields |
74
+
75
+ ### When to Use Each Analysis / 各分析使用场景
76
+
77
+ #### Code Hotspots & Complexity / 代码热点与复杂度
78
+
79
+ - **`summary`** — Quick overview of project scale / 快速了解项目规模
80
+ - **`revisions`** — Find most frequently modified files (hotspots) / 找出修改最频繁的文件(热点)
81
+ - **`age`** — Find code that hasn't changed in a long time (stable infrastructure or forgotten debt) / 找出长期未修改的代码(可能是稳定的基础设施,也可能是被遗忘的技术债)
82
+
83
+ #### Code Coupling / 代码耦合
84
+
85
+ - **`coupling`** — Find files that often change together (implicit dependencies) / 找出经常一起修改的文件对(隐式依赖)
86
+ - **`soc`** — Sum of coupling per file. High soc = "God file" that's coupled to too many others, risky to change / 每个文件的耦合度总和。高 soc = God 文件,与过多文件耦合,改动风险大
87
+ - **`messages`** — Match commit message patterns (e.g. "Fix", "Bug") to find bug-prone files / 按 commit message 匹配模式(如 "Fix", "Bug"),找出 bug 重灾区
88
+
89
+ #### Code Churn / 代码变动
90
+
91
+ - **`abs-churn`** — Daily code churn, identify development rhythm / 每日代码变动量,识别开发节奏
92
+ - **`entity-churn`** — Churn per file, combine with revisions to identify high-risk files / 每个文件的变动量,结合 revisions 识别高风险文件
93
+ - **`author-churn`** — Churn per author / 每个作者的变动量
94
+
95
+ #### Code Ownership / 代码所有权
96
+
97
+ - **`entity-ownership`** — Who owns which code (by lines added) / 谁拥有哪些代码(按新增行数)
98
+ - **`main-dev`** — Primary developer per file (by lines added). Ownership < 0.5 = no clear owner / 每个文件的主要开发者(按新增行数)。ownership < 0.5 表示没有明确负责人
99
+ - **`main-dev-by-revs`** — Primary developer per file (by commit count) / 按 commit 次数计算的主要开发者
100
+ - **`refactoring-main-dev`** — Who is doing refactoring (by lines deleted) / 谁在做重构(按删除行数)
101
+
102
+ #### Team & Collaboration / 团队与协作
103
+
104
+ - **`entity-effort`** — Effort distribution across files / 工作量分布
105
+ - **`fragmentation`** — Knowledge fragmentation level (0–1). High = many people touching code with no clear owner / 知识碎片化程度(0–1)。值高说明多人零散修改,无人真正负责
106
+ - **`communication`** — Developer collaboration network / 开发者协作网络
107
+ - **`identity`** — Raw data export for custom analysis / 原始数据导出,供自定义分析
108
+
109
+ ## CLI Options / 命令行选项
110
+
111
+ ```
112
+ roux <analysis> [options]
113
+ ```
114
+
115
+ ### Input / 输入
116
+
117
+ | Flag | Description / 说明 | Default / 默认值 |
118
+ |------|-------------|---------|
119
+ | `-l, --log <file>` | Git log file path / 日志文件路径 | reads current repo / 读取当前仓库 |
120
+ | `--repo <path>` | Git repository path / Git 仓库路径 | current directory / 当前目录 |
121
+ | `--after <date>` | Only commits after date / 只包含此日期之后的提交 (YYYY-MM-DD) | — |
122
+ | `--before <date>` | Only commits before date / 只包含此日期之前的提交 (YYYY-MM-DD) | — |
123
+ | `--rev <range>` | Git revision range / Git 版本范围 (e.g. `v1.0..v2.0`) | `--all` |
124
+ | `-c, --format <fmt>` | Log format / 日志格式: `git2` or `git` | `git2` |
125
+
126
+ Roux also reads from stdin / 也支持从标准输入读取:
127
+
128
+ ```bash
129
+ cat git.log | npx roux summary
130
+ ```
131
+
132
+ ### Thresholds / 阈值
133
+
134
+ | Flag | Description / 说明 | Default / 默认值 |
135
+ |--------------------------------|-------------------------------------------------------------------|------------------|
136
+ | `-n, --min-revs <n>` | Minimum revisions to include entity / 文件最少修改次数 | 5 |
137
+ | `-m, --min-shared-revs <n>` | Minimum shared revisions for coupling / 耦合分析最少共同修改次数 | 5 |
138
+ | `-i, --min-coupling <n>` | Minimum coupling percentage / 最低耦合百分比 | 30 |
139
+ | `-x, --max-coupling <n>` | Maximum coupling percentage / 最高耦合百分比 | 100 |
140
+ | `-s, --max-changeset-size <n>` | Ignore commits touching more files / 忽略涉及文件数超过此值的提交 | 30 |
141
+
142
+ ### Transforms / 数据变换
143
+
144
+ | Flag | Description / 说明 |
145
+ |--------------------------------|------------------------------------------------------------------------|
146
+ | `-g, --group <file>` | Map files to architectural groups (txt/json/md) / 将文件映射到架构分组 |
147
+ | `-p, --team-map <file>` | Map authors to teams (csv/json/md) / 将作者映射到团队 |
148
+ | `-t, --temporal-period <days>` | Sliding window aggregation / 滑动窗口聚合 |
149
+
150
+ ### Output / 输出
151
+
152
+ | Flag | Description / 说明 | Default / 默认值 |
153
+ |----------------------------------|----------------------------------------------------------|------------------|
154
+ | `-r, --rows <n>` | Limit output rows / 限制输出行数 | unlimited / 不限 |
155
+ | `-o, --output-format <fmt>` | `csv` or `json` | `csv` |
156
+ | `-d, --age-time-now <date>` | Reference date for age (YYYY-MM-DD) / age 分析的参考日期 | today / 今天 |
157
+ | `-e, --expression-to-match <re>` | Regex for messages analysis / messages 分析的正则表达式 | — |
158
+
159
+ ## Log Formats / 日志格式
160
+
161
+ ### git2 (default / 默认)
162
+
163
+ The recommended format. Generate with:
164
+
165
+ 推荐格式。生成方式:
166
+
167
+ ```bash
168
+ git log --all --numstat --date=short \
169
+ --pretty=format:'--%h--%ad--%aN--%s' --no-renames > git.log
170
+ ```
171
+
172
+ Example / 示例:
173
+
174
+ ```
175
+ --abc1234--2026-01-15--Alice--Fix parser bug
176
+ 12 4 src/parser.ts
177
+ 0 8 src/old-code.ts
178
+
179
+ --def5678--2026-01-14--Bob--Add feature
180
+ 45 0 src/feature.ts
181
+ ```
182
+
183
+ ### git (legacy / 旧版)
184
+
185
+ Compatible with code-maat's original git format.
186
+
187
+ 兼容 code-maat 原始 git 格式:
188
+
189
+ ```
190
+ [abc1234] Alice 2026-01-15 Fix parser bug
191
+ 12 4 src/parser.ts
192
+ 0 8 src/old-code.ts
193
+ ```
194
+
195
+ Use `-c git` to select this format. / 使用 `-c git` 选择此格式。
196
+
197
+ ## Transforms / 数据变换
198
+
199
+ ### Architectural Grouping / 架构分组 (`-g`)
200
+
201
+ Group files into logical layers for higher-level analysis.
202
+
203
+ 将文件分组到逻辑层,进行更高层次的分析。
204
+
205
+ Supports TXT, JSON, and Markdown formats (auto-detected by file extension).
206
+
207
+ 支持 TXT、JSON、Markdown 格式(按文件扩展名自动检测)。
208
+
209
+ **TXT** (`groups.txt`):
210
+ ```
211
+ src/api => API Layer
212
+ src/ui => UI Layer
213
+ src/models => Data Layer
214
+ ```
215
+
216
+ **JSON** (`groups.json`):
217
+ ```json
218
+ {
219
+ "src/api": "API Layer",
220
+ "src/ui": "UI Layer",
221
+ "^src\\/.*Test.*$": "Tests"
222
+ }
223
+ ```
224
+
225
+ **Markdown** (`groups.md`):
226
+ ```markdown
227
+ | path | group |
228
+ |------|-------|
229
+ | src/api | API Layer |
230
+ | src/ui | UI Layer |
231
+ | ^src\/.*Test.*$ | Tests |
232
+ ```
233
+
234
+ Regex patterns are also supported (must start with `^` and end with `$`):
235
+
236
+ 也支持正则表达式(必须以 `^` 开头,以 `$` 结尾):
237
+
238
+ ```
239
+ ^src\/.*Test.*$ => Tests
240
+ ^src\/((?!.*Test.*).).*$ => Production Code
241
+ ```
242
+
243
+ ```bash
244
+ npx roux coupling -l git.log -g groups.json
245
+ ```
246
+
247
+ ### Team Mapping / 团队映射 (`-p`)
248
+
249
+ Map individual authors to teams for team-level analysis.
250
+
251
+ 将个人作者映射到团队,进行团队级别的分析。
252
+
253
+ Supports CSV, JSON, and Markdown formats (auto-detected by file extension).
254
+
255
+ 支持 CSV、JSON、Markdown 格式(按文件扩展名自动检测)。
256
+
257
+ **CSV** (`teams.csv`):
258
+ ```csv
259
+ author,team
260
+ Alice,Backend
261
+ Bob,Backend
262
+ Charlie,Frontend
263
+ ```
264
+
265
+ **JSON** (`teams.json`):
266
+ ```json
267
+ {
268
+ "Alice": "Backend",
269
+ "Bob": "Backend",
270
+ "Charlie": "Frontend"
271
+ }
272
+ ```
273
+
274
+ **Markdown** (`teams.md`):
275
+ ```markdown
276
+ | author | team |
277
+ |--------|------|
278
+ | Alice | Backend |
279
+ | Bob | Backend |
280
+ | Charlie | Frontend |
281
+ ```
282
+
283
+ ```bash
284
+ npx roux communication -l git.log -p teams.json
285
+ ```
286
+
287
+ ### Temporal Coupling / 时间窗口耦合 (`-t`)
288
+
289
+ Analyze coupling within sliding time windows.
290
+
291
+ 在滑动时间窗口内分析耦合:
292
+
293
+ ```bash
294
+ npx roux coupling -l git.log -t 30 # 30-day windows / 30 天窗口
295
+ ```
296
+
297
+ ## Programmatic API / 编程接口
298
+
299
+ ```typescript
300
+ import { analyze, parseGitLog, generateGitLog } from "roux";
301
+
302
+ // Analyze a repo directly with time filters / 直接分析 repo,带时间过滤
303
+ const result = analyze({
304
+ analysis: "coupling",
305
+ repo: "/path/to/repo",
306
+ after: "2024-01-01",
307
+ before: "2025-01-01",
308
+ });
309
+ console.log(result); // CSV string
310
+
311
+ // Or with revision range / 或使用版本范围
312
+ const result2 = analyze({
313
+ analysis: "revisions",
314
+ repo: "/path/to/repo",
315
+ rev: "v1.0..v2.0",
316
+ outputFormat: "json",
317
+ });
318
+
319
+ // Or parse a log file manually / 或手动解析日志文件
320
+ const log = fs.readFileSync("git.log", "utf-8");
321
+ const mods = parseGitLog(log);
322
+ const result3 = analyze({ analysis: "summary", input: log });
323
+ ```
324
+
325
+ ## Development / 开发
326
+
327
+ ```bash
328
+ npm install
329
+ npm test # 94 unit tests / 94 个单元测试
330
+ bash test/acceptance/run.sh # 101 acceptance tests (requires code-maat) / 101 个验收测试(需要 code-maat)
331
+ ```
332
+
333
+ ## License / 许可证
334
+
335
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { Modification } from "../parsers/types";
2
+ import type { AnalysisOptions } from "./types";
3
+ export declare function age(data: Modification[], options: AnalysisOptions): Record<string, unknown>[];
@@ -0,0 +1,32 @@
1
+ import { groupBy, orderBy } from "../utils/dataset";
2
+ /** Parse "YYYY-MM-DD" into { year, month, day } without timezone issues */
3
+ function parseYMD(dateStr) {
4
+ const [y, m, d] = dateStr.split("-").map(Number);
5
+ return { year: y, month: m, day: d };
6
+ }
7
+ /** Calculate months between two dates, matching java.time.Period semantics */
8
+ function monthsBetween(from, to) {
9
+ let months = (to.year - from.year) * 12 + (to.month - from.month);
10
+ if (to.day < from.day) {
11
+ months--;
12
+ }
13
+ return months;
14
+ }
15
+ export function age(data, options) {
16
+ const now = options.ageTimeNow
17
+ ? parseYMD(options.ageTimeNow)
18
+ : (() => {
19
+ const d = new Date();
20
+ return { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() };
21
+ })();
22
+ const byEntity = groupBy(data, "entity");
23
+ const result = [];
24
+ for (const [entity, mods] of byEntity) {
25
+ const latestDate = mods.reduce((latest, m) => m.date > latest ? m.date : latest, mods[0].date);
26
+ const from = parseYMD(latestDate);
27
+ const months = monthsBetween(from, now);
28
+ result.push({ entity: entity, "age-months": months });
29
+ }
30
+ return orderBy(result, "age-months", "asc");
31
+ }
32
+ //# sourceMappingURL=age.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age.js","sourceRoot":"","sources":["../../src/analysis/age.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEpD,2EAA2E;AAC3E,SAAS,QAAQ,CAAC,OAAe;IAC/B,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,8EAA8E;AAC9E,SAAS,aAAa,CACpB,IAAkD,EAClD,EAAgD;IAEhD,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,IAAI,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,EAAE,CAAC;IACX,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,GAAG,CACjB,IAAoB,EACpB,OAAwB;IAExB,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU;QAC5B,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC;QAC9B,CAAC,CAAC,CAAC,GAAG,EAAE;YACJ,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9E,CAAC,CAAC,EAAE,CAAC;IAET,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,MAAM,GAA8B,EAAE,CAAC;IAE7C,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAC3C,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;QAClC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAgB,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Modification } from "../parsers/types";
2
+ import type { AnalysisOptions } from "./types";
3
+ export declare function authors(data: Modification[], options: AnalysisOptions): Record<string, unknown>[];
@@ -0,0 +1,18 @@
1
+ import { groupBy, orderBy } from "../utils/dataset";
2
+ export function authors(data, options) {
3
+ const byEntity = groupBy(data, "entity");
4
+ const result = [];
5
+ for (const [entity, mods] of byEntity) {
6
+ const uniqueAuthors = new Set(mods.map((m) => m.author));
7
+ const nRevs = mods.length;
8
+ if (nRevs >= options.minRevs) {
9
+ result.push({
10
+ entity: entity,
11
+ "n-authors": uniqueAuthors.size,
12
+ "n-revs": nRevs,
13
+ });
14
+ }
15
+ }
16
+ return orderBy(result, "n-authors", "desc");
17
+ }
18
+ //# sourceMappingURL=authors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authors.js","sourceRoot":"","sources":["../../src/analysis/authors.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,UAAU,OAAO,CACrB,IAAoB,EACpB,OAAwB;IAExB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,MAAM,GAA8B,EAAE,CAAC;IAE7C,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC;QACtC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC;gBACV,MAAM,EAAE,MAAgB;gBACxB,WAAW,EAAE,aAAa,CAAC,IAAI;gBAC/B,QAAQ,EAAE,KAAK;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Modification } from "../parsers/types";
2
+ import type { AnalysisOptions } from "./types";
3
+ export declare function absoluteChurn(data: Modification[], _options: AnalysisOptions): Record<string, unknown>[];
4
+ export declare function churnByEntity(data: Modification[], _options: AnalysisOptions): Record<string, unknown>[];
5
+ export declare function churnByAuthor(data: Modification[], _options: AnalysisOptions): Record<string, unknown>[];
@@ -0,0 +1,39 @@
1
+ import { groupBy, orderBy } from "../utils/dataset";
2
+ /** Normalize binary (-1) to 0 for churn calculations */
3
+ function loc(value) {
4
+ return value < 0 ? 0 : value;
5
+ }
6
+ export function absoluteChurn(data, _options) {
7
+ const byDate = groupBy(data, "date");
8
+ const result = [];
9
+ for (const [date, mods] of byDate) {
10
+ const commits = new Set(mods.map((m) => m.rev)).size;
11
+ const added = mods.reduce((sum, m) => sum + loc(m.locAdded), 0);
12
+ const deleted = mods.reduce((sum, m) => sum + loc(m.locDeleted), 0);
13
+ result.push({ date: date, added, deleted, commits });
14
+ }
15
+ return result.sort((a, b) => a.date.localeCompare(b.date));
16
+ }
17
+ export function churnByEntity(data, _options) {
18
+ const byEntity = groupBy(data, "entity");
19
+ const result = [];
20
+ for (const [entity, mods] of byEntity) {
21
+ const commits = new Set(mods.map((m) => m.rev)).size;
22
+ const added = mods.reduce((sum, m) => sum + loc(m.locAdded), 0);
23
+ const deleted = mods.reduce((sum, m) => sum + loc(m.locDeleted), 0);
24
+ result.push({ entity: entity, added, deleted, commits });
25
+ }
26
+ return orderBy(result, "added", "desc");
27
+ }
28
+ export function churnByAuthor(data, _options) {
29
+ const byAuthor = groupBy(data, "author");
30
+ const result = [];
31
+ for (const [author, mods] of byAuthor) {
32
+ const commits = new Set(mods.map((m) => m.rev)).size;
33
+ const added = mods.reduce((sum, m) => sum + loc(m.locAdded), 0);
34
+ const deleted = mods.reduce((sum, m) => sum + loc(m.locDeleted), 0);
35
+ result.push({ author: author, added, deleted, commits });
36
+ }
37
+ return orderBy(result, "added", "desc");
38
+ }
39
+ //# sourceMappingURL=churn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"churn.js","sourceRoot":"","sources":["../../src/analysis/churn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Modification } from "../parsers/types";
2
+ import type { AnalysisOptions } from "./types";
3
+ export declare function communication(data: Modification[], _options: AnalysisOptions): Record<string, unknown>[];
@@ -0,0 +1,44 @@
1
+ import { groupBy, orderBy } from "../utils/dataset";
2
+ export function communication(data, _options) {
3
+ const byEntity = groupBy(data, "entity");
4
+ // Build a map: author -> set of entities they contribute to
5
+ const authorEntities = new Map();
6
+ // Build a map: entity -> set of unique authors
7
+ const entityAuthors = new Map();
8
+ for (const [entity, mods] of byEntity) {
9
+ const authors = new Set(mods.map((m) => m.author));
10
+ entityAuthors.set(entity, authors);
11
+ for (const author of authors) {
12
+ let entities = authorEntities.get(author);
13
+ if (!entities) {
14
+ entities = new Set();
15
+ authorEntities.set(author, entities);
16
+ }
17
+ entities.add(entity);
18
+ }
19
+ }
20
+ // Count shared entities for each ordered pair
21
+ const pairShared = new Map();
22
+ for (const [, authors] of entityAuthors) {
23
+ const authorList = [...authors];
24
+ for (let i = 0; i < authorList.length; i++) {
25
+ for (let j = 0; j < authorList.length; j++) {
26
+ if (i === j)
27
+ continue;
28
+ const key = `${authorList[i]}\0${authorList[j]}`;
29
+ pairShared.set(key, (pairShared.get(key) ?? 0) + 1);
30
+ }
31
+ }
32
+ }
33
+ const result = [];
34
+ for (const [key, shared] of pairShared) {
35
+ const [author, peer] = key.split("\0");
36
+ const selfCountAuthor = authorEntities.get(author).size;
37
+ const selfCountPeer = authorEntities.get(peer).size;
38
+ const average = Math.ceil((selfCountAuthor + selfCountPeer) / 2);
39
+ const strength = Math.floor((shared / average) * 100);
40
+ result.push({ author, peer, shared, average, strength });
41
+ }
42
+ return orderBy(result, "strength", "desc");
43
+ }
44
+ //# sourceMappingURL=communication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"communication.js","sourceRoot":"","sources":["../../src/analysis/communication.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Modification } from "../parsers/types";
2
+ import type { AnalysisOptions } from "./types";
3
+ export declare function coupling(data: Modification[], options: AnalysisOptions): Record<string, unknown>[];
4
+ export declare function sumOfCoupling(data: Modification[], options: AnalysisOptions): Record<string, unknown>[];
@@ -0,0 +1,84 @@
1
+ import { groupBy, orderBy } from "../utils/dataset";
2
+ /** Group modifications by revision, return map of rev → entities */
3
+ function entitiesByRevision(data, maxChangesetSize) {
4
+ const byRev = groupBy(data, "rev");
5
+ const result = new Map();
6
+ for (const [rev, mods] of byRev) {
7
+ const entities = [...new Set(mods.map((m) => m.entity))];
8
+ if (entities.length <= maxChangesetSize) {
9
+ result.set(rev, entities);
10
+ }
11
+ }
12
+ return result;
13
+ }
14
+ /** Count how many times each pair of entities co-changed */
15
+ function couplingFrequencies(byRevision) {
16
+ const freqs = new Map();
17
+ for (const entities of byRevision.values()) {
18
+ const sorted = [...entities].sort();
19
+ for (let i = 0; i < sorted.length; i++) {
20
+ for (let j = i + 1; j < sorted.length; j++) {
21
+ const key = `${sorted[i]}||${sorted[j]}`;
22
+ freqs.set(key, (freqs.get(key) ?? 0) + 1);
23
+ }
24
+ }
25
+ }
26
+ return freqs;
27
+ }
28
+ /** Count total revisions per entity */
29
+ function revisionsByEntity(data) {
30
+ const byEntity = groupBy(data, "entity");
31
+ const result = new Map();
32
+ for (const [entity, mods] of byEntity) {
33
+ result.set(entity, new Set(mods.map((m) => m.rev)).size);
34
+ }
35
+ return result;
36
+ }
37
+ export function coupling(data, options) {
38
+ const byRevision = entitiesByRevision(data, options.maxChangesetSize);
39
+ const freqs = couplingFrequencies(byRevision);
40
+ const revsByEntity = revisionsByEntity(data);
41
+ const result = [];
42
+ for (const [key, sharedRevs] of freqs) {
43
+ const [e1, e2] = key.split("||");
44
+ const revs1 = revsByEntity.get(e1) ?? 0;
45
+ const revs2 = revsByEntity.get(e2) ?? 0;
46
+ const avgRevs = (revs1 + revs2) / 2;
47
+ const degree = Math.floor((sharedRevs / avgRevs) * 100);
48
+ const avgRevsRounded = Math.round(avgRevs);
49
+ if (sharedRevs >= options.minSharedRevs &&
50
+ degree >= options.minCoupling &&
51
+ degree <= options.maxCoupling &&
52
+ revs1 >= options.minRevs &&
53
+ revs2 >= options.minRevs) {
54
+ result.push({
55
+ entity: e1,
56
+ coupled: e2,
57
+ degree,
58
+ "average-revs": avgRevsRounded,
59
+ });
60
+ }
61
+ }
62
+ return orderBy(result, "degree", "desc");
63
+ }
64
+ export function sumOfCoupling(data, options) {
65
+ // SOC uses non-deduplicated entities per revision (matches code-maat row-level counting)
66
+ const byRev = groupBy(data, "rev");
67
+ const soc = new Map();
68
+ for (const [, mods] of byRev) {
69
+ const entities = mods.map((m) => m.entity);
70
+ if (entities.length > options.maxChangesetSize)
71
+ continue;
72
+ for (const entity of entities) {
73
+ soc.set(entity, (soc.get(entity) ?? 0) + (entities.length - 1));
74
+ }
75
+ }
76
+ const result = [];
77
+ for (const [entity, value] of soc) {
78
+ if (value > options.minRevs) {
79
+ result.push({ entity, soc: value });
80
+ }
81
+ }
82
+ return orderBy(result, "soc", "desc");
83
+ }
84
+ //# sourceMappingURL=coupling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coupling.js","sourceRoot":"","sources":["../../src/analysis/coupling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Modification } from "../parsers/types";
2
+ import type { AnalysisOptions } from "./types";
3
+ export declare function entityEffort(data: Modification[], _options: AnalysisOptions): Record<string, unknown>[];
@@ -0,0 +1,36 @@
1
+ import { groupBy } from "../utils/dataset";
2
+ export function entityEffort(data, _options) {
3
+ const byEntity = groupBy(data, "entity");
4
+ const result = [];
5
+ for (const [entity, mods] of byEntity) {
6
+ // Group by author within entity, counting unique revisions
7
+ const authorRevs = new Map();
8
+ for (const m of mods) {
9
+ const revs = authorRevs.get(m.author) ?? new Set();
10
+ revs.add(m.rev);
11
+ authorRevs.set(m.author, revs);
12
+ }
13
+ // total-revs = sum of all author-revs
14
+ let totalRevs = 0;
15
+ for (const revs of authorRevs.values()) {
16
+ totalRevs += revs.size;
17
+ }
18
+ // Build rows for each author
19
+ const entityRows = [];
20
+ for (const [author, revs] of authorRevs) {
21
+ entityRows.push({
22
+ entity: entity,
23
+ author,
24
+ "author-revs": revs.size,
25
+ "total-revs": totalRevs,
26
+ });
27
+ }
28
+ // Sort by author-revs desc within entity
29
+ entityRows.sort((a, b) => b["author-revs"] - a["author-revs"]);
30
+ result.push(...entityRows);
31
+ }
32
+ // Sort by entity asc (stable — preserves author-revs desc within entity)
33
+ result.sort((a, b) => a.entity.localeCompare(b.entity));
34
+ return result;
35
+ }
36
+ //# sourceMappingURL=entity-effort.js.map