Skip to content
27 changes: 27 additions & 0 deletions packages/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

Specialized subagents for orchestrated development workflows. This package provides skills, subagent definitions, and scripts that power the orchestration pipeline.

## Project declaration

A project opts into shared artifacts through `.agents/codeassembly.yaml`. Run `codeassembly-agents init` to scaffold one, declare the artifacts you want, then run `codeassembly-agents sync` to materialize them.

### Format

The declaration is grouped by artifact category. Each category takes a `use` list (the slugs to adopt) and an optional `drop` list (slugs to remove from what broader scopes contributed):

```yaml
rulebooks:
use:
- shell-conventions
```

A declared rulebook is materialized into `.agents/rulebooks/<slug>.md` and, depending on its delivery mode, inlined into `.agents/PROJECT.md` and/or delivered as a `consult-<slug>` skill in each detected harness.

Only `rulebooks` is deployed in this version. The `skills`, `subagents`, and `collections` categories are accepted for forward compatibility; declaring a non-empty block of them raises an error until their deployment lands in a later release.

### Scopes

The declaration resolves across two tiers, lowest to highest precedence:

1. **Project** — `.agents/codeassembly.yaml`, committed and shared with the team.
2. **Project-local** — `.agents/codeassembly.local.yaml`, gitignored, for personal overrides.

A higher tier adds to and overrides the tiers below it: `use` adds a rulebook, `drop` removes one inherited from a broader scope, and `root: true` discards everything declared in broader scopes, starting fresh from that file.

## Preferences

Agent behavior is configured through `.agents/preferences.yaml` files. The resolution cascade is:
Expand Down
245 changes: 0 additions & 245 deletions packages/agents/content/skills/shell-conventions/SKILL.md

This file was deleted.

6 changes: 3 additions & 3 deletions packages/agents/src/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,18 @@ describe('CLI rulebook routing', () => {
expect(result.stdout).toContain('sync');
});

it('dispatches sync, reporting a no-op when no rulebooks.yaml exists', async () => {
it('dispatches sync, reporting a no-op when no codeassembly.yaml exists', async () => {
const result = await runCliIn(projectRoot, 'sync');

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Nothing to sync');
});

it('dispatches init, scaffolding rulebooks.yaml in the project', async () => {
it('dispatches init, scaffolding codeassembly.yaml in the project', async () => {
const result = await runCliIn(projectRoot, 'init');

expect(result.exitCode).toBe(0);
expect(existsSync(path.join(projectRoot, '.agents', 'rulebooks.yaml'))).toBe(true);
expect(existsSync(path.join(projectRoot, '.agents', 'codeassembly.yaml'))).toBe(true);
});
});

Expand Down
4 changes: 2 additions & 2 deletions packages/agents/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ function printUsage(): void {

Commands:
install Install guidance, skills, and subagents into harness directories, removing files whose source was deleted
init Scaffold an empty .agents/rulebooks.yaml in the current project
sync Resolve .agents/rulebooks.yaml and materialize declared rulebooks
init Scaffold an empty .agents/codeassembly.yaml in the current project
sync Resolve .agents/codeassembly.yaml and materialize declared rulebooks
uninstall Remove installed guidance, skills, and subagents
status Show the current state of installed items
generate <target> Generate a configuration file (e.g., label-map)
Expand Down
21 changes: 11 additions & 10 deletions packages/agents/src/commands/__tests__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'node:path';

import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import { readRulebooksManifest } from '../../lib/rulebooks-manifest.ts';
import { resolveRulebookDeclaration } from '../../lib/codeassembly-manifest.ts';
import type { InstallOptions } from '../../lib/types.ts';
import { initCommand } from '../init.ts';

Expand All @@ -25,34 +25,35 @@ describe(initCommand, () => {
return { harness: 'claude', link: false, force: false, dryRun: false, ...overrides };
}

const manifestPath = (): string => path.join(projectRoot, '.agents', 'rulebooks.yaml');
const declarationPath = (): string => path.join(projectRoot, '.agents', 'codeassembly.yaml');

it('creates rulebooks.yaml with an empty declaration, creating .agents if absent', async () => {
it('creates codeassembly.yaml with an empty rulebooks declaration, creating .agents if absent', async () => {
await initCommand(makeOptions(), projectRoot);

const content = await readFile(manifestPath(), 'utf8');
expect(content).toContain('rulebooks: []');
const content = await readFile(declarationPath(), 'utf8');
expect(content).toContain('rulebooks:');
expect(content).toContain('use: []');
});

it('scaffolds a file that parses to zero declared rulebooks', async () => {
await initCommand(makeOptions(), projectRoot);

expect(await readRulebooksManifest(projectRoot)).toEqual([]);
expect(await resolveRulebookDeclaration({ cwd: projectRoot })).toEqual([]);
});

it('refuses to overwrite an existing rulebooks.yaml', async () => {
it('refuses to overwrite an existing codeassembly.yaml', async () => {
await mkdir(path.join(projectRoot, '.agents'), { recursive: true });
await writeFile(manifestPath(), 'rulebooks:\n - shell-conventions\n', 'utf8');
await writeFile(declarationPath(), 'rulebooks:\n use:\n - shell-conventions\n', 'utf8');

await expect(initCommand(makeOptions(), projectRoot)).rejects.toThrow(/overwrite/i);

const preserved = await readFile(manifestPath(), 'utf8');
const preserved = await readFile(declarationPath(), 'utf8');
expect(preserved).toContain('shell-conventions');
});

it('in dry-run mode, does not create the file', async () => {
await initCommand(makeOptions({ dryRun: true }), projectRoot);

expect(existsSync(manifestPath())).toBe(false);
expect(existsSync(declarationPath())).toBe(false);
});
});
Loading
Loading