Skip to content

feat(client): publish Agent Skills from the experimental entry point - #144

Merged
XieX merged 6 commits into
xie/agent-skillsfrom
xie/skills-experimental-entry-point
Oct 7, 2026
Merged

XieX merged 6 commits into
xie/agent-skillsfrom
xie/skills-experimental-entry-point

Conversation

@XieX

@XieX XieX commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Reorganizes Agent Skills to ship as an experimental feature.

Every Skills name is exported from launchdarkly_ai_server.experimental.skills, and none from the package root. Applications opt in by importing from that module. While the feature is experimental, its names may change in a minor release, so review the changelog when you upgrade.

Additionally, the store is configured via a set_skill_store(store) setter rather than injected into init_client, which logs a warning for any option key it does not read, so a retired skillStore option is reported rather than dropped silently. The check is generic and does not name the feature.

Other changes:

  • Shutdown: shutdown() clears the skill store in a way that logs a failure rather than raising it into the core call. The test reset clears it directly, so a failed reset fails the test.
  • Messages and docs: error messages, docstrings, the README and agents.md now point at set_skill_store and the experimental import path. The init_client docstring and README list all seven options it reads. The README quickstart stops before write_skills when inspect_config returns no config, since an empty reference list would prune every managed skill. The launchdarkly-ai-python README notes that experimental features are not re-exported and are imported from launchdarkly_ai_server.experimental. agents.md clarifies that the experimental part is the SDK's API, not the LaunchDarkly data model, so AiConfigRep still documents a config's skills references.
  • Tests:
    • The experimental exports are pinned to an exact list, so a new export fails. The module's public dir() is pinned too.
    • The root __all__ is pinned to a checked-in snapshot (tests/test_public_surface.py), so a new root export fails.
    • A failure clearing experimental state in shutdown() is logged, and the client is still closed.
    • set_skill_store raises TypeError for a value without callable get_object and all_objects, as in the JS SDK.
    • No Skills name is reachable from the package root.
    • init_client ignores a skillStore option and warns about it; unknown options warn and documented ones do not.
    • set_skill_store(None) is ignored and leaves the configured store in place, as in the JS SDK.
    • SKILL_OBJECT_KIND and MAX_SKILL_CONTENT_BYTES stay unexported from both the root and the experimental module.

Open questions

  • The config parser still validates skills. parse_ai_config fails a whole config when its skills array is malformed, so a bad Skills field can fail an ordinary config().invoke(). That conflicts with "experimental behaviour must not break a core call". The behaviour is unchanged here, pending a decision.
  • Evaluations is still exported from the package root. It was already released from there, so moving it to experimental.evaluations needs deprecated aliases. That is left for a separate change.

Test plan

  • make lint, make format-check, make typecheck
  • uv run pytest: 2217 passed, 11 skipped

🤖 Generated with Claude Code, updated by @XieX


Note

Overview
Moves Agent Skills off the stable package root into launchdarkly_ai_server.experimental.skills, so those APIs may change in minor releases while the root export list stays pinned in tests.

Configuration: skill stores are wired with set_skill_store(store) instead of init_client(options={"skillStore": ...}). init_client only accepts seven documented option keys and logs a warning for anything else (including retired skillStore). shutdown() clears experimental state in a path that logs failures instead of breaking core teardown.

Validation split: parse_ai_config no longer rejects variations for a malformed skills field (core config().invoke() / inspect_config keep working). skill_refs now validates fail-closed and raises ValueError (including for skills: null) so pruning reconciles never treat a bad field as “no skills.”

Docs and tests are updated for the experimental import path, set_skill_store, and frozen snapshots of root vs experimental __all__.

Reviewed by Cursor Bugbot for commit 40fd647. Bugbot is set up for automated code reviews on this repo. Configure here.

XieX and others added 2 commits October 6, 2026 13:01
Agent Skills is exported only from launchdarkly_ai_server.experimental.skills,
and none of its names is exported from the package root. Its names may change
in a minor release while it is experimental.

- Add set_skill_store(store) and remove the skillStore option from
  init_client: core client options do not name an experimental feature.
