dd-harness 0.26.0 → 0.27.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/dist/curar.js CHANGED
@@ -127,6 +127,7 @@ export async function apaga(raiz, endereco, confirmacao) {
127
127
  headers: cabecalhos(token, true),
128
128
  body: JSON.stringify({
129
129
  tenant: config.tenant,
130
+ projeto: config.projeto,
130
131
  ...(confirmacao ? { confirmacao } : {}),
131
132
  }),
132
133
  });
package/dist/index.js CHANGED
@@ -21,6 +21,8 @@ import { pergunta, escolha, fechaPerguntas } from "./pergunta.js";
21
21
  import { buscaPolitica } from "./politica.js";
22
22
  import { avisoDeOrdem, blocoDeSessao, criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "./roadmap.js";
23
23
  import { decideDoHook, guardaCache } from "./cinto.js";
24
+ import { escrevePonteirosDeSkills, leSkills, semeiaSkills } from "./skill.js";
25
+ import { SKILLS_INICIAIS } from "./skills-iniciais.js";
24
26
  import { busca } from "./buscar.js";
25
27
  import { apaga, arquiva, edita, le, promove } from "./curar.js";
26
28
  import { criaPasta } from "./pasta.js";
@@ -439,6 +441,33 @@ async function comandoStart() {
439
441
  acrescentado: `atualizado ${arquivo} (apontamento acrescentado ao que já existia)`,
440
442
  }[r.estado]);
441
443
  }
444
+ // 6.5. Os ponteiros das skills do projeto.
445
+ //
446
+ // O Claude Code descobre skill lendo `.claude/skills/*/SKILL.md` na ABERTURA da sessao,
447
+ // e servidor MCP nao fornece skill — entao o arquivo em disco e obrigatorio. O que ele
448
+ // carrega e so o frontmatter mais a chamada a `ler_skill`: o procedimento fica no
449
+ // servico, e corrigi-lo corrige em todos os repositorios de uma vez.
450
+ //
451
+ // Nunca falha o `start`: projeto sem skill e o caso comum, e nao ter skill nao impede
452
+ // nada do resto.
453
+ try {
454
+ // Projeto novo nasce com as skills iniciais, como nasce com politica e briefing. Num
455
+ // projeto que ja tem as suas, isto nao faz nada.
456
+ const { criadas } = await semeiaSkills(process.cwd(), SKILLS_INICIAIS);
457
+ if (criadas.length > 0) {
458
+ console.log(`criada(s) ${criadas.length} skill(s) inicial(is): ${criadas.join(", ")}`);
459
+ }
460
+ const skills = await leSkills(process.cwd());
461
+ if (skills.length > 0) {
462
+ const { escritos } = await escrevePonteirosDeSkills(process.cwd(), skills);
463
+ console.log(escritos > 0
464
+ ? `escritos ${escritos} ponteiro(s) de skill em .claude/skills/`
465
+ : `${skills.length} skill(s) — ponteiros já estavam em dia`);
466
+ }
467
+ }
468
+ catch {
469
+ // Serviço fora do ar ou projeto recém-criado: o `start` segue.
470
+ }
442
471
  // 7. Onde esta o monorepo do worker nesta maquina? So pergunta uma vez, e so importa
443
472
  // se houver memoria na fila agora — pular aqui nao trava nada, so avisa mais vezes.
