@wave-ai-os/wave-cli 0.1.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,928 @@
1
+ # @wave/cli
2
+
3
+ Machine-first CLI used by a Node LangGraph agent to execute semantic Wave operations against the Rails API.
4
+
5
+ See also:
6
+
7
+ - `./.env.example`
8
+ - `./docs/ENVIRONMENT_CONTRACT_CHECKLIST.md`
9
+
10
+ ## Principles
11
+
12
+ - `stdout` emits exactly one JSON envelope for machine commands by default.
13
+ - `stderr` is for debug/diagnostic logs only.
14
+ - Help output is human-readable and exits `0`; it does not emit a JSON error envelope.
15
+ - `--readable` enables human formatting where a command supports it; `--json` forces the stable machine envelope where both flags are present.
16
+ - No prompts, colors, or implicit interactive UX in this phase.
17
+ - JWT forwarding is centralized.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @wave/cli
23
+ ```
24
+
25
+ Or run locally from source:
26
+
27
+ ```bash
28
+ npm run --silent dev -- tasks list --project-id 123 --organization-id 42 --base-url https://api.example.com --token token
29
+ ```
30
+
31
+ Direct source smoke checks can use the same Node entrypoint as the dev script:
32
+
33
+ ```bash
34
+ node --disable-warning=DEP0205 --import tsx src/index.ts capabilities tools --json
35
+ ```
36
+
37
+ Secure stdin auth/context examples:
38
+
39
+ ```bash
40
+ printf '%s' "token-value" | wave --token-stdin --base-url https://api.example.com --organization-id 42 tasks list --page 1 --per 10
41
+
42
+ printf '%s' '{"token":"token-value","baseUrl":"https://api.example.com","organizationId":"42","agentName":"atlas"}' \
43
+ | wave --auth-json-stdin tasks list --page 1 --per 10
44
+ ```
45
+
46
+ ## Benchmark Wave vs Obsidian CLI
47
+
48
+ Run the side-by-side benchmark harness:
49
+
50
+ ```bash
51
+ npm run benchmark:cli
52
+ ```
53
+
54
+ Useful options:
55
+
56
+ ```bash
57
+ npm run benchmark:cli -- --runs 20 --warmup 3 --query "meeting notes"
58
+ npm run benchmark:cli -- --obsidian-bin /Applications/Obsidian.app/Contents/MacOS/obsidian-cli
59
+ npm run benchmark:cli -- --json-out /tmp/cli-benchmark.json
60
+ ```
61
+
62
+ Notes:
63
+
64
+ - Wave scenarios expect JSON envelopes for domain operations.
65
+ - Obsidian scenarios require the CLI to be registered and the Obsidian app available.
66
+
67
+ ## Runtime Contract
68
+
69
+ Required (from flags, stdin, or env):
70
+
71
+ - `WAVE_API_TOKEN`
72
+ - `WAVE_API_BASE_URL`
73
+ - `WAVE_ORGANIZATION_ID`
74
+
75
+ Optional env:
76
+
77
+ - `WAVE_AGENT_NAME`
78
+ - `WAVE_AGENT_RUN_ID`
79
+ - `WAVE_REQUEST_ID`
80
+ - `WAVE_CONFIG_PATH`
81
+ - `WAVE_TIMEOUT_MS`
82
+ - `WAVE_DEBUG`
83
+ - `WAVE_OPENAPI_PATH` (local contract file, e.g. `openapi/wave-cli.yaml`)
84
+ - `WAVE_OPENAPI_URL` (hosted contract file)
85
+ - `WAVE_OPENAPI_VERSION` (pinned spec version/hash)
86
+
87
+ Deterministic precedence:
88
+
89
+ - explicit flags (`--token` / `--jwt`, `--base-url`, `--organization-id`, and tracing/runtime flags)
90
+ - stdin context (`--token-stdin` or `--auth-json-stdin`)
91
+ - env vars (`WAVE_*`)
92
+ - config file (`--config`, `WAVE_CONFIG_PATH`, or `~/.wave/config.json` when present)
93
+ - failure with structured JSON error
94
+
95
+ Config files are JSON objects using the same keys accepted by `--auth-json-stdin`, for example:
96
+
97
+ ```json
98
+ {
99
+ "token": "token-value",
100
+ "baseUrl": "https://api.example.com",
101
+ "organizationId": "42",
102
+ "agentName": "atlas"
103
+ }
104
+ ```
105
+
106
+ Entity targeting follows the same explicit-first rule: command flags and positional args, then stdin JSON where supported, then environment/config context for organization and runtime. If a target remains unresolved, the CLI returns a structured JSON error with `suggestedAction`, `suggestedCommands`, or both where the command can provide actionable guidance.
107
+
108
+ ## Discoverability and Diagnostics
109
+
110
+ Wave exposes native discovery commands without undocumented spec/docs flags:
111
+
112
+ ```bash
113
+ wave help
114
+ wave capabilities list
115
+ wave capabilities list --readable
116
+ wave capabilities tools
117
+ wave capabilities tools mail
118
+ wave broadcast open
119
+ wave broadcast schema
120
+ wave broadcast examples
121
+ wave support counts
122
+ wave support tickets --status open
123
+ wave support knowledge-articles --collection-id 123
124
+ wave recognition list --page 1 --per 20
125
+ wave recognition top-value --period-start 2026-01-01
126
+ wave whiteboards list --page 1 --per 20
127
+ wave whiteboards show --slug product-planning
128
+ wave time-tracking entries --member-id 67 --start-date 2026-01-01 --end-date 2026-01-31
129
+ wave time-tracking report --start-date 2026-01-01 --end-date 2026-01-31
130
+ wave planner events --starts-at 2026-01-01T00:00:00Z --ends-at 2026-01-07T00:00:00Z
131
+ wave planner public-booking-link --slug intro-call --starts-at 2026-01-01T00:00:00Z --ends-at 2026-01-07T00:00:00Z --timezone America/Phoenix
132
+ wave api ls
133
+ wave graphql fields
134
+ wave osmd schema
135
+ wave osmd examples
136
+ wave markdown-tree schema
137
+ wave doctor
138
+ wave doctor --readable
139
+ ```
140
+
141
+ Namespace taxonomy:
142
+
143
+ - Primary: `osmd`, `broadcast`, `support`, `recognition`, `whiteboards`, `time-tracking`, `planner`, `tasks`, `projects`, `rocks`, `meetings`, `members`, `teams`, `issues`, `foundation`
144
+ - Advanced: generated CRUD resources, `markdown-tree`, content/meta/resource-specific command groups
145
+ - Aliases / shortcuts: `find`, `open`, `ls`, `cat`, `tree`, `phantom` help alias
146
+
147
+ `wave capabilities tools` exposes the shared recent-tool support registry for Broadcast, Support, Recognition, Whiteboards, Time Tracking, Planner, and Mail. Backend keys come from WAVE-02 evidence and route/deep-link templates come from WAVE-03 evidence. WAVE-05 enables only the read-only Broadcast commands `list`, `workspace`, `community`, and `open` for safe counts, metrics, provider/service status, and channel status metadata. WAVE-06 enables only read-only Support counts, inbox/ticket metadata, labels, settings, knowledge metadata, and route commands. WAVE-07 enables only read-only Recognition commands for list/show metadata, top value, point balance, and route metadata. WAVE-08 enables only read-only Whiteboards commands for list/show metadata and route metadata. WAVE-09 enables only read-only Time Tracking commands for timesheet, timer, entry, project, report, and route metadata. WAVE-10 enables only read-only Planner safe state, event, booking-link, preference, public booking-link, and route metadata. Mail remains registry-only, and Broadcast/Support/Recognition/Whiteboards/Time Tracking/Planner write, reply, send, publish, delete, destroy, media, scene, export, import, start, stop, cancel, upsert, payroll, billing, event write, preference update, booking-link write, and booking-reservation surfaces are not enabled.
148
+
149
+ `wave doctor` is read-only. It checks auth token resolution, organization ID resolution, base URL resolution, GraphQL reachability, API JSON response health, optional OpenAPI contract configuration, and CLI runtime integrity.
150
+
151
+ ## Commands
152
+
153
+ Examples:
154
+
155
+ ```bash
156
+ wave tasks list --project-id 123
157
+ wave tasks create --project-id 123 --title "Fix auth bug"
158
+ wave tasks update --id 99 --status in_progress
159
+ wave tasks destroy --id 99
160
+ wave projects list
161
+ wave projects show --id 123
162
+ wave rocks update-status --id 55 --status on_track
163
+ wave meetings show --id 77
164
+ wave members show --id 44
165
+ wave members meta-profile --member-id 44 --json
166
+ wave skills list --json
167
+ wave skills show --id 123 --json
168
+ wave meetings notes --id 77 --content "Decisions captured"
169
+ wave issues create --issue-group-id 321 --name "Auth issue" --issue-type short_term
170
+ wave foundation strategic-plans show
171
+ wave foundation strategic-objectives update --data-json '{"strategic_objective":{"summary":"Updated"}}'
172
+ wave foundation annual-objectives create --data-json '{"annual_objective":{"strategic_objective_id":"7","name":"Annual Goal"}}'
173
+ wave foundation quarterly-objectives create --data-json '{"quarterly_objective":{"strategic_objective_id":"7","annual_objective_id":"8","name":"Quarterly Goal"}}'
174
+ wave lists create --data-json '{"name":"Weekly List"}'
175
+ wave list-items create --data-json '{"list_id":"123","summary":"Follow up"}'
176
+ wave todos create --data-json '{"todo_group_id":"55","name":"Send update","status":"open"}'
177
+ wave knowledge create --data-json '{"content":{"name":"Runbook","content_type":"process","status":"draft","member_id":"67"}}'
178
+ wave content create-member --target-member-id 67 --name "1:1 Note" --body "Strong progress"
179
+ wave content create-manager --actor-member-id 67 --target-member-id 68 --name "Manager Note" --body "Coaching plan"
180
+ wave content create-team --actor-member-id 67 --team-id 9 --name "Team Note" --body "Team context"
181
+ wave content create-project --actor-member-id 67 --project-id 80 --name "Project Note" --body "Project context"
182
+ wave content create-customer --actor-member-id 67 --customer-id 30 --name "Customer Note" --body "Customer context"
183
+ wave content create-contact --actor-member-id 67 --contact-id 31 --name "Contact Note" --body "Contact context"
184
+ wave content list --contentable-type Member --contentable-id 67 --member-id 67 --page 1 --per 20
185
+ wave content show --id 1001
186
+ wave content update --id 1001 --name "Updated Note" --body "Updated body" --status published
187
+ wave content destroy --id 1001
188
+ wave markdown-tree root --tree-view
189
+ wave markdown-tree resolve --tool-key projects --node-key tasks --parent-id 123
190
+ wave markdown-tree children --tool-key directory --node-key members --record-id 67
191
+ wave markdown-tree subtree --tool-key knowledge --node-key knowledge --content-type processes --depth 2
192
+ wave markdown-tree schema
193
+ wave osmd tree
194
+ wave osmd search "Wave Tools" --scope combined --limit 10
195
+ wave osmd lint --scope combined
196
+ wave osmd lint --scope combined --json
197
+ wave osmd ingest text "Customer interview notes" --source-type pasted_text --slug customer-interviews --parent-ref "projects/Wave Tools" --summary "Customer interview source summary" --run-lint
198
+ wave osmd ingest file ./source.md --source-type uploaded_markdown --slug source-document --parent-ref "projects/Wave Tools" --summary "Source document summary"
199
+ wave osmd ingest url "https://example.com/source" --source-type url --slug example-source --parent-ref "projects/Wave Tools" --summary "Example source summary"
200
+ wave osmd ingest transcript --file ./meeting-transcript.md --source-type meeting_transcript --slug team-meeting-transcript --parent-ref "projects/Wave Tools" --summary "Meeting transcript summary"
201
+ wave osmd read "projects/Wave Tools"
202
+ wave osmd children "projects/Wave Tools"
203
+ wave osmd status "projects/Wave Tools/.agent/notes.md" --parent-ref "projects/Wave Tools"
204
+ wave osmd agent init "projects/Wave Tools"
205
+ wave osmd agent status "projects/Wave Tools" notes.md
206
+ wave osmd agent read "projects/Wave Tools" notes.md
207
+ wave osmd agent create "projects/Wave Tools" notes.md --content "Working notes"
208
+ wave osmd agent update "projects/Wave Tools" customer-interviews.md --content "# Customer Interviews"
209
+ wave osmd agent children "projects/Wave Tools"
210
+ wave osmd wiki read index.md
211
+ wave osmd wiki read log.md
212
+ wave osmd wiki index-upsert ".agent/customer-interviews.md" --parent-ref "projects/Wave Tools" --summary "Customer interview source summary"
213
+ wave osmd wiki append-log --content "Observed durable event"
214
+ wave osmd examples
215
+ wave osmd schema
216
+ wave capabilities list
217
+ wave api ls
218
+ wave graphql fields
219
+ wave doctor
220
+ wave find "Ted"
221
+ wave open "Ted Martinez"
222
+ wave ls --path "directory/Members/Ted Martinez"
223
+ wave cat --path "directory/Members/Ted Martinez/Member Profile"
224
+ wave tree --path "knowledge/Processes" --depth 2
225
+ wave pulse update --id 12 --data-json '{"status":"on_track"}'
226
+ wave subtasks list --task-id 123 --page 1 --per 20
227
+ wave milestones list --rock-id 234 --page 1 --per 20
228
+ wave subitems list --list-item-id 345 --page 1 --per 20
229
+ wave subtodos list --todo-id 456 --page 1 --per 20
230
+ wave subissues list --issue-id 567 --page 1 --per 20
231
+ wave talking-points list --meeting-id 678 --page 1 --per 20
232
+ wave subtasks create --data-json '{"subtask":{"task_id":"123","member_id":"67","name":"Subtask name","description":"Subtask description"}}'
233
+ wave subissues destroy --id 999
234
+ ```
235
+
236
+ All commands require organization context via `--organization-id` or `WAVE_ORGANIZATION_ID`.
237
+ You can also provide `organizationId` via `--auth-json-stdin`.
238
+
239
+ ## Parent-Child Create Rules
240
+
241
+ These child resources enforce parent IDs at CLI validation time:
242
+
243
+ - `list-items.create` requires `list_id`
244
+ - `issues.create` requires `issue_group_id`
245
+ - `todos.create` requires `todo_group_id`
246
+ - `rocks.create` requires `rock_collection_id`
247
+ - `kpis.create` requires `smart_kpi_view_id`
248
+ - `scorecards.create` requires `measurable_group_id`
249
+ - `subtasks.create` requires `task_id`
250
+ - `milestones.create` requires `rock_id`
251
+ - `subitems.create` requires `list_item_id`
252
+ - `subtodos.create` requires `todo_id`
253
+ - `subissues.create` requires `issue_id`
254
+ - `talking-points.create` requires `meeting_id`
255
+
256
+ If a required parent field is missing, CLI returns JSON error with exit code `2`.
257
+
258
+ `kpis.create` contract notes:
259
+
260
+ - `smart_kpi.name` must be one of the backend enum values.
261
+ - If payload uses free-text `smart_kpi.name` with `measurable_group_id`, CLI routes to `create_measurable` (`scorecards.create`) instead of `create_smart_kpi`.
262
+
263
+ `meetings.create` contract notes:
264
+
265
+ - `meeting.type` is required and must be `TeamMeeting` or `OneOnOneMeeting`.
266
+ - Required fields: `member_id`, `date`, `start_time`.
267
+ - `meeting.repeats` defaults to `never` if omitted.
268
+ - For `TeamMeeting`, `teams_ids` is required.
269
+
270
+ `organizations.update` contract notes:
271
+
272
+ - `organization.workspace_name` is normalized to `organization.organization_detail_attributes.workspace_name`.
273
+
274
+ `organizations.meta-profile.update` contract notes:
275
+
276
+ - Payload must be shaped as `organization_meta_profile.profile.<section>.<field>`.
277
+ - Legacy shorthand `organization_meta_profile.title` is normalized to `profile.company_identity.one_sentence_summary`.
278
+
279
+ `organizations.key-metric-meta-profile.update` contract notes:
280
+
281
+ - Payload must be shaped as `key_metric_meta_profile.profile.<section>.<field>`.
282
+ - Legacy shorthand `key_metric_meta_profile.annual_revenue` is normalized to `profile.financial.revenue`.
283
+
284
+ `knowledge.create` contract notes:
285
+
286
+ - `content.content_type` must be one of `company`, `policy`, `process`.
287
+ - `content.status` must be `draft` or `published`.
288
+ - `content.member_id` is required.
289
+ - `content.type` is not required for create.
290
+
291
+ `knowledge.list` filter notes:
292
+
293
+ - `--assigned true` uses backend "assigned for current authenticated member" logic.
294
+ - `--assigned true` defaults to incomplete items only.
295
+ - CLI `knowledge list` does not currently expose an `include_completed` toggle.
296
+ - `--member-id` filters `contents.member_id` (owner/author), not assignment target.
297
+ - `--focus-member-id` filters `contents.focus_member_id` (focus scope), not assignment target.
298
+ - For manager/admin users, `--assigned true` may include creator-visible items with assignments, not only direct assignee matches.
299
+
300
+ Examples:
301
+
302
+ ```bash
303
+ # Assigned to current authenticated member (incomplete only by default)
304
+ wave knowledge list --assigned true --page 1 --per 50
305
+
306
+ # Owner filter (not assignment target filter)
307
+ wave knowledge list --member-id 67 --page 1 --per 50
308
+
309
+ # Focus scope filter (not assignment target filter)
310
+ wave knowledge list --focus-member-id 67 --page 1 --per 50
311
+ ```
312
+
313
+ `news.create` contract notes:
314
+
315
+ - Requires `headline.member_id`, `headline.status`, and `headline.headline_type`.
316
+ - `headline.status` must be `active` or `completed`.
317
+ - `headline.headline_type` must be `team` or `org_wide`.
318
+
319
+ `questions.create/update` contract notes:
320
+
321
+ - Use `question.name` (legacy `summary` is normalized to `name`).
322
+ - `questions.create` requires `question.member_id` and `question.name`.
323
+ - `question.status`, when provided, must be `asked` or `answered`.
324
+ - `questions.list` supports only `asked` and `answered` filters (plus `page` / `per`).
325
+ - `questions.list` does not expose `--query-json`; unsupported filters are rejected at CLI parse time.
326
+
327
+ `pulse.create` contract notes:
328
+
329
+ - Requires `health_update.health_updatable_id`, `health_update.health_updatable_type`, `health_update.member_id`, and `health_update.status`.
330
+ - Legacy `updatable_id`/`updatable_type` are normalized to `health_updatable_id`/`health_updatable_type`.
331
+ - `health_update.status` must be `on_track`, `at_risk`, or `off_track`.
332
+
333
+ `surveys.create/update` contract notes:
334
+
335
+ - Use `survey.name` and `survey.recipient_type` (`title` is normalized to `name`).
336
+ - `survey.recipient_type` must be `member`, `team`, or `org_wide`.
337
+ - Survey results are written via `surveys.update` using nested `survey.survey_results_attributes`.
338
+ - There is no standalone `createSurveyResult` / `updateSurveyResult` CLI command.
339
+ - Valid `survey.name` values:
340
+ - `employee_net_promoter_score`
341
+ - `continuous_performance_review`
342
+ - `core_value_alignment`
343
+ - `engagement`
344
+ - `wellness_and_mental_health`
345
+ - `workspace_culture`
346
+ - `peer_review`
347
+ - `manager_review`
348
+ - `onboarding`
349
+
350
+ `feedbacks.create/update` contract notes:
351
+
352
+ - Use `feedback.name` (`title` is normalized to `name`).
353
+ - `feedbacks.create` requires `feedback.name`, `feedback.quarter`, `feedback.year`.
354
+ - `feedback.quarter` must be `q1`, `q2`, `q3`, or `q4`.
355
+
356
+ `accountability.create/update` contract notes:
357
+
358
+ - Use `responsibility.name` (`summary` is normalized to `name`).
359
+ - `accountability.create` requires `responsibility.name` and `responsibility.member_id`.
360
+
361
+ ## Markdown Tree Traversal
362
+
363
+ Thin GraphQL adapter over backend markdown-tree services.
364
+ CLI does not re-implement traversal/scope rules.
365
+
366
+ Commands:
367
+
368
+ - `wave markdown-tree root [--tree-view]`
369
+ - `wave markdown-tree resolve --tool-key <k> --node-key <k> [--parent-id <id>] [--record-id <id>] [--member-id <id>] [--team-id <id>] [--content-type <t>] [--tree-view]`
370
+ - `wave markdown-tree children --tool-key <k> --node-key <k> [--parent-id <id>] [--record-id <id>] [--member-id <id>] [--team-id <id>] [--content-type <t>] [--tree-view]`
371
+ - `wave markdown-tree subtree --tool-key <k> --node-key <k> --depth <n> [--parent-id <id>] [--record-id <id>] [--member-id <id>] [--team-id <id>] [--content-type <t>] [--tree-view]`
372
+
373
+ GraphQL mapping:
374
+
375
+ - `root` -> `markdownTreeRoot`
376
+ - `resolve` -> `markdownTreeNode`
377
+ - `children` -> `markdownTreeChildren`
378
+ - `subtree` -> `markdownTreeSubtree`
379
+
380
+ Backend error codes are surfaced explicitly from GraphQL `errors[].extensions.code`:
381
+
382
+ - `UNAUTHORIZED`
383
+ - `NOT_FOUND`
384
+ - `INVALID_SCOPE`
385
+ - `UNSUPPORTED_BRANCH`
386
+
387
+ ## OSMD Agent Markdown
388
+
389
+ The `osmd` command group is the machine interface for canonical OSMD, `.agent` pages, and Agent Wiki index/log files. Canonical OSMD files are read-only. New synthesized pages are written under an OSMD parent node's `.agent/` folder; Agent Wiki is limited to `index.md`, append-only `log.md`, and legacy read/status visibility for older content folders.
390
+
391
+ Commands:
392
+
393
+ - `wave osmd tree [--depth <n>] [--tree-view]`
394
+ - `wave osmd schema [--json | --readable]`
395
+ - `wave osmd examples [--json | --readable]`
396
+ - `wave osmd search "<query>" [--scope os-only|agent-overlay-only|agent-wiki-only|combined] [--limit <n>]`
397
+ - `wave osmd lint [--scope combined|agent-overlay-only|agent-wiki-only] [--json] [--fix=false]`
398
+ - `wave osmd memory-quality [--scope agent-overlay-only] [--json | --readable]`
399
+ - `wave osmd ingest text [text] [--content <text> | --file <path> | stdin] --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]`
400
+ - `wave osmd ingest file <path> --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]`
401
+ - `wave osmd ingest url <url> --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]`
402
+ - `wave osmd ingest transcript [text] [--content <text> | --file <path> | stdin] --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]`
403
+ - `wave osmd phantom run [--dry-run] [--readable]`
404
+ - `wave osmd read <path> [--parent-ref <canonicalPath>]`
405
+ - `wave osmd children [path] [--parent-ref <canonicalPath>] [--source canonical_osmd|agent_overlay|agent_wiki]`
406
+ - `wave osmd status <path> [--parent-ref <canonicalPath>]`
407
+ - `wave osmd agent status <parent-ref> <file>`
408
+ - `wave osmd agent init <parent-ref>`
409
+ - `wave osmd agent read <parent-ref> <file>`
410
+ - `wave osmd agent create <parent-ref> <file> (--content <markdown> | --file <path> | stdin)`
411
+ - `wave osmd agent update <parent-ref> <file> (--content <markdown> | --file <path> | stdin)`
412
+ - `wave osmd agent children <parent-ref>`
413
+ - `wave osmd wiki status <path>`
414
+ - `wave osmd wiki read <path>`
415
+ - `wave osmd wiki index-upsert <.agent-file> --summary <summary> --parent-ref <canonicalPath>`
416
+ - `wave osmd wiki append-log [log.md] (--content <markdown> | --file <path> | stdin)`
417
+ - `wave phantom inventory --json`
418
+ - `wave phantom observations --json`
419
+ - `wave phantom observations --run-id <run-id> --json`
420
+ - `wave phantom observations --source-table <source-table> --json`
421
+ - `wave phantom observations --source-record-id <source-record-id> --json`
422
+ - `wave phantom observations --source-event-id <source-event-id> --json`
423
+ - `wave phantom observations --test-run-id <test-run-id> --json`
424
+ - `wave phantom observation <id> --json`
425
+ - `wave phantom candidates --json`
426
+ - `wave phantom candidates --run-id <run-id> --json`
427
+ - `wave phantom candidates --observation-id <observation-id> --json`
428
+ - `wave phantom candidates --candidate-type <candidate-type> --json`
429
+ - `wave phantom candidates --test-run-id <test-run-id> --json`
430
+ - `wave phantom candidate <id> --json`
431
+ - `wave phantom evidences --json`
432
+ - `wave phantom evidences --candidate-id <candidate-id> --json`
433
+ - `wave phantom evidences --observation-id <observation-id> --json`
434
+ - `wave phantom evidences --test-run-id <test-run-id> --json`
435
+ - `wave phantom evidence <id> --json`
436
+ - `wave phantom validations --json`
437
+ - `wave phantom validations --run-id <run-id> --json`
438
+ - `wave phantom validations --candidate-id <candidate-id> --json`
439
+ - `wave phantom validations --status <status> --json`
440
+ - `wave phantom validations --validation-type <validation-type> --json`
441
+ - `wave phantom validations --test-run-id <test-run-id> --json`
442
+ - `wave phantom validation <id> --json`
443
+ - `wave phantom apply-operations --json`
444
+ - `wave phantom apply-operations --run-id <run-id> --json`
445
+ - `wave phantom apply-operations --candidate-id <candidate-id> --json`
446
+ - `wave phantom apply-operations --status <status> --json`
447
+ - `wave phantom apply-operations --operation-type <operation-type> --json`
448
+ - `wave phantom apply-operations --test-run-id <test-run-id> --json`
449
+ - `wave phantom apply-operation <id> --json`
450
+ - `wave phantom report <run-id|latest> --json`
451
+
452
+ GraphQL mapping:
453
+
454
+ - `osmd status` / `osmd agent status` / `osmd wiki status` -> `agentMarkdownStatus`
455
+ - `osmd search` -> `osMdSearch`
456
+ - `osmd lint` -> `osMdWikiLint`
457
+ - `osmd memory-quality` -> `osMdMemoryQuality`
458
+ - `osmd ingest text` / `osmd ingest file` / `osmd ingest url` / `osmd ingest transcript` -> `registerRawSource`, then `ingestRawSource`
459
+ - `osmd ingest ... --dry-run` -> `rawSourceIngestionPlan`
460
+ - `osmd phantom run --dry-run` -> `runPhantomOsmdMaintenance(dryRun: true)`
461
+ - `osmd phantom run` -> `runPhantomOsmdMaintenance(dryRun: false)`
462
+ - `phantom inventory --json` -> `phantomLedgerInventory`
463
+ - `phantom observations --json` -> `phantomObservations`
464
+ - `phantom observations --run-id <run-id> --json` -> `phantomObservations(phantomRunId: <run-id>)`
465
+ - `phantom observations --source-table <source-table> --json` -> `phantomObservations(sourceTable: <source-table>)`
466
+ - `phantom observations --source-record-id <source-record-id> --json` -> `phantomObservations(sourceRecordId: <source-record-id>)`
467
+ - `phantom observations --source-event-id <source-event-id> --json` -> `phantomObservations(sourceEventId: <source-event-id>)`
468
+ - `phantom observations --test-run-id <test-run-id> --json` -> `phantomObservations(testRunId: <test-run-id>)`
469
+ - `phantom observation <id> --json` -> `phantomObservation(id: <id>)`
470
+ - `phantom candidates --json` -> `phantomCandidates`
471
+ - `phantom candidates --run-id <run-id> --json` -> `phantomCandidates(phantomRunId: <run-id>)`
472
+ - `phantom candidates --observation-id <observation-id> --json` -> `phantomCandidates(phantomObservationId: <observation-id>)`
473
+ - `phantom candidates --candidate-type <candidate-type> --json` -> `phantomCandidates(candidateType: <candidate-type>)`
474
+ - `phantom candidates --test-run-id <test-run-id> --json` -> `phantomCandidates(testRunId: <test-run-id>)`
475
+ - `phantom candidate <id> --json` -> `phantomCandidate(id: <id>)`
476
+ - `phantom evidences --json` -> `phantomCandidateEvidences`
477
+ - `phantom evidences --candidate-id <candidate-id> --json` -> `phantomCandidateEvidences(phantomCandidateId: <candidate-id>)`
478
+ - `phantom evidences --observation-id <observation-id> --json` -> `phantomCandidateEvidences(phantomObservationId: <observation-id>)`
479
+ - `phantom evidences --test-run-id <test-run-id> --json` -> `phantomCandidateEvidences(testRunId: <test-run-id>)`
480
+ - `phantom evidence <id> --json` -> `phantomCandidateEvidence(id: <id>)`
481
+ - `phantom validations --json` -> `phantomValidations`
482
+ - `phantom validations --run-id <run-id> --json` -> `phantomValidations(phantomRunId: <run-id>)`
483
+ - `phantom validations --candidate-id <candidate-id> --json` -> `phantomValidations(phantomCandidateId: <candidate-id>)`
484
+ - `phantom validations --status <status> --json` -> `phantomValidations(status: <status>)`
485
+ - `phantom validations --validation-type <validation-type> --json` -> `phantomValidations(validationType: <validation-type>)`
486
+ - `phantom validations --test-run-id <test-run-id> --json` -> `phantomValidations(testRunId: <test-run-id>)`
487
+ - `phantom validation <id> --json` -> `phantomValidation(id: <id>)`
488
+ - `phantom apply-operations --json` -> `phantomApplyOperations`
489
+ - `phantom apply-operations --run-id <run-id> --json` -> `phantomApplyOperations(phantomRunId: <run-id>)`
490
+ - `phantom apply-operations --candidate-id <candidate-id> --json` -> `phantomApplyOperations(phantomCandidateId: <candidate-id>)`
491
+ - `phantom apply-operations --status <status> --json` -> `phantomApplyOperations(status: <status>)`
492
+ - `phantom apply-operations --operation-type <operation-type> --json` -> `phantomApplyOperations(operationType: <operation-type>)`
493
+ - `phantom apply-operations --test-run-id <test-run-id> --json` -> `phantomApplyOperations(testRunId: <test-run-id>)`
494
+ - `phantom apply-operation <id> --json` -> `phantomApplyOperation(id: <id>)`
495
+ - `phantom report <run-id> --json` -> `phantomRun`, then `phantomReports(phantomRunId: <run-id>)`
496
+ - `phantom report latest --json` -> `phantomRuns`, then `phantomReports(phantomRunId: <latest API-listed run id>)`
497
+ - `members meta-profile --member-id <member-id> --json` -> `memberMetaProfile(organizationId: <organization-id>, memberId: <member-id>)`
498
+ - `skills list --json` -> `skills(organizationId: <organization-id>)`
499
+ - `skills list --scope <scope> --query <query> --limit <limit> --json` -> `skills(organizationId: <organization-id>, scope: <scope>, query: <query>, limit: <limit>)`
500
+ - `skills show --id <skill-id> --json` -> `skills(organizationId: <organization-id>)`, then selects the API-returned skill id locally for read-only proof.
501
+ - `osmd read` / `osmd agent read` / `osmd wiki read` -> `agentMarkdownFile`
502
+ - `osmd children` / `osmd agent children` -> `agentMarkdownChildren`
503
+ - `osmd agent init` -> `initAgentMarkdown`
504
+ - `osmd agent create` -> `createAgentMarkdownFile`
505
+ - `osmd agent update` -> `updateAgentMarkdownFile`
506
+ - `osmd wiki create` / `osmd wiki update` -> local `unsupported_contract`
507
+ - `osmd wiki index-upsert` -> `upsertAgentWikiIndexEntry`
508
+ - `osmd wiki append-log` -> `recordAgentMarkdownLog`
509
+
510
+ Path and permission rules:
511
+
512
+ - Canonical OSMD reads use backend `path` values from tree/status/file responses.
513
+ - The OSMD display root is the organization name returned by `wave osmd tree`, formatted by the backend as `${OrgName}.MD`. Callers must use the returned `runtimeLabel`/`path` and must not hardcode a fixed root label.
514
+ - UI display folders such as `Org.MD`, `Members.MD`, `Teams.MD`, `Tools.MD`, and `Agent Wiki.MD` are presentation labels, not storage paths. Callers should resolve them from `wave osmd tree` metadata and then pass backend `path` values to `osmd status`/`osmd read`.
515
+ - `Members.MD` maps to the canonical directory branch identified by `toolKey: "directory"` and `nodeKey: "members"`; member records beneath it expose backend paths such as `directory/<member name>`.
516
+ - `Teams.MD` maps to `toolKey: "directory"` and `nodeKey: "teams"`; team records beneath it expose backend paths such as `directory/<team name>`.
517
+ - `Org.MD` maps to the canonical organization branch identified by `toolKey: "org"` and `nodeKey: "root"`; the root backend path is `org`.
518
+ - `Tools.MD` is a UI grouping over the remaining canonical tool roots returned by `osmd tree` such as `projects`, `knowledge`, `issues`, and other backend `toolKey` roots. Use each returned node's `path`.
519
+ - `Agent Wiki.MD` maps to the Agent Wiki root path `Agent Wiki`; new writes are limited to `index-upsert` and append-only `log.md`.
520
+ - Canonical OSMD writes are blocked before mutation when status reports `source: "canonical_osmd"` or `access: "read_only"`.
521
+ - `.agent` write commands require a canonical `parent-ref`; the CLI passes it as backend metadata and does not invent backend storage rules.
522
+ - `.agent` files support `notes.md`, `summary.md`, and `<lowercase-kebab-slug>.md`.
523
+ - `osmd agent init <parent-ref>` is idempotent for default `.agent` files.
524
+ - Agent Wiki index paths may be `index.md` or `Agent Wiki/index.md`; the CLI normalizes these to `Agent Wiki/index.md`.
525
+ - Legacy Agent Wiki content folder paths may still be used for `read`/`status` visibility when needed: `concepts/<slug>.md`, `playbooks/<slug>.md`, `decisions/<slug>.md`, `org-summaries/<slug>.md`, `sources/<slug>.md`, or the same forms prefixed with `Agent Wiki/`.
526
+ - New writes to Agent Wiki content folders are rejected locally with `unsupported_contract`.
527
+ - Agent Wiki log paths may be `log.md` or `Agent Wiki/log.md`; the CLI normalizes these to `Agent Wiki/log.md`.
528
+ - Agent Wiki `index-upsert` upserts one backend-managed `Agent Wiki/index.md` entry for a `.agent` target using backend metadata and permissions.
529
+ - `index-upsert` targets must be `.agent/notes.md`, `.agent/summary.md`, or `.agent/<lowercase-kebab-slug>.md` with `--parent-ref <canonicalPath>`.
530
+ - Full parent paths such as `directory/Wave Tools Team/.agent/notes.md` are intentionally unsupported for `index-upsert`; pass `.agent/notes.md` and `--parent-ref "directory/Wave Tools Team"` instead.
531
+ - `index-upsert` is not a broad write command and cannot target `Agent Wiki/index.md`, `Agent Wiki/log.md`, or Agent Wiki content folders.
532
+ - Agent Wiki `append-log` supports only `log.md` and calls status first before appending.
533
+ - The CLI uses backend GraphQL metadata directly and does not derive S3 paths, SHA paths, or backend storage conventions.
534
+ - Atlas should inspect `path`, `source`, `access`, `owner`, `parentRef`, `exists`, `canCreate`, `canUpdate`, `canAppend`, `wikiRole`, `formatVersion`, `createdAt`, `updatedAt`, `ingestedAt`, `lastSeenAt`, `canonicalParentUpdatedAt`, `suggestedAction`, and `errorCode`.
535
+ - Atlas owns report filename date choices and log-entry date content. The CLI does not generate, normalize, or decide report/log dates.
536
+ - OSMD status/read/children responses pass backend timestamp metadata through as strings when provided: `createdAt`, `updatedAt`, `ingestedAt`, `lastSeenAt`, and `canonicalParentUpdatedAt`. Missing timestamp fields are emitted as `null`.
537
+ - The CLI does not generate or normalize OSMD timestamp values. Atlas should treat timestamp strings as backend-owned metadata.
538
+ - `osmd lint` remains backend pass-through for lint payloads and does not add OSMD file timestamp fields.
539
+ - When Atlas needs organization timezone context for report naming, use `wave organizations show` and inspect `data.organization.organizationDetail.timezone` when the backend provides it.
540
+ - `osmd search` scopes map to backend `source` values: `os-only` -> `canonical_osmd`, `agent-overlay-only` -> `agent_overlay`, `agent-wiki-only` -> `agent_wiki`, and `combined` -> backend merged search.
541
+ - `osmd search` returns backend candidates with `path`, `source`, `access`, `owner`, `wikiRole`, `formatVersion`, `parentRef`, `exists`, `agentChildrenAllowed`, `title`, `label`, `snippet`, `rank`, `matchReason`, `toolKey`, `nodeKey`, and `nodeKind` where the backend provides them.
542
+ - `osmd lint` is read-only and delegates all lint rules to backend `osMdWikiLint`; the CLI does not implement, infer, or auto-fix lint rules.
543
+ - `osmd lint` scopes map to backend values: `combined` -> `combined`, `agent-overlay-only` -> `agent_overlay`, and `agent-wiki-only` -> `agent_wiki`.
544
+ - `osmd lint --fix=true` is unsupported and rejected before GraphQL. Lint never calls create, update, init, append-log, or index-upsert mutations.
545
+ - `osmd lint` defaults to readable text showing status, counts, and issue details for human review.
546
+ - `osmd lint --json` returns the stable global JSON envelope. The backend lint payload is passed through in `data` with `status`, `summary.totalIssues`, `summary.errors`, `summary.warnings`, `summary.suggestions`, and `issues`.
547
+ - `osmd memory-quality` is read-only and delegates Semantic Memory Quality Evaluation to backend `osMdMemoryQuality`.
548
+ - `osmd memory-quality --json` returns the stable global JSON envelope and passes backend structured scores, weakest overlays, and issues through in `data`.
549
+ - `osmd memory-quality --readable` renders status, evaluated/pass/warn/fail counts, weakest overlays, issue code, dimension, evidence snippet, suggested action, and `canAutoFix: false`. It does not expose fix, delete, path-target, or canonical write flags.
550
+ - `osmd ingest` commands are Source Ingestion Phase 1 pass-throughs. The CLI registers a raw source with backend `registerRawSource`, then calls backend `ingestRawSource` with the returned `rawSourceId`.
551
+ - `osmd ingest` requires `--slug <lowercase-kebab-slug>`, `--parent-ref <canonicalPath>`, and `--summary <summary>` so backend ingestion targets `.agent/<slug>.md` under the resolved parent node with an explicit summary.
552
+ - Supported raw source types are `pasted_text`, `uploaded_markdown`, `uploaded_text`, `uploaded_json`, `uploaded_csv`, `meeting_transcript`, and `url`.
553
+ - `registerRawSource` receives flat variables: `organization_id`, `source_type`, `title`, `content`, `file_name`, `url`, and `metadata`.
554
+ - `ingestRawSource` receives flat variables: `organization_id`, `raw_source_id`, `slug`, `parent_ref`, `summary`, `overlay_updates`, and `run_lint`.
555
+ - `rawSourceIngestionPlan` receives flat planning variables: `organization_id`, `slug`, `parent_ref`, `source_type`, `title`, `file_name`, `url`, `overlay_updates`, and `run_lint`; it is used for `--dry-run` and does not call `registerRawSource` or `ingestRawSource`.
556
+ - `osmd ingest` JSON output is the default machine interface and includes `rawSourceId`, `sourceSummaryPath`, `createdFiles`, `updatedFiles`, `skippedUpdates`, and optional `lintResult`.
557
+ - `osmd ingest --run-lint` is passed to backend `ingestRawSource`; the CLI does not call `osmd lint`, does not auto-fix, and does not run lint before ingestion.
558
+ - `osmd ingest --dry-run` calls backend `rawSourceIngestionPlan` and returns the backend plan through the same output schema.
559
+ - `osmd ingest --update-overlays` is rejected locally with `unsupported_contract`; CLI Phase 1 does not pass a blind overlay boolean.
560
+ - `osmd ingest file` reads local file content and sends the filename as source metadata. The CLI does not mutate raw sources, upload raw-source storage objects, infer source storage paths, or implement summarization rules.
561
+ - `osmd phantom run` is organization-scoped, manual-only, and fixed to the backend OSMD/agent semantic memory scope. The CLI does not schedule cron jobs.
562
+ - `osmd phantom run --dry-run` calls backend `runPhantomOsmdMaintenance` with `dryRun: true` and does not write files directly.
563
+ - `osmd phantom run` passes backend result fields through, including run id, run status, inventory count fields, optional `countSemantics`, lint summary, report path, updated files, skipped nodes, and notification metadata when provided.
564
+ - Phantom runs use a 120 second default HTTP timeout because the backend maintenance pass can outlive the CLI's normal 10 second default. Operators can still override this with `--timeout-ms <milliseconds>` or `WAVE_TIMEOUT_MS`.
565
+ - Top-level `phantom` commands are read-only operator/debug surfaces over the API-owned inert Phantom Evidence Ledger.
566
+ - `phantom inventory --json` returns backend ledger counts in the standard JSON envelope.
567
+ - `phantom observations --json` returns API-owned Phantom observations with ids, run ids, observation/source metadata, content hash, payload, schema version, test run id, idempotency key, and timestamps.
568
+ - `phantom observations --run-id <run-id> --json`, `--source-table <source-table>`, `--source-record-id <source-record-id>`, `--source-event-id <source-event-id>`, and `--test-run-id <test-run-id>` pass API read filters directly to `phantomObservations`.
569
+ - `phantom observation <id> --json` reads a single API-owned Phantom observation by id.
570
+ - `phantom candidates --json` returns API-owned Phantom candidates with candidate ids, run ids, observation ids, target metadata, approval state metadata, payload, schema version, test run id, idempotency key, and timestamps.
571
+ - `phantom candidates --run-id <run-id> --json`, `--observation-id <observation-id>`, `--candidate-type <candidate-type>`, and `--test-run-id <test-run-id>` pass API read filters directly to `phantomCandidates`.
572
+ - `phantom candidate <id> --json` reads a single API-owned Phantom candidate by id.
573
+ - `phantom evidences --json` returns API-owned Phantom candidate evidences with evidence ids, candidate ids, observation ids, source metadata, content hash, payload, schema version, test run id, idempotency key, and timestamps.
574
+ - `phantom evidences --candidate-id <candidate-id> --json`, `--observation-id <observation-id>`, and `--test-run-id <test-run-id>` pass API read filters directly to `phantomCandidateEvidences`.
575
+ - `phantom evidence <id> --json` reads a single API-owned Phantom candidate evidence by id.
576
+ - `phantom validations --json` returns API-owned Phantom validations with validation ids, run ids, candidate ids, validation type, dry-run flag, payloads, schema version, test run id, idempotency key, and timestamps.
577
+ - `phantom validations --run-id <run-id> --json`, `--candidate-id <candidate-id>`, `--status <status>`, `--validation-type <validation-type>`, and `--test-run-id <test-run-id>` pass API read filters directly to `phantomValidations`.
578
+ - `phantom validation <id> --json` reads a single API-owned Phantom validation by id.
579
+ - `phantom apply-operations --json` returns API-owned Phantom dry-run apply operation ledger records with operation ids, run ids, candidate ids, operation type, target metadata, payloads, schema version, test run id, idempotency key, and timestamps.
580
+ - `phantom apply-operations --run-id <run-id> --json`, `--candidate-id <candidate-id>`, `--status <status>`, `--operation-type <operation-type>`, and `--test-run-id <test-run-id>` pass API read filters directly to `phantomApplyOperations`.
581
+ - `phantom apply-operation <id> --json` reads a single API-owned Phantom apply operation ledger record by id.
582
+ - `phantom report <run-id> --json` returns the API-read Phantom run and reports for that run id.
583
+ - `phantom report latest --json` resolves `latest` only through API-listed `phantomRuns`; the CLI does not inspect local state or infer a latest run.
584
+ - Top-level `phantom` commands do not run Phantom, create ledger records, ingest observations, trigger extraction, create validations, create approvals, create apply operations, approve candidates, apply candidates, write memory/profile files, schedule work, or generate prompts.
585
+ - `members meta-profile --member-id <member-id> --json` is a read-only before/after proof surface over the API-owned `memberMetaProfile` query. It does not create, update, approve, or apply member profile changes.
586
+ - `skills list/show --json` are read-only before/after proof surfaces over the API-owned `skills` query. They do not create, update, destroy, archive, approve, or apply Skill records.
587
+ - Reads never create files.
588
+ - Create/update/append-log flows call status first and inspect `exists`, `canCreate`, `canUpdate`, `canAppend`, `suggestedAction`, and `errorCode`.
589
+ - `create` fails when `exists` is true.
590
+ - `update` fails when `exists` is false.
591
+ - `append-log` fails unless status reports `exists: true`, `access: "append_only"`, and `canAppend: true`.
592
+ - When `Agent Wiki/log.md` is missing and status reports `suggestedAction: "init"`, `append-log` returns a JSON error and does not initialize files implicitly.
593
+ - Missing `Agent Wiki/index.md` is create-ready when status returns `exists: false`, `canCreate: true`, `canUpdate: false`, and `suggestedAction: "create"`.
594
+
595
+ Atlas Agent Wiki log flow:
596
+
597
+ 1. `status_log`: run `wave osmd wiki status log.md`.
598
+ 2. Require `data.exists === true`, `data.access === "append_only"`, and `data.canAppend === true`.
599
+ 3. `append_log`: run `wave osmd wiki append-log --content "<markdown log entry>"`.
600
+
601
+ Atlas must not skip the status checks. `append-log` never initializes files implicitly; Agent Wiki index/log initialization is backend-owned.
602
+
603
+ Atlas maintenance examples:
604
+
605
+ Routine catalog maintenance should use backend-managed `index-upsert`, not manual `Agent Wiki/index.md` rewrites. Atlas should:
606
+
607
+ 1. Create or update a `.agent` page with an explicit parent ref.
608
+ 2. Run `wave osmd wiki index-upsert` with the relative `.agent/<file>.md` target and the backend-returned parent ref.
609
+ 3. Append a parseable `Agent Wiki/log.md` entry.
610
+
611
+ Entity note maintenance:
612
+
613
+ ```bash
614
+ wave osmd agent update "directory/Wave Tools Team" notes.md --content "## Working notes\n\n- New durable entity note."
615
+ wave osmd wiki index-upsert .agent/notes.md \
616
+ --parent-ref "directory/Wave Tools Team" \
617
+ --summary "Durable working notes for the Wave Tools Team"
618
+ wave osmd wiki append-log --content "```yaml
619
+ event: entity_note_updated
620
+ parentRef: directory/Wave Tools Team
621
+ targetPath: directory/Wave Tools Team/.agent/notes.md
622
+ summary: Added durable working notes for the Wave Tools Team.
623
+ ```"
624
+ ```
625
+
626
+ Synthesized page maintenance:
627
+
628
+ ```bash
629
+ wave osmd agent status "projects/Wave Tools" customer-interviews.md
630
+ wave osmd agent create "projects/Wave Tools" customer-interviews.md --content "# Customer Interviews\n\nSynthesized source summary."
631
+ wave osmd wiki index-upsert .agent/customer-interviews.md \
632
+ --parent-ref "projects/Wave Tools" \
633
+ --summary "Summary of customer interview source material"
634
+ wave osmd wiki append-log --content "```yaml
635
+ event: agent_page_updated
636
+ parentRef: projects/Wave Tools
637
+ targetPath: projects/Wave Tools/.agent/customer-interviews.md
638
+ summary: Created the customer interview synthesized page.
639
+ ```"
640
+ ```
641
+
642
+ Direct `Agent Wiki/index.md` create/update and content folder writes are not part of the current CLI write contract.
643
+
644
+ Append a parseable log entry. Use a stable fenced block so Atlas or backend jobs can parse the chronology later:
645
+
646
+ ````bash
647
+ wave osmd wiki append-log --content "```yaml
648
+ event: entity_note_updated
649
+ parentRef: directory/Wave Tools Team
650
+ agentFile: .agent/notes.md
651
+ wikiPath: Agent Wiki/index.md
652
+ summary: Added durable working notes for the Wave Tools Team.
653
+ ```"
654
+ ````
655
+
656
+ `append-log` accepts multi-line markdown from `--content`, `--file`, or stdin. Use `--file` for larger structured entries:
657
+
658
+ ```bash
659
+ wave osmd wiki append-log --file ./agent-wiki-log-entry.md
660
+ ```
661
+
662
+ When not using `--token-stdin` or `--auth-json-stdin`, stdin may also provide the log body:
663
+
664
+ ````bash
665
+ printf '%s\n' '```yaml' 'event: wiki_maintenance' 'summary: Recorded Agent Wiki maintenance event.' '```' | wave osmd wiki append-log
666
+ ````
667
+
668
+ OSMD command envelopes keep the global CLI envelope and put the file metadata directly in `data`:
669
+
670
+ ```json
671
+ {
672
+ "ok": true,
673
+ "command": "osmd.agent.read",
674
+ "status": 200,
675
+ "data": {
676
+ "path": "projects/Wave Tools/.agent/notes.md",
677
+ "source": "agent_overlay",
678
+ "access": "read_write",
679
+ "owner": "atlas",
680
+ "exists": true,
681
+ "canCreate": false,
682
+ "canUpdate": true,
683
+ "canAppend": false,
684
+ "wikiRole": "notes",
685
+ "formatVersion": "agent_markdown_v1",
686
+ "createdAt": "2026-05-07T18:10:11.123-07:00",
687
+ "updatedAt": "2026-05-07T18:20:21.456-07:00",
688
+ "ingestedAt": "2026-05-07T18:11:00.000-07:00",
689
+ "lastSeenAt": "2026-05-07T18:30:31.789-07:00",
690
+ "canonicalParentUpdatedAt": "2026-05-07T17:59:59.999-07:00",
691
+ "parentRef": "projects/Wave Tools",
692
+ "suggestedAction": "update",
693
+ "errorCode": null,
694
+ "content": "..."
695
+ },
696
+ "error": null,
697
+ "meta": { "requestId": "req_123" }
698
+ }
699
+ ```
700
+
701
+ Merged OSMD search keeps the same envelope and returns backend-ranked candidates:
702
+
703
+ ```json
704
+ {
705
+ "ok": true,
706
+ "command": "osmd.search",
707
+ "status": 200,
708
+ "data": {
709
+ "query": "Wave Tools",
710
+ "scope": "combined",
711
+ "count": 3,
712
+ "candidates": [
713
+ {
714
+ "path": "directory/Wave Tools Team/.agent/notes.md",
715
+ "source": "agent_overlay",
716
+ "access": "read_write",
717
+ "owner": "atlas",
718
+ "wikiRole": "notes",
719
+ "formatVersion": "agent_markdown_v1",
720
+ "parentRef": "directory/Wave Tools Team",
721
+ "exists": true,
722
+ "agentChildrenAllowed": false,
723
+ "title": "notes.md",
724
+ "label": "notes.md",
725
+ "snippet": "...",
726
+ "rank": 1,
727
+ "matchReason": "content",
728
+ "toolKey": "agent_overlay",
729
+ "nodeKey": "notes",
730
+ "nodeKind": "agent_overlay_file"
731
+ }
732
+ ]
733
+ },
734
+ "error": null,
735
+ "meta": { "requestId": "req_125" }
736
+ }
737
+ ```
738
+
739
+ OSMD lint keeps the same envelope and returns the backend-owned lint result. Human readers should use `data.status` and `data.summary` for the high-level status/counts, then inspect `data.issues` for backend-provided issue details:
740
+
741
+ ```json
742
+ {
743
+ "ok": true,
744
+ "command": "osmd.lint",
745
+ "status": 200,
746
+ "data": {
747
+ "status": "warn",
748
+ "summary": {
749
+ "totalIssues": 2,
750
+ "errors": 0,
751
+ "warnings": 1,
752
+ "suggestions": 1
753
+ },
754
+ "issues": [
755
+ {
756
+ "code": "missing_index_entry",
757
+ "severity": "warning",
758
+ "path": "Agent Wiki/concepts/customer-health.md",
759
+ "source": "agent_wiki",
760
+ "parentRef": null,
761
+ "message": "Controlled Agent Wiki page is missing from Agent Wiki/index.md.",
762
+ "suggestedAction": "upsert_index_entry",
763
+ "canAutoFix": false
764
+ }
765
+ ]
766
+ },
767
+ "error": null,
768
+ "meta": { "requestId": "req_126" }
769
+ }
770
+ ```
771
+
772
+ On write preflight errors, `data` contains the status metadata and `error.suggestedAction` mirrors backend guidance:
773
+
774
+ ```json
775
+ {
776
+ "ok": false,
777
+ "command": "osmd.agent.update",
778
+ "status": 404,
779
+ "data": {
780
+ "path": "projects/Wave Tools/.agent/customer-interviews.md",
781
+ "source": "agent_overlay",
782
+ "access": "read_write",
783
+ "owner": "atlas",
784
+ "exists": false,
785
+ "canCreate": true,
786
+ "canUpdate": false,
787
+ "canAppend": false,
788
+ "wikiRole": "index",
789
+ "formatVersion": "agent_markdown_v1",
790
+ "createdAt": null,
791
+ "updatedAt": null,
792
+ "ingestedAt": null,
793
+ "lastSeenAt": null,
794
+ "canonicalParentUpdatedAt": null,
795
+ "parentRef": "projects/Wave Tools",
796
+ "suggestedAction": "create",
797
+ "errorCode": null
798
+ },
799
+ "error": {
800
+ "code": "agent_file_not_found",
801
+ "message": "Agent markdown file does not exist: projects/Wave Tools/.agent/customer-interviews.md",
802
+ "suggestedAction": "create",
803
+ "details": {}
804
+ },
805
+ "meta": { "requestId": "req_124" }
806
+ }
807
+ ```
808
+
809
+ ## Navigation Shell
810
+
811
+ Primary agent-facing discovery/navigation commands:
812
+
813
+ - `wave find "<query>" [--under <path>] [--limit <n>]`
814
+ - `wave open "<name>" [--under <path>] [--tree-view]`
815
+ - `wave ls [query] --path <path>` or `wave ls [query] --tool-key ... --node-key ...`
816
+ - `wave cat --path <path>` or `wave cat --tool-key ... --node-key ...`
817
+ - `wave tree --depth <n> --path <path>` or `wave tree --depth <n> --tool-key ... --node-key ...`
818
+
819
+ These wrap backend-owned markdown-tree primitives:
820
+
821
+ - `find` -> `markdownTreeFind`
822
+ - `open` -> `markdownTreeFind` + `markdownTreeNode`
823
+ - `ls` -> `markdownTreeChildren`
824
+ - `cat` -> `markdownTreeNode`
825
+ - `tree` -> `markdownTreeSubtree`
826
+
827
+ Discovery, ranking, canonical paths, and narrowing are backend-owned.
828
+ An empty `find` result (`candidates: []`) means no evidence for that exact query/scope, not confirmed global absence.
829
+ `markdownTreeFind` remains canonical-focused. Agent overlay and Agent Wiki files are discoverable through `osmd tree`, `osmd children`, and `osmd status`, not through `find`.
830
+ Use `wave osmd search "<query>" --scope combined` when Atlas needs merged canonical OSMD, `.agent`, and Agent Wiki search. Use narrower scopes for targeted memory lookup:
831
+
832
+ - `--scope os-only` searches canonical OSMD only.
833
+ - `--scope agent-overlay-only` searches entity-specific `.agent` files.
834
+ - `--scope agent-wiki-only` searches backend Agent Wiki files, including `index.md`, legacy indexed pages when present, and append-only `log.md`.
835
+ - `--scope combined` searches the backend merged OSMD surface.
836
+
837
+ Ambiguity contract:
838
+
839
+ - no silent bad guesses
840
+ - returns `error.code = "ambiguous_match"` with ranked candidates in `error.details.candidates`
841
+
842
+ ## Content Command Contract
843
+
844
+ The `content` command uses exact GraphQL operation names:
845
+
846
+ - `CreateContent`
847
+ - `UpdateContent`
848
+ - `DestroyContent`
849
+ - `ContentShow`
850
+ - `ContentsIndex`
851
+
852
+ Placement rules enforced by command shape:
853
+
854
+ - `content create-member`
855
+ - `contentable_type: "Member"`
856
+ - `contentable_id: TARGET_MEMBER_ID`
857
+ - `member_id: TARGET_MEMBER_ID`
858
+ - `focus_member_id: null`
859
+ - `focus_team_id: null`
860
+ - `content create-manager`
861
+ - `contentable_type: "Member"`
862
+ - `contentable_id: ACTOR_MEMBER_ID`
863
+ - `member_id: ACTOR_MEMBER_ID`
864
+ - `focus_member_id: TARGET_MEMBER_ID`
865
+ - `content create-team`
866
+ - `contentable_type: "Team"`
867
+ - `contentable_id: TEAM_ID`
868
+ - `member_id: ACTOR_MEMBER_ID`
869
+ - `focus_team_id: TEAM_ID`
870
+ - `content create-project`
871
+ - `contentable_type: "Project"`
872
+ - `contentable_id: PROJECT_ID`
873
+ - `member_id: ACTOR_MEMBER_ID`
874
+ - `content create-customer`
875
+ - `contentable_type: "Customer"`
876
+ - `contentable_id: CUSTOMER_ID`
877
+ - `member_id: ACTOR_MEMBER_ID`
878
+ - `content create-contact`
879
+ - `contentable_type: "Contact"`
880
+ - `contentable_id: CONTACT_ID`
881
+ - `member_id: ACTOR_MEMBER_ID`
882
+
883
+ ## JSON Envelope
884
+
885
+ Success:
886
+
887
+ ```json
888
+ {
889
+ "ok": true,
890
+ "command": "tasks.list",
891
+ "status": 200,
892
+ "data": {},
893
+ "error": null,
894
+ "meta": {
895
+ "requestId": "req_123"
896
+ }
897
+ }
898
+ ```
899
+
900
+ Failure:
901
+
902
+ ```json
903
+ {
904
+ "ok": false,
905
+ "command": "tasks.create",
906
+ "status": 403,
907
+ "data": null,
908
+ "error": {
909
+ "code": "forbidden",
910
+ "message": "Forbidden",
911
+ "details": {}
912
+ },
913
+ "meta": {
914
+ "requestId": "req_124"
915
+ }
916
+ }
917
+ ```
918
+
919
+ ## Exit Codes
920
+
921
+ - `0`: success
922
+ - `1`: generic uncaught error
923
+ - `2`: invalid args/config
924
+ - `3`: missing or invalid auth
925
+ - `4`: forbidden
926
+ - `5`: not found
927
+ - `6`: validation failure
928
+ - `7`: network or upstream error