jorgex-stack 1.0.0
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/LICENSE +21 -0
- package/PRD.md +297 -0
- package/README.md +58 -0
- package/dist/cli.js +2894 -0
- package/package.json +51 -0
- package/stack/agents/README.md +33 -0
- package/stack/agents/backend-analyst.md +61 -0
- package/stack/agents/code-reviewer.md +66 -0
- package/stack/agents/code-simplifier.md +81 -0
- package/stack/agents/comment-fixer.md +54 -0
- package/stack/agents/docs-maintainer.md +88 -0
- package/stack/agents/engram.md +124 -0
- package/stack/agents/frontend-analyst.md +51 -0
- package/stack/agents/implementer.md +55 -0
- package/stack/agents/orchestrator.md +192 -0
- package/stack/agents/security-auditor.md +66 -0
- package/stack/agents/silent-failure-hunter.md +167 -0
- package/stack/agents/test-analyzer.md +91 -0
- package/stack/agents/tester.md +71 -0
- package/stack/agents/translator.md +101 -0
- package/stack/agents/type-design-analyzer.md +128 -0
- package/stack/commands/xreview.md +80 -0
- package/stack/config/defaults.json +37 -0
- package/stack/hooks/hooks.json +18 -0
- package/stack/mcp/servers.json +19 -0
- package/stack/plugins/opencode/engram.ts +378 -0
- package/stack/plugins/opencode/hooks.ts +766 -0
- package/stack/plugins/opencode/package.json +10 -0
- package/stack/plugins/opencode/worktree.ts +405 -0
- package/stack/scripts/post-pr-review.cjs +156 -0
- package/stack/skills/agent-browser/SKILL.md +55 -0
- package/stack/skills/agent-delegation/SKILL.md +58 -0
- package/stack/skills/deploy-to-vercel/SKILL.md +296 -0
- package/stack/skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
- package/stack/skills/deploy-to-vercel/resources/deploy.sh +301 -0
- package/stack/skills/diagnose/SKILL.md +117 -0
- package/stack/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/stack/skills/find-skills/SKILL.md +133 -0
- package/stack/skills/graphify/.graphify_version +1 -0
- package/stack/skills/graphify/SKILL.md +1319 -0
- package/stack/skills/mcp-builder/LICENSE.txt +202 -0
- package/stack/skills/mcp-builder/SKILL.md +236 -0
- package/stack/skills/mcp-builder/reference/evaluation.md +602 -0
- package/stack/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/stack/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/stack/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/stack/skills/mcp-builder/scripts/connections.py +151 -0
- package/stack/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/stack/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/stack/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/stack/skills/obsidian-cli/SKILL.md +106 -0
- package/stack/skills/obsidian-markdown/SKILL.md +196 -0
- package/stack/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/stack/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/stack/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/stack/skills/react-doctor/SKILL.md +19 -0
- package/stack/skills/skill-creator/LICENSE.txt +202 -0
- package/stack/skills/skill-creator/SKILL.md +485 -0
- package/stack/skills/skill-creator/agents/analyzer.md +274 -0
- package/stack/skills/skill-creator/agents/comparator.md +202 -0
- package/stack/skills/skill-creator/agents/grader.md +223 -0
- package/stack/skills/skill-creator/assets/eval_review.html +146 -0
- package/stack/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/stack/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/stack/skills/skill-creator/references/schemas.md +430 -0
- package/stack/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/stack/skills/skill-creator/scripts/generate_report.py +326 -0
- package/stack/skills/skill-creator/scripts/improve_description.py +248 -0
- package/stack/skills/skill-creator/scripts/package_skill.py +136 -0
- package/stack/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/stack/skills/skill-creator/scripts/run_eval.py +310 -0
- package/stack/skills/skill-creator/scripts/run_loop.py +332 -0
- package/stack/skills/skill-creator/scripts/utils.py +47 -0
- package/stack/skills/supabase/SKILL.md +135 -0
- package/stack/skills/supabase/assets/feedback-issue-template.md +17 -0
- package/stack/skills/supabase/references/skill-feedback.md +17 -0
- package/stack/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/stack/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
- package/stack/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
- package/stack/skills/supabase-postgres-best-practices/references/_template.md +34 -0
- package/stack/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/stack/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/stack/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/stack/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/stack/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/stack/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/stack/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/stack/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/stack/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/stack/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/stack/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/stack/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/stack/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/stack/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/stack/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/stack/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/stack/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/stack/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/stack/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/stack/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/stack/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/stack/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/stack/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/stack/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/stack/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/stack/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/stack/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/stack/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/stack/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/stack/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/stack/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
- package/stack/skills/tdd/SKILL.md +109 -0
- package/stack/skills/tdd/deep-modules.md +33 -0
- package/stack/skills/tdd/interface-design.md +31 -0
- package/stack/skills/tdd/mocking.md +59 -0
- package/stack/skills/tdd/refactoring.md +10 -0
- package/stack/skills/tdd/tests.md +61 -0
- package/stack/skills/to-issues/SKILL.md +83 -0
- package/stack/skills/to-prd/SKILL.md +72 -0
- package/stack/skills/work-lifecycle/SKILL.md +79 -0
- package/stack/skills/work-lifecycle/references/plan-template.md +185 -0
- package/stack/system-prompt/AGENTS.md +171 -0
- package/stack/system-prompt/engram-protocol.md +53 -0
- package/upstreams.json +96 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jorgex/opencode-plugins",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenCode plugins del stack: engram (memoria persistente), hooks (puente de scripts por evento/tool) y worktree (gestión de git worktrees)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "hooks.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@opencode-ai/plugin": "1.3.2"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
interface WorktreePluginConfig {
|
|
4
|
+
setupScript?: string;
|
|
5
|
+
docsReminderScript?: string;
|
|
6
|
+
pathContains?: string;
|
|
7
|
+
branchPrefix?: string;
|
|
8
|
+
reminderLines?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ScriptResult {
|
|
12
|
+
exitCode: number;
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
script: string;
|
|
16
|
+
scriptPath: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isAbsolutePath = (value: string) =>
|
|
20
|
+
/^[a-zA-Z]:[\\/]/.test(value) || value.startsWith("/");
|
|
21
|
+
|
|
22
|
+
const joinProjectPath = (directory: string, target: string) => {
|
|
23
|
+
const normalizedDirectory = directory.replace(/[\\/]+$/, "");
|
|
24
|
+
const normalizedTarget = target.replace(/^[\\/]+/, "").replace(/\\/g, "/");
|
|
25
|
+
return `${normalizedDirectory}/${normalizedTarget}`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const resolveProjectPath = (directory: string, target?: string) => {
|
|
29
|
+
if (!target) return undefined;
|
|
30
|
+
return isAbsolutePath(target) ? target : joinProjectPath(directory, target);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
34
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
+
|
|
36
|
+
const getPayloadWorktreePath = (payload: unknown) => {
|
|
37
|
+
if (!isPlainObject(payload)) return undefined;
|
|
38
|
+
|
|
39
|
+
const topLevel = payload.activeWorktreePath || payload.worktreePath;
|
|
40
|
+
if (typeof topLevel === "string" && topLevel) return topLevel;
|
|
41
|
+
|
|
42
|
+
const args = payload.args;
|
|
43
|
+
if (!isPlainObject(args)) return undefined;
|
|
44
|
+
|
|
45
|
+
const argPath = args.activeWorktreePath || args.worktreePath;
|
|
46
|
+
return typeof argPath === "string" && argPath ? argPath : undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const buildScriptEnv = (scriptPath: string, activeWorktreePath?: string) => {
|
|
50
|
+
const env = Object.fromEntries(
|
|
51
|
+
Object.entries(process.env).filter((entry): entry is [string, string] =>
|
|
52
|
+
typeof entry[1] === "string",
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (activeWorktreePath) {
|
|
57
|
+
env.OPENCODE_WORKTREE_PATH = activeWorktreePath;
|
|
58
|
+
} else {
|
|
59
|
+
delete env.OPENCODE_WORKTREE_PATH;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Windows-only: rutas de Git-for-Windows y ";" como separador de PATH.
|
|
63
|
+
if (scriptPath.endsWith(".sh") && process.platform === "win32") {
|
|
64
|
+
const gitUsrBin = "C:/Program Files/Git/usr/bin";
|
|
65
|
+
const gitBin = "C:/Program Files/Git/bin";
|
|
66
|
+
const currentPath = process.env.PATH || "";
|
|
67
|
+
env.PATH = `${gitUsrBin};${gitBin};${currentPath}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return env;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
interface RunScriptOptions {
|
|
74
|
+
payload?: unknown;
|
|
75
|
+
commandArgs?: string[];
|
|
76
|
+
activeWorktreePath?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const runScript = async (
|
|
80
|
+
client: any,
|
|
81
|
+
directory: string,
|
|
82
|
+
script: string,
|
|
83
|
+
options?: RunScriptOptions,
|
|
84
|
+
): Promise<ScriptResult> => {
|
|
85
|
+
const payload = options?.payload;
|
|
86
|
+
const scriptPath = isAbsolutePath(script)
|
|
87
|
+
? script
|
|
88
|
+
: joinProjectPath(directory, script);
|
|
89
|
+
const stdin = payload ? JSON.stringify(payload) : "";
|
|
90
|
+
const stdinSource = new Response(stdin);
|
|
91
|
+
|
|
92
|
+
let command: string[] | null = null;
|
|
93
|
+
|
|
94
|
+
// Resolve bash path: Bun's spawned process may not inherit Git Bash in PATH on Windows
|
|
95
|
+
const resolveBash = (): string => {
|
|
96
|
+
const fs = require("fs");
|
|
97
|
+
const candidates = [
|
|
98
|
+
"C:/Program Files/Git/usr/bin/bash.exe",
|
|
99
|
+
"C:/Program Files/Git/bin/bash.exe",
|
|
100
|
+
"C:/Program Files (x86)/Git/usr/bin/bash.exe",
|
|
101
|
+
process.env.BASH_PATH,
|
|
102
|
+
"bash",
|
|
103
|
+
].filter(Boolean) as string[];
|
|
104
|
+
for (const candidate of candidates) {
|
|
105
|
+
try {
|
|
106
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
107
|
+
} catch {
|
|
108
|
+
/* skip */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return "bash"; // fallback
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (scriptPath.endsWith(".ps1")) {
|
|
115
|
+
command = [
|
|
116
|
+
"powershell",
|
|
117
|
+
"-NoProfile",
|
|
118
|
+
"-ExecutionPolicy",
|
|
119
|
+
"Bypass",
|
|
120
|
+
"-File",
|
|
121
|
+
scriptPath,
|
|
122
|
+
...(options?.commandArgs || []),
|
|
123
|
+
];
|
|
124
|
+
} else if (scriptPath.endsWith(".sh")) {
|
|
125
|
+
command = [resolveBash(), scriptPath];
|
|
126
|
+
} else if (
|
|
127
|
+
scriptPath.endsWith(".cjs") ||
|
|
128
|
+
scriptPath.endsWith(".js") ||
|
|
129
|
+
scriptPath.endsWith(".mjs")
|
|
130
|
+
) {
|
|
131
|
+
command = ["node", scriptPath];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!command) {
|
|
135
|
+
await client.app.log({
|
|
136
|
+
body: {
|
|
137
|
+
service: "hooks",
|
|
138
|
+
level: "warn",
|
|
139
|
+
message: "Unsupported worktree hook script extension",
|
|
140
|
+
extra: { script, scriptPath },
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return { exitCode: 0, stdout: "", stderr: "", script, scriptPath };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const activeWorktreePath =
|
|
148
|
+
options?.activeWorktreePath || getPayloadWorktreePath(payload);
|
|
149
|
+
const spawnEnv = buildScriptEnv(scriptPath, activeWorktreePath);
|
|
150
|
+
|
|
151
|
+
const proc = (globalThis as any).Bun.spawn(command, {
|
|
152
|
+
stdin: stdinSource,
|
|
153
|
+
stdout: "pipe",
|
|
154
|
+
stderr: "pipe",
|
|
155
|
+
cwd: directory,
|
|
156
|
+
env: spawnEnv,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
160
|
+
new Response(proc.stdout).text(),
|
|
161
|
+
new Response(proc.stderr).text(),
|
|
162
|
+
proc.exited,
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
if (exitCode !== 0) {
|
|
166
|
+
await client.app.log({
|
|
167
|
+
body: {
|
|
168
|
+
service: "hooks",
|
|
169
|
+
level: "error",
|
|
170
|
+
message: "Worktree hook script execution failed",
|
|
171
|
+
extra: {
|
|
172
|
+
script,
|
|
173
|
+
scriptPath,
|
|
174
|
+
exitCode,
|
|
175
|
+
stderr: stderr.trim(),
|
|
176
|
+
stdout: stdout.trim(),
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { exitCode, stdout, stderr, script, scriptPath };
|
|
183
|
+
} catch (error) {
|
|
184
|
+
await client.app.log({
|
|
185
|
+
body: {
|
|
186
|
+
service: "hooks",
|
|
187
|
+
level: "error",
|
|
188
|
+
message: "Worktree hook script execution failed",
|
|
189
|
+
extra: {
|
|
190
|
+
script,
|
|
191
|
+
scriptPath,
|
|
192
|
+
error: error instanceof Error ? error.message : String(error),
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
exitCode: 1,
|
|
198
|
+
stdout: "",
|
|
199
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
200
|
+
script,
|
|
201
|
+
scriptPath,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const extractScriptMessage = (text: string) => {
|
|
207
|
+
const trimmed = text.trim();
|
|
208
|
+
if (!trimmed) return "";
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(trimmed);
|
|
212
|
+
if (typeof parsed?.additionalContext === "string") {
|
|
213
|
+
return parsed.additionalContext;
|
|
214
|
+
}
|
|
215
|
+
return trimmed;
|
|
216
|
+
} catch {
|
|
217
|
+
return trimmed;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const appendToolOutput = (output: any, messages: string[]) => {
|
|
222
|
+
if (!output || messages.length === 0) return;
|
|
223
|
+
const section = messages.join("\n\n");
|
|
224
|
+
// Built-in tools (bash, edit, read, write) use output.output (string)
|
|
225
|
+
// MCP tools (github_*, supabase_*) use output.content (array of content blocks)
|
|
226
|
+
if ("output" in output && typeof output.output === "string") {
|
|
227
|
+
output.output = output.output ? `${output.output}\n\n${section}` : section;
|
|
228
|
+
} else if ("content" in output && Array.isArray(output.content)) {
|
|
229
|
+
output.content.push({ type: "text", text: `\n\n${section}` });
|
|
230
|
+
} else if ("content" in output && typeof output.content === "string") {
|
|
231
|
+
output.content = output.content
|
|
232
|
+
? `${output.content}\n\n${section}`
|
|
233
|
+
: section;
|
|
234
|
+
} else {
|
|
235
|
+
output.output = section;
|
|
236
|
+
output.content = section;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const truncateMessage = (value: string, maxLength = 1200) => {
|
|
241
|
+
if (value.length <= maxLength) return value;
|
|
242
|
+
return `${value.slice(0, maxLength)}\n...`;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const parseWorktreePath = (command: string) => {
|
|
246
|
+
const match = command.match(
|
|
247
|
+
/git\s+worktree\s+add\s+(?:-[^\s]+\s+)*(?:-b\s+[^\s]+\s+)?(?:"([^"]+)"|'([^']+)'|(\S+))/i,
|
|
248
|
+
);
|
|
249
|
+
return match?.[1] || match?.[2] || match?.[3] || null;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const getWorktreeName = (worktreePath: string) => {
|
|
253
|
+
const segments = worktreePath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
254
|
+
return segments[segments.length - 1] || null;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const replaceToken = (value: string, token: string, replacement: string) =>
|
|
258
|
+
value.split(token).join(replacement);
|
|
259
|
+
|
|
260
|
+
export const WorktreePlugin: Plugin = async ({ $, client, directory }) => {
|
|
261
|
+
let config: WorktreePluginConfig = {
|
|
262
|
+
setupScript: "scripts/setup-worktree.ps1",
|
|
263
|
+
pathContains: "worktrees/",
|
|
264
|
+
branchPrefix: "feature/",
|
|
265
|
+
reminderLines: [],
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const configPath = `${directory}/.opencode/worktree.json`;
|
|
270
|
+
const content = await (globalThis as any).Bun.file(configPath).text();
|
|
271
|
+
config = {
|
|
272
|
+
...config,
|
|
273
|
+
...JSON.parse(content),
|
|
274
|
+
};
|
|
275
|
+
} catch {
|
|
276
|
+
// Optional project config
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
"tool.execute.after": async (input: any, output: any) => {
|
|
281
|
+
try {
|
|
282
|
+
const tool = (input.tool || "").toLowerCase();
|
|
283
|
+
const payload = {
|
|
284
|
+
event: "tool.execute.after",
|
|
285
|
+
directory,
|
|
286
|
+
tool: input.tool,
|
|
287
|
+
args: input.args || {},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
if (tool === "enterworktree") {
|
|
291
|
+
const docsReminderScript = resolveProjectPath(
|
|
292
|
+
directory,
|
|
293
|
+
config.docsReminderScript,
|
|
294
|
+
);
|
|
295
|
+
if (docsReminderScript) {
|
|
296
|
+
const result = await runScript(
|
|
297
|
+
client,
|
|
298
|
+
directory,
|
|
299
|
+
docsReminderScript,
|
|
300
|
+
{ payload },
|
|
301
|
+
);
|
|
302
|
+
const messages = [
|
|
303
|
+
extractScriptMessage(result.stdout),
|
|
304
|
+
extractScriptMessage(result.stderr),
|
|
305
|
+
].filter(Boolean);
|
|
306
|
+
appendToolOutput(output, messages);
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (tool !== "bash") return;
|
|
312
|
+
|
|
313
|
+
const args = input.args || {};
|
|
314
|
+
const command = args.command || "";
|
|
315
|
+
const commandLower = command.toLowerCase();
|
|
316
|
+
const pathContains = (
|
|
317
|
+
config.pathContains || "worktrees/"
|
|
318
|
+
).toLowerCase();
|
|
319
|
+
|
|
320
|
+
if (
|
|
321
|
+
!commandLower.includes("git worktree add") ||
|
|
322
|
+
!commandLower.includes(pathContains)
|
|
323
|
+
) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const parsedWorktreePath = parseWorktreePath(command);
|
|
328
|
+
if (!parsedWorktreePath) return;
|
|
329
|
+
|
|
330
|
+
const worktreeName = getWorktreeName(parsedWorktreePath);
|
|
331
|
+
if (!worktreeName) return;
|
|
332
|
+
|
|
333
|
+
const branchName = `${config.branchPrefix || "feature/"}${worktreeName}`;
|
|
334
|
+
|
|
335
|
+
const gitRoot = await $`git rev-parse --show-toplevel`.text();
|
|
336
|
+
const projectRoot = String(gitRoot).trim().replace(/\\/g, "/");
|
|
337
|
+
const absoluteWorktreePath = isAbsolutePath(parsedWorktreePath)
|
|
338
|
+
? parsedWorktreePath.replace(/\\/g, "/")
|
|
339
|
+
: joinProjectPath(projectRoot, parsedWorktreePath);
|
|
340
|
+
|
|
341
|
+
const setupScript = resolveProjectPath(directory, config.setupScript);
|
|
342
|
+
if (setupScript) {
|
|
343
|
+
const setupResult = await runScript(client, directory, setupScript, {
|
|
344
|
+
payload: {
|
|
345
|
+
...payload,
|
|
346
|
+
worktreePath: absoluteWorktreePath,
|
|
347
|
+
worktreeName,
|
|
348
|
+
branchName,
|
|
349
|
+
},
|
|
350
|
+
commandArgs: ["-WorktreePath", absoluteWorktreePath],
|
|
351
|
+
activeWorktreePath: absoluteWorktreePath,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (setupResult.exitCode !== 0) {
|
|
355
|
+
const setupErrorMessage = truncateMessage(
|
|
356
|
+
[setupResult.stderr.trim(), setupResult.stdout.trim()]
|
|
357
|
+
.filter(Boolean)
|
|
358
|
+
.join("\n\n"),
|
|
359
|
+
);
|
|
360
|
+
appendToolOutput(output, [
|
|
361
|
+
`Worktree setup failed for ${worktreeName}.`,
|
|
362
|
+
setupErrorMessage || "No additional details from setup script.",
|
|
363
|
+
]);
|
|
364
|
+
} else {
|
|
365
|
+
appendToolOutput(output, [
|
|
366
|
+
`Worktree setup complete: ${worktreeName}`,
|
|
367
|
+
]);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const reminderLines = (config.reminderLines || []).map((line) =>
|
|
372
|
+
replaceToken(
|
|
373
|
+
replaceToken(
|
|
374
|
+
replaceToken(line, "{worktreeName}", worktreeName),
|
|
375
|
+
"{worktreePath}",
|
|
376
|
+
absoluteWorktreePath,
|
|
377
|
+
),
|
|
378
|
+
"{branchName}",
|
|
379
|
+
branchName,
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (reminderLines.length > 0) {
|
|
384
|
+
const banner = [
|
|
385
|
+
"--------------------------------------------------",
|
|
386
|
+
...reminderLines,
|
|
387
|
+
"--------------------------------------------------",
|
|
388
|
+
].join("\n");
|
|
389
|
+
appendToolOutput(output, [banner]);
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
await client.app.log({
|
|
393
|
+
body: {
|
|
394
|
+
service: "hooks",
|
|
395
|
+
level: "error",
|
|
396
|
+
message: "Worktree plugin execution failed",
|
|
397
|
+
extra: {
|
|
398
|
+
error: error instanceof Error ? error.message : String(error),
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Global PostToolUse hook: gh pr create
|
|
4
|
+
*
|
|
5
|
+
* Injects a conditional multi-agent review request after a PR is created.
|
|
6
|
+
* Generic and project-agnostic: no version bump, no React Doctor, no project paths.
|
|
7
|
+
*
|
|
8
|
+
* The review subagents are CONDITIONAL — only the ones relevant to the diff run.
|
|
9
|
+
* Mirrors the `/xreview` command logic so both stay aligned: comment-fixer first
|
|
10
|
+
* (committed before the analysts), then the read-only analysts in parallel.
|
|
11
|
+
*
|
|
12
|
+
* Payload compatibility (stdin JSON), so the same script works on every runtime:
|
|
13
|
+
* - Claude Code hooks: { tool_name: "Bash", tool_input: { command: "..." }, cwd }
|
|
14
|
+
* - Codex hooks: { tool_name: "shell", tool_input: { command: [...] }, cwd }
|
|
15
|
+
* - OpenCode bridge: { tool: "bash", args: { command: "..." }, directory }
|
|
16
|
+
*
|
|
17
|
+
* Output (single channel per runtime, so the bridge never duplicates it):
|
|
18
|
+
* - OpenCode bridge payload → plain message on stderr.
|
|
19
|
+
* - Claude Code / Codex payload → JSON additionalContext on stdout.
|
|
20
|
+
* Exit 0 always.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const { execSync } = require('child_process');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
const DEFAULT_BASE_BRANCH = 'main';
|
|
27
|
+
const BASE_BRANCH_SOURCE = Object.freeze({
|
|
28
|
+
GH: 'gh',
|
|
29
|
+
ORIGIN_HEAD: 'origin-head',
|
|
30
|
+
DEFAULT: 'default',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function writeWarning(message) {
|
|
34
|
+
process.stderr.write(`post-pr-review: warning: ${message}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatGitRef(value) {
|
|
38
|
+
const ref = String(value);
|
|
39
|
+
return /^[A-Za-z0-9._/-]+$/.test(ref) ? ref : `'${ref.replace(/'/g, "'\\''")}'`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveBaseBranch(cwd) {
|
|
43
|
+
try {
|
|
44
|
+
const base = execSync('gh pr view --json baseRefName --jq .baseRefName', {
|
|
45
|
+
cwd,
|
|
46
|
+
encoding: 'utf8',
|
|
47
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
48
|
+
timeout: 8_000,
|
|
49
|
+
}).trim();
|
|
50
|
+
if (base) return { branch: base, source: BASE_BRANCH_SOURCE.GH };
|
|
51
|
+
} catch {
|
|
52
|
+
// PR not yet discoverable; fall back below.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const originHead = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
57
|
+
cwd,
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
60
|
+
timeout: 10_000,
|
|
61
|
+
}).trim();
|
|
62
|
+
if (originHead) {
|
|
63
|
+
return {
|
|
64
|
+
branch: originHead.replace(/^refs\/remotes\/origin\//, ''),
|
|
65
|
+
source: BASE_BRANCH_SOURCE.ORIGIN_HEAD,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { branch: DEFAULT_BASE_BRANCH, source: BASE_BRANCH_SOURCE.DEFAULT };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let raw = '';
|
|
76
|
+
process.stdin.setEncoding('utf8');
|
|
77
|
+
process.stdin.on('data', (chunk) => (raw += chunk));
|
|
78
|
+
process.stdin.on('end', () => {
|
|
79
|
+
let data = {};
|
|
80
|
+
try {
|
|
81
|
+
data = JSON.parse(raw || '{}');
|
|
82
|
+
} catch {
|
|
83
|
+
writeWarning('invalid JSON payload; skipping review hook.');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const toolName = String(data.tool_name || data.tool || '').toLowerCase();
|
|
88
|
+
const SHELL_TOOLS = ['bash', 'shell', 'local_shell', 'powershell'];
|
|
89
|
+
const commandValue = data?.tool_input?.command ?? data?.args?.command ?? '';
|
|
90
|
+
// Codex passes the shell command as an argv array; the rest as a string.
|
|
91
|
+
const rawToolCommand = Array.isArray(commandValue) ? commandValue.join(' ') : String(commandValue);
|
|
92
|
+
const toolCommand = rawToolCommand.toLowerCase();
|
|
93
|
+
|
|
94
|
+
if (!SHELL_TOOLS.includes(toolName) || !toolCommand.includes('gh pr create')) {
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const scriptDir = __dirname;
|
|
99
|
+
const projectDir = data.cwd || data.directory || path.resolve(scriptDir, '..');
|
|
100
|
+
const resolution = resolveBaseBranch(projectDir);
|
|
101
|
+
const baseBranch = resolution.branch;
|
|
102
|
+
const baseRef = formatGitRef(baseBranch);
|
|
103
|
+
const diffScope = `git diff ${baseRef}...HEAD`;
|
|
104
|
+
const isConfirmed = resolution.source === BASE_BRANCH_SOURCE.GH;
|
|
105
|
+
const baseSummary = isConfirmed
|
|
106
|
+
? `BASE (PR target, confirmed via gh pr view): ${baseBranch}`
|
|
107
|
+
: `BASE (PR target, NOT confirmed — fallback guess): ${baseBranch}. Before anything else, re-resolve it yourself: run \`gh pr view --json baseRefName --jq .baseRefName\` (the PR exists now; this hook may have raced its creation) and use THAT as BASE. Keep ${baseBranch} only if it still fails. Work is often done in sub-branches whose PR does NOT target the default branch — reviewing against the wrong BASE produces a huge, useless diff.`;
|
|
108
|
+
|
|
109
|
+
const message = `<post-pr-review-required>
|
|
110
|
+
A PR was just created. Run a conditional multi-agent review BEFORE reporting back to the user.
|
|
111
|
+
|
|
112
|
+
${baseSummary}
|
|
113
|
+
HEAD: the current branch / worktree (resolve with \`git rev-parse --abbrev-ref HEAD\`).
|
|
114
|
+
|
|
115
|
+
1. Routing only (lightweight): list changed file NAMES with \`${diffScope} --name-only\` to decide which subagents apply. Do NOT load the full diff into your own context.
|
|
116
|
+
|
|
117
|
+
Sanity check: if that list is far larger than the work just done (hundreds of files, unrelated areas), BASE is almost certainly wrong — STOP, re-resolve the PR base with \`gh pr view\`, and only continue when the diff matches the actual work.
|
|
118
|
+
|
|
119
|
+
2. Comment pass FIRST (conditional): if the diff adds or changes comments/docstrings, run comment-fixer ALONE before the analysts — it edits comments in place (comments only, never code). If it changed anything, comment-fixer itself never commits — YOU commit its fixes to the PR branch BEFORE launching the analysts, staging ONLY the files it touched (never -a/-A: don't sweep unrelated working-tree changes), so the diff they fetch is already clean of comment noise; push them together with whatever the review produces, or on their own if nothing else needs fixing. If the commit can't be made, leave the edits uncommitted and say so in the report. If the diff touches no comments, skip it.
|
|
120
|
+
|
|
121
|
+
3. The remaining subagents are CONDITIONAL, read-only, and each fetches its OWN diff. Launch in PARALLEL (with your runtime's delegation mechanism) ONLY the relevant ones, passing each EXACTLY the BASE and HEAD branches and the instruction: review only \`${diffScope}\` — never assume \`main\`, use the BASE/HEAD given.
|
|
122
|
+
- test-analyzer — only if the diff touches tests or code that should be tested
|
|
123
|
+
- silent-failure-hunter — only if the diff includes error handling, try/catch, fallbacks, or async flows
|
|
124
|
+
- type-design-analyzer — only if the diff changes types, interfaces, schemas, or public contracts
|
|
125
|
+
- code-reviewer — for general code quality whenever non-trivial source code changed
|
|
126
|
+
- code-simplifier — only if the diff introduces complexity worth simplifying
|
|
127
|
+
- security-auditor — only if the diff touches auth, authorization, permissions, secrets/credentials, sensitive data, input validation, webhooks, or other security-critical flows
|
|
128
|
+
|
|
129
|
+
If none of a subagent's triggers are present, skip it. Always state which subagents ran and which were skipped and why.
|
|
130
|
+
|
|
131
|
+
4. After the relevant subagents complete, synthesize a unified report:
|
|
132
|
+
- BASE and HEAD used
|
|
133
|
+
- Subagents run vs skipped (with reason)
|
|
134
|
+
- Critical Issues (must fix)
|
|
135
|
+
- Important Improvements (should fix)
|
|
136
|
+
- Suggestions (nice to have)
|
|
137
|
+
- Changes already applied (comment fixes: committed to the PR branch, or left uncommitted for working-tree reviews)
|
|
138
|
+
- Positive Findings
|
|
139
|
+
</post-pr-review-required>`;
|
|
140
|
+
|
|
141
|
+
// Un solo canal por runtime: el bridge de OpenCode recoge stdout Y stderr,
|
|
142
|
+
// así que emitir por ambos duplicaría el mensaje.
|
|
143
|
+
const isOpenCodeBridge = data.tool !== undefined && data.tool_name === undefined;
|
|
144
|
+
if (isOpenCodeBridge) {
|
|
145
|
+
process.stderr.write(message + '\n');
|
|
146
|
+
} else {
|
|
147
|
+
// Claude Code / Codex PostToolUse leen additionalContext del stdout JSON.
|
|
148
|
+
process.stdout.write(
|
|
149
|
+
JSON.stringify({
|
|
150
|
+
additionalContext: message,
|
|
151
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: message },
|
|
152
|
+
}) + '\n',
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
process.exit(0);
|
|
156
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-browser
|
|
3
|
+
description: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.
|
|
4
|
+
allowed-tools: Bash(agent-browser:*), Bash(npx agent-browser:*)
|
|
5
|
+
hidden: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# agent-browser
|
|
9
|
+
|
|
10
|
+
Fast browser automation CLI for AI agents. Chrome/Chromium via CDP with
|
|
11
|
+
accessibility-tree snapshots and compact `@eN` element refs.
|
|
12
|
+
|
|
13
|
+
Install: `npm i -g agent-browser && agent-browser install`
|
|
14
|
+
|
|
15
|
+
## Start here
|
|
16
|
+
|
|
17
|
+
This file is a discovery stub, not the usage guide. Before running any
|
|
18
|
+
`agent-browser` command, load the actual workflow content from the CLI:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
agent-browser skills get core # start here — workflows, common patterns, troubleshooting
|
|
22
|
+
agent-browser skills get core --full # include full command reference and templates
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The CLI serves skill content that always matches the installed version,
|
|
26
|
+
so instructions never go stale. The content in this stub cannot change
|
|
27
|
+
between releases, which is why it just points at `skills get core`.
|
|
28
|
+
|
|
29
|
+
## Specialized skills
|
|
30
|
+
|
|
31
|
+
Load a specialized skill when the task falls outside browser web pages:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
agent-browser skills get electron # Electron desktop apps (VS Code, Slack, Discord, Figma, ...)
|
|
35
|
+
agent-browser skills get slack # Slack workspace automation
|
|
36
|
+
agent-browser skills get dogfood # Exploratory testing / QA / bug hunts
|
|
37
|
+
agent-browser skills get vercel-sandbox # agent-browser inside Vercel Sandbox microVMs
|
|
38
|
+
agent-browser skills get agentcore # AWS Bedrock AgentCore cloud browsers
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Run `agent-browser skills list` to see everything available on the
|
|
42
|
+
installed version.
|
|
43
|
+
|
|
44
|
+
## Why agent-browser
|
|
45
|
+
|
|
46
|
+
- Fast native Rust CLI, not a Node.js wrapper
|
|
47
|
+
- Works with any AI agent (Cursor, Claude Code, Codex, Continue, Windsurf, etc.)
|
|
48
|
+
- Chrome/Chromium via CDP with no Playwright or Puppeteer dependency
|
|
49
|
+
- Accessibility-tree snapshots with element refs for reliable interaction
|
|
50
|
+
- Sessions, authentication vault, state persistence, video recording
|
|
51
|
+
- Specialized skills for Electron apps, Slack, exploratory testing, cloud providers
|
|
52
|
+
|
|
53
|
+
## Observability Dashboard
|
|
54
|
+
|
|
55
|
+
The dashboard runs independently of browser sessions on port 4848 and can also be opened through a proxied or forwarded URL such as `https://dashboard.agent-browser.localhost`. Agents should stay on the dashboard origin: session tabs, status, and stream traffic are proxied internally, so session ports do not need to be exposed.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-delegation
|
|
3
|
+
description: Regla universal de delegación entre subagentes. Usar cuando durante una tarea aparezca trabajo que pertenece a otro especialista y no deba resolverse en el mismo scope.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent Delegation
|
|
7
|
+
|
|
8
|
+
## Regla universal
|
|
9
|
+
|
|
10
|
+
Si durante tu tarea encuentras trabajo que pertenece a otro especialista, **no lo absorbas**. Haz solo tu parte y reporta el resto.
|
|
11
|
+
|
|
12
|
+
Importante sobre el mecanismo:
|
|
13
|
+
|
|
14
|
+
- Tú (subagente) **no lanzas a otros subagentes**. Solo el agente principal (orquestador) puede invocarlos.
|
|
15
|
+
- No te salgas de tu scope para "ayudar". Si algo no te corresponde, lo dejas sin hacer y lo delegas.
|
|
16
|
+
- Las delegaciones van **en tu output final**, en el formato de abajo. El orquestador las lee y decide a quién invocar.
|
|
17
|
+
|
|
18
|
+
## Agentes disponibles
|
|
19
|
+
|
|
20
|
+
| Agente | Scope | Delega aquí cuando aparezca... |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `implementer` | escribe código de producción | falta código para que algo funcione; hay que implementar el cambio real |
|
|
23
|
+
| `tester` | escribe/ejecuta tests | faltan tests, hay tests rotos por un cambio de contrato, o hay que verificar comportamiento |
|
|
24
|
+
| `translator` | traducciones, locales, multiidioma | strings hardcodeadas visibles, locales desincronizados, copy en varios idiomas |
|
|
25
|
+
| `docs-maintainer` | documentación (/docs y docs site público) | el cambio deja docs desactualizadas o requiere nueva documentación |
|
|
26
|
+
| `backend-analyst` | análisis backend (read-only) | hace falta mapear servicios, DB, APIs o riesgos backend antes de actuar |
|
|
27
|
+
| `frontend-analyst` | análisis frontend (read-only) | hace falta mapear componentes, estado, rendering o riesgos de UI antes de actuar |
|
|
28
|
+
| `security-auditor` | seguridad y privacidad (read-only) | auth, permisos, secretos, datos sensibles, validación de input, webhooks |
|
|
29
|
+
| `code-reviewer` | calidad de código vs guías (read-only) | hace falta revisar el diff contra las reglas del proyecto y detectar bugs |
|
|
30
|
+
| `code-simplifier` | simplificación (read-only, propone) | el código introduce complejidad que merece simplificarse |
|
|
31
|
+
| `silent-failure-hunter` | manejo de errores (read-only) | hay try/catch, fallbacks, errores silenciados o flujos async que auditar |
|
|
32
|
+
| `comment-fixer` | comentarios (escribe SOLO comentarios, los corrige directamente) | hay comentarios/docstrings nuevos o cambiados que corregir |
|
|
33
|
+
| `test-analyzer` | cobertura de tests (read-only) | hay que evaluar si los tests cubren bien lo cambiado (analiza, NO escribe) |
|
|
34
|
+
| `type-design-analyzer` | diseño de tipos/invariantes (read-only) | cambian tipos, interfaces, schemas o contratos públicos |
|
|
35
|
+
| `engram` | lectura de memoria (read-only) | hace falta recuperar contexto, decisiones o trabajo previo de memoria |
|
|
36
|
+
|
|
37
|
+
Nota: `test-analyzer` analiza cobertura pero no escribe tests; escribir los tests recomendados es de `tester`.
|
|
38
|
+
|
|
39
|
+
## Formato obligatorio
|
|
40
|
+
|
|
41
|
+
Una línea por delegación, al final de tu output:
|
|
42
|
+
|
|
43
|
+
```markdown
|
|
44
|
+
→ [agente]: [trabajo pendiente] — [archivos/rutas] — [inputs mínimos]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Ejemplos
|
|
48
|
+
|
|
49
|
+
- `implementer` detecta tests desactualizados → `tester`
|
|
50
|
+
- `implementer` detecta strings hardcodeadas o locales nuevos → `translator`
|
|
51
|
+
- `tester` detecta que falta código de producción → `implementer`
|
|
52
|
+
- cualquier agente detecta auth, permisos o datos sensibles → `security-auditor`
|
|
53
|
+
- cualquier agente detecta cambio documental relevante → `docs-maintainer`
|
|
54
|
+
- `test-analyzer` detecta gaps de cobertura → `tester` (que los escribe)
|
|
55
|
+
|
|
56
|
+
## Regla de conflicto
|
|
57
|
+
|
|
58
|
+
Si una task mezcla varios scopes, considéralo un problema de planificación. Ejecuta solo tu parte y devuelve las delegaciones pendientes en tu output final para que el orquestador las reparta.
|