@ryodeushii/ai-product-team-agents 0.0.6 → 0.0.7
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 +41 -43
- package/models.yml +25 -0
- package/package.json +2 -5
- package/src/config/schema.ts +2 -0
- package/src/models/resolver.ts +11 -2
- package/src/models/types.ts +1 -0
- package/src/setup/index.ts +173 -29
- package/templates/.agents.yml +9 -2
- package/templates/opencode-plugin.ts +16 -0
- package/src/mcp/server.ts +0 -60
package/README.md
CHANGED
|
@@ -132,71 +132,69 @@ Models are defined in `models.yml`. Three tiers:
|
|
|
132
132
|
- **executing** — roles that search and run tasks (Haiku)
|
|
133
133
|
- **creative** — roles that write copy and design UI (Sonnet, swap to Gemini etc.)
|
|
134
134
|
|
|
135
|
+
### Presets
|
|
136
|
+
|
|
137
|
+
Switch all tiers at once with a named preset:
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
preset: google # gemini 2.5 pro for thinking/creative, flash for executing
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| Preset | thinking | executing | creative |
|
|
144
|
+
|---|---|---|---|
|
|
145
|
+
| `balanced` | claude-sonnet-4-6 | claude-haiku-4-5 | claude-sonnet-4-6 |
|
|
146
|
+
| `fast` | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 |
|
|
147
|
+
| `google` | gemini-2.5-pro | gemini-2.5-flash | gemini-2.5-pro |
|
|
148
|
+
| `openai` | gpt-4.1 | gpt-4.1-mini | gpt-4.1 |
|
|
149
|
+
|
|
135
150
|
### Per-project overrides
|
|
136
151
|
|
|
137
|
-
|
|
152
|
+
Mix a preset with per-role overrides:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
preset: google
|
|
156
|
+
models:
|
|
157
|
+
roles:
|
|
158
|
+
explorer: local/qwen2.5-coder # override one role on top of preset
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Or override individual tiers without a preset:
|
|
138
162
|
|
|
139
163
|
```yaml
|
|
140
164
|
models:
|
|
141
165
|
defaults:
|
|
142
|
-
|
|
143
|
-
executing: anthropic/claude-haiku-4-5-20251001 # all executing-tier roles
|
|
144
|
-
creative: google/gemini-2.5-pro # all creative-tier roles
|
|
166
|
+
creative: google/gemini-2.5-pro # swap just the creative tier
|
|
145
167
|
roles:
|
|
146
|
-
architect: openai/gpt-4.1
|
|
147
|
-
explorer: local/qwen2.5-coder # local model via OpenCode
|
|
168
|
+
architect: openai/gpt-4.1 # one specific role
|
|
148
169
|
```
|
|
149
170
|
|
|
150
171
|
Resolution order (highest to lowest priority):
|
|
151
172
|
1. Project role override
|
|
152
173
|
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.
|
|
174
|
+
3. Preset tier
|
|
175
|
+
4. Framework role default
|
|
176
|
+
5. Framework tier default
|
|
157
177
|
|
|
158
|
-
###
|
|
178
|
+
### Auto-update on startup (Claude Code)
|
|
159
179
|
|
|
160
|
-
The
|
|
180
|
+
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.
|
|
161
181
|
|
|
162
|
-
|
|
182
|
+
The hook looks like this in `.claude/settings.json`:
|
|
163
183
|
|
|
164
184
|
```json
|
|
165
185
|
{
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
186
|
+
"hooks": {
|
|
187
|
+
"SessionStart": [
|
|
188
|
+
{
|
|
189
|
+
"matcher": "startup",
|
|
190
|
+
"hooks": [{ "type": "command", "command": "bunx --bun @ryodeushii/ai-product-team-agents --update 2>/dev/null; true" }]
|
|
191
|
+
}
|
|
192
|
+
]
|
|
171
193
|
}
|
|
172
194
|
}
|
|
173
195
|
```
|
|
174
196
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
```json
|
|
178
|
-
{
|
|
179
|
-
"mcpServers": {
|
|
180
|
-
"agents-framework": {
|
|
181
|
-
"command": "npx",
|
|
182
|
-
"args": ["-y", "-p", "@ryodeushii/ai-product-team-agents", "agents-mcp"]
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
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
|
-
```
|
|
197
|
+
**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
198
|
|
|
201
199
|
---
|
|
202
200
|
|
package/models.yml
CHANGED
|
@@ -16,3 +16,28 @@ 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
|
+
presets:
|
|
21
|
+
# Anthropic — balanced quality/cost (framework default)
|
|
22
|
+
balanced:
|
|
23
|
+
thinking: anthropic/claude-sonnet-4-6
|
|
24
|
+
executing: anthropic/claude-haiku-4-5-20251001
|
|
25
|
+
creative: anthropic/claude-sonnet-4-6
|
|
26
|
+
|
|
27
|
+
# Anthropic — all fast/cheap
|
|
28
|
+
fast:
|
|
29
|
+
thinking: anthropic/claude-haiku-4-5-20251001
|
|
30
|
+
executing: anthropic/claude-haiku-4-5-20251001
|
|
31
|
+
creative: anthropic/claude-haiku-4-5-20251001
|
|
32
|
+
|
|
33
|
+
# Google Gemini
|
|
34
|
+
google:
|
|
35
|
+
thinking: google/gemini-2.5-pro
|
|
36
|
+
executing: google/gemini-2.5-flash
|
|
37
|
+
creative: google/gemini-2.5-pro
|
|
38
|
+
|
|
39
|
+
# OpenAI
|
|
40
|
+
openai:
|
|
41
|
+
thinking: openai/gpt-4.1
|
|
42
|
+
executing: openai/gpt-4.1-mini
|
|
43
|
+
creative: openai/gpt-4.1
|
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.7",
|
|
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,7 @@ 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(),
|
|
28
30
|
models: z
|
|
29
31
|
.object({
|
|
30
32
|
defaults: tierOverrideSchema.optional(),
|
package/src/models/resolver.ts
CHANGED
|
@@ -7,6 +7,7 @@ export function resolveModel(
|
|
|
7
7
|
role: Role,
|
|
8
8
|
framework: ModelsConfig,
|
|
9
9
|
project: ProjectModelsOverride = {},
|
|
10
|
+
preset?: string,
|
|
10
11
|
): string {
|
|
11
12
|
// 1. project role override
|
|
12
13
|
if (project.roles?.[role] !== undefined) return project.roles[role]!;
|
|
@@ -15,10 +16,18 @@ export function resolveModel(
|
|
|
15
16
|
const tier = ROLE_TIERS[role];
|
|
16
17
|
if (project.defaults?.[tier] !== undefined) return project.defaults[tier]!;
|
|
17
18
|
|
|
18
|
-
// 3.
|
|
19
|
+
// 3. preset tier
|
|
20
|
+
if (preset !== undefined) {
|
|
21
|
+
const presetConfig = framework.presets[preset];
|
|
22
|
+
if (presetConfig === undefined)
|
|
23
|
+
throw new Error(`Unknown preset "${preset}"`);
|
|
24
|
+
if (presetConfig[tier] !== undefined) return presetConfig[tier]!;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 4. framework role default
|
|
19
28
|
if (framework.roles[role] !== undefined) return framework.roles[role]!;
|
|
20
29
|
|
|
21
|
-
//
|
|
30
|
+
// 5. framework tier default
|
|
22
31
|
const result = framework.defaults[tier];
|
|
23
32
|
if (result === undefined)
|
|
24
33
|
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,161 @@ 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
|
+
);
|
|
128
|
+
// inject or replace model: in frontmatter
|
|
129
|
+
if (content.startsWith("---")) {
|
|
130
|
+
content = content.replace(
|
|
131
|
+
/^(---\n)([\s\S]*?)(---)/,
|
|
132
|
+
(_, open, body, close) => {
|
|
133
|
+
const cleaned = body.replace(/^model:.*\n?/m, "");
|
|
134
|
+
return `${open}${cleaned}model: ${model}\n${close}`;
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
await writeFile(destFile, content, "utf-8");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// .claude/settings.json — add SessionStart hook for auto-update
|
|
144
|
+
await installClaudeHook(join(projectRoot, ".claude/settings.json"));
|
|
145
|
+
|
|
146
|
+
// .opencode/plugins/agents-auto-update.ts — OpenCode session.created hook
|
|
147
|
+
await installOpenCodePlugin(projectRoot, frameworkRoot);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function installOpenCodePlugin(
|
|
151
|
+
projectRoot: string,
|
|
152
|
+
frameworkRoot: string,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const pluginDir = join(projectRoot, ".opencode/plugins");
|
|
155
|
+
const pluginDest = join(pluginDir, "agents-auto-update.ts");
|
|
156
|
+
await mkdir(pluginDir, { recursive: true });
|
|
157
|
+
const src = await readFile(
|
|
158
|
+
join(frameworkRoot, "templates/opencode-plugin.ts"),
|
|
159
|
+
"utf-8",
|
|
160
|
+
);
|
|
161
|
+
await writeFile(pluginDest, src, "utf-8");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function installClaudeHook(settingsPath: string): Promise<void> {
|
|
165
|
+
const raw = await readFile(settingsPath, "utf-8").catch(
|
|
166
|
+
(err: NodeJS.ErrnoException) =>
|
|
167
|
+
err.code === "ENOENT" ? null : Promise.reject(err),
|
|
168
|
+
);
|
|
169
|
+
const settings = raw ? JSON.parse(raw) : {};
|
|
170
|
+
|
|
171
|
+
// idempotent: don't add duplicate hook
|
|
172
|
+
const existing: { matcher: string; hooks: { type: string; command: string }[] }[] =
|
|
173
|
+
settings.hooks?.SessionStart ?? [];
|
|
174
|
+
const alreadyInstalled = existing.some((entry) =>
|
|
175
|
+
entry.hooks?.some((h) => h.command === HOOK_COMMAND),
|
|
176
|
+
);
|
|
177
|
+
if (alreadyInstalled) return;
|
|
178
|
+
|
|
179
|
+
settings.hooks ??= {};
|
|
180
|
+
settings.hooks.SessionStart ??= [];
|
|
181
|
+
settings.hooks.SessionStart.push({
|
|
182
|
+
matcher: "startup",
|
|
183
|
+
hooks: [{ type: "command", command: HOOK_COMMAND }],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await mkdir(settingsPath.replace(/\/[^/]+$/, ""), { recursive: true });
|
|
187
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function removeClaudeHook(settingsPath: string): Promise<void> {
|
|
191
|
+
const raw = await readFile(settingsPath, "utf-8").catch(
|
|
192
|
+
(err: NodeJS.ErrnoException) =>
|
|
193
|
+
err.code === "ENOENT" ? null : Promise.reject(err),
|
|
194
|
+
);
|
|
195
|
+
if (!raw) return;
|
|
196
|
+
|
|
197
|
+
const settings = JSON.parse(raw);
|
|
198
|
+
if (!settings.hooks?.SessionStart) return;
|
|
199
|
+
|
|
200
|
+
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
|
|
201
|
+
(entry: { hooks?: { command: string }[] }) =>
|
|
202
|
+
!entry.hooks?.some((h) => h.command === HOOK_COMMAND),
|
|
203
|
+
);
|
|
204
|
+
if (settings.hooks.SessionStart.length === 0) {
|
|
205
|
+
delete settings.hooks.SessionStart;
|
|
206
|
+
}
|
|
207
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
208
|
+
delete settings.hooks;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (Object.keys(settings).length === 0) {
|
|
212
|
+
await rm(settingsPath, { force: true });
|
|
213
|
+
} else {
|
|
214
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
215
|
+
}
|
|
95
216
|
}
|
|
96
217
|
|
|
97
218
|
export async function uninstallProject(projectRoot: string): Promise<void> {
|
|
98
|
-
|
|
219
|
+
// Remove framework-owned files outright
|
|
220
|
+
for (const f of [
|
|
99
221
|
join(projectRoot, "AGENTS.md"),
|
|
100
|
-
join(projectRoot, "CLAUDE.md"),
|
|
101
222
|
join(projectRoot, ".agents.yml"),
|
|
102
223
|
join(projectRoot, ".claude/agents"),
|
|
103
224
|
join(projectRoot, ".opencode/agents"),
|
|
104
|
-
|
|
105
|
-
|
|
225
|
+
join(projectRoot, ".opencode/plugins/agents-auto-update.ts"),
|
|
226
|
+
]) {
|
|
106
227
|
await rm(f, { recursive: true, force: true });
|
|
107
228
|
}
|
|
229
|
+
|
|
230
|
+
// CLAUDE.md — remove only the import line, keep the rest
|
|
231
|
+
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
232
|
+
const content = await readFile(claudeMdPath, "utf-8").catch(
|
|
233
|
+
(err: NodeJS.ErrnoException) =>
|
|
234
|
+
err.code === "ENOENT" ? null : Promise.reject(err),
|
|
235
|
+
);
|
|
236
|
+
if (content !== null) {
|
|
237
|
+
const updated = content
|
|
238
|
+
.split("\n")
|
|
239
|
+
.filter((l) => l.trim() !== "@./AGENTS.md")
|
|
240
|
+
.join("\n")
|
|
241
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
242
|
+
.trim();
|
|
243
|
+
if (updated.length === 0) {
|
|
244
|
+
await rm(claudeMdPath, { force: true });
|
|
245
|
+
} else {
|
|
246
|
+
await writeFile(claudeMdPath, `${updated}\n`, "utf-8");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// .claude/settings.json — remove the SessionStart hook
|
|
251
|
+
await removeClaudeHook(join(projectRoot, ".claude/settings.json"));
|
|
108
252
|
}
|
|
109
253
|
|
|
110
254
|
// CLI entrypoint
|
package/templates/.agents.yml
CHANGED
|
@@ -24,8 +24,15 @@ autonomy: checkpoint # supervised | checkpoint | autonomous
|
|
|
24
24
|
# Resolution order (highest → lowest priority):
|
|
25
25
|
# 1. models.roles.<role> — this project, specific role
|
|
26
26
|
# 2. models.defaults.<tier> — this project, whole tier
|
|
27
|
-
# 3.
|
|
28
|
-
# 4. framework
|
|
27
|
+
# 3. preset.<tier> — named preset from models.yml
|
|
28
|
+
# 4. framework roles default — models.yml roles section
|
|
29
|
+
# 5. framework tier default — models.yml defaults section
|
|
30
|
+
#
|
|
31
|
+
# Presets (switch all tiers at once):
|
|
32
|
+
# preset: balanced # default — anthropic sonnet/haiku
|
|
33
|
+
# preset: fast # all haiku — cheap and quick
|
|
34
|
+
# preset: google # gemini 2.5 pro/flash
|
|
35
|
+
# preset: openai # gpt-4.1/mini
|
|
29
36
|
#
|
|
30
37
|
# models:
|
|
31
38
|
# defaults:
|
|
@@ -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
|
-
}
|