ticketlens 0.38.49 → 0.38.51
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
package/skills/jtb/SKILL.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- jtb-skill-version: 0.43.
|
|
1
|
+
<!-- jtb-skill-version: 0.43.1 -->
|
|
2
2
|
---
|
|
3
3
|
name: jtb
|
|
4
4
|
description: Fetch a Jira ticket's full context (description, comments, linked issues, code references) and assemble a structured TicketBrief for implementation planning. Use when user types /jtb, mentions a Jira ticket key, or wants to plan work from a Jira ticket.
|
|
@@ -424,6 +424,8 @@ ticketlens assign PROD-1234 --to=me # assign to yoursel
|
|
|
424
424
|
ticketlens duplicates PROD-1234 # find likely duplicates — read-only
|
|
425
425
|
ticketlens link PROD-1234 PROD-5678 # list valid link types — read-only
|
|
426
426
|
ticketlens link PROD-1234 PROD-5678 --type="Duplicate" --confirm # execute the link
|
|
427
|
+
# Jira's list shows the real resulting sentence per type (e.g. "Blocks — PROD-1234 blocks PROD-5678")
|
|
428
|
+
# so direction is never a guess — GitHub/Linear show just the type name (single relation, no ambiguity)
|
|
427
429
|
ticketlens update PROD-1234 --title="Fix login on mobile" # update title/description/labels/priority
|
|
428
430
|
ticketlens update PROD-1234 --add-labels=urgent --remove-labels=stale
|
|
429
431
|
ticketlens create --project=PROD --type="Task" --summary="Fix login on mobile" # create a new ticket
|
|
@@ -109,19 +109,26 @@ export function createJiraAdapter(conn, { fetcher = globalThis.fetch } = {}) {
|
|
|
109
109
|
/**
|
|
110
110
|
* Always fetched fresh — link type names are per-instance customizable
|
|
111
111
|
* in Jira, same "never trust a stale list" principle as getTransitions.
|
|
112
|
-
* Returns
|
|
113
|
-
*
|
|
112
|
+
* Returns name+inward+outward (backlog #31), not just the bare name:
|
|
113
|
+
* a bare name forces the caller to guess which end gets which phrase,
|
|
114
|
+
* which is exactly what produced 5 recurring "direction inverted"
|
|
115
|
+
* reports despite linkTo()'s own outward/inward mapping being correct
|
|
116
|
+
* and tested. GitHub/Linear keep returning plain strings — single
|
|
117
|
+
* relation type, no phrase ambiguity there.
|
|
114
118
|
*/
|
|
115
119
|
async getLinkTypes(opts = {}) {
|
|
116
120
|
const types = await getIssueLinkTypes({ ...base, ...opts });
|
|
117
|
-
return types.map(t => t.name);
|
|
121
|
+
return types.map(t => ({ name: t.name, inward: t.inward, outward: t.outward }));
|
|
118
122
|
},
|
|
119
123
|
|
|
120
124
|
/**
|
|
121
125
|
* Always re-fetches link types fresh and resolves `typeName` against
|
|
122
126
|
* them before executing — a caller can never blind-POST a stale or
|
|
123
127
|
* guessed type name, same principle as transition().
|
|
124
|
-
* sourceKey
|
|
128
|
+
* sourceKey ends up performing the type's outward verb onto targetKey
|
|
129
|
+
* (e.g. "sourceKey duplicates targetKey") — see postIssueLink's own
|
|
130
|
+
* doc comment in jira-client.mjs for why its POST body's field names
|
|
131
|
+
* don't map the way you'd guess from Jira's GET-response convention.
|
|
125
132
|
*/
|
|
126
133
|
async linkTo(sourceKey, targetKey, typeName, opts = {}) {
|
|
127
134
|
const types = await getIssueLinkTypes({ ...base, ...opts });
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Centralised here to avoid triplicating the regex and warning logic.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export const DEFAULT_API_BASE = '
|
|
6
|
+
export const DEFAULT_API_BASE = 'http://api.ticketlens.test';
|
|
7
7
|
export const DEFAULT_SITE_BASE = 'https://ticketlens.app';
|
|
8
8
|
|
|
9
9
|
// Matches localhost, 127.0.0.1, and any hostname ending in .test or .local,
|
|
@@ -544,10 +544,20 @@ export async function getIssueLinkTypes(opts = {}) {
|
|
|
544
544
|
}
|
|
545
545
|
|
|
546
546
|
/**
|
|
547
|
-
* Creates a link between two issues
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
547
|
+
* Creates a link between two issues so `sourceKey` performs the type's
|
|
548
|
+
* outward verb onto `targetKey` (e.g. "sourceKey duplicates targetKey").
|
|
549
|
+
*
|
|
550
|
+
* The POST body's `outwardIssue`/`inwardIssue` keys are the REVERSE of what
|
|
551
|
+
* you'd expect from the GET /issue response's self-referential convention
|
|
552
|
+
* (where an issue's OWN `issuelinks` entry uses `outwardIssue` to mean "I am
|
|
553
|
+
* the outward/performing side") — sending `outwardIssue: sourceKey` there
|
|
554
|
+
* produces the OPPOSITE real relationship. Live-verified against two real
|
|
555
|
+
* Jira Cloud instances, cross-checked from both linked issues' own GET
|
|
556
|
+
* responses, reproduced twice independently (Blocks and Duplicate types,
|
|
557
|
+
* both argument orders) — not a guess, not doc-derived. This is backlog
|
|
558
|
+
* #31's actual root cause: every prior "direction inverted" report was
|
|
559
|
+
* real, and no unit test caught it because mocked HTTP calls can only
|
|
560
|
+
* assert the request shape, never Jira's real interpretation of it.
|
|
551
561
|
*/
|
|
552
562
|
export async function postIssueLink(sourceKey, targetKey, typeName, opts = {}) {
|
|
553
563
|
const { env = process.env, fetcher = globalThis.fetch, lookup = defaultLookupFor(fetcher), apiVersion = 2, timeoutMs = 10_000, allowPrivateIp = false } = opts;
|
|
@@ -558,7 +568,7 @@ export async function postIssueLink(sourceKey, targetKey, typeName, opts = {}) {
|
|
|
558
568
|
const fetchOpts = {
|
|
559
569
|
method: 'POST',
|
|
560
570
|
headers: { ...buildAuthHeader(env), 'Content-Type': 'application/json' },
|
|
561
|
-
body: JSON.stringify({ type: { name: typeName }, outwardIssue: { key:
|
|
571
|
+
body: JSON.stringify({ type: { name: typeName }, outwardIssue: { key: targetKey }, inwardIssue: { key: sourceKey } }),
|
|
562
572
|
};
|
|
563
573
|
if (timeoutMs) fetchOpts.signal = AbortSignal.timeout(timeoutMs);
|
|
564
574
|
|
|
@@ -577,7 +577,7 @@ export async function runTicketDuplicates(cmdArgs, {
|
|
|
577
577
|
*
|
|
578
578
|
* @param {string[]} cmdArgs - [sourceKey, targetKey]
|
|
579
579
|
* @param {boolean} [cliHints] - see runTicketTransitionList's cliHints doc
|
|
580
|
-
* @returns {Promise<{ ok: boolean, types?: string[] }>}
|
|
580
|
+
* @returns {Promise<{ ok: boolean, types?: (string|{name: string, inward: string, outward: string})[] }>}
|
|
581
581
|
*/
|
|
582
582
|
export async function runTicketLinkList(cmdArgs, {
|
|
583
583
|
configDir = DEFAULT_CONFIG_DIR,
|
|
@@ -607,7 +607,14 @@ export async function runTicketLinkList(cmdArgs, {
|
|
|
607
607
|
return { ok: true, types: [] };
|
|
608
608
|
}
|
|
609
609
|
stream.write(` Available link types for ${s.brand(s.bold(sourceKey))} → ${s.brand(s.bold(targetKey))} (${adapter.type}):\n\n`);
|
|
610
|
-
for (const t of types)
|
|
610
|
+
for (const t of types) {
|
|
611
|
+
// Backlog #31: a bare type name (e.g. "Blocks") hides which end gets
|
|
612
|
+
// which phrase — show the real resulting sentence so the caller
|
|
613
|
+
// doesn't have to guess direction. GitHub/Linear still hand back
|
|
614
|
+
// plain strings (no inward/outward phrase pair to show).
|
|
615
|
+
const line = typeof t === 'string' ? t : `${t.name} — ${sourceKey} ${t.outward ?? t.name} ${targetKey}`;
|
|
616
|
+
stream.write(` ${s.brand('●')} ${line}\n`);
|
|
617
|
+
}
|
|
611
618
|
stream.write('\n');
|
|
612
619
|
if (adapter.type === 'github') {
|
|
613
620
|
stream.write(` Note: GitHub has no generic link relationship — linking will CLOSE ${sourceKey} as a duplicate of ${targetKey}.\n`);
|