Skip to content

Commit 43b02e7

Browse files
authored
Merge pull request #25 from j-plugins/claude/add-temporal-skills-xyHuM
claude/add-temporal-skills-xyHuM
2 parents e5b266f + dc44e23 commit 43b02e7

51 files changed

Lines changed: 3232 additions & 3 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Claude Code Skills for temporal-plugin
2+
3+
Invoke any skill in Claude Code with `/<skill-name>`.
4+
5+
## Plugin-development skills (this repo's stack)
6+
7+
| Skill | Purpose |
8+
|---|---|
9+
| `/intellij-plugin-dev` | General IntelliJ Platform plugin-dev rules (sourced from the official JetBrains SDK docs). Entry point with topic files: `structure.md`, `extensions.md`, `services.md`, `threading.md`, `psi.md`, `indexes.md`, `inspections.md`, `actions-ui.md`, `gradle.md`, `kotlin.md`. |
10+
| `/kotlin-dev` | Project-specific Kotlin conventions (common vs. languages packages, EP pattern, PHP mixins, naming, performance). |
11+
| `/kotlin-test` | Writing JUnit 4 + IntelliJ Platform Test Framework tests for inspections, indexes, EP impls, and pure logic. |
12+
13+
**How they layer:** `/intellij-plugin-dev` is the foundation (general platform
14+
knowledge). `/kotlin-dev` adds this repo's conventions on top. `/kotlin-test`
15+
covers tests for everything either produces.
16+
17+
## Temporal SDK scaffolding skills
18+
19+
These generate **official Temporal PHP SDK** patterns the plugin already
20+
recognises via `TemporalClasses.kt`, the file-based indexes, and the PHP
21+
inspections.
22+
23+
| Skill | Purpose |
24+
|---|---|
25+
| `/temporal-workflow` | Create a `#[WorkflowInterface]` + implementation pair |
26+
| `/temporal-activity` | Create an `#[ActivityInterface]` + implementation pair |
27+
| `/temporal-signal` | Add a `#[SignalMethod]` handler to a workflow |
28+
| `/temporal-query` | Add a `#[QueryMethod]` handler to a workflow |
29+
| `/temporal-update` | Add an `#[UpdateMethod]` (+ optional validator) to a workflow |
30+
| `/temporal-child-workflow` | Invoke a child workflow with `ChildWorkflowOptions` |
31+
| `/temporal-saga` | Generate a Saga / compensation scaffold |
32+
| `/temporal-schedule` | Create a Temporal Schedule (cron/interval) via `ScheduleClient` |
33+
| `/temporal-worker` | Generate `worker.php` + `.rr.yaml` worker bootstrap |
34+
| `/temporal-starter` | Generate a client script that starts a workflow |
35+
| `/temporal-test` | Scaffold PHPUnit tests using `WorkflowEnvironment` / `ActivityMocker` |
36+
37+
Each skill lives under `.claude/skills/<name>/SKILL.md` and contains the full
38+
prompt, the code template, and the conventions Claude must follow.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
name: intellij-plugin-dev
3+
description: Develop IntelliJ Platform plugins (IDEA / PhpStorm / etc.) following the official JetBrains SDK guidelines. Use when the user asks to add an extension point, service, action, listener, inspection, index, tool window, notification, run configuration, or any other platform-level plugin feature. This is the general platform skill — for this project's specific conventions also consult /kotlin-dev.
4+
---
5+
6+
# IntelliJ Platform Plugin Developer
7+
8+
Sourced from the official JetBrains SDK docs
9+
(<https://plugins.jetbrains.com/docs/intellij/>). Use this skill as the
10+
foundation; layer `/kotlin-dev` on top for this repo's conventions and
11+
`/kotlin-test` for tests.
12+
13+
## Decision tree — what kind of feature are you adding?
14+
15+
| I want to... | Open |
16+
|---|---|
17+
| Set up / modify `plugin.xml`, module structure, resources | `structure.md` |
18+
| Add or consume an Extension Point | `extensions.md` |
19+
| Expose shared state / long-lived logic | `services.md` |
20+
| Run on EDT / background; read/write PSI safely | `threading.md` |
21+
| Walk / analyze source code | `psi.md` |
22+
| Look up symbols fast across the project | `indexes.md` |
23+
| Surface code warnings + quick fixes | `inspections.md` |
24+
| Add menu items, tool windows, notifications, listeners | `actions-ui.md` |
25+
| Change Gradle / build / runIde / verifyPlugin | `gradle.md` |
26+
| Kotlin-specific pitfalls and idioms | `kotlin.md` |
27+
28+
Each file is short and self-contained — load only the one(s) you need.
29+
30+
## Ten non-negotiable platform rules
31+
32+
1. **Never block the EDT.** Long work → `Task.Backgroundable`, coroutines, or
33+
`NonBlockingReadAction`.
34+
2. **Mutate PSI / VFS inside a `WriteAction` on the EDT only.** Reads need a
35+
`ReadAction` from background threads.
36+
3. **Get services on-demand; never cache them in fields**
37+
`service<MyService>()` / `project.service<MyService>()`.
38+
4. **Register listeners declaratively** in `plugin.xml`
39+
(`<applicationListeners>`, `<projectListeners>`) — they're created lazily.
40+
5. **Iterate EPs lazily**`EP.lazyDumbAwareExtensions(project)`, not
41+
`extensionList`, unless you truly need all extensions eagerly.
42+
6. **Mark long-computation extensions as `DumbAware`** when they don't need
43+
indexes; otherwise gate them behind `DumbService.isDumb(project)`.
44+
7. **Cache PSI-derived values** with `CachedValuesManager` + a
45+
`PsiModificationTracker`. Don't hand-roll memoization.
46+
8. **Every user-visible string** comes from a `<resource-bundle>` — no inline
47+
English.
48+
9. **Bump `getVersion()`** on any index whose key/value layout changes —
49+
otherwise stale data persists across upgrades.
50+
10. **Inspections ship with an HTML description** at
51+
`inspectionDescriptions/<shortName>.html`, or they won't pass verification.
52+
53+
## Typical workflow for a new feature
54+
55+
1. Decide scope — application / project / module / per-PSI-element.
56+
2. Pick the right mechanism (service vs. EP vs. listener vs. action).
57+
3. Declare it in `plugin.xml` (or the right `config-file` for an optional
58+
dependency).
59+
4. Implement in Kotlin. Keep classes `final` unless you *need* extensibility.
60+
5. Wire i18n via `TemporalBundle.message("key")`.
61+
6. Run `./gradlew build runIde verifyPlugin` — all three must pass.
62+
7. Add tests per `/kotlin-test`.
63+
64+
## Reference links (official)
65+
66+
- Welcome: <https://plugins.jetbrains.com/docs/intellij/welcome.html>
67+
- Plugin structure: <https://plugins.jetbrains.com/docs/intellij/plugin-structure.html>
68+
- Threading: <https://plugins.jetbrains.com/docs/intellij/threading-model.html>
69+
- PSI: <https://plugins.jetbrains.com/docs/intellij/psi.html>
70+
- Indexes: <https://plugins.jetbrains.com/docs/intellij/indexing-and-psi-stubs.html>
71+
- Kotlin for plugins: <https://plugins.jetbrains.com/docs/intellij/using-kotlin.html>
72+
- IntelliJ Platform Gradle Plugin (v2): <https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin.html>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Actions, listeners, notifications, tool windows
2+
3+
## Actions
4+
5+
Official docs: <https://plugins.jetbrains.com/docs/intellij/basic-action-system.html>
6+
7+
```kotlin
8+
class RefreshPageAction : AnAction() {
9+
override fun getActionUpdateThread() = ActionUpdateThread.BGT
10+
11+
override fun update(e: AnActionEvent) {
12+
e.presentation.isEnabled = e.project != null
13+
}
14+
15+
override fun actionPerformed(e: AnActionEvent) {
16+
val project = e.project ?: return
17+
project.service<TemporalWebUIPanel>().reload()
18+
}
19+
}
20+
```
21+
22+
Registration:
23+
24+
```xml
25+
<actions>
26+
<action id="Temporal.RefreshPage"
27+
class="com.example.my.RefreshPageAction"
28+
text="Refresh" description="Refresh the Temporal UI"
29+
icon="AllIcons.Actions.Refresh">
30+
<add-to-group group-id="ToolWindowContextMenu" anchor="last"/>
31+
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt R"/>
32+
</action>
33+
</actions>
34+
```
35+
36+
Rules:
37+
- **Always override `getActionUpdateThread()`** — pick `BGT` when `update()`
38+
reads project/PSI state; `EDT` only for pure UI checks. Using BGT avoids
39+
freezes.
40+
- `update()` must be fast. Offload work to `actionPerformed`.
41+
- Define a `groupId` via `<group id="..." class="com.intellij.openapi.actionSystem.DefaultActionGroup">`
42+
if you need a submenu; add actions to it with `<add-to-group>`.
43+
44+
## Listeners & MessageBus
45+
46+
Official docs: <https://plugins.jetbrains.com/docs/intellij/plugin-listeners.html>
47+
48+
Declarative (preferred — lazy, no startup cost):
49+
50+
```xml
51+
<applicationListeners>
52+
<listener class="com.example.my.MyAppListener"
53+
topic="com.intellij.openapi.application.ApplicationActivationListener"/>
54+
</applicationListeners>
55+
56+
<projectListeners>
57+
<listener class="com.example.my.MyProjectListener"
58+
topic="com.intellij.openapi.vfs.newvfs.BulkFileListener"/>
59+
</projectListeners>
60+
```
61+
62+
Programmatic:
63+
64+
```kotlin
65+
project.messageBus.connect(parentDisposable)
66+
.subscribe(BulkFileListener.TOPIC, object : BulkFileListener {
67+
override fun after(events: MutableList<out VFileEvent>) { /* ... */ }
68+
})
69+
```
70+
71+
Custom topics:
72+
73+
```kotlin
74+
interface ServerListener {
75+
fun onServerStarted(event: ServerStarted)
76+
companion object {
77+
@Topic.ProjectLevel
78+
val TOPIC: Topic<ServerListener> = Topic.create("Temporal server", ServerListener::class.java)
79+
}
80+
}
81+
```
82+
83+
Rules:
84+
- Listener implementations must be **stateless**; persist state in services.
85+
- Always `connect(parentDisposable)` programmatically — orphan connections
86+
leak.
87+
88+
## Notifications
89+
90+
```xml
91+
<extensions defaultExtensionNs="com.intellij">
92+
<notificationGroup id="Temporal"
93+
displayType="BALLOON"
94+
isLogByDefault="true"
95+
bundle="messages.TemporalBundle"
96+
key="notification.group"/>
97+
</extensions>
98+
```
99+
100+
`displayType`: `BALLOON` (transient popup), `STICKY_BALLOON` (stays until
101+
dismissed), `TOOL_WINDOW` (shown inside the target tool window, needs
102+
`toolWindowId=`), `NONE` (event log only).
103+
104+
Emit:
105+
106+
```kotlin
107+
NotificationGroupManager.getInstance()
108+
.getNotificationGroup("Temporal")
109+
.createNotification(
110+
TemporalBundle.message("notification.server.started.title"),
111+
TemporalBundle.message("notification.server.started.content", port),
112+
NotificationType.INFORMATION,
113+
)
114+
.notify(project)
115+
```
116+
117+
## Tool windows
118+
119+
Official docs: <https://plugins.jetbrains.com/docs/intellij/tool-windows.html>
120+
121+
```xml
122+
<toolWindow id="Temporal"
123+
icon="/icons/temporal/icon.svg"
124+
anchor="right"
125+
factoryClass="com.example.my.TemporalWindowFactory"
126+
doNotActivateOnStart="true"
127+
secondary="false"/>
128+
```
129+
130+
```kotlin
131+
class TemporalWindowFactory : ToolWindowFactory, DumbAware {
132+
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
133+
val panel = TemporalWebUIPanel(project)
134+
val content = toolWindow.contentManager.factory.createContent(panel, "Web UI", false)
135+
toolWindow.contentManager.addContent(content)
136+
}
137+
138+
override fun isApplicable(project: Project) = true // gate visibility
139+
}
140+
```
141+
142+
Retrieve at runtime:
143+
144+
```kotlin
145+
ToolWindowManager.getInstance(project).getToolWindow("Temporal")?.show()
146+
```
147+
148+
Rules:
149+
- Implement `DumbAware` unless the tool window genuinely needs indexes.
150+
- `createToolWindowContent` runs on EDT — keep it cheap; defer heavy UI wiring
151+
to a background task or to a button click inside the panel.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Extension points & extensions
2+
3+
Official docs: <https://plugins.jetbrains.com/docs/intellij/plugin-extensions.html>
4+
5+
## Declaring your own EP
6+
7+
```xml
8+
<extensionPoints>
9+
<extensionPoint name="activity" dynamic="true"
10+
interface="com.example.my.extensionPoints.Activity"/>
11+
12+
<!-- bean-class variant (XML-configurable) -->
13+
<extensionPoint name="frameworkSupport" beanClass="com.intellij.util.xmlb.BaseKeyedLazyInstance">
14+
<with attribute="implementationClass" implements="com.example.my.FrameworkSupport"/>
15+
</extensionPoint>
16+
</extensionPoints>
17+
```
18+
19+
Guidelines:
20+
21+
- `name` must be unique **within your plugin**; the full ID becomes
22+
`<pluginId>.<name>`.
23+
- `dynamic="true"` allows the EP to be loaded/unloaded at runtime (required
24+
for v2 plugins that support hot reload).
25+
- Choose `interface` for code-driven extensions, `beanClass` for XML-configured
26+
extensions with attributes.
27+
28+
## Consuming an EP in code
29+
30+
```kotlin
31+
interface Activity {
32+
fun getActivities(project: Project): List<ActivityModel>
33+
34+
companion object {
35+
val EP = ExtensionPointName.create<Activity>("com.example.my.activity")
36+
37+
fun all(project: Project): List<ActivityModel> =
38+
EP.lazyDumbAwareExtensions(project)
39+
.flatMap { it.getActivities(project) }
40+
.toList()
41+
}
42+
}
43+
```
44+
45+
- Prefer `lazyDumbAwareExtensions(project)` — returns a lazy `Sequence` that
46+
skips non-`DumbAware` extensions while indexing is in progress.
47+
- `extensionList` / `extensions` eagerly instantiate **all** extensions — use
48+
only if you actually need every one, and only from background threads.
49+
- Extensions throwing `ExtensionNotApplicableException` in their constructor
50+
are silently skipped. Use this to opt out based on runtime state.
51+
52+
## Contributing to someone's EP
53+
54+
```xml
55+
<extensions defaultExtensionNs="com.intellij">
56+
<localInspection implementationClass="com.example.my.MyInspection" .../>
57+
<applicationService serviceImplementation="com.example.my.MyService"/>
58+
</extensions>
59+
60+
<!-- Contributing to YOUR OWN EP -->
61+
<extensions defaultExtensionNs="com.example.my">
62+
<activity implementation="com.example.my.PhpActivity"/>
63+
</extensions>
64+
```
65+
66+
`defaultExtensionNs` is the plugin ID that **owns** the EP, not the caller.
67+
68+
## Rules for extension implementations
69+
70+
- **Stateless.** Per-instance mutable state breaks with dynamic reloading.
71+
Store runtime state in a service instead.
72+
- **Cheap constructors.** No I/O, no heavy initialization. Defer work to the
73+
first real call.
74+
- **No `object` singletons** in Kotlin for extension classes — the platform
75+
instantiates them itself.
76+
- **Thread-safety.** Extensions are called from many threads; assume concurrent
77+
invocation.
78+
79+
## Optional-dependency pattern
80+
81+
1. `<depends optional="true" config-file="x.xml">other.plugin</depends>`.
82+
2. Put all `<extensions>` that rely on `other.plugin`'s APIs inside `x.xml`.
83+
3. If `other.plugin` is absent, `x.xml` is simply not loaded — the base plugin
84+
keeps working with reduced features.

0 commit comments

Comments
 (0)