Skip to content

Commit 5fda90b

Browse files
author
StackMemory Bot (CLI)
committed
chore: handoff checkpoint on main
1 parent d98d1f0 commit 5fda90b

44 files changed

Lines changed: 5860 additions & 671 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bin/opencode-sm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
* OpenCode-SM CLI Launcher (ESM)
44
* Delegates to built CLI in dist without requiring tsx.
55
*/
6-
import('../dist/cli/opencode-sm.js');
6+
import('../dist/src/cli/opencode-sm.js');

docs/linear-extension/PLAN.md

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# Linear Chrome Extension - Implementation Plan
2+
3+
## Philosophy
4+
5+
**Types are everything.** Define the shape of all data first. The interfaces ARE the spec.
6+
7+
## Phase 1: Core Types (First)
8+
9+
Create `packages/linear-extension/src/types.ts` - this file defines the ENTIRE system:
10+
11+
```typescript
12+
// === EXTENSION DOMAIN ===
13+
14+
/** What the user captures from a webpage */
15+
export interface CapturedContent {
16+
text: string;
17+
sourceUrl: string;
18+
timestamp: Date;
19+
// GitHub-specific (optional)
20+
github?: {
21+
repo: string;
22+
filePath: string;
23+
lineStart?: number;
24+
lineEnd?: number;
25+
prNumber?: number;
26+
};
27+
}
28+
29+
/** User's ticket creation input */
30+
export interface TicketDraft {
31+
title: string;
32+
description: string;
33+
projectId: string;
34+
priority?: 'urgent' | 'high' | 'medium' | 'low' | 'none';
35+
labels?: string[];
36+
captured: CapturedContent;
37+
}
38+
39+
// === LINEAR API DOMAIN ===
40+
41+
/** Linear OAuth tokens (stored in chrome.storage) */
42+
export interface LinearAuth {
43+
accessToken: string;
44+
refreshToken?: string;
45+
expiresAt?: number;
46+
teamId: string;
47+
userId: string;
48+
}
49+
50+
/** Linear issue creation request */
51+
export interface LinearIssueCreate {
52+
title: string;
53+
description: string;
54+
teamId: string;
55+
projectId?: string;
56+
priority?: number; // 0-4
57+
labelIds?: string[];
58+
}
59+
60+
/** Linear issue response */
61+
export interface LinearIssue {
62+
id: string;
63+
identifier: string; // "STA-123"
64+
title: string;
65+
url: string;
66+
}
67+
68+
// === WEBHOOK DOMAIN ===
69+
70+
/** Incoming Linear webhook payload */
71+
export interface LinearWebhookPayload {
72+
action: 'create' | 'update' | 'remove';
73+
type: 'Issue' | 'Comment' | 'Project';
74+
createdAt: string;
75+
data: {
76+
id: string;
77+
identifier: string;
78+
title: string;
79+
description?: string;
80+
url: string;
81+
labels: Array<{ id: string; name: string }>;
82+
team: { id: string; key: string };
83+
project?: { id: string; name: string };
84+
};
85+
url: string;
86+
organizationId: string;
87+
}
88+
89+
/** Webhook validation result */
90+
export interface WebhookValidation {
91+
valid: boolean;
92+
error?: string;
93+
payload?: LinearWebhookPayload;
94+
}
95+
96+
// === SUBAGENT DOMAIN ===
97+
98+
/** Config for spawning a Claude Code subagent */
99+
export interface SubagentSpawnConfig {
100+
agentType: 'general-purpose' | 'code-reviewer' | 'debugger' | 'github-workflow';
101+
task: string;
102+
context: SubagentContext;
103+
options: SubagentOptions;
104+
}
105+
106+
export interface SubagentContext {
107+
linearIssueId: string;
108+
linearIdentifier: string; // "STA-123"
109+
linearUrl: string;
110+
sourceUrl: string;
111+
sourceText: string;
112+
github?: CapturedContent['github'];
113+
}
114+
115+
export interface SubagentOptions {
116+
autoCloseIssue: boolean;
117+
postResultsToLinear: boolean;
118+
timeout?: number;
119+
model?: 'sonnet' | 'opus' | 'haiku';
120+
}
121+
122+
/** Subagent execution result */
123+
export interface SubagentResult {
124+
sessionId: string;
125+
status: 'pending' | 'running' | 'completed' | 'failed';
126+
output?: string;
127+
error?: string;
128+
duration?: number;
129+
}
130+
131+
// === ERROR DOMAIN ===
132+
133+
export type ExtensionError =
134+
| { code: 'AUTH_REQUIRED'; message: string }
135+
| { code: 'AUTH_EXPIRED'; message: string }
136+
| { code: 'LINEAR_API_ERROR'; message: string; status: number }
137+
| { code: 'WEBHOOK_INVALID'; message: string }
138+
| { code: 'SUBAGENT_SPAWN_FAILED'; message: string };
139+
```
140+
141+
**Review checkpoint:** These types define EVERYTHING. If the types are wrong, everything cascades.
142+
143+
## Phase 2: Contracts / Pure Functions
144+
145+
### 2.1 Validation functions (pure, testable)
146+
147+
```typescript
148+
// src/validation.ts - ZERO side effects
149+
150+
export function validateWebhookPayload(raw: unknown): WebhookValidation;
151+
export function shouldTriggerSubagent(payload: LinearWebhookPayload): boolean;
152+
export function extractGitHubContext(url: string): CapturedContent['github'] | undefined;
153+
export function buildSubagentTask(payload: LinearWebhookPayload): string;
154+
export function mapLinearPriority(priority: TicketDraft['priority']): number;
155+
```
156+
157+
### 2.2 Transformation functions (pure, testable)
158+
159+
```typescript
160+
// src/transforms.ts - ZERO side effects
161+
162+
export function capturedToDescription(captured: CapturedContent): string;
163+
export function draftToLinearCreate(draft: TicketDraft, auth: LinearAuth): LinearIssueCreate;
164+
export function webhookToSpawnConfig(payload: LinearWebhookPayload): SubagentSpawnConfig;
165+
```
166+
167+
**Tests written FIRST for all pure functions.**
168+
169+
## Phase 3: Edge Logic (Boundaries)
170+
171+
### 3.1 Linear API Client
172+
173+
```typescript
174+
// src/linear-client.ts
175+
export class LinearClient {
176+
constructor(auth: LinearAuth);
177+
178+
async createIssue(issue: LinearIssueCreate): Promise<LinearIssue>;
179+
async addComment(issueId: string, body: string): Promise<void>;
180+
async getProjects(teamId: string): Promise<Array<{id: string; name: string}>>;
181+
async getLabels(teamId: string): Promise<Array<{id: string; name: string}>>;
182+
}
183+
```
184+
185+
### 3.2 Chrome Storage
186+
187+
```typescript
188+
// src/storage.ts
189+
export async function getAuth(): Promise<LinearAuth | null>;
190+
export async function setAuth(auth: LinearAuth): Promise<void>;
191+
export async function clearAuth(): Promise<void>;
192+
```
193+
194+
### 3.3 Webhook Handler (StackMemory side)
195+
196+
```typescript
197+
// src/webhook-handler.ts
198+
export async function handleLinearWebhook(
199+
req: Request,
200+
signature: string
201+
): Promise<SubagentResult | ExtensionError>;
202+
```
203+
204+
### 3.4 Subagent Spawner
205+
206+
```typescript
207+
// src/subagent-spawner.ts
208+
export async function spawnSubagent(
209+
config: SubagentSpawnConfig
210+
): Promise<SubagentResult>;
211+
```
212+
213+
## Phase 4: UI Components (Isolated)
214+
215+
### 4.1 Popup (`popup/`)
216+
- Project selector dropdown
217+
- Priority selector
218+
- Title input (prefilled)
219+
- Description textarea (prefilled with captured text)
220+
- Submit button
221+
- Status display
222+
223+
### 4.2 Context Menu
224+
- "Create Linear Ticket" on right-click
225+
- Opens popup with selection pre-filled
226+
227+
**Built in isolation, screenshot tested.**
228+
229+
## Phase 5: Integration
230+
231+
### 5.1 Wire extension → Linear
232+
- Popup calls `LinearClient.createIssue()`
233+
- Success shows ticket link
234+
235+
### 5.2 Wire Linear → Webhook
236+
- Configure Linear webhook in workspace settings
237+
- Point to StackMemory endpoint
238+
239+
### 5.3 Wire Webhook → Subagent
240+
- Webhook handler calls `spawnSubagent()`
241+
- Subagent updates Linear issue with results
242+
243+
### 5.4 E2E Test
244+
```
245+
1. Select text on GitHub PR
246+
2. Create ticket via extension
247+
3. Verify Linear issue created
248+
4. Verify webhook received
249+
5. Verify subagent spawned
250+
6. Verify results posted back to Linear
251+
```
252+
253+
## File Structure
254+
255+
```
256+
packages/linear-extension/
257+
├── src/
258+
│ ├── types.ts # Phase 1: ALL types
259+
│ ├── validation.ts # Phase 2: Pure validation
260+
│ ├── transforms.ts # Phase 2: Pure transforms
261+
│ ├── linear-client.ts # Phase 3: Linear API
262+
│ ├── storage.ts # Phase 3: Chrome storage
263+
│ ├── background.ts # Service worker
264+
│ └── popup/
265+
│ ├── popup.html
266+
│ ├── popup.ts
267+
│ └── popup.css
268+
├── manifest.json
269+
├── tests/
270+
│ ├── validation.test.ts
271+
│ └── transforms.test.ts
272+
└── package.json
273+
274+
src/integrations/linear/
275+
├── webhook-handler.ts # Phase 3: Webhook endpoint
276+
└── subagent-spawner.ts # Phase 3: Spawn logic
277+
278+
docker/
279+
├── Dockerfile.webhook
280+
└── docker-compose.yml
281+
```
282+
283+
## Implementation Order
284+
285+
1. **types.ts** - Define everything (30 min, REVIEW CAREFULLY)
286+
2. **validation.ts + tests** - Pure logic, TDD (1 hr)
287+
3. **transforms.ts + tests** - Pure logic, TDD (1 hr)
288+
4. **linear-client.ts** - API wrapper (1 hr)
289+
5. **webhook-handler.ts** - StackMemory integration (1 hr)
290+
6. **subagent-spawner.ts** - Spawn logic (1 hr)
291+
7. **popup/** - UI (1 hr)
292+
8. **Integration + E2E** - Wire together (2 hr)
293+
294+
## Container Setup
295+
296+
```yaml
297+
# docker-compose.yml
298+
services:
299+
webhook:
300+
build:
301+
context: .
302+
dockerfile: docker/Dockerfile.webhook
303+
ports:
304+
- "3456:3456"
305+
environment:
306+
- LINEAR_WEBHOOK_SECRET=${LINEAR_WEBHOOK_SECRET}
307+
- STACKMEMORY_PATH=/app/stackmemory
308+
volumes:
309+
- stackmemory-data:/app/.stackmemory
310+
311+
tunnel:
312+
image: cloudflare/cloudflared
313+
command: tunnel --url http://webhook:3456
314+
```
315+
316+
## Success Metrics
317+
318+
- [ ] Types compile with strict mode
319+
- [ ] 100% test coverage on validation/transforms
320+
- [ ] Extension installs without errors
321+
- [ ] OAuth flow completes
322+
- [ ] Ticket created in <3 clicks
323+
- [ ] Webhook triggers subagent within 10s
324+
- [ ] Results posted back to Linear

0 commit comments

Comments
 (0)