@open-agent-toolkit/cli 0.3.29 → 0.3.31

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
@@ -51,6 +51,8 @@ Fumadocs static exports include `llms.txt`, `llms-full.txt` and `llms/<page-slug
51
51
 
52
52
  `oat docs check [--target-dir <dir>] [--rule <id>...] [--json]` performs read-only reader, navigation, contributor-command and Mermaid/SVG checks without a site build. Errors exit 1; warnings exit 0. Configure validated severities, numeric thresholds and reason-bearing exceptions under `documentation.check`. Mermaid syntax parsing lazily uses a real DOM with strict sanitization and no script/resource execution; Node 22.17 remains the minimum. See [checker rules and configuration](../../apps/oat-docs/docs/docs-tooling/check.md).
53
53
 
54
+ `oat docs literals [--target-dir <dir>] --page <content-relative-reader.md> --source-root <repo-relative-dir> [--source-exclude <pattern>] --json` supplies read-only inline-code occurrence candidates for a matching triad IA mirror. Repeat each single-value selection flag as needed. Sources stay within narrow explicit implementation roots; searched/skipped evidence and first source physical lines are reported. Once-only strict HTML entity decoding and case-sensitive line-local substring matching do not establish runtime validity; analyze judges canonical relevance and authored IA acceptance. Completed not-found/not-applicable exits 0, input/config errors 1, unexpected failures 2. See [literal helper scope and limits](../../apps/oat-docs/docs/docs-tooling/commands.md#oat-docs-literals).
55
+
54
56
  Fumadocs starters constrain `mdast-util-to-markdown` to 2.1.2 for the current upstream handler compatibility defect. New independent install roots get `pnpm-workspace.yaml`; workspace members get manual setup advice for the existing root YAML, with approval/conflict preservation in bootstrap. No member-local boundary or root dependency-policy merge is performed; `--no-root-patch` still controls build scripts only. Keep the effective policy and lockfile and verify actual resolution before building.
55
57
 
56
58
  Framework starters expose `docs:check` and run it before build; MkDocs `test` is a real check. Bootstrap proposes approved PR CI edits with explicit generated-manifest freshness. Markdown needs neither an app build nor a default manifest.
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "oatVersion": "0.3.29"
3
+ "oatVersion": "0.3.31"
4
4
  }
@@ -69,7 +69,7 @@ For framework apps, it wraps `oat docs init` with a seven-step guided flow: pref
69
69
 
70
70
  What the framework path adds over the raw CLI:
71
71
 
72
- - **Preflight + conflict resolution.** Detects existing `documentation` config / docs app dir / root `AGENTS.md` section and walks a deliberate resolution choice (replace, abort, repair, or deferred second-app) before any mutation.
72
+ - **Preflight + existing-docs front door.** Detects existing `documentation` config / docs app dir / root `AGENTS.md` section and defaults to audit (`oat-docs-analyze` then approved `oat-docs-apply`), then exits before scaffolding. Replacement requires an explicit choice; a second app remains unsupported. See [IA selection and existing-docs routing](workflows.md#ia-selection-and-guidance).
73
73
  - **Richer inputs.** Asks for a **site name** separate from the package name, so layout branding and page metadata converge on the same display title. `createDocsConfig()` handles runtime configuration, not titles.
74
74
  - **CLI-owned guidance.** Fresh Fumadocs and MkDocs apps include app-root `AGENTS.md`, a reader-first Getting Started stub, a human contributor entry and `docs/contributing/docs-toolchain.md`. Commands state their working directory. Bootstrap observes this output; older CLIs missing it get analyze/manual CLI-template upgrade advice, rather than a second template owner. Existing and Markdown surfaces are preserved. Nested-standalone Fumadocs still uses the ESM-safe Turbopack root while monorepo and single-package apps use Next root discovery.
75
75
  - **Agent Markdown.** Fumadocs exports `/llms.txt`, `/llms-full.txt` and `/llms/<page-slugs>/content.md` (root `/llms/content.md`) from loaded `.md` readers. MDX and configured non-readers are excluded. Source headers identify how to resolve authored relative links; rendered and Markdown URLs include the deployment base path once.
@@ -13,7 +13,7 @@ or **MkDocs Material**.
13
13
 
14
14
  - What it does: documents the docs-specific CLI surface for scaffolding apps, migrating markdown, generating Fumadocs app-root index manifests, and syncing MkDocs or Fumadocs navigation.
15
15
  - When to use it: when you already know you are working on a docs surface and need the exact command-level behavior.
16
- - Primary commands: `oat docs init`, `oat docs migrate`, `oat docs generate-index`, `oat docs nav sync`, `oat docs check`
16
+ - Primary commands: `oat docs init`, `oat docs migrate`, `oat docs generate-index`, `oat docs nav sync`, `oat docs check`, `oat docs literals`
17
17
 
18
18
  ## Command surface
19
19
 
@@ -24,6 +24,7 @@ or **MkDocs Material**.
24
24
  | `oat docs generate-index` | Generate a manifest from Markdown files, outside authored content. |
25
25
  | `oat docs nav sync` | Regenerate `mkdocs.yml` nav or Fumadocs `meta.json` from `index.md` maps. |
26
26
  | `oat docs check` | Read-only reader, navigation, command and diagram checks; errors fail, warnings pass. |
27
+ | `oat docs literals` | Read-only occurrence candidates for explicitly selected Product/QA pages in a triad. |
27
28
  | `oat docs analyze` | CLI entrypoint that points users to the `oat-docs-analyze` skill. |
28
29
  | `oat docs apply` | CLI entrypoint that points users to the `oat-docs-apply` skill. |
29
30
 
@@ -315,6 +316,68 @@ Related reference:
315
316
 
316
317
  - [`../reference/docs-index-contract.md`](../reference/docs-index-contract.md)
317
318
 
319
+ ## `oat docs literals`
320
+
321
+ After `oat-docs-analyze` confirms the authored accepted IA and config mirror
322
+ agree on `verification: triad`, select Product/QA reader pages and narrow
323
+ canonical implementation directories:
324
+
325
+ ```bash
326
+ oat docs literals --target-dir manual \
327
+ --page experience/save.md --page verify/save.md \
328
+ --source-root src/ui --source-exclude src/ui/generated/ --json
329
+ ```
330
+
331
+ `--target-dir` is optional and uses the same configured-root/app/content
332
+ resolution as check. `--page` and `--source-root` are required, repeatable
333
+ single-value flags. Pages are content-relative Markdown (`.md` or `.mdx`,
334
+ without evaluating JSX); roots are repo-relative directories, never the repo
335
+ root. Source roots must also use their canonical on-disk spelling (including case).
336
+ Pages must use their canonical on-disk spelling (including case) and be
337
+ readable readers within the real content tree, outside `nonReaderPaths`,
338
+ with no symlink components below the resolved content root
339
+ (including page files and directory aliases). Sources must remain within the
340
+ repository; nested symlinks are skipped, never traversed. Invalid/escaping selections, unreadable traversal
341
+ and an empty effective text inventory fail visibly. Select implementation
342
+ roots and explicit exclusions to omit docs, fixtures, generated output and
343
+ vendor code as evidence. The helper does not automatically exclude documentation
344
+ from selected source roots. A match to documentation itself is not canonical
345
+ implementation support; inspect the citation and judge its relevance.
346
+
347
+ `--source-exclude` is optional/repeatable, using the existing bounded POSIX
348
+ exclusion grammar in **repository** coordinates: segment `*`, whole-segment
349
+ `**`, trailing `/` for directories; other metacharacters are literal. Parent
350
+ segments, absolute patterns and backslashes are invalid. `documentation.excludes`
351
+ remains manifest-only; `nonReaderPaths` remains content-relative.
352
+
353
+ Only a matching mirror declaring triad enables extraction. Other overlays,
354
+ absent IA or an unrelated explicit target return `not-applicable` with reason
355
+ and empty candidates before selected pages/sources are read. This gate does
356
+ not establish authored acceptance; the skill judges that separately.
357
+
358
+ JSON completion has `status: ok`, `scope: {contentRoot, pages, sourceRoots,
359
+ sourceExcludes}`, `searched` source paths, `skipped: [{path, reason}]`, and
360
+ `literals: [{path, line, literal, normalized, status, source?}]`. Candidate
361
+ status is `found` or `not-found`; source is `{path, line}`. Docs locations are
362
+ content-relative; contentRoot/source scope is repo-relative POSIX. Duplicate
363
+ pages/overlapping inventories are deduplicated. Page order and source file
364
+ order are lexical, then physical line order; the first source occurrence is
365
+ reported. Skipped reasons include symlink, binary, non-UTF8 and non-regular.
366
+ No source snippets, severity or runtime verdict are emitted.
367
+
368
+ Extraction uses Markdown AST inline code and ignores fenced blocks. Matching
369
+ applies one strict HTML entity decode to each value and each physical source
370
+ line, then a case-sensitive substring search. CommonMark may normalize multiline
371
+ spans; source matching stays per physical line. There is no whitespace/Unicode
372
+ folding, JS escape decoding, interpolation, i18n loading or runtime evaluation.
373
+ A comment, dead code or longer text can match. Inspect canonical relevance;
374
+ `not-found` starts as unverified, and explained concepts require judgment.
375
+
376
+ Exit 0 means completed evidence, including not-found/not-applicable; exit 1 is
377
+ an actionable input/config error; exit 2 is an unexpected runtime failure.
378
+ Global `--json`/`--cwd` work as usual. This helper is separate from check's G5
379
+ rules, and `oat docs analyze` remains a guidance entrypoint.
380
+
318
381
  ## `oat docs analyze` and `oat docs apply`
319
382
 
320
383
  These CLI commands intentionally reserve the docs workflow surface without
@@ -19,7 +19,9 @@ Install the workflow skills with `oat tools install docs` (preferred) or
19
19
  - `oat docs migrate` converts MkDocs admonitions to GFM callouts and injects frontmatter
20
20
  - `oat docs generate-index` generates a Fumadocs app-root docs index manifest from the Markdown file tree
21
21
  - `oat docs nav sync` regenerates MkDocs `mkdocs.yml` nav, or strict Fumadocs `meta.json` files, from `index.md` `## Contents` sections
