dd-harness 0.28.0 → 0.30.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.
@@ -126,4 +126,8 @@ de ler código, responder ou planejar.
126
126
 
127
127
  Depois, \`ler_roadmap\`: se houver uma fase **Agora**, é dela que saem os passos
128
128
  desta sessão. Lista vazia significa que este projeto não usa roadmap, e isso é
129
- válido — não crie fase sem o usuário pedir.`;
129
+ válido — não crie fase sem o usuário pedir.
130
+
131
+ E \`listar_skills\`: são os procedimentos deste projeto. **Invoque a que couber
132
+ ANTES de fazer o trabalho, não depois** — skill lida no fim vira revisão do que
133
+ já saiu errado. A política diz quais são obrigatórias e quando.`;
package/dist/git.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * O repositorio git do projeto — criado pelo `start` quando ainda nao existe.
3
+ *
4
+ * Sem git, metade do dd-harness fica muda e ninguem avisa: `check --commit` nao tem
5
+ * commit para cruzar, `sugerir_ancoras` devolve lista vazia (a rodada 006 bateu nisso), e
6
+ * o gancho `post-commit` nao tem onde morar. Nada disso falha com erro — tudo degrada em
7
+ * silencio, que e a classe de defeito que este projeto existe para combater.
8
+ *
9
+ * Criar e barato e reversivel (`rm -rf .git`); nao criar custa features que a pessoa nem
10
+ * descobre que tem.
11
+ */
12
+ export type EstadoDoGit = "ja-era" | "criado" | "git-indisponivel" | "falhou";
13
+ /** Ja e um repositorio? Responde pelo proprio git, nao pela existencia de `.git` — submodulo e worktree tem `.git` como ARQUIVO, e checar a pasta erraria nos dois. */
14
+ export declare function ehRepositorio(raiz: string): Promise<boolean>;
15
+ /**
16
+ * Cria o repositorio, se ainda nao houver.
17
+ *
18
+ * Nao faz commit inicial: o que commitar e decisao de quem esta comecando o projeto, e um
19
+ * commit automatico com os arquivos que o `start` acabou de escrever criaria uma historia
20
+ * que ninguem pediu — inclusive num repositorio que a pessoa talvez queira que comece
21
+ * vazio.
22
+ *
23
+ * Nunca lanca. `start` nao pode falhar porque o git nao esta instalado.
24
+ */
25
+ export declare function iniciaRepositorio(raiz: string): Promise<EstadoDoGit>;
26
+ /**
27
+ * Escreve o gancho `post-commit`, que devolve a memoria ao code review.
28
+ *
29
+ * Ate aqui ele era IMPRESSO para a pessoa colar, pela regra de que `.git/hooks` nao e
30
+ * nosso. A regra continua valendo para repositorio que ja existia — mas num que o
31
+ * proprio `start` acabou de criar nao ha nada de ninguem para preservar, e imprimir
32
+ * instrucao que so vale se alguem copiar e colar e o mesmo "rode X depois" que a rodada
33
+ * 006 mostrou que ninguem segue.
34
+ *
35
+ * Nunca sobrescreve um gancho existente: ali ha trabalho de outra pessoa.
36
+ */
37
+ export declare function escreveGanchoDeCommit(raiz: string): Promise<"criado" | "ja-tinha" | "falhou">;
package/dist/git.js ADDED
@@ -0,0 +1,67 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const roda = promisify(execFile);
4
+ /** Ja e um repositorio? Responde pelo proprio git, nao pela existencia de `.git` — submodulo e worktree tem `.git` como ARQUIVO, e checar a pasta erraria nos dois. */
5
+ export async function ehRepositorio(raiz) {
6
+ try {
7
+ const { stdout } = await roda("git", ["rev-parse", "--is-inside-work-tree"], { cwd: raiz });
8
+ return stdout.trim() === "true";
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ /**
15
+ * Cria o repositorio, se ainda nao houver.
16
+ *
17
+ * Nao faz commit inicial: o que commitar e decisao de quem esta comecando o projeto, e um
18
+ * commit automatico com os arquivos que o `start` acabou de escrever criaria uma historia
19
+ * que ninguem pediu — inclusive num repositorio que a pessoa talvez queira que comece
20
+ * vazio.
21
+ *
22
+ * Nunca lanca. `start` nao pode falhar porque o git nao esta instalado.
23
+ */
24
+ export async function iniciaRepositorio(raiz) {
25
+ if (await ehRepositorio(raiz))
26
+ return "ja-era";
27
+ try {
28
+ await roda("git", ["init"], { cwd: raiz });
29
+ return "criado";
30
+ }
31
+ catch (erro) {
32
+ return erro.code === "ENOENT" ? "git-indisponivel" : "falhou";
33
+ }
34
+ }
35
+ /**
36
+ * Escreve o gancho `post-commit`, que devolve a memoria ao code review.
37
+ *
38
+ * Ate aqui ele era IMPRESSO para a pessoa colar, pela regra de que `.git/hooks` nao e
39
+ * nosso. A regra continua valendo para repositorio que ja existia — mas num que o
40
+ * proprio `start` acabou de criar nao ha nada de ninguem para preservar, e imprimir
41
+ * instrucao que so vale se alguem copiar e colar e o mesmo "rode X depois" que a rodada
42
+ * 006 mostrou que ninguem segue.
43
+ *
44
+ * Nunca sobrescreve um gancho existente: ali ha trabalho de outra pessoa.
45
+ */
46
+ export async function escreveGanchoDeCommit(raiz) {
47
+ const { join } = await import("node:path");
48
+ const { readFile, writeFile, chmod } = await import("node:fs/promises");
49
+ const caminho = join(raiz, ".git", "hooks", "post-commit");
50
+ try {
51
+ await readFile(caminho, "utf8");
52
+ return "ja-tinha";
53
+ }
54
+ catch {
55
+ // Sem gancho: escreve.
56
+ }
57
+ try {
58
+ // `|| true` porque aviso nao pode falhar um commit que ja aconteceu — o gancho roda
59
+ // DEPOIS, e sair diferente de zero aqui so polui a saida de quem commitou.
60
+ await writeFile(caminho, '#!/bin/sh\n# dd-harness: avisa quais memórias falam do que este commit mudou.\ndd-harness check --commit "$(git rev-parse HEAD)" || true\n', "utf8");
61
+ await chmod(caminho, 0o755);
62
+ return "criado";
63
+ }
64
+ catch {
65
+ return "falhou";
66
+ }
67
+ }
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { termosDaConsulta } from "./argv.js";
15
15
  import { check, status } from "./check.js";
16
16
  import { CAMINHO_CONFIG, guardaConfigDaMaquina, guardaToken, leConfigDaMaquina, leConfigDoRepo, leToken, } from "./config.js";
17
17
  import { escreveHook, escreveMcp, escrevePonteiro } from "./escreve-config.js";
18
+ import { escreveGanchoDeCommit, iniciaRepositorio } from "./git.js";
18
19
  import { grava } from "./gravar.js";
19
20
  import { init, SUGESTAO_AGENTS, SUGESTAO_HOOK, SUGESTAO_MCP } from "./init.js";
20
21
  import { pergunta, escolha, fechaPerguntas } from "./pergunta.js";
@@ -426,6 +427,28 @@ async function comandoStart() {
426
427
  console.log(resultadoProjeto.jaExistia
427
428
  ? `\nProjeto ${resultadoProjeto.projeto} já existia — usando ele.`
428
429
  : `\nProjeto ${resultadoProjeto.projeto} criado.`);
430
+ // 3.5. O repositorio git.
431
+ //
432
+ // Sem ele, metade do dd-harness fica muda SEM AVISAR: `check --commit` nao tem o que
433
+ // cruzar, `sugerir_ancoras` devolve vazio (a rodada 006 bateu nisso), e o gancho de
434
+ // commit nao tem onde morar. Criar aqui e barato e reversivel; nao criar custa features
435
+ // que a pessoa nem descobre que tem.
436
+ const git = await iniciaRepositorio(process.cwd());
437
+ console.log({
438
+ criado: "criado repositório git (`git init`)",
439
+ "ja-era": "mantido repositório git — já existia",
440
+ "git-indisponivel": "AVISO: git não encontrado nesta máquina. `check --commit` e a sugestão de " +
441
+ "âncoras não vão funcionar até você instalá-lo.",
442
+ falhou: "AVISO: não consegui rodar `git init` aqui — siga e crie à mão se quiser.",
443
+ }[git]);
444
+ // O gancho so quando FOMOS nos que criamos o repositorio: num que ja existia,
445
+ // `.git/hooks` e de outra pessoa, e a regra de sempre vale — sugerir, nunca escrever.
446
+ if (git === "criado") {
447
+ const gancho = await escreveGanchoDeCommit(process.cwd());
448
+ if (gancho === "criado") {
449
+ console.log("criado .git/hooks/post-commit (devolve a memória ao code review)");
450
+ }
451
+ }
429
452
  // 4. Config do repositorio — o unico arquivo que `init` tambem escreveria.
430
453
  const r = await init(process.cwd(), { tenant, projeto: resultadoProjeto.projeto, api: API_PADRAO });
431
454
  console.log(r.config === "criada" ? `criado ${CAMINHO_CONFIG}` : `mantido ${CAMINHO_CONFIG}`);
@@ -899,6 +922,31 @@ async function comandoPolitica(argv) {
899
922
  * so faria o Claude Code registrar falha no transcript — que ninguem le — enquanto a
900
923
  * sessao seguiria sem saber que esta sem protocolo.
901
924
  */
925
+ /**
926
+ * Lembra que ha skills — so quando a POLITICA nao lembra.
927
+ *
928
+ * Medido em uso real: a skill aparecia na listagem do host, a descricao estava no
929
+ * contexto, e mesmo assim nao era invocada. Estar disponivel nao e ser usada.
930
+ *
931
+ * O conserto de verdade e a politica dizer quando cada skill e obrigatoria — e o passo 5
932
+ * do briefing agora manda escrever isso. Mas projeto ja briefado nao passa mais por la, e
933
+ * a politica dele continua sem a regra. Esta linha cobre esse intervalo.
934
+ *
935
+ * CALA quando a politica ja fala de skill: repetir o que ela diz treina a ler a politica
936
+ * como se fosse opcional, e a politica e a unica coisa aqui que o agente trata como
937
+ * regra. A deteccao e grosseira de proposito — qualquer mencao basta, porque o custo de
938
+ * calar a mais e zero e o de repetir e corroer a autoridade do texto principal.
939
+ */
940
+ function lembreteDeSkills(politica) {
941
+ if (/skill/i.test(politica))
942
+ return "";
943
+ return ("\n\n---\n\n" +
944
+ "Este projeto pode ter **skills** — procedimentos escritos para o trabalho que se " +
945
+ "repete. `listar_skills` mostra quais, e `ler_skill` traz o passo a passo.\n\n" +
946
+ "Invoque a que couber **antes** de fazer o trabalho, não depois: skill lida no fim " +
947
+ "vira revisão do que já saiu errado. Se a política não disser quais são " +
948
+ "obrigatórias, vale propor ao usuário que ela passe a dizer.");
949
+ }
902
950
  function contextoDaSessao(r, worker) {
903
951
  const base = { hookEventName: "SessionStart" };
904
952
  const fila = avisoDaFila(r.esperandoIndexacao ?? 0, worker);
@@ -911,6 +959,7 @@ function contextoDaSessao(r, worker) {
911
959
  // Depois da politica, antes dos avisos operacionais: o roadmap e contexto de
912
960
  // trabalho, a fila e ruido de infraestrutura. Vazio quando nao ha fase aberta.
913
961
  blocoDeSessao(r.roadmap) +
962
+ lembreteDeSkills(r.conteudo) +
914
963
  fila,
915
964
  };
916
965
  }
@@ -925,9 +974,28 @@ function contextoDaSessao(r, worker) {
925
974
  "que ele já diz é ponto de partida, não é para ser ignorado.\n" +
926
975
  "2. Pergunte ao usuário o que o código e o `CLAUDE.md` não revelarem: " +
927
976
  "objetivo do projeto, stack principal, restrições e convenções.\n" +
928
- "3. Mostre o rascunho da política e do briefing e espere a confirmação " +
977
+ // O git decide o quanto o agente age sozinho, e e a UNICA decisao subtrativa do
978
+ // protocolo: nasce fechada e so abre por escolha explicita. Perguntar AQUI, junto
979
+ // da stack, e o unico momento em que alguem esta pensando no fluxo do projeto —
980
+ // depois vira negociacao no meio da tarefa, que e onde a permissao acaba sendo
981
+ // dada por conveniencia.
982
+ "3. Pergunte SOBRE GIT — é isso que define o quanto o agente age sozinho, e a " +
983
+ "resposta vira regra escrita na política, nunca acordo tácito:\n" +
984
+ " - **O agente pode commitar?** O padrão é NÃO. Só abra se o usuário disser " +
985
+ "que sim, e escreva na política exatamente o que ele autorizou.\n" +
986
+ " - **E `push`?** Trate separado de commit: commit local é reversível, `push` " +
987
+ "publica. Quem autoriza um não autoriza o outro por tabela.\n" +
988
+ " - **Com que frequência?** A cada mudança que funciona, ao fim da tarefa, ou " +
989
+ "só quando pedirem? Isso muda quanto ruído o histórico carrega.\n" +
990
+ " - **Branch ou direto na principal?** Se houver fluxo de branch, escreva " +
991
+ "qual — o agente não adivinha convenção de nome.\n" +
992
+ " - **O que NUNCA fazer**, mesmo com autorização: `push --force`, " +
993
+ "`reset --hard`, reescrever história publicada. Ficam fora por padrão.\n" +
994
+ " Escreva cada resposta como regra. \"Ele sabe o que pode\" não sobrevive à " +
995
+ "próxima sessão, que começa sem memória disto.\n" +
996
+ "4. Mostre o rascunho da política e do briefing e espere a confirmação " +
929
997
  "explícita do usuário antes de escrever.\n" +
930
- "4. Escreva os dois com a ferramenta MCP `escrever_artefato` " +
998
+ "5. Escreva os dois com a ferramenta MCP `escrever_artefato` " +
931
999
  "(`tipo: \"politica\"` e `tipo: \"briefing\"`, uma chamada cada). Se essa " +
932
1000
  "ferramenta não estiver disponível nesta sessão, pare e diga ao usuário que " +
933
1001
  "o servidor MCP do dd-harness precisa estar conectado para isto.\n" +
@@ -938,7 +1006,16 @@ function contextoDaSessao(r, worker) {
938
1006
  "sessão, e ao terminá-la o agente **propõe** concluí-la (`editar_fase` com " +
939
1007
  "`status: \"concluida\"`) — nunca conclui por conta própria, porque quem decide " +
940
1008
  "que uma fase acabou é o usuário.\n" +
941
- "5. Depois de escrever os dois, sobrescreva o `CLAUDE.md` da raiz com um " +
1009
+ // Medido em uso real: a skill aparecia na listagem, a descricao estava no
1010
+ // contexto, e mesmo assim nao era invocada. Estar disponivel nao e ser usada — o
1011
+ // que faz o agente parar e invocar e a POLITICA dizer que e obrigatorio, porque
1012
+ // ela e o unico texto que ele trata como regra em vez de catalogo.
1013
+ " Inclua também QUANDO cada skill é obrigatória — o gatilho, não a lista. " +
1014
+ "Chame `listar_skills` para ver o que o projeto tem e escreva uma linha por " +
1015
+ "skill que não pode ser pulada. Exemplo: \"ao escrever ou editar código, " +
1016
+ "invoque `como-desenvolver` ANTES de começar\". Sem isso a skill existe, " +
1017
+ "aparece na listagem e mesmo assim não é usada — foi medido.\n" +
1018
+ "6. Depois de escrever os dois, sobrescreva o `CLAUDE.md` da raiz com um " +
942
1019
  "ponteiro curto para a política deste serviço — a verdade passa a morar " +
943
1020
  "aqui, e o `CLAUDE.md` local nunca mais precisa ser editado à mão. O ponteiro " +
944
1021
  "precisa citar `ler_artefato` **e** `ler_roadmap`: quem chega por ele (Codex, " +
@@ -947,7 +1024,7 @@ function contextoDaSessao(r, worker) {
947
1024
  // O momento certo de perguntar: quem acabou de descrever o projeto tem o contexto
948
1025
  // fresco para dizer por onde ele vai. Depois disso, ninguem mais pergunta — e uma
949
1026
  // feature que so se descobre lendo a lista de ferramentas nao se descobre.
950
- "6. Pergunte se o trabalho deste projeto tem **fases** — algo que " +
1027
+ "7. Pergunte se o trabalho deste projeto tem **fases** — algo que " +
951
1028
  "atravessa sessões, com ordem entre as partes. Se tiver, proponha as primeiras e " +
952
1029
  "crie-as com `criar_fase`; a de menor ordem vira o foco de cada sessão, e " +
953
1030
  "concluí-la a move para o changelog sozinha. Se for um projeto pequeno, diga que " +
@@ -955,7 +1032,7 @@ function contextoDaSessao(r, worker) {
955
1032
  // O unico momento em que a stack acabou de ser descrita. Depois disto ninguem
956
1033
  // pergunta de novo, e uma skill que so se descobre lendo a lista nao se descobre —
957
1034
  // foi o mesmo gap que o roadmap teve, e que o passo 6 fechou.
958
- "7. Por fim, invoque a skill `propor-ferramentas`. A stack acabou de ser " +
1035
+ "8. Por fim, invoque a skill `propor-ferramentas`. A stack acabou de ser " +
959
1036
  "descrita e as restrições estão frescas — é o único momento em que a pesquisa " +
960
1037
  "tem o filtro certo. Ela **propõe**: MCP, skill ou lib que combine com este " +
961
1038
  "projeto, nada é instalado sem o OK do usuário, e recusar tudo é resposta " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-harness",
3
- "version": "0.28.0",
3
+ "version": "0.30.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",