up-cc 2.1.0 → 2.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "up-cc",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Spec-driven development for Claude Code, Gemini, OpenCode and OpenAI Codex. Brainstorm-first, GitHub-native, TDD por tipo.",
5
5
  "bin": {
6
6
  "up-cc": "up/bin/install.js"
package/up/CHANGELOG.md CHANGED
@@ -4,6 +4,52 @@ 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.3.0
8
+
9
+ > A rodada de perguntas do brainstorm fechava e o agente seguia sozinho pro
10
+ > design. Agora quem fecha a rodada e o usuario, via checkpoint de controle.
11
+
12
+ ### Adicionado
13
+
14
+ - **Checkpoint de fechamento no `up-brainstorm`.** Toda rodada de perguntas dos
15
+ tiers Pequena, full e exploracao termina com um AskUserQuestion de 2 opcoes:
16
+ "Fechar e seguir" (avanca pro proximo passo do tier) ou "Mais perguntas" (abre
17
+ nova rodada, mais especifica que a anterior, que fecha com o mesmo checkpoint;
18
+ loop ate o usuario liberar). O "Other" nativo do AskUserQuestion cobre resposta
19
+ livre. Tier Trivial fica fora (0 perguntas, so anuncia e segue). Os 3 pontos de
20
+ fluxo (linha Pequena da tabela de profundidade, passo 3 do brainstorm full,
21
+ passos 4-5 do modo exploracao) referenciam o checkpoint.
22
+
23
+ ## 2.2.0
24
+
25
+ > O `.plano/` sempre sobreviveu ao `/clear` no DISCO, mas a RETOMADA era pull: o
26
+ > hook reinjetava so a doutrina e o agente tinha que escolher ler o STATE.md. Agora
27
+ > e push: o SessionStart injeta a posicao atual sozinho.
28
+
29
+ ### Corrigido
30
+
31
+ - **SessionStart agora injeta `.plano/STATE.md`, nao so a doutrina.** O hook
32
+ `up-session-start.js` le `<cwd>/.plano/STATE.md` (cwd do payload, fallback
33
+ `process.cwd()`) e injeta um trecho capado (2800 chars) num bloco
34
+ `<UP-ESTADO-ATUAL>` junto do bootstrap. Resultado: a cada `/clear` (e no
35
+ startup) a posicao do projeto volta pro contexto **deterministicamente**, sem
36
+ depender do agente escolher ler. Resolve o "as vezes nao sobrevive ao /clear".
37
+ Fail-open mantido: sem `.plano`, so a doutrina (comportamento anterior). Head
38
+ bruto, independe do formato PT/EN do STATE.md.
39
+
40
+ ### Testes
41
+
42
+ - `up/hooks/up-session-start.test.cjs`: 5 casos red-green invocando o hook como
43
+ subprocesso (stdin->stdout), cobrindo clear/startup com e sem `.plano`, skip de
44
+ `resume`, e cap de STATE.md gigante.
45
+
46
+ ### Conhecido (nao corrigido aqui)
47
+
48
+ - `up-tools state-snapshot` parseia rotulos em INGLES e os STATE.md reais sao em
49
+ PORTUGUES, entao retorna campos null em projeto real. O hook NAO usa
50
+ state-snapshot (le STATE.md direto), entao a retomada nao e afetada. Fica
51
+ registrado pra um fix futuro do parser.
52
+
7
53
  ## 2.1.0
8
54
 
9
55
  > GitHub e a interacao humana viram **eixos separados**. Antes, `--solo` desligava o
@@ -53,20 +53,59 @@ function readBootstrap() {
53
53
  return FALLBACK_BOOTSTRAP;
54
54
  }
55
55
 
56
- function buildContext(bootstrap) {
57
- return '<EXTREMELY_IMPORTANT>\n' +
56
+ // Teto do trecho de STATE.md injetado. As secoes uteis (Posicao Atual,
57
+ // Contexto Acumulado, Continuidade de Sessao) vivem no topo do arquivo, entao
58
+ // um head capado retoma a posicao sem estourar o contexto.
59
+ const MAX_STATE_CHARS = 2800;
60
+
61
+ /**
62
+ * Le um resumo do estado do projeto em <cwd>/.plano/STATE.md, se existir.
63
+ * Retorna o trecho capado (string) ou null. PUSH deterministico da posicao:
64
+ * o agente sempre volta sabendo onde parou, sem depender de escolher ler.
65
+ * Independe do formato exato (PT/EN) -- e um head bruto, nunca quebra.
66
+ */
67
+ function readState(cwd) {
68
+ if (!cwd || typeof cwd !== 'string') return null;
69
+ try {
70
+ const statePath = path.join(cwd, '.plano', 'STATE.md');
71
+ if (!fs.existsSync(statePath)) return null;
72
+ let content = fs.readFileSync(statePath, 'utf8');
73
+ if (!content || !content.trim()) return null;
74
+ content = content.trim();
75
+ if (content.length > MAX_STATE_CHARS) {
76
+ content = content.slice(0, MAX_STATE_CHARS).trimEnd() +
77
+ '\n\n[...truncado -- leia .plano/STATE.md para o resto]';
78
+ }
79
+ return content;
80
+ } catch (e) {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ function buildContext(bootstrap, state) {
86
+ let ctx = '<EXTREMELY_IMPORTANT>\n' +
58
87
  'Voce tem o UP.\n\n' +
59
88
  'Abaixo esta o conteudo completo da sua skill \'usando-up\' -- sua introducao a usar ' +
60
89
  'as skills do UP. Para todas as outras skills, use a tool Skill:\n\n' +
61
90
  bootstrap.trim() + '\n' +
62
91
  '</EXTREMELY_IMPORTANT>';
92
+
93
+ if (state) {
94
+ ctx += '\n\n<UP-ESTADO-ATUAL>\n' +
95
+ 'Voce esta retomando um projeto UP em andamento (inicio de sessao ou apos /clear). ' +
96
+ 'NAO comece do zero nem assuma contexto perdido: esta e a posicao atual, lida de ' +
97
+ '.plano/STATE.md. Continue daqui. Para rotear o proximo passo, use /up sem argumento.\n\n' +
98
+ state + '\n' +
99
+ '</UP-ESTADO-ATUAL>';
100
+ }
101
+ return ctx;
63
102
  }
64
103
 
65
- function emit(bootstrap) {
104
+ function emit(bootstrap, state) {
66
105
  const output = {
67
106
  hookSpecificOutput: {
68
107
  hookEventName: 'SessionStart',
69
- additionalContext: buildContext(bootstrap),
108
+ additionalContext: buildContext(bootstrap, state || null),
70
109
  },
71
110
  };
72
111
  process.stdout.write(JSON.stringify(output));
@@ -74,8 +113,9 @@ function emit(bootstrap) {
74
113
 
75
114
  let input = '';
76
115
  const stdinTimeout = setTimeout(() => {
77
- // No stdin within 3s (Windows/Git Bash guard): still inject the bootstrap.
78
- try { emit(readBootstrap()); } catch (e) {}
116
+ // No stdin within 3s (Windows/Git Bash guard): inject the bootstrap.
117
+ // Sem payload nao ha cwd, entao sem bloco de estado (so a doutrina).
118
+ try { emit(readBootstrap(), null); } catch (e) {}
79
119
  process.exit(0);
80
120
  }, 3000);
81
121
 
@@ -85,10 +125,13 @@ process.stdin.on('end', () => {
85
125
  clearTimeout(stdinTimeout);
86
126
  try {
87
127
  let source = null;
128
+ let cwd = null;
88
129
  try {
89
130
  const data = JSON.parse(input);
90
- // SessionStart payload carries the trigger in `source` (startup|clear|compact|resume).
131
+ // SessionStart payload carries the trigger in `source` (startup|clear|compact|resume)
132
+ // e o diretorio do projeto em `cwd`.
91
133
  source = data && (data.source || data.hook_event_source || null);
134
+ cwd = (data && data.cwd) || null;
92
135
  } catch (e) {
93
136
  // No/invalid payload: with no matcher available, inject anyway.
94
137
  source = null;
@@ -99,7 +142,9 @@ process.stdin.on('end', () => {
99
142
  process.exit(0);
100
143
  }
101
144
 
102
- emit(readBootstrap());
145
+ // Sem cwd no payload, cai pro diretorio do processo (o harness roda o hook no cwd da sessao).
146
+ const projectCwd = cwd || process.cwd();
147
+ emit(readBootstrap(), readState(projectCwd));
103
148
  } catch (e) {
104
149
  // Fail-open: never block the session.
105
150
  }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * up-session-start.test.cjs - testa a injecao de estado no SessionStart.
3
+ * Roda: node up/hooks/up-session-start.test.cjs
4
+ * Invoca o hook como subprocesso (stdin JSON -> stdout JSON), igual o harness faz.
5
+ */
6
+ const assert = require('assert');
7
+ const fs = require('fs');
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const { execFileSync } = require('child_process');
11
+
12
+ const HOOK = path.resolve(__dirname, 'up-session-start.js');
13
+
14
+ /** Roda o hook com um payload e devolve {additionalContext} ou null. */
15
+ function runHook(payload) {
16
+ const out = execFileSync('node', [HOOK], {
17
+ input: JSON.stringify(payload),
18
+ encoding: 'utf-8',
19
+ timeout: 8000,
20
+ });
21
+ if (!out || !out.trim()) return null;
22
+ const parsed = JSON.parse(out);
23
+ return parsed.hookSpecificOutput ? parsed.hookSpecificOutput.additionalContext : null;
24
+ }
25
+
26
+ function mkProjectWithState(stateBody) {
27
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'up-ss-'));
28
+ fs.mkdirSync(path.join(dir, '.plano'), { recursive: true });
29
+ fs.writeFileSync(path.join(dir, '.plano', 'STATE.md'), stateBody, 'utf-8');
30
+ return dir;
31
+ }
32
+ function mkProjectNoPlano() {
33
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'up-ss-noplano-'));
34
+ }
35
+
36
+ let pass = 0, fail = 0;
37
+ function t(name, fn) {
38
+ try { fn(); console.log(' ok -', name); pass++; }
39
+ catch (e) { console.error(' FAIL -', name, '\n ', e.message); fail++; }
40
+ }
41
+
42
+ const SAMPLE_STATE = `# Estado do Projeto
43
+
44
+ ## Posicao Atual
45
+
46
+ Fase: 7 de 10 (Comando /up:melhorias)
47
+ Status: In progress
48
+ Ultima atividade: 2026-06-19 -- T3 do plano 02 concluido
49
+
50
+ ## Continuidade de Sessao
51
+
52
+ Parou em: implementando o gate de aprovacao visual
53
+ `;
54
+
55
+ // 1. clear + .plano/STATE.md presente -> injeta bootstrap E o estado
56
+ t('clear com .plano: injeta bootstrap + estado', () => {
57
+ const dir = mkProjectWithState(SAMPLE_STATE);
58
+ const ctx = runHook({ source: 'clear', cwd: dir, hook_event_name: 'SessionStart' });
59
+ assert.ok(ctx, 'deve haver additionalContext');
60
+ assert.ok(/Voce tem o UP/.test(ctx), 'deve conter a doutrina (bootstrap)');
61
+ assert.ok(/Fase: 7 de 10/.test(ctx), 'deve conter a posicao do STATE.md');
62
+ assert.ok(/Parou em: implementando o gate/.test(ctx), 'deve conter onde parou');
63
+ });
64
+
65
+ // 2. clear SEM .plano -> so bootstrap, sem bloco de estado
66
+ t('clear sem .plano: so bootstrap', () => {
67
+ const dir = mkProjectNoPlano();
68
+ const ctx = runHook({ source: 'clear', cwd: dir, hook_event_name: 'SessionStart' });
69
+ assert.ok(ctx, 'deve haver additionalContext');
70
+ assert.ok(/Voce tem o UP/.test(ctx), 'deve conter a doutrina');
71
+ assert.ok(!/UP-ESTADO-ATUAL/.test(ctx), 'NAO deve ter bloco de estado sem .plano');
72
+ });
73
+
74
+ // 3. startup tambem injeta estado (retomar projeto ao abrir)
75
+ t('startup com .plano: injeta estado', () => {
76
+ const dir = mkProjectWithState(SAMPLE_STATE);
77
+ const ctx = runHook({ source: 'startup', cwd: dir, hook_event_name: 'SessionStart' });
78
+ assert.ok(/Fase: 7 de 10/.test(ctx), 'startup tambem deve trazer o estado');
79
+ });
80
+
81
+ // 4. source fora dos triggers (resume) -> nada
82
+ t('resume: nenhum output', () => {
83
+ const dir = mkProjectWithState(SAMPLE_STATE);
84
+ const ctx = runHook({ source: 'resume', cwd: dir, hook_event_name: 'SessionStart' });
85
+ assert.strictEqual(ctx, null, 'resume nao deve injetar nada');
86
+ });
87
+
88
+ // 5. STATE.md gigante -> capado (bloco de estado nao explode o contexto)
89
+ t('STATE.md gigante: capado', () => {
90
+ const huge = SAMPLE_STATE + '\n' + 'x'.repeat(20000);
91
+ const dir = mkProjectWithState(huge);
92
+ const ctx = runHook({ source: 'clear', cwd: dir, hook_event_name: 'SessionStart' });
93
+ // o bloco de estado deve estar truncado bem abaixo do tamanho do STATE gigante
94
+ assert.ok(ctx.length < 8000, 'contexto total deve ficar capado (<8000 chars), veio ' + ctx.length);
95
+ assert.ok(/Fase: 7 de 10/.test(ctx), 'mesmo capado, a posicao do topo deve aparecer');
96
+ });
97
+
98
+ console.log(`\n${pass} passed, ${fail} failed`);
99
+ process.exit(fail ? 1 : 0);
@@ -35,7 +35,7 @@ Classifique a tarefa com o `classify-task` do `up-tools.cjs` (tiers: `simple` /
35
35
  | Tier | Profundidade |
36
36
  |------|--------------|
37
37
  | **Trivial** (1 arquivo, sem decisao de arquitetura) | 0 perguntas. Anuncia em 1 linha o que vai fazer e onde. Executa. |
38
- | **Pequena** (1 subsistema, 1 escolha de design) | 1 pergunta via AskUserQuestion (a decisao-chave) + design em 3 frases. Aprova e segue. |
38
+ | **Pequena** (1 subsistema, 1 escolha de design) | 1 pergunta via AskUserQuestion (a decisao-chave) + checkpoint de fechamento + design em 3 frases. Aprova e segue. |
39
39
  | **Media / Grande** (multi-subsistema, toca schema/API/auth) | Brainstorm full. |
40
40
 
41
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.
@@ -52,6 +52,17 @@ O tier automatico e so o default. O usuario manda na profundidade:
52
52
 
53
53
  Pressa nunca remove o gate; muda so quantas perguntas.
54
54
 
55
+ ## Checkpoint de fechamento (toda rodada de perguntas)
56
+
57
+ Regra transversal aos tiers **Pequena**, **full** e **exploracao**. Tier Trivial fica FORA (0 perguntas, so anuncia e segue).
58
+
59
+ Toda rodada de perguntas termina com um AskUserQuestion de controle com exatamente 2 opcoes:
60
+
61
+ - **"Fechar e seguir"**: encerra as perguntas e avanca pro proximo passo do tier (design em 3 frases, propor abordagens ou destilar a ideia).
62
+ - **"Mais perguntas"**: abre nova rodada, mais especifica que a anterior. Essa rodada termina com este mesmo checkpoint. Loop ate o usuario escolher fechar.
63
+
64
+ Nao adicione opcao de resposta livre: o "Other" nativo do AskUserQuestion ja cobre. Se o usuario responder algo livre, incorpore como novo insumo e feche a rodada seguinte com o mesmo checkpoint.
65
+
55
66
  ## Modo exploracao (ideia crua, acima do full)
56
67
 
57
68
  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.
@@ -61,8 +72,8 @@ Diferenca do full: o full valida um design que o usuario ja tem na cabeca; a exp
61
72
  1. **Nao pule pra solucao.** Primeiro entenda o PORQUE: que problema/desejo move a ideia, pra quem, por que agora.
62
73
  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
74
  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.
75
+ 4. **Uma pergunta por vez**, multipla escolha quando der. Vai estreitando do amplo pro especifico. Feche a rodada com o checkpoint de fechamento.
76
+ 5. So depois do "Fechar e seguir" do checkpoint, **destile** a ideia num paragrafo claro: o que e, pra quem, por que, o diferencial. Confirme com o usuario.
66
77
  6. So ENTAO transicione pro design (full) ou direto pro `BRIEFING.md`, conforme o tamanho do que emergiu.
67
78
 
68
79
  A exploracao termina numa ideia destilada e aprovada, que vira BRIEFING. Continua valendo o estado terminal: codigo so depois de `/up:plan`.
@@ -83,7 +94,7 @@ Como detectar: pedido fala em "documento, proposta, relatorio, analise, texto, r
83
94
 
84
95
  1. **Explore o contexto** (arquivos, docs, commits recentes).
85
96
  2. **Companion visual:** se o topico tem questao visual (mockup, layout, comparacao), ofereca em mensagem isolada, sozinha. Jonathan e visual: ofereca por default em UI. Metodo de quando/como oferecer e produzir: ver `visual-companion.md` (mesma pasta).
86
- 3. **Perguntas uma por vez.** Multipla escolha preferida. Foco: proposito, restricoes, criterio de sucesso. Se o escopo for multiplos subsistemas independentes, sinalize JA e ajude a decompor em sub-projetos.
97
+ 3. **Perguntas uma por vez.** Multipla escolha preferida. Foco: proposito, restricoes, criterio de sucesso. Se o escopo for multiplos subsistemas independentes, sinalize JA e ajude a decompor em sub-projetos. Feche a rodada com o checkpoint de fechamento antes de avancar pro passo 4.
87
98
  4. **Proponha 2-3 abordagens** com trade-offs e sua recomendacao.
88
99
  5. **Apresente o design por SECAO** (arquitetura / componentes / dados / erros / testes), cada secao escalada a complexidade, aprovacao do usuario apos CADA secao.
89
100
  6. **Escreva BRIEFING.md** e commite (`docs(brief): <topico>`).