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
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
|
-
|
|
57
|
-
|
|
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):
|
|
78
|
-
|
|
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
|
-
|
|
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. **
|
|
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>`).
|