groundfast 0.7.7

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.
Files changed (80) hide show
  1. package/README.md +67 -0
  2. package/extensions/index.ts +415 -0
  3. package/package.json +53 -0
  4. package/references/coverage.md +89 -0
  5. package/references/lenses.md +43 -0
  6. package/references/pr-goal.md +58 -0
  7. package/references/query-safety.md +50 -0
  8. package/scripts/cov-marker.sh +53 -0
  9. package/scripts/cr-comment.sh +159 -0
  10. package/scripts/cr-status.sh +161 -0
  11. package/scripts/origin-guard.sh +52 -0
  12. package/scripts/redact.sh +24 -0
  13. package/scripts/strip-shim.sh +10 -0
  14. package/skills/ask-groundfast/SKILL.md +64 -0
  15. package/skills/babysit/SKILL.md +134 -0
  16. package/skills/babysit/references/coderabbit.md +58 -0
  17. package/skills/babysit/references/loop.md +133 -0
  18. package/skills/babysit/scripts/checkout-pr.sh +46 -0
  19. package/skills/babysit/scripts/ci-cause.sh +12 -0
  20. package/skills/babysit/scripts/cov-comment.sh +115 -0
  21. package/skills/babysit/scripts/cycle.sh +226 -0
  22. package/skills/babysit/scripts/gh-thread.sh +164 -0
  23. package/skills/babysit/scripts/pr-goal.sh +226 -0
  24. package/skills/babysit/scripts/pr-state.sh +120 -0
  25. package/skills/babysit/scripts/push-pr.sh +43 -0
  26. package/skills/babysit/scripts/stage.sh +34 -0
  27. package/skills/babysit/scripts/wait-ci.sh +50 -0
  28. package/skills/babysit/scripts/wait-review.sh +203 -0
  29. package/skills/drain/SKILL.md +151 -0
  30. package/skills/drain/references/inner-loop.md +52 -0
  31. package/skills/drain/references/ordering.md +22 -0
  32. package/skills/drain/references/threads.md +32 -0
  33. package/skills/drain/scripts/babysit-cmd.sh +23 -0
  34. package/skills/drain/scripts/commit.sh +15 -0
  35. package/skills/drain/scripts/conflict-finish.sh +37 -0
  36. package/skills/drain/scripts/drain-queue.sh +74 -0
  37. package/skills/drain/scripts/integrate-base.sh +42 -0
  38. package/skills/drain/scripts/merge-pr.sh +98 -0
  39. package/skills/drain/scripts/order-queue.sh +203 -0
  40. package/skills/drain/scripts/review-diff.sh +46 -0
  41. package/skills/pipeline/SKILL.md +60 -0
  42. package/skills/pipeline/references/issue-contract.md +51 -0
  43. package/skills/pipeline/references/issue-loop.md +128 -0
  44. package/skills/pipeline/references/review-gate.md +17 -0
  45. package/skills/pipeline/scripts/claim-issue.sh +143 -0
  46. package/skills/pipeline/scripts/create-pr.sh +44 -0
  47. package/skills/pipeline/scripts/issue-context.sh +43 -0
  48. package/skills/pipeline/scripts/issue-note.sh +30 -0
  49. package/skills/pipeline/scripts/issue-queue.sh +115 -0
  50. package/skills/pipeline/scripts/pipeline-cmd.sh +53 -0
  51. package/skills/pipeline/scripts/prepare-issue.sh +87 -0
  52. package/skills/pipeline/scripts/repo-context.sh +17 -0
  53. package/skills/pr/SKILL.md +114 -0
  54. package/skills/pr/references/review-fanout.md +44 -0
  55. package/skills/pr/scripts/pre-pr-state.sh +102 -0
  56. package/skills/pr/scripts/push-branch.sh +17 -0
  57. package/skills/scaffolding-services/SKILL.md +52 -0
  58. package/skills/scaffolding-services/references/bun.md +69 -0
  59. package/skills/scaffolding-services/references/rust.md +52 -0
  60. package/skills/scaffolding-services/scripts/detect-stack.sh +13 -0
  61. package/skills/scoping-engagement/SKILL.md +77 -0
  62. package/skills/scoping-engagement/references/discovery-questions.md +62 -0
  63. package/skills/ship/SKILL.md +80 -0
  64. package/skills/ship/references/cloudflare.md +10 -0
  65. package/skills/ship/references/n8n.md +9 -0
  66. package/skills/ship/references/plugin.md +11 -0
  67. package/skills/ship/references/railway.md +10 -0
  68. package/skills/ship/references/vps.md +7 -0
  69. package/skills/ship/scripts/repo-state.sh +30 -0
  70. package/skills/tidy/SKILL.md +40 -0
  71. package/skills/tidy/scripts/orphans.sh +90 -0
  72. package/skills/wrap/SKILL.md +143 -0
  73. package/skills/wrap/references/formats.md +85 -0
  74. package/skills/wrap/references/selection.md +25 -0
  75. package/skills/wrap/references/session-coverage.md +49 -0
  76. package/skills/wrap/scripts/learn-file.sh +134 -0
  77. package/skills/wrap/scripts/repo-state.sh +110 -0
  78. package/skills/wrap/scripts/session-cover.sh +344 -0
  79. package/skills/wrap/scripts/tasks.sh +83 -0
  80. package/skills/wrap/scripts/verify.sh +204 -0
