@djolex999/vir-cli 0.1.0

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.
Files changed (69) hide show
  1. package/CLAUDE.md +149 -0
  2. package/LICENSE +21 -0
  3. package/README.md +155 -0
  4. package/dist/claude/updater.js +230 -0
  5. package/dist/claude/updater.js.map +1 -0
  6. package/dist/cli.js +779 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/config.js +82 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/daemon/launchd.js +93 -0
  11. package/dist/daemon/launchd.js.map +1 -0
  12. package/dist/dedupe/detector.js +159 -0
  13. package/dist/dedupe/detector.js.map +1 -0
  14. package/dist/dedupe/merger.js +116 -0
  15. package/dist/dedupe/merger.js.map +1 -0
  16. package/dist/lint/linter.js +224 -0
  17. package/dist/lint/linter.js.map +1 -0
  18. package/dist/pipeline/distiller.js +208 -0
  19. package/dist/pipeline/distiller.js.map +1 -0
  20. package/dist/pipeline/filter.js +28 -0
  21. package/dist/pipeline/filter.js.map +1 -0
  22. package/dist/pipeline/parser.js +109 -0
  23. package/dist/pipeline/parser.js.map +1 -0
  24. package/dist/pipeline/run.js +312 -0
  25. package/dist/pipeline/run.js.map +1 -0
  26. package/dist/pipeline/scanner.js +47 -0
  27. package/dist/pipeline/scanner.js.map +1 -0
  28. package/dist/pipeline/scrubber.js +51 -0
  29. package/dist/pipeline/scrubber.js.map +1 -0
  30. package/dist/pipeline/summarizer.js +162 -0
  31. package/dist/pipeline/summarizer.js.map +1 -0
  32. package/dist/pipeline/types.js +2 -0
  33. package/dist/pipeline/types.js.map +1 -0
  34. package/dist/pipeline/writer.js +195 -0
  35. package/dist/pipeline/writer.js.map +1 -0
  36. package/dist/search/embedder.js +93 -0
  37. package/dist/search/embedder.js.map +1 -0
  38. package/dist/search/retriever.js +212 -0
  39. package/dist/search/retriever.js.map +1 -0
  40. package/dist/search/synthesizer.js +26 -0
  41. package/dist/search/synthesizer.js.map +1 -0
  42. package/dist/state/db.js +309 -0
  43. package/dist/state/db.js.map +1 -0
  44. package/dist/ui/display.js +148 -0
  45. package/dist/ui/display.js.map +1 -0
  46. package/package.json +50 -0
  47. package/src/claude/updater.ts +273 -0
  48. package/src/cli.ts +953 -0
  49. package/src/config.ts +89 -0
  50. package/src/daemon/launchd.ts +115 -0
  51. package/src/dedupe/detector.ts +197 -0
  52. package/src/dedupe/merger.ts +172 -0
  53. package/src/lint/linter.ts +286 -0
  54. package/src/pipeline/distiller.ts +280 -0
  55. package/src/pipeline/filter.ts +43 -0
  56. package/src/pipeline/parser.ts +118 -0
  57. package/src/pipeline/run.ts +378 -0
  58. package/src/pipeline/scanner.ts +51 -0
  59. package/src/pipeline/scrubber.ts +55 -0
  60. package/src/pipeline/summarizer.ts +204 -0
  61. package/src/pipeline/types.ts +41 -0
  62. package/src/pipeline/writer.ts +242 -0
  63. package/src/search/embedder.ts +88 -0
  64. package/src/search/retriever.ts +255 -0
  65. package/src/search/synthesizer.ts +45 -0
  66. package/src/state/db.ts +451 -0
  67. package/src/ui/display.ts +184 -0
  68. package/tsconfig.json +23 -0
  69. package/vir-flow.html +708 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,149 @@
