dd-harness 0.38.2 → 0.39.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/README.md CHANGED
@@ -1,103 +1,103 @@
1
- # dd-harness
2
-
3
- Política e briefing carregados do serviço; memória consultada por MCP ou CLI.
4
- A fonte dos artefatos continua no serviço. O cliente mantém configuração, ponteiros
5
- para skills e estado descartável fora do repositório.
6
-
7
- Requer Node >=20.3.0. CLI sem dependências de runtime.
8
-
9
- ## Instalação e configuração
10
-
11
- ```sh
12
- npm install -g dd-harness@latest
13
- npx -y dd-harness-mcp@latest --help
14
- dd-harness start --host todos
15
- ```
16
-
17
- Para um projeto já criado no serviço:
18
-
19
- ```sh
20
- dd-harness init --tenant meu-espaco --projeto meu-projeto
21
- dd-harness login --token <token>
22
- dd-harness integrar --host claude,codex,antigravity
23
- dd-harness diagnostico --mcp
24
- ```
25
-
26
- `start` usa Claude por padrão. `--host` aceita `claude`, `codex`,
27
- `antigravity`, uma lista separada por vírgulas ou `todos`.
28
- `integrar` instala nos três por padrão e preserva configurações alheias.
29
- O token nasce em /tokens e fica em ~/.dd-harness/credentials.json, por origem de API.
30
- Para serviço local, use `init --api http://localhost:3000` antes do login.
31
-
32
- ## Hosts
33
-
34
- | Host | MCP | Boot e guarda | Skills |
35
- |---|---|---|---|
36
- | Claude Code | .mcp.json | .claude/settings.json | .claude/skills |
37
- | Codex | .codex/config.toml | .codex/hooks.json | .agents/skills |
38
- | Antigravity | .agents/mcp_config.json | .agents/hooks.json | .agents/skills |
39
-
40
- Codex exige revisão/confiança dos hooks em /hooks. Instalação não comprova ativação.
41
- Antigravity usa PreInvocation e mensagem efêmera: política e briefing são consultados
42
- antes de cada inferência. Sua guarda usa `ask` para respeitar concessões já existentes;
43
- ela pode acrescentar confirmações. Não amplia permissões automaticamente.
44
-
45
- Sem boot válido, a guarda recusa ferramentas cobertas, inclusive shell. Leitura conhecida
46
- e onboarding controlado continuam possíveis. Os hooks precisam estar ativos: não são
47
- sandbox nem controlam ferramentas que o aplicativo não encaminha a eles.
48
- Após boot válido, shell não tem análise de diff antecipada; avisos por âncora cobrem
49
- edições estruturadas, patches, remoções e renomeações.
50
-
51
- Configurações MCP novas levam DD_HARNESS_ROOT explícito. Ao copiar/mover um checkout,
52
- confira essa raiz com diagnostico. O MCP recusa divergência entre a raiz explícita e
53
- o projeto identificado no diretório de execução. Corrija a configuração e reinicie o host.
54
-
55
- ## Uso diário
56
-
57
- ```sh
58
- dd-harness politica
59
- dd-harness buscar "contrato de integração"
60
- dd-harness ler regras/contrato
61
- dd-harness gravar memoria.md
62
- dd-harness editar memoria.md
63
- dd-harness arquivar regras/contrato --motivo obsoleta
64
- dd-harness status
65
- dd-harness skills
66
- dd-harness roadmap
67
- dd-harness changelog
68
- dd-harness --help
69
- ```
70
-
71
- `check` mede e grava observações de deriva; `status` só consulta.
72
- O antigo comando `sync` não faz parte do CLI atual.
73
- `politica --hook` e `cinto` permanecem como compatibilidade legada;
74
- use `integrar` para instalar `sessao` e `guarda`.
75
-
76
- ## Estado e conflitos
77
-
78
- - Política/briefing não são materializados. Âncoras/resumos, sessão e manifestos ficam
79
- em ~/.dd-harness/repos, isolados por caminho/projeto/credencial quando aplicável.
80
- - DD_HARNESS_HOME permite isolamento explícito em testes. Nunca aponte testes ao estado pessoal.
81
- - Uma sessão validada expira em quatro horas. Boot, retomada e compactação revalidam.
82
- Alterações locais de política/briefing via MCP invalidam a sessão; reabra depois de editar.
83
- Alterações remotas feitas por outro cliente são percebidas no próximo boot/revalidação.
84
- - Curadoria invalida as âncoras do checkout atual. A próxima edição as consulta novamente.
85
- - Só ponteiros registrados e intactos são atualizados/removidos. Skills manuais, editadas
86
- ou redirecionadas por links são preservadas e aparecem como conflitos.
87
- - Skills marcadas só por comando não são instaladas para descoberta automática em
88
- .agents; use listar_skills/ler_skill após pedido explícito. Metadados próprios do
89
- Claude não são prometidos como portáveis.
90
- - Se há fila e worker local configurado, o boot solicita um lote. O log fica em
91
- ~/.dd-harness/worker.log; solicitar não significa que a indexação concluiu.
92
-
93
- ## Escritas e commits
94
-
95
- Só GET/HEAD têm retry automático (até três tentativas, timeout por tentativa).
96
- POST/PATCH/PUT/DELETE não são repetidos. Falha de conexão, 5xx ou resposta incompleta
97
- em escrita exige consultar o estado antes de tentar novamente.
98
-
99
- A política entregue exige `[IDENTIFICAÇÃO] - tipo: descrição` nos commits de agentes:
100
- `[CODEX] - feat: ...`, `[CLAUDE] - fix: ...`, `[GEMINI] - docs: ...`.
101
- Identifique quem realmente commitou, inclusive ao usar outro editor. A regra não altera
102
- Git author nem concede autorização de commit/push. É regra de trabalho do agente,
103
- não inferência automática de identidade nem hook que renomeia commits humanos.
1
+ # dd-harness
2
+
3
+ Política e briefing carregados do serviço; memória consultada por MCP ou CLI.
4
+ A fonte dos artefatos continua no serviço. O cliente mantém configuração, ponteiros
5
+ para skills e estado descartável fora do repositório.
6
+
7
+ Requer Node >=20.3.0. CLI sem dependências de runtime.
8
+
9
+ ## Instalação e configuração
10
+
11
+ ```sh
12
+ npm install -g dd-harness@latest
13
+ npx -y dd-harness-mcp@latest --help
14
+ dd-harness start --host todos
15
+ ```
16
+
17
+ Para um projeto já criado no serviço:
18
+
19
+ ```sh
20
+ dd-harness init --tenant meu-espaco --projeto meu-projeto
21
+ dd-harness login --token <token>
22
+ dd-harness integrar --host claude,codex,antigravity
23
+ dd-harness diagnostico --mcp
24
+ ```
25
+
26
+ `start` usa Claude por padrão. `--host` aceita `claude`, `codex`,
27
+ `antigravity`, uma lista separada por vírgulas ou `todos`.
28
+ `integrar` instala nos três por padrão e preserva configurações alheias.
29
+ O token nasce em /tokens e fica em ~/.dd-harness/credentials.json, por origem de API.
30
+ Para serviço local, use `init --api http://localhost:3000` antes do login.
31
+
32
+ ## Hosts
33
+
34
+ | Host | MCP | Boot e guarda | Skills |
35
+ |---|---|---|---|
36
+ | Claude Code | .mcp.json | .claude/settings.json | .claude/skills |
37
+ | Codex | .codex/config.toml | .codex/hooks.json | .agents/skills |
38
+ | Antigravity | .agents/mcp_config.json | .agents/hooks.json | .agents/skills |
39
+
40
+ Codex exige revisão/confiança dos hooks em /hooks. Instalação não comprova ativação.
41
+ Antigravity usa PreInvocation e mensagem efêmera: política e briefing são consultados
42
+ antes de cada inferência. Sua guarda usa `ask` para respeitar concessões já existentes;
43
+ ela pode acrescentar confirmações. Não amplia permissões automaticamente.
44
+
45
+ Sem boot válido, a guarda recusa ferramentas cobertas, inclusive shell. Leitura conhecida
46
+ e onboarding controlado continuam possíveis. Os hooks precisam estar ativos: não são
47
+ sandbox nem controlam ferramentas que o aplicativo não encaminha a eles.
48
+ Após boot válido, shell não tem análise de diff antecipada; avisos por âncora cobrem
49
+ edições estruturadas, patches, remoções e renomeações.
50
+
51
+ Configurações MCP novas levam DD_HARNESS_ROOT explícito. Ao copiar/mover um checkout,
52
+ confira essa raiz com diagnostico. O MCP recusa divergência entre a raiz explícita e
53
+ o projeto identificado no diretório de execução. Corrija a configuração e reinicie o host.
54
+
55
+ ## Uso diário
56
+
57
+ ```sh
58
+ dd-harness politica
59
+ dd-harness buscar "contrato de integração"
60
+ dd-harness ler regras/contrato
61
+ dd-harness gravar memoria.md
62
+ dd-harness editar memoria.md
63
+ dd-harness arquivar regras/contrato --motivo obsoleta
64
+ dd-harness status
65
+ dd-harness skills
66
+ dd-harness roadmap
67
+ dd-harness changelog
68
+ dd-harness --help
69
+ ```
70
+
71
+ `check` mede e grava observações de deriva; `status` só consulta.
72
+ O antigo comando `sync` não faz parte do CLI atual.
73
+ `politica --hook` e `cinto` permanecem como compatibilidade legada;
74
+ use `integrar` para instalar `sessao` e `guarda`.
75
+
76
+ ## Estado e conflitos
77
+
78
+ - Política/briefing não são materializados. Âncoras/resumos, sessão e manifestos ficam
79
+ em ~/.dd-harness/repos, isolados por caminho/projeto/credencial quando aplicável.
80
+ - DD_HARNESS_HOME permite isolamento explícito em testes. Nunca aponte testes ao estado pessoal.
81
+ - Uma sessão validada expira em quatro horas. Boot, retomada e compactação revalidam.
82
+ Alterações locais de política/briefing via MCP invalidam a sessão; reabra depois de editar.
83
+ Alterações remotas feitas por outro cliente são percebidas no próximo boot/revalidação.
84
+ - Curadoria invalida as âncoras do checkout atual. A próxima edição as consulta novamente.
85
+ - Só ponteiros registrados e intactos são atualizados/removidos. Skills manuais, editadas
86
+ ou redirecionadas por links são preservadas e aparecem como conflitos.
87
+ - Skills marcadas só por comando não são instaladas para descoberta automática em
88
+ .agents; use listar_skills/ler_skill após pedido explícito. Metadados próprios do
89
+ Claude não são prometidos como portáveis.
90
+ - Se há fila e worker local configurado, o boot solicita um lote. O log fica em
91
+ ~/.dd-harness/worker.log; solicitar não significa que a indexação concluiu.
92
+
93
+ ## Escritas e commits
94
+
95
+ Só GET/HEAD têm retry automático (até três tentativas, timeout por tentativa).
96
+ POST/PATCH/PUT/DELETE não são repetidos. Falha de conexão, 5xx ou resposta incompleta
97
+ em escrita exige consultar o estado antes de tentar novamente.
98
+
99
+ A política entregue exige `[IDENTIFICAÇÃO] - tipo: descrição` nos commits de agentes:
100
+ `[CODEX] - feat: ...`, `[CLAUDE] - fix: ...`, `[GEMINI] - docs: ...`.
101
+ Identifique quem realmente commitou, inclusive ao usar outro editor. A regra não altera
102
+ Git author nem concede autorização de commit/push. É regra de trabalho do agente,
103
+ não inferência automática de identidade nem hook que renomeia commits humanos.
@@ -6,24 +6,29 @@
6
6
  * tenha escrito nela que o CLI existe — e era justamente ela que estava fora de alcance.
