@lumy-pack/line-lore 0.0.2 → 0.0.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/README.md CHANGED
@@ -6,6 +6,7 @@ Trace code lines to their originating Pull Requests via deterministic git blame
6
6
 
7
7
  - **Line-to-PR tracing**: Reverse-trace any code line to its source PR in seconds
8
8
  - **4-stage pipeline**: Blame → Cosmetic detection → Ancestry traversal → PR lookup
9
+ - **PR/Issue graph traversal**: Explore relationships between PRs and issues with edges
9
10
  - **Multi-platform support**: GitHub, GitHub Enterprise, GitLab, and GitLab self-hosted
10
11
  - **Operating levels**: Graceful degradation from offline (Level 0) to full API access (Level 2)
11
12
  - **Dual deployment**: Use as CLI tool or import as a programmatic library
@@ -34,7 +35,7 @@ npx @lumy-pack/line-lore trace src/config.ts -L 10,50
34
35
  npx @lumy-pack/line-lore trace src/auth.ts -L 42 --deep
35
36
 
36
37
  # Traverse PR-to-issues graph
37
- npx @lumy-pack/line-lore graph --pr 42 --depth 2
38
+ npx @lumy-pack/line-lore graph pr 42 --depth 2
38
39
 
39
40
  # Check system health
40
41
  npx @lumy-pack/line-lore health
@@ -55,7 +56,7 @@ npx @lumy-pack/line-lore trace src/auth.ts -L 42 --quiet
55
56
  ### Programmatic API
56
57
 
57
58
  ```typescript
58
- import { trace, health, clearCache } from '@lumy-pack/line-lore';
59
+ import { trace, graph, health, clearCache } from '@lumy-pack/line-lore';
59
60
 
60
61
  // Trace a line to its PR
61
62
  const result = await trace({
@@ -63,9 +64,24 @@ const result = await trace({
63
64
  line: 42,
64
65
  });
65
66
 
66
- console.log(result.nodes); // TraceNode[]
67
- console.log(result.operatingLevel); // 0 | 1 | 2
68
- console.log(result.warnings); // degradation messages
67
+ // Find the PR node
68
+ const prNode = result.nodes.find(n => n.type === 'pull_request');
69
+ if (prNode) {
70
+ console.log(`PR #${prNode.prNumber}: ${prNode.prTitle}`);
71
+ }
72
+
73
+ // Traverse PR → issue graph
74
+ const graphResult = await graph({ type: 'pr', number: 42, depth: 1 });
75
+ for (const node of graphResult.nodes) {
76
+ if (node.type === 'issue') {
77
+ console.log(`Issue #${node.issueNumber}: ${node.issueTitle}`);
78
+ }
79
+ }
80
+
81
+ // Check system readiness
82
+ const report = await health();
83
+ console.log(`Operating Level: ${report.operatingLevel}`);
84
+ console.log(`Git version: ${report.gitVersion}`);
69
85
  ```
70
86
 
71
87
  ## How It Works
@@ -75,7 +91,7 @@ console.log(result.warnings); // degradation messages
75
91
  1. **Line → Commit (Blame)**: Git blame with `-C -C -M` flags to detect renames and copies
76
92
  2. **Cosmetic Detection**: AST structural comparison to skip formatting-only changes
77
93
  3. **Commit → Merge Commit**: Ancestry-path traversal + patch-id matching to resolve merge commits
78
- 4. **Merge Commit → PR**: Commit message parsing + platform API lookup
94
+ 4. **Merge Commit → PR**: Commit message parsing + platform API lookup (filters unmerged PRs)
79
95
 
80
96
  No ML or heuristics — results are always reproducible.
81
97
 
@@ -201,7 +217,6 @@ interface TraceNode {
201
217
  "astDiff": true,
202
218
  "deepTrace": false,
203
219
  "commitGraph": false,
204
- "issueGraph": false,
205
220
  "graphql": true
206
221
  },
207
222
  "warnings": []
@@ -262,69 +277,285 @@ gh auth login --hostname git.corp.com
262
277
  - GitLab.com
263
278
  - GitLab Self-Hosted
264
279
 
265
- Platform is auto-detected from your git remote URL. Unknown hosts default to GitHub Enterprise.
280
+ Platform is auto-detected from your git remote URL. For unknown hosts, default branch detection falls back to the local `origin/HEAD` symbolic ref.
281
+
282
+ ## Programmatic API Reference
283
+
284
+ All functions are exported from the package root:
266
285
 
267
- ## API Reference
286
+ ```typescript
287
+ import { trace, graph, health, clearCache, LineLoreError } from '@lumy-pack/line-lore';
288
+ ```
268
289
 
269
- ### `trace(options: TraceOptions): Promise<TraceFullResult>`
290
+ ### `trace(options): Promise<TraceFullResult>`
270
291
 
271
292
  Trace a code line to its originating PR.
272
293
 
273
- **Options:**
274
- - `file` (string): Path to the file
275
- - `line` (number): Starting line number (1-indexed)
276
- - `endLine?` (number): Ending line for range queries
277
- - `remote?` (string): Git remote name (default: 'origin')
278
- - `deep?` (boolean): Enable deep trace for squash merges
279
- - `graphDepth?` (number): Issue graph traversal depth
280
- - `output?` ('human' | 'json' | 'llm'): Output format
281
- - `quiet?` (boolean): Suppress formatting
282
- - `noAst?` (boolean): Disable AST analysis
283
- - `noCache?` (boolean): Disable caching
294
+ **Options (`TraceOptions`):**
295
+
296
+ | Parameter | Type | Required | Default | Description |
297
+ |-----------|------|----------|---------|-------------|
298
+ | `file` | `string` | yes | | Path to the file |
299
+ | `line` | `number` | yes | | Starting line number (1-indexed) |
300
+ | `endLine` | `number` | no | — | Ending line for range queries |
301
+ | `remote` | `string` | no | `'origin'` | Git remote name |
302
+ | `deep` | `boolean` | no | `false` | Expand patch-id scan range (500→2000), continue search after merge commit match |
303
+ | `noAst` | `boolean` | no | `false` | Disable AST analysis |
304
+ | `noCache` | `boolean` | no | `false` | Disable cache reads and writes |
305
+
306
+ **Returns (`TraceFullResult`):**
284
307
 
285
- **Returns:**
286
308
  ```typescript
