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.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +463 -0
  3. package/bin/devbooks.js +986 -0
  4. package/package.json +41 -0
  5. package/skills/Skill-Development-Guide.md +249 -0
  6. package/skills/Skills-Usage-Guide.md +447 -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/universal-gating-protocol.md +114 -0
  10. package/skills/_template/config-discovery-template.md +126 -0
  11. package/skills/devbooks-brownfield-bootstrap/SKILL.md +168 -0
  12. package/skills/devbooks-brownfield-bootstrap/references/10-glossary-template.md +42 -0
  13. package/skills/devbooks-brownfield-bootstrap/references/brownfield-bootstrap-prompt.md +115 -0
  14. package/skills/devbooks-brownfield-bootstrap/references/brownfield-bootstrap.md +96 -0
  15. package/skills/devbooks-brownfield-bootstrap/references/code-navigation-strategy.md +203 -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-architecture-map-prompt.md +33 -0
  20. package/skills/devbooks-c4-map/references/layered-constraint-checklist.md +185 -0
  21. package/skills/devbooks-code-review/SKILL.md +175 -0
  22. package/skills/devbooks-code-review/references/code-review-prompt.md +100 -0
  23. package/skills/devbooks-code-review/references/code-smell-cheatsheet.md +498 -0
  24. package/skills/devbooks-code-review/references/pr-template-and-guidelines.md +321 -0
  25. package/skills/devbooks-code-review/references/resource-management-review-checklist.md +311 -0
  26. package/skills/devbooks-coder/SKILL.md +219 -0
  27. package/skills/devbooks-coder/references/code-implementation-prompt.md +74 -0
  28. package/skills/devbooks-coder/references/coding-style-guidelines.md +351 -0
  29. package/skills/devbooks-coder/references/error-code-standard.md +463 -0
  30. package/skills/devbooks-coder/references/logging-standard.md +329 -0
  31. package/skills/devbooks-coder/references/low-risk-modification-techniques.md +275 -0
  32. package/skills/devbooks-delivery-workflow/SKILL.md +217 -0
  33. package/skills/devbooks-delivery-workflow/references/9-change-verification-traceability-template.md +133 -0
  34. package/skills/devbooks-delivery-workflow/references/delivery-acceptance-workflow.md +177 -0
  35. package/skills/devbooks-delivery-workflow/references/prototype-production-dual-track.md +169 -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 +1039 -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 +467 -0
  42. package/skills/devbooks-delivery-workflow/scripts/change-spec-delta-scaffold.sh +135 -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 +365 -0
  46. package/skills/devbooks-delivery-workflow/scripts/guardrail-check.sh +516 -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/design-backport-prompt.md +132 -0
  64. package/skills/devbooks-design-doc/SKILL.md +121 -0
  65. package/skills/devbooks-design-doc/references/design-doc-prompt.md +188 -0
  66. package/skills/devbooks-design-doc/references/microservice-design-checklist.md +149 -0
  67. package/skills/devbooks-design-doc/references/privacy-compliance-checklist.md +240 -0
  68. package/skills/devbooks-entropy-monitor/SKILL.md +188 -0
  69. package/skills/devbooks-entropy-monitor/references/entropy-metrics-methodology.md +218 -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/impact-analysis-prompt.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/implementation-plan-prompt.md +95 -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/proposal-authoring-prompt.md +66 -0
  84. package/skills/devbooks-proposal-challenger/SKILL.md +86 -0
  85. package/skills/devbooks-proposal-challenger/references/ethics-and-compliance-checklist.md +176 -0
  86. package/skills/devbooks-proposal-challenger/references/proposal-challenge-prompt.md +57 -0
  87. package/skills/devbooks-proposal-debate-workflow/SKILL.md +78 -0
  88. package/skills/devbooks-proposal-debate-workflow/references/11-proposal-debate-template.md +35 -0
  89. package/skills/devbooks-proposal-debate-workflow/references/proposal-debate-workflow.md +24 -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/proposal-judge-prompt.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-design-guide.md +349 -0
  96. package/skills/devbooks-spec-contract/references/contract-and-data-definition-prompt.md +85 -0
  97. package/skills/devbooks-spec-contract/references/implicit-change-detection-prompt.md +183 -0
  98. package/skills/devbooks-spec-contract/references/spec-change-prompt.md +63 -0
  99. package/skills/devbooks-spec-contract/scripts/implicit-change-detect.sh +378 -0
  100. package/skills/devbooks-spec-gardener/SKILL.md +73 -0
  101. package/skills/devbooks-spec-gardener/references/spec-gardener-prompt.md +41 -0
  102. package/skills/devbooks-test-owner/SKILL.md +173 -0
  103. package/skills/devbooks-test-owner/references/9-change-verification-traceability-template.md +133 -0
  104. package/skills/devbooks-test-owner/references/async-system-test-strategy.md +316 -0
  105. package/skills/devbooks-test-owner/references/decoupling-techniques-cheatsheet.md +269 -0
  106. package/skills/devbooks-test-owner/references/test-code-prompt.md +171 -0
  107. package/skills/devbooks-test-owner/references/test-driven-development.md +351 -0
  108. package/skills/devbooks-test-owner/references/test-layering-strategy.md +281 -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 +20 -0
  115. package/templates/claude-commands/devbooks/c4.md +20 -0
  116. package/templates/claude-commands/devbooks/challenger.md +19 -0
  117. package/templates/claude-commands/devbooks/code.md +20 -0
  118. package/templates/claude-commands/devbooks/debate.md +20 -0
  119. package/templates/claude-commands/devbooks/delivery.md +20 -0
  120. package/templates/claude-commands/devbooks/design.md +20 -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 +20 -0
  128. package/templates/claude-commands/devbooks/proposal.md +20 -0
  129. package/templates/claude-commands/devbooks/quick.md +43 -0
  130. package/templates/claude-commands/devbooks/review.md +20 -0
  131. package/templates/claude-commands/devbooks/router.md +19 -0
  132. package/templates/claude-commands/devbooks/spec.md +20 -0
  133. package/templates/claude-commands/devbooks/test-review.md +19 -0
  134. package/templates/claude-commands/devbooks/test.md +20 -0
  135. package/templates/dev-playbooks/changes/.gitkeep +1 -0
  136. package/templates/dev-playbooks/constitution.md +116 -0
  137. package/templates/dev-playbooks/project.md +96 -0
  138. package/templates/dev-playbooks/scripts/.gitkeep +1 -0
  139. package/templates/dev-playbooks/specs/_meta/anti-patterns/.gitkeep +2 -0
  140. package/templates/dev-playbooks/specs/_meta/glossary.md +48 -0
  141. package/templates/dev-playbooks/specs/_meta/project-profile.md +79 -0
  142. 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)"