dd-harness 0.38.2 → 0.40.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/skill.d.ts CHANGED
@@ -31,6 +31,11 @@ export declare function leSkill(raiz: string, slug: string): Promise<SkillComple
31
31
  export declare function criaSkill(raiz: string, dados: NovaSkillInicial & Partial<Pick<Skill, "so_por_comando" | "ferramentas" | "caminhos" | "dica_de_argumento">>): Promise<{
32
32
  skill: Skill;
33
33
  }>;
34
+ /** Só o que muda: o que não vier fica como está no serviço. */
35
+ export type EdicaoDeSkill = Partial<Pick<SkillCompleta, "descricao" | "conteudo" | "so_por_comando" | "ferramentas" | "caminhos" | "dica_de_argumento">>;
36
+ export declare function editaSkill(raiz: string, slug: string, dados: EdicaoDeSkill): Promise<{
37
+ skill: Skill;
38
+ }>;
34
39
  /**
35
40
  * O SKILL.md que vai para o disco: frontmatter de verdade, corpo que aponta para o MCP.
36
41
  *
package/dist/skill.js CHANGED
@@ -38,6 +38,17 @@ dados) {
38
38
  await recusa(resposta);
39
39
  return (await jsonDaEscrita(resposta));
40
40
  }
41
+ export async function editaSkill(raiz, slug, dados) {
42
+ const { config, token } = await credencial(raiz);
43
+ const resposta = await pede(`${config.api}/api/v1/skills/${encodeURIComponent(slug)}`, {
44
+ method: "PATCH",
45
+ headers: cabecalhos(token, true),
46
+ body: JSON.stringify({ tenant: config.tenant, projeto: config.projeto, ...dados }),
47
+ });
48
+ if (!resposta.ok)
49
+ await recusa(resposta);
50
+ return (await jsonDaEscrita(resposta));
51
+ }
41
52
  // --- o ponteiro em disco (puro, para ser testado sem rede) ---
42
53
  /**
43
54
  * Escapa o que quebraria o YAML do frontmatter.
@@ -50,6 +61,22 @@ dados) {
50
61
  function comoEscalarYaml(texto) {
51
62
  return `"${texto.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ")}"`;
52
63
  }
64
+ /**
65
+ * Um item de `allowed-tools` ou `paths`, limpo do que quebraria o frontmatter.
66
+ *
67
+ * Estas duas chaves sao listas separadas por virgula, e o host as le assim — entao nao da
68
+ * para citar o valor inteiro como se faz com `description`: as aspas entrariam no nome da
69
+ * ferramenta. O que sobra e neutralizar os tres caracteres que mudam a ESTRUTURA do YAML:
70
+ * quebra de linha (encerra o valor e a proxima linha vira chave nova), `:` seguido de
71
+ * espaco (o parser le chave) e a virgula (inventa um item que ninguem pediu).
72
+ *
73
+ * Importa porque estes itens vem da API, que aceita qualquer string nao vazia: um item com
74
+ * `\n` faz a skill sumir da lista ou ganhar uma chave que o host obedece — e frontmatter
75
+ * quebrado nao da erro visivel, exatamente como na `description`.
76
+ */
77
+ function comoItemDeLista(texto) {
78
+ return texto.replace(/[\r\n]+/g, " ").replace(/:\s/g, " ").replace(/,/g, " ").trim();
79
+ }
53
80
  /**
54
81
  * O SKILL.md que vai para o disco: frontmatter de verdade, corpo que aponta para o MCP.
55
82
  *
@@ -68,11 +95,18 @@ export function ponteiroDaSkill(skill, formato = "claude") {
68
95
  if (skill.dica_de_argumento) {
69
96
  frontmatter.push(`argument-hint: ${comoEscalarYaml(skill.dica_de_argumento)}`);
70
97
  }
71
- const ferramentas = ["mcp__dd-harness__ler_skill", ...skill.ferramentas];
98
+ const ferramentas = [
99
+ "mcp__dd-harness__ler_skill",
100
+ ...skill.ferramentas.map(comoItemDeLista).filter(Boolean),
101
+ ];
72
102
  if (formato === "claude")
73
103
  frontmatter.push(`allowed-tools: ${ferramentas.join(", ")}`);
74
- if (formato === "claude" && skill.caminhos.length > 0) {
75
- frontmatter.push(`paths: ${skill.caminhos.join(", ")}`);
104
+ if (formato === "claude") {
105
+ const caminhos = skill.caminhos.map(comoItemDeLista).filter(Boolean);
106
+ // Depois da limpeza a lista pode ficar vazia (item que era so `\n`). `paths: ` sem
107
+ // valor nao e o mesmo que ausente: seria uma skill que nunca casa arquivo nenhum.
108
+ if (caminhos.length > 0)
109
+ frontmatter.push(`paths: ${caminhos.join(", ")}`);
76
110
  }
77
111
  frontmatter.push("---");
78
112
  return `${frontmatter.join("\n")}
@@ -295,7 +295,7 @@ despromovida** — isso a deixaria invisível. Vincule-a a um projeto antes, ou
295
295
  {
296
296
  slug: "escrever-skill",
297
297
  descricao: `Escreve uma skill que o modelo de fato invoca e segue, em vez de uma que existe e nunca dispara. Invoque ao criar ou revisar uma skill deste projeto — ou quando notar que um procedimento já foi explicado três vezes e devia estar escrito.`,
298
- ferramentas: ["mcp__dd-harness__listar_skills", "Read"],
298
+ ferramentas: ["mcp__dd-harness__listar_skills", "mcp__dd-harness__escrever_skill", "Read"],
299
299
  conteudo: `# Escrever uma skill que funciona
300
300
 
301
301
  A falha mais comum de skill não é estar errada — é **nunca disparar**. Ela existe, o
@@ -376,9 +376,15 @@ skill** — a ferramenta já ensina isso, e melhor.
376
376
  **Releia a descrição sozinha**, sem o corpo. Se você não souber dizer em que momento
377
377
  invocá-la, o modelo também não saberá.
378
378
 
379
- Skill deste projeto vive no serviço: o repositório guarda só o ponteiro, e o
380
- \`dd-harness start\` o reescreve. Se você mudou a descrição, rode-o — o frontmatter em
381
- disco não muda sozinho.`,
379
+ ## Gravar
380
+
381
+ Skill deste projeto vive no serviço: o repositório guarda só o ponteiro. **Mostre a
382
+ descrição e o corpo ao humano e grave só depois do OK**, com \`escrever_skill\` —
383
+ \`criar: true\` para uma nova; sem ele, edita a existente. Na edição, mande só o que muda;
384
+ \`conteudo\` substitui o procedimento inteiro, então leia antes com \`ler_skill\`.
385
+
386
+ A ferramenta reescreve o ponteiro em disco. O procedimento novo vale na hora; descrição,
387
+ ferramentas e caminhos são frontmatter, e o host só os relê na próxima sessão.`,
382
388
  },
383
389
  {
384
390
  slug: "propor-ferramentas",
package/dist/sync.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Busca o Brain e escreve em disco. Uma direcao so.
3
+ *
4
+ * O manifesto guarda o hash de cada arquivo que este comando escreveu. Antes de
5
+ * sobrescrever, ele confere: hash igual ao guardado significa "isto e meu, posso
6
+ * reescrever"; hash diferente significa que alguem editou a mao, e ai o comando **para**.
7
+ * Deriva silenciosa entre repo e servico e exatamente o que este projeto existe para
8
+ * evitar — melhor falhar alto do que engolir a edicao de alguem.
9
+ */
10
+ /**
11
+ * Estado do ponteiro na raiz.
12
+ *
13
+ * `CLAUDE.md` nao e gerado — e do repositorio, e so precisa importar a politica. Mas o
14
+ * import falha em **silencio**: sem o arquivo alvo, ou sem a linha, a sessao abre e
15
+ * nada avisa que a politica nao veio junto. Foi medido, nao suposto. Como a sessao sem
16
+ * protocolo e a falha que o molde mais combate, o `sync` confere isso toda vez — mesmo
17
+ * quando o servico nao mudou, porque quem apaga a linha e quem mexe no repositorio.
18
+ */
19
+ export type Ponteiro = "ok" | "sem-claude-md" | "sem-a-linha" | "sem-politica"
20
+ /**
21
+ * A linha de import existe, mas nao ha politica no servico para ela apontar. O `init`
22
+ * escreve a linha antes de existir politica, entao o repositorio novo cai aqui: import
23
+ * pendurado desde o primeiro dia, e o mesmo silencio de sempre.
24
+ */
25
+ | "aponta-para-o-vazio";
26
+ type Resultado = {
27
+ ponteiro: Ponteiro;
28
+ } & ({
29
+ tipo: "sem-mudanca";
30
+ } | {
31
+ tipo: "sincronizado";
32
+ escritos: string[];
33
+ removidos: string[];
34
+ } | {
35
+ tipo: "editado-a-mao";
36
+ arquivos: string[];
37
+ });
38
+ /**
39
+ * Estado do que este comando escreveu, contra o hash guardado.
40
+ *
41
+ * Duas situacoes diferentes, e confundi-las custa caro: hash diferente e **edicao a
42
+ * mao**, e o comando tem que parar sem escrever; arquivo **ausente** nao e edicao — e
43
+ * trabalho a refazer, e o comando tem que reescrever.
44
+ */
45
+ export declare function confereDisco(raiz: string, arquivos: Record<string, string>): Promise<{
46
+ editados: string[];
47
+ faltando: string[];
48
+ }>;
49
+ /**
50
+ * O `CLAUDE.md` da raiz existe e importa a politica?
51
+ *
52
+ * Sem politica no servico E sem a linha, nao ha o que apontar nem o que avisar — avisar
53
+ * ai seria ruido que ensina a ignorar aviso. Mas com a linha escrita e a politica vazia o
54
+ * import esta pendurado de verdade, e esse e o silencio que a memoria
55
+ * `import-do-claude-md-falha-calado` manda quebrar.
56
+ */
57
+ export declare function confereOPonteiro(raiz: string, temPolitica: boolean): Promise<Ponteiro>;
58
+ export declare function sync(raiz: string): Promise<Resultado>;
59
+ export {};
package/dist/sync.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { pede } from "./api.js";
2
3
  import { dirname, join } from "node:path";
3
4
  import { gravaManifesto, hashDe, leConfigDoRepo, leManifesto, leToken, } from "./config.js";
4
5
  import { LINHA_DE_IMPORT, materializa } from "./materializa.js";
@@ -31,7 +32,7 @@ export async function confereDisco(raiz, arquivos) {
31
32
  }
32
33
  async function buscaBrain(config, token, etag) {
33
34
  const url = `${config.api}/api/v1/artefatos?tenant=${encodeURIComponent(config.tenant)}&projeto=${encodeURIComponent(config.projeto)}`;
34
- const resposta = await fetch(url, {
35
+ const resposta = await pede(url, {
35
36
  headers: {
36
37
  Authorization: `Bearer ${token}`,
37
38
  ...(etag ? { "If-None-Match": etag } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-harness",
3
- "version": "0.38.2",
3
+ "version": "0.40.0",
4
4
  "type": "module",
5
5
  "description": "Política, memória e integrações de Claude Code, Codex e Antigravity. CLI sem dependências de runtime.",
6
6
  "license": "UNLICENSED",