287
309
  {
288
310
  nodes: TraceNode[]; // Ancestry chain (commits → PRs → issues)
289
- operatingLevel: 0 | 1 | 2; // Current capability level
311
+ operatingLevel: 0 | 1 | 2; // Current capability level
290
312
  featureFlags: FeatureFlags; // Which features are active
291
313
  warnings: string[]; // Degradation notices
292
314
  }
293
315
  ```
294
316
 
295
- **How to read the result:**
317
+ **Example extracting PR info:**
318
+
296
319
  ```typescript
297
320
  const result = await trace({ file: 'src/auth.ts', line: 42 });
298
321
 
299
- // Did we find a PR?
322
+ // Find the PR
300
323
  const prNode = result.nodes.find(n => n.type === 'pull_request');
301
324
  if (prNode) {
302
325
  console.log(`PR #${prNode.prNumber}: ${prNode.prTitle}`);
303
326
  console.log(`URL: ${prNode.prUrl}`); // only at Level 2
304
327
  console.log(`Merged: ${prNode.mergedAt}`); // only at Level 2
305
328
  } else {
306
- // No PR found — line was a direct commit or Level 0
307
329
  const commit = result.nodes.find(n => n.type === 'original_commit');
308
330
  console.log(`Direct commit: ${commit?.sha}`);
309
331
  }
310
332
 
311
- // Check if results are degraded
333
+ // Check degradation
312
334
  if (result.operatingLevel < 2) {
313
335
  console.warn('Limited results:', result.warnings);
314
336
  }