- shutdown() still clears the skill store, through an internal hook whose
  failure is logged rather than raised.
- Pin the experimental surface to an explicit allowlist in tests, and assert
  that no Skills name is reachable from the package root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_skill_store

- init_client logs a warning for any option key it does not read, so a
  retired option such as skillStore is reported rather than dropped silently.
- Inline _resolve_client into init_client; the split existed only to install
  the skill store after a successful resolve.
- set_skill_store takes a SkillStore and raises TypeError on None.
- _reset_for_testing clears skills state directly so a failed reset fails
  the test instead of being logged.
- Soften the changelog wording for experimental names, and note in the
  launchdarkly-ai-python README where experimental features are imported from.
- Clarify in agents.md that AiConfigRep still documents skills references:
  the experimental part is the SDK API, not the data model.
- Drop init_client calls that tests no longer need before set_skill_store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XieX
XieX marked this pull request as ready for review October 6, 2026 19:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jeffdupont jeffdupont 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.

Reviewed at 01bfad1d; re-checked against 56bfacaa (set_skill_store(None) now ignored, matching JS).

The move itself is clean. Every Skills name is gone from the root and from launchdarkly_ai_python, skillStore is gone from code, docs and error messages, and the wheel ships experimental/. At 01bfad1d I get 2210 passed, 11 skipped, and ruff, format and mypy are clean.

I ran mutations against the export tests. These ones fail the tests, which is what we want:

  • adding frontmatter to experimental/skills.py and its __all__
  • adding get_skill to the root
  • adding SKILL_OBJECT_KIND to the experimental module
  • adding MAX_SKILL_CONTENT_BYTES to the root
  • adding SKILL_OBJECT_KIND to the root as a bare attribute

Dropping the unknown-option warning fails 2 tests. Bringing skillStore back fails 1.

Two of the mutations get through. Details are inline.

  1. The root half of the export test is still a denylist. If I add def frontmatter plus __all__.append("frontmatter") to the root __init__.py, all 8 tests pass (TestPackageExports and packages/ai/tests). #33 says neither the root nor the experimental entry point may expose a frontmatter accessor, and A.14 asks for a checked-in __all__ snapshot for the root as well as for each experimental module. js #114 checks in API reports for both entry points. Please pin the root __all__ (114 names) the same way you pinned EXPERIMENTAL_SKILLS_SURFACE.
  2. "Logged, not raised" in shutdown has no test. If I remove the try/except in _clear_experimental_state (lifecycle.py:292-295), the client suite still passes (1396). Nothing can fail there today, because clear_state only assigns two globals (skills_core.py:186-190). That makes the test cheap to add now, before a hook that can raise shows up. Patch skills._clear_state to raise, then assert that shutdown() returns, the client is closed, and the warning is logged.

Open questions from the PR body

(a) Malformed skills fails core calls. I think this needs a decision before 1.0, not in this PR. I tested skills[0].key = "My_Skill", skills: null, version: 0 and a non-list value. Each one makes extract_variation raise Invalid AI config variation ..., so config().invoke(), config().stream() and graph nodes fail (client.py:81, :184; graph.py:163). inspect_config returns enabled=True, config=None. main has no skills validation at all, so this behaviour arrives with #87. If any existing skill keys fall outside the gonfalon #73433 grammar (still unverified), apps that never import Skills will start failing invoke. My suggestion:

  • stop validating skills in parse_ai_config (types_validation.py:151-156)
  • make skill_refs raise on an array that is present but malformed, instead of dropping entries with a warning

write_skills then still never sees a partial or empty list, so the prune-safety reason for the check holds, and §0.3 is met. The core parse behaviour freezes at 1.0, and JS needs the same change.

