@ai-plugin-marketplace/core 0.1.0

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 (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/config.d.ts +47 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +38 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/core.d.ts +291 -0
  8. package/dist/index.d.ts +15 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +13 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/pipeline/build.d.ts +90 -0
  13. package/dist/pipeline/build.d.ts.map +1 -0
  14. package/dist/pipeline/build.js +224 -0
  15. package/dist/pipeline/build.js.map +1 -0
  16. package/dist/pipeline/discover.d.ts +37 -0
  17. package/dist/pipeline/discover.d.ts.map +1 -0
  18. package/dist/pipeline/discover.js +71 -0
  19. package/dist/pipeline/discover.js.map +1 -0
  20. package/dist/pipeline/init-template.d.ts +33 -0
  21. package/dist/pipeline/init-template.d.ts.map +1 -0
  22. package/dist/pipeline/init-template.js +142 -0
  23. package/dist/pipeline/init-template.js.map +1 -0
  24. package/dist/pipeline/init.d.ts +39 -0
  25. package/dist/pipeline/init.d.ts.map +1 -0
  26. package/dist/pipeline/init.js +84 -0
  27. package/dist/pipeline/init.js.map +1 -0
  28. package/dist/pipeline/load-config.d.ts +47 -0
  29. package/dist/pipeline/load-config.d.ts.map +1 -0
  30. package/dist/pipeline/load-config.js +106 -0
  31. package/dist/pipeline/load-config.js.map +1 -0
  32. package/dist/pipeline/operations.d.ts +70 -0
  33. package/dist/pipeline/operations.d.ts.map +1 -0
  34. package/dist/pipeline/operations.js +100 -0
  35. package/dist/pipeline/operations.js.map +1 -0
  36. package/dist/pipeline/scaffold.d.ts +105 -0
  37. package/dist/pipeline/scaffold.d.ts.map +1 -0
  38. package/dist/pipeline/scaffold.js +422 -0
  39. package/dist/pipeline/scaffold.js.map +1 -0
  40. package/dist/pipeline/sentinel.d.ts +127 -0
  41. package/dist/pipeline/sentinel.d.ts.map +1 -0
  42. package/dist/pipeline/sentinel.js +263 -0
  43. package/dist/pipeline/sentinel.js.map +1 -0
  44. package/dist/pipeline/types.d.ts +178 -0
  45. package/dist/pipeline/types.d.ts.map +1 -0
  46. package/dist/pipeline/types.js +26 -0
  47. package/dist/pipeline/types.js.map +1 -0
  48. package/dist/pipeline/validate.d.ts +90 -0
  49. package/dist/pipeline/validate.d.ts.map +1 -0
  50. package/dist/pipeline/validate.js +617 -0
  51. package/dist/pipeline/validate.js.map +1 -0
  52. package/dist/targets/claude/scaffold.d.ts +32 -0
  53. package/dist/targets/claude/scaffold.d.ts.map +1 -0
  54. package/dist/targets/claude/scaffold.js +48 -0
  55. package/dist/targets/claude/scaffold.js.map +1 -0
  56. package/dist/targets/claude/schemas.d.ts +119 -0
  57. package/dist/targets/claude/schemas.d.ts.map +1 -0
  58. package/dist/targets/claude/schemas.js +204 -0
  59. package/dist/targets/claude/schemas.js.map +1 -0
  60. package/dist/targets/claude/transform.d.ts +40 -0
  61. package/dist/targets/claude/transform.d.ts.map +1 -0
  62. package/dist/targets/claude/transform.js +48 -0
  63. package/dist/targets/claude/transform.js.map +1 -0
  64. package/dist/targets/claude/validate.d.ts +25 -0
  65. package/dist/targets/claude/validate.d.ts.map +1 -0
  66. package/dist/targets/claude/validate.js +263 -0
  67. package/dist/targets/claude/validate.js.map +1 -0
  68. package/dist/targets/cursor/scaffold.d.ts +29 -0
  69. package/dist/targets/cursor/scaffold.d.ts.map +1 -0
  70. package/dist/targets/cursor/scaffold.js +45 -0
  71. package/dist/targets/cursor/scaffold.js.map +1 -0
  72. package/dist/targets/cursor/schemas.d.ts +49 -0
  73. package/dist/targets/cursor/schemas.d.ts.map +1 -0
  74. package/dist/targets/cursor/schemas.js +125 -0
  75. package/dist/targets/cursor/schemas.js.map +1 -0
  76. package/dist/targets/cursor/validate.d.ts +28 -0
  77. package/dist/targets/cursor/validate.d.ts.map +1 -0
  78. package/dist/targets/cursor/validate.js +181 -0
  79. package/dist/targets/cursor/validate.js.map +1 -0
  80. package/dist/targets/gemini/bundle.d.ts +25 -0
  81. package/dist/targets/gemini/bundle.d.ts.map +1 -0
  82. package/dist/targets/gemini/bundle.js +149 -0
  83. package/dist/targets/gemini/bundle.js.map +1 -0
  84. package/dist/targets/gemini/scaffold.d.ts +28 -0
  85. package/dist/targets/gemini/scaffold.d.ts.map +1 -0
  86. package/dist/targets/gemini/scaffold.js +57 -0
  87. package/dist/targets/gemini/scaffold.js.map +1 -0
  88. package/dist/targets/gemini/schemas.d.ts +53 -0
  89. package/dist/targets/gemini/schemas.d.ts.map +1 -0
  90. package/dist/targets/gemini/schemas.js +72 -0
  91. package/dist/targets/gemini/schemas.js.map +1 -0
  92. package/dist/targets/gemini/transform.d.ts +106 -0
  93. package/dist/targets/gemini/transform.d.ts.map +1 -0
  94. package/dist/targets/gemini/transform.js +137 -0
  95. package/dist/targets/gemini/transform.js.map +1 -0
  96. package/dist/targets/gemini/validate.d.ts +26 -0
  97. package/dist/targets/gemini/validate.d.ts.map +1 -0
  98. package/dist/targets/gemini/validate.js +146 -0
  99. package/dist/targets/gemini/validate.js.map +1 -0
  100. package/dist/targets/kiro/bundle.d.ts +32 -0
  101. package/dist/targets/kiro/bundle.d.ts.map +1 -0
  102. package/dist/targets/kiro/bundle.js +106 -0
  103. package/dist/targets/kiro/bundle.js.map +1 -0
  104. package/dist/targets/kiro/scaffold.d.ts +28 -0
  105. package/dist/targets/kiro/scaffold.d.ts.map +1 -0
  106. package/dist/targets/kiro/scaffold.js +55 -0
  107. package/dist/targets/kiro/scaffold.js.map +1 -0
  108. package/dist/targets/kiro/schemas.d.ts +100 -0
  109. package/dist/targets/kiro/schemas.d.ts.map +1 -0
  110. package/dist/targets/kiro/schemas.js +147 -0
  111. package/dist/targets/kiro/schemas.js.map +1 -0
  112. package/dist/targets/kiro/transform.d.ts +53 -0
  113. package/dist/targets/kiro/transform.d.ts.map +1 -0
  114. package/dist/targets/kiro/transform.js +113 -0
  115. package/dist/targets/kiro/transform.js.map +1 -0
  116. package/dist/targets/kiro/validate.d.ts +36 -0
  117. package/dist/targets/kiro/validate.d.ts.map +1 -0
  118. package/dist/targets/kiro/validate.js +232 -0
  119. package/dist/targets/kiro/validate.js.map +1 -0
  120. package/dist/targets/scaffold-kit.d.ts +56 -0
  121. package/dist/targets/scaffold-kit.d.ts.map +1 -0
  122. package/dist/targets/scaffold-kit.js +33 -0
  123. package/dist/targets/scaffold-kit.js.map +1 -0
  124. package/dist/targets/vercel/scaffold.d.ts +34 -0
  125. package/dist/targets/vercel/scaffold.d.ts.map +1 -0
  126. package/dist/targets/vercel/scaffold.js +58 -0
  127. package/dist/targets/vercel/scaffold.js.map +1 -0
  128. package/dist/targets/vercel/schemas.d.ts +42 -0
  129. package/dist/targets/vercel/schemas.d.ts.map +1 -0
  130. package/dist/targets/vercel/schemas.js +69 -0
  131. package/dist/targets/vercel/schemas.js.map +1 -0
  132. package/dist/targets/vercel/validate.d.ts +28 -0
  133. package/dist/targets/vercel/validate.d.ts.map +1 -0
  134. package/dist/targets/vercel/validate.js +180 -0
  135. package/dist/targets/vercel/validate.js.map +1 -0
  136. package/package.json +50 -0
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Pipeline orchestration for scaffolding and compatibility-assist (§6.4).
3
+ *
4
+ * Three orchestrators perform filesystem I/O around the per-target *pure* scaffold functions:
5
+ *
6
+ * - `runScaffold` — create a brand-new plugin (the `aipm scaffold` surface).
7
+ * - `runAddTarget` — add one target's skeleton to an existing plugin (`aipm add-target`).
8
+ * - `runCheckSupport` — report declared-but-incomplete and plausibly-addable targets
9
+ * (`aipm check-support`).
10
+ *
11
+ * Per §3.4/§12.4 the pipeline layer is the orchestration boundary and MAY import each target's
12
+ * `scaffold.ts`; the per-target scaffold modules themselves never import one another.
13
+ *
14
+ * Envelope reading (§6.1): loading and executing a TypeScript `aipm.config.ts` from disk is the
15
+ * build orchestrator's job and is not yet built. `runCheckSupport`/`runAddTarget` therefore read
16
+ * the declared targets *pragmatically* — by extracting the `targets: [...]` array literal out of
17
+ * the config source text (see `parseDeclaredTargets`). This tolerates the canonical
18
+ * `defineConfig({ ... })` form this module emits and common hand-authored variants, but it is a
19
+ * lexical heuristic, not a TS evaluation. Documented limitation: a config that computes `targets`
20
+ * dynamically (spread, variable reference, conditional) is not understood and yields an error.
21
+ *
22
+ * @see docs/specs/architecture.md §6 (support envelope)
23
+ * @see docs/specs/architecture.md §6.4 (compatibility-assist tooling)
24
+ * @see docs/specs/architecture.md §10.1 (validation contract — artifact tables reused here)
25
+ * @see docs/specs/architecture.md §12.5 (per-target scaffold.ts)
26
+ */
27
+ import * as fs from 'node:fs';
28
+ import * as path from 'node:path';
29
+ import { scaffoldClaudeFiles } from '../targets/claude/scaffold.js';
30
+ import { scaffoldCursorFiles } from '../targets/cursor/scaffold.js';
31
+ import { scaffoldGeminiFiles } from '../targets/gemini/scaffold.js';
32
+ import { scaffoldKiroFiles } from '../targets/kiro/scaffold.js';
33
+ import { scaffoldVercelFiles } from '../targets/vercel/scaffold.js';
34
+ import { SCHEMA_VERSION } from '../targets/scaffold-kit.js';
35
+ import { TARGET_IDS } from './types.js';
36
+ import { TARGET_MIN_REQUIRED } from './validate.js';
37
+ // ---------------------------------------------------------------------------
38
+ // Per-target scaffold dispatch
39
+ // ---------------------------------------------------------------------------
40
+ /** Map a target ID to its pure scaffold function. */
41
+ const TARGET_SCAFFOLDERS = {
42
+ claude: scaffoldClaudeFiles,
43
+ cursor: scaffoldCursorFiles,
44
+ gemini: scaffoldGeminiFiles,
45
+ kiro: scaffoldKiroFiles,
46
+ vercel: scaffoldVercelFiles,
47
+ };
48
+ const md = String.raw;
49
+ const ts = String.raw;
50
+ // ---------------------------------------------------------------------------
51
+ // Name validation (mirrors the slug rules enforced by the manifest schemas)
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Validate a plugin name against the canonical slug rules shared by every manifest schema:
55
+ * lowercase, starts with a letter, `[a-z0-9-]` only, no consecutive hyphens, no trailing hyphen.
56
+ *
57
+ * @throws Error with a clear message when the name is invalid.
58
+ */
59
+ export function validatePluginName(name) {
60
+ if (name === '') {
61
+ throw new Error('Plugin name is required.');
62
+ }
63
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
64
+ throw new Error(`Invalid plugin name: "${name}". Names must be lowercase, start with a letter, and contain only letters, digits, and hyphens.`);
65
+ }
66
+ if (name.includes('--')) {
67
+ throw new Error(`Invalid plugin name: "${name}". Names must not contain consecutive hyphens.`);
68
+ }
69
+ if (name.endsWith('-')) {
70
+ throw new Error(`Invalid plugin name: "${name}". Names must not end with a hyphen.`);
71
+ }
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // aipm.config.ts generation + parsing
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Render the canonical `aipm.config.ts` source for a plugin with the given targets.
78
+ *
79
+ * Emits a `defineConfig({ version, targets })` literal importing from the public package root
80
+ * (§8.1 — the only public subpath). Deterministic: stable target ordering, no timestamps.
81
+ */
82
+ export function renderAipmConfig(targets) {
83
+ const ordered = orderTargets(targets);
84
+ const targetList = ordered.map((t) => `'${t}'`).join(', ');
85
+ return ts `import { defineConfig } from '@ai-plugin-marketplace/core';
86
+
87
+ export default defineConfig({
88
+ version: '${SCHEMA_VERSION}',
89
+ targets: [${targetList}],
90
+ });
91
+ `;
92
+ }
93
+ /**
94
+ * Extract the declared targets from `aipm.config.ts` source text.
95
+ *
96
+ * Lexical heuristic (see module doc): finds the first `targets:` key and parses the immediately
97
+ * following `[...]` array of single/double-quoted string literals. Only IDs in `TARGET_IDS` are
98
+ * returned; unknown IDs are ignored (the shape validator surfaces those separately).
99
+ *
100
+ * @returns Declared target IDs in canonical order, deduplicated.
101
+ * @throws Error when no `targets: [...]` array literal can be located.
102
+ */
103
+ export function parseDeclaredTargets(configSource) {
104
+ // Match `targets` followed by optional whitespace, a colon, then a bracketed list.
105
+ const match = /targets\s*:\s*\[([^\]]*)\]/.exec(configSource);
106
+ if (!match) {
107
+ throw new Error('Could not locate a `targets: [...]` array literal in aipm.config.ts. ' +
108
+ 'Dynamically-computed targets are not supported by the lexical envelope reader.');
109
+ }
110
+ const inner = match[1] ?? '';
111
+ const ids = [...inner.matchAll(/['"]([^'"]+)['"]/g)].map((m) => m[1] ?? '');
112
+ const known = new Set(TARGET_IDS);
113
+ const declared = ids.filter((id) => known.has(id));
114
+ return orderTargets([...new Set(declared)]);
115
+ }
116
+ /** Order a target set by the canonical `TARGET_IDS` ordering. Deterministic output. */
117
+ function orderTargets(targets) {
118
+ const present = new Set(targets);
119
+ return TARGET_IDS.filter((t) => present.has(t));
120
+ }
121
+ // ---------------------------------------------------------------------------
122
+ // Marketplace registration (§4.4, §10.1.4)
123
+ // ---------------------------------------------------------------------------
124
+ /**
125
+ * The template-level marketplace registries that a plugin must be registered in, keyed by the
126
+ * target whose presence in the envelope requires registration (§4.4). Both registries live at the
127
+ * **repo root**, not inside any plugin: `<repoRoot>/.claude-plugin/marketplace.json` (Claude) and
128
+ * `<repoRoot>/.cursor-plugin/marketplace.json` (Cursor).
129
+ *
130
+ * Mirrors `validateMarketplaceRegistration` in `validate.ts`: those two targets, those two paths.
131
+ */
132
+ const MARKETPLACE_REGISTRIES = [
133
+ { target: 'claude', dir: '.claude-plugin' },
134
+ { target: 'cursor', dir: '.cursor-plugin' },
135
+ ];
136
+ /** The canonical `source` value for a plugin's registry entry (§4.4 — `./plugins/<name>`). */
137
+ function expectedSource(pluginName) {
138
+ return `./plugins/${pluginName}`;
139
+ }
140
+ /**
141
+ * Register `pluginName` in the registry file at `registryPath`, creating it if absent.
142
+ *
143
+ * Idempotent: if an entry whose `name` already matches `pluginName` exists, the file is left
144
+ * untouched (no duplicate, no source rewrite — repairing a wrong source is the validator's job to
145
+ * report, not the scaffolder's to silently mutate). Output is 2-space JSON with a trailing newline.
146
+ *
147
+ * Existing entries and any extra top-level keys are preserved.
148
+ */
149
+ function registerInMarketplace(registryPath, pluginName) {
150
+ const entry = { name: pluginName, source: expectedSource(pluginName) };
151
+ let registry;
152
+ if (fs.existsSync(registryPath)) {
153
+ const raw = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
154
+ // Tolerate a missing/!object/!plugins file by normalizing to a registry with a plugins array.
155
+ registry =
156
+ typeof raw === 'object' && raw !== null ? raw : { plugins: [] };
157
+ }
158
+ else {
159
+ registry = { plugins: [] };
160
+ }
161
+ const plugins = Array.isArray(registry.plugins) ? registry.plugins : [];
162
+ if (plugins.some((p) => p.name === pluginName)) {
163
+ return; // already registered — idempotent no-op
164
+ }
165
+ registry.plugins = [...plugins, entry];
166
+ writeFileEnsuringDir(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
167
+ }
168
+ /**
169
+ * Register a plugin in every marketplace registry implied by its envelope (§4.4).
170
+ *
171
+ * For each of Claude/Cursor present in `targets`, create-or-append the plugin's entry in the
172
+ * corresponding `<repoRoot>/<dir>/marketplace.json`. Targets not in the envelope get no entry, so
173
+ * the scaffold→validate happy path stays clean (the validator forbids registration for
174
+ * undeclared targets).
175
+ */
176
+ function registerPluginInMarketplaces(repoRoot, pluginName, targets) {
177
+ const envelope = new Set(targets);
178
+ for (const { target, dir } of MARKETPLACE_REGISTRIES) {
179
+ if (!envelope.has(target))
180
+ continue;
181
+ registerInMarketplace(path.join(repoRoot, dir, 'marketplace.json'), pluginName);
182
+ }
183
+ }
184
+ // ---------------------------------------------------------------------------
185
+ // Filesystem helpers
186
+ // ---------------------------------------------------------------------------
187
+ /** Write `content` to `filePath`, creating parent directories. Does not check for existence. */
188
+ function writeFileEnsuringDir(filePath, content) {
189
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
190
+ fs.writeFileSync(filePath, content, 'utf-8');
191
+ }
192
+ /**
193
+ * Canonical top-level files every scaffolded plugin gets, independent of targets (§4 layout).
194
+ * Deterministic: no clock/env reads — the LICENSE year is intentionally omitted.
195
+ */
196
+ function canonicalRootFiles(pluginName, description) {
197
+ const readme = md `# ${pluginName}
198
+
199
+ ${description}
200
+
201
+ ## Installation
202
+
203
+ Install this plugin by copying it into your AI assistant's plugin directory.
204
+
205
+ ## License
206
+
207
+ ISC
208
+ `;
209
+ const license = `ISC License
210
+
211
+ Permission to use, copy, modify, and/or distribute this software for any
212
+ purpose with or without fee is hereby granted, provided that the above
213
+ copyright notice and this permission notice appear in all copies.
214
+
215
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
216
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
217
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
218
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
219
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
220
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
221
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
222
+ `;
223
+ return [
224
+ { path: 'README.md', content: readme },
225
+ { path: 'LICENSE', content: license },
226
+ ];
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // runScaffold
230
+ // ---------------------------------------------------------------------------
231
+ /**
232
+ * Create a brand-new plugin under `pluginsDir/<name>/`.
233
+ *
234
+ * Writes `aipm.config.ts` (via the `defineConfig` literal), each declared target's skeleton
235
+ * files, and the canonical `README.md` / `LICENSE`. Default targets are all known IDs when
236
+ * `opts.targets` is absent.
237
+ *
238
+ * Also registers the plugin in the template-level marketplace registries (§4.4) for each of
239
+ * Claude/Cursor in the envelope (`repoRoot = dirname(pluginsDir)`), so the scaffolded plugin
240
+ * passes `validate`'s `marketplace-registration` check (§10.1.4) out of the box.
241
+ *
242
+ * @throws Error when `name` is invalid or the plugin directory already exists.
243
+ */
244
+ export async function runScaffold(name, pluginsDir, opts = {}) {
245
+ validatePluginName(name);
246
+ const pluginDir = path.join(pluginsDir, name);
247
+ if (fs.existsSync(pluginDir)) {
248
+ throw new Error(`Plugin directory already exists: ${pluginDir}`);
249
+ }
250
+ const targets = orderTargets(opts.targets ?? TARGET_IDS);
251
+ const description = opts.description ?? `A plugin for ${name}`;
252
+ const targetOpts = { description };
253
+ const files = [
254
+ { path: 'aipm.config.ts', content: renderAipmConfig(targets) },
255
+ ...canonicalRootFiles(name, description),
256
+ ];
257
+ for (const target of targets) {
258
+ files.push(...TARGET_SCAFFOLDERS[target](name, targetOpts));
259
+ }
260
+ fs.mkdirSync(pluginDir, { recursive: true });
261
+ for (const file of files) {
262
+ writeFileEnsuringDir(path.join(pluginDir, file.path), file.content);
263
+ }
264
+ // Register in the repo-root marketplace registries (§4.4). repoRoot is the parent of pluginsDir.
265
+ registerPluginInMarketplaces(path.dirname(pluginsDir), name, targets);
266
+ return Promise.resolve();
267
+ }
268
+ // ---------------------------------------------------------------------------
269
+ // runAddTarget
270
+ // ---------------------------------------------------------------------------
271
+ /**
272
+ * Scaffold one target's skeleton files into an existing plugin (§6.4 `aipm add-target`).
273
+ *
274
+ * Manifest fields are emitted as placeholders for the author to complete. Existing files are
275
+ * NEVER clobbered: if any file this target would write already exists, the function refuses and
276
+ * throws — the author resolves the conflict deliberately.
277
+ *
278
+ * The plugin's `aipm.config.ts` is updated to include `target` in its `targets` array when the
279
+ * lexical reader can locate the array (see module doc). Limitation: if the array cannot be parsed
280
+ * (dynamic/computed targets), the config is left untouched and the error message instructs the
281
+ * author to add the target manually — the function never leaves the envelope silently
282
+ * inconsistent.
283
+ *
284
+ * When `target` is Claude or Cursor, the plugin is also registered in the corresponding
285
+ * repo-root marketplace registry (§4.4) so adding the target keeps `validate` green. repoRoot is
286
+ * the plugin's grandparent (`<repoRoot>/plugins/<name>`), matching `discoverPlugins`.
287
+ *
288
+ * @throws Error when `pluginDir` does not exist, a target file already exists, or the config's
289
+ * targets array cannot be located for the update.
290
+ */
291
+ export async function runAddTarget(pluginDir, target) {
292
+ if (!fs.existsSync(pluginDir)) {
293
+ throw new Error(`Plugin directory does not exist: ${pluginDir}`);
294
+ }
295
+ const pluginName = path.basename(pluginDir);
296
+ const files = TARGET_SCAFFOLDERS[target](pluginName, { placeholder: true });
297
+ // Refuse to overwrite: collect conflicts before writing anything.
298
+ const conflicts = files
299
+ .map((f) => f.path)
300
+ .filter((rel) => fs.existsSync(path.join(pluginDir, rel)));
301
+ if (conflicts.length > 0) {
302
+ throw new Error(`Refusing to overwrite existing files while adding target '${target}': ${conflicts.join(', ')}. ` +
303
+ 'Remove or move them first, then re-run.');
304
+ }
305
+ // Update the envelope BEFORE writing skeleton files, so a config we cannot parse aborts the
306
+ // whole operation rather than leaving orphan files with an unchanged envelope.
307
+ updateConfigTargets(pluginDir, target);
308
+ for (const file of files) {
309
+ writeFileEnsuringDir(path.join(pluginDir, file.path), file.content);
310
+ }
311
+ // Register in the repo-root marketplace registry when adding a registry-backed target (§4.4).
312
+ // repoRoot = dirname(dirname(pluginDir)) — the `<repoRoot>/plugins/<name>` grandparent.
313
+ registerPluginInMarketplaces(path.dirname(path.dirname(pluginDir)), pluginName, [target]);
314
+ return Promise.resolve();
315
+ }
316
+ /**
317
+ * Add `target` to the `targets` array in `pluginDir/aipm.config.ts`, rewriting the file.
318
+ *
319
+ * Idempotent: if `target` is already declared, the file is left byte-for-byte unchanged.
320
+ * Always re-renders the array in canonical order. Throws (rather than silently skipping) when the
321
+ * config is missing or the array cannot be located, so the caller never proceeds with an
322
+ * inconsistent envelope.
323
+ */
324
+ function updateConfigTargets(pluginDir, target) {
325
+ const configPath = path.join(pluginDir, 'aipm.config.ts');
326
+ if (!fs.existsSync(configPath)) {
327
+ throw new Error(`Cannot add target '${target}': no aipm.config.ts found at ${configPath}. ` +
328
+ 'Create one declaring the support envelope first.');
329
+ }
330
+ const source = fs.readFileSync(configPath, 'utf-8');
331
+ const declared = parseDeclaredTargets(source); // throws if no array literal located
332
+ if (declared.includes(target)) {
333
+ return; // already declared — nothing to do
334
+ }
335
+ const next = orderTargets([...declared, target]);
336
+ const targetList = next.map((t) => `'${t}'`).join(', ');
337
+ const rewritten = source.replace(/targets\s*:\s*\[[^\]]*\]/, `targets: [${targetList}]`);
338
+ fs.writeFileSync(configPath, rewritten, 'utf-8');
339
+ }
340
+ // ---------------------------------------------------------------------------
341
+ // runCheckSupport
342
+ // ---------------------------------------------------------------------------
343
+ /**
344
+ * Report a plugin's support posture (§6.4 `aipm check-support`).
345
+ *
346
+ * Reads the declared envelope from `aipm.config.ts` (lexically — see module doc), then:
347
+ * - `missingArtifacts`: for each declared target, the `TARGET_MIN_REQUIRED` files absent on disk
348
+ * (Vercel's "at least one SKILL.md under a skills subdirectory" rule is applied specially,
349
+ * mirroring the
350
+ * validator). Only targets with at least one missing artifact appear.
351
+ * - `suggestions`: each undeclared known target, with `wouldNeed` = that target's min-required
352
+ * files (the concrete list of files the author would write to add it).
353
+ *
354
+ * Reuses the validator's `TARGET_MIN_REQUIRED` table so "missing" stays consistent with
355
+ * `aipm validate`.
356
+ *
357
+ * @throws Error when `pluginDir` or its `aipm.config.ts` is missing, or the envelope is unreadable.
358
+ */
359
+ export async function runCheckSupport(pluginDir) {
360
+ if (!fs.existsSync(pluginDir)) {
361
+ throw new Error(`Plugin directory does not exist: ${pluginDir}`);
362
+ }
363
+ const configPath = path.join(pluginDir, 'aipm.config.ts');
364
+ if (!fs.existsSync(configPath)) {
365
+ throw new Error(`No aipm.config.ts found at ${configPath}.`);
366
+ }
367
+ const source = fs.readFileSync(configPath, 'utf-8');
368
+ const declared = parseDeclaredTargets(source);
369
+ const declaredSet = new Set(declared);
370
+ const missingArtifacts = [];
371
+ for (const target of declared) {
372
+ const missing = missingMinRequired(pluginDir, target);
373
+ if (missing.length > 0) {
374
+ missingArtifacts.push({ target, missing });
375
+ }
376
+ }
377
+ const suggestions = TARGET_IDS.filter((t) => !declaredSet.has(t)).map((target) => ({ target, wouldNeed: wouldNeedArtifacts(target) }));
378
+ return Promise.resolve({
379
+ plugin: path.basename(pluginDir),
380
+ declared,
381
+ missingArtifacts,
382
+ suggestions,
383
+ });
384
+ }
385
+ /**
386
+ * The min-required artifacts for `target` that are absent under `pluginDir`.
387
+ *
388
+ * Vercel has no entry in `TARGET_MIN_REQUIRED` (its requirement is "at least one
389
+ * a SKILL.md under a skills subdirectory", a directory-scan rule), so it is handled specially to
390
+ * match the validator.
391
+ */
392
+ function missingMinRequired(pluginDir, target) {
393
+ if (target === 'vercel') {
394
+ return hasAnyVercelSkill(pluginDir) ? [] : ['skills/<skill-name>/SKILL.md'];
395
+ }
396
+ return TARGET_MIN_REQUIRED[target].filter((rel) => !fs.existsSync(path.join(pluginDir, rel)));
397
+ }
398
+ /** The concrete files an author would write to add `target` (mirrors `missingMinRequired`). */
399
+ function wouldNeedArtifacts(target) {
400
+ if (target === 'vercel') {
401
+ return ['skills/<skill-name>/SKILL.md'];
402
+ }
403
+ return [...TARGET_MIN_REQUIRED[target]];
404
+ }
405
+ /** True iff at least one `skills/<dir>/SKILL.md` exists one level under `pluginDir/skills`. */
406
+ function hasAnyVercelSkill(pluginDir) {
407
+ const skillsDir = path.join(pluginDir, 'skills');
408
+ if (!fs.existsSync(skillsDir))
409
+ return false;
410
+ try {
411
+ for (const dirent of fs.readdirSync(skillsDir, { withFileTypes: true })) {
412
+ if (dirent.isDirectory() && fs.existsSync(path.join(skillsDir, dirent.name, 'SKILL.md'))) {
413
+ return true;
414
+ }
415
+ }
416
+ }
417
+ catch {
418
+ return false;
419
+ }
420
+ return false;
421
+ }
422
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/pipeline/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Generated-file sentinels.
3
+ *
4
+ * Per §4.3 of the architecture spec, every toolkit-generated file carries a sentinel so
5
+ * hand-edits are detectable by the freshness check (§10.5). There are three carriers:
6
+ *
7
+ * 1. **Inline text** — a comment block prepended to plain-text generated files.
8
+ * 2. **JSON `_generated` field** — a top-level object on JSON outputs that tolerate extra keys.
9
+ * 3. **Sidecar `<artifact>.generated`** — a companion file holding the inline-text body, used
10
+ * for strict-schema hosts that reject unknown top-level fields (Gemini's
11
+ * `gemini-extension.json`, Kiro's `.kiro/agents/*.json`).
12
+ *
13
+ * This module is **pure**: string/object in, string out. It performs no filesystem I/O — the
14
+ * build (§5.2) and freshness (§10.5) layers own all reads and writes. It is internal to
15
+ * `@ai-plugin-marketplace/core` and intentionally NOT re-exported from the package root.
16
+ *
17
+ * Round-trip invariants hold for every carrier:
18
+ * - `stripSentinel(applyXSentinel(x, src), mode) === x`
19
+ * - `readSentinelSource(applyXSentinel(x, src), mode) === src`
20
+ *
21
+ * @see docs/specs/architecture.md §4.3 (author-authored vs toolkit-generated — governing section)
22
+ * @see docs/specs/architecture.md §10.5 (freshness check — consumer of the detect/strip helpers)
23
+ * @see docs/specs/architecture.md §5.2 (build phase — consumer of the apply helpers)
24
+ */
25
+ import type { ClaudeHooksFile } from '../targets/claude/schemas.js';
26
+ /**
27
+ * Canonical generator identity string embedded in every sentinel. Centralized here so the
28
+ * inline comment block, the JSON `_generated.by` field, and any detector all agree on one
29
+ * literal — there is no second copy to drift.
30
+ */
31
+ export declare const GENERATOR_ID = "@ai-plugin-marketplace/cli";
32
+ /**
33
+ * Which sentinel carrier a given generated file uses. Selected by the build step per the
34
+ * artifact's format and host tolerance (§4.3):
35
+ * - `'inline'` — plain-text outputs (comment block prepended).
36
+ * - `'json-field'` — JSON outputs that tolerate an extra top-level `_generated` key.
37
+ * - `'sidecar'` — strict-schema JSON whose host rejects unknown fields; sentinel lives in
38
+ * a companion `<artifact>.generated` file and the artifact itself is left untouched.
39
+ */
40
+ export type SentinelMode = 'inline' | 'json-field' | 'sidecar';
41
+ /**
42
+ * Prepend the inline-text sentinel (§4.3) to a plain-text generated file's content.
43
+ *
44
+ * @param content - The generated file body, sans sentinel.
45
+ * @param source - Author-authored source path, relative to the plugin directory.
46
+ * @returns Content with the three-line sentinel block prepended.
47
+ */
48
+ export declare function applyInlineSentinel(content: string, source: string): string;
49
+ /**
50
+ * The body of a `<artifact>.generated` sidecar file (§4.3). Identical to the inline-text
51
+ * sentinel block — the sidecar carries no artifact content, only the sentinel.
52
+ *
53
+ * @param source - Author-authored source path, relative to the plugin directory.
54
+ * @returns The three-line sentinel block, newline-terminated.
55
+ */
56
+ export declare function sidecarContent(source: string): string;
57
+ /**
58
+ * Derive the sidecar sentinel path for an artifact: `<artifactPath>.generated` (§4.3). The
59
+ * sidecar sits next to the artifact; the artifact itself is left untouched.
60
+ *
61
+ * @param artifactPath - Path to the strict-schema artifact (e.g. `gemini-extension.json`).
62
+ * @returns The sidecar path.
63
+ */
64
+ export declare function sidecarPath(artifactPath: string): string;
65
+ /**
66
+ * Serialize an object as canonical JSON with a top-level `_generated` sentinel (§4.3). The
67
+ * `_generated` key is emitted FIRST, every other own-enumerable key follows in its original
68
+ * order, and the output uses 2-space indentation with a trailing newline — matching the JSON
69
+ * conventions in `claude/transform.ts`'s `serializeClaudeHooksJson`.
70
+ *
71
+ * Any pre-existing `_generated` key on the input is overwritten by the fresh sentinel rather
72
+ * than duplicated, so re-applying is idempotent at the field level.
73
+ *
74
+ * @param obj - The value to serialize. Treated as a string-keyed object; non-object inputs
75
+ * (arrays, primitives) cannot carry a top-level field and are rejected.
76
+ * @param source - Author-authored source path, relative to the plugin directory.
77
+ * @returns Canonical JSON string with `_generated` first and a trailing `\n`.
78
+ * @throws {TypeError} If `obj` is not a plain JSON object (null, array, or primitive).
79
+ */
80
+ export declare function applyJsonSentinel(obj: unknown, source: string): string;
81
+ /**
82
+ * Convenience overload for validated Claude hooks data, matching the call shape of
83
+ * `serializeClaudeHooksJson`. Equivalent to {@link applyJsonSentinel} with the hooks object.
84
+ *
85
+ * @param data - Validated {@link ClaudeHooksFile}.
86
+ * @param source - Author-authored source path, relative to the plugin directory.
87
+ * @returns Canonical JSON string with `_generated` first and a trailing `\n`.
88
+ */
89
+ export declare function applyJsonSentinelToHooks(data: ClaudeHooksFile, source: string): string;
90
+ /**
91
+ * Detect whether `content` carries a sentinel for the given carrier (§4.3). For `'sidecar'`,
92
+ * `content` is the sidecar file body (same shape as inline); the artifact itself is never
93
+ * inspected by this function.
94
+ *
95
+ * @param content - File content to inspect (artifact body, or sidecar body for `'sidecar'`).
96
+ * @param mode - Which carrier to check for.
97
+ * @returns `true` iff a well-formed sentinel of that carrier is present.
98
+ */
99
+ export declare function hasSentinel(content: string, mode: SentinelMode): boolean;
100
+ /**
101
+ * Extract the author-authored source path recorded in a sentinel, or `undefined` if no
102
+ * well-formed sentinel of the given carrier is present (§10.5 uses this to identify the source
103
+ * and to distinguish generated files from hand-authored ones).
104
+ *
105
+ * @param content - File content to inspect (artifact body, or sidecar body for `'sidecar'`).
106
+ * @param mode - Which carrier to read.
107
+ * @returns The recorded source path, or `undefined`.
108
+ */
109
+ export declare function readSentinelSource(content: string, mode: SentinelMode): string | undefined;
110
+ /**
111
+ * Remove a sentinel from `content`, returning the body sans sentinel. The inverse of the
112
+ * matching `applyXSentinel`, so freshness (§10.5) can compare an on-disk generated file to
113
+ * freshly-built content modulo the sentinel.
114
+ *
115
+ * - `'inline'` — drops the three-line comment block if present; otherwise returns `content`
116
+ * unchanged.
117
+ * - `'sidecar'` — the sidecar carries only the sentinel, so the stripped body is the empty
118
+ * string; if `content` is not a recognized sidecar it is returned unchanged.
119
+ * - `'json-field'` — re-serializes the object without `_generated`, preserving the remaining
120
+ * keys' order, 2-space indent, and trailing newline. Malformed JSON is returned unchanged.
121
+ *
122
+ * @param content - File content to strip.
123
+ * @param mode - Which carrier to strip.
124
+ * @returns The content with its sentinel removed.
125
+ */
126
+ export declare function stripSentinel(content: string, mode: SentinelMode): string;
127
+ //# sourceMappingURL=sentinel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/pipeline/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,YAAY,+BAA+B,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAC;AAqC/D;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3E;AASD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAExD;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBtE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtF;AAmDD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAExE;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAQ1F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAWzE"}