@hatem427/code-guard-ci 3.5.7 → 3.5.9
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/config/angular.config.ts +66 -22
- package/config/guidelines.config.ts +15 -2
- package/dist/config/angular.config.d.ts.map +1 -1
- package/dist/config/angular.config.js +97 -22
- package/dist/config/angular.config.js.map +1 -1
- package/dist/config/guidelines.config.d.ts.map +1 -1
- package/dist/config/guidelines.config.js +13 -2
- package/dist/config/guidelines.config.js.map +1 -1
- package/dist/scripts/cli.js +101 -0
- package/dist/scripts/cli.js.map +1 -1
- package/dist/scripts/config-generators/ai-config-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/ai-config-generator.js +108 -0
- package/dist/scripts/config-generators/ai-config-generator.js.map +1 -1
- package/dist/scripts/utils/report-generator.d.ts.map +1 -1
- package/dist/scripts/utils/report-generator.js +0 -4
- package/dist/scripts/utils/report-generator.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli.ts +121 -0
- package/scripts/config-generators/ai-config-generator.ts +133 -0
- package/scripts/utils/report-generator.ts +0 -5
package/scripts/cli.ts
CHANGED
|
@@ -453,6 +453,9 @@ function initProject(): void {
|
|
|
453
453
|
saveManifest(cwd, manifest);
|
|
454
454
|
console.log(` ${c.green('✓')} Saved manifest to .code-guardian/manifest.json\n`);
|
|
455
455
|
|
|
456
|
+
// ── Update .gitignore for .code-guardian/ ────────────────────────────────
|
|
457
|
+
ensureCodeGuardianGitignore(cwd);
|
|
458
|
+
|
|
456
459
|
// ── Done! ─────────────────────────────────────────────────────────────────
|
|
457
460
|
console.log(c.bold(c.green('═══════════════════════════════════════════════════════════')));
|
|
458
461
|
console.log(c.bold(c.green(' ✅ Code Guardian initialized successfully!')));
|
|
@@ -483,6 +486,124 @@ function initProject(): void {
|
|
|
483
486
|
console.log('');
|
|
484
487
|
}
|
|
485
488
|
|
|
489
|
+
// ── .gitignore management for .code-guardian/ ───────────────────────────────
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Best-practice .gitignore management for .code-guardian/.
|
|
493
|
+
*
|
|
494
|
+
* SENSITIVE — never commit (passwords, generated reports):
|
|
495
|
+
* .code-guardian/bypass-password.hash
|
|
496
|
+
* .code-guardian/admin-password.hash
|
|
497
|
+
* .code-guardian/CODE_GUARDIAN_REPORT.md
|
|
498
|
+
*
|
|
499
|
+
* TRACKED — must be committed (team config, audit trail):
|
|
500
|
+
* .code-guardian/custom-rules.json
|
|
501
|
+
* .code-guardian/structure-rules.json
|
|
502
|
+
* .code-guardian/naming-rules.json
|
|
503
|
+
* .code-guardian/manifest.json
|
|
504
|
+
* .code-guardian/bypass-log.json
|
|
505
|
+
*
|
|
506
|
+
* STRATEGY:
|
|
507
|
+
* If `.code-guardian` is broadly ignored → replace with surgical pattern:
|
|
508
|
+
* .code-guardian/* (ignore everything inside …)
|
|
509
|
+
* !.code-guardian/custom-rules.json (… except tracked files)
|
|
510
|
+
* …
|
|
511
|
+
* Always add sensitive files to ignore if not present.
|
|
512
|
+
*/
|
|
513
|
+
function ensureCodeGuardianGitignore(cwd: string): void {
|
|
514
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
515
|
+
|
|
516
|
+
const sensitiveFiles = [
|
|
517
|
+
'.code-guardian/bypass-password.hash',
|
|
518
|
+
'.code-guardian/admin-password.hash',
|
|
519
|
+
'.code-guardian/CODE_GUARDIAN_REPORT.md',
|
|
520
|
+
];
|
|
521
|
+
|
|
522
|
+
const trackedFiles = [
|
|
523
|
+
'.code-guardian/custom-rules.json',
|
|
524
|
+
'.code-guardian/structure-rules.json',
|
|
525
|
+
'.code-guardian/naming-rules.json',
|
|
526
|
+
'.code-guardian/manifest.json',
|
|
527
|
+
'.code-guardian/bypass-log.json',
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
let content = fs.existsSync(gitignorePath)
|
|
531
|
+
? fs.readFileSync(gitignorePath, 'utf-8')
|
|
532
|
+
: '';
|
|
533
|
+
|
|
534
|
+
let changed = false;
|
|
535
|
+
const messages: string[] = [];
|
|
536
|
+
|
|
537
|
+
// ── 1. Replace broad .code-guardian ignore with surgical pattern ─────────
|
|
538
|
+
const broadPattern = /^\.code-guardian\/?$/m;
|
|
539
|
+
const broadMatch = broadPattern.exec(content);
|
|
540
|
+
if (broadMatch) {
|
|
541
|
+
const surgicalLines = [
|
|
542
|
+
'.code-guardian/*',
|
|
543
|
+
...trackedFiles.map(f => `!${f}`),
|
|
544
|
+
];
|
|
545
|
+
content = content.replace(broadPattern, surgicalLines.join('\n'));
|
|
546
|
+
changed = true;
|
|
547
|
+
messages.push(
|
|
548
|
+
` ${c.green('✓')} .gitignore: replaced broad \`.code-guardian\` with surgical pattern:\n` +
|
|
549
|
+
surgicalLines.map(l =>
|
|
550
|
+
` ${l.startsWith('!') ? c.green(l) + ' (tracked)' : c.dim(l) + ' (ignored)'}`
|
|
551
|
+
).join('\n')
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ── 2. Always ensure sensitive files are explicitly ignored ──────────────
|
|
556
|
+
const toIgnore = sensitiveFiles.filter(f => !content.includes(f));
|
|
557
|
+
if (toIgnore.length > 0) {
|
|
558
|
+
content += [
|
|
559
|
+
'',
|
|
560
|
+
'# Code Guardian — sensitive/generated (do not commit)',
|
|
561
|
+
...toIgnore,
|
|
562
|
+
'',
|
|
563
|
+
].join('\n');
|
|
564
|
+
changed = true;
|
|
565
|
+
messages.push(
|
|
566
|
+
` ${c.green('✓')} .gitignore: sensitive files added:\n` +
|
|
567
|
+
toIgnore.map(f => ` ${c.dim(f)}`).join('\n')
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ── 3. Ensure tracked files are negated if the dir is still pattern-ignored
|
|
572
|
+
if (!broadMatch) {
|
|
573
|
+
// Dir wasn't broadly ignored — check if individual tracked files are ignored
|
|
574
|
+
// and add negations for any that are
|
|
575
|
+
const negationsNeeded = trackedFiles
|
|
576
|
+
.filter(f => {
|
|
577
|
+
const r = spawnSync('git', ['check-ignore', '--no-index', '-q', f], {
|
|
578
|
+
cwd, stdio: 'pipe',
|
|
579
|
+
});
|
|
580
|
+
return r.status === 0; // ignored
|
|
581
|
+
})
|
|
582
|
+
.map(f => `!${f}`)
|
|
583
|
+
.filter(neg => !content.includes(neg));
|
|
584
|
+
|
|
585
|
+
if (negationsNeeded.length > 0) {
|
|
586
|
+
content += [
|
|
587
|
+
'',
|
|
588
|
+
'# Code Guardian — team config files (must be tracked)',
|
|
589
|
+
...negationsNeeded,
|
|
590
|
+
'',
|
|
591
|
+
].join('\n');
|
|
592
|
+
changed = true;
|
|
593
|
+
messages.push(
|
|
594
|
+
` ${c.green('✓')} .gitignore: un-ignored tracked config files:\n` +
|
|
595
|
+
negationsNeeded.map(l => ` ${c.green(l)}`).join('\n')
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (changed) {
|
|
601
|
+
fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
602
|
+
messages.forEach(m => console.log(m));
|
|
603
|
+
console.log('');
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
486
607
|
// ── Setup Git Hooks ─────────────────────────────────────────────────────────
|
|
487
608
|
|
|
488
609
|
function setupGitHooks(cwd: string): void {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import * as fs from 'fs';
|
|
23
23
|
import * as path from 'path';
|
|
24
|
+
import { spawnSync } from 'child_process';
|
|
24
25
|
import { DetectionResult, ProjectType } from '../utils/project-detector';
|
|
25
26
|
import { AIConfigRegistry, AIConfigTemplate, defaultRegistry } from '../utils/ai-config-registry';
|
|
26
27
|
|
|
@@ -49,6 +50,9 @@ export function generateAIConfigs(project: DetectionResult, customRulesText?: st
|
|
|
49
50
|
|
|
50
51
|
// Clean up deprecated .agent/ directory (replaced by AGENTS.md)
|
|
51
52
|
cleanupDeprecatedAgentDir(project.rootDir);
|
|
53
|
+
|
|
54
|
+
// Ensure .gitignore doesn't swallow the generated AI config files
|
|
55
|
+
ensureGitignoreAllowsAIConfigs(project.rootDir, templates);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/**
|
|
@@ -188,6 +192,135 @@ function cleanupDeprecatedAgentDir(rootDir: string): void {
|
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Check every generated AI config file against .gitignore and add negation
|
|
197
|
+
* patterns so the files are tracked by git even when their parent directory
|
|
198
|
+
* is ignored (e.g. .cursor is often in .gitignore).
|
|
199
|
+
*/
|
|
200
|
+
/**
|
|
201
|
+
* Best-practice .gitignore management for AI config files.
|
|
202
|
+
*
|
|
203
|
+
* STRATEGY: Instead of piling negation hacks on top of broad ignores, we
|
|
204
|
+
* replace the broad pattern with a surgical one that:
|
|
205
|
+
* - keeps personal/IDE files ignored (e.g. .cursor/*)
|
|
206
|
+
* - explicitly tracks the shared rules folder (e.g. !.cursor/rules/)
|
|
207
|
+
*
|
|
208
|
+
* Example — if .gitignore contains `.cursor`, we replace it with:
|
|
209
|
+
* .cursor/*
|
|
210
|
+
* !.cursor/rules/
|
|
211
|
+
*
|
|
212
|
+
* If the broad pattern can't be replaced safely (e.g. it's inside a complex
|
|
213
|
+
* block), we fall back to appending negation patterns and warn the user.
|
|
214
|
+
*/
|
|
215
|
+
function ensureGitignoreAllowsAIConfigs(
|
|
216
|
+
rootDir: string,
|
|
217
|
+
templates: AIConfigTemplate[]
|
|
218
|
+
): void {
|
|
219
|
+
const gitignorePath = path.join(rootDir, '.gitignore');
|
|
220
|
+
if (!fs.existsSync(gitignorePath)) return;
|
|
221
|
+
|
|
222
|
+
let content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
223
|
+
let changed = false;
|
|
224
|
+
const advisories: string[] = [];
|
|
225
|
+
|
|
226
|
+
// Collect unique top-level directories used by templates
|
|
227
|
+
// e.g. '.cursor', '.github', '.windsurf'
|
|
228
|
+
const topDirs = new Set<string>();
|
|
229
|
+
for (const t of templates) {
|
|
230
|
+
if (t.directory) {
|
|
231
|
+
topDirs.add(t.directory.split('/')[0]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (const dir of topDirs) {
|
|
236
|
+
// Check if a broad pattern ignores this directory.
|
|
237
|
+
// Matches bare `.cursor`, `.cursor/`, `.cursor/*` on its own line.
|
|
238
|
+
const broadPattern = new RegExp(
|
|
239
|
+
`^(\.?${escapeRegex(dir)})\\/?\\*?$`,
|
|
240
|
+
'm'
|
|
241
|
+
);
|
|
242
|
+
const match = broadPattern.exec(content);
|
|
243
|
+
if (!match) continue;
|
|
244
|
+
|
|
245
|
+
// Find all rule sub-directories that belong to this top-level dir.
|
|
246
|
+
// e.g. for '.cursor' → ['.cursor/rules']
|
|
247
|
+
const ruleDirs = [...new Set(
|
|
248
|
+
templates
|
|
249
|
+
.filter(t => t.directory?.startsWith(dir + '/'))
|
|
250
|
+
.map(t => t.directory!)
|
|
251
|
+
)];
|
|
252
|
+
|
|
253
|
+
// Build the surgical replacement:
|
|
254
|
+
// .cursor/* ← ignore everything inside
|
|
255
|
+
// !.cursor/rules/ ← except the shared rules folder
|
|
256
|
+
const surgicalLines = [
|
|
257
|
+
`${dir}/*`,
|
|
258
|
+
...ruleDirs.map(d => `!${d}/`),
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
const replacement = surgicalLines.join('\n');
|
|
262
|
+
const newContent = content.replace(broadPattern, replacement);
|
|
263
|
+
|
|
264
|
+
if (newContent !== content) {
|
|
265
|
+
content = newContent;
|
|
266
|
+
changed = true;
|
|
267
|
+
advisories.push(
|
|
268
|
+
` ✅ .gitignore: replaced broad \`${match[0]}\` with surgical pattern:\n` +
|
|
269
|
+
surgicalLines.map(l => ` ${l}`).join('\n')
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// For any file still ignored after the surgical replacements, fall back to
|
|
275
|
+
// appending explicit negations (rare edge case).
|
|
276
|
+
const fallbackNegations: string[] = [];
|
|
277
|
+
// Write changes so far before running git check-ignore
|
|
278
|
+
if (changed) fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
279
|
+
|
|
280
|
+
for (const template of templates) {
|
|
281
|
+
const relativePath = template.directory
|
|
282
|
+
? `${template.directory}/${template.fileName}`
|
|
283
|
+
: template.fileName;
|
|
284
|
+
|
|
285
|
+
const result = spawnSync('git', ['check-ignore', '--no-index', '-q', relativePath], {
|
|
286
|
+
cwd: rootDir,
|
|
287
|
+
stdio: 'pipe',
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (result.status !== 0) continue; // not ignored — all good
|
|
291
|
+
|
|
292
|
+
// Still ignored — add explicit negation as fallback
|
|
293
|
+
const neg = `!${relativePath}`;
|
|
294
|
+
if (!content.includes(neg) && !fallbackNegations.includes(neg)) {
|
|
295
|
+
fallbackNegations.push(neg);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (fallbackNegations.length > 0) {
|
|
300
|
+
const section = [
|
|
301
|
+
'',
|
|
302
|
+
'# Code Guardian — AI config files (shared with team, un-ignored)',
|
|
303
|
+
...fallbackNegations,
|
|
304
|
+
'',
|
|
305
|
+
].join('\n');
|
|
306
|
+
content += section;
|
|
307
|
+
changed = true;
|
|
308
|
+
advisories.push(
|
|
309
|
+
` ⚠️ Some AI config files were still gitignored — added explicit negations:\n` +
|
|
310
|
+
fallbackNegations.map(l => ` ${l}`).join('\n')
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (changed) {
|
|
315
|
+
fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
316
|
+
advisories.forEach(msg => console.log(msg));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function escapeRegex(s: string): string {
|
|
321
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
322
|
+
}
|
|
323
|
+
|
|
191
324
|
/**
|
|
192
325
|
* Generate content for .cursor/rules/cursor.mdc
|
|
193
326
|
*/
|
|
@@ -76,11 +76,6 @@ export function generateReport(
|
|
|
76
76
|
const content = buildReportContent(report, options);
|
|
77
77
|
fs.writeFileSync(reportPath, content, 'utf-8');
|
|
78
78
|
|
|
79
|
-
// Attempt to open in VS Code
|
|
80
|
-
if (options.autoOpen !== false) {
|
|
81
|
-
openInEditor(reportPath);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
79
|
return reportPath;
|
|
85
80
|
}
|
|
86
81
|
|