@ivannikov-pro/ai-context-surgeon 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.
- package/LICENSE +22 -0
- package/README.md +372 -0
- package/bin/catalog.js +153 -0
- package/bin/cli.js +380 -0
- package/bin/installer.js +135 -0
- package/bin/prompts.js +371 -0
- package/checklists/phase-1-analysis.md +58 -0
- package/checklists/phase-2-planning.md +67 -0
- package/checklists/phase-3-restructuring.md +77 -0
- package/checklists/phase-4-documentation.md +111 -0
- package/checklists/phase-5-validation.md +91 -0
- package/examples/before-after/README.md +139 -0
- package/examples/ideal-monorepo/README.md +127 -0
- package/knowledge/agent-context-system/artifacts/guide.md +183 -0
- package/knowledge/agent-context-system/artifacts/knowledge.md +177 -0
- package/knowledge/agent-context-system/artifacts/skills.md +101 -0
- package/knowledge/agent-context-system/artifacts/workflows.md +143 -0
- package/knowledge/agent-context-system/metadata.json +26 -0
- package/knowledge/agent-context-system/timestamps.json +5 -0
- package/knowledge/agent-vulnerabilities/LICENSE +21 -0
- package/knowledge/agent-vulnerabilities/artifacts/stealth_injection.md +110 -0
- package/knowledge/agent-vulnerabilities/artifacts/vulnerabilities.md +232 -0
- package/knowledge/agent-vulnerabilities/metadata.json +14 -0
- package/knowledge/agent-vulnerabilities/timestamps.json +5 -0
- package/knowledge/power-words-dictionary/LICENSE +21 -0
- package/knowledge/power-words-dictionary/artifacts/dictionary.md +231 -0
- package/knowledge/power-words-dictionary/artifacts/prompt_amplifier.py +381 -0
- package/knowledge/power-words-dictionary/metadata.json +14 -0
- package/knowledge/power-words-dictionary/timestamps.json +5 -0
- package/package.json +77 -0
- package/roles/README.md +81 -0
- package/roles/architect.md +203 -0
- package/roles/inspector.md +232 -0
- package/roles/librarian.md +176 -0
- package/roles/scout.md +169 -0
- package/roles/surgeon.md +172 -0
- package/roles/tuner.md +204 -0
- package/skills/annotate-jsdoc/SKILL.md +262 -0
- package/skills/prompt-engineering/LICENSE +21 -0
- package/skills/prompt-engineering/SKILL.md +235 -0
- package/skills/prompt-engineering/scripts/extract_instructions.py +416 -0
- package/skills/prompt-engineering/scripts/prompt_amplifier.py +381 -0
- package/skills/prompt-engineering/scripts/prompt_diff_tracker.py +281 -0
- package/skills/prompt-engineering/scripts/prompt_dna_analyzer.py +692 -0
- package/skills/prompt-engineering/scripts/templates/code_review.md +47 -0
- package/skills/prompt-engineering/scripts/templates/dump_extraction.md +59 -0
- package/skills/prompt-engineering/scripts/templates/multi_agent_orchestration.md +100 -0
- package/skills/prompt-engineering/scripts/templates/prompt_audit.md +106 -0
- package/skills/prompt-engineering/scripts/templates/stealth_injection.md +110 -0
- package/skills/prompt-engineering/scripts/templates/task_automation.md +87 -0
- package/skills/prompt-engineering/workflows/amplify.md +36 -0
- package/skills/prompt-engineering/workflows/audit.md +55 -0
- package/skills/prompt-engineering/workflows/context-dump.md +90 -0
- package/skills/prompt-engineering/workflows/diff.md +44 -0
- package/strategy/bash-guide.md +134 -0
- package/strategy/context-exclusion.md +220 -0
- package/strategy/context-weight-theory.md +49 -0
- package/strategy/file-navigation-header.md +562 -0
- package/strategy/jsdoc-guide.md +596 -0
- package/strategy/monorepo_strategy.md +726 -0
- package/strategy/package-json-guide.md +541 -0
- package/templates/AGENTS.md.template +148 -0
- package/templates/antigravityignore.template +64 -0
- package/templates/cursorrules.template +7 -0
- package/templates/knowledge-item.template +44 -0
- package/templates/package-json-ideal.template +26 -0
- package/templates/package-readme.template +45 -0
- package/templates/publish-meta.template +34 -0
- package/templates/skill.template +50 -0
- package/templates/workflow.template +33 -0
- package/tools/analyze-package-json.sh +213 -0
- package/tools/analyze-structure.sh +101 -0
- package/tools/audit-jsdoc.sh +176 -0
- package/tools/check-fnh-freshness.sh +74 -0
- package/tools/detect-circular-deps.sh +147 -0
- package/tools/detect-god-files.sh +71 -0
- package/tools/enforce-god-files.sh +112 -0
- package/tools/enrich-package-json.js +311 -0
- package/tools/generate-jsdoc-headers.sh +109 -0
- package/tools/generate-source-map.sh +71 -0
- package/tools/lint-imports.sh +123 -0
- package/tools/measure-context-cost.sh +206 -0
- package/tools/scan-fnh.sh +174 -0
- package/tools/shared/config.sh +53 -0
- package/tools/validate-context-hygiene.sh +52 -0
- package/tools/validate-context-weight.sh +100 -0
- package/tools/validate-naming.sh +98 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: audit-jsdoc — Analyze JSDoc quality for AI-agent readiness | DEPS: none
|
|
3
|
+
# USAGE: bash tools/audit-jsdoc.sh /path/to/directory
|
|
4
|
+
# OUTPUT: JSDoc quality audit report
|
|
5
|
+
# MODE: read-only analysis
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/shared/config.sh"
|
|
10
|
+
|
|
11
|
+
TARGET="${1:-.}"
|
|
12
|
+
|
|
13
|
+
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
|
14
|
+
echo "Usage: bash tools/audit-jsdoc.sh /path/to/directory"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "Analyzes JSDoc comments in TypeScript/JavaScript files for AI-readiness."
|
|
17
|
+
echo "Detects:"
|
|
18
|
+
echo " - Missing file headers"
|
|
19
|
+
echo " - Wasteful tags (@author, @since, @copyright, @param {type} in TS)"
|
|
20
|
+
echo " - Missing critical tags (@throws, side effects)"
|
|
21
|
+
echo " - Multi-line blocks that could be one-liners"
|
|
22
|
+
echo " - Token cost estimation"
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [[ ! -d "$TARGET" ]]; then
|
|
27
|
+
echo "❌ Error: '$TARGET' is not a directory"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "📝 JSDoc AI-Readiness Audit: $TARGET"
|
|
32
|
+
echo "════════════════════════════════════════════════════════"
|
|
33
|
+
|
|
34
|
+
# Find source files
|
|
35
|
+
SRC_FILES=$(find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' \) \
|
|
36
|
+
-not -path '*/node_modules/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' -not -path '*/dist/*' -not -path '*/.git/*' \
|
|
37
|
+
-not -name '*.d.ts' -not -name '*.test.*' -not -name '*.spec.*' -not -name '*.min.*')
|
|
38
|
+
|
|
39
|
+
TOTAL_FILES=$(echo "$SRC_FILES" | wc -l | tr -d ' ')
|
|
40
|
+
echo "Scanning $TOTAL_FILES source files..."
|
|
41
|
+
echo ""
|
|
42
|
+
|
|
43
|
+
# Metrics
|
|
44
|
+
FILES_NO_HEADER=0
|
|
45
|
+
FILES_WITH_HEADER=0
|
|
46
|
+
WASTEFUL_TAGS=0
|
|
47
|
+
MULTILINE_BLOCKS=0
|
|
48
|
+
TOTAL_JSDOC_LINES=0
|
|
49
|
+
USEFUL_ANNOTATIONS=0
|
|
50
|
+
|
|
51
|
+
echo "$SRC_FILES" | while read -r file; do
|
|
52
|
+
[[ -z "$file" ]] && continue
|
|
53
|
+
|
|
54
|
+
rel_path="${file#$TARGET/}"
|
|
55
|
+
first_line=$(head -1 "$file" 2>/dev/null || echo "")
|
|
56
|
+
issues=""
|
|
57
|
+
|
|
58
|
+
# Check file header
|
|
59
|
+
if ! echo "$first_line" | grep -q '/\*\*'; then
|
|
60
|
+
FILES_NO_HEADER=$((FILES_NO_HEADER + 1))
|
|
61
|
+
else
|
|
62
|
+
FILES_WITH_HEADER=$((FILES_WITH_HEADER + 1))
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Count JSDoc lines
|
|
66
|
+
file_jsdoc_lines=$(grep -c '^\s*\*\|^\s*/\*\*' "$file" || true)
|
|
67
|
+
TOTAL_JSDOC_LINES=$((TOTAL_JSDOC_LINES + file_jsdoc_lines))
|
|
68
|
+
|
|
69
|
+
# Detect wasteful tags (Tier 4 — should be removed)
|
|
70
|
+
wasteful=""
|
|
71
|
+
for tag in "@author" "@since" "@version" "@copyright" "@license" "@date" "@fileoverview" "@module" "@namespace" "@memberof" "@classdesc" "@constructs"; do
|
|
72
|
+
if grep -q "$tag" "$file" 2>/dev/null; then
|
|
73
|
+
count=$(grep -c "$tag" "$file" || true)
|
|
74
|
+
wasteful="$wasteful $tag(${count})"
|
|
75
|
+
WASTEFUL_TAGS=$((WASTEFUL_TAGS + count))
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
# Detect @param {type} in TypeScript files (redundant)
|
|
80
|
+
if [[ "$file" == *.ts || "$file" == *.tsx ]]; then
|
|
81
|
+
param_type_count=$(grep -cE '@param\s+\{' "$file" || true)
|
|
82
|
+
if [[ $param_type_count -gt 0 ]]; then
|
|
83
|
+
wasteful="$wasteful @param{type}(${param_type_count})"
|
|
84
|
+
WASTEFUL_TAGS=$((WASTEFUL_TAGS + param_type_count))
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
returns_type_count=$(grep -cE '@returns?\s+\{' "$file" || true)
|
|
88
|
+
if [[ $returns_type_count -gt 0 ]]; then
|
|
89
|
+
wasteful="$wasteful @returns{type}(${returns_type_count})"
|
|
90
|
+
WASTEFUL_TAGS=$((WASTEFUL_TAGS + returns_type_count))
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Detect useful tags
|
|
95
|
+
useful=""
|
|
96
|
+
for tag in "@throws" "@deprecated" "@mutates" "@sideeffect"; do
|
|
97
|
+
if grep -q "$tag" "$file" 2>/dev/null; then
|
|
98
|
+
count=$(grep -c "$tag" "$file" || true)
|
|
99
|
+
useful="$useful $tag(${count})"
|
|
100
|
+
USEFUL_ANNOTATIONS=$((USEFUL_ANNOTATIONS + count))
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
# Detect multi-line JSDoc blocks (>3 lines)
|
|
105
|
+
multiline=$(python3 -c "
|
|
106
|
+
import re
|
|
107
|
+
with open('$file') as f:
|
|
108
|
+
content = f.read()
|
|
109
|
+
blocks = re.findall(r'/\*\*[\s\S]*?\*/', content)
|
|
110
|
+
big_blocks = [b for b in blocks if b.count('\n') > 3]
|
|
111
|
+
print(len(big_blocks))
|
|
112
|
+
" 2>/dev/null || echo "0")
|
|
113
|
+
|
|
114
|
+
if [[ $multiline -gt 0 ]]; then
|
|
115
|
+
MULTILINE_BLOCKS=$((MULTILINE_BLOCKS + multiline))
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Report per-file issues
|
|
119
|
+
if [[ -n "$wasteful" || $multiline -gt 0 ]] || ! echo "$first_line" | grep -q '/\*\*'; then
|
|
120
|
+
echo " 📄 $rel_path"
|
|
121
|
+
|
|
122
|
+
if ! echo "$first_line" | grep -q '/\*\*'; then
|
|
123
|
+
echo " 🟡 No file header"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if [[ -n "$wasteful" ]]; then
|
|
127
|
+
echo " 🔴 Wasteful:$wasteful"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [[ $multiline -gt 0 ]]; then
|
|
131
|
+
echo " 🟡 $multiline multi-line block(s) — consider one-liners"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [[ -n "$useful" ]]; then
|
|
135
|
+
echo " ✅ Useful:$useful"
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
done
|
|
140
|
+
|
|
141
|
+
echo ""
|
|
142
|
+
echo "════════════════════════════════════════════════════════"
|
|
143
|
+
echo ""
|
|
144
|
+
echo "📊 SUMMARY"
|
|
145
|
+
echo "────────────────────────────────────────"
|
|
146
|
+
echo "Total source files: $TOTAL_FILES"
|
|
147
|
+
echo "Files with header: $FILES_WITH_HEADER"
|
|
148
|
+
echo "Files without header: $FILES_NO_HEADER"
|
|
149
|
+
echo "Total JSDoc lines: $TOTAL_JSDOC_LINES"
|
|
150
|
+
echo "Wasteful tag instances: $WASTEFUL_TAGS"
|
|
151
|
+
echo "Multi-line blocks: $MULTILINE_BLOCKS"
|
|
152
|
+
echo "Useful annotations: $USEFUL_ANNOTATIONS"
|
|
153
|
+
echo ""
|
|
154
|
+
|
|
155
|
+
# Token estimate
|
|
156
|
+
EST_WASTEFUL_TOKENS=$((WASTEFUL_TAGS * 8))
|
|
157
|
+
EST_MULTILINE_TOKENS=$((MULTILINE_BLOCKS * 30))
|
|
158
|
+
EST_TOTAL_WASTE=$((EST_WASTEFUL_TOKENS + EST_MULTILINE_TOKENS))
|
|
159
|
+
|
|
160
|
+
echo "💰 ESTIMATED WASTE"
|
|
161
|
+
echo "────────────────────────────────────────"
|
|
162
|
+
echo "Wasteful tags: ~${EST_WASTEFUL_TOKENS} tokens"
|
|
163
|
+
echo "Multi-line blocks: ~${EST_MULTILINE_TOKENS} tokens (could be one-liners)"
|
|
164
|
+
echo "Total waste: ~${EST_TOTAL_WASTE} tokens"
|
|
165
|
+
echo ""
|
|
166
|
+
|
|
167
|
+
if [[ $EST_TOTAL_WASTE -gt 1000 ]]; then
|
|
168
|
+
echo "🔴 HIGH WASTE — significant context budget being burned on noise"
|
|
169
|
+
elif [[ $EST_TOTAL_WASTE -gt 300 ]]; then
|
|
170
|
+
echo "🟡 MODERATE WASTE — some cleanup recommended"
|
|
171
|
+
else
|
|
172
|
+
echo "✅ LOW WASTE — JSDoc is fairly optimized"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
echo ""
|
|
176
|
+
echo "📖 See strategy/jsdoc-guide.md for full optimization guide"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: check-fnh-freshness — warns if source files changed but their FNH headers didn't | DEPS: none
|
|
3
|
+
# USAGE: bash tools/check-fnh-freshness.sh [base-ref]
|
|
4
|
+
# OUTPUT: list of files with potentially stale FNH headers
|
|
5
|
+
# MODE: read-only, git-based analysis
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/shared/config.sh"
|
|
10
|
+
|
|
11
|
+
BASE_REF="${1:-HEAD~1}"
|
|
12
|
+
WARN_COUNT=0
|
|
13
|
+
CHECKED=0
|
|
14
|
+
|
|
15
|
+
echo "🧭 FNH Freshness Check"
|
|
16
|
+
echo " Comparing against: $BASE_REF"
|
|
17
|
+
echo " ──────────────────────────────────"
|
|
18
|
+
|
|
19
|
+
# Check TypeScript/JavaScript files
|
|
20
|
+
for file in $(git diff --name-only "$BASE_REF" -- '*.ts' '*.js' '*.tsx' '*.jsx' '*.sol' 2>/dev/null || true); do
|
|
21
|
+
[ -f "$file" ] || continue
|
|
22
|
+
CHECKED=$((CHECKED + 1))
|
|
23
|
+
|
|
24
|
+
# Count how many lines changed in the FNH region (first 8 lines)
|
|
25
|
+
fnh_diff=$(git diff "$BASE_REF" -- "$file" | awk '/^@@/{found=1} found{print}' | head -30 | grep -cE '^\+.*(/\*\*|\* )' || true)
|
|
26
|
+
|
|
27
|
+
# Count total changed lines (excluding FNH)
|
|
28
|
+
total_changes=$(git diff "$BASE_REF" -- "$file" | grep -cE '^[+-]' || true)
|
|
29
|
+
|
|
30
|
+
if [ "$total_changes" -gt 5 ] && [ "$fnh_diff" -eq 0 ]; then
|
|
31
|
+
echo " ⚠️ $file"
|
|
32
|
+
echo " $total_changes lines changed, FNH header unchanged"
|
|
33
|
+
WARN_COUNT=$((WARN_COUNT + 1))
|
|
34
|
+
fi
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
# Check Markdown files — look for > blockquote after H1
|
|
38
|
+
for file in $(git diff --name-only "$BASE_REF" -- '*.md' 2>/dev/null || true); do
|
|
39
|
+
[ -f "$file" ] || continue
|
|
40
|
+
CHECKED=$((CHECKED + 1))
|
|
41
|
+
|
|
42
|
+
# Check if file has a > context line in first 5 lines
|
|
43
|
+
has_fnh=$(head -5 "$file" | grep -c '^>' || true)
|
|
44
|
+
if [ "$has_fnh" -eq 0 ]; then
|
|
45
|
+
echo " ❌ $file — missing FNH (no > blockquote after H1)"
|
|
46
|
+
WARN_COUNT=$((WARN_COUNT + 1))
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
# Check shell scripts
|
|
51
|
+
for file in $(git diff --name-only "$BASE_REF" -- '*.sh' 2>/dev/null || true); do
|
|
52
|
+
[ -f "$file" ] || continue
|
|
53
|
+
CHECKED=$((CHECKED + 1))
|
|
54
|
+
|
|
55
|
+
has_fnh=$(head -3 "$file" | grep -c '^# Tool:\|^# Script:\|^# Config:' || true)
|
|
56
|
+
if [ "$has_fnh" -eq 0 ]; then
|
|
57
|
+
echo " ❌ $file — missing FNH (no # Tool/Script/Config header)"
|
|
58
|
+
WARN_COUNT=$((WARN_COUNT + 1))
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
echo ""
|
|
63
|
+
echo " ──────────────────────────────────"
|
|
64
|
+
echo " Files checked: $CHECKED"
|
|
65
|
+
echo " Warnings: $WARN_COUNT"
|
|
66
|
+
|
|
67
|
+
if [ "$WARN_COUNT" -gt 0 ]; then
|
|
68
|
+
echo ""
|
|
69
|
+
echo " 💡 Run the annotate-jsdoc skill to fix missing/stale FNH headers"
|
|
70
|
+
exit 1
|
|
71
|
+
else
|
|
72
|
+
echo " ✅ All FNH headers look fresh"
|
|
73
|
+
exit 0
|
|
74
|
+
fi
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: detect-circular-deps — Detect circular dependencies between packages | DEPS: none
|
|
3
|
+
# USAGE: bash tools/detect-circular-deps.sh /path/to/monorepo
|
|
4
|
+
# OUTPUT: list of circular dependency chains
|
|
5
|
+
# MODE: read-only analysis
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/shared/config.sh"
|
|
10
|
+
|
|
11
|
+
TARGET="${1:-.}"
|
|
12
|
+
|
|
13
|
+
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
|
14
|
+
echo "Usage: bash tools/detect-circular-deps.sh /path/to/monorepo"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "Detects circular dependencies between packages in a monorepo."
|
|
17
|
+
echo "Scans package.json files for internal dependencies and builds a graph."
|
|
18
|
+
echo "Reports any cycles found."
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [[ ! -d "$TARGET" ]]; then
|
|
23
|
+
echo "❌ Error: '$TARGET' is not a directory"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo "🔍 Checking circular dependencies in: $TARGET"
|
|
28
|
+
echo "────────────────────────────────────────"
|
|
29
|
+
|
|
30
|
+
# Build dependency graph from package.json files
|
|
31
|
+
TEMP_FILE=$(mktemp)
|
|
32
|
+
PACKAGES_DIR="$TARGET/packages"
|
|
33
|
+
APPS_DIR="$TARGET/apps"
|
|
34
|
+
|
|
35
|
+
# Collect all internal package names
|
|
36
|
+
declare -a INTERNAL_PACKAGES=()
|
|
37
|
+
|
|
38
|
+
for pkg_json in "$PACKAGES_DIR"/*/package.json "$APPS_DIR"/*/package.json; do
|
|
39
|
+
if [[ -f "$pkg_json" ]]; then
|
|
40
|
+
pkg_name=$(python3 -c "import json; print(json.load(open('$pkg_json'))['name'])" 2>/dev/null || echo "unknown")
|
|
41
|
+
if [[ "$pkg_name" != "unknown" ]]; then
|
|
42
|
+
INTERNAL_PACKAGES+=("$pkg_name")
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
if [[ ${#INTERNAL_PACKAGES[@]} -eq 0 ]]; then
|
|
48
|
+
echo "⚠️ No packages found in packages/ or apps/"
|
|
49
|
+
echo " This tool expects a monorepo with packages/*/package.json"
|
|
50
|
+
rm -f "$TEMP_FILE"
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
echo "Found ${#INTERNAL_PACKAGES[@]} internal packages"
|
|
55
|
+
echo ""
|
|
56
|
+
|
|
57
|
+
# Check for cycles using import scanning
|
|
58
|
+
CYCLES_FOUND=0
|
|
59
|
+
|
|
60
|
+
for pkg_dir in "$PACKAGES_DIR"/* "$APPS_DIR"/*; do
|
|
61
|
+
if [[ -d "$pkg_dir" && -f "$pkg_dir/package.json" ]]; then
|
|
62
|
+
pkg_name=$(python3 -c "import json; print(json.load(open('$pkg_dir/package.json'))['name'])" 2>/dev/null || continue)
|
|
63
|
+
|
|
64
|
+
# Get dependencies
|
|
65
|
+
deps=$(python3 -c "
|
|
66
|
+
import json
|
|
67
|
+
data = json.load(open('$pkg_dir/package.json'))
|
|
68
|
+
all_deps = {}
|
|
69
|
+
all_deps.update(data.get('dependencies', {}))
|
|
70
|
+
all_deps.update(data.get('devDependencies', {}))
|
|
71
|
+
internals = [d for d in all_deps if any(d == p for p in '''${INTERNAL_PACKAGES[*]}'''.split())]
|
|
72
|
+
print(' '.join(internals))
|
|
73
|
+
" 2>/dev/null || echo "")
|
|
74
|
+
|
|
75
|
+
if [[ -n "$deps" ]]; then
|
|
76
|
+
for dep in $deps; do
|
|
77
|
+
echo "$pkg_name -> $dep" >> "$TEMP_FILE"
|
|
78
|
+
done
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
echo "📊 DEPENDENCY GRAPH"
|
|
84
|
+
echo "────────────────────────────────────────"
|
|
85
|
+
|
|
86
|
+
if [[ -s "$TEMP_FILE" ]]; then
|
|
87
|
+
cat "$TEMP_FILE"
|
|
88
|
+
else
|
|
89
|
+
echo "No internal dependencies found."
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo ""
|
|
93
|
+
echo "────────────────────────────────────────"
|
|
94
|
+
|
|
95
|
+
# Simple cycle detection using Python
|
|
96
|
+
python3 -c "
|
|
97
|
+
import sys
|
|
98
|
+
|
|
99
|
+
edges = []
|
|
100
|
+
with open('$TEMP_FILE') as f:
|
|
101
|
+
for line in f:
|
|
102
|
+
parts = line.strip().split(' -> ')
|
|
103
|
+
if len(parts) == 2:
|
|
104
|
+
edges.append((parts[0], parts[1]))
|
|
105
|
+
|
|
106
|
+
# Build adjacency list
|
|
107
|
+
graph = {}
|
|
108
|
+
for src, dst in edges:
|
|
109
|
+
graph.setdefault(src, []).append(dst)
|
|
110
|
+
|
|
111
|
+
# DFS cycle detection
|
|
112
|
+
WHITE, GRAY, BLACK = 0, 1, 2
|
|
113
|
+
color = {node: WHITE for node in graph}
|
|
114
|
+
for node in set(dst for _, dst in edges):
|
|
115
|
+
color.setdefault(node, WHITE)
|
|
116
|
+
|
|
117
|
+
cycles = []
|
|
118
|
+
|
|
119
|
+
def dfs(node, path):
|
|
120
|
+
color[node] = GRAY
|
|
121
|
+
path.append(node)
|
|
122
|
+
for neighbor in graph.get(node, []):
|
|
123
|
+
if color.get(neighbor) == GRAY:
|
|
124
|
+
cycle_start = path.index(neighbor)
|
|
125
|
+
cycles.append(path[cycle_start:] + [neighbor])
|
|
126
|
+
elif color.get(neighbor) == WHITE:
|
|
127
|
+
dfs(neighbor, path)
|
|
128
|
+
path.pop()
|
|
129
|
+
color[node] = BLACK
|
|
130
|
+
|
|
131
|
+
for node in list(color.keys()):
|
|
132
|
+
if color[node] == WHITE:
|
|
133
|
+
dfs(node, [])
|
|
134
|
+
|
|
135
|
+
if cycles:
|
|
136
|
+
print(f'🔴 CIRCULAR DEPENDENCIES FOUND: {len(cycles)} cycle(s)')
|
|
137
|
+
print()
|
|
138
|
+
for i, cycle in enumerate(cycles, 1):
|
|
139
|
+
print(f' Cycle {i}: {\" → \".join(cycle)}')
|
|
140
|
+
print()
|
|
141
|
+
print('These MUST be resolved before restructuring.')
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
else:
|
|
144
|
+
print('✅ No circular dependencies detected!')
|
|
145
|
+
" 2>/dev/null || echo "⚠️ Python3 required for cycle detection"
|
|
146
|
+
|
|
147
|
+
rm -f "$TEMP_FILE"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: detect-god-files — Find files exceeding a line threshold | DEPS: none
|
|
3
|
+
# USAGE: bash tools/detect-god-files.sh /path/to/monorepo [threshold]
|
|
4
|
+
# OUTPUT: list of files with line counts exceeding threshold
|
|
5
|
+
# MODE: read-only analysis
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/shared/config.sh"
|
|
10
|
+
|
|
11
|
+
TARGET="${1:-.}"
|
|
12
|
+
THRESHOLD="${2:-500}"
|
|
13
|
+
|
|
14
|
+
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
|
15
|
+
echo "Usage: bash tools/detect-god-files.sh /path/to/monorepo [threshold]"
|
|
16
|
+
echo ""
|
|
17
|
+
echo "Finds source files exceeding [threshold] lines (default: 500)."
|
|
18
|
+
echo "These 'god-files' are candidates for decomposition."
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Arguments:"
|
|
21
|
+
echo " path Path to monorepo (default: current directory)"
|
|
22
|
+
echo " threshold Line count threshold (default: 500)"
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [[ ! -d "$TARGET" ]]; then
|
|
27
|
+
echo "❌ Error: '$TARGET' is not a directory"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "🔍 Searching for files > ${THRESHOLD} lines in: $TARGET"
|
|
32
|
+
echo "────────────────────────────────────────"
|
|
33
|
+
|
|
34
|
+
GOD_FILES=$(find "$TARGET" \
|
|
35
|
+
-type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.py' -o -name '*.go' -o -name '*.rs' -o -name '*.sol' \) \
|
|
36
|
+
-not -path '*/node_modules/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
|
|
37
|
+
-not -path '*/.git/*' \
|
|
38
|
+
-not -path '*/dist/*' \
|
|
39
|
+
-not -path '*/build/*' \
|
|
40
|
+
-not -path '*/.next/*' \
|
|
41
|
+
-not -path '*/generated/*' \
|
|
42
|
+
-not -path '*.min.*' \
|
|
43
|
+
-not -path '*.d.ts' \
|
|
44
|
+
-exec wc -l {} + 2>/dev/null \
|
|
45
|
+
| grep -v ' total$' \
|
|
46
|
+
| awk -v t="$THRESHOLD" '$1 > t' \
|
|
47
|
+
| sort -rn)
|
|
48
|
+
|
|
49
|
+
if [[ -z "$GOD_FILES" ]]; then
|
|
50
|
+
echo "✅ No god-files found! All files are within ${THRESHOLD} lines."
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
COUNT=$(echo "$GOD_FILES" | wc -l | tr -d ' ')
|
|
55
|
+
echo "🔴 Found ${COUNT} god-file(s):"
|
|
56
|
+
echo ""
|
|
57
|
+
|
|
58
|
+
echo "$GOD_FILES" | while read -r line; do
|
|
59
|
+
lines=$(echo "$line" | awk '{print $1}')
|
|
60
|
+
file=$(echo "$line" | awk '{print $2}')
|
|
61
|
+
severity="🟡"
|
|
62
|
+
[[ $lines -gt 1000 ]] && severity="🔴"
|
|
63
|
+
[[ $lines -gt 2000 ]] && severity="💀"
|
|
64
|
+
echo "${severity} ${lines} lines — ${file}"
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
echo ""
|
|
68
|
+
echo "────────────────────────────────────────"
|
|
69
|
+
echo "Severity: 🟡 ${THRESHOLD}-1000 | 🔴 1000-2000 | 💀 2000+"
|
|
70
|
+
echo ""
|
|
71
|
+
echo "Next: Use the Architect role to plan decomposition of these files."
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tool: enforce-god-files — Enforce file size limits across the codebase (CI blocker) | DEPS: none
|
|
3
|
+
# USAGE: bash tools/enforce-god-files.sh /path/to/scan [--strict]
|
|
4
|
+
# OUTPUT: File size compliance report
|
|
5
|
+
# MODE: ci-compliance
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/shared/config.sh"
|
|
10
|
+
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
YELLOW='\033[1;33m'
|
|
13
|
+
CYAN='\033[0;36m'
|
|
14
|
+
GREEN='\033[0;32m'
|
|
15
|
+
DIM='\033[2m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
CRITICAL_LIMIT=300
|
|
19
|
+
WARN_LIMIT=150
|
|
20
|
+
NOTICE_LIMIT=100
|
|
21
|
+
STRICT=false
|
|
22
|
+
|
|
23
|
+
TARGET="${1:-.}"
|
|
24
|
+
|
|
25
|
+
if [[ "$TARGET" == "--help" || "$TARGET" == "-h" ]]; then
|
|
26
|
+
echo "Usage: bash tools/enforce-god-files.sh /path/to/scan [--strict]"
|
|
27
|
+
echo "Enforces file size limits and exits with 1 if limit is exceeded."
|
|
28
|
+
echo ""
|
|
29
|
+
echo "Tiers:"
|
|
30
|
+
echo " CRITICAL (>300 lines) — hard blocker, exit 1"
|
|
31
|
+
echo " WARNING (151-300) — soft warning, logged but not blocking"
|
|
32
|
+
echo " NOTICE (101-150) — consider refactoring on next edit"
|
|
33
|
+
echo " OK (≤100) — fully compliant"
|
|
34
|
+
echo ""
|
|
35
|
+
echo "Arguments:"
|
|
36
|
+
echo " --strict Fail on WARNING tier (>150 lines) as well"
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [[ "${2:-}" == "--strict" || "${1:-}" == "--strict" ]]; then
|
|
41
|
+
STRICT=true
|
|
42
|
+
[[ "${1:-}" == "--strict" ]] && TARGET="."
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [[ ! -d "$TARGET" ]]; then
|
|
46
|
+
echo "❌ Error: '$TARGET' is not a directory"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
critical_count=0
|
|
52
|
+
warn_count=0
|
|
53
|
+
notice_count=0
|
|
54
|
+
ok_count=0
|
|
55
|
+
critical_files=""
|
|
56
|
+
warn_files=""
|
|
57
|
+
notice_files=""
|
|
58
|
+
|
|
59
|
+
echo "━━━ File Size Compliance Check ━━━"
|
|
60
|
+
|
|
61
|
+
while IFS= read -r file; do
|
|
62
|
+
lines=$(wc -l < "$file" | tr -d ' ')
|
|
63
|
+
|
|
64
|
+
if [ "$lines" -gt "$CRITICAL_LIMIT" ]; then
|
|
65
|
+
critical_count=$((critical_count + 1))
|
|
66
|
+
critical_files="${critical_files}\n ${RED}🚫 ${lines} lines${NC} $file"
|
|
67
|
+
elif [ "$lines" -gt "$WARN_LIMIT" ]; then
|
|
68
|
+
warn_count=$((warn_count + 1))
|
|
69
|
+
warn_files="${warn_files}\n ${YELLOW}⚠️ ${lines} lines${NC} $file"
|
|
70
|
+
elif [ "$lines" -gt "$NOTICE_LIMIT" ]; then
|
|
71
|
+
notice_count=$((notice_count + 1))
|
|
72
|
+
notice_files="${notice_files}\n ${CYAN}🔍 ${lines} lines${NC} $file"
|
|
73
|
+
else
|
|
74
|
+
ok_count=$((ok_count + 1))
|
|
75
|
+
fi
|
|
76
|
+
done < <(find "$TARGET" -type f \( -name '*.ts' -o -name '*.js' -o -name '*.tsx' -o -name '*.jsx' -o -name '*.py' -o -name '*.go' -o -name '*.rs' -o -name '*.sol' \) "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
77
|
+
|
|
78
|
+
echo ""
|
|
79
|
+
if [ "$critical_count" -gt 0 ]; then
|
|
80
|
+
echo -e "${RED}🚫 CRITICAL ($critical_count files > $CRITICAL_LIMIT lines) — MUST split before merge:${NC}"
|
|
81
|
+
echo -e "$critical_files"
|
|
82
|
+
echo ""
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if [ "$warn_count" -gt 0 ]; then
|
|
86
|
+
echo -e "${YELLOW}⚠️ WARNING ($warn_count files > $WARN_LIMIT lines) — split when editing:${NC}"
|
|
87
|
+
echo -e "$warn_files"
|
|
88
|
+
echo ""
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [ "$notice_count" -gt 0 ]; then
|
|
92
|
+
echo -e "${CYAN}🔍 NOTICE ($notice_count files > $NOTICE_LIMIT lines) — consider refactoring:${NC}"
|
|
93
|
+
echo -e "$notice_files"
|
|
94
|
+
echo ""
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
echo -e "${GREEN}✅ $ok_count files fully compliant (≤$NOTICE_LIMIT lines)${NC}"
|
|
98
|
+
echo -e "${DIM} $notice_count notice, $warn_count warnings, $critical_count critical${NC}"
|
|
99
|
+
echo ""
|
|
100
|
+
|
|
101
|
+
if [ "$critical_count" -gt 0 ]; then
|
|
102
|
+
echo -e "${RED}FAILED: $critical_count files exceed $CRITICAL_LIMIT lines. Split them first.${NC}"
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ "$STRICT" = true ] && [ "$warn_count" -gt 0 ]; then
|
|
107
|
+
echo -e "${YELLOW}FAILED (strict mode): $warn_count files exceed $WARN_LIMIT lines.${NC}"
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo -e "${GREEN}PASSED${NC}"
|
|
112
|
+
exit 0
|