ticketlens 0.38.53 → 0.38.54
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 +1 -1
- package/skills/jtb/scripts/fetch-ticket.mjs +13 -7
- package/skills/jtb/scripts/lib/api-utils.mjs +1 -1
- package/skills/jtb/scripts/lib/cli.mjs +14 -0
- package/skills/jtb/scripts/lib/note-command.mjs +28 -13
- package/skills/jtb/scripts/lib/recall-command.mjs +10 -4
- package/skills/jtb/scripts/lib/run-history.mjs +3 -1
- package/skills/jtb/scripts/lib/ticket-command.mjs +9 -3
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ import { promptSelect } from './lib/select-prompt.mjs';
|
|
|
21
21
|
import { printFetchHelp } from './lib/help.mjs';
|
|
22
22
|
import { readTextAttachments } from './lib/handoff-assembler.mjs';
|
|
23
23
|
import { handleUnknownFlags } from './lib/arg-validator.mjs';
|
|
24
|
-
import { TICKET_KEY_PATTERN } from './lib/cli.mjs';
|
|
24
|
+
import { TICKET_KEY_PATTERN, normalizeTicketKey } from './lib/cli.mjs';
|
|
25
25
|
import { downloadAttachments } from './lib/attachment-downloader.mjs';
|
|
26
26
|
import { readBriefCache, writeBriefCache, readSummaryCache, writeSummaryCache, briefCacheAge, BRIEF_TTL_MS } from './lib/brief-cache.mjs';
|
|
27
27
|
import { recordTokensSaved } from './lib/activity-counter.mjs';
|
|
@@ -552,17 +552,19 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
552
552
|
|
|
553
553
|
if (args[0] === 'pr') {
|
|
554
554
|
const { assemblePr } = await import('./lib/pr-assembler.mjs');
|
|
555
|
-
|
|
555
|
+
let ticketKeyArg = args[1];
|
|
556
556
|
if (!ticketKeyArg) {
|
|
557
557
|
printErrFn('Error: "pr" requires a ticket key. Usage: ticketlens pr PROJ-123\n');
|
|
558
558
|
process.exitCode = 1;
|
|
559
559
|
return;
|
|
560
560
|
}
|
|
561
|
-
|
|
561
|
+
const normalizedPr = normalizeTicketKey(ticketKeyArg);
|
|
562
|
+
if (!TICKET_KEY_PATTERN.test(normalizedPr)) {
|
|
562
563
|
printErrFn(`Error: "${ticketKeyArg}" is not a valid ticket key. Expected format: PROJ-123\n`);
|
|
563
564
|
process.exitCode = 1;
|
|
564
565
|
return;
|
|
565
566
|
}
|
|
567
|
+
ticketKeyArg = normalizeTicketKey(ticketKeyArg, { stream: errStream });
|
|
566
568
|
const resolvedConfigDir = configDir ?? (await import('./lib/config.mjs')).DEFAULT_CONFIG_DIR;
|
|
567
569
|
|
|
568
570
|
// Resolve connection (same pattern as compliance dispatch)
|
|
@@ -627,17 +629,19 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
627
629
|
}
|
|
628
630
|
|
|
629
631
|
if (args[0] === 'compliance') {
|
|
630
|
-
|
|
632
|
+
let ticketKeyArg = args[1];
|
|
631
633
|
if (!ticketKeyArg) {
|
|
632
634
|
printErrFn('Error: "compliance" requires a ticket key. Usage: ticketlens compliance PROJ-123\n');
|
|
633
635
|
process.exitCode = 1;
|
|
634
636
|
return;
|
|
635
637
|
}
|
|
636
|
-
|
|
638
|
+
const normalizedCompliance = normalizeTicketKey(ticketKeyArg);
|
|
639
|
+
if (!TICKET_KEY_PATTERN.test(normalizedCompliance)) {
|
|
637
640
|
printErrFn(`Error: "${ticketKeyArg}" is not a valid ticket key. Expected format: PROJ-123\n`);
|
|
638
641
|
process.exitCode = 1;
|
|
639
642
|
return;
|
|
640
643
|
}
|
|
644
|
+
ticketKeyArg = normalizeTicketKey(ticketKeyArg, { stream: errStream });
|
|
641
645
|
|
|
642
646
|
const resolvedConfigDir = configDir ?? (await import('./lib/config.mjs')).DEFAULT_CONFIG_DIR;
|
|
643
647
|
|
|
@@ -1005,17 +1009,19 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
1005
1009
|
return;
|
|
1006
1010
|
}
|
|
1007
1011
|
|
|
1008
|
-
|
|
1012
|
+
let ticketKey = args.find(a => !a.startsWith('--'));
|
|
1009
1013
|
if (!ticketKey) {
|
|
1010
1014
|
printFetchHelp({ stream: errStream });
|
|
1011
1015
|
process.exitCode = 1;
|
|
1012
1016
|
return;
|
|
1013
1017
|
}
|
|
1014
|
-
|
|
1018
|
+
const normalizedFetch = normalizeTicketKey(ticketKey);
|
|
1019
|
+
if (!TICKET_KEY_PATTERN.test(normalizedFetch)) {
|
|
1015
1020
|
printErrFn(`Error: "${ticketKey}" is not a valid ticket key. Expected format: PROJ-123\n`);
|
|
1016
1021
|
process.exitCode = 1;
|
|
1017
1022
|
return;
|
|
1018
1023
|
}
|
|
1024
|
+
ticketKey = normalizeTicketKey(ticketKey, { stream: errStream });
|
|
1019
1025
|
|
|
1020
1026
|
// Normalize --project= alias once at entry so all recursive calls only see --profile=
|
|
1021
1027
|
const projectArg = args.find(a => a.startsWith('--project='));
|
|
@@ -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 = 'https://api.ticketlens.app';
|
|
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,
|
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
|
|
6
6
|
export const TICKET_KEY_PATTERN = /^[A-Z][A-Z0-9]+-\d+$/;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Jira/Linear project and team keys are always created uppercase, so any
|
|
10
|
+
* lowercase input is a typo, never a distinct real key — safe to correct
|
|
11
|
+
* silently before TICKET_KEY_PATTERN ever sees it.
|
|
12
|
+
*/
|
|
13
|
+
export function normalizeTicketKey(ticketKey, { stream } = {}) {
|
|
14
|
+
if (!ticketKey) return ticketKey;
|
|
15
|
+
const upper = ticketKey.toUpperCase();
|
|
16
|
+
if (upper !== ticketKey && stream) {
|
|
17
|
+
stream.write(` Note: normalized "${ticketKey}" to "${upper}"\n`);
|
|
18
|
+
}
|
|
19
|
+
return upper;
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
export function parseCommand(args) {
|
|
9
23
|
const first = args[0];
|
|
10
24
|
|
|
@@ -19,7 +19,7 @@ import { enqueueNote, isRetryableFailure, maybeAutoFlush } from './recall-queue.
|
|
|
19
19
|
import { incrementDraftKept, incrementDraftDeleted } from './activity-counter.mjs';
|
|
20
20
|
import { extractText } from './attachment-text.mjs';
|
|
21
21
|
import { readAttachments, MAX_ATTACHMENTS } from './attachment-uploader.mjs';
|
|
22
|
-
import { TICKET_KEY_PATTERN } from './cli.mjs';
|
|
22
|
+
import { TICKET_KEY_PATTERN, normalizeTicketKey } from './cli.mjs';
|
|
23
23
|
import { createStyler } from './ansi.mjs';
|
|
24
24
|
import { confirmDestructive } from './confirm.mjs';
|
|
25
25
|
|
|
@@ -135,10 +135,15 @@ export async function runNoteAdd(cmdArgs, {
|
|
|
135
135
|
// to forge a fake "## heading" line when the note is later injected into a brief.
|
|
136
136
|
const title = rawTitle.replace(/[\r\n]+/g, ' ');
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
if (ticketKey
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
let ticketKey = parseFlag(cmdArgs, 'ticket');
|
|
139
|
+
if (ticketKey) {
|
|
140
|
+
const normalized = normalizeTicketKey(ticketKey);
|
|
141
|
+
if (!TICKET_KEY_PATTERN.test(normalized)) {
|
|
142
|
+
stream.write(` Invalid --ticket value "${ticketKey}" — expected a ticket key like PROJ-123.\n`);
|
|
143
|
+
return { written: false };
|
|
144
|
+
}
|
|
145
|
+
if (normalized !== ticketKey) normalizeTicketKey(ticketKey, { stream });
|
|
146
|
+
ticketKey = normalized;
|
|
142
147
|
}
|
|
143
148
|
const tagsArg = parseFlag(cmdArgs, 'tags');
|
|
144
149
|
const tags = tagsArg ? tagsArg.split(',').map(t => t.trim()).filter(Boolean) : [];
|
|
@@ -260,10 +265,15 @@ export async function runNotePatch(cmdArgs, {
|
|
|
260
265
|
return { patched: false };
|
|
261
266
|
}
|
|
262
267
|
|
|
263
|
-
|
|
264
|
-
if (ticketKey
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
let ticketKey = parseFlag(cmdArgs, 'ticket');
|
|
269
|
+
if (ticketKey) {
|
|
270
|
+
const normalized = normalizeTicketKey(ticketKey);
|
|
271
|
+
if (!TICKET_KEY_PATTERN.test(normalized)) {
|
|
272
|
+
stream.write(` Invalid --ticket value "${ticketKey}" — expected a ticket key like PROJ-123.\n`);
|
|
273
|
+
return { patched: false };
|
|
274
|
+
}
|
|
275
|
+
if (normalized !== ticketKey) normalizeTicketKey(ticketKey, { stream });
|
|
276
|
+
ticketKey = normalized;
|
|
267
277
|
}
|
|
268
278
|
|
|
269
279
|
const body = await readStdin();
|
|
@@ -339,10 +349,15 @@ export async function runNoteDelete(cmdArgs, {
|
|
|
339
349
|
return { deleted: false };
|
|
340
350
|
}
|
|
341
351
|
|
|
342
|
-
|
|
343
|
-
if (ticketKey
|
|
344
|
-
|
|
345
|
-
|
|
352
|
+
let ticketKey = parseFlag(cmdArgs, 'ticket');
|
|
353
|
+
if (ticketKey) {
|
|
354
|
+
const normalized = normalizeTicketKey(ticketKey);
|
|
355
|
+
if (!TICKET_KEY_PATTERN.test(normalized)) {
|
|
356
|
+
stream.write(` Invalid --ticket value "${ticketKey}" — expected a ticket key like PROJ-123.\n`);
|
|
357
|
+
return { deleted: false };
|
|
358
|
+
}
|
|
359
|
+
if (normalized !== ticketKey) normalizeTicketKey(ticketKey, { stream });
|
|
360
|
+
ticketKey = normalized;
|
|
346
361
|
}
|
|
347
362
|
|
|
348
363
|
const forceYes = cmdArgs.includes('--yes') || cmdArgs.includes('-y');
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { DEFAULT_CONFIG_DIR } from './config.mjs';
|
|
14
|
-
import { TICKET_KEY_PATTERN } from './cli.mjs';
|
|
14
|
+
import { TICKET_KEY_PATTERN, normalizeTicketKey } from './cli.mjs';
|
|
15
15
|
import { isLicensed, showUpgradePrompt } from './license.mjs';
|
|
16
16
|
import { listNotes } from './recall-vault.mjs';
|
|
17
17
|
import { readCliToken } from './cli-auth.mjs';
|
|
@@ -48,17 +48,23 @@ export async function runRecall(cmdArgs, {
|
|
|
48
48
|
return { ok: false };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// A lowercase-typed ticket key (e.g. "proj-123") must not be mistaken for a
|
|
52
|
+
// free-text search phrase — normalize before classifying, but never touch
|
|
53
|
+
// a genuine search query's own casing.
|
|
54
|
+
const normalizedArg = normalizeTicketKey(arg);
|
|
55
|
+
const isTicketKey = TICKET_KEY_PATTERN.test(normalizedArg);
|
|
56
|
+
if (isTicketKey && normalizedArg !== arg) normalizeTicketKey(arg, { stream: errorStream });
|
|
57
|
+
const key = isTicketKey ? normalizedArg : arg;
|
|
52
58
|
|
|
53
59
|
const cliToken = readCliTokenFn(configDir);
|
|
54
60
|
if (cliToken) {
|
|
55
|
-
const profile = resolveProfileFn(isTicketKey ?
|
|
61
|
+
const profile = resolveProfileFn(isTicketKey ? key : null, { configDir, cwd: process.cwd() });
|
|
56
62
|
const recallTeamId = profile ? loadProfileRecallTeamIdFn(profile.name, configDir) : null;
|
|
57
63
|
await pullNotesFn({ cliToken, configDir, ttlMs: 0, groupId: recallTeamId ?? undefined });
|
|
58
64
|
await maybeAutoFlushFn({ cliToken, configDir });
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
const filter = isTicketKey ? { ticketKey:
|
|
67
|
+
const filter = isTicketKey ? { ticketKey: key } : { query: key };
|
|
62
68
|
const results = listNotesFn(filter, { configDir });
|
|
63
69
|
|
|
64
70
|
const styled = !cmdArgs.includes('--plain') && !!stream.isTTY;
|
|
@@ -2,6 +2,7 @@ import { queryTicketHistory, DEFAULT_CONFIG_DIR } from './triage-history.mjs';
|
|
|
2
2
|
import { isLicensed as defaultIsLicensed, showUpgradePrompt } from './license.mjs';
|
|
3
3
|
import { createStyler } from './ansi.mjs';
|
|
4
4
|
import { printHistoryHelp } from './help.mjs';
|
|
5
|
+
import { normalizeTicketKey } from './cli.mjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Prints a ticket's local triage-history timeline. Extracted from
|
|
@@ -26,12 +27,13 @@ export async function runHistory(args = [], opts = {}) {
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
let ticketKey = args[0];
|
|
30
31
|
if (!ticketKey || ticketKey.startsWith('-')) {
|
|
31
32
|
warn('Usage: ticketlens history TICKET-KEY\n');
|
|
32
33
|
process.exitCode = 1;
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
36
|
+
ticketKey = normalizeTicketKey(ticketKey, { stream: { write: warn } });
|
|
35
37
|
|
|
36
38
|
const entries = queryFn(ticketKey, { configDir });
|
|
37
39
|
if (entries.length === 0) {
|
|
@@ -18,7 +18,7 @@ import { checkCooldown, recordAction } from './ticket-action-cooldown.mjs';
|
|
|
18
18
|
import { logAction } from './ticket-action-log.mjs';
|
|
19
19
|
import { readMetadataCache, writeMetadataCache } from './ticket-metadata-cache.mjs';
|
|
20
20
|
import { detectProjectOrTypeError, enrichCreateFailure } from './ticket-create-enrichment.mjs';
|
|
21
|
-
import { TICKET_KEY_PATTERN } from './cli.mjs';
|
|
21
|
+
import { TICKET_KEY_PATTERN, normalizeTicketKey } from './cli.mjs';
|
|
22
22
|
import { scoreCandidates } from './duplicate-scorer.mjs';
|
|
23
23
|
import { MAX_ATTACHMENTS } from './attachment-uploader.mjs';
|
|
24
24
|
import { createStyler } from './ansi.mjs';
|
|
@@ -209,11 +209,17 @@ function requireLicense(isLicensedFn, configDir, commandName, stream) {
|
|
|
209
209
|
|
|
210
210
|
function requireTicketKey(cmdArgs, usage, stream) {
|
|
211
211
|
const ticketKey = cmdArgs[0];
|
|
212
|
-
if (!ticketKey || ticketKey.startsWith('--')
|
|
212
|
+
if (!ticketKey || ticketKey.startsWith('--')) {
|
|
213
213
|
stream.write(usage);
|
|
214
214
|
return null;
|
|
215
215
|
}
|
|
216
|
-
|
|
216
|
+
const normalized = normalizeTicketKey(ticketKey);
|
|
217
|
+
if (!TICKET_KEY_PATTERN.test(normalized)) {
|
|
218
|
+
stream.write(usage);
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (normalized !== ticketKey) normalizeTicketKey(ticketKey, { stream });
|
|
222
|
+
return normalized;
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
/**
|