nuvanta-context-guard 0.1.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/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Nuvanta Context Guard
2
+
3
+ A developer tool that scans a software project, identifies files relevant to a task, and produces a context report for AI coding agents.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g nuvanta-context-guard
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ nuvanta scan <path> --task <task> [--budget <tokens>]
15
+ ```
16
+
17
+ ## Examples
18
+
19
+ Scan a project for files relevant to fixing a login bug:
20
+
21
+ ```bash
22
+ nuvanta scan ./my-project --task "fix login button"
23
+ ```
24
+
25
+ Scan with a token budget:
26
+
27
+ ```bash
28
+ nuvanta scan ./my-project --task "fix login button" --budget 5000
29
+ ```
30
+
31
+ ## Example Output
32
+
33
+ ```
34
+ Nuvanta Context Guard
35
+ ─────────────────────────────────────
36
+
37
+ Task: fix login button
38
+ Keywords: login, button
39
+ Budget: 5000 tokens
40
+
41
+ Files found: 24
42
+ Relevant: 5
43
+ Selected: 4
44
+ Context used: 3,842 / 5,000 tokens
45
+ Reduction: ~83%
46
+
47
+ Selected files:
48
+ src\auth\login.ts 820 tokens score 1.6
49
+ src\components\LoginButton.tsx 612 tokens score 1.0
50
+ src\api\auth.ts 445 tokens score 0.5
51
+ src\index.ts 392 tokens score 0.5
52
+ ```
53
+
54
+ ## How It Works
55
+
56
+ 1. **Scanner** — recursively discovers all files in the project
57
+ 2. **Relevance Engine** — scores each file based on filename, directory, and content
58
+ 3. **Budget Selector** — picks the most relevant files within the token limit
59
+ 4. **Output** — prints a clean report
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,11 @@
1
+ export function selectFiles(files, budget) {
2
+ const selected = [];
3
+ let remaining = budget;
4
+ for (const file of files) {
5
+ if (file.tokens <= remaining) {
6
+ selected.push(file);
7
+ remaining -= file.tokens;
8
+ }
9
+ }
10
+ return selected;
11
+ }
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { scanDirectory } from "./scanner/index.js";
4
+ import { extractKeywords, scoreFile, scoreContent } from "./relevance/index.js";
5
+ import { selectFiles } from "./budget/index.js";
6
+ import { printReport } from "./output/index.js";
7
+ import fs from "fs/promises";
8
+ const program = new Command();
9
+ program
10
+ .name("nuvanta")
11
+ .description("AI context management for developer projects")
12
+ .version("0.1.0");
13
+ program
14
+ .command("scan")
15
+ .description("Scan a project and find relevant files for a task")
16
+ .argument("<path>", "Path to the project directory")
17
+ .option("--task <task>", "The task you are working on")
18
+ .option("--budget <number>", "Max token budget", "10000")
19
+ .action(async (dirPath, options) => {
20
+ try {
21
+ const budget = parseInt(options.budget);
22
+ const files = await scanDirectory(dirPath);
23
+ const keywords = extractKeywords(options.task);
24
+ const scored = [];
25
+ for (const file of files) {
26
+ const content = await fs.readFile(file.path, "utf-8");
27
+ const score = scoreFile(file.path, keywords) + scoreContent(content, keywords);
28
+ if (score > 0) {
29
+ scored.push({ ...file, score });
30
+ }
31
+ }
32
+ scored.sort((a, b) => b.score - a.score);
33
+ const selected = selectFiles(scored, budget);
34
+ printReport({
35
+ task: options.task,
36
+ keywords,
37
+ budget,
38
+ totalFiles: files.length,
39
+ relevantFiles: scored.length,
40
+ selected,
41
+ });
42
+ }
43
+ catch (error) {
44
+ if (error instanceof Error && error.message.includes("ENOENT")) {
45
+ console.error(`\nError: Folder "${dirPath}" does not exist.\n`);
46
+ }
47
+ else if (error instanceof Error) {
48
+ console.error(`\nError: ${error.message}\n`);
49
+ }
50
+ else {
51
+ console.error("\nUnknown error.\n");
52
+ }
53
+ process.exit(1);
54
+ }
55
+ });
56
+ program.parse();
@@ -0,0 +1,21 @@
1
+ export function printReport(data) {
2
+ const totalTokens = data.selected.reduce((sum, f) => sum + f.tokens, 0);
3
+ const reduction = Math.round((1 - data.selected.length / data.totalFiles) * 100);
4
+ console.log("\nNuvanta Context Guard");
5
+ console.log("─────────────────────────────────────\n");
6
+ console.log(`Task: ${data.task}`);
7
+ console.log(`Keywords: ${data.keywords.join(", ")}`);
8
+ console.log(`Budget: ${data.budget} tokens\n`);
9
+ console.log(`Files found: ${data.totalFiles}`);
10
+ console.log(`Relevant: ${data.relevantFiles}`);
11
+ console.log(`Selected: ${data.selected.length}`);
12
+ console.log(`Context used: ${totalTokens} / ${data.budget} tokens`);
13
+ console.log(`Reduction: ~${reduction}%\n`);
14
+ console.log("Selected files:");
15
+ for (const file of data.selected) {
16
+ const tokens = `${file.tokens} tokens`.padStart(12);
17
+ const score = `score ${file.score}`.padEnd(10);
18
+ console.log(` ${file.path.padEnd(35)} ${tokens} ${score}`);
19
+ }
20
+ console.log("");
21
+ }
@@ -0,0 +1,45 @@
1
+ import path from "path";
2
+ const STOP_WORDS = new Set([
3
+ "fix",
4
+ "add",
5
+ "update",
6
+ "change",
7
+ "make",
8
+ "the",
9
+ "a",
10
+ "an",
11
+ "is",
12
+ "in",
13
+ "for",
14
+ "to",
15
+ ]);
16
+ export function extractKeywords(task) {
17
+ return task
18
+ .toLowerCase()
19
+ .split(" ")
20
+ .filter((word) => !STOP_WORDS.has(word));
21
+ }
22
+ export function scoreFile(filePath, keywords) {
23
+ const fileName = path.basename(filePath).toLowerCase();
24
+ const dirName = path.dirname(filePath).toLowerCase();
25
+ let score = 0;
26
+ for (const keyword of keywords) {
27
+ if (fileName.includes(keyword)) {
28
+ score += 1.0;
29
+ }
30
+ if (dirName.includes(keyword)) {
31
+ score += 0.6;
32
+ }
33
+ }
34
+ return score;
35
+ }
36
+ export function scoreContent(content, keywords) {
37
+ const normalizedContent = content.toLowerCase();
38
+ let score = 0;
39
+ for (const keyword of keywords) {
40
+ if (normalizedContent.includes(keyword)) {
41
+ score += 0.5;
42
+ }
43
+ }
44
+ return score;
45
+ }
@@ -0,0 +1,53 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ const IGNORED_DIRS = new Set([
4
+ "node_modules",
5
+ ".git",
6
+ "dist",
7
+ ".next",
8
+ "build",
9
+ "coverage",
10
+ ".cache",
11
+ ]);
12
+ const IGNORED_EXTENSIONS = new Set([
13
+ ".md",
14
+ ".lock",
15
+ ".env",
16
+ ".log",
17
+ ".png",
18
+ ".jpg",
19
+ ".jpeg",
20
+ ".gif",
21
+ ".svg",
22
+ ".ico",
23
+ ".woff",
24
+ ".woff2",
25
+ ".ttf",
26
+ ]);
27
+ export async function scanDirectory(dirPath) {
28
+ const result = [];
29
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
30
+ for (const entry of entries) {
31
+ const fullPath = path.join(dirPath, entry.name);
32
+ if (entry.isDirectory()) {
33
+ if (IGNORED_DIRS.has(entry.name))
34
+ continue;
35
+ const subFiles = await scanDirectory(fullPath);
36
+ result.push(...subFiles);
37
+ }
38
+ else {
39
+ const ext = path.extname(entry.name);
40
+ if (IGNORED_EXTENSIONS.has(ext))
41
+ continue;
42
+ const stat = await fs.stat(fullPath);
43
+ result.push({
44
+ path: fullPath,
45
+ name: entry.name,
46
+ extension: path.extname(entry.name),
47
+ size: stat.size,
48
+ tokens: Math.round(stat.size / 4),
49
+ });
50
+ }
51
+ }
52
+ return result;
53
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "nuvanta-context-guard",
3
+ "version": "0.1.0",
4
+ "description": "A developer tool that identifies relevant project files for AI coding agents",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "nuvanta": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "node dist/index.js",
12
+ "test": "vitest run --dir src",
13
+ "lint": "eslint src/**/*.ts",
14
+ "format": "prettier --write src/**/*.ts",
15
+ "prepublishOnly": "npm run build && npm test"
16
+ },
17
+ "keywords": ["ai", "context", "developer-tools", "cli"],
18
+ "author": "zeeqsleepy",
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "devDependencies": {
25
+ "@eslint/js": "^10.0.1",
26
+ "@types/node": "^26.5.0",
27
+ "eslint": "^10.10.0",
28
+ "prettier": "^3.9.6",
29
+ "typescript": "5.8",
30
+ "typescript-eslint": "^8.70.0",
31
+ "vitest": "^5.0.0"
32
+ },
33
+ "dependencies": {
34
+ "commander": "^15.0.0"
35
+ }
36
+ }