Add the task-startup gate scripts and local hooks
Why: agents skipped triggered reading and wrote into shared checkouts holding other tasks' in-flight changes; the 2026-09-30 empty-list incident session found 24 overlapping in-flight files only by accident at first write. Evidence: positive/negative suite in a real scratch repo passed 19/19 (precheck READY/BUSY/SYNC_REQUIRED, start refuse/establish, hook main/badname/ longsubject refusals, sync checkpoint, finish recycle and abandon double- confirm); the suite caught three real defects (pipefail empty-grep abort, worktree gitdir COMMIT_EDITMSG resolution, message checks in the wrong hook phase). Impact: make setup enables pre-commit/commit-msg hooks; make precheck/task- start/task-sync/task-finish provide the governed path; no existing workflow removed. AGENTS.md carries the resident gate rules. Refs: Development-Guidelines 0.1.0-draft.7; task card .task/delivery-mechanism.md
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# feature/delivery-mechanism 任务卡
|
||||
|
||||
任务主记录:<PR 创建后回填>;状态:进行中;owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:weichen/Agent-System-Dev;目标分支:main;R0(initial_base_sha)= 3092715bf68b(历史迁移后新基线)
|
||||
- 工作区:主检出续用(阶段 1 迁移窗口独占,未建 worktree——偏差已记录);分支:feature/delivery-mechanism
|
||||
- 授权与交付终点:落地任务启动门禁机制(task.sh/Makefile/hooks/AGENTS.md);未授权:其他仓库的内容变更(属阶段 3/4)
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
目标:precheck/start/sync/finish 四命令 + pre-commit/commit-msg 硬闸 + AGENTS.md 门禁条文 + guidelines gitlink 推进(draft.7)。
|
||||
非目标:子仓入口文档迁移(阶段 3)、空列表修复(阶段 4)。
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 3092715bf68b... | (见提交) | 2026-09-30 |
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 验证:临时真实仓库正负例 19/19 PASS(precheck 三态/start 拒绝与建立/hook 三类拒绝与放行/sync/finish 回收与 abandon 双确认);完整输出见任务执行记录。
|
||||
@@ -1,9 +1,24 @@
|
||||
# Agent 工作说明(Agent-System-Core)
|
||||
# Agent 工作说明(Agent-System-Dev)
|
||||
|
||||
本仓开发遵循 Development-Guidelines(见 Dev 仓 `development/guidelines`,gitlink 固定版本):
|
||||
本仓开发遵循 Development-Guidelines(见 `deps/development-guidelines`,gitlink 固定版本):
|
||||
核心规则与术语见其 `core/`,按任务触发阅读 `standards/` 专题;日常交付走 `workflows/delivery.md`。
|
||||
|
||||
仓内规则:
|
||||
## 任务启动门禁(强制)
|
||||
|
||||
- 任务启动或恢复后,**第一次写操作前**运行 `make precheck` 并附完整输出;verdict 为
|
||||
`SHARED_WORKSPACE_BUSY / SYNC_REQUIRED` 时不得编辑。
|
||||
- 按其必读清单完成阅读,然后 `make task-start NAME=feature/<slug>` 建立独立 worktree +
|
||||
feature 分支(自动记录 R0 与任务卡);任务中途不重复运行 precheck。
|
||||
- **验收、请求审核、合入前**各运行一次 `make task-sync`;完成声明附 precheck、task-sync、
|
||||
测试三段输出。
|
||||
- 合入后运行 `make task-finish` 回收 worktree 与分支;放弃未合入工作用
|
||||
`make task-finish ABANDON="--abandon <原因>"` 并记录原因。
|
||||
|
||||
## 提交与 PR
|
||||
|
||||
- 主题行:英文祈使句一行(≤100 列,不以句点结尾);body 写 `Why: / Evidence: / Impact: / Refs:`
|
||||
四段;任务卡与 PR 描述为同一份文档。
|
||||
- 跨仓 gitlink 变更须经组合验证(`make test`)后在 Dev 仓推进;受保护分支经 PR 合入。
|
||||
- 模块边界、公开契约与隔离档位见 `policy/modules/`(如有)与 PROJECT.md。
|
||||
- 提交信息用英文祈使句一行;跨仓 gitlink 变更须经组合验证后在 Dev 仓推进。
|
||||
- 验证命令见 PROJECT.md「验证」节;完成声明须附实际命令输出证据。
|
||||
- 首次克隆本仓后运行 `make setup` 启用本地 pre-commit 钩子。
|
||||
|
||||
@@ -9,7 +9,27 @@ BIN := bin/agent-system
|
||||
EMBED_DIR := $(BACKEND)/web/dist
|
||||
PLUGINS := plugins
|
||||
|
||||
.PHONY: help sync status build frontend test test-backend test-frontend test-core dev pin clean
|
||||
.PHONY: help sync status build frontend test test-backend test-frontend test-core dev pin clean setup precheck task-start task-sync task-finish
|
||||
|
||||
TASK := scripts/task.sh
|
||||
|
||||
setup:
|
||||
git config core.hooksPath scripts/hooks
|
||||
chmod +x scripts/hooks/pre-commit scripts/task.sh
|
||||
@echo "OK: pre-commit 钩子已启用(core.hooksPath=scripts/hooks)"
|
||||
|
||||
precheck:
|
||||
@bash $(TASK) precheck
|
||||
|
||||
task-start:
|
||||
@if [ -z "$(NAME)" ]; then echo "usage: make task-start NAME=feature/<slug>"; exit 2; fi
|
||||
@bash $(TASK) start "$(NAME)"
|
||||
|
||||
task-sync:
|
||||
@bash $(TASK) sync
|
||||
|
||||
task-finish:
|
||||
@bash $(TASK) finish $(ABANDON)
|
||||
|
||||
help:
|
||||
@echo "make sync 物化全部 submodule(递归)"
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地硬闸(commit-msg):主题行宽度/句点。git 将消息文件作为 $1 传入——
|
||||
# 注意不能用 pre-commit 做此事:-m 消息在 prepare-commit-msg 才写入,pre-commit 阶段尚不存在。
|
||||
msgfile="$1"
|
||||
[ -n "$msgfile" ] && [ -f "$msgfile" ] || exit 0
|
||||
subject=$(head -1 "$msgfile")
|
||||
[ -n "$subject" ] || exit 0
|
||||
w=$(python3 -c 'import sys,unicodedata;print(sum(2 if unicodedata.east_asian_width(c) in "WF" else 1 for c in sys.argv[1]))' "$subject")
|
||||
[ "$w" -le 100 ] || { echo "commit-msg: 拒绝——主题行宽 ${w} > 100 列:${subject}" >&2; exit 1; }
|
||||
case "$subject" in
|
||||
*.) echo "commit-msg: 拒绝——主题行不以句点结尾:${subject}" >&2; exit 1 ;;
|
||||
esac
|
||||
exit 0
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地硬闸(pre-commit):集成分支直提拒绝 + 分支命名。权威准入在 PR(baseline.md:26)。
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
case "$branch" in
|
||||
main|master)
|
||||
echo "pre-commit: 拒绝——集成分支 ${branch} 不得直接提交,走 feature 分支 + PR(collaboration.md)" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
echo "$branch" | grep -qE '^(feature|fix|hotfix|release)/[A-Za-z0-9._-]+$' || {
|
||||
echo "pre-commit: 拒绝——分支名须为 feature|fix|hotfix|release/<slug>,当前:${branch}" >&2
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# 任务启动门禁:precheck / start / sync / finish(配套 Makefile 同名目标)。
|
||||
# 规范依据:collaboration.md 共同基线与同步节点、治理清理;delivery.md §1/§5。
|
||||
set -euo pipefail
|
||||
|
||||
TARGET_BRANCH="${TASK_TARGET_BRANCH:-main}"
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
need_repo() { git rev-parse --git-dir >/dev/null 2>&1 || die "not inside a git repository"; }
|
||||
width() { python3 -c 'import sys,unicodedata;print(sum(2 if unicodedata.east_asian_width(c) in "WF" else 1 for c in sys.argv[1]))' "$1"; }
|
||||
|
||||
cmd_precheck() {
|
||||
need_repo
|
||||
git fetch -q origin "${TARGET_BRANCH}" 2>/dev/null || true
|
||||
local branch head rmain dirty sub_dirty
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
head=$(git rev-parse HEAD)
|
||||
rmain=$(git rev-parse --verify -q "origin/${TARGET_BRANCH}" 2>/dev/null || echo "$head")
|
||||
dirty=$(git status --porcelain | { grep -v '^.. \.zcode/' || true; } | wc -l)
|
||||
sub_dirty=$(git submodule status --recursive 2>/dev/null | { grep -cE '^\+|^U' || true; })
|
||||
local behind ahead
|
||||
behind=$(git rev-list --count "HEAD..origin/${TARGET_BRANCH}" 2>/dev/null || echo 0)
|
||||
ahead=$(git rev-list --count "origin/${TARGET_BRANCH}..HEAD" 2>/dev/null || echo 0)
|
||||
echo "[repo] branch=${branch} head=$(echo "$head" | cut -c1-10)"
|
||||
echo "[remote] origin/${TARGET_BRANCH}=$(echo "$rmain" | cut -c1-10) ahead=${ahead} behind=${behind}"
|
||||
echo "[tree] $([ "$dirty" -eq 0 ] && echo clean || echo "dirty(${dirty})") submodules_dirty=${sub_dirty}"
|
||||
local wt merged
|
||||
wt=$(git worktree list | tail -n +2 | wc -l)
|
||||
merged=$(git branch --merged "origin/${TARGET_BRANCH}" 2>/dev/null | { grep -vE 'main|master|\*' || true; } | tr -d ' +')
|
||||
echo "[worktrees] linked=${wt}"
|
||||
[ -n "$merged" ] && echo "[recycle] merged un-recycled branches: $(echo $merged)"
|
||||
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_dirty" -gt 0 ]; then
|
||||
echo "[verdict] SHARED_WORKSPACE_BUSY — 工作树不干净,不得写入;先收尾或经 owner 确认"
|
||||
exit 3
|
||||
fi
|
||||
if [ "$behind" -gt 0 ] && [ "$branch" = "${TARGET_BRANCH}" ]; then
|
||||
echo "[verdict] SYNC_REQUIRED — 本地 ${TARGET_BRANCH} 落后远端 ${behind} 提交;先 git pull --ff-only"
|
||||
exit 4
|
||||
fi
|
||||
echo "[verdict] READY — 可建任务工作区:make task-start NAME=feature/your-branch"
|
||||
}
|
||||
|
||||
cmd_start() {
|
||||
need_repo
|
||||
local name="${1:-}"; [ -n "$name" ] || die "usage: task.sh start <feature|fix|hotfix>/<slug>"
|
||||
echo "$name" | grep -qE '^(feature|fix|hotfix|release)/[A-Za-z0-9._-]+$' || die "branch name must match (feature|fix|hotfix|release)/<slug>"
|
||||
git fetch -q origin "${TARGET_BRANCH}" 2>/dev/null || true
|
||||
[ "$(git status --porcelain | grep -v '^.. \.zcode/' | wc -l)" -eq 0 ] || die "dirty tree — resolve first (precheck verdict BUSY)"
|
||||
git merge-base --is-ancestor "origin/${TARGET_BRANCH}" HEAD || die "local ${TARGET_BRANCH} behind origin — pull --ff-only first"
|
||||
local rmain slug wt
|
||||
rmain=$(git rev-parse "origin/${TARGET_BRANCH}")
|
||||
slug=$(echo "$name" | tr '/' '-')
|
||||
wt="$(dirname "$(pwd)")/$(basename "$(pwd)")-wt-${slug}"
|
||||
[ -e "$wt" ] && die "worktree path exists: $wt"
|
||||
git worktree add -q "$wt" -b "$name" "$rmain"
|
||||
(cd "$wt" && git submodule update --init --recursive >/dev/null 2>&1 || echo "[warn] submodule init incomplete in worktree")
|
||||
local card="$wt/.task/${slug}.md"
|
||||
mkdir -p "$wt/.task"
|
||||
cat > "$card" <<EOF
|
||||
# ${name} 任务卡
|
||||
|
||||
任务主记录:<PR 链接,task-finish 时填写>;状态:进行中;owner:$(git config user.name || echo unknown)
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:$(git -C "$wt" remote get-url origin 2>/dev/null || echo '(local)');目标分支:${TARGET_BRANCH};R0(initial_base_sha)= ${rmain}
|
||||
- 工作区:${wt}(worktree);分支:${name}
|
||||
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
<预期结果、受影响入口/模块/契约;明确不做什么。>
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | ${rmain} | $(git -C "$wt" rev-parse HEAD) | $(date -u +%FT%TZ) |
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:
|
||||
- 验证:
|
||||
- 影响与剩余项:
|
||||
EOF
|
||||
git -C "$wt" add .task && git -C "$wt" commit -q -m "Record the task card for ${slug}" && echo "[card] $card(已随分支提交)"
|
||||
echo "[started] branch=${name} worktree=${wt} R0=$(echo "$rmain" | cut -c1-10)"
|
||||
}
|
||||
|
||||
cmd_sync() {
|
||||
need_repo
|
||||
git fetch -q origin "${TARGET_BRANCH}"
|
||||
[ "$(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
|
||||
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
|
||||
printf '| 同步前验收 | %s | %s | %s |\n' "$base" "$head" "$(date -u +%FT%TZ)" >> "$card"
|
||||
git add "$card"
|
||||
git commit -q -m "Record the sync checkpoint" || true
|
||||
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 等),证据写入任务卡"
|
||||
}
|
||||
|
||||
cmd_finish() {
|
||||
need_repo
|
||||
local abandon="${1:-}"
|
||||
local branch head common mainrepo
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
head=$(git rev-parse HEAD)
|
||||
common=$(git rev-parse --git-common-dir); common=$(cd "$common" && pwd)
|
||||
mainrepo=$(dirname "$common")
|
||||
git fetch -q origin "${TARGET_BRANCH}" 2>/dev/null || true
|
||||
if [ "$abandon" = "--abandon" ]; then
|
||||
local reason="${2:-}"; [ -n "$reason" ] || die "--abandon 需要原因:task.sh finish --abandon <reason>"
|
||||
local untracked; untracked=$(git status --porcelain | grep -c '^??' || true)
|
||||
[ "$untracked" -gt 0 ] && die "存在 ${untracked} 个未跟踪文件 — 先处置或用 task-finish FORCE_ABANDON=1"
|
||||
[ "${FORCE_ABANDON:-0}" = "1" ] || { echo "[confirm] 放弃未合入工作属破坏性动作:重跑 FORCE_ABANDON=1 task.sh finish --abandon \"${reason}\""; exit 5; }
|
||||
echo "[abandoned] branch=${branch} reason=${reason} recorded_at=$(date -u +%FT%TZ)"
|
||||
else
|
||||
git merge-base --is-ancestor "$head" "origin/${TARGET_BRANCH}" || die "分支未合入 origin/${TARGET_BRANCH}——先走 PR 合入;放弃则用 --abandon <reason>"
|
||||
echo "[verified] ${branch} 已包含于 origin/${TARGET_BRANCH};成果已落盘"
|
||||
fi
|
||||
git -C "$mainrepo" worktree remove "$PWD" && git -C "$mainrepo" branch -d "$branch" 2>/dev/null || git -C "$mainrepo" branch -D "$branch"
|
||||
git -C "$mainrepo" worktree prune
|
||||
echo "[recycled] worktree 与分支 ${branch} 已回收($(date -u +%FT%TZ))"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
precheck) shift; cmd_precheck "$@" ;;
|
||||
start) shift; cmd_start "$@" ;;
|
||||
sync) shift; cmd_sync "$@" ;;
|
||||
finish) shift; cmd_finish "$@" ;;
|
||||
*) echo "usage: task.sh <precheck|start|sync|finish> [args]"; exit 2 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user