Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
facebook avatar

Modify Jsi Features

  • 10 installs
  • 11.2k repo stars
  • Updated August 4, 2026
  • facebook/hermes

Helps with ai & agent building tasks.

About

modify-jsi-features is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.

  • modify-jsi-features
  • AI & Agent Building
  • AI-coding skill

Modify Jsi Features by the numbers

  • 10 all-time installs (skills.sh)
  • Ranked #11,937 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/facebook/hermes --skill modify-jsi-features

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs10
repo stars11.2k
Last updatedAugust 4, 2026
Repositoryfacebook/hermes

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

Adding New JSI Functionality

When adding new functionality (methods) to the JSI Runtime interface, you must modify a specific set of files across multiple layers. This skill describes each file, the patterns to follow, and important conventions.

Architecture Overview

JSI (JavaScript Interface) is an abstraction layer that allows C++ code to interact with JavaScript runtimes. The architecture consists of:

1. JSI Core (xplat/jsi/jsi/) — The abstract interface definitions 2. Hermes Implementation (xplat/static_h/API/hermes/) — Hermes-specific implementation 3. SynthTrace (xplat/static_h/API/hermes/) — Recording and replay infrastructure for debugging

Checklist of Files to Modify

JSI Core Files

1. xplat/jsi/jsi/jsi.h — Add pure virtual method declaration to IRuntime interface AND override declaration in Runtime class 2. xplat/jsi/jsi/jsi.cpp — Add default implementation (if providing one) 3. xplat/jsi/jsi/jsi-inl.h — Add inline helper methods (if needed) 4. xplat/jsi/jsi/decorator.h — Add method overrides to RuntimeDecorator and WithRuntimeDecorator 5. xplat/jsi/jsi/test/testlib.cpp — Add tests for the JSI API

Hermes Implementation Files

6. xplat/static_h/API/hermes/hermes.cpp — Add Hermes-specific implementation in HermesRuntimeImpl 7. xplat/static_h/unittests/API/APITest.cpp — Add Hermes-specific tests

SynthTrace Files (Required for Hermes)

8. xplat/static_h/API/hermes/SynthTrace.h — Add new Record types 9. xplat/static_h/API/hermes/SynthTrace.cpp — Implement Record serialization 10. xplat/static_h/API/hermes/TracingRuntime.h — Declare tracing method overrides 11. xplat/static_h/API/hermes/TracingRuntime.cpp — Implement tracing logic 12. xplat/static_h/API/hermes/SynthTraceParser.cpp — Add parsing for new records 13. xplat/static_h/API/hermes/TraceInterpreter.cpp — Add replay logic 14. xplat/static_h/unittests/API/SynthTraceTest.cpp — Add replay tests 15. xplat/static_h/unittests/API/SynthTraceSerializationTest.cpp — Add serialization tests 16. xplat/static_h/unittests/API/SynthTraceParserTest.cpp — Add parser tests

Implementation Strategy

When adding new features (methods) to the JSI Runtime, you have two options:

Option A: Default Implementation (Recommended)

Provide a default implementation in jsi::Runtime that works via JavaScript calls. This allows all runtimes (JSC, V8, etc.) to work without modification.

// jsi.h - Add virtual method with default implementation
virtual void myNewMethod(const Object& obj, const Value& val);

// jsi.cpp - Implement default using JavaScript
void Runtime::myNewMethod(const Object& obj, const Value& val) {
  auto myFn = global()
                  .getPropertyAsObject(*this, "Object")
                  .getPropertyAsFunction(*this, "myMethod");
  myFn.call(*this, obj, val);
}

Option B: Pure Virtual (Requires All Runtime Implementations)

Make the method pure virtual, requiring all runtimes (JSCRuntime, V8Runtime, HermesRuntime) to implement it. Only use this when a JavaScript-based default is not possible.

Step-by-Step Guide

1. Add to jsi.h (JSI Core Interface)

Add the pure virtual method declaration to the IRuntime interface, and the override declaration to the Runtime class:

// In IRuntime interface (around line 580)
class JSI_EXPORT IRuntime : public ICast {
  // ... existing methods ...

  /// Brief description of what the method does.
  /// \param obj Description of the object parameter.
  /// \param val Description of the value parameter.
  /// \return Description of return value (if any).
  virtual void myNewMethod(const Object& obj, const Value& val) = 0;

