@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
package/src/skills/registry.ts
DELETED
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import matter from 'gray-matter';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { logger } from '../core/utils/logger.js';
|
|
7
|
-
import { loadOfficialSkills, OFFICIAL_SKILL_KEYWORDS } from './official-skills.js';
|
|
8
|
-
import { SemanticSkillMatcher } from './semantic-matcher.js';
|
|
9
|
-
|
|
10
|
-
const SKILLS_DIR = path.join(homedir(), '.claude', 'skills');
|
|
11
|
-
|
|
12
|
-
export interface Skill {
|
|
13
|
-
id: string;
|
|
14
|
-
name: string;
|
|
15
|
-
keywords: string[];
|
|
16
|
-
content: string;
|
|
17
|
-
path: string;
|
|
18
|
-
isOfficial?: boolean; // Mark official built-in skills
|
|
19
|
-
description?: string; // Optional description from frontmatter (used by agent-skills format)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Simple skill registry that scans ~/.claude/skills/*.md
|
|
24
|
-
* and matches by keywords in frontmatter
|
|
25
|
-
*/
|
|
26
|
-
export class SkillRegistry {
|
|
27
|
-
private skills: Map<string, Skill> = new Map();
|
|
28
|
-
private semanticMatcher: SemanticSkillMatcher | null = null;
|
|
29
|
-
|
|
30
|
-
constructor(apiKey?: string, model?: string, baseURL?: string) {
|
|
31
|
-
this.scan();
|
|
32
|
-
|
|
33
|
-
// Initialize semantic matcher if API key is provided
|
|
34
|
-
if (apiKey) {
|
|
35
|
-
this.semanticMatcher = new SemanticSkillMatcher(apiKey, model, baseURL);
|
|
36
|
-
logger.info('[SkillRegistry] Semantic matching enabled');
|
|
37
|
-
} else {
|
|
38
|
-
logger.debug('[SkillRegistry] Semantic matching disabled (no API key)');
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Resolve the directory containing official built-in skill .md files.
|
|
44
|
-
* Uses import.meta.url for ESM compatibility (works in both dev and npm-installed environments).
|
|
45
|
-
*/
|
|
46
|
-
private resolveBuiltinDir(): string {
|
|
47
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
48
|
-
return path.join(path.dirname(thisFile), 'official');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Scan ~/.claude/skills/ for markdown files with frontmatter
|
|
53
|
-
* Official skills are loaded first, then user skills (which can override)
|
|
54
|
-
*
|
|
55
|
-
* Supports two formats:
|
|
56
|
-
* 1. Flat format: ~/.claude/skills/skill-name.md
|
|
57
|
-
* 2. Directory format: ~/.claude/skills/skill-name/SKILL.md (agent-skills format)
|
|
58
|
-
*/
|
|
59
|
-
private scan(): void {
|
|
60
|
-
// 1. Load official built-in skills from src/skills/official/*.md
|
|
61
|
-
let officialCount = 0;
|
|
62
|
-
try {
|
|
63
|
-
const officialSkills = loadOfficialSkills(this.resolveBuiltinDir());
|
|
64
|
-
for (const officialSkill of officialSkills) {
|
|
65
|
-
// Merge extended keywords from OFFICIAL_SKILL_KEYWORDS if available
|
|
66
|
-
const extendedKeywords = OFFICIAL_SKILL_KEYWORDS[officialSkill.name] || officialSkill.keywords;
|
|
67
|
-
this.skills.set(officialSkill.name, {
|
|
68
|
-
id: officialSkill.name,
|
|
69
|
-
name: officialSkill.name,
|
|
70
|
-
keywords: extendedKeywords,
|
|
71
|
-
content: officialSkill.content,
|
|
72
|
-
path: this.resolveBuiltinDir() + '/' + officialSkill.name + '.md',
|
|
73
|
-
isOfficial: true,
|
|
74
|
-
description: officialSkill.description,
|
|
75
|
-
});
|
|
76
|
-
officialCount++;
|
|
77
|
-
}
|
|
78
|
-
} catch (err) {
|
|
79
|
-
logger.warn(`[SkillRegistry] Failed to load official skills: ${err}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
logger.info(`Loaded ${officialCount} official skill(s)`);
|
|
83
|
-
|
|
84
|
-
// 2. Load user skills from ~/.claude/skills/ (can override official skills)
|
|
85
|
-
if (!fs.existsSync(SKILLS_DIR)) {
|
|
86
|
-
logger.debug(`Skills directory not found: ${SKILLS_DIR}`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
|
|
91
|
-
let userCount = 0;
|
|
92
|
-
|
|
93
|
-
for (const entry of entries) {
|
|
94
|
-
try {
|
|
95
|
-
let filePath: string;
|
|
96
|
-
let id: string;
|
|
97
|
-
|
|
98
|
-
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
99
|
-
// Format 1: Flat .md file
|
|
100
|
-
filePath = path.join(SKILLS_DIR, entry.name);
|
|
101
|
-
id = path.basename(entry.name, '.md');
|
|
102
|
-
} else if (entry.isDirectory()) {
|
|
103
|
-
// Format 2: Directory with SKILL.md
|
|
104
|
-
const skillMdPath = path.join(SKILLS_DIR, entry.name, 'SKILL.md');
|
|
105
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
106
|
-
continue; // Skip directories without SKILL.md
|
|
107
|
-
}
|
|
108
|
-
filePath = skillMdPath;
|
|
109
|
-
id = entry.name;
|
|
110
|
-
} else {
|
|
111
|
-
continue; // Skip other file types
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
115
|
-
const parsed = matter(content);
|
|
116
|
-
const { data, content: body } = parsed;
|
|
117
|
-
|
|
118
|
-
const name = typeof data.name === 'string' ? data.name : id;
|
|
119
|
-
// 兼容两种字段名:keywords(claude-forge 标准)和 tags(Claude Code 官方 skill 约定)
|
|
120
|
-
const rawKeywords: unknown = data.keywords ?? data.tags;
|
|
121
|
-
const keywords: string[] = Array.isArray(rawKeywords)
|
|
122
|
-
? rawKeywords.filter((k): k is string => typeof k === 'string')
|
|
123
|
-
: [];
|
|
124
|
-
const description = typeof data.description === 'string' ? data.description : undefined;
|
|
125
|
-
|
|
126
|
-
// User skills override official skills, but merge keywords from OFFICIAL_SKILL_KEYWORDS
|
|
127
|
-
const extendedKeywords = OFFICIAL_SKILL_KEYWORDS[id];
|
|
128
|
-
const mergedKeywords = extendedKeywords
|
|
129
|
-
? [...new Set([...keywords, ...extendedKeywords])]
|
|
130
|
-
: keywords;
|
|
131
|
-
|
|
132
|
-
this.skills.set(id, {
|
|
133
|
-
id,
|
|
134
|
-
name,
|
|
135
|
-
keywords: mergedKeywords,
|
|
136
|
-
content: body.trim(),
|
|
137
|
-
path: filePath,
|
|
138
|
-
isOfficial: false,
|
|
139
|
-
description,
|
|
140
|
-
});
|
|
141
|
-
userCount++;
|
|
142
|
-
} catch (err) {
|
|
143
|
-
logger.warn(`Failed to parse skill ${entry.name}: ${err}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (userCount > 0) {
|
|
148
|
-
logger.info(`Loaded ${userCount} user skill(s) from ${SKILLS_DIR}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Match a prompt against skill keywords with hybrid approach:
|
|
154
|
-
* 1. Try semantic matching (AI-powered) if available
|
|
155
|
-
* 2. Fall back to keyword matching if semantic fails or not configured
|
|
156
|
-
*/
|
|
157
|
-
async match(prompt: string, keywords: string[] = [], context?: { recentFiles?: string[] }): Promise<Skill | null> {
|
|
158
|
-
const result = await this.matchWithConfidence(prompt, keywords, context);
|
|
159
|
-
return result?.skill ?? null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Like match(), but also returns the confidence score (0-100) so callers
|
|
164
|
-
* can compare across alternatives (e.g. agent vs skill routing).
|
|
165
|
-
*/
|
|
166
|
-
async matchWithConfidence(
|
|
167
|
-
prompt: string,
|
|
168
|
-
keywords: string[] = [],
|
|
169
|
-
context?: { recentFiles?: string[] },
|
|
170
|
-
): Promise<{ skill: Skill; confidence: number; source: 'semantic' | 'keyword' } | null> {
|
|
171
|
-
// Try semantic matching first
|
|
172
|
-
if (this.semanticMatcher) {
|
|
173
|
-
try {
|
|
174
|
-
const semanticResult = await this.semanticMatcher.match(
|
|
175
|
-
Array.from(this.skills.values()),
|
|
176
|
-
{
|
|
177
|
-
prompt,
|
|
178
|
-
intentKeywords: keywords,
|
|
179
|
-
recentFiles: context?.recentFiles,
|
|
180
|
-
}
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
if (semanticResult) {
|
|
184
|
-
logger.info(`[SkillRegistry] Semantic match: ${semanticResult.skill.id} (${semanticResult.confidence}%)`);
|
|
185
|
-
return { skill: semanticResult.skill, confidence: semanticResult.confidence, source: 'semantic' };
|
|
186
|
-
}
|
|
187
|
-
} catch (err) {
|
|
188
|
-
logger.warn(`[SkillRegistry] Semantic matching failed, falling back to keyword matching: ${err}`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Fall back to keyword matching
|
|
193
|
-
const kwSkill = await this.matchKeywords(prompt, keywords, context);
|
|
194
|
-
if (kwSkill) {
|
|
195
|
-
// Keyword matching has no real confidence; assign a conservative 60%
|
|
196
|
-
return { skill: kwSkill, confidence: 60, source: 'keyword' };
|
|
197
|
-
}
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Keyword-based matching (fallback method)
|
|
203
|
-
* Supports fallback to description-based substring matching when keywords are empty
|
|
204
|
-
*/
|
|
205
|
-
private async matchKeywords(
|
|
206
|
-
prompt: string,
|
|
207
|
-
keywords: string[] = [],
|
|
208
|
-
context?: { recentFiles?: string[] }
|
|
209
|
-
): Promise<Skill | null> {
|
|
210
|
-
const promptLower = prompt.toLowerCase();
|
|
211
|
-
const extraLower = keywords.map(k => k.toLowerCase());
|
|
212
|
-
|
|
213
|
-
let bestSkill: Skill | null = null;
|
|
214
|
-
let bestScore = 0;
|
|
215
|
-
let bestCandidate: Skill | null = null;
|
|
216
|
-
let bestCandidateScore = 0;
|
|
217
|
-
|
|
218
|
-
for (const skill of this.skills.values()) {
|
|
219
|
-
let score = 0;
|
|
220
|
-
|
|
221
|
-
// If skill has keywords, use keyword matching
|
|
222
|
-
if (skill.keywords.length > 0) {
|
|
223
|
-
for (const kw of skill.keywords) {
|
|
224
|
-
const kwLower = kw.toLowerCase();
|
|
225
|
-
|
|
226
|
-
// Check if keyword appears in prompt or intent keywords
|
|
227
|
-
if (this.matchKeywordFlexible(promptLower, kwLower)) {
|
|
228
|
-
score += 1;
|
|
229
|
-
}
|
|
230
|
-
if (extraLower.some(ek => this.matchKeywordFlexible(ek, kwLower))) {
|
|
231
|
-
score += 1;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
} else if (skill.description) {
|
|
235
|
-
// Fallback path 1: Use description substring matching when keywords are empty
|
|
236
|
-
const descLower = skill.description.toLowerCase();
|
|
237
|
-
if (descLower.includes(promptLower) || promptLower.includes(descLower)) {
|
|
238
|
-
score = 1; // Give a base score for description match
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
// Fallback path 2: If no keywords and no description, score remains 0
|
|
242
|
-
// This allows SemanticSkillMatcher to take over
|
|
243
|
-
|
|
244
|
-
// Track best candidate regardless of threshold
|
|
245
|
-
if (score > bestCandidateScore) {
|
|
246
|
-
bestCandidateScore = score;
|
|
247
|
-
bestCandidate = skill;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Official skills get priority: require 1+ match, user skills require 2+ matches
|
|
251
|
-
const threshold = skill.isOfficial ? 1 : 2;
|
|
252
|
-
|
|
253
|
-
if (score >= threshold && score > bestScore) {
|
|
254
|
-
bestScore = score;
|
|
255
|
-
bestSkill = skill;
|
|
256
|
-
} else if (
|
|
257
|
-
score === bestScore &&
|
|
258
|
-
score >= threshold &&
|
|
259
|
-
skill.isOfficial &&
|
|
260
|
-
!bestSkill?.isOfficial
|
|
261
|
-
) {
|
|
262
|
-
// Tie-break: prefer official skill when both meet threshold
|
|
263
|
-
bestSkill = skill;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (bestSkill) {
|
|
268
|
-
logger.info(`[SkillRegistry] Keyword match: ${bestSkill.id} (score: ${bestScore})`);
|
|
269
|
-
} else {
|
|
270
|
-
logger.debug(`[SkillRegistry] Best keyword candidate: ${bestCandidate?.id ?? 'none'} (score: ${bestCandidateScore})`);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return bestSkill;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Flexible keyword matching with word boundary for English, substring for Chinese
|
|
278
|
-
*/
|
|
279
|
-
private matchKeywordFlexible(text: string, keyword: string): boolean {
|
|
280
|
-
// For Chinese or multi-word keywords, use simple includes
|
|
281
|
-
if (/[一-龥]/.test(keyword) || keyword.includes(' ')) {
|
|
282
|
-
return text.includes(keyword);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// For single English words, require word boundaries to avoid false positives
|
|
286
|
-
// e.g., "test" won't match "latest"
|
|
287
|
-
const regex = new RegExp(`\\b${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
|
|
288
|
-
return regex.test(text);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Match with full result details (for debugging/logging)
|
|
293
|
-
*/
|
|
294
|
-
async matchWithDetails(prompt: string, keywords: string[] = [], context?: { recentFiles?: string[] }) {
|
|
295
|
-
const promptLower = prompt.toLowerCase();
|
|
296
|
-
const extraLower = keywords.map(k => k.toLowerCase());
|
|
297
|
-
|
|
298
|
-
const results = [];
|
|
299
|
-
for (const skill of this.skills.values()) {
|
|
300
|
-
let score = 0;
|
|
301
|
-
const matchedKeywords: string[] = [];
|
|
302
|
-
|
|
303
|
-
for (const kw of skill.keywords) {
|
|
304
|
-
const kwLower = kw.toLowerCase();
|
|
305
|
-
|
|
306
|
-
if (this.matchKeywordFlexible(promptLower, kwLower)) {
|
|
307
|
-
score += 1;
|
|
308
|
-
matchedKeywords.push(kw);
|
|
309
|
-
}
|
|
310
|
-
if (extraLower.some(ek => this.matchKeywordFlexible(ek, kwLower))) {
|
|
311
|
-
score += 1;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (score >= 1) {
|
|
316
|
-
results.push({
|
|
317
|
-
skill,
|
|
318
|
-
confidence: Math.min(100, score * 20), // Simple scoring: 1 match = 20%, 5 matches = 100%
|
|
319
|
-
matchedKeywords,
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
results.sort((a, b) => b.confidence - a.confidence);
|
|
325
|
-
return results;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/** Return all loaded skills */
|
|
329
|
-
getAll(): Skill[] {
|
|
330
|
-
return Array.from(this.skills.values());
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** Get a skill by ID */
|
|
334
|
-
get(id: string): Skill | undefined {
|
|
335
|
-
return this.skills.get(id);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Get skill manifest (id + description + keywords) for Agent pre-declaration
|
|
340
|
-
*/
|
|
341
|
-
getManifest(): Array<{ id: string; name: string; description: string; keywords: string[] }> {
|
|
342
|
-
return this.getAll().map(skill => ({
|
|
343
|
-
id: skill.id,
|
|
344
|
-
name: skill.name,
|
|
345
|
-
description: skill.description || '',
|
|
346
|
-
keywords: skill.keywords,
|
|
347
|
-
}));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** Re-scan the skills directory */
|
|
351
|
-
reload(): void {
|
|
352
|
-
this.skills.clear();
|
|
353
|
-
this.scan();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI-powered semantic skill matcher using Claude API
|
|
3
|
-
* Provides more accurate skill recommendations by understanding user intent
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
7
|
-
import type { Skill } from './registry.js';
|
|
8
|
-
import { logger } from '../core/utils/logger.js';
|
|
9
|
-
import { DEFAULTS } from '../core/constants.js';
|
|
10
|
-
|
|
11
|
-
export interface SemanticMatchResult {
|
|
12
|
-
skill: Skill;
|
|
13
|
-
confidence: number; // 0-100
|
|
14
|
-
reasoning: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface SemanticMatchContext {
|
|
18
|
-
prompt: string;
|
|
19
|
-
intentKeywords?: string[];
|
|
20
|
-
recentFiles?: string[];
|
|
21
|
-
projectPath?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Semantic matcher using Claude API for intent understanding
|
|
26
|
-
*/
|
|
27
|
-
export class SemanticSkillMatcher {
|
|
28
|
-
private client: Anthropic | null = null;
|
|
29
|
-
private model: string;
|
|
30
|
-
private cache: Map<string, SemanticMatchResult | null> = new Map();
|
|
31
|
-
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
32
|
-
private cacheTimestamps: Map<string, number> = new Map();
|
|
33
|
-
|
|
34
|
-
constructor(apiKey?: string, model?: string, baseURL?: string) {
|
|
35
|
-
this.model = model || DEFAULTS.AI_MODEL;
|
|
36
|
-
if (apiKey) {
|
|
37
|
-
this.client = new Anthropic({ apiKey, baseURL });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Match skills using semantic understanding
|
|
43
|
-
* Falls back to keyword matching if API is not configured
|
|
44
|
-
*/
|
|
45
|
-
async match(skills: Skill[], context: SemanticMatchContext): Promise<SemanticMatchResult | null> {
|
|
46
|
-
// Check cache first
|
|
47
|
-
const cacheKey = this.getCacheKey(context.prompt);
|
|
48
|
-
const cached = this.getFromCache(cacheKey);
|
|
49
|
-
if (cached !== undefined) {
|
|
50
|
-
return cached;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// If no API key, return null (caller will fall back to keyword matching)
|
|
54
|
-
if (!this.client) {
|
|
55
|
-
logger.debug('[SemanticMatcher] No API key configured, skipping semantic matching');
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const result = await this.matchWithAI(skills, context);
|
|
61
|
-
this.setCache(cacheKey, result);
|
|
62
|
-
return result;
|
|
63
|
-
} catch (err) {
|
|
64
|
-
logger.warn(`[SemanticMatcher] AI matching failed: ${err}`);
|
|
65
|
-
return null; // Fall back to keyword matching
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Use Claude API to understand intent and match skills
|
|
71
|
-
*/
|
|
72
|
-
private async matchWithAI(
|
|
73
|
-
skills: Skill[],
|
|
74
|
-
context: SemanticMatchContext
|
|
75
|
-
): Promise<SemanticMatchResult | null> {
|
|
76
|
-
if (!this.client) return null;
|
|
77
|
-
|
|
78
|
-
// Build skill descriptions for the prompt
|
|
79
|
-
const skillDescriptions = skills
|
|
80
|
-
.filter(s => s.isOfficial) // Only consider official skills for AI matching
|
|
81
|
-
.map((s, idx) => `${idx + 1}. **${s.id}**: ${s.name} - ${this.extractDescription(s)}`)
|
|
82
|
-
.join('\n');
|
|
83
|
-
|
|
84
|
-
const prompt = this.buildMatchingPrompt(context, skillDescriptions);
|
|
85
|
-
|
|
86
|
-
const response = await this.client.messages.create({
|
|
87
|
-
model: this.model,
|
|
88
|
-
max_tokens: 500,
|
|
89
|
-
temperature: 0,
|
|
90
|
-
messages: [
|
|
91
|
-
{
|
|
92
|
-
role: 'user',
|
|
93
|
-
content: prompt,
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const content = response.content[0];
|
|
99
|
-
if (content.type !== 'text') {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return this.parseAIResponse(content.text, skills);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Build the matching prompt for Claude API
|
|
108
|
-
*/
|
|
109
|
-
private buildMatchingPrompt(context: SemanticMatchContext, skillDescriptions: string): string {
|
|
110
|
-
return `You are a skill recommendation system. Analyze the user's request and recommend the most relevant skill.
|
|
111
|
-
|
|
112
|
-
User Request: "${context.prompt}"
|
|
113
|
-
|
|
114
|
-
${context.intentKeywords && context.intentKeywords.length > 0 ? `Intent Keywords: ${context.intentKeywords.join(', ')}` : ''}
|
|
115
|
-
|
|
116
|
-
Available Skills:
|
|
117
|
-
${skillDescriptions}
|
|
118
|
-
|
|
119
|
-
Instructions:
|
|
120
|
-
1. Understand the user's intent and task type
|
|
121
|
-
2. Match to the MOST relevant skill (only ONE skill)
|
|
122
|
-
3. If no skill is clearly relevant (confidence < 60%), respond with "NONE"
|
|
123
|
-
4. Respond in this exact format:
|
|
124
|
-
|
|
125
|
-
SKILL_ID: <skill-id or NONE>
|
|
126
|
-
CONFIDENCE: <0-100>
|
|
127
|
-
REASONING: <one sentence explaining why this skill matches or why no skill matches>
|
|
128
|
-
|
|
129
|
-
Example responses:
|
|
130
|
-
- SKILL_ID: official-tdd
|
|
131
|
-
CONFIDENCE: 85
|
|
132
|
-
REASONING: User wants to write tests before implementation, which is the core of TDD.
|
|
133
|
-
|
|
134
|
-
- SKILL_ID: NONE
|
|
135
|
-
CONFIDENCE: 30
|
|
136
|
-
REASONING: Request is about simple documentation update, no specialized skill needed.`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Parse AI response into match result
|
|
141
|
-
*/
|
|
142
|
-
private parseAIResponse(response: string, skills: Skill[]): SemanticMatchResult | null {
|
|
143
|
-
const lines = response.trim().split('\n');
|
|
144
|
-
let skillId: string | null = null;
|
|
145
|
-
let confidence = 0;
|
|
146
|
-
let reasoning = '';
|
|
147
|
-
|
|
148
|
-
for (const line of lines) {
|
|
149
|
-
const trimmed = line.trim();
|
|
150
|
-
if (trimmed.startsWith('SKILL_ID:')) {
|
|
151
|
-
skillId = trimmed.substring('SKILL_ID:'.length).trim();
|
|
152
|
-
} else if (trimmed.startsWith('CONFIDENCE:')) {
|
|
153
|
-
confidence = parseInt(trimmed.substring('CONFIDENCE:'.length).trim(), 10);
|
|
154
|
-
} else if (trimmed.startsWith('REASONING:')) {
|
|
155
|
-
reasoning = trimmed.substring('REASONING:'.length).trim();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// If AI says NONE or confidence too low, return null
|
|
160
|
-
if (!skillId || skillId === 'NONE' || confidence < 60) {
|
|
161
|
-
logger.debug(`[SemanticMatcher] No match: ${reasoning}`);
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Find the matched skill
|
|
166
|
-
const skill = skills.find(s => s.id === skillId);
|
|
167
|
-
if (!skill) {
|
|
168
|
-
logger.warn(`[SemanticMatcher] AI returned unknown skill: ${skillId}`);
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
logger.info(`[SemanticMatcher] Matched ${skillId} with ${confidence}% confidence`);
|
|
173
|
-
return { skill, confidence, reasoning };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Extract description from skill content
|
|
178
|
-
* Prioritizes frontmatter description over content extraction
|
|
179
|
-
*/
|
|
180
|
-
private extractDescription(skill: Skill): string {
|
|
181
|
-
// Prefer frontmatter description if available (agent-skills format)
|
|
182
|
-
if (skill.description) {
|
|
183
|
-
return skill.description;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Fallback: Try to extract first line of description from content
|
|
187
|
-
const lines = skill.content.split('\n');
|
|
188
|
-
for (const line of lines) {
|
|
189
|
-
const trimmed = line.trim();
|
|
190
|
-
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('---')) {
|
|
191
|
-
return trimmed.substring(0, 100);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return skill.keywords.join(', ');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Cache management
|
|
199
|
-
*/
|
|
200
|
-
private getCacheKey(prompt: string): string {
|
|
201
|
-
return prompt.toLowerCase().substring(0, 200);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
private getFromCache(key: string): SemanticMatchResult | null | undefined {
|
|
205
|
-
const timestamp = this.cacheTimestamps.get(key);
|
|
206
|
-
if (!timestamp || Date.now() - timestamp > this.CACHE_TTL) {
|
|
207
|
-
this.cache.delete(key);
|
|
208
|
-
this.cacheTimestamps.delete(key);
|
|
209
|
-
return undefined;
|
|
210
|
-
}
|
|
211
|
-
return this.cache.get(key);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private setCache(key: string, result: SemanticMatchResult | null): void {
|
|
215
|
-
this.cache.set(key, result);
|
|
216
|
-
this.cacheTimestamps.set(key, Date.now());
|
|
217
|
-
|
|
218
|
-
// Limit cache size to 100 entries
|
|
219
|
-
if (this.cache.size > 100) {
|
|
220
|
-
const oldestKey = Array.from(this.cacheTimestamps.entries())
|
|
221
|
-
.sort((a, b) => a[1] - b[1])[0][0];
|
|
222
|
-
this.cache.delete(oldestKey);
|
|
223
|
-
this.cacheTimestamps.delete(oldestKey);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Clear cache
|
|
229
|
-
*/
|
|
230
|
-
clearCache(): void {
|
|
231
|
-
this.cache.clear();
|
|
232
|
-
this.cacheTimestamps.clear();
|
|
233
|
-
}
|
|
234
|
-
}
|