@hyperdrive.bot/gut 0.1.8 → 0.1.10

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 (111) hide show
  1. package/README.md +1048 -1
  2. package/dist/base-command.d.ts +22 -0
  3. package/dist/base-command.js +99 -0
  4. package/dist/commands/add.d.ts +14 -0
  5. package/dist/commands/add.js +70 -0
  6. package/dist/commands/affected.d.ts +23 -0
  7. package/dist/commands/affected.js +323 -0
  8. package/dist/commands/audit.d.ts +33 -0
  9. package/dist/commands/audit.js +594 -0
  10. package/dist/commands/back.d.ts +6 -0
  11. package/dist/commands/back.js +29 -0
  12. package/dist/commands/checkout.d.ts +14 -0
  13. package/dist/commands/checkout.js +124 -0
  14. package/dist/commands/commit.d.ts +11 -0
  15. package/dist/commands/commit.js +107 -0
  16. package/dist/commands/context.d.ts +6 -0
  17. package/dist/commands/context.js +32 -0
  18. package/dist/commands/contexts.d.ts +7 -0
  19. package/dist/commands/contexts.js +88 -0
  20. package/dist/commands/deps.d.ts +10 -0
  21. package/dist/commands/deps.js +100 -0
  22. package/dist/commands/entity/add.d.ts +16 -0
  23. package/dist/commands/entity/add.js +103 -0
  24. package/dist/commands/entity/clone-all.d.ts +18 -0
  25. package/dist/commands/entity/clone-all.js +166 -0
  26. package/dist/commands/entity/clone.d.ts +17 -0
  27. package/dist/commands/entity/clone.js +132 -0
  28. package/dist/commands/entity/list.d.ts +11 -0
  29. package/dist/commands/entity/list.js +80 -0
  30. package/dist/commands/entity/remove.d.ts +12 -0
  31. package/dist/commands/entity/remove.js +54 -0
  32. package/dist/commands/extract.d.ts +35 -0
  33. package/dist/commands/extract.js +483 -0
  34. package/dist/commands/focus.d.ts +19 -0
  35. package/dist/commands/focus.js +137 -0
  36. package/dist/commands/graph.d.ts +18 -0
  37. package/dist/commands/graph.js +273 -0
  38. package/dist/commands/init.d.ts +11 -0
  39. package/dist/commands/init.js +75 -0
  40. package/dist/commands/insights.d.ts +21 -0
  41. package/dist/commands/insights.js +465 -0
  42. package/dist/commands/patterns.d.ts +40 -0
  43. package/dist/commands/patterns.js +405 -0
  44. package/dist/commands/pull.d.ts +11 -0
  45. package/dist/commands/pull.js +121 -0
  46. package/dist/commands/push.d.ts +11 -0
  47. package/dist/commands/push.js +97 -0
  48. package/dist/commands/quick-setup.d.ts +20 -0
  49. package/dist/commands/quick-setup.js +417 -0
  50. package/dist/commands/recent.d.ts +9 -0
  51. package/dist/commands/recent.js +51 -0
  52. package/dist/commands/related.d.ts +23 -0
  53. package/dist/commands/related.js +255 -0
  54. package/dist/commands/repos.d.ts +17 -0
  55. package/dist/commands/repos.js +184 -0
  56. package/dist/commands/stack.d.ts +10 -0
  57. package/dist/commands/stack.js +78 -0
  58. package/dist/commands/status.d.ts +13 -0
  59. package/dist/commands/status.js +193 -0
  60. package/dist/commands/sync.d.ts +11 -0
  61. package/dist/commands/sync.js +139 -0
  62. package/dist/commands/ticket/focus.d.ts +20 -0
  63. package/dist/commands/ticket/focus.js +217 -0
  64. package/dist/commands/ticket/get.d.ts +15 -0
  65. package/dist/commands/ticket/get.js +168 -0
  66. package/dist/commands/ticket/hint.d.ts +16 -0
  67. package/dist/commands/ticket/hint.js +147 -0
  68. package/dist/commands/ticket/index.d.ts +10 -0
  69. package/dist/commands/ticket/index.js +60 -0
  70. package/dist/commands/ticket/list.d.ts +13 -0
  71. package/dist/commands/ticket/list.js +120 -0
  72. package/dist/commands/ticket/sync.d.ts +14 -0
  73. package/dist/commands/ticket/sync.js +85 -0
  74. package/dist/commands/ticket/update.d.ts +17 -0
  75. package/dist/commands/ticket/update.js +142 -0
  76. package/dist/commands/unfocus.d.ts +6 -0
  77. package/dist/commands/unfocus.js +19 -0
  78. package/dist/commands/used-by.d.ts +13 -0
  79. package/dist/commands/used-by.js +110 -0
  80. package/dist/commands/workspace.d.ts +22 -0
  81. package/dist/commands/workspace.js +372 -0
  82. package/dist/index.d.ts +14 -0
  83. package/dist/index.js +16 -0
  84. package/dist/models/entity.model.d.ts +234 -0
  85. package/dist/models/entity.model.js +1 -0
  86. package/dist/models/ticket.model.d.ts +117 -0
  87. package/dist/models/ticket.model.js +43 -0
  88. package/dist/services/auth.service.d.ts +15 -0
  89. package/dist/services/auth.service.js +26 -0
  90. package/dist/services/config.service.d.ts +34 -0
  91. package/dist/services/config.service.js +234 -0
  92. package/dist/services/entity.service.d.ts +20 -0
  93. package/dist/services/entity.service.js +127 -0
  94. package/dist/services/focus.service.d.ts +71 -0
  95. package/dist/services/focus.service.js +614 -0
  96. package/dist/services/git.service.d.ts +39 -0
  97. package/dist/services/git.service.js +188 -0
  98. package/dist/services/gut-api.service.d.ts +53 -0
  99. package/dist/services/gut-api.service.js +99 -0
  100. package/dist/services/ticket.service.d.ts +84 -0
  101. package/dist/services/ticket.service.js +207 -0
  102. package/dist/utils/display.d.ts +26 -0
  103. package/dist/utils/display.js +145 -0
  104. package/dist/utils/filesystem.d.ts +32 -0
  105. package/dist/utils/filesystem.js +198 -0
  106. package/dist/utils/index.d.ts +13 -0
  107. package/dist/utils/index.js +14 -0
  108. package/dist/utils/validation.d.ts +22 -0
  109. package/dist/utils/validation.js +192 -0
  110. package/oclif.manifest.json +2006 -0
  111. package/package.json +11 -2
