@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.
- package/CLAUDE.md +149 -0
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/claude/updater.js +230 -0
- package/dist/claude/updater.js.map +1 -0
- package/dist/cli.js +779 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +82 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/launchd.js +93 -0
- package/dist/daemon/launchd.js.map +1 -0
- package/dist/dedupe/detector.js +159 -0
- package/dist/dedupe/detector.js.map +1 -0
- package/dist/dedupe/merger.js +116 -0
- package/dist/dedupe/merger.js.map +1 -0
- package/dist/lint/linter.js +224 -0
- package/dist/lint/linter.js.map +1 -0
- package/dist/pipeline/distiller.js +208 -0
- package/dist/pipeline/distiller.js.map +1 -0
- package/dist/pipeline/filter.js +28 -0
- package/dist/pipeline/filter.js.map +1 -0
- package/dist/pipeline/parser.js +109 -0
- package/dist/pipeline/parser.js.map +1 -0
- package/dist/pipeline/run.js +312 -0
- package/dist/pipeline/run.js.map +1 -0
- package/dist/pipeline/scanner.js +47 -0
- package/dist/pipeline/scanner.js.map +1 -0
- package/dist/pipeline/scrubber.js +51 -0
- package/dist/pipeline/scrubber.js.map +1 -0
- package/dist/pipeline/summarizer.js +162 -0
- package/dist/pipeline/summarizer.js.map +1 -0
- package/dist/pipeline/types.js +2 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/pipeline/writer.js +195 -0
- package/dist/pipeline/writer.js.map +1 -0
- package/dist/search/embedder.js +93 -0
- package/dist/search/embedder.js.map +1 -0
- package/dist/search/retriever.js +212 -0
- package/dist/search/retriever.js.map +1 -0
- package/dist/search/synthesizer.js +26 -0
- package/dist/search/synthesizer.js.map +1 -0
- package/dist/state/db.js +309 -0
- package/dist/state/db.js.map +1 -0
- package/dist/ui/display.js +148 -0
- package/dist/ui/display.js.map +1 -0
- package/package.json +50 -0
- package/src/claude/updater.ts +273 -0
- package/src/cli.ts +953 -0
- package/src/config.ts +89 -0
- package/src/daemon/launchd.ts +115 -0
- package/src/dedupe/detector.ts +197 -0
- package/src/dedupe/merger.ts +172 -0
- package/src/lint/linter.ts +286 -0
- package/src/pipeline/distiller.ts +280 -0
- package/src/pipeline/filter.ts +43 -0
- package/src/pipeline/parser.ts +118 -0
- package/src/pipeline/run.ts +378 -0
- package/src/pipeline/scanner.ts +51 -0
- package/src/pipeline/scrubber.ts +55 -0
- package/src/pipeline/summarizer.ts +204 -0
- package/src/pipeline/types.ts +41 -0
- package/src/pipeline/writer.ts +242 -0
- package/src/search/embedder.ts +88 -0
- package/src/search/retriever.ts +255 -0
- package/src/search/synthesizer.ts +45 -0
- package/src/state/db.ts +451 -0
- package/src/ui/display.ts +184 -0
- package/tsconfig.json +23 -0
- 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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"}
|