context-engineer 1.1.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 (74) hide show
  1. package/README.md +88 -0
  2. package/bin/cli.mjs +91 -0
  3. package/lib/copy.mjs +102 -0
  4. package/lib/init.mjs +166 -0
  5. package/lib/prompts.mjs +144 -0
  6. package/lib/update.mjs +198 -0
  7. package/package.json +35 -0
  8. package/templates/checksums.json +68 -0
  9. package/templates/claude/.claude/rules/context-maintenance.md +38 -0
  10. package/templates/claude/.claude/rules/experience-capture.md +46 -0
  11. package/templates/claude/.claude/settings.project.json +22 -0
  12. package/templates/claude/.claude/skills/bootstrap/SKILL.md +223 -0
  13. package/templates/claude/.claude/skills/dev/SKILL.md +119 -0
  14. package/templates/claude/.claude/skills/dev-capture/SKILL.md +111 -0
  15. package/templates/claude/.claude/skills/dev-commit/SKILL.md +90 -0
  16. package/templates/claude/.claude/skills/dev-decompose/SKILL.md +113 -0
  17. package/templates/claude/.claude/skills/dev-deps/SKILL.md +108 -0
  18. package/templates/claude/.claude/skills/dev-execute/SKILL.md +196 -0
  19. package/templates/claude/.claude/skills/dev-prd/SKILL.md +100 -0
  20. package/templates/claude/.claude/skills/dev-quality/SKILL.md +109 -0
  21. package/templates/claude/.claude/skills/dev-requirements/SKILL.md +75 -0
  22. package/templates/claude/.claude/skills/review-context/SKILL.md +120 -0
  23. package/templates/claude/.claude/skills/sync/SKILL.md +107 -0
  24. package/templates/claude/.claude/skills/update-context/SKILL.md +105 -0
  25. package/templates/claude/.claude/workflow/agents/implementer.md +65 -0
  26. package/templates/claude/.claude/workflow/agents/reviewer.md +96 -0
  27. package/templates/claude/.claude/workflow/agents/team-config.md +97 -0
  28. package/templates/claude/.claude/workflow/agents/tester.md +98 -0
  29. package/templates/claude/.claude/workflow/interfaces/phase-contract.md +157 -0
  30. package/templates/claude/CLAUDE.md +50 -0
  31. package/templates/core/.context/_meta/concepts.md +9 -0
  32. package/templates/core/.context/_meta/drift-report.md +16 -0
  33. package/templates/core/.context/_meta/last-sync.json +6 -0
  34. package/templates/core/.context/_meta/schema.md +242 -0
  35. package/templates/core/.context/architecture/api-surface.md +52 -0
  36. package/templates/core/.context/architecture/class-index.md +49 -0
  37. package/templates/core/.context/architecture/data-flow.md +103 -0
  38. package/templates/core/.context/architecture/data-model.md +35 -0
  39. package/templates/core/.context/architecture/decisions/001-template.md +35 -0
  40. package/templates/core/.context/architecture/dependencies.md +35 -0
  41. package/templates/core/.context/architecture/infrastructure.md +42 -0
  42. package/templates/core/.context/architecture/module-graph.md +68 -0
  43. package/templates/core/.context/architecture/overview.md +87 -0
  44. package/templates/core/.context/business/domain-model.md +43 -0
  45. package/templates/core/.context/business/glossary.md +23 -0
  46. package/templates/core/.context/business/overview.md +29 -0
  47. package/templates/core/.context/business/workflows.md +61 -0
  48. package/templates/core/.context/constitution.md +84 -0
  49. package/templates/core/.context/conventions/code-style.md +47 -0
  50. package/templates/core/.context/conventions/error-handling.md +50 -0
  51. package/templates/core/.context/conventions/git.md +46 -0
  52. package/templates/core/.context/conventions/patterns.md +41 -0
  53. package/templates/core/.context/conventions/testing.md +49 -0
  54. package/templates/core/.context/experience/debugging.md +21 -0
  55. package/templates/core/.context/experience/incidents.md +26 -0
  56. package/templates/core/.context/experience/lessons.md +23 -0
  57. package/templates/core/.context/experience/performance.md +29 -0
  58. package/templates/core/.context/index.md +93 -0
  59. package/templates/core/.context/progress/backlog.md +23 -0
  60. package/templates/core/.context/progress/status.md +30 -0
  61. package/templates/core/.context/workflow/artifacts/.gitkeep +0 -0
  62. package/templates/core/.context/workflow/config.md +35 -0
  63. package/templates/core/AGENTS.md +53 -0
  64. package/templates/core/scripts/compact-experience.sh +83 -0
  65. package/templates/core/scripts/detect-drift.sh +388 -0
  66. package/templates/core/scripts/extract-structure.sh +757 -0
  67. package/templates/core/scripts/sync-context.sh +510 -0
  68. package/templates/cursor/.cursor/rules/always.mdc +18 -0
  69. package/templates/cursor/.cursor/rules/backend.mdc +16 -0
  70. package/templates/cursor/.cursor/rules/database.mdc +16 -0
  71. package/templates/cursor/.cursor/rules/frontend.mdc +13 -0
  72. package/templates/cursor/.cursorrules +23 -0
  73. package/templates/github/.github/copilot-instructions.md +15 -0
  74. package/templates/github/.github/workflows/context-drift.yml +73 -0
