@ryodeushii/ai-product-team-agents 0.0.6 → 0.0.8
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 +46 -54
- package/models.yml +8 -0
- package/package.json +2 -5
- package/src/config/schema.ts +3 -0
- package/src/models/resolver.ts +13 -3
- package/src/models/types.ts +1 -0
- package/src/setup/index.ts +174 -29
- package/templates/.agents.yml +38 -23
- package/templates/opencode-plugin.ts +16 -0
- package/src/mcp/server.ts +0 -60
package/README.md
CHANGED
|
@@ -82,15 +82,20 @@ autonomy: checkpoint # supervised | checkpoint | autonomous
|
|
|
82
82
|
# Deployment target (used by devops agent)
|
|
83
83
|
deploy: vercel # vercel | fly | aws | gcp | docker | k8s
|
|
84
84
|
|
|
85
|
-
#
|
|
85
|
+
# Define your own presets (optional) — use the same name to shadow a built-in
|
|
86
|
+
presets:
|
|
87
|
+
google:
|
|
88
|
+
thinking: google/gemini-2.5-pro
|
|
89
|
+
executing: google/gemini-2.5-flash
|
|
90
|
+
creative: google/gemini-2.5-pro
|
|
91
|
+
|
|
92
|
+
# Select a preset (built-in: "anthropic"; or one you defined above)
|
|
93
|
+
preset: google
|
|
94
|
+
|
|
95
|
+
# Fine-tune individual roles on top of the preset (optional)
|
|
86
96
|
models:
|
|
87
|
-
defaults:
|
|
88
|
-
thinking: anthropic/claude-sonnet-4-6 # architect, developer, reviewer…
|
|
89
|
-
executing: anthropic/claude-haiku-4-5-20251001 # explorer, fixer, seo
|
|
90
|
-
creative: google/gemini-2.5-pro # designer, marketing
|
|
91
97
|
roles:
|
|
92
|
-
|
|
93
|
-
explorer: local/qwen2.5-coder # local model via OpenCode
|
|
98
|
+
explorer: local/qwen2.5-coder # local model via OpenCode
|
|
94
99
|
|
|
95
100
|
# Disable roles you don't need
|
|
96
101
|
roles:
|
|
@@ -132,71 +137,58 @@ Models are defined in `models.yml`. Three tiers:
|
|
|
132
137
|
- **executing** — roles that search and run tasks (Haiku)
|
|
133
138
|
- **creative** — roles that write copy and design UI (Sonnet, swap to Gemini etc.)
|
|
134
139
|
|
|
135
|
-
###
|
|
140
|
+
### Presets
|
|
136
141
|
|
|
137
|
-
|
|
142
|
+
The framework ships with one built-in preset (`anthropic`). Define your own presets in `.agents.yml` under `presets:` — project-defined presets shadow built-ins of the same name.
|
|
138
143
|
|
|
139
144
|
```yaml
|
|
145
|
+
# 1. Define presets in .agents.yml (once per project, or copy across projects)
|
|
146
|
+
presets:
|
|
147
|
+
google:
|
|
148
|
+
thinking: google/gemini-2.5-pro
|
|
149
|
+
executing: google/gemini-2.5-flash
|
|
150
|
+
creative: google/gemini-2.5-pro
|
|
151
|
+
fast:
|
|
152
|
+
thinking: anthropic/claude-haiku-4-5-20251001
|
|
153
|
+
executing: anthropic/claude-haiku-4-5-20251001
|
|
154
|
+
creative: anthropic/claude-haiku-4-5-20251001
|
|
155
|
+
|
|
156
|
+
# 2. Pick one
|
|
157
|
+
preset: google
|
|
158
|
+
|
|
159
|
+
# 3. Fine-tune on top (optional)
|
|
140
160
|
models:
|
|
141
|
-
defaults:
|
|
142
|
-
thinking: anthropic/claude-sonnet-4-6 # all thinking-tier roles
|
|
143
|
-
executing: anthropic/claude-haiku-4-5-20251001 # all executing-tier roles
|
|
144
|
-
creative: google/gemini-2.5-pro # all creative-tier roles
|
|
145
161
|
roles:
|
|
146
|
-
|
|
147
|
-
explorer: local/qwen2.5-coder # local model via OpenCode
|
|
162
|
+
explorer: local/qwen2.5-coder
|
|
148
163
|
```
|
|
149
164
|
|
|
150
165
|
Resolution order (highest to lowest priority):
|
|
151
|
-
1. Project role override
|
|
152
|
-
2. Project tier default
|
|
153
|
-
3.
|
|
154
|
-
4. Framework
|
|
155
|
-
|
|
156
|
-
Models are resolved **dynamically** — changes to `models.yml` or `.agents.yml` take effect immediately without re-running setup.
|
|
157
|
-
|
|
158
|
-
### MCP server
|
|
166
|
+
1. Project role override (`models.roles.<role>`)
|
|
167
|
+
2. Project tier default (`models.defaults.<tier>`)
|
|
168
|
+
3. Selected preset — project presets first, built-in presets fallback
|
|
169
|
+
4. Framework role default (`models.yml` roles section)
|
|
170
|
+
5. Framework tier default (`models.yml` defaults section)
|
|
159
171
|
|
|
160
|
-
|
|
172
|
+
### Auto-update on startup (Claude Code)
|
|
161
173
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
```json
|
|
165
|
-
{
|
|
166
|
-
"mcpServers": {
|
|
167
|
-
"agents-framework": {
|
|
168
|
-
"command": "bunx",
|
|
169
|
-
"args": ["--package=@ryodeushii/ai-product-team-agents", "agents-mcp"]
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
+
The setup script writes a `SessionStart` hook into `.claude/settings.json` that re-runs `--update` every time a Claude Code session starts. This means changes to `.agents.yml` (preset, model overrides) take effect automatically the next time you open Claude Code — no manual `--update` needed.
|
|
174
175
|
|
|
175
|
-
|
|
176
|
+
The hook looks like this in `.claude/settings.json`:
|
|
176
177
|
|
|
177
178
|
```json
|
|
178
179
|
{
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
"hooks": {
|
|
181
|
+
"SessionStart": [
|
|
182
|
+
{
|
|
183
|
+
"matcher": "startup",
|
|
184
|
+
"hooks": [{ "type": "command", "command": "bunx --bun @ryodeushii/ai-product-team-agents --update 2>/dev/null; true" }]
|
|
185
|
+
}
|
|
186
|
+
]
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
```
|
|
187
190
|
|
|
188
|
-
**OpenCode**
|
|
189
|
-
|
|
190
|
-
```json
|
|
191
|
-
{
|
|
192
|
-
"mcp": {
|
|
193
|
-
"agents-framework": {
|
|
194
|
-
"type": "local",
|
|
195
|
-
"command": ["npx", "-y", "-p", "@ryodeushii/ai-product-team-agents", "agents-mcp"]
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
```
|
|
191
|
+
**OpenCode** uses a local plugin instead. The setup script writes `.opencode/plugins/agents-auto-update.ts`, which hooks into `session.created` and runs `--update` automatically at the start of every OpenCode session.
|
|
200
192
|
|
|
201
193
|
---
|
|
202
194
|
|
package/models.yml
CHANGED
|
@@ -16,3 +16,11 @@ roles:
|
|
|
16
16
|
seo: anthropic/claude-haiku-4-5-20251001
|
|
17
17
|
designer: anthropic/claude-sonnet-4-6
|
|
18
18
|
marketing: anthropic/claude-sonnet-4-6
|
|
19
|
+
|
|
20
|
+
# Built-in presets. Projects can define their own in .agents.yml
|
|
21
|
+
# and shadow these by using the same name.
|
|
22
|
+
presets:
|
|
23
|
+
anthropic:
|
|
24
|
+
thinking: anthropic/claude-sonnet-4-6
|
|
25
|
+
executing: anthropic/claude-haiku-4-5-20251001
|
|
26
|
+
creative: anthropic/claude-sonnet-4-6
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@ryodeushii/ai-product-team-agents",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.8",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/ryodeushii/ai-product-team-agents"
|
|
7
7
|
},
|
|
@@ -16,13 +16,11 @@
|
|
|
16
16
|
"src/config/schema.ts",
|
|
17
17
|
"src/models/resolver.ts",
|
|
18
18
|
"src/models/types.ts",
|
|
19
|
-
"src/mcp/server.ts",
|
|
20
19
|
"src/setup/index.ts"
|
|
21
20
|
],
|
|
22
21
|
"bin": {
|
|
23
22
|
"ai-product-team-agents": "./src/setup/index.ts",
|
|
24
|
-
"agents-setup": "./src/setup/index.ts"
|
|
25
|
-
"agents-mcp": "./src/mcp/server.ts"
|
|
23
|
+
"agents-setup": "./src/setup/index.ts"
|
|
26
24
|
},
|
|
27
25
|
"scripts": {
|
|
28
26
|
"build": "tsc",
|
|
@@ -38,7 +36,6 @@
|
|
|
38
36
|
"typescript": "^5.9.3"
|
|
39
37
|
},
|
|
40
38
|
"dependencies": {
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
42
39
|
"js-yaml": "^4.1.1",
|
|
43
40
|
"zod": "^4.3.6"
|
|
44
41
|
}
|
package/src/config/schema.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const frameworkConfigSchema = z.object({
|
|
|
14
14
|
creative: z.string(),
|
|
15
15
|
}),
|
|
16
16
|
roles: z.record(z.string(), z.string()).default({}),
|
|
17
|
+
presets: z.record(z.string(), tierOverrideSchema).default({}),
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
export const projectConfigSchema = z.object({
|
|
@@ -25,6 +26,8 @@ export const projectConfigSchema = z.object({
|
|
|
25
26
|
.enum(["supervised", "checkpoint", "autonomous"])
|
|
26
27
|
.default("checkpoint"),
|
|
27
28
|
deploy: z.string().optional(),
|
|
29
|
+
preset: z.string().optional(),
|
|
30
|
+
presets: z.record(z.string(), tierOverrideSchema).optional(),
|
|
28
31
|
models: z
|
|
29
32
|
.object({
|
|
30
33
|
defaults: tierOverrideSchema.optional(),
|
package/src/models/resolver.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// src/models/resolver.ts
|
|
2
2
|
|
|
3
|
-
import type { ModelsConfig, ProjectModelsOverride, Role } from "./types";
|
|
3
|
+
import type { ModelsConfig, ProjectModelsOverride, Role, Tier } from "./types";
|
|
4
4
|
import { ROLE_TIERS } from "./types";
|
|
5
5
|
|
|
6
6
|
export function resolveModel(
|
|
7
7
|
role: Role,
|
|
8
8
|
framework: ModelsConfig,
|
|
9
9
|
project: ProjectModelsOverride = {},
|
|
10
|
+
preset?: string,
|
|
11
|
+
projectPresets: Record<string, Partial<Record<Tier, string>>> = {},
|
|
10
12
|
): string {
|
|
11
13
|
// 1. project role override
|
|
12
14
|
if (project.roles?.[role] !== undefined) return project.roles[role]!;
|
|
@@ -15,10 +17,18 @@ export function resolveModel(
|
|
|
15
17
|
const tier = ROLE_TIERS[role];
|
|
16
18
|
if (project.defaults?.[tier] !== undefined) return project.defaults[tier]!;
|
|
17
19
|
|
|
18
|
-
// 3. framework
|
|
20
|
+
// 3. preset tier — project presets shadow framework presets
|
|
21
|
+
if (preset !== undefined) {
|
|
22
|
+
const presetConfig = projectPresets[preset] ?? framework.presets[preset];
|
|
23
|
+
if (presetConfig === undefined)
|
|
24
|
+
throw new Error(`Unknown preset "${preset}"`);
|
|
25
|
+
if (presetConfig[tier] !== undefined) return presetConfig[tier]!;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 4. framework role default
|
|
19
29
|
if (framework.roles[role] !== undefined) return framework.roles[role]!;
|
|
20
30
|
|
|
21
|
-
//
|
|
31
|
+
// 5. framework tier default
|
|
22
32
|
const result = framework.defaults[tier];
|
|
23
33
|
if (result === undefined)
|
|
24
34
|
throw new Error(`No model configured for tier "${tier}"`);
|
package/src/models/types.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const ROLE_TIERS: Record<Role, Tier> = {
|
|
|
36
36
|
export interface ModelsConfig {
|
|
37
37
|
defaults: Record<Tier, string>;
|
|
38
38
|
roles: Partial<Record<Role, string>>;
|
|
39
|
+
presets: Record<string, Partial<Record<Tier, string>>>;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export interface ProjectModelsOverride {
|
package/src/setup/index.ts
CHANGED
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
// src/setup/index.ts
|
|
3
3
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
lstat,
|
|
7
|
-
mkdir,
|
|
8
|
-
readFile,
|
|
9
|
-
rm,
|
|
10
|
-
symlink,
|
|
11
|
-
writeFile,
|
|
12
|
-
} from "fs/promises";
|
|
5
|
+
import { lstat, mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
|
|
13
6
|
import { join } from "path";
|
|
7
|
+
import { loadFrameworkConfig, loadProjectConfig } from "../config/loader";
|
|
8
|
+
import { resolveModel } from "../models/resolver";
|
|
9
|
+
import { ROLES } from "../models/types";
|
|
10
|
+
import type { Role } from "../models/types";
|
|
11
|
+
|
|
12
|
+
// The SessionStart hook command written into .claude/settings.json.
|
|
13
|
+
// Runs --update on every Claude Code session start so model overrides
|
|
14
|
+
// from .agents.yml are always applied without manual intervention.
|
|
15
|
+
const HOOK_COMMAND =
|
|
16
|
+
"bunx --bun @ryodeushii/ai-product-team-agents --update 2>/dev/null; true";
|
|
14
17
|
|
|
15
18
|
interface SetupOptions {
|
|
16
19
|
projectName: string;
|
|
@@ -40,20 +43,23 @@ export async function setupProject(
|
|
|
40
43
|
await writeFile(dest, content, "utf-8");
|
|
41
44
|
};
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
await
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
// CLAUDE.md — append import if not already present
|
|
47
|
+
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
48
|
+
const claudeImport = "@./AGENTS.md";
|
|
49
|
+
const existingClaude = await readFile(claudeMdPath, "utf-8").catch(
|
|
50
|
+
(err: NodeJS.ErrnoException) =>
|
|
51
|
+
err.code === "ENOENT" ? null : Promise.reject(err),
|
|
52
|
+
);
|
|
53
|
+
if (existingClaude === null) {
|
|
54
|
+
await writeFile(claudeMdPath, `${claudeImport}\n`, "utf-8");
|
|
55
|
+
} else if (!existingClaude.includes(claudeImport)) {
|
|
56
|
+
const sep = existingClaude.endsWith("\n") ? "" : "\n";
|
|
57
|
+
await writeFile(
|
|
58
|
+
claudeMdPath,
|
|
59
|
+
`${existingClaude}${sep}${claudeImport}\n`,
|
|
60
|
+
"utf-8",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
57
63
|
|
|
58
64
|
// AGENTS.md — fill template
|
|
59
65
|
let agentsTpl: string;
|
|
@@ -88,23 +94,162 @@ export async function setupProject(
|
|
|
88
94
|
agentsYml = agentsYml.replaceAll("{{project_name}}", projectName);
|
|
89
95
|
await write(join(projectRoot, ".agents.yml"), agentsYml);
|
|
90
96
|
|
|
91
|
-
//
|
|
97
|
+
// copy role files into .claude/agents and .opencode/agents,
|
|
98
|
+
// injecting resolved model: into frontmatter for each role
|
|
92
99
|
const rolesPath = join(frameworkRoot, "roles");
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
const [framework, project] = await Promise.all([
|
|
101
|
+
loadFrameworkConfig(frameworkRoot),
|
|
102
|
+
loadProjectConfig(projectRoot),
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const roleFiles = (await readdir(rolesPath)).filter((f) => f.endsWith(".md"));
|
|
106
|
+
for (const dest of [
|
|
107
|
+
join(projectRoot, ".claude/agents"),
|
|
108
|
+
join(projectRoot, ".opencode/agents"),
|
|
109
|
+
]) {
|
|
110
|
+
await mkdir(dest, { recursive: true });
|
|
111
|
+
for (const file of roleFiles) {
|
|
112
|
+
const destFile = join(dest, file);
|
|
113
|
+
const exists = await lstat(destFile).then(
|
|
114
|
+
() => true,
|
|
115
|
+
() => false,
|
|
116
|
+
);
|
|
117
|
+
if (exists && !overwrite) continue;
|
|
118
|
+
|
|
119
|
+
let content = await readFile(join(rolesPath, file), "utf-8");
|
|
120
|
+
const roleName = file.replace(/\.md$/, "") as Role;
|
|
121
|
+
if (ROLES.includes(roleName)) {
|
|
122
|
+
const model = resolveModel(
|
|
123
|
+
roleName,
|
|
124
|
+
framework,
|
|
125
|
+
project.models ?? {},
|
|
126
|
+
project.preset,
|
|
127
|
+
project.presets ?? {},
|
|
128
|
+
);
|
|
129
|
+
// inject or replace model: in frontmatter
|
|
130
|
+
if (content.startsWith("---")) {
|
|
131
|
+
content = content.replace(
|
|
132
|
+
/^(---\n)([\s\S]*?)(---)/,
|
|
133
|
+
(_, open, body, close) => {
|
|
134
|
+
const cleaned = body.replace(/^model:.*\n?/m, "");
|
|
135
|
+
return `${open}${cleaned}model: ${model}\n${close}`;
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await writeFile(destFile, content, "utf-8");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// .claude/settings.json — add SessionStart hook for auto-update
|
|
145
|
+
await installClaudeHook(join(projectRoot, ".claude/settings.json"));
|
|
146
|
+
|
|
147
|
+
// .opencode/plugins/agents-auto-update.ts — OpenCode session.created hook
|
|
148
|
+
await installOpenCodePlugin(projectRoot, frameworkRoot);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function installOpenCodePlugin(
|
|
152
|
+
projectRoot: string,
|
|
153
|
+
frameworkRoot: string,
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
const pluginDir = join(projectRoot, ".opencode/plugins");
|
|
156
|
+
const pluginDest = join(pluginDir, "agents-auto-update.ts");
|
|
157
|
+
await mkdir(pluginDir, { recursive: true });
|
|
158
|
+
const src = await readFile(
|
|
159
|
+
join(frameworkRoot, "templates/opencode-plugin.ts"),
|
|
160
|
+
"utf-8",
|
|
161
|
+
);
|
|
162
|
+
await writeFile(pluginDest, src, "utf-8");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function installClaudeHook(settingsPath: string): Promise<void> {
|
|
166
|
+
const raw = await readFile(settingsPath, "utf-8").catch(
|
|
167
|
+
(err: NodeJS.ErrnoException) =>
|
|
168
|
+
err.code === "ENOENT" ? null : Promise.reject(err),
|
|
169
|
+
);
|
|
170
|
+
const settings = raw ? JSON.parse(raw) : {};
|
|
171
|
+
|
|
172
|
+
// idempotent: don't add duplicate hook
|
|
173
|
+
const existing: { matcher: string; hooks: { type: string; command: string }[] }[] =
|
|
174
|
+
settings.hooks?.SessionStart ?? [];
|
|
175
|
+
const alreadyInstalled = existing.some((entry) =>
|
|
176
|
+
entry.hooks?.some((h) => h.command === HOOK_COMMAND),
|
|
177
|
+
);
|
|
178
|
+
if (alreadyInstalled) return;
|
|
179
|
+
|
|
180
|
+
settings.hooks ??= {};
|
|
181
|
+
settings.hooks.SessionStart ??= [];
|
|
182
|
+
settings.hooks.SessionStart.push({
|
|
183
|
+
matcher: "startup",
|
|
184
|
+
hooks: [{ type: "command", command: HOOK_COMMAND }],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await mkdir(settingsPath.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
188
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function removeClaudeHook(settingsPath: string): Promise<void> {
|
|
192
|
+
const raw = await readFile(settingsPath, "utf-8").catch(
|
|
193
|
+
(err: NodeJS.ErrnoException) =>
|
|
194
|
+
err.code === "ENOENT" ? null : Promise.reject(err),
|
|
195
|
+
);
|
|
196
|
+
if (!raw) return;
|
|
197
|
+
|
|
198
|
+
const settings = JSON.parse(raw);
|
|
199
|
+
if (!settings.hooks?.SessionStart) return;
|
|
200
|
+
|
|
201
|
+
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
|
|
202
|
+
(entry: { hooks?: { command: string }[] }) =>
|
|
203
|
+
!entry.hooks?.some((h) => h.command === HOOK_COMMAND),
|
|
204
|
+
);
|
|
205
|
+
if (settings.hooks.SessionStart.length === 0) {
|
|
206
|
+
delete settings.hooks.SessionStart;
|
|
207
|
+
}
|
|
208
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
209
|
+
delete settings.hooks;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (Object.keys(settings).length === 0) {
|
|
213
|
+
await rm(settingsPath, { force: true });
|
|
214
|
+
} else {
|
|
215
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
216
|
+
}
|
|
95
217
|
}
|
|
96
218
|
|
|
97
219
|
export async function uninstallProject(projectRoot: string): Promise<void> {
|
|
98
|
-
|
|
220
|
+
// Remove framework-owned files outright
|
|
221
|
+
for (const f of [
|
|
99
222
|
join(projectRoot, "AGENTS.md"),
|
|
100
|
-
join(projectRoot, "CLAUDE.md"),
|
|
101
223
|
join(projectRoot, ".agents.yml"),
|
|
102
224
|
join(projectRoot, ".claude/agents"),
|
|
103
225
|
join(projectRoot, ".opencode/agents"),
|
|
104
|
-
|
|
105
|
-
|
|
226
|
+
join(projectRoot, ".opencode/plugins/agents-auto-update.ts"),
|
|
227
|
+
]) {
|
|
106
228
|
await rm(f, { recursive: true, force: true });
|
|
107
229
|
}
|
|
230
|
+
|
|
231
|
+
// CLAUDE.md — remove only the import line, keep the rest
|
|
232
|
+
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
233
|
+
const content = await readFile(claudeMdPath, "utf-8").catch(
|
|
234
|
+
(err: NodeJS.ErrnoException) =>
|
|
235
|
+
err.code === "ENOENT" ? null : Promise.reject(err),
|
|
236
|
+
);
|
|
237
|
+
if (content !== null) {
|
|
238
|
+
const updated = content
|
|
239
|
+
.split("\n")
|
|
240
|
+
.filter((l) => l.trim() !== "@./AGENTS.md")
|
|
241
|
+
.join("\n")
|
|
242
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
243
|
+
.trim();
|
|
244
|
+
if (updated.length === 0) {
|
|
245
|
+
await rm(claudeMdPath, { force: true });
|
|
246
|
+
} else {
|
|
247
|
+
await writeFile(claudeMdPath, `${updated}\n`, "utf-8");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// .claude/settings.json — remove the SessionStart hook
|
|
252
|
+
await removeClaudeHook(join(projectRoot, ".claude/settings.json"));
|
|
108
253
|
}
|
|
109
254
|
|
|
110
255
|
// CLI entrypoint
|
package/templates/.agents.yml
CHANGED
|
@@ -19,35 +19,50 @@ autonomy: checkpoint # supervised | checkpoint | autonomous
|
|
|
19
19
|
# Deployment target (used by devops agent)
|
|
20
20
|
# deploy: vercel # vercel | fly | aws | gcp | docker | k8s
|
|
21
21
|
|
|
22
|
-
# ─── Model
|
|
22
|
+
# ─── Model configuration ─────────────────────────────────────────────────────
|
|
23
23
|
#
|
|
24
24
|
# Resolution order (highest → lowest priority):
|
|
25
|
-
# 1. models.roles.<role>
|
|
26
|
-
# 2. models.defaults.<tier>
|
|
27
|
-
# 3.
|
|
28
|
-
# 4. framework
|
|
25
|
+
# 1. models.roles.<role> — this project, specific role
|
|
26
|
+
# 2. models.defaults.<tier> — this project, whole tier
|
|
27
|
+
# 3. presets.<selected>.<tier> — selected preset (project first, then built-in)
|
|
28
|
+
# 4. framework role default — models.yml roles section
|
|
29
|
+
# 5. framework tier default — models.yml defaults section
|
|
30
|
+
#
|
|
31
|
+
# ── Step 1: define your presets (optional) ──────────────────────────────────
|
|
32
|
+
#
|
|
33
|
+
# Built-in preset: "anthropic" (sonnet/haiku). Add your own here and they
|
|
34
|
+
# shadow built-ins of the same name.
|
|
35
|
+
#
|
|
36
|
+
# presets:
|
|
37
|
+
# google:
|
|
38
|
+
# thinking: google/gemini-2.5-pro
|
|
39
|
+
# executing: google/gemini-2.5-flash
|
|
40
|
+
# creative: google/gemini-2.5-pro
|
|
41
|
+
# openai:
|
|
42
|
+
# thinking: openai/gpt-4.1
|
|
43
|
+
# executing: openai/gpt-4.1-mini
|
|
44
|
+
# creative: openai/gpt-4.1
|
|
45
|
+
# fast:
|
|
46
|
+
# thinking: anthropic/claude-haiku-4-5-20251001
|
|
47
|
+
# executing: anthropic/claude-haiku-4-5-20251001
|
|
48
|
+
# creative: anthropic/claude-haiku-4-5-20251001
|
|
49
|
+
#
|
|
50
|
+
# ── Step 2: pick a preset ───────────────────────────────────────────────────
|
|
51
|
+
#
|
|
52
|
+
# preset: anthropic # built-in default
|
|
53
|
+
# preset: google # example project-defined preset above
|
|
54
|
+
#
|
|
55
|
+
# ── Step 3: fine-tune on top of the preset (optional) ───────────────────────
|
|
29
56
|
#
|
|
30
57
|
# models:
|
|
31
58
|
# defaults:
|
|
32
|
-
# thinking: anthropic/claude-sonnet-4-6 #
|
|
33
|
-
# executing: anthropic/claude-haiku-4-5-20251001
|
|
34
|
-
# creative: google/gemini-2.5-pro
|
|
59
|
+
# thinking: anthropic/claude-sonnet-4-6 # all thinking-tier roles
|
|
60
|
+
# executing: anthropic/claude-haiku-4-5-20251001
|
|
61
|
+
# creative: google/gemini-2.5-pro
|
|
35
62
|
# roles:
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# reviewer: anthropic/claude-sonnet-4-6
|
|
40
|
-
# qa: anthropic/claude-sonnet-4-6
|
|
41
|
-
# devops: anthropic/claude-sonnet-4-6
|
|
42
|
-
# pm: anthropic/claude-sonnet-4-6
|
|
43
|
-
# explorer: anthropic/claude-haiku-4-5-20251001
|
|
44
|
-
# fixer: anthropic/claude-haiku-4-5-20251001
|
|
45
|
-
# seo: anthropic/claude-haiku-4-5-20251001
|
|
46
|
-
# designer: google/gemini-2.5-pro
|
|
47
|
-
# marketing: google/gemini-2.5-pro
|
|
48
|
-
# # local models via OpenCode:
|
|
49
|
-
# # explorer: local/qwen2.5-coder
|
|
50
|
-
# # fixer: local/qwen2.5-coder
|
|
63
|
+
# architect: anthropic/claude-sonnet-4-6 # one specific role
|
|
64
|
+
# explorer: local/qwen2.5-coder # local model via OpenCode
|
|
65
|
+
# designer: google/gemini-2.5-pro
|
|
51
66
|
|
|
52
67
|
# ─── Disable roles you don't need ───────────────────────────────────────────
|
|
53
68
|
# roles:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Auto-generated by @ryodeushii/ai-product-team-agents
|
|
2
|
+
// Runs --update on session start so model overrides in .agents.yml
|
|
3
|
+
// are always applied without manual intervention.
|
|
4
|
+
// To remove: bunx @ryodeushii/ai-product-team-agents --uninstall
|
|
5
|
+
|
|
6
|
+
export const AgentsAutoUpdate = async ({
|
|
7
|
+
$,
|
|
8
|
+
}: {
|
|
9
|
+
$: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;
|
|
10
|
+
}) => {
|
|
11
|
+
return {
|
|
12
|
+
"session.created": async () => {
|
|
13
|
+
await ($ as any)`bunx --bun @ryodeushii/ai-product-team-agents --update`.quiet().nothrow();
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
};
|
package/src/mcp/server.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
// src/mcp/server.ts
|
|
3
|
-
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
import { loadFrameworkConfig, loadProjectConfig } from "../config/loader";
|
|
9
|
-
import { resolveModel } from "../models/resolver";
|
|
10
|
-
import type { Role } from "../models/types";
|
|
11
|
-
import { ROLES } from "../models/types";
|
|
12
|
-
|
|
13
|
-
const FRAMEWORK_ROOT = fileURLToPath(new URL("../../", import.meta.url));
|
|
14
|
-
|
|
15
|
-
export async function resolveModelForProject(
|
|
16
|
-
role: string,
|
|
17
|
-
projectRoot: string,
|
|
18
|
-
frameworkRoot = FRAMEWORK_ROOT,
|
|
19
|
-
): Promise<string> {
|
|
20
|
-
if (!ROLES.includes(role as Role)) throw new Error(`Unknown role: ${role}`);
|
|
21
|
-
const [framework, project] = await Promise.all([
|
|
22
|
-
loadFrameworkConfig(frameworkRoot),
|
|
23
|
-
loadProjectConfig(projectRoot),
|
|
24
|
-
]);
|
|
25
|
-
return resolveModel(role as Role, framework, project.models ?? {});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createMcpServer(): McpServer {
|
|
29
|
-
const server = new McpServer({
|
|
30
|
-
name: "agents-framework",
|
|
31
|
-
version: "0.1.0",
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
server.tool(
|
|
35
|
-
"resolve_model",
|
|
36
|
-
"Resolve the model for an agent role, applying project overrides",
|
|
37
|
-
{
|
|
38
|
-
role: z.string().describe("Agent role name"),
|
|
39
|
-
project_root: z.string().describe("Absolute path to target project root"),
|
|
40
|
-
},
|
|
41
|
-
async ({ role, project_root }) => {
|
|
42
|
-
try {
|
|
43
|
-
const model = await resolveModelForProject(role, project_root);
|
|
44
|
-
return { content: [{ type: "text", text: model }] };
|
|
45
|
-
} catch (err) {
|
|
46
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
return server;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// entrypoint when run directly
|
|
56
|
-
if (import.meta.main) {
|
|
57
|
-
const server = createMcpServer();
|
|
58
|
-
const transport = new StdioServerTransport();
|
|
59
|
-
await server.connect(transport);
|
|
60
|
-
}
|