@lumy-pack/line-lore 0.0.1 → 0.0.3

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,17 +91,184 @@ 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
 
98
+ ## Understanding the Output
99
+
100
+ ### TraceNode — the core unit of output
101
+
102
+ Every `trace()` call returns a `nodes` array. Each node represents one step in the ancestry chain from the code line back to its PR. Nodes are ordered from most recent (the line's direct commit) to most distant (the PR or issue).
103
+
104
+ ```typescript
105
+ interface TraceNode {
106
+ type: TraceNodeType; // What this node represents
107
+ sha?: string; // Git commit hash (40 chars)
108
+ trackingMethod: TrackingMethod; // How this node was discovered
109
+ confidence: Confidence; // How reliable this result is
110
+ prNumber?: number; // PR/MR number (only on pull_request nodes)
111
+ prUrl?: string; // Full URL to PR (only with Level 2 API access)
112
+ prTitle?: string; // PR title (only with Level 2 API access)
113
+ mergedAt?: string; // When the PR was merged (ISO 8601)
114
+ patchId?: string; // Git patch-id (only on rebased_commit nodes)
115
+ note?: string; // Additional context (e.g., "Cosmetic change: whitespace")
116
+ issueNumber?: number; // Issue number (only on issue nodes)
117
+ issueUrl?: string; // Issue URL
118
+ issueTitle?: string; // Issue title
119
+ issueState?: 'open' | 'closed';
120
+ issueLabels?: string[];
121
+ }
122
+ ```
123
+
124
+ ### Node types
125
+
126
+ | Type | Symbol | Meaning | When it appears |
127
+ |------|--------|---------|-----------------|
128
+ | `original_commit` | `●` | The commit that introduced or last modified this line | Always (at least one) |
129
+ | `cosmetic_commit` | `○` | A formatting-only change (whitespace, imports) | When AST detects no logic change |
130
+ | `merge_commit` | `◆` | The merge commit on the base branch | Merge-based workflows |
131
+ | `rebased_commit` | `◇` | A rebased version of the original commit | Rebase workflows with patch-id match |
132
+ | `pull_request` | `▸` | The PR/MR that introduced this change | When PR is found (Level 1 or 2) |
133
+ | `issue` | `▹` | A linked issue from the PR | When `--graph-depth >= 1` with Level 2 |
134
+
135
+ ### Tracking methods
136
+
137
+ | Method | Stage | Meaning |
138
+ |--------|-------|---------|
139
+ | `blame-CMw` | 1 | Found via `git blame -C -C -M -w` |
140
+ | `ast-signature` | 1-B | Found via AST structural comparison |
141
+ | `message-parse` | 3 | PR number extracted from merge commit message (e.g., `Merge pull request #42`) |
142
+ | `ancestry-path` | 3 | Found via `git log --ancestry-path --merges` |
143
+ | `patch-id` | 3 | Matched via `git patch-id` (rebase detection) |
144
+ | `api` | 4 | Found via GitHub/GitLab REST API |
145
+ | `issue-link` | 4+ | Found via PR-to-issue link in API |
146
+
147
+ ### Confidence levels
148
+
149
+ | Level | Meaning |
150
+ |-------|---------|
151
+ | `exact` | Deterministic match (blame, API lookup) |
152
+ | `structural` | AST structure matches but not byte-identical |
153
+ | `heuristic` | Best-effort match (message parsing, patch-id) |
154
+
155
+ ### Output examples
156
+
157
+ **Typical merge workflow (Level 2):**
158
+ ```
159
+ ● Commit a1b2c3d [exact] via blame-CMw
160
+ ▸ PR #42 feat: add authentication
161
+ └─ https://github.com/org/repo/pull/42
162
+ ```
163
+
164
+ **Squash merge (Level 2):**
165
+ ```
166
+ ● Commit e4f5a6b [exact] via blame-CMw
167
+ ▸ PR #55 refactor: user service
168
+ └─ https://github.com/org/repo/pull/55
169
+ ```
170
+
171
+ **Cosmetic change detected (AST enabled):**
172
+ ```
173
+ ○ Cosmetic d7e8f9a [exact] Cosmetic change: whitespace-only
174
+ ● Commit b2c3d4e [structural] via ast-signature
175
+ ▸ PR #31 feat: original logic
176
+ └─ https://github.com/org/repo/pull/31
177
+ ```
178
+
179
+ **Level 0 (offline — no platform CLI):**
180
+ ```
181
+ ● Commit a1b2c3d [exact] via blame-CMw
182
+
183
+ ⚠ Could not detect platform. Running in Level 0 (git only).
184
+ ```
185
+
186
+ **Level 1 (CLI found, not authenticated):**
187
+ ```
188
+ ● Commit a1b2c3d [exact] via blame-CMw
189
+ ▸ PR #42 [heuristic] via message-parse
190
+
191
+ ⚠ Platform CLI not authenticated. Running in Level 1 (local only).
192
+ ```
193
+
194
+ **JSON output (`--output json`):**
195
+ ```json
196
+ {
197
+ "nodes": [
198
+ {
199
+ "type": "original_commit",
200
+ "sha": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
201
+ "trackingMethod": "blame-CMw",
202
+ "confidence": "exact"
203
+ },
204
+ {
205
+ "type": "pull_request",
206
+ "sha": "f0e1d2c3b4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9",
207
+ "trackingMethod": "api",
208
+ "confidence": "exact",
209
+ "prNumber": 42,
210
+ "prUrl": "https://github.com/org/repo/pull/42",
211
+ "prTitle": "feat: add authentication",
212
+ "mergedAt": "2025-03-15T10:30:00Z"
213
+ }
214
+ ],
215
+ "operatingLevel": 2,
216
+ "featureFlags": {
217
+ "astDiff": true,
218
+ "deepTrace": false,
219
+ "commitGraph": false,
220
+ "graphql": true
221
+ },
222
+ "warnings": []
223
+ }
224
+ ```
225
+
226
+ **Quiet mode (`--quiet`):**
227
+ ```
228
+ 42
229
+ ```
230
+ Returns just the PR number. If no PR is found, returns the short commit SHA (e.g., `a1b2c3d`).
231
+
232
+ ### How to interpret results
233
+
234
+ | What you see | What it means |
235
+ |--------------|---------------|
236
+ | Only `original_commit` | The commit was found but no PR could be linked (direct push, or Level 0) |
237
+ | `original_commit` + `pull_request` | Successfully traced line → commit → PR |
238
+ | `cosmetic_commit` + `original_commit` + `pull_request` | Line was reformatted; AST traced back to the real logic change |
239
+ | `prUrl` is empty | PR was found via message parsing (Level 1) but no API details available |
240
+ | `warnings` array has entries | Some features are degraded — check `operatingLevel` |
241
+ | `operatingLevel: 0` | No platform CLI — only git blame results available |
242
+ | `operatingLevel: 1` | CLI found but not authenticated — PR lookup via merge message only |
243
+ | `operatingLevel: 2` | Full API access — most accurate results |
244
+
82
245
  ## Operating Levels
83
246
 
84
- - **Level 0**: Git only (offline, fastest)
85
- - **Level 1**: Platform CLI detected but not authenticated
86
- - **Level 2**: Full API access (GitHub/GitLab authenticated)
247
+ | Level | Requirements | What works | What doesn't |
248
+ |-------|-------------|------------|--------------|
249
+ | **0** | Git only | Blame, AST diff | PR lookup, issue graph |
250
+ | **1** | `gh`/`glab` CLI installed | Blame, AST diff, PR via merge message | API-based PR lookup, issue graph, deep trace |
251
+ | **2** | `gh`/`glab` CLI authenticated | Everything | — |
87
252
 
88
- Higher levels unlock deep tracing, issue graph traversal, and more accurate PR matching.
253
+ Run `line-lore health` to check your current level:
254
+ ```bash
255
+ npx @lumy-pack/line-lore health
256
+ ```
257
+
258
+ ### Upgrading your level
259
+
260
+ ```bash
261
+ # Level 0 → 1: Install the CLI
262
+ brew install gh # GitHub
263
+ brew install glab # GitLab
264
+
265
+ # Level 1 → 2: Authenticate
266
+ gh auth login # GitHub
267
+ glab auth login # GitLab
268
+
269
+ # GitHub Enterprise: authenticate with your hostname
270
+ gh auth login --hostname git.corp.com
271
+ ```
89
272
 
90
273
  ## Platform Support
91
274
 
@@ -94,45 +277,285 @@ Higher levels unlock deep tracing, issue graph traversal, and more accurate PR m
94
277
  - GitLab.com
95
278
  - GitLab Self-Hosted
96
279
 
97
- ## API Reference
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:
285
+
286
+ ```typescript
287
+ import { trace, graph, health, clearCache, LineLoreError } from '@lumy-pack/line-lore';
288
+ ```
98
289
 
99
- ### `trace(options: TraceOptions): Promise<TraceFullResult>`
290
+ ### `trace(options): Promise<TraceFullResult>`
100
291
 
101
292
  Trace a code line to its originating PR.
102
293
 
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`):**
307
+
308
+ ```typescript
309
+ {
310
+ nodes: TraceNode[]; // Ancestry chain (commits → PRs → issues)
311
+ operatingLevel: 0 | 1 | 2; // Current capability level
312
+ featureFlags: FeatureFlags; // Which features are active
313
+ warnings: string[]; // Degradation notices
314
+ }
315
+ ```
316
+
317
+ **Example — extracting PR info:**
318
+
319
+ ```typescript
320
+ const result = await trace({ file: 'src/auth.ts', line: 42 });
321
+
322
+ // Find the PR
323
+ const prNode = result.nodes.find(n => n.type === 'pull_request');
324
+ if (prNode) {
325
+ console.log(`PR #${prNode.prNumber}: ${prNode.prTitle}`);
326
+ console.log(`URL: ${prNode.prUrl}`); // only at Level 2
327
+ console.log(`Merged: ${prNode.mergedAt}`); // only at Level 2
328
+ } else {
329
+ const commit = result.nodes.find(n => n.type === 'original_commit');
330
+ console.log(`Direct commit: ${commit?.sha}`);
331
+ }
332
+
333
+ // Check degradation
334
+ if (result.operatingLevel < 2) {
335
+ console.warn('Limited results:', result.warnings);
336
+ }
337
+ ```
338
+
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
+
103
407
  **Options:**
104
- - `file` (string): Path to the file
105
- - `line` (number): Starting line number (1-indexed)
106
- - `endLine?` (number): Ending line for range queries
107
- - `remote?` (string): Git remote name (default: 'origin')
108
- - `deep?` (boolean): Enable deep trace for squash merges
109
- - `graphDepth?` (number): Issue graph traversal depth
110
- - `output?` ('human' | 'json' | 'llm'): Output format
111
- - `quiet?` (boolean): Suppress formatting
112
- - `noAst?` (boolean): Disable AST analysis
113
- - `noCache?` (boolean): Disable caching
408
+
409
+ | Parameter | Type | Required | Default | Description |
410
+ |-----------|------|----------|---------|-------------|
411
+ | `cwd` | `string` | no | process cwd | Working directory |
114
412
 
115
413
  **Returns:**
414
+
116
415
  ```typescript
117
416
  {
118
- nodes: TraceNode[]; // Ancestry nodes (commits, PRs, etc)
119
- operatingLevel: 0 | 1 | 2; // Capability level
120
- featureFlags: FeatureFlags; // Enabled features
121
- warnings: string[]; // Degradation notices
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
122
422
  }
123
423
  ```
124
424
 
125
- ### `health(options?: { cwd?: string }): Promise<HealthReport>`
425
+ **Example pre-flight check before batch processing:**
126
426
 
127
- 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
+ ```
128
439
 
129
440
  ### `clearCache(): Promise<void>`
130
441
 
131
442
  Clear PR lookup and patch-id caches.
132
443
 
133
- ### `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.
451
+
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[] }>();
134
478
 
