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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +466 -0
  3. package/bin/devbooks.js +987 -0
  4. package/package.json +43 -0
  5. package/skills/Skills/344/275/277/347/224/250/350/257/264/346/230/216.md +446 -0
  6. package/skills/Skill/345/274/200/345/217/221/346/214/207/345/215/227.md +248 -0
  7. package/skills/_shared/context-detection-template.md +315 -0
  8. package/skills/_shared/mcp-enhancement-template.md +144 -0
  9. 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
  10. package/skills/_template/config-discovery-template.md +126 -0
  11. package/skills/devbooks-brownfield-bootstrap/SKILL.md +167 -0
  12. 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
  13. 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
  14. 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
  15. package/skills/devbooks-brownfield-bootstrap/references//346/234/257/350/257/255/350/241/250/346/250/241/346/235/277.md +42 -0
  16. package/skills/devbooks-brownfield-bootstrap/scripts/cod-update.sh +357 -0
  17. package/skills/devbooks-brownfield-bootstrap/templates/project-profile-template.md +172 -0
  18. package/skills/devbooks-c4-map/SKILL.md +151 -0
  19. 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
  20. 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
  21. package/skills/devbooks-code-review/SKILL.md +175 -0
  22. 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
  23. 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
  24. 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
  25. 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
  26. package/skills/devbooks-coder/SKILL.md +219 -0
  27. 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
  28. 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
  29. package/skills/devbooks-coder/references//346/227/245/345/277/227/350/247/204/350/214/203.md +329 -0
  30. 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
  31. package/skills/devbooks-coder/references//351/224/231/350/257/257/347/240/201/350/247/204/350/214/203.md +463 -0
  32. package/skills/devbooks-delivery-workflow/SKILL.md +217 -0
  33. 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
  34. 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
  35. 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
  36. package/skills/devbooks-delivery-workflow/scripts/ac-trace-check.sh +330 -0
  37. package/skills/devbooks-delivery-workflow/scripts/audit-scope.sh +262 -0
  38. package/skills/devbooks-delivery-workflow/scripts/change-check.sh +1040 -0
  39. package/skills/devbooks-delivery-workflow/scripts/change-codemod-scaffold.sh +135 -0
  40. package/skills/devbooks-delivery-workflow/scripts/change-evidence.sh +152 -0
  41. package/skills/devbooks-delivery-workflow/scripts/change-scaffold.sh +442 -0
  42. package/skills/devbooks-delivery-workflow/scripts/change-spec-delta-scaffold.sh +136 -0
  43. package/skills/devbooks-delivery-workflow/scripts/constitution-check.sh +237 -0
  44. package/skills/devbooks-delivery-workflow/scripts/env-match-check.sh +128 -0
  45. package/skills/devbooks-delivery-workflow/scripts/fitness-check.sh +387 -0
  46. package/skills/devbooks-delivery-workflow/scripts/guardrail-check.sh +519 -0
  47. package/skills/devbooks-delivery-workflow/scripts/handoff-check.sh +141 -0
  48. package/skills/devbooks-delivery-workflow/scripts/hygiene-check.sh +340 -0
  49. package/skills/devbooks-delivery-workflow/scripts/migrate-from-openspec.sh +385 -0
  50. package/skills/devbooks-delivery-workflow/scripts/migrate-to-v2-gates.sh +202 -0
  51. package/skills/devbooks-delivery-workflow/scripts/progress-dashboard.sh +319 -0
  52. package/skills/devbooks-delivery-workflow/scripts/prototype-promote.sh +341 -0
  53. package/skills/devbooks-delivery-workflow/scripts/spec-preview.sh +203 -0
  54. package/skills/devbooks-delivery-workflow/scripts/spec-promote.sh +118 -0
  55. package/skills/devbooks-delivery-workflow/scripts/spec-rollback.sh +124 -0
  56. package/skills/devbooks-delivery-workflow/scripts/spec-stage.sh +117 -0
  57. package/skills/devbooks-delivery-workflow/scripts/verify-all.sh +78 -0
  58. package/skills/devbooks-delivery-workflow/scripts/verify-npm-package.sh +123 -0
  59. package/skills/devbooks-delivery-workflow/scripts/verify-openspec-free.sh +81 -0
  60. package/skills/devbooks-delivery-workflow/scripts/verify-slash-commands.sh +146 -0
  61. package/skills/devbooks-delivery-workflow/templates/handoff.md +50 -0
  62. package/skills/devbooks-design-backport/SKILL.md +73 -0
  63. 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
  64. package/skills/devbooks-design-doc/SKILL.md +121 -0
  65. 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
  66. 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
  67. 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
  68. package/skills/devbooks-entropy-monitor/SKILL.md +188 -0
  69. 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
  70. package/skills/devbooks-entropy-monitor/scripts/entropy-measure.sh +449 -0
  71. package/skills/devbooks-entropy-monitor/scripts/entropy-report.sh +303 -0
  72. package/skills/devbooks-entropy-monitor/templates/thresholds.json +99 -0
  73. package/skills/devbooks-federation/SKILL.md +264 -0
  74. package/skills/devbooks-federation/scripts/federation-check.sh +144 -0
  75. package/skills/devbooks-federation/templates/federation.yaml +89 -0
  76. package/skills/devbooks-impact-analysis/SKILL.md +135 -0
  77. 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
  78. package/skills/devbooks-impact-analysis/scripts/graph-cache.sh +214 -0
  79. package/skills/devbooks-implementation-plan/SKILL.md +83 -0
  80. 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
  81. package/skills/devbooks-index-bootstrap/SKILL.md +240 -0
  82. package/skills/devbooks-proposal-author/SKILL.md +83 -0
  83. 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
  84. package/skills/devbooks-proposal-challenger/SKILL.md +86 -0
  85. 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
  86. 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
  87. package/skills/devbooks-proposal-debate-workflow/SKILL.md +78 -0
  88. 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
  89. 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
  90. package/skills/devbooks-proposal-debate-workflow/scripts/proposal-debate-check.sh +102 -0
  91. package/skills/devbooks-proposal-judge/SKILL.md +78 -0
  92. 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
  93. package/skills/devbooks-router/SKILL.md +346 -0
  94. package/skills/devbooks-spec-contract/SKILL.md +191 -0
  95. package/skills/devbooks-spec-contract/references/API/350/256/276/350/256/241/346/214/207/345/215/227.md +349 -0
  96. 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
  97. 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
  98. 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
  99. package/skills/devbooks-spec-contract/scripts/implicit-change-detect.sh +378 -0
  100. package/skills/devbooks-spec-gardener/SKILL.md +72 -0
  101. 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
  102. package/skills/devbooks-test-owner/SKILL.md +172 -0
  103. 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
  104. 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
  105. 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
  106. 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
  107. package/skills/devbooks-test-owner/references//346/265/213/350/257/225/351/251/261/345/212/250.md +394 -0
  108. 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
  109. package/skills/devbooks-test-reviewer/SKILL.md +189 -0
  110. package/templates/.devbooks/config.yaml +88 -0
  111. package/templates/claude-commands/devbooks/apply.md +38 -0
  112. package/templates/claude-commands/devbooks/archive.md +33 -0
  113. package/templates/claude-commands/devbooks/backport.md +19 -0
  114. package/templates/claude-commands/devbooks/bootstrap.md +19 -0
  115. package/templates/claude-commands/devbooks/c4.md +19 -0
  116. package/templates/claude-commands/devbooks/challenger.md +19 -0
  117. package/templates/claude-commands/devbooks/code.md +19 -0
  118. package/templates/claude-commands/devbooks/debate.md +19 -0
  119. package/templates/claude-commands/devbooks/delivery.md +19 -0
  120. package/templates/claude-commands/devbooks/design.md +19 -0
  121. package/templates/claude-commands/devbooks/entropy.md +19 -0
  122. package/templates/claude-commands/devbooks/federation.md +19 -0
  123. package/templates/claude-commands/devbooks/gardener.md +19 -0
  124. package/templates/claude-commands/devbooks/impact.md +19 -0
  125. package/templates/claude-commands/devbooks/index.md +19 -0
  126. package/templates/claude-commands/devbooks/judge.md +19 -0
  127. package/templates/claude-commands/devbooks/plan.md +19 -0
  128. package/templates/claude-commands/devbooks/proposal.md +19 -0
  129. package/templates/claude-commands/devbooks/quick.md +42 -0
  130. package/templates/claude-commands/devbooks/review.md +19 -0
  131. package/templates/claude-commands/devbooks/router.md +19 -0
  132. package/templates/claude-commands/devbooks/spec.md +19 -0
  133. package/templates/claude-commands/devbooks/test-review.md +19 -0
  134. package/templates/claude-commands/devbooks/test.md +19 -0
  135. package/templates/dev-playbooks/README.md +458 -0
  136. package/templates/dev-playbooks/changes/.gitkeep +1 -0
  137. package/templates/dev-playbooks/constitution.md +116 -0
  138. package/templates/dev-playbooks/project.md +96 -0
  139. package/templates/dev-playbooks/scripts/.gitkeep +1 -0
  140. package/templates/dev-playbooks/specs/_meta/anti-patterns/.gitkeep +2 -0
  141. package/templates/dev-playbooks/specs/_meta/glossary.md +47 -0
  142. package/templates/dev-playbooks/specs/_meta/project-profile.md +79 -0
  143. 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)"