ganbatte-os 0.2.31 → 0.2.33
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/.gos/scripts/cli/gos-cli.js +382 -40
- package/README.md +330 -36
- package/package.json +2 -1
|
@@ -30,9 +30,74 @@ function readPackageVersion() {
|
|
|
30
30
|
|
|
31
31
|
const VERSION = readPackageVersion();
|
|
32
32
|
const UPSTREAM_REMOTE = 'upstream';
|
|
33
|
-
const
|
|
33
|
+
const DEFAULT_UPSTREAM_BRANCH = 'main';
|
|
34
34
|
const LOCAL_DIR = '.gos-local';
|
|
35
35
|
const MANIFEST_PATH = '.gos/manifests/gos-install-manifest.json';
|
|
36
|
+
const CONFIG_PATH = '.gos/config.json';
|
|
37
|
+
const CORRECT_UPSTREAM_URL = 'git@github-adriano:adrianomorais-ganbatte/g-os.git';
|
|
38
|
+
const CORRECT_UPSTREAM_URL_HTTPS = 'https://github.com/adrianomorais-ganbatte/g-os.git';
|
|
39
|
+
const STASH_LABEL = 'gos-update-auto-stash';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a development branch a partir de .gos/config.json.
|
|
43
|
+
* Fallback: 'main'. Permite override via env GOS_UPSTREAM_BRANCH.
|
|
44
|
+
*/
|
|
45
|
+
function resolveUpstreamBranch(root) {
|
|
46
|
+
if (process.env.GOS_UPSTREAM_BRANCH) return process.env.GOS_UPSTREAM_BRANCH;
|
|
47
|
+
const cfg = readJson(path.join(root, CONFIG_PATH));
|
|
48
|
+
return cfg?.defaultBranches?.development || cfg?.defaultBranches?.production || DEFAULT_UPSTREAM_BRANCH;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Detecta se este workspace é o framework G-OS em si (fork/clone) ou um projeto
|
|
53
|
+
* consumidor que apenas instalou o framework via `gos install`.
|
|
54
|
+
* - 'framework': package.json#name === 'ganbatte-os' AND .git no root
|
|
55
|
+
* - 'consumer': qualquer outro caso
|
|
56
|
+
*/
|
|
57
|
+
function detectMode(root) {
|
|
58
|
+
const pkg = readJson(path.join(root, 'package.json'), {});
|
|
59
|
+
const isGosPackage = pkg.name === 'ganbatte-os';
|
|
60
|
+
const hasGit = pathExists(path.join(root, '.git'));
|
|
61
|
+
return isGosPackage && hasGit ? 'framework' : 'consumer';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Valida que o remote upstream existe E responde. Não modifica nada.
|
|
66
|
+
* Retorna { ok, remoteUrl, error }.
|
|
67
|
+
*/
|
|
68
|
+
function validateUpstream(root) {
|
|
69
|
+
const remotes = gitCapture(['remote'], { cwd: root });
|
|
70
|
+
if (!remotes.split(/\r?\n/).includes(UPSTREAM_REMOTE)) {
|
|
71
|
+
return { ok: false, error: `remote "${UPSTREAM_REMOTE}" não configurado` };
|
|
72
|
+
}
|
|
73
|
+
const url = gitCapture(['remote', 'get-url', UPSTREAM_REMOTE], { cwd: root });
|
|
74
|
+
// Detecta URL antiga incorreta (ganbatte-os.git em vez de g-os.git)
|
|
75
|
+
if (/ganbatte-os\.git/.test(url) && !/g-os\.git/.test(url)) {
|
|
76
|
+
return { ok: false, remoteUrl: url, error: 'URL do upstream parece estar com nome antigo (ganbatte-os.git)' };
|
|
77
|
+
}
|
|
78
|
+
// Ping no remoto sem stashar nada
|
|
79
|
+
try {
|
|
80
|
+
execFileSync('git', ['ls-remote', '--quiet', '--exit-code', UPSTREAM_REMOTE, 'HEAD'], {
|
|
81
|
+
cwd: root, stdio: 'pipe', encoding: 'utf8',
|
|
82
|
+
});
|
|
83
|
+
return { ok: true, remoteUrl: url };
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return { ok: false, remoteUrl: url, error: `não foi possível alcançar ${url}` };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Lista todos stashes auto-criados pelo gos-update.
|
|
91
|
+
* Retorna [{ ref, branch, label, dateIso }].
|
|
92
|
+
*/
|
|
93
|
+
function listGosStashes(root) {
|
|
94
|
+
const out = gitCapture(['stash', 'list', '--format=%gd|%gs|%ci'], { cwd: root });
|
|
95
|
+
if (!out) return [];
|
|
96
|
+
return out.split(/\r?\n/).filter(Boolean).map(line => {
|
|
97
|
+
const [ref, subject, dateIso] = line.split('|');
|
|
98
|
+
return { ref, subject, dateIso };
|
|
99
|
+
}).filter(s => s.subject && s.subject.includes(STASH_LABEL));
|
|
100
|
+
}
|
|
36
101
|
|
|
37
102
|
// ---------------------------------------------------------------------------
|
|
38
103
|
// Utilitarios
|
|
@@ -347,57 +412,219 @@ function cmdInstall(args) {
|
|
|
347
412
|
|
|
348
413
|
function cmdUpdate(root, args) {
|
|
349
414
|
const skipStash = args.includes('--no-stash');
|
|
415
|
+
const branch = resolveUpstreamBranch(root);
|
|
416
|
+
const mode = detectMode(root);
|
|
350
417
|
|
|
351
418
|
log('Atualizando workspace ganbatte-os...');
|
|
352
419
|
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
info(
|
|
420
|
+
// 0. Modo: bloquear se for projeto consumidor (não fork do framework).
|
|
421
|
+
if (mode === 'consumer') {
|
|
422
|
+
fail('Este workspace é um projeto consumidor, não o repositório do framework G-OS.');
|
|
423
|
+
info('Para atualizar o framework dentro do seu projeto, use:');
|
|
424
|
+
info(' gos install --force');
|
|
425
|
+
info('Ou (se você instalou via npm install):');
|
|
426
|
+
info(' npm install ganbatte-os@latest');
|
|
427
|
+
info('');
|
|
428
|
+
info('`gos update` é apenas para forks/clones do repositório g-os.');
|
|
358
429
|
process.exit(1);
|
|
359
430
|
}
|
|
360
431
|
|
|
361
|
-
//
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
432
|
+
// 1. Validar upstream ANTES de qualquer modificação local (stash, etc).
|
|
433
|
+
const upstream = validateUpstream(root);
|
|
434
|
+
if (!upstream.ok) {
|
|
435
|
+
fail(`Upstream inválido: ${upstream.error}`);
|
|
436
|
+
if (upstream.remoteUrl) info(`URL atual: ${upstream.remoteUrl}`);
|
|
437
|
+
info('Corrija com:');
|
|
438
|
+
info(` git remote set-url ${UPSTREAM_REMOTE} ${CORRECT_UPSTREAM_URL}`);
|
|
439
|
+
info(` (ou via HTTPS: ${CORRECT_UPSTREAM_URL_HTTPS})`);
|
|
440
|
+
info('');
|
|
441
|
+
info(`Depois rode: npm run gos:update`);
|
|
442
|
+
process.exit(1);
|
|
369
443
|
}
|
|
370
444
|
|
|
371
|
-
//
|
|
372
|
-
log(`
|
|
373
|
-
|
|
445
|
+
// 2. Dry-run fetch (sem stashar): se nada mudou, sai cedo.
|
|
446
|
+
log(`Verificando ${UPSTREAM_REMOTE}/${branch}...`);
|
|
447
|
+
try {
|
|
448
|
+
git(['fetch', UPSTREAM_REMOTE, branch], { cwd: root, quiet: true });
|
|
449
|
+
} catch (e) {
|
|
450
|
+
fail(`Fetch de ${UPSTREAM_REMOTE}/${branch} falhou.`);
|
|
451
|
+
info('Verifique sua conexão e credenciais SSH/HTTPS. Nada foi modificado localmente.');
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
374
454
|
|
|
375
|
-
// 4. Checar se ha commits novos
|
|
376
455
|
const behind = gitCapture(
|
|
377
|
-
['rev-list', '--count', `HEAD..${UPSTREAM_REMOTE}/${
|
|
456
|
+
['rev-list', '--count', `HEAD..${UPSTREAM_REMOTE}/${branch}`],
|
|
378
457
|
{ cwd: root }
|
|
379
458
|
);
|
|
380
|
-
if (behind === '0') {
|
|
381
|
-
ok(
|
|
382
|
-
if (didStash) git(['stash', 'pop'], { cwd: root });
|
|
459
|
+
if (behind === '0' || behind === '') {
|
|
460
|
+
ok(`Workspace ja esta atualizado com ${UPSTREAM_REMOTE}/${branch}.`);
|
|
383
461
|
return;
|
|
384
462
|
}
|
|
385
463
|
|
|
464
|
+
// 3. AGORA é seguro stashar (upstream validado + commits novos confirmados).
|
|
465
|
+
// Importante: stash deve EXCLUIR paths do framework — eles serão sobrescritos
|
|
466
|
+
// pelo merge de qualquer forma, e stashar a si próprio (gos-cli.js) cria um
|
|
467
|
+
// loop em que o stash reverte a versão atualizada do CLI no working tree.
|
|
468
|
+
const status = gitCapture(['status', '--porcelain'], { cwd: root });
|
|
469
|
+
// Detectar se há mudanças além dos paths framework
|
|
470
|
+
const userModifiedLines = status
|
|
471
|
+
.split(/\r?\n/)
|
|
472
|
+
.filter(Boolean)
|
|
473
|
+
.filter(line => {
|
|
474
|
+
const file = line.slice(3); // 2-char status flag + space
|
|
475
|
+
// Exclui paths gerados/managed pelo framework
|
|
476
|
+
const FRAMEWORK_PREFIXES = [
|
|
477
|
+
'.gos/', '.claude/', '.qwen/', '.gemini/', '.cursor/', '.agents/',
|
|
478
|
+
'.kilocode/', '.antigravity/', '.opencode/', '.codex/',
|
|
479
|
+
];
|
|
480
|
+
return !FRAMEWORK_PREFIXES.some(p => file.startsWith(p));
|
|
481
|
+
});
|
|
482
|
+
let didStash = false;
|
|
483
|
+
if (userModifiedLines.length > 0 && !skipStash) {
|
|
484
|
+
log(`Mudancas locais do usuario detectadas (${userModifiedLines.length}). Fazendo stash...`);
|
|
485
|
+
const stashLabel = `${STASH_LABEL} ${new Date().toISOString()}`;
|
|
486
|
+
// Stash com pathspec excluindo paths do framework — assim o stash captura
|
|
487
|
+
// apenas mudanças do usuário, e o working tree mantém a versão atualizada
|
|
488
|
+
// dos arquivos do framework (incluindo este próprio CLI).
|
|
489
|
+
const stashArgs = [
|
|
490
|
+
'stash', 'push', '-m', stashLabel, '--',
|
|
491
|
+
':(exclude,top).gos',
|
|
492
|
+
':(exclude,top).claude',
|
|
493
|
+
':(exclude,top).qwen',
|
|
494
|
+
':(exclude,top).gemini',
|
|
495
|
+
':(exclude,top).cursor',
|
|
496
|
+
':(exclude,top).agents',
|
|
497
|
+
':(exclude,top).kilocode',
|
|
498
|
+
':(exclude,top).antigravity',
|
|
499
|
+
':(exclude,top).opencode',
|
|
500
|
+
':(exclude,top).codex',
|
|
501
|
+
'.',
|
|
502
|
+
];
|
|
503
|
+
git(stashArgs, { cwd: root });
|
|
504
|
+
didStash = true;
|
|
505
|
+
ok(`Stash criado (apenas mudancas do usuario): ${stashLabel}`);
|
|
506
|
+
} else if (status) {
|
|
507
|
+
info(`Mudancas locais detectadas apenas em paths do framework — serao sobrescritas pelo merge.`);
|
|
508
|
+
}
|
|
509
|
+
|
|
386
510
|
const commitBefore = gitCapture(['rev-parse', '--short', 'HEAD'], { cwd: root });
|
|
387
511
|
|
|
388
512
|
// 5. Merge
|
|
389
513
|
log(`${behind} commit(s) novo(s). Fazendo merge...`);
|
|
390
514
|
const manifest = getManifest(root);
|
|
391
515
|
const frameworkPaths = manifest.frameworkManaged || [];
|
|
516
|
+
const allowUnrelated = args.includes('--allow-unrelated');
|
|
517
|
+
const clobberUntracked = args.includes('--clobber-untracked');
|
|
518
|
+
|
|
519
|
+
// Paths que podemos clobberar com segurança:
|
|
520
|
+
// - IDE adapters: regenerados por sync:ides
|
|
521
|
+
// - .gos/: framework directory, sempre vem do upstream
|
|
522
|
+
const FRAMEWORK_GENERATED_PREFIXES = [
|
|
523
|
+
'.claude/', '.qwen/', '.gemini/', '.cursor/', '.agents/',
|
|
524
|
+
'.kilocode/', '.antigravity/', '.opencode/', '.codex/',
|
|
525
|
+
'.gos/',
|
|
526
|
+
];
|
|
527
|
+
const isGenerated = (p) => FRAMEWORK_GENERATED_PREFIXES.some(prefix => p.startsWith(prefix));
|
|
528
|
+
|
|
529
|
+
const mergeArgs = ['merge', `${UPSTREAM_REMOTE}/${branch}`, '--no-edit'];
|
|
530
|
+
if (allowUnrelated) mergeArgs.push('--allow-unrelated-histories');
|
|
531
|
+
|
|
532
|
+
// Função tentando o merge, capturando stderr — chamada mais de uma vez se houver retry
|
|
533
|
+
function tryMerge() {
|
|
534
|
+
try {
|
|
535
|
+
execFileSync('git', mergeArgs, { cwd: root, stdio: 'pipe', encoding: 'utf8' });
|
|
536
|
+
return { ok: true };
|
|
537
|
+
} catch (e) {
|
|
538
|
+
return { ok: false, err: ((e.stderr || '') + (e.stdout || '')).toString() };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
392
541
|
|
|
393
|
-
|
|
394
|
-
|
|
542
|
+
let mergeErr = '';
|
|
543
|
+
let attempt = tryMerge();
|
|
544
|
+
if (attempt.ok) {
|
|
395
545
|
ok('Merge concluido sem conflitos.');
|
|
396
|
-
}
|
|
397
|
-
|
|
546
|
+
} else {
|
|
547
|
+
mergeErr = attempt.err;
|
|
548
|
+
|
|
549
|
+
// Auto-resolução: untracked files que upstream quer escrever, mas só se
|
|
550
|
+
// todos forem paths gerados pelo framework (sync:ides regenera).
|
|
551
|
+
const untrackedMatch = mergeErr.match(/untracked working tree files would be overwritten by merge:\s*([\s\S]+?)(?:Please move or remove|$)/i);
|
|
552
|
+
if (untrackedMatch) {
|
|
553
|
+
const conflictingPaths = untrackedMatch[1]
|
|
554
|
+
.split(/\r?\n/)
|
|
555
|
+
.map(s => s.trim())
|
|
556
|
+
.filter(Boolean)
|
|
557
|
+
// git termina com "Aborting", "Merge with strategy ort failed.", "Please move or remove..."
|
|
558
|
+
// Caminhos reais sempre contêm "." (extensão ou dotfile) OU "/" (subdir).
|
|
559
|
+
.filter(s => (s.includes('.') || s.includes('/')) && !/^(Aborting|Merge with|Please move)/i.test(s));
|
|
560
|
+
const generated = conflictingPaths.filter(isGenerated);
|
|
561
|
+
const userOwned = conflictingPaths.filter(p => !isGenerated(p));
|
|
562
|
+
|
|
563
|
+
if (clobberUntracked && userOwned.length === 0) {
|
|
564
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
565
|
+
log(`${generated.length} arquivo(s) untracked em paths regenerados — movendo para .bak.${ts}/`);
|
|
566
|
+
for (const file of generated) {
|
|
567
|
+
const src = path.join(root, file);
|
|
568
|
+
const dst = path.join(root, `${file}.bak.${ts}`);
|
|
569
|
+
if (pathExists(src)) {
|
|
570
|
+
ensureDir(path.dirname(dst));
|
|
571
|
+
fs.renameSync(src, dst);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
ok(`Movidos ${generated.length} arquivo(s). Retentando merge...`);
|
|
575
|
+
attempt = tryMerge();
|
|
576
|
+
if (attempt.ok) {
|
|
577
|
+
ok('Merge concluido apos clobber.');
|
|
578
|
+
mergeErr = '';
|
|
579
|
+
} else {
|
|
580
|
+
mergeErr = attempt.err;
|
|
581
|
+
}
|
|
582
|
+
} else if (userOwned.length === 0 && !clobberUntracked) {
|
|
583
|
+
fail(`Merge abortou: ${conflictingPaths.length} arquivo(s) untracked seriam sobrescritos.`);
|
|
584
|
+
info('Todos os arquivos afetados estão em paths gerados pelo framework (regenerados por sync:ides):');
|
|
585
|
+
for (const p of generated.slice(0, 10)) info(` - ${p}`);
|
|
586
|
+
if (generated.length > 10) info(` ... mais ${generated.length - 10}`);
|
|
587
|
+
info('');
|
|
588
|
+
info('Para auto-mover esses arquivos para .bak.<timestamp> antes do merge:');
|
|
589
|
+
info(` npm run gos:update -- --clobber-untracked${allowUnrelated ? ' --allow-unrelated' : ''}`);
|
|
590
|
+
if (didStash) info('Stash preservado. Rode: git stash pop quando terminar.');
|
|
591
|
+
process.exit(1);
|
|
592
|
+
} else {
|
|
593
|
+
// Misto — tem arquivos do usuário também → não clobberamos nada
|
|
594
|
+
fail(`Merge abortou: arquivos untracked seriam sobrescritos.`);
|
|
595
|
+
info(`${userOwned.length} arquivo(s) NÃO gerados pelo framework (revisar antes):`);
|
|
596
|
+
for (const p of userOwned.slice(0, 10)) info(` - ${p}`);
|
|
597
|
+
info('Mova ou versione esses arquivos manualmente; depois retente.');
|
|
598
|
+
if (didStash) info('Stash preservado.');
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!attempt.ok) {
|
|
605
|
+
|
|
606
|
+
// Detecta histórias não relacionadas (comum em workspaces criados via `gos install`)
|
|
607
|
+
if (/unrelated histories/i.test(mergeErr) && !allowUnrelated) {
|
|
608
|
+
fail('Merge abortou: histórias não relacionadas entre HEAD e upstream.');
|
|
609
|
+
info('Isto é comum quando o workspace foi bootstrappado via `gos install`');
|
|
610
|
+
info('e nunca compartilhou commits com o repositório do framework.');
|
|
611
|
+
info('');
|
|
612
|
+
info('Para forçar o merge unindo as histórias (recomendado neste caso):');
|
|
613
|
+
info(` npm run gos:update -- --allow-unrelated`);
|
|
614
|
+
info('');
|
|
615
|
+
info('Alternativa segura (sobrescreve apenas .gos/ preservando seus arquivos):');
|
|
616
|
+
info(' gos install --force');
|
|
617
|
+
if (didStash) info('Stash preservado. Rode: git stash pop quando resolver.');
|
|
618
|
+
process.exit(1);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Checar conflitos de arquivo
|
|
398
622
|
const conflictFiles = gitCapture(['diff', '--name-only', '--diff-filter=U'], { cwd: root });
|
|
399
623
|
if (!conflictFiles) {
|
|
400
|
-
fail('Merge falhou
|
|
624
|
+
fail('Merge falhou. Erro do git:');
|
|
625
|
+
for (const line of mergeErr.split(/\r?\n/).filter(Boolean).slice(0, 6)) {
|
|
626
|
+
console.log(` ${line}`);
|
|
627
|
+
}
|
|
401
628
|
if (didStash) info('Stash preservado. Rode: git stash pop');
|
|
402
629
|
process.exit(1);
|
|
403
630
|
}
|
|
@@ -600,12 +827,46 @@ function cmdDoctor(root) {
|
|
|
600
827
|
check(`Diretorio ${LOCAL_DIR}/ existe`, hasLocalDir);
|
|
601
828
|
}
|
|
602
829
|
|
|
603
|
-
// 10.
|
|
830
|
+
// 10. Mode + upstream sanity (warnings, não falhas)
|
|
831
|
+
const mode = detectMode(root);
|
|
832
|
+
const branch = resolveUpstreamBranch(root);
|
|
833
|
+
const isCi = Boolean(process.env.CI || process.env.GITHUB_ACTIONS);
|
|
834
|
+
if (mode === 'framework') {
|
|
835
|
+
if (isCi) {
|
|
836
|
+
info('Upstream check ignorado em CI (sem credenciais para git ls-remote)');
|
|
837
|
+
} else {
|
|
838
|
+
const upstream = validateUpstream(root);
|
|
839
|
+
if (upstream.ok) {
|
|
840
|
+
ok(`Upstream alcançável (branch: ${branch})`);
|
|
841
|
+
} else {
|
|
842
|
+
warn(`Upstream inválido: ${upstream.error}`);
|
|
843
|
+
if (upstream.remoteUrl) info(`URL atual: ${upstream.remoteUrl}`);
|
|
844
|
+
info(`Corrija com: git remote set-url ${UPSTREAM_REMOTE} ${CORRECT_UPSTREAM_URL}`);
|
|
845
|
+
issues++;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
checks++;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// 11. Stashes acumulados de updates falhos
|
|
852
|
+
const goshStashes = listGosStashes(root);
|
|
853
|
+
if (goshStashes.length > 1) {
|
|
854
|
+
warn(`${goshStashes.length} stashes do gos-update acumulados (sintoma de updates falhos).`);
|
|
855
|
+
info('Resgate com: npm run gos:rescue');
|
|
856
|
+
issues++;
|
|
857
|
+
} else if (goshStashes.length === 1) {
|
|
858
|
+
info(`1 stash do gos-update presente: ${goshStashes[0].ref}`);
|
|
859
|
+
}
|
|
860
|
+
checks++;
|
|
861
|
+
|
|
862
|
+
// 12. Report
|
|
604
863
|
const updateLog = readJson(path.join(root, LOCAL_DIR, 'update-log.json'));
|
|
605
864
|
const installLogData = readJson(path.join(root, LOCAL_DIR, 'install-log.json'));
|
|
606
865
|
|
|
607
866
|
console.log('');
|
|
608
867
|
console.log(` Versao: ${pkg ? pkg.version || VERSION : VERSION}`);
|
|
868
|
+
console.log(` Modo: ${mode === 'framework' ? 'framework workspace' : 'projeto consumidor'}`);
|
|
869
|
+
console.log(` Branch dev: ${branch}`);
|
|
609
870
|
console.log(` Inicializado: ${installLogData ? installLogData.initializedAt : 'N/A'}`);
|
|
610
871
|
console.log(` Ultimo update: ${updateLog ? updateLog.lastUpdate : 'N/A'}`);
|
|
611
872
|
console.log(` Node.js: ${process.version}`);
|
|
@@ -620,6 +881,60 @@ function cmdDoctor(root) {
|
|
|
620
881
|
process.exit(issues > 0 ? 1 : 0);
|
|
621
882
|
}
|
|
622
883
|
|
|
884
|
+
// ---------------------------------------------------------------------------
|
|
885
|
+
// gos rescue — recuperar stashes acumulados de updates falhos
|
|
886
|
+
// ---------------------------------------------------------------------------
|
|
887
|
+
|
|
888
|
+
function cmdRescue(root, args) {
|
|
889
|
+
log('Buscando stashes do gos-update...');
|
|
890
|
+
const stashes = listGosStashes(root);
|
|
891
|
+
if (stashes.length === 0) {
|
|
892
|
+
ok('Nenhum stash do gos-update encontrado.');
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
console.log('');
|
|
897
|
+
console.log(` ${stashes.length} stash(es) auto-criado(s) pelo gos-update:`);
|
|
898
|
+
console.log('');
|
|
899
|
+
for (const s of stashes) {
|
|
900
|
+
console.log(` ${s.ref} (${s.dateIso})`);
|
|
901
|
+
const stat = gitCapture(['stash', 'show', '--stat', s.ref], { cwd: root });
|
|
902
|
+
if (stat) {
|
|
903
|
+
for (const line of stat.split(/\r?\n/).slice(0, 5)) {
|
|
904
|
+
if (line.trim()) console.log(` ${line}`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
console.log('');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (args.includes('--drop-all')) {
|
|
911
|
+
warn(`Removendo todos os ${stashes.length} stashes...`);
|
|
912
|
+
// Remove em ordem reversa porque os índices mudam
|
|
913
|
+
for (let i = stashes.length - 1; i >= 0; i--) {
|
|
914
|
+
git(['stash', 'drop', stashes[i].ref], { cwd: root, quiet: true });
|
|
915
|
+
}
|
|
916
|
+
ok('Todos os stashes do gos-update foram removidos.');
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (args.includes('--pop-latest')) {
|
|
921
|
+
log(`Aplicando ${stashes[0].ref}...`);
|
|
922
|
+
try {
|
|
923
|
+
git(['stash', 'pop', stashes[0].ref], { cwd: root });
|
|
924
|
+
ok('Stash aplicado.');
|
|
925
|
+
} catch {
|
|
926
|
+
warn('Conflito ao aplicar. Resolva manualmente e rode `git stash drop` quando terminar.');
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
info('Comandos disponíveis:');
|
|
932
|
+
info(' npm run gos:rescue -- --pop-latest # aplica o stash mais recente');
|
|
933
|
+
info(' npm run gos:rescue -- --drop-all # remove todos (após revisão)');
|
|
934
|
+
info(' git stash pop <ref> # aplica stash específico');
|
|
935
|
+
info(' git stash drop <ref> # remove stash específico');
|
|
936
|
+
}
|
|
937
|
+
|
|
623
938
|
// ---------------------------------------------------------------------------
|
|
624
939
|
// gos version
|
|
625
940
|
// ---------------------------------------------------------------------------
|
|
@@ -627,30 +942,43 @@ function cmdDoctor(root) {
|
|
|
627
942
|
function cmdVersion(root) {
|
|
628
943
|
const pkg = readJson(path.join(root, 'package.json'), {});
|
|
629
944
|
const localVersion = pkg.version || VERSION;
|
|
945
|
+
const mode = detectMode(root);
|
|
946
|
+
const branch = resolveUpstreamBranch(root);
|
|
630
947
|
|
|
631
948
|
console.log(`ganbatte-os v${localVersion}`);
|
|
949
|
+
console.log(` modo: ${mode === 'framework' ? 'framework workspace (fork/clone do g-os)' : 'projeto consumidor'}`);
|
|
632
950
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
info(
|
|
951
|
+
if (mode === 'consumer') {
|
|
952
|
+
info('Para checar a última versão publicada do framework:');
|
|
953
|
+
info(' npm view ganbatte-os version');
|
|
954
|
+
info('Para atualizar o framework dentro do seu projeto:');
|
|
955
|
+
info(' gos install --force');
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Modo framework: checar commits novos no upstream
|
|
960
|
+
const upstream = validateUpstream(root);
|
|
961
|
+
if (!upstream.ok) {
|
|
962
|
+
warn(`Upstream inválido: ${upstream.error}`);
|
|
963
|
+
if (upstream.remoteUrl) info(`URL atual: ${upstream.remoteUrl}`);
|
|
964
|
+
info(`Corrija com: git remote set-url ${UPSTREAM_REMOTE} ${CORRECT_UPSTREAM_URL}`);
|
|
637
965
|
return;
|
|
638
966
|
}
|
|
639
967
|
|
|
640
968
|
try {
|
|
641
|
-
execFileSync('git', ['fetch', UPSTREAM_REMOTE,
|
|
969
|
+
execFileSync('git', ['fetch', UPSTREAM_REMOTE, branch], {
|
|
642
970
|
encoding: 'utf8', stdio: 'pipe', cwd: root
|
|
643
971
|
});
|
|
644
972
|
const behind = gitCapture(
|
|
645
|
-
['rev-list', '--count', `HEAD..${UPSTREAM_REMOTE}/${
|
|
973
|
+
['rev-list', '--count', `HEAD..${UPSTREAM_REMOTE}/${branch}`],
|
|
646
974
|
{ cwd: root }
|
|
647
975
|
);
|
|
648
976
|
|
|
649
977
|
if (behind && behind !== '0') {
|
|
650
|
-
console.log(`\n ${behind} commit(s) novo(s)
|
|
978
|
+
console.log(`\n ${behind} commit(s) novo(s) em ${UPSTREAM_REMOTE}/${branch}.`);
|
|
651
979
|
console.log(' Atualize com: npm run gos:update\n');
|
|
652
980
|
} else {
|
|
653
|
-
ok(
|
|
981
|
+
ok(`Voce esta na versao mais recente de ${UPSTREAM_REMOTE}/${branch}.`);
|
|
654
982
|
}
|
|
655
983
|
} catch {
|
|
656
984
|
warn('Nao foi possivel checar atualizacoes (sem conexao?).');
|
|
@@ -683,6 +1011,10 @@ function main() {
|
|
|
683
1011
|
cmdDoctor(root);
|
|
684
1012
|
break;
|
|
685
1013
|
|
|
1014
|
+
case 'rescue':
|
|
1015
|
+
cmdRescue(root, args.slice(1));
|
|
1016
|
+
break;
|
|
1017
|
+
|
|
686
1018
|
case 'version':
|
|
687
1019
|
case '-v':
|
|
688
1020
|
case '--version':
|
|
@@ -698,14 +1030,24 @@ ganbatte-os CLI v${VERSION}
|
|
|
698
1030
|
Comandos:
|
|
699
1031
|
gos install Instalar G-OS no diretorio atual (via npx ou global)
|
|
700
1032
|
gos init Setup pos-clone (remote, dirs, IDEs)
|
|
701
|
-
gos update Atualizar do upstream/
|
|
1033
|
+
gos update Atualizar do upstream (apenas em fork/clone do g-os)
|
|
1034
|
+
gos rescue Listar/recuperar stashes acumulados de updates falhos
|
|
702
1035
|
gos doctor Validar integridade do workspace
|
|
703
|
-
gos version Mostrar versao e
|
|
1036
|
+
gos version Mostrar versao, modo (framework/consumer) e atualizacoes
|
|
704
1037
|
gos help Exibir esta ajuda
|
|
705
1038
|
|
|
706
1039
|
Flags:
|
|
707
|
-
--force
|
|
708
|
-
--no-stash
|
|
1040
|
+
--force Sobrescrever arquivos existentes (install/init)
|
|
1041
|
+
--no-stash Nao fazer stash automatico (update)
|
|
1042
|
+
--allow-unrelated Permitir merge de histórias não relacionadas (update)
|
|
1043
|
+
--clobber-untracked Mover untracked files (em paths regenerados) para
|
|
1044
|
+
.bak.<timestamp> antes do merge (update)
|
|
1045
|
+
--pop-latest Aplicar stash mais recente (rescue)
|
|
1046
|
+
--drop-all Remover todos os stashes do gos-update (rescue)
|
|
1047
|
+
|
|
1048
|
+
Env:
|
|
1049
|
+
GOS_UPSTREAM_BRANCH Override da branch a ser pulled (default lê de
|
|
1050
|
+
.gos/config.json#defaultBranches.development)
|
|
709
1051
|
|
|
710
1052
|
Exemplos:
|
|
711
1053
|
npx -p ganbatte-os gos install
|
package/README.md
CHANGED
|
@@ -43,30 +43,149 @@ Após rodar o install, o framework criará uma estrutura limpa na sua raiz:
|
|
|
43
43
|
|
|
44
44
|
### 3. Comandos do Workspace
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
| `npm run gos:
|
|
51
|
-
| `npm run gos:
|
|
52
|
-
| `npm run gos:
|
|
53
|
-
| `npm run
|
|
54
|
-
| `npm run
|
|
55
|
-
| `npm run
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
46
|
+
| Comando | Equivalente CLI | O que faz |
|
|
47
|
+
|---------|-----------------|-----------|
|
|
48
|
+
| `npm run gos:init` | `gos init` | Setup pos-clone: configura `upstream`, cria `.gos-local/`, gera IDE adapters |
|
|
49
|
+
| `npm run gos:update` | `gos update` | Fetch + merge do upstream (**apenas em fork/clone do g-os**) |
|
|
50
|
+
| `npm run gos:doctor` | `gos doctor` | Health-check (42+ checks: skills, agents, IDE adapters, upstream alcançável, stashes acumulados, modo workspace) |
|
|
51
|
+
| `npm run gos:version` | `gos version` | Versão instalada, modo do workspace e atualizações pendentes |
|
|
52
|
+
| `npm run gos:rescue` | `gos rescue` | Lista/recupera stashes acumulados de updates falhos |
|
|
53
|
+
| `npm run sync:ides` | — | Regenera apenas os IDE adapters (`.claude/`, `.qwen/`, `.gemini/`, `.cursor/`, `.agents/`) |
|
|
54
|
+
| `npm run check:ides` | — | Valida que adapters estão consistentes com `.gos/skills/registry.json` |
|
|
55
|
+
| `npm run clickup` | — | CLI ClickUp |
|
|
56
|
+
|
|
57
|
+
## Atualizar o ganbatte-os
|
|
58
|
+
|
|
59
|
+
Existem **três níveis distintos** de versão. Saiba qual atualizar antes de rodar qualquer comando.
|
|
60
|
+
|
|
61
|
+
### Descobrir em que cenário você está
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run gos:version
|
|
65
|
+
# imprime: versão local + modo (framework workspace | projeto consumidor)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Modo detectado | Significa | Como atualizar |
|
|
69
|
+
|----------------|-----------|----------------|
|
|
70
|
+
| `framework workspace` | Você clonou/forkou o repo `g-os` para contribuir | `npm run gos:update` |
|
|
71
|
+
| `projeto consumidor` | Seu projeto usa o G-OS (rodou `gos install` aqui) | `gos install --force` |
|
|
72
|
+
| (CLI global) | O comando `gos` instalado via `npm install -g` | `npm install -g ganbatte-os@latest` |
|
|
73
|
+
|
|
74
|
+
### Nível 1 — CLI global (`gos`)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Versão atual instalada vs publicada no registry:
|
|
78
|
+
npm list -g ganbatte-os
|
|
79
|
+
npm view ganbatte-os version
|
|
80
|
+
|
|
81
|
+
# Atualizar:
|
|
82
|
+
npm install -g ganbatte-os@latest
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Nível 2 — Projeto consumidor
|
|
86
|
+
|
|
87
|
+
Seu projeto NÃO é fork do G-OS, mas usa o framework via `gos install`. O `.gos/` mora dentro do seu repo.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Pré-checagem (uma vez):
|
|
91
|
+
git remote -v
|
|
92
|
+
# Se houver "upstream" apontando para g-os.git, REMOVA:
|
|
93
|
+
# git remote remove upstream
|
|
94
|
+
# (essa configuração só faz sentido em fork do framework)
|
|
95
|
+
|
|
96
|
+
# Atualizar o framework dentro do seu projeto:
|
|
97
|
+
gos install --force
|
|
98
|
+
# Sobrescreve .gos/ com a última versão do pacote ganbatte-os global,
|
|
99
|
+
# preservando seus arquivos (packages/, docs/, .gos-local/).
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> [!WARNING]
|
|
103
|
+
> NÃO use `npm run gos:update` em projeto consumidor. O CLI agora detecta esse cenário e aborta com instruções, mas em versões antigas ele tentava `git fetch upstream main` e quebrava de forma confusa.
|
|
104
|
+
|
|
105
|
+
### Nível 3 — Framework workspace (fork/clone do g-os)
|
|
106
|
+
|
|
107
|
+
Você está contribuindo com o próprio framework.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Pré-checagem (sempre antes de update):
|
|
111
|
+
npm run gos:doctor
|
|
112
|
+
# Valida 42+ pontos. Aborta se upstream estiver com URL quebrada
|
|
113
|
+
# ou se houver stashes acumulados de updates anteriores falhos.
|
|
114
|
+
|
|
115
|
+
# Ver quantos commits faltam:
|
|
116
|
+
npm run gos:version
|
|
117
|
+
|
|
118
|
+
# Aplicar (lê dev branch do .gos/config.json#defaultBranches.development):
|
|
119
|
+
npm run gos:update
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`gos:update` agora é **fail-safe**:
|
|
123
|
+
|
|
124
|
+
1. Bloqueia se modo for `consumer`
|
|
125
|
+
2. Valida `upstream` reachable (`git ls-remote`) ANTES de stashar
|
|
126
|
+
3. Lê branch de desenvolvimento do `.gos/config.json` (não hardcoded)
|
|
127
|
+
4. Faz fetch primeiro; se nada mudou, sai sem stash
|
|
128
|
+
5. Stash recebe label timestamped único
|
|
129
|
+
6. Auto-resolve conflitos em arquivos do framework (`frameworkManaged` no manifest); aborta em conflitos do usuário
|
|
130
|
+
|
|
131
|
+
Override de branch (debug):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
GOS_UPSTREAM_BRANCH=beta npm run gos:update
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Caso especial: "histórias não relacionadas"
|
|
138
|
+
|
|
139
|
+
Workspaces criados via `gos install` no passado podem ter `.git` próprio sem ancestor comum com o repo do framework. Nesse caso o merge falha com `fatal: refusing to merge unrelated histories`. O CLI detecta e instrui:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm run gos:update -- --allow-unrelated
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Isso une as duas histórias num commit de merge único. Faça uma vez; depois disso updates normais funcionam.
|
|
146
|
+
|
|
147
|
+
#### Caso especial: untracked files que conflitam com o merge
|
|
148
|
+
|
|
149
|
+
Se você tem arquivos não-versionados em paths que o framework gera (`.claude/`, `.qwen/`, `.gemini/`, `.cursor/`, `.agents/`, etc), o git recusa o merge para evitar perda. O CLI detecta e oferece:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm run gos:update -- --clobber-untracked
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Isso renomeia os arquivos conflitantes para `.bak.<timestamp>` antes do merge. Como esses paths são **sempre regenerados** por `npm run sync:ides`, o backup é só por garantia — você pode deletar depois.
|
|
156
|
+
|
|
157
|
+
Combinar com `--allow-unrelated` quando ambos os casos ocorrerem (típico de workspaces antigos):
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run gos:update -- --allow-unrelated --clobber-untracked
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Alternativa segura (não toca seu git, apenas atualiza `.gos/`):
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
gos install --force
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Resgate de stashes presos
|
|
170
|
+
|
|
171
|
+
Se você teve falhas anteriores que deixaram stashes:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm run gos:rescue # lista todos com stat
|
|
175
|
+
npm run gos:rescue -- --pop-latest # aplica o mais recente
|
|
176
|
+
npm run gos:rescue -- --drop-all # remove todos (após revisão)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Resumo (qual comando para qual cenário)
|
|
180
|
+
|
|
181
|
+
| Cenário | Comando |
|
|
182
|
+
|---------|---------|
|
|
183
|
+
| Atualizar CLI `gos` global | `npm install -g ganbatte-os@latest` |
|
|
184
|
+
| Atualizar G-OS num projeto consumidor | `gos install --force` |
|
|
185
|
+
| Atualizar G-OS num fork/clone do repo | `npm run gos:update` |
|
|
186
|
+
| Stashes presos de updates falhos | `npm run gos:rescue` |
|
|
187
|
+
| Saber em qual cenário você está | `npm run gos:version` |
|
|
188
|
+
| Validar tudo de uma vez | `npm run gos:doctor` |
|
|
70
189
|
|
|
71
190
|
> [!NOTE]
|
|
72
191
|
> A pasta `.agent/` que pode aparecer na raiz do workspace e criada pela IDE Google Antigravity / Gemini Code Assist — nao faz parte do setup padrao do ganbatte-os.
|
|
@@ -136,7 +255,7 @@ O `ganbatte-os` utiliza uma estrutura **encapsulada** para manter seu projeto li
|
|
|
136
255
|
|
|
137
256
|
## Plan Pipeline (stack-aware)
|
|
138
257
|
|
|
139
|
-
Pipeline padronizado para criacao de planos por tela. Toda tela
|
|
258
|
+
Pipeline padronizado para criacao de planos por tela. **Toda tela = 1 plano**. Toda execucao gera plano + tasks + contexto, com status auditavel e contrato de stack.
|
|
140
259
|
|
|
141
260
|
### Fluxo
|
|
142
261
|
|
|
@@ -161,31 +280,206 @@ Pipeline padronizado para criacao de planos por tela. Toda tela vira um plano +
|
|
|
161
280
|
|
|
162
281
|
### Configuracao por workspace
|
|
163
282
|
|
|
164
|
-
`.gos-local/plan-paths.json` define onde cada projeto guarda seus artefatos. Cada projeto/dev pode organizar diferente
|
|
283
|
+
`.gos-local/plan-paths.json` define onde cada projeto guarda seus artefatos. Cada projeto/dev pode organizar diferente.
|
|
284
|
+
|
|
285
|
+
**Inicializar com defaults** (helper):
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
node .gos/scripts/tools/plan-paths.js init # cria .gos-local/plan-paths.json se nao existir
|
|
289
|
+
node .gos/scripts/tools/plan-paths.js show # imprime config atual
|
|
290
|
+
node .gos/scripts/tools/plan-paths.js get planos # imprime path resolvido para "planos"
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Exemplo de config** (caso real do `packages/fractus`):
|
|
165
294
|
|
|
166
295
|
```json
|
|
167
296
|
{
|
|
168
297
|
"schema": "gos.plan-paths.v1",
|
|
169
298
|
"dirs": {
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
299
|
+
"projeto": "src/",
|
|
300
|
+
"storybook": ".referencia-storybook/",
|
|
301
|
+
"design_system_doc": ".referencia-storybook/docs/DESIGN_SYSTEM_REFERENCE.md",
|
|
302
|
+
"components": ".referencia-storybook/components/",
|
|
303
|
+
"stories": ".referencia-storybook/stories/",
|
|
304
|
+
"planos": "docs/plans/",
|
|
305
|
+
"tasks": "docs/plans/{plan}/tasks/",
|
|
306
|
+
"contexto": "docs/plans/{plan}/context.md",
|
|
307
|
+
"progress": "progress.txt",
|
|
308
|
+
"stack": "docs/stack.md",
|
|
309
|
+
"adr": "docs/adr/",
|
|
310
|
+
"postman": "docs/postman/",
|
|
311
|
+
"regras_negocio": "docs/regras-de-negocio/"
|
|
177
312
|
},
|
|
178
313
|
"knowledge_sources": [
|
|
179
314
|
{ "kind": "postman", "path": "docs/postman/", "required": false },
|
|
180
315
|
{ "kind": "business-rules", "path": "docs/regras-de-negocio/", "required": false },
|
|
316
|
+
{ "kind": "adr", "path": "docs/adr/", "required": false },
|
|
181
317
|
{ "kind": "design-system", "path": ".referencia-storybook/docs/DESIGN_SYSTEM_REFERENCE.md", "required": true }
|
|
182
|
-
]
|
|
318
|
+
],
|
|
319
|
+
"naming": { "plan_prefix": "PLAN", "task_prefix": "T", "seq_padding": 3 },
|
|
320
|
+
"figma": { "mcp_enabled": true, "default_file_url": null }
|
|
183
321
|
}
|
|
184
322
|
```
|
|
185
323
|
|
|
186
|
-
Helpers
|
|
324
|
+
Helpers em `.gos/scripts/tools/`:
|
|
325
|
+
- `plan-paths.js` — resolve caminhos do projeto-cliente
|
|
326
|
+
- `plan-status.js` — valida transicoes de status (state machine)
|
|
327
|
+
- `stack-scan.js` — infere stack lendo `package.json`, configs e `knowledge_sources`
|
|
328
|
+
|
|
329
|
+
### Exemplos de uso (end-to-end)
|
|
330
|
+
|
|
331
|
+
Todos os comandos abaixo sao invocados via slash command no `gos-master` (Claude Code, Gemini, Cursor, etc).
|
|
332
|
+
|
|
333
|
+
#### 1. Bootstrap do workspace (uma vez por projeto)
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
/gos:agents:gos-master
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Em seguida, no chat:
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
*stack refresh
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Saida esperada:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
## Stack profilada — packages/fractus
|
|
349
|
+
|
|
350
|
+
- Framework: Next.js 15 (App Router)
|
|
351
|
+
- DB: Supabase (read-only)
|
|
352
|
+
- Design System: .referencia-storybook
|
|
353
|
+
- Knowledge sources: 4 (postman, business-rules, adr, design-system)
|
|
354
|
+
|
|
355
|
+
stack.md: docs/stack.md
|
|
356
|
+
lock: .gos-local/stack.lock.json
|
|
357
|
+
hashes: 12 arquivos
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Verifique sempre que algo na stack mudar (lib, framework, ORM):
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
*stack drift
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### 2. Criar plano para uma tela
|
|
367
|
+
|
|
368
|
+
A partir de URL Figma (autodetecta e usa Figma MCP):
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
*plan https://www.figma.com/design/kXd8uP6dgeSuQypFnPmuQP/FRACTUS?node-id=9140-25387
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
A partir de descricao livre:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
*plan tela de checkout com formulario de pagamento e resumo do pedido
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Saida:
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
## Plano criado: PLAN-042-checkout
|
|
384
|
+
|
|
385
|
+
- plan.md: docs/plans/PLAN-042-checkout/plan.md
|
|
386
|
+
- context.md: docs/plans/PLAN-042-checkout/context.md
|
|
387
|
+
- tasks/: docs/plans/PLAN-042-checkout/tasks/ (5 tasks: T-042-001 ... T-042-005)
|
|
388
|
+
- progress: atualizado (status=pendente)
|
|
389
|
+
- stack_ref: docs/stack.md@a1b2c3d
|
|
390
|
+
|
|
391
|
+
Proximos passos:
|
|
392
|
+
1. Revisar plan.md e checklist de aceite
|
|
393
|
+
2. *progress status T-042-001 em-andamento
|
|
394
|
+
3. Executar
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Telas complexas (modal + drawer + sub-rotas) sao **subdivididas automaticamente** em planos filhos:
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
PLAN-042-checkout (pai, checklist consolidado)
|
|
401
|
+
├── PLAN-042.1-payment-modal (filho)
|
|
402
|
+
├── PLAN-042.2-summary-drawer
|
|
403
|
+
└── PLAN-042.3-confirm-page
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### 3. Executar tasks
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
*progress show # mostra progress.txt atual
|
|
410
|
+
*progress status T-042-001 em-andamento # iniciar task
|
|
411
|
+
# ... dev implementa ...
|
|
412
|
+
*progress status T-042-001 validacao # task pronta para revisao (commit preparado, nao pushado)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Tentar pular validacao falha:
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
*progress status T-042-001 concluido
|
|
419
|
+
> erro: transicao invalida: em-andamento → concluido
|
|
420
|
+
> use --rollback para voltar para pendente
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### 4. Fechar o plano
|
|
424
|
+
|
|
425
|
+
Quando todas as tasks estao em `validacao` e o checklist do plano esta completo:
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
*progress status PLAN-042-checkout validacao # validacao humana + QA
|
|
429
|
+
*progress status PLAN-042-checkout concluido # SOMENTE apos aprovacao
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
`progress-tracker compact` periodicamente reescreve `progress.txt` removendo tasks `concluido` antigas para manter o arquivo < 4kB (otimizado para LLM).
|
|
433
|
+
|
|
434
|
+
#### 5. Mudanca de arquitetura (excecao)
|
|
435
|
+
|
|
436
|
+
Se a tela exigir alteracao da stack (nova lib, novo padrao de fetching, schema novo):
|
|
437
|
+
|
|
438
|
+
```
|
|
439
|
+
*plan tela-relatorios --allow-arch-change
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Isso forca a Fase 2 propositiva e gera um ADR em `docs/adr/ADR-NNNN-<slug>.md` antes de prosseguir. O plano resultante referencia o ADR no frontmatter (`arch_change: true`).
|
|
443
|
+
|
|
444
|
+
### Estrutura de saida
|
|
445
|
+
|
|
446
|
+
Apos `*plan tela-checkout`:
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
docs/plans/PLAN-042-checkout/
|
|
450
|
+
├── plan.md # frontmatter (id, tela, figma_url, status, stack_ref) + secoes fixas
|
|
451
|
+
├── context.md # denso, indice de arquivos, decisoes, riscos
|
|
452
|
+
└── tasks/
|
|
453
|
+
├── T-042-001-criar-rota-checkout.md
|
|
454
|
+
├── T-042-002-fetching-supabase.md
|
|
455
|
+
├── T-042-003-form-pagamento.md
|
|
456
|
+
├── T-042-004-resumo-pedido.md
|
|
457
|
+
└── T-042-005-estados-erro-loading.md
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`progress.txt` na raiz do projeto fica:
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
# progress.l1
|
|
464
|
+
ts=2026-05-01T18:22:00-03:00
|
|
465
|
+
project=fractus
|
|
466
|
+
plan_active=docs/plans/PLAN-042-checkout/plan.md
|
|
467
|
+
tasks_dir=docs/plans/PLAN-042-checkout/tasks/
|
|
468
|
+
context=docs/plans/PLAN-042-checkout/context.md
|
|
469
|
+
stack_ref=docs/stack.md@a1b2c3d
|
|
470
|
+
status=em-andamento
|
|
471
|
+
|
|
472
|
+
last_done=T-042-002 fetching-supabase
|
|
473
|
+
current=T-042-003 form-pagamento
|
|
474
|
+
next=T-042-004 resumo-pedido
|
|
475
|
+
|
|
476
|
+
blockers=
|
|
477
|
+
notes=fetch usa server component em app/checkout/page.tsx
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Playbook completo
|
|
187
481
|
|
|
188
|
-
|
|
482
|
+
[`.gos/playbooks/plan-creation-playbook.md`](./.gos/playbooks/plan-creation-playbook.md) — documenta o fluxo end-to-end com troubleshooting.
|
|
189
483
|
|
|
190
484
|
## Documentacao
|
|
191
485
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ganbatte-os",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.33",
|
|
4
4
|
"description": "Framework operacional para design-to-code, squads de entrega e sprint sync com ClickUp.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gos": ".gos/scripts/cli/gos-cli.js"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"gos:update": "node .gos/scripts/cli/gos-cli.js update",
|
|
11
11
|
"gos:doctor": "node .gos/scripts/cli/gos-cli.js doctor",
|
|
12
12
|
"gos:version": "node .gos/scripts/cli/gos-cli.js version",
|
|
13
|
+
"gos:rescue": "node .gos/scripts/cli/gos-cli.js rescue",
|
|
13
14
|
"clickup": "node .gos/scripts/tools/clickup.js",
|
|
14
15
|
"sync:ides": "node .gos/scripts/integrations/setup-ide-adapters.js",
|
|
15
16
|
"check:ides": "node .gos/scripts/integrations/check-ide-compat.js",
|