dev-playbooks-cn 1.0.0
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/LICENSE +21 -0
- package/README.md +466 -0
- package/bin/devbooks.js +987 -0
- package/package.json +43 -0
- package/skills/Skills/344/275/277/347/224/250/350/257/264/346/230/216.md +446 -0
- package/skills/Skill/345/274/200/345/217/221/346/214/207/345/215/227.md +248 -0
- package/skills/_shared/context-detection-template.md +315 -0
- package/skills/_shared/mcp-enhancement-template.md +144 -0
- package/skills/_shared/references//351/200/232/347/224/250/345/256/210/351/227/250/345/215/217/350/256/256.md +114 -0
- package/skills/_template/config-discovery-template.md +126 -0
- package/skills/devbooks-brownfield-bootstrap/SKILL.md +167 -0
- package/skills/devbooks-brownfield-bootstrap/references//344/273/243/347/240/201/345/257/274/350/210/252/347/255/226/347/225/245.md +203 -0
- package/skills/devbooks-brownfield-bootstrap/references//345/255/230/351/207/217/351/241/271/347/233/256/345/210/235/345/247/213/345/214/226.md +96 -0
- package/skills/devbooks-brownfield-bootstrap/references//345/255/230/351/207/217/351/241/271/347/233/256/345/210/235/345/247/213/345/214/226/346/217/220/347/244/272/350/257/215.md +115 -0
- package/skills/devbooks-brownfield-bootstrap/references//346/234/257/350/257/255/350/241/250/346/250/241/346/235/277.md +42 -0
- package/skills/devbooks-brownfield-bootstrap/scripts/cod-update.sh +357 -0
- package/skills/devbooks-brownfield-bootstrap/templates/project-profile-template.md +172 -0
- package/skills/devbooks-c4-map/SKILL.md +151 -0
- package/skills/devbooks-c4-map/references/C4/346/236/266/346/236/204/345/234/260/345/233/276/346/217/220/347/244/272/350/257/215.md +33 -0
- package/skills/devbooks-c4-map/references//345/210/206/345/261/202/347/272/246/346/235/237/346/243/200/346/237/245/346/270/205/345/215/225.md +185 -0
- package/skills/devbooks-code-review/SKILL.md +175 -0
- package/skills/devbooks-code-review/references/PR/346/250/241/346/235/277/344/270/216/346/214/207/345/215/227.md +321 -0
- package/skills/devbooks-code-review/references//344/273/243/347/240/201/350/257/204/345/256/241/346/217/220/347/244/272/350/257/215.md +100 -0
- package/skills/devbooks-code-review/references//345/235/217/345/221/263/351/201/223/351/200/237/346/237/245/350/241/250.md +495 -0
- package/skills/devbooks-code-review/references//350/265/204/346/272/220/347/256/241/347/220/206/345/256/241/346/237/245/346/270/205/345/215/225.md +311 -0
- package/skills/devbooks-coder/SKILL.md +219 -0
- package/skills/devbooks-coder/references//344/273/243/347/240/201/345/256/236/347/216/260/346/217/220/347/244/272/350/257/215.md +70 -0
- package/skills/devbooks-coder/references//344/275/216/351/243/216/351/231/251/346/224/271/345/212/250/346/212/200/346/234/257.md +275 -0
- package/skills/devbooks-coder/references//346/227/245/345/277/227/350/247/204/350/214/203.md +329 -0
- package/skills/devbooks-coder/references//347/274/226/347/240/201/351/243/216/346/240/274/347/273/206/345/210/231.md +351 -0
- package/skills/devbooks-coder/references//351/224/231/350/257/257/347/240/201/350/247/204/350/214/203.md +463 -0
- package/skills/devbooks-delivery-workflow/SKILL.md +217 -0
- package/skills/devbooks-delivery-workflow/references//344/272/244/344/273/230/351/252/214/346/224/266/345/267/245/344/275/234/346/265/201.md +256 -0
- package/skills/devbooks-delivery-workflow/references//345/216/237/345/236/213-/347/224/237/344/272/247/345/217/214/350/275/250/346/250/241/345/274/217.md +168 -0
- package/skills/devbooks-delivery-workflow/references//345/217/230/346/233/264/351/252/214/350/257/201/344/270/216/350/277/275/346/272/257/346/250/241/346/235/277.md +133 -0
- package/skills/devbooks-delivery-workflow/scripts/ac-trace-check.sh +330 -0
- package/skills/devbooks-delivery-workflow/scripts/audit-scope.sh +262 -0
- package/skills/devbooks-delivery-workflow/scripts/change-check.sh +1040 -0
- package/skills/devbooks-delivery-workflow/scripts/change-codemod-scaffold.sh +135 -0
- package/skills/devbooks-delivery-workflow/scripts/change-evidence.sh +152 -0
- package/skills/devbooks-delivery-workflow/scripts/change-scaffold.sh +442 -0
- package/skills/devbooks-delivery-workflow/scripts/change-spec-delta-scaffold.sh +136 -0
- package/skills/devbooks-delivery-workflow/scripts/constitution-check.sh +237 -0
- package/skills/devbooks-delivery-workflow/scripts/env-match-check.sh +128 -0
- package/skills/devbooks-delivery-workflow/scripts/fitness-check.sh +387 -0
- package/skills/devbooks-delivery-workflow/scripts/guardrail-check.sh +519 -0
- package/skills/devbooks-delivery-workflow/scripts/handoff-check.sh +141 -0
- package/skills/devbooks-delivery-workflow/scripts/hygiene-check.sh +340 -0
- package/skills/devbooks-delivery-workflow/scripts/migrate-from-openspec.sh +385 -0
- package/skills/devbooks-delivery-workflow/scripts/migrate-to-v2-gates.sh +202 -0
- package/skills/devbooks-delivery-workflow/scripts/progress-dashboard.sh +319 -0
- package/skills/devbooks-delivery-workflow/scripts/prototype-promote.sh +341 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-preview.sh +203 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-promote.sh +118 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-rollback.sh +124 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-stage.sh +117 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-all.sh +78 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-npm-package.sh +123 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-openspec-free.sh +81 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-slash-commands.sh +146 -0
- package/skills/devbooks-delivery-workflow/templates/handoff.md +50 -0
- package/skills/devbooks-design-backport/SKILL.md +73 -0
- package/skills/devbooks-design-backport/references//345/233/236/345/206/231/350/256/276/350/256/241/346/226/207/346/241/243/346/217/220/347/244/272/350/257/215.md +196 -0
- package/skills/devbooks-design-doc/SKILL.md +121 -0
- package/skills/devbooks-design-doc/references//345/276/256/346/234/215/345/212/241/350/256/276/350/256/241/346/270/205/345/215/225.md +149 -0
- package/skills/devbooks-design-doc/references//350/256/276/350/256/241/346/226/207/346/241/243/346/217/220/347/244/272/350/257/215.md +189 -0
- package/skills/devbooks-design-doc/references//351/232/220/347/247/201/345/220/210/350/247/204/346/243/200/346/237/245/346/270/205/345/215/225.md +240 -0
- package/skills/devbooks-entropy-monitor/SKILL.md +188 -0
- package/skills/devbooks-entropy-monitor/references//347/206/265/345/272/246/351/207/217/346/226/271/346/263/225/350/256/272.md +223 -0
- package/skills/devbooks-entropy-monitor/scripts/entropy-measure.sh +449 -0
- package/skills/devbooks-entropy-monitor/scripts/entropy-report.sh +303 -0
- package/skills/devbooks-entropy-monitor/templates/thresholds.json +99 -0
- package/skills/devbooks-federation/SKILL.md +264 -0
- package/skills/devbooks-federation/scripts/federation-check.sh +144 -0
- package/skills/devbooks-federation/templates/federation.yaml +89 -0
- package/skills/devbooks-impact-analysis/SKILL.md +135 -0
- package/skills/devbooks-impact-analysis/references//345/275/261/345/223/215/345/210/206/346/236/220/346/217/220/347/244/272/350/257/215.md +82 -0
- package/skills/devbooks-impact-analysis/scripts/graph-cache.sh +214 -0
- package/skills/devbooks-implementation-plan/SKILL.md +83 -0
- package/skills/devbooks-implementation-plan/references//347/274/226/347/240/201/350/256/241/345/210/222/346/217/220/347/244/272/350/257/215.md +99 -0
- package/skills/devbooks-index-bootstrap/SKILL.md +240 -0
- package/skills/devbooks-proposal-author/SKILL.md +83 -0
- package/skills/devbooks-proposal-author/references//346/217/220/346/241/210/346/222/260/345/206/231/346/217/220/347/244/272/350/257/215.md +66 -0
- package/skills/devbooks-proposal-challenger/SKILL.md +86 -0
- package/skills/devbooks-proposal-challenger/references//344/274/246/347/220/206/344/270/216/345/220/210/350/247/204/346/243/200/346/237/245/346/270/205/345/215/225.md +176 -0
- package/skills/devbooks-proposal-challenger/references//346/217/220/346/241/210/350/264/250/347/226/221/346/217/220/347/244/272/350/257/215.md +57 -0
- package/skills/devbooks-proposal-debate-workflow/SKILL.md +78 -0
- package/skills/devbooks-proposal-debate-workflow/references//346/217/220/346/241/210/345/257/271/350/276/251/345/267/245/344/275/234/346/265/201.md +24 -0
- package/skills/devbooks-proposal-debate-workflow/references//346/217/220/346/241/210/345/257/271/350/276/251/346/250/241/346/235/277.md +35 -0
- package/skills/devbooks-proposal-debate-workflow/scripts/proposal-debate-check.sh +102 -0
- package/skills/devbooks-proposal-judge/SKILL.md +78 -0
- package/skills/devbooks-proposal-judge/references//346/217/220/346/241/210/350/243/201/345/206/263/346/217/220/347/244/272/350/257/215.md +37 -0
- package/skills/devbooks-router/SKILL.md +346 -0
- package/skills/devbooks-spec-contract/SKILL.md +191 -0
- package/skills/devbooks-spec-contract/references/API/350/256/276/350/256/241/346/214/207/345/215/227.md +349 -0
- package/skills/devbooks-spec-contract/references//345/245/221/347/272/246/344/270/216/346/225/260/346/215/256/345/256/232/344/271/211/346/217/220/347/244/272/350/257/215.md +85 -0
- package/skills/devbooks-spec-contract/references//350/247/204/346/240/274/345/217/230/346/233/264/346/217/220/347/244/272/350/257/215.md +63 -0
- package/skills/devbooks-spec-contract/references//351/232/220/345/274/217/345/217/230/346/233/264/346/243/200/346/265/213/346/217/220/347/244/272/350/257/215.md +183 -0
- package/skills/devbooks-spec-contract/scripts/implicit-change-detect.sh +378 -0
- package/skills/devbooks-spec-gardener/SKILL.md +72 -0
- package/skills/devbooks-spec-gardener/references//350/247/204/346/240/274/345/233/255/344/270/201/346/217/220/347/244/272/350/257/215.md +41 -0
- package/skills/devbooks-test-owner/SKILL.md +172 -0
- package/skills/devbooks-test-owner/references//345/217/230/346/233/264/351/252/214/350/257/201/344/270/216/350/277/275/346/272/257/346/250/241/346/235/277.md +228 -0
- package/skills/devbooks-test-owner/references//345/274/202/346/255/245/347/263/273/347/273/237/346/265/213/350/257/225/347/255/226/347/225/245.md +316 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/344/273/243/347/240/201/346/217/220/347/244/272/350/257/215.md +208 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/345/210/206/345/261/202/347/255/226/347/225/245.md +281 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/351/251/261/345/212/250.md +394 -0
- package/skills/devbooks-test-owner/references//350/247/243/344/276/235/350/265/226/346/212/200/346/234/257/351/200/237/346/237/245/350/241/250.md +432 -0
- package/skills/devbooks-test-reviewer/SKILL.md +189 -0
- package/templates/.devbooks/config.yaml +88 -0
- package/templates/claude-commands/devbooks/apply.md +38 -0
- package/templates/claude-commands/devbooks/archive.md +33 -0
- package/templates/claude-commands/devbooks/backport.md +19 -0
- package/templates/claude-commands/devbooks/bootstrap.md +19 -0
- package/templates/claude-commands/devbooks/c4.md +19 -0
- package/templates/claude-commands/devbooks/challenger.md +19 -0
- package/templates/claude-commands/devbooks/code.md +19 -0
- package/templates/claude-commands/devbooks/debate.md +19 -0
- package/templates/claude-commands/devbooks/delivery.md +19 -0
- package/templates/claude-commands/devbooks/design.md +19 -0
- package/templates/claude-commands/devbooks/entropy.md +19 -0
- package/templates/claude-commands/devbooks/federation.md +19 -0
- package/templates/claude-commands/devbooks/gardener.md +19 -0
- package/templates/claude-commands/devbooks/impact.md +19 -0
- package/templates/claude-commands/devbooks/index.md +19 -0
- package/templates/claude-commands/devbooks/judge.md +19 -0
- package/templates/claude-commands/devbooks/plan.md +19 -0
- package/templates/claude-commands/devbooks/proposal.md +19 -0
- package/templates/claude-commands/devbooks/quick.md +42 -0
- package/templates/claude-commands/devbooks/review.md +19 -0
- package/templates/claude-commands/devbooks/router.md +19 -0
- package/templates/claude-commands/devbooks/spec.md +19 -0
- package/templates/claude-commands/devbooks/test-review.md +19 -0
- package/templates/claude-commands/devbooks/test.md +19 -0
- package/templates/dev-playbooks/README.md +458 -0
- package/templates/dev-playbooks/changes/.gitkeep +1 -0
- package/templates/dev-playbooks/constitution.md +116 -0
- package/templates/dev-playbooks/project.md +96 -0
- package/templates/dev-playbooks/scripts/.gitkeep +1 -0
- package/templates/dev-playbooks/specs/_meta/anti-patterns/.gitkeep +2 -0
- package/templates/dev-playbooks/specs/_meta/glossary.md +47 -0
- package/templates/dev-playbooks/specs/_meta/project-profile.md +79 -0
- package/templates/dev-playbooks/specs/architecture/fitness-rules.md +95 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat >&2 <<EOF
|
|
6
|
+
usage: guardrail-check.sh <change-id> [options]
|
|
7
|
+
|
|
8
|
+
Options:
|
|
9
|
+
--project-root <dir> Project root directory (default: current dir)
|
|
10
|
+
--change-root <dir> Change root directory (default: changes)
|
|
11
|
+
--truth-root <dir> Truth root directory for architecture constraints
|
|
12
|
+
--role <role> Role to check permissions for (coder|test-owner|reviewer)
|
|
13
|
+
--check-lockfile Check if lockfile changes require explicit declaration
|
|
14
|
+
--check-engineering Check if engineering system changes require approval
|
|
15
|
+
--check-layers Check layering constraints (dependency guard)
|
|
16
|
+
--check-cycles Check for circular dependencies
|
|
17
|
+
--check-hotspots Warn if changes touch high-risk hotspots
|
|
18
|
+
-h, --help Show this help message
|
|
19
|
+
|
|
20
|
+
Exit codes:
|
|
21
|
+
0 - All checks passed
|
|
22
|
+
1 - Guardrail violation detected
|
|
23
|
+
2 - Invalid arguments
|
|
24
|
+
3 - Missing required files
|
|
25
|
+
EOF
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if [[ $# -eq 0 ]]; then
|
|
29
|
+
usage
|
|
30
|
+
exit 2
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
34
|
+
usage
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
change_id="$1"
|
|
39
|
+
shift
|
|
40
|
+
|
|
41
|
+
project_root="${DEVBOOKS_PROJECT_ROOT:-$(pwd)}"
|
|
42
|
+
change_root="${DEVBOOKS_CHANGE_ROOT:-changes}"
|
|
43
|
+
truth_root="${DEVBOOKS_TRUTH_ROOT:-}"
|
|
44
|
+
role=""
|
|
45
|
+
check_lockfile=false
|
|
46
|
+
check_engineering=false
|
|
47
|
+
check_layers=false
|
|
48
|
+
check_cycles=false
|
|
49
|
+
check_hotspots=false
|
|
50
|
+
|
|
51
|
+
while [[ $# -gt 0 ]]; do
|
|
52
|
+
case "$1" in
|
|
53
|
+
-h|--help)
|
|
54
|
+
usage
|
|
55
|
+
exit 0
|
|
56
|
+
;;
|
|
57
|
+
--project-root)
|
|
58
|
+
project_root="${2:-}"
|
|
59
|
+
shift 2
|
|
60
|
+
;;
|
|
61
|
+
--change-root)
|
|
62
|
+
change_root="${2:-}"
|
|
63
|
+
shift 2
|
|
64
|
+
;;
|
|
65
|
+
--truth-root)
|
|
66
|
+
truth_root="${2:-}"
|
|
67
|
+
shift 2
|
|
68
|
+
;;
|
|
69
|
+
--role)
|
|
70
|
+
role="${2:-}"
|
|
71
|
+
shift 2
|
|
72
|
+
;;
|
|
73
|
+
--check-lockfile)
|
|
74
|
+
check_lockfile=true
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
--check-engineering)
|
|
78
|
+
check_engineering=true
|
|
79
|
+
shift
|
|
80
|
+
;;
|
|
81
|
+
--check-layers)
|
|
82
|
+
check_layers=true
|
|
83
|
+
shift
|
|
84
|
+
;;
|
|
85
|
+
--check-cycles)
|
|
86
|
+
check_cycles=true
|
|
87
|
+
shift
|
|
88
|
+
;;
|
|
89
|
+
--check-hotspots)
|
|
90
|
+
check_hotspots=true
|
|
91
|
+
shift
|
|
92
|
+
;;
|
|
93
|
+
*)
|
|
94
|
+
usage
|
|
95
|
+
exit 2
|
|
96
|
+
;;
|
|
97
|
+
esac
|
|
98
|
+
done
|
|
99
|
+
|
|
100
|
+
if [[ -z "$project_root" || -z "$change_root" ]]; then
|
|
101
|
+
usage
|
|
102
|
+
exit 2
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
if ! command -v rg >/dev/null 2>&1; then
|
|
106
|
+
echo "error: missing dependency: rg (ripgrep)" >&2
|
|
107
|
+
exit 2
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if [[ "$change_root" = /* ]]; then
|
|
111
|
+
file="${change_root}/${change_id}/verification.md"
|
|
112
|
+
else
|
|
113
|
+
file="${project_root}/${change_root}/${change_id}/verification.md"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if [[ ! -f "$file" ]]; then
|
|
118
|
+
echo "error: missing ${file}" >&2
|
|
119
|
+
exit 2
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Check if guardrail section exists - if not, skip (guardrail review not applicable)
|
|
123
|
+
if ! rg -n "^F\\) Structural Quality Gate Record|^## F\\) Structural Quality Gate" "$file" >/dev/null 2>&1; then
|
|
124
|
+
echo "ok: guardrail section not present (not applicable for ${change_id})"
|
|
125
|
+
exit 0
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
decision_line=$(rg -n "^- Decision and Authorization:" "$file" || true)
|
|
129
|
+
if [[ -z "$decision_line" ]]; then
|
|
130
|
+
echo "error: guardrail section exists but missing '- Decision and Authorization:' line in ${file}" >&2
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
value="$(echo "$decision_line" | sed -E 's/^[0-9]+:- Decision and Authorization: *//')"
|
|
135
|
+
|
|
136
|
+
if [[ -z "$value" || "$value" == "<"* || "$value" == "TBD"* ]]; then
|
|
137
|
+
echo "error: unresolved guardrail decision in ${file}" >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
echo "ok: guardrail decision present for ${change_id}"
|
|
142
|
+
|
|
143
|
+
# =============================================================================
|
|
144
|
+
# Role Permission Checks (inspired by VS Code role permission separation)
|
|
145
|
+
# =============================================================================
|
|
146
|
+
|
|
147
|
+
# Define file patterns forbidden for each role
|
|
148
|
+
declare -A ROLE_FORBIDDEN_PATTERNS
|
|
149
|
+
ROLE_FORBIDDEN_PATTERNS[coder]="tests/|test/|\.test\.|\.spec\.|__tests__|verification\.md"
|
|
150
|
+
ROLE_FORBIDDEN_PATTERNS[test-owner]="" # test-owner can modify test files
|
|
151
|
+
ROLE_FORBIDDEN_PATTERNS[reviewer]=".*" # reviewer should not modify any files
|
|
152
|
+
|
|
153
|
+
# Define sensitive files forbidden for all roles (similar to VS Code engineering system protection)
|
|
154
|
+
SENSITIVE_PATTERNS="\.devbooks/|\.github/workflows/|build/|package-lock\.json|yarn\.lock|pnpm-lock\.yaml|Cargo\.lock"
|
|
155
|
+
|
|
156
|
+
check_role_permissions() {
|
|
157
|
+
local role="$1"
|
|
158
|
+
local change_path="$2"
|
|
159
|
+
|
|
160
|
+
if [[ -z "$role" ]]; then
|
|
161
|
+
return 0
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
echo "info: checking role permissions for '${role}'..."
|
|
165
|
+
|
|
166
|
+
# Get list of changed files (from git diff or change package record)
|
|
167
|
+
local changed_files=""
|
|
168
|
+
if [[ -d "${project_root}/.git" ]]; then
|
|
169
|
+
changed_files=$(cd "$project_root" && git diff --name-only HEAD~1 2>/dev/null || true)
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# If no git, try reading from proposal.md Impact section
|
|
173
|
+
if [[ -z "$changed_files" && -f "${change_path}/proposal.md" ]]; then
|
|
174
|
+
changed_files=$(grep -A 100 "^## Impact" "${change_path}/proposal.md" | grep -E "^\s*-\s+\`" | sed 's/.*`\([^`]*\)`.*/\1/' || true)
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
if [[ -z "$changed_files" ]]; then
|
|
178
|
+
echo "warn: cannot determine changed files, skipping role permission check"
|
|
179
|
+
return 0
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
local forbidden="${ROLE_FORBIDDEN_PATTERNS[$role]:-}"
|
|
183
|
+
local violations=""
|
|
184
|
+
|
|
185
|
+
while IFS= read -r file; do
|
|
186
|
+
[[ -z "$file" ]] && continue
|
|
187
|
+
|
|
188
|
+
# Check role-specific forbidden patterns
|
|
189
|
+
if [[ -n "$forbidden" ]] && echo "$file" | grep -qE "$forbidden"; then
|
|
190
|
+
violations="${violations}\n - ${file} (forbidden for role '${role}')"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Check sensitive files (forbidden for all roles unless explicitly declared)
|
|
194
|
+
if echo "$file" | grep -qE "$SENSITIVE_PATTERNS"; then
|
|
195
|
+
# Check if proposal.md has engineering-system-change tag
|
|
196
|
+
if ! grep -q "engineering-system-change" "${change_path}/proposal.md" 2>/dev/null; then
|
|
197
|
+
violations="${violations}\n - ${file} (sensitive file requires 'engineering-system-change' tag in proposal.md)"
|
|
198
|
+
fi
|
|
199
|
+
fi
|
|
200
|
+
done <<< "$changed_files"
|
|
201
|
+
|
|
202
|
+
if [[ -n "$violations" ]]; then
|
|
203
|
+
echo -e "error: role permission violations detected:${violations}" >&2
|
|
204
|
+
return 1
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
echo "ok: role permissions check passed for '${role}'"
|
|
208
|
+
return 0
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# =============================================================================
|
|
212
|
+
# Lockfile Idempotency Check (inspired by VS Code no-package-lock-changes.yml)
|
|
213
|
+
# =============================================================================
|
|
214
|
+
|
|
215
|
+
check_lockfile_changes() {
|
|
216
|
+
local change_path="$1"
|
|
217
|
+
|
|
218
|
+
echo "info: checking lockfile changes..."
|
|
219
|
+
|
|
220
|
+
local lockfiles="package-lock.json yarn.lock pnpm-lock.yaml Cargo.lock Gemfile.lock poetry.lock"
|
|
221
|
+
local changed_lockfiles=""
|
|
222
|
+
|
|
223
|
+
if [[ -d "${project_root}/.git" ]]; then
|
|
224
|
+
for lockfile in $lockfiles; do
|
|
225
|
+
if cd "$project_root" && git diff --name-only HEAD~1 2>/dev/null | grep -q "^${lockfile}$"; then
|
|
226
|
+
changed_lockfiles="${changed_lockfiles} ${lockfile}"
|
|
227
|
+
fi
|
|
228
|
+
done
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
if [[ -n "$changed_lockfiles" ]]; then
|
|
232
|
+
# Check if proposal.md explicitly declares dependency changes
|
|
233
|
+
if ! grep -qE "(dependency|deps|upgrade|update.*package)" "${change_path}/proposal.md" 2>/dev/null; then
|
|
234
|
+
echo "error: lockfile changes detected (${changed_lockfiles}) but proposal.md does not declare dependency changes" >&2
|
|
235
|
+
echo "hint: add dependency change description to proposal.md or use '--check-lockfile=false'" >&2
|
|
236
|
+
return 1
|
|
237
|
+
fi
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
echo "ok: lockfile check passed"
|
|
241
|
+
return 0
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# =============================================================================
|
|
245
|
+
# Engineering System Change Check
|
|
246
|
+
# =============================================================================
|
|
247
|
+
|
|
248
|
+
check_engineering_changes() {
|
|
249
|
+
local change_path="$1"
|
|
250
|
+
|
|
251
|
+
echo "info: checking engineering system changes..."
|
|
252
|
+
|
|
253
|
+
local eng_patterns="\.devbooks/|\.github/|build/|scripts/|Makefile|gulpfile|webpack\.config|vite\.config|tsconfig|eslint\.config|\.eslintrc"
|
|
254
|
+
local eng_changes=""
|
|
255
|
+
|
|
256
|
+
if [[ -d "${project_root}/.git" ]]; then
|
|
257
|
+
eng_changes=$(cd "$project_root" && git diff --name-only HEAD~1 2>/dev/null | grep -E "$eng_patterns" || true)
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
if [[ -n "$eng_changes" ]]; then
|
|
261
|
+
# Check if proposal.md has engineering-system-change tag
|
|
262
|
+
if ! grep -q "engineering-system-change" "${change_path}/proposal.md" 2>/dev/null; then
|
|
263
|
+
echo "error: engineering system changes detected but proposal.md missing 'engineering-system-change' tag:" >&2
|
|
264
|
+
echo "$eng_changes" | sed 's/^/ - /' >&2
|
|
265
|
+
return 1
|
|
266
|
+
fi
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
echo "ok: engineering system check passed"
|
|
270
|
+
return 0
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# =============================================================================
|
|
274
|
+
# Layering Constraints Check (Dependency Guard)
|
|
275
|
+
# Prevent dependency direction violations (upper layer cannot directly depend on lower layer implementation details)
|
|
276
|
+
# =============================================================================
|
|
277
|
+
|
|
278
|
+
check_layering_constraints() {
|
|
279
|
+
local change_path="$1"
|
|
280
|
+
local constraints_file="${truth_root}/architecture/c4.md"
|
|
281
|
+
|
|
282
|
+
echo "info: checking layering constraints (dependency guard)..."
|
|
283
|
+
|
|
284
|
+
# Skip if no truth_root or constraints file doesn't exist
|
|
285
|
+
if [[ -z "$truth_root" ]]; then
|
|
286
|
+
echo "warn: --truth-root not specified, skipping layering check"
|
|
287
|
+
return 0
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
if [[ ! -f "$constraints_file" ]]; then
|
|
291
|
+
echo "warn: no layering constraints file found at ${constraints_file}, skipping"
|
|
292
|
+
return 0
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
# Get changed files
|
|
296
|
+
local changed_files=""
|
|
297
|
+
if [[ -d "${project_root}/.git" ]]; then
|
|
298
|
+
changed_files=$(cd "$project_root" && git diff --name-only HEAD~1 2>/dev/null || true)
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
if [[ -z "$changed_files" ]]; then
|
|
302
|
+
echo "warn: cannot determine changed files, skipping layering check"
|
|
303
|
+
return 0
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
local violations=""
|
|
307
|
+
|
|
308
|
+
# Parse layering rules from constraints file
|
|
309
|
+
# Format: | base | src/base/ | ... | (none) | platform, domain, ... |
|
|
310
|
+
|
|
311
|
+
# Common layering violation checks
|
|
312
|
+
while IFS= read -r file; do
|
|
313
|
+
[[ -z "$file" ]] && continue
|
|
314
|
+
[[ ! "$file" =~ \.(ts|tsx|js|jsx|py|go|java|rs)$ ]] && continue
|
|
315
|
+
|
|
316
|
+
local file_path="${project_root}/${file}"
|
|
317
|
+
[[ ! -f "$file_path" ]] && continue
|
|
318
|
+
|
|
319
|
+
# Check if base layer imports platform/domain/application/ui
|
|
320
|
+
if [[ "$file" =~ ^src/base/ ]] || [[ "$file" =~ /base/ ]]; then
|
|
321
|
+
if rg -q "from ['\"].*(platform|domain|application|app|ui)/" "$file_path" 2>/dev/null; then
|
|
322
|
+
violations="${violations}\n - ${file}: base layer imports upper layer"
|
|
323
|
+
fi
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Check if common layer imports browser/node specific code
|
|
327
|
+
if [[ "$file" =~ /common/ ]]; then
|
|
328
|
+
if rg -q "from ['\"].*(browser|node)/" "$file_path" 2>/dev/null; then
|
|
329
|
+
violations="${violations}\n - ${file}: common layer imports platform-specific code"
|
|
330
|
+
fi
|
|
331
|
+
# Check if using DOM API
|
|
332
|
+
if rg -q "(document\.|window\.|navigator\.)" "$file_path" 2>/dev/null; then
|
|
333
|
+
violations="${violations}\n - ${file}: common layer uses DOM API"
|
|
334
|
+
fi
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# Check if core imports contrib
|
|
338
|
+
if [[ "$file" =~ /core/ ]] || [[ "$file" =~ /services/ ]]; then
|
|
339
|
+
if rg -q "from ['\"].*contrib/" "$file_path" 2>/dev/null; then
|
|
340
|
+
violations="${violations}\n - ${file}: core imports contrib (violates extension point design)"
|
|
341
|
+
fi
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
done <<< "$changed_files"
|
|
345
|
+
|
|
346
|
+
if [[ -n "$violations" ]]; then
|
|
347
|
+
echo -e "error: layering constraint violations detected:${violations}" >&2
|
|
348
|
+
echo "hint: see ${constraints_file} for allowed dependencies" >&2
|
|
349
|
+
return 1
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
echo "ok: layering constraints check passed"
|
|
353
|
+
return 0
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
# =============================================================================
|
|
357
|
+
# Circular Dependency Check
|
|
358
|
+
# =============================================================================
|
|
359
|
+
|
|
360
|
+
check_circular_dependencies() {
|
|
361
|
+
echo "info: checking for circular dependencies..."
|
|
362
|
+
|
|
363
|
+
# Check if madge tool is available
|
|
364
|
+
if command -v madge >/dev/null 2>&1; then
|
|
365
|
+
local circular=""
|
|
366
|
+
circular=$(cd "$project_root" && madge --circular --warning src/ 2>/dev/null | grep -E "^\s+[a-zA-Z]" || true)
|
|
367
|
+
|
|
368
|
+
if [[ -n "$circular" ]]; then
|
|
369
|
+
echo "error: circular dependencies detected:" >&2
|
|
370
|
+
echo "$circular" | sed 's/^/ /' >&2
|
|
371
|
+
return 1
|
|
372
|
+
fi
|
|
373
|
+
else
|
|
374
|
+
# Fallback: use simple grep to detect common circular patterns
|
|
375
|
+
echo "info: madge not available, using basic circular detection"
|
|
376
|
+
|
|
377
|
+
# Check if files both import each other (simple heuristic)
|
|
378
|
+
if [[ -d "${project_root}/.git" ]]; then
|
|
379
|
+
local changed_files
|
|
380
|
+
changed_files=$(cd "$project_root" && git diff --name-only HEAD~1 2>/dev/null | grep -E '\.(ts|js|tsx|jsx)$' || true)
|
|
381
|
+
|
|
382
|
+
while IFS= read -r file; do
|
|
383
|
+
[[ -z "$file" ]] && continue
|
|
384
|
+
local file_path="${project_root}/${file}"
|
|
385
|
+
[[ ! -f "$file_path" ]] && continue
|
|
386
|
+
|
|
387
|
+
# Get file's imports
|
|
388
|
+
local imports
|
|
389
|
+
imports=$(rg "^import .* from ['\"]\./" "$file_path" 2>/dev/null | sed "s/.*from ['\"]\\([^'\"]*\\)['\"].*/\\1/" || true)
|
|
390
|
+
|
|
391
|
+
# Check if imported files import current file back
|
|
392
|
+
local file_base
|
|
393
|
+
file_base=$(basename "$file" | sed 's/\.[^.]*$//')
|
|
394
|
+
|
|
395
|
+
while IFS= read -r imported; do
|
|
396
|
+
[[ -z "$imported" ]] && continue
|
|
397
|
+
local imported_path="${project_root}/$(dirname "$file")/${imported}"
|
|
398
|
+
[[ "$imported_path" =~ \.ts$ ]] || imported_path="${imported_path}.ts"
|
|
399
|
+
|
|
400
|
+
if [[ -f "$imported_path" ]] && rg -q "from ['\"].*${file_base}['\"]" "$imported_path" 2>/dev/null; then
|
|
401
|
+
echo "warn: potential circular dependency: ${file} <-> ${imported}" >&2
|
|
402
|
+
fi
|
|
403
|
+
done <<< "$imports"
|
|
404
|
+
done <<< "$changed_files"
|
|
405
|
+
fi
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
echo "ok: circular dependency check passed"
|
|
409
|
+
return 0
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
# =============================================================================
|
|
413
|
+
# Hotspot Warning Check
|
|
414
|
+
# Hotspot = High change frequency x High complexity
|
|
415
|
+
# =============================================================================
|
|
416
|
+
|
|
417
|
+
check_hotspot_changes() {
|
|
418
|
+
local change_path="$1"
|
|
419
|
+
local hotspots_file="${truth_root}/architecture/hotspots.md"
|
|
420
|
+
|
|
421
|
+
echo "info: checking if changes touch hotspots..."
|
|
422
|
+
|
|
423
|
+
# If hotspots file exists, read hotspot list from it
|
|
424
|
+
local hotspot_files=""
|
|
425
|
+
if [[ -n "$truth_root" && -f "$hotspots_file" ]]; then
|
|
426
|
+
hotspot_files=$(grep -E "^\| " "$hotspots_file" | grep -v "File\|---" | awk -F'|' '{print $2}' | tr -d ' ' || true)
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# If no hotspots file, try computing from git history
|
|
430
|
+
if [[ -z "$hotspot_files" && -d "${project_root}/.git" ]]; then
|
|
431
|
+
echo "info: no hotspots.md found, computing from git history (top 10 churn files)..."
|
|
432
|
+
hotspot_files=$(cd "$project_root" && git log --oneline --name-only --since="30 days ago" 2>/dev/null | \
|
|
433
|
+
grep -E '\.(ts|tsx|js|jsx|py|go|java|rs)$' | \
|
|
434
|
+
sort | uniq -c | sort -rn | head -10 | awk '{print $2}' || true)
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
if [[ -z "$hotspot_files" ]]; then
|
|
438
|
+
echo "info: no hotspot data available, skipping"
|
|
439
|
+
return 0
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
# Get changed files
|
|
443
|
+
local changed_files=""
|
|
444
|
+
if [[ -d "${project_root}/.git" ]]; then
|
|
445
|
+
changed_files=$(cd "$project_root" && git diff --name-only HEAD~1 2>/dev/null || true)
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
# Check if changed files touch hotspots
|
|
449
|
+
local hotspot_hits=""
|
|
450
|
+
while IFS= read -r changed; do
|
|
451
|
+
[[ -z "$changed" ]] && continue
|
|
452
|
+
if echo "$hotspot_files" | grep -qF "$changed"; then
|
|
453
|
+
hotspot_hits="${hotspot_hits}\n - ${changed}"
|
|
454
|
+
fi
|
|
455
|
+
done <<< "$changed_files"
|
|
456
|
+
|
|
457
|
+
if [[ -n "$hotspot_hits" ]]; then
|
|
458
|
+
echo -e "warn: changes touch high-risk hotspots (high churn x complexity):${hotspot_hits}" >&2
|
|
459
|
+
echo "hint: consider extra review and testing for these files" >&2
|
|
460
|
+
# This is a warning, not an error, does not block merge
|
|
461
|
+
fi
|
|
462
|
+
|
|
463
|
+
echo "ok: hotspot check completed"
|
|
464
|
+
return 0
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
# =============================================================================
|
|
468
|
+
# Run Additional Checks
|
|
469
|
+
# =============================================================================
|
|
470
|
+
|
|
471
|
+
exit_code=0
|
|
472
|
+
|
|
473
|
+
# Role permission check
|
|
474
|
+
if [[ -n "$role" ]]; then
|
|
475
|
+
change_path=$(dirname "$file")
|
|
476
|
+
if ! check_role_permissions "$role" "$change_path"; then
|
|
477
|
+
exit_code=1
|
|
478
|
+
fi
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
# Lockfile check
|
|
482
|
+
if [[ "$check_lockfile" == "true" ]]; then
|
|
483
|
+
change_path=$(dirname "$file")
|
|
484
|
+
if ! check_lockfile_changes "$change_path"; then
|
|
485
|
+
exit_code=1
|
|
486
|
+
fi
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
# Engineering system change check
|
|
490
|
+
if [[ "$check_engineering" == "true" ]]; then
|
|
491
|
+
change_path=$(dirname "$file")
|
|
492
|
+
if ! check_engineering_changes "$change_path"; then
|
|
493
|
+
exit_code=1
|
|
494
|
+
fi
|
|
495
|
+
fi
|
|
496
|
+
|
|
497
|
+
# Layering constraint check (Dependency Guard)
|
|
498
|
+
if [[ "$check_layers" == "true" ]]; then
|
|
499
|
+
change_path=$(dirname "$file")
|
|
500
|
+
if ! check_layering_constraints "$change_path"; then
|
|
501
|
+
exit_code=1
|
|
502
|
+
fi
|
|
503
|
+
fi
|
|
504
|
+
|
|
505
|
+
# Circular dependency check
|
|
506
|
+
if [[ "$check_cycles" == "true" ]]; then
|
|
507
|
+
if ! check_circular_dependencies; then
|
|
508
|
+
exit_code=1
|
|
509
|
+
fi
|
|
510
|
+
fi
|
|
511
|
+
|
|
512
|
+
# Hotspot warning check (warning only, does not affect exit code)
|
|
513
|
+
if [[ "$check_hotspots" == "true" ]]; then
|
|
514
|
+
change_path=$(dirname "$file")
|
|
515
|
+
check_hotspot_changes "$change_path"
|
|
516
|
+
# Hotspots are just warnings, do not affect exit_code
|
|
517
|
+
fi
|
|
518
|
+
|
|
519
|
+
exit $exit_code
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# handoff-check.sh - Verify role handoff has proper confirmation
|
|
3
|
+
#
|
|
4
|
+
# This script checks that handoff.md exists and has proper confirmation
|
|
5
|
+
# signatures from both roles involved in the handoff.
|
|
6
|
+
#
|
|
7
|
+
# Reference: harden-devbooks-quality-gates design.md AC-004
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
usage() {
|
|
12
|
+
cat <<'EOF' >&2
|
|
13
|
+
usage: handoff-check.sh <change-id> [options]
|
|
14
|
+
|
|
15
|
+
Verify role handoff has proper confirmation:
|
|
16
|
+
1. Checks handoff.md exists
|
|
17
|
+
2. Verifies all parties have confirmed (default behavior)
|
|
18
|
+
3. Returns exit code based on verification status
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--project-root <dir> Project root directory (default: pwd)
|
|
22
|
+
--change-root <dir> Change packages root (default: changes)
|
|
23
|
+
--allow-partial Allow partial confirmation (at least one [x])
|
|
24
|
+
-h, --help Show this help message
|
|
25
|
+
|
|
26
|
+
Exit Codes:
|
|
27
|
+
0 - All checks passed
|
|
28
|
+
1 - Check failed
|
|
29
|
+
2 - Usage error
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
handoff-check.sh my-change-001
|
|
33
|
+
handoff-check.sh my-change-001 --change-root dev-playbooks/changes
|
|
34
|
+
handoff-check.sh my-change-001 --allow-partial
|
|
35
|
+
EOF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if [[ $# -eq 0 ]]; then
|
|
39
|
+
usage
|
|
40
|
+
exit 2
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
44
|
+
usage
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
change_id="$1"
|
|
49
|
+
shift
|
|
50
|
+
|
|
51
|
+
project_root="${DEVBOOKS_PROJECT_ROOT:-$(pwd)}"
|
|
52
|
+
change_root="${DEVBOOKS_CHANGE_ROOT:-changes}"
|
|
53
|
+
allow_partial=false
|
|
54
|
+
|
|
55
|
+
while [[ $# -gt 0 ]]; do
|
|
56
|
+
case "$1" in
|
|
57
|
+
-h|--help)
|
|
58
|
+
usage
|
|
59
|
+
exit 0
|
|
60
|
+
;;
|
|
61
|
+
--project-root)
|
|
62
|
+
project_root="${2:-}"
|
|
63
|
+
shift 2
|
|
64
|
+
;;
|
|
65
|
+
--change-root)
|
|
66
|
+
change_root="${2:-}"
|
|
67
|
+
shift 2
|
|
68
|
+
;;
|
|
69
|
+
--allow-partial)
|
|
70
|
+
allow_partial=true
|
|
71
|
+
shift
|
|
72
|
+
;;
|
|
73
|
+
*)
|
|
74
|
+
echo "error: unknown option: $1" >&2
|
|
75
|
+
usage
|
|
76
|
+
exit 2
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
# Validate change-id
|
|
82
|
+
if [[ -z "$change_id" || "$change_id" == "-"* || "$change_id" =~ [[:space:]] ]]; then
|
|
83
|
+
echo "error: invalid change-id: '$change_id'" >&2
|
|
84
|
+
exit 2
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Build paths
|
|
88
|
+
project_root="${project_root%/}"
|
|
89
|
+
change_root="${change_root%/}"
|
|
90
|
+
|
|
91
|
+
if [[ "$change_root" = /* ]]; then
|
|
92
|
+
change_dir="${change_root}/${change_id}"
|
|
93
|
+
else
|
|
94
|
+
change_dir="${project_root}/${change_root}/${change_id}"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
handoff_file="${change_dir}/handoff.md"
|
|
98
|
+
|
|
99
|
+
echo "handoff-check: checking '${change_id}'"
|
|
100
|
+
echo " change-dir: ${change_dir}"
|
|
101
|
+
|
|
102
|
+
# Check change directory exists
|
|
103
|
+
if [[ ! -d "$change_dir" ]]; then
|
|
104
|
+
echo "error: missing change directory: ${change_dir}" >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Check handoff.md exists
|
|
109
|
+
if [[ ! -f "$handoff_file" ]]; then
|
|
110
|
+
echo "error: missing handoff.md: ${handoff_file}" >&2
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Check for confirmation section (supports both Chinese and English)
|
|
115
|
+
if ! grep -qE "Confirmation Signatures|Confirmation|Confirm|确认签名|确认|签名|交接" "$handoff_file" 2>/dev/null; then
|
|
116
|
+
echo "error: handoff.md missing confirmation section" >&2
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Count confirmed checkboxes (lines with [x] or [X])
|
|
121
|
+
confirmed_count=$(grep -cE "^- \[[xX]\]" "$handoff_file" 2>/dev/null) || confirmed_count=0
|
|
122
|
+
unconfirmed_count=$(grep -cE "^- \[ \]" "$handoff_file" 2>/dev/null) || unconfirmed_count=0
|
|
123
|
+
total_count=$((confirmed_count + unconfirmed_count))
|
|
124
|
+
|
|
125
|
+
echo " signatures: ${confirmed_count}/${total_count} confirmed"
|
|
126
|
+
|
|
127
|
+
if [[ "$confirmed_count" -eq 0 ]]; then
|
|
128
|
+
echo "error: no confirmed signatures in handoff.md (need at least one [x])" >&2
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Default: require all parties to confirm
|
|
133
|
+
if [[ "$allow_partial" != true ]]; then
|
|
134
|
+
if [[ "$unconfirmed_count" -gt 0 ]]; then
|
|
135
|
+
echo "error: incomplete signatures - all parties must confirm (${confirmed_count}/${total_count})" >&2
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
echo "ok: handoff verification passed"
|
|
141
|
+
exit 0
|