7
7
  */
8
8
  export type TipoDeArtefato = "politica" | "briefing";
9
+ export declare function artefatoDoPayload(payload: Record<string, unknown>, tipo: TipoDeArtefato): {
10
+ conteudo: string;
11
+ existe: boolean;
12
+ atualizadaEm: string | null;
13
+ };
9
14
  /**
10
15
  * O `GET` devolve o payload inteiro (politica, briefing, pastas e memorias); aqui so o
11
16
  * artefato pedido interessa.
12
17
  *
13
- * `null` e `""` significam a mesma coisa para quem le — "ainda nao existe" —, e a diferenca
14
- * entre elas e detalhe de como a linha foi parar no banco. Quem chama recebe `existe`, que
15
- * e a pergunta real.
18
+ * Conteúdo vazio ainda pode ser uma linha existente. A versão é o que distingue esse caso
19
+ * de um artefato que nunca foi criado e define qual precondição a próxima escrita envia.
16
20
  */
17
21
  export declare function leArtefato(raiz: string, tipo: TipoDeArtefato): Promise<{
18
22
  conteudo: string;
19
23
  existe: boolean;
24
+ atualizadaEm: string | null;
20
25
  }>;
21
26
  /**
22
27
  * Substituicao total, nao append: quem quer acrescentar um paragrafo le, concatena e
23
28
  * reenvia. Conteudo vazio e valido e significa "apagar" — a linha fica no banco, guardando
24
29
  * quem mexeu por ultimo.
25
30
  */
