aiwf 0.3.6 → 0.3.8

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 (271) hide show
  1. package/.aiwf/00_PROJECT_MANIFEST.md +226 -2
  2. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/S03_M02_sprint_meta.md +112 -0
  3. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T01_S03_new_test_task.md +39 -0
  4. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T02_S03_first_task.md +39 -0
  5. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T03_S03_test_task.md +39 -0
  6. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T04_S03_task_with_special_characters.md +39 -0
  7. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T05_S03_test.md +39 -0
  8. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T06_S03_test_task.md +39 -0
  9. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T07_S03_next_task.md +39 -0
  10. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T08_S03_next_task.md +39 -0
  11. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T09_S03_new_test_task.md +39 -0
  12. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T100_S03_task_with_special_characters.md +39 -0
  13. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T101_S03_test.md +39 -0
  14. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T102_S03_test_task.md +39 -0
  15. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T103_S03_next_task.md +39 -0
  16. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T104_S03_next_task.md +39 -0
  17. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T105_S03_new_test_task.md +39 -0
  18. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T106_S03_first_task.md +39 -0
  19. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T107_S03_test_task.md +39 -0
  20. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T108_S03_task_with_special_characters.md +39 -0
  21. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T109_S03_test.md +39 -0
  22. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T10_S03_first_task.md +39 -0
  23. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T110_S03_test_task.md +39 -0
  24. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T111_S03_next_task.md +39 -0
  25. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T112_S03_next_task.md +39 -0
  26. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T11_S03_test_task.md +39 -0
  27. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T12_S03_task_with_special_characters.md +39 -0
  28. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T13_S03_test.md +39 -0
  29. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T14_S03_test_task.md +39 -0
  30. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T15_S03_next_task.md +39 -0
  31. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T16_S03_next_task.md +39 -0
  32. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T17_S03_new_test_task.md +39 -0
  33. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T18_S03_first_task.md +39 -0
  34. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T19_S03_test_task.md +39 -0
  35. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T20_S03_task_with_special_characters.md +39 -0
  36. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T21_S03_test.md +39 -0
  37. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T22_S03_test_task.md +39 -0
  38. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T23_S03_next_task.md +39 -0
  39. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T24_S03_next_task.md +39 -0
  40. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T25_S03_new_test_task.md +39 -0
  41. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T26_S03_first_task.md +39 -0
  42. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T27_S03_test_task.md +39 -0
  43. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T28_S03_task_with_special_characters.md +39 -0
  44. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T29_S03_test.md +39 -0
  45. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T30_S03_test_task.md +39 -0
  46. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T31_S03_next_task.md +39 -0
  47. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T32_S03_next_task.md +39 -0
  48. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T33_S03_new_test_task.md +39 -0
  49. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T34_S03_first_task.md +39 -0
  50. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T35_S03_test_task.md +39 -0
  51. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T36_S03_task_with_special_characters.md +39 -0
  52. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T37_S03_test.md +39 -0
  53. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T38_S03_test_task.md +39 -0
  54. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T39_S03_next_task.md +39 -0
  55. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T40_S03_next_task.md +39 -0
  56. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T41_S03_new_test_task.md +39 -0
  57. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T42_S03_first_task.md +39 -0
  58. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T43_S03_test_task.md +39 -0
  59. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T44_S03_task_with_special_characters.md +39 -0
  60. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T45_S03_test.md +39 -0
  61. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T46_S03_test_task.md +39 -0
  62. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T47_S03_next_task.md +39 -0
  63. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T48_S03_next_task.md +39 -0
  64. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T49_S03_new_test_task.md +39 -0
  65. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T50_S03_first_task.md +39 -0
  66. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T51_S03_test_task.md +39 -0
  67. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T52_S03_task_with_special_characters.md +39 -0
  68. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T53_S03_test.md +39 -0
  69. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T54_S03_test_task.md +39 -0
  70. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T55_S03_next_task.md +39 -0
  71. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T56_S03_next_task.md +39 -0
  72. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T57_S03_new_test_task.md +39 -0
  73. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T58_S03_first_task.md +39 -0
  74. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T59_S03_test_task.md +39 -0
  75. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T60_S03_task_with_special_characters.md +39 -0
  76. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T61_S03_test.md +39 -0
  77. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T62_S03_test_task.md +39 -0
  78. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T63_S03_next_task.md +39 -0
  79. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T64_S03_next_task.md +39 -0
  80. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T65_S03_new_test_task.md +39 -0
  81. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T66_S03_first_task.md +39 -0
  82. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T67_S03_test_task.md +39 -0
  83. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T68_S03_task_with_special_characters.md +39 -0
  84. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T69_S03_test.md +39 -0
  85. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T70_S03_test_task.md +39 -0
  86. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T71_S03_next_task.md +39 -0
  87. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T72_S03_next_task.md +39 -0
  88. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T73_S03_new_test_task.md +39 -0
  89. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T74_S03_first_task.md +39 -0
  90. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T75_S03_test_task.md +39 -0
  91. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T76_S03_task_with_special_characters.md +39 -0
  92. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T77_S03_test.md +39 -0
  93. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T78_S03_test_task.md +39 -0
  94. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T79_S03_next_task.md +39 -0
  95. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T80_S03_next_task.md +39 -0
  96. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T81_S03_new_test_task.md +39 -0
  97. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T82_S03_first_task.md +39 -0
  98. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T83_S03_test_task.md +39 -0
  99. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T84_S03_task_with_special_characters.md +39 -0
  100. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T85_S03_test.md +39 -0
  101. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T86_S03_test_task.md +39 -0
  102. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T87_S03_next_task.md +39 -0
  103. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T88_S03_next_task.md +39 -0
  104. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T89_S03_new_test_task.md +39 -0
  105. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T90_S03_first_task.md +39 -0
  106. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T91_S03_test_task.md +39 -0
  107. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T92_S03_task_with_special_characters.md +39 -0
  108. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T93_S03_test.md +39 -0
  109. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T94_S03_test_task.md +39 -0
  110. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T95_S03_next_task.md +39 -0
  111. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T96_S03_next_task.md +39 -0
  112. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T97_S03_new_test_task.md +39 -0
  113. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T98_S03_first_task.md +39 -0
  114. package/.aiwf/03_SPRINTS/S03_M02_ecosystem_integration/T99_S03_test_task.md +39 -0
  115. package/.aiwf/test-results-s02.json +5 -5
  116. package/.claude/commands/aiwf/aiwf_compress_aggressive_persona.md +3 -15
  117. package/.claude/commands/aiwf/aiwf_compress_minimal_persona.md +2 -15
  118. package/.claude/commands/aiwf/aiwf_compress_stats.md +2 -51
  119. package/.claude/commands/aiwf/aiwf_compress_with_persona.md +5 -18
  120. package/.claude/commands/aiwf/aiwf_create_feature_ledger.md +1 -1
  121. package/.claude/commands/aiwf/aiwf_default_mode.md +1 -1
  122. package/.claude/commands/aiwf/aiwf_evaluate.md +3 -16
  123. package/.claude/commands/aiwf/aiwf_get_feature_details.md +1 -1
  124. package/.claude/commands/aiwf/aiwf_link_feature_to_milestone.md +1 -1
  125. package/.claude/commands/aiwf/aiwf_list_features.md +1 -1
  126. package/.claude/commands/aiwf/aiwf_switch_language.md +48 -120
  127. package/.claude/commands/aiwf/aiwf_update_feature_status.md +1 -1
  128. package/claude-code/aiwf/en/.claude/commands/aiwf/aiwf_create_testcase.md +561 -0
  129. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_compress_aggressive_persona.md +2 -2
  130. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_compress_minimal_persona.md +2 -2
  131. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_compress_stats.md +10 -47
  132. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_compress_with_persona.md +2 -2
  133. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_feature_ledger.md +1 -1
  134. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_create_testcase.md +561 -0
  135. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_default_mode.md +1 -1
  136. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_evaluate.md +2 -2
  137. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_get_feature_details.md +1 -1
  138. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_link_feature_to_milestone.md +1 -1
  139. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_list_features.md +1 -1
  140. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_persona_architect.md +1 -1
  141. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_persona_backend.md +1 -1
  142. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_persona_data_analyst.md +1 -1
  143. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_persona_frontend.md +1 -1
  144. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_persona_security.md +1 -1
  145. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_persona_status.md +1 -1
  146. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_update_feature_status.md +1 -1
  147. package/package.json +1 -1
  148. package/src/cli/index.js +174 -0
  149. package/src/commands/compress.js +60 -0
  150. package/src/commands/create-project.js +23 -17
  151. package/src/commands/evaluate.js +459 -0
  152. package/src/commands/feature.js +365 -0
  153. package/src/commands/persona.js +37 -16
  154. package/src/commands/token.js +312 -0
  155. package/src/lib/file-downloader.js +189 -33
  156. package/src/lib/installer.js +38 -11
  157. package/src/lib/resource-loader.js +250 -0
  158. package/src/lib/resources/commands/ai-persona.js +602 -0
  159. package/src/lib/resources/commands/compress-context.js +389 -0
  160. package/src/lib/resources/commands/evaluate.js +246 -0
  161. package/src/lib/resources/commands/feature-ledger.js +565 -0
  162. package/src/lib/resources/commands/feature_commit_report.js +370 -0
  163. package/src/lib/resources/commands/persona-context-apply.js +562 -0
  164. package/src/lib/resources/commands/scan_git_history.js +234 -0
  165. package/src/lib/resources/commands/sync_feature_commits.js +154 -0
  166. package/src/lib/resources/commands/token-tracking.js +443 -0
  167. package/src/lib/resources/config/commit-patterns.js +103 -0
  168. package/src/lib/resources/config/language.json +6 -0
  169. package/src/lib/resources/personas/PERSONA_INDEX.md +57 -0
  170. package/src/lib/resources/personas/analyst.json +44 -0
  171. package/src/lib/resources/personas/architect/best_practices.md +136 -0
  172. package/src/lib/resources/personas/architect/knowledge_base.md +377 -0
  173. package/src/lib/resources/personas/architect.json +44 -0
  174. package/src/lib/resources/personas/backend/best_practices.md +766 -0
  175. package/src/lib/resources/personas/backend/knowledge_base.md +1070 -0
  176. package/src/lib/resources/personas/data_analyst/best_practices.md +594 -0
  177. package/src/lib/resources/personas/data_analyst/knowledge_base.md +1057 -0
  178. package/src/lib/resources/personas/developer.json +44 -0
  179. package/src/lib/resources/personas/developer.md +37 -0
  180. package/src/lib/resources/personas/evaluation_criteria.json +137 -0
  181. package/src/lib/resources/personas/frontend/best_practices.md +445 -0
  182. package/src/lib/resources/personas/frontend/knowledge_base.md +729 -0
  183. package/src/lib/resources/personas/persona-index.json +31 -0
  184. package/src/lib/resources/personas/reviewer.json +44 -0
  185. package/src/lib/resources/personas/security/best_practices.md +307 -0
  186. package/src/lib/resources/personas/security/knowledge_base.md +498 -0
  187. package/src/lib/resources/personas/tester.json +44 -0
  188. package/src/lib/resources/templates/README.md +78 -0
  189. package/src/lib/resources/templates/api-server/config.json +64 -0
  190. package/src/lib/resources/templates/api-server/template/.aiwf/config.json +56 -0
  191. package/src/lib/resources/templates/api-server/template/.aiwf/feature-ledger.json +42 -0
  192. package/src/lib/resources/templates/api-server/template/.aiwf/personas/backend-engineer.json +40 -0
  193. package/src/lib/resources/templates/api-server/template/.aiwf/scripts/cli.js +111 -0
  194. package/src/lib/resources/templates/api-server/template/.env.example +29 -0
  195. package/src/lib/resources/templates/api-server/template/.eslintrc.json +23 -0
  196. package/src/lib/resources/templates/api-server/template/README.md +171 -0
  197. package/src/lib/resources/templates/api-server/template/jest.config.js +26 -0
  198. package/src/lib/resources/templates/api-server/template/nodemon.json +9 -0
  199. package/src/lib/resources/templates/api-server/template/package.json +61 -0
  200. package/src/lib/resources/templates/api-server/template/src/app.ts +60 -0
  201. package/src/lib/resources/templates/api-server/template/src/config/swagger.ts +38 -0
  202. package/src/lib/resources/templates/api-server/template/src/controllers/aiwfController.ts +68 -0
  203. package/src/lib/resources/templates/api-server/template/src/controllers/authController.ts +128 -0
  204. package/src/lib/resources/templates/api-server/template/src/controllers/statusController.ts +28 -0
  205. package/src/lib/resources/templates/api-server/template/src/index.ts +28 -0
  206. package/src/lib/resources/templates/api-server/template/src/middleware/aiwfMiddleware.ts +50 -0
  207. package/src/lib/resources/templates/api-server/template/src/middleware/authMiddleware.ts +41 -0
  208. package/src/lib/resources/templates/api-server/template/src/middleware/errorHandler.ts +42 -0
  209. package/src/lib/resources/templates/api-server/template/src/middleware/notFoundHandler.ts +14 -0
  210. package/src/lib/resources/templates/api-server/template/src/middleware/requestLogger.ts +20 -0
  211. package/src/lib/resources/templates/api-server/template/src/routes/aiwf.ts +42 -0
  212. package/src/lib/resources/templates/api-server/template/src/routes/index.ts +13 -0
  213. package/src/lib/resources/templates/api-server/template/src/routes/v1/index.ts +67 -0
  214. package/src/lib/resources/templates/api-server/template/src/utils/logger.ts +37 -0
  215. package/src/lib/resources/templates/api-server/template/tests/app.test.ts +40 -0
  216. package/src/lib/resources/templates/api-server/template/tsconfig.json +43 -0
  217. package/src/lib/resources/templates/npm-library/config.json +61 -0
  218. package/src/lib/resources/templates/npm-library/template/LICENSE +21 -0
  219. package/src/lib/resources/templates/npm-library/template/README.md +201 -0
  220. package/src/lib/resources/templates/npm-library/template/package.json +78 -0
  221. package/src/lib/resources/templates/web-app/config.json +53 -0
  222. package/src/lib/resources/templates/web-app/template/.aiwf/config.json +54 -0
  223. package/src/lib/resources/templates/web-app/template/.aiwf/feature-ledger.json +37 -0
  224. package/src/lib/resources/templates/web-app/template/.aiwf/personas/fullstack-developer.json +40 -0
  225. package/src/lib/resources/templates/web-app/template/.aiwf/scripts/cli.js +110 -0
  226. package/src/lib/resources/templates/web-app/template/.eslintrc.cjs +20 -0
  227. package/src/lib/resources/templates/web-app/template/README.md +151 -0
  228. package/src/lib/resources/templates/web-app/template/index.html +14 -0
  229. package/src/lib/resources/templates/web-app/template/package.json +44 -0
  230. package/src/lib/resources/templates/web-app/template/postcss.config.js +6 -0
  231. package/src/lib/resources/templates/web-app/template/public/vite.svg +1 -0
  232. package/src/lib/resources/templates/web-app/template/src/App.tsx +21 -0
  233. package/src/lib/resources/templates/web-app/template/src/components/Layout.tsx +57 -0
  234. package/src/lib/resources/templates/web-app/template/src/components/aiwf/ContextStatus.tsx +107 -0
  235. package/src/lib/resources/templates/web-app/template/src/components/aiwf/FeatureLedger.tsx +93 -0
  236. package/src/lib/resources/templates/web-app/template/src/components/aiwf/TokenUsage.tsx +71 -0
  237. package/src/lib/resources/templates/web-app/template/src/index.css +60 -0
  238. package/src/lib/resources/templates/web-app/template/src/main.tsx +10 -0
  239. package/src/lib/resources/templates/web-app/template/src/pages/AiwfDashboard.tsx +60 -0
  240. package/src/lib/resources/templates/web-app/template/src/pages/HomePage.tsx +89 -0
  241. package/src/lib/resources/templates/web-app/template/src/pages/NotFound.tsx +25 -0
  242. package/src/lib/resources/templates/web-app/template/src/stores/aiwfStore.ts +126 -0
  243. package/src/lib/resources/templates/web-app/template/src/types/global.d.ts +9 -0
  244. package/src/lib/resources/templates/web-app/template/src/vite-env.d.ts +1 -0
  245. package/src/lib/resources/templates/web-app/template/tailwind.config.js +30 -0
  246. package/src/lib/resources/templates/web-app/template/tsconfig.json +37 -0
  247. package/src/lib/resources/templates/web-app/template/tsconfig.node.json +10 -0
  248. package/src/lib/resources/templates/web-app/template/vite.config.ts +23 -0
  249. package/src/lib/resources/utils/background-monitor.js +223 -0
  250. package/src/lib/resources/utils/compression-metrics.js +1031 -0
  251. package/src/lib/resources/utils/compression-strategies.js +557 -0
  252. package/src/lib/resources/utils/content-normalizer.js +455 -0
  253. package/src/lib/resources/utils/context-compressor.js +583 -0
  254. package/src/lib/resources/utils/context-rule-parser.js +180 -0
  255. package/src/lib/resources/utils/context-token-monitor.js +463 -0
  256. package/src/lib/resources/utils/context-update-manager.js +502 -0
  257. package/src/lib/resources/utils/feature-updater.js +271 -0
  258. package/src/lib/resources/utils/git-utils.js +220 -0
  259. package/src/lib/resources/utils/importance-classifier.js +645 -0
  260. package/src/lib/resources/utils/information-filter.js +710 -0
  261. package/src/lib/resources/utils/persona-aware-compressor.js +598 -0
  262. package/src/lib/resources/utils/prompt-injector.js +242 -0
  263. package/src/lib/resources/utils/simplified-evaluator.js +119 -0
  264. package/src/lib/resources/utils/text-summarizer.js +492 -0
  265. package/src/lib/resources/utils/token-counter.js +177 -0
  266. package/src/lib/resources/utils/token-monitor.js +484 -0
  267. package/src/lib/resources/utils/token-reporter.js +453 -0
  268. package/src/lib/resources/utils/token-storage.js +433 -0
  269. package/src/lib/resources/utils/token-tracker.js +283 -0
  270. package/claude-code/aiwf/ko/.claude/commands/aiwf/aiwf_switch_language.md +0 -160
  271. /package/claude-code/aiwf/ko/.claude/commands/aiwf/{aiwf_update_docs_kr.md → aiwf_update_docs.md} +0 -0
