dd-harness 0.1.0 → 0.1.1

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/check.js CHANGED
@@ -72,7 +72,7 @@ export async function check(raiz, commit) {
72
72
  ? memoriasTocadas(brain, await caminhosDoCommit(raiz, commit))
73
73
  : [];
74
74
  if (medicoes.length === 0) {
75
- return { medidas: 0, ausentes: [], novas: 0, base: 0, jaAbertas: 0, tocadas };
75
+ return { medidas: 0, ausentes: [], novas: 0, base: 0, jaAbertas: 0, fechadas: 0, tocadas };
76
76
  }
77
77
  const envio = await fetch(`${config.api}/api/v1/deriva`, {
78
78
  method: "POST",
@@ -97,6 +97,7 @@ export async function check(raiz, commit) {
97
97
  novas: julgamento.novas,
98
98
  base: julgamento.base,
99
99
  jaAbertas: julgamento.ja_abertas,
100
+ fechadas: julgamento.fechadas ?? 0,
100
101
  tocadas,
101
102
  };
102
103
  }
package/dist/gravar.js ADDED
@@ -0,0 +1,111 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { leConfigDoRepo, leToken } from "./config.js";
3
+ const OBRIGATORIOS = ["name", "titulo", "description", "pasta"];
4
+ /** Frontmatter simples: `chave: valor` por linha. Sem lib de YAML — nao ha aninhamento. */
5
+ function leFrontmatter(texto) {
6
+ const linhas = texto.replace(/\r\n/g, "\n").split("\n");
7
+ if (linhas[0]?.trim() !== "---") {
8
+ throw new Error("o arquivo precisa começar com `---` e um frontmatter.");
9
+ }
10
+ const fim = linhas.indexOf("---", 1);
11
+ if (fim === -1)
12
+ throw new Error("frontmatter sem `---` de fechamento.");
13
+ const campos = new Map();
14
+ for (const linha of linhas.slice(1, fim)) {
15
+ const corte = linha.indexOf(":");
16
+ if (corte === -1)
17
+ continue;
18
+ campos.set(linha.slice(0, corte).trim(), linha.slice(corte + 1).trim());
19
+ }
20
+ return { campos, resto: linhas.slice(fim + 1).join("\n") };
21
+ }
22
+ /** `**Dano:** texto` — devolve o texto, sem o rotulo. */
23
+ function filtro(corpo, rotulo) {
24
+ const achado = corpo.match(new RegExp(`\\*\\*${rotulo}:\\*\\*\\s*([\\s\\S]*?)(?=\\n\\s*\\n\\*\\*|\\n## |$)`, "i"));
25
+ const valor = achado?.[1]?.trim() ?? "";
26
+ if (!valor)
27
+ throw new Error(`falta o filtro **${rotulo}:** no arquivo.`);
28
+ return valor;
29
+ }
30
+ /** Itens de lista da secao de ancoras, com ou sem crase em volta. */
31
+ function ancorasDe(texto) {
32
+ const secao = texto.split(/\n##\s+[ÂA]ncoras\s*\n/i)[1];
33
+ if (!secao)
34
+ return [];
35
+ return secao
36
+ .split("\n")
37
+ .map((l) => l.match(/^\s*[-*]\s+(.+?)\s*$/)?.[1])
38
+ .filter((v) => Boolean(v))
39
+ .map((v) => v.replace(/^`|`$/g, "").trim())
40
+ .filter(Boolean);
41
+ }
42
+ export function interpreta(texto) {
43
+ const { campos, resto } = leFrontmatter(texto);
44
+ for (const chave of OBRIGATORIOS) {
45
+ if (!campos.get(chave))
46
+ throw new Error(`frontmatter sem \`${chave}\`.`);
47
+ }
48
+ // O corpo vai ate a secao dos filtros; dali para baixo e metadado, nao conteudo.
49
+ const corte = resto.search(/\n##\s+Os tr[êe]s filtros\s*\n/i);
50
+ if (corte === -1)
51
+ throw new Error("falta a seção `## Os três filtros`.");
52
+ const corpo = resto
53
+ .slice(0, corte)
54
+ .replace(/<!--[\s\S]*?-->/g, "")
55
+ .trim();
56
+ if (!corpo)
57
+ throw new Error("o corpo da memória está vazio.");
58
+ const cauda = resto.slice(corte);
59
+ const tambem = campos.get("projetos") ?? "";
60
+ return {
61
+ slug: campos.get("name"),
62
+ titulo: campos.get("titulo"),
63
+ resumo: campos.get("description"),
64
+ pasta: campos.get("pasta"),
65
+ corpo,
66
+ dano: filtro(cauda, "Dano"),
67
+ invisibilidade: filtro(cauda, "Invisibilidade"),
68
+ externalidade: filtro(cauda, "Externalidade"),
69
+ ancoras: ancorasDe(cauda),
70
+ tambemEm: tambem
71
+ .split(",")
72
+ .map((s) => s.trim())
73
+ .filter(Boolean),
74
+ };
75
+ }
76
+ export async function grava(raiz, caminho) {
77
+ const config = await leConfigDoRepo(raiz);
78
+ const token = await leToken(config.api);
79
+ if (!token) {
80
+ throw new Error(`sem credencial para ${config.api}. Rode \`dd-harness login --token <token>\`.`);
81
+ }
82
+ const memoria = interpreta(await readFile(caminho, "utf8"));
83
+ const resposta = await fetch(`${config.api}/api/v1/memorias`, {
84
+ method: "POST",
85
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
86
+ body: JSON.stringify({
87
+ tenant: config.tenant,
88
+ projeto: config.projeto,
89
+ pasta: memoria.pasta,
90
+ slug: memoria.slug,
91
+ titulo: memoria.titulo,
92
+ resumo: memoria.resumo,
93
+ corpo: memoria.corpo,
94
+ dano: memoria.dano,
95
+ invisibilidade: memoria.invisibilidade,
96
+ externalidade: memoria.externalidade,
97
+ ancoras: memoria.ancoras,
98
+ tambem_em: memoria.tambemEm,
99
+ }),
100
+ });
101
+ if (!resposta.ok) {
102
+ const { erro } = (await resposta.json().catch(() => ({})));
103
+ throw new Error(erro ?? `a API respondeu ${resposta.status}.`);
104
+ }
105
+ const { endereco } = (await resposta.json());
106
+ return {
107
+ endereco,
108
+ ancoras: memoria.ancoras.length,
109
+ projetos: 1 + memoria.tambemEm.length,
110
+ };
111
+ }
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { check, status } from "./check.js";
3
3
  import { leConfigDoRepo, guardaToken } from "./config.js";
4
+ import { grava } from "./gravar.js";
4
5
  import { init } from "./init.js";
5
6
  import { LINHA_DE_IMPORT } from "./materializa.js";
6
7
  import { sync } from "./sync.js";
@@ -17,6 +18,7 @@ const AJUDA = `dd-harness — materializa política, briefing e Brain no reposit
17
18
  dd-harness init --tenant <t> --projeto <p> [--api <url>]
18
19
  prepara o repositório (config + CLAUDE.md)
19
20
  dd-harness login --token <token> guarda a credencial desta máquina
21
+ dd-harness gravar <arquivo.md> registra uma memória a partir de um markdown
20
22
  dd-harness sync escreve os artefatos em disco
21
23
  dd-harness check [--commit <sha>] mede as âncoras e reporta a deriva
22
24
  dd-harness status só lê: o que espera julgamento
@@ -142,7 +144,12 @@ async function comandoCheck(argv) {
142
144
  if (r.jaAbertas > 0) {
143
145
  console.log(`${r.jaAbertas} já estava(m) aberta(s) — nada novo, só o carimbo.`);
144
146
  }
145
- if (!r.ausentes.length && r.novas === 0 && r.jaAbertas === 0) {
147
+ // Reverter uma mudanca fecha a observacao que ela abriu. Anunciar isso importa: sem a
148
+ // linha, a fila encolhe sem explicacao e quem olha o `status` acha que perdeu algo.
149
+ if (r.fechadas > 0) {
150
+ console.log(`${r.fechadas} deriva(s) fechada(s): o alvo voltou a bater com a linha de base.`);
151
+ }
152
+ if (!r.ausentes.length && r.novas === 0 && r.jaAbertas === 0 && r.fechadas === 0) {
146
153
  console.log("nenhuma deriva: o mundo ainda bate com o que as memórias dizem.");
147
154
  }
148
155
  // O cruzamento com o diff e outra coisa que deriva: e "voce acabou de mexer no que
@@ -177,6 +184,22 @@ async function comandoStatus() {
177
184
  console.log(` ${m.pasta}/${m.memoria} — ${m.titulo}`);
178
185
  }
179
186
  }
187
+ /**
188
+ * O agente escreve o arquivo — que e o que ele ja fazia no modelo file-based — e este
189
+ * comando o transforma em requisicao. O arquivo nao fica no repositorio: quem
190
+ * materializa e o `sync`, a partir do servico, para nao existir copia escrita a mao ao
191
+ * lado da copia gerada.
192
+ */
193
+ async function comandoGravar(argv) {
194
+ const caminho = argv[0];
195
+ if (!caminho || caminho.startsWith("-")) {
196
+ throw new Error("uso: dd-harness gravar <arquivo.md>");
197
+ }
198
+ const r = await grava(process.cwd(), caminho);
199
+ console.log(`gravado ${r.endereco}`);
200
+ console.log(` ${r.ancoras} âncora(s), valendo para ${r.projetos} projeto(s).` +
201
+ " Rode `dd-harness sync` para materializar.");
202
+ }
180
203
  async function principal() {
181
204
  const [comando, ...resto] = process.argv.slice(2);
182
205
  switch (comando) {
@@ -184,6 +207,8 @@ async function principal() {
184
207
  return comandoInit(resto);
185
208
  case "login":
186
209
  return comandoLogin(resto);
210
+ case "gravar":
211
+ return comandoGravar(resto);
187
212
  case "sync":
188
213
  return comandoSync();
189
214
  case "check":
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "dd-harness",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Materializa politica, briefing e Brain do dd-harness no repositorio. Sem dependencia: fetch, crypto e fs sao do Node.",
6
6
  "license": "UNLICENSED",
7
7
  "author": "Diego Dias",
8
- "keywords": ["claude-code", "ai-agents", "memory", "brain", "cli"],
8
+ "keywords": [
9
+ "claude-code",
10
+ "ai-agents",
11
+ "memory",
12
+ "brain",
13
+ "cli"
14
+ ],
9
15
  "homepage": "https://dd-harness.vercel.app",
10
16
  "repository": {
11
17
  "type": "git",
@@ -15,7 +21,9 @@
15
21
  "bin": {
16
22
  "dd-harness": "dist/index.js"
17
23
  },
18
- "files": ["dist"],
24
+ "files": [
25
+ "dist"
26
+ ],
19
27
  "engines": {
20
28
  "node": ">=20"
21
29
  },