appydave-tools 0.83.0 → 0.85.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.
@@ -0,0 +1,354 @@
1
+ # Query Ecosystem — Skill Wrappers Plan
2
+
3
+ **Purpose**: Plan for creating thin skill wrappers around the four CLI tools in the query/gather ecosystem. Skills are shims — they call the Ruby CLI tools, not reimplementations.
4
+
5
+ **Status**: Planning
6
+ **Created**: 2026-04-05
7
+ **Depends on**: `query_apps` CLI tool (Phase 2 — complete, 76 tests passing)
8
+
9
+ ---
10
+
11
+ ## The Ecosystem
12
+
13
+ ```
14
+ QUERY TOOLS (find files) GATHER TOOL (assemble content)
15
+ ───────────────────────── ────────────────────────────
16
+ query_brain → brain files ─┐
17
+ query_omi → OMI transcripts ├──→ llm_context → LLM payload
18
+ query_apps → app files ─┘
19
+ ```
20
+
21
+ Each query tool outputs file paths (one per line). `llm_context` consumes those paths via `--stdin` and assembles content. The skills wrap each CLI tool to make them discoverable by Claude Code.
22
+
23
+ ---
24
+
25
+ ## Current State
26
+
27
+ | CLI Tool | Skill Exists? | Location | Notes |
28
+ |----------|--------------|----------|-------|
29
+ | `query_omi` | **Yes** — `omi-query` | `appydave-plugins/appydave/skills/omi-query/` | Gold standard pattern |
30
+ | `query_brain` | **No** | — | `focus` skill has `resolve_brain.py` doing subset of same work |
31
+ | `query_apps` | **No** | — | CLI tool just completed |
32
+ | `llm_context` | **No** | — | Downstream assembler, different trigger pattern |
33
+
34
+ ---
35
+
36
+ ## Skill 1: `brain-query`
37
+
38
+ **Plugin**: `appydave-plugins/appydave/skills/brain-query/SKILL.md`
39
+
40
+ **Triggers**: "search brains for X", "find brain about X", "which brains cover X", "brain files for X", "what's in the X brain", "active brains", "brains tagged X", "brains in category X"
41
+
42
+ **Relationship to `focus`**: `focus` is an orientation skill — it reads and summarises a brain's INDEX.md. `brain-query` is a file discovery skill — it returns file paths for piping into `llm_context`. Different intents, complementary tools.
43
+
44
+ **Relationship to `resolve_brain.py`**: The Python script in the `focus` skill does brain name resolution (exact → fuzzy → ambiguous). `query_brain` in Ruby does the same thing plus tag lookup, category search, alias resolution, and file path output. Long term, `focus` should call `query_brain --find --meta` instead of `resolve_brain.py`. But that's a separate refactor — don't block this skill on it.
45
+
46
+ ### CLI Interface (what the skill wraps)
47
+
48
+ ```bash
49
+ # Find by name/alias/tag (4-tier resolution: exact → alias → substring → tag)
50
+ query_brain --find anthropic-claude
51
+ query_brain --find claude # substring match
52
+ query_brain --find agent-systems # tag match
53
+
54
+ # Find by category
55
+ query_brain --category claude-core
56
+ query_brain --category agent-frameworks
57
+
58
+ # Active (high-activity) brains
59
+ query_brain --active
60
+
61
+ # Metadata mode — JSON with name, category, activity_level, status, tags, file_count
62
+ query_brain --find claude --meta
63
+ query_brain --active --meta
64
+
65
+ # Exclude INDEX.md from results
66
+ query_brain --find paperclip --files-only
67
+
68
+ # Combine with llm_context
69
+ query_brain --find paperclip | llm_context --stdin -f content --smart
70
+ query_brain --find paperclip --files-only | llm_context --stdin -f tree
71
+ ```
72
+
73
+ ### Skill Structure
74
+
75
+ ```
76
+ brain-query/
77
+ SKILL.md # Thin wrapper — triggers, CLI commands, common workflows
78
+ ```
79
+
80
+ No scripts, no Python, no reference files. Just the SKILL.md documenting how to call `query_brain`.
81
+
82
+ ### Skill Content Outline
83
+
84
+ 1. **Description/triggers** in YAML frontmatter
85
+ 2. **CLI Tool** section — show all flags with examples
86
+ 3. **Two Output Modes** — file paths (default) vs `--meta` (JSON)
87
+ 4. **Common Workflows**:
88
+ - "What brains cover topic X?" → `query_brain --find X --meta`
89
+ - "Load all files from brain X" → `query_brain --find X | llm_context --stdin -f content --smart`
90
+ - "Which brains are active?" → `query_brain --active --meta`
91
+ - "All brains in a category" → `query_brain --category claude-core --meta`
92
+ - "Get brain docs without INDEX.md" → `query_brain --find X --files-only`
93
+ 5. **Prerequisites** — `query_brain` installed (part of `appydave-tools`), `brains-index.json` must exist (built by brain-librarian's `build_brain_index.py`)
94
+ 6. **Related Skills** — `focus` (orientation), `brain-librarian` (curation), `brain-bridge` (write to brain)
95
+
96
+ ### Key Design Notes
97
+
98
+ - The skill should NOT reimplement resolution logic — just document the CLI flags
99
+ - `query_brain` already reads `brains-index.json` which is built by `build_brain_index.py` in the brain-librarian skill. The skill should mention this prerequisite
100
+ - The `--find` flag does 4-tier resolution internally (exact key → alias → substring → tag) — the skill doesn't need to know the tiers, just that `--find` handles fuzzy matching
101
+
102
+ ---
103
+
104
+ ## Skill 2: `app-query`
105
+
106
+ **Plugin**: `appydave-plugins/appydave/skills/app-query/SKILL.md`
107
+
108
+ **Triggers**: "get files from X app", "show me X's backend", "load X docs", "what apps have context", "list globs for X", "app files for X", "codebase of X", "understand X app", "X services", "X components", "X frontend", "X api"
109
+
110
+ ### CLI Interface (what the skill wraps)
111
+
112
+ ```bash
113
+ # Basic: app name + glob category
114
+ query_apps flihub --glob docs
115
+ query_apps angeleye --glob services
116
+
117
+ # Aliases resolve to multiple categories
118
+ query_apps flihub --glob backend # → services + routes
119
+ query_apps flihub --glob frontend # → components + views + styles
120
+
121
+ # Composites — pre-built bundles
122
+ query_apps flihub --glob understand # → context + docs + types + config
123
+ query_apps flihub --glob codebase # → services + routes + components + views
124
+
125
+ # Multiple globs (comma-separated)
126
+ query_apps flihub --glob docs,types,config
127
+
128
+ # Cross-app by pattern type
129
+ query_apps --pattern rvets --glob backend
130
+ query_apps --pattern nextjs --glob schema
131
+
132
+ # Discovery
133
+ query_apps flihub --list # available glob names for this app
134
+ query_apps --list-apps # all apps with context.globs.json
135
+
136
+ # Metadata mode
137
+ query_apps flihub --glob backend --meta
138
+
139
+ # Pipe to llm_context
140
+ query_apps flihub --glob understand | llm_context --stdin -f content --smart
141
+ query_apps angeleye --glob services | llm_context --stdin -f tree
142
+ ```
143
+
144
+ ### Skill Content Outline
145
+
146
+ 1. **Description/triggers** in YAML frontmatter
147
+ 2. **CLI Tool** section — all flags with examples
148
+ 3. **Glob Resolution** — direct name → alias → composite → substring fallback
149
+ 4. **Standard Vocabulary** — table of common category names and what they typically contain
150
+ 5. **Common Workflows**:
151
+ - "Help me understand app X" → `query_apps X --glob understand | llm_context --stdin -f content --smart`
152
+ - "What's the API look like?" → `query_apps X --glob api`
153
+ - "Show me all React code" → `query_apps X --glob react`
154
+ - "What globs are available?" → `query_apps X --list`
155
+ - "Which apps are queryable?" → `query_apps --list-apps`
156
+ - "All RVETS backend code" → `query_apps --pattern rvets --glob backend`
157
+ 6. **Prerequisites** — `query_apps` installed (appydave-tools), `context.globs.json` must exist in project root (generated by `system-context` skill)
158
+ 7. **Related Skills** — `system-context` (generates context.globs.json), `llm-context` (downstream assembler)
159
+
160
+ ### Key Design Notes
161
+
162
+ - The skill should explain the alias/composite concept so the agent knows "backend" is valid even though the globs file has "services" and "routes"
163
+ - Include the standard vocabulary table so agents can try common names without running `--list` first
164
+ - `context.globs.json` is generated by `system-context` — if it doesn't exist for an app, the skill should suggest running `/system-context` there first
165
+
166
+ ---
167
+
168
+ ## Skill 3: `llm-context`
169
+
170
+ **Plugin**: `appydave-plugins/appydave/skills/llm-context/SKILL.md`
171
+
172
+ **Triggers**: "gather files for context", "build llm context", "assemble context from files", "package files for llm", "get context from these files", "collect codebase", "gather code"
173
+
174
+ **Important distinction**: This is NOT a query tool. It's the downstream assembler. It takes file paths (from stdin or glob patterns) and produces formatted content for LLM consumption. The trigger pattern is different — it activates when the user wants to package/assemble, not when they want to search/find.
175
+
176
+ ### CLI Interface (what the skill wraps)
177
+
178
+ ```bash
179
+ # Direct glob patterns
180
+ llm_context -i 'lib/**/*.rb' -f content
181
+ llm_context -i 'src/**/*.ts' -e 'node_modules/**/*' -f tree,content
182
+
183
+ # Stdin mode — receive file paths from query tools
184
+ query_brain --find paperclip | llm_context --stdin -f content --smart
185
+ query_apps flihub --glob backend | llm_context --stdin -f content --smart
186
+ query_omi --brain til --days 7 | llm_context --stdin -f content --smart
187
+
188
+ # Output formats
189
+ llm_context -i 'lib/**/*.rb' -f tree # directory tree only
190
+ llm_context -i 'lib/**/*.rb' -f content # file contents with headers
191
+ llm_context -i 'lib/**/*.rb' -f json # structured JSON
192
+ llm_context -i 'lib/**/*.rb' -f files # file paths only
193
+ llm_context -i 'lib/**/*.rb' -f tree,content # multiple formats
194
+ llm_context -i 'lib/**/*.rb' -f aider -p 'Add logging' # aider command
195
+
196
+ # Output targets
197
+ llm_context -i 'lib/**/*.rb' -o clipboard # copy to clipboard (default)
198
+ llm_context -i 'lib/**/*.rb' -o temp # write to temp file, path on clipboard
199
+ llm_context -i 'lib/**/*.rb' -o context.txt # write to specific file
200
+ llm_context -i 'lib/**/*.rb' -o stdout # print to stdout
201
+ llm_context -i 'lib/**/*.rb' --smart # auto-route: clipboard if ≤100k tokens, else temp
202
+
203
+ # Other options
204
+ llm_context -i 'lib/**/*.rb' -l 50 # limit to first 50 lines per file
205
+ llm_context -i 'lib/**/*.rb' -t # show token estimate
206
+ llm_context -i 'lib/**/*.rb' -b /some/other/dir # set base directory
207
+ ```
208
+
209
+ ### Skill Content Outline
210
+
211
+ 1. **Description/triggers** in YAML frontmatter
212
+ 2. **Position in the chain** — this is the ASSEMBLER, not a query tool. Diagram showing query tools → llm_context → LLM
213
+ 3. **Two Input Modes**:
214
+ - **Pattern mode**: `-i` and `-e` glob patterns, run from any directory
215
+ - **Stdin mode**: `--stdin` receives file paths piped from query tools
216
+ 4. **Output Formats** — table explaining tree, content, json, files, aider
217
+ 5. **Output Targets** — clipboard, temp, file, stdout, `--smart` auto-routing
218
+ 6. **Common Workflows**:
219
+ - "Package this project's Ruby code" → `llm_context -i 'lib/**/*.rb' --smart`
220
+ - "Get a tree view of the project" → `llm_context -i '**/*' -e 'node_modules/**/*' -f tree`
221
+ - "Load brain files into context" → `query_brain --find X | llm_context --stdin -f content --smart`
222
+ - "Load app backend for review" → `query_apps X --glob backend | llm_context --stdin -f content --smart`
223
+ - "How big is this context?" → `llm_context -i 'src/**/*.ts' -t` (shows token estimate)
224
+ - "Save context to a file" → `llm_context -i 'lib/**/*.rb' -f tree,content -o context.txt`
225
+ 7. **The `--smart` flag** — auto-routes based on token count: clipboard if ≤100k tokens, temp file (path copied to clipboard) if larger. Mutually exclusive with explicit `-o clipboard` or `-o temp`
226
+ 8. **Prerequisites** — `llm_context` installed (part of `appydave-tools`)
227
+ 9. **Related Skills** — `brain-query`, `app-query`, `omi-query` (upstream query tools)
228
+
229
+ ### Key Design Notes
230
+
231
+ - The skill description must NOT overlap with query tool triggers. "Get files from FliHub" → `app-query`. "Package these files for an LLM" → `llm-context`
232
+ - The `--smart` flag is the recommended default for most workflows — mention it prominently
233
+ - The skill should show the full pipeline pattern: `query_X ... | llm_context --stdin -f content --smart`
234
+ - Token estimation (`-t`) is useful for checking context window fit before sending to an LLM
235
+
236
+ ---
237
+
238
+ ## Skill 4: `system-context` Update (not a new skill)
239
+
240
+ The existing `system-context` skill in `appydave-plugins/appydave/skills/system-context/SKILL.md` needs an addition to generate `context.globs.json` alongside `CONTEXT.md`.
241
+
242
+ **This is NOT a new skill** — it's a modification to the existing skill.
243
+
244
+ ### What to Add
245
+
246
+ After writing `CONTEXT.md`, the skill should also:
247
+
248
+ 1. **Detect project pattern** from project files:
249
+ - `package.json` with `workspaces` containing `shared/`, `server/`, `client/` → `rvets`
250
+ - `package.json` with `next` dependency → `nextjs`
251
+ - `Gemfile` or `*.gemspec` → `ruby-gem`
252
+ - `pyproject.toml` or `requirements.txt` → `python`
253
+ - Fallback: `unknown`
254
+
255
+ 2. **Scan directory structure** and map to standard vocabulary:
256
+
257
+ | Category | RVETS | Next.js | Ruby Gem | Python |
258
+ |----------|-------|---------|----------|--------|
259
+ | `docs` | `docs/**/*.md` | `docs/**/*.md` | `docs/**/*.md` | `docs/**/*.md` |
260
+ | `types` | `shared/**/*.ts` | `types/**/*.ts`, `lib/db/schema/**/*.ts` | — | — |
261
+ | `config` | `server/config.json`, `*.config.*` | `*.config.*`, `middleware.ts` | `config/**/*.json` | `*.yaml`, `*.toml` |
262
+ | `services` | `server/src/services/**/*.ts` | — | — | — |
263
+ | `routes` | `server/src/routes/**/*.ts` | `app/api/**/*.ts` | — | — |
264
+ | `components` | `client/src/components/**/*.tsx` | `components/**/*.tsx` | — | — |
265
+ | `views` | `client/src/views/**/*.tsx` | `app/**/*.tsx` (pages) | — | — |
266
+ | `tests` | `**/*.test.ts`, `**/*.spec.ts` | `**/*.test.*` | `spec/**/*_spec.rb` | `tests/**/*.py` |
267
+ | `styles` | `**/*.css` | `**/*.css` | — | — |
268
+ | `context` | `CLAUDE.md`, `CONTEXT.md`, `STEERING.md` | same | same | same |
269
+ | `lib` | — | — | `lib/**/*.rb` | `src/**/*.py` |
270
+ | `bin` | — | — | `bin/*` | — |
271
+ | `actions` | — | `lib/actions/**/*.ts` | — | — |
272
+ | `validation` | — | `lib/validation/**/*.ts` | — | — |
273
+ | `auth` | — | `lib/auth/**/*.ts` | — | — |
274
+ | `mockups` | `.mochaccino/designs/**/*.html` | — | — | — |
275
+ | `planning` | `docs/prd/**/*.md`, `docs/planning/**/*.md` | same | same | same |
276
+
277
+ 3. **Only include categories that actually match files** — don't generate a `mockups` entry if `.mochaccino/` doesn't exist
278
+
279
+ 4. **Generate standard aliases and composites**:
280
+
281
+ ```json
282
+ "aliases": {
283
+ "backend": ["services", "routes"],
284
+ "frontend": ["components", "views", "styles"],
285
+ "api": ["routes", "types"],
286
+ "data-layer": ["types", "schema"],
287
+ "react": ["components", "views"],
288
+ "ui": ["components", "views", "styles"]
289
+ },
290
+ "composites": {
291
+ "understand": ["context", "docs", "types", "config"],
292
+ "codebase": ["services", "routes", "components", "views"],
293
+ "full": ["*"]
294
+ }
295
+ ```
296
+
297
+ Only include alias/composite entries where the referenced categories exist in `globs`.
298
+
299
+ 5. **Write `context.globs.json`** to project root alongside `CONTEXT.md`
300
+
301
+ 6. **Add to CONTEXT.md sources** — include `context.globs.json` in the frontmatter sources list
302
+
303
+ ### Validation
304
+
305
+ After generating, the skill should verify:
306
+ - Every glob in `globs` matches at least one file on disk
307
+ - Every alias references only categories that exist in `globs`
308
+ - Every composite references only categories that exist in `globs`
309
+ - `pattern` field is set
310
+
311
+ ---
312
+
313
+ ## Implementation Order
314
+
315
+ | Phase | What | Where | Depends On |
316
+ |-------|------|-------|------------|
317
+ | 1 | `brain-query` skill | `appydave-plugins/appydave/skills/brain-query/` | `query_brain` CLI (exists) |
318
+ | 2 | `app-query` skill | `appydave-plugins/appydave/skills/app-query/` | `query_apps` CLI (exists) |
319
+ | 3 | `llm-context` skill | `appydave-plugins/appydave/skills/llm-context/` | `llm_context` CLI (exists) |
320
+ | 4 | `system-context` update | `appydave-plugins/appydave/skills/system-context/SKILL.md` | `app-query` skill (to validate output) |
321
+ | 5 | Generate `context.globs.json` for 12 existing apps | Run `/system-context` in each project | Phase 4 |
322
+
323
+ Phases 1-3 are independent — can be done in any order or in parallel. Phase 4 depends on the `context.globs.json` format being finalized (which it is — see `query-apps-design.md`). Phase 5 is a batch operation.
324
+
325
+ ---
326
+
327
+ ## Decisions Made
328
+
329
+ | Decision | Choice | Why |
330
+ |----------|--------|-----|
331
+ | Skills are thin shims | Yes | Ruby tools have robust test suites; reimplementing in Python/Bash loses coverage |
332
+ | No scripts in skill folders | Correct | Skills just document CLI flags — no `scripts/` subdirectory needed |
333
+ | `focus` stays separate from `brain-query` | Yes | Different intent: orientation vs file discovery |
334
+ | `resolve_brain.py` stays for now | Yes | Refactor to use `query_brain` later; don't block skills on it |
335
+ | `llm-context` has different triggers than query skills | Yes | It's a downstream assembler, not a query tool |
336
+ | All skills go in `appydave` plugin | Yes | These are appydave-tools wrappers, not project-specific |
337
+
338
+ ---
339
+
340
+ ## Related Files
341
+
342
+ | What | Where |
343
+ |------|-------|
344
+ | `query-apps-design.md` (Phase 2 design) | `docs/planning/query-apps-design.md` |
345
+ | `omi-query` skill (pattern to follow) | `appydave-plugins/appydave/skills/omi-query/SKILL.md` |
346
+ | `query_brain` CLI | `bin/query_brain.rb` |
347
+ | `query_apps` CLI | `bin/query_apps.rb` |
348
+ | `llm_context` CLI | `bin/llm_context.rb` |
349
+ | BrainQuery implementation | `lib/appydave/tools/brain_context/brain_finder.rb` |
350
+ | AppQuery implementation | `lib/appydave/tools/app_context/app_finder.rb` |
351
+ | FileCollector implementation | `lib/appydave/tools/llm_context/file_collector.rb` |
352
+ | Locations registry | `~/.config/appydave/locations.json` |
353
+ | Brain index | `~/dev/ad/brains/brains-index.json` |
354
+ | System-context skill | `appydave-plugins/appydave/skills/system-context/SKILL.md` |
@@ -0,0 +1,249 @@
1
+ # Spec: Fix `jump add` (and `update`/`remove`) Output Display
2
+
3
+ **Status**: Ready for implementation
4
+ **Filed by**: Claude Code session (brains repo), 2026-04-06
5
+ **Root cause session**: User ran `jump add --key awb --path ... --type app`, got "No locations found." — the add succeeded but the formatter showed a misleading empty-results message.
6
+
7
+ ---
8
+
9
+ ## Problem
10
+
11
+ `jump add`, `jump update`, and `jump remove` all return a **mutation result** shape:
12
+
13
+ ```ruby
14
+ { success: true, message: "Location 'awb' added successfully", location: location.to_h }
15
+ { success: true, message: "Location 'awb' removed successfully" }
16
+ ```
17
+
18
+ `TableFormatter#format` dispatches on result shape in this order:
19
+
20
+ ```ruby
21
+ def format
22
+ return format_error unless success?
23
+ return format_info if info_result?
24
+ return format_summary if summary_result?
25
+ return format_groups unless groups.empty?
26
+ return format_empty if results.empty? # ← mutation results land here
27
+ ...
28
+ format_results
29
+ end
30
+ ```
31
+
32
+ `results` reads `data[:results] || []`. Mutation results have no `:results` key, so `results` is always `[]`, so `format_empty` fires and prints **"No locations found."**
33
+
34
+ The operation worked. The data was saved. The message was misleading noise.
35
+
36
+ ---
37
+
38
+ ## Fix Required
39
+
40
+ ### `lib/appydave/tools/jump/formatters/table_formatter.rb`
41
+
42
+ Add a `mutation_result?` guard **before** the `format_empty` check, and a corresponding `format_mutation` method.
43
+
44
+ #### Change 1 — dispatch order
45
+
46
+ ```ruby
47
+ def format
48
+ return format_error unless success?
49
+ return format_info if info_result?
50
+ return format_summary if summary_result?
51
+ return format_groups unless groups.empty?
52
+ return format_mutation if mutation_result? # ← insert here
53
+ return format_empty if results.empty?
54
+ return format_definition_report if definition_report?
55
+ return format_count_report if count_report?
56
+ return format_category_report if category_report?
57
+
58
+ format_results
59
+ end
60
+ ```
61
+
62
+ #### Change 2 — new private methods
63
+
64
+ ```ruby
65
+ def mutation_result?
66
+ data.key?(:message) && (data.key?(:location) || data[:message].to_s.match?(/removed|updated|added/i))
67
+ end
68
+
69
+ def format_mutation
70
+ lines = [colorize(data[:message], :green)]
71
+ lines << colorize(data[:warning], :yellow) if data[:warning]
72
+ lines.join("\n")
73
+ end
74
+ ```
75
+
76
+ **No other files need changing.** `Commands::Add`, `Commands::Update`, and `Commands::Remove` already return the right shape.
77
+
78
+ ---
79
+
80
+ ## Unit Tests Required
81
+
82
+ Add these to `spec/appydave/tools/jump/formatters/table_formatter_spec.rb`.
83
+
84
+ Insert as a new context block after the existing `'when formatting empty results'` context (around line 31):
85
+
86
+ ```ruby
87
+ context 'when formatting a mutation result (add/update/remove)' do
88
+ context 'with a successful add' do
89
+ let(:data) do
90
+ {
91
+ success: true,
92
+ message: "Location 'awb' added successfully",
93
+ location: {
94
+ key: 'awb',
95
+ path: '/Users/davidcruwys/dev/ad/apps/awb',
96
+ jump: 'jawb',
97
+ type: 'app'
98
+ }
99
+ }
100
+ end
101
+
102
+ it 'displays the success message' do
103
+ output = formatter.format
104
+ expect(output).to include("Location 'awb' added successfully")
105
+ end
106
+
107
+ it 'does not display "No locations found"' do
108
+ output = formatter.format
109
+ expect(output).not_to include('No locations found')
110
+ end
111
+
112
+ it 'does not display a table header' do
113
+ output = formatter.format
114
+ expect(output).not_to include('KEY')
115
+ end
116
+ end
117
+
118
+ context 'with a successful add and a path warning' do
119
+ let(:data) do
120
+ {
121
+ success: true,
122
+ message: "Location 'awb' added successfully",
123
+ warning: "Warning: Path '/Users/davidcruwys/dev/ad/apps/awb' does not exist",
124
+ location: {
125
+ key: 'awb',
126
+ path: '/Users/davidcruwys/dev/ad/apps/awb'
127
+ }
128
+ }
129
+ end
130
+
131
+ it 'displays the success message' do
132
+ output = formatter.format
133
+ expect(output).to include("Location 'awb' added successfully")
134
+ end
135
+
136
+ it 'displays the path warning' do
137
+ output = formatter.format
138
+ expect(output).to include('does not exist')
139
+ end
140
+ end
141
+
142
+ context 'with a successful update' do
143
+ let(:data) do
144
+ {
145
+ success: true,
146
+ message: "Location 'awb' updated successfully",
147
+ location: { key: 'awb', path: '/Users/davidcruwys/dev/ad/apps/awb' }
148
+ }
149
+ end
150
+
151
+ it 'displays the update success message' do
152
+ output = formatter.format
153
+ expect(output).to include("Location 'awb' updated successfully")
154
+ end
155
+
156
+ it 'does not display "No locations found"' do
157
+ output = formatter.format
158
+ expect(output).not_to include('No locations found')
159
+ end
160
+ end
161
+
162
+ context 'with a successful remove' do
163
+ let(:data) do
164
+ {
165
+ success: true,
166
+ message: "Location 'awb' removed successfully"
167
+ # remove has no :location key
168
+ }
169
+ end
170
+
171
+ it 'displays the remove success message' do
172
+ output = formatter.format
173
+ expect(output).to include("Location 'awb' removed successfully")
174
+ end
175
+
176
+ it 'does not display "No locations found"' do
177
+ output = formatter.format
178
+ expect(output).not_to include('No locations found')
179
+ end
180
+ end
181
+ end
182
+ ```
183
+
184
+ ---
185
+
186
+ ## CLI Integration Test
187
+
188
+ The existing `cli_spec.rb` tests auto-regeneration but never asserts what the user actually *sees*. Add these to
189
+ `spec/appydave/tools/jump/cli_spec.rb` inside the existing `'when adding a location'` context (around line 43):
190
+
191
+ ```ruby
192
+ it 'shows success message after add' do
193
+ cli.run(['add', '--key', 'new-project', '--path', '~/dev/new-path'])
194
+
195
+ expect(output.string).to include("Location 'new-project' added successfully")
196
+ end
197
+
198
+ it 'does not show "No locations found" after add' do
199
+ cli.run(['add', '--key', 'new-project', '--path', '~/dev/new-path'])
200
+
201
+ expect(output.string).not_to include('No locations found')
202
+ end
203
+
204
+ it 'shows warning when path does not exist' do
205
+ no_path_validator = TestPathValidator.new(valid_paths: [])
206
+ bad_cli = described_class.new(config: config, path_validator: no_path_validator, output: output)
207
+ bad_cli.run(['add', '--key', 'ghost', '--path', '~/dev/ghost'])
208
+
209
+ expect(output.string).to include('does not exist')
210
+ expect(output.string).to include("Location 'ghost' added successfully")
211
+ end
212
+ ```
213
+
214
+ ---
215
+
216
+ ## After This Is Done — Skill Update
217
+
218
+ Once the fix is merged and released, the jump skill at
219
+ `~/.claude/skills/jump/SKILL.md` needs its **"Modifying Locations"** section rewritten.
220
+
221
+ **Current (wrong):**
222
+ ```
223
+ "Add a new jump alias for my-project"
224
+ - Action:
225
+ 1. Add new entry to ~/.config/appydave/locations.json
226
+ 2. Run /Users/.../jump.rb generate aliases > ~/.oh-my-zsh/custom/aliases-jump.zsh
227
+ ```
228
+
229
+ **Correct (after fix):**
230
+ ```
231
+ "Add a new jump alias for my-project"
232
+ - Action:
233
+ jump add --key my-project --path ~/dev/my-project [--type X] [--brand X]
234
+ # aliases auto-regenerate if aliases-output-path is set in settings.json
235
+ # use --no-generate to skip
236
+ ```
237
+
238
+ The skill should use the tool. Direct JSON editing is a workaround, not a workflow.
239
+
240
+ ---
241
+
242
+ ## Summary of Files to Touch
243
+
244
+ | File | Change |
245
+ |------|--------|
246
+ | `lib/.../formatters/table_formatter.rb` | Add `mutation_result?` + `format_mutation`, insert guard in `format` dispatch |
247
+ | `spec/.../formatters/table_formatter_spec.rb` | Add 4 new contexts covering add/update/remove/warning |
248
+ | `spec/.../jump/cli_spec.rb` | Add 3 assertions inside existing `'when adding a location'` context |
249
+ | `~/.claude/skills/jump/SKILL.md` | Update after gem is released (done in brains repo, not here) |
data/exe/query_apps 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_apps.rb', __dir__)