@ted-galago/wave-cli 0.1.19 → 0.1.22

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 CHANGED
@@ -120,6 +120,7 @@ wave markdown-tree resolve --tool-key projects --node-key tasks --parent-id 123
120
120
  wave markdown-tree children --tool-key directory --node-key members --record-id 67
121
121
  wave markdown-tree subtree --tool-key knowledge --node-key knowledge --content-type processes --depth 2
122
122
  wave osmd tree
123
+ wave osmd search "Wave Tools" --scope combined --limit 10
123
124
  wave osmd read "projects/Wave Tools"
124
125
  wave osmd children "projects/Wave Tools"
125
126
  wave osmd status "projects/Wave Tools/.agent/notes.md" --parent-ref "projects/Wave Tools"
@@ -132,6 +133,8 @@ wave osmd wiki read log.md
132
133
  wave osmd wiki init
133
134
  wave osmd wiki create index.md --content "# Agent Wiki"
134
135
  wave osmd wiki update "Agent Wiki/index.md" --file ./agent-wiki-index.md
136
+ wave osmd wiki create "concepts/customer-health.md" --content "# Customer Health"
137
+ wave osmd wiki update "decisions/use-agent-wiki.md" --content "# Use Agent Wiki"
135
138
  wave osmd wiki append-log --content "Observed durable event"
136
139
  wave find "Ted"
137
140
  wave open "Ted Martinez"
@@ -307,6 +310,7 @@ The `osmd` command group is the write-capable agent interface for canonical OSMD
307
310
  Commands:
308
311
 
309
312
  - `wave osmd tree [--depth <n>] [--tree-view]`
313
+ - `wave osmd search "<query>" [--scope os-only|agent-overlay-only|agent-wiki-only|combined] [--limit <n>]`
310
314
  - `wave osmd read <path> [--parent-ref <canonicalPath>]`
311
315
  - `wave osmd children [path] [--parent-ref <canonicalPath>] [--source canonical_osmd|agent_overlay|agent_wiki]`
312
316
  - `wave osmd status <path> [--parent-ref <canonicalPath>]`
@@ -326,6 +330,7 @@ Commands:
326
330
  GraphQL mapping:
327
331
 
328
332
  - `osmd status` / `osmd agent status` / `osmd wiki status` -> `agentMarkdownStatus`
333
+ - `osmd search` -> `osMdSearch`
329
334
  - `osmd read` / `osmd agent read` / `osmd wiki read` -> `agentMarkdownFile`
330
335
  - `osmd children` / `osmd wiki children` -> `agentMarkdownChildren`
331
336
  - `osmd agent init` -> `initAgentMarkdown`
@@ -342,13 +347,16 @@ Path and permission rules:
342
347
  - `.agent` first slice supports only `notes.md`.
343
348
  - `osmd agent init <parent-ref>` is idempotent for default `.agent` files.
344
349
  - Agent Wiki index paths may be `index.md` or `Agent Wiki/index.md`; the CLI normalizes these to `Agent Wiki/index.md`.
350
+ - Controlled Agent Wiki page paths may be `concepts/<name>.md`, `playbooks/<name>.md`, `decisions/<name>.md`, `org-summaries/<name>.md`, or the same forms prefixed with `Agent Wiki/`.
345
351
  - Agent Wiki log paths may be `log.md` or `Agent Wiki/log.md`; the CLI normalizes these to `Agent Wiki/log.md`.
346
- - Agent Wiki first-slice agents should use `index.md` for broad writable wiki memory and `log.md` for append-only event memory.
352
+ - Agent Wiki agents should use `index.md` for broad writable wiki memory, controlled folders for durable typed pages, and `log.md` for append-only event memory.
347
353
  - Agent Wiki `init` initializes backend-owned system files, including append-only `log.md`, through `initAgentMarkdown` at path `Agent Wiki`.
348
- - Agent Wiki `create` and `update` support only `index.md`; `log.md` cannot be overwritten.
354
+ - Agent Wiki `create` and `update` support `index.md`, `concepts/*.md`, `playbooks/*.md`, `decisions/*.md`, and `org-summaries/*.md`; `log.md` cannot be created or overwritten by these commands.
349
355
  - Agent Wiki `append-log` supports only `log.md` and calls status first before appending.
350
356
  - The CLI uses backend GraphQL metadata directly and does not derive S3 paths, SHA paths, or backend storage conventions.
351
357
  - Atlas should inspect `path`, `source`, `access`, `owner`, `parentRef`, `exists`, `canCreate`, `canUpdate`, `canAppend`, `wikiRole`, `formatVersion`, `suggestedAction`, and `errorCode`.
358
+ - `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.
359
+ - `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.
352
360
  - Reads never create files.
353
361
  - Create/update/append-log flows call status first and inspect `exists`, `canCreate`, `canUpdate`, `canAppend`, `suggestedAction`, and `errorCode`.
354
362
  - `create` fails when `exists` is true.
@@ -356,6 +364,7 @@ Path and permission rules:
356
364
  - `append-log` fails unless status reports `exists: true`, `access: "append_only"`, and `canAppend: true`.
357
365
  - When `Agent Wiki/log.md` is missing and status reports `suggestedAction: "init"`, `append-log` returns a JSON error telling Atlas to run `wave osmd wiki init`.
358
366
  - Missing `Agent Wiki/index.md` is create-ready when status returns `exists: false`, `canCreate: true`, `canUpdate: false`, and `suggestedAction: "create"`.
367
+ - Missing controlled pages are create-ready when backend status returns `exists: false`, `canCreate: true`, `canUpdate: false`, and `suggestedAction: "create"`.
359
368
 
360
369
  Atlas Agent Wiki log flow:
361
370
 
@@ -377,6 +386,22 @@ wave osmd wiki read index.md
377
386
  wave osmd wiki update index.md --content "# Agent Wiki\n\n## Entity Notes\n\n- directory/Wave Tools Team -> .agent/notes.md"
378
387
  ```
379
388
 
389
+ Create or update controlled Agent Wiki pages. Atlas should call `status` first, then choose `create` or `update` from backend `canCreate`, `canUpdate`, and `suggestedAction`:
390
+
391
+ ```bash
392
+ wave osmd wiki status concepts/customer-health.md
393
+ wave osmd wiki create concepts/customer-health.md --content "# Customer Health\n\nDurable definition and usage notes."
394
+
395
+ wave osmd wiki status decisions/use-agent-wiki.md
396
+ wave osmd wiki update decisions/use-agent-wiki.md --content "# Use Agent Wiki\n\nDecision: store broad agent memory in controlled Agent Wiki pages."
397
+
398
+ wave osmd wiki status playbooks/customer-review.md
399
+ wave osmd wiki create playbooks/customer-review.md --content "# Customer Review\n\n1. Read the customer summary.\n2. Review recent notes.\n3. Append the maintenance log."
400
+
401
+ wave osmd wiki status org-summaries/galago.md
402
+ wave osmd wiki update org-summaries/galago.md --content "# Galago\n\nCurrent durable organization summary."
403
+ ```
404
+
380
405
  Append a parseable log entry. Use a stable fenced block so Atlas or backend jobs can parse the chronology later:
381
406
 
382
407
  ````bash
@@ -429,6 +454,44 @@ OSMD command envelopes keep the global CLI envelope and put the file metadata di
429
454
  }
430
455
  ```
431
456
 
457
+ Merged OSMD search keeps the same envelope and returns backend-ranked candidates:
458
+
459
+ ```json
460
+ {
461
+ "ok": true,
462
+ "command": "osmd.search",
463
+ "status": 200,
464
+ "data": {
465
+ "query": "Wave Tools",
466
+ "scope": "combined",
467
+ "count": 3,
468
+ "candidates": [
469
+ {
470
+ "path": "directory/Wave Tools Team/.agent/notes.md",
471
+ "source": "agent_overlay",
472
+ "access": "read_write",
473
+ "owner": "atlas",
474
+ "wikiRole": "notes",
475
+ "formatVersion": "agent_markdown_v1",
476
+ "parentRef": "directory/Wave Tools Team",
477
+ "exists": true,
478
+ "agentChildrenAllowed": false,
479
+ "title": "notes.md",
480
+ "label": "notes.md",
481
+ "snippet": "...",
482
+ "rank": 1,
483
+ "matchReason": "content",
484
+ "toolKey": "agent_overlay",
485
+ "nodeKey": "notes",
486
+ "nodeKind": "agent_overlay_file"
487
+ }
488
+ ]
489
+ },
490
+ "error": null,
491
+ "meta": { "requestId": "req_125" }
492
+ }
493
+ ```
494
+
432
495
  On write preflight errors, `data` contains the status metadata and `error.suggestedAction` mirrors backend guidance:
433
496
 
