Compare pin trees offline and archive finished task cards
ci / test (pull_request) Failing after 1m0s
ci / test (pull_request) Failing after 1m0s
Why: Merge commits share a tree with the pinned feature commit, the import ratchet downloaded modules during CI, and task sync wrote the checkpoint onto the wrong card. Evidence: check-pins accepts 6c0f1e8 (same tree as Backend squad 8e987bf) and rejects empty-tree 8db069d; both objects stayed local. An empty GOMODCACHE run of lint-boundaries matches the cached go list, 14 pairs. bash -n scripts/task.sh. Impact: make pin no longer checks out Backend nested core or agent-command, and a failing pin check does not fail the target. task sync writes the branch card or warns. precheck reports completed-but-unarchived cards and card-close branches older than 24 hours. Refs: feature/agent-stack-r0-r1 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -20,6 +20,21 @@ check_link() {
|
||||
return
|
||||
fi
|
||||
if [ "$sha" != "$expect" ]; then
|
||||
local sha_tree expect_tree
|
||||
if ! sha_tree=$(git -C "$expect_path" rev-parse "${sha}^{tree}" 2>/dev/null); then
|
||||
echo "pin: $container deps/$name ${sha:0:12} is missing locally; fetch $expect_path first" >&2
|
||||
fail=1
|
||||
return
|
||||
fi
|
||||
if ! expect_tree=$(git -C "$expect_path" rev-parse "${expect}^{tree}" 2>/dev/null); then
|
||||
echo "pin: Dev $expect_path ${expect:0:12} is missing locally; fetch $expect_path first" >&2
|
||||
fail=1
|
||||
return
|
||||
fi
|
||||
if [ "$sha_tree" = "$expect_tree" ]; then
|
||||
echo "ok $container $name (same tree)"
|
||||
return
|
||||
fi
|
||||
echo "pin: $container deps/$name is ${sha:0:12}, Dev $expect_path is ${expect:0:12}" >&2
|
||||
fail=1
|
||||
return
|
||||
|
||||
@@ -34,8 +34,8 @@ siblings=(agent-intent agent-native agent-squad)
|
||||
for dir in deps/agent-intent deps/agent-native deps/agent-squad; do
|
||||
mod=$(awk '/^module /{print $2; exit}' "$root/$dir/go.mod")
|
||||
(
|
||||
cd "$root/$dir"
|
||||
GOWORK=off go list -f '{{.ImportPath}}{{range .Imports}} {{.}}{{end}}' ./...
|
||||
cd "$root/scripts"
|
||||
go run lint-imports.go "$root/$dir"
|
||||
) | while read -r importer rest; do
|
||||
for imp in $rest; do
|
||||
for sib in "${siblings[@]}"; do
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// 离线列出模块内非测试 Go 文件的导入。
|
||||
// 只使用标准库,跳过 _test.go 和带 go.mod 的子目录,避免 go list 去下载模块图。
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for _, root := range os.Args[1:] {
|
||||
mod := modulePath(filepath.Join(root, "go.mod"))
|
||||
if mod == "" {
|
||||
fmt.Fprintf(os.Stderr, "lint-imports: %s has no module path\n", root)
|
||||
os.Exit(1)
|
||||
}
|
||||
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil || !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
name := d.Name()
|
||||
if name == "vendor" || (name != "." && strings.HasPrefix(name, ".")) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if path != root {
|
||||
if _, statErr := os.Stat(filepath.Join(path, "go.mod")); statErr == nil {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
return printImports(root, mod, path)
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "lint-imports: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func modulePath(goMod string) string {
|
||||
f, err := os.Open(goMod)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
fields := strings.Fields(sc.Text())
|
||||
if len(fields) >= 2 && fields[0] == "module" {
|
||||
return fields[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func printImports(root, mod, dir string) error {
|
||||
rel, err := filepath.Rel(root, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
importer := mod
|
||||
if rel != "." {
|
||||
importer = mod + "/" + filepath.ToSlash(rel)
|
||||
}
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
seen := map[string]struct{}{}
|
||||
var imports []string
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
||||
continue
|
||||
}
|
||||
file, err := parser.ParseFile(fset, filepath.Join(dir, name), nil, parser.ImportsOnly)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, imp := range file.Imports {
|
||||
path := strings.Trim(imp.Path.Value, `"`)
|
||||
if _, ok := seen[path]; ok {
|
||||
continue
|
||||
}
|
||||
seen[path] = struct{}{}
|
||||
imports = append(imports, path)
|
||||
}
|
||||
}
|
||||
if len(imports) == 0 {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("%s %s\n", importer, strings.Join(imports, " "))
|
||||
return nil
|
||||
}
|
||||
+48
-5
@@ -42,6 +42,23 @@ cmd_precheck() {
|
||||
fi
|
||||
done
|
||||
[ -n "$open_cards" ] && echo "[cards] 已合入未闭合任务卡:$(echo $open_cards)——make task-close 回填归档(主检出运行)"
|
||||
local done_open="" stale="" now ts ref
|
||||
for pc in .task/*.md; do
|
||||
[ -f "$pc" ] || continue
|
||||
case "$(card_status "$pc")" in
|
||||
已完成*) done_open="${done_open} ${pc##*/}" ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$done_open" ] && echo "[cards] 已完成但未归档:$(echo $done_open)"
|
||||
now=$(date +%s)
|
||||
while read -r ref ts; do
|
||||
[ -n "$ref" ] || continue
|
||||
git merge-base --is-ancestor "$ref" "origin/${TARGET_BRANCH}" 2>/dev/null && continue
|
||||
if [ $((now - ts)) -gt 86400 ]; then
|
||||
stale="${stale} ${ref#origin/}"
|
||||
fi
|
||||
done < <(git for-each-ref --format='%(refname:short) %(committerdate:unix)' "refs/remotes/origin/feature/card-close-*")
|
||||
[ -n "$stale" ] && echo "[cards] 闭合分支超过 24 小时未合入:$(echo $stale)"
|
||||
echo "[required-reading] core/baseline.md, workflows/delivery.md §1$([ "$dirty" -gt 0 ] && echo ", standards/collaboration.md(树脏=他人或未收尾现场,不得写入)")"
|
||||
echo "[R0] $(echo "$rmain" | cut -c1-10)(完整:$rmain)"
|
||||
if [ "$dirty" -gt 0 ] || [ "${sub_drift:-0}" -gt 0 ]; then
|
||||
@@ -119,13 +136,27 @@ cmd_sync() {
|
||||
[ "$(git status --porcelain | grep -v '^.. \.zcode/' | wc -l)" -eq 0 ] || die "dirty tree — 先显式提交;不隐式 stash(collaboration.md:11)"
|
||||
git merge --no-edit "origin/${TARGET_BRANCH}"
|
||||
git merge-base --is-ancestor "origin/${TARGET_BRANCH}" HEAD || die "sync incomplete"
|
||||
local base head card
|
||||
local base head card branch slug pc
|
||||
base=$(git rev-parse "origin/${TARGET_BRANCH}"); head=$(git rev-parse HEAD)
|
||||
card=$(ls .task/*.md 2>/dev/null | head -1 || true)
|
||||
if [ -n "$card" ]; then
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
slug=$(echo "$branch" | tr '/' '-')
|
||||
card=".task/${slug}.md"
|
||||
if [ ! -f "$card" ]; then
|
||||
card=""
|
||||
for pc in .task/*.md; do
|
||||
[ -f "$pc" ] || continue
|
||||
if [ "$(card_of_branch "$pc")" = "$branch" ]; then
|
||||
card=$pc
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ -n "$card" ] && [ -f "$card" ]; then
|
||||
printf '| 同步前验收 | %s | %s | %s |\n' "$base" "$head" "$(date -u +%FT%TZ)" >> "$card"
|
||||
git add "$card"
|
||||
git commit -q -m "Record the sync checkpoint" || true
|
||||
else
|
||||
echo "[warn] 找不到分支 ${branch} 的任务卡,同步记录未写入" >&2
|
||||
fi
|
||||
echo "[synced] sync_base=$(echo "$base" | cut -c1-10) tested_head=$(echo "$head" | cut -c1-10) checked_at=$(date -u +%FT%TZ)"
|
||||
echo "[next] 跑正式验收(make test 等),证据写入任务卡"
|
||||
@@ -189,13 +220,18 @@ cmd_close() {
|
||||
for c in .task/*.md; do
|
||||
[ -f "$c" ] || continue
|
||||
b=$(card_of_branch "$c"); [ -n "$b" ] || continue
|
||||
st=$(card_status "$c"); case "$st" in 已完成*) continue ;; esac
|
||||
st=$(card_status "$c")
|
||||
done_unarchived=0
|
||||
case "$st" in 已完成*) done_unarchived=1 ;; esac
|
||||
if card_pending "${c##*/}"; then
|
||||
echo "[close] ${c##*/} 已有待合入的闭合分支,跳过(等其 PR 合入)"
|
||||
continue
|
||||
fi
|
||||
m=$(card_merge "$b") || m=""
|
||||
[ -n "$m" ] || continue
|
||||
if [ -z "$m" ]; then
|
||||
[ "$done_unarchived" = 1 ] || continue
|
||||
m="ARCHIVE"
|
||||
fi
|
||||
cards="$cards
|
||||
$c|$b|$m"
|
||||
done
|
||||
@@ -213,6 +249,13 @@ $c|$b|$m"
|
||||
local n=0 sha subject pr closed_list=""
|
||||
while IFS='|' read -r c b m; do
|
||||
[ -n "$c" ] || continue
|
||||
if [ "$m" = "ARCHIVE" ]; then
|
||||
mkdir -p .task/archive
|
||||
git mv "$c" ".task/archive/${c##*/}"
|
||||
closed_list="$closed_list ${c##*/}(already-complete)"
|
||||
n=$((n+1))
|
||||
continue
|
||||
fi
|
||||
sha="${m%%|*}"; subject="${m#*|}"
|
||||
pr=$(printf '%s' "$subject" | sed -n 's/.*(#\([0-9]\+\)) from.*/\1/p')
|
||||
[ -n "$pr" ] || pr=unknown
|
||||
|
||||
Reference in New Issue
Block a user