315
337
  ```
316
338
 
317
- ### `health(options?: { cwd?: string }): Promise<HealthReport>`
339
+ **Example trace a line range:**
340
+
341
+ ```typescript
342
+ const result = await trace({
343
+ file: 'src/config.ts',
344
+ line: 10,
345
+ endLine: 50,
346
+ deep: true, // search harder for squash merges
347
+ noCache: true, // skip cache for fresh results
348
+ });
349
+ ```
350
+
351
+ ### `graph(options): Promise<GraphResult>`
352
+
353
+ Traverse the PR/issue relationship graph. Requires Level 2 (authenticated CLI).
354
+
355
+ **Options (`GraphOptions`):**
356
+
357
+ | Parameter | Type | Required | Default | Description |
358
+ |-----------|------|----------|---------|-------------|
359
+ | `type` | `'pr' \| 'issue'` | yes | — | Starting node type |
360
+ | `number` | `number` | yes | — | PR or issue number |
361
+ | `depth` | `number` | no | `2` | Traversal depth |
362
+ | `remote` | `string` | no | `'origin'` | Git remote name |
363
+
364
+ **Returns (`GraphResult`):**
365
+
366
+ ```typescript
367
+ {
368
+ nodes: TraceNode[]; // All discovered nodes (PRs + issues)
369
+ edges: Array<{ // Relationships between nodes
370
+ from: string; // Source node identifier
371
+ to: string; // Target node identifier
372
+ relation: string; // Relationship type (e.g., "closes", "references")
373
+ }>;
374
+ }
375
+ ```
376
+
377
+ **Example — find issues linked to a PR:**
378
+
379
+ ```typescript
380
+ const result = await graph({ type: 'pr', number: 42, depth: 1 });
381
+
382
+ const issues = result.nodes.filter(n => n.type === 'issue');
383
+ for (const issue of issues) {
384
+ console.log(`#${issue.issueNumber} [${issue.issueState}]: ${issue.issueTitle}`);
385
+ console.log(` Labels: ${issue.issueLabels?.join(', ')}`);
386
+ }
387
+
388
+ // Inspect edges for relationship details
389
+ for (const edge of result.edges) {
390
+ console.log(`${edge.from} -[${edge.relation}]-> ${edge.to}`);
391
+ }
392
+ ```
393
+
394
+ **Example — build a dependency map from an issue:**
395
+
396
+ ```typescript
397
+ const result = await graph({ type: 'issue', number: 100, depth: 2 });
398
+
399
+ const prs = result.nodes.filter(n => n.type === 'pull_request');
400
+ console.log(`${prs.length} PRs linked to issue #100`);
401
+ ```
402
+
403
+ ### `health(options?): Promise<HealthReport & { operatingLevel }>`
404
+
405
+ Check system readiness: git version, platform CLI status, authentication.
406
+
407
+ **Options:**
408
+
409
+ | Parameter | Type | Required | Default | Description |
410
+ |-----------|------|----------|---------|-------------|
411
+ | `cwd` | `string` | no | process cwd | Working directory |
412
+
413
+ **Returns:**
414
+
415
+ ```typescript
416
+ {
417
+ gitVersion: string; // e.g., "2.43.0"
418
+ commitGraph: boolean; // commit-graph file detected
419
+ bloomFilter: boolean; // bloom filter support available
420
+ hints: string[]; // optimization suggestions
421
+ operatingLevel: 0 | 1 | 2; // current capability level
422
+ }
423
+ ```
424
+
425
+ **Example — pre-flight check before batch processing:**
318
426
 
319
- Check system health: git version, platform CLI status, authentication.
427
+ ```typescript
428
+ const report = await health();
429
+
430
+ if (report.operatingLevel < 2) {
431
+ console.error('Full API access required. Run: gh auth login');
432
+ process.exit(1);
433
+ }
434
+
435
+ if (!report.bloomFilter) {
436
+ console.warn('Consider running: git commit-graph write --reachable');
437
+ }
438
+ ```
320
439
 
321
440
  ### `clearCache(): Promise<void>`
322
441
 
323
442
  Clear PR lookup and patch-id caches.
324
443
 
325
- ### `traverseIssueGraph(adapter, startType, startNumber, options?): Promise<GraphResult>`
444
+ ```typescript
445
+ await clearCache();
446
+ ```
447
+
448
+ ### `traverseIssueGraph(adapter, startType, startNumber, options?)`
449
+
450
+ Low-level graph traversal that requires a `PlatformAdapter` instance. Prefer `graph()` unless you need to control adapter creation.
326
451
 
327
- Traverse PR-to-issues graph (requires Level 2 access).
452
+ ## Programmatic Usage Patterns
453
+
454
+ ### VSCode Extension Integration
455
+
456
+ ```typescript
457
+ import { trace } from '@lumy-pack/line-lore';
458
+
459
+ async function getPRForActiveLine(filePath: string, lineNumber: number) {
460
+ const result = await trace({ file: filePath, line: lineNumber });
461
+
462
+ const pr = result.nodes.find(n => n.type === 'pull_request');
463
+ if (pr?.prUrl) {
464
+ return { number: pr.prNumber, title: pr.prTitle, url: pr.prUrl };
465
+ }
466
+
467
+ return null;
468
+ }
469
+ ```
470
+
471
+ ### CI Pipeline — PR Impact Analysis
472
+
473
+ ```typescript
474
+ import { trace, graph } from '@lumy-pack/line-lore';
475
+
476
+ async function analyzeChangedLines(file: string, lines: number[]) {
477
+ const prs = new Map<number, { title: string; issues: string[] }>();
478
+
479
+ for (const line of lines) {
480
+ const result = await trace({ file, line });
481
+ const pr = result.nodes.find(n => n.type === 'pull_request');
482
+
483
+ if (pr?.prNumber && !prs.has(pr.prNumber)) {
484
+ const graphResult = await graph({
485
+ type: 'pr',
486
+ number: pr.prNumber,
487
+ depth: 1,
488
+ });
489
+
490
+ const issues = graphResult.nodes
491
+ .filter(n => n.type === 'issue')
492
+ .map(n => `#${n.issueNumber}`);
493
+
494
+ prs.set(pr.prNumber, { title: pr.prTitle ?? '', issues });
495
+ }
496
+ }
497
+
498
+ return prs;
499
+ }
500
+ ```
501
+
502
+ ### Batch Processing with Cache Control
503
+
504
+ ```typescript
505
+ import { trace, clearCache } from '@lumy-pack/line-lore';
506
+
507
+ async function batchTrace(entries: Array<{ file: string; line: number }>) {
508
+ // Clear stale cache before batch run
509
+ await clearCache();
510
+
511
+ const results = [];
512
+ for (const entry of entries) {
513
+ const result = await trace({
514
+ file: entry.file,
515
+ line: entry.line,
516
+ // Cache is enabled by default — subsequent lookups for the same
517
+ // PR will be fast
518
+ });
519
+ results.push({ ...entry, result });
520
+ }
521
+
522
+ return results;
523
+ }
524
+ ```
525
+
526
+ ## Exported Types
527
+
528
+ All types are re-exported from the package root for TypeScript consumers:
529
+
530
+ ```typescript
531
+ import type {
532
+ // Core result types
533
+ TraceNode,
534
+ TraceFullResult,
535
+ GraphResult,
536
+ GraphOptions,
537
+ TraceOptions,
538
+ HealthReport,
539
+ FeatureFlags,
540
+
541
+ // Node classification
542
+ TraceNodeType, // 'original_commit' | 'cosmetic_commit' | ...
543
+ TrackingMethod, // 'blame-CMw' | 'ast-signature' | ...
544
+ Confidence, // 'exact' | 'structural' | 'heuristic'
545
+ OperatingLevel, // 0 | 1 | 2
546
+
547
+ // Platform types
548
+ PlatformType, // 'github' | 'github-enterprise' | ...
549
+ PlatformAdapter,
550
+ AuthStatus,
551
+ PRInfo,
552
+ IssueInfo,
553
+ RateLimitInfo,
554
+
555
+ // Graph traversal
556
+ GraphTraversalOptions,
557
+ } from '@lumy-pack/line-lore';
558
+ ```
328
559
 
329
560
  ## CLI Reference
330
561
 
@@ -337,7 +568,8 @@ Traverse PR-to-issues graph (requires Level 2 access).
337
568
  | `--output <format>` | Output as json, llm, or human |
338
569
  | `--quiet` | Suppress formatting |
339
570
  | `npx @lumy-pack/line-lore health` | Check system health |
340
- | `npx @lumy-pack/line-lore graph --pr <num>` | Traverse PR graph |
571
+ | `npx @lumy-pack/line-lore graph pr <num>` | Show issues linked to a PR |
572
+ | `npx @lumy-pack/line-lore graph issue <num>` | Show PRs linked to an issue |
341
573
  | `--depth <num>` | Graph traversal depth |
342
574
  | `npx @lumy-pack/line-lore cache clear` | Clear caches |
343
575
 
@@ -352,21 +584,27 @@ try {
352
584
  await trace({ file: 'src/auth.ts', line: 42 });
353
585
  } catch (error) {
354
586
  if (error instanceof LineLoreError) {
355
- console.error(error.code); // e.g., 'FILE_NOT_FOUND'
587
+ console.error(error.code); // e.g., 'FILE_NOT_FOUND'
356
588
  console.error(error.message);
357
- console.error(error.context); // additional metadata
589
+ console.error(error.context); // additional metadata
358
590
  }
359
591
  }
360
592
  ```
