@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/daemon/lifecycle.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
import { execSync, execFileSync } from 'child_process';
|
|
5
|
-
import { logger } from '../core/utils/logger.js';
|
|
6
|
-
import { FORGE_PATHS } from '../core/constants.js';
|
|
7
|
-
|
|
8
|
-
const FORGE_HOME = FORGE_PATHS.home();
|
|
9
|
-
const TOKEN_FILE = FORGE_PATHS.daemonToken();
|
|
10
|
-
const PID_FILE = FORGE_PATHS.daemonPid();
|
|
11
|
-
|
|
12
|
-
export function writePidFile(): void {
|
|
13
|
-
if (!fs.existsSync(FORGE_HOME)) {
|
|
14
|
-
fs.mkdirSync(FORGE_HOME, { recursive: true });
|
|
15
|
-
}
|
|
16
|
-
fs.writeFileSync(PID_FILE, String(process.pid));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function removePidFile(): void {
|
|
20
|
-
if (fs.existsSync(PID_FILE)) {
|
|
21
|
-
fs.unlinkSync(PID_FILE);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function readPid(): number | null {
|
|
26
|
-
if (!fs.existsSync(PID_FILE)) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
32
|
-
|
|
33
|
-
// 检查进程是否存活
|
|
34
|
-
try {
|
|
35
|
-
process.kill(pid, 0);
|
|
36
|
-
return pid;
|
|
37
|
-
} catch {
|
|
38
|
-
// 进程不存在,清理过期 PID 文件
|
|
39
|
-
removePidFile();
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function isRunning(): boolean {
|
|
48
|
-
return readPid() !== null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function getSocketPath(): string {
|
|
52
|
-
return FORGE_PATHS.daemonSocket();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function cleanSocket(): void {
|
|
56
|
-
const socketPath = getSocketPath();
|
|
57
|
-
if (fs.existsSync(socketPath)) {
|
|
58
|
-
fs.unlinkSync(socketPath);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** daemon 启动时生成随机 auth token,写入 chmod 600 文件 */
|
|
63
|
-
export function writeAuthToken(): string {
|
|
64
|
-
const token = crypto.randomBytes(32).toString('hex');
|
|
65
|
-
if (!fs.existsSync(FORGE_HOME)) {
|
|
66
|
-
fs.mkdirSync(FORGE_HOME, { recursive: true });
|
|
67
|
-
}
|
|
68
|
-
fs.writeFileSync(TOKEN_FILE, token, { mode: 0o600 });
|
|
69
|
-
return token;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** 读取当前 auth token */
|
|
73
|
-
export function readAuthToken(): string | null {
|
|
74
|
-
if (!fs.existsSync(TOKEN_FILE)) return null;
|
|
75
|
-
try {
|
|
76
|
-
return fs.readFileSync(TOKEN_FILE, 'utf-8').trim();
|
|
77
|
-
} catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** daemon 关闭时清理 token 文件 */
|
|
83
|
-
export function removeAuthToken(): void {
|
|
84
|
-
if (fs.existsSync(TOKEN_FILE)) {
|
|
85
|
-
fs.unlinkSync(TOKEN_FILE);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Sync MCP registration token after daemon restart.
|
|
91
|
-
*
|
|
92
|
-
* Problem: Each daemon start generates a new token, but the MCP registration
|
|
93
|
-
* in Claude Code still has the old token → 401 → "Failed to connect".
|
|
94
|
-
*
|
|
95
|
-
* Solution: After writing the new token, re-register the MCP server with
|
|
96
|
-
* the updated Bearer header. This is a best-effort operation — if Claude CLI
|
|
97
|
-
* is not installed or registration fails, we log a warning and continue.
|
|
98
|
-
*/
|
|
99
|
-
export function syncMcpToken(token: string, port: number): void {
|
|
100
|
-
try {
|
|
101
|
-
execSync('command -v claude', { stdio: 'ignore' });
|
|
102
|
-
} catch {
|
|
103
|
-
return; // Claude CLI not installed, skip
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const checkOutput = execSync('claude mcp get claude-forge', {
|
|
108
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
109
|
-
encoding: 'utf-8',
|
|
110
|
-
});
|
|
111
|
-
if (!checkOutput.includes('claude-forge')) return; // Not registered
|
|
112
|
-
} catch {
|
|
113
|
-
return; // Not registered, skip
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const mcpUrl = `http://127.0.0.1:${port}/mcp`;
|
|
117
|
-
try {
|
|
118
|
-
execFileSync('claude', ['mcp', 'remove', 'claude-forge', '-s', 'user'], { stdio: 'ignore' });
|
|
119
|
-
execFileSync('claude', [
|
|
120
|
-
'mcp', 'add', '--transport', 'http', '-s', 'user',
|
|
121
|
-
'claude-forge', mcpUrl,
|
|
122
|
-
'-H', `Authorization: Bearer ${token}`,
|
|
123
|
-
], { stdio: 'ignore' });
|
|
124
|
-
logger.info('[Lifecycle] MCP token synced successfully');
|
|
125
|
-
} catch (err) {
|
|
126
|
-
logger.warn(`[Lifecycle] MCP token sync failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
package/src/daemon/router.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { ForgeEvent, HookResult } from '../core/types.js';
|
|
2
|
-
import { isUserPromptSubmit, isPostToolUse, isStop } from '../core/types.js';
|
|
3
|
-
import type { UserPromptHandler } from './handlers/user-prompt.js';
|
|
4
|
-
import type { PostToolUseHandler } from './handlers/post-tool-use.js';
|
|
5
|
-
import type { StopHandler } from './handlers/stop.js';
|
|
6
|
-
import { logger } from '../core/utils/logger.js';
|
|
7
|
-
import { formatError } from '../core/utils/format.js';
|
|
8
|
-
import { truncateSessionId } from '../core/utils/session.js';
|
|
9
|
-
|
|
10
|
-
export interface Handlers {
|
|
11
|
-
UserPromptSubmit: UserPromptHandler;
|
|
12
|
-
PostToolUse: PostToolUseHandler;
|
|
13
|
-
Stop: StopHandler;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Dispatch an event to the matching handler with hook-type narrowing.
|
|
18
|
-
*
|
|
19
|
-
* The narrow union (PreToolUseEvent / PostToolUseEvent / ...) is enforced at
|
|
20
|
-
* the handler entry, not at storage / wire level — so handlers can rely on
|
|
21
|
-
* required fields without `!` assertions.
|
|
22
|
-
*
|
|
23
|
-
* Error boundary: catches and logs handler errors to prevent daemon crashes.
|
|
24
|
-
*/
|
|
25
|
-
export async function routeEvent(event: ForgeEvent, handlers: Handlers): Promise<HookResult | void> {
|
|
26
|
-
try {
|
|
27
|
-
if (isUserPromptSubmit(event)) return await handlers.UserPromptSubmit.handle(event);
|
|
28
|
-
if (isPostToolUse(event)) return await handlers.PostToolUse.handle(event);
|
|
29
|
-
if (isStop(event)) return await handlers.Stop.handle(event);
|
|
30
|
-
// PreToolUse / Notification: handled inline in daemon/index.ts (no dedicated handler).
|
|
31
|
-
} catch (err) {
|
|
32
|
-
logger.error(
|
|
33
|
-
`[Router] Handler error for ${event.hook_type}: ${formatError(err)} ` +
|
|
34
|
-
`(session: ${truncateSessionId(event.session_id)})`
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// Return safe default to prevent daemon crash
|
|
38
|
-
return { allow: true };
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/daemon/server.ts
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import net from 'net';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import { EventParser } from './event-parser.js';
|
|
4
|
-
import { logger } from '../core/utils/logger.js';
|
|
5
|
-
import { formatError, truncateString } from '../core/utils/format.js';
|
|
6
|
-
|
|
7
|
-
export interface HookResponse {
|
|
8
|
-
allow: boolean;
|
|
9
|
-
reason?: string;
|
|
10
|
-
additionalContext?: string;
|
|
11
|
-
systemMessage?: string;
|
|
12
|
-
hookSpecificOutput?: {
|
|
13
|
-
hookEventName: string;
|
|
14
|
-
permissionDecision: 'allow' | 'deny' | 'ask' | 'defer';
|
|
15
|
-
permissionDecisionReason?: string;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type EventHandler = (
|
|
20
|
-
event: import('../core/types.js').ForgeEvent
|
|
21
|
-
) => HookResponse | void | Promise<HookResponse | void>;
|
|
22
|
-
|
|
23
|
-
export class SocketServer {
|
|
24
|
-
private server: net.Server;
|
|
25
|
-
private parser: EventParser;
|
|
26
|
-
private handler: EventHandler;
|
|
27
|
-
private authToken: string | null;
|
|
28
|
-
private static readonly MAX_BUFFER_SIZE = 512 * 1024; // 512KB
|
|
29
|
-
|
|
30
|
-
constructor(socketPath: string, handler: EventHandler, authToken?: string) {
|
|
31
|
-
this.parser = new EventParser();
|
|
32
|
-
this.handler = handler;
|
|
33
|
-
this.authToken = authToken ?? null;
|
|
34
|
-
|
|
35
|
-
this.server = net.createServer(this.handleConnection.bind(this));
|
|
36
|
-
this.server.listen(socketPath, () => {
|
|
37
|
-
// 限制 socket 文件仅当前用户可读写,防止同机其他用户注入伪造事件
|
|
38
|
-
try {
|
|
39
|
-
fs.chmodSync(socketPath, 0o600);
|
|
40
|
-
} catch (err) {
|
|
41
|
-
logger.warn(`[Socket] 设置权限失败:${formatError(err)}`);
|
|
42
|
-
}
|
|
43
|
-
logger.info(`Socket 服务器已监听:${socketPath}`);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
this.server.on('error', (err) => {
|
|
47
|
-
logger.error(`Socket 服务器错误:${formatError(err)}`);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private handleConnection(socket: net.Socket): void {
|
|
52
|
-
let buffer = '';
|
|
53
|
-
let connectionTimeout: NodeJS.Timeout | null = null;
|
|
54
|
-
|
|
55
|
-
// 设置 10 秒超时,防止连接挂起
|
|
56
|
-
connectionTimeout = setTimeout(() => {
|
|
57
|
-
if (buffer.length > 0) {
|
|
58
|
-
logger.warn(`连接超时,数据不完整(${buffer.length} 字节)`);
|
|
59
|
-
}
|
|
60
|
-
socket.destroy();
|
|
61
|
-
}, 10000);
|
|
62
|
-
|
|
63
|
-
socket.on('data', async (chunk) => {
|
|
64
|
-
buffer += chunk.toString();
|
|
65
|
-
|
|
66
|
-
// 主动检查 buffer 大小,防止内存溢出
|
|
67
|
-
if (buffer.length > SocketServer.MAX_BUFFER_SIZE) {
|
|
68
|
-
logger.warn(`事件过大(${buffer.length} 字节),关闭连接`);
|
|
69
|
-
buffer = '';
|
|
70
|
-
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
71
|
-
socket.destroy();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 性能修复(L3):hook 端用 `echo` 发送 JSON,结尾自带换行符。
|
|
76
|
-
// 只在 buffer 中遇到换行符时才尝试解析,避免大事件(接近 512KB)
|
|
77
|
-
// 在分片到达时每个 chunk 都跑一次完整 JSON.parse 造成的 N² 行为。
|
|
78
|
-
// 兼容:如果数据无换行(旧客户端、其他来源),等到 socket 关闭时
|
|
79
|
-
// 由 'end' 事件做最后一次解析尝试。
|
|
80
|
-
const newlineIdx = buffer.indexOf('\n');
|
|
81
|
-
if (newlineIdx === -1) {
|
|
82
|
-
// 尚未收到完整消息,继续等待
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 取换行前的完整消息;忽略行后可能存在的多余数据(hook 单连接一事件)
|
|
87
|
-
const message = buffer.slice(0, newlineIdx);
|
|
88
|
-
buffer = '';
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
await this.processMessage(socket, message);
|
|
92
|
-
} finally {
|
|
93
|
-
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// 兜底:连接关闭时若 buffer 仍有内容(无换行结尾的旧客户端),尝试解析一次
|
|
98
|
-
socket.on('end', async () => {
|
|
99
|
-
const remaining = buffer.trim();
|
|
100
|
-
buffer = '';
|
|
101
|
-
if (remaining.length === 0) return;
|
|
102
|
-
try {
|
|
103
|
-
await this.processMessage(socket, remaining);
|
|
104
|
-
} catch {
|
|
105
|
-
// processMessage 内部已记录错误
|
|
106
|
-
} finally {
|
|
107
|
-
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
socket.on('error', (err) => {
|
|
112
|
-
logger.debug(`Socket 连接错误:${formatError(err)}`);
|
|
113
|
-
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
socket.on('close', () => {
|
|
117
|
-
if (connectionTimeout) clearTimeout(connectionTimeout);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 解析单条完整消息(一次性,无 N² 行为)并执行 handler。
|
|
123
|
-
*
|
|
124
|
-
* 注意:调用方需确保 message 是一条完整的 JSON 文本(已根据换行符切分)。
|
|
125
|
-
*/
|
|
126
|
-
private async processMessage(socket: net.Socket, message: string): Promise<void> {
|
|
127
|
-
try {
|
|
128
|
-
// 认证检查:在 EventParser 之前提取 _auth 字段(zod 会剥离未知字段)
|
|
129
|
-
if (this.authToken) {
|
|
130
|
-
try {
|
|
131
|
-
const raw = JSON.parse(message);
|
|
132
|
-
if (raw._auth !== this.authToken) {
|
|
133
|
-
logger.warn('[Socket] 认证失败,关闭连接');
|
|
134
|
-
socket.destroy();
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
} catch {
|
|
138
|
-
// JSON 解析失败,交给下面的 EventParser 处理
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const event = this.parser.parse(message);
|
|
143
|
-
const result = await this.handler(event);
|
|
144
|
-
|
|
145
|
-
// 双向通信:如果 handler 返回了结果,写回给 hook 脚本
|
|
146
|
-
if (result) {
|
|
147
|
-
// 清理无意义内容,避免 Claude Code 注入干扰上下文
|
|
148
|
-
const cleaned = { ...result } as Record<string, unknown>;
|
|
149
|
-
if (cleaned['additionalContext'] && !this.isValidContent(cleaned['additionalContext'] as string)) {
|
|
150
|
-
delete cleaned['additionalContext'];
|
|
151
|
-
}
|
|
152
|
-
if (cleaned['systemMessage'] && !this.isValidContent(cleaned['systemMessage'] as string)) {
|
|
153
|
-
delete cleaned['systemMessage'];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const payload = JSON.stringify(cleaned);
|
|
157
|
-
socket.write(payload, () => socket.end());
|
|
158
|
-
} else {
|
|
159
|
-
socket.end();
|
|
160
|
-
}
|
|
161
|
-
} catch (err) {
|
|
162
|
-
logger.error(`事件解析失败:${formatError(err)}`);
|
|
163
|
-
logger.debug(`无效消息内容:${truncateString(message, 500)}`);
|
|
164
|
-
socket.destroy();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 判断内容是否有意义,过滤空字符串、纯标点/符号等
|
|
170
|
-
*/
|
|
171
|
-
private isValidContent(content: string): boolean {
|
|
172
|
-
if (!content) return false;
|
|
173
|
-
const trimmed = content.trim();
|
|
174
|
-
if (trimmed.length === 0) return false;
|
|
175
|
-
|
|
176
|
-
const stripped = trimmed
|
|
177
|
-
.split('\n')
|
|
178
|
-
.map(line => line.replace(/^>\s*/, '').trim())
|
|
179
|
-
.join('\n')
|
|
180
|
-
.trim();
|
|
181
|
-
|
|
182
|
-
if (stripped.length === 0) return false;
|
|
183
|
-
if (!/[\p{L}\p{N}]/u.test(stripped)) return false;
|
|
184
|
-
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
close(): Promise<void> {
|
|
189
|
-
return new Promise((resolve) => {
|
|
190
|
-
this.server.close(() => {
|
|
191
|
-
logger.info('Socket 服务器已关闭');
|
|
192
|
-
resolve();
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import type { SQLiteStorage } from '../../core/storage/sqlite.js';
|
|
3
|
-
import { logger } from '../../core/utils/logger.js';
|
|
4
|
-
import { normalizeTimestamp, timestampToMs } from '../../core/utils/time.js';
|
|
5
|
-
import { truncateSessionId } from '../../core/utils/session.js';
|
|
6
|
-
import { truncateString } from '../../core/utils/format.js';
|
|
7
|
-
|
|
8
|
-
const NEW_TASK_KEYWORDS = [
|
|
9
|
-
'帮我', '实现', '修复', '添加', '重构', '优化', '创建', '删除',
|
|
10
|
-
'升级', '迁移', '部署', '配置', '设计', '提交', '推送',
|
|
11
|
-
'fix', 'add', 'implement', 'refactor', 'create', 'delete', 'deploy',
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
const CONTINUE_KEYWORDS = [
|
|
15
|
-
'继续', '然后', '还有', '另外', '这个', '那个', '它',
|
|
16
|
-
'对', '好的', '可以', '是的', '不是', '不对', '改一下',
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const TASK_SWITCH_MINUTES = 10;
|
|
20
|
-
|
|
21
|
-
export class TaskSegmenter {
|
|
22
|
-
private currentTasks = new Map<string, { id: string; lastTime: number }>();
|
|
23
|
-
|
|
24
|
-
constructor(private storage: SQLiteStorage) {}
|
|
25
|
-
|
|
26
|
-
processPrompt(sessionId: string, prompt: string, timestamp: string, eventId?: string): string {
|
|
27
|
-
const key = sessionId;
|
|
28
|
-
const current = this.currentTasks.get(key) ?? this.recoverActiveTask(key);
|
|
29
|
-
const now = timestampToMs(timestamp);
|
|
30
|
-
|
|
31
|
-
if (!current || this.shouldStartNewTask(prompt, now, current)) {
|
|
32
|
-
if (current) {
|
|
33
|
-
this.storage.updateTask(current.id, { status: 'completed', end_time: timestamp });
|
|
34
|
-
}
|
|
35
|
-
const taskId = this.createTask(sessionId, prompt, timestamp);
|
|
36
|
-
this.currentTasks.set(key, { id: taskId, lastTime: now });
|
|
37
|
-
if (eventId) this.storage.linkEventToTask(taskId, eventId);
|
|
38
|
-
return taskId;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
current.lastTime = now;
|
|
42
|
-
if (eventId) this.storage.linkEventToTask(current.id, eventId);
|
|
43
|
-
return current.id;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
linkEvent(sessionId: string, eventId: string): void {
|
|
47
|
-
const current = this.currentTasks.get(sessionId) ?? this.recoverActiveTask(sessionId);
|
|
48
|
-
if (current) {
|
|
49
|
-
this.storage.linkEventToTask(current.id, eventId);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 从数据库恢复 session 的 active 任务到内存 Map。
|
|
55
|
-
* 用于 daemon 重启后内存丢失场景的 lazy 恢复。
|
|
56
|
-
* 找不到 active 任务时返回 null(不报错)。
|
|
57
|
-
*/
|
|
58
|
-
private recoverActiveTask(sessionId: string): { id: string; lastTime: number } | null {
|
|
59
|
-
const tasks = this.storage.queryTasks({ session_id: sessionId, limit: 5 });
|
|
60
|
-
const activeTask = tasks.find(t => t.status === 'active');
|
|
61
|
-
if (!activeTask) return null;
|
|
62
|
-
|
|
63
|
-
const anchor = activeTask.end_time ?? activeTask.start_time;
|
|
64
|
-
const lastTime = timestampToMs(anchor);
|
|
65
|
-
const entry = { id: activeTask.id, lastTime };
|
|
66
|
-
this.currentTasks.set(sessionId, entry);
|
|
67
|
-
logger.info(
|
|
68
|
-
`[TaskSegmenter] Recovered active task ${truncateString(activeTask.id, 8)} for session ${truncateSessionId(sessionId)} from DB`,
|
|
69
|
-
);
|
|
70
|
-
return entry;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
getCurrentTaskId(sessionId: string): string | null {
|
|
74
|
-
return this.currentTasks.get(sessionId)?.id ?? null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
completeCurrentTask(sessionId: string, timestamp: string): void {
|
|
78
|
-
const current = this.currentTasks.get(sessionId) ?? this.recoverActiveTask(sessionId);
|
|
79
|
-
if (!current) return;
|
|
80
|
-
this.storage.updateTask(current.id, {
|
|
81
|
-
status: 'completed',
|
|
82
|
-
end_time: timestamp,
|
|
83
|
-
});
|
|
84
|
-
this.currentTasks.delete(sessionId);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private shouldStartNewTask(prompt: string, now: number, current: { lastTime: number }): boolean {
|
|
88
|
-
const minutesDiff = (now - current.lastTime) / 60000;
|
|
89
|
-
if (minutesDiff > TASK_SWITCH_MINUTES) return true;
|
|
90
|
-
|
|
91
|
-
const hasContinue = CONTINUE_KEYWORDS.some(kw => prompt.includes(kw));
|
|
92
|
-
if (hasContinue) return false;
|
|
93
|
-
|
|
94
|
-
const hasNewTask = NEW_TASK_KEYWORDS.some(kw => prompt.includes(kw));
|
|
95
|
-
return hasNewTask;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private createTask(sessionId: string, prompt: string, timestamp: string): string {
|
|
99
|
-
const taskId = randomUUID();
|
|
100
|
-
const title = this.extractTitle(prompt);
|
|
101
|
-
this.storage.writeTask({ id: taskId, session_id: sessionId, title, start_time: timestamp });
|
|
102
|
-
logger.info(`[TaskSegmenter] New task: "${title}" (${truncateString(taskId, 8)})`);
|
|
103
|
-
return taskId;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private extractTitle(prompt: string): string {
|
|
107
|
-
const cleaned = prompt.replace(/\[Image #\d+\]/g, '').trim();
|
|
108
|
-
const firstLine = cleaned.split('\n')[0].trim();
|
|
109
|
-
if (firstLine.length <= 60) return firstLine || '(无标题)';
|
|
110
|
-
return truncateString(firstLine, 60);
|
|
111
|
-
}
|
|
112
|
-
}
|
package/src/hooks/hook-lib.sh
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Claude Forge - Hook shared library
|
|
3
|
-
# Source this file at the top of each hook script:
|
|
4
|
-
# source "$(dirname "$0")/hook-lib.sh"
|
|
5
|
-
#
|
|
6
|
-
# Provides:
|
|
7
|
-
# - SOCKET_PATH, QUEUE_DIR shared variables
|
|
8
|
-
# - send_event_or_enqueue() function
|
|
9
|
-
|
|
10
|
-
SOCKET_PATH="${CLAUDE_FORGE_SOCKET:-$HOME/.claude-forge/daemon.sock}"
|
|
11
|
-
QUEUE_DIR="$HOME/.claude-forge/queue"
|
|
12
|
-
|
|
13
|
-
# resolve_project_path <input_cwd>
|
|
14
|
-
#
|
|
15
|
-
# Walk up the directory tree from <input_cwd> to find the nearest ancestor that
|
|
16
|
-
# contains a `.git` entry (directory for normal clones, file for worktrees /
|
|
17
|
-
# submodules). Print the resolved git-root path on stdout.
|
|
18
|
-
#
|
|
19
|
-
# Fallback rules:
|
|
20
|
-
# - Empty input → start from $PWD
|
|
21
|
-
# - Relative input → prefixed with $PWD to absolutise
|
|
22
|
-
# - No .git found within 64 levels → print original input unchanged
|
|
23
|
-
#
|
|
24
|
-
# POSIX-only: uses `dirname` and `case`; does NOT depend on `realpath` or any
|
|
25
|
-
# GNU coreutils extensions, so it works on macOS BSD and Linux alike.
|
|
26
|
-
resolve_project_path() {
|
|
27
|
-
local input="${1:-}"
|
|
28
|
-
local dir="${input:-$PWD}"
|
|
29
|
-
|
|
30
|
-
# Absolutise: prefix relative paths with $PWD (no realpath dependency)
|
|
31
|
-
case "$dir" in
|
|
32
|
-
/*) ;;
|
|
33
|
-
*) dir="$PWD/$dir" ;;
|
|
34
|
-
esac
|
|
35
|
-
|
|
36
|
-
local guard=0
|
|
37
|
-
while [ "$dir" != "/" ] && [ "$dir" != "." ] && [ $guard -lt 64 ]; do
|
|
38
|
-
if [ -d "$dir/.git" ] || [ -f "$dir/.git" ]; then
|
|
39
|
-
printf '%s' "$dir"
|
|
40
|
-
return 0
|
|
41
|
-
fi
|
|
42
|
-
dir=$(dirname "$dir")
|
|
43
|
-
guard=$((guard + 1))
|
|
44
|
-
done
|
|
45
|
-
|
|
46
|
-
# No git ancestor found → fall back to the caller's original cwd
|
|
47
|
-
printf '%s' "${input:-$PWD}"
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
# send_event_or_enqueue <event_json> [timeout_seconds]
|
|
51
|
-
#
|
|
52
|
-
# 1. If socket file does not exist → enqueue in background → return empty string
|
|
53
|
-
# 2. If socket exists → send via nc with timeout
|
|
54
|
-
# - nc exit code != 0 → enqueue in background → return empty string
|
|
55
|
-
# - nc exit code == 0 → return daemon response on stdout
|
|
56
|
-
#
|
|
57
|
-
# The caller reads REPLY_RESPONSE after calling this function:
|
|
58
|
-
# send_event_or_enqueue "$EVENT" 10
|
|
59
|
-
# RESPONSE="$HOOK_RESPONSE"
|
|
60
|
-
#
|
|
61
|
-
# macOS BSD nc notes:
|
|
62
|
-
# - `nc -U` opens a Unix domain socket connection
|
|
63
|
-
# - `-w N` sets idle timeout (seconds); on macOS this is the "connection timeout"
|
|
64
|
-
# for UDP but for TCP/Unix it's inactivity timeout after connection
|
|
65
|
-
# - Exit code: 0 = success (data sent + connection closed by remote), 1 = error
|
|
66
|
-
# - An empty response (daemon returns nothing) still exits 0 if the connection
|
|
67
|
-
# was established and closed cleanly → we check exit code, NOT response content
|
|
68
|
-
|
|
69
|
-
HOOK_RESPONSE=""
|
|
70
|
-
|
|
71
|
-
send_event_or_enqueue() {
|
|
72
|
-
local event_json="$1"
|
|
73
|
-
local timeout_secs="${2:-10}"
|
|
74
|
-
|
|
75
|
-
HOOK_RESPONSE=""
|
|
76
|
-
|
|
77
|
-
# Fast path: no socket file → daemon is not running
|
|
78
|
-
if [ ! -S "$SOCKET_PATH" ]; then
|
|
79
|
-
_enqueue_event_bg "$event_json"
|
|
80
|
-
return 0
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
# Socket exists: try to send; capture exit code separately from stdout
|
|
84
|
-
local tmp_response
|
|
85
|
-
tmp_response=$(echo "$event_json" | nc -U -w "$timeout_secs" "$SOCKET_PATH" 2>/dev/null)
|
|
86
|
-
local nc_exit=$?
|
|
87
|
-
|
|
88
|
-
if [ $nc_exit -ne 0 ]; then
|
|
89
|
-
# nc failed (connection refused, socket disappeared between check and send, etc.)
|
|
90
|
-
_enqueue_event_bg "$event_json"
|
|
91
|
-
return 0
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
# Success: pass response back to caller
|
|
95
|
-
HOOK_RESPONSE="$tmp_response"
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
# _enqueue_event_bg <event_json>
|
|
99
|
-
# Forks a background subshell to write the event JSON to the queue directory.
|
|
100
|
-
# The main hook process is never blocked by this operation.
|
|
101
|
-
_enqueue_event_bg() {
|
|
102
|
-
local event_json="$1"
|
|
103
|
-
(
|
|
104
|
-
mkdir -p "$QUEUE_DIR"
|
|
105
|
-
# Timestamp with second precision (BSD date has no %N nanoseconds)
|
|
106
|
-
local ts
|
|
107
|
-
ts=$(date -u +"%Y%m%dT%H%M%S")
|
|
108
|
-
# UUID: prefer uuidgen (macOS), fall back to /proc on Linux
|
|
109
|
-
local uuid
|
|
110
|
-
uuid=$(uuidgen 2>/dev/null \
|
|
111
|
-
|| cat /proc/sys/kernel/random/uuid 2>/dev/null \
|
|
112
|
-
|| printf '%s-%s' "$$" "$RANDOM")
|
|
113
|
-
local filename="${ts}-${uuid}.json"
|
|
114
|
-
# Atomic write: write to temp file, then rename
|
|
115
|
-
local tmp_file="$QUEUE_DIR/.tmp-${filename}"
|
|
116
|
-
printf '%s' "$event_json" > "$tmp_file" && mv "$tmp_file" "$QUEUE_DIR/${filename}"
|
|
117
|
-
) &
|
|
118
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Claude Forge - Notification Hook
|
|
3
|
-
|
|
4
|
-
# shellcheck source=./hook-lib.sh
|
|
5
|
-
source "$(dirname "$0")/hook-lib.sh"
|
|
6
|
-
|
|
7
|
-
AUTH_TOKEN=$(cat "$HOME/.claude-forge/daemon.token" 2>/dev/null || echo '')
|
|
8
|
-
|
|
9
|
-
# 读取 stdin
|
|
10
|
-
INPUT=$(cat)
|
|
11
|
-
|
|
12
|
-
# 提取 cwd(jq 替代 python3)
|
|
13
|
-
RAW_CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
|
|
14
|
-
PROJECT_PATH=$(resolve_project_path "${RAW_CWD:-$PWD}")
|
|
15
|
-
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // .sessionId // ""')
|
|
16
|
-
SESSION_ID="${SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-cli}}"
|
|
17
|
-
|
|
18
|
-
# 构造事件 JSON(原始 INPUT 作为 tool_input,保留完整通知内容)
|
|
19
|
-
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
|
20
|
-
TOOL_INPUT=$(echo "$INPUT" | jq -c '.')
|
|
21
|
-
EVENT=$(jq -n \
|
|
22
|
-
--arg hook_type "Notification" \
|
|
23
|
-
--arg timestamp "$TIMESTAMP" \
|
|
24
|
-
--arg session_id "$SESSION_ID" \
|
|
25
|
-
--arg project_path "$PROJECT_PATH" \
|
|
26
|
-
--argjson tool_input "$TOOL_INPUT" \
|
|
27
|
-
--arg auth "$AUTH_TOKEN" \
|
|
28
|
-
'{hook_type: $hook_type, timestamp: $timestamp, session_id: $session_id,
|
|
29
|
-
project_path: $project_path, tool_name: "notification",
|
|
30
|
-
tool_input: $tool_input, _auth: $auth}')
|
|
31
|
-
|
|
32
|
-
# 发送到 daemon(1 秒超时;失败时入队)
|
|
33
|
-
send_event_or_enqueue "$EVENT" 1
|
|
34
|
-
|
|
35
|
-
exit 0
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Claude Forge - PostToolUse Hook
|
|
3
|
-
|
|
4
|
-
# shellcheck source=./hook-lib.sh
|
|
5
|
-
source "$(dirname "$0")/hook-lib.sh"
|
|
6
|
-
|
|
7
|
-
AUTH_TOKEN=$(cat "$HOME/.claude-forge/daemon.token" 2>/dev/null || echo '')
|
|
8
|
-
|
|
9
|
-
# 读取 stdin
|
|
10
|
-
INPUT=$(cat)
|
|
11
|
-
|
|
12
|
-
# 提取字段(单次 jq 调用,替代 4 个 python3 进程)
|
|
13
|
-
# tool_response 是 Claude Code 的字段名
|
|
14
|
-
TOOL_NAME="${CLAUDE_TOOL_NAME:-$(echo "$INPUT" | jq -r '.tool_name // ""')}"
|
|
15
|
-
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
|
|
16
|
-
TOOL_OUTPUT=$(echo "$INPUT" | jq -c '.tool_response // {}')
|
|
17
|
-
RAW_CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
|
|
18
|
-
PROJECT_PATH=$(resolve_project_path "${RAW_CWD:-$PWD}")
|
|
19
|
-
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // .sessionId // ""')
|
|
20
|
-
SESSION_ID="${SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-cli}}"
|
|
21
|
-
|
|
22
|
-
# 构造事件 JSON
|
|
23
|
-
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
|
24
|
-
EVENT=$(jq -n \
|
|
25
|
-
--arg hook_type "PostToolUse" \
|
|
26
|
-
--arg timestamp "$TIMESTAMP" \
|
|
27
|
-
--arg session_id "$SESSION_ID" \
|
|
28
|
-
--arg project_path "$PROJECT_PATH" \
|
|
29
|
-
--arg tool_name "$TOOL_NAME" \
|
|
30
|
-
--argjson tool_input "$TOOL_INPUT" \
|
|
31
|
-
--argjson tool_output "$TOOL_OUTPUT" \
|
|
32
|
-
--arg auth "$AUTH_TOKEN" \
|
|
33
|
-
'{hook_type: $hook_type, timestamp: $timestamp, session_id: $session_id,
|
|
34
|
-
project_path: $project_path, tool_name: $tool_name, tool_input: $tool_input,
|
|
35
|
-
tool_output: $tool_output, _auth: $auth}')
|
|
36
|
-
|
|
37
|
-
# 发送到 daemon(PostToolUse 也需要等待响应以支持通知注入;失败时入队)
|
|
38
|
-
send_event_or_enqueue "$EVENT" 5
|
|
39
|
-
RESPONSE="$HOOK_RESPONSE"
|
|
40
|
-
|
|
41
|
-
if [ -n "$RESPONSE" ]; then
|
|
42
|
-
# 硬阻断:优先使用 hookSpecificOutput(Claude Code 官方格式)
|
|
43
|
-
ALLOW=$(echo "$RESPONSE" | jq -r '.allow // true')
|
|
44
|
-
if [ "$ALLOW" = "false" ]; then
|
|
45
|
-
HAS_HOOK_OUTPUT=$(echo "$RESPONSE" | jq -r 'if .hookSpecificOutput then "yes" else "no" end')
|
|
46
|
-
if [ "$HAS_HOOK_OUTPUT" = "yes" ]; then
|
|
47
|
-
echo "$RESPONSE" | jq '{hookSpecificOutput: .hookSpecificOutput}'
|
|
48
|
-
exit 0
|
|
49
|
-
else
|
|
50
|
-
REASON=$(echo "$RESPONSE" | jq -r '.reason // "Pipeline 已阻断"')
|
|
51
|
-
echo "$REASON" >&2
|
|
52
|
-
exit 2
|
|
53
|
-
fi
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
HAS_CONTEXT=$(echo "$RESPONSE" | jq -r 'if .additionalContext and (.additionalContext != "") then "yes" else "no" end')
|
|
57
|
-
if [ "$HAS_CONTEXT" = "yes" ]; then
|
|
58
|
-
echo "$RESPONSE"
|
|
59
|
-
fi
|
|
60
|
-
fi
|
|
61
|
-
exit 0
|