  // For methods returning Value, use this pattern:
  virtual Value myNewGetter(const Object& obj) = 0;
};

// In Runtime class (around line 730) - add override declarations
class JSI_EXPORT Runtime : public IRuntime {
  // ... existing methods ...

  void myNewMethod(const Object& obj, const Value& val) override;
  Value myNewGetter(const Object& obj) override;
};

Important: The IRuntime interface contains the pure virtual declarations (= 0), while the Runtime class provides the default implementation with override keyword. This separation allows other runtimes to implement the interface differently while Runtime provides a JavaScript-based fallback.


If adding convenience methods to JSI types (like `Object`), add them too:

class JSI_EXPORT Object : public Pointer { // ... existing methods ...

/// Sets something on the object. void setMyThing(IRuntime& runtime, const Value& val) const { return runtime.myNewMethod(*this, val); }

/// Gets something from the object. inline Value getMyThing(Runtime& runtime) const; };


### 2. Add Default Implementation to `jsi.cpp`

Implement the default using JavaScript APIs when possible:

void Runtime::myNewMethod(const Object& obj, const Value& val) { // Get the JavaScript function auto myMethodFn = global() .getPropertyAsObject(this, "Object") .getPropertyAsFunction(this, "myMethod"); // Call it with the appropriate arguments myMethodFn.call(*this, obj, val); }

Value Runtime::myNewGetter(const Object& obj) { auto myGetterFn = global() .getPropertyAsObject(this, "Object") .getPropertyAsFunction(this, "myGetter"); return myGetterFn.call(*this, obj); }


### 3. Add Inline Methods to `jsi-inl.h` (If Needed)

For methods that return incomplete types at declaration time:

Value Object::getMyThing(Runtime& runtime) const { return runtime.myNewGetter(*this); }


### 4. Update `decorator.h`

Add method overrides to both `RuntimeDecorator` and `WithRuntimeDecorator`:

// In RuntimeDecorator class (around line 282) void myNewMethod(const Object& obj, const Value& val) override { plain_.myNewMethod(obj, val); }

Value myNewGetter(const Object& obj) override { return plain_.myNewGetter(obj); }

// In WithRuntimeDecorator class (around line 760) void myNewMethod(const Object& obj, const Value& val) override { Around around{with_}; RD::myNewMethod(obj, val); }

Value myNewGetter(const Object& obj) override { Around around{with_}; return RD::myNewGetter(obj); }


### 5. Add JSI Core Tests in `test/testlib.cpp`

Test the default implementation using a RuntimeDecorator:

TEST_P(JSITest, MyNewFeature) { // Use a RuntimeDecorator to test the default implementation class RD : public RuntimeDecorator<Runtime, Runtime> { public: explicit RD(Runtime& rt) : RuntimeDecorator(rt) {}

void myNewMethod(const Object& obj, const Value& val) override { return Runtime::myNewMethod(obj, val); }

Value myNewGetter(const Object& obj) override { return Runtime::myNewGetter(obj); } };

RD rd = RD(rt); Object obj(rd);

// Test the functionality obj.setMyThing(rd, Value(123)); EXPECT_EQ(obj.getMyThing(rd).getNumber(), 123); }


### 6. Add Hermes Implementation in `hermes.cpp`

Implement the optimized version using Hermes VM APIs inside `HermesRuntimeImpl`:

// In HermesRuntimeImpl class declaration (around line 673) void myNewMethod(const jsi::Object& obj, const jsi::Value& val) override; jsi::Value myNewGetter(const jsi::Object& obj) override;

// Implementation (around line 2074) void HermesRuntimeImpl::myNewMethod( const jsi::Object& obj, const jsi::Value& val) { // Validate input if needed if (!val.isObject() && !val.isNull()) { throw jsi::JSError(*this, "Invalid argument type"); }

// Use Hermes VM APIs directly auto cr = vm::JSObject::myVMMethod( vm::vmcast<vm::JSObject>(phv(obj)), runtime_, hvFromValue(val), vm::PropOpFlags().plusThrowOnError()); checkStatus(cr.getStatus()); }

jsi::Value HermesRuntimeImpl::myNewGetter(const jsi::Object& obj) { vm::CallResult<vm::PseudoHandle<vm::JSObject>> cr = vm::JSObject::myVMGetter(handle(obj), runtime_); checkStatus(cr.getStatus()); if (!*cr) { return jsi::Value::null(); } return valueFromHermesValue(cr->getHermesValue()); }


### 7. Add Hermes Tests in `APITest.cpp`

TEST_P(HermesRuntimeTest, MyNewFeature) { Object obj(*rt);

// Test normal operation Value val(rt, 123); obj.setMyThing(rt, val); EXPECT_EQ(obj.getMyThing(*rt).getNumber(), 123);

// Test edge cases obj.setMyThing(rt, Value::null()); EXPECT_TRUE(obj.getMyThing(rt).isNull());

// Test error cases EXPECT_THROW(obj.setMyThing(*rt, Value("invalid")), JSError); }


### 8. Add SynthTrace Record Types in `SynthTrace.h`

Add new record types for tracing:

// In the RECORD macro list (around line 210) RECORD(MyNewMethod) \ RECORD(MyNewGetter) \

// Define the record structs (after existing records)

/// A MyNewMethodRecord is an event where native code calls myNewMethod struct MyNewMethodRecord : public Record { static constexpr RecordType type{RecordType::MyNewMethod}; const ObjectID objID_; const TraceValue value_;

MyNewMethodRecord(TimeSinceStart time, ObjectID objID, TraceValue value) : Record(time), objID_(objID), value_(value) {}

void toJSONInternal(::hermes::JSONEmitter &json) const override;

RecordType getType() const override { return type; }

std::vector<ObjectID> uses() const override { std::vector<ObjectID> uses{objID_}; pushIfTrackedValue(value_, uses); return uses; } };

/// A MyNewGetterRecord is an event where native code calls myNewGetter struct MyNewGetterRecord : public Record { static constexpr RecordType type{RecordType::MyNewGetter}; const ObjectID objID_;

MyNewGetterRecord(TimeSinceStart time, ObjectID objID) : Record(time), objID_(objID) {}

void toJSONInternal(::hermes::JSONEmitter &json) const override;

RecordType getType() const override { return type; }

std::vector<ObjectID> uses() const override { return {objID_}; } };


### 9. Implement Record Serialization in `SynthTrace.cpp`

void SynthTrace::MyNewMethodRecord::toJSONInternal(JSONEmitter &json) const { Record::toJSONInternal(json); json.emitKeyValue("objID", objID_); json.emitKeyValue("value", encode(value_)); }

void SynthTrace::MyNewGetterRecord::toJSONInternal(JSONEmitter &json) const { Record::toJSONInternal(json); json.emitKeyValue("objID", objID_); }


### 10. Add Tracing Method Declarations in `TracingRuntime.h`

class TracingRuntime : public jsi::RuntimeDecorator<jsi::Runtime> { // ... existing methods ...

void myNewMethod(const jsi::Object& obj, const jsi::Value& val) override; jsi::Value myNewGetter(const jsi::Object& obj) override;


### 11. Implement Tracing Logic in `TracingRuntime.cpp`

void TracingRuntime::myNewMethod( const jsi::Object& obj, const jsi::Value& val) { trace_.emplace_back<SynthTrace::MyNewMethodRecord>( getTimeSinceStart(), useObjectID(obj), useTraceValue(val)); RD::myNewMethod(obj, val); }

jsi::Value TracingRuntime::myNewGetter(const jsi::Object& obj) { trace_.emplace_back<SynthTrace::MyNewGetterRecord>( getTimeSinceStart(), useObjectID(obj));

auto result = RD::myNewGetter(obj); trace_.emplace_back<SynthTrace::ReturnToNativeRecord>( getTimeSinceStart(), defTraceValue(result)); return result; }


### 12. Add Parsing in `SynthTraceParser.cpp`

case RecordType::MyNewMethod: { trace.emplace_back<SynthTrace::MyNewMethodRecord>( timeFromStart, objID->getValue(), SynthTrace::decode(propValue->c_str())); break; } case RecordType::MyNewGetter: trace.emplace_back<SynthTrace::MyNewGetterRecord>( timeFromStart, objID->getValue()); break;


### 13. Add Replay Logic in `TraceInterpreter.cpp`

case RecordType::MyNewMethod: { const auto &record = static_cast<const SynthTrace::MyNewMethodRecord &>(rec); auto obj = getJSIValueForUse(record.objID_).getObject(rt_); obj.setMyThing(rt_, traceValueToJSIValue(record.value_)); break; } case RecordType::MyNewGetter: { const auto &record = static_cast<const SynthTrace::MyNewGetterRecord &>(rec); auto obj = getJSIValueForUse(record.objID_).getObject(rt_); auto result = obj.getMyThing(rt_); retval = std::move(result); break; }


### 14. Add SynthTrace Tests

**In `SynthTraceSerializationTest.cpp`:**

TEST_F(SynthTraceSerializationTest, MyNewMethodTest) { EXPECT_EQ( R"({"type":"MyNewMethodRecord","time":0,"objID":1,"value":"null:"})", to_string(SynthTrace::MyNewMethodRecord( dummyTime, 1, SynthTrace::encodeNull()))); }

TEST_F(SynthTraceSerializationTest, MyNewGetterTest) { EXPECT_EQ( R"({"type":"MyNewGetterRecord","time":0,"objID":1})", to_string(SynthTrace::MyNewGetterRecord(dummyTime, 1))); }


**In `SynthTraceTest.cpp`:**

TEST_F(SynthTraceReplayTest, MyNewFeatureReplay) { { auto &rt = *traceRt; jsi::Object obj(rt); obj.setMyThing(rt, jsi::Value(123)); rt.global().setProperty(rt, "obj", obj);

auto result = obj.getMyThing(rt); rt.global().setProperty(rt, "result", result); } replay(); { auto &rt = *replayRt; auto obj = rt.global().getProperty(rt, "obj").getObject(rt); EXPECT_EQ(obj.getMyThing(rt).getNumber(), 123);

auto result = rt.global().getProperty(rt, "result"); EXPECT_EQ(result.getNumber(), 123); } }


**In `SynthTraceParserTest.cpp`:**

TEST_F(SynthTraceParserTest, ParseMyNewMethodRecord) { const char *src = R"( { "version": 5, "globalObjID": 258, "runtimeConfig": { "gcConfig": { "initHeapSize": 33554432, "maxHeapSize": 536870912 } }, "trace": [ { "type": "MyNewMethodRecord", "time": 1234, "objID": 1, "value": "number:123" } ] } )"; auto parseResult = parseSynthTrace(bufFromStr(src)); SynthTrace &trace = std::get<0>(parseResult);

auto record = dynamic_cast<const SynthTrace::MyNewMethodRecord &>( *trace.records().at(0)); ASSERT_EQ(record.objID_, 1); }


## Key Conventions

1. **Default Implementations**: Always provide a default implementation in
   `jsi::Runtime` when possible. This allows JSCRuntime and V8Runtime to work
   without modification.

2. **Hermes Optimization**: For Hermes, override the default with an optimized
   implementation using Hermes VM APIs directly.

3. **SynthTrace is Required**: For Hermes, you MUST add SynthTrace support.
   SynthTrace allows recording and replaying of JSI interactions, which is
   critical for debugging and testing. Without it, replays will fail.

4. **Consistent Naming**: Use consistent naming across all files. If the method
   is `setPrototypeOf` in JSI, use `SetPrototype` for the record type name.

5. **Error Handling**: Use `jsi::JSError` for JavaScript-level errors in JSI
   code. Use `checkStatus()` for Hermes VM call results.

6. **Testing**: Test at multiple levels:
   - JSI core tests verify the abstract interface and default implementation
   - Hermes API tests verify the optimized implementation
   - SynthTrace tests verify recording, serialization, parsing, and replay

## Testing

### Running JSI Tests

To run all JSI tests defined in `testlib.cpp`:

buck test xplat/jsi:jsi


This runs the JSI test suite against multiple runtime implementations (Hermes, StaticHermes, etc.).

### Running Hermes API Tests

To run the Hermes-specific API tests:

buck test xplat/static_h:HermesUnitTests


To run a specific test:

buck test xplat/static_h:HermesUnitTests -- MyTestName


## Reference Diffs

For a complete example of adding JSI functionality, refer to these diffs:

- **D66562549**: JSI core changes for `getPrototypeOf` / `setPrototypeOf`
- **D66729169**: Hermes implementation
- **D66792338**: SynthTrace support

Note: These diffs predate the `IRuntime` interface, but the patterns remain the
same.

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.