361
593
 
362
- Common codes:
363
- - `NOT_GIT_REPO` — not in a git repository
364
- - `FILE_NOT_FOUND` file does not exist
365
- - `INVALID_LINE` — line number out of range
366
- - `GIT_BLAME_FAILED` git blame execution failed
367
- - `PR_NOT_FOUND` PR not found for commit
368
- - `CLI_NOT_AUTHENTICATED` platform CLI not authenticated
369
- - `API_RATE_LIMITED` platform API rate limit exceeded
594
+ Common error codes:
595
+
596
+ | Code | Meaning |
597
+ |------|---------|
598
+ | `NOT_GIT_REPO` | Not in a git repository |
599
+ | `FILE_NOT_FOUND` | File does not exist |
600
+ | `INVALID_LINE` | Line number out of range |
601
+ | `GIT_BLAME_FAILED` | Git blame execution failed |
602
+ | `PR_NOT_FOUND` | PR not found for commit |
603
+ | `CLI_NOT_AUTHENTICATED` | Platform CLI not authenticated |
604
+ | `API_RATE_LIMITED` | Platform API rate limit exceeded |
605
+ | `API_REQUEST_FAILED` | Platform API request failed |
606
+ | `GIT_COMMAND_FAILED` | Git command execution failed |
607
+ | `GIT_TIMEOUT` | Git command timed out |
370
608
 
