@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 +471 -41
- package/dist/cache/file-cache.d.ts +3 -0
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/sharded-cache.d.ts +33 -0
- package/dist/cli.mjs +829 -504
- package/dist/components/TraceResult.d.ts +1 -1
- package/dist/core/blame/blame.d.ts +1 -1
- package/dist/core/core.d.ts +2 -1
- package/dist/core/patch-id/patch-id.d.ts +7 -2
- package/dist/core/pr-lookup/pr-lookup.d.ts +7 -1
- package/dist/git/executor.d.ts +1 -0
- package/dist/git/index.d.ts +1 -1
- package/dist/index.cjs +762 -448
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +762 -450
- package/dist/platform/github/github-adapter.d.ts +4 -0
- package/dist/platform/github/github-enterprise-adapter.d.ts +1 -0
- package/dist/platform/gitlab/gitlab-adapter.d.ts +4 -0
- package/dist/platform/gitlab/gitlab-self-hosted-adapter.d.ts +1 -0
- package/dist/platform/platform.d.ts +1 -1
- package/dist/platform/scheduler/request-scheduler.d.ts +0 -5
- package/dist/types/graph.d.ts +2 -1
- package/dist/types/pipeline.d.ts +0 -1
- package/dist/types/trace.d.ts +5 -11
- package/dist/version.d.ts +5 -1
- package/package.json +3 -1
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
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
425
|
+
**Example — pre-flight check before batch processing:**
|
|
126
426
|
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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);
|
|
587
|
+
console.error(error.code); // e.g., 'FILE_NOT_FOUND'
|
|
164
588
|
console.error(error.message);
|
|
165
|
-
console.error(error.context);
|
|
589
|
+
console.error(error.context); // additional metadata
|
|
166
590
|
}
|
|
167
591
|
}
|
|
168
592
|
```
|
|
169
593
|
|
|
170
|
-
Common codes:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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>;
|
package/dist/cache/index.d.ts
CHANGED
|
@@ -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>;
|