(b) Evaluations were released from the root. init_evaluations, EvaluationsModule, EvaluationsError, EvalRunResult, GenerationConfig and RunSummary are in the root __all__ of launchdarkly-ai-server 0.2.2 (PyPI, 8 Sep). 0.2.3 has the same. 0.2.4 (30 Sep) adds Judge, Scorer and Criterion. I checked the wheels. launchdarkly-ai-python 0.1.6-0.1.8 re-exports them through import *. So people can import them from the root today. 1.0 is a major release, so semver allows the move, but deprecated root aliases are cheap (module __getattr__ with a DeprecationWarning). Leaving that out of this PR is fine. #33 needs fixing, though: its register marks Evaluations as experimental and "absent from the package root", and §0 only describes forward moves, so it has no path for demoting a released feature. Either drop Evaluations from the register, or add the alias step. #41 (merged today) also lists client.init_evaluations as a helper string, which clashes with #33's experimental. prefix rule.

Smaller items

  • Parity with js #114: two of the three differences I found are now settled. 56bfacaa makes set_skill_store(None) a no-op like JS, and js #114's 5f3779b2 switches JS to the same generic unknown-option warning as here. The one left: JS adds @launchdarkly/ai-node/experimental, while the Python barrel deliberately doesn't (packages/ai/README.md:78-83). Either is defensible; #33's A.14 should record which.
  • README quickstart deletes skills when inspect_config fails (README.md:388, :398). The quickstart passes skill_refs(info["config"]) to write_skills with the default prune=True. If inspect_config fails (LaunchDarkly unreachable, or case (a) above), config is None, the refs come back [], and every managed skill is removed with report.ok == True. I reproduced this. The bug predates this PR, but this PR rewrites that block. Please check info["config"] is not None before writing. This is the Python half of the JS README issue.
  • The init_client allowlist is complete. lifecycle.py reads exactly those seven keys, nothing else reads init_client options, and the SDK's own lazy calls pass no options, so there are no false warnings. The keys aren't actually documented, though. The docstring (lifecycle.py:161-171) mentions only sdkKey, and the README table (packages/client/README.md:197) lists none. Please list them in both. Also, since skillStore never shipped in a Python release, the generic warning is enough here.
  • set_skill_store accepts anything that isn't None. A string, a dict or an int all succeed. The first accessor call then logs an ERROR traceback and returns store_unavailable ("'str' object has no attribute 'get_object'"). Checking callable(getattr(store, "get_object", None)) would catch this when the store is set. Optional.
  • AiConfigRep's docstring (types.py:96) says "see skill_refs". That's a core public type pointing at an experimental name. Please point at experimental.skills.skill_refs or drop the reference.
  • watch_skills's no-store message (skills_watch.py:229-231) leaves out the import path. NO_STORE_MESSAGE includes it.
  • Not a change request, but worth a line in the set_skill_store docstring. Swapping the store doesn't close the old one, and a running watcher keeps listening on the store it captured (skills_watch.py:227). Thread-safety is fine: there's one global, and each accessor reads it once.

Merge order

This PR implements monorepo #33, which is still open. main's TESTING.md still requires root exports and init_client({"skillStore"}) (:1031, :1539, :1647, :2860). Merging this into #87's branch is fine. #87 shouldn't reach main before #33. #33 also needs work before it lands:

  • It needs a new appendix number, because main now has "A.14 Helper usage strings".
  • #35 needs rebasing onto #33. #35's A.12 paragraph names _resolve_client and test_failed_init_client_leaves_no_store_configured, and this PR removes both. #35's §3.9 skillStore bullet is the one #33 deletes. The retry rule still holds: test_retries_after_a_failed_init_instead_of_replaying_it is at test_lifecycle.py:297.

Comment thread packages/client/tests/test_skills.py
Comment thread packages/client/tests/test_skills.py
Comment thread packages/client/src/launchdarkly_ai_server/lifecycle.py
Comment thread packages/client/src/launchdarkly_ai_server/lifecycle.py
Comment thread packages/client/src/launchdarkly_ai_server/skills.py Outdated
Comment thread packages/client/src/launchdarkly_ai_server/skills_watch.py Outdated
…tal move

- Pin the root `__all__` to a checked-in snapshot, so a root export such as
  a `frontmatter` accessor fails a test rather than relying on a denylist.
  The experimental module's `dir()` is pinned too, not only its `__all__`.
- Test that a failure clearing experimental state in `shutdown()` is logged,
  not raised, and the client is still closed.