22
- - `oat docs analyze` and `oat docs apply` expose the workflow surface in CLI help
22
+ - [`oat docs check`](check.md) checks reader pages, navigation, contributor commands and diagrams without building a site
23
+ - [`oat docs literals`](commands.md#oat-docs-literals) supplies bounded inline-code occurrence evidence for analyze
24
+ - `oat docs analyze` and `oat docs apply` expose guidance pointers in CLI help; the skills perform analysis and apply
23
25
 
24
26
  ### Skills
25
27
 
@@ -93,8 +95,9 @@ Diagram: bootstrap once, then analyze, approve and apply only agreed improvement
93
95
  Q -->|"all skipped"| STOP
94
96
  ```
95
97
 
96
- - **Setup, once.** `oat-docs-bootstrap` checks the repo without writing, asks
97
- you to confirm its inputs, then runs `oat docs init`. Plain Markdown setup validates authored files,
98
+ - **Fresh setup or explicit adoption.** `oat-docs-bootstrap` checks the repo without writing, asks
99
+ you to confirm its inputs, then runs `oat docs init`. Existing docs default to
100
+ [analyze then approved apply](#ia-selection-and-guidance) and exit before scaffolding. Plain Markdown setup validates authored files,
98
101
  configuration and guidance without installing app dependencies or building a
99
102
  site. Fumadocs and MkDocs setup inspects CLI-owned scaffold output, installs
100
103
  dependencies, verifies the build and provides a walkthrough.
@@ -277,9 +280,10 @@ with a docs app simply because one can be scaffolded.
277
280
 
278
281
  Preflight only reads. It checks declared documentation configuration before
279
282
  discovery and checks framework evidence before assuming a populated
280
- directory is plain Markdown. The skill then summarizes your choices and asks
281
- you to confirm them before it runs `oat docs init`. The two setup paths
282
- differ from that point on.
283
+ directory is plain Markdown. Existing docs default to analyze then approved
284
+ apply and exit before scaffolding. For fresh setup or explicit adoption, the
285
+ skill summarizes your choices and asks you to confirm them before it runs
286
+ `oat docs init`. The two setup paths differ from that point on.
283
287
 
284
288
  **Plain Markdown path:** Use this when you want plain Markdown files and no
285
289
  site runtime. The skill collects a title, a dedicated repository-relative
@@ -352,8 +356,9 @@ pages, grouped headings in one flat index often suffice.
352
356
  For fresh Markdown/framework setup and explicit adoption, bootstrap proposes
353
357
  two plausible two-level trees with 5–8 representative page placements each,
354
358
  page jobs, nearest presets and tradeoffs. Analyze's `--ia` does the same in its
355
- existing analysis artifact and review loop. These are **skill arguments**;
356
- they do not add CLI bootstrap commands or flags to `oat docs init`.
359
+ existing analysis artifact and review loop. `--ia` and `--ia-guidance` are
360
+ **skill arguments**, not `oat docs init` flags. The CLI accepts the approved
361
+ selection through the paired [`--ia-preset` and `--planned-pages` flags](commands.md#approved-preset-readers).
357
362
 
358
363
  Resolve advisory sources in this order: explicit `--ia-guidance <path|url>` or
359
364
  conversation, repo `documentation.guidance`, user config, then the installed
@@ -372,8 +377,15 @@ proposal becomes durable only after approval: bootstrap/apply use `oat decision
372
377
  and an Information architecture section in the framework docs-app `AGENTS.md`
373
378
  or Markdown contributing page, plus the shared `documentation.ia` mirror.
374
379
  Authored records stay authoritative; missing/conflicting records become `ask_user`.
375
- Preset scaffolds and Feature/QA validation page templates are a later delivery;
376
- the IA Proposal template is available now.
380
+ Fresh empty targets can now materialize all six presets and their relevant
381
+ page-template subsets, including Feature and QA validation templates, through
382
+ that opt-in flag pair. Counts below 15 collapse reader tracks into grouped root
383
+ headings; 15 or more creates tracks, while `flat-small` always stays flat.
384
+ `contributing/templates/**` is excluded from reader navigation, checks and
385
+ manifests. See [preset mappings and template subsets](commands.md#approved-preset-readers),
386
+ including the MkDocs rendered-route limitation. Selection does not create the
387
+ separate approved IA decision; the IA Proposal template remains the selection
388
+ artifact.
377
389
 
378
390
  ## oat-docs-analyze
379
391
 
@@ -454,8 +466,10 @@ navigation or generated-artifact refreshes, plus a structured result listing
454
466
  applied, skipped (ID/reason), and failed IDs. Markdown retains its authored
455
467
  index and needs no framework build or nav sync; Fumadocs sidebar metadata is
456
468
  regenerated only with recorded `navOwner: nav-sync`. Hand-authored navigation
457
- gets approved edits and read-only Contents membership/order comparison;
458
- undeclared ownership becomes `ask_user`. The summary
469
+ gets approved edits and read-only Contents membership/order comparison.
470
+ [Absent optional metadata/pages/nav skips comparison](check.md); implicit
471
+ Fumadocs order is not checked. Explicit lists remain checked and nav-sync
472
+ freshness stays strict. Undeclared ownership becomes `ask_user`. The summary
459
473
  reports the applied plan and checks.
460
474
 
461
475
  **What it does without asking:** Nothing changes until you approve the plan.
@@ -508,10 +522,31 @@ consumer and agent coverage. A top-level misroute is High when it blocks normal
508
522
  first success/verification, Medium when it causes material detours; cosmetic
509
523
  organization stays Low. Missing IA/owner raises `ask_user`.
510
524
 
525
+ When the authored accepted IA and its mirror agree on triad verification,
526
+ analyze also checks six source-related concerns: mapped feature slug parity,
527
+ real peer links/anchors (including QA Scope pairing), quoted literals versus
528
+ explained concepts, exact membership in the repository's own test-ID registry,
529
+ scoped duplicate/contradictory scenarios, and raw Contents links to templates
530
+ even when nav excludes them. Roles come from the decision/local guidance,
531
+ including renamed tracks and hybrid Engineering; flat layouts may be not
532
+ applicable. Missing mapping/registry stays unverified / `ask_user`. Other
533
+ overlays continue ordinary analysis unchanged.
534
+
535
+ The [literal occurrence helper](commands.md#oat-docs-literals) supplies bounded
536
+ text candidates for selected Product/QA pages. A found substring requires
537
+ canonical-source judgment; not-found is initially unverified. It cannot prove
538
+ runtime behavior, registry membership or scenario correctness. Accepted IA,
539
+ source citations and existing recommendation approval classes still govern.
540
+
511
541
  For framework apps, offline drift compares installed `@open-agent-toolkit/docs-*` packages to the
512
542
  running CLI's bundled `public-package-versions.json`, never registry latest.
513
- Missing installations are unverified. The recommendation includes the exact
514
- upgrade command. Individual tooling approval permits only docs-app `@open-agent-toolkit/docs-*`
543
+ Older installed packages produce an explicit drift recommendation with the
544
+ exact upgrade command. Equal versions are current. Newer packages are `ahead`: select
545
+ or update the intended CLI installation before re-analysis, without a package
546
+ downgrade command. Missing installations or bundle metadata and non-orderable
547
+ versions are `unverified` and require manual verification; hoisted-only packages
548
+ also remain unverified. Neither ahead nor unverified supplies a package-update
549
+ command or authorizes an upgrade. Individual tooling approval permits only docs-app `@open-agent-toolkit/docs-*`
515
550
  dependency bumps (and resulting lockfile), the admonition/frontmatter conversions
516
551
  already supported by `oat docs migrate`, and build verification. App config,
517
552
  routes and package scripts remain manual required changes.
@@ -80,6 +80,8 @@ Lifecycle](../workflows/backlog-and-planning/backlog-lifecycle.md).
80
80
 
81
81
  `oat docs check [--target-dir <dir>] [--rule <id>...] [--json]` validates reader structure, links, navigation, contributor commands and diagrams without a site build. Errors exit 1; warning-only exits 0. See [Check Documentation](../docs-tooling/check.md) for rules/config and JSON.
82
82
 
83
+ `oat docs literals --page <reader.md> --source-root <implementation-dir> [--target-dir <dir>] [--source-exclude <pattern>] --json` reports bounded inline-code occurrence candidates for triad IA mirrors. Pages and roots are repeatable single-value flags in content/repository coordinates respectively. Found candidates require canonical-source judgment. See [literal helper scope, JSON and limits](../docs-tooling/commands.md#oat-docs-literals).
84
+
83
85
  ## Command Groups
84
86
 
85
87
  | Command group | What it covers | Go deeper |
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.3.29",
3
- "docs-config": "0.3.29",
4
- "docs-theme": "0.3.29",
5
- "docs-transforms": "0.3.29"
2
+ "cli": "0.3.31",
3
+ "docs-config": "0.3.31",
4
+ "docs-theme": "0.3.31",
5
+ "docs-transforms": "0.3.31"
6
6
  }
@@ -4,9 +4,9 @@ description: Run when you need to evaluate documentation structure, navigation,
4
4
  argument-hint: '[target] [--ia] [--ia-guidance <path|url>]'
5
5
  disable-model-invocation: true
6
6
  user-invocable: true
7
- allowed-tools: Read, Write, Bash(git:*), Bash(oat config:*), Bash(oat docs nav sync --check:*), Bash(oat docs check:*), Bash(pnpm:*), Bash(bash:*), Bash(jq:*), Bash(node:*), Bash(mkdir:*), Bash(date:*), Bash(echo:*), Bash(cd:*), Bash(pwd:*), Glob, Grep, AskUserQuestion, Task
7
+ allowed-tools: Read, Write, Bash(git:*), Bash(oat config:*), Bash(oat docs nav sync --check:*), Bash(oat docs check:*), Bash(oat docs literals:*), Bash(pnpm:*), Bash(bash:*), Bash(jq:*), Bash(node:*), Bash(command -v:*), Bash(sed:*), Bash(npm root -g:*), Bash(test:*), Bash(oat --version), Bash(mkdir:*), Bash(date:*), Bash(echo:*), Bash(cd:*), Bash(pwd:*), Glob, Grep, AskUserQuestion, Task
8
8
  metadata:
9
- version: '1.7.3'
9
+ version: '1.7.5'
10
10
  ---
11
11
 
12
12
  # Docs Analysis
@@ -336,7 +336,8 @@ app's docs avoid merely mirroring implementation directories. Review coverage
336
336
  only for declared readers: Product features/journeys/screens/states; QA scenario
337
337
  tables with seeds and automation references; consumer first-success/task guides;
338
338
  agent entrypoints, canonical sources and verification commands. Do not demand
339
- these tracks from every repository or implement the later P24 source checks.
339
+ these tracks from every repository. For an accepted triad, also perform the
340
+ conditional source assessments below.
340
341
 
341
342
  Top-level IA misroutes a declared audience: `High` when the reader cannot find
342
343
  a normal first-success or required verification path; `Medium` when that path
@@ -389,6 +390,91 @@ Evidence standard for this step:
389
390
  - If multiple repo files are needed to verify a claim, cite all relevant sources.
390
391
  - If the canonical source is ambiguous, mark the claim `unverified` rather than guessing.
391
392
 
393
+ ### Conditional triad source assessments (P24)
394
+
395
+ Run these six assessments only after the authored **accepted** IA decision and
396
+ local Information architecture section agree with the mirror's
397
+ `verification: triad`. Cite that agreement first. A mirror alone does not prove
398
+ acceptance. For absent, `agent-only`, or `none` overlays, record not applicable
399
+ and continue ordinary analysis unchanged. Missing/broken authored acceptance or
400
+ a conflicting mirror blocks these assessments with `unverified` / `ask_user`;
401
+ it does not block ordinary analysis or justify choosing an IA.
402
+
403
+ Map Product, QA and Engineering feature subsets from the accepted decision,
404
+ tracks/principles and local guidance, citing each mapping. Do not infer roles
405
+ from fixed folder names or tracks array order. Engineering may mix domain
406
+ contracts with feature pages: compare only its mapped feature subset. A grouped
407
+ flat IA may have no per-feature triad; explain not applicable rather than invent
408
+ folders. An unresolved mapping stays `unverified` / `ask_user` without new config.
409
+
410
+ 1. **Slug parity.** Compare relative feature/journey keys in the mapped subsets.
411
+ Exclude indexes, support pages, templates and Engineering domain contracts.
412
+ Cite missing/extra peers and the declared mapping before recommending repair.
413
+ 2. **Paired documentation.** Inspect each member's actual peer links, resolving
414
+ relative paths and anchors within the repository. Accept case-insensitive
415
+ Paired Documentation headings and QA's canonical Scope links when they point
416
+ to the appropriate Product/Engineering peers. A heading alone or an unrelated
417
+ resolving link is insufficient. Cite both link and target/anchor; unresolved
418
+ or wrong-peer links become evidence-backed findings.
419
+ 3. **Quoted literals and concepts.** Select Product/QA reader Markdown and narrow
420
+ canonical implementation directories from cited source references/local
421
+ guidance, then invoke the read-only helper (repeat one value per flag):
422
+
423
+ ```bash
424
+ oat docs literals --target-dir <resolved-target> \
425
+ --page <product-page> --page <qa-page> \
426
+ --source-root <implementation-dir> \
427
+ --source-exclude <generated-or-fixture-pattern> --json
428
+ ```
429
+
430
+ Pages are content-relative; roots and exclusions are repository-relative.
431
+ Never scan the repository root, dependencies, home or external sources.
432
+ Select implementation roots and exclusions that omit docs, templates,
433
+ generated/vendor output and example/test fixtures as evidence. The helper
434
+ does not automatically exclude documentation from selected source roots;
435
+ a match to documentation itself is not canonical implementation support.
436
+ Inspect the returned scope, searched files and skipped symlink/binary/non-UTF8
437
+ inputs; incomplete traversal or invalid selections fail visibly. Missing
438
+ canonical sources remain unverified; do not broaden scanning to manufacture
439
+ matches.
440
+
441
+ The helper extracts AST inline code, ignores fences, and performs exactly
442
+ one strict HTML entity decode per value and physical source line, followed
443
+ by case-sensitive substring matching. It reports the first lexical source
444
+ file/physical line, without snippets or a runtime verdict. CommonMark can
445
+ normalize multiline spans; source matching stays per physical line. It does
446
+ not fold whitespace/Unicode, decode JS escapes, evaluate interpolation or
447
+ load i18n/runtime values. A found substring can be in a comment, dead code,
448
+ fixture or longer text: read the citation and judge canonical relevance.
449
+ `not-found` is initially **unverified**, never automatic contradiction.
450
+ Require a differing canonical value before judging contradicted. Distinguish
451
+ intentionally explained concepts from UI literals; prose concepts need no
452
+ occurrence, while ambiguous backticked concepts require clarification or a
453
+ cited conceptual explanation. Exit 0 means completed candidates (including
454
+ not-found/not-applicable), not verified documentation.
455
+
456
+ 4. **Test-ID registry membership.** Locate the repository's actual canonical
457
+ test-ID registry and read its real format. Check exact member equality,
458
+ never substring occurrence in arbitrary source. Cite registry and docs.
459
+ Missing/ambiguous registry stays `unverified` / `ask_user`; do not invent a
460
+ registry path/schema or treat helper matches as membership proof.
461
+ 5. **Scoped scenario candidates.** Inspect QA tables for duplicate IDs and the
462
+ same scoped Given + When with different Then outcomes. Compare feature,
463
+ seed, platform and configuration preconditions before judging contradiction;
464
+ meaningful differences can explain the result. Cite both rows and scope.
465
+ Ambiguous scope stays unverified. This is judgment, not a scenario engine.
466
+ 6. **Templates in authored Contents.** Read raw authored `## Contents` links
467
+ before applying reader/nav exclusions. Flag links that expose drafting
468
+ templates as reader destinations even when `nonReaderPaths` makes nav/check
469
+ skip them; cite the map entry and template purpose/local declaration. Do not
470
+ infer safety from a clean generated nav or checker result.
471
+
472
+ Record applicability, mappings, docs/source citations, candidates and judgments
473
+ in the existing artifact's Accuracy Verification section. Use the existing
474
+ severity and recommendation `approval: explicit | default` classes; missing
475
+ mapping/registry evidence is not permission to add a schema or implementation.
476
+ No P24 `oat docs check` rule is added, and its G5 JSON/rule IDs are unchanged.
477
+
392
478
  ### Step 5: Analyze Content Coverage Opportunities
393
479
 
394
480
  Add a coverage-gap pass after accuracy verification.
@@ -247,6 +247,29 @@ runtime-only behavior here.
247
247
 
248
248
  {Or: "No repo-checkable substantive claims required accuracy verification."}
249
249
 
250
+ ### Conditional triad source assessments (P24)
251
+
252
+ - Gate: {accepted authored IA + local section citations and mirror agreement;
253
+ non-triad: not applicable; missing/conflict: unverified / ask_user}
254
+ - Role/subset mapping: {Product, QA, Engineering feature roots and citations;
255
+ hybrid domain exclusions; flat applicability; never folder names/array order}
256
+ - Literal helper: {exact invocation; scope, searched files and skipped reasons;
257
+ occurrence candidates only, not a runtime verdict}
258
+
259
+ | Assessment | Docs / canonical evidence | Candidate and judgment |
260
+ | -------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
261
+ | Slug parity | {mapped feature keys and missing/extra peers} | {verdict or unverified / not applicable} |
262
+ | Paired documentation | {peer links + resolved target anchors; QA Scope accepted} | {correct peers, not just any resolving link} |
263
+ | Quoted literals / concepts | {helper path:line + canonical source / conceptual explanation} | {found requires relevance judgment; not-found initially unverified} |
264
+ | Test-ID registry | {actual registry path:line + exact member} | {membership; absent registry unverified / ask_user} |
265
+ | Scoped scenarios | {both rows + feature/seed/platform/config scope} | {duplicate ID / same Given+When with differing Then candidate; explained or unresolved} |
266
+ | Raw authored Contents | {map entry + template purpose / nonReaderPaths} | {flag reader links to templates even when nav excludes them} |
267
+
268
+ Keep one strict HTML entity decode per side and physical-source-line matching
269
+ limits visible. No JS/i18n evaluation, runtime claim, invented mapping/registry,
270
+ or automatic contradiction from absence. Recommendations retain existing
271
+ approval classes and docs/source citations.
272
+
250
273
  ## Coverage Review by Surface
251
274
 
252
275
  Summarize which documentable surfaces were proven by repo evidence and how well
@@ -55,6 +55,24 @@ Use this checklist when evaluating a docs surface.
55
55
  - Referenced paths exist.
56
56
  - Tooling/setup instructions are current.
57
57
 
58
+ ## Conditional triad source assessments (P24)
59
+
60
+ - Authored accepted IA and local section agree with the mirror's triad overlay;
61
+ other overlays are not applicable, missing/conflicting evidence unverified / ask_user.
62
+ - Roles and feature subsets come from cited decisions/local guidance, including
63
+ renamed tracks, hybrid Engineering and flat applicability; no folder/order inference.
64
+ - Slug parity compares only mapped feature keys, excluding support/domain pages.
65
+ - Peer links resolve to the correct member and anchor; QA Scope links count.
66
+ - Product/QA literals use `oat docs literals` with explicit pages, narrow source
67
+ roots and source exclusions. Scope, searched files and skipped reasons are reviewed.
68
+ - Once-decoded, case-sensitive physical-line occurrences receive canonical-source
69
+ judgment; not-found remains unverified and explained concepts are distinguished.
70
+ - Test IDs use exact membership in the actual registry; absent registry stays unverified.
71
+ - Duplicate IDs and same scoped Given+When/different Then candidates are judged
72
+ with seed/platform/configuration evidence, never automatically contradicted.
73
+ - Raw authored Contents links to templates are flagged despite reader/nav exclusions.
74
+ - Existing severity, citations and recommendation approval classes are preserved.
75
+
58
76
  ## Discoverability
59
77
 
60
78
  - Important pages are reachable from a parent `index.md` or site nav.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/docs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/docs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,iBAAiB,IAAI,OAAO,CAW3C"}
@@ -4,12 +4,14 @@ import { createDocsApplyCommand } from './apply.js';
4
4
  import { createDocsCheckCommand } from './check/check.js';
5
5
  import { createDocsGenerateIndexCommand } from './index-generate/index.js';
6
6
  import { createDocsInitCommand } from './init/index.js';
7
+ import { createDocsLiteralsCommand } from './literals/literals.js';
7
8
  import { createDocsMigrateCommand } from './migrate/index.js';
8
9
  import { createDocsNavCommand } from './nav/index.js';
9
10
  export function createDocsCommand() {
10
11
  return new Command('docs')
11
12
  .description('OAT documentation bootstrap and maintenance commands')
12
13
  .addCommand(createDocsCheckCommand())
14
+ .addCommand(createDocsLiteralsCommand())
13
15
  .addCommand(createDocsAnalyzeCommand())
14
16
  .addCommand(createDocsApplyCommand())
15
17
  .addCommand(createDocsGenerateIndexCommand())
@@ -0,0 +1,39 @@
1
+ export declare class LiteralInputError extends Error {
2
+ }
3
+ export interface LiteralOptions {
4
+ cwd: string;
5
+ targetDir?: string;
6
+ page: string[];
7
+ sourceRoot: string[];
8
+ sourceExclude: string[];
9
+ }
10
+ interface Candidate {
11
+ path: string;
12
+ line: number;
13
+ literal: string;
14
+ normalized: string;
15
+ status: 'found' | 'not-found';
16
+ source?: {
17
+ path: string;
18
+ line: number;
19
+ };
20
+ }
21
+ export interface LiteralResult {
22
+ status: 'ok' | 'not-applicable';
23
+ reason?: string;
24
+ scope: {
25
+ contentRoot: string;
26
+ pages: string[];
27
+ sourceRoots: string[];
28
+ sourceExcludes: string[];
29
+ };
30
+ searched: string[];
31
+ skipped: {
32
+ path: string;
33
+ reason: 'symlink' | 'binary' | 'non-utf8' | 'non-regular';
34
+ }[];
35
+ literals: Candidate[];
36
+ }
37
+ export declare function collectLiteralEvidence(options: LiteralOptions): Promise<LiteralResult>;
38
+ export {};
39
+ //# sourceMappingURL=evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/literals/evidence.ts"],"names":[],"mappings":"AAgBA,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AACD,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;IAC9B,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AACD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,IAAI,GAAG,gBAAgB,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,CAAC;KAC3D,EAAE,CAAC;IACJ,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB;AAkDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA6MxB"}
@@ -0,0 +1,205 @@
1
+ import { lstat, readFile, readdir, realpath, stat } from 'node:fs/promises';
2
+ import { join, relative, resolve } from 'node:path';
3
+ import { containedFile, inside, posix, resolveCheckTarget, } from '../../docs/check/files.js';
4
+ import { parsePage } from '../../docs/check/markdown.js';
5
+ import { createExclusionMatcher, isExcludedPath, } from '../../docs/shared/exclusions.js';
6
+ import { decodeHTMLStrict } from 'entities';
7
+ export class LiteralInputError extends Error {
8
+ }
9
+ function inputPath(value, label, pattern = false) {
10
+ const path = value.trim().replace(/^\.\//, '');
11
+ const parts = path.replace(/\/$/, '').split('/');
12
+ if (!path ||
13
+ path.startsWith('/') ||
14
+ /^[A-Za-z]:/.test(path) ||
15
+ path.includes('\\') ||
16
+ [...path].some((c) => c.charCodeAt(0) < 32) ||
17
+ parts.some((p) => !p || p === '.' || p === '..') ||
18
+ (!pattern && path.includes('*')))
19
+ throw new LiteralInputError(`${label} must be a non-empty relative POSIX ${pattern ? 'pattern' : 'path'} without parent segments.`);
20
+ return pattern ? path : path.replace(/\/$/, '');
21
+ }
22
+ const unique = (values) => [...new Set(values)].sort();
23
+ async function selected(label, operation) {
24
+ try {
25
+ return await operation();
26
+ }
27
+ catch (error) {
28
+ if (['ENOENT', 'ENOTDIR', 'EACCES', 'EPERM', 'ELOOP', 'EISDIR'].includes(error.code ?? ''))
29
+ throw new LiteralInputError(`Cannot read ${label}; check that it exists and is readable.`);
30
+ throw error;
31
+ }
32
+ }
33
+ function text(bytes) {
34
+ if (bytes.includes(0))
35
+ return { reason: 'binary' };
36
+ try {
37
+ return { value: new TextDecoder('utf-8', { fatal: true }).decode(bytes) };
38
+ }
39
+ catch {
40
+ return { reason: 'non-utf8' };
41
+ }
42
+ }
43
+ export async function collectLiteralEvidence(options) {
44
+ const target = await selected('documentation target/config', async () => {
45
+ try {
46
+ return await resolveCheckTarget(options.cwd, options.targetDir);
47
+ }
48
+ catch (error) {
49
+ if (!error.code)
50
+ throw new LiteralInputError('Cannot resolve documentation target/config; check .oat/config.json, documentation.root, documentation.ia and --target-dir.');
51
+ throw error;
52
+ }
53
+ });
54
+ const result = {
55
+ status: 'not-applicable',
56
+ scope: {
57
+ contentRoot: posix(relative(target.repoRoot, target.contentRoot)) || '.',
58
+ pages: [],
59
+ sourceRoots: [],
60
+ sourceExcludes: [],
61
+ },
62
+ searched: [],
63
+ skipped: [],
64
+ literals: [],
65
+ };
66
+ // Config is only the extraction gate. The skill separately reconciles authored acceptance.
67
+ if (target.documentation.ia?.verification !== 'triad') {
68
+ result.reason =
69
+ 'The matching documentation IA mirror does not declare verification: triad.';
70
+ return result;
71
+ }
72
+ if (!inside(target.repoRoot, target.contentRoot))
73
+ throw new LiteralInputError('Docs content root must be inside the repository.');
74
+ if (!options.page.length || !options.sourceRoot.length)
75
+ throw new LiteralInputError('Provide at least one --page and one narrow --source-root.');
76
+ const pages = unique(options.page.map((p) => inputPath(p, '--page')));
77
+ const roots = unique(options.sourceRoot.map((p) => inputPath(p, '--source-root')));
78
+ const excludes = unique(options.sourceExclude.map((p) => inputPath(p, '--source-exclude', true)));
79
+ result.scope = {
80
+ contentRoot: result.scope.contentRoot,
81
+ pages,
82
+ sourceRoots: roots,
83
+ sourceExcludes: excludes,
84
+ };
85
+ const matcher = createExclusionMatcher(excludes);
86
+ const sources = new Set();
87
+ const skipped = new Map();
88
+ // Inspect each component so an explicitly selected nested alias cannot bypass traversal policy.
89
+ const symlinkComponent = async (root, path) => {
90
+ const parts = path.split('/');
91
+ for (let i = 1; i <= parts.length; i++) {
92
+ if ((await lstat(join(root, ...parts.slice(0, i)))).isSymbolicLink())
93
+ return true;
94
+ }
95
+ return false;
96
+ };
97
+ const walk = async (path) => {
98
+ const absolute = join(target.repoRoot, path);
99
+ const info = await selected(`source ${path}`, () => lstat(absolute));
100
+ if (info.isSymbolicLink()) {
101
+ skipped.set(path, 'symlink');
102
+ return;
103
+ }
104
+ if (isExcludedPath(matcher, path, info.isDirectory()))
105
+ return;
106
+ if (info.isDirectory()) {
107
+ const entries = await selected(`source directory ${path}`, () => readdir(absolute));
108
+ for (const name of entries.sort())
109
+ await walk(`${path}/${name}`);
110
+ }
111
+ else if (info.isFile())
112
+ sources.add(path);
113
+ else
114
+ skipped.set(path, 'non-regular');
115
+ };
116
+ for (const path of roots) {
117
+ await selected(`source root ${path}`, async () => {
118
+ const absolute = resolve(target.repoRoot, path);
119
+ if (!inside(target.repoRoot, absolute) || absolute === target.repoRoot)
120
+ throw new LiteralInputError('Source roots must be narrow directories inside the repository, never the repository root.');
121
+ if (await symlinkComponent(target.repoRoot, path)) {
122
+ // An external alias is invalid input, even though it will never be read.
123
+ if (!inside(target.repoRoot, await realpath(absolute)))
124
+ throw new LiteralInputError(`Source root ${path} resolves outside the repository.`);
125
+ skipped.set(path, 'symlink');
126
+ return;
127
+ }
128
+ if (!inside(target.repoRoot, await realpath(absolute)) ||
129
+ !(await stat(absolute)).isDirectory())
130
+ throw new LiteralInputError(`Source root ${path} must be a contained directory.`);
131
+ const canonical = posix(relative(await realpath(target.repoRoot), await realpath(absolute)));
132
+ if (canonical !== path)
133
+ throw new LiteralInputError(`Source root ${path} must use its canonical repo-relative spelling: ${canonical}.`);
134
+ await walk(path);
135
+ });
136
+ }
137
+ const sourceLines = [];
138
+ for (const path of [...sources].sort()) {
139
+ if (!(await containedFile(target.repoRoot, join(target.repoRoot, path))))
140
+ throw new LiteralInputError(`Source ${path} is no longer a contained file; rerun with stable inputs.`);
141
+ const decoded = text(await selected(`source ${path}`, () => readFile(join(target.repoRoot, path))));
142
+ if ('reason' in decoded)
143
+ skipped.set(path, decoded.reason);
144
+ else {
145
+ result.searched.push(path);
146
+ sourceLines.push({
147
+ path,
148
+ lines: decoded.value
149
+ .split(/\r\n|\n|\r/)
150
+ .map((line) => decodeHTMLStrict(line)),
151
+ });
152
+ }
153
+ }
154
+ if (!sourceLines.length)
155
+ throw new LiteralInputError(`No readable UTF-8 source files remain after exclusions; correct --source-root/--source-exclude. Skipped: ${[...skipped]
156
+ .sort()
157
+ .map(([path, reason]) => `${path} (${reason})`)
158
+ .join(', ') || 'none'}.`);
159
+ for (const path of pages) {
160
+ await selected(`page ${path}`, async () => {
161
+ const absolute = resolve(target.contentRoot, path);
162
+ if (!/\.mdx?$/.test(path) ||
163
+ isExcludedPath(target.matcher, path) ||
164
+ (await symlinkComponent(target.contentRoot, path)) ||
165
+ !(await containedFile(target.contentRoot, absolute)))
166
+ throw new LiteralInputError(`Page ${path} must be readable reader Markdown inside the content root, outside nonReaderPaths, with no symlink components below the content root.`);
167
+ const canonical = posix(relative(await realpath(target.contentRoot), await realpath(absolute)));
168
+ if (canonical !== path)
169
+ throw new LiteralInputError(`Page ${path} must use its canonical content-relative spelling: ${canonical}.`);
170
+ const decoded = text(await readFile(absolute));
171
+ if ('reason' in decoded)
172
+ throw new LiteralInputError(`Page ${path} must be UTF-8 Markdown text.`);
173
+ const page = parsePage(path, decoded.value);
174
+ if (page.frontmatterError)
175
+ throw new LiteralInputError(`Page ${path} has invalid YAML frontmatter.`);
176
+ for (const node of page.nodes) {
177
+ if (node.type !== 'inlineCode')
178
+ continue;
179
+ const literal = node.value ?? '';
180
+ const normalized = decodeHTMLStrict(literal);
181
+ const candidate = {
182
+ path,
183
+ line: node.position?.start.line ?? 1,
184
+ literal,
185
+ normalized,
186
+ status: 'not-found',
187
+ };
188
+ for (const source of sourceLines) {
189
+ const index = source.lines.findIndex((line) => line.includes(normalized));
190
+ if (index >= 0) {
191
+ candidate.status = 'found';
192
+ candidate.source = { path: source.path, line: index + 1 };
193
+ break;
194
+ }
195
+ }
196
+ result.literals.push(candidate);
197
+ }
198
+ });
199
+ }
200
+ result.status = 'ok';
201
+ result.skipped = [...skipped]
202
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
203
+ .map(([path, reason]) => ({ path, reason }));
204
+ return result;
205
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createDocsLiteralsCommand(): Command;
3
+ //# sourceMappingURL=literals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"literals.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/literals/literals.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,yBAAyB,IAAI,OAAO,CAmEnD"}
@@ -0,0 +1,50 @@
1
+ import { buildCommandContext } from '../../../app/command-context.js';
2
+ import { readGlobalOptions } from '../../shared/shared.utils.js';
3
+ import { Command } from 'commander';
4
+ import { collectLiteralEvidence, LiteralInputError, } from './evidence.js';
5
+ export function createDocsLiteralsCommand() {
6
+ const append = (value, previous = []) => previous.concat(value);
7
+ return new Command('literals')
8
+ .allowExcessArguments(false)
9
+ .description('Read-only triad inline-code occurrence candidates; found does not prove runtime behavior')
10
+ .option('--target-dir <dir>', 'Docs app root or literal Markdown content root')
11
+ .requiredOption('--page <path>', 'Content-relative Product/QA reader Markdown (repeat one value per flag)', append)
12
+ .requiredOption('--source-root <dir>', 'Narrow repo-relative implementation directory, never repo root (repeatable)', append)
13
+ .option('--source-exclude <pattern>', 'Repo-relative bounded POSIX exclusion: segment *, whole-segment **, trailing / directories (repeatable)', append, [])
14
+ .addHelpText('after', '\nMatching: one strict HTML entity decode per inline-code value and physical source line; case-sensitive substring. CommonMark may normalize multiline code spans. No whitespace/Unicode folding, JS escapes, interpolation or runtime evaluation. Sources are searched only within explicit roots; inspect searched/skipped evidence. Exit 0: completed (including not-found/not-applicable); 1: input/config; 2: runtime failure.')
15
+ .action(async (options, command) => {
16
+ const context = buildCommandContext(readGlobalOptions(command));
17
+ try {
18
+ const result = await collectLiteralEvidence({
19
+ ...options,
20
+ cwd: context.cwd,
21
+ });
22
+ process.exitCode = 0;
23
+ if (context.json)
24
+ context.logger.json(result);
25
+ else {
26
+ context.logger.info(result.status === 'not-applicable'
27
+ ? `Not applicable: ${result.reason}`
28
+ : `Literal occurrence candidates: ${result.literals.filter((c) => c.status === 'found').length} found, ${result.literals.filter((c) => c.status === 'not-found').length} not-found. These require canonical-source judgment.`);
29
+ context.logger.info(JSON.stringify({
30
+ scope: result.scope,
31
+ searched: result.searched,
32
+ skipped: result.skipped,
33
+ literals: result.literals,
34
+ }));
35
+ }
36
+ }
37
+ catch (error) {
38
+ const input = error instanceof LiteralInputError;
39
+ process.exitCode = input ? 1 : 2;
40
+ // Resolver/config diagnostics can carry local absolute paths; keep evidence relative.
41
+ const message = error.message
42
+ .replaceAll(`${context.cwd}/`, '')
43
+ .replaceAll(context.cwd, '.');
44
+ if (context.json)
45
+ context.logger.json({ status: 'error', message });
46
+ else
47
+ context.logger.error(message);
48
+ }
49
+ });
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
4
4
  "private": false,
5
5
  "description": "Open Agent Toolkit CLI",
6
6
  "homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
@@ -42,8 +42,8 @@
42
42
  "unified": "11.0.5",
43
43
  "yaml": "2.8.2",
44
44
  "zod": "^3.25.76",
45
- "@open-agent-toolkit/control-plane": "0.3.29",
46
- "@open-agent-toolkit/docs-transforms": "0.3.29"
45
+ "@open-agent-toolkit/control-plane": "0.3.31",
46
+ "@open-agent-toolkit/docs-transforms": "0.3.31"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/jsdom": "28.0.3",