@luanpdd/kit-mcp 1.6.1 → 1.7.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.
@@ -234,43 +234,15 @@ Se "Executar discuss-phase primeiro":
234
234
 
235
235
  ## 5. Tratar Pesquisa
236
236
 
237
- **Pular se:** flag `--gaps` ou flag `--skip-research` ou flag `--reviews`.
237
+ **Pular se:** `--gaps`, `--skip-research`, ou `--reviews`.
238
238
 
239
- **Se `has_research` for true (do init) E sem flag `--research`:** Usar existente, pular para o passo 6.
239
+ **Se `has_research` true e sem `--research`:** usar existente, ir pra passo 6.
240
240
 
241
- **Se RESEARCH.md ausente OU flag `--research`:**
241
+ **Se ausente OR `--research`:** sem flag explícita e sem `--auto`, perguntar (AskUserQuestion ou lista numerada se TEXT_MODE):
242
+ - "Pesquisar primeiro (Recomendado)" — investiga domínio/padrões/deps antes de planejar. Melhor pra features novas, integrações novas, mudanças arquiteturais.
243
+ - "Pular pesquisa" — planeja direto do contexto. Melhor pra bug fix, refactor simples, tarefas bem compreendidas.
242
244
 
243
- **Se sem flag explícita (`--research` ou `--skip-research`) e não `--auto`:**
244
- Perguntar ao usuário se deseja pesquisar, com uma recomendação contextual baseada na fase:
245
-
246
- Se `TEXT_MODE` for true, apresentar como lista numerada de texto simples:
247
- ```
248
- Pesquisar antes de planejar a Fase {X}: {phase_name}?
249
-
250
- 1. Pesquisar primeiro (Recomendado) — Investigar domínio, padrões e dependências antes do planejamento. Melhor para novas funcionalidades, integrações desconhecidas ou mudanças arquiteturais.
251
- 2. Pular pesquisa — Planejar diretamente a partir do contexto e requisitos. Melhor para correções de bugs, refatorações simples ou tarefas bem compreendidas.
252
-
253
- Digite o número:
254
- ```
255
-
256
- Caso contrário usar AskUserQuestion:
257
- ```
258
- AskUserQuestion([
259
- {
260
- question: "Pesquisar antes de planejar a Fase {X}: {phase_name}?",
261
- header: "Pesquisa",
262
- multiSelect: false,
263
- options: [
264
- { label: "Pesquisar primeiro (Recomendado)", description: "Investigar domínio, padrões e dependências antes do planejamento. Melhor para novas funcionalidades, integrações desconhecidas ou mudanças arquiteturais." },
265
- { label: "Pular pesquisa", description: "Planejar diretamente a partir do contexto e requisitos. Melhor para correções de bugs, refatorações simples ou tarefas bem compreendidas." }
266
- ]
267
- }
268
- ])
269
- ```
270
-
271
- Se o usuário selecionar "Pular pesquisa": pular para o passo 6.
272
-
273
- **Se `--auto` e `research_enabled` for false:** Pular pesquisa silenciosamente (preserva comportamento automatizado).
245
+ Se "Pular": passo 6. Se `--auto` e `research_enabled=false`: pular silenciosamente.
274
246
 
275
247
  Exibir banner:
276
248
  ```
@@ -365,51 +337,16 @@ test -f "${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md" && echo "VALIDATION_CREATED
365
337
  > Pular se `workflow.ui_phase` for explicitamente `false` E `workflow.ui_safety_gate` for explicitamente `false` em `.planning/config.json`. Se as chaves estiverem ausentes, tratar como habilitado.
366
338
 
367
339
  ```bash
