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.
Files changed (134) hide show
  1. package/README.md +87 -577
  2. package/package.json +5 -3
  3. package/up/CHANGELOG.md +110 -0
  4. package/up/agents/up-arquiteto.md +95 -39
  5. package/up/agents/up-auditor.md +218 -0
  6. package/up/agents/up-executor.md +94 -31
  7. package/up/agents/up-mapeador-codigo.md +63 -10
  8. package/up/agents/up-pesquisador.md +278 -0
  9. package/up/agents/up-revisor.md +249 -0
  10. package/up/agents/up-sintetizador.md +156 -179
  11. package/up/agents/up-tester.md +280 -0
  12. package/up/agents/up-verificador.md +95 -11
  13. package/up/bin/install.js +190 -21
  14. package/up/bin/lib/core.cjs +17 -43
  15. package/up/bin/lib/github.cjs +495 -0
  16. package/up/bin/lib/multica.cjs +424 -0
  17. package/up/bin/up-tools.cjs +167 -46
  18. package/up/commands/auditar.md +66 -0
  19. package/up/commands/build.md +54 -43
  20. package/up/commands/depurar.md +1 -1
  21. package/up/commands/plan.md +52 -38
  22. package/up/commands/rapido.md +15 -9
  23. package/up/commands/testar.md +81 -122
  24. package/up/commands/up.md +106 -0
  25. package/up/hooks/up-session-start.js +107 -0
  26. package/up/references/engineering-principles.md +1 -1
  27. package/up/references/governance-rules.md +5 -5
  28. package/up/references/production-requirements.md +1 -1
  29. package/up/references/severity-levels.md +2 -2
  30. package/up/references/tdd-evidence-types.md +81 -0
  31. package/up/skills/up-brainstorm/SKILL.md +39 -0
  32. package/up/skills/up-tdd/SKILL.md +39 -0
  33. package/up/skills/up-verificar-antes-de-concluir/SKILL.md +49 -0
  34. package/up/skills/usando-up/SKILL.md +26 -0
  35. package/up/templates/audit-plan.md +3 -3
  36. package/up/templates/audit-report.md +2 -2
  37. package/up/templates/design-tokens.md +2 -2
  38. package/up/workflows/auditar.md +255 -0
  39. package/up/workflows/build.md +600 -386
  40. package/up/workflows/dcrv.md +183 -99
  41. package/up/workflows/governance.md +112 -220
  42. package/up/workflows/plan.md +169 -399
  43. package/up/workflows/rapido.md +7 -1
  44. package/up/workflows/up.md +447 -0
  45. package/up/agents/up-analista-codigo.md +0 -446
  46. package/up/agents/up-api-tester.md +0 -405
  47. package/up/agents/up-architecture-supervisor.md +0 -126
  48. package/up/agents/up-audit-supervisor.md +0 -83
  49. package/up/agents/up-auditor-modernidade.md +0 -378
  50. package/up/agents/up-auditor-performance.md +0 -426
  51. package/up/agents/up-auditor-ux.md +0 -396
  52. package/up/agents/up-backend-specialist.md +0 -175
  53. package/up/agents/up-blind-validator.md +0 -259
  54. package/up/agents/up-chief-architect.md +0 -184
  55. package/up/agents/up-chief-engineer.md +0 -202
  56. package/up/agents/up-chief-operations.md +0 -123
  57. package/up/agents/up-chief-product.md +0 -103
  58. package/up/agents/up-chief-quality.md +0 -211
  59. package/up/agents/up-clone-crawler.md +0 -234
  60. package/up/agents/up-clone-design-extractor.md +0 -227
  61. package/up/agents/up-clone-feature-mapper.md +0 -225
  62. package/up/agents/up-clone-prd-writer.md +0 -169
  63. package/up/agents/up-clone-verifier.md +0 -227
  64. package/up/agents/up-code-reviewer.md +0 -229
  65. package/up/agents/up-consolidador-ideias.md +0 -493
  66. package/up/agents/up-database-specialist.md +0 -169
  67. package/up/agents/up-delivery-auditor.md +0 -247
  68. package/up/agents/up-devops-agent.md +0 -203
  69. package/up/agents/up-execution-supervisor.md +0 -315
  70. package/up/agents/up-exhaustive-tester.md +0 -348
  71. package/up/agents/up-frontend-specialist.md +0 -152
  72. package/up/agents/up-operations-supervisor.md +0 -94
  73. package/up/agents/up-pesquisador-mercado.md +0 -350
  74. package/up/agents/up-pesquisador-projeto.md +0 -358
  75. package/up/agents/up-planning-auditor.md +0 -284
  76. package/up/agents/up-planning-supervisor.md +0 -260
  77. package/up/agents/up-product-analyst.md +0 -192
  78. package/up/agents/up-product-supervisor.md +0 -83
  79. package/up/agents/up-project-ceo.md +0 -352
  80. package/up/agents/up-qa-agent.md +0 -171
  81. package/up/agents/up-quality-supervisor.md +0 -178
  82. package/up/agents/up-requirements-validator.md +0 -230
  83. package/up/agents/up-security-reviewer.md +0 -137
  84. package/up/agents/up-sintetizador-melhorias.md +0 -407
  85. package/up/agents/up-system-designer.md +0 -332
  86. package/up/agents/up-technical-writer.md +0 -188
  87. package/up/agents/up-verification-supervisor.md +0 -111
  88. package/up/agents/up-visual-critic.md +0 -358
  89. package/up/commands/adicionar-fase.md +0 -47
  90. package/up/commands/adicionar-testes.md +0 -145
  91. package/up/commands/ajuda.md +0 -176
  92. package/up/commands/atualizar.md +0 -103
  93. package/up/commands/clone-builder.md +0 -67
  94. package/up/commands/configurar.md +0 -219
  95. package/up/commands/custos.md +0 -67
  96. package/up/commands/dashboard.md +0 -48
  97. package/up/commands/discutir-fase.md +0 -35
  98. package/up/commands/executar-fase.md +0 -40
  99. package/up/commands/ideias.md +0 -49
  100. package/up/commands/iniciar.md +0 -31
  101. package/up/commands/mapear-codigo.md +0 -63
  102. package/up/commands/melhorias.md +0 -45
  103. package/up/commands/mobile-first.md +0 -71
  104. package/up/commands/modo-builder.md +0 -186
  105. package/up/commands/novo-projeto.md +0 -40
  106. package/up/commands/onboard.md +0 -69
  107. package/up/commands/pausar.md +0 -33
  108. package/up/commands/planejar-fase.md +0 -45
  109. package/up/commands/progresso.md +0 -33
  110. package/up/commands/remover-fase.md +0 -34
  111. package/up/commands/resetar.md +0 -27
  112. package/up/commands/retomar.md +0 -35
  113. package/up/commands/saude.md +0 -103
  114. package/up/commands/ux-tester.md +0 -63
  115. package/up/commands/verificar-trabalho.md +0 -35
  116. package/up/workflows/adicionar-fase.md +0 -112
  117. package/up/workflows/builder-e2e.md +0 -501
  118. package/up/workflows/builder.md +0 -3419
  119. package/up/workflows/ceo-intake.md +0 -305
  120. package/up/workflows/ceo-updates.md +0 -183
  121. package/up/workflows/clone-builder.md +0 -320
  122. package/up/workflows/discutir-fase.md +0 -336
  123. package/up/workflows/executar-fase.md +0 -358
  124. package/up/workflows/executar-plano.md +0 -659
  125. package/up/workflows/ideias.md +0 -381
  126. package/up/workflows/iniciar.md +0 -235
  127. package/up/workflows/melhorias.md +0 -409
  128. package/up/workflows/mobile-first.md +0 -692
  129. package/up/workflows/novo-projeto.md +0 -778
  130. package/up/workflows/planejar-fase.md +0 -293
  131. package/up/workflows/progresso.md +0 -226
  132. package/up/workflows/retomar.md +0 -231
  133. package/up/workflows/ux-tester.md +0 -526
  134. 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
+ };
@@ -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
- 'up-executor': ['engineering-principles-compressed'],
2825
- 'up-frontend-specialist': ['engineering-principles-compressed', 'ui-brand', 'production-requirements-compressed'],
2826
- 'up-backend-specialist': ['engineering-principles-compressed', 'production-requirements-compressed'],
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-projeto': [],
2839
- 'up-sintetizador': [],
2878
+ 'up-pesquisador': [],
2879
+ 'up-sintetizador': ['production-requirements-compressed'],
2840
2880
  'up-mapeador-codigo': [],
2841
- 'up-requirements-validator': ['production-requirements-compressed'],
2842
-
2843
- // Governance fokus em rules + rework
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-blind-validator': [],
2864
- 'up-code-reviewer': ['engineering-principles-compressed'],
2865
- 'up-security-reviewer': ['production-requirements-compressed'],
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
  // =====================================================================