dev-playbooks 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 +463 -0
- package/bin/devbooks.js +986 -0
- package/package.json +41 -0
- package/skills/Skill-Development-Guide.md +249 -0
- package/skills/Skills-Usage-Guide.md +447 -0
- package/skills/_shared/context-detection-template.md +315 -0
- package/skills/_shared/mcp-enhancement-template.md +144 -0
- package/skills/_shared/references/universal-gating-protocol.md +114 -0
- package/skills/_template/config-discovery-template.md +126 -0
- package/skills/devbooks-brownfield-bootstrap/SKILL.md +168 -0
- package/skills/devbooks-brownfield-bootstrap/references/10-glossary-template.md +42 -0
- package/skills/devbooks-brownfield-bootstrap/references/brownfield-bootstrap-prompt.md +115 -0
- package/skills/devbooks-brownfield-bootstrap/references/brownfield-bootstrap.md +96 -0
- package/skills/devbooks-brownfield-bootstrap/references/code-navigation-strategy.md +203 -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-architecture-map-prompt.md +33 -0
- package/skills/devbooks-c4-map/references/layered-constraint-checklist.md +185 -0
- package/skills/devbooks-code-review/SKILL.md +175 -0
- package/skills/devbooks-code-review/references/code-review-prompt.md +100 -0
- package/skills/devbooks-code-review/references/code-smell-cheatsheet.md +498 -0
- package/skills/devbooks-code-review/references/pr-template-and-guidelines.md +321 -0
- package/skills/devbooks-code-review/references/resource-management-review-checklist.md +311 -0
- package/skills/devbooks-coder/SKILL.md +219 -0
- package/skills/devbooks-coder/references/code-implementation-prompt.md +74 -0
- package/skills/devbooks-coder/references/coding-style-guidelines.md +351 -0
- package/skills/devbooks-coder/references/error-code-standard.md +463 -0
- package/skills/devbooks-coder/references/logging-standard.md +329 -0
- package/skills/devbooks-coder/references/low-risk-modification-techniques.md +275 -0
- package/skills/devbooks-delivery-workflow/SKILL.md +217 -0
- package/skills/devbooks-delivery-workflow/references/9-change-verification-traceability-template.md +133 -0
- package/skills/devbooks-delivery-workflow/references/delivery-acceptance-workflow.md +177 -0
- package/skills/devbooks-delivery-workflow/references/prototype-production-dual-track.md +169 -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 +1039 -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 +467 -0
- package/skills/devbooks-delivery-workflow/scripts/change-spec-delta-scaffold.sh +135 -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 +365 -0
- package/skills/devbooks-delivery-workflow/scripts/guardrail-check.sh +516 -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/design-backport-prompt.md +132 -0
- package/skills/devbooks-design-doc/SKILL.md +121 -0
- package/skills/devbooks-design-doc/references/design-doc-prompt.md +188 -0
- package/skills/devbooks-design-doc/references/microservice-design-checklist.md +149 -0
- package/skills/devbooks-design-doc/references/privacy-compliance-checklist.md +240 -0
- package/skills/devbooks-entropy-monitor/SKILL.md +188 -0
- package/skills/devbooks-entropy-monitor/references/entropy-metrics-methodology.md +218 -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/impact-analysis-prompt.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/implementation-plan-prompt.md +95 -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/proposal-authoring-prompt.md +66 -0
- package/skills/devbooks-proposal-challenger/SKILL.md +86 -0
- package/skills/devbooks-proposal-challenger/references/ethics-and-compliance-checklist.md +176 -0
- package/skills/devbooks-proposal-challenger/references/proposal-challenge-prompt.md +57 -0
- package/skills/devbooks-proposal-debate-workflow/SKILL.md +78 -0
- package/skills/devbooks-proposal-debate-workflow/references/11-proposal-debate-template.md +35 -0
- package/skills/devbooks-proposal-debate-workflow/references/proposal-debate-workflow.md +24 -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/proposal-judge-prompt.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-design-guide.md +349 -0
- package/skills/devbooks-spec-contract/references/contract-and-data-definition-prompt.md +85 -0
- package/skills/devbooks-spec-contract/references/implicit-change-detection-prompt.md +183 -0
- package/skills/devbooks-spec-contract/references/spec-change-prompt.md +63 -0
- package/skills/devbooks-spec-contract/scripts/implicit-change-detect.sh +378 -0
- package/skills/devbooks-spec-gardener/SKILL.md +73 -0
- package/skills/devbooks-spec-gardener/references/spec-gardener-prompt.md +41 -0
- package/skills/devbooks-test-owner/SKILL.md +173 -0
- package/skills/devbooks-test-owner/references/9-change-verification-traceability-template.md +133 -0
- package/skills/devbooks-test-owner/references/async-system-test-strategy.md +316 -0
- package/skills/devbooks-test-owner/references/decoupling-techniques-cheatsheet.md +269 -0
- package/skills/devbooks-test-owner/references/test-code-prompt.md +171 -0
- package/skills/devbooks-test-owner/references/test-driven-development.md +351 -0
- package/skills/devbooks-test-owner/references/test-layering-strategy.md +281 -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 +20 -0
- package/templates/claude-commands/devbooks/c4.md +20 -0
- package/templates/claude-commands/devbooks/challenger.md +19 -0
- package/templates/claude-commands/devbooks/code.md +20 -0
- package/templates/claude-commands/devbooks/debate.md +20 -0
- package/templates/claude-commands/devbooks/delivery.md +20 -0
- package/templates/claude-commands/devbooks/design.md +20 -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 +20 -0
- package/templates/claude-commands/devbooks/proposal.md +20 -0
- package/templates/claude-commands/devbooks/quick.md +43 -0
- package/templates/claude-commands/devbooks/review.md +20 -0
- package/templates/claude-commands/devbooks/router.md +19 -0
- package/templates/claude-commands/devbooks/spec.md +20 -0
- package/templates/claude-commands/devbooks/test-review.md +19 -0
- package/templates/claude-commands/devbooks/test.md +20 -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 +48 -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,1039 @@
|
|
|
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 common placeholder markers
|
|
146
|
+
# shellcheck disable=SC2140
|
|
147
|
+
if rg -n '<change-id>|<truth-root>|<change-root>|<one-sentence goal>|<capability>|<you>|YYYY-MM-DD|<session/agent>|<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 target:" "$proposal_file" >/dev/null; then
|
|
246
|
+
if [[ "$mode" == "strict" ]]; then
|
|
247
|
+
err "proposal missing '- Value signal target:' (strict): ${proposal_file}"
|
|
248
|
+
else
|
|
249
|
+
warn "proposal missing '- Value signal target:' (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
|
+
|
|
261
|
+
local decision_line
|
|
262
|
+
decision_line=$(rg -n "^- Decision[::] *(Pending|Approved|Revise|Rejected)\\b" "$proposal_file" -m 1 || true)
|
|
263
|
+
if [[ -z "$decision_line" ]]; then
|
|
264
|
+
err "proposal missing decision line (e.g., '- Decision: Approved'): ${proposal_file}"
|
|
265
|
+
return 0
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
local value
|
|
269
|
+
value="$(echo "$decision_line" | sed -E 's/^[0-9]+:- Decision[::] *//')"
|
|
270
|
+
|
|
271
|
+
case "$value" in
|
|
272
|
+
Pending|Approved|Revise|Rejected) ;;
|
|
273
|
+
*)
|
|
274
|
+
err "proposal has invalid decision status '${value}': ${proposal_file}"
|
|
275
|
+
;;
|
|
276
|
+
esac
|
|
277
|
+
|
|
278
|
+
if [[ "$mode" == "apply" || "$mode" == "archive" || "$mode" == "strict" ]]; then
|
|
279
|
+
if [[ "$value" != "Approved" ]]; then
|
|
280
|
+
err "proposal decision status must be Approved for ${mode}: ${proposal_file}"
|
|
281
|
+
fi
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
if [[ "$mode" == "strict" ]]; then
|
|
285
|
+
if contains_placeholder "$proposal_file"; then
|
|
286
|
+
err "proposal contains placeholders/TODO (strict): ${proposal_file}"
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
check_design() {
|
|
292
|
+
if [[ ! -f "$design_file" ]]; then
|
|
293
|
+
if [[ "$mode" == "proposal" ]]; then
|
|
294
|
+
warn "missing design.md (recommended for non-trivial changes): ${design_file}"
|
|
295
|
+
return 0
|
|
296
|
+
fi
|
|
297
|
+
err "missing design.md: ${design_file}"
|
|
298
|
+
return 0
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# Require Acceptance Criteria heading
|
|
302
|
+
if ! rg -n "^## Acceptance Criteria" "$design_file" >/dev/null; then
|
|
303
|
+
err "design missing '## Acceptance Criteria' heading: ${design_file}"
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
if ! rg -n "AC-[0-9]{3}" "$design_file" >/dev/null; then
|
|
307
|
+
err "design missing any AC-xxx items: ${design_file}"
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
# ============================================================================
|
|
311
|
+
# Design completeness checks: Problem Context / Rationale / Trade-offs
|
|
312
|
+
# ============================================================================
|
|
313
|
+
if [[ "$mode" == "apply" || "$mode" == "archive" || "$mode" == "strict" ]]; then
|
|
314
|
+
if ! rg -n "^## Problem Context" "$design_file" >/dev/null; then
|
|
315
|
+
if [[ "$mode" == "strict" ]]; then
|
|
316
|
+
err "design missing '## Problem Context' section (strict): ${design_file}"
|
|
317
|
+
else
|
|
318
|
+
warn "design missing '## Problem Context' section (recommended): ${design_file}"
|
|
319
|
+
fi
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
if ! rg -n "^## Design Rationale" "$design_file" >/dev/null; then
|
|
323
|
+
if [[ "$mode" == "strict" ]]; then
|
|
324
|
+
err "design missing '## Design Rationale' section (strict): ${design_file}"
|
|
325
|
+
else
|
|
326
|
+
warn "design missing '## Design Rationale' section (recommended): ${design_file}"
|
|
327
|
+
fi
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
if ! rg -n "^## Trade-offs" "$design_file" >/dev/null; then
|
|
331
|
+
if [[ "$mode" == "strict" ]]; then
|
|
332
|
+
err "design missing '## Trade-offs' section (strict): ${design_file}"
|
|
333
|
+
else
|
|
334
|
+
warn "design missing '## Trade-offs' section (recommended): ${design_file}"
|
|
335
|
+
fi
|
|
336
|
+
fi
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
# ============================================================================
|
|
340
|
+
# Variation points should be identified in strict mode
|
|
341
|
+
# ============================================================================
|
|
342
|
+
if [[ "$mode" == "strict" ]]; then
|
|
343
|
+
if ! rg -n "Variation Point|Variation Points|Encapsulat(e|ion).*variat" "$design_file" >/dev/null; then
|
|
344
|
+
warn "design may not have identified variation points (strict): ${design_file}"
|
|
345
|
+
fi
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if [[ "$mode" == "strict" ]]; then
|
|
349
|
+
if contains_placeholder "$design_file"; then
|
|
350
|
+
err "design contains placeholders/TODO (strict): ${design_file}"
|
|
351
|
+
fi
|
|
352
|
+
fi
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
check_tasks() {
|
|
356
|
+
require_file "$tasks_file" || return 0
|
|
357
|
+
|
|
358
|
+
if ! rg -n "Main Plan Area" "$tasks_file" >/dev/null; then
|
|
359
|
+
err "tasks missing 'Main Plan Area': ${tasks_file}"
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
if ! rg -n "Context Switch Breakpoint Area" "$tasks_file" >/dev/null; then
|
|
363
|
+
err "tasks missing 'Context Switch Breakpoint Area': ${tasks_file}"
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
if ! rg -n "^- \\[[ xX]\\]" "$tasks_file" >/dev/null; then
|
|
367
|
+
err "tasks missing checkbox items '- [ ]'/'- [x]': ${tasks_file}"
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [[ "$mode" == "archive" || "$mode" == "strict" ]]; then
|
|
371
|
+
# Check for unchecked items that are NOT skip-approved
|
|
372
|
+
local has_unapproved_unchecked=false
|
|
373
|
+
_read_file_to_lines "$tasks_file"
|
|
374
|
+
|
|
375
|
+
for ((i=0; i<_LINE_COUNT; i++)); do
|
|
376
|
+
local line="${_LINES[$i]}"
|
|
377
|
+
if [[ "$line" =~ ^-\ \[\ \] ]]; then
|
|
378
|
+
_get_line_context "$i"
|
|
379
|
+
# Found unchecked task, check if skip-approved using shared helper
|
|
380
|
+
if ! is_skip_approved "$line" "$_PREV_LINE" "$_NEXT_LINE"; then
|
|
381
|
+
has_unapproved_unchecked=true
|
|
382
|
+
break
|
|
383
|
+
fi
|
|
384
|
+
fi
|
|
385
|
+
done
|
|
386
|
+
|
|
387
|
+
if [[ "$has_unapproved_unchecked" == true ]]; then
|
|
388
|
+
err "tasks still contains unchecked items without skip approval (archive/strict): ${tasks_file}"
|
|
389
|
+
fi
|
|
390
|
+
fi
|
|
391
|
+
|
|
392
|
+
if [[ "$mode" == "strict" ]]; then
|
|
393
|
+
if contains_placeholder "$tasks_file"; then
|
|
394
|
+
err "tasks contains placeholders/TODO (strict): ${tasks_file}"
|
|
395
|
+
fi
|
|
396
|
+
fi
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
extract_ac_ids() {
|
|
400
|
+
local file="$1"
|
|
401
|
+
rg -o "AC-[0-9]{3}" "$file" 2>/dev/null | sort -u || true
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
check_verification() {
|
|
405
|
+
if [[ ! -f "$verification_file" ]]; then
|
|
406
|
+
if [[ "$mode" == "proposal" ]]; then
|
|
407
|
+
warn "missing verification.md (expected during apply by test-owner): ${verification_file}"
|
|
408
|
+
return 0
|
|
409
|
+
fi
|
|
410
|
+
err "missing verification.md: ${verification_file}"
|
|
411
|
+
return 0
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
for h in "A\\) Test Plan Instructions" "B\\) Traceability Matrix" "C\\) Deterministic Anchors" "D\\) MANUAL-\\* Checklist"; do
|
|
415
|
+
if ! rg -n "${h}" "$verification_file" >/dev/null; then
|
|
416
|
+
err "verification missing section '${h}': ${verification_file}"
|
|
417
|
+
fi
|
|
418
|
+
done
|
|
419
|
+
|
|
420
|
+
if ! rg -n "^\\| AC-[0-9]{3} \\|" "$verification_file" >/dev/null; then
|
|
421
|
+
err "verification trace matrix missing any AC rows: ${verification_file}"
|
|
422
|
+
fi
|
|
423
|
+
|
|
424
|
+
if [[ "$mode" == "strict" ]]; then
|
|
425
|
+
# G) section is recommended but not blocking
|
|
426
|
+
if ! rg -n "^## G\\) Value stream and metrics|^G\\) Value stream and metrics" "$verification_file" >/dev/null; then
|
|
427
|
+
warn "verification missing 'G) Value stream and metrics' section (recommended for strict): ${verification_file}"
|
|
428
|
+
else
|
|
429
|
+
# Only check value signal target if G) section exists
|
|
430
|
+
if ! rg -n "^- Value signal target:" "$verification_file" >/dev/null; then
|
|
431
|
+
warn "verification missing '- Value signal target:' line (recommended): ${verification_file}"
|
|
432
|
+
fi
|
|
433
|
+
fi
|
|
434
|
+
|
|
435
|
+
if rg -n "^\\| AC-[0-9]{3} \\|.*\\| *TODO *\\|" "$verification_file" >/dev/null; then
|
|
436
|
+
err "verification trace matrix still has TODO rows (strict): ${verification_file}"
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
if contains_placeholder "$verification_file"; then
|
|
440
|
+
err "verification contains placeholders/TODO (strict): ${verification_file}"
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
if [[ -f "$design_file" ]]; then
|
|
444
|
+
design_acs="$(extract_ac_ids "$design_file")"
|
|
445
|
+
verification_acs="$(extract_ac_ids "$verification_file")"
|
|
446
|
+
while IFS= read -r ac; do
|
|
447
|
+
[[ -n "$ac" ]] || continue
|
|
448
|
+
if ! printf "%s\n" "$verification_acs" | rg -x "$ac" >/dev/null; then
|
|
449
|
+
err "verification missing AC '${ac}' from design: ${verification_file}"
|
|
450
|
+
fi
|
|
451
|
+
done <<<"$design_acs"
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
455
|
+
if [[ -x "${script_dir}/guardrail-check.sh" ]]; then
|
|
456
|
+
if ! "${script_dir}/guardrail-check.sh" "$change_id" --project-root "$project_root" --change-root "$change_root" >/dev/null; then
|
|
457
|
+
err "guardrail-check failed (strict): ${verification_file}"
|
|
458
|
+
fi
|
|
459
|
+
else
|
|
460
|
+
warn "missing guardrail-check.sh; skipping guardrail check"
|
|
461
|
+
fi
|
|
462
|
+
fi
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
check_spec_deltas() {
|
|
466
|
+
if [[ ! -d "$specs_dir" ]]; then
|
|
467
|
+
return 0
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
local found_any=false
|
|
471
|
+
while IFS= read -r file; do
|
|
472
|
+
[[ -n "$file" ]] || continue
|
|
473
|
+
found_any=true
|
|
474
|
+
if ! rg -n "^## (ADDED|MODIFIED|REMOVED) Requirements$" "$file" >/dev/null; then
|
|
475
|
+
err "spec delta missing '## ADDED|MODIFIED|REMOVED Requirements' headings: ${file}"
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
awk '
|
|
479
|
+
BEGIN { req = 0; scen = 0; bad = 0; }
|
|
480
|
+
/^### Requirement:/ {
|
|
481
|
+
if (req > 0 && scen == 0) { bad++; }
|
|
482
|
+
req++; scen = 0;
|
|
483
|
+
}
|
|
484
|
+
/^#### Scenario:/ { scen++; }
|
|
485
|
+
END {
|
|
486
|
+
if (req > 0 && scen == 0) { bad++; }
|
|
487
|
+
if (req == 0) { print "NO_REQUIREMENTS"; exit 0; }
|
|
488
|
+
if (bad > 0) { print "BAD_REQUIREMENTS " bad; exit 0; }
|
|
489
|
+
print "OK";
|
|
490
|
+
}
|
|
491
|
+
' "$file" | {
|
|
492
|
+
read -r result rest || true
|
|
493
|
+
if [[ "$result" == "NO_REQUIREMENTS" ]]; then
|
|
494
|
+
err "spec delta has no '### Requirement:' entries: ${file}"
|
|
495
|
+
elif [[ "$result" == "BAD_REQUIREMENTS" ]]; then
|
|
496
|
+
err "spec delta has Requirement(s) without Scenario (count=${rest}): ${file}"
|
|
497
|
+
fi
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if [[ "$mode" == "strict" ]]; then
|
|
501
|
+
if rg -n "<change-id>|<truth-root>|<change-root>|<capability>|TBD\\b|TODO\\b" "$file" >/dev/null; then
|
|
502
|
+
err "spec delta contains placeholders/TBD/TODO (strict): ${file}"
|
|
503
|
+
fi
|
|
504
|
+
fi
|
|
505
|
+
done < <(find "$specs_dir" -type f -name "spec.md" 2>/dev/null | sort)
|
|
506
|
+
|
|
507
|
+
if [[ "$found_any" == false ]]; then
|
|
508
|
+
return 0
|
|
509
|
+
fi
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
# =============================================================================
|
|
513
|
+
# Constitution Check (DevBooks 2.0)
|
|
514
|
+
# Verify project constitution is present and valid
|
|
515
|
+
# =============================================================================
|
|
516
|
+
check_constitution() {
|
|
517
|
+
# Only run in strict mode
|
|
518
|
+
if [[ "$mode" != "strict" ]]; then
|
|
519
|
+
return 0
|
|
520
|
+
fi
|
|
521
|
+
|
|
522
|
+
# Find constitution-check.sh
|
|
523
|
+
local script_dir
|
|
524
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
525
|
+
local constitution_script="${script_dir}/constitution-check.sh"
|
|
526
|
+
|
|
527
|
+
if [[ ! -x "$constitution_script" ]]; then
|
|
528
|
+
warn "constitution-check.sh not found; skipping constitution check"
|
|
529
|
+
return 0
|
|
530
|
+
fi
|
|
531
|
+
|
|
532
|
+
# Run constitution check
|
|
533
|
+
echo " constitution check..."
|
|
534
|
+
if ! "$constitution_script" "$project_root" --quiet 2>/dev/null; then
|
|
535
|
+
err "constitution check failed (strict): constitution missing or invalid"
|
|
536
|
+
return 0
|
|
537
|
+
fi
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
# =============================================================================
|
|
541
|
+
# Fitness Check (DevBooks 2.0)
|
|
542
|
+
# Verify architecture fitness rules
|
|
543
|
+
# =============================================================================
|
|
544
|
+
check_fitness() {
|
|
545
|
+
# Only run in apply/archive/strict modes
|
|
546
|
+
if [[ "$mode" != "apply" && "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
547
|
+
return 0
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
# Find fitness-check.sh
|
|
551
|
+
local script_dir
|
|
552
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
553
|
+
local fitness_script="${script_dir}/fitness-check.sh"
|
|
554
|
+
|
|
555
|
+
if [[ ! -x "$fitness_script" ]]; then
|
|
556
|
+
warn "fitness-check.sh not found; skipping fitness check"
|
|
557
|
+
return 0
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
# Determine fitness mode based on change-check mode
|
|
561
|
+
local fitness_mode="warn"
|
|
562
|
+
if [[ "$mode" == "strict" ]]; then
|
|
563
|
+
fitness_mode="error"
|
|
564
|
+
fi
|
|
565
|
+
|
|
566
|
+
# Run fitness check
|
|
567
|
+
echo " fitness check (mode=${fitness_mode})..."
|
|
568
|
+
if ! "$fitness_script" --project-root "$project_root" --mode "$fitness_mode" 2>/dev/null; then
|
|
569
|
+
if [[ "$fitness_mode" == "error" ]]; then
|
|
570
|
+
err "fitness check failed (strict): architecture fitness checks failed"
|
|
571
|
+
else
|
|
572
|
+
warn "fitness check warnings detected"
|
|
573
|
+
fi
|
|
574
|
+
fi
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
# =============================================================================
|
|
578
|
+
# AC-001: Green evidence closure check (The Mythical Man-Month, Ch. 6 “Passing the Word”)
|
|
579
|
+
# Block archive when Green evidence is missing
|
|
580
|
+
# =============================================================================
|
|
581
|
+
check_evidence_closure() {
|
|
582
|
+
# Only run in archive/strict modes
|
|
583
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
584
|
+
return 0
|
|
585
|
+
fi
|
|
586
|
+
|
|
587
|
+
local evidence_dir="${change_dir}/evidence/green-final"
|
|
588
|
+
|
|
589
|
+
if [[ ! -d "$evidence_dir" ]]; then
|
|
590
|
+
err "missing Green evidence: evidence/green-final/ not found (AC-001)"
|
|
591
|
+
return 0
|
|
592
|
+
fi
|
|
593
|
+
|
|
594
|
+
# Check if directory has at least one file
|
|
595
|
+
local file_count
|
|
596
|
+
file_count=$(find "$evidence_dir" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
597
|
+
if [[ "$file_count" -eq 0 ]]; then
|
|
598
|
+
err "missing Green evidence: evidence/green-final/ is empty (AC-001)"
|
|
599
|
+
return 0
|
|
600
|
+
fi
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
# =============================================================================
|
|
604
|
+
# AC-002: Task Completion Rate Check (Enhanced)
|
|
605
|
+
# Enforce 100% task completion in strict mode with rate display
|
|
606
|
+
# Skip-approved tasks count as completed
|
|
607
|
+
# =============================================================================
|
|
608
|
+
check_task_completion_rate() {
|
|
609
|
+
# Only run in strict mode for completion rate
|
|
610
|
+
if [[ "$mode" != "strict" ]]; then
|
|
611
|
+
return 0
|
|
612
|
+
fi
|
|
613
|
+
|
|
614
|
+
if [[ ! -f "$tasks_file" ]]; then
|
|
615
|
+
return 0
|
|
616
|
+
fi
|
|
617
|
+
|
|
618
|
+
# Read file using shared helper
|
|
619
|
+
_read_file_to_lines "$tasks_file"
|
|
620
|
+
|
|
621
|
+
local total_tasks=0
|
|
622
|
+
local completed_tasks=0
|
|
623
|
+
|
|
624
|
+
for ((i=0; i<_LINE_COUNT; i++)); do
|
|
625
|
+
local line="${_LINES[$i]}"
|
|
626
|
+
|
|
627
|
+
# Count all checkbox items (both checked and unchecked)
|
|
628
|
+
if [[ "$line" =~ ^-\ \[[\ xX]\] ]]; then
|
|
629
|
+
total_tasks=$((total_tasks + 1))
|
|
630
|
+
|
|
631
|
+
# Check if completed (checked)
|
|
632
|
+
if [[ "$line" =~ ^-\ \[[xX]\] ]]; then
|
|
633
|
+
completed_tasks=$((completed_tasks + 1))
|
|
634
|
+
elif [[ "$line" =~ ^-\ \[\ \] ]]; then
|
|
635
|
+
_get_line_context "$i"
|
|
636
|
+
# Unchecked - check if skip-approved using shared helper
|
|
637
|
+
if is_skip_approved "$line" "$_PREV_LINE" "$_NEXT_LINE"; then
|
|
638
|
+
completed_tasks=$((completed_tasks + 1))
|
|
639
|
+
fi
|
|
640
|
+
fi
|
|
641
|
+
fi
|
|
642
|
+
done
|
|
643
|
+
|
|
644
|
+
if [[ "$total_tasks" -eq 0 ]]; then
|
|
645
|
+
# No tasks = 100% complete
|
|
646
|
+
return 0
|
|
647
|
+
fi
|
|
648
|
+
|
|
649
|
+
local incomplete_tasks=$((total_tasks - completed_tasks))
|
|
650
|
+
if [[ "$incomplete_tasks" -gt 0 ]]; then
|
|
651
|
+
local rate=$((completed_tasks * 100 / total_tasks))
|
|
652
|
+
err "task completion ${rate}% (${completed_tasks}/${total_tasks}); expected 100% (AC-002)"
|
|
653
|
+
fi
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
# =============================================================================
|
|
657
|
+
# AC-007: Test Failure in Evidence Check
|
|
658
|
+
# Block archive when test failures exist in Green evidence
|
|
659
|
+
# Pattern is designed to avoid false positives like "0 tests FAIL" or comments
|
|
660
|
+
# =============================================================================
|
|
661
|
+
check_test_failure_in_evidence() {
|
|
662
|
+
# Only run in archive/strict modes
|
|
663
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
664
|
+
return 0
|
|
665
|
+
fi
|
|
666
|
+
|
|
667
|
+
local evidence_dir="${change_dir}/evidence/green-final"
|
|
668
|
+
|
|
669
|
+
if [[ ! -d "$evidence_dir" ]]; then
|
|
670
|
+
# Already checked in check_evidence_closure
|
|
671
|
+
return 0
|
|
672
|
+
fi
|
|
673
|
+
|
|
674
|
+
# Check for failure patterns in evidence files (use --no-ignore to search .log files)
|
|
675
|
+
# Patterns designed for common test frameworks:
|
|
676
|
+
# - TAP: "not ok" at line start
|
|
677
|
+
# - Jest/Vitest: "FAIL " followed by path
|
|
678
|
+
# - pytest: "FAILED " at line start
|
|
679
|
+
# - Go: "--- FAIL:" at line start
|
|
680
|
+
# - BATS: "not ok" at line start
|
|
681
|
+
# - Generic: "FAIL:" "FAILED:" "[FAIL]" "[FAILED]" "[ERROR]" markers
|
|
682
|
+
# Exclude: "0 tests FAIL", "0 failed", comments, variable names
|
|
683
|
+
local fail_pattern='^not ok |^FAIL[: ]|^FAILED[: ]|^--- FAIL:|^\[FAIL\]|^\[FAILED\]|^\[ERROR\]|^ERROR:|: FAIL$|: FAILED$'
|
|
684
|
+
|
|
685
|
+
if rg --no-ignore -l "$fail_pattern" "$evidence_dir" >/dev/null 2>&1; then
|
|
686
|
+
# Double-check: exclude files that only have success patterns
|
|
687
|
+
local fail_files
|
|
688
|
+
fail_files=$(rg --no-ignore -l "$fail_pattern" "$evidence_dir" 2>/dev/null || true)
|
|
689
|
+
|
|
690
|
+
for file in $fail_files; do
|
|
691
|
+
# Check if the match is a real failure (not "0 tests failed" pattern)
|
|
692
|
+
if rg --no-ignore "$fail_pattern" "$file" 2>/dev/null | grep -qvE "^[[:space:]]*#|0 (tests?|failures?|failed)"; then
|
|
693
|
+
err "test failure: Green evidence contains failure markers; cannot archive (AC-007)"
|
|
694
|
+
echo " file: $file" >&2
|
|
695
|
+
return 0
|
|
696
|
+
fi
|
|
697
|
+
done
|
|
698
|
+
fi
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
# =============================================================================
|
|
702
|
+
# AC-005: P0 Skip Approval Check
|
|
703
|
+
# Enforce P0 task skip requires SKIP-APPROVED comment
|
|
704
|
+
# SKIP-APPROVED can be on the line before, same line, or line after the task
|
|
705
|
+
# =============================================================================
|
|
706
|
+
check_skip_approval() {
|
|
707
|
+
# Only run in strict mode
|
|
708
|
+
if [[ "$mode" != "strict" ]]; then
|
|
709
|
+
return 0
|
|
710
|
+
fi
|
|
711
|
+
|
|
712
|
+
if [[ ! -f "$tasks_file" ]]; then
|
|
713
|
+
return 0
|
|
714
|
+
fi
|
|
715
|
+
|
|
716
|
+
# Read file using shared helper
|
|
717
|
+
_read_file_to_lines "$tasks_file"
|
|
718
|
+
|
|
719
|
+
for ((i=0; i<_LINE_COUNT; i++)); do
|
|
720
|
+
local line="${_LINES[$i]}"
|
|
721
|
+
|
|
722
|
+
# Check for unchecked P0 task
|
|
723
|
+
if [[ "$line" =~ ^-\ \[\ \]\ \[P0\] ]]; then
|
|
724
|
+
local p0_task_name
|
|
725
|
+
p0_task_name=$(echo "$line" | sed -E 's/^- \[ \] \[P0\] //')
|
|
726
|
+
|
|
727
|
+
_get_line_context "$i"
|
|
728
|
+
# Use shared helper with strict_html=true for prev/next line HTML comment check
|
|
729
|
+
if ! is_skip_approved "$line" "$_PREV_LINE" "$_NEXT_LINE" true; then
|
|
730
|
+
err "P0 task skip requires approval: ${p0_task_name} (AC-005)"
|
|
731
|
+
fi
|
|
732
|
+
fi
|
|
733
|
+
done
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
# =============================================================================
|
|
737
|
+
# AC-006: Environment Match Check
|
|
738
|
+
# Call env-match-check.sh to verify test environment declaration
|
|
739
|
+
# =============================================================================
|
|
740
|
+
check_env_match() {
|
|
741
|
+
# Only run in archive/strict modes
|
|
742
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
743
|
+
return 0
|
|
744
|
+
fi
|
|
745
|
+
|
|
746
|
+
# Check if env-match-check.sh exists
|
|
747
|
+
local script_dir
|
|
748
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
749
|
+
local env_check_script="${script_dir}/env-match-check.sh"
|
|
750
|
+
|
|
751
|
+
if [[ ! -x "$env_check_script" ]]; then
|
|
752
|
+
warn "env-match-check.sh not found; skipping environment check"
|
|
753
|
+
return 0
|
|
754
|
+
fi
|
|
755
|
+
|
|
756
|
+
# Run env-match-check
|
|
757
|
+
if ! "$env_check_script" "$change_id" --project-root "$project_root" --change-root "$change_root" >/dev/null 2>&1; then
|
|
758
|
+
err "Test Environment section missing: verification.md must include a 'Test Environment' section (AC-006)"
|
|
759
|
+
fi
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
# =============================================================================
|
|
763
|
+
# AC-008: Documentation Impact Check
|
|
764
|
+
# Verify documentation impact is declared and fulfilled
|
|
765
|
+
# =============================================================================
|
|
766
|
+
check_docs_impact() {
|
|
767
|
+
# Only run in archive/strict modes
|
|
768
|
+
if [[ "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
769
|
+
return 0
|
|
770
|
+
fi
|
|
771
|
+
|
|
772
|
+
if [[ ! -f "$design_file" ]]; then
|
|
773
|
+
return 0
|
|
774
|
+
fi
|
|
775
|
+
|
|
776
|
+
# Check if Documentation Impact section exists
|
|
777
|
+
if ! rg -n "^## Documentation Impact" "$design_file" >/dev/null; then
|
|
778
|
+
if [[ "$mode" == "strict" ]]; then
|
|
779
|
+
err "design.md missing '## Documentation Impact' section (AC-008)"
|
|
780
|
+
else
|
|
781
|
+
warn "design.md missing '## Documentation Impact' section (recommended)"
|
|
782
|
+
fi
|
|
783
|
+
return 0
|
|
784
|
+
fi
|
|
785
|
+
|
|
786
|
+
# Check for "no doc updates required" declaration - if checked, skip further checks
|
|
787
|
+
if rg -n "^- \\[x\\] (No documentation update required|This change is internal refactoring|This change is bug-fix only)" "$design_file" >/dev/null; then
|
|
788
|
+
# Declared as no doc update needed, skip
|
|
789
|
+
return 0
|
|
790
|
+
fi
|
|
791
|
+
|
|
792
|
+
# Check for P0 documentation updates that are NOT checked in the checklist
|
|
793
|
+
# Look for unchecked P0 items in the table
|
|
794
|
+
local has_p0_docs=false
|
|
795
|
+
if rg -n "\| P0 \|" "$design_file" >/dev/null 2>&1; then
|
|
796
|
+
has_p0_docs=true
|
|
797
|
+
fi
|
|
798
|
+
|
|
799
|
+
if [[ "$has_p0_docs" == true ]]; then
|
|
800
|
+
# Check if documentation update checklist items are completed
|
|
801
|
+
local unchecked_items
|
|
802
|
+
unchecked_items=$(rg -n "^- \\[ \\] (New scripts|New configuration|New workflows|API)" "$design_file" || true)
|
|
803
|
+
|
|
804
|
+
if [[ -n "$unchecked_items" && "$mode" == "strict" ]]; then
|
|
805
|
+
err "documentation checklist has incomplete items (AC-008)"
|
|
806
|
+
echo " unchecked items:" >&2
|
|
807
|
+
printf "%s\n" "$unchecked_items" | head -3 | sed 's/^/ /' >&2
|
|
808
|
+
fi
|
|
809
|
+
fi
|
|
810
|
+
|
|
811
|
+
# In strict mode, verify declared docs are actually modified
|
|
812
|
+
if [[ "$mode" == "strict" ]] && command -v git >/dev/null 2>&1; then
|
|
813
|
+
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
814
|
+
# Extract declared P0 docs from design.md
|
|
815
|
+
local declared_docs
|
|
816
|
+
declared_docs=$(rg -o "\\| (README\\.md|docs/[^|]+\\.md|CHANGELOG\\.md) \\|" "$design_file" 2>/dev/null | sed 's/| //g; s/ |//g' | sort -u || true)
|
|
817
|
+
|
|
818
|
+
if [[ -n "$declared_docs" ]]; then
|
|
819
|
+
# Get changed files
|
|
820
|
+
local changed_files
|
|
821
|
+
changed_files=$(git -C "$project_root" diff --name-only HEAD 2>/dev/null || git -C "$project_root" diff --name-only 2>/dev/null || true)
|
|
822
|
+
|
|
823
|
+
while IFS= read -r doc; do
|
|
824
|
+
[[ -n "$doc" ]] || continue
|
|
825
|
+
# Check if the declared doc is in the changed files
|
|
826
|
+
if ! printf "%s\n" "$changed_files" | grep -qF "$doc"; then
|
|
827
|
+
warn "declared for update but not modified: $doc (recommended to verify)"
|
|
828
|
+
fi
|
|
829
|
+
done <<< "$declared_docs"
|
|
830
|
+
fi
|
|
831
|
+
fi
|
|
832
|
+
fi
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
# =============================================================================
|
|
836
|
+
# AC-003: Role Boundary Check (Enhanced from check_no_tests_changed)
|
|
837
|
+
# Enforce role-specific file modification boundaries
|
|
838
|
+
# Refactored: split into per-role helper functions for maintainability
|
|
839
|
+
# =============================================================================
|
|
840
|
+
|
|
841
|
+
# Helper: Report role violation with changed files list (DRY extraction)
|
|
842
|
+
_report_role_violation() {
|
|
843
|
+
local role_name="$1" forbidden_target="$2" changed_list="$3"
|
|
844
|
+
err "role violation: ${role_name} must not modify ${forbidden_target} (AC-003)"
|
|
845
|
+
echo " detected changes:" >&2
|
|
846
|
+
printf "%s\n" "$changed_list" | head -5 | sed 's/^/ /' >&2
|
|
847
|
+
if [[ $(printf "%s\n" "$changed_list" | wc -l) -gt 5 ]]; then
|
|
848
|
+
echo " ... and more" >&2
|
|
849
|
+
fi
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
# Helper: Check Coder role boundaries
|
|
853
|
+
_check_coder_boundaries() {
|
|
854
|
+
local changed="$1"
|
|
855
|
+
|
|
856
|
+
# Coder cannot modify tests/**
|
|
857
|
+
local tests_changed
|
|
858
|
+
tests_changed=$(printf "%s\n" "$changed" | grep -E "^tests/" || true)
|
|
859
|
+
if [[ -n "$tests_changed" ]]; then
|
|
860
|
+
_report_role_violation "Coder" "tests/**" "$tests_changed"
|
|
861
|
+
fi
|
|
862
|
+
|
|
863
|
+
# Coder cannot modify verification.md
|
|
864
|
+
if printf "%s\n" "$changed" | grep -qE "verification\.md$"; then
|
|
865
|
+
err "role violation: Coder must not modify verification.md (AC-003)"
|
|
866
|
+
fi
|
|
867
|
+
|
|
868
|
+
# Coder cannot modify .devbooks/ config
|
|
869
|
+
if printf "%s\n" "$changed" | grep -qE "^\.devbooks/"; then
|
|
870
|
+
err "role violation: Coder must not modify .devbooks/ config (AC-003)"
|
|
871
|
+
fi
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
# Helper: Check Test Owner role boundaries
|
|
875
|
+
_check_test_owner_boundaries() {
|
|
876
|
+
local changed="$1"
|
|
877
|
+
|
|
878
|
+
# Test Owner cannot modify src/**
|
|
879
|
+
local src_changed
|
|
880
|
+
src_changed=$(printf "%s\n" "$changed" | grep -E "^src/" || true)
|
|
881
|
+
if [[ -n "$src_changed" ]]; then
|
|
882
|
+
_report_role_violation "Test Owner" "src/**" "$src_changed"
|
|
883
|
+
fi
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
# Helper: Check Reviewer role boundaries
|
|
887
|
+
_check_reviewer_boundaries() {
|
|
888
|
+
local changed="$1"
|
|
889
|
+
|
|
890
|
+
# Reviewer cannot modify any code files
|
|
891
|
+
local code_changed
|
|
892
|
+
code_changed=$(printf "%s\n" "$changed" | grep -E "\.(ts|js|tsx|jsx|py|sh)$" || true)
|
|
893
|
+
if [[ -n "$code_changed" ]]; then
|
|
894
|
+
_report_role_violation "Reviewer" "code files" "$code_changed"
|
|
895
|
+
fi
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
check_role_boundaries() {
|
|
899
|
+
# Only run when role is specified or in apply mode
|
|
900
|
+
if [[ -z "$role" && "$mode" != "apply" ]]; then
|
|
901
|
+
return 0
|
|
902
|
+
fi
|
|
903
|
+
|
|
904
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
905
|
+
warn "git not found; cannot enforce role boundaries"
|
|
906
|
+
return 0
|
|
907
|
+
fi
|
|
908
|
+
|
|
909
|
+
if ! git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
910
|
+
warn "not a git worktree; cannot enforce role boundaries"
|
|
911
|
+
return 0
|
|
912
|
+
fi
|
|
913
|
+
|
|
914
|
+
# Get changed files
|
|
915
|
+
local changed
|
|
916
|
+
changed="$(
|
|
917
|
+
{
|
|
918
|
+
git -C "$project_root" diff --name-only
|
|
919
|
+
git -C "$project_root" diff --cached --name-only
|
|
920
|
+
} | sort -u
|
|
921
|
+
)"
|
|
922
|
+
|
|
923
|
+
if [[ -z "$changed" ]]; then
|
|
924
|
+
return 0
|
|
925
|
+
fi
|
|
926
|
+
|
|
927
|
+
# Dispatch to role-specific helper
|
|
928
|
+
case "$role" in
|
|
929
|
+
coder) _check_coder_boundaries "$changed" ;;
|
|
930
|
+
test-owner) _check_test_owner_boundaries "$changed" ;;
|
|
931
|
+
reviewer) _check_reviewer_boundaries "$changed" ;;
|
|
932
|
+
esac
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
# Backward compatibility alias
|
|
936
|
+
check_no_tests_changed() {
|
|
937
|
+
check_role_boundaries
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
# ============================================================================
|
|
941
|
+
# Implicit Change Detection (The Mythical Man-Month, Ch. 7 “The Tower of Babel”)
|
|
942
|
+
# ============================================================================
|
|
943
|
+
check_implicit_changes() {
|
|
944
|
+
# Only run in apply/archive/strict modes
|
|
945
|
+
if [[ "$mode" != "apply" && "$mode" != "archive" && "$mode" != "strict" ]]; then
|
|
946
|
+
return 0
|
|
947
|
+
fi
|
|
948
|
+
|
|
949
|
+
local implicit_report="${change_dir}/evidence/implicit-changes.json"
|
|
950
|
+
|
|
951
|
+
# Check if implicit-change-detect.sh is available (now in devbooks-spec-contract)
|
|
952
|
+
local detect_script
|
|
953
|
+
detect_script="$(dirname "$0")/../devbooks-spec-contract/scripts/implicit-change-detect.sh"
|
|
954
|
+
if [[ ! -x "$detect_script" ]]; then
|
|
955
|
+
# Try alternate location
|
|
956
|
+
detect_script="$(dirname "$0")/../../devbooks-spec-contract/scripts/implicit-change-detect.sh"
|
|
957
|
+
fi
|
|
958
|
+
|
|
959
|
+
# If report doesn't exist and we can run detection, suggest it
|
|
960
|
+
if [[ ! -f "$implicit_report" ]]; then
|
|
961
|
+
if [[ -x "$detect_script" ]]; then
|
|
962
|
+
warn "missing implicit change detection report: ${implicit_report}"
|
|
963
|
+
warn "hint: run 'implicit-change-detect.sh ${change_id} --project-root ${project_root} --change-root ${change_root}'"
|
|
964
|
+
fi
|
|
965
|
+
return 0
|
|
966
|
+
fi
|
|
967
|
+
|
|
968
|
+
# Parse the report
|
|
969
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
970
|
+
warn "jq not found; cannot parse implicit-changes.json"
|
|
971
|
+
return 0
|
|
972
|
+
fi
|
|
973
|
+
|
|
974
|
+
local total_implicit
|
|
975
|
+
total_implicit=$(jq -r '.summary.total // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
976
|
+
|
|
977
|
+
if [[ "$total_implicit" -eq 0 ]]; then
|
|
978
|
+
return 0
|
|
979
|
+
fi
|
|
980
|
+
|
|
981
|
+
echo " implicit changes detected: ${total_implicit}"
|
|
982
|
+
|
|
983
|
+
# In strict mode, check if high-risk changes are declared in design.md
|
|
984
|
+
if [[ "$mode" == "strict" ]]; then
|
|
985
|
+
local dep_count cfg_count bld_count
|
|
986
|
+
dep_count=$(jq -r '.summary.dependency // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
987
|
+
cfg_count=$(jq -r '.summary.config // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
988
|
+
bld_count=$(jq -r '.summary.build // 0' "$implicit_report" 2>/dev/null || echo "0")
|
|
989
|
+
|
|
990
|
+
if [[ "$dep_count" -gt 0 || "$cfg_count" -gt 0 || "$bld_count" -gt 0 ]]; then
|
|
991
|
+
# Check if design.md exists and mentions these changes
|
|
992
|
+
if [[ ! -f "$design_file" ]]; then
|
|
993
|
+
err "implicit changes detected but design.md missing (strict): review ${implicit_report}"
|
|
994
|
+
else
|
|
995
|
+
# For strict mode, we just warn - full declaration check would need more sophisticated parsing
|
|
996
|
+
warn "implicit changes detected (${total_implicit}): verify these are declared in design.md"
|
|
997
|
+
warn " dependencies: ${dep_count}, config: ${cfg_count}, build: ${bld_count}"
|
|
998
|
+
warn " report: ${implicit_report}"
|
|
999
|
+
fi
|
|
1000
|
+
fi
|
|
1001
|
+
else
|
|
1002
|
+
# For non-strict modes, just warn
|
|
1003
|
+
warn "implicit changes detected (${total_implicit}): review ${implicit_report}"
|
|
1004
|
+
fi
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
echo "devbooks: checking change '${change_id}' (mode=${mode}${role:+, role=${role}})"
|
|
1008
|
+
echo " change-dir: ${change_dir}"
|
|
1009
|
+
echo " truth-dir: ${truth_dir}"
|
|
1010
|
+
|
|
1011
|
+
if [[ ! -d "$change_dir" ]]; then
|
|
1012
|
+
err "missing change directory: ${change_dir}"
|
|
1013
|
+
else
|
|
1014
|
+
check_proposal
|
|
1015
|
+
check_design
|
|
1016
|
+
check_tasks
|
|
1017
|
+
check_spec_deltas
|
|
1018
|
+
check_verification
|
|
1019
|
+
check_no_tests_changed
|
|
1020
|
+
check_implicit_changes
|
|
1021
|
+
# DevBooks 2.0: Constitution check
|
|
1022
|
+
check_constitution # Constitution validity check (strict mode)
|
|
1023
|
+
# DevBooks 2.0: Fitness check
|
|
1024
|
+
check_fitness # Architecture fitness check (apply/archive/strict)
|
|
1025
|
+
# New quality gates (harden-devbooks-quality-gates)
|
|
1026
|
+
check_evidence_closure # AC-001: Green evidence required for archive
|
|
1027
|
+
check_task_completion_rate # AC-002: 100% completion for strict mode
|
|
1028
|
+
check_test_failure_in_evidence # AC-007: No failures in Green evidence
|
|
1029
|
+
check_skip_approval # AC-005: P0 skip requires approval
|
|
1030
|
+
check_env_match # AC-006: Environment declaration required
|
|
1031
|
+
check_docs_impact # AC-008: Documentation impact declared and fulfilled
|
|
1032
|
+
fi
|
|
1033
|
+
|
|
1034
|
+
if [[ $errors -gt 0 ]]; then
|
|
1035
|
+
echo "fail: ${errors} error(s), ${warnings} warning(s)" >&2
|
|
1036
|
+
exit 1
|
|
1037
|
+
fi
|
|
1038
|
+
|
|
1039
|
+
echo "ok: ${warnings} warning(s)"
|