@girardelli/architect 2.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +105 -116
  2. package/__test_agent_output__/INDEX.md +1 -0
  3. package/__test_agent_output__/agents/AGENT-ORCHESTRATOR.md +1 -0
  4. package/__test_agent_output__/agents/DATABASE-ENGINEER.md +174 -0
  5. package/__test_agent_output__/agents/QA-TEST-ENGINEER.md +138 -0
  6. package/__test_agent_output__/agents/SECURITY-AUDITOR.md +106 -0
  7. package/__test_agent_output__/agents/TECH-DEBT-CONTROLLER.md +104 -0
  8. package/__test_agent_output__/agents/TYPESCRIPT-BACKEND-DEVELOPER.md +135 -0
  9. package/__test_agent_output__/guards/CODE-REVIEW-CHECKLIST.md +95 -0
  10. package/__test_agent_output__/guards/PREFLIGHT.md +200 -0
  11. package/__test_agent_output__/guards/QUALITY-GATES.md +1 -0
  12. package/__test_agent_output__/rules/00-general.md +229 -0
  13. package/__test_agent_output__/rules/01-architecture.md +191 -0
  14. package/__test_agent_output__/rules/02-security.md +402 -0
  15. package/__test_agent_output__/rules/03-nestjs.md +124 -0
  16. package/__test_agent_output__/templates/ADR.md +95 -0
  17. package/__test_agent_output__/templates/BDD.md +58 -0
  18. package/__test_agent_output__/templates/C4.md +68 -0
  19. package/__test_agent_output__/templates/TDD.md +86 -0
  20. package/__test_agent_output__/templates/THREAT-MODEL.md +82 -0
  21. package/__test_agent_output__/workflows/fix-bug.md +228 -0
  22. package/__test_agent_output__/workflows/new-feature.md +311 -0
  23. package/__test_agent_output__/workflows/review.md +95 -0
  24. package/__test_context_7RvUrO/src/modules/empty/empty.ts +0 -0
  25. package/__test_context_Rf5fNJ/src/modules/mixed/mixed.ts +5 -0
  26. package/__test_context_WRCnYH/src/modules/test/test.ts +10 -0
  27. package/__test_context_YsnVS3/src/modules/test/test.ts +10 -0
  28. package/__test_context_w7XZeH/src/modules/mixed/mixed.ts +5 -0
  29. package/__test_context_y5noh6/src/modules/empty/empty.ts +0 -0
  30. package/__test_framework__24OjAu/package.json +1 -0
  31. package/__test_framework__3ZDZsx/pyproject.toml +8 -0
  32. package/__test_framework__4T54Jn/package.json +1 -0
  33. package/__test_framework__4tlXu9/pyproject.toml +8 -0
  34. package/__test_framework__6boWqQ/Pipfile +6 -0
  35. package/__test_framework__6gygMU/pom.xml +10 -0
  36. package/__test_framework__6kxj0N/go.mod +8 -0
  37. package/__test_framework__7CEoXw/pom.xml +10 -0
  38. package/__test_framework__85DDz0/Pipfile +6 -0
  39. package/__test_framework__9WrRIr/pom.xml +7 -0
  40. package/__test_framework__ANqGKl/Gemfile +5 -0
  41. package/__test_framework__BCXTEM/go.mod +3 -0
  42. package/__test_framework__BHiPNq/setup.py +2 -0
  43. package/__test_framework__BqkiKv/package.json +1 -0
  44. package/__test_framework__C5yd8X/Pipfile.lock +1 -0
  45. package/__test_framework__C5yd8X/requirements.txt +1 -0
  46. package/__test_framework__C87d3a/manage.py +1 -0
  47. package/__test_framework__C87d3a/requirements.txt +2 -0
  48. package/__test_framework__DXNwc5/build.gradle +7 -0
  49. package/__test_framework__GhHSt3/build.gradle.kts +4 -0
  50. package/__test_framework__GzklJP/Cargo.toml +7 -0
  51. package/__test_framework__H4hd13/go.mod +8 -0
  52. package/__test_framework__HKjOXO/composer.json +1 -0
  53. package/__test_framework__HaDN45/Gemfile +3 -0
  54. package/__test_framework__IBO7YG/pyproject.toml +9 -0
  55. package/__test_framework__JwSOyF/pyproject.toml +6 -0
  56. package/__test_framework__K6HrCr/build.gradle +2 -0
  57. package/__test_framework__KzRPlh/pubspec.yaml +9 -0
  58. package/__test_framework__L6uIym/pyproject.toml +6 -0
  59. package/__test_framework__LOdoGK/requirements.txt +4 -0
  60. package/__test_framework__LgHzss/package.json +1 -0
  61. package/__test_framework__M76M6q/Gemfile +5 -0
  62. package/__test_framework__Mr9vWW/composer.json +1 -0
  63. package/__test_framework__N03Gnv/package.json +1 -0
  64. package/__test_framework__Num4UE/requirements +1 -0
  65. package/__test_framework__OAGw3Y/build.gradle +7 -0
  66. package/__test_framework__OQc8yG/pubspec.yaml +9 -0
  67. package/__test_framework__OwKZcd/requirements.txt +3 -0
  68. package/__test_framework__P0gFv7/requirements +1 -0
  69. package/__test_framework__PN55Rq/package.json +1 -0
  70. package/__test_framework__PQiqX8/pubspec.yaml +3 -0
  71. package/__test_framework__RBHsg7/composer.json +1 -0
  72. package/__test_framework__RHxif4/Cargo.toml +7 -0
  73. package/__test_framework__T0v0p1/Cargo.toml +4 -0
  74. package/__test_framework__Tu0clt/Pipfile.lock +1 -0
  75. package/__test_framework__Tu0clt/requirements.txt +1 -0
  76. package/__test_framework__TwDj9P/Cargo.toml +4 -0
  77. package/__test_framework__VQJNC4/pom.xml +7 -0
  78. package/__test_framework__W6sm05/package.json +1 -0
  79. package/__test_framework__W7vBLy/pyproject.toml +4 -0
  80. package/__test_framework__WNJOWT/setup.py +2 -0
  81. package/__test_framework__WSJs7U/package.json +1 -0
  82. package/__test_framework__YQ5VpA/build.gradle.kts +4 -0
  83. package/__test_framework__ZNEUEs/package.json +1 -0
  84. package/__test_framework__Znt922/pom.xml +7 -0
  85. package/__test_framework__azyg0h/pom.xml +7 -0
  86. package/__test_framework__c6otLr/package.json +1 -0
  87. package/__test_framework__cl9S9G/build.gradle +2 -0
  88. package/__test_framework__eilvV4/composer.json +1 -0
  89. package/__test_framework__gQZxXO/manage.py +1 -0
  90. package/__test_framework__gQZxXO/requirements.txt +2 -0
  91. package/__test_framework__ghvl26/poetry.lock +1 -0
  92. package/__test_framework__ghvl26/pyproject.toml +2 -0
  93. package/__test_framework__hR7b9U/Makefile +11 -0
  94. package/__test_framework__iESVsi/composer.json +1 -0
  95. package/__test_framework__jm6TJy/package.json +1 -0
  96. package/__test_framework__kBUpjs/pyproject.toml +9 -0
  97. package/__test_framework__kqoZrw/requirements.txt +4 -0
  98. package/__test_framework__lWkoyO/pyproject.toml +4 -0
  99. package/__test_framework__mTKnUO/package.json +1 -0
  100. package/__test_framework__nCeZwe/Makefile +11 -0
  101. package/__test_framework__oljsU0/package.json +1 -0
  102. package/__test_framework__osRG4q/go.mod +3 -0
  103. package/__test_framework__pCHH4F/package.json +1 -0
  104. package/__test_framework__pExx6E/Gemfile +3 -0
  105. package/__test_framework__pyBoGd/pyproject.toml +5 -0
  106. package/__test_framework__qw16VQ/package.json +1 -0
  107. package/__test_framework__rRayrG/package.json +1 -0
  108. package/__test_framework__s82zO5/package.json +1 -0
  109. package/__test_framework__tp8MFK/pyproject.toml +5 -0
  110. package/__test_framework__w44k4w/composer.json +1 -0
  111. package/__test_framework__yefPZY/poetry.lock +1 -0
  112. package/__test_framework__yefPZY/pyproject.toml +2 -0
  113. package/__test_framework__zCiyDT/requirements.txt +3 -0
  114. package/__test_framework__zGZN3j/pubspec.yaml +3 -0
  115. package/__test_framework__zXpnxL/package.json +1 -0
  116. package/architect-run.sh +431 -0
  117. package/assets/banner-v3.html +561 -0
  118. package/dist/agent-generator/context-enricher.d.ts +58 -0
  119. package/dist/agent-generator/context-enricher.d.ts.map +1 -0
  120. package/dist/agent-generator/context-enricher.js +581 -0
  121. package/dist/agent-generator/context-enricher.js.map +1 -0
  122. package/dist/agent-generator/domain-inferrer.d.ts +52 -0
  123. package/dist/agent-generator/domain-inferrer.d.ts.map +1 -0
  124. package/dist/agent-generator/domain-inferrer.js +575 -0
  125. package/dist/agent-generator/domain-inferrer.js.map +1 -0
  126. package/dist/agent-generator/framework-detector.d.ts +40 -0
  127. package/dist/agent-generator/framework-detector.d.ts.map +1 -0
  128. package/dist/agent-generator/framework-detector.js +611 -0
  129. package/dist/agent-generator/framework-detector.js.map +1 -0
  130. package/dist/agent-generator/index.d.ts +33 -0
  131. package/dist/agent-generator/index.d.ts.map +1 -0
  132. package/dist/agent-generator/index.js +477 -0
  133. package/dist/agent-generator/index.js.map +1 -0
  134. package/dist/agent-generator/stack-detector.d.ts +12 -0
  135. package/dist/agent-generator/stack-detector.d.ts.map +1 -0
  136. package/dist/agent-generator/stack-detector.js +128 -0
  137. package/dist/agent-generator/stack-detector.js.map +1 -0
  138. package/dist/agent-generator/templates/core/agents.d.ts +17 -0
  139. package/dist/agent-generator/templates/core/agents.d.ts.map +1 -0
  140. package/dist/agent-generator/templates/core/agents.js +1252 -0
  141. package/dist/agent-generator/templates/core/agents.js.map +1 -0
  142. package/dist/agent-generator/templates/core/architecture-rules.d.ts +7 -0
  143. package/dist/agent-generator/templates/core/architecture-rules.d.ts.map +1 -0
  144. package/dist/agent-generator/templates/core/architecture-rules.js +274 -0
  145. package/dist/agent-generator/templates/core/architecture-rules.js.map +1 -0
  146. package/dist/agent-generator/templates/core/general-rules.d.ts +8 -0
  147. package/dist/agent-generator/templates/core/general-rules.d.ts.map +1 -0
  148. package/dist/agent-generator/templates/core/general-rules.js +301 -0
  149. package/dist/agent-generator/templates/core/general-rules.js.map +1 -0
  150. package/dist/agent-generator/templates/core/index-md.d.ts +7 -0
  151. package/dist/agent-generator/templates/core/index-md.d.ts.map +1 -0
  152. package/dist/agent-generator/templates/core/index-md.js +246 -0
  153. package/dist/agent-generator/templates/core/index-md.js.map +1 -0
  154. package/dist/agent-generator/templates/core/orchestrator.d.ts +8 -0
  155. package/dist/agent-generator/templates/core/orchestrator.d.ts.map +1 -0
  156. package/dist/agent-generator/templates/core/orchestrator.js +422 -0
  157. package/dist/agent-generator/templates/core/orchestrator.js.map +1 -0
  158. package/dist/agent-generator/templates/core/preflight.d.ts +8 -0
  159. package/dist/agent-generator/templates/core/preflight.d.ts.map +1 -0
  160. package/dist/agent-generator/templates/core/preflight.js +213 -0
  161. package/dist/agent-generator/templates/core/preflight.js.map +1 -0
  162. package/dist/agent-generator/templates/core/quality-gates.d.ts +11 -0
  163. package/dist/agent-generator/templates/core/quality-gates.d.ts.map +1 -0
  164. package/dist/agent-generator/templates/core/quality-gates.js +254 -0
  165. package/dist/agent-generator/templates/core/quality-gates.js.map +1 -0
  166. package/dist/agent-generator/templates/core/security-rules.d.ts +7 -0
  167. package/dist/agent-generator/templates/core/security-rules.d.ts.map +1 -0
  168. package/dist/agent-generator/templates/core/security-rules.js +528 -0
  169. package/dist/agent-generator/templates/core/security-rules.js.map +1 -0
  170. package/dist/agent-generator/templates/core/skills-generator.d.ts +6 -0
  171. package/dist/agent-generator/templates/core/skills-generator.d.ts.map +1 -0
  172. package/dist/agent-generator/templates/core/skills-generator.js +207 -0
  173. package/dist/agent-generator/templates/core/skills-generator.js.map +1 -0
  174. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts +7 -0
  175. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts.map +1 -0
  176. package/dist/agent-generator/templates/core/workflow-fix-bug.js +237 -0
  177. package/dist/agent-generator/templates/core/workflow-fix-bug.js.map +1 -0
  178. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts +8 -0
  179. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts.map +1 -0
  180. package/dist/agent-generator/templates/core/workflow-new-feature.js +321 -0
  181. package/dist/agent-generator/templates/core/workflow-new-feature.js.map +1 -0
  182. package/dist/agent-generator/templates/core/workflow-review.d.ts +7 -0
  183. package/dist/agent-generator/templates/core/workflow-review.d.ts.map +1 -0
  184. package/dist/agent-generator/templates/core/workflow-review.js +104 -0
  185. package/dist/agent-generator/templates/core/workflow-review.js.map +1 -0
  186. package/dist/agent-generator/templates/domain/index.d.ts +22 -0
  187. package/dist/agent-generator/templates/domain/index.d.ts.map +1 -0
  188. package/dist/agent-generator/templates/domain/index.js +1176 -0
  189. package/dist/agent-generator/templates/domain/index.js.map +1 -0
  190. package/dist/agent-generator/templates/stack/index.d.ts +8 -0
  191. package/dist/agent-generator/templates/stack/index.d.ts.map +1 -0
  192. package/dist/agent-generator/templates/stack/index.js +695 -0
  193. package/dist/agent-generator/templates/stack/index.js.map +1 -0
  194. package/dist/agent-generator/templates/template-helpers.d.ts +75 -0
  195. package/dist/agent-generator/templates/template-helpers.d.ts.map +1 -0
  196. package/dist/agent-generator/templates/template-helpers.js +726 -0
  197. package/dist/agent-generator/templates/template-helpers.js.map +1 -0
  198. package/dist/agent-generator/types.d.ts +196 -0
  199. package/dist/agent-generator/types.d.ts.map +1 -0
  200. package/dist/agent-generator/types.js +27 -0
  201. package/dist/agent-generator/types.js.map +1 -0
  202. package/dist/analyzer.d.ts +5 -0
  203. package/dist/analyzer.d.ts.map +1 -1
  204. package/dist/analyzer.js +35 -4
  205. package/dist/analyzer.js.map +1 -1
  206. package/dist/analyzers/forecast.d.ts +85 -0
  207. package/dist/analyzers/forecast.d.ts.map +1 -0
  208. package/dist/analyzers/forecast.js +337 -0
  209. package/dist/analyzers/forecast.js.map +1 -0
  210. package/dist/analyzers/git-cache.d.ts +7 -0
  211. package/dist/analyzers/git-cache.d.ts.map +1 -0
  212. package/dist/analyzers/git-cache.js +41 -0
  213. package/dist/analyzers/git-cache.js.map +1 -0
  214. package/dist/analyzers/git-history.d.ts +113 -0
  215. package/dist/analyzers/git-history.d.ts.map +1 -0
  216. package/dist/analyzers/git-history.js +333 -0
  217. package/dist/analyzers/git-history.js.map +1 -0
  218. package/dist/analyzers/index.d.ts +10 -0
  219. package/dist/analyzers/index.d.ts.map +1 -0
  220. package/dist/analyzers/index.js +7 -0
  221. package/dist/analyzers/index.js.map +1 -0
  222. package/dist/analyzers/temporal-scorer.d.ts +72 -0
  223. package/dist/analyzers/temporal-scorer.d.ts.map +1 -0
  224. package/dist/analyzers/temporal-scorer.js +140 -0
  225. package/dist/analyzers/temporal-scorer.js.map +1 -0
  226. package/dist/cli.d.ts +2 -3
  227. package/dist/cli.d.ts.map +1 -1
  228. package/dist/cli.js +275 -113
  229. package/dist/cli.js.map +1 -1
  230. package/dist/html-reporter.d.ts +3 -1
  231. package/dist/html-reporter.d.ts.map +1 -1
  232. package/dist/html-reporter.js +248 -12
  233. package/dist/html-reporter.js.map +1 -1
  234. package/dist/index.d.ts +16 -3
  235. package/dist/index.d.ts.map +1 -1
  236. package/dist/index.js +63 -4
  237. package/dist/index.js.map +1 -1
  238. package/dist/project-summarizer.d.ts +18 -0
  239. package/dist/project-summarizer.d.ts.map +1 -0
  240. package/dist/project-summarizer.js +306 -0
  241. package/dist/project-summarizer.js.map +1 -0
  242. package/dist/refactor-reporter.js +1 -1
  243. package/dist/types.d.ts +13 -0
  244. package/dist/types.d.ts.map +1 -1
  245. package/package.json +12 -3
  246. package/src/agent-generator/context-enricher.ts +643 -0
  247. package/src/agent-generator/domain-inferrer.ts +625 -0
  248. package/src/agent-generator/framework-detector.ts +669 -0
  249. package/src/agent-generator/index.ts +555 -0
  250. package/src/agent-generator/stack-detector.ts +103 -0
  251. package/src/agent-generator/templates/core/agents.ts +1293 -0
  252. package/src/agent-generator/templates/core/architecture-rules.ts +287 -0
  253. package/src/agent-generator/templates/core/general-rules.ts +306 -0
  254. package/src/agent-generator/templates/core/index-md.ts +260 -0
  255. package/src/agent-generator/templates/core/orchestrator.ts +459 -0
  256. package/src/agent-generator/templates/core/preflight.ts +215 -0
  257. package/src/agent-generator/templates/core/quality-gates.ts +256 -0
  258. package/src/agent-generator/templates/core/security-rules.ts +543 -0
  259. package/src/agent-generator/templates/core/skills-generator.ts +236 -0
  260. package/src/agent-generator/templates/core/workflow-fix-bug.ts +239 -0
  261. package/src/agent-generator/templates/core/workflow-new-feature.ts +323 -0
  262. package/src/agent-generator/templates/core/workflow-review.ts +106 -0
  263. package/src/agent-generator/templates/domain/index.ts +1201 -0
  264. package/src/agent-generator/templates/stack/index.ts +705 -0
  265. package/src/agent-generator/templates/template-helpers.ts +776 -0
  266. package/src/agent-generator/types.ts +232 -0
  267. package/src/analyzer.ts +38 -4
  268. package/src/analyzers/forecast.ts +496 -0
  269. package/src/analyzers/git-cache.ts +52 -0
  270. package/src/analyzers/git-history.ts +488 -0
  271. package/src/analyzers/index.ts +33 -0
  272. package/src/analyzers/temporal-scorer.ts +227 -0
  273. package/src/cli.ts +316 -117
  274. package/src/html-reporter.ts +263 -13
  275. package/src/index.ts +92 -9
  276. package/src/project-summarizer.ts +347 -0
  277. package/src/refactor-reporter.ts +1 -1
  278. package/src/types.ts +10 -0
  279. package/tests/agent-generator.test.ts +411 -0
  280. package/tests/analyzers-integration.test.ts +174 -0
  281. package/tests/architect-adapter-enrichment.test.ts +9 -0
  282. package/tests/context-enricher.test.ts +971 -0
  283. package/tests/forecast.test.ts +509 -0
  284. package/tests/framework-detector.test.ts +1172 -0
  285. package/tests/git-history.test.ts +254 -0
  286. package/tests/scanner.test.ts +7 -8
  287. package/tests/scorer.test.ts +588 -0
  288. package/tests/stack-detector.test.ts +241 -0
  289. package/tests/template-generation.test.ts +706 -0
  290. package/tests/template-helpers.test.ts +1152 -0
  291. package/tests/temporal-scorer.test.ts +307 -0
  292. package/dist/agent-generator.d.ts +0 -106
  293. package/dist/agent-generator.d.ts.map +0 -1
  294. package/dist/agent-generator.js +0 -1398
  295. package/dist/agent-generator.js.map +0 -1
  296. package/src/agent-generator.ts +0 -1526
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Tests for GitHistoryAnalyzer
3
+ *
4
+ * Validates git log parsing, file history building, velocity vectors,
5
+ * change coupling detection, and timeline construction.
6
+ */
7
+
8
+ import { GitHistoryAnalyzer } from '../src/analyzers/git-history.js';
9
+ import type {
10
+ GitHistoryReport,
11
+ FileHistory,
12
+ ModuleHistory,
13
+ VelocityVector,
14
+ } from '../src/analyzers/git-history.js';
15
+ import { execSync } from 'child_process';
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+
19
+ // ═══════════════════════════════════════════════════════════════
20
+ // TEST HELPERS
21
+ // ═══════════════════════════════════════════════════════════════
22
+
23
+ const TEST_DIR = path.join('/tmp', 'architect-git-history-test');
24
+
25
+ function setupGitRepo(): void {
26
+ fs.rmSync(TEST_DIR, { recursive: true, force: true });
27
+ fs.mkdirSync(TEST_DIR, { recursive: true });
28
+
29
+ execSync('git init', { cwd: TEST_DIR, stdio: 'pipe' });
30
+ execSync('git config user.email "test@test.com"', { cwd: TEST_DIR, stdio: 'pipe' });
31
+ execSync('git config user.name "TestUser"', { cwd: TEST_DIR, stdio: 'pipe' });
32
+ }
33
+
34
+ function commitFile(
35
+ filePath: string,
36
+ content: string,
37
+ message: string,
38
+ author = 'TestUser <test@test.com>',
39
+ ): void {
40
+ const fullPath = path.join(TEST_DIR, filePath);
41
+ const dir = path.dirname(fullPath);
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ fs.writeFileSync(fullPath, content);
44
+ execSync(`git add "${filePath}"`, { cwd: TEST_DIR, stdio: 'pipe' });
45
+ execSync(`git commit --author="${author}" -m "${message}"`, {
46
+ cwd: TEST_DIR,
47
+ stdio: 'pipe',
48
+ });
49
+ }
50
+
51
+ function cleanupRepo(): void {
52
+ fs.rmSync(TEST_DIR, { recursive: true, force: true });
53
+ }
54
+
55
+ // ═══════════════════════════════════════════════════════════════
56
+ // UNIT TESTS — parseLogOutput (via analyze)
57
+ // ═══════════════════════════════════════════════════════════════
58
+
59
+ describe('GitHistoryAnalyzer', () => {
60
+ beforeAll(() => {
61
+ setupGitRepo();
62
+
63
+ // Create initial files across two modules
64
+ commitFile('src/core.ts', 'export class Core {}', 'initial core', 'Alice <alice@test.com>');
65
+ commitFile('src/utils.ts', 'export function util() {}', 'add utils', 'Alice <alice@test.com>');
66
+ commitFile('lib/helper.ts', 'export function help() {}', 'add helper', 'Bob <bob@test.com>');
67
+
68
+ // Add more changes to create churn
69
+ commitFile('src/core.ts', 'export class Core { run() {} }', 'enhance core', 'Bob <bob@test.com>');
70
+ commitFile('src/core.ts', 'export class Core { run() { return 1; } }', 'fix core', 'Alice <alice@test.com>');
71
+
72
+ // Concurrent changes for coupling detection
73
+ const coupledContent1 = 'export class Core { run() { return 2; } }';
74
+ const coupledContent2 = 'export function util() { return true; }';
75
+ const p1 = path.join(TEST_DIR, 'src/core.ts');
76
+ const p2 = path.join(TEST_DIR, 'src/utils.ts');
77
+ fs.writeFileSync(p1, coupledContent1);
78
+ fs.writeFileSync(p2, coupledContent2);
79
+ execSync('git add .', { cwd: TEST_DIR, stdio: 'pipe' });
80
+ execSync('git commit --author="Alice <alice@test.com>" -m "coupled change 1"', {
81
+ cwd: TEST_DIR,
82
+ stdio: 'pipe',
83
+ });
84
+
85
+ // Another coupled change
86
+ fs.writeFileSync(p1, coupledContent1 + '\n// v2');
87
+ fs.writeFileSync(p2, coupledContent2 + '\n// v2');
88
+ execSync('git add .', { cwd: TEST_DIR, stdio: 'pipe' });
89
+ execSync('git commit --author="Bob <bob@test.com>" -m "coupled change 2"', {
90
+ cwd: TEST_DIR,
91
+ stdio: 'pipe',
92
+ });
93
+
94
+ // Third coupled change to meet the default minCochanges=3
95
+ fs.writeFileSync(p1, coupledContent1 + '\n// v3');
96
+ fs.writeFileSync(p2, coupledContent2 + '\n// v3');
97
+ execSync('git add .', { cwd: TEST_DIR, stdio: 'pipe' });
98
+ execSync('git commit --author="Alice <alice@test.com>" -m "coupled change 3"', {
99
+ cwd: TEST_DIR,
100
+ stdio: 'pipe',
101
+ });
102
+ });
103
+
104
+ afterAll(() => {
105
+ cleanupRepo();
106
+ });
107
+
108
+ describe('analyze()', () => {
109
+ let report: GitHistoryReport;
110
+
111
+ beforeAll(() => {
112
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 52 });
113
+ report = analyzer.analyze(TEST_DIR);
114
+ });
115
+
116
+ it('should return correct project path', () => {
117
+ expect(report.projectPath).toBe(TEST_DIR);
118
+ });
119
+
120
+ it('should have analyzedAt timestamp', () => {
121
+ expect(report.analyzedAt).toBeDefined();
122
+ expect(new Date(report.analyzedAt).getTime()).not.toBeNaN();
123
+ });
124
+
125
+ it('should count total commits', () => {
126
+ expect(report.totalCommits).toBeGreaterThanOrEqual(7);
127
+ });
128
+
129
+ it('should count unique authors', () => {
130
+ expect(report.totalAuthors).toBe(2); // Alice + Bob
131
+ });
132
+
133
+ it('should detect modules by first directory level', () => {
134
+ const moduleNames = report.modules.map(m => m.modulePath);
135
+ expect(moduleNames).toContain('src');
136
+ expect(moduleNames).toContain('lib');
137
+ });
138
+
139
+ it('should build file histories with correct metrics', () => {
140
+ const srcModule = report.modules.find(m => m.modulePath === 'src');
141
+ expect(srcModule).toBeDefined();
142
+
143
+ const coreFile = srcModule!.files.find(f => f.path === 'src/core.ts');
144
+ expect(coreFile).toBeDefined();
145
+ expect(coreFile!.commits).toBeGreaterThanOrEqual(5);
146
+ expect(coreFile!.busFactor).toBe(2); // Alice + Bob
147
+ expect(coreFile!.totalAdditions).toBeGreaterThan(0);
148
+ });
149
+
150
+ it('should calculate churn rate correctly', () => {
151
+ const srcModule = report.modules.find(m => m.modulePath === 'src');
152
+ const coreFile = srcModule!.files.find(f => f.path === 'src/core.ts');
153
+ expect(coreFile!.churnRate).toBeGreaterThan(0);
154
+ expect(coreFile!.churnRate).toBe(
155
+ (coreFile!.totalAdditions + coreFile!.totalDeletions) / coreFile!.commits
156
+ );
157
+ });
158
+
159
+ it('should build weekly timeline', () => {
160
+ expect(report.commitTimeline.length).toBeGreaterThan(0);
161
+ const total = report.commitTimeline.reduce((s, w) => s + w.commits, 0);
162
+ expect(total).toBe(report.totalCommits);
163
+ });
164
+ });
165
+
166
+ describe('change coupling', () => {
167
+ it('should detect coupled files', () => {
168
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 52, couplingMinCochanges: 3 });
169
+ const report = analyzer.analyze(TEST_DIR);
170
+
171
+ const coupling = report.changeCouplings.find(
172
+ c =>
173
+ (c.fileA === 'src/core.ts' && c.fileB === 'src/utils.ts') ||
174
+ (c.fileA === 'src/utils.ts' && c.fileB === 'src/core.ts')
175
+ );
176
+
177
+ expect(coupling).toBeDefined();
178
+ expect(coupling!.cochangeCount).toBeGreaterThanOrEqual(3);
179
+ expect(coupling!.confidence).toBeGreaterThan(0);
180
+ expect(coupling!.confidence).toBeLessThanOrEqual(1);
181
+ });
182
+ });
183
+
184
+ describe('velocity vectors', () => {
185
+ it('should calculate velocity for each module', () => {
186
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 52 });
187
+ const report = analyzer.analyze(TEST_DIR);
188
+
189
+ const srcModule = report.modules.find(m => m.modulePath === 'src');
190
+ expect(srcModule).toBeDefined();
191
+ expect(srcModule!.velocityVector).toBeDefined();
192
+ expect(srcModule!.velocityVector.direction).toMatch(/^(accelerating|stable|decelerating)$/);
193
+ expect(typeof srcModule!.velocityVector.commitAcceleration).toBe('number');
194
+ expect(typeof srcModule!.velocityVector.churnTrend).toBe('number');
195
+ });
196
+ });
197
+
198
+ describe('module aggregation', () => {
199
+ it('should aggregate commits across module files', () => {
200
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 52 });
201
+ const report = analyzer.analyze(TEST_DIR);
202
+
203
+ const srcModule = report.modules.find(m => m.modulePath === 'src');
204
+ expect(srcModule!.aggregateCommits).toBe(
205
+ srcModule!.files.reduce((s, f) => s + f.commits, 0)
206
+ );
207
+ });
208
+
209
+ it('should calculate bus factor per module', () => {
210
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 52 });
211
+ const report = analyzer.analyze(TEST_DIR);
212
+
213
+ const srcModule = report.modules.find(m => m.modulePath === 'src');
214
+ expect(srcModule!.busFactor).toBe(2); // Alice + Bob both committed to src/
215
+ });
216
+
217
+ it('should sort modules by aggregate churn descending', () => {
218
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 52 });
219
+ const report = analyzer.analyze(TEST_DIR);
220
+
221
+ for (let i = 1; i < report.modules.length; i++) {
222
+ expect(report.modules[i - 1].aggregateChurn).toBeGreaterThanOrEqual(
223
+ report.modules[i].aggregateChurn
224
+ );
225
+ }
226
+ });
227
+ });
228
+
229
+ describe('error handling', () => {
230
+ it('should throw for non-git directory', () => {
231
+ const tmpDir = path.join('/tmp', 'not-a-git-repo');
232
+ fs.mkdirSync(tmpDir, { recursive: true });
233
+
234
+ const analyzer = new GitHistoryAnalyzer();
235
+ expect(() => analyzer.analyze(tmpDir)).toThrow('Not a git repository');
236
+
237
+ fs.rmSync(tmpDir, { recursive: true, force: true });
238
+ });
239
+ });
240
+
241
+ describe('configuration', () => {
242
+ it('should respect custom period weeks', () => {
243
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 1 });
244
+ const report = analyzer.analyze(TEST_DIR);
245
+ expect(report.periodWeeks).toBe(1);
246
+ });
247
+
248
+ it('should respect custom coupling threshold', () => {
249
+ const analyzer = new GitHistoryAnalyzer({ periodWeeks: 52, couplingMinCochanges: 100 });
250
+ const report = analyzer.analyze(TEST_DIR);
251
+ expect(report.changeCouplings.length).toBe(0);
252
+ });
253
+ });
254
+ });
@@ -1,10 +1,9 @@
1
- import { fileURLToPath } from 'url';
2
1
  import path from 'path';
