memory-journal-mcp 7.7.0 → 8.0.0
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.md +126 -56
- package/dist/chunk-6OHRCNYW.js +3231 -0
- package/dist/chunk-JFMITANR.js +5168 -0
- package/dist/{chunk-QCQPAF4I.js → chunk-MWNLAEHR.js} +301 -4321
- package/dist/{chunk-ARLYSFSI.js → chunk-UHSO65A4.js} +4242 -6092
- package/dist/cli.js +21 -3
- package/dist/index.d.ts +16 -13
- package/dist/index.js +4 -2
- package/dist/resources-IJVKDFGS.js +2 -0
- package/dist/tools-44DGXE3V.js +2 -0
- package/dist/worker-script.js +201 -20
- package/package.json +7 -4
- package/skills/README.md +62 -25
- package/skills/adversarial-performance/SKILL.md +139 -0
- package/skills/adversarial-performance/references/audit-categories.md +462 -0
- package/skills/adversarial-performance/references/copilot-performance-prompts.md +44 -0
- package/skills/adversarial-performance/references/copilot-usage.md +16 -0
- package/skills/adversarial-performance/references/feedback-loop.md +177 -0
- package/skills/adversarial-performance/references/multi-pass-performance-protocol.md +398 -0
- package/skills/adversarial-planner/SKILL.md +23 -54
- package/skills/adversarial-planner/references/copilot-integration.md +25 -40
- package/skills/adversarial-planner/references/copilot-usage.md +16 -0
- package/skills/adversarial-planner/references/multi-pass-protocol.md +4 -0
- package/skills/adversarial-security/SKILL.md +149 -0
- package/skills/adversarial-security/references/adversarial-base-protocol.md +44 -0
- package/skills/adversarial-security/references/audit-categories.md +723 -0
- package/skills/adversarial-security/references/copilot-security-prompts.md +142 -0
- package/skills/adversarial-security/references/copilot-usage.md +16 -0
- package/skills/adversarial-security/references/feedback-loop.md +206 -0
- package/skills/adversarial-security/references/journal-opt-out.md +7 -0
- package/skills/adversarial-security/references/multi-pass-security-protocol.md +403 -0
- package/skills/adversarial-skill-audit/SKILL.md +118 -0
- package/skills/adversarial-skill-audit/references/audit-categories.md +308 -0
- package/skills/adversarial-skill-audit/references/copilot-skill-prompts.md +68 -0
- package/skills/adversarial-skill-audit/references/copilot-usage.md +16 -0
- package/skills/adversarial-skill-audit/references/feedback-loop.md +155 -0
- package/skills/adversarial-skill-audit/references/multi-pass-skill-protocol.md +367 -0
- package/skills/adversarial-skill-audit/scripts/check-skills.ps1 +48 -0
- package/skills/adversarial-skill-audit/scripts/run-copilot.ps1 +52 -0
- package/skills/adversarial-workflow-audit/SKILL.md +82 -0
- package/skills/adversarial-workflow-audit/references/audit-categories.md +28 -0
- package/skills/adversarial-workflow-audit/references/copilot-usage.md +16 -0
- package/skills/adversarial-workflow-audit/scripts/check-workflows.ps1 +24 -0
- package/skills/agents-sdk/SKILL.md +220 -0
- package/skills/agents-sdk/references/callable.md +92 -0
- package/skills/agents-sdk/references/codemode.md +209 -0
- package/skills/agents-sdk/references/email.md +144 -0
- package/skills/agents-sdk/references/mcp/SKILL.md +65 -0
- package/skills/agents-sdk/references/mcp/code-mode-reference.md +245 -0
- package/skills/agents-sdk/references/mcp/oauth-reference.md +359 -0
- package/skills/agents-sdk/references/mcp/references/architecture-reference.md +208 -0
- package/skills/agents-sdk/references/mcp/references/cloudflare-quickstart.md +156 -0
- package/skills/agents-sdk/references/mcp/references/error-handling.md +343 -0
- package/skills/agents-sdk/references/mcp/references/http-security.md +164 -0
- package/skills/agents-sdk/references/mcp/references/implementation-guide.md +507 -0
- package/skills/agents-sdk/references/mcp/references/testing-reference.md +171 -0
- package/skills/agents-sdk/references/mcp.md +157 -0
- package/skills/agents-sdk/references/state-scheduling.md +164 -0
- package/skills/agents-sdk/references/streaming-chat.md +168 -0
- package/skills/agents-sdk/references/workflows.md +136 -0
- package/skills/auth-identity/SKILL.md +48 -0
- package/skills/autonomous-dev/SKILL.md +46 -23
- package/skills/autonomous-dev/references/workflow_orchestration.md +22 -0
- package/skills/aws/SKILL.md +39 -0
- package/skills/azure/SKILL.md +38 -0
- package/skills/bin/sync.js +7 -1
- package/skills/biome/SKILL.md +59 -0
- package/skills/bun/SKILL.md +8 -2
- package/skills/cloudflare/SKILL.md +37 -0
- package/skills/cloudflare/references/agents-sdk/README.md +95 -0
- package/skills/cloudflare/references/agents-sdk/api.md +195 -0
- package/skills/cloudflare/references/agents-sdk/configuration.md +178 -0
- package/skills/cloudflare/references/agents-sdk/gotchas.md +173 -0
- package/skills/cloudflare/references/agents-sdk/patterns.md +215 -0
- package/skills/cloudflare/references/ai-gateway/README.md +176 -0
- package/skills/cloudflare/references/ai-gateway/configuration.md +117 -0
- package/skills/cloudflare/references/ai-gateway/dynamic-routing.md +88 -0
- package/skills/cloudflare/references/ai-gateway/features.md +96 -0
- package/skills/cloudflare/references/ai-gateway/sdk-integration.md +110 -0
- package/skills/cloudflare/references/ai-gateway/troubleshooting.md +90 -0
- package/skills/cloudflare/references/ai-search/README.md +145 -0
- package/skills/cloudflare/references/ai-search/api.md +87 -0
- package/skills/cloudflare/references/ai-search/configuration.md +91 -0
- package/skills/cloudflare/references/ai-search/gotchas.md +92 -0
- package/skills/cloudflare/references/ai-search/patterns.md +87 -0
- package/skills/cloudflare/references/analytics-engine/README.md +96 -0
- package/skills/cloudflare/references/analytics-engine/api.md +112 -0
- package/skills/cloudflare/references/analytics-engine/configuration.md +107 -0
- package/skills/cloudflare/references/analytics-engine/gotchas.md +87 -0
- package/skills/cloudflare/references/analytics-engine/patterns.md +83 -0
- package/skills/cloudflare/references/api/README.md +66 -0
- package/skills/cloudflare/references/api/api.md +205 -0
- package/skills/cloudflare/references/api/configuration.md +158 -0
- package/skills/cloudflare/references/api/gotchas.md +231 -0
- package/skills/cloudflare/references/api/patterns.md +208 -0
- package/skills/cloudflare/references/api-shield/README.md +44 -0
- package/skills/cloudflare/references/api-shield/api.md +153 -0
- package/skills/cloudflare/references/api-shield/configuration.md +210 -0
- package/skills/cloudflare/references/api-shield/gotchas.md +132 -0
- package/skills/cloudflare/references/api-shield/patterns.md +185 -0
- package/skills/cloudflare/references/argo-smart-routing/README.md +96 -0
- package/skills/cloudflare/references/argo-smart-routing/api.md +253 -0
- package/skills/cloudflare/references/argo-smart-routing/configuration.md +205 -0
- package/skills/cloudflare/references/argo-smart-routing/gotchas.md +115 -0
- package/skills/cloudflare/references/argo-smart-routing/patterns.md +107 -0
- package/skills/cloudflare/references/bindings/README.md +127 -0
- package/skills/cloudflare/references/bindings/api.md +214 -0
- package/skills/cloudflare/references/bindings/configuration.md +200 -0
- package/skills/cloudflare/references/bindings/gotchas.md +210 -0
- package/skills/cloudflare/references/bindings/patterns.md +205 -0
- package/skills/cloudflare/references/bot-management/README.md +95 -0
- package/skills/cloudflare/references/bot-management/api.md +175 -0
- package/skills/cloudflare/references/bot-management/configuration.md +175 -0
- package/skills/cloudflare/references/bot-management/gotchas.md +116 -0
- package/skills/cloudflare/references/bot-management/patterns.md +181 -0
- package/skills/cloudflare/references/browser-rendering/README.md +84 -0
- package/skills/cloudflare/references/browser-rendering/api.md +108 -0
- package/skills/cloudflare/references/browser-rendering/configuration.md +78 -0
- package/skills/cloudflare/references/browser-rendering/gotchas.md +91 -0
- package/skills/cloudflare/references/browser-rendering/patterns.md +93 -0
- package/skills/cloudflare/references/c3/README.md +111 -0
- package/skills/cloudflare/references/c3/api.md +71 -0
- package/skills/cloudflare/references/c3/configuration.md +85 -0
- package/skills/cloudflare/references/c3/gotchas.md +97 -0
- package/skills/cloudflare/references/c3/patterns.md +84 -0
- package/skills/cloudflare/references/cache-reserve/README.md +150 -0
- package/skills/cloudflare/references/cache-reserve/api.md +184 -0
- package/skills/cloudflare/references/cache-reserve/configuration.md +170 -0
- package/skills/cloudflare/references/cache-reserve/gotchas.md +136 -0
- package/skills/cloudflare/references/cache-reserve/patterns.md +197 -0
- package/skills/cloudflare/references/containers/README.md +87 -0
- package/skills/cloudflare/references/containers/api.md +197 -0
- package/skills/cloudflare/references/containers/configuration.md +191 -0
- package/skills/cloudflare/references/containers/gotchas.md +182 -0
- package/skills/cloudflare/references/containers/patterns.md +204 -0
- package/skills/cloudflare/references/cron-triggers/README.md +101 -0
- package/skills/cloudflare/references/cron-triggers/api.md +224 -0
- package/skills/cloudflare/references/cron-triggers/configuration.md +190 -0
- package/skills/cloudflare/references/cron-triggers/gotchas.md +207 -0
- package/skills/cloudflare/references/cron-triggers/patterns.md +274 -0
- package/skills/cloudflare/references/d1/README.md +137 -0
- package/skills/cloudflare/references/d1/api.md +213 -0
- package/skills/cloudflare/references/d1/configuration.md +198 -0
- package/skills/cloudflare/references/d1/gotchas.md +98 -0
- package/skills/cloudflare/references/d1/patterns.md +240 -0
- package/skills/cloudflare/references/ddos/README.md +42 -0
- package/skills/cloudflare/references/ddos/api.md +158 -0
- package/skills/cloudflare/references/ddos/configuration.md +94 -0
- package/skills/cloudflare/references/ddos/gotchas.md +114 -0
- package/skills/cloudflare/references/ddos/patterns.md +220 -0
- package/skills/cloudflare/references/decision-trees.md +95 -0
- package/skills/cloudflare/references/do-storage/README.md +79 -0
- package/skills/cloudflare/references/do-storage/api.md +107 -0
- package/skills/cloudflare/references/do-storage/configuration.md +114 -0
- package/skills/cloudflare/references/do-storage/gotchas.md +153 -0
- package/skills/cloudflare/references/do-storage/patterns.md +210 -0
- package/skills/cloudflare/references/do-storage/testing.md +186 -0
- package/skills/cloudflare/references/durable-objects/README.md +194 -0
- package/skills/cloudflare/references/durable-objects/api.md +205 -0
- package/skills/cloudflare/references/durable-objects/configuration.md +160 -0
- package/skills/cloudflare/references/durable-objects/gotchas.md +200 -0
- package/skills/cloudflare/references/durable-objects/patterns.md +205 -0
- package/skills/cloudflare/references/email-routing/README.md +89 -0
- package/skills/cloudflare/references/email-routing/api.md +192 -0
- package/skills/cloudflare/references/email-routing/configuration.md +187 -0
- package/skills/cloudflare/references/email-routing/gotchas.md +203 -0
- package/skills/cloudflare/references/email-routing/patterns.md +241 -0
- package/skills/cloudflare/references/email-workers/README.md +153 -0
- package/skills/cloudflare/references/email-workers/api.md +227 -0
- package/skills/cloudflare/references/email-workers/configuration.md +115 -0
- package/skills/cloudflare/references/email-workers/gotchas.md +133 -0
- package/skills/cloudflare/references/email-workers/patterns.md +108 -0
- package/skills/cloudflare/references/graphql-api/README.md +147 -0
- package/skills/cloudflare/references/graphql-api/api.md +175 -0
- package/skills/cloudflare/references/graphql-api/configuration.md +151 -0
- package/skills/cloudflare/references/graphql-api/gotchas.md +111 -0
- package/skills/cloudflare/references/graphql-api/patterns.md +276 -0
- package/skills/cloudflare/references/hyperdrive/README.md +84 -0
- package/skills/cloudflare/references/hyperdrive/api.md +149 -0
- package/skills/cloudflare/references/hyperdrive/configuration.md +166 -0
- package/skills/cloudflare/references/hyperdrive/gotchas.md +77 -0
- package/skills/cloudflare/references/hyperdrive/patterns.md +203 -0
- package/skills/cloudflare/references/images/README.md +65 -0
- package/skills/cloudflare/references/images/api.md +101 -0
- package/skills/cloudflare/references/images/configuration.md +206 -0
- package/skills/cloudflare/references/images/gotchas.md +106 -0
- package/skills/cloudflare/references/images/patterns.md +126 -0
- package/skills/cloudflare/references/kv/README.md +90 -0
- package/skills/cloudflare/references/kv/api.md +163 -0
- package/skills/cloudflare/references/kv/configuration.md +148 -0
- package/skills/cloudflare/references/kv/gotchas.md +133 -0
- package/skills/cloudflare/references/kv/patterns.md +195 -0
- package/skills/cloudflare/references/miniflare/README.md +113 -0
- package/skills/cloudflare/references/miniflare/api.md +204 -0
- package/skills/cloudflare/references/miniflare/configuration.md +174 -0
- package/skills/cloudflare/references/miniflare/gotchas.md +179 -0
- package/skills/cloudflare/references/miniflare/patterns.md +187 -0
- package/skills/cloudflare/references/network-interconnect/README.md +104 -0
- package/skills/cloudflare/references/network-interconnect/api.md +220 -0
- package/skills/cloudflare/references/network-interconnect/configuration.md +123 -0
- package/skills/cloudflare/references/network-interconnect/gotchas.md +175 -0
- package/skills/cloudflare/references/network-interconnect/patterns.md +174 -0
- package/skills/cloudflare/references/observability/README.md +93 -0
- package/skills/cloudflare/references/observability/api.md +168 -0
- package/skills/cloudflare/references/observability/configuration.md +178 -0
- package/skills/cloudflare/references/observability/gotchas.md +125 -0
- package/skills/cloudflare/references/observability/patterns.md +105 -0
- package/skills/cloudflare/references/pages/README.md +92 -0
- package/skills/cloudflare/references/pages/api.md +205 -0
- package/skills/cloudflare/references/pages/configuration.md +216 -0
- package/skills/cloudflare/references/pages/gotchas.md +218 -0
- package/skills/cloudflare/references/pages/patterns.md +215 -0
- package/skills/cloudflare/references/pages-functions/README.md +104 -0
- package/skills/cloudflare/references/pages-functions/api.md +159 -0
- package/skills/cloudflare/references/pages-functions/configuration.md +130 -0
- package/skills/cloudflare/references/pages-functions/gotchas.md +102 -0
- package/skills/cloudflare/references/pages-functions/patterns.md +148 -0
- package/skills/cloudflare/references/pipelines/README.md +109 -0
- package/skills/cloudflare/references/pipelines/api.md +214 -0
- package/skills/cloudflare/references/pipelines/configuration.md +98 -0
- package/skills/cloudflare/references/pipelines/gotchas.md +84 -0
- package/skills/cloudflare/references/pipelines/patterns.md +87 -0
- package/skills/cloudflare/references/product-index.md +112 -0
- package/skills/cloudflare/references/pulumi/README.md +113 -0
- package/skills/cloudflare/references/pulumi/api.md +230 -0
- package/skills/cloudflare/references/pulumi/configuration.md +213 -0
- package/skills/cloudflare/references/pulumi/gotchas.md +205 -0
- package/skills/cloudflare/references/pulumi/patterns.md +260 -0
- package/skills/cloudflare/references/queues/README.md +99 -0
- package/skills/cloudflare/references/queues/api.md +211 -0
- package/skills/cloudflare/references/queues/configuration.md +151 -0
- package/skills/cloudflare/references/queues/gotchas.md +210 -0
- package/skills/cloudflare/references/queues/patterns.md +220 -0
- package/skills/cloudflare/references/r2/README.md +97 -0
- package/skills/cloudflare/references/r2/api.md +235 -0
- package/skills/cloudflare/references/r2/configuration.md +176 -0
- package/skills/cloudflare/references/r2/gotchas.md +190 -0
- package/skills/cloudflare/references/r2/patterns.md +203 -0
- package/skills/cloudflare/references/r2-data-catalog/README.md +157 -0
- package/skills/cloudflare/references/r2-data-catalog/api.md +199 -0
- package/skills/cloudflare/references/r2-data-catalog/configuration.md +205 -0
- package/skills/cloudflare/references/r2-data-catalog/gotchas.md +170 -0
- package/skills/cloudflare/references/r2-data-catalog/patterns.md +191 -0
- package/skills/cloudflare/references/r2-sql/README.md +138 -0
- package/skills/cloudflare/references/r2-sql/SKILL.md.backup +512 -0
- package/skills/cloudflare/references/r2-sql/api.md +159 -0
- package/skills/cloudflare/references/r2-sql/configuration.md +152 -0
- package/skills/cloudflare/references/r2-sql/gotchas.md +228 -0
- package/skills/cloudflare/references/r2-sql/patterns.md +230 -0
- package/skills/cloudflare/references/realtime-sfu/README.md +66 -0
- package/skills/cloudflare/references/realtime-sfu/api.md +164 -0
- package/skills/cloudflare/references/realtime-sfu/configuration.md +141 -0
- package/skills/cloudflare/references/realtime-sfu/gotchas.md +138 -0
- package/skills/cloudflare/references/realtime-sfu/patterns.md +187 -0
- package/skills/cloudflare/references/realtimekit/README.md +118 -0
- package/skills/cloudflare/references/realtimekit/api.md +234 -0
- package/skills/cloudflare/references/realtimekit/configuration.md +226 -0
- package/skills/cloudflare/references/realtimekit/gotchas.md +206 -0
- package/skills/cloudflare/references/realtimekit/patterns.md +240 -0
- package/skills/cloudflare/references/sandbox/README.md +104 -0
- package/skills/cloudflare/references/sandbox/api.md +200 -0
- package/skills/cloudflare/references/sandbox/configuration.md +154 -0
- package/skills/cloudflare/references/sandbox/gotchas.md +201 -0
- package/skills/cloudflare/references/sandbox/patterns.md +195 -0
- package/skills/cloudflare/references/secrets-store/README.md +77 -0
- package/skills/cloudflare/references/secrets-store/api.md +199 -0
- package/skills/cloudflare/references/secrets-store/configuration.md +187 -0
- package/skills/cloudflare/references/secrets-store/gotchas.md +97 -0
- package/skills/cloudflare/references/secrets-store/patterns.md +218 -0
- package/skills/cloudflare/references/smart-placement/README.md +143 -0
- package/skills/cloudflare/references/smart-placement/api.md +192 -0
- package/skills/cloudflare/references/smart-placement/configuration.md +202 -0
- package/skills/cloudflare/references/smart-placement/gotchas.md +180 -0
- package/skills/cloudflare/references/smart-placement/patterns.md +190 -0
- package/skills/cloudflare/references/snippets/README.md +74 -0
- package/skills/cloudflare/references/snippets/api.md +214 -0
- package/skills/cloudflare/references/snippets/configuration.md +239 -0
- package/skills/cloudflare/references/snippets/gotchas.md +104 -0
- package/skills/cloudflare/references/snippets/patterns.md +135 -0
- package/skills/cloudflare/references/spectrum/README.md +52 -0
- package/skills/cloudflare/references/spectrum/api.md +184 -0
- package/skills/cloudflare/references/spectrum/configuration.md +203 -0
- package/skills/cloudflare/references/spectrum/gotchas.md +155 -0
- package/skills/cloudflare/references/spectrum/patterns.md +206 -0
- package/skills/cloudflare/references/static-assets/README.md +65 -0
- package/skills/cloudflare/references/static-assets/api.md +201 -0
- package/skills/cloudflare/references/static-assets/configuration.md +186 -0
- package/skills/cloudflare/references/static-assets/gotchas.md +164 -0
- package/skills/cloudflare/references/static-assets/patterns.md +189 -0
- package/skills/cloudflare/references/stream/README.md +123 -0
- package/skills/cloudflare/references/stream/api-live.md +202 -0
- package/skills/cloudflare/references/stream/api.md +206 -0
- package/skills/cloudflare/references/stream/configuration.md +151 -0
- package/skills/cloudflare/references/stream/gotchas.md +139 -0
- package/skills/cloudflare/references/stream/patterns.md +217 -0
- package/skills/cloudflare/references/tail-workers/README.md +92 -0
- package/skills/cloudflare/references/tail-workers/api.md +203 -0
- package/skills/cloudflare/references/tail-workers/configuration.md +178 -0
- package/skills/cloudflare/references/tail-workers/gotchas.md +206 -0
- package/skills/cloudflare/references/tail-workers/patterns.md +190 -0
- package/skills/cloudflare/references/terraform/README.md +100 -0
- package/skills/cloudflare/references/terraform/api.md +178 -0
- package/skills/cloudflare/references/terraform/configuration.md +197 -0
- package/skills/cloudflare/references/terraform/gotchas.md +150 -0
- package/skills/cloudflare/references/terraform/patterns.md +174 -0
- package/skills/cloudflare/references/tunnel/README.md +137 -0
- package/skills/cloudflare/references/tunnel/api.md +205 -0
- package/skills/cloudflare/references/tunnel/configuration.md +163 -0
- package/skills/cloudflare/references/tunnel/gotchas.md +159 -0
- package/skills/cloudflare/references/tunnel/networking.md +174 -0
- package/skills/cloudflare/references/tunnel/patterns.md +199 -0
- package/skills/cloudflare/references/turn/README.md +86 -0
- package/skills/cloudflare/references/turn/api.md +236 -0
- package/skills/cloudflare/references/turn/configuration.md +181 -0
- package/skills/cloudflare/references/turn/gotchas.md +236 -0
- package/skills/cloudflare/references/turn/patterns.md +228 -0
- package/skills/cloudflare/references/turnstile/README.md +102 -0
- package/skills/cloudflare/references/turnstile/api.md +253 -0
- package/skills/cloudflare/references/turnstile/configuration.md +242 -0
- package/skills/cloudflare/references/turnstile/gotchas.md +253 -0
- package/skills/cloudflare/references/turnstile/patterns.md +195 -0
- package/skills/cloudflare/references/vectorize/README.md +133 -0
- package/skills/cloudflare/references/vectorize/api.md +89 -0
- package/skills/cloudflare/references/vectorize/configuration.md +91 -0
- package/skills/cloudflare/references/vectorize/gotchas.md +83 -0
- package/skills/cloudflare/references/vectorize/patterns.md +92 -0
- package/skills/cloudflare/references/waf/README.md +125 -0
- package/skills/cloudflare/references/waf/api.md +203 -0
- package/skills/cloudflare/references/waf/configuration.md +215 -0
- package/skills/cloudflare/references/waf/gotchas.md +208 -0
- package/skills/cloudflare/references/waf/patterns.md +236 -0
- package/skills/cloudflare/references/web-analytics/README.md +149 -0
- package/skills/cloudflare/references/web-analytics/configuration.md +81 -0
- package/skills/cloudflare/references/web-analytics/gotchas.md +86 -0
- package/skills/cloudflare/references/web-analytics/integration.md +63 -0
- package/skills/cloudflare/references/web-analytics/patterns.md +98 -0
- package/skills/cloudflare/references/workerd/README.md +85 -0
- package/skills/cloudflare/references/workerd/api.md +219 -0
- package/skills/cloudflare/references/workerd/configuration.md +200 -0
- package/skills/cloudflare/references/workerd/gotchas.md +151 -0
- package/skills/cloudflare/references/workerd/patterns.md +205 -0
- package/skills/cloudflare/references/workers/README.md +110 -0
- package/skills/cloudflare/references/workers/api.md +197 -0
- package/skills/cloudflare/references/workers/configuration.md +184 -0
- package/skills/cloudflare/references/workers/frameworks.md +200 -0
- package/skills/cloudflare/references/workers/gotchas.md +145 -0
- package/skills/cloudflare/references/workers/patterns.md +220 -0
- package/skills/cloudflare/references/workers-ai/README.md +206 -0
- package/skills/cloudflare/references/workers-ai/api.md +115 -0
- package/skills/cloudflare/references/workers-ai/configuration.md +98 -0
- package/skills/cloudflare/references/workers-ai/gotchas.md +130 -0
- package/skills/cloudflare/references/workers-ai/patterns.md +122 -0
- package/skills/cloudflare/references/workers-for-platforms/README.md +95 -0
- package/skills/cloudflare/references/workers-for-platforms/api.md +212 -0
- package/skills/cloudflare/references/workers-for-platforms/configuration.md +178 -0
- package/skills/cloudflare/references/workers-for-platforms/gotchas.md +134 -0
- package/skills/cloudflare/references/workers-for-platforms/patterns.md +210 -0
- package/skills/cloudflare/references/workers-playground/README.md +131 -0
- package/skills/cloudflare/references/workers-playground/api.md +101 -0
- package/skills/cloudflare/references/workers-playground/configuration.md +169 -0
- package/skills/cloudflare/references/workers-playground/gotchas.md +88 -0
- package/skills/cloudflare/references/workers-playground/patterns.md +134 -0
- package/skills/cloudflare/references/workers-vpc/README.md +130 -0
- package/skills/cloudflare/references/workers-vpc/api.md +196 -0
- package/skills/cloudflare/references/workers-vpc/configuration.md +151 -0
- package/skills/cloudflare/references/workers-vpc/gotchas.md +171 -0
- package/skills/cloudflare/references/workers-vpc/patterns.md +235 -0
- package/skills/cloudflare/references/workflows/README.md +72 -0
- package/skills/cloudflare/references/workflows/api.md +237 -0
- package/skills/cloudflare/references/workflows/configuration.md +158 -0
- package/skills/cloudflare/references/workflows/gotchas.md +97 -0
- package/skills/cloudflare/references/workflows/patterns.md +245 -0
- package/skills/cloudflare/references/wrangler/README.md +143 -0
- package/skills/cloudflare/references/wrangler/api.md +188 -0
- package/skills/cloudflare/references/wrangler/configuration.md +198 -0
- package/skills/cloudflare/references/wrangler/gotchas.md +212 -0
- package/skills/cloudflare/references/wrangler/patterns.md +211 -0
- package/skills/cloudflare/references/zaraz/IMPLEMENTATION_SUMMARY.md +131 -0
- package/skills/cloudflare/references/zaraz/README.md +114 -0
- package/skills/cloudflare/references/zaraz/api.md +118 -0
- package/skills/cloudflare/references/zaraz/configuration.md +94 -0
- package/skills/cloudflare/references/zaraz/gotchas.md +88 -0
- package/skills/cloudflare/references/zaraz/patterns.md +77 -0
- package/skills/docker/SKILL.md +7 -101
- package/skills/docker/references/advanced-examples.md +71 -0
- package/skills/docker/references/templates.md +34 -0
- package/skills/docs-marketer/SKILL.md +178 -0
- package/skills/docs-marketer/references/audit-categories.md +328 -0
- package/skills/docs-marketer/references/copilot-docs-prompts.md +88 -0
- package/skills/docs-marketer/references/copilot-usage.md +16 -0
- package/skills/docs-marketer/references/feedback-loop.md +155 -0
- package/skills/docs-marketer/references/multi-pass-docs-protocol.md +410 -0
- package/skills/drizzle-orm/SKILL.md +82 -0
- package/skills/durable-objects/SKILL.md +167 -0
- package/skills/durable-objects/references/advanced_features.md +29 -0
- package/skills/durable-objects/references/rules.md +300 -0
- package/skills/durable-objects/references/testing.md +261 -0
- package/skills/durable-objects/references/workers.md +336 -0
- package/skills/gcp/SKILL.md +37 -0
- package/skills/github-actions/SKILL.md +5 -58
- package/skills/github-actions/references/templates.md +65 -0
- package/skills/github-commander/SKILL.md +13 -21
- package/skills/github-commander/workflows/copilot-audit.md +12 -12
- package/skills/github-copilot-cli/SKILL.md +21 -26
- package/skills/github-repo-setup/SKILL.md +136 -0
- package/skills/github-repo-setup/references/community-standards.md +136 -0
- package/skills/github-repo-setup/references/github-automation.md +490 -0
- package/skills/github-repo-setup/references/inline-templates.md +205 -0
- package/skills/github-repo-setup/references/project-config.md +320 -0
- package/skills/gitlab/SKILL.md +7 -2
- package/skills/gitlab/package-lock.json +389 -389
- package/skills/golang/SKILL.md +8 -1
- package/skills/graphql/SKILL.md +30 -0
- package/skills/hono/SKILL.md +82 -0
- package/skills/journal-optimizer/SKILL.md +206 -0
- package/skills/journal-optimizer/references/optimizer-scripts.md +169 -0
- package/skills/llm-app-engineering/SKILL.md +18 -0
- package/skills/monorepo/SKILL.md +56 -0
- package/skills/multi-agent-orchestration/SKILL.md +14 -0
- package/skills/mysql/SKILL.md +6 -2
- package/skills/next-best-practices/SKILL.md +86 -0
- package/skills/next-best-practices/references/cache-components-examples.md +234 -0
- package/skills/next-best-practices/references/cache-components.md +210 -0
- package/skills/next-best-practices/references/upgrade-decision-tree.md +33 -0
- package/skills/next-best-practices/references/upgrade.md +43 -0
- package/skills/next-cache-components/SKILL.md +441 -0
- package/skills/next-upgrade/SKILL.md +43 -0
- package/skills/next-upgrade/references/decision-tree.md +33 -0
- package/skills/nodejs/SKILL.md +46 -0
- package/skills/opentelemetry/SKILL.md +62 -0
- package/skills/package.json +39 -4
- package/skills/playwright-standard/SKILL.md +6 -11
- package/skills/playwright-standard/references/locators.md +7 -0
- package/skills/postgres/SKILL.md +6 -1
- package/skills/python/SKILL.md +8 -70
- package/skills/python/references/advanced-patterns.md +37 -0
- package/skills/python/references/config-templates.md +48 -0
- package/skills/rag-pipelines/SKILL.md +14 -0
- package/skills/redis/SKILL.md +31 -0
- package/skills/render/SKILL.md +35 -0
- package/skills/rust/SKILL.md +15 -25
- package/skills/rust/references/borrow-checker.md +13 -0
- package/skills/rust/references/ecosystem.md +11 -0
- package/skills/sandbox-sdk/SKILL.md +186 -0
- package/skills/sandbox-sdk/references/api-quick-ref.md +113 -0
- package/skills/sandbox-sdk/references/examples.md +52 -0
- package/skills/shadcn-ui/SKILL.md +22 -57
- package/skills/skill-builder/SKILL.md +23 -424
- package/skills/skill-builder/references/tutorial.md +457 -0
- package/skills/sqlite/SKILL.md +16 -5
- package/skills/table.md +59 -0
- package/skills/tailwind-css/SKILL.md +11 -60
- package/skills/tailwind-css/references/component-patterns.md +52 -0
- package/skills/trpc/SKILL.md +56 -0
- package/skills/typescript/SKILL.md +30 -433
- package/skills/typescript/references/tutorial.md +453 -0
- package/skills/vercel-ai-sdk/SKILL.md +48 -0
- package/skills/vitest-standard/SKILL.md +5 -11
- package/skills/vitest-standard/references/assertions.md +11 -0
- package/skills/web-perf/SKILL.md +207 -0
- package/skills/workers-best-practices/SKILL.md +120 -0
- package/skills/workers-best-practices/references/anti-patterns.md +18 -0
- package/skills/workers-best-practices/references/review.md +174 -0
- package/skills/workers-best-practices/references/rules.md +485 -0
- package/skills/wrangler/SKILL.md +43 -0
- package/skills/wrangler/references/cli-commands.md +861 -0
- package/skills/zod/SKILL.md +48 -0
- package/dist/tools-P4VGG4FH.js +0 -1
- package/skills/react-best-practices/AGENTS.md +0 -2883
- package/skills/react-best-practices/SKILL.md +0 -138
- /package/skills/{react-best-practices → next-best-practices}/README.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/metadata.json +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/_sections.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/_template.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/advanced-event-handler-refs.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/advanced-init-once.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/advanced-use-latest.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/async-api-routes.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/async-defer-await.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/async-dependencies.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/async-parallel.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/async-suspense-boundaries.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/bundle-barrel-imports.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/bundle-conditional.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/bundle-defer-third-party.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/bundle-dynamic-imports.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/bundle-preload.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/client-event-listeners.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/client-localstorage-schema.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/client-passive-event-listeners.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/client-swr-dedup.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-batch-dom-css.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-cache-function-results.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-cache-property-access.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-cache-storage.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-combine-iterations.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-early-exit.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-hoist-regexp.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-index-maps.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-length-check-first.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-min-max-loop.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-set-map-lookups.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/js-tosorted-immutable.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-activity.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-conditional-render.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-content-visibility.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-hoist-jsx.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-hydration-suppress-warning.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-svg-precision.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rendering-usetransition-loading.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-defer-reads.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-dependencies.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-derived-state-no-effect.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-derived-state.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-functional-setstate.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-lazy-state-init.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-memo-with-default-value.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-memo.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-move-effect-to-event.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-simple-expression-in-memo.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-transitions.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/rerender-use-ref-transient-values.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/server-after-nonblocking.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/server-auth-actions.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/server-cache-lru.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/server-cache-react.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/server-dedup-props.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/server-parallel-fetching.md +0 -0
- /package/skills/{react-best-practices → next-best-practices}/rules/server-serialization.md +0 -0
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import * as path4 from 'path';
|
|
5
|
-
import { dirname } from 'path';
|
|
6
|
-
import { performance } from 'perf_hooks';
|
|
1
|
+
import { getTools, callTool, MetricsAccumulator, sendProgress } from './chunk-UHSO65A4.js';
|
|
2
|
+
import { AuditLogger, createAuditInterceptor, getResources, getPrompts, generateInstructions, VERSION, auditOperation, getPrompt, readResource } from './chunk-JFMITANR.js';
|
|
3
|
+
import { logger, getGitHubIntegration, parseToolFilter, getFilterSummary, getToolFilterFromEnv, getRequiredScope, getEnabledGroups, ConfigurationError, SUPPORTED_SCOPES, enforceAccessBoundary, runWithAuthContext, isValidScope, requestContextStorage, ConnectionError, ResourceNotFoundError, QueryError, assertNoPathTraversal, ValidationError, parseScopes, BASE_SCOPES, validateDateFormatPattern, MemoryJournalMcpError, sanitizeSearchQuery, DEFAULT_BRIEFING_CONFIG } from './chunk-6OHRCNYW.js';
|
|
7
4
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
6
|
import DatabaseAdapter from 'better-sqlite3';
|
|
10
7
|
import * as fs2 from 'fs';
|
|
8
|
+
import * as path2 from 'path';
|
|
11
9
|
import { execFile } from 'child_process';
|
|
12
10
|
import { createHash, timingSafeEqual, randomUUID } from 'crypto';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
11
|
import express from 'express';
|
|
15
12
|
import 'https';
|
|
16
13
|
import * as jose from 'jose';
|
|
@@ -20,473 +17,6 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
|
20
17
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
21
18
|
import { z } from 'zod';
|
|
22
19
|
|
|
23
|
-
var require2 = createRequire(import.meta.url);
|
|
24
|
-
var pkg = require2("../package.json");
|
|
25
|
-
var VERSION = pkg.version;
|
|
26
|
-
|
|
27
|
-
// src/audit/types.ts
|
|
28
|
-
var DEFAULT_AUDIT_LOG_MAX_SIZE_BYTES = 10 * 1024 * 1024;
|
|
29
|
-
var BUFFER_HIGH_WATER = 50;
|
|
30
|
-
var FLUSH_INTERVAL_MS = 100;
|
|
31
|
-
var DEFAULT_RECENT_COUNT = 50;
|
|
32
|
-
var STDERR_SENTINEL = "stderr";
|
|
33
|
-
var TAIL_READ_BYTES = 65536;
|
|
34
|
-
var MAX_ARCHIVES = 5;
|
|
35
|
-
var AuditLogger = class {
|
|
36
|
-
config;
|
|
37
|
-
buffer = [];
|
|
38
|
-
flushTimer = null;
|
|
39
|
-
flushQueue = Promise.resolve();
|
|
40
|
-
closed = false;
|
|
41
|
-
dirEnsured = false;
|
|
42
|
-
stderrMode;
|
|
43
|
-
_droppedCount = 0;
|
|
44
|
-
constructor(config) {
|
|
45
|
-
this.config = config;
|
|
46
|
-
this.stderrMode = config.logPath.toLowerCase() === STDERR_SENTINEL;
|
|
47
|
-
if (config.enabled) {
|
|
48
|
-
this.flushTimer = setInterval(() => {
|
|
49
|
-
void this.flush();
|
|
50
|
-
}, FLUSH_INTERVAL_MS);
|
|
51
|
-
this.flushTimer.unref();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Total number of events dropped due to memory limits or persistent I/O failure.
|
|
56
|
-
*/
|
|
57
|
-
get droppedCount() {
|
|
58
|
-
return this._droppedCount;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Append an audit entry to the buffer.
|
|
62
|
-
* Non-blocking — the entry is serialised and queued; the
|
|
63
|
-
* actual file write happens on the next flush cycle.
|
|
64
|
-
*
|
|
65
|
-
* NOTE: This is a lossy operational telemetry mechanism, not a guaranteed immutable ledger.
|
|
66
|
-
* Under extreme backpressure or persistent I/O failure, oldest entries will be dropped
|
|
67
|
-
* to preserve memory and system stability.
|
|
68
|
-
*/
|
|
69
|
-
log(entry) {
|
|
70
|
-
if (this.closed || !this.config.enabled) return;
|
|
71
|
-
this.buffer.push(JSON.stringify(entry));
|
|
72
|
-
if (this.buffer.length > 5e3) {
|
|
73
|
-
this.buffer.shift();
|
|
74
|
-
if (this._droppedCount === 0) {
|
|
75
|
-
logger.warning(
|
|
76
|
-
"Telemetry buffer overflow. Dropping oldest entries. Note: This log is a lossy operational telemetry mechanism.",
|
|
77
|
-
{ module: "Audit" }
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
this._droppedCount++;
|
|
81
|
-
}
|
|
82
|
-
if (this.buffer.length >= BUFFER_HIGH_WATER) {
|
|
83
|
-
void this.flush();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Log a denied access attempt.
|
|
88
|
-
*/
|
|
89
|
-
logDenial(toolName, reason, context) {
|
|
90
|
-
this.log({
|
|
91
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92
|
-
requestId: context?.requestId ?? `denied-${Date.now().toString()}`,
|
|
93
|
-
sessionId: context?.sessionId ?? void 0,
|
|
94
|
-
tool: toolName,
|
|
95
|
-
category: context?.category ?? "read",
|
|
96
|
-
scope: context?.scope ?? "",
|
|
97
|
-
user: context?.user ?? null,
|
|
98
|
-
scopes: context?.scopes ?? [],
|
|
99
|
-
durationMs: 0,
|
|
100
|
-
success: false,
|
|
101
|
-
error: `Access Denied: ${reason}`
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Flush the buffer to disk.
|
|
106
|
-
* Safe to call concurrently — serialises via `this.flushQueue` Promise chain.
|
|
107
|
-
*/
|
|
108
|
-
async flush() {
|
|
109
|
-
if (this.buffer.length === 0) return;
|
|
110
|
-
this.flushQueue = this.flushQueue.then(async () => {
|
|
111
|
-
if (this.buffer.length === 0) return;
|
|
112
|
-
await this.rotateIfNeeded();
|
|
113
|
-
const lines = this.buffer;
|
|
114
|
-
this.buffer = [];
|
|
115
|
-
try {
|
|
116
|
-
if (this.stderrMode) {
|
|
117
|
-
process.stderr.write(lines.join("\n") + "\n");
|
|
118
|
-
} else {
|
|
119
|
-
await this.ensureDirectory();
|
|
120
|
-
await appendFile(this.config.logPath, lines.join("\n") + "\n", "utf-8");
|
|
121
|
-
}
|
|
122
|
-
} catch (err) {
|
|
123
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
124
|
-
logger.error(`Write failed: ${message}`, { module: "Audit" });
|
|
125
|
-
this.buffer.unshift(...lines);
|
|
126
|
-
if (this.buffer.length > 5e3) {
|
|
127
|
-
logger.error(
|
|
128
|
-
`Buffer overflow (${String(this.buffer.length)} entries), dropping oldest entries`,
|
|
129
|
-
{ module: "Audit" }
|
|
130
|
-
);
|
|
131
|
-
const toDrop = this.buffer.length - 5e3;
|
|
132
|
-
this.buffer = this.buffer.slice(-5e3);
|
|
133
|
-
this._droppedCount += toDrop;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}).catch(() => {
|
|
137
|
-
});
|
|
138
|
-
await this.flushQueue;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Gracefully close the logger — flush remaining entries and stop the timer.
|
|
142
|
-
*/
|
|
143
|
-
async close() {
|
|
144
|
-
this.closed = true;
|
|
145
|
-
if (this.flushTimer) {
|
|
146
|
-
clearInterval(this.flushTimer);
|
|
147
|
-
this.flushTimer = null;
|
|
148
|
-
}
|
|
149
|
-
await this.flush();
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Read the most recent audit entries from the log file.
|
|
153
|
-
* Uses a streaming tail-read: only the last TAIL_READ_BYTES (64 KB) are
|
|
154
|
-
* read from disk, preventing O(n) memory spikes for large audit logs.
|
|
155
|
-
* Used by the `memory://audit` resource.
|
|
156
|
-
*
|
|
157
|
-
* @param count Maximum number of entries to return (default 50)
|
|
158
|
-
*/
|
|
159
|
-
async recent(count = DEFAULT_RECENT_COUNT) {
|
|
160
|
-
if (this.stderrMode) return [];
|
|
161
|
-
await this.flush();
|
|
162
|
-
try {
|
|
163
|
-
let fh;
|
|
164
|
-
try {
|
|
165
|
-
fh = await open(this.config.logPath, "r");
|
|
166
|
-
} catch {
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
try {
|
|
170
|
-
const info = await fh.stat();
|
|
171
|
-
if (info.isDirectory()) return [];
|
|
172
|
-
const fileSize = info.size;
|
|
173
|
-
if (fileSize === 0) return [];
|
|
174
|
-
const readSize = Math.min(fileSize, TAIL_READ_BYTES);
|
|
175
|
-
const startOffset = fileSize - readSize;
|
|
176
|
-
const buf = Buffer.alloc(readSize);
|
|
177
|
-
await fh.read(buf, 0, readSize, startOffset);
|
|
178
|
-
const chunk = buf.toString("utf-8");
|
|
179
|
-
const rawLines = chunk.split("\n").filter(Boolean);
|
|
180
|
-
const lines = startOffset > 0 ? rawLines.slice(1) : rawLines;
|
|
181
|
-
const tail = lines.slice(-count);
|
|
182
|
-
return tail.reduce((acc, line) => {
|
|
183
|
-
try {
|
|
184
|
-
acc.push(JSON.parse(line));
|
|
185
|
-
} catch {
|
|
186
|
-
}
|
|
187
|
-
return acc;
|
|
188
|
-
}, []);
|
|
189
|
-
} finally {
|
|
190
|
-
await fh.close();
|
|
191
|
-
}
|
|
192
|
-
} catch (err) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
`Failed to read telemetry log: ${err instanceof Error ? err.message : String(err)}`,
|
|
195
|
-
{ cause: err }
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// =========================================================================
|
|
200
|
-
// Private helpers
|
|
201
|
-
// =========================================================================
|
|
202
|
-
/**
|
|
203
|
-
* Ensure the parent directory of the log file exists.
|
|
204
|
-
*/
|
|
205
|
-
async ensureDirectory() {
|
|
206
|
-
if (this.dirEnsured) return;
|
|
207
|
-
try {
|
|
208
|
-
await mkdir(dirname(this.config.logPath), { recursive: true });
|
|
209
|
-
this.dirEnsured = true;
|
|
210
|
-
} catch {
|
|
211
|
-
this.dirEnsured = true;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Rotate the log file if it exceeds the configured size limit.
|
|
216
|
-
* Keeps up to 5 rotated files (`.1` through `.5`); older data is discarded.
|
|
217
|
-
* Rotation failure is non-fatal — audit must not block tool execution.
|
|
218
|
-
*/
|
|
219
|
-
async rotateIfNeeded() {
|
|
220
|
-
if (this.stderrMode || !this.config.maxSizeBytes) return;
|
|
221
|
-
try {
|
|
222
|
-
const info = await stat(this.config.logPath).catch(() => null);
|
|
223
|
-
if (!info || info.size < this.config.maxSizeBytes) return;
|
|
224
|
-
for (let i = MAX_ARCHIVES - 1; i >= 1; i--) {
|
|
225
|
-
const oldFile = `${this.config.logPath}.${String(i)}`;
|
|
226
|
-
const newFile = `${this.config.logPath}.${String(i + 1)}`;
|
|
227
|
-
await rename(oldFile, newFile).catch(() => null);
|
|
228
|
-
}
|
|
229
|
-
const rotatedPath = `${this.config.logPath}.1`;
|
|
230
|
-
await rename(this.config.logPath, rotatedPath);
|
|
231
|
-
} catch (err) {
|
|
232
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
233
|
-
logger.error(`Rotate failed: ${message}`, { module: "Audit" });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
var ALWAYS_AUDITED_SCOPES = /* @__PURE__ */ new Set(["write", "admin", "team", "audit"]);
|
|
238
|
-
function scopeToCategory(scope) {
|
|
239
|
-
if (scope === "admin") return "admin";
|
|
240
|
-
if (scope === "team") return "team";
|
|
241
|
-
if (scope === "audit") return "audit";
|
|
242
|
-
if (scope === "read") return "read";
|
|
243
|
-
return "write";
|
|
244
|
-
}
|
|
245
|
-
function generateRequestId() {
|
|
246
|
-
const ts = Date.now().toString(36);
|
|
247
|
-
const rand = Math.random().toString(36).slice(2, 8);
|
|
248
|
-
return `aud-${ts}-${rand}`;
|
|
249
|
-
}
|
|
250
|
-
function createAuditInterceptor(auditLogger) {
|
|
251
|
-
const auditReads = auditLogger.config.auditReads;
|
|
252
|
-
return {
|
|
253
|
-
async around(toolName, args, fn) {
|
|
254
|
-
const scope = getRequiredScope(toolName);
|
|
255
|
-
if (!ALWAYS_AUDITED_SCOPES.has(scope) && !auditReads) {
|
|
256
|
-
return fn();
|
|
257
|
-
}
|
|
258
|
-
const isReadScope = scope === "read";
|
|
259
|
-
const authCtx = getAuthContext();
|
|
260
|
-
const reqCtx = getRequestContext();
|
|
261
|
-
const user = authCtx?.claims?.sub ?? null;
|
|
262
|
-
const scopes = authCtx?.claims?.scopes ?? [];
|
|
263
|
-
const sessionId = reqCtx?.sessionId;
|
|
264
|
-
const requestId = generateRequestId();
|
|
265
|
-
const start = performance.now();
|
|
266
|
-
let success = true;
|
|
267
|
-
let error;
|
|
268
|
-
let tokenEstimate;
|
|
269
|
-
try {
|
|
270
|
-
const result = await fn();
|
|
271
|
-
if (typeof result === "object" && result !== null) {
|
|
272
|
-
try {
|
|
273
|
-
const json = JSON.stringify({
|
|
274
|
-
...result,
|
|
275
|
-
_meta: { tokenEstimate: 0 }
|
|
276
|
-
});
|
|
277
|
-
tokenEstimate = Math.ceil(Buffer.byteLength(json, "utf8") / 4);
|
|
278
|
-
} catch {
|
|
279
|
-
}
|
|
280
|
-
} else if (typeof result === "string") {
|
|
281
|
-
tokenEstimate = Math.ceil(Buffer.byteLength(result, "utf8") / 4);
|
|
282
|
-
}
|
|
283
|
-
return result;
|
|
284
|
-
} catch (err) {
|
|
285
|
-
success = false;
|
|
286
|
-
error = err instanceof Error ? err.message : String(err);
|
|
287
|
-
const errorResult = {
|
|
288
|
-
success: false,
|
|
289
|
-
error,
|
|
290
|
-
code: "INTERNAL_ERROR",
|
|
291
|
-
category: "internal",
|
|
292
|
-
recoverable: false
|
|
293
|
-
};
|
|
294
|
-
const enriched = JSON.stringify({
|
|
295
|
-
...errorResult,
|
|
296
|
-
_meta: { tokenEstimate: 0 }
|
|
297
|
-
});
|
|
298
|
-
tokenEstimate = Math.ceil(Buffer.byteLength(enriched, "utf8") / 4);
|
|
299
|
-
throw err;
|
|
300
|
-
} finally {
|
|
301
|
-
const durationMs = Math.round(performance.now() - start);
|
|
302
|
-
if (isReadScope) {
|
|
303
|
-
auditLogger.log({
|
|
304
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
305
|
-
requestId,
|
|
306
|
-
tool: toolName,
|
|
307
|
-
category: "read",
|
|
308
|
-
scope,
|
|
309
|
-
user,
|
|
310
|
-
scopes,
|
|
311
|
-
sessionId,
|
|
312
|
-
durationMs,
|
|
313
|
-
success,
|
|
314
|
-
error,
|
|
315
|
-
tokenEstimate
|
|
316
|
-
});
|
|
317
|
-
} else {
|
|
318
|
-
auditLogger.log({
|
|
319
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
320
|
-
requestId,
|
|
321
|
-
tool: toolName,
|
|
322
|
-
category: scopeToCategory(scope),
|
|
323
|
-
scope,
|
|
324
|
-
user,
|
|
325
|
-
scopes,
|
|
326
|
-
sessionId,
|
|
327
|
-
durationMs,
|
|
328
|
-
success,
|
|
329
|
-
error,
|
|
330
|
-
args: auditLogger.config.redact ? void 0 : args,
|
|
331
|
-
tokenEstimate
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
async function auditOperation(auditLogger, operationType, name, fn) {
|
|
339
|
-
if (!auditLogger?.config.auditReads) {
|
|
340
|
-
return Promise.resolve(fn());
|
|
341
|
-
}
|
|
342
|
-
const authCtx = getAuthContext();
|
|
343
|
-
const reqCtx = getRequestContext();
|
|
344
|
-
const user = authCtx?.claims?.sub ?? null;
|
|
345
|
-
const scopes = authCtx?.claims?.scopes ?? [];
|
|
346
|
-
const sessionId = reqCtx?.sessionId;
|
|
347
|
-
const requestId = generateRequestId();
|
|
348
|
-
const start = performance.now();
|
|
349
|
-
let success = true;
|
|
350
|
-
let error;
|
|
351
|
-
let tokenEstimate;
|
|
352
|
-
try {
|
|
353
|
-
const result = await fn();
|
|
354
|
-
if (typeof result === "object" && result !== null) {
|
|
355
|
-
try {
|
|
356
|
-
const json = JSON.stringify(result);
|
|
357
|
-
tokenEstimate = Math.ceil(Buffer.byteLength(json, "utf8") / 4);
|
|
358
|
-
} catch {
|
|
359
|
-
}
|
|
360
|
-
} else if (typeof result === "string") {
|
|
361
|
-
tokenEstimate = Math.ceil(Buffer.byteLength(result, "utf8") / 4);
|
|
362
|
-
}
|
|
363
|
-
return result;
|
|
364
|
-
} catch (err) {
|
|
365
|
-
success = false;
|
|
366
|
-
error = err instanceof Error ? err.message : String(err);
|
|
367
|
-
throw err;
|
|
368
|
-
} finally {
|
|
369
|
-
const durationMs = Math.round(performance.now() - start);
|
|
370
|
-
auditLogger.log({
|
|
371
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
372
|
-
requestId,
|
|
373
|
-
sessionId,
|
|
374
|
-
tool: `${operationType}:${name}`,
|
|
375
|
-
category: "read",
|
|
376
|
-
scope: "read",
|
|
377
|
-
user,
|
|
378
|
-
scopes,
|
|
379
|
-
durationMs,
|
|
380
|
-
success,
|
|
381
|
-
error,
|
|
382
|
-
tokenEstimate
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// src/utils/resource-annotations.ts
|
|
388
|
-
var HIGH_PRIORITY = {
|
|
389
|
-
priority: 0.9,
|
|
390
|
-
audience: ["user", "assistant"]
|
|
391
|
-
};
|
|
392
|
-
var MEDIUM_PRIORITY = {
|
|
393
|
-
priority: 0.6,
|
|
394
|
-
audience: ["user", "assistant"]
|
|
395
|
-
};
|
|
396
|
-
var LOW_PRIORITY = {
|
|
397
|
-
priority: 0.4,
|
|
398
|
-
audience: ["user", "assistant"]
|
|
399
|
-
};
|
|
400
|
-
var ASSISTANT_FOCUSED = {
|
|
401
|
-
priority: 0.5,
|
|
402
|
-
audience: ["assistant"]
|
|
403
|
-
};
|
|
404
|
-
function withPriority(priority, base = MEDIUM_PRIORITY) {
|
|
405
|
-
return { ...base, priority };
|
|
406
|
-
}
|
|
407
|
-
function withSessionInit(base = HIGH_PRIORITY) {
|
|
408
|
-
return { ...base, sessionInit: true };
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// src/audit/audit-resource.ts
|
|
412
|
-
function getAuditResourceDef(getLogger) {
|
|
413
|
-
return {
|
|
414
|
-
uri: "memory://audit",
|
|
415
|
-
name: "Audit Log",
|
|
416
|
-
title: "Operational Telemetry Log (last 50 entries)",
|
|
417
|
-
description: "Last 50 write/admin tool call telemetry entries from the JSONL log. Each entry includes tool name, scope, duration, token estimates, and error status. Includes a session summary with total token consumption and error count.",
|
|
418
|
-
mimeType: "text/plain",
|
|
419
|
-
annotations: {
|
|
420
|
-
...ASSISTANT_FOCUSED
|
|
421
|
-
},
|
|
422
|
-
capabilities: {
|
|
423
|
-
requiresAdminScope: true
|
|
424
|
-
},
|
|
425
|
-
handler: async (_uri, _context) => {
|
|
426
|
-
const lastModified = (/* @__PURE__ */ new Date()).toISOString();
|
|
427
|
-
const auditLogger = getLogger();
|
|
428
|
-
if (!auditLogger) {
|
|
429
|
-
return {
|
|
430
|
-
data: "audit: not configured\nhint: Set AUDIT_LOG_PATH env var or --audit-log CLI flag to enable audit logging.",
|
|
431
|
-
annotations: { lastModified }
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
const entries = await auditLogger.recent(50);
|
|
435
|
-
if (entries.length === 0) {
|
|
436
|
-
return {
|
|
437
|
-
data: `audit_log: ${auditLogger.config.logPath}
|
|
438
|
-
entries: 0
|
|
439
|
-
note: No write/admin operations have been audited yet.`,
|
|
440
|
-
annotations: { lastModified }
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
let totalTokens = 0;
|
|
444
|
-
let errorCount = 0;
|
|
445
|
-
let totalDuration = 0;
|
|
446
|
-
for (const e of entries) {
|
|
447
|
-
totalTokens += e.tokenEstimate ?? 0;
|
|
448
|
-
totalDuration += e.durationMs;
|
|
449
|
-
if (!e.success) errorCount++;
|
|
450
|
-
}
|
|
451
|
-
const formattedEntries = entries.map((e) => {
|
|
452
|
-
const parts = [
|
|
453
|
-
`- timestamp: ${e.timestamp}`,
|
|
454
|
-
` tool: ${e.tool}`,
|
|
455
|
-
` scope: ${e.scope}`,
|
|
456
|
-
` category: ${e.category}`,
|
|
457
|
-
` duration_ms: ${String(e.durationMs)}`,
|
|
458
|
-
` success: ${String(e.success)}`
|
|
459
|
-
];
|
|
460
|
-
if (e.error) {
|
|
461
|
-
parts.push(` error: ${e.error}`);
|
|
462
|
-
}
|
|
463
|
-
if (e.tokenEstimate !== void 0) {
|
|
464
|
-
parts.push(` token_estimate: ${String(e.tokenEstimate)}`);
|
|
465
|
-
}
|
|
466
|
-
if (e.args !== void 0) {
|
|
467
|
-
parts.push(` args: ${JSON.stringify(e.args)}`);
|
|
468
|
-
}
|
|
469
|
-
return parts.join("\n");
|
|
470
|
-
}).join("\n");
|
|
471
|
-
const text = `audit_log: ${auditLogger.config.logPath}
|
|
472
|
-
entries_shown: ${String(entries.length)}
|
|
473
|
-
as_of: ${lastModified}
|
|
474
|
-
session_summary:
|
|
475
|
-
total_tokens: ${String(totalTokens)}
|
|
476
|
-
total_duration_ms: ${String(totalDuration)}
|
|
477
|
-
error_count: ${String(errorCount)}
|
|
478
|
-
dropped_count: ${String(auditLogger.droppedCount)}
|
|
479
|
-
redact_mode: ${String(auditLogger.config.redact)}
|
|
480
|
-
|
|
481
|
-
` + formattedEntries;
|
|
482
|
-
return {
|
|
483
|
-
data: text,
|
|
484
|
-
annotations: { lastModified }
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
|
|
490
20
|
// src/database/core/schema.ts
|
|
491
21
|
var SCHEMA_SQL = `
|
|
492
22
|
-- Main journal entries table
|
|
@@ -641,7 +171,7 @@ var NativeConnectionManager = class {
|
|
|
641
171
|
}
|
|
642
172
|
async initialize() {
|
|
643
173
|
if (this.initialized) return;
|
|
644
|
-
const dir =
|
|
174
|
+
const dir = path2.dirname(this.dbPath);
|
|
645
175
|
if (dir && !fs2.existsSync(dir)) {
|
|
646
176
|
await fs2.promises.mkdir(dir, { recursive: true });
|
|
647
177
|
}
|
|
@@ -850,7 +380,7 @@ var NativeConnectionManager = class {
|
|
|
850
380
|
return this.dbPath;
|
|
851
381
|
}
|
|
852
382
|
getBackupsDir() {
|
|
853
|
-
return
|
|
383
|
+
return path2.join(path2.dirname(this.dbPath), "backups");
|
|
854
384
|
}
|
|
855
385
|
closeDbBeforeRestore() {
|
|
856
386
|
this.close();
|
|
@@ -967,7 +497,6 @@ var TagsManager = class {
|
|
|
967
497
|
const existingRows = db.prepare("SELECT entry_id FROM entry_tags WHERE tag_id = ?").all(targetTagId);
|
|
968
498
|
const existingEntryIds = new Set(existingRows.map((r) => r.entry_id));
|
|
969
499
|
const newEntryIds = entryIds.filter((id) => !existingEntryIds.has(id));
|
|
970
|
-
const entriesUpdated = newEntryIds.length;
|
|
971
500
|
if (newEntryIds.length > 0) {
|
|
972
501
|
const placeholders = newEntryIds.map(() => "(?, ?)").join(", ");
|
|
973
502
|
const params = newEntryIds.flatMap((entryId) => [entryId, targetTagId]);
|
|
@@ -975,15 +504,15 @@ var TagsManager = class {
|
|
|
975
504
|
`INSERT OR IGNORE INTO entry_tags (entry_id, tag_id) VALUES ${placeholders}`
|
|
976
505
|
).run(...params);
|
|
977
506
|
}
|
|
978
|
-
if (
|
|
507
|
+
if (newEntryIds.length > 0) {
|
|
979
508
|
db.prepare("UPDATE tags SET usage_count = usage_count + ? WHERE id = ?").run(
|
|
980
|
-
|
|
509
|
+
newEntryIds.length,
|
|
981
510
|
targetTagId
|
|
982
511
|
);
|
|
983
512
|
}
|
|
984
513
|
db.prepare("DELETE FROM entry_tags WHERE tag_id = ?").run(sourceTagId);
|
|
985
514
|
db.prepare("DELETE FROM tags WHERE id = ?").run(sourceTagId);
|
|
986
|
-
return { entriesUpdated, sourceDeleted: true };
|
|
515
|
+
return { entriesUpdated: newEntryIds.length, sourceDeleted: true };
|
|
987
516
|
});
|
|
988
517
|
const result = mergeOp();
|
|
989
518
|
this.ctx.scheduleSave();
|
|
@@ -1100,7 +629,10 @@ function createEntry(context, input) {
|
|
|
1100
629
|
const result = stmt.run(...values);
|
|
1101
630
|
insertId = result.lastInsertRowid;
|
|
1102
631
|
if (input.tags && input.tags.length > 0) {
|
|
1103
|
-
|
|
632
|
+
const formattedTags = input.tags.map(
|
|
633
|
+
(t) => t.toLowerCase().replace(/[^a-z0-9:@]+/g, "-").replace(/^-+|-+$/g, "")
|
|
634
|
+
);
|
|
635
|
+
tagsMgr.linkTagsToEntry(insertId, formattedTags);
|
|
1104
636
|
}
|
|
1105
637
|
});
|
|
1106
638
|
txn();
|
|
@@ -1216,7 +748,10 @@ function updateEntry(context, id, input) {
|
|
|
1216
748
|
}
|
|
1217
749
|
if (input.tags !== void 0) {
|
|
1218
750
|
db.prepare("DELETE FROM entry_tags WHERE entry_id = ?").run(id);
|
|
1219
|
-
|
|
751
|
+
const formattedTags = input.tags.map(
|
|
752
|
+
(t) => t.toLowerCase().replace(/[^a-z0-9:@]+/g, "-").replace(/^-+|-+$/g, "")
|
|
753
|
+
);
|
|
754
|
+
tagsMgr.linkTagsToEntry(id, formattedTags);
|
|
1220
755
|
}
|
|
1221
756
|
return getEntryById(context, id);
|
|
1222
757
|
}
|
|
@@ -1922,7 +1457,7 @@ var BackupManager = class {
|
|
|
1922
1457
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1923
1458
|
const sanitizedName = backupName ? backupName.replace(/[/\\:*?"<>|]/g, "_").slice(0, MAX_BACKUP_NAME_LENGTH) : `backup_${timestamp}`;
|
|
1924
1459
|
const filename = `${sanitizedName}.db`;
|
|
1925
|
-
const backupPath =
|
|
1460
|
+
const backupPath = path2.join(backupsDir, filename);
|
|
1926
1461
|
try {
|
|
1927
1462
|
this.ctx.pragma("wal_checkpoint(TRUNCATE)");
|
|
1928
1463
|
} catch (checkpointErr) {
|
|
@@ -1954,7 +1489,7 @@ var BackupManager = class {
|
|
|
1954
1489
|
const backups = [];
|
|
1955
1490
|
for (const filename of files) {
|
|
1956
1491
|
if (!filename.endsWith(".db")) continue;
|
|
1957
|
-
const filePath =
|
|
1492
|
+
const filePath = path2.join(backupsDir, filename);
|
|
1958
1493
|
try {
|
|
1959
1494
|
const stats = fs2.statSync(filePath);
|
|
1960
1495
|
if (stats.isFile()) {
|
|
@@ -2010,20 +1545,20 @@ var BackupManager = class {
|
|
|
2010
1545
|
async restoreFromFile(filename, runtime) {
|
|
2011
1546
|
assertNoPathTraversal(filename);
|
|
2012
1547
|
const backupsDir = this.ctx.getBackupsDir();
|
|
2013
|
-
const backupPath =
|
|
2014
|
-
if (!backupPath.startsWith(
|
|
1548
|
+
const backupPath = path2.resolve(backupsDir, filename);
|
|
1549
|
+
if (!backupPath.startsWith(path2.resolve(backupsDir))) {
|
|
2015
1550
|
throw new ValidationError(`Path traversal detected during restore resolution.`);
|
|
2016
1551
|
}
|
|
2017
|
-
let
|
|
1552
|
+
let stat;
|
|
2018
1553
|
try {
|
|
2019
|
-
|
|
1554
|
+
stat = await fs2.promises.lstat(backupPath);
|
|
2020
1555
|
} catch (e) {
|
|
2021
1556
|
if (e instanceof Error && "code" in e && e.code === "ENOENT") {
|
|
2022
1557
|
throw new ResourceNotFoundError("Backup", filename);
|
|
2023
1558
|
}
|
|
2024
1559
|
throw e;
|
|
2025
1560
|
}
|
|
2026
|
-
if (
|
|
1561
|
+
if (stat.isSymbolicLink()) {
|
|
2027
1562
|
throw new ValidationError("Symlinks are not allowed for backup restore.");
|
|
2028
1563
|
}
|
|
2029
1564
|
const realBackupPath = await fs2.promises.realpath(backupPath);
|
|
@@ -2049,7 +1584,7 @@ var BackupManager = class {
|
|
|
2049
1584
|
);
|
|
2050
1585
|
}
|
|
2051
1586
|
const lockDir = `${this.ctx.getDbPath()}.lock.d`;
|
|
2052
|
-
const lockFile =
|
|
1587
|
+
const lockFile = path2.join(lockDir, "lock.json");
|
|
2053
1588
|
const nonce = randomUUID();
|
|
2054
1589
|
try {
|
|
2055
1590
|
fs2.mkdirSync(lockDir);
|
|
@@ -2190,7 +1725,10 @@ var BackupManager = class {
|
|
|
2190
1725
|
} catch {
|
|
2191
1726
|
}
|
|
2192
1727
|
if (!rollbackSuccess) {
|
|
2193
|
-
throw new Error(
|
|
1728
|
+
throw new Error(
|
|
1729
|
+
`CRITICAL: Database restore failed AND rollback failed. The database may be in an inconsistent state. Your previous database is preserved at: ${oldDbBackupPath}. Original error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1730
|
+
{ cause: error }
|
|
1731
|
+
);
|
|
2194
1732
|
}
|
|
2195
1733
|
await this.ctx.initialize();
|
|
2196
1734
|
throw error;
|
|
@@ -2574,6 +2112,27 @@ var DatabaseAdapter2 = class {
|
|
|
2574
2112
|
"DELETE FROM vec_embeddings WHERE entry_id NOT IN (SELECT id FROM memory_journal WHERE deleted_at IS NULL)"
|
|
2575
2113
|
).run();
|
|
2576
2114
|
}
|
|
2115
|
+
pruneByImportance(olderThanDays, importanceThreshold) {
|
|
2116
|
+
const db = this.connection.getNativeDb();
|
|
2117
|
+
const cte = buildImportanceCte();
|
|
2118
|
+
const importanceExpr = buildImportanceSqlExpression();
|
|
2119
|
+
const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2120
|
+
const sql = `
|
|
2121
|
+
WITH ${cte}
|
|
2122
|
+
UPDATE memory_journal
|
|
2123
|
+
SET deleted_at = ?
|
|
2124
|
+
WHERE id IN (
|
|
2125
|
+
SELECT e.id
|
|
2126
|
+
FROM memory_journal e
|
|
2127
|
+
LEFT JOIN rel_stats rs ON e.id = rs.entry_id
|
|
2128
|
+
WHERE e.deleted_at IS NULL
|
|
2129
|
+
AND julianday('now') - julianday(e.timestamp) > ${String(olderThanDays)}
|
|
2130
|
+
AND ${importanceExpr} < ${String(importanceThreshold)}
|
|
2131
|
+
)
|
|
2132
|
+
`;
|
|
2133
|
+
const result = db.prepare(sql).run(deletedAt);
|
|
2134
|
+
return result.changes;
|
|
2135
|
+
}
|
|
2577
2136
|
executeInTransaction(cb) {
|
|
2578
2137
|
return this.connection.getNativeDb().transaction(cb)();
|
|
2579
2138
|
}
|
|
@@ -3407,3832 +2966,41 @@ var VectorSearchManager = class {
|
|
|
3407
2966
|
}
|
|
3408
2967
|
};
|
|
3409
2968
|
|
|
3410
|
-
// src/
|
|
3411
|
-
var
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
mimeType: "image/svg+xml",
|
|
3424
|
-
sizes: ["24x24"]
|
|
3425
|
-
};
|
|
3426
|
-
var ICON_BRIEFING = {
|
|
3427
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/%3E%3Cpolyline points="14 2 14 8 20 8"/%3E%3Cline x1="16" y1="13" x2="8" y2="13"/%3E%3Cline x1="16" y1="17" x2="8" y2="17"/%3E%3Cpolyline points="10 9 9 9 8 9"/%3E%3C/svg%3E',
|
|
3428
|
-
mimeType: "image/svg+xml",
|
|
3429
|
-
sizes: ["24x24"]
|
|
3430
|
-
};
|
|
3431
|
-
var ICON_GRAPH = {
|
|
3432
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Ccircle cx="18" cy="5" r="3"/%3E%3Ccircle cx="6" cy="12" r="3"/%3E%3Ccircle cx="18" cy="19" r="3"/%3E%3Cline x1="8.59" y1="13.51" x2="15.42" y2="17.49"/%3E%3Cline x1="15.41" y1="6.51" x2="8.59" y2="10.49"/%3E%3C/svg%3E',
|
|
3433
|
-
mimeType: "image/svg+xml",
|
|
3434
|
-
sizes: ["24x24"]
|
|
3435
|
-
};
|
|
3436
|
-
var ICON_CLOCK = {
|
|
3437
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Ccircle cx="12" cy="12" r="10"/%3E%3Cpolyline points="12 6 12 12 16 14"/%3E%3C/svg%3E',
|
|
3438
|
-
mimeType: "image/svg+xml",
|
|
3439
|
-
sizes: ["24x24"]
|
|
3440
|
-
};
|
|
3441
|
-
var ICON_HEALTH = {
|
|
3442
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M22 12h-4l-3 9L9 3l-3 9H2"/%3E%3C/svg%3E',
|
|
3443
|
-
mimeType: "image/svg+xml",
|
|
3444
|
-
sizes: ["24x24"]
|
|
3445
|
-
};
|
|
3446
|
-
var ICON_STAR = {
|
|
3447
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpolygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/%3E%3C/svg%3E',
|
|
3448
|
-
mimeType: "image/svg+xml",
|
|
3449
|
-
sizes: ["24x24"]
|
|
3450
|
-
};
|
|
3451
|
-
var ICON_TAG = {
|
|
3452
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/%3E%3Cline x1="7" y1="7" x2="7.01" y2="7"/%3E%3C/svg%3E',
|
|
3453
|
-
mimeType: "image/svg+xml",
|
|
3454
|
-
sizes: ["24x24"]
|
|
3455
|
-
};
|
|
3456
|
-
var ICON_ISSUE = {
|
|
3457
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Ccircle cx="12" cy="12" r="10"/%3E%3Cline x1="12" y1="8" x2="12" y2="12"/%3E%3Cline x1="12" y1="16" x2="12.01" y2="16"/%3E%3C/svg%3E',
|
|
3458
|
-
mimeType: "image/svg+xml",
|
|
3459
|
-
sizes: ["24x24"]
|
|
3460
|
-
};
|
|
3461
|
-
var ICON_PR = {
|
|
3462
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Ccircle cx="18" cy="18" r="3"/%3E%3Ccircle cx="6" cy="6" r="3"/%3E%3Cpath d="M6 21V9a9 9 0 009 9"/%3E%3C/svg%3E',
|
|
3463
|
-
mimeType: "image/svg+xml",
|
|
3464
|
-
sizes: ["24x24"]
|
|
3465
|
-
};
|
|
3466
|
-
var ICON_MILESTONE = {
|
|
3467
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/%3E%3Cline x1="4" y1="22" x2="4" y2="15"/%3E%3C/svg%3E',
|
|
3468
|
-
mimeType: "image/svg+xml",
|
|
3469
|
-
sizes: ["24x24"]
|
|
3470
|
-
};
|
|
3471
|
-
var ICON_PROMPT = {
|
|
3472
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/%3E%3C/svg%3E',
|
|
3473
|
-
mimeType: "image/svg+xml",
|
|
3474
|
-
sizes: ["24x24"]
|
|
3475
|
-
};
|
|
3476
|
-
var ICON_FLAG = {
|
|
3477
|
-
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/%3E%3Cline x1="12" y1="9" x2="12" y2="13"/%3E%3Cline x1="12" y1="17" x2="12.01" y2="17"/%3E%3C/svg%3E',
|
|
3478
|
-
mimeType: "image/svg+xml",
|
|
3479
|
-
sizes: ["24x24"]
|
|
3480
|
-
};
|
|
3481
|
-
|
|
3482
|
-
// src/handlers/resources/core/briefing/github-section.ts
|
|
3483
|
-
async function buildGitHubSection(github, config) {
|
|
3484
|
-
if (!github) return null;
|
|
3485
|
-
try {
|
|
3486
|
-
const resolved = await resolveGitHubRepo(github, config);
|
|
3487
|
-
if (isResourceError(resolved)) return null;
|
|
3488
|
-
const { owner, repo } = resolved;
|
|
3489
|
-
const batch1 = await Promise.allSettled([
|
|
3490
|
-
fetchCiStatus(github, owner, repo, config),
|
|
3491
|
-
fetchIssuesAndPrs(github, owner, repo, config),
|
|
3492
|
-
fetchMilestones(github, owner, repo, config.milestoneCount ?? 3)
|
|
3493
|
-
]);
|
|
3494
|
-
const batch2 = await Promise.allSettled([
|
|
3495
|
-
fetchInsights(github, owner, repo),
|
|
3496
|
-
config.copilotReviews ? fetchCopilotReviews(github, owner, repo) : Promise.resolve(void 0)
|
|
3497
|
-
]);
|
|
3498
|
-
const [ciStatusResult, issuesAndPrsResult, milestonesResult] = batch1;
|
|
3499
|
-
const [insightsResult, copilotReviewsResult] = batch2;
|
|
3500
|
-
const degradedReasons = [];
|
|
3501
|
-
const ciStatus = ciStatusResult.status === "fulfilled" ? ciStatusResult.value : { status: "unknown", workflowSummary: void 0, degraded: true };
|
|
3502
|
-
if (ciStatusResult.status === "rejected")
|
|
3503
|
-
degradedReasons.push(`CI Status fetch failed: ${String(ciStatusResult.reason)}`);
|
|
3504
|
-
else if (ciStatus.degraded) degradedReasons.push("CI Status partially degraded");
|
|
3505
|
-
const issuesAndPrs = issuesAndPrsResult.status === "fulfilled" ? issuesAndPrsResult.value : {
|
|
3506
|
-
openIssues: 0,
|
|
3507
|
-
openIssueList: void 0,
|
|
3508
|
-
openPRs: 0,
|
|
3509
|
-
openPrList: void 0,
|
|
3510
|
-
degraded: true
|
|
3511
|
-
};
|
|
3512
|
-
if (issuesAndPrsResult.status === "rejected")
|
|
3513
|
-
degradedReasons.push(`Issues/PRs fetch failed: ${String(issuesAndPrsResult.reason)}`);
|
|
3514
|
-
else if (issuesAndPrs.degraded) degradedReasons.push("Issues/PRs partially degraded");
|
|
3515
|
-
const milestonesData = milestonesResult.status === "fulfilled" ? milestonesResult.value : { items: [], degraded: true };
|
|
3516
|
-
if (milestonesResult.status === "rejected")
|
|
3517
|
-
degradedReasons.push(`Milestones fetch failed: ${String(milestonesResult.reason)}`);
|
|
3518
|
-
else if (milestonesData.degraded) degradedReasons.push("Milestones partially degraded");
|
|
3519
|
-
const insightsData = insightsResult.status === "fulfilled" ? insightsResult.value : { degraded: true };
|
|
3520
|
-
if (insightsResult.status === "rejected")
|
|
3521
|
-
degradedReasons.push(`Insights fetch failed: ${String(insightsResult.reason)}`);
|
|
3522
|
-
else if (insightsData.degraded) degradedReasons.push("Insights partially degraded");
|
|
3523
|
-
const copilotReviewsData = copilotReviewsResult.status === "fulfilled" ? copilotReviewsResult.value ?? {} : { degraded: true };
|
|
3524
|
-
if (copilotReviewsResult.status === "rejected")
|
|
3525
|
-
degradedReasons.push(
|
|
3526
|
-
`Copilot Reviews fetch failed: ${String(copilotReviewsResult.reason)}`
|
|
3527
|
-
);
|
|
3528
|
-
else if (copilotReviewsData.degraded)
|
|
3529
|
-
degradedReasons.push("Copilot Reviews partially degraded");
|
|
3530
|
-
const { openIssues, openIssueList, openPRs, openPrList } = issuesAndPrs;
|
|
3531
|
-
const workflowSummary = ciStatus.workflowSummary;
|
|
3532
|
-
const milestones = milestonesData.items ?? [];
|
|
3533
|
-
const insights = insightsData.insights;
|
|
3534
|
-
const copilotReviews = copilotReviewsData.reviews;
|
|
3535
|
-
const degraded = degradedReasons.length > 0;
|
|
3536
|
-
return {
|
|
3537
|
-
repo: `${owner}/${repo}`,
|
|
3538
|
-
branch: resolved.branch,
|
|
3539
|
-
ci: ciStatus.status,
|
|
3540
|
-
openIssues,
|
|
3541
|
-
openPRs,
|
|
3542
|
-
milestones,
|
|
3543
|
-
insights,
|
|
3544
|
-
openIssueList,
|
|
3545
|
-
openPrList,
|
|
3546
|
-
...config.prStatusBreakdown && openPrList ? {
|
|
3547
|
-
prStatusSummary: {
|
|
3548
|
-
open: openPrList.filter((p) => p.state === "OPEN").length + (openPRs - (openPrList?.filter((p) => p.state === "OPEN").length ?? 0)),
|
|
3549
|
-
merged: openPrList.filter((p) => p.state === "MERGED").length,
|
|
3550
|
-
closed: openPrList.filter((p) => p.state === "CLOSED").length
|
|
3551
|
-
}
|
|
3552
|
-
} : {},
|
|
3553
|
-
...workflowSummary ? { workflowSummary } : {},
|
|
3554
|
-
...copilotReviews ? { copilotReviews } : {},
|
|
3555
|
-
...degraded ? { degraded: true, degradedReasons } : {}
|
|
3556
|
-
};
|
|
3557
|
-
} catch (error) {
|
|
3558
|
-
logger.debug("Failed to build GitHub briefing section", {
|
|
3559
|
-
module: "BRIEFING",
|
|
3560
|
-
operation: "github-section",
|
|
3561
|
-
error
|
|
3562
|
-
});
|
|
3563
|
-
return null;
|
|
2969
|
+
// src/server/scheduler.ts
|
|
2970
|
+
var Scheduler = class {
|
|
2971
|
+
options;
|
|
2972
|
+
db;
|
|
2973
|
+
vectorManager;
|
|
2974
|
+
timers = [];
|
|
2975
|
+
started = false;
|
|
2976
|
+
runtime;
|
|
2977
|
+
constructor(options, db, vectorManager, runtime) {
|
|
2978
|
+
this.options = options;
|
|
2979
|
+
this.db = db;
|
|
2980
|
+
this.vectorManager = vectorManager ?? null;
|
|
2981
|
+
this.runtime = runtime;
|
|
3564
2982
|
}
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
if (
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
let status;
|
|
3576
|
-
if (!latestRun) {
|
|
3577
|
-
status = "unknown";
|
|
3578
|
-
} else if (latestRun.status !== "completed") {
|
|
3579
|
-
status = "pending";
|
|
3580
|
-
} else {
|
|
3581
|
-
switch (latestRun.conclusion) {
|
|
3582
|
-
case "success":
|
|
3583
|
-
status = "passing";
|
|
3584
|
-
break;
|
|
3585
|
-
case "failure":
|
|
3586
|
-
status = "failing";
|
|
3587
|
-
break;
|
|
3588
|
-
case "cancelled":
|
|
3589
|
-
status = "cancelled";
|
|
3590
|
-
break;
|
|
3591
|
-
default:
|
|
3592
|
-
status = "unknown";
|
|
3593
|
-
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Start all enabled scheduled jobs.
|
|
2985
|
+
* Each job runs on its own interval and failures are isolated.
|
|
2986
|
+
*/
|
|
2987
|
+
start() {
|
|
2988
|
+
if (this.started) {
|
|
2989
|
+
logger.warning("Scheduler already started, ignoring duplicate start()", {
|
|
2990
|
+
module: "Scheduler"
|
|
2991
|
+
});
|
|
2992
|
+
return;
|
|
3594
2993
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
counts.pending++;
|
|
3601
|
-
} else {
|
|
3602
|
-
switch (run.conclusion) {
|
|
3603
|
-
case "success":
|
|
3604
|
-
counts.passing++;
|
|
3605
|
-
break;
|
|
3606
|
-
case "failure":
|
|
3607
|
-
counts.failing++;
|
|
3608
|
-
break;
|
|
3609
|
-
case "cancelled":
|
|
3610
|
-
counts.cancelled++;
|
|
3611
|
-
break;
|
|
3612
|
-
default:
|
|
3613
|
-
break;
|
|
3614
|
-
}
|
|
3615
|
-
}
|
|
3616
|
-
}
|
|
3617
|
-
workflowSummary = {
|
|
3618
|
-
...counts,
|
|
3619
|
-
...config.workflowCount > 0 ? {
|
|
3620
|
-
runs: runs.slice(0, config.workflowCount).map((r) => ({
|
|
3621
|
-
name: r.name,
|
|
3622
|
-
conclusion: r.status !== "completed" ? "pending" : r.conclusion ?? "unknown"
|
|
3623
|
-
}))
|
|
3624
|
-
} : {}
|
|
3625
|
-
};
|
|
2994
|
+
this.started = true;
|
|
2995
|
+
process.on("SIGTERM", () => this.stop());
|
|
2996
|
+
const { backupIntervalMinutes, vacuumIntervalMinutes, rebuildIndexIntervalMinutes } = this.options;
|
|
2997
|
+
if (backupIntervalMinutes > 0) {
|
|
2998
|
+
this.scheduleJob("backup", backupIntervalMinutes, () => this.runBackup());
|
|
3626
2999
|
}
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
operation: "ci-status",
|
|
3632
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3633
|
-
});
|
|
3634
|
-
return { status: "unknown", degraded: true };
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
async function fetchIssuesAndPrs(github, owner, repo, config) {
|
|
3638
|
-
try {
|
|
3639
|
-
const issueLimit = Math.max(1, config.issueCount || 1);
|
|
3640
|
-
const issues = await github.getIssues(owner, repo, "open", issueLimit);
|
|
3641
|
-
const openIssues = issues.length > 0 ? issues.length : 0;
|
|
3642
|
-
const openIssueList = config.issueCount > 0 && issues.length > 0 ? issues.slice(0, config.issueCount).map((i) => ({
|
|
3643
|
-
number: i.number,
|
|
3644
|
-
title: markUntrustedContentInline(i.title)
|
|
3645
|
-
})) : void 0;
|
|
3646
|
-
const prState = config.prStatusBreakdown ? "all" : "open";
|
|
3647
|
-
const prLimit = config.prStatusBreakdown ? Math.max(20, config.prCount) : Math.max(1, config.prCount || 1);
|
|
3648
|
-
const prs = await github.getPullRequests(owner, repo, prState, prLimit);
|
|
3649
|
-
const openPRs = config.prStatusBreakdown ? prs.filter((p) => p.state === "OPEN").length : prs.length > 0 ? prs.length : 0;
|
|
3650
|
-
const openPrList = config.prCount > 0 && prs.length > 0 ? prs.slice(0, config.prCount).map((p) => ({
|
|
3651
|
-
number: p.number,
|
|
3652
|
-
title: markUntrustedContentInline(p.title),
|
|
3653
|
-
state: p.state
|
|
3654
|
-
})) : void 0;
|
|
3655
|
-
return { openIssues, openPRs, openIssueList, openPrList };
|
|
3656
|
-
} catch (error) {
|
|
3657
|
-
logger.debug("Failed to fetch issues and PRs", {
|
|
3658
|
-
module: "BRIEFING",
|
|
3659
|
-
operation: "issues-prs",
|
|
3660
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3661
|
-
});
|
|
3662
|
-
return { openIssues: 0, openPRs: 0, degraded: true };
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
async function fetchMilestones(github, owner, repo, limit) {
|
|
3666
|
-
if (limit <= 0) return { items: [] };
|
|
3667
|
-
try {
|
|
3668
|
-
const msList = await github.getMilestones(owner, repo, "open", limit);
|
|
3669
|
-
return {
|
|
3670
|
-
items: msList.map((m) => {
|
|
3671
|
-
const pct = milestoneCompletionPct(m.openIssues, m.closedIssues);
|
|
3672
|
-
return {
|
|
3673
|
-
title: m.title,
|
|
3674
|
-
progress: `${String(pct)}%`,
|
|
3675
|
-
dueOn: m.dueOn
|
|
3676
|
-
};
|
|
3677
|
-
})
|
|
3678
|
-
};
|
|
3679
|
-
} catch (error) {
|
|
3680
|
-
logger.debug("Failed to fetch milestones", {
|
|
3681
|
-
module: "BRIEFING",
|
|
3682
|
-
operation: "milestones",
|
|
3683
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3684
|
-
});
|
|
3685
|
-
return { items: [], degraded: true };
|
|
3686
|
-
}
|
|
3687
|
-
}
|
|
3688
|
-
async function fetchInsights(github, owner, repo) {
|
|
3689
|
-
try {
|
|
3690
|
-
const repoStats = await github.getRepoStats(owner, repo);
|
|
3691
|
-
if (!repoStats) return { degraded: true };
|
|
3692
|
-
const result = {
|
|
3693
|
-
stars: repoStats.stars ?? null,
|
|
3694
|
-
forks: repoStats.forks ?? null
|
|
3695
|
-
};
|
|
3696
|
-
try {
|
|
3697
|
-
const trafficData = await github.getTrafficData(owner, repo);
|
|
3698
|
-
if (trafficData) {
|
|
3699
|
-
result.clones14d = trafficData.clones.total;
|
|
3700
|
-
result.views14d = trafficData.views.total;
|
|
3701
|
-
}
|
|
3702
|
-
} catch (error) {
|
|
3703
|
-
logger.debug("Traffic data unavailable (requires push access)", {
|
|
3704
|
-
module: "BRIEFING",
|
|
3705
|
-
operation: "traffic",
|
|
3706
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3707
|
-
});
|
|
3708
|
-
return { insights: result, degraded: true };
|
|
3709
|
-
}
|
|
3710
|
-
return { insights: result };
|
|
3711
|
-
} catch (error) {
|
|
3712
|
-
logger.debug("Failed to fetch repo insights", {
|
|
3713
|
-
module: "BRIEFING",
|
|
3714
|
-
operation: "insights",
|
|
3715
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3716
|
-
});
|
|
3717
|
-
return { degraded: true };
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
async function fetchCopilotReviews(github, owner, repo) {
|
|
3721
|
-
try {
|
|
3722
|
-
const recentPrs = await github.getPullRequests(owner, repo, "all", 10);
|
|
3723
|
-
let reviewed = 0;
|
|
3724
|
-
let approved = 0;
|
|
3725
|
-
let changesRequested = 0;
|
|
3726
|
-
let totalComments = 0;
|
|
3727
|
-
const summaries = [];
|
|
3728
|
-
for (const pr of recentPrs.slice(0, 5)) {
|
|
3729
|
-
summaries.push(await github.getCopilotReviewSummary(owner, repo, pr.number));
|
|
3730
|
-
}
|
|
3731
|
-
for (const summary of summaries) {
|
|
3732
|
-
if (summary.state !== "none") {
|
|
3733
|
-
reviewed++;
|
|
3734
|
-
if (summary.state === "approved") approved++;
|
|
3735
|
-
else if (summary.state === "changes_requested") changesRequested++;
|
|
3736
|
-
totalComments += summary.commentCount;
|
|
3737
|
-
}
|
|
3738
|
-
}
|
|
3739
|
-
return reviewed > 0 ? { reviews: { reviewed, approved, changesRequested, totalComments } } : void 0;
|
|
3740
|
-
} catch (error) {
|
|
3741
|
-
logger.debug("Failed to fetch Copilot reviews", {
|
|
3742
|
-
module: "BRIEFING",
|
|
3743
|
-
operation: "copilot-reviews",
|
|
3744
|
-
error
|
|
3745
|
-
});
|
|
3746
|
-
return { degraded: true };
|
|
3747
|
-
}
|
|
3748
|
-
}
|
|
3749
|
-
var PREVIEW_LENGTH2 = 80;
|
|
3750
|
-
function buildJournalContext(context, config, projectNumber) {
|
|
3751
|
-
const recentEntries = typeof projectNumber === "number" ? context.db.searchEntries("", { limit: config.entryCount, projectNumber }) : context.db.getRecentEntries(config.entryCount);
|
|
3752
|
-
const latestEntries = recentEntries.map((e) => {
|
|
3753
|
-
const content = e.content ?? "";
|
|
3754
|
-
return {
|
|
3755
|
-
id: e.id,
|
|
3756
|
-
timestamp: e.timestamp,
|
|
3757
|
-
type: e.entryType,
|
|
3758
|
-
preview: markUntrustedContentInline(
|
|
3759
|
-
content.slice(0, PREVIEW_LENGTH2) + (content.length > PREVIEW_LENGTH2 ? "..." : "")
|
|
3760
|
-
)
|
|
3761
|
-
};
|
|
3762
|
-
});
|
|
3763
|
-
const summaryEntries = typeof projectNumber === "number" ? context.db.searchEntries("", {
|
|
3764
|
-
limit: config.summaryCount,
|
|
3765
|
-
projectNumber,
|
|
3766
|
-
tags: ["session-summary"]
|
|
3767
|
-
}) : context.db.searchEntries("", {
|
|
3768
|
-
limit: config.summaryCount,
|
|
3769
|
-
tags: ["session-summary"]
|
|
3770
|
-
});
|
|
3771
|
-
const retroEntries = summaryEntries.length === 0 ? typeof projectNumber === "number" ? context.db.searchEntries("", {
|
|
3772
|
-
limit: config.summaryCount,
|
|
3773
|
-
projectNumber,
|
|
3774
|
-
entryType: "retrospective"
|
|
3775
|
-
}) : context.db.searchEntries("", {
|
|
3776
|
-
limit: config.summaryCount,
|
|
3777
|
-
entryType: "retrospective"
|
|
3778
|
-
}) : [];
|
|
3779
|
-
const finalSummaryEntries = summaryEntries.length > 0 ? summaryEntries : retroEntries;
|
|
3780
|
-
let latestSessionSummary;
|
|
3781
|
-
let sessionSummaries;
|
|
3782
|
-
if (finalSummaryEntries.length > 0) {
|
|
3783
|
-
sessionSummaries = finalSummaryEntries.map((entry) => {
|
|
3784
|
-
const c = entry.content ?? "";
|
|
3785
|
-
return {
|
|
3786
|
-
id: entry.id,
|
|
3787
|
-
timestamp: entry.timestamp,
|
|
3788
|
-
type: entry.entryType,
|
|
3789
|
-
preview: markUntrustedContentInline(
|
|
3790
|
-
c.slice(0, PREVIEW_LENGTH2) + (c.length > PREVIEW_LENGTH2 ? "..." : "")
|
|
3791
|
-
)
|
|
3792
|
-
};
|
|
3793
|
-
});
|
|
3794
|
-
latestSessionSummary = sessionSummaries[0];
|
|
3795
|
-
}
|
|
3796
|
-
const totalEntries = context.db.getActiveEntryCount();
|
|
3797
|
-
const lastModified = recentEntries[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3798
|
-
return { totalEntries, latestEntries, latestSessionSummary, sessionSummaries, lastModified };
|
|
3799
|
-
}
|
|
3800
|
-
var TEAM_PREVIEW_LENGTH = 60;
|
|
3801
|
-
function buildTeamContext(context, config, projectNumber) {
|
|
3802
|
-
if (!context.teamDb) return void 0;
|
|
3803
|
-
try {
|
|
3804
|
-
const teamTotalEntries = context.teamDb.getActiveEntryCount();
|
|
3805
|
-
const teamRecent = typeof projectNumber === "number" ? context.teamDb.searchEntries("", { limit: 1, projectNumber }) : context.teamDb.getRecentEntries(1);
|
|
3806
|
-
const teamLatestEntry = teamRecent[0];
|
|
3807
|
-
const teamContent = teamLatestEntry ? teamLatestEntry["content"] ?? "" : "";
|
|
3808
|
-
const teamLatest = teamLatestEntry ? `#${String(teamLatestEntry["id"])}: ${markUntrustedContentInline(teamContent.slice(0, TEAM_PREVIEW_LENGTH) + (teamContent.length > TEAM_PREVIEW_LENGTH ? "..." : ""))}` : null;
|
|
3809
|
-
const teamInfo = {
|
|
3810
|
-
totalEntries: teamTotalEntries,
|
|
3811
|
-
latestPreview: teamLatest
|
|
3812
|
-
};
|
|
3813
|
-
let teamLatestEntries = void 0;
|
|
3814
|
-
if (config.includeTeam) {
|
|
3815
|
-
const teamEntries = typeof projectNumber === "number" ? context.teamDb.searchEntries("", { limit: config.entryCount, projectNumber }) : context.teamDb.getRecentEntries(config.entryCount);
|
|
3816
|
-
teamLatestEntries = teamEntries.map((e) => {
|
|
3817
|
-
const content = e.content ?? "";
|
|
3818
|
-
return {
|
|
3819
|
-
id: e.id,
|
|
3820
|
-
timestamp: e.timestamp,
|
|
3821
|
-
type: e.entryType,
|
|
3822
|
-
preview: markUntrustedContentInline(
|
|
3823
|
-
content.slice(0, TEAM_PREVIEW_LENGTH) + (content.length > TEAM_PREVIEW_LENGTH ? "..." : "")
|
|
3824
|
-
)
|
|
3825
|
-
};
|
|
3826
|
-
});
|
|
3827
|
-
}
|
|
3828
|
-
return { teamInfo, teamLatestEntries };
|
|
3829
|
-
} catch (error) {
|
|
3830
|
-
logger.debug("Failed to build team context", {
|
|
3831
|
-
module: "BRIEFING",
|
|
3832
|
-
operation: "team-context",
|
|
3833
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3834
|
-
});
|
|
3835
|
-
return void 0;
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
var MS_PER_HOUR = 36e5;
|
|
3839
|
-
var MS_PER_DAY = 864e5;
|
|
3840
|
-
function buildRulesFileInfo(rulesFilePath, allowedIoRoots = []) {
|
|
3841
|
-
if (!rulesFilePath) return void 0;
|
|
3842
|
-
try {
|
|
3843
|
-
const expandedRoots = [...allowedIoRoots, path4.dirname(rulesFilePath)];
|
|
3844
|
-
assertSafeFilePath(rulesFilePath, expandedRoots);
|
|
3845
|
-
const stat2 = fs2.statSync(rulesFilePath);
|
|
3846
|
-
const ageMs = Date.now() - stat2.mtimeMs;
|
|
3847
|
-
const ageHours = Math.floor(ageMs / MS_PER_HOUR);
|
|
3848
|
-
const ageDays = Math.floor(ageMs / MS_PER_DAY);
|
|
3849
|
-
const agoStr = ageDays > 0 ? `${String(ageDays)}d ago` : ageHours > 0 ? `${String(ageHours)}h ago` : "just now";
|
|
3850
|
-
return {
|
|
3851
|
-
path: path4.basename(rulesFilePath),
|
|
3852
|
-
name: path4.basename(rulesFilePath),
|
|
3853
|
-
sizeKB: Math.round(stat2.size / 1024),
|
|
3854
|
-
lastModified: agoStr
|
|
3855
|
-
};
|
|
3856
|
-
} catch (error) {
|
|
3857
|
-
logger.debug("Failed to read rules file", {
|
|
3858
|
-
module: "BRIEFING",
|
|
3859
|
-
operation: "rules-file",
|
|
3860
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3861
|
-
});
|
|
3862
|
-
return void 0;
|
|
3863
|
-
}
|
|
3864
|
-
}
|
|
3865
|
-
function buildSkillsDirInfo(skillsDirPath, allowedIoRoots = []) {
|
|
3866
|
-
if (!skillsDirPath) return void 0;
|
|
3867
|
-
try {
|
|
3868
|
-
const expandedRoots = [...allowedIoRoots, skillsDirPath];
|
|
3869
|
-
assertSafeDirectoryPath(skillsDirPath, expandedRoots);
|
|
3870
|
-
const entries = fs2.readdirSync(skillsDirPath, { withFileTypes: true });
|
|
3871
|
-
const skillDirs = entries.filter((e) => e.isDirectory());
|
|
3872
|
-
return {
|
|
3873
|
-
path: path4.basename(skillsDirPath),
|
|
3874
|
-
count: skillDirs.length,
|
|
3875
|
-
names: skillDirs.map((d) => d.name)
|
|
3876
|
-
};
|
|
3877
|
-
} catch (error) {
|
|
3878
|
-
logger.debug("Failed to read skills directory", {
|
|
3879
|
-
module: "BRIEFING",
|
|
3880
|
-
operation: "skills-dir",
|
|
3881
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3882
|
-
});
|
|
3883
|
-
return void 0;
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
function buildFlagsContext(context) {
|
|
3887
|
-
if (!context.teamDb) return void 0;
|
|
3888
|
-
try {
|
|
3889
|
-
const flagEntries = context.teamDb.searchEntries("", {
|
|
3890
|
-
entryType: "flag",
|
|
3891
|
-
limit: 20
|
|
3892
|
-
});
|
|
3893
|
-
const activeFlags = flagEntries.map((entry) => {
|
|
3894
|
-
const ctx = parseFlagContext(entry.autoContext);
|
|
3895
|
-
if (!ctx || ctx.resolved) return null;
|
|
3896
|
-
const content = entry.content ?? "";
|
|
3897
|
-
return {
|
|
3898
|
-
id: entry.id,
|
|
3899
|
-
flag_type: ctx.flag_type,
|
|
3900
|
-
target_user: ctx.target_user ?? null,
|
|
3901
|
-
preview: markUntrustedContentInline(
|
|
3902
|
-
content.slice(0, 80) + (content.length > 80 ? "..." : "")
|
|
3903
|
-
),
|
|
3904
|
-
timestamp: entry.timestamp
|
|
3905
|
-
};
|
|
3906
|
-
}).filter((f) => f !== null);
|
|
3907
|
-
if (activeFlags.length === 0) return void 0;
|
|
3908
|
-
return {
|
|
3909
|
-
count: activeFlags.length,
|
|
3910
|
-
flags: activeFlags
|
|
3911
|
-
};
|
|
3912
|
-
} catch (error) {
|
|
3913
|
-
logger.debug("Failed to build flags context", {
|
|
3914
|
-
module: "BRIEFING",
|
|
3915
|
-
operation: "flags-context",
|
|
3916
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3917
|
-
});
|
|
3918
|
-
return void 0;
|
|
3919
|
-
}
|
|
3920
|
-
}
|
|
3921
|
-
|
|
3922
|
-
// src/handlers/resources/core/briefing/user-message.ts
|
|
3923
|
-
var escapeTableCell = (text) => text.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
3924
|
-
function formatUserMessage(opts) {
|
|
3925
|
-
const {
|
|
3926
|
-
repoName,
|
|
3927
|
-
branchName,
|
|
3928
|
-
totalEntries,
|
|
3929
|
-
latestPreview,
|
|
3930
|
-
summaryPreviews,
|
|
3931
|
-
github,
|
|
3932
|
-
rulesFile,
|
|
3933
|
-
skillsDir,
|
|
3934
|
-
analyticsInsights
|
|
3935
|
-
} = opts;
|
|
3936
|
-
let ciDisplay = opts.ciStatus;
|
|
3937
|
-
if (github?.workflowSummary) {
|
|
3938
|
-
const ws = github.workflowSummary;
|
|
3939
|
-
if (ws.runs && ws.runs.length > 0) {
|
|
3940
|
-
const icons = {
|
|
3941
|
-
success: "\u2705",
|
|
3942
|
-
failure: "\u274C",
|
|
3943
|
-
pending: "\u23F3",
|
|
3944
|
-
cancelled: "\u26D4",
|
|
3945
|
-
unknown: "\u2753"
|
|
3946
|
-
};
|
|
3947
|
-
ciDisplay = ws.runs.map((r) => `${icons[r.conclusion] ?? "\u2753"} ${r.name}`).join(" \xB7 ");
|
|
3948
|
-
} else {
|
|
3949
|
-
const parts = [];
|
|
3950
|
-
if (ws.passing > 0) parts.push(`${String(ws.passing)} passing`);
|
|
3951
|
-
if (ws.failing > 0) parts.push(`${String(ws.failing)} failing`);
|
|
3952
|
-
if (ws.pending > 0) parts.push(`${String(ws.pending)} pending`);
|
|
3953
|
-
if (ws.cancelled > 0) parts.push(`${String(ws.cancelled)} cancelled`);
|
|
3954
|
-
ciDisplay = parts.join(" \xB7 ") || opts.ciStatus;
|
|
3955
|
-
}
|
|
3956
|
-
}
|
|
3957
|
-
let issuesRow = "";
|
|
3958
|
-
let prsRow = "";
|
|
3959
|
-
if (github) {
|
|
3960
|
-
if (github.openIssueList && github.openIssueList.length > 0) {
|
|
3961
|
-
const titles = github.openIssueList.map((i) => `#${String(i.number)} ${i.title}`).join(" \xB7 ");
|
|
3962
|
-
issuesRow = `
|
|
3963
|
-
| **Issues** | ${String(github.openIssues)} open: ${escapeTableCell(titles)} |`;
|
|
3964
|
-
} else {
|
|
3965
|
-
issuesRow = `
|
|
3966
|
-
| **Issues** | ${String(github.openIssues)} open |`;
|
|
3967
|
-
}
|
|
3968
|
-
if (github.prStatusSummary) {
|
|
3969
|
-
const s = github.prStatusSummary;
|
|
3970
|
-
const parts = [];
|
|
3971
|
-
if (s.open > 0) parts.push(`${String(s.open)} open`);
|
|
3972
|
-
if (s.merged > 0) parts.push(`${String(s.merged)} merged`);
|
|
3973
|
-
if (s.closed > 0) parts.push(`${String(s.closed)} closed`);
|
|
3974
|
-
prsRow = `
|
|
3975
|
-
| **PRs** | ${parts.join(" \xB7 ") || "0"} |`;
|
|
3976
|
-
} else if (github.openPrList && github.openPrList.length > 0) {
|
|
3977
|
-
const titles = github.openPrList.map((p) => `#${String(p.number)} ${p.title}`).join(" \xB7 ");
|
|
3978
|
-
prsRow = `
|
|
3979
|
-
| **PRs** | ${String(github.openPRs)} open: ${escapeTableCell(titles)} |`;
|
|
3980
|
-
} else {
|
|
3981
|
-
prsRow = `
|
|
3982
|
-
| **PRs** | ${String(github.openPRs)} open |`;
|
|
3983
|
-
}
|
|
3984
|
-
}
|
|
3985
|
-
const milestoneRow = github?.milestones && github.milestones.length > 0 ? `
|
|
3986
|
-
| **Milestones** | ${escapeTableCell(github.milestones.map((m) => `${m.title} (${m.progress}${m.dueOn ? `, due ${m.dueOn.split("T")[0] ?? ""}` : ""})`).join(", "))} |` : "";
|
|
3987
|
-
let insightsRow = "";
|
|
3988
|
-
if (github?.insights) {
|
|
3989
|
-
const parts = [];
|
|
3990
|
-
if (github.insights.stars !== null) parts.push(`\u2B50 ${String(github.insights.stars)} stars`);
|
|
3991
|
-
if (github.insights.forks !== null) parts.push(`\u{1F374} ${String(github.insights.forks)} forks`);
|
|
3992
|
-
if (github.insights.clones14d !== void 0)
|
|
3993
|
-
parts.push(`\u{1F4E6} ${String(github.insights.clones14d)} clones`);
|
|
3994
|
-
if (github.insights.views14d !== void 0)
|
|
3995
|
-
parts.push(`\u{1F441}\uFE0F ${String(github.insights.views14d)} views`);
|
|
3996
|
-
if (parts.length > 0) {
|
|
3997
|
-
const trafficNote = github.insights.clones14d !== void 0 ? " (14d)" : "";
|
|
3998
|
-
insightsRow = `
|
|
3999
|
-
| **Insights** | ${parts.join(" \xB7 ")}${trafficNote} |`;
|
|
4000
|
-
}
|
|
4001
|
-
}
|
|
4002
|
-
const copilotRow = github?.copilotReviews ? `
|
|
4003
|
-
| **Copilot** | ${String(github.copilotReviews.reviewed)} reviewed \xB7 ${String(github.copilotReviews.approved)} approved${github.copilotReviews.changesRequested > 0 ? ` \xB7 ${String(github.copilotReviews.changesRequested)} changes requested` : ""}${github.copilotReviews.totalComments > 0 ? ` (${String(github.copilotReviews.totalComments)} comments)` : ""} |` : "";
|
|
4004
|
-
let analyticsRow = "";
|
|
4005
|
-
if (analyticsInsights) {
|
|
4006
|
-
const parts = [];
|
|
4007
|
-
parts.push(`\u{1F4C8} ${analyticsInsights.activityTrend}`);
|
|
4008
|
-
if (analyticsInsights.significanceSpike !== null)
|
|
4009
|
-
parts.push(`\u{1F525} ${analyticsInsights.significanceSpike}`);
|
|
4010
|
-
if (analyticsInsights.relationshipDensity !== void 0)
|
|
4011
|
-
parts.push(`\u{1F517} Relationship density: ${analyticsInsights.relationshipDensity}`);
|
|
4012
|
-
if (analyticsInsights.staleProjects.length > 0)
|
|
4013
|
-
parts.push(`\u{1F4A4} ${analyticsInsights.staleProjects.length} stale projects`);
|
|
4014
|
-
analyticsRow = `
|
|
4015
|
-
| **Analytics** | ${escapeTableCell(parts.join(" \xB7 "))} |`;
|
|
4016
|
-
}
|
|
4017
|
-
const summariesOutput = summaryPreviews && summaryPreviews.length > 0 ? summaryPreviews.map((s) => `
|
|
4018
|
-
| **Summary** | ${escapeTableCell(s)} |`).join("") : "";
|
|
4019
|
-
let flagsRow = "";
|
|
4020
|
-
if (opts.flagSummary && opts.flagSummary.count > 0) {
|
|
4021
|
-
const flagParts = opts.flagSummary.flags.slice(0, 5).map((f) => {
|
|
4022
|
-
const target = f.target_user ? ` \u2192 @${f.target_user}` : "";
|
|
4023
|
-
return `\u{1F6A9} ${f.flag_type}${target}: ${f.preview.slice(0, 50)}`;
|
|
4024
|
-
});
|
|
4025
|
-
flagsRow = `
|
|
4026
|
-
| **Flags** | ${escapeTableCell(flagParts.join(" \xB7 "))}${opts.flagSummary.count > 5 ? ` (+${String(opts.flagSummary.count - 5)} more)` : ""} |`;
|
|
4027
|
-
}
|
|
4028
|
-
return `\u{1F4CB} **Session Context Loaded**
|
|
4029
|
-
| Context | Value |
|
|
4030
|
-
|---------|-------|
|
|
4031
|
-
| **Project** | ${escapeTableCell(repoName)} |
|
|
4032
|
-
| **Branch** | ${escapeTableCell(branchName)} |
|
|
4033
|
-
| **CI** | ${escapeTableCell(ciDisplay)} |
|
|
4034
|
-
| **Journal** | ${totalEntries} entries |${opts.teamTotalEntries !== void 0 ? `
|
|
4035
|
-
| **Team DB** | ${opts.teamTotalEntries} entries |` : ""}
|
|
4036
|
-
| **Latest** | ${escapeTableCell(latestPreview)} |${summariesOutput}${issuesRow}${prsRow}${milestoneRow}${insightsRow}${copilotRow}${analyticsRow}${flagsRow}${rulesFile ? `
|
|
4037
|
-
| **Rules** | ${escapeTableCell(rulesFile.name)} (${String(rulesFile.sizeKB)} KB, updated ${rulesFile.lastModified}) |` : ""}${skillsDir ? `
|
|
4038
|
-
| **Skills** | ${String(skillsDir.count)} skill${skillsDir.count !== 1 ? "s" : ""} available |` : ""}`;
|
|
4039
|
-
}
|
|
4040
|
-
|
|
4041
|
-
// src/handlers/resources/core/briefing/insights-section.ts
|
|
4042
|
-
function buildInsightsSection(context) {
|
|
4043
|
-
const snapshot = resolveDigestSnapshot(context);
|
|
4044
|
-
if (!snapshot) return null;
|
|
4045
|
-
return formatDigest(snapshot);
|
|
4046
|
-
}
|
|
4047
|
-
function resolveDigestSnapshot(context) {
|
|
4048
|
-
const schedulerDigest = context.scheduler?.getLatestDigest?.();
|
|
4049
|
-
if (schedulerDigest) return schedulerDigest;
|
|
4050
|
-
const dbSnapshot = context.db?.getLatestAnalyticsSnapshot?.("digest");
|
|
4051
|
-
if (dbSnapshot) return dbSnapshot.data;
|
|
4052
|
-
return null;
|
|
4053
|
-
}
|
|
4054
|
-
function formatDigest(snapshot) {
|
|
4055
|
-
let activityTrend;
|
|
4056
|
-
if (snapshot.activityGrowthPercent !== null) {
|
|
4057
|
-
const sign = snapshot.activityGrowthPercent >= 0 ? "+" : "";
|
|
4058
|
-
activityTrend = `${sign}${String(snapshot.activityGrowthPercent)}% vs. last period (${String(snapshot.currentPeriodEntries)} entries)`;
|
|
4059
|
-
} else {
|
|
4060
|
-
activityTrend = `${String(snapshot.currentPeriodEntries)} entries this period (no previous data)`;
|
|
4061
|
-
}
|
|
4062
|
-
let significanceSpike = null;
|
|
4063
|
-
if (snapshot.currentPeriodSignificant > 0) {
|
|
4064
|
-
if (snapshot.significanceMultiplier !== null && snapshot.significanceMultiplier > 1.5) {
|
|
4065
|
-
significanceSpike = `${String(snapshot.currentPeriodSignificant)} significant entries (${String(snapshot.significanceMultiplier)}\xD7 avg)`;
|
|
4066
|
-
} else {
|
|
4067
|
-
significanceSpike = `${String(snapshot.currentPeriodSignificant)} significant entries this period`;
|
|
4068
|
-
}
|
|
4069
|
-
}
|
|
4070
|
-
return {
|
|
4071
|
-
activityTrend,
|
|
4072
|
-
significanceSpike,
|
|
4073
|
-
staleProjects: snapshot.staleProjects.map((p) => ({
|
|
4074
|
-
projectNumber: p.projectNumber,
|
|
4075
|
-
daysSilent: p.daysSilent
|
|
4076
|
-
})),
|
|
4077
|
-
topImportance: snapshot.topImportanceEntries.map((e) => ({
|
|
4078
|
-
id: e.id,
|
|
4079
|
-
score: e.score,
|
|
4080
|
-
preview: e.preview
|
|
4081
|
-
})),
|
|
4082
|
-
...snapshot.currentRelDensity > 0 ? { relationshipDensity: snapshot.currentRelDensity } : {}
|
|
4083
|
-
};
|
|
4084
|
-
}
|
|
4085
|
-
|
|
4086
|
-
// src/handlers/resources/core/briefing/index.ts
|
|
4087
|
-
var briefingResource = {
|
|
4088
|
-
uri: "memory://briefing",
|
|
4089
|
-
name: "Initial Briefing",
|
|
4090
|
-
title: "Session Initialization Context",
|
|
4091
|
-
description: "AUTO-READ AT SESSION START: Project context for AI agents (~300 tokens). Contains userMessage to show user.",
|
|
4092
|
-
mimeType: "application/json",
|
|
4093
|
-
icons: [ICON_BRIEFING],
|
|
4094
|
-
annotations: {
|
|
4095
|
-
...withSessionInit(withPriority(1, ASSISTANT_FOCUSED)),
|
|
4096
|
-
autoRead: true
|
|
4097
|
-
},
|
|
4098
|
-
handler: async (_uri, context) => {
|
|
4099
|
-
return buildBriefingData(context);
|
|
4100
|
-
}
|
|
4101
|
-
};
|
|
4102
|
-
var dynamicBriefingResource = {
|
|
4103
|
-
uri: "memory://briefing/{+repo}",
|
|
4104
|
-
name: "Dynamic Briefing",
|
|
4105
|
-
title: "Project-Specific Session Context",
|
|
4106
|
-
description: "Project-specific briefing context for AI agents. Same as memory://briefing but targets a specific repository name from the registered workspaces.",
|
|
4107
|
-
mimeType: "application/json",
|
|
4108
|
-
icons: [ICON_BRIEFING],
|
|
4109
|
-
annotations: {
|
|
4110
|
-
...withPriority(0.8, ASSISTANT_FOCUSED)
|
|
4111
|
-
},
|
|
4112
|
-
handler: async (uri, context) => {
|
|
4113
|
-
const match = /memory:\/\/briefing\/(.+)/.exec(uri);
|
|
4114
|
-
const repoName = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
4115
|
-
return buildBriefingData(context, repoName);
|
|
4116
|
-
}
|
|
4117
|
-
};
|
|
4118
|
-
async function buildBriefingData(context, targetRepo) {
|
|
4119
|
-
const config = { ...DEFAULT_BRIEFING_CONFIG, ...context.briefingConfig };
|
|
4120
|
-
let activeGithub = context.github;
|
|
4121
|
-
let activeProjectNumber = config.defaultProjectNumber;
|
|
4122
|
-
if (targetRepo && config.projectRegistry?.[targetRepo]) {
|
|
4123
|
-
const repoPath = config.projectRegistry[targetRepo].path;
|
|
4124
|
-
activeGithub = getGitHubIntegration(repoPath, context.runtime);
|
|
4125
|
-
activeProjectNumber = config.projectRegistry[targetRepo].project_number ?? void 0;
|
|
4126
|
-
}
|
|
4127
|
-
const journal = buildJournalContext(context, config, activeProjectNumber);
|
|
4128
|
-
const github = await buildGitHubSection(activeGithub, config);
|
|
4129
|
-
const team = buildTeamContext(context, config, activeProjectNumber);
|
|
4130
|
-
const rulesFile = buildRulesFileInfo(config.rulesFilePath, config.allowedIoRoots);
|
|
4131
|
-
const skillsDir = buildSkillsDirInfo(config.skillsDirPath, config.allowedIoRoots);
|
|
4132
|
-
const insights = buildInsightsSection(context);
|
|
4133
|
-
const flags = buildFlagsContext(context);
|
|
4134
|
-
const latestPreview = journal.latestEntries[0] ? `#${journal.latestEntries[0].id} (${journal.latestEntries[0].type}): ${journal.latestEntries[0].preview}` : "No entries yet";
|
|
4135
|
-
const summaryPreviews = journal.sessionSummaries ? journal.sessionSummaries.map((s) => `#${s.id} (${s.type}): ${s.preview}`) : null;
|
|
4136
|
-
const userMessage = formatUserMessage({
|
|
4137
|
-
repoName: github?.repo ?? "local",
|
|
4138
|
-
branchName: github?.branch ?? "unknown",
|
|
4139
|
-
ciStatus: github?.ci ?? "unknown",
|
|
4140
|
-
totalEntries: journal.totalEntries,
|
|
4141
|
-
latestPreview,
|
|
4142
|
-
summaryPreviews,
|
|
4143
|
-
github,
|
|
4144
|
-
teamTotalEntries: team?.teamInfo.totalEntries,
|
|
4145
|
-
rulesFile,
|
|
4146
|
-
skillsDir,
|
|
4147
|
-
analyticsInsights: insights ?? void 0,
|
|
4148
|
-
flagSummary: flags
|
|
4149
|
-
});
|
|
4150
|
-
return {
|
|
4151
|
-
data: {
|
|
4152
|
-
version: VERSION,
|
|
4153
|
-
serverTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4154
|
-
localTime: new Intl.DateTimeFormat("en-US", {
|
|
4155
|
-
dateStyle: "full",
|
|
4156
|
-
timeStyle: "short",
|
|
4157
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
4158
|
-
}).format(/* @__PURE__ */ new Date()),
|
|
4159
|
-
journal: {
|
|
4160
|
-
totalEntries: journal.totalEntries,
|
|
4161
|
-
latestEntries: journal.latestEntries,
|
|
4162
|
-
...journal.latestSessionSummary ? { latestSessionSummary: journal.latestSessionSummary } : {}
|
|
4163
|
-
},
|
|
4164
|
-
github,
|
|
4165
|
-
teamContext: team?.teamInfo,
|
|
4166
|
-
...team?.teamLatestEntries ? { teamLatestEntries: team.teamLatestEntries } : {},
|
|
4167
|
-
...rulesFile ? { rulesFile } : {},
|
|
4168
|
-
...skillsDir ? { skillsDir } : {},
|
|
4169
|
-
...insights ? { insights } : {},
|
|
4170
|
-
...flags ? { activeFlags: flags } : {},
|
|
4171
|
-
...config.projectRegistry ? {
|
|
4172
|
-
registeredWorkspaces: Object.fromEntries(
|
|
4173
|
-
Object.entries(config.projectRegistry).map(([k, v]) => {
|
|
4174
|
-
const strippedPath = v.path.split(/[\\/]/).pop() || v.path;
|
|
4175
|
-
return [k, { ...v, path: strippedPath }];
|
|
4176
|
-
})
|
|
4177
|
-
)
|
|
4178
|
-
} : {},
|
|
4179
|
-
behaviors: {
|
|
4180
|
-
create: "implementations, decisions, bug-fixes, milestones",
|
|
4181
|
-
search: "before decisions, referencing prior work",
|
|
4182
|
-
link: "implementation\u2192spec, bugfix\u2192issue"
|
|
4183
|
-
},
|
|
4184
|
-
templateResources: [
|
|
4185
|
-
"memory://github/status/{repo}",
|
|
4186
|
-
"memory://github/insights/{repo}",
|
|
4187
|
-
"memory://github/milestones/{repo}",
|
|
4188
|
-
"memory://milestones/{repo}/{number}",
|
|
4189
|
-
"memory://projects/{number}/timeline",
|
|
4190
|
-
"memory://issues/{issue_number}/entries",
|
|
4191
|
-
"memory://prs/{pr_number}/entries",
|
|
4192
|
-
"memory://prs/{pr_number}/timeline",
|
|
4193
|
-
"memory://kanban/{project_number}",
|
|
4194
|
-
"memory://kanban/{project_number}/diagram",
|
|
4195
|
-
"memory://milestones/{number}"
|
|
4196
|
-
],
|
|
4197
|
-
more: {
|
|
4198
|
-
fullHealth: "memory://health",
|
|
4199
|
-
allRecent: "memory://recent",
|
|
4200
|
-
githubStatus: "memory://github/status",
|
|
4201
|
-
repoInsights: "memory://github/insights",
|
|
4202
|
-
contextBundle: "get-context-bundle prompt"
|
|
4203
|
-
},
|
|
4204
|
-
userMessage,
|
|
4205
|
-
clientNote: "For full tool reference and field notes, read memory://instructions \u2014 only if your client did NOT auto-inject server instructions at session start (most modern clients including AntiGravity do this automatically).\\n" + (config.projectRegistry ? "\\nMulti-project registry detected. To retrieve CI status, branch, and issues for a specific project, use the get_github_context tool or dynamic resources (e.g. memory://github/status/{repo}) with the repository name." : "")
|
|
4206
|
-
},
|
|
4207
|
-
annotations: { lastModified: journal.lastModified }
|
|
4208
|
-
};
|
|
4209
|
-
}
|
|
4210
|
-
|
|
4211
|
-
// src/constants/server-instructions.ts
|
|
4212
|
-
var CORE_INSTRUCTIONS = `# memory-journal-mcp
|
|
4213
|
-
|
|
4214
|
-
## **ESSENTIAL SESSION START!**
|
|
4215
|
-
|
|
4216
|
-
1. You **MUST** read the \`memory://briefing/{repo_name}\` at the start of each chat!
|
|
4217
|
-
2. Use the standard MCP \`read_resource\` tool for this (do NOT use Code Mode/execute_code).
|
|
4218
|
-
3. Infer the \`repo_name\` from the user's prompt or your active workspace context.
|
|
4219
|
-
4. **ACKNOWLEDGE FLAGS**: If the briefing JSON contains \`activeFlags\` (count > 0), you MUST print an alert ABOVE the table: \`\u26A0\uFE0F **{count} active flag(s)** \u2014 review before proceeding.\` followed by each flag (\`\u{1F6A9} {flag_type} \u2192 @{target_user}: {preview}\`).
|
|
4220
|
-
5. **RENDER TABLE**: Parse the remaining JSON into a dense 2-column Markdown Table (Field, Value).
|
|
4221
|
-
- **RESTRICTION**: NO bulleted lists inside the table. Do NOT truncate summaries or issues.
|
|
4222
|
-
- **FORMATTING**: Group related properties (use \`<br>\` for line breaks).
|
|
4223
|
-
- **REQUIRED GROUPS**: GitHub, Issues, Entry Counts, Latest Entries/Summaries, Analytics, Milestones, Workspaces.
|
|
4224
|
-
6. **STOP & WAIT**: Do NOT autonomously resume past tasks or start work on new issues. The briefing is strictly for context.
|
|
4225
|
-
|
|
4226
|
-
- **AntiGravity**: Tools are \`mcp_{name}_{tool}\` \u2192 server name = \`memory-journal-mcp\`
|
|
4227
|
-
- **Cursor**: Tools are \`user-{name}-{tool}\` \u2192 server name = \`user-memory-journal-mcp\`
|
|
4228
|
-
- **Other clients**: Use configured name exactly. Use tool-prefix discovery if unsure.
|
|
4229
|
-
|
|
4230
|
-
## Behaviors
|
|
4231
|
-
|
|
4232
|
-
### memory-journal-mcp Behaviors
|
|
4233
|
-
|
|
4234
|
-
- **Personal vs Team**: **ALWAYS use the personal journal** (e.g., \`create_entry\`) by default. ONLY save to the team journal (e.g., \`team_create_entry\`) if the user explicitly requests it.
|
|
4235
|
-
- **Create entries for**: implementations, decisions, bug fixes, milestones, user requests to "remember"
|
|
4236
|
-
- **Search before**: major decisions, referencing prior work, understanding project context. Use \`sort_by: "importance"\` on \`search_entries\`, \`get_recent_entries\`, or \`search_by_date_range\` to surface structurally significant entries (decisions, milestones, highly-connected nodes) over simply recent ones.
|
|
4237
|
-
- **Analyze insights**: Use cross-project insights (\`get_cross_project_insights\`) before defining architectures. Use \`team_get_collaboration_matrix\` to evaluate team health, cross-author activity patterns, and collaboration impact. Use repo insights (\`memory://github/insights\`) to gauge traction. View \`memory://insights/digest\` and \`memory://insights/team-collaboration\` for automated analytics snapshots.
|
|
4238
|
-
- **Link entries**: implementation\u2192spec, bugfix\u2192issue, followup\u2192prior work
|
|
4239
|
-
|
|
4240
|
-
### Rule & Skill Suggestions
|
|
4241
|
-
|
|
4242
|
-
When you notice the user consistently applies patterns, preferences, or workflows that could be codified:
|
|
4243
|
-
|
|
4244
|
-
**Suggest adding a rule** when you observe:
|
|
4245
|
-
|
|
4246
|
-
- Naming conventions, formatting preferences, or coding standards
|
|
4247
|
-
- Testing patterns or verification steps the user always follows
|
|
4248
|
-
- Project-specific commands, workflows, or deployment steps
|
|
4249
|
-
- Error handling patterns or logging conventions
|
|
4250
|
-
|
|
4251
|
-
**Suggest adding a skill** when you build:
|
|
4252
|
-
|
|
4253
|
-
- Reusable multi-step processes (e.g., deployment, release, audit workflows)
|
|
4254
|
-
- Project-specific templates or scaffolds
|
|
4255
|
-
- Complex integrations or tool chains the user may repeat
|
|
4256
|
-
|
|
4257
|
-
**Suggest refining existing rules/skills** when you notice:
|
|
4258
|
-
|
|
4259
|
-
- A rule conflict or ambiguity causing inconsistent behavior
|
|
4260
|
-
- An outdated pattern that no longer matches the codebase
|
|
4261
|
-
- Missing edge cases or exceptions to an existing rule
|
|
4262
|
-
- A skill that could be extended with new steps
|
|
4263
|
-
|
|
4264
|
-
**How to act:**
|
|
4265
|
-
|
|
4266
|
-
- The briefing shows **Rules** and **Skills** paths \u2014 use these to locate the files
|
|
4267
|
-
- **Always ask the user first** \u2014 never create or modify rules/skills silently
|
|
4268
|
-
- Frame suggestions as: "I noticed you always [pattern]. Would you like me to add/update a rule for this?"
|
|
4269
|
-
- For skills, explain the workflow it would automate and what triggers it
|
|
4270
|
-
|
|
4271
|
-
### Native Agent Skills (NPM Distribution)
|
|
4272
|
-
|
|
4273
|
-
This server leverages the \`neverinfamous-agent-skills\` package. If the user's \`SKILLS_DIR_PATH\` environment variable targets these, you have native access to foundational frameworks (\`typescript\`, \`react-best-practices\`, \`playwright-standard\`, \`golang\`, \`rust\`, \`python\`, \`docker\`, \`tailwind-css\`, \`shadcn-ui\`) and the \`github-commander\` DevOps workflows (\`issue-triage\`, \`pr-review\`, \`github-actions\`, \`copilot-audit\`, etc.). The \`adversarial-planner\` skill provides multi-pass plan review with structured critique stages.
|
|
4274
|
-
|
|
4275
|
-
- The user can distribute or update these skills across their repositories by running \`npx neverinfamous-agent-skills@latest\`.
|
|
4276
|
-
- If you need to create a new skill, reference the bundled \`skill-builder\` instructions!
|
|
4277
|
-
|
|
4278
|
-
### Hush Protocol (Team Flags)
|
|
4279
|
-
|
|
4280
|
-
Flags are machine-actionable signals stored in the team database. They replace Slack/Teams noise with structured, searchable entries that surface automatically in the briefing.
|
|
4281
|
-
|
|
4282
|
-
**When to create a flag** (\`pass_team_flag\`):
|
|
4283
|
-
|
|
4284
|
-
- \`blocker\` \u2014 work is blocked and requires another person's action
|
|
4285
|
-
- \`needs_review\` \u2014 code, document, or decision needs peer review
|
|
4286
|
-
- \`help_requested\` \u2014 stuck and need guidance or pairing
|
|
4287
|
-
- \`fyi\` \u2014 non-blocking awareness signal (completed migration, config change, etc.)
|
|
4288
|
-
|
|
4289
|
-
**When to resolve** (\`resolve_team_flag\`): After the blocking condition is cleared. Include a brief resolution comment describing what was done. Resolving is idempotent \u2014 safe to call on already-resolved flags.
|
|
4290
|
-
|
|
4291
|
-
**Briefing integration**: The \`memory://briefing\` payload includes \`activeFlags\` when unresolved flags exist. The user's agent rules may instruct you to render these prominently. Always check for and acknowledge active flags at session start.
|
|
4292
|
-
|
|
4293
|
-
**Dashboard**: Read \`memory://flags\` to see all active (unresolved) flags. Read \`memory://flags/vocabulary\` to see the configured flag types.
|
|
4294
|
-
|
|
4295
|
-
**Code Mode**: \`mj.team.passTeamFlag({ flag_type, message })\` and \`mj.team.resolveTeamFlag({ flag_id })\`.
|
|
4296
|
-
`;
|
|
4297
|
-
var COPILOT_REVIEW_INSTRUCTIONS = `
|
|
4298
|
-
## Copilot Review Patterns
|
|
4299
|
-
|
|
4300
|
-
When the user has GitHub Copilot code review enabled:
|
|
4301
|
-
|
|
4302
|
-
**Learn from reviews** \u2014 After a PR is merged or reviewed, use \`get_copilot_reviews(pr_number)\` to read Copilot's findings. If patterns emerge (e.g., repeated null check warnings, missing error handling), suggest adding a rule or updating existing rules. Create journal entries tagged \`copilot-finding\` and link to the PR via \`pr_number\`.
|
|
4303
|
-
|
|
4304
|
-
**Pre-emptive checking** \u2014 Before creating or modifying code, search journal entries with tag \`copilot-finding\` for patterns relevant to the current work. Apply those patterns proactively to reduce review cycles.
|
|
4305
|
-
|
|
4306
|
-
**How to act:**
|
|
4307
|
-
|
|
4308
|
-
- The briefing shows **Rules** and **Skills** paths \u2014 use these to locate the files
|
|
4309
|
-
- **Always ask the user first** \u2014 never create or modify rules/skills silently
|
|
4310
|
-
- Frame suggestions as: "I noticed you always [pattern]. Would you like me to add/update a rule for this?"
|
|
4311
|
-
- For skills, explain the workflow it would automate and what triggers it
|
|
4312
|
-
`;
|
|
4313
|
-
function buildQuickAccess(groups) {
|
|
4314
|
-
let table = `
|
|
4315
|
-
## Quick Access
|
|
4316
|
-
|
|
4317
|
-
| Purpose | Action |
|
|
4318
|
-
| --------------- | --------------------------- |
|
|
4319
|
-
| Session context | \`memory://briefing\` |
|
|
4320
|
-
| Recent entries | \`memory://recent\` |
|
|
4321
|
-
| Health/time | \`memory://health\` |
|
|
4322
|
-
`;
|
|
4323
|
-
if (groups.has("search")) {
|
|
4324
|
-
table += `| Semantic search | \`semantic_search(query)\` |
|
|
4325
|
-
`;
|
|
4326
|
-
}
|
|
4327
|
-
table += `| Full context | \`get-context-bundle\` prompt |
|
|
4328
|
-
`;
|
|
4329
|
-
return table;
|
|
4330
|
-
}
|
|
4331
|
-
var CODE_MODE_NAMESPACE_ROWS = [
|
|
4332
|
-
{
|
|
4333
|
-
group: "core",
|
|
4334
|
-
label: "Core",
|
|
4335
|
-
namespace: "`mj.core.*`",
|
|
4336
|
-
example: '`mj.core.createEntry("Implemented feature X")`'
|
|
4337
|
-
},
|
|
4338
|
-
{
|
|
4339
|
-
group: "search",
|
|
4340
|
-
label: "Search",
|
|
4341
|
-
namespace: "`mj.search.*`",
|
|
4342
|
-
example: '`mj.search.searchEntries("performance")`'
|
|
4343
|
-
},
|
|
4344
|
-
{
|
|
4345
|
-
group: "analytics",
|
|
4346
|
-
label: "Analytics",
|
|
4347
|
-
namespace: "`mj.analytics.*`",
|
|
4348
|
-
example: "`mj.analytics.getStatistics()`"
|
|
4349
|
-
},
|
|
4350
|
-
{
|
|
4351
|
-
group: "relationships",
|
|
4352
|
-
label: "Relationships",
|
|
4353
|
-
namespace: "`mj.relationships.*`",
|
|
4354
|
-
example: '`mj.relationships.linkEntries(1, 2, "implements")`'
|
|
4355
|
-
},
|
|
4356
|
-
{
|
|
4357
|
-
group: "io",
|
|
4358
|
-
label: "IO",
|
|
4359
|
-
namespace: "`mj.io.*`",
|
|
4360
|
-
example: '`mj.io.importMarkdown("content")`'
|
|
4361
|
-
},
|
|
4362
|
-
{
|
|
4363
|
-
group: "io",
|
|
4364
|
-
label: "Export",
|
|
4365
|
-
namespace: "`mj.export.*`",
|
|
4366
|
-
example: '`mj.export.exportEntries("json")`'
|
|
4367
|
-
},
|
|
4368
|
-
{
|
|
4369
|
-
group: "admin",
|
|
4370
|
-
label: "Admin",
|
|
4371
|
-
namespace: "`mj.admin.*`",
|
|
4372
|
-
example: "`mj.admin.rebuildVectorIndex()`"
|
|
4373
|
-
},
|
|
4374
|
-
{
|
|
4375
|
-
group: "github",
|
|
4376
|
-
label: "GitHub",
|
|
4377
|
-
namespace: "`mj.github.*`",
|
|
4378
|
-
example: '`mj.github.getGithubIssues({ state: "open" })`'
|
|
4379
|
-
},
|
|
4380
|
-
{
|
|
4381
|
-
group: "backup",
|
|
4382
|
-
label: "Backup",
|
|
4383
|
-
namespace: "`mj.backup.*`",
|
|
4384
|
-
example: "`mj.backup.backupJournal()`"
|
|
4385
|
-
},
|
|
4386
|
-
{
|
|
4387
|
-
group: "team",
|
|
4388
|
-
label: "Team",
|
|
4389
|
-
namespace: "`mj.team.*`",
|
|
4390
|
-
example: '`mj.team.teamCreateEntry("Team update")`'
|
|
4391
|
-
}
|
|
4392
|
-
];
|
|
4393
|
-
function buildCodeModeInstructions(groups) {
|
|
4394
|
-
const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map(
|
|
4395
|
-
(r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`
|
|
4396
|
-
).join("\n");
|
|
4397
|
-
const fullSection = CODE_MODE_FULL_TEXT;
|
|
4398
|
-
const tableStart = fullSection.indexOf("| Group");
|
|
4399
|
-
const tableEnd = fullSection.indexOf("\n\n**Features**");
|
|
4400
|
-
if (tableStart === -1 || tableEnd === -1) {
|
|
4401
|
-
return "\n" + fullSection;
|
|
4402
|
-
}
|
|
4403
|
-
const beforeTable = fullSection.slice(0, tableStart);
|
|
4404
|
-
const headerLine = "| Group | Namespace | Example |";
|
|
4405
|
-
const separatorLine = "| ------------- | -------------------- | -------------------------------------------------- |";
|
|
4406
|
-
const afterTable = fullSection.slice(tableEnd);
|
|
4407
|
-
return "\n" + beforeTable + headerLine + "\n" + separatorLine + "\n" + rows + afterTable;
|
|
4408
|
-
}
|
|
4409
|
-
var CODE_MODE_FULL_TEXT = `## Code Mode (Token-Efficient Multi-Step Operations)
|
|
4410
|
-
|
|
4411
|
-
For multi-step workflows (3+ operations), prefer \`mj_execute_code\` over individual tool calls.
|
|
4412
|
-
This executes JavaScript in a sandboxed environment with all tools available as \`mj.*\` API:
|
|
4413
|
-
|
|
4414
|
-
| Group | Namespace | Example |
|
|
4415
|
-
| ------------- | -------------------- | -------------------------------------------------- |
|
|
4416
|
-
| Core | \`mj.core.*\` | \`mj.core.createEntry("Implemented feature X")\` |
|
|
4417
|
-
| Search | \`mj.search.*\` | \`mj.search.searchEntries("performance")\` |
|
|
4418
|
-
| Analytics | \`mj.analytics.*\` | \`mj.analytics.getStatistics()\` |
|
|
4419
|
-
| Relationships | \`mj.relationships.*\` | \`mj.relationships.linkEntries(1, 2, "implements")\` |
|
|
4420
|
-
| IO | \`mj.io.*\` | \`mj.io.importMarkdown("content")\` |
|
|
4421
|
-
| Export | \`mj.export.*\` | \`mj.export.exportEntries("json")\` |
|
|
4422
|
-
| Admin | \`mj.admin.*\` | \`mj.admin.rebuildVectorIndex()\` |
|
|
4423
|
-
| GitHub | \`mj.github.*\` | \`mj.github.getGithubIssues({ state: "open" })\` |
|
|
4424
|
-
| Backup | \`mj.backup.*\` | \`mj.backup.backupJournal()\` |
|
|
4425
|
-
| Team | \`mj.team.*\` | \`mj.team.teamCreateEntry("Team update")\` |
|
|
4426
|
-
|
|
4427
|
-
**Features**: Positional args (\`createEntry("note")\`), aliases (\`mj.core.create\`), \`mj.help()\` for discovery.
|
|
4428
|
-
**Readonly mode**: \`readonly: true\` restricts to read-only tools only. Calling a mutation method (e.g., \`mj.core.create(...)\`) in readonly mode throws an error that halts execution \u2014 the sandbox returns \`{ success: false, error: "Operation '...' is not found in group" }\`. If a group has no methods at all (fully stripped), the error says \`"no methods (read-only mode?)"\`.
|
|
4429
|
-
**Returns**: Last expression value. Errors return \`{ success: false, error: "..." }\`.
|
|
4430
|
-
|
|
4431
|
-
**GitHub Context Injection**: You can pass \`repo: 'my-repo'\` directly to \`mj_execute_code\` (e.g., \`mj_execute_code({ code, repo: 'memory-journal-mcp' })\`) to instantly bind that repository and its default Kanban board to all GitHub and Kanban tools running inside the sandbox, avoiding the need to pass \`owner\`/\`repo\` manually to individual methods inside.
|
|
4432
|
-
|
|
4433
|
-
**Important \u2014 all \`mj.*\` methods return Promises. Always \`await\` them:**
|
|
4434
|
-
|
|
4435
|
-
\`\`\`js
|
|
4436
|
-
// \u2705 Correct
|
|
4437
|
-
const result = await mj.core.recent({ limit: 5 })
|
|
4438
|
-
return result.entries.map((e) => e.id)
|
|
4439
|
-
|
|
4440
|
-
// \u274C Wrong \u2014 returns a Promise object, not the entries
|
|
4441
|
-
const result = mj.core.recent({ limit: 5 })
|
|
4442
|
-
|
|
4443
|
-
// \u2705 Discovery
|
|
4444
|
-
const help = await mj.help() // { groups, totalMethods, usage }
|
|
4445
|
-
const groupHelp = await mj.core.help() // { group, methods }
|
|
4446
|
-
\`\`\`
|
|
4447
|
-
|
|
4448
|
-
**\`mj.core.recent()\` return shape**: Returns \`{ entries: JournalEntry[], count: number }\` \u2014 not a plain array. Access \`.entries\` to iterate:
|
|
4449
|
-
|
|
4450
|
-
\`\`\`js
|
|
4451
|
-
const { entries, count } = await mj.core.recent({ limit: 10 })
|
|
4452
|
-
return entries.map((e) => ({ id: e.id, content: e.content.slice(0, 50) }))
|
|
4453
|
-
\`\`\`
|
|
4454
|
-
`;
|
|
4455
|
-
var GITHUB_INSTRUCTIONS = `
|
|
4456
|
-
## GitHub Integration
|
|
4457
|
-
|
|
4458
|
-
- Include \`issue_number\`/\`pr_number\` in \`create_entry\` to auto-link
|
|
4459
|
-
- After closing issue/merging PR \u2192 create summary entry with learnings
|
|
4460
|
-
- CI failures \u2192 \`actions-failure-digest\` prompt or \`memory://actions/recent\`
|
|
4461
|
-
- Kanban: \`get_kanban_board\` \u2192 \`add_kanban_item\` / \`move_kanban_item\` / \`delete_kanban_item\` \u2192 document completion (project_number auto-resolves if repo is registered)
|
|
4462
|
-
- Milestones: \`get_github_milestones\` \u2192 track project progress, \`memory://github/milestones\`
|
|
4463
|
-
- **Multi-Project Routing**: If \`memory://briefing\` shows "Registered Workspaces":
|
|
4464
|
-
- **Tools**: Pass a \`repo\` parameter to ALL GitHub tools (including \`get_github_context\`) to explicitly target a specific project.
|
|
4465
|
-
- **Resources**: You MUST use the dynamic \`{repo}\` variants for resources (e.g., \`memory://github/status/{repo}\`, \`memory://github/insights/{repo}\`) rather than the base URI (\`memory://github/status\`), which will fail with a detection error.
|
|
4466
|
-
- **Dynamic Briefings**: You can explicitly request the briefing for a specific project by reading \`memory://briefing/{repo}\` instead of the global \`memory://briefing\` resource.
|
|
4467
|
-
`;
|
|
4468
|
-
var HELP_POINTERS = `
|
|
4469
|
-
## Help Resources
|
|
4470
|
-
|
|
4471
|
-
Read \`memory://help\` for tool group index and available help resources.
|
|
4472
|
-
Read \`memory://help/{group}\` for per-group tool reference (parameters, annotations, examples).
|
|
4473
|
-
Read \`memory://help/gotchas\` for critical field notes and usage patterns.
|
|
4474
|
-
Only help resources for your enabled tool groups are registered.
|
|
4475
|
-
`;
|
|
4476
|
-
var SERVER_ACCESS_INSTRUCTIONS = `
|
|
4477
|
-
## How to Access This Server
|
|
4478
|
-
|
|
4479
|
-
### Server Name Discovery
|
|
4480
|
-
|
|
4481
|
-
The server name used for resource and tool calls depends on your MCP client:
|
|
4482
|
-
|
|
4483
|
-
- **AntiGravity**: Prefixes tools with \`mcp_\` and uses underscores. If the server is named \`memory-journal-mcp\` in config, tools appear as \`mcp_memory-journal-mcp_create_entry\`. Use \`memory-journal-mcp\` as the server name for resource calls.
|
|
4484
|
-
- **Cursor**: Prepends \`user-\` to the configured name. If the server is named \`memory-journal-mcp\` in config, use \`user-memory-journal-mcp\` for \`ListMcpResources\` and \`FetchMcpResource\` calls.
|
|
4485
|
-
- **Other clients** (Claude Desktop, etc.): Likely use the configured name exactly. Only Cursor and AntiGravity have been verified \u2014 use the tool-prefix discovery method if unsure.
|
|
4486
|
-
|
|
4487
|
-
To identify your server name: look at the tool name prefix. Strip the tool name suffix to get the server name. Examples: \`mcp_memory-journal-mcp_create_entry\` \u2192 \`memory-journal-mcp\`; \`user-memory-journal-mcp-create_entry\` \u2192 \`user-memory-journal-mcp\`.
|
|
4488
|
-
|
|
4489
|
-
### Calling Tools
|
|
4490
|
-
|
|
4491
|
-
Use the tool functions directly \u2014 they are already available in your context by their full prefixed name.
|
|
4492
|
-
|
|
4493
|
-
### Reading Resources
|
|
4494
|
-
|
|
4495
|
-
Use the resource-reading mechanism provided by your MCP client with the discovered server name and \`memory://\` URIs.
|
|
4496
|
-
|
|
4497
|
-
Do NOT try to browse filesystem paths for MCP tool/resource definitions \u2014 use the MCP protocol directly.
|
|
4498
|
-
|
|
4499
|
-
## Quick Health Check
|
|
4500
|
-
|
|
4501
|
-
Fetch \`memory://health\` to verify server status, database stats, and tool availability.
|
|
4502
|
-
`;
|
|
4503
|
-
var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
|
|
4504
|
-
|
|
4505
|
-
## \u26A0\uFE0F Critical Patterns
|
|
4506
|
-
|
|
4507
|
-
- **\`autoContext\`**: Deprecated in v7.5.1. The feature was originally planned for background filesystem monitoring but has been abandoned to reduce telemetry overhead. Existing data with \`autoContext: null\` is safely ignored.
|
|
4508
|
-
- **\`memory://tags\` vs \`list_tags\`**: Resource includes \`id\`, \`name\`, \`count\`; tool returns only \`name\`, \`count\`. Neither returns orphan tags with zero usage.
|
|
4509
|
-
- **Tag naming**: Use lowercase with dashes (e.g., \`bug-fix\`, \`phase-2\`). Use \`merge_tags\` to consolidate duplicates (e.g., merge \`phase2\` into \`phase-2\`).
|
|
4510
|
-
- **\`merge_tags\` behavior**: Only updates non-deleted entries. Deleted entries retain their original tags.
|
|
4511
|
-
- **\`prStatus\` in entries**: Reflects PR state at entry creation time, not current state. Use \`get_github_pr\` for live status.
|
|
4512
|
-
- **\`restore_backup\` behavior**: Restores entire database state. Any recent changes (new entries, tag merges via \`merge_tags\`, relationships) are reverted. A pre-restore backup is automatically created for safety.
|
|
4513
|
-
|
|
4514
|
-
## Semantic Search
|
|
4515
|
-
|
|
4516
|
-
- **Indexing**: Entries are auto-indexed on creation (fire-and-forget). If index count drifts from DB count, use \`rebuild_vector_index\` or enable \`AUTO_REBUILD_INDEX=true\` for automatic reconciliation on server startup.
|
|
4517
|
-
- **Related by ID**: Provide \`entry_id\` instead of a query string to find entries semantically related to an existing entry (reuses the existing embedding to avoid inference costs).
|
|
4518
|
-
- **Metadata Filters**: Semantic search supports explicit filtering by \`tags\`, \`entry_type\`, \`start_date\`, and \`end_date\`.
|
|
4519
|
-
- **Thresholds**: Default similarity threshold is 0.25. For broader matches, try 0.15-0.2. Higher values (0.4+) return only very close semantic matches. A quality floor of 0.5 is always enforced: if all results score below 0.5, a hint is included indicating results may be noise. The \`hint_on_empty\` flag (default true) only controls advisory hints for empty indexes and zero-match queries \u2014 the quality gate hint is always shown.
|
|
4520
|
-
|
|
4521
|
-
## Search
|
|
4522
|
-
|
|
4523
|
-
- **Hybrid Ranking**: \`search_entries\` defaults to \`mode: 'auto'\`. Conversational prompts automatically utilize Reciprocal Rank Fusion (true Hybrid) bridging keyword and vector algorithms.
|
|
4524
|
-
- **\`search_entries\` FTS5 query syntax**: Uses FTS5 full-text search with Porter stemmer. Phrase queries: \`"error handling"\`. Prefix: \`auth*\`. Boolean: \`deploy OR release\`, \`error NOT warning\`. Word-boundary matching ("log" matches "log" but not "catalog"). Results ranked by BM25 relevance. Falls back to LIKE substring matching for queries with unbalanced quotes or special characters.
|
|
4525
|
-
|
|
4526
|
-
## Relationships & Analytics
|
|
4527
|
-
|
|
4528
|
-
- **Causal relationship types**: Use \`blocked_by\` (A was blocked by B), \`resolved\` (A resolved B), \`caused\` (A caused B) for decision tracing and failure analysis. Visualizations use distinct arrow styles for causal types.
|
|
4529
|
-
- **Enhanced analytics**: \`get_statistics\` returns \`decisionDensity\` (significant entries per period), \`relationshipComplexity\` (avg relationships per entry), \`activityTrend\` (period-over-period growth %), and \`causalMetrics\` (counts for blocked_by/resolved/caused).
|
|
4530
|
-
- **Importance scores**: \`get_entry_by_id\` returns \`importance\` (0.0-1.0) and \`importanceBreakdown\` showing weighted components: significance (30%), relationships (35%), causal (20%), recency (15%). \`memory://significant\` sorts entries by importance.
|
|
4531
|
-
- **\`inactiveThresholdDays\`**: \`get_cross_project_insights\` includes \`inactiveThresholdDays: 7\` in output, documenting the inactive project classification cutoff.
|
|
4532
|
-
|
|
4533
|
-
## GitHub Metadata
|
|
4534
|
-
|
|
4535
|
-
- **GitHub metadata in entries**: Entry output includes 10 GitHub fields (\`issueNumber\`, \`issueUrl\`, \`prNumber\`, \`prUrl\`, \`prStatus\`, \`projectNumber\`, \`projectOwner\`, \`workflowRunId\`, \`workflowName\`, \`workflowStatus\`) in all tool responses.
|
|
4536
|
-
|
|
4537
|
-
## Entry Operations
|
|
4538
|
-
|
|
4539
|
-
- **\`delete_entry\` on soft-deleted**: \`delete_entry(id, permanent: true)\` works on previously soft-deleted entries. Returns \`success: false\` for nonexistent entries.
|
|
4540
|
-
|
|
4541
|
-
## Team Database
|
|
4542
|
-
|
|
4543
|
-
- **Team cross-database search**: \`search_entries\` and \`search_by_date_range\` automatically merge team DB results when \`TEAM_DB_PATH\` is configured. Results include a \`source\` field ("personal" or "team").
|
|
4544
|
-
- **Team vector search**: Team has its own isolated vector index. Use \`team_rebuild_vector_index\` if the team index drifts. \`team_semantic_search\` works identically to personal \`semantic_search\`.
|
|
4545
|
-
- **Team tools without \`TEAM_DB_PATH\`**: All 25 team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
|
|
4546
|
-
`;
|
|
4547
|
-
function generateInstructions(enabledTools, prompts, latestEntry, level = "standard", enabledGroups) {
|
|
4548
|
-
const groups = enabledGroups ?? getEnabledGroups(enabledTools);
|
|
4549
|
-
let instructions = CORE_INSTRUCTIONS;
|
|
4550
|
-
if (groups.has("github")) {
|
|
4551
|
-
instructions += COPILOT_REVIEW_INSTRUCTIONS;
|
|
4552
|
-
}
|
|
4553
|
-
instructions += buildQuickAccess(groups);
|
|
4554
|
-
if (groups.has("codemode")) {
|
|
4555
|
-
instructions += buildCodeModeInstructions(groups);
|
|
4556
|
-
}
|
|
4557
|
-
if (level === "standard" || level === "full") {
|
|
4558
|
-
if (groups.has("github")) {
|
|
4559
|
-
instructions += GITHUB_INSTRUCTIONS;
|
|
4560
|
-
}
|
|
4561
|
-
instructions += HELP_POINTERS;
|
|
4562
|
-
}
|
|
4563
|
-
if (level === "full") {
|
|
4564
|
-
instructions += SERVER_ACCESS_INSTRUCTIONS;
|
|
4565
|
-
const activeGroups = getActiveToolGroups(enabledTools);
|
|
4566
|
-
if (activeGroups.length > 0) {
|
|
4567
|
-
instructions += `
|
|
4568
|
-
## Active Tools (${String(enabledTools.size)})
|
|
4569
|
-
`;
|
|
4570
|
-
for (const { group, tools } of activeGroups) {
|
|
4571
|
-
instructions += `**${group}**: ${tools.map((t) => `\`${t}\``).join(", ")}
|
|
4572
|
-
`;
|
|
4573
|
-
}
|
|
4574
|
-
}
|
|
4575
|
-
if (prompts.length > 0) {
|
|
4576
|
-
instructions += `
|
|
4577
|
-
## Prompts (${String(prompts.length)})
|
|
4578
|
-
`;
|
|
4579
|
-
instructions += "Pre-built templates and guided workflows:\n";
|
|
4580
|
-
for (const prompt of prompts) {
|
|
4581
|
-
instructions += `- \`${prompt.name}\` - ${prompt.description ?? ""}
|
|
4582
|
-
`;
|
|
4583
|
-
}
|
|
4584
|
-
}
|
|
4585
|
-
}
|
|
4586
|
-
return instructions;
|
|
4587
|
-
}
|
|
4588
|
-
function getActiveToolGroups(enabledTools) {
|
|
4589
|
-
const activeGroups = [];
|
|
4590
|
-
for (const [group, allTools] of Object.entries(TOOL_GROUPS)) {
|
|
4591
|
-
const enabledInGroup = allTools.filter((tool) => enabledTools.has(tool));
|
|
4592
|
-
if (enabledInGroup.length > 0) {
|
|
4593
|
-
activeGroups.push({ group, tools: enabledInGroup });
|
|
4594
|
-
}
|
|
4595
|
-
}
|
|
4596
|
-
return activeGroups;
|
|
4597
|
-
}
|
|
4598
|
-
CORE_INSTRUCTIONS + COPILOT_REVIEW_INSTRUCTIONS + buildQuickAccess(new Set(Object.keys(TOOL_GROUPS))) + buildCodeModeInstructions(new Set(Object.keys(TOOL_GROUPS))) + GITHUB_INSTRUCTIONS;
|
|
4599
|
-
|
|
4600
|
-
// src/handlers/prompts/workflow.ts
|
|
4601
|
-
var MS_PER_DAY2 = 864e5;
|
|
4602
|
-
function getWorkflowPromptDefinitions() {
|
|
4603
|
-
return [
|
|
4604
|
-
{
|
|
4605
|
-
name: "find-related",
|
|
4606
|
-
description: "Discover connected entries via semantic similarity",
|
|
4607
|
-
icons: [ICON_PROMPT],
|
|
4608
|
-
arguments: [
|
|
4609
|
-
{
|
|
4610
|
-
name: "query",
|
|
4611
|
-
description: "Search query for finding related entries",
|
|
4612
|
-
required: true
|
|
4613
|
-
}
|
|
4614
|
-
],
|
|
4615
|
-
handler: (args, db) => {
|
|
4616
|
-
const query = args["query"] ?? "";
|
|
4617
|
-
const entries = db.searchEntries(query, { limit: 5 });
|
|
4618
|
-
return {
|
|
4619
|
-
messages: [
|
|
4620
|
-
{
|
|
4621
|
-
role: "user",
|
|
4622
|
-
content: {
|
|
4623
|
-
type: "text",
|
|
4624
|
-
text: `Find entries related to: "${query}"
|
|
4625
|
-
|
|
4626
|
-
Recent matching entries:
|
|
4627
|
-
${markUntrustedContent(entries.map((e) => `- [${String(e.id)}] ${e.content.slice(0, 100)}...`).join("\n"))}`
|
|
4628
|
-
}
|
|
4629
|
-
}
|
|
4630
|
-
]
|
|
4631
|
-
};
|
|
4632
|
-
}
|
|
4633
|
-
},
|
|
4634
|
-
{
|
|
4635
|
-
name: "prepare-standup",
|
|
4636
|
-
description: "Daily standup summaries",
|
|
4637
|
-
icons: [ICON_PROMPT],
|
|
4638
|
-
arguments: [],
|
|
4639
|
-
handler: (_args, db) => {
|
|
4640
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
|
|
4641
|
-
const yesterday = new Date(Date.now() - MS_PER_DAY2).toISOString().split("T")[0] ?? "";
|
|
4642
|
-
const entries = db.searchByDateRange(yesterday, today);
|
|
4643
|
-
const digestSignal = buildDigestSignalForPrompt(db);
|
|
4644
|
-
return {
|
|
4645
|
-
messages: [
|
|
4646
|
-
{
|
|
4647
|
-
role: "user",
|
|
4648
|
-
content: {
|
|
4649
|
-
type: "text",
|
|
4650
|
-
text: `${digestSignal}Prepare a standup summary based on these recent entries.
|
|
4651
|
-
Format as:
|
|
4652
|
-
- Yesterday: <summary>
|
|
4653
|
-
- Today: <planned work>
|
|
4654
|
-
- Blockers: <any blockers>
|
|
4655
|
-
|
|
4656
|
-
Sources:
|
|
4657
|
-
${markUntrustedContent(entries.map((e) => `[${e.timestamp}] ${e.entryType}: ${e.content}`).join("\n\n"))}`
|
|
4658
|
-
}
|
|
4659
|
-
}
|
|
4660
|
-
]
|
|
4661
|
-
};
|
|
4662
|
-
}
|
|
4663
|
-
},
|
|
4664
|
-
{
|
|
4665
|
-
name: "prepare-retro",
|
|
4666
|
-
description: "Sprint retrospectives",
|
|
4667
|
-
icons: [ICON_PROMPT],
|
|
4668
|
-
arguments: [
|
|
4669
|
-
{
|
|
4670
|
-
name: "days",
|
|
4671
|
-
description: "Number of days to include (default: 14)",
|
|
4672
|
-
required: false
|
|
4673
|
-
}
|
|
4674
|
-
],
|
|
4675
|
-
handler: (args, db) => {
|
|
4676
|
-
const days = parseInt(args["days"] ?? "14", 10);
|
|
4677
|
-
const endDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
|
|
4678
|
-
const startDate = new Date(Date.now() - days * MS_PER_DAY2).toISOString().split("T")[0] ?? "";
|
|
4679
|
-
const entries = db.searchByDateRange(startDate, endDate);
|
|
4680
|
-
const digestSignal = buildDigestSignalForPrompt(db);
|
|
4681
|
-
return {
|
|
4682
|
-
messages: [
|
|
4683
|
-
{
|
|
4684
|
-
role: "user",
|
|
4685
|
-
content: {
|
|
4686
|
-
type: "text",
|
|
4687
|
-
text: `${digestSignal}Prepare a retrospective for the last ${String(days)} days based on these entries.
|
|
4688
|
-
Format as:
|
|
4689
|
-
- What went well
|
|
4690
|
-
- What could improve
|
|
4691
|
-
- Action items
|
|
4692
|
-
|
|
4693
|
-
Sources:
|
|
4694
|
-
${markUntrustedContent(
|
|
4695
|
-
entries.slice(0, 20).map((e) => `[${e.timestamp}] ${e.entryType}: ${e.content.slice(0, 200)}`).join("\n\n")
|
|
4696
|
-
)}`
|
|
4697
|
-
}
|
|
4698
|
-
}
|
|
4699
|
-
]
|
|
4700
|
-
};
|
|
4701
|
-
}
|
|
4702
|
-
},
|
|
4703
|
-
{
|
|
4704
|
-
name: "weekly-digest",
|
|
4705
|
-
description: "Day-by-day weekly summaries",
|
|
4706
|
-
icons: [ICON_PROMPT],
|
|
4707
|
-
arguments: [],
|
|
4708
|
-
handler: (_args, db) => {
|
|
4709
|
-
const endDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
|
|
4710
|
-
const startDate = new Date(Date.now() - 7 * MS_PER_DAY2).toISOString().split("T")[0] ?? "";
|
|
4711
|
-
const entries = db.searchByDateRange(startDate, endDate);
|
|
4712
|
-
return {
|
|
4713
|
-
messages: [
|
|
4714
|
-
{
|
|
4715
|
-
role: "user",
|
|
4716
|
-
content: {
|
|
4717
|
-
type: "text",
|
|
4718
|
-
text: `Create a weekly digest from these entries.
|
|
4719
|
-
Format as day-by-day summary with highlights.
|
|
4720
|
-
|
|
4721
|
-
Sources:
|
|
4722
|
-
${markUntrustedContent(entries.map((e) => `[${e.timestamp}] ${e.entryType}: ${e.content.slice(0, 150)}`).join("\n\n"))}`
|
|
4723
|
-
}
|
|
4724
|
-
}
|
|
4725
|
-
]
|
|
4726
|
-
};
|
|
4727
|
-
}
|
|
4728
|
-
},
|
|
4729
|
-
{
|
|
4730
|
-
name: "analyze-period",
|
|
4731
|
-
description: "Deep period analysis with insights",
|
|
4732
|
-
icons: [ICON_PROMPT],
|
|
4733
|
-
arguments: [
|
|
4734
|
-
{ name: "start_date", description: "Start date (YYYY-MM-DD)", required: true },
|
|
4735
|
-
{ name: "end_date", description: "End date (YYYY-MM-DD)", required: true }
|
|
4736
|
-
],
|
|
4737
|
-
handler: (args, db) => {
|
|
4738
|
-
const startDate = args["start_date"] ?? "";
|
|
4739
|
-
const endDate = args["end_date"] ?? "";
|
|
4740
|
-
const entries = db.searchByDateRange(startDate, endDate);
|
|
4741
|
-
const stats = db.getStatistics("day");
|
|
4742
|
-
return {
|
|
4743
|
-
messages: [
|
|
4744
|
-
{
|
|
4745
|
-
role: "user",
|
|
4746
|
-
content: {
|
|
4747
|
-
type: "text",
|
|
4748
|
-
text: `Analyze the period ${startDate} to ${endDate}:
|
|
4749
|
-
|
|
4750
|
-
Statistics: ${JSON.stringify(stats, null, 2)}
|
|
4751
|
-
|
|
4752
|
-
Provide insights on patterns, productivity, and recommendations.
|
|
4753
|
-
|
|
4754
|
-
Sources (${String(entries.length)} total):
|
|
4755
|
-
${markUntrustedContent(
|
|
4756
|
-
entries.slice(0, 15).map((e) => `[${e.timestamp}] ${e.entryType}: ${e.content.slice(0, 100)}`).join("\n")
|
|
4757
|
-
)}`
|
|
4758
|
-
}
|
|
4759
|
-
}
|
|
4760
|
-
]
|
|
4761
|
-
};
|
|
4762
|
-
}
|
|
4763
|
-
},
|
|
4764
|
-
{
|
|
4765
|
-
name: "goal-tracker",
|
|
4766
|
-
description: "Milestone and achievement tracking",
|
|
4767
|
-
icons: [ICON_PROMPT],
|
|
4768
|
-
arguments: [],
|
|
4769
|
-
handler: (_args, db) => {
|
|
4770
|
-
const entries = db.getSignificantEntries(20);
|
|
4771
|
-
const mappedEntries = entries.map((e) => ({
|
|
4772
|
-
id: e.id,
|
|
4773
|
-
type: e.entryType,
|
|
4774
|
-
timestamp: e.timestamp,
|
|
4775
|
-
content: markUntrustedContentInline(
|
|
4776
|
-
e.content.length > 250 ? e.content.slice(0, 250) + "..." : e.content
|
|
4777
|
-
)
|
|
4778
|
-
}));
|
|
4779
|
-
return {
|
|
4780
|
-
messages: [
|
|
4781
|
-
{
|
|
4782
|
-
role: "user",
|
|
4783
|
-
content: {
|
|
4784
|
-
type: "text",
|
|
4785
|
-
text: `Track goals and milestones based on significant entries.
|
|
4786
|
-
Summarize progress toward goals and highlight achievements.
|
|
4787
|
-
|
|
4788
|
-
Sources:
|
|
4789
|
-
${JSON.stringify(mappedEntries, null, 2)}`
|
|
4790
|
-
}
|
|
4791
|
-
}
|
|
4792
|
-
]
|
|
4793
|
-
};
|
|
4794
|
-
}
|
|
4795
|
-
},
|
|
4796
|
-
{
|
|
4797
|
-
name: "get-context-bundle",
|
|
4798
|
-
description: "Project context with recent entries, statistics, and GitHub status hints",
|
|
4799
|
-
icons: [ICON_PROMPT],
|
|
4800
|
-
arguments: [],
|
|
4801
|
-
handler: (_args, db) => {
|
|
4802
|
-
const recent = db.getRecentEntries(5);
|
|
4803
|
-
const stats = db.getStatistics("week");
|
|
4804
|
-
const entrySummaries = recent.map((e) => ({
|
|
4805
|
-
id: e.id,
|
|
4806
|
-
type: e.entryType,
|
|
4807
|
-
timestamp: e.timestamp,
|
|
4808
|
-
preview: e.content.slice(0, 60) + (e.content.length > 60 ? "..." : "")
|
|
4809
|
-
}));
|
|
4810
|
-
return {
|
|
4811
|
-
messages: [
|
|
4812
|
-
{
|
|
4813
|
-
role: "user",
|
|
4814
|
-
content: {
|
|
4815
|
-
type: "text",
|
|
4816
|
-
text: `Project context bundle:
|
|
4817
|
-
|
|
4818
|
-
**Statistics:** ${JSON.stringify(stats)}
|
|
4819
|
-
|
|
4820
|
-
**For full GitHub status:** Fetch \`memory://github/status\`
|
|
4821
|
-
**For full entry details:** Use \`get_entry_by_id\` with entry ID
|
|
4822
|
-
|
|
4823
|
-
**Recent Entries (${String(recent.length)}):**
|
|
4824
|
-
${markUntrustedContent(entrySummaries.map((e) => `- #${String(e.id)} (${e.type}) ${e.preview}`).join("\n"))}`
|
|
4825
|
-
}
|
|
4826
|
-
}
|
|
4827
|
-
]
|
|
4828
|
-
};
|
|
4829
|
-
}
|
|
4830
|
-
},
|
|
4831
|
-
{
|
|
4832
|
-
name: "get-recent-entries",
|
|
4833
|
-
description: "Formatted recent entries",
|
|
4834
|
-
icons: [ICON_PROMPT],
|
|
4835
|
-
arguments: [
|
|
4836
|
-
{ name: "limit", description: "Number of entries (default: 10)", required: false }
|
|
4837
|
-
],
|
|
4838
|
-
handler: (args, db) => {
|
|
4839
|
-
const limit = parseInt(args["limit"] ?? "10", 10);
|
|
4840
|
-
const entries = db.getRecentEntries(limit);
|
|
4841
|
-
return {
|
|
4842
|
-
messages: [
|
|
4843
|
-
{
|
|
4844
|
-
role: "user",
|
|
4845
|
-
content: {
|
|
4846
|
-
type: "text",
|
|
4847
|
-
text: `Recent ${String(limit)} entries:
|
|
4848
|
-
|
|
4849
|
-
${markUntrustedContent(entries.map((e) => `## ${e.timestamp} (${e.entryType})
|
|
4850
|
-
|
|
4851
|
-
${e.content}
|
|
4852
|
-
|
|
4853
|
-
Tags: ${e.tags.join(", ") || "none"}`).join("\n\n---\n\n"))}`
|
|
4854
|
-
}
|
|
4855
|
-
}
|
|
4856
|
-
]
|
|
4857
|
-
};
|
|
4858
|
-
}
|
|
4859
|
-
},
|
|
4860
|
-
{
|
|
4861
|
-
name: "confirm-briefing",
|
|
4862
|
-
description: "Acknowledge session context received from memory://briefing to inform the user",
|
|
4863
|
-
icons: [ICON_PROMPT],
|
|
4864
|
-
arguments: [],
|
|
4865
|
-
handler: (_args, db) => {
|
|
4866
|
-
const recent = db.getRecentEntries(3);
|
|
4867
|
-
const stats = db.getStatistics("week");
|
|
4868
|
-
const totalEntries = stats.totalEntries ?? 0;
|
|
4869
|
-
const entrySummary = recent.length > 0 ? recent.map(
|
|
4870
|
-
(e) => ` - #${String(e.id)} (${e.entryType}) ${e.content.slice(0, 40)}...`
|
|
4871
|
-
).join("\n") : " - No entries yet";
|
|
4872
|
-
return {
|
|
4873
|
-
messages: [
|
|
4874
|
-
{
|
|
4875
|
-
role: "user",
|
|
4876
|
-
content: {
|
|
4877
|
-
type: "text",
|
|
4878
|
-
text: `Generate a briefing acknowledgment for the user with this context:
|
|
4879
|
-
|
|
4880
|
-
**Session Context Received:**
|
|
4881
|
-
- **Journal**: ${String(totalEntries)} total entries
|
|
4882
|
-
- **Latest Entries**:
|
|
4883
|
-
${markUntrustedContent(entrySummary)}
|
|
4884
|
-
|
|
4885
|
-
**My Behaviors:**
|
|
4886
|
-
- Create entries for: implementations, decisions, bug fixes, milestones
|
|
4887
|
-
- Search before: major decisions, referencing prior work
|
|
4888
|
-
- Link entries: implementation\u2192spec, bugfix\u2192issue
|
|
4889
|
-
|
|
4890
|
-
**For More Context:**
|
|
4891
|
-
- Full entries: \`memory://recent\` or \`get_entry_by_id(ID)\`
|
|
4892
|
-
- GitHub status: \`memory://github/status\`
|
|
4893
|
-
- Repo insights: \`memory://github/insights\` (stars, traffic, clones)
|
|
4894
|
-
- Full health: \`memory://health\`
|
|
4895
|
-
|
|
4896
|
-
Please confirm this context to the user in a concise, friendly format. Use a table if helpful.`
|
|
4897
|
-
}
|
|
4898
|
-
}
|
|
4899
|
-
]
|
|
4900
|
-
};
|
|
4901
|
-
}
|
|
4902
|
-
},
|
|
4903
|
-
{
|
|
4904
|
-
name: "session-summary",
|
|
4905
|
-
description: "Create a session summary entry capturing what was accomplished, pending items, and context for the next session",
|
|
4906
|
-
icons: [ICON_PROMPT],
|
|
4907
|
-
arguments: [],
|
|
4908
|
-
handler: (_args, db) => {
|
|
4909
|
-
const recent = db.getRecentEntries(5);
|
|
4910
|
-
const entrySummary = recent.length > 0 ? recent.map(
|
|
4911
|
-
(e) => `- #${String(e.id)} (${e.entryType}) ${e.content.slice(0, 80)}${e.content.length > 80 ? "..." : ""}`
|
|
4912
|
-
).join("\n") : "- No entries yet";
|
|
4913
|
-
return {
|
|
4914
|
-
messages: [
|
|
4915
|
-
{
|
|
4916
|
-
role: "user",
|
|
4917
|
-
content: {
|
|
4918
|
-
type: "text",
|
|
4919
|
-
text: `Create a session summary journal entry based on this context:
|
|
4920
|
-
|
|
4921
|
-
**Instructions:**
|
|
4922
|
-
1. Summarize what was accomplished in this session (key changes, decisions, files modified)
|
|
4923
|
-
2. Note what's unfinished or blocked (pending items, open questions)
|
|
4924
|
-
3. Include context for the next session (relevant entry IDs, branch names, PR numbers)
|
|
4925
|
-
4. Use \`entry_type: "retrospective"\` and tag with \`session-summary\`
|
|
4926
|
-
|
|
4927
|
-
**Recent Entries:**
|
|
4928
|
-
${markUntrustedContent(entrySummary)}`
|
|
4929
|
-
}
|
|
4930
|
-
}
|
|
4931
|
-
]
|
|
4932
|
-
};
|
|
4933
|
-
}
|
|
4934
|
-
},
|
|
4935
|
-
{
|
|
4936
|
-
name: "team-session-summary",
|
|
4937
|
-
description: "Create a session summary entry for the team capturing what was accomplished, pending items, and context for the next team session",
|
|
4938
|
-
icons: [ICON_PROMPT],
|
|
4939
|
-
arguments: [],
|
|
4940
|
-
handler: (_args, _db, teamDb) => {
|
|
4941
|
-
if (!teamDb) {
|
|
4942
|
-
throw new ConfigurationError("Team database not configured");
|
|
4943
|
-
}
|
|
4944
|
-
const recent = teamDb.getRecentEntries(5);
|
|
4945
|
-
const entrySummary = recent.length > 0 ? recent.map(
|
|
4946
|
-
(e) => `- #${String(e.id)} (${e.entryType}) ${e.content.slice(0, 80)}${e.content.length > 80 ? "..." : ""}`
|
|
4947
|
-
).join("\n") : "- No entries yet";
|
|
4948
|
-
return {
|
|
4949
|
-
messages: [
|
|
4950
|
-
{
|
|
4951
|
-
role: "user",
|
|
4952
|
-
content: {
|
|
4953
|
-
type: "text",
|
|
4954
|
-
text: `Create a team session summary journal entry based on this context:
|
|
4955
|
-
|
|
4956
|
-
**Instructions:**
|
|
4957
|
-
1. Summarize what the team accomplished in this session (key changes, decisions, files modified)
|
|
4958
|
-
2. Note what's unfinished or blocked for the team (pending items, open questions)
|
|
4959
|
-
3. Include context for the next team session (relevant entry IDs, branch names, PR numbers)
|
|
4960
|
-
4. Use \`entry_type: "retrospective"\` and tag with \`session-summary\`
|
|
4961
|
-
5. YOU MUST USE \`team_create_entry\` OR \`mj.team.create\` TO SAVE THIS ENTRY.
|
|
4962
|
-
|
|
4963
|
-
**Recent Team Entries:**
|
|
4964
|
-
${markUntrustedContent(entrySummary)}`
|
|
4965
|
-
}
|
|
4966
|
-
}
|
|
4967
|
-
]
|
|
4968
|
-
};
|
|
4969
|
-
}
|
|
4970
|
-
}
|
|
4971
|
-
];
|
|
4972
|
-
}
|
|
4973
|
-
function buildDigestSignalForPrompt(db) {
|
|
4974
|
-
const snapshot = db.getLatestAnalyticsSnapshot?.("digest");
|
|
4975
|
-
if (!snapshot) return "";
|
|
4976
|
-
const data = snapshot.data;
|
|
4977
|
-
const lines = ["[Analytics Context]"];
|
|
4978
|
-
const growth = data["activityGrowthPercent"];
|
|
4979
|
-
const currentEntries = data["currentPeriodEntries"];
|
|
4980
|
-
if (growth !== null && growth !== void 0) {
|
|
4981
|
-
const sign = growth >= 0 ? "+" : "";
|
|
4982
|
-
lines.push(
|
|
4983
|
-
`Activity: ${sign}${String(growth)}% vs. last period (${String(currentEntries)} entries)`
|
|
4984
|
-
);
|
|
4985
|
-
}
|
|
4986
|
-
const sigMultiplier = data["significanceMultiplier"];
|
|
4987
|
-
const sigCount = data["currentPeriodSignificant"];
|
|
4988
|
-
if (sigMultiplier !== null && sigMultiplier !== void 0 && sigMultiplier > 1.5) {
|
|
4989
|
-
lines.push(
|
|
4990
|
-
`Significance: ${String(sigCount)} significant entries (${String(sigMultiplier)}\xD7 historical avg)`
|
|
4991
|
-
);
|
|
4992
|
-
}
|
|
4993
|
-
const stale = data["staleProjects"];
|
|
4994
|
-
if (stale && stale.length > 0) {
|
|
4995
|
-
const staleStr = stale.map((p) => `P${String(p.projectNumber)} (${String(p.daysSilent)}d silent)`).join(", ");
|
|
4996
|
-
lines.push(`\u26A0 Stale: ${staleStr}`);
|
|
4997
|
-
}
|
|
4998
|
-
if (lines.length <= 1) return "";
|
|
4999
|
-
return lines.join("\\n") + "\\n\\n";
|
|
5000
|
-
}
|
|
5001
|
-
|
|
5002
|
-
// src/handlers/prompts/github.ts
|
|
5003
|
-
function formatPromptEntries(entries, maxCount = 50) {
|
|
5004
|
-
return entries.slice(0, maxCount).map(
|
|
5005
|
-
(e) => ({
|
|
5006
|
-
id: e.id,
|
|
5007
|
-
type: e.entryType,
|
|
5008
|
-
timestamp: e.timestamp,
|
|
5009
|
-
content: markUntrustedContentInline(
|
|
5010
|
-
e.content.length > 250 ? e.content.slice(0, 250) + "..." : e.content
|
|
5011
|
-
)
|
|
5012
|
-
})
|
|
5013
|
-
);
|
|
5014
|
-
}
|
|
5015
|
-
function getGitHubPromptDefinitions() {
|
|
5016
|
-
return [
|
|
5017
|
-
{
|
|
5018
|
-
name: "project-status-summary",
|
|
5019
|
-
description: "GitHub Project status reports",
|
|
5020
|
-
icons: [ICON_PROMPT],
|
|
5021
|
-
arguments: [
|
|
5022
|
-
{ name: "project_number", description: "GitHub Project number", required: true }
|
|
5023
|
-
],
|
|
5024
|
-
handler: (args, db) => {
|
|
5025
|
-
const projectNumber = parseInt(args["project_number"] ?? "0", 10);
|
|
5026
|
-
const entries = db.searchEntries("", { projectNumber, limit: 20 });
|
|
5027
|
-
return {
|
|
5028
|
-
messages: [
|
|
5029
|
-
{
|
|
5030
|
-
role: "user",
|
|
5031
|
-
content: {
|
|
5032
|
-
type: "text",
|
|
5033
|
-
text: `Generate a status summary for Project #${String(projectNumber)}.
|
|
5034
|
-
Provide: overview, recent activity, blockers, next steps.
|
|
5035
|
-
|
|
5036
|
-
Sources:
|
|
5037
|
-
${JSON.stringify(formatPromptEntries(entries), null, 2)}`
|
|
5038
|
-
}
|
|
5039
|
-
}
|
|
5040
|
-
]
|
|
5041
|
-
};
|
|
5042
|
-
}
|
|
5043
|
-
},
|
|
5044
|
-
{
|
|
5045
|
-
name: "pr-summary",
|
|
5046
|
-
description: "Pull request journal activity summary",
|
|
5047
|
-
icons: [ICON_PROMPT],
|
|
5048
|
-
arguments: [{ name: "pr_number", description: "Pull request number", required: true }],
|
|
5049
|
-
handler: (args, db) => {
|
|
5050
|
-
const prNumber = parseInt(args["pr_number"] ?? "0", 10);
|
|
5051
|
-
const entries = db.searchEntries("", { prNumber, limit: 100 }).reverse();
|
|
5052
|
-
return {
|
|
5053
|
-
messages: [
|
|
5054
|
-
{
|
|
5055
|
-
role: "user",
|
|
5056
|
-
content: {
|
|
5057
|
-
type: "text",
|
|
5058
|
-
text: `Summarize PR #${String(prNumber)} activity.
|
|
5059
|
-
Provide: summary of changes, decisions made, testing done.
|
|
5060
|
-
|
|
5061
|
-
Sources:
|
|
5062
|
-
${JSON.stringify(formatPromptEntries(entries), null, 2)}`
|
|
5063
|
-
}
|
|
5064
|
-
}
|
|
5065
|
-
]
|
|
5066
|
-
};
|
|
5067
|
-
}
|
|
5068
|
-
},
|
|
5069
|
-
{
|
|
5070
|
-
name: "code-review-prep",
|
|
5071
|
-
description: "Comprehensive PR review preparation",
|
|
5072
|
-
icons: [ICON_PROMPT],
|
|
5073
|
-
arguments: [{ name: "pr_number", description: "Pull request number", required: true }],
|
|
5074
|
-
handler: (args, db) => {
|
|
5075
|
-
const prNumber = parseInt(args["pr_number"] ?? "0", 10);
|
|
5076
|
-
const entries = db.searchEntries("", { prNumber, limit: 100 }).reverse();
|
|
5077
|
-
return {
|
|
5078
|
-
messages: [
|
|
5079
|
-
{
|
|
5080
|
-
role: "user",
|
|
5081
|
-
content: {
|
|
5082
|
-
type: "text",
|
|
5083
|
-
text: `Prepare for code review of PR #${String(prNumber)}.
|
|
5084
|
-
Provide: review checklist, areas of concern, testing recommendations.
|
|
5085
|
-
|
|
5086
|
-
Sources:
|
|
5087
|
-
${JSON.stringify(formatPromptEntries(entries), null, 2)}`
|
|
5088
|
-
}
|
|
5089
|
-
}
|
|
5090
|
-
]
|
|
5091
|
-
};
|
|
5092
|
-
}
|
|
5093
|
-
},
|
|
5094
|
-
{
|
|
5095
|
-
name: "pr-retrospective",
|
|
5096
|
-
description: "Completed PR analysis with learnings",
|
|
5097
|
-
icons: [ICON_PROMPT],
|
|
5098
|
-
arguments: [{ name: "pr_number", description: "Pull request number", required: true }],
|
|
5099
|
-
handler: (args, db) => {
|
|
5100
|
-
const prNumber = parseInt(args["pr_number"] ?? "0", 10);
|
|
5101
|
-
const entries = db.searchEntries("", { prNumber, limit: 100 }).reverse();
|
|
5102
|
-
return {
|
|
5103
|
-
messages: [
|
|
5104
|
-
{
|
|
5105
|
-
role: "user",
|
|
5106
|
-
content: {
|
|
5107
|
-
type: "text",
|
|
5108
|
-
text: `Retrospective for PR #${String(prNumber)}.
|
|
5109
|
-
Provide: what went well, challenges, lessons learned.
|
|
5110
|
-
|
|
5111
|
-
Sources:
|
|
5112
|
-
${JSON.stringify(formatPromptEntries(entries), null, 2)}`
|
|
5113
|
-
}
|
|
5114
|
-
}
|
|
5115
|
-
]
|
|
5116
|
-
};
|
|
5117
|
-
}
|
|
5118
|
-
},
|
|
5119
|
-
{
|
|
5120
|
-
name: "actions-failure-digest",
|
|
5121
|
-
description: "CI/CD failure analysis with root cause identification",
|
|
5122
|
-
icons: [ICON_PROMPT],
|
|
5123
|
-
arguments: [],
|
|
5124
|
-
handler: (_args, db) => {
|
|
5125
|
-
const entries = db.getWorkflowActionEntries(20);
|
|
5126
|
-
return {
|
|
5127
|
-
messages: [
|
|
5128
|
-
{
|
|
5129
|
-
role: "user",
|
|
5130
|
-
content: {
|
|
5131
|
-
type: "text",
|
|
5132
|
-
text: `Analyze CI/CD failures from these workflow entries.
|
|
5133
|
-
Provide: failure patterns, root causes, remediation steps.
|
|
5134
|
-
|
|
5135
|
-
Sources:
|
|
5136
|
-
${JSON.stringify(formatPromptEntries(entries), null, 2)}`
|
|
5137
|
-
}
|
|
5138
|
-
}
|
|
5139
|
-
]
|
|
5140
|
-
};
|
|
5141
|
-
}
|
|
5142
|
-
},
|
|
5143
|
-
{
|
|
5144
|
-
name: "project-milestone-tracker",
|
|
5145
|
-
description: "Milestone progress tracking",
|
|
5146
|
-
icons: [ICON_PROMPT],
|
|
5147
|
-
arguments: [
|
|
5148
|
-
{ name: "project_number", description: "GitHub Project number", required: true }
|
|
5149
|
-
],
|
|
5150
|
-
handler: (args, db) => {
|
|
5151
|
-
const projectNumber = parseInt(args["project_number"] ?? "0", 10);
|
|
5152
|
-
const entries = db.getSignificantEntries(100, projectNumber);
|
|
5153
|
-
return {
|
|
5154
|
-
messages: [
|
|
5155
|
-
{
|
|
5156
|
-
role: "user",
|
|
5157
|
-
content: {
|
|
5158
|
-
type: "text",
|
|
5159
|
-
text: `Track milestones for Project #${String(projectNumber)}.
|
|
5160
|
-
Provide: progress summary, upcoming milestones, timeline.
|
|
5161
|
-
|
|
5162
|
-
Sources:
|
|
5163
|
-
${JSON.stringify(formatPromptEntries(entries), null, 2)}`
|
|
5164
|
-
}
|
|
5165
|
-
}
|
|
5166
|
-
]
|
|
5167
|
-
};
|
|
5168
|
-
}
|
|
5169
|
-
}
|
|
5170
|
-
];
|
|
5171
|
-
}
|
|
5172
|
-
|
|
5173
|
-
// src/handlers/prompts/index.ts
|
|
5174
|
-
function getPrompts() {
|
|
5175
|
-
const prompts = getAllPromptDefinitions();
|
|
5176
|
-
return prompts.map((p) => ({
|
|
5177
|
-
name: p.name,
|
|
5178
|
-
description: p.description,
|
|
5179
|
-
arguments: p.arguments,
|
|
5180
|
-
icons: p.icons
|
|
5181
|
-
}));
|
|
5182
|
-
}
|
|
5183
|
-
function getPrompt(name, args, db, teamDb) {
|
|
5184
|
-
const prompts = getAllPromptDefinitions();
|
|
5185
|
-
const prompt = prompts.find((p) => p.name === name);
|
|
5186
|
-
if (!prompt) {
|
|
5187
|
-
throw new ResourceNotFoundError("Prompt", name);
|
|
5188
|
-
}
|
|
5189
|
-
return prompt.handler(args, db, teamDb);
|
|
5190
|
-
}
|
|
5191
|
-
function getAllPromptDefinitions() {
|
|
5192
|
-
return [...getWorkflowPromptDefinitions(), ...getGitHubPromptDefinitions()];
|
|
5193
|
-
}
|
|
5194
|
-
|
|
5195
|
-
// src/handlers/resources/core/instructions.ts
|
|
5196
|
-
var instructionsResource = {
|
|
5197
|
-
uri: "memory://instructions",
|
|
5198
|
-
name: "Server Instructions",
|
|
5199
|
-
title: "Full Server Behavioral Guidance",
|
|
5200
|
-
description: "Full server instructions for AI agents.",
|
|
5201
|
-
mimeType: "text/markdown",
|
|
5202
|
-
icons: [ICON_BRIEFING],
|
|
5203
|
-
annotations: withPriority(0.95, ASSISTANT_FOCUSED),
|
|
5204
|
-
handler: (_uri, context) => {
|
|
5205
|
-
const level = "full";
|
|
5206
|
-
const allToolNames = new Set(getAllToolNames());
|
|
5207
|
-
const enabledTools = context.filterConfig?.enabledTools ?? allToolNames;
|
|
5208
|
-
const enabledGroups = context.filterConfig ? getEnabledGroups(context.filterConfig.enabledTools) : void 0;
|
|
5209
|
-
const prompts = getPrompts().map((p) => {
|
|
5210
|
-
const prompt = p;
|
|
5211
|
-
return { name: prompt.name, description: prompt.description };
|
|
5212
|
-
});
|
|
5213
|
-
const instructions = generateInstructions(
|
|
5214
|
-
enabledTools,
|
|
5215
|
-
prompts,
|
|
5216
|
-
void 0,
|
|
5217
|
-
level,
|
|
5218
|
-
enabledGroups
|
|
5219
|
-
);
|
|
5220
|
-
return {
|
|
5221
|
-
data: instructions
|
|
5222
|
-
};
|
|
5223
|
-
}
|
|
5224
|
-
};
|
|
5225
|
-
|
|
5226
|
-
// src/handlers/resources/core/health.ts
|
|
5227
|
-
function getTotalToolCount() {
|
|
5228
|
-
return getAllToolNames().length;
|
|
5229
|
-
}
|
|
5230
|
-
var healthResource = {
|
|
5231
|
-
uri: "memory://health",
|
|
5232
|
-
name: "Server Health",
|
|
5233
|
-
title: "Server Health & Diagnostics",
|
|
5234
|
-
description: "Server health status including database, backups, vector index (real-time stats), and tool filter status",
|
|
5235
|
-
mimeType: "application/json",
|
|
5236
|
-
icons: [ICON_HEALTH],
|
|
5237
|
-
annotations: {
|
|
5238
|
-
...HIGH_PRIORITY,
|
|
5239
|
-
audience: ["assistant"]
|
|
5240
|
-
},
|
|
5241
|
-
handler: (_uri, context) => {
|
|
5242
|
-
const dbHealth = context.db.getHealthStatus();
|
|
5243
|
-
let vectorIndex = null;
|
|
5244
|
-
if (context.vectorManager) {
|
|
5245
|
-
try {
|
|
5246
|
-
const stats = context.vectorManager.getStats();
|
|
5247
|
-
vectorIndex = {
|
|
5248
|
-
available: true,
|
|
5249
|
-
itemCount: stats.itemCount,
|
|
5250
|
-
modelName: stats.modelName,
|
|
5251
|
-
isReady: stats.isReady
|
|
5252
|
-
};
|
|
5253
|
-
} catch {
|
|
5254
|
-
vectorIndex = { available: false, itemCount: 0, modelName: null, isReady: false };
|
|
5255
|
-
}
|
|
5256
|
-
}
|
|
5257
|
-
const totalTools = getTotalToolCount();
|
|
5258
|
-
const toolFilter = {
|
|
5259
|
-
active: context.filterConfig !== null && context.filterConfig !== void 0,
|
|
5260
|
-
enabledCount: context.filterConfig?.enabledTools.size ?? totalTools,
|
|
5261
|
-
totalCount: totalTools,
|
|
5262
|
-
filterString: context.filterConfig?.raw ?? null
|
|
5263
|
-
};
|
|
5264
|
-
const lastModified = (/* @__PURE__ */ new Date()).toISOString();
|
|
5265
|
-
const metricsSummary = (() => {
|
|
5266
|
-
try {
|
|
5267
|
-
const s = context.runtime?.metrics.getSummary();
|
|
5268
|
-
if (!s) return null;
|
|
5269
|
-
return {
|
|
5270
|
-
totalCalls: s.totalCalls,
|
|
5271
|
-
totalErrors: s.totalErrors,
|
|
5272
|
-
totalOutputTokens: s.totalOutputTokens,
|
|
5273
|
-
upSince: s.upSince
|
|
5274
|
-
};
|
|
5275
|
-
} catch {
|
|
5276
|
-
return null;
|
|
5277
|
-
}
|
|
5278
|
-
})();
|
|
5279
|
-
return {
|
|
5280
|
-
data: {
|
|
5281
|
-
...dbHealth,
|
|
5282
|
-
vectorIndex,
|
|
5283
|
-
toolFilter,
|
|
5284
|
-
teamDatabase: context.teamDb ? {
|
|
5285
|
-
configured: true,
|
|
5286
|
-
...context.teamDb.getHealthStatus()
|
|
5287
|
-
} : { configured: false },
|
|
5288
|
-
scheduler: context.scheduler ? context.scheduler.getStatus() : { active: false, jobs: [] },
|
|
5289
|
-
audit: context.runtime?.auditLogger ? {
|
|
5290
|
-
droppedCount: context.runtime.auditLogger.droppedCount,
|
|
5291
|
-
status: context.runtime.auditLogger.droppedCount > 0 ? "degraded" : "ok"
|
|
5292
|
-
} : { status: "unknown" },
|
|
5293
|
-
metrics: metricsSummary,
|
|
5294
|
-
timestamp: lastModified
|
|
5295
|
-
},
|
|
5296
|
-
annotations: { lastModified }
|
|
5297
|
-
};
|
|
5298
|
-
}
|
|
5299
|
-
};
|
|
5300
|
-
var recentResource = {
|
|
5301
|
-
uri: "memory://recent",
|
|
5302
|
-
name: "Recent Entries",
|
|
5303
|
-
title: "Recent Journal Entries",
|
|
5304
|
-
description: "10 most recent journal entries",
|
|
5305
|
-
mimeType: "application/json",
|
|
5306
|
-
icons: [ICON_CLOCK],
|
|
5307
|
-
annotations: withPriority(0.8, ASSISTANT_FOCUSED),
|
|
5308
|
-
handler: (_uri, context) => {
|
|
5309
|
-
const entries = context.db.getRecentEntries(10);
|
|
5310
|
-
const lastModified = entries[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5311
|
-
return {
|
|
5312
|
-
data: { entries, count: entries.length },
|
|
5313
|
-
annotations: { lastModified }
|
|
5314
|
-
};
|
|
5315
|
-
}
|
|
5316
|
-
};
|
|
5317
|
-
var significantResource = {
|
|
5318
|
-
uri: "memory://significant",
|
|
5319
|
-
name: "Significant Entries",
|
|
5320
|
-
title: "Significant Milestones",
|
|
5321
|
-
description: "Significant milestones and breakthroughs",
|
|
5322
|
-
mimeType: "application/json",
|
|
5323
|
-
icons: [ICON_STAR],
|
|
5324
|
-
annotations: withPriority(0.7, ASSISTANT_FOCUSED),
|
|
5325
|
-
handler: (_uri, context) => {
|
|
5326
|
-
const entries = context.db.getSignificantEntries(100);
|
|
5327
|
-
const now = Date.now();
|
|
5328
|
-
const MS_PER_DAY3 = 864e5;
|
|
5329
|
-
const RECENCY_WINDOW_DAYS2 = 90;
|
|
5330
|
-
const MAX_REL_SCORE_AT = 5;
|
|
5331
|
-
const MAX_CAUSAL_SCORE_AT2 = 3;
|
|
5332
|
-
const entryIds = entries.map((e) => e.id);
|
|
5333
|
-
const relationshipsMap = context.db.getRelationshipsForEntries(entryIds);
|
|
5334
|
-
const entriesWithImportance = entries.map((entry) => {
|
|
5335
|
-
const relationships = relationshipsMap.get(entry.id) ?? [];
|
|
5336
|
-
const relCount = relationships.length;
|
|
5337
|
-
const causalCount = relationships.filter(
|
|
5338
|
-
(r) => ["blocked_by", "resolved", "caused"].includes(r.relationshipType)
|
|
5339
|
-
).length;
|
|
5340
|
-
const timestampMs = new Date(entry.timestamp).getTime();
|
|
5341
|
-
const daysSince = Math.floor((now - timestampMs) / MS_PER_DAY3);
|
|
5342
|
-
const recency = Math.max(0, 1 - daysSince / RECENCY_WINDOW_DAYS2);
|
|
5343
|
-
const importance = Math.round(
|
|
5344
|
-
(1 * 0.3 + Math.min(relCount / MAX_REL_SCORE_AT, 1) * 0.35 + Math.min(causalCount / MAX_CAUSAL_SCORE_AT2, 1) * 0.2 + recency * 0.15) * 100
|
|
5345
|
-
) / 100;
|
|
5346
|
-
return { ...entry, importance, timestampMs };
|
|
5347
|
-
});
|
|
5348
|
-
entriesWithImportance.sort((a, b) => {
|
|
5349
|
-
if (b.importance !== a.importance) {
|
|
5350
|
-
return b.importance - a.importance;
|
|
5351
|
-
}
|
|
5352
|
-
return b.timestampMs - a.timestampMs;
|
|
5353
|
-
});
|
|
5354
|
-
const top20 = entriesWithImportance.slice(0, 20);
|
|
5355
|
-
return { entries: top20, count: top20.length };
|
|
5356
|
-
}
|
|
5357
|
-
};
|
|
5358
|
-
var tagsResource = {
|
|
5359
|
-
uri: "memory://tags",
|
|
5360
|
-
name: "All Tags",
|
|
5361
|
-
title: "Tag List",
|
|
5362
|
-
description: "All available tags with usage counts",
|
|
5363
|
-
mimeType: "application/json",
|
|
5364
|
-
icons: [ICON_TAG],
|
|
5365
|
-
annotations: { ...LOW_PRIORITY, audience: ["assistant"] },
|
|
5366
|
-
handler: (_uri, context) => {
|
|
5367
|
-
const tags = context.db.listTags();
|
|
5368
|
-
const mappedTags = tags.map((t) => ({
|
|
5369
|
-
id: t.id,
|
|
5370
|
-
name: t.name,
|
|
5371
|
-
count: t.usageCount
|
|
5372
|
-
}));
|
|
5373
|
-
return { tags: mappedTags, count: mappedTags.length };
|
|
5374
|
-
}
|
|
5375
|
-
};
|
|
5376
|
-
var statisticsResource = {
|
|
5377
|
-
uri: "memory://statistics",
|
|
5378
|
-
name: "Statistics",
|
|
5379
|
-
title: "Journal Statistics",
|
|
5380
|
-
description: "Overall journal statistics",
|
|
5381
|
-
mimeType: "application/json",
|
|
5382
|
-
icons: [ICON_ANALYTICS],
|
|
5383
|
-
annotations: { ...LOW_PRIORITY, audience: ["assistant"] },
|
|
5384
|
-
handler: (_uri, context) => {
|
|
5385
|
-
return context.db.getStatistics("week");
|
|
5386
|
-
}
|
|
5387
|
-
};
|
|
5388
|
-
var cachedRulesMap = /* @__PURE__ */ new Map();
|
|
5389
|
-
var RULES_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
5390
|
-
var rulesResource = {
|
|
5391
|
-
uri: "memory://rules",
|
|
5392
|
-
name: "Rules File",
|
|
5393
|
-
title: "Agent Rules & Coding Standards",
|
|
5394
|
-
description: "Contents of the configured RULES_FILE_PATH (agent rules / GEMINI.md)",
|
|
5395
|
-
mimeType: "text/markdown",
|
|
5396
|
-
icons: [ICON_BRIEFING],
|
|
5397
|
-
annotations: withPriority(0.7, ASSISTANT_FOCUSED),
|
|
5398
|
-
handler: async (_uri, context) => {
|
|
5399
|
-
const rulesPath = context.briefingConfig?.rulesFilePath ?? process.env["RULES_FILE_PATH"];
|
|
5400
|
-
if (!rulesPath) {
|
|
5401
|
-
return {
|
|
5402
|
-
data: {
|
|
5403
|
-
configured: false,
|
|
5404
|
-
message: "RULES_FILE_PATH is not configured. Set this env var to serve rules content."
|
|
5405
|
-
}
|
|
5406
|
-
};
|
|
5407
|
-
}
|
|
5408
|
-
try {
|
|
5409
|
-
const allowedRoots = context.briefingConfig?.allowedIoRoots ?? [];
|
|
5410
|
-
const expandedRoots = [...allowedRoots, path4.dirname(rulesPath)];
|
|
5411
|
-
assertSafeFilePath(rulesPath, expandedRoots);
|
|
5412
|
-
} catch (err) {
|
|
5413
|
-
return {
|
|
5414
|
-
data: {
|
|
5415
|
-
configured: true,
|
|
5416
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5417
|
-
}
|
|
5418
|
-
};
|
|
5419
|
-
}
|
|
5420
|
-
try {
|
|
5421
|
-
const cached = cachedRulesMap.get(rulesPath);
|
|
5422
|
-
if (cached && Date.now() - cached.timestamp < RULES_CACHE_TTL_MS) {
|
|
5423
|
-
const stat2 = await fs2.promises.stat(rulesPath).catch(() => ({ mtimeMs: Date.now() }));
|
|
5424
|
-
return {
|
|
5425
|
-
data: cached.content,
|
|
5426
|
-
annotations: {
|
|
5427
|
-
lastModified: new Date(stat2.mtimeMs).toISOString()
|
|
5428
|
-
}
|
|
5429
|
-
};
|
|
5430
|
-
}
|
|
5431
|
-
const file = await fs2.promises.open(rulesPath, "r");
|
|
5432
|
-
try {
|
|
5433
|
-
const stat2 = await file.stat();
|
|
5434
|
-
if (stat2.size > 1024 * 1024) {
|
|
5435
|
-
throw new Error("Rules file exceeds 1MB limit");
|
|
5436
|
-
}
|
|
5437
|
-
const content = await file.readFile("utf8");
|
|
5438
|
-
cachedRulesMap.set(rulesPath, {
|
|
5439
|
-
content,
|
|
5440
|
-
timestamp: Date.now()
|
|
5441
|
-
});
|
|
5442
|
-
if (cachedRulesMap.size > 100) {
|
|
5443
|
-
const firstKey = cachedRulesMap.keys().next().value;
|
|
5444
|
-
if (firstKey) cachedRulesMap.delete(firstKey);
|
|
5445
|
-
}
|
|
5446
|
-
} finally {
|
|
5447
|
-
await file.close();
|
|
5448
|
-
}
|
|
5449
|
-
const cachedData = cachedRulesMap.get(rulesPath);
|
|
5450
|
-
return {
|
|
5451
|
-
data: cachedData ? cachedData.content : "",
|
|
5452
|
-
annotations: {
|
|
5453
|
-
lastModified: new Date(
|
|
5454
|
-
cachedData ? cachedData.timestamp : Date.now()
|
|
5455
|
-
).toISOString()
|
|
5456
|
-
}
|
|
5457
|
-
};
|
|
5458
|
-
} catch (err) {
|
|
5459
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5460
|
-
return {
|
|
5461
|
-
data: {
|
|
5462
|
-
configured: true,
|
|
5463
|
-
error: `Could not read rules file: ${message}`
|
|
5464
|
-
// Removed configuration path disclosure
|
|
5465
|
-
}
|
|
5466
|
-
};
|
|
5467
|
-
}
|
|
5468
|
-
}
|
|
5469
|
-
};
|
|
5470
|
-
var workflowsResource = {
|
|
5471
|
-
uri: "memory://workflows",
|
|
5472
|
-
name: "Workflows",
|
|
5473
|
-
title: "Agent Workflow Summaries",
|
|
5474
|
-
description: "Summary of available agent workflows from the configured workflow directory",
|
|
5475
|
-
mimeType: "application/json",
|
|
5476
|
-
icons: [ICON_BRIEFING],
|
|
5477
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
5478
|
-
handler: (_uri, context) => {
|
|
5479
|
-
const workflowSummary = context.briefingConfig?.workflowSummary ?? process.env["MEMORY_JOURNAL_WORKFLOW_SUMMARY"];
|
|
5480
|
-
if (workflowSummary === void 0 || workflowSummary === "") {
|
|
5481
|
-
return {
|
|
5482
|
-
data: {
|
|
5483
|
-
configured: false,
|
|
5484
|
-
message: "No workflow summary is available. Set MEMORY_JOURNAL_WORKFLOW_SUMMARY env var or use --workflow-summary."
|
|
5485
|
-
}
|
|
5486
|
-
};
|
|
5487
|
-
}
|
|
5488
|
-
return {
|
|
5489
|
-
data: {
|
|
5490
|
-
configured: true,
|
|
5491
|
-
summary: workflowSummary
|
|
5492
|
-
}
|
|
5493
|
-
};
|
|
5494
|
-
}
|
|
5495
|
-
};
|
|
5496
|
-
var cachedSkillsMap = /* @__PURE__ */ new Map();
|
|
5497
|
-
var SKILLS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
5498
|
-
function getShippedSkillsDir() {
|
|
5499
|
-
try {
|
|
5500
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
5501
|
-
let dir = path4.dirname(thisFile);
|
|
5502
|
-
while (true) {
|
|
5503
|
-
const pkgJsonPath = path4.join(dir, "package.json");
|
|
5504
|
-
if (fs2.existsSync(pkgJsonPath)) {
|
|
5505
|
-
const shipped = path4.join(dir, "skills");
|
|
5506
|
-
return fs2.existsSync(shipped) ? shipped : void 0;
|
|
5507
|
-
}
|
|
5508
|
-
const parent = path4.dirname(dir);
|
|
5509
|
-
if (parent === dir) {
|
|
5510
|
-
break;
|
|
5511
|
-
}
|
|
5512
|
-
dir = parent;
|
|
5513
|
-
}
|
|
5514
|
-
return void 0;
|
|
5515
|
-
} catch {
|
|
5516
|
-
return void 0;
|
|
5517
|
-
}
|
|
5518
|
-
}
|
|
5519
|
-
async function scanSkillsDir(dir, source) {
|
|
5520
|
-
if (!fs2.existsSync(dir)) return [];
|
|
5521
|
-
const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
5522
|
-
const skills = [];
|
|
5523
|
-
for (const entry of entries) {
|
|
5524
|
-
if (!entry.isDirectory()) continue;
|
|
5525
|
-
const skillMdPath = path4.join(dir, entry.name, "SKILL.md");
|
|
5526
|
-
if (!fs2.existsSync(skillMdPath)) continue;
|
|
5527
|
-
const content = await fs2.promises.readFile(skillMdPath, "utf8");
|
|
5528
|
-
const lines = content.split("\n");
|
|
5529
|
-
const excerptLine = lines.find(
|
|
5530
|
-
(l) => l.trim().length > 0 && !l.startsWith("#") && !l.startsWith("---")
|
|
5531
|
-
);
|
|
5532
|
-
const excerpt = excerptLine ? markUntrustedContentInline(excerptLine.trim().slice(0, 160)) : "";
|
|
5533
|
-
skills.push({ name: entry.name, path: skillMdPath, excerpt, source });
|
|
5534
|
-
}
|
|
5535
|
-
return skills;
|
|
5536
|
-
}
|
|
5537
|
-
var skillsResource = {
|
|
5538
|
-
uri: "memory://skills",
|
|
5539
|
-
name: "Skills",
|
|
5540
|
-
title: "Agent Skills Index",
|
|
5541
|
-
description: "Index of available agent skills (shipped + user-configured via SKILLS_DIR_PATH)",
|
|
5542
|
-
mimeType: "application/json",
|
|
5543
|
-
icons: [ICON_BRIEFING],
|
|
5544
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
5545
|
-
handler: async (_uri, context) => {
|
|
5546
|
-
const userSkillsDir = context.briefingConfig?.skillsDirPath ?? process.env["SKILLS_DIR_PATH"];
|
|
5547
|
-
const shippedSkillsDir = getShippedSkillsDir();
|
|
5548
|
-
const hasAnySource = !!userSkillsDir || !!shippedSkillsDir;
|
|
5549
|
-
if (userSkillsDir) {
|
|
5550
|
-
try {
|
|
5551
|
-
const allowedRoots = context.briefingConfig?.allowedIoRoots ?? [];
|
|
5552
|
-
const expandedRoots = [...allowedRoots, userSkillsDir];
|
|
5553
|
-
assertSafeDirectoryPath(userSkillsDir, expandedRoots);
|
|
5554
|
-
if (!fs2.existsSync(userSkillsDir)) {
|
|
5555
|
-
throw new Error(`Configured SKILLS_DIR_PATH does not exist: ${userSkillsDir}`);
|
|
5556
|
-
}
|
|
5557
|
-
} catch (err) {
|
|
5558
|
-
return {
|
|
5559
|
-
data: {
|
|
5560
|
-
configured: true,
|
|
5561
|
-
error: err instanceof Error ? err.message : String(err),
|
|
5562
|
-
skills: [],
|
|
5563
|
-
count: 0
|
|
5564
|
-
}
|
|
5565
|
-
};
|
|
5566
|
-
}
|
|
5567
|
-
}
|
|
5568
|
-
if (!hasAnySource) {
|
|
5569
|
-
return {
|
|
5570
|
-
data: {
|
|
5571
|
-
configured: false,
|
|
5572
|
-
message: "No skills available. Set SKILLS_DIR_PATH to index user skills."
|
|
5573
|
-
}
|
|
5574
|
-
};
|
|
5575
|
-
}
|
|
5576
|
-
try {
|
|
5577
|
-
const currentDirs = `${userSkillsDir ?? ""}|${shippedSkillsDir ?? ""}`;
|
|
5578
|
-
const cached = cachedSkillsMap.get(currentDirs);
|
|
5579
|
-
if (cached && Date.now() - cached.timestamp < SKILLS_CACHE_TTL_MS) {
|
|
5580
|
-
return {
|
|
5581
|
-
data: {
|
|
5582
|
-
configured: true,
|
|
5583
|
-
// Prevent path disclosure of host directories
|
|
5584
|
-
skills: cached.skills.map((s) => ({
|
|
5585
|
-
name: s.name,
|
|
5586
|
-
excerpt: s.excerpt,
|
|
5587
|
-
source: s.source
|
|
5588
|
-
})),
|
|
5589
|
-
count: cached.skills.length
|
|
5590
|
-
}
|
|
5591
|
-
};
|
|
5592
|
-
}
|
|
5593
|
-
const skillMap = /* @__PURE__ */ new Map();
|
|
5594
|
-
if (shippedSkillsDir) {
|
|
5595
|
-
for (const skill of await scanSkillsDir(shippedSkillsDir, "shipped")) {
|
|
5596
|
-
skillMap.set(skill.name, skill);
|
|
5597
|
-
}
|
|
5598
|
-
}
|
|
5599
|
-
if (userSkillsDir) {
|
|
5600
|
-
for (const skill of await scanSkillsDir(userSkillsDir, "user")) {
|
|
5601
|
-
skillMap.set(skill.name, skill);
|
|
5602
|
-
}
|
|
5603
|
-
}
|
|
5604
|
-
const skills = [...skillMap.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
5605
|
-
cachedSkillsMap.set(currentDirs, {
|
|
5606
|
-
skills,
|
|
5607
|
-
timestamp: Date.now()
|
|
5608
|
-
});
|
|
5609
|
-
if (cachedSkillsMap.size > 100) {
|
|
5610
|
-
const firstKey = cachedSkillsMap.keys().next().value;
|
|
5611
|
-
if (firstKey) cachedSkillsMap.delete(firstKey);
|
|
5612
|
-
}
|
|
5613
|
-
return {
|
|
5614
|
-
data: {
|
|
5615
|
-
configured: true,
|
|
5616
|
-
// Prevent path disclosure of host directories
|
|
5617
|
-
skills: skills.map((s) => ({
|
|
5618
|
-
name: s.name,
|
|
5619
|
-
excerpt: s.excerpt,
|
|
5620
|
-
source: s.source
|
|
5621
|
-
})),
|
|
5622
|
-
count: skills.length
|
|
5623
|
-
}
|
|
5624
|
-
};
|
|
5625
|
-
} catch (err) {
|
|
5626
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5627
|
-
return {
|
|
5628
|
-
data: {
|
|
5629
|
-
configured: true,
|
|
5630
|
-
error: `Could not scan skills directory: ${message}`,
|
|
5631
|
-
skills: [],
|
|
5632
|
-
count: 0
|
|
5633
|
-
}
|
|
5634
|
-
};
|
|
5635
|
-
}
|
|
5636
|
-
}
|
|
5637
|
-
};
|
|
5638
|
-
|
|
5639
|
-
// src/handlers/resources/core/metrics-resource.ts
|
|
5640
|
-
function nowIso() {
|
|
5641
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5642
|
-
}
|
|
5643
|
-
var metricsSummaryResource = {
|
|
5644
|
-
uri: "memory://metrics/summary",
|
|
5645
|
-
name: "Metrics Summary",
|
|
5646
|
-
title: "Tool Call Metrics Summary",
|
|
5647
|
-
description: "Aggregate metrics across all tool calls since server start. Includes total calls, errors, duration, and token estimates.",
|
|
5648
|
-
mimeType: "text/plain",
|
|
5649
|
-
annotations: {
|
|
5650
|
-
...HIGH_PRIORITY,
|
|
5651
|
-
audience: ["assistant"]
|
|
5652
|
-
},
|
|
5653
|
-
handler: (_uri, ctx) => {
|
|
5654
|
-
const lastModified = nowIso();
|
|
5655
|
-
const s = ctx.runtime?.metrics.getSummary();
|
|
5656
|
-
if (!s) {
|
|
5657
|
-
return {
|
|
5658
|
-
data: "metrics_summary:\n error: Metrics not available\n",
|
|
5659
|
-
annotations: { lastModified }
|
|
5660
|
-
};
|
|
5661
|
-
}
|
|
5662
|
-
const errorRate = s.totalCalls > 0 ? (s.totalErrors / s.totalCalls * 100).toFixed(1) : "0.0";
|
|
5663
|
-
const avgDuration = s.totalCalls > 0 ? Math.round(s.totalDurationMs / s.totalCalls) : 0;
|
|
5664
|
-
const text = `metrics_summary:
|
|
5665
|
-
up_since: ${s.upSince}
|
|
5666
|
-
as_of: ${lastModified}
|
|
5667
|
-
total_calls: ${s.totalCalls}
|
|
5668
|
-
total_errors: ${s.totalErrors}
|
|
5669
|
-
error_rate_pct: ${errorRate}
|
|
5670
|
-
total_duration_ms: ${s.totalDurationMs}
|
|
5671
|
-
avg_duration_ms: ${avgDuration}
|
|
5672
|
-
total_input_tokens: ${s.totalInputTokens}
|
|
5673
|
-
total_output_tokens: ${s.totalOutputTokens}
|
|
5674
|
-
tools_called: ${Object.keys(s.toolBreakdown).length}
|
|
5675
|
-
`;
|
|
5676
|
-
return { data: text, annotations: { lastModified } };
|
|
5677
|
-
}
|
|
5678
|
-
};
|
|
5679
|
-
var metricsTokensResource = {
|
|
5680
|
-
uri: "memory://metrics/tokens",
|
|
5681
|
-
name: "Metrics Tokens",
|
|
5682
|
-
title: "Token Usage Breakdown by Tool",
|
|
5683
|
-
description: "Per-tool token usage breakdown sorted by total output tokens. Use this to identify which tools are consuming the most context window.",
|
|
5684
|
-
mimeType: "text/plain",
|
|
5685
|
-
annotations: {
|
|
5686
|
-
...MEDIUM_PRIORITY,
|
|
5687
|
-
audience: ["assistant"]
|
|
5688
|
-
},
|
|
5689
|
-
handler: (_uri, ctx) => {
|
|
5690
|
-
const lastModified = nowIso();
|
|
5691
|
-
const breakdown = ctx.runtime?.metrics.getTokenBreakdown() ?? [];
|
|
5692
|
-
if (breakdown.length === 0) {
|
|
5693
|
-
return {
|
|
5694
|
-
data: `token_breakdown:
|
|
5695
|
-
note: No tool calls recorded yet.
|
|
5696
|
-
as_of: ${lastModified}
|
|
5697
|
-
`,
|
|
5698
|
-
annotations: { lastModified }
|
|
5699
|
-
};
|
|
5700
|
-
}
|
|
5701
|
-
const rows = breakdown.map(
|
|
5702
|
-
(t) => ` - tool: ${t.toolName}
|
|
5703
|
-
calls: ${t.callCount}
|
|
5704
|
-
input_tokens: ${t.inputTokens}
|
|
5705
|
-
output_tokens: ${t.outputTokens}
|
|
5706
|
-
avg_output_tokens: ${t.avgOutputTokens}`
|
|
5707
|
-
).join("\n");
|
|
5708
|
-
const text = `token_breakdown:
|
|
5709
|
-
as_of: ${lastModified}
|
|
5710
|
-
${rows}
|
|
5711
|
-
`;
|
|
5712
|
-
return { data: text, annotations: { lastModified } };
|
|
5713
|
-
}
|
|
5714
|
-
};
|
|
5715
|
-
var metricsSystemResource = {
|
|
5716
|
-
uri: "memory://metrics/system",
|
|
5717
|
-
name: "Metrics System",
|
|
5718
|
-
title: "System Metrics",
|
|
5719
|
-
description: "Process-level system metrics: memory usage, uptime, Node.js version, and platform.",
|
|
5720
|
-
mimeType: "text/plain",
|
|
5721
|
-
annotations: {
|
|
5722
|
-
...MEDIUM_PRIORITY,
|
|
5723
|
-
audience: ["assistant"]
|
|
5724
|
-
},
|
|
5725
|
-
handler: (_uri, ctx) => {
|
|
5726
|
-
const lastModified = nowIso();
|
|
5727
|
-
const sys = ctx.runtime?.metrics.getSystemMetrics();
|
|
5728
|
-
if (!sys) {
|
|
5729
|
-
return {
|
|
5730
|
-
data: "system_metrics:\n error: Metrics not available\n",
|
|
5731
|
-
annotations: { lastModified }
|
|
5732
|
-
};
|
|
5733
|
-
}
|
|
5734
|
-
const text = `system_metrics:
|
|
5735
|
-
up_since: ${sys.upSince}
|
|
5736
|
-
uptime_seconds: ${sys.uptimeSeconds}
|
|
5737
|
-
process_memory_mb: ${sys.processMemoryMb}
|
|
5738
|
-
node_version: ${sys.nodeVersion}
|
|
5739
|
-
platform: ${sys.platform}
|
|
5740
|
-
as_of: ${lastModified}
|
|
5741
|
-
`;
|
|
5742
|
-
return { data: text, annotations: { lastModified } };
|
|
5743
|
-
}
|
|
5744
|
-
};
|
|
5745
|
-
var metricsUsersResource = {
|
|
5746
|
-
uri: "memory://metrics/users",
|
|
5747
|
-
name: "Metrics Users",
|
|
5748
|
-
title: "Per-User Call Counts",
|
|
5749
|
-
description: "Per-user tool call counts. Populated when user identifiers are provided via OAuth or request metadata. Returns empty breakdown when no user tracking configured.",
|
|
5750
|
-
mimeType: "text/plain",
|
|
5751
|
-
annotations: {
|
|
5752
|
-
...LOW_PRIORITY,
|
|
5753
|
-
audience: ["assistant"]
|
|
5754
|
-
},
|
|
5755
|
-
handler: (_uri, ctx) => {
|
|
5756
|
-
const lastModified = nowIso();
|
|
5757
|
-
const userBreakdown = ctx.runtime?.metrics.getUserBreakdown() ?? {};
|
|
5758
|
-
const users = Object.entries(userBreakdown);
|
|
5759
|
-
if (users.length === 0) {
|
|
5760
|
-
return {
|
|
5761
|
-
data: `user_metrics:
|
|
5762
|
-
note: No user tracking data available.
|
|
5763
|
-
hint: User tracking activates when OAuth user identifiers are present.
|
|
5764
|
-
as_of: ${lastModified}
|
|
5765
|
-
`,
|
|
5766
|
-
annotations: { lastModified }
|
|
5767
|
-
};
|
|
5768
|
-
}
|
|
5769
|
-
const sorted = users.sort(([, a], [, b]) => b - a);
|
|
5770
|
-
const rows = sorted.map(([user, count]) => ` - user: ${user}
|
|
5771
|
-
calls: ${count}`).join("\n");
|
|
5772
|
-
const text = `user_metrics:
|
|
5773
|
-
as_of: ${lastModified}
|
|
5774
|
-
${rows}
|
|
5775
|
-
`;
|
|
5776
|
-
return { data: text, annotations: { lastModified } };
|
|
5777
|
-
}
|
|
5778
|
-
};
|
|
5779
|
-
function getMetricsResourceDefinitions() {
|
|
5780
|
-
return [
|
|
5781
|
-
metricsSummaryResource,
|
|
5782
|
-
metricsTokensResource,
|
|
5783
|
-
metricsSystemResource,
|
|
5784
|
-
metricsUsersResource
|
|
5785
|
-
];
|
|
5786
|
-
}
|
|
5787
|
-
|
|
5788
|
-
// src/handlers/resources/core/index.ts
|
|
5789
|
-
function getCoreResourceDefinitions() {
|
|
5790
|
-
return [
|
|
5791
|
-
briefingResource,
|
|
5792
|
-
dynamicBriefingResource,
|
|
5793
|
-
instructionsResource,
|
|
5794
|
-
recentResource,
|
|
5795
|
-
significantResource,
|
|
5796
|
-
tagsResource,
|
|
5797
|
-
statisticsResource,
|
|
5798
|
-
rulesResource,
|
|
5799
|
-
workflowsResource,
|
|
5800
|
-
skillsResource,
|
|
5801
|
-
healthResource,
|
|
5802
|
-
...getMetricsResourceDefinitions()
|
|
5803
|
-
];
|
|
5804
|
-
}
|
|
5805
|
-
|
|
5806
|
-
// src/handlers/resources/graph.ts
|
|
5807
|
-
function getGraphResourceDefinitions() {
|
|
5808
|
-
return [
|
|
5809
|
-
{
|
|
5810
|
-
uri: "memory://graph/recent",
|
|
5811
|
-
name: "Recent Relationship Graph",
|
|
5812
|
-
title: "Live Mermaid Diagram",
|
|
5813
|
-
description: "Live Mermaid diagram of recent relationships",
|
|
5814
|
-
mimeType: "text/plain",
|
|
5815
|
-
icons: [ICON_GRAPH],
|
|
5816
|
-
annotations: MEDIUM_PRIORITY,
|
|
5817
|
-
handler: (_uri, context) => {
|
|
5818
|
-
const relationships = context.db.getRecentGraphRelationships(20);
|
|
5819
|
-
if (relationships.length === 0) {
|
|
5820
|
-
return 'graph TD\n NoData["No relationships found \u2014 use link_entries to create relationships"]';
|
|
5821
|
-
}
|
|
5822
|
-
const lines = ["graph TD"];
|
|
5823
|
-
const seenNodes = /* @__PURE__ */ new Set();
|
|
5824
|
-
const arrowStyles = {
|
|
5825
|
-
references: "-->",
|
|
5826
|
-
evolves_from: "-->",
|
|
5827
|
-
depends_on: "-->",
|
|
5828
|
-
implements: "==>",
|
|
5829
|
-
resolved: "==>",
|
|
5830
|
-
clarifies: "-..->",
|
|
5831
|
-
caused: "-.->",
|
|
5832
|
-
related_to: "<-->",
|
|
5833
|
-
response_to: "<-->",
|
|
5834
|
-
blocked_by: "--x"
|
|
5835
|
-
};
|
|
5836
|
-
for (const rel of relationships) {
|
|
5837
|
-
if (!seenNodes.has(rel.from_entry_id)) {
|
|
5838
|
-
const label = rel.from_content.slice(0, 30).replace(/[\]"'`[]]/g, " ").trim();
|
|
5839
|
-
lines.push(
|
|
5840
|
-
` E${String(rel.from_entry_id)}["#${String(rel.from_entry_id)}: ${label}..."]`
|
|
5841
|
-
);
|
|
5842
|
-
seenNodes.add(rel.from_entry_id);
|
|
5843
|
-
}
|
|
5844
|
-
if (!seenNodes.has(rel.to_entry_id)) {
|
|
5845
|
-
const label = rel.to_content.slice(0, 30).replace(/[\]"'`[]]/g, " ").trim();
|
|
5846
|
-
lines.push(
|
|
5847
|
-
` E${String(rel.to_entry_id)}["#${String(rel.to_entry_id)}: ${label}..."]`
|
|
5848
|
-
);
|
|
5849
|
-
seenNodes.add(rel.to_entry_id);
|
|
5850
|
-
}
|
|
5851
|
-
const arrow = arrowStyles[rel.relationship_type] ?? "-->";
|
|
5852
|
-
lines.push(
|
|
5853
|
-
` E${String(rel.from_entry_id)} ${arrow}|${rel.relationship_type}| E${String(rel.to_entry_id)}`
|
|
5854
|
-
);
|
|
5855
|
-
}
|
|
5856
|
-
return lines.join("\n");
|
|
5857
|
-
}
|
|
5858
|
-
},
|
|
5859
|
-
{
|
|
5860
|
-
uri: "memory://graph/actions",
|
|
5861
|
-
name: "Actions Graph",
|
|
5862
|
-
title: "CI/CD Narrative Graph",
|
|
5863
|
-
description: "CI/CD narrative graph: commits \u2192 runs \u2192 failures \u2192 entries \u2192 fixes \u2192 deployments",
|
|
5864
|
-
mimeType: "text/plain",
|
|
5865
|
-
icons: [ICON_GITHUB],
|
|
5866
|
-
annotations: MEDIUM_PRIORITY,
|
|
5867
|
-
handler: async (uri, context) => {
|
|
5868
|
-
const match = /memory:\/\/graph\/actions\/?(.*)?/.exec(uri);
|
|
5869
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
5870
|
-
const resolved = await resolveGitHubRepo(
|
|
5871
|
-
context.github,
|
|
5872
|
-
context.briefingConfig,
|
|
5873
|
-
targetRepo,
|
|
5874
|
-
context.runtime
|
|
5875
|
-
);
|
|
5876
|
-
if (isResourceError(resolved)) {
|
|
5877
|
-
if (resolved.data !== null && typeof resolved.data === "object" && "error" in resolved.data && typeof resolved.data["error"] === "string") {
|
|
5878
|
-
const errorMsg = resolved.data["error"];
|
|
5879
|
-
if (errorMsg.includes("GitHub integration not available")) {
|
|
5880
|
-
return 'graph LR\n NoGitHub["GitHub integration not available \u2014 set GITHUB_TOKEN"]';
|
|
5881
|
-
}
|
|
5882
|
-
}
|
|
5883
|
-
return 'graph LR\n NoRepo["Repository not detected \u2014 run in valid git repo or use memory://graph/actions/{repo}"]';
|
|
5884
|
-
}
|
|
5885
|
-
const { owner, repo, github } = resolved;
|
|
5886
|
-
const workflowRuns = await github.getWorkflowRuns(owner, repo, 10);
|
|
5887
|
-
if (workflowRuns.length === 0) {
|
|
5888
|
-
return 'graph LR\n NoRuns["No GitHub Actions workflow runs found for this repository"]';
|
|
5889
|
-
}
|
|
5890
|
-
const lines = ["graph LR"];
|
|
5891
|
-
const statusStyles = {
|
|
5892
|
-
success: ":::success",
|
|
5893
|
-
failure: ":::failure",
|
|
5894
|
-
cancelled: ":::cancelled",
|
|
5895
|
-
skipped: ":::skipped"
|
|
5896
|
-
};
|
|
5897
|
-
lines.push(" classDef success fill:#28a745,color:#fff");
|
|
5898
|
-
lines.push(" classDef failure fill:#dc3545,color:#fff");
|
|
5899
|
-
lines.push(" classDef cancelled fill:#6c757d,color:#fff");
|
|
5900
|
-
lines.push(" classDef skipped fill:#ffc107,color:#000");
|
|
5901
|
-
for (const run of workflowRuns) {
|
|
5902
|
-
const shortSha = run.headSha.slice(0, 7);
|
|
5903
|
-
const nodeId = `R${String(run.id)}`;
|
|
5904
|
-
const commitId = `C${shortSha}`;
|
|
5905
|
-
const style = statusStyles[run.conclusion ?? "skipped"] ?? "";
|
|
5906
|
-
const statusIcon = run.conclusion === "success" ? "\u2713" : run.conclusion === "failure" ? "\u2717" : "\u25CB";
|
|
5907
|
-
lines.push(` ${commitId}["${shortSha}"]`);
|
|
5908
|
-
lines.push(` ${nodeId}["${statusIcon} ${run.name}"]${style}`);
|
|
5909
|
-
lines.push(` ${commitId} --> ${nodeId}`);
|
|
5910
|
-
}
|
|
5911
|
-
return lines.join("\n");
|
|
5912
|
-
}
|
|
5913
|
-
},
|
|
5914
|
-
{
|
|
5915
|
-
uri: "memory://actions/recent",
|
|
5916
|
-
name: "Recent Actions",
|
|
5917
|
-
title: "Recent Workflow Runs",
|
|
5918
|
-
description: "Recent workflow runs with CI status",
|
|
5919
|
-
mimeType: "application/json",
|
|
5920
|
-
icons: [ICON_GITHUB],
|
|
5921
|
-
annotations: ASSISTANT_FOCUSED,
|
|
5922
|
-
handler: async (uri, context) => {
|
|
5923
|
-
const match = /memory:\/\/actions\/recent\/?(.*)?/.exec(uri);
|
|
5924
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
5925
|
-
try {
|
|
5926
|
-
const resolved = await resolveGitHubRepo(
|
|
5927
|
-
context.github,
|
|
5928
|
-
context.briefingConfig,
|
|
5929
|
-
targetRepo,
|
|
5930
|
-
context.runtime
|
|
5931
|
-
);
|
|
5932
|
-
if (!isResourceError(resolved)) {
|
|
5933
|
-
const { owner, repo, github } = resolved;
|
|
5934
|
-
const runs = await github.getWorkflowRuns(owner, repo, 10);
|
|
5935
|
-
const entries2 = runs.map((run) => ({
|
|
5936
|
-
id: -1 * run.id,
|
|
5937
|
-
entryType: "tool_output",
|
|
5938
|
-
content: `Workflow: ${run.name}
|
|
5939
|
-
Status: ${run.status}
|
|
5940
|
-
Conclusion: ${run.conclusion || "pending"}
|
|
5941
|
-
Branch: ${run.headBranch}
|
|
5942
|
-
URL: ${run.url}`,
|
|
5943
|
-
timestamp: run.createdAt,
|
|
5944
|
-
isPersonal: false,
|
|
5945
|
-
significanceType: null,
|
|
5946
|
-
workflowRunId: run.id,
|
|
5947
|
-
workflowName: run.name,
|
|
5948
|
-
workflowStatus: run.conclusion || run.status
|
|
5949
|
-
}));
|
|
5950
|
-
return { entries: entries2, count: entries2.length, source: "github_api" };
|
|
5951
|
-
}
|
|
5952
|
-
} catch {
|
|
5953
|
-
}
|
|
5954
|
-
const entries = context.db.getWorkflowActionEntries(10);
|
|
5955
|
-
return { entries, count: entries.length, source: "database" };
|
|
5956
|
-
}
|
|
5957
|
-
}
|
|
5958
|
-
];
|
|
5959
|
-
}
|
|
5960
|
-
function getDynamicGraphResourceDefinitions() {
|
|
5961
|
-
const definitions = getGraphResourceDefinitions();
|
|
5962
|
-
const dynamicDefinitions = [];
|
|
5963
|
-
for (const def of definitions) {
|
|
5964
|
-
if (def.uri === "memory://graph/actions" || def.uri === "memory://actions/recent") {
|
|
5965
|
-
dynamicDefinitions.push({
|
|
5966
|
-
...def,
|
|
5967
|
-
uri: def.uri + "/{+repo}",
|
|
5968
|
-
name: def.name + " (Dynamic)",
|
|
5969
|
-
description: def.description + " (Supports explicit multi-project repository targeting via {repo})"
|
|
5970
|
-
});
|
|
5971
|
-
}
|
|
5972
|
-
}
|
|
5973
|
-
return [...definitions, ...dynamicDefinitions];
|
|
5974
|
-
}
|
|
5975
|
-
|
|
5976
|
-
// src/handlers/resources/github.ts
|
|
5977
|
-
var RESOURCE_ISSUE_LIMIT = 5;
|
|
5978
|
-
var RESOURCE_PR_LIMIT = 5;
|
|
5979
|
-
var RESOURCE_WORKFLOW_LIMIT = 5;
|
|
5980
|
-
var RESOURCE_STATUS_MILESTONE_LIMIT = 5;
|
|
5981
|
-
var RESOURCE_MILESTONE_LIMIT = 20;
|
|
5982
|
-
function getGitHubResourceDefinitions() {
|
|
5983
|
-
const definitions = [
|
|
5984
|
-
{
|
|
5985
|
-
uri: "memory://github/status",
|
|
5986
|
-
name: "GitHub Status",
|
|
5987
|
-
title: "GitHub Repository Status",
|
|
5988
|
-
description: "Compact GitHub status: repository, branch, CI, issues, PRs, Kanban summary",
|
|
5989
|
-
mimeType: "application/json",
|
|
5990
|
-
icons: [ICON_GITHUB],
|
|
5991
|
-
annotations: withPriority(0.7, ASSISTANT_FOCUSED),
|
|
5992
|
-
handler: async (uri, context) => {
|
|
5993
|
-
const match = /memory:\/\/github\/status\/?(.*)?/.exec(uri);
|
|
5994
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
5995
|
-
const resolved = await resolveGitHubRepo(
|
|
5996
|
-
context.github,
|
|
5997
|
-
context.briefingConfig,
|
|
5998
|
-
targetRepo,
|
|
5999
|
-
context.runtime
|
|
6000
|
-
);
|
|
6001
|
-
if (isResourceError(resolved)) return resolved;
|
|
6002
|
-
const { owner, repo, branch, lastModified, github } = resolved;
|
|
6003
|
-
const defaultProjectNumber = context.briefingConfig?.defaultProjectNumber;
|
|
6004
|
-
const withTimeout = (operation, ms, desc) => {
|
|
6005
|
-
const controller = new AbortController();
|
|
6006
|
-
return Promise.race([
|
|
6007
|
-
operation(controller.signal),
|
|
6008
|
-
new Promise(
|
|
6009
|
-
(_, reject) => setTimeout(() => {
|
|
6010
|
-
controller.abort();
|
|
6011
|
-
reject(new Error(`GitHub API timeout: ${desc}`));
|
|
6012
|
-
}, ms)
|
|
6013
|
-
)
|
|
6014
|
-
]);
|
|
6015
|
-
};
|
|
6016
|
-
const [repoContextResult, kanbanResult] = await Promise.allSettled([
|
|
6017
|
-
withTimeout((s) => github.getRepoContext(s), 1e4, "getRepoContext"),
|
|
6018
|
-
defaultProjectNumber !== void 0 ? withTimeout(
|
|
6019
|
-
(s) => github.getProjectKanban(owner, defaultProjectNumber, repo, s),
|
|
6020
|
-
1e4,
|
|
6021
|
-
"getProjectKanban"
|
|
6022
|
-
) : Promise.resolve(null)
|
|
6023
|
-
]);
|
|
6024
|
-
let commit = null;
|
|
6025
|
-
let issues = [];
|
|
6026
|
-
let prs = [];
|
|
6027
|
-
let workflowRuns = [];
|
|
6028
|
-
let milestonesContext = [];
|
|
6029
|
-
if (repoContextResult.status === "fulfilled") {
|
|
6030
|
-
commit = repoContextResult.value.commit;
|
|
6031
|
-
issues = (repoContextResult.value.issues ?? []).slice(0, RESOURCE_ISSUE_LIMIT);
|
|
6032
|
-
prs = (repoContextResult.value.pullRequests ?? []).slice(0, RESOURCE_PR_LIMIT);
|
|
6033
|
-
workflowRuns = (repoContextResult.value.workflowRuns ?? []).slice(
|
|
6034
|
-
0,
|
|
6035
|
-
RESOURCE_WORKFLOW_LIMIT
|
|
6036
|
-
);
|
|
6037
|
-
milestonesContext = (repoContextResult.value.milestones ?? []).slice(
|
|
6038
|
-
0,
|
|
6039
|
-
RESOURCE_STATUS_MILESTONE_LIMIT
|
|
6040
|
-
);
|
|
6041
|
-
} else {
|
|
6042
|
-
logger.debug("Failed to fetch repo context", {
|
|
6043
|
-
module: "RESOURCE",
|
|
6044
|
-
operation: "github-status",
|
|
6045
|
-
error: repoContextResult.reason
|
|
6046
|
-
});
|
|
6047
|
-
}
|
|
6048
|
-
const openIssues = issues.map((i) => ({
|
|
6049
|
-
number: i.number,
|
|
6050
|
-
title: markUntrustedContentInline(i.title.slice(0, 50))
|
|
6051
|
-
}));
|
|
6052
|
-
const openPrs = prs.map((pr) => ({
|
|
6053
|
-
number: pr.number,
|
|
6054
|
-
title: markUntrustedContentInline(pr.title.slice(0, 50)),
|
|
6055
|
-
state: pr.state
|
|
6056
|
-
}));
|
|
6057
|
-
let ciStatus = "unknown";
|
|
6058
|
-
let latestRun = null;
|
|
6059
|
-
if (workflowRuns.length > 0) {
|
|
6060
|
-
const latestCompleted = workflowRuns.find((r) => r.status === "completed");
|
|
6061
|
-
const latest = workflowRuns[0];
|
|
6062
|
-
latestRun = {
|
|
6063
|
-
name: latest?.name ?? "Unknown",
|
|
6064
|
-
conclusion: latest?.conclusion ?? null,
|
|
6065
|
-
headSha: latest?.headSha?.slice(0, 7) ?? ""
|
|
6066
|
-
};
|
|
6067
|
-
if (latestCompleted) {
|
|
6068
|
-
switch (latestCompleted.conclusion) {
|
|
6069
|
-
case "success":
|
|
6070
|
-
ciStatus = "passing";
|
|
6071
|
-
break;
|
|
6072
|
-
case "failure":
|
|
6073
|
-
ciStatus = "failing";
|
|
6074
|
-
break;
|
|
6075
|
-
case "cancelled":
|
|
6076
|
-
ciStatus = "cancelled";
|
|
6077
|
-
break;
|
|
6078
|
-
default:
|
|
6079
|
-
ciStatus = "unknown";
|
|
6080
|
-
}
|
|
6081
|
-
} else if (workflowRuns.some((r) => r.status !== "completed")) {
|
|
6082
|
-
ciStatus = "pending";
|
|
6083
|
-
}
|
|
6084
|
-
}
|
|
6085
|
-
let kanbanSummary = null;
|
|
6086
|
-
if (kanbanResult.status === "fulfilled" && kanbanResult.value) {
|
|
6087
|
-
kanbanSummary = {};
|
|
6088
|
-
for (const col of kanbanResult.value.columns) {
|
|
6089
|
-
kanbanSummary[col.status] = col.items.length;
|
|
6090
|
-
}
|
|
6091
|
-
} else if (kanbanResult.status === "rejected") {
|
|
6092
|
-
logger.debug("Failed to fetch Kanban board", {
|
|
6093
|
-
module: "RESOURCE",
|
|
6094
|
-
operation: "github-status",
|
|
6095
|
-
error: kanbanResult.reason
|
|
6096
|
-
});
|
|
6097
|
-
}
|
|
6098
|
-
let milestoneSummary;
|
|
6099
|
-
if (repoContextResult.status === "fulfilled") {
|
|
6100
|
-
milestoneSummary = {
|
|
6101
|
-
openCount: milestonesContext.length,
|
|
6102
|
-
items: milestonesContext.map((ms) => {
|
|
6103
|
-
const pct = milestoneCompletionPct(ms.openIssues, ms.closedIssues);
|
|
6104
|
-
return {
|
|
6105
|
-
number: ms.number,
|
|
6106
|
-
title: markUntrustedContentInline(ms.title),
|
|
6107
|
-
state: ms.state,
|
|
6108
|
-
openIssues: ms.openIssues,
|
|
6109
|
-
closedIssues: ms.closedIssues,
|
|
6110
|
-
completionPercentage: pct,
|
|
6111
|
-
dueOn: ms.dueOn
|
|
6112
|
-
};
|
|
6113
|
-
})
|
|
6114
|
-
};
|
|
6115
|
-
} else {
|
|
6116
|
-
milestoneSummary = null;
|
|
6117
|
-
logger.debug("Failed to fetch milestones from context", {
|
|
6118
|
-
module: "RESOURCE",
|
|
6119
|
-
operation: "github-status",
|
|
6120
|
-
error: repoContextResult.reason
|
|
6121
|
-
});
|
|
6122
|
-
}
|
|
6123
|
-
let fetchStatus = "ok";
|
|
6124
|
-
const failures = [];
|
|
6125
|
-
if (repoContextResult.status === "rejected") failures.push("context");
|
|
6126
|
-
if (kanbanResult.status === "rejected") failures.push("kanban");
|
|
6127
|
-
if (failures.length > 0) {
|
|
6128
|
-
const totalAttempts = defaultProjectNumber !== void 0 ? 2 : 1;
|
|
6129
|
-
if (failures.length === totalAttempts) {
|
|
6130
|
-
fetchStatus = "failed";
|
|
6131
|
-
} else {
|
|
6132
|
-
fetchStatus = "degraded";
|
|
6133
|
-
}
|
|
6134
|
-
}
|
|
6135
|
-
return {
|
|
6136
|
-
data: {
|
|
6137
|
-
status: fetchStatus,
|
|
6138
|
-
failures: failures.length > 0 ? failures : void 0,
|
|
6139
|
-
repository: `${owner}/${repo}`,
|
|
6140
|
-
branch,
|
|
6141
|
-
commit: commit?.slice(0, 7) ?? null,
|
|
6142
|
-
ci: {
|
|
6143
|
-
status: ciStatus,
|
|
6144
|
-
latestRun
|
|
6145
|
-
},
|
|
6146
|
-
issues: {
|
|
6147
|
-
openCount: issues.length,
|
|
6148
|
-
items: openIssues
|
|
6149
|
-
},
|
|
6150
|
-
pullRequests: {
|
|
6151
|
-
openCount: prs.length,
|
|
6152
|
-
items: openPrs
|
|
6153
|
-
},
|
|
6154
|
-
kanbanSummary,
|
|
6155
|
-
milestones: milestoneSummary
|
|
6156
|
-
},
|
|
6157
|
-
annotations: { lastModified }
|
|
6158
|
-
};
|
|
6159
|
-
}
|
|
6160
|
-
},
|
|
6161
|
-
// Repository insights resource
|
|
6162
|
-
{
|
|
6163
|
-
uri: "memory://github/insights",
|
|
6164
|
-
name: "Repository Insights",
|
|
6165
|
-
title: "Repository Stars & Traffic Summary",
|
|
6166
|
-
description: "Compact repo insights: stars, forks, 14-day traffic totals (~150 tokens)",
|
|
6167
|
-
mimeType: "application/json",
|
|
6168
|
-
icons: [ICON_ANALYTICS],
|
|
6169
|
-
annotations: { ...LOW_PRIORITY, audience: ["assistant"] },
|
|
6170
|
-
handler: async (uri, context) => {
|
|
6171
|
-
const match = /memory:\/\/github\/insights\/?(.*)?/.exec(uri);
|
|
6172
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
6173
|
-
const resolved = await resolveGitHubRepo(
|
|
6174
|
-
context.github,
|
|
6175
|
-
context.briefingConfig,
|
|
6176
|
-
targetRepo,
|
|
6177
|
-
context.runtime
|
|
6178
|
-
);
|
|
6179
|
-
if (isResourceError(resolved)) return resolved;
|
|
6180
|
-
const { owner, repo, lastModified, github } = resolved;
|
|
6181
|
-
const stats = await github.getRepoStats(owner, repo);
|
|
6182
|
-
let traffic = null;
|
|
6183
|
-
try {
|
|
6184
|
-
const trafficData = await github.getTrafficData(owner, repo);
|
|
6185
|
-
if (trafficData) {
|
|
6186
|
-
traffic = {
|
|
6187
|
-
clones14d: trafficData.clones.total,
|
|
6188
|
-
views14d: trafficData.views.total
|
|
6189
|
-
};
|
|
6190
|
-
}
|
|
6191
|
-
} catch {
|
|
6192
|
-
}
|
|
6193
|
-
return {
|
|
6194
|
-
data: {
|
|
6195
|
-
repository: `${owner}/${repo}`,
|
|
6196
|
-
stars: stats?.stars ?? null,
|
|
6197
|
-
forks: stats?.forks ?? null,
|
|
6198
|
-
watchers: stats?.watchers ?? null,
|
|
6199
|
-
...traffic ?? {},
|
|
6200
|
-
hint: !traffic ? "Traffic data requires push access to the repository." : void 0
|
|
6201
|
-
},
|
|
6202
|
-
annotations: { lastModified }
|
|
6203
|
-
};
|
|
6204
|
-
}
|
|
6205
|
-
},
|
|
6206
|
-
// Milestone resources
|
|
6207
|
-
{
|
|
6208
|
-
uri: "memory://github/milestones",
|
|
6209
|
-
name: "GitHub Milestones",
|
|
6210
|
-
title: "GitHub Repository Milestones",
|
|
6211
|
-
description: "Open GitHub milestones with completion percentages, due dates, and issue counts",
|
|
6212
|
-
mimeType: "application/json",
|
|
6213
|
-
icons: [ICON_MILESTONE],
|
|
6214
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
6215
|
-
handler: async (uri, context) => {
|
|
6216
|
-
const match = /memory:\/\/github\/milestones\/?(.*)?/.exec(uri);
|
|
6217
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
6218
|
-
const resolved = await resolveGitHubRepo(
|
|
6219
|
-
context.github,
|
|
6220
|
-
context.briefingConfig,
|
|
6221
|
-
targetRepo,
|
|
6222
|
-
context.runtime
|
|
6223
|
-
);
|
|
6224
|
-
if (isResourceError(resolved)) return resolved;
|
|
6225
|
-
const { owner, repo, lastModified, github } = resolved;
|
|
6226
|
-
const milestones = await github.getMilestones(
|
|
6227
|
-
owner,
|
|
6228
|
-
repo,
|
|
6229
|
-
"open",
|
|
6230
|
-
RESOURCE_MILESTONE_LIMIT
|
|
6231
|
-
);
|
|
6232
|
-
const milestonesWithProgress = milestones.map((ms) => {
|
|
6233
|
-
const completionPercentage = milestoneCompletionPct(
|
|
6234
|
-
ms.openIssues,
|
|
6235
|
-
ms.closedIssues
|
|
6236
|
-
);
|
|
6237
|
-
return {
|
|
6238
|
-
...ms,
|
|
6239
|
-
title: markUntrustedContentInline(ms.title),
|
|
6240
|
-
completionPercentage
|
|
6241
|
-
};
|
|
6242
|
-
});
|
|
6243
|
-
return {
|
|
6244
|
-
data: {
|
|
6245
|
-
repository: `${owner}/${repo}`,
|
|
6246
|
-
milestones: milestonesWithProgress,
|
|
6247
|
-
count: milestonesWithProgress.length,
|
|
6248
|
-
hint: "Use get_github_milestones tool for state filtering. Use memory://milestones/{number} for detail."
|
|
6249
|
-
},
|
|
6250
|
-
annotations: { lastModified }
|
|
6251
|
-
};
|
|
6252
|
-
}
|
|
6253
|
-
},
|
|
6254
|
-
{
|
|
6255
|
-
uri: "memory://milestones/{number}",
|
|
6256
|
-
name: "Milestone Detail",
|
|
6257
|
-
title: "GitHub Milestone Detail",
|
|
6258
|
-
description: "Detailed view of a single GitHub milestone with completion progress and issue counts. Use get_github_issues with the milestone filter for individual issue details.",
|
|
6259
|
-
mimeType: "application/json",
|
|
6260
|
-
icons: [ICON_MILESTONE],
|
|
6261
|
-
annotations: ASSISTANT_FOCUSED,
|
|
6262
|
-
handler: async (uri, context) => {
|
|
6263
|
-
const lastModified = (/* @__PURE__ */ new Date()).toISOString();
|
|
6264
|
-
const match = /memory:\/\/milestones\/(?:(.*)\/)?(\d+)$/.exec(uri);
|
|
6265
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
6266
|
-
const milestoneNumber = match?.[2] ? parseInt(match[2], 10) : null;
|
|
6267
|
-
if (milestoneNumber === null) {
|
|
6268
|
-
return {
|
|
6269
|
-
data: { error: "Invalid milestone number" },
|
|
6270
|
-
annotations: { lastModified }
|
|
6271
|
-
};
|
|
6272
|
-
}
|
|
6273
|
-
const resolved = await resolveGitHubRepo(
|
|
6274
|
-
context.github,
|
|
6275
|
-
context.briefingConfig,
|
|
6276
|
-
targetRepo,
|
|
6277
|
-
context.runtime
|
|
6278
|
-
);
|
|
6279
|
-
if (isResourceError(resolved)) return resolved;
|
|
6280
|
-
const { owner, repo, github } = resolved;
|
|
6281
|
-
const milestone = await github.getMilestone(owner, repo, milestoneNumber);
|
|
6282
|
-
if (!milestone) {
|
|
6283
|
-
return {
|
|
6284
|
-
data: { error: `Milestone #${String(milestoneNumber)} not found` },
|
|
6285
|
-
annotations: { lastModified }
|
|
6286
|
-
};
|
|
6287
|
-
}
|
|
6288
|
-
const completionPercentage = milestoneCompletionPct(
|
|
6289
|
-
milestone.openIssues,
|
|
6290
|
-
milestone.closedIssues
|
|
6291
|
-
);
|
|
6292
|
-
return {
|
|
6293
|
-
data: {
|
|
6294
|
-
repository: `${owner}/${repo}`,
|
|
6295
|
-
milestone: {
|
|
6296
|
-
...milestone,
|
|
6297
|
-
title: markUntrustedContentInline(milestone.title),
|
|
6298
|
-
completionPercentage
|
|
6299
|
-
},
|
|
6300
|
-
hint: "Use get_github_issues tool to list issues associated with this milestone."
|
|
6301
|
-
},
|
|
6302
|
-
annotations: { lastModified }
|
|
6303
|
-
};
|
|
6304
|
-
}
|
|
6305
|
-
}
|
|
6306
|
-
];
|
|
6307
|
-
const dynamicDefinitions = definitions.map((def) => {
|
|
6308
|
-
const dynamicName = def.name + " (Dynamic)";
|
|
6309
|
-
let dynamicUri;
|
|
6310
|
-
if (def.uri === "memory://milestones/{number}") {
|
|
6311
|
-
dynamicUri = "memory://milestones/{+repo}/{number}";
|
|
6312
|
-
} else {
|
|
6313
|
-
dynamicUri = def.uri + "/{+repo}";
|
|
6314
|
-
}
|
|
6315
|
-
return {
|
|
6316
|
-
...def,
|
|
6317
|
-
uri: dynamicUri,
|
|
6318
|
-
name: dynamicName,
|
|
6319
|
-
description: def.description + " (Supports explicit multi-project repository targeting via {repo})"
|
|
6320
|
-
};
|
|
6321
|
-
});
|
|
6322
|
-
return [...definitions, ...dynamicDefinitions];
|
|
6323
|
-
}
|
|
6324
|
-
|
|
6325
|
-
// src/handlers/resources/templates.ts
|
|
6326
|
-
function getTemplateResourceDefinitions() {
|
|
6327
|
-
const definitions = [
|
|
6328
|
-
{
|
|
6329
|
-
uri: "memory://projects/{number}/timeline",
|
|
6330
|
-
name: "Project Timeline",
|
|
6331
|
-
title: "Project Activity Timeline",
|
|
6332
|
-
description: "Project activity timeline",
|
|
6333
|
-
mimeType: "application/json",
|
|
6334
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
6335
|
-
handler: (uri, context) => {
|
|
6336
|
-
const match = /memory:\/\/projects\/(\d+)\/timeline/.exec(uri);
|
|
6337
|
-
const projectNumber = match?.[1] ? parseInt(match[1], 10) : null;
|
|
6338
|
-
if (projectNumber === null) {
|
|
6339
|
-
return { error: "Invalid project number" };
|
|
6340
|
-
}
|
|
6341
|
-
const entries = context.db.searchEntries("", { projectNumber, limit: 50 });
|
|
6342
|
-
return { projectNumber, entries, count: entries.length };
|
|
6343
|
-
}
|
|
6344
|
-
},
|
|
6345
|
-
{
|
|
6346
|
-
uri: "memory://issues/{issue_number}/entries",
|
|
6347
|
-
name: "Issue Entries",
|
|
6348
|
-
title: "Entries Linked to Issue",
|
|
6349
|
-
description: "All entries linked to a specific issue",
|
|
6350
|
-
mimeType: "application/json",
|
|
6351
|
-
icons: [ICON_ISSUE],
|
|
6352
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
6353
|
-
handler: (uri, context) => {
|
|
6354
|
-
const match = /memory:\/\/issues\/(\d+)\/entries/.exec(uri);
|
|
6355
|
-
const issueNumber = match?.[1] ? parseInt(match[1], 10) : null;
|
|
6356
|
-
if (issueNumber === null) {
|
|
6357
|
-
return { error: "Invalid issue number" };
|
|
6358
|
-
}
|
|
6359
|
-
const entries = context.db.searchEntries("", { issueNumber, limit: 100 });
|
|
6360
|
-
return { issueNumber, entries, count: entries.length };
|
|
6361
|
-
}
|
|
6362
|
-
},
|
|
6363
|
-
{
|
|
6364
|
-
uri: "memory://prs/{pr_number}/entries",
|
|
6365
|
-
name: "PR Entries",
|
|
6366
|
-
title: "Entries Linked to PR",
|
|
6367
|
-
description: "All entries linked to a specific pull request",
|
|
6368
|
-
mimeType: "application/json",
|
|
6369
|
-
icons: [ICON_PR],
|
|
6370
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
6371
|
-
handler: (uri, context) => {
|
|
6372
|
-
const match = /memory:\/\/prs\/(\d+)\/entries/.exec(uri);
|
|
6373
|
-
const prNumber = match?.[1] ? parseInt(match[1], 10) : null;
|
|
6374
|
-
if (prNumber === null) {
|
|
6375
|
-
return { error: "Invalid PR number" };
|
|
6376
|
-
}
|
|
6377
|
-
const entries = context.db.searchEntries("", { prNumber, limit: 100 });
|
|
6378
|
-
return {
|
|
6379
|
-
prNumber,
|
|
6380
|
-
entries,
|
|
6381
|
-
count: entries.length,
|
|
6382
|
-
...entries.length === 0 ? {
|
|
6383
|
-
hint: "No journal entries linked to this PR. Use create_entry with pr_number to link entries."
|
|
6384
|
-
} : {}
|
|
6385
|
-
};
|
|
6386
|
-
}
|
|
6387
|
-
},
|
|
6388
|
-
{
|
|
6389
|
-
uri: "memory://prs/{pr_number}/timeline",
|
|
6390
|
-
name: "PR Timeline",
|
|
6391
|
-
title: "Combined PR and Journal Timeline",
|
|
6392
|
-
description: "Combined PR + journal timeline with live PR metadata",
|
|
6393
|
-
mimeType: "application/json",
|
|
6394
|
-
icons: [ICON_PR],
|
|
6395
|
-
annotations: ASSISTANT_FOCUSED,
|
|
6396
|
-
handler: async (uri, context) => {
|
|
6397
|
-
const match = /memory:\/\/prs\/(\d+)\/timeline/.exec(uri);
|
|
6398
|
-
const prNumber = match?.[1] ? parseInt(match[1], 10) : null;
|
|
6399
|
-
if (prNumber === null) {
|
|
6400
|
-
return { error: "Invalid PR number" };
|
|
6401
|
-
}
|
|
6402
|
-
let prMetadata = null;
|
|
6403
|
-
if (context.github) {
|
|
6404
|
-
try {
|
|
6405
|
-
const repoInfo = await context.github.getRepoInfo();
|
|
6406
|
-
if (repoInfo.owner && repoInfo.repo) {
|
|
6407
|
-
const pr = await context.github.getPullRequest(
|
|
6408
|
-
repoInfo.owner,
|
|
6409
|
-
repoInfo.repo,
|
|
6410
|
-
prNumber
|
|
6411
|
-
);
|
|
6412
|
-
if (pr) {
|
|
6413
|
-
prMetadata = {
|
|
6414
|
-
title: pr.title,
|
|
6415
|
-
state: pr.state,
|
|
6416
|
-
draft: pr.draft,
|
|
6417
|
-
mergedAt: pr.mergedAt,
|
|
6418
|
-
closedAt: pr.closedAt,
|
|
6419
|
-
author: pr.author,
|
|
6420
|
-
headBranch: pr.headBranch,
|
|
6421
|
-
baseBranch: pr.baseBranch
|
|
6422
|
-
};
|
|
6423
|
-
}
|
|
6424
|
-
}
|
|
6425
|
-
} catch {
|
|
6426
|
-
}
|
|
6427
|
-
}
|
|
6428
|
-
const entries = context.db.searchEntries("", { prNumber, limit: 100 });
|
|
6429
|
-
let timelineNote;
|
|
6430
|
-
if (prMetadata) {
|
|
6431
|
-
const stateDesc = prMetadata.state.toLowerCase();
|
|
6432
|
-
const mergedNote = prMetadata.mergedAt ? " (merged)" : "";
|
|
6433
|
-
const draftNote = prMetadata.draft ? " [DRAFT]" : "";
|
|
6434
|
-
timelineNote = `PR #${String(prNumber)} is ${stateDesc}${mergedNote}${draftNote}`;
|
|
6435
|
-
} else {
|
|
6436
|
-
timelineNote = "GitHub integration unavailable for live PR status. Entry timestamps show journal activity.";
|
|
6437
|
-
}
|
|
6438
|
-
return {
|
|
6439
|
-
prNumber,
|
|
6440
|
-
prMetadata,
|
|
6441
|
-
entries,
|
|
6442
|
-
count: entries.length,
|
|
6443
|
-
timelineNote,
|
|
6444
|
-
...entries.length === 0 ? {
|
|
6445
|
-
hint: "No journal entries linked to this PR. Use create_entry with pr_number to link entries."
|
|
6446
|
-
} : {}
|
|
6447
|
-
};
|
|
6448
|
-
}
|
|
6449
|
-
},
|
|
6450
|
-
// Kanban board resources (GitHub Projects v2)
|
|
6451
|
-
{
|
|
6452
|
-
uri: "memory://kanban/{project_number}",
|
|
6453
|
-
name: "Kanban Board",
|
|
6454
|
-
title: "GitHub Project Kanban Board",
|
|
6455
|
-
description: "View a GitHub Project v2 as a Kanban board with items grouped by Status",
|
|
6456
|
-
mimeType: "application/json",
|
|
6457
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
6458
|
-
handler: async (uri, context) => {
|
|
6459
|
-
const match = /memory:\/\/kanban\/(?:(.*)\/)?(\d+)$/.exec(uri);
|
|
6460
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
6461
|
-
const projectNumber = match?.[2] ? parseInt(match[2], 10) : null;
|
|
6462
|
-
if (projectNumber === null) {
|
|
6463
|
-
return { error: "Invalid project number" };
|
|
6464
|
-
}
|
|
6465
|
-
const resolved = await resolveGitHubRepo(
|
|
6466
|
-
context.github,
|
|
6467
|
-
context.briefingConfig,
|
|
6468
|
-
targetRepo,
|
|
6469
|
-
context.runtime
|
|
6470
|
-
);
|
|
6471
|
-
if (isResourceError(resolved)) return resolved.data;
|
|
6472
|
-
const { owner, repo, github } = resolved;
|
|
6473
|
-
const board = await github.getProjectKanban(owner, projectNumber, repo);
|
|
6474
|
-
if (!board) {
|
|
6475
|
-
return {
|
|
6476
|
-
error: `Project #${String(projectNumber)} not found or Status field not configured`,
|
|
6477
|
-
projectNumber,
|
|
6478
|
-
owner,
|
|
6479
|
-
hint: "Projects can be at user, repository, or organization level."
|
|
6480
|
-
};
|
|
6481
|
-
}
|
|
6482
|
-
for (const column of board.columns) {
|
|
6483
|
-
for (const item of column.items) {
|
|
6484
|
-
if ("body" in item) {
|
|
6485
|
-
delete item.body;
|
|
6486
|
-
}
|
|
6487
|
-
}
|
|
6488
|
-
}
|
|
6489
|
-
return board;
|
|
6490
|
-
}
|
|
6491
|
-
},
|
|
6492
|
-
{
|
|
6493
|
-
uri: "memory://kanban/{project_number}/diagram",
|
|
6494
|
-
name: "Kanban Diagram",
|
|
6495
|
-
title: "Kanban Board Mermaid Diagram",
|
|
6496
|
-
description: "Mermaid diagram visualization of a GitHub Project Kanban board",
|
|
6497
|
-
mimeType: "text/plain",
|
|
6498
|
-
annotations: MEDIUM_PRIORITY,
|
|
6499
|
-
handler: async (uri, context) => {
|
|
6500
|
-
const match = /memory:\/\/kanban\/(?:(.*)\/)?(\d+)\/diagram$/.exec(uri);
|
|
6501
|
-
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
6502
|
-
const projectNumber = match?.[2] ? parseInt(match[2], 10) : null;
|
|
6503
|
-
if (projectNumber === null) {
|
|
6504
|
-
return { error: "Invalid project number" };
|
|
6505
|
-
}
|
|
6506
|
-
const resolved = await resolveGitHubRepo(
|
|
6507
|
-
context.github,
|
|
6508
|
-
context.briefingConfig,
|
|
6509
|
-
targetRepo,
|
|
6510
|
-
context.runtime
|
|
6511
|
-
);
|
|
6512
|
-
if (isResourceError(resolved)) {
|
|
6513
|
-
const errObj = resolved.data;
|
|
6514
|
-
return `graph LR
|
|
6515
|
-
Error["${errObj.error.replace(/["[\]]/g, "'")} \u2014 ${errObj.hint ? errObj.hint.replace(/["[\]]/g, "'") : ""}"]`;
|
|
6516
|
-
}
|
|
6517
|
-
const { owner, repo, github } = resolved;
|
|
6518
|
-
const board = await github.getProjectKanban(owner, projectNumber, repo);
|
|
6519
|
-
if (!board) {
|
|
6520
|
-
return `graph LR
|
|
6521
|
-
NotFound["Project #${String(projectNumber)} not found \u2014 ensure project exists with a Status field"]`;
|
|
6522
|
-
}
|
|
6523
|
-
const lines = ["graph LR"];
|
|
6524
|
-
lines.push(" classDef issue fill:#28a745,color:#fff");
|
|
6525
|
-
lines.push(" classDef pr fill:#6f42c1,color:#fff");
|
|
6526
|
-
lines.push(" classDef draft fill:#6c757d,color:#fff");
|
|
6527
|
-
for (const column of board.columns) {
|
|
6528
|
-
const safeStatus = column.status.replace(/["\s]/g, "_");
|
|
6529
|
-
lines.push(
|
|
6530
|
-
` subgraph ${safeStatus}["${column.status} (${String(column.items.length)})"]`
|
|
6531
|
-
);
|
|
6532
|
-
for (const item of column.items) {
|
|
6533
|
-
const safeId = item.id.replace(/[^a-zA-Z0-9]/g, "").slice(-8);
|
|
6534
|
-
const label = item.title.slice(0, 25).replace(/["[\]]/g, "'");
|
|
6535
|
-
const typeIcon = item.type === "ISSUE" ? "\u{1F535}" : item.type === "PULL_REQUEST" ? "\u{1F7E3}" : "\u26AA";
|
|
6536
|
-
const numberStr = item.number !== void 0 && item.number !== 0 ? `#${String(item.number)}` : "";
|
|
6537
|
-
lines.push(` I${safeId}["${typeIcon} ${numberStr} ${label}..."]`);
|
|
6538
|
-
const typeClass = item.type === "ISSUE" ? "issue" : item.type === "PULL_REQUEST" ? "pr" : "draft";
|
|
6539
|
-
lines.push(` class I${safeId} ${typeClass}`);
|
|
6540
|
-
}
|
|
6541
|
-
lines.push(" end");
|
|
6542
|
-
}
|
|
6543
|
-
return lines.join("\n");
|
|
6544
|
-
}
|
|
6545
|
-
}
|
|
6546
|
-
];
|
|
6547
|
-
const dynamicDefinitions = [];
|
|
6548
|
-
for (const def of definitions) {
|
|
6549
|
-
if (def.uri.startsWith("memory://kanban/")) {
|
|
6550
|
-
const dynamicUri = def.uri.replace("{project_number}", "{+repo}/{project_number}");
|
|
6551
|
-
dynamicDefinitions.push({
|
|
6552
|
-
...def,
|
|
6553
|
-
uri: dynamicUri,
|
|
6554
|
-
name: def.name + " (Dynamic)",
|
|
6555
|
-
description: def.description + " (Supports explicit multi-project repository targeting via {repo})"
|
|
6556
|
-
});
|
|
6557
|
-
}
|
|
6558
|
-
}
|
|
6559
|
-
return [...definitions, ...dynamicDefinitions];
|
|
6560
|
-
}
|
|
6561
|
-
|
|
6562
|
-
// src/handlers/resources/team.ts
|
|
6563
|
-
function enrichWithAuthor(entries, context) {
|
|
6564
|
-
const teamDb = context.teamDb;
|
|
6565
|
-
if (!teamDb || entries.length === 0) return entries.map((e) => ({ ...e, author: null }));
|
|
6566
|
-
const ids = entries.map((e) => e.id);
|
|
6567
|
-
const authorMap = teamDb.getAuthorsForEntries(ids);
|
|
6568
|
-
return entries.map((e) => ({
|
|
6569
|
-
...e,
|
|
6570
|
-
author: authorMap.get(e.id) ?? null
|
|
6571
|
-
}));
|
|
6572
|
-
}
|
|
6573
|
-
function getTeamResourceDefinitions() {
|
|
6574
|
-
const resources = [
|
|
6575
|
-
{
|
|
6576
|
-
uri: "memory://team/recent",
|
|
6577
|
-
name: "Recent Team Entries",
|
|
6578
|
-
title: "Recent Team-Shared Entries",
|
|
6579
|
-
description: "Recent entries from the team database. Requires TEAM_DB_PATH configuration.",
|
|
6580
|
-
mimeType: "application/json",
|
|
6581
|
-
icons: [ICON_CLOCK],
|
|
6582
|
-
annotations: withPriority(0.7, ASSISTANT_FOCUSED),
|
|
6583
|
-
handler: (_uri, context) => {
|
|
6584
|
-
if (!context.teamDb) {
|
|
6585
|
-
return {
|
|
6586
|
-
data: {
|
|
6587
|
-
error: "Team database not configured. Set TEAM_DB_PATH to enable.",
|
|
6588
|
-
entries: [],
|
|
6589
|
-
count: 0
|
|
6590
|
-
}
|
|
6591
|
-
};
|
|
6592
|
-
}
|
|
6593
|
-
const entries = context.teamDb.getRecentEntries(10);
|
|
6594
|
-
const lastModified = entries[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6595
|
-
const enriched = enrichWithAuthor(entries, context);
|
|
6596
|
-
return {
|
|
6597
|
-
data: {
|
|
6598
|
-
entries: enriched.map((e) => ({
|
|
6599
|
-
...e,
|
|
6600
|
-
content: markUntrustedContent(e.content),
|
|
6601
|
-
author: e.author ? sanitizeAuthor(e.author) : null
|
|
6602
|
-
})),
|
|
6603
|
-
count: enriched.length,
|
|
6604
|
-
source: "team"
|
|
6605
|
-
},
|
|
6606
|
-
annotations: { lastModified }
|
|
6607
|
-
};
|
|
6608
|
-
}
|
|
6609
|
-
},
|
|
6610
|
-
{
|
|
6611
|
-
uri: "memory://team/statistics",
|
|
6612
|
-
name: "Team Statistics",
|
|
6613
|
-
title: "Team Database Statistics",
|
|
6614
|
-
description: "Entry counts, types, and contributor breakdown for the team database.",
|
|
6615
|
-
mimeType: "application/json",
|
|
6616
|
-
icons: [ICON_TEAM],
|
|
6617
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
6618
|
-
handler: (_uri, context) => {
|
|
6619
|
-
if (!context.teamDb) {
|
|
6620
|
-
return {
|
|
6621
|
-
data: {
|
|
6622
|
-
error: "Team database not configured. Set TEAM_DB_PATH to enable.",
|
|
6623
|
-
configured: false
|
|
6624
|
-
}
|
|
6625
|
-
};
|
|
6626
|
-
}
|
|
6627
|
-
const stats = context.teamDb.getStatistics("week");
|
|
6628
|
-
let authors = [];
|
|
6629
|
-
try {
|
|
6630
|
-
authors = context.teamDb.getAuthorStatistics();
|
|
6631
|
-
} catch (error) {
|
|
6632
|
-
logger.warning("Failed to get team author statistics", {
|
|
6633
|
-
module: "ResourceHandler",
|
|
6634
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
6635
|
-
});
|
|
6636
|
-
}
|
|
6637
|
-
return {
|
|
6638
|
-
data: {
|
|
6639
|
-
configured: true,
|
|
6640
|
-
...stats,
|
|
6641
|
-
authors,
|
|
6642
|
-
source: "team"
|
|
6643
|
-
}
|
|
6644
|
-
};
|
|
6645
|
-
}
|
|
6646
|
-
},
|
|
6647
|
-
// ====================================================================
|
|
6648
|
-
// Flag Resources (Hush Protocol)
|
|
6649
|
-
// ====================================================================
|
|
6650
|
-
{
|
|
6651
|
-
uri: "memory://flags",
|
|
6652
|
-
name: "Active Flags",
|
|
6653
|
-
title: "Active Team Flags Dashboard",
|
|
6654
|
-
description: "Active (unresolved) flags from the Hush Protocol. Shows machine-actionable developer signals that need attention. Requires TEAM_DB_PATH.",
|
|
6655
|
-
mimeType: "application/json",
|
|
6656
|
-
icons: [ICON_FLAG],
|
|
6657
|
-
annotations: withPriority(0.8, ASSISTANT_FOCUSED),
|
|
6658
|
-
handler: (_uri, context) => {
|
|
6659
|
-
if (!context.teamDb) {
|
|
6660
|
-
return {
|
|
6661
|
-
data: {
|
|
6662
|
-
error: "Team database not configured. Set TEAM_DB_PATH to enable.",
|
|
6663
|
-
activeFlags: [],
|
|
6664
|
-
count: 0
|
|
6665
|
-
}
|
|
6666
|
-
};
|
|
6667
|
-
}
|
|
6668
|
-
const flagEntries = context.teamDb.searchEntries("", {
|
|
6669
|
-
entryType: "flag",
|
|
6670
|
-
limit: 100
|
|
6671
|
-
});
|
|
6672
|
-
const enriched = enrichWithAuthor(flagEntries, context);
|
|
6673
|
-
const activeFlags = enriched.map((entry) => {
|
|
6674
|
-
const flagCtx = parseFlagContext(entry.autoContext);
|
|
6675
|
-
if (!flagCtx || flagCtx.resolved) return null;
|
|
6676
|
-
return {
|
|
6677
|
-
id: entry.id,
|
|
6678
|
-
flag_type: flagCtx.flag_type,
|
|
6679
|
-
target_user: flagCtx.target_user,
|
|
6680
|
-
link: flagCtx.link,
|
|
6681
|
-
author: entry.author ? sanitizeAuthor(entry.author) : null,
|
|
6682
|
-
timestamp: entry.timestamp,
|
|
6683
|
-
preview: markUntrustedContent(
|
|
6684
|
-
entry.content.slice(0, 120) + (entry.content.length > 120 ? "..." : "")
|
|
6685
|
-
),
|
|
6686
|
-
tags: entry.tags,
|
|
6687
|
-
projectNumber: entry.projectNumber ?? null
|
|
6688
|
-
};
|
|
6689
|
-
}).filter((f) => f !== null);
|
|
6690
|
-
const lastModified = activeFlags[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6691
|
-
return {
|
|
6692
|
-
data: {
|
|
6693
|
-
activeFlags,
|
|
6694
|
-
count: activeFlags.length
|
|
6695
|
-
},
|
|
6696
|
-
annotations: { lastModified }
|
|
6697
|
-
};
|
|
6698
|
-
}
|
|
6699
|
-
},
|
|
6700
|
-
{
|
|
6701
|
-
uri: "memory://flags/vocabulary",
|
|
6702
|
-
name: "Flag Vocabulary",
|
|
6703
|
-
title: "Hush Protocol Flag Vocabulary",
|
|
6704
|
-
description: "Returns the configured flag vocabulary for the Hush Protocol. Static resource reflecting server-wide configuration.",
|
|
6705
|
-
mimeType: "application/json",
|
|
6706
|
-
icons: [ICON_FLAG],
|
|
6707
|
-
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
6708
|
-
handler: (_uri, context) => {
|
|
6709
|
-
const config = context.briefingConfig;
|
|
6710
|
-
const custom = config?.flagVocabulary;
|
|
6711
|
-
const hasCustom = Array.isArray(custom) && custom.length > 0;
|
|
6712
|
-
const vocabulary = hasCustom ? custom.map(String) : [...DEFAULT_FLAG_VOCABULARY];
|
|
6713
|
-
return {
|
|
6714
|
-
data: {
|
|
6715
|
-
vocabulary,
|
|
6716
|
-
count: vocabulary.length,
|
|
6717
|
-
isDefault: !hasCustom
|
|
6718
|
-
}
|
|
6719
|
-
};
|
|
6720
|
-
}
|
|
6721
|
-
}
|
|
6722
|
-
];
|
|
6723
|
-
return resources.map((resource) => ({
|
|
6724
|
-
...resource,
|
|
6725
|
-
capabilities: {
|
|
6726
|
-
...resource.capabilities,
|
|
6727
|
-
requiresTeamScope: true
|
|
6728
|
-
}
|
|
6729
|
-
}));
|
|
6730
|
-
}
|
|
6731
|
-
|
|
6732
|
-
// src/handlers/resources/help.ts
|
|
6733
|
-
var GROUP_DESCRIPTIONS = {
|
|
6734
|
-
core: "Create, read, update, delete journal entries and manage recent activity",
|
|
6735
|
-
search: "Full-text search, semantic search, date range queries, and vector index stats",
|
|
6736
|
-
analytics: "Entry analytics, importance scoring, and productivity trends",
|
|
6737
|
-
relationships: "Link and visualize relationships between journal entries",
|
|
6738
|
-
io: "Import and export journal data \u2014 JSON export, Markdown round-trip (export/import)",
|
|
6739
|
-
admin: "Update entries, rebuild indexes, and manage the vector search index",
|
|
6740
|
-
github: "GitHub integration \u2014 issues, PRs, milestones, workflow runs, Kanban boards",
|
|
6741
|
-
backup: "Create and restore database backups",
|
|
6742
|
-
team: "Team collaboration \u2014 shared entries, cross-project insights, team analytics",
|
|
6743
|
-
codemode: "Sandboxed JavaScript execution with access to the journal API"
|
|
6744
|
-
};
|
|
6745
|
-
var ZOD_TYPE_DISPLAY = {
|
|
6746
|
-
ZodString: "string",
|
|
6747
|
-
ZodNumber: "number",
|
|
6748
|
-
ZodBoolean: "boolean",
|
|
6749
|
-
ZodArray: "array",
|
|
6750
|
-
ZodObject: "object",
|
|
6751
|
-
ZodEnum: "enum",
|
|
6752
|
-
ZodNativeEnum: "enum",
|
|
6753
|
-
ZodLiteral: "literal",
|
|
6754
|
-
ZodUnion: "union",
|
|
6755
|
-
ZodIntersection: "intersection",
|
|
6756
|
-
ZodRecord: "record",
|
|
6757
|
-
ZodTuple: "tuple",
|
|
6758
|
-
ZodAny: "any",
|
|
6759
|
-
ZodUnknown: "unknown",
|
|
6760
|
-
ZodNull: "null",
|
|
6761
|
-
ZodUndefined: "undefined",
|
|
6762
|
-
ZodVoid: "void"
|
|
6763
|
-
};
|
|
6764
|
-
var ZOD_OPTIONAL_WRAPPERS = /* @__PURE__ */ new Set([
|
|
6765
|
-
"ZodOptional",
|
|
6766
|
-
"ZodDefault",
|
|
6767
|
-
"ZodNullable",
|
|
6768
|
-
"optional",
|
|
6769
|
-
"default",
|
|
6770
|
-
"nullable"
|
|
6771
|
-
]);
|
|
6772
|
-
function peelZodType(def) {
|
|
6773
|
-
let isOptional = false;
|
|
6774
|
-
let description;
|
|
6775
|
-
let current = def;
|
|
6776
|
-
for (; ; ) {
|
|
6777
|
-
if (description === void 0 && typeof current["description"] === "string") {
|
|
6778
|
-
description = current["description"];
|
|
6779
|
-
}
|
|
6780
|
-
const tn = current["typeName"] ?? current["type"];
|
|
6781
|
-
if (!tn) break;
|
|
6782
|
-
if (ZOD_OPTIONAL_WRAPPERS.has(tn)) {
|
|
6783
|
-
isOptional = true;
|
|
6784
|
-
const inner = current["innerType"];
|
|
6785
|
-
if (!inner?._def) break;
|
|
6786
|
-
current = inner._def;
|
|
6787
|
-
} else {
|
|
6788
|
-
const normalized = tn.replace("Zod", "").toLowerCase();
|
|
6789
|
-
const displayName = ZOD_TYPE_DISPLAY[tn] ?? normalized;
|
|
6790
|
-
return { typeName: displayName, isOptional, description };
|
|
6791
|
-
}
|
|
6792
|
-
}
|
|
6793
|
-
return { typeName: "unknown", isOptional, description };
|
|
6794
|
-
}
|
|
6795
|
-
function extractParameters(inputSchema) {
|
|
6796
|
-
if (inputSchema === void 0 || inputSchema === null || typeof inputSchema !== "object") {
|
|
6797
|
-
return [];
|
|
6798
|
-
}
|
|
6799
|
-
const schema = inputSchema;
|
|
6800
|
-
const rawShape = schema["shape"];
|
|
6801
|
-
if (rawShape === void 0 || rawShape === null || typeof rawShape !== "object") {
|
|
6802
|
-
return [];
|
|
6803
|
-
}
|
|
6804
|
-
const shape = rawShape;
|
|
6805
|
-
const params = [];
|
|
6806
|
-
for (const [name, fieldSchema] of Object.entries(shape)) {
|
|
6807
|
-
if (fieldSchema === void 0 || fieldSchema === null || typeof fieldSchema !== "object") {
|
|
6808
|
-
continue;
|
|
6809
|
-
}
|
|
6810
|
-
const field = fieldSchema;
|
|
6811
|
-
const rawDef = field["_def"];
|
|
6812
|
-
if (!rawDef) continue;
|
|
6813
|
-
const { typeName, isOptional, description } = peelZodType(rawDef);
|
|
6814
|
-
params.push({
|
|
6815
|
-
name,
|
|
6816
|
-
type: typeName,
|
|
6817
|
-
required: !isOptional,
|
|
6818
|
-
...description !== void 0 ? { description } : {}
|
|
6819
|
-
});
|
|
6820
|
-
}
|
|
6821
|
-
return params;
|
|
6822
|
-
}
|
|
6823
|
-
function getHelpResourceDefinitions() {
|
|
6824
|
-
return [
|
|
6825
|
-
{
|
|
6826
|
-
uri: "memory://help",
|
|
6827
|
-
name: "Help \u2014 Tool Groups",
|
|
6828
|
-
title: "Tool Group Overview",
|
|
6829
|
-
description: "Lists all tool groups with tool counts and descriptions. Read memory://help/{group} for per-tool details.",
|
|
6830
|
-
mimeType: "application/json",
|
|
6831
|
-
icons: [ICON_BRIEFING],
|
|
6832
|
-
annotations: ASSISTANT_FOCUSED,
|
|
6833
|
-
handler: async (_uri, context) => {
|
|
6834
|
-
const tools = await getAllToolDefinitionsAsync(context);
|
|
6835
|
-
const groups = /* @__PURE__ */ new Map();
|
|
6836
|
-
for (const tool of tools) {
|
|
6837
|
-
const existing = groups.get(tool.group);
|
|
6838
|
-
if (existing) {
|
|
6839
|
-
existing.names.push(tool.name);
|
|
6840
|
-
if (!tool.annotations?.readOnlyHint) existing.readOnly = false;
|
|
6841
|
-
} else {
|
|
6842
|
-
groups.set(tool.group, {
|
|
6843
|
-
names: [tool.name],
|
|
6844
|
-
readOnly: tool.annotations?.readOnlyHint ?? false
|
|
6845
|
-
});
|
|
6846
|
-
}
|
|
6847
|
-
}
|
|
6848
|
-
const groupList = Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, info]) => ({
|
|
6849
|
-
name,
|
|
6850
|
-
description: GROUP_DESCRIPTIONS[name] ?? name,
|
|
6851
|
-
toolCount: info.names.length,
|
|
6852
|
-
readOnly: info.readOnly,
|
|
6853
|
-
tools: info.names.sort(),
|
|
6854
|
-
helpUri: `memory://help/${name}`
|
|
6855
|
-
}));
|
|
6856
|
-
return {
|
|
6857
|
-
data: {
|
|
6858
|
-
totalTools: tools.length,
|
|
6859
|
-
totalGroups: groupList.length,
|
|
6860
|
-
groups: groupList,
|
|
6861
|
-
gotchas: "memory://help/gotchas",
|
|
6862
|
-
hint: "Read memory://help/{group} for detailed parameter info on each tool. Read memory://help/gotchas for field notes and critical usage patterns."
|
|
6863
|
-
}
|
|
6864
|
-
};
|
|
6865
|
-
}
|
|
6866
|
-
},
|
|
6867
|
-
{
|
|
6868
|
-
uri: "memory://help/{group}",
|
|
6869
|
-
name: "Help \u2014 Tool Group Detail",
|
|
6870
|
-
title: "Per-Group Tool Reference",
|
|
6871
|
-
description: "Detailed tool reference for a specific group with parameters and annotations.",
|
|
6872
|
-
mimeType: "application/json",
|
|
6873
|
-
icons: [ICON_BRIEFING],
|
|
6874
|
-
annotations: ASSISTANT_FOCUSED,
|
|
6875
|
-
handler: async (uri, context) => {
|
|
6876
|
-
const match = /memory:\/\/help\/([a-z]+)/.exec(uri);
|
|
6877
|
-
const groupName = match?.[1];
|
|
6878
|
-
if (!groupName) {
|
|
6879
|
-
return {
|
|
6880
|
-
data: {
|
|
6881
|
-
error: "Invalid group name in URI",
|
|
6882
|
-
hint: "Read memory://help for a list of available groups."
|
|
6883
|
-
}
|
|
6884
|
-
};
|
|
6885
|
-
}
|
|
6886
|
-
const tools = await getAllToolDefinitionsAsync(context);
|
|
6887
|
-
const groupTools = tools.filter((t) => t.group === groupName);
|
|
6888
|
-
if (groupTools.length === 0) {
|
|
6889
|
-
const availableGroups = [...new Set(tools.map((t) => t.group))].sort();
|
|
6890
|
-
return {
|
|
6891
|
-
data: {
|
|
6892
|
-
error: `Group "${groupName}" not found`,
|
|
6893
|
-
availableGroups,
|
|
6894
|
-
hint: "Read memory://help for a list of available groups."
|
|
6895
|
-
}
|
|
6896
|
-
};
|
|
6897
|
-
}
|
|
6898
|
-
const toolDetails = groupTools.map((tool) => ({
|
|
6899
|
-
name: tool.name,
|
|
6900
|
-
title: tool.title,
|
|
6901
|
-
description: tool.description,
|
|
6902
|
-
parameters: extractParameters(tool.inputSchema),
|
|
6903
|
-
annotations: {
|
|
6904
|
-
readOnly: tool.annotations?.readOnlyHint ?? false,
|
|
6905
|
-
destructive: tool.annotations?.destructiveHint ?? false,
|
|
6906
|
-
idempotent: tool.annotations?.idempotentHint ?? false,
|
|
6907
|
-
openWorld: tool.annotations?.openWorldHint ?? false
|
|
6908
|
-
},
|
|
6909
|
-
hasOutputSchema: tool.outputSchema !== void 0
|
|
6910
|
-
}));
|
|
6911
|
-
return {
|
|
6912
|
-
data: {
|
|
6913
|
-
group: groupName,
|
|
6914
|
-
description: GROUP_DESCRIPTIONS[groupName] ?? groupName,
|
|
6915
|
-
toolCount: toolDetails.length,
|
|
6916
|
-
tools: toolDetails
|
|
6917
|
-
}
|
|
6918
|
-
};
|
|
6919
|
-
}
|
|
6920
|
-
},
|
|
6921
|
-
{
|
|
6922
|
-
uri: "memory://help/gotchas",
|
|
6923
|
-
name: "Help \u2014 Field Notes & Gotchas",
|
|
6924
|
-
title: "Critical Usage Patterns",
|
|
6925
|
-
description: "Field notes, edge cases, and critical usage patterns for memory-journal-mcp tools.",
|
|
6926
|
-
mimeType: "text/markdown",
|
|
6927
|
-
icons: [ICON_BRIEFING],
|
|
6928
|
-
annotations: ASSISTANT_FOCUSED,
|
|
6929
|
-
handler: () => {
|
|
6930
|
-
return {
|
|
6931
|
-
data: GOTCHAS_CONTENT
|
|
6932
|
-
};
|
|
6933
|
-
}
|
|
6934
|
-
}
|
|
6935
|
-
];
|
|
6936
|
-
}
|
|
6937
|
-
var toolIndexModule = null;
|
|
6938
|
-
async function getAllToolDefinitionsAsync(context) {
|
|
6939
|
-
try {
|
|
6940
|
-
toolIndexModule ??= await import('./tools-P4VGG4FH.js');
|
|
6941
|
-
if (toolIndexModule === null) return [];
|
|
6942
|
-
const tools = toolIndexModule.getTools(context.db, null);
|
|
6943
|
-
return tools.map((t) => ({
|
|
6944
|
-
name: t.name,
|
|
6945
|
-
title: t.title ?? t.name,
|
|
6946
|
-
description: t.description,
|
|
6947
|
-
group: inferGroupFromName(t.name),
|
|
6948
|
-
inputSchema: t.inputSchema,
|
|
6949
|
-
outputSchema: t.outputSchema,
|
|
6950
|
-
annotations: t.annotations
|
|
6951
|
-
}));
|
|
6952
|
-
} catch (e) {
|
|
6953
|
-
logger.error("HELP_LOAD_TOOLS_FAILED", {
|
|
6954
|
-
error: e instanceof Error ? e.message : String(e)
|
|
6955
|
-
});
|
|
6956
|
-
return [];
|
|
6957
|
-
}
|
|
6958
|
-
}
|
|
6959
|
-
function inferGroupFromName(name) {
|
|
6960
|
-
if (name.startsWith("team_")) return "team";
|
|
6961
|
-
if (name.startsWith("mj_")) return "codemode";
|
|
6962
|
-
const groupMap = {
|
|
6963
|
-
// core (6)
|
|
6964
|
-
create_entry: "core",
|
|
6965
|
-
create_entry_minimal: "core",
|
|
6966
|
-
get_entry_by_id: "core",
|
|
6967
|
-
get_recent_entries: "core",
|
|
6968
|
-
test_simple: "core",
|
|
6969
|
-
list_tags: "core",
|
|
6970
|
-
// search (4)
|
|
6971
|
-
search_entries: "search",
|
|
6972
|
-
search_by_date_range: "search",
|
|
6973
|
-
semantic_search: "search",
|
|
6974
|
-
get_vector_index_stats: "search",
|
|
6975
|
-
// analytics (2)
|
|
6976
|
-
get_statistics: "analytics",
|
|
6977
|
-
get_cross_project_insights: "analytics",
|
|
6978
|
-
// relationships (2)
|
|
6979
|
-
link_entries: "relationships",
|
|
6980
|
-
visualize_relationships: "relationships",
|
|
6981
|
-
// io (3)
|
|
6982
|
-
export_entries: "io",
|
|
6983
|
-
export_markdown: "io",
|
|
6984
|
-
import_markdown: "io",
|
|
6985
|
-
// admin (5)
|
|
6986
|
-
update_entry: "admin",
|
|
6987
|
-
delete_entry: "admin",
|
|
6988
|
-
merge_tags: "admin",
|
|
6989
|
-
rebuild_vector_index: "admin",
|
|
6990
|
-
add_to_vector_index: "admin",
|
|
6991
|
-
// backup (4)
|
|
6992
|
-
backup_journal: "backup",
|
|
6993
|
-
list_backups: "backup",
|
|
6994
|
-
restore_backup: "backup",
|
|
6995
|
-
cleanup_backups: "backup",
|
|
6996
|
-
// github (16)
|
|
6997
|
-
get_github_issues: "github",
|
|
6998
|
-
get_github_prs: "github",
|
|
6999
|
-
get_github_issue: "github",
|
|
7000
|
-
get_github_pr: "github",
|
|
7001
|
-
get_github_context: "github",
|
|
7002
|
-
get_github_milestones: "github",
|
|
7003
|
-
get_github_milestone: "github",
|
|
7004
|
-
create_github_milestone: "github",
|
|
7005
|
-
update_github_milestone: "github",
|
|
7006
|
-
delete_github_milestone: "github",
|
|
7007
|
-
get_kanban_board: "github",
|
|
7008
|
-
move_kanban_item: "github",
|
|
7009
|
-
create_github_issue_with_entry: "github",
|
|
7010
|
-
close_github_issue_with_entry: "github",
|
|
7011
|
-
get_repo_insights: "github",
|
|
7012
|
-
get_copilot_reviews: "github"
|
|
7013
|
-
};
|
|
7014
|
-
return groupMap[name] ?? "core";
|
|
7015
|
-
}
|
|
7016
|
-
|
|
7017
|
-
// src/handlers/resources/insights.ts
|
|
7018
|
-
var digestInsightsResource = {
|
|
7019
|
-
uri: "memory://insights/digest",
|
|
7020
|
-
name: "Analytics Digest",
|
|
7021
|
-
title: "Latest Analytics Digest Snapshot",
|
|
7022
|
-
description: "Full pre-computed analytics digest with activity trends, significance spikes, stale projects, relationship density, and top importance entries. Updated by the scheduled digest job.",
|
|
7023
|
-
mimeType: "application/json",
|
|
7024
|
-
icons: [ICON_ANALYTICS],
|
|
7025
|
-
annotations: {
|
|
7026
|
-
...withPriority(0.5, ASSISTANT_FOCUSED)
|
|
7027
|
-
},
|
|
7028
|
-
handler: (_uri, context) => {
|
|
7029
|
-
const lastModified = (/* @__PURE__ */ new Date()).toISOString();
|
|
7030
|
-
const schedulerDigest = context.scheduler?.getLatestDigest?.();
|
|
7031
|
-
if (schedulerDigest) {
|
|
7032
|
-
return {
|
|
7033
|
-
data: { success: true, snapshot: schedulerDigest },
|
|
7034
|
-
annotations: { lastModified }
|
|
7035
|
-
};
|
|
7036
|
-
}
|
|
7037
|
-
const dbSnapshot = context.db?.getLatestAnalyticsSnapshot?.("digest");
|
|
7038
|
-
if (dbSnapshot) {
|
|
7039
|
-
return {
|
|
7040
|
-
data: {
|
|
7041
|
-
success: true,
|
|
7042
|
-
snapshot: dbSnapshot.data,
|
|
7043
|
-
computedAt: dbSnapshot.createdAt,
|
|
7044
|
-
source: "persisted"
|
|
7045
|
-
},
|
|
7046
|
-
annotations: { lastModified: dbSnapshot.createdAt }
|
|
7047
|
-
};
|
|
7048
|
-
}
|
|
7049
|
-
return {
|
|
7050
|
-
data: {
|
|
7051
|
-
success: true,
|
|
7052
|
-
snapshot: null,
|
|
7053
|
-
message: "No digest available \u2014 enable with --digest-interval <minutes> (HTTP transport only)"
|
|
7054
|
-
},
|
|
7055
|
-
annotations: { lastModified }
|
|
7056
|
-
};
|
|
7057
|
-
}
|
|
7058
|
-
};
|
|
7059
|
-
var teamCollaborationResource = {
|
|
7060
|
-
uri: "memory://insights/team-collaboration",
|
|
7061
|
-
name: "Team Collaboration Matrix",
|
|
7062
|
-
title: "Team Collaboration Insights",
|
|
7063
|
-
description: "Cross-author collaboration metrics: activity heatmap, cross-linking patterns, and impact factor per contributor. Requires TEAM_DB_PATH.",
|
|
7064
|
-
mimeType: "application/json",
|
|
7065
|
-
icons: [ICON_ANALYTICS],
|
|
7066
|
-
annotations: {
|
|
7067
|
-
...withPriority(0.4, ASSISTANT_FOCUSED)
|
|
7068
|
-
},
|
|
7069
|
-
handler: (_uri, context) => {
|
|
7070
|
-
const lastModified = (/* @__PURE__ */ new Date()).toISOString();
|
|
7071
|
-
if (!context.teamDb) {
|
|
7072
|
-
return {
|
|
7073
|
-
data: {
|
|
7074
|
-
success: true,
|
|
7075
|
-
matrix: null,
|
|
7076
|
-
message: "Team database not configured \u2014 set TEAM_DB_PATH to enable."
|
|
7077
|
-
},
|
|
7078
|
-
annotations: { lastModified }
|
|
7079
|
-
};
|
|
7080
|
-
}
|
|
7081
|
-
try {
|
|
7082
|
-
const matrix = context.teamDb.getTeamCollaborationMatrix({
|
|
7083
|
-
period: "month",
|
|
7084
|
-
limit: 100
|
|
7085
|
-
});
|
|
7086
|
-
return {
|
|
7087
|
-
data: { success: true, matrix },
|
|
7088
|
-
annotations: { lastModified }
|
|
7089
|
-
};
|
|
7090
|
-
} catch (error) {
|
|
7091
|
-
return {
|
|
7092
|
-
data: {
|
|
7093
|
-
success: false,
|
|
7094
|
-
error: error instanceof Error ? error.message : String(error)
|
|
7095
|
-
},
|
|
7096
|
-
annotations: { lastModified }
|
|
7097
|
-
};
|
|
7098
|
-
}
|
|
7099
|
-
}
|
|
7100
|
-
};
|
|
7101
|
-
function getInsightResourceDefinitions() {
|
|
7102
|
-
return [digestInsightsResource, teamCollaborationResource];
|
|
7103
|
-
}
|
|
7104
|
-
|
|
7105
|
-
// src/handlers/resources/index.ts
|
|
7106
|
-
function getResources() {
|
|
7107
|
-
const resources = getAllResourceDefinitions();
|
|
7108
|
-
return resources.map((r) => ({
|
|
7109
|
-
uri: r.uri,
|
|
7110
|
-
name: r.name,
|
|
7111
|
-
description: r.description,
|
|
7112
|
-
mimeType: r.mimeType,
|
|
7113
|
-
annotations: r.annotations,
|
|
7114
|
-
icons: r.icons
|
|
7115
|
-
}));
|
|
7116
|
-
}
|
|
7117
|
-
function isResourceResult(result) {
|
|
7118
|
-
return result !== null && typeof result === "object" && "data" in result && result["data"] !== void 0;
|
|
7119
|
-
}
|
|
7120
|
-
function getBaseUri(uri) {
|
|
7121
|
-
if (uri.startsWith("memory://")) {
|
|
7122
|
-
const withoutScheme = uri.slice("memory://".length);
|
|
7123
|
-
const queryIndex = withoutScheme.indexOf("?");
|
|
7124
|
-
const hashIndex = withoutScheme.indexOf("#");
|
|
7125
|
-
let endIndex = withoutScheme.length;
|
|
7126
|
-
if (queryIndex !== -1 && queryIndex < endIndex) endIndex = queryIndex;
|
|
7127
|
-
if (hashIndex !== -1 && hashIndex < endIndex) endIndex = hashIndex;
|
|
7128
|
-
return "memory://" + withoutScheme.slice(0, endIndex);
|
|
7129
|
-
}
|
|
7130
|
-
try {
|
|
7131
|
-
const url = new URL(uri);
|
|
7132
|
-
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
7133
|
-
} catch {
|
|
7134
|
-
return uri;
|
|
7135
|
-
}
|
|
7136
|
-
}
|
|
7137
|
-
async function readResource(uri, db, vectorManager, filterConfig, github, scheduler, teamDb, briefingConfig, runtime) {
|
|
7138
|
-
const resources = getAllResourceDefinitions(runtime);
|
|
7139
|
-
const context = {
|
|
7140
|
-
db,
|
|
7141
|
-
teamDb,
|
|
7142
|
-
vectorManager,
|
|
7143
|
-
filterConfig,
|
|
7144
|
-
github,
|
|
7145
|
-
scheduler,
|
|
7146
|
-
briefingConfig,
|
|
7147
|
-
runtime
|
|
7148
|
-
};
|
|
7149
|
-
const baseUri = getBaseUri(uri);
|
|
7150
|
-
const exactMatch = resources.find((r) => r.uri === baseUri);
|
|
7151
|
-
if (exactMatch) {
|
|
7152
|
-
enforceAccessBoundary(baseUri, "resource", exactMatch.capabilities, runtime?.auditLogger);
|
|
7153
|
-
const result = await Promise.resolve(exactMatch.handler(uri, context));
|
|
7154
|
-
if (isResourceResult(result)) {
|
|
7155
|
-
return { data: result.data, annotations: result.annotations };
|
|
7156
|
-
}
|
|
7157
|
-
return { data: result };
|
|
7158
|
-
}
|
|
7159
|
-
for (const resource of resources) {
|
|
7160
|
-
if (resource.uri.includes("{")) {
|
|
7161
|
-
const escapedUri = resource.uri.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7162
|
-
const pattern = escapedUri.replace(
|
|
7163
|
-
/\\{([^}]+)\\}/g,
|
|
7164
|
-
(_match, paramName) => {
|
|
7165
|
-
const cleanParam = paramName.replace(/^\\?\+/, "");
|
|
7166
|
-
return cleanParam === "repo" ? "(.+)" : "([^/]+)";
|
|
7167
|
-
}
|
|
7168
|
-
);
|
|
7169
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
7170
|
-
if (regex.test(baseUri)) {
|
|
7171
|
-
enforceAccessBoundary(
|
|
7172
|
-
baseUri,
|
|
7173
|
-
"resource",
|
|
7174
|
-
resource.capabilities,
|
|
7175
|
-
runtime?.auditLogger
|
|
7176
|
-
);
|
|
7177
|
-
const result = await Promise.resolve(resource.handler(uri, context));
|
|
7178
|
-
if (isResourceResult(result)) {
|
|
7179
|
-
return { data: result.data, annotations: result.annotations };
|
|
7180
|
-
}
|
|
7181
|
-
return { data: result };
|
|
7182
|
-
}
|
|
7183
|
-
}
|
|
7184
|
-
}
|
|
7185
|
-
throw new ResourceNotFoundError("Resource", uri);
|
|
7186
|
-
}
|
|
7187
|
-
function getAllResourceDefinitions(runtime) {
|
|
7188
|
-
return [
|
|
7189
|
-
...getCoreResourceDefinitions(),
|
|
7190
|
-
...getDynamicGraphResourceDefinitions(),
|
|
7191
|
-
...getGitHubResourceDefinitions(),
|
|
7192
|
-
...getTemplateResourceDefinitions(),
|
|
7193
|
-
...getTeamResourceDefinitions(),
|
|
7194
|
-
...getHelpResourceDefinitions(),
|
|
7195
|
-
...getInsightResourceDefinitions(),
|
|
7196
|
-
// Audit resource — bound to the runtime's instance audit logger
|
|
7197
|
-
getAuditResourceDef(() => runtime?.auditLogger ?? null)
|
|
7198
|
-
];
|
|
7199
|
-
}
|
|
7200
|
-
|
|
7201
|
-
// src/server/scheduler.ts
|
|
7202
|
-
var Scheduler = class {
|
|
7203
|
-
options;
|
|
7204
|
-
db;
|
|
7205
|
-
vectorManager;
|
|
7206
|
-
timers = [];
|
|
7207
|
-
started = false;
|
|
7208
|
-
runtime;
|
|
7209
|
-
constructor(options, db, vectorManager, runtime) {
|
|
7210
|
-
this.options = options;
|
|
7211
|
-
this.db = db;
|
|
7212
|
-
this.vectorManager = vectorManager ?? null;
|
|
7213
|
-
this.runtime = runtime;
|
|
7214
|
-
}
|
|
7215
|
-
/**
|
|
7216
|
-
* Start all enabled scheduled jobs.
|
|
7217
|
-
* Each job runs on its own interval and failures are isolated.
|
|
7218
|
-
*/
|
|
7219
|
-
start() {
|
|
7220
|
-
if (this.started) {
|
|
7221
|
-
logger.warning("Scheduler already started, ignoring duplicate start()", {
|
|
7222
|
-
module: "Scheduler"
|
|
7223
|
-
});
|
|
7224
|
-
return;
|
|
7225
|
-
}
|
|
7226
|
-
this.started = true;
|
|
7227
|
-
process.on("SIGTERM", () => this.stop());
|
|
7228
|
-
const { backupIntervalMinutes, vacuumIntervalMinutes, rebuildIndexIntervalMinutes } = this.options;
|
|
7229
|
-
if (backupIntervalMinutes > 0) {
|
|
7230
|
-
this.scheduleJob("backup", backupIntervalMinutes, () => this.runBackup());
|
|
7231
|
-
}
|
|
7232
|
-
if (vacuumIntervalMinutes > 0) {
|
|
7233
|
-
this.scheduleJob("vacuum", vacuumIntervalMinutes, () => {
|
|
7234
|
-
this.runVacuumOptimize();
|
|
7235
|
-
return Promise.resolve();
|
|
3000
|
+
if (vacuumIntervalMinutes > 0) {
|
|
3001
|
+
this.scheduleJob("vacuum", vacuumIntervalMinutes, () => {
|
|
3002
|
+
this.runVacuumOptimize();
|
|
3003
|
+
return Promise.resolve();
|
|
7236
3004
|
});
|
|
7237
3005
|
}
|
|
7238
3006
|
if (rebuildIndexIntervalMinutes > 0) {
|
|
@@ -8103,17 +3871,17 @@ function extractBearerToken(authHeader) {
|
|
|
8103
3871
|
const token = tokenPart.trim();
|
|
8104
3872
|
return token.length > 0 ? token : null;
|
|
8105
3873
|
}
|
|
8106
|
-
function isPublicPath(
|
|
8107
|
-
if (
|
|
3874
|
+
function isPublicPath(path3, publicPaths) {
|
|
3875
|
+
if (path3 === "/.well-known/oauth-authorization-server" || path3 === "/.well-known/jwks.json") {
|
|
8108
3876
|
return true;
|
|
8109
3877
|
}
|
|
8110
3878
|
for (const pattern of publicPaths) {
|
|
8111
|
-
if (pattern ===
|
|
3879
|
+
if (pattern === path3) {
|
|
8112
3880
|
return true;
|
|
8113
3881
|
}
|
|
8114
3882
|
if (pattern.endsWith("/*")) {
|
|
8115
3883
|
const prefix = pattern.slice(0, -2);
|
|
8116
|
-
if (
|
|
3884
|
+
if (path3 === prefix || path3.startsWith(prefix + "/")) {
|
|
8117
3885
|
return true;
|
|
8118
3886
|
}
|
|
8119
3887
|
}
|
|
@@ -8779,6 +4547,37 @@ var HttpTransport = class {
|
|
|
8779
4547
|
});
|
|
8780
4548
|
const maxBody = this.config.maxBodySize ?? DEFAULT_MAX_BODY_BYTES;
|
|
8781
4549
|
this.app.use(express.json({ limit: maxBody }));
|
|
4550
|
+
this.app.use((err, _req, res, next) => {
|
|
4551
|
+
if (err !== null && typeof err === "object" && "type" in err) {
|
|
4552
|
+
const errRecord = err;
|
|
4553
|
+
if (typeof errRecord["type"] === "string") {
|
|
4554
|
+
const errType = errRecord["type"];
|
|
4555
|
+
if (errType === "entity.too.large") {
|
|
4556
|
+
res.status(413).json({
|
|
4557
|
+
jsonrpc: "2.0",
|
|
4558
|
+
error: {
|
|
4559
|
+
code: -32e3,
|
|
4560
|
+
message: "Payload Too Large: Maximum request body size exceeded"
|
|
4561
|
+
},
|
|
4562
|
+
id: null
|
|
4563
|
+
});
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
if (errType === "entity.parse.failed") {
|
|
4567
|
+
res.status(400).json({
|
|
4568
|
+
jsonrpc: "2.0",
|
|
4569
|
+
error: {
|
|
4570
|
+
code: -32700,
|
|
4571
|
+
message: "Parse error: Invalid JSON"
|
|
4572
|
+
},
|
|
4573
|
+
id: null
|
|
4574
|
+
});
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
next(err);
|
|
4580
|
+
});
|
|
8782
4581
|
if (this.config.enableRateLimit !== false) {
|
|
8783
4582
|
this.app.use((req, res, next) => {
|
|
8784
4583
|
const result = checkRateLimit(req, this.config, this.rateLimitMap);
|
|
@@ -9140,12 +4939,70 @@ var ServerRuntime = class {
|
|
|
9140
4939
|
githubClientPool = null;
|
|
9141
4940
|
};
|
|
9142
4941
|
|
|
4942
|
+
// src/server/auto-prune.ts
|
|
4943
|
+
async function runAutoPrune(db, options) {
|
|
4944
|
+
const { olderThanDays, importanceThreshold } = options;
|
|
4945
|
+
let backupFile = null;
|
|
4946
|
+
try {
|
|
4947
|
+
const backup = await db.exportToFile("pre-prune-backup");
|
|
4948
|
+
backupFile = backup.filename;
|
|
4949
|
+
logger.info("Pre-prune backup created", {
|
|
4950
|
+
module: "AutoPrune",
|
|
4951
|
+
context: { filename: backup.filename, sizeBytes: backup.sizeBytes }
|
|
4952
|
+
});
|
|
4953
|
+
} catch (backupError) {
|
|
4954
|
+
logger.warning("Failed to create pre-prune backup \u2014 proceeding anyway", {
|
|
4955
|
+
module: "AutoPrune",
|
|
4956
|
+
error: backupError instanceof Error ? backupError.message : String(backupError)
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
const prunedCount = db.pruneByImportance(olderThanDays, importanceThreshold);
|
|
4960
|
+
if (prunedCount === 0) {
|
|
4961
|
+
logger.info("Auto-prune: no candidates found", {
|
|
4962
|
+
module: "AutoPrune",
|
|
4963
|
+
olderThanDays,
|
|
4964
|
+
importanceThreshold
|
|
4965
|
+
});
|
|
4966
|
+
} else {
|
|
4967
|
+
logger.info(`Auto-prune: soft-deleted ${String(prunedCount)} entries`, {
|
|
4968
|
+
module: "AutoPrune",
|
|
4969
|
+
olderThanDays,
|
|
4970
|
+
importanceThreshold
|
|
4971
|
+
});
|
|
4972
|
+
}
|
|
4973
|
+
if (prunedCount > 0) {
|
|
4974
|
+
try {
|
|
4975
|
+
db.cleanupStaleVectors();
|
|
4976
|
+
logger.info("Auto-prune: cleaned up stale vector embeddings", {
|
|
4977
|
+
module: "AutoPrune"
|
|
4978
|
+
});
|
|
4979
|
+
} catch (cleanupError) {
|
|
4980
|
+
logger.warning("Auto-prune: failed to clean stale vectors (non-critical)", {
|
|
4981
|
+
module: "AutoPrune",
|
|
4982
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
4983
|
+
});
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
return { prunedCount, backupFile, olderThanDays, importanceThreshold };
|
|
4987
|
+
}
|
|
4988
|
+
|
|
9143
4989
|
// src/server/mcp-server.ts
|
|
9144
4990
|
async function createServer(options) {
|
|
9145
4991
|
const { transport, dbPath, teamDbPath, toolFilter, defaultProjectNumber } = options;
|
|
9146
4992
|
const db = await DatabaseAdapterFactory.create(dbPath);
|
|
9147
4993
|
await db.initialize();
|
|
9148
4994
|
logger.info("Database initialized", { module: "McpServer", dbPath });
|
|
4995
|
+
if (options.pruneOlderThanDays !== void 0 && options.pruneOlderThanDays > 0) {
|
|
4996
|
+
const pruneResult = await runAutoPrune(db, {
|
|
4997
|
+
olderThanDays: options.pruneOlderThanDays,
|
|
4998
|
+
importanceThreshold: options.pruneImportanceThreshold ?? 0.15
|
|
4999
|
+
});
|
|
5000
|
+
logger.info("Auto-prune completed", {
|
|
5001
|
+
module: "McpServer",
|
|
5002
|
+
prunedCount: pruneResult.prunedCount,
|
|
5003
|
+
backupFile: pruneResult.backupFile
|
|
5004
|
+
});
|
|
5005
|
+
}
|
|
9149
5006
|
const runtime = new ServerRuntime();
|
|
9150
5007
|
if (options.auditConfig?.enabled) {
|
|
9151
5008
|
runtime.auditLogger = new AuditLogger(options.auditConfig);
|
|
@@ -9264,6 +5121,19 @@ async function createServer(options) {
|
|
|
9264
5121
|
scheduler = new Scheduler(options.scheduler, db, vectorManager, runtime);
|
|
9265
5122
|
}
|
|
9266
5123
|
}
|
|
5124
|
+
try {
|
|
5125
|
+
const existingSnapshot = db.getLatestAnalyticsSnapshot("digest");
|
|
5126
|
+
if (!existingSnapshot) {
|
|
5127
|
+
const digest = db.computeDigest();
|
|
5128
|
+
db.saveAnalyticsSnapshot("digest", digest);
|
|
5129
|
+
logger.info("Initial analytics digest seeded", { module: "McpServer" });
|
|
5130
|
+
}
|
|
5131
|
+
} catch (error) {
|
|
5132
|
+
logger.debug("Failed to seed initial digest (non-critical)", {
|
|
5133
|
+
module: "McpServer",
|
|
5134
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
9267
5137
|
const resources = getResources();
|
|
9268
5138
|
const prompts = getPrompts();
|
|
9269
5139
|
let allowedIoRoots = options.allowedIoRoots;
|
|
@@ -9410,12 +5280,13 @@ async function createServer(options) {
|
|
|
9410
5280
|
teamDb,
|
|
9411
5281
|
teamVectorManager
|
|
9412
5282
|
);
|
|
5283
|
+
const textContent = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
9413
5284
|
if (hasOutputSchema) {
|
|
9414
5285
|
return {
|
|
9415
5286
|
content: [
|
|
9416
5287
|
{
|
|
9417
5288
|
type: "text",
|
|
9418
|
-
text:
|
|
5289
|
+
text: textContent
|
|
9419
5290
|
}
|
|
9420
5291
|
],
|
|
9421
5292
|
structuredContent: result
|
|
@@ -9425,7 +5296,7 @@ async function createServer(options) {
|
|
|
9425
5296
|
content: [
|
|
9426
5297
|
{
|
|
9427
5298
|
type: "text",
|
|
9428
|
-
text:
|
|
5299
|
+
text: textContent
|
|
9429
5300
|
}
|
|
9430
5301
|
]
|
|
9431
5302
|
};
|
|
@@ -9491,6 +5362,115 @@ async function createServer(options) {
|
|
|
9491
5362
|
};
|
|
9492
5363
|
registerResources(server, resources, handleResourceRead, runtime);
|
|
9493
5364
|
registerPrompts(server, prompts, db, teamDb, runtime);
|
|
5365
|
+
const serverObj = server.server;
|
|
5366
|
+
const requestHandlers = serverObj._requestHandlers;
|
|
5367
|
+
const originalHandler = requestHandlers?.get("tools/call");
|
|
5368
|
+
if (originalHandler) {
|
|
5369
|
+
requestHandlers.set("tools/call", async (request2, extra) => {
|
|
5370
|
+
try {
|
|
5371
|
+
const result = await originalHandler(request2, extra);
|
|
5372
|
+
if (result !== null && result !== void 0 && typeof result === "object" && "isError" in result && result.isError === true) {
|
|
5373
|
+
const resObj = result;
|
|
5374
|
+
if (Array.isArray(resObj.content) && resObj.content.length > 0) {
|
|
5375
|
+
const text = resObj.content[0]?.text;
|
|
5376
|
+
if (typeof text === "string" && text.includes("MCP error -32602")) {
|
|
5377
|
+
let errorMessage = text.replace(
|
|
5378
|
+
"MCP error -32602: Input validation error: ",
|
|
5379
|
+
""
|
|
5380
|
+
);
|
|
5381
|
+
try {
|
|
5382
|
+
const parts = text.split(": [");
|
|
5383
|
+
if (parts.length > 1) {
|
|
5384
|
+
const zodErrors = JSON.parse("[" + parts[1]);
|
|
5385
|
+
const formattedErrors = zodErrors.map((e) => {
|
|
5386
|
+
const path3 = e.path && e.path.length > 0 ? e.path.join(".") : "unknown";
|
|
5387
|
+
return `${path3}: ${e.message || "invalid"}`;
|
|
5388
|
+
}).join("; ");
|
|
5389
|
+
errorMessage = formattedErrors;
|
|
5390
|
+
}
|
|
5391
|
+
} catch {
|
|
5392
|
+
}
|
|
5393
|
+
const errorResult = {
|
|
5394
|
+
success: false,
|
|
5395
|
+
error: errorMessage,
|
|
5396
|
+
code: "VALIDATION_ERROR",
|
|
5397
|
+
category: "validation",
|
|
5398
|
+
suggestion: "Check input parameters against the tool schema",
|
|
5399
|
+
recoverable: false
|
|
5400
|
+
};
|
|
5401
|
+
const enriched = JSON.stringify({
|
|
5402
|
+
...errorResult,
|
|
5403
|
+
_meta: { tokenEstimate: 0 }
|
|
5404
|
+
});
|
|
5405
|
+
const tokenEstimate = Math.ceil(
|
|
5406
|
+
Buffer.byteLength(enriched, "utf8") / 4
|
|
5407
|
+
);
|
|
5408
|
+
const finalText = enriched.replace(
|
|
5409
|
+
'"tokenEstimate":0',
|
|
5410
|
+
`"tokenEstimate":${tokenEstimate}`
|
|
5411
|
+
);
|
|
5412
|
+
return {
|
|
5413
|
+
content: [
|
|
5414
|
+
{
|
|
5415
|
+
type: "text",
|
|
5416
|
+
text: finalText
|
|
5417
|
+
}
|
|
5418
|
+
],
|
|
5419
|
+
structuredContent: errorResult
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
return result;
|
|
5425
|
+
} catch (error) {
|
|
5426
|
+
const err = error;
|
|
5427
|
+
if (err?.code === -32602) {
|
|
5428
|
+
let errorMessage = err.message || "Validation Error";
|
|
5429
|
+
if (typeof errorMessage === "string" && errorMessage.includes("Invalid arguments for tool")) {
|
|
5430
|
+
try {
|
|
5431
|
+
const parts = errorMessage.split(": [");
|
|
5432
|
+
if (parts.length > 1) {
|
|
5433
|
+
const zodErrors = JSON.parse("[" + parts[1]);
|
|
5434
|
+
const formattedErrors = zodErrors.map((e) => {
|
|
5435
|
+
const path3 = e.path && e.path.length > 0 ? e.path.join(".") : "unknown";
|
|
5436
|
+
return `${path3}: ${e.message || "invalid"}`;
|
|
5437
|
+
}).join("; ");
|
|
5438
|
+
errorMessage = formattedErrors;
|
|
5439
|
+
}
|
|
5440
|
+
} catch {
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
const errorResult = {
|
|
5444
|
+
success: false,
|
|
5445
|
+
error: errorMessage,
|
|
5446
|
+
code: "VALIDATION_ERROR",
|
|
5447
|
+
category: "validation",
|
|
5448
|
+
suggestion: "Check input parameters against the tool schema",
|
|
5449
|
+
recoverable: false
|
|
5450
|
+
};
|
|
5451
|
+
const enriched = JSON.stringify({
|
|
5452
|
+
...errorResult,
|
|
5453
|
+
_meta: { tokenEstimate: 0 }
|
|
5454
|
+
});
|
|
5455
|
+
const tokenEstimate = Math.ceil(Buffer.byteLength(enriched, "utf8") / 4);
|
|
5456
|
+
const finalText = enriched.replace(
|
|
5457
|
+
'"tokenEstimate":0',
|
|
5458
|
+
`"tokenEstimate":${tokenEstimate}`
|
|
5459
|
+
);
|
|
5460
|
+
return {
|
|
5461
|
+
content: [
|
|
5462
|
+
{
|
|
5463
|
+
type: "text",
|
|
5464
|
+
text: finalText
|
|
5465
|
+
}
|
|
5466
|
+
],
|
|
5467
|
+
structuredContent: errorResult
|
|
5468
|
+
};
|
|
5469
|
+
}
|
|
5470
|
+
throw error;
|
|
5471
|
+
}
|
|
5472
|
+
});
|
|
5473
|
+
}
|
|
9494
5474
|
return server;
|
|
9495
5475
|
};
|
|
9496
5476
|
if (transport === "stdio") {
|
|
@@ -9544,4 +5524,4 @@ async function createServer(options) {
|
|
|
9544
5524
|
}
|
|
9545
5525
|
}
|
|
9546
5526
|
|
|
9547
|
-
export {
|
|
5527
|
+
export { createServer };
|