specvector 0.1.7 → 0.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specvector",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Context-aware AI code review using Model Context Protocol (MCP)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Linear Context Provider - Fetches ticket context for reviews.
3
3
  *
4
- * Extracts ticket ID from branch name/PR and fetches details via MCP.
4
+ * Extracts ticket ID from branch name/PR and fetches details via Linear GraphQL API.
5
5
  */
6
6
 
7
- import { createMCPClient } from "../mcp";
8
7
  import type { Result } from "../types/result";
9
8
  import { ok, err } from "../types/result";
10
9
 
@@ -13,12 +12,12 @@ import { ok, err } from "../types/result";
13
12
  // ============================================================================
14
13
 
15
14
  export interface LinearTicket {
16
- id: string;
17
- title: string;
18
- description: string;
19
- state: string;
20
- priority: number;
21
- labels: string[];
15
+ identifier?: string;
16
+ title?: string;
17
+ description?: string;
18
+ state?: { name?: string };
19
+ priority?: number;
20
+ labels?: { nodes?: Array<{ name?: string }> };
22
21
  }
23
22
 
24
23
  export interface LinearContextError {
@@ -76,7 +75,7 @@ export function extractTicketId(
76
75
  /**
77
76
  * Fetch Linear ticket context for a review.
78
77
  *
79
- * Returns formatted context string to inject into review prompt.
78
+ * Uses Linear's GraphQL API directly - simpler and more reliable than MCP.
80
79
  */
81
80
  export async function fetchLinearContext(
82
81
  ticketId: string
@@ -90,67 +89,85 @@ export async function fetchLinearContext(
90
89
  });
91
90
  }
92
91
 
93
- // Create MCP client
94
- const clientResult = await createMCPClient({
95
- name: "linear",
96
- command: "npx",
97
- args: ["linear-mcp-server"],
98
- env: {
99
- LINEAR_API_KEY: apiToken,
100
- },
101
- timeout: 30000,
102
- });
103
-
104
- if (!clientResult.ok) {
105
- return err({
106
- code: "CONNECTION_FAILED",
107
- message: clientResult.error.message,
108
- });
109
- }
110
-
111
- const client = clientResult.value;
112
-
113
92
  try {
114
- // Fetch ticket details
115
- const ticketResult = await client.callTool("get_ticket", { ticket_id: ticketId });
93
+ // Use Linear GraphQL API directly
94
+ const response = await fetch("https://api.linear.app/graphql", {
95
+ method: "POST",
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ Authorization: apiToken,
99
+ },
100
+ body: JSON.stringify({
101
+ query: `
102
+ query GetIssue($id: String!) {
103
+ issue(id: $id) {
104
+ identifier
105
+ title
106
+ description
107
+ state { name }
108
+ priority
109
+ labels { nodes { name } }
110
+ }
111
+ }
112
+ `,
113
+ variables: { id: ticketId },
114
+ }),
115
+ });
116
116
 
117
- if (!ticketResult.ok) {
117
+ if (!response.ok) {
118
118
  return err({
119
119
  code: "FETCH_FAILED",
120
- message: ticketResult.error.message,
120
+ message: `Linear API error: ${response.status}`,
121
121
  });
122
122
  }
123
123
 
124
- // Parse the response
125
- const content = ticketResult.value.content;
126
- const textContent = content.find(c => c.type === "text");
124
+ const data = await response.json() as { data?: { issue?: LinearTicket | null }; errors?: Array<{ message: string }> };
127
125
 
128
- if (!textContent?.text) {
126
+ if (data.errors?.length) {
127
+ return err({
128
+ code: "FETCH_FAILED",
129
+ message: data.errors[0]?.message ?? "GraphQL error",
130
+ });
131
+ }
132
+
133
+ const issue = data.data?.issue;
134
+ if (!issue) {
129
135
  return err({
130
136
  code: "FETCH_FAILED",
131
- message: "No ticket data returned",
137
+ message: `Issue ${ticketId} not found`,
132
138
  });
133
139
  }
134
140
 
135
141
  // Format the context for the review prompt
136
- const contextBlock = formatLinearContext(ticketId, textContent.text);
142
+ const contextBlock = formatLinearContext(ticketId, issue);
137
143
  return ok(contextBlock);
138
144
 
139
- } finally {
140
- await client.close();
145
+ } catch (error) {
146
+ return err({
147
+ code: "FETCH_FAILED",
148
+ message: error instanceof Error ? error.message : "Unknown error",
149
+ });
141
150
  }
142
151
  }
143
152
 
144
153
  /**
145
- * Format Linear ticket data as context for review prompt.
154
+ * Format Linear issue data as context for review prompt.
146
155
  */
147
- function formatLinearContext(ticketId: string, ticketData: string): string {
156
+ function formatLinearContext(ticketId: string, issue: { title?: string; description?: string; state?: { name?: string }; priority?: number; labels?: { nodes?: Array<{ name?: string }> } }): string {
157
+ const labels = issue.labels?.nodes?.map(l => l.name).filter(Boolean).join(", ") || "none";
158
+ const priority = issue.priority ?? 0;
159
+ const priorityLabels = ["", "Urgent", "High", "Medium", "Low", "No Priority"];
160
+
148
161
  return `
149
162
  ## Linear Ticket Context: ${ticketId}
150
163
 
151
- The following is the context from the linked Linear ticket. Use this to understand the business requirements and acceptance criteria for this PR.
164
+ **Title:** ${issue.title ?? "Untitled"}
165
+ **Status:** ${issue.state?.name ?? "Unknown"}
166
+ **Priority:** ${priorityLabels[priority] ?? "Unknown"}
167
+ **Labels:** ${labels}
152
168
 
153
- ${ticketData}
169
+ ### Description
170
+ ${issue.description ?? "No description provided."}
154
171
 
155
172
  ---
156
173
  When reviewing, check that the PR changes align with the ticket requirements above.