diff --git a/packages/guardrails/managed/opencode.json b/packages/guardrails/managed/opencode.json index 5806b2bef714..b352db54d0db 100644 --- a/packages/guardrails/managed/opencode.json +++ b/packages/guardrails/managed/opencode.json @@ -79,7 +79,25 @@ "external_directory": "ask", "bash": { "*": "ask", + "node *": "allow", + "npm *": "allow", + "npx *": "allow", + "bun *": "allow", + "git status*": "allow", + "git diff*": "allow", + "git log*": "allow", + "git show*": "allow", + "git branch*": "allow", + "git remote*": "allow", + "git stash*": "allow", + "gh *": "allow", + "ls *": "allow", + "pwd": "allow", + "which *": "allow", + "echo *": "allow", + "cat *": "ask", "rm -rf *": "deny", + "rm -r *": "deny", "sudo *": "deny", "curl * | sh*": "deny", "wget * | sh*": "deny" diff --git a/packages/guardrails/profile/agents/implement.md b/packages/guardrails/profile/agents/implement.md index e28ff7d21a6b..6d112fd28f4f 100644 --- a/packages/guardrails/profile/agents/implement.md +++ b/packages/guardrails/profile/agents/implement.md @@ -5,12 +5,18 @@ permission: question: allow plan_enter: allow bash: + "*": ask "git checkout -- *": deny "git merge *": deny "git push --force*": deny "git push * --force*": deny "git reset --hard*": deny "gh pr merge *": deny + "rm -rf *": deny + "rm -r *": deny + "sudo *": deny + "curl * | sh*": deny + "wget * | sh*": deny --- Implement changes in bounded increments. diff --git a/packages/guardrails/profile/opencode.json b/packages/guardrails/profile/opencode.json index 6cf165d34607..dbe1326ead04 100644 --- a/packages/guardrails/profile/opencode.json +++ b/packages/guardrails/profile/opencode.json @@ -80,7 +80,25 @@ "external_directory": "ask", "bash": { "*": "ask", + "node *": "allow", + "npm *": "allow", + "npx *": "allow", + "bun *": "allow", + "git status*": "allow", + "git diff*": "allow", + "git log*": "allow", + "git show*": "allow", + "git branch*": "allow", + "git remote*": "allow", + "git stash*": "allow", + "gh *": "allow", + "ls *": "allow", + "pwd": "allow", + "which *": "allow", + "echo *": "allow", + "cat *": "ask", "rm -rf *": "deny", + "rm -r *": "deny", "sudo *": "deny", "git checkout -- *": "deny", "git merge *": "deny", diff --git a/packages/guardrails/profile/plugins/guardrail.ts b/packages/guardrails/profile/plugins/guardrail.ts index 2b4fbd6c49b1..e35a7b5765d4 100644 --- a/packages/guardrails/profile/plugins/guardrail.ts +++ b/packages/guardrails/profile/plugins/guardrail.ts @@ -437,7 +437,7 @@ export default async function guardrail(input: { if ((item.tool === "edit" || item.tool === "write") && file && code(file)) { const count = await budget() if (count >= 4) { - const err = `context budget exceeded after ${count} source reads; narrow scope or delegate before editing` + const err = `context budget exceeded after ${count} source reads; call the team tool to delegate this edit to an isolated worker, or narrow scope` await mark({ last_block: item.tool, last_file: rel(input.worktree, file), last_reason: err }) throw new Error(text(err)) } diff --git a/packages/guardrails/profile/plugins/team.ts b/packages/guardrails/profile/plugins/team.ts index baeff52367a7..58fcea9f3913 100644 --- a/packages/guardrails/profile/plugins/team.ts +++ b/packages/guardrails/profile/plugins/team.ts @@ -165,6 +165,11 @@ function mut(cmd: string) { function big(text: string) { const data = text.trim() if (!data) return false + // Exempt read-only investigation requests that start with investigation verbs + // and do NOT contain write-intent keywords + const readOnly = /^\s*(investigate|diagnose|explain|analyze|check|status|report|describe|show|list|review|audit|inspect|確認|調査|分析|説明|レビュー)/i.test(data) + && !/(implement|create|rewrite|patch|refactor|fix|add|edit|write|modify|実装|改修|修正|追加)/i.test(data) + if (readOnly) return false const plan = (data.match(/^\s*([-*]|\d+\.)\s+/gm) ?? []).length const impl = /(implement|implementation|build|create|add|fix|refactor|rewrite|patch|parallel|subagent|team|background|worker|修正|実装|追加|改修|並列|サブエージェント|チーム)/i.test( data, @@ -475,7 +480,7 @@ export default async function team(input: { }, async execute(args, ctx) { defs(args.tasks) - if (args.tasks.length < 2) throw new Error("team requires at least two tasks") + if (args.tasks.length < 1) throw new Error("team requires at least one task") ctx.metadata({ title: "team run", metadata: { @@ -738,7 +743,7 @@ export default async function team(input: { messageID: out.message.id, type: "text", text: - "Parallel implementation policy is active for this request. Before any edit, write, apply_patch, or mutating bash call, you MUST call the `team` tool and fan out at least two worker tasks. Mark tasks that should edit code with `write: true`; those tasks will be isolated in git worktrees and merged back when possible. Use `background` only for side work that should keep running after this turn.", + "Parallel implementation policy is active for this request. Before any edit, write, apply_patch, or mutating bash call, you MUST call the `team` tool and fan out at least one worker task. Mark tasks that should edit code with `write: true`; those tasks will be isolated in git worktrees and merged back when possible. Use `background` only for side work that should keep running after this turn.", }) }, "tool.execute.before": async (