mindsystem-cc 3.18.1 → 3.19.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.
@@ -0,0 +1,379 @@
1
+ #!/bin/bash
2
+ #
3
+ # doctor-scan.sh
4
+ # Single-pass diagnostic scan of the .planning/ tree.
5
+ # Reports on 6 health check categories with structured output.
6
+ #
7
+ # Usage: ./scripts/doctor-scan.sh
8
+ #
9
+ # Exit codes:
10
+ # 0 — all checks pass
11
+ # 1 — one or more checks failed
12
+ # 2 — .planning/ or config.json missing (cannot scan)
13
+
14
+ set -e
15
+
16
+ # --- Find .planning from git root ---
17
+ GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
18
+ if [ -z "$GIT_ROOT" ]; then
19
+ echo "Error: Not in a git repository"
20
+ exit 2
21
+ fi
22
+
23
+ PLANNING="$GIT_ROOT/.planning"
24
+ if [ ! -d "$PLANNING" ]; then
25
+ echo "Error: No .planning/ directory found"
26
+ exit 2
27
+ fi
28
+
29
+ CONFIG="$PLANNING/config.json"
30
+ if [ ! -f "$CONFIG" ]; then
31
+ echo "Error: No config.json found at $CONFIG"
32
+ exit 2
33
+ fi
34
+
35
+ MILESTONES_FILE="$PLANNING/MILESTONES.md"
36
+ PHASES_DIR="$PLANNING/phases"
37
+ MILESTONES_DIR="$PLANNING/milestones"
38
+ KNOWLEDGE_DIR="$PLANNING/knowledge"
39
+
40
+ PASS_COUNT=0
41
+ FAIL_COUNT=0
42
+ SKIP_COUNT=0
43
+ FAILED_CHECKS=""
44
+
45
+ # --- Helper: record check result ---
46
+ record_result() {
47
+ local status="$1"
48
+ local name="$2"
49
+ case "$status" in
50
+ PASS) PASS_COUNT=$((PASS_COUNT + 1)) ;;
51
+ FAIL) FAIL_COUNT=$((FAIL_COUNT + 1)); FAILED_CHECKS="$FAILED_CHECKS $name" ;;
52
+ SKIP) SKIP_COUNT=$((SKIP_COUNT + 1)) ;;
53
+ esac
54
+ }
55
+
56
+ # ============================================================
57
+ # CHECK 1: Subsystem Vocabulary
58
+ # ============================================================
59
+ echo "=== Subsystem Vocabulary ==="
60
+
61
+ SUBSYSTEM_COUNT=$(jq -r '.subsystems // [] | length' "$CONFIG" 2>/dev/null || echo "0")
62
+
63
+ if [ "$SUBSYSTEM_COUNT" -eq 0 ]; then
64
+ echo "Status: FAIL"
65
+ echo "No subsystems array in config.json (or empty)"
66
+ record_result "FAIL" "Subsystem Vocabulary"
67
+ else
68
+ echo "Subsystems: $SUBSYSTEM_COUNT configured"
69
+ jq -r '.subsystems[]' "$CONFIG" 2>/dev/null | sed 's/^/ - /'
70
+
71
+ # Run artifact scan to check for mismatches
72
+ SCAN_SCRIPT="$(dirname "$0")/scan-artifact-subsystems.sh"
73
+ if [ -x "$SCAN_SCRIPT" ]; then
74
+ CANONICAL=$(jq -r '.subsystems[]' "$CONFIG" 2>/dev/null)
75
+ ARTIFACT_VALUES=$("$SCAN_SCRIPT" --values-only 2>/dev/null | grep -v '^===' | sort -u)
76
+ MISMATCHES=""
77
+ while IFS= read -r val; do
78
+ [ -z "$val" ] && continue
79
+ if ! echo "$CANONICAL" | grep -qx "$val"; then
80
+ MISMATCHES="$MISMATCHES $val"
81
+ fi
82
+ done <<< "$ARTIFACT_VALUES"
83
+
84
+ if [ -n "$MISMATCHES" ]; then
85
+ echo "Status: FAIL"
86
+ echo "Artifact values not in canonical list:$MISMATCHES"
87
+ record_result "FAIL" "Subsystem Vocabulary"
88
+ else
89
+ ARTIFACT_COUNT=$("$SCAN_SCRIPT" --values-only 2>/dev/null | grep -v '^===' | wc -l | tr -d ' ')
90
+ echo "Artifacts scanned: $ARTIFACT_COUNT (all OK)"
91
+ echo "Status: PASS"
92
+ record_result "PASS" "Subsystem Vocabulary"
93
+ fi
94
+ else
95
+ echo "Status: PASS"
96
+ echo "(scan-artifact-subsystems.sh not found — skipped artifact validation)"
97
+ record_result "PASS" "Subsystem Vocabulary"
98
+ fi
99
+ fi
100
+ echo ""
101
+
102
+ # ============================================================
103
+ # CHECK 2: Milestone Directory Structure
104
+ # ============================================================
105
+ echo "=== Milestone Directory Structure ==="
106
+
107
+ if [ ! -d "$MILESTONES_DIR" ]; then
108
+ # No milestones directory at all — check if MILESTONES.md has entries
109
+ if [ -f "$MILESTONES_FILE" ] && grep -q "^## " "$MILESTONES_FILE" 2>/dev/null; then
110
+ echo "Status: FAIL"
111
+ echo "MILESTONES.md has entries but no milestones/ directory"
112
+ record_result "FAIL" "Milestone Directory Structure"
113
+ else
114
+ echo "Status: SKIP"
115
+ echo "No completed milestones"
116
+ record_result "SKIP" "Milestone Directory Structure"
117
+ fi
118
+ else
119
+ # Look for flat files matching v*-*.md directly in milestones/
120
+ FLAT_FILES=""
121
+ FLAT_COUNT=0
122
+ for f in "$MILESTONES_DIR"/v*-*.md; do
123
+ [ -f "$f" ] || continue
124
+ FLAT_FILES="$FLAT_FILES $(basename "$f")"$'\n'
125
+ FLAT_COUNT=$((FLAT_COUNT + 1))
126
+ done
127
+
128
+ if [ "$FLAT_COUNT" -gt 0 ]; then
129
+ echo "Status: FAIL"
130
+ echo "Found $FLAT_COUNT flat file(s) in milestones/ (old format):"
131
+ echo "$FLAT_FILES"
132
+ # Check if corresponding versioned directories exist
133
+ for f in "$MILESTONES_DIR"/v*-*.md; do
134
+ [ -f "$f" ] || continue
135
+ fname=$(basename "$f")
136
+ # Extract version prefix: v0.1-ROADMAP.md -> v0.1
137
+ version=$(echo "$fname" | sed 's/^\(v[0-9.]*\)-.*/\1/')
138
+ if [ -d "$MILESTONES_DIR/$version" ]; then
139
+ echo " $fname → directory $version/ exists (can restructure)"
140
+ else
141
+ echo " $fname → directory $version/ missing (need to create)"
142
+ fi
143
+ done
144
+ record_result "FAIL" "Milestone Directory Structure"
145
+ else
146
+ # Count versioned directories
147
+ DIR_COUNT=0
148
+ for d in "$MILESTONES_DIR"/v*/; do
149
+ [ -d "$d" ] || continue
150
+ DIR_COUNT=$((DIR_COUNT + 1))
151
+ done
152
+
153
+ if [ "$DIR_COUNT" -eq 0 ]; then
154
+ echo "Status: SKIP"
155
+ echo "No completed milestones"
156
+ record_result "SKIP" "Milestone Directory Structure"
157
+ else
158
+ echo "Status: PASS"
159
+ echo "$DIR_COUNT versioned milestone directories"
160
+ record_result "PASS" "Milestone Directory Structure"
161
+ fi
162
+ fi
163
+ fi
164
+ echo ""
165
+
166
+ # ============================================================
167
+ # CHECK 3: Phase Archival
168
+ # ============================================================
169
+ echo "=== Phase Archival ==="
170
+
171
+ if [ ! -f "$MILESTONES_FILE" ] || ! grep -q "Phases completed" "$MILESTONES_FILE" 2>/dev/null; then
172
+ echo "Status: SKIP"
173
+ echo "No completed milestones with phase ranges in MILESTONES.md"
174
+ record_result "SKIP" "Phase Archival"
175
+ else
176
+ ORPHAN_COUNT=0
177
+ ORPHAN_LIST=""
178
+
179
+ # Parse each milestone's phase range from MILESTONES.md
180
+ while IFS= read -r line; do
181
+ # Extract range like "1-6" or "7-8"
182
+ range=$(echo "$line" | grep -o '[0-9]\+-[0-9]\+')
183
+ [ -z "$range" ] && continue
184
+ range_start=$(echo "$range" | cut -d'-' -f1)
185
+ range_end=$(echo "$range" | cut -d'-' -f2)
186
+
187
+ # Check if any phase directories in this range still exist in phases/
188
+ for i in $(seq "$range_start" "$range_end"); do
189
+ phase_prefix=$(printf "%02d" "$i")
190
+ for dir in "$PHASES_DIR"/${phase_prefix}-*/; do
191
+ [ -d "$dir" ] || continue
192
+ dirname=$(basename "$dir")
193
+ ORPHAN_COUNT=$((ORPHAN_COUNT + 1))
194
+ ORPHAN_LIST="$ORPHAN_LIST $dirname (should be archived)"$'\n'
195
+ done
196
+ done
197
+ done < <(grep "Phases completed" "$MILESTONES_FILE")
198
+
199
+ if [ "$ORPHAN_COUNT" -gt 0 ]; then
200
+ echo "Status: FAIL"
201
+ echo "Found $ORPHAN_COUNT orphaned phase directories from completed milestones:"
202
+ echo "$ORPHAN_LIST"
203
+ record_result "FAIL" "Phase Archival"
204
+ else
205
+ echo "Status: PASS"
206
+ echo "All completed milestone phases are archived"
207
+ record_result "PASS" "Phase Archival"
208
+ fi
209
+ fi
210
+ echo ""
211
+
212
+ # ============================================================
213
+ # CHECK 4: Knowledge Files
214
+ # ============================================================
215
+ echo "=== Knowledge Files ==="
216
+
217
+ if [ "$SUBSYSTEM_COUNT" -eq 0 ]; then
218
+ echo "Status: SKIP"
219
+ echo "No subsystems configured — knowledge check requires subsystem vocabulary"
220
+ record_result "SKIP" "Knowledge Files"
221
+ elif [ ! -d "$KNOWLEDGE_DIR" ]; then
222
+ echo "Status: FAIL"
223
+ echo "Knowledge directory missing: .planning/knowledge/"
224
+ echo "Expected files for $SUBSYSTEM_COUNT subsystems"
225
+ record_result "FAIL" "Knowledge Files"
226
+ else
227
+ MISSING_KNOWLEDGE=""
228
+ MISSING_COUNT=0
229
+ PRESENT_COUNT=0
230
+ while IFS= read -r subsystem; do
231
+ [ -z "$subsystem" ] && continue
232
+ if [ -f "$KNOWLEDGE_DIR/$subsystem.md" ]; then
233
+ PRESENT_COUNT=$((PRESENT_COUNT + 1))
234
+ else
235
+ MISSING_COUNT=$((MISSING_COUNT + 1))
236
+ MISSING_KNOWLEDGE="$MISSING_KNOWLEDGE $subsystem.md"$'\n'
237
+ fi
238
+ done < <(jq -r '.subsystems[]' "$CONFIG" 2>/dev/null)
239
+
240
+ # Check for orphaned knowledge files (not in subsystem list)
241
+ ORPHAN_KNOWLEDGE=""
242
+ ORPHAN_K_COUNT=0
243
+ for f in "$KNOWLEDGE_DIR"/*.md; do
244
+ [ -f "$f" ] || continue
245
+ fname=$(basename "$f" .md)
246
+ if ! jq -r '.subsystems[]' "$CONFIG" 2>/dev/null | grep -qx "$fname"; then
247
+ ORPHAN_K_COUNT=$((ORPHAN_K_COUNT + 1))
248
+ ORPHAN_KNOWLEDGE="$ORPHAN_KNOWLEDGE $fname.md (not in subsystems list)"$'\n'
249
+ fi
250
+ done
251
+
252
+ if [ "$MISSING_COUNT" -gt 0 ] || [ "$ORPHAN_K_COUNT" -gt 0 ]; then
253
+ echo "Status: FAIL"
254
+ echo "Coverage: $PRESENT_COUNT/$SUBSYSTEM_COUNT subsystems have knowledge files"
255
+ if [ "$MISSING_COUNT" -gt 0 ]; then
256
+ echo "Missing:"
257
+ echo "$MISSING_KNOWLEDGE"
258
+ fi
259
+ if [ "$ORPHAN_K_COUNT" -gt 0 ]; then
260
+ echo "Orphaned:"
261
+ echo "$ORPHAN_KNOWLEDGE"
262
+ fi
263
+ record_result "FAIL" "Knowledge Files"
264
+ else
265
+ echo "Status: PASS"
266
+ echo "All $SUBSYSTEM_COUNT subsystems have knowledge files"
267
+ record_result "PASS" "Knowledge Files"
268
+ fi
269
+ fi
270
+ echo ""
271
+
272
+ # ============================================================
273
+ # CHECK 5: Phase Summaries
274
+ # ============================================================
275
+ echo "=== Phase Summaries ==="
276
+
277
+ if [ ! -d "$MILESTONES_DIR" ]; then
278
+ echo "Status: SKIP"
279
+ echo "No milestones directory"
280
+ record_result "SKIP" "Phase Summaries"
281
+ else
282
+ MISSING_SUMMARIES=""
283
+ MISSING_S_COUNT=0
284
+ CHECKED=0
285
+
286
+ for d in "$MILESTONES_DIR"/v*/; do
287
+ [ -d "$d" ] || continue
288
+ CHECKED=$((CHECKED + 1))
289
+ version=$(basename "$d")
290
+ if [ ! -f "$d/PHASE-SUMMARIES.md" ]; then
291
+ MISSING_S_COUNT=$((MISSING_S_COUNT + 1))
292
+ MISSING_SUMMARIES="$MISSING_SUMMARIES $version/PHASE-SUMMARIES.md"$'\n'
293
+ fi
294
+ done
295
+
296
+ if [ "$CHECKED" -eq 0 ]; then
297
+ echo "Status: SKIP"
298
+ echo "No versioned milestone directories"
299
+ record_result "SKIP" "Phase Summaries"
300
+ elif [ "$MISSING_S_COUNT" -gt 0 ]; then
301
+ echo "Status: FAIL"
302
+ echo "Missing PHASE-SUMMARIES.md in $MISSING_S_COUNT milestone(s):"
303
+ echo "$MISSING_SUMMARIES"
304
+ record_result "FAIL" "Phase Summaries"
305
+ else
306
+ echo "Status: PASS"
307
+ echo "All $CHECKED milestones have PHASE-SUMMARIES.md"
308
+ record_result "PASS" "Phase Summaries"
309
+ fi
310
+ fi
311
+ echo ""
312
+
313
+ # ============================================================
314
+ # CHECK 6: PLAN Cleanup
315
+ # ============================================================
316
+ echo "=== PLAN Cleanup ==="
317
+
318
+ if [ ! -f "$MILESTONES_FILE" ] || ! grep -q "Phases completed" "$MILESTONES_FILE" 2>/dev/null; then
319
+ echo "Status: SKIP"
320
+ echo "No completed milestones — active phase PLANs are expected"
321
+ record_result "SKIP" "PLAN Cleanup"
322
+ else
323
+ LEFTOVER_PLANS=""
324
+ LEFTOVER_COUNT=0
325
+
326
+ # Check phases/ for PLANs belonging to completed milestones
327
+ while IFS= read -r line; do
328
+ range=$(echo "$line" | grep -o '[0-9]\+-[0-9]\+')
329
+ [ -z "$range" ] && continue
330
+ range_start=$(echo "$range" | cut -d'-' -f1)
331
+ range_end=$(echo "$range" | cut -d'-' -f2)
332
+
333
+ for i in $(seq "$range_start" "$range_end"); do
334
+ phase_prefix=$(printf "%02d" "$i")
335
+ for plan in "$PHASES_DIR"/${phase_prefix}-*/*-PLAN.md; do
336
+ [ -f "$plan" ] || continue
337
+ LEFTOVER_COUNT=$((LEFTOVER_COUNT + 1))
338
+ LEFTOVER_PLANS="$LEFTOVER_PLANS $(echo "$plan" | sed "s|$GIT_ROOT/.planning/||")"$'\n'
339
+ done
340
+ done
341
+ done < <(grep "Phases completed" "$MILESTONES_FILE")
342
+
343
+ # Check archived milestone phase directories for leftover PLANs
344
+ for d in "$MILESTONES_DIR"/v*/phases/*/; do
345
+ [ -d "$d" ] || continue
346
+ for plan in "$d"*-PLAN.md; do
347
+ [ -f "$plan" ] || continue
348
+ LEFTOVER_COUNT=$((LEFTOVER_COUNT + 1))
349
+ LEFTOVER_PLANS="$LEFTOVER_PLANS $(echo "$plan" | sed "s|$GIT_ROOT/.planning/||")"$'\n'
350
+ done
351
+ done
352
+
353
+ if [ "$LEFTOVER_COUNT" -gt 0 ]; then
354
+ echo "Status: FAIL"
355
+ echo "Found $LEFTOVER_COUNT leftover PLAN file(s) in completed phases:"
356
+ echo "$LEFTOVER_PLANS"
357
+ record_result "FAIL" "PLAN Cleanup"
358
+ else
359
+ echo "Status: PASS"
360
+ echo "No leftover PLAN files in completed phases"
361
+ record_result "PASS" "PLAN Cleanup"
362
+ fi
363
+ fi
364
+ echo ""
365
+
366
+ # ============================================================
367
+ # SUMMARY
368
+ # ============================================================
369
+ TOTAL=$((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))
370
+ echo "=== Summary ==="
371
+ echo "Checks: $TOTAL total, $PASS_COUNT passed, $FAIL_COUNT failed, $SKIP_COUNT skipped"
372
+
373
+ if [ "$FAIL_COUNT" -gt 0 ]; then
374
+ echo "Issues:$FAILED_CHECKS"
375
+ exit 1
376
+ else
377
+ echo "All checks passed"
378
+ exit 0
379
+ fi