@winspan/claude-forge 8.51.1 → 8.54.3

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 (409) hide show
  1. package/DEVELOPMENT.md +290 -221
  2. package/README.md +50 -8
  3. package/dist/cli/commands/skills.d.ts.map +1 -1
  4. package/dist/cli/commands/skills.js +121 -2
  5. package/dist/cli/commands/skills.js.map +1 -1
  6. package/dist/cli/init/hook-manager.d.ts +1 -1
  7. package/dist/cli/init/hook-manager.d.ts.map +1 -1
  8. package/dist/cli/init/hook-manager.js +1 -0
  9. package/dist/cli/init/hook-manager.js.map +1 -1
  10. package/dist/core/constants.d.ts +2 -0
  11. package/dist/core/constants.d.ts.map +1 -1
  12. package/dist/core/constants.js +4 -0
  13. package/dist/core/constants.js.map +1 -1
  14. package/dist/core/storage/events.d.ts.map +1 -1
  15. package/dist/core/storage/events.js +0 -1
  16. package/dist/core/storage/events.js.map +1 -1
  17. package/dist/core/storage/maintenance.d.ts +25 -3
  18. package/dist/core/storage/maintenance.d.ts.map +1 -1
  19. package/dist/core/storage/maintenance.js +33 -4
  20. package/dist/core/storage/maintenance.js.map +1 -1
  21. package/dist/core/storage/routing.d.ts +4 -0
  22. package/dist/core/storage/routing.d.ts.map +1 -1
  23. package/dist/core/storage/routing.js +10 -4
  24. package/dist/core/storage/routing.js.map +1 -1
  25. package/dist/core/storage/sessions.d.ts +17 -0
  26. package/dist/core/storage/sessions.d.ts.map +1 -1
  27. package/dist/core/storage/sessions.js +64 -0
  28. package/dist/core/storage/sessions.js.map +1 -1
  29. package/dist/core/storage/skills.d.ts +4 -0
  30. package/dist/core/storage/skills.d.ts.map +1 -1
  31. package/dist/core/storage/skills.js +10 -2
  32. package/dist/core/storage/skills.js.map +1 -1
  33. package/dist/core/storage/sqlite.d.ts +5 -0
  34. package/dist/core/storage/sqlite.d.ts.map +1 -1
  35. package/dist/core/storage/sqlite.js +6 -0
  36. package/dist/core/storage/sqlite.js.map +1 -1
  37. package/dist/core/storage/tasks.d.ts.map +1 -1
  38. package/dist/core/storage/tasks.js +2 -0
  39. package/dist/core/storage/tasks.js.map +1 -1
  40. package/dist/core/types.d.ts +7 -0
  41. package/dist/core/types.d.ts.map +1 -1
  42. package/dist/daemon/index.d.ts.map +1 -1
  43. package/dist/daemon/index.js +30 -5
  44. package/dist/daemon/index.js.map +1 -1
  45. package/dist/daemon/skill-sync.d.ts +21 -0
  46. package/dist/daemon/skill-sync.d.ts.map +1 -0
  47. package/dist/daemon/skill-sync.js +75 -0
  48. package/dist/daemon/skill-sync.js.map +1 -0
  49. package/dist/hooks/notification.sh +1 -1
  50. package/dist/hooks/post-tool-use.sh +1 -1
  51. package/dist/hooks/pre-tool-use.sh +1 -1
  52. package/dist/hooks/stop.sh +1 -1
  53. package/dist/hooks/user-prompt-submit.sh +1 -1
  54. package/dist/skills/official/code-simplifier.md +37 -1
  55. package/dist/skills/official/find-skills.md +120 -1
  56. package/dist/skills/official/official-api-design.md +14 -1
  57. package/dist/skills/official/official-architecture-decision.md +22 -1
  58. package/dist/skills/official/official-db-schema-design.md +19 -1
  59. package/dist/skills/official/official-debug.md +9 -1
  60. package/dist/skills/official/official-pr-review.md +1 -1
  61. package/dist/skills/official/official-security-hardening.md +7 -1
  62. package/dist/skills/official/planning-with-files.md +206 -2
  63. package/dist/skills/official/ui-ux-pro-max.md +88 -1
  64. package/dist/skills/official/webapp-testing.md +85 -1
  65. package/dist/skills/registry.d.ts +1 -1
  66. package/dist/skills/registry.d.ts.map +1 -1
  67. package/dist/skills/registry.js +15 -4
  68. package/dist/skills/registry.js.map +1 -1
  69. package/dist/skills/semantic-matcher.d.ts +4 -3
  70. package/dist/skills/semantic-matcher.d.ts.map +1 -1
  71. package/dist/skills/semantic-matcher.js +20 -22
  72. package/dist/skills/semantic-matcher.js.map +1 -1
  73. package/dist/skills/upgrade-engine.d.ts +93 -0
  74. package/dist/skills/upgrade-engine.d.ts.map +1 -0
  75. package/dist/skills/upgrade-engine.js +447 -0
  76. package/dist/skills/upgrade-engine.js.map +1 -0
  77. package/dist/skills/upgrade-prompt.d.ts +20 -0
  78. package/dist/skills/upgrade-prompt.d.ts.map +1 -0
  79. package/dist/skills/upgrade-prompt.js +75 -0
  80. package/dist/skills/upgrade-prompt.js.map +1 -0
  81. package/dist/web/analytics/weekly-report.d.ts.map +1 -1
  82. package/dist/web/analytics/weekly-report.js +21 -29
  83. package/dist/web/analytics/weekly-report.js.map +1 -1
  84. package/dist/web/routes/patch.d.ts.map +1 -1
  85. package/dist/web/routes/patch.js +32 -2
  86. package/dist/web/routes/patch.js.map +1 -1
  87. package/dist/web/routes/sessions.d.ts.map +1 -1
  88. package/dist/web/routes/sessions.js +9 -7
  89. package/dist/web/routes/sessions.js.map +1 -1
  90. package/dist/web/routes/trace.d.ts.map +1 -1
  91. package/dist/web/routes/trace.js +2 -3
  92. package/dist/web/routes/trace.js.map +1 -1
  93. package/dist/web/server.d.ts.map +1 -1
  94. package/dist/web/server.js +3 -2
  95. package/dist/web/server.js.map +1 -1
  96. package/package.json +12 -2
  97. package/scripts/postinstall.cjs +21 -0
  98. package/.claude/CLAUDE.md +0 -17
  99. package/.eslintrc.js +0 -23
  100. package/.prettierrc +0 -8
  101. package/ARCHITECTURE_ISSUES.md +0 -249
  102. package/CLAUDE.md +0 -265
  103. package/CLAUDE.md.backup +0 -488
  104. package/docs/concurrent-agents.md +0 -129
  105. package/docs/design/architecture-review-20260516.md +0 -232
  106. package/docs/design/fix-skills-data-and-set-leak-spec-20260516-1300.md +0 -219
  107. package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +0 -299
  108. package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +0 -191
  109. package/docs/design/h3-fallback-removal-spec-20260518-1245.md +0 -76
  110. package/docs/design/h4-index-dedup-spec-20260518-1230.md +0 -109
  111. package/docs/design/h6-services-migration-spec-20260518-1355.md +0 -82
  112. package/docs/design/hook-failure-queue-spec-20260516-1530.md +0 -204
  113. package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +0 -106
  114. package/docs/design/m10-forge-paths-spec-20260518-1320.md +0 -121
  115. package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +0 -131
  116. package/docs/design/m7-routing-event-association-spec-20260518-1545.md +0 -103
  117. package/docs/design/project-path-gitroot-spec-20260518-1715.md +0 -134
  118. package/docs/design/refactor-phase1-spec-20260515-1600.md +0 -543
  119. package/docs/design/refactor-phase2-spec-20260515-1700.md +0 -424
  120. package/docs/design/task-active-gc-spec-20260518-1745.md +0 -146
  121. package/docs/design/tasks-list-filter-pagination-spec-20260518-0930.md +0 -208
  122. package/docs/implementation/fix-skills-data-and-set-leak-changelog-20260516-1300.md +0 -104
  123. package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +0 -82
  124. package/docs/implementation/h2-final-changelog-20260518-1530.md +0 -61
  125. package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +0 -70
  126. package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +0 -120
  127. package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +0 -71
  128. package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +0 -71
  129. package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +0 -60
  130. package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +0 -46
  131. package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +0 -46
  132. package/docs/implementation/hook-failure-queue-changelog-20260516-1530.md +0 -196
  133. package/docs/implementation/hotfix-daemon-event-reject-20260516-1430.md +0 -56
  134. package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +0 -45
  135. package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +0 -63
  136. package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +0 -38
  137. package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +0 -58
  138. package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +0 -60
  139. package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +0 -43
  140. package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +0 -56
  141. package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +0 -69
  142. package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +0 -63
  143. package/docs/implementation/refactor-phase1-changelog-20260515-1630.md +0 -354
  144. package/docs/implementation/refactor-phase2-changelog-20260515-1705.md +0 -421
  145. package/docs/implementation/task-active-gc-changelog-20260518-1745.md +0 -35
  146. package/docs/implementation/task-title-summary-changelog-20260518-1130.md +0 -39
  147. package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +0 -22
  148. package/docs/implementation/tasks-list-filter-pagination-changelog-20260518-0930.md +0 -72
  149. package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +0 -56
  150. package/docs/reviews/claudemd-template-sync.md +0 -54
  151. package/docs/reviews/task-title-summary.md +0 -92
  152. package/docs/reviews/tasks-detail-back-loses-filters.md +0 -58
  153. package/docs/reviews/tasks-filter-pagination.md +0 -80
  154. package/docs/reviews/tasks-page-white-screen-hotfix.md +0 -126
  155. package/docs/ruflo-learning-strategy.md +0 -322
  156. package/docs/skills-deduplication-analysis.md +0 -83
  157. package/docs/skills-multiformat-support.md +0 -177
  158. package/docs/skills-third-party.md +0 -183
  159. package/docs/testing/tasks-filter-pagination-test-report.md +0 -86
  160. package/forge +0 -321
  161. package/playwright.config.ts +0 -40
  162. package/scripts/demo-v2.ts +0 -91
  163. package/scripts/dev-daemon.sh +0 -232
  164. package/scripts/dev-web.ts +0 -109
  165. package/scripts/e2e-mcp-link.ts +0 -423
  166. package/scripts/e2e-methodology-quality.ts +0 -253
  167. package/scripts/e2e-routing.ts +0 -456
  168. package/scripts/e2e-user-methodology.ts +0 -326
  169. package/scripts/e2e-web-workflows.ts +0 -299
  170. package/scripts/migrate-legacy-to-dynamic.sql +0 -108
  171. package/scripts/regenerate-execution-docs.ts +0 -116
  172. package/scripts/sync-agent-skills.ts +0 -193
  173. package/scripts/test-hook.sh +0 -71
  174. package/scripts/verify-skill-loading.ts +0 -62
  175. package/src/claudemd/claudemd-generator.ts +0 -568
  176. package/src/claudemd/convention-extractor.ts +0 -69
  177. package/src/claudemd/index.ts +0 -35
  178. package/src/claudemd/persona-manager.ts +0 -88
  179. package/src/claudemd/resume-manager.ts +0 -236
  180. package/src/claudemd/tech-detector.ts +0 -220
  181. package/src/claudemd/templates/swarm-protocol.md +0 -222
  182. package/src/cli/commands/claudemd.ts +0 -84
  183. package/src/cli/commands/config.ts +0 -46
  184. package/src/cli/commands/daemon.ts +0 -310
  185. package/src/cli/commands/executions.ts +0 -115
  186. package/src/cli/commands/init.ts +0 -204
  187. package/src/cli/commands/logs.ts +0 -181
  188. package/src/cli/commands/mcp.ts +0 -242
  189. package/src/cli/commands/menu.ts +0 -357
  190. package/src/cli/commands/skills.ts +0 -185
  191. package/src/cli/commands/stats.ts +0 -73
  192. package/src/cli/commands/status.ts +0 -69
  193. package/src/cli/commands/template.ts +0 -77
  194. package/src/cli/commands/trace.ts +0 -148
  195. package/src/cli/index.ts +0 -42
  196. package/src/cli/init/hook-manager.ts +0 -132
  197. package/src/core/ai/provider.ts +0 -308
  198. package/src/core/ai/types.ts +0 -51
  199. package/src/core/config.ts +0 -124
  200. package/src/core/constants.ts +0 -62
  201. package/src/core/event-fields.ts +0 -32
  202. package/src/core/queue/index.ts +0 -192
  203. package/src/core/storage/base.ts +0 -302
  204. package/src/core/storage/events.ts +0 -434
  205. package/src/core/storage/injections.ts +0 -78
  206. package/src/core/storage/maintenance.ts +0 -59
  207. package/src/core/storage/migrations/002_add_skill_tracking.sql +0 -6
  208. package/src/core/storage/migrations/003_add_skill_invocations.sql +0 -23
  209. package/src/core/storage/performance-indexes.sql +0 -23
  210. package/src/core/storage/routing.ts +0 -322
  211. package/src/core/storage/rows.ts +0 -112
  212. package/src/core/storage/schema.sql +0 -224
  213. package/src/core/storage/sessions.ts +0 -168
  214. package/src/core/storage/skills.ts +0 -233
  215. package/src/core/storage/sqlite.ts +0 -293
  216. package/src/core/storage/tasks.ts +0 -318
  217. package/src/core/storage/token-usage.ts +0 -93
  218. package/src/core/types.ts +0 -181
  219. package/src/core/utils/error-handler.ts +0 -257
  220. package/src/core/utils/forge-resume-block.ts +0 -74
  221. package/src/core/utils/format.ts +0 -69
  222. package/src/core/utils/git.ts +0 -23
  223. package/src/core/utils/logger.ts +0 -134
  224. package/src/core/utils/lru-cache.ts +0 -54
  225. package/src/core/utils/path.ts +0 -19
  226. package/src/core/utils/session.ts +0 -26
  227. package/src/core/utils/time.ts +0 -37
  228. package/src/core/utils/token-tracker.ts +0 -97
  229. package/src/daemon/event-parser.ts +0 -36
  230. package/src/daemon/handlers/history-exporter.ts +0 -117
  231. package/src/daemon/handlers/post-tool-use.ts +0 -54
  232. package/src/daemon/handlers/stop.ts +0 -208
  233. package/src/daemon/handlers/user-prompt.ts +0 -178
  234. package/src/daemon/hook-sync.ts +0 -91
  235. package/src/daemon/index.ts +0 -302
  236. package/src/daemon/launchd/com.claude-forge.daemon.plist.template +0 -47
  237. package/src/daemon/launchd-installer.ts +0 -260
  238. package/src/daemon/lifecycle.ts +0 -128
  239. package/src/daemon/router.ts +0 -40
  240. package/src/daemon/server.ts +0 -196
  241. package/src/daemon/services/task-segmenter.ts +0 -112
  242. package/src/hooks/hook-lib.sh +0 -118
  243. package/src/hooks/notification.sh +0 -35
  244. package/src/hooks/post-tool-use.sh +0 -61
  245. package/src/hooks/pre-tool-use.sh +0 -63
  246. package/src/hooks/stop.sh +0 -43
  247. package/src/hooks/user-prompt-submit.sh +0 -69
  248. package/src/mcp/server.ts +0 -322
  249. package/src/skills/index.ts +0 -2
  250. package/src/skills/invocation-guard.ts +0 -177
  251. package/src/skills/matcher.ts +0 -148
  252. package/src/skills/official/code-simplifier.md +0 -16
  253. package/src/skills/official/find-skills.md +0 -23
  254. package/src/skills/official/official-api-design.md +0 -17
  255. package/src/skills/official/official-architecture-decision.md +0 -20
  256. package/src/skills/official/official-bmad.md +0 -118
  257. package/src/skills/official/official-db-schema-design.md +0 -16
  258. package/src/skills/official/official-debug.md +0 -17
  259. package/src/skills/official/official-doc-driven.md +0 -31
  260. package/src/skills/official/official-harness-engineering.md +0 -108
  261. package/src/skills/official/official-performance-optimization.md +0 -30
  262. package/src/skills/official/official-pr-review.md +0 -35
  263. package/src/skills/official/official-release-checklist.md +0 -30
  264. package/src/skills/official/official-security-hardening.md +0 -26
  265. package/src/skills/official/official-spec-driven-design.md +0 -31
  266. package/src/skills/official/planning-with-files.md +0 -37
  267. package/src/skills/official/ui-ux-pro-max.md +0 -18
  268. package/src/skills/official/webapp-testing.md +0 -12
  269. package/src/skills/official-skills.ts +0 -89
  270. package/src/skills/registry.ts +0 -355
  271. package/src/skills/semantic-matcher.ts +0 -231
  272. package/src/skills/tools/pipeline-suggest.ts +0 -226
  273. package/src/skills/tools/skill-invoke.ts +0 -168
  274. package/src/skills/tools/skill-list.ts +0 -59
  275. package/src/templates/go.yaml +0 -53
  276. package/src/templates/python.yaml +0 -59
  277. package/src/templates/react.yaml +0 -55
  278. package/src/templates/template-manager.ts +0 -170
  279. package/src/web/analytics/anti-pattern-detector.ts +0 -367
  280. package/src/web/analytics/drift-detector.ts +0 -219
  281. package/src/web/analytics/weekly-report.ts +0 -431
  282. package/src/web/auth-middleware.ts +0 -54
  283. package/src/web/routes/_helpers.ts +0 -34
  284. package/src/web/routes/ai.ts +0 -204
  285. package/src/web/routes/auth.ts +0 -22
  286. package/src/web/routes/drift.ts +0 -25
  287. package/src/web/routes/error-handler.ts +0 -120
  288. package/src/web/routes/events.ts +0 -47
  289. package/src/web/routes/insights.ts +0 -43
  290. package/src/web/routes/patch.ts +0 -117
  291. package/src/web/routes/reports.ts +0 -34
  292. package/src/web/routes/rules.ts +0 -76
  293. package/src/web/routes/sessions.ts +0 -250
  294. package/src/web/routes/skill-stats.ts +0 -92
  295. package/src/web/routes/skills.ts +0 -350
  296. package/src/web/routes/static.ts +0 -67
  297. package/src/web/routes/stats.ts +0 -50
  298. package/src/web/routes/status.ts +0 -30
  299. package/src/web/routes/tasks.ts +0 -193
  300. package/src/web/routes/token-usage.ts +0 -20
  301. package/src/web/routes/trace.ts +0 -126
  302. package/src/web/routes/types.ts +0 -57
  303. package/src/web/server.ts +0 -134
  304. package/src/web/ssrf-guard.ts +0 -112
  305. package/src/web/static/index.html +0 -3251
  306. package/src/web/static/vendor/chart.umd.min.js +0 -20
  307. package/tests/e2e/dashboard.spec.ts +0 -205
  308. package/tests/e2e/routing-skill-e2e.test.ts +0 -39
  309. package/tests/helpers/mock-ai.ts +0 -92
  310. package/tests/helpers/mock-storage.ts +0 -159
  311. package/tests/integration/claudemd-generator.test.ts +0 -90
  312. package/tests/integration/queue-replay.integration.test.ts +0 -193
  313. package/tests/integration/tasks-filter.integration.test.ts +0 -154
  314. package/tests/integration/web-analytics.integration.test.ts +0 -133
  315. package/tests/integration/web-stats.integration.test.ts +0 -135
  316. package/tests/integration/web-trace.integration.test.ts +0 -175
  317. package/tests/performance/database.benchmark.ts +0 -161
  318. package/tests/semantic-matcher.test.ts +0 -99
  319. package/tests/skill-matcher.test.ts +0 -110
  320. package/tests/unit/ai-provider-retry.test.ts +0 -194
  321. package/tests/unit/ai-provider-vision.test.ts +0 -224
  322. package/tests/unit/claudemd-generator.test.ts +0 -68
  323. package/tests/unit/cli-mcp.test.ts +0 -141
  324. package/tests/unit/core/forge-paths.test.ts +0 -99
  325. package/tests/unit/daemon/hook-sync.test.ts +0 -71
  326. package/tests/unit/daemon/post-tool-use.test.ts +0 -121
  327. package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +0 -202
  328. package/tests/unit/daemon/task-segmenter-recover.test.ts +0 -84
  329. package/tests/unit/event-fields.test.ts +0 -88
  330. package/tests/unit/event-parser.test.ts +0 -55
  331. package/tests/unit/handlers.test.ts +0 -171
  332. package/tests/unit/hooks/resolve-project-path.test.ts +0 -122
  333. package/tests/unit/invocation-guard.test.ts +0 -125
  334. package/tests/unit/queue.test.ts +0 -272
  335. package/tests/unit/router.test.ts +0 -138
  336. package/tests/unit/security.test.ts +0 -128
  337. package/tests/unit/skill-invocations-workflow.test.ts +0 -495
  338. package/tests/unit/skill-registry.test.ts +0 -94
  339. package/tests/unit/skills/invocation-guard-ttl.test.ts +0 -211
  340. package/tests/unit/skills/official-skills-loader.test.ts +0 -126
  341. package/tests/unit/skills/registry-multiformat.test.ts +0 -92
  342. package/tests/unit/socket-server.test.ts +0 -183
  343. package/tests/unit/storage/event-operations-aggregates.test.ts +0 -342
  344. package/tests/unit/storage/migration-idempotent.test.ts +0 -304
  345. package/tests/unit/storage/routing-aggregates.test.ts +0 -276
  346. package/tests/unit/storage/routing.test.ts +0 -117
  347. package/tests/unit/storage/schema-missing.test.ts +0 -81
  348. package/tests/unit/storage/session-operations-aggregates.test.ts +0 -120
  349. package/tests/unit/storage/sessions-aggregate.test.ts +0 -435
  350. package/tests/unit/storage/skill-operations-counts.test.ts +0 -106
  351. package/tests/unit/storage/skills-aggregates.test.ts +0 -104
  352. package/tests/unit/storage/sqlite-refactor-harness.test.ts +0 -314
  353. package/tests/unit/storage/task-operations-counts.test.ts +0 -46
  354. package/tests/unit/storage/tasks-getById.test.ts +0 -343
  355. package/tests/unit/storage/tasks-stale-gc.test.ts +0 -86
  356. package/tests/unit/storage.test.ts +0 -172
  357. package/tests/unit/token-usage.test.ts +0 -144
  358. package/tests/unit/type-guards.test.ts +0 -201
  359. package/tests/unit/utils/format.test.ts +0 -189
  360. package/tests/unit/utils/session.test.ts +0 -89
  361. package/tests/unit/utils/time.test.ts +0 -112
  362. package/tests/unit/web/navigation-back-contract.test.ts +0 -134
  363. package/tests/unit/web/routes-auth.test.ts +0 -93
  364. package/tests/unit/web/routes-events.test.ts +0 -101
  365. package/tests/unit/web/routes-rules.test.ts +0 -182
  366. package/tests/unit/web/routes-sessions.test.ts +0 -181
  367. package/tests/unit/web/routes-skill-stats.test.ts +0 -179
  368. package/tests/unit/web/routes-stats.test.ts +0 -92
  369. package/tests/unit/web/routes-tasks.test.ts +0 -385
  370. package/tests/unit/web/task-title-contract.test.ts +0 -210
  371. package/tests/unit/web/tasks-component-contract.test.ts +0 -179
  372. package/tsconfig.json +0 -22
  373. package/vitest.config.ts +0 -21
  374. package/vitest.integration.config.ts +0 -16
  375. package/web/CLAUDE.md +0 -20
  376. package/web/index.html +0 -13
  377. package/web/package-lock.json +0 -4854
  378. package/web/package.json +0 -35
  379. package/web/postcss.config.js +0 -6
  380. package/web/src/App.tsx +0 -110
  381. package/web/src/components/CodeBlock.tsx +0 -31
  382. package/web/src/components/Confirm.tsx +0 -96
  383. package/web/src/components/Drawer.tsx +0 -60
  384. package/web/src/components/Layout.tsx +0 -145
  385. package/web/src/components/MarkdownRenderer.tsx +0 -77
  386. package/web/src/components/SearchInput.tsx +0 -31
  387. package/web/src/components/SessionDetailContent.tsx +0 -157
  388. package/web/src/components/Toast.tsx +0 -92
  389. package/web/src/index.css +0 -19
  390. package/web/src/main.tsx +0 -31
  391. package/web/src/pages/AIConfig.tsx +0 -233
  392. package/web/src/pages/Dashboard.tsx +0 -572
  393. package/web/src/pages/Events.tsx +0 -271
  394. package/web/src/pages/Reports.tsx +0 -428
  395. package/web/src/pages/SessionDetail.tsx +0 -162
  396. package/web/src/pages/Sessions.tsx +0 -205
  397. package/web/src/pages/Skills.tsx +0 -180
  398. package/web/src/pages/TaskDetail.tsx +0 -515
  399. package/web/src/pages/Tasks.tsx +0 -415
  400. package/web/src/utils/auth.ts +0 -59
  401. package/web/src/utils/export.ts +0 -54
  402. package/web/src/utils/navigation.ts +0 -25
  403. package/web/src/utils/task-title.ts +0 -49
  404. package/web/src/utils/time.ts +0 -13
  405. package/web/tailwind.config.js +0 -11
  406. package/web/tsconfig.json +0 -21
  407. package/web/tsconfig.node.json +0 -10
  408. package/web/vite.config.ts +0 -76
  409. package/winspan-claude-forge-8.43.0.tgz +0 -0
