@fink-andreas/pi-linear-tools 0.4.2 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.4.3 (2026-04-02)
4
+
5
+ Sync-doc parity and tool prompt metadata polish release.
6
+
7
+ ### Bug Fixes
8
+ - Fixed first-run `sync-doc` batch parity so `check` and `run` produce aligned project index planning
9
+ - Preserved existing managed marker location when `position` is `top`
10
+ - Resolved archived projects more reliably during project update flows
11
+
12
+ ### Improvements
13
+ - Added `promptSnippet` metadata consistently across all Linear tools for better in-app tool discoverability/context
14
+ - Added default activity limit handling for issue activity execution paths
15
+
16
+ ### Tests
17
+ - Added/updated sync-doc regression coverage for first-run check/run parity and marker placement stability
18
+
3
19
  ## v0.4.2 (2026-03-26)
4
20
 
5
21
  Rate-limit resilience and API usage diagnostics release.
package/README.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  `pi-linear-tools` is a Pi extension for the [Pi coding agent](https://github.com/badlogic/pi-mono) that lets you manage [Linear](https://linear.app/about) issues, projects, and milestones via LLM tools and CLI commands.
4
4
 
5
+ Useful mental model:
6
+ - `issue update` changes issue fields; `issue comment` adds discussion; `issue activity` reads the Activity timeline
7
+ - `project update` changes project fields; `project-update` manages Updates tab entries
8
+ - `project-update` maps to Linear project updates in the Updates tab
9
+ - `sync-doc init` scaffolds `.linear-tools/config.json` in the folder you point at
10
+ - `sync-doc run` and `sync-doc check` default to all configured targets in `.linear-tools/config.json`
11
+ - `sync-doc --target X` narrows the operation to one configured target
12
+
13
+ Reference conventions:
14
+ - issues use issue key or issue ID
15
+ - projects use project name or project ID
16
+ - project updates use project update ID
17
+ - milestones use milestone ID
18
+
5
19
  ## Install
6
20
 
7
21
  ### As a pi package (recommended)
@@ -43,10 +57,13 @@ Optional non-interactive commands:
43
57
  ## LLM-callable tools
44
58
 
45
59
  ### `linear_issue`
46
- Actions: `list`, `view`, `create`, `update`, `comment`, `start`, `delete`
60
+ Actions: `list`, `view`, `activity`, `create`, `update`, `comment`, `start`, `delete`
47
61
 
48
62
  ### `linear_project`
49
- Actions: `list`
63
+ Actions: `list`, `view`, `create`, `update`, `delete`, `archive`, `unarchive`
64
+
65
+ ### `linear_project_update`
66
+ Actions: `list`, `view`, `create`, `update`, `archive`, `unarchive`
50
67
 
51
68
  ### `linear_milestone`
52
69
  Actions: `list`, `view`, `create`, `update`, `delete`
@@ -57,6 +74,12 @@ If installed globally via ```npm install -g @fink-andreas/pi-linear-tools```, CL
57
74
 
58
75
  ```bash
59
76
  pi-linear-tools --help
77
+ pi-linear-tools issue --help
78
+ pi-linear-tools project --help
79
+ pi-linear-tools project-update --help
80
+ pi-linear-tools sync-doc --help
81
+ pi-linear-tools sync-doc init --cwd /path/to/subproject --project "Project name or ID"
82
+ pi-linear-tools sync-doc explain
60
83
  pi-linear-tools config
61
84
  pi-linear-tools config --api-key lin_xxx
62
85
  pi-linear-tools config --default-team ENG
@@ -65,6 +88,8 @@ pi-linear-tools config --team ENG --project "My Project"
65
88
 
66
89
  ### Issue commands
67
90
 
91
+ Use `update` to change the issue itself. Use `comment` to add discussion. Use `activity` to read the Activity timeline shown in Linear.
92
+
68
93
  ```bash
69
94
  # List issues
70
95
  pi-linear-tools issue list --project "My Project"
@@ -75,6 +100,10 @@ pi-linear-tools issue list --project "My Project" --assignee me
75
100
  pi-linear-tools issue view ENG-123
76
101
  pi-linear-tools issue view ENG-123 --no-comments
77
102
 
103
+ # Read Activity timeline
104
+ pi-linear-tools issue activity ENG-123 --limit 20
105
+ pi-linear-tools issue activity https://linear.app/workspace/issue/ENG-123/example --limit 20
106
+
78
107
  # Create issue
79
108
  pi-linear-tools issue create --title "Fix login bug" --team ENG
80
109
  pi-linear-tools issue create --title "New feature" --team ENG --project "My Project" --priority 2 --assignee me
@@ -83,13 +112,15 @@ pi-linear-tools issue create --title "New feature" --team ENG --project "My Proj
83
112
  pi-linear-tools issue update ENG-123 --state "In Progress"
84
113
  pi-linear-tools issue update ENG-123 --title "Updated title" --assignee me
85
114
  pi-linear-tools issue update ENG-123 --milestone "Sprint 1"
115
+ pi-linear-tools issue update ENG-123 --sub-issue-of ENG-100
86
116
 
87
117
  # Comment on issue
88
118
  pi-linear-tools issue comment ENG-123 --body "This is fixed in PR #456"
119
+ pi-linear-tools issue comment ENG-123 --body "Blocked on API review"
89
120
 
90
121
  # Start working on issue (creates branch, sets state to In Progress)
91
122
  pi-linear-tools issue start ENG-123
92
- pi-linear-tools issue start ENG-123 --branch custom-branch-name
123
+ pi-linear-tools issue start ENG-123 --from-ref main --on-branch-exists suffix
93
124
 
94
125
  # Delete issue
95
126
  pi-linear-tools issue delete ENG-123
@@ -97,10 +128,107 @@ pi-linear-tools issue delete ENG-123
97
128
 
98
129
  ### Project commands
99
130
 
131
+ Use `project update` to change the project record itself: name, teams, dates, lead, description, priority, color, or icon.
132
+
100
133
  ```bash
101
134
  pi-linear-tools project list
135
+ pi-linear-tools project view "My Project"
136
+ pi-linear-tools project create --name "Roadmap Refresh" --teams ENG,OPS --lead me
137
+ pi-linear-tools project update "Roadmap Refresh" --description "Updated scope" --target-date 2026-06-30
138
+ pi-linear-tools project update "Roadmap Refresh" --lead me --teams ENG,OPS
139
+ pi-linear-tools project delete "Roadmap Refresh"
140
+ pi-linear-tools project archive "Roadmap Refresh"
141
+ pi-linear-tools project unarchive 11111111-1111-4111-8111-111111111111
102
142
  ```
103
143
 
144
+ ### Project update commands
145
+
146
+ This maps to the Updates tab inside a Linear project. Create an update by project name or ID, then use the returned project update ID for later view, update, archive, or unarchive actions.
147
+
148
+ ```bash
149
+ pi-linear-tools project-update list --project "My Project"
150
+ pi-linear-tools project-update view 22222222-2222-4222-8222-222222222222
151
+ pi-linear-tools project-update create --project "My Project" --body "Weekly progress update" --health onTrack
152
+ pi-linear-tools project-update update 22222222-2222-4222-8222-222222222222 --body "Revised update" --health atRisk
153
+ pi-linear-tools project-update archive 22222222-2222-4222-8222-222222222222
154
+ pi-linear-tools project-update unarchive 22222222-2222-4222-8222-222222222222
155
+ ```
156
+
157
+ ### Sync doc commands
158
+
159
+ ```bash
160
+ pi-linear-tools sync-doc init --cwd /path/to/subproject --project "Project name or ID"
161
+ pi-linear-tools sync-doc explain
162
+ pi-linear-tools sync-doc list
163
+ pi-linear-tools sync-doc run
164
+ pi-linear-tools sync-doc check
165
+ pi-linear-tools sync-doc run --target project-overview
166
+ pi-linear-tools sync-doc check --target project-overview
167
+ pi-linear-tools sync-doc run --file README.md --project "Project name or ID" --field content
168
+ pi-linear-tools sync-doc run --file docs/provider.md --project "Project name or ID" --target-type document --document-title "Provider Doc"
169
+ ```
170
+
171
+ Use one project overview target for the project body, then sync deeper docs as separate Linear documents.
172
+
173
+ Use `pi-linear-tools sync-doc init --cwd /path/to/subproject --project "Project name or ID"` to scaffold a starter config.
174
+
175
+ Placement rule:
176
+ - put `.linear-tools/config.json` in the smallest folder that owns the docs you are syncing
177
+ - the CLI resolves the nearest `.linear-tools/config.json` upward from the current directory
178
+ - use repo root only for targets intentionally shared across multiple subprojects
179
+ - `~/.linear-tools/config.json` is supported for personal defaults, but it should be treated as an override layer, not the main source of truth for repo-owned sync targets
180
+
181
+ Recommended pattern for multiple docs:
182
+ - one `projectField` target syncs the project overview into `project.content` or `project.description`
183
+ - deeper docs use `targetType: "document"` so each file gets its own Linear document
184
+ - the project overview target can set `documentIndexMarker` to maintain a managed list of links to those documents
185
+
186
+ For repos with multiple sync targets, the normal workflow is:
187
+ - define them in the repo-root `.linear-tools/config.json`
188
+ - run `pi-linear-tools sync-doc run` to push everything
189
+ - run `pi-linear-tools sync-doc check` in CI or before updates if you want drift visibility
190
+
191
+ Example:
192
+
193
+ ```json
194
+ {
195
+ "syncDocs": {
196
+ "targets": [
197
+ {
198
+ "name": "project-overview",
199
+ "file": "README.md",
200
+ "project": "Project name or ID",
201
+ "field": "content",
202
+ "marker": "project-overview",
203
+ "documentIndexMarker": "project-doc-links"
204
+ },
205
+ {
206
+ "name": "provider-foo",
207
+ "targetType": "document",
208
+ "file": "docs/provider.md",
209
+ "project": "Project name or ID",
210
+ "title": "Provider Doc",
211
+ "marker": "provider-foo",
212
+ "documentId": "optional-existing-document-id"
213
+ }
214
+ ]
215
+ }
216
+ }
217
+ ```
218
+
219
+ Store that config at `.linear-tools/config.json`. Sync state is written to `.linear-tools/sync-state.json`.
220
+ If `documentId` is omitted on a document target, the first sync creates a new Linear document and stores the created ID in sync state.
221
+
222
+ Managed content is wrapped in marker comments inside the target Linear field so manual content above or below the managed block is preserved:
223
+
224
+ ```md
225
+ <!-- linear-tools:sync-start README -->
226
+ ...synced markdown...
227
+ <!-- linear-tools:sync-end README -->
228
+ ```
229
+
230
+ When `documentIndexMarker` is configured on the overview target, the project field also gets a second managed block containing links to the synced Linear documents.
231
+
104
232
  ### Team commands
105
233
 
106
234
  ```bash
@@ -48,12 +48,25 @@ try {
48
48
  import {
49
49
  executeIssueList,
50
50
  executeIssueView,
51
+ executeIssueActivity,
51
52
  executeIssueCreate,
52
53
  executeIssueUpdate,
53
54
  executeIssueComment,
54
55
  executeIssueStart,
55
56
  executeIssueDelete,
56
57
  executeProjectList,
58
+ executeProjectView,
59
+ executeProjectCreate,
60
+ executeProjectUpdate,
61
+ executeProjectDelete,
62
+ executeProjectArchive,
63
+ executeProjectUnarchive,
64
+ executeProjectUpdateList,
65
+ executeProjectUpdateView,
66
+ executeProjectUpdateCreate,
67
+ executeProjectUpdateUpdate,
68
+ executeProjectUpdateArchive,
69
+ executeProjectUpdateUnarchive,
57
70
  executeTeamList,
58
71
  executeMilestoneList,
59
72
  executeMilestoneView,
@@ -478,18 +491,19 @@ async function registerLinearTools(pi) {
478
491
  pi.registerTool({
479
492
  name: 'linear_issue',
480
493
  label: 'Linear Issue',
481
- description: 'Interact with Linear issues. Actions: list, view, create, update, comment, start, delete',
494
+ description: 'Interact with Linear issues. Actions: list, view, activity, create, update, comment, start, delete',
495
+ promptSnippet: 'Interact with Linear issues (list, view, activity, create, update, comment, start, delete)',
482
496
  parameters: {
483
497
  type: 'object',
484
498
  properties: {
485
499
  action: {
486
500
  type: 'string',
487
- enum: ['list', 'view', 'create', 'update', 'comment', 'start', 'delete'],
501
+ enum: ['list', 'view', 'activity', 'create', 'update', 'comment', 'start', 'delete'],
488
502
  description: 'Action to perform on issue(s)',
489
503
  },
490
504
  issue: {
491
505
  type: 'string',
492
- description: 'Issue key (ABC-123) or Linear issue ID (for view, update, comment, start, delete)',
506
+ description: 'Issue key (ABC-123) or Linear issue ID (for view, activity, update, comment, start, delete)',
493
507
  },
494
508
  project: {
495
509
  type: 'string',
@@ -509,8 +523,8 @@ async function registerLinearTools(pi) {
509
523
  description: 'Optional explicit assignee ID alias for update/create debugging/compatibility.',
510
524
  },
511
525
  limit: {
512
- type: 'number',
513
- description: 'Maximum number of issues to list (default: 50)',
526
+ type: 'integer',
527
+ description: 'Maximum number of issues or activity entries to list',
514
528
  },
515
529
  includeComments: {
516
530
  type: 'boolean',
@@ -525,9 +539,13 @@ async function registerLinearTools(pi) {
525
539
  description: 'Issue description in markdown (for create, update)',
526
540
  },
527
541
  priority: {
528
- type: 'number',
542
+ type: 'integer',
529
543
  description: 'Priority 0..4 (for create, update)',
530
544
  },
545
+ includeArchived: {
546
+ type: 'boolean',
547
+ description: 'Include archived resources when listing activity or project updates',
548
+ },
531
549
  state: {
532
550
  type: 'string',
533
551
  description: 'Target state name or ID (for create, update)',
@@ -618,6 +636,8 @@ async function registerLinearTools(pi) {
618
636
  return await executeIssueList(client, params);
619
637
  case 'view':
620
638
  return await executeIssueView(client, params);
639
+ case 'activity':
640
+ return await executeIssueActivity(client, params);
621
641
  case 'create':
622
642
  return await executeIssueCreate(client, params, { resolveDefaultTeam });
623
643
  case 'update':
@@ -692,15 +712,59 @@ async function registerLinearTools(pi) {
692
712
  pi.registerTool({
693
713
  name: 'linear_project',
694
714
  label: 'Linear Project',
695
- description: 'Interact with Linear projects. Actions: list',
715
+ description: 'Interact with Linear projects. Actions: list, view, create, update, delete, archive, unarchive',
716
+ promptSnippet: 'Interact with Linear projects (list, view, create, update, delete, archive, unarchive)',
696
717
  parameters: {
697
718
  type: 'object',
698
719
  properties: {
699
720
  action: {
700
721
  type: 'string',
701
- enum: ['list'],
722
+ enum: ['list', 'view', 'create', 'update', 'delete', 'archive', 'unarchive'],
702
723
  description: 'Action to perform on project(s)',
703
724
  },
725
+ project: {
726
+ type: 'string',
727
+ description: 'Project name or ID (for view, update, delete)',
728
+ },
729
+ name: {
730
+ type: 'string',
731
+ description: 'Project name (required for create, optional for update)',
732
+ },
733
+ teams: {
734
+ type: 'string',
735
+ description: 'Comma-separated team keys or IDs (required for create, optional for update)',
736
+ },
737
+ description: {
738
+ type: 'string',
739
+ description: 'Project description in markdown',
740
+ },
741
+ lead: {
742
+ type: 'string',
743
+ description: 'Project lead user ID, "me", or "none" when updating',
744
+ },
745
+ priority: {
746
+ type: 'integer',
747
+ description: 'Priority 0-4',
748
+ minimum: 0,
749
+ maximum: 4,
750
+ multipleOf: 1,
751
+ },
752
+ color: {
753
+ type: 'string',
754
+ description: 'Project color (hex)',
755
+ },
756
+ icon: {
757
+ type: 'string',
758
+ description: 'Project icon',
759
+ },
760
+ startDate: {
761
+ type: 'string',
762
+ description: 'Planned start date (YYYY-MM-DD)',
763
+ },
764
+ targetDate: {
765
+ type: 'string',
766
+ description: 'Planned target date (YYYY-MM-DD)',
767
+ },
704
768
  },
705
769
  required: ['action'],
706
770
  additionalProperties: false,
@@ -714,6 +778,18 @@ async function registerLinearTools(pi) {
714
778
  switch (params.action) {
715
779
  case 'list':
716
780
  return await executeProjectList(client);
781
+ case 'view':
782
+ return await executeProjectView(client, params);
783
+ case 'create':
784
+ return await executeProjectCreate(client, params);
785
+ case 'update':
786
+ return await executeProjectUpdate(client, params);
787
+ case 'delete':
788
+ return await executeProjectDelete(client, params);
789
+ case 'archive':
790
+ return await executeProjectArchive(client, params);
791
+ case 'unarchive':
792
+ return await executeProjectUnarchive(client, params);
717
793
  default:
718
794
  throw new Error(`Unknown action: ${params.action}`);
719
795
  }
@@ -739,10 +815,102 @@ async function registerLinearTools(pi) {
739
815
  },
740
816
  });
741
817
 
818
+ pi.registerTool({
819
+ name: 'linear_project_update',
820
+ label: 'Linear Project Update',
821
+ description: 'Interact with Linear project updates. Actions: list, view, create, update, archive, unarchive',
822
+ promptSnippet: 'Interact with Linear project updates (list, view, create, update, archive, unarchive)',
823
+ parameters: {
824
+ type: 'object',
825
+ properties: {
826
+ action: {
827
+ type: 'string',
828
+ enum: ['list', 'view', 'create', 'update', 'archive', 'unarchive'],
829
+ description: 'Action to perform on project update(s)',
830
+ },
831
+ project: {
832
+ type: 'string',
833
+ description: 'Project name or ID (for list, create)',
834
+ },
835
+ projectUpdate: {
836
+ type: 'string',
837
+ description: 'Project update ID (for view, update, archive, unarchive)',
838
+ },
839
+ body: {
840
+ type: 'string',
841
+ description: 'Project update body in markdown',
842
+ },
843
+ health: {
844
+ type: 'string',
845
+ description: 'Project update health: onTrack, atRisk, or offTrack',
846
+ enum: ['onTrack', 'atRisk', 'offTrack'],
847
+ },
848
+ isDiffHidden: {
849
+ type: 'boolean',
850
+ description: 'Whether to hide the diff on the update',
851
+ },
852
+ limit: {
853
+ type: 'integer',
854
+ description: 'Max updates to list',
855
+ minimum: 1,
856
+ multipleOf: 1,
857
+ },
858
+ includeArchived: {
859
+ type: 'boolean',
860
+ description: 'Whether archived updates should be included when listing',
861
+ },
862
+ },
863
+ required: ['action'],
864
+ additionalProperties: false,
865
+ },
866
+ renderResult: renderMarkdownResult,
867
+ async execute(_toolCallId, params) {
868
+ try {
869
+ const client = await createAuthenticatedClient();
870
+
871
+ return await withRequestUsageLogging(client, 'linear_project_update', params.action, async () => {
872
+ switch (params.action) {
873
+ case 'list':
874
+ return await executeProjectUpdateList(client, params);
875
+ case 'view':
876
+ return await executeProjectUpdateView(client, params);
877
+ case 'create':
878
+ return await executeProjectUpdateCreate(client, params);
879
+ case 'update':
880
+ return await executeProjectUpdateUpdate(client, params);
881
+ case 'archive':
882
+ return await executeProjectUpdateArchive(client, params);
883
+ case 'unarchive':
884
+ return await executeProjectUpdateUnarchive(client, params);
885
+ default:
886
+ throw new Error(`Unknown action: ${params.action}`);
887
+ }
888
+ });
889
+ } catch (error) {
890
+ const errorType = error?.type || '';
891
+ const errorMessage = String(error?.message || error || 'Unknown error');
892
+
893
+ if (errorType === 'Ratelimited' || errorMessage.toLowerCase().includes('rate limit')) {
894
+ const resetAt = error?.requestsResetAt
895
+ ? new Date(error.requestsResetAt).toLocaleTimeString()
896
+ : 'approximately 1 hour from now';
897
+ throw new Error(`Linear API rate limit exceeded. Resets at: ${resetAt}. Please wait before retrying.`);
898
+ }
899
+
900
+ if (errorMessage.includes('Linear API error:')) {
901
+ throw error;
902
+ }
903
+
904
+ throw new Error(`Linear project update operation failed: ${errorMessage}`);
905
+ }
906
+ },
907
+ });
908
+
742
909
  pi.registerTool({
743
910
  name: 'linear_team',
744
911
  label: 'Linear Team',
745
912
  description: 'Interact with Linear teams. Actions: list',
913
+ promptSnippet: 'Interact with Linear teams (list)',
746
914
  parameters: {
747
915
  type: 'object',
748
916
  properties: {
@@ -794,6 +962,7 @@ async function registerLinearTools(pi) {
794
962
  name: 'linear_milestone',
795
963
  label: 'Linear Milestone',
796
964
  description: 'Interact with Linear project milestones. Actions: list, view, create, update, delete',
965
+ promptSnippet: 'Interact with Linear milestones (list, view, create, update, delete)',
797
966
  parameters: {
798
967
  type: 'object',
799
968
  properties: {
@@ -983,8 +1152,9 @@ export default async function piLinearToolsExtension(pi) {
983
1152
  const showMilestoneTool = await shouldExposeMilestoneTool();
984
1153
  const toolLines = [
985
1154
  'LLM-callable tools:',
986
- ' linear_issue (list/view/create/update/comment/start/delete)',
987
- ' linear_project (list)',
1155
+ ' linear_issue (list/view/activity/create/update/comment/start/delete)',
1156
+ ' linear_project (list/view/create/update/delete/archive/unarchive)',
1157
+ ' linear_project_update (list/view/create/update/archive/unarchive)',
988
1158
  ' linear_team (list)',
989
1159
  ];
990
1160