repo-cloak-cli 1.3.2 → 1.3.4

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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "repo-cloak-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "šŸŽ­ Selectively extract and anonymize files from repositories. Perfect for sharing code with AI agents without exposing proprietary details.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "repo-cloak": "./bin/repo-cloak.js",
9
- "cloak": "./bin/repo-cloak.js"
8
+ "repo-cloak": "bin/repo-cloak.js",
9
+ "cloak": "bin/repo-cloak.js"
10
10
  },
11
11
  "scripts": {
12
12
  "start": "node bin/repo-cloak.js",
@@ -32,12 +32,13 @@
32
32
  "license": "MIT",
33
33
  "repository": {
34
34
  "type": "git",
35
- "url": "https://github.com/iamshz97/repo-cloak.git"
35
+ "url": "git+https://github.com/iamshz97/repo-cloak.git"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=18.0.0"
39
39
  },
40
40
  "dependencies": {
41
+ "@inquirer/checkbox": "^5.1.2",
41
42
  "chalk": "^5.3.0",
42
43
  "commander": "^12.1.0",
43
44
  "figlet": "^1.7.0",
@@ -48,4 +49,4 @@
48
49
  "devDependencies": {
49
50
  "vitest": "^4.0.18"
50
51
  }
51
- }
52
+ }
package/src/cli.js CHANGED
@@ -32,6 +32,8 @@ program
32
32
  .option('-s, --source <path>', 'Source directory (default: current directory)')
33
33
  .option('-d, --dest <path>', 'Destination directory')
34
34
  .option('-f, --force', 'Force pull all files (skip prompts, requires existing mapping)')
35
+ .option('-c, --commit <hash...>', 'Extract files changed in specific commits')
36
+ .option('-l, --list-commits [count]', 'List and select from recent commits (default: 10)')
35
37
  .option('-q, --quiet', 'Minimal output')
