@luanpdd/kit-mcp 1.34.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/README.md +1 -1
- 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 -0
- 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 -0
- 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 +5 -2
- 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 -0
- 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,210 +1,210 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// hook-version: 1.14.0
|
|
3
|
-
// kit-mcp · Sidecar Tool Publisher (PostToolUse)
|
|
4
|
-
//
|
|
5
|
-
// Publishes every Claude Code tool invocation to the kit-mcp sidecar so the
|
|
6
|
-
// localhost UI shows real-time activity from this IDE — including edits,
|
|
7
|
-
// reads, bash, agent spawns, MCP calls. Closes the gap where the sidecar
|
|
8
|
-
// previously only saw `kit sync`/`reverse-sync`/`gates` operations.
|
|
9
|
-
//
|
|
10
|
-
// Pipeline: PostToolUse hook → reads stdin envelope → discovers sidecar
|
|
11
|
-
// lockfile (per project_root) → POST /publish → fire-and-forget.
|
|
12
|
-
//
|
|
13
|
-
// SOFT failure: any error logs to stderr and exits 0. Never blocks the user.
|
|
14
|
-
//
|
|
15
|
-
// Module format: ESM (package.json "type": "module"). Stays compatible whether
|
|
16
|
-
// run from inside the kit-mcp repo or from a user project.
|
|
17
|
-
//
|
|
18
|
-
// Enable via `~/.claude/settings.json` (or per-project `.claude/settings.json`):
|
|
19
|
-
// {
|
|
20
|
-
// "hooks": {
|
|
21
|
-
// "PostToolUse": [{
|
|
22
|
-
// "matcher": "*",
|
|
23
|
-
// "hooks": [{
|
|
24
|
-
// "type": "command",
|
|
25
|
-
// "command": "node /abs/path/to/sidecar-tool-publisher.js"
|
|
26
|
-
// }]
|
|
27
|
-
// }]
|
|
28
|
-
// }
|
|
29
|
-
// }
|
|
30
|
-
|
|
31
|
-
import fs from 'node:fs';
|
|
32
|
-
import os from 'node:os';
|
|
33
|
-
import path from 'node:path';
|
|
34
|
-
import http from 'node:http';
|
|
35
|
-
import crypto from 'node:crypto';
|
|
36
|
-
import process from 'node:process';
|
|
37
|
-
|
|
38
|
-
let input = '';
|
|
39
|
-
const stdinTimeout = setTimeout(() => process.exit(0), 1500);
|
|
40
|
-
process.stdin.setEncoding('utf8');
|
|
41
|
-
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
42
|
-
process.stdin.on('end', () => {
|
|
43
|
-
clearTimeout(stdinTimeout);
|
|
44
|
-
try {
|
|
45
|
-
const data = input ? JSON.parse(input) : {};
|
|
46
|
-
const toolName = data.tool_name || data.toolName || 'unknown';
|
|
47
|
-
const projectRoot = data.project_root || data.cwd || process.cwd();
|
|
48
|
-
|
|
49
|
-
debugLog({ phase: 'received', toolName, projectRoot, cwd: process.cwd(), keys: Object.keys(data) });
|
|
50
|
-
|
|
51
|
-
// Try requested projectRoot first; if no lockfile found, scan all
|
|
52
|
-
// kit-mcp-ui-*.lock files in tmpdir and pick one that healthz-responds.
|
|
53
|
-
// This makes the hook resilient to projectRoot mismatch (case, separators,
|
|
54
|
-
// trailing slash, parent-of-project edits, etc).
|
|
55
|
-
let sidecar = readSidecarLock(projectRoot);
|
|
56
|
-
if (!sidecar) sidecar = scanAnyRunningSidecar();
|
|
57
|
-
if (!sidecar) {
|
|
58
|
-
debugLog({ phase: 'no_sidecar', projectRoot });
|
|
59
|
-
process.exit(0);
|
|
60
|
-
}
|
|
61
|
-
const { port, token } = sidecar;
|
|
62
|
-
|
|
63
|
-
const payload = {
|
|
64
|
-
tool: toolName,
|
|
65
|
-
sessionId: data.session_id || data.sessionId || null,
|
|
66
|
-
durationMs: typeof data.duration_ms === 'number' ? data.duration_ms : null,
|
|
67
|
-
argsSummary: summarizeArgs(data.tool_input),
|
|
68
|
-
source: detectSource(),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const event = {
|
|
72
|
-
type: 'tool_invocation',
|
|
73
|
-
ts: Date.now(),
|
|
74
|
-
runId: null,
|
|
75
|
-
payload,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
publish(port, token, event).then(() => process.exit(0));
|
|
79
|
-
} catch (err) {
|
|
80
|
-
process.stderr.write(`[sidecar-tool-publisher] ${err.message}\n`);
|
|
81
|
-
process.exit(0);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
function readSidecarLock(projectRoot) {
|
|
86
|
-
// Mirror src/ui/lockfile.js#lockPathFor (sha1(projectRoot).slice(0,16))
|
|
87
|
-
try {
|
|
88
|
-
const hash = crypto.createHash('sha1').update(projectRoot).digest('hex').slice(0, 16);
|
|
89
|
-
const lockPath = path.join(os.tmpdir(), `kit-mcp-ui-${hash}.lock`);
|
|
90
|
-
const raw = fs.readFileSync(lockPath, 'utf8');
|
|
91
|
-
const lock = JSON.parse(raw);
|
|
92
|
-
if (typeof lock.port !== 'number') return null;
|
|
93
|
-
return {
|
|
94
|
-
port: lock.port,
|
|
95
|
-
// SEC-14-02 (kit-mcp v1.14+): null for sidecars from v1.13 and earlier.
|
|
96
|
-
token: typeof lock.token === 'string' && /^[0-9a-f]{64}$/.test(lock.token) ? lock.token : null,
|
|
97
|
-
};
|
|
98
|
-
} catch {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Scan os.tmpdir() for any kit-mcp-ui-*.lock and return the first { port, token }
|
|
104
|
-
// of a live sidecar. Used as a fallback when projectRoot doesn't match any
|
|
105
|
-
// known lockfile (case variants, separator differences, parent-dir edits, etc).
|
|
106
|
-
function scanAnyRunningSidecar() {
|
|
107
|
-
try {
|
|
108
|
-
const dir = os.tmpdir();
|
|
109
|
-
const entries = fs.readdirSync(dir);
|
|
110
|
-
for (const name of entries) {
|
|
111
|
-
if (!/^kit-mcp-ui-[0-9a-f]{16}\.lock$/.test(name)) continue;
|
|
112
|
-
try {
|
|
113
|
-
const raw = fs.readFileSync(path.join(dir, name), 'utf8');
|
|
114
|
-
const lock = JSON.parse(raw);
|
|
115
|
-
if (typeof lock.port === 'number' && typeof lock.pid === 'number') {
|
|
116
|
-
try {
|
|
117
|
-
process.kill(lock.pid, 0);
|
|
118
|
-
// SEC-14-02: return token from same lockfile so cross-project
|
|
119
|
-
// publishing can authenticate. If token missing (older sidecar),
|
|
120
|
-
// returns null → publish degrades to 401 silent-fail.
|
|
121
|
-
return {
|
|
122
|
-
port: lock.port,
|
|
123
|
-
token: typeof lock.token === 'string' && /^[0-9a-f]{64}$/.test(lock.token) ? lock.token : null,
|
|
124
|
-
};
|
|
125
|
-
} catch { /* dead */ }
|
|
126
|
-
}
|
|
127
|
-
} catch { /* skip unreadable */ }
|
|
128
|
-
}
|
|
129
|
-
} catch { /* tmpdir unreadable */ }
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function debugLog(obj) {
|
|
134
|
-
if (process.env.KIT_MCP_HOOK_DEBUG !== '1') return;
|
|
135
|
-
try {
|
|
136
|
-
const line = JSON.stringify({ ts: Date.now(), ...obj }) + '\n';
|
|
137
|
-
fs.appendFileSync(path.join(os.tmpdir(), 'kit-mcp-hook.log'), line);
|
|
138
|
-
} catch { /* noop */ }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function summarizeArgs(args) {
|
|
142
|
-
if (!args || typeof args !== 'object') return null;
|
|
143
|
-
const out = {};
|
|
144
|
-
if (typeof args.command === 'string') out.command = truncate(args.command, 120);
|
|
145
|
-
if (typeof args.file_path === 'string') out.file_path = truncate(args.file_path, 200);
|
|
146
|
-
if (typeof args.pattern === 'string') out.pattern = truncate(args.pattern, 80);
|
|
147
|
-
if (typeof args.url === 'string') out.url = truncate(args.url, 120);
|
|
148
|
-
if (typeof args.description === 'string') out.description = truncate(args.description, 80);
|
|
149
|
-
if (Array.isArray(args.actions)) out.action_count = args.actions.length;
|
|
150
|
-
return Object.keys(out).length ? out : null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function truncate(s, n) { return s.length > n ? s.slice(0, n - 1) + '…' : s; }
|
|
154
|
-
|
|
155
|
-
function detectSource() {
|
|
156
|
-
const ide = detectIde();
|
|
157
|
-
const pid = process.ppid || process.pid;
|
|
158
|
-
const id = `${ide}:${pid}`;
|
|
159
|
-
return {
|
|
160
|
-
id,
|
|
161
|
-
ide,
|
|
162
|
-
pid,
|
|
163
|
-
hostname: os.hostname(),
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function detectIde() {
|
|
168
|
-
if (process.env.CLAUDE_CODE_VERSION || process.env.CLAUDECODE) return 'claude-code';
|
|
169
|
-
if (process.env.CURSOR_TRACE_ID) return 'cursor';
|
|
170
|
-
if (process.env.TERM_PROGRAM === 'vscode') return 'vscode';
|
|
171
|
-
if (process.env.JETBRAINS_IDE) return 'jetbrains';
|
|
172
|
-
return 'unknown';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function publish(port, token, event) {
|
|
176
|
-
return new Promise((resolve) => {
|
|
177
|
-
const body = JSON.stringify(event);
|
|
178
|
-
const req = http.request({
|
|
179
|
-
method: 'POST',
|
|
180
|
-
host: '127.0.0.1',
|
|
181
|
-
port,
|
|
182
|
-
path: '/publish',
|
|
183
|
-
agent: false,
|
|
184
|
-
headers: {
|
|
185
|
-
host: `127.0.0.1:${port}`,
|
|
186
|
-
'content-type': 'application/json',
|
|
187
|
-
'content-length': Buffer.byteLength(body, 'utf8'),
|
|
188
|
-
origin: `http://127.0.0.1:${port}`,
|
|
189
|
-
connection: 'close',
|
|
190
|
-
// SEC-14-02: token is null for sidecars from v1.13 and earlier; in that
|
|
191
|
-
// case we omit the header and the server returns 401, which the hook
|
|
192
|
-
// silent-fails on (matching pre-existing soft-fail discipline). A
|
|
193
|
-
// shipped hook v1.14 talking to a still-running sidecar v1.13 just
|
|
194
|
-
// loses the event — acceptable trade-off.
|
|
195
|
-
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
196
|
-
},
|
|
197
|
-
}, (res) => {
|
|
198
|
-
// Drain response body to ensure server has fully processed before resolve.
|
|
199
|
-
// v1.12.1 fix: await BOTH 'end' and 'close' to avoid premature exit before
|
|
200
|
-
// sidecar publishes via SSE. Preserve that pattern here.
|
|
201
|
-
res.resume();
|
|
202
|
-
res.on('end', resolve);
|
|
203
|
-
res.on('close', resolve);
|
|
204
|
-
});
|
|
205
|
-
req.on('error', () => resolve());
|
|
206
|
-
req.setTimeout(800, () => { try { req.destroy(); } catch (_) { /* noop */ } resolve(); });
|
|
207
|
-
req.write(body);
|
|
208
|
-
req.end();
|
|
209
|
-
});
|
|
210
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hook-version: 1.14.0
|
|
3
|
+
// kit-mcp · Sidecar Tool Publisher (PostToolUse)
|
|
4
|
+
//
|
|
5
|
+
// Publishes every Claude Code tool invocation to the kit-mcp sidecar so the
|
|
6
|
+
// localhost UI shows real-time activity from this IDE — including edits,
|
|
7
|
+
// reads, bash, agent spawns, MCP calls. Closes the gap where the sidecar
|
|
8
|
+
// previously only saw `kit sync`/`reverse-sync`/`gates` operations.
|
|
9
|
+
//
|
|
10
|
+
// Pipeline: PostToolUse hook → reads stdin envelope → discovers sidecar
|
|
11
|
+
// lockfile (per project_root) → POST /publish → fire-and-forget.
|
|
12
|
+
//
|
|
13
|
+
// SOFT failure: any error logs to stderr and exits 0. Never blocks the user.
|
|
14
|
+
//
|
|
15
|
+
// Module format: ESM (package.json "type": "module"). Stays compatible whether
|
|
16
|
+
// run from inside the kit-mcp repo or from a user project.
|
|
17
|
+
//
|
|
18
|
+
// Enable via `~/.claude/settings.json` (or per-project `.claude/settings.json`):
|
|
19
|
+
// {
|
|
20
|
+
// "hooks": {
|
|
21
|
+
// "PostToolUse": [{
|
|
22
|
+
// "matcher": "*",
|
|
23
|
+
// "hooks": [{
|
|
24
|
+
// "type": "command",
|
|
25
|
+
// "command": "node /abs/path/to/sidecar-tool-publisher.js"
|
|
26
|
+
// }]
|
|
27
|
+
// }]
|
|
28
|
+
// }
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
import fs from 'node:fs';
|
|
32
|
+
import os from 'node:os';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
import http from 'node:http';
|
|
35
|
+
import crypto from 'node:crypto';
|
|
36
|
+
import process from 'node:process';
|
|
37
|
+
|
|
38
|
+
let input = '';
|
|
39
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 1500);
|
|
40
|
+
process.stdin.setEncoding('utf8');
|
|
41
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
42
|
+
process.stdin.on('end', () => {
|
|
43
|
+
clearTimeout(stdinTimeout);
|
|
44
|
+
try {
|
|
45
|
+
const data = input ? JSON.parse(input) : {};
|
|
46
|
+
const toolName = data.tool_name || data.toolName || 'unknown';
|
|
47
|
+
const projectRoot = data.project_root || data.cwd || process.cwd();
|
|
48
|
+
|
|
49
|
+
debugLog({ phase: 'received', toolName, projectRoot, cwd: process.cwd(), keys: Object.keys(data) });
|
|
50
|
+
|
|
51
|
+
// Try requested projectRoot first; if no lockfile found, scan all
|
|
52
|
+
// kit-mcp-ui-*.lock files in tmpdir and pick one that healthz-responds.
|
|
53
|
+
// This makes the hook resilient to projectRoot mismatch (case, separators,
|
|
54
|
+
// trailing slash, parent-of-project edits, etc).
|
|
55
|
+
let sidecar = readSidecarLock(projectRoot);
|
|
56
|
+
if (!sidecar) sidecar = scanAnyRunningSidecar();
|
|
57
|
+
if (!sidecar) {
|
|
58
|
+
debugLog({ phase: 'no_sidecar', projectRoot });
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
const { port, token } = sidecar;
|
|
62
|
+
|
|
63
|
+
const payload = {
|
|
64
|
+
tool: toolName,
|
|
65
|
+
sessionId: data.session_id || data.sessionId || null,
|
|
66
|
+
durationMs: typeof data.duration_ms === 'number' ? data.duration_ms : null,
|
|
67
|
+
argsSummary: summarizeArgs(data.tool_input),
|
|
68
|
+
source: detectSource(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const event = {
|
|
72
|
+
type: 'tool_invocation',
|
|
73
|
+
ts: Date.now(),
|
|
74
|
+
runId: null,
|
|
75
|
+
payload,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
publish(port, token, event).then(() => process.exit(0));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
process.stderr.write(`[sidecar-tool-publisher] ${err.message}\n`);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function readSidecarLock(projectRoot) {
|
|
86
|
+
// Mirror src/ui/lockfile.js#lockPathFor (sha1(projectRoot).slice(0,16))
|
|
87
|
+
try {
|
|
88
|
+
const hash = crypto.createHash('sha1').update(projectRoot).digest('hex').slice(0, 16);
|
|
89
|
+
const lockPath = path.join(os.tmpdir(), `kit-mcp-ui-${hash}.lock`);
|
|
90
|
+
const raw = fs.readFileSync(lockPath, 'utf8');
|
|
91
|
+
const lock = JSON.parse(raw);
|
|
92
|
+
if (typeof lock.port !== 'number') return null;
|
|
93
|
+
return {
|
|
94
|
+
port: lock.port,
|
|
95
|
+
// SEC-14-02 (kit-mcp v1.14+): null for sidecars from v1.13 and earlier.
|
|
96
|
+
token: typeof lock.token === 'string' && /^[0-9a-f]{64}$/.test(lock.token) ? lock.token : null,
|
|
97
|
+
};
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Scan os.tmpdir() for any kit-mcp-ui-*.lock and return the first { port, token }
|
|
104
|
+
// of a live sidecar. Used as a fallback when projectRoot doesn't match any
|
|
105
|
+
// known lockfile (case variants, separator differences, parent-dir edits, etc).
|
|
106
|
+
function scanAnyRunningSidecar() {
|
|
107
|
+
try {
|
|
108
|
+
const dir = os.tmpdir();
|
|
109
|
+
const entries = fs.readdirSync(dir);
|
|
110
|
+
for (const name of entries) {
|
|
111
|
+
if (!/^kit-mcp-ui-[0-9a-f]{16}\.lock$/.test(name)) continue;
|
|
112
|
+
try {
|
|
113
|
+
const raw = fs.readFileSync(path.join(dir, name), 'utf8');
|
|
114
|
+
const lock = JSON.parse(raw);
|
|
115
|
+
if (typeof lock.port === 'number' && typeof lock.pid === 'number') {
|
|
116
|
+
try {
|
|
117
|
+
process.kill(lock.pid, 0);
|
|
118
|
+
// SEC-14-02: return token from same lockfile so cross-project
|
|
119
|
+
// publishing can authenticate. If token missing (older sidecar),
|
|
120
|
+
// returns null → publish degrades to 401 silent-fail.
|
|
121
|
+
return {
|
|
122
|
+
port: lock.port,
|
|
123
|
+
token: typeof lock.token === 'string' && /^[0-9a-f]{64}$/.test(lock.token) ? lock.token : null,
|
|
124
|
+
};
|
|
125
|
+
} catch { /* dead */ }
|
|
126
|
+
}
|
|
127
|
+
} catch { /* skip unreadable */ }
|
|
128
|
+
}
|
|
129
|
+
} catch { /* tmpdir unreadable */ }
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function debugLog(obj) {
|
|
134
|
+
if (process.env.KIT_MCP_HOOK_DEBUG !== '1') return;
|
|
135
|
+
try {
|
|
136
|
+
const line = JSON.stringify({ ts: Date.now(), ...obj }) + '\n';
|
|
137
|
+
fs.appendFileSync(path.join(os.tmpdir(), 'kit-mcp-hook.log'), line);
|
|
138
|
+
} catch { /* noop */ }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function summarizeArgs(args) {
|
|
142
|
+
if (!args || typeof args !== 'object') return null;
|
|
143
|
+
const out = {};
|
|
144
|
+
if (typeof args.command === 'string') out.command = truncate(args.command, 120);
|
|
145
|
+
if (typeof args.file_path === 'string') out.file_path = truncate(args.file_path, 200);
|
|
146
|
+
if (typeof args.pattern === 'string') out.pattern = truncate(args.pattern, 80);
|
|
147
|
+
if (typeof args.url === 'string') out.url = truncate(args.url, 120);
|
|
148
|
+
if (typeof args.description === 'string') out.description = truncate(args.description, 80);
|
|
149
|
+
if (Array.isArray(args.actions)) out.action_count = args.actions.length;
|
|
150
|
+
return Object.keys(out).length ? out : null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function truncate(s, n) { return s.length > n ? s.slice(0, n - 1) + '…' : s; }
|
|
154
|
+
|
|
155
|
+
function detectSource() {
|
|
156
|
+
const ide = detectIde();
|
|
157
|
+
const pid = process.ppid || process.pid;
|
|
158
|
+
const id = `${ide}:${pid}`;
|
|
159
|
+
return {
|
|
160
|
+
id,
|
|
161
|
+
ide,
|
|
162
|
+
pid,
|
|
163
|
+
hostname: os.hostname(),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function detectIde() {
|
|
168
|
+
if (process.env.CLAUDE_CODE_VERSION || process.env.CLAUDECODE) return 'claude-code';
|
|
169
|
+
if (process.env.CURSOR_TRACE_ID) return 'cursor';
|
|
170
|
+
if (process.env.TERM_PROGRAM === 'vscode') return 'vscode';
|
|
171
|
+
if (process.env.JETBRAINS_IDE) return 'jetbrains';
|
|
172
|
+
return 'unknown';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function publish(port, token, event) {
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
const body = JSON.stringify(event);
|
|
178
|
+
const req = http.request({
|
|
179
|
+
method: 'POST',
|
|
180
|
+
host: '127.0.0.1',
|
|
181
|
+
port,
|
|
182
|
+
path: '/publish',
|
|
183
|
+
agent: false,
|
|
184
|
+
headers: {
|
|
185
|
+
host: `127.0.0.1:${port}`,
|
|
186
|
+
'content-type': 'application/json',
|
|
187
|
+
'content-length': Buffer.byteLength(body, 'utf8'),
|
|
188
|
+
origin: `http://127.0.0.1:${port}`,
|
|
189
|
+
connection: 'close',
|
|
190
|
+
// SEC-14-02: token is null for sidecars from v1.13 and earlier; in that
|
|
191
|
+
// case we omit the header and the server returns 401, which the hook
|
|
192
|
+
// silent-fails on (matching pre-existing soft-fail discipline). A
|
|
193
|
+
// shipped hook v1.14 talking to a still-running sidecar v1.13 just
|
|
194
|
+
// loses the event — acceptable trade-off.
|
|
195
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
196
|
+
},
|
|
197
|
+
}, (res) => {
|
|
198
|
+
// Drain response body to ensure server has fully processed before resolve.
|
|
199
|
+
// v1.12.1 fix: await BOTH 'end' and 'close' to avoid premature exit before
|
|
200
|
+
// sidecar publishes via SSE. Preserve that pattern here.
|
|
201
|
+
res.resume();
|
|
202
|
+
res.on('end', resolve);
|
|
203
|
+
res.on('close', resolve);
|
|
204
|
+
});
|
|
205
|
+
req.on('error', () => resolve());
|
|
206
|
+
req.setTimeout(800, () => { try { req.destroy(); } catch (_) { /* noop */ } resolve(); });
|
|
207
|
+
req.write(body);
|
|
208
|
+
req.end();
|
|
209
|
+
});
|
|
210
|
+
}
|