@@ -1,367 +0,0 @@
1
- /**
2
- * AntiPatternDetector — 工作流反模式检测引擎
3
- *
4
- * 扫描 events / sessions 表,识别可操作的反模式:
5
- * - edit_loop: 同一文件反复 Edit/Write(调试鬼打墙)
6
- * - tool_failure_spike: 最近 24h 工具失败率突增
7
- * - no_commit: 长会话无 git commit
8
- * - event_overflow: 单 session 事件数异常
9
- *
10
- * 性能策略:
11
- * - 一次性拉取窗口内事件,4 条规则共享缓存
12
- * - 仅读 storage(getDatabase + queryEvents + querySessions),不改任何状态
13
- */
14
-
15
- import { createHash } from 'node:crypto';
16
- import type { SQLiteStorage } from '../../core/storage/sqlite.js';
17
- import type { ForgeEvent } from '../../core/types.js';
18
- import type { SessionSummary } from '../../core/storage/sessions.js';
19
-
20
- export type AntiPatternType =
21
- | 'edit_loop'
22
- | 'tool_failure_spike'
23
- | 'no_commit'
24
- | 'event_overflow';
25
-
26
- export type AntiPatternSeverity = 'info' | 'warn' | 'critical';
27
-
28
- export interface AntiPattern {
29
- id: string;
30
- type: AntiPatternType;
31
- severity: AntiPatternSeverity;
32
- title: string;
33
- description: string;
34
- detail: Record<string, unknown>;
35
- detectedAt: string;
36
- sessionId?: string;
37
- filePath?: string;
38
- }
39
-
40
- const DAY_MS = 24 * 3600 * 1000;
41
- const EDIT_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
42
-
43
- export class AntiPatternDetector {
44
- constructor(private storage: SQLiteStorage) {}
45
-
46
- /**
47
- * 运行全部反模式规则。
48
- *
49
- * @param windowDays 检测窗口(天),默认 7
50
- */
51
- detectAll(windowDays: number = 7): AntiPattern[] {
52
- const now = Date.now();
53
- const sinceMs = now - windowDays * DAY_MS;
54
- const sinceISO = new Date(sinceMs).toISOString();
55
- const detectedAt = new Date(now).toISOString();
56
-
57
- // 一次性拉取窗口内事件 + 会话,4 条规则共用
58
- const events = this.queryEventsSince(sinceISO);
59
- const sessions = this.querySessionsSince(sinceISO);
60
-
61
- const patterns: AntiPattern[] = [
62
- ...this.detectEditLoop(events, detectedAt),
63
- ...this.detectToolFailureSpike(events, now, windowDays, detectedAt),
64
- ...this.detectNoCommit(sessions, events, detectedAt),
65
- ...this.detectEventOverflow(sessions, events, detectedAt),
66
- ];
67
-
68
- // 按 severity → critical→warn→info 排序,方便前端取 top N
69
- const sevOrder: Record<AntiPatternSeverity, number> = { critical: 0, warn: 1, info: 2 };
70
- patterns.sort((a, b) => sevOrder[a.severity] - sevOrder[b.severity]);
71
- return patterns;
72
- }
73
-
74
- // ── 数据加载 ──────────────────────────────────────────────────────────
75
-
76
- /**
77
- * 拉取窗口内全部事件。
78
- *
79
- * H2 Phase 3: 改用 facade 聚合方法。
80
- */
81
- private queryEventsSince(sinceISO: string): ForgeEvent[] {
82
- return this.storage.queryEventsByTimeRange({ since: sinceISO });
83
- }
84
-
85
- private querySessionsSince(sinceISO: string): SessionSummary[] {
86
- return this.storage.querySessionsByTimeRange({ since: sinceISO });
87
- }
88
-
89
- // ── Rule 1: edit_loop ─────────────────────────────────────────────────
90
-
91
- private detectEditLoop(events: ForgeEvent[], detectedAt: string): AntiPattern[] {
92
- // 仅 PreToolUse 计一次(避免 Pre + Post 重复计数)
93
- const buckets = new Map<string, {
94
- sessionId: string;
95
- filePath: string;
96
- count: number;
97
- firstAt: string;
98
- lastAt: string;
99
- }>();
100
-
101
- for (const ev of events) {
102
- if (ev.hook_type !== 'PreToolUse') continue;
103
- if (!ev.tool_name || !EDIT_TOOLS.has(ev.tool_name)) continue;
104
- const filePath = extractFilePath(ev.tool_input);
105
- if (!filePath) continue;
106
-
107
- const key = `${ev.session_id}::${filePath}`;
108
- const cur = buckets.get(key);
109
- if (cur) {
110
- cur.count += 1;
111
- if (ev.timestamp < cur.firstAt) cur.firstAt = ev.timestamp;
112
- if (ev.timestamp > cur.lastAt) cur.lastAt = ev.timestamp;
113
- } else {
114
- buckets.set(key, {
115
- sessionId: ev.session_id,
116
- filePath,
117
- count: 1,
118
- firstAt: ev.timestamp,
119
- lastAt: ev.timestamp,
120
- });
121
- }
122
- }
123
-
124
- const out: AntiPattern[] = [];
125
- for (const b of buckets.values()) {
126
- if (b.count < 5) continue;
127
- const severity: AntiPatternSeverity = b.count >= 8 ? 'critical' : 'warn';
128
- out.push({
129
- id: stableId('edit_loop', b.sessionId, b.filePath),
130
- type: 'edit_loop',
131
- severity,
132
- title: `编辑循环:${shortPath(b.filePath)} 被改动 ${b.count} 次`,
133
- description: `会话 ${shortId(b.sessionId)} 内对同一文件反复 Edit/Write,疑似调试鬼打墙。`,
134
- detail: {
135
- filePath: b.filePath,
136
- count: b.count,
137
- sessionId: b.sessionId,
138
- firstEditAt: b.firstAt,
139
- lastEditAt: b.lastAt,
140
- },
141
- detectedAt,
142
- sessionId: b.sessionId,
143
- filePath: b.filePath,
144
- });
145
- }
146
- return out;
147
- }
148
-
149
- // ── Rule 2: tool_failure_spike ────────────────────────────────────────
150
-
151
- private detectToolFailureSpike(
152
- events: ForgeEvent[],
153
- nowMs: number,
154
- windowDays: number,
155
- detectedAt: string,
156
- ): AntiPattern[] {
157
- const recentCutoffMs = nowMs - DAY_MS;
158
- const recentCutoffISO = new Date(recentCutoffMs).toISOString();
159
-
160
- let recentTotal = 0;
161
- let recentFailures = 0;
162
- let baselineTotal = 0;
163
- let baselineFailures = 0;
164
-
165
- for (const ev of events) {
166
- if (ev.hook_type !== 'PostToolUse') continue;
167
- const failed = isFailedToolOutput(ev.tool_output);
168
- if (ev.timestamp >= recentCutoffISO) {
169
- recentTotal += 1;
170
- if (failed) recentFailures += 1;
171
- } else {
172
- baselineTotal += 1;
173
- if (failed) baselineFailures += 1;
174
- }
175
- }
176
-
177
- if (recentTotal < 10) {
178
- // 样本太少,不报警
179
- return [];
180
- }
181
-
182
- const recentRate = recentFailures / recentTotal;
183
- const baselineRate = baselineTotal > 0 ? baselineFailures / baselineTotal : 0;
184
-
185
- if (recentRate < 0.2) return [];
186
-
187
- const severity: AntiPatternSeverity = recentRate >= 0.4 ? 'critical' : 'warn';
188
- return [{
189
- id: stableId('tool_failure_spike', detectedAt.slice(0, 13)),
190
- type: 'tool_failure_spike',
191
- severity,
192
- title: `工具失败率 ${(recentRate * 100).toFixed(1)}%(最近 24h)`,
193
- description: `最近 24 小时 PostToolUse 失败比例显著高于过去 ${windowDays - 1} 天均值(${(baselineRate * 100).toFixed(1)}%)。`,
194
- detail: {
195
- recentFailureRate: round(recentRate, 4),
196
- baselineFailureRate: round(baselineRate, 4),
197
- recentWindowHours: 24,
198
- baselineWindowDays: Math.max(windowDays - 1, 1),
199
- totalToolUses: recentTotal,
200
- failures: recentFailures,
201
- baselineTotal,
202
- baselineFailures,
203
- },
204
- detectedAt,
205
- }];
206
- }
207
-
208
- // ── Rule 3: no_commit ─────────────────────────────────────────────────
209
-
210
- private detectNoCommit(
211
- sessions: SessionSummary[],
212
- events: ForgeEvent[],
213
- detectedAt: string,
214
- ): AntiPattern[] {
215
- // 预聚合:每个 session 内的 Bash 命令文本
216
- const bashBySession = new Map<string, string[]>();
217
- for (const ev of events) {
218
- if (ev.tool_name !== 'Bash') continue;
219
- if (ev.hook_type !== 'PreToolUse') continue;
220
- const cmd = extractBashCommand(ev.tool_input);
221
- if (!cmd) continue;
222
- const list = bashBySession.get(ev.session_id);
223
- if (list) list.push(cmd);
224
- else bashBySession.set(ev.session_id, [cmd]);
225
- }
226
-
227
- const out: AntiPattern[] = [];
228
- for (const s of sessions) {
229
- const durationMs = isoDiffMs(s.start_time, s.end_time);
230
- const durationMin = durationMs / 60000;
231
- if (durationMin <= 30) continue;
232
-
233
- const cmds = bashBySession.get(s.session_id) ?? [];
234
- const hasCommit = cmds.some((c) => /\bgit\s+commit\b/.test(c));
235
- if (hasCommit) continue;
236
-
237
- const severity: AntiPatternSeverity = durationMin > 60 ? 'critical' : 'warn';
238
- out.push({
239
- id: stableId('no_commit', s.session_id),
240
- type: 'no_commit',
241
- severity,
242
- title: `${Math.round(durationMin)} 分钟会话无 git commit`,
243
- description: `会话 ${shortId(s.session_id)} 持续 ${Math.round(durationMin)} 分钟未沉淀提交。`,
244
- detail: {
245
- sessionId: s.session_id,
246
- durationMin: Math.round(durationMin),
247
- eventCount: s.event_count,
248
- startTime: s.start_time,
249
- endTime: s.end_time,
250
- bashCommands: cmds.length,
251
- },
252
- detectedAt,
253
- sessionId: s.session_id,
254
- });
255
- }
256
- return out;
257
- }
258
-
259
- // ── Rule 4: event_overflow ────────────────────────────────────────────
260
-
261
- private detectEventOverflow(
262
- sessions: SessionSummary[],
263
- events: ForgeEvent[],
264
- detectedAt: string,
265
- ): AntiPattern[] {
266
- // 按 session 统计 top tools
267
- const toolBySession = new Map<string, Map<string, number>>();
268
- for (const ev of events) {
269
- if (!ev.tool_name) continue;
270
- let m = toolBySession.get(ev.session_id);
271
- if (!m) {
272
- m = new Map();
273
- toolBySession.set(ev.session_id, m);
274
- }
275
- m.set(ev.tool_name, (m.get(ev.tool_name) ?? 0) + 1);
276
- }
277
-
278
- const out: AntiPattern[] = [];
279
- for (const s of sessions) {
280
- if (s.event_count <= 500) continue;
281
- const severity: AntiPatternSeverity = s.event_count > 1000 ? 'critical' : 'warn';
282
- const durationMin = Math.round(isoDiffMs(s.start_time, s.end_time) / 60000);
283
- const topTools = topN(toolBySession.get(s.session_id), 5);
284
-
285
- out.push({
286
- id: stableId('event_overflow', s.session_id),
287
- type: 'event_overflow',
288
- severity,
289
- title: `事件爆量:${s.event_count} 个事件`,
290
- description: `会话 ${shortId(s.session_id)} 单次累计 ${s.event_count} 个事件,可能任务规划失败或循环失控。`,
291
- detail: {
292
- sessionId: s.session_id,
293
- eventCount: s.event_count,
294
- durationMin,
295
- topTools,
296
- startTime: s.start_time,
297
- endTime: s.end_time,
298
- },
299
- detectedAt,
300
- sessionId: s.session_id,
301
- });
302
- }
303
- return out;
304
- }
305
- }
306
-
307
- // ── helpers ─────────────────────────────────────────────────────────────
308
-
309
- function extractFilePath(input: ForgeEvent['tool_input']): string | undefined {
310
- if (!input) return undefined;
311
- const fp = input.file_path;
312
- if (typeof fp === 'string' && fp.length > 0) return fp;
313
- return undefined;
314
- }
315
-
316
- function extractBashCommand(input: ForgeEvent['tool_input']): string | undefined {
317
- if (!input) return undefined;
318
- const cmd = input.command;
319
- return typeof cmd === 'string' ? cmd : undefined;
320
- }
321
-
322
- function isFailedToolOutput(out: Record<string, unknown> | undefined): boolean {
323
- if (!out) return false;
324
- // Claude Code 的 PostToolUse output 形态:
325
- // - { error: "..." } → 失败
326
- // - { success: false, error: "..." } → 失败
327
- // - { stderr: "...", stdout: "" } → 通常代表失败(仅当 stderr 非空且 stdout 空)
328
- // - { isError: true } → 失败
329
- const err = out.error;
330
- if (typeof err === 'string' && err.length > 0) return true;
331
- if (out.success === false) return true;
332
- if (out.isError === true) return true;
333
- return false;
334
- }
335
-
336
- function isoDiffMs(startISO: string, endISO: string): number {
337
- const s = Date.parse(startISO);
338
- const e = Date.parse(endISO);
339
- if (Number.isNaN(s) || Number.isNaN(e)) return 0;
340
- return Math.max(0, e - s);
341
- }
342
-
343
- function topN(map: Map<string, number> | undefined, n: number): Array<{ tool: string; count: number }> {
344
- if (!map) return [];
345
- return [...map.entries()]
346
- .sort((a, b) => b[1] - a[1])
347
- .slice(0, n)
348
- .map(([tool, count]) => ({ tool, count }));
349
- }
350
-
351
- function round(n: number, digits: number): number {
352
- const f = 10 ** digits;
353
- return Math.round(n * f) / f;
354
- }
355
-
356
- function shortId(id: string): string {
357
- return id.length > 8 ? id.slice(0, 8) : id;
358
- }
359
-
360
- function shortPath(p: string): string {
361
- if (p.length <= 60) return p;
362
- return '…' + p.slice(-58);
363
- }
364
-
365
- function stableId(...parts: string[]): string {
366
- return createHash('sha1').update(parts.join('|')).digest('hex').slice(0, 16);
367
- }
@@ -1,219 +0,0 @@
1
- /**
2
- * DriftDetector — CLAUDE.md 漂移检测引擎
3
- *
4
- * 对比 CLAUDE.md 中声明的行为预期与实际事件流数据,
5
- * 生成合规度报告(DriftReport)。
6
- *
7
- * 只读 storage,不写入任何数据。
8
- */
9
-
10
- import type { SQLiteStorage } from '../../core/storage/sqlite.js';
11
-
12
- export interface DriftReport {
13
- generatedAt: string;
14
- period: { days: number; since: string };
15
- checks: DriftCheck[];
16
- overallScore: number; // 0-100, 100=完全符合
17
- }
18
-
19
- export interface DriftCheck {
20
- rule: string;
21
- expected: string;
22
- actual: string;
23
- status: 'pass' | 'warn' | 'fail';
24
- detail?: string;
25
- }
26
-
27
- export class DriftDetector {
28
- constructor(private storage: SQLiteStorage) {}
29
-
30
- /**
31
- * 执行全部漂移检测规则,返回报告。
32
- */
33
- run(days: number = 7): DriftReport {
34
- const since = Date.now() - days * 24 * 3600 * 1000;
35
- const sinceISO = new Date(since).toISOString();
36
-
37
- const checks: DriftCheck[] = [
38
- this.checkAgentDelegationRate(since),
39
- this.checkSkillTriggerRate(since),
40
- this.checkActiveSkillCoverage(since),
41
- this.checkEventContinuity(),
42
- this.checkTaskSegmentation(since),
43
- ];
44
-
45
- const overallScore = this.computeScore(checks);
46
-
47
- return {
48
- generatedAt: new Date().toISOString(),
49
- period: { days, since: sinceISO },
50
- checks,
51
- overallScore,
52
- };
53
- }
54
-
55
- /**
56
- * 规则 1: Agent 委托率
57
- * 预期: CLAUDE.md 声明"默认 spawn Agent",obeyed 率应 >30%
58
- */
59
- private checkAgentDelegationRate(since: number): DriftCheck {
60
- const events = this.storage.queryRoutingEvents({ since_ts: since, limit: 10000 });
61
- const total = events.length;
62
- const obeyed = events.filter(e => e.obeyed === 1).length;
63
- const rate = total > 0 ? Math.round((obeyed / total) * 1000) / 10 : 0;
64
-
65
- let status: DriftCheck['status'];
66
- if (total === 0) {
67
- status = 'warn';
68
- } else if (rate >= 30) {
69
- status = 'pass';
70
- } else if (rate >= 10) {
71
- status = 'warn';
72
- } else {
73
- status = 'fail';
74
- }
75
-
76
- return {
77
- rule: 'Agent 委托率',
78
- expected: '>30% 路由事件被 Agent 执行 (obeyed=1)',
79
- actual: `${rate}% (${obeyed}/${total})`,
80
- status,
81
- detail: total === 0 ? '期间无路由事件' : undefined,
82
- };
83
- }
84
-
85
- /**
86
- * 规则 2: Skill 触发率
87
- * 预期: 有 Skill 系统就应该有触发,>10%
88
- */
89
- private checkSkillTriggerRate(since: number): DriftCheck {
90
- const routingEvents = this.storage.queryRoutingEvents({ since_ts: since, limit: 10000 });
91
- const totalRouting = routingEvents.length;
92
- const invocations = this.storage.querySkillInvocations({ since, limit: 10000 });
93
- const totalInvocations = invocations.length;
94
-
95
- const rate = totalRouting > 0
96
- ? Math.round((totalInvocations / totalRouting) * 1000) / 10
97
- : 0;
98
-
99
- let status: DriftCheck['status'];
100
- if (totalRouting === 0) {
101
- status = 'warn';
102
- } else if (rate >= 10) {
103
- status = 'pass';
104
- } else if (rate >= 5) {
105
- status = 'warn';
106
- } else {
107
- status = 'fail';
108
- }
109
-
110
- return {
111
- rule: 'Skill 触发率',
112
- expected: '>10% (skill_invocations / routing_events)',
113
- actual: `${rate}% (${totalInvocations}/${totalRouting})`,
114
- status,
115
- detail: totalRouting === 0 ? '期间无路由事件' : undefined,
116
- };
117
- }
118
-
119
- /**
120
- * 规则 3: 活跃 Skill 覆盖率
121
- * 预期: 至少 3 个不同 Skill 被调用
122
- */
123
- private checkActiveSkillCoverage(since: number): DriftCheck {
124
- // H2 Phase 3: 改用 facade。countDistinctSkillsSince 接收 unix ms。
125
- const distinctCount = this.storage.countDistinctSkillsSince(since);
126
-
127
- let status: DriftCheck['status'];
128
- if (distinctCount >= 3) {
129
- status = 'pass';
130
- } else if (distinctCount >= 1) {
131
- status = 'warn';
132
- } else {
133
- status = 'fail';
134
- }
135
-
136
- return {
137
- rule: '活跃 Skill 覆盖率',
138
- expected: '>=3 个不同 Skill 被调用',
139
- actual: `${distinctCount} 个不同 Skill`,
140
- status,
141
- };
142
- }
143
-
144
- /**
145
- * 规则 4: 事件记录连续性
146
- * 预期: 最近 7 天中至少 5 天有事件(工作日)
147
- */
148
- private checkEventContinuity(): DriftCheck {
149
- // H2 Phase 3: 改用 facade。
150
- // 旧 SQL: timestamp >= date('now', '-7 days')
151
- // 新口径:调用方计算 ISO 字符串。
152
- const sevenDaysAgoIso = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
153
- const activeDays = this.storage.countActiveDays({ since: sevenDaysAgoIso });
154
-
155
- let status: DriftCheck['status'];
156
- if (activeDays >= 5) {
157
- status = 'pass';
158
- } else if (activeDays >= 3) {
159
- status = 'warn';
160
- } else {
161
- status = 'fail';
162
- }
163
-
164
- return {
165
- rule: '事件记录连续性',
166
- expected: '最近 7 天中 >=5 天有事件',
167
- actual: `${activeDays} 天有事件`,
168
- status,
169
- };
170
- }
171
-
172
- /**
173
- * 规则 5: 任务分段有效性
174
- * 预期: 每个 session 至少有 1 个 task (tasks/sessions >= 0.8)
175
- */
176
- private checkTaskSegmentation(since: number): DriftCheck {
177
- // H2 Phase 3: 改用 facade。
178
- const sinceISO = new Date(since).toISOString();
179
- const sessionCount = this.storage.countSessionsByRange({ since: sinceISO });
180
- const taskCount = this.storage.countTasksByRange({ since: sinceISO });
181
-
182
- const ratio = sessionCount > 0
183
- ? Math.round((taskCount / sessionCount) * 100) / 100
184
- : 0;
185
-
186
- let status: DriftCheck['status'];
187
- if (sessionCount === 0) {
188
- status = 'warn';
189
- } else if (ratio >= 0.8) {
190
- status = 'pass';
191
- } else if (ratio >= 0.5) {
192
- status = 'warn';
193
- } else {
194
- status = 'fail';
195
- }
196
-
197
- return {
198
- rule: '任务分段有效性',
199
- expected: 'tasks/sessions >= 0.8',
200
- actual: `${ratio} (${taskCount} tasks / ${sessionCount} sessions)`,
201
- status,
202
- detail: sessionCount === 0 ? '期间无会话' : undefined,
203
- };
204
- }
205
-
206
- /**
207
- * 计算总体合规分数 (0-100)
208
- * pass=20, warn=10, fail=0 (每条规则满分 20)
209
- */
210
- private computeScore(checks: DriftCheck[]): number {
211
- const maxPerCheck = 100 / checks.length;
212
- let total = 0;
213
- for (const check of checks) {
214
- if (check.status === 'pass') total += maxPerCheck;
215
- else if (check.status === 'warn') total += maxPerCheck * 0.5;
216
- }
217
- return Math.round(total);
218
- }
219
- }