@fink-andreas/pi-linear-tools 0.7.0 → 0.7.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.
- package/CHANGELOG.md +23 -13
- package/extensions/pi-linear-tools.js +3 -3
- package/package.json +2 -2
- package/src/handlers.js +37 -59
- package/src/linear.js +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v0.7.
|
|
3
|
+
## v0.7.1 (2026-06-09)
|
|
4
|
+
|
|
5
|
+
Patch release for issue creation milestone assignment and configuration text cleanup.
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
- **Fixed issue create milestone assignment**: `linear_issue create` now assigns project milestones when `milestone` or `projectMilestoneId` is provided.
|
|
9
|
+
- **Fixed configuration text typo**: Corrected config command/help text in the pi extension.
|
|
10
|
+
|
|
11
|
+
### Release Maintenance
|
|
12
|
+
- Updated package metadata to `0.7.1`.
|
|
13
|
+
|
|
14
|
+
## v0.7.0 (2026-06-09)
|
|
4
15
|
|
|
5
16
|
Issue image fetching and file download release.
|
|
6
17
|
|
|
7
18
|
### New Features
|
|
8
|
-
-
|
|
9
|
-
-
|
|
19
|
+
- **Issue image fetching**: Added `linear_issue(action="images")` to fetch embedded markdown/HTML images from issue descriptions and comments as tool image content.
|
|
20
|
+
- **Issue attachment downloads**: Added `linear_issue(action="download")` to download Linear issue attachments to validated local paths with filename sanitization and overwrite controls.
|
|
21
|
+
- **Workspace-wide issue listing**: `linear_issue list` can now list across the workspace without requiring a project context.
|
|
22
|
+
- **Overwrite configuration**: Added `allow_overwrite_files` config support via `/linear-tools-config` and `pi-linear-tools config`.
|
|
10
23
|
|
|
11
|
-
###
|
|
12
|
-
-
|
|
24
|
+
### Bug Fixes
|
|
25
|
+
- Fixed project archive mutation so `archiveProject` archives instead of deleting projects.
|
|
26
|
+
- Fixed comment rendering so submitted comment text appears in `linear_issue comment` results.
|
|
27
|
+
- Fixed rate-limit display to reflect actual complexity-based limits instead of a hardcoded total.
|
|
28
|
+
- Added project and project-update rate-limit pre-checks.
|
|
13
29
|
|
|
14
30
|
### Documentation
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
### Bug Fixes
|
|
18
|
-
- **Fixed workspace-wide issue listing (#4)**: `linear_issue list` now falls back to workspace-level listing (using `fetchIssues`) when no project is specified and cwd doesn't match a Linear project. This allows `linear_issue list assignee=me` to work without requiring a project context.
|
|
19
|
-
- Fixed project archive mutation to actually archive instead of delete (INN-301).
|
|
20
|
-
- Fixed `linear_issue comment` to render submitted comment text in result (INN-305).
|
|
21
|
-
- Fixed rate limit display to not show hardcoded total=5000 (INN-304).
|
|
22
|
-
- Fixed `linear_project` and `linear_project_update` to have rate-limit pre-check (INN-303).
|
|
31
|
+
- Added smoke test coverage for image and attachment workflows.
|
|
32
|
+
- Added dedicated release notes in `docs/RELEASE_NOTES_v0.7.0.md`.
|
|
23
33
|
|
|
24
34
|
## v0.6.0 (2026-04-23)
|
|
25
35
|
|
|
@@ -358,7 +358,7 @@ async function runInteractiveConfigFlow(ctx, pi) {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
if (!client) {
|
|
361
|
-
const selectedAuthMethod = await ctx.ui.select('Select authentication method', ['API Key (recommended for full
|
|
361
|
+
const selectedAuthMethod = await ctx.ui.select('Select authentication method', ['API Key (recommended for full functionality)', 'OAuth']);
|
|
362
362
|
|
|
363
363
|
if (!selectedAuthMethod) {
|
|
364
364
|
ctx.ui.notify('Configuration cancelled', 'warning');
|
|
@@ -748,11 +748,11 @@ async function registerLinearTools(pi) {
|
|
|
748
748
|
},
|
|
749
749
|
milestone: {
|
|
750
750
|
type: 'string',
|
|
751
|
-
description: 'For update: milestone name/ID,
|
|
751
|
+
description: 'For create/update: milestone name/ID. For create, also provide project so names can be resolved. Use "none" to clear on update.',
|
|
752
752
|
},
|
|
753
753
|
projectMilestoneId: {
|
|
754
754
|
type: 'string',
|
|
755
|
-
description: 'Optional explicit milestone ID alias for update.',
|
|
755
|
+
description: 'Optional explicit milestone ID alias for create/update.',
|
|
756
756
|
},
|
|
757
757
|
subIssueOf: {
|
|
758
758
|
type: 'string',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fink-andreas/pi-linear-tools",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Pi extension with Linear SDK tools and configuration commands",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"start": "node index.js",
|
|
30
|
-
"test": "node tests/test-package-manifest.js && node tests/test-extension-registration.js && node tests/test-issue-comment-result.js && node tests/test-issue-priority.js && node tests/test-settings.js && node tests/test-issue-download.js && node tests/test-api-usage-caching.js && node tests/test-assignee-update.js && node tests/test-rate-limit-fallback-update.js && node tests/test-full-assignee-flow.js && node tests/test-branch-param.js && node tests/test-team-filter.js && node tests/test-project-crud.js && node tests/test-project-lifecycle.js && node tests/test-sync-doc.js && node tests/test-issue-activity.js",
|
|
30
|
+
"test": "node tests/test-package-manifest.js && node tests/test-extension-registration.js && node tests/test-issue-comment-result.js && node tests/test-issue-priority.js && node tests/test-settings.js && node tests/test-issue-download.js && node tests/test-api-usage-caching.js && node tests/test-issue-create-milestone.js && node tests/test-assignee-update.js && node tests/test-rate-limit-fallback-update.js && node tests/test-full-assignee-flow.js && node tests/test-branch-param.js && node tests/test-team-filter.js && node tests/test-project-crud.js && node tests/test-project-lifecycle.js && node tests/test-sync-doc.js && node tests/test-issue-activity.js",
|
|
31
31
|
"dev:sync-local-extension": "node scripts/dev-sync-local-extension.mjs",
|
|
32
32
|
"release:check": "npm test && npm pack --dry-run"
|
|
33
33
|
},
|
package/src/handlers.js
CHANGED
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
fetchIssueActivity,
|
|
26
26
|
formatIssueAsMarkdown,
|
|
27
27
|
formatIssueActivityAsMarkdown,
|
|
28
|
-
fetchIssues,
|
|
29
28
|
fetchIssuesByProject,
|
|
30
29
|
fetchProjectMilestones,
|
|
31
30
|
fetchMilestoneDetails,
|
|
@@ -390,24 +389,12 @@ async function startGitBranch(branchName, fromRef = 'HEAD', onBranchExists = 'sw
|
|
|
390
389
|
export async function executeIssueList(client, params) {
|
|
391
390
|
return withHandlerErrorHandling(async () => {
|
|
392
391
|
let projectRef = params.project;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (projectRef) {
|
|
396
|
-
// Explicit project specified - use project-scoped listing
|
|
397
|
-
projectMode = true;
|
|
398
|
-
} else {
|
|
399
|
-
// No explicit project - try cwd fallback
|
|
400
|
-
const cwdProject = path.basename(process.cwd());
|
|
401
|
-
try {
|
|
402
|
-
const resolved = await resolveProjectRef(client, cwdProject);
|
|
403
|
-
projectRef = resolved.name; // use resolved name for display
|
|
404
|
-
projectMode = true;
|
|
405
|
-
} catch {
|
|
406
|
-
// cwd doesn't match any project - fall back to workspace-level listing
|
|
407
|
-
projectMode = false;
|
|
408
|
-
}
|
|
392
|
+
if (!projectRef) {
|
|
393
|
+
projectRef = path.basename(process.cwd());
|
|
409
394
|
}
|
|
410
395
|
|
|
396
|
+
const resolved = await resolveProjectRef(client, projectRef);
|
|
397
|
+
|
|
411
398
|
let assigneeId = null;
|
|
412
399
|
if (params.assignee === 'me') {
|
|
413
400
|
const viewer = await getViewer(client);
|
|
@@ -421,50 +408,21 @@ export async function executeIssueList(client, params) {
|
|
|
421
408
|
teamId = team.id;
|
|
422
409
|
}
|
|
423
410
|
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
let projectName = null;
|
|
430
|
-
|
|
431
|
-
if (projectMode) {
|
|
432
|
-
// Project-scoped listing
|
|
433
|
-
const resolved = await resolveProjectRef(client, projectRef);
|
|
434
|
-
projectId = resolved.id;
|
|
435
|
-
projectName = resolved.name;
|
|
436
|
-
|
|
437
|
-
const result = await fetchIssuesByProject(client, resolved.id, params.states || null, {
|
|
438
|
-
assigneeId,
|
|
439
|
-
teamId,
|
|
440
|
-
limit,
|
|
441
|
-
});
|
|
442
|
-
issues = result.issues;
|
|
443
|
-
truncated = result.truncated;
|
|
444
|
-
} else {
|
|
445
|
-
// Workspace-level listing (no project filter)
|
|
446
|
-
// Default to open states if none specified
|
|
447
|
-
const workspaceStates = params.states || ['Backlog', 'Triage', 'In Progress', 'In Review'];
|
|
448
|
-
const result = await fetchIssues(client, assigneeId, workspaceStates, limit);
|
|
449
|
-
issues = result.issues;
|
|
450
|
-
truncated = result.truncated;
|
|
451
|
-
}
|
|
411
|
+
const { issues, truncated } = await fetchIssuesByProject(client, resolved.id, params.states || null, {
|
|
412
|
+
assigneeId,
|
|
413
|
+
teamId,
|
|
414
|
+
limit: params.limit || 20,
|
|
415
|
+
});
|
|
452
416
|
|
|
453
417
|
if (issues.length === 0) {
|
|
454
|
-
return toTextResult(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
projectId,
|
|
460
|
-
projectName,
|
|
461
|
-
issueCount: 0,
|
|
462
|
-
}
|
|
463
|
-
);
|
|
418
|
+
return toTextResult(`No issues found in project "${resolved.name}"`, {
|
|
419
|
+
projectId: resolved.id,
|
|
420
|
+
projectName: resolved.name,
|
|
421
|
+
issueCount: 0,
|
|
422
|
+
});
|
|
464
423
|
}
|
|
465
424
|
|
|
466
|
-
const
|
|
467
|
-
const lines = [`## Issues in ${scopeLabel} (${issues.length}${truncated ? '+' : ''})\n`];
|
|
425
|
+
const lines = [`## Issues in project "${resolved.name}" (${issues.length}${truncated ? '+' : ''})\n`];
|
|
468
426
|
|
|
469
427
|
for (const issue of issues) {
|
|
470
428
|
const stateLabel = issue.state?.name || 'Unknown';
|
|
@@ -484,8 +442,8 @@ export async function executeIssueList(client, params) {
|
|
|
484
442
|
}
|
|
485
443
|
|
|
486
444
|
return toTextResult(lines.join('\n'), {
|
|
487
|
-
projectId,
|
|
488
|
-
projectName,
|
|
445
|
+
projectId: resolved.id,
|
|
446
|
+
projectName: resolved.name,
|
|
489
447
|
issueCount: issues.length,
|
|
490
448
|
truncated,
|
|
491
449
|
});
|
|
@@ -732,6 +690,22 @@ export async function executeIssueCreate(client, params, options = {}) {
|
|
|
732
690
|
createInput.projectId = resolvedProject.id;
|
|
733
691
|
}
|
|
734
692
|
|
|
693
|
+
if (params.projectMilestoneId !== undefined && params.projectMilestoneId !== null) {
|
|
694
|
+
createInput.projectMilestoneId = params.projectMilestoneId;
|
|
695
|
+
} else if (params.milestone !== undefined && params.milestone !== null) {
|
|
696
|
+
const milestoneRef = String(params.milestone || '').trim();
|
|
697
|
+
const clearMilestoneValues = new Set(['', 'none', 'null', 'unassigned', 'clear']);
|
|
698
|
+
|
|
699
|
+
if (!clearMilestoneValues.has(milestoneRef.toLowerCase())) {
|
|
700
|
+
if (!resolvedProject?.id) {
|
|
701
|
+
throw new Error('Missing required field: project. Provide project when assigning milestone by name during issue create, or use projectMilestoneId.');
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const milestone = await resolveMilestoneRef(client, milestoneRef, resolvedProject.id);
|
|
705
|
+
createInput.projectMilestoneId = milestone.id;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
735
709
|
const issue = await createIssue(client, createInput);
|
|
736
710
|
|
|
737
711
|
const identifier = issue.identifier || issue.id || 'unknown';
|
|
@@ -742,8 +716,11 @@ export async function executeIssueCreate(client, params, options = {}) {
|
|
|
742
716
|
const stateLabel = issue.state?.name || 'Unknown';
|
|
743
717
|
const assigneeLabel = issue.assignee?.displayName || 'Unassigned';
|
|
744
718
|
|
|
719
|
+
const milestoneLabel = issue.projectMilestone?.name || null;
|
|
720
|
+
|
|
745
721
|
const metaParts = [`Team: ${team.name}`, `Project: ${projectLabel}`, `State: ${stateLabel}`, `Assignee: ${assigneeLabel}`];
|
|
746
722
|
if (priorityLabel) metaParts.push(`Priority: ${priorityLabel}`);
|
|
723
|
+
if (milestoneLabel) metaParts.push(`Milestone: ${milestoneLabel}`);
|
|
747
724
|
|
|
748
725
|
return toTextResult(
|
|
749
726
|
`Created issue **${identifier}**: ${issue.title}\n${metaParts.join(' | ')}`,
|
|
@@ -755,6 +732,7 @@ export async function executeIssueCreate(client, params, options = {}) {
|
|
|
755
732
|
project: issue.project,
|
|
756
733
|
state: issue.state,
|
|
757
734
|
assignee: issue.assignee,
|
|
735
|
+
projectMilestone: issue.projectMilestone,
|
|
758
736
|
url: issue.url,
|
|
759
737
|
}
|
|
760
738
|
);
|
package/src/linear.js
CHANGED
|
@@ -3150,6 +3150,7 @@ export async function setIssueState(client, issueId, stateId) {
|
|
|
3150
3150
|
* @param {string} [input.projectId] - Project ID
|
|
3151
3151
|
* @param {number|string} [input.priority] - Issue priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low; or none/urgent/high/medium/low
|
|
3152
3152
|
* @param {string} [input.assigneeId] - Assignee ID
|
|
3153
|
+
* @param {string} [input.projectMilestoneId] - Project milestone ID
|
|
3153
3154
|
* @param {string} [input.parentId] - Parent issue ID for sub-issues
|
|
3154
3155
|
* @returns {Promise<Object>} Created issue
|
|
3155
3156
|
*/
|
|
@@ -3202,6 +3203,10 @@ export async function createIssue(client, input) {
|
|
|
3202
3203
|
createInput.stateId = input.stateId;
|
|
3203
3204
|
}
|
|
3204
3205
|
|
|
3206
|
+
if (input.projectMilestoneId !== undefined) {
|
|
3207
|
+
createInput.projectMilestoneId = input.projectMilestoneId;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3205
3210
|
if (getRawRequest(client)) {
|
|
3206
3211
|
const payload = await executeGraphQL(client, ISSUE_CREATE_MUTATION, { input: createInput });
|
|
3207
3212
|
if (!payload?.issueCreate?.success) {
|
|
@@ -3245,6 +3250,9 @@ export async function createIssue(client, input) {
|
|
|
3245
3250
|
state: null,
|
|
3246
3251
|
team: null,
|
|
3247
3252
|
project: null,
|
|
3253
|
+
projectMilestone: createInput.projectMilestoneId
|
|
3254
|
+
? { id: createInput.projectMilestoneId, name: 'Unknown' }
|
|
3255
|
+
: null,
|
|
3248
3256
|
assignee: null,
|
|
3249
3257
|
};
|
|
3250
3258
|
}
|
|
@@ -3259,6 +3267,9 @@ export async function createIssue(client, input) {
|
|
|
3259
3267
|
state: null,
|
|
3260
3268
|
team: null,
|
|
3261
3269
|
project: null,
|
|
3270
|
+
projectMilestone: createInput.projectMilestoneId
|
|
3271
|
+
? { id: createInput.projectMilestoneId, name: 'Unknown' }
|
|
3272
|
+
: null,
|
|
3262
3273
|
assignee: null,
|
|
3263
3274
|
};
|
|
3264
3275
|
}, 'createIssue');
|