368
- UI_PHASE_CFG=$(node "./.claude/framework/bin/tools.cjs" config-get workflow.ui_phase 2>/dev/null || echo "true")
369
- UI_GATE_CFG=$(node "./.claude/framework/bin/tools.cjs" config-get workflow.ui_safety_gate 2>/dev/null || echo "true")
370
- ```
371
-
372
- **Se ambos forem `false`:** Pular para o passo 6.
373
-
374
- Verificar se a fase tem indicadores de frontend:
375
-
376
- ```bash
377
- PHASE_SECTION=$(node "./.claude/framework/bin/tools.cjs" roadmap get-phase "${PHASE}" 2>/dev/null)
378
- echo "$PHASE_SECTION" | grep -iE "UI|interface|frontend|component|layout|page|screen|view|form|dashboard|widget" > /dev/null 2>&1
379
- HAS_UI=$?
380
- ```
381
-
382
- **Se `HAS_UI` for 0 (indicadores de frontend encontrados):**
383
-
384
- Verificar UI-SPEC existente:
385
- ```bash
386
- UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
387
- ```
388
-
389
- **Se UI-SPEC.md encontrado:** Definir `UI_SPEC_PATH=$UI_SPEC_FILE`. Exibir: `Usando contrato de design de UI: ${UI_SPEC_PATH}`
390
-
391
- **Se UI-SPEC.md ausente E `UI_GATE_CFG` for `true`:**
392
-
393
- Se `TEXT_MODE` for true, apresentar como lista numerada de texto simples:
394
- ```
395
- A Fase {N} tem indicadores de frontend mas sem UI-SPEC.md. Gerar um contrato de design antes do planejamento?
340
+ `UI_PHASE_CFG` / `UI_GATE_CFG` (default true). Se ambos false, pular pra passo 6.
396
341
 
397
- 1. Gerar UI-SPEC primeiro Execute /fase-ui {N} então re-execute /planejar-fase {N}
398
- 2. Continuar sem UI-SPEC
399
- 3. Não é uma fase de frontend
342
+ **Detecção:** grep `-iE "UI|interface|frontend|component|layout|page|screen|view|form|dashboard|widget"` na descrição da fase. Se sem match, pular silenciosamente.
400
343
 
