@luanpdd/kit-mcp 1.2.3 → 1.4.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/CHANGELOG.md +69 -0
- package/kit/agents/schema-checker.md +159 -0
- package/kit/commands/publicar-rapido.md +207 -0
- package/kit/commands/publicar.md +123 -3
- package/kit/file-manifest.json +223 -215
- package/kit/hooks/post-apply-migration.js +191 -0
- package/package.json +1 -1
- package/src/ui/static/index.html +1636 -966
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hook-version: 1.4.0
|
|
3
|
+
// kit-mcp · Post-apply Migration Hook (PostToolUse)
|
|
4
|
+
//
|
|
5
|
+
// Triggers automatically AFTER a successful Supabase MCP apply_migration call.
|
|
6
|
+
// Performs the 3 manual steps that devs always forget:
|
|
7
|
+
//
|
|
8
|
+
// (a) Mirror the .sql to supabase/migrations/{TIMESTAMP}_{name}.sql
|
|
9
|
+
// so the project's git history captures the migration.
|
|
10
|
+
// (b) Create a stub note in the Obsidian vault under
|
|
11
|
+
// 07 - Banco de Dados/Migrations/{YYYY}/{TIMESTAMP}_{name}.md
|
|
12
|
+
// (only if the vault is detected — same heuristic as /publicar Step 0.7).
|
|
13
|
+
// (c) Stage both files in git so the next commit picks them up.
|
|
14
|
+
//
|
|
15
|
+
// All three steps are SOFT — failures log to stderr and continue.
|
|
16
|
+
// The hook never blocks the calling tool.
|
|
17
|
+
//
|
|
18
|
+
// Enable via .claude/settings.json:
|
|
19
|
+
// "hooks": {
|
|
20
|
+
// "post_apply_migration": true
|
|
21
|
+
// }
|
|
22
|
+
//
|
|
23
|
+
// Triggered on PostToolUse for tool_name matching Supabase MCP apply_migration.
|
|
24
|
+
// Reads from stdin a JSON envelope including:
|
|
25
|
+
// tool_name — full tool id, ex "mcp__0a71...__apply_migration"
|
|
26
|
+
// tool_input — { name: "20260409_contact_prefs", query: "ALTER TABLE..." }
|
|
27
|
+
// tool_response — server response (if available)
|
|
28
|
+
// project_root — absolute path to the project (best-effort)
|
|
29
|
+
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const os = require('os');
|
|
35
|
+
const { execSync } = require('child_process');
|
|
36
|
+
|
|
37
|
+
let input = '';
|
|
38
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
39
|
+
process.stdin.setEncoding('utf8');
|
|
40
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
41
|
+
process.stdin.on('end', () => {
|
|
42
|
+
clearTimeout(stdinTimeout);
|
|
43
|
+
try {
|
|
44
|
+
const data = JSON.parse(input);
|
|
45
|
+
const toolName = data.tool_name || '';
|
|
46
|
+
|
|
47
|
+
// Match any MCP tool ending in apply_migration.
|
|
48
|
+
if (!/apply_migration$/.test(toolName)) {
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const migrationName = data.tool_input?.name || '';
|
|
53
|
+
const sqlBody = data.tool_input?.query || '';
|
|
54
|
+
if (!migrationName || !sqlBody) {
|
|
55
|
+
process.stderr.write('[post-apply-migration] missing name or query — skipping\n');
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const projectRoot = data.project_root || process.cwd();
|
|
60
|
+
const ts = formatTimestamp(new Date());
|
|
61
|
+
const safeName = migrationName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
62
|
+
const fileName = `${ts}_${safeName}.sql`;
|
|
63
|
+
|
|
64
|
+
// --- (a) mirror to supabase/migrations/ ---
|
|
65
|
+
const targetDir = path.join(projectRoot, 'supabase', 'migrations');
|
|
66
|
+
let mirroredPath = null;
|
|
67
|
+
try {
|
|
68
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
69
|
+
mirroredPath = path.join(targetDir, fileName);
|
|
70
|
+
fs.writeFileSync(mirroredPath, sqlBody, 'utf8');
|
|
71
|
+
process.stderr.write(`[post-apply-migration] ✓ mirrored to ${path.relative(projectRoot, mirroredPath)}\n`);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
process.stderr.write(`[post-apply-migration] ⚠ mirror failed: ${err.message}\n`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- (b) stub in Obsidian vault (best-effort) ---
|
|
77
|
+
const vault = detectObsidianVault();
|
|
78
|
+
let stubPath = null;
|
|
79
|
+
if (vault) {
|
|
80
|
+
try {
|
|
81
|
+
const year = new Date().getFullYear();
|
|
82
|
+
const stubDir = path.join(vault, '07 - Banco de Dados', 'Migrations', String(year));
|
|
83
|
+
fs.mkdirSync(stubDir, { recursive: true });
|
|
84
|
+
stubPath = path.join(stubDir, `${ts}_${safeName}.md`);
|
|
85
|
+
if (!fs.existsSync(stubPath)) {
|
|
86
|
+
fs.writeFileSync(stubPath, renderStub(migrationName, sqlBody, ts), 'utf8');
|
|
87
|
+
process.stderr.write(`[post-apply-migration] ✓ stub criado em ${path.relative(vault, stubPath)}\n`);
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
process.stderr.write(`[post-apply-migration] ⚠ stub failed: ${err.message}\n`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
process.stderr.write('[post-apply-migration] vault não detectado — pulando stub\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- (c) git stage (best-effort) ---
|
|
97
|
+
if (mirroredPath) {
|
|
98
|
+
try {
|
|
99
|
+
execSync(`git add "${path.relative(projectRoot, mirroredPath)}"`, { cwd: projectRoot, stdio: 'ignore' });
|
|
100
|
+
process.stderr.write('[post-apply-migration] ✓ staged no git\n');
|
|
101
|
+
} catch (err) {
|
|
102
|
+
// Project may not be a git repo, or .sql may be ignored. Soft-fail.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// The final advisory printed back to Claude (and to the user via stderr)
|
|
107
|
+
if (mirroredPath || stubPath) {
|
|
108
|
+
const lines = ['[post-apply-migration] resumo:'];
|
|
109
|
+
if (mirroredPath) lines.push(` • SQL: ${path.relative(projectRoot, mirroredPath)}`);
|
|
110
|
+
if (stubPath) lines.push(` • Stub: ${path.relative(vault, stubPath)}`);
|
|
111
|
+
lines.push(' → cofre Obsidian: edite o stub e commite quando puder.');
|
|
112
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
process.exit(0);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
process.stderr.write(`[post-apply-migration] hook error: ${err.message}\n`);
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ───────────────────────────────────────────────────────── helpers
|
|
123
|
+
|
|
124
|
+
function formatTimestamp(d) {
|
|
125
|
+
// YYYYMMDDHHMMSS — same convention used by Supabase migrations.
|
|
126
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
127
|
+
return (
|
|
128
|
+
d.getFullYear().toString() +
|
|
129
|
+
pad(d.getMonth() + 1) +
|
|
130
|
+
pad(d.getDate()) +
|
|
131
|
+
pad(d.getHours()) +
|
|
132
|
+
pad(d.getMinutes()) +
|
|
133
|
+
pad(d.getSeconds())
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function detectObsidianVault() {
|
|
138
|
+
// Mesma heurística do Passo 0.7 do /publicar.
|
|
139
|
+
if (process.env.OBSIDIAN_TEAM_VAULT && fs.existsSync(process.env.OBSIDIAN_TEAM_VAULT)) {
|
|
140
|
+
return process.env.OBSIDIAN_TEAM_VAULT;
|
|
141
|
+
}
|
|
142
|
+
const candidates = [
|
|
143
|
+
process.env.HOME && path.join(process.env.HOME, 'Documentos', 'Obsidian', 'chat-trynux'),
|
|
144
|
+
process.env.HOME && path.join(process.env.HOME, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
145
|
+
process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Documentos', 'Obsidian', 'chat-trynux'),
|
|
146
|
+
process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
147
|
+
process.env.USER && path.join('/mnt/c/Users', process.env.USER, 'Documents', 'Obsidian', 'chat-trynux'),
|
|
148
|
+
].filter(Boolean);
|
|
149
|
+
for (const c of candidates) {
|
|
150
|
+
try { if (fs.existsSync(c) && fs.statSync(c).isDirectory()) return c; } catch { /* noop */ }
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function renderStub(name, sql, ts) {
|
|
156
|
+
const firstLines = sql
|
|
157
|
+
.split('\n')
|
|
158
|
+
.filter((l) => l.trim().length > 0 && !l.trim().startsWith('--'))
|
|
159
|
+
.slice(0, 8)
|
|
160
|
+
.join('\n');
|
|
161
|
+
return [
|
|
162
|
+
'---',
|
|
163
|
+
`migration: "${name}"`,
|
|
164
|
+
`applied_at: ${new Date().toISOString()}`,
|
|
165
|
+
`timestamp: ${ts}`,
|
|
166
|
+
'status: applied',
|
|
167
|
+
'pr: (preencher quando abrir PR)',
|
|
168
|
+
'---',
|
|
169
|
+
'',
|
|
170
|
+
`# ${name}`,
|
|
171
|
+
'',
|
|
172
|
+
'## O que essa migration faz',
|
|
173
|
+
'',
|
|
174
|
+
'_(uma frase, em linguagem de produto)_',
|
|
175
|
+
'',
|
|
176
|
+
'## Tabelas / colunas afetadas',
|
|
177
|
+
'',
|
|
178
|
+
'_(liste tabelas, colunas, FKs novas/alteradas)_',
|
|
179
|
+
'',
|
|
180
|
+
'## Como reverter',
|
|
181
|
+
'',
|
|
182
|
+
'_(comando ou DDL para desfazer; "irreversível" se for o caso)_',
|
|
183
|
+
'',
|
|
184
|
+
'## Primeiras linhas (para referência)',
|
|
185
|
+
'',
|
|
186
|
+
'```sql',
|
|
187
|
+
firstLines,
|
|
188
|
+
'```',
|
|
189
|
+
'',
|
|
190
|
+
].join('\n');
|
|
191
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luanpdd/kit-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Generic infrastructure to ship YOUR personal kit of agents/commands/skills as an MCP server, with cross-IDE sync (Claude Code, Cursor, Codex, Gemini, Windsurf, Antigravity, Copilot, Trae).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|