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.
- package/bin/ts.js +12 -16
- package/lib/stack.js +51 -0
- 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
|
-
|
|
690
|
-
|
|
691
|
-
if (
|
|
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
|
|
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
|
|
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
|
-
|
|
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 };
|