appydave-tools 0.79.0 → 0.81.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec01667ec40e9b0b051f2e834800cd458c736f27107df033ddebeaf7de413823
4
- data.tar.gz: 6627f68aefaf25f40cd7d55ba87e8470bd8e5c5a99146927424149b84a31b3b6
3
+ metadata.gz: '09f48a7490c924291dff958e3df8ba53c28c2dae599cae0ab8581ac3e4ad3ee2'
4
+ data.tar.gz: f1e55198dd31bb3b2b61b61d4f929fc3bda799ecd4cedb96e5f810c994edae4a
5
5
  SHA512:
6
- metadata.gz: 8e745ed73fc0f812a6275edb827df2901e5eb0b41c060b4ac68fdc27c9eff750b60b84d2244e21f2b6afb2cda98eacacc3895b6c82502673162202cb31075ddb
7
- data.tar.gz: 1814d3250ecf2aebf1ccb9cf50e117fc7b9f220334c263b5b1677336848db1cd7e37f9b684df2903aba96ecc69bbad96368c1acc87fbd6addfed4086eeffe00a
6
+ metadata.gz: 9d4f2bc474af95d2ff538d5360d9ec659b559f155b611c9b7c13f9c475118311a1fc99a40b1f3fc6655b20bee568a3c5fcb4466f709eef948cd400d4efee6709
7
+ data.tar.gz: 84af7abbcc284e85742f1db64737622e8c929b57815988908a2aba716d970adcfd0f52e29f7a432fb2d544fd8be950fd27254d7ec61fdf705802c126b34e6d84
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ # [0.80.0](https://github.com/appydave/appydave-tools/compare/v0.79.0...v0.80.0) (2026-04-03)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * resolve RSpec linting violations in test suite ([0bb643c](https://github.com/appydave/appydave-tools/commit/0bb643c374934227ab6831f87b4b8e7fd6d2444d))
7
+
8
+
9
+ ### Features
10
+
11
+ * add brain_context CLI tool for querying brains + OMI ([7498e25](https://github.com/appydave/appydave-tools/commit/7498e25d8fa3094ee97ddfb67980d414f70efbab))
12
+
13
+ # [0.79.0](https://github.com/appydave/appydave-tools/compare/v0.78.0...v0.79.0) (2026-04-03)
14
+
15
+
16
+ ### Features
17
+
18
+ * add --stdin support to llm_context for piping file paths ([8890cfe](https://github.com/appydave/appydave-tools/commit/8890cfebb80cfda81b679da3e52a185bbb1c9141))
19
+
1
20
  # [0.78.0](https://github.com/appydave/appydave-tools/compare/v0.77.7...v0.78.0) (2026-04-03)
2
21
 
3
22
 
data/CLAUDE.md CHANGED
@@ -170,6 +170,8 @@ bin/console # Interactive Ruby console for experimentation
170
170
  | **DAM (Digital Asset Management)** | `vat` | Multi-tenant video project storage orchestration | ✅ ACTIVE |
171
171
  | **Configuration** | `ad_config` | Manage JSON configs (channels, paths, sequences) | ✅ ACTIVE |
172
172
  | **Move Images** | N/A (dev only) | Organize video asset images | ✅ ACTIVE |
173
+ | **Brain Query** | `query_brain` | Query brain knowledge index by name/tag/category | ✅ ACTIVE |
174
+ | **OMI Query** | `query_omi` | Query OMI transcripts by routing/brain/activity | ✅ ACTIVE |
173
175
  | **Prompt Tools** | `prompt_tools` | OpenAI Completion API wrapper | ⚠️ DEPRECATED API |
174
176
  | **YouTube Automation** | `youtube_automation` | Prompt sequence runner | ⚠️ INTERNAL USE |
175
177
 
@@ -434,7 +436,92 @@ bin/move_images.rb -f b40 thumb b40
434
436
 
435
437
  **Note:** This is a development/workflow tool specific to video project organization. Not installed as a gem command.
436
438
 
437
- #### 8. Bank Reconciliation (`bin/bank_reconciliation.rb`) 🗄️ DEPRECATED
439
+ #### 8. Brain Query (`bin/query_brain`)
440
+ Query the brain knowledge index to locate relevant brain files for LLM context pipelines:
441
+
442
+ ```bash
443
+ # Find brains by name, alias, or tag
444
+ query_brain --find paperclip
445
+ query_brain --find bmad --find method
446
+
447
+ # Find all brains in a category
448
+ query_brain --category tools
449
+ query_brain --category workflows
450
+
451
+ # Find high-activity brains
452
+ query_brain --active
453
+
454
+ # Exclude INDEX.md from results
455
+ query_brain --find paperclip --files-only
456
+
457
+ # Return JSON metadata instead of file paths
458
+ query_brain --active --meta
459
+ query_brain --find bmad --meta
460
+ ```
461
+
462
+ **Options:**
463
+ - `--find TERM` — find by name, alias, or tag (repeatable)
464
+ - `--category CAT` — all brains in a category (repeatable)
465
+ - `--active` — all high-activity brains
466
+ - `--files-only` — exclude INDEX.md from results
467
+ - `--meta` — return JSON metadata instead of file paths
468
+
469
+ **Pipeline examples:**
470
+ ```bash
471
+ # Feed brain files directly into LLM context
472
+ query_brain --find paperclip | xargs llm_context.rb -i
473
+
474
+ # Metadata pipeline (direct to LLM — no llm_context needed)
475
+ query_brain --active --meta
476
+ ```
477
+
478
+ #### 9. OMI Query (`bin/query_omi`)
479
+ Query OMI wearable transcript sessions by routing type, brain reference, or activity:
480
+
481
+ ```bash
482
+ # Find sessions mentioning a brain
483
+ query_omi --brain paperclip
484
+ query_omi --brain bmad
485
+
486
+ # Filter by routing type
487
+ query_omi --routing brain-update
488
+ query_omi --routing til
489
+ query_omi --routing todo-item
490
+ query_omi --routing personal
491
+ query_omi --routing archive
492
+
493
+ # Filter by activity
494
+ query_omi --activity planning
495
+ query_omi --activity meeting
496
+ query_omi --activity debugging
497
+
498
+ # Limit by recency
499
+ query_omi --days 7
500
+ query_omi --brain paperclip --days 30 --limit 10
501
+
502
+ # Return JSON metadata instead of file paths
503
+ query_omi --brain paperclip --meta
504
+ query_omi --routing brain-update --limit 5 --meta
505
+ ```
506
+
507
+ **Options:**
508
+ - `--brain NAME` — sessions mentioning a brain
509
+ - `--routing TYPE` — brain-update, til, todo-item, personal, archive
510
+ - `--activity ACT` — planning, meeting, debugging, etc.
511
+ - `--days N` — last N days
512
+ - `--limit N` — most recent N results
513
+ - `--meta` — return JSON metadata instead of file paths
514
+
515
+ **Pipeline examples:**
516
+ ```bash
517
+ # Feed recent brain-update sessions into LLM context
518
+ query_omi --routing brain-update --limit 5 | xargs llm_context.rb -i
519
+
520
+ # Metadata pipeline (direct to LLM — no llm_context needed)
521
+ query_omi --brain paperclip --meta
522
+ ```
523
+
524
+ #### 10. Bank Reconciliation (`bin/bank_reconciliation.rb`) 🗄️ DEPRECATED
438
525
  Bank transaction reconciliation tool - **DEPRECATED, DO NOT USE**
439
526
 
440
527
  **WARNING:** This tool contains deprecated code and should not be used for new work. Code has been moved to `lib/appydave/tools/deprecated/bank_reconciliation/`
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'appydave/tools'
6
+
7
+ options = Appydave::Tools::BrainContextOptions.new
8
+
9
+ def setup_options(options)
10
+ OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
11
+ opts.banner = 'Usage: query_brain [options]'
12
+
13
+ opts.on('--find TERM', 'Find brain by name, tag, or alias (repeatable)') do |term|
14
+ options.brain_names << term
15
+ end
16
+
17
+ opts.on('--category CAT', 'Find all brains in category (repeatable)') do |cat|
18
+ options.categories << cat
19
+ end
20
+
21
+ opts.on('--active', 'Return all high-activity brains') do
22
+ options.active = true
23
+ end
24
+
25
+ opts.on('--meta', 'Return metadata as JSON instead of file paths') do
26
+ options.meta = true
27
+ end
28
+
29
+ opts.on('--files-only', 'Exclude INDEX.md, only include content files') do
30
+ options.include_index = false
31
+ end
32
+
33
+ opts.on('-d', '--debug [MODE]', %w[none info params debug],
34
+ 'Debug output level') do |level|
35
+ options.debug_level = level || 'info'
36
+ end
37
+
38
+ opts.on('-v', '--version', 'Show version') do
39
+ puts "query_brain v#{Appydave::Tools::VERSION}"
40
+ exit 0
41
+ end
42
+
43
+ opts.on('-h', '--help', 'Show this message') do
44
+ puts opts
45
+ exit 0
46
+ end
47
+ end.parse!
48
+ end
49
+
50
+ setup_options(options)
51
+
52
+ # Query
53
+ finder = Appydave::Tools::BrainQuery.new(options)
54
+
55
+ if options.meta
56
+ require 'json'
57
+ puts JSON.pretty_generate(finder.find_meta)
58
+ else
59
+ finder.find.each { |p| puts p }
60
+ end
61
+
62
+ exit 0
data/bin/query_omi.rb ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'appydave/tools'
6
+
7
+ options = Appydave::Tools::BrainContextOptions.new
8
+ options.omi = true
9
+
10
+ def setup_options(options)
11
+ OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
12
+ opts.banner = 'Usage: query_omi [options]'
13
+
14
+ opts.on('--brain NAME', 'Find OMI sessions mentioning this brain') do |name|
15
+ options.brain_names << name
16
+ end
17
+
18
+ opts.on('--routing ROUTING',
19
+ 'Filter by routing (brain-update, todo-item, personal, til, archive; pipe-delimited)') do |routing|
20
+ options.omi_routings.concat(routing.split('|').map(&:strip))
21
+ end
22
+
23
+ opts.on('--activity ACTIVITY',
24
+ 'Filter by activity (planning, reviewing, learning, debugging, etc.; pipe-delimited)') do |activity|
25
+ options.omi_activities.concat(activity.split('|').map(&:strip))
26
+ end
27
+
28
+ opts.on('--days N', Integer, 'Include sessions from the last N days') do |n|
29
+ options.days = n
30
+ end
31
+
32
+ opts.on('--meta', 'Return metadata as JSON instead of file paths') do
33
+ options.meta = true
34
+ end
35
+
36
+ opts.on('--limit N', Integer, 'Return at most N results (most recent)') do |n|
37
+ options.limit = n
38
+ end
39
+
40
+ opts.on('-d', '--debug [MODE]', %w[none info params debug],
41
+ 'Debug output level') do |level|
42
+ options.debug_level = level || 'info'
43
+ end
44
+
45
+ opts.on('-v', '--version', 'Show version') do
46
+ puts "query_omi v#{Appydave::Tools::VERSION}"
47
+ exit 0
48
+ end
49
+
50
+ opts.on('-h', '--help', 'Show this message') do
51
+ puts opts
52
+ exit 0
53
+ end
54
+ end.parse!
55
+ end
56
+
57
+ setup_options(options)
58
+
59
+ # Query
60
+ finder = Appydave::Tools::OmiQuery.new(options)
61
+
62
+ if options.meta
63
+ require 'json'
64
+ puts JSON.pretty_generate(finder.find_meta)
65
+ else
66
+ finder.find.each { |p| puts p }
67
+ end
68
+
69
+ exit 0
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: Handover — Query Tools Session (2026-04-04)
3
+ type: handover
4
+ ---
5
+
6
+ # Handover — Query Tools Session (2026-04-04)
7
+
8
+ ## What Was Built
9
+
10
+ Three tools in `appydave-tools` now work as a unified query pipeline:
11
+
12
+ - **`query_brain`** — queries the brains knowledge index
13
+ - **`query_omi`** — queries OMI conversation transcripts by frontmatter
14
+ - **`llm_context`** — assembles file content for LLM consumption (pre-existing, unchanged)
15
+
16
+ Full documentation: `/Users/davidcruwys/dev/ad/appydave-tools/docs/guides/query-tools.md`
17
+
18
+ ---
19
+
20
+ ## What Changed Today
21
+
22
+ ### API Simplification
23
+ Both query tools had their APIs simplified — fewer flags, unified search:
24
+
25
+ **query_brain**: `--find` replaces separate `--brain` + `--tag`. `--active` replaces broken `--activity-min`. Removed: `--status` (dead code), `--tag`, `--activity-min`.
26
+
27
+ **query_omi**: Added `--days N` and `--limit N`. Removed: `--signal`, `--date-from`, `--date-to`, `--enriched-only` (enriched is now default).
28
+
29
+ ### New --meta Mode
30
+ Both tools now support `--meta` — returns JSON metadata instead of file paths. This is a **separate pipeline** from llm_context, used when you want summaries without loading full file content.
31
+
32
+ ### Bug Fixed
33
+ `query_brain --active` was silently returning nothing. Fixed — now correctly returns all 29 high-activity brains.
34
+
35
+ ---
36
+
37
+ ## Relevant Skills That May Want This Context
38
+
39
+ ### OMI Skill (`/Users/davidcruwys/dev/ad/appydave-plugins/appydave/skills/omi/`)
40
+ The `query_omi` tool is the programmatic interface to the same OMI data that the OMI skill works with. The frontmatter fields (`routing`, `matched_brains`, `extraction_summary`, etc.) that `query_omi` filters on are produced by the OMI enrichment pipeline. The skill should know:
41
+ - `--routing brain-update` surfaces the backlog of conversations that should feed into brains
42
+ - `--meta` gives extraction summaries without loading full transcripts
43
+
44
+ ### Brain Librarian (wherever it lives)
45
+ The `query_brain` tool consumes `brains-index.json` built by brain-librarian. The librarian should know:
46
+ - Index must be at `~/dev/ad/brains/audit/brains-index.json`
47
+ - Fields used: `activity_level`, `tags`, `category`, `status`, `file_count`, `index_path`, `files[]`
48
+ - `files[]` array is currently empty for most brains — only `INDEX.md` is returned. Populating this would make `query_brain --find X` return actual content files, not just index files.
49
+
50
+ ### LLM Context Skill (if one exists)
51
+ The pipeline pattern is: `query_brain --find X | xargs llm_context.rb -i -f content`. The query tools are the **selector layer** feeding into llm_context as the **assembler layer**.
52
+
53
+ ---
54
+
55
+ ## What Is Not Done Yet (Intentionally Deferred)
56
+
57
+ - **Randomizer** — a discovery tool that pre-validates queries against result counts and randomly surfaces one. Concept is clear, not yet implemented. Would live as `/Users/davidcruwys/dev/ad/appydave-tools/bin/random_context.rb`.
58
+ - **Markdown format for --meta** — decided against. JSON is sufficient for LLM consumption and adding a format flag adds complexity back that was just removed.
@@ -0,0 +1,255 @@
1
+ ---
2
+ title: Query Tools — brain, OMI, and LLM Context Pipeline
3
+ category: guides
4
+ tools: [query_brain, query_omi, llm_context]
5
+ status: active
6
+ created: 2026-04-04
7
+ ---
8
+
9
+ # Query Tools — Brain, OMI, and LLM Context Pipeline
10
+
11
+ ## Purpose
12
+
13
+ Three tools work together as a **file selection and context assembly pipeline** for AI-assisted workflows:
14
+
15
+ | Tool | Role | Output |
16
+ |------|------|--------|
17
+ | `query_brain` | Select brain knowledge files | File paths or JSON metadata |
18
+ | `query_omi` | Select OMI conversation files | File paths or JSON metadata |
19
+ | `llm_context` | Assemble file content for LLM | Tree listing or full content |
20
+
21
+ The tools are **composable**: query tools select files, `llm_context` loads them.
22
+
23
+ ---
24
+
25
+ ## What Is Being Queried
26
+
27
+ ### query_brain
28
+ Reads `~/dev/ad/brains/audit/brains-index.json` — a pre-built index of all brain folders. Does **not** scan brain files at query time. The index contains per-brain metadata: name, category, activity_level, tags, status, file_count.
29
+
30
+ Brain folders live at `~/dev/ad/brains/<brain-name>/`. Each has an `INDEX.md` and optional content files.
31
+
32
+ ### query_omi
33
+ Scans `~/dev/raw-intake/omi/*.md` and reads YAML frontmatter from each file. Enriched files (processed by Gemini extraction) have rich frontmatter. Raw files (unprocessed transcripts) have minimal frontmatter. Default behaviour: **enriched files only**.
34
+
35
+ ### llm_context
36
+ Takes file paths (from stdin or arguments) and assembles content for LLM consumption. Two formats: `tree` (file listing) and `content` (file listing + full file content).
37
+
38
+ ---
39
+
40
+ ## query_brain API
41
+
42
+ ```bash
43
+ query_brain [options]
44
+ ```
45
+
46
+ | Flag | Description |
47
+ |------|-------------|
48
+ | `--find TERM` | Find brain by name, alias, or tag — unified search (repeatable) |
49
+ | `--category CAT` | All brains in a category (repeatable) |
50
+ | `--active` | All high-activity brains |
51
+ | `--files-only` | Exclude INDEX.md, return content files only |
52
+ | `--meta` | Return JSON metadata instead of file paths |
53
+
54
+ ### Categories (as of 2026-04)
55
+ `claude-core`, `agent-frameworks`, `agent-systems`, `infrastructure`, `content-production`, `brand-strategy`, `knowledge-capture`, `client-infrastructure`, `lifestyle`, `private`
56
+
57
+ ### Activity Levels
58
+ `high` (29 brains), `medium`, `low`, `none`
59
+
60
+ ### Search Resolution Order (--find)
61
+ 1. Exact brain name match
62
+ 2. Alias match (from alias_index in brains-index.json)
63
+ 3. Substring match on brain name
64
+ 4. Tag match (falls through to tag_index)
65
+
66
+ ### Examples
67
+ ```bash
68
+ # Find a specific brain (name, partial name, or tag all work)
69
+ query_brain --find paperclip
70
+ query_brain --find agentic-engineering # searches by tag
71
+
72
+ # Browse a category
73
+ query_brain --category agent-systems
74
+ query_brain --category agent-systems --category agent-frameworks
75
+
76
+ # All high-activity brains
77
+ query_brain --active
78
+
79
+ # Metadata instead of file paths
80
+ query_brain --active --meta
81
+ query_brain --find paperclip --meta
82
+ ```
83
+
84
+ ### --meta Output Structure
85
+ ```json
86
+ [
87
+ {
88
+ "name": "paperclip",
89
+ "category": "agent-systems",
90
+ "activity_level": "high",
91
+ "status": "active",
92
+ "tags": ["paperclip", "multi-agent", "orchestration"],
93
+ "file_count": 6
94
+ }
95
+ ]
96
+ ```
97
+
98
+ ---
99
+
100
+ ## query_omi API
101
+
102
+ ```bash
103
+ query_omi [options]
104
+ ```
105
+
106
+ | Flag | Description |
107
+ |------|-------------|
108
+ | `--brain NAME` | Sessions where this brain appears in matched_brains |
109
+ | `--routing TYPE` | Filter by routing tag (pipe-delimited values supported) |
110
+ | `--activity ACT` | Filter by activity type (pipe-delimited values supported) |
111
+ | `--days N` | Sessions from the last N days (based on extracted_at) |
112
+ | `--limit N` | Return at most N results, most recent first |
113
+ | `--meta` | Return JSON metadata instead of file paths |
114
+
115
+ ### Routing Values
116
+ `brain-update`, `til`, `todo-item`, `personal`, `archive`
117
+
118
+ ### Activity Values
119
+ `planning`, `meeting`, `learning`, `debugging`, `reviewing`, `building`, `teaching`
120
+
121
+ ### Enriched Frontmatter Structure
122
+ ```yaml
123
+ signal: work
124
+ extraction_summary: "One-sentence summary of the conversation"
125
+ extracted_at: 2026-04-03
126
+ matched_brains: [agentic-os, anthropic-claude, paperclip]
127
+ activity: meeting|planning|learning
128
+ routing: brain-update
129
+ people_present: [david, nick]
130
+ people_mentioned: [boris]
131
+ entities_tools: [Claude, Gemini, Archon]
132
+ entities_projects: [Agent Workflow Builder]
133
+ entities_concepts: [token economics, RAG, prompt caching]
134
+ overflow_topics: [Thai chocolate, mask hardware]
135
+ ```
136
+
137
+ ### Examples
138
+ ```bash
139
+ # Sessions flagged for brain updates (your backlog)
140
+ query_omi --routing brain-update
141
+ query_omi --routing brain-update --limit 10
142
+
143
+ # Recent planning sessions
144
+ query_omi --activity planning --days 7
145
+
146
+ # Everything related to a specific brain
147
+ query_omi --brain paperclip
148
+
149
+ # Metadata view — summaries without loading full files
150
+ query_omi --routing brain-update --limit 5 --meta
151
+ query_omi --brain paperclip --days 14 --meta
152
+ ```
153
+
154
+ ### --meta Output Structure
155
+ ```json
156
+ [
157
+ {
158
+ "file": "2026-04-03-1705-group-debates.md",
159
+ "extracted_at": "2026-04-03",
160
+ "extraction_summary": "A group discusses LLM usage and agentic workflows...",
161
+ "matched_brains": ["agentic-os", "anthropic-claude", "paperclip"],
162
+ "activity": "meeting|planning",
163
+ "routing": "brain-update",
164
+ "entities_tools": ["Claude", "Gemini", "Archon"],
165
+ "entities_projects": ["Archon"],
166
+ "entities_concepts": ["token economics", "RAG"]
167
+ }
168
+ ]
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Pipeline Patterns
174
+
175
+ ### Content pipeline — load files into LLM context
176
+ ```bash
177
+ # Brain knowledge files → LLM
178
+ query_brain --find paperclip | xargs llm_context.rb -i -f content
179
+
180
+ # OMI sessions → LLM
181
+ query_omi --brain paperclip --limit 5 | xargs llm_context.rb -i -f content
182
+
183
+ # Mixed: brain + OMI for a topic
184
+ { query_brain --find paperclip; query_omi --brain paperclip --limit 5; } | sort -u | xargs llm_context.rb -i -f content
185
+ ```
186
+
187
+ ### Metadata pipeline — summaries direct to LLM (no llm_context needed)
188
+ ```bash
189
+ # What's in my brain-update backlog?
190
+ query_omi --routing brain-update --meta
191
+
192
+ # What are all my active brains?
193
+ query_brain --active --meta
194
+
195
+ # What OMI sessions touched paperclip recently?
196
+ query_omi --brain paperclip --days 14 --meta
197
+ ```
198
+
199
+ ### Discovery pattern — bounded queries for randomizer
200
+ ```bash
201
+ # These return 1–20 results — good for random surfacing
202
+ query_brain --find paperclip # 1 brain
203
+ query_brain --category agent-systems # ~10 brains
204
+ query_omi --brain paperclip # ~10 sessions
205
+ query_omi --routing til --limit 5 # 5 things-I-learned
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Two Different Data Shapes
211
+
212
+ **Brain files** are **curated knowledge** — processed, structured, human-maintained. High signal density. Use when you want what is known and confirmed.
213
+
214
+ **OMI files** are **conversation transcripts** — raw or AI-enriched recordings of spoken work sessions. Variable quality. Use when you want recent context, planning discussions, or to find what should be promoted into a brain.
215
+
216
+ These are intentionally separate query tools. Do not combine them by default.
217
+
218
+ ---
219
+
220
+ ## Key Files
221
+
222
+ | File | Purpose |
223
+ |------|---------|
224
+ | `/Users/davidcruwys/dev/ad/brains/audit/brains-index.json` | Brain index — built by brain-librarian |
225
+ | `/Users/davidcruwys/dev/raw-intake/omi/*.md` | OMI conversation transcripts |
226
+ | `/Users/davidcruwys/dev/ad/appydave-tools/bin/query_brain.rb` | Brain query CLI |
227
+ | `/Users/davidcruwys/dev/ad/appydave-tools/bin/query_omi.rb` | OMI query CLI |
228
+ | `/Users/davidcruwys/dev/ad/appydave-tools/bin/llm_context.rb` | File content assembler |
229
+ | `/Users/davidcruwys/dev/ad/appydave-tools/exe/query_brain` | Gem-installed wrapper |
230
+ | `/Users/davidcruwys/dev/ad/appydave-tools/exe/query_omi` | Gem-installed wrapper |
231
+ | `/Users/davidcruwys/dev/ad/appydave-tools/lib/appydave/tools/brain_context/brain_finder.rb` | BrainQuery implementation |
232
+ | `/Users/davidcruwys/dev/ad/appydave-tools/lib/appydave/tools/brain_context/omi_finder.rb` | OmiQuery implementation |
233
+ | `/Users/davidcruwys/dev/ad/appydave-tools/lib/appydave/tools/brain_context/options.rb` | Shared options struct |
234
+
235
+ ---
236
+
237
+ ## Rebuilding the Brain Index
238
+
239
+ The `brains-index.json` is not auto-updated. When brain folders change, rebuild it:
240
+
241
+ ```bash
242
+ python /Users/davidcruwys/dev/ad/brains/.claude/skills/brain-librarian/scripts/build_brain_index.py build --all /Users/davidcruwys/dev/ad/brains
243
+ ```
244
+
245
+ OMI files are queried directly — no index rebuild needed.
246
+
247
+ ---
248
+
249
+ ## Design Decisions
250
+
251
+ - **query_brain and query_omi are separate tools** — brains are confirmed knowledge, OMI is conversation. Different trust levels.
252
+ - **`--meta` bypasses llm_context** — metadata is already summarised; loading full file content adds tokens without value for discovery use cases.
253
+ - **enriched_only defaults to true** in query_omi — raw transcripts have no frontmatter to filter on; they're noise in most query contexts.
254
+ - **`--find` unifies name/alias/tag** — users shouldn't need to know whether something is a name or a tag.
255
+ - **`--active` standalone** — returns all 29 high-activity brains without needing to enumerate categories.
data/exe/query_brain ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'appydave/tools'
6
+
7
+ load File.expand_path('../bin/query_brain.rb', __dir__)
data/exe/query_omi ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'appydave/tools'
6
+
7
+ load File.expand_path('../bin/query_omi.rb', __dir__)
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Appydave
6
+ module Tools
7
+ # Queries brains-index.json to find brain files
8
+ class BrainQuery
9
+ def initialize(options)
10
+ @options = options
11
+ @index = nil
12
+ @alias_map = nil
13
+ end
14
+
15
+ def find
16
+ return [] unless @options.brain_query?
17
+
18
+ load_index!
19
+ paths = []
20
+
21
+ # Active brains: return all high-activity brains
22
+ paths.concat(find_active) if @options.active
23
+
24
+ # Handle brain name/tag/alias queries (unified)
25
+ @options.brain_names.each do |name|
26
+ paths.concat(find_by_name(name))
27
+ end
28
+
29
+ # Handle category queries
30
+ @options.categories.each do |category|
31
+ paths.concat(find_by_category(category))
32
+ end
33
+
34
+ paths.uniq.sort
35
+ end
36
+
37
+ def find_meta
38
+ return [] unless @options.brain_query?
39
+
40
+ load_index!
41
+ entries = []
42
+
43
+ entries.concat(active_meta_entries) if @options.active
44
+ @options.brain_names.each { |name| entries.concat(meta_entries_by_name(name)) }
45
+ @options.categories.each { |cat| entries.concat(meta_entries_by_category(cat)) }
46
+
47
+ entries.uniq { |e| e['name'] }.sort_by { |e| e['name'] }
48
+ end
49
+
50
+ private
51
+
52
+ def load_index!
53
+ return if @index
54
+
55
+ index_path = @options.brains_index_path
56
+ unless File.exist?(index_path)
57
+ raise "brains-index.json not found at #{index_path}. Run: python ~/dev/ad/brains/.claude/skills/brain-librarian/scripts/build_brain_index.py build --all ~/dev/ad/brains"
58
+ end
59
+
60
+ @index = JSON.parse(File.read(index_path))
61
+ build_alias_map!
62
+ end
63
+
64
+ def build_alias_map!
65
+ @alias_map = {}
66
+ @index['alias_index']&.each { |alias_name, brain_name| @alias_map[alias_name.downcase] = brain_name }
67
+ end
68
+
69
+ def find_by_name(name)
70
+ load_index!
71
+
72
+ # Step 1: Exact match on brain key
73
+ brain_entry = find_brain_in_index(name)
74
+ return brain_entries_to_paths([brain_entry]) if brain_entry
75
+
76
+ # Step 2: Alias match
77
+ if @alias_map && @alias_map[name.downcase]
78
+ brain_name = @alias_map[name.downcase]
79
+ brain_entry = find_brain_in_index(brain_name)
80
+ return brain_entries_to_paths([brain_entry]) if brain_entry
81
+ end
82
+
83
+ # Step 3: Substring match on brain key (case-insensitive)
84
+ matches = []
85
+ @index['categories'].each_value do |category_data|
86
+ category_data['brains'].each do |brain_name, brain_data|
87
+ matches << brain_data if brain_name.downcase.include?(name.downcase)
88
+ end
89
+ end
90
+ return brain_entries_to_paths(matches) if matches.any?
91
+
92
+ # Step 4: Tag match (so --find works for tags too)
93
+ find_by_tag(name)
94
+ end
95
+
96
+ def find_by_tag(tag)
97
+ load_index!
98
+ tag = tag.downcase.gsub('_', '-')
99
+
100
+ brain_names = @index['tag_index']&.[](tag) || []
101
+ brain_entries = []
102
+
103
+ brain_names.each do |brain_name|
104
+ entry = find_brain_in_index(brain_name)
105
+ brain_entries << entry if entry
106
+ end
107
+
108
+ brain_entries_to_paths(brain_entries)
109
+ end
110
+
111
+ def find_by_category(category)
112
+ load_index!
113
+ category_data = @index['categories'][category] || @index['categories'][category.downcase]
114
+
115
+ return [] unless category_data
116
+
117
+ brain_entries = category_data['brains'].values
118
+ brain_entries_to_paths(brain_entries)
119
+ end
120
+
121
+ def find_brain_in_index(brain_name)
122
+ @index['categories'].each_value do |category_data|
123
+ return category_data['brains'][brain_name] if category_data['brains'][brain_name]
124
+ end
125
+ nil
126
+ end
127
+
128
+ def brain_entries_to_paths(brain_entries)
129
+ paths = []
130
+
131
+ brain_entries.each do |entry|
132
+ next unless entry
133
+
134
+ # Add files from files[] array
135
+ entry['files']&.each do |file|
136
+ # File entry is relative to the brain directory
137
+ brain_name = extract_brain_name(entry)
138
+ full_path = File.join(@options.brains_root, brain_name, file)
139
+ paths << full_path if File.exist?(full_path)
140
+ end
141
+
142
+ # Add INDEX.md if requested
143
+ if @options.include_index
144
+ index_path = File.join(@options.brains_root, entry['index_path'])
145
+ paths << index_path if File.exist?(index_path)
146
+ end
147
+ end
148
+
149
+ paths
150
+ end
151
+
152
+ def extract_brain_name(entry)
153
+ # index_path is like "brain-name/INDEX.md"
154
+ entry['index_path'].split('/')[0]
155
+ end
156
+
157
+ def find_active
158
+ brain_entries = []
159
+ @index['categories'].each_value do |category_data|
160
+ category_data['brains'].each_value do |brain_data|
161
+ brain_entries << brain_data if brain_data['activity_level'] == 'high'
162
+ end
163
+ end
164
+ brain_entries_to_paths(brain_entries)
165
+ end
166
+
167
+ # --- find_meta helpers ---
168
+
169
+ def active_meta_entries
170
+ result = []
171
+ @index['categories'].each do |category_name, category_data|
172
+ category_data['brains'].each do |brain_name, brain_data|
173
+ result << build_meta_entry(brain_name, category_name, brain_data) if brain_data['activity_level'] == 'high'
174
+ end
175
+ end
176
+ result
177
+ end
178
+
179
+ def meta_entries_by_category(category)
180
+ category_key = @index['categories'].keys.find { |k| k.casecmp(category).zero? }
181
+ return [] unless category_key
182
+
183
+ @index['categories'][category_key]['brains'].map do |brain_name, brain_data|
184
+ build_meta_entry(brain_name, category_key, brain_data)
185
+ end
186
+ end
187
+
188
+ def meta_entries_by_name(name)
189
+ # Exact match
190
+ brain_name, category, data = find_brain_with_context(name)
191
+ return [build_meta_entry(brain_name, category, data)] if data
192
+
193
+ # Alias match
194
+ if @alias_map&.[](name.downcase)
195
+ brain_name, category, data = find_brain_with_context(@alias_map[name.downcase])
196
+ return [build_meta_entry(brain_name, category, data)] if data
197
+ end
198
+
199
+ # Substring match
200
+ matches = []
201
+ @index['categories'].each do |category_name, category_data|
202
+ category_data['brains'].each do |bname, bdata|
203
+ matches << build_meta_entry(bname, category_name, bdata) if bname.downcase.include?(name.downcase)
204
+ end
205
+ end
206
+ return matches if matches.any?
207
+
208
+ # Tag match
209
+ tag = name.downcase.gsub('_', '-')
210
+ (@index['tag_index']&.[](tag) || []).filter_map do |bname|
211
+ brain_name, category, data = find_brain_with_context(bname)
212
+ build_meta_entry(brain_name, category, data) if data
213
+ end
214
+ end
215
+
216
+ def find_brain_with_context(brain_name)
217
+ @index['categories'].each do |category_name, category_data|
218
+ data = category_data['brains'][brain_name]
219
+ return [brain_name, category_name, data] if data
220
+ end
221
+ [nil, nil, nil]
222
+ end
223
+
224
+ def build_meta_entry(name, category, data)
225
+ {
226
+ 'name' => name,
227
+ 'category' => category,
228
+ 'activity_level' => data['activity_level'],
229
+ 'status' => data['status'],
230
+ 'tags' => data['tags'] || [],
231
+ 'file_count' => data['file_count'] || 0
232
+ }
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Appydave
6
+ module Tools
7
+ # Queries OMI transcript directory by enriched frontmatter
8
+ class OmiQuery
9
+ def initialize(options)
10
+ @options = options
11
+ end
12
+
13
+ def find
14
+ return [] unless @options.omi_query?
15
+
16
+ resolve_days!
17
+ paths = each_matching.map { |file_path, _frontmatter| file_path }
18
+ paths = paths.last(@options.limit) if @options.limit
19
+ paths
20
+ end
21
+
22
+ def find_meta
23
+ return [] unless @options.omi_query?
24
+
25
+ resolve_days!
26
+ entries = each_matching.map do |file_path, frontmatter|
27
+ build_omi_meta(file_path, frontmatter)
28
+ end
29
+ entries = entries.last(@options.limit) if @options.limit
30
+ entries
31
+ end
32
+
33
+ private
34
+
35
+ def each_matching
36
+ results = []
37
+ Dir.glob(File.join(@options.omi_dir, '*.md')).sort.each do |file_path|
38
+ frontmatter = extract_frontmatter(file_path)
39
+ next unless frontmatter
40
+ next unless passes_filters?(frontmatter)
41
+
42
+ results << [file_path, frontmatter]
43
+ end
44
+ results
45
+ end
46
+
47
+ def passes_filters?(frontmatter)
48
+ return false if @options.enriched_only && (!frontmatter['signal'] || !frontmatter['extraction_summary'])
49
+ return false unless routing_matches?(frontmatter)
50
+ return false unless activity_matches?(frontmatter)
51
+ return false unless date_matches?(frontmatter)
52
+ return false unless brain_matches?(frontmatter)
53
+
54
+ true
55
+ end
56
+
57
+ def build_omi_meta(file_path, frontmatter)
58
+ {
59
+ 'file' => File.basename(file_path),
60
+ 'extracted_at' => frontmatter['extracted_at'],
61
+ 'extraction_summary' => frontmatter['extraction_summary'],
62
+ 'matched_brains' => frontmatter['matched_brains'] || [],
63
+ 'activity' => frontmatter['activity'],
64
+ 'routing' => frontmatter['routing'],
65
+ 'entities_tools' => frontmatter['entities_tools'] || [],
66
+ 'entities_projects' => frontmatter['entities_projects'] || [],
67
+ 'entities_concepts' => frontmatter['entities_concepts'] || []
68
+ }
69
+ end
70
+
71
+ def extract_frontmatter(file_path)
72
+ content = File.read(file_path, encoding: 'utf-8')
73
+ lines = content.split("\n")
74
+
75
+ return nil unless lines[0] == '---'
76
+
77
+ frontmatter = {}
78
+ i = 1
79
+ while i < lines.length
80
+ line = lines[i]
81
+ break if line == '---'
82
+
83
+ # Parse YAML-like: key: value
84
+ if line.match?(/^[a-z_]+:/)
85
+ key, value = parse_yaml_line(line)
86
+ frontmatter[key] = value
87
+ end
88
+
89
+ i += 1
90
+ end
91
+
92
+ frontmatter.empty? ? nil : frontmatter
93
+ end
94
+
95
+ def parse_yaml_line(line)
96
+ # Handle simple cases: key: value, key: [a, b, c]
97
+ match = line.match(/^([a-z_]+):\s*(.*)$/)
98
+ return [nil, nil] unless match
99
+
100
+ key = match[1]
101
+ value_str = match[2].strip
102
+
103
+ # Parse array [a, b, c]
104
+ if value_str.start_with?('[') && value_str.end_with?(']')
105
+ array_content = value_str[1..-2]
106
+ value = array_content.split(',').map { |v| v.strip.gsub(/^["']|["']$/, '') }
107
+ # Parse quoted string
108
+ elsif (value_str.start_with?('"') && value_str.end_with?('"')) ||
109
+ (value_str.start_with?("'") && value_str.end_with?("'"))
110
+ value = value_str[1..-2]
111
+ # Parse date (YYYY-MM-DD) or other values as-is
112
+ else
113
+ value = value_str
114
+ end
115
+
116
+ [key, value]
117
+ end
118
+
119
+ def resolve_days!
120
+ return unless @options.days
121
+
122
+ @options.date_from = (Date.today - @options.days).to_s
123
+ end
124
+
125
+ def routing_matches?(frontmatter)
126
+ return true if @options.omi_routings.empty?
127
+
128
+ routing = frontmatter['routing']
129
+ return false unless routing
130
+
131
+ # routing can be pipe-delimited
132
+ routings = routing.split('|').map(&:strip)
133
+ routings.any? { |r| @options.omi_routings.include?(r) }
134
+ end
135
+
136
+ def activity_matches?(frontmatter)
137
+ return true if @options.omi_activities.empty?
138
+
139
+ activity = frontmatter['activity']
140
+ return false unless activity
141
+
142
+ # activity can be pipe-delimited
143
+ activities = activity.split('|').map(&:strip)
144
+ activities.any? { |a| @options.omi_activities.include?(a) }
145
+ end
146
+
147
+ def date_matches?(frontmatter)
148
+ extracted_at = frontmatter['extracted_at']
149
+ return true if extracted_at.nil? # No date field = include
150
+
151
+ begin
152
+ date = Date.parse(extracted_at)
153
+ rescue StandardError
154
+ return true # Can't parse = include
155
+ end
156
+
157
+ from_ok = @options.date_from.nil? || date >= Date.parse(@options.date_from)
158
+ to_ok = @options.date_to.nil? || date <= Date.parse(@options.date_to)
159
+
160
+ from_ok && to_ok
161
+ end
162
+
163
+ def brain_matches?(frontmatter)
164
+ return true if @options.brain_names.empty?
165
+
166
+ matched_brains = frontmatter['matched_brains']
167
+ return false unless matched_brains
168
+
169
+ # matched_brains is an array
170
+ matched_brains_list = matched_brains.is_a?(Array) ? matched_brains : [matched_brains]
171
+ matched_brains_list.any? { |brain| @options.brain_names.include?(brain) }
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ # Options struct for brain/OMI query tools
6
+ class BrainContextOptions
7
+ attr_accessor :brain_names, :categories, :active, :meta,
8
+ :omi, :omi_routings, :omi_activities,
9
+ :date_from, :date_to, :enriched_only, :days, :limit,
10
+ :include_index, :output_targets, :formats, :line_limit,
11
+ :debug_level, :dry_run, :tokens, :base_dir, :omi_dir
12
+
13
+ def initialize
14
+ @brain_names = []
15
+ @categories = []
16
+ @active = false
17
+ @meta = false
18
+
19
+ @omi = false
20
+ @omi_routings = []
21
+ @omi_activities = []
22
+ @date_from = nil
23
+ @date_to = nil
24
+ @enriched_only = true
25
+ @days = nil
26
+ @limit = nil
27
+
28
+ @include_index = true
29
+ @output_targets = ['clipboard'] # default to clipboard
30
+ @formats = ['content']
31
+ @line_limit = nil
32
+ @debug_level = 'none'
33
+ @dry_run = false
34
+ @tokens = false
35
+ @base_dir = Dir.pwd
36
+
37
+ @omi_dir = File.expand_path('~/dev/raw-intake/omi')
38
+ end
39
+
40
+ def brains_root
41
+ @brains_root ||= File.expand_path('~/dev/ad/brains')
42
+ end
43
+
44
+ def brains_index_path
45
+ File.join(brains_root, 'audit', 'brains-index.json')
46
+ end
47
+
48
+ def brain_query?
49
+ brain_names.any? || categories.any? || active
50
+ end
51
+
52
+ def omi_query?
53
+ omi
54
+ end
55
+ end
56
+ end
57
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.79.0'
5
+ VERSION = '0.81.0'
6
6
  end
7
7
  end
@@ -37,6 +37,10 @@ require 'appydave/tools/llm_context/options'
37
37
  require 'appydave/tools/llm_context/file_collector'
38
38
  require 'appydave/tools/llm_context/output_handler'
39
39
 
40
+ require 'appydave/tools/brain_context/options'
41
+ require 'appydave/tools/brain_context/brain_finder'
42
+ require 'appydave/tools/brain_context/omi_finder'
43
+
40
44
  require 'appydave/tools/configuration/openai'
41
45
  require 'appydave/tools/configuration/configurable'
42
46
  require 'appydave/tools/configuration/config'
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.79.0",
3
+ "version": "0.81.0",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydave-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.79.0
4
+ version: 0.81.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-03 00:00:00.000000000 Z
11
+ date: 2026-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -190,6 +190,8 @@ executables:
190
190
  - jump
191
191
  - llm_context
192
192
  - prompt_tools
193
+ - query_brain
194
+ - query_omi
193
195
  - subtitle_processor
194
196
  - youtube_automation
195
197
  - youtube_manager
@@ -229,6 +231,8 @@ files:
229
231
  - bin/llm_context.rb
230
232
  - bin/move_images.rb
231
233
  - bin/prompt_tools.rb
234
+ - bin/query_brain.rb
235
+ - bin/query_omi.rb
232
236
  - bin/setup
233
237
  - bin/subtitle_manager-old.rb
234
238
  - bin/subtitle_manager.rb
@@ -281,6 +285,8 @@ files:
281
285
  - docs/guides/platforms/windows/README.md
282
286
  - docs/guides/platforms/windows/dam-testing-plan-windows-powershell.md
283
287
  - docs/guides/platforms/windows/installation.md
288
+ - docs/guides/query-tools-handover.md
289
+ - docs/guides/query-tools.md
284
290
  - docs/guides/tools/bank-reconciliation.md
285
291
  - docs/guides/tools/cli-actions.md
286
292
  - docs/guides/tools/configuration.md
@@ -338,12 +344,17 @@ files:
338
344
  - exe/jump
339
345
  - exe/llm_context
340
346
  - exe/prompt_tools
347
+ - exe/query_brain
348
+ - exe/query_omi
341
349
  - exe/subtitle_processor
342
350
  - exe/youtube_automation
343
351
  - exe/youtube_manager
344
352
  - exe/zsh_history
345
353
  - images.log
346
354
  - lib/appydave/tools.rb
355
+ - lib/appydave/tools/brain_context/brain_finder.rb
356
+ - lib/appydave/tools/brain_context/omi_finder.rb
357
+ - lib/appydave/tools/brain_context/options.rb
347
358
  - lib/appydave/tools/cli_actions/_doc.md
348
359
  - lib/appydave/tools/cli_actions/base_action.rb
349
360
  - lib/appydave/tools/cli_actions/get_video_action.rb