@lore-ai/cli 0.1.7 → 0.1.9

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
@@ -2,7 +2,33 @@
2
2
 
3
3
  **Automated project knowledge that writes itself.**
4
4
 
5
- Lore is a CLI that runs periodic agentic scans over your development activity -- git history, AI coding conversations (Cursor, Claude Code) -- and distills them into living, always-current project documentation.
5
+ Lore is a CLI that scans your development activity -- git history, AI coding conversations (Cursor, Claude Code) -- and distills them into living, always-current project documentation. It works globally across all your repos from a single `~/.lore/` installation.
6
+
7
+ ## Prerequisites
8
+
9
+ ### AI Agent CLI (required -- pick one)
10
+
11
+ Lore is an orchestrator. All intelligence -- scanning, classifying, distilling, and generating documentation -- is delegated to an AI agent CLI. **Without one of these installed and authenticated, Lore cannot do anything.**
12
+
13
+ | Agent | Command | Install |
14
+ |-------|---------|---------|
15
+ | **Claude Code CLI** | `claude` | [docs.anthropic.com/en/docs/claude-code](https://docs.anthropic.com/en/docs/claude-code/overview) |
16
+ | **Cursor Agent CLI** | `cursor` | Built into [Cursor IDE](https://cursor.com) |
17
+
18
+ By default, `lore sync` tries Claude Code first, then falls back to Cursor Agent. You can lock to one:
19
+
20
+ ```bash
21
+ lore sync --cli claude # use only Claude Code
22
+ lore sync --cli cursor # use only Cursor Agent
23
+ ```
24
+
25
+ Or set it globally in `~/.lore/config.json` under `llm.cli` (`"auto"` | `"claude"` | `"cursor"`).
26
+
27
+ ### Other Dependencies
28
+
29
+ - **Node.js 18+** -- runtime ([nodejs.org](https://nodejs.org))
30
+ - **Git** -- for scanning commit history
31
+ - **GitHub CLI** (`gh`) -- for PR metadata scanning and `lore pr` ([cli.github.com](https://cli.github.com/))
6
32
 
7
33
  ## Installation
8
34
 
@@ -18,106 +44,125 @@ npx @lore-ai/cli
18
44
 
19
45
  ### Editor Extension
20
46
 
21
- Search **"Lore"** in the VS Code or Cursor extension marketplace, or install manually from a [GitHub Release](https://github.com/matan1542/lore/releases):
47
+ Search **"Lore"** in the Cursor extension marketplace, or install manually from a [GitHub Release](https://github.com/matan1542/lore/releases):
22
48
 
23
49
  ```bash
24
- # VS Code
25
- code --install-extension lore-cursor-extension-<version>.vsix
26
-
27
- # Cursor
28
50
  cursor --install-extension lore-cursor-extension-<version>.vsix
29
51
  ```
30
52
 
31
- ### Prerequisites
53
+ ### From Source (contributors)
32
54
 
33
- - **Node.js 18+**
34
- - **Git** -- for scanning commit history
35
- - **Claude Code CLI** (`claude`) -- for AI-powered distillation ([install](https://docs.anthropic.com/en/docs/claude-code/overview))
36
- - **GitHub CLI** (`gh`) -- optional, for PR metadata scanning ([install](https://cli.github.com/))
55
+ ```bash
56
+ git clone https://github.com/matan1542/lore.git
57
+ cd lore
58
+ npm install
59
+ npm run build
60
+ npm link
61
+ ```
37
62
 
38
63
  ## Quick Start
39
64
 
40
65
  ```bash
41
- # Initialize Lore in your project
66
+ # 1. Initialize Lore globally
42
67
  lore init
43
68
 
44
- # Run your first scan
69
+ # 2. Select which repos to scan
70
+ lore repos
71
+
72
+ # 3. Run your first scan
45
73
  lore sync
46
74
 
47
- # Check what was captured
75
+ # 4. Check what was captured
48
76
  lore status
49
77
 
50
- # Review proposed documentation updates
51
- lore review
78
+ # 5. Launch the web dashboard
79
+ lore ui
52
80
 
53
- # Ask questions about your project
54
- lore recall "why did we choose TypeScript?"
81
+ # 6. Generate a SKILL.md for a specific repo
82
+ lore skills -r my-project
55
83
 
56
- # Start automatic periodic scanning
57
- lore watch
84
+ # 7. Generate a PR description
85
+ lore pr
58
86
  ```
59
87
 
60
88
  ## How It Works
61
89
 
62
90
  ```
63
- Developer writes code
64
- |
65
- v
66
- Git commits + AI conversations accumulate
91
+ AI conversations accumulate (Cursor sessions, Claude Code transcripts)
92
+ Git commits accumulate (commits, diffs, PRs)
67
93
  |
68
94
  v
69
95
  [ lore sync ] <-- manual or cron trigger
70
96
  |
71
- +---> Scans git log (commits, diffs)
72
- +---> Scans GitHub PRs (titles, reviews, comments)
73
- +---> Scans Claude Code sessions (JSONL transcripts)
74
- +---> Scans Cursor conversations (workspace storage)
75
- +---> Reads current documentation state
76
- |
77
- v
78
- [ Alignment Engine ]
79
- |
80
- +---> Matches commits to conversations (by file overlap, branch, timing)
81
- +---> Produces: matched pairs, unmatched commits, unmatched conversations
97
+ +---> Phase 1: Summarize
98
+ | Reads all AI conversations, produces concise summaries
82
99
  |
83
- v
84
- [ Agentic Distillation ] (via Claude Code CLI + Octocode MCP)
100
+ +---> Phase 2: Classify
101
+ | Matches conversations to repos (file overlap, branch, timing)
85
102
  |
86
- +---> Extracts what changed and WHY
87
- +---> Identifies architectural decisions
88
- +---> Detects emerging patterns
89
- +---> Finds stale documentation
103
+ +---> Phase 3: Distill
104
+ | Extracts what changed and WHY, architectural decisions,
105
+ | emerging patterns, stale documentation
90
106
  |
91
- v
92
- [ Documentation Output ]
107
+ +---> Phase 4: Verdict
108
+ | Checks relevance, decides what to update
93
109
  |
94
- +---> Updates CHANGELOG.md
95
- +---> Creates Architecture Decision Records (ADRs)
96
- +---> Updates architecture docs
97
- +---> Queues items for human review
110
+ +---> Phase 5: Skill Generate
111
+ Writes/updates SKILL.md files per repo
112
+ (.cursor/skills/{repo}-guide/SKILL.md)
98
113
  ```
99
114
 
100
115
  ## Commands
101
116
 
102
117
  | Command | Description |
103
118
  |---------|-------------|
104
- | `lore init` | Set up Lore for a repository. Auto-detects available sources. |
105
- | `lore sync` | Run a one-off scan, distill, and write cycle. |
119
+ | `lore init` | Set up Lore globally (`~/.lore/`). Auto-detects available sources and CLI agents. |
120
+ | `lore sync` | Run a one-off scan of all AI conversations. |
106
121
  | `lore watch` | Start periodic scanning on a cron schedule. |
107
- | `lore status` | Show scan history, pending reviews, and source status. |
108
- | `lore recall <topic>` | Query accumulated knowledge about a topic. |
109
- | `lore review` | Interactively review and approve proposed doc updates. |
122
+ | `lore status` | Show what has been captured and what is pending. |
123
+ | `lore recall <topic>` | Query accumulated knowledge about a topic (progressive disclosure). |
124
+ | `lore review` | Show proposed documentation updates awaiting approval. |
110
125
  | `lore config [get\|set] <key> [value]` | View or edit configuration. |
126
+ | `lore repos [select\|list\|add\|remove]` | Manage which repos Lore processes. |
127
+ | `lore ui [-p port]` | Launch the web dashboard (default: port 3333). |
128
+ | `lore summarize` | Summarize all AI conversations and cluster by repo. |
129
+ | `lore skills [-r repo]` | Generate per-repo SKILL.md guides from conversations and git history. |
130
+ | `lore debug [sources\|scan\|conversations]` | Run pipeline diagnostics to troubleshoot scan issues. |
131
+ | `lore pr [-t tier]` | Generate a PR description from git history and cached conversation summaries. |
132
+
133
+ ## Web Dashboard
134
+
135
+ Launch the built-in dashboard to explore everything Lore has captured:
136
+
137
+ ```bash
138
+ lore ui # starts at http://localhost:3333
139
+ lore ui -p 4000 # custom port
140
+ ```
141
+
142
+ Requires `lore init` and at least one `lore sync` to have data to display.
143
+
144
+ **Available pages:**
145
+
146
+ | Page | What it shows |
147
+ |------|---------------|
148
+ | Overview | Summary of all scanned repos, recent activity |
149
+ | Conversations | All AI conversations with summaries and metadata |
150
+ | Skills | Generated SKILL.md files per repo |
151
+ | Scans | Scan history and timeline |
152
+ | Repo Scans | Per-repo scan details |
153
+ | Analytics | Charts and metrics across scans |
154
+ | Vetting | Skill quality vetting pipeline |
155
+ | Review | Pending documentation updates |
156
+ | Phase Review | Drill into individual pipeline phases |
157
+ | Tool Usage | AI tool call analytics |
111
158
 
112
159
  ## Configuration
113
160
 
114
- After `lore init`, configuration lives in `.lore/config.json`:
161
+ After `lore init`, configuration lives in `~/.lore/config.json`:
115
162
 
116
163
  ```json
117
164
  {
118
165
  "sources": {
119
- "git": { "enabled": true },
120
- "github": { "enabled": true, "repo": "owner/repo" },
121
166
  "cursor": { "enabled": true },
122
167
  "claudeCode": { "enabled": true }
123
168
  },
@@ -126,53 +171,79 @@ After `lore init`, configuration lives in `.lore/config.json`:
126
171
  "adr": "docs/decisions/",
127
172
  "architecture": "docs/architecture.md"
128
173
  },
174
+ "repos": {
175
+ "mode": "auto",
176
+ "include": [],
177
+ "exclude": [],
178
+ "auto": {
179
+ "maxRepos": 15,
180
+ "activityWindowDays": 14
181
+ }
182
+ },
183
+ "llm": {
184
+ "cli": "auto"
185
+ },
129
186
  "schedule": "0 */4 * * *",
130
187
  "reviewMode": "propose"
131
188
  }
132
189
  ```
133
190
 
134
- ### Review Modes
135
-
136
- - **`propose`** (default): All doc updates go to `.lore/pending/` for human review via `lore review`.
137
- - **`auto`**: Changes are applied directly to documentation files.
191
+ ### Key Settings
192
+
193
+ | Key | Default | Description |
194
+ |-----|---------|-------------|
195
+ | `sources.cursor.enabled` | `true` | Scan Cursor AI conversations |
196
+ | `sources.claudeCode.enabled` | `true` | Scan Claude Code transcripts |
197
+ | `repos.mode` | `"auto"` | `auto` (top N by activity), `whitelist` (only `include` list), or `all` |
198
+ | `repos.include` | `[]` | Always process these repos (names, org/repo, or path substrings) |
199
+ | `repos.exclude` | `[]` | Never process these repos |
200
+ | `repos.auto.maxRepos` | `15` | Max repos to process in auto mode |
201
+ | `repos.auto.activityWindowDays` | `14` | Only consider repos with recent activity |
202
+ | `llm.cli` | `"auto"` | AI agent: `auto` (Claude first, then Cursor), `claude`, or `cursor` |
203
+ | `schedule` | `"0 */4 * * *"` | Cron schedule for `lore watch` |
204
+ | `reviewMode` | `"propose"` | `propose` (queue for review) or `auto` (apply directly) |
138
205
 
139
206
  ## Data Sources
140
207
 
141
- ### Git History
142
- Scans commits, diffs, and changed files since the last sync.
143
-
144
- ### GitHub PRs
145
- Uses `gh` CLI to fetch merged PRs, review comments, and discussions.
208
+ ### AI Conversations
146
209
 
147
- ### Claude Code Sessions
148
- Reads JSONL transcripts from `~/.claude/projects/`. Extracts conversation turns, tool usage, and files modified.
210
+ **Cursor** -- Reads conversation metadata from Cursor's workspace SQLite storage.
149
211
 
150
- ### Cursor Conversations
151
- Reads conversation metadata from Cursor's workspace SQLite storage.
212
+ **Claude Code** -- Reads JSONL transcripts from `~/.claude/projects/`. Extracts conversation turns, tool usage, and files modified.
152
213
 
153
- ## Alignment Engine
214
+ ### Git History
154
215
 
155
- The key insight: **conversations explain the "why" behind commits**. Lore aligns them using:
216
+ Scans commits, diffs, and changed files since the last sync.
156
217
 
157
- 1. **File overlap** (strongest): files edited in conversation match files in commit diff
158
- 2. **Branch + time window**: same branch, commit within session timeframe
159
- 3. **Commit message match**: session contains a git commit command with matching message
218
+ ### GitHub PRs
160
219
 
161
- Unmatched conversations (planning, research, debugging sessions) are also captured -- they contain decisions and knowledge even when no code was committed.
220
+ Uses `gh` CLI to fetch merged PRs, review comments, and discussions.
162
221
 
163
222
  ## Architecture
164
223
 
165
224
  Built with:
225
+
226
+ **CLI & Pipeline:**
166
227
  - **TypeScript** with strict mode
167
228
  - **Commander** for CLI framework
168
- - **Clack** for beautiful interactive prompts
229
+ - **Clack** for interactive prompts
169
230
  - **Zod** for runtime validation
170
231
  - **Simple-git** for git operations
171
232
  - **Execa** for subprocess management
172
- - **Claude Code CLI** for AI-powered distillation
173
- - **Octocode MCP** for code research during distillation
233
+ - **better-sqlite3** / **sql.js** for local state
234
+ - **js-tiktoken** for token counting
235
+
236
+ **Web Dashboard:**
237
+ - **Hono** for API server
238
+ - **React 19** with **React Router 7**
239
+ - **TanStack Query** for data fetching
240
+ - **Recharts** for analytics charts
241
+ - **Vite** for build tooling
242
+ - **Tailwind CSS** for styling
243
+
244
+ **AI Integration:**
245
+ - **Claude Code CLI** or **Cursor Agent CLI** for all AI-powered phases
174
246
 
175
247
  ## License
176
248
 
177
249
  MIT
178
- # lore
package/dist/bin/lore.js CHANGED
@@ -505,6 +505,44 @@ function isRateLimited(output) {
505
505
  const lower = output.toLowerCase();
506
506
  return lower.includes("you've hit your limit") || lower.includes("hit your limit") || lower.includes("rate limit") || lower.includes("rate_limit") || lower.includes("too many requests") || lower.includes("quota exceeded") || lower.includes("overloaded") || lower.includes("usage limit reached");
507
507
  }
508
+ function safeExeca(cmd, args, options) {
509
+ const { signal, ...execaOpts } = options;
510
+ const child = execa(cmd, args, {
511
+ ...execaOpts,
512
+ // Disable execa's cleanup handler that registers signal-exit listeners
513
+ // on the parent process. We manage the lifecycle manually.
514
+ cleanup: false
515
+ });
516
+ if (signal) {
517
+ let forceKillTimer = null;
518
+ const onAbort = () => {
519
+ try {
520
+ child.kill("SIGTERM");
521
+ } catch {
522
+ }
523
+ forceKillTimer = setTimeout(() => {
524
+ try {
525
+ child.kill("SIGKILL");
526
+ } catch {
527
+ }
528
+ }, FORCE_KILL_TIMEOUT_MS);
529
+ };
530
+ if (signal.aborted) {
531
+ onAbort();
532
+ } else {
533
+ signal.addEventListener("abort", onAbort, { once: true });
534
+ }
535
+ const cleanup = () => {
536
+ signal.removeEventListener("abort", onAbort);
537
+ if (forceKillTimer) {
538
+ clearTimeout(forceKillTimer);
539
+ forceKillTimer = null;
540
+ }
541
+ };
542
+ child.then(cleanup, cleanup);
543
+ }
544
+ return child;
545
+ }
508
546
  async function invokeCliOnce(target, opts) {
509
547
  const { cmd, name } = target;
510
548
  const vendor = getVendorConfig(name);
@@ -543,11 +581,11 @@ ${input}`;
543
581
  }
544
582
  args = filtered;
545
583
  }
546
- const child = execa(cmd, args, {
584
+ const child = safeExeca(cmd, args, {
547
585
  input,
548
586
  cwd: opts.cwd,
549
587
  timeout: opts.timeout,
550
- cancelSignal: opts.signal,
588
+ signal: opts.signal,
551
589
  ...opts.env && { env: { ...process.env, ...opts.env } }
552
590
  });
553
591
  let rawOutput = "";
@@ -713,7 +751,7 @@ async function invokeWithFallbackAndTag(opts, label, phase, runId) {
713
751
  }
714
752
  return result;
715
753
  }
716
- var LORE_MARKER_PREFIX, LORE_PROCESS_MARKER, CLI_VENDOR_CONFIG, CLI_CHAIN, BACKOFF_DELAYS, JITTER_FRACTION, MODEL_TIER_MAP, rateLimitStats, cliPreference, resolvedCli;
754
+ var LORE_MARKER_PREFIX, LORE_PROCESS_MARKER, CLI_VENDOR_CONFIG, CLI_CHAIN, BACKOFF_DELAYS, JITTER_FRACTION, MODEL_TIER_MAP, rateLimitStats, cliPreference, resolvedCli, FORCE_KILL_TIMEOUT_MS;
717
755
  var init_cli = __esm({
718
756
  "src/utils/cli.ts"() {
719
757
  "use strict";
@@ -777,6 +815,7 @@ var init_cli = __esm({
777
815
  };
778
816
  cliPreference = "auto";
779
817
  resolvedCli = null;
818
+ FORCE_KILL_TIMEOUT_MS = 5e3;
780
819
  }
781
820
  });
782
821
 
@@ -7700,14 +7739,14 @@ ${userPrompt}`;
7700
7739
  // auto-approve MCPs in headless mode (needed for Octocode)
7701
7740
  ]
7702
7741
  ];
7703
- const child = execa3(
7742
+ const child = safeExeca(
7704
7743
  cli.cmd,
7705
7744
  baseArgs,
7706
7745
  {
7707
7746
  cwd,
7708
7747
  input: researchInput,
7709
7748
  timeout: SKILL_TIMEOUT_MS,
7710
- cancelSignal: options.signal
7749
+ signal: options.signal
7711
7750
  }
7712
7751
  );
7713
7752
  const result = isClaude ? await streamAndCollect(child, `research:${repoName}`, options) : await collectPlainOutput(child);
@@ -7807,14 +7846,14 @@ ${userPrompt}`;
7807
7846
  ];
7808
7847
  const startTime = Date.now();
7809
7848
  let lastActivityTime = Date.now();
7810
- const child = execa3(
7849
+ const child = safeExeca(
7811
7850
  cli.cmd,
7812
7851
  baseArgs,
7813
7852
  {
7814
7853
  input: genInput,
7815
7854
  timeout: WRITING_HARD_CEILING_MS,
7816
7855
  // absolute safety net only
7817
- cancelSignal: options.signal,
7856
+ signal: options.signal,
7818
7857
  env: { ...process.env, ...WRITING_ENV_OVERRIDES }
7819
7858
  }
7820
7859
  );
@@ -7835,7 +7874,10 @@ ${userPrompt}`;
7835
7874
  if (stallMs > WRITING_STALL_TIMEOUT_MS) {
7836
7875
  const stallSec = Math.round(stallMs / 1e3);
7837
7876
  options.onOutput?.(`[skill-gen] [gen:${repoName}] No progress for ${stallSec}s \u2014 aborting stalled agent (${elapsed}s total)`);
7838
- child.kill();
7877
+ try {
7878
+ child.kill();
7879
+ } catch {
7880
+ }
7839
7881
  return;
7840
7882
  }
7841
7883
  options.onOutput?.(`[skill-gen] [gen:${repoName}] Writing...${progressStr} (${elapsed}s elapsed)`);
@@ -7974,14 +8016,14 @@ ${userPrompt}`;
7974
8016
  ];
7975
8017
  const startTime = Date.now();
7976
8018
  let lastActivityTime = Date.now();
7977
- const child = execa3(
8019
+ const child = safeExeca(
7978
8020
  cli.cmd,
7979
8021
  baseArgs,
7980
8022
  {
7981
8023
  input: genInput,
7982
8024
  timeout: WRITING_HARD_CEILING_MS,
7983
8025
  // absolute safety net only
7984
- cancelSignal: options.signal,
8026
+ signal: options.signal,
7985
8027
  env: { ...process.env, ...WRITING_ENV_OVERRIDES }
7986
8028
  }
7987
8029
  );
@@ -8002,7 +8044,10 @@ ${userPrompt}`;
8002
8044
  if (stallMs > WRITING_STALL_TIMEOUT_MS) {
8003
8045
  const stallSec = Math.round(stallMs / 1e3);
8004
8046
  options.onOutput?.(`[skill-gen] [delta:${repoName}] No progress for ${stallSec}s \u2014 aborting stalled agent (${elapsed}s total)`);
8005
- child.kill();
8047
+ try {
8048
+ child.kill();
8049
+ } catch {
8050
+ }
8006
8051
  return;
8007
8052
  }
8008
8053
  options.onOutput?.(`[skill-gen] [delta:${repoName}] Writing...${progressStr} (${elapsed}s elapsed)`);