- `set_skill_store` raises `TypeError` for a value without callable
  `get_object` and `all_objects`, instead of failing later as
  `store_unavailable`. Its docstring says replacing a store does not close
  the old one and a running watcher keeps the store it started with.
- Document all seven `init_client` options in the docstring and README.
- The README quickstart stops before `write_skills` when `inspect_config`
  returns no config, since an empty reference list would prune every skill.
- `AiConfigRep` points at `experimental.skills.skill_refs`, and the
  `watch_skills` no-store message names the import path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: c9bcdca8-149b-407d-b97e-265a8e2509b8)

@XieX

XieX commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks @jeffdupont. f5c9aa2 covers the two mutations that got through and the smaller items. Inline replies have the details.

  • Root export test: the root __all__ is now pinned to a snapshot.
  • Shutdown "logged, not raised": now tested.
  • README quickstart: it stops before write_skills when inspect_config returns no config, and the comment says why.
  • init_client options: all seven are listed in the docstring and in a README table.
  • set_skill_store: raises TypeError for a non-store, and JS matches. The docstring now says replacing a store does not close the old one, and a running watcher keeps the store it started with.
  • AiConfigRep points at experimental.skills.skill_refs. watch_skills's no-store message names the import path.

Not changed here:

  • (a) Malformed skills failing core calls: I'm working on another pair of PRs to move skills validation into the skills functions.
  • (b) Evaluations at the root, the ai-node /experimental parity note, and the generic unknown-option rule would need spec changes. Not in scope here.

@XieX
XieX requested a review from jeffdupont October 7, 2026 05:12
`parse_ai_config` rejected a whole AI Config when its `skills` field was
malformed, so a bad experimental field failed `config().invoke()`,
`stream()`, graph nodes and `extract_variation` for apps that never use
Agent Skills. That breaks TESTING.md §0.3: experimental behaviour must not
break a core call.

The parser now passes `skills` through unchecked. `skill_refs` validates it
instead and raises `ValueError` when the field is present but malformed,
including `skills: null` and a single bad entry. It used to drop bad entries
with a warning, which was safe only because the parser had already rejected
them. Raising keeps the original reason for the check: `write_skills` with
`prune=True` never receives an empty or partial list for a field that is
present. An absent field, or a config that is not a dict, still returns `[]`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…146)

## Summary

A malformed `skills` field no longer fails core config calls. Today
`parse_ai_config` rejects the whole AI Config when `skills` is
malformed. `config().invoke()`, `stream()`, graph nodes and
`extract_variation` then raise, and `inspect_config` returns `config:
None`, even for apps that never use Agent Skills. Agent Skills is
experimental, and TESTING.md §0.3 says experimental behaviour must not
break a core call.

- **`parse_ai_config` passes `skills` through unchecked.**
- **`skill_refs` validates the field and raises `ValueError`** when it
is present but malformed, including `skills: null` and a single bad
entry. Before, it dropped bad entries with a warning, which was only
safe because the parser had already rejected them.
- **An absent field, or a config that is not a dict, still returns
`[]`.**

Raising rather than dropping keeps the reason the parser checked in the
first place. `write_skills` with the default `prune=True` deletes every
managed skill missing from the list it is given, so `skill_refs` never
returns an empty or partial list for a field that is present.

Stacked on #144. JS counterpart: launchdarkly/js-ai-sdk#117.

## Tests

- **Parser:** a malformed `skills` field of every kind (null, non-list,
bad entry, one bad entry among good ones) parses successfully and passes
through unchanged.
- **`skill_refs`:** the cases the parser used to reject, including
`null`, now raise `ValueError` naming the field. One bad entry rejects
the whole field. The error does not echo the rejected key.
- **Core calls:** `inspect_config` and `extract_variation` succeed on a
variation with malformed `skills`.

## Open question

`skill_refs(None)` still returns `[]`, as documented. That is the case
where a failed `inspect_config` feeds an empty list to a pruning
`write_skills`. The README quickstart guards against it, but raising
would remove the trap for every caller. That is a contract change, so it
is not in this PR.

## Test plan

