@hatem427/code-guard-ci 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.
Files changed (107) hide show
  1. package/.husky/pre-commit +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +646 -0
  4. package/config/angular.config.ts +223 -0
  5. package/config/guidelines.config.ts +229 -0
  6. package/config/nextjs.config.ts +160 -0
  7. package/config/react.config.ts +330 -0
  8. package/dist/config/angular.config.d.ts +15 -0
  9. package/dist/config/angular.config.d.ts.map +1 -0
  10. package/dist/config/angular.config.js +187 -0
  11. package/dist/config/angular.config.js.map +1 -0
  12. package/dist/config/guidelines.config.d.ts +63 -0
  13. package/dist/config/guidelines.config.d.ts.map +1 -0
  14. package/dist/config/guidelines.config.js +167 -0
  15. package/dist/config/guidelines.config.js.map +1 -0
  16. package/dist/config/nextjs.config.d.ts +18 -0
  17. package/dist/config/nextjs.config.d.ts.map +1 -0
  18. package/dist/config/nextjs.config.js +133 -0
  19. package/dist/config/nextjs.config.js.map +1 -0
  20. package/dist/config/react.config.d.ts +15 -0
  21. package/dist/config/react.config.d.ts.map +1 -0
  22. package/dist/config/react.config.js +287 -0
  23. package/dist/config/react.config.js.map +1 -0
  24. package/dist/scripts/auto-fix.d.ts +16 -0
  25. package/dist/scripts/auto-fix.d.ts.map +1 -0
  26. package/dist/scripts/auto-fix.js +130 -0
  27. package/dist/scripts/auto-fix.js.map +1 -0
  28. package/dist/scripts/cli.d.ts +17 -0
  29. package/dist/scripts/cli.d.ts.map +1 -0
  30. package/dist/scripts/cli.js +255 -0
  31. package/dist/scripts/cli.js.map +1 -0
  32. package/dist/scripts/delete-bypass-logs.d.ts +17 -0
  33. package/dist/scripts/delete-bypass-logs.d.ts.map +1 -0
  34. package/dist/scripts/delete-bypass-logs.js +242 -0
  35. package/dist/scripts/delete-bypass-logs.js.map +1 -0
  36. package/dist/scripts/generate-doc.d.ts +18 -0
  37. package/dist/scripts/generate-doc.d.ts.map +1 -0
  38. package/dist/scripts/generate-doc.js +300 -0
  39. package/dist/scripts/generate-doc.js.map +1 -0
  40. package/dist/scripts/generate-pr-checklist.d.ts +20 -0
  41. package/dist/scripts/generate-pr-checklist.d.ts.map +1 -0
  42. package/dist/scripts/generate-pr-checklist.js +276 -0
  43. package/dist/scripts/generate-pr-checklist.js.map +1 -0
  44. package/dist/scripts/precommit-check.d.ts +23 -0
  45. package/dist/scripts/precommit-check.d.ts.map +1 -0
  46. package/dist/scripts/precommit-check.js +331 -0
  47. package/dist/scripts/precommit-check.js.map +1 -0
  48. package/dist/scripts/set-admin-password.d.ts +14 -0
  49. package/dist/scripts/set-admin-password.d.ts.map +1 -0
  50. package/dist/scripts/set-admin-password.js +116 -0
  51. package/dist/scripts/set-admin-password.js.map +1 -0
  52. package/dist/scripts/set-bypass-password.d.ts +11 -0
  53. package/dist/scripts/set-bypass-password.d.ts.map +1 -0
  54. package/dist/scripts/set-bypass-password.js +106 -0
  55. package/dist/scripts/set-bypass-password.js.map +1 -0
  56. package/dist/scripts/utils/auto-fixer.d.ts +28 -0
  57. package/dist/scripts/utils/auto-fixer.d.ts.map +1 -0
  58. package/dist/scripts/utils/auto-fixer.js +177 -0
  59. package/dist/scripts/utils/auto-fixer.js.map +1 -0
  60. package/dist/scripts/utils/bypass-manager.d.ts +101 -0
  61. package/dist/scripts/utils/bypass-manager.d.ts.map +1 -0
  62. package/dist/scripts/utils/bypass-manager.js +496 -0
  63. package/dist/scripts/utils/bypass-manager.js.map +1 -0
  64. package/dist/scripts/utils/code-analyzer.d.ts +34 -0
  65. package/dist/scripts/utils/code-analyzer.d.ts.map +1 -0
  66. package/dist/scripts/utils/code-analyzer.js +323 -0
  67. package/dist/scripts/utils/code-analyzer.js.map +1 -0
  68. package/dist/scripts/utils/file-checker.d.ts +93 -0
  69. package/dist/scripts/utils/file-checker.d.ts.map +1 -0
  70. package/dist/scripts/utils/file-checker.js +248 -0
  71. package/dist/scripts/utils/file-checker.js.map +1 -0
  72. package/dist/scripts/utils/logger.d.ts +26 -0
  73. package/dist/scripts/utils/logger.d.ts.map +1 -0
  74. package/dist/scripts/utils/logger.js +86 -0
  75. package/dist/scripts/utils/logger.js.map +1 -0
  76. package/dist/scripts/utils/project-detector.d.ts +34 -0
  77. package/dist/scripts/utils/project-detector.d.ts.map +1 -0
  78. package/dist/scripts/utils/project-detector.js +124 -0
  79. package/dist/scripts/utils/project-detector.js.map +1 -0
  80. package/dist/scripts/utils/rule-engine.d.ts +57 -0
  81. package/dist/scripts/utils/rule-engine.d.ts.map +1 -0
  82. package/dist/scripts/utils/rule-engine.js +158 -0
  83. package/dist/scripts/utils/rule-engine.js.map +1 -0
  84. package/dist/scripts/view-bypass-log.d.ts +13 -0
  85. package/dist/scripts/view-bypass-log.d.ts.map +1 -0
  86. package/dist/scripts/view-bypass-log.js +117 -0
  87. package/dist/scripts/view-bypass-log.js.map +1 -0
  88. package/package.json +74 -0
  89. package/scripts/auto-fix.ts +115 -0
  90. package/scripts/cli.ts +246 -0
  91. package/scripts/delete-bypass-logs.ts +253 -0
  92. package/scripts/generate-doc.ts +317 -0
  93. package/scripts/generate-pr-checklist.ts +285 -0
  94. package/scripts/precommit-check.ts +349 -0
  95. package/scripts/set-admin-password.ts +90 -0
  96. package/scripts/set-bypass-password.ts +80 -0
  97. package/scripts/utils/auto-fixer.ts +181 -0
  98. package/scripts/utils/bypass-manager.ts +566 -0
  99. package/scripts/utils/code-analyzer.ts +341 -0
  100. package/scripts/utils/file-checker.ts +253 -0
  101. package/scripts/utils/logger.ts +88 -0
  102. package/scripts/utils/project-detector.ts +115 -0
  103. package/scripts/utils/rule-engine.ts +186 -0
  104. package/scripts/view-bypass-log.ts +92 -0
  105. package/templates/feature-doc-api.md +101 -0
  106. package/templates/feature-doc-service.md +113 -0
  107. package/templates/feature-doc-ui.md +91 -0
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env ts-node
2
+ /**
3
+ * ============================================================================
4
+ * delete-bypass-logs.ts — Delete bypass log entries (ADMIN ONLY)
5
+ * ============================================================================
6
+ *
7
+ * Requires admin password to delete entries from bypass log.
8
+ * Deleted entries are automatically archived for audit trail.
9
+ *
10
+ * Usage:
11
+ * npm run delete-bypass-logs
12
+ * npm run delete-bypass-logs -- --id="entry-uuid"
13
+ * npm run delete-bypass-logs -- --author="john@example.com"
14
+ * npm run delete-bypass-logs -- --before="2024-01-01"
15
+ */
16
+
17
+ import {
18
+ deleteBypassEntries,
19
+ getBypassesByAuthor,
20
+ getRecentBypasses
21
+ } from './utils/bypass-manager';
22
+ import * as readline from 'readline';
23
+ import * as fs from 'fs';
24
+ import * as path from 'path';
25
+
26
+ interface BypassEntry {
27
+ id: string;
28
+ author: string;
29
+ email: string;
30
+ timestamp: string;
31
+ reason: string;
32
+ commitMessage: string;
33
+ commitHash?: string;
34
+ branch: string;
35
+ files: string[];
36
+ method: 'commit-message' | 'env-variable' | 'password';
37
+ }
38
+
39
+ function promptPassword(question: string): Promise<string> {
40
+ return new Promise((resolve) => {
41
+ const rl = readline.createInterface({
42
+ input: process.stdin,
43
+ output: process.stdout,
44
+ });
45
+
46
+ const stdin = process.stdin as any;
47
+ stdin.setRawMode(true);
48
+
49
+ let password = '';
50
+ process.stdout.write(question);
51
+
52
+ stdin.on('data', (char: Buffer) => {
53
+ const str = char.toString('utf-8');
54
+
55
+ if (str === '\n' || str === '\r' || str === '\u0004') {
56
+ stdin.setRawMode(false);
57
+ process.stdout.write('\n');
58
+ rl.close();
59
+ resolve(password);
60
+ } else if (str === '\u0003') {
61
+ // Ctrl+C
62
+ process.exit(1);
63
+ } else if (str === '\u007f' || str === '\b') {
64
+ // Backspace
65
+ if (password.length > 0) {
66
+ password = password.slice(0, -1);
67
+ process.stdout.write('\b \b');
68
+ }
69
+ } else {
70
+ password += str;
71
+ process.stdout.write('*');
72
+ }
73
+ });
74
+ });
75
+ }
76
+
77
+ function promptConfirmation(question: string): Promise<boolean> {
78
+ return new Promise((resolve) => {
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stdout,
82
+ });
83
+
84
+ rl.question(question, (answer) => {
85
+ rl.close();
86
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
87
+ });
88
+ });
89
+ }
90
+
91
+ function parseArgs(): { id?: string; author?: string; before?: string } {
92
+ const args = process.argv.slice(2);
93
+ const opts: { id?: string; author?: string; before?: string } = {};
94
+
95
+ for (const arg of args) {
96
+ const [key, ...valueParts] = arg.replace(/^--/, '').split('=');
97
+ const value = valueParts.join('=').replace(/^["']|["']$/g, '');
98
+
99
+ switch (key) {
100
+ case 'id':
101
+ opts.id = value;
102
+ break;
103
+ case 'author':
104
+ opts.author = value;
105
+ break;
106
+ case 'before':
107
+ opts.before = value;
108
+ break;
109
+ }
110
+ }
111
+
112
+ return opts;
113
+ }
114
+
115
+ function loadBypassLog(): BypassEntry[] {
116
+ const logPath = path.join(process.cwd(), '.code-guardian', 'bypass-log.json');
117
+
118
+ if (!fs.existsSync(logPath)) {
119
+ return [];
120
+ }
121
+
122
+ try {
123
+ const content = fs.readFileSync(logPath, 'utf-8');
124
+ const log = JSON.parse(content);
125
+ return log.entries || [];
126
+ } catch {
127
+ return [];
128
+ }
129
+ }
130
+
131
+ async function main() {
132
+ console.log('🗑️ Code Guardian — Delete Bypass Logs (ADMIN ONLY)\n');
133
+ console.log('⚠️ WARNING: This operation requires admin authentication.');
134
+ console.log(' Deleted entries will be archived for audit purposes.\n');
135
+
136
+ const opts = parseArgs();
137
+ const allEntries = loadBypassLog();
138
+
139
+ if (allEntries.length === 0) {
140
+ console.log('ℹ️ No bypass entries found in the log.');
141
+ return;
142
+ }
143
+
144
+ let entriesToDelete: BypassEntry[] = [];
145
+
146
+ if (opts.id) {
147
+ // Delete specific entry by ID
148
+ const entry = allEntries.find((e) => e.id === opts.id);
149
+ if (!entry) {
150
+ console.error(`❌ Entry with ID "${opts.id}" not found.`);
151
+ process.exit(1);
152
+ }
153
+ entriesToDelete = [entry];
154
+ console.log(`Found entry to delete:`);
155
+ console.log(` • ID: ${entry.id}`);
156
+ console.log(` • Author: ${entry.author} <${entry.email}>`);
157
+ console.log(` • Date: ${new Date(entry.timestamp).toLocaleString()}`);
158
+ console.log(` • Reason: ${entry.reason}\n`);
159
+ } else if (opts.author) {
160
+ // Delete entries by author
161
+ entriesToDelete = allEntries.filter((e) =>
162
+ e.email === opts.author || e.author === opts.author
163
+ );
164
+ if (entriesToDelete.length === 0) {
165
+ console.error(`❌ No entries found for author "${opts.author}".`);
166
+ process.exit(1);
167
+ }
168
+ console.log(`Found ${entriesToDelete.length} entry(ies) by ${opts.author}:\n`);
169
+ entriesToDelete.forEach((entry) => {
170
+ console.log(` • ${new Date(entry.timestamp).toLocaleString()} - ${entry.reason}`);
171
+ });
172
+ console.log('');
173
+ } else if (opts.before) {
174
+ // Delete entries before a certain date
175
+ const cutoffDate = new Date(opts.before);
176
+ if (isNaN(cutoffDate.getTime())) {
177
+ console.error(`❌ Invalid date format: "${opts.before}"`);
178
+ process.exit(1);
179
+ }
180
+ entriesToDelete = allEntries.filter((e) => new Date(e.timestamp) < cutoffDate);
181
+ if (entriesToDelete.length === 0) {
182
+ console.error(`❌ No entries found before ${cutoffDate.toLocaleDateString()}.`);
183
+ process.exit(1);
184
+ }
185
+ console.log(`Found ${entriesToDelete.length} entry(ies) before ${cutoffDate.toLocaleDateString()}:\n`);
186
+ entriesToDelete.slice(0, 5).forEach((entry) => {
187
+ console.log(` • ${new Date(entry.timestamp).toLocaleString()} - ${entry.author}`);
188
+ });
189
+ if (entriesToDelete.length > 5) {
190
+ console.log(` ... and ${entriesToDelete.length - 5} more\n`);
191
+ }
192
+ } else {
193
+ // Interactive mode: show all entries
194
+ console.log('Available entries:\n');
195
+ allEntries.forEach((entry, index) => {
196
+ console.log(`${index + 1}. ${new Date(entry.timestamp).toLocaleString()}`);
197
+ console.log(` Author: ${entry.author} <${entry.email}>`);
198
+ console.log(` Reason: ${entry.reason}`);
199
+ console.log(` ID: ${entry.id}\n`);
200
+ });
201
+
202
+ const confirmed = await promptConfirmation(
203
+ 'Enter entry number(s) to delete (comma-separated) or "all": '
204
+ );
205
+
206
+ if (!confirmed) {
207
+ console.log('Operation cancelled.');
208
+ return;
209
+ }
210
+
211
+ // For simplicity in interactive mode, we'll just exit with instructions
212
+ console.log('\n📝 To delete specific entries, use:');
213
+ console.log(' npm run delete-bypass-logs -- --id="<entry-id>"');
214
+ console.log(' npm run delete-bypass-logs -- --author="<email>"');
215
+ console.log(' npm run delete-bypass-logs -- --before="YYYY-MM-DD"');
216
+ return;
217
+ }
218
+
219
+ // Confirm deletion
220
+ const confirmDelete = await promptConfirmation(
221
+ `❗ Are you sure you want to delete ${entriesToDelete.length} entry(ies)? (y/N): `
222
+ );
223
+
224
+ if (!confirmDelete) {
225
+ console.log('Operation cancelled.');
226
+ return;
227
+ }
228
+
229
+ // Request admin password
230
+ console.log('');
231
+ const adminPassword = await promptPassword('Enter admin password: ');
232
+
233
+ if (!adminPassword) {
234
+ console.error('\n❌ Password is required.');
235
+ process.exit(1);
236
+ }
237
+
238
+ // Attempt deletion
239
+ const entryIds = entriesToDelete.map((e) => e.id);
240
+ const result = deleteBypassEntries(entryIds, adminPassword);
241
+
242
+ console.log('\n' + result.message);
243
+
244
+ if (result.success) {
245
+ console.log(`📊 Remaining entries: ${allEntries.length - result.deletedCount}`);
246
+ console.log('📦 Deleted entries have been archived for audit purposes.');
247
+ }
248
+ }
249
+
250
+ main().catch((err) => {
251
+ console.error(`Error: ${err.message}`);
252
+ process.exit(1);
253
+ });
@@ -0,0 +1,317 @@
1
+ /**
2
+ * ============================================================================
3
+ * generate-doc.ts — Feature documentation generator
4
+ * ============================================================================
5
+ *
6
+ * Generates Markdown feature documentation from templates.
7
+ * Supports multiple template types: ui, api, service.
8
+ *
9
+ * Usage:
10
+ * npm run generate-doc -- --name="my-feature" --type=ui
11
+ * npm run generate-doc -- --name="auth-api" --type=api
12
+ * npm run generate-doc -- --name="user-service" --type=service
13
+ *
14
+ * Templates live in /templates/feature-doc-{type}.md
15
+ * Output goes to /docs/features/{name}.md
16
+ */
17
+
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+ import * as logger from './utils/logger';
21
+ import { analyzeComponent, findComponentFile, ComponentInfo } from './utils/code-analyzer';
22
+
23
+ // ── Types ───────────────────────────────────────────────────────────────────
24
+
25
+ type DocType = 'ui' | 'api' | 'service';
26
+
27
+ interface DocOptions {
28
+ /** Feature name — used for file naming and placeholder replacement */
29
+ name: string;
30
+ /** Template type */
31
+ type: DocType;
32
+ /** Optional description to pre-fill */
33
+ description?: string;
34
+ /** Optional author */
35
+ author?: string;
36
+ /** Auto-analyze code and generate docs */
37
+ auto?: boolean;
38
+ /** File path for auto-analysis */
39
+ file?: string;
40
+ }
41
+
42
+ // ── Argument parsing ────────────────────────────────────────────────────────
43
+
44
+ /**
45
+ * Parse CLI arguments into DocOptions.
46
+ * Expected format: --name="feature-name" --type=ui [--description="..."] [--author="..."]
47
+ */
48
+ function parseArgs(): DocOptions {
49
+ const args = process.argv.slice(2);
50
+ const opts: Partial<DocOptions> = {};
51
+
52
+ for (const arg of args) {
53
+ const [key, ...valueParts] = arg.replace(/^--/, '').split('=');
54
+ const value = valueParts.join('=').replace(/^["']|["']$/g, ''); // Remove surrounding quotes
55
+
56
+ switch (key) {
57
+ case 'name':
58
+ opts.name = value;
59
+ break;
60
+ case 'type':
61
+ if (['ui', 'api', 'service'].includes(value)) {
62
+ opts.type = value as DocType;
63
+ } else {
64
+ logger.error(`Invalid type "${value}". Expected: ui, api, or service.`);
65
+ process.exit(1);
66
+ }
67
+ break;
68
+ case 'description':
69
+ opts.description = value;
70
+ break;
71
+ case 'author':
72
+ opts.author = value;
73
+ break;
74
+ case 'auto':
75
+ opts.auto = value === 'true' || value === '' || !value; // --auto or --auto=true
76
+ break;
77
+ case 'file':
78
+ opts.file = value;
79
+ break;
80
+ }
81
+ }
82
+
83
+ // Validate required fields
84
+ if (!opts.name) {
85
+ logger.error('Missing required argument: --name="feature-name"');
86
+ logger.dim('Usage: npm run generate-doc -- --name="my-feature" --type=ui');
87
+ process.exit(1);
88
+ }
89
+
90
+ if (!opts.type) {
91
+ logger.warn('No --type specified, defaulting to "ui".');
92
+ opts.type = 'ui';
93
+ }
94
+
95
+ return opts as DocOptions;
96
+ }
97
+
98
+ // ── Template processing ─────────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Load and process a template file, replacing placeholders with actual values.
102
+ *
103
+ * Supported placeholders:
104
+ * {{FEATURE_NAME}} — The feature name (kebab-case)
105
+ * {{FEATURE_TITLE}} — The feature name in Title Case
106
+ * {{DATE}} — Today's date in YYYY-MM-DD format
107
+ * {{AUTHOR}} — Author name (or "Team" if not provided)
108
+ * {{DESCRIPTION}} — Pre-filled description (or placeholder text)
109
+ * {{DOC_TYPE}} — The template type (ui, api, service)
110
+ */
111
+ function processTemplate(templatePath: string, opts: DocOptions): string {
112
+ if (!fs.existsSync(templatePath)) {
113
+ logger.error(`Template not found: ${templatePath}`);
114
+ logger.dim('Available templates:');
115
+ logger.dim(' templates/feature-doc-ui.md');
116
+ logger.dim(' templates/feature-doc-api.md');
117
+ logger.dim(' templates/feature-doc-service.md');
118
+ process.exit(1);
119
+ }
120
+
121
+ let content = fs.readFileSync(templatePath, 'utf-8');
122
+
123
+ // Convert kebab-case to Title Case
124
+ const titleCase = opts.name
125
+ .split('-')
126
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
127
+ .join(' ');
128
+
129
+ // Today's date
130
+ const today = new Date().toISOString().split('T')[0];
131
+
132
+ // Replace all placeholders
133
+ const replacements: Record<string, string> = {
134
+ '{{FEATURE_NAME}}': opts.name,
135
+ '{{FEATURE_TITLE}}': titleCase,
136
+ '{{DATE}}': today,
137
+ '{{AUTHOR}}': opts.author || 'Team',
138
+ '{{DESCRIPTION}}': opts.description || 'TODO: Add feature description here.',
139
+ '{{DOC_TYPE}}': opts.type,
140
+ };
141
+
142
+ for (const [placeholder, value] of Object.entries(replacements)) {
143
+ content = content.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), value);
144
+ }
145
+
146
+ return content;
147
+ }
148
+
149
+ /**
150
+ * Generate documentation automatically from code analysis
151
+ */
152
+ function generateFromCode(componentInfo: ComponentInfo, opts: DocOptions): string {
153
+ const today = new Date().toISOString().split('T')[0];
154
+ const titleCase = componentInfo.name
155
+ .replace(/([A-Z])/g, ' $1')
156
+ .trim();
157
+
158
+ let doc = `# ${titleCase}\n\n`;
159
+ doc += `> **Type**: UI Component \n`;
160
+ doc += `> **Created**: ${today} \n`;
161
+ doc += `> **Author**: ${opts.author || 'Team'} \n`;
162
+ doc += `> **Status**: ✅ Auto-generated\n\n`;
163
+ doc += `---\n\n`;
164
+
165
+ // Description
166
+ doc += `## 📝 Description\n\n`;
167
+ if (componentInfo.description) {
168
+ doc += `${componentInfo.description}\n\n`;
169
+ } else {
170
+ doc += `_Auto-generated from \`${path.basename(componentInfo.filePath)}\`_\n\n`;
171
+ }
172
+
173
+ // Usage
174
+ doc += `## 🚀 Usage\n\n`;
175
+ doc += `\`\`\`tsx\n`;
176
+ doc += `import { ${componentInfo.name} } from './${path.basename(componentInfo.filePath, path.extname(componentInfo.filePath))}';\n\n`;
177
+
178
+ if (componentInfo.props.length > 0) {
179
+ doc += `<${componentInfo.name}\n`;
180
+ componentInfo.props.slice(0, 3).forEach(prop => {
181
+ const example = getExampleValue(prop.type);
182
+ doc += ` ${prop.name}={${example}}\n`;
183
+ });
184
+ if (componentInfo.props.length > 3) {
185
+ doc += ` // ... more props\n`;
186
+ }
187
+ doc += `/>\n`;
188
+ } else {
189
+ doc += `<${componentInfo.name} />\n`;
190
+ }
191
+ doc += `\`\`\`\n\n`;
192
+
193
+ // Props table
194
+ if (componentInfo.props.length > 0) {
195
+ doc += `## 📥 Props\n\n`;
196
+ doc += `| Name | Type | Required | Default | Description |\n`;
197
+ doc += `|------|------|----------|---------|-------------|\n`;
198
+
199
+ componentInfo.props.forEach(prop => {
200
+ const req = prop.required ? '✅ Yes' : 'No';
201
+ const def = prop.defaultValue || '-';
202
+ const desc = prop.description || '-';
203
+ doc += `| \`${prop.name}\` | \`${prop.type}\` | ${req} | \`${def}\` | ${desc} |\n`;
204
+ });
205
+ doc += `\n`;
206
+ }
207
+
208
+ // Footer
209
+ doc += `## 📝 Notes\n\n`;
210
+ doc += `- Auto-generated from code analysis\n`;
211
+ doc += `- Source file: \`${componentInfo.filePath}\`\n`;
212
+ doc += `- Last analyzed: ${today}\n\n`;
213
+ doc += `---\n\n`;
214
+ doc += `_Generated by Code Guardian — Auto-analysis_\n`;
215
+
216
+ return doc;
217
+ }
218
+
219
+ /**
220
+ * Get example value for a type
221
+ */
222
+ function getExampleValue(type: string): string {
223
+ if (type.includes('string')) return '"example"';
224
+ if (type.includes('number')) return '42';
225
+ if (type.includes('boolean')) return 'true';
226
+ if (type.includes('Function') || type.includes('=>')) return '{handleClick}';
227
+ if (type.includes('[]')) return '[]';
228
+ if (type.includes('{}') || type.includes('object')) return '{{}}';
229
+ return '...';
230
+ }
231
+
232
+ // ── Main ────────────────────────────────────────────────────────────────────
233
+
234
+ function main(): void {
235
+ logger.header('📄 Code Guardian — Feature Doc Generator');
236
+
237
+ const opts = parseArgs();
238
+
239
+ logger.info(`Generating "${opts.type}" doc for feature: "${opts.name}"`);
240
+
241
+ // Resolve paths
242
+ const rootDir = process.cwd();
243
+ const outputDir = path.join(rootDir, 'docs', 'features');
244
+ const outputPath = path.join(outputDir, `${opts.name}.md`);
245
+
246
+ // Create output directory if needed
247
+ if (!fs.existsSync(outputDir)) {
248
+ fs.mkdirSync(outputDir, { recursive: true });
249
+ logger.dim(`Created directory: ${outputDir}`);
250
+ }
251
+
252
+ // Check if doc already exists
253
+ if (fs.existsSync(outputPath)) {
254
+ logger.warn(`Doc already exists: ${outputPath}`);
255
+ logger.warn('Overwriting with fresh content...');
256
+ }
257
+
258
+ let content: string;
259
+
260
+ // Auto-analyze mode
261
+ if (opts.auto) {
262
+ logger.info('Auto-analysis mode enabled — analyzing code...');
263
+
264
+ // Find component file
265
+ let componentPath = opts.file;
266
+ if (!componentPath) {
267
+ logger.dim(`Searching for ${opts.name} component...`);
268
+ const foundPath = findComponentFile(opts.name, rootDir);
269
+ componentPath = foundPath || undefined;
270
+
271
+ if (!componentPath) {
272
+ logger.error(`Could not find component file for "${opts.name}"`);
273
+ logger.dim('Use --file="path/to/component.tsx" to specify manually');
274
+ logger.dim('Or use template mode: remove --auto flag');
275
+ process.exit(1);
276
+ }
277
+
278
+ logger.dim(`Found: ${componentPath}`);
279
+ }
280
+
281
+ // Analyze component
282
+ const componentInfo = analyzeComponent(componentPath);
283
+
284
+ if (!componentInfo || !componentInfo.isReactComponent) {
285
+ logger.error(`Could not analyze component at ${componentPath}`);
286
+ logger.warn('Falling back to template mode...');
287
+ const templatePath = path.join(rootDir, 'templates', `feature-doc-${opts.type}.md`);
288
+ content = processTemplate(templatePath, opts);
289
+ } else {
290
+ logger.success(`Analyzed ${componentInfo.name} — found ${componentInfo.props.length} prop(s)`);
291
+ content = generateFromCode(componentInfo, opts);
292
+ }
293
+ } else {
294
+ // Template mode
295
+ const templatePath = path.join(rootDir, 'templates', `feature-doc-${opts.type}.md`);
296
+ content = processTemplate(templatePath, opts);
297
+ }
298
+
299
+ // Write output
300
+ fs.writeFileSync(outputPath, content, 'utf-8');
301
+
302
+ logger.success(`Feature doc generated: ${outputPath}`);
303
+
304
+ if (!opts.auto) {
305
+ logger.dim('');
306
+ logger.dim('Next steps:');
307
+ logger.dim(` 1. Open ${outputPath} and fill in the details`);
308
+ logger.dim(' 2. Add it to your commit');
309
+ logger.dim(' 3. The PR checklist will verify doc completeness');
310
+ logger.dim('');
311
+ logger.dim('💡 Tip: Use --auto flag to auto-generate from code!');
312
+ }
313
+ }
314
+
315
+ // ── Execute ─────────────────────────────────────────────────────────────────
316
+
317
+ main();