self-evolve-framework 1.0.7 → 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 (126) hide show
  1. package/README.md +12 -8
  2. package/bin/cli.js +63 -35
  3. package/package.json +1 -1
  4. package/template/rules/CodeGraph.mdc +23 -0
  5. package/template/rules/Svelte_5.mdc +167 -0
  6. package/template/rules/Svelte_Flow.mdc +176 -0
  7. package/template/rules/Tailwind_CSS_v4.mdc +187 -0
  8. package/template/rules/Tauri.mdc +145 -0
  9. package/template/rules/app-error-pattern.mdc +65 -0
  10. package/template/rules/invoke-safe-pattern.mdc +53 -0
  11. package/template/rules/js.mdc +10 -0
  12. package/template/rules/powershell.mdc +9 -0
  13. package/template/rules/self-evolve.mdc +4 -4
  14. package/template/rules//346/227/245/345/277/227.mdc +15 -0
  15. package/template/rules//350/257/267/346/261/202.mdc +49 -0
  16. package/template/skills/caveman/SKILL.md +49 -0
  17. package/template/skills/check/SKILL.md +393 -0
  18. package/template/skills/check/agents/reviewer-architecture.md +39 -0
  19. package/template/skills/check/agents/reviewer-security.md +39 -0
  20. package/template/skills/check/references/persona-catalog.md +56 -0
  21. package/template/skills/check/references/project-context.md +120 -0
  22. package/template/skills/check/references/public-reply.md +14 -0
  23. package/template/skills/check/scripts/audit_signals.py +666 -0
  24. package/template/skills/check/scripts/run-tests.sh +19 -0
  25. package/template/skills/design/SKILL.md +173 -0
  26. package/template/skills/design/references/design-aesthetic-quality.md +67 -0
  27. package/template/skills/design/references/design-data-viz.md +34 -0
  28. package/template/skills/design/references/design-reference.md +295 -0
  29. package/template/skills/design/references/design-tokens.md +45 -0
  30. package/template/skills/design/references/design-traps.md +43 -0
  31. package/template/skills/design-an-interface/SKILL.md +94 -0
  32. package/template/skills/diagnose/SKILL.md +117 -0
  33. package/template/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
  34. package/template/skills/edit-article/SKILL.md +14 -0
  35. package/template/skills/git-guardrails-claude-code/SKILL.md +95 -0
  36. package/template/skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh +25 -0
  37. package/template/skills/grill-me/SKILL.md +10 -0
  38. package/template/skills/grill-with-docs/ADR-FORMAT.md +47 -0
  39. package/template/skills/grill-with-docs/CONTEXT-FORMAT.md +60 -0
  40. package/template/skills/grill-with-docs/SKILL.md +88 -0
  41. package/template/skills/handoff/SKILL.md +15 -0
  42. package/template/skills/health/SKILL.md +260 -0
  43. package/template/skills/health/agents/inspector-context.md +119 -0
  44. package/template/skills/health/agents/inspector-control.md +84 -0
  45. package/template/skills/health/agents/inspector-maintainability.md +55 -0
  46. package/template/skills/health/scripts/check-agent-context.sh +5 -0
  47. package/template/skills/health/scripts/check-doc-refs.sh +8 -0
  48. package/template/skills/health/scripts/check-maintainability.sh +8 -0
  49. package/template/skills/health/scripts/check-verifier-output.sh +5 -0
  50. package/template/skills/health/scripts/check_agent_context.py +444 -0
  51. package/template/skills/health/scripts/check_doc_refs.py +110 -0
  52. package/template/skills/health/scripts/check_maintainability.py +635 -0
  53. package/template/skills/health/scripts/check_verifier_output.py +116 -0
  54. package/template/skills/health/scripts/collect-data.sh +751 -0
  55. package/template/skills/hunt/SKILL.md +232 -0
  56. package/template/skills/hunt/references/failure-patterns.md +138 -0
  57. package/template/skills/hunt/references/ime-unicode.md +58 -0
  58. package/template/skills/hunt/references/logging-techniques.md +72 -0
  59. package/template/skills/hunt/references/rendering-debug.md +34 -0
  60. package/template/skills/impeccable/SKILL.md +47 -0
  61. package/template/skills/improve-codebase-architecture/DEEPENING.md +37 -0
  62. package/template/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
  63. package/template/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
  64. package/template/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
  65. package/template/skills/improve-codebase-architecture/SKILL.md +81 -0
  66. package/template/skills/learn/SKILL.md +140 -0
  67. package/template/skills/migrate-to-shoehorn/SKILL.md +118 -0
  68. package/template/skills/obsidian-vault/SKILL.md +59 -0
  69. package/template/skills/prototype/LOGIC.md +79 -0
  70. package/template/skills/prototype/SKILL.md +30 -0
  71. package/template/skills/prototype/UI.md +112 -0
  72. package/template/skills/qa/SKILL.md +130 -0
  73. package/template/skills/read/SKILL.md +141 -0
  74. package/template/skills/read/references/read-methods.md +129 -0
  75. package/template/skills/read/scripts/fetch.sh +106 -0
  76. package/template/skills/read/scripts/fetch_feishu.py +251 -0
  77. package/template/skills/read/scripts/fetch_local.py +218 -0
  78. package/template/skills/read/scripts/fetch_weixin.py +107 -0
  79. package/template/skills/request-refactor-plan/SKILL.md +68 -0
  80. package/template/skills/review/SKILL.md +78 -0
  81. package/template/skills/rust-auto-fix/SKILL.md +94 -0
  82. package/template/skills/scaffold-exercises/SKILL.md +106 -0
  83. package/template/skills/sdd-dev/SKILL.md +114 -0
  84. package/template/skills/setup-matt-pocock-skills/SKILL.md +121 -0
  85. package/template/skills/setup-matt-pocock-skills/domain.md +51 -0
  86. package/template/skills/setup-matt-pocock-skills/issue-tracker-github.md +22 -0
  87. package/template/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +23 -0
  88. package/template/skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
  89. package/template/skills/setup-matt-pocock-skills/triage-labels.md +15 -0
  90. package/template/skills/setup-pre-commit/SKILL.md +91 -0
  91. package/template/skills/skillopt-sleep/SKILL.md +1 -1
  92. package/template/skills/svelte-warnings-fix/SKILL.md +94 -0
  93. package/template/skills/tauri-nsis-installer-icon/SKILL.md +92 -0
  94. package/template/skills/tauri-nsis-installer-icon/references/tauri-nsis-schema.md +71 -0
  95. package/template/skills/tb/SKILL.md +62 -0
  96. package/template/skills/tdd/SKILL.md +109 -0
  97. package/template/skills/tdd/deep-modules.md +33 -0
  98. package/template/skills/tdd/interface-design.md +31 -0
  99. package/template/skills/tdd/mocking.md +59 -0
  100. package/template/skills/tdd/refactoring.md +10 -0
  101. package/template/skills/tdd/tests.md +61 -0
  102. package/template/skills/teach/GLOSSARY-FORMAT.md +35 -0
  103. package/template/skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
  104. package/template/skills/teach/MISSION-FORMAT.md +31 -0
  105. package/template/skills/teach/RESOURCES-FORMAT.md +32 -0
  106. package/template/skills/teach/SKILL.md +91 -0
  107. package/template/skills/think/SKILL.md +184 -0
  108. package/template/skills/to-issues/SKILL.md +83 -0
  109. package/template/skills/to-prd/SKILL.md +74 -0
  110. package/template/skills/triage/AGENT-BRIEF.md +168 -0
  111. package/template/skills/triage/OUT-OF-SCOPE.md +101 -0
  112. package/template/skills/triage/SKILL.md +103 -0
  113. package/template/skills/ubiquitous-language/SKILL.md +93 -0
  114. package/template/skills/ver/SKILL.md +62 -0
  115. package/template/skills/write/SKILL.md +209 -0
  116. package/template/skills/write/references/write-en.md +199 -0
  117. package/template/skills/write/references/write-product-localization.md +43 -0
  118. package/template/skills/write/references/write-zh-bilingual.md +59 -0
  119. package/template/skills/write/references/write-zh-prose.md +50 -0
  120. package/template/skills/write/references/write-zh-release-notes.md +40 -0
  121. package/template/skills/write/references/write-zh.md +721 -0
  122. package/template/skills/write-a-skill/SKILL.md +117 -0
  123. package/template/skills/writing-beats/SKILL.md +52 -0
  124. package/template/skills/writing-fragments/SKILL.md +75 -0
  125. package/template/skills/writing-shape/SKILL.md +64 -0
  126. package/template/skills/zoom-out/SKILL.md +7 -0