- [x] `make lint`, `make format-check`, `make typecheck`
- [x] `uv run pytest`: 2209 passed, 11 skipped

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Agent Skills validation moves off the core config parser** so
experimental `skills` data cannot break `config().invoke()`,
`inspect_config`, or `extract_variation` when apps never use skills
(TESTING.md §0.3).
> 
> `parse_ai_config` now **passes `skills` through unchanged**;
`_parse_skills` was removed from the parse path and exposed as
`skills_field_rejection_reason` for reference discovery only.
**`skill_refs` validates the field** and raises `ValueError` when
`skills` is present but malformed (including `null` or one bad entry in
a list), instead of dropping bad entries with warnings—avoiding a
shortened list that would authorize `write_skills(..., prune=True)` to
delete on-disk skills the config still references.
> 
> Docs (`README.md`, `agents.md`, `AiConfigRep` docstring) and tests
were updated: parser tests expect malformed `skills` to parse
successfully; `skill_refs` and lifecycle tests cover the new raise
behavior and that core calls succeed with invalid `skills`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
0f303f4. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@XieX
XieX merged commit cbb2c30 into xie/agent-skills Oct 7, 2026
@XieX
XieX deleted the xie/skills-experimental-entry-point branch October 7, 2026 17:01
XieX added a commit to launchdarkly/js-ai-sdk that referenced this pull request Oct 7, 2026
…114)

Refactors Agent Skills to ship as an **experimental** feature. Skills
features are exported **only** from
`@launchdarkly/ai-server/experimental` and are absent from the package
root.

Additionally, the skill store is now configured via a
`setSkillStore(store)` setter rather than injected as an option to
`initClient`. Passing the removed `skillStore` option to `initClient`
(from JavaScript, or past the type) logs a generic warning, `Ignoring
unrecognized initClient option(s): skillStore`. The same warning covers
any key `initClient` does not read and does not name the experimental
feature or its setter, matching the Python SDK.

Other changes:
- `AiConfigRep.skills` was `SkillReference[]`; it is now typed
structurally (`ReadonlyArray<{ readonly key: string; readonly version:
number }>`).
- **Core never imports experimental.** `shutdown()` clears Skills state
through a shutdown-hook registry (`src/shutdown-hooks.ts`) kept on a
`globalThis` symbol slot. `skills-core.ts` registers its cleanup when it
loads, so the root entry point imports no Skills module, and the built
`dist/index.{js,cjs}` contain no Skills code. Each hook runs in its own
try/catch: a throw is logged and fails neither the other hooks nor
`shutdown()`.
- **`@launchdarkly/ai-node/experimental`.** ai-node re-exports the root
with `export *`, so Skills left it too. It now has its own
`./experimental` subpath re-exporting
`@launchdarkly/ai-server/experimental`, so Node users who install only
ai-node can reach Skills under strict package managers (pnpm, Yarn PnP).
- **`typesVersions`.** Both packages map `experimental` to
`dist/experimental.d.ts`, so the subpath's types resolve under
`moduleResolution: node` (node10), which ignores `exports` and is still
the default for `module: commonjs`.
- **`setSkillStore` checks its argument.** A non-nullish value without
`getObject` and `allObjects` methods throws `TypeError`, matching the
Python SDK.
- **API reports.** The repo had no API Extractor setup, so this adds
one. API Extractor 7.x takes one entry point per config, so there are
two configs and two checked-in reports: `etc/ai-server.api.md` and
`etc/ai-server-experimental.api.md`. New `api:check` / `api:update`
scripts; CI runs `api:check` in the typecheck job. The reports are
generated from `tsc` declaration output rather than tsup's, whose
hash-named chunks would churn the report.
- **Docs.** README and `agents.md` use the experimental import path and
`setSkillStore`. The `agents.md` export blocks are regenerated for both
entry points, and the stage rules are written down, including that
`/experimental` is the one sub-path anyone may import (deep `dist/*`
imports stay banned). The README does not promise an "Experimental"
changelog section, since release-please doesn't produce one yet (open
question 5).

## Build note: no code splitting

