@xelth/eck-snapshot 4.1.0 → 4.2.1
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 +1 -1
- package/package.json +8 -4
- package/setup.json +10 -30
- package/src/cli/cli.js +11 -54
- package/src/cli/commands/autoDocs.js +1 -32
- package/src/cli/commands/createSnapshot.js +125 -6
- package/src/cli/commands/doctor.js +60 -0
- package/src/cli/commands/updateSnapshot.js +3 -3
- package/src/config.js +44 -0
- package/src/core/skeletonizer.js +77 -52
- package/src/services/claudeCliService.js +5 -0
- package/src/templates/agent-prompt.template.md +104 -7
- package/src/templates/architect-prompt.template.md +112 -23
- package/src/templates/multiAgent.md +9 -0
- package/src/utils/aiHeader.js +147 -95
- package/src/utils/eckProtocolParser.js +221 -0
- package/src/utils/fileUtils.js +181 -185
- package/src/utils/tokenEstimator.js +4 -1
- package/src/cli/commands/askGpt.js +0 -29
- package/src/services/authService.js +0 -20
- package/src/services/dispatcherService.js +0 -33
- package/src/services/gptService.js +0 -302
- package/src/services/gptService.test.js +0 -120
- package/src/templates/vectorMode.md +0 -22
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This tool is built for a modern workflow where you act as the architect, guiding
|
|
|
12
12
|
`eckSnapshot` is designed to support a two-part AI workflow for maximum efficiency and quality:
|
|
13
13
|
|
|
14
14
|
1. **The Architect LLM (Gemini, GPT-4, Grok):** A model with a large context window. You provide it with a snapshot of your project, and it analyzes the codebase to create a high-level plan for new features or refactoring.
|
|
15
|
-
2. **The Coder LLM (Claude Code,
|
|
15
|
+
2. **The Coder LLM (Claude Code, Minimax, etc.):** A model specialized in writing and modifying code. It takes the detailed instructions generated by the Architect and performs the hands-on coding tasks.
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xelth/eck-snapshot",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "A powerful CLI tool to create and restore single-file text snapshots of Git repositories and directories. Optimized for AI context and LLM workflows.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
"test": "vitest",
|
|
20
20
|
"test:ui": "vitest --ui",
|
|
21
21
|
"test:run": "vitest run",
|
|
22
|
-
"docs:auto": "node index.js docs-auto"
|
|
23
|
-
"test:gpt": "vitest src/services/gptService.test.js"
|
|
22
|
+
"docs:auto": "node index.js docs-auto"
|
|
24
23
|
},
|
|
25
24
|
"author": "xelth-com",
|
|
26
25
|
"license": "MIT",
|
|
@@ -41,15 +40,20 @@
|
|
|
41
40
|
"inquirer": "^9.2.20",
|
|
42
41
|
"is-binary-path": "^2.1.0",
|
|
43
42
|
"micromatch": "^4.0.8",
|
|
43
|
+
"minimatch": "^9.0.3",
|
|
44
44
|
"ora": "^8.1.0",
|
|
45
45
|
"p-limit": "^5.0.0",
|
|
46
46
|
"p-retry": "^6.2.1",
|
|
47
|
+
"which": "^4.0.0"
|
|
48
|
+
},
|
|
49
|
+
"optionalDependencies": {
|
|
47
50
|
"tree-sitter": "^0.25.0",
|
|
48
51
|
"tree-sitter-c": "^0.24.1",
|
|
52
|
+
"tree-sitter-go": "^0.23.4",
|
|
49
53
|
"tree-sitter-java": "^0.23.5",
|
|
50
54
|
"tree-sitter-kotlin": "^0.3.8",
|
|
51
55
|
"tree-sitter-python": "^0.25.0",
|
|
52
|
-
"
|
|
56
|
+
"tree-sitter-rust": "^0.23.2"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"jsdom": "^24.0.0",
|
package/setup.json
CHANGED
|
@@ -231,16 +231,13 @@
|
|
|
231
231
|
},
|
|
232
232
|
"contextProfiles": {
|
|
233
233
|
"backend": {
|
|
234
|
-
"description": "Backend API,
|
|
234
|
+
"description": "Backend API, services, business logic",
|
|
235
235
|
"include": [
|
|
236
236
|
"packages/backend/**",
|
|
237
|
-
"packages/core/**"
|
|
238
|
-
"knexfile.js",
|
|
239
|
-
"migrations/**"
|
|
237
|
+
"packages/core/**"
|
|
240
238
|
],
|
|
241
239
|
"exclude": [
|
|
242
240
|
"**/*.test.*",
|
|
243
|
-
"**/*.sqlite*",
|
|
244
241
|
"node_modules/**"
|
|
245
242
|
]
|
|
246
243
|
},
|
|
@@ -301,8 +298,7 @@
|
|
|
301
298
|
"description": "Database schema and migrations only",
|
|
302
299
|
"include": [
|
|
303
300
|
"**/migrations/**",
|
|
304
|
-
"**/
|
|
305
|
-
"**/schema.sql"
|
|
301
|
+
"**/schema/**"
|
|
306
302
|
]
|
|
307
303
|
},
|
|
308
304
|
"deployment": {
|
|
@@ -544,6 +540,10 @@
|
|
|
544
540
|
"maxDepth": 10,
|
|
545
541
|
"concurrency": 10
|
|
546
542
|
},
|
|
543
|
+
"security": {
|
|
544
|
+
"scanForSecrets": true,
|
|
545
|
+
"_comment": "Automatically detects and redacts API keys, tokens, and credentials in snapshots to prevent accidental exposure"
|
|
546
|
+
},
|
|
547
547
|
"output": {
|
|
548
548
|
"defaultFormat": "md",
|
|
549
549
|
"defaultPath": "./.eck/snapshots",
|
|
@@ -671,25 +671,6 @@
|
|
|
671
671
|
"no hardware debugging interfaces"
|
|
672
672
|
]
|
|
673
673
|
},
|
|
674
|
-
"ci_cd": {
|
|
675
|
-
"active": false,
|
|
676
|
-
"name": "CI/CD Pipeline Agent (AGENT_CI_CD)",
|
|
677
|
-
"description": "Automated testing and deployment pipeline",
|
|
678
|
-
"guiSupport": false,
|
|
679
|
-
"capabilities": [
|
|
680
|
-
"npm ci",
|
|
681
|
-
"npm test",
|
|
682
|
-
"npm run build",
|
|
683
|
-
"docker build",
|
|
684
|
-
"artifact generation"
|
|
685
|
-
],
|
|
686
|
-
"restrictions": [
|
|
687
|
-
"no interactive commands",
|
|
688
|
-
"no GUI applications",
|
|
689
|
-
"no watch modes",
|
|
690
|
-
"no development servers"
|
|
691
|
-
]
|
|
692
|
-
},
|
|
693
674
|
"gemini_wsl": {
|
|
694
675
|
"active": true,
|
|
695
676
|
"name": "Gemini WSL Agent (Junior Architect)",
|
|
@@ -763,7 +744,6 @@
|
|
|
763
744
|
"envScanRequest": "src/templates/envScanRequest.md",
|
|
764
745
|
"gitWorkflow": "src/templates/gitWorkflow.md",
|
|
765
746
|
"multiAgent": "src/templates/multiAgent.md",
|
|
766
|
-
"vectorMode": "src/templates/vectorMode.md",
|
|
767
747
|
"agent": "src/templates/agent-prompt.template.md"
|
|
768
748
|
}
|
|
769
749
|
},
|
|
@@ -802,11 +782,11 @@
|
|
|
802
782
|
},
|
|
803
783
|
"database_expert": {
|
|
804
784
|
"active": true,
|
|
805
|
-
"modelName": "
|
|
785
|
+
"modelName": "Claude",
|
|
806
786
|
"role": "Database Specialist",
|
|
807
787
|
"strengths": [
|
|
808
|
-
"
|
|
809
|
-
"
|
|
788
|
+
"Schema design",
|
|
789
|
+
"Query optimization",
|
|
810
790
|
"data integrity"
|
|
811
791
|
]
|
|
812
792
|
},
|
package/src/cli/cli.js
CHANGED
|
@@ -13,14 +13,13 @@ import { pruneSnapshot } from './commands/pruneSnapshot.js';
|
|
|
13
13
|
import { generateConsilium } from './commands/consilium.js';
|
|
14
14
|
import { detectProject, testFileParsing } from './commands/detectProject.js';
|
|
15
15
|
import { trainTokens, showTokenStats } from './commands/trainTokens.js';
|
|
16
|
-
import { askGpt } from './commands/askGpt.js';
|
|
17
|
-
import { ask as askGptService } from '../services/gptService.js';
|
|
18
16
|
import { executePrompt, executePromptWithSession } from '../services/claudeCliService.js';
|
|
19
17
|
import { detectProfiles } from './commands/detectProfiles.js';
|
|
20
18
|
import { generateProfileGuide } from './commands/generateProfileGuide.js';
|
|
21
19
|
import { setupGemini } from './commands/setupGemini.js';
|
|
22
20
|
import { generateAutoDocs } from './commands/autoDocs.js';
|
|
23
21
|
import { showFile } from './commands/showFile.js';
|
|
22
|
+
import { runDoctor } from './commands/doctor.js';
|
|
24
23
|
import inquirer from 'inquirer';
|
|
25
24
|
import ora from 'ora';
|
|
26
25
|
import { execa } from 'execa';
|
|
@@ -112,7 +111,7 @@ Option C: Using Profiles
|
|
|
112
111
|
|
|
113
112
|
- restore: Restore files from a snapshot to disk.
|
|
114
113
|
- prune: Use AI to shrink a snapshot file by importance.
|
|
115
|
-
- ask-
|
|
114
|
+
- ask-claude: Delegate tasks to Claude CLI agent.
|
|
116
115
|
- setup-gemini: Configure gemini-cli integration.
|
|
117
116
|
`;
|
|
118
117
|
|
|
@@ -246,47 +245,6 @@ Creating Custom Profiles:
|
|
|
246
245
|
console.log(JSON.stringify(result, null, 2));
|
|
247
246
|
});
|
|
248
247
|
|
|
249
|
-
program
|
|
250
|
-
.command('ask-gpt')
|
|
251
|
-
.description('Delegate tasks to OpenAI Codex agent with automatic authentication')
|
|
252
|
-
.argument('<payload>', 'JSON payload string (e.g. \'{"objective": "Calculate 5+2"}\')')
|
|
253
|
-
.option('-v, --verbose', 'Enable verbose logging and detailed execution output')
|
|
254
|
-
.option('--model <name>', 'Model to use (default: gpt-5-codex)', 'gpt-5-codex')
|
|
255
|
-
.option('--reasoning <level>', 'Reasoning level: low, medium, high (default: high)', 'high')
|
|
256
|
-
.action((payloadArg, cmd) => askGpt(payloadArg, cmd))
|
|
257
|
-
.addHelpText('after', `
|
|
258
|
-
Examples:
|
|
259
|
-
Ask a simple question:
|
|
260
|
-
eck-snapshot ask-gpt '{"objective": "What is 5+2?"}'
|
|
261
|
-
|
|
262
|
-
Request code changes with context:
|
|
263
|
-
eck-snapshot ask-gpt '{
|
|
264
|
-
"target_agent": "local_dev",
|
|
265
|
-
"task_id": "feature-123",
|
|
266
|
-
"payload": {
|
|
267
|
-
"objective": "Add error handling to login function",
|
|
268
|
-
"files_to_modify": [{"path": "src/auth.js", "action": "modify"}]
|
|
269
|
-
},
|
|
270
|
-
"post_execution_steps": {
|
|
271
|
-
"journal_entry": {
|
|
272
|
-
"type": "feat",
|
|
273
|
-
"scope": "auth",
|
|
274
|
-
"summary": "Add error handling"
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}' --verbose
|
|
278
|
-
|
|
279
|
-
Prerequisites:
|
|
280
|
-
1. Install Codex CLI: npm install -g @openai/codex
|
|
281
|
-
2. Login: codex login (requires ChatGPT Plus/Pro subscription)
|
|
282
|
-
3. The command automatically loads .eck project context
|
|
283
|
-
|
|
284
|
-
Authentication:
|
|
285
|
-
- Uses your existing 'codex login' credentials
|
|
286
|
-
- Auto-retries on authentication errors
|
|
287
|
-
- Supports ChatGPT Plus/Pro subscriptions
|
|
288
|
-
`);
|
|
289
|
-
|
|
290
248
|
// Project detection command
|
|
291
249
|
program
|
|
292
250
|
.command('detect')
|
|
@@ -344,16 +302,8 @@ Authentication:
|
|
|
344
302
|
const result = await executePrompt(prompt, options.continue);
|
|
345
303
|
console.log(JSON.stringify(result, null, 2));
|
|
346
304
|
} catch (error) {
|
|
347
|
-
console.
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const payload = (typeof prompt === 'string' && prompt.startsWith('{')) ? prompt : JSON.stringify({ objective: prompt });
|
|
351
|
-
const gptResult = await askGptService(payload, { verbose: false });
|
|
352
|
-
console.log(JSON.stringify(gptResult, null, 2));
|
|
353
|
-
} catch (gptError) {
|
|
354
|
-
console.error('Failed to execute prompt with both Claude and GPT:', gptError.message);
|
|
355
|
-
process.exit(1);
|
|
356
|
-
}
|
|
305
|
+
console.error(`Failed to execute prompt: ${error.message}`);
|
|
306
|
+
process.exit(1);
|
|
357
307
|
}
|
|
358
308
|
});
|
|
359
309
|
|
|
@@ -413,5 +363,12 @@ Authentication:
|
|
|
413
363
|
.argument('<filePaths...>', 'Space-separated paths to files')
|
|
414
364
|
.action(showFile);
|
|
415
365
|
|
|
366
|
+
// Doctor command (health check for manifests)
|
|
367
|
+
program
|
|
368
|
+
.command('doctor')
|
|
369
|
+
.description('Check project health and detect unfinished manifest stubs')
|
|
370
|
+
.argument('[repoPath]', 'Path to the repository', process.cwd())
|
|
371
|
+
.action(runDoctor);
|
|
372
|
+
|
|
416
373
|
program.parse(process.argv);
|
|
417
374
|
}
|
|
@@ -19,38 +19,7 @@ export async function generateAutoDocs() {
|
|
|
19
19
|
await fs.access(extensionsDir);
|
|
20
20
|
} catch (error) {
|
|
21
21
|
console.log(`Extensions directory not found at: ${extensionsDir}`);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Create the directory structure
|
|
25
|
-
await fs.mkdir(extensionsDir, { recursive: true });
|
|
26
|
-
|
|
27
|
-
// Create a sample gemini-extension.json file for demonstration
|
|
28
|
-
const sampleExtension = {
|
|
29
|
-
name: "sample-extension",
|
|
30
|
-
description: "Sample Gemini extension for demonstration",
|
|
31
|
-
commands: [
|
|
32
|
-
{
|
|
33
|
-
name: "sample-command",
|
|
34
|
-
description: "A sample command for testing auto-docs",
|
|
35
|
-
usage: "sample-command [options]",
|
|
36
|
-
examples: ["sample-command --help"]
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
tools: [
|
|
40
|
-
{
|
|
41
|
-
name: "sample-tool",
|
|
42
|
-
description: "A sample tool for testing auto-docs",
|
|
43
|
-
usage: "Use this tool for sample operations"
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
await fs.writeFile(
|
|
49
|
-
path.join(extensionsDir, 'sample-extension.json'),
|
|
50
|
-
JSON.stringify(sampleExtension, null, 2)
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
console.log('Created sample extension at:', path.join(extensionsDir, 'sample-extension.json'));
|
|
22
|
+
return;
|
|
54
23
|
}
|
|
55
24
|
|
|
56
25
|
// Read all JSON files in the extensions directory
|
|
@@ -8,12 +8,14 @@ import zlib from 'zlib';
|
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import ora from 'ora';
|
|
10
10
|
import micromatch from 'micromatch';
|
|
11
|
+
import chalk from 'chalk';
|
|
11
12
|
|
|
12
13
|
import {
|
|
13
14
|
parseSize, formatSize, matchesPattern, checkGitRepository,
|
|
14
15
|
scanDirectoryRecursively, loadGitignore, readFileWithSizeCheck,
|
|
15
16
|
generateDirectoryTree, loadConfig, displayProjectInfo, loadProjectEckManifest,
|
|
16
|
-
ensureSnapshotsInGitignore, initializeEckManifest
|
|
17
|
+
ensureSnapshotsInGitignore, initializeEckManifest, generateTimestamp,
|
|
18
|
+
SecretScanner
|
|
17
19
|
} from '../../utils/fileUtils.js';
|
|
18
20
|
import { detectProjectType, getProjectSpecificFiltering } from '../../utils/projectDetector.js';
|
|
19
21
|
import { estimateTokensWithPolynomial, generateTrainingCommand } from '../../utils/tokenEstimator.js';
|
|
@@ -131,11 +133,77 @@ import { generateEnhancedAIHeader } from '../../utils/aiHeader.js';
|
|
|
131
133
|
|
|
132
134
|
const gzip = promisify(zlib.gzip);
|
|
133
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Check if a path is a hidden directory/folder (starts with '.')
|
|
138
|
+
* This excludes all hidden folders like .git, .eck, .claude, .gemini from snapshots
|
|
139
|
+
* @param {string} filePath - File or directory path
|
|
140
|
+
* @returns {boolean} True if path is hidden
|
|
141
|
+
*/
|
|
142
|
+
function isHiddenPath(filePath) {
|
|
143
|
+
// Check if path or any parent directory starts with '.'
|
|
144
|
+
const parts = filePath.split('/');
|
|
145
|
+
return parts.some(part => part.startsWith('.'));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Scans the .eck directory for confidential files
|
|
150
|
+
* @param {string} projectPath - Path to the project
|
|
151
|
+
* @param {object} config - Configuration object
|
|
152
|
+
* @returns {Promise<string[]>} Array of confidential file paths
|
|
153
|
+
*/
|
|
154
|
+
async function scanEckForConfidentialFiles(projectPath, config) {
|
|
155
|
+
const eckPath = path.join(projectPath, '.eck');
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await fs.access(eckPath);
|
|
159
|
+
} catch {
|
|
160
|
+
return []; // .eck directory doesn't exist
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const result = await scanDirectoryRecursively(eckPath, config, projectPath, null, true);
|
|
164
|
+
return result.confidentialFiles || [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generates CLAUDE.md content with references to confidential files
|
|
169
|
+
* @param {string[]} confidentialFiles - Array of confidential file paths
|
|
170
|
+
* @param {string} repoPath - Path to the repository
|
|
171
|
+
* @returns {string} CLAUDE.md content
|
|
172
|
+
*/
|
|
173
|
+
function generateClaudeMdContent(confidentialFiles, repoPath) {
|
|
174
|
+
const content = [`# Project Access & Credentials Reference`, ``];
|
|
175
|
+
|
|
176
|
+
if (confidentialFiles.length === 0) {
|
|
177
|
+
content.push('No confidential files found in .eck directory.');
|
|
178
|
+
return content.join('\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
content.push('## Access & Credentials');
|
|
182
|
+
content.push('');
|
|
183
|
+
content.push('The following confidential files are available locally but not included in snapshots:');
|
|
184
|
+
content.push('');
|
|
185
|
+
|
|
186
|
+
for (const file of confidentialFiles) {
|
|
187
|
+
const absolutePath = path.join(repoPath, file);
|
|
188
|
+
const fileName = path.basename(file);
|
|
189
|
+
content.push(`- **${fileName}**: \`${absolutePath}\``);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
content.push('');
|
|
193
|
+
content.push('> **Note**: These files contain sensitive information and should only be accessed when needed.');
|
|
194
|
+
content.push('> They are excluded from snapshots for security reasons but can be referenced on demand.');
|
|
195
|
+
|
|
196
|
+
return content.join('\n');
|
|
197
|
+
}
|
|
198
|
+
|
|
134
199
|
async function getProjectFiles(projectPath, config) {
|
|
135
200
|
const isGitRepo = await checkGitRepository(projectPath);
|
|
136
201
|
if (isGitRepo) {
|
|
137
202
|
const { stdout } = await execa('git', ['ls-files'], { cwd: projectPath });
|
|
138
|
-
|
|
203
|
+
const gitFiles = stdout.split('\n').filter(Boolean);
|
|
204
|
+
// Filter out hidden directories/files (starting with '.')
|
|
205
|
+
const filteredFiles = gitFiles.filter(file => !isHiddenPath(file));
|
|
206
|
+
return filteredFiles;
|
|
139
207
|
}
|
|
140
208
|
return scanDirectoryRecursively(projectPath, config);
|
|
141
209
|
}
|
|
@@ -231,6 +299,7 @@ async function processProjectFiles(repoPath, options, config, projectType = null
|
|
|
231
299
|
ignoredFiles: 0,
|
|
232
300
|
totalSize: 0,
|
|
233
301
|
processedSize: 0,
|
|
302
|
+
secretsRedacted: 0,
|
|
234
303
|
errors: [],
|
|
235
304
|
skipReasons: new Map(),
|
|
236
305
|
skippedFilesDetails: new Map()
|
|
@@ -242,6 +311,11 @@ async function processProjectFiles(repoPath, options, config, projectType = null
|
|
|
242
311
|
console.log('🔍 Scanning repository...');
|
|
243
312
|
let allFiles = await getProjectFiles(repoPath, config);
|
|
244
313
|
|
|
314
|
+
// Filter the raw file list immediately so ignored files don't show up in the Tree
|
|
315
|
+
if (config.filesToIgnore && config.filesToIgnore.length > 0) {
|
|
316
|
+
allFiles = allFiles.filter(file => !matchesPattern(file, config.filesToIgnore));
|
|
317
|
+
}
|
|
318
|
+
|
|
245
319
|
if (options.profile) {
|
|
246
320
|
console.log(`Applying profile filter: '${options.profile}'...`);
|
|
247
321
|
allFiles = await applyProfileFilter(allFiles, options.profile, repoPath);
|
|
@@ -277,6 +351,13 @@ async function processProjectFiles(repoPath, options, config, projectType = null
|
|
|
277
351
|
progressBar.update(index + 1, { filename: normalizedPath.slice(0, 50) });
|
|
278
352
|
|
|
279
353
|
try {
|
|
354
|
+
// Skip all hidden directories and files (starting with '.')
|
|
355
|
+
if (isHiddenPath(normalizedPath)) {
|
|
356
|
+
stats.ignoredFiles++;
|
|
357
|
+
trackSkippedFile(normalizedPath, 'Hidden directories/files');
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
280
361
|
// Check if file should be ignored by directory patterns
|
|
281
362
|
if (config.dirsToIgnore.some(dir => normalizedPath.startsWith(dir))) {
|
|
282
363
|
stats.ignoredFiles++;
|
|
@@ -325,6 +406,17 @@ async function processProjectFiles(repoPath, options, config, projectType = null
|
|
|
325
406
|
}
|
|
326
407
|
|
|
327
408
|
let content = await readFileWithSizeCheck(fullPath, maxFileSize);
|
|
409
|
+
|
|
410
|
+
// Security scan for secrets
|
|
411
|
+
if (config.security?.scanForSecrets !== false) {
|
|
412
|
+
const scanResult = SecretScanner.redact(content, normalizedPath);
|
|
413
|
+
if (scanResult.found.length > 0) {
|
|
414
|
+
stats.secretsRedacted += scanResult.found.length;
|
|
415
|
+
console.log(chalk.yellow(`\n ⚠️ Security: Found ${scanResult.found.join(', ')} in ${normalizedPath}. Redacting...`));
|
|
416
|
+
content = scanResult.content;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
328
420
|
stats.includedFiles++;
|
|
329
421
|
stats.processedSize += fileStats.size;
|
|
330
422
|
|
|
@@ -399,8 +491,8 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
399
491
|
if (status) {
|
|
400
492
|
spinner.text = 'Unstaged changes detected. Auto-committing...';
|
|
401
493
|
await execa('git', ['add', '.'], { cwd: repoPath });
|
|
402
|
-
const
|
|
403
|
-
await execa('git', ['commit', '-m', `chore(snapshot): Auto-commit before snapshot [${
|
|
494
|
+
const commitTimestamp = generateTimestamp();
|
|
495
|
+
await execa('git', ['commit', '-m', `chore(snapshot): Auto-commit before snapshot [${commitTimestamp}]`], { cwd: repoPath });
|
|
404
496
|
spinner.info('Auto-commit complete.');
|
|
405
497
|
} else {
|
|
406
498
|
// No changes, do nothing. Logging this would be too verbose.
|
|
@@ -434,6 +526,15 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
434
526
|
...options // Command-line options have the final say
|
|
435
527
|
};
|
|
436
528
|
|
|
529
|
+
// If NOT in Junior Architect mode, hide JA-specific documentation to prevent context pollution
|
|
530
|
+
if (!options.withJa) {
|
|
531
|
+
if (!config.filesToIgnore) config.filesToIgnore = [];
|
|
532
|
+
config.filesToIgnore.push(
|
|
533
|
+
'COMMANDS_REFERENCE.md',
|
|
534
|
+
'codex_delegation_snapshot.md'
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
437
538
|
// Apply defaults for options that may not be provided via command line
|
|
438
539
|
if (!config.output) {
|
|
439
540
|
config.output = setupConfig.output?.defaultPath || './snapshots';
|
|
@@ -468,8 +569,8 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
468
569
|
process.chdir(processedRepoPath); // Go back to repo path for git hash and tree
|
|
469
570
|
|
|
470
571
|
try {
|
|
471
|
-
// --- Common Data ---
|
|
472
|
-
const timestamp =
|
|
572
|
+
// --- Common Data ---
|
|
573
|
+
const timestamp = generateTimestamp();
|
|
473
574
|
const repoName = path.basename(processedRepoPath);
|
|
474
575
|
const gitHash = await getGitCommitHash(processedRepoPath);
|
|
475
576
|
const fileExtension = options.format || config.defaultFormat || 'md';
|
|
@@ -531,6 +632,17 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
531
632
|
// Save git anchor for future delta updates
|
|
532
633
|
await saveGitAnchor(processedRepoPath);
|
|
533
634
|
|
|
635
|
+
// --- Generate CLAUDE.md with confidential file references ---
|
|
636
|
+
console.log('🔐 Scanning for confidential files in .eck directory...');
|
|
637
|
+
const confidentialFiles = await scanEckForConfidentialFiles(processedRepoPath, config);
|
|
638
|
+
|
|
639
|
+
if (confidentialFiles.length > 0) {
|
|
640
|
+
const claudeMdContent = generateClaudeMdContent(confidentialFiles, processedRepoPath);
|
|
641
|
+
const claudeMdPath = path.join(processedRepoPath, 'CLAUDE.md');
|
|
642
|
+
await fs.writeFile(claudeMdPath, claudeMdContent);
|
|
643
|
+
console.log(`📝 Generated CLAUDE.md with ${confidentialFiles.length} confidential file reference(s)`);
|
|
644
|
+
}
|
|
645
|
+
|
|
534
646
|
// --- Combined Report ---
|
|
535
647
|
console.log('\n✅ Snapshot generation complete!');
|
|
536
648
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
@@ -562,6 +674,13 @@ export async function createRepoSnapshot(repoPath, options) {
|
|
|
562
674
|
}
|
|
563
675
|
}
|
|
564
676
|
|
|
677
|
+
// Security Report Section
|
|
678
|
+
if (stats.secretsRedacted > 0) {
|
|
679
|
+
console.log('\n🔐 Security:');
|
|
680
|
+
console.log('---------------------------------');
|
|
681
|
+
console.log(chalk.yellow(` ⚠️ ${stats.secretsRedacted} secret(s) detected and redacted`));
|
|
682
|
+
}
|
|
683
|
+
|
|
565
684
|
// Excluded/Skipped Files Section
|
|
566
685
|
const hasExcludedContent = stats.excludedFiles > 0 || stats.binaryFiles > 0 || stats.oversizedFiles > 0 || stats.ignoredFiles > 0 || stats.errors.length > 0;
|
|
567
686
|
if (hasExcludedContent) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scans .eck directory for files containing [STUB] markers
|
|
7
|
+
*/
|
|
8
|
+
export async function runDoctor(repoPath = process.cwd()) {
|
|
9
|
+
const eckDir = path.join(repoPath, '.eck');
|
|
10
|
+
console.log(chalk.blue('🏥 Checking project health and manifest integrity...'));
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(eckDir);
|
|
14
|
+
} catch {
|
|
15
|
+
console.log(chalk.yellow('⚠️ .eck directory not found. Nothing to check.'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const stubFiles = [];
|
|
20
|
+
const scannedFiles = [];
|
|
21
|
+
|
|
22
|
+
async function scan(dir) {
|
|
23
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const fullPath = path.join(dir, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
await scan(fullPath);
|
|
28
|
+
} else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.json'))) {
|
|
29
|
+
scannedFiles.push(fullPath);
|
|
30
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
31
|
+
if (content.includes('[STUB:')) {
|
|
32
|
+
stubFiles.push({
|
|
33
|
+
path: path.relative(repoPath, fullPath),
|
|
34
|
+
type: 'STUB'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await scan(eckDir);
|
|
42
|
+
|
|
43
|
+
if (stubFiles.length === 0) {
|
|
44
|
+
console.log(chalk.green(`\n✅ All clear! Found ${scannedFiles.length} manifest files and no stubs.`));
|
|
45
|
+
} else {
|
|
46
|
+
console.log(chalk.red(`\n❌ Found ${stubFiles.length} files that need attention:`));
|
|
47
|
+
stubFiles.forEach(file => {
|
|
48
|
+
console.log(chalk.yellow(` - ${file.path} `) + chalk.gray('(contains [STUB] marker)'));
|
|
49
|
+
});
|
|
50
|
+
console.log(chalk.cyan('\n💡 Tip: Instruct your Coder agent to "Finalize these stubs by analyzing the code".'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Cross-platform tree-sitter check
|
|
54
|
+
try {
|
|
55
|
+
const ts = await import('tree-sitter');
|
|
56
|
+
console.log(chalk.green('✅ tree-sitter: Installed and loadable.'));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.log(chalk.yellow('ℹ️ tree-sitter: Not available (Skeleton mode will be limited for non-JS files).'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { getGitAnchor, getChangedFiles, getGitDiffOutput } from '../../utils/gitUtils.js';
|
|
5
5
|
import { loadSetupConfig } from '../../config.js';
|
|
6
|
-
import { readFileWithSizeCheck, parseSize, formatSize, matchesPattern, loadGitignore } from '../../utils/fileUtils.js';
|
|
6
|
+
import { readFileWithSizeCheck, parseSize, formatSize, matchesPattern, loadGitignore, generateTimestamp } from '../../utils/fileUtils.js';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -53,14 +53,14 @@ export async function updateSnapshot(repoPath, options) {
|
|
|
53
53
|
const templatePath = path.join(__dirname, '../../templates/update-prompt.template.md');
|
|
54
54
|
let header = await fs.readFile(templatePath, 'utf-8');
|
|
55
55
|
header = header.replace('{{anchor}}', anchor.substring(0, 7))
|
|
56
|
-
.replace('{{timestamp}}', new Date().
|
|
56
|
+
.replace('{{timestamp}}', new Date().toLocaleString())
|
|
57
57
|
.replace('{{fileList}}', fileList.join('\n'));
|
|
58
58
|
|
|
59
59
|
// Add Git Diff at the end for context
|
|
60
60
|
const diffOutput = await getGitDiffOutput(repoPath, anchor);
|
|
61
61
|
const diffSection = `\n--- GIT DIFF (For Context) ---\n\n\`\`\`diff\n${diffOutput}\n\`\`\``;
|
|
62
62
|
|
|
63
|
-
const outputFilename = `update_${
|
|
63
|
+
const outputFilename = `update_${generateTimestamp()}.md`;
|
|
64
64
|
const outputPath = path.join(repoPath, '.eck', 'snapshots', outputFilename);
|
|
65
65
|
|
|
66
66
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
package/src/config.js
CHANGED
|
@@ -16,6 +16,10 @@ export async function loadSetupConfig() {
|
|
|
16
16
|
const setupPath = path.join(__dirname, '..', 'setup.json');
|
|
17
17
|
const setupContent = await fs.readFile(setupPath, 'utf-8');
|
|
18
18
|
cachedConfig = JSON.parse(setupContent);
|
|
19
|
+
|
|
20
|
+
// Basic schema validation for critical fields
|
|
21
|
+
validateConfigSchema(cachedConfig);
|
|
22
|
+
|
|
19
23
|
return cachedConfig;
|
|
20
24
|
} catch (error) {
|
|
21
25
|
console.error('Error loading setup.json:', error.message);
|
|
@@ -23,6 +27,46 @@ export async function loadSetupConfig() {
|
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Validates critical config fields and warns if missing or invalid
|
|
32
|
+
*/
|
|
33
|
+
function validateConfigSchema(config) {
|
|
34
|
+
const warnings = [];
|
|
35
|
+
|
|
36
|
+
// Validate fileFiltering section
|
|
37
|
+
if (!config.fileFiltering) {
|
|
38
|
+
warnings.push('Missing "fileFiltering" section');
|
|
39
|
+
} else {
|
|
40
|
+
if (!Array.isArray(config.fileFiltering.filesToIgnore)) {
|
|
41
|
+
warnings.push('"fileFiltering.filesToIgnore" must be an array');
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(config.fileFiltering.dirsToIgnore)) {
|
|
44
|
+
warnings.push('"fileFiltering.dirsToIgnore" must be an array');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate aiInstructions section
|
|
49
|
+
if (!config.aiInstructions) {
|
|
50
|
+
warnings.push('Missing "aiInstructions" section');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Legacy support
|
|
54
|
+
if (!config.filesToIgnore || !Array.isArray(config.filesToIgnore)) {
|
|
55
|
+
warnings.push('filesToIgnore missing or not an array - using defaults');
|
|
56
|
+
config.filesToIgnore = DEFAULT_CONFIG.filesToIgnore;
|
|
57
|
+
}
|
|
58
|
+
if (!config.dirsToIgnore || !Array.isArray(config.dirsToIgnore)) {
|
|
59
|
+
warnings.push('dirsToIgnore missing or not an array - using defaults');
|
|
60
|
+
config.dirsToIgnore = DEFAULT_CONFIG.dirsToIgnore;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (warnings.length > 0) {
|
|
64
|
+
console.warn('\n⚠️ Config Validation Warnings:');
|
|
65
|
+
warnings.forEach(w => console.warn(` - ${w}`));
|
|
66
|
+
console.warn(' (Falling back to defaults for missing values where possible)\n');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
26
70
|
/**
|
|
27
71
|
* Loads and merges all profiles (local-first).
|
|
28
72
|
*/
|