135
- Traverse PR-to-issues graph (requires Level 2 access).
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
+ ```
136
559
 
137
560
  ## CLI Reference
138
561
 
@@ -145,7 +568,8 @@ Traverse PR-to-issues graph (requires Level 2 access).
145
568
  | `--output <format>` | Output as json, llm, or human |
146
569
  | `--quiet` | Suppress formatting |
147
570
  | `npx @lumy-pack/line-lore health` | Check system health |
148
- | `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 |
149
573
  | `--depth <num>` | Graph traversal depth |
150
574
  | `npx @lumy-pack/line-lore cache clear` | Clear caches |
151
575
 
@@ -160,21 +584,27 @@ try {
160
584
  await trace({ file: 'src/auth.ts', line: 42 });
161
585
  } catch (error) {
162
586
  if (error instanceof LineLoreError) {
163
- console.error(error.code); // e.g., 'FILE_NOT_FOUND'
587
+ console.error(error.code); // e.g., 'FILE_NOT_FOUND'
164
588
  console.error(error.message);
165
- console.error(error.context); // additional metadata
589
+ console.error(error.context); // additional metadata
166
590
  }
167
591
  }
168
592
  ```
169
593
 
170
- Common codes:
171
- - `NOT_GIT_REPO` — not in a git repository
172
- - `FILE_NOT_FOUND` file does not exist
173
- - `INVALID_LINE` — line number out of range
174
- - `GIT_BLAME_FAILED` git blame execution failed
175
- - `PR_NOT_FOUND` PR not found for commit
176
- - `CLI_NOT_AUTHENTICATED` platform CLI not authenticated
177
- - `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 |
178
608
 
179
609
  ## Requirements
180
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>;