memory-journal-mcp 4.4.2 → 5.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/.github/workflows/codeql.yml +1 -6
- package/.github/workflows/docker-publish.yml +15 -49
- package/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/secrets-scanning.yml +4 -3
- package/.github/workflows/security-update.yml +3 -3
- package/CHANGELOG.md +213 -0
- package/CONTRIBUTING.md +132 -97
- package/DOCKER_README.md +184 -235
- package/Dockerfile +27 -24
- package/README.md +218 -190
- package/SECURITY.md +27 -35
- package/dist/cli.js +16 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +133 -73
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -2
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +7 -6
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +37 -24
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +319 -157
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/database/schema.d.ts +45 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +92 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +13 -2
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +1 -3
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/github.d.ts +12 -0
- package/dist/handlers/prompts/github.d.ts.map +1 -0
- package/dist/handlers/prompts/github.js +178 -0
- package/dist/handlers/prompts/github.js.map +1 -0
- package/dist/handlers/prompts/index.d.ts +23 -2
- package/dist/handlers/prompts/index.d.ts.map +1 -1
- package/dist/handlers/prompts/index.js +7 -432
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/prompts/workflow.d.ts +12 -0
- package/dist/handlers/prompts/workflow.d.ts.map +1 -0
- package/dist/handlers/prompts/workflow.js +277 -0
- package/dist/handlers/prompts/workflow.js.map +1 -0
- package/dist/handlers/resources/core.d.ts +11 -0
- package/dist/handlers/resources/core.d.ts.map +1 -0
- package/dist/handlers/resources/core.js +433 -0
- package/dist/handlers/resources/core.js.map +1 -0
- package/dist/handlers/resources/github.d.ts +11 -0
- package/dist/handlers/resources/github.d.ts.map +1 -0
- package/dist/handlers/resources/github.js +314 -0
- package/dist/handlers/resources/github.js.map +1 -0
- package/dist/handlers/resources/graph.d.ts +11 -0
- package/dist/handlers/resources/graph.d.ts.map +1 -0
- package/dist/handlers/resources/graph.js +204 -0
- package/dist/handlers/resources/graph.js.map +1 -0
- package/dist/handlers/resources/index.d.ts +5 -20
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +16 -1278
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/resources/shared.d.ts +60 -0
- package/dist/handlers/resources/shared.d.ts.map +1 -0
- package/dist/handlers/resources/shared.js +49 -0
- package/dist/handlers/resources/shared.js.map +1 -0
- package/dist/handlers/resources/team.d.ts +13 -0
- package/dist/handlers/resources/team.d.ts.map +1 -0
- package/dist/handlers/resources/team.js +119 -0
- package/dist/handlers/resources/team.js.map +1 -0
- package/dist/handlers/resources/templates.d.ts +13 -0
- package/dist/handlers/resources/templates.d.ts.map +1 -0
- package/dist/handlers/resources/templates.js +310 -0
- package/dist/handlers/resources/templates.js.map +1 -0
- package/dist/handlers/tools/admin.d.ts +8 -0
- package/dist/handlers/tools/admin.d.ts.map +1 -0
- package/dist/handlers/tools/admin.js +270 -0
- package/dist/handlers/tools/admin.js.map +1 -0
- package/dist/handlers/tools/analytics.d.ts +8 -0
- package/dist/handlers/tools/analytics.d.ts.map +1 -0
- package/dist/handlers/tools/analytics.js +256 -0
- package/dist/handlers/tools/analytics.js.map +1 -0
- package/dist/handlers/tools/backup.d.ts +8 -0
- package/dist/handlers/tools/backup.d.ts.map +1 -0
- package/dist/handlers/tools/backup.js +224 -0
- package/dist/handlers/tools/backup.js.map +1 -0
- package/dist/handlers/tools/core.d.ts +9 -0
- package/dist/handlers/tools/core.d.ts.map +1 -0
- package/dist/handlers/tools/core.js +326 -0
- package/dist/handlers/tools/core.js.map +1 -0
- package/dist/handlers/tools/export.d.ts +8 -0
- package/dist/handlers/tools/export.d.ts.map +1 -0
- package/dist/handlers/tools/export.js +89 -0
- package/dist/handlers/tools/export.js.map +1 -0
- package/dist/handlers/tools/github/helpers.d.ts +34 -0
- package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
- package/dist/handlers/tools/github/helpers.js +52 -0
- package/dist/handlers/tools/github/helpers.js.map +1 -0
- package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
- package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/insights-tools.js +104 -0
- package/dist/handlers/tools/github/insights-tools.js.map +1 -0
- package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
- package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/issue-tools.js +359 -0
- package/dist/handlers/tools/github/issue-tools.js.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.js +108 -0
- package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.js +302 -0
- package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.js +15 -0
- package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
- package/dist/handlers/tools/github/read-tools.d.ts +8 -0
- package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/read-tools.js +260 -0
- package/dist/handlers/tools/github/read-tools.js.map +1 -0
- package/dist/handlers/tools/github/schemas.d.ts +467 -0
- package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/github/schemas.js +335 -0
- package/dist/handlers/tools/github/schemas.js.map +1 -0
- package/dist/handlers/tools/github.d.ts +14 -0
- package/dist/handlers/tools/github.d.ts.map +1 -0
- package/dist/handlers/tools/github.js +28 -0
- package/dist/handlers/tools/github.js.map +1 -0
- package/dist/handlers/tools/index.d.ts +15 -20
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +117 -2909
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/handlers/tools/relationships.d.ts +8 -0
- package/dist/handlers/tools/relationships.d.ts.map +1 -0
- package/dist/handlers/tools/relationships.js +308 -0
- package/dist/handlers/tools/relationships.js.map +1 -0
- package/dist/handlers/tools/schemas.d.ts +108 -0
- package/dist/handlers/tools/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/schemas.js +122 -0
- package/dist/handlers/tools/schemas.js.map +1 -0
- package/dist/handlers/tools/search.d.ts +8 -0
- package/dist/handlers/tools/search.d.ts.map +1 -0
- package/dist/handlers/tools/search.js +282 -0
- package/dist/handlers/tools/search.js.map +1 -0
- package/dist/handlers/tools/team.d.ts +11 -0
- package/dist/handlers/tools/team.d.ts.map +1 -0
- package/dist/handlers/tools/team.js +239 -0
- package/dist/handlers/tools/team.js.map +1 -0
- package/dist/server/McpServer.d.ts +4 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +48 -297
- package/dist/server/McpServer.js.map +1 -1
- package/dist/server/Scheduler.d.ts +91 -0
- package/dist/server/Scheduler.d.ts.map +1 -0
- package/dist/server/Scheduler.js +201 -0
- package/dist/server/Scheduler.js.map +1 -0
- package/dist/transports/http.d.ts +66 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +519 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/types/entities.d.ts +101 -0
- package/dist/types/entities.d.ts.map +1 -0
- package/dist/types/entities.js +5 -0
- package/dist/types/entities.js.map +1 -0
- package/dist/types/filtering.d.ts +34 -0
- package/dist/types/filtering.d.ts.map +1 -0
- package/dist/types/filtering.js +5 -0
- package/dist/types/filtering.js.map +1 -0
- package/dist/types/github.d.ts +166 -0
- package/dist/types/github.d.ts.map +1 -0
- package/dist/types/github.js +5 -0
- package/dist/types/github.js.map +1 -0
- package/dist/types/index.d.ts +35 -292
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-helpers.d.ts +37 -0
- package/dist/utils/error-helpers.d.ts.map +1 -0
- package/dist/utils/error-helpers.js +47 -0
- package/dist/utils/error-helpers.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +0 -21
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +0 -47
- package/dist/utils/security-utils.js.map +1 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +9 -32
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +11 -2
- package/hooks/README.md +107 -0
- package/hooks/cursor/hooks.json +10 -0
- package/hooks/cursor/memory-journal.mdc +22 -0
- package/hooks/cursor/session-end.sh +19 -0
- package/hooks/kilo-code/session-end-mode.json +11 -0
- package/hooks/kiro/session-end.md +13 -0
- package/mcp-config-example.json +1 -0
- package/package.json +11 -9
- package/playwright.config.ts +29 -0
- package/releases/v4.5.0.md +116 -0
- package/releases/v5.0.0.md +105 -0
- package/scripts/generate-server-instructions.ts +176 -0
- package/scripts/server-instructions-function-body.ts +77 -0
- package/server.json +3 -3
- package/src/cli.ts +45 -1
- package/src/constants/ServerInstructions.ts +133 -73
- package/src/constants/icons.ts +8 -7
- package/src/constants/server-instructions.md +268 -0
- package/src/database/SqliteAdapter.ts +358 -192
- package/src/database/schema.ts +125 -0
- package/src/filtering/ToolFilter.ts +13 -2
- package/src/github/GitHubIntegration.ts +1 -3
- package/src/handlers/prompts/github.ts +209 -0
- package/src/handlers/prompts/index.ts +10 -499
- package/src/handlers/prompts/workflow.ts +314 -0
- package/src/handlers/resources/core.ts +528 -0
- package/src/handlers/resources/github.ts +358 -0
- package/src/handlers/resources/graph.ts +254 -0
- package/src/handlers/resources/index.ts +23 -1570
- package/src/handlers/resources/shared.ts +103 -0
- package/src/handlers/resources/team.ts +133 -0
- package/src/handlers/resources/templates.ts +374 -0
- package/src/handlers/tools/admin.ts +285 -0
- package/src/handlers/tools/analytics.ts +301 -0
- package/src/handlers/tools/backup.ts +242 -0
- package/src/handlers/tools/core.ts +350 -0
- package/src/handlers/tools/export.ts +115 -0
- package/src/handlers/tools/github/helpers.ts +86 -0
- package/src/handlers/tools/github/insights-tools.ts +119 -0
- package/src/handlers/tools/github/issue-tools.ts +439 -0
- package/src/handlers/tools/github/kanban-tools.ts +134 -0
- package/src/handlers/tools/github/milestone-tools.ts +392 -0
- package/src/handlers/tools/github/mutation-tools.ts +17 -0
- package/src/handlers/tools/github/read-tools.ts +328 -0
- package/src/handlers/tools/github/schemas.ts +369 -0
- package/src/handlers/tools/github.ts +36 -0
- package/src/handlers/tools/index.ts +144 -3325
- package/src/handlers/tools/relationships.ts +358 -0
- package/src/handlers/tools/schemas.ts +132 -0
- package/src/handlers/tools/search.ts +343 -0
- package/src/handlers/tools/team.ts +273 -0
- package/src/server/McpServer.ts +63 -358
- package/src/server/Scheduler.ts +278 -0
- package/src/transports/http.ts +635 -0
- package/src/types/entities.ts +145 -0
- package/src/types/filtering.ts +54 -0
- package/src/types/github.ts +180 -0
- package/src/types/index.ts +67 -375
- package/src/utils/error-helpers.ts +52 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/src/vector/VectorSearchManager.ts +9 -33
- package/tests/constants/icons.test.ts +1 -2
- package/tests/constants/server-instructions.test.ts +30 -4
- package/tests/database/sqlite-adapter.test.ts +91 -7
- package/tests/e2e/auth.spec.ts +154 -0
- package/tests/e2e/health.spec.ts +63 -0
- package/tests/e2e/protocols.spec.ts +134 -0
- package/tests/e2e/resources.spec.ts +103 -0
- package/tests/e2e/scheduler.spec.ts +79 -0
- package/tests/e2e/security.spec.ts +91 -0
- package/tests/e2e/sessions.spec.ts +95 -0
- package/tests/e2e/stateless.spec.ts +121 -0
- package/tests/e2e/tools.spec.ts +111 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/error-path-coverage.test.ts +324 -0
- package/tests/handlers/github-resource-handlers.test.ts +453 -0
- package/tests/handlers/github-tool-handlers.test.ts +899 -0
- package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handler-coverage.test.ts +181 -0
- package/tests/handlers/resource-handlers.test.ts +33 -9
- package/tests/handlers/search-tool-handlers.test.ts +272 -0
- package/tests/handlers/targeted-gap-closure.test.ts +387 -0
- package/tests/handlers/team-resource-handlers.test.ts +156 -0
- package/tests/handlers/team-tool-handlers.test.ts +301 -0
- package/tests/handlers/tool-handler-coverage.test.ts +469 -0
- package/tests/handlers/tool-handlers.test.ts +2 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +503 -8
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/transports/http-transport.test.ts +620 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/vitest.config.ts +4 -1
- package/.memory-journal-team.db +0 -0
- package/.vscode/settings.json +0 -84
|
@@ -1,72 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory Journal MCP Server - Resource Handlers
|
|
3
3
|
*
|
|
4
|
+
* Barrel file composing resource definitions from sub-modules.
|
|
4
5
|
* Exports all MCP resources with annotations following MCP 2025-11-25 spec.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import type { SqliteAdapter } from '../../database/SqliteAdapter.js'
|
|
8
8
|
import type { VectorSearchManager } from '../../vector/VectorSearchManager.js'
|
|
9
9
|
import type { ToolFilterConfig } from '../../filtering/ToolFilter.js'
|
|
10
|
-
import { getAllToolNames } from '../../filtering/ToolFilter.js'
|
|
11
|
-
import type { Tag, McpIcon } from '../../types/index.js'
|
|
12
10
|
import type { GitHubIntegration } from '../../github/GitHubIntegration.js'
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
ICON_BRIEFING,
|
|
17
|
-
ICON_CLOCK,
|
|
18
|
-
ICON_GRAPH,
|
|
19
|
-
ICON_HEALTH,
|
|
20
|
-
ICON_GITHUB,
|
|
21
|
-
ICON_MILESTONE,
|
|
22
|
-
ICON_STAR,
|
|
23
|
-
ICON_TAG,
|
|
24
|
-
ICON_TEAM,
|
|
25
|
-
ICON_ISSUE,
|
|
26
|
-
ICON_PR,
|
|
27
|
-
ICON_ANALYTICS,
|
|
28
|
-
} from '../../constants/icons.js'
|
|
29
|
-
import pkg from '../../../package.json' with { type: 'json' }
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resource context for handlers that need extended access
|
|
33
|
-
*/
|
|
34
|
-
export interface ResourceContext {
|
|
35
|
-
db: SqliteAdapter
|
|
36
|
-
vectorManager?: VectorSearchManager
|
|
37
|
-
filterConfig?: ToolFilterConfig | null
|
|
38
|
-
github?: GitHubIntegration | null
|
|
39
|
-
}
|
|
11
|
+
import type { Scheduler } from '../../server/Scheduler.js'
|
|
12
|
+
import type { SqliteAdapter } from '../../database/SqliteAdapter.js'
|
|
40
13
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
export interface ResourceResult {
|
|
45
|
-
data: unknown
|
|
46
|
-
annotations?: {
|
|
47
|
-
lastModified?: string // ISO 8601 timestamp
|
|
48
|
-
}
|
|
49
|
-
}
|
|
14
|
+
// Re-export shared types
|
|
15
|
+
export type { ResourceContext, ResourceResult, InternalResourceDef } from './shared.js'
|
|
50
16
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
description: string
|
|
59
|
-
mimeType: string
|
|
60
|
-
icons?: McpIcon[] // MCP 2025-11-25 icons
|
|
61
|
-
annotations?: {
|
|
62
|
-
audience?: ('user' | 'assistant')[]
|
|
63
|
-
priority?: number
|
|
64
|
-
lastModified?: string // ISO 8601 timestamp - can be static or dynamic
|
|
65
|
-
autoRead?: boolean // Hint: clients should auto-fetch this resource at session start
|
|
66
|
-
sessionInit?: boolean // Hint: this resource provides session initialization context
|
|
67
|
-
}
|
|
68
|
-
handler: (uri: string, context: ResourceContext) => unknown
|
|
69
|
-
}
|
|
17
|
+
// Import sub-module definitions
|
|
18
|
+
import { getCoreResourceDefinitions } from './core.js'
|
|
19
|
+
import { getGraphResourceDefinitions } from './graph.js'
|
|
20
|
+
import { getGitHubResourceDefinitions } from './github.js'
|
|
21
|
+
import { getTemplateResourceDefinitions } from './templates.js'
|
|
22
|
+
import { getTeamResourceDefinitions } from './team.js'
|
|
23
|
+
import type { InternalResourceDef, ResourceResult } from './shared.js'
|
|
70
24
|
|
|
71
25
|
/**
|
|
72
26
|
* Get all resource definitions for MCP list
|
|
@@ -79,7 +33,7 @@ export function getResources(): object[] {
|
|
|
79
33
|
description: r.description,
|
|
80
34
|
mimeType: r.mimeType,
|
|
81
35
|
annotations: r.annotations,
|
|
82
|
-
icons: r.icons,
|
|
36
|
+
icons: r.icons,
|
|
83
37
|
}))
|
|
84
38
|
}
|
|
85
39
|
|
|
@@ -131,10 +85,12 @@ export async function readResource(
|
|
|
131
85
|
db: SqliteAdapter,
|
|
132
86
|
vectorManager?: VectorSearchManager,
|
|
133
87
|
filterConfig?: ToolFilterConfig | null,
|
|
134
|
-
github?: GitHubIntegration | null
|
|
88
|
+
github?: GitHubIntegration | null,
|
|
89
|
+
scheduler?: Scheduler | null,
|
|
90
|
+
teamDb?: SqliteAdapter
|
|
135
91
|
): Promise<{ data: unknown; annotations?: { lastModified?: string } }> {
|
|
136
92
|
const resources = getAllResourceDefinitions()
|
|
137
|
-
const context
|
|
93
|
+
const context = { db, teamDb, vectorManager, filterConfig, github, scheduler }
|
|
138
94
|
|
|
139
95
|
// Strip query parameters for matching, but pass full URI to handler
|
|
140
96
|
const baseUri = getBaseUri(uri)
|
|
@@ -169,1517 +125,14 @@ export async function readResource(
|
|
|
169
125
|
}
|
|
170
126
|
|
|
171
127
|
/**
|
|
172
|
-
*
|
|
173
|
-
*/
|
|
174
|
-
function execQuery(
|
|
175
|
-
db: SqliteAdapter,
|
|
176
|
-
sql: string,
|
|
177
|
-
params: unknown[] = []
|
|
178
|
-
): Record<string, unknown>[] {
|
|
179
|
-
const rawDb = db.getRawDb()
|
|
180
|
-
const result = rawDb.exec(sql, params)
|
|
181
|
-
if (result.length === 0) return []
|
|
182
|
-
|
|
183
|
-
const columns = result[0]?.columns ?? []
|
|
184
|
-
return (result[0]?.values ?? []).map((values: unknown[]) => {
|
|
185
|
-
const obj: Record<string, unknown> = {}
|
|
186
|
-
columns.forEach((col: string, i: number) => {
|
|
187
|
-
obj[col] = values[i]
|
|
188
|
-
})
|
|
189
|
-
return obj
|
|
190
|
-
})
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Get total tool count for health status
|
|
195
|
-
*/
|
|
196
|
-
function getTotalToolCount(): number {
|
|
197
|
-
return getAllToolNames().length
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Transform snake_case SQL row to camelCase entry object
|
|
202
|
-
* Ensures consistency with SqliteAdapter.getRecentEntries() output
|
|
203
|
-
*/
|
|
204
|
-
function transformEntryRow(row: Record<string, unknown>): Record<string, unknown> {
|
|
205
|
-
return {
|
|
206
|
-
id: row['id'],
|
|
207
|
-
entryType: row['entry_type'],
|
|
208
|
-
content: row['content'],
|
|
209
|
-
timestamp: row['timestamp'],
|
|
210
|
-
isPersonal: row['is_personal'] === 1 || row['is_personal'] === true,
|
|
211
|
-
significanceType: row['significance_type'] ?? null,
|
|
212
|
-
autoContext: row['auto_context'] ?? null,
|
|
213
|
-
deletedAt: row['deleted_at'] ?? null,
|
|
214
|
-
projectNumber: row['project_number'] ?? null,
|
|
215
|
-
projectOwner: row['project_owner'] ?? null,
|
|
216
|
-
issueNumber: row['issue_number'] ?? null,
|
|
217
|
-
issueUrl: row['issue_url'] ?? null,
|
|
218
|
-
prNumber: row['pr_number'] ?? null,
|
|
219
|
-
prUrl: row['pr_url'] ?? null,
|
|
220
|
-
prStatus: row['pr_status'] ?? null,
|
|
221
|
-
workflowRunId: row['workflow_run_id'] ?? null,
|
|
222
|
-
workflowName: row['workflow_name'] ?? null,
|
|
223
|
-
workflowStatus: row['workflow_status'] ?? null,
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Get all resource definitions
|
|
128
|
+
* Get all resource definitions by composing sub-module definitions
|
|
229
129
|
*/
|
|
230
130
|
function getAllResourceDefinitions(): InternalResourceDef[] {
|
|
231
131
|
return [
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
description:
|
|
238
|
-
'AUTO-READ AT SESSION START: Project context for AI agents (~300 tokens). Contains userMessage to show user.',
|
|
239
|
-
mimeType: 'application/json',
|
|
240
|
-
icons: [ICON_BRIEFING],
|
|
241
|
-
annotations: {
|
|
242
|
-
audience: ['assistant'],
|
|
243
|
-
priority: 1.0, // Highest priority - should be read first
|
|
244
|
-
// Custom hints for clients that support auto-subscribe behavior
|
|
245
|
-
autoRead: true, // Hint: automatically fetch this resource at session start
|
|
246
|
-
sessionInit: true, // Hint: this resource is specifically for session initialization
|
|
247
|
-
},
|
|
248
|
-
handler: async (_uri: string, context: ResourceContext) => {
|
|
249
|
-
// Get latest 3 entries (compact)
|
|
250
|
-
const recentEntries = context.db.getRecentEntries(3)
|
|
251
|
-
const latestEntries = recentEntries.map((e) => ({
|
|
252
|
-
id: e.id,
|
|
253
|
-
timestamp: e.timestamp,
|
|
254
|
-
type: e.entryType,
|
|
255
|
-
preview: e.content.slice(0, 80) + (e.content.length > 80 ? '...' : ''),
|
|
256
|
-
}))
|
|
257
|
-
|
|
258
|
-
// Get compact GitHub status if available
|
|
259
|
-
let github: {
|
|
260
|
-
repo: string | null
|
|
261
|
-
branch: string | null
|
|
262
|
-
ci: 'passing' | 'failing' | 'pending' | 'cancelled' | 'unknown'
|
|
263
|
-
openIssues: number
|
|
264
|
-
openPRs: number
|
|
265
|
-
milestones: { title: string; progress: string; dueOn: string | null }[]
|
|
266
|
-
insights?: {
|
|
267
|
-
stars: number | null
|
|
268
|
-
forks: number | null
|
|
269
|
-
clones14d?: number
|
|
270
|
-
views14d?: number
|
|
271
|
-
}
|
|
272
|
-
} | null = null
|
|
273
|
-
|
|
274
|
-
if (context.github) {
|
|
275
|
-
try {
|
|
276
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
277
|
-
const owner = repoInfo.owner
|
|
278
|
-
const repo = repoInfo.repo
|
|
279
|
-
|
|
280
|
-
if (owner && repo) {
|
|
281
|
-
// Get CI status (based on latest run only)
|
|
282
|
-
let ciStatus:
|
|
283
|
-
| 'passing'
|
|
284
|
-
| 'failing'
|
|
285
|
-
| 'pending'
|
|
286
|
-
| 'cancelled'
|
|
287
|
-
| 'unknown' = 'unknown'
|
|
288
|
-
try {
|
|
289
|
-
const runs = await context.github.getWorkflowRuns(owner, repo, 1)
|
|
290
|
-
if (runs.length > 0) {
|
|
291
|
-
const latestRun = runs[0]
|
|
292
|
-
if (!latestRun) {
|
|
293
|
-
ciStatus = 'unknown'
|
|
294
|
-
} else if (latestRun.status !== 'completed') {
|
|
295
|
-
ciStatus = 'pending'
|
|
296
|
-
} else {
|
|
297
|
-
// Map workflow conclusion to CI status
|
|
298
|
-
switch (latestRun.conclusion) {
|
|
299
|
-
case 'success':
|
|
300
|
-
ciStatus = 'passing'
|
|
301
|
-
break
|
|
302
|
-
case 'failure':
|
|
303
|
-
ciStatus = 'failing'
|
|
304
|
-
break
|
|
305
|
-
case 'cancelled':
|
|
306
|
-
ciStatus = 'cancelled'
|
|
307
|
-
break
|
|
308
|
-
default:
|
|
309
|
-
ciStatus = 'unknown'
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
} catch {
|
|
314
|
-
// CI status unavailable
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Get issue/PR counts
|
|
318
|
-
let openIssues = 0
|
|
319
|
-
let openPRs = 0
|
|
320
|
-
try {
|
|
321
|
-
const issues = await context.github.getIssues(
|
|
322
|
-
owner,
|
|
323
|
-
repo,
|
|
324
|
-
'open',
|
|
325
|
-
1
|
|
326
|
-
)
|
|
327
|
-
openIssues = issues.length > 0 ? issues.length : 0
|
|
328
|
-
const prs = await context.github.getPullRequests(
|
|
329
|
-
owner,
|
|
330
|
-
repo,
|
|
331
|
-
'open',
|
|
332
|
-
1
|
|
333
|
-
)
|
|
334
|
-
openPRs = prs.length > 0 ? prs.length : 0
|
|
335
|
-
} catch {
|
|
336
|
-
// Counts unavailable
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Get milestone summary for briefing
|
|
340
|
-
let milestones: {
|
|
341
|
-
title: string
|
|
342
|
-
progress: string
|
|
343
|
-
dueOn: string | null
|
|
344
|
-
}[] = []
|
|
345
|
-
try {
|
|
346
|
-
const msList = await context.github.getMilestones(
|
|
347
|
-
owner,
|
|
348
|
-
repo,
|
|
349
|
-
'open',
|
|
350
|
-
3
|
|
351
|
-
)
|
|
352
|
-
milestones = msList.map((m) => {
|
|
353
|
-
const total = m.closedIssues + m.openIssues
|
|
354
|
-
const pct =
|
|
355
|
-
total > 0 ? Math.round((m.closedIssues / total) * 100) : 0
|
|
356
|
-
return {
|
|
357
|
-
title: m.title,
|
|
358
|
-
progress: `${String(pct)}%`,
|
|
359
|
-
dueOn: m.dueOn,
|
|
360
|
-
}
|
|
361
|
-
})
|
|
362
|
-
} catch {
|
|
363
|
-
// Milestones unavailable
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Get repo insights (stars, forks, traffic)
|
|
367
|
-
let insights:
|
|
368
|
-
| {
|
|
369
|
-
stars: number | null
|
|
370
|
-
forks: number | null
|
|
371
|
-
clones14d?: number
|
|
372
|
-
views14d?: number
|
|
373
|
-
}
|
|
374
|
-
| undefined = undefined
|
|
375
|
-
try {
|
|
376
|
-
const repoStats = await context.github.getRepoStats(owner, repo)
|
|
377
|
-
if (repoStats) {
|
|
378
|
-
insights = {
|
|
379
|
-
stars: repoStats.stars ?? null,
|
|
380
|
-
forks: repoStats.forks ?? null,
|
|
381
|
-
}
|
|
382
|
-
// Traffic requires push access - may fail
|
|
383
|
-
try {
|
|
384
|
-
const trafficData = await context.github.getTrafficData(
|
|
385
|
-
owner,
|
|
386
|
-
repo
|
|
387
|
-
)
|
|
388
|
-
if (trafficData) {
|
|
389
|
-
insights.clones14d = trafficData.clones.total
|
|
390
|
-
insights.views14d = trafficData.views.total
|
|
391
|
-
}
|
|
392
|
-
} catch {
|
|
393
|
-
// Traffic data unavailable (requires push access)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
} catch {
|
|
397
|
-
// Repo stats unavailable
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
github = {
|
|
401
|
-
repo: `${owner}/${repo}`,
|
|
402
|
-
branch: repoInfo.branch ?? null,
|
|
403
|
-
ci: ciStatus,
|
|
404
|
-
openIssues,
|
|
405
|
-
openPRs,
|
|
406
|
-
milestones,
|
|
407
|
-
insights,
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
} catch {
|
|
411
|
-
// GitHub unavailable
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Get entry count for context
|
|
416
|
-
const stats = context.db.getStatistics('week')
|
|
417
|
-
const totalEntries = stats.totalEntries ?? 0
|
|
418
|
-
|
|
419
|
-
// Determine lastModified from most recent entry or current time
|
|
420
|
-
const lastModified = recentEntries[0]?.timestamp ?? new Date().toISOString()
|
|
421
|
-
|
|
422
|
-
// Build acknowledgment message for user
|
|
423
|
-
const repoName = github?.repo ?? 'local'
|
|
424
|
-
const branchName = github?.branch ?? 'unknown'
|
|
425
|
-
const ciStatus = github?.ci ?? 'unknown'
|
|
426
|
-
const latestPreview = latestEntries[0]
|
|
427
|
-
? `#${latestEntries[0].id} (${latestEntries[0].type}): ${latestEntries[0].preview}`
|
|
428
|
-
: 'No entries yet'
|
|
429
|
-
|
|
430
|
-
const milestoneRow =
|
|
431
|
-
github?.milestones && github.milestones.length > 0
|
|
432
|
-
? `\n| **Milestones** | ${github.milestones.map((m) => `${m.title} (${m.progress}${m.dueOn ? `, due ${m.dueOn.split('T')[0] ?? ''}` : ''})`).join(', ')} |`
|
|
433
|
-
: ''
|
|
434
|
-
|
|
435
|
-
// Build insights row for userMessage
|
|
436
|
-
let insightsRow = ''
|
|
437
|
-
if (github?.insights) {
|
|
438
|
-
const parts: string[] = []
|
|
439
|
-
if (github.insights.stars !== null)
|
|
440
|
-
parts.push(`⭐ ${String(github.insights.stars)} stars`)
|
|
441
|
-
if (github.insights.forks !== null)
|
|
442
|
-
parts.push(`🍴 ${String(github.insights.forks)} forks`)
|
|
443
|
-
if (github.insights.clones14d !== undefined)
|
|
444
|
-
parts.push(`📦 ${String(github.insights.clones14d)} clones`)
|
|
445
|
-
if (github.insights.views14d !== undefined)
|
|
446
|
-
parts.push(`👁️ ${String(github.insights.views14d)} views`)
|
|
447
|
-
if (parts.length > 0) {
|
|
448
|
-
const trafficNote = github.insights.clones14d !== undefined ? ' (14d)' : ''
|
|
449
|
-
insightsRow = `\n| **Insights** | ${parts.join(' · ')}${trafficNote} |`
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
return {
|
|
454
|
-
data: {
|
|
455
|
-
version: pkg.version,
|
|
456
|
-
serverTime: new Date().toISOString(),
|
|
457
|
-
journal: {
|
|
458
|
-
totalEntries,
|
|
459
|
-
latestEntries,
|
|
460
|
-
},
|
|
461
|
-
github,
|
|
462
|
-
behaviors: {
|
|
463
|
-
create: 'implementations, decisions, bug-fixes, milestones',
|
|
464
|
-
search: 'before decisions, referencing prior work',
|
|
465
|
-
link: 'implementation→spec, bugfix→issue',
|
|
466
|
-
},
|
|
467
|
-
templateResources: [
|
|
468
|
-
'memory://projects/{number}/timeline',
|
|
469
|
-
'memory://issues/{issue_number}/entries',
|
|
470
|
-
'memory://prs/{pr_number}/entries',
|
|
471
|
-
'memory://prs/{pr_number}/timeline',
|
|
472
|
-
'memory://kanban/{project_number}',
|
|
473
|
-
'memory://kanban/{project_number}/diagram',
|
|
474
|
-
'memory://milestones/{number}',
|
|
475
|
-
],
|
|
476
|
-
more: {
|
|
477
|
-
fullHealth: 'memory://health',
|
|
478
|
-
allRecent: 'memory://recent',
|
|
479
|
-
githubStatus: 'memory://github/status',
|
|
480
|
-
repoInsights: 'memory://github/insights',
|
|
481
|
-
contextBundle: 'get-context-bundle prompt',
|
|
482
|
-
},
|
|
483
|
-
// IMPORTANT: Agent should relay this message to the user
|
|
484
|
-
userMessage: `📋 **Session Context Loaded**
|
|
485
|
-
| Context | Value |
|
|
486
|
-
|---------|-------|
|
|
487
|
-
| **Project** | ${repoName} |
|
|
488
|
-
| **Branch** | ${branchName} |
|
|
489
|
-
| **CI Status** | ${ciStatus} |
|
|
490
|
-
| **Journal** | ${totalEntries} entries |
|
|
491
|
-
| **Latest** | ${latestPreview} |${milestoneRow}${insightsRow}
|
|
492
|
-
|
|
493
|
-
I have project memory access and will create entries for significant work.`,
|
|
494
|
-
// Note for clients that don't auto-inject ServerInstructions
|
|
495
|
-
clientNote:
|
|
496
|
-
'For complete tool reference and field notes, read memory://instructions.',
|
|
497
|
-
},
|
|
498
|
-
annotations: { lastModified },
|
|
499
|
-
} satisfies ResourceResult
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
// Server instructions resource - for clients that don't auto-inject ServerInstructions
|
|
503
|
-
{
|
|
504
|
-
uri: 'memory://instructions',
|
|
505
|
-
name: 'Server Instructions',
|
|
506
|
-
title: 'Full Server Behavioral Guidance',
|
|
507
|
-
description: 'Full server instructions for AI agents.',
|
|
508
|
-
mimeType: 'text/markdown',
|
|
509
|
-
icons: [ICON_BRIEFING],
|
|
510
|
-
annotations: {
|
|
511
|
-
audience: ['assistant'],
|
|
512
|
-
priority: 0.95, // High priority, but below briefing
|
|
513
|
-
},
|
|
514
|
-
handler: (_uri: string, context: ResourceContext): ResourceResult => {
|
|
515
|
-
// Note: Query parameters (e.g., ?level=essential) are not supported
|
|
516
|
-
// because the MCP SDK performs exact URI matching before calling handlers.
|
|
517
|
-
const level: InstructionLevel = 'full'
|
|
518
|
-
|
|
519
|
-
// Get enabled tools from filter config, or fall back to all tool names
|
|
520
|
-
const allToolNames = new Set(getAllToolNames())
|
|
521
|
-
const enabledTools = context.filterConfig?.enabledTools ?? allToolNames
|
|
522
|
-
|
|
523
|
-
// Get prompts for instruction generation
|
|
524
|
-
const prompts = getPrompts().map((p) => {
|
|
525
|
-
const prompt = p as { name: string; description?: string }
|
|
526
|
-
return { name: prompt.name, description: prompt.description }
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
// Get resources for instruction generation (simplified)
|
|
530
|
-
const resources = getResources().map((r) => {
|
|
531
|
-
const res = r as { uri: string; name: string; description?: string }
|
|
532
|
-
return { uri: res.uri, name: res.name, description: res.description }
|
|
533
|
-
})
|
|
534
|
-
|
|
535
|
-
// Generate instructions at requested level
|
|
536
|
-
const instructions = generateInstructions(
|
|
537
|
-
enabledTools,
|
|
538
|
-
resources,
|
|
539
|
-
prompts,
|
|
540
|
-
undefined, // No latest entry needed for instructions
|
|
541
|
-
level
|
|
542
|
-
)
|
|
543
|
-
|
|
544
|
-
return {
|
|
545
|
-
data: instructions,
|
|
546
|
-
}
|
|
547
|
-
},
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
uri: 'memory://recent',
|
|
551
|
-
name: 'Recent Entries',
|
|
552
|
-
title: 'Recent Journal Entries',
|
|
553
|
-
description: '10 most recent journal entries',
|
|
554
|
-
mimeType: 'application/json',
|
|
555
|
-
icons: [ICON_CLOCK],
|
|
556
|
-
annotations: {
|
|
557
|
-
audience: ['assistant'],
|
|
558
|
-
priority: 0.8,
|
|
559
|
-
},
|
|
560
|
-
handler: (_uri: string, context: ResourceContext): ResourceResult => {
|
|
561
|
-
const entries = context.db.getRecentEntries(10)
|
|
562
|
-
const lastModified = entries[0]?.timestamp ?? new Date().toISOString()
|
|
563
|
-
return {
|
|
564
|
-
data: { entries, count: entries.length },
|
|
565
|
-
annotations: { lastModified },
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
uri: 'memory://significant',
|
|
571
|
-
name: 'Significant Entries',
|
|
572
|
-
title: 'Significant Milestones',
|
|
573
|
-
description: 'Significant milestones and breakthroughs',
|
|
574
|
-
mimeType: 'application/json',
|
|
575
|
-
icons: [ICON_STAR],
|
|
576
|
-
annotations: {
|
|
577
|
-
audience: ['assistant'],
|
|
578
|
-
priority: 0.7,
|
|
579
|
-
},
|
|
580
|
-
handler: (_uri: string, context: ResourceContext) => {
|
|
581
|
-
// Fetch ALL significant entries so importance sort runs on the full set
|
|
582
|
-
// (not just the 20 most recent). We then slice after sorting.
|
|
583
|
-
const rows = execQuery(
|
|
584
|
-
context.db,
|
|
585
|
-
`
|
|
586
|
-
SELECT * FROM memory_journal
|
|
587
|
-
WHERE significance_type IS NOT NULL
|
|
588
|
-
AND deleted_at IS NULL
|
|
589
|
-
`
|
|
590
|
-
)
|
|
591
|
-
// Transform entries and calculate importance scores
|
|
592
|
-
const entriesWithImportance: (Record<string, unknown> & { importance: number })[] =
|
|
593
|
-
rows.map((row) => {
|
|
594
|
-
const entry = transformEntryRow(row)
|
|
595
|
-
const { score: importance } = context.db.calculateImportance(
|
|
596
|
-
entry['id'] as number
|
|
597
|
-
)
|
|
598
|
-
return { ...entry, importance }
|
|
599
|
-
})
|
|
600
|
-
// Sort by importance (highest first), then by timestamp (newest first) for ties
|
|
601
|
-
entriesWithImportance.sort((a, b) => {
|
|
602
|
-
if (b.importance !== a.importance) {
|
|
603
|
-
return b.importance - a.importance
|
|
604
|
-
}
|
|
605
|
-
// Secondary sort: newest first for equal importance
|
|
606
|
-
const aTime = new Date(a['timestamp'] as string).getTime()
|
|
607
|
-
const bTime = new Date(b['timestamp'] as string).getTime()
|
|
608
|
-
return bTime - aTime
|
|
609
|
-
})
|
|
610
|
-
// Slice to top 20 AFTER sorting (not before) to ensure correctness
|
|
611
|
-
const top20 = entriesWithImportance.slice(0, 20)
|
|
612
|
-
return { entries: top20, count: top20.length }
|
|
613
|
-
},
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
uri: 'memory://graph/recent',
|
|
617
|
-
name: 'Recent Relationship Graph',
|
|
618
|
-
title: 'Live Mermaid Diagram',
|
|
619
|
-
description: 'Live Mermaid diagram of recent relationships',
|
|
620
|
-
mimeType: 'text/plain',
|
|
621
|
-
icons: [ICON_GRAPH],
|
|
622
|
-
annotations: {
|
|
623
|
-
audience: ['user', 'assistant'],
|
|
624
|
-
priority: 0.5,
|
|
625
|
-
},
|
|
626
|
-
handler: (_uri: string, context: ResourceContext) => {
|
|
627
|
-
// Get recent relationships from database
|
|
628
|
-
const relationships = execQuery(
|
|
629
|
-
context.db,
|
|
630
|
-
`
|
|
631
|
-
SELECT
|
|
632
|
-
r.id, r.from_entry_id, r.to_entry_id, r.relationship_type, r.description,
|
|
633
|
-
e1.content as from_content,
|
|
634
|
-
e2.content as to_content
|
|
635
|
-
FROM relationships r
|
|
636
|
-
JOIN memory_journal e1 ON r.from_entry_id = e1.id
|
|
637
|
-
JOIN memory_journal e2 ON r.to_entry_id = e2.id
|
|
638
|
-
WHERE e1.deleted_at IS NULL AND e2.deleted_at IS NULL
|
|
639
|
-
ORDER BY r.created_at DESC
|
|
640
|
-
LIMIT 20
|
|
641
|
-
`
|
|
642
|
-
) as {
|
|
643
|
-
from_entry_id: number
|
|
644
|
-
to_entry_id: number
|
|
645
|
-
relationship_type: string
|
|
646
|
-
from_content: string
|
|
647
|
-
to_content: string
|
|
648
|
-
}[]
|
|
649
|
-
|
|
650
|
-
if (relationships.length === 0) {
|
|
651
|
-
return {
|
|
652
|
-
format: 'mermaid',
|
|
653
|
-
diagram: 'graph TD\n NoData[No relationships found]',
|
|
654
|
-
message:
|
|
655
|
-
'No entry relationships exist yet. Use link_entries tool to create relationships.',
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Build Mermaid graph
|
|
660
|
-
const lines: string[] = ['graph TD']
|
|
661
|
-
const seenNodes = new Set<number>()
|
|
662
|
-
|
|
663
|
-
// Relationship type to arrow style mapping (harmonized with visualize_relationships tool)
|
|
664
|
-
const arrowStyles: Record<string, string> = {
|
|
665
|
-
// Standard relationship types
|
|
666
|
-
references: '-->',
|
|
667
|
-
evolves_from: '-->',
|
|
668
|
-
depends_on: '-->',
|
|
669
|
-
// Emphasis relationships
|
|
670
|
-
implements: '==>',
|
|
671
|
-
resolved: '==>',
|
|
672
|
-
// Subtle/clarifying relationships
|
|
673
|
-
clarifies: '-.->',
|
|
674
|
-
caused: '-.->',
|
|
675
|
-
// Bidirectional
|
|
676
|
-
related_to: '<-->',
|
|
677
|
-
response_to: '<-->',
|
|
678
|
-
// Blockers (crossed arrow)
|
|
679
|
-
blocked_by: '--x',
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
for (const rel of relationships) {
|
|
683
|
-
// Add node definitions if not seen
|
|
684
|
-
if (!seenNodes.has(rel.from_entry_id)) {
|
|
685
|
-
const label = rel.from_content
|
|
686
|
-
.slice(0, 30)
|
|
687
|
-
.replace(/[\]"'`[]/g, ' ')
|
|
688
|
-
.trim()
|
|
689
|
-
lines.push(
|
|
690
|
-
` E${String(rel.from_entry_id)}["#${String(rel.from_entry_id)}: ${label}..."]`
|
|
691
|
-
)
|
|
692
|
-
seenNodes.add(rel.from_entry_id)
|
|
693
|
-
}
|
|
694
|
-
if (!seenNodes.has(rel.to_entry_id)) {
|
|
695
|
-
const label = rel.to_content
|
|
696
|
-
.slice(0, 30)
|
|
697
|
-
.replace(/[\]"'`[]/g, ' ')
|
|
698
|
-
.trim()
|
|
699
|
-
lines.push(
|
|
700
|
-
` E${String(rel.to_entry_id)}["#${String(rel.to_entry_id)}: ${label}..."]`
|
|
701
|
-
)
|
|
702
|
-
seenNodes.add(rel.to_entry_id)
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Add edge with relationship label
|
|
706
|
-
const arrow = arrowStyles[rel.relationship_type] ?? '-->'
|
|
707
|
-
lines.push(
|
|
708
|
-
` E${String(rel.from_entry_id)} ${arrow}|${rel.relationship_type}| E${String(rel.to_entry_id)}`
|
|
709
|
-
)
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
return {
|
|
713
|
-
format: 'mermaid',
|
|
714
|
-
diagram: lines.join('\n'),
|
|
715
|
-
relationshipCount: relationships.length,
|
|
716
|
-
nodeCount: seenNodes.size,
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
uri: 'memory://team/recent',
|
|
722
|
-
name: 'Team Entries',
|
|
723
|
-
title: 'Recent Team-Shared Entries',
|
|
724
|
-
description: 'Recent team-shared entries',
|
|
725
|
-
mimeType: 'application/json',
|
|
726
|
-
icons: [ICON_TEAM],
|
|
727
|
-
annotations: {
|
|
728
|
-
audience: ['assistant'],
|
|
729
|
-
priority: 0.6,
|
|
730
|
-
},
|
|
731
|
-
handler: (_uri: string, context: ResourceContext) => {
|
|
732
|
-
const entries = context.db.getRecentEntries(10, false)
|
|
733
|
-
return { entries, count: entries.length }
|
|
734
|
-
},
|
|
735
|
-
},
|
|
736
|
-
{
|
|
737
|
-
uri: 'memory://projects/{number}/timeline',
|
|
738
|
-
name: 'Project Timeline',
|
|
739
|
-
title: 'Project Activity Timeline',
|
|
740
|
-
description: 'Project activity timeline',
|
|
741
|
-
mimeType: 'application/json',
|
|
742
|
-
annotations: {
|
|
743
|
-
audience: ['assistant'],
|
|
744
|
-
priority: 0.6,
|
|
745
|
-
},
|
|
746
|
-
handler: (uri: string, context: ResourceContext) => {
|
|
747
|
-
const match = /memory:\/\/projects\/(\d+)\/timeline/.exec(uri)
|
|
748
|
-
const projectNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
749
|
-
|
|
750
|
-
if (projectNumber === null) {
|
|
751
|
-
return { error: 'Invalid project number' }
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const rows = execQuery(
|
|
755
|
-
context.db,
|
|
756
|
-
`
|
|
757
|
-
SELECT * FROM memory_journal
|
|
758
|
-
WHERE project_number = ?
|
|
759
|
-
AND deleted_at IS NULL
|
|
760
|
-
ORDER BY timestamp DESC
|
|
761
|
-
LIMIT 50
|
|
762
|
-
`,
|
|
763
|
-
[projectNumber]
|
|
764
|
-
)
|
|
765
|
-
const entries = rows.map(transformEntryRow)
|
|
766
|
-
return { projectNumber, entries, count: entries.length }
|
|
767
|
-
},
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
uri: 'memory://issues/{issue_number}/entries',
|
|
771
|
-
name: 'Issue Entries',
|
|
772
|
-
title: 'Entries Linked to Issue',
|
|
773
|
-
description: 'All entries linked to a specific issue',
|
|
774
|
-
mimeType: 'application/json',
|
|
775
|
-
icons: [ICON_ISSUE],
|
|
776
|
-
annotations: {
|
|
777
|
-
audience: ['assistant'],
|
|
778
|
-
priority: 0.6,
|
|
779
|
-
},
|
|
780
|
-
handler: (uri: string, context: ResourceContext) => {
|
|
781
|
-
const match = /memory:\/\/issues\/(\d+)\/entries/.exec(uri)
|
|
782
|
-
const issueNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
783
|
-
|
|
784
|
-
if (issueNumber === null) {
|
|
785
|
-
return { error: 'Invalid issue number' }
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
const rows = execQuery(
|
|
789
|
-
context.db,
|
|
790
|
-
`
|
|
791
|
-
SELECT * FROM memory_journal
|
|
792
|
-
WHERE issue_number = ?
|
|
793
|
-
AND deleted_at IS NULL
|
|
794
|
-
ORDER BY timestamp DESC
|
|
795
|
-
`,
|
|
796
|
-
[issueNumber]
|
|
797
|
-
)
|
|
798
|
-
const entries = rows.map(transformEntryRow)
|
|
799
|
-
return { issueNumber, entries, count: entries.length }
|
|
800
|
-
},
|
|
801
|
-
},
|
|
802
|
-
{
|
|
803
|
-
uri: 'memory://prs/{pr_number}/entries',
|
|
804
|
-
name: 'PR Entries',
|
|
805
|
-
title: 'Entries Linked to PR',
|
|
806
|
-
description: 'All entries linked to a specific pull request',
|
|
807
|
-
mimeType: 'application/json',
|
|
808
|
-
icons: [ICON_PR],
|
|
809
|
-
annotations: {
|
|
810
|
-
audience: ['assistant'],
|
|
811
|
-
priority: 0.6,
|
|
812
|
-
},
|
|
813
|
-
handler: (uri: string, context: ResourceContext) => {
|
|
814
|
-
const match = /memory:\/\/prs\/(\d+)\/entries/.exec(uri)
|
|
815
|
-
const prNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
816
|
-
|
|
817
|
-
if (prNumber === null) {
|
|
818
|
-
return { error: 'Invalid PR number' }
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const rows = execQuery(
|
|
822
|
-
context.db,
|
|
823
|
-
`
|
|
824
|
-
SELECT * FROM memory_journal
|
|
825
|
-
WHERE pr_number = ?
|
|
826
|
-
AND deleted_at IS NULL
|
|
827
|
-
ORDER BY timestamp DESC
|
|
828
|
-
`,
|
|
829
|
-
[prNumber]
|
|
830
|
-
)
|
|
831
|
-
const entries = rows.map(transformEntryRow)
|
|
832
|
-
return {
|
|
833
|
-
prNumber,
|
|
834
|
-
entries,
|
|
835
|
-
count: entries.length,
|
|
836
|
-
...(entries.length === 0
|
|
837
|
-
? {
|
|
838
|
-
hint: 'No journal entries linked to this PR. Use create_entry with pr_number to link entries.',
|
|
839
|
-
}
|
|
840
|
-
: {}),
|
|
841
|
-
}
|
|
842
|
-
},
|
|
843
|
-
},
|
|
844
|
-
{
|
|
845
|
-
uri: 'memory://prs/{pr_number}/timeline',
|
|
846
|
-
name: 'PR Timeline',
|
|
847
|
-
title: 'Combined PR and Journal Timeline',
|
|
848
|
-
description: 'Combined PR + journal timeline with live PR metadata',
|
|
849
|
-
mimeType: 'application/json',
|
|
850
|
-
icons: [ICON_PR],
|
|
851
|
-
annotations: {
|
|
852
|
-
audience: ['assistant'],
|
|
853
|
-
priority: 0.5,
|
|
854
|
-
},
|
|
855
|
-
handler: async (uri: string, context: ResourceContext) => {
|
|
856
|
-
const match = /memory:\/\/prs\/(\d+)\/timeline/.exec(uri)
|
|
857
|
-
const prNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
858
|
-
|
|
859
|
-
if (prNumber === null) {
|
|
860
|
-
return { error: 'Invalid PR number' }
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// Fetch live PR metadata from GitHub if available
|
|
864
|
-
let prMetadata: {
|
|
865
|
-
title: string
|
|
866
|
-
state: string
|
|
867
|
-
draft: boolean
|
|
868
|
-
mergedAt: string | null
|
|
869
|
-
closedAt: string | null
|
|
870
|
-
author: string
|
|
871
|
-
headBranch: string
|
|
872
|
-
baseBranch: string
|
|
873
|
-
} | null = null
|
|
874
|
-
|
|
875
|
-
if (context.github) {
|
|
876
|
-
try {
|
|
877
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
878
|
-
if (repoInfo.owner && repoInfo.repo) {
|
|
879
|
-
const pr = await context.github.getPullRequest(
|
|
880
|
-
repoInfo.owner,
|
|
881
|
-
repoInfo.repo,
|
|
882
|
-
prNumber
|
|
883
|
-
)
|
|
884
|
-
if (pr) {
|
|
885
|
-
prMetadata = {
|
|
886
|
-
title: pr.title,
|
|
887
|
-
state: pr.state,
|
|
888
|
-
draft: pr.draft,
|
|
889
|
-
mergedAt: pr.mergedAt,
|
|
890
|
-
closedAt: pr.closedAt,
|
|
891
|
-
author: pr.author,
|
|
892
|
-
headBranch: pr.headBranch,
|
|
893
|
-
baseBranch: pr.baseBranch,
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
} catch {
|
|
898
|
-
// GitHub not available, proceed without metadata
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const rows = execQuery(
|
|
903
|
-
context.db,
|
|
904
|
-
`
|
|
905
|
-
SELECT * FROM memory_journal
|
|
906
|
-
WHERE pr_number = ?
|
|
907
|
-
AND deleted_at IS NULL
|
|
908
|
-
ORDER BY timestamp DESC
|
|
909
|
-
`,
|
|
910
|
-
[prNumber]
|
|
911
|
-
)
|
|
912
|
-
const entries = rows.map(transformEntryRow)
|
|
913
|
-
|
|
914
|
-
// Build timeline note based on PR state
|
|
915
|
-
let timelineNote: string
|
|
916
|
-
if (prMetadata) {
|
|
917
|
-
const stateDesc = prMetadata.state.toLowerCase()
|
|
918
|
-
const mergedNote = prMetadata.mergedAt ? ' (merged)' : ''
|
|
919
|
-
const draftNote = prMetadata.draft ? ' [DRAFT]' : ''
|
|
920
|
-
timelineNote = `PR #${String(prNumber)} is ${stateDesc}${mergedNote}${draftNote}`
|
|
921
|
-
} else {
|
|
922
|
-
timelineNote =
|
|
923
|
-
'GitHub integration unavailable for live PR status. Entry timestamps show journal activity.'
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
return {
|
|
927
|
-
prNumber,
|
|
928
|
-
prMetadata,
|
|
929
|
-
entries,
|
|
930
|
-
count: entries.length,
|
|
931
|
-
timelineNote,
|
|
932
|
-
...(entries.length === 0
|
|
933
|
-
? {
|
|
934
|
-
hint: 'No journal entries linked to this PR. Use create_entry with pr_number to link entries.',
|
|
935
|
-
}
|
|
936
|
-
: {}),
|
|
937
|
-
}
|
|
938
|
-
},
|
|
939
|
-
},
|
|
940
|
-
{
|
|
941
|
-
uri: 'memory://graph/actions',
|
|
942
|
-
name: 'Actions Graph',
|
|
943
|
-
title: 'CI/CD Narrative Graph',
|
|
944
|
-
description:
|
|
945
|
-
'CI/CD narrative graph: commits → runs → failures → entries → fixes → deployments',
|
|
946
|
-
mimeType: 'text/plain',
|
|
947
|
-
icons: [ICON_GITHUB],
|
|
948
|
-
annotations: {
|
|
949
|
-
audience: ['user', 'assistant'],
|
|
950
|
-
priority: 0.5,
|
|
951
|
-
},
|
|
952
|
-
handler: async (_uri: string, context: ResourceContext) => {
|
|
953
|
-
// Check if GitHub integration is available
|
|
954
|
-
if (!context.github) {
|
|
955
|
-
return {
|
|
956
|
-
format: 'mermaid',
|
|
957
|
-
diagram: 'graph LR\n NoGitHub[GitHub integration not available]',
|
|
958
|
-
message:
|
|
959
|
-
'GitHub integration not configured. Set GITHUB_TOKEN and GITHUB_REPO_PATH.',
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// Get repository info and workflow runs
|
|
964
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
965
|
-
if (!repoInfo.owner || !repoInfo.repo) {
|
|
966
|
-
return {
|
|
967
|
-
format: 'mermaid',
|
|
968
|
-
diagram: 'graph LR\n NoRepo[Repository not detected]',
|
|
969
|
-
message:
|
|
970
|
-
'Could not detect repository. Set GITHUB_REPO_PATH in your config.',
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
const workflowRuns = await context.github.getWorkflowRuns(
|
|
975
|
-
repoInfo.owner,
|
|
976
|
-
repoInfo.repo,
|
|
977
|
-
10
|
|
978
|
-
)
|
|
979
|
-
|
|
980
|
-
if (workflowRuns.length === 0) {
|
|
981
|
-
return {
|
|
982
|
-
format: 'mermaid',
|
|
983
|
-
diagram: 'graph LR\n NoRuns[No workflow runs found]',
|
|
984
|
-
message: 'No GitHub Actions workflow runs found for this repository.',
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// Build Mermaid graph showing workflow runs
|
|
989
|
-
const lines: string[] = ['graph LR']
|
|
990
|
-
|
|
991
|
-
// Status to styling map
|
|
992
|
-
const statusStyles: Record<string, string> = {
|
|
993
|
-
success: ':::success',
|
|
994
|
-
failure: ':::failure',
|
|
995
|
-
cancelled: ':::cancelled',
|
|
996
|
-
skipped: ':::skipped',
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// Add style definitions
|
|
1000
|
-
lines.push(' classDef success fill:#28a745,color:#fff')
|
|
1001
|
-
lines.push(' classDef failure fill:#dc3545,color:#fff')
|
|
1002
|
-
lines.push(' classDef cancelled fill:#6c757d,color:#fff')
|
|
1003
|
-
lines.push(' classDef skipped fill:#ffc107,color:#000')
|
|
1004
|
-
|
|
1005
|
-
for (const run of workflowRuns) {
|
|
1006
|
-
const shortSha = run.headSha.slice(0, 7)
|
|
1007
|
-
const nodeId = `R${String(run.id)}`
|
|
1008
|
-
const commitId = `C${shortSha}`
|
|
1009
|
-
const style = statusStyles[run.conclusion ?? 'skipped'] ?? ''
|
|
1010
|
-
const statusIcon =
|
|
1011
|
-
run.conclusion === 'success'
|
|
1012
|
-
? '✓'
|
|
1013
|
-
: run.conclusion === 'failure'
|
|
1014
|
-
? '✗'
|
|
1015
|
-
: '○'
|
|
1016
|
-
|
|
1017
|
-
// Add commit and run nodes
|
|
1018
|
-
lines.push(` ${commitId}["${shortSha}"]`)
|
|
1019
|
-
lines.push(` ${nodeId}["${statusIcon} ${run.name}"]${style}`)
|
|
1020
|
-
lines.push(` ${commitId} --> ${nodeId}`)
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
return {
|
|
1024
|
-
format: 'mermaid',
|
|
1025
|
-
diagram: lines.join('\n'),
|
|
1026
|
-
workflowRunCount: workflowRuns.length,
|
|
1027
|
-
repository: `${repoInfo.owner}/${repoInfo.repo}`,
|
|
1028
|
-
}
|
|
1029
|
-
},
|
|
1030
|
-
},
|
|
1031
|
-
{
|
|
1032
|
-
uri: 'memory://actions/recent',
|
|
1033
|
-
name: 'Recent Actions',
|
|
1034
|
-
title: 'Recent Workflow Runs',
|
|
1035
|
-
description: 'Recent workflow runs with CI status',
|
|
1036
|
-
mimeType: 'application/json',
|
|
1037
|
-
icons: [ICON_GITHUB],
|
|
1038
|
-
annotations: {
|
|
1039
|
-
audience: ['assistant'],
|
|
1040
|
-
priority: 0.5,
|
|
1041
|
-
},
|
|
1042
|
-
handler: async (_uri: string, context: ResourceContext) => {
|
|
1043
|
-
// If GitHub integration is available, synthesize entries from recent workflow runs
|
|
1044
|
-
if (context.github) {
|
|
1045
|
-
try {
|
|
1046
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
1047
|
-
if (repoInfo.owner && repoInfo.repo) {
|
|
1048
|
-
const runs = await context.github.getWorkflowRuns(
|
|
1049
|
-
repoInfo.owner,
|
|
1050
|
-
repoInfo.repo,
|
|
1051
|
-
10
|
|
1052
|
-
)
|
|
1053
|
-
|
|
1054
|
-
// Return virtual entries synthesized from workflow runs
|
|
1055
|
-
const entries = runs.map((run) => ({
|
|
1056
|
-
id: -1 * run.id, // Virtual ID (negative to distinguish from DB)
|
|
1057
|
-
entryType: 'tool_output',
|
|
1058
|
-
content: `Workflow: ${run.name}\nStatus: ${run.status}\nConclusion: ${run.conclusion || 'pending'}\nBranch: ${run.headBranch}\nURL: ${run.url}`,
|
|
1059
|
-
timestamp: run.createdAt,
|
|
1060
|
-
isPersonal: false,
|
|
1061
|
-
significanceType: null,
|
|
1062
|
-
workflowRunId: run.id,
|
|
1063
|
-
workflowName: run.name,
|
|
1064
|
-
workflowStatus: run.conclusion || run.status,
|
|
1065
|
-
}))
|
|
1066
|
-
|
|
1067
|
-
return { entries, count: entries.length, source: 'github_api' }
|
|
1068
|
-
}
|
|
1069
|
-
} catch {
|
|
1070
|
-
// Fallback to DB if GitHub fails
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
const rows = execQuery(
|
|
1075
|
-
context.db,
|
|
1076
|
-
`
|
|
1077
|
-
SELECT * FROM memory_journal
|
|
1078
|
-
WHERE workflow_run_id IS NOT NULL
|
|
1079
|
-
AND deleted_at IS NULL
|
|
1080
|
-
ORDER BY timestamp DESC
|
|
1081
|
-
LIMIT 10
|
|
1082
|
-
`
|
|
1083
|
-
)
|
|
1084
|
-
const entries = rows.map(transformEntryRow)
|
|
1085
|
-
return { entries, count: entries.length, source: 'database' }
|
|
1086
|
-
},
|
|
1087
|
-
},
|
|
1088
|
-
{
|
|
1089
|
-
uri: 'memory://tags',
|
|
1090
|
-
name: 'All Tags',
|
|
1091
|
-
title: 'Tag List',
|
|
1092
|
-
description: 'All available tags with usage counts',
|
|
1093
|
-
mimeType: 'application/json',
|
|
1094
|
-
icons: [ICON_TAG],
|
|
1095
|
-
annotations: {
|
|
1096
|
-
audience: ['assistant'],
|
|
1097
|
-
priority: 0.4,
|
|
1098
|
-
},
|
|
1099
|
-
handler: (_uri: string, context: ResourceContext) => {
|
|
1100
|
-
const tags: Tag[] = context.db.listTags()
|
|
1101
|
-
// Map usageCount to count for consistency with list_tags tool
|
|
1102
|
-
const mappedTags = tags.map((t) => ({
|
|
1103
|
-
id: t.id,
|
|
1104
|
-
name: t.name,
|
|
1105
|
-
count: t.usageCount,
|
|
1106
|
-
}))
|
|
1107
|
-
return { tags: mappedTags, count: mappedTags.length }
|
|
1108
|
-
},
|
|
1109
|
-
},
|
|
1110
|
-
{
|
|
1111
|
-
uri: 'memory://statistics',
|
|
1112
|
-
name: 'Statistics',
|
|
1113
|
-
title: 'Journal Statistics',
|
|
1114
|
-
description: 'Overall journal statistics',
|
|
1115
|
-
mimeType: 'application/json',
|
|
1116
|
-
icons: [ICON_ANALYTICS],
|
|
1117
|
-
annotations: {
|
|
1118
|
-
audience: ['assistant'],
|
|
1119
|
-
priority: 0.4,
|
|
1120
|
-
},
|
|
1121
|
-
handler: (_uri: string, context: ResourceContext) => {
|
|
1122
|
-
return context.db.getStatistics('week')
|
|
1123
|
-
},
|
|
1124
|
-
},
|
|
1125
|
-
{
|
|
1126
|
-
uri: 'memory://health',
|
|
1127
|
-
name: 'Server Health',
|
|
1128
|
-
title: 'Server Health & Diagnostics',
|
|
1129
|
-
description:
|
|
1130
|
-
'Server health status including database, backups, vector index (real-time stats), and tool filter status',
|
|
1131
|
-
mimeType: 'application/json',
|
|
1132
|
-
icons: [ICON_HEALTH],
|
|
1133
|
-
annotations: {
|
|
1134
|
-
audience: ['assistant'],
|
|
1135
|
-
priority: 0.9,
|
|
1136
|
-
},
|
|
1137
|
-
handler: async (_uri: string, context: ResourceContext): Promise<ResourceResult> => {
|
|
1138
|
-
const dbHealth = context.db.getHealthStatus()
|
|
1139
|
-
|
|
1140
|
-
// Get vector index status if available
|
|
1141
|
-
let vectorIndex: {
|
|
1142
|
-
available: boolean
|
|
1143
|
-
itemCount: number
|
|
1144
|
-
modelName: string | null
|
|
1145
|
-
} | null = null
|
|
1146
|
-
if (context.vectorManager) {
|
|
1147
|
-
try {
|
|
1148
|
-
const stats = await context.vectorManager.getStats()
|
|
1149
|
-
vectorIndex = {
|
|
1150
|
-
available: true,
|
|
1151
|
-
itemCount: stats.itemCount,
|
|
1152
|
-
modelName: stats.modelName,
|
|
1153
|
-
}
|
|
1154
|
-
} catch {
|
|
1155
|
-
vectorIndex = { available: false, itemCount: 0, modelName: null }
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// Get tool filter status
|
|
1160
|
-
const totalTools = getTotalToolCount()
|
|
1161
|
-
const toolFilter = {
|
|
1162
|
-
active: context.filterConfig !== null && context.filterConfig !== undefined,
|
|
1163
|
-
enabledCount: context.filterConfig?.enabledTools.size ?? totalTools,
|
|
1164
|
-
totalCount: totalTools,
|
|
1165
|
-
filterString: context.filterConfig?.raw ?? null,
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const lastModified = new Date().toISOString()
|
|
1169
|
-
|
|
1170
|
-
return {
|
|
1171
|
-
data: {
|
|
1172
|
-
...dbHealth,
|
|
1173
|
-
vectorIndex,
|
|
1174
|
-
toolFilter,
|
|
1175
|
-
timestamp: lastModified,
|
|
1176
|
-
},
|
|
1177
|
-
annotations: { lastModified },
|
|
1178
|
-
}
|
|
1179
|
-
},
|
|
1180
|
-
},
|
|
1181
|
-
// GitHub status resource - compact overview with progressive disclosure
|
|
1182
|
-
{
|
|
1183
|
-
uri: 'memory://github/status',
|
|
1184
|
-
name: 'GitHub Status',
|
|
1185
|
-
title: 'GitHub Repository Status',
|
|
1186
|
-
description:
|
|
1187
|
-
'Compact GitHub status: repository, branch, CI, issues, PRs, Kanban summary',
|
|
1188
|
-
mimeType: 'application/json',
|
|
1189
|
-
icons: [ICON_GITHUB],
|
|
1190
|
-
annotations: {
|
|
1191
|
-
audience: ['assistant'],
|
|
1192
|
-
priority: 0.7,
|
|
1193
|
-
},
|
|
1194
|
-
handler: async (_uri: string, context: ResourceContext): Promise<ResourceResult> => {
|
|
1195
|
-
const lastModified = new Date().toISOString()
|
|
1196
|
-
|
|
1197
|
-
if (!context.github) {
|
|
1198
|
-
return {
|
|
1199
|
-
data: {
|
|
1200
|
-
error: 'GitHub integration not available',
|
|
1201
|
-
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1202
|
-
},
|
|
1203
|
-
annotations: { lastModified },
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
1208
|
-
const owner = repoInfo.owner
|
|
1209
|
-
const repo = repoInfo.repo
|
|
1210
|
-
|
|
1211
|
-
if (!owner || !repo) {
|
|
1212
|
-
return {
|
|
1213
|
-
data: {
|
|
1214
|
-
error: 'Could not detect repository',
|
|
1215
|
-
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1216
|
-
branch: repoInfo.branch,
|
|
1217
|
-
},
|
|
1218
|
-
annotations: { lastModified },
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// Get current commit
|
|
1223
|
-
let commit: string | null = null
|
|
1224
|
-
try {
|
|
1225
|
-
const repoContext = await context.github.getRepoContext()
|
|
1226
|
-
commit = repoContext.commit
|
|
1227
|
-
} catch {
|
|
1228
|
-
// Ignore
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// Get open issues (limited for token efficiency)
|
|
1232
|
-
const issues = await context.github.getIssues(owner, repo, 'open', 5)
|
|
1233
|
-
const openIssues = issues.map((i) => ({
|
|
1234
|
-
number: i.number,
|
|
1235
|
-
title: i.title.slice(0, 50),
|
|
1236
|
-
}))
|
|
1237
|
-
|
|
1238
|
-
// Get open PRs (limited for token efficiency)
|
|
1239
|
-
const prs = await context.github.getPullRequests(owner, repo, 'open', 5)
|
|
1240
|
-
const openPrs = prs.map((pr) => ({
|
|
1241
|
-
number: pr.number,
|
|
1242
|
-
title: pr.title.slice(0, 50),
|
|
1243
|
-
state: pr.state,
|
|
1244
|
-
}))
|
|
1245
|
-
|
|
1246
|
-
// Get CI status from workflow runs
|
|
1247
|
-
// Get CI status from latest workflow run (matches briefing logic)
|
|
1248
|
-
const workflowRuns = await context.github.getWorkflowRuns(owner, repo, 5)
|
|
1249
|
-
let ciStatus: 'passing' | 'failing' | 'pending' | 'cancelled' | 'unknown' =
|
|
1250
|
-
'unknown'
|
|
1251
|
-
let latestRun: { name: string; conclusion: string | null; headSha: string } | null =
|
|
1252
|
-
null
|
|
1253
|
-
|
|
1254
|
-
if (workflowRuns.length > 0) {
|
|
1255
|
-
// Find the latest completed run for accurate CI status
|
|
1256
|
-
const latestCompleted = workflowRuns.find((r) => r.status === 'completed')
|
|
1257
|
-
const latest = workflowRuns[0]
|
|
1258
|
-
|
|
1259
|
-
latestRun = {
|
|
1260
|
-
name: latest?.name ?? 'Unknown',
|
|
1261
|
-
conclusion: latest?.conclusion ?? null,
|
|
1262
|
-
headSha: latest?.headSha?.slice(0, 7) ?? '',
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
// CI status based on latest completed run (consistent with briefing)
|
|
1266
|
-
if (latestCompleted) {
|
|
1267
|
-
// Map workflow conclusion to CI status
|
|
1268
|
-
switch (latestCompleted.conclusion) {
|
|
1269
|
-
case 'success':
|
|
1270
|
-
ciStatus = 'passing'
|
|
1271
|
-
break
|
|
1272
|
-
case 'failure':
|
|
1273
|
-
ciStatus = 'failing'
|
|
1274
|
-
break
|
|
1275
|
-
case 'cancelled':
|
|
1276
|
-
ciStatus = 'cancelled'
|
|
1277
|
-
break
|
|
1278
|
-
default:
|
|
1279
|
-
ciStatus = 'unknown'
|
|
1280
|
-
}
|
|
1281
|
-
} else if (workflowRuns.some((r) => r.status !== 'completed')) {
|
|
1282
|
-
ciStatus = 'pending'
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// Get Kanban summary if project 1 exists (common default)
|
|
1287
|
-
let kanbanSummary: Record<string, number> | null = null
|
|
1288
|
-
try {
|
|
1289
|
-
const kanban = await context.github.getProjectKanban(owner, 1, repo)
|
|
1290
|
-
if (kanban) {
|
|
1291
|
-
kanbanSummary = {}
|
|
1292
|
-
for (const col of kanban.columns) {
|
|
1293
|
-
kanbanSummary[col.status] = col.items.length
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
} catch {
|
|
1297
|
-
// Kanban not available
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
// Get milestone summary
|
|
1301
|
-
let milestoneSummary:
|
|
1302
|
-
| {
|
|
1303
|
-
number: number
|
|
1304
|
-
title: string
|
|
1305
|
-
state: string
|
|
1306
|
-
openIssues: number
|
|
1307
|
-
closedIssues: number
|
|
1308
|
-
completionPercentage: number
|
|
1309
|
-
dueOn: string | null
|
|
1310
|
-
}[]
|
|
1311
|
-
| null = null
|
|
1312
|
-
try {
|
|
1313
|
-
const milestones = await context.github.getMilestones(owner, repo, 'open', 5)
|
|
1314
|
-
if (milestones.length > 0) {
|
|
1315
|
-
milestoneSummary = milestones.map((ms) => {
|
|
1316
|
-
const total = ms.openIssues + ms.closedIssues
|
|
1317
|
-
const pct = total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0
|
|
1318
|
-
return {
|
|
1319
|
-
number: ms.number,
|
|
1320
|
-
title: ms.title,
|
|
1321
|
-
state: ms.state,
|
|
1322
|
-
openIssues: ms.openIssues,
|
|
1323
|
-
closedIssues: ms.closedIssues,
|
|
1324
|
-
completionPercentage: pct,
|
|
1325
|
-
dueOn: ms.dueOn,
|
|
1326
|
-
}
|
|
1327
|
-
})
|
|
1328
|
-
}
|
|
1329
|
-
} catch {
|
|
1330
|
-
// Milestones not available
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
return {
|
|
1334
|
-
data: {
|
|
1335
|
-
repository: `${owner}/${repo}`,
|
|
1336
|
-
branch: repoInfo.branch,
|
|
1337
|
-
commit: commit?.slice(0, 7) ?? null,
|
|
1338
|
-
ci: {
|
|
1339
|
-
status: ciStatus,
|
|
1340
|
-
latestRun,
|
|
1341
|
-
},
|
|
1342
|
-
issues: {
|
|
1343
|
-
openCount: issues.length,
|
|
1344
|
-
items: openIssues,
|
|
1345
|
-
},
|
|
1346
|
-
pullRequests: {
|
|
1347
|
-
openCount: prs.length,
|
|
1348
|
-
items: openPrs,
|
|
1349
|
-
},
|
|
1350
|
-
kanbanSummary,
|
|
1351
|
-
milestones: milestoneSummary,
|
|
1352
|
-
},
|
|
1353
|
-
annotations: { lastModified },
|
|
1354
|
-
}
|
|
1355
|
-
},
|
|
1356
|
-
},
|
|
1357
|
-
// Repository insights resource
|
|
1358
|
-
{
|
|
1359
|
-
uri: 'memory://github/insights',
|
|
1360
|
-
name: 'Repository Insights',
|
|
1361
|
-
title: 'Repository Stars & Traffic Summary',
|
|
1362
|
-
description: 'Compact repo insights: stars, forks, 14-day traffic totals (~150 tokens)',
|
|
1363
|
-
mimeType: 'application/json',
|
|
1364
|
-
icons: [ICON_ANALYTICS],
|
|
1365
|
-
annotations: {
|
|
1366
|
-
audience: ['assistant'],
|
|
1367
|
-
priority: 0.4, // Lower than status — optional enrichment
|
|
1368
|
-
},
|
|
1369
|
-
handler: async (_uri: string, context: ResourceContext): Promise<ResourceResult> => {
|
|
1370
|
-
const lastModified = new Date().toISOString()
|
|
1371
|
-
|
|
1372
|
-
if (!context.github) {
|
|
1373
|
-
return {
|
|
1374
|
-
data: {
|
|
1375
|
-
error: 'GitHub integration not available',
|
|
1376
|
-
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1377
|
-
},
|
|
1378
|
-
annotations: { lastModified },
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
1383
|
-
const owner = repoInfo.owner
|
|
1384
|
-
const repo = repoInfo.repo
|
|
1385
|
-
|
|
1386
|
-
if (!owner || !repo) {
|
|
1387
|
-
return {
|
|
1388
|
-
data: {
|
|
1389
|
-
error: 'Could not detect repository',
|
|
1390
|
-
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1391
|
-
},
|
|
1392
|
-
annotations: { lastModified },
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// Get repo stats (stars, forks)
|
|
1397
|
-
const stats = await context.github.getRepoStats(owner, repo)
|
|
1398
|
-
|
|
1399
|
-
// Get traffic data (clones, views) - may fail if token lacks push access
|
|
1400
|
-
let traffic: { clones14d: number; views14d: number } | null = null
|
|
1401
|
-
try {
|
|
1402
|
-
const trafficData = await context.github.getTrafficData(owner, repo)
|
|
1403
|
-
if (trafficData) {
|
|
1404
|
-
traffic = {
|
|
1405
|
-
clones14d: trafficData.clones.total,
|
|
1406
|
-
views14d: trafficData.views.total,
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
} catch {
|
|
1410
|
-
// Traffic data requires push access - silently skip
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
return {
|
|
1414
|
-
data: {
|
|
1415
|
-
repository: `${owner}/${repo}`,
|
|
1416
|
-
stars: stats?.stars ?? null,
|
|
1417
|
-
forks: stats?.forks ?? null,
|
|
1418
|
-
watchers: stats?.watchers ?? null,
|
|
1419
|
-
...(traffic ?? {}),
|
|
1420
|
-
hint: !traffic
|
|
1421
|
-
? 'Traffic data requires push access to the repository.'
|
|
1422
|
-
: undefined,
|
|
1423
|
-
},
|
|
1424
|
-
annotations: { lastModified },
|
|
1425
|
-
}
|
|
1426
|
-
},
|
|
1427
|
-
},
|
|
1428
|
-
// Milestone resources
|
|
1429
|
-
{
|
|
1430
|
-
uri: 'memory://github/milestones',
|
|
1431
|
-
name: 'GitHub Milestones',
|
|
1432
|
-
title: 'GitHub Repository Milestones',
|
|
1433
|
-
description:
|
|
1434
|
-
'Open GitHub milestones with completion percentages, due dates, and issue counts',
|
|
1435
|
-
mimeType: 'application/json',
|
|
1436
|
-
icons: [ICON_MILESTONE],
|
|
1437
|
-
annotations: {
|
|
1438
|
-
audience: ['assistant'],
|
|
1439
|
-
priority: 0.6,
|
|
1440
|
-
},
|
|
1441
|
-
handler: async (_uri: string, context: ResourceContext) => {
|
|
1442
|
-
if (!context.github) {
|
|
1443
|
-
return {
|
|
1444
|
-
error: 'GitHub integration not available',
|
|
1445
|
-
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
1450
|
-
const owner = repoInfo.owner
|
|
1451
|
-
const repo = repoInfo.repo
|
|
1452
|
-
|
|
1453
|
-
if (!owner || !repo) {
|
|
1454
|
-
return {
|
|
1455
|
-
error: 'Could not detect repository',
|
|
1456
|
-
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
const milestones = await context.github.getMilestones(owner, repo, 'open', 20)
|
|
1461
|
-
const milestonesWithProgress = milestones.map((ms) => {
|
|
1462
|
-
const total = ms.openIssues + ms.closedIssues
|
|
1463
|
-
const completionPercentage =
|
|
1464
|
-
total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0
|
|
1465
|
-
return { ...ms, completionPercentage }
|
|
1466
|
-
})
|
|
1467
|
-
|
|
1468
|
-
return {
|
|
1469
|
-
repository: `${owner}/${repo}`,
|
|
1470
|
-
milestones: milestonesWithProgress,
|
|
1471
|
-
count: milestonesWithProgress.length,
|
|
1472
|
-
hint: 'Use get_github_milestones tool for state filtering. Use memory://milestones/{number} for detail.',
|
|
1473
|
-
}
|
|
1474
|
-
},
|
|
1475
|
-
},
|
|
1476
|
-
{
|
|
1477
|
-
uri: 'memory://milestones/{number}',
|
|
1478
|
-
name: 'Milestone Detail',
|
|
1479
|
-
title: 'GitHub Milestone Detail',
|
|
1480
|
-
description:
|
|
1481
|
-
'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.',
|
|
1482
|
-
mimeType: 'application/json',
|
|
1483
|
-
icons: [ICON_MILESTONE],
|
|
1484
|
-
annotations: {
|
|
1485
|
-
audience: ['assistant'],
|
|
1486
|
-
priority: 0.5,
|
|
1487
|
-
},
|
|
1488
|
-
handler: async (uri: string, context: ResourceContext) => {
|
|
1489
|
-
const match = /memory:\/\/milestones\/(\d+)/.exec(uri)
|
|
1490
|
-
const milestoneNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
1491
|
-
|
|
1492
|
-
if (milestoneNumber === null) {
|
|
1493
|
-
return { error: 'Invalid milestone number' }
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
if (!context.github) {
|
|
1497
|
-
return {
|
|
1498
|
-
error: 'GitHub integration not available',
|
|
1499
|
-
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
1504
|
-
const owner = repoInfo.owner
|
|
1505
|
-
const repo = repoInfo.repo
|
|
1506
|
-
|
|
1507
|
-
if (!owner || !repo) {
|
|
1508
|
-
return {
|
|
1509
|
-
error: 'Could not detect repository',
|
|
1510
|
-
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
const milestone = await context.github.getMilestone(owner, repo, milestoneNumber)
|
|
1515
|
-
if (!milestone) {
|
|
1516
|
-
return { error: `Milestone #${String(milestoneNumber)} not found` }
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
const total = milestone.openIssues + milestone.closedIssues
|
|
1520
|
-
const completionPercentage =
|
|
1521
|
-
total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0
|
|
1522
|
-
|
|
1523
|
-
return {
|
|
1524
|
-
repository: `${owner}/${repo}`,
|
|
1525
|
-
milestone: { ...milestone, completionPercentage },
|
|
1526
|
-
hint: 'Use get_github_issues tool to list issues associated with this milestone.',
|
|
1527
|
-
}
|
|
1528
|
-
},
|
|
1529
|
-
},
|
|
1530
|
-
// Kanban board resources (GitHub Projects v2)
|
|
1531
|
-
{
|
|
1532
|
-
uri: 'memory://kanban/{project_number}',
|
|
1533
|
-
name: 'Kanban Board',
|
|
1534
|
-
title: 'GitHub Project Kanban Board',
|
|
1535
|
-
description: 'View a GitHub Project v2 as a Kanban board with items grouped by Status',
|
|
1536
|
-
mimeType: 'application/json',
|
|
1537
|
-
annotations: {
|
|
1538
|
-
audience: ['assistant'],
|
|
1539
|
-
priority: 0.6,
|
|
1540
|
-
},
|
|
1541
|
-
handler: async (uri: string, context: ResourceContext) => {
|
|
1542
|
-
const match = /memory:\/\/kanban\/(\d+)/.exec(uri)
|
|
1543
|
-
const projectNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
1544
|
-
|
|
1545
|
-
if (projectNumber === null) {
|
|
1546
|
-
return { error: 'Invalid project number' }
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
if (!context.github) {
|
|
1550
|
-
return {
|
|
1551
|
-
error: 'GitHub integration not available',
|
|
1552
|
-
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
1557
|
-
const owner = repoInfo.owner
|
|
1558
|
-
const repo = repoInfo.repo ?? undefined
|
|
1559
|
-
|
|
1560
|
-
if (!owner) {
|
|
1561
|
-
return {
|
|
1562
|
-
error: 'Could not detect repository owner',
|
|
1563
|
-
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
const board = await context.github.getProjectKanban(owner, projectNumber, repo)
|
|
1568
|
-
if (!board) {
|
|
1569
|
-
return {
|
|
1570
|
-
error: `Project #${String(projectNumber)} not found or Status field not configured`,
|
|
1571
|
-
projectNumber,
|
|
1572
|
-
owner,
|
|
1573
|
-
hint: 'Projects can be at user, repository, or organization level.',
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
return board
|
|
1578
|
-
},
|
|
1579
|
-
},
|
|
1580
|
-
{
|
|
1581
|
-
uri: 'memory://kanban/{project_number}/diagram',
|
|
1582
|
-
name: 'Kanban Diagram',
|
|
1583
|
-
title: 'Kanban Board Mermaid Diagram',
|
|
1584
|
-
description: 'Mermaid diagram visualization of a GitHub Project Kanban board',
|
|
1585
|
-
mimeType: 'text/plain',
|
|
1586
|
-
annotations: {
|
|
1587
|
-
audience: ['user', 'assistant'],
|
|
1588
|
-
priority: 0.5,
|
|
1589
|
-
},
|
|
1590
|
-
handler: async (uri: string, context: ResourceContext) => {
|
|
1591
|
-
const match = /memory:\/\/kanban\/(\d+)\/diagram/.exec(uri)
|
|
1592
|
-
const projectNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
1593
|
-
|
|
1594
|
-
if (projectNumber === null) {
|
|
1595
|
-
return { error: 'Invalid project number' }
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
if (!context.github) {
|
|
1599
|
-
return {
|
|
1600
|
-
format: 'mermaid',
|
|
1601
|
-
diagram: 'graph LR\n NoGitHub[GitHub integration not available]',
|
|
1602
|
-
message: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
const repoInfo = await context.github.getRepoInfo()
|
|
1607
|
-
const owner = repoInfo.owner
|
|
1608
|
-
const repo = repoInfo.repo ?? undefined
|
|
1609
|
-
|
|
1610
|
-
if (!owner) {
|
|
1611
|
-
return {
|
|
1612
|
-
format: 'mermaid',
|
|
1613
|
-
diagram: 'graph LR\n NoOwner[Repository owner not detected]',
|
|
1614
|
-
message: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
const board = await context.github.getProjectKanban(owner, projectNumber, repo)
|
|
1619
|
-
if (!board) {
|
|
1620
|
-
return {
|
|
1621
|
-
format: 'mermaid',
|
|
1622
|
-
diagram: `graph LR\n NotFound[Project #${String(projectNumber)} not found]`,
|
|
1623
|
-
message: 'Ensure the project exists and has a Status field.',
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
// Build Mermaid diagram with subgraphs for each column
|
|
1628
|
-
const lines: string[] = ['graph LR']
|
|
1629
|
-
|
|
1630
|
-
// Add style definitions
|
|
1631
|
-
lines.push(' classDef issue fill:#28a745,color:#fff')
|
|
1632
|
-
lines.push(' classDef pr fill:#6f42c1,color:#fff')
|
|
1633
|
-
lines.push(' classDef draft fill:#6c757d,color:#fff')
|
|
1634
|
-
|
|
1635
|
-
for (const column of board.columns) {
|
|
1636
|
-
const safeStatus = column.status.replace(/["\s]/g, '_')
|
|
1637
|
-
lines.push(
|
|
1638
|
-
` subgraph ${safeStatus}["${column.status} (${String(column.items.length)})"]`
|
|
1639
|
-
)
|
|
1640
|
-
|
|
1641
|
-
for (const item of column.items) {
|
|
1642
|
-
const safeId = item.id.replace(/[^a-zA-Z0-9]/g, '').slice(-8)
|
|
1643
|
-
const label = item.title.slice(0, 25).replace(/["[\]]/g, "'")
|
|
1644
|
-
const typeIcon =
|
|
1645
|
-
item.type === 'ISSUE'
|
|
1646
|
-
? '🔵'
|
|
1647
|
-
: item.type === 'PULL_REQUEST'
|
|
1648
|
-
? '🟣'
|
|
1649
|
-
: '⚪'
|
|
1650
|
-
const numberStr =
|
|
1651
|
-
item.number !== undefined && item.number !== 0
|
|
1652
|
-
? `#${String(item.number)}`
|
|
1653
|
-
: ''
|
|
1654
|
-
lines.push(` I${safeId}["${typeIcon} ${numberStr} ${label}..."]`)
|
|
1655
|
-
|
|
1656
|
-
// Add class based on type
|
|
1657
|
-
const typeClass =
|
|
1658
|
-
item.type === 'ISSUE'
|
|
1659
|
-
? 'issue'
|
|
1660
|
-
: item.type === 'PULL_REQUEST'
|
|
1661
|
-
? 'pr'
|
|
1662
|
-
: 'draft'
|
|
1663
|
-
lines.push(` class I${safeId} ${typeClass}`)
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
lines.push(' end')
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
return {
|
|
1670
|
-
format: 'mermaid',
|
|
1671
|
-
diagram: lines.join('\n'),
|
|
1672
|
-
projectNumber,
|
|
1673
|
-
projectTitle: board.projectTitle,
|
|
1674
|
-
columnCount: board.columns.length,
|
|
1675
|
-
totalItems: board.totalItems,
|
|
1676
|
-
legend: {
|
|
1677
|
-
'🔵': 'Issue',
|
|
1678
|
-
'🟣': 'Pull Request',
|
|
1679
|
-
'⚪': 'Draft Issue',
|
|
1680
|
-
},
|
|
1681
|
-
}
|
|
1682
|
-
},
|
|
1683
|
-
},
|
|
132
|
+
...getCoreResourceDefinitions(),
|
|
133
|
+
...getGraphResourceDefinitions(),
|
|
134
|
+
...getGitHubResourceDefinitions(),
|
|
135
|
+
...getTemplateResourceDefinitions(),
|
|
136
|
+
...getTeamResourceDefinitions(),
|
|
1684
137
|
]
|
|
1685
138
|
}
|