Skip to content

feat(cli): generate .well-known agent skills bundle from docs.yml skills path#16510

Open
matlegault wants to merge 6 commits into
mainfrom
matlegault/skills-docs-yml-path
Open

feat(cli): generate .well-known agent skills bundle from docs.yml skills path#16510
matlegault wants to merge 6 commits into
mainfrom
matlegault/skills-docs-yml-path

Conversation

@matlegault

@matlegault matlegault commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Description

Linear ticket: Refs FER-11043

Reworks the docs-site Agent Skills feature so skills are declared explicitly in docs.yml instead of authors hand-building fern/.well-known/skills/ (follow-up to #16446, serving side in fern-api/fern-platform#11947 — which needs zero changes). A new load-bearing page-actions.options.skills.path field points at a same-repo directory of skills (resolved relative to docs.yml, ../ allowed, e.g. a repo-root .agents/skills/); at fern generate --docs the CLI discovers every subdirectory containing a SKILL.md, validates the bundle, generates the v0.1.0 index.json discovery manifest in a temp dir (nothing written back to the repo), and re-homes every file in the publish payload at .well-known/skills/<name>/… so npx skills add https://<docs-domain> works. A declared path wins over a hand-populated fern/.well-known/skills/ folder (warning on conflict), while the raw well-known passthrough remains the quiet, undocumented fallback when no path is declared.

Changes Made

  • path added to SkillsPageActionConfig (docs.yml definition, generated SDK types, both JSON schemas); parsed into ParsedPageActionsConfig.options.skillsDirectory (CLI-only — never sent to FDR)
  • The repository display field is removed from SkillsPageActionConfig — with skills published from a local path it's redundant "View source" sugar that can drift (and is useless for private docs repos); install-command stays as the escape hatch for auth-walled docs, private registries, and multi-step installs
  • New shared discovery/manifest module docs-resolver/src/utils/declaredSkills.ts: hard errors for missing/empty path, non-kebab-case or over-long names, name ≠ directory, empty description, duplicate names, and markdown references escaping a skill's directory; manifest format verified against Stripe's live /.well-known/skills/index.json (sha256 digests / v0.2.0 left as fast-follow)
  • DocsDefinitionResolver.collectAgentSkillsUploads() builds the re-homed upload payload (declared path) or falls back to verbatim passthrough (no path)
  • valid-well-known-skills rule repointed at the declared path (with conflict warning), keeping the raw-bundle validation for the fallback; valid-skills-page-action rejects an empty path
  • Moved the (hermetic) skills tests into src/utils/__test__/ so they actually run in CI — the docs-resolver test script skips src/__test__/, so feat(cli): agent skills for docs sites — upload .well-known bundles and add the Install-skills page action config #16446's tests were never executed
  • Updated README.md generator (if applicable)

Testing

  • Unit tests added/updated — fixtures for multi-skill happy path with references, single skill, missing/empty path, name/dir mismatch, duplicates, escaping references, and well-known conflict, asserting exact upload fullPaths and exact generated index.json contents (docs-resolver + docs-validator + configuration-loader suites pass; all 159 dependent packages compile)
  • Manual testing completed

🤖 Generated with Claude Code

@matlegault matlegault requested a review from amckinney as a code owner June 12, 2026 19:22
@matlegault matlegault self-assigned this Jun 12, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +37 to +44
if (declaredSkillsPath != null) {
return validateDeclaredSkillsPath({
absolutePathToFernFolder: workspace.absoluteFilePath,
absolutePathToSkillsDirectory: resolve(
dirname(workspace.absoluteFilepathToDocsConfig),
declaredSkillsPath
)
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 .well-known/agent-skills/ validation skipped when a declared skills path is set, but files are still uploaded

When config.pageActions?.options?.skills?.path is non-null, the validator at valid-well-known-skills.ts:37-44 returns early from validateDeclaredSkillsPath(), which only validates the declared path and warns about .well-known/skills/. It never validates .well-known/agent-skills/. However, DocsDefinitionResolver.ts:879-890 still uploads .well-known/agent-skills/ files via collectWellKnownSkillsFiles when a declared path is set. This is a regression: before this PR, both directories were always validated. Now, an invalid .well-known/agent-skills/index.json (e.g., not valid JSON) or broken SKILL.md frontmatter would pass fern check but still be served, silently breaking npx skills add for the agent-skills layout.

Prompt for agents
In valid-well-known-skills.ts, the file-level visitor (lines 35-57) returns early when a declared skills path is set, skipping validation of .well-known/agent-skills/. But the DocsDefinitionResolver (lines 879-890) still uploads .well-known/agent-skills/ files in that scenario.

The fix: after calling validateDeclaredSkillsPath, also validate the .well-known/agent-skills/ directory (WELL_KNOWN_AGENT_SKILLS_DIRECTORY) by appending its violations before returning. Something like:

  const violations = await validateDeclaredSkillsPath({...});
  violations.push(...(await validateWellKnownSkillsDirectory({
      absolutePathToFernFolder: workspace.absoluteFilePath,
      wellKnownDirectory: WELL_KNOWN_AGENT_SKILLS_DIRECTORY
  })));
  return violations;

This ensures agent-skills bundles that are still uploaded verbatim continue to be validated, matching pre-PR behavior.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Docs Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-07-13T05:08:13Z).

Fixture main PR Delta
docs 241.5s (n=5) 238.3s (35 versions) -3.2s (-1.3%)

Docs generation runs fern generate --docs --preview end-to-end against the benchmark fixture with 35 API versions (each version: markdown processing + OpenAPI-to-IR + FDR upload).
Delta is computed against the nightly baseline on main.
Baseline from nightly run(s) on main (latest: 2026-07-13T05:08:13Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-07-13 18:34 UTC

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-07-13T05:08:13Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 76s (n=5) 115s (n=5) 67s -9s (-11.8%)
go-sdk square 139s (n=5) 285s (n=5) 135s -4s (-2.9%)
java-sdk square 226s (n=5) 267s (n=5) 204s -22s (-9.7%)
php-sdk square 61s (n=5) 85s (n=5) 50s -11s (-18.0%)
python-sdk square 135s (n=5) 241s (n=5) 132s -3s (-2.2%)
ruby-sdk-v2 square 94s (n=5) 124s (n=5) 83s -11s (-11.7%)
rust-sdk square 169s (n=5) 176s (n=5) 172s +3s (+1.8%)
swift-sdk square 58s (n=5) 433s (n=5) 56s -2s (-3.4%)
ts-sdk square 127s (n=5) 128s (n=5) 124s -3s (-2.4%)

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-07-13T05:08:13Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-07-13 18:35 UTC

matlegault and others added 2 commits July 7, 2026 10:46
…lls path

Skills are now declared explicitly via page-actions.options.skills.path
(a same-repo directory, resolved relative to docs.yml; ../ allowed). At
generate/check time the CLI discovers every subdirectory containing a
SKILL.md, validates the bundle, generates the index.json discovery
manifest (v0.1.0, in a temp dir — nothing written back to the repo), and
re-homes every file in the publish payload at .well-known/skills/<name>/….
A declared path wins over a hand-populated fern/.well-known/skills/
folder (warned on conflict); the raw passthrough remains the quiet
fallback when no path is declared.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With skills now declared locally via path and the CLI publishing the
bundle, the "View source" repository link is redundant display sugar
that can drift from what's actually served — and it's useless when the
docs repo is private. install-command stays as the escape hatch for
auth-walled docs, private registries, and multi-step installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@matlegault matlegault force-pushed the matlegault/skills-docs-yml-path branch from 46b30ab to f462b38 Compare July 7, 2026 14:59
@matlegault matlegault requested a review from Swimburger as a code owner July 7, 2026 14:59
matlegault and others added 4 commits July 13, 2026 11:45
…s-yml-path

# Conflicts:
#	packages/cli/docs-resolver/src/DocsDefinitionResolver.ts
- Keep accepting the deprecated `repository` skills field (accept-and-ignore)
  instead of removing it, so existing docs.yml files that still declare it
  continue to load rather than failing schema validation.
- Skip Agent Skills collection during validation-only resolves so its errors
  no longer surface as a misattributed failure of an unrelated rule
  (valid-markdown-links); publish/preview still collect and validate.
- Treat a `skills.path` that points at a file as a clean violation instead of
  crashing with ENOTDIR (doesPathExist "directory"/"file" modes).
- Ignore link/image references shown inside fenced or inline code so example
  syntax with `../` no longer blocks publishing.
- Keep validating the `.well-known/agent-skills/` passthrough when a skills
  path is declared (it is still uploaded verbatim).

Adds regression tests for the file-path, code-fence, and agent-skills cases.

Co-Authored-By: Claude <noreply@anthropic.com>
…prefix

`relative(skillDir, resolved).startsWith("..")` false-positived on in-directory
targets like `...` (the ellipsis placeholder in `[x](...)`) and `..foo`. Test
for a leading `..` path segment instead. fs-utils `relative` always returns
forward slashes, so the separator is `/`, not the platform `sep`.

Co-Authored-By: Claude <noreply@anthropic.com>
…egenerate

The earlier accept-and-ignore fix hand-edited the generated schema/SDK files
without updating their source definition, so the "regenerate and diff" CI guard
failed. Add `repository` (deprecated, ignored) back to fern/apis/docs-yml and
regenerate docs-yml.schema.json via `pnpm docs-yml:jsonschema`; align the
generated SDK types to the generator's output.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant