@liriraid/agentflow-ai 1.0.14 → 1.0.16
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/bin/agentflow.mjs +3 -2
- package/orchestrator.js +590 -80
- package/package.json +1 -5
- package/src/ink/app.mjs +22 -14
- package/src/ink/index.mjs +34 -4
- package/templates/en/ORCHESTRATOR.md +44 -26
- package/templates/es/ORCHESTRATOR.md +55 -23
- package/scripts/scaffold-agent-configs.mjs +0 -100
- package/scripts/scaffold-openspec-change.mjs +0 -84
- package/scripts/update-skill-registry.mjs +0 -174
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liriraid/agentflow-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "Multi-agent workspace orchestrator with TUI. Coordinates AI coding agents over your real frontend and backend projects.",
|
|
5
5
|
"author": "LiriRaid",
|
|
6
6
|
"homepage": "https://github.com/LiriRaid/agentflow-ai#readme",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"bin",
|
|
23
23
|
"src",
|
|
24
24
|
"templates",
|
|
25
|
-
"scripts",
|
|
26
25
|
"orchestrator.js",
|
|
27
26
|
"LICENSE"
|
|
28
27
|
],
|
|
@@ -31,9 +30,6 @@
|
|
|
31
30
|
"start:paused": "node orchestrator.js --paused",
|
|
32
31
|
"start:ink": "node src/ink/index.mjs",
|
|
33
32
|
"start:ink:paused": "node src/ink/index.mjs --paused",
|
|
34
|
-
"skills:registry": "node scripts/update-skill-registry.mjs",
|
|
35
|
-
"openspec:new": "node scripts/scaffold-openspec-change.mjs",
|
|
36
|
-
"agent-config:init": "node scripts/scaffold-agent-configs.mjs",
|
|
37
33
|
"cli:help": "node bin/agentflow.mjs --help"
|
|
38
34
|
},
|
|
39
35
|
"keywords": [
|
package/src/ink/app.mjs
CHANGED
|
@@ -19,6 +19,8 @@ const TEXT = {
|
|
|
19
19
|
es: {
|
|
20
20
|
busy: 'Ocupado',
|
|
21
21
|
idle: 'En espera',
|
|
22
|
+
failed: 'Falló',
|
|
23
|
+
retrying: 'Reintentando',
|
|
22
24
|
noTask: 'Sin tarea activa',
|
|
23
25
|
ready: 'Listo para trabajar',
|
|
24
26
|
project: 'Proyecto',
|
|
@@ -31,9 +33,7 @@ const TEXT = {
|
|
|
31
33
|
paused: 'Pausado',
|
|
32
34
|
preview: 'Explorando Ink',
|
|
33
35
|
shortcuts: 'Atajos',
|
|
34
|
-
start: 'iniciar/reanudar',
|
|
35
36
|
pause: 'pausar',
|
|
36
|
-
reload: 'recargar QUEUE.md',
|
|
37
37
|
quit: 'salir y matar agentes',
|
|
38
38
|
summary: 'Resumen',
|
|
39
39
|
activeQueue: 'Cola activa',
|
|
@@ -43,6 +43,8 @@ const TEXT = {
|
|
|
43
43
|
en: {
|
|
44
44
|
busy: 'Busy',
|
|
45
45
|
idle: 'Idle',
|
|
46
|
+
failed: 'Failed',
|
|
47
|
+
retrying: 'Retrying',
|
|
46
48
|
noTask: 'No active task',
|
|
47
49
|
ready: 'Ready to work',
|
|
48
50
|
project: 'Project',
|
|
@@ -55,9 +57,7 @@ const TEXT = {
|
|
|
55
57
|
paused: 'Paused',
|
|
56
58
|
preview: 'Exploring Ink',
|
|
57
59
|
shortcuts: 'Shortcuts',
|
|
58
|
-
start: 'start/resume',
|
|
59
60
|
pause: 'pause',
|
|
60
|
-
reload: 'reload QUEUE.md',
|
|
61
61
|
quit: 'quit and stop agents',
|
|
62
62
|
summary: 'Summary',
|
|
63
63
|
activeQueue: 'Active Queue',
|
|
@@ -82,7 +82,20 @@ const Panel = ({title, width, children}) =>
|
|
|
82
82
|
);
|
|
83
83
|
|
|
84
84
|
const AgentCard = ({agent, text}) => {
|
|
85
|
-
const statusColor =
|
|
85
|
+
const statusColor =
|
|
86
|
+
agent.status === 'busy' ? COLORS.success :
|
|
87
|
+
agent.status === 'failed' ? 'red' :
|
|
88
|
+
agent.status === 'retrying' ? COLORS.warning :
|
|
89
|
+
COLORS.muted;
|
|
90
|
+
|
|
91
|
+
const statusLabel =
|
|
92
|
+
agent.status === 'busy' ? text.busy :
|
|
93
|
+
agent.status === 'failed' ? text.failed :
|
|
94
|
+
agent.status === 'retrying' ? text.retrying :
|
|
95
|
+
text.idle;
|
|
96
|
+
|
|
97
|
+
const costLine = agent.totalCost > 0 ? `${text.cost}: $${agent.totalCost.toFixed(2)}` : null;
|
|
98
|
+
|
|
86
99
|
return h(
|
|
87
100
|
Box,
|
|
88
101
|
{
|
|
@@ -94,13 +107,14 @@ const AgentCard = ({agent, text}) => {
|
|
|
94
107
|
flexDirection: 'column'
|
|
95
108
|
},
|
|
96
109
|
h(Text, {bold: true, wrap: 'wrap'}, agent.name),
|
|
97
|
-
h(Text, {color: statusColor
|
|
110
|
+
h(Text, {color: statusColor, bold: agent.status === 'failed' || agent.status === 'retrying'}, statusLabel),
|
|
98
111
|
h(
|
|
99
112
|
Text,
|
|
100
113
|
{color: COLORS.muted, wrap: 'wrap'},
|
|
101
114
|
agent.task || text.noTask
|
|
102
115
|
),
|
|
103
|
-
h(Text, {color: COLORS.muted, wrap: 'wrap'}, agent.detail || text.ready)
|
|
116
|
+
h(Text, {color: COLORS.muted, wrap: 'wrap'}, agent.detail || text.ready),
|
|
117
|
+
...(costLine ? [h(Text, {color: COLORS.success, wrap: 'wrap'}, costLine)] : [])
|
|
104
118
|
);
|
|
105
119
|
};
|
|
106
120
|
|
|
@@ -123,8 +137,6 @@ export function App({snapshot, paused = false, onAction}) {
|
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
const normalized = input.toLowerCase();
|
|
126
|
-
if (normalized === 'r') onAction?.('reload');
|
|
127
|
-
if (normalized === 's') onAction?.('start');
|
|
128
140
|
if (normalized === 'p') onAction?.('pause');
|
|
129
141
|
if (normalized === 'q') onAction?.('quit');
|
|
130
142
|
});
|
|
@@ -173,14 +185,10 @@ export function App({snapshot, paused = false, onAction}) {
|
|
|
173
185
|
Box,
|
|
174
186
|
{marginBottom: 1},
|
|
175
187
|
h(Text, {color: COLORS.warning}, `${text.shortcuts}: `),
|
|
176
|
-
h(Text, {bold: true}, 'S'),
|
|
177
|
-
h(Text, {color: COLORS.muted}, truncate(` ${text.start} `, Math.max(0, shortcutWidth - 40))),
|
|
178
188
|
h(Text, {bold: true}, 'P'),
|
|
179
189
|
h(Text, {color: COLORS.muted}, truncate(` ${text.pause} `, Math.max(0, shortcutWidth - 55))),
|
|
180
|
-
h(Text, {bold: true}, 'R'),
|
|
181
|
-
h(Text, {color: COLORS.muted}, truncate(` ${text.reload} `, Math.max(0, shortcutWidth - 70))),
|
|
182
190
|
h(Text, {bold: true}, 'Q'),
|
|
183
|
-
h(Text, {color: COLORS.muted}, truncate(` ${text.quit}`, Math.max(0, shortcutWidth -
|
|
191
|
+
h(Text, {color: COLORS.muted}, truncate(` ${text.quit}`, Math.max(0, shortcutWidth - 70)))
|
|
184
192
|
),
|
|
185
193
|
h(
|
|
186
194
|
Box,
|
package/src/ink/index.mjs
CHANGED
|
@@ -219,14 +219,33 @@ function buildSnapshot() {
|
|
|
219
219
|
|
|
220
220
|
const agents = Object.entries(config.agents || {}).map(([name]) => {
|
|
221
221
|
const agent = engineState.agents?.[name];
|
|
222
|
+
const lastLine = agent?.lastLine || '';
|
|
223
|
+
|
|
224
|
+
let status;
|
|
225
|
+
if (agent?.status === 'busy') {
|
|
226
|
+
status = 'busy';
|
|
227
|
+
} else if (lastLine.startsWith('FALLÓ:') || lastLine.startsWith('FAILED:')) {
|
|
228
|
+
status = 'failed';
|
|
229
|
+
} else if (
|
|
230
|
+
lastLine.startsWith('REINTENTO:') ||
|
|
231
|
+
lastLine.startsWith('LÍMITE:') ||
|
|
232
|
+
lastLine.startsWith('RETRY:') ||
|
|
233
|
+
lastLine.startsWith('LIMIT:')
|
|
234
|
+
) {
|
|
235
|
+
status = 'retrying';
|
|
236
|
+
} else {
|
|
237
|
+
status = 'idle';
|
|
238
|
+
}
|
|
239
|
+
|
|
222
240
|
return {
|
|
223
241
|
name,
|
|
224
|
-
status
|
|
242
|
+
status,
|
|
225
243
|
task: agent?.task ? `${agent.task.id} · ${agent.task.title}` : null,
|
|
226
244
|
detail:
|
|
227
245
|
agent?.status === 'busy'
|
|
228
246
|
? `${agent.task?.priority || 'P?'} · ${agent.task?.repo || 'repo'}`
|
|
229
|
-
:
|
|
247
|
+
: lastLine || text.ready,
|
|
248
|
+
totalCost: agent?.totalCost || 0
|
|
230
249
|
};
|
|
231
250
|
});
|
|
232
251
|
|
|
@@ -350,7 +369,7 @@ function requestAction(action) {
|
|
|
350
369
|
}
|
|
351
370
|
|
|
352
371
|
function shutdown() {
|
|
353
|
-
if (refreshTimer)
|
|
372
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
354
373
|
if (resizeTimer) clearTimeout(resizeTimer);
|
|
355
374
|
if (spawnedEngine && !spawnedEngine.killed && quitRequested) {
|
|
356
375
|
try {
|
|
@@ -364,10 +383,21 @@ function shutdown() {
|
|
|
364
383
|
try { fs.unlinkSync(STATE_FILE); } catch {}
|
|
365
384
|
}
|
|
366
385
|
|
|
386
|
+
function scheduleRefresh() {
|
|
387
|
+
const engineState = readEngineState();
|
|
388
|
+
const busy = engineState && Object.values(engineState.agents || {}).some(a => a.status === 'busy');
|
|
389
|
+
const hasWork = engineState && ((engineState.queue || []).length > 0 || (engineState.inProgress || []).length > 0);
|
|
390
|
+
const ms = (busy || hasWork) ? 1000 : 4000;
|
|
391
|
+
refreshTimer = setTimeout(() => {
|
|
392
|
+
refresh();
|
|
393
|
+
scheduleRefresh();
|
|
394
|
+
}, ms);
|
|
395
|
+
}
|
|
396
|
+
|
|
367
397
|
function mount() {
|
|
368
398
|
ensureEngine();
|
|
369
399
|
refresh();
|
|
370
|
-
|
|
400
|
+
scheduleRefresh();
|
|
371
401
|
|
|
372
402
|
if (process.stdout.isTTY) {
|
|
373
403
|
process.stdout.on('resize', () => {
|
|
@@ -64,44 +64,56 @@ When the user says something like `Read ORCHESTRATOR.md and start`, do this:
|
|
|
64
64
|
|
|
65
65
|
1. Read this file completely.
|
|
66
66
|
2. Read `orchestrator.config.json` — identify the real project paths in `repos` (frontend, backend). Those are the paths where the worker agents operate.
|
|
67
|
-
3.
|
|
68
|
-
4. Read
|
|
69
|
-
5.
|
|
70
|
-
6. Read `
|
|
71
|
-
7. Read
|
|
72
|
-
8. Read `
|
|
73
|
-
9.
|
|
74
|
-
10.
|
|
67
|
+
3. **Verify automation:** The orchestrator uses `fs.watch` (Node.js real-time watching). No Task Scheduler needed. The TUI stays running in a terminal and detects changes immediately.
|
|
68
|
+
4. Read `<projectName>-plan.md`, `PLAN.md`, or `plan.md` if present.
|
|
69
|
+
5. Read the newest `handoffs/HANDOFF-*.md` if the folder exists.
|
|
70
|
+
6. **Read `INBOX.md` if it exists** — it contains automatic TUI notifications of completed tasks that require your attention (creating next TASKs, reading agent reports, etc.).
|
|
71
|
+
7. Read `QUEUE.md` to understand pending, active, and completed work.
|
|
72
|
+
8. Read all `progress/PROGRESS-*.md` files if present.
|
|
73
|
+
9. Read `ENGRAM.md` and follow the memory rules.
|
|
74
|
+
10. Use `openspec/` for large or multi-phase changes.
|
|
75
|
+
11. Tell the user the orchestrator is ready and ask what to prioritize.
|
|
75
76
|
|
|
76
77
|
**INBOX rule:** At the start of EACH response, if `INBOX.md` has new entries since your last read, check it first. This is how you know when an agent finished and what to create next — without Away Mode active.
|
|
77
78
|
|
|
79
|
+
**STATUS rule:** Also read `STATUS.md` at the start of each response to get current context of agents and queue. This file updates automatically every 60 seconds.
|
|
80
|
+
|
|
81
|
+
**ACTIONS rule:** If `ACTIONS.md` exists, read it too — it contains automatic monitoring actions (completed tasks needing follow-up, failed tasks, stuck tasks, etc).
|
|
82
|
+
|
|
78
83
|
Startup is context loading only. Do not create project code changes during startup.
|
|
79
84
|
|
|
80
85
|
## Away Mode
|
|
81
86
|
|
|
82
87
|
If the user says something like:
|
|
83
88
|
|
|
89
|
+
- `I will be away for 1 hour`
|
|
84
90
|
- `I will be away for 2 hours`
|
|
85
|
-
- `
|
|
86
|
-
- `
|
|
87
|
-
- `continue while I am away`
|
|
91
|
+
- `going out for a while`
|
|
92
|
+
- `activate monitoring`
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
**Activate Away Mode:**
|
|
95
|
+
```bash
|
|
96
|
+
echo away > .away-mode
|
|
97
|
+
```
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
**Away Mode checks every 5 minutes** and will:
|
|
100
|
+
- Completed tasks without follow-up
|
|
101
|
+
- Failed tasks
|
|
102
|
+
- Stuck tasks (>10 min)
|
|
103
|
+
- Pending tasks with no agent assigned → assign them automatically
|
|
104
|
+
- Write to ACTIONS.md
|
|
98
105
|
|
|
99
|
-
|
|
106
|
+
**Auto-deactivate:**
|
|
107
|
+
When there are NO pending tasks AND NO agents working AND all tasks are completed:
|
|
108
|
+
- Away Mode removes .away-mode automatically
|
|
109
|
+
- Away Mode deactivates by itself
|
|
110
|
+
- When you return and say "I'm back" → Claude responds normally
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
**Deactivate Away Mode:**
|
|
113
|
+
```bash
|
|
114
|
+
# Delete indicator file
|
|
115
|
+
del .away-mode
|
|
116
|
+
```
|
|
105
117
|
|
|
106
118
|
## Fallback Policy
|
|
107
119
|
|
|
@@ -142,7 +154,13 @@ Default agent summary:
|
|
|
142
154
|
|
|
143
155
|
## How To Assign Work
|
|
144
156
|
|
|
145
|
-
|
|
157
|
+
1. **When the user asks for a change or new task** → **NEVER analyze directly yourself**
|
|
158
|
+
- **First**: Create a TASK in `QUEUE.md` assigned to **OpenCode** to analyze the context
|
|
159
|
+
- **Second**: Wait for OpenCode to finish its analysis (check INBOX.md or progress/)
|
|
160
|
+
- **Third**: You receive the analysis → create new TASK to implement (Codex or OpenCode)
|
|
161
|
+
- **Never analyze the project code yourself** - that's OpenCode's job
|
|
162
|
+
|
|
163
|
+
2. Write TASKs in `QUEUE.md` with this format:
|
|
146
164
|
|
|
147
165
|
```text
|
|
148
166
|
TASK-NNN | short title | Agent | P1 | repo | detailed description
|
|
@@ -155,7 +173,7 @@ Rules:
|
|
|
155
173
|
3. Add `> after:TASK-NNN` at the end of the description for dependencies.
|
|
156
174
|
4. Use `TASKS.md` under `### TASK-NNN` for longer task specs when needed.
|
|
157
175
|
5. Use `briefs/TASK-NNN-BRIEF.md` for very detailed briefs when needed.
|
|
158
|
-
6.
|
|
176
|
+
6. **The TUI starts automatically** - you don't need to press R or S. The TUI detects new tasks and launches them.
|
|
159
177
|
|
|
160
178
|
Routing preferences:
|
|
161
179
|
|
|
@@ -43,18 +43,23 @@ Cuando necesites entender el proyecto para planificar tareas, **lee archivos des
|
|
|
43
43
|
## Al iniciar la sesión — OBLIGATORIO
|
|
44
44
|
|
|
45
45
|
1. Lee este archivo completo.
|
|
46
|
-
2. Lee `orchestrator.config.json` — identifica las rutas reales en `repos` (frontend, backend). Esas son las rutas del proyecto real donde trabajan los
|
|
47
|
-
3.
|
|
48
|
-
4. Lee
|
|
49
|
-
5.
|
|
50
|
-
6. Lee `
|
|
51
|
-
7. Lee
|
|
52
|
-
8. Lee `
|
|
53
|
-
9.
|
|
54
|
-
10.
|
|
46
|
+
2. Lee `orchestrator.config.json` — identifica las rutas reales en `repos` (frontend, backend). Esas son las rutas del proyecto real donde trabajan los agentees.
|
|
47
|
+
3. **Verifica la automatización:** El orquestador usa `fs.watch` (realtime de Node.js). No necesita Task Scheduler. La TUI corre en una terminal y detecta cambios inmediatamente.
|
|
48
|
+
4. Lee `<projectName>-plan.md` (o `PLAN.md` / `plan.md`) si existe; ese es el plan general.
|
|
49
|
+
5. Lee el handoff más reciente en `handoffs/HANDOFF-*.md` si existe la carpeta.
|
|
50
|
+
6. **Lee `INBOX.md` si existe** — contiene notificaciones automáticas del TUI de tasks completadas que requieren tu atención (crear siguientes TASKs, leer reportes de agentes, etc.).
|
|
51
|
+
7. Lee `QUEUE.md` para ver trabajo activo y pendiente.
|
|
52
|
+
8. Lee todos los archivos `progress/PROGRESS-*.md` que existan para entender el estado actual de cada agente.
|
|
53
|
+
9. Lee `ENGRAM.md` para respetar la convención de memoria persistente del proyecto.
|
|
54
|
+
10. Si existe `openspec/`, úsalo como capa de artefactos para cambios grandes o de varias fases.
|
|
55
|
+
11. Pregunta al usuario qué quiere priorizar; no planifiques toda la sesión automáticamente.
|
|
55
56
|
|
|
56
57
|
**Regla de INBOX:** Al inicio de CADA respuesta, si `INBOX.md` tiene entradas nuevas desde tu última lectura, léelo primero antes de responder al usuario. Así sabrás qué agentes terminaron y qué falta crear.
|
|
57
58
|
|
|
59
|
+
**Regla de STATUS:** También lee `STATUS.md` al inicio de cada respuesta para tener contexto del estado actual de los agentes y la cola. Este archivo se actualiza automáticamente cada 60 segundos.
|
|
60
|
+
|
|
61
|
+
**Regla de ACTIONS:** Si existe `ACTIONS.md`, léelo también - contiene acciones automáticas del monitoreo (tareas completadas que necesitan seguimiento, tareas fallidas, etc).
|
|
62
|
+
|
|
58
63
|
## Restricción operativa por defecto
|
|
59
64
|
|
|
60
65
|
Aunque esta plantilla soporte múltiples agentes, en este proyecto debes operar **solo con estas 3 IA por defecto**:
|
|
@@ -71,13 +76,34 @@ Los demás agentes pueden permanecer configurados en `orchestrator.config.json`,
|
|
|
71
76
|
|
|
72
77
|
Si el usuario dice explícitamente algo como:
|
|
73
78
|
|
|
79
|
+
- `estaré ausente 1 hora`
|
|
74
80
|
- `estaré ausente 2 horas`
|
|
75
81
|
- `me voy un rato`
|
|
76
82
|
- `activa monitoreo`
|
|
77
|
-
- `quédate revisando`
|
|
78
|
-
- `monitorea mientras no estoy`
|
|
79
83
|
|
|
80
|
-
|
|
84
|
+
**Activar Modo Ausencia:**
|
|
85
|
+
```bash
|
|
86
|
+
echo away > .away-mode
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**El modo ausente hace revisión cada 5 minutos** y revisará:
|
|
90
|
+
- Tareas completadas sin seguimiento
|
|
91
|
+
- Tareas fallidas
|
|
92
|
+
- Tareas atascadas (>10 min)
|
|
93
|
+
- Tareas pendientes sin agente asignado → las asignará automáticamente
|
|
94
|
+
- Y escribirá en ACTIONS.md
|
|
95
|
+
|
|
96
|
+
**Auto-desactivación:**
|
|
97
|
+
Cuando NO hay tareas pendientes Y NO hay agentes trabajando Y todas las tareas están completadas:
|
|
98
|
+
- El modo elimina .away-mode automáticamente
|
|
99
|
+
- Modo Ausencia se desactiva solo
|
|
100
|
+
- Cuando vuelvas y le digas "ya volví" → Claude responde normalmente
|
|
101
|
+
|
|
102
|
+
**Desactivar Modo Ausencia:**
|
|
103
|
+
```bash
|
|
104
|
+
# Eliminar archivo indicador
|
|
105
|
+
del .away-mode
|
|
106
|
+
```
|
|
81
107
|
|
|
82
108
|
### Qué significa Modo Ausencia
|
|
83
109
|
|
|
@@ -149,17 +175,23 @@ Revisa `orchestrator.config.json` → `agents`. Cada entrada tiene:
|
|
|
149
175
|
|
|
150
176
|
## Cómo asignar trabajo
|
|
151
177
|
|
|
152
|
-
1.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
2.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
1. **Cuando el usuario pide un cambio o nueva tarea** → **NUNCA analices directamente**
|
|
179
|
+
- **Primero**: Crea una TASK en `QUEUE.md` asignada a **OpenCode** para que analice el contexto
|
|
180
|
+
- **Segundo**: Espera a que OpenCode termine su análisis (revisa INBOX.md o progress/)
|
|
181
|
+
- **Tercero**: Recibes el análisis → creas nueva TASK para implementar (Codex o OpenCode)
|
|
182
|
+
- **Nunca analices el código del proyecto directamente tu mismo** - eso lo hace OpenCode
|
|
183
|
+
|
|
184
|
+
2. Escribe TASKs en `QUEUE.md` (formato pipe; la TUI lo lee):
|
|
185
|
+
```
|
|
186
|
+
TASK-NNN | titulo corto | Agent | P1 | repo | descripcion larga
|
|
187
|
+
```
|
|
188
|
+
Valores válidos de `Agent`: exactamente las keys de `orchestrator.config.json.agents`.
|
|
189
|
+
Valores válidos de `repo`: exactamente las keys de `orchestrator.config.json.repos`.
|
|
190
|
+
3. (Opcional) También escribe una spec larga en `TASKS.md` bajo un heading `### TASK-NNN`; se inyecta al brief.
|
|
191
|
+
4. (Opcional) Para un brief muy detallado, crea `briefs/TASK-NNN-BRIEF.md`; también se inyecta.
|
|
192
|
+
5. Dependencias: agrega `> after:TASK-NNN` al final de la descripción para bloquear la tarea.
|
|
193
|
+
6. **La TUI inicia automáticamente** - NO necesitas presionar R ni S. La TUI detecta nuevas tasks y las lanza.
|
|
194
|
+
7. **Prioriza Codex y OpenCode** para toda implementación y exploración. Claude-Workers solo cuando hay saturación o fallo total de agentes de soporte.
|
|
163
195
|
7. Distribución según cantidad de TASKs independientes:
|
|
164
196
|
- **1 tarea**: OpenCode (exploración) o Codex (implementación). Nunca Claude-Worker en primera instancia.
|
|
165
197
|
- **2 tareas**: OpenCode + Codex, una cada uno.
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
const ROOT = process.cwd();
|
|
7
|
-
const CONFIG_FILE = path.join(ROOT, 'orchestrator.config.json');
|
|
8
|
-
const CONFIG = fs.existsSync(CONFIG_FILE)
|
|
9
|
-
? JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'))
|
|
10
|
-
: {};
|
|
11
|
-
const LANGUAGE = CONFIG.workspaceLanguage === 'en' ? 'en' : 'es';
|
|
12
|
-
|
|
13
|
-
const CONTENT = {
|
|
14
|
-
es: {
|
|
15
|
-
claude: [
|
|
16
|
-
'# Claude Local Config',
|
|
17
|
-
'',
|
|
18
|
-
'Esta carpeta contiene la configuración local del proyecto para Claude.',
|
|
19
|
-
'',
|
|
20
|
-
'- `skills/` guarda skills propias del repo',
|
|
21
|
-
'- `CLAUDE.md` en la raíz define el routing del proyecto',
|
|
22
|
-
'- esta capa local debe priorizarse sobre configuración global del usuario'
|
|
23
|
-
],
|
|
24
|
-
codex: [
|
|
25
|
-
'# Codex Local Config',
|
|
26
|
-
'',
|
|
27
|
-
'Esta carpeta reserva la configuración local del proyecto para Codex.',
|
|
28
|
-
'',
|
|
29
|
-
'- hoy se usa como base reusable del proyecto',
|
|
30
|
-
'- mañana puede alojar prompts, perfiles, reglas o plugins locales',
|
|
31
|
-
'- no debe depender solo de configuración global del usuario'
|
|
32
|
-
],
|
|
33
|
-
opencode: [
|
|
34
|
-
'# OpenCode Local Config',
|
|
35
|
-
'',
|
|
36
|
-
'Esta carpeta reserva la configuración local del proyecto para OpenCode.',
|
|
37
|
-
'',
|
|
38
|
-
'- hoy se usa como base reusable del proyecto',
|
|
39
|
-
'- mañana puede alojar reglas, prompts o convenciones específicas',
|
|
40
|
-
'- no debe depender solo de configuración global del usuario'
|
|
41
|
-
],
|
|
42
|
-
done: 'Configuración local por agente creada o verificada.'
|
|
43
|
-
},
|
|
44
|
-
en: {
|
|
45
|
-
claude: [
|
|
46
|
-
'# Claude Local Config',
|
|
47
|
-
'',
|
|
48
|
-
'This folder contains project-local Claude configuration.',
|
|
49
|
-
'',
|
|
50
|
-
'- `skills/` stores repo-specific skills',
|
|
51
|
-
'- root `CLAUDE.md` defines project routing',
|
|
52
|
-
'- this local layer should take priority over global user config'
|
|
53
|
-
],
|
|
54
|
-
codex: [
|
|
55
|
-
'# Codex Local Config',
|
|
56
|
-
'',
|
|
57
|
-
'This folder reserves project-local configuration for Codex.',
|
|
58
|
-
'',
|
|
59
|
-
'- today it is used as the reusable local project base',
|
|
60
|
-
'- later it can hold prompts, profiles, rules, or local plugins',
|
|
61
|
-
'- it should not depend only on global user config'
|
|
62
|
-
],
|
|
63
|
-
opencode: [
|
|
64
|
-
'# OpenCode Local Config',
|
|
65
|
-
'',
|
|
66
|
-
'This folder reserves project-local configuration for OpenCode.',
|
|
67
|
-
'',
|
|
68
|
-
'- today it is used as the reusable local project base',
|
|
69
|
-
'- later it can hold rules, prompts, or specific conventions',
|
|
70
|
-
'- it should not depend only on global user config'
|
|
71
|
-
],
|
|
72
|
-
done: 'Local agent configuration created or verified.'
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const L = CONTENT[LANGUAGE];
|
|
76
|
-
|
|
77
|
-
const files = [
|
|
78
|
-
[
|
|
79
|
-
'.claude/README.md',
|
|
80
|
-
L.claude.join('\n')
|
|
81
|
-
],
|
|
82
|
-
[
|
|
83
|
-
'.codex/README.md',
|
|
84
|
-
L.codex.join('\n')
|
|
85
|
-
],
|
|
86
|
-
[
|
|
87
|
-
'.opencode/README.md',
|
|
88
|
-
L.opencode.join('\n')
|
|
89
|
-
]
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
for (const [relativePath, content] of files) {
|
|
93
|
-
const absolutePath = path.join(ROOT, relativePath);
|
|
94
|
-
fs.mkdirSync(path.dirname(absolutePath), {recursive: true});
|
|
95
|
-
if (!fs.existsSync(absolutePath)) {
|
|
96
|
-
fs.writeFileSync(absolutePath, `${content}\n`, 'utf8');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
console.log(L.done);
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
const ROOT = process.cwd();
|
|
7
|
-
const CHANGE_NAME = process.argv[2];
|
|
8
|
-
const CONFIG_FILE = path.join(ROOT, 'orchestrator.config.json');
|
|
9
|
-
const CONFIG = fs.existsSync(CONFIG_FILE)
|
|
10
|
-
? JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'))
|
|
11
|
-
: {};
|
|
12
|
-
const LANGUAGE = CONFIG.workspaceLanguage === 'en' ? 'en' : 'es';
|
|
13
|
-
const TEXT = {
|
|
14
|
-
es: {
|
|
15
|
-
usage: 'Uso: npm run openspec:new -- <change-name>',
|
|
16
|
-
readmeIntro: 'Este change fue generado desde las plantillas locales de `openspec/`.',
|
|
17
|
-
created: base => `OpenSpec change creado en ${base}`
|
|
18
|
-
},
|
|
19
|
-
en: {
|
|
20
|
-
usage: 'Usage: npm run openspec:new -- <change-name>',
|
|
21
|
-
readmeIntro: 'This change was generated from the local `openspec/` templates.',
|
|
22
|
-
created: base => `OpenSpec change created at ${base}`
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
const L = TEXT[LANGUAGE];
|
|
26
|
-
|
|
27
|
-
if (!CHANGE_NAME) {
|
|
28
|
-
console.error(L.usage);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const base = path.join(ROOT, 'openspec', 'changes', CHANGE_NAME);
|
|
33
|
-
const templates = path.join(ROOT, 'openspec', 'templates');
|
|
34
|
-
|
|
35
|
-
const files = [
|
|
36
|
-
['proposal.md', 'proposal.md'],
|
|
37
|
-
['design.md', 'design.md'],
|
|
38
|
-
['tasks.md', 'tasks.md'],
|
|
39
|
-
['verify-report.md', 'verify-report.md'],
|
|
40
|
-
['archive-report.md', 'archive-report.md'],
|
|
41
|
-
['.openspec.yaml', 'change-metadata.yaml']
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
fs.mkdirSync(base, {recursive: true});
|
|
45
|
-
const specsDir = path.join(base, 'specs');
|
|
46
|
-
fs.mkdirSync(specsDir, {recursive: true});
|
|
47
|
-
|
|
48
|
-
for (const [targetName, templateName] of files) {
|
|
49
|
-
const target = path.join(base, targetName);
|
|
50
|
-
if (fs.existsSync(target)) continue;
|
|
51
|
-
const template = fs.readFileSync(path.join(templates, templateName), 'utf8');
|
|
52
|
-
const content = template.replaceAll('<change-name>', CHANGE_NAME);
|
|
53
|
-
fs.writeFileSync(target, content.endsWith('\n') ? content : `${content}\n`, 'utf8');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const specTemplate = fs.readFileSync(path.join(templates, 'spec.md'), 'utf8');
|
|
57
|
-
const specTarget = path.join(specsDir, 'spec.md');
|
|
58
|
-
if (!fs.existsSync(specTarget)) {
|
|
59
|
-
const specContent = specTemplate.replaceAll('<change-name>', CHANGE_NAME);
|
|
60
|
-
fs.writeFileSync(specTarget, specContent.endsWith('\n') ? specContent : `${specContent}\n`, 'utf8');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const readme = [
|
|
64
|
-
`# ${CHANGE_NAME}`,
|
|
65
|
-
'',
|
|
66
|
-
L.readmeIntro,
|
|
67
|
-
'',
|
|
68
|
-
'## Files',
|
|
69
|
-
'',
|
|
70
|
-
'- `proposal.md`',
|
|
71
|
-
'- `design.md`',
|
|
72
|
-
'- `tasks.md`',
|
|
73
|
-
'- `verify-report.md`',
|
|
74
|
-
'- `archive-report.md`',
|
|
75
|
-
'- `.openspec.yaml`',
|
|
76
|
-
'- `specs/spec.md`'
|
|
77
|
-
].join('\n');
|
|
78
|
-
|
|
79
|
-
const changeReadme = path.join(base, 'README.md');
|
|
80
|
-
if (!fs.existsSync(changeReadme)) {
|
|
81
|
-
fs.writeFileSync(changeReadme, `${readme}\n`, 'utf8');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log(L.created(base));
|