devmem 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 +21 -0
- package/README.md +152 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +105 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +96 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/ai.d.ts +6 -0
- package/dist/utils/ai.d.ts.map +1 -0
- package/dist/utils/ai.js +65 -0
- package/dist/utils/ai.js.map +1 -0
- package/dist/utils/config.d.ts +12 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +37 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/prompts.d.ts +11 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +60 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/scanner.d.ts +21 -0
- package/dist/utils/scanner.d.ts.map +1 -0
- package/dist/utils/scanner.js +106 -0
- package/dist/utils/scanner.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nikhil Dhaliya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# DevMem
|
|
2
|
+
|
|
3
|
+
> Generate persistent, domain-specific context files for AI tools.
|
|
4
|
+
|
|
5
|
+
DevMem scans your codebase and generates structured context files that any AI tool can use to instantly understand your project - no more re-explaining.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Folder-aware output** — generates one `.md` per top-level folder, named to match your project structure
|
|
12
|
+
- **Targeted generation** — generate context for a specific folder, subfolder, or file with `--only`
|
|
13
|
+
- **Multi-provider AI** — supports Gemini, OpenAI, and Anthropic
|
|
14
|
+
- **Smart scanning** — ignores `node_modules`, binaries, lockfiles, and respects size limits
|
|
15
|
+
- **AI-optimized output** — concise, structured, bullet-point context that fits in LLM context windows
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Clone and install
|
|
23
|
+
git clone https://github.com/NikhilDhaliya/devmem.git
|
|
24
|
+
cd devmem
|
|
25
|
+
npm install
|
|
26
|
+
npm run build
|
|
27
|
+
npm link
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### 1. Setup your AI provider
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
devmem setup
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Choose between **Gemini**, **OpenAI**, or **Anthropic**, enter your API key, and optionally pick a model.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# View current config
|
|
44
|
+
devmem setup --show
|
|
45
|
+
|
|
46
|
+
# Remove config and API key
|
|
47
|
+
devmem setup --remove
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Generate context
|
|
51
|
+
|
|
52
|
+
Navigate to any project and run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
devmem generate
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This scans your project and creates `.dev/<folder>.md` for each top-level folder:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
my-app/
|
|
62
|
+
├── client/ → .dev/client.md
|
|
63
|
+
├── server/ → .dev/server.md
|
|
64
|
+
├── shared/ → .dev/shared.md
|
|
65
|
+
├── package.json → .dev/root.md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Targeted generation
|
|
69
|
+
|
|
70
|
+
Generate context for specific parts of your project:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Single folder
|
|
74
|
+
devmem generate --only client
|
|
75
|
+
|
|
76
|
+
# Multiple folders
|
|
77
|
+
devmem generate --only "client,server"
|
|
78
|
+
|
|
79
|
+
# Subfolder or feature
|
|
80
|
+
devmem generate --only src/auth
|
|
81
|
+
|
|
82
|
+
# Specific file
|
|
83
|
+
devmem generate --only src/utils/db.ts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> **Note:** Even with `--only`, DevMem sends the **full project** to the AI so it understands the complete picture, but generates documentation only for the targeted area.
|
|
87
|
+
|
|
88
|
+
### 4. Custom output directory
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
devmem generate -o context/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
Config is stored in `~/.devmem/config.json`:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"provider": "gemini",
|
|
103
|
+
"apiKey": "your-api-key",
|
|
104
|
+
"model": "gemini-2.0-flash"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Default models
|
|
109
|
+
|
|
110
|
+
| Provider | Default Model |
|
|
111
|
+
|-----------|--------------------------|
|
|
112
|
+
| Gemini | `gemini-2.0-flash` |
|
|
113
|
+
| OpenAI | `gpt-4o-mini` |
|
|
114
|
+
| Anthropic | `claude-3-5-haiku-latest` |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Philosophy
|
|
119
|
+
|
|
120
|
+
DevMem is **not** an AI assistant or agent. It's a **context preservation layer** for developers:
|
|
121
|
+
|
|
122
|
+
- Scan once, reuse everywhere
|
|
123
|
+
- Works with ChatGPT, Claude, Gemini, or any AI tool
|
|
124
|
+
- Structured, compressed, AI-ready context
|
|
125
|
+
- No fluff, no generic explanations
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Project Structure
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
devmem/
|
|
133
|
+
├── src/
|
|
134
|
+
│ ├── index.ts # CLI entry point
|
|
135
|
+
│ ├── commands/
|
|
136
|
+
│ │ ├── setup.ts # Interactive provider config
|
|
137
|
+
│ │ └── generate.ts # Scan + AI context generation
|
|
138
|
+
│ └── utils/
|
|
139
|
+
│ ├── config.ts # ~/.devmem/config.json management
|
|
140
|
+
│ ├── scanner.ts # File discovery & folder grouping
|
|
141
|
+
│ ├── ai.ts # Multi-provider AI client
|
|
142
|
+
│ └── prompts.ts # Dynamic prompt generation
|
|
143
|
+
├── package.json
|
|
144
|
+
├── tsconfig.json
|
|
145
|
+
└── README.md
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuG9D"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { loadConfig } from '../utils/config.js';
|
|
6
|
+
import { createAIClient } from '../utils/ai.js';
|
|
7
|
+
import { scanProjectByFolders, getAllFiles, filterByTargets, } from '../utils/scanner.js';
|
|
8
|
+
import { buildFolderPrompt, buildTargetedPrompt } from '../utils/prompts.js';
|
|
9
|
+
function formatFileContents(files) {
|
|
10
|
+
if (files.length === 0)
|
|
11
|
+
return '';
|
|
12
|
+
return files
|
|
13
|
+
.map((f) => `### ${f.relativePath}\n\`\`\`\n${f.content}\n\`\`\``)
|
|
14
|
+
.join('\n\n');
|
|
15
|
+
}
|
|
16
|
+
export function registerGenerateCommand(program) {
|
|
17
|
+
program
|
|
18
|
+
.command('generate')
|
|
19
|
+
.description('Scan codebase and generate context files')
|
|
20
|
+
.option('-o, --output <dir>', 'Output directory', '.dev')
|
|
21
|
+
.option('--only <targets>', 'Generate only for specific folders, subfolders, or files (comma-separated)')
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
const projectDir = process.cwd();
|
|
24
|
+
const outputDir = path.resolve(projectDir, options.output);
|
|
25
|
+
// Load config
|
|
26
|
+
let config;
|
|
27
|
+
try {
|
|
28
|
+
config = await loadConfig();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.log(pc.red(`\n ✗ ${err.message}\n`));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
console.log();
|
|
35
|
+
console.log(pc.bold(' 🧠 DevMem Generate'));
|
|
36
|
+
console.log(pc.dim(' ─────────────────────'));
|
|
37
|
+
console.log(pc.dim(` Provider: ${config.provider} | Model: ${config.model}`));
|
|
38
|
+
console.log();
|
|
39
|
+
// Step 1: Scan full project
|
|
40
|
+
const scanSpinner = ora({ text: ' Scanning project...', indent: 2 }).start();
|
|
41
|
+
const fullScan = await scanProjectByFolders(projectDir);
|
|
42
|
+
const allFiles = getAllFiles(fullScan);
|
|
43
|
+
const folderNames = Object.keys(fullScan);
|
|
44
|
+
if (allFiles.length === 0) {
|
|
45
|
+
scanSpinner.fail(' No relevant files found.');
|
|
46
|
+
console.log(pc.dim(' Make sure you are in the root of a project.\n'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Determine what to generate
|
|
50
|
+
const isTargeted = !!options.only;
|
|
51
|
+
let targetsToGenerate;
|
|
52
|
+
if (isTargeted) {
|
|
53
|
+
const targets = options.only.split(',').map((t) => t.trim());
|
|
54
|
+
targetsToGenerate = filterByTargets(fullScan, targets);
|
|
55
|
+
if (Object.keys(targetsToGenerate).length === 0) {
|
|
56
|
+
scanSpinner.fail(' No matching targets found.');
|
|
57
|
+
console.log(pc.dim(` Available folders: ${folderNames.join(', ')}\n`));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const targetKeys = Object.keys(targetsToGenerate);
|
|
61
|
+
const targetFileCount = Object.values(targetsToGenerate).flat().length;
|
|
62
|
+
scanSpinner.succeed(` Found ${pc.cyan(String(allFiles.length))} total files | Targeting: ${targetKeys.map((t) => pc.yellow(t)).join(', ')} (${targetFileCount} files)`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
targetsToGenerate = fullScan;
|
|
66
|
+
const folderSummary = folderNames
|
|
67
|
+
.map((f) => `${f}: ${fullScan[f].length}`)
|
|
68
|
+
.join(', ');
|
|
69
|
+
scanSpinner.succeed(` Found ${pc.cyan(String(allFiles.length))} files across ${pc.cyan(String(folderNames.length))} folders — ${folderSummary}`);
|
|
70
|
+
}
|
|
71
|
+
// Step 2: Generate with AI
|
|
72
|
+
const ai = createAIClient(config);
|
|
73
|
+
await fs.ensureDir(outputDir);
|
|
74
|
+
// Full project context (used for targeted generation)
|
|
75
|
+
const fullContext = formatFileContents(allFiles);
|
|
76
|
+
for (const [folderKey, files] of Object.entries(targetsToGenerate)) {
|
|
77
|
+
const spinner = ora({ text: ` Generating ${pc.cyan(folderKey)} context...`, indent: 2 }).start();
|
|
78
|
+
try {
|
|
79
|
+
let prompt;
|
|
80
|
+
let context;
|
|
81
|
+
if (isTargeted) {
|
|
82
|
+
// Targeted: send FULL project as context, but prompt asks for specific target only
|
|
83
|
+
prompt = buildTargetedPrompt(folderKey);
|
|
84
|
+
context = fullContext;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Full scan: send only the folder's files
|
|
88
|
+
prompt = buildFolderPrompt(folderKey);
|
|
89
|
+
context = formatFileContents(files);
|
|
90
|
+
}
|
|
91
|
+
const result = await ai.summarize(prompt, context);
|
|
92
|
+
const outPath = path.join(outputDir, `${folderKey}.md`);
|
|
93
|
+
await fs.writeFile(outPath, result, 'utf-8');
|
|
94
|
+
spinner.succeed(` ${folderKey} → ${pc.green(path.relative(projectDir, outPath))}`);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
spinner.fail(` ${folderKey} failed: ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(pc.green(pc.bold(' ✓ Context files generated!')));
|
|
102
|
+
console.log(pc.dim(` Output: ${path.relative(projectDir, outputDir)}/\n`));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0F3D"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { saveConfig, loadConfig, maskKey, DEFAULT_MODELS } from '../utils/config.js';
|
|
4
|
+
function createRL() {
|
|
5
|
+
return readline.createInterface({
|
|
6
|
+
input: process.stdin,
|
|
7
|
+
output: process.stdout,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function ask(rl, question) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function registerSetupCommand(program) {
|
|
16
|
+
program
|
|
17
|
+
.command('setup')
|
|
18
|
+
.description('Configure your AI provider and API key')
|
|
19
|
+
.option('--show', 'Show current configuration')
|
|
20
|
+
.option('--remove', 'Remove current configuration and API key')
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
if (options.remove) {
|
|
23
|
+
try {
|
|
24
|
+
await import('../utils/config.js').then(m => m.removeConfig());
|
|
25
|
+
console.log(pc.green('\n ✓ Configuration and API key removed successfully.\n'));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.log(pc.red(`\n ✗ Failed to remove configuration: ${err.message}\n`));
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (options.show) {
|
|
33
|
+
try {
|
|
34
|
+
const config = await loadConfig();
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(pc.bold(' DevMem Configuration'));
|
|
37
|
+
console.log(pc.dim(' ─────────────────────'));
|
|
38
|
+
console.log(` Provider : ${pc.cyan(config.provider)}`);
|
|
39
|
+
console.log(` Model : ${pc.cyan(config.model)}`);
|
|
40
|
+
console.log(` API Key : ${pc.yellow(maskKey(config.apiKey))}`);
|
|
41
|
+
console.log();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
console.log(pc.red('\n ✗ No configuration found. Run `devmem setup` first.\n'));
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const rl = createRL();
|
|
49
|
+
try {
|
|
50
|
+
console.log();
|
|
51
|
+
console.log(pc.bold(' 🧠 DevMem Setup'));
|
|
52
|
+
console.log(pc.dim(' ─────────────────'));
|
|
53
|
+
console.log();
|
|
54
|
+
// 1. Choose provider
|
|
55
|
+
console.log(pc.dim(' Available providers:'));
|
|
56
|
+
console.log(` ${pc.cyan('1')} — Gemini`);
|
|
57
|
+
console.log(` ${pc.cyan('2')} — OpenAI`);
|
|
58
|
+
console.log(` ${pc.cyan('3')} — Anthropic`);
|
|
59
|
+
console.log();
|
|
60
|
+
let providerChoice = '';
|
|
61
|
+
while (!['1', '2', '3'].includes(providerChoice)) {
|
|
62
|
+
providerChoice = await ask(rl, pc.bold(' Select provider (1/2/3): '));
|
|
63
|
+
}
|
|
64
|
+
const providerMap = {
|
|
65
|
+
'1': 'gemini',
|
|
66
|
+
'2': 'openai',
|
|
67
|
+
'3': 'anthropic',
|
|
68
|
+
};
|
|
69
|
+
const provider = providerMap[providerChoice];
|
|
70
|
+
// 2. Enter API key
|
|
71
|
+
console.log();
|
|
72
|
+
const apiKey = await ask(rl, pc.bold(` Enter your ${provider} API key: `));
|
|
73
|
+
if (!apiKey) {
|
|
74
|
+
console.log(pc.red('\n ✗ API key cannot be empty.\n'));
|
|
75
|
+
rl.close();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// 3. Choose model
|
|
79
|
+
const defaultModel = DEFAULT_MODELS[provider];
|
|
80
|
+
console.log();
|
|
81
|
+
const modelInput = await ask(rl, pc.bold(` Model ${pc.dim(`(default: ${defaultModel})`)}: `));
|
|
82
|
+
const model = modelInput || defaultModel;
|
|
83
|
+
// Save
|
|
84
|
+
const config = { provider, apiKey, model };
|
|
85
|
+
await saveConfig(config);
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(pc.green(' ✓ Configuration saved!'));
|
|
88
|
+
console.log(pc.dim(` Provider: ${provider} | Model: ${model}`));
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
rl.close();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerSetupCommand } from './commands/setup.js';
|
|
4
|
+
import { registerGenerateCommand } from './commands/generate.js';
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program
|
|
7
|
+
.name('devmem')
|
|
8
|
+
.description('Generate persistent, domain-specific context files for AI tools')
|
|
9
|
+
.version('1.0.0');
|
|
10
|
+
registerSetupCommand(program);
|
|
11
|
+
registerGenerateCommand(program);
|
|
12
|
+
program.parse();
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,iEAAiE,CAAC;KAC9E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAEjC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/utils/ai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,MAAM,WAAW,QAAQ;IACvB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClE;AAwDD,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CAW7D"}
|
package/dist/utils/ai.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
import OpenAI from 'openai';
|
|
3
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
4
|
+
import { SYSTEM_PROMPT } from './prompts.js';
|
|
5
|
+
function createGeminiClient(config) {
|
|
6
|
+
const genAI = new GoogleGenerativeAI(config.apiKey);
|
|
7
|
+
const model = genAI.getGenerativeModel({ model: config.model });
|
|
8
|
+
return {
|
|
9
|
+
async summarize(prompt, fileContents) {
|
|
10
|
+
const result = await model.generateContent([
|
|
11
|
+
{ text: SYSTEM_PROMPT },
|
|
12
|
+
{ text: prompt },
|
|
13
|
+
{ text: `\n\n--- SOURCE FILES ---\n\n${fileContents}` },
|
|
14
|
+
]);
|
|
15
|
+
return result.response.text();
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function createOpenAIClient(config) {
|
|
20
|
+
const client = new OpenAI({ apiKey: config.apiKey });
|
|
21
|
+
return {
|
|
22
|
+
async summarize(prompt, fileContents) {
|
|
23
|
+
const response = await client.chat.completions.create({
|
|
24
|
+
model: config.model,
|
|
25
|
+
messages: [
|
|
26
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
27
|
+
{ role: 'user', content: `${prompt}\n\n--- SOURCE FILES ---\n\n${fileContents}` },
|
|
28
|
+
],
|
|
29
|
+
temperature: 0.3,
|
|
30
|
+
max_tokens: 4096,
|
|
31
|
+
});
|
|
32
|
+
return response.choices[0]?.message?.content ?? '';
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function createAnthropicClient(config) {
|
|
37
|
+
const client = new Anthropic({ apiKey: config.apiKey });
|
|
38
|
+
return {
|
|
39
|
+
async summarize(prompt, fileContents) {
|
|
40
|
+
const response = await client.messages.create({
|
|
41
|
+
model: config.model,
|
|
42
|
+
max_tokens: 4096,
|
|
43
|
+
system: SYSTEM_PROMPT,
|
|
44
|
+
messages: [
|
|
45
|
+
{ role: 'user', content: `${prompt}\n\n--- SOURCE FILES ---\n\n${fileContents}` },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
const block = response.content[0];
|
|
49
|
+
return block.type === 'text' ? block.text : '';
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function createAIClient(config) {
|
|
54
|
+
switch (config.provider) {
|
|
55
|
+
case 'gemini':
|
|
56
|
+
return createGeminiClient(config);
|
|
57
|
+
case 'openai':
|
|
58
|
+
return createOpenAIClient(config);
|
|
59
|
+
case 'anthropic':
|
|
60
|
+
return createAnthropicClient(config);
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(`Unsupported provider: ${config.provider}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/utils/ai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DevMemConfig {
|
|
2
|
+
provider: 'gemini' | 'openai' | 'anthropic';
|
|
3
|
+
apiKey: string;
|
|
4
|
+
model: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getConfigPath(): string;
|
|
7
|
+
export declare function loadConfig(): Promise<DevMemConfig>;
|
|
8
|
+
export declare function saveConfig(config: DevMemConfig): Promise<void>;
|
|
9
|
+
export declare function removeConfig(): Promise<void>;
|
|
10
|
+
export declare function maskKey(key: string): string;
|
|
11
|
+
export declare const DEFAULT_MODELS: Record<string, string>;
|
|
12
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAKD,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAGD,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAiBxD;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIjD,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), '.devmem');
|
|
5
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
6
|
+
export function getConfigPath() {
|
|
7
|
+
return CONFIG_FILE;
|
|
8
|
+
}
|
|
9
|
+
export async function loadConfig() {
|
|
10
|
+
const exists = await fs.pathExists(CONFIG_FILE);
|
|
11
|
+
if (!exists) {
|
|
12
|
+
throw new Error('DevMem is not configured yet. Run `devmem setup` first.');
|
|
13
|
+
}
|
|
14
|
+
const raw = await fs.readJSON(CONFIG_FILE);
|
|
15
|
+
if (!raw.provider || !raw.apiKey) {
|
|
16
|
+
throw new Error('Invalid config. Run `devmem setup` to reconfigure.');
|
|
17
|
+
}
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
export async function saveConfig(config) {
|
|
21
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
22
|
+
await fs.writeJSON(CONFIG_FILE, config, { spaces: 2 });
|
|
23
|
+
}
|
|
24
|
+
export async function removeConfig() {
|
|
25
|
+
await fs.remove(CONFIG_FILE);
|
|
26
|
+
}
|
|
27
|
+
export function maskKey(key) {
|
|
28
|
+
if (key.length <= 8)
|
|
29
|
+
return '****';
|
|
30
|
+
return key.slice(0, 4) + '...' + key.slice(-4);
|
|
31
|
+
}
|
|
32
|
+
export const DEFAULT_MODELS = {
|
|
33
|
+
gemini: 'gemini-2.0-flash',
|
|
34
|
+
openai: 'gpt-4o-mini',
|
|
35
|
+
anthropic: 'claude-3-5-haiku-latest',
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAQpB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACtD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAE3C,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,oDAAoD,CACrD,CAAC;IACJ,CAAC;IAED,OAAO,GAAmB,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAoB;IACnD,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC/B,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACnC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,MAAM,EAAE,kBAAkB;IAC1B,MAAM,EAAE,aAAa;IACrB,SAAS,EAAE,yBAAyB;CACrC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const SYSTEM_PROMPT = "You are DevMem, a context extraction engine. Your job is to analyze source code and produce structured, concise, AI-ready context documentation.\n\nRules:\n- Include only high-signal information: architecture, patterns, structure, conventions\n- Preserve actual project decisions \u2014 do not assume or generalize beyond the codebase\n- Optimize for AI usage: concise, structured, no fluff\n- Prefer bullet points\n- Keep output concise enough to fit in an LLM context window\n- Do NOT generate code\n- Do NOT hallucinate missing details\n- Do NOT include low-value information";
|
|
2
|
+
/**
|
|
3
|
+
* Build a context-generation prompt for a specific folder/target.
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildFolderPrompt(folderName: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Build a prompt that provides full project context but asks to generate
|
|
8
|
+
* documentation only for a specific target (folder, subfolder, or file).
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildTargetedPrompt(targetName: string): string;
|
|
11
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,ukBAUa,CAAC;AAExC;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAiB5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB9D"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const SYSTEM_PROMPT = `You are DevMem, a context extraction engine. Your job is to analyze source code and produce structured, concise, AI-ready context documentation.
|
|
2
|
+
|
|
3
|
+
Rules:
|
|
4
|
+
- Include only high-signal information: architecture, patterns, structure, conventions
|
|
5
|
+
- Preserve actual project decisions — do not assume or generalize beyond the codebase
|
|
6
|
+
- Optimize for AI usage: concise, structured, no fluff
|
|
7
|
+
- Prefer bullet points
|
|
8
|
+
- Keep output concise enough to fit in an LLM context window
|
|
9
|
+
- Do NOT generate code
|
|
10
|
+
- Do NOT hallucinate missing details
|
|
11
|
+
- Do NOT include low-value information`;
|
|
12
|
+
/**
|
|
13
|
+
* Build a context-generation prompt for a specific folder/target.
|
|
14
|
+
*/
|
|
15
|
+
export function buildFolderPrompt(folderName) {
|
|
16
|
+
return `Analyze the following source files from the "${folderName}" part of the project and generate a structured context document.
|
|
17
|
+
|
|
18
|
+
Include:
|
|
19
|
+
- Tech stack and frameworks used in this area
|
|
20
|
+
- Folder structure overview
|
|
21
|
+
- Key patterns and conventions
|
|
22
|
+
- Important modules/components and their responsibilities
|
|
23
|
+
- How this area connects to other parts of the project
|
|
24
|
+
- Any notable design decisions or constraints
|
|
25
|
+
|
|
26
|
+
Avoid:
|
|
27
|
+
- Trivial descriptions
|
|
28
|
+
- Listing every single file
|
|
29
|
+
- Generic explanations not specific to this codebase
|
|
30
|
+
|
|
31
|
+
Output a clean markdown document titled "# ${capitalize(folderName)} Context".`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a prompt that provides full project context but asks to generate
|
|
35
|
+
* documentation only for a specific target (folder, subfolder, or file).
|
|
36
|
+
*/
|
|
37
|
+
export function buildTargetedPrompt(targetName) {
|
|
38
|
+
return `You are given the FULL project source code for context. However, generate documentation ONLY for the "${targetName}" part of the project.
|
|
39
|
+
|
|
40
|
+
Use the full codebase to understand relationships, dependencies, imports, and how this area fits into the bigger picture, but ONLY document "${targetName}".
|
|
41
|
+
|
|
42
|
+
Include:
|
|
43
|
+
- Tech stack and frameworks used in this specific area
|
|
44
|
+
- Structure overview
|
|
45
|
+
- Key patterns and conventions
|
|
46
|
+
- Important modules/components and their responsibilities
|
|
47
|
+
- How this area connects to other parts of the project
|
|
48
|
+
- Dependencies and relationships with other parts of the codebase
|
|
49
|
+
|
|
50
|
+
Avoid:
|
|
51
|
+
- Documenting parts of the project outside "${targetName}"
|
|
52
|
+
- Trivial descriptions
|
|
53
|
+
- Generic explanations not specific to this codebase
|
|
54
|
+
|
|
55
|
+
Output a clean markdown document titled "# ${capitalize(targetName)} Context".`;
|
|
56
|
+
}
|
|
57
|
+
function capitalize(s) {
|
|
58
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;uCAUU,CAAC;AAExC;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,OAAO,gDAAgD,UAAU;;;;;;;;;;;;;;;6CAetB,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC;AAChF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,yGAAyG,UAAU;;+IAEmB,UAAU;;;;;;;;;;;8CAW3G,UAAU;;;;6CAIX,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC;AAChF,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface FileEntry {
|
|
2
|
+
relativePath: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
/** Map of folder name → files in that folder */
|
|
6
|
+
export type FolderScanResult = Record<string, FileEntry[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Scan a project and group files by their top-level folder.
|
|
9
|
+
* Root-level files are grouped under "root".
|
|
10
|
+
*/
|
|
11
|
+
export declare function scanProjectByFolders(projectDir: string): Promise<FolderScanResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Get all files from a FolderScanResult as a flat list.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getAllFiles(scan: FolderScanResult): FileEntry[];
|
|
16
|
+
/**
|
|
17
|
+
* Filter a scan result to only include specific targets.
|
|
18
|
+
* Targets can be top-level folder names, subfolder paths, or file paths.
|
|
19
|
+
*/
|
|
20
|
+
export declare function filterByTargets(scan: FolderScanResult, targets: string[]): FolderScanResult;
|
|
21
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/utils/scanner.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;AA8B3D;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsCxF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAAE,CAE/D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EAAE,GAChB,gBAAgB,CAmClB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const IGNORE_PATTERNS = [
|
|
5
|
+
'node_modules/**',
|
|
6
|
+
'dist/**',
|
|
7
|
+
'build/**',
|
|
8
|
+
'.next/**',
|
|
9
|
+
'.git/**',
|
|
10
|
+
'.dev/**',
|
|
11
|
+
'*.lock',
|
|
12
|
+
'package-lock.json',
|
|
13
|
+
'yarn.lock',
|
|
14
|
+
'pnpm-lock.yaml',
|
|
15
|
+
'*.map',
|
|
16
|
+
'*.min.*',
|
|
17
|
+
'.env',
|
|
18
|
+
'.env.*',
|
|
19
|
+
'!.env.example',
|
|
20
|
+
];
|
|
21
|
+
const BINARY_EXTENSIONS = new Set([
|
|
22
|
+
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.webp',
|
|
23
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
24
|
+
'.mp4', '.webm', '.mp3', '.wav', '.ogg',
|
|
25
|
+
'.pdf', '.zip', '.tar', '.gz', '.bz2',
|
|
26
|
+
'.exe', '.dll', '.so', '.dylib',
|
|
27
|
+
]);
|
|
28
|
+
const MAX_FILE_SIZE = 50 * 1024; // 50KB
|
|
29
|
+
/**
|
|
30
|
+
* Scan a project and group files by their top-level folder.
|
|
31
|
+
* Root-level files are grouped under "root".
|
|
32
|
+
*/
|
|
33
|
+
export async function scanProjectByFolders(projectDir) {
|
|
34
|
+
const files = await glob('**/*', {
|
|
35
|
+
cwd: projectDir,
|
|
36
|
+
nodir: true,
|
|
37
|
+
ignore: IGNORE_PATTERNS,
|
|
38
|
+
dot: false,
|
|
39
|
+
});
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const absPath = path.join(projectDir, file);
|
|
43
|
+
// Skip large files
|
|
44
|
+
const stat = await fs.stat(absPath);
|
|
45
|
+
if (stat.size > MAX_FILE_SIZE)
|
|
46
|
+
continue;
|
|
47
|
+
// Skip binary files
|
|
48
|
+
const ext = path.extname(file).toLowerCase();
|
|
49
|
+
if (BINARY_EXTENSIONS.has(ext))
|
|
50
|
+
continue;
|
|
51
|
+
// Determine the top-level folder (or "root")
|
|
52
|
+
const parts = file.split(path.sep);
|
|
53
|
+
const folderKey = parts.length > 1 ? parts[0] : 'root';
|
|
54
|
+
try {
|
|
55
|
+
const content = await fs.readFile(absPath, 'utf-8');
|
|
56
|
+
if (!result[folderKey]) {
|
|
57
|
+
result[folderKey] = [];
|
|
58
|
+
}
|
|
59
|
+
result[folderKey].push({ relativePath: file, content });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Skip unreadable files
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get all files from a FolderScanResult as a flat list.
|
|
69
|
+
*/
|
|
70
|
+
export function getAllFiles(scan) {
|
|
71
|
+
return Object.values(scan).flat();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Filter a scan result to only include specific targets.
|
|
75
|
+
* Targets can be top-level folder names, subfolder paths, or file paths.
|
|
76
|
+
*/
|
|
77
|
+
export function filterByTargets(scan, targets) {
|
|
78
|
+
const filtered = {};
|
|
79
|
+
for (const target of targets) {
|
|
80
|
+
const normalizedTarget = target.replace(/\/$/, ''); // remove trailing slash
|
|
81
|
+
// 1. Exact top-level folder match
|
|
82
|
+
if (scan[normalizedTarget]) {
|
|
83
|
+
filtered[normalizedTarget] = scan[normalizedTarget];
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// 2. Subfolder or file match — search across all folders
|
|
87
|
+
for (const [folder, files] of Object.entries(scan)) {
|
|
88
|
+
const matching = files.filter((f) => {
|
|
89
|
+
// Match if the file path starts with the target (subfolder)
|
|
90
|
+
// or equals the target (exact file)
|
|
91
|
+
return (f.relativePath === normalizedTarget ||
|
|
92
|
+
f.relativePath.startsWith(normalizedTarget + '/'));
|
|
93
|
+
});
|
|
94
|
+
if (matching.length > 0) {
|
|
95
|
+
// Use the target as the key for the output file name
|
|
96
|
+
const key = normalizedTarget.replace(/\//g, '-');
|
|
97
|
+
if (!filtered[key]) {
|
|
98
|
+
filtered[key] = [];
|
|
99
|
+
}
|
|
100
|
+
filtered[key].push(...matching);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return filtered;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/utils/scanner.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "devmem",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool that scans a codebase and generates persistent, domain-specific context files for AI tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"devmem": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"start": "node dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cli",
|
|
17
|
+
"ai",
|
|
18
|
+
"context",
|
|
19
|
+
"devmem",
|
|
20
|
+
"llm",
|
|
21
|
+
"developer-tools",
|
|
22
|
+
"codebase-analysis"
|
|
23
|
+
],
|
|
24
|
+
"author": "nikhildhaliya",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/NikhilDhaliya/devmem.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/NikhilDhaliya/devmem/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/NikhilDhaliya/devmem#readme",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@anthropic-ai/sdk": "^0.80.0",
|
|
44
|
+
"@google/generative-ai": "^0.24.1",
|
|
45
|
+
"commander": "^14.0.3",
|
|
46
|
+
"fs-extra": "^11.3.4",
|
|
47
|
+
"glob": "^13.0.6",
|
|
48
|
+
"openai": "^6.33.0",
|
|
49
|
+
"ora": "^9.3.0",
|
|
50
|
+
"picocolors": "^1.1.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/fs-extra": "^11.0.4",
|
|
54
|
+
"@types/node": "^25.5.0",
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^6.0.2"
|
|
57
|
+
}
|
|
58
|
+
}
|