Files

155 lines
8.5 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# preflight-lock.sh — 【开工门禁】「先判定可锁定范围,确认锁定后再处理」
#
# 用户口径(2026-09-22):
# 「会话执行任务前先判断,当前任务涉及范围是否都可锁定,确认锁定后开始处理」
#
# 与 handoff-guard.sh 的关系:**门禁(本脚本,先判定)→ 抢锁(handoff-guard.sh,后动手)**。
# ⛔ 本脚本**不建任何锁**,只做判定与提示;它**不替换** handoff-guard.sh。
#
# 在 CODEBUDDY.md §6(并发纪律)里是**开工第 0 步**:判定全部可锁 ⇒ 才去 --claim-exec。
#
# 用法:
# bash preflight-lock.sh "<会话名>" <目标文件1> [目标文件2 ...]
#
# 退出码:0 = 全部可锁且已锁(可以开工)|1 = 存在不可锁项(停手)|2 = 用法错误
#
set -uo pipefail
ME="${1:-}"; shift || true
if [ -z "$ME" ] || [ $# -eq 0 ]; then
echo "用法:bash preflight-lock.sh \"<会话名>\" <目标文件...>" >&2
exit 2
fi
# [session-mechanism] roots.env 外置(由 install.py 生成;缺失则回落到按位置推导)
_SM_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_SM_PKG="$(cd "$_SM_HERE/../.." && pwd)"
[ -f "$_SM_PKG/roots.env" ] && . "$_SM_PKG/roots.env"
DOCS_ROOT="${DSH_DOCS_ROOT:-$(cd "$_SM_HERE/.." && pwd)}"
CODE_ROOT="${DSH_CODE_REPO:-${DOCS_ROOT%/*}}" # ⛔ 不留盘符:代码库=文档库的上一级(纯 bash 展开,⛔ 不调 dirname)
WS_ROOT="${DSH_WS_ROOT:-$(pwd)}" # ⛔ 不留盘符:默认=当前工作目录(本文件内未被使用)
LOCKROOT="$DOCS_ROOT/05-交接单/.locks"
# ── 机制层(⛔ 不受 architecture.md 的 src/** 分层管辖,但**全平台共用**)────────
# 判据:这些文件任一改动都会改变**所有会话**的行为 ⇒ 与层④同性质(全局影响)。
# 2026-09-22 实测踩到:首版门禁把本任务自己的 4 个目标文件全判进【D】未归类 ⇒ 误报。
declare -a MECHANISM_RE=(
'^07-scripts/handoff-guard\.sh$' # 全局锁本体
'^07-scripts/preflight-lock\.sh$' # 本门禁自身(同属"全平台共用",2026-10-01 补齐——
# ⛔ 漏登时它会把自己判成【D】未归类 ⇒ 任何人改本文件都开不了工)
'^07-scripts/lock-guard-hook\.py$' # 无锁拒写钩子
'^07-scripts/op-lock\.sh$' # 服务器侧操作锁
'^07-scripts/(skill-load-guard|stop-dialog-guard|session-log-guard|bash-output-guard)\.py$'
# ↑ 本行=**全部宿主钩子脚本**。🔴 **新增钩子必须加进本行**,否则本门禁把它判成
# 【D】未归类 ⇒ **直接拒开工**(2026-10-01 实测:`session-log-guard.py` 与
# **既有**的 `bash-output-guard.py` 都踩过;后者自注册起一直缺登)。
# 同步面还有**两处**:宿主 settings.json hooks 段 + 库内 CODEBUDDY.md §A 那行清单。
'^07-scripts/docs-sync-check\.sh$' # 推送前对账(所有会话共用)
'^07-scripts/check-layering\.mjs$' # 分层 ratchet
'^07-scripts/layering-baseline\.json$'
'^state\.py$' # 所有会话的开机第 0 步
'^CODEBUDDY\.md$' # 每会话自动加载的规则
'\.workbuddy/skills/' # 🔴 **技能包全体**(2026-10-01 补登)。判据:任何技能都被
# **任意会话**加载 ⇒ 改它=改全平台行为,与上面几条同性质。
# ⛔ 漏登时会发生什么(本轮实测):技能包**不在**
# `ai1net-dsh-server/`/`dsh-server-docs/` 等工作区根下 ⇒
# 下面的"剥根前缀"全剥不掉 ⇒ 落【D】未归类 ⇒ 门禁报
# 「先定域(R2)」—— **提示词本身是错的**(技能包根本不可域锁,
# 正解是"独占")。⇒ 换个会话改本技能包,一律开不了工。
)
# ── 不可锁资源清单(命中 ⇒ 必须走"秒级独占"或串行排队)──────────────
# 判据来源:architecture.md R1–R4 层号 + docs/tsconfig.json outDir
declare -a NONLOCKABLE_RE=(
'^src/(config|crypto|isolation|index)\.ts$' # 层④ 零业务依赖 · 全层依赖
'^src/db/schema\.ts$' # 迁移号 = 全局单调序列
'^src/web/server\.ts$' # 唯一挂载注册点
'^package\.json$' # 依赖账本单一
'^07-scripts/layering-baseline\.json$' # 分层 ratchet 单一
)
declare -a SHARED_RE=(
'^lib/' # 构建产物(outDir=lib)· 全局独占
'^node_modules/'
)
warn_shared=(); need_skeleton=(); ok_files=(); unknown=(); mechanism=()
for f in "$@"; do
rel="${f//\\//}"
# 剥掉已知根前缀(顺序要紧:先长后短,否则残留中间段 ⇒ 正则永不匹配)
rel="${rel#*ai1net-dsh-server/}"
rel="${rel#*aliyun-dsh-server/}"
rel="${rel#*dsh-server-docs/}"
rel="${rel#*dsh_shenxian/}"
rel="${rel#./}"
matched=0
for re in "${MECHANISM_RE[@]}"; do
if printf '%s' "$rel" | grep -qE "$re"; then mechanism+=("$rel"); matched=1; break; fi
done
[ "$matched" = 1 ] && continue
for re in "${NONLOCKABLE_RE[@]}"; do
if printf '%s' "$rel" | grep -qE "$re"; then need_skeleton+=("$rel"); matched=1; break; fi
done
[ "$matched" = 1 ] && continue
for re in "${SHARED_RE[@]}"; do
if printf '%s' "$rel" | grep -qE "$re"; then warn_shared+=("$rel"); matched=1; break; fi
done
[ "$matched" = 1 ] && continue
if printf '%s' "$rel" | grep -qE '^(src|poc|test|web|docs|scripts|交接单|skills)/'; then
ok_files+=("$rel")
else
unknown+=("$rel")
fi
done
echo "══════════ 开工门禁 · 可锁定范围判定 ══════════"
echo "会话:$ME"
echo
echo "【A】可独立锁定(域锁,全程持有):${#ok_files[@]} 个"
for f in "${ok_files[@]:0:8}"; do echo " ✓ $f"; done
[ ${#ok_files[@]} -gt 8 ] && echo " …还有 $(( ${#ok_files[@]} - 8 )) 个"
echo
echo "【B】不可全程锁定(层④/迁移号/唯一注册点)→ 改走**秒级独占**:${#need_skeleton[@]} 个"
for f in "${need_skeleton[@]}"; do echo " ⚠ $f"; done
[ ${#need_skeleton[@]} -eq 0 ] && echo " (无)"
echo
echo "【E】机制层(⛔ 不受 src 分层管辖,但**全平台共用** ⇒ 与层④同性质):${#mechanism[@]} 个"
for f in "${mechanism[@]}"; do echo " ◆ $f"; done
[ ${#mechanism[@]} -eq 0 ] && echo " (无)"
[ ${#mechanism[@]} -gt 0 ] && echo " ⚠ 改这些 = 改所有会话的行为 ⇒ 必须: ①独占②兼容旧行为③改后跑自证"
echo
echo "【C】共享资源(构建产物 / 依赖树)→ 纳入发布锁:${#warn_shared[@]} 个"
for f in "${warn_shared[@]:0:4}"; do echo " ⚠ $f"; done
[ ${#warn_shared[@]} -eq 0 ] && echo " (无)"
if [ ${#unknown[@]} -gt 0 ]; then
echo
echo "【D】未归类路径(⛔ 必须先想清"属哪个域",R2):${#unknown[@]} 个"
for f in "${unknown[@]:0:6}"; do echo " ? $f"; done
fi
echo
echo "════════════════════════════════════════════"
rc=0
[ ${#unknown[@]} -gt 0 ] && rc=1
[ ${#mechanism[@]} -gt 0 ] && rc=1
if [ "$rc" = 1 ]; then
echo "结论:**暂不可并行开工** ——"
[ ${#unknown[@]} -gt 0 ] && echo " · 【D】${#unknown[@]} 个未归类路径:先定域(R2)"
if [ ${#mechanism[@]} -gt 0 ]; then
echo " · 【E】${#mechanism[@]} 个机制层文件:**全平台共用** ⇒ ⛔ 不得与其他会话并行"
echo " ⇒ 仅当**确认无其他会话在跑**时,才允许独占开工;否则停手等窗口"
fi
exit 1
fi
echo "结论:**可以锁定** ——"
echo " · ${#ok_files[@]} 个走域锁|${#need_skeleton[@]} 个走秒级独占|${#warn_shared[@]} 个并发布锁"
echo " · 下一步:bash handoff-guard.sh --claim-exec \"$ME\" --domains <由 A 组反推的域>"
echo " ⛔ 本脚本是**提示性门禁**:hook 只拦 Write/Edit,Bash 写入不受限(有意安全阀)"
exit 0