@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 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
- # Model overrides all optional, framework defaults apply otherwise
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
- architect: anthropic/claude-sonnet-4-6 # override a specific role
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
- ### Per-project overrides
140
+ ### Presets
136
141
 
137
- In your project's `.agents.yml`, override any tier or individual role:
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
- architect: openai/gpt-4.1 # one specific role
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. Framework role default
154
- 4. Framework tier default
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
- The framework ships an MCP server that exposes a `resolve_model(role, project_root)` tool for dynamic model resolution.
172
+ ### Auto-update on startup (Claude Code)
161
173
 
162
- Add to your Claude Code MCP config (`~/.claude/mcp.json` or project-level `.mcp.json`):
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
- Or with npx:
176
+ The hook looks like this in `.claude/settings.json`:
176
177
 
177
178
  ```json
178
179
  {
179
- "mcpServers": {
180
- "agents-framework": {
181
- "command": "npx",
182
- "args": ["-y", "-p", "@ryodeushii/ai-product-team-agents", "agents-mcp"]
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** add to `opencode.json` (project root or `~/.config/opencode/opencode.json`):
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.6",
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
  }
@@ -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(),
@@ -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 role default
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
- // 4. framework tier default
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}"`);
@@ -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 {
@@ -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
- const link = async (target: string, linkPath: string) => {
44
- try {
45
- await lstat(linkPath);
46
- if (!overwrite) return;
47
- await rm(linkPath, { recursive: true, force: true });
48
- } catch (err) {
49
- if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
50
- }
51
- await mkdir(join(linkPath, ".."), { recursive: true });
52
- await symlink(target, linkPath);
53
- };
54
-
55
- // CLAUDE.md — one-liner import
56
- await write(join(projectRoot, "CLAUDE.md"), "@./AGENTS.md\n");
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
- // symlink roles into .claude/agents and .opencode/agents
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
- await link(rolesPath, join(projectRoot, ".claude/agents"));
94
- await link(rolesPath, join(projectRoot, ".opencode/agents"));
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
- const files = [
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
- for (const f of files) {
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
@@ -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 overrides ────────────────────────────────────────────────────────
22
+ # ─── Model configuration ─────────────────────────────────────────────────────
23
23
  #
24
24
  # Resolution order (highest → lowest priority):
25
- # 1. models.roles.<role> — this project, specific role
26
- # 2. models.defaults.<tier> — this project, whole tier
27
- # 3. framework roles default models.yml roles section
28
- # 4. framework tier default — models.yml defaults section
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 # architect, developer, reviewer…
33
- # executing: anthropic/claude-haiku-4-5-20251001 # explorer, fixer, seo
34
- # creative: google/gemini-2.5-pro # designer, marketing
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
- # orchestrator: anthropic/claude-sonnet-4-6
37
- # architect: anthropic/claude-sonnet-4-6
38
- # developer: anthropic/claude-sonnet-4-6
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
- }