specweave 0.4.0 ā 0.4.1
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/CLAUDE.md +501 -28
- package/README.md +99 -57
- package/bin/specweave.js +16 -0
- package/dist/adapters/adapter-base.d.ts +21 -0
- package/dist/adapters/adapter-base.d.ts.map +1 -1
- package/dist/adapters/adapter-base.js +28 -0
- package/dist/adapters/adapter-base.js.map +1 -1
- package/dist/adapters/adapter-interface.d.ts +41 -0
- package/dist/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/adapters/claude/adapter.d.ts +36 -0
- package/dist/adapters/claude/adapter.d.ts.map +1 -1
- package/dist/adapters/claude/adapter.js +135 -0
- package/dist/adapters/claude/adapter.js.map +1 -1
- package/dist/adapters/copilot/adapter.d.ts +25 -0
- package/dist/adapters/copilot/adapter.d.ts.map +1 -1
- package/dist/adapters/copilot/adapter.js +112 -0
- package/dist/adapters/copilot/adapter.js.map +1 -1
- package/dist/adapters/cursor/adapter.d.ts +36 -0
- package/dist/adapters/cursor/adapter.d.ts.map +1 -1
- package/dist/adapters/cursor/adapter.js +140 -0
- package/dist/adapters/cursor/adapter.js.map +1 -1
- package/dist/adapters/generic/adapter.d.ts +25 -0
- package/dist/adapters/generic/adapter.d.ts.map +1 -1
- package/dist/adapters/generic/adapter.js +111 -0
- package/dist/adapters/generic/adapter.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +48 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/plugin.d.ts +37 -0
- package/dist/cli/commands/plugin.d.ts.map +1 -0
- package/dist/cli/commands/plugin.js +296 -0
- package/dist/cli/commands/plugin.js.map +1 -0
- package/dist/core/plugin-detector.d.ts +96 -0
- package/dist/core/plugin-detector.d.ts.map +1 -0
- package/dist/core/plugin-detector.js +349 -0
- package/dist/core/plugin-detector.js.map +1 -0
- package/dist/core/plugin-loader.d.ts +111 -0
- package/dist/core/plugin-loader.d.ts.map +1 -0
- package/dist/core/plugin-loader.js +319 -0
- package/dist/core/plugin-loader.js.map +1 -0
- package/dist/core/plugin-manager.d.ts +144 -0
- package/dist/core/plugin-manager.d.ts.map +1 -0
- package/dist/core/plugin-manager.js +393 -0
- package/dist/core/plugin-manager.js.map +1 -0
- package/dist/core/schemas/plugin-manifest.schema.json +253 -0
- package/dist/core/types/plugin.d.ts +252 -0
- package/dist/core/types/plugin.d.ts.map +1 -0
- package/dist/core/types/plugin.js +48 -0
- package/dist/core/types/plugin.js.map +1 -0
- package/dist/integrations/jira/jira-mapper.d.ts +2 -2
- package/dist/integrations/jira/jira-mapper.js +2 -2
- package/package.json +13 -9
- package/src/adapters/adapter-base.ts +33 -0
- package/src/adapters/adapter-interface.ts +46 -0
- package/src/adapters/claude/adapter.ts +164 -0
- package/src/adapters/copilot/adapter.ts +138 -0
- package/src/adapters/cursor/adapter.ts +170 -0
- package/src/adapters/generic/adapter.ts +137 -0
- package/src/commands/specweave.increment.md +48 -4
- package/src/hooks/post-increment-plugin-detect.sh +142 -0
- package/src/hooks/post-task-completion.sh +53 -11
- package/src/hooks/pre-task-plugin-detect.sh +96 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin System Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Defines interfaces and types for the SpecWeave plugin architecture.
|
|
5
|
+
* Plugins are modular, domain-specific extensions that enhance SpecWeave's capabilities.
|
|
6
|
+
*
|
|
7
|
+
* @module core/types/plugin
|
|
8
|
+
* @version 0.4.0
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Plugin Manifest - Metadata describing a plugin
|
|
12
|
+
*
|
|
13
|
+
* Loaded from .claude-plugin/manifest.json in each plugin directory.
|
|
14
|
+
* Follows JSON Schema defined in src/core/schemas/plugin-manifest.schema.json
|
|
15
|
+
*/
|
|
16
|
+
export interface PluginManifest {
|
|
17
|
+
/** Unique plugin name (must start with 'specweave-') */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Semantic version (e.g., "1.0.0") */
|
|
20
|
+
version: string;
|
|
21
|
+
/** Human-readable description (max 1024 chars) */
|
|
22
|
+
description: string;
|
|
23
|
+
/** Plugin author */
|
|
24
|
+
author?: string;
|
|
25
|
+
/** License identifier (e.g., "MIT", "Apache-2.0") */
|
|
26
|
+
license?: string;
|
|
27
|
+
/** Required SpecWeave core version (e.g., ">=0.4.0") */
|
|
28
|
+
specweave_core_version: string;
|
|
29
|
+
/** Plugin dependencies */
|
|
30
|
+
dependencies?: {
|
|
31
|
+
/** Other plugins this plugin requires */
|
|
32
|
+
plugins?: string[];
|
|
33
|
+
};
|
|
34
|
+
/** Auto-detection rules for suggesting this plugin */
|
|
35
|
+
auto_detect?: {
|
|
36
|
+
/** File/directory patterns to detect (e.g., "kubernetes/", ".git/") */
|
|
37
|
+
files?: string[];
|
|
38
|
+
/** NPM package dependencies to detect */
|
|
39
|
+
packages?: string[];
|
|
40
|
+
/** Environment variables to detect */
|
|
41
|
+
env_vars?: string[];
|
|
42
|
+
/** Git remote pattern (e.g., "github\\.com") */
|
|
43
|
+
git_remote_pattern?: string;
|
|
44
|
+
};
|
|
45
|
+
/** What this plugin provides */
|
|
46
|
+
provides: {
|
|
47
|
+
/** Skill names provided by this plugin */
|
|
48
|
+
skills: string[];
|
|
49
|
+
/** Agent names provided by this plugin */
|
|
50
|
+
agents: string[];
|
|
51
|
+
/** Command names provided by this plugin */
|
|
52
|
+
commands: string[];
|
|
53
|
+
};
|
|
54
|
+
/** Keywords that trigger plugin suggestions in specs/tasks */
|
|
55
|
+
triggers?: string[];
|
|
56
|
+
/** Attribution for forked/borrowed plugins */
|
|
57
|
+
credits?: {
|
|
58
|
+
/** Upstream source URL if forked */
|
|
59
|
+
based_on?: string | null;
|
|
60
|
+
/** Original author name */
|
|
61
|
+
original_author?: string;
|
|
62
|
+
/** List of contributors */
|
|
63
|
+
contributors?: string[];
|
|
64
|
+
/** Modifications made for SpecWeave */
|
|
65
|
+
modifications?: string[];
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Skill - Auto-activating capability
|
|
70
|
+
*/
|
|
71
|
+
export interface Skill {
|
|
72
|
+
/** Skill name (unique within plugin) */
|
|
73
|
+
name: string;
|
|
74
|
+
/** Path to SKILL.md file */
|
|
75
|
+
path: string;
|
|
76
|
+
/** Description from SKILL.md frontmatter */
|
|
77
|
+
description: string;
|
|
78
|
+
/** Test cases for this skill */
|
|
79
|
+
testCases?: TestCase[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Agent - Specialized role with isolated context
|
|
83
|
+
*/
|
|
84
|
+
export interface Agent {
|
|
85
|
+
/** Agent name (unique within plugin) */
|
|
86
|
+
name: string;
|
|
87
|
+
/** Path to AGENT.md file */
|
|
88
|
+
path: string;
|
|
89
|
+
/** System prompt from AGENT.md */
|
|
90
|
+
systemPrompt: string;
|
|
91
|
+
/** Agent capabilities */
|
|
92
|
+
capabilities: string[];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Command - Slash command (e.g., /specweave.github.sync)
|
|
96
|
+
*/
|
|
97
|
+
export interface Command {
|
|
98
|
+
/** Command name (e.g., "github-sync") */
|
|
99
|
+
name: string;
|
|
100
|
+
/** Path to command.md file */
|
|
101
|
+
path: string;
|
|
102
|
+
/** Description of what the command does */
|
|
103
|
+
description: string;
|
|
104
|
+
/** Command prompt/template */
|
|
105
|
+
prompt: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Test Case - YAML-based test case for skills
|
|
109
|
+
*/
|
|
110
|
+
export interface TestCase {
|
|
111
|
+
/** Test case ID (e.g., "TC-001") */
|
|
112
|
+
id: string;
|
|
113
|
+
/** Test description */
|
|
114
|
+
description: string;
|
|
115
|
+
/** Input for the skill */
|
|
116
|
+
input: string;
|
|
117
|
+
/** Expected output pattern */
|
|
118
|
+
expected: string;
|
|
119
|
+
/** Path to test case file */
|
|
120
|
+
path: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Plugin - Complete plugin with all components loaded
|
|
124
|
+
*/
|
|
125
|
+
export interface Plugin {
|
|
126
|
+
/** Plugin manifest (metadata) */
|
|
127
|
+
manifest: PluginManifest;
|
|
128
|
+
/** Absolute path to plugin directory */
|
|
129
|
+
path: string;
|
|
130
|
+
/** Skills provided by this plugin */
|
|
131
|
+
skills: Skill[];
|
|
132
|
+
/** Agents provided by this plugin */
|
|
133
|
+
agents: Agent[];
|
|
134
|
+
/** Commands provided by this plugin */
|
|
135
|
+
commands: Command[];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Plugin Info - Lightweight plugin metadata (for listing)
|
|
139
|
+
*/
|
|
140
|
+
export interface PluginInfo {
|
|
141
|
+
/** Plugin name */
|
|
142
|
+
name: string;
|
|
143
|
+
/** Plugin version */
|
|
144
|
+
version: string;
|
|
145
|
+
/** Description */
|
|
146
|
+
description: string;
|
|
147
|
+
/** Path to plugin directory */
|
|
148
|
+
path: string;
|
|
149
|
+
/** Is this plugin currently enabled? */
|
|
150
|
+
enabled: boolean;
|
|
151
|
+
/** Skill count */
|
|
152
|
+
skillCount: number;
|
|
153
|
+
/** Agent count */
|
|
154
|
+
agentCount: number;
|
|
155
|
+
/** Command count */
|
|
156
|
+
commandCount: number;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Validation Result - Result of manifest validation
|
|
160
|
+
*/
|
|
161
|
+
export interface ValidationResult {
|
|
162
|
+
/** Is the manifest valid? */
|
|
163
|
+
valid: boolean;
|
|
164
|
+
/** Validation errors (if any) */
|
|
165
|
+
errors: string[];
|
|
166
|
+
/** Warnings (non-fatal issues) */
|
|
167
|
+
warnings: string[];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Detection Result - Result of plugin auto-detection
|
|
171
|
+
*/
|
|
172
|
+
export interface DetectionResult {
|
|
173
|
+
/** Detected plugin name */
|
|
174
|
+
pluginName: string;
|
|
175
|
+
/** Detection confidence (0-1) */
|
|
176
|
+
confidence: number;
|
|
177
|
+
/** Reason for detection */
|
|
178
|
+
reason: string;
|
|
179
|
+
/** What triggered detection (file, package, env var, etc.) */
|
|
180
|
+
trigger: string;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Plugin Config - User's plugin configuration
|
|
184
|
+
*
|
|
185
|
+
* Stored in .specweave/config.yaml
|
|
186
|
+
*/
|
|
187
|
+
export interface PluginConfig {
|
|
188
|
+
/** List of enabled plugin names */
|
|
189
|
+
enabled: string[];
|
|
190
|
+
/** Plugin-specific settings */
|
|
191
|
+
settings?: {
|
|
192
|
+
[pluginName: string]: Record<string, any>;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Plugin Load Options - Options for loading a plugin
|
|
197
|
+
*/
|
|
198
|
+
export interface PluginLoadOptions {
|
|
199
|
+
/** Force reload even if already loaded */
|
|
200
|
+
force?: boolean;
|
|
201
|
+
/** Skip dependency checking */
|
|
202
|
+
skipDependencies?: boolean;
|
|
203
|
+
/** Validate plugin integrity */
|
|
204
|
+
validate?: boolean;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Plugin Unload Options - Options for unloading a plugin
|
|
208
|
+
*/
|
|
209
|
+
export interface PluginUnloadOptions {
|
|
210
|
+
/** Remove plugin files (vs. just marking as disabled) */
|
|
211
|
+
remove?: boolean;
|
|
212
|
+
/** Force unload even if other plugins depend on it */
|
|
213
|
+
force?: boolean;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Plugin Dependency - Dependency information
|
|
217
|
+
*/
|
|
218
|
+
export interface PluginDependency {
|
|
219
|
+
/** Plugin name */
|
|
220
|
+
name: string;
|
|
221
|
+
/** Version requirement (e.g., ">=1.0.0") */
|
|
222
|
+
version?: string;
|
|
223
|
+
/** Is this dependency optional? */
|
|
224
|
+
optional?: boolean;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Plugin Error - Custom error for plugin operations
|
|
228
|
+
*/
|
|
229
|
+
export declare class PluginError extends Error {
|
|
230
|
+
pluginName?: string | undefined;
|
|
231
|
+
code?: string | undefined;
|
|
232
|
+
constructor(message: string, pluginName?: string | undefined, code?: string | undefined);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Plugin Manifest Validation Error
|
|
236
|
+
*/
|
|
237
|
+
export declare class ManifestValidationError extends PluginError {
|
|
238
|
+
constructor(message: string, pluginName?: string);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Plugin Not Found Error
|
|
242
|
+
*/
|
|
243
|
+
export declare class PluginNotFoundError extends PluginError {
|
|
244
|
+
constructor(pluginName: string);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Plugin Dependency Error
|
|
248
|
+
*/
|
|
249
|
+
export declare class PluginDependencyError extends PluginError {
|
|
250
|
+
constructor(message: string, pluginName?: string);
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/core/types/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin System Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Defines interfaces and types for the SpecWeave plugin architecture.
|
|
5
|
+
* Plugins are modular, domain-specific extensions that enhance SpecWeave's capabilities.
|
|
6
|
+
*
|
|
7
|
+
* @module core/types/plugin
|
|
8
|
+
* @version 0.4.0
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Plugin Error - Custom error for plugin operations
|
|
12
|
+
*/
|
|
13
|
+
export class PluginError extends Error {
|
|
14
|
+
constructor(message, pluginName, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.pluginName = pluginName;
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = 'PluginError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Plugin Manifest Validation Error
|
|
23
|
+
*/
|
|
24
|
+
export class ManifestValidationError extends PluginError {
|
|
25
|
+
constructor(message, pluginName) {
|
|
26
|
+
super(message, pluginName, 'MANIFEST_INVALID');
|
|
27
|
+
this.name = 'ManifestValidationError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Plugin Not Found Error
|
|
32
|
+
*/
|
|
33
|
+
export class PluginNotFoundError extends PluginError {
|
|
34
|
+
constructor(pluginName) {
|
|
35
|
+
super(`Plugin '${pluginName}' not found`, pluginName, 'PLUGIN_NOT_FOUND');
|
|
36
|
+
this.name = 'PluginNotFoundError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Plugin Dependency Error
|
|
41
|
+
*/
|
|
42
|
+
export class PluginDependencyError extends PluginError {
|
|
43
|
+
constructor(message, pluginName) {
|
|
44
|
+
super(message, pluginName, 'DEPENDENCY_ERROR');
|
|
45
|
+
this.name = 'PluginDependencyError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/core/types/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA4RH;;GAEG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YACE,OAAe,EACR,UAAmB,EACnB,IAAa;QAEpB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,eAAU,GAAV,UAAU,CAAS;QACnB,SAAI,GAAJ,IAAI,CAAS;QAGpB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,uBAAwB,SAAQ,WAAW;IACtD,YAAY,OAAe,EAAE,UAAmB;QAC9C,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,WAAW;IAClD,YAAY,UAAkB;QAC5B,KAAK,CAAC,WAAW,UAAU,aAAa,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAC1E,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,qBAAsB,SAAQ,WAAW;IACpD,YAAY,OAAe,EAAE,UAAmB;QAC9C,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF"}
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Syncs with:
|
|
10
10
|
* - .specweave/increments/{id}/ - Increment folder
|
|
11
|
-
* - .specweave/docs/
|
|
12
|
-
* - .specweave/docs/
|
|
11
|
+
* - .specweave/docs/internal/architecture/rfc/ - RFC documents for detailed specs
|
|
12
|
+
* - .specweave/docs/internal/architecture/adr/ - Architecture decisions (ADRs)
|
|
13
13
|
*/
|
|
14
14
|
import { JiraClient } from './jira-client';
|
|
15
15
|
export interface SpecWeaveIncrement {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Syncs with:
|
|
10
10
|
* - .specweave/increments/{id}/ - Increment folder
|
|
11
|
-
* - .specweave/docs/
|
|
12
|
-
* - .specweave/docs/
|
|
11
|
+
* - .specweave/docs/internal/architecture/rfc/ - RFC documents for detailed specs
|
|
12
|
+
* - .specweave/docs/internal/architecture/adr/ - Architecture decisions (ADRs)
|
|
13
13
|
*/
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Replace vibe coding with spec-driven development. Smart workflow: /specweave inc auto-closes previous, /specweave build auto-resumes, /specweave progress shows status. PM-led planning, 10 agents, 35+ skills. spec-weave.com",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,16 +14,19 @@
|
|
|
14
14
|
"test": "jest",
|
|
15
15
|
"test:smoke": "bash tests/smoke/e2e-smoke-test.sh",
|
|
16
16
|
"test:e2e": "playwright test tests/e2e/specweave-smoke.spec.ts",
|
|
17
|
-
"test:integration": "npm run test:integration:ado && npm run test:integration:jira",
|
|
18
|
-
"test:integration:ado": "
|
|
19
|
-
"test:integration:jira": "
|
|
20
|
-
"test:
|
|
21
|
-
"test:
|
|
22
|
-
"
|
|
23
|
-
"
|
|
17
|
+
"test:integration": "npm run test:integration:ado && npm run test:integration:jira && npm run test:integration:ml",
|
|
18
|
+
"test:integration:ado": "npx tsx tests/integration/ado-sync/ado-sync.test.ts",
|
|
19
|
+
"test:integration:jira": "npx tsx tests/integration/jira-sync/jira-sync.test.ts",
|
|
20
|
+
"test:integration:ml": "npx tsx tests/integration/ml-pipeline-workflow/ml-pipeline-soccer-detection.test.ts",
|
|
21
|
+
"test:sync:jira": "npx tsx tests/integration/jira-sync/jira-bidirectional-sync.test.ts",
|
|
22
|
+
"test:incremental:jira": "npx tsx tests/integration/jira-sync/jira-incremental-sync.test.ts",
|
|
23
|
+
"test:ml:pipeline": "npx tsx tests/integration/ml-pipeline-workflow/ml-pipeline-soccer-detection.test.ts",
|
|
24
|
+
"test:ml:real": "npx tsx tests/integration/ml-pipeline-workflow/ml-pipeline-real-video.test.ts",
|
|
25
|
+
"generate:tests": "npx tsx scripts/generate-tests.ts",
|
|
26
|
+
"generate:tests:skill": "npx tsx scripts/generate-tests.ts",
|
|
24
27
|
"test:all:generated": "bash scripts/run-all-tests.sh",
|
|
25
28
|
"test:all": "npm test && npm run test:smoke && npm run test:integration",
|
|
26
|
-
"generate:skills-index": "
|
|
29
|
+
"generate:skills-index": "npx tsx src/utils/generate-skills-index.ts",
|
|
27
30
|
"prepublishOnly": "npm run build",
|
|
28
31
|
"install:agents": "bash bin/install-agents.sh",
|
|
29
32
|
"install:skills": "bash bin/install-skills.sh",
|
|
@@ -77,6 +80,7 @@
|
|
|
77
80
|
"LICENSE"
|
|
78
81
|
],
|
|
79
82
|
"dependencies": {
|
|
83
|
+
"ajv": "^8.17.1",
|
|
80
84
|
"chalk": "^5.3.0",
|
|
81
85
|
"commander": "^11.1.0",
|
|
82
86
|
"fs-extra": "^11.2.0",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
AutomationLevel
|
|
17
17
|
} from './adapter-interface.js';
|
|
18
18
|
import { getDirname } from '../utils/esm-helpers.js';
|
|
19
|
+
import type { Plugin } from '../core/types/plugin.js';
|
|
19
20
|
|
|
20
21
|
const __dirname = getDirname(import.meta.url);
|
|
21
22
|
|
|
@@ -146,4 +147,36 @@ export abstract class AdapterBase implements IAdapter {
|
|
|
146
147
|
|
|
147
148
|
return content;
|
|
148
149
|
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check if this adapter supports plugins
|
|
153
|
+
* Default: No plugin support (override in concrete adapters)
|
|
154
|
+
*/
|
|
155
|
+
supportsPlugins(): boolean {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Compile and install a plugin
|
|
161
|
+
* Default: Throw error (override in concrete adapters that support plugins)
|
|
162
|
+
*/
|
|
163
|
+
async compilePlugin(plugin: Plugin): Promise<void> {
|
|
164
|
+
throw new Error(`Plugin support not implemented for ${this.name} adapter`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Unload a plugin
|
|
169
|
+
* Default: Throw error (override in concrete adapters that support plugins)
|
|
170
|
+
*/
|
|
171
|
+
async unloadPlugin(pluginName: string): Promise<void> {
|
|
172
|
+
throw new Error(`Plugin support not implemented for ${this.name} adapter`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get installed plugins
|
|
177
|
+
* Default: Return empty array (override in concrete adapters that support plugins)
|
|
178
|
+
*/
|
|
179
|
+
async getInstalledPlugins(): Promise<string[]> {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
149
182
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Enables SpecWeave to work with ANY AI coding tool (Claude, Cursor, Copilot, etc.)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { Plugin } from '../core/types/plugin.js';
|
|
9
|
+
|
|
8
10
|
export interface AdapterOptions {
|
|
9
11
|
projectPath: string;
|
|
10
12
|
projectName: string;
|
|
@@ -117,4 +119,48 @@ export interface IAdapter {
|
|
|
117
119
|
* @returns string Markdown-formatted instructions
|
|
118
120
|
*/
|
|
119
121
|
getInstructions(): string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if this adapter supports plugins
|
|
125
|
+
*
|
|
126
|
+
* Returns true if the adapter can install and compile plugins
|
|
127
|
+
* (Claude native, Cursor AGENTS.md, Copilot instructions.md)
|
|
128
|
+
* Returns false for generic/manual adapters
|
|
129
|
+
*
|
|
130
|
+
* @returns boolean True if plugins are supported
|
|
131
|
+
*/
|
|
132
|
+
supportsPlugins(): boolean;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Compile and install a plugin for this adapter
|
|
136
|
+
*
|
|
137
|
+
* Transforms plugin content (skills/agents/commands) to tool-specific format:
|
|
138
|
+
* - Claude: Copy to .claude/skills/, .claude/agents/, .claude/commands/
|
|
139
|
+
* - Cursor: Compile to AGENTS.md + team commands JSON
|
|
140
|
+
* - Copilot: Compile to .github/copilot/instructions.md
|
|
141
|
+
* - Generic: Generate manual copy-paste instructions
|
|
142
|
+
*
|
|
143
|
+
* @param plugin Plugin to compile and install
|
|
144
|
+
* @returns Promise<void>
|
|
145
|
+
*/
|
|
146
|
+
compilePlugin(plugin: Plugin): Promise<void>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Unload (disable) a plugin for this adapter
|
|
150
|
+
*
|
|
151
|
+
* Removes plugin-specific files and restores previous state
|
|
152
|
+
*
|
|
153
|
+
* @param pluginName Name of plugin to unload
|
|
154
|
+
* @returns Promise<void>
|
|
155
|
+
*/
|
|
156
|
+
unloadPlugin(pluginName: string): Promise<void>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get plugin installation status
|
|
160
|
+
*
|
|
161
|
+
* Returns information about which plugins are currently installed
|
|
162
|
+
*
|
|
163
|
+
* @returns Promise<string[]> Array of installed plugin names
|
|
164
|
+
*/
|
|
165
|
+
getInstalledPlugins(): Promise<string[]>;
|
|
120
166
|
}
|
|
@@ -12,6 +12,7 @@ import * as path from 'path';
|
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
13
|
import { AdapterBase } from '../adapter-base.js';
|
|
14
14
|
import { AdapterOptions, AdapterFile } from '../adapter-interface.js';
|
|
15
|
+
import type { Plugin } from '../../core/types/plugin.js';
|
|
15
16
|
|
|
16
17
|
export class ClaudeAdapter extends AdapterBase {
|
|
17
18
|
name = 'claude';
|
|
@@ -154,4 +155,167 @@ You're ready to build with SpecWeave on Claude Code!
|
|
|
154
155
|
For complete documentation, see: .claude/README.md
|
|
155
156
|
`;
|
|
156
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check if Claude adapter supports plugins
|
|
161
|
+
*
|
|
162
|
+
* Claude Code has FULL plugin support via native .claude/ directory
|
|
163
|
+
*
|
|
164
|
+
* @returns boolean Always true
|
|
165
|
+
*/
|
|
166
|
+
supportsPlugins(): boolean {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Compile and install a plugin for Claude Code
|
|
172
|
+
*
|
|
173
|
+
* Claude uses native plugin installation:
|
|
174
|
+
* - Copy skills to .claude/skills/{plugin-name}/{skill-name}/
|
|
175
|
+
* - Copy agents to .claude/agents/{plugin-name}/{agent-name}/
|
|
176
|
+
* - Copy commands to .claude/commands/
|
|
177
|
+
*
|
|
178
|
+
* @param plugin Plugin to install
|
|
179
|
+
*/
|
|
180
|
+
async compilePlugin(plugin: Plugin): Promise<void> {
|
|
181
|
+
const projectPath = process.cwd();
|
|
182
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
183
|
+
|
|
184
|
+
console.log(`\nš¦ Installing plugin: ${plugin.manifest.name}`);
|
|
185
|
+
|
|
186
|
+
// Ensure base directories exist
|
|
187
|
+
await fs.ensureDir(path.join(claudeDir, 'skills'));
|
|
188
|
+
await fs.ensureDir(path.join(claudeDir, 'agents'));
|
|
189
|
+
await fs.ensureDir(path.join(claudeDir, 'commands'));
|
|
190
|
+
|
|
191
|
+
// Install skills
|
|
192
|
+
for (const skill of plugin.skills) {
|
|
193
|
+
const targetPath = path.join(claudeDir, 'skills', skill.name);
|
|
194
|
+
await fs.ensureDir(targetPath);
|
|
195
|
+
|
|
196
|
+
// Copy SKILL.md
|
|
197
|
+
const skillMdPath = path.join(skill.path, 'SKILL.md');
|
|
198
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
199
|
+
await fs.copy(skillMdPath, path.join(targetPath, 'SKILL.md'));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Copy test cases if they exist
|
|
203
|
+
const testCasesDir = path.join(skill.path, 'test-cases');
|
|
204
|
+
if (await fs.pathExists(testCasesDir)) {
|
|
205
|
+
await fs.copy(testCasesDir, path.join(targetPath, 'test-cases'));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log(` ā Skill: ${skill.name}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Install agents
|
|
212
|
+
for (const agent of plugin.agents) {
|
|
213
|
+
const targetPath = path.join(claudeDir, 'agents', agent.name);
|
|
214
|
+
await fs.ensureDir(targetPath);
|
|
215
|
+
|
|
216
|
+
// Copy AGENT.md
|
|
217
|
+
const agentMdPath = path.join(agent.path, 'AGENT.md');
|
|
218
|
+
if (await fs.pathExists(agentMdPath)) {
|
|
219
|
+
await fs.copy(agentMdPath, path.join(targetPath, 'AGENT.md'));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log(` ā Agent: ${agent.name}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Install commands
|
|
226
|
+
for (const command of plugin.commands) {
|
|
227
|
+
// Commands use their full name as filename
|
|
228
|
+
const fileName = command.name.replace(/\./g, '-') + '.md';
|
|
229
|
+
const targetPath = path.join(claudeDir, 'commands', fileName);
|
|
230
|
+
|
|
231
|
+
if (await fs.pathExists(command.path)) {
|
|
232
|
+
await fs.copy(command.path, targetPath);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.log(` ā Command: /${command.name}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log(`\nā
Plugin ${plugin.manifest.name} installed!`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Unload a plugin from Claude Code
|
|
243
|
+
*
|
|
244
|
+
* Removes plugin files from .claude/ directory
|
|
245
|
+
*
|
|
246
|
+
* @param pluginName Name of plugin to unload
|
|
247
|
+
*/
|
|
248
|
+
async unloadPlugin(pluginName: string): Promise<void> {
|
|
249
|
+
const projectPath = process.cwd();
|
|
250
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
251
|
+
|
|
252
|
+
console.log(`\nšļø Unloading plugin: ${pluginName}`);
|
|
253
|
+
|
|
254
|
+
// Read plugin manifest to know what to remove
|
|
255
|
+
const pluginsDir = path.join(projectPath, 'src', 'plugins');
|
|
256
|
+
const pluginPath = path.join(pluginsDir, pluginName);
|
|
257
|
+
|
|
258
|
+
if (!(await fs.pathExists(pluginPath))) {
|
|
259
|
+
console.warn(`ā ļø Plugin ${pluginName} not found`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Load plugin to get its components
|
|
264
|
+
const { PluginLoader } = await import('../../core/plugin-loader.js');
|
|
265
|
+
const loader = new PluginLoader();
|
|
266
|
+
const plugin = await loader.loadFromDirectory(pluginPath);
|
|
267
|
+
|
|
268
|
+
// Remove skills
|
|
269
|
+
for (const skill of plugin.skills) {
|
|
270
|
+
const skillPath = path.join(claudeDir, 'skills', skill.name);
|
|
271
|
+
if (await fs.pathExists(skillPath)) {
|
|
272
|
+
await fs.remove(skillPath);
|
|
273
|
+
console.log(` ā Removed skill: ${skill.name}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Remove agents
|
|
278
|
+
for (const agent of plugin.agents) {
|
|
279
|
+
const agentPath = path.join(claudeDir, 'agents', agent.name);
|
|
280
|
+
if (await fs.pathExists(agentPath)) {
|
|
281
|
+
await fs.remove(agentPath);
|
|
282
|
+
console.log(` ā Removed agent: ${agent.name}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Remove commands
|
|
287
|
+
for (const command of plugin.commands) {
|
|
288
|
+
const fileName = command.name.replace(/\./g, '-') + '.md';
|
|
289
|
+
const commandPath = path.join(claudeDir, 'commands', fileName);
|
|
290
|
+
if (await fs.pathExists(commandPath)) {
|
|
291
|
+
await fs.remove(commandPath);
|
|
292
|
+
console.log(` ā Removed command: /${command.name}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log(`\nā
Plugin ${pluginName} unloaded!`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get list of installed plugins
|
|
301
|
+
*
|
|
302
|
+
* Returns plugin names that are currently installed in .claude/
|
|
303
|
+
*
|
|
304
|
+
* @returns Array of installed plugin names
|
|
305
|
+
*/
|
|
306
|
+
async getInstalledPlugins(): Promise<string[]> {
|
|
307
|
+
const projectPath = process.cwd();
|
|
308
|
+
const configPath = path.join(projectPath, '.specweave', 'config.yaml');
|
|
309
|
+
|
|
310
|
+
if (!(await fs.pathExists(configPath))) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Read config to get enabled plugins
|
|
315
|
+
const yaml = await import('js-yaml');
|
|
316
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
317
|
+
const config = yaml.load(content) as any;
|
|
318
|
+
|
|
319
|
+
return config.plugins?.enabled || [];
|
|
320
|
+
}
|
|
157
321
|
}
|