zugzbot 1.0.5 → 1.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/.opencode/plugins/loop-enforcer.ts +4 -1
- package/.opencode/plugins/sdd-bridge.ts +87 -2
- package/.opencode/tools/brain.ts +8 -1
- package/.opencode/tools/sdd.ts +27 -19
- package/.utils/docs_opencode/acp.md +165 -0
- package/.utils/docs_opencode/acp.pdf +0 -0
- package/.utils/docs_opencode/agents.md +803 -0
- package/.utils/docs_opencode/agents.pdf +0 -0
- package/.utils/docs_opencode/commands.md +354 -0
- package/.utils/docs_opencode/commands.pdf +0 -0
- package/.utils/docs_opencode/custom-tools.md +209 -0
- package/.utils/docs_opencode/custom-tools.pdf +0 -0
- package/.utils/docs_opencode/ecosystem.md +81 -0
- package/.utils/docs_opencode/ecosystem.pdf +0 -0
- package/.utils/docs_opencode/formatters.md +142 -0
- package/.utils/docs_opencode/formatters.pdf +0 -0
- package/.utils/docs_opencode/keybinds.md +205 -0
- package/.utils/docs_opencode/keybinds.pdf +0 -0
- package/.utils/docs_opencode/lsp.md +202 -0
- package/.utils/docs_opencode/lsp.pdf +0 -0
- package/.utils/docs_opencode/mcp-servers.md +565 -0
- package/.utils/docs_opencode/mcp-servers.pdf +0 -0
- package/.utils/docs_opencode/models.md +234 -0
- package/.utils/docs_opencode/models.pdf +0 -0
- package/.utils/docs_opencode/permissions.md +248 -0
- package/.utils/docs_opencode/permissions.pdf +0 -0
- package/.utils/docs_opencode/plugins.md +409 -0
- package/.utils/docs_opencode/plugins.pdf +0 -0
- package/.utils/docs_opencode/rules.md +189 -0
- package/.utils/docs_opencode/rules.pdf +0 -0
- package/.utils/docs_opencode/sdk.md +522 -0
- package/.utils/docs_opencode/sdk.pdf +0 -0
- package/.utils/docs_opencode/server.md +324 -0
- package/.utils/docs_opencode/server.pdf +0 -0
- package/.utils/docs_opencode/skills.md +235 -0
- package/.utils/docs_opencode/skills.pdf +0 -0
- package/.utils/docs_opencode/themes.md +378 -0
- package/.utils/docs_opencode/themes.pdf +0 -0
- package/.utils/docs_opencode/tools.md +364 -0
- package/.utils/docs_opencode/tools.pdf +0 -0
- package/.utils/export_opencode_session.py +242 -0
- package/.utils/toggle_model.py +441 -0
- package/README.md +39 -0
- package/bin/init.js +25 -6
- package/models.json +8 -0
- package/opencode.json +4 -5
- package/package.json +3 -1
|
@@ -52,7 +52,10 @@ const discardLastFailedAttempt = (projectRoot: string): { success: boolean; mess
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export const LoopEnforcerPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
|
55
|
-
|
|
55
|
+
let projectRoot = process.cwd();
|
|
56
|
+
if (directory && directory !== "/") projectRoot = directory;
|
|
57
|
+
else if (worktree && worktree !== "/") projectRoot = worktree;
|
|
58
|
+
|
|
56
59
|
const stateFilePath = path.resolve(projectRoot, ".openspec/sdd_state.json")
|
|
57
60
|
|
|
58
61
|
const getStateFilePath = () => stateFilePath
|
|
@@ -33,7 +33,10 @@ const emptyMetrics = (): SessionMetrics => ({
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
export const SddBridgePlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
|
36
|
-
|
|
36
|
+
let projectRoot = process.cwd();
|
|
37
|
+
if (directory && directory !== "/") projectRoot = directory;
|
|
38
|
+
else if (worktree && worktree !== "/") projectRoot = worktree;
|
|
39
|
+
|
|
37
40
|
const stateFilePath = path.resolve(projectRoot, ".openspec/sdd_state.json")
|
|
38
41
|
const metricsFilePath = path.resolve(projectRoot, ".openspec/.sdd_session_metrics.json")
|
|
39
42
|
|
|
@@ -206,6 +209,88 @@ export const SddBridgePlugin: Plugin = async ({ project, client, $, directory, w
|
|
|
206
209
|
}
|
|
207
210
|
}
|
|
208
211
|
|
|
212
|
+
// Helper to synchronize models automatically from models.json
|
|
213
|
+
const syncModelsFromConfig = () => {
|
|
214
|
+
try {
|
|
215
|
+
const modelsPath = path.resolve(projectRoot, "models.json")
|
|
216
|
+
const altModelsPath = path.resolve(projectRoot, ".opencode/models.json")
|
|
217
|
+
let selectedModelsPath = ""
|
|
218
|
+
|
|
219
|
+
if (fs.existsSync(modelsPath)) {
|
|
220
|
+
selectedModelsPath = modelsPath
|
|
221
|
+
} else if (fs.existsSync(altModelsPath)) {
|
|
222
|
+
selectedModelsPath = altModelsPath
|
|
223
|
+
} else {
|
|
224
|
+
return // No config file, do nothing silently
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const modelsContent = fs.readFileSync(selectedModelsPath, "utf8")
|
|
228
|
+
const modelsData = JSON.parse(modelsContent)
|
|
229
|
+
const globalModel = modelsData.global || "deepseek/deepseek-v4-flash"
|
|
230
|
+
|
|
231
|
+
// 1. Update opencode.json
|
|
232
|
+
const opencodeJsonPath = path.resolve(projectRoot, "opencode.json")
|
|
233
|
+
if (fs.existsSync(opencodeJsonPath)) {
|
|
234
|
+
const opencodeContent = fs.readFileSync(opencodeJsonPath, "utf8")
|
|
235
|
+
const opencodeData = JSON.parse(opencodeContent)
|
|
236
|
+
let updated = false
|
|
237
|
+
|
|
238
|
+
if (opencodeData.agent && typeof opencodeData.agent === "object") {
|
|
239
|
+
for (const agentName of Object.keys(opencodeData.agent)) {
|
|
240
|
+
const configModel = modelsData[agentName]
|
|
241
|
+
const targetModel = configModel && configModel.trim() !== "" ? configModel : globalModel
|
|
242
|
+
|
|
243
|
+
if (opencodeData.agent[agentName].model !== targetModel) {
|
|
244
|
+
opencodeData.agent[agentName].model = targetModel
|
|
245
|
+
updated = true
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (updated) {
|
|
251
|
+
fs.writeFileSync(opencodeJsonPath, JSON.stringify(opencodeData, null, 2), "utf8")
|
|
252
|
+
logPhase("MODEL_SYNC", "Sincronizados modelos en opencode.json desde models.json")
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 2. Update agent markdown files (.opencode/agents/*.md)
|
|
257
|
+
const agentsDir = path.resolve(projectRoot, ".opencode/agents")
|
|
258
|
+
if (fs.existsSync(agentsDir)) {
|
|
259
|
+
const files = fs.readdirSync(agentsDir)
|
|
260
|
+
for (const file of files) {
|
|
261
|
+
if (file.endsWith(".md")) {
|
|
262
|
+
const agentName = file.slice(0, -3)
|
|
263
|
+
const configModel = modelsData[agentName]
|
|
264
|
+
const targetModel = configModel && configModel.trim() !== "" ? configModel : globalModel
|
|
265
|
+
const filepath = path.join(agentsDir, file)
|
|
266
|
+
const content = fs.readFileSync(filepath, "utf8")
|
|
267
|
+
|
|
268
|
+
const frontmatterPattern = /^---$([\s\S]*?)^---$/m
|
|
269
|
+
const match = frontmatterPattern.exec(content)
|
|
270
|
+
if (match) {
|
|
271
|
+
const frontmatter = match[1]
|
|
272
|
+
if (frontmatter.includes("model:")) {
|
|
273
|
+
const modelLinePattern = /^model:\s*(.*?)$/m
|
|
274
|
+
const modelLineMatch = modelLinePattern.exec(frontmatter)
|
|
275
|
+
if (modelLineMatch && modelLineMatch[1].trim() !== targetModel) {
|
|
276
|
+
const newFrontmatter = frontmatter.replace(modelLinePattern, `model: ${targetModel}`)
|
|
277
|
+
const newContent = content.replace(frontmatter, newFrontmatter)
|
|
278
|
+
fs.writeFileSync(filepath, newContent, "utf8")
|
|
279
|
+
logPhase("MODEL_SYNC", `Sincronizado agente ${file} a modelo: ${targetModel}`)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch (e) {
|
|
287
|
+
// Best-effort: don't crash on model sync failures
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Run model sync from models.json automatically on plugin load
|
|
292
|
+
syncModelsFromConfig()
|
|
293
|
+
|
|
209
294
|
// Ensure metrics file matches current contract on plugin load
|
|
210
295
|
maybeResetMetrics()
|
|
211
296
|
|
|
@@ -230,7 +315,7 @@ export const SddBridgePlugin: Plugin = async ({ project, client, $, directory, w
|
|
|
230
315
|
const currentState = readState()
|
|
231
316
|
|
|
232
317
|
// Enforce: No code edits allowed unless we are in the implementation phase (F2_IMPLEMENTATION)
|
|
233
|
-
if (isWritingCode && !targetFilePath.includes("tests") && !targetFilePath.includes("specs") && !targetFilePath.includes(".opencode") && !targetFilePath.includes(".openspec") && !targetFilePath.includes("config.ts") && !targetFilePath.includes("config.js")) {
|
|
318
|
+
if (isWritingCode && !targetFilePath.includes("tests") && !targetFilePath.includes("specs") && !targetFilePath.includes(".opencode") && !targetFilePath.includes(".openspec") && !targetFilePath.includes(".utils") && !targetFilePath.includes("config.ts") && !targetFilePath.includes("config.js")) {
|
|
234
319
|
if (currentState.phase !== "F2_IMPLEMENTATION") {
|
|
235
320
|
throw new Error(
|
|
236
321
|
`[SDD Contract Violation] No se permite escribir o editar código de la aplicación. Fase actual: '${currentState.phase}'. Debe transicionar a 'F2_IMPLEMENTATION' una vez que el contrato esté aprobado.`
|
package/.opencode/tools/brain.ts
CHANGED
|
@@ -2,8 +2,15 @@ import { tool } from "@opencode-ai/plugin"
|
|
|
2
2
|
import fs from "fs"
|
|
3
3
|
import path from "path"
|
|
4
4
|
|
|
5
|
+
const getRoot = (context: any) => {
|
|
6
|
+
if (context?.directory && context.directory !== "/") return context.directory;
|
|
7
|
+
if (context?.worktree && context.worktree !== "/") return context.worktree;
|
|
8
|
+
if (context?.cwd && context.cwd !== "/") return context.cwd;
|
|
9
|
+
return process.cwd();
|
|
10
|
+
};
|
|
11
|
+
|
|
5
12
|
const getBrainFilePath = (context: any) => {
|
|
6
|
-
const root = context
|
|
13
|
+
const root = getRoot(context);
|
|
7
14
|
const openspecDir = path.resolve(root, ".openspec")
|
|
8
15
|
if (!fs.existsSync(openspecDir)) {
|
|
9
16
|
fs.mkdirSync(openspecDir, { recursive: true })
|
package/.opencode/tools/sdd.ts
CHANGED
|
@@ -3,6 +3,14 @@ import fs from "fs"
|
|
|
3
3
|
import path from "path"
|
|
4
4
|
import { execSync, spawn } from "child_process"
|
|
5
5
|
|
|
6
|
+
// Helper to safely resolve root directory (avoiding OpenCode bug where worktree is '/' in non-git repos)
|
|
7
|
+
const getRoot = (context: any) => {
|
|
8
|
+
if (context?.directory && context.directory !== "/") return context.directory;
|
|
9
|
+
if (context?.worktree && context.worktree !== "/") return context.worktree;
|
|
10
|
+
if (context?.cwd && context.cwd !== "/") return context.cwd;
|
|
11
|
+
return process.cwd();
|
|
12
|
+
};
|
|
13
|
+
|
|
6
14
|
|
|
7
15
|
// Helper to parse semantic errors from compiler and linter outputs (reducing raw trace log bloat for the LLM)
|
|
8
16
|
const parseSemanticErrors = (rawOutput: string, type: "eslint" | "tsc"): any[] => {
|
|
@@ -43,7 +51,7 @@ const parseSemanticErrors = (rawOutput: string, type: "eslint" | "tsc"): any[] =
|
|
|
43
51
|
|
|
44
52
|
// Helper to resolve state path
|
|
45
53
|
const getStateFilePath = (context: any) => {
|
|
46
|
-
const root = context
|
|
54
|
+
const root = getRoot(context)
|
|
47
55
|
return path.resolve(root, ".openspec/sdd_state.json")
|
|
48
56
|
}
|
|
49
57
|
|
|
@@ -285,7 +293,7 @@ export const set_phase = tool({
|
|
|
285
293
|
loopCurrentIteration: tool.schema.number().optional().describe("Número de la iteración autónoma actual (empieza en 1)."),
|
|
286
294
|
},
|
|
287
295
|
async execute(args, context) {
|
|
288
|
-
const root = context
|
|
296
|
+
const root = getRoot(context)
|
|
289
297
|
const filePath = getStateFilePath(context)
|
|
290
298
|
const currentState = readState(filePath)
|
|
291
299
|
|
|
@@ -472,7 +480,7 @@ export const get_initial_session_data = tool({
|
|
|
472
480
|
description: "Obtiene atómicamente todos los datos de inicio de sesión: el estado actual del arnés, las memorias históricas clave del Brain ('learnings', 'design', 'routing'), y la lista curada de recomendaciones de diseño de Oh My Design. Reemplaza las llamadas secuenciales a sdd_get_state, brain_read_memory y sdd_list_design_recommendations.",
|
|
473
481
|
args: {},
|
|
474
482
|
async execute(args, context) {
|
|
475
|
-
const root = context
|
|
483
|
+
const root = getRoot(context)
|
|
476
484
|
const statePath = getStateFilePath(context)
|
|
477
485
|
const currentState = readState(statePath)
|
|
478
486
|
|
|
@@ -509,7 +517,7 @@ export const save_active_brief = tool({
|
|
|
509
517
|
brief: tool.schema.string().describe("Contenido en formato Markdown con el resumen del spec activo, componentes, diseño y dependencias.")
|
|
510
518
|
},
|
|
511
519
|
async execute(args, context) {
|
|
512
|
-
const root = context
|
|
520
|
+
const root = getRoot(context)
|
|
513
521
|
const openspecDir = path.resolve(root, ".openspec")
|
|
514
522
|
if (!fs.existsSync(openspecDir)) {
|
|
515
523
|
fs.mkdirSync(openspecDir, { recursive: true })
|
|
@@ -531,7 +539,7 @@ export const create_spec_folder = tool({
|
|
|
531
539
|
name: tool.schema.string().describe("Nombre del cambio en minúsculas y separado por guiones (ej. sumar-endpoint)")
|
|
532
540
|
},
|
|
533
541
|
async execute(args, context) {
|
|
534
|
-
const root = context
|
|
542
|
+
const root = getRoot(context)
|
|
535
543
|
const specsDir = path.resolve(root, ".openspec/specs")
|
|
536
544
|
|
|
537
545
|
if (!fs.existsSync(specsDir)) {
|
|
@@ -635,7 +643,7 @@ export const start_server = tool({
|
|
|
635
643
|
cwd: tool.schema.string().optional().describe("Directorio de trabajo para ejecutar el comando")
|
|
636
644
|
},
|
|
637
645
|
async execute(args, context) {
|
|
638
|
-
const root = context
|
|
646
|
+
const root = getRoot(context)
|
|
639
647
|
const targetCwd = args.cwd ? path.resolve(root, args.cwd) : root
|
|
640
648
|
const pidFile = getPidFilePath(root)
|
|
641
649
|
|
|
@@ -693,7 +701,7 @@ export const stop_server = tool({
|
|
|
693
701
|
description: "Detiene el servidor en segundo plano usando el PID registrado",
|
|
694
702
|
args: {},
|
|
695
703
|
async execute(args, context) {
|
|
696
|
-
const root = context
|
|
704
|
+
const root = getRoot(context)
|
|
697
705
|
const pidFile = getPidFilePath(root)
|
|
698
706
|
|
|
699
707
|
if (fs.existsSync(pidFile)) {
|
|
@@ -738,7 +746,7 @@ export const select_design = tool({
|
|
|
738
746
|
brandId: tool.schema.string().describe("ID exacto del diseño en oh-my-design (ej: 'linear.app', 'vercel', 'stripe')")
|
|
739
747
|
},
|
|
740
748
|
async execute(args, context) {
|
|
741
|
-
const root = context
|
|
749
|
+
const root = getRoot(context)
|
|
742
750
|
const brandId = args.brandId.trim()
|
|
743
751
|
const sourceDir = path.resolve(root, ".opencode/oh-my-design/design-md", brandId)
|
|
744
752
|
const targetDir = path.resolve(root, ".openspec")
|
|
@@ -767,7 +775,7 @@ export const select_design = tool({
|
|
|
767
775
|
|
|
768
776
|
// Helper extracted to allow substring-match recursion without `this.execute` typing issues.
|
|
769
777
|
async function selectDesignHelper(brandId: string, context: any) {
|
|
770
|
-
const root = context
|
|
778
|
+
const root = getRoot(context)
|
|
771
779
|
const sourceDir = path.resolve(root, ".opencode/oh-my-design/design-md", brandId)
|
|
772
780
|
const targetDir = path.resolve(root, ".openspec")
|
|
773
781
|
|
|
@@ -907,7 +915,7 @@ export const apply_brand_tokens = tool({
|
|
|
907
915
|
tokens: tool.schema.string().describe("JSON stringificado con {colors: {...}, typography: {...}, radius: {...}} extraído de contract.json design.tokens"),
|
|
908
916
|
},
|
|
909
917
|
async execute(args, context) {
|
|
910
|
-
const root = context
|
|
918
|
+
const root = getRoot(context)
|
|
911
919
|
const globalsPath = path.resolve(root, "src/app/globals.css")
|
|
912
920
|
|
|
913
921
|
if (!fs.existsSync(globalsPath)) {
|
|
@@ -981,7 +989,7 @@ export const generate_dockerfile = tool({
|
|
|
981
989
|
port: tool.schema.number().default(3000).describe("Puerto de la aplicación"),
|
|
982
990
|
},
|
|
983
991
|
async execute(args, context) {
|
|
984
|
-
const root = context
|
|
992
|
+
const root = getRoot(context)
|
|
985
993
|
|
|
986
994
|
if (args.stack === "nextjs") {
|
|
987
995
|
// Detect package manager
|
|
@@ -1157,7 +1165,7 @@ export const quick_lint = tool({
|
|
|
1157
1165
|
description: "Ejecuta el linter del proyecto (eslint) restringido a src/. Devuelve exit code y warnings. Usado como gate automático antes de transicionar a F3.",
|
|
1158
1166
|
args: {},
|
|
1159
1167
|
async execute(args, context) {
|
|
1160
|
-
const root = context
|
|
1168
|
+
const root = getRoot(context)
|
|
1161
1169
|
|
|
1162
1170
|
// Detect package manager scripts
|
|
1163
1171
|
const pkgPath = path.resolve(root, "package.json")
|
|
@@ -1195,7 +1203,7 @@ export const shift_left_verify = tool({
|
|
|
1195
1203
|
description: "Ejecuta validaciones estáticas Shift-Left completas en el proyecto: ejecuta el compilador de TypeScript (tsc) y el linter (eslint) de manera combinada. Limpia y parsea semánticamente los stack traces y logs de error crudos, devolviendo un JSON limpio y estructurado de errores que el LLM puede digerir y solucionar de inmediato sin perder atención.",
|
|
1196
1204
|
args: {},
|
|
1197
1205
|
async execute(args, context) {
|
|
1198
|
-
const root = context
|
|
1206
|
+
const root = getRoot(context)
|
|
1199
1207
|
const result: { tsc: { status: string, errors?: any[] }, eslint: { status: string, errors?: any[] } } = {
|
|
1200
1208
|
tsc: { status: "SUCCESS" },
|
|
1201
1209
|
eslint: { status: "SUCCESS" }
|
|
@@ -1346,7 +1354,7 @@ export const bootstrap_status = tool({
|
|
|
1346
1354
|
description: "Reporta el estado de bootstrap del proyecto (qué plantilla se usó, cuándo, qué componentes shadcn están instalados, versión de Node y package manager). Si el proyecto no está bootstrapped, retorna NOT_BOOTSTRAPPED. Útil para que el coder verifique antes de empezar a codear.",
|
|
1347
1355
|
args: {},
|
|
1348
1356
|
async execute(args, context) {
|
|
1349
|
-
const root = context
|
|
1357
|
+
const root = getRoot(context)
|
|
1350
1358
|
const status = readBootstrapStatus(root)
|
|
1351
1359
|
|
|
1352
1360
|
if (!status) {
|
|
@@ -1396,7 +1404,7 @@ export const bootstrap_nextjs_shadcn = tool({
|
|
|
1396
1404
|
force: tool.schema.boolean().default(false).describe("Si true, sobrescribe archivos existentes. Si false, los salta (mergea package.json)."),
|
|
1397
1405
|
},
|
|
1398
1406
|
async execute(args, context) {
|
|
1399
|
-
const root = context
|
|
1407
|
+
const root = getRoot(context)
|
|
1400
1408
|
const start = Date.now()
|
|
1401
1409
|
const templateDir = path.resolve(root, ".opencode/templates/nextjs-shadcn")
|
|
1402
1410
|
|
|
@@ -1619,7 +1627,7 @@ export const bootstrap_fastapi = tool({
|
|
|
1619
1627
|
force: tool.schema.boolean().default(false).describe("Si true, sobrescribe archivos existentes. Si false, los salta."),
|
|
1620
1628
|
},
|
|
1621
1629
|
async execute(args, context) {
|
|
1622
|
-
const root = context
|
|
1630
|
+
const root = getRoot(context)
|
|
1623
1631
|
const start = Date.now()
|
|
1624
1632
|
const templateDir = path.resolve(root, ".opencode/templates/fastapi-sdd")
|
|
1625
1633
|
|
|
@@ -1816,7 +1824,7 @@ export const validate_lucide_icons_batch = tool({
|
|
|
1816
1824
|
icons: tool.schema.array(tool.schema.string()).describe("Lista de nombres de iconos a validar (ej: ['Sun', 'Moon', 'Plus'])"),
|
|
1817
1825
|
},
|
|
1818
1826
|
async execute(args, context) {
|
|
1819
|
-
const root = context
|
|
1827
|
+
const root = getRoot(context)
|
|
1820
1828
|
const results: Record<string, { valid: boolean; source: string }> = {}
|
|
1821
1829
|
|
|
1822
1830
|
// Lista de iconos comunes como fallback
|
|
@@ -1887,7 +1895,7 @@ export const generate_tests = tool({
|
|
|
1887
1895
|
description: "Autogenera plantillas de pruebas unitarias/integración en tests/unit/ a partir de los escenarios de prueba descritos en el contrato activo de sdd_state.json. No pisa archivos de pruebas existentes.",
|
|
1888
1896
|
args: {},
|
|
1889
1897
|
async execute(args, context) {
|
|
1890
|
-
const root = context
|
|
1898
|
+
const root = getRoot(context)
|
|
1891
1899
|
const stateFile = path.resolve(root, ".openspec/sdd_state.json")
|
|
1892
1900
|
if (!fs.existsSync(stateFile)) {
|
|
1893
1901
|
return JSON.stringify({ success: false, error: "sdd_state.json no existe. Inicia una sesión SDD primero." }, null, 2)
|
|
@@ -1977,7 +1985,7 @@ export const save_playwright_artifacts = tool({
|
|
|
1977
1985
|
move: tool.schema.boolean().default(false).describe("Si true, mueve los archivos en lugar de copiarlos.")
|
|
1978
1986
|
},
|
|
1979
1987
|
async execute(args, context) {
|
|
1980
|
-
const root = context
|
|
1988
|
+
const root = getRoot(context)
|
|
1981
1989
|
const stateFile = path.resolve(root, ".openspec/sdd_state.json")
|
|
1982
1990
|
if (!fs.existsSync(stateFile)) {
|
|
1983
1991
|
return JSON.stringify({ success: false, error: "sdd_state.json no existe. Inicia una sesión SDD primero." }, null, 2)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Soporte ACP
|
|
2
|
+
|
|
3
|
+
Utilice OpenCode en cualquier editor compatible con ACP.
|
|
4
|
+
|
|
5
|
+
OpenCode admite el [Agent Client Protocol](https://agentclientprotocol.com) o (ACP), lo que le permite usarlo directamente en editores e IDE compatibles.
|
|
6
|
+
|
|
7
|
+
> [!TIP]
|
|
8
|
+
> Para obtener una lista de editores y herramientas compatibles con ACP, consulte el [informe de progreso de ACP](https://zed.dev/blog/acp-progress-report#available-now).
|
|
9
|
+
|
|
10
|
+
ACP es un protocolo abierto que estandariza la comunicación entre editores de código y agentes de codificación de IA.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [Configuración](#configuración)
|
|
15
|
+
|
|
16
|
+
Para usar OpenCode a través de ACP, configure su editor para ejecutar el comando `opencode acp`.
|
|
17
|
+
|
|
18
|
+
El comando inicia OpenCode como un subproceso compatible con ACP que se comunica con su editor a través de JSON-RPC a través de stdio.
|
|
19
|
+
|
|
20
|
+
A continuación se muestran ejemplos de editores populares que admiten ACP.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### [Zed](#zed)
|
|
25
|
+
|
|
26
|
+
Agregue a su configuración [Zed](https://zed.dev) (`~/.config/zed/settings.json`):
|
|
27
|
+
|
|
28
|
+
**File**: ~/.config/zed/settings.json
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"agent_servers": {
|
|
33
|
+
"OpenCode": {
|
|
34
|
+
"command": "opencode",
|
|
35
|
+
"args": ["acp"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Para abrirlo, use la acción `agent: new thread` en la **Paleta de comandos**.
|
|
42
|
+
|
|
43
|
+
También puedes vincular un atajo de teclado editando tu `keymap.json`:
|
|
44
|
+
|
|
45
|
+
**File**: keymap.json
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
[
|
|
49
|
+
{
|
|
50
|
+
"bindings": {
|
|
51
|
+
"cmd-alt-o": [
|
|
52
|
+
"agent::NewExternalAgentThread",
|
|
53
|
+
{
|
|
54
|
+
"agent": {
|
|
55
|
+
"custom": {
|
|
56
|
+
"name": "OpenCode",
|
|
57
|
+
"command": {
|
|
58
|
+
"command": "opencode",
|
|
59
|
+
"args": ["acp"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### [JetBrains IDEs](#jetbrains-ides)
|
|
73
|
+
|
|
74
|
+
Agregue a su [JetBrains IDE](https://www.jetbrains.com/) acp.json de acuerdo con la [documentación](https://www.jetbrains.com/help/ai-assistant/acp.html):
|
|
75
|
+
|
|
76
|
+
**File**: acp.json
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"agent_servers": {
|
|
81
|
+
"OpenCode": {
|
|
82
|
+
"command": "/absolute/path/bin/opencode",
|
|
83
|
+
"args": ["acp"]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Para abrirlo, use el nuevo agente ‘OpenCode’ en el selector de agentes de AI Chat.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### [Avante.nvim](#avantenvim)
|
|
94
|
+
|
|
95
|
+
Agregue a su configuración [Avante.nvim](https://github.com/yetone/avante.nvim):
|
|
96
|
+
|
|
97
|
+
**File**:
|
|
98
|
+
|
|
99
|
+
```lua
|
|
100
|
+
{
|
|
101
|
+
acp_providers = {
|
|
102
|
+
["opencode"] = {
|
|
103
|
+
command = "opencode",
|
|
104
|
+
args = { "acp" }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Si necesita pasar variables de entorno:
|
|
111
|
+
|
|
112
|
+
**File**:
|
|
113
|
+
|
|
114
|
+
```lua
|
|
115
|
+
{
|
|
116
|
+
acp_providers = {
|
|
117
|
+
["opencode"] = {
|
|
118
|
+
command = "opencode",
|
|
119
|
+
args = { "acp" },
|
|
120
|
+
env = {
|
|
121
|
+
OPENCODE_API_KEY = os.getenv("OPENCODE_API_KEY")
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### [CodeCompanion.nvim](#codecompanionnvim)
|
|
131
|
+
|
|
132
|
+
Para usar OpenCode como agente ACP en [CodeCompanion.nvim](https://github.com/olimorris/codecompanion.nvim), agregue lo siguiente a su configuración de Neovim:
|
|
133
|
+
|
|
134
|
+
**File**:
|
|
135
|
+
|
|
136
|
+
```lua
|
|
137
|
+
require("codecompanion").setup({
|
|
138
|
+
interactions = {
|
|
139
|
+
chat = {
|
|
140
|
+
adapter = {
|
|
141
|
+
name = "opencode",
|
|
142
|
+
model = "claude-sonnet-4",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Esta configuración configura CodeCompanion para usar OpenCode como agente ACP para el chat.
|
|
150
|
+
|
|
151
|
+
Si necesita pasar variables de entorno (como `OPENCODE_API_KEY`), consulte [Configuración de adaptadores: variables de entorno](https://codecompanion.olimorris.dev/getting-started#setting-an-api-key) en la documentación de CodeCompanion.nvim para obtener detalles completos.
|
|
152
|
+
|
|
153
|
+
## [Soporte](#soporte)
|
|
154
|
+
|
|
155
|
+
OpenCode funciona igual a través de ACP que en la terminal. Todas las funciones son compatibles:
|
|
156
|
+
|
|
157
|
+
> [!NOTE]
|
|
158
|
+
> Algunos comandos de barra integrados como `/undo` y `/redo` no son compatibles actualmente.
|
|
159
|
+
|
|
160
|
+
- Herramientas integradas (operaciones de archivos, comandos de terminal, etc.)
|
|
161
|
+
- Herramientas personalizadas y comandos de barra
|
|
162
|
+
- Servidores MCP configurados en su configuración OpenCode
|
|
163
|
+
- Reglas específicas del proyecto de `AGENTS.md`
|
|
164
|
+
- Formateadores y linters personalizados
|
|
165
|
+
- Sistema de agentes y permisos.
|
|
Binary file
|