@askdkc/kiokuko 0.1.17 → 0.1.19
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/README.ja.md +91 -112
- package/README.ko.md +90 -102
- package/README.md +103 -262
- package/README.zh-CN.md +88 -102
- package/dist/agent-file/atomic-write.d.ts +90 -3
- package/dist/agent-file/atomic-write.d.ts.map +1 -1
- package/dist/agent-file/atomic-write.js +990 -14
- package/dist/agent-file/atomic-write.js.map +1 -1
- package/dist/agent-file/managed-block.d.ts +12 -1
- package/dist/agent-file/managed-block.d.ts.map +1 -1
- package/dist/agent-file/managed-block.js +49 -4
- package/dist/agent-file/managed-block.js.map +1 -1
- package/dist/agent-file/render.d.ts +1 -1
- package/dist/agent-file/render.d.ts.map +1 -1
- package/dist/agent-file/render.js +10 -7
- package/dist/agent-file/render.js.map +1 -1
- package/dist/akinator/agent-task.d.ts +11 -22
- package/dist/akinator/agent-task.d.ts.map +1 -1
- package/dist/akinator/agent-task.js +454 -99
- package/dist/akinator/agent-task.js.map +1 -1
- package/dist/akinator/capabilities.d.ts +18 -6
- package/dist/akinator/capabilities.d.ts.map +1 -1
- package/dist/akinator/capabilities.js +72 -35
- package/dist/akinator/capabilities.js.map +1 -1
- package/dist/akinator/capability-binding.d.ts +11 -0
- package/dist/akinator/capability-binding.d.ts.map +1 -0
- package/dist/akinator/capability-binding.js +56 -0
- package/dist/akinator/capability-binding.js.map +1 -0
- package/dist/akinator/domain.js +2 -2
- package/dist/akinator/domain.js.map +1 -1
- package/dist/akinator/knowledge-path.d.ts.map +1 -1
- package/dist/akinator/knowledge-path.js +409 -39
- package/dist/akinator/knowledge-path.js.map +1 -1
- package/dist/akinator/orchestrator.d.ts +3 -4
- package/dist/akinator/orchestrator.d.ts.map +1 -1
- package/dist/akinator/orchestrator.js +1 -4
- package/dist/akinator/orchestrator.js.map +1 -1
- package/dist/akinator/service.d.ts +0 -4
- package/dist/akinator/service.d.ts.map +1 -1
- package/dist/akinator/service.js +19 -35
- package/dist/akinator/service.js.map +1 -1
- package/dist/akinator/skill-discovery-attempt.d.ts +20 -0
- package/dist/akinator/skill-discovery-attempt.d.ts.map +1 -0
- package/dist/akinator/skill-discovery-attempt.js +390 -0
- package/dist/akinator/skill-discovery-attempt.js.map +1 -0
- package/dist/akinator/store.d.ts.map +1 -1
- package/dist/akinator/store.js +107 -52
- package/dist/akinator/store.js.map +1 -1
- package/dist/akinator/types.d.ts +0 -12
- package/dist/akinator/types.d.ts.map +1 -1
- package/dist/bin/kiokuko.js +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +534 -148
- package/dist/cli.js.map +1 -1
- package/dist/client/server-client.d.ts +5 -3
- package/dist/client/server-client.d.ts.map +1 -1
- package/dist/client/server-client.js +486 -181
- package/dist/client/server-client.js.map +1 -1
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +861 -86
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/backup.d.ts +2 -1
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +64 -8
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/doctor.d.ts +5 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +70 -26
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/export.d.ts +0 -4
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +191 -32
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/import.d.ts +10 -1
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +592 -113
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +19 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +569 -22
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/purge.d.ts.map +1 -1
- package/dist/commands/purge.js +3 -0
- package/dist/commands/purge.js.map +1 -1
- package/dist/commands/setup.d.ts +34 -8
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +418 -86
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +317 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/use.d.ts +10 -2
- package/dist/commands/use.d.ts.map +1 -1
- package/dist/commands/use.js +851 -73
- package/dist/commands/use.js.map +1 -1
- package/dist/config/paths.d.ts +11 -1
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +64 -29
- package/dist/config/paths.js.map +1 -1
- package/dist/config/project-config.d.ts +1 -0
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +70 -32
- package/dist/config/project-config.js.map +1 -1
- package/dist/context/broker.d.ts +28 -14
- package/dist/context/broker.d.ts.map +1 -1
- package/dist/context/broker.js +604 -145
- package/dist/context/broker.js.map +1 -1
- package/dist/context/delivery.d.ts +6 -21
- package/dist/context/delivery.d.ts.map +1 -1
- package/dist/context/delivery.js +196 -147
- package/dist/context/delivery.js.map +1 -1
- package/dist/context/feedback.d.ts +11 -12
- package/dist/context/feedback.d.ts.map +1 -1
- package/dist/context/feedback.js +229 -261
- package/dist/context/feedback.js.map +1 -1
- package/dist/context/ranking.d.ts +1 -12
- package/dist/context/ranking.d.ts.map +1 -1
- package/dist/context/ranking.js +13 -4
- package/dist/context/ranking.js.map +1 -1
- package/dist/context/recommendations.d.ts +0 -5
- package/dist/context/recommendations.d.ts.map +1 -1
- package/dist/context/recommendations.js +0 -1
- package/dist/context/recommendations.js.map +1 -1
- package/dist/context/run-state.d.ts +26 -0
- package/dist/context/run-state.d.ts.map +1 -0
- package/dist/context/run-state.js +399 -0
- package/dist/context/run-state.js.map +1 -0
- package/dist/context/scoped-broker.d.ts +17 -5
- package/dist/context/scoped-broker.d.ts.map +1 -1
- package/dist/context/scoped-broker.js +512 -106
- package/dist/context/scoped-broker.js.map +1 -1
- package/dist/context/selection-state.d.ts +19 -0
- package/dist/context/selection-state.d.ts.map +1 -0
- package/dist/context/selection-state.js +250 -0
- package/dist/context/selection-state.js.map +1 -0
- package/dist/db/adapter.d.ts +5 -3
- package/dist/db/adapter.d.ts.map +1 -1
- package/dist/db/adapter.js +6 -3
- package/dist/db/adapter.js.map +1 -1
- package/dist/db/capabilities.d.ts.map +1 -1
- package/dist/db/capabilities.js +34 -16
- package/dist/db/capabilities.js.map +1 -1
- package/dist/db/connection.d.ts +8 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +55 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/migrate.d.ts +28 -7
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +106 -78
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/sqlite-retry.d.ts +11 -0
- package/dist/db/sqlite-retry.d.ts.map +1 -0
- package/dist/db/sqlite-retry.js +66 -0
- package/dist/db/sqlite-retry.js.map +1 -0
- package/dist/db/transaction.d.ts +13 -0
- package/dist/db/transaction.d.ts.map +1 -1
- package/dist/db/transaction.js +53 -24
- package/dist/db/transaction.js.map +1 -1
- package/dist/db/upgrade-backup.d.ts +58 -2
- package/dist/db/upgrade-backup.d.ts.map +1 -1
- package/dist/db/upgrade-backup.js +757 -28
- package/dist/db/upgrade-backup.js.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/gateway/agent-service.d.ts +4 -1
- package/dist/gateway/agent-service.d.ts.map +1 -1
- package/dist/gateway/agent-service.js +71 -58
- package/dist/gateway/agent-service.js.map +1 -1
- package/dist/gateway/checkpoint-service.d.ts +1 -0
- package/dist/gateway/checkpoint-service.d.ts.map +1 -1
- package/dist/gateway/checkpoint-service.js +47 -24
- package/dist/gateway/checkpoint-service.js.map +1 -1
- package/dist/ledger/archive.d.ts +1 -4
- package/dist/ledger/archive.d.ts.map +1 -1
- package/dist/ledger/archive.js +77 -39
- package/dist/ledger/archive.js.map +1 -1
- package/dist/ledger/maintenance.d.ts +0 -11
- package/dist/ledger/maintenance.d.ts.map +1 -1
- package/dist/ledger/maintenance.js +24 -40
- package/dist/ledger/maintenance.js.map +1 -1
- package/dist/ledger/projection.d.ts +0 -1
- package/dist/ledger/projection.d.ts.map +1 -1
- package/dist/ledger/projection.js.map +1 -1
- package/dist/ledger/promotion.d.ts +0 -9
- package/dist/ledger/promotion.d.ts.map +1 -1
- package/dist/ledger/promotion.js.map +1 -1
- package/dist/ledger/query.d.ts +1 -15
- package/dist/ledger/query.d.ts.map +1 -1
- package/dist/ledger/query.js.map +1 -1
- package/dist/ledger/store.d.ts +0 -1
- package/dist/ledger/store.d.ts.map +1 -1
- package/dist/ledger/store.js +11 -8
- package/dist/ledger/store.js.map +1 -1
- package/dist/ledger/types.d.ts +0 -4
- package/dist/ledger/types.d.ts.map +1 -1
- package/dist/ledger/types.js.map +1 -1
- package/dist/ledger/validate.d.ts.map +1 -1
- package/dist/ledger/validate.js +0 -5
- package/dist/ledger/validate.js.map +1 -1
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +105 -54
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory/audit.d.ts +0 -1
- package/dist/memory/audit.d.ts.map +1 -1
- package/dist/memory/audit.js +0 -1
- package/dist/memory/audit.js.map +1 -1
- package/dist/memory/curator.d.ts.map +1 -1
- package/dist/memory/curator.js +172 -96
- package/dist/memory/curator.js.map +1 -1
- package/dist/memory/entries.d.ts +12 -2
- package/dist/memory/entries.d.ts.map +1 -1
- package/dist/memory/entries.js +198 -114
- package/dist/memory/entries.js.map +1 -1
- package/dist/memory/federated-retrieval.d.ts +12 -1
- package/dist/memory/federated-retrieval.d.ts.map +1 -1
- package/dist/memory/federated-retrieval.js +183 -70
- package/dist/memory/federated-retrieval.js.map +1 -1
- package/dist/memory/hybrid-retrieval.d.ts +5 -11
- package/dist/memory/hybrid-retrieval.d.ts.map +1 -1
- package/dist/memory/hybrid-retrieval.js +78 -58
- package/dist/memory/hybrid-retrieval.js.map +1 -1
- package/dist/memory/lifecycle.d.ts.map +1 -1
- package/dist/memory/lifecycle.js +17 -13
- package/dist/memory/lifecycle.js.map +1 -1
- package/dist/memory/rebuild-search.d.ts +5 -0
- package/dist/memory/rebuild-search.d.ts.map +1 -1
- package/dist/memory/rebuild-search.js +101 -76
- package/dist/memory/rebuild-search.js.map +1 -1
- package/dist/memory/retrieval-query.d.ts +0 -5
- package/dist/memory/retrieval-query.d.ts.map +1 -1
- package/dist/memory/retrieval-query.js +0 -5
- package/dist/memory/retrieval-query.js.map +1 -1
- package/dist/memory/retrieval.d.ts.map +1 -1
- package/dist/memory/retrieval.js +25 -131
- package/dist/memory/retrieval.js.map +1 -1
- package/dist/memory/revisions.d.ts +76 -1
- package/dist/memory/revisions.d.ts.map +1 -1
- package/dist/memory/revisions.js +539 -35
- package/dist/memory/revisions.js.map +1 -1
- package/dist/memory/scoped-memory.d.ts +16 -1
- package/dist/memory/scoped-memory.d.ts.map +1 -1
- package/dist/memory/scoped-memory.js +240 -107
- package/dist/memory/scoped-memory.js.map +1 -1
- package/dist/memory/secrets.d.ts +2 -0
- package/dist/memory/secrets.d.ts.map +1 -1
- package/dist/memory/secrets.js +36 -0
- package/dist/memory/secrets.js.map +1 -1
- package/dist/memory/structured-memory.d.ts +3 -2
- package/dist/memory/structured-memory.d.ts.map +1 -1
- package/dist/memory/structured-memory.js +54 -22
- package/dist/memory/structured-memory.js.map +1 -1
- package/dist/memory/workspaces.d.ts +9 -0
- package/dist/memory/workspaces.d.ts.map +1 -1
- package/dist/memory/workspaces.js +67 -0
- package/dist/memory/workspaces.js.map +1 -1
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +7 -0
- package/dist/package-version.js.map +1 -0
- package/dist/repository/binding.d.ts +5 -0
- package/dist/repository/binding.d.ts.map +1 -1
- package/dist/repository/binding.js +117 -17
- package/dist/repository/binding.js.map +1 -1
- package/dist/repository/framework-version.d.ts +8 -0
- package/dist/repository/framework-version.d.ts.map +1 -0
- package/dist/repository/framework-version.js +29 -0
- package/dist/repository/framework-version.js.map +1 -0
- package/dist/repository/identity-value.d.ts +4 -0
- package/dist/repository/identity-value.d.ts.map +1 -0
- package/dist/repository/identity-value.js +24 -0
- package/dist/repository/identity-value.js.map +1 -0
- package/dist/repository/identity.d.ts.map +1 -1
- package/dist/repository/identity.js +2 -0
- package/dist/repository/identity.js.map +1 -1
- package/dist/repository/project-fingerprint.d.ts +14 -2
- package/dist/repository/project-fingerprint.d.ts.map +1 -1
- package/dist/repository/project-fingerprint.js +312 -33
- package/dist/repository/project-fingerprint.js.map +1 -1
- package/dist/repository/remote-url.d.ts +0 -1
- package/dist/repository/remote-url.d.ts.map +1 -1
- package/dist/repository/remote-url.js +0 -1
- package/dist/repository/remote-url.js.map +1 -1
- package/dist/runtime-version.d.ts +2 -1
- package/dist/runtime-version.d.ts.map +1 -1
- package/dist/runtime-version.js +21 -4
- package/dist/runtime-version.js.map +1 -1
- package/dist/serialization/boundary-json.d.ts +20 -0
- package/dist/serialization/boundary-json.d.ts.map +1 -0
- package/dist/serialization/boundary-json.js +129 -0
- package/dist/serialization/boundary-json.js.map +1 -0
- package/dist/serialization/envelope.js +1 -1
- package/dist/serialization/envelope.js.map +1 -1
- package/dist/serialization/validate.d.ts +7 -0
- package/dist/serialization/validate.d.ts.map +1 -1
- package/dist/serialization/validate.js +53 -13
- package/dist/serialization/validate.js.map +1 -1
- package/dist/server/app.js +1 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/http.d.ts +4 -3
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +90 -50
- package/dist/server/http.js.map +1 -1
- package/dist/server/idempotency.d.ts +0 -6
- package/dist/server/idempotency.d.ts.map +1 -1
- package/dist/server/idempotency.js.map +1 -1
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +21 -4
- package/dist/server/router.js.map +1 -1
- package/dist/server/routes/agent-capability-gate.d.ts +40 -0
- package/dist/server/routes/agent-capability-gate.d.ts.map +1 -0
- package/dist/server/routes/agent-capability-gate.js +182 -0
- package/dist/server/routes/agent-capability-gate.js.map +1 -0
- package/dist/server/routes/agent-events.d.ts.map +1 -1
- package/dist/server/routes/agent-events.js +8 -1
- package/dist/server/routes/agent-events.js.map +1 -1
- package/dist/server/routes/agent-intake.d.ts.map +1 -1
- package/dist/server/routes/agent-intake.js +10 -2
- package/dist/server/routes/agent-intake.js.map +1 -1
- package/dist/server/routes/agent-promotions.d.ts.map +1 -1
- package/dist/server/routes/agent-promotions.js +5 -2
- package/dist/server/routes/agent-promotions.js.map +1 -1
- package/dist/server/routes/agent-runs.d.ts.map +1 -1
- package/dist/server/routes/agent-runs.js +18 -4
- package/dist/server/routes/agent-runs.js.map +1 -1
- package/dist/server/routes/request-binding.d.ts +5 -0
- package/dist/server/routes/request-binding.d.ts.map +1 -0
- package/dist/server/routes/request-binding.js +49 -0
- package/dist/server/routes/request-binding.js.map +1 -0
- package/dist/server/routes/task5-support.d.ts +1 -8
- package/dist/server/routes/task5-support.d.ts.map +1 -1
- package/dist/server/routes/task5-support.js +1 -17
- package/dist/server/routes/task5-support.js.map +1 -1
- package/dist/server/routes/task5.d.ts.map +1 -1
- package/dist/server/routes/task5.js +106 -36
- package/dist/server/routes/task5.js.map +1 -1
- package/dist/server/write-queue.js +2 -2
- package/dist/server/write-queue.js.map +1 -1
- package/dist/setup/claude-config.d.ts +2 -1
- package/dist/setup/claude-config.d.ts.map +1 -1
- package/dist/setup/claude-config.js +54 -4
- package/dist/setup/claude-config.js.map +1 -1
- package/dist/setup/client-detection.d.ts +7 -2
- package/dist/setup/client-detection.d.ts.map +1 -1
- package/dist/setup/client-detection.js +27 -9
- package/dist/setup/client-detection.js.map +1 -1
- package/dist/setup/hermes-config.d.ts +2 -1
- package/dist/setup/hermes-config.d.ts.map +1 -1
- package/dist/setup/hermes-config.js +53 -12
- package/dist/setup/hermes-config.js.map +1 -1
- package/dist/setup/legacy-client-cleanup.d.ts +6 -0
- package/dist/setup/legacy-client-cleanup.d.ts.map +1 -0
- package/dist/setup/legacy-client-cleanup.js +152 -0
- package/dist/setup/legacy-client-cleanup.js.map +1 -0
- package/dist/setup/managed-text.d.ts.map +1 -1
- package/dist/setup/managed-text.js +11 -1
- package/dist/setup/managed-text.js.map +1 -1
- package/dist/setup/opencode-config.d.ts +2 -1
- package/dist/setup/opencode-config.d.ts.map +1 -1
- package/dist/setup/opencode-config.js +57 -5
- package/dist/setup/opencode-config.js.map +1 -1
- package/dist/setup/render.d.ts +2 -1
- package/dist/setup/render.d.ts.map +1 -1
- package/dist/setup/render.js +91 -17
- package/dist/setup/render.js.map +1 -1
- package/dist/setup/standard-skills.d.ts +16 -2
- package/dist/setup/standard-skills.d.ts.map +1 -1
- package/dist/setup/standard-skills.js +37 -13
- package/dist/setup/standard-skills.js.map +1 -1
- package/dist/setup/strict-json.d.ts +7 -0
- package/dist/setup/strict-json.d.ts.map +1 -0
- package/dist/setup/strict-json.js +98 -0
- package/dist/setup/strict-json.js.map +1 -0
- package/dist/setup/strict-toml.d.ts +7 -0
- package/dist/setup/strict-toml.d.ts.map +1 -0
- package/dist/setup/strict-toml.js +470 -0
- package/dist/setup/strict-toml.js.map +1 -0
- package/dist/skills/cache.d.ts +15 -0
- package/dist/skills/cache.d.ts.map +1 -0
- package/dist/skills/cache.js +19 -0
- package/dist/skills/cache.js.map +1 -0
- package/dist/skills/candidate-ranking.d.ts +15 -0
- package/dist/skills/candidate-ranking.d.ts.map +1 -0
- package/dist/skills/candidate-ranking.js +52 -0
- package/dist/skills/candidate-ranking.js.map +1 -0
- package/dist/skills/chunking.d.ts +11 -0
- package/dist/skills/chunking.d.ts.map +1 -0
- package/dist/skills/chunking.js +291 -0
- package/dist/skills/chunking.js.map +1 -0
- package/dist/skills/config.d.ts +19 -0
- package/dist/skills/config.d.ts.map +1 -0
- package/dist/skills/config.js +103 -0
- package/dist/skills/config.js.map +1 -0
- package/dist/skills/discovery-service.d.ts +26 -0
- package/dist/skills/discovery-service.d.ts.map +1 -0
- package/dist/skills/discovery-service.js +921 -0
- package/dist/skills/discovery-service.js.map +1 -0
- package/dist/skills/external-transport.d.ts +2 -0
- package/dist/skills/external-transport.d.ts.map +1 -0
- package/dist/skills/external-transport.js +30 -0
- package/dist/skills/external-transport.js.map +1 -0
- package/dist/skills/find.d.ts +16 -0
- package/dist/skills/find.d.ts.map +1 -0
- package/dist/skills/find.js +58 -0
- package/dist/skills/find.js.map +1 -0
- package/dist/skills/gap-detection.d.ts +23 -0
- package/dist/skills/gap-detection.d.ts.map +1 -0
- package/dist/skills/gap-detection.js +62 -0
- package/dist/skills/gap-detection.js.map +1 -0
- package/dist/skills/import-preparation.d.ts +4 -0
- package/dist/skills/import-preparation.d.ts.map +1 -0
- package/dist/skills/import-preparation.js +20 -0
- package/dist/skills/import-preparation.js.map +1 -0
- package/dist/skills/materialization-authority.d.ts +11 -0
- package/dist/skills/materialization-authority.d.ts.map +1 -0
- package/dist/skills/materialization-authority.js +92 -0
- package/dist/skills/materialization-authority.js.map +1 -0
- package/dist/skills/materialization-service.d.ts +22 -0
- package/dist/skills/materialization-service.d.ts.map +1 -0
- package/dist/skills/materialization-service.js +101 -0
- package/dist/skills/materialization-service.js.map +1 -0
- package/dist/skills/official-catalog.d.ts +31 -0
- package/dist/skills/official-catalog.d.ts.map +1 -0
- package/dist/skills/official-catalog.js +110 -0
- package/dist/skills/official-catalog.js.map +1 -0
- package/dist/skills/providers/schema.d.ts +30 -0
- package/dist/skills/providers/schema.d.ts.map +1 -0
- package/dist/skills/providers/schema.js +354 -0
- package/dist/skills/providers/schema.js.map +1 -0
- package/dist/skills/providers/skills-sh-compat.d.ts +17 -0
- package/dist/skills/providers/skills-sh-compat.d.ts.map +1 -0
- package/dist/skills/providers/skills-sh-compat.js +71 -0
- package/dist/skills/providers/skills-sh-compat.js.map +1 -0
- package/dist/skills/providers/skills-sh-v1.d.ts +23 -0
- package/dist/skills/providers/skills-sh-v1.d.ts.map +1 -0
- package/dist/skills/providers/skills-sh-v1.js +134 -0
- package/dist/skills/providers/skills-sh-v1.js.map +1 -0
- package/dist/skills/query-builder.d.ts +18 -0
- package/dist/skills/query-builder.d.ts.map +1 -0
- package/dist/skills/query-builder.js +93 -0
- package/dist/skills/query-builder.js.map +1 -0
- package/dist/skills/source/errors.d.ts +9 -0
- package/dist/skills/source/errors.d.ts.map +1 -0
- package/dist/skills/source/errors.js +15 -0
- package/dist/skills/source/errors.js.map +1 -0
- package/dist/skills/source/frontmatter.d.ts +9 -0
- package/dist/skills/source/frontmatter.d.ts.map +1 -0
- package/dist/skills/source/frontmatter.js +77 -0
- package/dist/skills/source/frontmatter.js.map +1 -0
- package/dist/skills/source/github-fetcher.d.ts +20 -0
- package/dist/skills/source/github-fetcher.d.ts.map +1 -0
- package/dist/skills/source/github-fetcher.js +292 -0
- package/dist/skills/source/github-fetcher.js.map +1 -0
- package/dist/skills/source/snapshot-validator.d.ts +28 -0
- package/dist/skills/source/snapshot-validator.d.ts.map +1 -0
- package/dist/skills/source/snapshot-validator.js +186 -0
- package/dist/skills/source/snapshot-validator.js.map +1 -0
- package/dist/skills/store.d.ts +181 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +1710 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/skills/types.d.ts +169 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +2 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/web/i18n.d.ts +32 -0
- package/dist/web/i18n.d.ts.map +1 -1
- package/dist/web/i18n.js +128 -0
- package/dist/web/i18n.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +386 -38
- package/dist/web/server.js.map +1 -1
- package/dist/web/ui.d.ts +4 -0
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +382 -6
- package/dist/web/ui.js.map +1 -1
- package/migrations/009_external_skill_discovery.sql +126 -0
- package/package.json +4 -3
- package/skills/kiokuko-single-purpose-functions/SKILL.md +288 -0
- package/skills/kiokuko-single-purpose-functions/references/kiokuko-patterns.md +344 -0
- package/skills/kiokuko-single-purpose-functions/references/review-checklist.md +97 -0
- package/skills/kiokuko-ui-design-soul/SKILL.md +1186 -28
- package/templates/AGENTS.md +8 -7
- package/dist/client/runtime-discovery.d.ts +0 -15
- package/dist/client/runtime-discovery.d.ts.map +0 -1
- package/dist/client/runtime-discovery.js +0 -45
- package/dist/client/runtime-discovery.js.map +0 -1
- package/dist/commands/call.d.ts +0 -7
- package/dist/commands/call.d.ts.map +0 -1
- package/dist/commands/call.js +0 -10
- package/dist/commands/call.js.map +0 -1
- package/dist/commands/link.d.ts +0 -6
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/link.js +0 -6
- package/dist/commands/link.js.map +0 -1
- package/dist/commands/promote.d.ts +0 -6
- package/dist/commands/promote.d.ts.map +0 -1
- package/dist/commands/promote.js +0 -6
- package/dist/commands/promote.js.map +0 -1
- package/dist/commands/read.d.ts +0 -6
- package/dist/commands/read.d.ts.map +0 -1
- package/dist/commands/read.js +0 -4
- package/dist/commands/read.js.map +0 -1
- package/dist/commands/recall.d.ts +0 -6
- package/dist/commands/recall.d.ts.map +0 -1
- package/dist/commands/recall.js +0 -4
- package/dist/commands/recall.js.map +0 -1
- package/dist/commands/record.d.ts +0 -6
- package/dist/commands/record.d.ts.map +0 -1
- package/dist/commands/record.js +0 -4
- package/dist/commands/record.js.map +0 -1
- package/dist/commands/search.d.ts +0 -6
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js +0 -4
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/supersede.d.ts +0 -6
- package/dist/commands/supersede.d.ts.map +0 -1
- package/dist/commands/supersede.js +0 -6
- package/dist/commands/supersede.js.map +0 -1
- package/dist/knowledge/sources.d.ts +0 -72
- package/dist/knowledge/sources.d.ts.map +0 -1
- package/dist/knowledge/sources.js +0 -388
- package/dist/knowledge/sources.js.map +0 -1
- package/dist/setup/opencode-evidence.d.ts +0 -37
- package/dist/setup/opencode-evidence.d.ts.map +0 -1
- package/dist/setup/opencode-evidence.js +0 -58
- package/dist/setup/opencode-evidence.js.map +0 -1
- package/dist/setup/opencode-loop-guard.d.ts +0 -9
- package/dist/setup/opencode-loop-guard.d.ts.map +0 -1
- package/dist/setup/opencode-loop-guard.js +0 -248
- package/dist/setup/opencode-loop-guard.js.map +0 -1
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
import { satisfiesFrameworkVersion } from '../repository/framework-version.js';
|
|
2
|
+
import { detectSkillGap } from './gap-detection.js';
|
|
3
|
+
import { buildSkillQueries } from './query-builder.js';
|
|
4
|
+
import { rankSkillCandidates } from './candidate-ranking.js';
|
|
5
|
+
import { documentsFromSkillSnapshot } from './import-preparation.js';
|
|
6
|
+
import { readSkillDiscoveryConfig } from './config.js';
|
|
7
|
+
import { SkillProviderError } from './providers/schema.js';
|
|
8
|
+
import { SkillDiscoveryCache } from './cache.js';
|
|
9
|
+
import { GitHubSkillSourceFetcher, SkillSourceError } from './source/github-fetcher.js';
|
|
10
|
+
import { validateSkillSnapshot } from './source/snapshot-validator.js';
|
|
11
|
+
import { clearPersistentSkillAuditFailure, clearPersistentSkillSourceFailure, externalSkillRefreshExpectation, externalSkillRequirement, externalSkillSourceFetchRequest, externalSkillWorkspace, markExternalSkillRefreshFailureInTransaction, persistExistingSkillImportInTransaction, persistNewSkillImportInTransaction, readExternalSkill, readPersistentSkillAuditFailure, readPersistentSkillSourceFailure, refreshExternalSkillSnapshotInTransaction, listExternalSkills, readPersistentSkillSearchCache, recordDiscoveredSkillInTransaction, writePersistentSkillAuditFailure, writePersistentSkillSearchCache, writePersistentSkillSourceFailure } from './store.js';
|
|
12
|
+
import { createSkillRegistryProvider, findSkills } from './find.js';
|
|
13
|
+
import { KiokukoError } from '../errors.js';
|
|
14
|
+
import { reviewedCatalogSkill, technologyDefinition } from './official-catalog.js';
|
|
15
|
+
import { isSqliteOperationalError } from '../db/sqlite-retry.js';
|
|
16
|
+
import { withImmediateTransaction } from '../db/transaction.js';
|
|
17
|
+
import { authorizeSkillMaterialization, hasSkillMaterializationAuthorization } from './materialization-authority.js';
|
|
18
|
+
import { DEFAULT_EXTERNAL_SKILL_FAILURE_TTL_MS, externalSkillFailureTtlMs } from './materialization-service.js';
|
|
19
|
+
import { compareCanonicalStrings } from '../serialization/validate.js';
|
|
20
|
+
function candidateId(candidate) { return `${candidate.sourceType}:${candidate.source}:${candidate.slug}`; }
|
|
21
|
+
function compatibilityProvider(provider) { return provider.startsWith('skills-sh-compat-'); }
|
|
22
|
+
function containsAlias(value, alias) {
|
|
23
|
+
const escaped = alias.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
24
|
+
return new RegExp(`(?:^|[^\\p{L}\\p{N}])${escaped}(?:$|[^\\p{L}\\p{N}])`, 'iu').test(value);
|
|
25
|
+
}
|
|
26
|
+
function providerFailureCode(error) {
|
|
27
|
+
if (!(error instanceof SkillProviderError))
|
|
28
|
+
return undefined;
|
|
29
|
+
const code = error.code;
|
|
30
|
+
switch (code) {
|
|
31
|
+
case 'registry_authentication_failed':
|
|
32
|
+
case 'registry_unavailable':
|
|
33
|
+
case 'registry_rate_limited':
|
|
34
|
+
return code;
|
|
35
|
+
case 'registry_invalid_response':
|
|
36
|
+
default:
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function sourceOrValidationFailureCode(error) {
|
|
41
|
+
if (!(error instanceof SkillSourceError))
|
|
42
|
+
return undefined;
|
|
43
|
+
const code = error.code;
|
|
44
|
+
switch (code) {
|
|
45
|
+
case 'source_missing':
|
|
46
|
+
return 'candidate_not_found_at_source';
|
|
47
|
+
case 'source_rate_limited':
|
|
48
|
+
case 'source_unavailable':
|
|
49
|
+
case 'candidate_not_found_at_source':
|
|
50
|
+
case 'source_tree_truncated':
|
|
51
|
+
case 'skill_disabled_for_model_invocation':
|
|
52
|
+
case 'skill_secret_detected':
|
|
53
|
+
case 'skill_too_large':
|
|
54
|
+
case 'skill_validation_failed':
|
|
55
|
+
case 'skill_blocked':
|
|
56
|
+
return code;
|
|
57
|
+
default:
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function persistenceFailureCode(error) {
|
|
62
|
+
if (error instanceof KiokukoError) {
|
|
63
|
+
if (error.code === 'CONFLICT')
|
|
64
|
+
return 'persistence_conflict';
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return isSqliteOperationalError(error) ? 'persistence_failed' : undefined;
|
|
68
|
+
}
|
|
69
|
+
class CallerPersistenceAssertionError extends Error {
|
|
70
|
+
assertionError;
|
|
71
|
+
constructor(assertionError) {
|
|
72
|
+
super('Caller persistence assertion failed');
|
|
73
|
+
this.assertionError = assertionError;
|
|
74
|
+
this.name = 'CallerPersistenceAssertionError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function cachedProviderFailure(value) {
|
|
78
|
+
if (value.failureCode === 'registry_authentication_failed')
|
|
79
|
+
return value.failureCode;
|
|
80
|
+
if (value.cacheOutcome === 'unavailable')
|
|
81
|
+
return 'registry_unavailable';
|
|
82
|
+
if (value.cacheOutcome === 'rate_limited')
|
|
83
|
+
return 'registry_rate_limited';
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
function requirementMatchScore(candidate, requirement, requirements) {
|
|
87
|
+
const values = [candidate.name, candidate.slug.split('/').at(-1) ?? candidate.slug].map((value) => value.toLowerCase());
|
|
88
|
+
const identities = [...new Set([requirement.id, requirement.technology].map((value) => value.toLowerCase()))];
|
|
89
|
+
if (values.some((value) => identities.includes(value)))
|
|
90
|
+
return 100;
|
|
91
|
+
if (values.some((value) => identities.some((identity) => containsAlias(value, identity))))
|
|
92
|
+
return 80;
|
|
93
|
+
const uniqueAliases = requirement.aliases.map((alias) => alias.toLowerCase()).filter((alias) => !requirements.some((other) => other !== requirement
|
|
94
|
+
&& [other.id, other.technology, ...other.aliases].some((value) => value.toLowerCase() === alias)));
|
|
95
|
+
if (values.some((value) => uniqueAliases.some((alias) => containsAlias(value, alias))))
|
|
96
|
+
return 60;
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
function requirementFor(candidate, requirements) {
|
|
100
|
+
return requirements.map((requirement) => ({ requirement, score: requirementMatchScore(candidate, requirement, requirements) }))
|
|
101
|
+
.filter(({ score }) => score > 0)
|
|
102
|
+
.sort((left, right) => right.score - left.score || compareCanonicalStrings(left.requirement.id, right.requirement.id))[0]?.requirement;
|
|
103
|
+
}
|
|
104
|
+
function normalizeOfficial(candidate, requirement) {
|
|
105
|
+
const official = reviewedCatalogSkill(candidate) !== undefined
|
|
106
|
+
? 'catalog-verified'
|
|
107
|
+
: requirement.owners.some((owner) => candidate.source.toLowerCase().startsWith(`${owner.toLowerCase()}/`))
|
|
108
|
+
? 'owner-verified'
|
|
109
|
+
: candidate.officialStatus;
|
|
110
|
+
return { ...candidate, officialStatus: official };
|
|
111
|
+
}
|
|
112
|
+
function requirementsForQuery(query, requirements) {
|
|
113
|
+
const normalized = query.normalize('NFKC').toLowerCase();
|
|
114
|
+
return requirements.filter((requirement) => {
|
|
115
|
+
const technology = requirement.technology.normalize('NFKC').toLowerCase();
|
|
116
|
+
return requirement.queries.some((value) => value.normalize('NFKC').toLowerCase() === normalized)
|
|
117
|
+
|| normalized === `${technology} testing`
|
|
118
|
+
|| normalized === `${technology} debug`
|
|
119
|
+
|| normalized === `${technology} review`;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function locallySafeCommunityCandidate(candidate) {
|
|
123
|
+
return candidate.sourceType === 'github'
|
|
124
|
+
&& !candidate.duplicate
|
|
125
|
+
&& /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/u.test(candidate.source)
|
|
126
|
+
&& /^[A-Za-z0-9_.\-/]{1,240}$/u.test(candidate.slug)
|
|
127
|
+
&& !candidate.slug.split('/').some((part) => part === '' || part === '.' || part === '..');
|
|
128
|
+
}
|
|
129
|
+
const CURATED_CACHE_QUERY = '__curated__';
|
|
130
|
+
const DEFAULT_AUTOMATIC_MATERIALIZATION_LIMIT = 1;
|
|
131
|
+
const MAX_AUTOMATIC_MATERIALIZATION_LIMIT = 2;
|
|
132
|
+
const MAX_SOURCE_ATTEMPTS = 6;
|
|
133
|
+
const MAX_COMMUNITY_AUDITS = 6;
|
|
134
|
+
const IMPORT_FRESHNESS_MS = 7 * 24 * 60 * 60_000;
|
|
135
|
+
function automaticMaterializationLimit(value) {
|
|
136
|
+
const resolved = value ?? DEFAULT_AUTOMATIC_MATERIALIZATION_LIMIT;
|
|
137
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > MAX_AUTOMATIC_MATERIALIZATION_LIMIT) {
|
|
138
|
+
throw new KiokukoError('VALIDATION_ERROR', 'External skill materialization limit must be 1 or 2');
|
|
139
|
+
}
|
|
140
|
+
return resolved;
|
|
141
|
+
}
|
|
142
|
+
function providerNegativeCache(error) {
|
|
143
|
+
const code = error.code;
|
|
144
|
+
switch (code) {
|
|
145
|
+
case 'registry_rate_limited':
|
|
146
|
+
return { outcome: 'rate_limited', ttlMs: externalSkillFailureTtlMs(error.retryAfterSeconds) };
|
|
147
|
+
case 'registry_authentication_failed':
|
|
148
|
+
return { outcome: 'unavailable', ttlMs: DEFAULT_EXTERNAL_SKILL_FAILURE_TTL_MS, failureCode: code };
|
|
149
|
+
case 'registry_unavailable':
|
|
150
|
+
return { outcome: 'unavailable', ttlMs: DEFAULT_EXTERNAL_SKILL_FAILURE_TTL_MS };
|
|
151
|
+
case 'registry_invalid_response':
|
|
152
|
+
return undefined;
|
|
153
|
+
default:
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function frameworkVersionMatches(actual, expected) {
|
|
158
|
+
if (expected === undefined)
|
|
159
|
+
return true;
|
|
160
|
+
if (actual === undefined)
|
|
161
|
+
return false;
|
|
162
|
+
const compatibility = satisfiesFrameworkVersion(actual, expected);
|
|
163
|
+
return compatibility === 'exact' || compatibility === 'compatible';
|
|
164
|
+
}
|
|
165
|
+
function applicabilityMatchesFingerprint(requirement, fingerprint) {
|
|
166
|
+
const normalize = (value) => value.normalize('NFKC').toLowerCase();
|
|
167
|
+
const dimensionMatches = (expected, actual) => expected === undefined || expected.length === 0 || expected.some((value) => actual.some((candidate) => normalize(candidate) === normalize(value)));
|
|
168
|
+
if (!dimensionMatches(requirement.applicability.languages, fingerprint.languages)
|
|
169
|
+
|| !dimensionMatches(requirement.applicability.databases, fingerprint.databases)
|
|
170
|
+
|| !dimensionMatches(requirement.applicability.runtimes, fingerprint.runtimes)
|
|
171
|
+
|| !dimensionMatches(requirement.applicability.tools, fingerprint.tools))
|
|
172
|
+
return false;
|
|
173
|
+
const frameworks = requirement.applicability.frameworks;
|
|
174
|
+
return frameworks === undefined || frameworks.length === 0 || frameworks.some((expected) => fingerprint.frameworks.some((actual) => normalize(actual.name) === normalize(expected.name) && frameworkVersionMatches(actual.version, expected.version)));
|
|
175
|
+
}
|
|
176
|
+
function officialStatus(value) {
|
|
177
|
+
return value === 'curated' || value === 'catalog-verified' || value === 'owner-verified' || value === 'registry-only' ? value : 'unknown';
|
|
178
|
+
}
|
|
179
|
+
function reusableImportedSkill(record, mode) {
|
|
180
|
+
if (reviewedCatalogSkill({ source: record.sourceLocator, slug: record.slug }) !== undefined)
|
|
181
|
+
return true;
|
|
182
|
+
// The materialized record itself crossed the runtime authority boundary.
|
|
183
|
+
// Its persisted audit label is descriptive and must not become a reusable
|
|
184
|
+
// authorization input for later discovery runs.
|
|
185
|
+
return mode === 'community';
|
|
186
|
+
}
|
|
187
|
+
function isReviewedCatalogCandidate(candidate, requirement) {
|
|
188
|
+
const definition = technologyDefinition(requirement.id);
|
|
189
|
+
return definition !== undefined
|
|
190
|
+
&& requirement.repositories.some((source) => source.normalize('NFKC').toLowerCase() === candidate.source.normalize('NFKC').toLowerCase())
|
|
191
|
+
&& (definition.reviewedSkills ?? []).some((skill) => skill.slug === candidate.slug);
|
|
192
|
+
}
|
|
193
|
+
function isReviewedCatalogIdentity(record, requirement) {
|
|
194
|
+
return isReviewedCatalogCandidate(candidateFromRecord(record), requirement);
|
|
195
|
+
}
|
|
196
|
+
function freshImportedSkill(database, requirement, rows, now, mode, fingerprint) {
|
|
197
|
+
const nowMs = Date.parse(now);
|
|
198
|
+
if (!Number.isFinite(nowMs))
|
|
199
|
+
return undefined;
|
|
200
|
+
return rows.find((record) => {
|
|
201
|
+
if (record.state !== 'imported' || record.sourceCommit === null || record.snapshotHash === null)
|
|
202
|
+
return false;
|
|
203
|
+
if (!reusableImportedSkill(record, mode))
|
|
204
|
+
return false;
|
|
205
|
+
const checkedAt = Date.parse(record.lastCheckedAt);
|
|
206
|
+
const age = nowMs - checkedAt;
|
|
207
|
+
if (!Number.isFinite(checkedAt) || age < 0 || age >= IMPORT_FRESHNESS_MS)
|
|
208
|
+
return false;
|
|
209
|
+
const importedRequirement = externalSkillRequirement(database, record.skillId);
|
|
210
|
+
if (importedRequirement === undefined)
|
|
211
|
+
return false;
|
|
212
|
+
return applicabilityMatchesFingerprint(importedRequirement, fingerprint)
|
|
213
|
+
&& (requirementOverlaps(importedRequirement, requirement) || isReviewedCatalogIdentity(record, requirement));
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function sourceSkillIdentity(value) {
|
|
217
|
+
return `${value.sourceType.normalize('NFKC').toLowerCase()}\u0000${value.source.normalize('NFKC').toLowerCase()}\u0000${value.slug}`;
|
|
218
|
+
}
|
|
219
|
+
function suppressAutomaticImport(record) {
|
|
220
|
+
return record !== undefined
|
|
221
|
+
&& (record.disabledAt !== null || record.state === 'disabled' || record.state === 'blocked');
|
|
222
|
+
}
|
|
223
|
+
function sameSkillSource(record, candidate) {
|
|
224
|
+
return record.sourceType.normalize('NFKC').toLowerCase() === candidate.sourceType.normalize('NFKC').toLowerCase()
|
|
225
|
+
&& record.sourceLocator.normalize('NFKC').toLowerCase() === candidate.source.normalize('NFKC').toLowerCase();
|
|
226
|
+
}
|
|
227
|
+
function requirementOverlaps(left, right) {
|
|
228
|
+
const normalize = (value) => value.normalize('NFKC').toLowerCase();
|
|
229
|
+
const leftCanonical = new Set([left.id, left.technology].map(normalize));
|
|
230
|
+
const rightCanonical = new Set([right.id, right.technology].map(normalize));
|
|
231
|
+
if ([...leftCanonical].some((value) => rightCanonical.has(value)))
|
|
232
|
+
return true;
|
|
233
|
+
const leftNames = new Set([left.id, left.technology, ...left.aliases].map(normalize));
|
|
234
|
+
const rightNames = new Set([right.id, right.technology, ...right.aliases].map(normalize));
|
|
235
|
+
return [...rightCanonical].some((value) => leftNames.has(value))
|
|
236
|
+
&& [...leftCanonical].some((value) => rightNames.has(value));
|
|
237
|
+
}
|
|
238
|
+
function existingIdentityOwners(database, rows, candidate, canonical, requirement, fingerprint) {
|
|
239
|
+
const requestedIdentity = sourceSkillIdentity(candidate);
|
|
240
|
+
const canonicalIdentity = sourceSkillIdentity(canonical);
|
|
241
|
+
return rows.filter((record) => {
|
|
242
|
+
if (!sameSkillSource(record, canonical))
|
|
243
|
+
return false;
|
|
244
|
+
const identity = sourceSkillIdentity({ sourceType: record.sourceType, source: record.sourceLocator, slug: record.slug });
|
|
245
|
+
if (identity === requestedIdentity || identity === canonicalIdentity)
|
|
246
|
+
return true;
|
|
247
|
+
if (record.sourceCommit === null && record.snapshotHash === null && record.state === 'stale' && record.disabledAt === null)
|
|
248
|
+
return true;
|
|
249
|
+
const stored = externalSkillRequirement(database, record.skillId);
|
|
250
|
+
return stored !== undefined && requirementOverlaps(stored, requirement)
|
|
251
|
+
&& (record.disabledAt !== null || record.state === 'disabled' || record.state === 'stale' || record.state === 'blocked'
|
|
252
|
+
|| applicabilityMatchesFingerprint(stored, fingerprint));
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function candidateFromRecord(record) {
|
|
256
|
+
if (record.sourceType !== 'github')
|
|
257
|
+
throw new KiokukoError('INTEGRITY_ERROR', 'External Skill source type is invalid');
|
|
258
|
+
return {
|
|
259
|
+
id: record.skillId,
|
|
260
|
+
provider: record.provider,
|
|
261
|
+
name: record.name,
|
|
262
|
+
slug: record.slug,
|
|
263
|
+
source: record.sourceLocator,
|
|
264
|
+
sourceType: record.sourceType,
|
|
265
|
+
installUrl: record.installUrl,
|
|
266
|
+
installs: record.installs,
|
|
267
|
+
duplicate: record.duplicate,
|
|
268
|
+
officialStatus: officialStatus(record.officialStatus),
|
|
269
|
+
auditStatus: record.auditStatus,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export class SkillDiscoveryService {
|
|
273
|
+
cache;
|
|
274
|
+
provider;
|
|
275
|
+
sourceFetcher;
|
|
276
|
+
sourceFlights = new Map();
|
|
277
|
+
now;
|
|
278
|
+
assertBeforePersist;
|
|
279
|
+
constructor(dependencies = {}) {
|
|
280
|
+
const config = readSkillDiscoveryConfig();
|
|
281
|
+
this.provider = dependencies.provider ?? createSkillRegistryProvider(dependencies.fetchImpl);
|
|
282
|
+
this.sourceFetcher = dependencies.sourceFetcher ?? new GitHubSkillSourceFetcher({ token: config.githubToken, ...(dependencies.fetchImpl ? { fetchImpl: dependencies.fetchImpl } : {}) });
|
|
283
|
+
this.cache = dependencies.cache ?? new SkillDiscoveryCache();
|
|
284
|
+
this.now = dependencies.now ?? (() => new Date().toISOString());
|
|
285
|
+
this.assertBeforePersist = dependencies.assertBeforePersist;
|
|
286
|
+
}
|
|
287
|
+
async fetchSnapshot(candidate, request) {
|
|
288
|
+
const key = JSON.stringify([candidateId(candidate), request.purpose, request.purpose === 'refresh' ? request.expectedPrimaryPath : null]);
|
|
289
|
+
const active = this.sourceFlights.get(key);
|
|
290
|
+
if (active)
|
|
291
|
+
return active;
|
|
292
|
+
const flight = this.sourceFetcher.fetch(candidate, request);
|
|
293
|
+
this.sourceFlights.set(key, flight);
|
|
294
|
+
try {
|
|
295
|
+
return await flight;
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
if (this.sourceFlights.get(key) === flight)
|
|
299
|
+
this.sourceFlights.delete(key);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async searchWithCache(database, input) {
|
|
303
|
+
const persistent = readPersistentSkillSearchCache(database, { ...input, provider: input.provider.id });
|
|
304
|
+
if (persistent !== null) {
|
|
305
|
+
if (persistent.failureCode === 'registry_authentication_failed') {
|
|
306
|
+
if (!input.allowAuthenticationFallback || input.provider.authenticationFallback === undefined)
|
|
307
|
+
return { result: persistent, cacheHits: 1, authenticationFallbackUsed: false, failureCode: persistent.failureCode };
|
|
308
|
+
const fallback = await this.searchWithCache(database, { ...input, provider: input.provider.authenticationFallback, allowAuthenticationFallback: false });
|
|
309
|
+
return { ...fallback, cacheHits: fallback.cacheHits + 1, authenticationFallbackUsed: true };
|
|
310
|
+
}
|
|
311
|
+
const failureCode = cachedProviderFailure(persistent);
|
|
312
|
+
if (failureCode !== undefined)
|
|
313
|
+
return { result: persistent, cacheHits: 1, authenticationFallbackUsed: false, failureCode };
|
|
314
|
+
return { result: persistent, cacheHits: 1, authenticationFallbackUsed: false };
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const loaded = await this.cache.getOrLoad({
|
|
318
|
+
provider: input.provider.id,
|
|
319
|
+
query: input.query,
|
|
320
|
+
mode: input.mode,
|
|
321
|
+
owner: input.owner,
|
|
322
|
+
loader: () => findSkills({ query: input.query, limit: 20, ...(input.owner === null ? {} : { owner: input.owner }) }, {
|
|
323
|
+
provider: input.provider,
|
|
324
|
+
fallbackOnAuthentication: false,
|
|
325
|
+
}),
|
|
326
|
+
});
|
|
327
|
+
writePersistentSkillSearchCache(database, {
|
|
328
|
+
provider: input.provider.id,
|
|
329
|
+
query: input.query,
|
|
330
|
+
owner: input.owner,
|
|
331
|
+
mode: input.mode,
|
|
332
|
+
result: loaded.result,
|
|
333
|
+
outcome: loaded.result.candidates.length === 0 ? 'empty' : 'success',
|
|
334
|
+
ttlMs: loaded.result.candidates.length === 0 ? 6 * 60 * 60_000 : 24 * 60 * 60_000,
|
|
335
|
+
now: input.now,
|
|
336
|
+
});
|
|
337
|
+
return { result: loaded.result, cacheHits: loaded.hit ? 1 : 0, authenticationFallbackUsed: false };
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (error instanceof SkillProviderError) {
|
|
341
|
+
const negativeCache = providerNegativeCache(error);
|
|
342
|
+
if (negativeCache !== undefined) {
|
|
343
|
+
writePersistentSkillSearchCache(database, {
|
|
344
|
+
provider: input.provider.id,
|
|
345
|
+
query: input.query,
|
|
346
|
+
owner: input.owner,
|
|
347
|
+
mode: input.mode,
|
|
348
|
+
result: { provider: input.provider.id, experimental: compatibilityProvider(input.provider.id), candidates: [] },
|
|
349
|
+
outcome: negativeCache.outcome,
|
|
350
|
+
...(negativeCache.failureCode === undefined ? {} : { failureCode: negativeCache.failureCode }),
|
|
351
|
+
ttlMs: negativeCache.ttlMs,
|
|
352
|
+
now: input.now,
|
|
353
|
+
});
|
|
354
|
+
if (error.code === 'registry_authentication_failed' && input.allowAuthenticationFallback && input.provider.authenticationFallback !== undefined) {
|
|
355
|
+
const fallback = await this.searchWithCache(database, { ...input, provider: input.provider.authenticationFallback, allowAuthenticationFallback: false });
|
|
356
|
+
return { ...fallback, authenticationFallbackUsed: true };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async discover(database, input, modeOverride) {
|
|
364
|
+
const selectedSkillLimit = automaticMaterializationLimit(input.maxSelectedSkills);
|
|
365
|
+
const requestedMode = modeOverride ?? input.mode;
|
|
366
|
+
const now = this.now();
|
|
367
|
+
if (requestedMode === 'off')
|
|
368
|
+
return { attempted: false, mode: requestedMode, requirements: [], queries: [], cacheHits: 0, candidates: 0, selected: [], failures: [] };
|
|
369
|
+
const gap = detectSkillGap({ fingerprint: input.fingerprint, task: input.task, profile: input.profile, recommendedTags: input.recommendedTags, capabilities: input.capabilities, mode: requestedMode });
|
|
370
|
+
const mode = requestedMode === 'community' && gap.catalogAvailability === 'unknown' ? 'official' : requestedMode;
|
|
371
|
+
const base = { attempted: false, mode, requirements: [], queries: [], cacheHits: 0, candidates: 0, selected: [], failures: [] };
|
|
372
|
+
base.requirements = gap.missing.map((requirement) => requirement.id);
|
|
373
|
+
if (!gap.shouldDiscover || gap.missing.length === 0)
|
|
374
|
+
return base;
|
|
375
|
+
const existingSkills = listExternalSkills(database);
|
|
376
|
+
const unresolved = [];
|
|
377
|
+
const reused = new Set();
|
|
378
|
+
for (const requirement of gap.missing) {
|
|
379
|
+
const existing = freshImportedSkill(database, requirement, existingSkills, now, mode, input.fingerprint);
|
|
380
|
+
if (!existing) {
|
|
381
|
+
unresolved.push(requirement);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (!reused.has(existing.skillId) && base.selected.length < selectedSkillLimit) {
|
|
385
|
+
reused.add(existing.skillId);
|
|
386
|
+
base.selected.push({ skillId: existing.skillId, name: existing.name, source: existing.sourceLocator, officialStatus: officialStatus(existing.officialStatus), imported: true, updated: false });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const nowMs = Date.parse(now);
|
|
390
|
+
if (!Number.isFinite(nowMs))
|
|
391
|
+
throw new KiokukoError('INTEGRITY_ERROR', 'Skill discovery clock is invalid');
|
|
392
|
+
const requirementsToSearch = base.selected.length >= selectedSkillLimit ? [] : unresolved;
|
|
393
|
+
if (requirementsToSearch.length === 0)
|
|
394
|
+
return base;
|
|
395
|
+
base.attempted = true;
|
|
396
|
+
const queries = buildSkillQueries({ requirements: requirementsToSearch, profile: input.profile, mode });
|
|
397
|
+
base.queries = queries;
|
|
398
|
+
for (const query of queries) {
|
|
399
|
+
if (requirementsForQuery(query, requirementsToSearch).length === 0)
|
|
400
|
+
throw new KiokukoError('INTEGRITY_ERROR', 'Generated Skill query has no requirement provenance');
|
|
401
|
+
}
|
|
402
|
+
const seenCandidates = new Set();
|
|
403
|
+
const officialCandidates = new Map();
|
|
404
|
+
const communityCandidates = new Map();
|
|
405
|
+
const associateCandidate = (target, candidate, requirements, forcedRequirement) => {
|
|
406
|
+
seenCandidates.add(candidateId(candidate));
|
|
407
|
+
const requirement = forcedRequirement ?? requirementFor(candidate, requirements);
|
|
408
|
+
if (requirement === undefined)
|
|
409
|
+
return;
|
|
410
|
+
const normalized = normalizeOfficial(candidate, requirement);
|
|
411
|
+
const identity = sourceSkillIdentity(normalized);
|
|
412
|
+
const current = target.get(identity);
|
|
413
|
+
const score = requirementMatchScore(normalized, requirement, gap.missing);
|
|
414
|
+
const currentScore = current === undefined ? -1 : requirementMatchScore(current.candidate, current.requirement, gap.missing);
|
|
415
|
+
if (current === undefined || score > currentScore || (score === currentScore && compareCanonicalStrings(requirement.id, current.requirement.id) < 0)) {
|
|
416
|
+
target.set(identity, { candidate: normalized, requirement });
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
let primaryAuthenticationFailed = false;
|
|
420
|
+
let providerRequestsLatched = false;
|
|
421
|
+
const search = async (query, owner, requirements, target) => {
|
|
422
|
+
if (providerRequestsLatched)
|
|
423
|
+
return;
|
|
424
|
+
try {
|
|
425
|
+
const loaded = await this.searchWithCache(database, { provider: this.provider, query, owner, mode, now, allowAuthenticationFallback: true });
|
|
426
|
+
base.cacheHits += loaded.cacheHits;
|
|
427
|
+
primaryAuthenticationFailed ||= loaded.authenticationFallbackUsed;
|
|
428
|
+
if (loaded.failureCode !== undefined) {
|
|
429
|
+
base.failures.push({ stage: 'search', code: loaded.failureCode });
|
|
430
|
+
providerRequestsLatched = true;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
for (const candidate of loaded.result.candidates)
|
|
434
|
+
associateCandidate(target, candidate, requirements);
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
const code = providerFailureCode(error);
|
|
438
|
+
if (code === undefined)
|
|
439
|
+
throw error;
|
|
440
|
+
base.failures.push({ stage: 'search', code });
|
|
441
|
+
providerRequestsLatched = true;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
// Phase 1: scoped searches against verified owners only.
|
|
445
|
+
for (const query of queries) {
|
|
446
|
+
const queryRequirements = requirementsForQuery(query, requirementsToSearch);
|
|
447
|
+
const owners = [...new Set(queryRequirements.flatMap((requirement) => requirement.owners))];
|
|
448
|
+
for (const owner of owners) {
|
|
449
|
+
await search(query, owner, queryRequirements.filter((requirement) => requirement.owners.includes(owner)), officialCandidates);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (this.provider.curated && !primaryAuthenticationFailed && !providerRequestsLatched) {
|
|
453
|
+
const cachedCurated = readPersistentSkillSearchCache(database, { provider: this.provider.id, query: CURATED_CACHE_QUERY, owner: null, mode, now });
|
|
454
|
+
if (cachedCurated) {
|
|
455
|
+
base.cacheHits += 1;
|
|
456
|
+
const failureCode = cachedProviderFailure(cachedCurated);
|
|
457
|
+
if (failureCode !== undefined) {
|
|
458
|
+
base.failures.push({ stage: 'search', code: failureCode });
|
|
459
|
+
providerRequestsLatched = true;
|
|
460
|
+
}
|
|
461
|
+
else
|
|
462
|
+
for (const candidate of cachedCurated.candidates)
|
|
463
|
+
associateCandidate(officialCandidates, candidate, requirementsToSearch);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
try {
|
|
467
|
+
const curated = await this.provider.curated();
|
|
468
|
+
const result = { provider: this.provider.id, experimental: compatibilityProvider(this.provider.id), candidates: curated ?? [] };
|
|
469
|
+
writePersistentSkillSearchCache(database, { provider: this.provider.id, query: CURATED_CACHE_QUERY, owner: null, mode, result, outcome: result.candidates.length === 0 ? 'empty' : 'success', ttlMs: 6 * 60 * 60_000, now });
|
|
470
|
+
for (const candidate of result.candidates)
|
|
471
|
+
associateCandidate(officialCandidates, candidate, requirementsToSearch);
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
const code = providerFailureCode(error);
|
|
475
|
+
if (code === undefined)
|
|
476
|
+
throw error;
|
|
477
|
+
if (error instanceof SkillProviderError) {
|
|
478
|
+
const negativeCache = providerNegativeCache(error);
|
|
479
|
+
if (negativeCache !== undefined) {
|
|
480
|
+
writePersistentSkillSearchCache(database, {
|
|
481
|
+
provider: this.provider.id,
|
|
482
|
+
query: CURATED_CACHE_QUERY,
|
|
483
|
+
owner: null,
|
|
484
|
+
mode,
|
|
485
|
+
result: { provider: this.provider.id, experimental: compatibilityProvider(this.provider.id), candidates: [] },
|
|
486
|
+
outcome: negativeCache.outcome,
|
|
487
|
+
...(negativeCache.failureCode === undefined ? {} : { failureCode: negativeCache.failureCode }),
|
|
488
|
+
ttlMs: negativeCache.ttlMs,
|
|
489
|
+
now,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
base.failures.push({ stage: 'search', code });
|
|
494
|
+
providerRequestsLatched = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const communityAudit = new Map();
|
|
499
|
+
let auditAttempts = 0;
|
|
500
|
+
let auditLimitReported = false;
|
|
501
|
+
let communityAuditLatched = false;
|
|
502
|
+
let communityAuditUnavailableReported = false;
|
|
503
|
+
const communityAllowed = async (candidate) => {
|
|
504
|
+
if (!locallySafeCommunityCandidate(candidate))
|
|
505
|
+
return null;
|
|
506
|
+
if (reviewedCatalogSkill(candidate) !== undefined)
|
|
507
|
+
return { candidate: { ...candidate, officialStatus: 'catalog-verified', auditStatus: 'not-required' } };
|
|
508
|
+
const key = candidateId(candidate);
|
|
509
|
+
const known = communityAudit.get(key);
|
|
510
|
+
if (known !== undefined)
|
|
511
|
+
return known.allowed
|
|
512
|
+
? { candidate: { ...candidate, auditStatus: 'passed' }, authorization: known.authorization }
|
|
513
|
+
: null;
|
|
514
|
+
if (providerRequestsLatched || primaryAuthenticationFailed || communityAuditLatched || this.provider.audit === undefined) {
|
|
515
|
+
providerRequestsLatched = true;
|
|
516
|
+
communityAudit.set(key, { allowed: false });
|
|
517
|
+
communityAuditLatched = true;
|
|
518
|
+
if (!communityAuditUnavailableReported) {
|
|
519
|
+
base.failures.push({ stage: 'validation', code: 'community_audit_unavailable' });
|
|
520
|
+
communityAuditUnavailableReported = true;
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const cachedAuditFailure = readPersistentSkillAuditFailure(database, this.provider.id, candidate, now);
|
|
525
|
+
if (cachedAuditFailure !== null) {
|
|
526
|
+
providerRequestsLatched = true;
|
|
527
|
+
communityAudit.set(key, { allowed: false });
|
|
528
|
+
communityAuditLatched = true;
|
|
529
|
+
if (!communityAuditUnavailableReported) {
|
|
530
|
+
base.failures.push({ stage: 'validation', code: 'community_audit_unavailable' });
|
|
531
|
+
communityAuditUnavailableReported = true;
|
|
532
|
+
}
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
if (auditAttempts >= MAX_COMMUNITY_AUDITS) {
|
|
536
|
+
communityAudit.set(key, { allowed: false });
|
|
537
|
+
if (!auditLimitReported) {
|
|
538
|
+
base.failures.push({ stage: 'validation', code: 'community_audit_limit_reached' });
|
|
539
|
+
auditLimitReported = true;
|
|
540
|
+
}
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
auditAttempts += 1;
|
|
544
|
+
let audit;
|
|
545
|
+
try {
|
|
546
|
+
audit = await authorizeSkillMaterialization(this.provider, candidate);
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
const code = providerFailureCode(error);
|
|
550
|
+
if (code === undefined)
|
|
551
|
+
throw error;
|
|
552
|
+
if (error instanceof SkillProviderError && (error.code === 'registry_rate_limited' || error.code === 'registry_unavailable')) {
|
|
553
|
+
writePersistentSkillAuditFailure(database, this.provider.id, candidate, error.code, externalSkillFailureTtlMs(error.retryAfterSeconds), now);
|
|
554
|
+
}
|
|
555
|
+
providerRequestsLatched = true;
|
|
556
|
+
communityAudit.set(key, { allowed: false });
|
|
557
|
+
communityAuditLatched = true;
|
|
558
|
+
if (!communityAuditUnavailableReported) {
|
|
559
|
+
base.failures.push({ stage: 'validation', code: 'community_audit_unavailable' });
|
|
560
|
+
communityAuditUnavailableReported = true;
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
if (audit.status === 'unavailable') {
|
|
565
|
+
writePersistentSkillAuditFailure(database, this.provider.id, candidate, 'registry_unavailable', DEFAULT_EXTERNAL_SKILL_FAILURE_TTL_MS, now);
|
|
566
|
+
providerRequestsLatched = true;
|
|
567
|
+
communityAudit.set(key, { allowed: false });
|
|
568
|
+
communityAuditLatched = true;
|
|
569
|
+
if (!communityAuditUnavailableReported) {
|
|
570
|
+
base.failures.push({ stage: 'validation', code: 'community_audit_unavailable' });
|
|
571
|
+
communityAuditUnavailableReported = true;
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
clearPersistentSkillAuditFailure(database, this.provider.id, candidate);
|
|
576
|
+
const allowed = audit.status === 'passed' ? { candidate: audit.candidate, authorization: audit.authorization } : null;
|
|
577
|
+
communityAudit.set(key, allowed === null ? { allowed: false } : { allowed: true, authorization: allowed.authorization });
|
|
578
|
+
if (allowed === null)
|
|
579
|
+
base.failures.push({ stage: 'validation', code: 'community_audit_failed' });
|
|
580
|
+
return allowed;
|
|
581
|
+
};
|
|
582
|
+
const existingByIdentity = new Map(existingSkills.map((record) => [sourceSkillIdentity({ sourceType: record.sourceType, source: record.sourceLocator, slug: record.slug }), record]));
|
|
583
|
+
const successfulSkills = new Set(base.selected.flatMap((candidate) => {
|
|
584
|
+
const record = existingSkills.find((skill) => skill.skillId === candidate.skillId);
|
|
585
|
+
return record === undefined ? [] : [sourceSkillIdentity({ sourceType: record.sourceType, source: record.sourceLocator, slug: record.slug })];
|
|
586
|
+
}));
|
|
587
|
+
const successfulRequirements = new Set();
|
|
588
|
+
const attemptedSkills = new Set();
|
|
589
|
+
let sourceRequestsLatched = false;
|
|
590
|
+
const preparedSelections = [];
|
|
591
|
+
const pendingFailureTransitions = new Map();
|
|
592
|
+
const recordRecoverableFailure = (candidate, error, selectedExisting) => {
|
|
593
|
+
const code = sourceOrValidationFailureCode(error);
|
|
594
|
+
if (code === undefined)
|
|
595
|
+
return false;
|
|
596
|
+
const identity = sourceSkillIdentity(candidate);
|
|
597
|
+
const exactExisting = existingByIdentity.get(identity);
|
|
598
|
+
if (code === 'candidate_not_found_at_source') {
|
|
599
|
+
const selectedExact = selectedExisting !== undefined
|
|
600
|
+
&& sourceSkillIdentity({ sourceType: selectedExisting.sourceType, source: selectedExisting.sourceLocator, slug: selectedExisting.slug }) === identity
|
|
601
|
+
? selectedExisting
|
|
602
|
+
: undefined;
|
|
603
|
+
const existing = selectedExact ?? exactExisting;
|
|
604
|
+
if (existing === undefined)
|
|
605
|
+
pendingFailureTransitions.set(`new:${identity}`, { kind: 'new-stale', candidate });
|
|
606
|
+
else
|
|
607
|
+
pendingFailureTransitions.set(`existing:${existing.skillId}`, { kind: 'existing', record: existing, state: 'stale' });
|
|
608
|
+
}
|
|
609
|
+
else if ((code === 'skill_disabled_for_model_invocation' || code === 'skill_secret_detected' || code === 'skill_blocked') && exactExisting !== undefined) {
|
|
610
|
+
// A registry alias is only a hint. Never create or block that guessed
|
|
611
|
+
// identity when no verified canonical mapping exists.
|
|
612
|
+
pendingFailureTransitions.set(`existing:${exactExisting.skillId}`, { kind: 'existing', record: exactExisting, state: 'blocked' });
|
|
613
|
+
}
|
|
614
|
+
base.failures.push({ stage: /^(?:skill_|candidate_|source_tree)/u.test(code) ? 'validation' : 'source', code });
|
|
615
|
+
return true;
|
|
616
|
+
};
|
|
617
|
+
const processCandidates = async (candidates) => {
|
|
618
|
+
if (attemptedSkills.size >= MAX_SOURCE_ATTEMPTS || base.selected.length + preparedSelections.length >= selectedSkillLimit)
|
|
619
|
+
return;
|
|
620
|
+
const selected = new Map();
|
|
621
|
+
for (const requirement of requirementsToSearch) {
|
|
622
|
+
if (successfulRequirements.has(requirement.id))
|
|
623
|
+
continue;
|
|
624
|
+
const ranked = rankSkillCandidates({
|
|
625
|
+
candidates: [...candidates.values()].filter((item) => item.requirement.id === requirement.id).map((item) => item.candidate),
|
|
626
|
+
requirement,
|
|
627
|
+
task: input.task,
|
|
628
|
+
profile: input.profile,
|
|
629
|
+
mode,
|
|
630
|
+
});
|
|
631
|
+
for (const rankedCandidate of ranked) {
|
|
632
|
+
const candidate = rankedCandidate.candidate;
|
|
633
|
+
const identity = sourceSkillIdentity(candidate);
|
|
634
|
+
if (candidate.duplicate || selected.has(identity) || attemptedSkills.has(identity))
|
|
635
|
+
continue;
|
|
636
|
+
const owners = existingIdentityOwners(database, listExternalSkills(database), candidate, candidate, requirement, input.fingerprint);
|
|
637
|
+
const exactOwners = owners.filter((record) => sourceSkillIdentity({ sourceType: record.sourceType, source: record.sourceLocator, slug: record.slug }) === identity);
|
|
638
|
+
if (exactOwners.length > 1 || (exactOwners.length === 0 && owners.length > 1))
|
|
639
|
+
throw new KiokukoError('CONFLICT', 'External Skill canonical identity is ambiguous');
|
|
640
|
+
const existing = exactOwners[0] ?? owners[0];
|
|
641
|
+
if (suppressAutomaticImport(existing))
|
|
642
|
+
continue;
|
|
643
|
+
const audited = await communityAllowed(candidate);
|
|
644
|
+
if (audited === null)
|
|
645
|
+
continue;
|
|
646
|
+
selected.set(identity, { candidate: audited.candidate, requirement, ...(existing === undefined ? {} : { existing }) });
|
|
647
|
+
attemptedSkills.add(identity);
|
|
648
|
+
if (attemptedSkills.size >= MAX_SOURCE_ATTEMPTS)
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
if (attemptedSkills.size >= MAX_SOURCE_ATTEMPTS)
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
for (const { candidate, requirement, existing: selectedExisting } of selected.values()) {
|
|
655
|
+
if (sourceRequestsLatched)
|
|
656
|
+
break;
|
|
657
|
+
const identity = sourceSkillIdentity(candidate);
|
|
658
|
+
if (successfulRequirements.has(requirement.id) || successfulSkills.has(identity))
|
|
659
|
+
continue;
|
|
660
|
+
const cachedSourceFailure = readPersistentSkillSourceFailure(database, candidate, now);
|
|
661
|
+
if (cachedSourceFailure !== null) {
|
|
662
|
+
base.failures.push({ stage: 'source', code: cachedSourceFailure.code });
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
let snapshot;
|
|
666
|
+
let documents;
|
|
667
|
+
let authorizedCanonicalCandidate;
|
|
668
|
+
let authorization;
|
|
669
|
+
try {
|
|
670
|
+
let fetchRequest = { purpose: 'discovery' };
|
|
671
|
+
if (selectedExisting !== undefined) {
|
|
672
|
+
const detail = readExternalSkill(database, selectedExisting.skillId);
|
|
673
|
+
if (detail === undefined)
|
|
674
|
+
throw new KiokukoError('CONFLICT', 'External Skill changed during discovery');
|
|
675
|
+
fetchRequest = externalSkillSourceFetchRequest(detail);
|
|
676
|
+
}
|
|
677
|
+
const fetched = await this.fetchSnapshot(candidate, fetchRequest);
|
|
678
|
+
snapshot = validateSkillSnapshot({
|
|
679
|
+
candidate,
|
|
680
|
+
sourceCommit: fetched.sourceCommit,
|
|
681
|
+
files: fetched.files.map((file) => ({ path: file.path, content: file.content, primary: file.primary })),
|
|
682
|
+
});
|
|
683
|
+
const reviewedCatalog = isReviewedCatalogCandidate(candidate, requirement) ? reviewedCatalogSkill(candidate) : undefined;
|
|
684
|
+
const primaryPath = snapshot.files.find((file) => file.primary)?.path;
|
|
685
|
+
if (reviewedCatalog !== undefined && (primaryPath === undefined || !reviewedCatalog.primaryPaths.includes(primaryPath))) {
|
|
686
|
+
throw new SkillSourceError('skill_validation_failed');
|
|
687
|
+
}
|
|
688
|
+
if (requirementMatchScore(snapshot.candidate, requirement, gap.missing) === 0)
|
|
689
|
+
throw new SkillSourceError('skill_validation_failed');
|
|
690
|
+
snapshot = { ...snapshot, candidate: normalizeOfficial(snapshot.candidate, requirement) };
|
|
691
|
+
const authorized = await communityAllowed(snapshot.candidate);
|
|
692
|
+
if (authorized === null)
|
|
693
|
+
continue;
|
|
694
|
+
authorizedCanonicalCandidate = authorized.candidate;
|
|
695
|
+
authorization = authorized.authorization;
|
|
696
|
+
documents = documentsFromSkillSnapshot(snapshot);
|
|
697
|
+
clearPersistentSkillSourceFailure(database, candidate);
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
if (error instanceof SkillSourceError && (error.code === 'source_rate_limited' || error.code === 'source_unavailable')) {
|
|
701
|
+
writePersistentSkillSourceFailure(database, candidate, error.code, externalSkillFailureTtlMs(error.retryAfterSeconds), now);
|
|
702
|
+
if (error.code === 'source_rate_limited')
|
|
703
|
+
sourceRequestsLatched = true;
|
|
704
|
+
}
|
|
705
|
+
if (!recordRecoverableFailure(candidate, error, selectedExisting))
|
|
706
|
+
throw error;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
const canonicalCandidate = authorizedCanonicalCandidate;
|
|
710
|
+
const canonicalIdentity = sourceSkillIdentity(canonicalCandidate);
|
|
711
|
+
if (successfulSkills.has(canonicalIdentity))
|
|
712
|
+
continue;
|
|
713
|
+
preparedSelections.push({
|
|
714
|
+
requestedCandidate: candidate,
|
|
715
|
+
canonicalCandidate,
|
|
716
|
+
...(authorization === undefined ? {} : { authorization }),
|
|
717
|
+
requirement,
|
|
718
|
+
...(selectedExisting === undefined ? {} : { selectedExisting }),
|
|
719
|
+
...(existingByIdentity.get(canonicalIdentity) === undefined ? {} : { observedCanonical: existingByIdentity.get(canonicalIdentity) }),
|
|
720
|
+
snapshot: { ...snapshot, candidate: canonicalCandidate },
|
|
721
|
+
documents,
|
|
722
|
+
});
|
|
723
|
+
successfulSkills.add(canonicalIdentity);
|
|
724
|
+
successfulRequirements.add(requirement.id);
|
|
725
|
+
if (base.selected.length + preparedSelections.length >= selectedSkillLimit)
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
// Owner-scoped and curated candidates are source-validated before any
|
|
730
|
+
// unscoped community query leaves the process.
|
|
731
|
+
await processCandidates(officialCandidates);
|
|
732
|
+
// Phase 3: general registry search is community-only and runs only for
|
|
733
|
+
// requirements that direct/owner-verified discovery did not satisfy.
|
|
734
|
+
if (mode === 'community' && base.selected.length + preparedSelections.length < selectedSkillLimit && attemptedSkills.size < MAX_SOURCE_ATTEMPTS) {
|
|
735
|
+
const remaining = requirementsToSearch.filter((requirement) => !successfulRequirements.has(requirement.id));
|
|
736
|
+
for (const query of queries) {
|
|
737
|
+
const queryRequirements = requirementsForQuery(query, remaining);
|
|
738
|
+
if (queryRequirements.length > 0)
|
|
739
|
+
await search(query, null, queryRequirements, communityCandidates);
|
|
740
|
+
}
|
|
741
|
+
await processCandidates(communityCandidates);
|
|
742
|
+
}
|
|
743
|
+
if (preparedSelections.length > 0 || pendingFailureTransitions.size > 0) {
|
|
744
|
+
let outcomes;
|
|
745
|
+
try {
|
|
746
|
+
outcomes = withImmediateTransaction(database, () => {
|
|
747
|
+
// Every current-state and CAS check runs under the same write lock,
|
|
748
|
+
// before the first candidate import mutates durable state.
|
|
749
|
+
try {
|
|
750
|
+
this.assertBeforePersist?.();
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
// Caller-owned state assertions are not persistence failures. Keep
|
|
754
|
+
// them out of the recoverable CAS/SQLite summary boundary below.
|
|
755
|
+
throw new CallerPersistenceAssertionError(error);
|
|
756
|
+
}
|
|
757
|
+
const currentSkills = listExternalSkills(database);
|
|
758
|
+
const failureTransitions = [];
|
|
759
|
+
for (const transition of pendingFailureTransitions.values()) {
|
|
760
|
+
if (transition.kind === 'new-stale') {
|
|
761
|
+
const identity = sourceSkillIdentity(transition.candidate);
|
|
762
|
+
if (!currentSkills.some((record) => sourceSkillIdentity({ sourceType: record.sourceType, source: record.sourceLocator, slug: record.slug }) === identity)) {
|
|
763
|
+
failureTransitions.push(transition);
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const current = currentSkills.find((record) => record.skillId === transition.record.skillId);
|
|
768
|
+
const expected = externalSkillRefreshExpectation(transition.record);
|
|
769
|
+
if (current === undefined
|
|
770
|
+
|| current.generation !== expected.generation
|
|
771
|
+
|| current.sourceCommit !== expected.sourceCommit
|
|
772
|
+
|| current.snapshotHash !== expected.snapshotHash
|
|
773
|
+
|| current.state !== expected.state
|
|
774
|
+
|| current.lastCheckedAt !== expected.lastCheckedAt) {
|
|
775
|
+
throw new KiokukoError('CONFLICT', 'External Skill changed during discovery');
|
|
776
|
+
}
|
|
777
|
+
failureTransitions.push(transition);
|
|
778
|
+
}
|
|
779
|
+
const resolvedSelections = preparedSelections.map((plan) => {
|
|
780
|
+
const { requestedCandidate, canonicalCandidate, requirement, selectedExisting, observedCanonical, snapshot, documents } = plan;
|
|
781
|
+
const canonicalIdentity = sourceSkillIdentity(canonicalCandidate);
|
|
782
|
+
const selectedMaterialized = selectedExisting !== undefined && (selectedExisting.sourceCommit !== null || selectedExisting.snapshotHash !== null);
|
|
783
|
+
const canonicalTarget = selectedExisting !== undefined && !selectedMaterialized && observedCanonical !== undefined && selectedExisting.skillId !== observedCanonical.skillId
|
|
784
|
+
? { skillId: observedCanonical.skillId, expected: externalSkillRefreshExpectation(observedCanonical) }
|
|
785
|
+
: undefined;
|
|
786
|
+
const observedExisting = selectedExisting ?? observedCanonical;
|
|
787
|
+
const currentOwners = existingIdentityOwners(database, currentSkills, requestedCandidate, canonicalCandidate, requirement, input.fingerprint);
|
|
788
|
+
const concurrentlyImported = observedExisting === undefined && currentOwners.length === 1
|
|
789
|
+
? currentOwners.find((record) => {
|
|
790
|
+
if (sourceSkillIdentity({ sourceType: record.sourceType, source: record.sourceLocator, slug: record.slug }) !== canonicalIdentity
|
|
791
|
+
|| record.state !== 'imported' || record.sourceCommit !== snapshot.sourceCommit || record.snapshotHash !== snapshot.snapshotHash
|
|
792
|
+
|| !hasSkillMaterializationAuthorization(canonicalCandidate, plan.authorization))
|
|
793
|
+
return false;
|
|
794
|
+
const storedRequirement = externalSkillRequirement(database, record.skillId);
|
|
795
|
+
return storedRequirement !== undefined && requirementOverlaps(storedRequirement, requirement)
|
|
796
|
+
&& applicabilityMatchesFingerprint(storedRequirement, input.fingerprint);
|
|
797
|
+
})
|
|
798
|
+
: undefined;
|
|
799
|
+
if (concurrentlyImported !== undefined)
|
|
800
|
+
return { kind: 'reused', plan, record: concurrentlyImported };
|
|
801
|
+
const allowedOwnerIds = new Set([selectedExisting?.skillId, observedCanonical?.skillId].filter((value) => value !== undefined));
|
|
802
|
+
if (currentOwners.some((record) => !allowedOwnerIds.has(record.skillId))
|
|
803
|
+
|| observedExisting === undefined && currentOwners.length > 0
|
|
804
|
+
|| selectedExisting === undefined && currentOwners.length > 1
|
|
805
|
+
|| selectedExisting !== undefined && !selectedMaterialized && currentOwners.length > (canonicalTarget === undefined ? 1 : 2)) {
|
|
806
|
+
throw new KiokukoError('CONFLICT', 'External Skill changed during discovery');
|
|
807
|
+
}
|
|
808
|
+
const currentObserved = observedExisting === undefined ? undefined : currentSkills.find((record) => record.skillId === observedExisting.skillId);
|
|
809
|
+
if (observedExisting !== undefined && currentObserved === undefined)
|
|
810
|
+
throw new KiokukoError('CONFLICT', 'External Skill changed during discovery');
|
|
811
|
+
if (suppressAutomaticImport(currentObserved))
|
|
812
|
+
return { kind: 'skipped', plan };
|
|
813
|
+
const prepared = {
|
|
814
|
+
skill: canonicalCandidate,
|
|
815
|
+
sourceWorkspace: externalSkillWorkspace(canonicalCandidate),
|
|
816
|
+
sourceCommit: snapshot.sourceCommit,
|
|
817
|
+
snapshotHash: snapshot.snapshotHash,
|
|
818
|
+
frontmatter: snapshot.frontmatter,
|
|
819
|
+
documents,
|
|
820
|
+
requirement,
|
|
821
|
+
};
|
|
822
|
+
if (observedExisting === undefined)
|
|
823
|
+
return { kind: 'new', plan, input: prepared };
|
|
824
|
+
if (observedExisting.sourceCommit === null && observedExisting.snapshotHash === null) {
|
|
825
|
+
return { kind: 'existing', plan, input: prepared, existing: observedExisting, ...(canonicalTarget === undefined ? {} : { canonicalTarget }) };
|
|
826
|
+
}
|
|
827
|
+
return { kind: 'refresh', plan, input: prepared, existing: observedExisting };
|
|
828
|
+
});
|
|
829
|
+
for (const transition of failureTransitions) {
|
|
830
|
+
if (transition.kind === 'existing') {
|
|
831
|
+
markExternalSkillRefreshFailureInTransaction(database, transition.record.skillId, transition.state, externalSkillRefreshExpectation(transition.record), now);
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
const discovered = recordDiscoveredSkillInTransaction(database, transition.candidate, now);
|
|
835
|
+
markExternalSkillRefreshFailureInTransaction(database, discovered.skillId, 'stale', externalSkillRefreshExpectation(discovered), now);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return resolvedSelections.map((selection) => {
|
|
839
|
+
if (selection.kind === 'reused' || selection.kind === 'skipped')
|
|
840
|
+
return selection;
|
|
841
|
+
if (selection.kind === 'new') {
|
|
842
|
+
return { kind: 'selected', plan: selection.plan, result: persistNewSkillImportInTransaction(database, selection.input, now, selection.plan.authorization) };
|
|
843
|
+
}
|
|
844
|
+
if (selection.kind === 'existing') {
|
|
845
|
+
return {
|
|
846
|
+
kind: 'selected',
|
|
847
|
+
plan: selection.plan,
|
|
848
|
+
result: persistExistingSkillImportInTransaction(database, selection.existing.skillId, selection.input, externalSkillRefreshExpectation(selection.existing), now, selection.canonicalTarget, selection.plan.authorization),
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
const result = refreshExternalSkillSnapshotInTransaction(database, selection.existing.skillId, selection.plan.snapshot, selection.input.documents, selection.plan.requirement, externalSkillRefreshExpectation(selection.existing), now, selection.plan.authorization);
|
|
852
|
+
return result.kind === 'staled'
|
|
853
|
+
? { kind: 'staled', plan: selection.plan }
|
|
854
|
+
: { kind: 'selected', plan: selection.plan, result: result.result };
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
if (error instanceof CallerPersistenceAssertionError)
|
|
860
|
+
throw error.assertionError;
|
|
861
|
+
const code = persistenceFailureCode(error);
|
|
862
|
+
if (code === undefined)
|
|
863
|
+
throw error;
|
|
864
|
+
base.failures.push({ stage: 'persistence', code });
|
|
865
|
+
outcomes = [];
|
|
866
|
+
}
|
|
867
|
+
for (const outcome of outcomes) {
|
|
868
|
+
if (outcome.kind === 'staled') {
|
|
869
|
+
base.failures.push({ stage: 'persistence', code: 'persistence_conflict' });
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (outcome.kind === 'skipped' || base.selected.length >= selectedSkillLimit)
|
|
873
|
+
continue;
|
|
874
|
+
if (outcome.kind === 'reused') {
|
|
875
|
+
if (!base.selected.some((item) => item.skillId === outcome.record.skillId)) {
|
|
876
|
+
base.selected.push({ skillId: outcome.record.skillId, name: outcome.record.name, source: outcome.record.sourceLocator, officialStatus: officialStatus(outcome.record.officialStatus), imported: true, updated: false });
|
|
877
|
+
}
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (!base.selected.some((item) => item.skillId === outcome.result.skillId)) {
|
|
881
|
+
base.selected.push({
|
|
882
|
+
skillId: outcome.result.skillId,
|
|
883
|
+
name: outcome.plan.canonicalCandidate.name,
|
|
884
|
+
source: outcome.plan.canonicalCandidate.source,
|
|
885
|
+
officialStatus: outcome.plan.canonicalCandidate.officialStatus,
|
|
886
|
+
imported: outcome.result.imported > 0,
|
|
887
|
+
updated: outcome.result.updated,
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
base.candidates = seenCandidates.size;
|
|
893
|
+
return base;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
export async function discoverSkills(database, input, dependencies = {}) {
|
|
897
|
+
const effectiveDependencies = dependencies.fetchImpl === undefined && input.fetchImpl !== undefined
|
|
898
|
+
? { ...dependencies, fetchImpl: input.fetchImpl }
|
|
899
|
+
: dependencies;
|
|
900
|
+
return discoveryServiceFor(effectiveDependencies).discover(database, input);
|
|
901
|
+
}
|
|
902
|
+
let sharedDiscoveryService;
|
|
903
|
+
const fetchDiscoveryServices = new WeakMap();
|
|
904
|
+
function discoveryServiceFor(dependencies) {
|
|
905
|
+
const keys = Object.keys(dependencies);
|
|
906
|
+
if (keys.length === 0) {
|
|
907
|
+
sharedDiscoveryService ??= new SkillDiscoveryService();
|
|
908
|
+
return sharedDiscoveryService;
|
|
909
|
+
}
|
|
910
|
+
if (keys.length === 1 && dependencies.fetchImpl !== undefined) {
|
|
911
|
+
const key = dependencies.fetchImpl;
|
|
912
|
+
const existing = fetchDiscoveryServices.get(key);
|
|
913
|
+
if (existing)
|
|
914
|
+
return existing;
|
|
915
|
+
const service = new SkillDiscoveryService(dependencies);
|
|
916
|
+
fetchDiscoveryServices.set(key, service);
|
|
917
|
+
return service;
|
|
918
|
+
}
|
|
919
|
+
return new SkillDiscoveryService(dependencies);
|
|
920
|
+
}
|
|
921
|
+
//# sourceMappingURL=discovery-service.js.map
|