Skip to content

Commit 60784e3

Browse files
committed
docs(spec): add instructions assembly with base + per-host overlays
Replace the single-file instructions model with a base + overlay assembly system. Overlays declare positioning (append, prepend, or insert at named markers) via YAML frontmatter. Updates spec, schema, and design document (Decision 15). Fixes #5
1 parent 6a38865 commit 60784e3

3 files changed

Lines changed: 148 additions & 23 deletions

File tree

docs/plans/2026-02-14-ccpkg-design.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,20 @@ The ccpkg format targets multiple AI coding assistant hosts, but each host has f
362362

363363
---
364364

365+
### 15. Instructions Assembly — Base + Per-Host Overlays
366+
367+
**Decision**: The `components.instructions` field supports both a simple string form (single file) and a structured form declaring a base file with optional per-host overlay files. Overlays declare their assembly position (`append`, `prepend`, or `insert` at a named marker) via YAML frontmatter. The installer assembles the final instructions output per host at install time.
368+
369+
**Rationale**: The original "one file, copy everywhere" model is too rigid for real-world packages. Package authors need shared context that applies to all hosts (project conventions, error handling patterns, tool usage guidelines) combined with host-specific tuning (e.g., "use Claude Code's subagent spawning" or "enable Copilot agent mode"). Rather than maintaining entirely separate instruction files per host (which defeats the shared-base purpose), the assembly model lets authors write common content once and layer host-specific additions. Three positioning strategies cover real needs: append for additive content, prepend for prerequisite notices, and marker-based insertion for content that belongs mid-document. Overlay frontmatter keeps the positioning declaration co-located with the content it governs.
370+
371+
**Alternatives considered**:
372+
373+
1. **Separate instruction files per host** -- Each host gets its own complete file (`claude-instructions.md`, `copilot-instructions.md`). Simple to implement but leads to content duplication. When shared content changes, authors must update N files. Rejected because it undermines the DRY principle and scales poorly with host count.
374+
2. **Template language with conditionals** -- Use a templating syntax (e.g., Handlebars, Jinja) with `{{#if host == "claude"}}` blocks. Powerful but introduces a template engine dependency, makes the raw files hard to read, and is overkill for what is typically "shared base + small per-host additions." Rejected for complexity.
375+
3. **mappings.json only (previous design)** -- Map a single canonical file to host-specific filenames without any content variation. Already proven insufficient — the filename mapping exists via `targets.*.instructions_file`, but the content is identical everywhere. Superseded by the assembly model which adds content variation on top of filename mapping.
376+
377+
---
378+
365379
## Relationship to Existing Specifications
366380

367381
ccpkg does not replace existing standards. It composes them.

spec/schemas/manifest.schema.json

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,31 @@
217217
"description": "Path to a .lsp.json template file relative to the package root."
218218
},
219219
"instructions": {
220-
"type": "string",
221-
"description": "Path to a canonical instructions file relative to the package root."
220+
"oneOf": [
221+
{
222+
"type": "string",
223+
"description": "Path to a single instructions file relative to the package root."
224+
},
225+
{
226+
"type": "object",
227+
"required": ["base"],
228+
"additionalProperties": false,
229+
"description": "Structured instructions declaration with base file and optional per-host overlays.",
230+
"properties": {
231+
"base": {
232+
"type": "string",
233+
"description": "Path to the base instructions file within the archive."
234+
},
235+
"hosts": {
236+
"type": "object",
237+
"description": "Map of host identifiers to overlay file paths within the archive.",
238+
"additionalProperties": {
239+
"type": "string"
240+
}
241+
}
242+
}
243+
}
244+
]
222245
}
223246
}
224247
},

