mindsystem-cc 3.2.3 → 3.3.1

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 (51) hide show
  1. package/README.md +2 -2
  2. package/agents/ms-code-simplifier.md +215 -0
  3. package/agents/ms-consolidator.md +377 -0
  4. package/agents/ms-executor.md +32 -261
  5. package/{commands/ms/simplify-flutter.md → agents/ms-flutter-simplifier.md} +41 -49
  6. package/agents/ms-researcher.md +2 -2
  7. package/commands/ms/complete-milestone.md +24 -3
  8. package/commands/ms/do-work.md +17 -3
  9. package/commands/ms/execute-phase.md +29 -14
  10. package/commands/ms/help.md +1 -1
  11. package/commands/ms/new-milestone.md +42 -30
  12. package/commands/ms/progress.md +1 -1
  13. package/commands/ms/research-project.md +8 -0
  14. package/commands/ms/review-design.md +1 -1
  15. package/mindsystem/references/git-integration.md +2 -2
  16. package/mindsystem/references/goal-backward.md +2 -2
  17. package/mindsystem/references/principles.md +1 -1
  18. package/mindsystem/templates/adhoc-summary.md +3 -0
  19. package/mindsystem/templates/codebase/architecture.md +2 -2
  20. package/mindsystem/templates/codebase/structure.md +1 -1
  21. package/mindsystem/templates/config.json +2 -1
  22. package/mindsystem/templates/decisions.md +145 -0
  23. package/mindsystem/workflows/complete-milestone.md +66 -15
  24. package/mindsystem/workflows/create-milestone.md +60 -20
  25. package/mindsystem/workflows/do-work.md +81 -4
  26. package/mindsystem/workflows/execute-phase.md +66 -0
  27. package/mindsystem/workflows/map-codebase.md +9 -1
  28. package/mindsystem/workflows/verify-work.md +1 -1
  29. package/package.json +1 -1
  30. package/scripts/generate-adhoc-patch.sh +79 -0
  31. package/scripts/ms-lookup/uv.lock +17 -17
  32. package/commands/ms/discuss-milestone.md +0 -48
  33. package/commands/ms/linear.md +0 -195
  34. package/mindsystem/templates/milestone-context.md +0 -93
  35. package/scripts/ms-linear/ms_linear/__init__.py +0 -3
  36. package/scripts/ms-linear/ms_linear/__main__.py +0 -6
  37. package/scripts/ms-linear/ms_linear/__pycache__/__init__.cpython-314.pyc +0 -0
  38. package/scripts/ms-linear/ms_linear/__pycache__/__main__.cpython-314.pyc +0 -0
  39. package/scripts/ms-linear/ms_linear/__pycache__/cli.cpython-314.pyc +0 -0
  40. package/scripts/ms-linear/ms_linear/__pycache__/client.cpython-314.pyc +0 -0
  41. package/scripts/ms-linear/ms_linear/__pycache__/config.cpython-314.pyc +0 -0
  42. package/scripts/ms-linear/ms_linear/__pycache__/errors.cpython-314.pyc +0 -0
  43. package/scripts/ms-linear/ms_linear/__pycache__/output.cpython-314.pyc +0 -0
  44. package/scripts/ms-linear/ms_linear/cli.py +0 -604
  45. package/scripts/ms-linear/ms_linear/client.py +0 -503
  46. package/scripts/ms-linear/ms_linear/config.py +0 -104
  47. package/scripts/ms-linear/ms_linear/errors.py +0 -29
  48. package/scripts/ms-linear/ms_linear/output.py +0 -45
  49. package/scripts/ms-linear/pyproject.toml +0 -16
  50. package/scripts/ms-linear/uv.lock +0 -196
  51. package/scripts/ms-linear-wrapper.sh +0 -22
