@luanpdd/kit-mcp 1.35.0 → 1.36.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/cli.js +2 -2
- package/bin/mcp.js +6 -6
- package/bin/ui.js +74 -74
- package/gates/ai-prompt-stability.md +120 -120
- package/gates/budget-description.md +68 -68
- package/gates/confidence.md +29 -29
- package/gates/dependency-check.md +33 -33
- package/gates/dept-cycle-prevention.md +179 -179
- package/gates/golden-signals-coverage.md +133 -133
- package/gates/legacy-refactor-safety.md +178 -178
- package/gates/multi-tenant-rls-coverage.md +102 -102
- package/gates/no-personal-uuid.md +72 -72
- package/gates/obs-agents-mcp-supabase.md +86 -86
- package/gates/obs-skills-frontmatter.md +76 -76
- package/gates/observability-coverage.md +151 -151
- package/gates/omm-no-regression.md +83 -83
- package/gates/postmortem-template-required.md +127 -127
- package/gates/prr-checklist-coverage.md +128 -128
- package/gates/regression.md +32 -32
- package/gates/release-pipeline-policy.md +132 -132
- package/gates/secrets-scan.md +33 -33
- package/gates/service-role-not-in-user-facing.md +113 -113
- package/gates/skill-must-include.md +71 -71
- package/gates/sync-idempotent.md +62 -62
- package/gates/verify-phase-goal.md +34 -34
- package/kit/agents/designer-ui.md +216 -216
- package/kit/agents/workflow-generator.md +537 -167
- package/kit/commands/adicionar-backlog.md +1 -1
- package/kit/commands/adicionar-fase.md +1 -1
- package/kit/commands/adicionar-tarefa.md +1 -1
- package/kit/commands/auditar-observabilidade.md +103 -103
- package/kit/commands/auditar-toil.md +129 -129
- package/kit/commands/caracterizar-prompt.md +195 -195
- package/kit/commands/criar-workflow.md +158 -158
- package/kit/commands/definir-perfil.md +1 -1
- package/kit/commands/definir-slo.md +108 -108
- package/kit/commands/fio.md +1 -1
- package/kit/commands/golden-signals.md +142 -142
- package/kit/commands/instrumentar-fase.md +200 -200
- package/kit/commands/investigar-producao.md +162 -162
- package/kit/commands/observabilidade.md +118 -118
- package/kit/commands/postmortem.md +179 -179
- package/kit/commands/prr.md +205 -205
- package/kit/commands/publicar-rapido.md +207 -207
- package/kit/commands/risk-budget.md +220 -220
- package/kit/commands/sre.md +230 -230
- package/kit/file-manifest.json +424 -424
- package/kit/framework/references/output-style.md +22 -22
- package/kit/hooks/post-apply-migration.js +199 -199
- package/kit/hooks/sidecar-tool-publisher.js +210 -210
- package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
- package/kit/skills/_shared-legacy/glossary.md +389 -389
- package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
- package/kit/skills/_shared-observability/glossary.md +396 -396
- package/kit/skills/_shared-sre/glossary.md +712 -712
- package/kit/skills/_shared-supabase/glossary.md +234 -234
- package/kit/skills/blameless-postmortems/SKILL.md +340 -340
- package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
- package/kit/skills/cascading-failures/SKILL.md +311 -311
- package/kit/skills/core-analysis-loop/SKILL.md +352 -352
- package/kit/skills/distributed-tracing/SKILL.md +362 -362
- package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -223
- package/kit/skills/eliminating-toil/SKILL.md +243 -243
- package/kit/skills/event-based-slos/SKILL.md +296 -296
- package/kit/skills/four-golden-signals/SKILL.md +314 -314
- package/kit/skills/hermetic-builds/SKILL.md +323 -323
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
- package/kit/skills/llm-as-dependency/SKILL.md +436 -436
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
- package/kit/skills/observability-driven-development/SKILL.md +315 -315
- package/kit/skills/observability-maturity-model/SKILL.md +222 -222
- package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
- package/kit/skills/production-readiness-review/SKILL.md +305 -305
- package/kit/skills/release-engineering/SKILL.md +367 -367
- package/kit/skills/retry-strategies/SKILL.md +372 -372
- package/kit/skills/sre-risk-management/SKILL.md +221 -221
- package/kit/skills/structured-events/SKILL.md +265 -265
- package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
- package/kit/skills/supabase-database-functions/SKILL.md +332 -332
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
- package/kit/skills/supabase-storage/SKILL.md +234 -234
- package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
- package/kit/skills/telemetry-sampling/SKILL.md +256 -256
- package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
- package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
- package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
- package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
- package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
- package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
- package/kit/skills/ui-tipografia/SKILL.md +211 -211
- package/package.json +1 -1
- package/src/cli/index.js +1114 -1114
- package/src/cli/render.js +194 -194
- package/src/cli/upgrade-check.js +135 -135
- package/src/core/error-redaction.js +76 -76
- package/src/core/failures.js +153 -153
- package/src/core/gate-runner.js +205 -205
- package/src/core/gates.js +82 -82
- package/src/core/logger.js +170 -170
- package/src/core/manifest-verify.js +174 -174
- package/src/core/metrics.js +268 -268
- package/src/core/notify.js +60 -60
- package/src/core/path-safety.js +141 -141
- package/src/core/replays.js +120 -120
- package/src/core/ui.js +185 -185
- package/src/mcp-server/install.js +149 -149
- package/src/mcp-server/roots.js +124 -124
- package/src/ui/auto-spawn.js +113 -113
- package/src/ui/browser.js +78 -78
- package/src/ui/client.js +130 -130
- package/src/ui/events.js +65 -65
- package/src/ui/lockfile.js +191 -191
- package/src/ui/port.js +67 -67
- package/src/ui/server.js +547 -547
- package/src/ui/wrapper.js +129 -129
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# Output Style — Caveman (compartilhado por todos os agentes framework)
|
|
2
|
-
|
|
3
|
-
> Referenciado por agentes via `@./.claude/framework/references/output-style.md`. Agentes herdam essas regras automaticamente — não duplique o conteúdo no agente.
|
|
4
|
-
|
|
5
|
-
**Estilo: caveman — compressão alta na fala, prosa normal em artefatos.**
|
|
6
|
-
|
|
7
|
-
Em mensagens conversacionais, logs e relatórios ao orquestrador:
|
|
8
|
-
- Cortar: filler (just/really/basically/actually/simply), pleasantries (claro/com certeza/feliz em ajudar), hedging desnecessário, artigos quando não compromete clareza
|
|
9
|
-
- Fragments OK. Sinônimos curtos. Padrão: `[coisa] [ação] [razão]. [próximo passo].`
|
|
10
|
-
- Termos técnicos exatos. Código inalterado. Erros citados literais.
|
|
11
|
-
- NÃO: "Claro! O problema que você está enfrentando provavelmente é causado por..."
|
|
12
|
-
- SIM: "Bug em auth middleware. Token expiry usa `<` em vez de `<=`. Fix:"
|
|
13
|
-
|
|
14
|
-
**Auto-clarity — sair do caveman quando:**
|
|
15
|
-
- Avisos de segurança ou ações destrutivas/irreversíveis
|
|
16
|
-
- Sequências multi-passo onde fragmentar arrisca má interpretação
|
|
17
|
-
- Usuário pediu clarificação ou está confuso
|
|
18
|
-
|
|
19
|
-
**Boundary CRÍTICO — artefatos em `.planning/` mantêm formato completo:**
|
|
20
|
-
PLAN.md, ROADMAP.md, REQUIREMENTS.md, CONTEXT.md, SUMMARY.md, VERIFICATION.md e qualquer outro artefato em `.planning/` é o **prompt de execução** ou registro auditável que outros agentes/humanos vão consumir. Esses DEVEM seguir prosa estruturada conforme template, com regras inequívocas, dependências explícitas e critérios completos. Caveman em artefatos = ambíguo = quebrado.
|
|
21
|
-
|
|
22
|
-
**Caveman aplica-se SÓ ao raciocínio falado, logs de progresso e retorno ao orquestrador — NUNCA ao conteúdo de artefatos em `.planning/`.**
|
|
1
|
+
# Output Style — Caveman (compartilhado por todos os agentes framework)
|
|
2
|
+
|
|
3
|
+
> Referenciado por agentes via `@./.claude/framework/references/output-style.md`. Agentes herdam essas regras automaticamente — não duplique o conteúdo no agente.
|
|
4
|
+
|
|
5
|
+
**Estilo: caveman — compressão alta na fala, prosa normal em artefatos.**
|
|
6
|
+
|
|
7
|
+
Em mensagens conversacionais, logs e relatórios ao orquestrador:
|
|
8
|
+
- Cortar: filler (just/really/basically/actually/simply), pleasantries (claro/com certeza/feliz em ajudar), hedging desnecessário, artigos quando não compromete clareza
|
|
9
|
+
- Fragments OK. Sinônimos curtos. Padrão: `[coisa] [ação] [razão]. [próximo passo].`
|
|
10
|
+
- Termos técnicos exatos. Código inalterado. Erros citados literais.
|
|
11
|
+
- NÃO: "Claro! O problema que você está enfrentando provavelmente é causado por..."
|
|
12
|
+
- SIM: "Bug em auth middleware. Token expiry usa `<` em vez de `<=`. Fix:"
|
|
13
|
+
|
|
14
|
+
**Auto-clarity — sair do caveman quando:**
|
|
15
|
+
- Avisos de segurança ou ações destrutivas/irreversíveis
|
|
16
|
+
- Sequências multi-passo onde fragmentar arrisca má interpretação
|
|
17
|
+
- Usuário pediu clarificação ou está confuso
|
|
18
|
+
|
|
19
|
+
**Boundary CRÍTICO — artefatos em `.planning/` mantêm formato completo:**
|
|
20
|
+
PLAN.md, ROADMAP.md, REQUIREMENTS.md, CONTEXT.md, SUMMARY.md, VERIFICATION.md e qualquer outro artefato em `.planning/` é o **prompt de execução** ou registro auditável que outros agentes/humanos vão consumir. Esses DEVEM seguir prosa estruturada conforme template, com regras inequívocas, dependências explícitas e critérios completos. Caveman em artefatos = ambíguo = quebrado.
|
|
21
|
+
|
|
22
|
+
**Caveman aplica-se SÓ ao raciocínio falado, logs de progresso e retorno ao orquestrador — NUNCA ao conteúdo de artefatos em `.planning/`.**
|
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// hook-version: 1.4.1
|
|
3
|
-
// SEC-13-05: flush-before-exit category = A (stderr.write + immediate exit)
|
|
4
|
-
// Fix applied: process.stderr.write(summary, () => process.exit(0)) on success path.
|
|
5
|
-
// kit-mcp · Post-apply Migration Hook (PostToolUse)
|
|
6
|
-
//
|
|
7
|
-
// Triggers automatically AFTER a successful Supabase MCP apply_migration call.
|
|
8
|
-
// Performs the 3 manual steps that devs always forget:
|
|
9
|
-
//
|
|
10
|
-
// (a) Mirror the .sql to supabase/migrations/{TIMESTAMP}_{name}.sql
|
|
11
|
-
// so the project's git history captures the migration.
|
|
12
|
-
// (b) Create a stub note in the Obsidian vault under
|
|
13
|
-
// 07 - Banco de Dados/Migrations/{YYYY}/{TIMESTAMP}_{name}.md
|
|
14
|
-
// (only if the vault is detected — same heuristic as /publicar Step 0.7).
|
|
15
|
-
// (c) Stage both files in git so the next commit picks them up.
|
|
16
|
-
//
|
|
17
|
-
// All three steps are SOFT — failures log to stderr and continue.
|
|
18
|
-
// The hook never blocks the calling tool.
|
|
19
|
-
//
|
|
20
|
-
// Enable via .claude/settings.json:
|
|
21
|
-
// "hooks": {
|
|
22
|
-
// "post_apply_migration": true
|
|
23
|
-
// }
|
|
24
|
-
//
|
|
25
|
-
// Triggered on PostToolUse for tool_name matching Supabase MCP apply_migration.
|
|
26
|
-
// Reads from stdin a JSON envelope including:
|
|
27
|
-
// tool_name — full tool id, ex "mcp__0a71...__apply_migration"
|
|
28
|
-
// tool_input — { name: "20260409_contact_prefs", query: "ALTER TABLE..." }
|
|
29
|
-
// tool_response — server response (if available)
|
|
30
|
-
// project_root — absolute path to the project (best-effort)
|
|
31
|
-
|
|
32
|
-
'use strict';
|
|
33
|
-
|
|
34
|
-
const fs = require('fs');
|
|
35
|
-
const path = require('path');
|
|
36
|
-
const os = require('os');
|
|
37
|
-
const { execSync } = require('child_process');
|
|
38
|
-
|
|
39
|
-
let input = '';
|
|
40
|
-
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
41
|
-
process.stdin.setEncoding('utf8');
|
|
42
|
-
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
43
|
-
process.stdin.on('end', () => {
|
|
44
|
-
clearTimeout(stdinTimeout);
|
|
45
|
-
try {
|
|
46
|
-
const data = JSON.parse(input);
|
|
47
|
-
const toolName = data.tool_name || '';
|
|
48
|
-
|
|
49
|
-
// Match any MCP tool ending in apply_migration.
|
|
50
|
-
if (!/apply_migration$/.test(toolName)) {
|
|
51
|
-
process.exit(0);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const migrationName = data.tool_input?.name || '';
|
|
55
|
-
const sqlBody = data.tool_input?.query || '';
|
|
56
|
-
if (!migrationName || !sqlBody) {
|
|
57
|
-
process.stderr.write('[post-apply-migration] missing name or query — skipping\n');
|
|
58
|
-
process.exit(0);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const projectRoot = data.project_root || process.cwd();
|
|
62
|
-
const ts = formatTimestamp(new Date());
|
|
63
|
-
const safeName = migrationName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
64
|
-
const fileName = `${ts}_${safeName}.sql`;
|
|
65
|
-
|
|
66
|
-
// --- (a) mirror to supabase/migrations/ ---
|
|
67
|
-
const targetDir = path.join(projectRoot, 'supabase', 'migrations');
|
|
68
|
-
let mirroredPath = null;
|
|
69
|
-
try {
|
|
70
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
71
|
-
mirroredPath = path.join(targetDir, fileName);
|
|
72
|
-
fs.writeFileSync(mirroredPath, sqlBody, 'utf8');
|
|
73
|
-
process.stderr.write(`[post-apply-migration] ✓ mirrored to ${path.relative(projectRoot, mirroredPath)}\n`);
|
|
74
|
-
} catch (err) {
|
|
75
|
-
process.stderr.write(`[post-apply-migration] ⚠ mirror failed: ${err.message}\n`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// --- (b) stub in Obsidian vault (best-effort) ---
|
|
79
|
-
const vault = detectObsidianVault();
|
|
80
|
-
let stubPath = null;
|
|
81
|
-
if (vault) {
|
|
82
|
-
try {
|
|
83
|
-
const year = new Date().getFullYear();
|
|
84
|
-
const stubDir = path.join(vault, '07 - Banco de Dados', 'Migrations', String(year));
|
|
85
|
-
fs.mkdirSync(stubDir, { recursive: true });
|
|
86
|
-
stubPath = path.join(stubDir, `${ts}_${safeName}.md`);
|
|
87
|
-
if (!fs.existsSync(stubPath)) {
|
|
88
|
-
fs.writeFileSync(stubPath, renderStub(migrationName, sqlBody, ts), 'utf8');
|
|
89
|
-
process.stderr.write(`[post-apply-migration] ✓ stub criado em ${path.relative(vault, stubPath)}\n`);
|
|
90
|
-
}
|
|
91
|
-
} catch (err) {
|
|
92
|
-
process.stderr.write(`[post-apply-migration] ⚠ stub failed: ${err.message}\n`);
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
process.stderr.write('[post-apply-migration] vault não detectado — pulando stub\n');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// --- (c) git stage (best-effort) ---
|
|
99
|
-
if (mirroredPath) {
|
|
100
|
-
try {
|
|
101
|
-
execSync(`git add "${path.relative(projectRoot, mirroredPath)}"`, { cwd: projectRoot, stdio: 'ignore' });
|
|
102
|
-
process.stderr.write('[post-apply-migration] ✓ staged no git\n');
|
|
103
|
-
} catch (err) {
|
|
104
|
-
// Project may not be a git repo, or .sql may be ignored. Soft-fail.
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// The final advisory printed back to Claude (and to the user via stderr)
|
|
109
|
-
// SEC-13-05: aguardar flush do stderr antes do exit. Sem callback, o
|
|
110
|
-
// resumo final pode ser dropado em pipes lentos (CI/Windows). Os outros
|
|
111
|
-
// process.stderr.write intermediários (linhas ~71/87/93/100) NÃO precisam
|
|
112
|
-
// do callback porque o process continua executando após eles — o event
|
|
113
|
-
// loop drena o buffer naturalmente antes do próximo write.
|
|
114
|
-
if (mirroredPath || stubPath) {
|
|
115
|
-
const lines = ['[post-apply-migration] resumo:'];
|
|
116
|
-
if (mirroredPath) lines.push(` • SQL: ${path.relative(projectRoot, mirroredPath)}`);
|
|
117
|
-
if (stubPath) lines.push(` • Stub: ${path.relative(vault, stubPath)}`);
|
|
118
|
-
lines.push(' → cofre Obsidian: edite o stub e commite quando puder.');
|
|
119
|
-
process.stderr.write(lines.join('\n') + '\n', () => process.exit(0));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
process.exit(0);
|
|
124
|
-
} catch (err) {
|
|
125
|
-
process.stderr.write(`[post-apply-migration] hook error: ${err.message}\n`);
|
|
126
|
-
process.exit(0);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// ───────────────────────────────────────────────────────── helpers
|
|
131
|
-
|
|
132
|
-
function formatTimestamp(d) {
|
|
133
|
-
// YYYYMMDDHHMMSS — same convention used by Supabase migrations.
|
|
134
|
-
const pad = (n) => String(n).padStart(2, '0');
|
|
135
|
-
return (
|
|
136
|
-
d.getFullYear().toString() +
|
|
137
|
-
pad(d.getMonth() + 1) +
|
|
138
|
-
pad(d.getDate()) +
|
|
139
|
-
pad(d.getHours()) +
|
|
140
|
-
pad(d.getMinutes()) +
|
|
141
|
-
pad(d.getSeconds())
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function detectObsidianVault() {
|
|
146
|
-
// Mesma heurística do Passo 0.7 do /publicar.
|
|
147
|
-
if (process.env.OBSIDIAN_TEAM_VAULT && fs.existsSync(process.env.OBSIDIAN_TEAM_VAULT)) {
|
|
148
|
-
return process.env.OBSIDIAN_TEAM_VAULT;
|
|
149
|
-
}
|
|
150
|
-
const candidates = [
|
|
151
|
-
process.env.HOME && path.join(process.env.HOME, 'Documentos', 'Obsidian', 'chat-trynux'),
|
|
152
|
-
process.env.HOME && path.join(process.env.HOME, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
153
|
-
process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Documentos', 'Obsidian', 'chat-trynux'),
|
|
154
|
-
process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
155
|
-
process.env.USER && path.join('/mnt/c/Users', process.env.USER, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
156
|
-
].filter(Boolean);
|
|
157
|
-
for (const c of candidates) {
|
|
158
|
-
try { if (fs.existsSync(c) && fs.statSync(c).isDirectory()) return c; } catch { /* noop */ }
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function renderStub(name, sql, ts) {
|
|
164
|
-
const firstLines = sql
|
|
165
|
-
.split('\n')
|
|
166
|
-
.filter((l) => l.trim().length > 0 && !l.trim().startsWith('--'))
|
|
167
|
-
.slice(0, 8)
|
|
168
|
-
.join('\n');
|
|
169
|
-
return [
|
|
170
|
-
'---',
|
|
171
|
-
`migration: "${name}"`,
|
|
172
|
-
`applied_at: ${new Date().toISOString()}`,
|
|
173
|
-
`timestamp: ${ts}`,
|
|
174
|
-
'status: applied',
|
|
175
|
-
'pr: (preencher quando abrir PR)',
|
|
176
|
-
'---',
|
|
177
|
-
'',
|
|
178
|
-
`# ${name}`,
|
|
179
|
-
'',
|
|
180
|
-
'## O que essa migration faz',
|
|
181
|
-
'',
|
|
182
|
-
'_(uma frase, em linguagem de produto)_',
|
|
183
|
-
'',
|
|
184
|
-
'## Tabelas / colunas afetadas',
|
|
185
|
-
'',
|
|
186
|
-
'_(liste tabelas, colunas, FKs novas/alteradas)_',
|
|
187
|
-
'',
|
|
188
|
-
'## Como reverter',
|
|
189
|
-
'',
|
|
190
|
-
'_(comando ou DDL para desfazer; "irreversível" se for o caso)_',
|
|
191
|
-
'',
|
|
192
|
-
'## Primeiras linhas (para referência)',
|
|
193
|
-
'',
|
|
194
|
-
'```sql',
|
|
195
|
-
firstLines,
|
|
196
|
-
'```',
|
|
197
|
-
'',
|
|
198
|
-
].join('\n');
|
|
199
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hook-version: 1.4.1
|
|
3
|
+
// SEC-13-05: flush-before-exit category = A (stderr.write + immediate exit)
|
|
4
|
+
// Fix applied: process.stderr.write(summary, () => process.exit(0)) on success path.
|
|
5
|
+
// kit-mcp · Post-apply Migration Hook (PostToolUse)
|
|
6
|
+
//
|
|
7
|
+
// Triggers automatically AFTER a successful Supabase MCP apply_migration call.
|
|
8
|
+
// Performs the 3 manual steps that devs always forget:
|
|
9
|
+
//
|
|
10
|
+
// (a) Mirror the .sql to supabase/migrations/{TIMESTAMP}_{name}.sql
|
|
11
|
+
// so the project's git history captures the migration.
|
|
12
|
+
// (b) Create a stub note in the Obsidian vault under
|
|
13
|
+
// 07 - Banco de Dados/Migrations/{YYYY}/{TIMESTAMP}_{name}.md
|
|
14
|
+
// (only if the vault is detected — same heuristic as /publicar Step 0.7).
|
|
15
|
+
// (c) Stage both files in git so the next commit picks them up.
|
|
16
|
+
//
|
|
17
|
+
// All three steps are SOFT — failures log to stderr and continue.
|
|
18
|
+
// The hook never blocks the calling tool.
|
|
19
|
+
//
|
|
20
|
+
// Enable via .claude/settings.json:
|
|
21
|
+
// "hooks": {
|
|
22
|
+
// "post_apply_migration": true
|
|
23
|
+
// }
|
|
24
|
+
//
|
|
25
|
+
// Triggered on PostToolUse for tool_name matching Supabase MCP apply_migration.
|
|
26
|
+
// Reads from stdin a JSON envelope including:
|
|
27
|
+
// tool_name — full tool id, ex "mcp__0a71...__apply_migration"
|
|
28
|
+
// tool_input — { name: "20260409_contact_prefs", query: "ALTER TABLE..." }
|
|
29
|
+
// tool_response — server response (if available)
|
|
30
|
+
// project_root — absolute path to the project (best-effort)
|
|
31
|
+
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
const os = require('os');
|
|
37
|
+
const { execSync } = require('child_process');
|
|
38
|
+
|
|
39
|
+
let input = '';
|
|
40
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
41
|
+
process.stdin.setEncoding('utf8');
|
|
42
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
43
|
+
process.stdin.on('end', () => {
|
|
44
|
+
clearTimeout(stdinTimeout);
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(input);
|
|
47
|
+
const toolName = data.tool_name || '';
|
|
48
|
+
|
|
49
|
+
// Match any MCP tool ending in apply_migration.
|
|
50
|
+
if (!/apply_migration$/.test(toolName)) {
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const migrationName = data.tool_input?.name || '';
|
|
55
|
+
const sqlBody = data.tool_input?.query || '';
|
|
56
|
+
if (!migrationName || !sqlBody) {
|
|
57
|
+
process.stderr.write('[post-apply-migration] missing name or query — skipping\n');
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const projectRoot = data.project_root || process.cwd();
|
|
62
|
+
const ts = formatTimestamp(new Date());
|
|
63
|
+
const safeName = migrationName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
64
|
+
const fileName = `${ts}_${safeName}.sql`;
|
|
65
|
+
|
|
66
|
+
// --- (a) mirror to supabase/migrations/ ---
|
|
67
|
+
const targetDir = path.join(projectRoot, 'supabase', 'migrations');
|
|
68
|
+
let mirroredPath = null;
|
|
69
|
+
try {
|
|
70
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
71
|
+
mirroredPath = path.join(targetDir, fileName);
|
|
72
|
+
fs.writeFileSync(mirroredPath, sqlBody, 'utf8');
|
|
73
|
+
process.stderr.write(`[post-apply-migration] ✓ mirrored to ${path.relative(projectRoot, mirroredPath)}\n`);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
process.stderr.write(`[post-apply-migration] ⚠ mirror failed: ${err.message}\n`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- (b) stub in Obsidian vault (best-effort) ---
|
|
79
|
+
const vault = detectObsidianVault();
|
|
80
|
+
let stubPath = null;
|
|
81
|
+
if (vault) {
|
|
82
|
+
try {
|
|
83
|
+
const year = new Date().getFullYear();
|
|
84
|
+
const stubDir = path.join(vault, '07 - Banco de Dados', 'Migrations', String(year));
|
|
85
|
+
fs.mkdirSync(stubDir, { recursive: true });
|
|
86
|
+
stubPath = path.join(stubDir, `${ts}_${safeName}.md`);
|
|
87
|
+
if (!fs.existsSync(stubPath)) {
|
|
88
|
+
fs.writeFileSync(stubPath, renderStub(migrationName, sqlBody, ts), 'utf8');
|
|
89
|
+
process.stderr.write(`[post-apply-migration] ✓ stub criado em ${path.relative(vault, stubPath)}\n`);
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
process.stderr.write(`[post-apply-migration] ⚠ stub failed: ${err.message}\n`);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
process.stderr.write('[post-apply-migration] vault não detectado — pulando stub\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// --- (c) git stage (best-effort) ---
|
|
99
|
+
if (mirroredPath) {
|
|
100
|
+
try {
|
|
101
|
+
execSync(`git add "${path.relative(projectRoot, mirroredPath)}"`, { cwd: projectRoot, stdio: 'ignore' });
|
|
102
|
+
process.stderr.write('[post-apply-migration] ✓ staged no git\n');
|
|
103
|
+
} catch (err) {
|
|
104
|
+
// Project may not be a git repo, or .sql may be ignored. Soft-fail.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The final advisory printed back to Claude (and to the user via stderr)
|
|
109
|
+
// SEC-13-05: aguardar flush do stderr antes do exit. Sem callback, o
|
|
110
|
+
// resumo final pode ser dropado em pipes lentos (CI/Windows). Os outros
|
|
111
|
+
// process.stderr.write intermediários (linhas ~71/87/93/100) NÃO precisam
|
|
112
|
+
// do callback porque o process continua executando após eles — o event
|
|
113
|
+
// loop drena o buffer naturalmente antes do próximo write.
|
|
114
|
+
if (mirroredPath || stubPath) {
|
|
115
|
+
const lines = ['[post-apply-migration] resumo:'];
|
|
116
|
+
if (mirroredPath) lines.push(` • SQL: ${path.relative(projectRoot, mirroredPath)}`);
|
|
117
|
+
if (stubPath) lines.push(` • Stub: ${path.relative(vault, stubPath)}`);
|
|
118
|
+
lines.push(' → cofre Obsidian: edite o stub e commite quando puder.');
|
|
119
|
+
process.stderr.write(lines.join('\n') + '\n', () => process.exit(0));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
process.exit(0);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
process.stderr.write(`[post-apply-migration] hook error: ${err.message}\n`);
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ───────────────────────────────────────────────────────── helpers
|
|
131
|
+
|
|
132
|
+
function formatTimestamp(d) {
|
|
133
|
+
// YYYYMMDDHHMMSS — same convention used by Supabase migrations.
|
|
134
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
135
|
+
return (
|
|
136
|
+
d.getFullYear().toString() +
|
|
137
|
+
pad(d.getMonth() + 1) +
|
|
138
|
+
pad(d.getDate()) +
|
|
139
|
+
pad(d.getHours()) +
|
|
140
|
+
pad(d.getMinutes()) +
|
|
141
|
+
pad(d.getSeconds())
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function detectObsidianVault() {
|
|
146
|
+
// Mesma heurística do Passo 0.7 do /publicar.
|
|
147
|
+
if (process.env.OBSIDIAN_TEAM_VAULT && fs.existsSync(process.env.OBSIDIAN_TEAM_VAULT)) {
|
|
148
|
+
return process.env.OBSIDIAN_TEAM_VAULT;
|
|
149
|
+
}
|
|
150
|
+
const candidates = [
|
|
151
|
+
process.env.HOME && path.join(process.env.HOME, 'Documentos', 'Obsidian', 'chat-trynux'),
|
|
152
|
+
process.env.HOME && path.join(process.env.HOME, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
153
|
+
process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Documentos', 'Obsidian', 'chat-trynux'),
|
|
154
|
+
process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
155
|
+
process.env.USER && path.join('/mnt/c/Users', process.env.USER, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
156
|
+
].filter(Boolean);
|
|
157
|
+
for (const c of candidates) {
|
|
158
|
+
try { if (fs.existsSync(c) && fs.statSync(c).isDirectory()) return c; } catch { /* noop */ }
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderStub(name, sql, ts) {
|
|
164
|
+
const firstLines = sql
|
|
165
|
+
.split('\n')
|
|
166
|
+
.filter((l) => l.trim().length > 0 && !l.trim().startsWith('--'))
|
|
167
|
+
.slice(0, 8)
|
|
168
|
+
.join('\n');
|
|
169
|
+
return [
|
|
170
|
+
'---',
|
|
171
|
+
`migration: "${name}"`,
|
|
172
|
+
`applied_at: ${new Date().toISOString()}`,
|
|
173
|
+
`timestamp: ${ts}`,
|
|
174
|
+
'status: applied',
|
|
175
|
+
'pr: (preencher quando abrir PR)',
|
|
176
|
+
'---',
|
|
177
|
+
'',
|
|
178
|
+
`# ${name}`,
|
|
179
|
+
'',
|
|
180
|
+
'## O que essa migration faz',
|
|
181
|
+
'',
|
|
182
|
+
'_(uma frase, em linguagem de produto)_',
|
|
183
|
+
'',
|
|
184
|
+
'## Tabelas / colunas afetadas',
|
|
185
|
+
'',
|
|
186
|
+
'_(liste tabelas, colunas, FKs novas/alteradas)_',
|
|
187
|
+
'',
|
|
188
|
+
'## Como reverter',
|
|
189
|
+
'',
|
|
190
|
+
'_(comando ou DDL para desfazer; "irreversível" se for o caso)_',
|
|
191
|
+
'',
|
|
192
|
+
'## Primeiras linhas (para referência)',
|
|
193
|
+
'',
|
|
194
|
+
'```sql',
|
|
195
|
+
firstLines,
|
|
196
|
+
'```',
|
|
197
|
+
'',
|
|
198
|
+
].join('\n');
|
|
199
|
+
}
|