Skip to content

Commit e26d4aa

Browse files
authored
Merge branch 'main' into rachaelrenk/aeo-citation-blocks
2 parents 252a081 + 64e82c6 commit e26d4aa

54 files changed

Lines changed: 2614 additions & 145 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/missing_docs/references/feature_surface_map.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,22 @@ SkillArguments -> src/content/docs/agent-platform/warp-agents/skills.md
137137
## CLI commands -> doc pages
138138

139139
# Top-level Oz CLI commands
140-
oz agent -> src/content/docs/reference/cli/README.md
141-
oz environment -> src/content/docs/reference/cli/integration-setup.md
142-
oz mcp -> src/content/docs/reference/cli/mcp-servers.md
143-
oz run -> src/content/docs/reference/cli/README.md
144-
oz model -> src/content/docs/reference/cli/README.md
145-
oz login -> src/content/docs/reference/cli/README.md
146-
oz logout -> src/content/docs/reference/cli/README.md
147-
oz integration -> src/content/docs/reference/cli/integration-setup.md
148-
oz schedule -> src/content/docs/reference/cli/README.md
149-
oz secret -> src/content/docs/reference/cli/README.md
150-
oz provider -> src/content/docs/reference/cli/README.md
140+
oz agent -> src/content/docs/reference/cli/index.mdx
141+
oz environment -> src/content/docs/reference/cli/integration-setup.mdx
142+
oz mcp -> src/content/docs/reference/cli/mcp-servers.mdx
143+
oz run -> src/content/docs/reference/cli/index.mdx
144+
oz model -> src/content/docs/reference/cli/index.mdx
145+
oz login -> src/content/docs/reference/cli/index.mdx
146+
oz logout -> src/content/docs/reference/cli/index.mdx
147+
oz whoami -> src/content/docs/reference/cli/index.mdx
148+
oz integration -> src/content/docs/reference/cli/integration-setup.mdx
149+
oz schedule -> src/content/docs/reference/cli/index.mdx
150+
oz secret -> src/content/docs/reference/cli/index.mdx
151+
oz provider -> src/content/docs/reference/cli/index.mdx
152+
oz federate -> src/content/docs/reference/cli/federate.mdx
153+
oz artifact -> src/content/docs/reference/cli/artifacts.mdx
154+
# Internal/hidden command — not a user-facing surface, so no public docs.
155+
oz harness-support -> internal
151156

152157
## API endpoints -> doc pages
153158

.agents/skills/missing_docs/scripts/audit_docs.py

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
SKIP_DIRECTORIES = {"_book", "node_modules", ".git", ".docs"}
2626

