@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.
- package/DEVELOPMENT.md +290 -221
- package/README.md +50 -8
- package/dist/cli/commands/skills.d.ts.map +1 -1
- package/dist/cli/commands/skills.js +121 -2
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/init/hook-manager.d.ts +1 -1
- package/dist/cli/init/hook-manager.d.ts.map +1 -1
- package/dist/cli/init/hook-manager.js +1 -0
- package/dist/cli/init/hook-manager.js.map +1 -1
- package/dist/core/constants.d.ts +2 -0
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +4 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/core/storage/events.d.ts.map +1 -1
- package/dist/core/storage/events.js +0 -1
- package/dist/core/storage/events.js.map +1 -1
- package/dist/core/storage/maintenance.d.ts +25 -3
- package/dist/core/storage/maintenance.d.ts.map +1 -1
- package/dist/core/storage/maintenance.js +33 -4
- package/dist/core/storage/maintenance.js.map +1 -1
- package/dist/core/storage/routing.d.ts +4 -0
- package/dist/core/storage/routing.d.ts.map +1 -1
- package/dist/core/storage/routing.js +10 -4
- package/dist/core/storage/routing.js.map +1 -1
- package/dist/core/storage/sessions.d.ts +17 -0
- package/dist/core/storage/sessions.d.ts.map +1 -1
- package/dist/core/storage/sessions.js +64 -0
- package/dist/core/storage/sessions.js.map +1 -1
- package/dist/core/storage/skills.d.ts +4 -0
- package/dist/core/storage/skills.d.ts.map +1 -1
- package/dist/core/storage/skills.js +10 -2
- package/dist/core/storage/skills.js.map +1 -1
- package/dist/core/storage/sqlite.d.ts +5 -0
- package/dist/core/storage/sqlite.d.ts.map +1 -1
- package/dist/core/storage/sqlite.js +6 -0
- package/dist/core/storage/sqlite.js.map +1 -1
- package/dist/core/storage/tasks.d.ts.map +1 -1
- package/dist/core/storage/tasks.js +2 -0
- package/dist/core/storage/tasks.js.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +30 -5
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/skill-sync.d.ts +21 -0
- package/dist/daemon/skill-sync.d.ts.map +1 -0
- package/dist/daemon/skill-sync.js +75 -0
- package/dist/daemon/skill-sync.js.map +1 -0
- package/dist/hooks/notification.sh +1 -1
- package/dist/hooks/post-tool-use.sh +1 -1
- package/dist/hooks/pre-tool-use.sh +1 -1
- package/dist/hooks/stop.sh +1 -1
- package/dist/hooks/user-prompt-submit.sh +1 -1
- package/dist/skills/official/code-simplifier.md +37 -1
- package/dist/skills/official/find-skills.md +120 -1
- package/dist/skills/official/official-api-design.md +14 -1
- package/dist/skills/official/official-architecture-decision.md +22 -1
- package/dist/skills/official/official-db-schema-design.md +19 -1
- package/dist/skills/official/official-debug.md +9 -1
- package/dist/skills/official/official-pr-review.md +1 -1
- package/dist/skills/official/official-security-hardening.md +7 -1
- package/dist/skills/official/planning-with-files.md +206 -2
- package/dist/skills/official/ui-ux-pro-max.md +88 -1
- package/dist/skills/official/webapp-testing.md +85 -1
- package/dist/skills/registry.d.ts +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +15 -4
- package/dist/skills/registry.js.map +1 -1
- package/dist/skills/semantic-matcher.d.ts +4 -3
- package/dist/skills/semantic-matcher.d.ts.map +1 -1
- package/dist/skills/semantic-matcher.js +20 -22
- package/dist/skills/semantic-matcher.js.map +1 -1
- package/dist/skills/upgrade-engine.d.ts +93 -0
- package/dist/skills/upgrade-engine.d.ts.map +1 -0
- package/dist/skills/upgrade-engine.js +447 -0
- package/dist/skills/upgrade-engine.js.map +1 -0
- package/dist/skills/upgrade-prompt.d.ts +20 -0
- package/dist/skills/upgrade-prompt.d.ts.map +1 -0
- package/dist/skills/upgrade-prompt.js +75 -0
- package/dist/skills/upgrade-prompt.js.map +1 -0
- package/dist/web/analytics/weekly-report.d.ts.map +1 -1
- package/dist/web/analytics/weekly-report.js +21 -29
- package/dist/web/analytics/weekly-report.js.map +1 -1
- package/dist/web/routes/patch.d.ts.map +1 -1
- package/dist/web/routes/patch.js +32 -2
- package/dist/web/routes/patch.js.map +1 -1
- package/dist/web/routes/sessions.d.ts.map +1 -1
- package/dist/web/routes/sessions.js +9 -7
- package/dist/web/routes/sessions.js.map +1 -1
- package/dist/web/routes/trace.d.ts.map +1 -1
- package/dist/web/routes/trace.js +2 -3
- package/dist/web/routes/trace.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +3 -2
- package/dist/web/server.js.map +1 -1
- package/package.json +12 -2
- package/scripts/postinstall.cjs +21 -0
- package/.claude/CLAUDE.md +0 -17
- package/.eslintrc.js +0 -23
- package/.prettierrc +0 -8
- package/ARCHITECTURE_ISSUES.md +0 -249
- package/CLAUDE.md +0 -265
- package/CLAUDE.md.backup +0 -488
- package/docs/concurrent-agents.md +0 -129
- package/docs/design/architecture-review-20260516.md +0 -232
- package/docs/design/fix-skills-data-and-set-leak-spec-20260516-1300.md +0 -219
- package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +0 -299
- package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +0 -191
- package/docs/design/h3-fallback-removal-spec-20260518-1245.md +0 -76
- package/docs/design/h4-index-dedup-spec-20260518-1230.md +0 -109
- package/docs/design/h6-services-migration-spec-20260518-1355.md +0 -82
- package/docs/design/hook-failure-queue-spec-20260516-1530.md +0 -204
- package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +0 -106
- package/docs/design/m10-forge-paths-spec-20260518-1320.md +0 -121
- package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +0 -131
- package/docs/design/m7-routing-event-association-spec-20260518-1545.md +0 -103
- package/docs/design/project-path-gitroot-spec-20260518-1715.md +0 -134
- package/docs/design/refactor-phase1-spec-20260515-1600.md +0 -543
- package/docs/design/refactor-phase2-spec-20260515-1700.md +0 -424
- package/docs/design/task-active-gc-spec-20260518-1745.md +0 -146
- package/docs/design/tasks-list-filter-pagination-spec-20260518-0930.md +0 -208
- package/docs/implementation/fix-skills-data-and-set-leak-changelog-20260516-1300.md +0 -104
- package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +0 -82
- package/docs/implementation/h2-final-changelog-20260518-1530.md +0 -61
- package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +0 -70
- package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +0 -120
- package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +0 -71
- package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +0 -71
- package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +0 -60
- package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +0 -46
- package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +0 -46
- package/docs/implementation/hook-failure-queue-changelog-20260516-1530.md +0 -196
- package/docs/implementation/hotfix-daemon-event-reject-20260516-1430.md +0 -56
- package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +0 -45
- package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +0 -63
- package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +0 -38
- package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +0 -58
- package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +0 -60
- package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +0 -43
- package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +0 -56
- package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +0 -69
- package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +0 -63
- package/docs/implementation/refactor-phase1-changelog-20260515-1630.md +0 -354
- package/docs/implementation/refactor-phase2-changelog-20260515-1705.md +0 -421
- package/docs/implementation/task-active-gc-changelog-20260518-1745.md +0 -35
- package/docs/implementation/task-title-summary-changelog-20260518-1130.md +0 -39
- package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +0 -22
- package/docs/implementation/tasks-list-filter-pagination-changelog-20260518-0930.md +0 -72
- package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +0 -56
- package/docs/reviews/claudemd-template-sync.md +0 -54
- package/docs/reviews/task-title-summary.md +0 -92
- package/docs/reviews/tasks-detail-back-loses-filters.md +0 -58
- package/docs/reviews/tasks-filter-pagination.md +0 -80
- package/docs/reviews/tasks-page-white-screen-hotfix.md +0 -126
- package/docs/ruflo-learning-strategy.md +0 -322
- package/docs/skills-deduplication-analysis.md +0 -83
- package/docs/skills-multiformat-support.md +0 -177
- package/docs/skills-third-party.md +0 -183
- package/docs/testing/tasks-filter-pagination-test-report.md +0 -86
- package/forge +0 -321
- package/playwright.config.ts +0 -40
- package/scripts/demo-v2.ts +0 -91
- package/scripts/dev-daemon.sh +0 -232
- package/scripts/dev-web.ts +0 -109
- package/scripts/e2e-mcp-link.ts +0 -423
- package/scripts/e2e-methodology-quality.ts +0 -253
- package/scripts/e2e-routing.ts +0 -456
- package/scripts/e2e-user-methodology.ts +0 -326
- package/scripts/e2e-web-workflows.ts +0 -299
- package/scripts/migrate-legacy-to-dynamic.sql +0 -108
- package/scripts/regenerate-execution-docs.ts +0 -116
- package/scripts/sync-agent-skills.ts +0 -193
- package/scripts/test-hook.sh +0 -71
- package/scripts/verify-skill-loading.ts +0 -62
- package/src/claudemd/claudemd-generator.ts +0 -568
- package/src/claudemd/convention-extractor.ts +0 -69
- package/src/claudemd/index.ts +0 -35
- package/src/claudemd/persona-manager.ts +0 -88
- package/src/claudemd/resume-manager.ts +0 -236
- package/src/claudemd/tech-detector.ts +0 -220
- package/src/claudemd/templates/swarm-protocol.md +0 -222
- package/src/cli/commands/claudemd.ts +0 -84
- package/src/cli/commands/config.ts +0 -46
- package/src/cli/commands/daemon.ts +0 -310
- package/src/cli/commands/executions.ts +0 -115
- package/src/cli/commands/init.ts +0 -204
- package/src/cli/commands/logs.ts +0 -181
- package/src/cli/commands/mcp.ts +0 -242
- package/src/cli/commands/menu.ts +0 -357
- package/src/cli/commands/skills.ts +0 -185
- package/src/cli/commands/stats.ts +0 -73
- package/src/cli/commands/status.ts +0 -69
- package/src/cli/commands/template.ts +0 -77
- package/src/cli/commands/trace.ts +0 -148
- package/src/cli/index.ts +0 -42
- package/src/cli/init/hook-manager.ts +0 -132
- package/src/core/ai/provider.ts +0 -308
- package/src/core/ai/types.ts +0 -51
- package/src/core/config.ts +0 -124
- package/src/core/constants.ts +0 -62
- package/src/core/event-fields.ts +0 -32
- package/src/core/queue/index.ts +0 -192
- package/src/core/storage/base.ts +0 -302
- package/src/core/storage/events.ts +0 -434
- package/src/core/storage/injections.ts +0 -78
- package/src/core/storage/maintenance.ts +0 -59
- package/src/core/storage/migrations/002_add_skill_tracking.sql +0 -6
- package/src/core/storage/migrations/003_add_skill_invocations.sql +0 -23
- package/src/core/storage/performance-indexes.sql +0 -23
- package/src/core/storage/routing.ts +0 -322
- package/src/core/storage/rows.ts +0 -112
- package/src/core/storage/schema.sql +0 -224
- package/src/core/storage/sessions.ts +0 -168
- package/src/core/storage/skills.ts +0 -233
- package/src/core/storage/sqlite.ts +0 -293
- package/src/core/storage/tasks.ts +0 -318
- package/src/core/storage/token-usage.ts +0 -93
- package/src/core/types.ts +0 -181
- package/src/core/utils/error-handler.ts +0 -257
- package/src/core/utils/forge-resume-block.ts +0 -74
- package/src/core/utils/format.ts +0 -69
- package/src/core/utils/git.ts +0 -23
- package/src/core/utils/logger.ts +0 -134
- package/src/core/utils/lru-cache.ts +0 -54
- package/src/core/utils/path.ts +0 -19
- package/src/core/utils/session.ts +0 -26
- package/src/core/utils/time.ts +0 -37
- package/src/core/utils/token-tracker.ts +0 -97
- package/src/daemon/event-parser.ts +0 -36
- package/src/daemon/handlers/history-exporter.ts +0 -117
- package/src/daemon/handlers/post-tool-use.ts +0 -54
- package/src/daemon/handlers/stop.ts +0 -208
- package/src/daemon/handlers/user-prompt.ts +0 -178
- package/src/daemon/hook-sync.ts +0 -91
- package/src/daemon/index.ts +0 -302
- package/src/daemon/launchd/com.claude-forge.daemon.plist.template +0 -47
- package/src/daemon/launchd-installer.ts +0 -260
- package/src/daemon/lifecycle.ts +0 -128
- package/src/daemon/router.ts +0 -40
- package/src/daemon/server.ts +0 -196
- package/src/daemon/services/task-segmenter.ts +0 -112
- package/src/hooks/hook-lib.sh +0 -118
- package/src/hooks/notification.sh +0 -35
- package/src/hooks/post-tool-use.sh +0 -61
- package/src/hooks/pre-tool-use.sh +0 -63
- package/src/hooks/stop.sh +0 -43
- package/src/hooks/user-prompt-submit.sh +0 -69
- package/src/mcp/server.ts +0 -322
- package/src/skills/index.ts +0 -2
- package/src/skills/invocation-guard.ts +0 -177
- package/src/skills/matcher.ts +0 -148
- package/src/skills/official/code-simplifier.md +0 -16
- package/src/skills/official/find-skills.md +0 -23
- package/src/skills/official/official-api-design.md +0 -17
- package/src/skills/official/official-architecture-decision.md +0 -20
- package/src/skills/official/official-bmad.md +0 -118
- package/src/skills/official/official-db-schema-design.md +0 -16
- package/src/skills/official/official-debug.md +0 -17
- package/src/skills/official/official-doc-driven.md +0 -31
- package/src/skills/official/official-harness-engineering.md +0 -108
- package/src/skills/official/official-performance-optimization.md +0 -30
- package/src/skills/official/official-pr-review.md +0 -35
- package/src/skills/official/official-release-checklist.md +0 -30
- package/src/skills/official/official-security-hardening.md +0 -26
- package/src/skills/official/official-spec-driven-design.md +0 -31
- package/src/skills/official/planning-with-files.md +0 -37
- package/src/skills/official/ui-ux-pro-max.md +0 -18
- package/src/skills/official/webapp-testing.md +0 -12
- package/src/skills/official-skills.ts +0 -89
- package/src/skills/registry.ts +0 -355
- package/src/skills/semantic-matcher.ts +0 -231
- package/src/skills/tools/pipeline-suggest.ts +0 -226
- package/src/skills/tools/skill-invoke.ts +0 -168
- package/src/skills/tools/skill-list.ts +0 -59
- package/src/templates/go.yaml +0 -53
- package/src/templates/python.yaml +0 -59
- package/src/templates/react.yaml +0 -55
- package/src/templates/template-manager.ts +0 -170
- package/src/web/analytics/anti-pattern-detector.ts +0 -367
- package/src/web/analytics/drift-detector.ts +0 -219
- package/src/web/analytics/weekly-report.ts +0 -431
- package/src/web/auth-middleware.ts +0 -54
- package/src/web/routes/_helpers.ts +0 -34
- package/src/web/routes/ai.ts +0 -204
- package/src/web/routes/auth.ts +0 -22
- package/src/web/routes/drift.ts +0 -25
- package/src/web/routes/error-handler.ts +0 -120
- package/src/web/routes/events.ts +0 -47
- package/src/web/routes/insights.ts +0 -43
- package/src/web/routes/patch.ts +0 -117
- package/src/web/routes/reports.ts +0 -34
- package/src/web/routes/rules.ts +0 -76
- package/src/web/routes/sessions.ts +0 -250
- package/src/web/routes/skill-stats.ts +0 -92
- package/src/web/routes/skills.ts +0 -350
- package/src/web/routes/static.ts +0 -67
- package/src/web/routes/stats.ts +0 -50
- package/src/web/routes/status.ts +0 -30
- package/src/web/routes/tasks.ts +0 -193
- package/src/web/routes/token-usage.ts +0 -20
- package/src/web/routes/trace.ts +0 -126
- package/src/web/routes/types.ts +0 -57
- package/src/web/server.ts +0 -134
- package/src/web/ssrf-guard.ts +0 -112
- package/src/web/static/index.html +0 -3251
- package/src/web/static/vendor/chart.umd.min.js +0 -20
- package/tests/e2e/dashboard.spec.ts +0 -205
- package/tests/e2e/routing-skill-e2e.test.ts +0 -39
- package/tests/helpers/mock-ai.ts +0 -92
- package/tests/helpers/mock-storage.ts +0 -159
- package/tests/integration/claudemd-generator.test.ts +0 -90
- package/tests/integration/queue-replay.integration.test.ts +0 -193
- package/tests/integration/tasks-filter.integration.test.ts +0 -154
- package/tests/integration/web-analytics.integration.test.ts +0 -133
- package/tests/integration/web-stats.integration.test.ts +0 -135
- package/tests/integration/web-trace.integration.test.ts +0 -175
- package/tests/performance/database.benchmark.ts +0 -161
- package/tests/semantic-matcher.test.ts +0 -99
- package/tests/skill-matcher.test.ts +0 -110
- package/tests/unit/ai-provider-retry.test.ts +0 -194
- package/tests/unit/ai-provider-vision.test.ts +0 -224
- package/tests/unit/claudemd-generator.test.ts +0 -68
- package/tests/unit/cli-mcp.test.ts +0 -141
- package/tests/unit/core/forge-paths.test.ts +0 -99
- package/tests/unit/daemon/hook-sync.test.ts +0 -71
- package/tests/unit/daemon/post-tool-use.test.ts +0 -121
- package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +0 -202
- package/tests/unit/daemon/task-segmenter-recover.test.ts +0 -84
- package/tests/unit/event-fields.test.ts +0 -88
- package/tests/unit/event-parser.test.ts +0 -55
- package/tests/unit/handlers.test.ts +0 -171
- package/tests/unit/hooks/resolve-project-path.test.ts +0 -122
- package/tests/unit/invocation-guard.test.ts +0 -125
- package/tests/unit/queue.test.ts +0 -272
- package/tests/unit/router.test.ts +0 -138
- package/tests/unit/security.test.ts +0 -128
- package/tests/unit/skill-invocations-workflow.test.ts +0 -495
- package/tests/unit/skill-registry.test.ts +0 -94
- package/tests/unit/skills/invocation-guard-ttl.test.ts +0 -211
- package/tests/unit/skills/official-skills-loader.test.ts +0 -126
- package/tests/unit/skills/registry-multiformat.test.ts +0 -92
- package/tests/unit/socket-server.test.ts +0 -183
- package/tests/unit/storage/event-operations-aggregates.test.ts +0 -342
- package/tests/unit/storage/migration-idempotent.test.ts +0 -304
- package/tests/unit/storage/routing-aggregates.test.ts +0 -276
- package/tests/unit/storage/routing.test.ts +0 -117
- package/tests/unit/storage/schema-missing.test.ts +0 -81
- package/tests/unit/storage/session-operations-aggregates.test.ts +0 -120
- package/tests/unit/storage/sessions-aggregate.test.ts +0 -435
- package/tests/unit/storage/skill-operations-counts.test.ts +0 -106
- package/tests/unit/storage/skills-aggregates.test.ts +0 -104
- package/tests/unit/storage/sqlite-refactor-harness.test.ts +0 -314
- package/tests/unit/storage/task-operations-counts.test.ts +0 -46
- package/tests/unit/storage/tasks-getById.test.ts +0 -343
- package/tests/unit/storage/tasks-stale-gc.test.ts +0 -86
- package/tests/unit/storage.test.ts +0 -172
- package/tests/unit/token-usage.test.ts +0 -144
- package/tests/unit/type-guards.test.ts +0 -201
- package/tests/unit/utils/format.test.ts +0 -189
- package/tests/unit/utils/session.test.ts +0 -89
- package/tests/unit/utils/time.test.ts +0 -112
- package/tests/unit/web/navigation-back-contract.test.ts +0 -134
- package/tests/unit/web/routes-auth.test.ts +0 -93
- package/tests/unit/web/routes-events.test.ts +0 -101
- package/tests/unit/web/routes-rules.test.ts +0 -182
- package/tests/unit/web/routes-sessions.test.ts +0 -181
- package/tests/unit/web/routes-skill-stats.test.ts +0 -179
- package/tests/unit/web/routes-stats.test.ts +0 -92
- package/tests/unit/web/routes-tasks.test.ts +0 -385
- package/tests/unit/web/task-title-contract.test.ts +0 -210
- package/tests/unit/web/tasks-component-contract.test.ts +0 -179
- package/tsconfig.json +0 -22
- package/vitest.config.ts +0 -21
- package/vitest.integration.config.ts +0 -16
- package/web/CLAUDE.md +0 -20
- package/web/index.html +0 -13
- package/web/package-lock.json +0 -4854
- package/web/package.json +0 -35
- package/web/postcss.config.js +0 -6
- package/web/src/App.tsx +0 -110
- package/web/src/components/CodeBlock.tsx +0 -31
- package/web/src/components/Confirm.tsx +0 -96
- package/web/src/components/Drawer.tsx +0 -60
- package/web/src/components/Layout.tsx +0 -145
- package/web/src/components/MarkdownRenderer.tsx +0 -77
- package/web/src/components/SearchInput.tsx +0 -31
- package/web/src/components/SessionDetailContent.tsx +0 -157
- package/web/src/components/Toast.tsx +0 -92
- package/web/src/index.css +0 -19
- package/web/src/main.tsx +0 -31
- package/web/src/pages/AIConfig.tsx +0 -233
- package/web/src/pages/Dashboard.tsx +0 -572
- package/web/src/pages/Events.tsx +0 -271
- package/web/src/pages/Reports.tsx +0 -428
- package/web/src/pages/SessionDetail.tsx +0 -162
- package/web/src/pages/Sessions.tsx +0 -205
- package/web/src/pages/Skills.tsx +0 -180
- package/web/src/pages/TaskDetail.tsx +0 -515
- package/web/src/pages/Tasks.tsx +0 -415
- package/web/src/utils/auth.ts +0 -59
- package/web/src/utils/export.ts +0 -54
- package/web/src/utils/navigation.ts +0 -25
- package/web/src/utils/task-title.ts +0 -49
- package/web/src/utils/time.ts +0 -13
- package/web/tailwind.config.js +0 -11
- package/web/tsconfig.json +0 -21
- package/web/tsconfig.node.json +0 -10
- package/web/vite.config.ts +0 -76
- package/winspan-claude-forge-8.43.0.tgz +0 -0
package/src/web/routes/ai.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import type { Application } from 'express';
|
|
2
|
-
import { ConfigManager } from '../../core/config.js';
|
|
3
|
-
import { logger } from '../../core/utils/logger.js';
|
|
4
|
-
import {
|
|
5
|
-
validateBaseUrl,
|
|
6
|
-
extractHostFromBaseUrl,
|
|
7
|
-
UPSTREAM_TIMEOUT_MS,
|
|
8
|
-
} from '../ssrf-guard.js';
|
|
9
|
-
import type { RouteContext } from './types.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* /api/ai/* and /api/config/ai — AI provider config + upstream connectivity.
|
|
13
|
-
*
|
|
14
|
-
* All outbound calls route through validateBaseUrl + redirect:'manual' so the
|
|
15
|
-
* daemon cannot be tricked into reaching internal hosts (SSRF guard).
|
|
16
|
-
*/
|
|
17
|
-
export function registerAIRoutes(app: Application, _ctx: RouteContext): void {
|
|
18
|
-
// GET /api/config/ai — read current AI config
|
|
19
|
-
app.get('/api/config/ai', (_req, res) => {
|
|
20
|
-
const configManager = new ConfigManager();
|
|
21
|
-
const config = configManager.get();
|
|
22
|
-
|
|
23
|
-
res.json({
|
|
24
|
-
api_key: config.ai.api_key || '',
|
|
25
|
-
base_url: config.ai.base_url || '',
|
|
26
|
-
model: config.ai.model,
|
|
27
|
-
provider: config.ai.provider,
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// PUT /api/config/ai — update AI config
|
|
32
|
-
app.put('/api/config/ai', (req, res) => {
|
|
33
|
-
const { api_key, base_url, model, provider } = req.body ?? {};
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const configManager = new ConfigManager();
|
|
37
|
-
const config = configManager.get();
|
|
38
|
-
|
|
39
|
-
if (typeof api_key === 'string') config.ai.api_key = api_key;
|
|
40
|
-
if (typeof base_url === 'string') {
|
|
41
|
-
// Normalize http → https for upstream gateways that force HTTPS redirect
|
|
42
|
-
// (e.g. iflytek one.iflytek.com returns 302 and drops Authorization header)
|
|
43
|
-
let url = base_url.trim();
|
|
44
|
-
if (url.startsWith('http://')) url = 'https://' + url.slice('http://'.length);
|
|
45
|
-
config.ai.base_url = url;
|
|
46
|
-
}
|
|
47
|
-
if (typeof model === 'string') config.ai.model = model;
|
|
48
|
-
if (typeof provider === 'string') config.ai.provider = provider;
|
|
49
|
-
|
|
50
|
-
configManager.save();
|
|
51
|
-
logger.info('[Web] AI config updated');
|
|
52
|
-
res.json({ success: true });
|
|
53
|
-
} catch (err) {
|
|
54
|
-
logger.warn(`[Web] Failed to update AI config: ${err}`);
|
|
55
|
-
res.status(500).json({ error: String(err) });
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// GET /api/ai/models — proxy to upstream /v1/models
|
|
60
|
-
app.get('/api/ai/models', async (req, res) => {
|
|
61
|
-
try {
|
|
62
|
-
const configManager = new ConfigManager();
|
|
63
|
-
const config = configManager.get();
|
|
64
|
-
|
|
65
|
-
const queryKey = typeof req.query.api_key === 'string' ? req.query.api_key : '';
|
|
66
|
-
const queryUrl = typeof req.query.base_url === 'string' ? req.query.base_url : '';
|
|
67
|
-
const apiKey = queryKey || config.ai.api_key;
|
|
68
|
-
let baseUrl = queryUrl || config.ai.base_url || 'https://api.anthropic.com';
|
|
69
|
-
if (baseUrl.startsWith('http://')) baseUrl = 'https://' + baseUrl.slice('http://'.length);
|
|
70
|
-
|
|
71
|
-
if (!apiKey) {
|
|
72
|
-
res.status(400).json({ error: 'API key not configured' });
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const configuredHost = extractHostFromBaseUrl(config.ai.base_url);
|
|
77
|
-
const validation = validateBaseUrl(baseUrl, configuredHost ? [configuredHost] : []);
|
|
78
|
-
if (!validation.ok) {
|
|
79
|
-
res.status(400).json({ error: validation.error });
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const modelsUrl = baseUrl.endsWith('/v1')
|
|
84
|
-
? `${baseUrl}/models`
|
|
85
|
-
: `${baseUrl}/v1/models`;
|
|
86
|
-
|
|
87
|
-
const controller = new AbortController();
|
|
88
|
-
const timer = setTimeout(() => controller.abort(), UPSTREAM_TIMEOUT_MS);
|
|
89
|
-
let response: Response;
|
|
90
|
-
try {
|
|
91
|
-
response = await fetch(modelsUrl, {
|
|
92
|
-
redirect: 'manual',
|
|
93
|
-
signal: controller.signal,
|
|
94
|
-
headers: {
|
|
95
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
96
|
-
'x-api-key': apiKey,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
} finally {
|
|
100
|
-
clearTimeout(timer);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (response.status >= 300 && response.status < 400) {
|
|
104
|
-
res.status(400).json({
|
|
105
|
-
error: `Upstream tried to redirect (status ${response.status}); refusing to follow`,
|
|
106
|
-
});
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
const text = await response.text();
|
|
112
|
-
logger.warn(`[Web] Upstream /v1/models failed: ${response.status} ${text}`);
|
|
113
|
-
res.status(response.status).json({ error: text });
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const data = await response.json();
|
|
118
|
-
res.json(data);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
if (err instanceof Error && err.name === 'AbortError') {
|
|
121
|
-
res.status(504).json({ error: `Upstream timeout (${UPSTREAM_TIMEOUT_MS}ms)` });
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
logger.warn(`[Web] Failed to fetch models: ${err}`);
|
|
125
|
-
res.status(500).json({ error: String(err) });
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// POST /api/ai/test — test AI connection using provided or saved config
|
|
130
|
-
app.post('/api/ai/test', async (req, res) => {
|
|
131
|
-
try {
|
|
132
|
-
const configManager = new ConfigManager();
|
|
133
|
-
const config = configManager.get();
|
|
134
|
-
|
|
135
|
-
const { api_key, base_url, model } = req.body ?? {};
|
|
136
|
-
const apiKey = (typeof api_key === 'string' && api_key) || config.ai.api_key;
|
|
137
|
-
let baseUrl = (typeof base_url === 'string' && base_url) || config.ai.base_url || 'https://api.anthropic.com';
|
|
138
|
-
const useModel = (typeof model === 'string' && model) || config.ai.model;
|
|
139
|
-
if (baseUrl.startsWith('http://')) baseUrl = 'https://' + baseUrl.slice('http://'.length);
|
|
140
|
-
|
|
141
|
-
if (!apiKey) {
|
|
142
|
-
res.status(400).json({ error: 'API key not configured' });
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const configuredHost = extractHostFromBaseUrl(config.ai.base_url);
|
|
147
|
-
const validation = validateBaseUrl(baseUrl, configuredHost ? [configuredHost] : []);
|
|
148
|
-
if (!validation.ok) {
|
|
149
|
-
res.status(400).json({ error: validation.error });
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const messagesUrl = baseUrl.endsWith('/v1')
|
|
154
|
-
? `${baseUrl}/messages`
|
|
155
|
-
: `${baseUrl}/v1/messages`;
|
|
156
|
-
|
|
157
|
-
const controller = new AbortController();
|
|
158
|
-
const timer = setTimeout(() => controller.abort(), UPSTREAM_TIMEOUT_MS);
|
|
159
|
-
let response: Response;
|
|
160
|
-
try {
|
|
161
|
-
response = await fetch(messagesUrl, {
|
|
162
|
-
method: 'POST',
|
|
163
|
-
redirect: 'manual',
|
|
164
|
-
signal: controller.signal,
|
|
165
|
-
headers: {
|
|
166
|
-
'Content-Type': 'application/json',
|
|
167
|
-
'x-api-key': apiKey,
|
|
168
|
-
'anthropic-version': '2023-06-01',
|
|
169
|
-
},
|
|
170
|
-
body: JSON.stringify({
|
|
171
|
-
model: useModel,
|
|
172
|
-
max_tokens: 10,
|
|
173
|
-
messages: [{ role: 'user', content: 'ping' }],
|
|
174
|
-
}),
|
|
175
|
-
});
|
|
176
|
-
} finally {
|
|
177
|
-
clearTimeout(timer);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (response.status >= 300 && response.status < 400) {
|
|
181
|
-
res.status(400).json({
|
|
182
|
-
error: `Upstream tried to redirect (status ${response.status}); refusing to follow`,
|
|
183
|
-
});
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (!response.ok) {
|
|
188
|
-
const text = await response.text();
|
|
189
|
-
res.status(response.status).json({ error: text });
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const data = await response.json() as { model?: string };
|
|
194
|
-
res.json({ success: true, model: data.model || useModel });
|
|
195
|
-
} catch (err) {
|
|
196
|
-
if (err instanceof Error && err.name === 'AbortError') {
|
|
197
|
-
res.status(504).json({ error: `Upstream timeout (${UPSTREAM_TIMEOUT_MS}ms)` });
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
logger.warn(`[Web] AI connection test failed: ${err}`);
|
|
201
|
-
res.status(500).json({ error: String(err) });
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
}
|
package/src/web/routes/auth.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { Application } from 'express';
|
|
2
|
-
import { readAuthToken } from '../auth-middleware.js';
|
|
3
|
-
import type { RouteContext } from './types.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Auth routes — localhost bootstrap endpoint that returns the daemon token.
|
|
7
|
-
*
|
|
8
|
-
* This endpoint is intentionally NOT protected by requireAuth: the token file
|
|
9
|
-
* on disk is chmod 600 (current user only), so anyone who can reach this
|
|
10
|
-
* endpoint already has access to the file. Protecting it would create a
|
|
11
|
-
* chicken-and-egg problem for the UI.
|
|
12
|
-
*/
|
|
13
|
-
export function registerAuthRoutes(app: Application, _ctx: RouteContext): void {
|
|
14
|
-
app.get('/api/auth/token', (_req, res) => {
|
|
15
|
-
const token = readAuthToken();
|
|
16
|
-
if (!token) {
|
|
17
|
-
res.status(404).json({ error: 'TOKEN_NOT_FOUND' });
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
res.json({ token });
|
|
21
|
-
});
|
|
22
|
-
}
|
package/src/web/routes/drift.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Application } from 'express';
|
|
2
|
-
import type { RouteContext } from './types.js';
|
|
3
|
-
import { DriftDetector } from '../analytics/drift-detector.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* /api/drift/* — CLAUDE.md 漂移检测 API
|
|
7
|
-
*
|
|
8
|
-
* Provides compliance drift report comparing declared behavior
|
|
9
|
-
* in CLAUDE.md against actual event stream data.
|
|
10
|
-
*/
|
|
11
|
-
export function registerDriftRoutes(app: Application, ctx: RouteContext): void {
|
|
12
|
-
const { storage } = ctx;
|
|
13
|
-
|
|
14
|
-
app.get('/api/drift/report', (req, res) => {
|
|
15
|
-
const days = parseInt((req.query.days as string) || '7');
|
|
16
|
-
if (isNaN(days) || days < 1 || days > 90) {
|
|
17
|
-
res.status(400).json({ error: 'days must be between 1 and 90' });
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const detector = new DriftDetector(storage);
|
|
22
|
-
const report = detector.run(days);
|
|
23
|
-
res.json(report);
|
|
24
|
-
});
|
|
25
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { logger } from '../../core/utils/logger.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Error types for classification
|
|
6
|
-
*/
|
|
7
|
-
export enum ErrorType {
|
|
8
|
-
VALIDATION = 'validation',
|
|
9
|
-
NOT_FOUND = 'not_found',
|
|
10
|
-
UNAUTHORIZED = 'unauthorized',
|
|
11
|
-
FORBIDDEN = 'forbidden',
|
|
12
|
-
INTERNAL = 'internal',
|
|
13
|
-
BAD_REQUEST = 'bad_request',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Structured error response
|
|
18
|
-
*/
|
|
19
|
-
export interface ErrorResponse {
|
|
20
|
-
error: string;
|
|
21
|
-
type: ErrorType;
|
|
22
|
-
details?: unknown;
|
|
23
|
-
timestamp: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Custom error class with type information
|
|
28
|
-
*/
|
|
29
|
-
export class AppError extends Error {
|
|
30
|
-
constructor(
|
|
31
|
-
message: string,
|
|
32
|
-
public type: ErrorType = ErrorType.INTERNAL,
|
|
33
|
-
public statusCode: number = 500,
|
|
34
|
-
public details?: unknown,
|
|
35
|
-
) {
|
|
36
|
-
super(message);
|
|
37
|
-
this.name = 'AppError';
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Global error handler middleware
|
|
43
|
-
*/
|
|
44
|
-
export function errorHandler(
|
|
45
|
-
err: Error | AppError,
|
|
46
|
-
req: Request,
|
|
47
|
-
res: Response,
|
|
48
|
-
_next: NextFunction,
|
|
49
|
-
): void {
|
|
50
|
-
// Log error
|
|
51
|
-
const errorMessage = err.message;
|
|
52
|
-
const errorStack = err.stack;
|
|
53
|
-
|
|
54
|
-
logger.error(`[Web] ${req.method} ${req.path} - ${errorMessage}`);
|
|
55
|
-
if (errorStack) {
|
|
56
|
-
logger.error(`[Web] Stack trace: ${errorStack}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Determine error type and status code
|
|
60
|
-
let statusCode = 500;
|
|
61
|
-
let errorType = ErrorType.INTERNAL;
|
|
62
|
-
let details: unknown = undefined;
|
|
63
|
-
|
|
64
|
-
if (err instanceof AppError) {
|
|
65
|
-
statusCode = err.statusCode;
|
|
66
|
-
errorType = err.type;
|
|
67
|
-
details = err.details;
|
|
68
|
-
} else if (err.name === 'ValidationError') {
|
|
69
|
-
statusCode = 400;
|
|
70
|
-
errorType = ErrorType.VALIDATION;
|
|
71
|
-
} else if (err.message.includes('not found')) {
|
|
72
|
-
statusCode = 404;
|
|
73
|
-
errorType = ErrorType.NOT_FOUND;
|
|
74
|
-
} else if (err.message.includes('unauthorized') || err.message.includes('authentication')) {
|
|
75
|
-
statusCode = 401;
|
|
76
|
-
errorType = ErrorType.UNAUTHORIZED;
|
|
77
|
-
} else if (err.message.includes('forbidden') || err.message.includes('permission')) {
|
|
78
|
-
statusCode = 403;
|
|
79
|
-
errorType = ErrorType.FORBIDDEN;
|
|
80
|
-
} else if (err.message.includes('Invalid') || err.message.includes('invalid')) {
|
|
81
|
-
statusCode = 400;
|
|
82
|
-
errorType = ErrorType.BAD_REQUEST;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Send error response
|
|
86
|
-
const response: ErrorResponse = {
|
|
87
|
-
error: err.message,
|
|
88
|
-
type: errorType,
|
|
89
|
-
timestamp: new Date().toISOString(),
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
if (details) {
|
|
93
|
-
response.details = details;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
res.status(statusCode).json(response);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 404 handler for undefined routes
|
|
101
|
-
*/
|
|
102
|
-
export function notFoundHandler(req: Request, res: Response): void {
|
|
103
|
-
logger.warn(`[Web] 404 - ${req.method} ${req.path}`);
|
|
104
|
-
res.status(404).json({
|
|
105
|
-
error: `Route not found: ${req.method} ${req.path}`,
|
|
106
|
-
type: ErrorType.NOT_FOUND,
|
|
107
|
-
timestamp: new Date().toISOString(),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Async route handler wrapper to catch errors
|
|
113
|
-
*/
|
|
114
|
-
export function asyncHandler(
|
|
115
|
-
fn: (req: Request, res: Response, next: NextFunction) => Promise<void>,
|
|
116
|
-
) {
|
|
117
|
-
return (req: Request, res: Response, next: NextFunction): void => {
|
|
118
|
-
Promise.resolve(fn(req, res, next)).catch(next);
|
|
119
|
-
};
|
|
120
|
-
}
|
package/src/web/routes/events.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { Application } from 'express';
|
|
2
|
-
import type { RouteContext } from './types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* /api/events — recent events list + SSE live stream.
|
|
6
|
-
*
|
|
7
|
-
* Note: /api/events/stream must be registered BEFORE `/api/events` handlers
|
|
8
|
-
* that might swallow it, but Express matches exact paths so order here is
|
|
9
|
-
* not actually load-bearing for correctness.
|
|
10
|
-
*/
|
|
11
|
-
export function registerEventsRoutes(app: Application, ctx: RouteContext): void {
|
|
12
|
-
const { storage } = ctx;
|
|
13
|
-
|
|
14
|
-
app.get('/api/events', (req, res) => {
|
|
15
|
-
const limit = parseInt(req.query.limit as string) || 50;
|
|
16
|
-
const projectPath = req.query.project as string | undefined;
|
|
17
|
-
const sessionId = req.query.session as string | undefined;
|
|
18
|
-
const events = storage.queryEvents({ project_path: projectPath, session_id: sessionId, limit });
|
|
19
|
-
res.json(events);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// SSE: real-time event stream
|
|
23
|
-
app.get('/api/events/stream', (req, res) => {
|
|
24
|
-
res.writeHead(200, {
|
|
25
|
-
'Content-Type': 'text/event-stream',
|
|
26
|
-
'Cache-Control': 'no-cache',
|
|
27
|
-
Connection: 'keep-alive',
|
|
28
|
-
});
|
|
29
|
-
res.write('data: {"type":"connected"}\n\n');
|
|
30
|
-
|
|
31
|
-
const filterSession = req.query.session as string | undefined;
|
|
32
|
-
const filterProject = req.query.project as string | undefined;
|
|
33
|
-
const filterHook = req.query.hook as string | undefined;
|
|
34
|
-
|
|
35
|
-
const onEvent = (event: Record<string, unknown>) => {
|
|
36
|
-
if (filterSession && event.session_id !== filterSession) return;
|
|
37
|
-
if (filterProject && event.project_path !== filterProject) return;
|
|
38
|
-
if (filterHook && event.hook_type !== filterHook) return;
|
|
39
|
-
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
storage.on('event', onEvent);
|
|
43
|
-
req.on('close', () => {
|
|
44
|
-
storage.removeListener('event', onEvent);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Application } from 'express';
|
|
2
|
-
import type { RouteContext } from './types.js';
|
|
3
|
-
import {
|
|
4
|
-
AntiPatternDetector,
|
|
5
|
-
type AntiPattern,
|
|
6
|
-
type AntiPatternSeverity,
|
|
7
|
-
} from '../analytics/anti-pattern-detector.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* /api/insights — 工作流反模式检测 API
|
|
11
|
-
*
|
|
12
|
-
* 扫描事件流,识别可操作的反模式(编辑循环 / 工具失败 / 无 commit / 事件爆量)。
|
|
13
|
-
*/
|
|
14
|
-
export function registerInsightsRoutes(app: Application, ctx: RouteContext): void {
|
|
15
|
-
const { storage } = ctx;
|
|
16
|
-
|
|
17
|
-
app.get('/api/insights', (req, res) => {
|
|
18
|
-
const raw = req.query.days;
|
|
19
|
-
const days = parseInt(typeof raw === 'string' ? raw : '7', 10);
|
|
20
|
-
if (Number.isNaN(days) || days < 1 || days > 90) {
|
|
21
|
-
res.status(400).json({ error: 'days must be between 1 and 90' });
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const detector = new AntiPatternDetector(storage);
|
|
26
|
-
const patterns = detector.detectAll(days);
|
|
27
|
-
|
|
28
|
-
const summary: Record<AntiPatternSeverity, number> & { total: number } = {
|
|
29
|
-
total: patterns.length,
|
|
30
|
-
critical: 0,
|
|
31
|
-
warn: 0,
|
|
32
|
-
info: 0,
|
|
33
|
-
};
|
|
34
|
-
for (const p of patterns) summary[p.severity] += 1;
|
|
35
|
-
|
|
36
|
-
res.json({
|
|
37
|
-
generatedAt: new Date().toISOString(),
|
|
38
|
-
windowDays: days,
|
|
39
|
-
summary,
|
|
40
|
-
patterns: patterns satisfies AntiPattern[],
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
}
|
package/src/web/routes/patch.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import type { Application } from 'express';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { ConfigManager } from '../../core/config.js';
|
|
5
|
-
import { ClaudeProvider } from '../../core/ai/provider.js';
|
|
6
|
-
import { logger } from '../../core/utils/logger.js';
|
|
7
|
-
import type { RouteContext } from './types.js';
|
|
8
|
-
import { resolvePatchTarget } from './types.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* /api/patch/* — AI-assisted patch preview + apply (with backup).
|
|
12
|
-
*/
|
|
13
|
-
export function registerPatchRoutes(app: Application, _ctx: RouteContext): void {
|
|
14
|
-
// POST /api/patch/preview — generate structured patch preview
|
|
15
|
-
app.post('/api/patch/preview', async (req, res) => {
|
|
16
|
-
const { targetType, targetName, recommendation, rationale } = req.body ?? {};
|
|
17
|
-
if (!targetType || !targetName || !recommendation) {
|
|
18
|
-
res.status(400).json({ error: 'targetType, targetName, and recommendation are required' });
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const config = new ConfigManager().get();
|
|
24
|
-
const apiKey = config.ai.api_key || process.env.ANTHROPIC_API_KEY || '';
|
|
25
|
-
if (!apiKey) {
|
|
26
|
-
res.status(400).json({ error: 'AI API key not configured' });
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const { filePath } = resolvePatchTarget(targetType, targetName);
|
|
31
|
-
if (!fs.existsSync(filePath)) {
|
|
32
|
-
res.status(404).json({ error: `Target file not found: ${filePath}` });
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const currentContent = fs.readFileSync(filePath, 'utf-8');
|
|
37
|
-
const ai = new ClaudeProvider(apiKey, config.ai.model, config.ai.base_url);
|
|
38
|
-
|
|
39
|
-
const prompt = `You are a code/config optimization assistant. Given the current file content and a recommended change, generate the updated content.
|
|
40
|
-
|
|
41
|
-
Current file (${targetType}/${targetName}):
|
|
42
|
-
\`\`\`
|
|
43
|
-
${currentContent}
|
|
44
|
-
\`\`\`
|
|
45
|
-
|
|
46
|
-
Recommendation: ${recommendation}
|
|
47
|
-
${rationale ? `Rationale: ${rationale}` : ''}
|
|
48
|
-
|
|
49
|
-
Return ONLY a JSON object with this exact structure (no markdown, no explanation):
|
|
50
|
-
{
|
|
51
|
-
"summary": "brief description of what changed",
|
|
52
|
-
"afterContent": "the complete updated file content",
|
|
53
|
-
"risk": "low|medium|high"
|
|
54
|
-
}`;
|
|
55
|
-
|
|
56
|
-
const response = await ai.complete(prompt, { maxTokens: 4096 });
|
|
57
|
-
const parsed = JSON.parse(response.trim());
|
|
58
|
-
|
|
59
|
-
res.json({
|
|
60
|
-
targetType,
|
|
61
|
-
targetName,
|
|
62
|
-
filePath,
|
|
63
|
-
before: currentContent,
|
|
64
|
-
after: parsed.afterContent,
|
|
65
|
-
summary: parsed.summary,
|
|
66
|
-
risk: parsed.risk || 'medium',
|
|
67
|
-
recommendation,
|
|
68
|
-
rationale: rationale || null,
|
|
69
|
-
});
|
|
70
|
-
} catch (err) {
|
|
71
|
-
logger.warn(`[Web] Patch preview failed: ${err}`);
|
|
72
|
-
res.status(500).json({ error: String(err) });
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// POST /api/patch/apply — apply patch with backup
|
|
77
|
-
app.post('/api/patch/apply', (req, res) => {
|
|
78
|
-
const { targetType, targetName, afterContent } = req.body ?? {};
|
|
79
|
-
if (!targetType || !targetName || typeof afterContent !== 'string') {
|
|
80
|
-
res.status(400).json({ error: 'targetType, targetName, and afterContent are required' });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const { filePath, backupDir } = resolvePatchTarget(targetType, targetName);
|
|
86
|
-
if (!fs.existsSync(filePath)) {
|
|
87
|
-
res.status(404).json({ error: `Target file not found: ${filePath}` });
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Create backup
|
|
92
|
-
if (!fs.existsSync(backupDir)) {
|
|
93
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
94
|
-
}
|
|
95
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
96
|
-
const backupName = `${targetName}_${timestamp}${path.extname(filePath)}`;
|
|
97
|
-
const backupPath = path.join(backupDir, backupName);
|
|
98
|
-
fs.copyFileSync(filePath, backupPath);
|
|
99
|
-
|
|
100
|
-
// Apply patch
|
|
101
|
-
fs.writeFileSync(filePath, afterContent, 'utf-8');
|
|
102
|
-
|
|
103
|
-
logger.info(`[Web] Patch applied to ${targetType}/${targetName}, backup: ${backupPath}`);
|
|
104
|
-
res.json({
|
|
105
|
-
success: true,
|
|
106
|
-
targetType,
|
|
107
|
-
targetName,
|
|
108
|
-
filePath,
|
|
109
|
-
backupPath,
|
|
110
|
-
timestamp,
|
|
111
|
-
});
|
|
112
|
-
} catch (err) {
|
|
113
|
-
logger.warn(`[Web] Patch apply failed: ${err}`);
|
|
114
|
-
res.status(500).json({ error: String(err) });
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /api/reports/weekly — Weekly work report
|
|
3
|
-
*
|
|
4
|
-
* Aggregates the last week's activity into a structured report. Supports
|
|
5
|
-
* both JSON (default) and Markdown output formats.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Application } from 'express';
|
|
9
|
-
import type { RouteContext } from './types.js';
|
|
10
|
-
import { WeeklyReportGenerator } from '../analytics/weekly-report.js';
|
|
11
|
-
|
|
12
|
-
export function registerReportsRoutes(app: Application, ctx: RouteContext): void {
|
|
13
|
-
const { storage, skillRegistry } = ctx;
|
|
14
|
-
|
|
15
|
-
app.get('/api/reports/weekly', (req, res) => {
|
|
16
|
-
const weekOffsetRaw = (req.query.weekOffset as string) ?? '0';
|
|
17
|
-
const weekOffset = Number.isFinite(Number(weekOffsetRaw)) ? Number(weekOffsetRaw) : 0;
|
|
18
|
-
const format = (req.query.format as string) === 'markdown' ? 'markdown' : 'json';
|
|
19
|
-
|
|
20
|
-
const generator = new WeeklyReportGenerator(storage, skillRegistry);
|
|
21
|
-
const report = generator.generate(weekOffset);
|
|
22
|
-
|
|
23
|
-
if (format === 'markdown') {
|
|
24
|
-
const md = generator.toMarkdown(report);
|
|
25
|
-
const filename = `weekly-report-w${report.week.isoWeekNumber}.md`;
|
|
26
|
-
res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
|
|
27
|
-
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
28
|
-
res.send(md);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
res.json(report);
|
|
33
|
-
});
|
|
34
|
-
}
|
package/src/web/routes/rules.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /api/rules/hit-rate — Rule hit-rate statistics
|
|
3
|
-
*
|
|
4
|
-
* Aggregates routing_events and skill_invocations to show:
|
|
5
|
-
* - Agent delegation rate
|
|
6
|
-
* - Skill invocation rate
|
|
7
|
-
* - Per-skill breakdown
|
|
8
|
-
* - Per-agent breakdown
|
|
9
|
-
* - Never-triggered skills
|
|
10
|
-
*
|
|
11
|
-
* H1 refactor: pushed GROUP BY into SQL via storage.aggregateRoutingStats /
|
|
12
|
-
* aggregateSkillInvocationsBySkill — no more 100k-row JS scans.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { Application } from 'express';
|
|
16
|
-
import type { RouteContext } from './types.js';
|
|
17
|
-
|
|
18
|
-
function pct(num: number, denom: number): string {
|
|
19
|
-
if (denom <= 0) return '0%';
|
|
20
|
-
return `${(num / denom * 100).toFixed(1)}%`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function registerRulesRoutes(app: Application, ctx: RouteContext): void {
|
|
24
|
-
const { storage, skillRegistry } = ctx;
|
|
25
|
-
|
|
26
|
-
app.get('/api/rules/hit-rate', (req, res) => {
|
|
27
|
-
const days = Math.max(1, parseInt((req.query.days as string) || '7'));
|
|
28
|
-
const since = Date.now() - days * 24 * 3600 * 1000;
|
|
29
|
-
const sinceISO = new Date(since).toISOString();
|
|
30
|
-
|
|
31
|
-
// SQL-side aggregates (replaces 2 × 100k-row scans + JS reduce)
|
|
32
|
-
const routing = storage.aggregateRoutingStats({ since_ts: since });
|
|
33
|
-
const skillsAgg = storage.aggregateSkillInvocationsBySkill({ since });
|
|
34
|
-
|
|
35
|
-
const totalPrompts = routing.total;
|
|
36
|
-
const agentDelegations = routing.obeyed;
|
|
37
|
-
|
|
38
|
-
// Skills frequency (sorted by total desc — SQL ORDER BY total DESC)
|
|
39
|
-
const skillTotal = skillsAgg.reduce((acc, s) => acc + s.total, 0);
|
|
40
|
-
const skills = skillsAgg.map(s => ({
|
|
41
|
-
skill_id: s.skill_id,
|
|
42
|
-
total: s.total,
|
|
43
|
-
success: s.success,
|
|
44
|
-
failed: s.failed,
|
|
45
|
-
rate: pct(s.total, totalPrompts),
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
|
-
// Agents breakdown (already obeyed=1, sorted by count desc in SQL)
|
|
49
|
-
const agents = routing.by_agent.map(a => ({
|
|
50
|
-
agent: a.agent,
|
|
51
|
-
count: a.count,
|
|
52
|
-
rate: pct(a.count, totalPrompts),
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
// Never-triggered skills
|
|
56
|
-
const triggeredSkillIds = new Set(skillsAgg.map(s => s.skill_id));
|
|
57
|
-
const allSkillIds = skillRegistry
|
|
58
|
-
? skillRegistry.getAll().map(s => s.id)
|
|
59
|
-
: [];
|
|
60
|
-
const neverTriggered = allSkillIds.filter(id => !triggeredSkillIds.has(id));
|
|
61
|
-
|
|
62
|
-
res.json({
|
|
63
|
-
period: { days, since: sinceISO },
|
|
64
|
-
summary: {
|
|
65
|
-
totalPrompts,
|
|
66
|
-
agentDelegations,
|
|
67
|
-
agentRate: pct(agentDelegations, totalPrompts),
|
|
68
|
-
skillInvocations: skillTotal,
|
|
69
|
-
skillRate: pct(skillTotal, totalPrompts),
|
|
70
|
-
},
|
|
71
|
-
skills,
|
|
72
|
-
agents,
|
|
73
|
-
neverTriggered,
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|