@@ -0,0 +1,751 @@
1
+ #!/usr/bin/env bash
2
+ # Collect agent configuration data for health audit.
3
+ # Outputs labeled sections for each data source.
4
+ # Run from any directory; uses pwd as the project root.
5
+ #
6
+ # Known failure modes (for interpreting (unavailable) output):
7
+ # jq not installed -> conversation extract and signals print "(unavailable)"; treat as [INSUFFICIENT DATA]
8
+ # python3 not on PATH -> MCP/hooks/allowedTools sections print "(unavailable)"; do not flag those areas
9
+ # settings.local.json absent -> hooks, MCP, allowedTools all show "(unavailable)"; normal for global-settings-only projects
10
+ # MEMORY.md path -> built via sed on pwd; unusual chars produce wrong project key; verify manually if (none) seems wrong
11
+ # Conversation scope -> 2 most recent PREVIOUS .jsonl sampled (live session skipped); fewer than 2 = [LOW CONFIDENCE]
12
+ # MCP token estimate -> assumes ~25 tools/server, ~200 tokens/tool; treat as directional, not precise
13
+ # Tier misclassification -> .next/, __pycache__, .turbo/ can inflate file count; recheck manually if tier feels wrong
14
+ set -euo pipefail
15
+
16
+ P=$(pwd)
17
+ SETTINGS="$P/.claude/settings.local.json"
18
+ TIER="${1:-auto}"
19
+ MODE="${2:-${WAZA_HEALTH_MODE:-summary}}"
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ if [ "${WAZA_HEALTH_DEEP:-0}" = "1" ]; then
22
+ MODE="deep"
23
+ fi
24
+ case "$MODE" in
25
+ summary|deep) ;;
26
+ *) MODE="summary" ;;
27
+ esac
28
+ PROJECT_KEY=$(printf '%s' "$P" | sed 's|[/_]|-|g; s|^-||')
29
+ CONVO_DIR="$HOME/.claude/projects/-${PROJECT_KEY}"
30
+
31
+ resolve_health_helper() {
32
+ local name="$1"
33
+ local installed_path=""
34
+ local candidate=""
35
+
36
+ for candidate in "$SCRIPT_DIR/$name" "./skills/health/scripts/$name"; do
37
+ if [ -f "$candidate" ]; then
38
+ printf '%s\n' "$candidate"
39
+ return 0
40
+ fi
41
+ done
42
+
43
+ installed_path="$(npx skills path tw93/Waza 2>/dev/null || true)"
44
+ if [ -n "$installed_path" ] && [ -f "$installed_path/skills/health/scripts/$name" ]; then
45
+ printf '%s\n' "$installed_path/skills/health/scripts/$name"
46
+ return 0
47
+ fi
48
+
49
+ return 1
50
+ }
51
+
52
+ count_project_files() {
53
+ local count
54
+ count=$(git -C "$P" ls-files 2>/dev/null | wc -l | tr -d ' ' || true)
55
+ if [ -z "$count" ] || [ "$count" = "0" ]; then
56
+ count=$(find "$P" -type f \
57
+ -not -path "*/.git/*" \
58
+ -not -path "*/node_modules/*" \
59
+ -not -path "*/dist/*" \
60
+ -not -path "*/build/*" \
61
+ 2>/dev/null | wc -l | tr -d ' ')
62
+ fi
63
+ printf '%s\n' "${count:-0}"
64
+ }
65
+
66
+ count_contributors() {
67
+ local count
68
+ count=$(git -C "$P" log -n 500 --format='%ae' 2>/dev/null | sort -u | wc -l | tr -d ' ' || true)
69
+ printf '%s\n' "${count:-0}"
70
+ }
71
+
72
+ count_ci_workflows() {
73
+ local count=0
74
+ if [ -d "$P/.github/workflows" ]; then
75
+ count=$(find "$P/.github/workflows" -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | wc -l | tr -d ' ')
76
+ fi
77
+ printf '%s\n' "${count:-0}"
78
+ }
79
+
80
+ count_local_skills() {
81
+ local count=0
82
+ if [ -d "$P/.claude/skills" ]; then
83
+ count=$(find -L "$P/.claude/skills" -maxdepth 4 -name "SKILL.md" 2>/dev/null | while IFS= read -r f; do
84
+ grep -q '^name: health$' "$f" 2>/dev/null && continue
85
+ echo "$f"
86
+ done | wc -l | tr -d ' ')
87
+ fi
88
+ printf '%s\n' "${count:-0}"
89
+ }
90
+
91
+ resolve_symlink() {
92
+ readlink -f "$1" 2>/dev/null && return
93
+ # macOS fallback: resolve symlink chain manually
94
+ local target="$1"
95
+ local depth=0
96
+ while [ -L "$target" ] && [ "$depth" -lt 32 ]; do
97
+ local dir
98
+ dir=$(cd "$(dirname "$target")" && pwd -P)
99
+ target=$(readlink "$target")
100
+ case "$target" in /*) ;; *) target="$dir/$target" ;; esac
101
+ depth=$((depth + 1))
102
+ done
103
+ printf '%s\n' "$target"
104
+ }
105
+
106
+ count_file_lines() {
107
+ local file="$1"
108
+ if [ -f "$file" ]; then
109
+ wc -l < "$file" | tr -d ' '
110
+ else
111
+ echo 0
112
+ fi
113
+ }
114
+
115
+ count_file_words() {
116
+ local file="$1"
117
+ if [ -f "$file" ]; then
118
+ wc -w < "$file" | tr -d ' '
119
+ else
120
+ echo 0
121
+ fi
122
+ }
123
+
124
+ list_rule_files() {
125
+ if [ -d "$P/.claude/rules" ]; then
126
+ find "$P/.claude/rules" -type f -name "*.md" 2>/dev/null | sort || true
127
+ fi
128
+ }
129
+
130
+ print_rule_files() {
131
+ local found=0
132
+ while IFS= read -r f; do
133
+ [ -n "$f" ] || continue
134
+ found=1
135
+ echo "--- $f ---"
136
+ cat "$f"
137
+ done < <(list_rule_files)
138
+ [ "$found" -eq 1 ] || echo "(none)"
139
+ }
140
+
141
+ print_file_summary() {
142
+ local label="$1"
143
+ local file="$2"
144
+
145
+ if [ ! -f "$file" ]; then
146
+ echo "${label}_present: no"
147
+ return
148
+ fi
149
+
150
+ echo "${label}_present: yes"
151
+ echo "${label}_path: $file"
152
+ echo "${label}_lines: $(count_file_lines "$file")"
153
+ echo "${label}_words: $(count_file_words "$file")"
154
+
155
+ local headings
156
+ headings=$(grep -nE '^[[:space:]]*#{1,3}[[:space:]]+' "$file" 2>/dev/null | head -8 || true)
157
+ if [ -n "$headings" ]; then
158
+ echo "${label}_headings:"
159
+ printf '%s\n' "$headings" | sed 's/^/ /'
160
+ fi
161
+ }
162
+
163
+ print_settings_summary() {
164
+ local file="$1"
165
+ if [ ! -f "$file" ]; then
166
+ echo "settings_local_json: no"
167
+ return
168
+ fi
169
+
170
+ echo "settings_local_json: yes"
171
+ echo "settings_local_json_path: $file"
172
+ echo "settings_local_json_lines: $(count_file_lines "$file")"
173
+ echo "settings_local_json_bytes: $(wc -c < "$file" | tr -d ' ')"
174
+ }
175
+
176
+ print_rule_file_summary() {
177
+ local files count=0
178
+ files=$(list_rule_files)
179
+ if [ -z "$files" ]; then
180
+ echo "rule_files: 0"
181
+ return
182
+ fi
183
+
184
+ count=$(printf '%s\n' "$files" | wc -l | tr -d ' ')
185
+ echo "rule_files: $count"
186
+ while IFS= read -r f; do
187
+ [ -n "$f" ] || continue
188
+ echo "path=$f lines=$(count_file_lines "$f") words=$(count_file_words "$f")"
189
+ done <<EOF
190
+ $files
191
+ EOF
192
+ }
193
+
194
+ rules_word_count() {
195
+ local words=0
196
+ if [ -d "$P/.claude/rules" ]; then
197
+ words=$(while IFS= read -r f; do
198
+ [ -n "$f" ] || continue
199
+ cat "$f"
200
+ done < <(list_rule_files) | wc -w | tr -d ' ')
201
+ fi
202
+ printf '%s\n' "${words:-0}"
203
+ }
204
+
205
+ collect_skill_descriptions_raw() {
206
+ if [ -d "$P/.claude/skills" ]; then
207
+ grep -r "^description:" "$P/.claude/skills" 2>/dev/null || true
208
+ fi
209
+ if [ -d "$HOME/.claude/skills" ]; then
210
+ grep -r "^description:" "$HOME/.claude/skills" 2>/dev/null || true
211
+ fi
212
+ }
213
+
214
+ print_skill_descriptions() {
215
+ local out
216
+ out=$(collect_skill_descriptions_raw | sort -u)
217
+ if [ -n "$out" ]; then
218
+ printf '%s\n' "$out"
219
+ else
220
+ echo "(none)"
221
+ fi
222
+ }
223
+
224
+ print_skill_description_summary() {
225
+ local out count
226
+ out=$(collect_skill_descriptions_raw | sort -u)
227
+ if [ -z "$out" ]; then
228
+ echo "skill_descriptions: 0"
229
+ return
230
+ fi
231
+
232
+ count=$(printf '%s\n' "$out" | wc -l | tr -d ' ')
233
+ echo "skill_descriptions: $count"
234
+ printf '%s\n' "$out" | head -20 | awk -F: '{
235
+ path=$1
236
+ line=$0
237
+ sub(/^[^:]*:/, "", line)
238
+ printf "path=%s description_chars=%d\n", path, length(line)
239
+ }'
240
+ if [ "$count" -gt 20 ]; then
241
+ echo "skill_descriptions_truncated: yes"
242
+ fi
243
+ }
244
+
245
+ skill_description_word_count() {
246
+ local words
247
+ words=$(collect_skill_descriptions_raw | wc -w | tr -d ' ')
248
+ printf '%s\n' "${words:-0}"
249
+ }
250
+
251
+ list_skill_files() {
252
+ local dir="$1"
253
+ [ -d "$dir" ] || return 0
254
+ find -L "$dir" -maxdepth 4 -name "SKILL.md" 2>/dev/null | sort || true
255
+ }
256
+
257
+ is_health_skill() {
258
+ grep -q '^name: health$' "$1" 2>/dev/null
259
+ }
260
+
261
+ list_conversation_files() {
262
+ [ -d "$CONVO_DIR" ] || return 0
263
+ ls -1t "$CONVO_DIR"/*.jsonl 2>/dev/null || true
264
+ }
265
+
266
+ print_conversation_file_listing() {
267
+ local out
268
+ out=$(ls -lhS "$CONVO_DIR"/*.jsonl 2>/dev/null || true)
269
+ if [ -n "$out" ]; then
270
+ printf '%s\n' "$out" | head -10
271
+ else
272
+ echo "(no conversation files)"
273
+ fi
274
+ }
275
+
276
+ previous_conversation_files() {
277
+ list_conversation_files | tail -n +2 | head -2
278
+ }
279
+
280
+ sample_jsonl_prefix() {
281
+ local file="$1"
282
+ local limit="${2:-512000}"
283
+ LC_ALL=C awk -v limit="$limit" '
284
+ {
285
+ line = $0 ORS
286
+ next_bytes = bytes + length(line)
287
+ if (next_bytes > limit) {
288
+ exit
289
+ }
290
+ printf "%s", line
291
+ bytes = next_bytes
292
+ }
293
+ ' "$file"
294
+ }
295
+
296
+ # Shared jq filter: collapse one transcript record to a single trimmed text
297
+ # line, dropping meta and tool-result noise. Defined once and prepended to both
298
+ # extract_* programs below so the flattening logic lives in exactly one place.
299
+ JQ_FLATTEN='
300
+ def flatten:
301
+ if (.isMeta // false) or (.toolUseResult? != null) then
302
+ empty
303
+ else
304
+ (.message.content // .content // .text // "")
305
+ | if type == "array" then
306
+ [ .[] | if type == "object" and .type == "text" then .text elif type == "string" then . else empty end ] | join(" ")
307
+ elif type == "string" then .
308
+ else empty
309
+ end
310
+ | gsub("[\\r\\n]+"; " ")
311
+ | gsub(" +"; " ")
312
+ | sub("^ "; "")
313
+ | sub(" $"; "")
314
+ end;
315
+ '
316
+
317
+ extract_messages_from_file() {
318
+ local file="$1"
319
+ sample_jsonl_prefix "$file" | jq -r "$JQ_FLATTEN"'
320
+ (.type // .role // "") as $kind
321
+ | (flatten) as $text
322
+ | if ($text | length) == 0 then
323
+ empty
324
+ elif $kind == "user" then
325
+ "USER: " + $text
326
+ elif $kind == "assistant" then
327
+ "ASSISTANT: " + $text
328
+ elif $kind == "system" then
329
+ "SYSTEM: " + $text
330
+ else
331
+ empty
332
+ end
333
+ ' 2>/dev/null
334
+ }
335
+
336
+ extract_signals_from_file() {
337
+ local file="$1"
338
+ sample_jsonl_prefix "$file" | jq -r "$JQ_FLATTEN"'
339
+ def is_correction:
340
+ test("(?i)(\\bdon'\''t\\b|\\bdo not\\b|\\bplease don'\''t\\b|\\binstead\\b|\\bnext time\\b|\\bremember\\b|\\buse\\b.*\\binstead\\b|\\bnot\\b.*\\bbut\\b)")
341
+ or test("(不要再|请不要|不要|别再|下次|记得|改成|改为|而不是|别用|去掉|统一成)");
342
+ (.type // .role // "") as $kind
343
+ | (flatten) as $text
344
+ | if ($text | length) == 0 then
345
+ empty
346
+ elif ($text | test("(?i)(conversation was compressed|context limit|context window|truncat|/compact|context management|token limit|window is full|compaction)")) then
347
+ "CONTEXT SIGNAL: " + $text
348
+ # Keep this conservative: false positives pollute enforcement-gap analysis.
349
+ elif $kind == "user" and ($text | is_correction) then
350
+ "USER CORRECTION: " + $text
351
+ else
352
+ empty
353
+ end
354
+ ' 2>/dev/null
355
+ }
356
+
357
+ print_conversation_signals() {
358
+ local files file chunk found=0
359
+ files=$(previous_conversation_files)
360
+ if [ -z "$files" ]; then
361
+ echo "(no conversation files)"
362
+ return
363
+ fi
364
+ if ! command -v jq >/dev/null 2>&1; then
365
+ echo "(unavailable: jq not installed or parse error)"
366
+ return
367
+ fi
368
+ while IFS= read -r file; do
369
+ [ -f "$file" ] || continue
370
+ if ! chunk=$(extract_signals_from_file "$file"); then
371
+ echo "(unavailable: jq not installed or parse error)"
372
+ return
373
+ fi
374
+ chunk=$(printf '%s\n' "$chunk" | head -20 || true)
375
+ if [ -n "$chunk" ]; then
376
+ found=1
377
+ echo "--- file: $file ---"
378
+ printf '%s\n' "$chunk"
379
+ fi
380
+ done <<EOF
381
+ $files
382
+ EOF
383
+ [ "$found" -eq 1 ] || echo "(no conversation signals detected)"
384
+ }
385
+
386
+ print_conversation_extract() {
387
+ local files file chunk found=0
388
+ files=$(previous_conversation_files)
389
+ if [ -z "$files" ]; then
390
+ echo "(no conversation files)"
391
+ return
392
+ fi
393
+ if ! command -v jq >/dev/null 2>&1; then
394
+ echo "(unavailable: jq not installed or parse error)"
395
+ return
396
+ fi
397
+ while IFS= read -r file; do
398
+ [ -f "$file" ] || continue
399
+ found=1
400
+ echo "--- file: $file ---"
401
+ if ! chunk=$(extract_messages_from_file "$file"); then
402
+ echo "(unavailable: jq not installed or parse error)"
403
+ return
404
+ fi
405
+ chunk=$(printf '%s\n' "$chunk" | grep -v '^ASSISTANT: $' | head -150 || true)
406
+ if [ -n "$chunk" ]; then
407
+ printf '%s\n' "$chunk"
408
+ else
409
+ echo "(no extractable conversation messages)"
410
+ fi
411
+ done <<EOF
412
+ $files
413
+ EOF
414
+ [ "$found" -eq 1 ] || echo "(no conversation files)"
415
+ }
416
+
417
+ print_mcp_access_denials() {
418
+ local files file chunk found=0
419
+ files=$(list_conversation_files | head -5)
420
+ if [ -z "$files" ]; then
421
+ echo "(no conversation files)"
422
+ return
423
+ fi
424
+ while IFS= read -r file; do
425
+ [ -f "$file" ] || continue
426
+ chunk=$(head -c 1048576 "$file" | grep -Em 2 'Access denied - path outside allowed directories|tool-results/.+ not in ' 2>/dev/null || true)
427
+ if [ -n "$chunk" ]; then
428
+ found=1
429
+ printf '%s\n' "$chunk"
430
+ fi
431
+ done <<EOF
432
+ $files
433
+ EOF
434
+ [ "$found" -eq 1 ] || echo "(none found)"
435
+ }
436
+
437
+ PROJECT_FILES=$(count_project_files)
438
+ CONTRIBUTORS=$(count_contributors)
439
+ CI_WORKFLOWS=$(count_ci_workflows)
440
+
441
+ echo "[1/12] Tier metrics..."
442
+ echo "=== TIER METRICS ==="
443
+ echo "project_files: $PROJECT_FILES"
444
+ echo "contributors: $CONTRIBUTORS"
445
+ echo "ci_workflows: $CI_WORKFLOWS"
446
+ echo "skills: $(count_local_skills)"
447
+ echo "claude_md_lines: $(count_file_lines "$P/CLAUDE.md")"
448
+ echo "collection_mode: $MODE"
449
+
450
+ # Auto-detect tier if not passed as argument.
451
+ # Matches SKILL.md definition: Simple = <500 files AND <=1 contributor AND no CI.
452
+ if [ "$TIER" = "auto" ]; then
453
+ if [ "${PROJECT_FILES:-0}" -lt 500 ] && [ "${CONTRIBUTORS:-0}" -le 1 ] && [ "${CI_WORKFLOWS:-0}" -eq 0 ]; then
454
+ TIER="simple"
455
+ elif [ "${PROJECT_FILES:-0}" -lt 5000 ]; then
456
+ TIER="standard"
457
+ else
458
+ TIER="complex"
459
+ fi
460
+ fi
461
+ echo "detected_tier: $TIER"
462
+
463
+ echo "[2/12] CLAUDE.md (global + local)..."
464
+ echo "=== CLAUDE.md (global) ==="
465
+ if [ "$MODE" = "deep" ]; then
466
+ cat ~/.claude/CLAUDE.md 2>/dev/null || echo "(none)"
467
+ else
468
+ print_file_summary "global_claude_md" "$HOME/.claude/CLAUDE.md"
469
+ fi
470
+ echo "=== CLAUDE.md (local) ==="
471
+ if [ "$MODE" = "deep" ]; then
472
+ cat "$P/CLAUDE.md" 2>/dev/null || echo "(none)"
473
+ else
474
+ print_file_summary "local_claude_md" "$P/CLAUDE.md"
475
+ fi
476
+
477
+ echo "[3/12] Settings, hooks, MCP..."
478
+ echo "=== settings.local.json ==="
479
+ if [ "$MODE" = "deep" ]; then
480
+ cat "$SETTINGS" 2>/dev/null || echo "(none)"
481
+ else
482
+ print_settings_summary "$SETTINGS"
483
+ fi
484
+
485
+ echo "[4/12] Rules + skill descriptions..."
486
+ echo "=== rules/ ==="
487
+ if [ "$MODE" = "deep" ]; then
488
+ print_rule_files
489
+ else
490
+ print_rule_file_summary
491
+ fi
492
+ echo "=== skill descriptions ==="
493
+ if [ "$MODE" = "deep" ]; then
494
+ print_skill_descriptions
495
+ else
496
+ print_skill_description_summary
497
+ fi
498
+
499
+ echo "[5/12] Context budget estimate..."
500
+ echo "=== STARTUP CONTEXT ESTIMATE ==="
501
+ echo "global_claude_words: $(count_file_words "$HOME/.claude/CLAUDE.md")"
502
+ echo "local_claude_words: $(count_file_words "$P/CLAUDE.md")"
503
+ echo "rules_words: $(rules_word_count)"
504
+ echo "skill_desc_words: $(skill_description_word_count)"
505
+ if command -v python3 >/dev/null 2>&1; then
506
+ python3 - "$SETTINGS" "$MODE" <<'PYEOF' 2>/dev/null || echo "(unavailable)"
507
+ import json
508
+ import sys
509
+
510
+ path = sys.argv[1]
511
+ mode = sys.argv[2]
512
+ try:
513
+ with open(path) as fh:
514
+ d = json.load(fh)
515
+ except Exception:
516
+ msg = '(unavailable: settings.local.json missing or malformed)'
517
+ print('=== hooks ===')
518
+ print(msg)
519
+ print('=== MCP ===')
520
+ print(msg)
521
+ print('=== MCP FILESYSTEM ===')
522
+ print(msg)
523
+ print('=== allowedTools count ===')
524
+ print(msg)
525
+ sys.exit(0)
526
+
527
+ print('=== hooks ===')
528
+ hooks = d.get('hooks', {})
529
+ if mode == 'deep':
530
+ print(json.dumps(hooks, indent=2))
531
+ elif isinstance(hooks, dict):
532
+ names = sorted(hooks.keys())
533
+ print(f'hook_events: {len(names)}')
534
+ print('hook_event_names:', ', '.join(names) if names else '(none)')
535
+ else:
536
+ print('hook_events: (unknown format)')
537
+
538
+ print('=== MCP ===')
539
+ servers = d.get('mcpServers', d.get('enabledMcpjsonServers', {}))
540
+ names = list(servers.keys()) if isinstance(servers, dict) else list(servers)
541
+ count = len(names)
542
+ print(f'servers({count}):', ', '.join(names))
543
+ est = count * 25 * 200
544
+ print(f'est_tokens: ~{est} ({round(est/2000)}% of 200K)')
545
+
546
+ print('=== MCP FILESYSTEM ===')
547
+ if isinstance(servers, list):
548
+ print('filesystem_present: (array format -- check .mcp.json)')
549
+ print('allowedDirectories: (not detectable)')
550
+ else:
551
+ filesystem = servers.get('filesystem') if isinstance(servers, dict) else None
552
+ allowed = []
553
+ if isinstance(filesystem, dict):
554
+ allowed = filesystem.get('allowedDirectories') or (
555
+ filesystem.get('config', {}).get('allowedDirectories')
556
+ if isinstance(filesystem.get('config'), dict)
557
+ else []
558
+ )
559
+ if not allowed and isinstance(filesystem.get('args'), list):
560
+ args = filesystem['args']
561
+ for index, value in enumerate(args):
562
+ if value in ('--allowed-directories', '--allowedDirectories') and index + 1 < len(args):
563
+ allowed = [args[index + 1]]
564
+ break
565
+ if not allowed:
566
+ allowed = [value for value in args if value.startswith('/') or (value.startswith('~') and len(value) > 1)]
567
+ print('filesystem_present:', 'yes' if filesystem else 'no')
568
+ if mode == 'deep':
569
+ print('allowedDirectories:', allowed or '(missing or not detected)')
570
+ else:
571
+ print('allowedDirectories_count:', len(allowed))
572
+
573
+ print('=== allowedTools count ===')
574
+ print(len(d.get('permissions', {}).get('allow', [])))
575
+ PYEOF
576
+ else
577
+ echo "=== hooks ==="
578
+ echo "(unavailable)"
579
+ echo "=== MCP ==="
580
+ echo "(unavailable)"
581
+ echo "=== MCP FILESYSTEM ==="
582
+ echo "(unavailable)"
583
+ echo "=== allowedTools count ==="
584
+ echo "(unavailable)"
585
+ fi
586
+
587
+ echo "[6/12] Nested CLAUDE.md + gitignore..."
588
+ echo "=== NESTED CLAUDE.md ==="
589
+ _NESTED_CLAUDE=$(find "$P" -maxdepth 4 -name "CLAUDE.md" -not -path "$P/CLAUDE.md" -not -path "*/.git/*" -not -path "*/node_modules/*" 2>/dev/null || true)
590
+ if [ -n "$_NESTED_CLAUDE" ]; then
591
+ printf '%s\n' "$_NESTED_CLAUDE"
592
+ else
593
+ echo "(none)"
594
+ fi
595
+ echo "=== GITIGNORE ==="
596
+ _GITIGNORE_HIT=$(git -C "$P" check-ignore -v .claude/settings.local.json 2>/dev/null || true)
597
+ if [ -n "$_GITIGNORE_HIT" ]; then
598
+ _GITIGNORE_SOURCE=${_GITIGNORE_HIT%%:*}
599
+ case "$_GITIGNORE_SOURCE" in
600
+ .gitignore|.claude/.gitignore)
601
+ echo "settings.local.json: gitignored"
602
+ ;;
603
+ *)
604
+ echo "settings.local.json: ignored only by non-project rule ($_GITIGNORE_SOURCE) -- add a repo-local ignore rule"
605
+ ;;
606
+ esac
607
+ else
608
+ echo "settings.local.json: NOT gitignored -- risk of committing tokens/credentials"
609
+ fi
610
+
611
+ echo "[7/12] HANDOFF.md + MEMORY.md..."
612
+ echo "=== HANDOFF.md ===" ; cat "$P/HANDOFF.md" 2>/dev/null || echo "(none)"
613
+ echo "=== MEMORY.md ==="
614
+ if [ -f "$HOME/.claude/projects/-${PROJECT_KEY}/memory/MEMORY.md" ]; then
615
+ head -50 "$HOME/.claude/projects/-${PROJECT_KEY}/memory/MEMORY.md"
616
+ else
617
+ echo "(none)"
618
+ fi
619
+
620
+ echo "[8/12] Conversation signals + extract..."
621
+ echo "=== CONVERSATION FILES ==="
622
+ print_conversation_file_listing
623
+
624
+ echo "=== CONVERSATION SIGNALS ==="
625
+ print_conversation_signals
626
+
627
+ if [ "$TIER" != "simple" ] && [ "$MODE" = "deep" ]; then
628
+ echo "=== CONVERSATION EXTRACT ==="
629
+ print_conversation_extract
630
+ echo "=== MCP ACCESS DENIALS ==="
631
+ print_mcp_access_denials
632
+ else
633
+ echo "=== CONVERSATION EXTRACT ===" ; echo "(skipped: summary mode; ask for a deep health audit or run collect-data.sh auto deep for full conversation extracts)"
634
+ echo "=== MCP ACCESS DENIALS ===" ; echo "(skipped: summary mode; ask for a deep health audit or run collect-data.sh auto deep for access-denial scan)"
635
+ fi
636
+
637
+ echo "[9/12] Agent config..."
638
+ if [ "$MODE" = "deep" ]; then
639
+ echo "=== AGENT CONFIG DETAIL ==="
640
+ else
641
+ echo "=== AGENT CONFIG SUMMARY ==="
642
+ fi
643
+ AGENT_CONTEXT_SCRIPT="$(resolve_health_helper check-agent-context.sh || true)"
644
+ if [ -n "$AGENT_CONTEXT_SCRIPT" ]; then
645
+ if ! bash "$AGENT_CONTEXT_SCRIPT" "$P" "$MODE"; then
646
+ echo "(unavailable: check-agent-context.sh failed)"
647
+ fi
648
+ else
649
+ echo "(unavailable: check-agent-context.sh not found)"
650
+ fi
651
+
652
+ echo "[10/12] AI maintainability..."
653
+ if [ "$MODE" = "deep" ]; then
654
+ echo "=== AI MAINTAINABILITY DETAIL ==="
655
+ else
656
+ echo "=== AI MAINTAINABILITY SUMMARY ==="
657
+ fi
658
+ MAINTAINABILITY_SCRIPT="$(resolve_health_helper check-maintainability.sh || true)"
659
+ if [ -n "$MAINTAINABILITY_SCRIPT" ]; then
660
+ if ! bash "$MAINTAINABILITY_SCRIPT" "$P" "$MODE"; then
661
+ echo "(unavailable: check-maintainability.sh failed)"
662
+ fi
663
+ else
664
+ echo "(unavailable: check-maintainability.sh not found)"
665
+ fi
666
+
667
+ echo "[11/12] Skill inventory + frontmatter + provenance..."
668
+ echo "=== SKILL INVENTORY ==="
669
+ _SKILL_FOUND=0
670
+ for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
671
+ [ -d "$DIR" ] || continue
672
+ while IFS= read -r f; do
673
+ [ -n "$f" ] || continue
674
+ is_health_skill "$f" && continue
675
+ _SKILL_FOUND=1
676
+ WORDS=$(wc -w < "$f" | tr -d ' ')
677
+ IS_LINK="no"; LINK_TARGET=""
678
+ SKILL_DIR=$(dirname "$f")
679
+ if [ -L "$SKILL_DIR" ]; then
680
+ IS_LINK="yes"; LINK_TARGET=$(resolve_symlink "$SKILL_DIR")
681
+ fi
682
+ echo "path=$f words=$WORDS symlink=$IS_LINK target=$LINK_TARGET"
683
+ done < <(list_skill_files "$DIR")
684
+ done
685
+ [ "$_SKILL_FOUND" -eq 1 ] || echo "(none)"
686
+
687
+ echo "=== SKILL FRONTMATTER ==="
688
+ if [ "$MODE" = "deep" ]; then
689
+ _FRONTMATTER_FOUND=0
690
+ for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
691
+ [ -d "$DIR" ] || continue
692
+ while IFS= read -r f; do
693
+ [ -n "$f" ] || continue
694
+ is_health_skill "$f" && continue
695
+ _FRONTMATTER_FOUND=1
696
+ if head -1 "$f" | grep -q '^---'; then
697
+ echo "frontmatter=yes path=$f"
698
+ sed -n '2,/^---$/p' "$f" | head -10
699
+ else
700
+ echo "frontmatter=MISSING path=$f"
701
+ fi
702
+ done < <(list_skill_files "$DIR")
703
+ done
704
+ [ "$_FRONTMATTER_FOUND" -eq 1 ] || echo "(none)"
705
+ else
706
+ echo "(skipped: summary mode; use collect-data.sh auto deep to print skill frontmatter samples)"
707
+ fi
708
+
709
+ echo "=== SKILL SYMLINK PROVENANCE ==="
710
+ _PROVENANCE_FOUND=0
711
+ for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
712
+ [ -d "$DIR" ] || continue
713
+ find "$DIR" -maxdepth 1 -type l 2>/dev/null | while IFS= read -r link; do
714
+ _PROVENANCE_FOUND=1
715
+ TARGET=$(resolve_symlink "$link")
716
+ echo "link=$(basename "$link") target=$TARGET"
717
+ GIT_ROOT=$(git -C "$TARGET" rev-parse --show-toplevel 2>/dev/null || echo "")
718
+ if [ -n "$GIT_ROOT" ]; then
719
+ REMOTE=$(git -C "$GIT_ROOT" remote get-url origin 2>/dev/null || echo "unknown")
720
+ COMMIT=$(git -C "$GIT_ROOT" rev-parse --short HEAD 2>/dev/null || echo "unknown")
721
+ echo " git_remote=$REMOTE commit=$COMMIT"
722
+ fi
723
+ done
724
+ done
725
+ if ! { for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
726
+ [ -d "$DIR" ] || continue
727
+ find "$DIR" -maxdepth 1 -type l 2>/dev/null
728
+ done | grep -q .; }; then
729
+ echo "(none)"
730
+ fi
731
+
732
+ echo "[12/12] Skill content sample + security scan..."
733
+ if [ "$TIER" != "simple" ] && [ "$MODE" = "deep" ]; then
734
+ echo "=== SKILL FULL CONTENT ==="
735
+ _CONTENT_COUNT=0
736
+ for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
737
+ [ -d "$DIR" ] || continue
738
+ while IFS= read -r f; do
739
+ [ -n "$f" ] || continue
740
+ is_health_skill "$f" && continue
741
+ _CONTENT_COUNT=$((_CONTENT_COUNT + 1))
742
+ [ "$_CONTENT_COUNT" -le 3 ] || break
743
+ echo "--- FULL: $f ---"
744
+ head -60 "$f"
745
+ done < <(list_skill_files "$DIR")
746
+ [ "$_CONTENT_COUNT" -ge 3 ] && break
747
+ done
748
+ [ "$_CONTENT_COUNT" -gt 0 ] || echo "(none)"
749
+ else
750
+ echo "=== SKILL FULL CONTENT ===" ; echo "(skipped: summary mode; ask for a deep health audit or run collect-data.sh auto deep to sample skill bodies)"
751
+ fi