444
473
  const semWorkerConfigurado = !(await temWorkerConfigurado());
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Skills: o procedimento mora no servico, o disco guarda so o ponteiro.
3
+ *
4
+ * O Claude Code descobre skill lendo `.claude/skills/<nome>/SKILL.md` na ABERTURA da
5
+ * sessao, e nenhum servidor MCP pode fornecer skill — o protocolo serve ferramentas e
6
+ * prompts, nao skills. Entao o arquivo em disco e inevitavel.
7
+ *
8
+ * O que e evitavel e o CONTEUDO em disco. O ponteiro carrega o frontmatter real (que e o
9
+ * que o host le para listar e decidir invocar) e um corpo de poucas linhas mandando
10
+ * chamar `ler_skill`. O procedimento fica num lugar so, e corrigi-lo corrige em todos os
11
+ * repositorios — que e o motivo de skills terem saido do disco.
12
+ *
13
+ * Repare que o custo de contexto NAO e o motivo: o Claude Code ja carrega so a descricao
14
+ * de cada skill e busca o corpo quando invocada. O ponteiro nao economiza nada ali — ele
15
+ * resolve divergencia, nao peso.
16
+ */
17
+ export type Skill = {
18
+ slug: string;
19
+ descricao: string;
20
+ so_por_comando: boolean;
21
+ ferramentas: string[];
22
+ dica_de_argumento: string | null;
23
+ caminhos: string[];
24
+ atualizada_em: string;
25
+ };
26
+ export type SkillCompleta = Skill & {
27
+ conteudo: string;
28
+ };
29
+ export declare function leSkills(raiz: string): Promise<Skill[]>;
30
+ export declare function leSkill(raiz: string, slug: string): Promise<SkillCompleta>;
31
+ export declare function criaSkill(raiz: string, dados: NovaSkillInicial & Partial<Pick<Skill, "so_por_comando" | "ferramentas" | "caminhos" | "dica_de_argumento">>): Promise<{
32
+ skill: Skill;
33
+ }>;
34
+ /**
35
+ * O SKILL.md que vai para o disco: frontmatter de verdade, corpo que aponta para o MCP.
36
+ *
37
+ * `allowed-tools` sempre inclui a ferramenta que busca o procedimento — sem ela o ponteiro
38
+ * pediria permissao para ler a propria skill, e um "permitir?" antes de cada invocacao
39
+ * ensina a desligar o mecanismo.
40
+ */
41
+ export declare function ponteiroDaSkill(skill: Skill): string;
42
+ /**
43
+ * Escreve os ponteiros de todas as skills do projeto.
44
+ *
45
+ * NAO apaga diretorio que sobrou: uma skill apagada no servico deixa o ponteiro orfao, e
46
+ * varrer `.claude/skills/` inteiro levaria junto as skills que a pessoa escreveu a mao —
47
+ * que sao legitimas e nao passam por aqui. O DELETE da API avisa o que apagar; a decisao
48
+ * de apagar arquivo alheio nao e nossa.
49
+ */
50
+ export declare function escrevePonteirosDeSkills(raiz: string, skills: Skill[]): Promise<{
51
+ escritos: number;
52
+ }>;
53
+ /** O minimo para criar uma skill: o resto tem default no banco. */
54
+ export type NovaSkillInicial = {
55
+ slug: string;
56
+ descricao: string;
57
+ conteudo: string;
58
+ };
59
+ /**
60
+ * Semeia as skills iniciais — so num projeto que ainda nao tem nenhuma.
61
+ *
62
+ * A checagem e "nenhuma skill", e nao "esta skill especifica": quem apagou
63
+ * `como-desenvolver` tomou uma decisao, e recria-la a cada `start` a desfaria em silencio.
64
+ * Projeto com qualquer skill propria ja passou do ponto de ser semeado.
65
+ *
66
+ * Devolve o que criou. Nunca lanca por falha de rede: semear e conveniencia de projeto
67
+ * novo, e um `start` nao pode falhar porque o mimo nao pode ser entregue.
68
+ */
69
+ export declare function semeiaSkills(raiz: string, iniciais: NovaSkillInicial[]): Promise<{
70
+ criadas: string[];
71
+ }>;
package/dist/skill.js ADDED
@@ -0,0 +1,143 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { cabecalhos, credencial, pede, recusa } from "./api.js";
4
+ // --- rede ---
5
+ export async function leSkills(raiz) {
6
+ const { config, token } = await credencial(raiz);
7
+ const url = new URL(`${config.api}/api/v1/skills`);
8
+ url.searchParams.set("tenant", config.tenant);
9
+ url.searchParams.set("projeto", config.projeto);
10
+ const resposta = await pede(url, { headers: cabecalhos(token) });
11
+ if (!resposta.ok)
12
+ await recusa(resposta);
13
+ return (await resposta.json()).skills;
14
+ }
15
+ export async function leSkill(raiz, slug) {
16
+ const { config, token } = await credencial(raiz);
17
+ const url = new URL(`${config.api}/api/v1/skills/${encodeURIComponent(slug)}`);
18
+ url.searchParams.set("tenant", config.tenant);
19
+ url.searchParams.set("projeto", config.projeto);
20
+ const resposta = await pede(url, { headers: cabecalhos(token) });
21
+ if (!resposta.ok)
22
+ await recusa(resposta);
23
+ return (await resposta.json()).skill;
24
+ }
25
+ export async function criaSkill(raiz,
26
+ // Só o que a API aceita na criação: `atualizada_em` é do servidor, e aceitá-la no tipo
27
+ // prometeria um campo que o Zod descarta em silêncio.
28
+ dados) {
29
+ const { config, token } = await credencial(raiz);
30
+ const resposta = await pede(`${config.api}/api/v1/skills`, {
31
+ method: "POST",
32
+ headers: cabecalhos(token, true),
33
+ body: JSON.stringify({ tenant: config.tenant, projeto: config.projeto, ...dados }),
34
+ });
35
+ if (!resposta.ok)
36
+ await recusa(resposta);
37
+ return (await resposta.json());
38
+ }
39
+ // --- o ponteiro em disco (puro, para ser testado sem rede) ---
40
+ /**
41
+ * Escapa o que quebraria o YAML do frontmatter.
42
+ *
43
+ * A descricao vem de texto livre editado na interface: `: ` no meio dela faz o parser ler
44
+ * uma chave nova, e uma quebra de linha encerra o valor. Aspas duplas com escape resolvem
45
+ * os dois — e um frontmatter quebrado nao da erro visivel, a skill simplesmente some da
46
+ * lista.
47
+ */
48
+ function comoEscalarYaml(texto) {
49
+ return `"${texto.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ")}"`;
50
+ }
51
+ /**
52
+ * O SKILL.md que vai para o disco: frontmatter de verdade, corpo que aponta para o MCP.
53
+ *
54
+ * `allowed-tools` sempre inclui a ferramenta que busca o procedimento — sem ela o ponteiro
55
+ * pediria permissao para ler a propria skill, e um "permitir?" antes de cada invocacao
56
+ * ensina a desligar o mecanismo.
57
+ */
58
+ export function ponteiroDaSkill(skill) {
59
+ const frontmatter = [
60
+ "---",
61
+ `name: ${skill.slug}`,
62
+ `description: ${comoEscalarYaml(skill.descricao)}`,
63
+ ];
64
+ if (skill.so_por_comando)
65
+ frontmatter.push("disable-model-invocation: true");
66
+ if (skill.dica_de_argumento) {
67
+ frontmatter.push(`argument-hint: ${comoEscalarYaml(skill.dica_de_argumento)}`);
68
+ }
69
+ const ferramentas = ["mcp__dd-harness__ler_skill", ...skill.ferramentas];
70
+ frontmatter.push(`allowed-tools: ${ferramentas.join(", ")}`);
71
+ if (skill.caminhos.length > 0) {
72
+ frontmatter.push(`paths: ${skill.caminhos.join(", ")}`);
73
+ }
74
+ frontmatter.push("---");
75
+ return `${frontmatter.join("\n")}
76
+
77
+ # ${skill.slug}
78
+
79
+ O procedimento desta skill vive no dd-harness, não neste arquivo.
80
+
81
+ **Chame a ferramenta MCP \`ler_skill\` com \`slug: "${skill.slug}"\` e siga o que ela
82
+ devolver.** É a primeira ação — não tente executar a skill a partir deste arquivo, que é
83
+ só o ponteiro.
84
+
85
+ Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado: avise o
86
+ usuário em vez de improvisar um procedimento.
87
+ `;
88
+ }
89
+ /**
90
+ * Escreve os ponteiros de todas as skills do projeto.
91
+ *
92
+ * NAO apaga diretorio que sobrou: uma skill apagada no servico deixa o ponteiro orfao, e
93
+ * varrer `.claude/skills/` inteiro levaria junto as skills que a pessoa escreveu a mao —
94
+ * que sao legitimas e nao passam por aqui. O DELETE da API avisa o que apagar; a decisao
95
+ * de apagar arquivo alheio nao e nossa.
96
+ */
97
+ export async function escrevePonteirosDeSkills(raiz, skills) {
98
+ let escritos = 0;
99
+ for (const skill of skills) {
100
+ const dir = join(raiz, ".claude", "skills", skill.slug);
101
+ const caminho = join(dir, "SKILL.md");
102
+ const novo = ponteiroDaSkill(skill);
103
+ // Só escreve quando muda: reescrever igual sujaria o `git status` a cada `start`.
104
+ const atual = await readFile(caminho, "utf8").catch(() => null);
105
+ if (atual === novo)
106
+ continue;
107
+ await mkdir(dir, { recursive: true });
108
+ await writeFile(caminho, novo, "utf8");
109
+ escritos++;
110
+ }
111
+ return { escritos };
112
+ }
113
+ /**
114
+ * Semeia as skills iniciais — so num projeto que ainda nao tem nenhuma.
115
+ *
116
+ * A checagem e "nenhuma skill", e nao "esta skill especifica": quem apagou
117
+ * `como-desenvolver` tomou uma decisao, e recria-la a cada `start` a desfaria em silencio.
118
+ * Projeto com qualquer skill propria ja passou do ponto de ser semeado.
119
+ *
120
+ * Devolve o que criou. Nunca lanca por falha de rede: semear e conveniencia de projeto
121
+ * novo, e um `start` nao pode falhar porque o mimo nao pode ser entregue.
122
+ */
123
+ export async function semeiaSkills(raiz, iniciais) {
124
+ try {
125
+ const existentes = await leSkills(raiz);
126
+ if (existentes.length > 0)
127
+ return { criadas: [] };
128
+ const criadas = [];
129
+ for (const nova of iniciais) {
130
+ try {
131
+ await criaSkill(raiz, nova);
132
+ criadas.push(nova.slug);
133
+ }
134
+ catch {
135
+ // Uma skill que nao entrou nao impede as outras — nem o `start`.
136
+ }
137
+ }
138
+ return { criadas };
139
+ }
140
+ catch {
141
+ return { criadas: [] };
142
+ }
143
+ }
@@ -0,0 +1,15 @@
1
+ import type { NovaSkillInicial } from "./skill.js";
2
+ /**
3
+ * As skills que todo projeto novo nasce tendo.
4
+ *
5
+ * Mesma ideia da politica e do briefing: projeto recem-criado ja vem com o minimo para
6
+ * trabalhar, em vez de comecar vazio esperando que alguem lembre de escrever. A diferenca
7
+ * e que skill e EDITAVEL na interface — o que entra aqui e ponto de partida do projeto,
8
+ * nao regra do produto, e ajusta-la e o uso esperado.
9
+ *
10
+ * Semeadas so quando o projeto NAO TEM skill alguma. Um projeto que ja tem as suas nao
11
+ * recebe nada: se alguem apagou `como-desenvolver`, foi decisao — e recria-la a cada
12
+ * `start` desfaria a decisao em silencio, toda vez.
13
+ */
14
+ export type SkillInicial = NovaSkillInicial;
15
+ export declare const SKILLS_INICIAIS: SkillInicial[];
@@ -0,0 +1,72 @@
1
+ export const SKILLS_INICIAIS = [
2
+ {
3
+ slug: "como-desenvolver",
4
+ descricao: `Como desenvolver neste projeto — simplicidade, modularização, mudanças cirúrgicas e execução orientada a objetivos. Invoque ao escrever ou editar código para garantir qualidade. Não cobre segurança nem o protocolo de briefing (isso é sempre obrigatório, vive no CLAUDE.md).`,
5
+ conteudo: `# Como Desenvolver
6
+
7
+ Aplique ao escrever ou editar código.
8
+
9
+ ## 1. Simplicidade Primeiro
10
+
11
+ **Mínimo de código que resolve o problema. Nada especulativo.**
12
+
13
+ - Sem funcionalidades além do que foi pedido.
14
+ - Sem abstrações para código de uso único.
15
+ - Sem "flexibilidade" ou "configurabilidade" que ninguém pediu.
16
+ - Sem tratamento de erro para cenários impossíveis.
17
+ - Se escreveu 200 linhas e dava em 50, reescreva.
18
+
19
+ O teste: *"Um engenheiro sênior diria que isso está complicado demais?"* Se sim, simplifique.
20
+
21
+ > Simplicidade e modularização (Seção 2) não brigam. Você **divide o que já existe e cresce** — não cria camadas para um futuro hipotético. Extrair um módulo de algo que repete é simplificar; criar um módulo "por via das dúvidas" é a complexidade especulativa que esta seção proíbe.
22
+
23
+ ## 2. Modularização e Componentização
24
+
25
+ **Prefira peças pequenas e com responsabilidade única a um monólito grande.** Vale para qualquer código — front-end, back-end, scripts.
26
+
27
+ Um arquivo ou função que faz coisa demais é difícil de ler, testar, reusar e mudar sem quebrar o resto. Quando algo cresce, divida:
28
+
29
+ - **Uma responsabilidade por unidade.** Um arquivo, uma função, um componente deve ter um motivo só para mudar. Se você descreve o que ele faz usando "e" várias vezes, ele faz coisa demais.
30
+ - **Separe as camadas.** Não misture lógica de negócio, acesso a dados e apresentação no mesmo lugar. Cada uma muda por razões diferentes.
31
+ - **Extraia o que repete ou o que cresce** — só depois que existe de fato (ver Seção 1). A regra prática: na **segunda** vez que o mesmo trecho aparece, considere extrair; na terceira, extraia.
32
+ - **No front-end:** quebre telas/páginas grandes em componentes menores e nomeados. Um componente que rola por centenas de linhas quase sempre é vários componentes disfarçados.
33
+
34
+ O teste: *"Eu preciso rolar muito para entender esta unidade, ou guardar várias coisas na cabeça ao mesmo tempo?"* Se sim, divida.
35
+
36
+ **Mas não fragmente à toa.** Dividir em peças minúsculas demais cria o problema oposto — saltar entre dez arquivos para seguir uma linha de raciocínio. Divida quando a unidade carrega mais de uma responsabilidade, não para perseguir uma contagem de linhas.
37
+
38
+ ## 3. Mudanças Cirúrgicas
39
+
40
+ **Toque apenas no necessário. Limpe apenas sua própria bagunça.**
41
+
42
+ Ao editar código existente:
43
+ - Não "melhore" código adjacente, comentários ou formatação que não fazem parte da tarefa.
44
+ - Não refatore o que não está quebrado.
45
+ - Mantenha o estilo existente, mesmo que você faria diferente.
46
+ - Viu código morto não relacionado? **Mencione — não delete.**
47
+
48
+ Quando suas mudanças criam órfãos:
49
+ - Remova imports/variáveis/funções que **suas** mudanças tornaram inúteis.
50
+ - Não remova código morto pré-existente sem ser solicitado.
51
+
52
+ O teste: cada linha alterada deve rastrear diretamente à solicitação do usuário.
53
+
54
+ ## 4. Execução Orientada a Objetivos
55
+
56
+ **Defina critérios de sucesso. Itere até verificar.**
57
+
58
+ Transforme tarefas vagas em objetivos verificáveis:
59
+ - "Adicionar validação" → "Escreva testes para entradas inválidas, depois faça-os passar."
60
+ - "Corrigir o bug" → "Escreva um teste que o reproduza, depois faça-o passar."
61
+ - "Refatorar X" → "Garanta que os testes passem antes e depois."
62
+
63
+ Para tarefas com múltiplos passos, declare um plano breve:
64
+ \`\`\`
65
+ 1. [Passo] → verificar: [checagem]
66
+ 2. [Passo] → verificar: [checagem]
67
+ 3. [Passo] → verificar: [checagem]
68
+ \`\`\`
69
+
70
+ Critérios fortes deixam você iterar sozinho. Critérios fracos ("faça funcionar") forçam esclarecimentos constantes.`,
71
+ },
72
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-harness",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "description": "Cliente do dd-harness: politica no inicio da sessao, e memoria por busca — nada em disco. Sem dependencia: fetch, crypto e fs sao do Node.",
6
6
  "license": "UNLICENSED",