special-agents 0.3.5 → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  # Default stack values + flags. These reproduce the original hardcoded rules.md
2
2
  # guidance: rendering the templated packs with these defaults equals the source.
3
- # `rafi create` writes a project.yaml that overrides these per project.
3
+ # `rafi create` writes a rafi-config.yaml that overrides these per project.
4
4
  stack:
5
5
  frontend: "React with TypeScript"
6
6
  backend: "Python and Node.js"
@@ -11,3 +11,4 @@ For Claude Code, create a repository-root `CLAUDE.md` that imports the same rule
11
11
 
12
12
  Keep durable process rules in this file. Put detailed project facts in the project documents named below, not in the agent rules.
13
13
 
14
+ Custom Rafi skills or agents can replace the defaults by setting `artifact_source: existing` and editing their paths in `rafi-config.yaml`.
package/dist/compile.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type Defaults, type LoadedPack } from "./content.js";
2
2
  import { type ConditionFlags, type ResolvableManifest } from "./resolve.js";
3
3
  import type { AgentManifest, AgentRole, EffortLevel } from "rafi-spec";
4
+ export declare const CUSTOM_ARTIFACTS_NOTE = "Custom Rafi skills or agents can replace the defaults by setting `artifact_source: existing` and editing their paths in `rafi-config.yaml`.\n";
4
5
  export interface CompileOptions {
5
6
  /** Stack/flags to render with. Defaults to the bundled `defaults.yaml`. */
6
7
  defaults?: Defaults;
@@ -62,9 +63,13 @@ export declare function emitAgentsMd(targetDir: string, opts?: CompileOptions):
62
63
  * Write `<targetDir>/CLAUDE.md` — the lean Claude entrypoint that imports `AGENTS.md`.
63
64
  */
64
65
  export declare function emitClaudeMd(targetDir: string, opts?: CompileOptions): void;
66
+ export declare function renderAgentsMd(opts?: CompileOptions): string;
67
+ export declare function renderClaudeMd(opts?: CompileOptions): string;
65
68
  export interface EmitOptions extends AgentComposeOptions {
66
69
  /** Roles to emit. Defaults to all four. */
67
70
  roles?: AgentRole[];
71
+ /** Map generic skill names in role manifests to the installed runtime skill names. */
72
+ skillNames?: Record<string, string>;
68
73
  }
69
74
  /**
70
75
  * Write `.rafi/compiled/<role>/system.md` + `meta.json` for each role.
@@ -77,3 +82,16 @@ export declare function emitCompiledBundles(targetDir: string, opts?: EmitOption
77
82
  * composed system text.
78
83
  */
79
84
  export declare function emitClaudeAgents(targetDir: string, opts?: EmitOptions): void;
85
+ export interface EmitMappedOptions extends EmitOptions {
86
+ paths?: Record<string, string>;
87
+ force?: boolean;
88
+ }
89
+ export declare function emitMappedClaudeAgents(targetDir: string, opts?: EmitMappedOptions): void;
90
+ export declare function emitCodexAgents(targetDir: string, opts?: EmitMappedOptions): void;
91
+ export interface EmitSkillsOptions {
92
+ /** Generic skill name -> runtime-specific path. */
93
+ paths?: Record<string, string>;
94
+ names?: string[];
95
+ force?: boolean;
96
+ }
97
+ export declare function emitSkills(targetDir: string, opts?: EmitSkillsOptions): void;
package/dist/compile.js CHANGED
@@ -9,12 +9,14 @@
9
9
  *
10
10
  * Per-role and lean-Claude emission build on this and the resolver (see resolve.ts).
11
11
  */
12
- import { mkdirSync, writeFileSync } from "node:fs";
13
- import { join } from "node:path";
12
+ import { cpSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
14
  import { render } from "./template.js";
15
15
  import { loadAllPacks, loadDefaults, loadPack, loadPacksIndex, loadPreamble, } from "./content.js";
16
16
  import { resolveAgentPacks } from "./resolve.js";
17
17
  import { loadAgent, AGENT_ROLES } from "./agents.js";
18
+ import { loadAllSkills, SKILLS_DIR } from "./skills.js";
19
+ export const CUSTOM_ARTIFACTS_NOTE = "Custom Rafi skills or agents can replace the defaults by setting `artifact_source: existing` and editing their paths in `rafi-config.yaml`.\n";
18
20
  /** Render one pack body, substituting `{{vars}}` and resolving `{{#if flag}}` blocks. */
19
21
  export function renderPackBody(pack, defaults) {
20
22
  return render(pack.body, { vars: defaults.stack, flags: defaults.flags });
@@ -69,17 +71,23 @@ export function buildConditionsHeader(flags) {
69
71
  * Format: one-line conditions header + preamble + all packs rendered with defaults.
70
72
  */
71
73
  export function emitAgentsMd(targetDir, opts = {}) {
72
- const defaults = opts.defaults ?? loadDefaults();
73
- const header = buildConditionsHeader(defaults.flags);
74
- writeFileSync(join(targetDir, "AGENTS.md"), header + composeRulesMarkdown({ defaults }), "utf8");
74
+ writeFileSync(join(targetDir, "AGENTS.md"), renderAgentsMd(opts), "utf8");
75
75
  }
76
76
  /**
77
77
  * Write `<targetDir>/CLAUDE.md` — the lean Claude entrypoint that imports `AGENTS.md`.
78
78
  */
79
79
  export function emitClaudeMd(targetDir, opts = {}) {
80
+ writeFileSync(join(targetDir, "CLAUDE.md"), renderClaudeMd(opts), "utf8");
81
+ }
82
+ export function renderAgentsMd(opts = {}) {
83
+ const defaults = opts.defaults ?? loadDefaults();
84
+ const header = buildConditionsHeader(defaults.flags);
85
+ return header + composeRulesMarkdown({ defaults });
86
+ }
87
+ export function renderClaudeMd(opts = {}) {
80
88
  const defaults = opts.defaults ?? loadDefaults();
81
89
  const header = buildConditionsHeader(defaults.flags);
82
- writeFileSync(join(targetDir, "CLAUDE.md"), header + "@AGENTS.md\n", "utf8");
90
+ return header + "@AGENTS.md\n\n" + CUSTOM_ARTIFACTS_NOTE;
83
91
  }
84
92
  /**
85
93
  * Write `.rafi/compiled/<role>/system.md` + `meta.json` for each role.
@@ -92,7 +100,8 @@ export function emitCompiledBundles(targetDir, opts = {}) {
92
100
  const dir = join(targetDir, ".rafi", "compiled", role);
93
101
  mkdirSync(dir, { recursive: true });
94
102
  writeFileSync(join(dir, "system.md"), bundle.system, "utf8");
95
- const meta = { skills: bundle.skills, model: bundle.model, effort: bundle.effort };
103
+ const skills = bundle.skills.map((skill) => opts.skillNames?.[skill] ?? skill);
104
+ const meta = { skills, model: bundle.model, effort: bundle.effort };
96
105
  writeFileSync(join(dir, "meta.json"), JSON.stringify(meta, null, 2) + "\n", "utf8");
97
106
  }
98
107
  }
@@ -111,3 +120,65 @@ export function emitClaudeAgents(targetDir, opts = {}) {
111
120
  writeFileSync(join(agentsDir, `${role}.md`), frontMatter + bundle.system, "utf8");
112
121
  }
113
122
  }
123
+ export function emitMappedClaudeAgents(targetDir, opts = {}) {
124
+ const roles = opts.roles ?? AGENT_ROLES;
125
+ for (const role of roles) {
126
+ const bundle = getAgent(role, opts);
127
+ const path = join(targetDir, opts.paths?.[role] ?? `./.claude/agents/${role}.md`);
128
+ if (!opts.force && fileExists(path))
129
+ continue;
130
+ mkdirSync(dirname(path), { recursive: true });
131
+ const name = artifactNameFromPath(path, role);
132
+ const frontMatter = `---\nname: ${name}\ndescription: ${bundle.manifest.description}\n---\n\n`;
133
+ writeFileSync(path, frontMatter + bundle.system, "utf8");
134
+ }
135
+ }
136
+ export function emitCodexAgents(targetDir, opts = {}) {
137
+ const roles = opts.roles ?? AGENT_ROLES;
138
+ for (const role of roles) {
139
+ const bundle = getAgent(role, opts);
140
+ const path = join(targetDir, opts.paths?.[role] ?? `./.codex/agents/${role}.toml`);
141
+ if (!opts.force && fileExists(path))
142
+ continue;
143
+ mkdirSync(dirname(path), { recursive: true });
144
+ const name = artifactNameFromPath(path, role);
145
+ const toml = `name = ${JSON.stringify(name)}\n` +
146
+ `description = ${JSON.stringify(bundle.manifest.description)}\n` +
147
+ `developer_instructions = ${JSON.stringify(bundle.system)}\n`;
148
+ writeFileSync(path, toml, "utf8");
149
+ }
150
+ }
151
+ export function emitSkills(targetDir, opts = {}) {
152
+ for (const skill of loadAllSkills()) {
153
+ if (opts.names && !opts.names.includes(skill.name))
154
+ continue;
155
+ const path = join(targetDir, opts.paths?.[skill.name] ?? `./.agents/skills/${skill.name}/SKILL.md`);
156
+ if (!opts.force && fileExists(path))
157
+ continue;
158
+ const dir = dirname(path);
159
+ mkdirSync(dirname(dir), { recursive: true });
160
+ cpSync(join(SKILLS_DIR, skill.name), dir, { recursive: true });
161
+ const installedName = artifactNameFromPath(path, skill.name);
162
+ if (installedName !== skill.name) {
163
+ const raw = readFileSync(join(SKILLS_DIR, skill.name, "SKILL.md"), "utf8");
164
+ writeFileSync(path, raw.replace(/^name:\s*.+$/m, `name: ${installedName}`), "utf8");
165
+ }
166
+ }
167
+ }
168
+ function artifactNameFromPath(path, fallback) {
169
+ const file = path.split(/[\\/]/).pop() ?? fallback;
170
+ if (file === "SKILL.md") {
171
+ const parent = path.split(/[\\/]/).at(-2);
172
+ return parent && parent.length > 0 ? parent : fallback;
173
+ }
174
+ return file.replace(/\.(md|toml)$/i, "") || fallback;
175
+ }
176
+ function fileExists(path) {
177
+ try {
178
+ readFileSync(path);
179
+ return true;
180
+ }
181
+ catch {
182
+ return false;
183
+ }
184
+ }
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  export * from "rafi-spec";
7
7
  export { render, type TemplateContext } from "./template.js";
8
8
  export { CONTENT_DIR, DOCS_DIR, parseRulePack, loadDefaults, loadDocsIndex, loadPreamble, loadPacksIndex, loadPack, loadAllPacks, packFilesOnDisk, type Defaults, type PackIndexEntry, type LoadedPack, type DocIndexEntry, } from "./content.js";
9
- export { composeRulesMarkdown, composeAgentSystem, getAgent, buildConditionsHeader, emitAgentsMd, emitClaudeMd, emitCompiledBundles, emitClaudeAgents, renderPackBody, type CompileOptions, type AgentComposeOptions, type ComposedAgent, type EmitOptions, } from "./compile.js";
9
+ export { composeRulesMarkdown, composeAgentSystem, getAgent, buildConditionsHeader, CUSTOM_ARTIFACTS_NOTE, renderAgentsMd, renderClaudeMd, emitAgentsMd, emitClaudeMd, emitCompiledBundles, emitClaudeAgents, emitMappedClaudeAgents, emitCodexAgents, emitSkills, renderPackBody, type CompileOptions, type AgentComposeOptions, type ComposedAgent, type EmitOptions, type EmitMappedOptions, type EmitSkillsOptions, } from "./compile.js";
10
10
  export { resolvePackRefs, resolveAgentPacks, type ConditionFlags, type ResolveContext, type ResolvableManifest, } from "./resolve.js";
11
11
  export { SKILLS_DIR, parseSkillManifest, loadSkill, loadAllSkills, skillNames, } from "./skills.js";
12
12
  export { AGENTS_DIR, AGENT_ROLES, parseAgentManifest, loadAgent, loadAllAgents, } from "./agents.js";
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  export * from "rafi-spec";
7
7
  export { render } from "./template.js";
8
8
  export { CONTENT_DIR, DOCS_DIR, parseRulePack, loadDefaults, loadDocsIndex, loadPreamble, loadPacksIndex, loadPack, loadAllPacks, packFilesOnDisk, } from "./content.js";
9
- export { composeRulesMarkdown, composeAgentSystem, getAgent, buildConditionsHeader, emitAgentsMd, emitClaudeMd, emitCompiledBundles, emitClaudeAgents, renderPackBody, } from "./compile.js";
9
+ export { composeRulesMarkdown, composeAgentSystem, getAgent, buildConditionsHeader, CUSTOM_ARTIFACTS_NOTE, renderAgentsMd, renderClaudeMd, emitAgentsMd, emitClaudeMd, emitCompiledBundles, emitClaudeAgents, emitMappedClaudeAgents, emitCodexAgents, emitSkills, renderPackBody, } from "./compile.js";
10
10
  export { resolvePackRefs, resolveAgentPacks, } from "./resolve.js";
11
11
  export { SKILLS_DIR, parseSkillManifest, loadSkill, loadAllSkills, skillNames, } from "./skills.js";
12
12
  export { AGENTS_DIR, AGENT_ROLES, parseAgentManifest, loadAgent, loadAllAgents, } from "./agents.js";
@@ -133,7 +133,7 @@ export declare const projectConfigSchema: {
133
133
  readonly $id: "rafi/projectConfig";
134
134
  readonly type: "object";
135
135
  readonly additionalProperties: false;
136
- readonly required: readonly ["appName", "timezone", "stack", "flags", "harness"];
136
+ readonly required: readonly ["appName", "timezone", "stack", "flags", "harness", "agent_files", "agents", "skills"];
137
137
  readonly properties: {
138
138
  readonly appName: {
139
139
  readonly type: "string";
@@ -181,5 +181,65 @@ export declare const projectConfigSchema: {
181
181
  };
182
182
  };
183
183
  };
184
+ readonly agent_files: {
185
+ readonly type: "object";
186
+ readonly additionalProperties: false;
187
+ readonly required: readonly ["mode", "codex", "claude"];
188
+ readonly properties: {
189
+ readonly mode: {
190
+ readonly enum: readonly ["append", "update", "overwrite"];
191
+ };
192
+ readonly codex: {
193
+ readonly type: "string";
194
+ readonly minLength: 1;
195
+ };
196
+ readonly claude: {
197
+ readonly type: "string";
198
+ readonly minLength: 1;
199
+ };
200
+ };
201
+ };
202
+ readonly agents: {
203
+ readonly type: "object";
204
+ readonly additionalProperties: {
205
+ readonly type: "object";
206
+ readonly additionalProperties: false;
207
+ readonly required: readonly ["artifact_source", "claude", "codex"];
208
+ readonly properties: {
209
+ readonly artifact_source: {
210
+ readonly enum: readonly ["rafi", "existing"];
211
+ };
212
+ readonly claude: {
213
+ readonly type: "string";
214
+ readonly minLength: 1;
215
+ };
216
+ readonly codex: {
217
+ readonly type: "string";
218
+ readonly minLength: 1;
219
+ };
220
+ };
221
+ };
222
+ };
223
+ readonly skills: {
224
+ readonly type: "object";
225
+ readonly additionalProperties: {
226
+ readonly type: "object";
227
+ readonly additionalProperties: false;
228
+ readonly required: readonly ["artifact_source", "claude", "codex"];
229
+ readonly properties: {
230
+ readonly artifact_source: {
231
+ readonly enum: readonly ["rafi", "existing"];
232
+ };
233
+ readonly claude: {
234
+ readonly type: "string";
235
+ readonly minLength: 1;
236
+ };
237
+ readonly codex: {
238
+ readonly type: "string";
239
+ readonly minLength: 1;
240
+ };
241
+ };
242
+ };
243
+ };
184
244
  };
185
245
  };
@@ -68,11 +68,25 @@ const boolRecord = (keys) => ({
68
68
  required: keys,
69
69
  properties: Object.fromEntries(keys.map((k) => [k, { type: "boolean" }])),
70
70
  });
71
+ const runtimeArtifactPaths = {
72
+ type: "object",
73
+ additionalProperties: false,
74
+ required: ["artifact_source", "claude", "codex"],
75
+ properties: {
76
+ artifact_source: { enum: ["rafi", "existing"] },
77
+ claude: { type: "string", minLength: 1 },
78
+ codex: { type: "string", minLength: 1 },
79
+ },
80
+ };
81
+ const artifactPathMap = {
82
+ type: "object",
83
+ additionalProperties: runtimeArtifactPaths,
84
+ };
71
85
  export const projectConfigSchema = {
72
86
  $id: "rafi/projectConfig",
73
87
  type: "object",
74
88
  additionalProperties: false,
75
- required: ["appName", "timezone", "stack", "flags", "harness"],
89
+ required: ["appName", "timezone", "stack", "flags", "harness", "agent_files", "agents", "skills"],
76
90
  properties: {
77
91
  appName: { type: "string", minLength: 1 },
78
92
  timezone: { type: "string", minLength: 1 },
@@ -91,5 +105,17 @@ export const projectConfigSchema = {
91
105
  qa: { type: "boolean" },
92
106
  },
93
107
  },
108
+ agent_files: {
109
+ type: "object",
110
+ additionalProperties: false,
111
+ required: ["mode", "codex", "claude"],
112
+ properties: {
113
+ mode: { enum: ["append", "update", "overwrite"] },
114
+ codex: { type: "string", minLength: 1 },
115
+ claude: { type: "string", minLength: 1 },
116
+ },
117
+ },
118
+ agents: artifactPathMap,
119
+ skills: artifactPathMap,
94
120
  },
95
121
  };
@@ -98,8 +98,23 @@ export interface HarnessConfig {
98
98
  targets: HarnessTarget[];
99
99
  qa: boolean;
100
100
  }
101
+ /** How Rafi should handle existing root instruction files. */
102
+ export type AgentFileMode = "append" | "update" | "overwrite";
103
+ /** Root instruction files used by Codex and Claude. */
104
+ export interface AgentFilesConfig {
105
+ mode: AgentFileMode;
106
+ codex: string;
107
+ claude: string;
108
+ }
109
+ /** Per-runtime paths for a skill or agent artifact. */
110
+ export type ArtifactSource = "rafi" | "existing";
111
+ export interface RuntimeArtifactConfig {
112
+ artifact_source: ArtifactSource;
113
+ claude: string;
114
+ codex: string;
115
+ }
101
116
  /**
102
- * The committed `project.yaml` in a target repo. Skipping the walkthrough uses
117
+ * The committed `rafi-config.yaml` in a target repo. Skipping the walkthrough uses
103
118
  * the library defaults, which reproduce today's hardcoded guidance.
104
119
  */
105
120
  export interface ProjectConfig {
@@ -108,4 +123,7 @@ export interface ProjectConfig {
108
123
  stack: ProjectStack;
109
124
  flags: ProjectFlags;
110
125
  harness: HarnessConfig;
126
+ agent_files: AgentFilesConfig;
127
+ agents: Record<string, RuntimeArtifactConfig>;
128
+ skills: Record<string, RuntimeArtifactConfig>;
111
129
  }
@@ -75,11 +75,27 @@ const boolRecord = (keys: string[]) => ({
75
75
  properties: Object.fromEntries(keys.map((k) => [k, { type: "boolean" }])),
76
76
  });
77
77
 
78
+ const runtimeArtifactPaths = {
79
+ type: "object",
80
+ additionalProperties: false,
81
+ required: ["artifact_source", "claude", "codex"],
82
+ properties: {
83
+ artifact_source: { enum: ["rafi", "existing"] },
84
+ claude: { type: "string", minLength: 1 },
85
+ codex: { type: "string", minLength: 1 },
86
+ },
87
+ } as const;
88
+
89
+ const artifactPathMap = {
90
+ type: "object",
91
+ additionalProperties: runtimeArtifactPaths,
92
+ } as const;
93
+
78
94
  export const projectConfigSchema = {
79
95
  $id: "rafi/projectConfig",
80
96
  type: "object",
81
97
  additionalProperties: false,
82
- required: ["appName", "timezone", "stack", "flags", "harness"],
98
+ required: ["appName", "timezone", "stack", "flags", "harness", "agent_files", "agents", "skills"],
83
99
  properties: {
84
100
  appName: { type: "string", minLength: 1 },
85
101
  timezone: { type: "string", minLength: 1 },
@@ -98,5 +114,17 @@ export const projectConfigSchema = {
98
114
  qa: { type: "boolean" },
99
115
  },
100
116
  },
117
+ agent_files: {
118
+ type: "object",
119
+ additionalProperties: false,
120
+ required: ["mode", "codex", "claude"],
121
+ properties: {
122
+ mode: { enum: ["append", "update", "overwrite"] },
123
+ codex: { type: "string", minLength: 1 },
124
+ claude: { type: "string", minLength: 1 },
125
+ },
126
+ },
127
+ agents: artifactPathMap,
128
+ skills: artifactPathMap,
101
129
  },
102
130
  } as const;
@@ -121,8 +121,27 @@ export interface HarnessConfig {
121
121
  qa: boolean;
122
122
  }
123
123
 
124
+ /** How Rafi should handle existing root instruction files. */
125
+ export type AgentFileMode = "append" | "update" | "overwrite";
126
+
127
+ /** Root instruction files used by Codex and Claude. */
128
+ export interface AgentFilesConfig {
129
+ mode: AgentFileMode;
130
+ codex: string;
131
+ claude: string;
132
+ }
133
+
134
+ /** Per-runtime paths for a skill or agent artifact. */
135
+ export type ArtifactSource = "rafi" | "existing";
136
+
137
+ export interface RuntimeArtifactConfig {
138
+ artifact_source: ArtifactSource;
139
+ claude: string;
140
+ codex: string;
141
+ }
142
+
124
143
  /**
125
- * The committed `project.yaml` in a target repo. Skipping the walkthrough uses
144
+ * The committed `rafi-config.yaml` in a target repo. Skipping the walkthrough uses
126
145
  * the library defaults, which reproduce today's hardcoded guidance.
127
146
  */
128
147
  export interface ProjectConfig {
@@ -131,4 +150,7 @@ export interface ProjectConfig {
131
150
  stack: ProjectStack;
132
151
  flags: ProjectFlags;
133
152
  harness: HarnessConfig;
153
+ agent_files: AgentFilesConfig;
154
+ agents: Record<string, RuntimeArtifactConfig>;
155
+ skills: Record<string, RuntimeArtifactConfig>;
134
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "special-agents",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Rafi library: composable best-practice rules, skills, and agents for Claude Code and Codex.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",