Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/scaffold-skills-under-src.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-agent-bundle": patch
---

Scaffold the minimal template's conventional Skill under `src/skills/`.
5 changes: 5 additions & 0 deletions .changeset/skills-under-src.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": minor
---

Move conventional authored documents under `src/`: skills now use `src/skills/`, commands use `src/commands/`, and rules use `src/rules/`. Top-level conventional documents are no longer discovered and report AB4736 unless a legacy skill is claimed by explicit `skills` configuration. Explicit skill paths remain supported anywhere, and published artifact paths are unchanged.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { defineConfig } from 'agent-bundle/config';
export default defineConfig({
plugin: { name: 'my-plugin', version: '0.1.0', description: 'What it does.' },
targets: ['plugin'],
skills: ['skills/*'],
skills: ['src/skills/*'],
hooks: { sessionStart: { handler: './src/session-start.ts' } },
mcp: { servers: { tools: { entry: './src/mcp.ts' } } },
});
Expand All @@ -55,7 +55,7 @@ Claude Code language servers are declared under `claude.lspServers`; the `claude

Claude Code plugin defaults are declared under `claude.settings` and emitted as plugin-root `settings.json`, which Claude Code applies when the plugin is enabled. The pinned contract supports only `agent` and `subagentStatusLine`; Agent Bundle rejects any other key rather than shipping a default Claude Code would silently ignore, and it expands no path tokens here because `settings.json` is absent from the host's placeholder-substitution table. Because the plugin `agents/` component is still deferred, declaring `agent` also raises a warning: the referenced agent has to reach the plugin root some other way, such as a prebuilt payload.

The same config also owns the npm package build — no second bundler config, bin shims, or hand-rolled stdio lifecycles. `bin` and `lib` entries (or the conventions `src/cli.ts`, `src/index.ts`, and `src/mcp/<server-id>.ts`) emit executable `dist/bin/<name>.js` bundles and a library output alongside the host artifacts; an MCP entry that default-exports a server factory runs under a framework-owned stdio lifecycle; `tools.rsbuild` / `tools.rspack` is the one bundler escape hatch. [Entry conventions](docs/entry-conventions.md) is the full contract, and [Framework mode](docs/framework-mode.md) is the whole authoring model on one screen: structure in config and conventions (`skills/<name>/SKILL.md` ships with no declaration at all), JSX only where something is rendered.
The same config also owns the npm package build — no second bundler config, bin shims, or hand-rolled stdio lifecycles. `bin` and `lib` entries (or the conventions `src/cli.ts`, `src/index.ts`, and `src/mcp/<server-id>.ts`) emit executable `dist/bin/<name>.js` bundles and a library output alongside the host artifacts; an MCP entry that default-exports a server factory runs under a framework-owned stdio lifecycle; `tools.rsbuild` / `tools.rspack` is the one bundler escape hatch. [Entry conventions](docs/entry-conventions.md) is the full contract, and [Framework mode](docs/framework-mode.md) is the whole authoring model on one screen: structure in config and conventions (`src/skills/<name>/SKILL.md` ships with no declaration at all), JSX only where something is rendered.

## Commands

Expand Down
28 changes: 21 additions & 7 deletions docs/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,16 @@ development-only fallback can never produce a release artifact, so
| `AB4011` | warning | `package.json` is unusable — unparsable, not a JSON object, or symlinked outside the project root. |
| `AB4013` | error (build) | `agent-bundle build` refuses a project with no release version: `plugin.version` is omitted and `package.json` declares no valid semantic version. |

## Migration nudges (`AB4730`–`AB4735`)
## Migration nudges (`AB4730`–`AB4736`)

The entry conventions and the framework-owned stdio lifecycle shell (RFC #50)
replaced patterns consumers previously wrote by hand. When `validate`,
`inspect`, `build`, or `dev` prepares project source and finds one of those
pre-convention patterns, it reports an **informational** nudge. Nudges are
never errors and never block anything — migrations stay optional, per the
RFC's additive-first principle. The CLI prints them in human `validate`
output and includes them in every `--json` diagnostics array.
pre-convention patterns, it reports a migration diagnostic. `AB4730`–`AB4735`
are **informational** nudges and never block anything. `AB4736` is an error:
the removed top-level authored-document locations are no longer discovered,
so the compiler refuses to omit them silently. The CLI prints these in human
`validate` output and includes them in every `--json` diagnostics array.

### `AB4730` — self-connecting stdio MCP entry

Expand Down Expand Up @@ -158,10 +159,10 @@ Silence: remove the shadowed file.

### `AB4734` — conventional skill shadowed by explicit `skills` config

A `skills/<name>/SKILL.md` (or rendered `SKILL.tsx`/`SKILL.ts`) directory
A `src/skills/<name>/SKILL.md` (or rendered `SKILL.tsx`/`SKILL.ts`) directory
exists, but the explicit `skills` configuration does not cover it — the
conventional skill is silently shadowed. When config is silent, every
`skills/<name>/` directory ships by convention and this nudge never fires.
`src/skills/<name>/` directory ships by convention and this nudge never fires.

Adopt: remove the explicit `skills` configuration so the convention applies,
or add the directory to `skills`. Silence: remove the directory.
Expand All @@ -175,6 +176,19 @@ document beats a generated one — so the component module never compiles.
Adopt: remove `SKILL.md` so the rendered skill compiles at build. Silence:
remove the component module.

### `AB4736` — legacy top-level authored document location

A document still matches a removed top-level convention:
`skills/<name>/SKILL.md` (or rendered `SKILL.tsx`/`SKILL.ts`),
`commands/*.md`, or `rules/*.mdc`. These locations are no longer discovered,
and every unignored legacy document is reported as an error. A top-level
skill covered by explicit `skills` configuration is claimed and stays valid;
commands and rules have no equivalent override.

Recover: move the document under `src/skills/`, `src/commands/`, or
`src/rules/`. Explicit `skills` paths remain valid anywhere. Published
artifact paths remain `skills/`, `commands/`, and `rules/`.

## Prebuilt payloads (`AB4740`–`AB4750`)

The `payload` block and `{ prebuilt: ... }` entries (see
Expand Down
8 changes: 5 additions & 3 deletions docs/framework-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

Agent Bundle has one newcomer model:

1. **Files under conventional `src/` roots are the app.** For MCP, put one
1. **Authored source lives under `src/`.** For MCP, put one
module at `src/mcp/<server>/{tools,resources,prompts}/<name>.tsx`; its path
is its identity. `skills/<name>/SKILL.md`, `src/scripts/<name>.ts`,
`src/cli.ts`, and `src/index.ts` keep their existing conventions.
is its identity. Skills, commands, rules, scripts, routes, and state use
their conventional `src/` roots, including `src/skills/<name>/SKILL.md`,
`src/commands/*.md`, and `src/rules/*.mdc`. Top-level `assets/` holds
static resources, and `agent-bundle.config.ts` stays at the project root.
2. **One small flat config.** `agent-bundle.config.ts` holds project identity,
targets, and policy that no route file can own.
3. **JSX = rendering.** An executable route is one async default Server
Expand Down
8 changes: 4 additions & 4 deletions examples/audiobook-curator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ The completion contract and real-volume checklist are in

This example is the reference consumer of the framework-owned package build:
one `agent-bundle.config.ts` declares the structure, conventional
`src/mcp/**`, `src/cli/**`, `src/providers/**`, and `src/state.ts` modules supply
the application surfaces, and agent-bundle owns the generated package and host
artifacts. See [`docs/entry-conventions.md`](../../docs/entry-conventions.md)
for the contract.
`src/skills/**`, `src/mcp/**`, `src/cli/**`, `src/providers/**`, and
`src/state.ts` modules supply the application surfaces, and agent-bundle owns
the generated package and host artifacts. See
[`docs/entry-conventions.md`](../../docs/entry-conventions.md) for the contract.
2 changes: 1 addition & 1 deletion examples/audiobook-curator/agent-bundle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default defineConfig({
// No `scripts` or `bin` fields needed: the routed `src/cli/` commands
// compile into the package executable (dist/bin/audiobook-curator.js) by
// convention (#102 stages 2-3).
// No `skills` field needed: `skills/curate-audiobooks/SKILL.md` is
// No `skills` field needed: `src/skills/curate-audiobooks/SKILL.md` is
// discovered by convention.
targets: ['claude', 'codex'],
});
2 changes: 1 addition & 1 deletion examples/mcp-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ and Claude artifacts; the App resource remains portable.

## What is authored

- `skills/service-readiness` documents the evidence, checks, and report needed
- `src/skills/service-readiness` documents the evidence, checks, and report needed
for a service-readiness decision.
- `src/hooks/session-start.ts` adds the readiness workflow to compatible host
sessions, while `check-service-fixture` validates the checked-in compiler
Expand Down
8 changes: 4 additions & 4 deletions examples/skills-starter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ required. Both eval suites are deterministic and read only checked-in fixtures.
## What is authored

- `agent-bundle.config.ts` declares the plugin and its portable, Codex, and
Claude targets. The Skills are not listed there: every `skills/*/SKILL.md`
Claude targets. The Skills are not listed there: every `src/skills/*/SKILL.md`
directory is discovered automatically by convention, the model described in
[`docs/framework-mode.md`](../../docs/framework-mode.md).
- `skills/incident-triage/SKILL.md` guides a production incident from first
- `src/skills/incident-triage/SKILL.md` guides a production incident from first
signal through containment, evidence collection, and a handoff-ready update.
- `skills/dependency-upgrade/SKILL.md` plans dependency upgrades with API,
- `src/skills/dependency-upgrade/SKILL.md` plans dependency upgrades with API,
runtime, rollout, and rollback checks.
- `skills/release-review/SKILL.md` defines the evidence, severity, workflow,
- `src/skills/release-review/SKILL.md` defines the evidence, severity, workflow,
and final-report requirements for an explicit release review.
- Each Skill links its own `references/` checklist or runbook and reusable
`assets/` handoff or planning template.
Expand Down
2 changes: 1 addition & 1 deletion fixtures/integration/packed-release/agent-bundle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ export default {
scripts: {
review: './src/review.ts',
},
skills: ['skills/review'],
skills: ['src/skills/review'],
targets: ['portable', 'claude'],
};
54 changes: 48 additions & 6 deletions packages/agent-bundle/src/config/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,18 @@ export interface DiscoveredPayload {

export interface DiscoveredProject {
assets?: DiscoveredAsset[];
/** Conventional flat `commands/*.md` documents; absent when none are discovered. */
/** Conventional flat `src/commands/*.md` documents; absent when none are discovered. */
commands?: readonly CommandDocument[];
/**
* Documents using removed top-level conventions. Validation reports AB4736
* for every unclaimed document; absent when none are discovered.
*/
legacyConventionalDocuments?: readonly {
readonly kind: 'skill' | 'command' | 'rule';
readonly source: string;
}[];
payloads?: DiscoveredPayload[];
/** Conventional flat `rules/*.mdc` documents; absent when none are discovered. */
/** Conventional flat `src/rules/*.mdc` documents; absent when none are discovered. */
rules?: readonly RuleDocument[];
/**
* The compiled conventional route graph (#93). Present only when route
Expand All @@ -56,7 +64,7 @@ export interface DiscoveredProject {
*/
routeGraph?: CompiledRouteGraph;
/**
* Conventional `skills/<name>/SKILL.md` documents that explicit `skills`
* Conventional `src/skills/<name>/SKILL.md` documents that explicit `skills`
* configuration leaves uncovered — the confusable shadowed state surfaced
* by the AB4734 migration nudge. Absent when config is silent (the
* convention itself applies) or when every conventional skill is covered.
Expand Down Expand Up @@ -245,7 +253,7 @@ export const discoverProject = async (
const projectRoot = resolve(root);
const rules = await readProjectIgnoreRules(projectRoot);
const configuredSkills = config.skills;
const conventionalSources = (await fastGlob('skills/*/SKILL.{md,ts,tsx}', {
const conventionalSources = (await fastGlob('src/skills/*/SKILL.{md,ts,tsx}', {
absolute: true,
cwd: projectRoot,
dot: true,
Expand All @@ -272,9 +280,42 @@ export const discoverProject = async (
}
const shadowedConventionalSkills = [...shadowedByDir.values()];

const legacySkillSources = (await fastGlob('skills/*/SKILL.{md,ts,tsx}', {
absolute: true,
cwd: projectRoot,
dot: true,
followSymbolicLinks: false,
onlyFiles: true,
}))
.filter((source) =>
!isProjectPathIgnored(rules, projectRoot, source) &&
!coveredDirs.has(dirname(source))
);
const legacyCommandSources = (await fastGlob('commands/*.md', {
absolute: true,
cwd: projectRoot,
dot: true,
followSymbolicLinks: false,
onlyFiles: true,
})).filter((source) => !isProjectPathIgnored(rules, projectRoot, source));
const legacyRuleSources = (await fastGlob('rules/*.mdc', {
absolute: true,
cwd: projectRoot,
dot: true,
followSymbolicLinks: false,
onlyFiles: true,
})).filter((source) => !isProjectPathIgnored(rules, projectRoot, source));
const legacyConventionalDocuments = [
...legacySkillSources.map((source) => ({ kind: 'skill' as const, source })),
...legacyCommandSources.map((source) => ({ kind: 'command' as const, source })),
...legacyRuleSources.map((source) => ({ kind: 'rule' as const, source })),
].sort((left, right) =>
left.source.localeCompare(right.source) || left.kind.localeCompare(right.kind)
);

const payloads = await discoverPayloads(projectRoot, config.payload);
const routeGraph = await compileRouteGraph(projectRoot, config, rules);
const commandSources = (await fastGlob('commands/*.md', {
const commandSources = (await fastGlob('src/commands/*.md', {
absolute: true,
cwd: projectRoot,
dot: true,
Expand All @@ -284,7 +325,7 @@ export const discoverProject = async (
.filter((source) => !isProjectPathIgnored(rules, projectRoot, source))
.sort((left, right) => left.localeCompare(right));
const discoveredCommands = await Promise.all(commandSources.map((source) => parseCommand(source)));
const ruleSources = (await fastGlob('rules/*.mdc', {
const ruleSources = (await fastGlob('src/rules/*.mdc', {
absolute: true,
cwd: projectRoot,
dot: true,
Expand All @@ -298,6 +339,7 @@ export const discoverProject = async (
return {
assets: await discoverAssets(projectRoot, config.assets, rules),
...(discoveredCommands.length === 0 ? {} : { commands: discoveredCommands }),
...(legacyConventionalDocuments.length === 0 ? {} : { legacyConventionalDocuments }),
...(payloads.length === 0 ? {} : { payloads }),
...(routeGraph === undefined || isEmptyRouteGraph(routeGraph) ? {} : { routeGraph }),
...(discoveredRules.length === 0 ? {} : { rules: discoveredRules }),
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/config/render-markdown.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* The minimal honest React-element-tree → Markdown renderer behind rendered
* skills (`skills/<name>/SKILL.tsx`). It walks plain element objects (the
* skills (`src/skills/<name>/SKILL.tsx`). It walks plain element objects (the
* shape React's automatic JSX runtime produces) without depending on React
* itself, resolves function components (sync or async), and hand-emits
* Markdown for a documented element subset. Anything outside the subset is a
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/config/rendered-skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Diagnostic } from '../core/diagnostics.ts';
import { MarkdownRenderError, renderElementToMarkdown } from './render-markdown.ts';

/**
* The rendered-skill convention: `skills/<name>/SKILL.tsx` (or `.ts`)
* The rendered-skill convention: `src/skills/<name>/SKILL.tsx` (or `.ts`)
* default-exports a component and exports a `frontmatter` record; the build
* compiles the rendered tree to the `SKILL.md` document every host consumes.
* A hand-authored `SKILL.md` in the same directory always wins (config beats
Expand Down
23 changes: 20 additions & 3 deletions packages/agent-bundle/src/config/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const sourceDiagnostic = (
});

/**
* Informational migration nudges (AB473x): they surface pre-convention
* Informational migration nudges (AB4730-AB4735): they surface pre-convention
* patterns the entry conventions now replace, and they must never gate a
* build — migrations stay optional, so the severity is always `info`.
*/
Expand Down Expand Up @@ -1607,7 +1607,7 @@ const validatePrebuiltReference = (

/**
* AB4734: explicit `skills` configuration leaves a conventional
* `skills/<name>/SKILL.md` document uncovered, so the convention is silently
* `src/skills/<name>/SKILL.md` document uncovered, so the convention is silently
* shadowed — the skills-directory analogue of AB4731/AB4732/AB4733.
*/
const skillConventionShadowNudges = (
Expand All @@ -1617,9 +1617,25 @@ const skillConventionShadowNudges = (
'AB4734',
`${relativePosix(loaded.context.projectRoot, source)} is present but explicit skills configuration does not cover it; the conventional skill is shadowed.`,
source,
'Optional: remove the explicit skills configuration to adopt the skills/<name>/SKILL.md convention, add the directory to skills, or remove it to silence this nudge.',
'Optional: remove the explicit skills configuration to adopt the src/skills/<name>/SKILL.md convention, add the directory to skills, or remove it to silence this nudge.',
));

const legacyConventionalDocumentErrors = (
loaded: LoadedConfig,
discovered: DiscoveredProject,
): Diagnostic[] => (discovered.legacyConventionalDocuments ?? []).map(({ kind, source }) => {
const relativePath = relativePosix(loaded.context.projectRoot, source);
const destination = `src/${relativePath}`;
return sourceDiagnostic(
'AB4736',
`${relativePath} uses the removed top-level ${kind} convention and is no longer discovered.`,
source,
kind === 'skill'
? `Move the document to ${destination}, or cover its directory with explicit skills configuration.`
: `Move the document to ${destination}.`,
);
});

const isRspackHatchValue = (value: unknown): boolean =>
typeof value === 'function' || isRecord(value);

Expand Down Expand Up @@ -1905,6 +1921,7 @@ export const validateSource = (
}
diagnostics.push(...packageConventionShadowNudges(loaded));
diagnostics.push(...skillConventionShadowNudges(loaded, discovered));
diagnostics.push(...legacyConventionalDocumentErrors(loaded, discovered));
// Route overrides are validated during discovery; source validation must
// still observe the config getter so hostile accessors fail closed as AB7001.
void loaded.config['routes'];
Expand Down
4 changes: 2 additions & 2 deletions packages/agent-bundle/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ export interface NormalizedPlugin {
*/
readonly assets?: readonly NormalizedAsset[];
/**
* Conventional `commands/*.md` documents. Present only when commands are
* Conventional `src/commands/*.md` documents. Present only when commands are
* discovered; optional so hand-constructed models predating commands remain valid.
*/
readonly commands?: readonly NormalizedCommand[];
Expand Down Expand Up @@ -624,7 +624,7 @@ export interface NormalizedPlugin {
/** Conventional context providers executed for every generated render request. */
readonly providers?: readonly CompiledProvider[];
/**
* Conventional `rules/*.mdc` documents. Present only when rules are
* Conventional `src/rules/*.mdc` documents. Present only when rules are
* discovered; optional so hand-constructed models predating rules remain valid.
*/
readonly rules?: readonly NormalizedRule[];
Expand Down
Loading
Loading