@@ -0,0 +1,388 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # detect-drift.sh — Detect drift between source code and .context/ documentation
4
+ #
5
+ # Usage: bash scripts/detect-drift.sh [--report] [--ci] [--semantic]
6
+ # --report Save results to .context/_meta/drift-report.md
7
+ # --ci Exit with code 1 if drift is detected (for CI/CD)
8
+ # --semantic Enable entity-list comparison (compares documented names vs code)
9
+ #
10
+ # This script checks for drift between source code and context documentation.
11
+ # It does NOT block commits — it only reports warnings.
12
+
13
+ set -euo pipefail
14
+
15
+ CONTEXT_DIR=".context"
16
+ META_DIR="$CONTEXT_DIR/_meta"
17
+ REPORT_FILE="$META_DIR/drift-report.md"
18
+ SAVE_REPORT=false
19
+ CI_MODE=false
20
+ SEMANTIC_MODE=false
21
+ DRIFT_COUNT=0
22
+ REPORT_LINES=()
23
+
24
+ # Parse arguments
25
+ for arg in "$@"; do
26
+ case $arg in
27
+ --report) SAVE_REPORT=true ;;
28
+ --ci) CI_MODE=true ;;
29
+ --semantic) SEMANTIC_MODE=true ;;
30
+ esac
31
+ done
32
+
33
+ # Colors (disabled in CI)
34
+ if [ -t 1 ] && [ "$CI_MODE" = false ]; then
35
+ RED='\033[0;31m'
36
+ YELLOW='\033[1;33m'
37
+ GREEN='\033[0;32m'
38
+ NC='\033[0m'
39
+ else
40
+ RED='' YELLOW='' GREEN='' NC=''
41
+ fi
42
+
43
+ log_ok() {
44
+ echo -e "${GREEN}[OK]${NC} $1"
45
+ REPORT_LINES+=("| $1 | OK | — |")
46
+ }
47
+
48
+ log_warn() {
49
+ echo -e "${YELLOW}[DRIFT]${NC} $1"
50
+ REPORT_LINES+=("| $1 | DRIFT | $2 |")
51
+ DRIFT_COUNT=$((DRIFT_COUNT + 1))
52
+ }
53
+
54
+ log_missing() {
55
+ echo -e "${RED}[MISSING]${NC} $1"
56
+ REPORT_LINES+=("| $1 | MISSING | File does not exist or is empty |")
57
+ DRIFT_COUNT=$((DRIFT_COUNT + 1))
58
+ }
59
+
60
+ echo "========================================="
61
+ echo " Context Drift Detection"
62
+ echo "========================================="
63
+ echo ""
64
+
65
+ # Check that .context/ exists
66
+ if [ ! -d "$CONTEXT_DIR" ]; then
67
+ echo -e "${RED}ERROR: .context/ directory not found. Run bootstrap first.${NC}"
68
+ exit 1
69
+ fi
70
+
71
+ # --- Check 1: Schema/Migration drift ---
72
+ echo "--- Checking Data Model Drift ---"
73
+ DATA_MODEL="$CONTEXT_DIR/architecture/data-model.md"
74
+ if [ -f "$DATA_MODEL" ]; then
75
+ # Check if data model file contains only placeholders
76
+ if grep -q "PLACEHOLDER" "$DATA_MODEL" 2>/dev/null; then
77
+ log_warn "architecture/data-model.md" "Contains placeholder text — not yet populated"
78
+ else
79
+ # Find any migration/model files newer than the data model doc
80
+ NEWER_FILES=""
81
+
82
+ # Check common migration/model patterns
83
+ for pattern in "**/migrations/*" "**/Migration*" "prisma/schema.prisma" "**/models.py" "**/entities/*.cs" "**/entities/*.ts" "**/models/*.ts" "**/models/*.cs"; do
84
+ while IFS= read -r file; do
85
+ if [ -n "$file" ] && [ "$file" -nt "$DATA_MODEL" ]; then
86
+ NEWER_FILES="$NEWER_FILES $file"
87
+ fi
88
+ done < <(find . -path "./.git" -prune -o -path "$pattern" -print 2>/dev/null || true)
89
+ done
90
+
91
+ if [ -n "$NEWER_FILES" ]; then
92
+ log_warn "architecture/data-model.md" "Source files modified after last update:${NEWER_FILES}"
93
+ else
94
+ log_ok "architecture/data-model.md"
95
+ fi
96
+ fi
97
+ else
98
+ log_missing "architecture/data-model.md"
99
+ fi
100
+
101
+ # --- Check 2: API Surface drift ---
102
+ echo "--- Checking API Surface Drift ---"
103
+ API_SURFACE="$CONTEXT_DIR/architecture/api-surface.md"
104
+ if [ -f "$API_SURFACE" ]; then
105
+ if grep -q "PLACEHOLDER" "$API_SURFACE" 2>/dev/null; then
106
+ log_warn "architecture/api-surface.md" "Contains placeholder text — not yet populated"
107
+ else
108
+ NEWER_FILES=""
109
+ for pattern in "**/controllers/*" "**/routes/*" "**/api/*" "**/*Controller*" "**/*Router*"; do
110
+ while IFS= read -r file; do
111
+ if [ -n "$file" ] && [ "$file" -nt "$API_SURFACE" ]; then
112
+ NEWER_FILES="$NEWER_FILES $file"
113
+ fi
114
+ done < <(find . -path "./.git" -prune -o -path "$pattern" -print 2>/dev/null || true)
115
+ done
116
+
117
+ if [ -n "$NEWER_FILES" ]; then
118
+ log_warn "architecture/api-surface.md" "Source files modified after last update:${NEWER_FILES}"
119
+ else
120
+ log_ok "architecture/api-surface.md"
121
+ fi
122
+ fi
123
+ else
124
+ log_missing "architecture/api-surface.md"
125
+ fi
126
+
127
+ # --- Check 3: Dependencies drift ---
128
+ echo "--- Checking Dependencies Drift ---"
129
+ DEPS_FILE="$CONTEXT_DIR/architecture/dependencies.md"
130
+ if [ -f "$DEPS_FILE" ]; then
131
+ if grep -q "PLACEHOLDER" "$DEPS_FILE" 2>/dev/null; then
132
+ log_warn "architecture/dependencies.md" "Contains placeholder text — not yet populated"
133
+ else
134
+ NEWER_FILES=""
135
+ for manifest in "package.json" "package-lock.json" "yarn.lock" "pnpm-lock.yaml" \
136
+ "requirements.txt" "pyproject.toml" "Pipfile.lock" \
137
+ "go.mod" "go.sum" "Cargo.toml" "Cargo.lock" \
138
+ "*.csproj" "*.sln" "pom.xml" "build.gradle"; do
139
+ while IFS= read -r file; do
140
+ if [ -n "$file" ] && [ "$file" -nt "$DEPS_FILE" ]; then
141
+ NEWER_FILES="$NEWER_FILES $file"
142
+ fi
143
+ done < <(find . -maxdepth 3 -name "$manifest" -not -path "./.git/*" 2>/dev/null || true)
144
+ done
145
+
146
+ if [ -n "$NEWER_FILES" ]; then
147
+ log_warn "architecture/dependencies.md" "Package manifests modified after last update:${NEWER_FILES}"
148
+ else
149
+ log_ok "architecture/dependencies.md"
150
+ fi
151
+ fi
152
+ else
153
+ log_missing "architecture/dependencies.md"
154
+ fi
155
+
156
+ # --- Check 4: Module Graph drift ---
157
+ echo "--- Checking Module Graph Drift ---"
158
+ MODULE_GRAPH="$CONTEXT_DIR/architecture/module-graph.md"
159
+ if [ -f "$MODULE_GRAPH" ]; then
160
+ if grep -q "PLACEHOLDER\|TO BE FILLED" "$MODULE_GRAPH" 2>/dev/null; then
161
+ log_warn "architecture/module-graph.md" "Contains placeholder text — not yet populated"
162
+ else
163
+ NEWER_FILES=""
164
+ # Check if any source files are newer than module-graph.md
165
+ for pattern in "*.h" "*.hpp" "*.cs" "*.ts" "*.py" "*.go" "*.rs" "*.java"; do
166
+ while IFS= read -r file; do
167
+ if [ -n "$file" ] && [ "$file" -nt "$MODULE_GRAPH" ]; then
168
+ NEWER_FILES="$NEWER_FILES $(basename "$file")"
169
+ break # One per pattern is enough to trigger
170
+ fi
171
+ done < <(find . -path "./.git" -prune -o -path "./node_modules" -prune -o -name "$pattern" -print 2>/dev/null | head -5 || true)
172
+ done
173
+
174
+ if [ -n "$NEWER_FILES" ]; then
175
+ log_warn "architecture/module-graph.md" "Source files modified after last update (samples:${NEWER_FILES})"
176
+ else
177
+ log_ok "architecture/module-graph.md"
178
+ fi
179
+ fi
180
+ else
181
+ log_missing "architecture/module-graph.md"
182
+ fi
183
+
184
+ # --- Check 5: Class Index drift ---
185
+ echo "--- Checking Class Index Drift ---"
186
+ CLASS_INDEX="$CONTEXT_DIR/architecture/class-index.md"
187
+ if [ -f "$CLASS_INDEX" ]; then
188
+ if grep -q "PLACEHOLDER" "$CLASS_INDEX" 2>/dev/null; then
189
+ log_warn "architecture/class-index.md" "Contains placeholder text — not yet populated"
190
+ else
191
+ NEWER_FILES=""
192
+ for pattern in "*.h" "*.hpp" "*.cs" "*.ts" "*.py" "*.go" "*.rs" "*.java"; do
193
+ while IFS= read -r file; do
194
+ if [ -n "$file" ] && [ "$file" -nt "$CLASS_INDEX" ]; then
195
+ NEWER_FILES="$NEWER_FILES $(basename "$file")"
196
+ break
197
+ fi
198
+ done < <(find . -path "./.git" -prune -o -path "./node_modules" -prune -o -name "$pattern" -print 2>/dev/null | head -5 || true)
199
+ done
200
+
201
+ if [ -n "$NEWER_FILES" ]; then
202
+ log_warn "architecture/class-index.md" "Source files modified after last update (samples:${NEWER_FILES})"
203
+ else
204
+ log_ok "architecture/class-index.md"
205
+ fi
206
+ fi
207
+ else
208
+ log_missing "architecture/class-index.md"
209
+ fi
210
+
211
+ # --- Check 6: Data Flow drift ---
212
+ echo "--- Checking Data Flow Drift ---"
213
+ DATA_FLOW="$CONTEXT_DIR/architecture/data-flow.md"
214
+ if [ -f "$DATA_FLOW" ]; then
215
+ if grep -q "PLACEHOLDER" "$DATA_FLOW" 2>/dev/null; then
216
+ log_warn "architecture/data-flow.md" "Contains placeholder text — not yet populated"
217
+ else
218
+ log_ok "architecture/data-flow.md"
219
+ fi
220
+ else
221
+ log_missing "architecture/data-flow.md"
222
+ fi
223
+
224
+ # --- Check 7: Semantic entity-list comparison (optional) ---
225
+ if [ "$SEMANTIC_MODE" = true ]; then
226
+ echo "--- Semantic Entity Comparison ---"
227
+
228
+ # Helper: compare two sorted name lists, report additions and removals
229
+ semantic_compare() {
230
+ local label="$1"
231
+ local doc_names="$2"
232
+ local code_names="$3"
233
+
234
+ if [ -z "$doc_names" ] || [ -z "$code_names" ]; then
235
+ return # skip if either list is empty (file not populated)
236
+ fi
237
+
238
+ local added removed
239
+ added=$(comm -13 <(echo "$doc_names") <(echo "$code_names") | tr '\n' ', ' | sed 's/,$//')
240
+ removed=$(comm -23 <(echo "$doc_names") <(echo "$code_names") | tr '\n' ', ' | sed 's/,$//')
241
+
242
+ if [ -n "$added" ] || [ -n "$removed" ]; then
243
+ local detail=""
244
+ [ -n "$added" ] && detail="New in code: [${added}]"
245
+ [ -n "$removed" ] && detail="${detail:+$detail | }In doc but not in code: [${removed}]"
246
+ log_warn "$label (semantic)" "$detail"
247
+ else
248
+ log_ok "$label (semantic match)"
249
+ fi
250
+ }
251
+
252
+ # --- Semantic: class-index.md ---
253
+ if [ -f "$CLASS_INDEX" ] && ! grep -q "PLACEHOLDER" "$CLASS_INDEX" 2>/dev/null; then
254
+ # Extract class/interface/struct names from the document (markdown table rows)
255
+ doc_classes=$(grep -oP '^\| \K[A-Z][a-zA-Z0-9_<>]+' "$CLASS_INDEX" 2>/dev/null | sed 's/<.*>//' | sort -u)
256
+ # Extract from source code
257
+ code_classes=$(find . -path "./.git" -prune -o -path "./node_modules" -prune -o \
258
+ \( -name "*.cs" -o -name "*.ts" -o -name "*.py" -o -name "*.java" -o -name "*.go" -o -name "*.h" -o -name "*.hpp" -o -name "*.rs" \) \
259
+ -print 2>/dev/null | xargs grep -ohP '^\s*(export\s+)?(public\s+)?(abstract\s+)?(sealed\s+)?(partial\s+)?(class|interface|struct|trait|enum|record)\s+\K[A-Z][a-zA-Z0-9_]+' 2>/dev/null | sort -u)
260
+ semantic_compare "architecture/class-index.md" "$doc_classes" "$code_classes"
261
+ fi
262
+
263
+ # --- Semantic: dependencies.md ---
264
+ if [ -f "$DEPS_FILE" ] && ! grep -q "PLACEHOLDER" "$DEPS_FILE" 2>/dev/null; then
265
+ # Extract package names from the document
266
+ doc_deps=$(grep -oP '^\| `?\K[a-z@][a-z0-9_./@-]+' "$DEPS_FILE" 2>/dev/null | sort -u)
267
+ # Extract from package.json (if exists)
268
+ code_deps=""
269
+ if [ -f "package.json" ]; then
270
+ code_deps=$(grep -oP '"[a-z@][a-z0-9_./@-]+":\s*"' package.json 2>/dev/null | grep -oP '"[a-z@][a-z0-9_./@-]+"' | tr -d '"' | sort -u)
271
+ elif [ -f "requirements.txt" ]; then
272
+ code_deps=$(grep -oP '^[a-zA-Z][a-zA-Z0-9_-]+' requirements.txt 2>/dev/null | tr '[:upper:]' '[:lower:]' | sort -u)
273
+ elif [ -f "go.mod" ]; then
274
+ code_deps=$(grep -oP '^\s+\K\S+' go.mod 2>/dev/null | grep '/' | sort -u)
275
+ fi
276
+ [ -n "$code_deps" ] && semantic_compare "architecture/dependencies.md" "$doc_deps" "$code_deps"
277
+ fi
278
+
279
+ # --- Semantic: api-surface.md ---
280
+ if [ -f "$API_SURFACE" ] && ! grep -q "PLACEHOLDER" "$API_SURFACE" 2>/dev/null; then
281
+ # Extract endpoint paths from the document (e.g., /api/users, GET /users)
282
+ doc_endpoints=$(grep -oP '(GET|POST|PUT|DELETE|PATCH)\s+\K/[a-zA-Z0-9_/{}-]+' "$API_SURFACE" 2>/dev/null | sort -u)
283
+ if [ -z "$doc_endpoints" ]; then
284
+ # Try extracting from table format: | /api/users | or | `GET /api/users` |
285
+ doc_endpoints=$(grep -oP '^\| `?(/[a-zA-Z0-9_/{}-]+)' "$API_SURFACE" 2>/dev/null | sed 's/^| `\?//' | sort -u)
286
+ fi
287
+ # Only report if we found documented endpoints but skip code extraction (too varied per framework)
288
+ # The timestamp-based check already covers this — semantic check for APIs needs framework-specific logic
289
+ fi
290
+
291
+ # --- Semantic: data-model.md ---
292
+ if [ -f "$DATA_MODEL" ] && ! grep -q "PLACEHOLDER" "$DATA_MODEL" 2>/dev/null; then
293
+ # Extract entity/table names from the document
294
+ doc_entities=$(grep -oP '^\| `?\K[A-Z][a-zA-Z0-9_]+' "$DATA_MODEL" 2>/dev/null | sort -u)
295
+ # Extract from common ORM patterns
296
+ code_entities=""
297
+ # EF Core / C# entities
298
+ if find . -name "*.cs" -path "*/entities/*" -o -name "*.cs" -path "*/models/*" 2>/dev/null | head -1 | grep -q .; then
299
+ code_entities=$(find . \( -path "*/entities/*.cs" -o -path "*/models/*.cs" \) 2>/dev/null | xargs grep -ohP '(class|record)\s+\K[A-Z][a-zA-Z0-9_]+' 2>/dev/null | sort -u)
300
+ # Python models
301
+ elif find . -name "models.py" 2>/dev/null | head -1 | grep -q .; then
302
+ code_entities=$(find . -name "models.py" 2>/dev/null | xargs grep -ohP 'class\s+\K[A-Z][a-zA-Z0-9_]+' 2>/dev/null | sort -u)
303
+ # Prisma
304
+ elif [ -f "prisma/schema.prisma" ]; then
305
+ code_entities=$(grep -oP 'model\s+\K[A-Z][a-zA-Z0-9_]+' prisma/schema.prisma 2>/dev/null | sort -u)
306
+ fi
307
+ [ -n "$code_entities" ] && semantic_compare "architecture/data-model.md" "$doc_entities" "$code_entities"
308
+ fi
309
+ fi
310
+
311
+ # --- Check 8: File size budgets ---
312
+ # (was Check 7 before semantic check was added)
313
+ echo "--- Checking File Size Budgets ---"
314
+ check_size() {
315
+ local file=$1
316
+ local budget=$2
317
+ local label=$3
318
+ if [ -f "$file" ]; then
319
+ local lines
320
+ lines=$(wc -l < "$file")
321
+ if [ "$lines" -gt "$budget" ]; then
322
+ log_warn "$label" "Exceeds budget: ${lines}/${budget} lines"
323
+ else
324
+ log_ok "$label (${lines}/${budget} lines)"
325
+ fi
326
+ fi
327
+ }
328
+
329
+ check_size "$CONTEXT_DIR/constitution.md" 200 "constitution.md"
330
+ check_size "AGENTS.md" 150 "AGENTS.md"
331
+ check_size "$CONTEXT_DIR/architecture/overview.md" 800 "architecture/overview.md"
332
+ check_size "$CONTEXT_DIR/architecture/module-graph.md" 500 "architecture/module-graph.md"
333
+ check_size "$CONTEXT_DIR/architecture/data-flow.md" 500 "architecture/data-flow.md"
334
+ check_size "$CONTEXT_DIR/architecture/class-index.md" 500 "architecture/class-index.md"
335
+ check_size "$CONTEXT_DIR/experience/lessons.md" 500 "experience/lessons.md"
336
+ check_size "$CONTEXT_DIR/experience/debugging.md" 500 "experience/debugging.md"
337
+
338
+ # --- Check 9: Required files existence ---
339
+ echo "--- Checking Required Files ---"
340
+ for required in "index.md" "constitution.md" "business/overview.md" "architecture/overview.md" \
341
+ "architecture/module-graph.md" "architecture/data-flow.md" "architecture/class-index.md" \
342
+ "conventions/code-style.md" "conventions/patterns.md"; do
343
+ if [ -f "$CONTEXT_DIR/$required" ]; then
344
+ log_ok "$required exists"
345
+ else
346
+ log_missing "$required"
347
+ fi
348
+ done
349
+
350
+ # --- Summary ---
351
+ echo ""
352
+ echo "========================================="
353
+ if [ "$DRIFT_COUNT" -eq 0 ]; then
354
+ echo -e "${GREEN}All checks passed. No drift detected.${NC}"
355
+ else
356
+ echo -e "${YELLOW}${DRIFT_COUNT} issue(s) detected.${NC}"
357
+ fi
358
+ echo "========================================="
359
+
360
+ # --- Save report ---
361
+ if [ "$SAVE_REPORT" = true ]; then
362
+ {
363
+ echo "# Drift Detection Report"
364
+ echo ""
365
+ echo "<!-- AUTO-GENERATED by scripts/detect-drift.sh -->"
366
+ echo "<!-- generated-at: $(date -u +%Y-%m-%dT%H:%M:%SZ) -->"
367
+ echo ""
368
+ if [ "$DRIFT_COUNT" -eq 0 ]; then
369
+ echo "## Status: All Clear"
370
+ else
371
+ echo "## Status: ${DRIFT_COUNT} issue(s) detected"
372
+ fi
373
+ echo ""
374
+ echo "| File | Status | Details |"
375
+ echo "|------|--------|---------|"
376
+ for line in "${REPORT_LINES[@]}"; do
377
+ echo "$line"
378
+ done
379
+ } > "$REPORT_FILE"
380
+ echo "Report saved to $REPORT_FILE"
381
+ fi
382
+
383
+ # --- CI mode exit code ---
384
+ if [ "$CI_MODE" = true ] && [ "$DRIFT_COUNT" -gt 0 ]; then
385
+ exit 1
386
+ fi
387
+
388
+ exit 0