26
- export declare function escreveArtefato(raiz: string, tipo: TipoDeArtefato, conteudo: string): Promise<{
31
+ export declare function escreveArtefato(raiz: string, tipo: TipoDeArtefato, conteudo: string, atualizadaEm: string | null): Promise<{
27
32
  artefato: string;
28
33
  criou: boolean;
29
34
  tamanho: number;
package/dist/artefato.js CHANGED
@@ -1,12 +1,17 @@
1
1
  import { jsonDaEscrita, cabecalhos, credencial, pede, recusa } from "./api.js";
2
2
  import { invalidaContexto } from "./estado-local.js";
3
+ export function artefatoDoPayload(payload, tipo) {
4
+ const conteudo = typeof payload[tipo] === "string" ? payload[tipo] : "";
5
+ const versoes = payload.artefatos_atualizados_em;
6
+ const atualizadaEm = typeof versoes?.[tipo] === "string" ? versoes[tipo] : null;
7
+ return { conteudo, existe: atualizadaEm !== null || conteudo.length > 0, atualizadaEm };
8
+ }
3
9
  /**
4
10
  * O `GET` devolve o payload inteiro (politica, briefing, pastas e memorias); aqui so o
5
11
  * artefato pedido interessa.
6
12
  *
7
- * `null` e `""` significam a mesma coisa para quem le — "ainda nao existe" —, e a diferenca
8
- * entre elas e detalhe de como a linha foi parar no banco. Quem chama recebe `existe`, que
9
- * e a pergunta real.
13
+ * Conteúdo vazio ainda pode ser uma linha existente. A versão é o que distingue esse caso
14
+ * de um artefato que nunca foi criado e define qual precondição a próxima escrita envia.
10
15
  */
11
16
  export async function leArtefato(raiz, tipo) {
12
17
  const { config, token } = await credencial(raiz);
@@ -16,20 +21,23 @@ export async function leArtefato(raiz, tipo) {
16
21
  const resposta = await pede(url, { headers: cabecalhos(token) });
17
22
  if (!resposta.ok)
18
23
  await recusa(resposta);
19
- const payload = (await resposta.json());
20
- const conteudo = typeof payload[tipo] === "string" ? payload[tipo] : "";
21
- return { conteudo, existe: conteudo.length > 0 };
24
+ return artefatoDoPayload((await resposta.json()), tipo);
22
25
  }
23
26
  /**
24
27
  * Substituicao total, nao append: quem quer acrescentar um paragrafo le, concatena e
25
28
  * reenvia. Conteudo vazio e valido e significa "apagar" — a linha fica no banco, guardando
26
29
  * quem mexeu por ultimo.
27
30
  */
28
- export async function escreveArtefato(raiz, tipo, conteudo) {
31
+ export async function escreveArtefato(raiz, tipo, conteudo, atualizadaEm) {
29
32
  const { config, token } = await credencial(raiz);
30
33
  const resposta = await pede(`${config.api}/api/v1/artefatos`, {
31
34
  method: "PUT",
32
- headers: cabecalhos(token, true),
35
+ headers: {
36
+ ...cabecalhos(token, true),
37
+ ...(atualizadaEm
38
+ ? { "If-Match": atualizadaEm }
39
+ : { "If-None-Match": "*" }),
40
+ },
33
41
  body: JSON.stringify({
34
42
  tenant: config.tenant,
35
43
  projeto: config.projeto,
package/dist/brain.d.ts CHANGED
@@ -11,21 +11,30 @@ export type Ancora = {
11
11
  valor: string;
12
12
  sha: string | null;
13
13
  };
14
+ /**
15
+ * A memoria como o payload de sessao a entrega — o indice dela, nao o conteudo.
16
+ *
17
+ * Sem `corpo` e sem as tres justificativas de proposito: quem precisa do texto integral
18
+ * busca a memoria individual (`MemoriaDoServico`, em `curar.ts`), e e uma memoria por vez.
19
+ * Aqui vem TODAS as do projeto a cada abertura de sessao, entao cada campo custa
20
+ * multiplicado pelo acervo.
21
+ */
14
22
  export type Memoria = {
15
23
  pasta: string;
16
24
  slug: string;
17
25
  titulo: string;
18
26
  resumo: string;
19
- corpo: string;
27
+ /**
28
+ * Sempre `"ativa"` desde que o payload passou a filtrar: as arquivadas viram a contagem
29
+ * `arquivadas` do `Brain`. O campo fica porque o cache local guarda memorias gravadas na
30
+ * propria sessao, e `acrescentaAoCache` decide por ele.
31
+ */
20
32
  status: "ativa" | "historico";
21
33
  /**
22
34
  * `global` = vale para todo projeto do tenant, inclusive os que ainda nao existem.
23
35
  * Ausente em payload antigo, e ai a memoria e de projeto — que era o unico escopo.
24
36
  */
25
37
  escopo?: "projeto" | "global";
26
- dano: string;
27
- invisibilidade: string;
28
- externalidade: string;
29
38
  ancoras: Ancora[];
30
39
  revisar_ate: string | null;
31
40
  /** Observacoes de deriva esperando julgamento. Ausente em payload antigo. */
@@ -48,7 +57,14 @@ export type Brain = {
48
57
  slug: string;
49
58
  definicao: string;
50
59
  }[];
60
+ /** So as ativas. As arquivadas nao vem na lista — viram o numero `arquivadas`. */
51
61
  memorias: Memoria[];
62
+ /**
63
+ * Quantas memorias arquivadas o projeto tem. Ausente em payload antigo, e ai o `check`
64
+ * cai para o calculo velho (lista inteira menos ativas), que dava o mesmo resultado
65
+ * quando a lista ainda trazia as duas.
66
+ */
67
+ arquivadas?: number;
52
68
  /**
53
69
  * Memorias que excederam as tentativas de indexacao e nao serao mais tentadas. Ficam
54
70
  * sem embedding — somem da busca semantica — e so este numero denuncia.
package/dist/check.js CHANGED
@@ -2,6 +2,7 @@ import { pede } from "./api.js";
2
2
  import { leConfigDoRepo, leToken } from "./config.js";
3
3
  import { caminhosDoCommit, memoriasTocadas, sugereReancoragem, } from "./diff.js";
4
4
  import { mede } from "./medir.js";
5
+ import { corpoComCache, leCacheDoPayload } from "./payload-cache.js";
5
6
  /** Separado do IO para poder ser testado sem rede: dado um Brain, o que se mede. */
6
7
  export async function medeAsAncoras(raiz, brain) {
7
8
  const medicoes = [];
@@ -30,16 +31,27 @@ async function buscaOBrain(raiz) {
30
31
  throw new Error(`sem credencial para ${config.api}. Rode \`dd-harness login --token <token>\`.`);
31
32
  }
32
33
  const parametros = `tenant=${encodeURIComponent(config.tenant)}&projeto=${encodeURIComponent(config.projeto)}`;
34
+ // Mesmo ETag do hook de sessao: `check` e `status` leem o mesmo payload, e rodar os dois
35
+ // em sequencia (o que a abertura de sessao faz) nao precisa baixar o acervo duas vezes.
36
+ const cache = await leCacheDoPayload(raiz);
33
37
  const resposta = await pede(`${config.api}/api/v1/artefatos?${parametros}`, {
34
- headers: { Authorization: `Bearer ${token}` },
38
+ headers: {
39
+ Authorization: `Bearer ${token}`,
40
+ ...(cache ? { "If-None-Match": cache.etag } : {}),
41
+ },
35
42
  });
36
43
  if (resposta.status === 401) {
37
44
  throw new Error("token recusado. Rode `dd-harness login --token <token>`.");
38
45
  }
39
- if (!resposta.ok) {
46
+ // `304` nao e `ok`, mas e o caminho feliz: o corpo vem do cache logo abaixo.
47
+ if (!resposta.ok && resposta.status !== 304) {
40
48
  throw new Error(`a API respondeu ${resposta.status} ao buscar as âncoras.`);
41
49
  }
42
- return { config, token, brain: (await resposta.json()) };
50
+ const corpo = await corpoComCache(raiz, resposta, cache);
51
+ if (corpo === null) {
52
+ throw new Error("o cache local do payload sumiu; rode de novo.");
53
+ }
54
+ return { config, token, brain: JSON.parse(corpo) };
43
55
  }
44
56
  /**
45
57
  * Somente leitura, de proposito: e o que roda na ABERTURA da sessao. Medir e reportar
@@ -56,7 +68,9 @@ export async function status(raiz) {
56
68
  return {
57
69
  acervo: {
58
70
  ativas: ativas.length,
59
- arquivadas: brain.memorias.length - ativas.length,
71
+ // O servico manda o numero pronto, porque a lista so traz ativas. O `??` cobre
72
+ // servico antigo, cujo payload ainda misturava as duas — ali a subtracao valia.
73
+ arquivadas: brain.arquivadas ?? brain.memorias.length - ativas.length,
60
74
  porPasta: [...porPasta]
61
75
  .map(([pasta, quantas]) => ({ pasta, quantas }))
62
76
  .sort((a, b) => a.pasta.localeCompare(b.pasta)),
package/dist/curar.d.ts CHANGED
@@ -20,6 +20,11 @@ export type MemoriaDoServico = {
20
20
  invisibilidade: string;
21
21
  externalidade: string;
22
22
  revisar_ate: string | null;
23
+ /**
24
+ * O carimbo de versão que o PATCH exige como `If-Match`. O tipo continua opcional para
25
+ * reconhecer uma leitura de serviço antigo e recusá-la com uma mensagem útil.
26
+ */
27
+ atualizada_em?: string;
23
28
  ancoras: {
24
29
  tipo: string;
25
30
  valor: string;
@@ -73,7 +78,7 @@ export declare function promove(raiz: string, endereco: string, global: boolean)
73
78
  * a memoria dizia pode voltar a importar. Isto e para o que nunca deveria ter existido.
74
79
  *
75
80
  * Sem `confirmacao`, o servidor RECUSA e devolve o que a cascata levaria junto — e so
76
- * entao se repete a chamada com o nome do espaco. Duas etapas de proposito: DELETE nao
81
+ * entao se repete a chamada com a frase de confirmação devolvida. Duas etapas de proposito: DELETE nao
77
82
  * tem desfazer, e a cascata e invisivel de fora.
78
83
  */
79
84
  export declare function apaga(raiz: string, endereco: string, confirmacao?: string): Promise<{
package/dist/curar.js CHANGED
@@ -30,6 +30,7 @@ export function comoMarkdown(m) {
30
30
  `titulo: ${m.titulo}`,
31
31
  `description: ${m.resumo}`,
32
32
  `pasta: ${m.pasta}`,
33
+ ...(m.atualizada_em ? [`atualizada-em: ${m.atualizada_em}`] : []),
33
34
  ...(m.revisar_ate ? [`revisar-ate: ${m.revisar_ate.slice(0, 10)}`] : []),
34
35
  // So aparece quando ha vinculo — omitir e "so este projeto", igual a `gravar` sem
35
36
  // `projetos:`. Formato inline (`a, b`), que e o que `interpreta` ja sabe ler.
@@ -53,9 +54,12 @@ export async function edita(raiz, caminho) {
53
54
  const cru = await readFile(caminho, "utf8");
54
55
  const memoria = interpreta(cru);
55
56
  const endereco = `${memoria.pasta}/${memoria.slug}`;
57
+ if (!memoria.atualizadaEm) {
58
+ throw new Error("a edição precisa partir de `dd-harness ler`: falta `atualizada-em` no frontmatter.");
59
+ }
56
60
  const resposta = await pede(`${config.api}/api/v1/memorias/${endereco}`, {
57
61
  method: "PATCH",
58
- headers: cabecalhos(token, true),
62
+ headers: { ...cabecalhos(token, true), "If-Match": memoria.atualizadaEm },
59
63
  body: JSON.stringify({
60
64
  tenant: config.tenant,
61
65
  projeto: config.projeto,
@@ -121,7 +125,7 @@ export async function promove(raiz, endereco, global) {
121
125
  * a memoria dizia pode voltar a importar. Isto e para o que nunca deveria ter existido.
122
126
  *
123
127
  * Sem `confirmacao`, o servidor RECUSA e devolve o que a cascata levaria junto — e so
124
- * entao se repete a chamada com o nome do espaco. Duas etapas de proposito: DELETE nao
128
+ * entao se repete a chamada com a frase de confirmação devolvida. Duas etapas de proposito: DELETE nao
125
129
  * tem desfazer, e a cascata e invisivel de fora.
126
130
  */
127
131
  export async function apaga(raiz, endereco, confirmacao) {
@@ -140,28 +140,28 @@ export const escreveAgents = (raiz) => escrevePonteiro(raiz, "AGENTS.md");
140
140
  * em producao compara para saber se precisa de atualizacao.
141
141
  */
142
142
  export const VERSAO_DO_MOLDE = "v1.3.0";
143
- export const SUGESTAO_AGENTS = `## Protocolo do dd-harness
144
-
145
- Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
146
-
147
- **ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
148
- \`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
149
- de ler código, responder ou planejar.
150
-
151
- - Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
152
- avise o usuário e **não modifique nada** até ele resolver.
153
- - Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
154
-
155
- Leia também o briefing com a mesma ferramenta (tipo briefing). Os dois são obrigatórios.
156
-
157
- Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
158
- desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
159
- válido — não crie fase sem o usuário pedir.
160
-
161
- E \`listar_skills\`: são os procedimentos deste projeto. **Invoque a que couber
162
- ANTES de fazer o trabalho, não depois** — skill lida no fim vira revisão do que
163
- já saiu errado, e é tarde.
164
-
165
- Vale mesmo quando o pedido parece pequeno: "é só um ajuste" é exatamente a
166
- frase que antecede pular o procedimento. A política diz quais são obrigatórias
143
+ export const SUGESTAO_AGENTS = `## Protocolo do dd-harness
144
+
145
+ Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
146
+
147
+ **ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
148
+ \`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
149
+ de ler código, responder ou planejar.
150
+
151
+ - Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
152
+ avise o usuário e **não modifique nada** até ele resolver.
153
+ - Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
154
+
155
+ Leia também o briefing com a mesma ferramenta (tipo briefing). Os dois são obrigatórios.
156
+
157
+ Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
158
+ desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
159
+ válido — não crie fase sem o usuário pedir.
160
+
161
+ E \`listar_skills\`: são os procedimentos deste projeto. **Invoque a que couber
162
+ ANTES de fazer o trabalho, não depois** — skill lida no fim vira revisão do que
163
+ já saiu errado, e é tarde.
164
+
165
+ Vale mesmo quando o pedido parece pequeno: "é só um ajuste" é exatamente a
166
+ frase que antecede pular o procedimento. A política diz quais são obrigatórias
167
167
  e quando.`;
package/dist/gravar.d.ts CHANGED
@@ -24,6 +24,7 @@ export type MemoriaLida = {
24
24
  * vinculo transversal por omissao.
25
25
  */
26
26
  tambemEmInformado: boolean;
27
+ atualizadaEm?: string;
27
28
  };
28
29
  export declare function interpreta(texto: string): MemoriaLida;
29
30
  export declare function grava(raiz: string, caminho: string): Promise<{
package/dist/gravar.js CHANGED
@@ -163,6 +163,7 @@ export function interpreta(texto) {
163
163
  .map((s) => s.trim())
164
164
  .filter(Boolean),
165
165
  tambemEmInformado: campos.has("projetos"),
166
+ atualizadaEm: campos.get("atualizada-em") || undefined,
166
167
  };
167
168
  }
168
169
  export async function grava(raiz, caminho) {
package/dist/index.js CHANGED
@@ -80,7 +80,7 @@ const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
80
80
  projeto do espaço, inclusive os futuros
81
81
  dd-harness despromover <pasta>/<slug>
82
82
  traz de volta ao alcance dos vínculos
83
- dd-harness apagar <pasta>/<slug> [--confirmar <espaço>]
83
+ dd-harness apagar <pasta>/<slug> [--confirmar "<frase devolvida>"]
84
84
  apaga de vez, em cascata — sem desfazer.
85
85
  Para tirar de circulação guardando o
86
86
  conteúdo, use "arquivar"
@@ -691,21 +691,21 @@ async function comandoPromover(argv, global) {
691
691
  /**
692
692
  * `apagar` — o unico caminho que destroi.
693
693
  *
694
- * Duas etapas, e a segunda pede o nome do espaco digitado. Nao e cerimonia: a cascata leva
694
+ * Duas etapas, e a segunda pede a frase vinculada à versão atual. Nao e cerimonia: a cascata leva
695
695
  * ancoras e toda a deriva medida delas, e nao ha desfazer. Arquivar continua sendo o
696
696
  * caminho normal — isto e para o que nunca deveria ter existido.
697
697
  */
698
698
  async function comandoApagar(argv) {
699
699
  const endereco = argv[0];
700
700
  if (!endereco || endereco.startsWith("-") || !endereco.includes("/")) {
701
- throw new Error("uso: dd-harness apagar <pasta>/<slug> [--confirmar <espaço>]");
701
+ throw new Error('uso: dd-harness apagar <pasta>/<slug> [--confirmar "<frase devolvida>"]');
702
702
  }
703
703
  const r = await apaga(process.cwd(), endereco, argumento(argv, "confirmar"));
704
704
  if (!r.apagou) {
705
705
  console.log(r.detalhe);
706
706
  if (r.confirmacaoEsperada) {
707
707
  console.log(`
708
- dd-harness apagar ${endereco} --confirmar ${r.confirmacaoEsperada}`);
708
+ dd-harness apagar ${endereco} --confirmar "${r.confirmacaoEsperada}"`);
709
709
  }
710
710
  // Sem exit diferente de zero: recusar por falta de confirmacao nao e falha, e o
711
711
  // caminho normal da primeira chamada.