ワンクリックで
add-interactor
Scaffold a new Strata interactor with interface, implementation, fake, and test
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Scaffold a new Strata interactor with interface, implementation, fake, and test
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Add a new screen to an existing feature module with Store, UI, factories, and tests
Add missing test cases, targeting a specific file or all changed files on the branch
Thorough pre-merge audit — dependency graph, correctness, conventions, security, test coverage, and improvement suggestions
Quick review of the current diff for bugs, logic errors, convention violations, and test gaps
Scaffold a new feature module with the appropriate layers and MESA conventions
Bump library version in VERSION file and prepare release notes
| name | add-interactor |
| description | Scaffold a new Strata interactor with interface, implementation, fake, and test |
| disable-model-invocation | true |
| argument-hint | <feature-name> <InteractorName> [--observe] |
Scaffold a new Strata interactor for an existing feature module, including the implementation, fake, and test.
Input: $ARGUMENTS
Extract:
Summary)FetchUser, ObserveLastSavedValue)StrataInteractor (one-shot async, returns StrataResult<R>)--observe → StrataSubjectInteractor (stream, exposes AsyncStream<T> via .stream)Verify the feature module exists at <AppTarget>/<FeatureName>/. If it doesn't exist, stop and ask the user whether they intended a different feature name or whether they need to create the feature first with /add-feature. Do not proceed until the feature exists.
Ask the user:
Int, String, Void)User, [Item], Int?)Read the feature module's existing files to understand:
Domain/Place at: <AppTarget>/<FeatureName>/Domain/<InteractorName>UseCase.swift
StrataInteractor (one-shot):
import Foundation
import Strata
public final class <InteractorName>UseCase: StrataInteractor<P, R> {
private let repository: <FeatureName>Repository
public init(repository: <FeatureName>Repository) {
self.repository = repository
super.init()
}
public override func doWork(params: P) async -> StrataResult<R> {
return await executeCatching(params: params) { p in
try await repository.someMethod(p)
}
}
}
StrataSubjectInteractor (observe):
import Foundation
import Strata
public class <InteractorName>UseCase: StrataSubjectInteractor<P, T> {
private let repository: <FeatureName>Repository
public init(repository: <FeatureName>Repository) {
self.repository = repository
super.init()
}
public override func createObservable(params: P) -> AsyncStream<T> {
return repository.observe()
}
}
Place in the consuming module's test directory under a Fakes/ subdirectory (e.g., <AppTarget>Tests/Fakes/Fake<InteractorName>UseCase.swift). Typically this is the app's test target.
StrataInteractor Fake:
import Foundation
import Strata
@testable import <AppTarget>
final class Fake<InteractorName>UseCase: StrataInteractor<P, R>, @unchecked Sendable {
var stubResult: StrataResult<R> = .success(/* default value */)
override func doWork(params: P) async -> StrataResult<R> {
return stubResult
}
}
StrataSubjectInteractor Fake:
import Foundation
import Strata
@testable import <AppTarget>
final class Fake<InteractorName>UseCase: StrataSubjectInteractor<P, T>, @unchecked Sendable {
var values: [T] = []
override func createObservable(params: P) -> AsyncStream<T> {
AsyncStream { continuation in
for value in values {
continuation.yield(value)
}
continuation.finish()
}
}
}
Place at: <AppTarget>Tests/<InteractorName>UseCaseTests.swift
For MESA library tests (Swift Testing):
import Foundation
import Testing
@testable import <AppTarget>
@Suite("<InteractorName>UseCase")
struct <InteractorName>UseCaseTests {
@Test("executes successfully with valid params")
func executeSuccess() async {
// Given
let useCase = <InteractorName>UseCase(repository: FakeRepository())
// When
let result = await useCase.execute(params: /* test params */)
// Then
#expect(result.getOrNull() == /* expected value */)
}
@Test("returns failure on repository error")
func executeFailure() async {
// Given
let useCase = <InteractorName>UseCase(repository: FailingFakeRepository())
// When
let result = await useCase.execute(params: /* test params */)
// Then
#expect(result.getOrNull() == nil)
}
}
For Counter app tests (XCTest):
import XCTest
@testable import Counter
final class <InteractorName>UseCaseTests: XCTestCase {
func test_execute_succeeds() async {
// Given
let useCase = <InteractorName>UseCase(repository: FakeRepository())
// When
let result = await useCase.execute(params: /* test params */)
// Then
XCTAssertEqual(result.getOrNull(), /* expected value */)
}
}
For StrataSubjectInteractor, test the stream output:
@Test("emits values from createObservable")
func basicEmission() async {
let useCase = <InteractorName>UseCase(repository: FakeRepository())
let task = Task { () -> [T] in
var collected: [T] = []
for await value in useCase.stream {
collected.append(value)
if collected.count >= 1 { break }
}
return collected
}
try? await Task.sleep(nanoseconds: 10_000_000)
useCase(/* params */)
let collected = await task.value
#expect(collected == [/* expected */])
}
All generated files MUST include the Apache 2.0 license header:
/*
* Copyright 2026 Jason Jamieson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Run cd MESA && swift test --parallel for the affected modules to verify compilation.
Report: