@stigmer/plugin-package 3.15.3-dev.20260916211208

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.
Files changed (173) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +66 -0
  3. package/detect.d.ts +50 -0
  4. package/detect.d.ts.map +1 -0
  5. package/detect.js +164 -0
  6. package/detect.js.map +1 -0
  7. package/dialects/claude.d.ts +30 -0
  8. package/dialects/claude.d.ts.map +1 -0
  9. package/dialects/claude.js +71 -0
  10. package/dialects/claude.js.map +1 -0
  11. package/dialects/codex.d.ts +18 -0
  12. package/dialects/codex.d.ts.map +1 -0
  13. package/dialects/codex.js +19 -0
  14. package/dialects/codex.js.map +1 -0
  15. package/dialects/cursor.d.ts +23 -0
  16. package/dialects/cursor.d.ts.map +1 -0
  17. package/dialects/cursor.js +63 -0
  18. package/dialects/cursor.js.map +1 -0
  19. package/dialects/manifest.d.ts +109 -0
  20. package/dialects/manifest.d.ts.map +1 -0
  21. package/dialects/manifest.js +194 -0
  22. package/dialects/manifest.js.map +1 -0
  23. package/dialects/open.d.ts +18 -0
  24. package/dialects/open.d.ts.map +1 -0
  25. package/dialects/open.js +47 -0
  26. package/dialects/open.js.map +1 -0
  27. package/documents.d.ts +43 -0
  28. package/documents.d.ts.map +1 -0
  29. package/documents.js +111 -0
  30. package/documents.js.map +1 -0
  31. package/files.d.ts +114 -0
  32. package/files.d.ts.map +1 -0
  33. package/files.js +187 -0
  34. package/files.js.map +1 -0
  35. package/frontmatter.d.ts +51 -0
  36. package/frontmatter.d.ts.map +1 -0
  37. package/frontmatter.js +61 -0
  38. package/frontmatter.js.map +1 -0
  39. package/index.d.ts +19 -0
  40. package/index.d.ts.map +1 -0
  41. package/index.js +17 -0
  42. package/index.js.map +1 -0
  43. package/messages.d.ts +39 -0
  44. package/messages.d.ts.map +1 -0
  45. package/messages.js +135 -0
  46. package/messages.js.map +1 -0
  47. package/normalise/ignored.d.ts +24 -0
  48. package/normalise/ignored.d.ts.map +1 -0
  49. package/normalise/ignored.js +68 -0
  50. package/normalise/ignored.js.map +1 -0
  51. package/normalise/mcp-servers.d.ts +47 -0
  52. package/normalise/mcp-servers.d.ts.map +1 -0
  53. package/normalise/mcp-servers.js +397 -0
  54. package/normalise/mcp-servers.js.map +1 -0
  55. package/normalise/overlay.d.ts +27 -0
  56. package/normalise/overlay.d.ts.map +1 -0
  57. package/normalise/overlay.js +67 -0
  58. package/normalise/overlay.js.map +1 -0
  59. package/normalise/skills.d.ts +31 -0
  60. package/normalise/skills.d.ts.map +1 -0
  61. package/normalise/skills.js +116 -0
  62. package/normalise/skills.js.map +1 -0
  63. package/normalise/sub-agents.d.ts +44 -0
  64. package/normalise/sub-agents.d.ts.map +1 -0
  65. package/normalise/sub-agents.js +168 -0
  66. package/normalise/sub-agents.js.map +1 -0
  67. package/normalise/variables.d.ts +27 -0
  68. package/normalise/variables.d.ts.map +1 -0
  69. package/normalise/variables.js +155 -0
  70. package/normalise/variables.js.map +1 -0
  71. package/outcome.d.ts +46 -0
  72. package/outcome.d.ts.map +1 -0
  73. package/outcome.js +21 -0
  74. package/outcome.js.map +1 -0
  75. package/package.json +40 -0
  76. package/placeholders.d.ts +41 -0
  77. package/placeholders.d.ts.map +1 -0
  78. package/placeholders.js +68 -0
  79. package/placeholders.js.map +1 -0
  80. package/read-plugin-package.d.ts +19 -0
  81. package/read-plugin-package.d.ts.map +1 -0
  82. package/read-plugin-package.js +67 -0
  83. package/read-plugin-package.js.map +1 -0
  84. package/src/__test-utils__/directory-files.ts +29 -0
  85. package/src/__test-utils__/read.ts +55 -0
  86. package/src/__tests__/adversarial.test.ts +434 -0
  87. package/src/__tests__/detect.test.ts +133 -0
  88. package/src/__tests__/files.test.ts +119 -0
  89. package/src/__tests__/fixtures/cursor-plugins/NOTICE +22 -0
  90. package/src/__tests__/fixtures/cursor-plugins/advisor/.cursor-plugin/plugin.json +33 -0
  91. package/src/__tests__/fixtures/cursor-plugins/advisor/CHANGELOG.md +8 -0
  92. package/src/__tests__/fixtures/cursor-plugins/advisor/LICENSE +21 -0
  93. package/src/__tests__/fixtures/cursor-plugins/advisor/README.md +87 -0
  94. package/src/__tests__/fixtures/cursor-plugins/advisor/agents/advisor-subagent.md +48 -0
  95. package/src/__tests__/fixtures/cursor-plugins/advisor/assets/avatar.png +0 -0
  96. package/src/__tests__/fixtures/cursor-plugins/advisor/hooks/capture-response.sh +20 -0
  97. package/src/__tests__/fixtures/cursor-plugins/advisor/hooks/hooks.json +27 -0
  98. package/src/__tests__/fixtures/cursor-plugins/advisor/hooks/lib.sh +61 -0
  99. package/src/__tests__/fixtures/cursor-plugins/advisor/hooks/mark-pending.sh +27 -0
  100. package/src/__tests__/fixtures/cursor-plugins/advisor/hooks/record-consult.sh +41 -0
  101. package/src/__tests__/fixtures/cursor-plugins/advisor/hooks/stop-hook.sh +48 -0
  102. package/src/__tests__/fixtures/cursor-plugins/advisor/skills/advisor/SKILL.md +123 -0
  103. package/src/__tests__/fixtures/cursor-plugins/advisor/skills/advisor/references/briefing-template.md +44 -0
  104. package/src/__tests__/fixtures/cursor-plugins/github/.cursor-plugin/plugin.json +45 -0
  105. package/src/__tests__/fixtures/cursor-plugins/github/CHANGELOG.md +9 -0
  106. package/src/__tests__/fixtures/cursor-plugins/github/LICENSE +21 -0
  107. package/src/__tests__/fixtures/cursor-plugins/github/README.md +64 -0
  108. package/src/__tests__/fixtures/cursor-plugins/github/assets/logo.svg +0 -0
  109. package/src/__tests__/fixtures/cursor-plugins/github/mcp.json +11 -0
  110. package/src/__tests__/fixtures/cursor-plugins/playwright/.cursor-plugin/plugin.json +35 -0
  111. package/src/__tests__/fixtures/cursor-plugins/playwright/CHANGELOG.md +8 -0
  112. package/src/__tests__/fixtures/cursor-plugins/playwright/LICENSE +21 -0
  113. package/src/__tests__/fixtures/cursor-plugins/playwright/README.md +46 -0
  114. package/src/__tests__/fixtures/cursor-plugins/playwright/assets/logo.svg +0 -0
  115. package/src/__tests__/fixtures/cursor-plugins/playwright/mcp.json +8 -0
  116. package/src/__tests__/fixtures/cursor-plugins/salesforce/.cursor-plugin/plugin.json +48 -0
  117. package/src/__tests__/fixtures/cursor-plugins/salesforce/CHANGELOG.md +10 -0
  118. package/src/__tests__/fixtures/cursor-plugins/salesforce/LICENSE +21 -0
  119. package/src/__tests__/fixtures/cursor-plugins/salesforce/README.md +95 -0
  120. package/src/__tests__/fixtures/cursor-plugins/salesforce/assets/logo.svg +0 -0
  121. package/src/__tests__/fixtures/cursor-plugins/salesforce/mcp.json +12 -0
  122. package/src/__tests__/fixtures/cursor-plugins/thermos/.cursor-plugin/plugin.json +32 -0
  123. package/src/__tests__/fixtures/cursor-plugins/thermos/CHANGELOG.md +8 -0
  124. package/src/__tests__/fixtures/cursor-plugins/thermos/LICENSE +21 -0
  125. package/src/__tests__/fixtures/cursor-plugins/thermos/README.md +70 -0
  126. package/src/__tests__/fixtures/cursor-plugins/thermos/agents/thermo-nuclear-code-quality-review-subagent.md +23 -0
  127. package/src/__tests__/fixtures/cursor-plugins/thermos/agents/thermo-nuclear-review-subagent.md +28 -0
  128. package/src/__tests__/fixtures/cursor-plugins/thermos/assets/logo.png +0 -0
  129. package/src/__tests__/fixtures/cursor-plugins/thermos/skills/thermo-nuclear-code-quality-review/SKILL.md +192 -0
  130. package/src/__tests__/fixtures/cursor-plugins/thermos/skills/thermo-nuclear-review/SKILL.md +51 -0
  131. package/src/__tests__/fixtures/cursor-plugins/thermos/skills/thermos/SKILL.md +21 -0
  132. package/src/__tests__/fixtures/cursor-plugins/xero/.cursor-plugin/plugin.json +53 -0
  133. package/src/__tests__/fixtures/cursor-plugins/xero/CHANGELOG.md +9 -0
  134. package/src/__tests__/fixtures/cursor-plugins/xero/LICENSE +21 -0
  135. package/src/__tests__/fixtures/cursor-plugins/xero/README.md +79 -0
  136. package/src/__tests__/fixtures/cursor-plugins/xero/assets/logo.png +0 -0
  137. package/src/__tests__/fixtures/cursor-plugins/xero/mcp.json +16 -0
  138. package/src/__tests__/fixtures.test.ts +198 -0
  139. package/src/__tests__/mcp-servers.test.ts +120 -0
  140. package/src/__tests__/overlay-and-ignored.test.ts +65 -0
  141. package/src/__tests__/skills.test.ts +89 -0
  142. package/src/__tests__/sub-agents.test.ts +111 -0
  143. package/src/__tests__/variables.test.ts +94 -0
  144. package/src/detect.ts +189 -0
  145. package/src/dialects/claude.ts +90 -0
  146. package/src/dialects/codex.ts +24 -0
  147. package/src/dialects/cursor.ts +73 -0
  148. package/src/dialects/manifest.ts +237 -0
  149. package/src/dialects/open.ts +48 -0
  150. package/src/documents.ts +145 -0
  151. package/src/files.ts +213 -0
  152. package/src/frontmatter.ts +70 -0
  153. package/src/index.ts +59 -0
  154. package/src/messages.ts +206 -0
  155. package/src/normalise/ignored.ts +70 -0
  156. package/src/normalise/mcp-servers.ts +427 -0
  157. package/src/normalise/overlay.ts +71 -0
  158. package/src/normalise/skills.ts +126 -0
  159. package/src/normalise/sub-agents.ts +184 -0
  160. package/src/normalise/variables.ts +161 -0
  161. package/src/outcome.ts +122 -0
  162. package/src/placeholders.ts +74 -0
  163. package/src/read-plugin-package.ts +74 -0
  164. package/src/testing.ts +258 -0
  165. package/src/types.ts +189 -0
  166. package/testing.d.ts +106 -0
  167. package/testing.d.ts.map +1 -0
  168. package/testing.js +182 -0
  169. package/testing.js.map +1 -0
  170. package/types.d.ts +152 -0
  171. package/types.d.ts.map +1 -0
  172. package/types.js +19 -0
  173. package/types.js.map +1 -0
package/src/testing.ts ADDED
@@ -0,0 +1,258 @@
1
+ /**
2
+ * In-memory plugin builders for tests (exported as
3
+ * `@stigmer/plugin-package/testing`).
4
+ *
5
+ * One builder per dialect, each writing the exact file layout that
6
+ * dialect's tools produce: `openPlugin` a root `plugin.json` with the
7
+ * canonical `$schema` and an `mcp.json` beside it; `claudePlugin` a
8
+ * `.claude-plugin/plugin.json` with `.mcp.json`; `cursorPlugin` a
9
+ * `.cursor-plugin/plugin.json` declaring `./skills/`, `./agents/` and
10
+ * `./mcp.json` the way every published Cursor plugin does; `codexPlugin`
11
+ * the legacy `.codex-plugin/plugin.json` compatibility layout. The
12
+ * adversarial suite starts from a valid plugin and breaks exactly one
13
+ * thing with the mutators (`withFile`, `withoutFile`,
14
+ * `withManifestField`), so each test reads as "this plugin, minus this",
15
+ * and the consumers' tests (the CLI's, the server's) craft their fixtures
16
+ * from the same source so the shapes never drift apart.
17
+ *
18
+ * Builders return a path -> content map; `inMemoryPluginFiles` turns one
19
+ * into the `PluginFiles` the reader takes. Zipping a fixture for an
20
+ * archive-based consumer is `@stigmer/zip-structure/testing`'s job.
21
+ */
22
+
23
+ import { stringify as stringifyYaml } from "yaml";
24
+
25
+ import { inMemoryPluginFiles } from "./files.js";
26
+ import { AGENT_PLUGINS_MANIFEST_SCHEMA, AGENT_PLUGINS_MCP_SCHEMA, MANIFEST_LOCATIONS } from "./messages.js";
27
+
28
+ export { inMemoryPluginFiles };
29
+
30
+ /** A plugin as files: plugin-relative path -> content. */
31
+ export type PluginFixture = Map<string, string | Uint8Array>;
32
+
33
+ export interface SkillFixture {
34
+ /** The frontmatter `name`; also the directory name unless `dir` says otherwise. */
35
+ readonly name: string;
36
+ readonly description?: string;
37
+ /** Directory under `skills/` (or the declared skills root); defaults to `name`. */
38
+ readonly dir?: string;
39
+ /** Extra frontmatter fields, or `null` for a `SKILL.md` with no frontmatter at all. */
40
+ readonly frontmatter?: Readonly<Record<string, unknown>> | null;
41
+ readonly body?: string;
42
+ /** Extra files inside the skill directory, relative to it. */
43
+ readonly files?: Readonly<Record<string, string>>;
44
+ }
45
+
46
+ export interface AgentFixture {
47
+ /** File name under `agents/`, without `.md`. */
48
+ readonly file: string;
49
+ /** Frontmatter fields, or `null` for a bare prompt with no frontmatter. */
50
+ readonly frontmatter?: Readonly<Record<string, unknown>> | null;
51
+ readonly body?: string;
52
+ }
53
+
54
+ interface CommonFixture {
55
+ readonly name?: string;
56
+ readonly version?: string;
57
+ readonly description?: string;
58
+ /** Extra or overriding manifest fields, merged last. */
59
+ readonly manifest?: Readonly<Record<string, unknown>>;
60
+ readonly skills?: readonly SkillFixture[];
61
+ /** Extra files anywhere in the plugin. */
62
+ readonly files?: Readonly<Record<string, string | Uint8Array>>;
63
+ }
64
+
65
+ export interface OpenPluginFixture extends CommonFixture {
66
+ /** The manifest `$schema`; `null` omits it. Defaults to the canonical 1.0.0 identifier. */
67
+ readonly schema?: string | null;
68
+ /** The `mcpServers` map for `mcp.json`; absent means no `mcp.json`. */
69
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
70
+ /** The `mcp.json` `$schema`; `null` omits it. */
71
+ readonly mcpSchema?: string | null;
72
+ /** Extra top-level `mcp.json` fields. */
73
+ readonly mcpConfig?: Readonly<Record<string, unknown>>;
74
+ }
75
+
76
+ export interface ClaudePluginFixture extends CommonFixture {
77
+ readonly agents?: readonly AgentFixture[];
78
+ /** The `mcpServers` map for `.mcp.json`; absent means no `.mcp.json`. */
79
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
80
+ readonly userConfig?: Readonly<Record<string, unknown>>;
81
+ }
82
+
83
+ export interface CursorPluginFixture extends CommonFixture {
84
+ readonly agents?: readonly AgentFixture[];
85
+ /** The `mcpServers` map for `mcp.json`, declared as `"mcpServers": "./mcp.json"`. */
86
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
87
+ /** The `variables` JSON Schema: property name -> `{ type, title, description }`. */
88
+ readonly variables?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
89
+ readonly required?: readonly string[];
90
+ }
91
+
92
+ export interface CodexPluginFixture extends CommonFixture {
93
+ /** The `mcpServers` map for `.mcp.json`, declared as `"mcpServers": "./.mcp.json"`. */
94
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
95
+ /** The `apps` map for `.app.json`. */
96
+ readonly apps?: Readonly<Record<string, unknown>>;
97
+ }
98
+
99
+ const DEFAULT_NAME = "example";
100
+
101
+ /** An Agent Plugins 1.0.0 package. */
102
+ export function openPlugin(fixture: OpenPluginFixture = {}): PluginFixture {
103
+ const files: PluginFixture = new Map();
104
+ const manifest: Record<string, unknown> = {
105
+ ...(fixture.schema !== null && { $schema: fixture.schema ?? AGENT_PLUGINS_MANIFEST_SCHEMA }),
106
+ name: fixture.name ?? DEFAULT_NAME,
107
+ ...(fixture.version !== undefined && { version: fixture.version }),
108
+ ...(fixture.description !== undefined && { description: fixture.description }),
109
+ ...fixture.manifest,
110
+ };
111
+ files.set(MANIFEST_LOCATIONS["agent-plugins"], json(manifest));
112
+ if (fixture.mcpServers !== undefined) {
113
+ files.set(
114
+ "mcp.json",
115
+ json({
116
+ ...(fixture.mcpSchema !== null && { $schema: fixture.mcpSchema ?? AGENT_PLUGINS_MCP_SCHEMA }),
117
+ mcpServers: fixture.mcpServers,
118
+ ...fixture.mcpConfig,
119
+ }),
120
+ );
121
+ }
122
+ writeSkills(files, "skills", fixture.skills);
123
+ writeExtra(files, fixture.files);
124
+ return files;
125
+ }
126
+
127
+ /** A Claude Code plugin. */
128
+ export function claudePlugin(fixture: ClaudePluginFixture = {}): PluginFixture {
129
+ const files: PluginFixture = new Map();
130
+ const manifest: Record<string, unknown> = {
131
+ name: fixture.name ?? DEFAULT_NAME,
132
+ ...(fixture.version !== undefined && { version: fixture.version }),
133
+ ...(fixture.description !== undefined && { description: fixture.description }),
134
+ ...(fixture.userConfig !== undefined && { userConfig: fixture.userConfig }),
135
+ ...fixture.manifest,
136
+ };
137
+ files.set(MANIFEST_LOCATIONS.claude, json(manifest));
138
+ if (fixture.mcpServers !== undefined) files.set(".mcp.json", json({ mcpServers: fixture.mcpServers }));
139
+ writeSkills(files, "skills", fixture.skills);
140
+ writeAgents(files, "agents", fixture.agents);
141
+ writeExtra(files, fixture.files);
142
+ return files;
143
+ }
144
+
145
+ /** A Cursor plugin, declaring its components the way the published catalogue does. */
146
+ export function cursorPlugin(fixture: CursorPluginFixture = {}): PluginFixture {
147
+ const files: PluginFixture = new Map();
148
+ const manifest: Record<string, unknown> = {
149
+ name: fixture.name ?? DEFAULT_NAME,
150
+ ...(fixture.version !== undefined && { version: fixture.version }),
151
+ ...(fixture.description !== undefined && { description: fixture.description }),
152
+ ...(fixture.skills !== undefined && { skills: "./skills/" }),
153
+ ...(fixture.agents !== undefined && { agents: "./agents/" }),
154
+ ...(fixture.variables !== undefined && {
155
+ variables: {
156
+ type: "object",
157
+ properties: fixture.variables,
158
+ ...(fixture.required !== undefined && { required: fixture.required }),
159
+ },
160
+ }),
161
+ ...(fixture.mcpServers !== undefined && { mcpServers: "./mcp.json" }),
162
+ ...fixture.manifest,
163
+ };
164
+ files.set(MANIFEST_LOCATIONS.cursor, json(manifest));
165
+ if (fixture.mcpServers !== undefined) files.set("mcp.json", json({ mcpServers: fixture.mcpServers }));
166
+ writeSkills(files, "skills", fixture.skills);
167
+ writeAgents(files, "agents", fixture.agents);
168
+ writeExtra(files, fixture.files);
169
+ return files;
170
+ }
171
+
172
+ /** The legacy Codex compatibility layout (`.codex-plugin/plugin.json`, `.mcp.json`, `.app.json`). */
173
+ export function codexPlugin(fixture: CodexPluginFixture = {}): PluginFixture {
174
+ const files: PluginFixture = new Map();
175
+ const manifest: Record<string, unknown> = {
176
+ name: fixture.name ?? DEFAULT_NAME,
177
+ ...(fixture.version !== undefined && { version: fixture.version }),
178
+ ...(fixture.description !== undefined && { description: fixture.description }),
179
+ ...(fixture.skills !== undefined && { skills: "./skills/" }),
180
+ ...(fixture.mcpServers !== undefined && { mcpServers: "./.mcp.json" }),
181
+ ...(fixture.apps !== undefined && { apps: "./.app.json" }),
182
+ ...fixture.manifest,
183
+ };
184
+ files.set(MANIFEST_LOCATIONS.codex, json(manifest));
185
+ if (fixture.mcpServers !== undefined) files.set(".mcp.json", json({ mcpServers: fixture.mcpServers }));
186
+ if (fixture.apps !== undefined) files.set(".app.json", json({ apps: fixture.apps }));
187
+ writeSkills(files, "skills", fixture.skills);
188
+ writeExtra(files, fixture.files);
189
+ return files;
190
+ }
191
+
192
+ /** A copy of `files` with `path` set to `content`. */
193
+ export function withFile(files: PluginFixture, path: string, content: string | Uint8Array): PluginFixture {
194
+ const copy = new Map(files);
195
+ copy.set(path, content);
196
+ return copy;
197
+ }
198
+
199
+ /** A copy of `files` without `path`. */
200
+ export function withoutFile(files: PluginFixture, path: string): PluginFixture {
201
+ const copy = new Map(files);
202
+ copy.delete(path);
203
+ return copy;
204
+ }
205
+
206
+ /**
207
+ * A copy of `files` with one top-level field of the JSON document at
208
+ * `manifestPath` set (or removed, when `value` is `undefined`).
209
+ */
210
+ export function withManifestField(files: PluginFixture, manifestPath: string, field: string, value: unknown): PluginFixture {
211
+ const current = files.get(manifestPath);
212
+ if (current === undefined) throw new Error(`fixture has no document at '${manifestPath}'`);
213
+ const text = typeof current === "string" ? current : new TextDecoder().decode(current);
214
+ const document = JSON.parse(text) as Record<string, unknown>;
215
+ if (value === undefined) delete document[field];
216
+ else document[field] = value;
217
+ return withFile(files, manifestPath, json(document));
218
+ }
219
+
220
+ /** A `SKILL.md` document from a fixture description. */
221
+ export function skillMarkdown(skill: SkillFixture): string {
222
+ const body = skill.body ?? `# ${skill.name}\n\nInstructions for the ${skill.name} skill.\n`;
223
+ if (skill.frontmatter === null) return body;
224
+ const frontmatter = {
225
+ name: skill.name,
226
+ ...(skill.description !== undefined && { description: skill.description }),
227
+ ...skill.frontmatter,
228
+ };
229
+ return `---\n${stringifyYaml(frontmatter)}---\n${body}`;
230
+ }
231
+
232
+ /** A sub-agent document from a fixture description. */
233
+ export function agentMarkdown(agent: AgentFixture): string {
234
+ const body = agent.body ?? `You are the ${agent.file} sub-agent. Do the task thoroughly and report back.\n`;
235
+ if (agent.frontmatter === null) return body;
236
+ const frontmatter = { name: agent.file, ...agent.frontmatter };
237
+ return `---\n${stringifyYaml(frontmatter)}---\n${body}`;
238
+ }
239
+
240
+ function writeSkills(files: PluginFixture, root: string, skills: readonly SkillFixture[] | undefined): void {
241
+ for (const skill of skills ?? []) {
242
+ const dir = `${root}/${skill.dir ?? skill.name}`;
243
+ files.set(`${dir}/SKILL.md`, skillMarkdown(skill));
244
+ for (const [path, content] of Object.entries(skill.files ?? {})) files.set(`${dir}/${path}`, content);
245
+ }
246
+ }
247
+
248
+ function writeAgents(files: PluginFixture, root: string, agents: readonly AgentFixture[] | undefined): void {
249
+ for (const agent of agents ?? []) files.set(`${root}/${agent.file}.md`, agentMarkdown(agent));
250
+ }
251
+
252
+ function writeExtra(files: PluginFixture, extra: Readonly<Record<string, string | Uint8Array>> | undefined): void {
253
+ for (const [path, content] of Object.entries(extra ?? {})) files.set(path, content);
254
+ }
255
+
256
+ function json(value: unknown): string {
257
+ return `${JSON.stringify(value, null, 2)}\n`;
258
+ }
package/src/types.ts ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * The normalised description of a plugin: what Stigmer would install from a
3
+ * package, whatever dialect the package was written in.
4
+ *
5
+ * These are the library's own plain types, deliberately not the protos. The
6
+ * library describes the plugin FORMAT; the server maps this description onto
7
+ * resources when it installs, and the CLI validates offline without pulling
8
+ * the resource schemas in for a parse. Field names still mirror the protos
9
+ * (`SubAgent.name/description/instructions/model_override`,
10
+ * `EnvVarDeclaration.is_secret/description/optional`, the
11
+ * `McpServerSpec` `stdio`/`http` variants) so the server's mapping is one
12
+ * function per kind and a proto rename is a visible edit here.
13
+ *
14
+ * Every path in this description is plugin-relative, POSIX, with no leading
15
+ * `./`: one path vocabulary for the whole package, whichever reader produced
16
+ * the files.
17
+ */
18
+
19
+ /** The manifest that gave the plugin its identity. */
20
+ export type PluginDialect = "agent-plugins" | "claude" | "cursor" | "codex";
21
+
22
+ export interface PluginAuthor {
23
+ readonly name?: string;
24
+ readonly email?: string;
25
+ readonly url?: string;
26
+ }
27
+
28
+ /**
29
+ * One skill: a directory holding `SKILL.md`. `files` lists every file under
30
+ * `dir` (plugin-relative), so an installer can build the skill's own archive
31
+ * without walking the package again; `SKILL.md` is among them. For a plugin
32
+ * that IS a single skill (a root `SKILL.md`, the Claude Code layout) `dir` is
33
+ * the empty string and `files` is the whole package.
34
+ */
35
+ export interface PluginSkill {
36
+ readonly name: string;
37
+ readonly description?: string;
38
+ readonly dir: string;
39
+ readonly files: readonly string[];
40
+ }
41
+
42
+ /**
43
+ * One MCP server in the shape `McpServerSpec` takes. `env` is the list of
44
+ * variable NAMES the server references (in headers, arguments or its stdio
45
+ * environment), which is what `McpServerSpec.env` declares; values never
46
+ * appear anywhere in this library.
47
+ */
48
+ export type PluginMcpServer =
49
+ | {
50
+ readonly name: string;
51
+ readonly transport: "http";
52
+ readonly url: string;
53
+ readonly headers: Readonly<Record<string, string>>;
54
+ readonly env: readonly string[];
55
+ }
56
+ | {
57
+ readonly name: string;
58
+ readonly transport: "stdio";
59
+ readonly command: string;
60
+ readonly args: readonly string[];
61
+ readonly env: readonly string[];
62
+ };
63
+
64
+ /**
65
+ * A dialect's `model` value classified into an alias the server resolves
66
+ * against its model registry. The library never names a Stigmer model id:
67
+ * the runner drops a sub-agent whose `model_override` is unregistered, so an
68
+ * id the parser could not verify would silently remove sub-agents at run
69
+ * time. `inherit` and an absent `model` both mean "no override"; `unknown`
70
+ * carries the raw text for the server's warning.
71
+ */
72
+ export type ModelAlias = "fast" | "sonnet" | "opus" | "haiku" | "inherit" | "unknown";
73
+
74
+ export interface ModelHint {
75
+ readonly raw: string;
76
+ readonly alias: ModelAlias;
77
+ }
78
+
79
+ /** One sub-agent file (`agents/*.md`) in the shape `SubAgent` takes. */
80
+ export interface PluginSubAgent {
81
+ readonly name: string;
82
+ readonly description?: string;
83
+ readonly instructions: string;
84
+ /** Plugin skill names this sub-agent asked for (Claude `skills:`), resolved to `skill_refs` by the installer. */
85
+ readonly skillNames: readonly string[];
86
+ readonly modelHint?: ModelHint;
87
+ /** The agent file, for messages that point at it. */
88
+ readonly path: string;
89
+ }
90
+
91
+ /**
92
+ * One variable in the shape `EnvVarDeclaration` takes. `declaredBy` says
93
+ * where the declaration came from: a Cursor `variables` schema, a Claude
94
+ * `userConfig` entry, or inference from an undeclared `${VAR}` reference,
95
+ * which the library declares as a required secret because the runner hands a
96
+ * server only the variables its spec declares.
97
+ */
98
+ export interface PluginVariable {
99
+ readonly name: string;
100
+ readonly description?: string;
101
+ readonly isSecret: boolean;
102
+ readonly optional: boolean;
103
+ readonly declaredBy: "cursor" | "claude" | "inferred";
104
+ }
105
+
106
+ /**
107
+ * A document under `ai.stigmer/`, handed over as bytes. The library locates
108
+ * these and checks they name things the plugin declares; parsing them into
109
+ * resources is the installer's, where the resource schemas live.
110
+ */
111
+ export interface OverlayDocument {
112
+ readonly path: string;
113
+ readonly bytes: Uint8Array;
114
+ }
115
+
116
+ export interface OverlayNamedDocument extends OverlayDocument {
117
+ /** The file stem, the workflow's name. */
118
+ readonly name: string;
119
+ }
120
+
121
+ export interface OverlayServerDocument extends OverlayDocument {
122
+ /** The MCP server (a `mcpServers` key) this overlay layers over. */
123
+ readonly server: string;
124
+ }
125
+
126
+ export interface StigmerOverlay {
127
+ /** `ai.stigmer/agent.yaml`: the Agent that replaces the composed default. */
128
+ readonly agent?: OverlayDocument;
129
+ /** `ai.stigmer/workflows/<name>.yaml`. */
130
+ readonly workflows: readonly OverlayNamedDocument[];
131
+ /** `ai.stigmer/mcp-servers/<server>.yaml`: the richer `McpServer` overlay. */
132
+ readonly mcpServers: readonly OverlayServerDocument[];
133
+ }
134
+
135
+ /**
136
+ * The component kinds Stigmer reads past without carrying. Recorded once per
137
+ * component (a directory, a file or a manifest field), never per file, so
138
+ * an author sees "hooks/ is not installed" once.
139
+ */
140
+ export type IgnoredComponentKind =
141
+ | "agents"
142
+ | "apps"
143
+ | "assets"
144
+ | "bin"
145
+ | "canvases"
146
+ | "channels"
147
+ | "commands"
148
+ | "default-enabled"
149
+ | "dependencies"
150
+ | "evals"
151
+ | "extension"
152
+ | "hooks"
153
+ | "logo"
154
+ | "lsp-servers"
155
+ | "min-client-versions"
156
+ | "monitors"
157
+ | "output-styles"
158
+ | "rules"
159
+ | "settings"
160
+ | "themes"
161
+ | "workflows";
162
+
163
+ export interface IgnoredComponent {
164
+ readonly kind: IgnoredComponentKind;
165
+ /** The directory, file, or `<manifest path>#<field>` the component came from. */
166
+ readonly path: string;
167
+ }
168
+
169
+ /** What Stigmer would install from the package. */
170
+ export interface PluginPackage {
171
+ readonly name: string;
172
+ /** As written; Semantic Versioning is recommended by the format, not enforced. */
173
+ readonly version?: string;
174
+ readonly description?: string;
175
+ readonly author?: PluginAuthor;
176
+ readonly homepage?: string;
177
+ readonly repository?: string;
178
+ readonly license?: string;
179
+ readonly keywords: readonly string[];
180
+ readonly dialect: PluginDialect;
181
+ /** Every manifest the reader consulted, identity first. */
182
+ readonly manifestsFound: readonly string[];
183
+ readonly skills: readonly PluginSkill[];
184
+ readonly mcpServers: readonly PluginMcpServer[];
185
+ readonly subAgents: readonly PluginSubAgent[];
186
+ readonly variables: readonly PluginVariable[];
187
+ readonly overlay: StigmerOverlay;
188
+ readonly ignored: readonly IgnoredComponent[];
189
+ }
package/testing.d.ts ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * In-memory plugin builders for tests (exported as
3
+ * `@stigmer/plugin-package/testing`).
4
+ *
5
+ * One builder per dialect, each writing the exact file layout that
6
+ * dialect's tools produce: `openPlugin` a root `plugin.json` with the
7
+ * canonical `$schema` and an `mcp.json` beside it; `claudePlugin` a
8
+ * `.claude-plugin/plugin.json` with `.mcp.json`; `cursorPlugin` a
9
+ * `.cursor-plugin/plugin.json` declaring `./skills/`, `./agents/` and
10
+ * `./mcp.json` the way every published Cursor plugin does; `codexPlugin`
11
+ * the legacy `.codex-plugin/plugin.json` compatibility layout. The
12
+ * adversarial suite starts from a valid plugin and breaks exactly one
13
+ * thing with the mutators (`withFile`, `withoutFile`,
14
+ * `withManifestField`), so each test reads as "this plugin, minus this",
15
+ * and the consumers' tests (the CLI's, the server's) craft their fixtures
16
+ * from the same source so the shapes never drift apart.
17
+ *
18
+ * Builders return a path -> content map; `inMemoryPluginFiles` turns one
19
+ * into the `PluginFiles` the reader takes. Zipping a fixture for an
20
+ * archive-based consumer is `@stigmer/zip-structure/testing`'s job.
21
+ */
22
+ import { inMemoryPluginFiles } from "./files.js";
23
+ export { inMemoryPluginFiles };
24
+ /** A plugin as files: plugin-relative path -> content. */
25
+ export type PluginFixture = Map<string, string | Uint8Array>;
26
+ export interface SkillFixture {
27
+ /** The frontmatter `name`; also the directory name unless `dir` says otherwise. */
28
+ readonly name: string;
29
+ readonly description?: string;
30
+ /** Directory under `skills/` (or the declared skills root); defaults to `name`. */
31
+ readonly dir?: string;
32
+ /** Extra frontmatter fields, or `null` for a `SKILL.md` with no frontmatter at all. */
33
+ readonly frontmatter?: Readonly<Record<string, unknown>> | null;
34
+ readonly body?: string;
35
+ /** Extra files inside the skill directory, relative to it. */
36
+ readonly files?: Readonly<Record<string, string>>;
37
+ }
38
+ export interface AgentFixture {
39
+ /** File name under `agents/`, without `.md`. */
40
+ readonly file: string;
41
+ /** Frontmatter fields, or `null` for a bare prompt with no frontmatter. */
42
+ readonly frontmatter?: Readonly<Record<string, unknown>> | null;
43
+ readonly body?: string;
44
+ }
45
+ interface CommonFixture {
46
+ readonly name?: string;
47
+ readonly version?: string;
48
+ readonly description?: string;
49
+ /** Extra or overriding manifest fields, merged last. */
50
+ readonly manifest?: Readonly<Record<string, unknown>>;
51
+ readonly skills?: readonly SkillFixture[];
52
+ /** Extra files anywhere in the plugin. */
53
+ readonly files?: Readonly<Record<string, string | Uint8Array>>;
54
+ }
55
+ export interface OpenPluginFixture extends CommonFixture {
56
+ /** The manifest `$schema`; `null` omits it. Defaults to the canonical 1.0.0 identifier. */
57
+ readonly schema?: string | null;
58
+ /** The `mcpServers` map for `mcp.json`; absent means no `mcp.json`. */
59
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
60
+ /** The `mcp.json` `$schema`; `null` omits it. */
61
+ readonly mcpSchema?: string | null;
62
+ /** Extra top-level `mcp.json` fields. */
63
+ readonly mcpConfig?: Readonly<Record<string, unknown>>;
64
+ }
65
+ export interface ClaudePluginFixture extends CommonFixture {
66
+ readonly agents?: readonly AgentFixture[];
67
+ /** The `mcpServers` map for `.mcp.json`; absent means no `.mcp.json`. */
68
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
69
+ readonly userConfig?: Readonly<Record<string, unknown>>;
70
+ }
71
+ export interface CursorPluginFixture extends CommonFixture {
72
+ readonly agents?: readonly AgentFixture[];
73
+ /** The `mcpServers` map for `mcp.json`, declared as `"mcpServers": "./mcp.json"`. */
74
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
75
+ /** The `variables` JSON Schema: property name -> `{ type, title, description }`. */
76
+ readonly variables?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
77
+ readonly required?: readonly string[];
78
+ }
79
+ export interface CodexPluginFixture extends CommonFixture {
80
+ /** The `mcpServers` map for `.mcp.json`, declared as `"mcpServers": "./.mcp.json"`. */
81
+ readonly mcpServers?: Readonly<Record<string, unknown>>;
82
+ /** The `apps` map for `.app.json`. */
83
+ readonly apps?: Readonly<Record<string, unknown>>;
84
+ }
85
+ /** An Agent Plugins 1.0.0 package. */
86
+ export declare function openPlugin(fixture?: OpenPluginFixture): PluginFixture;
87
+ /** A Claude Code plugin. */
88
+ export declare function claudePlugin(fixture?: ClaudePluginFixture): PluginFixture;
89
+ /** A Cursor plugin, declaring its components the way the published catalogue does. */
90
+ export declare function cursorPlugin(fixture?: CursorPluginFixture): PluginFixture;
91
+ /** The legacy Codex compatibility layout (`.codex-plugin/plugin.json`, `.mcp.json`, `.app.json`). */
92
+ export declare function codexPlugin(fixture?: CodexPluginFixture): PluginFixture;
93
+ /** A copy of `files` with `path` set to `content`. */
94
+ export declare function withFile(files: PluginFixture, path: string, content: string | Uint8Array): PluginFixture;
95
+ /** A copy of `files` without `path`. */
96
+ export declare function withoutFile(files: PluginFixture, path: string): PluginFixture;
97
+ /**
98
+ * A copy of `files` with one top-level field of the JSON document at
99
+ * `manifestPath` set (or removed, when `value` is `undefined`).
100
+ */
101
+ export declare function withManifestField(files: PluginFixture, manifestPath: string, field: string, value: unknown): PluginFixture;
102
+ /** A `SKILL.md` document from a fixture description. */
103
+ export declare function skillMarkdown(skill: SkillFixture): string;
104
+ /** A sub-agent document from a fixture description. */
105
+ export declare function agentMarkdown(agent: AgentFixture): string;
106
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}