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.
- package/README.md +67 -0
- package/extensions/index.ts +415 -0
- package/package.json +53 -0
- package/references/coverage.md +89 -0
- package/references/lenses.md +43 -0
- package/references/pr-goal.md +58 -0
- package/references/query-safety.md +50 -0
- package/scripts/cov-marker.sh +53 -0
- package/scripts/cr-comment.sh +159 -0
- package/scripts/cr-status.sh +161 -0
- package/scripts/origin-guard.sh +52 -0
- package/scripts/redact.sh +24 -0
- package/scripts/strip-shim.sh +10 -0
- package/skills/ask-groundfast/SKILL.md +64 -0
- package/skills/babysit/SKILL.md +134 -0
- package/skills/babysit/references/coderabbit.md +58 -0
- package/skills/babysit/references/loop.md +133 -0
- package/skills/babysit/scripts/checkout-pr.sh +46 -0
- package/skills/babysit/scripts/ci-cause.sh +12 -0
- package/skills/babysit/scripts/cov-comment.sh +115 -0
- package/skills/babysit/scripts/cycle.sh +226 -0
- package/skills/babysit/scripts/gh-thread.sh +164 -0
- package/skills/babysit/scripts/pr-goal.sh +226 -0
- package/skills/babysit/scripts/pr-state.sh +120 -0
- package/skills/babysit/scripts/push-pr.sh +43 -0
- package/skills/babysit/scripts/stage.sh +34 -0
- package/skills/babysit/scripts/wait-ci.sh +50 -0
- package/skills/babysit/scripts/wait-review.sh +203 -0
- package/skills/drain/SKILL.md +151 -0
- package/skills/drain/references/inner-loop.md +52 -0
- package/skills/drain/references/ordering.md +22 -0
- package/skills/drain/references/threads.md +32 -0
- package/skills/drain/scripts/babysit-cmd.sh +23 -0
- package/skills/drain/scripts/commit.sh +15 -0
- package/skills/drain/scripts/conflict-finish.sh +37 -0
- package/skills/drain/scripts/drain-queue.sh +74 -0
- package/skills/drain/scripts/integrate-base.sh +42 -0
- package/skills/drain/scripts/merge-pr.sh +98 -0
- package/skills/drain/scripts/order-queue.sh +203 -0
- package/skills/drain/scripts/review-diff.sh +46 -0
- package/skills/pipeline/SKILL.md +60 -0
- package/skills/pipeline/references/issue-contract.md +51 -0
- package/skills/pipeline/references/issue-loop.md +128 -0
- package/skills/pipeline/references/review-gate.md +17 -0
- package/skills/pipeline/scripts/claim-issue.sh +143 -0
- package/skills/pipeline/scripts/create-pr.sh +44 -0
- package/skills/pipeline/scripts/issue-context.sh +43 -0
- package/skills/pipeline/scripts/issue-note.sh +30 -0
- package/skills/pipeline/scripts/issue-queue.sh +115 -0
- package/skills/pipeline/scripts/pipeline-cmd.sh +53 -0
- package/skills/pipeline/scripts/prepare-issue.sh +87 -0
- package/skills/pipeline/scripts/repo-context.sh +17 -0
- package/skills/pr/SKILL.md +114 -0
- package/skills/pr/references/review-fanout.md +44 -0
- package/skills/pr/scripts/pre-pr-state.sh +102 -0
- package/skills/pr/scripts/push-branch.sh +17 -0
- package/skills/scaffolding-services/SKILL.md +52 -0
- package/skills/scaffolding-services/references/bun.md +69 -0
- package/skills/scaffolding-services/references/rust.md +52 -0
- package/skills/scaffolding-services/scripts/detect-stack.sh +13 -0
- package/skills/scoping-engagement/SKILL.md +77 -0
- package/skills/scoping-engagement/references/discovery-questions.md +62 -0
- package/skills/ship/SKILL.md +80 -0
- package/skills/ship/references/cloudflare.md +10 -0
- package/skills/ship/references/n8n.md +9 -0
- package/skills/ship/references/plugin.md +11 -0
- package/skills/ship/references/railway.md +10 -0
- package/skills/ship/references/vps.md +7 -0
- package/skills/ship/scripts/repo-state.sh +30 -0
- package/skills/tidy/SKILL.md +40 -0
- package/skills/tidy/scripts/orphans.sh +90 -0
- package/skills/wrap/SKILL.md +143 -0
- package/skills/wrap/references/formats.md +85 -0
- package/skills/wrap/references/selection.md +25 -0
- package/skills/wrap/references/session-coverage.md +49 -0
- package/skills/wrap/scripts/learn-file.sh +134 -0
- package/skills/wrap/scripts/repo-state.sh +110 -0
- package/skills/wrap/scripts/session-cover.sh +344 -0
- package/skills/wrap/scripts/tasks.sh +83 -0
- 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
|