@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
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for FORGE_PATHS (src/core/constants.ts)
|
|
3
|
-
*
|
|
4
|
-
* Verifies that every path method returns a value prefixed by FORGE_HOME
|
|
5
|
-
* and matches the expected sub-path.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect } from 'vitest';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import { FORGE_HOME, FORGE_PATHS } from '../../../src/core/constants.js';
|
|
11
|
-
|
|
12
|
-
describe('FORGE_PATHS', () => {
|
|
13
|
-
describe('existing methods', () => {
|
|
14
|
-
it('home() returns FORGE_HOME', () => {
|
|
15
|
-
expect(FORGE_PATHS.home()).toBe(FORGE_HOME);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('config() returns FORGE_HOME/config.yaml', () => {
|
|
19
|
-
expect(FORGE_PATHS.config()).toBe(join(FORGE_HOME, 'config.yaml'));
|
|
20
|
-
expect(FORGE_PATHS.config().startsWith(FORGE_HOME)).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('database() returns FORGE_HOME/data.db', () => {
|
|
24
|
-
expect(FORGE_PATHS.database()).toBe(join(FORGE_HOME, 'data.db'));
|
|
25
|
-
expect(FORGE_PATHS.database().startsWith(FORGE_HOME)).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('logs() returns FORGE_HOME/logs', () => {
|
|
29
|
-
expect(FORGE_PATHS.logs()).toBe(join(FORGE_HOME, 'logs'));
|
|
30
|
-
expect(FORGE_PATHS.logs().startsWith(FORGE_HOME)).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('daemon runtime files', () => {
|
|
35
|
-
it('daemonSocket() returns FORGE_HOME/daemon.sock', () => {
|
|
36
|
-
expect(FORGE_PATHS.daemonSocket()).toBe(join(FORGE_HOME, 'daemon.sock'));
|
|
37
|
-
expect(FORGE_PATHS.daemonSocket().startsWith(FORGE_HOME)).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('daemonPid() returns FORGE_HOME/daemon.pid', () => {
|
|
41
|
-
expect(FORGE_PATHS.daemonPid()).toBe(join(FORGE_HOME, 'daemon.pid'));
|
|
42
|
-
expect(FORGE_PATHS.daemonPid().startsWith(FORGE_HOME)).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('daemonToken() returns FORGE_HOME/daemon.token', () => {
|
|
46
|
-
expect(FORGE_PATHS.daemonToken()).toBe(join(FORGE_HOME, 'daemon.token'));
|
|
47
|
-
expect(FORGE_PATHS.daemonToken().startsWith(FORGE_HOME)).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('daemonLog() returns FORGE_HOME/daemon.log', () => {
|
|
51
|
-
expect(FORGE_PATHS.daemonLog()).toBe(join(FORGE_HOME, 'daemon.log'));
|
|
52
|
-
expect(FORGE_PATHS.daemonLog().startsWith(FORGE_HOME)).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('daemonStdout() returns FORGE_HOME/daemon-stdout.log', () => {
|
|
56
|
-
expect(FORGE_PATHS.daemonStdout()).toBe(join(FORGE_HOME, 'daemon-stdout.log'));
|
|
57
|
-
expect(FORGE_PATHS.daemonStdout().startsWith(FORGE_HOME)).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('daemonStderr() returns FORGE_HOME/daemon-stderr.log', () => {
|
|
61
|
-
expect(FORGE_PATHS.daemonStderr()).toBe(join(FORGE_HOME, 'daemon-stderr.log'));
|
|
62
|
-
expect(FORGE_PATHS.daemonStderr().startsWith(FORGE_HOME)).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('subdirs', () => {
|
|
67
|
-
it('hooks() returns FORGE_HOME/hooks', () => {
|
|
68
|
-
expect(FORGE_PATHS.hooks()).toBe(join(FORGE_HOME, 'hooks'));
|
|
69
|
-
expect(FORGE_PATHS.hooks().startsWith(FORGE_HOME)).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('queue() returns FORGE_HOME/queue', () => {
|
|
73
|
-
expect(FORGE_PATHS.queue()).toBe(join(FORGE_HOME, 'queue'));
|
|
74
|
-
expect(FORGE_PATHS.queue().startsWith(FORGE_HOME)).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('queueDead() returns FORGE_HOME/queue/dead', () => {
|
|
78
|
-
expect(FORGE_PATHS.queueDead()).toBe(join(FORGE_HOME, 'queue', 'dead'));
|
|
79
|
-
expect(FORGE_PATHS.queueDead().startsWith(FORGE_HOME)).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('patchable targets', () => {
|
|
84
|
-
it('routingYaml() returns FORGE_HOME/routing.yaml', () => {
|
|
85
|
-
expect(FORGE_PATHS.routingYaml()).toBe(join(FORGE_HOME, 'routing.yaml'));
|
|
86
|
-
expect(FORGE_PATHS.routingYaml().startsWith(FORGE_HOME)).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("backups('skills') returns FORGE_HOME/backups/skills", () => {
|
|
90
|
-
expect(FORGE_PATHS.backups('skills')).toBe(join(FORGE_HOME, 'backups', 'skills'));
|
|
91
|
-
expect(FORGE_PATHS.backups('skills').startsWith(FORGE_HOME)).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("backups('routing') returns FORGE_HOME/backups/routing", () => {
|
|
95
|
-
expect(FORGE_PATHS.backups('routing')).toBe(join(FORGE_HOME, 'backups', 'routing'));
|
|
96
|
-
expect(FORGE_PATHS.backups('routing').startsWith(FORGE_HOME)).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { syncHooks } from '../../../src/daemon/hook-sync.js';
|
|
6
|
-
|
|
7
|
-
describe('syncHooks', () => {
|
|
8
|
-
let tmpRoot: string;
|
|
9
|
-
let sourceDir: string;
|
|
10
|
-
let targetDir: string;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
tmpRoot = mkdtempSync(join(tmpdir(), 'forge-hook-sync-'));
|
|
14
|
-
sourceDir = join(tmpRoot, 'src-hooks');
|
|
15
|
-
targetDir = join(tmpRoot, 'target-hooks');
|
|
16
|
-
mkdirSync(sourceDir, { recursive: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('target dir not exist → returns zero counts (user did not init)', () => {
|
|
24
|
-
writeFileSync(join(sourceDir, 'pre-tool-use.sh'), '#!/bin/bash\necho "new"\n');
|
|
25
|
-
const result = syncHooks({ sourceDir, targetDir });
|
|
26
|
-
expect(result.copied).toBe(0);
|
|
27
|
-
expect(result.checked).toBe(0);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('source and target identical → no copy', () => {
|
|
31
|
-
mkdirSync(targetDir, { recursive: true });
|
|
32
|
-
const content = '#!/bin/bash\necho "same"\n';
|
|
33
|
-
writeFileSync(join(sourceDir, 'pre-tool-use.sh'), content);
|
|
34
|
-
writeFileSync(join(targetDir, 'pre-tool-use.sh'), content);
|
|
35
|
-
|
|
36
|
-
const result = syncHooks({ sourceDir, targetDir });
|
|
37
|
-
expect(result.copied).toBe(0);
|
|
38
|
-
expect(result.checked).toBe(1);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('source and target differ → copies new content', () => {
|
|
42
|
-
mkdirSync(targetDir, { recursive: true });
|
|
43
|
-
writeFileSync(join(sourceDir, 'pre-tool-use.sh'), '#!/bin/bash\necho "NEW"\n');
|
|
44
|
-
writeFileSync(join(targetDir, 'pre-tool-use.sh'), '#!/bin/bash\necho "old"\n');
|
|
45
|
-
|
|
46
|
-
const result = syncHooks({ sourceDir, targetDir });
|
|
47
|
-
expect(result.copied).toBe(1);
|
|
48
|
-
expect(result.checked).toBe(1);
|
|
49
|
-
expect(readFileSync(join(targetDir, 'pre-tool-use.sh'), 'utf-8')).toContain('NEW');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('target missing file but exists in source → copies it', () => {
|
|
53
|
-
mkdirSync(targetDir, { recursive: true });
|
|
54
|
-
writeFileSync(join(sourceDir, 'hook-lib.sh'), '# lib\n');
|
|
55
|
-
// target has no hook-lib.sh
|
|
56
|
-
|
|
57
|
-
const result = syncHooks({ sourceDir, targetDir });
|
|
58
|
-
expect(result.copied).toBe(1);
|
|
59
|
-
expect(existsSync(join(targetDir, 'hook-lib.sh'))).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('source missing some files → skips them silently', () => {
|
|
63
|
-
mkdirSync(targetDir, { recursive: true });
|
|
64
|
-
// Only one source file out of 6
|
|
65
|
-
writeFileSync(join(sourceDir, 'pre-tool-use.sh'), '#!/bin/bash\necho "x"\n');
|
|
66
|
-
|
|
67
|
-
const result = syncHooks({ sourceDir, targetDir });
|
|
68
|
-
expect(result.checked).toBe(1);
|
|
69
|
-
expect(result.skipped).toBe(5);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* M7: PostToolUseHandler routing_event association behavior.
|
|
3
|
-
*
|
|
4
|
-
* Targets the time-ordering bug where 2+ Agent invocations within a single
|
|
5
|
-
* user prompt could overwrite the same routing_event repeatedly. After M7,
|
|
6
|
-
* the handler must only touch the *pending* (obeyed IS NULL) recent event;
|
|
7
|
-
* later Agents within the same prompt are silently skipped (M7 limitation).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
11
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
12
|
-
import { tmpdir } from 'node:os';
|
|
13
|
-
import { join } from 'node:path';
|
|
14
|
-
import { SQLiteStorage } from '../../../src/core/storage/sqlite.js';
|
|
15
|
-
import { PostToolUseHandler } from '../../../src/daemon/handlers/post-tool-use.js';
|
|
16
|
-
import type { PostToolUseEvent } from '../../../src/core/types.js';
|
|
17
|
-
|
|
18
|
-
const PROJECT = '/tmp/m7-handler-proj';
|
|
19
|
-
|
|
20
|
-
function makeAgentEvent(session_id: string, agentType: string): PostToolUseEvent {
|
|
21
|
-
return {
|
|
22
|
-
session_id,
|
|
23
|
-
project_path: PROJECT,
|
|
24
|
-
timestamp: new Date().toISOString(),
|
|
25
|
-
hook_type: 'PostToolUse',
|
|
26
|
-
tool_name: 'Agent',
|
|
27
|
-
tool_input: { subagent_type: agentType },
|
|
28
|
-
tool_output: {},
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('PostToolUseHandler — routing_event association (M7)', () => {
|
|
33
|
-
let tmp: string;
|
|
34
|
-
let storage: SQLiteStorage;
|
|
35
|
-
let handler: PostToolUseHandler;
|
|
36
|
-
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
tmp = mkdtempSync(join(tmpdir(), 'forge-m7-handler-'));
|
|
39
|
-
storage = new SQLiteStorage(join(tmp, 'data.db'));
|
|
40
|
-
handler = new PostToolUseHandler(storage);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
try { storage.close(); } catch { /* ignore */ }
|
|
45
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('case 1: same prompt, 2 Agent calls — first updates pending row, second does NOT re-overwrite', async () => {
|
|
49
|
-
const sess = 'sess-same-prompt';
|
|
50
|
-
|
|
51
|
-
// UserPromptHandler writes a single pending routing_event.
|
|
52
|
-
const eventId = storage.writeRoutingEvent({
|
|
53
|
-
session_id: sess, project_path: PROJECT, ts: 1000,
|
|
54
|
-
prompt: 'spawn two agents', intent_json: '{}',
|
|
55
|
-
obeyed: null,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Agent #1 fires → first PostToolUse fills the row.
|
|
59
|
-
await handler.handle(makeAgentEvent(sess, 'researcher'));
|
|
60
|
-
|
|
61
|
-
const rowsAfter1 = storage.queryRoutingEvents({ session_id: sess });
|
|
62
|
-
expect(rowsAfter1).toHaveLength(1);
|
|
63
|
-
expect(rowsAfter1[0].id).toBe(eventId);
|
|
64
|
-
expect(rowsAfter1[0].obeyed).toBe(1);
|
|
65
|
-
expect(rowsAfter1[0].routed_to_name).toBe('researcher');
|
|
66
|
-
const firstToolTs = rowsAfter1[0].first_tool_ts;
|
|
67
|
-
expect(firstToolTs).not.toBeNull();
|
|
68
|
-
|
|
69
|
-
// Agent #2 fires → with the pending filter, no pending row remains;
|
|
70
|
-
// the handler must NOT overwrite the existing obeyed=1 row.
|
|
71
|
-
await handler.handle(makeAgentEvent(sess, 'coder'));
|
|
72
|
-
|
|
73
|
-
const rowsAfter2 = storage.queryRoutingEvents({ session_id: sess });
|
|
74
|
-
expect(rowsAfter2).toHaveLength(1);
|
|
75
|
-
expect(rowsAfter2[0].id).toBe(eventId);
|
|
76
|
-
expect(rowsAfter2[0].routed_to_name).toBe('researcher'); // NOT overwritten to 'coder'
|
|
77
|
-
expect(rowsAfter2[0].first_tool_ts).toBe(firstToolTs); // ts not stomped
|
|
78
|
-
expect(rowsAfter2[0].obeyed).toBe(1);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('case 2: cross-prompt — each prompt’s Agent updates its own pending event', async () => {
|
|
82
|
-
const sess = 'sess-cross-prompt';
|
|
83
|
-
|
|
84
|
-
// Prompt 1 → routing_event #1 pending.
|
|
85
|
-
const eid1 = storage.writeRoutingEvent({
|
|
86
|
-
session_id: sess, project_path: PROJECT, ts: 1000,
|
|
87
|
-
prompt: 'p1', intent_json: '{}',
|
|
88
|
-
obeyed: null,
|
|
89
|
-
});
|
|
90
|
-
// Agent for P1.
|
|
91
|
-
await handler.handle(makeAgentEvent(sess, 'researcher'));
|
|
92
|
-
|
|
93
|
-
// Prompt 2 → routing_event #2 pending.
|
|
94
|
-
const eid2 = storage.writeRoutingEvent({
|
|
95
|
-
session_id: sess, project_path: PROJECT, ts: 2000,
|
|
96
|
-
prompt: 'p2', intent_json: '{}',
|
|
97
|
-
obeyed: null,
|
|
98
|
-
});
|
|
99
|
-
// Agent for P2.
|
|
100
|
-
await handler.handle(makeAgentEvent(sess, 'coder'));
|
|
101
|
-
|
|
102
|
-
const rows = storage.queryRoutingEvents({ session_id: sess });
|
|
103
|
-
expect(rows).toHaveLength(2);
|
|
104
|
-
|
|
105
|
-
const byId = new Map(rows.map(r => [r.id, r]));
|
|
106
|
-
const r1 = byId.get(eid1)!;
|
|
107
|
-
const r2 = byId.get(eid2)!;
|
|
108
|
-
expect(r1.routed_to_name).toBe('researcher');
|
|
109
|
-
expect(r1.obeyed).toBe(1);
|
|
110
|
-
expect(r2.routed_to_name).toBe('coder');
|
|
111
|
-
expect(r2.obeyed).toBe(1);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('case 3: no routing_event in DB — handler does not throw', async () => {
|
|
115
|
-
const sess = 'sess-none';
|
|
116
|
-
await expect(handler.handle(makeAgentEvent(sess, 'researcher'))).resolves.toEqual({ allow: true });
|
|
117
|
-
|
|
118
|
-
const rows = storage.queryRoutingEvents({ session_id: sess });
|
|
119
|
-
expect(rows).toHaveLength(0);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* H2 Phase 1 safety-net: StopHandler behavior summary.
|
|
3
|
-
*
|
|
4
|
-
* Locks the output format and aggregation logic of `generateBehaviorSummary`
|
|
5
|
-
* before its inline SQL (events GROUP BY tool_name / Agent-Task subagent_type /
|
|
6
|
-
* COUNT skill_invocations) is migrated into EventOperations / SkillOperations.
|
|
7
|
-
*
|
|
8
|
-
* Since `generateBehaviorSummary` is `private`, we exercise it indirectly via
|
|
9
|
-
* `handle()` and capture the resume content passed to `resume.save`. That is
|
|
10
|
-
* also the actually-exercised path in production, so this gives a higher-value
|
|
11
|
-
* baseline than reflection-style private access.
|
|
12
|
-
*
|
|
13
|
-
* NOTE on test location: the spec listed `src/tests/...`, but vitest.config.ts
|
|
14
|
-
* only discovers `tests/unit/**` and `tests/integration/**`, so the file is
|
|
15
|
-
* placed under `tests/unit/daemon/` to ensure it actually runs. The semantic
|
|
16
|
-
* intent (a unit test, not an integration test) is preserved.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
20
|
-
import { StopHandler } from '../../../src/daemon/handlers/stop.js';
|
|
21
|
-
import { SQLiteStorage } from '../../../src/core/storage/sqlite.js';
|
|
22
|
-
import type { HistoryExporter } from '../../../src/daemon/handlers/history-exporter.js';
|
|
23
|
-
import type { StopEvent } from '../../../src/core/types.js';
|
|
24
|
-
|
|
25
|
-
const SESSION = 'sess-behavior-summary';
|
|
26
|
-
const PROJECT = '/tmp/proj-behavior-summary';
|
|
27
|
-
|
|
28
|
-
function nowIso(): string {
|
|
29
|
-
return new Date().toISOString();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function makeEvent(): StopEvent {
|
|
33
|
-
return {
|
|
34
|
-
session_id: SESSION,
|
|
35
|
-
project_path: PROJECT,
|
|
36
|
-
timestamp: nowIso(),
|
|
37
|
-
hook_type: 'Stop',
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe('StopHandler.generateBehaviorSummary (safety-net)', () => {
|
|
42
|
-
let storage: SQLiteStorage;
|
|
43
|
-
let exporter: HistoryExporter;
|
|
44
|
-
let saved: { content: string | null };
|
|
45
|
-
|
|
46
|
-
// Build a mock ResumeManager that returns a known stub from generate() and
|
|
47
|
-
// captures whatever the handler ends up passing to save().
|
|
48
|
-
function makeResumeMock(): { generate: ReturnType<typeof vi.fn>; save: ReturnType<typeof vi.fn>; load: ReturnType<typeof vi.fn>; clear: ReturnType<typeof vi.fn> } {
|
|
49
|
-
return {
|
|
50
|
-
generate: vi.fn().mockReturnValue('STUB_RESUME\n_生成时间: 2026-01-01T00:00:00.000Z_\n'),
|
|
51
|
-
save: vi.fn((_p: string, c: string) => { saved.content = c; }),
|
|
52
|
-
load: vi.fn(),
|
|
53
|
-
clear: vi.fn(),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
beforeEach(() => {
|
|
58
|
-
storage = new SQLiteStorage(':memory:');
|
|
59
|
-
saved = { content: null };
|
|
60
|
-
exporter = { export: vi.fn().mockResolvedValue(undefined) } as unknown as HistoryExporter;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
afterEach(() => {
|
|
64
|
-
storage.close();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('does not append summary when storage has no events for the session', async () => {
|
|
68
|
-
const resume = makeResumeMock();
|
|
69
|
-
const handler = new StopHandler(exporter, resume as any, null, storage, null, null);
|
|
70
|
-
|
|
71
|
-
const res = await handler.handle(makeEvent());
|
|
72
|
-
expect(res.allow).toBe(true);
|
|
73
|
-
// resume.save should still be called (resume content exists)
|
|
74
|
-
expect(resume.save).toHaveBeenCalledTimes(1);
|
|
75
|
-
expect(saved.content).not.toBeNull();
|
|
76
|
-
// Behavior summary line MUST NOT appear when there are no tool calls
|
|
77
|
-
expect(saved.content!).not.toContain('会话行为');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('appends a tool-only behavior summary when only tool events exist', async () => {
|
|
81
|
-
// Seed events: 2 Bash, 1 Edit — no Agent/Task, no skill invocations
|
|
82
|
-
storage.writeEvent({
|
|
83
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
84
|
-
hook_type: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'ls' },
|
|
85
|
-
});
|
|
86
|
-
storage.writeEvent({
|
|
87
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
88
|
-
hook_type: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'pwd' },
|
|
89
|
-
});
|
|
90
|
-
storage.writeEvent({
|
|
91
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
92
|
-
hook_type: 'PreToolUse', tool_name: 'Edit', tool_input: { file_path: '/x' },
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const resume = makeResumeMock();
|
|
96
|
-
const handler = new StopHandler(exporter, resume as any, null, storage, null, null);
|
|
97
|
-
|
|
98
|
-
const res = await handler.handle(makeEvent());
|
|
99
|
-
expect(res.allow).toBe(true);
|
|
100
|
-
expect(saved.content).not.toBeNull();
|
|
101
|
-
|
|
102
|
-
// Must contain a behavior summary line with total tool calls
|
|
103
|
-
expect(saved.content!).toContain('**会话行为**');
|
|
104
|
-
expect(saved.content!).toContain('工具调用 3 次');
|
|
105
|
-
expect(saved.content!).toContain('Agent 委托 0 次');
|
|
106
|
-
expect(saved.content!).toContain('Skill 调用 0 次');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('includes Agent delegation counts and agent type names', async () => {
|
|
110
|
-
// 2 normal tools + 2 Task delegations to different subagents
|
|
111
|
-
storage.writeEvent({
|
|
112
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
113
|
-
hook_type: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'ls' },
|
|
114
|
-
});
|
|
115
|
-
storage.writeEvent({
|
|
116
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
117
|
-
hook_type: 'PreToolUse', tool_name: 'Read', tool_input: { file_path: '/a' },
|
|
118
|
-
});
|
|
119
|
-
storage.writeEvent({
|
|
120
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
121
|
-
hook_type: 'PreToolUse', tool_name: 'Task',
|
|
122
|
-
tool_input: { subagent_type: 'explore', description: 'find x' },
|
|
123
|
-
});
|
|
124
|
-
storage.writeEvent({
|
|
125
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
126
|
-
hook_type: 'PreToolUse', tool_name: 'Task',
|
|
127
|
-
tool_input: { subagent_type: 'coder', description: 'fix y' },
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const resume = makeResumeMock();
|
|
131
|
-
const handler = new StopHandler(exporter, resume as any, null, storage, null, null);
|
|
132
|
-
|
|
133
|
-
await handler.handle(makeEvent());
|
|
134
|
-
expect(saved.content).not.toBeNull();
|
|
135
|
-
|
|
136
|
-
const summary = saved.content!;
|
|
137
|
-
expect(summary).toContain('工具调用 4 次');
|
|
138
|
-
expect(summary).toContain('Agent 委托 2 次');
|
|
139
|
-
// Agent names appear in parenthesised detail
|
|
140
|
-
expect(summary).toMatch(/explore|coder/);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('includes skill invocation count when skill_invocations rows exist', async () => {
|
|
144
|
-
storage.writeEvent({
|
|
145
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
146
|
-
hook_type: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'ls' },
|
|
147
|
-
});
|
|
148
|
-
storage.writeSkillInvocation({
|
|
149
|
-
id: 'si-bs-1', route_request_id: null, session_id: SESSION,
|
|
150
|
-
agent_id: null, skill_id: 'official-tdd',
|
|
151
|
-
invocation_type: 'dynamic', reason: null,
|
|
152
|
-
workflow: null, phase: null, feature_slug: null, artifact_path: null,
|
|
153
|
-
depth: 0, success: 1, error: null, timestamp: Date.now(),
|
|
154
|
-
});
|
|
155
|
-
storage.writeSkillInvocation({
|
|
156
|
-
id: 'si-bs-2', route_request_id: null, session_id: SESSION,
|
|
157
|
-
agent_id: null, skill_id: 'official-debug',
|
|
158
|
-
invocation_type: 'dynamic', reason: null,
|
|
159
|
-
workflow: null, phase: null, feature_slug: null, artifact_path: null,
|
|
160
|
-
depth: 0, success: 1, error: null, timestamp: Date.now(),
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const resume = makeResumeMock();
|
|
164
|
-
const handler = new StopHandler(exporter, resume as any, null, storage, null, null);
|
|
165
|
-
|
|
166
|
-
await handler.handle(makeEvent());
|
|
167
|
-
expect(saved.content).not.toBeNull();
|
|
168
|
-
expect(saved.content!).toContain('Skill 调用 2 次');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('does not include events from other sessions in the summary', async () => {
|
|
172
|
-
// Same session — 1 tool call
|
|
173
|
-
storage.writeEvent({
|
|
174
|
-
session_id: SESSION, project_path: PROJECT, timestamp: nowIso(),
|
|
175
|
-
hook_type: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'ls' },
|
|
176
|
-
});
|
|
177
|
-
// Different session — 5 tool calls + 3 skill invocations: MUST be excluded
|
|
178
|
-
for (let i = 0; i < 5; i++) {
|
|
179
|
-
storage.writeEvent({
|
|
180
|
-
session_id: 'other-session', project_path: PROJECT, timestamp: nowIso(),
|
|
181
|
-
hook_type: 'PreToolUse', tool_name: 'Bash', tool_input: { command: 'noise' },
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
for (let i = 0; i < 3; i++) {
|
|
185
|
-
storage.writeSkillInvocation({
|
|
186
|
-
id: `si-noise-${i}`, route_request_id: null, session_id: 'other-session',
|
|
187
|
-
agent_id: null, skill_id: 'official-noise',
|
|
188
|
-
invocation_type: 'dynamic', reason: null,
|
|
189
|
-
workflow: null, phase: null, feature_slug: null, artifact_path: null,
|
|
190
|
-
depth: 0, success: 1, error: null, timestamp: Date.now(),
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const resume = makeResumeMock();
|
|
195
|
-
const handler = new StopHandler(exporter, resume as any, null, storage, null, null);
|
|
196
|
-
|
|
197
|
-
await handler.handle(makeEvent());
|
|
198
|
-
expect(saved.content).not.toBeNull();
|
|
199
|
-
expect(saved.content!).toContain('工具调用 1 次');
|
|
200
|
-
expect(saved.content!).toContain('Skill 调用 0 次');
|
|
201
|
-
});
|
|
202
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 单测:TaskSegmenter.completeCurrentTask recover fallback
|
|
3
|
-
*
|
|
4
|
-
* 覆盖:
|
|
5
|
-
* - Map 为空但 DB 有 active task → completeCurrentTask 仍能将其转为 completed
|
|
6
|
-
* - Map 不为空的正常路径不退化
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
10
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
11
|
-
import { tmpdir } from 'node:os';
|
|
12
|
-
import { join } from 'node:path';
|
|
13
|
-
import { SQLiteStorage } from '../../../src/core/storage/sqlite.js';
|
|
14
|
-
import { TaskSegmenter } from '../../../src/daemon/services/task-segmenter.js';
|
|
15
|
-
|
|
16
|
-
const SESSION = 'sess-segmenter-recover';
|
|
17
|
-
|
|
18
|
-
function openStorage(tmp: string): SQLiteStorage {
|
|
19
|
-
return new SQLiteStorage(join(tmp, 'data.db'));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('TaskSegmenter.completeCurrentTask recover fallback', () => {
|
|
23
|
-
let tmp: string;
|
|
24
|
-
let storage: SQLiteStorage;
|
|
25
|
-
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
tmp = mkdtempSync(join(tmpdir(), 'forge-segmenter-recover-'));
|
|
28
|
-
storage = openStorage(tmp);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
try { storage.close(); } catch { /* ignore */ }
|
|
33
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('Map 为空但 DB 有 active task → completeCurrentTask 将其转 completed', () => {
|
|
37
|
-
// 直接写 active task 到 DB(不经过 processPrompt,模拟 daemon 重启后内存丢失)
|
|
38
|
-
storage.writeTask({
|
|
39
|
-
id: 'task-in-db',
|
|
40
|
-
session_id: SESSION,
|
|
41
|
-
title: 'Persisted active task',
|
|
42
|
-
start_time: '2026-01-01T10:00:00.000Z',
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// 确认此刻 DB 中是 active
|
|
46
|
-
expect(storage.getTask('task-in-db')?.status).toBe('active');
|
|
47
|
-
|
|
48
|
-
// 创建新的 TaskSegmenter,Map 为空(模拟重启)
|
|
49
|
-
const segmenter = new TaskSegmenter(storage);
|
|
50
|
-
|
|
51
|
-
// Stop hook 触发 completeCurrentTask,Map 中没有该 session 的 entry
|
|
52
|
-
segmenter.completeCurrentTask(SESSION, '2026-01-01T10:30:00.000Z');
|
|
53
|
-
|
|
54
|
-
// task 应该被 recover 并标记为 completed
|
|
55
|
-
const task = storage.getTask('task-in-db');
|
|
56
|
-
expect(task?.status).toBe('completed');
|
|
57
|
-
expect(task?.end_time).toBe('2026-01-01T10:30:00.000Z');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('DB 中无 active task 时 completeCurrentTask 安全退出(不报错)', () => {
|
|
61
|
-
// 完全空 DB,completeCurrentTask 不应抛出
|
|
62
|
-
const segmenter = new TaskSegmenter(storage);
|
|
63
|
-
expect(() => {
|
|
64
|
-
segmenter.completeCurrentTask(SESSION, '2026-01-01T10:30:00.000Z');
|
|
65
|
-
}).not.toThrow();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('Map 有 entry 时(正常路径)completeCurrentTask 正常关闭 task', () => {
|
|
69
|
-
const segmenter = new TaskSegmenter(storage);
|
|
70
|
-
|
|
71
|
-
// 通过 processPrompt 走正常路径,将 task 写入 Map
|
|
72
|
-
const taskId = segmenter.processPrompt(SESSION, 'implement feature', '2026-01-01T10:00:00.000Z');
|
|
73
|
-
|
|
74
|
-
// 确认 DB 中是 active
|
|
75
|
-
expect(storage.getTask(taskId)?.status).toBe('active');
|
|
76
|
-
|
|
77
|
-
// completeCurrentTask 正常关闭
|
|
78
|
-
segmenter.completeCurrentTask(SESSION, '2026-01-01T10:30:00.000Z');
|
|
79
|
-
|
|
80
|
-
const task = storage.getTask(taskId);
|
|
81
|
-
expect(task?.status).toBe('completed');
|
|
82
|
-
expect(task?.end_time).toBe('2026-01-01T10:30:00.000Z');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
getCommand,
|
|
4
|
-
getFilePath,
|
|
5
|
-
getUserPrompt,
|
|
6
|
-
getSubagentType,
|
|
7
|
-
} from '../../src/core/event-fields.js';
|
|
8
|
-
import type { ForgeEvent } from '../../src/core/types.js';
|
|
9
|
-
|
|
10
|
-
function makeEvent(overrides: Partial<ForgeEvent> = {}): ForgeEvent {
|
|
11
|
-
return {
|
|
12
|
-
session_id: 's',
|
|
13
|
-
project_path: '/p',
|
|
14
|
-
timestamp: '2026-05-18T00:00:00Z',
|
|
15
|
-
hook_type: 'PreToolUse',
|
|
16
|
-
...overrides,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('event-fields getters', () => {
|
|
21
|
-
describe('getCommand', () => {
|
|
22
|
-
it('returns undefined when tool_input is missing', () => {
|
|
23
|
-
expect(getCommand(makeEvent())).toBeUndefined();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('returns undefined when command is not a string', () => {
|
|
27
|
-
expect(getCommand(makeEvent({ tool_input: { command: 42 as unknown as string } }))).toBeUndefined();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('returns the command string when present', () => {
|
|
31
|
-
expect(getCommand(makeEvent({ tool_input: { command: 'ls -la' } }))).toBe('ls -la');
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('getFilePath', () => {
|
|
36
|
-
it('returns undefined when tool_input is missing', () => {
|
|
37
|
-
expect(getFilePath(makeEvent())).toBeUndefined();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('returns undefined when file_path is wrong type', () => {
|
|
41
|
-
expect(getFilePath(makeEvent({ tool_input: { file_path: 123 as unknown as string } }))).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('returns file_path when present', () => {
|
|
45
|
-
expect(getFilePath(makeEvent({ tool_input: { file_path: '/foo/bar.ts' } }))).toBe('/foo/bar.ts');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('falls back to notebook_path when file_path is missing', () => {
|
|
49
|
-
expect(getFilePath(makeEvent({ tool_input: { notebook_path: '/x.ipynb' } }))).toBe('/x.ipynb');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('returns undefined for empty string', () => {
|
|
53
|
-
expect(getFilePath(makeEvent({ tool_input: { file_path: '' } }))).toBeUndefined();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('getUserPrompt', () => {
|
|
58
|
-
it('returns undefined when neither field set', () => {
|
|
59
|
-
expect(getUserPrompt(makeEvent())).toBeUndefined();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('returns top-level user_prompt when set', () => {
|
|
63
|
-
expect(getUserPrompt(makeEvent({ user_prompt: 'hi' }))).toBe('hi');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('falls back to tool_input.user_prompt envelope', () => {
|
|
67
|
-
expect(getUserPrompt(makeEvent({ tool_input: { user_prompt: 'envelope' } }))).toBe('envelope');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('returns undefined when fallback is wrong type', () => {
|
|
71
|
-
expect(getUserPrompt(makeEvent({ tool_input: { user_prompt: 7 as unknown as string } }))).toBeUndefined();
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('getSubagentType', () => {
|
|
76
|
-
it('returns undefined when missing', () => {
|
|
77
|
-
expect(getSubagentType(makeEvent())).toBeUndefined();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('returns undefined when wrong type', () => {
|
|
81
|
-
expect(getSubagentType(makeEvent({ tool_input: { subagent_type: {} as unknown as string } }))).toBeUndefined();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns the subagent_type string', () => {
|
|
85
|
-
expect(getSubagentType(makeEvent({ tool_input: { subagent_type: 'planner' } }))).toBe('planner');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|