@@ -1,503 +0,0 @@
1
- """Linear GraphQL API client."""
2
-
3
- from typing import Any
4
-
5
- import httpx
6
-
7
- from ms_linear.config import LINEAR_API_URL, LinearConfig, get_api_key
8
- from ms_linear.errors import ErrorCode, MsLinearError
9
-
10
- # GraphQL Queries
11
- QUERY_ISSUE = """
12
- query Issue($id: String!) {
13
- issue(id: $id) {
14
- id
15
- identifier
16
- title
17
- description
18
- priority
19
- estimate
20
- url
21
- state {
22
- id
23
- name
24
- type
25
- }
26
- parent {
27
- identifier
28
- title
29
- }
30
- children {
31
- nodes {
32
- identifier
33
- title
34
- state {
35
- name
36
- }
37
- }
38
- }
39
- project {
40
- id
41
- name
42
- }
43
- team {
44
- id
45
- key
46
- name
47
- }
48
- }
49
- }
50
- """
51
-
52
- QUERY_WORKFLOW_STATES = """
53
- query {
54
- workflowStates {
55
- nodes {
56
- id
57
- name
58
- type
59
- position
60
- team {
61
- id
62
- key
63
- name
64
- }
65
- }
66
- }
67
- }
68
- """
69
-
70
- QUERY_TEAM_STATES = """
71
- query TeamWorkflowStates($teamId: String!) {
72
- team(id: $teamId) {
73
- id
74
- name
75
- key
76
- states {
77
- nodes {
78
- id
79
- name
80
- type
81
- position
82
- }
83
- }
84
- }
85
- }
86
- """
87
-
88
- QUERY_PROJECTS = """
89
- query {
90
- projects {
91
- nodes {
92
- id
93
- name
94
- state
95
- team {
96
- id
97
- key
98
- name
99
- }
100
- }
101
- }
102
- }
103
- """
104
-
105
- QUERY_TEAM_PROJECTS = """
106
- query TeamProjects($teamId: String!) {
107
- team(id: $teamId) {
108
- id
109
- name
110
- key
111
- projects {
112
- nodes {
113
- id
114
- name
115
- state
116
- }
117
- }
118
- }
119
- }
120
- """
121
-
122
- # GraphQL Mutations
123
- MUTATION_CREATE_ISSUE = """
124
- mutation IssueCreate($input: IssueCreateInput!) {
125
- issueCreate(input: $input) {
126
- success
127
- issue {
128
- id
129
- identifier
130
- title
131
- url
132
- state {
133
- name
134
- }
135
- }
136
- }
137
- }
138
- """
139
-
140
- MUTATION_UPDATE_ISSUE = """
141
- mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {
142
- issueUpdate(id: $id, input: $input) {
143
- success
144
- issue {
145
- id
146
- identifier
147
- title
148
- url
149
- state {
150
- id
151
- name
152
- }
153
- }
154
- }
155
- }
156
- """
157
-
158
-
159
- class LinearClient:
160
- """Client for Linear GraphQL API."""
161
-
162
- def __init__(self, api_key: str | None = None):
163
- self.api_key = api_key or get_api_key()
164
- self.client = httpx.Client(timeout=30.0)
165
-
166
- def _request(self, query: str, variables: dict[str, Any] | None = None) -> dict[str, Any]:
167
- """Make a GraphQL request to Linear."""
168
- headers = {
169
- "Content-Type": "application/json",
170
- "Authorization": self.api_key,
171
- }
172
-
173
- payload: dict[str, Any] = {"query": query}
174
- if variables:
175
- payload["variables"] = variables
176
-
177
- try:
178
- response = self.client.post(LINEAR_API_URL, headers=headers, json=payload)
179
- except httpx.NetworkError as e:
180
- raise MsLinearError(
181
- code=ErrorCode.NETWORK_ERROR,
182
- message=f"Network error: {e}",
183
- suggestions=["Check your internet connection"],
184
- )
185
-
186
- if response.status_code == 429:
187
- raise MsLinearError(
188
- code=ErrorCode.RATE_LIMITED,
189
- message="Rate limited by Linear API",
190
- suggestions=["Wait a moment and retry"],
191
- )
192
-
193
- if response.status_code != 200:
194
- raise MsLinearError(
195
- code=ErrorCode.API_ERROR,
196
- message=f"API error: HTTP {response.status_code}",
197
- suggestions=["Check your API key is valid"],
198
- )
199
-
200
- try:
201
- data = response.json()
202
- except ValueError:
203
- raise MsLinearError(
204
- code=ErrorCode.INVALID_RESPONSE,
205
- message="Invalid JSON response from Linear",
206
- )
207
-
208
- if "errors" in data:
209
- error_msg = data["errors"][0].get("message", "Unknown error")
210
- if "Authentication" in error_msg:
211
- raise MsLinearError(
212
- code=ErrorCode.MISSING_API_KEY,
213
- message="Authentication failed",
214
- suggestions=[
215
- "Check LINEAR_API_KEY is valid",
216
- "Get a new key from Linear Settings > API",
217
- ],
218
- )
219
- if "not found" in error_msg.lower():
220
- raise MsLinearError(
221
- code=ErrorCode.ISSUE_NOT_FOUND,
222
- message=error_msg,
223
- suggestions=["Check the issue identifier is correct"],
224
- )
225
- raise MsLinearError(
226
- code=ErrorCode.API_ERROR,
227
- message=error_msg,
228
- )
229
-
230
- return data.get("data", {})
231
-
232
- def get_issue(self, issue_id: str) -> dict[str, Any]:
233
- """Fetch issue details by ID or identifier."""
234
- data = self._request(QUERY_ISSUE, {"id": issue_id})
235
- issue = data.get("issue")
236
- if not issue:
237
- raise MsLinearError(
238
- code=ErrorCode.ISSUE_NOT_FOUND,
239
- message=f"Issue {issue_id} not found",
240
- suggestions=["Check the issue identifier is correct"],
241
- )
242
- return issue
243
-
244
- def get_workflow_states(self, team_id: str | None = None) -> list[dict[str, Any]]:
245
- """Get workflow states, optionally filtered by team."""
246
- if team_id:
247
- data = self._request(QUERY_TEAM_STATES, {"teamId": team_id})
248
- team = data.get("team", {})
249
- states = team.get("states", {}).get("nodes", [])
250
- # Add team info to each state
251
- team_info = {"id": team.get("id"), "key": team.get("key"), "name": team.get("name")}
252
- return [{"team": team_info, **state} for state in states]
253
- else:
254
- data = self._request(QUERY_WORKFLOW_STATES)
255
- return data.get("workflowStates", {}).get("nodes", [])
256
-
257
- def get_projects(self, team_id: str | None = None) -> list[dict[str, Any]]:
258
- """Get projects, optionally filtered by team."""
259
- if team_id:
260
- data = self._request(QUERY_TEAM_PROJECTS, {"teamId": team_id})
261
- team = data.get("team", {})
262
- projects = team.get("projects", {}).get("nodes", [])
263
- # Add team info to each project
264
- team_info = {"id": team.get("id"), "key": team.get("key"), "name": team.get("name")}
265
- return [{"team": team_info, **project} for project in projects]
266
- else:
267
- data = self._request(QUERY_PROJECTS)
268
- return data.get("projects", {}).get("nodes", [])
269
-
270
- def find_project_by_name(self, project_name: str, team_id: str | None = None) -> dict[str, Any]:
271
- """Find a project by name, optionally within a specific team."""
272
- projects = self.get_projects(team_id)
273
- project_name_lower = project_name.lower()
274
-
275
- # Exact match first
276
- for project in projects:
277
- if project["name"].lower() == project_name_lower:
278
- return project
279
-
280
- # Partial match
281
- for project in projects:
282
- if project_name_lower in project["name"].lower():
283
- return project
284
-
285
- available = ", ".join(sorted(set(p["name"] for p in projects)))
286
- raise MsLinearError(
287
- code=ErrorCode.PROJECT_NOT_FOUND,
288
- message=f"Project '{project_name}' not found",
289
- suggestions=[f"Available projects: {available}"],
290
- )
291
-
292
- def create_issue(
293
- self,
294
- config: LinearConfig,
295
- title: str,
296
- description: str | None = None,
297
- priority: int | None = None,
298
- estimate: int | None = None,
299
- parent_id: str | None = None,
300
- state_id: str | None = None,
301
- label_ids: list[str] | None = None,
302
- project_id: str | None = None,
303
- no_project: bool = False,
304
- ) -> dict[str, Any]:
305
- """Create a new issue.
306
-
307
- Args:
308
- config: Linear configuration
309
- title: Issue title
310
- description: Issue description (markdown)
311
- priority: Priority (0-4)
312
- estimate: Estimate value
313
- parent_id: Parent issue ID for sub-issues
314
- state_id: Initial state ID
315
- label_ids: Label IDs to apply
316
- project_id: Explicit project ID (overrides config default)
317
- no_project: If True, don't assign to any project (ignores config default)
318
- """
319
- input_data: dict[str, Any] = {
320
- "teamId": config.team_id,
321
- "title": title,
322
- }
323
-
324
- if description:
325
- input_data["description"] = description
326
-
327
- # Project logic: explicit > config default, unless no_project
328
- if not no_project:
329
- if project_id:
330
- input_data["projectId"] = project_id
331
- elif config.project_id:
332
- input_data["projectId"] = config.project_id
333
-
334
- if priority is not None:
335
- input_data["priority"] = priority
336
- else:
337
- input_data["priority"] = config.default_priority
338
- if estimate is not None:
339
- input_data["estimate"] = estimate
340
- if parent_id:
341
- input_data["parentId"] = parent_id
342
- if state_id:
343
- input_data["stateId"] = state_id
344
- if label_ids:
345
- input_data["labelIds"] = label_ids
346
- elif config.default_labels:
347
- input_data["labelIds"] = config.default_labels
348
-
349
- data = self._request(MUTATION_CREATE_ISSUE, {"input": input_data})
350
- result = data.get("issueCreate", {})
351
-
352
- if not result.get("success"):
353
- raise MsLinearError(
354
- code=ErrorCode.API_ERROR,
355
- message="Failed to create issue",
356
- )
357
-
358
- return result.get("issue", {})
359
-
360
- def update_issue(
361
- self,
362
- issue_id: str,
363
- title: str | None = None,
364
- description: str | None = None,
365
- priority: int | None = None,
366
- state_id: str | None = None,
367
- ) -> dict[str, Any]:
368
- """Update an existing issue."""
369
- input_data: dict[str, Any] = {}
370
-
371
- if title is not None:
372
- input_data["title"] = title
373
- if description is not None:
374
- input_data["description"] = description
375
- if priority is not None:
376
- input_data["priority"] = priority
377
- if state_id is not None:
378
- input_data["stateId"] = state_id
379
-
380
- if not input_data:
381
- raise MsLinearError(
382
- code=ErrorCode.INVALID_INPUT,
383
- message="No fields to update",
384
- suggestions=["Provide at least one field to update"],
385
- )
386
-
387
- data = self._request(MUTATION_UPDATE_ISSUE, {"id": issue_id, "input": input_data})
388
- result = data.get("issueUpdate", {})
389
-
390
- if not result.get("success"):
391
- raise MsLinearError(
392
- code=ErrorCode.API_ERROR,
393
- message="Failed to update issue",
394
- )
395
-
396
- return result.get("issue", {})
397
-
398
- def find_state_by_name(self, team_id: str, state_name: str) -> dict[str, Any]:
399
- """Find a workflow state by name within a team."""
400
- states = self.get_workflow_states(team_id)
401
- state_name_lower = state_name.lower()
402
-
403
- # Exact match first
404
- for state in states:
405
- if state["name"].lower() == state_name_lower:
406
- return state
407
-
408
- # Partial match
409
- for state in states:
410
- if state_name_lower in state["name"].lower():
411
- return state
412
-
413
- # Type-based match (e.g., "done" matches completed type)
414
- type_mapping = {
415
- "done": "completed",
416
- "complete": "completed",
417
- "finished": "completed",
418
- "todo": "unstarted",
419
- "backlog": "backlog",
420
- "in progress": "started",
421
- "started": "started",
422
- "cancelled": "canceled",
423
- "canceled": "canceled",
424
- }
425
- if state_name_lower in type_mapping:
426
- target_type = type_mapping[state_name_lower]
427
- for state in states:
428
- if state.get("type") == target_type:
429
- return state
430
-
431
- available = ", ".join(sorted(set(s["name"] for s in states)))
432
- raise MsLinearError(
433
- code=ErrorCode.STATE_NOT_FOUND,
434
- message=f"State '{state_name}' not found",
435
- suggestions=[f"Available states: {available}"],
436
- )
437
-
438
- def mark_done(self, issue_id: str) -> dict[str, Any]:
439
- """Mark an issue as completed."""
440
- # First get the issue to find its team
441
- issue = self.get_issue(issue_id)
442
- team_id = issue.get("team", {}).get("id")
443
-
444
- if not team_id:
445
- raise MsLinearError(
446
- code=ErrorCode.API_ERROR,
447
- message="Could not determine team for issue",
448
- )
449
-
450
- # Find the completed state
451
- done_state = self.find_state_by_name(team_id, "done")
452
- return self.update_issue(issue_id, state_id=done_state["id"])
453
-
454
- def change_state(self, issue_id: str, state_name: str) -> dict[str, Any]:
455
- """Change an issue's state by name."""
456
- # First get the issue to find its team
457
- issue = self.get_issue(issue_id)
458
- team_id = issue.get("team", {}).get("id")
459
-
460
- if not team_id:
461
- raise MsLinearError(
462
- code=ErrorCode.API_ERROR,
463
- message="Could not determine team for issue",
464
- )
465
-
466
- # Find the target state
467
- target_state = self.find_state_by_name(team_id, state_name)
468
- return self.update_issue(issue_id, state_id=target_state["id"])
469
-
470
- def create_sub_issues(
471
- self,
472
- config: LinearConfig,
473
- parent_id: str,
474
- issues: list[dict[str, Any]],
475
- project_id: str | None = None,
476
- no_project: bool = False,
477
- ) -> list[dict[str, Any]]:
478
- """Create multiple sub-issues under a parent."""
479
- # Get parent issue to extract its UUID and project
480
- parent = self.get_issue(parent_id)
481
- parent_uuid = parent.get("id")
482
-
483
- # Inherit parent's project if not explicitly specified
484
- if not no_project and not project_id:
485
- parent_project = parent.get("project")
486
- if parent_project:
487
- project_id = parent_project.get("id")
488
-
489
- created = []
490
- for issue_data in issues:
491
- issue = self.create_issue(
492
- config=config,
493
- title=issue_data.get("title", ""),
494
- description=issue_data.get("description"),
495
- priority=issue_data.get("priority"),
496
- estimate=issue_data.get("estimate"),
497
- parent_id=parent_uuid,
498
- project_id=project_id,
499
- no_project=no_project,
500
- )
501
- created.append(issue)
502
-
503
- return created
@@ -1,104 +0,0 @@
1
- """Configuration and environment variables."""
2
-
3
- import json
4
- import os
5
- from dataclasses import dataclass
6
- from pathlib import Path
7
-
8
- from ms_linear.errors import ErrorCode, MsLinearError
9
-
10
- # API Configuration
11
- LINEAR_API_URL = "https://api.linear.app/graphql"
12
-
13
-
14
- @dataclass
15
- class LinearConfig:
16
- """Linear project configuration from .linear.json."""
17
-
18
- team_id: str
19
- project_id: str | None = None
20
- default_priority: int = 3
21
- default_labels: list[str] | None = None
22
-
23
- def __post_init__(self) -> None:
24
- if self.default_labels is None:
25
- self.default_labels = []
26
-
27
-
28
- def get_api_key() -> str:
29
- """Get LINEAR_API_KEY from environment."""
30
- api_key = os.environ.get("LINEAR_API_KEY", "")
31
- if not api_key:
32
- raise MsLinearError(
33
- code=ErrorCode.MISSING_API_KEY,
34
- message="LINEAR_API_KEY environment variable not set",
35
- suggestions=[
36
- "Set LINEAR_API_KEY in your shell: export LINEAR_API_KEY='lin_api_...'",
37
- "Get your API key from Linear Settings > API > Personal API keys",
38
- ],
39
- )
40
- return api_key
41
-
42
-
43
- def load_config(config_path: Path | None = None) -> LinearConfig:
44
- """Load configuration from .linear.json.
45
-
46
- Searches upward from current directory if no path specified.
47
- """
48
- if config_path is None:
49
- config_path = find_config_file()
50
-
51
- if config_path is None or not config_path.exists():
52
- raise MsLinearError(
53
- code=ErrorCode.MISSING_CONFIG,
54
- message=".linear.json not found in project",
55
- suggestions=[
56
- "Create .linear.json in your project root",
57
- 'Required fields: {"teamId": "uuid", "projectId": "uuid"}',
58
- "Find IDs from Linear URLs or use `ms-linear states` to discover team",
59
- ],
60
- )
61
-
62
- try:
63
- with open(config_path) as f:
64
- data = json.load(f)
65
- except json.JSONDecodeError as e:
66
- raise MsLinearError(
67
- code=ErrorCode.INVALID_CONFIG,
68
- message=f"Invalid JSON in .linear.json: {e}",
69
- suggestions=["Check .linear.json for syntax errors"],
70
- )
71
-
72
- if "teamId" not in data:
73
- raise MsLinearError(
74
- code=ErrorCode.INVALID_CONFIG,
75
- message="teamId is required in .linear.json",
76
- suggestions=['Add "teamId": "your-team-uuid" to .linear.json'],
77
- )
78
-
79
- return LinearConfig(
80
- team_id=data["teamId"],
81
- project_id=data.get("projectId"),
82
- default_priority=data.get("defaultPriority", 3),
83
- default_labels=data.get("defaultLabels", []),
84
- )
85
-
86
-
87
- def find_config_file() -> Path | None:
88
- """Find .linear.json by searching upward from original working directory."""
89
- # Use MS_LINEAR_CWD if set (passed from wrapper script), otherwise fall back to cwd
90
- start_dir = os.environ.get("MS_LINEAR_CWD")
91
- current = Path(start_dir) if start_dir else Path.cwd()
92
-
93
- while current != current.parent:
94
- config_path = current / ".linear.json"
95
- if config_path.exists():
96
- return config_path
97
- current = current.parent
98
-
99
- # Check root
100
- root_config = current / ".linear.json"
101
- if root_config.exists():
102
- return root_config
103
-
104
- return None
@@ -1,29 +0,0 @@
1
- """Error codes and error handling."""
2
-
3
- from enum import Enum
4
-
5
-
6
- class ErrorCode(str, Enum):
7
- """Error codes for CLI responses."""
8
-
9
- MISSING_API_KEY = "MISSING_API_KEY"
10
- MISSING_CONFIG = "MISSING_CONFIG"
11
- INVALID_CONFIG = "INVALID_CONFIG"
12
- ISSUE_NOT_FOUND = "ISSUE_NOT_FOUND"
13
- STATE_NOT_FOUND = "STATE_NOT_FOUND"
14
- PROJECT_NOT_FOUND = "PROJECT_NOT_FOUND"
15
- API_ERROR = "API_ERROR"
16
- RATE_LIMITED = "RATE_LIMITED"
17
- NETWORK_ERROR = "NETWORK_ERROR"
18
- INVALID_RESPONSE = "INVALID_RESPONSE"
19
- INVALID_INPUT = "INVALID_INPUT"
20
-
21
-
22
- class MsLinearError(Exception):
23
- """Base exception for ms-linear errors."""
24
-
25
- def __init__(self, code: ErrorCode, message: str, suggestions: list[str] | None = None):
26
- self.code = code
27
- self.message = message
28
- self.suggestions = suggestions or []
29
- super().__init__(message)
@@ -1,45 +0,0 @@
1
- """JSON output formatting."""
2
-
3
- import json
4
- from typing import Any
5
-
6
- from ms_linear.errors import MsLinearError
7
-
8
-
9
- def format_success(
10
- command: str,
11
- result: dict[str, Any],
12
- metadata: dict[str, Any] | None = None,
13
- ) -> dict[str, Any]:
14
- """Format successful response."""
15
- response: dict[str, Any] = {
16
- "success": True,
17
- "command": command,
18
- "result": result,
19
- }
20
- if metadata:
21
- response["metadata"] = metadata
22
- return response
23
-
24
-
25
- def format_error(command: str, error: MsLinearError) -> dict[str, Any]:
26
- """Format error response."""
27
- error_dict: dict[str, Any] = {
28
- "code": error.code.value,
29
- "message": error.message,
30
- }
31
- if error.suggestions:
32
- error_dict["suggestions"] = error.suggestions
33
-
34
- return {
35
- "success": False,
36
- "command": command,
37
- "error": error_dict,
38
- }
39
-
40
-
41
- def output_json(data: dict[str, Any], pretty: bool = False) -> str:
42
- """Convert data to JSON string."""
43
- if pretty:
44
- return json.dumps(data, indent=2, ensure_ascii=False)
45
- return json.dumps(data, ensure_ascii=False)
@@ -1,16 +0,0 @@
1
- [project]
2
- name = "ms-linear"
3
- version = "1.0.0"
4
- description = "CLI tool for Mindsystem Linear integration - create, update, and manage issues"
5
- requires-python = ">=3.10"
6
- dependencies = [
7
- "typer>=0.9.0",
8
- "httpx>=0.25.0",
9
- ]
10
-
11
- [project.scripts]
12
- ms-linear = "ms_linear.cli:app"
13
-
14
- [build-system]
15
- requires = ["hatchling"]
16
- build-backend = "hatchling.build"