@girardelli/architect 5.0.0 → 8.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 (335) hide show
  1. package/dist/{cli.d.ts → src/adapters/cli.d.ts} +1 -2
  2. package/dist/{cli.js → src/adapters/cli.js} +191 -213
  3. package/dist/src/adapters/cli.js.map +1 -0
  4. package/dist/src/adapters/github-action.d.ts +9 -0
  5. package/dist/src/adapters/github-action.js +94 -0
  6. package/dist/src/adapters/github-action.js.map +1 -0
  7. package/dist/src/adapters/html-reporter/scripts.d.ts +5 -0
  8. package/dist/src/adapters/html-reporter/scripts.js +400 -0
  9. package/dist/src/adapters/html-reporter/scripts.js.map +1 -0
  10. package/dist/src/adapters/html-reporter/sections/agents.d.ts +2 -0
  11. package/dist/src/adapters/html-reporter/sections/agents.js +260 -0
  12. package/dist/src/adapters/html-reporter/sections/agents.js.map +1 -0
  13. package/dist/src/adapters/html-reporter/sections/anti-patterns.d.ts +13 -0
  14. package/dist/src/adapters/html-reporter/sections/anti-patterns.js +64 -0
  15. package/dist/src/adapters/html-reporter/sections/anti-patterns.js.map +1 -0
  16. package/dist/src/adapters/html-reporter/sections/header.d.ts +3 -0
  17. package/dist/src/adapters/html-reporter/sections/header.js +30 -0
  18. package/dist/src/adapters/html-reporter/sections/header.js.map +1 -0
  19. package/dist/src/adapters/html-reporter/sections/layers.d.ts +9 -0
  20. package/dist/src/adapters/html-reporter/sections/layers.js +143 -0
  21. package/dist/src/adapters/html-reporter/sections/layers.js.map +1 -0
  22. package/dist/src/adapters/html-reporter/sections/overview.d.ts +2 -0
  23. package/dist/src/adapters/html-reporter/sections/overview.js +58 -0
  24. package/dist/src/adapters/html-reporter/sections/overview.js.map +1 -0
  25. package/dist/src/adapters/html-reporter/sections/refactoring-plan.d.ts +3 -0
  26. package/dist/src/adapters/html-reporter/sections/refactoring-plan.js +151 -0
  27. package/dist/src/adapters/html-reporter/sections/refactoring-plan.js.map +1 -0
  28. package/dist/src/adapters/html-reporter/sections/score.d.ts +7 -0
  29. package/dist/src/adapters/html-reporter/sections/score.js +70 -0
  30. package/dist/src/adapters/html-reporter/sections/score.js.map +1 -0
  31. package/dist/src/adapters/html-reporter/sections/suggestions.d.ts +7 -0
  32. package/dist/src/adapters/html-reporter/sections/suggestions.js +34 -0
  33. package/dist/src/adapters/html-reporter/sections/suggestions.js.map +1 -0
  34. package/dist/src/adapters/html-reporter/styles.d.ts +1 -0
  35. package/dist/src/adapters/html-reporter/styles.js +526 -0
  36. package/dist/src/adapters/html-reporter/styles.js.map +1 -0
  37. package/dist/src/adapters/html-reporter/utils_adapters.d.ts +20 -0
  38. package/dist/src/adapters/html-reporter/utils_adapters.js +32 -0
  39. package/dist/src/adapters/html-reporter/utils_adapters.js.map +1 -0
  40. package/dist/src/adapters/html-reporter/utils_sections.d.ts +7 -0
  41. package/dist/src/adapters/html-reporter/utils_sections.js +58 -0
  42. package/dist/src/adapters/html-reporter/utils_sections.js.map +1 -0
  43. package/dist/src/adapters/html-reporter.d.ts +10 -0
  44. package/dist/src/adapters/html-reporter.js +97 -0
  45. package/dist/src/adapters/html-reporter.js.map +1 -0
  46. package/dist/src/adapters/progress-logger.d.ts +55 -0
  47. package/dist/src/adapters/progress-logger.js +200 -0
  48. package/dist/src/adapters/progress-logger.js.map +1 -0
  49. package/dist/{refactor-reporter.d.ts → src/adapters/refactor-reporter.d.ts} +1 -2
  50. package/dist/{refactor-reporter.js → src/adapters/refactor-reporter.js} +1 -1
  51. package/dist/src/adapters/refactor-reporter.js.map +1 -0
  52. package/dist/{reporter.d.ts → src/adapters/reporter.d.ts} +1 -2
  53. package/dist/src/adapters/reporter.js.map +1 -0
  54. package/dist/src/core/GenesisTerminal.d.ts +8 -0
  55. package/dist/src/core/GenesisTerminal.js +105 -0
  56. package/dist/src/core/GenesisTerminal.js.map +1 -0
  57. package/dist/{index.d.ts → src/core/architect.d.ts} +4 -18
  58. package/dist/{index.js → src/core/architect.js} +22 -21
  59. package/dist/src/core/architect.js.map +1 -0
  60. package/dist/tests/architect-adapter-enrichment.test.d.ts +1 -0
  61. package/dist/tests/architect-adapter-enrichment.test.js +11 -0
  62. package/dist/tests/architect-adapter-enrichment.test.js.map +1 -0
  63. package/dist/tests/github-action.test.d.ts +1 -0
  64. package/dist/tests/github-action.test.js +92 -0
  65. package/dist/tests/github-action.test.js.map +1 -0
  66. package/package.json +15 -65
  67. package/src/adapters/cli.ts +492 -0
  68. package/src/adapters/github-action.ts +109 -0
  69. package/src/adapters/html-reporter/scripts.ts +402 -0
  70. package/src/adapters/html-reporter/sections/agents.ts +267 -0
  71. package/src/adapters/html-reporter/sections/anti-patterns.ts +81 -0
  72. package/src/adapters/html-reporter/sections/header.ts +35 -0
  73. package/src/adapters/html-reporter/sections/layers.ts +165 -0
  74. package/src/adapters/html-reporter/sections/overview.ts +64 -0
  75. package/src/adapters/html-reporter/sections/refactoring-plan.ts +166 -0
  76. package/src/adapters/html-reporter/sections/score.ts +80 -0
  77. package/src/adapters/html-reporter/sections/suggestions.ts +39 -0
  78. package/src/adapters/html-reporter/styles.ts +525 -0
  79. package/src/adapters/html-reporter/utils_adapters.ts +39 -0
  80. package/src/adapters/html-reporter/utils_sections.ts +55 -0
  81. package/src/adapters/html-reporter.ts +102 -0
  82. package/src/adapters/progress-logger.ts +236 -0
  83. package/src/{refactor-reporter.ts → adapters/refactor-reporter.ts} +2 -2
  84. package/src/{reporter.ts → adapters/reporter.ts} +1 -1
  85. package/src/core/GenesisTerminal.ts +127 -0
  86. package/src/{index.ts → core/architect.ts} +27 -45
  87. package/tests/github-action.test.ts +109 -0
  88. package/tsconfig.json +12 -19
  89. package/CONTRIBUTING.md +0 -140
  90. package/LICENSE +0 -21
  91. package/PROJECT_STRUCTURE.txt +0 -168
  92. package/README.md +0 -257
  93. package/architect-run.sh +0 -431
  94. package/assets/banner-v3.html +0 -561
  95. package/dist/agent-generator/context-enricher.d.ts +0 -58
  96. package/dist/agent-generator/context-enricher.d.ts.map +0 -1
  97. package/dist/agent-generator/context-enricher.js +0 -613
  98. package/dist/agent-generator/context-enricher.js.map +0 -1
  99. package/dist/agent-generator/domain-inferrer.d.ts +0 -52
  100. package/dist/agent-generator/domain-inferrer.d.ts.map +0 -1
  101. package/dist/agent-generator/domain-inferrer.js +0 -585
  102. package/dist/agent-generator/domain-inferrer.js.map +0 -1
  103. package/dist/agent-generator/framework-detector.d.ts +0 -40
  104. package/dist/agent-generator/framework-detector.d.ts.map +0 -1
  105. package/dist/agent-generator/framework-detector.js +0 -611
  106. package/dist/agent-generator/framework-detector.js.map +0 -1
  107. package/dist/agent-generator/index.d.ts +0 -47
  108. package/dist/agent-generator/index.d.ts.map +0 -1
  109. package/dist/agent-generator/index.js +0 -545
  110. package/dist/agent-generator/index.js.map +0 -1
  111. package/dist/agent-generator/stack-detector.d.ts +0 -14
  112. package/dist/agent-generator/stack-detector.d.ts.map +0 -1
  113. package/dist/agent-generator/stack-detector.js +0 -124
  114. package/dist/agent-generator/stack-detector.js.map +0 -1
  115. package/dist/agent-generator/templates/core/agents.d.ts +0 -17
  116. package/dist/agent-generator/templates/core/agents.d.ts.map +0 -1
  117. package/dist/agent-generator/templates/core/agents.js +0 -1256
  118. package/dist/agent-generator/templates/core/agents.js.map +0 -1
  119. package/dist/agent-generator/templates/core/architecture-rules.d.ts +0 -7
  120. package/dist/agent-generator/templates/core/architecture-rules.d.ts.map +0 -1
  121. package/dist/agent-generator/templates/core/architecture-rules.js +0 -274
  122. package/dist/agent-generator/templates/core/architecture-rules.js.map +0 -1
  123. package/dist/agent-generator/templates/core/general-rules.d.ts +0 -8
  124. package/dist/agent-generator/templates/core/general-rules.d.ts.map +0 -1
  125. package/dist/agent-generator/templates/core/general-rules.js +0 -301
  126. package/dist/agent-generator/templates/core/general-rules.js.map +0 -1
  127. package/dist/agent-generator/templates/core/hooks-generator.d.ts +0 -21
  128. package/dist/agent-generator/templates/core/hooks-generator.d.ts.map +0 -1
  129. package/dist/agent-generator/templates/core/hooks-generator.js +0 -233
  130. package/dist/agent-generator/templates/core/hooks-generator.js.map +0 -1
  131. package/dist/agent-generator/templates/core/index-md.d.ts +0 -7
  132. package/dist/agent-generator/templates/core/index-md.d.ts.map +0 -1
  133. package/dist/agent-generator/templates/core/index-md.js +0 -246
  134. package/dist/agent-generator/templates/core/index-md.js.map +0 -1
  135. package/dist/agent-generator/templates/core/orchestrator.d.ts +0 -8
  136. package/dist/agent-generator/templates/core/orchestrator.d.ts.map +0 -1
  137. package/dist/agent-generator/templates/core/orchestrator.js +0 -422
  138. package/dist/agent-generator/templates/core/orchestrator.js.map +0 -1
  139. package/dist/agent-generator/templates/core/preflight.d.ts +0 -8
  140. package/dist/agent-generator/templates/core/preflight.d.ts.map +0 -1
  141. package/dist/agent-generator/templates/core/preflight.js +0 -213
  142. package/dist/agent-generator/templates/core/preflight.js.map +0 -1
  143. package/dist/agent-generator/templates/core/quality-gates.d.ts +0 -11
  144. package/dist/agent-generator/templates/core/quality-gates.d.ts.map +0 -1
  145. package/dist/agent-generator/templates/core/quality-gates.js +0 -254
  146. package/dist/agent-generator/templates/core/quality-gates.js.map +0 -1
  147. package/dist/agent-generator/templates/core/security-rules.d.ts +0 -7
  148. package/dist/agent-generator/templates/core/security-rules.d.ts.map +0 -1
  149. package/dist/agent-generator/templates/core/security-rules.js +0 -528
  150. package/dist/agent-generator/templates/core/security-rules.js.map +0 -1
  151. package/dist/agent-generator/templates/core/skills-generator.d.ts +0 -19
  152. package/dist/agent-generator/templates/core/skills-generator.d.ts.map +0 -1
  153. package/dist/agent-generator/templates/core/skills-generator.js +0 -546
  154. package/dist/agent-generator/templates/core/skills-generator.js.map +0 -1
  155. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts +0 -7
  156. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts.map +0 -1
  157. package/dist/agent-generator/templates/core/workflow-fix-bug.js +0 -237
  158. package/dist/agent-generator/templates/core/workflow-fix-bug.js.map +0 -1
  159. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts +0 -8
  160. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts.map +0 -1
  161. package/dist/agent-generator/templates/core/workflow-new-feature.js +0 -321
  162. package/dist/agent-generator/templates/core/workflow-new-feature.js.map +0 -1
  163. package/dist/agent-generator/templates/core/workflow-review.d.ts +0 -7
  164. package/dist/agent-generator/templates/core/workflow-review.d.ts.map +0 -1
  165. package/dist/agent-generator/templates/core/workflow-review.js +0 -104
  166. package/dist/agent-generator/templates/core/workflow-review.js.map +0 -1
  167. package/dist/agent-generator/templates/domain/index.d.ts +0 -22
  168. package/dist/agent-generator/templates/domain/index.d.ts.map +0 -1
  169. package/dist/agent-generator/templates/domain/index.js +0 -1176
  170. package/dist/agent-generator/templates/domain/index.js.map +0 -1
  171. package/dist/agent-generator/templates/stack/index.d.ts +0 -8
  172. package/dist/agent-generator/templates/stack/index.d.ts.map +0 -1
  173. package/dist/agent-generator/templates/stack/index.js +0 -695
  174. package/dist/agent-generator/templates/stack/index.js.map +0 -1
  175. package/dist/agent-generator/templates/template-helpers.d.ts +0 -75
  176. package/dist/agent-generator/templates/template-helpers.d.ts.map +0 -1
  177. package/dist/agent-generator/templates/template-helpers.js +0 -726
  178. package/dist/agent-generator/templates/template-helpers.js.map +0 -1
  179. package/dist/agent-generator/types.d.ts +0 -196
  180. package/dist/agent-generator/types.d.ts.map +0 -1
  181. package/dist/agent-generator/types.js +0 -27
  182. package/dist/agent-generator/types.js.map +0 -1
  183. package/dist/analyzer.d.ts +0 -38
  184. package/dist/analyzer.d.ts.map +0 -1
  185. package/dist/analyzer.js +0 -383
  186. package/dist/analyzer.js.map +0 -1
  187. package/dist/analyzers/forecast.d.ts +0 -85
  188. package/dist/analyzers/forecast.d.ts.map +0 -1
  189. package/dist/analyzers/forecast.js +0 -337
  190. package/dist/analyzers/forecast.js.map +0 -1
  191. package/dist/analyzers/git-cache.d.ts +0 -7
  192. package/dist/analyzers/git-cache.d.ts.map +0 -1
  193. package/dist/analyzers/git-cache.js +0 -41
  194. package/dist/analyzers/git-cache.js.map +0 -1
  195. package/dist/analyzers/git-history.d.ts +0 -113
  196. package/dist/analyzers/git-history.d.ts.map +0 -1
  197. package/dist/analyzers/git-history.js +0 -333
  198. package/dist/analyzers/git-history.js.map +0 -1
  199. package/dist/analyzers/index.d.ts +0 -10
  200. package/dist/analyzers/index.d.ts.map +0 -1
  201. package/dist/analyzers/index.js +0 -7
  202. package/dist/analyzers/index.js.map +0 -1
  203. package/dist/analyzers/temporal-scorer.d.ts +0 -72
  204. package/dist/analyzers/temporal-scorer.d.ts.map +0 -1
  205. package/dist/analyzers/temporal-scorer.js +0 -140
  206. package/dist/analyzers/temporal-scorer.js.map +0 -1
  207. package/dist/anti-patterns.d.ts +0 -24
  208. package/dist/anti-patterns.d.ts.map +0 -1
  209. package/dist/anti-patterns.js +0 -230
  210. package/dist/anti-patterns.js.map +0 -1
  211. package/dist/cli.d.ts.map +0 -1
  212. package/dist/cli.js.map +0 -1
  213. package/dist/config.d.ts +0 -12
  214. package/dist/config.d.ts.map +0 -1
  215. package/dist/config.js +0 -110
  216. package/dist/config.js.map +0 -1
  217. package/dist/diagram.d.ts +0 -9
  218. package/dist/diagram.d.ts.map +0 -1
  219. package/dist/diagram.js +0 -116
  220. package/dist/diagram.js.map +0 -1
  221. package/dist/html-reporter.d.ts +0 -47
  222. package/dist/html-reporter.d.ts.map +0 -1
  223. package/dist/html-reporter.js +0 -1747
  224. package/dist/html-reporter.js.map +0 -1
  225. package/dist/index.d.ts.map +0 -1
  226. package/dist/index.js.map +0 -1
  227. package/dist/project-summarizer.d.ts +0 -38
  228. package/dist/project-summarizer.d.ts.map +0 -1
  229. package/dist/project-summarizer.js +0 -463
  230. package/dist/project-summarizer.js.map +0 -1
  231. package/dist/refactor-engine.d.ts +0 -18
  232. package/dist/refactor-engine.d.ts.map +0 -1
  233. package/dist/refactor-engine.js +0 -86
  234. package/dist/refactor-engine.js.map +0 -1
  235. package/dist/refactor-reporter.d.ts.map +0 -1
  236. package/dist/refactor-reporter.js.map +0 -1
  237. package/dist/reporter.d.ts.map +0 -1
  238. package/dist/reporter.js.map +0 -1
  239. package/dist/rules/barrel-optimizer.d.ts +0 -13
  240. package/dist/rules/barrel-optimizer.d.ts.map +0 -1
  241. package/dist/rules/barrel-optimizer.js +0 -77
  242. package/dist/rules/barrel-optimizer.js.map +0 -1
  243. package/dist/rules/dead-code-detector.d.ts +0 -21
  244. package/dist/rules/dead-code-detector.d.ts.map +0 -1
  245. package/dist/rules/dead-code-detector.js +0 -117
  246. package/dist/rules/dead-code-detector.js.map +0 -1
  247. package/dist/rules/hub-splitter.d.ts +0 -13
  248. package/dist/rules/hub-splitter.d.ts.map +0 -1
  249. package/dist/rules/hub-splitter.js +0 -110
  250. package/dist/rules/hub-splitter.js.map +0 -1
  251. package/dist/rules/import-organizer.d.ts +0 -13
  252. package/dist/rules/import-organizer.d.ts.map +0 -1
  253. package/dist/rules/import-organizer.js +0 -85
  254. package/dist/rules/import-organizer.js.map +0 -1
  255. package/dist/rules/module-grouper.d.ts +0 -13
  256. package/dist/rules/module-grouper.d.ts.map +0 -1
  257. package/dist/rules/module-grouper.js +0 -110
  258. package/dist/rules/module-grouper.js.map +0 -1
  259. package/dist/scanner.d.ts +0 -31
  260. package/dist/scanner.d.ts.map +0 -1
  261. package/dist/scanner.js +0 -328
  262. package/dist/scanner.js.map +0 -1
  263. package/dist/scorer.d.ts +0 -27
  264. package/dist/scorer.d.ts.map +0 -1
  265. package/dist/scorer.js +0 -229
  266. package/dist/scorer.js.map +0 -1
  267. package/dist/types.d.ts +0 -186
  268. package/dist/types.d.ts.map +0 -1
  269. package/dist/types.js +0 -2
  270. package/dist/types.js.map +0 -1
  271. package/examples/sample-report.md +0 -207
  272. package/jest.config.js +0 -18
  273. package/src/agent-generator/context-enricher.ts +0 -672
  274. package/src/agent-generator/domain-inferrer.ts +0 -635
  275. package/src/agent-generator/framework-detector.ts +0 -669
  276. package/src/agent-generator/index.ts +0 -634
  277. package/src/agent-generator/stack-detector.ts +0 -115
  278. package/src/agent-generator/templates/core/agents.ts +0 -1296
  279. package/src/agent-generator/templates/core/architecture-rules.ts +0 -287
  280. package/src/agent-generator/templates/core/general-rules.ts +0 -306
  281. package/src/agent-generator/templates/core/hooks-generator.ts +0 -242
  282. package/src/agent-generator/templates/core/index-md.ts +0 -260
  283. package/src/agent-generator/templates/core/orchestrator.ts +0 -459
  284. package/src/agent-generator/templates/core/preflight.ts +0 -215
  285. package/src/agent-generator/templates/core/quality-gates.ts +0 -256
  286. package/src/agent-generator/templates/core/security-rules.ts +0 -543
  287. package/src/agent-generator/templates/core/skills-generator.ts +0 -585
  288. package/src/agent-generator/templates/core/workflow-fix-bug.ts +0 -239
  289. package/src/agent-generator/templates/core/workflow-new-feature.ts +0 -323
  290. package/src/agent-generator/templates/core/workflow-review.ts +0 -106
  291. package/src/agent-generator/templates/domain/index.ts +0 -1201
  292. package/src/agent-generator/templates/stack/index.ts +0 -705
  293. package/src/agent-generator/templates/template-helpers.ts +0 -776
  294. package/src/agent-generator/types.ts +0 -232
  295. package/src/analyzer.ts +0 -447
  296. package/src/analyzers/forecast.ts +0 -496
  297. package/src/analyzers/git-cache.ts +0 -52
  298. package/src/analyzers/git-history.ts +0 -488
  299. package/src/analyzers/index.ts +0 -33
  300. package/src/analyzers/temporal-scorer.ts +0 -227
  301. package/src/anti-patterns.ts +0 -287
  302. package/src/cli.ts +0 -517
  303. package/src/config.ts +0 -123
  304. package/src/diagram.ts +0 -144
  305. package/src/html-reporter.ts +0 -1830
  306. package/src/project-summarizer.ts +0 -521
  307. package/src/refactor-engine.ts +0 -117
  308. package/src/rules/barrel-optimizer.ts +0 -97
  309. package/src/rules/dead-code-detector.ts +0 -132
  310. package/src/rules/hub-splitter.ts +0 -123
  311. package/src/rules/import-organizer.ts +0 -98
  312. package/src/rules/module-grouper.ts +0 -124
  313. package/src/scanner.ts +0 -344
  314. package/src/scorer.ts +0 -254
  315. package/src/types.ts +0 -193
  316. package/tests/agent-generator.test.ts +0 -427
  317. package/tests/analyzers-integration.test.ts +0 -174
  318. package/tests/anti-patterns.test.ts +0 -94
  319. package/tests/context-enricher.test.ts +0 -971
  320. package/tests/fixtures/monorepo/package.json +0 -6
  321. package/tests/fixtures/monorepo/packages/app/package.json +0 -12
  322. package/tests/fixtures/monorepo/packages/app/src/index.ts +0 -6
  323. package/tests/fixtures/monorepo/packages/core/package.json +0 -7
  324. package/tests/fixtures/monorepo/packages/core/src/index.ts +0 -7
  325. package/tests/forecast.test.ts +0 -509
  326. package/tests/framework-detector.test.ts +0 -1172
  327. package/tests/git-history.test.ts +0 -254
  328. package/tests/monorepo-scan.test.ts +0 -170
  329. package/tests/scanner.test.ts +0 -54
  330. package/tests/scorer.test.ts +0 -674
  331. package/tests/stack-detector.test.ts +0 -241
  332. package/tests/template-generation.test.ts +0 -706
  333. package/tests/template-helpers.test.ts +0 -1152
  334. package/tests/temporal-scorer.test.ts +0 -307
  335. /package/dist/{reporter.js → src/adapters/reporter.js} +0 -0
