up-cc 2.0.2 → 2.1.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/package.json +1 -1
- package/up/CHANGELOG.md +49 -0
- package/up/agents/up-arquiteto.md +5 -2
- package/up/bin/lib/core.cjs +3 -2
- package/up/bin/lib/github.cjs +185 -76
- package/up/bin/lib/github.test.cjs +161 -0
- package/up/bin/up-tools.cjs +20 -3
- package/up/commands/build.md +15 -14
- package/up/skills/up-brainstorm/SKILL.md +41 -0
- package/up/skills/usando-up/SKILL.md +6 -2
- package/up/workflows/build.md +98 -51
- package/up/workflows/rapido.md +1 -1
- package/up/workflows/up.md +9 -4
package/package.json
CHANGED
package/up/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,55 @@ Todas as mudancas relevantes do `up-cc` ficam documentadas aqui. O formato segue
|
|
|
4
4
|
o espirito de [Keep a Changelog](https://keepachangelog.com/) e o versionamento
|
|
5
5
|
e [SemVer](https://semver.org/). v2.0.0 e um **major** (breaking change).
|
|
6
6
|
|
|
7
|
+
## 2.1.0
|
|
8
|
+
|
|
9
|
+
> GitHub e a interacao humana viram **eixos separados**. Antes, `--solo` desligava o
|
|
10
|
+
> GitHub (zero worktree/issue/PR) E pulava a cerimonia. Num run autonomo/loop isso fazia
|
|
11
|
+
> o projeto inteiro ir pro `main` sem nenhum artefato GitHub, mesmo com remote conectado.
|
|
12
|
+
> Agora `--solo` NAO desliga o GitHub: e so autonomia. Pra pular o GitHub use `--local`.
|
|
13
|
+
|
|
14
|
+
### Mudado (atencao: semantica de `--solo`)
|
|
15
|
+
|
|
16
|
+
- **`--solo` agora MANTEM o GitHub-nativo.** Solo virou "autonomo total": cria
|
|
17
|
+
worktree + branch + issue + PR e auto-mergeia, SEM menu e SEM gate visual (pra
|
|
18
|
+
loop/headless onde ninguem aprova). Nao desliga mais o GitHub.
|
|
19
|
+
- **`--local` e o novo escape hatch sem GitHub** (o comportamento que `--solo` tinha
|
|
20
|
+
ate a 2.0.x): commit atomico na branch atual, zero worktree/issue/PR/rede. `/up:rapido`
|
|
21
|
+
segue como o atalho nomeado pro mesmo modo.
|
|
22
|
+
- **`--auto`** continua igual: pula so o menu de fim de fase, mantem GitHub, e o gate
|
|
23
|
+
visual ainda roda se `require_visual_test=true`.
|
|
24
|
+
|
|
25
|
+
### Adicionado
|
|
26
|
+
|
|
27
|
+
- **Deteccao de GitHub por `gh` CLI OU MCP do GitHub.** O eixo GitHub liga sempre que
|
|
28
|
+
ha remote + (`gh` autenticado OU MCP do GitHub conectado). Transporte de issue/PR:
|
|
29
|
+
`gh` (CLI cria direto), `mcp` (o workflow cria via `mcp__...github__*` e grava de
|
|
30
|
+
volta), `none` (sem remote -> git local). Worktree/branch/push sao git local e
|
|
31
|
+
acontecem sempre, offline-ok.
|
|
32
|
+
- **Subcomandos `github record-issue` / `github record-pr`** no `up-tools.cjs` pra
|
|
33
|
+
gravar no `git-map.json` artefatos criados fora do `.cjs` (ex: via MCP). `record-pr
|
|
34
|
+
--merged` fecha a fase e limpa worktree+branch.
|
|
35
|
+
- **Flag `--local`** no `/up:build` e `start-phase`/`finish-phase` (`--mode local`).
|
|
36
|
+
- **Config builder grava `github_native: true` explicito** (up-arquiteto, workflows/up),
|
|
37
|
+
deixando a intencao visivel em run autonomo (antes dependia do default implicito).
|
|
38
|
+
- **Testes red-green** do `github.cjs` em `up/bin/lib/github.test.cjs` (10 casos; repo
|
|
39
|
+
git temp + remote bare local + seam `UP_FORCE_NO_GH`).
|
|
40
|
+
|
|
41
|
+
### Corrigido
|
|
42
|
+
|
|
43
|
+
- **Bug de doc em `workflows/up.md`** que dizia `--solo (default, commit na branch atual)`
|
|
44
|
+
e descrevia `--pr` como o fluxo GitHub (vocabulario v1). Era a unica fonte que podia
|
|
45
|
+
induzir um agente autonomo a escolher `--solo` e pular o GitHub. Reescrito pro v2.
|
|
46
|
+
- **`--local` numa fase nao polui mais o `github_native` global** do `git-map.json`
|
|
47
|
+
(poderia degradar fases nao-local seguintes). `finish-phase auto` passou a consultar
|
|
48
|
+
o config, nao o flag global do mapa.
|
|
49
|
+
|
|
50
|
+
### Migracao
|
|
51
|
+
|
|
52
|
+
- Quem usava `--solo` pra **commit local sem GitHub** deve trocar para `--local`.
|
|
53
|
+
- Quem quer **autonomo mantendo GitHub** (loop/headless): use `--solo` (sem gate visual)
|
|
54
|
+
ou `--auto` (com gate visual se `require_visual_test=true`).
|
|
55
|
+
|
|
7
56
|
## 2.0.0
|
|
8
57
|
|
|
9
58
|
> Redesign completo do UP. **Breaking change.** Os comandos antigos somem (nao ha
|
|
@@ -305,11 +305,14 @@ Modo builder ativo. Proxima acao: planejar fase 1.
|
|
|
305
305
|
"granularity": "standard",
|
|
306
306
|
"parallelization": true,
|
|
307
307
|
"commit_docs": true,
|
|
308
|
-
"builder_mode": true
|
|
308
|
+
"builder_mode": true,
|
|
309
|
+
"github_native": true
|
|
309
310
|
}
|
|
310
311
|
```
|
|
311
312
|
|
|
312
|
-
Note: `builder_mode: true` sinaliza que o projeto foi criado em modo autonomo.
|
|
313
|
+
Note: `builder_mode: true` sinaliza que o projeto foi criado em modo autonomo. `github_native: true`
|
|
314
|
+
explicito deixa a intencao gravada no arquivo (o build usa true por default; gravar evita ambiguidade
|
|
315
|
+
em run autonomo/loop). So mude pra `false` se o projeto e local sem GitHub.
|
|
313
316
|
</state_and_config>
|
|
314
317
|
|
|
315
318
|
<brownfield_mode>
|
package/up/bin/lib/core.cjs
CHANGED
|
@@ -114,8 +114,9 @@ function loadConfig(cwd) {
|
|
|
114
114
|
auto_advance: false,
|
|
115
115
|
instrumentation: { enabled: true },
|
|
116
116
|
budget_ceiling: null,
|
|
117
|
-
//
|
|
118
|
-
//
|
|
117
|
+
// GitHub-native e o PADRAO. So --local (ou este flag = false) desliga. --solo NAO
|
|
118
|
+
// desliga: solo = autonomia (sem menu/gate visual), GitHub segue ligado. Transporte
|
|
119
|
+
// de issue/PR: gh CLI OU MCP do GitHub (worktree/branch sao git local, sempre rodam).
|
|
119
120
|
github_native: true,
|
|
120
121
|
merge_strategy: 'squash',
|
|
121
122
|
// Fim de fase: se a fase tem UI, sobe dev server e exige aprovacao visual ANTES do merge.
|
package/up/bin/lib/github.cjs
CHANGED
|
@@ -34,8 +34,13 @@ function getRemoteUrl(cwd) {
|
|
|
34
34
|
return res.exitCode === 0 ? res.stdout.trim() : null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* `gh` instalado E autenticado?
|
|
39
|
+
* Seam de teste: UP_FORCE_NO_GH=1 forca indisponivel; UP_FORCE_GH=1 forca disponivel.
|
|
40
|
+
*/
|
|
38
41
|
function ghAvailable(cwd) {
|
|
42
|
+
if (process.env.UP_FORCE_NO_GH === '1') return false;
|
|
43
|
+
if (process.env.UP_FORCE_GH === '1') return true;
|
|
39
44
|
try {
|
|
40
45
|
execSync('gh auth status', { cwd, stdio: 'pipe' });
|
|
41
46
|
return true;
|
|
@@ -69,12 +74,34 @@ function execGh(cwd, args) {
|
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
/**
|
|
78
|
+
* EIXO A — queremos artefatos GitHub (worktree/branch sempre; issue/PR se remote)?
|
|
79
|
+
* Ligado a menos que: `--local`, config.github_native=false, ou sem remote origin.
|
|
80
|
+
* NAO depende de `gh`: gh-vs-MCP e o transporte (eixo separado), nao o liga/desliga.
|
|
81
|
+
*/
|
|
82
|
+
function gitWantsRemote(cwd, local) {
|
|
83
|
+
if (local) return false;
|
|
75
84
|
const config = loadConfig(cwd);
|
|
76
85
|
if (config.github_native === false) return false;
|
|
77
|
-
return
|
|
86
|
+
return gitHasRemote(cwd);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Transporte pra criar issue/PR remoto: 'gh' | 'mcp' | 'none'.
|
|
91
|
+
* - 'gh': gh CLI disponivel -> github.cjs cria direto (deterministico).
|
|
92
|
+
* - 'mcp': remote existe mas sem gh -> o WORKFLOW (LLM) cria via MCP do GitHub,
|
|
93
|
+
* porque um subprocesso Node nao chama tools MCP. github.cjs sinaliza e
|
|
94
|
+
* grava de volta via recordIssue/recordPr.
|
|
95
|
+
* - 'none': sem remote (ou --local / config off) -> git local puro, sem issue/PR.
|
|
96
|
+
*/
|
|
97
|
+
function issueTransport(cwd, local) {
|
|
98
|
+
if (!gitWantsRemote(cwd, local)) return 'none';
|
|
99
|
+
return ghAvailable(cwd) ? 'gh' : 'mcp';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Compat: github-native "classico" = transporte gh disponivel. */
|
|
103
|
+
function githubMode(cwd, local) {
|
|
104
|
+
return issueTransport(cwd, local) === 'gh';
|
|
78
105
|
}
|
|
79
106
|
|
|
80
107
|
// =====================================================================
|
|
@@ -189,24 +216,27 @@ function phaseKey(phase) {
|
|
|
189
216
|
// =====================================================================
|
|
190
217
|
|
|
191
218
|
/**
|
|
192
|
-
* startPhase({cwd, phase, slug, solo})
|
|
193
|
-
*
|
|
194
|
-
* -
|
|
195
|
-
*
|
|
196
|
-
* -
|
|
197
|
-
*
|
|
219
|
+
* startPhase({cwd, phase, slug, local, solo})
|
|
220
|
+
* EIXO A (GitHub) e EIXO B (interacao) sao SEPARADOS:
|
|
221
|
+
* - `local=true` (flag --local / config off / /up:rapido): SEM worktree/issue, commit
|
|
222
|
+
* na branch atual. E o unico jeito de desligar o GitHub aqui.
|
|
223
|
+
* - `solo` NAO desliga o GitHub: solo so afeta o gate visual e o merge (no build.md).
|
|
224
|
+
* Por isso `solo` e aceito mas ignorado neste passo (compat).
|
|
225
|
+
* - Caso contrario: cria worktree+branch SEMPRE (git local, offline-ok). Issue conforme
|
|
226
|
+
* o transporte: 'gh' cria direto; 'mcp' deixa pro workflow criar via MCP (retorna
|
|
227
|
+
* `pending.issue` e mode 'github-mcp'); 'none' (sem remote) so git local.
|
|
228
|
+
* - Escreve .plano/git-map.json. Retorna {branch, worktree, issue, issue_url, mode, transport, warnings, pending?}.
|
|
198
229
|
*/
|
|
199
|
-
function startPhase({ cwd, phase, slug, solo = false }) {
|
|
230
|
+
function startPhase({ cwd, phase, slug, local = false, solo = false }) {
|
|
231
|
+
void solo; // solo nao desliga GitHub (eixo de interacao, tratado no build.md)
|
|
200
232
|
const warnings = [];
|
|
201
233
|
const key = phaseKey(phase);
|
|
202
234
|
const branch = branchName(phase, slug);
|
|
203
|
-
const ghOn = githubMode(cwd, solo);
|
|
204
235
|
|
|
205
236
|
const map = readGitMap(cwd);
|
|
206
|
-
if (solo) map.github_native = false;
|
|
207
237
|
|
|
208
|
-
// ---
|
|
209
|
-
if (
|
|
238
|
+
// --- LOCAL: nada de worktree/issue, trabalha na branch atual ---
|
|
239
|
+
if (local) {
|
|
210
240
|
const cur = execGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
211
241
|
const currentBranch = cur.exitCode === 0 ? cur.stdout.trim() : null;
|
|
212
242
|
map.phases[key] = {
|
|
@@ -225,22 +255,21 @@ function startPhase({ cwd, phase, slug, solo = false }) {
|
|
|
225
255
|
worktree: null,
|
|
226
256
|
issue: null,
|
|
227
257
|
issue_url: null,
|
|
228
|
-
mode: '
|
|
258
|
+
mode: 'local',
|
|
259
|
+
transport: 'none',
|
|
229
260
|
warnings,
|
|
230
261
|
};
|
|
231
262
|
}
|
|
232
263
|
|
|
233
|
-
// ---
|
|
264
|
+
// --- worktree+branch SEMPRE (git local, independe de gh/MCP) ---
|
|
234
265
|
const wt = worktreePath(cwd, phase, slug);
|
|
235
266
|
shieldWorktreeDir(cwd);
|
|
236
267
|
|
|
237
|
-
// Worktree e branch existem? (idempotente / recovery)
|
|
238
268
|
const existing = (map.phases[key] && map.phases[key].worktree) || null;
|
|
239
269
|
let worktreeCreated = false;
|
|
240
270
|
|
|
241
271
|
if (!fs.existsSync(wt)) {
|
|
242
272
|
fs.mkdirSync(path.dirname(wt), { recursive: true });
|
|
243
|
-
// Branch ja existe? Anexa sem -b. Senao cria com -b.
|
|
244
273
|
const branchExists = execGit(cwd, ['rev-parse', '--verify', '--quiet', branch]).exitCode === 0;
|
|
245
274
|
const wtArgs = branchExists
|
|
246
275
|
? ['worktree', 'add', wt, branch]
|
|
@@ -248,7 +277,6 @@ function startPhase({ cwd, phase, slug, solo = false }) {
|
|
|
248
277
|
const wtRes = execGit(cwd, wtArgs);
|
|
249
278
|
if (wtRes.exitCode !== 0) {
|
|
250
279
|
warnings.push('worktree_failed: ' + (wtRes.stderr || wtRes.stdout));
|
|
251
|
-
// FAIL-OPEN: cai pra branch local sem worktree
|
|
252
280
|
const co = execGit(cwd, branchExists ? ['checkout', branch] : ['checkout', '-b', branch]);
|
|
253
281
|
if (co.exitCode !== 0) warnings.push('branch_checkout_failed: ' + (co.stderr || co.stdout));
|
|
254
282
|
} else {
|
|
@@ -259,13 +287,16 @@ function startPhase({ cwd, phase, slug, solo = false }) {
|
|
|
259
287
|
}
|
|
260
288
|
const worktree = worktreeCreated || existing ? wt : null;
|
|
261
289
|
|
|
262
|
-
// --- issue (
|
|
290
|
+
// --- issue: depende do transporte (gh | mcp | none) ---
|
|
291
|
+
const transport = issueTransport(cwd, local);
|
|
263
292
|
let issue = null;
|
|
264
293
|
let issueUrl = null;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
294
|
+
let pending = null;
|
|
295
|
+
const issueTitle = `[fase ${padPhase(phase)}] ${slug || branch}`;
|
|
296
|
+
const issueBody = buildIssueBody(cwd, phase, slug);
|
|
297
|
+
|
|
298
|
+
if (transport === 'gh') {
|
|
299
|
+
const res = execGh(cwd, ['issue', 'create', '--title', issueTitle, '--body', issueBody]);
|
|
269
300
|
if (res.exitCode === 0) {
|
|
270
301
|
issueUrl = res.stdout.trim().split('\n').pop().trim() || null;
|
|
271
302
|
const m = issueUrl && issueUrl.match(/\/issues\/(\d+)/);
|
|
@@ -273,9 +304,13 @@ function startPhase({ cwd, phase, slug, solo = false }) {
|
|
|
273
304
|
} else {
|
|
274
305
|
warnings.push('issue_create_failed: ' + (res.stderr || res.stdout));
|
|
275
306
|
}
|
|
276
|
-
} else if (
|
|
277
|
-
|
|
278
|
-
|
|
307
|
+
} else if (transport === 'mcp') {
|
|
308
|
+
// Node nao chama MCP: o workflow cria a issue via mcp__...github__issue_write
|
|
309
|
+
// e grava de volta com `up-tools github record-issue`. Worktree/branch ja existem.
|
|
310
|
+
pending = { issue: { title: issueTitle, body: issueBody } };
|
|
311
|
+
warnings.push('gh indisponivel: criar issue via MCP (github-mcp) e gravar com record-issue. worktree/branch ja prontos.');
|
|
312
|
+
} else {
|
|
313
|
+
warnings.push('sem remote origin: git local (sem issue/PR).');
|
|
279
314
|
}
|
|
280
315
|
|
|
281
316
|
map.phases[key] = {
|
|
@@ -290,14 +325,10 @@ function startPhase({ cwd, phase, slug, solo = false }) {
|
|
|
290
325
|
};
|
|
291
326
|
writeGitMap(cwd, map);
|
|
292
327
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
issue_url: issueUrl,
|
|
298
|
-
mode: ghOn ? 'github' : 'git-local',
|
|
299
|
-
warnings,
|
|
300
|
-
};
|
|
328
|
+
const mode = transport === 'gh' ? 'github' : transport === 'mcp' ? 'github-mcp' : 'git-local';
|
|
329
|
+
const ret = { branch, worktree, issue, issue_url: issueUrl, mode, transport, warnings };
|
|
330
|
+
if (pending) ret.pending = pending;
|
|
331
|
+
return ret;
|
|
301
332
|
}
|
|
302
333
|
|
|
303
334
|
/** Body da issue: goal + criterios do ROADMAP, se disponiveis. */
|
|
@@ -317,12 +348,34 @@ function buildIssueBody(cwd, phase, slug) {
|
|
|
317
348
|
// finishPhase — solo | menu | auto/pr
|
|
318
349
|
// =====================================================================
|
|
319
350
|
|
|
351
|
+
/** Remove worktree + branch local (best-effort). Usado por finishPhase e recordPr. */
|
|
352
|
+
function cleanupWorktreeBranch(cwd, worktree, branch, warnings) {
|
|
353
|
+
if (worktree && fs.existsSync(worktree)) {
|
|
354
|
+
const rm = execGit(cwd, ['worktree', 'remove', worktree]);
|
|
355
|
+
if (rm.exitCode !== 0) {
|
|
356
|
+
const rmF = execGit(cwd, ['worktree', 'remove', '--force', worktree]);
|
|
357
|
+
if (rmF.exitCode !== 0) warnings.push('worktree_remove_failed: ' + (rmF.stderr || rmF.stdout));
|
|
358
|
+
}
|
|
359
|
+
try { if (fs.existsSync(worktree)) fs.rmdirSync(worktree); } catch (e) { /* nao-vazio: ignora */ }
|
|
360
|
+
try {
|
|
361
|
+
const parent = path.dirname(worktree);
|
|
362
|
+
if (fs.existsSync(parent) && fs.readdirSync(parent).length === 0) fs.rmdirSync(parent);
|
|
363
|
+
} catch (e) { /* ignora */ }
|
|
364
|
+
}
|
|
365
|
+
if (branch) {
|
|
366
|
+
execGit(cwd, ['branch', '-D', branch]); // silencioso: gh/merge ja pode ter removido
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
320
370
|
/**
|
|
321
371
|
* finishPhase({cwd, phase, mode, strategy})
|
|
322
|
-
* - solo: nao faz nada (ja committado na branch atual).
|
|
372
|
+
* - local (alias 'solo'): nao faz nada (ja committado na branch atual). status=done.
|
|
323
373
|
* - menu: imprime as 4 opcoes pro orquestrador perguntar (nao age).
|
|
324
|
-
* - auto (a.k.a. pr):
|
|
325
|
-
* ->
|
|
374
|
+
* - auto (a.k.a. pr): conforme o transporte ('gh' | 'mcp' | 'none'):
|
|
375
|
+
* 'gh' -> push + gh pr create (Closes #issue) + merge + cleanup.
|
|
376
|
+
* 'mcp' -> push e PARA: retorna action 'needs-mcp-pr' + pr_payload pro workflow
|
|
377
|
+
* criar/mergear o PR via MCP e fechar com `record-pr --merged` (que limpa).
|
|
378
|
+
* 'none' -> merge LOCAL fail-open + cleanup.
|
|
326
379
|
* Atualiza git-map.json. Retorna estado da operacao.
|
|
327
380
|
*/
|
|
328
381
|
function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
@@ -332,12 +385,12 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
332
385
|
const entry = map.phases[key] || {};
|
|
333
386
|
const mergeStrategy = strategy || map.merge_strategy || loadConfig(cwd).merge_strategy || 'squash';
|
|
334
387
|
|
|
335
|
-
// --- SOLO: nada a fazer, ja committado ---
|
|
336
|
-
if (mode === 'solo') {
|
|
388
|
+
// --- LOCAL / SOLO: nada a fazer, ja committado ---
|
|
389
|
+
if (mode === 'local' || mode === 'solo') {
|
|
337
390
|
entry.status = 'done';
|
|
338
391
|
map.phases[key] = entry;
|
|
339
392
|
writeGitMap(cwd, map);
|
|
340
|
-
return { mode
|
|
393
|
+
return { mode, action: 'none', status: 'done', warnings };
|
|
341
394
|
}
|
|
342
395
|
|
|
343
396
|
// --- MENU: imprime as 4 opcoes, nao age ---
|
|
@@ -361,7 +414,10 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
361
414
|
if (mode === 'auto' || mode === 'pr') {
|
|
362
415
|
const branch = entry.branch || branchName(phase, '');
|
|
363
416
|
const worktree = entry.worktree || null;
|
|
364
|
-
|
|
417
|
+
// mode 'auto' ja significa "quero GitHub": consulta o config (intencao do projeto),
|
|
418
|
+
// nao o flag global do git-map (que uma fase --local anterior poderia ter mexido).
|
|
419
|
+
const wants = gitHasRemote(cwd) && loadConfig(cwd).github_native !== false;
|
|
420
|
+
const transport = wants ? (ghAvailable(cwd) ? 'gh' : 'mcp') : 'none';
|
|
365
421
|
|
|
366
422
|
// Base de merge (default: branch ativa na main do repo)
|
|
367
423
|
const baseRes = execGit(cwd, ['symbolic-ref', '--short', 'HEAD']);
|
|
@@ -370,16 +426,41 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
370
426
|
let pr = entry.pr || null;
|
|
371
427
|
let prUrl = entry.pr_url || null;
|
|
372
428
|
|
|
373
|
-
|
|
374
|
-
|
|
429
|
+
const issueRef = entry.issue ? `\n\nCloses #${entry.issue}` : '';
|
|
430
|
+
const prTitle = `Fase ${padPhase(phase)}` + (entry.branch ? `: ${entry.branch.replace(/^up\/fase-\d+-?/, '')}` : '');
|
|
431
|
+
const prBody = `Entrega da fase ${padPhase(phase)} (UP github-native).${issueRef}`;
|
|
432
|
+
|
|
433
|
+
// push da branch (necessario pro PR, gh OU MCP). Worktree tem a branch checada.
|
|
434
|
+
if (transport === 'gh' || transport === 'mcp') {
|
|
375
435
|
const pushCwd = worktree && fs.existsSync(worktree) ? worktree : cwd;
|
|
376
436
|
const push = execGit(pushCwd, ['push', '-u', 'origin', branch]);
|
|
377
437
|
if (push.exitCode !== 0) warnings.push('push_failed: ' + (push.stderr || push.stdout));
|
|
438
|
+
}
|
|
378
439
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
440
|
+
// --- transporte MCP: push feito, PR fica pro workflow (LLM) via MCP ---
|
|
441
|
+
if (transport === 'mcp') {
|
|
442
|
+
map.phases[key] = entry; // status segue in_progress ate record-pr
|
|
443
|
+
writeGitMap(cwd, map);
|
|
444
|
+
return {
|
|
445
|
+
mode,
|
|
446
|
+
action: 'needs-mcp-pr',
|
|
447
|
+
transport: 'mcp',
|
|
448
|
+
pr_payload: {
|
|
449
|
+
base,
|
|
450
|
+
head: branch,
|
|
451
|
+
title: prTitle,
|
|
452
|
+
body: prBody,
|
|
453
|
+
issue: entry.issue || null,
|
|
454
|
+
strategy: mergeStrategy,
|
|
455
|
+
worktree,
|
|
456
|
+
},
|
|
457
|
+
branch,
|
|
458
|
+
status: entry.status || 'in_progress',
|
|
459
|
+
warnings,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (transport === 'gh') {
|
|
383
464
|
const create = execGh(cwd, ['pr', 'create', '--base', base, '--head', branch, '--title', prTitle, '--body', prBody]);
|
|
384
465
|
if (create.exitCode === 0) {
|
|
385
466
|
prUrl = create.stdout.trim().split('\n').pop().trim() || null;
|
|
@@ -389,7 +470,6 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
389
470
|
warnings.push('pr_create_failed: ' + (create.stderr || create.stdout));
|
|
390
471
|
}
|
|
391
472
|
|
|
392
|
-
// merge via gh
|
|
393
473
|
const stratFlag = mergeStrategy === 'merge' ? '--merge' : mergeStrategy === 'rebase' ? '--rebase' : '--squash';
|
|
394
474
|
const mergeTarget = pr ? String(pr) : branch;
|
|
395
475
|
const merge = execGh(cwd, ['pr', 'merge', mergeTarget, stratFlag, '--delete-branch']);
|
|
@@ -401,7 +481,6 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
401
481
|
} else {
|
|
402
482
|
// FAIL-OPEN: merge LOCAL (sem remote/gh)
|
|
403
483
|
warnings.push('sem gh/remote: merge local da branch ' + branch + ' em ' + base);
|
|
404
|
-
// Garante base checada no repo principal
|
|
405
484
|
const co = execGit(cwd, ['checkout', base]);
|
|
406
485
|
if (co.exitCode !== 0) warnings.push('checkout_base_failed: ' + (co.stderr || co.stdout));
|
|
407
486
|
const stratArgs = mergeStrategy === 'squash'
|
|
@@ -421,35 +500,19 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
421
500
|
}
|
|
422
501
|
}
|
|
423
502
|
|
|
424
|
-
|
|
425
|
-
if (worktree && fs.existsSync(worktree)) {
|
|
426
|
-
const rm = execGit(cwd, ['worktree', 'remove', worktree]);
|
|
427
|
-
if (rm.exitCode !== 0) {
|
|
428
|
-
const rmF = execGit(cwd, ['worktree', 'remove', '--force', worktree]);
|
|
429
|
-
if (rmF.exitCode !== 0) warnings.push('worktree_remove_failed: ' + (rmF.stderr || rmF.stdout));
|
|
430
|
-
}
|
|
431
|
-
// best-effort: remove a casca vazia que git deixa, e o dir-pai .up-worktrees/<repo> se vazio
|
|
432
|
-
try { if (fs.existsSync(worktree)) fs.rmdirSync(worktree); } catch (e) { /* nao-vazio: ignora */ }
|
|
433
|
-
try {
|
|
434
|
-
const parent = path.dirname(worktree);
|
|
435
|
-
if (fs.existsSync(parent) && fs.readdirSync(parent).length === 0) fs.rmdirSync(parent);
|
|
436
|
-
} catch (e) { /* ignora */ }
|
|
437
|
-
}
|
|
438
|
-
// remove branch local (se ainda existir e nao for a branch ativa)
|
|
439
|
-
const delBranch = execGit(cwd, ['branch', '-D', branch]);
|
|
440
|
-
if (delBranch.exitCode !== 0 && !/not found|não encontrad/i.test(delBranch.stderr)) {
|
|
441
|
-
// silencioso: gh --delete-branch ja pode ter removido
|
|
442
|
-
}
|
|
503
|
+
cleanupWorktreeBranch(cwd, worktree, branch, warnings);
|
|
443
504
|
|
|
444
505
|
entry.pr = pr;
|
|
445
506
|
entry.pr_url = prUrl;
|
|
446
|
-
|
|
507
|
+
entry.worktree = null;
|
|
508
|
+
if (!entry.status || entry.status === 'in_progress') entry.status = transport === 'gh' ? 'merged' : entry.status;
|
|
447
509
|
map.phases[key] = entry;
|
|
448
510
|
writeGitMap(cwd, map);
|
|
449
511
|
|
|
450
512
|
return {
|
|
451
|
-
mode
|
|
513
|
+
mode,
|
|
452
514
|
action: 'merged',
|
|
515
|
+
transport,
|
|
453
516
|
pr,
|
|
454
517
|
pr_url: prUrl,
|
|
455
518
|
branch,
|
|
@@ -459,10 +522,47 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
459
522
|
};
|
|
460
523
|
}
|
|
461
524
|
|
|
462
|
-
warnings.push('modo desconhecido: ' + mode + ' (use
|
|
525
|
+
warnings.push('modo desconhecido: ' + mode + ' (use local|menu|auto)');
|
|
463
526
|
return { mode, action: 'noop', warnings };
|
|
464
527
|
}
|
|
465
528
|
|
|
529
|
+
// =====================================================================
|
|
530
|
+
// recordIssue / recordPr — gravam artefatos criados FORA (via MCP no workflow)
|
|
531
|
+
// =====================================================================
|
|
532
|
+
|
|
533
|
+
/** Grava no git-map a issue criada externamente (ex: via MCP). */
|
|
534
|
+
function recordIssue({ cwd, phase, issue, url }) {
|
|
535
|
+
const key = phaseKey(phase);
|
|
536
|
+
const map = readGitMap(cwd);
|
|
537
|
+
const entry = map.phases[key] || {};
|
|
538
|
+
if (issue != null && issue !== '') entry.issue = parseInt(issue, 10);
|
|
539
|
+
if (url) entry.issue_url = url;
|
|
540
|
+
map.phases[key] = entry;
|
|
541
|
+
writeGitMap(cwd, map);
|
|
542
|
+
return { recorded: true, phase: key, issue: entry.issue ?? null, issue_url: entry.issue_url || null };
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Grava no git-map o PR criado externamente (ex: via MCP).
|
|
547
|
+
* Com `merged=true`: marca status=merged e limpa worktree+branch local.
|
|
548
|
+
*/
|
|
549
|
+
function recordPr({ cwd, phase, pr, url, merged = false }) {
|
|
550
|
+
const warnings = [];
|
|
551
|
+
const key = phaseKey(phase);
|
|
552
|
+
const map = readGitMap(cwd);
|
|
553
|
+
const entry = map.phases[key] || {};
|
|
554
|
+
if (pr != null && pr !== '') entry.pr = parseInt(pr, 10);
|
|
555
|
+
if (url) entry.pr_url = url;
|
|
556
|
+
if (merged) {
|
|
557
|
+
entry.status = 'merged';
|
|
558
|
+
cleanupWorktreeBranch(cwd, entry.worktree, entry.branch, warnings);
|
|
559
|
+
entry.worktree = null;
|
|
560
|
+
}
|
|
561
|
+
map.phases[key] = entry;
|
|
562
|
+
writeGitMap(cwd, map);
|
|
563
|
+
return { recorded: true, phase: key, pr: entry.pr ?? null, pr_url: entry.pr_url || null, status: entry.status || null, merged: !!merged, warnings };
|
|
564
|
+
}
|
|
565
|
+
|
|
466
566
|
// =====================================================================
|
|
467
567
|
// status — mostra git-map.json
|
|
468
568
|
// =====================================================================
|
|
@@ -470,12 +570,16 @@ function finishPhase({ cwd, phase, mode = 'menu', strategy }) {
|
|
|
470
570
|
/** status({cwd}) — retorna o git-map.json atual (ou defaults se nao existir). Nunca crasha. */
|
|
471
571
|
function status({ cwd }) {
|
|
472
572
|
const map = readGitMap(cwd);
|
|
573
|
+
const hasRemote = gitHasRemote(cwd);
|
|
574
|
+
const ghOk = ghAvailable(cwd);
|
|
473
575
|
return {
|
|
474
576
|
github_native: map.github_native,
|
|
475
577
|
merge_strategy: map.merge_strategy,
|
|
476
|
-
gh_available:
|
|
477
|
-
has_remote:
|
|
578
|
+
gh_available: ghOk,
|
|
579
|
+
has_remote: hasRemote,
|
|
478
580
|
remote: getRemoteUrl(cwd),
|
|
581
|
+
// transporte efetivo pra issue/PR: 'gh' | 'mcp' | 'none' (assumindo nao-local)
|
|
582
|
+
transport: hasRemote && map.github_native !== false ? (ghOk ? 'gh' : 'mcp') : 'none',
|
|
479
583
|
phases: map.phases,
|
|
480
584
|
};
|
|
481
585
|
}
|
|
@@ -483,13 +587,18 @@ function status({ cwd }) {
|
|
|
483
587
|
module.exports = {
|
|
484
588
|
startPhase,
|
|
485
589
|
finishPhase,
|
|
590
|
+
recordIssue,
|
|
591
|
+
recordPr,
|
|
486
592
|
status,
|
|
487
593
|
// helpers expostos para teste/reuso
|
|
488
594
|
branchName,
|
|
489
595
|
worktreePath,
|
|
490
596
|
gitHasRemote,
|
|
491
597
|
ghAvailable,
|
|
598
|
+
gitWantsRemote,
|
|
599
|
+
issueTransport,
|
|
492
600
|
githubMode,
|
|
601
|
+
cleanupWorktreeBranch,
|
|
493
602
|
readGitMap,
|
|
494
603
|
writeGitMap,
|
|
495
604
|
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* github.test.cjs — testes red-green do github.cjs (eixo GitHub vs interacao).
|
|
3
|
+
* Roda: node up/bin/lib/github.test.cjs
|
|
4
|
+
* Sem framework. Cria repos git temporarios. Usa remote bare local (push sem rede)
|
|
5
|
+
* e o seam UP_FORCE_NO_GH=1 pra simular "sem gh CLI, so MCP".
|
|
6
|
+
*/
|
|
7
|
+
const assert = require('assert');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
const gh = require('./github.cjs');
|
|
13
|
+
|
|
14
|
+
process.env.GIT_TERMINAL_PROMPT = '0';
|
|
15
|
+
|
|
16
|
+
function sh(cmd, cwd) { execSync(cmd, { cwd, stdio: 'pipe' }); }
|
|
17
|
+
|
|
18
|
+
/** Cria repo git temp. withRemote=true adiciona origin = bare repo local. */
|
|
19
|
+
function mkRepo(withRemote) {
|
|
20
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'up-gh-'));
|
|
21
|
+
sh('git init -q', dir);
|
|
22
|
+
sh('git config user.email t@t.co', dir);
|
|
23
|
+
sh('git config user.name t', dir);
|
|
24
|
+
sh('git config commit.gpgsign false', dir);
|
|
25
|
+
fs.writeFileSync(path.join(dir, 'README.md'), '# t\n');
|
|
26
|
+
sh('git add -A', dir);
|
|
27
|
+
sh('git commit -qm init', dir);
|
|
28
|
+
sh('git branch -M main', dir);
|
|
29
|
+
fs.mkdirSync(path.join(dir, '.plano'), { recursive: true });
|
|
30
|
+
let bare = null;
|
|
31
|
+
if (withRemote) {
|
|
32
|
+
bare = fs.mkdtempSync(path.join(os.tmpdir(), 'up-gh-bare-'));
|
|
33
|
+
sh('git init -q --bare', bare);
|
|
34
|
+
sh(`git remote add origin ${bare}`, dir);
|
|
35
|
+
}
|
|
36
|
+
return { dir, bare };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let pass = 0, fail = 0;
|
|
40
|
+
function t(name, fn) {
|
|
41
|
+
try { fn(); console.log(' ok -', name); pass++; }
|
|
42
|
+
catch (e) { console.error(' FAIL -', name, '\n ', e.message); fail++; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 1. local=true -> sem worktree/issue, branch atual, mode local
|
|
46
|
+
t('local: sem worktree/issue, branch atual', () => {
|
|
47
|
+
const { dir } = mkRepo(false);
|
|
48
|
+
const r = gh.startPhase({ cwd: dir, phase: 1, slug: 'x', local: true });
|
|
49
|
+
assert.strictEqual(r.worktree, null);
|
|
50
|
+
assert.strictEqual(r.issue, null);
|
|
51
|
+
assert.strictEqual(r.mode, 'local');
|
|
52
|
+
assert.strictEqual(r.transport, 'none');
|
|
53
|
+
assert.strictEqual(r.branch, 'main');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 2. sem remote, nao-local -> worktree criado, git-local, sem issue
|
|
57
|
+
t('sem remote: worktree criado, git-local', () => {
|
|
58
|
+
const { dir } = mkRepo(false);
|
|
59
|
+
const r = gh.startPhase({ cwd: dir, phase: 2, slug: 'feat' });
|
|
60
|
+
assert.ok(r.worktree && fs.existsSync(r.worktree), 'worktree deve existir');
|
|
61
|
+
assert.strictEqual(r.mode, 'git-local');
|
|
62
|
+
assert.strictEqual(r.transport, 'none');
|
|
63
|
+
assert.strictEqual(r.issue, null);
|
|
64
|
+
assert.strictEqual(r.branch, 'up/fase-02-feat');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 3. remote + sem gh -> github-mcp, worktree criado, pending.issue, transport mcp
|
|
68
|
+
t('remote sem gh: github-mcp pendente', () => {
|
|
69
|
+
const { dir } = mkRepo(true);
|
|
70
|
+
process.env.UP_FORCE_NO_GH = '1';
|
|
71
|
+
try {
|
|
72
|
+
const r = gh.startPhase({ cwd: dir, phase: 3, slug: 'mcp' });
|
|
73
|
+
assert.strictEqual(r.transport, 'mcp');
|
|
74
|
+
assert.strictEqual(r.mode, 'github-mcp');
|
|
75
|
+
assert.ok(r.worktree && fs.existsSync(r.worktree), 'worktree deve existir mesmo sem gh');
|
|
76
|
+
assert.strictEqual(r.issue, null);
|
|
77
|
+
assert.ok(r.pending && r.pending.issue && r.pending.issue.title, 'deve trazer pending.issue.title');
|
|
78
|
+
} finally { delete process.env.UP_FORCE_NO_GH; }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// 4. REGRESSAO do bug: solo NAO desliga github
|
|
82
|
+
t('solo nao desliga github (vira github-mcp, nao local)', () => {
|
|
83
|
+
const { dir } = mkRepo(true);
|
|
84
|
+
process.env.UP_FORCE_NO_GH = '1';
|
|
85
|
+
try {
|
|
86
|
+
const r = gh.startPhase({ cwd: dir, phase: 4, slug: 's', solo: true });
|
|
87
|
+
assert.notStrictEqual(r.mode, 'local');
|
|
88
|
+
assert.ok(r.worktree && fs.existsSync(r.worktree), 'solo deve criar worktree');
|
|
89
|
+
assert.strictEqual(r.transport, 'mcp');
|
|
90
|
+
} finally { delete process.env.UP_FORCE_NO_GH; }
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 5. recordIssue grava no git-map
|
|
94
|
+
t('recordIssue grava issue no git-map', () => {
|
|
95
|
+
const { dir } = mkRepo(true);
|
|
96
|
+
process.env.UP_FORCE_NO_GH = '1';
|
|
97
|
+
try {
|
|
98
|
+
gh.startPhase({ cwd: dir, phase: 5, slug: 'r' });
|
|
99
|
+
const r = gh.recordIssue({ cwd: dir, phase: 5, issue: 42, url: 'https://github.com/x/y/issues/42' });
|
|
100
|
+
assert.strictEqual(r.issue, 42);
|
|
101
|
+
const map = gh.readGitMap(dir);
|
|
102
|
+
assert.strictEqual(map.phases['5'].issue, 42);
|
|
103
|
+
assert.strictEqual(map.phases['5'].issue_url, 'https://github.com/x/y/issues/42');
|
|
104
|
+
} finally { delete process.env.UP_FORCE_NO_GH; }
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// 6. issueTransport puro
|
|
108
|
+
t('issueTransport: local->none; sem remote->none', () => {
|
|
109
|
+
const { dir } = mkRepo(false);
|
|
110
|
+
assert.strictEqual(gh.issueTransport(dir, true), 'none');
|
|
111
|
+
assert.strictEqual(gh.issueTransport(dir, false), 'none');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 7. finish mode local -> noop, done
|
|
115
|
+
t('finish local: noop status done', () => {
|
|
116
|
+
const { dir } = mkRepo(false);
|
|
117
|
+
gh.startPhase({ cwd: dir, phase: 7, slug: 'l', local: true });
|
|
118
|
+
const r = gh.finishPhase({ cwd: dir, phase: 7, mode: 'local' });
|
|
119
|
+
assert.strictEqual(r.status, 'done');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// 8. finish auto sem remote -> merge local fail-open
|
|
123
|
+
t('finish auto sem remote: merge local', () => {
|
|
124
|
+
const { dir } = mkRepo(false);
|
|
125
|
+
const s = gh.startPhase({ cwd: dir, phase: 8, slug: 'm' });
|
|
126
|
+
fs.writeFileSync(path.join(s.worktree, 'f.txt'), 'x');
|
|
127
|
+
sh('git add -A', s.worktree);
|
|
128
|
+
sh('git commit -qm w', s.worktree);
|
|
129
|
+
const r = gh.finishPhase({ cwd: dir, phase: 8, mode: 'auto' });
|
|
130
|
+
assert.strictEqual(r.status, 'merged');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// 9. finish auto, remote sem gh -> needs-mcp-pr + payload (push pro bare local funciona)
|
|
134
|
+
t('finish auto remote sem gh: sinaliza needs-mcp-pr', () => {
|
|
135
|
+
const { dir } = mkRepo(true);
|
|
136
|
+
const s = gh.startPhase({ cwd: dir, phase: 9, slug: 'p' });
|
|
137
|
+
fs.writeFileSync(path.join(s.worktree, 'f.txt'), 'x');
|
|
138
|
+
sh('git add -A', s.worktree);
|
|
139
|
+
sh('git commit -qm w', s.worktree);
|
|
140
|
+
process.env.UP_FORCE_NO_GH = '1';
|
|
141
|
+
try {
|
|
142
|
+
const r = gh.finishPhase({ cwd: dir, phase: 9, mode: 'auto' });
|
|
143
|
+
assert.strictEqual(r.action, 'needs-mcp-pr');
|
|
144
|
+
assert.ok(r.pr_payload && r.pr_payload.head, 'deve trazer pr_payload.head');
|
|
145
|
+
assert.strictEqual(r.pr_payload.head, 'up/fase-09-p');
|
|
146
|
+
} finally { delete process.env.UP_FORCE_NO_GH; }
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// 10. recordPr --merged grava pr e limpa worktree
|
|
150
|
+
t('recordPr merged: grava pr, status merged, limpa worktree', () => {
|
|
151
|
+
const { dir } = mkRepo(true);
|
|
152
|
+
const s = gh.startPhase({ cwd: dir, phase: 10, slug: 'pr' });
|
|
153
|
+
const wt = s.worktree;
|
|
154
|
+
const r = gh.recordPr({ cwd: dir, phase: 10, pr: 7, url: 'https://github.com/x/y/pull/7', merged: true });
|
|
155
|
+
assert.strictEqual(r.pr, 7);
|
|
156
|
+
assert.strictEqual(r.status, 'merged');
|
|
157
|
+
assert.ok(!fs.existsSync(wt), 'worktree deve ter sido removida');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
console.log(`\n${pass} passed, ${fail} failed`);
|
|
161
|
+
process.exit(fail ? 1 : 0);
|
package/up/bin/up-tools.cjs
CHANGED
|
@@ -352,11 +352,14 @@ function main() {
|
|
|
352
352
|
if (sub === 'start-phase') {
|
|
353
353
|
const phase = getFlag('--phase');
|
|
354
354
|
const slug = getFlag('--slug');
|
|
355
|
-
if (!phase) error('Usage: github start-phase --phase N --slug S [--
|
|
355
|
+
if (!phase) error('Usage: github start-phase --phase N --slug S [--local]');
|
|
356
|
+
// --local desliga GitHub (commit na branch atual). --solo NAO desliga mais
|
|
357
|
+
// (so afeta gate visual/merge no build.md); aceito por compat, ignorado aqui.
|
|
356
358
|
const result = github.startPhase({
|
|
357
359
|
cwd,
|
|
358
360
|
phase,
|
|
359
361
|
slug: slug || '',
|
|
362
|
+
local: hasFlag('--local'),
|
|
360
363
|
solo: hasFlag('--solo'),
|
|
361
364
|
});
|
|
362
365
|
output(result, raw, JSON.stringify(result));
|
|
@@ -364,14 +367,28 @@ function main() {
|
|
|
364
367
|
const phase = getFlag('--phase');
|
|
365
368
|
const mode = getFlag('--mode') || 'menu';
|
|
366
369
|
const strategy = getFlag('--strategy');
|
|
367
|
-
if (!phase) error('Usage: github finish-phase --phase N --mode menu|auto|
|
|
370
|
+
if (!phase) error('Usage: github finish-phase --phase N --mode menu|auto|local [--strategy squash|merge|rebase]');
|
|
368
371
|
const result = github.finishPhase({ cwd, phase, mode, strategy });
|
|
369
372
|
output(result, raw, JSON.stringify(result));
|
|
373
|
+
} else if (sub === 'record-issue') {
|
|
374
|
+
const phase = getFlag('--phase');
|
|
375
|
+
const issue = getFlag('--issue');
|
|
376
|
+
const url = getFlag('--url');
|
|
377
|
+
if (!phase || !issue) error('Usage: github record-issue --phase N --issue <num> [--url <url>]');
|
|
378
|
+
const result = github.recordIssue({ cwd, phase, issue, url });
|
|
379
|
+
output(result, raw, JSON.stringify(result));
|
|
380
|
+
} else if (sub === 'record-pr') {
|
|
381
|
+
const phase = getFlag('--phase');
|
|
382
|
+
const pr = getFlag('--pr');
|
|
383
|
+
const url = getFlag('--url');
|
|
384
|
+
if (!phase || !pr) error('Usage: github record-pr --phase N --pr <num> [--url <url>] [--merged]');
|
|
385
|
+
const result = github.recordPr({ cwd, phase, pr, url, merged: hasFlag('--merged') });
|
|
386
|
+
output(result, raw, JSON.stringify(result));
|
|
370
387
|
} else if (sub === 'status') {
|
|
371
388
|
const result = github.status({ cwd });
|
|
372
389
|
output(result, raw, JSON.stringify(result));
|
|
373
390
|
} else {
|
|
374
|
-
error('Unknown github subcommand. Available: start-phase, finish-phase, status');
|
|
391
|
+
error('Unknown github subcommand. Available: start-phase, finish-phase, record-issue, record-pr, status');
|
|
375
392
|
}
|
|
376
393
|
break;
|
|
377
394
|
}
|
package/up/commands/build.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: up:build
|
|
3
|
-
description: Use quando o usuario quer EXECUTAR um projeto ja planejado (existe .plano/PLAN-READY.md). Default GitHub-nativo por fase (worktree, issue, PR, merge). Flag --solo
|
|
4
|
-
argument-hint: "[--solo] [--
|
|
3
|
+
description: Use quando o usuario quer EXECUTAR um projeto ja planejado (existe .plano/PLAN-READY.md). Default GitHub-nativo por fase (worktree, issue, PR, merge) via gh OU MCP. Flag --solo = autonomo total mantendo GitHub (sem menu, sem gate visual); --auto pula so o menu; --local pula o GitHub (commit atomico na branch atual); --board espelha no Multica.
|
|
4
|
+
argument-hint: "[--solo] [--auto] [--local] [--board]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -40,11 +40,12 @@ Exemplo: planejou em Claude Code (`/up:plan "X"`), agora roda em OpenCode (`/up-
|
|
|
40
40
|
<context>
|
|
41
41
|
$ARGUMENTS
|
|
42
42
|
|
|
43
|
-
**Flags
|
|
44
|
-
- (sem flag) = **GitHub-nativo, o DEFAULT** (`config.github_native=true`): cada fase abre worktree + branch `up/fase-NN-slug` + issue, executa, sobe o dev server pra teste visual (se houver UI) e oferece o menu de fim de fase.
|
|
45
|
-
- `--solo` - **
|
|
43
|
+
**Flags** (GitHub e a interacao humana sao eixos SEPARADOS; as flags so mexem na interacao):
|
|
44
|
+
- (sem flag) = **GitHub-nativo, o DEFAULT** (`config.github_native=true`): cada fase abre worktree + branch `up/fase-NN-slug` + issue (via `gh` OU MCP do GitHub), executa, sobe o dev server pra teste visual (se houver UI) e oferece o menu de fim de fase.
|
|
45
|
+
- `--solo` - **autonomo total**. NAO desliga o GitHub: cria worktree/branch/issue/PR e auto-mergeia, SEM menu e SEM gate visual. Pra loop/headless.
|
|
46
|
+
- `--auto` - pula so o menu de fim de fase (merge automatico). Mantem GitHub. O teste visual ainda roda se `require_visual_test=true`.
|
|
47
|
+
- `--local` - **ESCAPE HATCH sem GitHub**. Commit atomico na branch ATUAL, zero worktree/issue/PR/board. (Mesmo que `/up:rapido`.) Unico jeito de pular o GitHub.
|
|
46
48
|
- `--board` - espelha o progresso das fases no Multica (board opt-in, batched, fail-open).
|
|
47
|
-
- `--auto` - pula o menu de fim de fase (merge automatico). O teste visual ainda roda se `require_visual_test=true`.
|
|
48
49
|
|
|
49
50
|
O comando le o resto de `.plano/PLAN-READY.md`.
|
|
50
51
|
</context>
|
|
@@ -75,23 +76,23 @@ Se algo falta: alertar e oferecer planejamento local OU abortar.
|
|
|
75
76
|
|
|
76
77
|
**Sem model routing:** O runtime decide o modelo.
|
|
77
78
|
|
|
78
|
-
**Parsear flags primeiro:** extrair `--solo`/`--board
|
|
79
|
+
**Parsear flags primeiro:** extrair `--solo`/`--auto`/`--local`/`--board`. Sem flag = GitHub-nativo (default).
|
|
79
80
|
|
|
80
81
|
**Modo de orquestracao (resolver antes de comecar):**
|
|
81
|
-
- DEFAULT (sem `--
|
|
82
|
-
- `--
|
|
82
|
+
- DEFAULT (sem `--local`): GitHub-nativo. Por fase: `up-tools.cjs github start-phase` (worktree + branch + issue via gh OU MCP, fail-open) -> executa as waves -> gate -> teste visual pre-merge (se UI) -> `github finish-phase` (PR -> merge squash -> cleanup) conforme o menu. `--auto` pula o menu (mantem GitHub). `--solo` pula menu E gate visual (autonomo total, mantem GitHub). `--board` adiciona `multica init/sync` (batched).
|
|
83
|
+
- `--local`: executa local, commits atomicos na branch atual. Sem worktree/issue/PR/board.
|
|
83
84
|
|
|
84
85
|
**Execute the build workflow from @~/.claude/up/workflows/build.md end-to-end.**
|
|
85
86
|
|
|
86
87
|
Pipeline por fase:
|
|
87
88
|
1. Validacao light
|
|
88
|
-
2. `github start-phase` (worktree + issue; pulado em `--
|
|
89
|
+
2. `github start-phase` (worktree + issue via gh OU MCP; pulado so em `--local`)
|
|
89
90
|
3. Execucao em WAVES PARALELAS: os planos da mesma wave rodam ao mesmo tempo (varios `up-executor`); waves em sequencia. Re-plan local se algum plano ficar inviavel.
|
|
90
91
|
4. up-verificador emite VERIFICATION.md com evidencia fresca por tipo (TDD-por-tipo: red-green / visual / smoke)
|
|
91
92
|
5. **GATE deterministico** `approvals.log` (governance.md, gate-only; exige `evidence=`)
|
|
92
93
|
6. up-revisor (two-stage: spec-compliance cetico + code-quality) alimenta o gate
|
|
93
94
|
7. **Teste visual pre-merge** (fase de UI e `require_visual_test=true`): sobe o dev server na worktree, "testar primeiro ou pode mergear?", loop de ajuste ate aprovar
|
|
94
|
-
8. **Menu de fim de fase** (GitHub-nativo, fora `--auto`): merge local / abrir PR / deixa a branch / descarta. Em `--
|
|
95
|
+
8. **Menu de fim de fase** (GitHub-nativo, fora `--auto`/`--solo`): merge local / abrir PR / deixa a branch / descarta. Autonomo (`--solo`/`--auto`) auto-mergeia sem menu. Em `--local`, ja committou na branch atual
|
|
95
96
|
9. Reassessment do roadmap antes da proxima fase
|
|
96
97
|
|
|
97
98
|
**Re-plan local permitido (max 1 round por fase):**
|
|
@@ -102,9 +103,9 @@ Se ficar inviavel, o up-planejador LOCAL refaz a fase. Registrar em `.plano/gove
|
|
|
102
103
|
|
|
103
104
|
<success_criteria>
|
|
104
105
|
- [ ] PLAN-READY.md validado
|
|
105
|
-
- [ ] Flags parseadas (default = GitHub-nativo; --solo = escape)
|
|
106
|
+
- [ ] Flags parseadas (default = GitHub-nativo; --solo/--auto = autonomia mantendo GitHub; --local = escape sem GitHub)
|
|
106
107
|
- [ ] Build rodou todas as fases; planos da mesma wave em paralelo
|
|
107
108
|
- [ ] GATE approvals.log respeitado (deterministico, com evidence= por tipo)
|
|
108
|
-
- [ ] Teste visual pre-merge em fase de UI (require_visual_test)
|
|
109
|
-
- [ ] Fechamento por fase: menu (GitHub-nativo) ou commit na branch (--
|
|
109
|
+
- [ ] Teste visual pre-merge em fase de UI (require_visual_test; pulado em --solo)
|
|
110
|
+
- [ ] Fechamento por fase: menu (GitHub-nativo), auto-merge (--solo/--auto) ou commit na branch (--local)
|
|
110
111
|
</success_criteria>
|
|
@@ -38,6 +38,47 @@ Classifique a tarefa com o `classify-task` do `up-tools.cjs` (tiers: `simple` /
|
|
|
38
38
|
| **Pequena** (1 subsistema, 1 escolha de design) | 1 pergunta via AskUserQuestion (a decisao-chave) + design em 3 frases. Aprova e segue. |
|
|
39
39
|
| **Media / Grande** (multi-subsistema, toca schema/API/auth) | Brainstorm full. |
|
|
40
40
|
|
|
41
|
+
O `classify-task` define o PISO (minimo garantido). O usuario sempre pode SUBIR ou DESCER manualmente (override abaixo). Nunca diminua a profundidade por conta propria; so o usuario rebaixa.
|
|
42
|
+
|
|
43
|
+
## Override de profundidade (controle do usuario)
|
|
44
|
+
|
|
45
|
+
O tier automatico e so o default. O usuario manda na profundidade:
|
|
46
|
+
|
|
47
|
+
| Sinal do usuario | Efeito |
|
|
48
|
+
|------------------|--------|
|
|
49
|
+
| Palavras "a fundo", "detalhado", "explorar", "pensar junto", "completo", "caprichado" OU flag `--deep` | Sobe pra **full** (ou exploracao, se for ideia crua), ignora o score baixo. |
|
|
50
|
+
| Palavras "rapido", "simples", "so faz", "sem perguntas" OU flag `--quick` | Desce pra **trivial** (0 perguntas), mesmo que o score ache complexo. O HARD-GATE continua: anuncia antes de agir. |
|
|
51
|
+
| Nada declarado | Usa o tier do `classify-task` (piso automatico). |
|
|
52
|
+
|
|
53
|
+
Pressa nunca remove o gate; muda so quantas perguntas.
|
|
54
|
+
|
|
55
|
+
## Modo exploracao (ideia crua, acima do full)
|
|
56
|
+
|
|
57
|
+
Quando o usuario tem so uma SEMENTE e quer DESCOBRIR o que e (nao validar um design ja pronto). Gatilhos: "tenho uma ideia", "to pensando em", "e se", "me ajuda a pensar", "queria explorar", `--deep` numa ideia vaga.
|
|
58
|
+
|
|
59
|
+
Diferenca do full: o full valida um design que o usuario ja tem na cabeca; a exploracao ABRE o espaco antes de fechar.
|
|
60
|
+
|
|
61
|
+
1. **Nao pule pra solucao.** Primeiro entenda o PORQUE: que problema/desejo move a ideia, pra quem, por que agora.
|
|
62
|
+
2. **Abra alternativas radicais.** Ofereca 3-5 direcoes bem diferentes (nao variacoes da mesma), incluindo uma obvia, uma ousada e uma "e se fizesse o oposto".
|
|
63
|
+
3. **Provoque com "e se".** Tensione premissas: "e se nao precisasse de X?", "e se o publico fosse outro?", "qual a versao 10x menor que ja entrega valor?".
|
|
64
|
+
4. **Uma pergunta por vez**, multipla escolha quando der. Vai estreitando do amplo pro especifico.
|
|
65
|
+
5. **Destile** a ideia num paragrafo claro: o que e, pra quem, por que, o diferencial. Confirme com o usuario.
|
|
66
|
+
6. So ENTAO transicione pro design (full) ou direto pro `BRIEFING.md`, conforme o tamanho do que emergiu.
|
|
67
|
+
|
|
68
|
+
A exploracao termina numa ideia destilada e aprovada, que vira BRIEFING. Continua valendo o estado terminal: codigo so depois de `/up:plan`.
|
|
69
|
+
|
|
70
|
+
## Trilha NAO-codigo (documento, relatorio, analise, conteudo, plano de negocio)
|
|
71
|
+
|
|
72
|
+
Nem todo trabalho e software. Se a tarefa e produzir um ARTEFATO que nao e codigo (documento, proposta, relatorio, analise, roteiro, estrategia, pesquisa), o fluxo muda:
|
|
73
|
+
|
|
74
|
+
- **Brainstorm igual** (escala por tier + override + modo exploracao valem). A diferenca e o que vem depois.
|
|
75
|
+
- **NAO ha `/up:plan` -> `/up:build` -> worktree/PR.** Sem fases de software, sem TDD-por-tipo, sem GitHub-nativo. Isso e cerimonia de codigo.
|
|
76
|
+
- **Apos o design/escopo aprovado:** produza o artefato direto (escreva o documento/analise). Para conteudo do Jonathan (carrossel, aula, post), use as skills dedicadas (`carrossel-*`, `aula-generator`, etc) quando aplicaveis.
|
|
77
|
+
- **Verificacao por adequacao, nao por teste:** a prova e "o artefato existe, cobre o que foi pedido, sem placeholder/TBD, e bate com o briefing". Aplica a Lei de Ferro adaptada: nao diga "pronto" sem reler o artefato e conferir contra o escopo combinado.
|
|
78
|
+
- **Persistencia leve:** salve o artefato no local certo (vault, pasta do projeto) e registre 1 linha no STATE.md se houver `.plano/`. Sem roadmap.
|
|
79
|
+
|
|
80
|
+
Como detectar: pedido fala em "documento, proposta, relatorio, analise, texto, roteiro, estrategia, plano, pesquisa" e NAO em codigo/app/feature/sistema. Na duvida, pergunte: "isso e pra virar codigo ou e um documento/analise?".
|
|
81
|
+
|
|
41
82
|
## Brainstorm full (media/grande)
|
|
42
83
|
|
|
43
84
|
1. **Explore o contexto** (arquivos, docs, commits recentes).
|
|
@@ -15,13 +15,17 @@ O UP ativa por contexto. Nao precisa decorar comando: a skill certa dispara pelo
|
|
|
15
15
|
|
|
16
16
|
**Passo ZERO de todo trabalho:** invocar `up-brainstorm`. Profundidade escala por tamanho (trivial = 0 perguntas; grande = design por secao). Nada de implementar antes de design aprovado.
|
|
17
17
|
|
|
18
|
-
**Fluxo obrigatorio para PROJETO ou FEATURE (nao-trivial):** brainstorm -> `/up:plan` (gera `.plano/PLAN-READY.md`) -> `/up:build` (executa). Depois do design aprovado voce NAO comeca a codar nem a criar fundacao/scaffold direto: registra os artefatos (BRIEFING/PROJECT) e PARA, entregando o handoff para `/up:plan`. Planejar e um passo separado e obrigatorio, nao opcional. Pular o plan so e permitido em tarefa pontual declarada via `/up:rapido`. Se nao tem `.plano/PLAN-READY.md`, voce ainda nao pode buildar.
|
|
18
|
+
**Fluxo obrigatorio para PROJETO ou FEATURE de codigo (nao-trivial):** brainstorm -> `/up:plan` (gera `.plano/PLAN-READY.md`) -> `/up:build` (executa). Depois do design aprovado voce NAO comeca a codar nem a criar fundacao/scaffold direto: registra os artefatos (BRIEFING/PROJECT) e PARA, entregando o handoff para `/up:plan`. Planejar e um passo separado e obrigatorio, nao opcional. Pular o plan so e permitido em tarefa pontual declarada via `/up:rapido`. Se nao tem `.plano/PLAN-READY.md`, voce ainda nao pode buildar.
|
|
19
|
+
|
|
20
|
+
**Tarefa NAO-codigo** (documento, relatorio, analise, conteudo, estrategia): brainstorma igual, mas NAO passa por `/up:plan`/`/up:build`/worktree. Apos o escopo aprovado, produz o artefato direto e verifica por adequacao (cobre o pedido, sem TBD), nao por teste. Detalhe na skill `up-brainstorm`.
|
|
21
|
+
|
|
22
|
+
**Profundidade sob controle do usuario:** o tier automatico e so o piso. "A fundo/detalhado/explorar" ou `--deep` sobe pra brainstorm completo (ou modo exploracao pra ideia crua); "rapido/simples" ou `--quick` desce pra 0 perguntas. O usuario manda na profundidade; voce nunca a reduz sozinho.
|
|
19
23
|
|
|
20
24
|
**Lei de Ferro:** evidencia fresca antes de afirmar pronto. Nunca diga "Pronto" ou "Perfeito" sem o comando de prova rodado NESTA mensagem. Detalhe em `up-verificar-antes-de-concluir`.
|
|
21
25
|
|
|
22
26
|
**Prova por tipo:** logica/bugfix = teste red-green; UI/CSS = captura visual; glue/integracao = smoke-test. Veja `up-tdd`.
|
|
23
27
|
|
|
24
|
-
**GitHub-nativo e o padrao
|
|
28
|
+
**GitHub-nativo e o padrao** (worktree -> issue -> PR -> merge), via `gh` OU MCP do GitHub, menu de 4 opcoes no fim. `--auto` pula o menu; `--solo` e autonomo total (mantem GitHub, sem menu nem gate visual). Pra pular o GitHub de propósito (commit local puro): `--local` no build ou `/up:rapido`. Atencao: `--solo` NAO desliga mais o GitHub.
|
|
25
29
|
|
|
26
30
|
**Persistencia:** tudo vive em `.plano/` e sobrevive a `/clear`. Leia `.plano/STATE.md` antes de assumir contexto perdido.
|
|
27
31
|
|
package/up/workflows/build.md
CHANGED
|
@@ -52,27 +52,44 @@ O LLM tende a colapsar passos (mesmo agente executa + verifica). Isso e PROIBIDO
|
|
|
52
52
|
e um `Agent()` SEPARADO. O enforcement e o GATE deterministico do `approvals.log`
|
|
53
53
|
(ver `@~/.claude/up/workflows/governance.md`), nao uma piramide de supervisores.
|
|
54
54
|
|
|
55
|
-
**GitHub-nativo e o DEFAULT (v2):**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
branch
|
|
55
|
+
**GitHub-nativo e o DEFAULT (v2): DOIS EIXOS SEPARADOS:**
|
|
56
|
+
|
|
57
|
+
O GitHub (worktree/branch/issue/PR) e a INTERACAO humana (menu/gate visual/merge) sao eixos
|
|
58
|
+
independentes. As flags mexem so na interacao; o GitHub fica ligado sempre que da.
|
|
59
|
+
|
|
60
|
+
- **Eixo GitHub:** LIGADO sempre que ha remote E (`gh` autenticado OU MCP do GitHub conectado).
|
|
61
|
+
Worktree+branch sao git local e SEMPRE acontecem (offline-ok). Issue/PR usam o transporte:
|
|
62
|
+
`gh` (CLI cria direto) ou `mcp` (o workflow cria via `mcp__...github__*` e grava com
|
|
63
|
+
`record-issue`/`record-pr`). DESLIGA so com `--local` ou `config.github_native=false`.
|
|
64
|
+
- `--local`: ESCAPE HATCH sem GitHub. Commit atomico na branch ATUAL. Zero worktree/issue/PR/rede.
|
|
65
|
+
(Mesmo comportamento de `/up:rapido`.) **Este e o unico jeito de pular o GitHub no build.**
|
|
66
|
+
- `--solo`: NAO desliga o GitHub. Solo = autonomo total: GitHub completo (branch/worktree/issue/PR
|
|
67
|
+
+ auto-merge), SEM menu e SEM gate visual. Pra loop/headless onde ninguem aprova nada.
|
|
68
|
+
- `--auto`: pula o MENU de fechamento (auto-merge), MAS o gate visual (3.8.0) ainda roda se
|
|
69
|
+
`require_visual_test=true`. Mantem GitHub ligado.
|
|
62
70
|
- **Teste visual antes do merge (`require_visual_test`, default true):** fase de UI sobe dev server e exige
|
|
63
|
-
o dono aprovar na tela ANTES do merge (3.8.0).
|
|
64
|
-
|
|
65
|
-
cleanup). MAS o teste visual (3.8.0) ainda roda se `require_visual_test=true` (so `false` deixa o --auto
|
|
66
|
-
mergear UI sem aprovacao). So faz sentido com github_native ligado.
|
|
71
|
+
o dono aprovar na tela ANTES do merge (3.8.0). `--solo` pula sempre; `--auto` so pula com `require_visual_test=false`.
|
|
72
|
+
Projeto em PRODUCAO: deixe ligado e nao use `--solo` (nada sobe sem voce ver).
|
|
67
73
|
- `--board`: espelha status no Multica (espelho de board OPT-IN, BATCHED no fim da onda/fase). NAO ha
|
|
68
74
|
stream ao vivo no fluxo local: o board mostra so o status (`todo -> in_progress -> in_review -> done /
|
|
69
75
|
blocked`), nunca cada tool_use. Chamadas via `up-tools.cjs multica {init|sync|board}` (que usa
|
|
70
76
|
`multica.cjs`, deteccao `uname -s` Mac->`ssh server-ecoup`, FAIL-OPEN: se `multica` indisponivel, avisa
|
|
71
77
|
e segue sem board, nunca crasha). So roda quando `--board` ligado.
|
|
72
78
|
|
|
73
|
-
**
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
**Resumo das flags** (GitHub = artefatos; demais = interacao):
|
|
80
|
+
|
|
81
|
+
| Flag | GitHub | Menu fim | Gate visual | Merge |
|
|
82
|
+
|------|:---:|:---:|:---:|---|
|
|
83
|
+
| (nenhum) | SIM | SIM (4 opcoes) | SIM | conforme menu |
|
|
84
|
+
| `--auto` | SIM | NAO | SIM (a menos require_visual_test=false) | auto squash |
|
|
85
|
+
| `--solo` | SIM | NAO | NAO (pula sempre) | auto squash |
|
|
86
|
+
| `--local` | NAO | NAO | NAO | commit na branch atual |
|
|
87
|
+
|
|
88
|
+
**FAIL-OPEN universal:** `start-phase`/`finish-phase` detectam remote + transporte (`gh`/`mcp`/`none`).
|
|
89
|
+
Sem remote, degradam para git local (worktree local + merge local; issue/PR = null) com aviso, NUNCA
|
|
90
|
+
crasham. `git worktree` e sempre local e funciona offline. Sem `gh` mas com MCP, o transporte e `mcp`:
|
|
91
|
+
worktree/branch/push acontecem no `.cjs` e o workflow cria issue/PR via MCP. Com `--local` nao ha nem
|
|
92
|
+
worktree (commit direto na branch atual).
|
|
76
93
|
|
|
77
94
|
**Onde o estado vive:** `git-map.json` e canonico no working dir PRINCIPAL (`.plano/git-map.json`). O `.plano/`
|
|
78
95
|
de cada fase viaja na branch da fase (worktree) e volta pra main no merge. STATE.md permanece a fonte humana
|
|
@@ -175,25 +192,31 @@ Projeto planejado em {runtime}.
|
|
|
175
192
|
Resumo: {N} fases, {M} planos. Planning confidence: {X}/100.
|
|
176
193
|
Pendencias conhecidas: {de PENDING.md}.
|
|
177
194
|
|
|
178
|
-
Modo git: {GITHUB_MODE} (GitHub-nativo e o default; --
|
|
195
|
+
Modo git: {GITHUB_MODE} (GitHub-nativo e o default; --local desliga; --solo/--auto sao autonomia, nao desligam)
|
|
179
196
|
```
|
|
180
197
|
|
|
181
|
-
Resolver `GITHUB_MODE` antes do banner
|
|
198
|
+
Resolver `GITHUB_MODE` antes do banner. `$LOCAL`/`$SOLO`/`$AUTO`/`$BOARD_FLAG` vem das flags da
|
|
199
|
+
invocacao. **`--solo` NAO desliga o GitHub** (so `--local` desliga):
|
|
182
200
|
|
|
183
201
|
```bash
|
|
184
|
-
# --
|
|
185
|
-
if [ "$
|
|
202
|
+
# So --local (ou config github_native=false) desliga o GitHub.
|
|
203
|
+
if [ "$LOCAL" = "true" ]; then
|
|
186
204
|
GITHUB_NATIVE=false
|
|
187
205
|
else
|
|
188
206
|
GITHUB_NATIVE=$(node "$HOME/.claude/up/bin/up-tools.cjs" config get github_native --raw 2>/dev/null)
|
|
189
207
|
[ -z "$GITHUB_NATIVE" ] && GITHUB_NATIVE=true # default TRUE
|
|
190
208
|
fi
|
|
191
209
|
|
|
210
|
+
# --solo e --auto sao AUTONOMIA (sem menu). --solo tambem pula o gate visual.
|
|
211
|
+
AUTONOMO=false
|
|
212
|
+
{ [ "$SOLO" = "true" ] || [ "$AUTO" = "true" ]; } && AUTONOMO=true
|
|
213
|
+
|
|
192
214
|
if [ "$GITHUB_NATIVE" = "true" ]; then
|
|
193
215
|
GITHUB_MODE="GitHub-nativo (worktree + issue + PR/menu por fase)"
|
|
194
|
-
[ "$AUTO" = "true" ]
|
|
216
|
+
[ "$AUTO" = "true" ] && GITHUB_MODE="GitHub-nativo --auto (PR + merge squash; gate visual ainda roda)"
|
|
217
|
+
[ "$SOLO" = "true" ] && GITHUB_MODE="GitHub-nativo --solo (autonomo total: PR + merge, SEM gate visual)"
|
|
195
218
|
else
|
|
196
|
-
GITHUB_MODE="--
|
|
219
|
+
GITHUB_MODE="--local (commit atomico na branch atual, sem worktree/issue/PR)"
|
|
197
220
|
fi
|
|
198
221
|
|
|
199
222
|
# --board liga o espelho Multica (OPT-IN). So tem efeito se passado explicitamente.
|
|
@@ -231,27 +254,37 @@ Para cada fase em ROADMAP.md (em ordem):
|
|
|
231
254
|
|
|
232
255
|
### 3.0 Abrir a fase (GitHub-nativo - DEFAULT)
|
|
233
256
|
|
|
234
|
-
A menos que `--
|
|
257
|
+
A menos que `--local` (ou `github_native=false`), abrir worktree + branch + issue ANTES de executar a fase.
|
|
235
258
|
|
|
236
259
|
```bash
|
|
237
260
|
PHASE_SLUG=$(node "$HOME/.claude/up/bin/up-tools.cjs" slug "{phase_name}" --raw)
|
|
238
261
|
|
|
239
262
|
if [ "$GITHUB_NATIVE" = "true" ]; then
|
|
240
|
-
# Cria worktree + branch up/fase-NN-slug
|
|
241
|
-
#
|
|
263
|
+
# Cria worktree + branch up/fase-NN-slug SEMPRE (git local). Issue por transporte:
|
|
264
|
+
# gh -> issue criada aqui; mcp -> retorna pending.issue (workflow cria via MCP); none -> sem remote.
|
|
242
265
|
START=$(node "$HOME/.claude/up/bin/up-tools.cjs" github start-phase \
|
|
243
266
|
--phase {phase_number} --slug "$PHASE_SLUG" --raw)
|
|
244
267
|
if [[ "$START" == @file:* ]]; then START=$(cat "${START#@file:}"); fi
|
|
245
|
-
WORKTREE=$(echo "$START"
|
|
246
|
-
BRANCH=$(echo "$START"
|
|
247
|
-
ISSUE=$(echo "$START"
|
|
248
|
-
echo "
|
|
268
|
+
WORKTREE=$(echo "$START" | grep -oE '"worktree"[^,}]*' | sed 's/.*: *"//;s/"//')
|
|
269
|
+
BRANCH=$(echo "$START" | grep -oE '"branch"[^,}]*' | sed 's/.*: *"//;s/"//')
|
|
270
|
+
ISSUE=$(echo "$START" | grep -oE '"issue"[^,}]*' | sed 's/.*: *//')
|
|
271
|
+
TRANSPORT=$(echo "$START" | grep -oE '"transport"[^,}]*' | sed 's/.*: *"//;s/"//')
|
|
272
|
+
echo "Fase {phase_number}: branch=$BRANCH worktree=$WORKTREE issue=${ISSUE:-null} transport=$TRANSPORT"
|
|
249
273
|
else
|
|
250
|
-
# --
|
|
251
|
-
WORKTREE="$(pwd)"; BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"; ISSUE=""
|
|
274
|
+
# --local: sem worktree/issue. Trabalho acontece na branch atual.
|
|
275
|
+
WORKTREE="$(pwd)"; BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"; ISSUE=""; TRANSPORT=none
|
|
252
276
|
fi
|
|
253
277
|
```
|
|
254
278
|
|
|
279
|
+
**Issue via MCP (quando `transport=mcp`):** sem `gh` mas com remote, `start-phase` ja criou worktree+branch
|
|
280
|
+
e devolveu `pending.issue {title, body}` no JSON. Se voce (orquestrador) TEM as tools `mcp__...github__*`,
|
|
281
|
+
crie a issue agora e grave o numero de volta:
|
|
282
|
+
|
|
283
|
+
> Ao detectar `transport=mcp` no `$START`: extraia `pending.issue.title`/`pending.issue.body`, chame
|
|
284
|
+
> `mcp__plugin_github_github__issue_write` (create) no repo do remote, pegue o numero retornado e rode
|
|
285
|
+
> `node "$HOME/.claude/up/bin/up-tools.cjs" github record-issue --phase {phase_number} --issue <num> --url <url>`.
|
|
286
|
+
> Se voce NAO tem MCP do GitHub: siga sem issue (worktree/branch ja existem; PR pode degradar pra merge local no fim).
|
|
287
|
+
|
|
255
288
|
**Entrar na worktree (so se github_native):** o trabalho da fase (executor, commits) acontece
|
|
256
289
|
DENTRO de `$WORKTREE`. Preferir a tool nativa do harness **EnterWorktree** apontando para `$WORKTREE`; se
|
|
257
290
|
indisponivel, a worktree ja foi criada por `start-phase` (basta usar `--cwd "$WORKTREE"` nos comandos
|
|
@@ -259,7 +292,7 @@ indisponivel, a worktree ja foi criada por `start-phase` (basta usar `--cwd "$WO
|
|
|
259
292
|
`git-map.json` permanece canonico no working dir principal. Ao terminar a fase usar **ExitWorktree** (ou
|
|
260
293
|
voltar `cd` para o repo principal) antes de atualizar `git-map.json` na main.
|
|
261
294
|
|
|
262
|
-
> Em `--
|
|
295
|
+
> Em `--local`, IGNORAR EnterWorktree/ExitWorktree: tudo na branch atual. Em `--solo` HA worktree (solo nao desliga GitHub).
|
|
263
296
|
|
|
264
297
|
**Multica: marcar a fase em execucao (so se `--board`, 1 chamada na ENTRADA da fase).**
|
|
265
298
|
Uma transicao por fase (nao por microtransicao): status `in_progress` + metadata `gh_issue`/`branch`.
|
|
@@ -281,7 +314,7 @@ NAO existe mais "1 plano por fase" (era a regressao do `head -1`): descobrimos T
|
|
|
281
314
|
agrupamento por wave do disco.
|
|
282
315
|
|
|
283
316
|
Os planos vivem DENTRO de `$WORKTREE` (a `.plano/` da fase viaja na branch da fase). Por isso TODA chamada
|
|
284
|
-
de descoberta usa `--cwd "$WORKTREE"` (em `--
|
|
317
|
+
de descoberta usa `--cwd "$WORKTREE"` (em `--local`, `$WORKTREE` = repo principal, entao funciona igual).
|
|
285
318
|
|
|
286
319
|
```bash
|
|
287
320
|
# (a) Metadados da fase + flag de paralelizacao (config). Trata @file: (saida > 50KB).
|
|
@@ -635,7 +668,7 @@ fi
|
|
|
635
668
|
|
|
636
669
|
So apos o GATE aprovar (APPROVE ou forced approval registrado).
|
|
637
670
|
|
|
638
|
-
**Caso `--
|
|
671
|
+
**Caso `--local` (github_native=false):** nada a fazer aqui. Tudo ja foi committado atomicamente na branch
|
|
639
672
|
atual. Seguir direto para 3.9. (Sem worktree, sem teste-visual-gate, sem merge.)
|
|
640
673
|
|
|
641
674
|
#### 3.8.0 Checkpoint de teste visual (PRE-MERGE) - so GitHub-nativo
|
|
@@ -649,9 +682,10 @@ REQUIRE_VISUAL=$(node "$HOME/.claude/up/bin/up-tools.cjs" config get require_vis
|
|
|
649
682
|
HAS_DEV=$(node -e "try{const s=require('./package.json').scripts||{};process.stdout.write((s.dev||s.start||s.serve)?'1':'')}catch(e){}" 2>/dev/null)
|
|
650
683
|
```
|
|
651
684
|
|
|
652
|
-
**Aplica o gate quando:** (HAS_UI ou HAS_DEV) E NAO (`--auto` E REQUIRE_VISUAL=false).
|
|
653
|
-
Ou seja: por padrao SEMPRE pede aprovacao visual antes do merge em fase de UI.
|
|
654
|
-
`--auto` +
|
|
685
|
+
**Aplica o gate quando:** (HAS_UI ou HAS_DEV) E NAO `--solo` E NAO (`--auto` E REQUIRE_VISUAL=false).
|
|
686
|
+
Ou seja: por padrao SEMPRE pede aprovacao visual antes do merge em fase de UI. `--solo` pula SEMPRE
|
|
687
|
+
(autonomo total). Pra pular sem solo (CI/yolo): `--auto` + `require_visual_test=false`. Projeto em
|
|
688
|
+
PRODUCAO: deixe o default (true) e nao use `--solo`.
|
|
655
689
|
|
|
656
690
|
Se aplica:
|
|
657
691
|
|
|
@@ -706,32 +740,44 @@ pkill -f "npm run dev" 2>/dev/null || true; pkill -f "npm start" 2>/dev/null ||
|
|
|
706
740
|
A escolha do checkpoint define a acao de fechamento (3.8.1): "Pode mergear"/"Aprovado" -> MERGE;
|
|
707
741
|
"Deixa a branch" -> nao mergeia; "Descarta" -> remove sem merge.
|
|
708
742
|
|
|
709
|
-
**Fase SEM UI** (infra/schema/backend puro) ou `--auto` com `require_visual_test=false`: pula 3.8.0.
|
|
743
|
+
**Fase SEM UI** (infra/schema/backend puro), `--solo`, ou `--auto` com `require_visual_test=false`: pula 3.8.0.
|
|
710
744
|
Nesse caso, GitHub-nativo interativo ainda apresenta o mesmo AskUserQuestion de 4 opcoes (sem o passo do
|
|
711
|
-
dev server) pra o dono decidir merge/PR/deixa/descarta. `--auto`
|
|
745
|
+
dev server) pra o dono decidir merge/PR/deixa/descarta. **Autonomo (`--solo`/`--auto`)** fecha direto sem
|
|
746
|
+
menu: `ESCOLHA=mergear`.
|
|
712
747
|
|
|
713
748
|
#### 3.8.1 Merge e avancar
|
|
714
749
|
|
|
715
750
|
Sair da worktree (**ExitWorktree** ou `cd` de volta ao repo principal) para que `finish-phase` opere e
|
|
716
|
-
atualize `git-map.json` na main.
|
|
717
|
-
(`--mode menu|auto|
|
|
751
|
+
atualize `git-map.json` na main. **Autonomo (`--solo`/`--auto`):** sem menu, `ESCOLHA=mergear`.
|
|
752
|
+
Mapear a escolha de 3.8.0 para `finish-phase` (`--mode menu|auto|local`):
|
|
718
753
|
|
|
719
754
|
```bash
|
|
755
|
+
[ "$AUTONOMO" = "true" ] && [ -z "$ESCOLHA" ] && ESCOLHA=mergear
|
|
720
756
|
case "$ESCOLHA" in
|
|
721
|
-
# Pode mergear / Aprovado: finish-phase --mode auto
|
|
722
|
-
#
|
|
723
|
-
mergear|aprovado) node "$HOME/.claude/up/bin/up-tools.cjs" github finish-phase --phase {phase_number} --mode auto --strategy squash ;;
|
|
757
|
+
# Pode mergear / Aprovado: finish-phase --mode auto. Transporte gh: pr create (Closes #N) -> merge -> cleanup.
|
|
758
|
+
# Transporte mcp (sem gh): faz push e retorna action 'needs-mcp-pr' (ver abaixo). none: merge LOCAL + cleanup.
|
|
759
|
+
mergear|aprovado) FIN=$(node "$HOME/.claude/up/bin/up-tools.cjs" github finish-phase --phase {phase_number} --mode auto --strategy squash --raw) ;;
|
|
724
760
|
# Deixa a branch: nao mergeia; worktree+branch vivos. menu so atualiza git-map.json (status=in_review).
|
|
725
|
-
deixa) node "$HOME/.claude/up/bin/up-tools.cjs" github finish-phase --phase {phase_number} --mode menu ;;
|
|
761
|
+
deixa) FIN=$(node "$HOME/.claude/up/bin/up-tools.cjs" github finish-phase --phase {phase_number} --mode menu --raw) ;;
|
|
726
762
|
# Descarta: orquestrador remove worktree + branch (sem merge); reflete em git-map.json via status=cancelled.
|
|
727
|
-
descarta) echo "Descartando fase {phase_number}: remover worktree + branch (sem merge)." ;;
|
|
763
|
+
descarta) echo "Descartando fase {phase_number}: remover worktree + branch (sem merge)." ; FIN="" ;;
|
|
728
764
|
esac
|
|
729
765
|
```
|
|
730
766
|
|
|
731
|
-
`
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
767
|
+
**PR via MCP (quando `FIN` traz `"action":"needs-mcp-pr"`):** sem `gh`, o `finish-phase` ja deu `git push` da
|
|
768
|
+
branch e devolveu `pr_payload {base, head, title, body, issue, strategy}`. Se voce TEM `mcp__...github__*`:
|
|
769
|
+
|
|
770
|
+
> Chame `mcp__plugin_github_github__create_pull_request` (base/head/title/body do `pr_payload`), depois
|
|
771
|
+
> `mcp__plugin_github_github__merge_pull_request` (merge_method=squash). Pegue o numero do PR e rode:
|
|
772
|
+
> `node "$HOME/.claude/up/bin/up-tools.cjs" github record-pr --phase {phase_number} --pr <num> --url <url> --merged`
|
|
773
|
+
> (`--merged` grava o PR, marca a fase merged e limpa worktree+branch). Se NAO tem MCP: rode
|
|
774
|
+
> `github finish-phase --phase {phase_number} --mode local` como degradacao (a branch ja foi pushada; faca o
|
|
775
|
+
> merge no GitHub manualmente depois) OU avise o dono.
|
|
776
|
+
|
|
777
|
+
`finish-phase --mode auto` (transporte gh) faz: gh pr create (body com `Closes #<issue>`) -> merge (squash
|
|
778
|
+
default, ou `--strategy merge|rebase`) -> cleanup worktree+branch, atualiza `git-map.json`. Sem remote (none),
|
|
779
|
+
faz merge LOCAL da branch na base e remove a worktree (issue/PR=null). `--mode local` nao faz nada (usado em
|
|
780
|
+
`--local`, ja committado na branch atual); em `--local` o fluxo nem chega aqui (tratado em 3.8).
|
|
735
781
|
|
|
736
782
|
**Multica: sync BATCHED no FIM da fase/onda (so se `--board`).**
|
|
737
783
|
Uma unica chamada que reflete TODAS as transicoes acumuladas da fase de uma vez (nao por microtransicao):
|
|
@@ -759,7 +805,7 @@ if [ "$BOARD" = "true" ]; then
|
|
|
759
805
|
fi
|
|
760
806
|
```
|
|
761
807
|
|
|
762
|
-
> Em `--
|
|
808
|
+
> Em `--local` o fluxo nem chega aqui (sem `--board`): nenhuma chamada Multica.
|
|
763
809
|
|
|
764
810
|
### 3.9 Reassessment de roadmap (pos-fase, inline, ~30s)
|
|
765
811
|
|
|
@@ -845,8 +891,9 @@ final_confidence: [do up-revisor de delivery]
|
|
|
845
891
|
- [ ] E2E + DCRV rodaram por fase (delegado a dcrv.md)
|
|
846
892
|
- [ ] up-revisor emitiu veredito por fase e LOGOU em approvals.log COM campo evidence=<tipo>:<resultado>
|
|
847
893
|
- [ ] GATE de fase deterministico passou (APPROVE + evidence do tipo certo, ou forced approval com debito)
|
|
848
|
-
- [ ] GitHub-nativo (default): worktree+branch+issue por fase via `github start-phase
|
|
849
|
-
`github finish-phase` no fim. `--solo`
|
|
894
|
+
- [ ] GitHub-nativo (default): worktree+branch+issue por fase via `github start-phase` (transporte gh OU
|
|
895
|
+
MCP); menu 4 opcoes / `github finish-phase` no fim. `--solo`/`--auto` mantem GitHub (autonomia, nao
|
|
896
|
+
desliga). `--local` degrada para commit na branch atual (sem worktree/issue/PR)
|
|
850
897
|
- [ ] Execucao sempre via up-executor (roteia por contexto: frontend/backend/database/misto); SEM agentes
|
|
851
898
|
specialist separados (Onda 2 do corte)
|
|
852
899
|
- [ ] `--board`: Multica init no inicio (project+pai), sync `in_progress` na entrada da fase, sync BATCHED
|
package/up/workflows/rapido.md
CHANGED
|
@@ -4,7 +4,7 @@ Executar tarefas pequenas e ad-hoc com garantias UP (commits atomicos, rastreame
|
|
|
4
4
|
**ESCAPE HATCH PURO (sem cerimonia GitHub).** Diferente de `/up:build` (GitHub-nativo por DEFAULT:
|
|
5
5
|
worktree -> branch `up/fase-NN` -> issue -> PR -> menu), o modo rapido NUNCA cria worktree, NUNCA cria
|
|
6
6
|
issue do GitHub, NUNCA abre PR e NAO toca em `.plano/git-map.json`. Todo o trabalho e committado
|
|
7
|
-
atomicamente na branch ATUAL (
|
|
7
|
+
atomicamente na branch ATUAL (mesma semantica do `--local` do build). E o caminho quente para
|
|
8
8
|
"so faz e commita". Quem quer worktree/issue/PR usa `/up:build`.
|
|
9
9
|
</purpose>
|
|
10
10
|
|
package/up/workflows/up.md
CHANGED
|
@@ -290,9 +290,12 @@ Escrever inline (sem CEO):
|
|
|
290
290
|
**`.plano/config.json`** — defaults (perguntar so na complex):
|
|
291
291
|
|
|
292
292
|
```json
|
|
293
|
-
{ "mode": "yolo", "granularity": "standard", "parallelization": true, "commit_docs": true }
|
|
293
|
+
{ "mode": "yolo", "granularity": "standard", "parallelization": true, "commit_docs": true, "github_native": true }
|
|
294
294
|
```
|
|
295
295
|
|
|
296
|
+
`github_native: true` explicito torna a intencao visivel no arquivo (o build ja usa true por default, mas
|
|
297
|
+
deixar gravado evita ambiguidade em run autonomo). So vira `false` se o projeto e local sem GitHub.
|
|
298
|
+
|
|
296
299
|
Commits atomicos:
|
|
297
300
|
|
|
298
301
|
```bash
|
|
@@ -345,9 +348,10 @@ Apos estruturar, o `/up` NAO planeja nem executa fases sozinho. Ele entrega o ha
|
|
|
345
348
|
---
|
|
346
349
|
```
|
|
347
350
|
|
|
348
|
-
> `/up:build`
|
|
349
|
-
> `--
|
|
350
|
-
>
|
|
351
|
+
> `/up:build` e **GitHub-nativo por padrao** (worktree + issue + PR por fase, via `gh` OU MCP do GitHub).
|
|
352
|
+
> Flags: `--auto` (pula o menu, mantem GitHub), `--solo` (autonomo total: GitHub + auto-merge, sem menu
|
|
353
|
+
> nem gate visual), `--local` (escape sem GitHub: commit na branch atual), `--board` (espelho de status no
|
|
354
|
+
> Multica, batched, fail-open). Para ver o board depois: `/up estado board`. Aqui so roteamos pro /up:plan.
|
|
351
355
|
|
|
352
356
|
## Passo 4: CLONE (URL recebida)
|
|
353
357
|
|
|
@@ -394,6 +398,7 @@ escreve o PRD pronto pro planejamento.
|
|
|
394
398
|
|
|
395
399
|
```json
|
|
396
400
|
{ "mode": "yolo", "granularity": "standard", "parallelization": true, "commit_docs": true,
|
|
401
|
+
"github_native": true,
|
|
397
402
|
"builder_type": "clone", "clone_source": "{URL}", "clone_mode": "{exact|improve|inspiration}",
|
|
398
403
|
"clone_data": ".plano/clone/" }
|
|
399
404
|
```
|