36
38
  .action(async (options) => {
37
39
  await showBanner();
@@ -7,10 +7,10 @@
7
7
  import ora from 'ora';
8
8
  import chalk from 'chalk';
9
9
  import inquirer from 'inquirer';
10
- import { existsSync, mkdirSync } from 'fs';
11
- import { resolve, relative } from 'path';
10
+ import { existsSync, writeFileSync } from 'fs';
11
+ import { resolve, relative, join } from 'path';
12
12
 
13
- import { selectFiles } from '../ui/fileSelector.js';
13
+ import { checkboxTreeSelectFilesOnly } from '../ui/treeCheckboxSelector.js';
14
14
  import {
15
15
  promptSourceDirectory,
16
16
  promptDestinationDirectory,
@@ -20,12 +20,15 @@ import {
20
20
  } from '../ui/prompts.js';
21
21
  import { showSuccess, showError, showInfo } from '../ui/banner.js';
22
22
  import { getAllFiles } from '../core/scanner.js';
23
+ import { scanFilesForSecrets } from '../core/secrets.js';
24
+ import { getAgentsMarkdown } from '../core/agents-template.js';
23
25
 
24
26
  import { copyFiles } from '../core/copier.js';
25
27
  import { createAnonymizer } from '../core/anonymizer.js';
26
28
  import { createMapping, saveMapping, loadRawMapping, mergeMapping, hasMapping, decryptMapping } from '../core/mapper.js';
27
29
  import { getOrCreateSecret, hasSecret, decryptReplacements } from '../core/crypto.js';
28
- import { isGitRepo, getChangedFiles } from '../core/git.js';
30
+ import { isGitRepo, getChangedFiles, getRecentCommits, getFilesChangedInCommits } from '../core/git.js';
31
+
29
32
 
30
33
  export async function pull(options = {}) {
31
34
  try {
@@ -38,6 +41,23 @@ export async function pull(options = {}) {
38
41
  // Step 1: Check if current directory is already a cloaked directory
39
42
  const currentDir = process.cwd();
40
43
 
44
+ // ── Step 2: Resolve sourceDir first (always ask source before destination) ──
45
+ if (!sourceDir) {
46
+ sourceDir = options.source
47
+ ? resolve(options.source)
48
+ : await promptSourceDirectory();
49
+ }
50
+
51
+ if (!existsSync(sourceDir)) {
52
+ showError(`Source directory does not exist: ${sourceDir}`);
53
+ return;
54
+ }
55
+
56
+ if (!options.force) {
57
+ console.log(chalk.dim(` Source: ${sourceDir}\n`));
58
+ }
59
+
60
+ // ── Step 3: Resolve destDir ──
41
61
  if (hasMapping(currentDir) && !options.dest) {
42
62
  // Running from inside an existing cloaked directory - auto-detect!
43
63
  destDir = currentDir;
@@ -59,18 +79,18 @@ export async function pull(options = {}) {
59
79
  {
60
80
  type: 'list',
61
81
  name: 'mode',
62
- message: 'What would you like to do?',
82
+ message: 'This looks like a folder you already used — what do you want to do?',
63
83
  choices: [
64
84
  {
65
- name: 'Quick Add - Use existing replacements and add more files',
85
+ name: 'Quick Add — keep existing settings and add more files',
66
86
  value: 'quick'
67
87
  },
68
88
  {
69
- name: 'Add More Replacements - Add files with additional anonymization',
89
+ name: 'Add More Replacements — add files with extra anonymization',
70
90
  value: 'extend'
71
91
  },
72
92
  {
73
- name: 'Fresh Start - Choose new destination',
93
+ name: 'Fresh Start — pick a new output folder',
74
94
  value: 'fresh'
75
95
  }
76
96
  ]
@@ -116,33 +136,9 @@ export async function pull(options = {}) {
116
136
  console.log('');
117
137
  }
118
138
  }
119
-
120
- // Try to get original source path
121
- if (existingMapping.encrypted && hasSecret()) {
122
- const secret = getOrCreateSecret();
123
- try {
124
- const decrypted = decryptMapping(existingMapping, secret);
125
- if (decrypted.source?.path && existsSync(decrypted.source.path)) {
126
- sourceDir = decrypted.source.path;
127
- if (!options.force) {
128
- console.log(chalk.dim(` Source: ${sourceDir}\n`));
129
- }
130
- }
131
- } catch (err) {
132
- // Source path couldn't be decrypted, will prompt
133
- }
134
- } else if (!existingMapping.encrypted && existingMapping.source?.path) {
135
- // Not encrypted - use directly
136
- if (existsSync(existingMapping.source.path)) {
137
- sourceDir = existingMapping.source.path;
138
- if (!options.force) {
139
- console.log(chalk.dim(` Source: ${sourceDir}\n`));
140
- }
141
- }
142
- }
143
139
  }
144
140
  } else {
145
- // Not running from a cloaked directory - ask for destination
141
+ // Not running from a cloaked directory
146
142
  if (options.force) {
147
143
  showError('Force flag can only be used within an existing cloaked directory.');
148
144
  return;
@@ -172,11 +168,11 @@ export async function pull(options = {}) {
172
168
  {
173
169
  type: 'list',
174
170
  name: 'mode',
175
- message: 'What would you like to do?',
171
+ message: 'This looks like a folder you already used — what do you want to do?',
176
172
  choices: [
177
- { name: 'Quick Add - Use existing replacements and add more files', value: 'quick' },
178
- { name: 'Add More Replacements - Add files with additional anonymization', value: 'extend' },
179
- { name: 'Fresh Start - Choose new destination', value: 'fresh' }
173
+ { name: 'Quick Add — keep existing settings and add more files', value: 'quick' },
174
+ { name: 'Add More Replacements — add files with extra anonymization', value: 'extend' },
175
+ { name: 'Fresh Start — pick a new output folder', value: 'fresh' }
180
176
  ]
181
177
  }
182
178
  ]);
@@ -218,53 +214,11 @@ export async function pull(options = {}) {
218
214
  console.log('');
219
215
  }
220
216
  }
221
-
222
-
223
-
224
- // Try to get original source path
225
- if (existingMapping.encrypted && hasSecret()) {
226
- const secret = getOrCreateSecret();
227
- try {
228
- const decrypted = decryptMapping(existingMapping, secret);
229
- if (decrypted.source?.path && existsSync(decrypted.source.path)) {
230
- sourceDir = decrypted.source.path;
231
- if (!options.force) {
232
- console.log(chalk.dim(` Source: ${sourceDir}\n`));
233
- }
234
- }
235
- } catch (err) {
236
- // Source path couldn't be decrypted, will prompt
237
- }
238
- } else if (!existingMapping.encrypted && existingMapping.source?.path) {
239
- if (existsSync(existingMapping.source.path)) {
240
- sourceDir = existingMapping.source.path;
241
- if (!options.force) {
242
- console.log(chalk.dim(` Source: ${sourceDir}\n`));
243
- }
244
- }
245
- }
246
217
  }
247
218
  }
248
219
  }
249
220
 
250
- // ...
251
-
252
- // Step 3: Get source directory if not already determined
253
- if (!sourceDir) {
254
- sourceDir = options.source
255
- ? resolve(options.source)
256
- : await promptSourceDirectory();
257
- }
258
-
259
- if (!existsSync(sourceDir)) {
260
- showError(`Source directory does not exist: ${sourceDir}`);
261
- return;
262
- }
263
-
264
- if (!options.force) {
265
- console.log(chalk.dim(` Source: ${sourceDir}\n`));
266
- }
267
-
221
+ // ...\n
268
222
  // Step 4: Select files (Check for Git integration first)
269
223
  let selectedFiles = [];
270
224
  let useGitFiles = false;
@@ -358,67 +312,250 @@ export async function pull(options = {}) {
358
312
  showError('No files found in existing mapping.');
359
313
  return;
360
314
  }
315
+ } else if (!isGitRepo(sourceDir) && (options.commit || options.listCommits !== undefined)) {
316
+ showError('Source directory is not a Git repository. Cannot use commit flags.');
317
+ return;
361
318
  } else if (isGitRepo(sourceDir)) {
362
- const { useGit } = await inquirer.prompt([
363
- {
364
- type: 'confirm',
365
- name: 'useGit',
366
- message: 'Git repository detected. Do you want to select changed/added files?',
367
- default: false
319
+ if (options.commit) {
320
+ const spinner = ora('Fetching files from commits...').start();
321
+ const commitFiles = await getFilesChangedInCommits(sourceDir, options.commit);
322
+ spinner.stop();
323
+
324
+ if (commitFiles.length === 0) {
325
+ showError('No files found in the specified commits.');
326
+ return;
327
+ }
328
+
329
+ // Filter to absolute paths and exist check
330
+ const validCommitFiles = commitFiles
331
+ .map(f => resolve(sourceDir, f))
332
+ .filter(f => existsSync(f));
333
+
334
+ if (validCommitFiles.length > 0) {
335
+ console.log(chalk.green(` Found ${validCommitFiles.length} files in specified commits.`));
336
+ selectedFiles = validCommitFiles;
337
+ useGitFiles = true;
338
+ } else {
339
+ showError('None of the files from the specified commits exist locally.');
340
+ return;
368
341
  }
369
- ]);
342
+ } else if (options.listCommits !== undefined) {
343
+ const count = options.listCommits === true ? 10 : parseInt(options.listCommits, 10) || 10;
344
+ const commits = await getRecentCommits(sourceDir, count);
370
345
 
371
- if (useGit) {
372
- const spinner = ora('Scanning changed files...').start();
373
- const gitFiles = await getChangedFiles(sourceDir);
346
+ if (commits.length === 0) {
347
+ showError('No commits found in the repository.');
348
+ return;
349
+ }
350
+
351
+ const { selectedCommits } = await inquirer.prompt([
352
+ {
353
+ type: 'checkbox',
354
+ name: 'selectedCommits',
355
+ message: 'Which commits do you want to pull files from? (space to select, enter to confirm):',
356
+ choices: commits.map(c => ({
357
+ name: `${c.hash} - ${c.message}`,
358
+ value: c.hash
359
+ }))
360
+ }
361
+ ]);
362
+
363
+ if (selectedCommits.length === 0) {
364
+ showError('No commits selected. Aborting.');
365
+ return;
366
+ }
367
+
368
+ const spinner = ora('Fetching files from selected commits...').start();
369
+ const commitFiles = await getFilesChangedInCommits(sourceDir, selectedCommits);
374
370
  spinner.stop();
371
+
372
+ if (commitFiles.length === 0) {
373
+ showError('No files found in the selected commits.');
374
+ return;
375
+ }
376
+
377
+ const validCommitFiles = commitFiles
378
+ .map(f => resolve(sourceDir, f))
379
+ .filter(f => existsSync(f));
380
+
381
+ if (validCommitFiles.length > 0) {
382
+ console.log(chalk.green(` Found ${validCommitFiles.length} files in selected commits.`));
383
+ selectedFiles = validCommitFiles;
384
+ useGitFiles = true;
385
+ } else {
386
+ showError('None of the files from the selected commits exist locally.');
387
+ return;
388
+ }
389
+ } else {
390
+ const { gitAction } = await inquirer.prompt([
391
+ {
392
+ type: 'list',
393
+ name: 'gitAction',
394
+ message: 'This is a Git repo — how do you want to pick files?',
395
+ choices: [
396
+ { name: 'Uncommitted changes (working directory)', value: 'uncommitted' },
397
+ { name: 'Files from recent commits', value: 'commits' },
398
+ { name: 'Specific commit ID', value: 'commit_id' },
399
+ { name: 'Manual selection (bypasses git)', value: 'manual' }
400
+ ]
401
+ }
402
+ ]);
375
403
 
376
- if (gitFiles.length === 0) {
377
- console.log(chalk.yellow(' No changed or added files found in Git status.'));
378
- const { fallback } = await inquirer.prompt([
404
+ if (gitAction === 'commit_id') {
405
+ const { commitHash } = await inquirer.prompt([
379
406
  {
380
- type: 'confirm',
381
- name: 'fallback',
382
- message: 'Do you want to manually select files instead?',
383
- default: true
407
+ type: 'input',
408
+ name: 'commitHash',
409
+ message: 'Paste or type the commit hash you want to pull files from:',
410
+ validate: input => input.trim() !== '' ? true : 'Commit hash cannot be empty.'
384
411
  }
385
412
  ]);
386
413
 
387
- if (!fallback) {
388
- return;
389
- }
390
- } else {
391
- // Filter to absolute paths and exist check
392
- const validGitFiles = gitFiles
393
- .map(f => resolve(sourceDir, f))
394
- .filter(f => existsSync(f));
414
+ const spinner = ora(`Fetching files from commit ${commitHash}...`).start();
415
+ const commitFiles = await getFilesChangedInCommits(sourceDir, [commitHash]);
416
+ spinner.stop();
395
417
 
396
- if (validGitFiles.length > 0) {
397
- console.log(chalk.green(` Found ${validGitFiles.length} changed files.`));
418
+ if (commitFiles.length === 0) {
419
+ console.log(chalk.yellow(` No files found in commit ${commitHash} or invalid commit ID.`));
420
+ } else {
421
+ const validCommitFiles = commitFiles
422
+ .map(f => resolve(sourceDir, f))
423
+ .filter(f => existsSync(f));
424
+
425
+ if (validCommitFiles.length > 0) {
426
+ const allowedPaths = new Set(validCommitFiles);
427
+ const allowedDirs = new Set();
428
+ for (const file of validCommitFiles) {
429
+ let dir = resolve(file, '..');
430
+ while (dir && dir !== sourceDir && dir !== '/' && dir !== resolve(dir, '..')) {
431
+ allowedDirs.add(dir);
432
+ dir = resolve(dir, '..');
433
+ }
434
+ }
435
+ allowedDirs.add(sourceDir);
436
+
437
+ const pickedFiles = await checkboxTreeSelectFilesOnly({
438
+ root: sourceDir,
439
+ message: `Found ${validCommitFiles.length} files in commit ${commitHash} — uncheck any you want to skip:`,
440
+ precheck: validCommitFiles,
441
+ ignore: (fullPath, name) => {
442
+ return !(allowedPaths.has(fullPath) || allowedDirs.has(fullPath));
443
+ }
444
+ });
398
445
 
399
- // Let user confirm/deselect git files
400
- const { confirmGitFiles } = await inquirer.prompt([
446
+ if (pickedFiles.length > 0) {
447
+ selectedFiles = pickedFiles;
448
+ useGitFiles = true;
449
+ } else {
450
+ console.log(chalk.yellow(' No files selected.'));
451
+ }
452
+ } else {
453
+ console.log(chalk.yellow(` None of the files from the specified commit exist locally.`));
454
+ }
455
+ }
456
+ } else if (gitAction === 'commits') {
457
+ const commits = await getRecentCommits(sourceDir, 10);
458
+ if (commits.length === 0) {
459
+ console.log(chalk.yellow(' No commits found in the repository.'));
460
+ } else {
461
+ const { selectedCommits } = await inquirer.prompt([
401
462
  {
402
463
  type: 'checkbox',
403
- name: 'confirmGitFiles',
404
- message: 'Select changed files to extract:',
405
- choices: validGitFiles.map(f => ({
406
- name: relative(sourceDir, f),
407
- value: f,
408
- checked: true
464
+ name: 'selectedCommits',
465
+ message: 'Which commits do you want to pull files from? (space to select, enter to confirm):',
466
+ choices: commits.map(c => ({
467
+ name: `${c.hash} - ${c.message}`,
468
+ value: c.hash
409
469
  }))
410
470
  }
411
471
  ]);
412
472
 
413
- selectedFiles = confirmGitFiles;
414
- useGitFiles = true;
473
+ if (selectedCommits.length > 0) {
474
+ const spinner = ora('Fetching files from selected commits...').start();
475
+ const commitFiles = await getFilesChangedInCommits(sourceDir, selectedCommits);
476
+ spinner.stop();
477
+
478
+ const validCommitFiles = commitFiles
479
+ .map(f => resolve(sourceDir, f))
480
+ .filter(f => existsSync(f));
481
+
482
+ if (validCommitFiles.length > 0) {
483
+ const allowedPaths = new Set(validCommitFiles);
484
+ const allowedDirs = new Set();
485
+ for (const file of validCommitFiles) {
486
+ let dir = resolve(file, '..');
487
+ while (dir && dir !== sourceDir && dir !== '/' && dir !== resolve(dir, '..')) {
488
+ allowedDirs.add(dir);
489
+ dir = resolve(dir, '..');
490
+ }
491
+ }
492
+ allowedDirs.add(sourceDir);
493
+
494
+ const pickedFiles = await checkboxTreeSelectFilesOnly({
495
+ root: sourceDir,
496
+ message: `Found ${validCommitFiles.length} files across selected commits — uncheck any you want to skip:`,
497
+ precheck: validCommitFiles,
498
+ ignore: (fullPath, name) => {
499
+ return !(allowedPaths.has(fullPath) || allowedDirs.has(fullPath));
500
+ }
501
+ });
502
+
503
+ if (pickedFiles.length > 0) {
504
+ selectedFiles = pickedFiles;
505
+ useGitFiles = true;
506
+ } else {
507
+ console.log(chalk.yellow(' No files selected.'));
508
+ }
509
+ } else {
510
+ console.log(chalk.yellow(' None of the files from the selected commits exist locally.'));
511
+ }
512
+ }
513
+ }
514
+ } else if (gitAction === 'uncommitted') {
515
+ const spinner = ora('Scanning uncommitted files...').start();
516
+ const gitFiles = await getChangedFiles(sourceDir);
517
+ spinner.stop();
518
+
519
+ if (gitFiles.length === 0) {
520
+ console.log(chalk.yellow(' No uncommitted files found in Git status.'));
521
+ } else {
522
+ const validGitFiles = gitFiles
523
+ .map(f => resolve(sourceDir, f))
524
+ .filter(f => existsSync(f));
525
+
526
+ if (validGitFiles.length > 0) {
527
+ console.log(chalk.green(` Found ${validGitFiles.length} changed files.`));
528
+
529
+ const allowedPaths = new Set(validGitFiles);
530
+ const allowedDirs = new Set();
531
+ for (const file of validGitFiles) {
532
+ let dir = resolve(file, '..');
533
+ while (dir && dir !== sourceDir && dir !== '/' && dir !== resolve(dir, '..')) {
534
+ allowedDirs.add(dir);
535
+ dir = resolve(dir, '..');
536
+ }
537
+ }
538
+ allowedDirs.add(sourceDir);
539
+
540
+ const pickedFiles = await checkboxTreeSelectFilesOnly({
541
+ root: sourceDir,
542
+ message: 'Which changed files do you want to extract? (space to deselect):',
543
+ precheck: validGitFiles,
544
+ ignore: (fullPath, name) => {
545
+ return !(allowedPaths.has(fullPath) || allowedDirs.has(fullPath));
546
+ }
547
+ });
548
+
549
+ selectedFiles = pickedFiles;
550
+ useGitFiles = true;
551
+ }
415
552
  }
416
553
  }
417
554
  }
418
555
  }
419
556
 
420
557
  if (!options.force && (!useGitFiles || selectedFiles.length === 0)) {
421
- selectedFiles = await selectFiles(sourceDir);
558
+ selectedFiles = await checkboxTreeSelectFilesOnly({ root: sourceDir });
422
559
  }
423
560
 
424
561
  if (selectedFiles.length === 0) {
@@ -430,6 +567,48 @@ export async function pull(options = {}) {
430
567
  console.log(chalk.green(`\nāœ“ Selected ${selectedFiles.length} files\n`));
431
568
  }
432
569
 
570
+ // Step 4.5: Scan for Secrets
571
+ if (!options.force) {
572
+ const scanSpinner = ora('Scanning selected files for sensitive data...').start();
573
+ const secretFindings = await scanFilesForSecrets(selectedFiles);
574
+ scanSpinner.stop();
575
+
576
+ if (secretFindings.length > 0) {
577
+ console.log(chalk.red.bold('\nāš ļø WARNING: POTENTIAL SENSITIVE DATA DETECTED āš ļø\n'));
578
+
579
+ // Group by file to present a cleaner list
580
+ const findingsByFile = secretFindings.reduce((acc, finding) => {
581
+ const relPath = relative(sourceDir, finding.file);
582
+ if (!acc[relPath]) acc[relPath] = new Set();
583
+ acc[relPath].add(`${finding.type} (Line ${finding.line})`);
584
+ return acc;
585
+ }, {});
586
+
587
+ for (const [file, secrets] of Object.entries(findingsByFile)) {
588
+ console.log(chalk.yellow(` ${file}:`));
589
+ for (const secret of secrets) {
590
+ console.log(chalk.dim(` - ${secret}`));
591
+ }
592
+ }
593
+ console.log('');
594
+
595
+ const { proceedWithSecrets } = await inquirer.prompt([
596
+ {
597
+ type: 'confirm',
598
+ name: 'proceedWithSecrets',
599
+ message: 'Some sensitive data was detected — are you sure you want to continue?',
600
+ default: false
601
+ }
602
+ ]);
603
+
604
+ if (!proceedWithSecrets) {
605
+ showInfo('Operation cancelled to protect sensitive data.');
606
+ return;
607
+ }
608
+ console.log('');
609
+ }
610
+ }
611
+
433
612
  // Step 5: Handle replacements based on mode
434
613
  let replacements = [...existingReplacements];
435
614
 
@@ -445,7 +624,7 @@ export async function pull(options = {}) {
445
624
  {
446
625
  type: 'confirm',
447
626
  name: 'addMore',
448
- message: 'Add additional replacements?',
627
+ message: 'Want to add any more replacements?',
449
628
  default: false
450
629
  }
451
630
  ]);
@@ -479,12 +658,6 @@ export async function pull(options = {}) {
479
658
  }
480
659
  }
481
660
 
482
- // Step 7: Create destination directory
483
- if (!existsSync(destDir)) {
484
- mkdirSync(destDir, { recursive: true });
485
- console.log(chalk.dim(` Created directory: ${destDir}`));
486
- }
487
-
488
661
  // Step 8: Copy and anonymize files
489
662
  const spinner = ora('Copying and anonymizing files...').start();
490
663
 
@@ -520,6 +693,13 @@ export async function pull(options = {}) {
520
693
  });
521
694
  }
522
695
 
696
+ // Step 8.5: Write AGENTS.md into the cloaked workspace
697
+ if (!existingMapping) {
698
+ const agentsPath = join(destDir, 'AGENTS.md');
699
+ writeFileSync(agentsPath, getAgentsMarkdown(), 'utf-8');
700
+ console.log(chalk.cyan(` šŸ¤– AGENTS.md created for AI agent context`));
701
+ }
702
+
523
703
  // Step 9: Prepare new file mappings
524
704
  const newFiles = selectedFiles.map(f => {
525
705
  const originalPath = relative(sourceDir, f);
@@ -0,0 +1,37 @@
1
+ /**
2
+ * AGENTS.md Template
3
+ * Auto-generated file placed inside cloaked workspaces to instruct AI agents
4
+ * about the nature of the isolated, anonymized repository.
5
+ */
6
+
7
+ export function getAgentsMarkdown() {
8
+ return `# AI Agent Guidelines for This Repository
9
+
10
+ ## Important Context
11
+
12
+ This is an **isolated, partially cloned repository** created using [Repo-Cloak](https://www.npmjs.com/package/repo-cloak-cli). It contains only a subset of files selectively pulled from a larger enterprise codebase for the purpose of working with AI coding tools in a safe, anonymized environment.
13
+
14
+ ## What You Need to Know
15
+
16
+ - **Partial repository.** This workspace does NOT contain the full codebase. Only specific files were selected and pulled by the developer. Missing files, references, or imports pointing to modules not present in this workspace are expected.
17
+ - **Anonymized identifiers.** Company names, project names, and other proprietary identifiers have been systematically replaced with anonymized alternatives. Do not attempt to guess or restore the original names.
18
+ - **Preserve the existing structure.** All file paths, folder hierarchies, and naming conventions in this workspace mirror the original repository structure. Maintain this structure in any changes you make.
19
+ - **Only modify files present in this workspace.** Do not create files outside the directories already present unless explicitly asked to by the user. Your changes will be pushed back into the original repository, so they must align with the existing structure.
20
+
21
+ ## If You Need More Context
22
+
23
+ If you encounter a situation where:
24
+ - A referenced file, module, or dependency is missing
25
+ - You cannot determine the correct interface, type, or contract
26
+ - The available code is insufficient to complete the task confidently
27
+
28
+ **Ask the user to pull additional files** using Repo-Cloak. They can selectively add more files to this workspace without starting over. Do not guess or fabricate missing implementations.
29
+
30
+ ## Working With This Repository
31
+
32
+ 1. Treat this workspace as a real project. The structure and patterns are genuine.
33
+ 2. Write code that follows the conventions and patterns you observe in the existing files.
34
+ 3. Your changes will be de-anonymized and merged back into the original codebase automatically.
35
+ 4. Focus on the task the user has given you. The files present are the files they have chosen to expose for this purpose.
36
+ `;
37
+ }