401
- Digite o número:
402
- ```
403
-
404
- Caso contrário usar AskUserQuestion:
405
- - header: "Contrato de Design de UI"
406
- - question: "A Fase {N} tem indicadores de frontend mas sem UI-SPEC.md. Gerar um contrato de design antes do planejamento?"
407
- - options:
408
- - "Gerar UI-SPEC primeiro" → Exibir: "Execute `/fase-ui {N} ${WS}` então re-execute `/planejar-fase {N} ${WS}`". Sair do workflow.
409
- - "Continuar sem UI-SPEC" → Continuar para o passo 6.
410
- - "Não é uma fase de frontend" → Continuar para o passo 6.
411
-
412
- **Se `HAS_UI` for 1 (sem indicadores de frontend):** Pular silenciosamente para o passo 6.
344
+ **Se match encontrado:**
345
+ - UI-SPEC.md existe → usar (`UI_SPEC_PATH`); exibir confirmação
346
+ - UI-SPEC.md ausente E `UI_GATE_CFG=true` → AskUserQuestion (ou lista numerada se TEXT_MODE):
347
+ - "Gerar UI-SPEC primeiro" → exibir `/fase-ui {N} ${WS}` e sair do workflow
348
+ - "Continuar sem UI-SPEC" passo 6
349
+ - "Não é frontend" passo 6
413
350
 
414
351
  ## 6. Verificar Planos Existentes
415
352
 
@@ -511,28 +448,13 @@ Output consumed by /execute-phase. Plans need:
511
448
 
512
449
  Every task MUST include these fields — they are NOT optional:
513
450
 
514
- 1. **`<read_first>`** — Files the executor MUST read before touching anything. Always include:
515
- - The file being modified (so executor sees current state, not assumptions)
516
- - Any "source of truth" file referenced in CONTEXT.md (reference implementations, existing patterns, config files, schemas)
517
- - Any file whose patterns, signatures, types, or conventions must be replicated or respected
518
-
519
- 2. **`<acceptance_criteria>`** — Verifiable conditions that prove the task was done correctly. Rules:
520
- - Every criterion must be checkable with grep, file read, test command, or CLI output
521
- - NEVER use subjective language ("looks correct", "properly configured", "consistent with")
522
- - ALWAYS include exact strings, patterns, values, or command outputs that must be present
523
- - Examples:
524
- - Code: `auth.py contains def verify_token(` / `test_auth.py exits 0`
525
- - Config: `.env.example contains DATABASE_URL=` / `Dockerfile contains HEALTHCHECK`
526
- - Docs: `README.md contains '## Installation'` / `API.md lists all endpoints`
527
- - Infra: `deploy.yml has rollback step` / `docker-compose.yml has healthcheck for db`
528
-
529
- 3. **`<action>`** — Must include CONCRETE values, not references. Rules:
530
- - NEVER say "align X with Y", "match X to Y", "update to be consistent" without specifying the exact target state
531
- - ALWAYS include the actual values: config keys, function signatures, SQL statements, class names, import paths, env vars, etc.
532
- - If CONTEXT.md has a comparison table or expected values, copy them into the action verbatim
533
- - The executor should be able to complete the task from the action text alone, without needing to read CONTEXT.md or reference files (read_first is for verification, not discovery)
534
-
535
- **Why this matters:** Executor agents work from the plan text. Vague instructions like "update the config to match production" produce shallow one-line changes. Concrete instructions like "add DATABASE_URL=postgresql://... , set POOL_SIZE=20, add REDIS_URL=redis://..." produce complete work. The cost of verbose plans is far less than the cost of re-doing shallow execution.
451
+ 1. **`<read_first>`** — files o executor DEVE ler antes de tocar em qualquer coisa: o arquivo sendo modificado, "source of truth" do CONTEXT.md, qualquer arquivo cujas convenções/tipos/assinaturas precisem ser replicados.
452
+
453
+ 2. **`<acceptance_criteria>`** condições verificáveis com grep/file read/test command/CLI output. NUNCA linguagem subjetiva ("looks correct"); SEMPRE strings/patterns exatos. Ex: `auth.py contains "def verify_token("`, `test_auth.py exits 0`, `.env.example contains "DATABASE_URL="`.
454
+
455
+ 3. **`<action>`** — valores CONCRETOS, nunca referências. NUNCA "align X with Y"; SEMPRE valores reais (config keys, function signatures, SQL, imports, env vars). Se CONTEXT.md tem tabela de comparação, copie no `<action>` literal. Executor deve completar só com texto do action.
456
+
457
+ **Por quê:** instruções vagas ("update config to match production") geram one-line changes; instruções concretas ("add DATABASE_URL=..., POOL_SIZE=20, REDIS_URL=...") geram trabalho completo. Custo de plano verboso é ínfimo vs custo de redo de execução shallow.
536
458
  </deep_work_rules>
537
459
 
538
460
  <quality_gate>
@@ -725,58 +647,17 @@ Rotear para `<offer_next>` OU `auto_advance` dependendo de flags/config.
725
647
 
726
648
  Verificar gatilho de avanço automático:
727
649
 
728
- 1. Analisar flag `--auto` de $ARGUMENTS
729
- 2. **Sincronizar flag de cadeia com intenção** — se o usuário invocou manualmente (sem `--auto`), limpar a flag de cadeia efêmera de qualquer cadeia `--auto` anterior interrompida. Isso NÃO toca em `workflow.auto_advance` (preferência persistente do usuário):
730
- ```bash
731
- if [[ ! "$ARGUMENTS" =~ --auto ]]; then
732
- node "./.claude/framework/bin/tools.cjs" config-set workflow._auto_chain_active false 2>/dev/null
733
- fi
734
- ```
735
- 3. Ler tanto a flag de cadeia quanto a preferência do usuário:
736
- ```bash
737
- AUTO_CHAIN=$(node "./.claude/framework/bin/tools.cjs" config-get workflow._auto_chain_active 2>/dev/null || echo "false")
738
- AUTO_CFG=$(node "./.claude/framework/bin/tools.cjs" config-get workflow.auto_advance 2>/dev/null || echo "false")
739
- ```
740
-
741
- **Se flag `--auto` presente OU `AUTO_CHAIN` for true OU `AUTO_CFG` for true:**
742
-
743
- Exibir banner:
744
- ```
745
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
746
- framework ► AVANÇANDO AUTOMATICAMENTE PARA EXECUÇÃO
747
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
748
-
749
- Planos prontos. Iniciando execute-phase...
750
- ```
751
-
752
- Iniciar execute-phase usando a ferramenta Skill para evitar sessões Task aninhadas (que causam freezes de runtime devido ao aninhamento profundo de agentes):
753
- ```
754
- Skill(skill="framework:executar-fase", args="${PHASE} --auto --no-transition ${WS}")
755
- ```
756
-
757
- A flag `--no-transition` diz ao execute-phase para retornar status após verificação em vez de encadear mais. Isso mantém a cadeia de avanço automático plana — cada fase roda no mesmo nível de aninhamento em vez de criar agentes Task mais profundos.
650
+ **Detecção:** flag `--auto` em $ARGUMENTS, OR `workflow._auto_chain_active=true`, OR `workflow.auto_advance=true`.
758
651
 
759
- **Lidar com retorno do execute-phase:**
760
- - **FASE CONCLUÍDA** → Exibir resumo final:
761
- ```
762
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
763
- framework ► FASE ${PHASE} CONCLUÍDA ✓
764
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
652
+ **Sync de cadeia:** se invocação manual (sem `--auto`), zere `workflow._auto_chain_active` (não toque `workflow.auto_advance`).
765
653
 
766
- Pipeline de avanço automático finalizado.
654
+ **Quando ativo:** dispare `Skill(skill="framework:executar-fase", args="${PHASE} --auto --no-transition ${WS}")`. A flag `--no-transition` diz pra execute-phase retornar status após verificação (não encadear), mantendo cadeia plana.
767
655
 
768
- Próximo: /discutir-fase ${NEXT_PHASE} --auto ${WS}
769
- ```
770
- - **LACUNAS ENCONTRADAS / VERIFICAÇÃO FALHOU**Exibir resultado, parar cadeia:
771
- ```
772
- Avanço automático parado: Execução precisa de revisão.
773
-
774
- Revisar a saída acima e continuar manualmente:
775
- /executar-fase ${PHASE} ${WS}
776
- ```
656
+ **Roteamento de retorno:**
657
+ - `FASE CONCLUÍDA` → próximo: `/discutir-fase ${NEXT_PHASE} --auto ${WS}` (após `/clear`)
658
+ - `LACUNAS ENCONTRADAS` / `VERIFICAÇÃO FALHOU` → parar cadeia. Continuar: `/executar-fase ${PHASE} ${WS}`
777
659
 
778
- **Se nem `--auto` nem config habilitado:**
779
- Rotear para `<offer_next>` (comportamento existente).
660
+ **Quando inativo:** rotear para `<offer_next>`.
780
661
 
781
662
  </process>
782
663
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luanpdd/kit-mcp",
3
- "version": "1.6.1",
3
+ "version": "1.7.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": {
package/src/core/kit.js CHANGED
@@ -33,28 +33,50 @@ export function resolveKitRoot(kitRoot) {
33
33
  // disk on every invocation. Trade-off: callers that edit kit/ inside the same
34
34
  // process may see stale data for up to 30s. Acceptable for MCP/CLI ergonomics.
35
35
  const KIT_CACHE_TTL_MS = 30_000;
36
- const kitCache = new Map(); // kitRoot -> { value, ts }
36
+ const kitCache = new Map(); // `${kitRoot}:${mode}` -> { value, ts }
37
+
38
+ // PERF-S1: when sync runs in mode=reference (default), the body/content of each
39
+ // kit file is never used — only frontmatter (name + description). Reading just
40
+ // the first STUB_READ_BYTES is enough for any frontmatter we'd ever produce and
41
+ // avoids loading 50 KB+ files (planner.md etc) from disk.
42
+ const STUB_READ_BYTES = 4096;
37
43
 
38
44
  export function clearKitCache() { kitCache.clear(); }
39
45
 
40
- export async function listKit(kitRoot) {
46
+ export async function listKit(kitRoot, opts = {}) {
41
47
  kitRoot = resolveKitRoot(kitRoot);
42
- const cached = kitCache.get(kitRoot);
48
+ const stubsOnly = opts.stubsOnly === true;
49
+ const cacheKey = `${kitRoot}:${stubsOnly ? 'stubs' : 'full'}`;
50
+ const cached = kitCache.get(cacheKey);
43
51
  if (cached && Date.now() - cached.ts < KIT_CACHE_TTL_MS) {
44
52
  return cached.value;
45
53
  }
46
54
  const [agents, commands, skills, skillsExtras] = await Promise.all([
47
- readMdDir(path.join(kitRoot, 'agents'), 'agent'),
48
- readMdDir(path.join(kitRoot, 'commands'), 'command'),
49
- readSkillsDir(path.join(kitRoot, 'skills')),
50
- readSkillsDir(path.join(kitRoot, 'skills-extras')).catch(() => []),
55
+ readMdDir(path.join(kitRoot, 'agents'), 'agent', { stubsOnly }),
56
+ readMdDir(path.join(kitRoot, 'commands'), 'command', { stubsOnly }),
57
+ readSkillsDir(path.join(kitRoot, 'skills'), { stubsOnly }),
58
+ readSkillsDir(path.join(kitRoot, 'skills-extras'), { stubsOnly }).catch(() => []),
51
59
  ]);
52
- const value = { agents, commands, skills, skillsExtras, kitRoot };
53
- kitCache.set(kitRoot, { value, ts: Date.now() });
60
+ const value = { agents, commands, skills, skillsExtras, kitRoot, stubsOnly };
61
+ kitCache.set(cacheKey, { value, ts: Date.now() });
54
62
  return value;
55
63
  }
56
64
 
57
- async function readMdDir(dir, kind) {
65
+ // Read just enough bytes from the head of the file to capture the frontmatter.
66
+ // Returns the partial string. fs.open + fd.read avoids the OS pre-fetching the
67
+ // rest of the file (which fs.readFile would force).
68
+ async function readHead(absPath, n) {
69
+ const fd = await fs.open(absPath, 'r');
70
+ try {
71
+ const buf = Buffer.alloc(n);
72
+ const { bytesRead } = await fd.read(buf, 0, n, 0);
73
+ return buf.subarray(0, bytesRead).toString('utf8');
74
+ } finally {
75
+ await fd.close();
76
+ }
77
+ }
78
+
79
+ async function readMdDir(dir, kind, { stubsOnly = false } = {}) {
58
80
  let entries;
59
81
  try {
60
82
  entries = await fs.readdir(dir, { withFileTypes: true });
@@ -65,23 +87,28 @@ async function readMdDir(dir, kind) {
65
87
  for (const e of entries) {
66
88
  if (!e.isFile() || !e.name.endsWith('.md')) continue;
67
89
  const absPath = path.join(dir, e.name);
68
- const raw = await fs.readFile(absPath, 'utf8');
90
+ const raw = stubsOnly
91
+ ? await readHead(absPath, STUB_READ_BYTES)
92
+ : await fs.readFile(absPath, 'utf8');
69
93
  const { frontmatter, body } = splitFrontmatter(raw);
70
- out.push({
94
+ const item = {
71
95
  kind,
72
96
  name: e.name.replace(/\.md$/, ''),
73
97
  absPath,
74
98
  frontmatter,
75
99
  frontmatterRaw: matchFrontmatterRaw(raw),
76
- body,
77
- content: raw,
78
100
  description: frontmatter?.description ?? firstNonEmptyLine(body),
79
- });
101
+ };
102
+ if (!stubsOnly) {
103
+ item.body = body;
104
+ item.content = raw;
105
+ }
106
+ out.push(item);
80
107
  }
81
108
  return out.sort((a, b) => a.name.localeCompare(b.name));
82
109
  }
83
110
 
84
- async function readSkillsDir(dir) {
111
+ async function readSkillsDir(dir, { stubsOnly = false } = {}) {
85
112
  let entries;
86
113
  try {
87
114
  entries = await fs.readdir(dir, { withFileTypes: true });
@@ -93,20 +120,26 @@ async function readSkillsDir(dir) {
93
120
  if (!e.isDirectory()) continue;
94
121
  const skillPath = path.join(dir, e.name, 'SKILL.md');
95
122
  let raw;
96
- try { raw = await fs.readFile(skillPath, 'utf8'); }
97
- catch { continue; }
123
+ try {
124
+ raw = stubsOnly
125
+ ? await readHead(skillPath, STUB_READ_BYTES)
126
+ : await fs.readFile(skillPath, 'utf8');
127
+ } catch { continue; }
98
128
  const { frontmatter, body } = splitFrontmatter(raw);
99
- out.push({
129
+ const item = {
100
130
  kind: 'skill',
101
131
  name: e.name,
102
132
  absPath: skillPath,
103
133
  dirPath: path.join(dir, e.name),
104
134
  frontmatter,
105
135
  frontmatterRaw: matchFrontmatterRaw(raw),
106
- body,
107
- skillContent: raw,
108
136
  description: frontmatter?.description ?? firstNonEmptyLine(body),
109
- });
137
+ };
138
+ if (!stubsOnly) {
139
+ item.body = body;
140
+ item.skillContent = raw;
141
+ }
142
+ out.push(item);
110
143
  }
111
144
  return out.sort((a, b) => a.name.localeCompare(b.name));
112
145
  }
package/src/core/sync.js CHANGED
@@ -28,7 +28,9 @@ export async function syncTo(targetId, opts = {}) {
28
28
 
29
29
  // PERF-03: accept a pre-loaded kit to avoid re-walking the disk when callers
30
30
  // already have one in hand (CLI sync that follows reverse-sync detect, etc).
31
- const kit = opts.kit ?? await listKit(kitRoot);
31
+ // PERF-S1: in mode=reference (default), read just frontmatter — body/content
32
+ // is never used by stub renderers. Saves I/O on big kit files (planner.md etc).
33
+ const kit = opts.kit ?? await listKit(kitRoot, { stubsOnly: mode === 'reference' });
32
34
  const ops = [];
33
35
 
34
36
  if (target.rules) {