@@ -0,0 +1,85 @@
1
+ # Formatos do wrap
2
+
3
+ Fonte única dos templates. O corpo da skill decide **quando** escrever; este arquivo, **o shape**.
4
+
5
+ ## Tasks
6
+
7
+ Campos: `id`, `title`, `state` (`open|doing|blocked|done|dropped`), `updated` (`YYYY-MM-DD`), e opcionalmente `evidence`, `notes`, `deps`, `branch`, `worktree`.
8
+
9
+ Mudança de estado é **linha nova com o mesmo `id`**. O script exige `id`/`title`/`state`/`updated` válidos e recusa o append inteiro se qualquer linha falhar; chaves extras passam.
10
+
11
+ ## Memória do projeto
12
+
13
+ `memory.md` é a **visão corrente**, não um diário. Três seções fixas, nesta ordem, cada bullet terminando na data `(YYYY-MM-DD)`:
14
+
15
+ ```markdown
16
+ # Memória do projeto — <projeto>
17
+
18
+ ## Decisões negativas — NÃO reverter sem evidência nova
19
+ ## Fonte de verdade
20
+ ## Processo que funcionou
21
+ ```
22
+
23
+ Tema já coberto se **funde** no bullet existente, com a data atualizada; tema novo entra como bullet novo. **Remova** o que ficou obsoleto, o que virou seção estrutural do `CLAUDE.md`/`AGENTS.md` ou o que virou código. Decisão negativa nunca é removida, só fundida.
24
+
25
+ Repo com o formato antigo (blocos `## … (data)` apendados): converta inteiro neste wrap, preservando a data de cada bullet.
26
+
27
+ ## Arquivo de instruções
28
+
29
+ `AGENTS.md` é o canônico da casa: o Pi carrega um arquivo por diretório (`AGENTS.md` antes de `CLAUDE.md`), o Codex lê só `AGENTS.md`, e o Claude Code chega lá por um `CLAUDE.md` de uma linha, `@AGENTS.md`. As seções Learned vivem **num arquivo só** — o que `repo-state.sh` nomeia em `## Arquivo de instruções`, pela regra de `scripts/learn-file.sh`:
30
+
31
+ | Repo | Alvo |
32
+ | :-- | :-- |
33
+ | `CLAUDE.md` importa `@AGENTS.md`, ou não existe | `AGENTS.md` |
34
+ | `CLAUDE.md` dono (sem import) | `CLAUDE.md`; `AGENTS.md` só se as seções Learned já vivem lá e não nele |
35
+
36
+ Import inline conta (`Leia @AGENTS.md antes`); import dentro de código não conta, e o Claude Code também não o avalia.
37
+
38
+ Repo sem nenhum dos dois: crie `AGENTS.md` e um `CLAUDE.md` de uma linha, `@AGENTS.md`; os dois entram no commit B.
39
+
40
+ Seção Learned no arquivo que **não** é o alvo (o `verify.sh` acusa no check 4c): mova os bullets para o alvo neste wrap, fundindo com os que já estão lá, deixe o outro arquivo sem a seção e leve os dois no commit B. O check 4b soma os dois arquivos, então mover não conta como bullet novo.
41
+
42
+ ## HANDOFF.md
43
+
44
+ Sobrescreva no worktree principal com exatamente estas seções, nesta ordem:
45
+
46
+ ```markdown
47
+ # HANDOFF — <projeto>
48
+ Atualizado: YYYY-MM-DD HH:MM BRT (<motivo>)
49
+
50
+ ## Done
51
+ ## Arquivos tocados
52
+ ## Decisões
53
+ ## Verificação
54
+ ## Tasks abertas
55
+ ## Estado do repo
56
+ ## Pendências / Riscos
57
+ ## Próxima ação física
58
+ ```
59
+
60
+ - **Done** descreve efeito, não atividade.
61
+ - **Verificação** cola resultado de comando que rodou nesta sessão.
62
+ - **Tasks abertas** vem de `tasks.sh open`.
63
+ - **Próxima ação física** é a última seção e tem uma linha só.
64
+
65
+ Cópia datada em `docs/handoffs/<YYYY-MM-DD>-<slug>.md` e linha no topo de `docs/handoffs/INDEX.md`: `- <data> — <slug> — <próxima ação>`.
66
+
67
+ Repo com `HANDOFF.md` legado: mova as decisões para `memory.md` **antes** de sobrescrever.
68
+
69
+ ## Espelho `.remember/remember.md`
70
+
71
+ ```markdown
72
+ # Handoff
73
+
74
+ ## State
75
+ <uma a três frases: onde o trabalho parou>
76
+
77
+ ## Next
78
+ 1. <próxima ação física>
79
+ 2. <as demais tasks abertas>
80
+
81
+ ## Context
82
+ - <o que a próxima sessão precisa saber para não repetir descoberta>
83
+ ```
84
+
85
+ Sem projeto: no Claude `~/.remember/remember.md`; no Pi a extensão sobe do cwd até a raiz git e para — sem git, só o cwd. Não atravessa diretório world-writable.
@@ -0,0 +1,25 @@
1
+ # Seleção por destino
2
+
3
+ O wrap **escreve** a tríade; esta régua decide o que entra em cada destino. Nada além do agente escreve a tríade: um overlay de julgamento, se houver, só observa ou pede revisão — não grava, não apaga e não substitui o gate humano de Learned.
4
+
5
+ ## Destinos
6
+
7
+ | Destino | Entra quando | Não entra quando |
8
+ | :-- | :-- | :-- |
9
+ | Handoff | Informação corrente, com evidência, para retomar: resultado, decisão, verificação, bloqueio, risco, próxima ação. Escopo desta tarefa vale aqui. | Preferência permanente, palpite, comando só sugerido. |
10
+ | `memory.md` | Decisão (sobretudo negativa) com motivo; fonte de verdade; processo que **já** funcionou. Fundir duplicata. | Status transitório, cache de código/config, experimento ainda não feito. |
11
+ | Memória nativa do host | Ponteiro do projeto (onde a tríade mora, qual é a próxima ação) e fato **cross-repo e não-derivável**: preferência do Mestre, cliente, prazo. Data absoluta. | O que o repo já registra; o que só vale neste repo (isso é Learned); host sem memória nativa. |
12
+ | Learned Preferences | Correção explícita do Mestre **nesta sessão** sobre comportamento **deste repo**. | Pedido, aprovação, permissão, escopo de tarefa, preferência global. |
13
+ | Learned Facts | Convenção **deste repo** **e** momento desta sessão em que a ausência causou erro/retrabalho repetível. | Descoberta isolada, dica genérica de ferramenta. |
14
+ | Task | Trabalho acionável, bloqueio, ou `done` com evidência de comando **executado**. | Comando apenas proposto. Baixa utilidade não fecha pendência. |
15
+ | Espelho | Cópia derivada do handoff. | Segunda seleção independente. |
16
+
17
+ Estrutura de docs (README, Mapa, Verificar) atualiza-se no passo de docs vivas, no lugar onde já vive. Não vira Learned por ser importante.
18
+
19
+ ## Protegidos
20
+
21
+ Decisão negativa, pendência aberta, bloqueio e próxima ação **não** são omitidos por score baixo. Dúvida vira revisão, não descarte.
22
+
23
+ ## Cobertura da sessão
24
+
25
+ Fonte explícita, nunca o arquivo mais recente: [`session-coverage.md`](session-coverage.md). Sem fonte, declare cobertura indisponível e escreva só o que a sessão ainda contém.
@@ -0,0 +1,49 @@
1
+ # Cobertura da sessão
2
+
3
+ “Sessão inteira” é o histórico **original** do arquivo explícito, não o contexto compactado que o modelo vê agora.
4
+
5
+ ## Fonte
6
+
7
+ | Host | Identidade | Folha |
8
+ | :-- | :-- | :-- |
9
+ | Pi | `PI_SESSION_FILE` (absoluto). Não liste o diretório de sessões. | Último `id` gravado no arquivo, ou o 3º argumento posicional. Caminho = `parentId` até raiz. |
10
+ | Claude Code | `${CLAUDE_CODE_SESSION_ID}` (ou `${CLAUDE_SESSION_ID}`) + arquivo `~/.claude/projects/<slug>/<id>.jsonl`, **somente se o `sessionId` interno coincidir com o nome do arquivo**. O slug sai de `${CLAUDE_PROJECT_DIR}`; sem ela, do toplevel git; sem repo, do cwd. | `last-prompt.leafUuid`. Caminho = `parentUuid`; no `system/compact_boundary`, que o zera, `logicalParentUuid` quando ele aparece antes no arquivo; com elo para frente ou ausente, a cadeia para no boundary. |
11
+
12
+ O script recebe `<caminho> <host> <folha>` por posição — não há flag. Sem identidade: `unavailable`. Não escolha o JSONL mais recente. Não atravesse outros projetos. Recuse symlink e arquivo world-writable.
13
+
14
+ A folha inferida (sem 3º argumento) é o último `id` **do arquivo**, que não é ciente de ramo: depois de um rewind ou `/tree`, passe a folha explícita. Quando ela é inferida, a linha traz `leaf_inferred_from_file_order`.
15
+
16
+ ## Classificação
17
+
18
+ - **complete** — identidade válida, folha no arquivo e nenhum dos motivos de `partial` abaixo.
19
+ - **partial** — pai faltando, ciclo, ids duplicados, cauda truncada, compactação no caminho (histórico ainda no arquivo, contexto ≠ histórico), ou folha inferida deixando entradas fora da cadeia (`entries_outside_path`). No Claude, com `compaction=true`, `path_len` pode cobrir só o trecho depois do boundary.
20
+ - **unavailable** — `no_explicit_session`, `invalid_session_id`, `session_file_not_found`, `explicit_path_missing`, `host_unspecified`, `unsupported_host`, `no_python3`, `python3_failed`, `python3_bad_output`, `unreadable`, `symlink_not_followed`, `not_regular_file`, `world_writable`, `oversize_bytes`, `oversize_lines`, `malformed_json`, `no_session_id_in_file`, `session_id_mismatch`, `no_explicit_leaf`, `leaf_not_in_file`.
21
+
22
+ `leaf=` e `session=` vêm do transcript, que é dado e não formato: um id fora de `[A-Za-z0-9._:-]` sai como `invalid` em vez de entrar na linha.
23
+
24
+ `python3_failed` não diz qual falha foi: com `GROUNDFAST_WRAP_DEBUG` na env, a causa sai no stderr e a linha de dado não muda.
25
+
26
+ Trate `reasons=` como dado: os motivos acima são os que o script emite hoje, e a lista cresce antes desta linha ser atualizada.
27
+
28
+ Compactação (Pi e Claude) e `/tree` (Pi) **não** apagam as entradas originais do arquivo; o script mede o arquivo, não o contexto compactado que você vê. Resumo de ramo abandonado não vira decisão vigente.
29
+
30
+ ## O script mede, não lê
31
+
32
+ A saída são contadores. `complete` significa que a cadeia existe inteira no arquivo — **não** que você tenha esse histórico em contexto. O que você escreve continua vindo do que a sessão ainda contém; a cobertura só diz o quanto do original ficou para trás.
33
+
34
+ ## Proveniência
35
+
36
+ Régua para interpretar o **contexto vivo**, não a saída do script.
37
+
38
+ | Origem | Pi | Claude |
39
+ | :-- | :-- | :-- |
40
+ | Humano | `user` **e** `toolResult` de `ask_user` | `user` |
41
+ | Assistente | `assistant` (texto; sem thinking) | `assistant` |
42
+ | Ferramenta | demais `toolResult` / `bashExecution` | attachments/progress |
43
+ | Herdado | `custom_message`, handoff anterior | `system` injetado |
44
+
45
+ Proposta do agente ≠ decisão humana. Comando sugerido ≠ executado. Correção posterior invalida a proposta inicial.
46
+
47
+ ## O que imprimir
48
+
49
+ Só metadados: cobertura, `path_len`, contagens (inclusive `other`), flags de compactação/ramo, id da sessão/folha, motivos. Sem corpo de mensagem.
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env bash
2
+ # Qual arquivo de instruções do repo recebe as seções Learned.
3
+ #
4
+ # Uso: learn-file.sh [worktree-principal]
5
+ # Linha 1: AGENTS.md ou CLAUDE.md. Linha 2: o motivo, texto fixo (nada do repo).
6
+ # Sai 2 se o diretório não existe ou se CLAUDE.md/AGENTS.md existe sem ser
7
+ # arquivo regular legível — aí não há regra a aplicar.
8
+ # Ou: . learn-file.sh — só define as funções, para o verify.sh e o repo-state.sh
9
+ # decidirem pela mesma regra. Sourced não executa nada nem mexe em `set`.
10
+ #
11
+ # Claude Code lê só CLAUDE.md; Pi lê um arquivo por diretório, AGENTS.md antes
12
+ # de CLAUDE.md; Codex lê só AGENTS.md. Convenção da casa: AGENTS.md canônico e
13
+ # CLAUDE.md = `@AGENTS.md`. Regra, nesta ordem:
14
+ # CLAUDE.md ausente ou importando @AGENTS.md → AGENTS.md
15
+ # CLAUDE.md dono com seção Learned → CLAUDE.md
16
+ # CLAUDE.md dono, Learned só no AGENTS.md → AGENTS.md
17
+ # CLAUDE.md dono, nenhum com Learned → CLAUDE.md
18
+ # Import inline conta (`Leia @AGENTS.md`); dentro de código não conta — o Claude
19
+ # Code tokeniza o arquivo com marked e pula os tokens code e codespan antes de
20
+ # procurar `@path` (visto no binário 2.1.261). O path vai até o espaço:
21
+ # `@AGENTS.md.` ou `(@AGENTS.md)` não é este arquivo.
22
+ #
23
+ # Os greps leem até o fim (sem -q): quem chama tem pipefail, e um grep que sai
24
+ # no primeiro match mata o pipeline a montante com SIGPIPE quando o arquivo é
25
+ # longo o bastante para estourar o buffer de stdout — a função devolvia falso.
26
+
27
+ # Fence (CommonMark): até 3 espaços de indentação; fecha só com o mesmo
28
+ # caractere, comprimento >= o de abertura e nada além de espaço depois. `~~~`
29
+ # dentro de um bloco ``` é conteúdo, ```text não fecha nada, e ``` com 4
30
+ # espaços é código dentro do bloco.
31
+ unfenced() { # [arquivo] — sem argumento, lê a stdin
32
+ awk '
33
+ match($0, /^ {0,3}(```+|~~~+)/) {
34
+ m = substr($0, RSTART, RLENGTH); sub(/^ +/, "", m)
35
+ rest = substr($0, RSTART + RLENGTH)
36
+ if (!f) { f = 1; fc = substr(m, 1, 1); fl = length(m); next }
37
+ if (substr(m, 1, 1) == fc && length(m) >= fl && rest ~ /^[[:space:]]*$/) { f = 0; next }
38
+ }
39
+ !f' "$@"
40
+ }
41
+ # Heading canônico: os dois nomes do passo 8, exatos — `## Learned Workspace
42
+ # Facts Archive` não é seção Learned. verify.sh usa o mesmo padrão.
43
+ LEARNED_HEADING='^## Learned (User Preferences|Workspace Facts)[[:space:]]*$'
44
+ learned() { # <arquivo>
45
+ [ -f "$1" ] && unfenced "$1" | grep -E "$LEARNED_HEADING" >/dev/null
46
+ }
47
+ # Code span (CommonMark, e o marked): um run de N backticks fecha no próximo
48
+ # run de exatamente N — na mesma linha ou nas seguintes do mesmo parágrafo.
49
+ # Sem par até linha em branco, heading ou fim, o run é literal e o conteúdo
50
+ # volta. Um sed com backreference perde ``@x `y``: `[^`]*` não atravessa o
51
+ # backtick interno, cai no par de um e deixa o conteúdo à mostra.
52
+ unspanned() { # stdin → stdout sem os code spans
53
+ awk '
54
+ function fecha( pos) { # corta s logo após o fechamento de tamanho n
55
+ for (pos = 1; match(substr(s, pos), /`+/); pos += RSTART + RLENGTH - 1)
56
+ if (RLENGTH == n) { s = substr(s, pos + RSTART + RLENGTH - 1); return 1 }
57
+ return 0
58
+ }
59
+ {
60
+ s = $0; out = ""; h = (s ~ /^ {0,3}#{1,6}([[:space:]]|$)/)
61
+ if (n && (h || s ~ /^[[:space:]]*$/)) { print pend; n = 0 }
62
+ if (n) { if (fecha()) n = 0; else { pend = pend "\n" s; next } }
63
+ while (match(s, /`+/)) {
64
+ out = out substr(s, 1, RSTART - 1)
65
+ n = RLENGTH; run = substr(s, RSTART, n); s = substr(s, RSTART + n)
66
+ if (fecha()) { n = 0; continue }
67
+ pend = run s; s = ""; break
68
+ }
69
+ if (n && h) { out = out pend; n = 0 } # heading é bloco de uma linha
70
+ print out s
71
+ }
72
+ END { if (n) print pend }'
73
+ }
74
+ imports() {
75
+ [ -f "$1" ] && unfenced "$1" | unspanned \
76
+ | grep -E '(^|[[:space:]])@(\./)?AGENTS\.md([[:space:]]|$)' >/dev/null
77
+ }
78
+ learn_unreadable() { # <root> → nome do arquivo que existe sem ser regular e legível; exit 1 se nenhum
79
+ local f
80
+ for f in CLAUDE.md AGENTS.md; do
81
+ if [ -e "$1/$f" ] && ! { [ -f "$1/$f" ] && [ -r "$1/$f" ]; }; then echo "$f"; return 0; fi
82
+ done
83
+ return 1
84
+ }
85
+
86
+ # O alvo e o motivo saem do mesmo julgamento: quem decide é esta função, e as
87
+ # duas abaixo só traduzem o estado. Assim as duas não podem discordar.
88
+ learn_case() { # <root> → um nome de estado da regra
89
+ local c="$1/CLAUDE.md" a="$1/AGENTS.md"
90
+ if [ ! -f "$c" ]; then
91
+ [ -f "$a" ] && echo only-agents || echo none
92
+ elif imports "$c"; then
93
+ learned "$c" && echo import-owner || echo import
94
+ elif learned "$c"; then
95
+ learned "$a" && echo drift-owner || echo owner-learned
96
+ elif learned "$a"; then
97
+ echo agents-learned
98
+ else
99
+ echo owner
100
+ fi
101
+ }
102
+
103
+ learn_target() { # <root> → AGENTS.md | CLAUDE.md
104
+ case "$(learn_case "$1")" in
105
+ owner|owner-learned|drift-owner) echo CLAUDE.md ;;
106
+ *) echo AGENTS.md ;;
107
+ esac
108
+ }
109
+
110
+ learn_reason() { # <root> → uma linha, texto fixo
111
+ case "$(learn_case "$1")" in
112
+ none) echo "nenhum dos dois existe — crie AGENTS.md e um CLAUDE.md de uma linha, @AGENTS.md" ;;
113
+ only-agents) echo "CLAUDE.md não existe — crie-o com uma linha, @AGENTS.md, para o Claude Code ler o mesmo" ;;
114
+ import) echo "CLAUDE.md importa @AGENTS.md" ;;
115
+ import-owner) echo "CLAUDE.md importa @AGENTS.md — as seções Learned que ainda estão no CLAUDE.md vêm para cá" ;;
116
+ owner-learned) echo "CLAUDE.md é o dono e já tem as seções Learned" ;;
117
+ drift-owner) echo "CLAUDE.md é o dono e já tem as seções Learned — as do AGENTS.md vêm para cá" ;;
118
+ agents-learned) echo "CLAUDE.md é o dono, mas as seções Learned já vivem no AGENTS.md" ;;
119
+ owner) echo "CLAUDE.md é o dono (sem @AGENTS.md)" ;;
120
+ esac
121
+ }
122
+
123
+ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
124
+ set -uo pipefail
125
+ root=${1:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}
126
+ [ -d "$root" ] || { echo "learn-file.sh: '$root' não é um diretório" >&2; exit 2; }
127
+ if bad=$(learn_unreadable "$root"); then
128
+ echo "learn-file.sh: $bad existe em $root mas não é arquivo regular legível" >&2
129
+ exit 2
130
+ fi
131
+ learn_target "$root"
132
+ learn_reason "$root"
133
+ exit 0
134
+ fi
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ # Inventário do estado do repositório para o fechamento de sessão.
3
+ #
4
+ # Garantias: nunca imprime conteúdo de arquivo — só caminho, nome de branch,
5
+ # assunto de commit, contagem e texto fixo deste script. Nome de branch e
6
+ # assunto de commit são texto livre escrito por humano e podem carregar segredo,
7
+ # então passam por redação best-effort. Toda linha é truncada: a saída daqui
8
+ # entra direto no prompt.
9
+ set -uo pipefail
10
+
11
+ here=$(cd "$(dirname "$0")" && pwd -P)
12
+ # Redação é garantia, não conveniência: sem o redact.sh (ou com um que não
13
+ # carrega) o script para, em vez de imprimir branch e commit crus no prompt.
14
+ redact_file="$here/../../../scripts/redact.sh"
15
+ # shellcheck source=../../../scripts/redact.sh
16
+ { [ -f "$redact_file" ] && [ -r "$redact_file" ] && . "$redact_file"; } \
17
+ || { echo "(redact.sh ausente ou inválido em $here/../../../scripts — sem redação; abortando)"; exit 2; }
18
+
19
+ # Corpo de seção: a garantia do cabeçalho — nada cru no prompt — mora aqui, e
20
+ # não em sete cópias que uma edição distraída deixa sem `| redact`.
21
+ body() { # <texto quando não há nada> — conteúdo já truncado na stdin
22
+ local b
23
+ b=$(cat)
24
+ [ -n "$b" ] && printf '%s\n' "$b" | redact || echo "$1"
25
+ }
26
+
27
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
28
+ echo "(fora de um repositório git — sem tríade; grave só memória nativa)"
29
+ exit 0
30
+ fi
31
+
32
+ main=$(git worktree list --porcelain 2>/dev/null | head -1)
33
+ main=${main#worktree }
34
+ top=$(git rev-parse --show-toplevel 2>/dev/null)
35
+
36
+ echo "## Worktree principal"
37
+ printf '%s\n' "${main:-(desconhecido)}" | redact
38
+ [ -n "$main" ] && [ "$main" != "$top" ] && echo "ATENÇÃO: cwd está em worktree secundário — a tríade vai no principal"
39
+
40
+ echo
41
+ echo "## Branch"
42
+ branch=$(git branch --show-current 2>/dev/null)
43
+ if [ -n "$branch" ]; then
44
+ printf '%s\n' "$branch" | redact
45
+ else
46
+ echo "(detached HEAD @ $(git rev-parse --short HEAD 2>/dev/null || echo 'sem commits'))"
47
+ fi
48
+
49
+ echo
50
+ echo "## Operação em curso"
51
+ inflight=
52
+ for p in rebase-merge rebase-apply MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD BISECT_LOG; do
53
+ [ -e "$(git rev-parse --git-path "$p" 2>/dev/null)" ] && inflight="$inflight $p"
54
+ done
55
+ [ -n "$inflight" ] && echo "EM CURSO:$inflight — não commite" || echo "(nenhuma)"
56
+
57
+ echo
58
+ echo "## Working tree"
59
+ git status --porcelain 2>/dev/null | head -40 | body "(limpo)"
60
+
61
+ echo
62
+ echo "## Diff vs HEAD (linhas por arquivo)"
63
+ git diff --stat HEAD 2>/dev/null | tail -30 | body "(sem diff)"
64
+
65
+ echo
66
+ echo "## Commits das últimas 24h"
67
+ git log --since="24 hours ago" --format='%h %s' --no-decorate 2>/dev/null | head -20 | body "(nenhum)"
68
+
69
+ echo
70
+ echo "## Branches locais (upstream e ahead/behind)"
71
+ git for-each-ref --format='%(refname:short) -> %(upstream:short) %(upstream:track)' refs/heads 2>/dev/null | head -30 | body "(nenhuma)"
72
+
73
+ echo
74
+ echo "## Worktrees"
75
+ git worktree list 2>/dev/null | head -20 | body "(só o principal)"
76
+
77
+ echo
78
+ echo "## Stash"
79
+ git stash list --format='%gd %ci %gs' 2>/dev/null | head -20 | body "(vazio)"
80
+
81
+ echo
82
+ echo "## Tríade"
83
+ for f in HANDOFF.md tasks.jsonl memory.md; do
84
+ if [ -f "$main/$f" ]; then
85
+ echo "$f: existe ($(wc -l <"$main/$f" | tr -d ' ') linhas)"
86
+ else
87
+ echo "$f: ausente"
88
+ fi
89
+ done
90
+
91
+ echo
92
+ echo "## Arquivo de instruções"
93
+ # Mesma função que o verify.sh usa no 4c; o motivo é texto fixo do script, e
94
+ # passa pela redação só para manter a garantia do cabeçalho em toda linha.
95
+ learn_root=${main:-$top}
96
+ # shellcheck source=./learn-file.sh
97
+ if [ -f "$here/learn-file.sh" ] && [ -r "$here/learn-file.sh" ] && . "$here/learn-file.sh"; then
98
+ if bad=$(learn_unreadable "$learn_root"); then
99
+ echo "($bad existe mas não é arquivo regular legível — sem alvo; o verify.sh vai acusar)"
100
+ else
101
+ printf '%s — %s\n' "$(learn_target "$learn_root")" "$(learn_reason "$learn_root")" | redact
102
+ fi
103
+ else
104
+ echo "(learn-file.sh ausente em $here — sem regra; o verify.sh vai acusar)"
105
+ fi
106
+
107
+ echo
108
+ echo "## Plan files da sessão (7 dias)"
109
+ find "$HOME/.claude/plans" -maxdepth 1 -name '*.md' -mtime -7 2>/dev/null | head -10 | body "(nenhum)"
110
+ exit 0