@winspan/claude-forge 8.53.2 → 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 +7 -3
- 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/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 +19 -4
- package/dist/daemon/index.js.map +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +13 -2
- package/dist/skills/registry.js.map +1 -1
- package/dist/skills/semantic-matcher.d.ts +2 -2
- package/dist/skills/semantic-matcher.d.ts.map +1 -1
- package/dist/skills/semantic-matcher.js +14 -19
- package/dist/skills/semantic-matcher.js.map +1 -1
- package/dist/skills/upgrade-engine.d.ts +3 -1
- package/dist/skills/upgrade-engine.d.ts.map +1 -1
- package/dist/skills/upgrade-engine.js +25 -14
- package/dist/skills/upgrade-engine.js.map +1 -1
- 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/skill-ai-upgrade-spec-20260518-1930.md +0 -297
- 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/daemon-skill-sync-changelog-20260518-2000.md +0 -22
- 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/skill-ai-upgrade-changelog-20260518-1930.md +0 -49
- 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 -328
- 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 -67
- 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 -312
- 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/daemon/skill-sync.ts +0 -88
- 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 -52
- package/src/skills/official/find-skills.md +0 -142
- package/src/skills/official/official-api-design.md +0 -30
- package/src/skills/official/official-architecture-decision.md +0 -41
- package/src/skills/official/official-bmad.md +0 -118
- package/src/skills/official/official-db-schema-design.md +0 -34
- package/src/skills/official/official-debug.md +0 -25
- 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 -32
- package/src/skills/official/official-spec-driven-design.md +0 -31
- package/src/skills/official/planning-with-files.md +0 -241
- package/src/skills/official/ui-ux-pro-max.md +0 -105
- package/src/skills/official/webapp-testing.md +0 -96
- package/src/skills/official-skills.ts +0 -89
- package/src/skills/registry.ts +0 -355
- package/src/skills/semantic-matcher.ts +0 -234
- 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/skills/upgrade-engine.ts +0 -541
- package/src/skills/upgrade-prompt.ts +0 -84
- 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/skill-sync.test.ts +0 -75
- 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/skills/upgrade-engine-parse.test.ts +0 -138
- package/tests/unit/skills/upgrade-engine.test.ts +0 -401
- package/tests/unit/skills/upgrade-prompt.test.ts +0 -89
- 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,211 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { InvocationGuard } from '../../../src/skills/invocation-guard.js';
|
|
3
|
-
|
|
4
|
-
describe('InvocationGuard - TTL Mechanism', () => {
|
|
5
|
-
let guard: InvocationGuard;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
guard = new InvocationGuard();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
guard.stopCleanupTimer();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('TTL expiration', () => {
|
|
16
|
-
it('should allow invocation after session expires', () => {
|
|
17
|
-
const sessionId = 'test-session-1';
|
|
18
|
-
const skillId = 'test-skill';
|
|
19
|
-
|
|
20
|
-
// Record initial invocation
|
|
21
|
-
guard.record(sessionId, skillId);
|
|
22
|
-
|
|
23
|
-
// Verify skill is blocked (idempotent guard)
|
|
24
|
-
let result = guard.check(sessionId, skillId);
|
|
25
|
-
expect(result.allowed).toBe(false);
|
|
26
|
-
expect(result.reason).toContain('already invoked');
|
|
27
|
-
|
|
28
|
-
// Fast-forward time by 31 minutes (beyond TTL)
|
|
29
|
-
const stats = guard.getStats(sessionId);
|
|
30
|
-
if (stats) {
|
|
31
|
-
stats.lastAccessTime = Date.now() - 31 * 60 * 1000;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Should allow invocation after expiration
|
|
35
|
-
result = guard.check(sessionId, skillId);
|
|
36
|
-
expect(result.allowed).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should update lastAccessTime on record', () => {
|
|
40
|
-
const sessionId = 'test-session-2';
|
|
41
|
-
const skillId = 'test-skill';
|
|
42
|
-
|
|
43
|
-
const before = Date.now();
|
|
44
|
-
guard.record(sessionId, skillId);
|
|
45
|
-
const after = Date.now();
|
|
46
|
-
|
|
47
|
-
const stats = guard.getStats(sessionId);
|
|
48
|
-
expect(stats).not.toBeNull();
|
|
49
|
-
expect(stats!.lastAccessTime).toBeGreaterThanOrEqual(before);
|
|
50
|
-
expect(stats!.lastAccessTime).toBeLessThanOrEqual(after);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should update lastAccessTime on complete', () => {
|
|
54
|
-
const sessionId = 'test-session-3';
|
|
55
|
-
const skillId = 'test-skill';
|
|
56
|
-
|
|
57
|
-
guard.record(sessionId, skillId);
|
|
58
|
-
const initialTime = guard.getStats(sessionId)!.lastAccessTime;
|
|
59
|
-
|
|
60
|
-
// Wait a bit
|
|
61
|
-
vi.useFakeTimers();
|
|
62
|
-
vi.advanceTimersByTime(1000);
|
|
63
|
-
|
|
64
|
-
guard.complete(sessionId);
|
|
65
|
-
const updatedTime = guard.getStats(sessionId)!.lastAccessTime;
|
|
66
|
-
|
|
67
|
-
expect(updatedTime).toBeGreaterThan(initialTime);
|
|
68
|
-
|
|
69
|
-
vi.useRealTimers();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('automatic cleanup', () => {
|
|
74
|
-
it('should clean up expired sessions', () => {
|
|
75
|
-
const sessionId1 = 'expired-session-1';
|
|
76
|
-
const sessionId2 = 'active-session-2';
|
|
77
|
-
|
|
78
|
-
// Record two sessions
|
|
79
|
-
guard.record(sessionId1, 'skill-1');
|
|
80
|
-
guard.record(sessionId2, 'skill-2');
|
|
81
|
-
|
|
82
|
-
expect(guard.getSessionCount()).toBe(2);
|
|
83
|
-
|
|
84
|
-
// Expire first session
|
|
85
|
-
const stats1 = guard.getStats(sessionId1);
|
|
86
|
-
if (stats1) {
|
|
87
|
-
stats1.lastAccessTime = Date.now() - 31 * 60 * 1000;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Trigger cleanup manually
|
|
91
|
-
(guard as any).cleanupExpiredSessions();
|
|
92
|
-
|
|
93
|
-
// Only active session should remain
|
|
94
|
-
expect(guard.getSessionCount()).toBe(1);
|
|
95
|
-
expect(guard.getStats(sessionId1)).toBeNull();
|
|
96
|
-
expect(guard.getStats(sessionId2)).not.toBeNull();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should not clean up active sessions', () => {
|
|
100
|
-
const sessionId = 'active-session';
|
|
101
|
-
|
|
102
|
-
guard.record(sessionId, 'skill-1');
|
|
103
|
-
expect(guard.getSessionCount()).toBe(1);
|
|
104
|
-
|
|
105
|
-
// Trigger cleanup
|
|
106
|
-
(guard as any).cleanupExpiredSessions();
|
|
107
|
-
|
|
108
|
-
// Session should still exist
|
|
109
|
-
expect(guard.getSessionCount()).toBe(1);
|
|
110
|
-
expect(guard.getStats(sessionId)).not.toBeNull();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should handle empty sessions map', () => {
|
|
114
|
-
expect(guard.getSessionCount()).toBe(0);
|
|
115
|
-
|
|
116
|
-
// Should not throw
|
|
117
|
-
expect(() => {
|
|
118
|
-
(guard as any).cleanupExpiredSessions();
|
|
119
|
-
}).not.toThrow();
|
|
120
|
-
|
|
121
|
-
expect(guard.getSessionCount()).toBe(0);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('cleanup timer', () => {
|
|
126
|
-
it('should start cleanup timer on construction', () => {
|
|
127
|
-
const newGuard = new InvocationGuard();
|
|
128
|
-
expect((newGuard as any).cleanupTimer).not.toBeNull();
|
|
129
|
-
newGuard.stopCleanupTimer();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should stop cleanup timer', () => {
|
|
133
|
-
guard.stopCleanupTimer();
|
|
134
|
-
expect((guard as any).cleanupTimer).toBeNull();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('should not start multiple timers', () => {
|
|
138
|
-
const timer1 = (guard as any).cleanupTimer;
|
|
139
|
-
(guard as any).startCleanupTimer();
|
|
140
|
-
const timer2 = (guard as any).cleanupTimer;
|
|
141
|
-
|
|
142
|
-
expect(timer1).toBe(timer2);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe('getSessionCount', () => {
|
|
147
|
-
it('should return 0 for empty guard', () => {
|
|
148
|
-
expect(guard.getSessionCount()).toBe(0);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should return correct count after recording', () => {
|
|
152
|
-
guard.record('session-1', 'skill-1');
|
|
153
|
-
expect(guard.getSessionCount()).toBe(1);
|
|
154
|
-
|
|
155
|
-
guard.record('session-2', 'skill-2');
|
|
156
|
-
expect(guard.getSessionCount()).toBe(2);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('should decrease count after clear', () => {
|
|
160
|
-
guard.record('session-1', 'skill-1');
|
|
161
|
-
guard.record('session-2', 'skill-2');
|
|
162
|
-
expect(guard.getSessionCount()).toBe(2);
|
|
163
|
-
|
|
164
|
-
guard.clear('session-1');
|
|
165
|
-
expect(guard.getSessionCount()).toBe(1);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('integration with existing functionality', () => {
|
|
170
|
-
it('should maintain depth tracking with TTL', () => {
|
|
171
|
-
const sessionId = 'test-session';
|
|
172
|
-
|
|
173
|
-
guard.record(sessionId, 'skill-1');
|
|
174
|
-
expect(guard.getStats(sessionId)!.depth).toBe(1);
|
|
175
|
-
|
|
176
|
-
guard.record(sessionId, 'skill-2');
|
|
177
|
-
expect(guard.getStats(sessionId)!.depth).toBe(2);
|
|
178
|
-
|
|
179
|
-
guard.complete(sessionId);
|
|
180
|
-
expect(guard.getStats(sessionId)!.depth).toBe(1);
|
|
181
|
-
|
|
182
|
-
// lastAccessTime should be updated
|
|
183
|
-
expect(guard.getStats(sessionId)!.lastAccessTime).toBeLessThanOrEqual(Date.now());
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should maintain total count with TTL', () => {
|
|
187
|
-
const sessionId = 'test-session';
|
|
188
|
-
|
|
189
|
-
guard.record(sessionId, 'skill-1');
|
|
190
|
-
expect(guard.getStats(sessionId)!.total).toBe(1);
|
|
191
|
-
|
|
192
|
-
guard.record(sessionId, 'skill-2');
|
|
193
|
-
expect(guard.getStats(sessionId)!.total).toBe(2);
|
|
194
|
-
|
|
195
|
-
// lastAccessTime should be updated
|
|
196
|
-
expect(guard.getStats(sessionId)!.lastAccessTime).toBeLessThanOrEqual(Date.now());
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should maintain calledSkills set with TTL', () => {
|
|
200
|
-
const sessionId = 'test-session';
|
|
201
|
-
|
|
202
|
-
guard.record(sessionId, 'skill-1');
|
|
203
|
-
guard.record(sessionId, 'skill-2');
|
|
204
|
-
|
|
205
|
-
const stats = guard.getStats(sessionId);
|
|
206
|
-
expect(stats!.calledSkills.has('skill-1')).toBe(true);
|
|
207
|
-
expect(stats!.calledSkills.has('skill-2')).toBe(true);
|
|
208
|
-
expect(stats!.lastAccessTime).toBeLessThanOrEqual(Date.now());
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { loadOfficialSkills } from '../../../src/skills/official-skills.js';
|
|
6
|
-
|
|
7
|
-
describe('loadOfficialSkills', () => {
|
|
8
|
-
let tempDir: string;
|
|
9
|
-
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
tempDir = mkdtempSync(join(tmpdir(), 'forge-official-skills-test-'));
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterAll(() => {
|
|
15
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('loads all .md files from the given directory', () => {
|
|
19
|
-
writeFileSync(
|
|
20
|
-
join(tempDir, 'test-skill-a.md'),
|
|
21
|
-
`---\nname: test-skill-a\nversion: 1.0.0\ndescription: "Test skill A"\ntags: [tag1, tag2]\n---\n\n# Test Skill A\n\nBody content here.`
|
|
22
|
-
);
|
|
23
|
-
writeFileSync(
|
|
24
|
-
join(tempDir, 'test-skill-b.md'),
|
|
25
|
-
`---\nname: test-skill-b\nversion: 2.0.0\ndescription: "Test skill B"\nkeywords: [kw1, kw2]\n---\n\n# Test Skill B\n\nMore content.`
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const skills = loadOfficialSkills(tempDir);
|
|
29
|
-
const names = skills.map(s => s.name);
|
|
30
|
-
expect(names).toContain('test-skill-a');
|
|
31
|
-
expect(names).toContain('test-skill-b');
|
|
32
|
-
expect(skills.length).toBeGreaterThanOrEqual(2);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('parses frontmatter correctly (name, version, description, keywords from tags)', () => {
|
|
36
|
-
const skillDir = mkdtempSync(join(tmpdir(), 'forge-frontmatter-test-'));
|
|
37
|
-
try {
|
|
38
|
-
writeFileSync(
|
|
39
|
-
join(skillDir, 'my-skill.md'),
|
|
40
|
-
`---\nname: my-skill\nversion: 3.1.0\ndescription: "My test skill"\ntags: [alpha, beta, gamma]\n---\n\n# My Skill\n\nContent.`
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const skills = loadOfficialSkills(skillDir);
|
|
44
|
-
expect(skills).toHaveLength(1);
|
|
45
|
-
const skill = skills[0];
|
|
46
|
-
expect(skill.name).toBe('my-skill');
|
|
47
|
-
expect(skill.version).toBe('3.1.0');
|
|
48
|
-
expect(skill.description).toBe('My test skill');
|
|
49
|
-
expect(skill.keywords).toEqual(['alpha', 'beta', 'gamma']);
|
|
50
|
-
} finally {
|
|
51
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('parses frontmatter correctly (keywords field takes precedence over tags)', () => {
|
|
56
|
-
const skillDir = mkdtempSync(join(tmpdir(), 'forge-kw-test-'));
|
|
57
|
-
try {
|
|
58
|
-
writeFileSync(
|
|
59
|
-
join(skillDir, 'kw-skill.md'),
|
|
60
|
-
`---\nname: kw-skill\nversion: 1.0.0\ndescription: "KW skill"\nkeywords: [kw-a, kw-b]\ntags: [tag-a]\n---\n\n# KW Skill`
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const skills = loadOfficialSkills(skillDir);
|
|
64
|
-
expect(skills).toHaveLength(1);
|
|
65
|
-
// keywords takes precedence (data.keywords ?? data.tags => keywords wins)
|
|
66
|
-
expect(skills[0].keywords).toEqual(['kw-a', 'kw-b']);
|
|
67
|
-
} finally {
|
|
68
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('falls back to filename as name when frontmatter name is missing', () => {
|
|
73
|
-
const skillDir = mkdtempSync(join(tmpdir(), 'forge-fallback-test-'));
|
|
74
|
-
try {
|
|
75
|
-
writeFileSync(
|
|
76
|
-
join(skillDir, 'unnamed-skill.md'),
|
|
77
|
-
`---\nversion: 1.0.0\n---\n\n# Unnamed Skill`
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
const skills = loadOfficialSkills(skillDir);
|
|
81
|
-
expect(skills).toHaveLength(1);
|
|
82
|
-
expect(skills[0].name).toBe('unnamed-skill');
|
|
83
|
-
} finally {
|
|
84
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('ignores non-.md files in the directory', () => {
|
|
89
|
-
const skillDir = mkdtempSync(join(tmpdir(), 'forge-nonmd-test-'));
|
|
90
|
-
try {
|
|
91
|
-
writeFileSync(join(skillDir, 'skill.md'), `---\nname: skill\n---\n# Skill`);
|
|
92
|
-
writeFileSync(join(skillDir, 'README.txt'), 'not a skill');
|
|
93
|
-
writeFileSync(join(skillDir, 'index.json'), '{}');
|
|
94
|
-
|
|
95
|
-
const skills = loadOfficialSkills(skillDir);
|
|
96
|
-
expect(skills).toHaveLength(1);
|
|
97
|
-
expect(skills[0].name).toBe('skill');
|
|
98
|
-
} finally {
|
|
99
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('throws a meaningful error when directory does not exist', () => {
|
|
104
|
-
const nonExistentDir = join(tmpdir(), 'forge-does-not-exist-' + Date.now());
|
|
105
|
-
expect(() => loadOfficialSkills(nonExistentDir)).toThrow(/Official skills directory not found/);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('loads all official skills from the actual built-in directory', async () => {
|
|
109
|
-
// Verify the real built-in directory works end-to-end
|
|
110
|
-
const { fileURLToPath } = await import('node:url');
|
|
111
|
-
const { dirname, join: pathJoin } = await import('node:path');
|
|
112
|
-
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
113
|
-
const builtinDir = pathJoin(thisDir, '../../../src/skills/official');
|
|
114
|
-
|
|
115
|
-
const skills = loadOfficialSkills(builtinDir);
|
|
116
|
-
// Should have 17 official skills
|
|
117
|
-
expect(skills.length).toBeGreaterThanOrEqual(17);
|
|
118
|
-
// Spot-check a known skill
|
|
119
|
-
const harness = skills.find(s => s.name === 'official-harness-engineering');
|
|
120
|
-
expect(harness).toBeDefined();
|
|
121
|
-
expect(harness!.version).toBe('2.0.1');
|
|
122
|
-
expect(harness!.description).toContain('Harness Engineering');
|
|
123
|
-
// path should NOT be '<built-in>'
|
|
124
|
-
expect(harness!.name).toBe('official-harness-engineering');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for SkillRegistry multi-format support
|
|
3
|
-
* Tests both flat .md and directory-based SKILL.md formats
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect } from 'vitest';
|
|
7
|
-
import matter from 'gray-matter';
|
|
8
|
-
|
|
9
|
-
describe('SkillRegistry - Multi-format Support', () => {
|
|
10
|
-
it('should parse flat .md format with description', () => {
|
|
11
|
-
const flatSkillContent = `---
|
|
12
|
-
name: flat-skill
|
|
13
|
-
description: A flat format skill for testing
|
|
14
|
-
keywords: [test, flat, format]
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
# Flat Skill
|
|
18
|
-
|
|
19
|
-
This is a flat format skill.`;
|
|
20
|
-
|
|
21
|
-
const parsed = matter(flatSkillContent);
|
|
22
|
-
expect(parsed.data.name).toBe('flat-skill');
|
|
23
|
-
expect(parsed.data.description).toBe('A flat format skill for testing');
|
|
24
|
-
expect(parsed.data.keywords).toEqual(['test', 'flat', 'format']);
|
|
25
|
-
expect(parsed.content.trim()).toContain('# Flat Skill');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should parse directory format SKILL.md with description', () => {
|
|
29
|
-
const dirSkillContent = `---
|
|
30
|
-
name: dir-skill
|
|
31
|
-
description: A directory format skill from agent-skills
|
|
32
|
-
keywords: [agent, directory, skill]
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
# Directory Skill
|
|
36
|
-
|
|
37
|
-
This is a directory format skill (agent-skills format).`;
|
|
38
|
-
|
|
39
|
-
const parsed = matter(dirSkillContent);
|
|
40
|
-
expect(parsed.data.name).toBe('dir-skill');
|
|
41
|
-
expect(parsed.data.description).toBe('A directory format skill from agent-skills');
|
|
42
|
-
expect(parsed.data.keywords).toEqual(['agent', 'directory', 'skill']);
|
|
43
|
-
expect(parsed.content.trim()).toContain('# Directory Skill');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should handle skills without keywords', () => {
|
|
47
|
-
const noKeywordsContent = `---
|
|
48
|
-
name: no-keywords-skill
|
|
49
|
-
description: A skill without keywords for fallback testing
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
# No Keywords Skill
|
|
53
|
-
|
|
54
|
-
This skill has no keywords, should fallback to description matching.`;
|
|
55
|
-
|
|
56
|
-
const parsed = matter(noKeywordsContent);
|
|
57
|
-
expect(parsed.data.name).toBe('no-keywords-skill');
|
|
58
|
-
expect(parsed.data.description).toBe('A skill without keywords for fallback testing');
|
|
59
|
-
expect(parsed.data.keywords).toBeUndefined();
|
|
60
|
-
expect(parsed.content.trim()).toContain('# No Keywords Skill');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should handle skills without description', () => {
|
|
64
|
-
const noDescContent = `---
|
|
65
|
-
name: no-desc-skill
|
|
66
|
-
keywords: [test, nodesc]
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
# No Description Skill
|
|
70
|
-
|
|
71
|
-
This skill has no description field.`;
|
|
72
|
-
|
|
73
|
-
const parsed = matter(noDescContent);
|
|
74
|
-
expect(parsed.data.name).toBe('no-desc-skill');
|
|
75
|
-
expect(parsed.data.description).toBeUndefined();
|
|
76
|
-
expect(parsed.data.keywords).toEqual(['test', 'nodesc']);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should handle empty frontmatter gracefully', () => {
|
|
80
|
-
const emptyFrontmatterContent = `---
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
# Minimal Skill
|
|
84
|
-
|
|
85
|
-
Just content, no metadata.`;
|
|
86
|
-
|
|
87
|
-
const parsed = matter(emptyFrontmatterContent);
|
|
88
|
-
expect(parsed.data).toEqual({});
|
|
89
|
-
expect(parsed.content.trim()).toContain('# Minimal Skill');
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for AI response parsing in upgrade-engine.ts (evaluateWithAI edge cases).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
-
import { evaluateWithAI } from '../../../src/skills/upgrade-engine.js';
|
|
7
|
-
import type { CandidateSkill } from '../../../src/skills/upgrade-engine.js';
|
|
8
|
-
import type { OfficialSkill } from '../../../src/skills/official-skills.js';
|
|
9
|
-
import type { ClaudeProvider } from '../../../src/core/ai/provider.js';
|
|
10
|
-
|
|
11
|
-
const candidate: CandidateSkill = {
|
|
12
|
-
id: 'test-skill',
|
|
13
|
-
source: 'agent-skills',
|
|
14
|
-
filePath: '/tmp/test/SKILL.md',
|
|
15
|
-
name: 'test-skill',
|
|
16
|
-
description: 'A test skill',
|
|
17
|
-
keywords: ['test'],
|
|
18
|
-
content: '# Test Skill',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const official: OfficialSkill = {
|
|
22
|
-
name: 'official-test',
|
|
23
|
-
version: '1.0.0',
|
|
24
|
-
description: 'Official test skill',
|
|
25
|
-
keywords: ['test'],
|
|
26
|
-
content: '# Official Test Skill',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function makeProvider(value: string): ClaudeProvider {
|
|
30
|
-
return {
|
|
31
|
-
complete: vi.fn().mockResolvedValue(value),
|
|
32
|
-
} as unknown as ClaudeProvider;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe('evaluateWithAI — response parsing', () => {
|
|
36
|
-
it('non-JSON text → needs_review=true, action=skip', async () => {
|
|
37
|
-
const provider = makeProvider('Sorry, I cannot evaluate this.');
|
|
38
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
39
|
-
expect(result.needs_review).toBe(true);
|
|
40
|
-
expect(result.action).toBe('skip');
|
|
41
|
-
expect(result.confidence).toBe(0);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('partial JSON (truncated) → needs_review=true', async () => {
|
|
45
|
-
const provider = makeProvider('{"action":"upgrade","confidence":80,');
|
|
46
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
47
|
-
expect(result.needs_review).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('valid JSON with action=upgrade → parsed correctly', async () => {
|
|
51
|
-
const json = JSON.stringify({
|
|
52
|
-
action: 'upgrade',
|
|
53
|
-
confidence: 90,
|
|
54
|
-
reasoning: '候选质量更高',
|
|
55
|
-
merged_content: null,
|
|
56
|
-
});
|
|
57
|
-
const provider = makeProvider(json);
|
|
58
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
59
|
-
expect(result.action).toBe('upgrade');
|
|
60
|
-
expect(result.confidence).toBe(90);
|
|
61
|
-
expect(result.reasoning).toBe('候选质量更高');
|
|
62
|
-
expect(result.needs_review).toBeUndefined();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('valid JSON with action=merge and merged_content → parsed correctly', async () => {
|
|
66
|
-
const mergedMd = '---\nname: merged\n---\n# Merged';
|
|
67
|
-
const json = JSON.stringify({
|
|
68
|
-
action: 'merge',
|
|
69
|
-
confidence: 75,
|
|
70
|
-
reasoning: '各有特色章节',
|
|
71
|
-
merged_content: mergedMd,
|
|
72
|
-
});
|
|
73
|
-
const provider = makeProvider(json);
|
|
74
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
75
|
-
expect(result.action).toBe('merge');
|
|
76
|
-
expect(result.merged_content).toBe(mergedMd);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('JSON with unknown action → defaults to skip', async () => {
|
|
80
|
-
const json = JSON.stringify({
|
|
81
|
-
action: 'replace', // invalid
|
|
82
|
-
confidence: 60,
|
|
83
|
-
reasoning: 'bad action',
|
|
84
|
-
merged_content: null,
|
|
85
|
-
});
|
|
86
|
-
const provider = makeProvider(json);
|
|
87
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
88
|
-
expect(result.action).toBe('skip');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('JSON missing confidence → defaults to 0', async () => {
|
|
92
|
-
const json = JSON.stringify({ action: 'skip', reasoning: 'ok', merged_content: null });
|
|
93
|
-
const provider = makeProvider(json);
|
|
94
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
95
|
-
expect(result.confidence).toBe(0);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('response wrapped in markdown code block → parsed correctly', async () => {
|
|
99
|
-
const json = JSON.stringify({
|
|
100
|
-
action: 'skip',
|
|
101
|
-
confidence: 40,
|
|
102
|
-
reasoning: 'wrapped',
|
|
103
|
-
merged_content: null,
|
|
104
|
-
});
|
|
105
|
-
const provider = makeProvider('```json\n' + json + '\n```');
|
|
106
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
107
|
-
expect(result.action).toBe('skip');
|
|
108
|
-
expect(result.needs_review).toBeUndefined();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('response wrapped in plain code block → parsed correctly', async () => {
|
|
112
|
-
const json = JSON.stringify({
|
|
113
|
-
action: 'merge',
|
|
114
|
-
confidence: 65,
|
|
115
|
-
reasoning: 'wrapped plain',
|
|
116
|
-
merged_content: null,
|
|
117
|
-
});
|
|
118
|
-
const provider = makeProvider('```\n' + json + '\n```');
|
|
119
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
120
|
-
expect(result.action).toBe('merge');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('API error → needs_review=true with error message in reasoning', async () => {
|
|
124
|
-
const provider = {
|
|
125
|
-
complete: vi.fn().mockRejectedValue(new Error('Network timeout')),
|
|
126
|
-
} as unknown as ClaudeProvider;
|
|
127
|
-
|
|
128
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
129
|
-
expect(result.needs_review).toBe(true);
|
|
130
|
-
expect(result.reasoning).toContain('Network timeout');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('empty string response → needs_review=true', async () => {
|
|
134
|
-
const provider = makeProvider('');
|
|
135
|
-
const result = await evaluateWithAI(candidate, official, provider);
|
|
136
|
-
expect(result.needs_review).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
});
|