up-cc 0.16.0 → 2.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/README.md +87 -577
- package/package.json +5 -3
- package/up/CHANGELOG.md +110 -0
- package/up/agents/up-arquiteto.md +95 -39
- package/up/agents/up-auditor.md +218 -0
- package/up/agents/up-executor.md +94 -31
- package/up/agents/up-mapeador-codigo.md +63 -10
- package/up/agents/up-pesquisador.md +278 -0
- package/up/agents/up-revisor.md +249 -0
- package/up/agents/up-sintetizador.md +156 -179
- package/up/agents/up-tester.md +280 -0
- package/up/agents/up-verificador.md +95 -11
- package/up/bin/install.js +190 -21
- package/up/bin/lib/core.cjs +17 -43
- package/up/bin/lib/github.cjs +495 -0
- package/up/bin/lib/multica.cjs +424 -0
- package/up/bin/up-tools.cjs +167 -46
- package/up/commands/auditar.md +66 -0
- package/up/commands/build.md +54 -43
- package/up/commands/depurar.md +1 -1
- package/up/commands/plan.md +52 -38
- package/up/commands/rapido.md +15 -9
- package/up/commands/testar.md +81 -122
- package/up/commands/up.md +106 -0
- package/up/hooks/up-session-start.js +107 -0
- package/up/references/engineering-principles.md +1 -1
- package/up/references/governance-rules.md +5 -5
- package/up/references/production-requirements.md +1 -1
- package/up/references/severity-levels.md +2 -2
- package/up/references/tdd-evidence-types.md +81 -0
- package/up/skills/up-brainstorm/SKILL.md +39 -0
- package/up/skills/up-tdd/SKILL.md +39 -0
- package/up/skills/up-verificar-antes-de-concluir/SKILL.md +49 -0
- package/up/skills/usando-up/SKILL.md +26 -0
- package/up/templates/audit-plan.md +3 -3
- package/up/templates/audit-report.md +2 -2
- package/up/templates/design-tokens.md +2 -2
- package/up/workflows/auditar.md +255 -0
- package/up/workflows/build.md +600 -386
- package/up/workflows/dcrv.md +183 -99
- package/up/workflows/governance.md +112 -220
- package/up/workflows/plan.md +169 -399
- package/up/workflows/rapido.md +7 -1
- package/up/workflows/up.md +447 -0
- package/up/agents/up-analista-codigo.md +0 -446
- package/up/agents/up-api-tester.md +0 -405
- package/up/agents/up-architecture-supervisor.md +0 -126
- package/up/agents/up-audit-supervisor.md +0 -83
- package/up/agents/up-auditor-modernidade.md +0 -378
- package/up/agents/up-auditor-performance.md +0 -426
- package/up/agents/up-auditor-ux.md +0 -396
- package/up/agents/up-backend-specialist.md +0 -175
- package/up/agents/up-blind-validator.md +0 -259
- package/up/agents/up-chief-architect.md +0 -184
- package/up/agents/up-chief-engineer.md +0 -202
- package/up/agents/up-chief-operations.md +0 -123
- package/up/agents/up-chief-product.md +0 -103
- package/up/agents/up-chief-quality.md +0 -211
- package/up/agents/up-clone-crawler.md +0 -234
- package/up/agents/up-clone-design-extractor.md +0 -227
- package/up/agents/up-clone-feature-mapper.md +0 -225
- package/up/agents/up-clone-prd-writer.md +0 -169
- package/up/agents/up-clone-verifier.md +0 -227
- package/up/agents/up-code-reviewer.md +0 -229
- package/up/agents/up-consolidador-ideias.md +0 -493
- package/up/agents/up-database-specialist.md +0 -169
- package/up/agents/up-delivery-auditor.md +0 -247
- package/up/agents/up-devops-agent.md +0 -203
- package/up/agents/up-execution-supervisor.md +0 -315
- package/up/agents/up-exhaustive-tester.md +0 -348
- package/up/agents/up-frontend-specialist.md +0 -152
- package/up/agents/up-operations-supervisor.md +0 -94
- package/up/agents/up-pesquisador-mercado.md +0 -350
- package/up/agents/up-pesquisador-projeto.md +0 -358
- package/up/agents/up-planning-auditor.md +0 -284
- package/up/agents/up-planning-supervisor.md +0 -260
- package/up/agents/up-product-analyst.md +0 -192
- package/up/agents/up-product-supervisor.md +0 -83
- package/up/agents/up-project-ceo.md +0 -352
- package/up/agents/up-qa-agent.md +0 -171
- package/up/agents/up-quality-supervisor.md +0 -178
- package/up/agents/up-requirements-validator.md +0 -230
- package/up/agents/up-security-reviewer.md +0 -137
- package/up/agents/up-sintetizador-melhorias.md +0 -407
- package/up/agents/up-system-designer.md +0 -332
- package/up/agents/up-technical-writer.md +0 -188
- package/up/agents/up-verification-supervisor.md +0 -111
- package/up/agents/up-visual-critic.md +0 -358
- package/up/commands/adicionar-fase.md +0 -47
- package/up/commands/adicionar-testes.md +0 -145
- package/up/commands/ajuda.md +0 -176
- package/up/commands/atualizar.md +0 -103
- package/up/commands/clone-builder.md +0 -67
- package/up/commands/configurar.md +0 -219
- package/up/commands/custos.md +0 -67
- package/up/commands/dashboard.md +0 -48
- package/up/commands/discutir-fase.md +0 -35
- package/up/commands/executar-fase.md +0 -40
- package/up/commands/ideias.md +0 -49
- package/up/commands/iniciar.md +0 -31
- package/up/commands/mapear-codigo.md +0 -63
- package/up/commands/melhorias.md +0 -45
- package/up/commands/mobile-first.md +0 -71
- package/up/commands/modo-builder.md +0 -186
- package/up/commands/novo-projeto.md +0 -40
- package/up/commands/onboard.md +0 -69
- package/up/commands/pausar.md +0 -33
- package/up/commands/planejar-fase.md +0 -45
- package/up/commands/progresso.md +0 -33
- package/up/commands/remover-fase.md +0 -34
- package/up/commands/resetar.md +0 -27
- package/up/commands/retomar.md +0 -35
- package/up/commands/saude.md +0 -103
- package/up/commands/ux-tester.md +0 -63
- package/up/commands/verificar-trabalho.md +0 -35
- package/up/workflows/adicionar-fase.md +0 -112
- package/up/workflows/builder-e2e.md +0 -501
- package/up/workflows/builder.md +0 -3419
- package/up/workflows/ceo-intake.md +0 -305
- package/up/workflows/ceo-updates.md +0 -183
- package/up/workflows/clone-builder.md +0 -320
- package/up/workflows/discutir-fase.md +0 -336
- package/up/workflows/executar-fase.md +0 -358
- package/up/workflows/executar-plano.md +0 -659
- package/up/workflows/ideias.md +0 -381
- package/up/workflows/iniciar.md +0 -235
- package/up/workflows/melhorias.md +0 -409
- package/up/workflows/mobile-first.md +0 -692
- package/up/workflows/novo-projeto.md +0 -778
- package/up/workflows/planejar-fase.md +0 -293
- package/up/workflows/progresso.md +0 -226
- package/up/workflows/retomar.md +0 -231
- package/up/workflows/ux-tester.md +0 -526
- package/up/workflows/verificar-trabalho.md +0 -308
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* multica.cjs — Espelho OPT-IN do board Multica para o UP (Fase 5).
|
|
3
|
+
*
|
|
4
|
+
* Multica = espelho de board, NAO orquestrador, NAO stream ao vivo. So roda
|
|
5
|
+
* quando o build esta com --board ligado. Status batched no fim da onda/fase.
|
|
6
|
+
*
|
|
7
|
+
* Construido sobre child_process (CLI `multica`, Go-based, instalado global na
|
|
8
|
+
* VPS Dev). FAIL-OPEN em TUDO: se o `multica` faltar/nao autenticar/der erro,
|
|
9
|
+
* a funcao avisa via { ok:false, warning } e segue — NUNCA crasha o build.
|
|
10
|
+
*
|
|
11
|
+
* Deteccao de ambiente via `uname -s`:
|
|
12
|
+
* - Darwin (Mac do Jonathan): prefixa `ssh server-ecoup 'multica ...'`
|
|
13
|
+
* (os binarios `multica` e o daemon vivem na VPS Dev, nao no Mac).
|
|
14
|
+
* - Linux / VPS Dev: roda `multica ...` direto.
|
|
15
|
+
*
|
|
16
|
+
* Todas as funcoes aceitam { dryRun }: quando true, NAO executam nada —
|
|
17
|
+
* retornam { dryRun:true, command:"<comando que rodaria>" }. Isso garante
|
|
18
|
+
* que nenhum project/issue de teste seja criado no board de PRODUCAO.
|
|
19
|
+
*
|
|
20
|
+
* Mapeamento de identidade (metadata KV por issue, reconciliacao idempotente):
|
|
21
|
+
* up_project=<repo>, up_phase=N, gh_issue=<n>, branch=<up/fase-NN>, pr=<n>
|
|
22
|
+
*
|
|
23
|
+
* Status UP -> Multica (statuses validos do schema:
|
|
24
|
+
* backlog/todo/in_progress/in_review/done/blocked/cancelled):
|
|
25
|
+
* in_progress->in_progress, in_review->in_review, done->done,
|
|
26
|
+
* blocked->blocked, todo->todo.
|
|
27
|
+
*
|
|
28
|
+
* Exporta: ensureProject, ensurePhaseIssue, syncStatus, boardUrl.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const { execFileSync } = require('child_process');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
|
|
34
|
+
// =====================================================================
|
|
35
|
+
// Deteccao de ambiente (uname) + montagem de comando
|
|
36
|
+
// =====================================================================
|
|
37
|
+
|
|
38
|
+
/** `uname -s`. Em erro, assume Linux (VPS). */
|
|
39
|
+
function unameSync() {
|
|
40
|
+
try {
|
|
41
|
+
return execFileSync('uname', ['-s'], { encoding: 'utf-8' }).trim();
|
|
42
|
+
} catch {
|
|
43
|
+
return 'Linux';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** true quando rodando no Mac do Jonathan (precisa de ssh server-ecoup). */
|
|
48
|
+
function isMac() {
|
|
49
|
+
return unameSync() === 'Darwin';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Status UP -> Multica (1:1 nos casos que importam; os demais sao passthrough
|
|
53
|
+
// quando ja sao statuses validos do schema).
|
|
54
|
+
const STATUS_MAP = {
|
|
55
|
+
in_progress: 'in_progress',
|
|
56
|
+
in_review: 'in_review',
|
|
57
|
+
done: 'done',
|
|
58
|
+
blocked: 'blocked',
|
|
59
|
+
todo: 'todo',
|
|
60
|
+
backlog: 'backlog',
|
|
61
|
+
cancelled: 'cancelled',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const VALID_STATUSES = new Set(Object.keys(STATUS_MAP));
|
|
65
|
+
|
|
66
|
+
function mapStatus(status) {
|
|
67
|
+
return STATUS_MAP[status] || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Escapa um argumento para exibicao/uso dentro de uma string de shell single-quoted.
|
|
72
|
+
* Usado tanto para montar o comando ssh quanto para o preview do dry-run.
|
|
73
|
+
*/
|
|
74
|
+
function shellQuote(arg) {
|
|
75
|
+
const s = String(arg);
|
|
76
|
+
if (/^[a-zA-Z0-9._\-/=:@,]+$/.test(s)) return s;
|
|
77
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Monta a representacao do comando que seria executado (para dry-run e ssh).
|
|
82
|
+
* Em Mac: `ssh server-ecoup 'multica <args>'` (multica + args viram um unico
|
|
83
|
+
* payload single-quoted pro shell remoto). Em Linux: `multica <args>`.
|
|
84
|
+
*/
|
|
85
|
+
function buildCommandString(args) {
|
|
86
|
+
const inner = ['multica', ...args].map(shellQuote).join(' ');
|
|
87
|
+
if (isMac()) {
|
|
88
|
+
return 'ssh server-ecoup ' + shellQuote(inner);
|
|
89
|
+
}
|
|
90
|
+
return inner;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Executa `multica <args>` (ou via ssh em Mac). FAIL-OPEN: nunca lanca.
|
|
95
|
+
* Retorna { exitCode, stdout, stderr }.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} cwd diretorio de execucao (so afeta o processo local; em
|
|
98
|
+
* Mac o `cwd` real do multica e o da VPS, mas o CLI usa
|
|
99
|
+
* o workspace default do profile, entao isso e benigno).
|
|
100
|
+
*/
|
|
101
|
+
function runMultica(cwd, args) {
|
|
102
|
+
try {
|
|
103
|
+
let bin, fullArgs;
|
|
104
|
+
if (isMac()) {
|
|
105
|
+
// ssh server-ecoup 'multica <args...>' — empacota tudo num payload remoto.
|
|
106
|
+
const remote = ['multica', ...args].map(shellQuote).join(' ');
|
|
107
|
+
bin = 'ssh';
|
|
108
|
+
fullArgs = ['server-ecoup', remote];
|
|
109
|
+
} else {
|
|
110
|
+
bin = 'multica';
|
|
111
|
+
fullArgs = args;
|
|
112
|
+
}
|
|
113
|
+
const stdout = execFileSync(bin, fullArgs, {
|
|
114
|
+
cwd,
|
|
115
|
+
encoding: 'utf-8',
|
|
116
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
117
|
+
timeout: 30000,
|
|
118
|
+
});
|
|
119
|
+
return { exitCode: 0, stdout: (stdout || '').trim(), stderr: '' };
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return {
|
|
122
|
+
exitCode: err.status ?? 1,
|
|
123
|
+
stdout: (err.stdout ?? '').toString().trim(),
|
|
124
|
+
stderr: (err.stderr ?? err.message ?? '').toString().trim(),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Parse JSON best-effort. Em erro, null. */
|
|
130
|
+
function tryParseJson(text) {
|
|
131
|
+
if (!text) return null;
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(text);
|
|
134
|
+
} catch {
|
|
135
|
+
// alguns comandos imprimem linhas extras antes/depois do JSON
|
|
136
|
+
const start = text.indexOf('{');
|
|
137
|
+
const end = text.lastIndexOf('}');
|
|
138
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
139
|
+
try {
|
|
140
|
+
return JSON.parse(text.slice(start, end + 1));
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Nome do repo (= up_project). Usa basename do toplevel/cwd. */
|
|
150
|
+
function repoName(cwd) {
|
|
151
|
+
return path.basename(cwd);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// =====================================================================
|
|
155
|
+
// ensureProject — garante 1 project Multica por repo (idempotente)
|
|
156
|
+
// =====================================================================
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* ensureProject({ cwd, name, dryRun })
|
|
160
|
+
* - Procura um project existente pelo titulo (== name). Se achar, retorna o id.
|
|
161
|
+
* - Se nao achar, cria via `multica project create --title <name> --output json`.
|
|
162
|
+
* - dryRun: retorna o comando de create que rodaria, sem executar.
|
|
163
|
+
* FAIL-OPEN: erro -> { ok:false, warning }.
|
|
164
|
+
*
|
|
165
|
+
* Retorno: { ok, project_id?, identifier?, created?, dryRun?, command?, warning? }
|
|
166
|
+
*/
|
|
167
|
+
function ensureProject({ cwd, name, dryRun = false }) {
|
|
168
|
+
const title = name || repoName(cwd);
|
|
169
|
+
const createArgs = ['project', 'create', '--title', title, '--output', 'json'];
|
|
170
|
+
|
|
171
|
+
if (dryRun) {
|
|
172
|
+
return { ok: true, dryRun: true, command: buildCommandString(createArgs), title };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 1) Procura project existente pelo titulo (idempotencia).
|
|
176
|
+
const listRes = runMultica(cwd, ['project', 'list', '--output', 'json']);
|
|
177
|
+
if (listRes.exitCode === 0) {
|
|
178
|
+
const parsed = tryParseJson(listRes.stdout);
|
|
179
|
+
const projects = Array.isArray(parsed) ? parsed : (parsed && Array.isArray(parsed.projects) ? parsed.projects : []);
|
|
180
|
+
const found = projects.find(p => p && (p.title === title || p.name === title));
|
|
181
|
+
if (found && found.id) {
|
|
182
|
+
return { ok: true, project_id: found.id, identifier: found.identifier || null, created: false };
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
warning: `multica project list falhou: ${listRes.stderr || listRes.stdout || 'erro desconhecido'} (board ignorado, build segue)`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 2) Cria.
|
|
192
|
+
const createRes = runMultica(cwd, createArgs);
|
|
193
|
+
if (createRes.exitCode !== 0) {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
warning: `multica project create falhou: ${createRes.stderr || createRes.stdout || 'erro desconhecido'} (board ignorado, build segue)`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const created = tryParseJson(createRes.stdout);
|
|
200
|
+
if (!created || !created.id) {
|
|
201
|
+
return { ok: false, warning: 'multica project create nao retornou id (board ignorado, build segue)' };
|
|
202
|
+
}
|
|
203
|
+
return { ok: true, project_id: created.id, identifier: created.identifier || null, created: true };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// =====================================================================
|
|
207
|
+
// ensurePhaseIssue — garante 1 issue por fase (idempotente via metadata)
|
|
208
|
+
// =====================================================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* ensurePhaseIssue({ cwd, phase, title, parent, project, dryRun })
|
|
212
|
+
* - Reconciliacao idempotente: procura issue ja existente filtrando por
|
|
213
|
+
* metadata up_project=<repo> AND up_phase=<phase>. Se achar, retorna o id
|
|
214
|
+
* (nao duplica).
|
|
215
|
+
* - Se nao achar, cria a issue (--status backlog, sem assignee — respeita o
|
|
216
|
+
* "empilha por padrao"/sem disparo automatico) com --parent quando dado, e
|
|
217
|
+
* grava metadata up_project + up_phase.
|
|
218
|
+
* - dryRun: retorna os comandos (create + metadata) que rodariam, sem executar.
|
|
219
|
+
* FAIL-OPEN.
|
|
220
|
+
*
|
|
221
|
+
* Retorno: { ok, issue_id?, identifier?, created?, dryRun?, command?, warning? }
|
|
222
|
+
*/
|
|
223
|
+
function ensurePhaseIssue({ cwd, phase, title, parent, project, dryRun = false }) {
|
|
224
|
+
const proj = repoName(cwd);
|
|
225
|
+
const issueTitle = title || `Fase ${phase}`;
|
|
226
|
+
|
|
227
|
+
const createArgs = ['issue', 'create', '--title', issueTitle, '--status', 'backlog', '--output', 'json'];
|
|
228
|
+
if (project) createArgs.push('--project', String(project));
|
|
229
|
+
if (parent) createArgs.push('--parent', String(parent));
|
|
230
|
+
|
|
231
|
+
// comandos de metadata (rodados apos create, com o issue id real)
|
|
232
|
+
const metaPreview = [
|
|
233
|
+
['issue', 'metadata', 'set', '<issue-id>', '--key', 'up_project', '--value', proj, '--type', 'string'],
|
|
234
|
+
['issue', 'metadata', 'set', '<issue-id>', '--key', 'up_phase', '--value', String(phase), '--type', 'number'],
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
if (dryRun) {
|
|
238
|
+
return {
|
|
239
|
+
ok: true,
|
|
240
|
+
dryRun: true,
|
|
241
|
+
command: buildCommandString(createArgs),
|
|
242
|
+
metadata_commands: metaPreview.map(buildCommandString),
|
|
243
|
+
title: issueTitle,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 1) Reconciliacao: issue ja existe pra (up_project, up_phase)?
|
|
248
|
+
const existing = findPhaseIssue({ cwd, phase, project: proj });
|
|
249
|
+
if (existing.ok && existing.issue_id) {
|
|
250
|
+
return { ok: true, issue_id: existing.issue_id, identifier: existing.identifier || null, created: false };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 2) Cria a issue.
|
|
254
|
+
const createRes = runMultica(cwd, createArgs);
|
|
255
|
+
if (createRes.exitCode !== 0) {
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
warning: `multica issue create falhou: ${createRes.stderr || createRes.stdout || 'erro desconhecido'} (board ignorado, build segue)`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const created = tryParseJson(createRes.stdout);
|
|
262
|
+
if (!created || !created.id) {
|
|
263
|
+
return { ok: false, warning: 'multica issue create nao retornou id (board ignorado, build segue)' };
|
|
264
|
+
}
|
|
265
|
+
const issueId = created.id;
|
|
266
|
+
|
|
267
|
+
// 3) Grava metadata de identidade (best-effort: warning agrega, nao quebra).
|
|
268
|
+
const warnings = [];
|
|
269
|
+
const m1 = runMultica(cwd, ['issue', 'metadata', 'set', issueId, '--key', 'up_project', '--value', proj, '--type', 'string']);
|
|
270
|
+
if (m1.exitCode !== 0) warnings.push('metadata up_project: ' + (m1.stderr || m1.stdout));
|
|
271
|
+
const m2 = runMultica(cwd, ['issue', 'metadata', 'set', issueId, '--key', 'up_phase', '--value', String(phase), '--type', 'number']);
|
|
272
|
+
if (m2.exitCode !== 0) warnings.push('metadata up_phase: ' + (m2.stderr || m2.stdout));
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
ok: true,
|
|
276
|
+
issue_id: issueId,
|
|
277
|
+
identifier: created.identifier || null,
|
|
278
|
+
created: true,
|
|
279
|
+
warning: warnings.length ? warnings.join('; ') : undefined,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* findPhaseIssue({ cwd, phase, project }) — reconciliacao idempotente.
|
|
285
|
+
* `multica issue list --metadata up_project=<repo> --metadata up_phase=N --output json`.
|
|
286
|
+
* Retorna { ok, issue_id?, identifier? } (ok=false em erro — fail-open no caller).
|
|
287
|
+
*/
|
|
288
|
+
function findPhaseIssue({ cwd, phase, project }) {
|
|
289
|
+
const proj = project || repoName(cwd);
|
|
290
|
+
const args = [
|
|
291
|
+
'issue', 'list',
|
|
292
|
+
'--metadata', `up_project=${proj}`,
|
|
293
|
+
'--metadata', `up_phase=${phase}`,
|
|
294
|
+
'--output', 'json',
|
|
295
|
+
];
|
|
296
|
+
const res = runMultica(cwd, args);
|
|
297
|
+
if (res.exitCode !== 0) {
|
|
298
|
+
return { ok: false, warning: res.stderr || res.stdout || 'issue list falhou' };
|
|
299
|
+
}
|
|
300
|
+
const parsed = tryParseJson(res.stdout);
|
|
301
|
+
const issues = Array.isArray(parsed) ? parsed : (parsed && Array.isArray(parsed.issues) ? parsed.issues : []);
|
|
302
|
+
if (issues.length && issues[0] && issues[0].id) {
|
|
303
|
+
return { ok: true, issue_id: issues[0].id, identifier: issues[0].identifier || null };
|
|
304
|
+
}
|
|
305
|
+
return { ok: true, issue_id: null };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// =====================================================================
|
|
309
|
+
// syncStatus — espelha status UP -> Multica (batched) + metadata
|
|
310
|
+
// =====================================================================
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* syncStatus({ cwd, phase, status, metadata, dryRun, issueId })
|
|
314
|
+
* - Mapeia o status UP -> Multica e aplica via `multica issue status <id> <status>`.
|
|
315
|
+
* - Resolve o issue id por reconciliacao (metadata up_project + up_phase) se
|
|
316
|
+
* nao vier explicitamente (issueId).
|
|
317
|
+
* - metadata (objeto, ex { gh_issue, branch, pr }) -> `issue metadata set` por chave.
|
|
318
|
+
* - dryRun: imprime os comandos que rodariam (status + metadata), sem executar
|
|
319
|
+
* e sem precisar resolver o id real (usa placeholder <issue-id>).
|
|
320
|
+
* FAIL-OPEN: status invalido ou issue inexistente -> { ok:false, warning }.
|
|
321
|
+
*
|
|
322
|
+
* Retorno: { ok, issue_id?, status?, dryRun?, command?, metadata_commands?, warning? }
|
|
323
|
+
*/
|
|
324
|
+
function syncStatus({ cwd, phase, status, metadata = {}, dryRun = false, issueId = null }) {
|
|
325
|
+
const mapped = mapStatus(status);
|
|
326
|
+
const metaEntries = Object.entries(metadata || {}).filter(([, v]) => v !== null && v !== undefined && v !== '');
|
|
327
|
+
|
|
328
|
+
if (!mapped) {
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
warning: `status UP "${status}" nao mapeia para Multica (validos: ${[...VALID_STATUSES].join('/')}); board ignorado, build segue`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// dry-run: monta comandos com placeholder, sem tocar no board.
|
|
336
|
+
if (dryRun) {
|
|
337
|
+
const idForPreview = issueId || '<issue-id>';
|
|
338
|
+
const statusCmd = buildCommandString(['issue', 'status', idForPreview, mapped, '--output', 'json']);
|
|
339
|
+
const metaCmds = metaEntries.map(([k, v]) =>
|
|
340
|
+
buildCommandString(['issue', 'metadata', 'set', idForPreview, '--key', k, '--value', String(v)])
|
|
341
|
+
);
|
|
342
|
+
const lookupCmd = issueId
|
|
343
|
+
? null
|
|
344
|
+
: buildCommandString([
|
|
345
|
+
'issue', 'list',
|
|
346
|
+
'--metadata', `up_project=${repoName(cwd)}`,
|
|
347
|
+
'--metadata', `up_phase=${phase}`,
|
|
348
|
+
'--output', 'json',
|
|
349
|
+
]);
|
|
350
|
+
return {
|
|
351
|
+
ok: true,
|
|
352
|
+
dryRun: true,
|
|
353
|
+
phase: phase ?? null,
|
|
354
|
+
status: mapped,
|
|
355
|
+
lookup_command: lookupCmd,
|
|
356
|
+
command: statusCmd,
|
|
357
|
+
metadata_commands: metaCmds,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// resolve issue id (reconciliacao por metadata) se nao foi passado.
|
|
362
|
+
let id = issueId;
|
|
363
|
+
if (!id) {
|
|
364
|
+
const found = findPhaseIssue({ cwd, phase });
|
|
365
|
+
if (!found.ok) {
|
|
366
|
+
return { ok: false, warning: `multica issue list falhou: ${found.warning} (board ignorado, build segue)` };
|
|
367
|
+
}
|
|
368
|
+
id = found.issue_id;
|
|
369
|
+
if (!id) {
|
|
370
|
+
return { ok: false, warning: `nenhuma issue Multica para up_phase=${phase} (rode ensurePhaseIssue antes); board ignorado, build segue` };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const warnings = [];
|
|
375
|
+
|
|
376
|
+
// aplica status.
|
|
377
|
+
const statusRes = runMultica(cwd, ['issue', 'status', id, mapped, '--output', 'json']);
|
|
378
|
+
if (statusRes.exitCode !== 0) {
|
|
379
|
+
warnings.push('status: ' + (statusRes.stderr || statusRes.stdout));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// aplica metadata (gh_issue, branch, pr, ...).
|
|
383
|
+
for (const [k, v] of metaEntries) {
|
|
384
|
+
const mRes = runMultica(cwd, ['issue', 'metadata', 'set', id, '--key', k, '--value', String(v)]);
|
|
385
|
+
if (mRes.exitCode !== 0) warnings.push(`metadata ${k}: ` + (mRes.stderr || mRes.stdout));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (warnings.length) {
|
|
389
|
+
return { ok: false, issue_id: id, status: mapped, warning: warnings.join('; ') + ' (board parcial, build segue)' };
|
|
390
|
+
}
|
|
391
|
+
return { ok: true, issue_id: id, status: mapped };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// =====================================================================
|
|
395
|
+
// boardUrl — imprime a URL do board (workspace/project)
|
|
396
|
+
// =====================================================================
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* boardUrl({ cwd, project }) — retorna a URL do board Multica.
|
|
400
|
+
* Best-effort: usa a base conhecida da instancia EcoUp self-hosted.
|
|
401
|
+
* NUNCA crasha. Nao depende de chamada de rede.
|
|
402
|
+
*
|
|
403
|
+
* Retorno: { ok, url, project? }
|
|
404
|
+
*/
|
|
405
|
+
function boardUrl({ cwd, project = null } = {}) {
|
|
406
|
+
const base = 'https://multica.ecoup.digital';
|
|
407
|
+
if (project) {
|
|
408
|
+
return { ok: true, url: `${base}/projects/${project}`, project };
|
|
409
|
+
}
|
|
410
|
+
return { ok: true, url: base };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = {
|
|
414
|
+
ensureProject,
|
|
415
|
+
ensurePhaseIssue,
|
|
416
|
+
syncStatus,
|
|
417
|
+
boardUrl,
|
|
418
|
+
// helpers expostos para reuso/teste
|
|
419
|
+
findPhaseIssue,
|
|
420
|
+
mapStatus,
|
|
421
|
+
isMac,
|
|
422
|
+
buildCommandString,
|
|
423
|
+
STATUS_MAP,
|
|
424
|
+
};
|
package/up/bin/up-tools.cjs
CHANGED
|
@@ -34,6 +34,9 @@ const {
|
|
|
34
34
|
pathExistsInternal, generateSlugInternal, toPosixPath,
|
|
35
35
|
} = require('./lib/core.cjs');
|
|
36
36
|
|
|
37
|
+
const github = require('./lib/github.cjs');
|
|
38
|
+
const multica = require('./lib/multica.cjs');
|
|
39
|
+
|
|
37
40
|
// --- Frontmatter helpers ---
|
|
38
41
|
|
|
39
42
|
function extractFrontmatter(content) {
|
|
@@ -337,6 +340,48 @@ function main() {
|
|
|
337
340
|
break;
|
|
338
341
|
}
|
|
339
342
|
|
|
343
|
+
// ==================== GITHUB (Fase 4: GitHub-native) ====================
|
|
344
|
+
case 'github': {
|
|
345
|
+
const sub = args[1];
|
|
346
|
+
const getFlag = (name) => {
|
|
347
|
+
const i = args.indexOf(name);
|
|
348
|
+
return i !== -1 ? args[i + 1] : null;
|
|
349
|
+
};
|
|
350
|
+
const hasFlag = (name) => args.indexOf(name) !== -1;
|
|
351
|
+
|
|
352
|
+
if (sub === 'start-phase') {
|
|
353
|
+
const phase = getFlag('--phase');
|
|
354
|
+
const slug = getFlag('--slug');
|
|
355
|
+
if (!phase) error('Usage: github start-phase --phase N --slug S [--solo]');
|
|
356
|
+
const result = github.startPhase({
|
|
357
|
+
cwd,
|
|
358
|
+
phase,
|
|
359
|
+
slug: slug || '',
|
|
360
|
+
solo: hasFlag('--solo'),
|
|
361
|
+
});
|
|
362
|
+
output(result, raw, JSON.stringify(result));
|
|
363
|
+
} else if (sub === 'finish-phase') {
|
|
364
|
+
const phase = getFlag('--phase');
|
|
365
|
+
const mode = getFlag('--mode') || 'menu';
|
|
366
|
+
const strategy = getFlag('--strategy');
|
|
367
|
+
if (!phase) error('Usage: github finish-phase --phase N --mode menu|auto|solo [--strategy squash|merge|rebase]');
|
|
368
|
+
const result = github.finishPhase({ cwd, phase, mode, strategy });
|
|
369
|
+
output(result, raw, JSON.stringify(result));
|
|
370
|
+
} else if (sub === 'status') {
|
|
371
|
+
const result = github.status({ cwd });
|
|
372
|
+
output(result, raw, JSON.stringify(result));
|
|
373
|
+
} else {
|
|
374
|
+
error('Unknown github subcommand. Available: start-phase, finish-phase, status');
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ==================== MULTICA (Fase 5: board OPT-IN) ====================
|
|
380
|
+
case 'multica': {
|
|
381
|
+
cmdMultica(cwd, args.slice(1), raw);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
|
|
340
385
|
// ==================== COMMIT ====================
|
|
341
386
|
case 'commit': {
|
|
342
387
|
const filesIndex = args.indexOf('--files');
|
|
@@ -2820,60 +2865,26 @@ function cmdValidatePlan(cwd, args, raw) {
|
|
|
2820
2865
|
// =====================================================================
|
|
2821
2866
|
|
|
2822
2867
|
const SKILL_MANIFEST = {
|
|
2823
|
-
// Execution agents — fokus em principles + production reqs
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
'up-
|
|
2827
|
-
'up-database-specialist': ['engineering-principles-compressed', 'production-requirements-compressed'],
|
|
2828
|
-
'up-devops-agent': ['production-requirements-compressed'],
|
|
2829
|
-
'up-technical-writer': [],
|
|
2868
|
+
// Execution agents — fokus em principles + production reqs.
|
|
2869
|
+
// up-executor roteia frontend/backend/database por contexto (carrega
|
|
2870
|
+
// ui-brand + production-requirements sob demanda quando o dominio exige).
|
|
2871
|
+
'up-executor': ['engineering-principles-compressed', 'ui-brand', 'production-requirements-compressed'],
|
|
2830
2872
|
'up-depurador': ['engineering-principles-compressed'],
|
|
2831
2873
|
|
|
2832
2874
|
// Planning agents — fokus em arquitetura + reqs
|
|
2833
2875
|
'up-planejador': ['engineering-principles-compressed'],
|
|
2834
2876
|
'up-arquiteto': ['engineering-principles-compressed', 'production-requirements-compressed'],
|
|
2835
|
-
'up-product-analyst': [],
|
|
2836
|
-
'up-system-designer': ['engineering-principles-compressed', 'production-requirements-compressed'],
|
|
2837
2877
|
'up-roteirista': [],
|
|
2838
|
-
'up-pesquisador
|
|
2839
|
-
'up-sintetizador': [],
|
|
2878
|
+
'up-pesquisador': [],
|
|
2879
|
+
'up-sintetizador': ['production-requirements-compressed'],
|
|
2840
2880
|
'up-mapeador-codigo': [],
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
//
|
|
2844
|
-
'up-execution-supervisor': ['governance-rules-compressed', 'engineering-principles-compressed', 'rework-limits-compressed'],
|
|
2845
|
-
'up-verification-supervisor': ['governance-rules-compressed', 'rework-limits-compressed'],
|
|
2846
|
-
'up-planning-supervisor': ['governance-rules-compressed', 'rework-limits-compressed'],
|
|
2847
|
-
'up-quality-supervisor': ['governance-rules-compressed'],
|
|
2848
|
-
'up-audit-supervisor': ['governance-rules-compressed'],
|
|
2849
|
-
'up-product-supervisor': ['governance-rules-compressed'],
|
|
2850
|
-
'up-architecture-supervisor': ['governance-rules-compressed'],
|
|
2851
|
-
'up-operations-supervisor': ['governance-rules-compressed'],
|
|
2852
|
-
'up-chief-engineer': ['governance-rules-compressed', 'rework-limits-compressed'],
|
|
2853
|
-
'up-chief-architect': ['governance-rules-compressed'],
|
|
2854
|
-
'up-chief-quality': ['governance-rules-compressed'],
|
|
2855
|
-
'up-chief-operations': ['governance-rules-compressed'],
|
|
2856
|
-
'up-chief-product': ['governance-rules-compressed'],
|
|
2857
|
-
'up-project-ceo': ['governance-rules-compressed'],
|
|
2858
|
-
'up-delivery-auditor': ['governance-rules-compressed', 'production-requirements-compressed'],
|
|
2859
|
-
'up-planning-auditor': ['governance-rules-compressed'],
|
|
2860
|
-
|
|
2861
|
-
// Review & Testing — fokus em quality
|
|
2881
|
+
|
|
2882
|
+
// Review & Testing — fokus em quality.
|
|
2883
|
+
// up-tester funde visual + exhaustive + api (multi-pass via Playwright).
|
|
2862
2884
|
'up-verificador': ['production-requirements-compressed'],
|
|
2863
|
-
'up-
|
|
2864
|
-
'up-
|
|
2865
|
-
'up-
|
|
2866
|
-
'up-visual-critic': ['ui-brand'],
|
|
2867
|
-
'up-exhaustive-tester': [],
|
|
2868
|
-
'up-api-tester': [],
|
|
2869
|
-
'up-qa-agent': [],
|
|
2870
|
-
'up-auditor-ux': ['audit-ux'],
|
|
2871
|
-
'up-auditor-performance': ['audit-performance'],
|
|
2872
|
-
'up-auditor-modernidade': ['audit-modernidade'],
|
|
2873
|
-
'up-sintetizador-melhorias': [],
|
|
2874
|
-
'up-analista-codigo': ['engineering-principles-compressed'],
|
|
2875
|
-
'up-pesquisador-mercado': [],
|
|
2876
|
-
'up-consolidador-ideias': [],
|
|
2885
|
+
'up-revisor': ['engineering-principles-compressed', 'production-requirements-compressed'],
|
|
2886
|
+
'up-auditor': ['audit-ux', 'audit-performance', 'audit-modernidade'],
|
|
2887
|
+
'up-tester': ['ui-brand', 'production-requirements-compressed'],
|
|
2877
2888
|
};
|
|
2878
2889
|
|
|
2879
2890
|
/**
|
|
@@ -2912,6 +2923,116 @@ function cmdSkillManifest(args, raw) {
|
|
|
2912
2923
|
output({ agent, refs, paths, count: refs.length }, raw, paths.join('\n'));
|
|
2913
2924
|
}
|
|
2914
2925
|
|
|
2926
|
+
// =====================================================================
|
|
2927
|
+
// MULTICA COMMAND (Fase 5 — espelho de board OPT-IN)
|
|
2928
|
+
// =====================================================================
|
|
2929
|
+
|
|
2930
|
+
/**
|
|
2931
|
+
* Espelha o estado do UP no board Multica (so quando --board ligado no build).
|
|
2932
|
+
* Subverbos: init, sync, board. FAIL-OPEN em tudo (warning, nunca crasha).
|
|
2933
|
+
*
|
|
2934
|
+
* Usage:
|
|
2935
|
+
* up-tools.cjs multica init [--name <proj>] [--phase N --title <t>] [--dry-run]
|
|
2936
|
+
* up-tools.cjs multica sync --phase N --status <s> [--metadata k=v ...] [--dry-run]
|
|
2937
|
+
* up-tools.cjs multica board [--project <id>]
|
|
2938
|
+
*
|
|
2939
|
+
* Atualiza .plano/git-map.json com multica_issue por fase (init/sync).
|
|
2940
|
+
* Saida JSON.
|
|
2941
|
+
*/
|
|
2942
|
+
function cmdMultica(cwd, args, raw) {
|
|
2943
|
+
const sub = args[0];
|
|
2944
|
+
const getFlag = (name) => {
|
|
2945
|
+
const i = args.indexOf(name);
|
|
2946
|
+
return i !== -1 ? args[i + 1] : null;
|
|
2947
|
+
};
|
|
2948
|
+
const hasFlag = (name) => args.indexOf(name) !== -1;
|
|
2949
|
+
// --metadata k=v (repetivel)
|
|
2950
|
+
const collectMetadata = () => {
|
|
2951
|
+
const meta = {};
|
|
2952
|
+
for (let i = 0; i < args.length; i++) {
|
|
2953
|
+
if (args[i] === '--metadata' && args[i + 1]) {
|
|
2954
|
+
const kv = args[i + 1];
|
|
2955
|
+
const eq = kv.indexOf('=');
|
|
2956
|
+
if (eq > 0) meta[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
return meta;
|
|
2960
|
+
};
|
|
2961
|
+
|
|
2962
|
+
const dryRun = hasFlag('--dry-run');
|
|
2963
|
+
|
|
2964
|
+
if (sub === 'init') {
|
|
2965
|
+
// ensureProject (+ parent issue opcional por fase quando --phase dado)
|
|
2966
|
+
const name = getFlag('--name');
|
|
2967
|
+
const phase = getFlag('--phase');
|
|
2968
|
+
const title = getFlag('--title');
|
|
2969
|
+
|
|
2970
|
+
const proj = multica.ensureProject({ cwd, name, dryRun });
|
|
2971
|
+
const result = { subcommand: 'init', dry_run: dryRun, project: proj };
|
|
2972
|
+
|
|
2973
|
+
if (phase) {
|
|
2974
|
+
const projectId = proj && proj.ok && proj.project_id ? proj.project_id : null;
|
|
2975
|
+
const issue = multica.ensurePhaseIssue({
|
|
2976
|
+
cwd, phase, title: title || `Fase ${phase}`, project: projectId, dryRun,
|
|
2977
|
+
});
|
|
2978
|
+
result.issue = issue;
|
|
2979
|
+
if (!dryRun && issue && issue.ok && issue.issue_id) {
|
|
2980
|
+
persistMulticaIssue(cwd, phase, issue.issue_id);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
output(result, raw, JSON.stringify(result));
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
if (sub === 'sync') {
|
|
2989
|
+
const phase = getFlag('--phase');
|
|
2990
|
+
const status = getFlag('--status');
|
|
2991
|
+
if (!phase) error('Usage: multica sync --phase N --status <s> [--metadata k=v ...] [--dry-run]');
|
|
2992
|
+
if (!status) error('Usage: multica sync --phase N --status <s> [--metadata k=v ...] [--dry-run]');
|
|
2993
|
+
|
|
2994
|
+
const metadata = collectMetadata();
|
|
2995
|
+
const res = multica.syncStatus({ cwd, phase, status, metadata, dryRun });
|
|
2996
|
+
|
|
2997
|
+
// persiste id resolvido no git-map (so quando rodou de verdade)
|
|
2998
|
+
if (!dryRun && res && res.issue_id) {
|
|
2999
|
+
persistMulticaIssue(cwd, phase, res.issue_id);
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
const result = { subcommand: 'sync', dry_run: dryRun, phase, status, metadata, result: res };
|
|
3003
|
+
output(result, raw, JSON.stringify(result));
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
if (sub === 'board') {
|
|
3008
|
+
const project = getFlag('--project');
|
|
3009
|
+
const res = multica.boardUrl({ cwd, project });
|
|
3010
|
+
const result = { subcommand: 'board', ...res };
|
|
3011
|
+
output(result, raw, res.url);
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
error('Unknown multica subcommand. Available: init, sync, board');
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
/**
|
|
3019
|
+
* Grava multica_issue por fase no .plano/git-map.json (ao lado de issue/pr do
|
|
3020
|
+
* GitHub). Best-effort: nunca quebra o fluxo.
|
|
3021
|
+
*/
|
|
3022
|
+
function persistMulticaIssue(cwd, phase, multicaIssueId) {
|
|
3023
|
+
try {
|
|
3024
|
+
const map = github.readGitMap(cwd);
|
|
3025
|
+
const key = String(phase).replace(/^0+(\d)/, '$1').replace(/^(\d+)$/, '$1');
|
|
3026
|
+
const normKey = require('./lib/core.cjs').normalizePhaseName(phase).replace(/^0+(\d)/, '$1');
|
|
3027
|
+
const k = normKey || key;
|
|
3028
|
+
map.phases = map.phases || {};
|
|
3029
|
+
map.phases[k] = { ...(map.phases[k] || {}), multica_issue: multicaIssueId };
|
|
3030
|
+
github.writeGitMap(cwd, map);
|
|
3031
|
+
} catch {
|
|
3032
|
+
// fail-open
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
|
|
2915
3036
|
// =====================================================================
|
|
2916
3037
|
// CLASSIFY-TASK COMMAND (Wave 5 — complexity-based routing)
|
|
2917
3038
|
// =====================================================================
|