ticketlens 0.38.8 → 0.38.9

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/README.md CHANGED
@@ -20,6 +20,47 @@
20
20
 
21
21
  ---
22
22
 
23
+ ## Contents
24
+
25
+ - [What is TicketLens?](#what-is-ticketlens)
26
+ - [Why TicketLens?](#why-ticketlens)
27
+ - [Quick Start](#quick-start)
28
+ - [Demos](#demos)
29
+ - [Commands](#commands)
30
+ - [Setup](#setup)
31
+ - [Fetch a ticket](#fetch-a-ticket)
32
+ - [Brief Templates](#brief-templates)
33
+ - [Triage](#triage)
34
+ - [Collisions](#collisions)
35
+ - [Review](#review)
36
+ - [Compliance](#compliance)
37
+ - [Compliance Ledger](#compliance-ledger)
38
+ - [Git Hook](#git-hook)
39
+ - [PR Description](#pr-description)
40
+ - [Standup](#standup)
41
+ - [Cache](#cache)
42
+ - [Schedule](#schedule)
43
+ - [History](#history)
44
+ - [Recall](#recall)
45
+ - [Comment, Transition, Assign, Duplicates, Link, Update & Create](#comment-transition-assign-duplicates-link-update--create)
46
+ - [Response-Time Stats](#response-time-stats)
47
+ - [Custom Attention Rules](#custom-attention-rules)
48
+ - [Login](#login)
49
+ - [License](#license)
50
+ - [Update Skill](#update-skill)
51
+ - [/jtb — Jira TicketBrief for Claude Code](#jtb--jira-ticketbrief-for-claude-code)
52
+ - [All Examples](#all-examples)
53
+ - [Pro & Teams Features](#pro--teams-features)
54
+ - [Pro — $9/mo](#pro--9mo)
55
+ - [Team — $19/seat/mo](#team--19seatmo)
56
+ - [Multi-Profile Setup](#multi-profile-setup)
57
+ - [Running Tests](#running-tests)
58
+ - [Roadmap](#roadmap)
59
+ - [Contributing](#contributing)
60
+ - [License](#license-1)
61
+
62
+ ---
63
+
23
64
  ## What is TicketLens?
24
65
 
25
66
  TicketLens is a local-first Jira CLI that preprocesses ticket context on your machine and hands your AI tools a clean, compressed brief — instead of dumping raw Jira API JSON into your session. It supports Jira Cloud, Server, and Data Center, works with any AI tool that accepts text, and runs independently of any AI session.
@@ -271,24 +312,24 @@ Displays the append-only local ledger of all compliance checks run on this machi
271
312
  ### Git Hook
272
313
 
273
314
  ```bash
274
- ticketlens install-hooks # Install pre-push compliance gate [Pro]
315
+ ticketlens install-hooks # Install pre-push compliance gate
275
316
  ticketlens install-hooks --uninstall # Remove installed hooks
276
317
  ```
277
318
 
278
- Installs a `pre-push` git hook that runs `ticketlens compliance` on every push. Blocks the push if compliance coverage falls below the configured threshold. Requires a Pro license.
319
+ Installs a `pre-push` git hook that runs `ticketlens compliance` on every push. Blocks the push if compliance coverage falls below the configured threshold. Free — the hook itself needs no license; the `compliance` check it runs follows its own free (3/month) or Pro (unlimited) limit.
279
320
 
280
321
  ---
281
322
 
282
323
  ### PR Description
283
324
 
284
325
  ```bash
285
- ticketlens pr <TICKET-KEY> # Generate PR description from ticket [Pro]
326
+ ticketlens pr <TICKET-KEY> # Generate PR description from ticket
286
327
  ticketlens pr <TICKET-KEY> --profile=acme # Specify a profile
287
328
  ticketlens pr <TICKET-KEY> --plain # Plain markdown output
288
329
  ticketlens pr <TICKET-KEY> | pbcopy # Copy to clipboard
289
330
  ```
290
331
 
291
- Generates a PR description template pre-filled with the ticket summary, acceptance criteria, and compliance coverage. Requires a Pro license.
332
+ Generates a PR description template pre-filled with the ticket summary, acceptance criteria, and compliance coverage. Free — the compliance coverage section follows the `compliance` command's own free (3/month) or Pro (unlimited) limit.
292
333
 
293
334
  ---
294
335
 
@@ -425,6 +466,7 @@ Every note is scanned before saving — anything shaped like a real secret (API
425
466
 
426
467
  ```bash
427
468
  ticketlens comment PROJ-123 --body="Looks good, merging." # Post a comment to the tracker
469
+ ticketlens comment PROJ-123 --body="See screenshot" --attach=./bug.png # Attach local files
428
470
  ticketlens transition PROJ-123 # List valid transitions (read-only)
429
471
  ticketlens transition PROJ-123 --target="Done" --confirm # Execute the transition
430
472
  ticketlens assign PROJ-123 --to=me # Assign the ticket to yourself
@@ -435,6 +477,7 @@ ticketlens update PROJ-123 --title="Fix login on mobile" # Update title/de
435
477
  ticketlens update PROJ-123 --add-labels=urgent,backend --remove-labels=stale
436
478
  ticketlens create --project=PROJ --type="Task" --summary="Fix login on mobile" # Create a new ticket
437
479
  ticketlens create --project=ENG --summary="New Linear issue" --profile=linear-team
480
+ ticketlens create --project=PROJ --type="Bug" --summary="Broken layout" --attach=./screenshot.png
438
481
  ```
439
482
 
440
483
  Write directly to the ticket in its real tracker — Jira, GitHub, or Linear — from your terminal or an AI session via `ticket_comment`/`ticket_transition`/`ticket_assign`/`ticket_duplicates`/`ticket_link`/`ticket_update`/`ticket_create` MCP tools. Requires a Pro license.
@@ -443,7 +486,7 @@ Write directly to the ticket in its real tracker — Jira, GitHub, or Linear —
443
486
 
444
487
  `ticketlens assign` is self-assign only for now — `--to` must be `me`. Assigning to someone else needs a per-tracker user-lookup step this doesn't do yet, so it's deliberately out of scope until that's built.
445
488
 
446
- `ticketlens duplicates` is read-only — it never links or changes anything, just lists likely matches in the same project. No tracker (Jira/GitHub/Linear) scores similarity server-side, so ranking happens locally from title/description word overlap; treat a match as a nudge to check manually, not a verdict. `--threshold=N` (0–1, default 0.35) controls how loose a match counts.
489
+ `ticketlens duplicates` is read-only — it never links or changes anything, just lists likely matches in the same project. No tracker (Jira/GitHub/Linear) scores similarity server-side, so ranking happens locally from title/description word overlap; treat a match as a nudge to check manually, not a verdict. That local scoring can also miss a real duplicate — an empty result means none were found by this heuristic, not a confirmed absence. `--threshold=N` (0–1, default 0.35) controls how loose a match counts.
447
490
 
448
491
  `ticketlens link SOURCE-KEY TARGET-KEY` links two tickets — direction matters: SOURCE "types" TARGET (e.g. `link A B --type=Duplicate` means A duplicates B, not the other way around). With just the two keys it lists the tracker's current valid link types without changing anything — always fetched live for Jira, since link type names are per-instance configurable there. GitHub is different from Jira/Linear: it has no generic link relationship, so linking on a GitHub-tracked ticket *closes SOURCE as a duplicate of TARGET* — a state change, not just a relationship add — and prints an explicit warning immediately before that happens, on top of the same `--confirm` gate.
449
492
 
@@ -451,6 +494,8 @@ Write directly to the ticket in its real tracker — Jira, GitHub, or Linear —
451
494
 
452
495
  `ticketlens create` creates a new ticket with a fixed minimal field set — no arbitrary custom fields. Unlike every other write command, there's no existing ticket to target, so `--profile` (or your default profile) picks the tracker instead of a ticket key. `--project` is the Jira project key or Linear team key — required for both, ignored on GitHub since its target repo is already fixed by the profile. `--type` is Jira's issue type (e.g. `"Task"`, `"Bug"`) — required for Jira, ignored elsewhere. No `--confirm` gate, same risk tier as `update`/`assign` — but this is the highest-blast-radius command in the whole family: a bad `--project`/`--type` fabricates a real, hard-to-walk-back item in a live tracker, so an invalid value surfaces the tracker's own error rather than a silent guess.
453
496
 
497
+ `--attach=path1,path2` (comma-separated local file paths) is available on `comment` and `create` only. Images render as an inline thumbnail on Jira and Linear; GitHub has no attachment upload API, so `--attach` is unsupported there.
498
+
454
499
  **A bad `--project`/`--type` gets a better error, automatically.** If create fails because the project or issue type doesn't exist, TicketLens fetches your tracker's real, current project list (and, for Jira, the real issue types for that project) and shows them alongside the failure — e.g. `Known creatable projects: CNV1, CNV2.` — rather than a bare tracker error. This is reactive only: it never runs on a successful create, never auto-retries the write, and is cached locally per profile for 24h so a burst of failed attempts doesn't re-fetch every time.
455
500
 
456
501
  All six write actions (comment/transition/assign/link/update/create) have a short local debounce (10s) against an accidental double-fire (a flaky retry, hitting enter twice), and every successful write is appended to a local, append-only audit log (`~/.ticketlens/ticket-action-log.jsonl`). A write that times out is never retried automatically — unlike Recall notes, ticket writes aren't naturally idempotent, so a timed-out attempt is surfaced to you instead of silently repeated. `duplicates` has neither, since nothing is written.
@@ -736,6 +781,7 @@ ticketlens mcp install --dry-run # Preview the registration without
736
781
 
737
782
  # ── Comment, Transition, Assign, Duplicates, Link, Update & Create ──────────────
738
783
  ticketlens comment CNV1-2 --body="Looks good, merging." # Post a comment to the tracker [Pro]
784
+ ticketlens comment CNV1-2 --body="See screenshot" --attach=./bug.png # Attach local files [Pro]
739
785
  ticketlens transition CNV1-2 # List valid transitions (read-only) [Pro]
740
786
  ticketlens transition CNV1-2 --target="Done" --confirm # Execute the transition [Pro]
741
787
  ticketlens assign CNV1-2 --to=me # Assign the ticket to yourself [Pro]
@@ -747,6 +793,7 @@ ticketlens update CNV1-2 --title="New title" # Update title/descr
747
793
  ticketlens update CNV1-2 --add-labels=urgent --remove-labels=stale # Add/remove labels [Pro]
748
794
  ticketlens create --project=CNV1 --type="Task" --summary="New ticket" # Create a new ticket [Pro]
749
795
  ticketlens create --project=ENG --summary="New issue" --profile=linear-team # Create on a different profile [Pro]
796
+ ticketlens create --project=CNV1 --type="Bug" --summary="Broken layout" --attach=./screenshot.png # Create with an attachment [Pro]
750
797
 
751
798
  # ── Stats ──────────────────────────────────────────────────────────────────────
752
799
  ticketlens stats # Response-time metrics from local history
@@ -757,12 +804,12 @@ ticketlens stats --format=json # JSON output for scripting
757
804
  # ── Compliance ────────────────────────────────────────────────────────────────
758
805
  ticketlens compliance <TICKET-KEY> # Check ticket requirements against local diff [Pro/Free 3/mo]
759
806
  ticketlens ledger # View local compliance audit ledger [Pro]
760
- ticketlens install-hooks # Install pre-push compliance gate [Pro]
761
- ticketlens install-hooks --uninstall # Remove installed hooks [Pro]
807
+ ticketlens install-hooks # Install pre-push compliance gate
808
+ ticketlens install-hooks --uninstall # Remove installed hooks
762
809
 
763
810
  # ── PR Description ─────────────────────────────────────────────────────────────
764
- ticketlens pr <TICKET-KEY> # Generate PR description from ticket [Pro]
765
- ticketlens pr <TICKET-KEY> | pbcopy # Copy to clipboard [Pro]
811
+ ticketlens pr <TICKET-KEY> # Generate PR description from ticket
812
+ ticketlens pr <TICKET-KEY> | pbcopy # Copy to clipboard
766
813
 
767
814
  # ── AI provider keys (BYOK) ───────────────────────────────────────────────────
768
815
  ticketlens cloud-keys list # List configured AI providers
@@ -820,7 +867,6 @@ ticketlens CNV1-2 --summarize --cloud # AI summary via TicketLens API (no loc
820
867
  ticketlens CNV1-2 --handoff # AI handoff brief from the ticket's comment thread (BYOK)
821
868
  ticketlens CNV1-2 --handoff --cloud # AI handoff brief via TicketLens API
822
869
  ticketlens CNV1-2 --compliance # Check ticket requirements against local diff [Pro/Free 3/mo]
823
- ticketlens triage --stale=3 # Custom stale threshold (default is 5)
824
870
  ticketlens triage --digest # POST scored triage results to digest endpoint
825
871
  ticketlens schedule # Set up a scheduled daily digest
826
872
  ticketlens note add --title="..." # Save a Recall note (body from stdin)
@@ -830,6 +876,7 @@ ticketlens recall sync # Retry any notes stuck in the local qu
830
876
  ticketlens recall settings # Show effective retry-queue settings, fetched live
831
877
  ticketlens mcp # Start the MCP stdio server (recall/ticket write tools)
832
878
  ticketlens comment CNV1-2 --body="..." # Post a comment to the tracker
879
+ ticketlens comment CNV1-2 --body="..." --attach=./bug.png # Attach local files (comment/create only)
833
880
  ticketlens transition CNV1-2 --target="Done" --confirm # Transition ticket status
834
881
  ticketlens assign CNV1-2 --to=me # Assign the ticket to yourself
835
882
  ticketlens duplicates CNV1-2 # Find likely duplicates (read-only)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticketlens",
3
- "version": "0.38.8",
3
+ "version": "0.38.9",
4
4
  "description": "Jira CLI for developers — fetch ticket context, triage your queue, and stop tab-switching. Zero dependencies, all local.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- <!-- jtb-skill-version: 0.31.0 -->
1
+ <!-- jtb-skill-version: 0.32.0 -->
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.
@@ -292,7 +292,7 @@ ticketlens create --project=PROD --type="Task" --summary="Fix login on mobile"
292
292
 
293
293
  `assign` is self-assign only — `--to` must be `me`. There is no way to assign to anyone else yet; don't attempt a workaround (e.g. via `comment`) if the user asks for that — tell them it isn't supported.
294
294
 
295
- `duplicates` lists likely-duplicate tickets in the same project. On Jira, any ticket already linked as a "Duplicate" is always listed first — that's a confirmed relationship a human already recorded, not a heuristic. Everything else is ranked by local title/description overlap — no tracker scores similarity server-side, so treat those as a nudge for the user to check manually, never as a confirmed duplicate to act on unprompted (e.g. don't auto-close or auto-comment based on a match). `--threshold=N` (0–1, default 0.35) tightens or loosens what counts as a text-match — it has no effect on Jira-linked duplicates, which are always shown.
295
+ `duplicates` lists likely-duplicate tickets in the same project. On Jira, any ticket already linked as a "Duplicate" is always listed first — that's a confirmed relationship a human already recorded, not a heuristic. Everything else is ranked by local title/description overlap — no tracker scores similarity server-side, so treat those as a nudge for the user to check manually, never as a confirmed duplicate to act on unprompted (e.g. don't auto-close or auto-comment based on a match). `--threshold=N` (0–1, default 0.35) tightens or loosens what counts as a text-match — it has no effect on Jira-linked duplicates, which are always shown. An empty result is the same approximation in the other direction — the local scorer can miss a real duplicate too, so don't treat "no likely duplicates found" as proof none exist.
296
296
 
297
297
  `link SOURCE-KEY TARGET-KEY` links two tickets — direction matters: SOURCE "types" TARGET (e.g. `link A B --type=Duplicate` means A duplicates B, not the other way around). Called with just the two keys, it lists the tracker's current valid link types without changing anything — never guess `--type`; always list first, then use one of the names shown. GitHub has no generic link relationship, so linking on a GitHub-tracked ticket *closes SOURCE as a duplicate of TARGET* — a real state change, not just a relationship add — and prints an explicit warning immediately before that happens, on top of the same `--confirm` gate.
298
298
 
@@ -760,6 +760,7 @@ export function printDuplicatesHelp({ stream = process.stdout } = {}) {
760
760
  ` that's a confirmed relationship, not a guess. Everything else comes from a`,
761
761
  ` local title/description overlap score (no tracker scores similarity`,
762
762
  ` server-side) — not exact, treat those as a nudge to check.`,
763
+ ` An empty result isn't a guarantee — this heuristic can miss real matches too.`,
763
764
  '',
764
765
  ` ${s.bold('OPTIONS')}`,
765
766
  '',
@@ -93,7 +93,7 @@ const TOOLS = [
93
93
  },
94
94
  {
95
95
  name: 'ticket_duplicates',
96
- description: 'Find likely duplicate tickets in the same project (Jira/GitHub/Linear). Read-only — never links or changes anything. On Jira, any ticket already linked as a "Duplicate" is always included first (a confirmed relationship, not a guess); everything else comes from a local, approximate title/description overlap score, since no tracker scores similarity server-side. Requires a TicketLens Pro license.',
96
+ description: 'Find likely duplicate tickets in the same project (Jira/GitHub/Linear). Read-only — never links or changes anything. On Jira, any ticket already linked as a "Duplicate" is always included first (a confirmed relationship, not a guess); everything else comes from a local, approximate title/description overlap score, since no tracker scores similarity server-side. That scorer can miss real duplicates as easily as it over-matches, so an empty result means none were found, not a guarantee that none exist. Requires a TicketLens Pro license.',
97
97
  inputSchema: {
98
98
  type: 'object',
99
99
  properties: {
@@ -518,7 +518,7 @@ export async function runTicketDuplicates(cmdArgs, {
518
518
  stream.write(` ${s.brand(s.bold(ticketKey))}: ${s.bold(source.summary ?? '')}\n\n`);
519
519
 
520
520
  if (results.length === 0) {
521
- stream.write(` No likely duplicates found.\n`);
521
+ stream.write(` No likely duplicates found (heuristic match only — not a guarantee none exist).\n`);
522
522
  return { ok: true, results: [] };
523
523
  }
524
524
  stream.write(` Possible duplicates:\n\n`);