@amsterdamdatalabs/enact-extensions 0.1.5 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +15 -1
- package/dist/install.js.map +1 -1
- package/dist/internal/agents.d.ts +29 -0
- package/dist/internal/agents.d.ts.map +1 -0
- package/dist/internal/agents.js +83 -0
- package/dist/internal/agents.js.map +1 -0
- package/extensions/enact-context/hooks/hooks.json +20 -0
- package/extensions/enact-core/.agents/plugin.json +39 -0
- package/extensions/enact-core/hooks/hooks.json +14 -0
- package/extensions/enact-factory/.agents/plugin.json +9 -3
- package/extensions/enact-factory/agents/architect.toml +30 -0
- package/extensions/enact-factory/agents/code-reviewer.toml +29 -0
- package/extensions/enact-factory/agents/critic.toml +35 -0
- package/extensions/enact-factory/agents/executor.toml +23 -0
- package/extensions/enact-factory/agents/explore.toml +22 -0
- package/extensions/enact-factory/agents/planner.toml +23 -0
- package/extensions/enact-factory/agents/verifier.toml +29 -0
- package/extensions/enact-factory/skills/ai-slop-cleaner/SKILL.md +52 -0
- package/extensions/enact-factory/skills/azdo-ci-strategy/SKILL.md +262 -0
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/build-failures.md +60 -0
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/cli-reference.md +87 -0
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/policies-and-pipelines.md +132 -0
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/troubleshooting.md +53 -0
- package/extensions/enact-factory/skills/deep-interview/SKILL.md +72 -0
- package/extensions/enact-factory/skills/drive-loop/SKILL.md +259 -0
- package/extensions/enact-factory/skills/drive-loop/references/contract-schema.md +107 -0
- package/extensions/enact-factory/skills/hyperplan/SKILL.md +51 -0
- package/extensions/enact-factory/skills/looplan/SKILL.md +103 -0
- package/extensions/enact-factory/skills/plan/SKILL.md +71 -0
- package/extensions/enact-factory/skills/remove-deadcode/SKILL.md +41 -0
- package/extensions/enact-factory/skills/research/SKILL.md +73 -0
- package/extensions/enact-factory/skills/review/SKILL.md +48 -0
- package/extensions/enact-factory/skills/security-research/SKILL.md +54 -0
- package/extensions/enact-factory/skills/tdd/SKILL.md +56 -0
- package/extensions/enact-factory/skills/trace/SKILL.md +37 -0
- package/extensions/enact-factory/skills/ultraqa/SKILL.md +79 -0
- package/extensions/enact-factory/skills/work-with-workitem/SKILL.md +51 -0
- package/extensions/enact-factory/skills/workitem-triage/SKILL.md +15 -0
- package/extensions/enact-loop/.agents/plugin.json +46 -0
- package/extensions/enact-loop/.mcp.json +1 -0
- package/extensions/enact-loop/hooks/hooks.json +27 -0
- package/extensions/enact-loop/skills/enact-loop/SKILL.md +327 -0
- package/extensions/enact-operator/.agents/plugin.json +0 -1
- package/extensions/enact-operator/hooks/hooks.json +0 -35
- package/extensions/enact-wiki/skills/wiki/SKILL.md +42 -0
- package/extensions/plugin-dev/.agents/plugin.json +4 -6
- package/extensions/plugin-dev/agents/plugin-validator.md +1 -1
- package/extensions/plugin-dev/skills/agent-development/SKILL.md +7 -7
- package/extensions/plugin-dev/{commands/create-plugin.md → skills/create-plugin/SKILL.md} +44 -37
- package/extensions/plugin-dev/skills/plugin-dev-guide/SKILL.md +13 -14
- package/extensions/plugin-dev/skills/skill-development/SKILL.md +0 -2
- package/extensions/plugin-dev/{commands/start.md → skills/start/SKILL.md} +7 -6
- package/package.json +11 -6
- package/scripts/check-hooks.mjs +174 -0
- package/scripts/check-principles.mjs +101 -0
- package/scripts/enact-extensions.mjs +87 -3
- package/scripts/lib/run-validate.mjs +36 -2
- package/scripts/lib/ups-router.mjs +432 -0
- package/spec/enact.json +4 -0
- package/spec/enact.md +5 -2
- package/extensions/cmux/.agents/plugin.json +0 -37
- package/extensions/cmux/skills/cmux/SKILL.md +0 -82
- package/extensions/cmux/skills/cmux/agents/openai.yaml +0 -4
- package/extensions/cmux/skills/cmux/references/handles-and-identify.md +0 -35
- package/extensions/cmux/skills/cmux/references/panes-surfaces.md +0 -37
- package/extensions/cmux/skills/cmux/references/trigger-flash-and-health.md +0 -23
- package/extensions/cmux/skills/cmux/references/windows-workspaces.md +0 -31
- package/extensions/cmux/skills/cmux-vm-monitor/SKILL.md +0 -122
- package/extensions/cmux/skills/cmux-vm-monitor/agents/openai.yaml +0 -4
- package/extensions/cmux/skills/cmux-vm-monitor/references/cmux-commands.md +0 -66
- package/extensions/cmux/skills/cmux-vm-monitor/scripts/codex_vm_monitor.sh +0 -45
- package/extensions/cmux/skills/cmux-workspace/SKILL.md +0 -93
- package/extensions/devops/.agents/plugin.json +0 -36
- package/extensions/devops/skills/azure-devops-cli/SKILL.md +0 -431
- package/extensions/devops/skills/azure-devops-cli/agents/openai.yaml +0 -4
- package/extensions/devops/skills/ci-pipeline-strategy/SKILL.md +0 -217
- package/extensions/devops/skills/ci-pipeline-strategy/agents/openai.yaml +0 -4
- package/extensions/enact-factory/hooks/user-prompt-submit.mjs +0 -67
- package/extensions/enact-operator/commands/doctor.md +0 -39
- package/extensions/enact-operator/commands/setup.md +0 -51
- package/extensions/plugin-dev/.mcp.json +0 -3
- package/extensions/plugin-dev/commands/_archive/create-marketplace.md +0 -427
- package/extensions/plugin-dev/commands/_archive/plugin-dev-guide.md +0 -12
- package/extensions/plugin-dev/hooks/hooks.json +0 -3
- package/extensions/plugin-dev/skills/command-development/SKILL.md +0 -763
- package/extensions/plugin-dev/skills/command-development/examples/plugin-commands.md +0 -612
- package/extensions/plugin-dev/skills/command-development/examples/simple-commands.md +0 -527
- package/extensions/plugin-dev/skills/command-development/references/advanced-workflows.md +0 -762
- package/extensions/plugin-dev/skills/command-development/references/documentation-patterns.md +0 -769
- package/extensions/plugin-dev/skills/command-development/references/frontmatter-reference.md +0 -508
- package/extensions/plugin-dev/skills/command-development/references/interactive-commands.md +0 -966
- package/extensions/plugin-dev/skills/command-development/references/marketplace-considerations.md +0 -943
- package/extensions/plugin-dev/skills/command-development/references/plugin-features-reference.md +0 -637
- package/extensions/plugin-dev/skills/command-development/references/plugin-integration.md +0 -191
- package/extensions/plugin-dev/skills/command-development/references/skill-tool.md +0 -447
- package/extensions/plugin-dev/skills/command-development/references/testing-strategies.md +0 -723
- package/extensions/plugin-dev/skills/command-development/scripts/check-frontmatter.sh +0 -234
- package/extensions/plugin-dev/skills/command-development/scripts/validate-command.sh +0 -160
- /package/extensions/enact-operator/{skills/_variants.md → docs/skill-variants.md} +0 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
name: create-plugin
|
|
3
|
+
description: >-
|
|
4
|
+
Guided workflow to create a complete Enact plugin from concept to tested
|
|
5
|
+
implementation. Use when someone says "create a plugin", "build a plugin",
|
|
6
|
+
"/create-plugin", or describes plugin functionality they want to scaffold.
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# Plugin Creation Workflow
|
|
9
10
|
|
|
11
|
+
> **Commands are skills.** Enact plugins have no separate "commands" component.
|
|
12
|
+
> A user-initiated action (e.g. `/create-plugin`) is an *invocable skill* — a
|
|
13
|
+
> skill whose `SKILL.md` carries an invocable frontmatter and `$ARGUMENTS`.
|
|
14
|
+
> Wherever you'd reach for a "command", create a skill instead.
|
|
15
|
+
|
|
10
16
|
Guide the user through creating a complete, high-quality Claude Code plugin from initial concept to tested implementation. Follow a systematic approach: understand requirements, design components, clarify details, implement following best practices, validate, and test.
|
|
11
17
|
|
|
12
18
|
## Core Principles
|
|
@@ -55,8 +61,10 @@ Guide the user through creating a complete, high-quality Claude Code plugin from
|
|
|
55
61
|
|
|
56
62
|
1. Load plugin-structure skill to understand component types
|
|
57
63
|
2. Analyze plugin requirements and determine needed components:
|
|
58
|
-
- **Skills**:
|
|
59
|
-
|
|
64
|
+
- **Skills**: Specialized knowledge AND user-initiated actions. A skill can
|
|
65
|
+
be passive (auto-triggered by its description) or *invocable* (a slash
|
|
66
|
+
entry point like `/deploy` carrying `$ARGUMENTS`). There is no separate
|
|
67
|
+
"command" component — invocable skills replace commands.
|
|
60
68
|
- **Agents**: Autonomous tasks? (validation, generation, analysis)
|
|
61
69
|
- **Hooks**: Event-driven automation? (validation, notifications)
|
|
62
70
|
- **MCP**: External service integration? (databases, APIs)
|
|
@@ -68,13 +76,13 @@ Guide the user through creating a complete, high-quality Claude Code plugin from
|
|
|
68
76
|
- Rough triggering/usage patterns
|
|
69
77
|
4. Present component plan to user as table:
|
|
70
78
|
```
|
|
71
|
-
| Component Type
|
|
72
|
-
|
|
73
|
-
| Skills
|
|
74
|
-
|
|
|
75
|
-
| Agents
|
|
76
|
-
| Hooks
|
|
77
|
-
| MCP
|
|
79
|
+
| Component Type | Count | Purpose |
|
|
80
|
+
|--------------------|-------|---------|
|
|
81
|
+
| Skills (passive) | 2 | Hook patterns, MCP usage |
|
|
82
|
+
| Skills (invocable) | 3 | /deploy, /configure, /validate |
|
|
83
|
+
| Agents | 1 | Autonomous validation |
|
|
84
|
+
| Hooks | 0 | Not needed |
|
|
85
|
+
| MCP | 1 | Database integration |
|
|
78
86
|
```
|
|
79
87
|
5. Get user confirmation or adjustments
|
|
80
88
|
|
|
@@ -91,8 +99,8 @@ Guide the user through creating a complete, high-quality Claude Code plugin from
|
|
|
91
99
|
**Actions**:
|
|
92
100
|
|
|
93
101
|
1. For each component in the plan, identify underspecified aspects:
|
|
94
|
-
- **Skills**: What triggers them? What knowledge do they provide? How detailed?
|
|
95
|
-
- **
|
|
102
|
+
- **Skills (passive)**: What triggers them? What knowledge do they provide? How detailed?
|
|
103
|
+
- **Skills (invocable)**: What slash name? What `$ARGUMENTS`? Interactive or automated?
|
|
96
104
|
- **Agents**: When to trigger (proactive/reactive)? What tools? Output format?
|
|
97
105
|
- **Hooks**: Which events? Prompt or command based? Validation criteria?
|
|
98
106
|
- **MCP**: What server type? Authentication? Which tools?
|
|
@@ -135,12 +143,11 @@ Guide the user through creating a complete, high-quality Claude Code plugin from
|
|
|
135
143
|
3. Create directory structure using bash:
|
|
136
144
|
```bash
|
|
137
145
|
mkdir -p plugin-name/.agents
|
|
138
|
-
mkdir -p plugin-name/skills #
|
|
139
|
-
mkdir -p plugin-name/commands # if needed
|
|
146
|
+
mkdir -p plugin-name/skills # passive + invocable skills (no commands/ dir)
|
|
140
147
|
mkdir -p plugin-name/agents # if needed
|
|
141
148
|
mkdir -p plugin-name/hooks # if needed
|
|
142
149
|
```
|
|
143
|
-
4. Create **canonical** `.agents/plugin.json` (see `plugin-structure` skill and `spec/enact.json`):
|
|
150
|
+
4. Create **canonical** `.agents/plugin.json` (see `plugin-structure` skill and `spec/enact.json`). Declare only the components that exist on disk — an undeclared dir silently fails to install, and a declared-but-empty component (e.g. `"hooks"` pointing at `{ "hooks": {} }`) is a guard error:
|
|
144
151
|
```json
|
|
145
152
|
{
|
|
146
153
|
"name": "plugin-name",
|
|
@@ -148,12 +155,12 @@ Guide the user through creating a complete, high-quality Claude Code plugin from
|
|
|
148
155
|
"description": "[brief description]",
|
|
149
156
|
"targets": ["claude", "codex", "cursor"],
|
|
150
157
|
"skills": "./skills/",
|
|
151
|
-
"commands": "./commands/",
|
|
152
158
|
"hooks": "./hooks/hooks.json",
|
|
153
159
|
"mcpServers": "./.mcp.json",
|
|
154
160
|
"author": { "name": "[author]" }
|
|
155
161
|
}
|
|
156
162
|
```
|
|
163
|
+
There is no `commands` field — that field is banned by the repo guard.
|
|
157
164
|
5. Sync and validate host manifests:
|
|
158
165
|
```bash
|
|
159
166
|
cd /path/to/plugin-name
|
|
@@ -181,8 +188,7 @@ git commit -m "feat: initial plugin structure"
|
|
|
181
188
|
|
|
182
189
|
**LOAD RELEVANT SKILLS** before implementing each component type:
|
|
183
190
|
|
|
184
|
-
- Skills: Load skill-development skill
|
|
185
|
-
- Commands: Load command-development skill
|
|
191
|
+
- Skills (passive + invocable): Load skill-development skill
|
|
186
192
|
- Agents: Load agent-development skill
|
|
187
193
|
- Hooks: Load hook-development skill
|
|
188
194
|
- MCP: Load mcp-integration skill
|
|
@@ -207,16 +213,17 @@ git commit -m "feat: initial plugin structure"
|
|
|
207
213
|
- Create utility scripts if needed
|
|
208
214
|
3. Use skill-reviewer agent to validate each skill
|
|
209
215
|
|
|
210
|
-
### For
|
|
216
|
+
### For Invocable Skills (slash entry points — formerly "commands")
|
|
211
217
|
|
|
212
|
-
1. Load
|
|
213
|
-
2. For each
|
|
214
|
-
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
- Write instructions FOR Claude (not TO user)
|
|
218
|
-
- Provide usage examples
|
|
219
|
-
-
|
|
218
|
+
1. Load skill-development skill using Skill tool
|
|
219
|
+
2. For each invocable skill, create a normal `skills/<name>/SKILL.md` and:
|
|
220
|
+
- Give it invocable frontmatter (slash name, `argument-hint`, optional
|
|
221
|
+
`allowed-tools` kept minimal)
|
|
222
|
+
- Consume user input via `$ARGUMENTS`
|
|
223
|
+
- Write the body as instructions FOR Claude (not TO the user)
|
|
224
|
+
- Provide usage examples; reference passive skills where useful
|
|
225
|
+
- Use `references/` and `scripts/` for progressive disclosure
|
|
226
|
+
3. Use skill-reviewer agent to validate it like any other skill
|
|
220
227
|
|
|
221
228
|
### For Agents
|
|
222
229
|
|
|
@@ -265,7 +272,7 @@ git commit -m "feat: initial plugin structure"
|
|
|
265
272
|
1. Load plugin-settings skill using Skill tool
|
|
266
273
|
2. Create settings template in README
|
|
267
274
|
3. Create example .claude/plugin-name.local.md file (as documentation)
|
|
268
|
-
4. Implement settings reading in hooks/
|
|
275
|
+
4. Implement settings reading in hooks/skills as needed
|
|
269
276
|
5. Add to .gitignore: `.claude/*.local.md`
|
|
270
277
|
|
|
271
278
|
**Progress tracking**: Update tasks as each component is completed
|
|
@@ -329,7 +336,7 @@ git commit -m "feat: initial plugin structure"
|
|
|
329
336
|
|
|
330
337
|
2. **Verification checklist** for user to perform:
|
|
331
338
|
- [ ] Skills load when triggered (ask questions with trigger phrases)
|
|
332
|
-
- [ ]
|
|
339
|
+
- [ ] Invocable skills appear in `/help` and execute correctly
|
|
333
340
|
- [ ] Agents trigger on appropriate scenarios
|
|
334
341
|
- [ ] Hooks activate on events (if applicable)
|
|
335
342
|
- [ ] MCP servers connect (if applicable)
|
|
@@ -337,7 +344,7 @@ git commit -m "feat: initial plugin structure"
|
|
|
337
344
|
|
|
338
345
|
3. **Testing recommendations**:
|
|
339
346
|
- For skills: Ask questions using trigger phrases from descriptions
|
|
340
|
-
- For
|
|
347
|
+
- For invocable skills: Run `/plugin-name:skill-name` with various arguments
|
|
341
348
|
- For agents: Create scenarios matching agent examples
|
|
342
349
|
- For hooks: Use `claude --debug` to see hook execution
|
|
343
350
|
- For MCP: Use `/mcp` to verify servers and tools
|
|
@@ -391,7 +398,7 @@ git commit -m "feat: initial plugin structure"
|
|
|
391
398
|
- Update marketplace metadata.version (bump patch version)
|
|
392
399
|
|
|
393
400
|
- If create new marketplace:
|
|
394
|
-
- Suggest using `/plugin-dev:create-marketplace`
|
|
401
|
+
- Suggest using the `/plugin-dev:create-marketplace` skill
|
|
395
402
|
- Or create minimal marketplace.json with this plugin as first entry
|
|
396
403
|
- Validate marketplace after update using plugin-validator agent
|
|
397
404
|
|
|
@@ -399,7 +406,7 @@ git commit -m "feat: initial plugin structure"
|
|
|
399
406
|
- Mark all tasks complete
|
|
400
407
|
- List what was created:
|
|
401
408
|
- Plugin name and purpose
|
|
402
|
-
- Components created (X skills
|
|
409
|
+
- Components created (X skills incl. invocable, Y agents, etc.)
|
|
403
410
|
- Key files and their purposes
|
|
404
411
|
- Total file count and structure
|
|
405
412
|
- If added to marketplace:
|
|
@@ -448,7 +455,7 @@ git commit -m "feat: initial plugin structure"
|
|
|
448
455
|
### Skills to Load by Phase
|
|
449
456
|
|
|
450
457
|
- **Phase 2**: plugin-structure
|
|
451
|
-
- **Phase 5**: skill-development,
|
|
458
|
+
- **Phase 5**: skill-development, agent-development, hook-development, mcp-integration, lsp-integration, plugin-settings (as needed)
|
|
452
459
|
- **Phase 6**: (agents will use skills automatically)
|
|
453
460
|
- **Phase 8**: marketplace-structure (if publishing to marketplace)
|
|
454
461
|
|
|
@@ -480,7 +487,7 @@ Every component must meet these standards:
|
|
|
480
487
|
### Phase 2: Component Planning
|
|
481
488
|
|
|
482
489
|
- Skills: 1 (migration best practices)
|
|
483
|
-
-
|
|
490
|
+
- Invocable skills: 3 (create-migration, run-migrations, rollback)
|
|
484
491
|
- Agents: 1 (migration-validator)
|
|
485
492
|
- MCP: 1 (database connection)
|
|
486
493
|
|
|
@@ -22,8 +22,7 @@ See `ORGANIZATION.md` in the enact-extensions repo root.
|
|
|
22
22
|
| Skill | Purpose |
|
|
23
23
|
|-------|---------|
|
|
24
24
|
| **plugin-structure** | Multi-platform layout and manifests |
|
|
25
|
-
| **skill-development** | `skills/*/SKILL.md` |
|
|
26
|
-
| **command-development** | Slash commands in `commands/` |
|
|
25
|
+
| **skill-development** | `skills/*/SKILL.md` — passive and invocable (slash) skills |
|
|
27
26
|
| **hook-development** | `hooks/hooks.json` |
|
|
28
27
|
| **mcp-integration** | `.mcp.json` |
|
|
29
28
|
| **agent-development** | `agents/*.md` (Claude/Cursor) |
|
|
@@ -46,14 +45,15 @@ Use when the user needs to:
|
|
|
46
45
|
- Learn about component auto-discovery
|
|
47
46
|
- Use ${CLAUDE_PLUGIN_ROOT} for portable paths
|
|
48
47
|
|
|
49
|
-
### Adding User-Facing
|
|
48
|
+
### Adding User-Facing Slash Entry Points (invocable skills)
|
|
50
49
|
|
|
51
|
-
**Skill: `
|
|
50
|
+
**Skill: `skill-development`**
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
There is no separate "command" component — a user-initiated `/name` action is an
|
|
53
|
+
*invocable skill*. Use `skill-development` when the user needs to:
|
|
54
54
|
|
|
55
|
-
- Create
|
|
56
|
-
- Configure
|
|
55
|
+
- Create an invocable skill (`/skill-name`)
|
|
56
|
+
- Configure invocable frontmatter (description, allowed-tools, model)
|
|
57
57
|
- Use dynamic arguments ($ARGUMENTS, $1, $2)
|
|
58
58
|
- Reference files with @ syntax
|
|
59
59
|
- Execute bash inline with `[BANG]` backticks
|
|
@@ -147,9 +147,8 @@ Use when the user needs to:
|
|
|
147
147
|
```
|
|
148
148
|
User wants to...
|
|
149
149
|
├── Create/organize a plugin structure? → plugin-structure
|
|
150
|
-
├── Add a
|
|
150
|
+
├── Add a slash entry point / any skill (passive or invocable)? → skill-development
|
|
151
151
|
├── Create an autonomous agent? → agent-development
|
|
152
|
-
├── Add a complex skill with scripts/references? → skill-development
|
|
153
152
|
├── React to Claude Code events? → hook-development
|
|
154
153
|
├── Integrate external service/API? → mcp-integration
|
|
155
154
|
├── Add code intelligence/LSP? → lsp-integration
|
|
@@ -162,7 +161,7 @@ User wants to...
|
|
|
162
161
|
### Building a Complete Plugin
|
|
163
162
|
|
|
164
163
|
1. **Start**: Load `plugin-structure` skill to create directory layout
|
|
165
|
-
2. **Add features**: Load `
|
|
164
|
+
2. **Add features**: Load `skill-development` for passive and invocable skills
|
|
166
165
|
3. **Automation**: Load `hook-development` for event-driven behavior
|
|
167
166
|
4. **Configuration**: Load `plugin-settings` if user configuration needed
|
|
168
167
|
5. **Validation**: Use plugin-validator agent to validate structure
|
|
@@ -171,14 +170,14 @@ User wants to...
|
|
|
171
170
|
|
|
172
171
|
1. **Start**: Load `plugin-structure` for basic structure
|
|
173
172
|
2. **Integration**: Load `mcp-integration` to configure MCP servers
|
|
174
|
-
3. **
|
|
173
|
+
3. **Skills**: Load `skill-development` to create invocable skills that use MCP tools
|
|
175
174
|
4. **Agents**: Load `agent-development` for autonomous MCP workflows
|
|
176
175
|
|
|
177
176
|
### Building a Code Intelligence Plugin
|
|
178
177
|
|
|
179
178
|
1. **Start**: Load `plugin-structure` for basic structure
|
|
180
179
|
2. **LSP**: Load `lsp-integration` to configure language servers
|
|
181
|
-
3. **
|
|
180
|
+
3. **Skills**: Load `skill-development` for invocable skills using LSP features
|
|
182
181
|
|
|
183
182
|
### Building a Skill-Focused Plugin
|
|
184
183
|
|
|
@@ -198,9 +197,9 @@ The plugin-dev plugin also provides 3 agents:
|
|
|
198
197
|
|
|
199
198
|
Use agents proactively after creating components to ensure quality.
|
|
200
199
|
|
|
201
|
-
## Available
|
|
200
|
+
## Available Invocable Skills
|
|
202
201
|
|
|
203
|
-
|
|
|
202
|
+
| Invocable skill | Purpose |
|
|
204
203
|
| -------------------------------- | --------------------------------------------------- |
|
|
205
204
|
| `/plugin-dev:plugin-dev-guide` | Overview and skill routing |
|
|
206
205
|
| `/plugin-dev:start` | Entry point - choose plugin or marketplace creation |
|
|
@@ -559,6 +559,4 @@ Plugin-dev's skills demonstrate best practices:
|
|
|
559
559
|
- `../hook-development/` - Progressive disclosure, utilities
|
|
560
560
|
- `../agent-development/` - AI-assisted creation, references
|
|
561
561
|
- `../mcp-integration/` - Comprehensive references
|
|
562
|
-
- `../plugin-settings/` - Real-world examples
|
|
563
|
-
- `../command-development/` - Clear critical concepts
|
|
564
562
|
- `../plugin-structure/` - Good organization
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
name: start
|
|
3
|
+
description: >-
|
|
4
|
+
Entry point for plugin development — routes the user to create or validate an
|
|
5
|
+
Enact plugin. Use when someone says "start a plugin", "new plugin", "/start",
|
|
6
|
+
or asks how to begin authoring a plugin.
|
|
6
7
|
disable-model-invocation: true
|
|
7
8
|
---
|
|
8
9
|
|
|
@@ -34,7 +35,7 @@ Welcome to the Enact Plugin Manager!
|
|
|
34
35
|
|
|
35
36
|
**Plugin** → One bundle for Claude, Codex, and Cursor
|
|
36
37
|
- Canonical manifest: .agents/plugin.json
|
|
37
|
-
- Shared components: skills
|
|
38
|
+
- Shared components: skills/ (passive + invocable), agents/, hooks/, .mcp.json
|
|
38
39
|
- Host copies: .claude-plugin/, .codex-plugin/, .cursor-plugin/
|
|
39
40
|
- Example: ../enact-operator/extensions/ (sibling top-level submodule of enact-os)
|
|
40
41
|
|
|
@@ -74,7 +75,7 @@ Option 2:
|
|
|
74
75
|
|
|
75
76
|
- Load `plugin-structure` when the user is new to multi-platform layout.
|
|
76
77
|
- After editing `.agents/plugin.json`, remind them to run `enact-extensions sync`.
|
|
77
|
-
- Marketplace authoring is archived under `
|
|
78
|
+
- Marketplace authoring is archived under `skills/_archive/` — only mention if explicitly requested.
|
|
78
79
|
|
|
79
80
|
---
|
|
80
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amsterdamdatalabs/enact-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Create and validate Enact multi-platform plugin manifests",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -31,22 +31,27 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
34
|
-
"prepublishOnly": "npm run build",
|
|
34
|
+
"prepublishOnly": "npm run check:principles && npm run check:hooks && npm run build",
|
|
35
|
+
"check:principles": "node scripts/check-principles.mjs",
|
|
36
|
+
"check:hooks": "node scripts/check-hooks.mjs",
|
|
37
|
+
"check:hooks:smoke": "node scripts/check-hooks.mjs --smoke",
|
|
35
38
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
39
|
"validate": "npm run build && node scripts/enact-extensions.mjs validate extensions/plugin-dev",
|
|
37
40
|
"sync": "npm run build && node scripts/enact-extensions.mjs sync extensions/plugin-dev",
|
|
38
41
|
"setup:context": "bash scripts/setup-enact-context.sh",
|
|
39
|
-
"test": "npm run build && node --test tests/*.test.mjs",
|
|
40
|
-
"postinstall": "node scripts/postinstall.mjs"
|
|
42
|
+
"test": "npm run check:principles && npm run check:hooks:smoke && npm run build && node --test tests/*.test.mjs",
|
|
43
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
44
|
+
"prepare": "husky"
|
|
41
45
|
},
|
|
42
46
|
"dependencies": {
|
|
47
|
+
"@iarna/toml": "^2.2.5",
|
|
43
48
|
"ajv": "^8.17.1",
|
|
44
|
-
"ajv-formats": "^3.0.1"
|
|
45
|
-
"@iarna/toml": "^2.2.5"
|
|
49
|
+
"ajv-formats": "^3.0.1"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
48
52
|
"@types/node": "^22.0.0",
|
|
49
53
|
"esbuild": "^0.28.0",
|
|
54
|
+
"husky": "^9.1.7",
|
|
50
55
|
"typescript": "^5.9.3"
|
|
51
56
|
},
|
|
52
57
|
"engines": {
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Repo guard: validate every plugin's hooks before they ship.
|
|
4
|
+
*
|
|
5
|
+
* Born from real breakage: enact-operator shipped hook entries for a
|
|
6
|
+
* doom-loop CLI that had been deleted (module-not-found at runtime), and
|
|
7
|
+
* enact-factory's npm package omitted `extensions/` from its files whitelist
|
|
8
|
+
* so `enact-factory hook ...` could never load. Both passed every static check
|
|
9
|
+
* yet failed the moment Claude Code fired the hook. plugin-dev meanwhile
|
|
10
|
+
* declared a `hooks` component whose hooks.json was `{ "hooks": {} }` — a
|
|
11
|
+
* component that installs nothing.
|
|
12
|
+
*
|
|
13
|
+
* Two passes:
|
|
14
|
+
* STATIC (always, no binaries): every plugin that declares a `hooks` manifest
|
|
15
|
+
* field must have a hooks file that is valid JSON, has a NON-EMPTY `hooks`
|
|
16
|
+
* object, and whose every entry is a well-formed `command` hook.
|
|
17
|
+
* SMOKE (--smoke, best-effort): for each distinct hook command whose binary
|
|
18
|
+
* resolves on PATH, actually run it with a synthetic payload in an isolated
|
|
19
|
+
* HOME/TMPDIR and fail if it crashes on module resolution (the exact class
|
|
20
|
+
* of bug above). Binaries that aren't installed are skipped, so this stays
|
|
21
|
+
* green in CI where the plugin CLIs are absent.
|
|
22
|
+
*
|
|
23
|
+
* Run via `npm run check:hooks` (static) or `npm run check:hooks:smoke`.
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync, existsSync, readdirSync, statSync, mkdtempSync, rmSync } from "node:fs";
|
|
26
|
+
import { join, dirname, resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { spawnSync, execSync } from "node:child_process";
|
|
30
|
+
|
|
31
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
32
|
+
const EXT = join(ROOT, "extensions");
|
|
33
|
+
const SMOKE = process.argv.includes("--smoke");
|
|
34
|
+
|
|
35
|
+
const errors = [];
|
|
36
|
+
const warnings = [];
|
|
37
|
+
const notes = [];
|
|
38
|
+
const err = (p, m) => errors.push(`[ERROR] ${p}: ${m}`);
|
|
39
|
+
const warn = (p, m) => warnings.push(`[warn] ${p}: ${m}`);
|
|
40
|
+
const note = (m) => notes.push(`[note] ${m}`);
|
|
41
|
+
|
|
42
|
+
function isDir(p) {
|
|
43
|
+
try { return statSync(p).isDirectory(); } catch { return false; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Module-resolution / load crash signatures — the failure class this guard exists for.
|
|
47
|
+
const CRASH = [
|
|
48
|
+
"Cannot find module",
|
|
49
|
+
"Cannot find package",
|
|
50
|
+
"ERR_MODULE_NOT_FOUND",
|
|
51
|
+
"MODULE_NOT_FOUND",
|
|
52
|
+
"ERR_REQUIRE_ESM",
|
|
53
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function onPath(bin) {
|
|
57
|
+
try { execSync(`command -v ${bin}`, { stdio: "ignore", shell: "/bin/sh" }); return true; }
|
|
58
|
+
catch { return false; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Walk a hooks.json object, yielding { event, command } for every command entry.
|
|
62
|
+
function* commandEntries(hooks, id) {
|
|
63
|
+
for (const [event, groups] of Object.entries(hooks)) {
|
|
64
|
+
if (!Array.isArray(groups)) { err(id, `hooks.${event} is not an array`); continue; }
|
|
65
|
+
for (const g of groups) {
|
|
66
|
+
const list = g && g.hooks;
|
|
67
|
+
if (!Array.isArray(list)) { err(id, `hooks.${event} group has no hooks[] array`); continue; }
|
|
68
|
+
for (const h of list) {
|
|
69
|
+
if (!h || h.type !== "command") { err(id, `hooks.${event} entry is not a command hook (type="${h && h.type}")`); continue; }
|
|
70
|
+
if (typeof h.command !== "string" || !h.command.trim()) { err(id, `hooks.${event} command hook has empty command`); continue; }
|
|
71
|
+
yield { event, command: h.command.trim() };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const smokePayload = JSON.stringify({
|
|
78
|
+
session_id: "hooks-smoke",
|
|
79
|
+
transcript_path: "/dev/null",
|
|
80
|
+
cwd: ROOT,
|
|
81
|
+
hook_event_name: "Smoke",
|
|
82
|
+
tool_name: "Bash",
|
|
83
|
+
tool_input: { command: "true" },
|
|
84
|
+
tool_response: {},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function smokeRun(command) {
|
|
88
|
+
const parts = command.split(/\s+/);
|
|
89
|
+
const bin = parts[0];
|
|
90
|
+
const args = parts.slice(1);
|
|
91
|
+
const home = mkdtempSync(join(tmpdir(), "hooks-smoke-"));
|
|
92
|
+
try {
|
|
93
|
+
const r = spawnSync(bin, args, {
|
|
94
|
+
input: smokePayload,
|
|
95
|
+
timeout: 8000,
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
env: { ...process.env, HOME: home, TMPDIR: home, XDG_CONFIG_HOME: join(home, ".config"), XDG_STATE_HOME: join(home, ".state") },
|
|
98
|
+
});
|
|
99
|
+
const out = `${r.stdout || ""}\n${r.stderr || ""}`;
|
|
100
|
+
if (r.error && r.error.code === "ETIMEDOUT") return { ok: true, soft: "timed out (hook may wait on stdin/state) — not a crash" };
|
|
101
|
+
if (r.error) return { ok: false, why: r.error.message };
|
|
102
|
+
const hit = CRASH.find((s) => out.includes(s));
|
|
103
|
+
if (hit) return { ok: false, why: `module-load crash: "${hit}"` };
|
|
104
|
+
if (r.signal) return { ok: false, why: `killed by signal ${r.signal}` };
|
|
105
|
+
return { ok: true };
|
|
106
|
+
} finally {
|
|
107
|
+
rmSync(home, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!existsSync(EXT)) {
|
|
112
|
+
console.error(`No extensions dir at ${EXT}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const smokeCommands = new Set();
|
|
117
|
+
|
|
118
|
+
for (const name of readdirSync(EXT)) {
|
|
119
|
+
const root = join(EXT, name);
|
|
120
|
+
if (!isDir(root)) continue;
|
|
121
|
+
const manifestPath = join(root, ".agents", "plugin.json");
|
|
122
|
+
if (!existsSync(manifestPath)) continue;
|
|
123
|
+
const id = `extensions/${name}`;
|
|
124
|
+
let m;
|
|
125
|
+
try { m = JSON.parse(readFileSync(manifestPath, "utf8")); }
|
|
126
|
+
catch (e) { err(id, `unreadable .agents/plugin.json: ${e.message}`); continue; }
|
|
127
|
+
|
|
128
|
+
const field = m.hooks;
|
|
129
|
+
if (!field) continue; // no hooks declared — nothing to validate here (E2 in check-principles covers undeclared dirs)
|
|
130
|
+
|
|
131
|
+
const hooksPath = resolve(root, field);
|
|
132
|
+
if (!existsSync(hooksPath)) { err(id, `manifest hooks -> "${field}" but file is missing at ${hooksPath}`); continue; }
|
|
133
|
+
|
|
134
|
+
let doc;
|
|
135
|
+
try { doc = JSON.parse(readFileSync(hooksPath, "utf8")); }
|
|
136
|
+
catch (e) { err(id, `${field} is not valid JSON: ${e.message}`); continue; }
|
|
137
|
+
|
|
138
|
+
const hooks = doc && doc.hooks;
|
|
139
|
+
if (!hooks || typeof hooks !== "object") { err(id, `${field} has no "hooks" object`); continue; }
|
|
140
|
+
if (Object.keys(hooks).length === 0) {
|
|
141
|
+
err(id, `${field} declares an empty hooks object — a hooks component that registers nothing; drop the "hooks" manifest field and the file`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const { command } of commandEntries(hooks, id)) {
|
|
146
|
+
smokeCommands.add(command);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (SMOKE) {
|
|
151
|
+
const cmds = [...smokeCommands].sort();
|
|
152
|
+
const bins = new Set(cmds.map((c) => c.split(/\s+/)[0]));
|
|
153
|
+
const present = new Set([...bins].filter(onPath));
|
|
154
|
+
for (const b of bins) if (!present.has(b)) note(`smoke skipped for "${b}" — not on PATH (can't test what isn't installed)`);
|
|
155
|
+
for (const c of cmds) {
|
|
156
|
+
const bin = c.split(/\s+/)[0];
|
|
157
|
+
if (!present.has(bin)) continue;
|
|
158
|
+
const r = smokeRun(c);
|
|
159
|
+
if (r.ok && r.soft) warn(`smoke`, `\`${c}\` ${r.soft}`);
|
|
160
|
+
else if (!r.ok) err(`smoke`, `\`${c}\` ${r.why}`);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
note(`static only — run with --smoke to exercise installed hook binaries (${smokeCommands.size} distinct commands)`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const n of notes) console.log(n);
|
|
167
|
+
for (const w of warnings) console.log(w);
|
|
168
|
+
for (const e of errors) console.log(e);
|
|
169
|
+
console.log(
|
|
170
|
+
errors.length
|
|
171
|
+
? `\n✗ hooks check FAILED: ${errors.length} error(s), ${warnings.length} warning(s)`
|
|
172
|
+
: `\n✓ hooks check passed (${warnings.length} warning(s))`,
|
|
173
|
+
);
|
|
174
|
+
process.exit(errors.length ? 1 : 0);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Repo guard: enforce the authoring principles in docs/AUTHORING-GUIDELINES.md.
|
|
4
|
+
* Fails (exit 1) on any ERROR. Pure Node, no deps. Run via `npm run
|
|
5
|
+
* check:principles`; also wired into `test` and `prepublishOnly`, and the
|
|
6
|
+
* optional `.githooks/pre-commit`.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* E1 No commands. A `commands/` dir or a `commands` manifest field is banned —
|
|
10
|
+
* commands are skills.
|
|
11
|
+
* E2 Declare every component. A `skills/`, `agents/`, or `hooks/` dir on disk
|
|
12
|
+
* must be named by the matching manifest field (else it silently doesn't
|
|
13
|
+
* install).
|
|
14
|
+
* E3 Skill hygiene. Each `skills/<dir>/` needs a `SKILL.md` with `name` +
|
|
15
|
+
* `description` frontmatter, and no `README.md`.
|
|
16
|
+
* E4 `targets` must not include "enact" (enact is the canonical source).
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
19
|
+
import { join, dirname } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
23
|
+
const EXT = join(ROOT, "extensions");
|
|
24
|
+
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const err = (p, m) => errors.push(`[ERROR] ${p}: ${m}`);
|
|
28
|
+
const warn = (p, m) => warnings.push(`[warn] ${p}: ${m}`);
|
|
29
|
+
|
|
30
|
+
function frontmatter(file) {
|
|
31
|
+
const t = readFileSync(file, "utf8");
|
|
32
|
+
if (!t.startsWith("---")) return null;
|
|
33
|
+
const m = /^---\n([\s\S]*?)\n---/.exec(t);
|
|
34
|
+
if (!m) return null;
|
|
35
|
+
const body = m[1];
|
|
36
|
+
return {
|
|
37
|
+
name: /(^|\n)name:/.test(body),
|
|
38
|
+
description: /(^|\n)description:/.test(body),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isDir(p) {
|
|
43
|
+
try { return statSync(p).isDirectory(); } catch { return false; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!existsSync(EXT)) {
|
|
47
|
+
console.error(`No extensions dir at ${EXT}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const name of readdirSync(EXT)) {
|
|
52
|
+
const root = join(EXT, name);
|
|
53
|
+
if (!isDir(root)) continue;
|
|
54
|
+
const manifestPath = join(root, ".agents", "plugin.json");
|
|
55
|
+
if (!existsSync(manifestPath)) continue;
|
|
56
|
+
const id = `extensions/${name}`;
|
|
57
|
+
let m;
|
|
58
|
+
try { m = JSON.parse(readFileSync(manifestPath, "utf8")); }
|
|
59
|
+
catch (e) { err(id, `unreadable .agents/plugin.json: ${e.message}`); continue; }
|
|
60
|
+
|
|
61
|
+
// E1 — no commands
|
|
62
|
+
if ("commands" in m) err(id, `manifest declares "commands" — commands are skills; remove it`);
|
|
63
|
+
if (isDir(join(root, "commands"))) err(id, `has a commands/ dir — commands are skills; migrate to skills/`);
|
|
64
|
+
|
|
65
|
+
// E4 — targets must not include enact
|
|
66
|
+
if (Array.isArray(m.targets) && m.targets.includes("enact"))
|
|
67
|
+
err(id, `targets includes "enact" — enact is the canonical source, never a target`);
|
|
68
|
+
|
|
69
|
+
// E2 — declare components that exist on disk
|
|
70
|
+
for (const comp of ["skills", "agents", "hooks"]) {
|
|
71
|
+
if (isDir(join(root, comp)) && !m[comp])
|
|
72
|
+
err(id, `${comp}/ exists on disk but manifest has no "${comp}" field → it will NOT install`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// E3 — skill hygiene
|
|
76
|
+
const skillsDir = join(root, "skills");
|
|
77
|
+
if (isDir(skillsDir)) {
|
|
78
|
+
for (const s of readdirSync(skillsDir)) {
|
|
79
|
+
const sp = join(skillsDir, s);
|
|
80
|
+
if (!isDir(sp)) { warn(id, `skills/${s} is a loose file, not a skill dir`); continue; }
|
|
81
|
+
const smd = join(sp, "SKILL.md");
|
|
82
|
+
if (!existsSync(smd)) { err(id, `skills/${s} has no SKILL.md`); continue; }
|
|
83
|
+
const fm = frontmatter(smd);
|
|
84
|
+
if (!fm) err(id, `skills/${s}/SKILL.md has no YAML frontmatter`);
|
|
85
|
+
else {
|
|
86
|
+
if (!fm.name) err(id, `skills/${s}/SKILL.md frontmatter missing "name"`);
|
|
87
|
+
if (!fm.description) err(id, `skills/${s}/SKILL.md frontmatter missing "description"`);
|
|
88
|
+
}
|
|
89
|
+
if (existsSync(join(sp, "README.md"))) err(id, `skills/${s} contains README.md (not allowed in a skill)`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const w of warnings) console.log(w);
|
|
95
|
+
for (const e of errors) console.log(e);
|
|
96
|
+
console.log(
|
|
97
|
+
errors.length
|
|
98
|
+
? `\n✗ principles check FAILED: ${errors.length} error(s), ${warnings.length} warning(s)`
|
|
99
|
+
: `\n✓ principles check passed (${warnings.length} warning(s))`,
|
|
100
|
+
);
|
|
101
|
+
process.exit(errors.length ? 1 : 0);
|