ticketlens 0.42.3 → 0.42.4

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticketlens",
3
- "version": "0.42.3",
3
+ "version": "0.42.4",
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": {
@@ -29,7 +29,7 @@ import { appendFileSync } from 'node:fs';
29
29
  import { join } from 'node:path';
30
30
  import { isLicensed } from '../scripts/lib/license.mjs';
31
31
  import { readCliToken } from '../scripts/lib/cli-auth.mjs';
32
- import { buildCaptureExcerpt, privateTmpDir } from './recall-nudge-lib.mjs';
32
+ import { buildCaptureExcerpt, privateTmpDir, writeLastCaptureAt } from './recall-nudge-lib.mjs';
33
33
  import { autoCapture } from '../scripts/lib/summarizer.mjs';
34
34
  import { runNoteAdd } from '../scripts/lib/note-command.mjs';
35
35
  import { DEFAULT_CONFIG_DIR } from '../scripts/lib/config.mjs';
@@ -56,17 +56,20 @@ function logLine(message) {
56
56
  * @param {string} opts.transcriptPath
57
57
  * @param {string} [opts.ticketKey]
58
58
  * @param {string} [opts.configDir]
59
+ * @param {string} [opts.cwd] - directory whose shared capture marker gets updated on a real capture (backlog #44: makes a successful background capture visible to a later sync Stop check, which otherwise has no way to know this ran)
59
60
  * @returns {Promise<{outcome: 'skipped'|'captured'|'not-written'|'error', reason?: string, title?: string, error?: string}>}
60
61
  */
61
62
  export async function runAutoCapture({
62
63
  transcriptPath,
63
64
  ticketKey,
64
65
  configDir = DEFAULT_CONFIG_DIR,
66
+ cwd = process.cwd(),
65
67
  isLicensedFn = isLicensed,
66
68
  readCliTokenFn = readCliToken,
67
69
  buildCaptureExcerptFn = buildCaptureExcerpt,
68
70
  autoCaptureFn = autoCapture,
69
71
  runNoteAddFn = runNoteAdd,
72
+ writeLastCaptureAtFn = writeLastCaptureAt,
70
73
  } = {}) {
71
74
  if (!isLicensedFn('pro', configDir)) {
72
75
  logLine('skipped: not licensed');
@@ -112,10 +115,16 @@ export async function runAutoCapture({
112
115
  });
113
116
 
114
117
  logLine(written ? `captured: ${result.title}` : `not-written: ${result.title}`);
118
+ if (written) writeLastCaptureAtFn(cwd, Date.now());
115
119
  return written ? { outcome: 'captured', title: result.title } : { outcome: 'not-written', title: result.title };
116
120
  }
117
121
 
118
122
  if (import.meta.url === `file://${process.argv[1]}`) {
119
- const [, , transcriptPath, ticketKeyArg] = process.argv;
120
- runAutoCapture({ transcriptPath, ticketKey: ticketKeyArg || undefined }).catch(() => { /* never escape — nothing is watching this process */ });
123
+ // cwdArg (backlog #44 code review): passed explicitly by the spawning Stop
124
+ // hook rather than relying on this child's own process.cwd() — Node
125
+ // resolves process.cwd() through symlinks, so a cwd with a symlinked
126
+ // component would otherwise hash to a different marker file than the one
127
+ // a later Stop's hasRecentCapture(cwd) reads, silently breaking the bridge.
128
+ const [, , transcriptPath, ticketKeyArg, cwdArg] = process.argv;
129
+ runAutoCapture({ transcriptPath, ticketKey: ticketKeyArg || undefined, cwd: cwdArg || undefined }).catch(() => { /* never escape — nothing is watching this process */ });
121
130
  }
@@ -89,11 +89,31 @@ if (!sessionId || !transcriptPath) process.exit(0);
89
89
 
90
90
  const { sawFetch, sawMutatingAction, sawRecallFlag, sawNoteAdd, ticketKey } = scanTranscript(transcriptPath);
91
91
  const cliToken = readCliToken();
92
+ // Backlog #44 (9th/10th reports): entitled accounts already run the async
93
+ // auto-capture judge below — throttled to at most once per CAPTURE_FRESHNESS_MS
94
+ // per cwd (hasRecentAutoCaptureAttempt below), not on every single Stop. Once
95
+ // it succeeds, its outcome is now visible via writeLastCaptureAt
96
+ // (recall-auto-capture.mjs) to a LATER Stop's hasRecentCapture() check — but
97
+ // THIS Stop's own decision below still can't see an attempt spawned moments
98
+ // ago (detached, still running), nor is a fresh one spawned if the window's
99
+ // one attempt already ran and moved on. For that residual window, a hard
100
+ // block duplicates a safety net that's already in flight or already spent:
101
+ // exit 0 instead of 2 so this never blocks the turn for these accounts. Free
102
+ // tier has no such net, so it keeps the hard block unchanged — this is its
103
+ // only enforcement.
104
+ const hasSafetyNet = isLicensed('pro') && Boolean(cliToken);
92
105
 
93
- if (isLicensed('pro') && cliToken && !hasRecentAutoCaptureAttempt(cwd)) {
106
+ if (hasSafetyNet && !hasRecentAutoCaptureAttempt(cwd)) {
94
107
  try {
95
108
  writeLastAutoCaptureAttemptAt(cwd, Date.now());
96
- const child = spawn(process.execPath, [AUTO_CAPTURE_SCRIPT, transcriptPath, ticketKey ?? ''], {
109
+ // cwd is passed explicitly as argv (not left to the child's own
110
+ // process.cwd()) — code review finding: Node resolves process.cwd()
111
+ // through symlinks, so a cwd with any symlink component (macOS os.tmpdir()
112
+ // itself, an iCloud-synced Desktop, a Docker bind mount) would make the
113
+ // child's own process.cwd() differ from the string used here, hashing to
114
+ // a different marker file and silently breaking the bridge this fix exists
115
+ // to build.
116
+ const child = spawn(process.execPath, [AUTO_CAPTURE_SCRIPT, transcriptPath, ticketKey ?? '', cwd], {
97
117
  cwd,
98
118
  detached: true,
99
119
  stdio: 'ignore',
@@ -156,4 +176,7 @@ if (sawRecallFlag) {
156
176
  '`ticketlens note add`. If genuinely nothing qualified, just say so — then finish.\n',
157
177
  );
158
178
  }
159
- process.exit(2);
179
+ // hasSafetyNet: the message above is still written (a diagnostic trail if
180
+ // the async job silently fails) but exit 0 means Claude Code never surfaces
181
+ // it or blocks the turn — only exit 2 feeds stderr back (backlog #44).
182
+ process.exit(hasSafetyNet ? 0 : 2);
@@ -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 = 'http://api.ticketlens.test';
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,