spec/specification.md

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,11 @@ example-plugin-1.2.0.ccpkg (ZIP)
159159
├── lsp/
160160
│ └── .lsp.json # LSP server config template
161161
├── instructions/
162-
│ ├── INSTRUCTIONS.md # Canonical instruction file
163-
│ └── mappings.json # Tool-specific filename mappings
162+
│ ├── base.md # Base instructions (shared across all hosts)
163+
│ └── hosts/ # Per-host overlay files
164+
│ ├── claude.md # Claude-specific overlay
165+
│ ├── copilot.md # Copilot-specific overlay
166+
│ └── gemini.md # Gemini-specific overlay
164167
├── config.schema.json # OPTIONAL — JSON Schema for config
165168
├── icon.png # OPTIONAL — package icon (PNG, max 512x512)
166169
└── LICENSE # OPTIONAL — license file
@@ -224,7 +227,7 @@ The `components` object declares which component types are included in the archi
224227
| `hooks` | `string` | Path to a `hooks.json` file within the archive. |
225228
| `mcp` | `string` | Path to an `.mcp.json` template file within the archive. |
226229
| `lsp` | `string` | Path to an `.lsp.json` template file within the archive. |
227-
| `instructions` | `string` | Path to the canonical `INSTRUCTIONS.md` file within the archive. |
230+
| `instructions` | `string` or `object` | Instructions declaration. A string is a path to a single instructions file. An object declares a base file and optional per-host overlays for assembly. See [Instructions](#instructions). |
228231

229232
Each component field that accepts an array (`skills`, `agents`, `commands`) supports two declaration forms:
230233

@@ -318,7 +321,7 @@ The `targets` object supports the following standard fields. Tool-specific adapt
318321
| `hook_events` | `object` | Map of canonical event names to host-native event names |
319322
| `mcp_env_prefix` | `string` | Environment variable prefix for MCP server credential injection |
320323

321-
`instructions_file` — An OPTIONAL string specifying the filename to which the canonical `INSTRUCTIONS.md` is copied for that tool.
324+
`instructions_file` — An OPTIONAL string specifying the filename to which the assembled instructions content is written for that tool.
322325

323326
`hook_events` — An OPTIONAL object that maps canonical event names to the host's native event type names. Keys are canonical event names (lowercase-hyphenated); values are the host's native event type strings.
324327

@@ -408,7 +411,13 @@ Codex CLI has minimal hook support (only `AfterToolUse` and `notify`). OpenCode
408411
],
409412
"hooks": "hooks/hooks.json",
410413
"mcp": "mcp/.mcp.json",
411-
"instructions": "instructions/INSTRUCTIONS.md"
414+
"instructions": {
415+
"base": "instructions/base.md",
416+
"hosts": {
417+
"claude": "instructions/hosts/claude.md",
418+
"copilot": "instructions/hosts/copilot.md"
419+
}
420+
}
412421
},
413422
"config": {
414423
"API_BASE_URL": {
@@ -818,30 +827,109 @@ LSP (Language Server Protocol) server configurations enable packages to provide
818827

819828
### Instructions
820829

821-
Instructions are canonical documentation files that provide guidance to the AI coding assistant. The `instructions/INSTRUCTIONS.md` file contains the universal instruction content. The `instructions/mappings.json` file maps this content to tool-specific filenames.
830+
Instructions are documentation files that provide guidance to the AI coding assistant. The `components.instructions` field declares the instruction content to be assembled and installed.
822831

823-
**Requirements:**
832+
#### Simple Form
833+
834+
When `components.instructions` is a string, it is a path to a single instructions file. The installer copies this file to the host-specific filename defined in `targets.*.instructions_file`. This is equivalent to the base-only assembly model with no per-host overlays.
835+
836+
```json
837+
"instructions": "instructions/base.md"
838+
```
824839

825-
- If `components.instructions` is declared, the referenced file MUST exist in the archive.
826-
- A `mappings.json` file SHOULD be present alongside the instructions file.
827-
- Installers MUST copy the canonical instructions file to the appropriate tool-specific filename during installation.
840+
#### Structured Form (Base + Overlay Assembly)
828841

829-
**mappings.json Format:**
842+
When `components.instructions` is an object, it declares a base file and optional per-host overlay files. The installer assembles the final output by combining the base content with the overlay for the active host.
830843

831844
```json
832-
{
833-
"claude": "CLAUDE.md",
834-
"codex": "AGENTS.md",
835-
"copilot": ".github/copilot-instructions.md",
836-
"gemini": "GEMINI.md"
845+
"instructions": {
846+
"base": "instructions/base.md",
847+
"hosts": {
848+
"claude": "instructions/hosts/claude.md",
849+
"copilot": "instructions/hosts/copilot.md",
850+
"gemini": "instructions/hosts/gemini.md"
851+
}
837852
}
838853
```
839854

840-
The keys are tool identifiers matching those used in the `targets` manifest field. The values are relative file paths (from the project or user config root) where the instructions should be written.
855+
| Field | Required | Type | Description |
856+
|---|---|---|---|
857+
| `base` | REQUIRED | `string` | Path to the base instructions file within the archive. |
858+
| `hosts` | OPTIONAL | `object` | Map of host identifiers to overlay file paths within the archive. Keys match identifiers used in the `targets` manifest field. |
859+
860+
#### Overlay Files
861+
862+
Overlay files are Markdown files with YAML frontmatter that declares how the overlay content is positioned relative to the base content.
863+
864+
**Frontmatter fields:**
865+
866+
| Field | Required | Type | Description |
867+
|---|---|---|---|
868+
| `position` | OPTIONAL | `string` | One of `append`, `prepend`, or `insert`. Default: `append`. |
869+
| `marker` | Conditional | `string` | Name of the insertion marker in the base file. REQUIRED when `position` is `insert`. |
870+
871+
**Example overlay — append (default):**
872+
873+
```markdown
874+
---
875+
position: append
876+
---
877+
## Claude-Specific Guidelines
878+
879+
Use Claude Code's native subagent spawning for parallel research tasks.
880+
```
881+
882+
**Example overlay — prepend:**
883+
884+
```markdown
885+
---
886+
position: prepend
887+
---
888+
> This package requires Copilot agent mode. Enable it in VS Code settings.
889+
```
890+
891+
**Example overlay — insert at marker:**
892+
893+
```markdown
894+
---
895+
position: insert
896+
marker: host-tools
897+
---
898+
When using Gemini CLI, prefer the built-in extension system for tool management.
899+
```
900+
901+
Where the base file contains a named marker at the desired insertion point:
902+
903+
```markdown
904+
## Tool Usage
905+
906+
General tool guidelines here...
907+
908+
<!-- ccpkg:host-tools -->
909+
910+
## Error Handling
911+
...
912+
```
913+
914+
#### Assembly Rules
915+
916+
| `position` | `marker` | Behavior |
917+
|---|---|---|
918+
| `append` | ignored | Overlay content appended after base content |
919+
| `prepend` | ignored | Overlay content prepended before base content |
920+
| `insert` | REQUIRED | Replaces `<!-- ccpkg:{marker} -->` in base with overlay content |
921+
922+
- If no overlay exists for the active host, the base content is used as-is.
923+
- If `position` is `insert` but the marker `<!-- ccpkg:{marker} -->` is not found in the base file, the installer MUST report an error.
924+
- The overlay's YAML frontmatter MUST be stripped before assembly — only the Markdown body is included in the output.
925+
- The assembled output is written to the host-specific filename defined in `targets.*.instructions_file`.
926+
- If the active host is not present in either `hosts` or `targets`, the installer SHOULD write the base content as `INSTRUCTIONS.md` and emit a warning.
841927

842-
If `targets` includes an `instructions_file` override for a tool, that value takes precedence over `mappings.json`. Authors SHOULD use `targets.*.instructions_file` for simple cases. Use `mappings.json` when the package needs to support hosts not listed in targets.
928+
#### Requirements
843929

844-
If the active host tool is not present in either `targets` or `mappings.json`, the installer SHOULD copy the file as `INSTRUCTIONS.md` and emit a warning.
930+
- If `components.instructions` is declared (in either form), the referenced base file MUST exist in the archive.
931+
- All overlay files declared in `hosts` MUST exist in the archive.
932+
- Marker names MUST match the pattern `[a-z0-9]+(-[a-z0-9]+)*` (lowercase alphanumeric with hyphens).
845933

846934
---
847935

@@ -1523,7 +1611,7 @@ Package authors SHOULD use per-component host scoping (see [Components Object](#
15231611

15241612
The following mechanisms handle tool-specific differences:
15251613

1526-
1. **Instruction file mapping.** The `instructions/mappings.json` file and `targets.*.instructions_file` manifest field map canonical `INSTRUCTIONS.md` content to tool-specific filenames (`CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`, `GEMINI.md`).
1614+
1. **Instruction file assembly.** The `components.instructions` field supports base + per-host overlay assembly. Overlays declare positioning (`append`, `prepend`, `insert` at marker) via YAML frontmatter. The `targets.*.instructions_file` field maps assembled output to host-specific filenames (`CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`, `GEMINI.md`).
15271615

15281616
2. **Targets object.** The `targets` field in `manifest.json` allows authors to declare tool-specific overrides. Each tool adapter defines its own schema for the target value object.
15291617

@@ -1533,7 +1621,7 @@ The following mechanisms handle tool-specific differences:
15331621

15341622
### Portability Guidelines for Authors
15351623

1536-
- Use `INSTRUCTIONS.md` and `mappings.json` for instructions. Do not hardcode tool-specific filenames.
1624+
- Use `instructions.base` with per-host overlays for instructions. Do not hardcode tool-specific filenames.
15371625
- Prefer MCP for tool integration over host-specific mechanisms.
15381626
- Use the `compatibility` field to declare minimum host versions rather than excluding hosts.
15391627
- Test packages across multiple hosts when possible.

0 commit comments

Comments
 (0)