@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.
Files changed (87) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +372 -0
  3. package/bin/catalog.js +153 -0
  4. package/bin/cli.js +380 -0
  5. package/bin/installer.js +135 -0
  6. package/bin/prompts.js +371 -0
  7. package/checklists/phase-1-analysis.md +58 -0
  8. package/checklists/phase-2-planning.md +67 -0
  9. package/checklists/phase-3-restructuring.md +77 -0
  10. package/checklists/phase-4-documentation.md +111 -0
  11. package/checklists/phase-5-validation.md +91 -0
  12. package/examples/before-after/README.md +139 -0
  13. package/examples/ideal-monorepo/README.md +127 -0
  14. package/knowledge/agent-context-system/artifacts/guide.md +183 -0
  15. package/knowledge/agent-context-system/artifacts/knowledge.md +177 -0
  16. package/knowledge/agent-context-system/artifacts/skills.md +101 -0
  17. package/knowledge/agent-context-system/artifacts/workflows.md +143 -0
  18. package/knowledge/agent-context-system/metadata.json +26 -0
  19. package/knowledge/agent-context-system/timestamps.json +5 -0
  20. package/knowledge/agent-vulnerabilities/LICENSE +21 -0
  21. package/knowledge/agent-vulnerabilities/artifacts/stealth_injection.md +110 -0
  22. package/knowledge/agent-vulnerabilities/artifacts/vulnerabilities.md +232 -0
  23. package/knowledge/agent-vulnerabilities/metadata.json +14 -0
  24. package/knowledge/agent-vulnerabilities/timestamps.json +5 -0
  25. package/knowledge/power-words-dictionary/LICENSE +21 -0
  26. package/knowledge/power-words-dictionary/artifacts/dictionary.md +231 -0
  27. package/knowledge/power-words-dictionary/artifacts/prompt_amplifier.py +381 -0
  28. package/knowledge/power-words-dictionary/metadata.json +14 -0
  29. package/knowledge/power-words-dictionary/timestamps.json +5 -0
  30. package/package.json +77 -0
  31. package/roles/README.md +81 -0
  32. package/roles/architect.md +203 -0
  33. package/roles/inspector.md +232 -0
  34. package/roles/librarian.md +176 -0
  35. package/roles/scout.md +169 -0
  36. package/roles/surgeon.md +172 -0
  37. package/roles/tuner.md +204 -0
  38. package/skills/annotate-jsdoc/SKILL.md +262 -0
  39. package/skills/prompt-engineering/LICENSE +21 -0
  40. package/skills/prompt-engineering/SKILL.md +235 -0
  41. package/skills/prompt-engineering/scripts/extract_instructions.py +416 -0
  42. package/skills/prompt-engineering/scripts/prompt_amplifier.py +381 -0
  43. package/skills/prompt-engineering/scripts/prompt_diff_tracker.py +281 -0
  44. package/skills/prompt-engineering/scripts/prompt_dna_analyzer.py +692 -0
  45. package/skills/prompt-engineering/scripts/templates/code_review.md +47 -0
  46. package/skills/prompt-engineering/scripts/templates/dump_extraction.md +59 -0
  47. package/skills/prompt-engineering/scripts/templates/multi_agent_orchestration.md +100 -0
  48. package/skills/prompt-engineering/scripts/templates/prompt_audit.md +106 -0
  49. package/skills/prompt-engineering/scripts/templates/stealth_injection.md +110 -0
  50. package/skills/prompt-engineering/scripts/templates/task_automation.md +87 -0
  51. package/skills/prompt-engineering/workflows/amplify.md +36 -0
  52. package/skills/prompt-engineering/workflows/audit.md +55 -0
  53. package/skills/prompt-engineering/workflows/context-dump.md +90 -0
  54. package/skills/prompt-engineering/workflows/diff.md +44 -0
  55. package/strategy/bash-guide.md +134 -0
  56. package/strategy/context-exclusion.md +220 -0
  57. package/strategy/context-weight-theory.md +49 -0
  58. package/strategy/file-navigation-header.md +562 -0
  59. package/strategy/jsdoc-guide.md +596 -0
  60. package/strategy/monorepo_strategy.md +726 -0
  61. package/strategy/package-json-guide.md +541 -0
  62. package/templates/AGENTS.md.template +148 -0
  63. package/templates/antigravityignore.template +64 -0
  64. package/templates/cursorrules.template +7 -0
  65. package/templates/knowledge-item.template +44 -0
  66. package/templates/package-json-ideal.template +26 -0
  67. package/templates/package-readme.template +45 -0
  68. package/templates/publish-meta.template +34 -0
  69. package/templates/skill.template +50 -0
  70. package/templates/workflow.template +33 -0
  71. package/tools/analyze-package-json.sh +213 -0
  72. package/tools/analyze-structure.sh +101 -0
  73. package/tools/audit-jsdoc.sh +176 -0
  74. package/tools/check-fnh-freshness.sh +74 -0
  75. package/tools/detect-circular-deps.sh +147 -0
  76. package/tools/detect-god-files.sh +71 -0
  77. package/tools/enforce-god-files.sh +112 -0
  78. package/tools/enrich-package-json.js +311 -0
  79. package/tools/generate-jsdoc-headers.sh +109 -0
  80. package/tools/generate-source-map.sh +71 -0
  81. package/tools/lint-imports.sh +123 -0
  82. package/tools/measure-context-cost.sh +206 -0
  83. package/tools/scan-fnh.sh +174 -0
  84. package/tools/shared/config.sh +53 -0
  85. package/tools/validate-context-hygiene.sh +52 -0
  86. package/tools/validate-context-weight.sh +100 -0
  87. package/tools/validate-naming.sh +98 -0
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env node
2
+ /** Category: Tool — Enrich lean package.json with publish metadata before npm publish. | DEPS: node:fs, node:path
3
+ *
4
+ * Usage:
5
+ * node tools/enrich-package-json.js /path/to/package [--dry-run]
6
+ * node tools/enrich-package-json.js /path/to/monorepo --all [--dry-run]
7
+ *
8
+ * Reads .publish-meta.json from project root for shared metadata,
9
+ * merges it into the target package.json, and writes the result.
10
+ *
11
+ * Use --dry-run to preview without writing.
12
+ * Use --restore to strip publish metadata back to lean version.
13
+ */
14
+
15
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
16
+ import { join, resolve, basename } from "node:path";
17
+
18
+ // ── Config ──────────────────────────────────────────────
19
+
20
+ const PUBLISH_META_FILE = ".publish-meta.json";
21
+
22
+ // Fields that enrich adds (and --restore removes)
23
+ const ENRICHED_FIELDS = [
24
+ "author",
25
+ "license",
26
+ "repository",
27
+ "bugs",
28
+ "homepage",
29
+ "keywords",
30
+ "funding",
31
+ "contributors",
32
+ "publishConfig",
33
+ "engines",
34
+ "os",
35
+ "cpu",
36
+ "sideEffects",
37
+ "files",
38
+ ];
39
+
40
+ // Fields order for clean output (Tier 1 first, metadata last)
41
+ const FIELD_ORDER = [
42
+ // Tier 1 — Critical
43
+ "name",
44
+ "version",
45
+ "description",
46
+ "private",
47
+ "type",
48
+ "main",
49
+ "module",
50
+ "types",
51
+ "exports",
52
+ "bin",
53
+ "scripts",
54
+ "dependencies",
55
+ // Tier 2 — Dev
56
+ "devDependencies",
57
+ "peerDependencies",
58
+ "peerDependenciesMeta",
59
+ "optionalDependencies",
60
+ // Build
61
+ "workspaces",
62
+ "engines",
63
+ "os",
64
+ "cpu",
65
+ "files",
66
+ "sideEffects",
67
+ "publishConfig",
68
+ // Metadata (publish-enriched)
69
+ "keywords",
70
+ "author",
71
+ "contributors",
72
+ "license",
73
+ "repository",
74
+ "bugs",
75
+ "homepage",
76
+ "funding",
77
+ // Overrides (root only)
78
+ "resolutions",
79
+ "overrides",
80
+ "pnpm",
81
+ ];
82
+
83
+ // ── Helpers ──────────────────────────────────────────────
84
+
85
+ function readJSON(path) {
86
+ return JSON.parse(readFileSync(path, "utf-8"));
87
+ }
88
+
89
+ function writeJSON(path, data) {
90
+ writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
91
+ }
92
+
93
+ function sortFields(obj) {
94
+ const sorted = {};
95
+ // Add fields in order
96
+ for (const key of FIELD_ORDER) {
97
+ if (key in obj) sorted[key] = obj[key];
98
+ }
99
+ // Add any remaining fields not in the order list
100
+ for (const key of Object.keys(obj)) {
101
+ if (!(key in sorted)) sorted[key] = obj[key];
102
+ }
103
+ return sorted;
104
+ }
105
+
106
+ function findMonorepoRoot(startPath) {
107
+ let dir = resolve(startPath);
108
+ while (dir !== "/") {
109
+ if (existsSync(join(dir, PUBLISH_META_FILE))) return dir;
110
+ if (existsSync(join(dir, "turbo.json")) || existsSync(join(dir, "nx.json"))) return dir;
111
+ dir = resolve(dir, "..");
112
+ }
113
+ return resolve(startPath);
114
+ }
115
+
116
+ function findAllPackages(root) {
117
+ const packages = [];
118
+ for (const dir of ["packages", "apps"]) {
119
+ const dirPath = join(root, dir);
120
+ if (!existsSync(dirPath)) continue;
121
+ for (const pkg of readdirSync(dirPath)) {
122
+ const pkgJson = join(dirPath, pkg, "package.json");
123
+ if (existsSync(pkgJson)) packages.push(join(dirPath, pkg));
124
+ }
125
+ }
126
+ // Also check root
127
+ if (existsSync(join(root, "package.json"))) {
128
+ packages.unshift(root);
129
+ }
130
+ return packages;
131
+ }
132
+
133
+ // ── Enrich ───────────────────────────────────────────────
134
+
135
+ function enrich(pkgPath, meta, dryRun) {
136
+ const pkgJsonPath = join(pkgPath, "package.json");
137
+ if (!existsSync(pkgJsonPath)) {
138
+ console.log(` ⚠️ No package.json in ${pkgPath}`);
139
+ return;
140
+ }
141
+
142
+ const pkg = readJSON(pkgJsonPath);
143
+ const pkgName = pkg.name || basename(pkgPath);
144
+
145
+ // Skip private packages (they don't need publish metadata)
146
+ if (pkg.private) {
147
+ console.log(` ⏭️ ${pkgName} — private, skipping`);
148
+ return;
149
+ }
150
+
151
+ let changed = false;
152
+ const changes = [];
153
+
154
+ // Apply shared metadata (don't overwrite if already set)
155
+ for (const [key, value] of Object.entries(meta.shared || {})) {
156
+ if (!(key in pkg)) {
157
+ pkg[key] = value;
158
+ changes.push(`+${key}`);
159
+ changed = true;
160
+ }
161
+ }
162
+
163
+ // Apply per-package overrides
164
+ const overrides = (meta.packages || {})[pkgName] || {};
165
+ for (const [key, value] of Object.entries(overrides)) {
166
+ pkg[key] = value;
167
+ changes.push(`~${key}`);
168
+ changed = true;
169
+ }
170
+
171
+ // Auto-generate repository URL if pattern provided
172
+ if (meta.repositoryPattern && !pkg.repository) {
173
+ pkg.repository = {
174
+ type: "git",
175
+ url: meta.repositoryPattern,
176
+ directory: pkgPath.replace(findMonorepoRoot(pkgPath) + "/", ""),
177
+ };
178
+ changes.push("+repository");
179
+ changed = true;
180
+ }
181
+
182
+ // Auto-generate homepage from repository
183
+ if (pkg.repository && !pkg.homepage && meta.homepagePattern) {
184
+ pkg.homepage = meta.homepagePattern.replace("{{package}}", basename(pkgPath));
185
+ changes.push("+homepage");
186
+ changed = true;
187
+ }
188
+
189
+ // Auto-generate bugs URL
190
+ if (meta.bugsUrl && !pkg.bugs) {
191
+ pkg.bugs = { url: meta.bugsUrl };
192
+ changes.push("+bugs");
193
+ changed = true;
194
+ }
195
+
196
+ // Add default files array
197
+ if (!pkg.files && meta.defaultFiles) {
198
+ pkg.files = meta.defaultFiles;
199
+ changes.push("+files");
200
+ changed = true;
201
+ }
202
+
203
+ if (!changed) {
204
+ console.log(` ✅ ${pkgName} — already enriched`);
205
+ return;
206
+ }
207
+
208
+ // Sort fields for clean output
209
+ const sorted = sortFields(pkg);
210
+
211
+ if (dryRun) {
212
+ console.log(` 🔍 ${pkgName} — would add: ${changes.join(", ")}`);
213
+ } else {
214
+ writeJSON(pkgJsonPath, sorted);
215
+ console.log(` ✅ ${pkgName} — enriched: ${changes.join(", ")}`);
216
+ }
217
+ }
218
+
219
+ // ── Restore (strip publish metadata) ─────────────────────
220
+
221
+ function restore(pkgPath, dryRun) {
222
+ const pkgJsonPath = join(pkgPath, "package.json");
223
+ if (!existsSync(pkgJsonPath)) return;
224
+
225
+ const pkg = readJSON(pkgJsonPath);
226
+ const pkgName = pkg.name || basename(pkgPath);
227
+ const removed = [];
228
+
229
+ for (const field of ENRICHED_FIELDS) {
230
+ if (field in pkg) {
231
+ delete pkg[field];
232
+ removed.push(`-${field}`);
233
+ }
234
+ }
235
+
236
+ if (removed.length === 0) {
237
+ console.log(` ✅ ${pkgName} — already lean`);
238
+ return;
239
+ }
240
+
241
+ const sorted = sortFields(pkg);
242
+
243
+ if (dryRun) {
244
+ console.log(` 🔍 ${pkgName} — would remove: ${removed.join(", ")}`);
245
+ } else {
246
+ writeJSON(pkgJsonPath, sorted);
247
+ console.log(` ✅ ${pkgName} — restored to lean: ${removed.join(", ")}`);
248
+ }
249
+ }
250
+
251
+ // ── Main ─────────────────────────────────────────────────
252
+
253
+ const args = process.argv.slice(2);
254
+
255
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
256
+ console.log(`
257
+ 📦 enrich-package-json — Add publish metadata to lean package.json files
258
+
259
+ Usage:
260
+ node tools/enrich-package-json.js ./packages/core Enrich one package
261
+ node tools/enrich-package-json.js . --all Enrich all packages
262
+ node tools/enrich-package-json.js . --all --dry-run Preview changes
263
+ node tools/enrich-package-json.js . --all --restore Strip back to lean
264
+
265
+ Config: Create ${PUBLISH_META_FILE} in project root (see template).
266
+
267
+ Workflow:
268
+ 1. Keep lean package.json during development (AI-optimized)
269
+ 2. Before publish: node tools/enrich-package-json.js . --all
270
+ 3. npm publish (or turbo publish)
271
+ 4. After publish: node tools/enrich-package-json.js . --all --restore
272
+ `);
273
+ process.exit(0);
274
+ }
275
+
276
+ const dryRun = args.includes("--dry-run");
277
+ const restoreMode = args.includes("--restore");
278
+ const allMode = args.includes("--all");
279
+ const targetPath = resolve(args.find((a) => !a.startsWith("--")) || ".");
280
+
281
+ // Find monorepo root and load meta
282
+ const root = findMonorepoRoot(targetPath);
283
+ const metaPath = join(root, PUBLISH_META_FILE);
284
+
285
+ let meta = {};
286
+ if (existsSync(metaPath)) {
287
+ meta = readJSON(metaPath);
288
+ console.log(`📋 Loaded ${PUBLISH_META_FILE} from ${root}`);
289
+ } else if (!restoreMode) {
290
+ console.log(`⚠️ No ${PUBLISH_META_FILE} found. Create one in project root.`);
291
+ console.log(` See templates/publish-meta.template for format.`);
292
+ process.exit(1);
293
+ }
294
+
295
+ console.log(
296
+ `Mode: ${restoreMode ? "♻️ RESTORE (lean)" : "📦 ENRICH (publish)"} ${dryRun ? "[DRY RUN]" : ""}`,
297
+ );
298
+ console.log("");
299
+
300
+ const packages = allMode ? findAllPackages(root) : [targetPath];
301
+
302
+ for (const pkg of packages) {
303
+ if (restoreMode) {
304
+ restore(pkg, dryRun);
305
+ } else {
306
+ enrich(pkg, meta, dryRun);
307
+ }
308
+ }
309
+
310
+ console.log("");
311
+ console.log(dryRun ? "🔍 Dry run complete. Run without --dry-run to apply." : "✅ Done!");
@@ -0,0 +1,109 @@
1
+ #!/bin/bash
2
+ # Tool: generate-jsdoc-headers — Add JSDoc navigation headers to source files | DEPS: none
3
+ # USAGE: bash tools/generate-jsdoc-headers.sh /path/to/directory [--dry-run]
4
+ # OUTPUT: updated source files with JSDoc headers
5
+ # MODE: write (modifies source files)
6
+
7
+ set -euo pipefail
8
+
9
+ source "$(dirname "$0")/shared/config.sh"
10
+
11
+ TARGET="${1:-.}"
12
+ DRY_RUN=false
13
+ [[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
14
+
15
+ if [[ "$1" == "--help" || "$1" == "-h" ]]; then
16
+ echo "Usage: bash tools/generate-jsdoc-headers.sh /path/to/directory [--dry-run]"
17
+ echo ""
18
+ echo "Adds JSDoc navigation headers to TypeScript/JavaScript files in directories"
19
+ echo "with 20+ files. These headers help AI agents navigate without reading full files."
20
+ echo ""
21
+ echo "Options:"
22
+ echo " --dry-run Show what would be done without modifying files"
23
+ echo ""
24
+ echo "Header format:"
25
+ echo " /** <Type>: <Name> — <description from filename> */"
26
+ echo ""
27
+ echo "Examples:"
28
+ echo " /** Service: UserService — User entity business logic */"
29
+ echo " /** Route: GET/POST /api/v1/users — User CRUD operations */"
30
+ echo " /** Middleware: AuthGuard — JWT token verification */"
31
+ exit 0
32
+ fi
33
+
34
+ if [[ ! -d "$TARGET" ]]; then
35
+ echo "❌ Error: '$TARGET' is not a directory"
36
+ exit 1
37
+ fi
38
+
39
+ echo "🔍 Scanning for directories with 20+ source files..."
40
+ echo "────────────────────────────────────────"
41
+
42
+ MODIFIED=0
43
+
44
+ find "$TARGET" -type d \
45
+ -not -path '*/node_modules/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
46
+ -not -path '*/.git/*' \
47
+ -not -path '*/dist/*' \
48
+ | while read -r dir; do
49
+
50
+ file_count=$(find "$dir" -maxdepth 1 -type f \( -name '*.ts' -o -name '*.js' -o -name '*.sol' \) \
51
+ -not -name '*.test.*' -not -name '*.spec.*' -not -name '*.d.ts' \
52
+ 2>/dev/null | wc -l | tr -d ' ')
53
+
54
+ if [[ $file_count -ge 20 ]]; then
55
+ echo ""
56
+ echo "📁 $dir ($file_count files)"
57
+
58
+ find "$dir" -maxdepth 1 -type f \( -name '*.ts' -o -name '*.js' -o -name '*.sol' \) \
59
+ -not -name '*.test.*' -not -name '*.spec.*' -not -name '*.d.ts' \
60
+ | while read -r file; do
61
+
62
+ # Check if file already has JSDoc header
63
+ first_line=$(head -1 "$file" 2>/dev/null || echo "")
64
+ if echo "$first_line" | grep -q '^\s*/\*\*'; then
65
+ continue # Already has JSDoc
66
+ fi
67
+
68
+ basename=$(basename "$file" | sed 's/\.\(ts\|js\|tsx\|jsx\)$//')
69
+
70
+ # Detect type from filename pattern
71
+ type="Module"
72
+ if echo "$basename" | grep -qi 'service'; then type="Service"
73
+ elif echo "$basename" | grep -qi 'route\|router\|handler'; then type="Route"
74
+ elif echo "$basename" | grep -qi 'middleware\|guard\|interceptor'; then type="Middleware"
75
+ elif echo "$basename" | grep -qi 'controller'; then type="Controller"
76
+ elif echo "$basename" | grep -qi 'model\|entity\|schema'; then type="Entity"
77
+ elif echo "$basename" | grep -qi 'util\|helper'; then type="Utility"
78
+ elif echo "$basename" | grep -qi 'config'; then type="Config"
79
+ elif echo "$basename" | grep -qi 'test\|spec'; then type="Test"
80
+ elif echo "$basename" | grep -qi 'type\|interface\|dto'; then type="Types"
81
+ elif echo "$basename" | grep -qi 'constant\|enum'; then type="Constants"
82
+ fi
83
+
84
+ # Generate description from filename
85
+ desc=$(echo "$basename" | sed 's/[-_.]/ /g' | sed 's/\b\(.\)/\u\1/g')
86
+ header="/** ${type}: ${desc} — TODO: add description */"
87
+
88
+ if $DRY_RUN; then
89
+ echo " [DRY RUN] Would add to $(basename "$file"): ${header}"
90
+ else
91
+ # Prepend header
92
+ temp=$(mktemp)
93
+ echo "$header" > "$temp"
94
+ cat "$file" >> "$temp"
95
+ mv "$temp" "$file"
96
+ echo " ✅ Added header to $(basename "$file")"
97
+ MODIFIED=$((MODIFIED + 1))
98
+ fi
99
+ done
100
+ fi
101
+ done
102
+
103
+ echo ""
104
+ echo "────────────────────────────────────────"
105
+ if $DRY_RUN; then
106
+ echo "🔍 Dry run complete. Use without --dry-run to apply changes."
107
+ else
108
+ echo "✅ Complete. Review the TODO descriptions and replace with actual descriptions."
109
+ fi
@@ -0,0 +1,71 @@
1
+ #!/bin/bash
2
+ # Tool: generate-source-map — Generate Source Structure block for a package README | DEPS: none
3
+ # USAGE: bash tools/generate-source-map.sh /path/to/package/src
4
+ # OUTPUT: markdown tree of source files
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/generate-source-map.sh /path/to/package/src"
15
+ echo ""
16
+ echo "Generates a Source Structure tree for a package, suitable for README.md."
17
+ echo "Annotates files with JSDoc descriptions if available."
18
+ echo ""
19
+ echo "Output can be copied into a README.md Source Structure section."
20
+ exit 0
21
+ fi
22
+
23
+ if [[ ! -d "$TARGET" ]]; then
24
+ echo "❌ Error: '$TARGET' is not a directory"
25
+ exit 1
26
+ fi
27
+
28
+ echo "## Source Structure"
29
+ echo "\`\`\`"
30
+
31
+ # Generate tree with annotations
32
+ find "$TARGET" -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.sol' \) \
33
+ -not -path '*/node_modules/*' -not -path '*/lib/forge-std/*' -not -path '*/lib/openzeppelin-contracts*' -not -path '*/lib/erc721a*' \
34
+ -not -path '*/dist/*' \
35
+ -not -path '*/*.test.*' \
36
+ -not -path '*/*.spec.*' \
37
+ -not -path '*/__tests__/*' \
38
+ -not -name '*.d.ts' \
39
+ | sort \
40
+ | while read -r filepath; do
41
+ # Get relative path
42
+ relpath="${filepath#$TARGET/}"
43
+
44
+ # Try to extract JSDoc description from first line
45
+ desc=""
46
+ first_line=$(head -1 "$filepath" 2>/dev/null || echo "")
47
+ if echo "$first_line" | grep -q '^\s*/\*\*'; then
48
+ # Extract text between /** and */
49
+ desc=$(echo "$first_line" | sed 's/.*\/\*\*\s*//' | sed 's/\s*\*\/.*//' | head -c 60)
50
+ fi
51
+
52
+ # Calculate indentation based on depth
53
+ depth=$(echo "$relpath" | tr -cd '/' | wc -c | tr -d ' ')
54
+ indent=""
55
+ for ((i=0; i<depth; i++)); do
56
+ indent=" $indent"
57
+ done
58
+
59
+ basename=$(basename "$relpath")
60
+
61
+ if [[ -n "$desc" ]]; then
62
+ echo "${indent}${basename}$(printf '%*s' $((40 - ${#indent} - ${#basename})) '')← ${desc}"
63
+ else
64
+ echo "${indent}${basename}"
65
+ fi
66
+ done
67
+
68
+ echo "\`\`\`"
69
+ echo ""
70
+ echo "# Paste the above into your package README.md"
71
+ echo "# Then manually add ← descriptions for files without JSDoc"
@@ -0,0 +1,123 @@
1
+ #!/bin/bash
2
+ # Tool: lint-imports — detects cross-package boundary violations in monorepo | DEPS: none
3
+ # USAGE: bash tools/lint-imports.sh /path/to/repo
4
+ # OUTPUT: list of import violations (importing internal paths from other packages)
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
+ CHECKED=0
14
+
15
+ echo "🔍 Import Boundary Lint"
16
+ echo " Target: $TARGET"
17
+ echo " ──────────────────────────────────"
18
+
19
+ # Find all packages directories
20
+ PACKAGES_DIR=""
21
+ if [ -d "$TARGET/packages" ]; then
22
+ PACKAGES_DIR="$TARGET/packages"
23
+ elif [ -d "$TARGET/libs" ]; then
24
+ PACKAGES_DIR="$TARGET/libs"
25
+ elif [ -d "$TARGET/modules" ]; then
26
+ PACKAGES_DIR="$TARGET/modules"
27
+ fi
28
+
29
+ if [ -z "$PACKAGES_DIR" ]; then
30
+ echo " ⚠️ No packages/, libs/, or modules/ directory found"
31
+ exit 0
32
+ fi
33
+
34
+ echo " Packages dir: $PACKAGES_DIR"
35
+ echo ""
36
+
37
+ # Pattern 1: Direct path imports crossing package boundaries
38
+ # e.g., import { User } from '../../core/src/models/user'
39
+ echo " ### Pattern 1: Relative cross-package imports"
40
+ echo ""
41
+ CROSS_PKG=$(grep -rnI "from ['\"]\.\..*packages/\|from ['\"]\.\..*libs/\|from ['\"]\.\..*modules/" \
42
+ "$PACKAGES_DIR" \
43
+ --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' --include='*.sol' \
44
+ 2>/dev/null || true)
45
+
46
+ if [ -n "$CROSS_PKG" ]; then
47
+ echo "$CROSS_PKG" | while IFS= read -r line; do
48
+ echo " ❌ $line"
49
+ VIOLATIONS=$((VIOLATIONS + 1))
50
+ done
51
+ echo ""
52
+ else
53
+ echo " ✅ No relative cross-package imports found"
54
+ echo ""
55
+ fi
56
+
57
+ # Pattern 2: Importing from /src/ of another package
58
+ # e.g., import { User } from '@scope/core/src/models/user'
59
+ echo " ### Pattern 2: Deep imports into package internals"
60
+ echo ""
61
+ DEEP_IMPORTS=$(grep -rnI "from ['\"]@[^'\"]*\/src\/" \
62
+ "$PACKAGES_DIR" \
63
+ --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' --include='*.sol' \
64
+ 2>/dev/null || true)
65
+
66
+ if [ -n "$DEEP_IMPORTS" ]; then
67
+ echo "$DEEP_IMPORTS" | while IFS= read -r line; do
68
+ echo " ❌ $line"
69
+ done
70
+ echo ""
71
+ else
72
+ echo " ✅ No deep imports into package internals found"
73
+ echo ""
74
+ fi
75
+
76
+ # Pattern 3: Importing from internal/ or _internal/ of another package
77
+ echo " ### Pattern 3: Internal module access violations"
78
+ echo ""
79
+ INTERNAL_IMPORTS=$(grep -rnI "from ['\"].*internal\/" \
80
+ "$PACKAGES_DIR" \
81
+ --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' --include='*.sol' \
82
+ 2>/dev/null | grep -vE "(node_modules|lib/forge-std|lib/openzeppelin|lib/erc721a)" || true)
83
+
84
+ if [ -n "$INTERNAL_IMPORTS" ]; then
85
+ echo "$INTERNAL_IMPORTS" | while IFS= read -r line; do
86
+ echo " ⚠️ $line"
87
+ done
88
+ echo ""
89
+ else
90
+ echo " ✅ No internal module access violations found"
91
+ echo ""
92
+ fi
93
+
94
+ # Pattern 4: Check each package has index.ts (public API boundary)
95
+ echo " ### Pattern 4: Missing public API boundary (index.ts)"
96
+ echo ""
97
+ for pkg in "$PACKAGES_DIR"/*/; do
98
+ [ -d "$pkg" ] || continue
99
+ pkg_name=$(basename "$pkg")
100
+
101
+ if [ -f "$pkg/src/index.ts" ] || [ -f "$pkg/src/index.tsx" ] || [ -f "$pkg/index.ts" ]; then
102
+ : # has boundary
103
+ else
104
+ echo " ❌ $pkg_name — no src/index.ts (no public API boundary)"
105
+ fi
106
+ done
107
+
108
+ echo ""
109
+ echo " ──────────────────────────────────"
110
+
111
+ # Count all violations
112
+ TOTAL=$(echo "$CROSS_PKG$DEEP_IMPORTS$INTERNAL_IMPORTS" | grep -c "." || true)
113
+ echo " Total violations: $TOTAL"
114
+
115
+ if [ "$TOTAL" -gt 0 ]; then
116
+ echo ""
117
+ echo " 💡 Fix: Replace internal imports with public API imports through index.ts"
118
+ echo " Example: import { User } from '@scope/core' (not from '@scope/core/src/...')"
119
+ exit 1
120
+ else
121
+ echo " ✅ All imports respect package boundaries"
122
+ exit 0
123
+ fi