terminal-smart-cli 0.63.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/bin/ts.js +12 -16
  2. package/lib/stack.js +51 -0
  3. package/package.json +1 -1
package/bin/ts.js CHANGED
@@ -682,33 +682,29 @@ async function hooksCmd(words) {
682
682
  async function initCmd() {
683
683
  const _fs = require('fs'), _p = require('path');
684
684
  const cwd = process.cwd();
685
+ const auto = FLAGS.has('--auto') || FLAGS.has('--stack');
685
686
  const dest = _p.join(cwd, '.ts-memoria.md');
686
- const has = (f) => _fs.existsSync(_p.join(cwd, f));
687
- const readJson = (f) => { try { return JSON.parse(_fs.readFileSync(_p.join(cwd, f), 'utf8')); } catch (_) { return null; } };
688
687
  const en = cfg.lang === 'en';
689
- let tipo = en ? 'unknown' : 'desconhecido', build = [], run = [], test = [];
690
- const pkg = has('package.json') && readJson('package.json');
691
- if (pkg) {
692
- tipo = 'Node.js' + (pkg.dependencies && (pkg.dependencies.react || pkg.dependencies.next) ? ' (React/Next)' : '');
693
- const s = pkg.scripts || {};
694
- if (s.build) build.push('npm run build'); if (s.dev || s.start) run.push('npm run ' + (s.dev ? 'dev' : 'start')); if (s.test) test.push('npm test');
695
- } else if (has('pubspec.yaml')) { tipo = 'Flutter/Dart'; build = ['flutter build']; run = ['flutter run']; test = ['flutter test']; }
696
- else if (has('requirements.txt') || has('pyproject.toml')) { tipo = 'Python'; run = ['python main.py']; test = ['pytest']; }
697
- else if (has('go.mod')) { tipo = 'Go'; build = ['go build ./...']; run = ['go run .']; test = ['go test ./...']; }
698
- else if (has('Cargo.toml')) { tipo = 'Rust'; build = ['cargo build']; run = ['cargo run']; test = ['cargo test']; }
699
- else if (has('index.html')) { tipo = en ? 'Static web (HTML/JS)' : 'Web estático (HTML/JS)'; run = ['python -m http.server']; }
688
+ const st = require('../lib/stack').detectStack(cwd);
689
+ let { tipo, build, run, test } = st;
690
+ if (tipo === 'desconhecido' && en) tipo = 'unknown';
700
691
  const top = _fs.readdirSync(cwd).filter(n => !n.startsWith('.') && n !== 'node_modules').slice(0, 25).join(', ');
701
692
  const nl = (a) => a.length ? a.map(x => '`' + x + '`').join(' · ') : (en ? '(fill in)' : '(preencher)');
693
+ // Seção de convenções: com --auto vem PREENCHIDA pelo detector (determinístico); senão fica em branco.
694
+ const convBlock = (auto && st.conv.length)
695
+ ? st.conv.map(c => '- ' + c).join('\n') + '\n- ' + (en ? '(add project-specific pitfalls / how to deploy)' : '(acrescente armadilhas específicas / como fazer deploy)')
696
+ : '- ' + (en ? '(add what the agent should always know: architecture, pitfalls, how to deploy)' : '(anote o que o agente sempre deve saber: arquitetura, pegadinhas, como fazer deploy)');
702
697
  const body = (en
703
- ? `# Project memory (ts)\n\n> Auto-generated by \`ts init\`. Edit freely — the ts agent reads this every session.\n\n- **Type:** ${tipo}\n- **Build:** ${nl(build)}\n- **Run:** ${nl(run)}\n- **Test:** ${nl(test)}\n- **Top-level:** ${top}\n\n## Conventions / gotchas\n- (add what the agent should always know: architecture, pitfalls, how to deploy)\n`
704
- : `# Memória do projeto (ts)\n\n> Gerado por \`ts init\`. Edite à vontade — o agente ts lê isto toda sessão.\n\n- **Tipo:** ${tipo}\n- **Build:** ${nl(build)}\n- **Rodar:** ${nl(run)}\n- **Testar:** ${nl(test)}\n- **Raiz:** ${top}\n\n## Convenções / armadilhas\n- (anote o que o agente sempre deve saber: arquitetura, pegadinhas, como fazer deploy)\n`);
698
+ ? `# Project memory (ts)\n\n> Auto-generated by \`ts init${auto ? ' --auto' : ''}\`. Edit freely — the ts agent reads this every session.\n\n- **Type:** ${tipo}\n- **Build:** ${nl(build)}\n- **Run:** ${nl(run)}\n- **Test:** ${nl(test)}\n- **Top-level:** ${top}\n\n## Conventions / gotchas\n${convBlock}\n`
699
+ : `# Memória do projeto (ts)\n\n> Gerado por \`ts init${auto ? ' --auto' : ''}\`. Edite à vontade — o agente ts lê isto toda sessão.\n\n- **Tipo:** ${tipo}\n- **Build:** ${nl(build)}\n- **Rodar:** ${nl(run)}\n- **Testar:** ${nl(test)}\n- **Raiz:** ${top}\n\n## Convenções / armadilhas\n${convBlock}\n`);
705
700
  if (_fs.existsSync(dest)) {
706
701
  const ok = await ui.ask(C.warn('▲ ') + (en ? '.ts-memoria.md already exists. Overwrite? (backup kept) [s/N] ' : '.ts-memoria.md já existe. Sobrescrever? (com backup) [s/N] '));
707
702
  if (!['s', 'sim', 'y', 'yes'].includes(String(ok).trim().toLowerCase())) { console.log(' ' + C.dim(en ? 'cancelled.' : 'cancelado.')); return; }
708
703
  let r; try { r = await require('../lib/tools').execute('escrever_arquivo', { caminho: dest, conteudo: body }, { baseDir: cwd }); } catch (_) {}
709
704
  if (!r || r.erro) { try { _fs.writeFileSync(dest, body); } catch (e) { console.error(ui.errLine((en ? 'Failed to write .ts-memoria.md: ' : 'Falha ao escrever .ts-memoria.md: ') + e.message)); return; } }
710
705
  } else { _fs.writeFileSync(dest, body); }
711
- console.log(' ' + C.ok('✔ ') + (en ? 'Created ' : 'Criado ') + C.bold('.ts-memoria.md') + C.dim(' — ' + tipo) + '\n ' + C.dim(en ? 'Edit it to teach the agent your project. It loads automatically.' : 'Edite pra ensinar o projeto ao agente. Ele carrega sozinho.'));
706
+ const detN = auto ? st.conv.length : 0;
707
+ console.log(' ' + C.ok('✔ ') + (en ? 'Created ' : 'Criado ') + C.bold('.ts-memoria.md') + C.dim(' — ' + tipo + (detN ? (en ? ` · ${detN} convention(s) detected` : ` · ${detN} convenção(ões) detectada(s)`) : '')) + '\n ' + C.dim(auto ? (en ? 'Detected your stack automatically. Add project-specific notes and it loads every session.' : 'Detectei sua stack sozinho. Acrescente notas específicas — carrega toda sessão.') : (en ? 'Tip: `ts init --auto` detects your stack and fills the conventions.' : 'Dica: `ts init --auto` detecta a stack e preenche as convenções.')));
712
708
  }
