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/README.md +103 -103
- package/dist/artefato.d.ts +9 -4
- package/dist/artefato.js +16 -8
- package/dist/atualizar.d.ts +2 -0
- package/dist/atualizar.js +1 -1
- package/dist/brain.d.ts +20 -4
- package/dist/check.js +18 -4
- package/dist/curar.d.ts +6 -1
- package/dist/curar.js +6 -2
- package/dist/escreve-config.js +24 -24
- package/dist/gravar.d.ts +1 -0
- package/dist/gravar.js +1 -0
- package/dist/index.js +49 -12
- package/dist/init.js +45 -45
- package/dist/materializa.d.ts +70 -0
- package/dist/materializa.js +5 -0
- package/dist/moldes-historicos.js +70 -70
- package/dist/payload-cache.d.ts +31 -0
- package/dist/payload-cache.js +62 -0
- package/dist/politica.js +20 -3
- package/dist/reancorar.js +19 -1
- package/dist/skill.d.ts +5 -0
- package/dist/skill.js +37 -3
- package/dist/skills-iniciais.js +10 -4
- package/dist/sync.d.ts +59 -0
- package/dist/sync.js +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { diagnostico } from "./diagnostico.js";
|
|
3
|
-
import { aplicaAtualizacao, diagnosticaAtualizacao, versaoInstalada, versaoPublicada } from "./atualizar.js";
|
|
3
|
+
import { aplicaAtualizacao, diagnosticaAtualizacao, reescrevePonteiros, versaoInstalada, versaoPublicada } from "./atualizar.js";
|
|
4
4
|
import { instalaHosts, selecionaHosts } from "./hosts.js";
|
|
5
5
|
import { achaRaiz } from "./config.js";
|
|
6
6
|
import { abreSessao, guardaSessao, saidaDaGuarda, saidaDoBoot } from "./sessao.js";
|
|
@@ -27,7 +27,7 @@ import { pergunta, escolha, fechaPerguntas } from "./pergunta.js";
|
|
|
27
27
|
import { buscaPolitica } from "./politica.js";
|
|
28
28
|
import { avisoDeOrdem, blocoDeSessao, criaFase, editaFase, formataChangelog, formataRoadmap, leFases, } from "./roadmap.js";
|
|
29
29
|
import { decideDoHook, guardaCache } from "./cinto.js";
|
|
30
|
-
import { escrevePonteirosDeSkills, leSkill, leSkills, semeiaSkills } from "./skill.js";
|
|
30
|
+
import { criaSkill, editaSkill, escrevePonteirosDeSkills, leSkill, leSkills, semeiaSkills } from "./skill.js";
|
|
31
31
|
import { SKILLS_INICIAIS } from "./skills-iniciais.js";
|
|
32
32
|
import { busca } from "./buscar.js";
|
|
33
33
|
import { apaga, arquiva, edita, le, promove } from "./curar.js";
|
|
@@ -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 <
|
|
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"
|
|
@@ -101,6 +101,11 @@ const AJUDA = `dd-harness — a política e o Brain do projeto, no serviço
|
|
|
101
101
|
dd-harness skills as skills deste projeto e quando cada uma serve
|
|
102
102
|
dd-harness skill <nome> o procedimento de uma delas (o mesmo que o
|
|
103
103
|
agente recebe ao invocá-la)
|
|
104
|
+
dd-harness skill criar <nome> --descricao "<quando invocar>" [--conteudo <arquivo.md>]
|
|
105
|
+
[--so-por-comando true|false]
|
|
106
|
+
dd-harness skill editar <nome> [--descricao "<d>"] [--conteudo <arquivo.md>]
|
|
107
|
+
[--so-por-comando true|false]
|
|
108
|
+
grava no serviço e reescreve o ponteiro em disco
|
|
104
109
|
dd-harness roadmap as fases abertas: a atual inteira, as próximas
|
|
105
110
|
por título (opcional — projeto sem fase não tem)
|
|
106
111
|
dd-harness changelog [--versao <v>]
|
|
@@ -140,11 +145,9 @@ function argumento(argv, nome) {
|
|
|
140
145
|
const API_PADRAO = "https://dd-harness.vercel.app";
|
|
141
146
|
// --- roadmap e changelog ---
|
|
142
147
|
/**
|
|
143
|
-
* `skills` e `skill <slug>` —
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* texto longo, e um editor de verdade e melhor que um flag de linha de comando. O que o
|
|
147
|
-
* terminal precisa e ver o que existe e conferir um procedimento sem trocar de janela.
|
|
148
|
+
* `skills` e `skill <slug>` — leitura. Criar e editar sao `skill criar|editar`, com o
|
|
149
|
+
* procedimento vindo de um .md (`--conteudo`), como em `fase` e `gravar`: sem isso a skill
|
|
150
|
+
* `escrever-skill` redigia o texto e nao tinha como grava-lo fora da interface.
|
|
148
151
|
*/
|
|
149
152
|
async function comandoSkills() {
|
|
150
153
|
const skills = await leSkills(process.cwd());
|
|
@@ -160,10 +163,44 @@ async function comandoSkills() {
|
|
|
160
163
|
}
|
|
161
164
|
console.log(`${skills.length} skill(s). \`dd-harness skill <nome>\` mostra o procedimento.`);
|
|
162
165
|
}
|
|
166
|
+
/** `--so-por-comando true|false`: flag solta seria so "ligar", e desligar tambem e edicao. */
|
|
167
|
+
function soPorComandoDoArgv(argv) {
|
|
168
|
+
const v = argumento(argv, "so-por-comando");
|
|
169
|
+
if (v === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
if (v !== "true" && v !== "false")
|
|
172
|
+
throw new Error("--so-por-comando: use true ou false.");
|
|
173
|
+
return v === "true";
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* `skill criar` e `skill editar`. O ponteiro em disco e reescrito em seguida: descricao e
|
|
177
|
+
* "so por comando" moram no frontmatter dele, e o host so le o disco.
|
|
178
|
+
*/
|
|
179
|
+
async function comandoSkillEscrita(acao, argv) {
|
|
180
|
+
const [slug, ...opcoes] = argv;
|
|
181
|
+
if (!slug || slug.startsWith("--"))
|
|
182
|
+
throw new Error(`skill ${acao} exige o nome da skill.`);
|
|
183
|
+
const campos = {
|
|
184
|
+
descricao: argumento(opcoes, "descricao"),
|
|
185
|
+
conteudo: await conteudoDoArgv(opcoes),
|
|
186
|
+
so_por_comando: soPorComandoDoArgv(opcoes),
|
|
187
|
+
};
|
|
188
|
+
if (acao === "criar") {
|
|
189
|
+
if (!campos.descricao)
|
|
190
|
+
throw new Error('skill criar exige --descricao "<quando invocar>".');
|
|
191
|
+
await criaSkill(process.cwd(), { ...campos, slug, descricao: campos.descricao, conteudo: campos.conteudo ?? "" });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
await editaSkill(process.cwd(), slug, campos);
|
|
195
|
+
}
|
|
196
|
+
console.log(`Skill ${slug} ${acao === "criar" ? "criada" : "atualizada"}; ${await reescrevePonteiros(process.cwd())}.`);
|
|
197
|
+
}
|
|
163
198
|
async function comandoSkill(argv) {
|
|
164
199
|
const slug = argv[0];
|
|
200
|
+
if (slug === "criar" || slug === "editar")
|
|
201
|
+
return comandoSkillEscrita(slug, argv.slice(1));
|
|
165
202
|
if (!slug || slug.startsWith("-")) {
|
|
166
|
-
throw new Error("uso: dd-harness skill <nome>");
|
|
203
|
+
throw new Error("uso: dd-harness skill <nome> | dd-harness skill criar|editar <nome> [...]");
|
|
167
204
|
}
|
|
168
205
|
const s = await leSkill(process.cwd(), slug);
|
|
169
206
|
console.log(`# ${s.slug}
|
|
@@ -691,21 +728,21 @@ async function comandoPromover(argv, global) {
|
|
|
691
728
|
/**
|
|
692
729
|
* `apagar` — o unico caminho que destroi.
|
|
693
730
|
*
|
|
694
|
-
* Duas etapas, e a segunda pede
|
|
731
|
+
* Duas etapas, e a segunda pede a frase vinculada à versão atual. Nao e cerimonia: a cascata leva
|
|
695
732
|
* ancoras e toda a deriva medida delas, e nao ha desfazer. Arquivar continua sendo o
|
|
696
733
|
* caminho normal — isto e para o que nunca deveria ter existido.
|
|
697
734
|
*/
|
|
698
735
|
async function comandoApagar(argv) {
|
|
699
736
|
const endereco = argv[0];
|
|
700
737
|
if (!endereco || endereco.startsWith("-") || !endereco.includes("/")) {
|
|
701
|
-
throw new Error(
|
|
738
|
+
throw new Error('uso: dd-harness apagar <pasta>/<slug> [--confirmar "<frase devolvida>"]');
|
|
702
739
|
}
|
|
703
740
|
const r = await apaga(process.cwd(), endereco, argumento(argv, "confirmar"));
|
|
704
741
|
if (!r.apagou) {
|
|
705
742
|
console.log(r.detalhe);
|
|
706
743
|
if (r.confirmacaoEsperada) {
|
|
707
744
|
console.log(`
|
|
708
|
-
dd-harness apagar ${endereco} --confirmar ${r.confirmacaoEsperada}`);
|
|
745
|
+
dd-harness apagar ${endereco} --confirmar "${r.confirmacaoEsperada}"`);
|
|
709
746
|
}
|
|
710
747
|
// Sem exit diferente de zero: recusar por falta de confirmacao nao e falha, e o
|
|
711
748
|
// caminho normal da primeira chamada.
|
package/dist/init.js
CHANGED
|
@@ -8,13 +8,13 @@ import { CAMINHO_CONFIG } from "./config.js";
|
|
|
8
8
|
* onde falha silenciosa nasce — entrada errada nao da erro, a ferramenta so nao aparece.
|
|
9
9
|
* Quem cola sabe o que colou.
|
|
10
10
|
*/
|
|
11
|
-
export const SUGESTAO_MCP = `{
|
|
12
|
-
"mcpServers": {
|
|
13
|
-
"dd-harness": {
|
|
14
|
-
"command": "npx",
|
|
15
|
-
"args": ["-y", "dd-harness-mcp"]
|
|
16
|
-
}
|
|
17
|
-
}
|
|
11
|
+
export const SUGESTAO_MCP = `{
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"dd-harness": {
|
|
14
|
+
"command": "npx",
|
|
15
|
+
"args": ["-y", "dd-harness-mcp"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
18
|
}`;
|
|
19
19
|
/**
|
|
20
20
|
* Os dois hooks do dd-harness, pelas duas razoes que nenhuma instrucao em markdown
|
|
@@ -33,31 +33,31 @@ export const SUGESTAO_MCP = `{
|
|
|
33
33
|
*
|
|
34
34
|
* Sugerido e nao escrito, pelo mesmo motivo do `.mcp.json`.
|
|
35
35
|
*/
|
|
36
|
-
export const SUGESTAO_HOOK = `{
|
|
37
|
-
"hooks": {
|
|
38
|
-
"SessionStart": [
|
|
39
|
-
{
|
|
40
|
-
"hooks": [
|
|
41
|
-
{
|
|
42
|
-
"type": "command",
|
|
43
|
-
"command": "dd-harness politica --hook",
|
|
44
|
-
"statusMessage": "Carregando a política do dd-harness..."
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
|
-
"PreToolUse": [
|
|
50
|
-
{
|
|
51
|
-
"matcher": "Edit|Write|MultiEdit",
|
|
52
|
-
"hooks": [
|
|
53
|
-
{
|
|
54
|
-
"type": "command",
|
|
55
|
-
"command": "dd-harness cinto"
|
|
56
|
-
}
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
]
|
|
60
|
-
}
|
|
36
|
+
export const SUGESTAO_HOOK = `{
|
|
37
|
+
"hooks": {
|
|
38
|
+
"SessionStart": [
|
|
39
|
+
{
|
|
40
|
+
"hooks": [
|
|
41
|
+
{
|
|
42
|
+
"type": "command",
|
|
43
|
+
"command": "dd-harness politica --hook",
|
|
44
|
+
"statusMessage": "Carregando a política do dd-harness..."
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"PreToolUse": [
|
|
50
|
+
{
|
|
51
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"command": "dd-harness cinto"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
61
|
}`;
|
|
62
62
|
/**
|
|
63
63
|
* O que escrever num `AGENTS.md`, para agente que NAO e o Claude Code.
|
|
@@ -71,19 +71,19 @@ export const SUGESTAO_HOOK = `{
|
|
|
71
71
|
* simplesmente nunca perguntar pela politica. Dai esta linha, que e curta de proposito —
|
|
72
72
|
* ela manda buscar a regra, nao repete a regra.
|
|
73
73
|
*/
|
|
74
|
-
export const SUGESTAO_AGENTS = `# AGENTS.md
|
|
75
|
-
|
|
76
|
-
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
77
|
-
|
|
78
|
-
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
79
|
-
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
80
|
-
de ler código, responder ou planejar.
|
|
81
|
-
|
|
82
|
-
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
83
|
-
avise o usuário e **não modifique nada** até ele resolver.
|
|
84
|
-
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
85
|
-
|
|
86
|
-
O Claude Code carrega a política sozinho, por hook. Nas outras ferramentas, a
|
|
74
|
+
export const SUGESTAO_AGENTS = `# AGENTS.md
|
|
75
|
+
|
|
76
|
+
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
77
|
+
|
|
78
|
+
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
79
|
+
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
80
|
+
de ler código, responder ou planejar.
|
|
81
|
+
|
|
82
|
+
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
83
|
+
avise o usuário e **não modifique nada** até ele resolver.
|
|
84
|
+
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
85
|
+
|
|
86
|
+
O Claude Code carrega a política sozinho, por hook. Nas outras ferramentas, a
|
|
87
87
|
chamada acima é o que substitui esse hook.`;
|
|
88
88
|
async function declaraMcp(raiz) {
|
|
89
89
|
try {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Do payload do contrato para arquivos em disco.
|
|
3
|
+
*
|
|
4
|
+
* Funcao pura de proposito: recebe o Brain e devolve caminho -> conteudo, sem rede e sem
|
|
5
|
+
* `fs`. E a parte que precisa de teste — o formato tem que casar com o que o
|
|
6
|
+
* `validate_brain.cjs` do molde espera (frontmatter `name` igual ao arquivo, `pasta`
|
|
7
|
+
* igual a pasta que o contem, e uma linha de indice por memoria).
|
|
8
|
+
*/
|
|
9
|
+
export type Ancora = {
|
|
10
|
+
tipo: string;
|
|
11
|
+
valor: string;
|
|
12
|
+
sha: string | null;
|
|
13
|
+
};
|
|
14
|
+
export type Memoria = {
|
|
15
|
+
pasta: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
titulo: string;
|
|
18
|
+
resumo: string;
|
|
19
|
+
corpo: string;
|
|
20
|
+
status: "ativa" | "historico";
|
|
21
|
+
dano: string;
|
|
22
|
+
invisibilidade: string;
|
|
23
|
+
externalidade: string;
|
|
24
|
+
ancoras: Ancora[];
|
|
25
|
+
revisar_ate: string | null;
|
|
26
|
+
/** Observacoes de deriva esperando julgamento. Ausente em payload antigo. */
|
|
27
|
+
deriva_aberta?: number;
|
|
28
|
+
};
|
|
29
|
+
export type Brain = {
|
|
30
|
+
tenant: {
|
|
31
|
+
slug: string;
|
|
32
|
+
nome: string;
|
|
33
|
+
};
|
|
34
|
+
projeto: {
|
|
35
|
+
slug: string;
|
|
36
|
+
nome: string;
|
|
37
|
+
};
|
|
38
|
+
/** `CLAUDE.md`. Nulo ou vazio: ainda nao existe, e nao vira arquivo. */
|
|
39
|
+
politica?: string | null;
|
|
40
|
+
/** `BRIEFING.md`. Mesma regra. */
|
|
41
|
+
briefing?: string | null;
|
|
42
|
+
pastas: {
|
|
43
|
+
slug: string;
|
|
44
|
+
definicao: string;
|
|
45
|
+
}[];
|
|
46
|
+
memorias: Memoria[];
|
|
47
|
+
/**
|
|
48
|
+
* Memorias que excederam as tentativas de indexacao e nao serao mais tentadas. Ficam
|
|
49
|
+
* sem embedding — somem da busca semantica — e so este numero denuncia.
|
|
50
|
+
*/
|
|
51
|
+
travadas_na_fila?: number;
|
|
52
|
+
};
|
|
53
|
+
export declare function arquivoDaMemoria(m: Memoria): string;
|
|
54
|
+
export declare function arquivoDoIndice(brain: Brain): string;
|
|
55
|
+
/** Tudo o que o servico gera vive aqui — e nada fora daqui e escrito pelo `sync`. */
|
|
56
|
+
export declare const PASTA = "dd-harness";
|
|
57
|
+
/** O que a raiz precisa conter para a politica chegar a sessao. */
|
|
58
|
+
export declare const LINHA_DE_IMPORT = "@dd-harness/politica.md";
|
|
59
|
+
/**
|
|
60
|
+
* Caminho relativo (POSIX) -> conteúdo. As chaves são o que o manifesto guarda.
|
|
61
|
+
*
|
|
62
|
+
* Tudo dentro de `dd-harness/`, inclusive a política. Na raiz fica só o `CLAUDE.md`, que
|
|
63
|
+
* é **seu**: o `sync` não o escreve, apenas confere que ele importa a política. É o que
|
|
64
|
+
* deixa conviverem a parte gerenciada e o que aquele repositório tem de próprio — e o
|
|
65
|
+
* que faz adotar um projeto existente ser uma linha, não um ritual.
|
|
66
|
+
*
|
|
67
|
+
* Artefato vazio ou ausente não entra no mapa; como o manifesto remove o que saiu do
|
|
68
|
+
* conjunto, esvaziar no serviço apaga o arquivo no próximo `sync`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function materializa(brain: Brain, pasta?: string): Map<string, string>;
|
package/dist/materializa.js
CHANGED
|
@@ -28,9 +28,14 @@ function secaoDeFiltros(m) {
|
|
|
28
28
|
].join("\n");
|
|
29
29
|
}
|
|
30
30
|
export function arquivoDaMemoria(m) {
|
|
31
|
+
// `titulo` vai no frontmatter porque `gravar` e `editar` o exigem la: sem ele o arquivo
|
|
32
|
+
// materializado nao volta pelo `editar`, e o ciclo "sincroniza, corrige, manda de volta"
|
|
33
|
+
// — que e como o agente cura memoria — para com "frontmatter sem `titulo`". O titulo
|
|
34
|
+
// tambem aparece no indice, mas indice nao e o que se edita.
|
|
31
35
|
const frontmatter = [
|
|
32
36
|
"---",
|
|
33
37
|
`name: ${m.slug}`,
|
|
38
|
+
`titulo: ${m.titulo.replace(/\n/g, " ")}`,
|
|
34
39
|
`description: ${m.resumo.replace(/\n/g, " ")}`,
|
|
35
40
|
`pasta: ${m.pasta}`,
|
|
36
41
|
...(m.revisar_ate ? [`revisar-ate: ${m.revisar_ate.slice(0, 10)}`] : []),
|
|
@@ -10,81 +10,81 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export const MOLDES_HISTORICOS = [
|
|
12
12
|
// ca1e529
|
|
13
|
-
`## Protocolo do dd-harness
|
|
14
|
-
|
|
15
|
-
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
16
|
-
|
|
17
|
-
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
18
|
-
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
19
|
-
de ler código, responder ou planejar.
|
|
20
|
-
|
|
21
|
-
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
22
|
-
avise o usuário e **não modifique nada** até ele resolver.
|
|
23
|
-
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
24
|
-
|
|
25
|
-
Leia também o briefing com a mesma ferramenta (tipo briefing). Os dois são obrigatórios.
|
|
26
|
-
|
|
27
|
-
Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
|
|
28
|
-
desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
|
|
29
|
-
válido — não crie fase sem o usuário pedir.
|
|
30
|
-
|
|
31
|
-
E \`listar_skills\`: são os procedimentos deste projeto. **Invoque a que couber
|
|
32
|
-
ANTES de fazer o trabalho, não depois** — skill lida no fim vira revisão do que
|
|
33
|
-
já saiu errado, e é tarde.
|
|
34
|
-
|
|
35
|
-
Vale mesmo quando o pedido parece pequeno: "é só um ajuste" é exatamente a
|
|
36
|
-
frase que antecede pular o procedimento. A política diz quais são obrigatórias
|
|
13
|
+
`## Protocolo do dd-harness
|
|
14
|
+
|
|
15
|
+
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
16
|
+
|
|
17
|
+
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
18
|
+
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
19
|
+
de ler código, responder ou planejar.
|
|
20
|
+
|
|
21
|
+
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
22
|
+
avise o usuário e **não modifique nada** até ele resolver.
|
|
23
|
+
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
24
|
+
|
|
25
|
+
Leia também o briefing com a mesma ferramenta (tipo briefing). Os dois são obrigatórios.
|
|
26
|
+
|
|
27
|
+
Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
|
|
28
|
+
desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
|
|
29
|
+
válido — não crie fase sem o usuário pedir.
|
|
30
|
+
|
|
31
|
+
E \`listar_skills\`: são os procedimentos deste projeto. **Invoque a que couber
|
|
32
|
+
ANTES de fazer o trabalho, não depois** — skill lida no fim vira revisão do que
|
|
33
|
+
já saiu errado, e é tarde.
|
|
34
|
+
|
|
35
|
+
Vale mesmo quando o pedido parece pequeno: "é só um ajuste" é exatamente a
|
|
36
|
+
frase que antecede pular o procedimento. A política diz quais são obrigatórias
|
|
37
37
|
e quando.`,
|
|
38
38
|
// 875342e
|
|
39
|
-
`## Protocolo do dd-harness
|
|
40
|
-
|
|
41
|
-
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
42
|
-
|
|
43
|
-
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
44
|
-
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
45
|
-
de ler código, responder ou planejar.
|
|
46
|
-
|
|
47
|
-
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
48
|
-
avise o usuário e **não modifique nada** até ele resolver.
|
|
49
|
-
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
50
|
-
|
|
51
|
-
Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
|
|
52
|
-
desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
|
|
53
|
-
válido — não crie fase sem o usuário pedir.
|
|
54
|
-
|
|
55
|
-
E \`listar_skills\`: são os procedimentos deste projeto. **Invoque a que couber
|
|
56
|
-
ANTES de fazer o trabalho, não depois** — skill lida no fim vira revisão do que
|
|
57
|
-
já saiu errado, e é tarde.
|
|
58
|
-
|
|
59
|
-
Vale mesmo quando o pedido parece pequeno: "é só um ajuste" é exatamente a
|
|
60
|
-
frase que antecede pular o procedimento. A política diz quais são obrigatórias
|
|
39
|
+
`## Protocolo do dd-harness
|
|
40
|
+
|
|
41
|
+
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
42
|
+
|
|
43
|
+
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
44
|
+
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
45
|
+
de ler código, responder ou planejar.
|
|
46
|
+
|
|
47
|
+
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
48
|
+
avise o usuário e **não modifique nada** até ele resolver.
|
|
49
|
+
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
50
|
+
|
|
51
|
+
Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
|
|
52
|
+
desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
|
|
53
|
+
válido — não crie fase sem o usuário pedir.
|
|
54
|
+
|
|
55
|
+
E \`listar_skills\`: são os procedimentos deste projeto. **Invoque a que couber
|
|
56
|
+
ANTES de fazer o trabalho, não depois** — skill lida no fim vira revisão do que
|
|
57
|
+
já saiu errado, e é tarde.
|
|
58
|
+
|
|
59
|
+
Vale mesmo quando o pedido parece pequeno: "é só um ajuste" é exatamente a
|
|
60
|
+
frase que antecede pular o procedimento. A política diz quais são obrigatórias
|
|
61
61
|
e quando.`,
|
|
62
62
|
// f59da48
|
|
63
|
-
`## Protocolo do dd-harness
|
|
64
|
-
|
|
65
|
-
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
66
|
-
|
|
67
|
-
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
68
|
-
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
69
|
-
de ler código, responder ou planejar.
|
|
70
|
-
|
|
71
|
-
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
72
|
-
avise o usuário e **não modifique nada** até ele resolver.
|
|
73
|
-
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
74
|
-
|
|
75
|
-
Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
|
|
76
|
-
desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
|
|
63
|
+
`## Protocolo do dd-harness
|
|
64
|
+
|
|
65
|
+
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
66
|
+
|
|
67
|
+
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
68
|
+
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
69
|
+
de ler código, responder ou planejar.
|
|
70
|
+
|
|
71
|
+
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
72
|
+
avise o usuário e **não modifique nada** até ele resolver.
|
|
73
|
+
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.
|
|
74
|
+
|
|
75
|
+
Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
|
|
76
|
+
desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
|
|
77
77
|
válido — não crie fase sem o usuário pedir.`,
|
|
78
78
|
// 5fb3f02
|
|
79
|
-
`## Protocolo do dd-harness
|
|
80
|
-
|
|
81
|
-
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
82
|
-
|
|
83
|
-
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
84
|
-
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
85
|
-
de ler código, responder ou planejar.
|
|
86
|
-
|
|
87
|
-
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
88
|
-
avise o usuário e **não modifique nada** até ele resolver.
|
|
79
|
+
`## Protocolo do dd-harness
|
|
80
|
+
|
|
81
|
+
Este projeto tem um protocolo de trabalho que **não está neste arquivo**.
|
|
82
|
+
|
|
83
|
+
**ANTES de qualquer outra coisa**, chame a ferramenta MCP \`ler_artefato\` com
|
|
84
|
+
\`tipo: "politica"\` e siga o que ela devolver. É a primeira ação da sessão, antes
|
|
85
|
+
de ler código, responder ou planejar.
|
|
86
|
+
|
|
87
|
+
- Se a ferramenta não existir, o servidor MCP do dd-harness não está declarado:
|
|
88
|
+
avise o usuário e **não modifique nada** até ele resolver.
|
|
89
89
|
- Se ela devolver vazio, este projeto nunca foi briefado — diga isso ao usuário.`,
|
|
90
90
|
];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O payload de sessao guardado em disco, com o ETag que o servico deu.
|
|
3
|
+
*
|
|
4
|
+
* `GET /api/v1/artefatos` ja respondia `ETag` e ja sabia responder `304` — mas nenhum
|
|
5
|
+
* cliente mandava `If-None-Match`, entao o caminho condicional nunca rodava: toda sessao
|
|
6
|
+
* baixava o Brain inteiro de novo, igual ao acervo inteiro, mesmo quando nada mudara.
|
|
7
|
+
*
|
|
8
|
+
* O cache guarda o corpo junto do ETag porque `304` vem SEM corpo — sem a copia local,
|
|
9
|
+
* responder 304 deixaria quem chamou sem payload nenhum. O disco e otimizacao, nao
|
|
10
|
+
* contrato: qualquer falha de leitura ou escrita cai para a requisicao completa, que e o
|
|
11
|
+
* comportamento de antes.
|
|
12
|
+
*/
|
|
13
|
+
export type PayloadCacheado = {
|
|
14
|
+
etag: string;
|
|
15
|
+
corpo: string;
|
|
16
|
+
projeto?: string;
|
|
17
|
+
};
|
|
18
|
+
/** Sem cache, cache ilegivel, de formato antigo ou de outro projeto: `null` — pede inteiro. */
|
|
19
|
+
export declare function leCacheDoPayload(raiz: string): Promise<PayloadCacheado | null>;
|
|
20
|
+
/** Falha em silencio: cache e otimizacao. Nao vale derrubar a sessao por disco cheio. */
|
|
21
|
+
export declare function guardaCacheDoPayload(raiz: string, cache: PayloadCacheado): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* O corpo da resposta, vindo da rede ou do cache — e o cache ja atualizado.
|
|
24
|
+
*
|
|
25
|
+
* Trata as tres situacoes que interessam a quem chama:
|
|
26
|
+
* - `304`: o servico confirmou que nada mudou. Devolve o corpo guardado.
|
|
27
|
+
* - `200` com `ETag`: corpo novo, que passa a ser o cache.
|
|
28
|
+
* - `200` sem `ETag`: servico antigo ou proxy que removeu o cabecalho. Funciona igual,
|
|
29
|
+
* so nao guarda nada — o proximo boot volta a pedir inteiro.
|
|
30
|
+
*/
|
|
31
|
+
export declare function corpoComCache(raiz: string, resposta: Response, cacheAnterior: PayloadCacheado | null): Promise<string | null>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { leConfigDoRepo, leToken } from "./config.js";
|
|
5
|
+
import { escreveAtomico, estadoDoRepo } from "./estado-local.js";
|
|
6
|
+
const caminho = (raiz) => join(estadoDoRepo(raiz), "artefatos.json");
|
|
7
|
+
/**
|
|
8
|
+
* Config e token, hasheados. O mesmo diretorio de estado serve um repositorio, mas o
|
|
9
|
+
* repositorio pode ser reapontado para outro projeto — e ai o ETag guardado e de um
|
|
10
|
+
* acervo que nao e este. Reenvia-lo faria o servico responder `304` e o cliente servir o
|
|
11
|
+
* Brain do projeto anterior, sem nada denunciar. Igual ao cache de ancoras do cinto.
|
|
12
|
+
*/
|
|
13
|
+
async function identidade(raiz) {
|
|
14
|
+
const config = await leConfigDoRepo(raiz).catch(() => null);
|
|
15
|
+
return createHash("sha256")
|
|
16
|
+
.update(JSON.stringify([config, config ? await leToken(config.api) : null]))
|
|
17
|
+
.digest("hex");
|
|
18
|
+
}
|
|
19
|
+
/** Sem cache, cache ilegivel, de formato antigo ou de outro projeto: `null` — pede inteiro. */
|
|
20
|
+
export async function leCacheDoPayload(raiz) {
|
|
21
|
+
try {
|
|
22
|
+
const lido = JSON.parse(await readFile(caminho(raiz), "utf8"));
|
|
23
|
+
if (typeof lido.etag !== "string" || typeof lido.corpo !== "string")
|
|
24
|
+
return null;
|
|
25
|
+
if (lido.projeto !== (await identidade(raiz)))
|
|
26
|
+
return null;
|
|
27
|
+
return { etag: lido.etag, corpo: lido.corpo, projeto: lido.projeto };
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Falha em silencio: cache e otimizacao. Nao vale derrubar a sessao por disco cheio. */
|
|
34
|
+
export async function guardaCacheDoPayload(raiz, cache) {
|
|
35
|
+
try {
|
|
36
|
+
await escreveAtomico(caminho(raiz), JSON.stringify({ ...cache, projeto: await identidade(raiz) }));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* Sem cache o proximo boot so repete a requisicao completa. */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* O corpo da resposta, vindo da rede ou do cache — e o cache ja atualizado.
|
|
44
|
+
*
|
|
45
|
+
* Trata as tres situacoes que interessam a quem chama:
|
|
46
|
+
* - `304`: o servico confirmou que nada mudou. Devolve o corpo guardado.
|
|
47
|
+
* - `200` com `ETag`: corpo novo, que passa a ser o cache.
|
|
48
|
+
* - `200` sem `ETag`: servico antigo ou proxy que removeu o cabecalho. Funciona igual,
|
|
49
|
+
* so nao guarda nada — o proximo boot volta a pedir inteiro.
|
|
50
|
+
*/
|
|
51
|
+
export async function corpoComCache(raiz, resposta, cacheAnterior) {
|
|
52
|
+
if (resposta.status === 304) {
|
|
53
|
+
// Sem corpo guardado nao ha o que devolver. So acontece se o cache sumir entre a
|
|
54
|
+
// leitura e a resposta; quem chama trata como falha e tenta de novo na proxima.
|
|
55
|
+
return cacheAnterior?.corpo ?? null;
|
|
56
|
+
}
|
|
57
|
+
const corpo = await resposta.text();
|
|
58
|
+
const etag = resposta.headers.get("etag");
|
|
59
|
+
if (etag)
|
|
60
|
+
await guardaCacheDoPayload(raiz, { etag, corpo });
|
|
61
|
+
return corpo;
|
|
62
|
+
}
|
package/dist/politica.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cabecalhos, credencial, pede } from "./api.js";
|
|
2
|
+
import { corpoComCache, leCacheDoPayload } from "./payload-cache.js";
|
|
2
3
|
import { politicaParaSessao } from "./regras-de-commit.js";
|
|
3
4
|
export async function buscaPolitica(raiz) {
|
|
4
5
|
let config;
|
|
@@ -14,15 +15,31 @@ export async function buscaPolitica(raiz) {
|
|
|
14
15
|
url.searchParams.set("tenant", config.config.tenant);
|
|
15
16
|
url.searchParams.set("projeto", config.config.projeto);
|
|
16
17
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// O ETag guardado do boot anterior. Com ele, o caso comum — reabrir a sessao sem
|
|
19
|
+
// ninguem ter mexido no Brain — custa um 304 sem corpo, em vez do acervo inteiro.
|
|
20
|
+
const cache = await leCacheDoPayload(raiz);
|
|
21
|
+
const resposta = await pede(url, {
|
|
22
|
+
headers: {
|
|
23
|
+
...cabecalhos(config.token),
|
|
24
|
+
...(cache ? { "If-None-Match": cache.etag } : {}),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
// 304 e sucesso, mas `resposta.ok` e falso para ele — checar `ok` primeiro mandaria
|
|
28
|
+
// o caminho feliz do cache direto para "inalcancavel".
|
|
29
|
+
if (!resposta.ok && resposta.status !== 304) {
|
|
19
30
|
const { erro } = (await resposta.json().catch(() => ({})));
|
|
20
31
|
return {
|
|
21
32
|
estado: "inalcancavel",
|
|
22
33
|
motivo: erro ?? `a API respondeu ${resposta.status}.`,
|
|
23
34
|
};
|
|
24
35
|
}
|
|
25
|
-
const
|
|
36
|
+
const corpo = await corpoComCache(raiz, resposta, cache);
|
|
37
|
+
if (corpo === null) {
|
|
38
|
+
// 304 sem copia local: o cache sumiu entre a leitura e a resposta. Raro, e a sessao
|
|
39
|
+
// seguinte se resolve sozinha pedindo o payload inteiro.
|
|
40
|
+
return { estado: "inalcancavel", motivo: "o cache local do payload sumiu; tente de novo." };
|
|
41
|
+
}
|
|
42
|
+
const payload = JSON.parse(corpo);
|
|
26
43
|
const conteudo = payload.politica?.trim();
|
|
27
44
|
const esperandoIndexacao = payload.esperando_indexacao ?? 0;
|
|
28
45
|
const roadmap = payload.roadmap;
|
package/dist/reancorar.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cabecalhos, credencial, pede, recusa } from "./api.js";
|
|
2
|
+
import { invalidaCache } from "./cinto.js";
|
|
2
3
|
import {} from "./curar.js";
|
|
3
4
|
/**
|
|
4
5
|
* Trocar o alvo de uma ancora, sem reescrever a memoria.
|
|
@@ -27,9 +28,21 @@ export async function reancora(raiz, endereco, de, para) {
|
|
|
27
28
|
const novas = memoria.ancoras.map((a) => (a.valor === de ? { ...a, valor: para } : a));
|
|
28
29
|
// Reaproveita o PATCH de edicao: ele recebe a memoria inteira, entao mandamos o que ja
|
|
29
30
|
// estava la com a ancora trocada. Uma porta so para escrever memoria, e nao duas.
|
|
31
|
+
//
|
|
32
|
+
// `If-Match` com o carimbo que acabamos de ler: entre a leitura acima e este PATCH,
|
|
33
|
+
// outra sessao pode ter reescrito a memoria — e como o PATCH e INTEGRAL, gravar assim
|
|
34
|
+
// mesmo apagaria o texto dela por completo para trocar uma ancora. Com a precondicao, o
|
|
35
|
+
// servico recusa com 412 e `recusa` mostra a mensagem que manda reler.
|
|
36
|
+
//
|
|
37
|
+
// Servico mais velho nao devolve `atualizada_em`: sem o campo nao ha cabecalho, e a
|
|
38
|
+
// escrita segue como antes. Melhor que recusar contra um servico que nao sabe do
|
|
39
|
+
// contrato novo.
|
|
30
40
|
const envio = await pede(`${config.api}/api/v1/memorias/${endereco}`, {
|
|
31
41
|
method: "PATCH",
|
|
32
|
-
headers:
|
|
42
|
+
headers: {
|
|
43
|
+
...cabecalhos(token, true),
|
|
44
|
+
...(memoria.atualizada_em ? { "If-Match": memoria.atualizada_em } : {}),
|
|
45
|
+
},
|
|
33
46
|
body: JSON.stringify({
|
|
34
47
|
tenant: config.tenant,
|
|
35
48
|
projeto: config.projeto,
|
|
@@ -44,5 +57,10 @@ export async function reancora(raiz, endereco, de, para) {
|
|
|
44
57
|
});
|
|
45
58
|
if (!envio.ok)
|
|
46
59
|
await recusa(envio);
|
|
60
|
+
// O cache de ancoras do cinto e o que o hook consulta para avisar quem toca um arquivo
|
|
61
|
+
// ancorado. Sem invalidar, ele segue avisando no caminho ANTIGO e nao protege o novo —
|
|
62
|
+
// ate alguem reconstruir o cache por outro motivo, o que pode nunca acontecer na mesma
|
|
63
|
+
// sessao em que a refatoracao foi feita.
|
|
64
|
+
await invalidaCache(raiz);
|
|
47
65
|
return { endereco, de, para };
|
|
48
66
|
}
|