@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,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
|