@@ -1,1747 +0,0 @@
1
- /**
2
- * Generates premium visual HTML reports from AnalysisReport.
3
- * Features: D3.js force graph, bubble charts, radar chart, animated counters.
4
- */
5
- export class HtmlReportGenerator {
6
- generateHtml(report, plan, agentSuggestion) {
7
- const grouped = this.groupAntiPatterns(report.antiPatterns);
8
- const sugGrouped = this.groupSuggestions(report.suggestions);
9
- return `<!DOCTYPE html>
10
- <html lang="en">
11
- <head>
12
- <meta charset="UTF-8">
13
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
- <title>Architect Report — ${this.escapeHtml(report.projectInfo.name)}</title>
15
- ${this.getStyles()}
16
- <script src="https://cdn.jsdelivr.net/npm/d3@7"><\/script>
17
- </head>
18
- <body>
19
- ${this.renderHeader(report)}
20
- <div class="report-layout">
21
- <nav class="sidebar" id="reportSidebar">
22
- <div class="sidebar-title">Navigation</div>
23
- <a href="#score" class="sidebar-link active" data-section="score">📊 Score</a>
24
- ${report.projectSummary ? `<a href="#overview" class="sidebar-link" data-section="overview">📋 Overview</a>` : ''}
25
- <a href="#layers" class="sidebar-link" data-section="layers">📐 Layers & Graph</a>
26
- <a href="#anti-patterns" class="sidebar-link" data-section="anti-patterns">⚠️ Anti-Patterns (${report.antiPatterns.length})</a>
27
- <a href="#suggestions" class="sidebar-link" data-section="suggestions">💡 Suggestions (${report.suggestions.length})</a>
28
- ${plan ? `<a href="#refactoring" class="sidebar-link" data-section="refactoring">🔧 Refactoring (${plan.steps.length})</a>` : ''}
29
- ${agentSuggestion ? `<a href="#agents" class="sidebar-link" data-section="agents">🤖 Agents</a>` : ''}
30
- </nav>
31
- <button class="sidebar-toggle" onclick="document.getElementById('reportSidebar').classList.toggle('sidebar-open')">☰</button>
32
-
33
- <div class="container">
34
- <div id="score">
35
- ${this.renderScoreHero(report)}
36
- ${this.renderRadarChart(report)}
37
- ${this.renderStats(report)}
38
- </div>
39
-
40
- ${this.renderProjectOverview(report)}
41
-
42
- <details class="section-accordion" id="layers" open>
43
- <summary class="section-accordion-header">📐 Layer Analysis & Dependencies</summary>
44
- <div class="section-accordion-body">
45
- ${this.renderLayers(report)}
46
- ${this.renderDependencyGraph(report)}
47
- </div>
48
- </details>
49
-
50
- <details class="section-accordion" id="anti-patterns" open>
51
- <summary class="section-accordion-header">⚠️ Anti-Patterns (${report.antiPatterns.length})</summary>
52
- <div class="section-accordion-body">
53
- ${this.renderAntiPatternBubbles(report, grouped)}
54
- ${this.renderAntiPatterns(report, grouped)}
55
- </div>
56
- </details>
57
-
58
- <details class="section-accordion" id="suggestions">
59
- <summary class="section-accordion-header">💡 Suggestions (${report.suggestions.length})</summary>
60
- <div class="section-accordion-body">
61
- ${this.renderSuggestions(sugGrouped)}
62
- </div>
63
- </details>
64
-
65
- ${plan ? `<details class="section-accordion" id="refactoring" open>
66
- <summary class="section-accordion-header">🔧 Refactoring Plan (${plan.steps.length} steps, ${plan.totalOperations} operations)</summary>
67
- <div class="section-accordion-body">
68
- ${this.renderRefactoringPlan(plan)}
69
- </div>
70
- </details>` : ''}
71
-
72
- ${agentSuggestion ? `<details class="section-accordion" id="agents" open>
73
- <summary class="section-accordion-header">🤖 Agent System</summary>
74
- <div class="section-accordion-body">
75
- ${this.renderAgentSuggestions(agentSuggestion)}
76
- </div>
77
- </details>` : ''}
78
- </div>
79
- </div>
80
- ${this.renderFooter()}
81
- ${this.getScripts(report)}
82
- </body>
83
- </html>`;
84
- }
85
- scoreColor(score) {
86
- if (score >= 70)
87
- return '#22c55e';
88
- if (score >= 50)
89
- return '#f59e0b';
90
- return '#ef4444';
91
- }
92
- scoreEmoji(score) {
93
- if (score >= 70)
94
- return '✅';
95
- if (score >= 50)
96
- return '⚠️';
97
- return '❌';
98
- }
99
- scoreLabel(score) {
100
- if (score >= 90)
101
- return 'Excellent';
102
- if (score >= 70)
103
- return 'Good';
104
- if (score >= 50)
105
- return 'Needs Attention';
106
- if (score >= 30)
107
- return 'Poor';
108
- return 'Critical';
109
- }
110
- escapeHtml(text) {
111
- return text
112
- .replace(/&/g, '&amp;')
113
- .replace(/</g, '&lt;')
114
- .replace(/>/g, '&gt;')
115
- .replace(/"/g, '&quot;');
116
- }
117
- groupAntiPatterns(antiPatterns) {
118
- const grouped = {};
119
- for (const p of antiPatterns) {
120
- if (!grouped[p.name]) {
121
- grouped[p.name] = { count: 0, severity: p.severity, locations: [], suggestion: p.suggestion };
122
- }
123
- grouped[p.name].count++;
124
- if (grouped[p.name].locations.length < 10) {
125
- grouped[p.name].locations.push(p.location);
126
- }
127
- }
128
- return grouped;
129
- }
130
- groupSuggestions(suggestions) {
131
- const map = new Map();
132
- for (const s of suggestions) {
133
- const key = `${s.title}|${s.priority}`;
134
- if (!map.has(key)) {
135
- map.set(key, { ...s, count: 0 });
136
- }
137
- map.get(key).count++;
138
- }
139
- return Array.from(map.values()).slice(0, 15);
140
- }
141
- renderHeader(report) {
142
- const date = new Date(report.timestamp).toLocaleDateString('en-US', {
143
- year: 'numeric',
144
- month: 'long',
145
- day: 'numeric',
146
- });
147
- return `
148
- <div class="header">
149
- <h1>🏗️ Architect</h1>
150
- <p class="subtitle">Architecture Analysis Report</p>
151
- <div class="meta">
152
- <span>📂 <strong>${this.escapeHtml(report.projectInfo.name)}</strong></span>
153
- <span>📁 <strong>${report.projectInfo.totalFiles}</strong> files</span>
154
- <span>📝 <strong>${report.projectInfo.totalLines.toLocaleString()}</strong> lines</span>
155
- <span>💻 <strong>${report.projectInfo.primaryLanguages.join(', ')}</strong></span>
156
- ${report.projectInfo.frameworks.length > 0 ? `<span>🔧 <strong>${report.projectInfo.frameworks.join(', ')}</strong></span>` : ''}
157
- <span>📅 <strong>${date}</strong></span>
158
- </div>
159
- </div>`;
160
- }
161
- renderProjectOverview(report) {
162
- const summary = report.projectSummary;
163
- if (!summary)
164
- return '';
165
- const modulesHtml = summary.modules.length > 0
166
- ? summary.modules.map(m => `
167
- <div class="overview-module">
168
- <div class="overview-module-name">${this.esc(m.name)}</div>
169
- <div class="overview-module-desc">${this.esc(m.description)}</div>
170
- <div class="overview-module-files">${m.files} file${m.files > 1 ? 's' : ''}</div>
171
- </div>`).join('')
172
- : '<div class="overview-empty">Nenhum módulo detectado</div>';
173
- const techHtml = summary.techStack
174
- .map(t => `<span class="overview-tag tech-tag">${this.esc(t)}</span>`)
175
- .join('');
176
- const keywordsHtml = summary.keywords
177
- .map(k => `<span class="overview-tag keyword-tag">${this.esc(k)}</span>`)
178
- .join('');
179
- const entryHtml = summary.entryPoints.length > 0
180
- ? summary.entryPoints.map(e => `<code class="overview-entry">${this.esc(e)}</code>`).join(' ')
181
- : '<span class="overview-empty">—</span>';
182
- return `
183
- <details class="section-accordion" id="overview" open>
184
- <summary class="section-accordion-header">📋 Project Overview</summary>
185
- <div class="section-accordion-body">
186
- <div class="overview-grid">
187
- <div class="overview-card overview-main">
188
- <div class="overview-label">O que é</div>
189
- <div class="overview-description">${this.esc(summary.description)}</div>
190
- <div class="overview-purpose-row">
191
- <span class="overview-purpose-label">Tipo:</span>
192
- <span class="overview-purpose-value">${this.esc(summary.purpose)}</span>
193
- </div>
194
- </div>
195
- <div class="overview-card">
196
- <div class="overview-label">Tech Stack</div>
197
- <div class="overview-tags">${techHtml || '<span class="overview-empty">—</span>'}</div>
198
- </div>
199
- <div class="overview-card">
200
- <div class="overview-label">Keywords</div>
201
- <div class="overview-tags">${keywordsHtml || '<span class="overview-empty">—</span>'}</div>
202
- </div>
203
- <div class="overview-card">
204
- <div class="overview-label">Entry Points</div>
205
- <div class="overview-entries">${entryHtml}</div>
206
- </div>
207
- </div>
208
- <div class="overview-modules-section">
209
- <div class="overview-label">Módulos Detectados (${summary.modules.length})</div>
210
- <div class="overview-modules-grid">
211
- ${modulesHtml}
212
- </div>
213
- </div>
214
- </div>
215
- </details>`;
216
- }
217
- esc(text) {
218
- return text
219
- .replace(/&/g, '&amp;')
220
- .replace(/</g, '&lt;')
221
- .replace(/>/g, '&gt;')
222
- .replace(/"/g, '&quot;');
223
- }
224
- renderScoreHero(report) {
225
- const overall = report.score.overall;
226
- const circumference = 2 * Math.PI * 85;
227
- const offset = circumference * (1 - overall / 100);
228
- const breakdownItems = Object.entries(report.score.breakdown)
229
- .map(([name, score]) => `
230
- <div class="score-item">
231
- <div class="name">${name}</div>
232
- <div class="val" style="color: ${this.scoreColor(score)}">${score} ${this.scoreEmoji(score)}</div>
233
- <div class="bar-container">
234
- <div class="bar" style="width: ${score}%; background: ${this.scoreColor(score)}"></div>
235
- </div>
236
- </div>`)
237
- .join('');
238
- return `
239
- <div class="score-hero">
240
- <div class="score-circle">
241
- <svg viewBox="0 0 200 200" width="180" height="180">
242
- <circle class="bg" cx="100" cy="100" r="85" />
243
- <circle class="fg" cx="100" cy="100" r="85"
244
- stroke="${this.scoreColor(overall)}"
245
- stroke-dasharray="${circumference}"
246
- stroke-dashoffset="${offset}" />
247
- </svg>
248
- <div class="score-value">
249
- <div class="number score-counter" data-target="${overall}" style="color: ${this.scoreColor(overall)}">0</div>
250
- <div class="label">/ 100</div>
251
- <div class="grade">${this.scoreLabel(overall)}</div>
252
- </div>
253
- </div>
254
- <div class="score-breakdown">
255
- ${breakdownItems}
256
- </div>
257
- </div>`;
258
- }
259
- /**
260
- * Radar chart for the 4 score components
261
- */
262
- renderRadarChart(report) {
263
- const entries = Object.entries(report.score.breakdown);
264
- return `
265
- <h2 class="section-title">🎯 Health Radar</h2>
266
- <div class="card" style="display: flex; justify-content: center;">
267
- <svg id="radar-chart" width="350" height="350" viewBox="0 0 350 350"></svg>
268
- </div>`;
269
- }
270
- renderStats(report) {
271
- return `
272
- <div class="stats-grid">
273
- <div class="stat-card">
274
- <div class="value stat-counter" data-target="${report.projectInfo.totalFiles}">0</div>
275
- <div class="label">Files Scanned</div>
276
- </div>
277
- <div class="stat-card">
278
- <div class="value stat-counter" data-target="${report.projectInfo.totalLines}">0</div>
279
- <div class="label">Lines of Code</div>
280
- </div>
281
- <div class="stat-card">
282
- <div class="value stat-counter" data-target="${report.antiPatterns.length}">0</div>
283
- <div class="label">Anti-Patterns</div>
284
- </div>
285
- <div class="stat-card">
286
- <div class="value stat-counter" data-target="${report.dependencyGraph.edges.length}">0</div>
287
- <div class="label">Dependencies</div>
288
- </div>
289
- </div>`;
290
- }
291
- renderLayers(report) {
292
- if (report.layers.length === 0)
293
- return '';
294
- const layerColors = {
295
- API: '#ec4899',
296
- Service: '#3b82f6',
297
- Data: '#10b981',
298
- UI: '#f59e0b',
299
- Infrastructure: '#8b5cf6',
300
- };
301
- const cards = report.layers
302
- .map((l) => {
303
- const color = layerColors[l.name] || '#64748b';
304
- return `
305
- <div class="layer-card" style="--layer-color: ${color}">
306
- <div class="count" style="color: ${color}">${l.files.length}</div>
307
- <div class="name">${l.name}</div>
308
- <div class="desc">${this.escapeHtml(l.description)}</div>
309
- </div>`;
310
- })
311
- .join('');
312
- return `
313
- <h2 class="section-title">📐 Architectural Layers</h2>
314
- <div class="layers-grid">${cards}</div>`;
315
- }
316
- /**
317
- * Interactive D3.js force-directed dependency graph
318
- */
319
- renderDependencyGraph(report) {
320
- if (report.dependencyGraph.edges.length === 0)
321
- return '';
322
- // Build real file set — only files that appear as SOURCE in edges (these are real scanned files)
323
- const realFiles = new Set(report.dependencyGraph.edges.map(e => e.from));
324
- // Count connections only for real files
325
- const connectionCount = {};
326
- for (const edge of report.dependencyGraph.edges) {
327
- if (realFiles.has(edge.from)) {
328
- connectionCount[edge.from] = (connectionCount[edge.from] || 0) + 1;
329
- }
330
- if (realFiles.has(edge.to)) {
331
- connectionCount[edge.to] = (connectionCount[edge.to] || 0) + 1;
332
- }
333
- }
334
- // Build layer map from report layers
335
- const layerMap = {};
336
- for (const layer of report.layers) {
337
- for (const file of layer.files) {
338
- layerMap[file] = layer.name;
339
- }
340
- }
341
- // Create nodes only from real files
342
- const allNodes = [...realFiles].map(n => ({
343
- id: n,
344
- name: n.split('/').pop() || n,
345
- connections: connectionCount[n] || 0,
346
- layer: layerMap[n] || 'Other',
347
- }));
348
- // ── Fallback: color by module/directory when layer detection is weak ──
349
- const otherCount = allNodes.filter(n => n.layer === 'Other').length;
350
- const useModuleColoring = allNodes.length > 0 && (otherCount / allNodes.length) > 0.7;
351
- // Palette for module-based coloring (10 distinct, vibrant colors)
352
- const modulePalette = [
353
- '#3b82f6', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6',
354
- '#06b6d4', '#ef4444', '#84cc16', '#f97316', '#6366f1',
355
- ];
356
- let moduleColorMap = {};
357
- if (useModuleColoring) {
358
- // Extract module (first meaningful directory) from each node path
359
- const getModule = (filePath) => {
360
- const parts = filePath.split('/');
361
- if (parts.length < 2)
362
- return 'root';
363
- const first = parts[0];
364
- // If first dir is common source dir, use second level
365
- if (['src', 'lib', 'app', 'packages', 'modules', 'features', 'apps'].includes(first)) {
366
- return parts.length > 2 ? parts[1] : first;
367
- }
368
- return first;
369
- };
370
- // Assign colors to modules
371
- const moduleNames = [...new Set(allNodes.map(n => getModule(n.id)))];
372
- moduleNames.forEach((mod, i) => {
373
- moduleColorMap[mod] = modulePalette[i % modulePalette.length];
374
- });
375
- // Reassign layer field to module name for coloring
376
- for (const node of allNodes) {
377
- node.layer = getModule(node.id);
378
- }
379
- }
380
- // Build links only between real files
381
- const allLinks = report.dependencyGraph.edges
382
- .filter(e => realFiles.has(e.from) && realFiles.has(e.to))
383
- .map(e => ({ source: e.from, target: e.to }));
384
- // Limit to top N most-connected nodes for large projects
385
- const maxNodes = 60;
386
- const sortedNodes = [...allNodes].sort((a, b) => b.connections - a.connections);
387
- const limitedNodes = sortedNodes.slice(0, maxNodes);
388
- const limitedNodeIds = new Set(limitedNodes.map(n => n.id));
389
- const limitedLinks = allLinks.filter(l => limitedNodeIds.has(l.source) && limitedNodeIds.has(l.target));
390
- const isLimited = allNodes.length > maxNodes;
391
- // Collect unique layers/modules from limited nodes
392
- const uniqueLayers = [...new Set(limitedNodes.map(n => n.layer))];
393
- // Build dynamic color map for legend and D3
394
- const colorMap = useModuleColoring
395
- ? moduleColorMap
396
- : { API: '#ec4899', Service: '#3b82f6', Data: '#10b981', UI: '#f59e0b', Infrastructure: '#8b5cf6', Other: '#64748b' };
397
- const legendLabel = useModuleColoring ? 'Colored by module' : 'Colored by layer';
398
- const legendHtml = uniqueLayers.map(l => {
399
- const color = colorMap[l] || '#64748b';
400
- return `<span class="legend-item"><span class="legend-dot" style="background: ${color}"></span> ${l}</span>`;
401
- }).join('');
402
- const filterHtml = uniqueLayers.map(l => {
403
- const color = colorMap[l] || '#64748b';
404
- return `<label class="graph-filter-check"><input type="checkbox" checked data-layer="${l}" onchange="toggleGraphLayer('${l}', this.checked)"><span class="legend-dot" style="background: ${color}"></span> ${l}</label>`;
405
- }).join('');
406
- return `
407
- <h2 class="section-title">🔗 Dependency Graph</h2>
408
- <div class="card graph-card">
409
- <div class="graph-controls">
410
- <div class="graph-legend">
411
- <span class="legend-label" style="color:#94a3b8;font-size:11px;margin-right:8px;">${legendLabel}:</span>
412
- ${legendHtml}
413
- </div>
414
- <div class="graph-filters">
415
- <input type="text" id="graphSearch" class="graph-search" placeholder="🔍 Search node..." oninput="filterGraphNodes(this.value)">
416
- <div class="graph-layer-filters">
417
- ${filterHtml}
418
- </div>
419
- </div>
420
- ${isLimited ? `<div class="graph-limit-notice">Showing top ${maxNodes} of ${allNodes.length} source files (most connected) · ${limitedLinks.length} links</div>` : ''}
421
- </div>
422
- <div id="dep-graph" style="width:100%; min-height:500px;"></div>
423
- <div class="graph-hint">🖱️ Drag nodes • Scroll to zoom • Double-click to reset • Node size = connections</div>
424
- </div>
425
- <script type="application/json" id="graph-nodes">${JSON.stringify(limitedNodes)}${'</' + 'script>'}
426
- <script type="application/json" id="graph-links">${JSON.stringify(limitedLinks)}${'</' + 'script>'}
427
- <script type="application/json" id="graph-colors">${JSON.stringify(colorMap)}${'</' + 'script>'}`;
428
- }
429
- /**
430
- * Bubble chart for anti-patterns — bigger = more severe
431
- */
432
- renderAntiPatternBubbles(report, grouped) {
433
- if (report.antiPatterns.length === 0) {
434
- return `
435
- <h2 class="section-title">✅ Anti-Patterns</h2>
436
- <div class="card success-card">
437
- <p>No significant anti-patterns detected. Excellent architecture!</p>
438
- </div>`;
439
- }
440
- const severityWeight = {
441
- CRITICAL: 80, HIGH: 60, MEDIUM: 40, LOW: 25,
442
- };
443
- const severityColor = {
444
- CRITICAL: '#ef4444', HIGH: '#f59e0b', MEDIUM: '#60a5fa', LOW: '#22c55e',
445
- };
446
- const bubbles = Object.entries(grouped).map(([name, data]) => ({
447
- name,
448
- count: data.count,
449
- severity: data.severity,
450
- radius: (severityWeight[data.severity] || 30) + data.count * 8,
451
- color: severityColor[data.severity] || '#64748b',
452
- }));
453
- return `
454
- <h2 class="section-title">🫧 Anti-Pattern Impact Map</h2>
455
- <div class="card" style="display:flex; justify-content:center;">
456
- <div id="bubble-chart" style="width:100%; min-height:300px;"></div>
457
- </div>
458
- <script type="application/json" id="bubble-data">${JSON.stringify(bubbles)}<\/script>`;
459
- }
460
- renderAntiPatterns(report, grouped) {
461
- if (report.antiPatterns.length === 0)
462
- return '';
463
- const rows = Object.entries(grouped)
464
- .sort((a, b) => b[1].count - a[1].count)
465
- .map(([name, data]) => `
466
- <tr>
467
- <td><strong>${this.escapeHtml(name)}</strong></td>
468
- <td class="count-cell">${data.count}</td>
469
- <td><span class="severity-badge severity-${data.severity}">${data.severity}</span></td>
470
- <td><small class="suggestion">${this.escapeHtml(data.suggestion)}</small></td>
471
- <td><div class="locations">${data.locations
472
- .slice(0, 5)
473
- .map((l) => `<code>${this.escapeHtml(l)}</code>`)
474
- .join(' ')}${data.locations.length > 5 ? ` <em>+${data.count - 5} more</em>` : ''}</div></td>
475
- </tr>`)
476
- .join('');
477
- return `
478
- <h2 class="section-title">⚠️ Anti-Pattern Details (${report.antiPatterns.length})</h2>
479
- <div class="card">
480
- <table>
481
- <thead>
482
- <tr>
483
- <th>Pattern</th>
484
- <th>Count</th>
485
- <th>Severity</th>
486
- <th>Suggestion</th>
487
- <th>Locations</th>
488
- </tr>
489
- </thead>
490
- <tbody>${rows}</tbody>
491
- </table>
492
- </div>`;
493
- }
494
- renderSuggestions(suggestions) {
495
- if (suggestions.length === 0)
496
- return '';
497
- const rows = suggestions
498
- .map((s, i) => `
499
- <tr>
500
- <td>${i + 1}</td>
501
- <td><span class="severity-badge severity-${s.priority}">${s.priority}</span></td>
502
- <td>
503
- <strong>${this.escapeHtml(s.title)}</strong>
504
- ${s.count > 1 ? `<span class="count-badge">×${s.count}</span>` : ''}
505
- <br/><small class="suggestion">${this.escapeHtml(s.description)}</small>
506
- </td>
507
- <td class="impact">${this.escapeHtml(s.impact)}</td>
508
- </tr>`)
509
- .join('');
510
- return `
511
- <h2 class="section-title">💡 Refactoring Suggestions</h2>
512
- <div class="card">
513
- <table>
514
- <thead>
515
- <tr>
516
- <th>#</th>
517
- <th>Priority</th>
518
- <th>Suggestion</th>
519
- <th>Impact</th>
520
- </tr>
521
- </thead>
522
- <tbody>${rows}</tbody>
523
- </table>
524
- </div>`;
525
- }
526
- renderFooter() {
527
- return `
528
- <div class="footer">
529
- <p>Generated by <a href="https://github.com/camilooscargbaptista/architect">⚡ Architect v3.1</a> — Enterprise Architecture Intelligence</p>
530
- <p>By <strong>Camilo Girardelli</strong> · <a href="https://www.girardellitecnologia.com">Girardelli Tecnologia</a></p>
531
- </div>`;
532
- }
533
- // ── Refactoring Plan Section ──
534
- opColor(type) {
535
- switch (type) {
536
- case 'CREATE': return '#22c55e';
537
- case 'MOVE': return '#3b82f6';
538
- case 'MODIFY': return '#f59e0b';
539
- case 'DELETE': return '#ef4444';
540
- default: return '#64748b';
541
- }
542
- }
543
- opIcon(type) {
544
- switch (type) {
545
- case 'CREATE': return '➕';
546
- case 'MOVE': return '📦';
547
- case 'MODIFY': return '✏️';
548
- case 'DELETE': return '🗑️';
549
- default: return '📄';
550
- }
551
- }
552
- renderRefactoringPlan(plan) {
553
- if (plan.steps.length === 0) {
554
- return `
555
- <h2 class="section-title">✅ Refactoring Plan</h2>
556
- <div class="card success-card">
557
- <p>No refactoring needed! Your architecture is already in great shape.</p>
558
- </div>`;
559
- }
560
- const improvement = plan.estimatedScoreAfter.overall - plan.currentScore.overall;
561
- const metrics = Object.keys(plan.currentScore.breakdown);
562
- const bars = metrics.map(metric => {
563
- const before = plan.currentScore.breakdown[metric];
564
- const after = plan.estimatedScoreAfter.breakdown[metric] ?? before;
565
- const diff = after - before;
566
- return `
567
- <div class="comparison-row">
568
- <div class="refactor-metric-name">${metric}</div>
569
- <div class="refactor-metric-bars">
570
- <div class="rbar-before" style="width: ${before}%; background: ${this.scoreColor(before)}40"><span>${before}</span></div>
571
- <div class="rbar-after" style="width: ${after}%; background: ${this.scoreColor(after)}"><span>${after}</span></div>
572
- </div>
573
- <div class="refactor-metric-diff" style="color: ${diff > 0 ? '#22c55e' : '#64748b'}">
574
- ${diff > 0 ? `+${diff}` : diff === 0 ? '—' : String(diff)}
575
- </div>
576
- </div>`;
577
- }).join('');
578
- const stepsHtml = plan.steps.map(step => this.renderRefactorStep(step)).join('');
579
- const criticalCount = plan.steps.filter(s => s.priority === 'CRITICAL').length;
580
- const highCount = plan.steps.filter(s => s.priority === 'HIGH').length;
581
- const mediumCount = plan.steps.filter(s => s.priority === 'MEDIUM').length;
582
- const lowCount = plan.steps.filter(s => s.priority === 'LOW').length;
583
- return `
584
- <h2 class="section-title">🔧 Refactoring Plan</h2>
585
-
586
- <div class="card refactor-score">
587
- <div class="refactor-score-pair">
588
- <div class="rscore-box">
589
- <div class="rscore-num" style="color: ${this.scoreColor(plan.currentScore.overall)}">${plan.currentScore.overall}</div>
590
- <div class="rscore-label">Current</div>
591
- </div>
592
- <div class="rscore-arrow">
593
- <svg width="60" height="30" viewBox="0 0 60 30">
594
- <path d="M5 15 L45 15 M40 8 L48 15 L40 22" stroke="#818cf8" stroke-width="2.5" fill="none"/>
595
- </svg>
596
- </div>
597
- <div class="rscore-box">
598
- <div class="rscore-num" style="color: ${this.scoreColor(plan.estimatedScoreAfter.overall)}">${plan.estimatedScoreAfter.overall}</div>
599
- <div class="rscore-label">Estimated</div>
600
- </div>
601
- <div class="rscore-improvement" style="color: #22c55e">+${improvement} pts</div>
602
- </div>
603
- <div class="refactor-bars-section">
604
- <div class="refactor-legend">
605
- <span class="rlegend-tag rbefore">Before</span>
606
- <span class="rlegend-tag rafter">After</span>
607
- </div>
608
- ${bars}
609
- </div>
610
- </div>
611
-
612
- <div class="refactor-stats-row">
613
- <div class="rstat">${plan.steps.length} steps</div>
614
- <div class="rstat">${plan.totalOperations} operations</div>
615
- <div class="rstat">Tier 1: ${plan.tier1Steps}</div>
616
- <div class="rstat">Tier 2: ${plan.tier2Steps}</div>
617
- </div>
618
-
619
- <div class="priority-bar">
620
- ${criticalCount ? `<div class="prio-seg prio-critical" style="flex: ${criticalCount}">🔴 ${criticalCount}</div>` : ''}
621
- ${highCount ? `<div class="prio-seg prio-high" style="flex: ${highCount}">🟠 ${highCount}</div>` : ''}
622
- ${mediumCount ? `<div class="prio-seg prio-medium" style="flex: ${mediumCount}">🔵 ${mediumCount}</div>` : ''}
623
- ${lowCount ? `<div class="prio-seg prio-low" style="flex: ${lowCount}">🟢 ${lowCount}</div>` : ''}
624
- </div>
625
-
626
- <div class="refactor-roadmap">
627
- ${stepsHtml}
628
- </div>`;
629
- }
630
- renderRefactorStep(step) {
631
- const operationsHtml = step.operations.map(op => `
632
- <div class="rop">
633
- <span class="rop-icon">${this.opIcon(op.type)}</span>
634
- <span class="rop-badge" style="background: ${this.opColor(op.type)}20; color: ${this.opColor(op.type)}; border: 1px solid ${this.opColor(op.type)}40">${op.type}</span>
635
- <code class="rop-path">${this.escapeHtml(op.path)}</code>
636
- ${op.newPath ? `<span class="rop-arrow">→</span> <code class="rop-path">${this.escapeHtml(op.newPath)}</code>` : ''}
637
- <div class="rop-desc">${this.escapeHtml(op.description)}</div>
638
- </div>
639
- `).join('');
640
- const impactHtml = step.scoreImpact.map(i => `<span class="rimpact-tag">${i.metric}: ${i.before}→${i.after} <strong>+${i.after - i.before}</strong></span>`).join('');
641
- return `
642
- <div class="rstep-card">
643
- <div class="rstep-header">
644
- <div class="rstep-number">${step.id}</div>
645
- <div class="rstep-info">
646
- <div class="rstep-title-row">
647
- <h3>${this.escapeHtml(step.title)}</h3>
648
- <span class="severity-badge severity-${step.priority}">${step.priority}</span>
649
- <span class="tier-badge">Tier ${step.tier}</span>
650
- </div>
651
- <p class="rstep-desc">${this.escapeHtml(step.description)}</p>
652
- <details class="rstep-details">
653
- <summary>📖 Why?</summary>
654
- <p class="rstep-rationale">${this.escapeHtml(step.rationale)}</p>
655
- </details>
656
- </div>
657
- </div>
658
- <details class="rstep-ops-accordion">
659
- <summary class="rstep-ops-toggle">📋 Operations (${step.operations.length})</summary>
660
- <div class="rstep-ops">
661
- ${operationsHtml}
662
- </div>
663
- </details>
664
- <div class="rstep-impact">
665
- <h4>📈 Score Impact</h4>
666
- <div class="rimpact-tags">${impactHtml}</div>
667
- </div>
668
- </div>`;
669
- }
670
- /**
671
- * All JavaScript for D3.js visualizations, animated counters, and radar chart
672
- */
673
- getScripts(report) {
674
- const breakdown = report.score.breakdown;
675
- return `<script>
676
- // ── Animated Counters ──
677
- document.addEventListener('DOMContentLoaded', () => {
678
- const counters = document.querySelectorAll('.score-counter, .stat-counter');
679
- const observer = new IntersectionObserver((entries) => {
680
- entries.forEach(entry => {
681
- if (entry.isIntersecting) {
682
- const el = entry.target;
683
- const target = parseInt(el.dataset.target || '0');
684
- animateCounter(el, target);
685
- observer.unobserve(el);
686
- }
687
- });
688
- }, { threshold: 0.5 });
689
-
690
- counters.forEach(c => observer.observe(c));
691
-
692
- // ── Sidebar Active Section Tracking ──
693
- const sectionIds = ['score', 'layers', 'anti-patterns', 'suggestions', 'refactoring', 'agents'];
694
- const sectionObserver = new IntersectionObserver((entries) => {
695
- entries.forEach(entry => {
696
- if (entry.isIntersecting) {
697
- document.querySelectorAll('.sidebar-link').forEach(l => l.classList.remove('active'));
698
- const link = document.querySelector('.sidebar-link[data-section="' + entry.target.id + '"]');
699
- if (link) link.classList.add('active');
700
- }
701
- });
702
- }, { threshold: 0.15, rootMargin: '-80px 0px -60% 0px' });
703
-
704
- sectionIds.forEach(id => {
705
- const el = document.getElementById(id);
706
- if (el) sectionObserver.observe(el);
707
- });
708
- });
709
-
710
- function animateCounter(el, target) {
711
- const duration = 1500;
712
- const start = performance.now();
713
- const update = (now) => {
714
- const elapsed = now - start;
715
- const progress = Math.min(elapsed / duration, 1);
716
- const ease = 1 - Math.pow(1 - progress, 3);
717
- el.textContent = Math.round(target * ease).toLocaleString();
718
- if (progress < 1) requestAnimationFrame(update);
719
- };
720
- requestAnimationFrame(update);
721
- }
722
-
723
- // ── Radar Chart ──
724
- (function() {
725
- const data = [
726
- { axis: 'Modularity', value: ${breakdown.modularity} },
727
- { axis: 'Coupling', value: ${breakdown.coupling} },
728
- { axis: 'Cohesion', value: ${breakdown.cohesion} },
729
- { axis: 'Layering', value: ${breakdown.layering} },
730
- ];
731
-
732
- const svg = d3.select('#radar-chart');
733
- const w = 350, h = 350, cx = w/2, cy = h/2, maxR = 120;
734
- const levels = 5;
735
- const total = data.length;
736
- const angleSlice = (Math.PI * 2) / total;
737
-
738
- // Grid circles
739
- for (let i = 1; i <= levels; i++) {
740
- const r = (maxR / levels) * i;
741
- svg.append('circle')
742
- .attr('cx', cx).attr('cy', cy).attr('r', r)
743
- .attr('fill', 'none').attr('stroke', '#334155').attr('stroke-width', 0.5)
744
- .attr('stroke-dasharray', '4,4');
745
-
746
- svg.append('text')
747
- .attr('x', cx + 4).attr('y', cy - r + 4)
748
- .text(Math.round(100 / levels * i))
749
- .attr('fill', '#475569').attr('font-size', '10px');
750
- }
751
-
752
- // Axis lines
753
- data.forEach((d, i) => {
754
- const angle = angleSlice * i - Math.PI/2;
755
- const x = cx + Math.cos(angle) * (maxR + 20);
756
- const y = cy + Math.sin(angle) * (maxR + 20);
757
-
758
- svg.append('line')
759
- .attr('x1', cx).attr('y1', cy).attr('x2', cx + Math.cos(angle) * maxR).attr('y2', cy + Math.sin(angle) * maxR)
760
- .attr('stroke', '#334155').attr('stroke-width', 1);
761
-
762
- svg.append('text')
763
- .attr('x', x).attr('y', y)
764
- .attr('text-anchor', 'middle').attr('dominant-baseline', 'middle')
765
- .attr('fill', '#94a3b8').attr('font-size', '12px').attr('font-weight', '600')
766
- .text(d.axis);
767
- });
768
-
769
- // Data polygon
770
- const points = data.map((d, i) => {
771
- const angle = angleSlice * i - Math.PI/2;
772
- const r = (d.value / 100) * maxR;
773
- return [cx + Math.cos(angle) * r, cy + Math.sin(angle) * r];
774
- });
775
-
776
- const pointsStr = points.map(p => p.join(',')).join(' ');
777
-
778
- svg.append('polygon')
779
- .attr('points', pointsStr)
780
- .attr('fill', 'rgba(129, 140, 248, 0.15)')
781
- .attr('stroke', '#818cf8').attr('stroke-width', 2);
782
-
783
- // Data dots
784
- points.forEach((p, i) => {
785
- const color = data[i].value >= 70 ? '#22c55e' : data[i].value >= 50 ? '#f59e0b' : '#ef4444';
786
- svg.append('circle')
787
- .attr('cx', p[0]).attr('cy', p[1]).attr('r', 5)
788
- .attr('fill', color).attr('stroke', '#0f172a').attr('stroke-width', 2);
789
-
790
- svg.append('text')
791
- .attr('x', p[0]).attr('y', p[1] - 12)
792
- .attr('text-anchor', 'middle')
793
- .attr('fill', color).attr('font-size', '12px').attr('font-weight', '700')
794
- .text(data[i].value);
795
- });
796
- })();
797
-
798
- // ── D3 Force Dependency Graph ──
799
- (function() {
800
- const nodesEl = document.getElementById('graph-nodes');
801
- const linksEl = document.getElementById('graph-links');
802
- if (!nodesEl || !linksEl) return;
803
-
804
- const nodes = JSON.parse(nodesEl.textContent || '[]');
805
- const links = JSON.parse(linksEl.textContent || '[]');
806
- if (nodes.length === 0) return;
807
-
808
- const container = document.getElementById('dep-graph');
809
- const width = container.clientWidth || 800;
810
- const height = 500;
811
- container.style.height = height + 'px';
812
-
813
- // Dynamic color map — loaded from JSON (supports both layer and module coloring)
814
- const colorsEl = document.getElementById('graph-colors');
815
- const layerColors = colorsEl ? JSON.parse(colorsEl.textContent || '{}') : {
816
- API: '#ec4899', Service: '#3b82f6', Data: '#10b981',
817
- UI: '#f59e0b', Infrastructure: '#8b5cf6', Other: '#64748b',
818
- };
819
-
820
- const svg = d3.select('#dep-graph').append('svg')
821
- .attr('width', width).attr('height', height)
822
- .attr('viewBox', [0, 0, width, height]);
823
-
824
- // Zoom container
825
- const g = svg.append('g');
826
-
827
- // Zoom behavior
828
- const zoom = d3.zoom()
829
- .scaleExtent([0.2, 5])
830
- .on('zoom', (event) => { g.attr('transform', event.transform); });
831
- svg.call(zoom);
832
-
833
- // Double-click to reset zoom
834
- svg.on('dblclick.zoom', () => {
835
- svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
836
- });
837
-
838
- // Arrow marker
839
- g.append('defs').append('marker')
840
- .attr('id', 'arrowhead').attr('viewBox', '-0 -5 10 10')
841
- .attr('refX', 20).attr('refY', 0).attr('orient', 'auto')
842
- .attr('markerWidth', 6).attr('markerHeight', 6)
843
- .append('path').attr('d', 'M 0,-5 L 10,0 L 0,5')
844
- .attr('fill', '#475569');
845
-
846
- // Tuned simulation for better spread
847
- const simulation = d3.forceSimulation(nodes)
848
- .force('link', d3.forceLink(links).id(d => d.id).distance(80))
849
- .force('charge', d3.forceManyBody().strength(-250))
850
- .force('center', d3.forceCenter(width / 2, height / 2))
851
- .force('x', d3.forceX(width / 2).strength(0.05))
852
- .force('y', d3.forceY(height / 2).strength(0.05))
853
- .force('collision', d3.forceCollide().radius(d => Math.max(d.connections * 2 + 16, 20)));
854
-
855
- const link = g.append('g')
856
- .selectAll('line').data(links).join('line')
857
- .attr('stroke', '#334155').attr('stroke-width', 1)
858
- .attr('stroke-opacity', 0.4).attr('marker-end', 'url(#arrowhead)');
859
-
860
- const node = g.append('g')
861
- .selectAll('g').data(nodes).join('g')
862
- .call(d3.drag()
863
- .on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
864
- .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
865
- .on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
866
- );
867
-
868
- // Node circles — color by layer
869
- node.append('circle')
870
- .attr('r', d => Math.max(d.connections * 2.5 + 5, 6))
871
- .attr('fill', d => layerColors[d.layer] || '#64748b')
872
- .attr('stroke', '#0f172a').attr('stroke-width', 1.5)
873
- .attr('opacity', 0.9);
874
-
875
- // Node labels — only show for nodes with enough connections
876
- node.filter(d => d.connections >= 2).append('text')
877
- .text(d => d.name.replace(/\\.[^.]+$/, ''))
878
- .attr('x', 0).attr('y', d => -(Math.max(d.connections * 2.5 + 5, 6) + 4))
879
- .attr('text-anchor', 'middle')
880
- .attr('fill', '#e2e8f0').attr('font-size', '9px').attr('font-weight', '500');
881
-
882
- // Tooltip
883
- node.append('title')
884
- .text(d => d.id + '\\nConnections: ' + d.connections + '\\nLayer: ' + d.layer);
885
-
886
- simulation.on('tick', () => {
887
- link
888
- .attr('x1', d => d.source.x).attr('y1', d => d.source.y)
889
- .attr('x2', d => d.target.x).attr('y2', d => d.target.y);
890
- node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
891
- });
892
-
893
- // Expose search and filter functions
894
- window.filterGraphNodes = function(query) {
895
- if (!query) {
896
- node.attr('opacity', 1);
897
- link.attr('opacity', 0.4);
898
- return;
899
- }
900
- query = query.toLowerCase();
901
- node.attr('opacity', d => d.id.toLowerCase().includes(query) || d.name.toLowerCase().includes(query) ? 1 : 0.1);
902
- link.attr('opacity', d => {
903
- const srcMatch = d.source.id.toLowerCase().includes(query);
904
- const tgtMatch = d.target.id.toLowerCase().includes(query);
905
- return (srcMatch || tgtMatch) ? 0.6 : 0.05;
906
- });
907
- };
908
-
909
- window.toggleGraphLayer = function(layer, visible) {
910
- node.filter(d => d.layer === layer)
911
- .transition().duration(300)
912
- .attr('opacity', visible ? 1 : 0.05);
913
- link.filter(d => d.source.layer === layer || d.target.layer === layer)
914
- .transition().duration(300)
915
- .attr('opacity', visible ? 0.4 : 0.02);
916
- };
917
- })();
918
-
919
- // ── Bubble Chart ──
920
- (function() {
921
- const dataEl = document.getElementById('bubble-data');
922
- if (!dataEl) return;
923
-
924
- const bubbles = JSON.parse(dataEl.textContent || '[]');
925
- if (bubbles.length === 0) return;
926
-
927
- const container = document.getElementById('bubble-chart');
928
- const width = container.clientWidth || 600;
929
- const height = 300;
930
-
931
- const svg = d3.select('#bubble-chart').append('svg')
932
- .attr('width', width).attr('height', height)
933
- .attr('viewBox', [0, 0, width, height]);
934
-
935
- const simulation = d3.forceSimulation(bubbles)
936
- .force('charge', d3.forceManyBody().strength(5))
937
- .force('center', d3.forceCenter(width / 2, height / 2))
938
- .force('collision', d3.forceCollide().radius(d => d.radius + 4))
939
- .stop();
940
-
941
- for (let i = 0; i < 120; i++) simulation.tick();
942
-
943
- const g = svg.selectAll('g').data(bubbles).join('g')
944
- .attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
945
-
946
- // Glow effect
947
- g.append('circle')
948
- .attr('r', d => d.radius)
949
- .attr('fill', d => d.color + '20')
950
- .attr('stroke', d => d.color).attr('stroke-width', 2)
951
- .attr('opacity', 0)
952
- .transition().duration(800).delay((d, i) => i * 200)
953
- .attr('opacity', 1);
954
-
955
- // Inner circle
956
- g.append('circle')
957
- .attr('r', d => d.radius * 0.7)
958
- .attr('fill', d => d.color + '30')
959
- .attr('opacity', 0)
960
- .transition().duration(800).delay((d, i) => i * 200)
961
- .attr('opacity', 1);
962
-
963
- // Name
964
- g.append('text')
965
- .text(d => d.name)
966
- .attr('text-anchor', 'middle').attr('dy', '-0.3em')
967
- .attr('fill', '#e2e8f0').attr('font-size', d => Math.max(d.radius / 4, 10) + 'px')
968
- .attr('font-weight', '700');
969
-
970
- // Count badge
971
- g.append('text')
972
- .text(d => '×' + d.count)
973
- .attr('text-anchor', 'middle').attr('dy', '1.2em')
974
- .attr('fill', d => d.color).attr('font-size', d => Math.max(d.radius / 5, 9) + 'px')
975
- .attr('font-weight', '600');
976
-
977
- // Severity label
978
- g.append('text')
979
- .text(d => d.severity)
980
- .attr('text-anchor', 'middle').attr('dy', '2.5em')
981
- .attr('fill', '#64748b').attr('font-size', '9px').attr('text-transform', 'uppercase');
982
- })();
983
- <\/script>`;
984
- }
985
- renderAgentSuggestions(s) {
986
- const roleIcon = (name) => {
987
- if (name.includes('ORCHESTRATOR'))
988
- return '\u{1F3AD}';
989
- if (name.includes('BACKEND') || name.includes('FRONTEND') || name.includes('DATABASE') || name.includes('FLUTTER'))
990
- return '\u{1F4BB}';
991
- if (name.includes('SECURITY'))
992
- return '\u{1F6E1}\uFE0F';
993
- if (name.includes('QA'))
994
- return '\u{1F9EA}';
995
- if (name.includes('TECH-DEBT'))
996
- return '\u{1F4CA}';
997
- return '\u{1F916}';
998
- };
999
- const roleLabel = (name) => {
1000
- if (name.includes('ORCHESTRATOR'))
1001
- return 'coordination';
1002
- if (name.includes('SECURITY'))
1003
- return 'protection';
1004
- if (name.includes('QA'))
1005
- return 'quality';
1006
- if (name.includes('TECH-DEBT'))
1007
- return 'governance';
1008
- return 'development';
1009
- };
1010
- const roleColor = (name) => {
1011
- if (name.includes('ORCHESTRATOR'))
1012
- return '#c084fc';
1013
- if (name.includes('SECURITY'))
1014
- return '#f87171';
1015
- if (name.includes('QA'))
1016
- return '#34d399';
1017
- if (name.includes('TECH-DEBT'))
1018
- return '#fbbf24';
1019
- return '#60a5fa';
1020
- };
1021
- // Status helpers
1022
- const statusBadge = (status) => {
1023
- const map = {
1024
- 'KEEP': { icon: '✅', label: 'KEEP', color: '#22c55e' },
1025
- 'MODIFY': { icon: '🔵', label: 'MODIFY', color: '#3b82f6' },
1026
- 'CREATE': { icon: '🟡', label: 'NEW', color: '#f59e0b' },
1027
- 'DELETE': { icon: '🔴', label: 'REMOVE', color: '#ef4444' },
1028
- };
1029
- const s = map[status] || map['CREATE'];
1030
- return `<span class="agent-status-badge" style="background:${s.color}20;color:${s.color};border:1px solid ${s.color}40">${s.icon} ${s.label}</span>`;
1031
- };
1032
- const statusBorder = (status) => {
1033
- const map = {
1034
- 'KEEP': '#22c55e', 'MODIFY': '#3b82f6', 'CREATE': '#f59e0b', 'DELETE': '#ef4444',
1035
- };
1036
- return map[status] || '#334155';
1037
- };
1038
- const agentCards = s.suggestedAgents.map(a => `<label class="agent-toggle-card" data-category="agents" data-name="${a.name}">
1039
- <input type="checkbox" class="agent-check" ${a.status !== 'DELETE' ? 'checked' : ''} data-type="agents" data-item="${a.name}">
1040
- <div class="agent-toggle-inner" style="border-color:${statusBorder(a.status)}">
1041
- <div class="agent-toggle-icon">${roleIcon(a.name)}</div>
1042
- <div class="agent-toggle-info">
1043
- <span class="agent-toggle-name">${a.name}</span>
1044
- <span class="agent-toggle-role" style="color:${roleColor(a.name)}">${roleLabel(a.name)}</span>
1045
- ${a.description ? `<span class="agent-toggle-desc">${a.description}</span>` : ''}
1046
- </div>
1047
- ${statusBadge(a.status)}
1048
- <div class="agent-toggle-check">\u2713</div>
1049
- </div>
1050
- </label>`).join('\n');
1051
- const miniCard = (item, icon, type) => `<label class="agent-toggle-card mini" data-category="${type}">
1052
- <input type="checkbox" class="agent-check" ${item.status !== 'DELETE' ? 'checked' : ''} data-type="${type}" data-item="${item.name}">
1053
- <div class="agent-toggle-inner" style="border-color:${statusBorder(item.status)}">
1054
- <span class="agent-toggle-icon">${icon}</span>
1055
- <div class="agent-toggle-info">
1056
- <span class="agent-toggle-name">${item.name}.md</span>
1057
- ${item.description ? `<span class="agent-toggle-desc">${item.description}</span>` : ''}
1058
- </div>
1059
- ${statusBadge(item.status)}
1060
- <div class="agent-toggle-check">\u2713</div>
1061
- </div>
1062
- </label>`;
1063
- const ruleCards = s.suggestedRules.map(r => miniCard(r, '\u{1F4CF}', 'rules')).join('\n');
1064
- const guardCards = s.suggestedGuards.map(g => miniCard(g, '\u{1F6E1}\uFE0F', 'guards')).join('\n');
1065
- const workflowCards = s.suggestedWorkflows.map(w => miniCard(w, '\u26A1', 'workflows')).join('\n');
1066
- const skillCards = s.suggestedSkills.map(sk => `<label class="agent-toggle-card" data-category="skills">
1067
- <input type="checkbox" class="agent-check" checked data-type="skills" data-item="${sk.source}">
1068
- <div class="agent-toggle-inner" style="border-color:${statusBorder(sk.status)}">
1069
- <span class="agent-toggle-icon">\u{1F9E0}</span>
1070
- <div class="agent-toggle-info">
1071
- <span class="agent-toggle-name">${sk.name}</span>
1072
- <span class="agent-toggle-role" style="color:#34d399">${sk.description}</span>
1073
- </div>
1074
- ${statusBadge(sk.status)}
1075
- <div class="agent-toggle-check">\u2713</div>
1076
- </div>
1077
- </label>`).join('\n');
1078
- const auditSection = s.audit.filter(f => f.type !== 'OK').length > 0 ? `
1079
- <div class="agent-audit-section">
1080
- <h3 class="agent-section-subtitle">\u{1F50D} Audit Findings</h3>
1081
- <div class="agent-audit-grid">
1082
- ${s.audit.filter(f => f.type !== 'OK').map(f => {
1083
- const icon = f.type === 'MISSING' ? '\u274C' : f.type === 'IMPROVEMENT' ? '\u{1F4A1}' : '\u26A0\uFE0F';
1084
- const cls = f.type === 'MISSING' ? 'audit-missing' : 'audit-improvement';
1085
- return `<div class="agent-audit-item ${cls}">
1086
- <span class="audit-icon">${icon}</span>
1087
- <div class="audit-content">
1088
- <span class="audit-desc">${f.description}</span>
1089
- ${f.suggestion ? `<span class="audit-suggestion">\u2192 ${f.suggestion}</span>` : ''}
1090
- </div>
1091
- </div>`;
1092
- }).join('\n')}
1093
- </div>
1094
- </div>` : '';
1095
- const stackPills = [
1096
- `\u{1F527} ${s.stack.primary}`,
1097
- `\u{1F4E6} ${s.stack.frameworks.length > 0 ? s.stack.frameworks.join(', ') : 'No framework'}`,
1098
- s.hasExistingAgents ? '\u{1F4C1} Existing .agent/' : '\u{1F4C1} New .agent/',
1099
- ...(s.stack.hasBackend ? ['\u{1F519} Backend'] : []),
1100
- ...(s.stack.hasFrontend ? ['\u{1F5A5}\uFE0F Frontend'] : []),
1101
- ...(s.stack.hasMobile ? ['\u{1F4F1} Mobile'] : []),
1102
- ...(s.stack.hasDatabase ? ['\u{1F5C4}\uFE0F Database'] : []),
1103
- ];
1104
- const totalItems = s.suggestedAgents.length + s.suggestedRules.length + s.suggestedGuards.length + s.suggestedWorkflows.length + s.suggestedSkills.length;
1105
- // Status summary counts
1106
- const allItems = [...s.suggestedAgents, ...s.suggestedRules, ...s.suggestedGuards, ...s.suggestedWorkflows];
1107
- const keepCount = allItems.filter(i => i.status === 'KEEP').length;
1108
- const modifyCount = allItems.filter(i => i.status === 'MODIFY').length;
1109
- const createCount = allItems.filter(i => i.status === 'CREATE').length;
1110
- return `
1111
- <h2 class="section-title">\u{1F916} Agent System</h2>
1112
-
1113
- <div class="card agent-system-card">
1114
- <div class="agent-stack-banner">
1115
- ${stackPills.map(p => `<div class="stack-pill">${p}</div>`).join('\n ')}
1116
- </div>
1117
-
1118
- <div class="agent-status-legend">
1119
- <span class="status-legend-item"><span class="legend-dot" style="background:#22c55e"></span> KEEP (${keepCount})</span>
1120
- <span class="status-legend-item"><span class="legend-dot" style="background:#3b82f6"></span> MODIFY (${modifyCount})</span>
1121
- <span class="status-legend-item"><span class="legend-dot" style="background:#f59e0b"></span> NEW (${createCount})</span>
1122
- </div>
1123
-
1124
- <div class="agent-controls">
1125
- <button class="agent-ctrl-btn" onclick="toggleAll(true)">\u2705 Select All</button>
1126
- <button class="agent-ctrl-btn" onclick="toggleAll(false)">\u2B1C Select None</button>
1127
- <span class="agent-count-label"><span id="agentSelectedCount">${totalItems}</span> selected</span>
1128
- </div>
1129
-
1130
- <h3 class="agent-section-subtitle">\u{1F916} Agents</h3>
1131
- <div class="agent-toggle-grid">
1132
- ${agentCards}
1133
- </div>
1134
-
1135
- <div class="agent-extras-grid">
1136
- <div>
1137
- <h3 class="agent-section-subtitle">\u{1F4CF} Rules</h3>
1138
- <div class="agent-toggle-list">${ruleCards}</div>
1139
- </div>
1140
- <div>
1141
- <h3 class="agent-section-subtitle">\u{1F6E1}\uFE0F Guards</h3>
1142
- <div class="agent-toggle-list">${guardCards}</div>
1143
- </div>
1144
- <div>
1145
- <h3 class="agent-section-subtitle">\u26A1 Workflows</h3>
1146
- <div class="agent-toggle-list">${workflowCards}</div>
1147
- </div>
1148
- </div>
1149
-
1150
- <h3 class="agent-section-subtitle">\u{1F9E0} Skills <span style="font-size:0.7rem;color:#94a3b8;font-weight:400">from skills.sh</span></h3>
1151
- <div class="agent-toggle-grid">
1152
- ${skillCards}
1153
- </div>
1154
-
1155
- ${auditSection}
1156
-
1157
- <div class="agent-command-box">
1158
- <div class="agent-command-header">
1159
- <span>\u{1F4A1} Command to generate selected items:</span>
1160
- <button class="agent-copy-btn" onclick="copyAgentCommand()">
1161
- <span id="copyIcon">\u{1F4CB}</span> Copy
1162
- </button>
1163
- </div>
1164
- <code id="agentCommandOutput" class="agent-command-code">${s.command}</code>
1165
- </div>
1166
- </div>
1167
-
1168
- <style>
1169
- .agent-system-card { padding: 1.5rem; }
1170
- .agent-stack-banner { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
1171
- .stack-pill { background: #1e293b; border: 1px solid #334155; border-radius: 99px; padding: 0.4rem 1rem; font-size: 0.8rem; color: #94a3b8; white-space: nowrap; }
1172
- .agent-status-legend { display: flex; gap: 1.5rem; margin-bottom: 1rem; padding: 0.5rem 0; border-bottom: 1px solid #1e293b; }
1173
- .status-legend-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: #94a3b8; }
1174
- .agent-status-badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.15rem 0.5rem; border-radius: 99px; font-size: 0.65rem; font-weight: 700; flex-shrink: 0; letter-spacing: 0.03em; }
1175
- .agent-toggle-desc { display: block; font-size: 0.65rem; color: #64748b; margin-top: 0.15rem; line-height: 1.3; }
1176
- .agent-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; }
1177
- .agent-ctrl-btn { background: #1e293b; border: 1px solid #334155; color: #e2e8f0; padding: 0.4rem 1rem; border-radius: 8px; font-size: 0.8rem; cursor: pointer; transition: all 0.2s; }
1178
- .agent-ctrl-btn:hover { background: #334155; }
1179
- .agent-count-label { color: #94a3b8; font-size: 0.85rem; margin-left: auto; }
1180
- #agentSelectedCount { color: #c084fc; font-weight: 700; }
1181
- .agent-section-subtitle { color: #e2e8f0; font-size: 1.05rem; font-weight: 700; margin: 1.25rem 0 0.75rem; }
1182
- .agent-toggle-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
1183
- .agent-toggle-card { cursor: pointer; transition: all 0.3s; }
1184
- .agent-toggle-card input { display: none; }
1185
- .agent-toggle-inner { display: flex; align-items: center; gap: 0.75rem; background: #1e293b; border: 2px solid #334155; border-radius: 12px; padding: 0.75rem 1rem; transition: all 0.3s; }
1186
- .agent-toggle-card input:checked + .agent-toggle-inner { background: #1e1b4b; }
1187
- .agent-toggle-icon { font-size: 1.3rem; flex-shrink: 0; }
1188
- .agent-toggle-info { flex: 1; min-width: 0; }
1189
- .agent-toggle-name { display: block; color: #e2e8f0; font-weight: 600; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1190
- .agent-toggle-role { display: block; font-size: 0.7rem; margin-top: 0.15rem; }
1191
- .agent-toggle-check { color: #334155; font-size: 1rem; flex-shrink: 0; transition: color 0.3s; }
1192
- .agent-toggle-card input:checked + .agent-toggle-inner .agent-toggle-check { color: #818cf8; }
1193
- .agent-toggle-card.mini .agent-toggle-inner { padding: 0.5rem 0.75rem; border-radius: 8px; }
1194
- .agent-extras-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 0.5rem; }
1195
- @media (max-width: 768px) { .agent-extras-grid { grid-template-columns: 1fr; } }
1196
- .agent-toggle-list { display: flex; flex-direction: column; gap: 0.5rem; }
1197
- .agent-audit-section { margin-top: 1.5rem; }
1198
- .agent-audit-grid { display: flex; flex-direction: column; gap: 0.5rem; }
1199
- .agent-audit-item { display: flex; gap: 0.75rem; align-items: flex-start; background: #1e293b; padding: 0.75rem 1rem; border-radius: 8px; }
1200
- .agent-audit-item.audit-missing { border-left: 3px solid #ef4444; }
1201
- .agent-audit-item.audit-improvement { border-left: 3px solid #fbbf24; }
1202
- .audit-icon { font-size: 1rem; flex-shrink: 0; margin-top: 2px; }
1203
- .audit-content { display: flex; flex-direction: column; gap: 0.25rem; }
1204
- .audit-desc { color: #e2e8f0; font-size: 0.85rem; }
1205
- .audit-suggestion { color: #94a3b8; font-size: 0.8rem; font-style: italic; }
1206
- .agent-command-box { margin-top: 1.5rem; background: #0f172a; border-radius: 12px; border: 1px solid #334155; overflow: hidden; }
1207
- .agent-command-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; background: #1e293b; font-size: 0.8rem; color: #94a3b8; }
1208
- .agent-copy-btn { background: #c084fc; color: #0f172a; border: none; border-radius: 6px; padding: 0.4rem 0.8rem; cursor: pointer; font-size: 0.75rem; font-weight: 600; transition: all 0.2s; }
1209
- .agent-copy-btn:hover { background: #a855f7; transform: scale(1.05); }
1210
- .agent-command-code { display: block; padding: 1rem; color: #c084fc; font-size: 0.85rem; word-break: break-all; font-family: 'Fira Code', monospace; }
1211
- </style>
1212
-
1213
- <script>
1214
- (function() {
1215
- var basePath = ${JSON.stringify(s.command.replace('architect agents ', ''))};
1216
- var totalItems = ${totalItems};
1217
- function updateCommand() {
1218
- var checks = document.querySelectorAll('.agent-check');
1219
- var selected = { agents: [], rules: [], guards: [], workflows: [], skills: [] };
1220
- var count = 0;
1221
- checks.forEach(function(cb) { if (cb.checked) { selected[cb.dataset.type].push(cb.dataset.item); count++; } });
1222
- document.getElementById('agentSelectedCount').textContent = count;
1223
- var cmd;
1224
- if (count === totalItems) { cmd = 'architect agents ' + basePath; }
1225
- else if (count === 0) { cmd = '# No items selected'; }
1226
- else {
1227
- var parts = ['architect agents ' + basePath];
1228
- if (selected.agents.length > 0) parts.push('--agents ' + selected.agents.join(','));
1229
- if (selected.rules.length > 0) parts.push('--rules ' + selected.rules.join(','));
1230
- if (selected.guards.length > 0) parts.push('--guards ' + selected.guards.join(','));
1231
- if (selected.workflows.length > 0) parts.push('--workflows ' + selected.workflows.join(','));
1232
- if (selected.skills.length > 0) parts.push('&& ' + selected.skills.map(function(sk){ return 'npx skills add ' + sk; }).join(' && '));
1233
- cmd = parts.join(' ');
1234
- }
1235
- document.getElementById('agentCommandOutput').textContent = cmd;
1236
- }
1237
- document.querySelectorAll('.agent-check').forEach(function(cb) { cb.addEventListener('change', updateCommand); });
1238
- window.toggleAll = function(state) { document.querySelectorAll('.agent-check').forEach(function(cb) { cb.checked = state; }); updateCommand(); };
1239
- window.copyAgentCommand = function() { var cmd = document.getElementById('agentCommandOutput').textContent; navigator.clipboard.writeText(cmd).then(function() { var btn = document.getElementById('copyIcon'); btn.textContent = '\u2705'; setTimeout(function() { btn.textContent = '\ud83d\udccb'; }, 2000); }); };
1240
- })();
1241
- <\/script>`;
1242
- }
1243
- getStyles() {
1244
- return `<style>
1245
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
1246
-
1247
- * { margin: 0; padding: 0; box-sizing: border-box; }
1248
-
1249
- body {
1250
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
1251
- background: #0f172a;
1252
- color: #e2e8f0;
1253
- line-height: 1.6;
1254
- min-height: 100vh;
1255
- }
1256
-
1257
- html { scroll-behavior: smooth; }
1258
-
1259
- /* ── Layout ── */
1260
- .report-layout { display: flex; min-height: 100vh; }
1261
-
1262
- .sidebar {
1263
- position: sticky; top: 0; height: 100vh; width: 220px; min-width: 220px;
1264
- background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
1265
- border-right: 1px solid #334155; padding: 1.5rem 0;
1266
- display: flex; flex-direction: column; gap: 0.25rem;
1267
- overflow-y: auto; z-index: 100;
1268
- }
1269
- .sidebar-title {
1270
- font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
1271
- letter-spacing: 0.15em; color: #475569; padding: 0 1.25rem; margin-bottom: 0.75rem;
1272
- }
1273
- .sidebar-link {
1274
- display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 1.25rem;
1275
- color: #94a3b8; text-decoration: none; font-size: 0.8rem; font-weight: 500;
1276
- border-left: 3px solid transparent; transition: all 0.2s;
1277
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1278
- }
1279
- .sidebar-link:hover { color: #e2e8f0; background: #1e293b; border-left-color: #475569; }
1280
- .sidebar-link.active { color: #c084fc; background: #c084fc10; border-left-color: #c084fc; font-weight: 700; }
1281
-
1282
- .sidebar-toggle {
1283
- display: none; position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 200;
1284
- width: 48px; height: 48px; border-radius: 50%; border: none;
1285
- background: #c084fc; color: #0f172a; font-size: 1.2rem; cursor: pointer;
1286
- box-shadow: 0 4px 16px rgba(192,132,252,0.4); transition: all 0.2s;
1287
- }
1288
- .sidebar-toggle:hover { transform: scale(1.1); }
1289
-
1290
- @media (max-width: 1024px) {
1291
- .sidebar {
1292
- position: fixed; left: -240px; top: 0; width: 240px; min-width: 240px;
1293
- transition: left 0.3s ease; box-shadow: none;
1294
- }
1295
- .sidebar.sidebar-open { left: 0; box-shadow: 4px 0 24px rgba(0,0,0,0.5); }
1296
- .sidebar-toggle { display: flex; align-items: center; justify-content: center; }
1297
- .report-layout { flex-direction: column; }
1298
- }
1299
-
1300
- .container { max-width: 1200px; margin: 0 auto; padding: 2rem; flex: 1; min-width: 0; }
1301
-
1302
- /* ── Header ── */
1303
- .header {
1304
- text-align: center;
1305
- padding: 3rem 2rem;
1306
- background: linear-gradient(135deg, #1e293b 0%, #0f172a 50%, #1e1b4b 100%);
1307
- border-bottom: 1px solid #334155;
1308
- margin-bottom: 2rem;
1309
- }
1310
- .header h1 {
1311
- font-size: 2.5rem;
1312
- font-weight: 900;
1313
- background: linear-gradient(135deg, #818cf8, #c084fc, #f472b6);
1314
- -webkit-background-clip: text;
1315
- -webkit-text-fill-color: transparent;
1316
- margin-bottom: 0.5rem;
1317
- }
1318
- .header .subtitle { color: #94a3b8; font-size: 1.1rem; font-weight: 300; }
1319
- .header .meta {
1320
- margin-top: 1rem;
1321
- display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap;
1322
- }
1323
- .header .meta span {
1324
- background: #1e293b; padding: 0.4rem 1rem; border-radius: 99px;
1325
- font-size: 0.85rem; color: #94a3b8; border: 1px solid #334155;
1326
- }
1327
- .header .meta span strong { color: #e2e8f0; }
1328
-
1329
- /* ── Score Hero ── */
1330
- .score-hero {
1331
- display: flex; align-items: center; justify-content: center; gap: 3rem;
1332
- padding: 2.5rem;
1333
- background: linear-gradient(135deg, #1e293b, #1e1b4b);
1334
- border-radius: 24px; border: 1px solid #334155;
1335
- margin-bottom: 2rem; flex-wrap: wrap;
1336
- }
1337
- .score-circle { position: relative; width: 180px; height: 180px; }
1338
- .score-circle svg { transform: rotate(-90deg); }
1339
- .score-circle circle { fill: none; stroke-width: 10; stroke-linecap: round; }
1340
- .score-circle .bg { stroke: #334155; }
1341
- .score-circle .fg { transition: stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1); }
1342
- .score-value {
1343
- position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
1344
- text-align: center;
1345
- }
1346
- .score-value .number { font-size: 3rem; font-weight: 900; line-height: 1; }
1347
- .score-value .label { font-size: 0.85rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 2px; }
1348
- .score-value .grade { font-size: 0.75rem; color: #64748b; margin-top: 4px; text-transform: uppercase; letter-spacing: 1px; }
1349
-
1350
- .score-breakdown { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
1351
- .score-item {
1352
- padding: 1rem 1.5rem; background: rgba(255,255,255,0.03);
1353
- border-radius: 12px; border: 1px solid #334155; min-width: 200px;
1354
- }
1355
- .score-item .name { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 1px; color: #94a3b8; margin-bottom: 0.3rem; }
1356
- .score-item .bar-container { background: #1e293b; border-radius: 99px; height: 8px; margin-top: 0.5rem; overflow: hidden; }
1357
- .score-item .bar { height: 100%; border-radius: 99px; transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1); }
1358
- .score-item .val { font-size: 1.5rem; font-weight: 700; }
1359
-
1360
- /* ── Stats Grid ── */
1361
- .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
1362
- .stat-card {
1363
- background: linear-gradient(135deg, #1e293b, #0f172a);
1364
- border: 1px solid #334155; border-radius: 16px; padding: 1.5rem; text-align: center;
1365
- }
1366
- .stat-card .value {
1367
- font-size: 2rem; font-weight: 800;
1368
- background: linear-gradient(135deg, #818cf8, #c084fc);
1369
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
1370
- }
1371
- .stat-card .label { font-size: 0.85rem; color: #94a3b8; margin-top: 0.3rem; }
1372
-
1373
- /* ── Section Title ── */
1374
- .section-title {
1375
- font-size: 1.4rem; font-weight: 700; margin: 2.5rem 0 1rem;
1376
- display: flex; align-items: center; gap: 0.5rem;
1377
- }
1378
-
1379
- /* ── Section Accordion ── */
1380
- .section-accordion {
1381
- margin: 1.5rem 0; border: 1px solid #334155; border-radius: 16px;
1382
- background: transparent; overflow: hidden;
1383
- }
1384
- .section-accordion-header {
1385
- cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.75rem;
1386
- font-size: 1.3rem; font-weight: 700; color: #e2e8f0;
1387
- padding: 1.25rem 1.5rem; background: linear-gradient(135deg, #1e293b, #0f172a);
1388
- border-bottom: 1px solid transparent; transition: all 0.3s; user-select: none;
1389
- }
1390
- .section-accordion-header:hover { background: linear-gradient(135deg, #334155, #1e293b); }
1391
- .section-accordion[open] > .section-accordion-header { border-bottom-color: #334155; }
1392
- .section-accordion-header::after {
1393
- content: '\\25B6'; margin-left: auto; font-size: 0.8rem; color: #818cf8;
1394
- transition: transform 0.3s;
1395
- }
1396
- .section-accordion[open] > .section-accordion-header::after { transform: rotate(90deg); }
1397
- .section-accordion-header::-webkit-details-marker { display: none; }
1398
- .section-accordion-body { padding: 0.5rem 0; }
1399
-
1400
- /* ── Project Overview ── */
1401
- .overview-grid {
1402
- display: grid;
1403
- grid-template-columns: 1fr 1fr;
1404
- gap: 1rem;
1405
- margin-bottom: 1.5rem;
1406
- }
1407
- .overview-card {
1408
- background: rgba(255,255,255,0.03);
1409
- border: 1px solid #334155;
1410
- border-radius: 12px;
1411
- padding: 1.25rem;
1412
- }
1413
- .overview-main {
1414
- grid-column: 1 / -1;
1415
- background: linear-gradient(135deg, rgba(59,130,246,0.08), rgba(139,92,246,0.08));
1416
- border-color: #3b82f6;
1417
- }
1418
- .overview-label {
1419
- font-size: 0.75rem;
1420
- font-weight: 600;
1421
- text-transform: uppercase;
1422
- letter-spacing: 0.05em;
1423
- color: #94a3b8;
1424
- margin-bottom: 0.75rem;
1425
- }
1426
- .overview-description {
1427
- font-size: 1.1rem;
1428
- color: #e2e8f0;
1429
- line-height: 1.6;
1430
- margin-bottom: 0.75rem;
1431
- }
1432
- .overview-purpose-row {
1433
- display: flex;
1434
- align-items: center;
1435
- gap: 0.5rem;
1436
- }
1437
- .overview-purpose-label {
1438
- font-size: 0.8rem;
1439
- color: #64748b;
1440
- }
1441
- .overview-purpose-value {
1442
- font-size: 0.85rem;
1443
- color: #a78bfa;
1444
- font-weight: 600;
1445
- background: rgba(139,92,246,0.1);
1446
- padding: 0.2rem 0.6rem;
1447
- border-radius: 6px;
1448
- }
1449
- .overview-tags {
1450
- display: flex;
1451
- flex-wrap: wrap;
1452
- gap: 0.4rem;
1453
- }
1454
- .overview-tag {
1455
- font-size: 0.75rem;
1456
- padding: 0.25rem 0.6rem;
1457
- border-radius: 6px;
1458
- font-weight: 500;
1459
- }
1460
- .tech-tag {
1461
- background: rgba(59,130,246,0.15);
1462
- color: #60a5fa;
1463
- border: 1px solid rgba(59,130,246,0.3);
1464
- }
1465
- .keyword-tag {
1466
- background: rgba(16,185,129,0.1);
1467
- color: #34d399;
1468
- border: 1px solid rgba(16,185,129,0.2);
1469
- }
1470
- .overview-entry {
1471
- font-size: 0.8rem;
1472
- background: rgba(255,255,255,0.05);
1473
- padding: 0.25rem 0.5rem;
1474
- border-radius: 4px;
1475
- color: #e2e8f0;
1476
- font-family: 'SF Mono', monospace;
1477
- }
1478
- .overview-entries {
1479
- display: flex;
1480
- flex-wrap: wrap;
1481
- gap: 0.4rem;
1482
- }
1483
- .overview-modules-section {
1484
- margin-top: 0.5rem;
1485
- }
1486
- .overview-modules-grid {
1487
- display: grid;
1488
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
1489
- gap: 0.75rem;
1490
- margin-top: 0.5rem;
1491
- }
1492
- .overview-module {
1493
- background: rgba(255,255,255,0.03);
1494
- border: 1px solid #1e293b;
1495
- border-radius: 8px;
1496
- padding: 0.75rem 1rem;
1497
- transition: border-color 0.2s;
1498
- }
1499
- .overview-module:hover {
1500
- border-color: #3b82f6;
1501
- }
1502
- .overview-module-name {
1503
- font-weight: 600;
1504
- color: #e2e8f0;
1505
- font-size: 0.9rem;
1506
- margin-bottom: 0.25rem;
1507
- }
1508
- .overview-module-desc {
1509
- color: #94a3b8;
1510
- font-size: 0.75rem;
1511
- margin-bottom: 0.25rem;
1512
- }
1513
- .overview-module-files {
1514
- color: #64748b;
1515
- font-size: 0.7rem;
1516
- }
1517
- .overview-empty {
1518
- color: #475569;
1519
- font-size: 0.85rem;
1520
- font-style: italic;
1521
- }
1522
- @media (max-width: 768px) {
1523
- .overview-grid { grid-template-columns: 1fr; }
1524
- }
1525
-
1526
- /* ── Operations Accordion (inside refactoring steps) ── */
1527
- .rstep-ops-accordion {
1528
- margin: 0.75rem 0; border: 1px solid #1e293b; border-radius: 10px; overflow: hidden;
1529
- }
1530
- .rstep-ops-toggle {
1531
- cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.5rem;
1532
- font-size: 0.9rem; font-weight: 600; color: #94a3b8;
1533
- padding: 0.75rem 1rem; background: #0f172a; transition: all 0.2s;
1534
- }
1535
- .rstep-ops-toggle:hover { background: #1e293b; color: #e2e8f0; }
1536
- .rstep-ops-toggle::after {
1537
- content: '\\25B6'; margin-left: auto; font-size: 0.65rem; color: #818cf8;
1538
- transition: transform 0.3s;
1539
- }
1540
- .rstep-ops-accordion[open] > .rstep-ops-toggle::after { transform: rotate(90deg); }
1541
- .rstep-ops-toggle::-webkit-details-marker { display: none; }
1542
-
1543
- /* ── Cards ── */
1544
- .card {
1545
- background: #1e293b; border-radius: 16px; border: 1px solid #334155;
1546
- padding: 1.5rem; margin-bottom: 1rem; overflow-x: auto;
1547
- }
1548
- .success-card { border-color: #22c55e40; color: #22c55e; text-align: center; padding: 2rem; font-size: 1.1rem; }
1549
-
1550
- /* ── Graph ── */
1551
- .graph-card { padding: 1rem; }
1552
- .graph-controls { margin-bottom: 0.75rem; }
1553
- .graph-legend {
1554
- display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem;
1555
- justify-content: center;
1556
- }
1557
- .legend-item { display: flex; align-items: center; gap: 4px; font-size: 0.75rem; color: #94a3b8; }
1558
- .legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
1559
- .graph-filters {
1560
- display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;
1561
- justify-content: center; margin-top: 0.5rem;
1562
- }
1563
- .graph-search {
1564
- background: #0f172a; border: 1px solid #334155; border-radius: 8px;
1565
- padding: 0.4rem 0.75rem; color: #e2e8f0; font-size: 0.8rem;
1566
- outline: none; width: 180px; transition: border-color 0.2s;
1567
- }
1568
- .graph-search:focus { border-color: #818cf8; }
1569
- .graph-layer-filters {
1570
- display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;
1571
- }
1572
- .graph-filter-check {
1573
- display: flex; align-items: center; gap: 4px;
1574
- font-size: 0.75rem; color: #94a3b8; cursor: pointer;
1575
- }
1576
- .graph-filter-check input { width: 14px; height: 14px; accent-color: #818cf8; }
1577
- .graph-limit-notice {
1578
- text-align: center; font-size: 0.75rem; color: #f59e0b;
1579
- background: #f59e0b15; padding: 0.3rem 0.75rem; border-radius: 6px;
1580
- margin-top: 0.5rem;
1581
- }
1582
- .graph-hint {
1583
- text-align: center; font-size: 0.75rem; color: #475569; margin-top: 0.5rem;
1584
- font-style: italic;
1585
- }
1586
- #dep-graph svg { background: rgba(0,0,0,0.2); border-radius: 12px; cursor: grab; }
1587
- #dep-graph svg:active { cursor: grabbing; }
1588
-
1589
- /* ── Layers Grid ── */
1590
- .layers-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
1591
- .layer-card {
1592
- background: linear-gradient(135deg, #1e293b, #0f172a);
1593
- border: 1px solid #334155; border-radius: 16px; padding: 1.5rem;
1594
- text-align: center; position: relative; overflow: hidden;
1595
- }
1596
- .layer-card::before {
1597
- content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
1598
- background: var(--layer-color, #64748b);
1599
- }
1600
- .layer-card .count { font-size: 2.5rem; font-weight: 900; line-height: 1; }
1601
- .layer-card .name { font-size: 1rem; color: #94a3b8; margin-top: 0.3rem; font-weight: 600; }
1602
- .layer-card .desc { font-size: 0.75rem; color: #475569; margin-top: 0.5rem; }
1603
-
1604
- /* ── Tables ── */
1605
- table { width: 100%; border-collapse: collapse; }
1606
- th, td { text-align: left; padding: 0.75rem 1rem; border-bottom: 1px solid #334155; }
1607
- th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; color: #64748b; font-weight: 600; }
1608
- .count-cell { font-weight: 700; font-size: 1.1rem; }
1609
- .impact { color: #94a3b8; font-size: 0.85rem; }
1610
- .suggestion { color: #64748b; font-size: 0.8rem; }
1611
-
1612
- .severity-badge {
1613
- display: inline-block; padding: 0.2rem 0.6rem; border-radius: 99px;
1614
- font-size: 0.72rem; font-weight: 600; letter-spacing: 0.5px;
1615
- }
1616
- .severity-CRITICAL { background: #dc262620; color: #ef4444; border: 1px solid #ef444440; }
1617
- .severity-HIGH { background: #f59e0b20; color: #f59e0b; border: 1px solid #f59e0b40; }
1618
- .severity-MEDIUM { background: #3b82f620; color: #60a5fa; border: 1px solid #60a5fa40; }
1619
- .severity-LOW { background: #22c55e20; color: #22c55e; border: 1px solid #22c55e40; }
1620
-
1621
- .count-badge {
1622
- display: inline-block; background: #818cf820; color: #818cf8; padding: 0.1rem 0.4rem;
1623
- border-radius: 99px; font-size: 0.7rem; margin-left: 0.5rem; font-weight: 600;
1624
- }
1625
-
1626
- .locations { font-size: 0.75rem; color: #64748b; }
1627
- .locations code { background: #0f172a; padding: 1px 4px; border-radius: 3px; font-size: 0.7rem; }
1628
-
1629
- /* ── Footer ── */
1630
- .footer {
1631
- text-align: center; padding: 2rem; color: #475569; font-size: 0.85rem;
1632
- border-top: 1px solid #1e293b; margin-top: 3rem;
1633
- }
1634
- .footer a { color: #818cf8; text-decoration: none; }
1635
- .footer a:hover { text-decoration: underline; }
1636
-
1637
- /* ── Refactoring Plan ── */
1638
- .refactor-score { padding: 2rem; }
1639
- .refactor-score-pair {
1640
- display: flex; align-items: center; justify-content: center; gap: 1.5rem;
1641
- margin-bottom: 2rem; flex-wrap: wrap;
1642
- }
1643
- .rscore-box { text-align: center; }
1644
- .rscore-num { font-size: 3rem; font-weight: 900; line-height: 1; }
1645
- .rscore-label { font-size: 0.8rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 1px; }
1646
- .rscore-improvement { font-size: 1.3rem; font-weight: 700; }
1647
-
1648
- .refactor-legend { display: flex; gap: 1rem; margin-bottom: 0.5rem; }
1649
- .rlegend-tag { font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 6px; }
1650
- .rlegend-tag.rbefore { background: rgba(255,255,255,0.05); color: #94a3b8; }
1651
- .rlegend-tag.rafter { background: rgba(129,140,248,0.2); color: #818cf8; }
1652
-
1653
- .refactor-metric-name { width: 100px; font-size: 0.8rem; text-transform: uppercase; color: #94a3b8; font-weight: 600; }
1654
- .refactor-metric-bars { flex: 1; position: relative; height: 30px; }
1655
- .rbar-before, .rbar-after {
1656
- position: absolute; left: 0; height: 14px; border-radius: 4px;
1657
- display: flex; align-items: center; padding-left: 6px;
1658
- font-size: 0.7rem; font-weight: 600;
1659
- }
1660
- .rbar-before { top: 0; }
1661
- .rbar-after { top: 15px; }
1662
- .refactor-metric-diff { width: 50px; text-align: right; font-weight: 700; font-size: 0.85rem; }
1663
-
1664
- .refactor-stats-row {
1665
- display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap;
1666
- }
1667
- .rstat {
1668
- background: #1e293b; border: 1px solid #334155; border-radius: 99px;
1669
- padding: 0.4rem 1rem; font-size: 0.85rem; color: #94a3b8; font-weight: 500;
1670
- }
1671
-
1672
- .priority-bar {
1673
- display: flex; border-radius: 12px; overflow: hidden; height: 32px; margin-bottom: 2rem;
1674
- }
1675
- .prio-seg {
1676
- display: flex; align-items: center; justify-content: center;
1677
- font-size: 0.75rem; font-weight: 600;
1678
- }
1679
- .prio-critical { background: #ef444430; color: #ef4444; }
1680
- .prio-high { background: #f59e0b30; color: #f59e0b; }
1681
- .prio-medium { background: #3b82f630; color: #60a5fa; }
1682
- .prio-low { background: #22c55e30; color: #22c55e; }
1683
-
1684
- .refactor-roadmap { display: flex; flex-direction: column; gap: 1rem; }
1685
- .rstep-card {
1686
- background: #1e293b; border-radius: 16px; border: 1px solid #334155;
1687
- padding: 1.5rem; transition: border-color 0.2s;
1688
- }
1689
- .rstep-card:hover { border-color: #818cf8; }
1690
- .rstep-header { display: flex; gap: 1rem; margin-bottom: 1rem; }
1691
- .rstep-number {
1692
- width: 40px; height: 40px; border-radius: 50%;
1693
- background: linear-gradient(135deg, #818cf8, #c084fc);
1694
- display: flex; align-items: center; justify-content: center;
1695
- font-weight: 800; font-size: 1rem; color: white; flex-shrink: 0;
1696
- }
1697
- .rstep-info { flex: 1; }
1698
- .rstep-title-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
1699
- .rstep-title-row h3 { font-size: 1.1rem; font-weight: 700; }
1700
- .rstep-desc { color: #94a3b8; font-size: 0.9rem; margin-top: 0.3rem; }
1701
- .tier-badge {
1702
- background: #818cf815; color: #818cf8; border: 1px solid #818cf830;
1703
- padding: 0.15rem 0.5rem; border-radius: 99px; font-size: 0.65rem; font-weight: 600;
1704
- }
1705
- .rstep-details { margin-top: 0.5rem; }
1706
- .rstep-details summary { cursor: pointer; color: #818cf8; font-size: 0.85rem; font-weight: 500; }
1707
- .rstep-rationale { color: #64748b; font-size: 0.85rem; margin-top: 0.3rem; font-style: italic; }
1708
-
1709
- .rstep-ops { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #334155; }
1710
- .rstep-ops h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem; }
1711
- .rop { display: flex; align-items: flex-start; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
1712
- .rop-icon { font-size: 0.9rem; }
1713
- .rop-badge { padding: 0.1rem 0.4rem; border-radius: 6px; font-size: 0.65rem; font-weight: 700; }
1714
- .rop-path { background: #0f172a; padding: 1px 6px; border-radius: 4px; font-size: 0.8rem; color: #c084fc; }
1715
- .rop-arrow { color: #818cf8; font-weight: 700; }
1716
- .rop-desc { width: 100%; color: #64748b; font-size: 0.8rem; padding-left: 1.8rem; }
1717
-
1718
- .rstep-impact { margin-top: 0.5rem; }
1719
- .rstep-impact h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.3rem; }
1720
- .rimpact-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; }
1721
- .rimpact-tag {
1722
- background: #22c55e10; color: #22c55e; border: 1px solid #22c55e30;
1723
- padding: 0.2rem 0.6rem; border-radius: 8px; font-size: 0.75rem; font-weight: 500;
1724
- }
1725
-
1726
- /* ── Responsive ── */
1727
- @media (max-width: 768px) {
1728
- .score-hero { flex-direction: column; gap: 1.5rem; }
1729
- .score-breakdown { grid-template-columns: 1fr; }
1730
- .header h1 { font-size: 1.8rem; }
1731
- .container { padding: 1rem; }
1732
- .refactor-score-pair { flex-direction: column; }
1733
- }
1734
-
1735
- /* ── Print ── */
1736
- @media print {
1737
- body { background: white; color: #1e293b; }
1738
- .header { background: white; border-bottom: 2px solid #e2e8f0; }
1739
- .header h1 { -webkit-text-fill-color: #4f46e5; }
1740
- .card, .stat-card, .score-hero, .layer-card, .score-item {
1741
- background: white; border-color: #e2e8f0;
1742
- }
1743
- }
1744
- </style>`;
1745
- }
1746
- }
1747
- //# sourceMappingURL=html-reporter.js.map