Code splitting would let both entry points share one copy of each
module, but in CJS tsup rewrites the dynamic `import()` of the optional
peers into `require()`. That is the AIC-3370 webpack regression, and the
commonjs-webpack integration test caught it. Splitting stays off, so
modules both entries reach (`types.ts`, `shutdown-hooks.ts`) are copied
into each. The Skills modules are only in the experimental bundle now
that the root no longer imports them. This is safe because the skill
store, the shutdown hooks, and the client singleton live on `globalThis`
symbol slots (the A.12 design for multiple module instances). Verified:
a store set through the experimental bundle is cleared by the root
`shutdown()`.

## Tests

- The experimental entry point's runtime exports equal the documented
set exactly (an allowlist, which also covers the §3.23.7 "no reader over
`content`" rule). None of those names is reachable from the root.
- The root API report names no Skills type and no `skillStore`; the
experimental report names every Skills name.
- The `setSkillStore` behaviour above, including that the SDK `init`
runs exactly once.
- Shutdown hooks: every registered hook runs, including before a client
exists; a throwing hook is logged and fails neither the other hooks nor
client teardown; re-registering a name replaces the hook. Loading the
package root registers no Skills hook, so the root imports no Skills
module (checked by temporarily adding the import, which makes the test
fail).
- `initClient` warns about an unrecognized option, `skillStore`
included, on both overloads, and stays quiet for the documented options.
- ai-node's experimental entry point re-exports every
`@launchdarkly/ai-server/experimental` name as the same reference, and
its root has none of them.
- The package root's runtime exports equal a checked-in snapshot.
- The integration test type-checks root and experimental imports against
the packed tarballs under `node10`, `node16` and `bundler`, and asserts
every loaded declaration is the consumer's own.
- The integration test resolves `@launchdarkly/ai-server/experimental`
and `@launchdarkly/ai-node/experimental` through `require` and `import`,
asserts `getSkill` is absent from both roots, and checks both subpaths'
manifest entries and packed files.

Build, typecheck, Biome, Sherif, all workspace tests,
`test:integration`, and `api:check` pass locally. The §3.23.2 swap-race
tests skip on macOS and run only on Linux CI, as before.

## Open questions

- **`parseAiConfig` still validates `skills`.** It fails a whole config
when its `skills` array is malformed (`types.ts`), so a bad Skills field
can fail an ordinary `config().invoke()`. That conflicts with
"experimental behaviour must not break a core call".
launchdarkly/python-ai-sdk#144 has the same open question; it should be
decided once for both SDKs.
- **Land order.** This conflicts with #105 in `lifecycle.ts` and
`skills.test.ts`. Whichever lands second keeps #105's retry and shutdown
shape, calls `runShutdownHooks()` instead of `_clearState()`, and drops
`resolveClient`. `main`'s TESTING.md still requires Skills at the root,
so this lands with launchdarkly/ai-sdks-monorepo#33, not before it.

🤖 Generated with [Claude Code](https://claude.com/claude-code), updated
by @XieX


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Moves **Agent Skills** off the stable package root onto published
**`@launchdarkly/ai-server/experimental`** and
**`@launchdarkly/ai-node/experimental`** subpaths (dual ESM/CJS builds,
`typesVersions` for node10). Consumers import skills APIs from
`/experimental`; the root barrel no longer exports them.
> 
> **Configuration and core/experimental boundary:** Skill storage is
configured with **`setSkillStore(store)`** instead of `initClient({
skillStore })`. Unknown `initClient` keys (including `skillStore`) log a
generic warning. **`AiConfigRep.skills`** is **`unknown`** and
**`parseAiConfig`** no longer validates it, so malformed skills do not
fail core config paths; **`skillRefs`** validates and **throws** on bad
data (fail-closed for prune). Core clears skills state via
**`shutdown-hooks`** on `globalThis` without importing experimental
modules.
> 
> **Tooling and tests:** Adds **API Extractor** reports for root and
experimental surfaces, **`api:check`** in CI, and expands
**commonjs-webpack** integration coverage (subpath resolution, packed
artifacts, consumer `tsc` under multiple `moduleResolution` modes).
Renames skills default URI/debounce exports to **`SKILLS_DEFAULT_*`**.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f2f9f51. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

2 participants