@luanpdd/kit-mcp 1.12.1 → 1.13.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 +10 -10
- package/kit/agents/codebase-mapper.md +0 -6
- package/kit/agents/debugger.md +0 -6
- package/kit/agents/executor.md +0 -6
- package/kit/agents/phase-researcher.md +0 -6
- package/kit/agents/planner.md +0 -6
- package/kit/agents/project-researcher.md +0 -6
- package/kit/agents/research-synthesizer.md +0 -6
- package/kit/agents/roadmapper.md +0 -6
- package/kit/agents/ui-auditor.md +0 -6
- package/kit/agents/ui-researcher.md +0 -6
- package/kit/agents/verifier.md +0 -6
- package/kit/hooks/check-update.js +4 -0
- package/kit/hooks/context-monitor.js +9 -2
- package/kit/hooks/post-apply-migration.js +10 -2
- package/kit/hooks/prompt-guard.js +9 -2
- package/kit/hooks/statusline.js +6 -0
- package/kit/hooks/workflow-guard.js +9 -2
- package/package.json +1 -2
- package/src/cli/index.js +5 -2
- package/src/core/replays.js +49 -3
- package/src/core/sync.js +4 -2
- package/src/mcp-server/index.js +34 -9
- package/CHANGELOG.md +0 -883
package/README.md
CHANGED
|
@@ -28,9 +28,9 @@ Inspired by [vinilana/dotcontext](https://github.com/vinilana/dotcontext) — se
|
|
|
28
28
|
```
|
|
29
29
|
kit-mcp/
|
|
30
30
|
├── kit/ ← bundled brownfield workflow (PT-BR)
|
|
31
|
-
│ ├── agents/
|
|
31
|
+
│ ├── agents/ 47 agents (planner, executor, verifier, debugger,
|
|
32
32
|
│ │ ui-auditor, codebase-mapper, …)
|
|
33
|
-
│ ├── commands/
|
|
33
|
+
│ ├── commands/ 87 slash-commands (/novo-marco, /planejar-fase,
|
|
34
34
|
│ │ /executar-fase, /publicar, …)
|
|
35
35
|
│ ├── framework/ workflows + templates + bin libs the agents use
|
|
36
36
|
│ ├── hooks/ workflow guards, prompt guards, statusline
|
|
@@ -59,7 +59,7 @@ kit-mcp/
|
|
|
59
59
|
|
|
60
60
|
### About the bundled workflow
|
|
61
61
|
|
|
62
|
-
The bundled `kit/` is an opinionated **brownfield planning workflow** in Portuguese — milestones, phases, requirements, planning, execution with atomic commits and checkpoints, retrospective auditing. Installing `@luanpdd/kit-mcp` and syncing into your IDE gives you all
|
|
62
|
+
The bundled `kit/` is an opinionated **brownfield planning workflow** in Portuguese — milestones, phases, requirements, planning, execution with atomic commits and checkpoints, retrospective auditing. Installing `@luanpdd/kit-mcp` and syncing into your IDE gives you all 87+ slash-commands, 47+ agents, plus the framework templates that they delegate into.
|
|
63
63
|
|
|
64
64
|
If that's not what you want, point `--kit-root` at your own folder and ignore everything under `kit/` — the infrastructure (registry, sync, gates, forensics, MCP server) works the same regardless of what kit you load.
|
|
65
65
|
|
|
@@ -175,8 +175,8 @@ A production engineering layer derived from *Site Reliability Engineering: How G
|
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
177
|
# Browse what's bundled
|
|
178
|
-
npx -y @luanpdd/kit-mcp kit list-agents #
|
|
179
|
-
npx -y @luanpdd/kit-mcp kit list-commands #
|
|
178
|
+
npx -y @luanpdd/kit-mcp kit list-agents # 47 agents
|
|
179
|
+
npx -y @luanpdd/kit-mcp kit list-commands # 87 commands
|
|
180
180
|
npx -y @luanpdd/kit-mcp sync targets # supported IDEs
|
|
181
181
|
|
|
182
182
|
# Install into your project for Claude Code
|
|
@@ -239,9 +239,9 @@ In non-TTY mode (pipes, CI), animations degrade to linear status lines automatic
|
|
|
239
239
|
### `kit kit ...` — browse the kit
|
|
240
240
|
|
|
241
241
|
```bash
|
|
242
|
-
kit kit list-agents #
|
|
243
|
-
kit kit list-commands #
|
|
244
|
-
kit kit list-skills #
|
|
242
|
+
kit kit list-agents # 47 agents (bundled workflow)
|
|
243
|
+
kit kit list-commands # 87 commands (bundled workflow)
|
|
244
|
+
kit kit list-skills # 49 skills (bundled workflow)
|
|
245
245
|
kit kit get agent planner
|
|
246
246
|
kit kit search "milestone" # fuzzy match across all kinds
|
|
247
247
|
```
|
|
@@ -627,9 +627,9 @@ npm run test:all # both
|
|
|
627
627
|
Plus the original quick smokes:
|
|
628
628
|
|
|
629
629
|
```bash
|
|
630
|
-
node bin/cli.js kit list-agents | head -5 #
|
|
630
|
+
node bin/cli.js kit list-agents | head -5 # 47 bundled agents
|
|
631
631
|
node bin/cli.js sync targets # 8 IDEs
|
|
632
|
-
node bin/cli.js gates list #
|
|
632
|
+
node bin/cli.js gates list # 20 gates
|
|
633
633
|
node bin/cli.js install dry-run claude-code --via npx
|
|
634
634
|
```
|
|
635
635
|
|
|
@@ -3,12 +3,6 @@ name: codebase-mapper
|
|
|
3
3
|
description: Explora a codebase e escreve docs de análise estruturados. Invocado por /mapear-codebase com foco (tech, arch, quality, concerns). Reduz carga de contexto do orquestrador.
|
|
4
4
|
tools: Read, Bash, Grep, Glob, Write
|
|
5
5
|
color: cyan
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
package/kit/agents/debugger.md
CHANGED
|
@@ -4,12 +4,6 @@ description: Investiga bugs usando método científico, gerencia sessões de deb
|
|
|
4
4
|
tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch
|
|
5
5
|
permissionMode: acceptEdits
|
|
6
6
|
color: orange
|
|
7
|
-
# hooks:
|
|
8
|
-
# PostToolUse:
|
|
9
|
-
# - matcher: "Write|Edit"
|
|
10
|
-
# hooks:
|
|
11
|
-
# - type: command
|
|
12
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
13
7
|
---
|
|
14
8
|
|
|
15
9
|
<output_style>
|
package/kit/agents/executor.md
CHANGED
|
@@ -4,12 +4,6 @@ description: Executa planos framework com commits atômicos, tratamento de desvi
|
|
|
4
4
|
tools: Read, Write, Edit, Bash, Grep, Glob
|
|
5
5
|
permissionMode: acceptEdits
|
|
6
6
|
color: yellow
|
|
7
|
-
# hooks:
|
|
8
|
-
# PostToolUse:
|
|
9
|
-
# - matcher: "Write|Edit"
|
|
10
|
-
# hooks:
|
|
11
|
-
# - type: command
|
|
12
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
13
7
|
---
|
|
14
8
|
|
|
15
9
|
<output_style>
|
|
@@ -3,12 +3,6 @@ name: phase-researcher
|
|
|
3
3
|
description: Pesquisa como implementar uma fase antes do planejamento. Produz RESEARCH.md consumido pelo planner. Invocado pelo orquestrador /planejar-fase.
|
|
4
4
|
tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*
|
|
5
5
|
color: cyan
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
package/kit/agents/planner.md
CHANGED
|
@@ -3,12 +3,6 @@ name: planner
|
|
|
3
3
|
description: Cria planos de fase executáveis com decomposição de tarefas, análise de dependências e verificação orientada a objetivos. Acionado pelo orquestrador /planejar-fase.
|
|
4
4
|
tools: Read, Write, Bash, Glob, Grep, WebFetch, mcp__context7__*
|
|
5
5
|
color: green
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
|
@@ -3,12 +3,6 @@ name: project-researcher
|
|
|
3
3
|
description: Pesquisa ecossistema do domínio antes do roadmap. Produz arquivos em .planning/research/ consumidos pelo roadmapper. Invocado por /novo-projeto ou /novo-marco.
|
|
4
4
|
tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*
|
|
5
5
|
color: cyan
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
|
@@ -3,12 +3,6 @@ name: research-synthesizer
|
|
|
3
3
|
description: Sintetiza outputs de agentes pesquisadores paralelos em SUMMARY.md. Invocado por /novo-projeto após 4 agentes pesquisadores concluírem.
|
|
4
4
|
tools: Read, Write, Bash
|
|
5
5
|
color: purple
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
package/kit/agents/roadmapper.md
CHANGED
|
@@ -3,12 +3,6 @@ name: roadmapper
|
|
|
3
3
|
description: Cria roadmaps de projeto com divisão de fases, mapeamento de requisitos, derivação de critérios de sucesso e validação de cobertura. Invocado pelo orquestrador /novo-projeto.
|
|
4
4
|
tools: Read, Write, Bash, Glob, Grep
|
|
5
5
|
color: purple
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
package/kit/agents/ui-auditor.md
CHANGED
|
@@ -3,12 +3,6 @@ name: ui-auditor
|
|
|
3
3
|
description: Auditoria visual retroativa de 6 pilares do código frontend implementado. Produz UI-REVIEW.md pontuado. Invocado pelo orquestrador /revisar-ui.
|
|
4
4
|
tools: Read, Write, Bash, Grep, Glob
|
|
5
5
|
color: "#F472B6"
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
|
@@ -3,12 +3,6 @@ name: ui-researcher
|
|
|
3
3
|
description: Produz contrato de design UI-SPEC.md para fases frontend. Lê artefatos upstream, detecta estado do sistema de design, faz apenas perguntas não respondidas. Invocado pelo orquestrador /fase-ui.
|
|
4
4
|
tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*
|
|
5
5
|
color: "#E879F9"
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
package/kit/agents/verifier.md
CHANGED
|
@@ -3,12 +3,6 @@ name: verifier
|
|
|
3
3
|
description: Verifica atingimento do objetivo da fase via análise reversa. Checa se codebase entrega o prometido, não só task completion. Cria VERIFICATION.md.
|
|
4
4
|
tools: Read, Write, Bash, Grep, Glob
|
|
5
5
|
color: green
|
|
6
|
-
# hooks:
|
|
7
|
-
# PostToolUse:
|
|
8
|
-
# - matcher: "Write|Edit"
|
|
9
|
-
# hooks:
|
|
10
|
-
# - type: command
|
|
11
|
-
# command: "npx eslint --fix $FILE 2>/dev/null || true"
|
|
12
6
|
---
|
|
13
7
|
|
|
14
8
|
<output_style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// hook-version: 1.30.0
|
|
3
|
+
// SEC-13-05: flush-before-exit category = E (parent returns after spawn unref; child uses sync fs writes) — no fix needed
|
|
3
4
|
// Check for framework updates in background, write result to cache
|
|
4
5
|
// Called by SessionStart hook - runs once per session
|
|
5
6
|
|
|
@@ -42,6 +43,9 @@ if (!fs.existsSync(cacheDir)) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// Run check in background (spawn background process, windowsHide prevents console flash)
|
|
46
|
+
// SEC-13-05: parent process retorna imediatamente após child.unref() — não
|
|
47
|
+
// há buffered I/O no parent. Child usa fs.writeFileSync (sync), sem race.
|
|
48
|
+
// Categoria E na taxonomia da Phase 80.
|
|
45
49
|
const child = spawn(process.execPath, ['-e', `
|
|
46
50
|
const fs = require('fs');
|
|
47
51
|
const path = require('path');
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// hook-version: 1.30.
|
|
2
|
+
// hook-version: 1.30.1
|
|
3
|
+
// SEC-13-05: flush-before-exit category = A (stdout.write + immediate exit)
|
|
4
|
+
// Fix applied: process.stdout.write(payload, () => process.exit(0)) on warning path.
|
|
3
5
|
// Context Monitor - PostToolUse/AfterTool hook (Gemini uses AfterTool)
|
|
4
6
|
// Reads context metrics from the statusline bridge file and injects
|
|
5
7
|
// warnings when context usage is high. This makes the AGENT aware of
|
|
@@ -148,7 +150,12 @@ process.stdin.on('end', () => {
|
|
|
148
150
|
}
|
|
149
151
|
};
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
// SEC-13-05: aguardar flush do stdout antes do exit. Sem callback, em
|
|
154
|
+
// pipes lentos (CI/Windows/Git Bash) o JSON pode ser dropado quando o
|
|
155
|
+
// process termina antes do kernel drenar o buffer.
|
|
156
|
+
process.stdout.write(JSON.stringify(output), () => {
|
|
157
|
+
process.exit(0);
|
|
158
|
+
});
|
|
152
159
|
} catch (e) {
|
|
153
160
|
// Silent fail -- never block tool execution
|
|
154
161
|
process.exit(0);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// hook-version: 1.4.
|
|
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.
|
|
3
5
|
// kit-mcp · Post-apply Migration Hook (PostToolUse)
|
|
4
6
|
//
|
|
5
7
|
// Triggers automatically AFTER a successful Supabase MCP apply_migration call.
|
|
@@ -104,12 +106,18 @@ process.stdin.on('end', () => {
|
|
|
104
106
|
}
|
|
105
107
|
|
|
106
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.
|
|
107
114
|
if (mirroredPath || stubPath) {
|
|
108
115
|
const lines = ['[post-apply-migration] resumo:'];
|
|
109
116
|
if (mirroredPath) lines.push(` • SQL: ${path.relative(projectRoot, mirroredPath)}`);
|
|
110
117
|
if (stubPath) lines.push(` • Stub: ${path.relative(vault, stubPath)}`);
|
|
111
118
|
lines.push(' → cofre Obsidian: edite o stub e commite quando puder.');
|
|
112
|
-
process.stderr.write(lines.join('\n') + '\n');
|
|
119
|
+
process.stderr.write(lines.join('\n') + '\n', () => process.exit(0));
|
|
120
|
+
return;
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
process.exit(0);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// hook-version: 1.30.
|
|
2
|
+
// hook-version: 1.30.1
|
|
3
|
+
// SEC-13-05: flush-before-exit category = A (stdout.write + immediate exit)
|
|
4
|
+
// Fix applied: process.stdout.write(payload, () => process.exit(0)) on warning path.
|
|
3
5
|
// framework Prompt Injection Guard — PreToolUse hook
|
|
4
6
|
// Scans file content being written to .planning/ for prompt injection patterns.
|
|
5
7
|
// Defense-in-depth: catches injected instructions before they enter agent context.
|
|
@@ -88,7 +90,12 @@ process.stdin.on('end', () => {
|
|
|
88
90
|
},
|
|
89
91
|
};
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
// SEC-13-05: aguardar flush do stdout antes do exit. Sem callback, em
|
|
94
|
+
// pipes lentos (CI/Windows/Git Bash) o JSON pode ser dropado quando o
|
|
95
|
+
// process termina antes do kernel drenar o buffer.
|
|
96
|
+
process.stdout.write(JSON.stringify(output), () => {
|
|
97
|
+
process.exit(0);
|
|
98
|
+
});
|
|
92
99
|
} catch {
|
|
93
100
|
// Silent fail — never block tool execution
|
|
94
101
|
process.exit(0);
|
package/kit/hooks/statusline.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// hook-version: 1.30.0
|
|
3
|
+
// SEC-13-05: flush-before-exit category = C (no process.exit, natural termination flushes) — no fix needed
|
|
3
4
|
// Claude Code Statusline - Edition
|
|
4
5
|
// Shows: model | current task | directory | context usage
|
|
5
6
|
|
|
@@ -107,6 +108,11 @@ process.stdin.on('end', () => {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
// Output
|
|
111
|
+
// SEC-13-05: statusline termina naturalmente após este write — Node
|
|
112
|
+
// garante o flush antes do process exit quando não há process.exit
|
|
113
|
+
// explícito. NÃO converter para process.stdout.write(x, callback) +
|
|
114
|
+
// process.exit() — isso introduziria um early-exit que poderia
|
|
115
|
+
// truncar saída em casos onde o write é maior que o buffer do pipe.
|
|
110
116
|
const dirname = path.basename(dir);
|
|
111
117
|
if (task) {
|
|
112
118
|
process.stdout.write(`${updateNotice}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// hook-version: 1.30.
|
|
2
|
+
// hook-version: 1.30.1
|
|
3
|
+
// SEC-13-05: flush-before-exit category = A (stdout.write + immediate exit)
|
|
4
|
+
// Fix applied: process.stdout.write(payload, () => process.exit(0)) on warning path.
|
|
3
5
|
// framework Workflow Guard — PreToolUse hook
|
|
4
6
|
// Detects when Claude attempts file edits outside a framework workflow context
|
|
5
7
|
// (no active / command or Task subagent) and injects an advisory warning.
|
|
@@ -86,7 +88,12 @@ process.stdin.on('end', () => {
|
|
|
86
88
|
}
|
|
87
89
|
};
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
// SEC-13-05: aguardar flush do stdout antes do exit. Sem callback, em
|
|
92
|
+
// pipes lentos (CI/Windows/Git Bash) o JSON pode ser dropado quando o
|
|
93
|
+
// process termina antes do kernel drenar o buffer.
|
|
94
|
+
process.stdout.write(JSON.stringify(output), () => {
|
|
95
|
+
process.exit(0);
|
|
96
|
+
});
|
|
90
97
|
} catch (e) {
|
|
91
98
|
// Silent fail — never block tool execution
|
|
92
99
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luanpdd/kit-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"kit/",
|
|
17
17
|
"gates/",
|
|
18
18
|
"README.md",
|
|
19
|
-
"CHANGELOG.md",
|
|
20
19
|
"LICENSE"
|
|
21
20
|
],
|
|
22
21
|
"keywords": [
|
package/src/cli/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { listKit, searchKit, findItem } from '../core/kit.js';
|
|
20
20
|
import { listTargets } from '../core/registry.js';
|
|
21
|
-
import { syncTo, statusOf, removeFrom } from '../core/sync.js';
|
|
21
|
+
import { syncTo, statusOf, removeFrom, summarize } from '../core/sync.js';
|
|
22
22
|
import { watchKit, detectExistingTargets } from '../core/watch.js';
|
|
23
23
|
import { listGates, getGate, gatesForStage } from '../core/gates.js';
|
|
24
24
|
import { runGate } from '../core/gate-runner.js';
|
|
@@ -148,7 +148,10 @@ function fail(msg) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function slim(x) {
|
|
151
|
-
|
|
151
|
+
// PERF-13-01: cap description at SUMMARY_MAX_CHARS via shared summarize()
|
|
152
|
+
// helper from src/core/sync.js — keeps cross-surface behavior identical
|
|
153
|
+
// (CLI listing == MCP listing). Full text remains in each item's source file.
|
|
154
|
+
return { kind: x.kind, name: x.name, description: summarize(x.description) };
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
// --- kit ---
|
package/src/core/replays.js
CHANGED
|
@@ -17,15 +17,55 @@ import fs from 'node:fs/promises';
|
|
|
17
17
|
|
|
18
18
|
const REPLAY_DIR_REL = path.join('.planning', 'replays');
|
|
19
19
|
|
|
20
|
+
// SEC-13-02: replayId path traversal guard. The MCP forensics tool exposes
|
|
21
|
+
// load-replay/annotate-replay/record-replay actions; without sanitization,
|
|
22
|
+
// a malicious replayId like '../../../etc/passwd' would read/write files
|
|
23
|
+
// outside .planning/replays/.
|
|
24
|
+
//
|
|
25
|
+
// Strategy: allowlist regex (no slashes, no '..', no NUL) + post-resolve assertion
|
|
26
|
+
// that the final path stays inside REPLAY_DIR_REL.
|
|
27
|
+
const REPLAY_ID_RE = /^[A-Za-z0-9_.-]+$/;
|
|
28
|
+
|
|
29
|
+
function validateReplayId(id) {
|
|
30
|
+
if (typeof id !== 'string' || !id) {
|
|
31
|
+
throw new Error('invalid replay id: must be a non-empty string');
|
|
32
|
+
}
|
|
33
|
+
if (id === '.' || id === '..' || id.includes('..')) {
|
|
34
|
+
throw new Error('invalid replay id: traversal sequences not allowed');
|
|
35
|
+
}
|
|
36
|
+
if (!REPLAY_ID_RE.test(id)) {
|
|
37
|
+
throw new Error(`invalid replay id: only [A-Za-z0-9_.-] allowed, got ${JSON.stringify(id)}`);
|
|
38
|
+
}
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function assertPathInside(filePath, baseDir) {
|
|
43
|
+
const resolved = path.resolve(filePath);
|
|
44
|
+
const base = path.resolve(baseDir);
|
|
45
|
+
// Ensure resolved is base or a child of base (handle trailing-sep edge case).
|
|
46
|
+
if (resolved !== base && !resolved.startsWith(base + path.sep)) {
|
|
47
|
+
throw new Error('invalid replay id: resolved path escapes replay directory');
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
export async function recordReplay(payload, opts = {}) {
|
|
21
53
|
const projectRoot = path.resolve(opts.projectRoot ?? process.cwd());
|
|
22
54
|
const dir = path.join(projectRoot, REPLAY_DIR_REL);
|
|
23
55
|
await fs.mkdir(dir, { recursive: true });
|
|
24
56
|
|
|
25
57
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
26
|
-
|
|
58
|
+
// SEC-13-02: validate each slug component independently before concat
|
|
59
|
+
const slugParts = [payload.phase, payload.plan, payload.agent].filter(Boolean);
|
|
60
|
+
for (const part of slugParts) {
|
|
61
|
+
validateReplayId(String(part));
|
|
62
|
+
}
|
|
63
|
+
const slug = slugParts.join('-') || 'unknown';
|
|
27
64
|
const id = `${ts}-${slug}`;
|
|
65
|
+
// Re-validate the full id (defense in depth — ts is well-formed but cheap to check)
|
|
66
|
+
validateReplayId(id);
|
|
28
67
|
const file = path.join(dir, `${id}.json`);
|
|
68
|
+
assertPathInside(file, dir);
|
|
29
69
|
|
|
30
70
|
const record = { id, recorded_at: new Date().toISOString(), ...payload };
|
|
31
71
|
await fs.writeFile(file, JSON.stringify(record, null, 2), 'utf8');
|
|
@@ -49,15 +89,21 @@ export async function listReplays(opts = {}) {
|
|
|
49
89
|
}
|
|
50
90
|
|
|
51
91
|
export async function loadReplay(id, opts = {}) {
|
|
92
|
+
validateReplayId(id);
|
|
52
93
|
const projectRoot = path.resolve(opts.projectRoot ?? process.cwd());
|
|
53
|
-
const
|
|
94
|
+
const dir = path.join(projectRoot, REPLAY_DIR_REL);
|
|
95
|
+
const file = path.join(dir, `${id}.json`);
|
|
96
|
+
assertPathInside(file, dir);
|
|
54
97
|
const raw = await fs.readFile(file, 'utf8');
|
|
55
98
|
return JSON.parse(raw);
|
|
56
99
|
}
|
|
57
100
|
|
|
58
101
|
export async function annotateReplay(id, outcome, opts = {}) {
|
|
102
|
+
validateReplayId(id);
|
|
59
103
|
const projectRoot = path.resolve(opts.projectRoot ?? process.cwd());
|
|
60
|
-
const
|
|
104
|
+
const dir = path.join(projectRoot, REPLAY_DIR_REL);
|
|
105
|
+
const file = path.join(dir, `${id}.json`);
|
|
106
|
+
assertPathInside(file, dir);
|
|
61
107
|
const r = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
62
108
|
r.outcome = { ...(r.outcome ?? {}), ...outcome, annotated_at: new Date().toISOString() };
|
|
63
109
|
await fs.writeFile(file, JSON.stringify(r, null, 2), 'utf8');
|
package/src/core/sync.js
CHANGED
|
@@ -257,8 +257,10 @@ See: [\`${rel}\`](${rel})
|
|
|
257
257
|
// own file under kit/ — duplicating them here costs tokens in every Claude
|
|
258
258
|
// Code session. Cap each line at ~80 chars; users can `kit get <name>` for the
|
|
259
259
|
// full description.
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
// PERF-13-01: exported so slim() in src/mcp-server/index.js and src/cli/index.js
|
|
261
|
+
// can reuse the same cap (single source of truth — no duplicated constants).
|
|
262
|
+
export const SUMMARY_MAX_CHARS = 80;
|
|
263
|
+
export function summarize(desc) {
|
|
262
264
|
if (!desc) return '';
|
|
263
265
|
const flat = desc.replace(/\s+/g, ' ').trim();
|
|
264
266
|
if (flat.length <= SUMMARY_MAX_CHARS) return flat;
|
package/src/mcp-server/index.js
CHANGED
|
@@ -12,9 +12,13 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
12
12
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
13
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
14
14
|
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
|
|
15
19
|
import { listKit, searchKit, findItem } from '../core/kit.js';
|
|
16
20
|
import { listTargets } from '../core/registry.js';
|
|
17
|
-
import { syncTo, statusOf, removeFrom } from '../core/sync.js';
|
|
21
|
+
import { syncTo, statusOf, removeFrom, summarize } from '../core/sync.js';
|
|
18
22
|
import { detectReverse, applyReverse } from '../core/reverse-sync.js';
|
|
19
23
|
import { listGates, getGate, gatesForStage } from '../core/gates.js';
|
|
20
24
|
import { runGate } from '../core/gate-runner.js';
|
|
@@ -125,6 +129,23 @@ const TOOLS = [
|
|
|
125
129
|
},
|
|
126
130
|
];
|
|
127
131
|
|
|
132
|
+
// DRIFT-13-03: read version from package.json at module load (NOT inside
|
|
133
|
+
// createServer — re-reading on every call adds zero value). Same pattern as
|
|
134
|
+
// bin/cli.js:43-51. Both files are 2 levels deep from repo root, so the
|
|
135
|
+
// '..', '..' resolution works identically. Falls back to 'unknown' if the
|
|
136
|
+
// package.json lookup fails (unusual install layout).
|
|
137
|
+
function readPkgVersion() {
|
|
138
|
+
try {
|
|
139
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
140
|
+
const pkgPath = path.resolve(here, '..', '..', 'package.json');
|
|
141
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version;
|
|
142
|
+
} catch {
|
|
143
|
+
return 'unknown';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const PKG_VERSION = readPkgVersion();
|
|
148
|
+
|
|
128
149
|
// --- handlers ---
|
|
129
150
|
|
|
130
151
|
async function handleKit(args) {
|
|
@@ -200,12 +221,14 @@ async function handleGates(args) {
|
|
|
200
221
|
case 'get': return getGate(args.id);
|
|
201
222
|
case 'for-stage': return gatesForStage(args.stage);
|
|
202
223
|
case 'run':
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
224
|
+
// SEC-13-01: MCP transport must never execute shell — runGate spawns bash with
|
|
225
|
+
// arbitrary content from gates/*.md (which reverse-sync can rewrite). Even with
|
|
226
|
+
// {yes: true}, this skips the interactive "y/N before exec" promise. The CLI
|
|
227
|
+
// entry point (`kit gates run <id>` via bin/cli.js) preserves the prompt and
|
|
228
|
+
// remains the only path to executing gates.
|
|
229
|
+
return {
|
|
230
|
+
error: 'MCP gates.run requires interactive TTY confirmation; use `kit gates run` from CLI instead.',
|
|
231
|
+
};
|
|
209
232
|
default: return { error: `Unknown action: ${args.action}` };
|
|
210
233
|
}
|
|
211
234
|
}
|
|
@@ -255,14 +278,16 @@ const HANDLERS = {
|
|
|
255
278
|
function slim(x) {
|
|
256
279
|
// absPath omitted by design — list-* tools are AI-consumed in tight context budgets.
|
|
257
280
|
// Use action=get to fetch the absPath (and content) for a specific item.
|
|
258
|
-
|
|
281
|
+
// PERF-13-01 (TOK-02): truncate description via SUMMARY_MAX_CHARS (80) cap shared
|
|
282
|
+
// with src/core/sync.js — full description lives in each item's file under kit/.
|
|
283
|
+
return { kind: x.kind, name: x.name, description: summarize(x.description) };
|
|
259
284
|
}
|
|
260
285
|
|
|
261
286
|
// --- server bootstrap ---
|
|
262
287
|
|
|
263
288
|
export async function createServer() {
|
|
264
289
|
const server = new Server(
|
|
265
|
-
{ name: 'kit-mcp', version:
|
|
290
|
+
{ name: 'kit-mcp', version: PKG_VERSION },
|
|
266
291
|
{ capabilities: { tools: {} } }
|
|
267
292
|
);
|
|
268
293
|
|