harness-alchemist 0.1.4 → 0.1.6

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.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "harness-alchemist",
4
4
  "displayName": "Harness Alchemist",
5
- "version": "0.1.4",
5
+ "version": "0.1.6",
6
6
  "description": "Scaffold, validate, and publish portable coding-agent plugins across Claude Code, Codex, OpenCode, Antigravity, and DeepSeek Harness.",
7
7
  "author": {
8
8
  "name": "Haochuan Zhang"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-alchemist",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Scaffold, validate, and publish portable coding-agent plugins across Claude Code, Codex, OpenCode, Antigravity, and DeepSeek Harness.",
5
5
  "author": {
6
6
  "name": "Haochuan Zhang",
package/README.md CHANGED
@@ -1,140 +1,144 @@
1
- # Harness Alchemist
2
-
3
- Scaffold, validate, and publish portable coding-agent plugins across Claude Code, Codex/ChatGPT, OpenCode, Google Antigravity, and DeepSeek Harness/Cordis.
4
-
5
- Harness Alchemist is a zero-runtime-dependency CLI for Node.js and Bun. Its own repository follows the same universal plugin layout that it generates. The first frozen scaffold is `templates/v0.1.0`.
6
-
7
- ## Quick Start
8
-
9
- Run with Node.js:
1
+ <p align="center">
2
+ <a href="https://github.com/lunarmoon26/harness-alchemist">
3
+ <picture>
4
+ <source srcset="assets/harness-alchemist_dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="assets/harness-alchemist_light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="assets/harness-alchemist_light.svg" alt="Harness Alchemist logo" height="110">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+
11
+ <p align="center">The universal scaffold for coding-agent plugins. One repository, five harnesses, skills that own their runtime.</p>
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/harness-alchemist" target="blank">
14
+ <img src="https://img.shields.io/npm/v/harness-alchemist?style=flat-square" alt="Npm package for Harness Alchemist">
15
+ </a>
16
+ <a href="./LICENSE">
17
+ <img alt="License: MIT" src="https://img.shields.io/github/license/lunarmoon26/harness-alchemist?style=flat-square" />
18
+ </a>
19
+ <a href="https://github.com/lunarmoon26/harness-alchemist/actions/workflows/ci.yml" target="blank">
20
+ <img src="https://img.shields.io/github/actions/workflow/status/lunarmoon26/harness-alchemist/ci.yml?branch=main&style=flat-square&label=CI" alt="CI status for Harness Alchemist">
21
+ </a>
22
+ <br /><br />
23
+ </p>
24
+
25
+ <p align="center">
26
+ Follow <a href="https://x.com/haochuanzero">@haochuanzero on X</a> for updates · Start from the <a href="https://blog.haochuanz.net/harness-alchemist/">one-page tour</a>.
27
+ </p>
28
+
29
+ ---
30
+
31
+ Harness Alchemist scaffolds one TypeScript plugin repository that installs natively into **Claude Code**, **Codex/ChatGPT**, **OpenCode**, **Google Antigravity**, and **DeepSeek Harness/Cordis**. Product skills ship behavioral `.mjs`/`.py` script twins; every harness entrypoint is a thin adapter that delegates to them.
32
+
33
+ ## Install
10
34
 
11
35
  ```bash
12
- npx harness-alchemist create ./my-plugin \
13
- --description "What the plugin does" \
14
- --author "Example Team" \
15
- --repository example/my-plugin
36
+ npm install -g harness-alchemist
37
+ # or run it without installing:
38
+ npx harness-alchemist@latest create my-plugin --help
16
39
  ```
17
40
 
18
- Or with Bun:
41
+ Requires Node.js 22.20+ (Bun 1.2+ also supported). Zero runtime dependencies.
42
+
43
+ ## Quick start
19
44
 
20
45
  ```bash
21
- bunx harness-alchemist create ./my-plugin \
46
+ npx harness-alchemist@latest create my-plugin \
22
47
  --description "What the plugin does" \
23
48
  --author "Example Team" \
24
49
  --repository example/my-plugin
25
- ```
26
-
27
- The destination must be missing or empty. Harness Alchemist renders into a staging directory, validates the result, and moves it into place without replacing existing content.
28
-
29
- ## Commands
30
50
 
31
- ```text
32
- harness-alchemist create <directory> [options]
33
- harness-alchemist validate [directory] [--external] [--json]
34
- harness-alchemist templates
35
- harness-alchemist version
51
+ cd my-plugin && npm install && npm run verify
36
52
  ```
37
53
 
38
- `init` and `new` are aliases for `create`. Run `harness-alchemist create --help` for metadata and license options.
39
-
40
- New projects use the unreleased canonical `v0.1.0` template. Unknown versions
41
- fail instead of silently selecting a different layout.
42
-
43
- ## Generated Layout
44
-
45
- ```text
46
- .agents/
47
- plugins/ Codex repository marketplace
48
- skills/develop-<name>/ Project-local maintenance skill and scripts
49
- .claude-plugin/ Claude plugin and marketplace manifests
50
- .codex-plugin/ Codex/ChatGPT plugin manifest
51
- skills/<name>/SKILL.md Shared installable Agent Skill
52
- src/opencode.ts OpenCode npm plugin entrypoint
53
- src/deepseek.ts Cordis plugin entrypoint
54
- cordis.patch.yml DeepSeek Harness bundle layer
55
- plugin.json Antigravity plugin manifest
56
- package.json Canonical package metadata
57
- ```
58
-
59
- Generated repositories start at version `0.1.0` with inert OpenCode and Cordis entrypoints. Add only the runtime hooks or services the plugin actually needs.
60
-
61
- The recursive part is intentional: Harness Alchemist itself uses this layout, and every generated repository receives `.agents/skills/develop-<name>/` with its own sync, validation, package-payload, and compatibility workflow.
54
+ Creation only writes to a missing or empty destination. The generated project passes its own gate out of the box: TypeScript checks, runtime delegation tests, scaffold validation, and an npm-payload audit.
62
55
 
63
- ## Guidance Tiers
56
+ ## Existing monorepos and SDK packages
64
57
 
65
- The repository-maintainer skill, the published CLI skill, and a generated
66
- project's local maintenance skill have different audiences and scopes. See
67
- [docs/skill-tiers.md](docs/skill-tiers.md) before moving guidance between them.
58
+ `validate` also supports an adapted plugin package inside an existing repository.
59
+ Add `alchemy.json` at the repository root:
68
60
 
69
- ## Canonical Template
70
-
71
- `templates/v0.1.0/universal-typescript/` is the unreleased canonical scaffold. It includes the generated project's local maintenance skill, scoped to that project's ownership boundary. `lib/create.mjs` supplies the version-specific validator and license during the atomic render.
61
+ ```json
62
+ {
63
+ "$schema": "https://unpkg.com/harness-alchemist/alchemy.schema.json",
64
+ "pluginRoot": "packages/my-sdk",
65
+ "opencodeExport": "./server"
66
+ }
67
+ ```
72
68
 
73
- Future incompatible scaffold changes should create a new `templates/<version>/`
74
- snapshot instead of mutating `v0.1.0` after release.
69
+ The manifest is JSON-Schema-validated; the schema ships in the npm package and
70
+ is referenced through `$schema`, so editors autocomplete and check every field.
75
71
 
76
- ## Local Development
72
+ `pluginRoot` contains the canonical plugin manifests, shared skills, Cordis
73
+ patch, adapter sources, and publishable package metadata. Repository marketplace
74
+ manifests remain at the project root and point at that package directory.
75
+ `opencodeExport: "./server"` preserves an SDK at the package root while exposing
76
+ the OpenCode adapter through the modern server entrypoint. Omitting the file
77
+ retains the strict generated single-package layout.
77
78
 
78
- Requires Node.js 22.20 or newer for the full build and package checks. The scaffolding and validation CLI also runs directly with Bun 1.2 or newer.
79
+ The optional `runtime` field selects what the adapted package must contain:
79
80
 
80
- ```bash
81
- npm install
82
- npm run verify
83
- node ./bin/harness-alchemist.mjs templates
84
- bun ./bin/harness-alchemist.mjs templates
85
- ```
81
+ - `"npm"` (default) — the full generated contract: npm metadata, OpenCode and
82
+ Cordis adapters, Cordis patch, and `.mjs`/`.py` script twins.
83
+ - `"skills"` — skills and harness manifests only. No npm package, adapters, or
84
+ Cordis patch are required, and single-language scripts are allowed, so
85
+ Python, Go, Rust, Java, C#, or Swift repositories can expose their workflows
86
+ to Claude Code, Codex, Antigravity, and DeepSeek's filesystem skill roots
87
+ without adopting a JavaScript runtime.
86
88
 
87
- After changing package metadata:
88
-
89
- ```bash
90
- npm run sync
91
- npm run verify
92
- ```
89
+ Generated projects include a `alchemy.json` manifest recording their
90
+ `runtime`, canonical `template` version, `generator`, `generatorVersion`, and
91
+ `createdAt`; `npm run sync` keeps `generatorVersion` aligned with the package
92
+ version.
93
93
 
94
- Inspect the publish payload before release:
94
+ ## What you get
95
95
 
96
- ```bash
97
- npm pack --dry-run
98
- ```
96
+ | Surface | Purpose |
97
+ | --- | --- |
98
+ | `skills/<name>/` | Agent Skills spec skill with `.mjs`/`.py` script twins under `scripts/` and a tool-contract reference |
99
+ | `src/opencode.ts` | OpenCode plugin registering tools that spawn the skill scripts |
100
+ | `src/deepseek.ts` + `cordis.patch.yml` | Cordis function plugin providing a service over the same scripts |
101
+ | `.claude-plugin/`, `.codex-plugin/`, `.agents/plugins/`, `plugin.json` | Native manifests for Claude Code, Codex, and Antigravity marketplaces |
102
+ | `.agents/skills/develop-<name>/` | Repository-maintenance skill so agents can develop the project recursively |
103
+ | `.github/workflows/` | CI plus tag-triggered publish (`vX.Y.Z` → verify → npm provenance) |
99
104
 
100
- ## Package Surfaces
105
+ ## Supported harnesses
101
106
 
102
- - npm binary: `harness-alchemist`
103
- - Shared Agent Skill: `skills/harness-alchemist/SKILL.md`
104
- - OpenCode package-root export: `harness-alchemist`
105
- - Cordis export: `harness-alchemist/deepseek`
106
- - DeepSeek bundle patch: `harness-alchemist/cordis.patch.yml`
107
+ Install paths below are verified against real CLIs before shipping in the template.
107
108
 
108
- The runtime entrypoints are intentionally inert in v0.1.0. The CLI and Agent Skill are the implemented product surfaces.
109
+ | Harness | Skills | Runtime | Install |
110
+ | --- | --- | --- | --- |
111
+ | Claude Code | bundled natively | — | `claude plugin install <name>@<marketplace>` |
112
+ | Codex/ChatGPT | bundled natively | — | `codex plugin add <name>@<marketplace>` |
113
+ | OpenCode | via `~/.agents/skills/` | npm package hooks | `"plugin": ["<package>"]` in `opencode.json` |
114
+ | Google Antigravity | nested bundle | — | `agy plugin validate . && agy plugin install .` |
115
+ | DeepSeek Harness | profile filesystem roots | Cordis service plugin | `dsh plugin --profile demo add <package-or-path>` |
109
116
 
110
- ## Harness Installation
117
+ ## Skill script contract
111
118
 
112
- Claude Code:
119
+ Product skills own their logic; adapters never do.
113
120
 
114
121
  ```bash
115
- claude plugin marketplace add lunarmoon26/harness-alchemist
116
- claude plugin install harness-alchemist@harness-alchemist-plugins
122
+ echo '{"request": "hello"}' | node skills/<name>/scripts/main.mjs
123
+ # {"ok":true,"plugin":"my-plugin","echo":{"request":"hello"}}
117
124
  ```
118
125
 
119
- Codex and ChatGPT:
126
+ - One JSON object on stdin, one JSON result plus newline on stdout.
127
+ - Non-zero exit with a stderr diagnostic on failure.
128
+ - `scripts/main.py` is a stdlib-only behavioral twin of `scripts/main.mjs`.
120
129
 
121
- ```bash
122
- codex plugin marketplace add lunarmoon26/harness-alchemist
123
- ```
130
+ ## Validation tiers
124
131
 
125
- OpenCode shared skill:
126
-
127
- ```bash
128
- npx skills add lunarmoon26/harness-alchemist --agent opencode
129
- ```
132
+ `npm run validate` always enforces Agent Skills frontmatter compliance, SKILL.md reference resolution, and twin parity. With the optional `pyodide` devDependency installed, Python entrypoints are additionally compiled and smoke-executed inside a WebAssembly CPython sandbox — no native Python required.
130
133
 
131
- DeepSeek Harness after npm publication:
134
+ ## Release automation
132
135
 
133
136
  ```bash
134
- dsh plugin --profile demo add harness-alchemist
135
- dsh --profile demo --dump-config
137
+ # bump package.json version, then:
138
+ npm run sync && npm run verify && git commit -am "Release vX.Y.Z" && git push
139
+ git tag vX.Y.Z && git push origin vX.Y.Z # publishes automatically
136
140
  ```
137
141
 
138
142
  ## License
139
143
 
140
- MIT
144
+ [MIT](./LICENSE)
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://unpkg.com/harness-alchemist/alchemy.schema.json",
4
+ "title": "Harness Alchemist layout manifest",
5
+ "description": "Declares how a repository adapts the Harness Alchemist universal plugin layout. Omitting the file selects the strict generated single-package layout.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "URL of this schema."
12
+ },
13
+ "runtime": {
14
+ "enum": ["npm", "skills"],
15
+ "default": "npm",
16
+ "description": "'npm' requires the full generated contract (npm package, OpenCode and Cordis adapters, Cordis patch, .mjs/.py script twins). 'skills' requires skills and harness manifests only and allows single-language scripts."
17
+ },
18
+ "pluginRoot": {
19
+ "type": "string",
20
+ "default": ".",
21
+ "pattern": "^(?!\\.{2}(/|\\\\))[^\\u0000]+",
22
+ "description": "Repository-relative directory containing the plugin manifests, product skills, runtime sources, Cordis patch, and npm metadata. Defaults to the repository root."
23
+ },
24
+ "opencodeExport": {
25
+ "enum": [".", "./server"],
26
+ "default": ".",
27
+ "description": "Package export key that exposes the OpenCode adapter. Only meaningful when runtime is 'npm'."
28
+ },
29
+ "template": {
30
+ "type": "string",
31
+ "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$",
32
+ "description": "Canonical template version the project was generated from."
33
+ },
34
+ "generator": {
35
+ "const": "harness-alchemist",
36
+ "description": "Tool that generated the project."
37
+ },
38
+ "generatorVersion": {
39
+ "type": "string",
40
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$",
41
+ "description": "Version of the generating harness-alchemist CLI."
42
+ },
43
+ "createdAt": {
44
+ "type": "string",
45
+ "format": "date-time",
46
+ "description": "ISO 8601 creation timestamp written by the generator."
47
+ }
48
+ },
49
+ "if": {
50
+ "properties": { "opencodeExport": { "const": "./server" } },
51
+ "required": ["opencodeExport"]
52
+ },
53
+ "then": { "properties": { "runtime": { "const": "npm" } } }
54
+ }
@@ -35,7 +35,7 @@ if (!command || command === "help" || command === "--help" || command === "-h")
35
35
  } else if (["create", "init", "new"].includes(command)) {
36
36
  process.exitCode = await runCreate(args)
37
37
  } else if (command === "validate") {
38
- process.exitCode = await runValidate(args.length > 0 ? args : [process.cwd()])
38
+ process.exitCode = await runValidate(args)
39
39
  } else {
40
40
  console.error(`Unknown command: ${command}\n`)
41
41
  console.error(usage())
package/lib/create.mjs CHANGED
@@ -237,6 +237,7 @@ export async function runCreate(argv) {
237
237
  const projectRoot = dirname(dirname(scriptPath))
238
238
  const canonicalRoot = join(projectRoot, "templates", options.templateVersion)
239
239
  const templateRoot = join(canonicalRoot, "universal-typescript")
240
+ const generatorVersion = JSON.parse(await readFile(join(projectRoot, "package.json"), "utf8")).version
240
241
  const state = await destinationState(options.output)
241
242
  if (state === "not-directory" || state === "non-empty") {
242
243
  console.error(`Destination must be missing or empty: ${options.output}`)
@@ -250,6 +251,9 @@ export async function runCreate(argv) {
250
251
  DISPLAY_NAME_JSON: JSON.stringify(options.displayName),
251
252
  DESCRIPTION: options.description,
252
253
  DESCRIPTION_JSON: JSON.stringify(options.description),
254
+ TEMPLATE_VERSION: options.templateVersion,
255
+ GENERATOR_VERSION: generatorVersion,
256
+ CREATED_AT: new Date().toISOString(),
253
257
  SHARED_SKILL_DESCRIPTION_JSON: JSON.stringify(
254
258
  `${options.description} Use when the user requests ${options.displayName} workflows or explicitly asks to use the ${options.name} plugin.`,
255
259
  ),
package/lib/validate.mjs CHANGED
@@ -6,24 +6,32 @@ import { basename, dirname, join, resolve, sep } from "node:path"
6
6
  import { fileURLToPath } from "node:url"
7
7
  import { spawnSync } from "node:child_process"
8
8
 
9
- const REQUIRED_FILES = [
9
+ const PROJECT_REQUIRED_FILES = [
10
10
  ".agents/plugins/marketplace.json",
11
11
  ".claude-plugin/marketplace.json",
12
- ".claude-plugin/plugin.json",
13
- ".codex-plugin/plugin.json",
14
12
  ".github/workflows/npm-publish.yml",
15
13
  ".gitignore",
16
14
  "AGENTS.md",
17
15
  "LICENSE",
18
16
  "README.md",
17
+ ]
18
+
19
+ const PLUGIN_REQUIRED_FILES = [
20
+ ".claude-plugin/plugin.json",
21
+ ".codex-plugin/plugin.json",
22
+ "plugin.json",
23
+ ]
24
+
25
+ const NPM_PLUGIN_REQUIRED_FILES = [
19
26
  "cordis.patch.yml",
20
27
  "package.json",
21
- "plugin.json",
22
28
  "src/deepseek.ts",
23
29
  "src/opencode.ts",
24
30
  "tsconfig.json",
25
31
  ]
26
32
 
33
+ const RUNTIME_MODES = ["npm", "skills"]
34
+
27
35
  function usage() {
28
36
  return `Usage: harness-alchemist validate [project-directory] [--external] [--json]
29
37
 
@@ -44,16 +52,19 @@ Options:
44
52
 
45
53
  function findProjectRoot(start) {
46
54
  let current = resolve(start)
55
+ let conventionalRoot
47
56
  while (true) {
57
+ if (existsSync(join(current, "alchemy.json"))) return current
48
58
  if (
59
+ !conventionalRoot &&
49
60
  existsSync(join(current, "package.json")) &&
50
61
  existsSync(join(current, ".claude-plugin")) &&
51
62
  existsSync(join(current, ".codex-plugin"))
52
63
  ) {
53
- return current
64
+ conventionalRoot = current
54
65
  }
55
66
  const parent = dirname(current)
56
- if (parent === current) return undefined
67
+ if (parent === current) return conventionalRoot
57
68
  current = parent
58
69
  }
59
70
  }
@@ -294,7 +305,7 @@ async function smokePythonScripts(scripts, errors, warnings) {
294
305
  instance.globals.delete("__ha_payload__")
295
306
  }
296
307
 
297
- async function checkProductSkillRuntime(skillFile, content, frontmatter, errors, pythonScripts) {
308
+ async function checkProductSkillRuntime(skillFile, content, frontmatter, errors, pythonScripts, runtime) {
298
309
  if ((frontmatter.description?.length ?? 0) > 1024) {
299
310
  errors.push(`${skillFile}: description must be at most 1024 characters`)
300
311
  }
@@ -325,12 +336,12 @@ async function checkProductSkillRuntime(skillFile, content, frontmatter, errors,
325
336
  entries.filter((entry) => entry.endsWith(".mjs")).map((entry) => entry.slice(0, -4)),
326
337
  )
327
338
  for (const base of pythonBases) {
328
- if (!mjsBases.has(base)) {
339
+ if (runtime === "npm" && !mjsBases.has(base)) {
329
340
  errors.push(`${skillFile}: scripts/${base}.py is missing its scripts/${base}.mjs twin`)
330
341
  }
331
342
  }
332
343
  for (const base of mjsBases) {
333
- if (!pythonBases.has(base)) {
344
+ if (runtime === "npm" && !pythonBases.has(base)) {
334
345
  errors.push(`${skillFile}: scripts/${base}.mjs is missing its scripts/${base}.py twin`)
335
346
  }
336
347
  }
@@ -344,25 +355,107 @@ export async function validateProject(projectRoot, options = {}) {
344
355
  const errors = []
345
356
  const warnings = []
346
357
 
347
- for (const file of REQUIRED_FILES) requirePath(root, file, errors)
358
+ const layoutPath = join(root, "alchemy.json")
359
+ const parsedLayout = existsSync(layoutPath) ? await readJson(layoutPath, errors) : {}
360
+ if (parsedLayout === undefined) return { root, errors, warnings }
361
+ if (
362
+ typeof parsedLayout !== "object" ||
363
+ parsedLayout === null ||
364
+ Array.isArray(parsedLayout)
365
+ ) {
366
+ errors.push("alchemy.json must contain a JSON object")
367
+ return { root, errors, warnings }
368
+ }
369
+ const layout = parsedLayout
370
+ const knownLayoutFields = [
371
+ "pluginRoot",
372
+ "opencodeExport",
373
+ "runtime",
374
+ "$schema",
375
+ "template",
376
+ "generator",
377
+ "generatorVersion",
378
+ "createdAt",
379
+ ]
380
+ const unknownLayoutFields = Object.keys(layout).filter(
381
+ (key) => !knownLayoutFields.includes(key),
382
+ )
383
+ if (unknownLayoutFields.length) {
384
+ errors.push(`alchemy.json has unknown fields: ${unknownLayoutFields.join(", ")}`)
385
+ }
386
+ if (layout.$schema !== undefined && typeof layout.$schema !== "string") {
387
+ errors.push("alchemy.json $schema must be a string")
388
+ }
389
+ if (layout.template !== undefined && !/^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$/.test(layout.template)) {
390
+ errors.push("alchemy.json template must be a 'vX.Y.Z' version")
391
+ }
392
+ if (layout.generator !== undefined && layout.generator !== "harness-alchemist") {
393
+ errors.push("alchemy.json generator must be 'harness-alchemist'")
394
+ }
395
+ if (
396
+ layout.generatorVersion !== undefined &&
397
+ !/^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$/.test(layout.generatorVersion)
398
+ ) {
399
+ errors.push("alchemy.json generatorVersion must be an 'X.Y.Z' version")
400
+ }
401
+ if (layout.createdAt !== undefined) {
402
+ const createdAt = new Date(layout.createdAt)
403
+ if (typeof layout.createdAt !== "string" || Number.isNaN(createdAt.getTime())) {
404
+ errors.push("alchemy.json createdAt must be an ISO 8601 timestamp")
405
+ }
406
+ }
407
+ const runtime = layout.runtime ?? "npm"
408
+ if (!RUNTIME_MODES.includes(runtime)) {
409
+ errors.push(`alchemy.json runtime must be one of: ${RUNTIME_MODES.join(", ")}`)
410
+ }
411
+ const pluginRootRelative = layout.pluginRoot ?? "."
412
+ if (typeof pluginRootRelative !== "string" || !pluginRootRelative.trim()) {
413
+ errors.push("alchemy.json pluginRoot must be a non-empty relative path")
414
+ }
415
+ const pluginRoot = resolve(root, typeof pluginRootRelative === "string" ? pluginRootRelative : ".")
416
+ if (!(pluginRoot === root || pluginRoot.startsWith(`${root}${sep}`))) {
417
+ errors.push("alchemy.json pluginRoot escapes the project root")
418
+ } else if (existsSync(pluginRoot)) {
419
+ const [canonicalRoot, canonicalPluginRoot] = await Promise.all([
420
+ realpath(root),
421
+ realpath(pluginRoot),
422
+ ])
423
+ if (!(canonicalPluginRoot === canonicalRoot || canonicalPluginRoot.startsWith(`${canonicalRoot}${sep}`))) {
424
+ errors.push("alchemy.json pluginRoot resolves through a symlink outside the project root")
425
+ }
426
+ }
427
+ const opencodeExport = layout.opencodeExport ?? "."
428
+ if (![".", "./server"].includes(opencodeExport)) {
429
+ errors.push("alchemy.json opencodeExport must be '.' or './server'")
430
+ }
431
+ if (opencodeExport !== "." && runtime !== "npm") {
432
+ errors.push("alchemy.json opencodeExport requires runtime 'npm'")
433
+ }
348
434
  if (errors.length > 0) return { root, errors, warnings }
349
435
 
350
- const packageJson = await readJson(join(root, "package.json"), errors)
351
- const claudePlugin = await readJson(join(root, ".claude-plugin/plugin.json"), errors)
436
+ for (const file of PROJECT_REQUIRED_FILES) requirePath(root, file, errors)
437
+ const pluginRequiredFiles = runtime === "npm"
438
+ ? [...PLUGIN_REQUIRED_FILES, ...NPM_PLUGIN_REQUIRED_FILES]
439
+ : PLUGIN_REQUIRED_FILES
440
+ for (const file of pluginRequiredFiles) {
441
+ requirePath(root, join(pluginRootRelative, file), errors)
442
+ }
443
+ if (errors.length > 0) return { root, errors, warnings }
444
+
445
+ const claudePlugin = await readJson(join(pluginRoot, ".claude-plugin/plugin.json"), errors)
352
446
  const claudeMarketplace = await readJson(
353
447
  join(root, ".claude-plugin/marketplace.json"),
354
448
  errors,
355
449
  )
356
- const codexPlugin = await readJson(join(root, ".codex-plugin/plugin.json"), errors)
450
+ const codexPlugin = await readJson(join(pluginRoot, ".codex-plugin/plugin.json"), errors)
357
451
  const codexMarketplace = await readJson(
358
452
  join(root, ".agents/plugins/marketplace.json"),
359
453
  errors,
360
454
  )
361
- const antigravityPlugin = await readJson(join(root, "plugin.json"), errors)
455
+ const antigravityPlugin = await readJson(join(pluginRoot, "plugin.json"), errors)
362
456
 
363
457
  if (errors.length > 0) return { root, errors, warnings }
364
458
 
365
- const packageBase = packageJson.name?.split("/").at(-1)
366
459
  const pluginName = claudePlugin.name
367
460
  const validName = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
368
461
 
@@ -372,9 +465,6 @@ export async function validateProject(projectRoot, options = {}) {
372
465
  if ((pluginName?.length ?? 0) > 56) {
373
466
  errors.push("Plugin name must be at most 56 characters")
374
467
  }
375
- if (packageBase !== pluginName) {
376
- errors.push(`npm package basename '${packageBase}' must match plugin name '${pluginName}'`)
377
- }
378
468
 
379
469
  for (const [label, value] of [
380
470
  ["Codex plugin", codexPlugin.name],
@@ -383,64 +473,104 @@ export async function validateProject(projectRoot, options = {}) {
383
473
  if (value !== pluginName) errors.push(`${label} name '${value}' does not match '${pluginName}'`)
384
474
  }
385
475
 
386
- for (const [label, manifest] of [
387
- ["Claude plugin", claudePlugin],
388
- ["Codex plugin", codexPlugin],
389
- ]) {
390
- if (manifest.version !== packageJson.version) {
391
- errors.push(`${label} version does not match package.json`)
476
+ if (runtime === "skills") {
477
+ for (const [label, manifest] of [["Codex plugin", codexPlugin]]) {
478
+ if (manifest.version !== claudePlugin.version) {
479
+ errors.push(`${label} version does not match the Claude plugin`)
480
+ }
481
+ if (manifest.description !== claudePlugin.description) {
482
+ errors.push(`${label} description does not match the Claude plugin`)
483
+ }
392
484
  }
393
- if (manifest.description !== packageJson.description) {
394
- errors.push(`${label} description does not match package.json`)
485
+ if (antigravityPlugin.description !== claudePlugin.description) {
486
+ errors.push("Antigravity description does not match the Claude plugin")
395
487
  }
396
- }
397
- if (antigravityPlugin.description !== packageJson.description) {
398
- errors.push("Antigravity description does not match package.json")
399
- }
488
+ } else {
489
+ const packageJson = await readJson(join(pluginRoot, "package.json"), errors)
490
+ if (errors.length > 0) return { root, errors, warnings }
400
491
 
401
- if (packageJson.type !== "module") errors.push("package.json type must be 'module'")
402
- if (packageJson.engines?.node !== ">=22.20.0") {
403
- errors.push("package.json engines.node must be '>=22.20.0'")
404
- }
405
- const requiredExports = [
406
- [".", "import", "./dist/opencode.js"],
407
- [".", "types", "./dist/opencode.d.ts"],
408
- ["./deepseek", "import", "./dist/deepseek.js"],
409
- ["./deepseek", "types", "./dist/deepseek.d.ts"],
410
- ["./cordis.patch.yml", "import", "./cordis.patch.yml"],
411
- ]
412
- for (const [key, field, expected] of requiredExports) {
413
- const target = exportTarget(packageJson.exports, key, field)
414
- if (target !== expected) {
415
- errors.push(`package.json export '${key}' ${field} target must be '${expected}'`)
492
+ const packageBase = packageJson.name?.split("/").at(-1)
493
+ if (packageBase !== pluginName) {
494
+ errors.push(`npm package basename '${packageBase}' must match plugin name '${pluginName}'`)
495
+ }
496
+
497
+ for (const [label, manifest] of [
498
+ ["Claude plugin", claudePlugin],
499
+ ["Codex plugin", codexPlugin],
500
+ ]) {
501
+ if (manifest.version !== packageJson.version) {
502
+ errors.push(`${label} version does not match package.json`)
503
+ }
504
+ if (manifest.description !== packageJson.description) {
505
+ errors.push(`${label} description does not match package.json`)
506
+ }
507
+ }
508
+ if (antigravityPlugin.description !== packageJson.description) {
509
+ errors.push("Antigravity description does not match package.json")
510
+ }
511
+
512
+ if (packageJson.type !== "module") errors.push("package.json type must be 'module'")
513
+ if (opencodeExport === "." && packageJson.engines?.node !== ">=22.20.0") {
514
+ errors.push("package.json engines.node must be '>=22.20.0'")
515
+ }
516
+ const requiredExports = [
517
+ [opencodeExport, "import", "./dist/opencode.js"],
518
+ [opencodeExport, "types", "./dist/opencode.d.ts"],
519
+ ["./deepseek", "import", "./dist/deepseek.js"],
520
+ ["./deepseek", "types", "./dist/deepseek.d.ts"],
521
+ ["./cordis.patch.yml", "import", "./cordis.patch.yml"],
522
+ ]
523
+ for (const [key, field, expected] of requiredExports) {
524
+ const target = exportTarget(packageJson.exports, key, field)
525
+ if (target !== expected) {
526
+ errors.push(`package.json export '${key}' ${field} target must be '${expected}'`)
527
+ }
528
+ }
529
+ for (const entry of [
530
+ "dist",
531
+ "skills",
532
+ "alchemy.json",
533
+ "cordis.patch.yml",
534
+ ".claude-plugin/plugin.json",
535
+ ".codex-plugin/plugin.json",
536
+ "plugin.json",
537
+ ]) {
538
+ if (!packageJson.files?.includes(entry)) errors.push(`package.json files is missing '${entry}'`)
539
+ }
540
+ if (packageJson.dsh?.bundle?.patch !== "./cordis.patch.yml") {
541
+ errors.push("package.json dsh.bundle.patch must be './cordis.patch.yml'")
542
+ }
543
+
544
+ const patch = await readFile(join(pluginRoot, "cordis.patch.yml"), "utf8")
545
+ const expectedModule = `${packageJson.name}/deepseek`
546
+ const cordisEntries = parseCordisInsertEntries(patch)
547
+ .filter((entry) => entry.id === pluginName)
548
+ const cordisEntry = cordisEntries[0]
549
+ if (cordisEntries.length !== 1 || !cordisEntry?.valid) {
550
+ errors.push(`cordis.patch.yml must contain a valid insert entry for '${pluginName}'`)
551
+ } else if (cordisEntry.name !== expectedModule) {
552
+ errors.push(`cordis.patch.yml entry '${pluginName}' must load '${expectedModule}'`)
416
553
  }
417
- }
418
- for (const entry of [
419
- "dist",
420
- "skills",
421
- "cordis.patch.yml",
422
- ".claude-plugin/plugin.json",
423
- ".codex-plugin/plugin.json",
424
- "plugin.json",
425
- ]) {
426
- if (!packageJson.files?.includes(entry)) errors.push(`package.json files is missing '${entry}'`)
427
- }
428
- if (packageJson.dsh?.bundle?.patch !== "./cordis.patch.yml") {
429
- errors.push("package.json dsh.bundle.patch must be './cordis.patch.yml'")
430
554
  }
431
555
 
432
- await requireManifestPath(root, claudePlugin.skills, "Claude skills", errors)
433
- await requireManifestPath(root, codexPlugin.skills, "Codex skills", errors)
556
+ await requireManifestPath(pluginRoot, claudePlugin.skills, "Claude skills", errors)
557
+ await requireManifestPath(pluginRoot, codexPlugin.skills, "Codex skills", errors)
558
+
559
+ const marketplaceSource = pluginRoot === root
560
+ ? "./"
561
+ : `./${pluginRootRelative.replaceAll("\\", "/").replace(/^\.\//, "")}`
434
562
 
435
563
  const claudeEntry = claudeMarketplace.plugins?.find((entry) => entry.name === pluginName)
436
564
  if (!claudeEntry) errors.push("Claude marketplace is missing the plugin entry")
437
- else if (claudeEntry.source !== "./") errors.push("Claude marketplace source must be './'")
565
+ else if (claudeEntry.source !== marketplaceSource) {
566
+ errors.push(`Claude marketplace source must be '${marketplaceSource}'`)
567
+ }
438
568
 
439
569
  const codexEntry = codexMarketplace.plugins?.find((entry) => entry.name === pluginName)
440
570
  if (!codexEntry) errors.push("Codex marketplace is missing the plugin entry")
441
571
  else {
442
- if (codexEntry.source?.source !== "local" || codexEntry.source?.path !== "./") {
443
- errors.push("Codex marketplace source must be a local './' path")
572
+ if (codexEntry.source?.source !== "local" || codexEntry.source?.path !== marketplaceSource) {
573
+ errors.push(`Codex marketplace source must be a local '${marketplaceSource}' path`)
444
574
  }
445
575
  if (!codexEntry.policy?.installation || !codexEntry.policy?.authentication) {
446
576
  errors.push("Codex marketplace entry requires installation and authentication policies")
@@ -448,19 +578,9 @@ export async function validateProject(projectRoot, options = {}) {
448
578
  if (!codexEntry.category) errors.push("Codex marketplace entry requires a category")
449
579
  }
450
580
 
451
- const patch = await readFile(join(root, "cordis.patch.yml"), "utf8")
452
- const expectedModule = `${packageJson.name}/deepseek`
453
- const cordisEntries = parseCordisInsertEntries(patch)
454
- .filter((entry) => entry.id === pluginName)
455
- const cordisEntry = cordisEntries[0]
456
- if (cordisEntries.length !== 1 || !cordisEntry?.valid) {
457
- errors.push(`cordis.patch.yml must contain a valid insert entry for '${pluginName}'`)
458
- } else if (cordisEntry.name !== expectedModule) {
459
- errors.push(`cordis.patch.yml entry '${pluginName}' must load '${expectedModule}'`)
460
- }
461
581
 
462
582
  const maintenanceSkillFiles = await collectSkillFiles(root, ".agents/skills")
463
- const productSkillFiles = await collectSkillFiles(root, "skills")
583
+ const productSkillFiles = await collectSkillFiles(pluginRoot, "skills")
464
584
  const skillFiles = [...productSkillFiles, ...maintenanceSkillFiles]
465
585
  if (skillFiles.length < 2) errors.push("Expected a shared skill and a project development skill")
466
586
 
@@ -479,7 +599,7 @@ export async function validateProject(projectRoot, options = {}) {
479
599
  errors.push(`${skillFile}: invalid Agent Skill name`)
480
600
  }
481
601
  if (productSkillFiles.includes(skillFile)) {
482
- await checkProductSkillRuntime(skillFile, content, frontmatter, errors, pythonScripts)
602
+ await checkProductSkillRuntime(skillFile, content, frontmatter, errors, pythonScripts, runtime)
483
603
  }
484
604
  }
485
605
  }
@@ -493,7 +613,7 @@ export async function validateProject(projectRoot, options = {}) {
493
613
  }
494
614
 
495
615
  if (options.external) {
496
- const result = spawnSync("claude", ["plugin", "validate", root, "--strict"], {
616
+ const result = spawnSync("claude", ["plugin", "validate", pluginRoot, "--strict"], {
497
617
  encoding: "utf8",
498
618
  })
499
619
  if (result.error?.code === "ENOENT") warnings.push("Claude CLI not found; skipped external validation")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-alchemist",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Scaffold, validate, and publish portable coding-agent plugins across Claude Code, Codex, OpenCode, Antigravity, and DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "lib",
26
26
  "templates",
27
27
  "skills",
28
+ "alchemy.schema.json",
28
29
  "cordis.patch.yml",
29
30
  ".claude-plugin/plugin.json",
30
31
  ".codex-plugin/plugin.json",
@@ -16,7 +16,11 @@ replace the generated repository's local maintenance skill.
16
16
  2. Read [references/compatibility.md](references/compatibility.md) before choosing paths or manifests.
17
17
  3. For a new project, run `harness-alchemist create --help` and use the CLI instead of recreating the structure manually.
18
18
  4. After creation, load `.agents/skills/develop-<name>/` in the generated project before maintaining its files.
19
- 5. Run `harness-alchemist validate` and the generated package's `verify` script before reporting completion.
19
+ 5. For an existing monorepo or SDK package, use the explicit
20
+ `alchemy.json` adaptation described in
21
+ [references/compatibility.md](references/compatibility.md); do not replace the
22
+ product package root merely to match the generated layout.
23
+ 6. Run `harness-alchemist validate` and the generated package's `verify` script before reporting completion.
20
24
 
21
25
  ## Create A Project
22
26
 
@@ -27,9 +27,13 @@ The rich `interface` object is recommended for public presentation but is not re
27
27
 
28
28
  ```bash
29
29
  codex plugin marketplace add owner/repo
30
+ codex plugin add <plugin-name>@<marketplace-name>
31
+ codex plugin list
30
32
  ```
31
33
 
32
- Open `/plugins` to install from a configured marketplace, then start a new session. Codex npm marketplace sources download packages without running lifecycle scripts, so published packages must already contain built runtime files.
34
+ The `@marketplace` suffix is required for `codex plugin add`; a bare plugin
35
+ name is rejected. Headless install works without opening `/plugins`, then
36
+ start a new session. Codex npm marketplace sources download packages without running lifecycle scripts, so published packages must already contain built runtime files.
33
37
 
34
38
  Official references:
35
39
 
@@ -28,6 +28,20 @@ stdout), and `src/opencode.ts` and `src/deepseek.ts` stay thin adapters that
28
28
  spawn those scripts. See the generated project's tool contract reference for
29
29
  details.
30
30
 
31
+ ## Existing repositories
32
+
33
+ Existing monorepos may declare `pluginRoot` and an optional `./server` OpenCode
34
+ export in root `alchemy.json`. The repository root keeps marketplaces
35
+ and maintenance guidance; the plugin package keeps product skills, host
36
+ manifests, adapter sources, Cordis patch, and npm metadata. Generated repositories
37
+ remain single-package and need no configuration file.
38
+
39
+ The optional `runtime` field adapts the contract to the repository's language:
40
+ `"npm"` (default) requires the full generated package; `"skills"` accepts
41
+ skills and harness manifests alone — no npm package, adapters, Cordis patch,
42
+ or `.mjs`/`.py` twins — so polyglot repositories can publish workflows to
43
+ Claude Code, Codex, Antigravity, and DeepSeek without a JavaScript runtime.
44
+
31
45
  ## Capability Matrix
32
46
 
33
47
  | Artifact | Claude | Codex | OpenCode | Antigravity | DeepSeek |
@@ -49,6 +49,12 @@ dsh plugin --profile demo add @scope/my-plugin
49
49
  dsh --profile demo --dump-config
50
50
  ```
51
51
 
52
+ `dsh plugin add` forwards to pnpm in the profile directory, so an unpublished checkout can be installed by absolute path and stays linked to the working tree:
53
+
54
+ ```bash
55
+ dsh plugin --profile demo add /absolute/path/to/my-plugin
56
+ ```
57
+
52
58
  Bundle membership changes require a profile restart. Profile patch edits may hot-reload.
53
59
 
54
60
  Shared Agent Skills are discovered from project `.agents/skills` and other configured filesystem roots. Package-relative skill assets are not automatically activated by a bundle; install repository skills separately when needed.
@@ -33,12 +33,16 @@ OpenCode accepts npm package names in `opencode.json`:
33
33
 
34
34
  OpenCode installs npm plugins with Bun at startup. For local development, register an absolute `file://` URL to compiled JavaScript or a direct TypeScript file with all of its dependencies available.
35
35
 
36
- Skills are separate from the npm plugin. Install them from the Git repository:
36
+ Skills are discovered from `~/.agents/skills/` (and project `.agents/skills/`), not from the OpenCode config directory, and symlinked skill directories are skipped. Install them from the Git repository or a checkout:
37
37
 
38
38
  ```bash
39
39
  npx skills add owner/repo --agent opencode
40
+ # or, from a local clone:
41
+ cp -R skills/<name> ~/.agents/skills/
40
42
  ```
41
43
 
44
+ Verify discovery with `opencode debug skill`.
45
+
42
46
  ## Extension Rules
43
47
 
44
48
  - Return `{}` when no hooks are registered.
@@ -6,6 +6,16 @@
6
6
  - `.agents/skills/develop-{{NAME}}/` is repository-local development guidance and is discoverable by Vercel Skills.
7
7
  - OpenCode and DeepSeek users install shared skills separately from the npm runtime plugin.
8
8
 
9
+ ## Layout Manifest
10
+
11
+ `alchemy.json` records this repository's layout: `runtime: "npm"`,
12
+ the canonical `template` version, `generator`, `generatorVersion`, and
13
+ `createdAt`. It is validated against the published JSON Schema referenced by
14
+ `$schema`. `npm run sync` refreshes `generatorVersion` from `package.json`.
15
+ If this project ever moves into a monorepo, add `pluginRoot` (and optionally
16
+ `opencodeExport: "./server"` for SDK packages); `runtime: "skills"` adapts
17
+ non-JavaScript repositories to skills-only validation.
18
+
9
19
  ## Harness-Specific Contract
10
20
 
11
21
  | Harness | Files | Rule |
@@ -53,6 +53,7 @@ for (const required of [
53
53
  "cordis.patch.yml",
54
54
  "dist/deepseek.js",
55
55
  "dist/opencode.js",
56
+ "alchemy.json",
56
57
  "plugin.json",
57
58
  ]) {
58
59
  if (!files.has(required)) throw new Error(`npm package is missing ${required}`)
@@ -121,4 +121,13 @@ for (let index = 0; index < cordisLines.length; index += 1) {
121
121
  if (!updatedCordisEntry) throw new Error(`Could not find Cordis entry '${pluginName}' to update`)
122
122
  await writeFile(cordisPath, cordisLines.join("\n"))
123
123
 
124
+ const layoutPath = join(root, "alchemy.json")
125
+ if (existsSync(layoutPath)) {
126
+ const layout = await readJson(layoutPath)
127
+ if (layout.generatorVersion !== packageJson.version) {
128
+ layout.generatorVersion = packageJson.version
129
+ await writeJson(layoutPath, layout)
130
+ }
131
+ }
132
+
124
133
  console.log(`Synchronized plugin manifests from ${packagePath}`)
@@ -19,7 +19,7 @@ cordis.patch.yml DeepSeek Harness bundle layer
19
19
  plugin.json Antigravity plugin manifest
20
20
  ```
21
21
 
22
- The initial runtime entrypoints are intentionally inert. Add only the hooks, tools, or services the plugin actually needs.
22
+ The runtime entrypoints are thin adapters that delegate to skill scripts; see `skills/{{NAME}}/references/tool-contract.md` before extending them.
23
23
 
24
24
  ## Skill Boundaries
25
25
 
@@ -54,13 +54,13 @@ npm pack --dry-run
54
54
 
55
55
  ## GitHub Release Publishing
56
56
 
57
- `.github/workflows/npm-publish.yml` publishes when a GitHub release is
58
- published. Tag the release as `vX.Y.Z` (or a semver prerelease such as
59
- `vX.Y.Z-rc.1`); the workflow applies that version, synchronizes manifests,
57
+ `.github/workflows/npm-publish.yml` publishes when a `vX.Y.Z` tag is pushed
58
+ (or a GitHub release with a semver tag such as `vX.Y.Z-rc.1` is published);
59
+ the workflow applies that version, synchronizes manifests,
60
60
  verifies the package, and publishes with npm provenance.
61
61
 
62
62
  Configure the repository `NPM_TOKEN` secret with an npm publish token before
63
- creating a release.
63
+ pushing the tag.
64
64
 
65
65
  ## Claude Code
66
66
 
@@ -80,9 +80,11 @@ claude plugin validate . --strict
80
80
 
81
81
  ```bash
82
82
  codex plugin marketplace add {{REPOSITORY_SOURCE}}
83
+ codex plugin add {{NAME}}@{{MARKETPLACE}}
84
+ codex plugin list
83
85
  ```
84
86
 
85
- Open `/plugins`, install **{{DISPLAY_NAME}}**, and start a new session.
87
+ The `@{{MARKETPLACE}}` suffix is required; a bare plugin name is rejected.
86
88
 
87
89
  ## OpenCode
88
90
 
@@ -95,12 +97,24 @@ After publishing `{{PACKAGE_NAME}}`, add it to `opencode.json`:
95
97
  }
96
98
  ```
97
99
 
98
- Install the shared skill separately:
100
+ For an unpublished checkout, point the entry at the built adapter instead:
101
+
102
+ ```json
103
+ {
104
+ "plugin": ["file:///absolute/path/to/{{NAME}}/dist/opencode.js"]
105
+ }
106
+ ```
107
+
108
+ Skills are discovered from `~/.agents/skills/`, not from the OpenCode config
109
+ directory, and symlinked directories are skipped. Copy each shared skill you
110
+ need:
99
111
 
100
112
  ```bash
101
- npx skills add {{REPOSITORY_SOURCE}} --agent opencode
113
+ cp -R skills/{{NAME}} ~/.agents/skills/
102
114
  ```
103
115
 
116
+ Verify with `opencode debug skill`.
117
+
104
118
  ## Google Antigravity
105
119
 
106
120
  Clone the repository and install its root as a plugin:
@@ -120,12 +134,18 @@ dsh plugin --profile demo add {{PACKAGE_NAME}}
120
134
  dsh --profile demo --dump-config
121
135
  ```
122
136
 
123
- The bundle loads `{{PACKAGE_NAME}}/deepseek`. Install the shared Agent Skill separately when the workflow needs it:
137
+ For an unpublished checkout, `dsh plugin add` forwards to pnpm, so a local
138
+ path works and stays linked to your working tree:
124
139
 
125
140
  ```bash
126
- npx skills add {{REPOSITORY_SOURCE}}
141
+ dsh plugin --profile demo add /absolute/path/to/{{NAME}}
127
142
  ```
128
143
 
144
+ The bundle loads `{{PACKAGE_NAME}}/deepseek` through `package.json`'s
145
+ `dsh.bundle.patch` entry. Verify composition with
146
+ `dsh --profile demo --dump-config`, which should list an insert for
147
+ `{{NAME}}`.
148
+
129
149
  ## License
130
150
 
131
151
  {{LICENSE}}
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/harness-alchemist/alchemy.schema.json",
3
+ "runtime": "npm",
4
+ "template": "{{TEMPLATE_VERSION}}",
5
+ "generator": "harness-alchemist",
6
+ "generatorVersion": "{{GENERATOR_VERSION}}",
7
+ "createdAt": "{{CREATED_AT}}"
8
+ }
@@ -19,6 +19,7 @@
19
19
  "files": [
20
20
  "dist",
21
21
  "skills",
22
+ "alchemy.json",
22
23
  "cordis.patch.yml",
23
24
  ".claude-plugin/plugin.json",
24
25
  ".codex-plugin/plugin.json",