713
709
 
714
710
  // ── git worktrees: isola uma run numa cópia descartável do repo ──
package/lib/stack.js ADDED
@@ -0,0 +1,51 @@
1
+ // Detecta a STACK de um projeto de forma DETERMINÍSTICA (zero IA): tipo, comandos
2
+ // (build/run/test) e CONVENÇÕES (o que o agente sempre deve saber). Lê config files +
3
+ // deps do package.json. Puro/testável. Usado por `ts init --auto` (e reutilizável pelo meta).
4
+ 'use strict';
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ function detectStack(cwd) {
9
+ const dir = cwd || process.cwd();
10
+ const has = (f) => { try { return fs.existsSync(path.join(dir, f)); } catch (_) { return false; } };
11
+ const readJson = (f) => { try { return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); } catch (_) { return null; } };
12
+ let tipo = 'desconhecido', build = [], run = [], test = [];
13
+ const conv = [];
14
+ const pkg = has('package.json') && readJson('package.json');
15
+ if (pkg) {
16
+ const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
17
+ const dep = (n) => Object.prototype.hasOwnProperty.call(deps, n);
18
+ tipo = dep('next') ? 'Next.js' : dep('nuxt') ? 'Nuxt' : dep('@remix-run/react') ? 'Remix' : dep('react') ? 'React' : dep('vue') ? 'Vue' : dep('svelte') ? 'Svelte' : dep('@angular/core') ? 'Angular' : (dep('express') || dep('fastify') || dep('koa')) ? 'Node backend' : 'Node.js';
19
+ const s = pkg.scripts || {};
20
+ if (s.build) build.push('npm run build'); if (s.dev || s.start) run.push('npm run ' + (s.dev ? 'dev' : 'start')); if (s.test) test.push('npm test'); if (s.lint) test.push('npm run lint');
21
+ if (has('tsconfig.json') || dep('typescript')) conv.push('**TypeScript** — mantenha os tipos corretos; rode o type-check (tsc/`npm run build`) antes de concluir. Não use `any` à toa.');
22
+ if (dep('next')) conv.push('**Next.js** — respeite o roteamento do framework (App Router em `app/` ou Pages em `pages/`); não reinvente SSR/rotas/data-fetching.');
23
+ if (dep('vite') || has('vite.config.js') || has('vite.config.ts')) conv.push('**Vite** — dev/build pelo Vite; imports com alias respeitam `vite.config`.');
24
+ if (dep('tailwindcss') || has('tailwind.config.js') || has('tailwind.config.ts')) conv.push('**Tailwind CSS** — estilize com classes utilitárias; evite CSS solto/inline fora do padrão do projeto.');
25
+ if (dep('@prisma/client') || dep('prisma') || has('prisma/schema.prisma')) conv.push('**Prisma** — altere `schema.prisma` + `prisma migrate`; NÃO edite SQL/tabela na mão.');
26
+ if (dep('drizzle-orm')) conv.push('**Drizzle ORM** — schema em código + migrations; não edite o banco direto.');
27
+ if (dep('jest') || dep('vitest') || dep('mocha')) conv.push('**Testes** (' + (dep('vitest') ? 'vitest' : dep('jest') ? 'jest' : 'mocha') + ') — rode e mantenha verdes; toda mudança de lógica precisa de teste.');
28
+ if (has('.eslintrc') || has('.eslintrc.js') || has('.eslintrc.json') || has('eslint.config.js') || dep('eslint')) conv.push('**ESLint** — rode o lint antes de concluir; siga as regras do projeto.');
29
+ if (has('.prettierrc') || has('.prettierrc.json') || dep('prettier')) conv.push('**Prettier** — mantenha a formatação (não reformate arquivos inteiros à toa).');
30
+ if (pkg.workspaces || has('pnpm-workspace.yaml') || has('turbo.json') || has('lerna.json')) conv.push('**Monorepo** — trabalhe no pacote/workspace certo; não misture dependências entre pacotes.');
31
+ } else if (has('pubspec.yaml')) { tipo = 'Flutter/Dart'; build = ['flutter build']; run = ['flutter run']; test = ['flutter test']; conv.push('**Flutter** — widgets + state management do projeto; `flutter analyze` limpo antes de concluir.'); }
32
+ else if (has('requirements.txt') || has('pyproject.toml')) {
33
+ tipo = 'Python'; run = ['python main.py']; test = ['pytest'];
34
+ const req = has('requirements.txt') ? (() => { try { return fs.readFileSync(path.join(dir, 'requirements.txt'), 'utf8').toLowerCase(); } catch (_) { return ''; } })() : '';
35
+ if (/django/.test(req)) { tipo = 'Python/Django'; run = ['python manage.py runserver']; conv.push('**Django** — use models/migrations (`makemigrations`+`migrate`); respeite apps e settings.'); }
36
+ else if (/fastapi/.test(req)) { tipo = 'Python/FastAPI'; run = ['uvicorn main:app --reload']; conv.push('**FastAPI** — rotas com Pydantic; mantenha os schemas tipados.'); }
37
+ else if (/flask/.test(req)) { tipo = 'Python/Flask'; run = ['flask run']; }
38
+ conv.push('**Python** — use o venv do projeto; não instale global. `pytest` verde antes de concluir.');
39
+ }
40
+ else if (has('go.mod')) { tipo = 'Go'; build = ['go build ./...']; run = ['go run .']; test = ['go test ./...']; conv.push('**Go** — `go build`/`go vet`/`go test ./...` limpos; erros tratados explicitamente.'); }
41
+ else if (has('Cargo.toml')) { tipo = 'Rust'; build = ['cargo build']; run = ['cargo run']; test = ['cargo test']; conv.push('**Rust** — `cargo clippy` e `cargo test` limpos antes de concluir.'); }
42
+ else if (has('composer.json')) { tipo = 'PHP'; run = ['php -S localhost:8000']; conv.push('**PHP/Composer** — `composer install`; siga o autoload PSR do projeto.'); }
43
+ else if (has('index.html')) { tipo = 'Web estático (HTML/JS)'; run = ['python -m http.server']; }
44
+ // sinais transversais (independem da linguagem)
45
+ if (has('Dockerfile') || has('docker-compose.yml') || has('docker-compose.yaml') || has('compose.yaml')) conv.push('**Docker** — mudanças de ambiente/infra passam pelo Docker; rebuild da imagem quando alterar deps.');
46
+ if (has('.env.example') || has('.env.sample')) conv.push('**Config via .env** — NUNCA commite segredos; use `.env.example` como referência das variáveis.');
47
+ if (has('.git')) conv.push('**Git** — commits pequenos e descritivos; não faça `push --force` na branch principal.');
48
+ return { tipo, build, run, test, conv };
49
+ }
50
+
51
+ module.exports = { detectStack };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.63.0",
3
+ "version": "0.64.0",
4
4
  "description": "Terminal Smart no seu terminal — pergunte, analise logs por pipe e orquestre agentes de IA. Comando: ts",
5
5
  "bin": {
6
6
  "ts": "bin/ts.js"