@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,206 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: measure-context-cost — Estimate AI agent context cost for a package | DEPS: none
|
|
3
|
+
# USAGE: bash tools/measure-context-cost.sh /path/to/package
|
|
4
|
+
# OUTPUT: estimated token costs and efficiency metrics
|
|
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/measure-context-cost.sh /path/to/package"
|
|
15
|
+
echo "Usage: bash tools/measure-context-cost.sh /path/to/package [--internal-scope=scope1,scope2]"
|
|
16
|
+
echo ""
|
|
17
|
+
echo "Estimates the context cost (in tokens) for an AI agent to understand"
|
|
18
|
+
echo "a package. Compares cost with and without README/navigation aids."
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Token estimation: 1 token ≈ 4 characters (rough approximation)"
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
TARGET="."
|
|
25
|
+
INTERNAL_SCOPES=""
|
|
26
|
+
|
|
27
|
+
while [[ $# -gt 0 ]]; do
|
|
28
|
+
case $1 in
|
|
29
|
+
--internal-scope=*)
|
|
30
|
+
INTERNAL_SCOPES="${1#*=}"
|
|
31
|
+
shift
|
|
32
|
+
;;
|
|
33
|
+
-h|--help)
|
|
34
|
+
exit 0
|
|
35
|
+
;;
|
|
36
|
+
*)
|
|
37
|
+
TARGET="$1"
|
|
38
|
+
shift
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
if [[ ! -d "$TARGET" ]]; then
|
|
44
|
+
echo "❌ Error: '$TARGET' is not a directory"
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
PKG_NAME=$(basename "$TARGET")
|
|
49
|
+
CHARS_PER_TOKEN=4
|
|
50
|
+
|
|
51
|
+
echo "📊 Context Cost Analysis: $PKG_NAME"
|
|
52
|
+
if [[ -n "$INTERNAL_SCOPES" ]]; then
|
|
53
|
+
echo "🔍 Treating scopes as local: $INTERNAL_SCOPES"
|
|
54
|
+
fi
|
|
55
|
+
echo "────────────────────────────────────────"
|
|
56
|
+
|
|
57
|
+
# Construct regex for local imports
|
|
58
|
+
if [[ -n "$INTERNAL_SCOPES" ]]; then
|
|
59
|
+
SCOPE_PATTERN=$(echo "$INTERNAL_SCOPES" | tr ',' '|')
|
|
60
|
+
LOCAL_REGEX="^(import |from |use |const .* = require\()).*['\"](\.|/|@/|~/|${SCOPE_PATTERN})"
|
|
61
|
+
else
|
|
62
|
+
LOCAL_REGEX="^(import |from |use |const .* = require\()).*['\"](\.|/|@/|~/)"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Count source files
|
|
66
|
+
SRC_FILES=$(find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' -o -name '*.py' -o -name '*.rs' \) \
|
|
67
|
+
-not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
|
|
68
|
+
-not -name '*.d.ts' -not -name '*.test.*' -not -name '*.spec.*' \
|
|
69
|
+
| wc -l | tr -d ' ')
|
|
70
|
+
|
|
71
|
+
# Total source bytes
|
|
72
|
+
TOTAL_BYTES=$(find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' -o -name '*.py' -o -name '*.rs' \) \
|
|
73
|
+
-not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
|
|
74
|
+
-not -name '*.d.ts' -not -name '*.test.*' -not -name '*.spec.*' \
|
|
75
|
+
-exec wc -c {} + 2>/dev/null | grep total | awk '{print $1}')
|
|
76
|
+
|
|
77
|
+
if [[ -z "$TOTAL_BYTES" ]]; then
|
|
78
|
+
TOTAL_BYTES=0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
TOTAL_TOKENS=$((TOTAL_BYTES / CHARS_PER_TOKEN))
|
|
82
|
+
|
|
83
|
+
IMPORTS_ALL=$(find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' -o -name '*.py' -o -name '*.rs' \) \
|
|
84
|
+
-not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
|
|
85
|
+
-not -name '*.d.ts' -not -name '*.test.*' -not -name '*.spec.*' \
|
|
86
|
+
-exec grep -E "^(import |from |use |const .* = require\()" {} + 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
87
|
+
|
|
88
|
+
IMPORTS_LOCAL=$(find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' -o -name '*.py' -o -name '*.rs' \) \
|
|
89
|
+
-not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
|
|
90
|
+
-not -name '*.d.ts' -not -name '*.test.*' -not -name '*.spec.*' \
|
|
91
|
+
-exec grep -E "$LOCAL_REGEX" {} + 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
92
|
+
|
|
93
|
+
TOTAL_LINES=$(find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' \) \
|
|
94
|
+
-not -path '*/node_modules/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' -not -path '*/dist/*' -not -name '*.d.ts' -not -name '*.test.*' -not -name '*.spec.*' \
|
|
95
|
+
-exec cat {} + 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
96
|
+
|
|
97
|
+
if [[ $SRC_FILES -gt 0 ]]; then
|
|
98
|
+
IMPORTS_GLOBAL=$((IMPORTS_ALL - IMPORTS_LOCAL))
|
|
99
|
+
FANOUT_LOCAL=$(awk "BEGIN {printf \"%.1f\", $IMPORTS_LOCAL/$SRC_FILES}")
|
|
100
|
+
FANOUT_GLOBAL=$(awk "BEGIN {printf \"%.1f\", $IMPORTS_GLOBAL/$SRC_FILES}")
|
|
101
|
+
AVG_LOC=$((TOTAL_LINES / SRC_FILES))
|
|
102
|
+
AVG_WEIGHT=$(awk "BEGIN {print int($AVG_LOC + ($IMPORTS_LOCAL/$SRC_FILES) * 20 + ($IMPORTS_GLOBAL/$SRC_FILES) * 5)}")
|
|
103
|
+
else
|
|
104
|
+
FANOUT_LOCAL=0
|
|
105
|
+
FANOUT_GLOBAL=0
|
|
106
|
+
AVG_LOC=0
|
|
107
|
+
AVG_WEIGHT=0
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Determine Weight Category
|
|
111
|
+
if [[ $AVG_WEIGHT -gt 300 ]]; then
|
|
112
|
+
WEIGHT_CLASS="🔴 Heavy"
|
|
113
|
+
elif [[ $AVG_WEIGHT -gt 150 ]]; then
|
|
114
|
+
WEIGHT_CLASS="⚠️ Moderate"
|
|
115
|
+
else
|
|
116
|
+
WEIGHT_CLASS="✅ Light"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo "Source files: $SRC_FILES"
|
|
120
|
+
echo "Total source: ~${TOTAL_TOKENS} tokens (${TOTAL_BYTES} bytes, $TOTAL_LINES lines)"
|
|
121
|
+
echo "Fan-out: $FANOUT_LOCAL Local / $FANOUT_GLOBAL Global (Avg dependencies per file)"
|
|
122
|
+
echo "Avg Context Weight (LOC + Loc*20 + Glob*5): $AVG_WEIGHT ($WEIGHT_CLASS)"
|
|
123
|
+
echo ""
|
|
124
|
+
|
|
125
|
+
# Scenario 1: No README (blind navigation)
|
|
126
|
+
echo "📍 WITHOUT README (blind navigation)"
|
|
127
|
+
echo "────────────────────────────────────────"
|
|
128
|
+
|
|
129
|
+
LIST_DIR_CALLS=0
|
|
130
|
+
VIEW_FILE_CALLS=0
|
|
131
|
+
|
|
132
|
+
# Count directories that need list_dir
|
|
133
|
+
SEARCH_DIR="$TARGET/src"
|
|
134
|
+
if [[ ! -d "$SEARCH_DIR" ]]; then
|
|
135
|
+
SEARCH_DIR="$TARGET"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
DIR_COUNT=$(find "$SEARCH_DIR" -maxdepth 2 -type d -not -path '*/node_modules/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' -not -path '*/.*' 2>/dev/null | wc -l | tr -d ' ')
|
|
139
|
+
LIST_DIR_CALLS=$((DIR_COUNT))
|
|
140
|
+
|
|
141
|
+
# Agent would need to view ~30% of files to understand the package
|
|
142
|
+
VIEW_FILE_CALLS=$((SRC_FILES * 30 / 100))
|
|
143
|
+
[[ $VIEW_FILE_CALLS -lt 3 ]] && VIEW_FILE_CALLS=3
|
|
144
|
+
|
|
145
|
+
# Estimate tokens per operation
|
|
146
|
+
LIST_DIR_TOKENS=$((LIST_DIR_CALLS * 250)) # ~250 tokens per list_dir
|
|
147
|
+
AVG_FILE_TOKENS=$((TOTAL_TOKENS / SRC_FILES))
|
|
148
|
+
[[ $AVG_FILE_TOKENS -lt 100 ]] && AVG_FILE_TOKENS=100
|
|
149
|
+
VIEW_FILE_TOKENS=$((VIEW_FILE_CALLS * AVG_FILE_TOKENS))
|
|
150
|
+
|
|
151
|
+
BLIND_TOTAL=$((LIST_DIR_TOKENS + VIEW_FILE_TOKENS))
|
|
152
|
+
|
|
153
|
+
echo " list_dir calls: ${LIST_DIR_CALLS} (~${LIST_DIR_TOKENS} tokens)"
|
|
154
|
+
echo " view_file calls: ${VIEW_FILE_CALLS} (~${VIEW_FILE_TOKENS} tokens)"
|
|
155
|
+
echo " TOTAL: ~${BLIND_TOTAL} tokens"
|
|
156
|
+
echo ""
|
|
157
|
+
|
|
158
|
+
# Scenario 2: With README (guided navigation)
|
|
159
|
+
echo "📍 WITH README (guided navigation)"
|
|
160
|
+
echo "────────────────────────────────────────"
|
|
161
|
+
|
|
162
|
+
README_EXISTS="No"
|
|
163
|
+
README_TOKENS=0
|
|
164
|
+
if [[ -f "$TARGET/README.md" ]]; then
|
|
165
|
+
README_EXISTS="Yes"
|
|
166
|
+
README_BYTES=$(wc -c < "$TARGET/README.md" | tr -d ' ')
|
|
167
|
+
README_TOKENS=$((README_BYTES / CHARS_PER_TOKEN))
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# With README, agent only needs README + 1-3 target files
|
|
171
|
+
GUIDED_VIEW_CALLS=3
|
|
172
|
+
GUIDED_VIEW_TOKENS=$((GUIDED_VIEW_CALLS * AVG_FILE_TOKENS))
|
|
173
|
+
GUIDED_TOTAL=$((README_TOKENS + GUIDED_VIEW_TOKENS))
|
|
174
|
+
|
|
175
|
+
echo " README exists: ${README_EXISTS}"
|
|
176
|
+
echo " README size: ~${README_TOKENS} tokens"
|
|
177
|
+
echo " view_file calls: ${GUIDED_VIEW_CALLS} (targeted)"
|
|
178
|
+
echo " TOTAL: ~${GUIDED_TOTAL} tokens"
|
|
179
|
+
echo ""
|
|
180
|
+
|
|
181
|
+
# Comparison
|
|
182
|
+
echo "📈 COMPARISON"
|
|
183
|
+
echo "────────────────────────────────────────"
|
|
184
|
+
|
|
185
|
+
if [[ $BLIND_TOTAL -gt 0 ]]; then
|
|
186
|
+
SAVINGS=$(( (BLIND_TOTAL - GUIDED_TOTAL) * 100 / BLIND_TOTAL ))
|
|
187
|
+
else
|
|
188
|
+
SAVINGS=0
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
echo " Without README: ~${BLIND_TOTAL} tokens"
|
|
192
|
+
echo " With README: ~${GUIDED_TOTAL} tokens"
|
|
193
|
+
echo " Savings: ${SAVINGS}%"
|
|
194
|
+
echo ""
|
|
195
|
+
|
|
196
|
+
if [[ "$README_EXISTS" == "No" ]]; then
|
|
197
|
+
echo "⚠️ README.md is MISSING! Creating one would save ~$((BLIND_TOTAL - GUIDED_TOTAL)) tokens per task."
|
|
198
|
+
else
|
|
199
|
+
# Check if README has Source Structure
|
|
200
|
+
if grep -q "Source Structure\|src/" "$TARGET/README.md" 2>/dev/null; then
|
|
201
|
+
echo "✅ README has Source Structure — agent can navigate efficiently"
|
|
202
|
+
else
|
|
203
|
+
echo "⚠️ README exists but lacks Source Structure section"
|
|
204
|
+
echo " Adding it would improve navigation further."
|
|
205
|
+
fi
|
|
206
|
+
fi
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: scan-fnh — Generate a repository map from FNH headers | DEPS: none
|
|
3
|
+
# USAGE: bash tools/scan-fnh.sh [path] [--internal-scope=...] [--depth=N] [--no-legend]
|
|
4
|
+
# OUTPUT: File map mapping paths to their semantic FNH descriptions and context metrics
|
|
5
|
+
# MODE: read-only analysis
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
TARGET="."
|
|
10
|
+
INTERNAL_SCOPES=""
|
|
11
|
+
DEPTH="all"
|
|
12
|
+
SHOW_LEGEND=true
|
|
13
|
+
|
|
14
|
+
show_help() {
|
|
15
|
+
echo "Usage: bash tools/scan-fnh.sh [target] [options]"
|
|
16
|
+
echo ""
|
|
17
|
+
echo "Scans files and extracts their FNH (File Navigation Header)."
|
|
18
|
+
echo "If FNH is missing, computes 'Context Weight' (LOC + Fan-out) on the fly."
|
|
19
|
+
echo "Respects .gitignore natively via git ls-files."
|
|
20
|
+
echo ""
|
|
21
|
+
echo "Arguments:"
|
|
22
|
+
echo " target Directory or specific file to scan (default: .)"
|
|
23
|
+
echo ""
|
|
24
|
+
echo "Options:"
|
|
25
|
+
echo " --depth=N Limit scan depth to N subdirectories (default: all)."
|
|
26
|
+
echo " --internal-scope=@org,pkg Treat these scopes as local imports when calculating penalty weight."
|
|
27
|
+
echo " --no-legend Hide formatting legend and headers, returning pure table data."
|
|
28
|
+
echo " -h, --help Show this help message."
|
|
29
|
+
exit 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
while [[ $# -gt 0 ]]; do
|
|
33
|
+
case $1 in
|
|
34
|
+
--internal-scope=*)
|
|
35
|
+
INTERNAL_SCOPES="${1#*=}"
|
|
36
|
+
shift
|
|
37
|
+
;;
|
|
38
|
+
--depth=*)
|
|
39
|
+
DEPTH="${1#*=}"
|
|
40
|
+
shift
|
|
41
|
+
;;
|
|
42
|
+
--no-legend)
|
|
43
|
+
SHOW_LEGEND=false
|
|
44
|
+
shift
|
|
45
|
+
;;
|
|
46
|
+
-h|--help)
|
|
47
|
+
show_help
|
|
48
|
+
;;
|
|
49
|
+
*)
|
|
50
|
+
TARGET="$1"
|
|
51
|
+
shift
|
|
52
|
+
;;
|
|
53
|
+
esac
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
if [[ ! -e "$TARGET" ]]; then
|
|
57
|
+
echo "❌ Error: target '$TARGET' does not exist"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
IS_SINGLE_FILE=false
|
|
62
|
+
if [[ -f "$TARGET" ]]; then
|
|
63
|
+
IS_SINGLE_FILE=true
|
|
64
|
+
TARGET_DIR="$(dirname "$TARGET")"
|
|
65
|
+
TARGET_FILE="$(basename "$TARGET")"
|
|
66
|
+
cd "$TARGET_DIR"
|
|
67
|
+
else
|
|
68
|
+
cd "$TARGET"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if [[ "$SHOW_LEGEND" == true ]]; then
|
|
72
|
+
echo "🗺️ File Navigation Header (FNH) Semantic Map: $(pwd)"
|
|
73
|
+
if [[ -n "$INTERNAL_SCOPES" ]]; then
|
|
74
|
+
echo "🔍 Treating scopes as local: $INTERNAL_SCOPES"
|
|
75
|
+
fi
|
|
76
|
+
echo "FORMAT: [LOC L] path/to/file | Semantic Description OR 🔴 [NO FNH] ContextWeight W (L_loc: local_deps, I_glb: global_deps)"
|
|
77
|
+
echo "───────────────────────────────────────────────────────────────────────────────"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Construct regex for local imports (used if we need to calculate context weight)
|
|
81
|
+
if [[ -n "$INTERNAL_SCOPES" ]]; then
|
|
82
|
+
SCOPE_PATTERN=$(echo "$INTERNAL_SCOPES" | tr ',' '|')
|
|
83
|
+
LOCAL_REGEX="^(import |from |use |const .* = require\()).*['\"](\.|/|@/|~/|${SCOPE_PATTERN})"
|
|
84
|
+
else
|
|
85
|
+
LOCAL_REGEX="^(import |from |use |const .* = require\()).*['\"](\.|/|@/|~/)"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# FNH Matching Regex (includes all common AI directives across languages)
|
|
89
|
+
FNH_REGEX="(FNH:|Category:|Tool:|Role:|Guide:|Checklist:|Knowledge:|Architecture:|Report:|Skill:|Service:|Middleware:|Controller:|Repository:|Entity:|Types:|Utility:|Config:|Schema:|Hook:|Component:|Test:|Migration:|Script:|Constants:|Factory:|Adapter:|Event:|Guard:|Package:)"
|
|
90
|
+
|
|
91
|
+
# Extension filter for code/doc files
|
|
92
|
+
EXT_REGEX='\.(ts|tsx|js|jsx|sol|py|rs|md|yml|yaml|sh|json)$|^Dockerfile'
|
|
93
|
+
|
|
94
|
+
# Discover files (Prefer git ls-files to automatically respect all .gitignore files)
|
|
95
|
+
if [[ "$IS_SINGLE_FILE" == true ]]; then
|
|
96
|
+
FILES="$TARGET_FILE"
|
|
97
|
+
else
|
|
98
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
99
|
+
FILES=$(git ls-files | grep -E "$EXT_REGEX" | grep -vE '(package-lock\.json|yarn\.lock|pnpm-lock\.yaml)' || true)
|
|
100
|
+
else
|
|
101
|
+
FILES=$(find . -type f | sed 's|^\./||' | grep -E "$EXT_REGEX" | grep -v 'node_modules/' | grep -v 'dist/' | grep -v '\.git/' | grep -vE '(package-lock\.json|yarn\.lock|pnpm-lock\.yaml)' || true)
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if [[ "$DEPTH" != "all" && "$DEPTH" =~ ^[0-9]+$ ]]; then
|
|
105
|
+
FILES=$(echo "$FILES" | awk -F'/' -v d="$DEPTH" '{ if (NF <= d) print $0 }')
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
if [[ -z "$FILES" ]]; then
|
|
110
|
+
if [[ "$SHOW_LEGEND" == true ]]; then
|
|
111
|
+
echo "No relevant files found."
|
|
112
|
+
fi
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
echo "$FILES" | sort | while read -r FILE; do
|
|
117
|
+
|
|
118
|
+
if [[ ! -f "$FILE" ]]; then continue; fi
|
|
119
|
+
|
|
120
|
+
# Count LOC (trimming whitespace)
|
|
121
|
+
LOC=$(wc -l < "$FILE" | tr -d ' ')
|
|
122
|
+
|
|
123
|
+
# Extract FNH string
|
|
124
|
+
DESC=""
|
|
125
|
+
if [[ "$FILE" == *.json ]]; then
|
|
126
|
+
# Light JSON parsing for description
|
|
127
|
+
DESC=$(grep -E '"description"\s*:' "$FILE" 2>/dev/null | head -n 1 | sed -E 's/.*"description"\s*:\s*"([^"]+)".*/\1/' || true)
|
|
128
|
+
if [[ -n "$DESC" ]]; then
|
|
129
|
+
DESC="Description: $DESC"
|
|
130
|
+
fi
|
|
131
|
+
else
|
|
132
|
+
# 5-line lookahead is purely a search window. We output only the matched line.
|
|
133
|
+
HEADER=$(head -n 5 "$FILE" | grep -iE "$FNH_REGEX" | head -n 1 || true)
|
|
134
|
+
|
|
135
|
+
if [[ -n "$HEADER" ]]; then
|
|
136
|
+
# Clean up formatting wrappers (HTML, JSDoc, blockquotes, python docstrings)
|
|
137
|
+
CL_HEADER=$(echo "$HEADER" | sed -E 's/<!--\s*|\s*-->//g' | sed -E 's/\/\*\*\s*|\s*\*\///g' | sed -E 's/^[>#"-]+ //g' | sed -E 's/"""//g')
|
|
138
|
+
# Strip leading/trailing margins
|
|
139
|
+
DESC=$(echo "$CL_HEADER" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Report Output Formatting
|
|
144
|
+
# Minimal formatting to preserve AI context tokens (no excessive space padding)
|
|
145
|
+
if [[ -n "$DESC" ]]; then
|
|
146
|
+
echo "[$LOC L] $FILE | $DESC"
|
|
147
|
+
else
|
|
148
|
+
# If standard markdown, just say NO FNH without calculating code context weight
|
|
149
|
+
if [[ "$FILE" == *.md || "$FILE" == *.json || "$FILE" == *.yml || "$FILE" == *.yaml ]]; then
|
|
150
|
+
if [[ $(basename "$FILE") == "README.md" ]]; then
|
|
151
|
+
echo "[$LOC L] $FILE | ⚠️ [Standard README]"
|
|
152
|
+
else
|
|
153
|
+
echo "[$LOC L] $FILE | 🔴 [NO FNH]"
|
|
154
|
+
fi
|
|
155
|
+
else
|
|
156
|
+
# If code file, calculate Context Weight as a penalty/diagnostic string!
|
|
157
|
+
IMPORTS_ALL=$(grep -E "^(import |from |use |const .* = require\()" "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
158
|
+
IMPORTS_LOCAL=$(grep -E "$LOCAL_REGEX" "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
159
|
+
IMPORTS_GLOBAL=$((IMPORTS_ALL - IMPORTS_LOCAL))
|
|
160
|
+
|
|
161
|
+
WEIGHT=$((LOC + (IMPORTS_LOCAL * 20) + (IMPORTS_GLOBAL * 5)))
|
|
162
|
+
|
|
163
|
+
# Print with rich context penalty warning
|
|
164
|
+
echo "[$LOC L] $FILE | 🔴 [NO FNH] $WEIGHT W (L_loc: $IMPORTS_LOCAL, I_glb: $IMPORTS_GLOBAL)"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
done
|
|
169
|
+
|
|
170
|
+
if [[ "$SHOW_LEGEND" == true ]]; then
|
|
171
|
+
echo "───────────────────────────────────────────────────────────────────────────────"
|
|
172
|
+
echo "AI Context Tip: Use this map to navigate the repository instantly without blind list_dir scans."
|
|
173
|
+
fi
|
|
174
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: shared/config.sh
|
|
3
|
+
# FNH: Global Configuration for ai-context-surgeon Bash Tools | SECTIONS: Exclusions, Environment
|
|
4
|
+
# Description: Provides unified exclusion logic for CI scanners to ensure blind ecosystem adaptability.
|
|
5
|
+
|
|
6
|
+
# -----------------------------------------------------------------------------
|
|
7
|
+
# FIND_EXCLUDES: Used by tools calling `find` directly
|
|
8
|
+
# Usage: find "$DIR" -type f "${FIND_EXCLUDES[@]}"
|
|
9
|
+
# -----------------------------------------------------------------------------
|
|
10
|
+
export FIND_EXCLUDES=(
|
|
11
|
+
"!" "-path" "*/node_modules/*"
|
|
12
|
+
"!" "-path" "*/lib/forge-std/*"
|
|
13
|
+
"!" "-path" "*/lib/openzeppelin-contracts*"
|
|
14
|
+
"!" "-path" "*/lib/erc721a*"
|
|
15
|
+
"!" "-path" "*/venv/*"
|
|
16
|
+
"!" "-path" "*/.venv/*"
|
|
17
|
+
"!" "-path" "*/vendor/*"
|
|
18
|
+
"!" "-path" "*/target/*"
|
|
19
|
+
"!" "-path" "*/dist/*"
|
|
20
|
+
"!" "-path" "*/build/*"
|
|
21
|
+
"!" "-path" "*/out/*"
|
|
22
|
+
"!" "-path" "*/out_forge/*"
|
|
23
|
+
"!" "-path" "*/.next/*"
|
|
24
|
+
"!" "-path" "*/.cache/*"
|
|
25
|
+
"!" "-path" "*/.turbo/*"
|
|
26
|
+
"!" "-path" "*/.git/*"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# -----------------------------------------------------------------------------
|
|
30
|
+
# GREP_EXCLUDES_REGEX: Used for fast `grep -vE` filtering
|
|
31
|
+
# -----------------------------------------------------------------------------
|
|
32
|
+
export GREP_EXCLUDES_REGEX="node_modules|lib/(forge-std|openzeppelin|erc721a)|venv|\.venv|vendor|target|dist|build|out|out_forge|\.next|\.cache|\.turbo|\.git"
|
|
33
|
+
|
|
34
|
+
# -----------------------------------------------------------------------------
|
|
35
|
+
# BASH_ARRAY_EXCLUDES: Used when scripting bash loops and `find` match conditions
|
|
36
|
+
# -----------------------------------------------------------------------------
|
|
37
|
+
export BASH_ARRAY_EXCLUDES=(
|
|
38
|
+
"*/node_modules/*"
|
|
39
|
+
"*/lib/forge-std/*"
|
|
40
|
+
"*/lib/openzeppelin-contracts*"
|
|
41
|
+
"*/lib/erc721a*"
|
|
42
|
+
"*/venv/*"
|
|
43
|
+
"*/.venv/*"
|
|
44
|
+
"*/vendor/*"
|
|
45
|
+
"*/target/*"
|
|
46
|
+
"*/dist/*"
|
|
47
|
+
"*/build/*"
|
|
48
|
+
"*/out/*"
|
|
49
|
+
"*/.next/*"
|
|
50
|
+
"*/.cache/*"
|
|
51
|
+
"*/.turbo/*"
|
|
52
|
+
"*/.git/*"
|
|
53
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tool: validate-context-hygiene — Verify target directories contain compliance files | DEPS: none
|
|
3
|
+
# USAGE: bash tools/validate-context-hygiene.sh [directory_patterns...]
|
|
4
|
+
# OUTPUT: success/fail logs for file presence
|
|
5
|
+
# MODE: ci-compliance
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/shared/config.sh"
|
|
10
|
+
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
NC='\033[0m'
|
|
14
|
+
|
|
15
|
+
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
|
16
|
+
echo "Usage: bash tools/validate-context-hygiene.sh [directories...]"
|
|
17
|
+
echo "Example: bash tools/validate-context-hygiene.sh apps/* packages/*"
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [ "$#" -eq 0 ]; then
|
|
22
|
+
echo "❌ Error: Please provide directories to check."
|
|
23
|
+
echo "Example: bash tools/validate-context-hygiene.sh apps/* packages/*"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
errors=0
|
|
28
|
+
checked=0
|
|
29
|
+
|
|
30
|
+
for dir in "$@"; do
|
|
31
|
+
[ -d "$dir" ] || continue
|
|
32
|
+
|
|
33
|
+
checked=$((checked + 1))
|
|
34
|
+
|
|
35
|
+
for file in README.md .gitignore .antigravityignore; do
|
|
36
|
+
if [ ! -f "$dir/$file" ]; then
|
|
37
|
+
echo -e "${RED}❌ Missing${NC} $dir/$file"
|
|
38
|
+
errors=$((errors + 1))
|
|
39
|
+
fi
|
|
40
|
+
done
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
echo ""
|
|
44
|
+
if [ "$errors" -eq 0 ]; then
|
|
45
|
+
echo -e "${GREEN}✅ All $checked directories have README.md, .gitignore, .antigravityignore${NC}"
|
|
46
|
+
exit 0
|
|
47
|
+
else
|
|
48
|
+
echo -e "${RED}❌ $errors missing files across $checked directories${NC}"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Fix: Each target package needs self-contained ignore files + README."
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: validate-context-weight — Identify files with massive context weight | DEPS: none
|
|
3
|
+
# USAGE: bash tools/validate-context-weight.sh /path/to/package
|
|
4
|
+
# OUTPUT: files exceeding context weight threshold
|
|
5
|
+
# MODE: read-only analysis
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
TARGET="."
|
|
10
|
+
INTERNAL_SCOPES=""
|
|
11
|
+
|
|
12
|
+
while [[ $# -gt 0 ]]; do
|
|
13
|
+
case $1 in
|
|
14
|
+
--internal-scope=*)
|
|
15
|
+
INTERNAL_SCOPES="${1#*=}"
|
|
16
|
+
shift
|
|
17
|
+
;;
|
|
18
|
+
-h|--help)
|
|
19
|
+
echo "Usage: bash tools/validate-context-weight.sh [target_dir] [--internal-scope=@myorg,my-package]"
|
|
20
|
+
echo ""
|
|
21
|
+
echo "Finds files that are highly complex based on Context Weight formula:"
|
|
22
|
+
echo "Weight = LOC + (Local Imports * 20) + (Global Imports * 5)"
|
|
23
|
+
exit 0
|
|
24
|
+
;;
|
|
25
|
+
*)
|
|
26
|
+
TARGET="$1"
|
|
27
|
+
shift
|
|
28
|
+
;;
|
|
29
|
+
esac
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
if [[ ! -d "$TARGET" ]]; then
|
|
33
|
+
echo "❌ Error: '$TARGET' is not a directory"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
echo "⚖️ Context Weight Analysis: $TARGET"
|
|
38
|
+
if [[ -n "$INTERNAL_SCOPES" ]]; then
|
|
39
|
+
echo "🔍 Treating scopes as local: $INTERNAL_SCOPES"
|
|
40
|
+
fi
|
|
41
|
+
echo "────────────────────────────────────────"
|
|
42
|
+
|
|
43
|
+
# Construct regex for local imports
|
|
44
|
+
if [[ -n "$INTERNAL_SCOPES" ]]; then
|
|
45
|
+
SCOPE_PATTERN=$(echo "$INTERNAL_SCOPES" | tr ',' '|')
|
|
46
|
+
LOCAL_REGEX="^(import |from |use |const .* = require\()).*['\"](\.|/|@/|~/|${SCOPE_PATTERN})"
|
|
47
|
+
else
|
|
48
|
+
LOCAL_REGEX="^(import |from |use |const .* = require\()).*['\"](\.|/|@/|~/)"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Temporary file to store results
|
|
52
|
+
TMP_RESULTS=$(mktemp)
|
|
53
|
+
|
|
54
|
+
# Find all relevant files
|
|
55
|
+
find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' -o -name '*.py' -o -name '*.rs' \) \
|
|
56
|
+
-not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
|
|
57
|
+
-not -name '*.d.ts' -not -name '*.test.*' -not -name '*.spec.*' | while read -r FILE; do
|
|
58
|
+
|
|
59
|
+
LOC=$(wc -l < "$FILE" | tr -d ' ')
|
|
60
|
+
|
|
61
|
+
IMPORTS_ALL=$(grep -E "^(import |from |use |const .* = require\()" "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
62
|
+
IMPORTS_LOCAL=$(grep -E "$LOCAL_REGEX" "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
63
|
+
IMPORTS_GLOBAL=$((IMPORTS_ALL - IMPORTS_LOCAL))
|
|
64
|
+
|
|
65
|
+
WEIGHT=$((LOC + (IMPORTS_LOCAL * 20) + (IMPORTS_GLOBAL * 5)))
|
|
66
|
+
|
|
67
|
+
# Only report if weight is high enough
|
|
68
|
+
if [[ $WEIGHT -gt 150 ]]; then
|
|
69
|
+
echo "$WEIGHT|$LOC|$IMPORTS_LOCAL|$IMPORTS_GLOBAL|$FILE" >> "$TMP_RESULTS"
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# Check if there are results
|
|
74
|
+
if [[ -s "$TMP_RESULTS" ]]; then
|
|
75
|
+
echo "Found files with high Context Weight (Warning > 150, Critical > 300):"
|
|
76
|
+
|
|
77
|
+
# Sort by weight (descending) and format output
|
|
78
|
+
sort -t '|' -k1 -rn "$TMP_RESULTS" | awk -F '|' '{
|
|
79
|
+
weight=$1; loc=$2; i_loc=$3; i_glob=$4; file=$5;
|
|
80
|
+
|
|
81
|
+
if (weight > 300) {
|
|
82
|
+
color="\033[0;31m"; # Red
|
|
83
|
+
icon="🔴";
|
|
84
|
+
} else {
|
|
85
|
+
color="\033[1;33m"; # Yellow
|
|
86
|
+
icon="⚠️ ";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
printf " %s%s %d W\033[0m (L:%d, I_loc:%d, I_glb:%d) %s\n", color, icon, weight, loc, i_loc, i_glob, file
|
|
90
|
+
}'
|
|
91
|
+
|
|
92
|
+
echo ""
|
|
93
|
+
echo "Context Weight = LOC + (Local Imports * 20) + (Global Imports * 5)"
|
|
94
|
+
echo "To reduce weight, either extract dependencies or split the logic."
|
|
95
|
+
else
|
|
96
|
+
echo "✅ No files exceed context weight threshold (150 W)!"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
rm "$TMP_RESULTS"
|
|
100
|
+
exit 0
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tool: validate-naming — checks naming conventions across monorepo | DEPS: none
|
|
3
|
+
# USAGE: bash tools/validate-naming.sh /path/to/repo
|
|
4
|
+
# OUTPUT: naming violations grouped by type
|
|
5
|
+
# MODE: read-only, no modifications
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
source "$(dirname "$0")/shared/config.sh"
|
|
10
|
+
|
|
11
|
+
TARGET="${1:-.}"
|
|
12
|
+
VIOLATIONS=0
|
|
13
|
+
|
|
14
|
+
echo "📝 Naming Convention Validator"
|
|
15
|
+
echo " Target: $TARGET"
|
|
16
|
+
echo " ──────────────────────────────────"
|
|
17
|
+
echo ""
|
|
18
|
+
|
|
19
|
+
# Rule 1: Files should be kebab-case (except special files)
|
|
20
|
+
echo " ### Rule 1: Files must be kebab-case.ts"
|
|
21
|
+
echo " Exceptions: index.ts, AGENTS.md, README.md, LICENSE, Dockerfile, .env"
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
SPECIAL_FILES="index\|README\|AGENTS\|LICENSE\|Dockerfile\|CHANGELOG\|CONTRIBUTING\|SECURITY\|\.env\|\.gitignore\|\.npmrc\|\.eslintrc\|\.prettierrc\|node_modules\|\.git"
|
|
25
|
+
|
|
26
|
+
BAD_FILES=$(find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' \) \
|
|
27
|
+
! -path '*/node_modules/*' ! -path '*/lib/forge-std/*' ! -path '*/lib/openzeppelin-contracts*' ! -path '*/lib/erc721a*' ! -path '*/.git/*' ! -path '*/dist/*' ! -path '*/.next/*' \
|
|
28
|
+
| xargs -I {} basename {} \
|
|
29
|
+
| grep -v "^index\." \
|
|
30
|
+
| grep -v "^[a-z][a-z0-9]*\(-[a-z0-9]*\)*\.\(ts\|tsx\|js\|jsx\)$" \
|
|
31
|
+
| sort -u 2>/dev/null || true)
|
|
32
|
+
|
|
33
|
+
if [ -n "$BAD_FILES" ]; then
|
|
34
|
+
echo "$BAD_FILES" | while IFS= read -r name; do
|
|
35
|
+
echo " ❌ $name — should be kebab-case"
|
|
36
|
+
done
|
|
37
|
+
echo ""
|
|
38
|
+
else
|
|
39
|
+
echo " ✅ All source files use kebab-case"
|
|
40
|
+
echo ""
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Rule 2: Directories should be kebab-case
|
|
44
|
+
echo " ### Rule 2: Directories must be kebab-case"
|
|
45
|
+
echo " Exceptions: node_modules, .git, .agents, .github, __tests__, __mocks__"
|
|
46
|
+
echo ""
|
|
47
|
+
|
|
48
|
+
BAD_DIRS=$(find "$TARGET" -type d \
|
|
49
|
+
! -path '*/node_modules/*' ! -path '*/lib/forge-std/*' ! -path '*/lib/openzeppelin-contracts*' ! -path '*/lib/erc721a*' ! -path '*/.git/*' ! -path '*/dist/*' ! -path '*/.next/*' \
|
|
50
|
+
| xargs -I {} basename {} \
|
|
51
|
+
| grep -v '^\.' \
|
|
52
|
+
| grep -v '^__' \
|
|
53
|
+
| grep -v '^node_modules$' \
|
|
54
|
+
| grep -v '^[a-z][a-z0-9]*\(-[a-z0-9]*\)*$' \
|
|
55
|
+
| sort -u 2>/dev/null || true)
|
|
56
|
+
|
|
57
|
+
if [ -n "$BAD_DIRS" ]; then
|
|
58
|
+
echo "$BAD_DIRS" | while IFS= read -r name; do
|
|
59
|
+
echo " ⚠️ $name — should be kebab-case"
|
|
60
|
+
done
|
|
61
|
+
echo ""
|
|
62
|
+
else
|
|
63
|
+
echo " ✅ All directories use kebab-case"
|
|
64
|
+
echo ""
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Rule 3: Check for inconsistent naming in same directory
|
|
68
|
+
echo " ### Rule 3: Consistency within directories"
|
|
69
|
+
echo ""
|
|
70
|
+
|
|
71
|
+
MIXED=0
|
|
72
|
+
OLD_IFS=$IFS
|
|
73
|
+
IFS='
|
|
74
|
+
'
|
|
75
|
+
for dir in $(find "$TARGET" -type d ! -path '*/node_modules/*' ! -path '*/lib/forge-std/*' ! -path '*/lib/openzeppelin-contracts*' ! -path '*/lib/erc721a*' ! -path '*/.git/*' ! -path '*/dist/*' 2>/dev/null); do
|
|
76
|
+
[ -z "$dir" ] && continue
|
|
77
|
+
|
|
78
|
+
FILES=$(find "$dir" -maxdepth 1 -type f \( -name '*.ts' -o -name '*.js' -o -name '*.sol' \) ! -name 'index.*' 2>/dev/null)
|
|
79
|
+
[ -z "$FILES" ] && continue
|
|
80
|
+
|
|
81
|
+
HAS_CAMEL=$(echo "$FILES" | xargs -I {} basename {} | grep -c '[A-Z]' || true)
|
|
82
|
+
HAS_KEBAB=$(echo "$FILES" | xargs -I {} basename {} | grep -c '\-' || true)
|
|
83
|
+
|
|
84
|
+
if [ "$HAS_CAMEL" -gt 0 ] && [ "$HAS_KEBAB" -gt 0 ]; then
|
|
85
|
+
echo " ⚠️ Mixed naming in: $dir"
|
|
86
|
+
echo " camelCase: $HAS_CAMEL files, kebab-case: $HAS_KEBAB files"
|
|
87
|
+
MIXED=$((MIXED + 1))
|
|
88
|
+
fi
|
|
89
|
+
done
|
|
90
|
+
IFS=$OLD_IFS
|
|
91
|
+
|
|
92
|
+
if [ "$MIXED" -eq 0 ]; then
|
|
93
|
+
echo " ✅ No mixed naming conventions within directories"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
echo ""
|
|
97
|
+
echo " ──────────────────────────────────"
|
|
98
|
+
echo " Naming check complete"
|