- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次) - .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…), 目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪 - .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/) - .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*) - .gitignore 补:备份件(*.bak-*) - 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
160 lines
6.9 KiB
Bash
160 lines
6.9 KiB
Bash
#!/usr/bin/env bash
|
||
# =============================================================================
|
||
# v-neg.sh —— S0 尺子④:负控驱动(**S0 的唯一完成判据**)
|
||
# 规格来源:交付物/功能打包到基础插件-分步实施方案-20260926.md §3.10.1(负控驱动行)· §3.10.2 S0-5
|
||
# -----------------------------------------------------------------------------
|
||
# 断言(两个都成立才 rc=0 绿)
|
||
# ① 改坏靶子一处 ⇒ v-plugin.sh 对应面 **必须红(rc≠0)**
|
||
# ② 改回(用未改动的靶子)⇒ 同一条判据 **必须绿(rc=0)**
|
||
# -----------------------------------------------------------------------------
|
||
# 🔴 铁律:改坏**只动 tmp/ 里的副本** ——
|
||
# ⛔ 不碰任何其他工作区的文件(用户 2026-09-24 明令);⛔ 不改真 profile、⛔ 不改真日志
|
||
# 连跑两次一致的比较对象:见 v-baseline.sh --stable(本脚本只负责红/绿双态)
|
||
# 用法
|
||
# bash v-neg.sh 装配|取数|回调|运行|all
|
||
# =============================================================================
|
||
set -u
|
||
|
||
SELF_DIR="$(cd "$(dirname "$0")" && pwd -W)"
|
||
[ -d "$SELF_DIR" ] || SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
WS="$(cd "$SELF_DIR/../.." && pwd -W)"
|
||
[ -d "$WS" ] || WS="$(cd "$SELF_DIR/../.." && pwd)"
|
||
OUT="${P39_OUT:-$WS/tmp/p39}"
|
||
NEG="$OUT/neg"
|
||
DATE="$(date +%Y%m%d)"
|
||
REC="$OUT/neg-记录-$DATE.txt"
|
||
VPLUGIN="$SELF_DIR/v-plugin.sh"
|
||
|
||
DSH_DESKTOP_HOME="${DSH_DESKTOP_HOME:-E:/github/dsh-desktop-0.1.7rc2/apps/desktop/.desktop-build/development/home-adapt}"
|
||
TARGET="@dsh-client/portal-first-screen"
|
||
PROFILE_JSON="$DSH_DESKTOP_HOME/profiles/desktop/package.json"
|
||
|
||
mkdir -p "$NEG"
|
||
: > "$REC"
|
||
|
||
CASE="${1:-}"
|
||
[ -z "$CASE" ] && { echo "用法: v-neg.sh <装配|取数|回调|运行|all>"; exit 2; }
|
||
|
||
log() { printf '%s\n' "$*" >> "$REC"; }
|
||
|
||
# 跑一条判据,返回 rc;输出同时落记录
|
||
run_face() { # $1=face $2..=额外参数
|
||
local face="$1"; shift
|
||
local tmpf="$OUT/.neg-last.out"
|
||
bash "$VPLUGIN" "$face" "$@" > "$tmpf" 2>&1
|
||
local rc=$?
|
||
cat "$tmpf" >> "$REC"
|
||
printf '%s' "$rc"
|
||
}
|
||
|
||
# 找靶子入口(真件)
|
||
resolve_entry() {
|
||
"$NODE_BIN" -e 'const{createRequire}=require("module");const r=createRequire(process.argv[1]);process.stdout.write(r.resolve(process.argv[2]))' "$PROFILE_JSON" "$TARGET" 2>/dev/null
|
||
}
|
||
NODE_BIN="${NODE:-E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3/node.exe}"
|
||
REAL_ENTRY="$(resolve_entry)"
|
||
REAL_PKG_JSON="$("$NODE_BIN" -e 'const{createRequire}=require("module");const r=createRequire(process.argv[1]);process.stdout.write(r.resolve(process.argv[2]+"/package.json"))' "$PROFILE_JSON" "$TARGET" 2>/dev/null)"
|
||
REAL_PKG_DIR="$(dirname "$REAL_PKG_JSON")"
|
||
|
||
overall=0
|
||
declare -a SUMMARY
|
||
|
||
one_case() { # $1 = case name
|
||
local c="$1"
|
||
local dir="$NEG/$c"; rm -rf "$dir"; mkdir -p "$dir"
|
||
log ""
|
||
log "════ 负控 case【$c】 $(date -Iseconds) ════"
|
||
|
||
local HEALTHY_RC BROKEN_RC=1
|
||
|
||
case "$c" in
|
||
装配)
|
||
# 破坏点(单变量):从 profile 的 dsh.profile.bundles 里删掉靶子那一行
|
||
cp "$PROFILE_JSON" "$dir/package.json"
|
||
"$PY_BIN" - "$dir/package.json" "$TARGET" <<'PYEOF'
|
||
import json, sys
|
||
p, t = sys.argv[1], sys.argv[2]
|
||
d = json.load(open(p, encoding='utf-8'))
|
||
b = d['dsh']['profile']['bundles']
|
||
d['dsh']['profile']['bundles'] = [x for x in b if x != t]
|
||
json.dump(d, open(p, 'w', encoding='utf-8'), indent=2, ensure_ascii=False)
|
||
PYEOF
|
||
# 给副本补上**靶子自身**的最小 node_modules 投影(只复制靶子,不投影整棵树)
|
||
# ⇒ 破坏点收窄为单变量:只有 profile 的 bundles 声明缺了靶子
|
||
STUB="$dir/node_modules/@dsh-client/portal-first-screen"
|
||
mkdir -p "$STUB"
|
||
cp "$REAL_PKG_JSON" "$STUB/package.json"
|
||
cp -r "$REAL_PKG_DIR/lib" "$STUB/lib"
|
||
[ -f "$REAL_PKG_DIR/cordis.patch.yml" ] && cp "$REAL_PKG_DIR/cordis.patch.yml" "$STUB/cordis.patch.yml"
|
||
JUNC="stub"
|
||
log "改坏方式:删掉副本 profile 的 bundles 行;node_modules 投影=已建(stub 只含靶子自身 ⇒ 单变量:只有 bundles 声明这一处被改坏)"
|
||
BROKEN_RC="$(run_face 装配 --profile-json "$dir/package.json")"
|
||
log ""
|
||
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
|
||
;;
|
||
取数)
|
||
cp "$REAL_ENTRY" "$dir/index.js"
|
||
sed -i 's/^export const name = /export const nameQ39broken = /' "$dir/index.js"
|
||
log "改坏方式:副本产物里把导出 name 改名(单变量)"
|
||
BROKEN_RC="$(run_face 取数 --entry "$dir/index.js")"
|
||
log ""
|
||
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
|
||
;;
|
||
回调)
|
||
cp "$REAL_ENTRY" "$dir/index.js"
|
||
sed -i 's/^export function apply(/export function applyQ39broken(/' "$dir/index.js"
|
||
log "改坏方式:副本产物里把 apply 改名(单变量)"
|
||
BROKEN_RC="$(run_face 回调 --entry "$dir/index.js")"
|
||
log ""
|
||
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
|
||
;;
|
||
运行)
|
||
REAL_LOG="$(ls -1t "$DSH_DESKTOP_HOME/logs/"startup-*.log 2>/dev/null | head -1)"
|
||
if [ -z "$REAL_LOG" ]; then
|
||
log "⚠️ 无真启动日志 ⇒ 本 case 未取到(rc=3)"
|
||
SUMMARY+=("$c: 未取到(无真启动日志)")
|
||
overall=1
|
||
return
|
||
fi
|
||
cp "$REAL_LOG" "$dir/fake-failed.log"
|
||
printf "\n Failed plugins (1):\n %s\n Error: [S0 负控构造] 靶子启动失败(⛔ 构造副本,真日志未被改动)\n" "$TARGET" >> "$dir/fake-failed.log"
|
||
log "改坏方式:把真日志**副本**改出一行靶子失败条目(单变量;⛔ 真日志未动)"
|
||
BROKEN_RC="$(run_face 运行 --log "$dir/fake-failed.log")"
|
||
log ""
|
||
log "▶ 改坏后:rc=$BROKEN_RC(期望 ≠0)"
|
||
;;
|
||
esac
|
||
|
||
HEALTHY_RC="$(run_face "$c")"
|
||
log ""
|
||
log "▶ 改回(真靶子):rc=$HEALTHY_RC(期望 0)"
|
||
|
||
local ok=1
|
||
[ "$BROKEN_RC" = "0" ] && ok=0
|
||
[ "$HEALTHY_RC" != "0" ] && ok=0
|
||
if [ "$ok" = "1" ]; then
|
||
log "✅ case【$c】负控通过:改坏 rc=$BROKEN_RC(红)/改回 rc=$HEALTHY_RC(绿)"
|
||
SUMMARY+=("$c: ✅ 通过(改坏 rc=$BROKEN_RC 红 · 改回 rc=$HEALTHY_RC 绿)")
|
||
else
|
||
log "❌ case【$c】负控未通过:改坏 rc=$BROKEN_RC / 改回 rc=$HEALTHY_RC(要求 改坏≠0 且 改回=0)"
|
||
SUMMARY+=("$c: ❌ 未通过(改坏 rc=$BROKEN_RC · 改回 rc=$HEALTHY_RC)")
|
||
overall=1
|
||
fi
|
||
}
|
||
|
||
PY_BIN="${PY:-E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe}"
|
||
[ -z "$REAL_ENTRY" ] || [ -f "$REAL_ENTRY" ] || { echo "⛔ 解析不到真靶子入口($TARGET)⇒ 负控前置不成立"; exit 1; }
|
||
|
||
if [ "$CASE" = "all" ]; then
|
||
for c in 装配 运行 取数 回调; do one_case "$c"; done
|
||
else
|
||
case "$CASE" in 装配|运行|取数|回调) one_case "$CASE" ;; *) echo "⛔ 未知 case:$CASE"; exit 2 ;; esac
|
||
fi
|
||
|
||
echo "════ v-neg 结果 ════"
|
||
for s in "${SUMMARY[@]}"; do echo " $s"; done
|
||
echo "记录(含全部原始输出):$REC"
|
||
[ "$overall" != "0" ] && { echo "⇒ 红:负控未全过(⛔ 不通过负控的判据不算判据)"; exit 1; }
|
||
echo "⇒ 绿:红/绿双态均成立"
|
||
exit 0
|