1
+ # vir
2
+
3
+ Local macOS daemon that distills Claude Code session transcripts into an
4
+ Obsidian vault. Reads `~/.claude/projects/**/*.jsonl`, filters by heuristic,
5
+ classifies with Haiku, extracts durable knowledge with Sonnet, writes typed
6
+ notes to the vault, maintains an index, and syncs back into CLAUDE.md files.
7
+
8
+ ## Stack
9
+
10
+ - Node.js ≥ 20, TypeScript strict (`noImplicitAny`, `noUncheckedIndexedAccess`)
11
+ - `commander` for CLI, `chalk` for output, `zod` for config validation
12
+ - `better-sqlite3` for state (synchronous — no async complexity)
13
+ - `@anthropic-ai/sdk` for the Anthropic path; native `fetch` for the Kie path
14
+ - macOS `launchd` for the daemon, `osascript` for notifications
15
+
16
+ ## Structure
17
+
18
+ ```
19
+ src/
20
+ cli.ts # commander entry — every subcommand wired here
21
+ config.ts # ~/.vir/config.json schema + helpers
22
+ pipeline/
23
+ run.ts # orchestrator (scan → filter → scrub → distill → write)
24
+ scanner.ts # walk ~/.claude/projects, SHA-256 each .jsonl
25
+ parser.ts # extract assistant/user text, tool calls, files touched
26
+ filter.ts # heuristic scorer (length, tools, files, signal words)
27
+ scrubber.ts # strip API keys, bearer tokens, absolute paths, emails
28
+ distiller.ts # callLLM helper, Haiku classify + Sonnet extract,
29
+ # withRateLimitRetry, buildAnthropicClient,
30
+ # normalizeModelName
31
+ writer.ts # frontmatter + body, wikilink injection, index/log
32
+ summarizer.ts # per-project knowledge summaries
33
+ types.ts # ParsedSession, Classification, DistilledNote, Category
34
+ search/
35
+ retriever.ts # unified async search: embeddings → TF-IDF fallback
36
+ embedder.ts # Ollama integration (nomic-embed-text), cosine sim
37
+ synthesizer.ts # Claude synthesis for `vir query`
38
+ lint/
39
+ linter.ts # orphans, staleness, contradictions
40
+ dedupe/
41
+ detector.ts # candidate pairs + LLM judgment
42
+ merger.ts # archive / keep / Sonnet-merge
43
+ claude/
44
+ updater.ts # VIR:START / VIR:END block management
45
+ state/
46
+ db.ts # better-sqlite3, idempotent migrations
47
+ daemon/
48
+ launchd.ts # plist render + load/unload via spawnSync('launchctl')
49
+ ui/
50
+ display.ts # the ONE place console.log lives — palette, glyphs,
51
+ # header(), divider(), box(), spinner(), summary(),
52
+ # wrap(), sourceRow(), categoryRow()
53
+ ```
54
+
55
+ ## Key conventions
56
+
57
+ - **Path expansion.** All paths from config (`vaultPath`, `claudeProjectsDir`)
58
+ must run through `expandHome()` (or `os.homedir()` directly) before use.
59
+ Never assume `~/` works in `fs.*`.
60
+ - **Per-session try/catch.** The daemon must never die on a single bad
61
+ transcript. `pipeline/run.ts` wraps every session iteration and records the
62
+ error in the DB so it doesn't keep retrying.
63
+ - **Provider routing.**
64
+ - `provider: 'anthropic'` → `@anthropic-ai/sdk` (`buildAnthropicClient`).
65
+ - `provider: 'kie'` → native `fetch` to `https://api.kie.ai/claude/v1/messages`
66
+ with `Authorization: Bearer <kieApiKey>`. The SDK is **not** used for Kie
67
+ even with a `baseURL` override — its `x-api-key` header conflicts.
68
+ - Both flow through `callLLM(config, client, opts)`.
69
+ - **Model names.** `normalizeModelName(model, provider)` collapses any model
70
+ that *starts with* a Kie canonical id (`claude-haiku-4-5`,
71
+ `claude-sonnet-4-6`) back to the bare id. Fallback strips trailing
72
+ `-YYYYMMDD`. Anthropic path passes through untouched.
73
+ - **Rate limits.** Every LLM call is wrapped in `withRateLimitRetry()` — three
74
+ attempts with `60s / 120s / 240s` backoff on `429` (Anthropic SDK
75
+ `APIError.status === 429` and our `HttpError.status === 429` both detected).
76
+ Sessions are processed sequentially with a `2s` delay after each successful
77
+ distill.
78
+ - **State is the source of truth.** `~/.vir/vir.db` records every session by
79
+ path with its SHA-256 hash. Reruns are idempotent; a session is only
80
+ re-distilled if its file content changes. Migrations are additive only
81
+ (`PRAGMA table_info(sessions)` + `ALTER TABLE ADD COLUMN`).
82
+ - **VIR:START / VIR:END markers are sacred.** `sync-claude` only mutates bytes
83
+ between those markers. When a CLAUDE.md has no block, the new one is
84
+ appended; the rest of the file is preserved verbatim.
85
+ - **No comments explaining obvious things.** Comment WHY a non-obvious
86
+ invariant exists — never WHAT the code does.
87
+ - **All user-facing output goes through `ui/display.ts`.** Other modules
88
+ must not call `console.log` directly. The pipeline's `fileLog()` writes
89
+ to `~/.vir/daemon.log` in plain text regardless of UI mode; the
90
+ display module renders only when `!opts.quiet`.
91
+ - **Ollama is best-effort.** `writer.maybeEmbed()` and the search path
92
+ both probe via `isOllamaAvailableCached()` and silently fall back
93
+ (TF-IDF for query; no-op for writer). An embedding failure must never
94
+ fail a write.
95
+ - **Cost prompts respect intent.** `--yes`, `--daemon`, and
96
+ `--rewrite-only` all skip the > 20 new-session confirmation. The
97
+ daemon path *never* prompts (it has no tty).
98
+ - **`vir init` is an @inquirer/* wizard.** Arrow-key `select` for
99
+ provider and models, `input` with `validate` for keys/numbers,
100
+ `confirm` for the "create missing path?" flow. Don't fall back to
101
+ raw readline here — the rest of the file already imports it for
102
+ dedupe/sync-claude/cost prompts, but the init UX is the wizard.
103
+ - **`vir run` prints a preflight line.** `N files found · M cached · K
104
+ new` shows after the scan in dim text, and the same triple goes to
105
+ the daemon log. Diagnoses "fresh DB looks stale" misconfigurations
106
+ in one line.
107
+
108
+ ## Commands
109
+
110
+ ```
111
+ vir init # interactive setup
112
+ vir run # one pass (used by daemon)
113
+ vir run --full # ignore state cache, re-process everything
114
+ vir run --rewrite-only # re-render notes from stored content
115
+ # (no scan, no LLM, free)
116
+ vir run --yes # skip the > 20 sessions cost prompt
117
+ vir schedule install # write + load ~/Library/LaunchAgents plist
118
+ vir schedule uninstall # unload + remove plist
119
+ vir status # daemon state + knowledge base breakdown
120
+ vir query "<question>" # embeddings (Ollama) → TF-IDF fallback
121
+ # → Claude synthesis
122
+ vir embed # generate Ollama embeddings for notes
123
+ vir embed --force # regenerate all embeddings
124
+ vir summarize <project> # generate per-project summary
125
+ vir summarize --all # all projects with notes
126
+ vir lint # orphans + stale + contradictions
127
+ vir lint --orphans # orphans only (free)
128
+ vir lint --stale # staleness only (free)
129
+ vir lint --contradictions # contradictions only (Haiku tokens)
130
+ vir dedupe # interactive duplicate review + merge
131
+ vir sync-claude # diff then confirm CLAUDE.md updates
132
+ vir sync-claude --dry-run # diff only, never write
133
+ vir sync-claude --force # apply without confirmation
134
+ vir sync-claude <project> # specific project only
135
+ vir sync-claude --global # only ~/.claude/CLAUDE.md
136
+ ```
137
+
138
+ ## File locations
139
+
140
+ - Config: `~/.vir/config.json`
141
+ - State: `~/.vir/vir.db` (better-sqlite3 with WAL). A one-shot rename in
142
+ `StateDb`'s constructor moves the pre-rename `~/.vir/state.db` over to
143
+ `~/.vir/vir.db` if it's still around — preserves cache, hides the
144
+ history of the doc/code mismatch.
145
+ - Daemon log: `~/.vir/daemon.log`
146
+ - launchd plist: `~/Library/LaunchAgents/lab.growthq.vir.plist`
147
+ - Vault notes: `<vaultPath>/<outputDir>/{patterns,gotchas,decisions,tools,projects,archived}/`
148
+ - Vault index: `<vaultPath>/<outputDir>/index.md`
149
+ - Vault run log: `<vaultPath>/<outputDir>/log.md`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Djordje Marković / GrowthQ Lab DOO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # vir
2
+
3
+ Distills Claude Code sessions into a compounding knowledge vault.
4
+
5
+ ## What it does
6
+
7
+ Every Claude Code session produces patterns, gotchas, and architecture
8
+ decisions. 95% sits in JSONL transcripts you never open again.
9
+
10
+ Vir is a local macOS daemon that runs every 4 hours, distills sessions
11
+ into structured markdown in your Obsidian vault, and feeds knowledge back
12
+ into CLAUDE.md so every future session starts sharper.
13
+
14
+ The loop: sessions → vir → vault → CLAUDE.md → better sessions.
15
+
16
+ Inspired by Andrej Karpathy's LLM Wiki pattern and Uros Pesic's KB Brain concept.
17
+
18
+ ## Why Vir?
19
+
20
+ Vir (вир) is the Serbian word for whirlpool — the place where a river
21
+ pulls everything in and concentrates it. That is what this tool does.
22
+ Sessions flow in, Vir pulls out what matters, and deposits it somewhere
23
+ permanent.
24
+
25
+ The name felt right for a tool whose job is to take the chaos of a
26
+ Claude Code session and find the still point at the center.
27
+
28
+ ## Prerequisites
29
+
30
+ - macOS (launchd daemon)
31
+ - Node.js 18+
32
+ - Claude Code (sessions at `~/.claude/projects/`)
33
+ - Obsidian vault
34
+ - Anthropic API key OR Kie.ai API key (~72% cheaper, same models)
35
+ - Optional: Ollama + `nomic-embed-text` for semantic search
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ npm install -g @djolex999/vir-cli
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ ```bash
46
+ vir init # guided setup wizard with provider picker
47
+ vir run # process historical sessions
48
+ vir schedule install # daemonize at configured cadence
49
+ ```
50
+
51
+ ## First run cost
52
+
53
+ Vir processes all historical Claude Code sessions on first run. Cost
54
+ varies by session depth:
55
+
56
+ - Simple sessions: ~$0.02 each
57
+ - Deep code reviews: up to ~$0.10 each
58
+ - Typical first run (200 sessions): $1–5 one-time
59
+
60
+ All subsequent runs process only new sessions: ~$0.05 per run.
61
+
62
+ Use Kie.ai as provider for 72% discount on same Claude models. Pass
63
+ `--yes` to skip the cost confirmation prompt.
64
+
65
+ ## Commands
66
+
67
+ | Command | Description |
68
+ |---|---|
69
+ | `vir init` | Interactive setup |
70
+ | `vir run` | Process new sessions |
71
+ | `vir run --full` | Reprocess all sessions |
72
+ | `vir run --rewrite-only` | Reformat notes, no API calls |
73
+ | `vir run --yes` | Skip cost confirmation |
74
+ | `vir query "<question>"` | Semantic search your vault |
75
+ | `vir summarize <project>` | Cross-session project synthesis |
76
+ | `vir summarize --all` | Summarize all projects |
77
+ | `vir lint` | Find orphans, stale notes, contradictions |
78
+ | `vir lint --orphans` | Orphan check only (free) |
79
+ | `vir lint --stale` | Staleness check only (free) |
80
+ | `vir lint --contradictions` | Contradiction check (Haiku) |
81
+ | `vir dedupe` | Interactive duplicate detection |
82
+ | `vir sync-claude` | Inject top knowledge into CLAUDE.md |
83
+ | `vir sync-claude --dry-run` | Preview changes, no writes |
84
+ | `vir sync-claude --force` | Apply without confirmation |
85
+ | `vir embed` | Generate embeddings for semantic search |
86
+ | `vir embed --force` | Regenerate all embeddings |
87
+ | `vir schedule install` | Register launchd daemon |
88
+ | `vir schedule uninstall` | Remove launchd daemon |
89
+ | `vir status` | Knowledge heatmap + daemon status |
90
+
91
+ ## Semantic search (optional)
92
+
93
+ Vir uses TF-IDF by default. For semantic search via embeddings:
94
+
95
+ ```bash
96
+ brew install ollama
97
+ ollama pull nomic-embed-text
98
+ ollama serve
99
+ ```
100
+
101
+ Then in a new terminal:
102
+
103
+ ```bash
104
+ vir embed
105
+ vir query "how do I handle rate limiting in Next.js"
106
+ ```
107
+
108
+ Falls back to TF-IDF automatically if Ollama is not running.
109
+
110
+ ## Config reference
111
+
112
+ Located at `~/.vir/config.json`.
113
+
114
+ | Field | Default | Description |
115
+ |---|---|---|
116
+ | `vaultPath` | — | Absolute path to Obsidian vault |
117
+ | `outputDir` | `vir` | Subdir inside vault |
118
+ | `claudeProjectsDir` | `~/.claude/projects` | Claude Code sessions |
119
+ | `cadenceHours` | `4` | Daemon run frequency (hours) |
120
+ | `provider` | `anthropic` | `anthropic` or `kie` |
121
+ | `anthropicApiKey` | — | Required if `provider=anthropic` |
122
+ | `kieApiKey` | — | Required if `provider=kie` |
123
+ | `filterThreshold` | `0.4` | Heuristic pre-filter (0..1) |
124
+ | `models.classify` | `claude-haiku-4-5-20251001` | Classify model |
125
+ | `models.distill` | `claude-sonnet-4-6` | Distill model |
126
+
127
+ ## Vault structure
128
+
129
+ ```
130
+ vault/vir/
131
+ index.md # full catalog of all notes
132
+ log.md # chronological append log
133
+ patterns/ # reusable approaches
134
+ gotchas/ # bugs, footguns, edge cases
135
+ decisions/ # architecture decisions with rationale
136
+ tools/ # per-tool knowledge
137
+ projects/ # cross-session project summaries
138
+ archived/ # deduplicated notes (not deleted)
139
+ ```
140
+
141
+ ## State & logs
142
+
143
+ ```
144
+ ~/.vir/config.json — configuration
145
+ ~/.vir/vir.db — SQLite (hashes, embeddings, content)
146
+ ~/.vir/daemon.log — daemon run log
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT
152
+
153
+ ## Author
154
+
155
+ Built by Djordje Marković / GrowthQ Lab DOO
@@ -0,0 +1,230 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { kebab } from "../pipeline/writer.js";
5
+ export const VIR_START = "<!-- VIR:START -->";
6
+ export const VIR_END = "<!-- VIR:END -->";
7
+ const TOP_N_PER_CATEGORY = 5;
8
+ export function planUpdates(_cfg, db, options = {}) {
9
+ const rows = db.listDistilled();
10
+ const plans = [];
11
+ if (!options.project) {
12
+ plans.push(buildPlan(globalClaudePath(), rows, { scope: "global" }));
13
+ }
14
+ if (options.globalOnly)
15
+ return plans;
16
+ const byProject = new Map();
17
+ for (const r of rows) {
18
+ const slug = kebab(r.project);
19
+ if (slug.length === 0)
20
+ continue;
21
+ if (options.project && slug !== options.project)
22
+ continue;
23
+ let arr = byProject.get(slug);
24
+ if (!arr) {
25
+ arr = [];
26
+ byProject.set(slug, arr);
27
+ }
28
+ arr.push(r);
29
+ }
30
+ for (const [slug, projectRows] of byProject) {
31
+ const target = projectClaudePath(slug);
32
+ plans.push(buildPlan(target, projectRows, { scope: { project: slug } }));
33
+ }
34
+ return plans;
35
+ }
36
+ function buildPlan(target, rows, meta) {
37
+ const entries = selectTopEntries(rows);
38
+ const newBlock = renderBlock(entries);
39
+ const existsAtPath = existsSync(target);
40
+ let existingBlock = "";
41
+ let lastUpdated = null;
42
+ if (existsAtPath) {
43
+ try {
44
+ const raw = readFileSync(target, "utf8");
45
+ existingBlock = extractBlock(raw);
46
+ lastUpdated = extractLastUpdated(existingBlock);
47
+ }
48
+ catch {
49
+ // ignore
50
+ }
51
+ }
52
+ const oldEntries = parseEntries(existingBlock);
53
+ const diff = computeDiff(oldEntries, entries);
54
+ return {
55
+ target,
56
+ exists: existsAtPath,
57
+ hasBlock: existingBlock.length > 0,
58
+ lastUpdated,
59
+ newBlock,
60
+ diff,
61
+ scope: meta.scope,
62
+ };
63
+ }
64
+ function selectTopEntries(rows) {
65
+ const byCategory = {
66
+ pattern: [],
67
+ gotcha: [],
68
+ decision: [],
69
+ tool: [],
70
+ };
71
+ for (const r of rows) {
72
+ const bucket = byCategory[r.category];
73
+ if (bucket)
74
+ bucket.push(r);
75
+ }
76
+ const out = [];
77
+ for (const cat of Object.keys(byCategory)) {
78
+ const sorted = (byCategory[cat] ?? [])
79
+ .slice()
80
+ .sort((a, b) => b.confidence - a.confidence)
81
+ .slice(0, TOP_N_PER_CATEGORY);
82
+ for (const r of sorted) {
83
+ out.push({
84
+ slug: `${cat}/${kebab(r.topic)}`,
85
+ topic: r.topic,
86
+ category: cat,
87
+ confidence: r.confidence,
88
+ startedAt: r.startedAt,
89
+ });
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+ function renderBlock(entries) {
95
+ const today = new Date().toISOString().slice(0, 10);
96
+ const lines = [];
97
+ lines.push(VIR_START);
98
+ lines.push(`<!-- vir-last-updated: ${today} -->`);
99
+ lines.push("");
100
+ lines.push("## Distilled Knowledge (from Vir)");
101
+ lines.push("");
102
+ const byCat = {
103
+ pattern: [],
104
+ gotcha: [],
105
+ decision: [],
106
+ tool: [],
107
+ };
108
+ for (const e of entries) {
109
+ const cat = byCat[e.category];
110
+ if (cat)
111
+ cat.push(e);
112
+ }
113
+ const order = [
114
+ ["pattern", "Patterns"],
115
+ ["gotcha", "Gotchas"],
116
+ ["decision", "Decisions"],
117
+ ["tool", "Tools"],
118
+ ];
119
+ for (const [key, label] of order) {
120
+ const list = byCat[key] ?? [];
121
+ if (list.length === 0)
122
+ continue;
123
+ lines.push(`### ${label}`);
124
+ for (const e of list) {
125
+ lines.push(`- ${e.slug} (conf ${e.confidence.toFixed(2)}) — ${e.topic}`);
126
+ }
127
+ lines.push("");
128
+ }
129
+ lines.push(VIR_END);
130
+ return lines.join("\n");
131
+ }
132
+ function extractBlock(raw) {
133
+ const start = raw.indexOf(VIR_START);
134
+ const end = raw.indexOf(VIR_END);
135
+ if (start === -1 || end === -1 || end < start)
136
+ return "";
137
+ return raw.slice(start, end + VIR_END.length);
138
+ }
139
+ function extractLastUpdated(block) {
140
+ const m = block.match(/vir-last-updated:\s*(\d{4}-\d{2}-\d{2})/);
141
+ return m ? (m[1] ?? null) : null;
142
+ }
143
+ function parseEntries(block) {
144
+ if (block.length === 0)
145
+ return [];
146
+ const out = [];
147
+ const lines = block.split("\n");
148
+ for (const line of lines) {
149
+ // - pattern/topic (conf 0.84) — display topic
150
+ const m = line.match(/^- ([a-z]+\/[a-z0-9-]+) \(conf ([\d.]+)\)\s*—\s*(.+)$/i);
151
+ if (!m)
152
+ continue;
153
+ const slug = m[1] ?? "";
154
+ const conf = Number(m[2] ?? 0);
155
+ const topic = m[3] ?? "";
156
+ const category = slug.split("/")[0] ?? "";
157
+ out.push({
158
+ slug,
159
+ topic,
160
+ category,
161
+ confidence: Number.isFinite(conf) ? conf : 0,
162
+ startedAt: null,
163
+ });
164
+ }
165
+ return out;
166
+ }
167
+ function computeDiff(old, next) {
168
+ const oldBySlug = new Map(old.map((e) => [e.slug, e]));
169
+ const newBySlug = new Map(next.map((e) => [e.slug, e]));
170
+ const added = [];
171
+ const removed = [];
172
+ const upgraded = [];
173
+ const unchanged = [];
174
+ for (const e of next) {
175
+ const prev = oldBySlug.get(e.slug);
176
+ if (!prev) {
177
+ added.push(e);
178
+ }
179
+ else if (Math.abs(prev.confidence - e.confidence) > 0.05) {
180
+ upgraded.push({
181
+ slug: e.slug,
182
+ oldConf: prev.confidence,
183
+ newConf: e.confidence,
184
+ });
185
+ }
186
+ else {
187
+ unchanged.push(e);
188
+ }
189
+ }
190
+ for (const e of old) {
191
+ if (!newBySlug.has(e.slug))
192
+ removed.push({ slug: e.slug });
193
+ }
194
+ return { added, removed, upgraded, unchanged };
195
+ }
196
+ export function applyPlan(plan) {
197
+ if (!plan.exists)
198
+ return false;
199
+ let raw;
200
+ try {
201
+ raw = readFileSync(plan.target, "utf8");
202
+ }
203
+ catch {
204
+ return false;
205
+ }
206
+ let updated;
207
+ if (raw.includes(VIR_START) && raw.includes(VIR_END)) {
208
+ const start = raw.indexOf(VIR_START);
209
+ const end = raw.indexOf(VIR_END) + VIR_END.length;
210
+ updated = raw.slice(0, start) + plan.newBlock + raw.slice(end);
211
+ }
212
+ else {
213
+ const sep = raw.endsWith("\n") ? "\n" : "\n\n";
214
+ updated = raw + sep + plan.newBlock + "\n";
215
+ }
216
+ try {
217
+ writeFileSync(plan.target, updated);
218
+ return true;
219
+ }
220
+ catch {
221
+ return false;
222
+ }
223
+ }
224
+ export function globalClaudePath() {
225
+ return join(homedir(), ".claude", "CLAUDE.md");
226
+ }
227
+ export function projectClaudePath(projectSlug) {
228
+ return join(homedir(), "projects", projectSlug, "CLAUDE.md");
229
+ }
230
+ //# sourceMappingURL=updater.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updater.js","sourceRoot":"","sources":["../../src/claude/updater.ts"],"names":[],"mappings":"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"}