Skip to content

Commit 5f05913

Browse files
authored
feat(profiles): add 7 role-based presets (#63)
* feat(profiles): add 7 role-based presets Add built-in presets for different user personas: - junior-dev: code, MRs, basic issue tracking - senior-dev: + code review, pipeline monitoring - devops: CI/CD, pipelines, variables, infrastructure - team-lead: planning, milestones, oversight - pm: planning, tracking, documentation (no code ops) - ci: minimal tools for automation - gitlab-com: rate-limit friendly for gitlab.com Each preset is optimized for its role to minimize LLM context usage while providing sufficient tools. Closes #55 * fix(profiles): correct regex anchor precedence in preset patterns Fix denied_tools_regex patterns to properly anchor at start: - Use ^manage_(a|b|c) instead of ^manage_a|^manage_b|^manage_c - The latter incorrectly matches 'manage_b' anywhere in the string Also simplify junior-dev description to match preset name.
1 parent 1255ea5 commit 5f05913

File tree

8 files changed

+391
-0
lines changed

8 files changed

+391
-0
lines changed

src/profiles/builtin/ci.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# CI/CD Bot preset - automation
2+
#
3+
# Minimal tools for CI/CD automation:
4+
# - Pipeline operations
5+
# - File management
6+
# - Variables access
7+
#
8+
# No access to development workflow tools.
9+
#
10+
# NOTE: This is a PRESET - host/auth come from environment variables.
11+
12+
description: "CI/CD Bot - minimal tools for automation"
13+
14+
read_only: false
15+
16+
features:
17+
wiki: false
18+
milestones: false
19+
pipelines: true
20+
labels: false
21+
mrs: false
22+
files: true
23+
variables: true
24+
workitems: false
25+
webhooks: false
26+
snippets: false
27+
integrations: false
28+
29+
# Block workflow-related operations
30+
denied_tools_regex: "^manage_(work_item|mr|label|milestone|wiki)"

src/profiles/builtin/devops.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# DevOps Engineer preset - CI/CD and infrastructure
2+
#
3+
# Focused on infrastructure and automation:
4+
# - Full pipeline management
5+
# - Variables and secrets
6+
# - Webhooks and integrations
7+
# - File operations for configs
8+
#
9+
# No access to development workflow tools (MRs, issues, wiki).
10+
#
11+
# NOTE: This is a PRESET - host/auth come from environment variables.
12+
13+
description: "DevOps Engineer - CI/CD pipelines, variables, infrastructure"
14+
15+
read_only: false
16+
17+
features:
18+
wiki: false
19+
milestones: false
20+
pipelines: true
21+
labels: false
22+
mrs: false
23+
files: true
24+
variables: true
25+
workitems: false
26+
webhooks: true
27+
snippets: false
28+
integrations: true
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# gitlab.com preset - rate-limit friendly
2+
#
3+
# Optimized for gitlab.com public instance:
4+
# - Standard development features enabled
5+
# - Rate-limit friendly restrictions
6+
# - Safe defaults for public projects
7+
#
8+
# Features that typically require elevated access are disabled.
9+
#
10+
# NOTE: This is a PRESET - host/auth come from environment variables.
11+
12+
description: "Optimized for gitlab.com - respects rate limits, safe defaults"
13+
14+
read_only: false
15+
16+
features:
17+
wiki: true
18+
milestones: true
19+
pipelines: true
20+
labels: true
21+
mrs: true
22+
files: true
23+
variables: false # Often restricted on public projects
24+
workitems: true
25+
webhooks: false # Requires maintainer+ access
26+
snippets: true
27+
integrations: false # Requires admin access
28+
29+
# Rate-limit friendly restrictions
30+
denied_actions:
31+
# Pagination-heavy operations
32+
- "browse_commits:list"
33+
- "browse_pipelines:list"
34+
# Operations that can trigger many API calls
35+
- "manage_pipeline:retry"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Junior Developer preset - day-to-day development
2+
#
3+
# Focused on core development tasks:
4+
# - Code browsing and file management
5+
# - Work items and merge requests
6+
# - Labels and snippets
7+
#
8+
# No access to pipelines, variables, or admin features.
9+
#
10+
# NOTE: This is a PRESET - host/auth come from environment variables.
11+
12+
description: "Junior Developer - code, MRs, basic issue tracking"
13+
14+
read_only: false
15+
16+
features:
17+
wiki: false
18+
milestones: false
19+
pipelines: false
20+
labels: true
21+
mrs: true
22+
files: true
23+
variables: false
24+
workitems: true
25+
webhooks: false
26+
snippets: true
27+
integrations: false
28+
29+
# Block pipeline and admin operations
30+
denied_tools_regex: "^manage_(pipeline|variable|webhook)"

src/profiles/builtin/pm.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Project Manager preset - planning and tracking
2+
#
3+
# Focused on project management:
4+
# - Work items and milestones
5+
# - Merge request status tracking
6+
# - Wiki and documentation
7+
# - Labels for organization
8+
#
9+
# No access to code operations, pipelines, or technical features.
10+
#
11+
# NOTE: This is a PRESET - host/auth come from environment variables.
12+
13+
description: "Project Manager - planning, tracking, documentation (no code ops)"
14+
15+
read_only: false
16+
17+
features:
18+
wiki: true
19+
milestones: true
20+
pipelines: false
21+
labels: true
22+
mrs: true
23+
files: false
24+
variables: false
25+
workitems: true
26+
webhooks: false
27+
snippets: false
28+
integrations: false
29+
30+
# Block code and pipeline operations
31+
denied_tools_regex: "^manage_(files|pipeline|variable)"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Senior Developer preset - code review and pipeline monitoring
2+
#
3+
# Extended access for senior developers:
4+
# - Full code browsing and file management
5+
# - Work items and merge requests
6+
# - Pipeline monitoring (read-only)
7+
# - Wiki and documentation
8+
#
9+
# No control over pipelines, variables, or webhooks.
10+
#
11+
# NOTE: This is a PRESET - host/auth come from environment variables.
12+
13+
description: "Senior Developer - code review, discussions, pipeline monitoring"
14+
15+
read_only: false
16+
17+
features:
18+
wiki: true
19+
milestones: false
20+
pipelines: true
21+
labels: true
22+
mrs: true
23+
files: true
24+
variables: false
25+
workitems: true
26+
webhooks: false
27+
snippets: true
28+
integrations: false
29+
30+
# Block pipeline control and webhook management
31+
denied_actions:
32+
- "manage_pipeline:cancel"
33+
- "manage_pipeline:retry"
34+
- "manage_webhook:create"
35+
- "manage_webhook:update"
36+
- "manage_webhook:delete"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Team Lead preset - planning and oversight
2+
#
3+
# Focused on team management:
4+
# - Planning with milestones and work items
5+
# - Code review via merge requests
6+
# - Pipeline monitoring
7+
# - Wiki and documentation
8+
#
9+
# No access to sensitive variables or webhooks.
10+
#
11+
# NOTE: This is a PRESET - host/auth come from environment variables.
12+
13+
description: "Team Lead - planning, milestones, team oversight, code review"
14+
15+
read_only: false
16+
17+
features:
18+
wiki: true
19+
milestones: true
20+
pipelines: true
21+
labels: true
22+
mrs: true
23+
files: true
24+
variables: false
25+
workitems: true
26+
webhooks: false
27+
snippets: true
28+
integrations: false
29+
30+
# Block sensitive operations
31+
denied_actions:
32+
- "manage_variable:create"
33+
- "manage_variable:update"
34+
- "manage_variable:delete"
35+
- "manage_webhook:create"
36+
- "manage_webhook:update"
37+
- "manage_webhook:delete"
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* Unit tests for built-in presets
3+
* Validates that all preset YAML files conform to the schema
4+
* and contain no host/auth (security requirement)
5+
*/
6+
7+
import * as fs from "fs";
8+
import * as path from "path";
9+
import * as yaml from "yaml";
10+
import { PresetSchema, Preset } from "../../../src/profiles/types";
11+
12+
// Mock logger to suppress output during tests
13+
jest.mock("../../../src/logger", () => ({
14+
logger: {
15+
info: jest.fn(),
16+
warn: jest.fn(),
17+
error: jest.fn(),
18+
debug: jest.fn(),
19+
},
20+
}));
21+
22+
describe("Built-in Presets", () => {
23+
const builtinDir = path.join(__dirname, "../../../src/profiles/builtin");
24+
25+
// Get all yaml files in the builtin directory
26+
const presetFiles = fs.readdirSync(builtinDir).filter(f => f.endsWith(".yaml"));
27+
28+
describe("all presets should be valid", () => {
29+
it.each(presetFiles)("%s should conform to PresetSchema", filename => {
30+
const filepath = path.join(builtinDir, filename);
31+
const content = fs.readFileSync(filepath, "utf8");
32+
const parsed = yaml.parse(content);
33+
34+
const result = PresetSchema.safeParse(parsed);
35+
if (!result.success) {
36+
console.error(`Validation errors for ${filename}:`, result.error.issues);
37+
}
38+
expect(result.success).toBe(true);
39+
});
40+
41+
it.each(presetFiles)("%s should have a description", filename => {
42+
const filepath = path.join(builtinDir, filename);
43+
const content = fs.readFileSync(filepath, "utf8");
44+
const parsed = yaml.parse(content) as Preset;
45+
46+
expect(parsed.description).toBeDefined();
47+
expect(parsed.description?.length).toBeGreaterThan(0);
48+
});
49+
50+
it.each(presetFiles)("%s should NOT contain host (security)", filename => {
51+
const filepath = path.join(builtinDir, filename);
52+
const content = fs.readFileSync(filepath, "utf8");
53+
const parsed = yaml.parse(content);
54+
55+
// Presets must NEVER contain host/auth
56+
expect(parsed.host).toBeUndefined();
57+
});
58+
59+
it.each(presetFiles)("%s should NOT contain auth (security)", filename => {
60+
const filepath = path.join(builtinDir, filename);
61+
const content = fs.readFileSync(filepath, "utf8");
62+
const parsed = yaml.parse(content);
63+
64+
// Presets must NEVER contain host/auth
65+
expect(parsed.auth).toBeUndefined();
66+
});
67+
68+
it.each(presetFiles)("%s should have valid denied_tools_regex if present", filename => {
69+
const filepath = path.join(builtinDir, filename);
70+
const content = fs.readFileSync(filepath, "utf8");
71+
const parsed = yaml.parse(content) as Preset;
72+
73+
if (parsed.denied_tools_regex) {
74+
expect(() => new RegExp(parsed.denied_tools_regex!)).not.toThrow();
75+
}
76+
});
77+
78+
it.each(presetFiles)("%s should have valid denied_actions format if present", filename => {
79+
const filepath = path.join(builtinDir, filename);
80+
const content = fs.readFileSync(filepath, "utf8");
81+
const parsed = yaml.parse(content) as Preset;
82+
83+
if (parsed.denied_actions) {
84+
for (const action of parsed.denied_actions) {
85+
// Format: tool:action
86+
expect(action).toMatch(/^[a-z_]+:[a-z_]+$/);
87+
}
88+
}
89+
});
90+
});
91+
92+
describe("specific preset validations", () => {
93+
it("readonly.yaml should have read_only: true", () => {
94+
const filepath = path.join(builtinDir, "readonly.yaml");
95+
const content = fs.readFileSync(filepath, "utf8");
96+
const parsed = yaml.parse(content) as Preset;
97+
98+
expect(parsed.read_only).toBe(true);
99+
});
100+
101+
it("admin.yaml should have all features enabled", () => {
102+
const filepath = path.join(builtinDir, "admin.yaml");
103+
const content = fs.readFileSync(filepath, "utf8");
104+
const parsed = yaml.parse(content) as Preset;
105+
106+
expect(parsed.features?.wiki).toBe(true);
107+
expect(parsed.features?.pipelines).toBe(true);
108+
expect(parsed.features?.variables).toBe(true);
109+
expect(parsed.features?.webhooks).toBe(true);
110+
expect(parsed.features?.integrations).toBe(true);
111+
});
112+
113+
it("junior-dev.yaml should have pipelines disabled", () => {
114+
const filepath = path.join(builtinDir, "junior-dev.yaml");
115+
const content = fs.readFileSync(filepath, "utf8");
116+
const parsed = yaml.parse(content) as Preset;
117+
118+
expect(parsed.features?.pipelines).toBe(false);
119+
expect(parsed.features?.variables).toBe(false);
120+
});
121+
122+
it("devops.yaml should have pipelines and variables enabled", () => {
123+
const filepath = path.join(builtinDir, "devops.yaml");
124+
const content = fs.readFileSync(filepath, "utf8");
125+
const parsed = yaml.parse(content) as Preset;
126+
127+
expect(parsed.features?.pipelines).toBe(true);
128+
expect(parsed.features?.variables).toBe(true);
129+
expect(parsed.features?.webhooks).toBe(true);
130+
});
131+
132+
it("pm.yaml should have files disabled", () => {
133+
const filepath = path.join(builtinDir, "pm.yaml");
134+
const content = fs.readFileSync(filepath, "utf8");
135+
const parsed = yaml.parse(content) as Preset;
136+
137+
expect(parsed.features?.files).toBe(false);
138+
expect(parsed.features?.pipelines).toBe(false);
139+
});
140+
141+
it("ci.yaml should have minimal features for automation", () => {
142+
const filepath = path.join(builtinDir, "ci.yaml");
143+
const content = fs.readFileSync(filepath, "utf8");
144+
const parsed = yaml.parse(content) as Preset;
145+
146+
expect(parsed.features?.pipelines).toBe(true);
147+
expect(parsed.features?.files).toBe(true);
148+
expect(parsed.features?.variables).toBe(true);
149+
expect(parsed.features?.wiki).toBe(false);
150+
expect(parsed.features?.workitems).toBe(false);
151+
});
152+
153+
it("gitlab-com.yaml should have rate-limit friendly restrictions", () => {
154+
const filepath = path.join(builtinDir, "gitlab-com.yaml");
155+
const content = fs.readFileSync(filepath, "utf8");
156+
const parsed = yaml.parse(content) as Preset;
157+
158+
expect(parsed.features?.webhooks).toBe(false);
159+
expect(parsed.features?.integrations).toBe(false);
160+
expect(parsed.denied_actions).toBeDefined();
161+
expect(parsed.denied_actions?.length).toBeGreaterThan(0);
162+
});
163+
});
164+
});

0 commit comments

Comments
 (0)