ticketlens 0.38.40 → 0.38.42
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
|
@@ -460,7 +460,7 @@ Every note is scanned before saving — anything shaped like a real secret (API
|
|
|
460
460
|
|
|
461
461
|
**Tags matter for search relevance.** `--tags=a,b` accepts anything, but a generic tag (the project name, "gotcha", "bug") gives future search almost nothing to match on. Tag with what the note is actually *about* — the specific technology, error type, or root cause (`retry-backoff`, `null-pointer`, `auth-middleware`) — so it surfaces when someone else hits the same problem.
|
|
462
462
|
|
|
463
|
-
**Local file attachments.** `note add --attach=path1,path2` (or the `recall_add` MCP tool's `attachments` array) saves a screenshot or file alongside the note, in your local vault
|
|
463
|
+
**Local file attachments.** `note add --attach=path1,path2` (or the `recall_add` MCP tool's `attachments` array) saves a screenshot or file alongside the note, in your local vault (`~/.ticketlens/recall/<PREFIX>/<note-id>/`) — same 10 MB/file, 50 MB/call, 20-file caps as ticket attachments for the local save. With Team Recall sync active, the attachment syncs too — visible and downloadable from Console > Admin > Recall — but the sync path caps at 12 MB/call (the backend's request-size limit, lower than the 50 MB local-save cap). Going over it fails the whole push, not just the attachment — the note stays saved locally, but neither its text nor the attachment reaches the team until it's pushed within the cap. Text-like attachments go through the same secret scan as the note body before syncing; a rejected scan blocks the whole push the same way.
|
|
464
464
|
|
|
465
465
|
**Gaps** — every `ticketlens PROJ-123` brief also diffs the ticket's own description against its linked tickets (from the depth traversal you already requested) and its own downloaded attachments, looking for requirements mentioned there but missing here. Anything uncovered shows up under a `## Gaps` section, citing exactly where it came from — a linked ticket key or an attachment filename — as evidence, never an instruction to act on. Nothing is saved anywhere; it's recomputed fresh on every fetch. Requires a Pro license, same as Recall. No network call beyond what the brief already made.
|
|
466
466
|
|
package/package.json
CHANGED
package/skills/jtb/SKILL.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- jtb-skill-version: 0.42.
|
|
1
|
+
<!-- jtb-skill-version: 0.42.2 -->
|
|
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.
|
|
@@ -371,7 +371,7 @@ echo "The body text of the note, one or more paragraphs." | \
|
|
|
371
371
|
|
|
372
372
|
**Choosing tags:** derive them from this note's actual content — the specific technology, error type, root cause, or affected component (e.g. `retry-backoff`, `null-pointer`, `auth-middleware`) — never the project name or a generic category word like `gotcha` or `bug`. A tag like `jtb` or `ticketlens` tells a future search nothing that the ticket/project context doesn't already say; a tag like `retry-backoff` is what actually surfaces this note when someone else hits the same problem. A tag that just restates the title in different words, or one you can't trace to a specific sentence in the body, gives that same zero signal — if you can't point to the exact phrase that justifies it, drop it. Same rule whether you're constructing the bash command above or calling `recall_add` directly — see its tool description for the same guidance.
|
|
373
373
|
|
|
374
|
-
**Attaching local files.** `note add --attach=path1,path2` (Pro) saves a screenshot or file alongside the note
|
|
374
|
+
**Attaching local files.** `note add --attach=path1,path2` (Pro) saves a screenshot or file alongside the note — same 10 MB/file, 50 MB/call, 20-file caps as `ticket_create`/`ticket_comment`'s `--attach` for the local save. If this account is entitled and the note syncs to a team (Team Recall sync active), the attachment syncs with it — visible and downloadable from Console > Admin > Recall, not just text-only. The sync path has a lower 12 MB/call cap than the local save (bounded by the backend's request-size limit, not the CLI). Going over it fails the whole push, not just the attachment — the note stays saved locally, but neither its text nor the attachment reaches the team until pushed within the cap. Text-like attachments (`.txt`/`.log`/`.md`/etc.) go through the same secret scan as the note body before syncing; a rejected scan blocks the whole push the same way. The `recall_add` MCP tool has a matching `attachments` array parameter — prefer it over the bash form when available, same rule as the rest of this section.
|
|
375
375
|
|
|
376
376
|
To search saved notes directly (outside of automatic brief injection): `ticketlens recall "<query>"`.
|
|
377
377
|
|
|
@@ -165,7 +165,7 @@ export const TOOLS = [
|
|
|
165
165
|
ticket: { type: 'string', description: 'Optional ticket key, e.g. PROJ-123.' },
|
|
166
166
|
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags derived from this note\'s actual content — the specific technology, error type, root cause, or affected component (e.g. "retry-backoff", "null-pointer", "auth-middleware"). Never the project name or a generic category word like "gotcha" or "bug" — those provide no search signal to someone else looking for this note later. A tag that just restates the title in different words, or one you cannot trace to a specific sentence in the body, gives that same zero signal — if you cannot point to the exact phrase that justifies it, drop it.' },
|
|
167
167
|
body: { type: 'string', description: 'The note body — one or more paragraphs.' },
|
|
168
|
-
attachments: { type: 'array', items: { type: 'string' }, description: 'Local file paths to attach (screenshots, logs, etc.), saved next to the note in the local vault. Same 10MB/file, 50MB/call, 20-file caps as ticket_comment/ticket_create.
|
|
168
|
+
attachments: { type: 'array', items: { type: 'string' }, description: 'Local file paths to attach (screenshots, logs, etc.), saved next to the note in the local vault. Same 10MB/file, 50MB/call, 20-file caps as ticket_comment/ticket_create for the local save. If this account is entitled and the note syncs to a team, attachments sync too — visible and downloadable from Console > Admin > Recall — but the sync path has a lower 12MB/call cap than the local save (backend request-size limit). Going over it fails the WHOLE push (note text included), not just the attachment; the note stays saved locally either way.' },
|
|
169
169
|
},
|
|
170
170
|
required: ['title', 'body'],
|
|
171
171
|
},
|
|
@@ -191,6 +191,13 @@ export async function runNoteAdd(cmdArgs, {
|
|
|
191
191
|
const recallTeamId = profile ? loadProfileRecallTeamIdFn(profile.name, configDir) : null;
|
|
192
192
|
const payload = { external_id: id, title, tickets: ticketKeys, tags, author, sources: [], body, captured_at: capturedAt.toISOString() };
|
|
193
193
|
if (recallTeamId !== null) payload.group_id = recallTeamId;
|
|
194
|
+
// Base64 over the existing JSON push endpoint — additive to the wire
|
|
195
|
+
// contract, no new request format. Backend re-validates size/count caps
|
|
196
|
+
// independently (RecallAttachmentStorage) rather than trusting this
|
|
197
|
+
// client-side pass, same defense-in-depth posture as the secret scanner.
|
|
198
|
+
if (attachments.length > 0) {
|
|
199
|
+
payload.attachments = attachments.map(a => ({ filename: a.filename, content: a.buffer.toString('base64') }));
|
|
200
|
+
}
|
|
194
201
|
const result = await pushNoteFn(payload, { cliToken, configDir, warn });
|
|
195
202
|
if (isRetryableFailureFn(result)) {
|
|
196
203
|
await enqueueNoteFn(payload, { cliToken, configDir, warn });
|
|
@@ -172,34 +172,49 @@ const UNDERSCORE_COMPOUND_RE = /^[A-Za-z]+(_[A-Za-z]+)+$/;
|
|
|
172
172
|
// delimiters within one candidate would need.
|
|
173
173
|
const SLASH_PATH_RE = /^[A-Za-z0-9]+(\/[A-Za-z0-9]+)+\/?$/;
|
|
174
174
|
|
|
175
|
+
// PHP static method/property/const access (Class_Name::method,
|
|
176
|
+
// Class_Name::CONST), optionally prefixed by a leading backslash (PHP's
|
|
177
|
+
// fully-qualified global-namespace form — stripEdgePunctuation does not
|
|
178
|
+
// strip a LEADING backslash, only the trailing/leading punctuation in
|
|
179
|
+
// EDGE_PUNCTUATION_RE's class). The class part reuses the same shape as
|
|
180
|
+
// UNDERSCORE_COMPOUND_RE but allows a bare (non-underscore) class name too
|
|
181
|
+
// — real code has both ("Zend_Http_Client::GET" and "Router::dispatch").
|
|
182
|
+
// Found live: backlog #17's ORIGINAL bug report repro included this exact
|
|
183
|
+
// class+member shape (`Advent_Http_ClientFactory::clientForJson`,
|
|
184
|
+
// `Zend_Http_Client::GET`), but the first fix only covered the bare class
|
|
185
|
+
// name in isolation — the embedded "::member" broke UNDERSCORE_COMPOUND_RE's
|
|
186
|
+
// match (colons aren't letters/underscores) and no other exemption applied.
|
|
187
|
+
const STATIC_REFERENCE_RE = /^\\?([A-Za-z]+(?:_[A-Za-z]+)*)::([A-Za-z_][A-Za-z0-9_]*)$/;
|
|
188
|
+
|
|
175
189
|
/**
|
|
176
190
|
* Downgrade-only, same never-exempt treatment as looksLikeCodeFilename and
|
|
177
191
|
* looksLikeCodeSyntax below: true for a candidate shaped like a multi-segment
|
|
178
|
-
* code identifier (underscore-delimited PHP/Zend-1-style class name)
|
|
179
|
-
* namespace/filesystem path (slash-delimited),
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
192
|
+
* code identifier (underscore-delimited PHP/Zend-1-style class name), a
|
|
193
|
+
* namespace/filesystem path (slash-delimited), or a PHP static method/const
|
|
194
|
+
* reference (Class_Name::member, see STATIC_REFERENCE_RE), each segment
|
|
195
|
+
* capped at MAX_COMPOUND_SEGMENT_LENGTH so a long random run can't pose as
|
|
196
|
+
* one "segment" — same cap isHyphenatedWordCompound already uses. Backlog
|
|
197
|
+
* #17: isLabelWord's ordinary-word branch only matches letters-only tokens
|
|
198
|
+
* with no separator at all, and neither existing downgrade path below
|
|
199
|
+
* requires a file extension or bracket/paren — so a standalone identifier,
|
|
200
|
+
* path, or static reference like the three shapes above had no exemption
|
|
201
|
+
* path whatsoever, unlike a hyphenated compound or a dotted filename.
|
|
187
202
|
*
|
|
188
|
-
* A disguised real secret matching
|
|
189
|
-
* warning, never a silent pass — see the security regression tests
|
|
190
|
-
* alongside this function's own tests for
|
|
203
|
+
* A disguised real secret matching any of the three shapes still surfaces
|
|
204
|
+
* as a warning, never a silent pass — see the security regression tests
|
|
205
|
+
* alongside this function's own tests for all three shapes.
|
|
191
206
|
*
|
|
192
207
|
* Known accepted gap (security review, backlog #17): unlike
|
|
193
208
|
* isHyphenatedWordCompound, this has no whole-token case-switch guard (see
|
|
194
209
|
* UNDERSCORE_COMPOUND_RE's own comment for why one can't be added without
|
|
195
210
|
* rejecting the real Zend_Http_Client-shaped identifiers this exists to
|
|
196
211
|
* recognize) — so a uniform-case (all-upper or all-lower) letters-only
|
|
197
|
-
* secret needs only ONE underscore or
|
|
198
|
-
* full reject to a warning, no camouflage (fake extension, case
|
|
199
|
-
* required. Still never a silent pass — always a warning — so
|
|
200
|
-
* same class of trade-off as CODE_SYNTAX_RE's even less-
|
|
201
|
-
* downgrade above (any bracket/paren, no shape requirement at
|
|
202
|
-
* new exposure.
|
|
212
|
+
* secret needs only ONE underscore, slash, or "::" inserted to downgrade
|
|
213
|
+
* from a full reject to a warning, no camouflage (fake extension, case
|
|
214
|
+
* pattern) required. Still never a silent pass — always a warning — so
|
|
215
|
+
* this is the same class of trade-off as CODE_SYNTAX_RE's even less-
|
|
216
|
+
* constrained downgrade above (any bracket/paren, no shape requirement at
|
|
217
|
+
* all), not a new exposure.
|
|
203
218
|
*/
|
|
204
219
|
function looksLikeCodeIdentifierOrPath(rawToken) {
|
|
205
220
|
const stripped = stripEdgePunctuation(rawToken);
|
|
@@ -209,6 +224,12 @@ function looksLikeCodeIdentifierOrPath(rawToken) {
|
|
|
209
224
|
if (SLASH_PATH_RE.test(stripped)) {
|
|
210
225
|
return stripped.split('/').filter(Boolean).every(segment => segment.length <= MAX_COMPOUND_SEGMENT_LENGTH);
|
|
211
226
|
}
|
|
227
|
+
const staticRefMatch = stripped.match(STATIC_REFERENCE_RE);
|
|
228
|
+
if (staticRefMatch) {
|
|
229
|
+
const classSegmentsWithinCap = staticRefMatch[1].split('_').every(segment => segment.length <= MAX_COMPOUND_SEGMENT_LENGTH);
|
|
230
|
+
const memberWithinCap = staticRefMatch[2].length <= MAX_COMPOUND_SEGMENT_LENGTH;
|
|
231
|
+
return classSegmentsWithinCap && memberWithinCap;
|
|
232
|
+
}
|
|
212
233
|
return false;
|
|
213
234
|
}
|
|
214
235
|
|