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 +4 -4
- data/CHANGELOG.md +19 -0
- data/CLAUDE.md +88 -1
- data/bin/query_brain.rb +62 -0
- data/bin/query_omi.rb +69 -0
- data/docs/guides/query-tools-handover.md +58 -0
- data/docs/guides/query-tools.md +255 -0
- data/exe/query_brain +7 -0
- data/exe/query_omi +7 -0
- data/lib/appydave/tools/brain_context/brain_finder.rb +236 -0
- data/lib/appydave/tools/brain_context/omi_finder.rb +175 -0
- data/lib/appydave/tools/brain_context/options.rb +57 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +4 -0
- data/package.json +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '09f48a7490c924291dff958e3df8ba53c28c2dae599cae0ab8581ac3e4ad3ee2'
|
|
4
|
+
data.tar.gz: f1e55198dd31bb3b2b61b61d4f929fc3bda799ecd4cedb96e5f810c994edae4a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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/`
|
data/bin/query_brain.rb
ADDED
|
@@ -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
data/exe/query_omi
ADDED
|
@@ -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
|
data/lib/appydave/tools.rb
CHANGED
|
@@ -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
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.
|
|
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-
|
|
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
|