371
609
  ## Requirements
372
610
 
@@ -1,10 +1,13 @@
1
1
  export declare class FileCache<T> {
2
2
  private readonly filePath;
3
3
  private readonly maxEntries;
4
+ private readonly enabled;
4
5
  private writeQueue;
6
+ private store;
5
7
  constructor(fileName: string, options?: {
6
8
  maxEntries?: number;
7
9
  cacheDir?: string;
10
+ enabled?: boolean;
8
11
  });
9
12
  get(key: string): Promise<T | null>;
10
13
  has(key: string): Promise<boolean>;
@@ -1 +1,3 @@
1
1
  export { FileCache } from './file-cache.js';
2
+ export { ShardedCache, cleanupLegacyCache } from './sharded-cache.js';
3
+ export type { RepoIdentity } from './sharded-cache.js';
@@ -0,0 +1,33 @@
1
+ export interface RepoIdentity {
2
+ host: string;
3
+ owner: string;
4
+ repo: string;
5
+ }
6
+ export declare class ShardedCache<T> {
7
+ private readonly baseDir;
8
+ private readonly maxEntriesPerShard;
9
+ private readonly enabled;
10
+ private readonly shards;
11
+ constructor(namespace: string, options?: {
12
+ repoId?: RepoIdentity;
13
+ maxEntriesPerShard?: number;
14
+ cacheBase?: string;
15
+ enabled?: boolean;
16
+ });
17
+ get(key: string): Promise<T | null>;
18
+ has(key: string): Promise<boolean>;
19
+ set(key: string, value: T): Promise<void>;
20
+ delete(key: string): Promise<boolean>;
21
+ clear(): Promise<void>;
22
+ size(): Promise<number>;
23
+ destroy(): Promise<void>;
24
+ private getShardState;
25
+ private doSet;
26
+ private readShard;
27
+ private writeShard;
28
+ }
29
+ /**
30
+ * Removes the legacy flat cache files (pre-sharding).
31
+ * Safe to call multiple times — no-ops if already cleaned.
32
+ */
33
+ export declare function cleanupLegacyCache(): Promise<void>;