Skip to content

feat: skills worker#70

Merged
sergiofilhowz merged 1 commit intomainfrom
feat/skills-worker
May 4, 2026
Merged

feat: skills worker#70
sergiofilhowz merged 1 commit intomainfrom
feat/skills-worker

Conversation

@sergiofilhowz
Copy link
Copy Markdown
Contributor

@sergiofilhowz sergiofilhowz commented May 4, 2026

Summary by CodeRabbit

New Features

  • Added a skills worker enabling registration and management of markdown-based skills content with URI-addressable resources via the iii:// scheme
  • Added a prompts registry for slash-command templates with handler function bindings
  • Enabled change notifications for skills and prompts mutations
  • Integrated MCP bridge support for resource resolution and prompt discovery
  • Included comprehensive BDD test suite with feature specifications for validation

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

Introduces a new skills Rust worker providing state-backed registries for markdown skills and slash-command prompts. The worker exposes public APIs (skills::register, prompts::register, etc.), integrates with the state service for persistence, fans out mutations via custom trigger types, and bridges with the MCP worker for resource/prompt resolution via iii:// URIs and MCP envelopes.

Changes

Skills Registry Worker

Layer / File(s) Summary
Configuration & Data Structures
skills/Cargo.toml, skills/config.yaml, skills/src/config.rs
Workspace manifest with pinned iii-sdk 0.11.3 and test dependencies; configuration schema for state scopes and timeouts with YAML loading and defaults.
State Wrappers
skills/src/state.rs
Async helpers wrapping iii.trigger() for state::{set,get,delete,list}, normalizing backend response shapes.
Trigger Types & Fan-Out
skills/src/trigger_types.rs
Custom skills::on-change and prompts::on-change trigger types with subscriber registry and async dispatch to registered functions.
Skills Registry & URIs
skills/src/functions/skills.rs
State-backed skills registry with public CRUD (skills::{register,unregister,list}), internal MCP bridges (skills::resources-*), URI parsing/validation (iii://{skills,id,id/function}), markdown helpers (extract title/description, truncate, render index), and output normalization.
Prompts Registry & MCP
skills/src/functions/prompts.rs
State-backed prompts registry with public CRUD (prompts::{register,unregister,list}), internal MCP endpoints (prompts::{mcp-list,mcp-get}), handler invocation with output normalization (string/content/messages shapes).
Module Wiring
skills/src/functions/mod.rs, skills/src/lib.rs
Entry points for skills/prompts registration with shared config and trigger fan-out; crate-level public API.
Manifest & Build
skills/src/manifest.rs, skills/build.rs
Worker manifest generation for --manifest CLI flag with compile-time metadata; build script to propagate TARGET env var.
Binary Entry Point
skills/src/main.rs
CLI argument parsing (config, WebSocket URL, manifest mode); engine registration, trigger setup, function registration, and graceful shutdown.
Worker Configuration
skills/iii.worker.yaml
Worker metadata defining Rust binary deployment for skills worker.
Test Infrastructure
skills/tests/bdd.rs, skills/tests/common/
BDD runner with cucumber feature discovery, per-scenario world (engine connection, config, unique id, stash); shared engine connection (connect-or-skip), worker registration, and state reset helpers.
Pure Unit Tests
skills/tests/features/markdown.feature, skills/tests/steps/markdown.rs
Feature specs and step defs for URI parsing, ID/name validation, markdown extraction, string truncation, output normalization, and infra namespace guards.
Skills CRUD Tests
skills/tests/features/skills_register.feature, skills/tests/steps/skills_register.rs
Feature specs and step defs for registration validation (id format, body size), round-trip persistence with timestamp updates, idempotent unregistration, and sorted listing.
Skills Resource Tests
skills/tests/features/skills_resources.feature, skills/tests/steps/skills_resources.rs
Feature specs and step defs exercising skills::resources-list, skills::resources-templates, and URI reading (index, skill body, section rendering with markdown/JSON MIME detection).
Prompts CRUD Tests
skills/tests/features/prompts_register.feature, skills/tests/steps/prompts_register.rs
Feature specs and step defs validating prompt name/description/function_id/arguments, round-trip registration with timestamps, idempotent unregistration, sorted listing.
Prompts MCP Tests
skills/tests/features/prompts_get.feature, skills/tests/steps/prompts_get.rs
Feature specs and step defs for prompts::mcp-get output normalization (string/content/messages), error handling (unknown prompt, internal namespaces), and message shape validation.
Notification Tests
skills/tests/features/notifications.feature, skills/tests/steps/notifications.rs
Feature specs and step defs for skills::on-change and prompts::on-change fan-out with per-observer counters, event counts within time windows, and subscription lifecycle.
MCP Bridge Tests
skills/tests/features/mcp_bridge.feature, skills/tests/steps/mcp_bridge.rs
Feature specs and step defs exercising bridged MCP endpoints (resource list/read, prompt list/get) via the mcp worker integration.
Documentation
README.md, skills/README.md, worker-readme.md
Updated root README with links to worker scaffolding guides; comprehensive skills worker README covering install, run, quickstart examples, configuration, and end-to-end usage; and worker README contract template for future workers.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client
    participant Skills Worker
    participant State Service
    participant Subscriber Function

    User->>Client: Call skills::register {id, skill}
    Client->>Skills Worker: TriggerRequest
    Skills Worker->>Skills Worker: Validate id & body
    Skills Worker->>State Service: state::set(scope="skills", key=id, value=StoredSkill)
    State Service-->>Skills Worker: OK
    Skills Worker->>Skills Worker: Dispatch skills::on-change {op:"register", id}
    Skills Worker-->>Skills Worker: Trigger subscribers async
    Subscriber Function->>Skills Worker: Receives on-change event
    Subscriber Function->>Subscriber Function: Handle change notification
    Skills Worker-->>Client: {id, registered_at}
    Client-->>User: Success
Loading
sequenceDiagram
    actor User
    participant Client
    participant Skills Worker
    participant State Service
    participant URI Resolver

    User->>Client: Call skills::resources-read {uri: "iii://brain/brain::summarize"}
    Client->>Skills Worker: TriggerRequest
    Skills Worker->>Skills Worker: Parse URI to Skill(brain) + Section{skill_id,function_id}
    Skills Worker->>State Service: state::get(scope="skills", key="brain")
    State Service-->>Skills Worker: StoredSkill {body: markdown}
    Skills Worker->>Skills Worker: Guard against is_always_hidden("brain::summarize")
    Skills Worker->>URI Resolver: Trigger "brain::summarize" with {}
    URI Resolver-->>Skills Worker: {content: "summary text"}
    Skills Worker->>Skills Worker: Normalize to (text, mime) = ("summary text", "text/markdown")
    Skills Worker-->>Client: {contents: [{mimeType: "text/markdown", text: "summary text"}]}
    Client-->>User: Resource content
Loading
sequenceDiagram
    actor User
    participant Client
    participant Prompts Worker
    participant State Service
    participant Handler Function

    User->>Client: Call prompts::mcp-get {name: "email-draft", arguments: {to: "alice@example"}}
    Client->>Prompts Worker: TriggerRequest
    Prompts Worker->>State Service: state::get(scope="prompts", key="email-draft")
    State Service-->>Prompts Worker: StoredPrompt {function_id: "user-handler-1"}
    Prompts Worker->>Prompts Worker: Guard against is_always_hidden("user-handler-1")
    Prompts Worker->>Handler Function: Trigger "user-handler-1" {to: "alice@example"}
    Handler Function-->>Prompts Worker: {messages: [{role: "assistant", content: {type: "text", text: "Draft..."}}]}
    Prompts Worker->>Prompts Worker: Normalize output to MCP shape
    Prompts Worker-->>Client: {description: "...", messages: [...]}
    Client-->>User: Prompt response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ytallo

Hop hop, the skills garden grows! 🐰✨
With prompts and URIs, a registry glows,
State-backed persistence and change-fan fan-out,
No more markdown doubt—the MCP bridge shout! 📚🌉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: skills worker' directly describes the main change: implementation of a complete skills worker for the iii engine.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skills-worker

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

13-21: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the skills worker to the Modules table.

This PR introduces the skills worker but doesn't add it to the ## Modules table, leaving the registry incomplete and the worker undiscoverable from the top-level README.

📝 Proposed addition
 | [`mcp`](mcp/) | Rust | Model Context Protocol surface — stdio + HTTP JSON-RPC, exposes iii functions tagged `mcp.expose` as MCP tools. |
+| [`skills`](skills/) | Rust | Agentic content registry — skills + prompts + `iii://` resource resolver bridged to the `mcp` worker. |
 | [`proof`](proof/) | Node | AI-driven browser testing — diffs changes, generates test plans, drives Playwright. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 13 - 21, Add a new row for the skills worker to the
"## Modules" Markdown table in README.md so the module registry is complete and
discoverable: insert an entry with the display name `skills` (folder `skills/`)
and a short summary like "Node | Skills worker providing reusable action
primitives" (or your preferred concise description) into the existing table
alongside other workers such as `iii-lsp`, `image-resize`, `mcp`, etc.; ensure
the row follows the same pipe-delimited table format and style used by the other
entries so rendering and ordering remain consistent.
🧹 Nitpick comments (5)
skills/src/manifest.rs (1)

21-27: ⚡ Quick win

default_config duplicates SkillsConfig::default() — two sources of truth.

The hardcoded serde_json::json!({ "scopes": ..., "state_timeout_ms": 10_000 }) is a manual copy of SkillsConfig's defaults. If the defaults in config.rs change, manifest.rs silently diverges.

♻️ Proposed fix: derive from the canonical defaults
+use crate::config::SkillsConfig;
+
 pub fn build_manifest() -> ModuleManifest {
     ModuleManifest {
         // ...
-        default_config: serde_json::json!({
-            "scopes": {
-                "skills": "skills",
-                "prompts": "prompts"
-            },
-            "state_timeout_ms": 10_000
-        }),
+        default_config: serde_json::to_value(SkillsConfig::default())
+            .expect("SkillsConfig::default() is always serializable"),
         // ...
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/src/manifest.rs` around lines 21 - 27, The hardcoded default_config
JSON duplicates SkillsConfig::default(); replace the literal with a canonical
serialization of the config default so there is a single source of truth: call
serde_json::to_value(SkillsConfig::default()) (or equivalent) to produce the
Value for default_config in manifest.rs, and add the necessary use/import for
SkillsConfig and serde_json::to_value so the manifest now derives its defaults
from SkillsConfig::default() instead of the manual serde_json! literal.
skills/tests/bdd.rs (1)

33-37: 💤 Low value

Verify shared() is always Some when get_or_init() returns Some.

world.cfg is only populated when common::workers::shared() returns Some. If there is any initialization path where the engine connection succeeds but the shared worker state isn't set (e.g., a registration failure inside get_or_init), world.cfg silently stays at its default and scenarios that rely on it will fail with an opaque error rather than a clear "workers not initialized" message.

Consider asserting or logging when shared() returns None inside this branch:

 if let Some(iii) = common::engine::get_or_init().await {
     world.iii = Some(iii.clone());
     if let Some(shared) = common::workers::shared() {
         world.cfg = shared.cfg.clone();
+    } else {
+        tracing::warn!("engine available but shared worker state not initialised — cfg will be default");
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/tests/bdd.rs` around lines 33 - 37, When get_or_init() returns
Some(iii) ensure common::workers::shared() is also present instead of silently
leaving world.cfg default; in the branch handling the successful engine init
(the block that sets world.iii from common::engine::get_or_init()), call
common::workers::shared() and if it returns None either assert/panic or log an
explicit error and fail the test so failures surface as "workers not
initialized" rather than causing opaque downstream errors; reference the symbols
common::engine::get_or_init, common::workers::shared, world.iii and world.cfg
when making the change.
skills/tests/common/workers.rs (1)

34-53: 💤 Low value

Consider get_or_try_init to make register_all race-free.

The current get() → work → set() pattern on tokio::sync::OnceCell has a TOCTOU window: two concurrent callers can both see None, both call trigger_types::register_all and functions::register_all (double-registering every function), and then race on set() — with the loser silently returning a different Arc<Shared>. In sequential BDD execution this is harmless, but using get_or_try_init eliminates the gap cleanly:

♻️ Proposed refactor
-pub async fn register_all(iii: &Arc<III>) -> Result<Arc<Shared>> {
-    if let Some(s) = SHARED.get() {
-        return Ok(s.clone());
-    }
-
-    let cfg = Arc::new(SkillsConfig::default());
-    let registered = trigger_types::register_all(iii);
-    functions::register_all(iii, &cfg, &registered);
-
-    tokio::time::sleep(std::time::Duration::from_millis(150)).await;
-
-    let shared = Arc::new(Shared {
-        cfg,
-        triggers: Arc::new(registered),
-    });
-    let _ = SHARED.set(shared.clone());
-    Ok(shared)
-}
+pub async fn register_all(iii: &Arc<III>) -> Result<Arc<Shared>> {
+    let iii = iii.clone();
+    SHARED
+        .get_or_try_init(|| async move {
+            let cfg = Arc::new(SkillsConfig::default());
+            let registered = trigger_types::register_all(&iii);
+            functions::register_all(&iii, &cfg, &registered);
+            tokio::time::sleep(std::time::Duration::from_millis(150)).await;
+            Ok(Arc::new(Shared {
+                cfg,
+                triggers: Arc::new(registered),
+            }))
+        })
+        .await
+        .cloned()
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/tests/common/workers.rs` around lines 34 - 53, The register_all
function currently races by calling SHARED.get() then doing work and
SHARED.set(), so replace that pattern with SHARED.get_or_try_init(...) to
perform the registration atomically: move creation of cfg, call
trigger_types::register_all and functions::register_all, the tokio::time::sleep,
and construction of Arc<Shared> into the async init closure passed to
SHARED.get_or_try_init so only one initializer runs; then return the cloned Arc
from the get_or_try_init result. Update the function signature and error
propagation as needed to use the Result returned by get_or_try_init and remove
the manual SHARED.set/get usage.
skills/src/functions/prompts.rs (1)

294-294: 💤 Low value

Option comparison in sort may have unexpected behavior.

a["name"].as_str().cmp(&b["name"].as_str()) compares Option<&str> values. While this works (None < Some), if a malformed entry somehow lacks a name field, it would sort to the front rather than causing an obvious error.

Since entries are constructed from StoredPrompt with a required name: String field, this is likely safe in practice, but consider using .expect() or explicit handling for defensive coding.

♻️ More explicit sort key extraction
-    prompts.sort_by(|a, b| a["name"].as_str().cmp(&b["name"].as_str()));
+    prompts.sort_by(|a, b| {
+        let a_name = a["name"].as_str().unwrap_or("");
+        let b_name = b["name"].as_str().unwrap_or("");
+        a_name.cmp(b_name)
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/src/functions/prompts.rs` at line 294, The sort currently uses
prompts.sort_by(|a, b| a["name"].as_str().cmp(&b["name"].as_str())), which
compares Option<&str> and can silently place missing names first; change to
explicitly extract the string key (e.g., via a["name"].as_str().expect("prompt
missing name") or by mapping to a required &str and handling errors) before
calling sort_by to ensure a missing or malformed `name` panics or is handled
deterministically — update the call site where `prompts` is sorted in prompts.rs
to use the explicit extraction (expect or explicit match) so the comparator
works on &str not Option<&str>.
skills/tests/steps/notifications.rs (1)

102-126: 💤 Low value

Observer index retrieval may race under parallel execution.

The observer is pushed to the global OBSERVERS vec, then the index is retrieved as len() - 1. If scenarios run concurrently, another scenario could push an observer between these two operations, causing the stored index to point to the wrong observer.

If parallel scenario execution is intended in the future, consider returning the index directly from new_observer or register_observer instead of computing it separately.

♻️ Suggested fix to return index from new_observer
-fn new_observer(_world: &IiiSkillsWorld) -> Arc<Observer> {
+fn new_observer(_world: &IiiSkillsWorld) -> (Arc<Observer>, usize) {
     let o = Arc::new(Observer {
         counter: Arc::new(Mutex::new(0)),
     });
-    observers().lock().unwrap().push(o.clone());
-    o
+    let mut vec = observers().lock().unwrap();
+    let idx = vec.len();
+    vec.push(o.clone());
+    (o, idx)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/tests/steps/notifications.rs` around lines 102 - 126, The current
subscribe_skills and subscribe_prompts compute the observer index with
observers().lock().unwrap().len() - 1 which races if other scenarios push into
OBSERVERS; change register_observer (or new_observer) to return the created
observer's index (or return a handle containing the index) and update
subscribe_skills and subscribe_prompts to use that returned idx when storing
"notifications_obs_idx" (avoid computing len()-1), or alternatively ensure
register_observer itself locks OBSERVERS, pushes the observer and returns the
index atomically so callers don't need to call observers().lock() themselves;
update references to OBS_COUNT_SLOT and notifications_obs_idx accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@skills/README.md`:
- Around line 454-485: The example uses top-level await with iii.trigger (and
the `@iii.register_function` coroutines) which causes a SyntaxError in normal
Python scripts; wrap the async parts into an async entrypoint (e.g., create
async def main(): move the two await iii.trigger(...) calls and any async
registration logic into main) and invoke it with asyncio.run(main()) so the
register_worker/register_function coroutines and iii.trigger calls run inside an
event loop.

In `@skills/src/config.rs`:
- Around line 93-97: Rename the existing test function malformed_yaml_errors to
something like missing_config_file_errors to reflect that it exercises
load_config when std::fs::read_to_string fails, and add a new unit test that
calls load_config with a path to a temporary file containing invalid YAML to
assert that the returned Result is Err due to serde_yaml deserialization
(exercise serde_yaml::from_str path). Update references to the test name if
needed and ensure the new test writes invalid YAML to a temp file (or uses
std::fs::write to a tempfile) before calling load_config to trigger the parse
error.

In `@skills/src/functions/mod.rs`:
- Line 25: The log message in tracing::info currently hardcodes counts ("skills
registered 5 skills::* and 5 prompts::* functions"), which will become incorrect
as handlers change; update the log in skills::register (or wherever
tracing::info is called) to either remove the literal counts altogether or
compute them dynamically by counting the registered entries from
skills::register and prompts::register (or the collections they populate) and
include those computed values in the message so it always reflects the real
number of handlers.

In `@skills/src/trigger_types.rs`:
- Around line 115-127: The current calls to iii.register_trigger_type (using let
_ = ...) drop the Result and always emit tracing::info!, which hides failures;
update the registration in the function that registers all trigger types (where
register_trigger_type and RegisterTriggerType::new are used) to handle the
Result: call iii.register_trigger_type(...) for both SKILLS_ON_CHANGE and
PROMPTS_ON_CHANGE, check the returned Result, and on Err either propagate the
error (return Err from the register_all function) or at minimum log the failure
with tracing::error! including the error details and the trigger name
(SKILLS_ON_CHANGE / PROMPTS_ON_CHANGE), and only emit tracing::info! on Ok so
the success log is conditional; keep the use of SkillsTriggerHandler::new(...)
and RegisterTriggerType::new(...) but don't discard the Result.

In `@skills/tests/common/engine.rs`:
- Around line 54-63: The worker registration error is being swallowed by .ok()?
inside the ENGINE.get_or_init closure; update the closure in get_or_init so
register_all's Err is explicitly handled: call
crate::common::workers::register_all(&iii).await and on Err(e) emit a clear
skip/diagnostic message (same style as try_connect_raw's skip output) and return
None, and on Ok(_) return Some(iii); this ensures ENGINE only stores Some(iii)
when registration succeeds and that registration failures are logged instead of
silently dropped.

In `@skills/tests/common/world.rs`:
- Around line 56-60: scoped_id currently truncates the combined string which can
drop the unique_id suffix; change it to build the suffix first (using
self.unique_id and the "-" separator), then truncate the base to at most (64 -
suffix.len()) characters (but not negative) and return format!("{}{}",
truncated_base, suffix) so the unique suffix is always preserved and the final
id respects skills::validate_id's 64-char limit.

In `@skills/tests/steps/prompts_get.rs`:
- Around line 104-114: The step attributes on seed_content and seed_messages are
being parsed as Cucumber Expression parameters; change their attribute strings
to regex-style patterns so the braces are treated literally (e.g. replace the
plain string in the #[given(...)] for seed_content and seed_messages with a
regex pattern that matches the literal text "a prompt handler that returns a
{content} object" and "a prompt handler that returns a {messages: [...]} object"
respectively); no changes needed to the function signatures—keep
register_handler_returning and register_prompt_pointing_at usage the same.

---

Outside diff comments:
In `@README.md`:
- Around line 13-21: Add a new row for the skills worker to the "## Modules"
Markdown table in README.md so the module registry is complete and discoverable:
insert an entry with the display name `skills` (folder `skills/`) and a short
summary like "Node | Skills worker providing reusable action primitives" (or
your preferred concise description) into the existing table alongside other
workers such as `iii-lsp`, `image-resize`, `mcp`, etc.; ensure the row follows
the same pipe-delimited table format and style used by the other entries so
rendering and ordering remain consistent.

---

Nitpick comments:
In `@skills/src/functions/prompts.rs`:
- Line 294: The sort currently uses prompts.sort_by(|a, b|
a["name"].as_str().cmp(&b["name"].as_str())), which compares Option<&str> and
can silently place missing names first; change to explicitly extract the string
key (e.g., via a["name"].as_str().expect("prompt missing name") or by mapping to
a required &str and handling errors) before calling sort_by to ensure a missing
or malformed `name` panics or is handled deterministically — update the call
site where `prompts` is sorted in prompts.rs to use the explicit extraction
(expect or explicit match) so the comparator works on &str not Option<&str>.

In `@skills/src/manifest.rs`:
- Around line 21-27: The hardcoded default_config JSON duplicates
SkillsConfig::default(); replace the literal with a canonical serialization of
the config default so there is a single source of truth: call
serde_json::to_value(SkillsConfig::default()) (or equivalent) to produce the
Value for default_config in manifest.rs, and add the necessary use/import for
SkillsConfig and serde_json::to_value so the manifest now derives its defaults
from SkillsConfig::default() instead of the manual serde_json! literal.

In `@skills/tests/bdd.rs`:
- Around line 33-37: When get_or_init() returns Some(iii) ensure
common::workers::shared() is also present instead of silently leaving world.cfg
default; in the branch handling the successful engine init (the block that sets
world.iii from common::engine::get_or_init()), call common::workers::shared()
and if it returns None either assert/panic or log an explicit error and fail the
test so failures surface as "workers not initialized" rather than causing opaque
downstream errors; reference the symbols common::engine::get_or_init,
common::workers::shared, world.iii and world.cfg when making the change.

In `@skills/tests/common/workers.rs`:
- Around line 34-53: The register_all function currently races by calling
SHARED.get() then doing work and SHARED.set(), so replace that pattern with
SHARED.get_or_try_init(...) to perform the registration atomically: move
creation of cfg, call trigger_types::register_all and functions::register_all,
the tokio::time::sleep, and construction of Arc<Shared> into the async init
closure passed to SHARED.get_or_try_init so only one initializer runs; then
return the cloned Arc from the get_or_try_init result. Update the function
signature and error propagation as needed to use the Result returned by
get_or_try_init and remove the manual SHARED.set/get usage.

In `@skills/tests/steps/notifications.rs`:
- Around line 102-126: The current subscribe_skills and subscribe_prompts
compute the observer index with observers().lock().unwrap().len() - 1 which
races if other scenarios push into OBSERVERS; change register_observer (or
new_observer) to return the created observer's index (or return a handle
containing the index) and update subscribe_skills and subscribe_prompts to use
that returned idx when storing "notifications_obs_idx" (avoid computing
len()-1), or alternatively ensure register_observer itself locks OBSERVERS,
pushes the observer and returns the index atomically so callers don't need to
call observers().lock() themselves; update references to OBS_COUNT_SLOT and
notifications_obs_idx accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2d74395a-7be5-4cfc-80bd-638a2c0b3b5a

📥 Commits

Reviewing files that changed from the base of the PR and between aba459c and 3f91b62.

⛔ Files ignored due to path filters (1)
  • skills/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (36)
  • README.md
  • skills/Cargo.toml
  • skills/README.md
  • skills/build.rs
  • skills/config.yaml
  • skills/iii.worker.yaml
  • skills/src/config.rs
  • skills/src/functions/mod.rs
  • skills/src/functions/prompts.rs
  • skills/src/functions/skills.rs
  • skills/src/lib.rs
  • skills/src/main.rs
  • skills/src/manifest.rs
  • skills/src/state.rs
  • skills/src/trigger_types.rs
  • skills/tests/bdd.rs
  • skills/tests/common/engine.rs
  • skills/tests/common/mod.rs
  • skills/tests/common/workers.rs
  • skills/tests/common/world.rs
  • skills/tests/features/markdown.feature
  • skills/tests/features/mcp_bridge.feature
  • skills/tests/features/notifications.feature
  • skills/tests/features/prompts_get.feature
  • skills/tests/features/prompts_register.feature
  • skills/tests/features/skills_register.feature
  • skills/tests/features/skills_resources.feature
  • skills/tests/steps/markdown.rs
  • skills/tests/steps/mcp_bridge.rs
  • skills/tests/steps/mod.rs
  • skills/tests/steps/notifications.rs
  • skills/tests/steps/prompts_get.rs
  • skills/tests/steps/prompts_register.rs
  • skills/tests/steps/skills_register.rs
  • skills/tests/steps/skills_resources.rs
  • worker-readme.md

Comment thread skills/README.md
Comment thread skills/src/config.rs
Comment thread skills/src/functions/mod.rs
Comment thread skills/src/trigger_types.rs
Comment thread skills/tests/common/engine.rs
Comment thread skills/tests/common/world.rs
Comment thread skills/tests/steps/prompts_get.rs
@sergiofilhowz sergiofilhowz merged commit 5aad006 into main May 4, 2026
7 checks passed
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