3
2
  import { ProjectScanner } from '../src/scanner.js';
4
3
  import { ArchitectConfig } from '../src/types.js';
5
4
 
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
5
+ // Use path.resolve for Jest compatibility (import.meta.url not supported by ts-jest)
6
+ const testDir = path.resolve(__dirname);
8
7
 
9
8
  describe('ProjectScanner', () => {
10
9
  const mockConfig: ArchitectConfig = {
@@ -14,11 +13,11 @@ describe('ProjectScanner', () => {
14
13
 
15
14
  describe('scan', () => {
16
15
  it('should scan a project directory and return project info', () => {
17
- const scanner = new ProjectScanner(__dirname, mockConfig);
16
+ const scanner = new ProjectScanner(testDir, mockConfig);
18
17
  const info = scanner.scan();
19
18
 
20
19
  expect(info).toBeDefined();
21
- expect(info.path).toBe(__dirname);
20
+ expect(info.path).toBe(testDir);
22
21
  expect(info.totalFiles).toBeGreaterThanOrEqual(0);
23
22
  expect(info.totalLines).toBeGreaterThanOrEqual(0);
24
23
  expect(Array.isArray(info.primaryLanguages)).toBe(true);
@@ -26,7 +25,7 @@ describe('ProjectScanner', () => {
26
25
  });
27
26
 
28
27
  it('should detect TypeScript files', () => {
29
- const scanner = new ProjectScanner(__dirname, mockConfig);
28
+ const scanner = new ProjectScanner(testDir, mockConfig);
30
29
  const info = scanner.scan();
31
30
 
32
31
  if (info.totalFiles > 0) {
@@ -35,7 +34,7 @@ describe('ProjectScanner', () => {
35
34
  });
36
35
 
37
36
  it('should build a file tree structure', () => {
38
- const scanner = new ProjectScanner(__dirname, mockConfig);
37
+ const scanner = new ProjectScanner(testDir, mockConfig);
39
38
  const info = scanner.scan();
40
39
 
41
40
  expect(info.fileTree).toBeDefined();
@@ -46,7 +45,7 @@ describe('ProjectScanner', () => {
46
45
 
47
46
  describe('framework detection', () => {
48
47
  it('should detect frameworks from configuration files', () => {
49
- const scanner = new ProjectScanner(__dirname, mockConfig);
48
+ const scanner = new ProjectScanner(testDir, mockConfig);
50
49
  const info = scanner.scan();
51
50
 
52
51
  expect(Array.isArray(info.frameworks)).toBe(true);