groundfast 0.8.0 → 0.8.2
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/extensions/index.ts +1 -2
- package/package.json +3 -2
- package/references/coverage.md +26 -16
- package/references/pr-goal.md +19 -6
- package/scripts/cr-comment.sh +19 -6
- package/scripts/cr-status.sh +7 -7
- package/scripts/origin-guard.sh +25 -7
- package/scripts/redact.sh +40 -3
- package/scripts/require-timeout.sh +13 -0
- package/scripts/required-checks.sh +52 -0
- package/scripts/strip-shim.sh +2 -1
- package/skills/babysit/SKILL.md +3 -3
- package/skills/babysit/references/coderabbit.md +6 -3
- package/skills/babysit/references/loop.md +23 -2
- package/skills/babysit/scripts/cov-comment.sh +6 -3
- package/skills/babysit/scripts/cycle.sh +24 -12
- package/skills/babysit/scripts/flaky.sh +166 -0
- package/skills/babysit/scripts/gh-thread.sh +1 -1
- package/skills/babysit/scripts/pr-goal.sh +52 -14
- package/skills/babysit/scripts/pr-state.sh +6 -2
- package/skills/babysit/scripts/push-pr.sh +1 -0
- package/skills/babysit/scripts/stage.sh +6 -2
- package/skills/babysit/scripts/wait-ci.sh +22 -1
- package/skills/babysit/scripts/wait-review.sh +5 -1
- package/skills/drain/SKILL.md +3 -2
- package/skills/drain/references/inner-loop.md +3 -1
- package/skills/drain/scripts/conflict-finish.sh +5 -3
- package/skills/drain/scripts/drain-queue.sh +4 -3
- package/skills/drain/scripts/integrate-base.sh +31 -1
- package/skills/drain/scripts/merge-pr.sh +8 -2
- package/skills/drain/scripts/order-queue.sh +18 -10
- package/skills/drain/scripts/review-diff.sh +3 -1
- package/skills/pipeline/references/issue-contract.md +12 -0
- package/skills/pipeline/references/issue-loop.md +6 -4
- package/skills/pipeline/references/review-gate.md +1 -1
- package/skills/pipeline/scripts/claim-issue.sh +1 -1
- package/skills/pipeline/scripts/create-pr.sh +2 -1
- package/skills/pipeline/scripts/issue-context.sh +1 -1
- package/skills/pipeline/scripts/issue-note.sh +1 -1
- package/skills/pipeline/scripts/issue-queue.sh +70 -7
- package/skills/pipeline/scripts/pipeline-cmd.sh +14 -7
- package/skills/pipeline/scripts/prepare-issue.sh +1 -0
- package/skills/pr/scripts/pre-pr-state.sh +8 -2
- package/skills/pr/scripts/push-branch.sh +13 -0
- package/skills/scaffolding-services/scripts/detect-stack.sh +28 -2
- package/skills/scoping-engagement/SKILL.md +1 -1
- package/skills/ship/references/plugin.md +2 -2
- package/skills/ship/scripts/repo-state.sh +5 -1
- package/skills/tidy/SKILL.md +2 -2
- package/skills/tidy/scripts/drop-stash.sh +26 -0
- package/skills/tidy/scripts/orphans.sh +34 -13
- package/skills/wrap/scripts/repo-state.sh +6 -2
- package/skills/wrap/scripts/session-cover.sh +8 -9
- package/skills/wrap/scripts/tasks.sh +10 -4
- package/skills/wrap/scripts/verify.sh +17 -2
|
@@ -12,6 +12,8 @@ set -uo pipefail
|
|
|
12
12
|
|
|
13
13
|
# shellcheck source=../../../scripts/redact.sh
|
|
14
14
|
. "$(dirname "$0")/../../../scripts/redact.sh"
|
|
15
|
+
# shellcheck source=../../../scripts/strip-shim.sh
|
|
16
|
+
. "$(dirname "$0")/../../../scripts/strip-shim.sh"
|
|
15
17
|
|
|
16
18
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "(fora de um repositório git)"; exit 0; }
|
|
17
19
|
|
|
@@ -36,19 +38,25 @@ git worktree list --porcelain | sed -n 's/^worktree //p' | while IFS= read -r wt
|
|
|
36
38
|
ahead=$(git -C "$wt" rev-list --count '@{upstream}..HEAD' 2>/dev/null || echo '?')
|
|
37
39
|
if [ -z "$clean" ] && [ -n "$track" ] && [ "$ahead" = "0" ]; then
|
|
38
40
|
cls=AUTO; why="tree limpa e ${br:-HEAD} já pushada em $track"
|
|
41
|
+
rm='git worktree remove'
|
|
39
42
|
else
|
|
40
43
|
cls=GATE
|
|
41
44
|
why="${clean:+conteúdo local não salvo (inclui ignorados)}${clean:+; }${track:-sem upstream}${track:+, $ahead commit(s) não pushado(s)}"
|
|
45
|
+
# GATE aprovado descarta conteúdo local: sem --force o git recusa (rc 128).
|
|
46
|
+
rm='git worktree remove --force'
|
|
42
47
|
fi
|
|
43
48
|
printf '%s %s @%s — %s\n' "$cls" "$wt" "$sha" "$why" | redact
|
|
44
|
-
printf ' remover:
|
|
49
|
+
printf ' remover: %s %q\n' "$rm" "$wt" | redact
|
|
45
50
|
printf ' recuperar: git worktree add %q %q\n' "$wt" "${br:-$sha}" | redact
|
|
46
51
|
done
|
|
47
52
|
echo
|
|
48
53
|
|
|
49
54
|
echo "## Branches locais"
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
gh_err=$(mktemp); trap 'rm -f "$gh_err"' EXIT
|
|
56
|
+
# Tab separa os campos: `|` é legal em nome de branch (`a|b` viraria a branch
|
|
57
|
+
# `a`, e o `remover:` apontaria para outra), tab não é. Campo vazio só no fim.
|
|
58
|
+
git for-each-ref --format='%(refname:short)%09%(objectname:short)%09%(upstream:short)%09%(upstream:track)' refs/heads |
|
|
59
|
+
while IFS=$'\t' read -r br sha up track; do
|
|
52
60
|
[ "$br" = "$head_branch" ] && continue
|
|
53
61
|
git worktree list --porcelain | grep -qxF -- "branch refs/heads/$br" && continue
|
|
54
62
|
|
|
@@ -56,20 +64,31 @@ while IFS='|' read -r br sha up track; do
|
|
|
56
64
|
if [ -n "$up" ]; then
|
|
57
65
|
why="upstream $up${track:+ $track}"
|
|
58
66
|
if [ "$gh_ok" -eq 1 ]; then
|
|
59
|
-
|
|
67
|
+
pr=$(gh pr view "$br" --json state,headRefOid,number --jq '"\(.state) \(.headRefOid) \(.number)"' 2>"$gh_err" | strip_shim)
|
|
68
|
+
read -r st merged prnum <<<"$pr"
|
|
60
69
|
case "$st" in
|
|
61
70
|
MERGED)
|
|
62
|
-
# Merged só basta se
|
|
63
|
-
# `branch -D`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
# Merged só basta se todo commit local está no head que o PR mergeou:
|
|
72
|
+
# `branch -D` deixa o resto só no reflog. O track não serve de prova —
|
|
73
|
+
# com --delete-branch o upstream fica [gone] e o ahead some da conta.
|
|
74
|
+
if ! [[ $merged =~ ^[0-9a-f]{40}$ ]] || ! git cat-file -e "$merged^{commit}" 2>/dev/null; then
|
|
75
|
+
why="PR MERGED, mas o head mergeado não está neste clone — nada confirma o local"
|
|
76
|
+
[[ $prnum =~ ^[1-9][0-9]*$ ]] && why="$why; confirme com: git fetch origin refs/pull/$prnum/head"
|
|
77
|
+
elif git merge-base --is-ancestor "refs/heads/$br" "$merged"; then
|
|
78
|
+
cls=AUTO; why="PR MERGED e todo commit local está no head mergeado (${merged:0:7})"
|
|
79
|
+
else
|
|
80
|
+
why="PR MERGED, mas há commit local fora do head mergeado${track:+ ($track)}"
|
|
81
|
+
fi
|
|
68
82
|
;;
|
|
69
83
|
# CLOSED é PR fechado SEM merge: trabalho rejeitado ou abandonado, cujo
|
|
70
84
|
# único registro pode ser esta branch. O remote não confirma nada.
|
|
71
85
|
CLOSED) why="PR CLOSED sem merge — o trabalho não foi para lugar nenhum" ;;
|
|
72
|
-
"
|
|
86
|
+
# Falha de rede ou de API não é "sem PR": o motivo diz qual das duas foi.
|
|
87
|
+
# O shim do mise pode pôr o banner no stderr também; -a e LC_ALL=C: byte
|
|
88
|
+
# fora de UTF-8 faria o grep ver binário e apagar o motivo.
|
|
89
|
+
"") errs=$(strip_shim <"$gh_err")
|
|
90
|
+
if printf '%s\n' "$errs" | grep -q '^no pull requests found'; then why="$why; sem PR encontrado"
|
|
91
|
+
else why="$why; gh falhou ao consultar o PR: $(printf '%s\n' "${errs:-sem stderr}" | LC_ALL=C grep -a -m1 . | REDACT_MAXCOL=120 redact)"; fi ;;
|
|
73
92
|
*) why="$why; PR $st — ainda aberto" ;;
|
|
74
93
|
esac
|
|
75
94
|
fi
|
|
@@ -82,9 +101,11 @@ echo
|
|
|
82
101
|
|
|
83
102
|
echo "## Stash"
|
|
84
103
|
# Stash nunca é AUTO: depois do drop, a recuperação depende do reflog, que expira.
|
|
85
|
-
|
|
104
|
+
# O remover: é por sha (drop-stash.sh relê a pilha), então a ordem não importa.
|
|
105
|
+
git stash list --format='%gd|%H|%cr|%gs' |
|
|
106
|
+
while IFS='|' read -r ref sha age subj; do
|
|
86
107
|
printf 'GATE %s @%s — %s — %s\n' "$ref" "${sha:0:7}" "$age" "$subj" | redact
|
|
87
|
-
printf ' remover:
|
|
108
|
+
printf ' remover: scripts/drop-stash.sh %s\n' "$sha" | redact
|
|
88
109
|
printf ' recuperar: git stash apply %s\n' "$sha" | redact
|
|
89
110
|
done
|
|
90
111
|
exit 0
|
|
@@ -56,7 +56,11 @@ done
|
|
|
56
56
|
|
|
57
57
|
echo
|
|
58
58
|
echo "## Working tree"
|
|
59
|
-
git status --porcelain 2>/dev/null
|
|
59
|
+
all=$(git status --porcelain 2>/dev/null)
|
|
60
|
+
printf '%s' "$all" | head -40 | body "(limpo)"
|
|
61
|
+
# O corte é dito: o passo 2 separa as mudanças desta sessão das alheias por aqui.
|
|
62
|
+
extra=$(( $(printf '%s\n' "$all" | wc -l) - 40 ))
|
|
63
|
+
[ "$extra" -gt 0 ] && echo "(+$extra linha(s) fora do corte — git status --porcelain mostra todas)"
|
|
60
64
|
|
|
61
65
|
echo
|
|
62
66
|
echo "## Diff vs HEAD (linhas por arquivo)"
|
|
@@ -106,5 +110,5 @@ fi
|
|
|
106
110
|
|
|
107
111
|
echo
|
|
108
112
|
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)"
|
|
113
|
+
find "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/plans" -maxdepth 1 -name '*.md' -mtime -7 2>/dev/null | head -10 | body "(nenhum)"
|
|
110
114
|
exit 0
|
|
@@ -32,15 +32,14 @@ if [ -z "$abs" ]; then
|
|
|
32
32
|
*)
|
|
33
33
|
host=${host:-claude}
|
|
34
34
|
base=${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd -P)}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
done
|
|
35
|
+
# O Claude Code grava em ${CLAUDE_CONFIG_DIR:-~/.claude}/projects/<slug>,
|
|
36
|
+
# com cada unidade UTF-16 fora de [a-zA-Z0-9] trocada por `-`. Acima de
|
|
37
|
+
# 200 caracteres ele soma um hash ao slug, e a busca não acha: sai
|
|
38
|
+
# not_found. O sed reproduz a regra para texto BMP em locale UTF-8; fora
|
|
39
|
+
# do BMP (emoji) ou em C/POSIX a contagem de `-` diverge e sai not_found.
|
|
40
|
+
slug=$(printf '%s' "$base" | sed 's/[^0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]/-/g')
|
|
41
|
+
cand="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/projects/$slug/$sid.jsonl"
|
|
42
|
+
[ -f "$cand" ] && abs=$cand
|
|
44
43
|
if [ -z "$abs" ]; then
|
|
45
44
|
echo "coverage=unavailable $empty reasons=session_file_not_found"
|
|
46
45
|
exit 0
|
|
@@ -58,12 +58,18 @@ case "$cmd" in
|
|
|
58
58
|
[ -s "$tmp" ] || die "stdin vazio — nada para apender"
|
|
59
59
|
|
|
60
60
|
# Valida TODAS as linhas antes de escrever uma: um append parcial deixaria
|
|
61
|
-
# o ledger num estado que a próxima sessão não sabe interpretar.
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# o ledger num estado que a próxima sessão não sabe interpretar. O `|| [ -n ]`
|
|
62
|
+
# entrega a última linha sem `\n`, que o `jq -c` abaixo apenda do mesmo jeito.
|
|
63
|
+
line=0; added=0
|
|
64
|
+
while IFS= read -r l || [ -n "$l" ]; do
|
|
64
65
|
line=$((line + 1))
|
|
65
66
|
[ -n "${l//[[:space:]]/}" ] || continue
|
|
67
|
+
added=$((added + 1)) # linha em branco não vira evento no `jq -c` abaixo
|
|
66
68
|
printf '%s' "$l" | jq -e . >/dev/null 2>&1 || die "linha $line não é JSON válido"
|
|
69
|
+
# Um objeto por linha: com dois valores, o `jq -c` abaixo gravaria os dois
|
|
70
|
+
# e só o primeiro passaria pelos campos.
|
|
71
|
+
printf '%s' "$l" | jq -se 'length == 1 and (.[0] | type == "object")' >/dev/null 2>&1 \
|
|
72
|
+
|| die "linha $line: mais de um valor JSON, ou valor que não é objeto"
|
|
67
73
|
id=$(printf '%s' "$l" | jq -r '.id // empty')
|
|
68
74
|
st=$(printf '%s' "$l" | jq -r '.state // empty')
|
|
69
75
|
ti=$(printf '%s' "$l" | jq -r '.title // empty')
|
|
@@ -76,7 +82,7 @@ case "$cmd" in
|
|
|
76
82
|
|
|
77
83
|
# Compacta para uma linha por evento — o formato do ledger é JSONL.
|
|
78
84
|
jq -c . <"$tmp" >>"$file" || die "append falhou"
|
|
79
|
-
echo "ok: $
|
|
85
|
+
echo "ok: $added linha(s) apendada(s) em $file"
|
|
80
86
|
;;
|
|
81
87
|
|
|
82
88
|
*) die "subcomando desconhecido: $cmd" ;;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -uo pipefail
|
|
7
7
|
|
|
8
8
|
root=${1:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}
|
|
9
|
-
mem_root="$HOME/.claude/projects"
|
|
9
|
+
mem_root="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/projects"
|
|
10
10
|
here=$(cd "$(dirname "$0")" && pwd -P)
|
|
11
11
|
fails=0
|
|
12
12
|
|
|
@@ -160,9 +160,24 @@ fi
|
|
|
160
160
|
# slug não é derivável daqui: procuramos o caminho do projeto em todos eles.
|
|
161
161
|
# No CI o runner é efêmero e não tem memória nativa, e o check mede o ritual
|
|
162
162
|
# local. Variável própria, não `CI`: uma sessão local com `CI` exportado não pula.
|
|
163
|
+
# Caminho inteiro, não prefixo: `/x/repo-site` não é ponteiro de `/x/repo`. O
|
|
164
|
+
# caractere depois do caminho não pode continuar o nome (`/` é subcaminho e vale);
|
|
165
|
+
# `.` só continua quando vem seguido de nome (`/x/repo.bak`), não no fim da frase.
|
|
166
|
+
mentions_root() {
|
|
167
|
+
local f
|
|
168
|
+
while IFS= read -r f; do
|
|
169
|
+
ROOT=$root awk '{ s = $0; r = ENVIRON["ROOT"]
|
|
170
|
+
while ((i = index(s, r)) > 0) {
|
|
171
|
+
c = substr(s, i + length(r), 1); n = substr(s, i + length(r) + 1, 1)
|
|
172
|
+
if (c !~ /[A-Za-z0-9_-]/ && !(c == "." && n ~ /[A-Za-z0-9_-]/)) { found = 1; exit }
|
|
173
|
+
s = substr(s, i + 1)
|
|
174
|
+
} } END { exit !found }' "$f" && return 0
|
|
175
|
+
done < <(grep -rlF -- "$root" "$mem_root"/*/memory 2>/dev/null)
|
|
176
|
+
return 1
|
|
177
|
+
}
|
|
163
178
|
if [ "${GROUNDFAST_CI-}" = 1 ]; then
|
|
164
179
|
skip "memória nativa não se aplica no CI (GROUNDFAST_CI=1)"
|
|
165
|
-
elif [ -d "$mem_root" ] &&
|
|
180
|
+
elif [ -d "$mem_root" ] && mentions_root; then
|
|
166
181
|
pass "memória nativa tem ponteiro para $root"
|
|
167
182
|
else
|
|
168
183
|
fail "nenhum arquivo em $mem_root/*/memory menciona $root"
|