@@ -0,0 +1,729 @@
1
+ # Frontend 페르소나 Knowledge Base
2
+
3
+ ## 모던 프레임워크 가이드
4
+
5
+ ### React 18+ 최신 기능
6
+
7
+ #### Concurrent Features
8
+ ```jsx
9
+ // Suspense for Data Fetching
10
+ const ProfilePage = () => {
11
+ return (
12
+ <Suspense fallback={<ProfileSkeleton />}>
13
+ <ProfileDetails />
14
+ <Suspense fallback={<PostsSkeleton />}>
15
+ <ProfilePosts />
16
+ </Suspense>
17
+ </Suspense>
18
+ );
19
+ };
20
+
21
+ // useTransition for Non-urgent Updates
22
+ const SearchResults = () => {
23
+ const [query, setQuery] = useState('');
24
+ const [results, setResults] = useState([]);
25
+ const [isPending, startTransition] = useTransition();
26
+
27
+ const handleSearch = (e) => {
28
+ setQuery(e.target.value);
29
+
30
+ startTransition(() => {
31
+ // 검색 결과 업데이트는 낮은 우선순위
32
+ setResults(searchDatabase(e.target.value));
33
+ });
34
+ };
35
+
36
+ return (
37
+ <>
38
+ <input value={query} onChange={handleSearch} />
39
+ {isPending && <Spinner />}
40
+ <SearchResultsList results={results} />
41
+ </>
42
+ );
43
+ };
44
+
45
+ // useDeferredValue for Debouncing
46
+ const DebouncedResults = ({ query }) => {
47
+ const deferredQuery = useDeferredValue(query);
48
+
49
+ const results = useMemo(
50
+ () => expensiveSearch(deferredQuery),
51
+ [deferredQuery]
52
+ );
53
+
54
+ return <ResultsList results={results} />;
55
+ };
56
+ ```
57
+
58
+ #### Server Components
59
+ ```jsx
60
+ // app/page.server.jsx (Server Component)
61
+ async function BlogPost({ id }) {
62
+ // 서버에서 직접 데이터 페칭
63
+ const post = await db.posts.findUnique({ where: { id } });
64
+
65
+ return (
66
+ <article>
67
+ <h1>{post.title}</h1>
68
+ <div>{post.content}</div>
69
+ <ClientInteractions postId={id} />
70
+ </article>
71
+ );
72
+ }
73
+
74
+ // app/ClientInteractions.client.jsx (Client Component)
75
+ 'use client';
76
+
77
+ function ClientInteractions({ postId }) {
78
+ const [likes, setLikes] = useState(0);
79
+
80
+ return (
81
+ <button onClick={() => setLikes(l => l + 1)}>
82
+ 좋아요 {likes}
83
+ </button>
84
+ );
85
+ }
86
+ ```
87
+
88
+ ### Vue 3 Composition API
89
+
90
+ #### Composables 패턴
91
+ ```javascript
92
+ // useCounter.js
93
+ import { ref, computed } from 'vue';
94
+
95
+ export function useCounter(initialValue = 0) {
96
+ const count = ref(initialValue);
97
+ const double = computed(() => count.value * 2);
98
+
99
+ function increment() {
100
+ count.value++;
101
+ }
102
+
103
+ function decrement() {
104
+ count.value--;
105
+ }
106
+
107
+ return {
108
+ count,
109
+ double,
110
+ increment,
111
+ decrement
112
+ };
113
+ }
114
+
115
+ // Component.vue
116
+ <template>
117
+ <div>
118
+ <p>Count: {{ count }}</p>
119
+ <p>Double: {{ double }}</p>
120
+ <button @click="increment">+</button>
121
+ <button @click="decrement">-</button>
122
+ </div>
123
+ </template>
124
+
125
+ <script setup>
126
+ import { useCounter } from './useCounter';
127
+
128
+ const { count, double, increment, decrement } = useCounter(10);
129
+ </script>
130
+ ```
131
+
132
+ #### Reactive State Management
133
+ ```javascript
134
+ // store/userStore.js
135
+ import { reactive, readonly } from 'vue';
136
+
137
+ const state = reactive({
138
+ user: null,
139
+ isLoading: false,
140
+ error: null
141
+ });
142
+
143
+ const actions = {
144
+ async fetchUser(userId) {
145
+ state.isLoading = true;
146
+ state.error = null;
147
+
148
+ try {
149
+ const response = await api.getUser(userId);
150
+ state.user = response.data;
151
+ } catch (error) {
152
+ state.error = error.message;
153
+ } finally {
154
+ state.isLoading = false;
155
+ }
156
+ },
157
+
158
+ logout() {
159
+ state.user = null;
160
+ }
161
+ };
162
+
163
+ export function useUserStore() {
164
+ return {
165
+ state: readonly(state),
166
+ ...actions
167
+ };
168
+ }
169
+ ```
170
+
171
+ ## 상태 관리 패턴
172
+
173
+ ### Redux Toolkit
174
+ ```javascript
175
+ // features/cart/cartSlice.js
176
+ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
177
+
178
+ // Async thunk
179
+ export const fetchCart = createAsyncThunk(
180
+ 'cart/fetch',
181
+ async (userId) => {
182
+ const response = await api.getCart(userId);
183
+ return response.data;
184
+ }
185
+ );
186
+
187
+ const cartSlice = createSlice({
188
+ name: 'cart',
189
+ initialState: {
190
+ items: [],
191
+ status: 'idle',
192
+ error: null
193
+ },
194
+ reducers: {
195
+ addItem: (state, action) => {
196
+ const existingItem = state.items.find(
197
+ item => item.id === action.payload.id
198
+ );
199
+
200
+ if (existingItem) {
201
+ existingItem.quantity += 1;
202
+ } else {
203
+ state.items.push({ ...action.payload, quantity: 1 });
204
+ }
205
+ },
206
+ removeItem: (state, action) => {
207
+ state.items = state.items.filter(
208
+ item => item.id !== action.payload
209
+ );
210
+ },
211
+ updateQuantity: (state, action) => {
212
+ const item = state.items.find(
213
+ item => item.id === action.payload.id
214
+ );
215
+ if (item) {
216
+ item.quantity = action.payload.quantity;
217
+ }
218
+ }
219
+ },
220
+ extraReducers: (builder) => {
221
+ builder
222
+ .addCase(fetchCart.pending, (state) => {
223
+ state.status = 'loading';
224
+ })
225
+ .addCase(fetchCart.fulfilled, (state, action) => {
226
+ state.status = 'succeeded';
227
+ state.items = action.payload;
228
+ })
229
+ .addCase(fetchCart.rejected, (state, action) => {
230
+ state.status = 'failed';
231
+ state.error = action.error.message;
232
+ });
233
+ }
234
+ });
235
+
236
+ export const { addItem, removeItem, updateQuantity } = cartSlice.actions;
237
+ export default cartSlice.reducer;
238
+ ```
239
+
240
+ ### Zustand
241
+ ```javascript
242
+ // stores/useAppStore.js
243
+ import { create } from 'zustand';
244
+ import { devtools, persist } from 'zustand/middleware';
245
+
246
+ const useAppStore = create(
247
+ devtools(
248
+ persist(
249
+ (set, get) => ({
250
+ // State
251
+ user: null,
252
+ theme: 'light',
253
+ notifications: [],
254
+
255
+ // Actions
256
+ setUser: (user) => set({ user }),
257
+
258
+ toggleTheme: () => set((state) => ({
259
+ theme: state.theme === 'light' ? 'dark' : 'light'
260
+ })),
261
+
262
+ addNotification: (notification) => set((state) => ({
263
+ notifications: [...state.notifications, {
264
+ id: Date.now(),
265
+ ...notification
266
+ }]
267
+ })),
268
+
269
+ removeNotification: (id) => set((state) => ({
270
+ notifications: state.notifications.filter(n => n.id !== id)
271
+ })),
272
+
273
+ // Computed/Derived state
274
+ get hasUnreadNotifications() {
275
+ return get().notifications.some(n => !n.read);
276
+ }
277
+ }),
278
+ {
279
+ name: 'app-storage',
280
+ partialize: (state) => ({ user: state.user, theme: state.theme })
281
+ }
282
+ )
283
+ )
284
+ );
285
+ ```
286
+
287
+ ## CSS 아키텍처
288
+
289
+ ### CSS Custom Properties (CSS Variables)
290
+ ```css
291
+ /* Design Tokens */
292
+ :root {
293
+ /* Colors */
294
+ --color-primary-50: #eff6ff;
295
+ --color-primary-100: #dbeafe;
296
+ --color-primary-500: #3b82f6;
297
+ --color-primary-900: #1e3a8a;
298
+
299
+ /* Spacing */
300
+ --space-xs: 0.25rem;
301
+ --space-sm: 0.5rem;
302
+ --space-md: 1rem;
303
+ --space-lg: 1.5rem;
304
+ --space-xl: 2rem;
305
+
306
+ /* Typography */
307
+ --font-sans: system-ui, -apple-system, sans-serif;
308
+ --font-mono: 'Fira Code', monospace;
309
+
310
+ /* Shadows */
311
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
312
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
313
+
314
+ /* Animations */
315
+ --transition-fast: 150ms ease;
316
+ --transition-base: 250ms ease;
317
+ }
318
+
319
+ /* Dark Mode */
320
+ [data-theme="dark"] {
321
+ --color-primary-50: #1e3a8a;
322
+ --color-primary-500: #60a5fa;
323
+ }
324
+
325
+ /* Component using tokens */
326
+ .button {
327
+ background: var(--color-primary-500);
328
+ padding: var(--space-sm) var(--space-md);
329
+ box-shadow: var(--shadow-sm);
330
+ transition: all var(--transition-base);
331
+ }
332
+ ```
333
+
334
+ ### CSS Grid Layout Systems
335
+ ```css
336
+ /* Responsive Grid System */
337
+ .grid {
338
+ display: grid;
339
+ gap: var(--space-md);
340
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
341
+ }
342
+
343
+ /* Advanced Grid Layout */
344
+ .dashboard {
345
+ display: grid;
346
+ grid-template-areas:
347
+ "header header header"
348
+ "sidebar main aside"
349
+ "footer footer footer";
350
+ grid-template-columns: 200px 1fr 300px;
351
+ grid-template-rows: auto 1fr auto;
352
+ min-height: 100vh;
353
+ }
354
+
355
+ .header { grid-area: header; }
356
+ .sidebar { grid-area: sidebar; }
357
+ .main { grid-area: main; }
358
+ .aside { grid-area: aside; }
359
+ .footer { grid-area: footer; }
360
+
361
+ /* Responsive adjustment */
362
+ @media (max-width: 768px) {
363
+ .dashboard {
364
+ grid-template-areas:
365
+ "header"
366
+ "main"
367
+ "sidebar"
368
+ "aside"
369
+ "footer";
370
+ grid-template-columns: 1fr;
371
+ }
372
+ }
373
+ ```
374
+
375
+ ## 성능 최적화 기법
376
+
377
+ ### 이미지 최적화
378
+ ```jsx
379
+ // Next.js Image Component
380
+ import Image from 'next/image';
381
+
382
+ const OptimizedImage = () => (
383
+ <Image
384
+ src="/hero.jpg"
385
+ alt="Hero image"
386
+ width={1200}
387
+ height={600}
388
+ priority // LCP 이미지에 사용
389
+ placeholder="blur"
390
+ blurDataURL={shimmer}
391
+ sizes="(max-width: 768px) 100vw,
392
+ (max-width: 1200px) 50vw,
393
+ 33vw"
394
+ />
395
+ );
396
+
397
+ // Progressive Image Loading
398
+ const ProgressiveImage = ({ src, placeholder, alt }) => {
399
+ const [imgSrc, setImgSrc] = useState(placeholder);
400
+ const [isLoaded, setIsLoaded] = useState(false);
401
+
402
+ useEffect(() => {
403
+ const img = new Image();
404
+ img.src = src;
405
+ img.onload = () => {
406
+ setImgSrc(src);
407
+ setIsLoaded(true);
408
+ };
409
+ }, [src]);
410
+
411
+ return (
412
+ <div className={`image-wrapper ${isLoaded ? 'loaded' : ''}`}>
413
+ <img src={imgSrc} alt={alt} />
414
+ </div>
415
+ );
416
+ };
417
+ ```
418
+
419
+ ### 웹 워커 활용
420
+ ```javascript
421
+ // worker.js
422
+ self.addEventListener('message', (e) => {
423
+ const { type, data } = e.data;
424
+
425
+ switch(type) {
426
+ case 'PROCESS_DATA':
427
+ const result = expensiveDataProcessing(data);
428
+ self.postMessage({ type: 'DATA_PROCESSED', result });
429
+ break;
430
+
431
+ case 'GENERATE_REPORT':
432
+ const report = generateComplexReport(data);
433
+ self.postMessage({ type: 'REPORT_READY', report });
434
+ break;
435
+ }
436
+ });
437
+
438
+ // Main thread
439
+ const worker = new Worker('/worker.js');
440
+
441
+ worker.postMessage({
442
+ type: 'PROCESS_DATA',
443
+ data: largeDataset
444
+ });
445
+
446
+ worker.addEventListener('message', (e) => {
447
+ if (e.data.type === 'DATA_PROCESSED') {
448
+ updateUI(e.data.result);
449
+ }
450
+ });
451
+ ```
452
+
453
+ ## 접근성 구현
454
+
455
+ ### ARIA 패턴
456
+ ```jsx
457
+ // Accessible Modal
458
+ const Modal = ({ isOpen, onClose, title, children }) => {
459
+ const modalRef = useRef();
460
+ const previousActiveElement = useRef();
461
+
462
+ useEffect(() => {
463
+ if (isOpen) {
464
+ previousActiveElement.current = document.activeElement;
465
+ modalRef.current?.focus();
466
+ } else {
467
+ previousActiveElement.current?.focus();
468
+ }
469
+ }, [isOpen]);
470
+
471
+ useEffect(() => {
472
+ const handleEscape = (e) => {
473
+ if (e.key === 'Escape') onClose();
474
+ };
475
+
476
+ if (isOpen) {
477
+ document.addEventListener('keydown', handleEscape);
478
+ document.body.style.overflow = 'hidden';
479
+ }
480
+
481
+ return () => {
482
+ document.removeEventListener('keydown', handleEscape);
483
+ document.body.style.overflow = '';
484
+ };
485
+ }, [isOpen, onClose]);
486
+
487
+ if (!isOpen) return null;
488
+
489
+ return createPortal(
490
+ <div
491
+ className="modal-overlay"
492
+ onClick={onClose}
493
+ aria-hidden="true"
494
+ >
495
+ <div
496
+ ref={modalRef}
497
+ className="modal"
498
+ role="dialog"
499
+ aria-modal="true"
500
+ aria-labelledby="modal-title"
501
+ onClick={e => e.stopPropagation()}
502
+ tabIndex={-1}
503
+ >
504
+ <h2 id="modal-title">{title}</h2>
505
+ <button
506
+ className="modal-close"
507
+ onClick={onClose}
508
+ aria-label="닫기"
509
+ >
510
+ ×
511
+ </button>
512
+ {children}
513
+ </div>
514
+ </div>,
515
+ document.body
516
+ );
517
+ };
518
+ ```
519
+
520
+ ### 폼 접근성
521
+ ```jsx
522
+ // Accessible Form with Validation
523
+ const AccessibleForm = () => {
524
+ const [errors, setErrors] = useState({});
525
+ const [touched, setTouched] = useState({});
526
+
527
+ return (
528
+ <form noValidate onSubmit={handleSubmit}>
529
+ <div className="form-group">
530
+ <label htmlFor="email">
531
+ 이메일 <span aria-label="필수">*</span>
532
+ </label>
533
+ <input
534
+ id="email"
535
+ type="email"
536
+ aria-required="true"
537
+ aria-invalid={!!errors.email}
538
+ aria-describedby={errors.email ? "email-error" : undefined}
539
+ onBlur={() => handleBlur('email')}
540
+ />
541
+ {errors.email && (
542
+ <span id="email-error" role="alert" className="error">
543
+ {errors.email}
544
+ </span>
545
+ )}
546
+ </div>
547
+
548
+ <fieldset>
549
+ <legend>알림 설정</legend>
550
+ <div className="radio-group" role="radiogroup">
551
+ <input
552
+ type="radio"
553
+ id="notify-all"
554
+ name="notifications"
555
+ value="all"
556
+ />
557
+ <label htmlFor="notify-all">모든 알림 받기</label>
558
+
559
+ <input
560
+ type="radio"
561
+ id="notify-important"
562
+ name="notifications"
563
+ value="important"
564
+ />
565
+ <label htmlFor="notify-important">중요 알림만</label>
566
+
567
+ <input
568
+ type="radio"
569
+ id="notify-none"
570
+ name="notifications"
571
+ value="none"
572
+ />
573
+ <label htmlFor="notify-none">알림 받지 않기</label>
574
+ </div>
575
+ </fieldset>
576
+
577
+ <button type="submit">제출</button>
578
+ </form>
579
+ );
580
+ };
581
+ ```
582
+
583
+ ## 데이터 시각화
584
+
585
+ ### Chart.js 고급 활용
586
+ ```javascript
587
+ // Responsive & Interactive Chart
588
+ const InteractiveChart = ({ data }) => {
589
+ const chartRef = useRef();
590
+ const [chartInstance, setChartInstance] = useState(null);
591
+
592
+ useEffect(() => {
593
+ const ctx = chartRef.current.getContext('2d');
594
+
595
+ const chart = new Chart(ctx, {
596
+ type: 'line',
597
+ data: {
598
+ labels: data.labels,
599
+ datasets: [{
600
+ label: '매출',
601
+ data: data.values,
602
+ borderColor: 'rgb(75, 192, 192)',
603
+ tension: 0.1,
604
+ pointRadius: 4,
605
+ pointHoverRadius: 6
606
+ }]
607
+ },
608
+ options: {
609
+ responsive: true,
610
+ maintainAspectRatio: false,
611
+ interaction: {
612
+ mode: 'index',
613
+ intersect: false
614
+ },
615
+ plugins: {
616
+ tooltip: {
617
+ callbacks: {
618
+ label: (context) => {
619
+ return `${context.dataset.label}: ${
620
+ new Intl.NumberFormat('ko-KR', {
621
+ style: 'currency',
622
+ currency: 'KRW'
623
+ }).format(context.parsed.y)
624
+ }`;
625
+ }
626
+ }
627
+ },
628
+ zoom: {
629
+ zoom: {
630
+ wheel: { enabled: true },
631
+ pinch: { enabled: true },
632
+ mode: 'x'
633
+ },
634
+ pan: {
635
+ enabled: true,
636
+ mode: 'x'
637
+ }
638
+ }
639
+ }
640
+ }
641
+ });
642
+
643
+ setChartInstance(chart);
644
+
645
+ return () => chart.destroy();
646
+ }, [data]);
647
+
648
+ return (
649
+ <div className="chart-container">
650
+ <canvas ref={chartRef} />
651
+ </div>
652
+ );
653
+ };
654
+ ```
655
+
656
+ ### D3.js 커스텀 시각화
657
+ ```javascript
658
+ // Interactive Force-Directed Graph
659
+ const ForceGraph = ({ nodes, links }) => {
660
+ const svgRef = useRef();
661
+
662
+ useEffect(() => {
663
+ const width = 800;
664
+ const height = 600;
665
+
666
+ const svg = d3.select(svgRef.current)
667
+ .attr('width', width)
668
+ .attr('height', height);
669
+
670
+ // Clear previous render
671
+ svg.selectAll('*').remove();
672
+
673
+ const simulation = d3.forceSimulation(nodes)
674
+ .force('link', d3.forceLink(links).id(d => d.id))
675
+ .force('charge', d3.forceManyBody().strength(-300))
676
+ .force('center', d3.forceCenter(width / 2, height / 2));
677
+
678
+ const link = svg.append('g')
679
+ .selectAll('line')
680
+ .data(links)
681
+ .enter().append('line')
682
+ .attr('stroke', '#999')
683
+ .attr('stroke-opacity', 0.6);
684
+
685
+ const node = svg.append('g')
686
+ .selectAll('circle')
687
+ .data(nodes)
688
+ .enter().append('circle')
689
+ .attr('r', d => d.size || 5)
690
+ .attr('fill', d => d.color || '#69b3a2')
691
+ .call(drag(simulation));
692
+
693
+ node.append('title')
694
+ .text(d => d.label);
695
+
696
+ simulation.on('tick', () => {
697
+ link
698
+ .attr('x1', d => d.source.x)
699
+ .attr('y1', d => d.source.y)
700
+ .attr('x2', d => d.target.x)
701
+ .attr('y2', d => d.target.y);
702
+
703
+ node
704
+ .attr('cx', d => d.x)
705
+ .attr('cy', d => d.y);
706
+ });
707
+
708
+ function drag(simulation) {
709
+ return d3.drag()
710
+ .on('start', (event, d) => {
711
+ if (!event.active) simulation.alphaTarget(0.3).restart();
712
+ d.fx = d.x;
713
+ d.fy = d.y;
714
+ })
715
+ .on('drag', (event, d) => {
716
+ d.fx = event.x;
717
+ d.fy = event.y;
718
+ })
719
+ .on('end', (event, d) => {
720
+ if (!event.active) simulation.alphaTarget(0);
721
+ d.fx = null;
722
+ d.fy = null;
723
+ });
724
+ }
725
+ }, [nodes, links]);
726
+
727
+ return <svg ref={svgRef} />;
728
+ };
729
+ ```