@dcyfr/ai 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +11 -30
  3. package/README.md +216 -145
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/ai/agents/agent-router.d.ts.map +1 -1
  6. package/dist/ai/agents/agent-router.js +3 -1
  7. package/dist/ai/agents/agent-router.js.map +1 -1
  8. package/dist/ai/config/loader.d.ts +5 -1
  9. package/dist/ai/config/loader.d.ts.map +1 -1
  10. package/dist/ai/config/loader.js +24 -4
  11. package/dist/ai/config/loader.js.map +1 -1
  12. package/dist/ai/core/provider-registry.d.ts +9 -1
  13. package/dist/ai/core/provider-registry.d.ts.map +1 -1
  14. package/dist/ai/core/provider-registry.js +97 -138
  15. package/dist/ai/core/provider-registry.js.map +1 -1
  16. package/dist/ai/core/telemetry-engine.d.ts +1 -1
  17. package/dist/ai/core/telemetry-engine.d.ts.map +1 -1
  18. package/dist/ai/core/telemetry-engine.js +14 -10
  19. package/dist/ai/core/telemetry-engine.js.map +1 -1
  20. package/dist/ai/delegation/contract-manager.d.ts.map +1 -1
  21. package/dist/ai/delegation/contract-manager.js +5 -2
  22. package/dist/ai/delegation/contract-manager.js.map +1 -1
  23. package/dist/ai/delegation/execution-mode-dashboard.d.ts.map +1 -1
  24. package/dist/ai/delegation/execution-mode-dashboard.js +4 -2
  25. package/dist/ai/delegation/execution-mode-dashboard.js.map +1 -1
  26. package/dist/ai/mcp/servers/shared/utils.d.ts.map +1 -1
  27. package/dist/ai/mcp/servers/shared/utils.js +4 -2
  28. package/dist/ai/mcp/servers/shared/utils.js.map +1 -1
  29. package/dist/ai/memory/file-memory-adapter.d.ts.map +1 -1
  30. package/dist/ai/memory/file-memory-adapter.js +11 -13
  31. package/dist/ai/memory/file-memory-adapter.js.map +1 -1
  32. package/dist/ai/memory/working-memory-persistence.d.ts.map +1 -1
  33. package/dist/ai/memory/working-memory-persistence.js +4 -2
  34. package/dist/ai/memory/working-memory-persistence.js.map +1 -1
  35. package/dist/ai/metacognition/config.d.ts +41 -0
  36. package/dist/ai/metacognition/config.d.ts.map +1 -0
  37. package/dist/ai/metacognition/config.js +51 -0
  38. package/dist/ai/metacognition/config.js.map +1 -0
  39. package/dist/ai/metacognition/governance.d.ts +68 -0
  40. package/dist/ai/metacognition/governance.d.ts.map +1 -0
  41. package/dist/ai/metacognition/governance.js +118 -0
  42. package/dist/ai/metacognition/governance.js.map +1 -0
  43. package/dist/ai/metacognition/index.d.ts +24 -0
  44. package/dist/ai/metacognition/index.d.ts.map +1 -0
  45. package/dist/ai/metacognition/index.js +18 -0
  46. package/dist/ai/metacognition/index.js.map +1 -0
  47. package/dist/ai/metacognition/ledger.d.ts +121 -0
  48. package/dist/ai/metacognition/ledger.d.ts.map +1 -0
  49. package/dist/ai/metacognition/ledger.js +268 -0
  50. package/dist/ai/metacognition/ledger.js.map +1 -0
  51. package/dist/ai/metacognition/runtime.d.ts +205 -0
  52. package/dist/ai/metacognition/runtime.d.ts.map +1 -0
  53. package/dist/ai/metacognition/runtime.js +391 -0
  54. package/dist/ai/metacognition/runtime.js.map +1 -0
  55. package/dist/ai/metacognition/telemetry.d.ts +144 -0
  56. package/dist/ai/metacognition/telemetry.d.ts.map +1 -0
  57. package/dist/ai/metacognition/telemetry.js +149 -0
  58. package/dist/ai/metacognition/telemetry.js.map +1 -0
  59. package/dist/ai/metacognition/transfer.d.ts +153 -0
  60. package/dist/ai/metacognition/transfer.d.ts.map +1 -0
  61. package/dist/ai/metacognition/transfer.js +182 -0
  62. package/dist/ai/metacognition/transfer.js.map +1 -0
  63. package/dist/ai/metacognition/types.d.ts +302 -0
  64. package/dist/ai/metacognition/types.d.ts.map +1 -0
  65. package/dist/ai/metacognition/types.js +79 -0
  66. package/dist/ai/metacognition/types.js.map +1 -0
  67. package/dist/ai/runtime/agent-runtime.d.ts.map +1 -1
  68. package/dist/ai/runtime/agent-runtime.js +12 -18
  69. package/dist/ai/runtime/agent-runtime.js.map +1 -1
  70. package/dist/ai/src/capability-manifest-generator.d.ts.map +1 -1
  71. package/dist/ai/src/capability-manifest-generator.js +11 -5
  72. package/dist/ai/src/capability-manifest-generator.js.map +1 -1
  73. package/dist/ai/src/cli/telemetry-dashboard.js +3 -3
  74. package/dist/ai/src/cli/telemetry-dashboard.js.map +1 -1
  75. package/dist/ai/src/compaction/memory-compaction.d.ts.map +1 -1
  76. package/dist/ai/src/compaction/memory-compaction.js +5 -4
  77. package/dist/ai/src/compaction/memory-compaction.js.map +1 -1
  78. package/dist/ai/src/integrations/linear/index.d.ts +19 -0
  79. package/dist/ai/src/integrations/linear/index.d.ts.map +1 -0
  80. package/dist/ai/src/integrations/linear/index.js +20 -0
  81. package/dist/ai/src/integrations/linear/index.js.map +1 -0
  82. package/dist/ai/src/integrations/linear/issue-mapper.d.ts +93 -0
  83. package/dist/ai/src/integrations/linear/issue-mapper.d.ts.map +1 -0
  84. package/dist/ai/src/integrations/linear/issue-mapper.js +186 -0
  85. package/dist/ai/src/integrations/linear/issue-mapper.js.map +1 -0
  86. package/dist/ai/src/integrations/linear/linear-client.d.ts +199 -0
  87. package/dist/ai/src/integrations/linear/linear-client.d.ts.map +1 -0
  88. package/dist/ai/src/integrations/linear/linear-client.js +300 -0
  89. package/dist/ai/src/integrations/linear/linear-client.js.map +1 -0
  90. package/dist/ai/src/plugins/security/secret-detector.js +3 -3
  91. package/dist/ai/src/plugins/security/secret-detector.js.map +1 -1
  92. package/dist/ai/src/runtime/agent-runtime.js +1 -1
  93. package/dist/ai/src/runtime/agent-runtime.js.map +1 -1
  94. package/dist/ai/src/security/prompt-scan-worker.d.ts +63 -0
  95. package/dist/ai/src/security/prompt-scan-worker.d.ts.map +1 -0
  96. package/dist/ai/src/security/prompt-scan-worker.js +177 -0
  97. package/dist/ai/src/security/prompt-scan-worker.js.map +1 -0
  98. package/dist/ai/src/telemetry/delegation-telemetry.d.ts +10 -0
  99. package/dist/ai/src/telemetry/delegation-telemetry.d.ts.map +1 -1
  100. package/dist/ai/src/telemetry/delegation-telemetry.js +23 -0
  101. package/dist/ai/src/telemetry/delegation-telemetry.js.map +1 -1
  102. package/dist/ai/types/index.d.ts +8 -3
  103. package/dist/ai/types/index.d.ts.map +1 -1
  104. package/dist/ai/types/index.js.map +1 -1
  105. package/dist/ai/utils/safe-fs.d.ts +19 -0
  106. package/dist/ai/utils/safe-fs.d.ts.map +1 -0
  107. package/dist/ai/utils/safe-fs.js +64 -0
  108. package/dist/ai/utils/safe-fs.js.map +1 -0
  109. package/package.json +33 -22
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Issue Key Correlation and Mapping
3
+ *
4
+ * Extracts issue keys from GitHub PR titles, branch names, and commit messages
5
+ * to automatically correlate them with Linear issues. Implements a priority-based
6
+ * matching strategy and stores mappings for future reference.
7
+ *
8
+ * Correlation Strategy (in order of confidence):
9
+ * 1. Branch name: `feature/DCYFR-123-description` [confidence: 0.95]
10
+ * 2. PR title: `[DCYFR-123] Feature description` [confidence: 0.85]
11
+ * 3. Commit message: Usually at end: `... Fixes DCYFR-123` [confidence: 0.75]
12
+ * 4. Manual mapping: User explicitly linked (stored in DB) [confidence: 1.0]
13
+ *
14
+ * Usage:
15
+ * const mapper = new IssueMapper({ store: myMappingStore });
16
+ * const match = await mapper.correlate({
17
+ * branch: 'feature/DCYFR-123-auth',
18
+ * prTitle: '[DCYFR-123] Add OAuth',
19
+ * commits: ['Add auth module (#123)', 'Fixes DCYFR-123']
20
+ * });
21
+ * // => { linearIssueId: 'uuid-xxx', identifier: 'DCYFR-123', confidence: 0.95 }
22
+ *
23
+ * Part of: Phase 2 (MVP Linear Sync) + Phase 3 (Bidirectional Sync)
24
+ * Test: __tests__/integrations/linear/issue-mapper.test.ts
25
+ */
26
+ // ============================================================================
27
+ // Issue Key Regex Patterns
28
+ // ============================================================================
29
+ /**
30
+ * Matches Jira-style issue keys: TEAM-NUMBER
31
+ * Examples: DCYFR-123, FOO-999
32
+ *
33
+ * Pattern explanation:
34
+ * [A-Z]+ = one or more uppercase letters (team key)
35
+ * - = hyphen
36
+ * \d+ = one or more digits (issue number)
37
+ */
38
+ const ISSUE_KEY_PATTERN = /([A-Z][A-Z0-9]*-\d+)/g;
39
+ /**
40
+ * Branch name format: feature/TEAM-NUMBER-description
41
+ */
42
+ const BRANCH_PATTERN = /^[a-z]+\/([A-Z][A-Z0-9]*-\d+)/;
43
+ /**
44
+ * PR title format: "[TEAM-NUMBER] Description" or "[TEAM-NUMBER] Description (Fixes)"
45
+ */
46
+ const PR_TITLE_PATTERN = /^\[([A-Z][A-Z0-9]*-\d+)\]/;
47
+ /**
48
+ * Commit pattern: "Description (Fixes|Closes|Resolves) TEAM-NUMBER"
49
+ */
50
+ const COMMIT_PATTERN = /(Fixes|Closes|Resolves|Fixed|Closed|Resolved)\s+([A-Z][A-Z0-9]*-\d+)/i;
51
+ // ============================================================================
52
+ // IssueMapper
53
+ // ============================================================================
54
+ export class IssueMapper {
55
+ /**
56
+ * Extract issue key from a string, returning up to `limit` matches.
57
+ */
58
+ static extractIssueKeys(text, limit = 5) {
59
+ const matches = text.matchAll(ISSUE_KEY_PATTERN);
60
+ return Array.from(matches)
61
+ .map(m => m[1])
62
+ .slice(0, limit)
63
+ .filter((v, i, a) => a.indexOf(v) === i); // Deduplicate
64
+ }
65
+ /**
66
+ * Extract issue key from branch name (highest confidence).
67
+ * Pattern: feature/DCYFR-123-description
68
+ */
69
+ static extractFromBranch(branch) {
70
+ const match = branch.match(BRANCH_PATTERN);
71
+ return match ? match[1] : null;
72
+ }
73
+ /**
74
+ * Extract issue key from PR title (high confidence).
75
+ * Patterns: "[DCYFR-123] Title", "[DCYFR-123]: Title"
76
+ */
77
+ static extractFromPrTitle(title) {
78
+ const match = title.match(PR_TITLE_PATTERN);
79
+ return match ? match[1] : null;
80
+ }
81
+ /**
82
+ * Extract issue key from commit messages (moderate confidence).
83
+ * Patterns: "Fixes DCYFR-123", "Closes DCYFR-123", "Resolves DCYFR-123"
84
+ */
85
+ static extractFromCommits(commits) {
86
+ for (const commit of commits) {
87
+ const match = commit.match(COMMIT_PATTERN);
88
+ if (match) {
89
+ return match[2]; // Return the issue key
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ /**
95
+ * Extract issue keys from PR body/description (low confidence).
96
+ * Uses generic issue key pattern matching.
97
+ */
98
+ static extractFromPrBody(body) {
99
+ return this.extractIssueKeys(body, 1);
100
+ }
101
+ /**
102
+ * Correlate a pull request to a Linear issue by examining branch, title, commits, and body.
103
+ *
104
+ * Returns the best match with highest confidence, plus alternates for manual review.
105
+ * Implements priority-based matching:
106
+ * 1. Branch name (0.95) — most specific
107
+ * 2. PR title (0.85)
108
+ * 3. Commit messages (0.75)
109
+ * 4. PR body (0.60) — lowest confidence
110
+ */
111
+ static correlate(input) {
112
+ const candidates = [];
113
+ // 1. Branch (highest priority)
114
+ if (input.branch) {
115
+ const key = this.extractFromBranch(input.branch);
116
+ if (key) {
117
+ candidates.push({
118
+ identifier: key,
119
+ source: 'branch',
120
+ confidence: 0.95,
121
+ });
122
+ }
123
+ }
124
+ // 2. PR title
125
+ if (input.prTitle) {
126
+ const key = this.extractFromPrTitle(input.prTitle);
127
+ if (key) {
128
+ candidates.push({
129
+ identifier: key,
130
+ source: 'pr_title',
131
+ confidence: 0.85,
132
+ });
133
+ }
134
+ }
135
+ // 3. Commit messages
136
+ if (input.commits && input.commits.length > 0) {
137
+ const key = this.extractFromCommits(input.commits);
138
+ if (key) {
139
+ candidates.push({
140
+ identifier: key,
141
+ source: 'commit_message',
142
+ confidence: 0.75,
143
+ });
144
+ }
145
+ }
146
+ // 4. PR body
147
+ if (input.prBody) {
148
+ const keys = this.extractFromPrBody(input.prBody);
149
+ for (const key of keys) {
150
+ candidates.push({
151
+ identifier: key,
152
+ source: 'pr_body',
153
+ confidence: 0.60,
154
+ });
155
+ }
156
+ }
157
+ // Return best match (by confidence, then by source priority)
158
+ if (candidates.length === 0) {
159
+ return {
160
+ identifier: null,
161
+ source: null,
162
+ confidence: 0,
163
+ alternates: [],
164
+ };
165
+ }
166
+ // Sort by confidence descending
167
+ candidates.sort((a, b) => b.confidence - a.confidence);
168
+ const best = candidates[0];
169
+ const alternates = candidates.slice(1);
170
+ return {
171
+ identifier: best.identifier,
172
+ source: best.source,
173
+ confidence: best.confidence,
174
+ alternates,
175
+ };
176
+ }
177
+ /**
178
+ * Validate that a correlation result meets minimum confidence threshold.
179
+ * Default threshold: 0.8 (requires high confidence)
180
+ */
181
+ static isConfident(result, threshold = 0.8) {
182
+ return result.confidence >= threshold && result.identifier !== null;
183
+ }
184
+ }
185
+ export default IssueMapper;
186
+ //# sourceMappingURL=issue-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-mapper.js","sourceRoot":"","sources":["../../../../../packages/ai/src/integrations/linear/issue-mapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Linear API Client
3
+ *
4
+ * Provides a typed, authenticated wrapper around the Linear GraphQL API for
5
+ * reading/writing issues, creating comments, adding labels, and managing
6
+ * issue state transitions.
7
+ *
8
+ * Usage:
9
+ * const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY });
10
+ * const issue = await client.getIssue('DCYFR-123');
11
+ * await client.addComment(issue.id, 'Review started');
12
+ * await client.updateIssueState(issue.id, 'In Review');
13
+ *
14
+ * Authentication:
15
+ * - Requires LINEAR_API_KEY environment variable (org API key)
16
+ * - API documentation: https://developers.linear.app/docs
17
+ * - GraphQL endpoint: https://api.linear.app/graphql
18
+ *
19
+ * Rate Limiting:
20
+ * - Linear API: 1000 requests/hour per token
21
+ * - Implements sliding-window rate limiter with exponential backoff
22
+ *
23
+ * Idempotency:
24
+ * - All write operations include deduplication keys
25
+ * - Prevents duplicate comments/updates on identical requests
26
+ *
27
+ * Error Handling:
28
+ * - GraphQL errors returned as structured `LinearError`
29
+ * - Network failures trigger exponential backoff retry
30
+ * - Rate limits surface as `RateLimitError`
31
+ *
32
+ * Part of: dcyfr-ai integration layer
33
+ * Roadmap: Phase 2 (MVP Linear Sync) + Phase 3 (Bidirectional Sync)
34
+ * Test: __tests__/integrations/linear/linear-client.test.ts
35
+ */
36
+ type GraphQLError = {
37
+ message: string;
38
+ extensions?: Record<string, unknown>;
39
+ };
40
+ /**
41
+ * Linear API credentials and configuration.
42
+ */
43
+ export interface LinearClientConfig {
44
+ /** Linear API key (org-scoped, from workspace settings) */
45
+ apiKey: string;
46
+ /** Optional: Override GraphQL endpoint (default: https://api.linear.app/graphql) */
47
+ graphqlEndpoint?: string;
48
+ /** Optional: Maximum retry attempts for transient failures (default: 3) */
49
+ maxRetries?: number;
50
+ /** Optional: Timeout in ms for API requests (default: 30000) */
51
+ requestTimeout?: number;
52
+ }
53
+ /**
54
+ * Mapping between Linear issue and GitHub PR/issue.
55
+ * Stored in persistent mapping store for correlation.
56
+ */
57
+ export interface IssueMapping {
58
+ /** Linear issue ID (UUID) */
59
+ linearIssueId: string;
60
+ /** GitHub repository owner */
61
+ owner: string;
62
+ /** GitHub repository name */
63
+ repo: string;
64
+ /** GitHub PR number (if PR) or issue number (if issue) */
65
+ prOrIssueNumber: number;
66
+ /** How the correlation was established */
67
+ correlationSource: 'branch' | 'pr_title' | 'commit_message' | 'manual';
68
+ /** Confidence score (0-1); 1.0 = certain, < 0.8 = needs review */
69
+ confidence: number;
70
+ /** When the mapping was created */
71
+ createdAt: Date;
72
+ /** When the mapping was last verified */
73
+ lastVerifiedAt?: Date;
74
+ }
75
+ /**
76
+ * Linear issue with relevant fields for review/PR sync.
77
+ */
78
+ export interface LinearIssue {
79
+ id: string;
80
+ identifier: string;
81
+ title: string;
82
+ description?: string;
83
+ state: {
84
+ id: string;
85
+ name: string;
86
+ };
87
+ assignee?: {
88
+ id: string;
89
+ name: string;
90
+ email: string;
91
+ };
92
+ team: {
93
+ id: string;
94
+ key: string;
95
+ };
96
+ labels: Array<{
97
+ id: string;
98
+ name: string;
99
+ }>;
100
+ createdAt: string;
101
+ updatedAt: string;
102
+ /** Custom field for linking to GitHub PR */
103
+ _githubPrUrl?: string;
104
+ /** Custom field for PR review status */
105
+ _prReviewStatus?: 'pending' | 'in_progress' | 'approved' | 'changes_requested';
106
+ }
107
+ /**
108
+ * Linear issue state for workflow transitions.
109
+ */
110
+ export interface LinearIssueState {
111
+ id: string;
112
+ name: string;
113
+ type: 'backlog' | 'unstarted' | 'started' | 'completed' | 'cancelled';
114
+ position: number;
115
+ }
116
+ /**
117
+ * Comment created on a Linear issue.
118
+ */
119
+ export interface LinearComment {
120
+ id: string;
121
+ body: string;
122
+ author: {
123
+ id: string;
124
+ name: string;
125
+ };
126
+ createdAt: string;
127
+ updatedAt: string;
128
+ }
129
+ /**
130
+ * Linear API error with detailed context.
131
+ */
132
+ export declare class LinearError extends Error {
133
+ readonly statusCode?: number | undefined;
134
+ readonly graphqlErrors?: GraphQLError[] | undefined;
135
+ constructor(message: string, statusCode?: number | undefined, graphqlErrors?: GraphQLError[] | undefined);
136
+ }
137
+ /**
138
+ * Rate limit error when API quota is exceeded.
139
+ */
140
+ export declare class LinearRateLimitError extends LinearError {
141
+ readonly resetAt: Date;
142
+ constructor(message: string, resetAt: Date);
143
+ }
144
+ export declare class LinearClient {
145
+ private apiKey;
146
+ private graphqlEndpoint;
147
+ private maxRetries;
148
+ private requestTimeout;
149
+ private requestCount;
150
+ private requestResetAt;
151
+ constructor(config: LinearClientConfig);
152
+ /**
153
+ * Retrieve a Linear issue by identifier (e.g., "DCYFR-123").
154
+ */
155
+ getIssue(identifier: string): Promise<LinearIssue>;
156
+ /**
157
+ * Get all available workflow states for a team.
158
+ * Used for determining valid state transitions.
159
+ */
160
+ getWorkflowStates(teamId: string): Promise<LinearIssueState[]>;
161
+ /**
162
+ * Update an issue's state (e.g., "In Review" → "Changes Requested").
163
+ * Returns the updated issue.
164
+ */
165
+ updateIssueState(issueId: string, newState: string): Promise<LinearIssue>;
166
+ /**
167
+ * Add a comment to a Linear issue.
168
+ * Includes deduplication key to prevent duplicate comments.
169
+ */
170
+ addComment(issueId: string, body: string, deduplicationKey?: string): Promise<LinearComment>;
171
+ /**
172
+ * Add a label to a Linear issue.
173
+ */
174
+ addLabel(issueId: string, labelId: string): Promise<void>;
175
+ /**
176
+ * Assign a Linear issue to a team member.
177
+ */
178
+ assignIssue(issueId: string, userId: string): Promise<void>;
179
+ /**
180
+ * Search for issues by query (e.g., team or label filters).
181
+ * Used for finding issues linked to GitHub PRs.
182
+ */
183
+ searchIssues(query: string, limit?: number): Promise<LinearIssue[]>;
184
+ /**
185
+ * Internal: Execute a GraphQL query/mutation with rate-limit and retry handling.
186
+ */
187
+ private graphql;
188
+ /**
189
+ * Check if request is within rate limit quota.
190
+ * Resets hourly.
191
+ */
192
+ private checkRateLimit;
193
+ /**
194
+ * Determine if an error is transient (can be retried).
195
+ */
196
+ private isTransient;
197
+ }
198
+ export default LinearClient;
199
+ //# sourceMappingURL=linear-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-client.d.ts","sourceRoot":"","sources":["../../../../../packages/ai/src/integrations/linear/linear-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,KAAK,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAc9E;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;IACxB,0CAA0C;IAC1C,iBAAiB,EAAE,QAAQ,GAAG,UAAU,GAAG,gBAAgB,GAAG,QAAQ,CAAC;IACvE,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,SAAS,EAAE,IAAI,CAAC;IAChB,yCAAyC;IACzC,cAAc,CAAC,EAAE,IAAI,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE;QACH,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,CAAC,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,IAAI,EAAE;QACF,EAAE,EAAE,MAAM,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;KACf,CAAC;IACF,MAAM,EAAE,KAAK,CAAC;QACV,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,eAAe,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,UAAU,GAAG,mBAAmB,CAAC;CAClF;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;IACtE,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;aAGd,UAAU,CAAC,EAAE,MAAM;aACnB,aAAa,CAAC,EAAE,YAAY,EAAE;gBAF9C,OAAO,EAAE,MAAM,EACC,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,aAAa,CAAC,EAAE,YAAY,EAAE,YAAA;CAKrD;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,WAAW;aAG7B,OAAO,EAAE,IAAI;gBAD7B,OAAO,EAAE,MAAM,EACC,OAAO,EAAE,IAAI;CAKpC;AAMD,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,cAAc,CAAwB;gBAElC,MAAM,EAAE,kBAAkB;IAWtC;;OAEG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAsBxD;;;OAGG;IACG,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyBpE;;;OAGG;IACG,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAsB/E;;;OAGG;IACG,UAAU,CACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,gBAAgB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,aAAa,CAAC;IAsBzB;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjE;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA2BrE;;OAEG;YACW,OAAO;IA0ErB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAkBtB;;OAEG;IACH,OAAO,CAAC,WAAW;CAUtB;AAED,eAAe,YAAY,CAAC"}
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Linear API Client
3
+ *
4
+ * Provides a typed, authenticated wrapper around the Linear GraphQL API for
5
+ * reading/writing issues, creating comments, adding labels, and managing
6
+ * issue state transitions.
7
+ *
8
+ * Usage:
9
+ * const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY });
10
+ * const issue = await client.getIssue('DCYFR-123');
11
+ * await client.addComment(issue.id, 'Review started');
12
+ * await client.updateIssueState(issue.id, 'In Review');
13
+ *
14
+ * Authentication:
15
+ * - Requires LINEAR_API_KEY environment variable (org API key)
16
+ * - API documentation: https://developers.linear.app/docs
17
+ * - GraphQL endpoint: https://api.linear.app/graphql
18
+ *
19
+ * Rate Limiting:
20
+ * - Linear API: 1000 requests/hour per token
21
+ * - Implements sliding-window rate limiter with exponential backoff
22
+ *
23
+ * Idempotency:
24
+ * - All write operations include deduplication keys
25
+ * - Prevents duplicate comments/updates on identical requests
26
+ *
27
+ * Error Handling:
28
+ * - GraphQL errors returned as structured `LinearError`
29
+ * - Network failures trigger exponential backoff retry
30
+ * - Rate limits surface as `RateLimitError`
31
+ *
32
+ * Part of: dcyfr-ai integration layer
33
+ * Roadmap: Phase 2 (MVP Linear Sync) + Phase 3 (Bidirectional Sync)
34
+ * Test: __tests__/integrations/linear/linear-client.test.ts
35
+ */
36
+ /**
37
+ * Linear API error with detailed context.
38
+ */
39
+ export class LinearError extends Error {
40
+ statusCode;
41
+ graphqlErrors;
42
+ constructor(message, statusCode, graphqlErrors) {
43
+ super(message);
44
+ this.statusCode = statusCode;
45
+ this.graphqlErrors = graphqlErrors;
46
+ this.name = 'LinearError';
47
+ }
48
+ }
49
+ /**
50
+ * Rate limit error when API quota is exceeded.
51
+ */
52
+ export class LinearRateLimitError extends LinearError {
53
+ resetAt;
54
+ constructor(message, resetAt) {
55
+ super(message, 429);
56
+ this.resetAt = resetAt;
57
+ this.name = 'LinearRateLimitError';
58
+ }
59
+ }
60
+ // ============================================================================
61
+ // Linear Client
62
+ // ============================================================================
63
+ export class LinearClient {
64
+ apiKey;
65
+ graphqlEndpoint;
66
+ maxRetries;
67
+ requestTimeout;
68
+ requestCount = 0;
69
+ requestResetAt = Date.now() + 3600000; // 1 hour from now
70
+ constructor(config) {
71
+ this.apiKey = config.apiKey;
72
+ this.graphqlEndpoint = config.graphqlEndpoint ?? 'https://api.linear.app/graphql';
73
+ this.maxRetries = config.maxRetries ?? 3;
74
+ this.requestTimeout = config.requestTimeout ?? 30000;
75
+ if (!this.apiKey) {
76
+ throw new LinearError('LINEAR_API_KEY is required');
77
+ }
78
+ }
79
+ /**
80
+ * Retrieve a Linear issue by identifier (e.g., "DCYFR-123").
81
+ */
82
+ async getIssue(identifier) {
83
+ const query = `
84
+ query GetIssue($identifier: String!) {
85
+ issue(id: $identifier) {
86
+ id
87
+ identifier
88
+ title
89
+ description
90
+ state { id name }
91
+ assignee { id name email }
92
+ team { id key }
93
+ labels { id name }
94
+ createdAt
95
+ updatedAt
96
+ }
97
+ }
98
+ `;
99
+ const data = await this.graphql(query, { identifier });
100
+ return data.issue;
101
+ }
102
+ /**
103
+ * Get all available workflow states for a team.
104
+ * Used for determining valid state transitions.
105
+ */
106
+ async getWorkflowStates(teamId) {
107
+ const query = `
108
+ query GetWorkflowStates($teamId: String!) {
109
+ team(id: $teamId) {
110
+ states {
111
+ nodes {
112
+ id
113
+ name
114
+ type
115
+ position
116
+ }
117
+ }
118
+ }
119
+ }
120
+ `;
121
+ const data = await this.graphql(query, { teamId });
122
+ return data.team.states.nodes;
123
+ }
124
+ /**
125
+ * Update an issue's state (e.g., "In Review" → "Changes Requested").
126
+ * Returns the updated issue.
127
+ */
128
+ async updateIssueState(issueId, newState) {
129
+ const mutation = `
130
+ mutation UpdateIssueState($issueId: String!, $state: String!) {
131
+ issueUpdate(id: $issueId, input: { stateId: $state }) {
132
+ issue {
133
+ id
134
+ identifier
135
+ title
136
+ state { id name }
137
+ updatedAt
138
+ }
139
+ }
140
+ }
141
+ `;
142
+ const data = await this.graphql(mutation, { issueId, state: newState });
143
+ return data.issueUpdate.issue;
144
+ }
145
+ /**
146
+ * Add a comment to a Linear issue.
147
+ * Includes deduplication key to prevent duplicate comments.
148
+ */
149
+ async addComment(issueId, body, deduplicationKey) {
150
+ const mutation = `
151
+ mutation AddComment($issueId: String!, $body: String!, $deduplicationKey: String) {
152
+ commentCreate(input: { issueId: $issueId, body: $body, deduplicationKey: $deduplicationKey }) {
153
+ comment {
154
+ id
155
+ body
156
+ author { id name }
157
+ createdAt
158
+ updatedAt
159
+ }
160
+ }
161
+ }
162
+ `;
163
+ const data = await this.graphql(mutation, { issueId, body, deduplicationKey });
164
+ return data.commentCreate.comment;
165
+ }
166
+ /**
167
+ * Add a label to a Linear issue.
168
+ */
169
+ async addLabel(issueId, labelId) {
170
+ const mutation = `
171
+ mutation AddLabel($issueId: String!, $labelIds: [String!]!) {
172
+ issueUpdate(id: $issueId, input: { labelIds: $labelIds }) {
173
+ issue { id }
174
+ }
175
+ }
176
+ `;
177
+ await this.graphql(mutation, { issueId, labelIds: [labelId] });
178
+ }
179
+ /**
180
+ * Assign a Linear issue to a team member.
181
+ */
182
+ async assignIssue(issueId, userId) {
183
+ const mutation = `
184
+ mutation AssignIssue($issueId: String!, $userId: String!) {
185
+ issueUpdate(id: $issueId, input: { assigneeId: $userId }) {
186
+ issue { id }
187
+ }
188
+ }
189
+ `;
190
+ await this.graphql(mutation, { issueId, userId });
191
+ }
192
+ /**
193
+ * Search for issues by query (e.g., team or label filters).
194
+ * Used for finding issues linked to GitHub PRs.
195
+ */
196
+ async searchIssues(query, limit = 25) {
197
+ const graphqlQuery = `
198
+ query SearchIssues($query: String!, $first: Int!) {
199
+ issues(filter: $query, first: $first) {
200
+ nodes {
201
+ id
202
+ identifier
203
+ title
204
+ description
205
+ state { id name }
206
+ assignee { id name email }
207
+ team { id key }
208
+ labels { id name }
209
+ createdAt
210
+ updatedAt
211
+ }
212
+ }
213
+ }
214
+ `;
215
+ const data = await this.graphql(graphqlQuery, { query, first: limit });
216
+ return data.issues.nodes;
217
+ }
218
+ /**
219
+ * Internal: Execute a GraphQL query/mutation with rate-limit and retry handling.
220
+ */
221
+ async graphql(query, variables, retryCount = 0) {
222
+ // Check rate limit
223
+ this.checkRateLimit();
224
+ const payload = {
225
+ query,
226
+ variables: variables ?? {},
227
+ };
228
+ const options = {
229
+ method: 'POST',
230
+ headers: {
231
+ 'Authorization': `Bearer ${this.apiKey}`,
232
+ 'Content-Type': 'application/json',
233
+ },
234
+ body: JSON.stringify(payload),
235
+ timeout: this.requestTimeout,
236
+ };
237
+ try {
238
+ const response = await fetch(this.graphqlEndpoint, options);
239
+ this.requestCount++;
240
+ // Handle rate limiting
241
+ if (response.status === 429) {
242
+ const resetAfter = response.headers.get('Retry-After');
243
+ const resetAt = new Date(Date.now() + (Number.parseInt(resetAfter ?? '60', 10) * 1000));
244
+ throw new LinearRateLimitError('Linear API rate limit exceeded', resetAt);
245
+ }
246
+ if (!response.ok) {
247
+ throw new LinearError(`Linear API returned ${response.status}`, response.status);
248
+ }
249
+ const data = await response.json();
250
+ // Handle GraphQL errors
251
+ if (data.errors && data.errors.length > 0) {
252
+ throw new LinearError(`GraphQL error: ${data.errors[0].message}`, 400, data.errors);
253
+ }
254
+ return data.data;
255
+ }
256
+ catch (error) {
257
+ // Retry on transient errors
258
+ if (retryCount < this.maxRetries && this.isTransient(error)) {
259
+ const backoffMs = Math.pow(2, retryCount) * 1000;
260
+ await new Promise(resolve => setTimeout(resolve, backoffMs));
261
+ return this.graphql(query, variables, retryCount + 1);
262
+ }
263
+ if (error instanceof LinearError) {
264
+ throw error;
265
+ }
266
+ throw new LinearError(`Linear API request failed: ${error instanceof Error ? error.message : String(error)}`);
267
+ }
268
+ }
269
+ /**
270
+ * Check if request is within rate limit quota.
271
+ * Resets hourly.
272
+ */
273
+ checkRateLimit() {
274
+ const now = Date.now();
275
+ // Reset counter if window has passed
276
+ if (now > this.requestResetAt) {
277
+ this.requestCount = 0;
278
+ this.requestResetAt = now + 3600000; // 1 hour from now
279
+ }
280
+ // Linear: 1000 requests/hour
281
+ if (this.requestCount >= 1000) {
282
+ throw new LinearRateLimitError('Linear API rate limit exceeded (1000/hour)', new Date(this.requestResetAt));
283
+ }
284
+ }
285
+ /**
286
+ * Determine if an error is transient (can be retried).
287
+ */
288
+ isTransient(error) {
289
+ if (error instanceof LinearRateLimitError) {
290
+ return true;
291
+ }
292
+ if (error instanceof LinearError) {
293
+ // Only retry on server errors (5xx)
294
+ return error.statusCode ? error.statusCode >= 500 : false;
295
+ }
296
+ return true; // Assume network errors are transient
297
+ }
298
+ }
299
+ export default LinearClient;
300
+ //# sourceMappingURL=linear-client.js.map