@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.
- package/CHANGELOG.md +25 -0
- package/kit/agents/advisor-researcher.md +1 -14
- package/kit/agents/assumptions-analyzer.md +1 -14
- package/kit/agents/codebase-mapper.md +1 -14
- package/kit/agents/debugger.md +1 -19
- package/kit/agents/executor.md +1 -18
- package/kit/agents/integration-checker.md +1 -16
- package/kit/agents/nyquist-auditor.md +1 -16
- package/kit/agents/phase-researcher.md +1 -14
- package/kit/agents/plan-checker.md +1 -16
- package/kit/agents/planner.md +1 -16
- package/kit/agents/project-researcher.md +1 -14
- package/kit/agents/research-synthesizer.md +1 -9
- package/kit/agents/roadmapper.md +1 -14
- package/kit/agents/ui-auditor.md +1 -16
- package/kit/agents/ui-checker.md +1 -16
- package/kit/agents/ui-researcher.md +1 -14
- package/kit/agents/user-profiler.md +1 -9
- package/kit/agents/verifier.md +1 -16
- package/kit/commands/expresso.md +9 -0
- package/kit/commands/fazer.md +17 -4
- package/kit/commands/proximo.md +7 -0
- package/kit/commands/rapido.md +6 -0
- package/kit/framework/references/output-style.md +22 -0
- package/kit/framework/workflows/discuss-phase.md +47 -331
- package/kit/framework/workflows/help.md +14 -1
- package/kit/framework/workflows/new-project.md +16 -107
- package/kit/framework/workflows/plan-phase.md +28 -147
- package/package.json +1 -1
- package/src/core/kit.js +55 -22
- package/src/core/sync.js +3 -1
|
@@ -234,43 +234,15 @@ Se "Executar discuss-phase primeiro":
|
|
|
234
234
|
|
|
235
235
|
## 5. Tratar Pesquisa
|
|
236
236
|
|
|
237
|
-
**Pular se:**
|
|
237
|
+
**Pular se:** `--gaps`, `--skip-research`, ou `--reviews`.
|
|
238
238
|
|
|
239
|
-
**Se `has_research`
|
|
239
|
+
**Se `has_research` true e sem `--research`:** usar existente, ir pra passo 6.
|
|
240
240
|
|
|
241
|
-
**Se
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
-
|
|
406
|
-
-
|
|
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>`** —
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
769
|
-
|
|
770
|
-
-
|
|
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
|
-
**
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|