@rafi-ai/cli 0.1.0

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.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @rafi-ai/cli
2
+
3
+ Scaffold and compile AI agent configs for your repo.
4
+
5
+ `rafi create` reads your stack, assembles best-practice rule packs, and writes the files Claude Code and Codex read — AGENTS.md, CLAUDE.md, subagents, and starter docs — all in one command.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install -g @rafi-ai/cli
11
+ ```
12
+
13
+ ## Use
14
+
15
+ ```sh
16
+ rafi create ./my-repo # interactive walkthrough
17
+ rafi create ./my-repo --defaults # skip walkthrough, use built-in defaults
18
+ rafi compile ./my-repo # re-render from an existing project.yaml
19
+ ```
20
+
21
+ ## Commands
22
+
23
+ ### `rafi create <project>`
24
+
25
+ Runs the walkthrough (or `--defaults` to skip it), writes `project.yaml`, and compiles all configs.
26
+
27
+ ```sh
28
+ rafi create ./my-repo
29
+ rafi create ./my-repo --defaults # built-in defaults; byte-equivalent to the bundled rule set
30
+ rafi create ./my-repo --force # overwrite existing doc files
31
+ ```
32
+
33
+ The walkthrough collects your stack (frontend, backend, database, cloud, package manager) and three boolean flags: `usesAI`, `hasFrontend`, `runsInCloud`. These gate which rule packs are included. Answers are saved to `project.yaml`.
34
+
35
+ ### `rafi compile <project>`
36
+
37
+ Re-renders all configs from an existing `project.yaml`. Run this after editing the config or upgrading `special-agents`.
38
+
39
+ ```sh
40
+ rafi compile ./my-repo
41
+ rafi compile ./my-repo --force
42
+ ```
43
+
44
+ ## What gets written
45
+
46
+ ```
47
+ <project>/
48
+ AGENTS.md Codex flat rules doc
49
+ CLAUDE.md Claude Code entrypoint (@AGENTS.md import)
50
+ project.yaml stack config (commit this)
51
+ .claude/agents/<role>.md Claude subagent files (builder, qa, planner, ticket-maker)
52
+ .rafi/compiled/<role>/system.md role system text — read by ai-foreman at runtime
53
+ .rafi/compiled/<role>/meta.json skills + model config
54
+ docs/ starter doc templates (flag-gated by stack)
55
+ ```
56
+
57
+ ## Part of Rafi
58
+
59
+ - **`special-agents`** — library (rules + skills + agents + composition)
60
+ - **`ai-foreman`** — runtime that drives agents through a ticket loop
61
+ - **`@rafi-ai/cli`** — this CLI
@@ -0,0 +1,19 @@
1
+ import { type Defaults } from "special-agents";
2
+ import type { ProjectConfig } from "rafi-spec";
3
+ import { type CopyDocsOptions } from "./docs.js";
4
+ export interface CompileProjectOptions extends CopyDocsOptions {
5
+ /** Skip doc copying entirely (useful in tests that don't need docs). */
6
+ skipDocs?: boolean;
7
+ }
8
+ /** Map a ProjectConfig to the Defaults shape special-agents compile functions expect. */
9
+ export declare function projectConfigToDefaults(config: ProjectConfig): Defaults;
10
+ /**
11
+ * Full compile: write AGENTS.md, CLAUDE.md, lean Claude agents,
12
+ * compiled role bundles, and (optionally) starter docs to `targetDir`.
13
+ */
14
+ export declare function compile(targetDir: string, config: ProjectConfig, opts?: CompileProjectOptions): void;
15
+ /**
16
+ * Write `project.yaml` to `<targetDir>/project.yaml`. Creates the directory
17
+ * if it does not exist.
18
+ */
19
+ export declare function writeProjectYaml(targetDir: string, config: ProjectConfig): void;
@@ -0,0 +1,47 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { stringify } from "yaml";
4
+ import { emitAgentsMd, emitClaudeMd, emitClaudeAgents, emitCompiledBundles, } from "special-agents";
5
+ import { copyDocs } from "./docs.js";
6
+ /** Map a ProjectConfig to the Defaults shape special-agents compile functions expect. */
7
+ export function projectConfigToDefaults(config) {
8
+ return {
9
+ stack: config.stack,
10
+ flags: config.flags,
11
+ };
12
+ }
13
+ /** Map ProjectFlags to the ConditionFlags shape (for role-specific compilation). */
14
+ function flagsToConditions(flags) {
15
+ return {
16
+ ai: flags.usesAI,
17
+ frontend: flags.hasFrontend,
18
+ cloud: flags.runsInCloud,
19
+ };
20
+ }
21
+ /**
22
+ * Full compile: write AGENTS.md, CLAUDE.md, lean Claude agents,
23
+ * compiled role bundles, and (optionally) starter docs to `targetDir`.
24
+ */
25
+ export function compile(targetDir, config, opts = {}) {
26
+ const defaults = projectConfigToDefaults(config);
27
+ const conditions = flagsToConditions(config.flags);
28
+ // Flat Codex doc + lean Claude entrypoint
29
+ emitAgentsMd(targetDir, { defaults });
30
+ emitClaudeMd(targetDir, { defaults });
31
+ // Lean Claude subagent files (role-filtered)
32
+ emitClaudeAgents(targetDir, { defaults, conditions });
33
+ // Compiled role bundles for foreman (role-filtered)
34
+ emitCompiledBundles(targetDir, { defaults, conditions });
35
+ // Starter docs (flag-gated)
36
+ if (!opts.skipDocs) {
37
+ copyDocs(targetDir, config.flags, { force: opts.force });
38
+ }
39
+ }
40
+ /**
41
+ * Write `project.yaml` to `<targetDir>/project.yaml`. Creates the directory
42
+ * if it does not exist.
43
+ */
44
+ export function writeProjectYaml(targetDir, config) {
45
+ mkdirSync(targetDir, { recursive: true });
46
+ writeFileSync(join(targetDir, "project.yaml"), stringify(config), "utf8");
47
+ }
package/dist/docs.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { ProjectFlags } from "rafi-spec";
2
+ export interface CopyDocsOptions {
3
+ force?: boolean;
4
+ }
5
+ /**
6
+ * Copy starter doc templates from special-agents into `<targetDir>/docs/`,
7
+ * respecting gate flags. Returns the list of paths actually written.
8
+ */
9
+ export declare function copyDocs(targetDir: string, flags: ProjectFlags, opts?: CopyDocsOptions): string[];
package/dist/docs.js ADDED
@@ -0,0 +1,25 @@
1
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { DOCS_DIR, loadDocsIndex } from "special-agents";
4
+ /**
5
+ * Copy starter doc templates from special-agents into `<targetDir>/docs/`,
6
+ * respecting gate flags. Returns the list of paths actually written.
7
+ */
8
+ export function copyDocs(targetDir, flags, opts = {}) {
9
+ const entries = loadDocsIndex();
10
+ const written = [];
11
+ for (const entry of entries) {
12
+ const include = entry.gate === "always" ||
13
+ (entry.gate === "ai" && flags.usesAI) ||
14
+ (entry.gate === "frontend" && flags.hasFrontend);
15
+ if (!include)
16
+ continue;
17
+ const dest = join(targetDir, "docs", entry.path);
18
+ if (!opts.force && existsSync(dest))
19
+ continue;
20
+ mkdirSync(dirname(dest), { recursive: true });
21
+ copyFileSync(join(DOCS_DIR, entry.path), dest);
22
+ written.push(entry.path);
23
+ }
24
+ return written;
25
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { resolve, join } from "node:path";
4
+ import { existsSync } from "node:fs";
5
+ import { readFileSync } from "node:fs";
6
+ import { parse as parseYaml } from "yaml";
7
+ import { assertProjectConfig } from "rafi-spec";
8
+ import { compile, writeProjectYaml } from "./compiler.js";
9
+ import { buildProjectConfig, defaultAnswers } from "./project.js";
10
+ const PACKAGE_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))?.version;
11
+ const program = new Command();
12
+ program
13
+ .name("rafi")
14
+ .description("Scaffold and compile Rafi AI framework configs for a target repo.")
15
+ .version(PACKAGE_VERSION);
16
+ program
17
+ .command("compile")
18
+ .description("Re-render .claude/, AGENTS.md, and role bundles from an existing project.yaml.")
19
+ .argument("<project>", "path to the target repo")
20
+ .option("--force", "overwrite existing doc files")
21
+ .action((project, opts) => {
22
+ const targetDir = resolve(project);
23
+ const configPath = join(targetDir, "project.yaml");
24
+ if (!existsSync(configPath)) {
25
+ console.error(`rafi: project.yaml not found at ${configPath}`);
26
+ process.exit(1);
27
+ }
28
+ const raw = parseYaml(readFileSync(configPath, "utf8"));
29
+ assertProjectConfig(raw);
30
+ compile(targetDir, raw, { force: opts.force });
31
+ console.log(`rafi: compiled ${targetDir}`);
32
+ });
33
+ program
34
+ .command("create")
35
+ .description("Run the walkthrough, write project.yaml, and compile the target repo.")
36
+ .argument("<project>", "path to the target repo")
37
+ .option("--defaults", "skip walkthrough and use built-in defaults")
38
+ .option("--force", "overwrite existing doc files")
39
+ .action(async (project, opts) => {
40
+ const targetDir = resolve(project);
41
+ let answers = defaultAnswers();
42
+ if (!opts.defaults) {
43
+ // Interactive walkthrough — requires Node >=20 for @clack/prompts
44
+ const { intro, outro, text, confirm, select, isCancel } = await import("@clack/prompts");
45
+ intro("rafi create — configure your AI framework");
46
+ const appName = await text({ message: "App name:", defaultValue: answers.appName });
47
+ if (isCancel(appName))
48
+ process.exit(0);
49
+ const timezone = await text({ message: "Timezone:", defaultValue: answers.timezone });
50
+ if (isCancel(timezone))
51
+ process.exit(0);
52
+ const frontendRaw = await text({
53
+ message: `Frontend stack (Enter "${answers.frontend}" to keep default, type "No UI" for no frontend):`,
54
+ defaultValue: answers.frontend,
55
+ });
56
+ if (isCancel(frontendRaw))
57
+ process.exit(0);
58
+ const backend = await text({ message: "Backend stack:", defaultValue: answers.backend });
59
+ if (isCancel(backend))
60
+ process.exit(0);
61
+ const database = await text({ message: "Database:", defaultValue: answers.database });
62
+ if (isCancel(database))
63
+ process.exit(0);
64
+ const cloudRaw = await text({
65
+ message: `Cloud provider (type "Local only" for no cloud):`,
66
+ defaultValue: answers.cloud,
67
+ });
68
+ if (isCancel(cloudRaw))
69
+ process.exit(0);
70
+ const packageManager = await text({ message: "Package manager:", defaultValue: answers.packageManager });
71
+ if (isCancel(packageManager))
72
+ process.exit(0);
73
+ const usesAI = await confirm({ message: "Will this app call LLMs / do AI generation?", initialValue: false });
74
+ if (isCancel(usesAI))
75
+ process.exit(0);
76
+ answers = {
77
+ appName: String(appName),
78
+ timezone: String(timezone),
79
+ frontend: String(frontendRaw),
80
+ backend: String(backend),
81
+ database: String(database),
82
+ cloud: String(cloudRaw),
83
+ packageManager: String(packageManager),
84
+ usesAI: Boolean(usesAI),
85
+ targets: ["claude", "codex"],
86
+ qa: true,
87
+ };
88
+ outro("Configuration collected — compiling...");
89
+ }
90
+ const config = buildProjectConfig(answers);
91
+ writeProjectYaml(targetDir, config);
92
+ compile(targetDir, config, { force: opts.force });
93
+ const aiStatus = config.flags.usesAI ? "on" : "off";
94
+ console.log(`rafi: compiled ${targetDir}`);
95
+ console.log(`rafi: AI rules: ${aiStatus === "off" ? "excluded — re-run \`rafi compile\` after setting usesAI: true to add them" : "included"}`);
96
+ });
97
+ program.parseAsync(process.argv).catch((err) => {
98
+ console.error(`rafi: ${err instanceof Error ? err.message : String(err)}`);
99
+ process.exit(1);
100
+ });
@@ -0,0 +1,19 @@
1
+ import type { ProjectConfig, HarnessTarget } from "rafi-spec";
2
+ export declare const NO_UI = "No UI";
3
+ export declare const LOCAL_ONLY = "Local only";
4
+ export interface WalkthroughAnswers {
5
+ appName: string;
6
+ timezone: string;
7
+ frontend: string;
8
+ backend: string;
9
+ database: string;
10
+ cloud: string;
11
+ packageManager: string;
12
+ usesAI: boolean;
13
+ targets: HarnessTarget[];
14
+ qa: boolean;
15
+ }
16
+ /** Default answers — equivalent to running with `--defaults`. */
17
+ export declare function defaultAnswers(): WalkthroughAnswers;
18
+ /** Map walkthrough answers to a validated ProjectConfig. */
19
+ export declare function buildProjectConfig(answers: WalkthroughAnswers): ProjectConfig;
@@ -0,0 +1,44 @@
1
+ import { loadDefaults } from "special-agents";
2
+ export const NO_UI = "No UI";
3
+ export const LOCAL_ONLY = "Local only";
4
+ /** Default answers — equivalent to running with `--defaults`. */
5
+ export function defaultAnswers() {
6
+ const d = loadDefaults();
7
+ return {
8
+ appName: "My App",
9
+ timezone: "UTC",
10
+ frontend: d.stack.frontend,
11
+ backend: d.stack.backend,
12
+ database: d.stack.database,
13
+ cloud: d.stack.cloud,
14
+ packageManager: d.stack.packageManager,
15
+ usesAI: Boolean(d.flags.usesAI),
16
+ targets: ["claude", "codex"],
17
+ qa: true,
18
+ };
19
+ }
20
+ /** Map walkthrough answers to a validated ProjectConfig. */
21
+ export function buildProjectConfig(answers) {
22
+ const hasFrontend = answers.frontend !== NO_UI;
23
+ const runsInCloud = answers.cloud !== LOCAL_ONLY;
24
+ return {
25
+ appName: answers.appName,
26
+ timezone: answers.timezone,
27
+ stack: {
28
+ frontend: hasFrontend ? answers.frontend : "",
29
+ backend: answers.backend,
30
+ database: answers.database,
31
+ cloud: runsInCloud ? answers.cloud : "",
32
+ packageManager: answers.packageManager,
33
+ },
34
+ flags: {
35
+ hasFrontend,
36
+ usesAI: answers.usesAI,
37
+ runsInCloud,
38
+ },
39
+ harness: {
40
+ targets: answers.targets,
41
+ qa: answers.qa,
42
+ },
43
+ };
44
+ }
@@ -0,0 +1,4 @@
1
+ /** Rafi neutral schema — public surface. */
2
+ export * from "./types.js";
3
+ export { rulePackSchema, skillManifestSchema, agentManifestSchema, projectConfigSchema, } from "./schemas.js";
4
+ export { type ValidationResult, validateRulePack, validateSkillManifest, validateAgentManifest, validateProjectConfig, assertRulePack, assertSkillManifest, assertAgentManifest, assertProjectConfig, } from "./validate.js";
@@ -0,0 +1,4 @@
1
+ /** Rafi neutral schema — public surface. */
2
+ export * from "./types.js";
3
+ export { rulePackSchema, skillManifestSchema, agentManifestSchema, projectConfigSchema, } from "./schemas.js";
4
+ export { validateRulePack, validateSkillManifest, validateAgentManifest, validateProjectConfig, assertRulePack, assertSkillManifest, assertAgentManifest, assertProjectConfig, } from "./validate.js";
@@ -0,0 +1,185 @@
1
+ /**
2
+ * JSON Schemas (draft-07) for the neutral types in {@link ./types}. Kept in lockstep
3
+ * with those interfaces; validated by {@link ./validate}.
4
+ */
5
+ export declare const rulePackSchema: {
6
+ readonly $id: "rafi/rulePack";
7
+ readonly type: "object";
8
+ readonly additionalProperties: false;
9
+ readonly required: readonly ["name", "category", "description", "condition", "template"];
10
+ readonly properties: {
11
+ readonly name: {
12
+ readonly type: "string";
13
+ readonly pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$";
14
+ };
15
+ readonly category: {
16
+ readonly enum: readonly ["base", "process", "domain", "templated"];
17
+ };
18
+ readonly description: {
19
+ readonly type: "string";
20
+ readonly minLength: 1;
21
+ };
22
+ readonly condition: {
23
+ readonly enum: readonly ["always", "frontend", "ai", "cloud", "backend"];
24
+ };
25
+ readonly template: {
26
+ readonly type: "boolean";
27
+ };
28
+ readonly supersededByForeman: {
29
+ readonly type: "boolean";
30
+ };
31
+ readonly body: {
32
+ readonly type: "string";
33
+ };
34
+ };
35
+ };
36
+ export declare const skillManifestSchema: {
37
+ readonly $id: "rafi/skillManifest";
38
+ readonly type: "object";
39
+ readonly additionalProperties: false;
40
+ readonly required: readonly ["name", "description"];
41
+ readonly properties: {
42
+ readonly name: {
43
+ readonly type: "string";
44
+ readonly pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$";
45
+ };
46
+ readonly description: {
47
+ readonly type: "string";
48
+ readonly minLength: 1;
49
+ };
50
+ readonly pins: {
51
+ readonly type: "array";
52
+ readonly items: {
53
+ readonly type: "string";
54
+ };
55
+ };
56
+ readonly codexPriority: {
57
+ readonly enum: readonly ["inline", "reference"];
58
+ };
59
+ readonly body: {
60
+ readonly type: "string";
61
+ };
62
+ };
63
+ };
64
+ export declare const agentManifestSchema: {
65
+ readonly $id: "rafi/agentManifest";
66
+ readonly type: "object";
67
+ readonly additionalProperties: false;
68
+ readonly required: readonly ["name", "description", "role", "packs", "skills"];
69
+ readonly properties: {
70
+ readonly name: {
71
+ readonly type: "string";
72
+ readonly pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$";
73
+ };
74
+ readonly description: {
75
+ readonly type: "string";
76
+ readonly minLength: 1;
77
+ };
78
+ readonly role: {
79
+ readonly enum: readonly ["builder", "qa", "planner", "ticket-maker"];
80
+ };
81
+ readonly packs: {
82
+ readonly type: "array";
83
+ readonly items: {
84
+ readonly type: "string";
85
+ };
86
+ };
87
+ readonly skills: {
88
+ readonly type: "array";
89
+ readonly items: {
90
+ readonly type: "string";
91
+ };
92
+ };
93
+ readonly conditionalPacks: {
94
+ readonly type: "object";
95
+ readonly additionalProperties: false;
96
+ readonly properties: {
97
+ readonly ai: {
98
+ readonly type: "array";
99
+ readonly items: {
100
+ readonly type: "string";
101
+ };
102
+ };
103
+ readonly frontend: {
104
+ readonly type: "array";
105
+ readonly items: {
106
+ readonly type: "string";
107
+ };
108
+ };
109
+ readonly cloud: {
110
+ readonly type: "array";
111
+ readonly items: {
112
+ readonly type: "string";
113
+ };
114
+ };
115
+ readonly backend: {
116
+ readonly type: "array";
117
+ readonly items: {
118
+ readonly type: "string";
119
+ };
120
+ };
121
+ };
122
+ };
123
+ readonly model: {
124
+ readonly type: readonly ["string", "null"];
125
+ };
126
+ readonly effort: {
127
+ readonly type: readonly ["string", "null"];
128
+ readonly enum: readonly ["low", "medium", "high", "xhigh", null];
129
+ };
130
+ };
131
+ };
132
+ export declare const projectConfigSchema: {
133
+ readonly $id: "rafi/projectConfig";
134
+ readonly type: "object";
135
+ readonly additionalProperties: false;
136
+ readonly required: readonly ["appName", "timezone", "stack", "flags", "harness"];
137
+ readonly properties: {
138
+ readonly appName: {
139
+ readonly type: "string";
140
+ readonly minLength: 1;
141
+ };
142
+ readonly timezone: {
143
+ readonly type: "string";
144
+ readonly minLength: 1;
145
+ };
146
+ readonly stack: {
147
+ type: string;
148
+ additionalProperties: boolean;
149
+ required: string[];
150
+ properties: {
151
+ [k: string]: {
152
+ type: string;
153
+ minLength: number;
154
+ };
155
+ };
156
+ };
157
+ readonly flags: {
158
+ type: string;
159
+ additionalProperties: boolean;
160
+ required: string[];
161
+ properties: {
162
+ [k: string]: {
163
+ type: string;
164
+ };
165
+ };
166
+ };
167
+ readonly harness: {
168
+ readonly type: "object";
169
+ readonly additionalProperties: false;
170
+ readonly required: readonly ["targets", "qa"];
171
+ readonly properties: {
172
+ readonly targets: {
173
+ readonly type: "array";
174
+ readonly minItems: 1;
175
+ readonly items: {
176
+ readonly enum: readonly ["claude", "codex"];
177
+ };
178
+ };
179
+ readonly qa: {
180
+ readonly type: "boolean";
181
+ };
182
+ };
183
+ };
184
+ };
185
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * JSON Schemas (draft-07) for the neutral types in {@link ./types}. Kept in lockstep
3
+ * with those interfaces; validated by {@link ./validate}.
4
+ */
5
+ const KEBAB = "^[a-z0-9]+(?:-[a-z0-9]+)*$";
6
+ export const rulePackSchema = {
7
+ $id: "rafi/rulePack",
8
+ type: "object",
9
+ additionalProperties: false,
10
+ required: ["name", "category", "description", "condition", "template"],
11
+ properties: {
12
+ name: { type: "string", pattern: KEBAB },
13
+ category: { enum: ["base", "process", "domain", "templated"] },
14
+ description: { type: "string", minLength: 1 },
15
+ condition: { enum: ["always", "frontend", "ai", "cloud", "backend"] },
16
+ template: { type: "boolean" },
17
+ supersededByForeman: { type: "boolean" },
18
+ body: { type: "string" },
19
+ },
20
+ };
21
+ export const skillManifestSchema = {
22
+ $id: "rafi/skillManifest",
23
+ type: "object",
24
+ additionalProperties: false,
25
+ required: ["name", "description"],
26
+ properties: {
27
+ name: { type: "string", pattern: KEBAB },
28
+ description: { type: "string", minLength: 1 },
29
+ pins: { type: "array", items: { type: "string" } },
30
+ codexPriority: { enum: ["inline", "reference"] },
31
+ body: { type: "string" },
32
+ },
33
+ };
34
+ export const agentManifestSchema = {
35
+ $id: "rafi/agentManifest",
36
+ type: "object",
37
+ additionalProperties: false,
38
+ required: ["name", "description", "role", "packs", "skills"],
39
+ properties: {
40
+ name: { type: "string", pattern: KEBAB },
41
+ description: { type: "string", minLength: 1 },
42
+ role: { enum: ["builder", "qa", "planner", "ticket-maker"] },
43
+ packs: { type: "array", items: { type: "string" } },
44
+ skills: { type: "array", items: { type: "string" } },
45
+ conditionalPacks: {
46
+ type: "object",
47
+ additionalProperties: false,
48
+ properties: {
49
+ ai: { type: "array", items: { type: "string" } },
50
+ frontend: { type: "array", items: { type: "string" } },
51
+ cloud: { type: "array", items: { type: "string" } },
52
+ backend: { type: "array", items: { type: "string" } },
53
+ },
54
+ },
55
+ model: { type: ["string", "null"] },
56
+ effort: { type: ["string", "null"], enum: ["low", "medium", "high", "xhigh", null] },
57
+ },
58
+ };
59
+ const stringRecord = (keys) => ({
60
+ type: "object",
61
+ additionalProperties: false,
62
+ required: keys,
63
+ properties: Object.fromEntries(keys.map((k) => [k, { type: "string", minLength: 1 }])),
64
+ });
65
+ const boolRecord = (keys) => ({
66
+ type: "object",
67
+ additionalProperties: false,
68
+ required: keys,
69
+ properties: Object.fromEntries(keys.map((k) => [k, { type: "boolean" }])),
70
+ });
71
+ export const projectConfigSchema = {
72
+ $id: "rafi/projectConfig",
73
+ type: "object",
74
+ additionalProperties: false,
75
+ required: ["appName", "timezone", "stack", "flags", "harness"],
76
+ properties: {
77
+ appName: { type: "string", minLength: 1 },
78
+ timezone: { type: "string", minLength: 1 },
79
+ stack: stringRecord(["frontend", "backend", "database", "cloud", "packageManager"]),
80
+ flags: boolRecord(["hasFrontend", "usesAI", "runsInCloud"]),
81
+ harness: {
82
+ type: "object",
83
+ additionalProperties: false,
84
+ required: ["targets", "qa"],
85
+ properties: {
86
+ targets: {
87
+ type: "array",
88
+ minItems: 1,
89
+ items: { enum: ["claude", "codex"] },
90
+ },
91
+ qa: { type: "boolean" },
92
+ },
93
+ },
94
+ },
95
+ };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Rafi neutral schema — the shapes that the `special-agents` library and the
3
+ * `ai-foreman` runtime must agree on. Authoring inputs (rule packs, skills,
4
+ * agent manifests) and the per-project configuration that drives composition.
5
+ */
6
+ /** Which category a rule pack belongs to (drives default load behavior). */
7
+ export type PackCategory = "base" | "process" | "domain" | "templated";
8
+ /**
9
+ * When a pack applies. `always` packs load for every project; the others load
10
+ * only when the matching project flag is on (see {@link ProjectFlags}).
11
+ */
12
+ export type PackCondition = "always" | "frontend" | "ai" | "cloud" | "backend";
13
+ /** The YAML front-matter carried by each rule pack markdown file. */
14
+ export interface RulePackFrontmatter {
15
+ /** Unique, kebab-case identifier (e.g. `security`). */
16
+ name: string;
17
+ category: PackCategory;
18
+ /** One-line summary used in indexes and pack pickers. */
19
+ description: string;
20
+ condition: PackCondition;
21
+ /** True when the body contains `{{placeholders}}` / `{{#if}}` directives. */
22
+ template: boolean;
23
+ /** When true, the pack is omitted while the foreman ticket tracker is active. */
24
+ supersededByForeman?: boolean;
25
+ }
26
+ /** A fully loaded rule pack: its front-matter plus the markdown body. */
27
+ export interface RulePack extends RulePackFrontmatter {
28
+ /** The rule text (bullets) below the front-matter. */
29
+ body: string;
30
+ }
31
+ /** How a skill should be treated when flattening for Codex (which can't lazy-load). */
32
+ export type CodexPriority = "inline" | "reference";
33
+ /**
34
+ * A skill manifest. Keeps the existing Anthropic `SKILL.md` format and adds two
35
+ * optional composition fields that non-Rafi tools can safely ignore.
36
+ */
37
+ export interface SkillManifest {
38
+ /** Unique, kebab-case identifier matching the skill directory name. */
39
+ name: string;
40
+ /** One-line trigger description (the cheap progressive-disclosure index). */
41
+ description: string;
42
+ /** Rule packs this skill wants loaded alongside it. */
43
+ pins?: string[];
44
+ /** Whether Codex flattening should inline this skill's body or just reference it. */
45
+ codexPriority?: CodexPriority;
46
+ /** The skill body (instructions). Optional in metadata-only contexts. */
47
+ body?: string;
48
+ }
49
+ /** The role an agent fills, mapped to an ai-foreman turn-type or command. */
50
+ export type AgentRole = "builder" | "qa" | "planner" | "ticket-maker";
51
+ /** Reasoning effort levels accepted by the builders. */
52
+ export type EffortLevel = "low" | "medium" | "high" | "xhigh";
53
+ /** Packs added to a role only when the matching project flag is on. */
54
+ export interface ConditionalPacks {
55
+ ai?: string[];
56
+ frontend?: string[];
57
+ cloud?: string[];
58
+ backend?: string[];
59
+ }
60
+ /**
61
+ * A role manifest: a named composition of rule packs + skills that the runtime
62
+ * loads for a given turn-type.
63
+ */
64
+ export interface AgentManifest {
65
+ /** Unique, kebab-case identifier (usually equals {@link role}). */
66
+ name: string;
67
+ description: string;
68
+ role: AgentRole;
69
+ /** Pack references; globs like `base/*` are allowed and expanded at compile time. */
70
+ packs: string[];
71
+ /** Skill names this role preloads. */
72
+ skills: string[];
73
+ /** Extra packs gated on project flags. */
74
+ conditionalPacks?: ConditionalPacks;
75
+ /** Model override; null inherits the runtime's `--model`. */
76
+ model?: string | null;
77
+ /** Effort override; null inherits the runtime's `--effort`. */
78
+ effort?: EffortLevel | null;
79
+ }
80
+ /** Which harness targets to emit native config for. */
81
+ export type HarnessTarget = "claude" | "codex";
82
+ /** The stack choices collected by `rafi create` (free-text strings). */
83
+ export interface ProjectStack {
84
+ frontend: string;
85
+ backend: string;
86
+ database: string;
87
+ cloud: string;
88
+ packageManager: string;
89
+ }
90
+ /** Boolean flags that gate conditional packs and docs. */
91
+ export interface ProjectFlags {
92
+ hasFrontend: boolean;
93
+ usesAI: boolean;
94
+ runsInCloud: boolean;
95
+ }
96
+ /** Harness emission + QA preferences. */
97
+ export interface HarnessConfig {
98
+ targets: HarnessTarget[];
99
+ qa: boolean;
100
+ }
101
+ /**
102
+ * The committed `project.yaml` in a target repo. Skipping the walkthrough uses
103
+ * the library defaults, which reproduce today's hardcoded guidance.
104
+ */
105
+ export interface ProjectConfig {
106
+ appName: string;
107
+ timezone: string;
108
+ stack: ProjectStack;
109
+ flags: ProjectFlags;
110
+ harness: HarnessConfig;
111
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Rafi neutral schema — the shapes that the `special-agents` library and the
3
+ * `ai-foreman` runtime must agree on. Authoring inputs (rule packs, skills,
4
+ * agent manifests) and the per-project configuration that drives composition.
5
+ */
6
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { RulePackFrontmatter, SkillManifest, AgentManifest, ProjectConfig } from "./types.js";
2
+ /** Outcome of validating a value against one of the neutral schemas. */
3
+ export interface ValidationResult {
4
+ valid: boolean;
5
+ /** Human-readable messages; empty when valid. */
6
+ errors: string[];
7
+ }
8
+ export declare const validateRulePack: (d: unknown) => ValidationResult;
9
+ export declare const validateSkillManifest: (d: unknown) => ValidationResult;
10
+ export declare const validateAgentManifest: (d: unknown) => ValidationResult;
11
+ export declare const validateProjectConfig: (d: unknown) => ValidationResult;
12
+ /** Validate and narrow, throwing on failure. */
13
+ export declare function assertRulePack(d: unknown): asserts d is RulePackFrontmatter;
14
+ export declare function assertSkillManifest(d: unknown): asserts d is SkillManifest;
15
+ export declare function assertAgentManifest(d: unknown): asserts d is AgentManifest;
16
+ export declare function assertProjectConfig(d: unknown): asserts d is ProjectConfig;
@@ -0,0 +1,40 @@
1
+ /** ajv-backed validation for the neutral schemas. */
2
+ import { Ajv } from "ajv";
3
+ import { rulePackSchema, skillManifestSchema, agentManifestSchema, projectConfigSchema, } from "./schemas.js";
4
+ const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
5
+ function run(fn, data) {
6
+ const valid = fn(data);
7
+ if (valid)
8
+ return { valid: true, errors: [] };
9
+ const errors = (fn.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? "is invalid"}`.trim());
10
+ return { valid: false, errors };
11
+ }
12
+ const vRulePack = ajv.compile(rulePackSchema);
13
+ const vSkill = ajv.compile(skillManifestSchema);
14
+ const vAgent = ajv.compile(agentManifestSchema);
15
+ const vProject = ajv.compile(projectConfigSchema);
16
+ export const validateRulePack = (d) => run(vRulePack, d);
17
+ export const validateSkillManifest = (d) => run(vSkill, d);
18
+ export const validateAgentManifest = (d) => run(vAgent, d);
19
+ export const validateProjectConfig = (d) => run(vProject, d);
20
+ /** Validate and narrow, throwing on failure. */
21
+ export function assertRulePack(d) {
22
+ const r = validateRulePack(d);
23
+ if (!r.valid)
24
+ throw new Error(`Invalid rule pack: ${r.errors.join("; ")}`);
25
+ }
26
+ export function assertSkillManifest(d) {
27
+ const r = validateSkillManifest(d);
28
+ if (!r.valid)
29
+ throw new Error(`Invalid skill manifest: ${r.errors.join("; ")}`);
30
+ }
31
+ export function assertAgentManifest(d) {
32
+ const r = validateAgentManifest(d);
33
+ if (!r.valid)
34
+ throw new Error(`Invalid agent manifest: ${r.errors.join("; ")}`);
35
+ }
36
+ export function assertProjectConfig(d) {
37
+ const r = validateProjectConfig(d);
38
+ if (!r.valid)
39
+ throw new Error(`Invalid project config: ${r.errors.join("; ")}`);
40
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "rafi-spec",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "description": "Rafi neutral schema: rule packs, skills, agents, and project config shapes shared by special-agents and ai-foreman.",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "tsx --test test/*.test.ts"
23
+ },
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "dependencies": {
28
+ "ajv": "^8.20.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.14.0",
32
+ "tsx": "^4.19.0",
33
+ "typescript": "^5.6.0"
34
+ }
35
+ }
@@ -0,0 +1,19 @@
1
+ /** Rafi neutral schema — public surface. */
2
+ export * from "./types.js";
3
+ export {
4
+ rulePackSchema,
5
+ skillManifestSchema,
6
+ agentManifestSchema,
7
+ projectConfigSchema,
8
+ } from "./schemas.js";
9
+ export {
10
+ type ValidationResult,
11
+ validateRulePack,
12
+ validateSkillManifest,
13
+ validateAgentManifest,
14
+ validateProjectConfig,
15
+ assertRulePack,
16
+ assertSkillManifest,
17
+ assertAgentManifest,
18
+ assertProjectConfig,
19
+ } from "./validate.js";
@@ -0,0 +1,102 @@
1
+ /**
2
+ * JSON Schemas (draft-07) for the neutral types in {@link ./types}. Kept in lockstep
3
+ * with those interfaces; validated by {@link ./validate}.
4
+ */
5
+
6
+ const KEBAB = "^[a-z0-9]+(?:-[a-z0-9]+)*$";
7
+
8
+ export const rulePackSchema = {
9
+ $id: "rafi/rulePack",
10
+ type: "object",
11
+ additionalProperties: false,
12
+ required: ["name", "category", "description", "condition", "template"],
13
+ properties: {
14
+ name: { type: "string", pattern: KEBAB },
15
+ category: { enum: ["base", "process", "domain", "templated"] },
16
+ description: { type: "string", minLength: 1 },
17
+ condition: { enum: ["always", "frontend", "ai", "cloud", "backend"] },
18
+ template: { type: "boolean" },
19
+ supersededByForeman: { type: "boolean" },
20
+ body: { type: "string" },
21
+ },
22
+ } as const;
23
+
24
+ export const skillManifestSchema = {
25
+ $id: "rafi/skillManifest",
26
+ type: "object",
27
+ additionalProperties: false,
28
+ required: ["name", "description"],
29
+ properties: {
30
+ name: { type: "string", pattern: KEBAB },
31
+ description: { type: "string", minLength: 1 },
32
+ pins: { type: "array", items: { type: "string" } },
33
+ codexPriority: { enum: ["inline", "reference"] },
34
+ body: { type: "string" },
35
+ },
36
+ } as const;
37
+
38
+ export const agentManifestSchema = {
39
+ $id: "rafi/agentManifest",
40
+ type: "object",
41
+ additionalProperties: false,
42
+ required: ["name", "description", "role", "packs", "skills"],
43
+ properties: {
44
+ name: { type: "string", pattern: KEBAB },
45
+ description: { type: "string", minLength: 1 },
46
+ role: { enum: ["builder", "qa", "planner", "ticket-maker"] },
47
+ packs: { type: "array", items: { type: "string" } },
48
+ skills: { type: "array", items: { type: "string" } },
49
+ conditionalPacks: {
50
+ type: "object",
51
+ additionalProperties: false,
52
+ properties: {
53
+ ai: { type: "array", items: { type: "string" } },
54
+ frontend: { type: "array", items: { type: "string" } },
55
+ cloud: { type: "array", items: { type: "string" } },
56
+ backend: { type: "array", items: { type: "string" } },
57
+ },
58
+ },
59
+ model: { type: ["string", "null"] },
60
+ effort: { type: ["string", "null"], enum: ["low", "medium", "high", "xhigh", null] },
61
+ },
62
+ } as const;
63
+
64
+ const stringRecord = (keys: string[]) => ({
65
+ type: "object",
66
+ additionalProperties: false,
67
+ required: keys,
68
+ properties: Object.fromEntries(keys.map((k) => [k, { type: "string", minLength: 1 }])),
69
+ });
70
+
71
+ const boolRecord = (keys: string[]) => ({
72
+ type: "object",
73
+ additionalProperties: false,
74
+ required: keys,
75
+ properties: Object.fromEntries(keys.map((k) => [k, { type: "boolean" }])),
76
+ });
77
+
78
+ export const projectConfigSchema = {
79
+ $id: "rafi/projectConfig",
80
+ type: "object",
81
+ additionalProperties: false,
82
+ required: ["appName", "timezone", "stack", "flags", "harness"],
83
+ properties: {
84
+ appName: { type: "string", minLength: 1 },
85
+ timezone: { type: "string", minLength: 1 },
86
+ stack: stringRecord(["frontend", "backend", "database", "cloud", "packageManager"]),
87
+ flags: boolRecord(["hasFrontend", "usesAI", "runsInCloud"]),
88
+ harness: {
89
+ type: "object",
90
+ additionalProperties: false,
91
+ required: ["targets", "qa"],
92
+ properties: {
93
+ targets: {
94
+ type: "array",
95
+ minItems: 1,
96
+ items: { enum: ["claude", "codex"] },
97
+ },
98
+ qa: { type: "boolean" },
99
+ },
100
+ },
101
+ },
102
+ } as const;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Rafi neutral schema — the shapes that the `special-agents` library and the
3
+ * `ai-foreman` runtime must agree on. Authoring inputs (rule packs, skills,
4
+ * agent manifests) and the per-project configuration that drives composition.
5
+ */
6
+
7
+ // ───────────────────────────── Rule packs ─────────────────────────────
8
+
9
+ /** Which category a rule pack belongs to (drives default load behavior). */
10
+ export type PackCategory = "base" | "process" | "domain" | "templated";
11
+
12
+ /**
13
+ * When a pack applies. `always` packs load for every project; the others load
14
+ * only when the matching project flag is on (see {@link ProjectFlags}).
15
+ */
16
+ export type PackCondition = "always" | "frontend" | "ai" | "cloud" | "backend";
17
+
18
+ /** The YAML front-matter carried by each rule pack markdown file. */
19
+ export interface RulePackFrontmatter {
20
+ /** Unique, kebab-case identifier (e.g. `security`). */
21
+ name: string;
22
+ category: PackCategory;
23
+ /** One-line summary used in indexes and pack pickers. */
24
+ description: string;
25
+ condition: PackCondition;
26
+ /** True when the body contains `{{placeholders}}` / `{{#if}}` directives. */
27
+ template: boolean;
28
+ /** When true, the pack is omitted while the foreman ticket tracker is active. */
29
+ supersededByForeman?: boolean;
30
+ }
31
+
32
+ /** A fully loaded rule pack: its front-matter plus the markdown body. */
33
+ export interface RulePack extends RulePackFrontmatter {
34
+ /** The rule text (bullets) below the front-matter. */
35
+ body: string;
36
+ }
37
+
38
+ // ───────────────────────────── Skills ─────────────────────────────
39
+
40
+ /** How a skill should be treated when flattening for Codex (which can't lazy-load). */
41
+ export type CodexPriority = "inline" | "reference";
42
+
43
+ /**
44
+ * A skill manifest. Keeps the existing Anthropic `SKILL.md` format and adds two
45
+ * optional composition fields that non-Rafi tools can safely ignore.
46
+ */
47
+ export interface SkillManifest {
48
+ /** Unique, kebab-case identifier matching the skill directory name. */
49
+ name: string;
50
+ /** One-line trigger description (the cheap progressive-disclosure index). */
51
+ description: string;
52
+ /** Rule packs this skill wants loaded alongside it. */
53
+ pins?: string[];
54
+ /** Whether Codex flattening should inline this skill's body or just reference it. */
55
+ codexPriority?: CodexPriority;
56
+ /** The skill body (instructions). Optional in metadata-only contexts. */
57
+ body?: string;
58
+ }
59
+
60
+ // ───────────────────────────── Agents (roles) ─────────────────────────────
61
+
62
+ /** The role an agent fills, mapped to an ai-foreman turn-type or command. */
63
+ export type AgentRole = "builder" | "qa" | "planner" | "ticket-maker";
64
+
65
+ /** Reasoning effort levels accepted by the builders. */
66
+ export type EffortLevel = "low" | "medium" | "high" | "xhigh";
67
+
68
+ /** Packs added to a role only when the matching project flag is on. */
69
+ export interface ConditionalPacks {
70
+ ai?: string[];
71
+ frontend?: string[];
72
+ cloud?: string[];
73
+ backend?: string[];
74
+ }
75
+
76
+ /**
77
+ * A role manifest: a named composition of rule packs + skills that the runtime
78
+ * loads for a given turn-type.
79
+ */
80
+ export interface AgentManifest {
81
+ /** Unique, kebab-case identifier (usually equals {@link role}). */
82
+ name: string;
83
+ description: string;
84
+ role: AgentRole;
85
+ /** Pack references; globs like `base/*` are allowed and expanded at compile time. */
86
+ packs: string[];
87
+ /** Skill names this role preloads. */
88
+ skills: string[];
89
+ /** Extra packs gated on project flags. */
90
+ conditionalPacks?: ConditionalPacks;
91
+ /** Model override; null inherits the runtime's `--model`. */
92
+ model?: string | null;
93
+ /** Effort override; null inherits the runtime's `--effort`. */
94
+ effort?: EffortLevel | null;
95
+ }
96
+
97
+ // ───────────────────────────── Project config ─────────────────────────────
98
+
99
+ /** Which harness targets to emit native config for. */
100
+ export type HarnessTarget = "claude" | "codex";
101
+
102
+ /** The stack choices collected by `rafi create` (free-text strings). */
103
+ export interface ProjectStack {
104
+ frontend: string;
105
+ backend: string;
106
+ database: string;
107
+ cloud: string;
108
+ packageManager: string;
109
+ }
110
+
111
+ /** Boolean flags that gate conditional packs and docs. */
112
+ export interface ProjectFlags {
113
+ hasFrontend: boolean;
114
+ usesAI: boolean;
115
+ runsInCloud: boolean;
116
+ }
117
+
118
+ /** Harness emission + QA preferences. */
119
+ export interface HarnessConfig {
120
+ targets: HarnessTarget[];
121
+ qa: boolean;
122
+ }
123
+
124
+ /**
125
+ * The committed `project.yaml` in a target repo. Skipping the walkthrough uses
126
+ * the library defaults, which reproduce today's hardcoded guidance.
127
+ */
128
+ export interface ProjectConfig {
129
+ appName: string;
130
+ timezone: string;
131
+ stack: ProjectStack;
132
+ flags: ProjectFlags;
133
+ harness: HarnessConfig;
134
+ }
@@ -0,0 +1,60 @@
1
+ /** ajv-backed validation for the neutral schemas. */
2
+ import { Ajv, type ValidateFunction } from "ajv";
3
+ import {
4
+ rulePackSchema,
5
+ skillManifestSchema,
6
+ agentManifestSchema,
7
+ projectConfigSchema,
8
+ } from "./schemas.js";
9
+ import type {
10
+ RulePackFrontmatter,
11
+ SkillManifest,
12
+ AgentManifest,
13
+ ProjectConfig,
14
+ } from "./types.js";
15
+
16
+ const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
17
+
18
+ /** Outcome of validating a value against one of the neutral schemas. */
19
+ export interface ValidationResult {
20
+ valid: boolean;
21
+ /** Human-readable messages; empty when valid. */
22
+ errors: string[];
23
+ }
24
+
25
+ function run(fn: ValidateFunction, data: unknown): ValidationResult {
26
+ const valid = fn(data) as boolean;
27
+ if (valid) return { valid: true, errors: [] };
28
+ const errors = (fn.errors ?? []).map(
29
+ (e) => `${e.instancePath || "(root)"} ${e.message ?? "is invalid"}`.trim(),
30
+ );
31
+ return { valid: false, errors };
32
+ }
33
+
34
+ const vRulePack = ajv.compile(rulePackSchema);
35
+ const vSkill = ajv.compile(skillManifestSchema);
36
+ const vAgent = ajv.compile(agentManifestSchema);
37
+ const vProject = ajv.compile(projectConfigSchema);
38
+
39
+ export const validateRulePack = (d: unknown): ValidationResult => run(vRulePack, d);
40
+ export const validateSkillManifest = (d: unknown): ValidationResult => run(vSkill, d);
41
+ export const validateAgentManifest = (d: unknown): ValidationResult => run(vAgent, d);
42
+ export const validateProjectConfig = (d: unknown): ValidationResult => run(vProject, d);
43
+
44
+ /** Validate and narrow, throwing on failure. */
45
+ export function assertRulePack(d: unknown): asserts d is RulePackFrontmatter {
46
+ const r = validateRulePack(d);
47
+ if (!r.valid) throw new Error(`Invalid rule pack: ${r.errors.join("; ")}`);
48
+ }
49
+ export function assertSkillManifest(d: unknown): asserts d is SkillManifest {
50
+ const r = validateSkillManifest(d);
51
+ if (!r.valid) throw new Error(`Invalid skill manifest: ${r.errors.join("; ")}`);
52
+ }
53
+ export function assertAgentManifest(d: unknown): asserts d is AgentManifest {
54
+ const r = validateAgentManifest(d);
55
+ if (!r.valid) throw new Error(`Invalid agent manifest: ${r.errors.join("; ")}`);
56
+ }
57
+ export function assertProjectConfig(d: unknown): asserts d is ProjectConfig {
58
+ const r = validateProjectConfig(d);
59
+ if (!r.valid) throw new Error(`Invalid project config: ${r.errors.join("; ")}`);
60
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@rafi-ai/cli",
3
+ "version": "0.1.0",
4
+ "description": "Rafi CLI — scaffold and compile AI framework configs for a target repo.",
5
+ "type": "module",
6
+ "bin": {
7
+ "rafi": "./dist/index.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "bundledDependencies": [
23
+ "rafi-spec"
24
+ ],
25
+ "dependencies": {
26
+ "@clack/prompts": "^1.4.0",
27
+ "commander": "^12.1.0",
28
+ "yaml": "^2.5.1",
29
+ "special-agents": "0.1.0",
30
+ "rafi-spec": "0.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.14.0",
34
+ "tsx": "^4.19.0",
35
+ "typescript": "^5.6.0"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.build.json",
39
+ "typecheck": "tsc --noEmit",
40
+ "test": "tsx --test test/*.test.ts"
41
+ }
42
+ }