434
497
  ```json
@@ -482,6 +545,12 @@ These wrap backend-owned markdown-tree primitives:
482
545
  Discovery, ranking, canonical paths, and narrowing are backend-owned.
483
546
  An empty `find` result (`candidates: []`) means no evidence for that exact query/scope, not confirmed global absence.
484
547
  `markdownTreeFind` remains canonical-focused. Agent overlay and Agent Wiki files are discoverable through `osmd tree`, `osmd children`, and `osmd status`, not through `find`.
548
+ 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:
549
+
550
+ - `--scope os-only` searches canonical OSMD only.
551
+ - `--scope agent-overlay-only` searches entity-specific `.agent` files.
552
+ - `--scope agent-wiki-only` searches broad Agent Wiki files, including `index.md`, controlled pages, and append-only `log.md`.
553
+ - `--scope combined` searches the backend merged OSMD surface.
485
554
 
486
555
  Ambiguity contract:
487
556
 
package/dist/index.cjs CHANGED
@@ -6143,10 +6143,26 @@ var agentMarkdownErrorCodeSchema = import_zod16.z.enum([
6143
6143
  var agentMarkdownErrorCodeFieldSchema = agentMarkdownErrorCodeSchema.nullable().catch(null);
6144
6144
  var nonEmptyString3 = import_zod16.z.string().min(1);
6145
6145
  var nonNegativeInt3 = import_zod16.z.coerce.number().int().min(0);
6146
+ var positiveInt = import_zod16.z.coerce.number().int().min(1);
6146
6147
  var supportedAgentOverlayFile = "notes.md";
6147
6148
  var supportedWikiIndexFile = "index.md";
6148
6149
  var supportedWikiLogFile = "log.md";
6150
+ var supportedWikiControlledFolders = [
6151
+ "concepts",
6152
+ "playbooks",
6153
+ "decisions",
6154
+ "org-summaries"
6155
+ ];
6156
+ var supportedWikiControlledPatterns = supportedWikiControlledFolders.map(
6157
+ (folder) => `${folder}/*.md`
6158
+ );
6149
6159
  var agentOverlayFileSchema = import_zod16.z.enum([supportedAgentOverlayFile]);
6160
+ var osmdSearchScopeSchema = import_zod16.z.enum([
6161
+ "os-only",
6162
+ "agent-overlay-only",
6163
+ "agent-wiki-only",
6164
+ "combined"
6165
+ ]);
6150
6166
  var agentMarkdownFileSchema = import_zod16.z.object({
6151
6167
  path: import_zod16.z.string(),
6152
6168
  source: sourceSchema,
@@ -6178,6 +6194,47 @@ var agentMarkdownMutationDataSchema = import_zod16.z.union([
6178
6194
  agentMarkdownFileSchema,
6179
6195
  import_zod16.z.object({ files: agentMarkdownFilesSchema })
6180
6196
  ]);
6197
+ var osmdSearchCandidateSchema = import_zod16.z.preprocess(
6198
+ normalizeAgentMarkdownJsonKeys,
6199
+ import_zod16.z.object({
6200
+ path: import_zod16.z.string(),
6201
+ source: sourceSchema,
6202
+ access: accessSchema,
6203
+ owner: ownerSchema,
6204
+ wikiRole: import_zod16.z.string().nullable().optional(),
6205
+ formatVersion: import_zod16.z.string().nullable().optional(),
6206
+ parentRef: import_zod16.z.string().nullable().optional(),
6207
+ exists: import_zod16.z.boolean(),
6208
+ agentChildrenAllowed: import_zod16.z.boolean(),
6209
+ title: import_zod16.z.string(),
6210
+ label: import_zod16.z.string(),
6211
+ snippet: import_zod16.z.string().nullable().optional(),
6212
+ rank: import_zod16.z.number().int(),
6213
+ matchReason: import_zod16.z.string().nullable().optional(),
6214
+ toolKey: import_zod16.z.string().nullable().optional(),
6215
+ nodeKey: import_zod16.z.string().nullable().optional(),
6216
+ nodeKind: import_zod16.z.string().nullable().optional()
6217
+ }).transform((value) => ({
6218
+ ...value,
6219
+ wikiRole: value.wikiRole ?? null,
6220
+ formatVersion: value.formatVersion ?? null,
6221
+ parentRef: value.parentRef ?? null,
6222
+ snippet: value.snippet ?? null,
6223
+ matchReason: value.matchReason ?? null,
6224
+ toolKey: value.toolKey ?? null,
6225
+ nodeKey: value.nodeKey ?? null,
6226
+ nodeKind: value.nodeKind ?? null
6227
+ }))
6228
+ );
6229
+ var osmdSearchResultSchema = import_zod16.z.preprocess(
6230
+ normalizeAgentMarkdownJsonKeys,
6231
+ import_zod16.z.object({
6232
+ query: import_zod16.z.string(),
6233
+ scope: import_zod16.z.string(),
6234
+ count: import_zod16.z.number().int().min(0),
6235
+ candidates: import_zod16.z.array(osmdSearchCandidateSchema)
6236
+ })
6237
+ );
6181
6238
  var mutationPayloadSchema = import_zod16.z.object({
6182
6239
  ok: import_zod16.z.boolean(),
6183
6240
  status: import_zod16.z.number(),
@@ -6229,6 +6286,30 @@ var AGENT_MARKDOWN_FILE_SELECTION = `{
6229
6286
  content
6230
6287
  url
6231
6288
  }`;
6289
+ var OSMD_SEARCH_SELECTION = `{
6290
+ query
6291
+ scope
6292
+ count
6293
+ candidates {
6294
+ path
6295
+ source
6296
+ access
6297
+ owner
6298
+ wikiRole
6299
+ formatVersion
6300
+ parentRef
6301
+ exists
6302
+ agentChildrenAllowed
6303
+ title
6304
+ label
6305
+ snippet
6306
+ rank
6307
+ matchReason
6308
+ toolKey
6309
+ nodeKey
6310
+ nodeKind
6311
+ }
6312
+ }`;
6232
6313
  var MUTATION_RESULT_SELECTION = "{ ok status errorCode data errors }";
6233
6314
  var AGENT_JSON_KEY_ALIASES = {
6234
6315
  can_create: "canCreate",
@@ -6239,7 +6320,11 @@ var AGENT_JSON_KEY_ALIASES = {
6239
6320
  parent_ref: "parentRef",
6240
6321
  agent_children_allowed: "agentChildrenAllowed",
6241
6322
  suggested_action: "suggestedAction",
6242
- error_code: "errorCode"
6323
+ error_code: "errorCode",
6324
+ match_reason: "matchReason",
6325
+ tool_key: "toolKey",
6326
+ node_key: "nodeKey",
6327
+ node_kind: "nodeKind"
6243
6328
  };
6244
6329
  function isSupportedAgentMarkdownFile(file) {
6245
6330
  if (file.source !== "agent_overlay") {
@@ -6391,6 +6476,22 @@ async function requestAgentMarkdownChildren(params) {
6391
6476
  schema: agentMarkdownFilesSchema
6392
6477
  });
6393
6478
  }
6479
+ async function requestOsmdSearch(params) {
6480
+ return requestOsmdQuery({
6481
+ command: params.command,
6482
+ operationName: "OsMdSearch",
6483
+ runtimeOptions: params.runtimeOptions,
6484
+ field: "os_md_search",
6485
+ variables: {
6486
+ organization_id: params.organizationId,
6487
+ query: params.query,
6488
+ source: backendSearchSourceForScope(params.scope),
6489
+ limit: params.limit
6490
+ },
6491
+ selectionSet: OSMD_SEARCH_SELECTION,
6492
+ schema: osmdSearchResultSchema
6493
+ });
6494
+ }
6394
6495
  async function runAgentMarkdownMutation(params) {
6395
6496
  const result = await graphqlRequest({
6396
6497
  config: getConfig(params.runtimeOptions),
@@ -6448,41 +6549,113 @@ function normalizeAgentFilePath(file) {
6448
6549
  }
6449
6550
  return `.agent/${parsed.data}`;
6450
6551
  }
6451
- function supportedWikiFilesMessage(files) {
6452
- return `Agent Wiki file commands support only ${files.join(" and ")} in this CLI slice.`;
6552
+ function formatHumanList(items) {
6553
+ if (items.length <= 1) {
6554
+ return items[0] ?? "";
6555
+ }
6556
+ if (items.length === 2) {
6557
+ return `${items[0]} and ${items[1]}`;
6558
+ }
6559
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
6560
+ }
6561
+ function supportedWikiPathsMessage(options = {}) {
6562
+ const allowed = allowedWikiPathLabels(options);
6563
+ return `Agent Wiki file commands support only ${formatHumanList(allowed)} in this CLI slice.`;
6564
+ }
6565
+ function allowedWikiPathLabels(options = {}) {
6566
+ const allowIndex = options.allowIndex ?? true;
6567
+ const allowLog = options.allowLog ?? true;
6568
+ const allowControlledPages = options.allowControlledPages ?? true;
6569
+ const labels = [];
6570
+ if (options.allowRoot) {
6571
+ labels.push("Agent Wiki");
6572
+ }
6573
+ if (allowIndex) {
6574
+ labels.push(supportedWikiIndexFile);
6575
+ }
6576
+ if (allowLog) {
6577
+ labels.push(supportedWikiLogFile);
6578
+ }
6579
+ if (allowControlledPages) {
6580
+ labels.push(...supportedWikiControlledPatterns);
6581
+ }
6582
+ if (options.allowControlledFolders) {
6583
+ labels.push(...supportedWikiControlledFolders);
6584
+ }
6585
+ return labels;
6586
+ }
6587
+ function rejectUnsupportedWikiPath(options) {
6588
+ throw new CliError({
6589
+ message: supportedWikiPathsMessage(options),
6590
+ kind: "invalid_args",
6591
+ status: 400,
6592
+ exitCode: EXIT_CODES.invalidArgs,
6593
+ details: { supportedPaths: allowedWikiPathLabels(options) }
6594
+ });
6595
+ }
6596
+ function isControlledWikiFolder(value) {
6597
+ return supportedWikiControlledFolders.includes(
6598
+ value
6599
+ );
6600
+ }
6601
+ function isMarkdownLeafFileName(value) {
6602
+ return value.endsWith(".md") && value.length > ".md".length;
6453
6603
  }
6454
6604
  function normalizeWikiPath(path, options = {}) {
6455
- const supportedFiles = options.supportedFiles ?? [supportedWikiIndexFile, supportedWikiLogFile];
6605
+ const normalizedOptions = {
6606
+ allowIndex: options.allowIndex ?? true,
6607
+ allowLog: options.allowLog ?? true,
6608
+ allowControlledPages: options.allowControlledPages ?? true,
6609
+ allowRoot: options.allowRoot ?? false,
6610
+ allowControlledFolders: options.allowControlledFolders ?? false
6611
+ };
6456
6612
  const trimmed = nonEmptyString3.parse(path).trim().replace(/^\/+/, "");
6457
6613
  if (trimmed.localeCompare("Agent Wiki", void 0, { sensitivity: "accent" }) === 0) {
6458
- if (options.allowRoot) {
6614
+ if (normalizedOptions.allowRoot) {
6459
6615
  return "Agent Wiki";
6460
6616
  }
6461
- throw new CliError({
6462
- message: supportedWikiFilesMessage(supportedFiles),
6463
- kind: "invalid_args",
6464
- status: 400,
6465
- exitCode: EXIT_CODES.invalidArgs,
6466
- details: { supportedFiles }
6467
- });
6617
+ return rejectUnsupportedWikiPath(normalizedOptions);
6468
6618
  }
6469
6619
  const fileName = trimmed.toLowerCase().startsWith("agent wiki/") ? trimmed.slice("Agent Wiki/".length) : trimmed;
6470
- if (!supportedFiles.includes(fileName)) {
6471
- throw new CliError({
6472
- message: supportedWikiFilesMessage(supportedFiles),
6473
- kind: "invalid_args",
6474
- status: 400,
6475
- exitCode: EXIT_CODES.invalidArgs,
6476
- details: { supportedFiles }
6477
- });
6620
+ if (fileName.includes("\\") || fileName.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
6621
+ return rejectUnsupportedWikiPath(normalizedOptions);
6622
+ }
6623
+ if (fileName === supportedWikiIndexFile) {
6624
+ if (normalizedOptions.allowIndex) {
6625
+ return `Agent Wiki/${fileName}`;
6626
+ }
6627
+ return rejectUnsupportedWikiPath(normalizedOptions);
6628
+ }
6629
+ if (fileName === supportedWikiLogFile) {
6630
+ if (normalizedOptions.allowLog) {
6631
+ return `Agent Wiki/${fileName}`;
6632
+ }
6633
+ return rejectUnsupportedWikiPath(normalizedOptions);
6634
+ }
6635
+ if (isControlledWikiFolder(fileName)) {
6636
+ if (normalizedOptions.allowControlledFolders) {
6637
+ return `Agent Wiki/${fileName}`;
6638
+ }
6639
+ return rejectUnsupportedWikiPath(normalizedOptions);
6640
+ }
6641
+ const parts = fileName.split("/");
6642
+ if (parts.length === 2 && isControlledWikiFolder(parts[0] ?? "") && isMarkdownLeafFileName(parts[1] ?? "")) {
6643
+ if (normalizedOptions.allowControlledPages) {
6644
+ return `Agent Wiki/${fileName}`;
6645
+ }
6646
+ return rejectUnsupportedWikiPath(normalizedOptions);
6478
6647
  }
6479
- return `Agent Wiki/${fileName}`;
6648
+ return rejectUnsupportedWikiPath(normalizedOptions);
6480
6649
  }
6481
- function normalizeWikiIndexPath(path) {
6482
- return normalizeWikiPath(path, { supportedFiles: [supportedWikiIndexFile] });
6650
+ function normalizeWikiWritablePath(path) {
6651
+ return normalizeWikiPath(path, { allowLog: false });
6483
6652
  }
6484
6653
  function normalizeWikiLogPath(path = supportedWikiLogFile) {
6485
- return normalizeWikiPath(path, { supportedFiles: [supportedWikiLogFile] });
6654
+ return normalizeWikiPath(path, {
6655
+ allowIndex: false,
6656
+ allowLog: true,
6657
+ allowControlledPages: false
6658
+ });
6486
6659
  }
6487
6660
  function parseOptionalParentRef(value) {
6488
6661
  return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
@@ -6503,6 +6676,46 @@ function parseOptionalSource(value) {
6503
6676
  }
6504
6677
  return parsed.data;
6505
6678
  }
6679
+ function parseOsmdSearchScope(value) {
6680
+ const raw = typeof value === "string" && value.trim() !== "" ? value.trim() : "combined";
6681
+ const parsed = osmdSearchScopeSchema.safeParse(raw);
6682
+ if (!parsed.success) {
6683
+ throw new CliError({
6684
+ message: "Invalid OSMD search scope. Use os-only, agent-overlay-only, agent-wiki-only, or combined.",
6685
+ kind: "invalid_args",
6686
+ status: 400,
6687
+ exitCode: EXIT_CODES.invalidArgs,
6688
+ details: { supportedScopes: osmdSearchScopeSchema.options }
6689
+ });
6690
+ }
6691
+ return parsed.data;
6692
+ }
6693
+ function backendSearchSourceForScope(scope) {
6694
+ if (scope === "os-only") {
6695
+ return "canonical_osmd";
6696
+ }
6697
+ if (scope === "agent-overlay-only") {
6698
+ return "agent_overlay";
6699
+ }
6700
+ if (scope === "agent-wiki-only") {
6701
+ return "agent_wiki";
6702
+ }
6703
+ return "combined";
6704
+ }
6705
+ function parseSearchLimit(value) {
6706
+ const raw = value === void 0 || value === null || value === "" ? "10" : value;
6707
+ const parsed = positiveInt.safeParse(raw);
6708
+ if (!parsed.success) {
6709
+ throw new CliError({
6710
+ message: "Invalid OSMD search limit. Use a positive integer.",
6711
+ kind: "invalid_args",
6712
+ status: 400,
6713
+ exitCode: EXIT_CODES.invalidArgs,
6714
+ details: { issues: parsed.error.issues }
6715
+ });
6716
+ }
6717
+ return parsed.data;
6718
+ }
6506
6719
  function parseDepth(value) {
6507
6720
  if (value === void 0 || value === null || value === "") {
6508
6721
  return 1;
@@ -6792,7 +7005,7 @@ function registerOsmdCommands(program) {
6792
7005
  })
6793
7006
  });
6794
7007
  });
6795
- osmd.command("children [path]").description("List backend-supported agent markdown children; search remains canonical-only").option("--parent-ref <parentRef>", "Canonical OSMD parent ref for .agent files").option("--source <source>", "Filter by canonical_osmd, agent_overlay, or agent_wiki").action(async (path, opts, cmd) => {
7008
+ osmd.command("children [path]").description("List backend-supported agent markdown children").option("--parent-ref <parentRef>", "Canonical OSMD parent ref for .agent files").option("--source <source>", "Filter by canonical_osmd, agent_overlay, or agent_wiki").action(async (path, opts, cmd) => {
6796
7009
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
6797
7010
  const result = await requestAgentMarkdownChildren({
6798
7011
  command: "osmd.children",
@@ -6811,6 +7024,25 @@ function registerOsmdCommands(program) {
6811
7024
  })
6812
7025
  });
6813
7026
  });
7027
+ osmd.command("search <query>").description("Search merged OSMD, .agent overlays, and Agent Wiki through backend OSMD search").option("--scope <scope>", "os-only, agent-overlay-only, agent-wiki-only, or combined", "combined").option("--limit <limit>", "Maximum number of results", "10").action(async (query, opts, cmd) => {
7028
+ const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
7029
+ const result = await requestOsmdSearch({
7030
+ command: "osmd.search",
7031
+ runtimeOptions,
7032
+ organizationId,
7033
+ query: nonEmptyString3.parse(query).trim(),
7034
+ scope: parseOsmdSearchScope(opts.scope),
7035
+ limit: parseSearchLimit(opts.limit)
7036
+ });
7037
+ return printEnvelopeAndExit({
7038
+ envelope: buildSuccessEnvelope({
7039
+ command: "osmd.search",
7040
+ status: result.status,
7041
+ data: result.data,
7042
+ requestId: result.requestId
7043
+ })
7044
+ });
7045
+ });
6814
7046
  const agent = osmd.command("agent").description(".agent overlay memory attached to canonical OSMD parents; supports notes.md");
6815
7047
  agent.command("status <parent-ref> <file>").description("Show .agent notes.md metadata without creating files").action(async (parentRef, file, _opts, cmd) => {
6816
7048
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
@@ -6898,7 +7130,7 @@ function registerOsmdCommands(program) {
6898
7130
  });
6899
7131
  }
6900
7132
  );
6901
- const wiki = osmd.command("wiki").description("Agent Wiki files writable by agents; supports index.md and append-only log.md");
7133
+ const wiki = osmd.command("wiki").description("Agent Wiki files writable by agents; supports index.md, controlled pages, and append-only log.md");
6902
7134
  wiki.command("init").description("Idempotently initialize Agent Wiki system files").action(async function() {
6903
7135
  const { organizationId, runtimeOptions } = await resolveOsmdContext(this);
6904
7136
  return runAgentMarkdownMutation({
@@ -6912,7 +7144,7 @@ function registerOsmdCommands(program) {
6912
7144
  }
6913
7145
  });
6914
7146
  });
6915
- wiki.command("status <path>").description("Show Agent Wiki index.md or log.md metadata without creating files").action(async (path, _opts, cmd) => {
7147
+ wiki.command("status <path>").description("Show Agent Wiki index.md, log.md, or controlled page metadata without creating files").action(async (path, _opts, cmd) => {
6916
7148
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
6917
7149
  const result = await requestAgentMarkdownStatus({
6918
7150
  command: "osmd.wiki.status",
@@ -6929,7 +7161,7 @@ function registerOsmdCommands(program) {
6929
7161
  })
6930
7162
  });
6931
7163
  });
6932
- wiki.command("read <path>").description("Read Agent Wiki index.md or log.md without creating files").action(async (path, _opts, cmd) => {
7164
+ wiki.command("read <path>").description("Read Agent Wiki index.md, log.md, or controlled pages without creating files").action(async (path, _opts, cmd) => {
6933
7165
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
6934
7166
  const result = await requestAgentMarkdownFile({
6935
7167
  command: "osmd.wiki.read",
@@ -6952,7 +7184,7 @@ function registerOsmdCommands(program) {
6952
7184
  command: "osmd.wiki.children",
6953
7185
  runtimeOptions,
6954
7186
  organizationId,
6955
- path: typeof path === "string" && path.trim() !== "" ? normalizeWikiPath(path, { allowRoot: true }) : "Agent Wiki",
7187
+ path: typeof path === "string" && path.trim() !== "" ? normalizeWikiPath(path, { allowRoot: true, allowControlledFolders: true }) : "Agent Wiki",
6956
7188
  source: "agent_wiki"
6957
7189
  });
6958
7190
  return printEnvelopeAndExit({
@@ -6965,7 +7197,7 @@ function registerOsmdCommands(program) {
6965
7197
  });
6966
7198
  });
6967
7199
  addContentOptions(
6968
- wiki.command("create <path>").description("Create Agent Wiki index.md after status/permission preflight")
7200
+ wiki.command("create <path>").description("Create Agent Wiki index.md or controlled pages after status/permission preflight")
6969
7201
  ).action(async (path, opts, cmd) => {
6970
7202
  const globals = cmd.optsWithGlobals();
6971
7203
  const content = await resolveContentInput(opts, globals);
@@ -6975,12 +7207,12 @@ function registerOsmdCommands(program) {
6975
7207
  action: "create",
6976
7208
  runtimeOptions,
6977
7209
  organizationId,
6978
- path: normalizeWikiIndexPath(path),
7210
+ path: normalizeWikiWritablePath(path),
6979
7211
  content
6980
7212
  });
6981
7213
  });
6982
7214
  addContentOptions(
6983
- wiki.command("update <path>").description("Update Agent Wiki index.md after status/permission preflight")
7215
+ wiki.command("update <path>").description("Update Agent Wiki index.md or controlled pages after status/permission preflight")
6984
7216
  ).action(async (path, opts, cmd) => {
6985
7217
  const globals = cmd.optsWithGlobals();
6986
7218
  const content = await resolveContentInput(opts, globals);
@@ -6990,7 +7222,7 @@ function registerOsmdCommands(program) {
6990
7222
  action: "update",
6991
7223
  runtimeOptions,
6992
7224
  organizationId,
6993
- path: normalizeWikiIndexPath(path),
7225
+ path: normalizeWikiWritablePath(path),
6994
7226
  content
6995
7227
  });
6996
7228
  });
package/dist/index.js CHANGED
@@ -6142,10 +6142,26 @@ var agentMarkdownErrorCodeSchema = z16.enum([
6142
6142
  var agentMarkdownErrorCodeFieldSchema = agentMarkdownErrorCodeSchema.nullable().catch(null);
6143
6143
  var nonEmptyString3 = z16.string().min(1);
6144
6144
  var nonNegativeInt3 = z16.coerce.number().int().min(0);
6145
+ var positiveInt = z16.coerce.number().int().min(1);
6145
6146
  var supportedAgentOverlayFile = "notes.md";
6146
6147
  var supportedWikiIndexFile = "index.md";
6147
6148
  var supportedWikiLogFile = "log.md";
6149
+ var supportedWikiControlledFolders = [
6150
+ "concepts",
6151
+ "playbooks",
6152
+ "decisions",
6153
+ "org-summaries"
6154
+ ];
6155
+ var supportedWikiControlledPatterns = supportedWikiControlledFolders.map(
6156
+ (folder) => `${folder}/*.md`
6157
+ );
6148
6158
  var agentOverlayFileSchema = z16.enum([supportedAgentOverlayFile]);
6159
+ var osmdSearchScopeSchema = z16.enum([
6160
+ "os-only",
6161
+ "agent-overlay-only",
6162
+ "agent-wiki-only",
6163
+ "combined"
6164
+ ]);
6149
6165
  var agentMarkdownFileSchema = z16.object({
6150
6166
  path: z16.string(),
6151
6167
  source: sourceSchema,
@@ -6177,6 +6193,47 @@ var agentMarkdownMutationDataSchema = z16.union([
6177
6193
  agentMarkdownFileSchema,
6178
6194
  z16.object({ files: agentMarkdownFilesSchema })
6179
6195
  ]);
6196
+ var osmdSearchCandidateSchema = z16.preprocess(
6197
+ normalizeAgentMarkdownJsonKeys,
6198
+ z16.object({
6199
+ path: z16.string(),
6200
+ source: sourceSchema,
6201
+ access: accessSchema,
6202
+ owner: ownerSchema,
6203
+ wikiRole: z16.string().nullable().optional(),
6204
+ formatVersion: z16.string().nullable().optional(),
6205
+ parentRef: z16.string().nullable().optional(),
6206
+ exists: z16.boolean(),
6207
+ agentChildrenAllowed: z16.boolean(),
6208
+ title: z16.string(),
6209
+ label: z16.string(),
6210
+ snippet: z16.string().nullable().optional(),
6211
+ rank: z16.number().int(),
6212
+ matchReason: z16.string().nullable().optional(),
6213
+ toolKey: z16.string().nullable().optional(),
6214
+ nodeKey: z16.string().nullable().optional(),
6215
+ nodeKind: z16.string().nullable().optional()
6216
+ }).transform((value) => ({
6217
+ ...value,
6218
+ wikiRole: value.wikiRole ?? null,
6219
+ formatVersion: value.formatVersion ?? null,
6220
+ parentRef: value.parentRef ?? null,
6221
+ snippet: value.snippet ?? null,
6222
+ matchReason: value.matchReason ?? null,
6223
+ toolKey: value.toolKey ?? null,
6224
+ nodeKey: value.nodeKey ?? null,
6225
+ nodeKind: value.nodeKind ?? null
6226
+ }))
6227
+ );
6228
+ var osmdSearchResultSchema = z16.preprocess(
6229
+ normalizeAgentMarkdownJsonKeys,
6230
+ z16.object({
6231
+ query: z16.string(),
6232
+ scope: z16.string(),
6233
+ count: z16.number().int().min(0),
6234
+ candidates: z16.array(osmdSearchCandidateSchema)
6235
+ })
6236
+ );
6180
6237
  var mutationPayloadSchema = z16.object({
6181
6238
  ok: z16.boolean(),
6182
6239
  status: z16.number(),
@@ -6228,6 +6285,30 @@ var AGENT_MARKDOWN_FILE_SELECTION = `{
6228
6285
  content
6229
6286
  url
6230
6287
  }`;
6288
+ var OSMD_SEARCH_SELECTION = `{
6289
+ query
6290
+ scope
6291
+ count
6292
+ candidates {
6293
+ path
6294
+ source
6295
+ access
6296
+ owner
6297
+ wikiRole
6298
+ formatVersion
6299
+ parentRef
6300
+ exists
6301
+ agentChildrenAllowed
6302
+ title
6303
+ label
6304
+ snippet
6305
+ rank
6306
+ matchReason
6307
+ toolKey
6308
+ nodeKey
6309
+ nodeKind
6310
+ }
6311
+ }`;
6231
6312
  var MUTATION_RESULT_SELECTION = "{ ok status errorCode data errors }";
6232
6313
  var AGENT_JSON_KEY_ALIASES = {
6233
6314
  can_create: "canCreate",
@@ -6238,7 +6319,11 @@ var AGENT_JSON_KEY_ALIASES = {
6238
6319
  parent_ref: "parentRef",
6239
6320
  agent_children_allowed: "agentChildrenAllowed",
6240
6321
  suggested_action: "suggestedAction",
6241
- error_code: "errorCode"
6322
+ error_code: "errorCode",
6323
+ match_reason: "matchReason",
6324
+ tool_key: "toolKey",
6325
+ node_key: "nodeKey",
6326
+ node_kind: "nodeKind"
6242
6327
  };
6243
6328
  function isSupportedAgentMarkdownFile(file) {
6244
6329
  if (file.source !== "agent_overlay") {
@@ -6390,6 +6475,22 @@ async function requestAgentMarkdownChildren(params) {
6390
6475
  schema: agentMarkdownFilesSchema
6391
6476
  });
6392
6477
  }
6478
+ async function requestOsmdSearch(params) {
6479
+ return requestOsmdQuery({
6480
+ command: params.command,
6481
+ operationName: "OsMdSearch",
6482
+ runtimeOptions: params.runtimeOptions,
6483
+ field: "os_md_search",
6484
+ variables: {
6485
+ organization_id: params.organizationId,
6486
+ query: params.query,
6487
+ source: backendSearchSourceForScope(params.scope),
6488
+ limit: params.limit
6489
+ },
6490
+ selectionSet: OSMD_SEARCH_SELECTION,
6491
+ schema: osmdSearchResultSchema
6492
+ });
6493
+ }
6393
6494
  async function runAgentMarkdownMutation(params) {
6394
6495
  const result = await graphqlRequest({
6395
6496
  config: getConfig(params.runtimeOptions),
@@ -6447,41 +6548,113 @@ function normalizeAgentFilePath(file) {
6447
6548
  }
6448
6549
  return `.agent/${parsed.data}`;
6449
6550
  }
6450
- function supportedWikiFilesMessage(files) {
6451
- return `Agent Wiki file commands support only ${files.join(" and ")} in this CLI slice.`;
6551
+ function formatHumanList(items) {
6552
+ if (items.length <= 1) {
6553
+ return items[0] ?? "";
6554
+ }
6555
+ if (items.length === 2) {
6556
+ return `${items[0]} and ${items[1]}`;
6557
+ }
6558
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
6559
+ }
6560
+ function supportedWikiPathsMessage(options = {}) {
6561
+ const allowed = allowedWikiPathLabels(options);
6562
+ return `Agent Wiki file commands support only ${formatHumanList(allowed)} in this CLI slice.`;
6563
+ }
6564
+ function allowedWikiPathLabels(options = {}) {
6565
+ const allowIndex = options.allowIndex ?? true;
6566
+ const allowLog = options.allowLog ?? true;
6567
+ const allowControlledPages = options.allowControlledPages ?? true;
6568
+ const labels = [];
6569
+ if (options.allowRoot) {
6570
+ labels.push("Agent Wiki");
6571
+ }
6572
+ if (allowIndex) {
6573
+ labels.push(supportedWikiIndexFile);
6574
+ }
6575
+ if (allowLog) {
6576
+ labels.push(supportedWikiLogFile);
6577
+ }
6578
+ if (allowControlledPages) {
6579
+ labels.push(...supportedWikiControlledPatterns);
6580
+ }
6581
+ if (options.allowControlledFolders) {
6582
+ labels.push(...supportedWikiControlledFolders);
6583
+ }
6584
+ return labels;
6585
+ }
6586
+ function rejectUnsupportedWikiPath(options) {
6587
+ throw new CliError({
6588
+ message: supportedWikiPathsMessage(options),
6589
+ kind: "invalid_args",
6590
+ status: 400,
6591
+ exitCode: EXIT_CODES.invalidArgs,
6592
+ details: { supportedPaths: allowedWikiPathLabels(options) }
6593
+ });
6594
+ }
6595
+ function isControlledWikiFolder(value) {
6596
+ return supportedWikiControlledFolders.includes(
6597
+ value
6598
+ );
6599
+ }
6600
+ function isMarkdownLeafFileName(value) {
6601
+ return value.endsWith(".md") && value.length > ".md".length;
6452
6602
  }
6453
6603
  function normalizeWikiPath(path, options = {}) {
6454
- const supportedFiles = options.supportedFiles ?? [supportedWikiIndexFile, supportedWikiLogFile];
6604
+ const normalizedOptions = {
6605
+ allowIndex: options.allowIndex ?? true,
6606
+ allowLog: options.allowLog ?? true,
6607
+ allowControlledPages: options.allowControlledPages ?? true,
6608
+ allowRoot: options.allowRoot ?? false,
6609
+ allowControlledFolders: options.allowControlledFolders ?? false
6610
+ };
6455
6611
  const trimmed = nonEmptyString3.parse(path).trim().replace(/^\/+/, "");
6456
6612
  if (trimmed.localeCompare("Agent Wiki", void 0, { sensitivity: "accent" }) === 0) {
6457
- if (options.allowRoot) {
6613
+ if (normalizedOptions.allowRoot) {
6458
6614
  return "Agent Wiki";
6459
6615
  }
6460
- throw new CliError({
6461
- message: supportedWikiFilesMessage(supportedFiles),
6462
- kind: "invalid_args",
6463
- status: 400,
6464
- exitCode: EXIT_CODES.invalidArgs,
6465
- details: { supportedFiles }
6466
- });
6616
+ return rejectUnsupportedWikiPath(normalizedOptions);
6467
6617
  }
6468
6618
  const fileName = trimmed.toLowerCase().startsWith("agent wiki/") ? trimmed.slice("Agent Wiki/".length) : trimmed;
6469
- if (!supportedFiles.includes(fileName)) {
6470
- throw new CliError({
6471
- message: supportedWikiFilesMessage(supportedFiles),
6472
- kind: "invalid_args",
6473
- status: 400,
6474
- exitCode: EXIT_CODES.invalidArgs,
6475
- details: { supportedFiles }
6476
- });
6619
+ if (fileName.includes("\\") || fileName.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
6620
+ return rejectUnsupportedWikiPath(normalizedOptions);
6621
+ }
6622
+ if (fileName === supportedWikiIndexFile) {
6623
+ if (normalizedOptions.allowIndex) {
6624
+ return `Agent Wiki/${fileName}`;
6625
+ }
6626
+ return rejectUnsupportedWikiPath(normalizedOptions);
6627
+ }
6628
+ if (fileName === supportedWikiLogFile) {
6629
+ if (normalizedOptions.allowLog) {
6630
+ return `Agent Wiki/${fileName}`;
6631
+ }
6632
+ return rejectUnsupportedWikiPath(normalizedOptions);
6633
+ }
6634
+ if (isControlledWikiFolder(fileName)) {
6635
+ if (normalizedOptions.allowControlledFolders) {
6636
+ return `Agent Wiki/${fileName}`;
6637
+ }
6638
+ return rejectUnsupportedWikiPath(normalizedOptions);
6639
+ }
6640
+ const parts = fileName.split("/");
6641
+ if (parts.length === 2 && isControlledWikiFolder(parts[0] ?? "") && isMarkdownLeafFileName(parts[1] ?? "")) {
6642
+ if (normalizedOptions.allowControlledPages) {
6643
+ return `Agent Wiki/${fileName}`;
6644
+ }
6645
+ return rejectUnsupportedWikiPath(normalizedOptions);
6477
6646
  }
6478
- return `Agent Wiki/${fileName}`;
6647
+ return rejectUnsupportedWikiPath(normalizedOptions);
6479
6648
  }
6480
- function normalizeWikiIndexPath(path) {
6481
- return normalizeWikiPath(path, { supportedFiles: [supportedWikiIndexFile] });
6649
+ function normalizeWikiWritablePath(path) {
6650
+ return normalizeWikiPath(path, { allowLog: false });
6482
6651
  }
6483
6652
  function normalizeWikiLogPath(path = supportedWikiLogFile) {
6484
- return normalizeWikiPath(path, { supportedFiles: [supportedWikiLogFile] });
6653
+ return normalizeWikiPath(path, {
6654
+ allowIndex: false,
6655
+ allowLog: true,
6656
+ allowControlledPages: false
6657
+ });
6485
6658
  }
6486
6659
  function parseOptionalParentRef(value) {
6487
6660
  return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
@@ -6502,6 +6675,46 @@ function parseOptionalSource(value) {
6502
6675
  }
6503
6676
  return parsed.data;
6504
6677
  }
6678
+ function parseOsmdSearchScope(value) {
6679
+ const raw = typeof value === "string" && value.trim() !== "" ? value.trim() : "combined";
6680
+ const parsed = osmdSearchScopeSchema.safeParse(raw);
6681
+ if (!parsed.success) {
6682
+ throw new CliError({
6683
+ message: "Invalid OSMD search scope. Use os-only, agent-overlay-only, agent-wiki-only, or combined.",
6684
+ kind: "invalid_args",
6685
+ status: 400,
6686
+ exitCode: EXIT_CODES.invalidArgs,
6687
+ details: { supportedScopes: osmdSearchScopeSchema.options }
6688
+ });
6689
+ }
6690
+ return parsed.data;
6691
+ }
6692
+ function backendSearchSourceForScope(scope) {
6693
+ if (scope === "os-only") {
6694
+ return "canonical_osmd";
6695
+ }
6696
+ if (scope === "agent-overlay-only") {
6697
+ return "agent_overlay";
6698
+ }
6699
+ if (scope === "agent-wiki-only") {
6700
+ return "agent_wiki";
6701
+ }
6702
+ return "combined";
6703
+ }
6704
+ function parseSearchLimit(value) {
6705
+ const raw = value === void 0 || value === null || value === "" ? "10" : value;
6706
+ const parsed = positiveInt.safeParse(raw);
6707
+ if (!parsed.success) {
6708
+ throw new CliError({
6709
+ message: "Invalid OSMD search limit. Use a positive integer.",
6710
+ kind: "invalid_args",
6711
+ status: 400,
6712
+ exitCode: EXIT_CODES.invalidArgs,
6713
+ details: { issues: parsed.error.issues }
6714
+ });
6715
+ }
6716
+ return parsed.data;
6717
+ }
6505
6718
  function parseDepth(value) {
6506
6719
  if (value === void 0 || value === null || value === "") {
6507
6720
  return 1;
@@ -6791,7 +7004,7 @@ function registerOsmdCommands(program) {
6791
7004
  })
6792
7005
  });
6793
7006
  });
6794
- osmd.command("children [path]").description("List backend-supported agent markdown children; search remains canonical-only").option("--parent-ref <parentRef>", "Canonical OSMD parent ref for .agent files").option("--source <source>", "Filter by canonical_osmd, agent_overlay, or agent_wiki").action(async (path, opts, cmd) => {
7007
+ osmd.command("children [path]").description("List backend-supported agent markdown children").option("--parent-ref <parentRef>", "Canonical OSMD parent ref for .agent files").option("--source <source>", "Filter by canonical_osmd, agent_overlay, or agent_wiki").action(async (path, opts, cmd) => {
6795
7008
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
6796
7009
  const result = await requestAgentMarkdownChildren({
6797
7010
  command: "osmd.children",
@@ -6810,6 +7023,25 @@ function registerOsmdCommands(program) {
6810
7023
  })
6811
7024
  });
6812
7025
  });
7026
+ osmd.command("search <query>").description("Search merged OSMD, .agent overlays, and Agent Wiki through backend OSMD search").option("--scope <scope>", "os-only, agent-overlay-only, agent-wiki-only, or combined", "combined").option("--limit <limit>", "Maximum number of results", "10").action(async (query, opts, cmd) => {
7027
+ const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
7028
+ const result = await requestOsmdSearch({
7029
+ command: "osmd.search",
7030
+ runtimeOptions,
7031
+ organizationId,
7032
+ query: nonEmptyString3.parse(query).trim(),
7033
+ scope: parseOsmdSearchScope(opts.scope),
7034
+ limit: parseSearchLimit(opts.limit)
7035
+ });
7036
+ return printEnvelopeAndExit({
7037
+ envelope: buildSuccessEnvelope({
7038
+ command: "osmd.search",
7039
+ status: result.status,
7040
+ data: result.data,
7041
+ requestId: result.requestId
7042
+ })
7043
+ });
7044
+ });
6813
7045
  const agent = osmd.command("agent").description(".agent overlay memory attached to canonical OSMD parents; supports notes.md");
6814
7046
  agent.command("status <parent-ref> <file>").description("Show .agent notes.md metadata without creating files").action(async (parentRef, file, _opts, cmd) => {
6815
7047
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
@@ -6897,7 +7129,7 @@ function registerOsmdCommands(program) {
6897
7129
  });
6898
7130
  }
6899
7131
  );
6900
- const wiki = osmd.command("wiki").description("Agent Wiki files writable by agents; supports index.md and append-only log.md");
7132
+ const wiki = osmd.command("wiki").description("Agent Wiki files writable by agents; supports index.md, controlled pages, and append-only log.md");
6901
7133
  wiki.command("init").description("Idempotently initialize Agent Wiki system files").action(async function() {
6902
7134
  const { organizationId, runtimeOptions } = await resolveOsmdContext(this);
6903
7135
  return runAgentMarkdownMutation({
@@ -6911,7 +7143,7 @@ function registerOsmdCommands(program) {
6911
7143
  }
6912
7144
  });
6913
7145
  });
6914
- wiki.command("status <path>").description("Show Agent Wiki index.md or log.md metadata without creating files").action(async (path, _opts, cmd) => {
7146
+ wiki.command("status <path>").description("Show Agent Wiki index.md, log.md, or controlled page metadata without creating files").action(async (path, _opts, cmd) => {
6915
7147
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
6916
7148
  const result = await requestAgentMarkdownStatus({
6917
7149
  command: "osmd.wiki.status",
@@ -6928,7 +7160,7 @@ function registerOsmdCommands(program) {
6928
7160
  })
6929
7161
  });
6930
7162
  });
6931
- wiki.command("read <path>").description("Read Agent Wiki index.md or log.md without creating files").action(async (path, _opts, cmd) => {
7163
+ wiki.command("read <path>").description("Read Agent Wiki index.md, log.md, or controlled pages without creating files").action(async (path, _opts, cmd) => {
6932
7164
  const { organizationId, runtimeOptions } = await resolveOsmdContext(cmd);
6933
7165
  const result = await requestAgentMarkdownFile({
6934
7166
  command: "osmd.wiki.read",
@@ -6951,7 +7183,7 @@ function registerOsmdCommands(program) {
6951
7183
  command: "osmd.wiki.children",
6952
7184
  runtimeOptions,
6953
7185
  organizationId,
6954
- path: typeof path === "string" && path.trim() !== "" ? normalizeWikiPath(path, { allowRoot: true }) : "Agent Wiki",
7186
+ path: typeof path === "string" && path.trim() !== "" ? normalizeWikiPath(path, { allowRoot: true, allowControlledFolders: true }) : "Agent Wiki",
6955
7187
  source: "agent_wiki"
6956
7188
  });
6957
7189
  return printEnvelopeAndExit({
@@ -6964,7 +7196,7 @@ function registerOsmdCommands(program) {
6964
7196
  });
6965
7197
  });
6966
7198
  addContentOptions(
6967
- wiki.command("create <path>").description("Create Agent Wiki index.md after status/permission preflight")
7199
+ wiki.command("create <path>").description("Create Agent Wiki index.md or controlled pages after status/permission preflight")
6968
7200
  ).action(async (path, opts, cmd) => {
6969
7201
  const globals = cmd.optsWithGlobals();
6970
7202
  const content = await resolveContentInput(opts, globals);
@@ -6974,12 +7206,12 @@ function registerOsmdCommands(program) {
6974
7206
  action: "create",
6975
7207
  runtimeOptions,
6976
7208
  organizationId,
6977
- path: normalizeWikiIndexPath(path),
7209
+ path: normalizeWikiWritablePath(path),
6978
7210
  content
6979
7211
  });
6980
7212
  });
6981
7213
  addContentOptions(
6982
- wiki.command("update <path>").description("Update Agent Wiki index.md after status/permission preflight")
7214
+ wiki.command("update <path>").description("Update Agent Wiki index.md or controlled pages after status/permission preflight")
6983
7215
  ).action(async (path, opts, cmd) => {
6984
7216
  const globals = cmd.optsWithGlobals();
6985
7217
  const content = await resolveContentInput(opts, globals);
@@ -6989,7 +7221,7 @@ function registerOsmdCommands(program) {
6989
7221
  action: "update",
6990
7222
  runtimeOptions,
6991
7223
  organizationId,
6992
- path: normalizeWikiIndexPath(path),
7224
+ path: normalizeWikiWritablePath(path),
6993
7225
  content
6994
7226
  });
6995
7227
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ted-galago/wave-cli",
3
- "version": "0.1.19",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "wave": "dist/index.js"
@@ -1330,6 +1330,124 @@ function runOsmdLiveVerification() {
1330
1330
  );
1331
1331
  }
1332
1332
 
1333
+ const controlledWikiSearchTerm = `CLI live controlled wiki ${verifyStamp}`;
1334
+ const controlledWikiPages = [
1335
+ {
1336
+ path: `concepts/cli-live-concept-${verifyStamp}.md`,
1337
+ fullPath: `Agent Wiki/concepts/cli-live-concept-${verifyStamp}.md`,
1338
+ wikiRole: "concept",
1339
+ title: "concept"
1340
+ },
1341
+ {
1342
+ path: `playbooks/cli-live-playbook-${verifyStamp}.md`,
1343
+ fullPath: `Agent Wiki/playbooks/cli-live-playbook-${verifyStamp}.md`,
1344
+ wikiRole: "playbook",
1345
+ title: "playbook"
1346
+ },
1347
+ {
1348
+ path: `decisions/cli-live-decision-${verifyStamp}.md`,
1349
+ fullPath: `Agent Wiki/decisions/cli-live-decision-${verifyStamp}.md`,
1350
+ wikiRole: "decision",
1351
+ title: "decision"
1352
+ },
1353
+ {
1354
+ path: `org-summaries/cli-live-org-summary-${verifyStamp}.md`,
1355
+ fullPath: `Agent Wiki/org-summaries/cli-live-org-summary-${verifyStamp}.md`,
1356
+ wikiRole: "org_summary",
1357
+ title: "org_summary"
1358
+ }
1359
+ ];
1360
+
1361
+ for (const page of controlledWikiPages) {
1362
+ const controlledStatus = runWave(["osmd", "wiki", "status", page.path], env);
1363
+ results.push(controlledStatus);
1364
+ osmdVerificationFailures += recordExactCheck({
1365
+ lines: osmdVerificationLines,
1366
+ label: `osmd.wiki status controlled ${page.title}`,
1367
+ result: controlledStatus,
1368
+ ok:
1369
+ controlledStatus.parsed?.ok === true &&
1370
+ controlledStatus.parsed?.data?.path === page.fullPath &&
1371
+ controlledStatus.parsed?.data?.source === "agent_wiki" &&
1372
+ controlledStatus.parsed?.data?.access === "read_write" &&
1373
+ controlledStatus.parsed?.data?.wikiRole === page.wikiRole &&
1374
+ controlledStatus.parsed?.data?.formatVersion === "agent_markdown_v1" &&
1375
+ controlledStatus.parsed?.data?.exists === false &&
1376
+ controlledStatus.parsed?.data?.canCreate === true &&
1377
+ controlledStatus.parsed?.data?.canUpdate === false &&
1378
+ controlledStatus.parsed?.data?.canAppend === false &&
1379
+ controlledStatus.parsed?.data?.suggestedAction === "create",
1380
+ reason: "agent_wiki_controlled_status"
1381
+ });
1382
+
1383
+ const createContent = `# CLI Live ${page.title}\n\n${controlledWikiSearchTerm} create ${page.title}`;
1384
+ const controlledCreate = runWave(["osmd", "wiki", "create", page.path, "--content", createContent], env);
1385
+ results.push(controlledCreate);
1386
+ osmdVerificationFailures += recordExactCheck({
1387
+ lines: osmdVerificationLines,
1388
+ label: `osmd.wiki create controlled ${page.title}`,
1389
+ result: controlledCreate,
1390
+ ok:
1391
+ controlledCreate.parsed?.ok === true &&
1392
+ controlledCreate.parsed?.data?.path === page.fullPath &&
1393
+ controlledCreate.parsed?.data?.source === "agent_wiki" &&
1394
+ controlledCreate.parsed?.data?.wikiRole === page.wikiRole &&
1395
+ controlledCreate.parsed?.data?.formatVersion === "agent_markdown_v1" &&
1396
+ controlledCreate.parsed?.data?.exists === true &&
1397
+ controlledCreate.parsed?.data?.canUpdate === true &&
1398
+ controlledCreate.parsed?.data?.canAppend === false &&
1399
+ controlledCreate.parsed?.data?.content === createContent,
1400
+ reason: "agent_wiki_controlled_create"
1401
+ });
1402
+
1403
+ const updateContent = `# CLI Live ${page.title}\n\n${controlledWikiSearchTerm} update ${page.title}`;
1404
+ const controlledUpdate = runWave(["osmd", "wiki", "update", page.path, "--content", updateContent], env);
1405
+ results.push(controlledUpdate);
1406
+ osmdVerificationFailures += recordExactCheck({
1407
+ lines: osmdVerificationLines,
1408
+ label: `osmd.wiki update controlled ${page.title}`,
1409
+ result: controlledUpdate,
1410
+ ok:
1411
+ controlledUpdate.parsed?.ok === true &&
1412
+ controlledUpdate.parsed?.data?.path === page.fullPath &&
1413
+ controlledUpdate.parsed?.data?.source === "agent_wiki" &&
1414
+ controlledUpdate.parsed?.data?.wikiRole === page.wikiRole &&
1415
+ controlledUpdate.parsed?.data?.formatVersion === "agent_markdown_v1" &&
1416
+ controlledUpdate.parsed?.data?.exists === true &&
1417
+ controlledUpdate.parsed?.data?.canUpdate === true &&
1418
+ controlledUpdate.parsed?.data?.canAppend === false &&
1419
+ controlledUpdate.parsed?.data?.content === updateContent,
1420
+ reason: "agent_wiki_controlled_update"
1421
+ });
1422
+ }
1423
+
1424
+ const logUpdateRejected = runWave(["osmd", "wiki", "update", "log.md", "--content", "verify forbidden"], env);
1425
+ osmdVerificationFailures += recordExactCheck({
1426
+ lines: osmdVerificationLines,
1427
+ label: "osmd.wiki update rejects log locally",
1428
+ result: logUpdateRejected,
1429
+ ok:
1430
+ logUpdateRejected.parsed?.ok === false &&
1431
+ logUpdateRejected.parsed?.status === 400 &&
1432
+ logUpdateRejected.parsed?.error?.code === "invalid_args",
1433
+ reason: "agent_wiki_log_update_local_reject"
1434
+ });
1435
+
1436
+ const arbitraryWikiRejected = runWave(
1437
+ ["osmd", "wiki", "create", "random/cli-live-random.md", "--content", "verify forbidden"],
1438
+ env
1439
+ );
1440
+ osmdVerificationFailures += recordExactCheck({
1441
+ lines: osmdVerificationLines,
1442
+ label: "osmd.wiki create rejects arbitrary wiki path locally",
1443
+ result: arbitraryWikiRejected,
1444
+ ok:
1445
+ arbitraryWikiRejected.parsed?.ok === false &&
1446
+ arbitraryWikiRejected.parsed?.status === 400 &&
1447
+ arbitraryWikiRejected.parsed?.error?.code === "invalid_args",
1448
+ reason: "agent_wiki_arbitrary_path_local_reject"
1449
+ });
1450
+
1333
1451
  const parentRef = resolveOsmdAgentParentRef();
1334
1452
  if (!parentRef) {
1335
1453
  osmdVerificationLines.push(
@@ -1392,6 +1510,174 @@ function runOsmdLiveVerification() {
1392
1510
  reason: "agent_overlay_update"
1393
1511
  });
1394
1512
 
1513
+ const searchCandidates = (result) => firstArrayValue(result.parsed, [["data", "candidates"]]);
1514
+ const hasOwn = (candidate, key) => Object.prototype.hasOwnProperty.call(candidate ?? {}, key);
1515
+ const hasSearchMetadata = (candidate, expectedSource) =>
1516
+ candidate &&
1517
+ typeof candidate.path === "string" &&
1518
+ candidate.source === expectedSource &&
1519
+ typeof candidate.access === "string" &&
1520
+ typeof candidate.owner === "string" &&
1521
+ hasOwn(candidate, "wikiRole") &&
1522
+ hasOwn(candidate, "formatVersion") &&
1523
+ hasOwn(candidate, "parentRef") &&
1524
+ typeof candidate.exists === "boolean";
1525
+
1526
+ const canonicalSearch = runWave(["osmd", "search", "Wave Tools", "--scope", "os-only", "--limit", "5"], env);
1527
+ results.push(canonicalSearch);
1528
+ const canonicalSearchCandidates = searchCandidates(canonicalSearch);
1529
+ osmdVerificationFailures += recordExactCheck({
1530
+ lines: osmdVerificationLines,
1531
+ label: "osmd.search canonical results include OS metadata",
1532
+ result: canonicalSearch,
1533
+ ok:
1534
+ canonicalSearch.parsed?.ok === true &&
1535
+ canonicalSearch.parsed?.command === "osmd.search" &&
1536
+ typeof canonicalSearch.parsed?.meta?.requestId === "string" &&
1537
+ canonicalSearch.parsed?.data?.scope === "canonical_osmd" &&
1538
+ canonicalSearchCandidates.some(
1539
+ (candidate) =>
1540
+ hasSearchMetadata(candidate, "canonical_osmd") &&
1541
+ candidate.access === "read_only" &&
1542
+ candidate.owner === "system"
1543
+ ),
1544
+ reason: "osmd_search_canonical"
1545
+ });
1546
+
1547
+ const wikiSearch = runWave(["osmd", "search", "Agent Wiki", "--scope", "agent-wiki-only", "--limit", "10"], env);
1548
+ results.push(wikiSearch);
1549
+ const wikiSearchCandidates = searchCandidates(wikiSearch);
1550
+ osmdVerificationFailures += recordExactCheck({
1551
+ lines: osmdVerificationLines,
1552
+ label: "osmd.search Agent Wiki results include index and log metadata",
1553
+ result: wikiSearch,
1554
+ ok:
1555
+ wikiSearch.parsed?.ok === true &&
1556
+ wikiSearch.parsed?.data?.scope === "agent_wiki" &&
1557
+ wikiSearchCandidates.some(
1558
+ (candidate) =>
1559
+ hasSearchMetadata(candidate, "agent_wiki") &&
1560
+ candidate.path === "Agent Wiki/index.md" &&
1561
+ candidate.wikiRole === "index"
1562
+ ) &&
1563
+ wikiSearchCandidates.some(
1564
+ (candidate) =>
1565
+ hasSearchMetadata(candidate, "agent_wiki") &&
1566
+ candidate.path === "Agent Wiki/log.md" &&
1567
+ candidate.wikiRole === "log" &&
1568
+ candidate.access === "append_only"
1569
+ ),
1570
+ reason: "osmd_search_agent_wiki"
1571
+ });
1572
+
1573
+ const controlledWikiSearch = runWave(
1574
+ ["osmd", "search", controlledWikiSearchTerm, "--scope", "agent-wiki-only", "--limit", "10"],
1575
+ env
1576
+ );
1577
+ results.push(controlledWikiSearch);
1578
+ const controlledWikiSearchCandidates = searchCandidates(controlledWikiSearch);
1579
+ osmdVerificationFailures += recordExactCheck({
1580
+ lines: osmdVerificationLines,
1581
+ label: "osmd.search Agent Wiki controlled pages",
1582
+ result: controlledWikiSearch,
1583
+ ok:
1584
+ controlledWikiSearch.parsed?.ok === true &&
1585
+ controlledWikiSearch.parsed?.data?.scope === "agent_wiki" &&
1586
+ controlledWikiPages.every((page) =>
1587
+ controlledWikiSearchCandidates.some(
1588
+ (candidate) =>
1589
+ hasSearchMetadata(candidate, "agent_wiki") &&
1590
+ candidate.path === page.fullPath &&
1591
+ candidate.wikiRole === page.wikiRole &&
1592
+ candidate.access === "read_write"
1593
+ )
1594
+ ),
1595
+ reason: "osmd_search_agent_wiki_controlled_pages"
1596
+ });
1597
+
1598
+ const controlledCombinedSearch = runWave(
1599
+ ["osmd", "search", controlledWikiSearchTerm, "--scope", "combined", "--limit", "10"],
1600
+ env
1601
+ );
1602
+ results.push(controlledCombinedSearch);
1603
+ const controlledCombinedSearchCandidates = searchCandidates(controlledCombinedSearch);
1604
+ osmdVerificationFailures += recordExactCheck({
1605
+ lines: osmdVerificationLines,
1606
+ label: "osmd.search combined includes controlled Agent Wiki pages",
1607
+ result: controlledCombinedSearch,
1608
+ ok:
1609
+ controlledCombinedSearch.parsed?.ok === true &&
1610
+ controlledCombinedSearch.parsed?.data?.scope === "combined" &&
1611
+ controlledWikiPages.some((page) =>
1612
+ controlledCombinedSearchCandidates.some(
1613
+ (candidate) =>
1614
+ hasSearchMetadata(candidate, "agent_wiki") &&
1615
+ candidate.path === page.fullPath &&
1616
+ candidate.wikiRole === page.wikiRole
1617
+ )
1618
+ ),
1619
+ reason: "osmd_search_combined_controlled_pages"
1620
+ });
1621
+
1622
+ const logSearch = runWave(
1623
+ ["osmd", "search", `CLI live verify log ${verifyStamp}`, "--scope", "agent-wiki-only", "--limit", "5"],
1624
+ env
1625
+ );
1626
+ results.push(logSearch);
1627
+ const logSearchCandidates = searchCandidates(logSearch);
1628
+ osmdVerificationFailures += recordExactCheck({
1629
+ lines: osmdVerificationLines,
1630
+ label: "osmd.search Agent Wiki log content result",
1631
+ result: logSearch,
1632
+ ok:
1633
+ logSearch.parsed?.ok === true &&
1634
+ logSearchCandidates.some(
1635
+ (candidate) =>
1636
+ hasSearchMetadata(candidate, "agent_wiki") &&
1637
+ candidate.path === "Agent Wiki/log.md" &&
1638
+ candidate.wikiRole === "log"
1639
+ ),
1640
+ reason: "osmd_search_agent_wiki_log"
1641
+ });
1642
+
1643
+ const notesSearch = runWave(
1644
+ ["osmd", "search", notesContent, "--scope", "agent-overlay-only", "--limit", "5"],
1645
+ env
1646
+ );
1647
+ results.push(notesSearch);
1648
+ const notesSearchCandidates = searchCandidates(notesSearch);
1649
+ osmdVerificationFailures += recordExactCheck({
1650
+ lines: osmdVerificationLines,
1651
+ label: "osmd.search agent overlay notes content result",
1652
+ result: notesSearch,
1653
+ ok:
1654
+ notesSearch.parsed?.ok === true &&
1655
+ notesSearch.parsed?.data?.scope === "agent_overlay" &&
1656
+ notesSearchCandidates.some(
1657
+ (candidate) =>
1658
+ hasSearchMetadata(candidate, "agent_overlay") &&
1659
+ candidate.path.endsWith("/.agent/notes.md") &&
1660
+ candidate.wikiRole === "notes" &&
1661
+ candidate.parentRef === parentRef
1662
+ ),
1663
+ reason: "osmd_search_agent_overlay_notes"
1664
+ });
1665
+
1666
+ const combinedSearch = runWave(["osmd", "search", "Wave", "--scope", "combined", "--limit", "3"], env);
1667
+ results.push(combinedSearch);
1668
+ osmdVerificationFailures += recordExactCheck({
1669
+ lines: osmdVerificationLines,
1670
+ label: "osmd.search combined scope returns stable envelope",
1671
+ result: combinedSearch,
1672
+ ok:
1673
+ combinedSearch.parsed?.ok === true &&
1674
+ combinedSearch.parsed?.command === "osmd.search" &&
1675
+ combinedSearch.parsed?.data?.scope === "combined" &&
1676
+ Array.isArray(combinedSearch.parsed?.data?.candidates) &&
1677
+ typeof combinedSearch.parsed?.meta?.requestId === "string",
1678
+ reason: "osmd_search_combined"
1679
+ });
1680
+
1395
1681
  }
1396
1682
 
1397
1683
  runOsmdLiveVerification();