27+
# Mutable holder for the docs repo root, set by main()
28+
DOCS_REPO_ROOT: list = [None]
29+
2730
# Paths to reference files (relative to this script)
2831
SCRIPT_DIR = Path(__file__).resolve().parent
2932
SKILL_DIR = SCRIPT_DIR.parent
@@ -179,13 +182,17 @@ def search_docs_for_terms(docs_text: dict[str, str], terms: list[str]) -> list[s
179182

180183
def parse_feature_flags(warp_internal: Path) -> list[str]:
181184
"""Parse FeatureFlag enum variants from features.rs."""
182-
# The FeatureFlag enum lives in the warp_features crate
183-
features_rs = warp_internal / "crates" / "warp_features" / "src" / "lib.rs"
184-
if not features_rs.exists():
185-
# Fall back to legacy path
186-
features_rs = warp_internal / "warp_core" / "src" / "features.rs"
187-
if not features_rs.exists():
188-
print(f"Warning: {features_rs} not found", file=sys.stderr)
185+
# Try known locations in order
186+
candidates = [
187+
warp_internal / "crates" / "warp_features" / "src" / "lib.rs",
188+
warp_internal / "crates" / "warp_core" / "src" / "features.rs",
189+
warp_internal / "app" / "src" / "features.rs",
190+
warp_internal / "warp_core" / "src" / "features.rs",
191+
]
192+
features_rs = next((c for c in candidates if c.exists()), None)
193+
if features_rs is None:
194+
print(f"Warning: features.rs not found. Tried: {[str(c) for c in candidates]}",
195+
file=sys.stderr)
189196
return []
190197

191198
content = features_rs.read_text()
@@ -213,9 +220,14 @@ def parse_feature_flags(warp_internal: Path) -> list[str]:
213220

214221
def parse_default_features(warp_internal: Path) -> set[str]:
215222
"""Parse the default feature list from app/Cargo.toml."""
216-
cargo_toml = warp_internal / "app" / "Cargo.toml"
217-
if not cargo_toml.exists():
218-
print(f"Warning: {cargo_toml} not found", file=sys.stderr)
223+
candidates = [
224+
warp_internal / "app" / "Cargo.toml",
225+
warp_internal / "crates" / "warp_features" / "Cargo.toml",
226+
]
227+
cargo_toml = next((c for c in candidates if c.exists()), None)
228+
if cargo_toml is None:
229+
print(f"Warning: app/Cargo.toml not found. Tried: {[str(c) for c in candidates]}",
230+
file=sys.stderr)
219231
return set()
220232

221233
content = cargo_toml.read_text()
@@ -292,8 +304,14 @@ def audit_features(warp_internal: Path, docs_root: Path, surface_map: dict,
292304

293305
def parse_cli_commands(warp_internal: Path) -> list[dict]:
294306
"""Parse CLI subcommands from warp_cli/src/lib.rs."""
295-
lib_rs = warp_internal / "warp_cli" / "src" / "lib.rs"
296-
if not lib_rs.exists():
307+
candidates = [
308+
warp_internal / "crates" / "warp_cli" / "src" / "lib.rs",
309+
warp_internal / "warp_cli" / "src" / "lib.rs",
310+
]
311+
lib_rs = next((c for c in candidates if c.exists()), None)
312+
if lib_rs is None:
313+
print(f"Warning: warp_cli/src/lib.rs not found. Tried: {[str(c) for c in candidates]}",
314+
file=sys.stderr)
297315
return []
298316

299317
content = lib_rs.read_text()
@@ -329,8 +347,12 @@ def parse_cli_commands(warp_internal: Path) -> list[dict]:
329347

330348
def parse_subcommands_from_file(warp_internal: Path, filename: str) -> list[str]:
331349
"""Parse subcommand names from a CLI command file (e.g., agent.rs)."""
332-
filepath = warp_internal / "warp_cli" / "src" / filename
333-
if not filepath.exists():
350+
candidates = [
351+
warp_internal / "crates" / "warp_cli" / "src" / filename,
352+
warp_internal / "warp_cli" / "src" / filename,
353+
]
354+
filepath = next((c for c in candidates if c.exists()), None)
355+
if filepath is None:
334356
return []
335357

336358
content = filepath.read_text()
@@ -366,6 +388,10 @@ def audit_cli(warp_internal: Path, docs_root: Path, surface_map: dict,
366388
# Check surface map
367389
if cmd_str in cli_to_doc:
368390
doc_path = cli_to_doc[cmd_str]
391+
# `internal` is a sentinel for hidden/internal commands that
392+
# intentionally have no public docs (matches API audit semantics).
393+
if doc_path == "internal":
394+
continue
369395
if (docs_root.parent / doc_path).exists():
370396
continue # Mapped and exists
371397

@@ -437,8 +463,13 @@ def audit_api(warp_server: Path, docs_root: Path, surface_map: dict,
437463
except Exception:
438464
pass
439465

440-
# Also check OpenAPI spec
441-
openapi_path = docs_root / "developers" / "agent-api-openapi.yaml"
466+
# Also check OpenAPI spec (lives at repo root, not under content/docs)
467+
repo_root = DOCS_REPO_ROOT[0] or docs_root.parent
468+
openapi_candidates = [
469+
repo_root / "developers" / "agent-api-openapi.yaml",
470+
docs_root / "developers" / "agent-api-openapi.yaml",
471+
]
472+
openapi_path = next((c for c in openapi_candidates if c.exists()), openapi_candidates[0])
442473
openapi_text = ""
443474
if openapi_path.exists():
444475
try:
@@ -640,12 +671,20 @@ def main():
640671
args = parser.parse_args()
641672

642673
# Find repos
643-
docs_root = SKILL_DIR.parent.parent.parent # .warp/skills/missing_docs -> docs root
644-
docs_root = docs_root / "docs"
645-
646-
if not docs_root.exists():
647-
print(f"Error: docs directory not found at {docs_root}", file=sys.stderr)
674+
# SKILL_DIR is at <repo>/.agents/skills/missing_docs (or legacy <repo>/.warp/skills/...)
675+
repo_root = SKILL_DIR.parent.parent.parent
676+
# Astro Starlight docs live at src/content/docs
677+
candidates = [
678+
repo_root / "src" / "content" / "docs",
679+
repo_root / "docs",
680+
]
681+
docs_root = next((c for c in candidates if c.exists()), None)
682+
if docs_root is None:
683+
print(f"Error: docs directory not found. Tried: {[str(c) for c in candidates]}",
684+
file=sys.stderr)
648685
sys.exit(1)
686+
# repo_root carries the developers/ openapi spec etc.
687+
DOCS_REPO_ROOT[0] = repo_root
649688

650689
warp_internal = find_repo("warp-internal", args.warp_internal, docs_root)
651690
warp_server = find_repo("warp-server", args.warp_server, docs_root)

.agents/skills/style_lint/style_lint.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
# Configuration
2525
# ---------------------------------------------------------------------------
2626

27-
DOCS_ROOT = Path("docs")
27+
# Astro Starlight content lives under src/content/docs/, not a top-level docs/.
28+
# This used to be `Path("docs")`, which silently scanned 0 files in the
29+
# Astro layout — running --all reported "No issues found" without auditing
30+
# anything.
31+
DOCS_ROOT = Path("src/content/docs")
2832
CHANGELOG_DIR = DOCS_ROOT / "changelog"
2933
EXCLUDED_DIRS = {"_book", "node_modules", ".docs"}
3034

@@ -134,7 +138,7 @@ class Report:
134138
def find_all_md_files() -> List[Path]:
135139
"""Find all markdown files in docs/, excluding build artifacts and changelog."""
136140
files = []
137-
for f in DOCS_ROOT.rglob("*.md"):
141+
for f in [*DOCS_ROOT.rglob("*.md"), *DOCS_ROOT.rglob("*.mdx")]:
138142
if any(part in EXCLUDED_DIRS for part in f.parts):
139143
continue
140144
# Exclude changelog (historical record)
@@ -148,12 +152,12 @@ def find_changed_md_files() -> List[Path]:
148152
"""Find markdown files changed in the current branch vs main."""
149153
try:
150154
result = subprocess.run(
151-
["git", "diff", "--name-only", "origin/main...HEAD", "--", "docs/"],
155+
["git", "diff", "--name-only", "origin/main...HEAD", "--", str(DOCS_ROOT)],
152156
capture_output=True, text=True, check=True,
153157
)
154158
files = []
155159
for line in result.stdout.strip().split("\n"):
156-
if line.endswith(".md") and os.path.exists(line):
160+
if (line.endswith(".md") or line.endswith(".mdx")) and os.path.exists(line):
157161
p = Path(line)
158162
if not any(part in EXCLUDED_DIRS for part in p.parts):
159163
files.append(p)
@@ -658,7 +662,7 @@ def create_pr_with_fixes() -> None:
658662
"""Create a branch and PR with the auto-fixes."""
659663
branch = "fix/style-lint-auto-fixes"
660664
subprocess.run(["git", "checkout", "-b", branch], check=True)
661-
subprocess.run(["git", "add", "docs/"], check=True)
665+
subprocess.run(["git", "add", str(DOCS_ROOT)], check=True)
662666
result = subprocess.run(["git", "diff", "--cached", "--quiet"])
663667
if result.returncode == 0:
664668
print("No changes to commit.")
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
name: sync-openapi-spec
3+
description: >-
4+
Sync the public Oz Agent API OpenAPI spec from warp-server into the docs
5+
repo, regenerating `developers/agent-api-openapi.yaml` (the file that
6+
powers the Scalar API reference at `docs.warp.dev/api`). Use when the
7+
warp-server public API has changed, when the Scalar reference looks
8+
stale, or on a scheduled cadence to keep the public API docs aligned
9+
with the canonical spec.
10+
---
11+
12+
# Sync OpenAPI Spec
13+
14+
Keep `developers/agent-api-openapi.yaml` in sync with the canonical spec at `warp-server/public_api/openapi.yaml`.
15+
16+
**Direction:** warp-server → docs. The server spec is the source of truth. The docs file is a curated subset (drops `memory_stores`/`harness-support` and a handful of internal `agent` paths) that Scalar renders on `docs.warp.dev/api`.
17+
18+
## Repos
19+
20+
This skill requires two repos in the agent's environment:
21+
22+
- `warpdotdev/warp-server` — source of truth (`public_api/openapi.yaml`)
23+
- `warpdotdev/docs` — Scalar-facing copy (`developers/agent-api-openapi.yaml`)
24+
25+
## Prerequisites
26+
27+
- Both repos checked out, with `warp-server` reachable from the docs repo (default assumption: sibling directories — `../warp-server/public_api/openapi.yaml`)
28+
- Python 3 with `pyyaml` installed (`pip install pyyaml` or `pip install --break-system-packages pyyaml` in managed environments)
29+
- `gh` CLI authenticated against `warpdotdev/docs`
30+
31+
## Workflow
32+
33+
### Step 1: Self-test
34+
35+
Run the script's self-test first to confirm `pyyaml` is available and the transform logic still passes:
36+
37+
```bash
38+
python3 .agents/skills/sync-openapi-spec/scripts/sync_openapi.py --mode self-test
39+
```
40+
41+
Expected output: `self-test: OK`. If this fails, fix the script before going further.
42+
43+
### Step 2: Diff source against target
44+
45+
```bash
46+
python3 .agents/skills/sync-openapi-spec/scripts/sync_openapi.py \
47+
--mode diff \
48+
--source ../warp-server/public_api/openapi.yaml \
49+
--target developers/agent-api-openapi.yaml
50+
```
51+
52+
The script prints structural drift grouped into:
53+
- Paths added/removed/modified relative to the expected docs subset
54+
- Component schemas added/removed/modified
55+
- Top-level changes (`openapi`, `info`, `servers`)
56+
- Unclassified tags or paths (anything not covered by `EXCLUDED_TAGS` or the `agent`/`schedules` allowlist)
57+
58+
If the script reports `In sync. No changes needed.`, stop here.
59+
60+
### Step 3: Triage unclassified items
61+
62+
Any line prefixed with `!` flags a tag or path the policy doesn't recognize. Do NOT auto-include or auto-drop these. For each one:
63+
1. Read the corresponding handler in `warp-server/router/handlers/public_api/` to confirm whether the endpoint is intended to be public.
64+
2. If the endpoint is public-facing, leave the policy alone — the script will include it on the next `apply`.
65+
3. If the endpoint should remain hidden, extend `EXCLUDED_TAGS` or `EXCLUDED_PATHS` in `scripts/sync_openapi.py` and update `references/sync-policy.md` to record the rationale.
66+
4. Re-run `--mode diff` until no `!` lines remain.
67+
68+
### Step 4: Apply the regenerated subset
69+
70+
```bash
71+
python3 .agents/skills/sync-openapi-spec/scripts/sync_openapi.py \
72+
--mode apply \
73+
--source ../warp-server/public_api/openapi.yaml \
74+
--target developers/agent-api-openapi.yaml
75+
```
76+
77+
This rewrites `developers/agent-api-openapi.yaml` with the regenerated subset. Apply mode validates every `$ref` in the output before writing the file: if any reference is unresolved, the script exits with code 3 and refuses to write. On success it prints `All $refs resolve in the regenerated spec.`
78+
79+
### Step 5: Validate the regenerated spec
80+
81+
Apply mode already catches unresolved `$ref`s (see Step 4). Run these as belt-and-braces integration checks:
82+
83+
```bash
84+
# Astro picks up the new YAML and parses it through Scalar's runtime.
85+
npm run build
86+
```
87+
88+
Optional, recommended when many schemas changed (full OpenAPI lint):
89+
```bash
90+
npx @redocly/cli lint developers/agent-api-openapi.yaml
91+
```
92+
93+
If `npm run build` fails, the most common cause is a malformed path or missing `description` field. Schema-ref breakage is already prevented by Step 4's validator.
94+
95+
### Step 6: Commit and open a PR
96+
97+
```bash
98+
git checkout -b sync-openapi-spec/YYYY-MM-DD
99+
git add developers/agent-api-openapi.yaml
100+
git commit -m "docs: sync agent-api-openapi.yaml from warp-server
101+
102+
Co-Authored-By: Oz <oz-agent@warp.dev>"
103+
git push origin sync-openapi-spec/YYYY-MM-DD
104+
```
105+
106+
Open a draft PR with:
107+
- **Title:** `docs: sync agent-api-openapi.yaml from warp-server`
108+
- **Body:** include the full output from Step 2 (paths/schemas added/removed/modified) so reviewers can see exactly what changed and why.
109+
- **Labels:** `documentation`
110+
111+
Use `report_pr` to surface the PR link.
112+
113+
### Step 7: Report
114+
115+
Summarize:
116+
- Source commit SHA used (capture with `cd ../warp-server && git rev-parse HEAD`)
117+
- Number of paths added / removed / modified in the regenerated subset
118+
- Number of schemas added / removed / modified
119+
- Any items flagged for triage and how they were resolved
120+
- Or confirm `In sync. No changes needed.`
121+
122+
## Sync policy
123+
124+
The policy is encoded in `scripts/sync_openapi.py` as `EXCLUDED_TAGS` and `EXCLUDED_PATHS`. See `references/sync-policy.md` for the rationale behind each entry and the rules for adding new ones.
125+
126+
## Schedule
127+
128+
Run on demand whenever `warp-server/public_api/openapi.yaml` has changed materially since the last docs sync, or on a weekly cadence as a safety net.
129+
130+
## Troubleshooting
131+
132+
### `ModuleNotFoundError: No module named 'yaml'`
133+
Install pyyaml: `pip install pyyaml`. On Debian-based images with externally managed Python, use `pip install --break-system-packages pyyaml`.
134+
135+
### `error: source spec not found at ...`
136+
The `warp-server` repo isn't where the script expected. Pass `--source /absolute/path/to/warp-server/public_api/openapi.yaml`.
137+
138+
### `--mode apply` exits with code 3 and "unresolved $refs"
139+
Apply mode refuses to write the target if any `$ref` in the regenerated spec doesn't resolve to a defined component. The script's recursive `$ref` walker is supposed to keep transitive references (`allOf`/`oneOf`/`anyOf`/`items`/`additionalProperties`/etc.) reachable, so this means either:
140+
- The source spec itself has a dangling reference (fix it in `warp-server`), or
141+
- The walker is missing a reference shape (file a bug against the script).
142+
143+
The error output lists the offending JSON pointer paths so you can locate the reference quickly. Apply will not overwrite `developers/agent-api-openapi.yaml` while this fails.
144+
145+
### Diff shows changes that aren't in the source spec
146+
Make sure `../warp-server` is on the branch you intended to compare against (usually `develop`). Run `cd ../warp-server && git status -sb && git --no-pager log -1` to confirm.
147+
148+
## References
149+
150+
- `scripts/sync_openapi.py` — the diff/apply tool
151+
- `references/sync-policy.md` — exclusion policy and how to extend it
152+
- `../warp-server/.agents/skills/update-open-api-spec/SKILL.md` — server-side workflow for editing the canonical spec
153+
- `../../../src/pages/api.astro` — how the docs site loads the YAML into Scalar

0 commit comments

Comments
 (0)