@netanelyasi/agent-ready 0.2.0 → 0.2.2
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/README.md +23 -7
- package/dist/cli.js +33 -3
- package/dist/generators/generate.js +70 -27
- package/dist/scanner/scanProject.js +34 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<img alt="Status" src="https://img.shields.io/badge/status-experimental-f59e0b?style=flat-square" />
|
|
17
|
-
<img alt="Version" src="https://img.shields.io/badge/version-0.2.
|
|
17
|
+
<img alt="Version" src="https://img.shields.io/badge/version-0.2.2-111827?style=flat-square" />
|
|
18
18
|
<img alt="License" src="https://img.shields.io/badge/license-MIT-0f766e?style=flat-square" />
|
|
19
19
|
<img alt="Runtime" src="https://img.shields.io/badge/runtime-Node.js-3c873a?style=flat-square" />
|
|
20
20
|
<img alt="Built by BrainboxAI" src="https://img.shields.io/badge/by-BrainboxAI-111827?style=flat-square" />
|
|
@@ -76,7 +76,7 @@ CODEMAP.md # repository map for navigation
|
|
|
76
76
|
.agent-ready/report.md # readiness score and findings
|
|
77
77
|
.agent-ready/recommendations.md
|
|
78
78
|
.agent-ready/hooks/README.md
|
|
79
|
-
.
|
|
79
|
+
.claude/skills/*/SKILL.md # Claude Code-loadable skills
|
|
80
80
|
apps/*/CLAUDE.md # generated for detected monorepo workspaces
|
|
81
81
|
```
|
|
82
82
|
|
|
@@ -122,6 +122,14 @@ Preview generated files:
|
|
|
122
122
|
agent-ready init /path/to/project --dry-run
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
Preview generated file contents:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
agent-ready init /path/to/project --dry-run --verbose
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
> Run `agent-ready init` manually from your terminal, not from inside an autonomous coding agent. It writes agent harness files such as `CLAUDE.md`, `.claude/settings.json`, and `.claude/hooks/*`, which some agent security classifiers correctly treat as self-modification.
|
|
132
|
+
|
|
125
133
|
Generate the harness:
|
|
126
134
|
|
|
127
135
|
```bash
|
|
@@ -194,7 +202,7 @@ Generated hooks include:
|
|
|
194
202
|
- `PreToolUse` for `Write|Edit|MultiEdit` — blocks edits to generated/noisy paths such as `node_modules`, `dist`, `build`, `coverage`, `.next`, `vendor`, and `*.generated.*`.
|
|
195
203
|
- `PostToolUse` for `Write|Edit|MultiEdit` — reminds the agent which local validation command to run after edits.
|
|
196
204
|
|
|
197
|
-
### `.
|
|
205
|
+
### `.claude/skills/*/SKILL.md`
|
|
198
206
|
|
|
199
207
|
On-demand task expertise. Examples:
|
|
200
208
|
|
|
@@ -327,10 +335,10 @@ Generating 14 files:
|
|
|
327
335
|
- created: .agent-ready/report.md
|
|
328
336
|
- created: .agent-ready/recommendations.md
|
|
329
337
|
- created: .agent-ready/hooks/README.md
|
|
330
|
-
- created: .
|
|
331
|
-
- created: .
|
|
332
|
-
- created: .
|
|
333
|
-
- created: .
|
|
338
|
+
- created: .claude/skills/codebase-navigation/SKILL.md
|
|
339
|
+
- created: .claude/skills/validation/SKILL.md
|
|
340
|
+
- created: .claude/skills/nextjs-hydration/SKILL.md
|
|
341
|
+
- created: .claude/skills/supabase-debugging/SKILL.md
|
|
334
342
|
- created: apps/web/CLAUDE.md
|
|
335
343
|
- created: packages/db/CLAUDE.md
|
|
336
344
|
```
|
|
@@ -355,12 +363,20 @@ agent-ready init [path] --dry-run
|
|
|
355
363
|
|
|
356
364
|
Show which files would be generated without writing anything.
|
|
357
365
|
|
|
366
|
+
```bash
|
|
367
|
+
agent-ready init [path] --dry-run --verbose
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Show the generated contents too. Use this before deciding whether to run `init` for real or manually merge proposed files.
|
|
371
|
+
|
|
358
372
|
```bash
|
|
359
373
|
agent-ready init [path] --force
|
|
360
374
|
```
|
|
361
375
|
|
|
362
376
|
Overwrite existing files instead of writing `*.agent-ready-proposed`.
|
|
363
377
|
|
|
378
|
+
When existing harness files are present, default `init` writes `*.agent-ready-proposed` files. Review and manually merge them; `agent-ready` does not assume its generated `CLAUDE.md` is better than a maintainer-authored one.
|
|
379
|
+
|
|
364
380
|
## Development
|
|
365
381
|
|
|
366
382
|
Requirements:
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { scoreReadiness } from "./analyzers/scoreReadiness.js";
|
|
4
4
|
import { generateFiles } from "./generators/generate.js";
|
|
5
5
|
import { scanProject } from "./scanner/scanProject.js";
|
|
6
|
-
import { safeWriteFile } from "./utils/fs.js";
|
|
6
|
+
import { pathExists, safeWriteFile } from "./utils/fs.js";
|
|
7
7
|
async function main() {
|
|
8
8
|
const args = parseArgs(process.argv.slice(2));
|
|
9
9
|
if (args.command === "help") {
|
|
@@ -18,17 +18,47 @@ async function main() {
|
|
|
18
18
|
return;
|
|
19
19
|
const files = generateFiles(scan, score, args.force);
|
|
20
20
|
console.log(`\nGenerating ${files.length} files${args.dryRun ? " (dry-run)" : ""}:`);
|
|
21
|
+
let proposedCount = 0;
|
|
21
22
|
for (const file of files) {
|
|
22
23
|
const relative = path.relative(root, file.path).replaceAll(path.sep, "/");
|
|
23
24
|
if (args.dryRun) {
|
|
24
|
-
|
|
25
|
+
const result = await plannedWriteResult(file.path, args.force);
|
|
26
|
+
if (result === "proposed")
|
|
27
|
+
proposedCount += 1;
|
|
28
|
+
const target = result === "proposed" ? `${relative}.agent-ready-proposed` : relative;
|
|
29
|
+
console.log(`- would ${writeVerb(result)}: ${target}`);
|
|
30
|
+
if (args.verbose)
|
|
31
|
+
printDryRunContent(target, file.content);
|
|
25
32
|
continue;
|
|
26
33
|
}
|
|
27
34
|
const result = await safeWriteFile(file.path, file.content, args.force);
|
|
35
|
+
if (result === "proposed")
|
|
36
|
+
proposedCount += 1;
|
|
28
37
|
const target = result === "proposed" ? `${relative}.agent-ready-proposed` : relative;
|
|
29
38
|
console.log(`- ${result}: ${target}`);
|
|
30
39
|
}
|
|
31
40
|
console.log("\nDone. Start with CODEMAP.md and .agent-ready/report.md.");
|
|
41
|
+
if (proposedCount > 0)
|
|
42
|
+
console.log(`Review and manually merge ${proposedCount} *.agent-ready-proposed file(s); existing harness files were not overwritten.`);
|
|
43
|
+
}
|
|
44
|
+
async function plannedWriteResult(filePath, force) {
|
|
45
|
+
const exists = await pathExists(filePath);
|
|
46
|
+
if (force)
|
|
47
|
+
return exists ? "overwritten" : "created";
|
|
48
|
+
return exists ? "proposed" : "created";
|
|
49
|
+
}
|
|
50
|
+
function writeVerb(result) {
|
|
51
|
+
if (result === "created")
|
|
52
|
+
return "create";
|
|
53
|
+
if (result === "overwritten")
|
|
54
|
+
return "overwrite";
|
|
55
|
+
return "propose";
|
|
56
|
+
}
|
|
57
|
+
function printDryRunContent(target, content) {
|
|
58
|
+
console.log(` --- ${target} begin ---`);
|
|
59
|
+
for (const line of content.trimEnd().split("\n"))
|
|
60
|
+
console.log(` ${line}`);
|
|
61
|
+
console.log(` --- ${target} end ---`);
|
|
32
62
|
}
|
|
33
63
|
function parseArgs(argv) {
|
|
34
64
|
const command = argv[0] === "init" || argv[0] === "analyze" ? argv[0] : argv[0] ? "help" : "help";
|
|
@@ -65,7 +95,7 @@ function printSummary(scan, score) {
|
|
|
65
95
|
}
|
|
66
96
|
}
|
|
67
97
|
function printHelp() {
|
|
68
|
-
console.log(`agent-ready\n\nUsage:\n agent-ready analyze [path]\n agent-ready init [path] [--dry-run] [--force]\n\nCommands:\n analyze Scan project and print readiness summary\n init Generate CLAUDE.md, CODEMAP.md, .aiignore, settings, skills, and reports\n\nOptions:\n --dry-run Show files that would be written\n --force Overwrite existing files instead of writing *.agent-ready-proposed\n`);
|
|
98
|
+
console.log(`agent-ready\n\nUsage:\n agent-ready analyze [path]\n agent-ready init [path] [--dry-run] [--verbose] [--force]\n\nCommands:\n analyze Scan project and print readiness summary\n init Generate CLAUDE.md, CODEMAP.md, .aiignore, settings, skills, and reports\n\nOptions:\n --dry-run Show files that would be written\n --verbose With --dry-run, print generated file contents\n --force Overwrite existing files instead of writing *.agent-ready-proposed\n`);
|
|
69
99
|
}
|
|
70
100
|
main().catch((error) => {
|
|
71
101
|
console.error(error instanceof Error ? error.message : String(error));
|
|
@@ -4,7 +4,7 @@ export function generateFiles(scan, score, force) {
|
|
|
4
4
|
add("CLAUDE.md", generateClaudeMd(scan));
|
|
5
5
|
add("CODEMAP.md", generateCodemap(scan));
|
|
6
6
|
add(".aiignore", generateAiIgnore(scan));
|
|
7
|
-
add(".claude/settings.json", generateClaudeSettings(
|
|
7
|
+
add(".claude/settings.json", generateClaudeSettings());
|
|
8
8
|
add(".claude/hooks/prevent-destructive.mjs", generatePreventDestructiveHook());
|
|
9
9
|
add(".claude/hooks/protect-generated.mjs", generateProtectGeneratedHook());
|
|
10
10
|
add(".claude/hooks/suggest-validation.mjs", generateSuggestValidationHook(scan));
|
|
@@ -15,7 +15,7 @@ export function generateFiles(scan, score, force) {
|
|
|
15
15
|
add(`${subdir.dir}/CLAUDE.md`, subdir.content);
|
|
16
16
|
}
|
|
17
17
|
for (const skill of generateSkills(scan)) {
|
|
18
|
-
add(`.
|
|
18
|
+
add(`.claude/skills/${skill.slug}/SKILL.md`, skill.content);
|
|
19
19
|
}
|
|
20
20
|
return files;
|
|
21
21
|
function add(relativePath, content) {
|
|
@@ -114,7 +114,7 @@ function generateClaudeMd(scan) {
|
|
|
114
114
|
if (scan.frameworks.includes("Next.js")) {
|
|
115
115
|
lines.push("- Next.js detected: avoid hydration mismatches; access browser-only APIs only in client-safe code.");
|
|
116
116
|
}
|
|
117
|
-
lines.push("", "## Generated Skills", "See `.
|
|
117
|
+
lines.push("", "## Generated Skills", "See `.claude/skills/*/SKILL.md` for task-specific instructions that Claude Code can load on demand. Keep skills focused and avoid copying them wholesale into this file.", "");
|
|
118
118
|
return lines.join("\n");
|
|
119
119
|
}
|
|
120
120
|
function commandLines(scan) {
|
|
@@ -132,6 +132,20 @@ function commandLines(scan) {
|
|
|
132
132
|
lines.push("- No standard validation commands were detected. Add project-specific commands here.");
|
|
133
133
|
return lines;
|
|
134
134
|
}
|
|
135
|
+
// Flat one-command-per-rule list for the validation skill, e.g. "test: `npm run test`".
|
|
136
|
+
// Unlike commandLines (which nests for CLAUDE.md), this stays flat so it renders
|
|
137
|
+
// correctly as a SKILL.md bullet list.
|
|
138
|
+
function validationSkillRules(scan) {
|
|
139
|
+
const order = ["dev", "build", "test", "lint", "typecheck", "format"];
|
|
140
|
+
const rules = [];
|
|
141
|
+
for (const name of order) {
|
|
142
|
+
for (const command of (scan.commands[name] ?? []).slice(0, 8))
|
|
143
|
+
rules.push(`${name}: \`${command}\``);
|
|
144
|
+
}
|
|
145
|
+
if (!rules.length)
|
|
146
|
+
rules.push("No standard validation commands were detected. Add project-specific commands here.");
|
|
147
|
+
return rules;
|
|
148
|
+
}
|
|
135
149
|
function generateCodemap(scan) {
|
|
136
150
|
return [
|
|
137
151
|
`# ${scan.name} — CODEMAP`,
|
|
@@ -196,33 +210,40 @@ function externalImportLines(scan) {
|
|
|
196
210
|
function generateAiIgnore(scan) {
|
|
197
211
|
const base = [
|
|
198
212
|
"# Generated by agent-ready",
|
|
199
|
-
"node_modules/",
|
|
200
|
-
".next/",
|
|
201
|
-
".nuxt/",
|
|
202
|
-
"dist/",
|
|
203
|
-
"build/",
|
|
204
213
|
"coverage/",
|
|
205
|
-
".turbo/",
|
|
206
214
|
".cache/",
|
|
207
|
-
".venv/",
|
|
208
|
-
"venv/",
|
|
209
|
-
"__pycache__/",
|
|
210
|
-
"vendor/",
|
|
211
|
-
"target/",
|
|
212
|
-
"bin/",
|
|
213
|
-
"obj/",
|
|
214
215
|
"*.log",
|
|
215
216
|
"*.lock",
|
|
216
217
|
"generated/",
|
|
217
218
|
"**/*.generated.*",
|
|
218
219
|
];
|
|
220
|
+
const add = (...patterns) => {
|
|
221
|
+
for (const pattern of patterns)
|
|
222
|
+
if (!base.includes(pattern))
|
|
223
|
+
base.push(pattern);
|
|
224
|
+
};
|
|
225
|
+
if (scan.packageManager || scan.languages.some((language) => ["TypeScript", "JavaScript", "Svelte"].includes(language)))
|
|
226
|
+
add("node_modules/", "dist/", "build/", ".turbo/");
|
|
227
|
+
if (scan.frameworks.includes("Next.js"))
|
|
228
|
+
add(".next/");
|
|
229
|
+
if (scan.frameworks.includes("Nuxt"))
|
|
230
|
+
add(".nuxt/");
|
|
231
|
+
if (scan.languages.includes("Python"))
|
|
232
|
+
add(".venv/", "venv/", "__pycache__/");
|
|
233
|
+
if (scan.languages.includes("Rust"))
|
|
234
|
+
add("target/");
|
|
235
|
+
if (scan.languages.includes("Go"))
|
|
236
|
+
add("bin/");
|
|
237
|
+
if (scan.languages.includes("C#"))
|
|
238
|
+
add("bin/", "obj/");
|
|
239
|
+
if (scan.languages.includes("PHP") || scan.frameworks.some((framework) => framework.includes("Composer")))
|
|
240
|
+
add("vendor/");
|
|
219
241
|
for (const noisy of scan.noisyPaths)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return `${[...new Set(base)].join("\n")}\n`;
|
|
242
|
+
add(`${noisy}/`);
|
|
243
|
+
return `${base.join("\n")}\n`;
|
|
223
244
|
}
|
|
224
|
-
function generateClaudeSettings(
|
|
225
|
-
const
|
|
245
|
+
function generateClaudeSettings() {
|
|
246
|
+
const deniedPathPatterns = [
|
|
226
247
|
"node_modules/**",
|
|
227
248
|
".next/**",
|
|
228
249
|
"dist/**",
|
|
@@ -233,6 +254,8 @@ function generateClaudeSettings(scan) {
|
|
|
233
254
|
"generated/**",
|
|
234
255
|
"**/*.generated.*",
|
|
235
256
|
];
|
|
257
|
+
const deniedWriteTools = ["Write", "Edit", "MultiEdit"];
|
|
258
|
+
const deny = deniedWriteTools.flatMap((tool) => deniedPathPatterns.map((pattern) => `${tool}(${pattern})`));
|
|
236
259
|
const settings = {
|
|
237
260
|
permissions: { deny: [...new Set(deny)] },
|
|
238
261
|
hooks: {
|
|
@@ -245,7 +268,7 @@ function generateClaudeSettings(scan) {
|
|
|
245
268
|
command: "node",
|
|
246
269
|
args: ["${CLAUDE_PROJECT_DIR}/.claude/hooks/prevent-destructive.mjs"],
|
|
247
270
|
timeout: 10,
|
|
248
|
-
statusMessage: "Checking command safety",
|
|
271
|
+
statusMessage: "agent-ready: Checking command safety",
|
|
249
272
|
},
|
|
250
273
|
],
|
|
251
274
|
},
|
|
@@ -257,7 +280,7 @@ function generateClaudeSettings(scan) {
|
|
|
257
280
|
command: "node",
|
|
258
281
|
args: ["${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-generated.mjs"],
|
|
259
282
|
timeout: 10,
|
|
260
|
-
statusMessage: "Checking generated/noisy path safety",
|
|
283
|
+
statusMessage: "agent-ready: Checking generated/noisy path safety",
|
|
261
284
|
},
|
|
262
285
|
],
|
|
263
286
|
},
|
|
@@ -271,13 +294,12 @@ function generateClaudeSettings(scan) {
|
|
|
271
294
|
command: "node",
|
|
272
295
|
args: ["${CLAUDE_PROJECT_DIR}/.claude/hooks/suggest-validation.mjs"],
|
|
273
296
|
timeout: 10,
|
|
274
|
-
statusMessage: "Suggesting validation",
|
|
297
|
+
statusMessage: "agent-ready: Suggesting validation",
|
|
275
298
|
},
|
|
276
299
|
],
|
|
277
300
|
},
|
|
278
301
|
],
|
|
279
302
|
},
|
|
280
|
-
agentReady: { generatedBy: "agent-ready", project: scan.name },
|
|
281
303
|
};
|
|
282
304
|
return `${JSON.stringify(settings, null, 2)}\n`;
|
|
283
305
|
}
|
|
@@ -459,7 +481,7 @@ function hookRecommendations(scan) {
|
|
|
459
481
|
function generateSkills(scan) {
|
|
460
482
|
const skills = [
|
|
461
483
|
{ slug: "codebase-navigation", content: skill("codebase-navigation", "Use when starting work in this repository or when a task spans unfamiliar directories.", ["Read CODEMAP.md first.", "Use narrow searches from the relevant directory before global search.", "Prefer symbol/reference search when LSP is available.", "Do not inspect ignored/generated directories unless explicitly needed."]) },
|
|
462
|
-
{ slug: "validation", content: skill("validation", "Use after code edits or before declaring a task complete.", [...
|
|
484
|
+
{ slug: "validation", content: skill("validation", "Use after code edits or before declaring a task complete.", [...validationSkillRules(scan), "Run the narrowest relevant command first.", "If validation cannot be run, report the exact reason."]) },
|
|
463
485
|
];
|
|
464
486
|
if (scan.frameworks.includes("Next.js"))
|
|
465
487
|
skills.push({ slug: "nextjs-hydration", content: skill("nextjs-hydration", "Use when editing Next.js/React components, routes, or client/server boundaries.", ["Do not read localStorage/sessionStorage/window/document during server render or initial state.", "Use useEffect or guarded client-only code for browser APIs.", "Avoid Math.random() or new Date() in render paths that must hydrate identically.", "Keep server/client boundaries explicit."]) });
|
|
@@ -472,7 +494,28 @@ function generateSkills(scan) {
|
|
|
472
494
|
return skills;
|
|
473
495
|
}
|
|
474
496
|
function skill(name, description, rules) {
|
|
475
|
-
return [
|
|
497
|
+
return [
|
|
498
|
+
"---",
|
|
499
|
+
`name: ${name}`,
|
|
500
|
+
`description: ${yamlScalar(description)}`,
|
|
501
|
+
"---",
|
|
502
|
+
"",
|
|
503
|
+
`# ${name}`,
|
|
504
|
+
"",
|
|
505
|
+
description,
|
|
506
|
+
"",
|
|
507
|
+
"## Rules",
|
|
508
|
+
...rules.map((rule) => `- ${rule}`),
|
|
509
|
+
"",
|
|
510
|
+
].join("\n");
|
|
511
|
+
}
|
|
512
|
+
// Claude Code parses SKILL.md frontmatter as YAML. Quote scalars that contain
|
|
513
|
+
// characters YAML would otherwise treat as structure (`:`, leading `#`, etc.).
|
|
514
|
+
function yamlScalar(value) {
|
|
515
|
+
if (/^[^\s].*[:#]|^[#&*!|>%@`"']|:\s|\s#/.test(value)) {
|
|
516
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
517
|
+
}
|
|
518
|
+
return value;
|
|
476
519
|
}
|
|
477
520
|
function list(items) {
|
|
478
521
|
return items.length ? items.join(", ") : "none detected";
|
|
@@ -10,6 +10,7 @@ const LANGUAGE_EXTS = {
|
|
|
10
10
|
"C#": [".cs"],
|
|
11
11
|
Go: [".go"],
|
|
12
12
|
Rust: [".rs"],
|
|
13
|
+
Svelte: [".svelte"],
|
|
13
14
|
"C/C++": [".c", ".cc", ".cpp", ".h", ".hpp"],
|
|
14
15
|
};
|
|
15
16
|
export async function scanProject(rootInput) {
|
|
@@ -263,12 +264,12 @@ async function harnessFileState(root, relativePath) {
|
|
|
263
264
|
if (!exists)
|
|
264
265
|
return { exists: false, generatedByAgentReady: false, countsAsMaintainerAuthored: false };
|
|
265
266
|
const text = await readText(fullPath);
|
|
266
|
-
const generatedByAgentReady = Boolean(text && /generated by `?agent-ready`?|
|
|
267
|
+
const generatedByAgentReady = Boolean(text && /generated by `?agent-ready`?|agent-ready:|Generated by agent-ready/i.test(text));
|
|
267
268
|
return { exists: true, generatedByAgentReady, countsAsMaintainerAuthored: !generatedByAgentReady };
|
|
268
269
|
}
|
|
269
270
|
async function analyzeCodeGraph(root, files, packages) {
|
|
270
271
|
const sourceFiles = files
|
|
271
|
-
.filter((file) => /\.(tsx?|jsx?|mjs|cjs|py|go|rs)$/.test(file))
|
|
272
|
+
.filter((file) => /\.(tsx?|jsx?|mjs|cjs|py|go|rs|svelte)$/.test(file))
|
|
272
273
|
.filter((file) => !file.endsWith(".d.ts"))
|
|
273
274
|
.filter((file) => !file.includes(`${path.sep}dist${path.sep}`) && !file.includes(`${path.sep}node_modules${path.sep}`) && !file.includes(`${path.sep}target${path.sep}`))
|
|
274
275
|
.slice(0, 2000);
|
|
@@ -342,10 +343,6 @@ function detectEntryPoints(root, packages, sourceSet) {
|
|
|
342
343
|
["src/main.js", "application entry", "conventional app entry"],
|
|
343
344
|
["src/server.ts", "server entry", "conventional server entry"],
|
|
344
345
|
["src/server.js", "server entry", "conventional server entry"],
|
|
345
|
-
["app/page.tsx", "Next.js route", "App Router page"],
|
|
346
|
-
["app/layout.tsx", "Next.js layout", "App Router layout"],
|
|
347
|
-
["src/app/page.tsx", "Next.js route", "App Router page"],
|
|
348
|
-
["pages/index.tsx", "Next.js route", "Pages Router index"],
|
|
349
346
|
["main.py", "Python entry", "conventional Python entry"],
|
|
350
347
|
["app.py", "Python app", "conventional Python app entry"],
|
|
351
348
|
["src/main.py", "Python entry", "conventional Python entry"],
|
|
@@ -363,10 +360,9 @@ function detectEntryPoints(root, packages, sourceSet) {
|
|
|
363
360
|
if (!source.startsWith(packageRoot))
|
|
364
361
|
continue;
|
|
365
362
|
const local = source.slice(packageRoot.length);
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
entries.set(source, { path: source, kind: "Next.js route", reason: "App Router route/page" });
|
|
363
|
+
const frameworkEntry = frameworkEntryPoint(local);
|
|
364
|
+
if (frameworkEntry)
|
|
365
|
+
entries.set(source, { path: source, ...frameworkEntry });
|
|
370
366
|
if (/^cmd\/[^/]+\/main\.go$/.test(local))
|
|
371
367
|
entries.set(source, { path: source, kind: "Go command", reason: "cmd/*/main.go" });
|
|
372
368
|
if (/^src\/bin\/[^/]+\.rs$/.test(local))
|
|
@@ -380,6 +376,34 @@ function detectEntryPoints(root, packages, sourceSet) {
|
|
|
380
376
|
}
|
|
381
377
|
return [...entries.values()].sort((a, b) => a.path.localeCompare(b.path)).slice(0, 40);
|
|
382
378
|
}
|
|
379
|
+
function frameworkEntryPoint(localPath) {
|
|
380
|
+
if (/^(src\/)?app\/(page|layout|route)\.(tsx?|jsx?)$/.test(localPath)) {
|
|
381
|
+
const file = localPath.includes("/layout.") ? "layout" : localPath.includes("/route.") ? "route handler" : "page";
|
|
382
|
+
return { kind: `Next.js ${file}`, reason: "App Router root entry" };
|
|
383
|
+
}
|
|
384
|
+
if (/^(src\/)?app\/.+\/(page|layout|route|loading|error|not-found)\.(tsx?|jsx?)$/.test(localPath)) {
|
|
385
|
+
return { kind: "Next.js route", reason: "App Router route segment entry" };
|
|
386
|
+
}
|
|
387
|
+
if (/^(src\/)?pages\/index\.(tsx?|jsx?)$/.test(localPath))
|
|
388
|
+
return { kind: "Next.js route", reason: "Pages Router index" };
|
|
389
|
+
if (/^(src\/)?pages\/(api\/.+|.+)\.(tsx?|jsx?)$/.test(localPath))
|
|
390
|
+
return { kind: "Next.js route", reason: "Pages Router route/API entry" };
|
|
391
|
+
if (/^(src\/)?middleware\.(tsx?|jsx?)$/.test(localPath))
|
|
392
|
+
return { kind: "Next.js middleware", reason: "Next.js request middleware entry" };
|
|
393
|
+
if (/^next\.config\.(tsx?|jsx?|mjs|cjs)$/.test(localPath))
|
|
394
|
+
return { kind: "Next.js config", reason: "Next.js configuration entry" };
|
|
395
|
+
if (/^app\/(root|entry\.(client|server))\.(tsx?|jsx?)$/.test(localPath))
|
|
396
|
+
return { kind: "Remix entry", reason: "Remix root/client/server entry" };
|
|
397
|
+
if (/^app\/routes\/.+\.(tsx?|jsx?)$/.test(localPath))
|
|
398
|
+
return { kind: "Remix route", reason: "Remix route module" };
|
|
399
|
+
if (/^src\/routes\/(\+page|\+layout|\+server)\.(svelte|tsx?|jsx?)$/.test(localPath))
|
|
400
|
+
return { kind: "SvelteKit route", reason: "SvelteKit root route entry" };
|
|
401
|
+
if (/^src\/routes\/.+\/(\+page|\+layout|\+server)\.(svelte|tsx?|jsx?)$/.test(localPath))
|
|
402
|
+
return { kind: "SvelteKit route", reason: "SvelteKit route entry" };
|
|
403
|
+
if (/^src\/hooks(\.server)?\.(tsx?|jsx?)$/.test(localPath))
|
|
404
|
+
return { kind: "SvelteKit hook", reason: "SvelteKit lifecycle hook entry" };
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
383
407
|
function extractImportSpecifiers(text, from) {
|
|
384
408
|
const specifiers = new Set();
|
|
385
409
|
const ext = path.posix.extname(from);
|
package/package.json
CHANGED