prjct-cli 0.39.0 → 0.41.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/CHANGELOG.md +38 -0
- package/core/agentic/orchestrator-executor.ts +23 -6
- package/core/integrations/jira/cache.ts +57 -0
- package/core/integrations/jira/index.ts +16 -11
- package/core/integrations/jira/service.ts +244 -0
- package/core/integrations/linear/cache.ts +68 -0
- package/core/integrations/linear/index.ts +15 -1
- package/core/integrations/linear/service.ts +260 -0
- package/core/services/skill-installer.ts +431 -0
- package/core/services/skill-lock.ts +132 -0
- package/core/services/skill-service.ts +34 -24
- package/core/types/services.ts +6 -1
- package/dist/bin/prjct.mjs +17 -6
- package/dist/core/infrastructure/setup.js +192 -238
- package/package.json +1 -1
- package/templates/commands/done.md +24 -1
- package/templates/commands/jira.md +91 -139
- package/templates/commands/linear.md +81 -130
- package/templates/commands/skill.md +174 -25
- package/templates/commands/task.md +66 -2
- package/templates/mcp-config.json +4 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.40.0] - 2026-01-28
|
|
4
|
+
|
|
5
|
+
### Feature: Enhanced Skill System
|
|
6
|
+
|
|
7
|
+
Inspired by vercel-labs/skills, prjct-cli now supports remote skill installation, version tracking via lock file, and full ecosystem-standard SKILL.md subdirectory format across all skill directories.
|
|
8
|
+
|
|
9
|
+
**New: Remote Skill Installation (`p. skill add`)**
|
|
10
|
+
- Install skills from GitHub repos: `p. skill add owner/repo`
|
|
11
|
+
- Install specific skill: `p. skill add owner/repo@skill-name`
|
|
12
|
+
- Install from local directory: `p. skill add ./path`
|
|
13
|
+
- Automatic source metadata injection (`_prjct` frontmatter block)
|
|
14
|
+
|
|
15
|
+
**New: Skill Lock File**
|
|
16
|
+
- Tracks installed skills at `~/.prjct-cli/skills/.skill-lock.json`
|
|
17
|
+
- Records source URL, commit SHA, install timestamp
|
|
18
|
+
- Enables update detection via `p. skill check`
|
|
19
|
+
|
|
20
|
+
**New: Additional Subcommands**
|
|
21
|
+
- `p. skill add <source>` — Install from GitHub or local path
|
|
22
|
+
- `p. skill remove <name>` — Uninstall a skill
|
|
23
|
+
- `p. skill init <name>` — Scaffold a new skill template
|
|
24
|
+
- `p. skill check` — Detect available updates
|
|
25
|
+
|
|
26
|
+
**Improved: Full SKILL.md Subdirectory Support**
|
|
27
|
+
- All skill directories now support both `{name}.md` (flat) and `{name}/SKILL.md` (subdirectory) formats
|
|
28
|
+
- Previously only provider dirs (`~/.claude/skills/`) checked subdirectories
|
|
29
|
+
- Orchestrator executor now checks both patterns when loading agent skills
|
|
30
|
+
|
|
31
|
+
**New Files:**
|
|
32
|
+
- `core/services/skill-installer.ts` — Remote installation service
|
|
33
|
+
- `core/services/skill-lock.ts` — Lock file management
|
|
34
|
+
|
|
35
|
+
**Modified:**
|
|
36
|
+
- `core/types/services.ts` — Extended `SkillMetadata` with `sourceUrl`, `sourceType`, `installedAt`, `sha`; added `'remote'` source type
|
|
37
|
+
- `core/services/skill-service.ts` — Unified SKILL.md discovery in all dirs
|
|
38
|
+
- `core/agentic/orchestrator-executor.ts` — Both path patterns for skill loading
|
|
39
|
+
- `templates/commands/skill.md` — New subcommands documentation
|
|
40
|
+
|
|
3
41
|
## [0.39.0] - 2026-01-24
|
|
4
42
|
|
|
5
43
|
### Feature: Windsurf IDE Support (PRJ-66)
|
|
@@ -344,18 +344,35 @@ export class OrchestratorExecutor {
|
|
|
344
344
|
// Skip if already loaded
|
|
345
345
|
if (loadedSkillNames.has(skillName)) continue
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
// Check both patterns: flat file and subdirectory (ecosystem standard)
|
|
348
|
+
const flatPath = path.join(skillsDir, `${skillName}.md`)
|
|
349
|
+
const subdirPath = path.join(skillsDir, skillName, 'SKILL.md')
|
|
350
|
+
|
|
351
|
+
let content: string | null = null
|
|
352
|
+
let resolvedPath = flatPath
|
|
353
|
+
|
|
354
|
+
// Prefer subdirectory format (ecosystem standard)
|
|
348
355
|
try {
|
|
349
|
-
|
|
356
|
+
content = await fs.readFile(subdirPath, 'utf-8')
|
|
357
|
+
resolvedPath = subdirPath
|
|
358
|
+
} catch {
|
|
359
|
+
// Fall back to flat file
|
|
360
|
+
try {
|
|
361
|
+
content = await fs.readFile(flatPath, 'utf-8')
|
|
362
|
+
resolvedPath = flatPath
|
|
363
|
+
} catch {
|
|
364
|
+
// Skill not found - not an error, just skip
|
|
365
|
+
console.warn(`Skill not found: ${skillName}`)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (content) {
|
|
350
370
|
skills.push({
|
|
351
371
|
name: skillName,
|
|
352
372
|
content,
|
|
353
|
-
filePath:
|
|
373
|
+
filePath: resolvedPath,
|
|
354
374
|
})
|
|
355
375
|
loadedSkillNames.add(skillName)
|
|
356
|
-
} catch {
|
|
357
|
-
// Skill not found - not an error, just skip
|
|
358
|
-
console.warn(`Skill not found: ${skillName}`)
|
|
359
376
|
}
|
|
360
377
|
}
|
|
361
378
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Cache Module
|
|
3
|
+
* 5-minute TTL cache for JIRA API responses to reduce API calls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TTLCache } from '../../utils/cache'
|
|
7
|
+
import type { Issue } from '../issue-tracker/types'
|
|
8
|
+
|
|
9
|
+
// 5-minute TTL for JIRA API responses
|
|
10
|
+
const JIRA_CACHE_TTL = 5 * 60 * 1000 // 300000ms
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cache for individual issues (by key like "ENG-123" or ID)
|
|
14
|
+
* Key format: "issue:{key}" or "issue:{id}"
|
|
15
|
+
*/
|
|
16
|
+
export const issueCache = new TTLCache<Issue>({
|
|
17
|
+
ttl: JIRA_CACHE_TTL,
|
|
18
|
+
maxSize: 100,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cache for assigned issues list
|
|
23
|
+
* Key format: "assigned:{userId}" or "assigned:me"
|
|
24
|
+
*/
|
|
25
|
+
export const assignedIssuesCache = new TTLCache<Issue[]>({
|
|
26
|
+
ttl: JIRA_CACHE_TTL,
|
|
27
|
+
maxSize: 10,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cache for projects list
|
|
32
|
+
* Key format: "projects"
|
|
33
|
+
*/
|
|
34
|
+
export const projectsCache = new TTLCache<Array<{ id: string; name: string; key?: string }>>({
|
|
35
|
+
ttl: JIRA_CACHE_TTL,
|
|
36
|
+
maxSize: 5,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Clear all JIRA caches
|
|
41
|
+
*/
|
|
42
|
+
export function clearJiraCache(): void {
|
|
43
|
+
issueCache.clear()
|
|
44
|
+
assignedIssuesCache.clear()
|
|
45
|
+
projectsCache.clear()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get cache statistics for debugging
|
|
50
|
+
*/
|
|
51
|
+
export function getJiraCacheStats() {
|
|
52
|
+
return {
|
|
53
|
+
issues: issueCache.stats(),
|
|
54
|
+
assignedIssues: assignedIssuesCache.stats(),
|
|
55
|
+
projects: projectsCache.stats(),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JIRA Integration
|
|
3
3
|
*
|
|
4
|
-
* Provides JIRA issue tracking integration for prjct-cli.
|
|
4
|
+
* Provides JIRA issue tracking integration for prjct-cli using REST API.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 1. API Token Mode (direct REST API):
|
|
6
|
+
* Authentication (API Token Mode):
|
|
9
7
|
* - JIRA_BASE_URL: Your JIRA instance URL (e.g., https://company.atlassian.net)
|
|
10
8
|
* - JIRA_EMAIL: Your Atlassian account email
|
|
11
9
|
* - JIRA_API_TOKEN: API token from https://id.atlassian.com/manage-profile/security/api-tokens
|
|
12
|
-
*
|
|
13
|
-
* 2. MCP Mode (for corporate SSO):
|
|
14
|
-
* - No environment variables needed
|
|
15
|
-
* - Requires Atlassian MCP server configured in ~/.claude/mcp.json
|
|
16
|
-
* - Authenticates via browser (OAuth 2.1, SSO compatible)
|
|
17
10
|
*/
|
|
18
11
|
|
|
19
|
-
// REST API client
|
|
12
|
+
// REST API client
|
|
20
13
|
export { JiraProvider, jiraProvider, type JiraAuthMode } from './client'
|
|
21
14
|
|
|
22
|
-
//
|
|
15
|
+
// Service layer with caching (preferred API)
|
|
16
|
+
export { JiraService, jiraService } from './service'
|
|
17
|
+
|
|
18
|
+
// Cache utilities
|
|
19
|
+
export {
|
|
20
|
+
issueCache,
|
|
21
|
+
assignedIssuesCache,
|
|
22
|
+
projectsCache,
|
|
23
|
+
clearJiraCache,
|
|
24
|
+
getJiraCacheStats,
|
|
25
|
+
} from './cache'
|
|
26
|
+
|
|
27
|
+
// MCP adapter (deprecated - will be removed)
|
|
23
28
|
export {
|
|
24
29
|
JiraMCPAdapter,
|
|
25
30
|
jiraMCPAdapter,
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Service Layer
|
|
3
|
+
* Wraps JiraProvider with caching for improved performance.
|
|
4
|
+
* All operations are cached with 5-minute TTL.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { jiraProvider } from './client'
|
|
8
|
+
import {
|
|
9
|
+
issueCache,
|
|
10
|
+
assignedIssuesCache,
|
|
11
|
+
projectsCache,
|
|
12
|
+
clearJiraCache,
|
|
13
|
+
getJiraCacheStats,
|
|
14
|
+
} from './cache'
|
|
15
|
+
import type {
|
|
16
|
+
Issue,
|
|
17
|
+
CreateIssueInput,
|
|
18
|
+
UpdateIssueInput,
|
|
19
|
+
FetchOptions,
|
|
20
|
+
JiraConfig,
|
|
21
|
+
} from '../issue-tracker/types'
|
|
22
|
+
|
|
23
|
+
export class JiraService {
|
|
24
|
+
private initialized = false
|
|
25
|
+
private userId: string | null = null
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if service is ready
|
|
29
|
+
*/
|
|
30
|
+
isReady(): boolean {
|
|
31
|
+
return this.initialized && jiraProvider.isConfigured()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the service with config
|
|
36
|
+
* Must be called before any operations
|
|
37
|
+
*/
|
|
38
|
+
async initialize(config: JiraConfig): Promise<void> {
|
|
39
|
+
if (this.initialized) return
|
|
40
|
+
|
|
41
|
+
await jiraProvider.initialize(config)
|
|
42
|
+
this.initialized = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize from credentials directly
|
|
47
|
+
* Convenience method for simple setup
|
|
48
|
+
*/
|
|
49
|
+
async initializeFromCredentials(
|
|
50
|
+
baseUrl: string,
|
|
51
|
+
email: string,
|
|
52
|
+
apiToken: string,
|
|
53
|
+
projectKey?: string
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
// Set env vars for the provider
|
|
56
|
+
process.env.JIRA_BASE_URL = baseUrl
|
|
57
|
+
process.env.JIRA_EMAIL = email
|
|
58
|
+
process.env.JIRA_API_TOKEN = apiToken
|
|
59
|
+
|
|
60
|
+
const config: JiraConfig = {
|
|
61
|
+
enabled: true,
|
|
62
|
+
provider: 'jira',
|
|
63
|
+
baseUrl,
|
|
64
|
+
projectKey,
|
|
65
|
+
syncOn: { task: true, done: true, ship: true },
|
|
66
|
+
enrichment: { enabled: true, updateProvider: true },
|
|
67
|
+
}
|
|
68
|
+
await this.initialize(config)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get issues assigned to current user (cached)
|
|
73
|
+
*/
|
|
74
|
+
async fetchAssignedIssues(options?: FetchOptions): Promise<Issue[]> {
|
|
75
|
+
this.ensureInitialized()
|
|
76
|
+
|
|
77
|
+
const cacheKey = `assigned:${this.userId || 'me'}`
|
|
78
|
+
const cached = assignedIssuesCache.get(cacheKey)
|
|
79
|
+
if (cached) {
|
|
80
|
+
return cached
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const issues = await jiraProvider.fetchAssignedIssues(options)
|
|
84
|
+
assignedIssuesCache.set(cacheKey, issues)
|
|
85
|
+
|
|
86
|
+
// Also cache individual issues
|
|
87
|
+
for (const issue of issues) {
|
|
88
|
+
issueCache.set(`issue:${issue.id}`, issue)
|
|
89
|
+
issueCache.set(`issue:${issue.externalId}`, issue)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return issues
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get issues from a project (cached)
|
|
97
|
+
*/
|
|
98
|
+
async fetchProjectIssues(projectKey: string, options?: FetchOptions): Promise<Issue[]> {
|
|
99
|
+
this.ensureInitialized()
|
|
100
|
+
|
|
101
|
+
const cacheKey = `project:${projectKey}`
|
|
102
|
+
const cached = assignedIssuesCache.get(cacheKey)
|
|
103
|
+
if (cached) {
|
|
104
|
+
return cached
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const issues = await jiraProvider.fetchTeamIssues(projectKey, options)
|
|
108
|
+
assignedIssuesCache.set(cacheKey, issues)
|
|
109
|
+
|
|
110
|
+
// Also cache individual issues
|
|
111
|
+
for (const issue of issues) {
|
|
112
|
+
issueCache.set(`issue:${issue.id}`, issue)
|
|
113
|
+
issueCache.set(`issue:${issue.externalId}`, issue)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return issues
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get a single issue by key (like "ENG-123") or ID (cached)
|
|
121
|
+
*/
|
|
122
|
+
async fetchIssue(id: string): Promise<Issue | null> {
|
|
123
|
+
this.ensureInitialized()
|
|
124
|
+
|
|
125
|
+
// Check cache first
|
|
126
|
+
const cacheKey = `issue:${id}`
|
|
127
|
+
const cached = issueCache.get(cacheKey)
|
|
128
|
+
if (cached) {
|
|
129
|
+
return cached
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const issue = await jiraProvider.fetchIssue(id)
|
|
133
|
+
if (issue) {
|
|
134
|
+
// Cache by both ID and key
|
|
135
|
+
issueCache.set(`issue:${issue.id}`, issue)
|
|
136
|
+
issueCache.set(`issue:${issue.externalId}`, issue)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return issue
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create a new issue (invalidates assigned cache)
|
|
144
|
+
*/
|
|
145
|
+
async createIssue(input: CreateIssueInput): Promise<Issue> {
|
|
146
|
+
this.ensureInitialized()
|
|
147
|
+
|
|
148
|
+
const issue = await jiraProvider.createIssue(input)
|
|
149
|
+
|
|
150
|
+
// Cache the new issue
|
|
151
|
+
issueCache.set(`issue:${issue.id}`, issue)
|
|
152
|
+
issueCache.set(`issue:${issue.externalId}`, issue)
|
|
153
|
+
|
|
154
|
+
// Invalidate assigned issues cache (new issue may be assigned)
|
|
155
|
+
assignedIssuesCache.clear()
|
|
156
|
+
|
|
157
|
+
return issue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Update an issue (invalidates cache for that issue)
|
|
162
|
+
*/
|
|
163
|
+
async updateIssue(id: string, input: UpdateIssueInput): Promise<Issue> {
|
|
164
|
+
this.ensureInitialized()
|
|
165
|
+
|
|
166
|
+
const issue = await jiraProvider.updateIssue(id, input)
|
|
167
|
+
|
|
168
|
+
// Update cache
|
|
169
|
+
issueCache.set(`issue:${issue.id}`, issue)
|
|
170
|
+
issueCache.set(`issue:${issue.externalId}`, issue)
|
|
171
|
+
|
|
172
|
+
return issue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Mark issue as in progress (invalidates cache)
|
|
177
|
+
*/
|
|
178
|
+
async markInProgress(id: string): Promise<void> {
|
|
179
|
+
this.ensureInitialized()
|
|
180
|
+
|
|
181
|
+
await jiraProvider.markInProgress(id)
|
|
182
|
+
|
|
183
|
+
// Invalidate caches
|
|
184
|
+
issueCache.delete(`issue:${id}`)
|
|
185
|
+
assignedIssuesCache.clear()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Mark issue as done (invalidates cache)
|
|
190
|
+
*/
|
|
191
|
+
async markDone(id: string): Promise<void> {
|
|
192
|
+
this.ensureInitialized()
|
|
193
|
+
|
|
194
|
+
await jiraProvider.markDone(id)
|
|
195
|
+
|
|
196
|
+
// Invalidate caches
|
|
197
|
+
issueCache.delete(`issue:${id}`)
|
|
198
|
+
assignedIssuesCache.clear()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get available projects (cached)
|
|
203
|
+
*/
|
|
204
|
+
async getProjects(): Promise<Array<{ id: string; name: string; key?: string }>> {
|
|
205
|
+
this.ensureInitialized()
|
|
206
|
+
|
|
207
|
+
const cached = projectsCache.get('projects')
|
|
208
|
+
if (cached) {
|
|
209
|
+
return cached
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const projects = await jiraProvider.getTeams()
|
|
213
|
+
projectsCache.set('projects', projects)
|
|
214
|
+
return projects
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Clear all caches
|
|
219
|
+
*/
|
|
220
|
+
clearCache(): void {
|
|
221
|
+
clearJiraCache()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get cache statistics for debugging
|
|
226
|
+
*/
|
|
227
|
+
getCacheStats() {
|
|
228
|
+
return getJiraCacheStats()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Ensure service is initialized
|
|
233
|
+
*/
|
|
234
|
+
private ensureInitialized(): void {
|
|
235
|
+
if (!this.initialized) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
'JIRA service not initialized. Call jiraService.initialize() first or run `p. jira setup`.'
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Singleton instance
|
|
244
|
+
export const jiraService = new JiraService()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear Cache Module
|
|
3
|
+
* 5-minute TTL cache for Linear API responses to reduce API calls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TTLCache } from '../../utils/cache'
|
|
7
|
+
import type { Issue } from '../issue-tracker/types'
|
|
8
|
+
|
|
9
|
+
// 5-minute TTL for Linear API responses
|
|
10
|
+
const LINEAR_CACHE_TTL = 5 * 60 * 1000 // 300000ms
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cache for individual issues (by ID or identifier)
|
|
14
|
+
* Key format: "issue:{id}" or "issue:{identifier}"
|
|
15
|
+
*/
|
|
16
|
+
export const issueCache = new TTLCache<Issue>({
|
|
17
|
+
ttl: LINEAR_CACHE_TTL,
|
|
18
|
+
maxSize: 100,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cache for assigned issues list
|
|
23
|
+
* Key format: "assigned:{userId}" or "assigned:me"
|
|
24
|
+
*/
|
|
25
|
+
export const assignedIssuesCache = new TTLCache<Issue[]>({
|
|
26
|
+
ttl: LINEAR_CACHE_TTL,
|
|
27
|
+
maxSize: 10,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cache for teams list
|
|
32
|
+
* Key format: "teams"
|
|
33
|
+
*/
|
|
34
|
+
export const teamsCache = new TTLCache<Array<{ id: string; name: string; key?: string }>>({
|
|
35
|
+
ttl: LINEAR_CACHE_TTL,
|
|
36
|
+
maxSize: 5,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Cache for projects list
|
|
41
|
+
* Key format: "projects"
|
|
42
|
+
*/
|
|
43
|
+
export const projectsCache = new TTLCache<Array<{ id: string; name: string }>>({
|
|
44
|
+
ttl: LINEAR_CACHE_TTL,
|
|
45
|
+
maxSize: 5,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Clear all Linear caches
|
|
50
|
+
*/
|
|
51
|
+
export function clearLinearCache(): void {
|
|
52
|
+
issueCache.clear()
|
|
53
|
+
assignedIssuesCache.clear()
|
|
54
|
+
teamsCache.clear()
|
|
55
|
+
projectsCache.clear()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get cache statistics for debugging
|
|
60
|
+
*/
|
|
61
|
+
export function getLinearCacheStats() {
|
|
62
|
+
return {
|
|
63
|
+
issues: issueCache.stats(),
|
|
64
|
+
assignedIssues: assignedIssuesCache.stats(),
|
|
65
|
+
teams: teamsCache.stats(),
|
|
66
|
+
projects: projectsCache.stats(),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Linear Integration
|
|
3
|
-
* Issue tracker provider for Linear
|
|
3
|
+
* Issue tracker provider for Linear using @linear/sdk
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// Core provider
|
|
6
7
|
export { LinearProvider, linearProvider } from './client'
|
|
8
|
+
|
|
9
|
+
// Service layer with caching (preferred API)
|
|
10
|
+
export { LinearService, linearService } from './service'
|
|
11
|
+
|
|
12
|
+
// Cache utilities
|
|
13
|
+
export {
|
|
14
|
+
issueCache,
|
|
15
|
+
assignedIssuesCache,
|
|
16
|
+
teamsCache,
|
|
17
|
+
projectsCache,
|
|
18
|
+
clearLinearCache,
|
|
19
|
+
getLinearCacheStats,
|
|
20
|
+
} from './cache'
|