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,1040 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'EOF' >&2
|
|
6
|
+
usage: change-check.sh <change-id> [--mode <proposal|apply|review|archive|strict>] [--role <test-owner|coder|reviewer>] [--project-root <dir>] [--change-root <dir>] [--truth-root <dir>]
|
|
7
|
+
|
|
8
|
+
Defaults (can be overridden by flags or env):
|
|
9
|
+
DEVBOOKS_PROJECT_ROOT: pwd
|
|
10
|
+
DEVBOOKS_CHANGE_ROOT: changes
|
|
11
|
+
DEVBOOKS_TRUTH_ROOT: specs
|
|
12
|
+
|
|
13
|
+
Notes:
|
|
14
|
+
- Use --change-root and --truth-root to customize paths for your project layout.
|
|
15
|
+
- "strict" is meant for archive-ready packages: tasks complete, decision approved, trace matrix not TODO, etc.
|
|
16
|
+
EOF
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if [[ $# -eq 0 ]]; then
|
|
20
|
+
usage
|
|
21
|
+
exit 2
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
25
|
+
usage
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
change_id="$1"
|
|
30
|
+
shift
|
|
31
|
+
|
|
32
|
+
mode="${DEVBOOKS_MODE:-proposal}"
|
|
33
|
+
role="${DEVBOOKS_ROLE:-}"
|
|
34
|
+
project_root="${DEVBOOKS_PROJECT_ROOT:-$(pwd)}"
|
|
35
|
+
change_root="${DEVBOOKS_CHANGE_ROOT:-changes}"
|
|
36
|
+
truth_root="${DEVBOOKS_TRUTH_ROOT:-specs}"
|
|
37
|
+
|
|
38
|
+
while [[ $# -gt 0 ]]; do
|
|
39
|
+
case "$1" in
|
|
40
|
+
-h|--help)
|
|
41
|
+
usage
|
|
42
|
+
exit 0
|
|
43
|
+
;;
|
|
44
|
+
--mode)
|
|
45
|
+
mode="${2:-}"
|
|
46
|
+
shift 2
|
|
47
|
+
;;
|
|
48
|
+
--role)
|
|
49
|
+
role="${2:-}"
|
|
50
|
+
shift 2
|
|
51
|
+
;;
|
|
52
|
+
--project-root)
|
|
53
|
+
project_root="${2:-}"
|
|
54
|
+
shift 2
|
|
55
|
+
;;
|
|
56
|
+
--change-root)
|
|
57
|
+
change_root="${2:-}"
|
|
58
|
+
shift 2
|
|
59
|
+
;;
|
|
60
|
+
--truth-root)
|
|
61
|
+
truth_root="${2:-}"
|
|
62
|
+
shift 2
|
|
63
|
+
;;
|
|
64
|
+
*)
|
|
65
|
+
usage
|
|
66
|
+
exit 2
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
if [[ -z "$change_id" || "$change_id" == "-"* || "$change_id" =~ [[:space:]] ]]; then
|
|
72
|
+
echo "error: invalid change-id: '$change_id'" >&2
|
|
73
|
+
exit 2
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
case "$mode" in
|
|
77
|
+
proposal|apply|review|archive|strict) ;;
|
|
78
|
+
*)
|
|
79
|
+
echo "error: invalid --mode: '$mode'" >&2
|
|
80
|
+
usage
|
|
81
|
+
exit 2
|
|
82
|
+
;;
|
|
83
|
+
esac
|
|
84
|
+
|
|
85
|
+
case "$role" in
|
|
86
|
+
""|test-owner|coder|reviewer) ;;
|
|
87
|
+
*)
|
|
88
|
+
echo "error: invalid --role: '$role'" >&2
|
|
89
|
+
usage
|
|
90
|
+
exit 2
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
|
|
94
|
+
if ! command -v rg >/dev/null 2>&1; then
|
|
95
|
+
echo "error: missing dependency: rg (ripgrep)" >&2
|
|
96
|
+
exit 2
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
project_root="${project_root%/}"
|
|
100
|
+
change_root="${change_root%/}"
|
|
101
|
+
truth_root="${truth_root%/}"
|
|
102
|
+
|
|
103
|
+
if [[ "$change_root" = /* ]]; then
|
|
104
|
+
change_dir="${change_root}/${change_id}"
|
|
105
|
+
else
|
|
106
|
+
change_dir="${project_root}/${change_root}/${change_id}"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
if [[ "$truth_root" = /* ]]; then
|
|
110
|
+
truth_dir="${truth_root}"
|
|
111
|
+
else
|
|
112
|
+
truth_dir="${project_root}/${truth_root}"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
proposal_file="${change_dir}/proposal.md"
|
|
116
|
+
design_file="${change_dir}/design.md"
|
|
117
|
+
tasks_file="${change_dir}/tasks.md"
|
|
118
|
+
verification_file="${change_dir}/verification.md"
|
|
119
|
+
specs_dir="${change_dir}/specs"
|
|
120
|
+
|
|
121
|
+
errors=0
|
|
122
|
+
warnings=0
|
|
123
|
+
|
|
124
|
+
err() {
|
|
125
|
+
echo "error: $*" >&2
|
|
126
|
+
errors=$((errors + 1))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
warn() {
|
|
130
|
+
echo "warn: $*" >&2
|
|
131
|
+
warnings=$((warnings + 1))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
require_file() {
|
|
135
|
+
local file="$1"
|
|
136
|
+
if [[ ! -f "$file" ]]; then
|
|
137
|
+
err "missing file: ${file}"
|
|
138
|
+
return 1
|
|
139
|
+
fi
|
|
140
|
+
return 0
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
contains_placeholder() {
|
|
144
|
+
local file="$1"
|
|
145
|
+
# Pattern includes intentional Chinese quotes for detecting placeholders
|
|
146
|
+
# shellcheck disable=SC2140
|
|
147
|
+
if rg -n '<change-id>|<truth-root>|<change-root>|<one-line-goal>|<one-sentence goal>|<one-sentence-goal>|<capability>|<you>|YYYY-MM-DD|<session/agent>|<fill "none"|TODO\b' "$file" >/dev/null; then
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
return 1
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# =============================================================================
|
|
154
|
+
# Shared SKIP-APPROVED detection helper (DRY refactoring)
|
|
155
|
+
# Checks if a task at index has SKIP-APPROVED on prev/same/next line
|
|
156
|
+
# Usage: is_skip_approved "line" "prev_line" "next_line" [strict_html_comment]
|
|
157
|
+
# Note: Uses positional params instead of nameref for bash 3.2 compatibility
|
|
158
|
+
# =============================================================================
|
|
159
|
+
is_skip_approved() {
|
|
160
|
+
local line="$1"
|
|
161
|
+
local prev_line="${2:-}"
|
|
162
|
+
local next_line="${3:-}"
|
|
163
|
+
local strict_html="${4:-false}"
|
|
164
|
+
|
|
165
|
+
# Check same line
|
|
166
|
+
if [[ "$line" =~ SKIP-APPROVED: ]]; then
|
|
167
|
+
return 0
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Check previous line
|
|
171
|
+
if [[ -n "$prev_line" ]]; then
|
|
172
|
+
if [[ "$strict_html" == true ]]; then
|
|
173
|
+
if [[ "$prev_line" =~ \<\!--[[:space:]]*SKIP-APPROVED: ]]; then
|
|
174
|
+
return 0
|
|
175
|
+
fi
|
|
176
|
+
else
|
|
177
|
+
if [[ "$prev_line" =~ SKIP-APPROVED: ]]; then
|
|
178
|
+
return 0
|
|
179
|
+
fi
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Check next line
|
|
184
|
+
if [[ -n "$next_line" ]]; then
|
|
185
|
+
if [[ "$strict_html" == true ]]; then
|
|
186
|
+
if [[ "$next_line" =~ \<\!--[[:space:]]*SKIP-APPROVED: ]]; then
|
|
187
|
+
return 0
|
|
188
|
+
fi
|
|
189
|
+
else
|
|
190
|
+
if [[ "$next_line" =~ SKIP-APPROVED: ]]; then
|
|
191
|
+
return 0
|
|
192
|
+
fi
|
|
193
|
+
fi
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
return 1
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# =============================================================================
|
|
200
|
+
# Shared file reading helper (DRY refactoring)
|
|
201
|
+
# Reads file into global _LINES array and sets _LINE_COUNT
|
|
202
|
+
# Usage: _read_file_to_lines "$file_path"
|
|
203
|
+
# After call: use _LINES array and _LINE_COUNT variable
|
|
204
|
+
# =============================================================================
|
|
205
|
+
_LINES=()
|
|
206
|
+
_LINE_COUNT=0
|
|
207
|
+
|
|
208
|
+
_read_file_to_lines() {
|
|
209
|
+
local file="$1"
|
|
210
|
+
_LINES=()
|
|
211
|
+
_LINE_COUNT=0
|
|
212
|
+
|
|
213
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
214
|
+
_LINES+=("$line")
|
|
215
|
+
done < "$file"
|
|
216
|
+
|
|
217
|
+
_LINE_COUNT=${#_LINES[@]}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Helper: Get prev/next line context for iteration
|
|
221
|
+
# Usage: _get_line_context $index
|
|
222
|
+
# Sets: _PREV_LINE, _NEXT_LINE
|
|
223
|
+
_PREV_LINE=""
|
|
224
|
+
_NEXT_LINE=""
|
|
225
|
+
|
|
226
|
+
_get_line_context() {
|
|
227
|
+
local i=$1
|
|
228
|
+
_PREV_LINE=""
|
|
229
|
+
_NEXT_LINE=""
|
|
230
|
+
|
|
231
|
+
[[ $i -gt 0 ]] && _PREV_LINE="${_LINES[$((i-1))]}"
|
|
232
|
+
[[ $((i+1)) -lt $_LINE_COUNT ]] && _NEXT_LINE="${_LINES[$((i+1))]}"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
check_proposal() {
|
|
236
|
+
require_file "$proposal_file" || return 0
|
|
237
|
+
|
|
238
|
+
# Support both numbered (## 1. Why...) and unnumbered (## Why...) headings
|
|
239
|
+
for h in "Why" "What Changes" "Impact" "Risks" "Validation" "Debate Packet" "Decision Log"; do
|
|
240
|
+
if ! rg -n "^## [0-9]*\\.? *${h}" "$proposal_file" >/dev/null; then
|
|
241
|
+
err "proposal missing heading containing '${h}': ${proposal_file}"
|
|
242
|
+
fi
|
|
243
|
+
done
|
|
244
|
+
|
|
245
|
+
if ! rg -n "^- (Value Signal and Observation|价值信号与观测口径)[::]" "$proposal_file" >/dev/null; then
|
|
246
|
+
if [[ "$mode" == "strict" ]]; then
|
|
247
|
+
err "proposal missing '- Value Signal and Observation:' (strict): ${proposal_file}"
|
|
248
|
+
else
|
|
249
|
+
warn "proposal missing '- Value Signal and Observation:' (recommended): ${proposal_file}"
|
|
250
|
+
fi
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
if ! rg -n "^- (Value Stream Bottleneck Hypothesis|价值流瓶颈假设)[::]?" "$proposal_file" >/dev/null; then
|
|
254
|
+
if [[ "$mode" == "strict" ]]; then
|
|
255
|
+
err "proposal missing '- Value Stream Bottleneck Hypothesis...' (strict): ${proposal_file}"
|
|
256
|
+
else
|
|
257
|
+
warn "proposal missing '- Value Stream Bottleneck Hypothesis...' (recommended): ${proposal_file}"
|
|
258
|
+
fi
|
|
259
|
+
fi
|
|
260
|
+
local decision_line
|
|
261
|
+
decision_line=$(rg -n "^- (Decision Status|Decision|决策状态|决策)[::] *(Pending|Approved|Revise|Rejected)\b" "$proposal_file" -m 1 || true)
|
|
262
|
+
if [[ -z "$decision_line" ]]; then
|
|
263
|
+
err "proposal missing decision line (e.g., '- Decision Status: Approved' or '- 决策状态: Approved'): ${proposal_file}"
|
|
264
|
+
return 0
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
local value
|
|
268
|
+
value="$(echo "$decision_line" | sed -E 's/^[0-9]+:- (Decision Status|Decision|决策状态|决策)[::] *//')"
|
|
269
|
+
|
|
270
|
+
case "$value" in
|
|
271
|
+
Pending|Approved|Revise|Rejected) ;;
|
|
272
|
+
*)
|
|
273
|
+
err "proposal has invalid decision status '${value}': ${proposal_file}"
|
|
274
|
+
;;
|
|
275
|
+
esac
|
|
276
|
+
|
|
277
|
+
if [[ "$mode" == "apply" || "$mode" == "archive" || "$mode" == "strict" ]]; then
|
|
278
|
+
if [[ "$value" != "Approved" ]]; then
|
|
279
|
+
err "proposal decision status must be Approved for ${mode}: ${proposal_file}"
|
|
280
|
+
fi
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
if [[ "$mode" == "strict" ]]; then
|
|
284
|
+
if contains_placeholder "$proposal_file"; then
|
|
285
|
+
err "proposal contains placeholders/TODO (strict): ${proposal_file}"
|
|
286
|
+
fi
|
|
287
|
+
fi
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
check_design() {
|
|
291
|
+
if [[ ! -f "$design_file" ]]; then
|
|
292
|
+
if [[ "$mode" == "proposal" ]]; then
|
|
293
|
+
warn "missing design.md (recommended for non-trivial changes): ${design_file}"
|
|
294
|
+
return 0
|
|
295
|
+
fi
|
|
296
|
+
err "missing design.md: ${design_file}"
|
|
297
|
+
return 0
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# Support headings with annotations: ## Acceptance Criteria
|
|
301
|
+
if ! rg -n "^## Acceptance Criteria" "$design_file" >/dev/null; then
|
|
302
|
+
err "design missing '## Acceptance Criteria' heading: ${design_file}"
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
if ! rg -n "AC-[0-9]{3}" "$design_file" >/dev/null; then
|
|
306
|
+
err "design missing any AC-xxx items: ${design_file}"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# ============================================================================
|
|
310
|
+
# Design pattern reference: Problem Context / Rationale / Trade-offs required
|
|
311
|
+
# ============================================================================
|
|
312
|
+
if [[ "$mode" == "apply" || "$mode" == "archive" || "$mode" == "strict" ]]; then
|
|
313
|
+
if ! rg -n "^## Problem Context" "$design_file" >/dev/null; then
|
|
314
|
+
if [[ "$mode" == "strict" ]]; then
|
|
315
|
+
err "design missing '## Problem Context' section (strict): ${design_file}"
|
|
316
|
+
else
|
|
317
|
+
warn "design missing '## Problem Context' section (recommended): ${design_file}"
|
|
318
|
+
fi
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
if ! rg -n "^## Design Rationale" "$design_file" >/dev/null; then
|
|
322
|
+
if [[ "$mode" == "strict" ]]; then
|
|
323
|
+
err "design missing '## Design Rationale' section (strict): ${design_file}"
|
|
324
|
+
else
|
|
325
|
+
warn "design missing '## Design Rationale' section (recommended): ${design_file}"
|
|
326
|
+
fi
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
if ! rg -n "^## Trade-offs" "$design_file" >/dev/null; then
|
|
330
|
+
if [[ "$mode" == "strict" ]]; then
|
|
331
|
+
err "design missing '## Trade-offs' section (strict): ${design_file}"
|
|
332
|
+
else
|
|
333
|
+
warn "design missing '## Trade-offs' section (recommended): ${design_file}"
|
|
334
|
+
fi
|
|
335
|
+
fi
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
# ============================================================================
|
|
339
|
+
# Design pattern reference: Encapsulate variation points check (in design principles)
|
|
340
|
+
# ============================================================================
|
|
341
|
+
if [[ "$mode" == "strict" ]]; then
|
|
342
|
+
if ! rg -n "Variation Point|Encapsulate.*Vari" "$design_file" >/dev/null; then
|
|
343
|
+
warn "design may not have identified variation points (strict): ${design_file}"
|
|
344
|
+
fi
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
if [[ "$mode" == "strict" ]]; then
|
|
348
|
+
if contains_placeholder "$design_file"; then
|
|
349
|
+
err "design contains placeholders/TODO (strict): ${design_file}"
|
|
350
|
+
fi
|
|
351
|
+
fi
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
check_tasks() {
|
|
355
|
+
require_file "$tasks_file" || return 0
|
|
356
|
+
|
|
357
|
+
if ! rg -n "Main Plan Area|主线计划区" "$tasks_file" >/dev/null; then
|
|
358
|
+
err "tasks missing Main Plan Area/主线计划区: ${tasks_file}"
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
if ! rg -n "Context Switch Breakpoint Area|断点区" "$tasks_file" >/dev/null; then
|
|
362
|
+
err "tasks missing Context Switch Breakpoint Area/断点区: ${tasks_file}"
|
|
363
|
+
fi
|
|
364
|
+
|
|
365
|
+
if ! rg -n "^- \\[[ xX]\\]" "$tasks_file" >/dev/null; then
|
|
366
|
+
err "tasks missing checkbox items '- [ ]'/'- [x]': ${tasks_file}"
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
if [[ "$mode" == "archive" || "$mode" == "strict" ]]; then
|
|
370
|
+
# Check for unchecked items that are NOT skip-approved
|
|
371
|
+
local has_unapproved_unchecked=false
|
|
372
|
+
_read_file_to_lines "$tasks_file"
|
|
373
|
+
|
|
374
|
+
for ((i=0; i<_LINE_COUNT; i++)); do
|
|
375
|
+
local line="${_LINES[$i]}"
|
|
376
|
+
if [[ "$line" =~ ^-\ \[\ \] ]]; then
|
|
377
|
+
_get_line_context "$i"
|
|
378
|
+
# Found unchecked task, check if skip-approved using shared helper
|
|
379
|
+
if ! is_skip_approved "$line" "$_PREV_LINE" "$_NEXT_LINE"; then
|
|
380
|
+
has_unapproved_unchecked=true
|
|
381
|
+
break
|
|
382
|
+
fi
|
|
383
|
+
fi
|
|
384
|
+
done
|
|
385
|
+
|
|
386
|
+
if [[ "$has_unapproved_unchecked" == true ]]; then
|
|
387
|
+
err "tasks still contains unchecked items without skip approval (archive/strict): ${tasks_file}"
|
|
388
|
+
fi
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
if [[ "$mode" == "strict" ]]; then
|
|
392
|
+
if contains_placeholder "$tasks_file"; then
|
|
393
|
+
err "tasks contains placeholders/TODO (strict): ${tasks_file}"
|
|
394
|
+
fi
|
|
395
|
+
fi
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
extract_ac_ids() {
|
|
399
|
+
local file="$1"
|
|
400
|
+
rg -o "AC-[0-9]{3}" "$file" 2>/dev/null | sort -u || true
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
check_verification() {
|
|
404
|
+
if [[ ! -f "$verification_file" ]]; then
|
|
405
|
+
if [[ "$mode" == "proposal" ]]; then
|
|
406
|
+
warn "missing verification.md (expected during apply by test-owner): ${verification_file}"
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
err "missing verification.md: ${verification_file}"
|
|
410
|
+
return 0
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
for h in "A\) (Test Plan Directive Table|测试计划指令表)" "B\) (Traceability Matrix|追溯矩阵)" "C\) (Execution Anchors|执行锚点)" "D\) (MANUAL-\* Checklist|MANUAL-\* 清单)"; do
|
|
414
|
+
if ! rg -n "${h}" "$verification_file" >/dev/null; then
|
|
415
|
+
err "verification missing section '${h}': ${verification_file}"
|
|
416
|
+
fi
|
|
417
|
+
done
|
|
418
|
+
|
|
419
|
+
if ! rg -n "^\\| AC-[0-9]{3} \\|" "$verification_file" >/dev/null; then
|
|
420
|
+
err "verification trace matrix missing any AC rows: ${verification_file}"
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
if [[ "$mode" == "strict" ]]; then
|
|
424
|
+
# G) section is recommended but not blocking
|
|
425
|
+
if ! rg -n "^(## )?G\) (Value Stream and Metrics|价值流与度量)" "$verification_file" >/dev/null; then
|
|
426
|
+
warn "verification missing 'G) Value Stream and Metrics' section (recommended for strict): ${verification_file}"
|
|
427
|
+
else
|
|
428
|
+
# Only check Target Value Signal if G) section exists
|
|
429
|
+
if ! rg -n "^- (Target Value Signal|目标价值信号)[::]" "$verification_file" >/dev/null; then
|
|
430
|
+
warn "verification missing '- Target Value Signal:' line (recommended): ${verification_file}"
|
|
431
|
+
fi
|
|
432
|
+
fi
|
|
433
|
+
|
|
434
|
+
if rg -n "^\\| AC-[0-9]{3} \\|.*\\| *TODO *\\|" "$verification_file" >/dev/null; then
|
|
435
|
+
err "verification trace matrix still has TODO rows (strict): ${verification_file}"
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
if contains_placeholder "$verification_file"; then
|
|
439
|
+
err "verification contains placeholders/TODO (strict): ${verification_file}"
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
if [[ -f "$design_file" ]]; then
|
|
443
|
+
design_acs="$(extract_ac_ids "$design_file")"
|
|
444
|
+
verification_acs="$(extract_ac_ids "$verification_file")"
|
|
445
|
+
while IFS= read -r ac; do
|
|
446
|
+
[[ -n "$ac" ]] || continue
|
|
447
|
+
if ! printf "%s\n" "$verification_acs" | rg -x "$ac" >/dev/null; then
|
|
448
|
+
err "verification missing AC '${ac}' from design: ${verification_file}"
|
|
449
|
+
fi
|
|
450
|
+
done <<<"$design_acs"
|
|
451
|
+
fi
|
|
452
|
+
|
|
453
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
454
|
+
if [[ -x "${script_dir}/guardrail-check.sh" ]]; then
|
|
455
|
+
if ! "${script_dir}/guardrail-check.sh" "$change_id" --project-root "$project_root" --change-root "$change_root" >/dev/null; then
|
|
456
|
+
err "guardrail-check failed (strict): ${verification_file}"
|
|
457
|
+
fi
|
|
458
|
+
else
|
|
459
|
+
warn "missing guardrail-check.sh; skipping guardrail check"
|
|
460
|
+
fi
|
|
461
|
+
fi
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
check_spec_deltas() {
|
|
465
|
+
if [[ ! -d "$specs_dir" ]]; then
|
|
466
|
+
return 0
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
local found_any=false
|
|
470
|
+
while IFS= read -r file; do
|
|
471
|
+
[[ -n "$file" ]] || continue
|
|
472
|
+
found_any=true
|
|
473
|
+
if ! rg -n "^## (ADDED|MODIFIED|REMOVED) Requirements$" "$file" >/dev/null; then
|
|
474
|
+
err "spec delta missing '## ADDED|MODIFIED|REMOVED Requirements' headings: ${file}"
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
awk '
|
|
478
|
+
BEGIN { req = 0; scen = 0; bad = 0; }
|
|
479
|
+
/^### Requirement:/ {
|
|
480
|
+
if (req > 0 && scen == 0) { bad++; }
|
|
481
|
+
req++; scen = 0;
|
|
482
|
+
}
|
|
483
|
+
/^#### Scenario:/ { scen++; }
|
|
484
|
+
END {
|
|
485
|
+
if (req > 0 && scen == 0) { bad++; }
|
|
486
|
+
if (req == 0) { print "NO_REQUIREMENTS"; exit 0; }
|
|
487
|
+
if (bad > 0) { print "BAD_REQUIREMENTS " bad; exit 0; }
|
|
488
|
+
print "OK";
|
|
489
|
+
}
|
|
490
|
+
' "$file" | {
|
|
491
|
+
read -r result rest || true
|
|
492
|
+
if [[ "$result" == "NO_REQUIREMENTS" ]]; then
|
|
493
|
+
err "spec delta has no '### Requirement:' entries: ${file}"
|
|
494
|
+
elif [[ "$result" == "BAD_REQUIREMENTS" ]]; then
|
|
495
|
+
err "spec delta has Requirement(s) without Scenario (count=${rest}): ${file}"
|
|
496
|
+
fi
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if [[ "$mode" == "strict" ]]; then
|
|
500
|
+
if rg -n "<change-id>|<truth-root>|<change-root>|<capability>|TBD\\b|TODO\\b" "$file" >/dev/null; then
|
|
501
|
+
err "spec delta contains placeholders/TBD/TODO (strict): ${file}"
|
|
502
|
+
fi
|
|
503
|
+
fi
|
|
504
|
+
done < <(find "$specs_dir" -type f -name "spec.md" 2>/dev/null | sort)
|
|
505
|
+
|
|
506
|
+
if [[ "$found_any" == false ]]; then
|
|
507
|
+
return 0
|
|
508
|
+
fi
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
# =============================================================================
|
|
512
|
+
# Constitution Check (DevBooks 2.0)
|
|
513
|
+
# Verify project constitution is present and valid
|
|
514
|
+
# =============================================================================
|
|
515
|
+
check_constitution() {
|
|
516
|
+
# Only run in strict mode
|
|
517
|
+
if [[ "$mode" != "strict" ]]; then
|
|
518
|
+
return 0
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
# Skip for non-DevBooks projects (e.g., minimal CI fixtures in tests/)
|
|
522
|
+
# DevBooks projects usually have dev-playbooks/ (or devbooks/) or a .devbooks/config.yaml.
|
|
523
|
+
if [[ ! -d "${project_root}/dev-playbooks" && ! -d "${project_root}/devbooks" && ! -f "${project_root}/.devbooks/config.yaml" ]]; then
|
|
524
|
+
return 0
|
|
525
|
+
fi
|
|
526
|
+
|
|
527
|
+
# Find constitution-check.sh
|
|
528
|
+
local script_dir
|
|
529
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
530
|
+
local constitution_script="${script_dir}/constitution-check.sh"
|
|
531
|
+
|
|
532
|
+
if [[ ! -x "$constitution_script" ]]; then
|
|
533
|
+
warn "constitution-check.sh not found; skipping constitution check"
|
|
534
|
+
return 0
|
|
535
|
+
fi
|
|
536
|
+
|
|
537
|
+
# Run constitution check
|
|
538
|
+
echo " constitution check..."
|
|
539
|
+
if ! "$constitution_script" "$project_root" --quiet 2>/dev/null; then
|
|
540
|
+
err "constitution check failed (strict): project constitution missing or invalid"
|
|
541
|
+
return 0
|
|
542
|
+
fi
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# =============================================================================
|
|
546
|
+
# Fitness Check (DevBooks 2.0)
|
|
547
|
+
# Verify architecture fitness rules
|
|
548
|
+
# =============================================================================
|
|
549
|
+
check_fitness() {
|
|
550
|
+
# Only run in apply/archive/strict modes
|
|
551
|
+
if [[ "$mode" != "apply" && "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
552
|
+
return 0
|
|
553
|
+
fi
|
|
554
|
+
|
|
555
|
+
# Find fitness-check.sh
|
|
556
|
+
local script_dir
|
|
557
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
558
|
+
local fitness_script="${script_dir}/fitness-check.sh"
|
|
559
|
+
|
|
560
|
+
if [[ ! -x "$fitness_script" ]]; then
|
|
561
|
+
warn "fitness-check.sh not found; skipping fitness check"
|
|
562
|
+
return 0
|
|
563
|
+
fi
|
|
564
|
+
|
|
565
|
+
# Determine fitness mode based on change-check mode
|
|
566
|
+
local fitness_mode="warn"
|
|
567
|
+
if [[ "$mode" == "strict" ]]; then
|
|
568
|
+
fitness_mode="error"
|
|
569
|
+
fi
|
|
570
|
+
|
|
571
|
+
# Run fitness check
|
|
572
|
+
echo " fitness check (mode=${fitness_mode})..."
|
|
573
|
+
if ! "$fitness_script" --project-root "$project_root" --mode "$fitness_mode" 2>/dev/null; then
|
|
574
|
+
if [[ "$fitness_mode" == "error" ]]; then
|
|
575
|
+
err "fitness check failed (strict): architecture fitness check failed"
|
|
576
|
+
else
|
|
577
|
+
warn "fitness check warnings detected"
|
|
578
|
+
fi
|
|
579
|
+
fi
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
# =============================================================================
|
|
583
|
+
# AC-001: Green Evidence Closure Check (Mythical Man-Month Ch.6 "Communication")
|
|
584
|
+
# Block archive when Green evidence is missing
|
|
585
|
+
# =============================================================================
|
|
586
|
+
check_evidence_closure() {
|
|
587
|
+
# Only run in archive/strict modes
|
|
588
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
589
|
+
return 0
|
|
590
|
+
fi
|
|
591
|
+
|
|
592
|
+
local evidence_dir="${change_dir}/evidence/green-final"
|
|
593
|
+
|
|
594
|
+
if [[ ! -d "$evidence_dir" ]]; then
|
|
595
|
+
err "Missing Green evidence: evidence/green-final/ does not exist (AC-001)"
|
|
596
|
+
return 0
|
|
597
|
+
fi
|
|
598
|
+
|
|
599
|
+
# Check if directory has at least one file
|
|
600
|
+
local file_count
|
|
601
|
+
file_count=$(find "$evidence_dir" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
602
|
+
if [[ "$file_count" -eq 0 ]]; then
|
|
603
|
+
err "Missing Green evidence: evidence/green-final/ is empty (AC-001)"
|
|
604
|
+
return 0
|
|
605
|
+
fi
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
# =============================================================================
|
|
609
|
+
# AC-002: Task Completion Rate Check (Enhanced)
|
|
610
|
+
# Enforce 100% task completion in strict mode with rate display
|
|
611
|
+
# Skip-approved tasks count as completed
|
|
612
|
+
# =============================================================================
|
|
613
|
+
check_task_completion_rate() {
|
|
614
|
+
# Only run in strict mode for completion rate
|
|
615
|
+
if [[ "$mode" != "strict" ]]; then
|
|
616
|
+
return 0
|
|
617
|
+
fi
|
|
618
|
+
|
|
619
|
+
if [[ ! -f "$tasks_file" ]]; then
|
|
620
|
+
return 0
|
|
621
|
+
fi
|
|
622
|
+
|
|
623
|
+
# Read file using shared helper
|
|
624
|
+
_read_file_to_lines "$tasks_file"
|
|
625
|
+
|
|
626
|
+
local total_tasks=0
|
|
627
|
+
local completed_tasks=0
|
|
628
|
+
|
|
629
|
+
for ((i=0; i<_LINE_COUNT; i++)); do
|
|
630
|
+
local line="${_LINES[$i]}"
|
|
631
|
+
|
|
632
|
+
# Count all checkbox items (both checked and unchecked)
|
|
633
|
+
if [[ "$line" =~ ^-\ \[[\ xX]\] ]]; then
|
|
634
|
+
total_tasks=$((total_tasks + 1))
|
|
635
|
+
|
|
636
|
+
# Check if completed (checked)
|
|
637
|
+
if [[ "$line" =~ ^-\ \[[xX]\] ]]; then
|
|
638
|
+
completed_tasks=$((completed_tasks + 1))
|
|
639
|
+
elif [[ "$line" =~ ^-\ \[\ \] ]]; then
|
|
640
|
+
_get_line_context "$i"
|
|
641
|
+
# Unchecked - check if skip-approved using shared helper
|
|
642
|
+
if is_skip_approved "$line" "$_PREV_LINE" "$_NEXT_LINE"; then
|
|
643
|
+
completed_tasks=$((completed_tasks + 1))
|
|
644
|
+
fi
|
|
645
|
+
fi
|
|
646
|
+
fi
|
|
647
|
+
done
|
|
648
|
+
|
|
649
|
+
if [[ "$total_tasks" -eq 0 ]]; then
|
|
650
|
+
# No tasks = 100% complete
|
|
651
|
+
return 0
|
|
652
|
+
fi
|
|
653
|
+
|
|
654
|
+
local incomplete_tasks=$((total_tasks - completed_tasks))
|
|
655
|
+
if [[ "$incomplete_tasks" -gt 0 ]]; then
|
|
656
|
+
local rate=$((completed_tasks * 100 / total_tasks))
|
|
657
|
+
err "Task completion rate ${rate}% (${completed_tasks}/${total_tasks}), need 100% (AC-002)"
|
|
658
|
+
fi
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
# =============================================================================
|
|
662
|
+
# AC-007: Test Failure in Evidence Check
|
|
663
|
+
# Block archive when test failures exist in Green evidence
|
|
664
|
+
# Pattern is designed to avoid false positives like "0 tests FAIL" or comments
|
|
665
|
+
# =============================================================================
|
|
666
|
+
check_test_failure_in_evidence() {
|
|
667
|
+
# Only run in archive/strict modes
|
|
668
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
669
|
+
return 0
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
local evidence_dir="${change_dir}/evidence/green-final"
|
|
673
|
+
|
|
674
|
+
if [[ ! -d "$evidence_dir" ]]; then
|
|
675
|
+
# Already checked in check_evidence_closure
|
|
676
|
+
return 0
|
|
677
|
+
fi
|
|
678
|
+
|
|
679
|
+
# Check for failure patterns in evidence files (use --no-ignore to search .log files)
|
|
680
|
+
# Patterns designed for common test frameworks:
|
|
681
|
+
# - TAP: "not ok" at line start
|
|
682
|
+
# - Jest/Vitest: "FAIL " followed by path
|
|
683
|
+
# - pytest: "FAILED " at line start
|
|
684
|
+
# - Go: "--- FAIL:" at line start
|
|
685
|
+
# - BATS: "not ok" at line start
|
|
686
|
+
# - Generic: "FAIL:" "FAILED:" "[FAIL]" "[FAILED]" "[ERROR]" markers
|
|
687
|
+
# Exclude: "0 tests FAIL", "0 failed", comments, variable names
|
|
688
|
+
local fail_pattern='^not ok |^FAIL[: ]|^FAILED[: ]|^--- FAIL:|^\[FAIL\]|^\[FAILED\]|^\[ERROR\]|^ERROR:|: FAIL$|: FAILED$'
|
|
689
|
+
|
|
690
|
+
if rg --no-ignore -l "$fail_pattern" "$evidence_dir" >/dev/null 2>&1; then
|
|
691
|
+
# Double-check: exclude files that only have success patterns
|
|
692
|
+
local fail_files
|
|
693
|
+
fail_files=$(rg --no-ignore -l "$fail_pattern" "$evidence_dir" 2>/dev/null || true)
|
|
694
|
+
|
|
695
|
+
for file in $fail_files; do
|
|
696
|
+
# Check if the match is a real failure (not "0 tests failed" pattern)
|
|
697
|
+
if rg --no-ignore "$fail_pattern" "$file" 2>/dev/null | grep -qvE "^[[:space:]]*#|0 (tests?|failures?|failed)"; then
|
|
698
|
+
err "Test failure: Green evidence contains failure pattern, cannot archive (AC-007)"
|
|
699
|
+
echo " File: $file" >&2
|
|
700
|
+
return 0
|
|
701
|
+
fi
|
|
702
|
+
done
|
|
703
|
+
fi
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
# =============================================================================
|
|
707
|
+
# AC-005: P0 Skip Approval Check
|
|
708
|
+
# Enforce P0 task skip requires SKIP-APPROVED comment
|
|
709
|
+
# SKIP-APPROVED can be on the line before, same line, or line after the task
|
|
710
|
+
# =============================================================================
|
|
711
|
+
check_skip_approval() {
|
|
712
|
+
# Only run in strict mode
|
|
713
|
+
if [[ "$mode" != "strict" ]]; then
|
|
714
|
+
return 0
|
|
715
|
+
fi
|
|
716
|
+
|
|
717
|
+
if [[ ! -f "$tasks_file" ]]; then
|
|
718
|
+
return 0
|
|
719
|
+
fi
|
|
720
|
+
|
|
721
|
+
# Read file using shared helper
|
|
722
|
+
_read_file_to_lines "$tasks_file"
|
|
723
|
+
|
|
724
|
+
for ((i=0; i<_LINE_COUNT; i++)); do
|
|
725
|
+
local line="${_LINES[$i]}"
|
|
726
|
+
|
|
727
|
+
# Check for unchecked P0 task
|
|
728
|
+
if [[ "$line" =~ ^-\ \[\ \]\ \[P0\] ]]; then
|
|
729
|
+
local p0_task_name
|
|
730
|
+
p0_task_name=$(echo "$line" | sed -E 's/^- \[ \] \[P0\] //')
|
|
731
|
+
|
|
732
|
+
_get_line_context "$i"
|
|
733
|
+
# Use shared helper with strict_html=true for prev/next line HTML comment check
|
|
734
|
+
if ! is_skip_approved "$line" "$_PREV_LINE" "$_NEXT_LINE" true; then
|
|
735
|
+
err "P0 task skip requires approval: ${p0_task_name} (AC-005)"
|
|
736
|
+
fi
|
|
737
|
+
fi
|
|
738
|
+
done
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
# =============================================================================
|
|
742
|
+
# AC-006: Environment Match Check
|
|
743
|
+
# Call env-match-check.sh to verify test environment declaration
|
|
744
|
+
# =============================================================================
|
|
745
|
+
check_env_match() {
|
|
746
|
+
# Only run in archive/strict modes
|
|
747
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
748
|
+
return 0
|
|
749
|
+
fi
|
|
750
|
+
|
|
751
|
+
# Check if env-match-check.sh exists
|
|
752
|
+
local script_dir
|
|
753
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
754
|
+
local env_check_script="${script_dir}/env-match-check.sh"
|
|
755
|
+
|
|
756
|
+
if [[ ! -x "$env_check_script" ]]; then
|
|
757
|
+
warn "env-match-check.sh not found; skipping environment check"
|
|
758
|
+
return 0
|
|
759
|
+
fi
|
|
760
|
+
|
|
761
|
+
# Run env-match-check
|
|
762
|
+
if ! "$env_check_script" "$change_id" --project-root "$project_root" --change-root "$change_root" >/dev/null 2>&1; then
|
|
763
|
+
err "Test environment declaration missing: verification.md needs 'Test Environment Declaration' section (AC-006)"
|
|
764
|
+
fi
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
# =============================================================================
|
|
768
|
+
# AC-008: Documentation Impact Check
|
|
769
|
+
# Verify documentation impact is declared and fulfilled
|
|
770
|
+
# =============================================================================
|
|
771
|
+
check_docs_impact() {
|
|
772
|
+
# Only run in archive/strict modes
|
|
773
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
774
|
+
return 0
|
|
775
|
+
fi
|
|
776
|
+
|
|
777
|
+
if [[ ! -f "$design_file" ]]; then
|
|
778
|
+
return 0
|
|
779
|
+
fi
|
|
780
|
+
|
|
781
|
+
# Check if Documentation Impact section exists
|
|
782
|
+
if ! rg -n "^## Documentation Impact" "$design_file" >/dev/null; then
|
|
783
|
+
warn "design.md missing '## Documentation Impact' section (recommended)"
|
|
784
|
+
return 0
|
|
785
|
+
fi
|
|
786
|
+
|
|
787
|
+
# Check for "no update needed" declaration - if checked, skip further checks
|
|
788
|
+
if rg -n "^\- \[x\] This change is internal refactoring|^\- \[x\] This change only fixes" "$design_file" >/dev/null; then
|
|
789
|
+
# Declared as no doc update needed, skip
|
|
790
|
+
return 0
|
|
791
|
+
fi
|
|
792
|
+
|
|
793
|
+
# Check for P0 documentation updates that are NOT checked in the checklist
|
|
794
|
+
# Look for unchecked P0 items in the table
|
|
795
|
+
local has_p0_docs=false
|
|
796
|
+
if rg -n "\| P0 \|" "$design_file" >/dev/null 2>&1; then
|
|
797
|
+
has_p0_docs=true
|
|
798
|
+
fi
|
|
799
|
+
|
|
800
|
+
if [[ "$has_p0_docs" == true ]]; then
|
|
801
|
+
# Check if documentation update checklist items are completed
|
|
802
|
+
local unchecked_items
|
|
803
|
+
unchecked_items=$(rg -n "^\- \[ \] New script|^\- \[ \] New config|^\- \[ \] New workflow|^\- \[ \] API" "$design_file" || true)
|
|
804
|
+
|
|
805
|
+
if [[ -n "$unchecked_items" && "$mode" == "strict" ]]; then
|
|
806
|
+
err "Documentation update checklist has incomplete items (AC-008)"
|
|
807
|
+
echo " Incomplete items:" >&2
|
|
808
|
+
printf "%s\n" "$unchecked_items" | head -3 | sed 's/^/ /' >&2
|
|
809
|
+
fi
|
|
810
|
+
fi
|
|
811
|
+
|
|
812
|
+
# In strict mode, verify declared docs are actually modified
|
|
813
|
+
if [[ "$mode" == "strict" ]] && command -v git >/dev/null 2>&1; then
|
|
814
|
+
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
815
|
+
# Extract declared P0 docs from design.md
|
|
816
|
+
local declared_docs
|
|
817
|
+
declared_docs=$(rg -o "\| (README\.md|docs/[^|]+\.md|CHANGELOG\.md) \|" "$design_file" 2>/dev/null | sed 's/| //g; s/ |//g' | sort -u || true)
|
|
818
|
+
|
|
819
|
+
if [[ -n "$declared_docs" ]]; then
|
|
820
|
+
# Get changed files
|
|
821
|
+
local changed_files
|
|
822
|
+
changed_files=$(git -C "$project_root" diff --name-only HEAD 2>/dev/null || git -C "$project_root" diff --name-only 2>/dev/null || true)
|
|
823
|
+
|
|
824
|
+
while IFS= read -r doc; do
|
|
825
|
+
[[ -n "$doc" ]] || continue
|
|
826
|
+
# Check if the declared doc is in the changed files
|
|
827
|
+
if ! printf "%s\n" "$changed_files" | grep -qF "$doc"; then
|
|
828
|
+
warn "Declared to update but not modified: $doc (recommend checking)"
|
|
829
|
+
fi
|
|
830
|
+
done <<< "$declared_docs"
|
|
831
|
+
fi
|
|
832
|
+
fi
|
|
833
|
+
fi
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
# =============================================================================
|
|
837
|
+
# AC-003: Role Boundary Check (Enhanced from check_no_tests_changed)
|
|
838
|
+
# Enforce role-specific file modification boundaries
|
|
839
|
+
# Refactored: split into per-role helper functions for maintainability
|
|
840
|
+
# =============================================================================
|
|
841
|
+
|
|
842
|
+
# Helper: Report role violation with changed files list (DRY extraction)
|
|
843
|
+
_report_role_violation() {
|
|
844
|
+
local role_name="$1" forbidden_target="$2" changed_list="$3"
|
|
845
|
+
err "Role violation: ${role_name} cannot modify ${forbidden_target} (AC-003)"
|
|
846
|
+
echo " Detected changes:" >&2
|
|
847
|
+
printf "%s\n" "$changed_list" | head -5 | sed 's/^/ /' >&2
|
|
848
|
+
if [[ $(printf "%s\n" "$changed_list" | wc -l) -gt 5 ]]; then
|
|
849
|
+
echo " ... and more" >&2
|
|
850
|
+
fi
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
# Helper: Check Coder role boundaries
|
|
854
|
+
_check_coder_boundaries() {
|
|
855
|
+
local changed="$1"
|
|
856
|
+
|
|
857
|
+
# Coder cannot modify tests/**
|
|
858
|
+
local tests_changed
|
|
859
|
+
tests_changed=$(printf "%s\n" "$changed" | grep -E "^tests/" || true)
|
|
860
|
+
if [[ -n "$tests_changed" ]]; then
|
|
861
|
+
_report_role_violation "Coder" "tests/**" "$tests_changed"
|
|
862
|
+
fi
|
|
863
|
+
|
|
864
|
+
# Coder cannot modify verification.md
|
|
865
|
+
if printf "%s\n" "$changed" | grep -qE "verification\.md$"; then
|
|
866
|
+
err "Role violation: Coder cannot modify verification.md (AC-003)"
|
|
867
|
+
fi
|
|
868
|
+
|
|
869
|
+
# Coder cannot modify .devbooks/ config
|
|
870
|
+
if printf "%s\n" "$changed" | grep -qE "^\.devbooks/"; then
|
|
871
|
+
err "Role violation: Coder cannot modify .devbooks/ config (AC-003)"
|
|
872
|
+
fi
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
# Helper: Check Test Owner role boundaries
|
|
876
|
+
_check_test_owner_boundaries() {
|
|
877
|
+
local changed="$1"
|
|
878
|
+
|
|
879
|
+
# Test Owner cannot modify src/**
|
|
880
|
+
local src_changed
|
|
881
|
+
src_changed=$(printf "%s\n" "$changed" | grep -E "^src/" || true)
|
|
882
|
+
if [[ -n "$src_changed" ]]; then
|
|
883
|
+
_report_role_violation "Test Owner" "src/**" "$src_changed"
|
|
884
|
+
fi
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
# Helper: Check Reviewer role boundaries
|
|
888
|
+
_check_reviewer_boundaries() {
|
|
889
|
+
local changed="$1"
|
|
890
|
+
|
|
891
|
+
# Reviewer cannot modify any code files
|
|
892
|
+
local code_changed
|
|
893
|
+
code_changed=$(printf "%s\n" "$changed" | grep -E "\.(ts|js|tsx|jsx|py|sh)$" || true)
|
|
894
|
+
if [[ -n "$code_changed" ]]; then
|
|
895
|
+
_report_role_violation "Reviewer" "code files" "$code_changed"
|
|
896
|
+
fi
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
check_role_boundaries() {
|
|
900
|
+
# Only run when role is specified or in apply mode
|
|
901
|
+
if [[ -z "$role" && "$mode" != "apply" ]]; then
|
|
902
|
+
return 0
|
|
903
|
+
fi
|
|
904
|
+
|
|
905
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
906
|
+
warn "git not found; cannot enforce role boundaries"
|
|
907
|
+
return 0
|
|
908
|
+
fi
|
|
909
|
+
|
|
910
|
+
if ! git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
911
|
+
warn "not a git worktree; cannot enforce role boundaries"
|
|
912
|
+
return 0
|
|
913
|
+
fi
|
|
914
|
+
|
|
915
|
+
# Get changed files
|
|
916
|
+
local changed
|
|
917
|
+
changed="$(
|
|
918
|
+
{
|
|
919
|
+
git -C "$project_root" diff --name-only
|
|
920
|
+
git -C "$project_root" diff --cached --name-only
|
|
921
|
+
} | sort -u
|
|
922
|
+
)"
|
|
923
|
+
|
|
924
|
+
if [[ -z "$changed" ]]; then
|
|
925
|
+
return 0
|
|
926
|
+
fi
|
|
927
|
+
|
|
928
|
+
# Dispatch to role-specific helper
|
|
929
|
+
case "$role" in
|
|
930
|
+
coder) _check_coder_boundaries "$changed" ;;
|
|
931
|
+
test-owner) _check_test_owner_boundaries "$changed" ;;
|
|
932
|
+
reviewer) _check_reviewer_boundaries "$changed" ;;
|
|
933
|
+
esac
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
# Backward compatibility alias
|
|
937
|
+
check_no_tests_changed() {
|
|
938
|
+
check_role_boundaries
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
# ============================================================================
|
|
942
|
+
# Implicit Change Detection (Mythical Man-Month Ch.7 "Tower of Babel")
|
|
943
|
+
# ============================================================================
|
|
944
|
+
check_implicit_changes() {
|
|
945
|
+
# Only run in apply/archive/strict modes
|
|
946
|
+
if [[ "$mode" != "apply" && "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
947
|
+
return 0
|
|
948
|
+
fi
|
|
949
|
+
|
|
950
|
+
local implicit_report="${change_dir}/evidence/implicit-changes.json"
|
|
951
|
+
|
|
952
|
+
# Check if implicit-change-detect.sh is available (now in devbooks-spec-contract)
|
|
953
|
+
local detect_script
|
|
954
|
+
detect_script="$(dirname "$0")/../devbooks-spec-contract/scripts/implicit-change-detect.sh"
|
|
955
|
+
if [[ ! -x "$detect_script" ]]; then
|
|
956
|
+
# Try alternate location
|
|
957
|
+
detect_script="$(dirname "$0")/../../devbooks-spec-contract/scripts/implicit-change-detect.sh"
|
|
958
|
+
fi
|
|
959
|
+
|
|
960
|
+
# If report doesn't exist and we can run detection, suggest it
|
|
961
|
+
if [[ ! -f "$implicit_report" ]]; then
|
|
962
|
+
if [[ -x "$detect_script" ]]; then
|
|
963
|
+
warn "missing implicit change detection report: ${implicit_report}"
|
|
964
|
+
warn "hint: run 'implicit-change-detect.sh ${change_id} --project-root ${project_root} --change-root ${change_root}'"
|
|
965
|
+
fi
|
|
966
|
+
return 0
|
|
967
|
+
fi
|
|
968
|
+
|
|
969
|
+
# Parse the report
|
|
970
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
971
|
+
warn "jq not found; cannot parse implicit-changes.json"
|
|
972
|
+
return 0
|
|
973
|
+
fi
|
|
974
|
+
|
|
975
|
+
local total_implicit
|
|
976
|
+
total_implicit=$(jq -r '.summary.total // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
977
|
+
|
|
978
|
+
if [[ "$total_implicit" -eq 0 ]]; then
|
|
979
|
+
return 0
|
|
980
|
+
fi
|
|
981
|
+
|
|
982
|
+
echo " implicit changes detected: ${total_implicit}"
|
|
983
|
+
|
|
984
|
+
# In strict mode, check if high-risk changes are declared in design.md
|
|
985
|
+
if [[ "$mode" == "strict" ]]; then
|
|
986
|
+
local dep_count cfg_count bld_count
|
|
987
|
+
dep_count=$(jq -r '.summary.dependency // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
988
|
+
cfg_count=$(jq -r '.summary.config // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
989
|
+
bld_count=$(jq -r '.summary.build // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
990
|
+
|
|
991
|
+
if [[ "$dep_count" -gt 0 || "$cfg_count" -gt 0 || "$bld_count" -gt 0 ]]; then
|
|
992
|
+
# Check if design.md exists and mentions these changes
|
|
993
|
+
if [[ ! -f "$design_file" ]]; then
|
|
994
|
+
err "implicit changes detected but design.md missing (strict): review ${implicit_report}"
|
|
995
|
+
else
|
|
996
|
+
# For strict mode, we just warn - full declaration check would need more sophisticated parsing
|
|
997
|
+
warn "implicit changes detected (${total_implicit}): verify these are declared in design.md"
|
|
998
|
+
warn " dependencies: ${dep_count}, config: ${cfg_count}, build: ${bld_count}"
|
|
999
|
+
warn " report: ${implicit_report}"
|
|
1000
|
+
fi
|
|
1001
|
+
fi
|
|
1002
|
+
else
|
|
1003
|
+
# For non-strict modes, just warn
|
|
1004
|
+
warn "implicit changes detected (${total_implicit}): review ${implicit_report}"
|
|
1005
|
+
fi
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
echo "devbooks: checking change '${change_id}' (mode=${mode}${role:+, role=${role}})"
|
|
1009
|
+
echo " change-dir: ${change_dir}"
|
|
1010
|
+
echo " truth-dir: ${truth_dir}"
|
|
1011
|
+
|
|
1012
|
+
if [[ ! -d "$change_dir" ]]; then
|
|
1013
|
+
err "missing change directory: ${change_dir}"
|
|
1014
|
+
else
|
|
1015
|
+
check_proposal
|
|
1016
|
+
check_design
|
|
1017
|
+
check_tasks
|
|
1018
|
+
check_spec_deltas
|
|
1019
|
+
check_verification
|
|
1020
|
+
check_no_tests_changed
|
|
1021
|
+
check_implicit_changes
|
|
1022
|
+
# DevBooks 2.0: Constitution check
|
|
1023
|
+
check_constitution # Constitution validity check (strict mode)
|
|
1024
|
+
# DevBooks 2.0: Fitness check
|
|
1025
|
+
check_fitness # Architecture fitness check (apply/archive/strict)
|
|
1026
|
+
# New quality gates (harden-devbooks-quality-gates)
|
|
1027
|
+
check_evidence_closure # AC-001: Green evidence required for archive
|
|
1028
|
+
check_task_completion_rate # AC-002: 100% completion for strict mode
|
|
1029
|
+
check_test_failure_in_evidence # AC-007: No failures in Green evidence
|
|
1030
|
+
check_skip_approval # AC-005: P0 skip requires approval
|
|
1031
|
+
check_env_match # AC-006: Environment declaration required
|
|
1032
|
+
check_docs_impact # AC-008: Documentation impact declared and fulfilled
|
|
1033
|
+
fi
|
|
1034
|
+
|
|
1035
|
+
if [[ $errors -gt 0 ]]; then
|
|
1036
|
+
echo "fail: ${errors} error(s), ${warnings} warning(s)" >&2
|
|
1037
|
+
exit 1
|
|
1038
|
+
fi
|
|
1039
|
+
|
|
1040
|
+
echo "ok: ${warnings} warning(s)"
|