@@ -0,0 +1,22 @@
1
+ import { Command } from '@oclif/core';
2
+ import { Entity, EntityType } from './models/entity.model.js';
3
+ import { ConfigService } from './services/config.service.js';
4
+ import { EntityService } from './services/entity.service.js';
5
+ import { FocusService } from './services/focus.service.js';
6
+ import { GitService } from './services/git.service.js';
7
+ import { TicketService } from './services/ticket.service.js';
8
+ export declare abstract class BaseCommand extends Command {
9
+ protected configService: ConfigService;
10
+ protected entityService: EntityService;
11
+ protected focusService: FocusService;
12
+ protected gitService: GitService;
13
+ protected ticketService: TicketService;
14
+ protected get requiresInit(): boolean;
15
+ protected formatPath(path: string): string;
16
+ protected getStatusIcon(hasChanges: boolean): string;
17
+ protected getTypeEmoji(type: EntityType | string): string;
18
+ init(): Promise<void>;
19
+ protected printEntityList(entities: Entity[]): void;
20
+ protected withFocus<T>(callback: (entities: Entity[]) => Promise<T>): Promise<T>;
21
+ protected withSpinner<T>(text: string, callback: () => Promise<T>): Promise<T>;
22
+ }
@@ -0,0 +1,99 @@
1
+ import { Command } from '@oclif/core';
2
+ import { ConfigService } from './services/config.service.js';
3
+ import { EntityService } from './services/entity.service.js';
4
+ import { FocusService } from './services/focus.service.js';
5
+ import { GitService } from './services/git.service.js';
6
+ import { TicketService } from './services/ticket.service.js';
7
+ const ENTITY_TYPE_EMOJI = {
8
+ client: '🏢',
9
+ company: '🏛️',
10
+ delivery: '📦',
11
+ initiative: '🚀',
12
+ module: '📚',
13
+ prospect: '🎯',
14
+ service: '🔧',
15
+ system: '⚙️',
16
+ tool: '🛠️',
17
+ };
18
+ const DEFAULT_ENTITY_EMOJI = '📁';
19
+ export class BaseCommand extends Command {
20
+ configService;
21
+ entityService;
22
+ focusService;
23
+ gitService;
24
+ ticketService;
25
+ get requiresInit() {
26
+ // Override in commands that don't require initialization
27
+ return true;
28
+ }
29
+ formatPath(path) {
30
+ const workspaceRoot = this.configService.getWorkspaceRoot();
31
+ if (path.startsWith(workspaceRoot)) {
32
+ return path.slice(Math.max(0, workspaceRoot.length + 1));
33
+ }
34
+ return path;
35
+ }
36
+ getStatusIcon(hasChanges) {
37
+ return hasChanges ? '●' : '✓';
38
+ }
39
+ getTypeEmoji(type) {
40
+ return ENTITY_TYPE_EMOJI[type] ?? DEFAULT_ENTITY_EMOJI;
41
+ }
42
+ async init() {
43
+ await super.init();
44
+ // Initialize services
45
+ this.configService = new ConfigService();
46
+ this.entityService = new EntityService(this.configService);
47
+ this.focusService = new FocusService(this.configService);
48
+ this.gitService = new GitService();
49
+ this.ticketService = new TicketService(this.configService);
50
+ // Check workspace initialization for commands that require it
51
+ if (this.requiresInit && !this.configService.isInitialized()) {
52
+ this.error('Workspace not initialized. Run "gut init" first.');
53
+ }
54
+ }
55
+ printEntityList(entities) {
56
+ if (entities.length === 0) {
57
+ this.log('No entities configured');
58
+ return;
59
+ }
60
+ // Group by type
61
+ const byType = {};
62
+ for (const entity of entities) {
63
+ if (!byType[entity.type]) {
64
+ byType[entity.type] = [];
65
+ }
66
+ byType[entity.type].push(entity);
67
+ }
68
+ // Display grouped
69
+ for (const [type, typeEntities] of Object.entries(byType)) {
70
+ this.log(`\n${this.getTypeEmoji(type)} ${type.charAt(0).toUpperCase() + type.slice(1)} entities:`);
71
+ for (const entity of typeEntities) {
72
+ this.log(` • ${entity.name} (${entity.path})`);
73
+ if (entity.description) {
74
+ this.log(` ${entity.description}`);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ async withFocus(callback) {
80
+ const entities = await this.focusService.getFocusedEntities();
81
+ if (entities.length === 0) {
82
+ this.error('No entities focused. Use "gut focus <entity>" first.');
83
+ }
84
+ return callback(entities);
85
+ }
86
+ async withSpinner(text, callback) {
87
+ // Note: oclif doesn't have built-in spinner, we'll use ora later
88
+ this.log(text);
89
+ try {
90
+ const result = await callback();
91
+ this.log('✓ Done');
92
+ return result;
93
+ }
94
+ catch (error) {
95
+ this.log('✗ Failed');
96
+ throw error;
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Add extends BaseCommand {
3
+ static args: {
4
+ path: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ patch: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ private buildGitAddArgs;
14
+ }
@@ -0,0 +1,70 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { BaseCommand } from '../base-command.js';
5
+ export default class Add extends BaseCommand {
6
+ static args = {
7
+ path: Args.string({
8
+ default: '.',
9
+ description: 'Path(s) to stage',
10
+ required: false,
11
+ }),
12
+ };
13
+ static description = 'Stage changes in focused entities';
14
+ static examples = [
15
+ '<%= config.bin %> <%= command.id %>',
16
+ '<%= config.bin %> <%= command.id %> .',
17
+ '<%= config.bin %> <%= command.id %> src/',
18
+ '<%= config.bin %> <%= command.id %> --all',
19
+ ];
20
+ static flags = {
21
+ all: Flags.boolean({
22
+ char: 'A',
23
+ default: false,
24
+ description: 'Stage all changes (equivalent to git add -A)',
25
+ }),
26
+ patch: Flags.boolean({
27
+ char: 'p',
28
+ default: false,
29
+ description: 'Interactive staging',
30
+ }),
31
+ };
32
+ async run() {
33
+ const { args, flags } = await this.parse(Add);
34
+ const focusedEntities = await this.focusService.getFocusedEntities();
35
+ if (focusedEntities.length === 0) {
36
+ this.error('No entities are focused. Use "gut focus <entity>" first.');
37
+ }
38
+ this.log(chalk.bold('\n📝 Staging changes in focused entities\n'));
39
+ for (const entity of focusedEntities) {
40
+ const spinner = ora(`Staging in ${chalk.cyan(entity.name)}`).start();
41
+ try {
42
+ const gitAddArgs = this.buildGitAddArgs(flags, args.path);
43
+ await this.gitService.add(entity.path, gitAddArgs);
44
+ // Get status to check what was staged
45
+ const status = await this.gitService.getStatus(entity.path);
46
+ const staged = status.changes.length;
47
+ if (staged > 0) {
48
+ spinner.succeed(chalk.green(`✓ ${entity.name}: ${staged} file(s) staged`));
49
+ }
50
+ else {
51
+ spinner.info(chalk.yellow(`${entity.name}: No changes to stage`));
52
+ }
53
+ }
54
+ catch (error) {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ spinner.fail(chalk.red(`✗ ${entity.name}: ${message}`));
57
+ }
58
+ }
59
+ this.log(chalk.dim('\nTip: Use "gut commit" to commit staged changes'));
60
+ }
61
+ buildGitAddArgs(flags, path) {
62
+ if (flags.all) {
63
+ return ['-A'];
64
+ }
65
+ if (flags.patch) {
66
+ return ['-p', path];
67
+ }
68
+ return [path];
69
+ }
70
+ }
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Affected extends BaseCommand {
3
+ static args: {
4
+ entity: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ 'include-docs': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ 'include-tests': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ since: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ run(): Promise<void>;
15
+ private analyzeAffected;
16
+ private checkAPIChanges;
17
+ private checkConfigChanges;
18
+ private checkDependencies;
19
+ private checkSchemaChanges;
20
+ private getChangedFiles;
21
+ private getEntityFiles;
22
+ private printAffected;
23
+ }
@@ -0,0 +1,323 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { execSync } from 'node:child_process';
4
+ import * as fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import ora from 'ora';
7
+ import { BaseCommand } from '../base-command.js';
8
+ export default class Affected extends BaseCommand {
9
+ static args = {
10
+ entity: Args.string({
11
+ description: 'Entity to analyze (uses current focus if not provided)',
12
+ name: 'entity',
13
+ required: false,
14
+ }),
15
+ };
16
+ static description = 'Detect entities potentially affected by changes in current focus';
17
+ static examples = [
18
+ '<%= config.bin %> <%= command.id %>',
19
+ '<%= config.bin %> <%= command.id %> my-app',
20
+ '<%= config.bin %> <%= command.id %> --since HEAD~5',
21
+ '<%= config.bin %> <%= command.id %> --include-tests',
22
+ ];
23
+ static flags = {
24
+ 'include-docs': Flags.boolean({
25
+ default: false,
26
+ description: 'Include documentation changes in analysis',
27
+ }),
28
+ 'include-tests': Flags.boolean({
29
+ default: false,
30
+ description: 'Include test file changes in analysis',
31
+ }),
32
+ since: Flags.string({
33
+ char: 's',
34
+ default: 'HEAD~1',
35
+ description: 'Git reference to compare against',
36
+ }),
37
+ verbose: Flags.boolean({
38
+ char: 'v',
39
+ default: false,
40
+ description: 'Show detailed analysis',
41
+ }),
42
+ };
43
+ async run() {
44
+ const { args, flags } = await this.parse(Affected);
45
+ const { 'include-docs': includeDocs, 'include-tests': includeTests, since, verbose } = flags;
46
+ const spinner = ora();
47
+ try {
48
+ let sourceEntities = [];
49
+ if (args.entity) {
50
+ const entity = await this.entityService.getEntity(args.entity);
51
+ if (!entity) {
52
+ this.error(`Entity '${args.entity}' not found`);
53
+ }
54
+ sourceEntities = [entity];
55
+ }
56
+ else {
57
+ const focus = await this.focusService.getCurrentFocus();
58
+ if (!focus || !focus.entities || focus.entities.length === 0) {
59
+ this.error('No entity specified and no current focus');
60
+ }
61
+ const entities = await Promise.all(focus.entities.map((e) => this.entityService.getEntity(e.name)));
62
+ sourceEntities = entities.filter(e => e !== null);
63
+ }
64
+ spinner.start('Analyzing changes and dependencies');
65
+ const changedFiles = await this.getChangedFiles(sourceEntities, since, includeTests, includeDocs);
66
+ if (changedFiles.length === 0) {
67
+ spinner.succeed('No changes detected');
68
+ this.log(chalk.yellow('No file changes found since ' + since));
69
+ return;
70
+ }
71
+ const allEntities = await this.entityService.listEntities();
72
+ const otherEntities = allEntities.filter(e => !sourceEntities.some(se => se.name === e.name));
73
+ const affected = await this.analyzeAffected(sourceEntities, otherEntities, changedFiles);
74
+ spinner.succeed(`Analysis complete: ${affected.length} entities potentially affected`);
75
+ if (affected.length === 0) {
76
+ this.log(chalk.green('No other entities appear to be affected by the changes'));
77
+ return;
78
+ }
79
+ this.printAffected(affected, changedFiles, verbose);
80
+ }
81
+ catch (error) {
82
+ spinner.fail();
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ this.error(message);
85
+ }
86
+ }
87
+ async analyzeAffected(sourceEntities, targetEntities, changedFiles) {
88
+ const affected = [];
89
+ for (const target of targetEntities) {
90
+ const reasons = [];
91
+ let confidence = 'low';
92
+ // Check direct dependencies
93
+ const depReason = await this.checkDependencies(sourceEntities, target);
94
+ if (depReason) {
95
+ reasons.push(depReason);
96
+ confidence = 'high';
97
+ }
98
+ // Check for API changes
99
+ const apiChanges = this.checkAPIChanges(changedFiles, target);
100
+ if (apiChanges) {
101
+ reasons.push(apiChanges);
102
+ if (confidence === 'low')
103
+ confidence = 'medium';
104
+ }
105
+ // Check for shared configuration
106
+ const configChanges = this.checkConfigChanges(changedFiles, target);
107
+ if (configChanges) {
108
+ reasons.push(configChanges);
109
+ if (confidence === 'low')
110
+ confidence = 'medium';
111
+ }
112
+ // Check for database/schema changes
113
+ const schemaChanges = this.checkSchemaChanges(changedFiles, target);
114
+ if (schemaChanges) {
115
+ reasons.push(schemaChanges);
116
+ confidence = 'high';
117
+ }
118
+ if (reasons.length > 0) {
119
+ affected.push({ confidence, entity: target, reason: reasons });
120
+ }
121
+ }
122
+ // Sort by confidence
123
+ affected.sort((a, b) => {
124
+ const confidenceOrder = { high: 3, low: 1, medium: 2 };
125
+ return confidenceOrder[b.confidence] - confidenceOrder[a.confidence];
126
+ });
127
+ return affected;
128
+ }
129
+ checkAPIChanges(changedFiles, target) {
130
+ const apiPatterns = [
131
+ /api\//i,
132
+ /routes\//i,
133
+ /controllers\//i,
134
+ /services\//i,
135
+ /endpoints\//i,
136
+ /graphql\//i,
137
+ ];
138
+ const apiFiles = changedFiles.filter(f => apiPatterns.some(pattern => pattern.test(f)));
139
+ if (apiFiles.length > 0) {
140
+ // Check if target might consume these APIs
141
+ const targetFiles = this.getEntityFiles(target.path);
142
+ const hasApiConsumption = targetFiles.some(f => f.includes('api') || f.includes('client') || f.includes('service'));
143
+ if (hasApiConsumption) {
144
+ return `API changes detected (${apiFiles.length} files)`;
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+ checkConfigChanges(changedFiles, _target) {
150
+ const configPatterns = [
151
+ /config\./i,
152
+ /\.env/,
153
+ /settings\./i,
154
+ /constants\./i,
155
+ ];
156
+ const configFiles = changedFiles.filter(f => configPatterns.some(pattern => pattern.test(path.basename(f))));
157
+ if (configFiles.length > 0) {
158
+ return `Configuration changes detected (${configFiles.length} files)`;
159
+ }
160
+ return null;
161
+ }
162
+ async checkDependencies(sources, target) {
163
+ const targetPkgPath = path.join(target.path, 'package.json');
164
+ if (!fs.existsSync(targetPkgPath)) {
165
+ return null;
166
+ }
167
+ try {
168
+ const targetPkg = JSON.parse(fs.readFileSync(targetPkgPath, 'utf8'));
169
+ const dependencies = {
170
+ ...targetPkg.dependencies,
171
+ ...targetPkg.devDependencies,
172
+ ...targetPkg.peerDependencies,
173
+ };
174
+ for (const source of sources) {
175
+ const sourcePkgPath = path.join(source.path, 'package.json');
176
+ if (fs.existsSync(sourcePkgPath)) {
177
+ const sourcePkg = JSON.parse(fs.readFileSync(sourcePkgPath, 'utf8'));
178
+ if (sourcePkg.name && dependencies[sourcePkg.name]) {
179
+ return `Depends on ${sourcePkg.name}`;
180
+ }
181
+ }
182
+ }
183
+ }
184
+ catch {
185
+ // Ignore parsing errors
186
+ }
187
+ return null;
188
+ }
189
+ checkSchemaChanges(changedFiles, target) {
190
+ const schemaPatterns = [
191
+ /schema\./i,
192
+ /migration/i,
193
+ /\.sql$/,
194
+ /models?\//i,
195
+ /entities\//i,
196
+ ];
197
+ const schemaFiles = changedFiles.filter(f => schemaPatterns.some(pattern => pattern.test(f)));
198
+ if (schemaFiles.length > 0) {
199
+ // Check if target uses database
200
+ const targetFiles = this.getEntityFiles(target.path);
201
+ const hasDatabase = targetFiles.some(f => f.includes('model') || f.includes('entity') || f.includes('schema') || f.includes('database'));
202
+ if (hasDatabase) {
203
+ return `Database/Schema changes detected (${schemaFiles.length} files)`;
204
+ }
205
+ }
206
+ return null;
207
+ }
208
+ async getChangedFiles(entities, since, includeTests, includeDocs) {
209
+ const changedFiles = [];
210
+ for (const entity of entities) {
211
+ const entityPath = path.resolve(entity.path);
212
+ if (!fs.existsSync(path.join(entityPath, '.git'))) {
213
+ continue;
214
+ }
215
+ try {
216
+ const diff = execSync(`git diff ${since} --name-only`, {
217
+ cwd: entityPath,
218
+ encoding: 'utf8',
219
+ stdio: 'pipe',
220
+ }).toString();
221
+ const files = diff.split('\n')
222
+ .filter(f => f.trim())
223
+ .filter(f => {
224
+ if (!includeTests && (f.includes('test') || f.includes('spec'))) {
225
+ return false;
226
+ }
227
+ if (!includeDocs && (f.endsWith('.md') || f.includes('docs/'))) {
228
+ return false;
229
+ }
230
+ return true;
231
+ })
232
+ .map(f => path.join(entityPath, f));
233
+ changedFiles.push(...files);
234
+ }
235
+ catch {
236
+ // Ignore git errors
237
+ }
238
+ }
239
+ return [...new Set(changedFiles)];
240
+ }
241
+ getEntityFiles(entityPath) {
242
+ const files = [];
243
+ if (!fs.existsSync(entityPath))
244
+ return files;
245
+ const walkDir = (dir) => {
246
+ try {
247
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
248
+ for (const entry of entries) {
249
+ if (entry.name === 'node_modules' || entry.name === '.git')
250
+ continue;
251
+ const fullPath = path.join(dir, entry.name);
252
+ if (entry.isDirectory()) {
253
+ walkDir(fullPath);
254
+ }
255
+ else {
256
+ files.push(path.relative(entityPath, fullPath));
257
+ }
258
+ }
259
+ }
260
+ catch {
261
+ // Ignore permission errors
262
+ }
263
+ };
264
+ walkDir(entityPath);
265
+ return files;
266
+ }
267
+ printAffected(affected, changedFiles, verbose) {
268
+ this.log('');
269
+ this.log(chalk.bold(`Changed files: ${changedFiles.length}`));
270
+ if (verbose) {
271
+ this.log(chalk.dim('Changed:'));
272
+ for (const f of changedFiles.slice(0, 5)) {
273
+ this.log(chalk.dim(` - ${path.basename(f)}`));
274
+ }
275
+ if (changedFiles.length > 5) {
276
+ this.log(chalk.dim(` ... and ${changedFiles.length - 5} more`));
277
+ }
278
+ }
279
+ this.log('');
280
+ this.log(chalk.bold('Potentially affected entities:'));
281
+ this.log('');
282
+ const groupedByConfidence = {
283
+ high: affected.filter(a => a.confidence === 'high'),
284
+ low: affected.filter(a => a.confidence === 'low'),
285
+ medium: affected.filter(a => a.confidence === 'medium'),
286
+ };
287
+ if (groupedByConfidence.high.length > 0) {
288
+ this.log(chalk.red.bold('High confidence:'));
289
+ for (const a of groupedByConfidence.high) {
290
+ this.log(` ${chalk.red('●')} ${chalk.bold(a.entity.name)}`);
291
+ for (const r of a.reason) {
292
+ this.log(chalk.dim(` - ${r}`));
293
+ }
294
+ }
295
+ this.log('');
296
+ }
297
+ if (groupedByConfidence.medium.length > 0) {
298
+ this.log(chalk.yellow.bold('Medium confidence:'));
299
+ for (const a of groupedByConfidence.medium) {
300
+ this.log(` ${chalk.yellow('●')} ${chalk.bold(a.entity.name)}`);
301
+ if (verbose) {
302
+ for (const r of a.reason) {
303
+ this.log(chalk.dim(` - ${r}`));
304
+ }
305
+ }
306
+ }
307
+ this.log('');
308
+ }
309
+ if (groupedByConfidence.low.length > 0) {
310
+ this.log(chalk.dim('Low confidence:'));
311
+ for (const a of groupedByConfidence.low) {
312
+ this.log(` ${chalk.gray('●')} ${a.entity.name}`);
313
+ if (verbose) {
314
+ for (const r of a.reason) {
315
+ this.log(chalk.dim(` - ${r}`));
316
+ }
317
+ }
318
+ }
319
+ }
320
+ this.log('');
321
+ this.log(chalk.dim('Tip: Use --verbose flag for detailed analysis'));
322
+ }
323
+ }
@@ -0,0 +1,33 @@
1
+ import { BaseCommand } from '../base-command.js';
2
+ export default class Audit extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ access: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ changes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ compliance: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ entity: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ security: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ private auditEntity;
15
+ private auditFileStructure;
16
+ private auditGitRepository;
17
+ private auditMetadata;
18
+ private calculateMetadataCompleteness;
19
+ private calculateStructureScore;
20
+ private displayAuditResults;
21
+ private displayEntityAudit;
22
+ private displayRecommendations;
23
+ private getAllFiles;
24
+ private getAuditTypes;
25
+ private getExpectedFiles;
26
+ private getRequiredMetadataFields;
27
+ private hasNestedField;
28
+ private hasSpecificAuditType;
29
+ private performAccessAudit;
30
+ private performAudit;
31
+ private performComplianceAudit;
32
+ private performSecurityAudit;
33
+ }