devbrain-cli 0.1.0 → 0.2.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 (37) hide show
  1. package/dist/bin.js +50 -1
  2. package/dist/commands/history.command.d.ts +10 -0
  3. package/dist/commands/history.command.js +66 -0
  4. package/dist/commands/hook.command.d.ts +14 -0
  5. package/dist/commands/hook.command.js +40 -0
  6. package/dist/commands/init.command.d.ts +1 -0
  7. package/dist/commands/init.command.js +51 -15
  8. package/dist/commands/learn.command.d.ts +14 -4
  9. package/dist/commands/learn.command.js +148 -43
  10. package/dist/commands/status.command.d.ts +12 -0
  11. package/dist/commands/status.command.js +82 -0
  12. package/node_modules/@devbrain/core/dist/analysis/analyzers/source-code.analyzer.d.ts +24 -0
  13. package/node_modules/@devbrain/core/dist/analysis/analyzers/source-code.analyzer.js +126 -0
  14. package/node_modules/@devbrain/core/dist/context/context.service.d.ts +6 -7
  15. package/node_modules/@devbrain/core/dist/context/context.service.js +102 -16
  16. package/node_modules/@devbrain/core/dist/engine/dependency.resolver.d.ts +12 -0
  17. package/node_modules/@devbrain/core/dist/engine/dependency.resolver.js +101 -0
  18. package/node_modules/@devbrain/core/dist/engine/history.manager.d.ts +24 -0
  19. package/node_modules/@devbrain/core/dist/engine/history.manager.js +64 -0
  20. package/node_modules/@devbrain/core/dist/engine/lock.manager.d.ts +21 -0
  21. package/node_modules/@devbrain/core/dist/engine/lock.manager.js +89 -0
  22. package/node_modules/@devbrain/core/dist/engine/logger.service.d.ts +16 -0
  23. package/node_modules/@devbrain/core/dist/engine/logger.service.js +49 -0
  24. package/node_modules/@devbrain/core/dist/engine/memory.engine.d.ts +30 -0
  25. package/node_modules/@devbrain/core/dist/engine/memory.engine.js +200 -0
  26. package/node_modules/@devbrain/core/dist/git/git.service.d.ts +42 -0
  27. package/node_modules/@devbrain/core/dist/git/git.service.js +192 -0
  28. package/node_modules/@devbrain/core/dist/index.d.ts +7 -0
  29. package/node_modules/@devbrain/core/dist/index.js +7 -0
  30. package/node_modules/@devbrain/core/dist/memory/memory.service.d.ts +6 -3
  31. package/node_modules/@devbrain/core/dist/memory/memory.service.js +186 -6
  32. package/node_modules/@devbrain/core/package.json +2 -2
  33. package/node_modules/@devbrain/shared/dist/constants.d.ts +10 -2
  34. package/node_modules/@devbrain/shared/dist/constants.js +11 -2
  35. package/node_modules/@devbrain/shared/dist/types.d.ts +34 -0
  36. package/node_modules/@devbrain/shared/package.json +1 -1
  37. package/package.json +3 -3
package/dist/bin.js CHANGED
@@ -3,6 +3,9 @@ import { Command } from 'commander';
3
3
  import { InitCommand } from './commands/init.command.js';
4
4
  import { LearnCommand } from './commands/learn.command.js';
5
5
  import { ContextCommand } from './commands/context.command.js';
6
+ import { StatusCommand } from './commands/status.command.js';
7
+ import { HistoryCommand } from './commands/history.command.js';
8
+ import { HookCommand } from './commands/hook.command.js';
6
9
  import { DEVBRAIN_VERSION } from '@devbrain/shared';
7
10
  const program = new Command();
8
11
  program
@@ -23,11 +26,17 @@ program
23
26
  .command('learn')
24
27
  .description('Scan repository files and update documentation memory deterministically')
25
28
  .option('-d, --debug', 'Enable verbose debug outputs and print stack traces')
29
+ .option('-f, --force', 'Force a full scanned refresh of memory')
30
+ .option('--silent', 'Execute silently in the background')
26
31
  .option('--no-gitignore', 'Skip matching .gitignore exclude rules')
27
32
  .action(async (options) => {
28
33
  const cwd = process.cwd();
29
34
  const cmd = new LearnCommand();
30
- await cmd.execute(cwd, !!options.debug, { respectGitignore: options.gitignore !== false });
35
+ await cmd.execute(cwd, !!options.debug, {
36
+ force: !!options.force,
37
+ silent: !!options.silent,
38
+ respectGitignore: options.gitignore !== false,
39
+ });
31
40
  });
32
41
  program
33
42
  .command('context')
@@ -39,6 +48,46 @@ program
39
48
  const cmd = new ContextCommand();
40
49
  await cmd.execute(cwd, !!options.debug, { raw: !!options.raw });
41
50
  });
51
+ program
52
+ .command('status')
53
+ .description('Display autonomous Git integration status and repository statistics')
54
+ .option('-d, --debug', 'Enable verbose debug outputs and print stack traces')
55
+ .action(async (options) => {
56
+ const cwd = process.cwd();
57
+ const cmd = new StatusCommand();
58
+ await cmd.execute(cwd, !!options.debug, {});
59
+ });
60
+ program
61
+ .command('history')
62
+ .description('Display commit history processing logs and version metrics')
63
+ .option('-d, --debug', 'Enable verbose debug outputs and print stack traces')
64
+ .action(async (options) => {
65
+ const cwd = process.cwd();
66
+ const cmd = new HistoryCommand();
67
+ await cmd.execute(cwd, !!options.debug, {});
68
+ });
69
+ // Git Hook Subcommand group
70
+ const hookGroup = program
71
+ .command('hook')
72
+ .description('Manage DevBrain autonomous Git integration hooks');
73
+ hookGroup
74
+ .command('install')
75
+ .description('Install the post-commit git hook')
76
+ .option('-d, --debug', 'Enable verbose debug outputs and print stack traces')
77
+ .action(async (options) => {
78
+ const cwd = process.cwd();
79
+ const cmd = new HookCommand();
80
+ await cmd.execute(cwd, !!options.debug, { action: 'install' });
81
+ });
82
+ hookGroup
83
+ .command('remove')
84
+ .description('Remove the post-commit git hook')
85
+ .option('-d, --debug', 'Enable verbose debug outputs and print stack traces')
86
+ .action(async (options) => {
87
+ const cwd = process.cwd();
88
+ const cmd = new HookCommand();
89
+ await cmd.execute(cwd, !!options.debug, { action: 'remove' });
90
+ });
42
91
  // Handle unknown commands
43
92
  program.on('command:*', () => {
44
93
  console.error('Invalid command: %s\nUse --help for a list of available commands.', program.args.join(' '));
@@ -0,0 +1,10 @@
1
+ import { CommandContext } from '@devbrain/shared';
2
+ import { CommandPipeline } from '../pipeline/command.pipeline.js';
3
+ export declare class HistoryCommand extends CommandPipeline {
4
+ private historyManager;
5
+ protected validate(context: CommandContext, _args: any): Promise<void>;
6
+ protected createContext(context: CommandContext, _args: any): Promise<void>;
7
+ protected executeService(context: CommandContext, _args: any): Promise<any>;
8
+ protected writeOutput(_context: CommandContext, _output: any, _args: any): Promise<void>;
9
+ protected reportResult(context: CommandContext, output: any, _args: any): Promise<void>;
10
+ }
@@ -0,0 +1,66 @@
1
+ import { join } from 'node:path';
2
+ import { DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME, VERSION_FILE_NAME } from '@devbrain/shared';
3
+ import { HistoryManager } from '@devbrain/core';
4
+ import { CommandPipeline } from '../pipeline/command.pipeline.js';
5
+ import chalk from 'chalk';
6
+ export class HistoryCommand extends CommandPipeline {
7
+ historyManager;
8
+ async validate(context, _args) {
9
+ const configPath = join(context.cwd, DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME);
10
+ if (!(await this.fsService.exists(configPath))) {
11
+ throw new Error('DevBrain is not initialized. Run "devbrain init" first.');
12
+ }
13
+ }
14
+ async createContext(context, _args) {
15
+ this.historyManager = new HistoryManager(context.cwd, this.fsService);
16
+ }
17
+ async executeService(context, _args) {
18
+ const history = await this.historyManager.loadHistory();
19
+ const versionPath = join(context.cwd, DEVBRAIN_DIR_NAME, VERSION_FILE_NAME);
20
+ let versionInfo = null;
21
+ if (await this.fsService.exists(versionPath)) {
22
+ try {
23
+ versionInfo = await this.fsService.readJson(versionPath);
24
+ }
25
+ catch {
26
+ // Ignore
27
+ }
28
+ }
29
+ return {
30
+ history,
31
+ versionInfo,
32
+ };
33
+ }
34
+ async writeOutput(_context, _output, _args) {
35
+ // No-op
36
+ }
37
+ async reportResult(context, output, _args) {
38
+ this.logger.header('DevBrain Commit Processing History');
39
+ const history = output.history;
40
+ console.log(`- Latest Processed Commit: ${chalk.yellow(history.lastProcessedCommit || 'none')}`);
41
+ console.log(`- Memory Schema Version: ${chalk.green(output.versionInfo?.version || '0.1.0')}`);
42
+ console.log(`- Total Commits Learned: ${chalk.cyan(history.commits.length)}`);
43
+ console.log('');
44
+ if (history.commits.length === 0) {
45
+ console.log(chalk.gray('No commit learning logs recorded. Run a commit or "devbrain learn" to populate history.'));
46
+ return;
47
+ }
48
+ console.log(chalk.bold('Learned Commits Log:'));
49
+ // Display up to 10 recent commits
50
+ const displayedCommits = history.commits.slice(0, 10);
51
+ for (const commit of displayedCommits) {
52
+ const hash = chalk.yellow(commit.commitHash);
53
+ const date = chalk.gray(new Date(commit.timestamp).toLocaleString());
54
+ const msg = commit.commitMessage.split('\n')[0];
55
+ const statusIcon = commit.status === 'success'
56
+ ? chalk.green('✔')
57
+ : commit.status === 'skipped' ? chalk.yellow('ℹ') : chalk.red('✖');
58
+ console.log(` ${statusIcon} ${hash} [${date}] - ${msg}`);
59
+ }
60
+ if (history.commits.length > 10) {
61
+ console.log(chalk.gray(`\n...and ${history.commits.length - 10} more older entries.`));
62
+ }
63
+ console.log('');
64
+ }
65
+ }
66
+ //# sourceMappingURL=history.command.js.map
@@ -0,0 +1,14 @@
1
+ import { CommandContext } from '@devbrain/shared';
2
+ import { CommandPipeline } from '../pipeline/command.pipeline.js';
3
+ interface HookArgs {
4
+ action: 'install' | 'remove';
5
+ }
6
+ export declare class HookCommand extends CommandPipeline<HookArgs> {
7
+ private gitService;
8
+ protected validate(context: CommandContext, _args: HookArgs): Promise<void>;
9
+ protected createContext(context: CommandContext, _args: HookArgs): Promise<void>;
10
+ protected executeService(context: CommandContext, args: HookArgs): Promise<void>;
11
+ protected writeOutput(_context: CommandContext, _output: any, _args: HookArgs): Promise<void>;
12
+ protected reportResult(context: CommandContext, _output: any, args: HookArgs): Promise<void>;
13
+ }
14
+ export {};
@@ -0,0 +1,40 @@
1
+ import { join } from 'node:path';
2
+ import { DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME } from '@devbrain/shared';
3
+ import { GitService } from '@devbrain/core';
4
+ import { CommandPipeline } from '../pipeline/command.pipeline.js';
5
+ export class HookCommand extends CommandPipeline {
6
+ gitService;
7
+ async validate(context, _args) {
8
+ const configPath = join(context.cwd, DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME);
9
+ if (!(await this.fsService.exists(configPath))) {
10
+ throw new Error('DevBrain is not initialized. Run "devbrain init" first.');
11
+ }
12
+ }
13
+ async createContext(context, _args) {
14
+ this.gitService = new GitService(context.cwd, this.fsService);
15
+ }
16
+ async executeService(context, args) {
17
+ const isGit = await this.gitService.isGitRepository();
18
+ if (!isGit) {
19
+ throw new Error('This workspace is not a valid Git repository.');
20
+ }
21
+ if (args.action === 'install') {
22
+ await this.gitService.installHook('post-commit');
23
+ }
24
+ else if (args.action === 'remove') {
25
+ await this.gitService.removeHook('post-commit');
26
+ }
27
+ }
28
+ async writeOutput(_context, _output, _args) {
29
+ // No-op
30
+ }
31
+ async reportResult(context, _output, args) {
32
+ if (args.action === 'install') {
33
+ this.logger.success('Successfully installed post-commit hook for DevBrain.');
34
+ }
35
+ else {
36
+ this.logger.success('Successfully removed post-commit hook for DevBrain.');
37
+ }
38
+ }
39
+ }
40
+ //# sourceMappingURL=hook.command.js.map
@@ -7,6 +7,7 @@ interface InitArgs {
7
7
  * Command pipeline for "devbrain init".
8
8
  */
9
9
  export declare class InitCommand extends CommandPipeline<InitArgs> {
10
+ private hookInstalled;
10
11
  protected validate(context: CommandContext, args: InitArgs): Promise<void>;
11
12
  protected createContext(context: CommandContext, _args: InitArgs): Promise<void>;
12
13
  protected executeService(context: CommandContext, _args: InitArgs): Promise<void>;
@@ -1,10 +1,12 @@
1
1
  import { join, basename } from 'node:path';
2
- import { ValidationError, DEVBRAIN_VERSION, DEFAULT_IGNORE_PATTERNS, DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME, DEFAULT_MEMORY_DIR, } from '@devbrain/shared';
2
+ import { ValidationError, DEVBRAIN_VERSION, DEFAULT_IGNORE_PATTERNS, DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME, VERSION_FILE_NAME, CUSTOM_NOTES_FILE_NAME, DEFAULT_MEMORY_DIR, } from '@devbrain/shared';
3
+ import { GitService } from '@devbrain/core';
3
4
  import { CommandPipeline } from '../pipeline/command.pipeline.js';
4
5
  /**
5
6
  * Command pipeline for "devbrain init".
6
7
  */
7
8
  export class InitCommand extends CommandPipeline {
9
+ hookInstalled = false;
8
10
  async validate(context, args) {
9
11
  const configPath = join(context.cwd, DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME);
10
12
  if (!args.force && (await this.fsService.exists(configPath))) {
@@ -12,7 +14,6 @@ export class InitCommand extends CommandPipeline {
12
14
  }
13
15
  }
14
16
  async createContext(context, _args) {
15
- // Setup default config parameters
16
17
  const projectName = basename(context.cwd);
17
18
  context.config = {
18
19
  version: DEVBRAIN_VERSION,
@@ -28,30 +29,65 @@ export class InitCommand extends CommandPipeline {
28
29
  format: 'markdown',
29
30
  directory: DEFAULT_MEMORY_DIR,
30
31
  },
32
+ auto_update: true,
33
+ enabled_hooks: ['post-commit'],
34
+ ignored_directories: [],
35
+ ignored_extensions: [],
36
+ maximum_incremental_files: 50,
37
+ documentation_generation: true,
38
+ context_generation: true,
39
+ verbose_logging: false,
40
+ safe_mode: true,
31
41
  };
32
42
  }
33
43
  async executeService(context, _args) {
34
44
  const devbrainPath = join(context.cwd, DEVBRAIN_DIR_NAME);
35
45
  const configPath = join(devbrainPath, CONFIG_FILE_NAME);
36
- const memoryPath = join(context.cwd, context.config.memory.directory);
37
- // Create directories
46
+ const versionPath = join(devbrainPath, VERSION_FILE_NAME);
47
+ const customNotesPath = join(devbrainPath, CUSTOM_NOTES_FILE_NAME);
48
+ // Create directories & write config
38
49
  await this.fsService.writeJson(configPath, context.config);
39
- // Generate starter memory summary
40
- const summaryPath = join(memoryPath, 'summary.md');
41
- const initialSummary = [
42
- `# Project Summary: ${context.config.project.name}`,
43
- '',
44
- '## Overview',
45
- 'This project has been initialized with DevBrain. Run "devbrain learn" to populate structured memory.',
46
- '',
47
- ].join('\n');
48
- await this.fsService.write(summaryPath, initialSummary);
50
+ // Create version.json
51
+ const versionData = {
52
+ version: DEVBRAIN_VERSION,
53
+ memorySchema: 2,
54
+ lastMigration: new Date().toISOString().split('T')[0],
55
+ };
56
+ await this.fsService.writeJson(versionPath, versionData);
57
+ // Write starter custom_notes.md if not present
58
+ if (!(await this.fsService.exists(customNotesPath))) {
59
+ const defaultCustomNotes = [
60
+ '# Custom Developer Notes',
61
+ '',
62
+ 'Use this file to document manual project conventions, architecture diagrams, decision logs, or any other notes.',
63
+ 'This file will NEVER be overwritten by DevBrain.',
64
+ '',
65
+ ].join('\n');
66
+ await this.fsService.write(customNotesPath, defaultCustomNotes);
67
+ }
68
+ // Git Hook Integration
69
+ try {
70
+ const gitService = new GitService(context.cwd, this.fsService);
71
+ if (await gitService.isGitRepository()) {
72
+ await gitService.installHook('post-commit');
73
+ this.hookInstalled = true;
74
+ }
75
+ }
76
+ catch (err) {
77
+ this.logger.warn(`Failed to configure Git hooks: ${err.message || err}`);
78
+ }
49
79
  }
50
80
  async writeOutput(_context, _output, _args) {
51
- // No-op: files written in execution service
81
+ // No-op
52
82
  }
53
83
  async reportResult(context, _output, _args) {
54
84
  this.logger.success(`Initialized DevBrain project memory directory at ${join(context.cwd, DEVBRAIN_DIR_NAME)}`);
85
+ if (this.hookInstalled) {
86
+ this.logger.success('Automatically installed post-commit Git hook.');
87
+ }
88
+ else {
89
+ this.logger.warn('Git hooks not installed (directory is not a Git repository).');
90
+ }
55
91
  }
56
92
  }
57
93
  //# sourceMappingURL=init.command.js.map
@@ -1,17 +1,27 @@
1
1
  import { CommandContext } from '@devbrain/shared';
2
2
  import { CommandPipeline } from '../pipeline/command.pipeline.js';
3
3
  interface LearnArgs {
4
+ force?: boolean;
5
+ silent?: boolean;
4
6
  respectGitignore?: boolean;
5
7
  }
6
8
  /**
7
9
  * Command pipeline for "devbrain learn".
8
10
  */
9
11
  export declare class LearnCommand extends CommandPipeline<LearnArgs> {
10
- private filesList;
12
+ private lockManager;
13
+ private loggerService;
14
+ private gitService;
15
+ private historyManager;
16
+ private memoryEngine;
17
+ private memoryService;
18
+ private contextService;
19
+ private stats;
11
20
  protected validate(context: CommandContext, _args: LearnArgs): Promise<void>;
12
- protected createContext(context: CommandContext, args: LearnArgs): Promise<void>;
13
- protected executeService(context: CommandContext, _args: LearnArgs): Promise<void>;
21
+ protected createContext(context: CommandContext, _args: LearnArgs): Promise<void>;
22
+ execute(cwd: string, debug: boolean, args: LearnArgs): Promise<void>;
23
+ protected executeService(context: CommandContext, args: LearnArgs): Promise<any>;
14
24
  protected writeOutput(_context: CommandContext, _output: any, _args: LearnArgs): Promise<void>;
15
- protected reportResult(context: CommandContext, _output: any, _args: LearnArgs): Promise<void>;
25
+ protected reportResult(context: CommandContext, output: any, args: LearnArgs): Promise<void>;
16
26
  }
17
27
  export {};
@@ -1,72 +1,177 @@
1
1
  import { join } from 'node:path';
2
2
  import { ValidationError, DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME, } from '@devbrain/shared';
3
- import { RepositoryScanner, AnalyzerRegistry, MemoryService, ReadmeAnalyzer, PackageJsonAnalyzer, TsconfigAnalyzer, GitAnalyzer, DockerfileAnalyzer, RequirementsTxtAnalyzer, PomXmlAnalyzer, BuildGradleAnalyzer, } from '@devbrain/core';
3
+ import { GitService, LockManager, LoggerService, HistoryManager, DependencyResolver, MemoryEngine, MemoryService, ContextService, RepositoryScanner, } from '@devbrain/core';
4
4
  import { CommandPipeline } from '../pipeline/command.pipeline.js';
5
5
  import ora from 'ora';
6
6
  /**
7
7
  * Command pipeline for "devbrain learn".
8
8
  */
9
9
  export class LearnCommand extends CommandPipeline {
10
- filesList = [];
10
+ lockManager;
11
+ loggerService;
12
+ gitService;
13
+ historyManager;
14
+ memoryEngine;
15
+ memoryService;
16
+ contextService;
17
+ stats = {
18
+ strategy: 'full',
19
+ filesCount: 0,
20
+ commitMessage: '',
21
+ commitHash: '',
22
+ };
11
23
  async validate(context, _args) {
12
24
  const configPath = join(context.cwd, DEVBRAIN_DIR_NAME, CONFIG_FILE_NAME);
13
25
  if (!(await this.fsService.exists(configPath))) {
14
26
  throw new ValidationError('DevBrain is not initialized in this repository.', 'Configuration file config.json not found.', 'Run "devbrain init" before executing "devbrain learn".');
15
27
  }
16
28
  }
17
- async createContext(context, args) {
18
- const spinner = ora('Scanning repository files...').start();
29
+ async createContext(context, _args) {
30
+ this.lockManager = new LockManager(context.cwd, this.fsService);
31
+ this.loggerService = new LoggerService(context.cwd, this.fsService);
32
+ this.gitService = new GitService(context.cwd, this.fsService);
33
+ this.historyManager = new HistoryManager(context.cwd, this.fsService);
34
+ this.memoryEngine = new MemoryEngine(context.cwd, context.config, this.fsService);
35
+ this.memoryService = new MemoryService(this.fsService);
36
+ this.contextService = new ContextService(this.fsService);
37
+ }
38
+ async execute(cwd, debug, args) {
39
+ // Override execute method to gracefully handle lock and hook errors without throwing
40
+ const isSilent = !!args.silent;
19
41
  try {
20
- const scanner = new RepositoryScanner(this.fsService);
21
- const respectGitignore = args.respectGitignore ?? context.config?.scanner.respectGitignore;
22
- this.filesList = await scanner.scan(context.cwd, {
23
- ignore: context.config?.scanner.ignore,
24
- followSymlinks: context.config?.scanner.followSymlinks,
25
- respectGitignore,
26
- });
27
- spinner.succeed(`Scanned repository: found ${this.filesList.length} files.`);
42
+ await super.execute(cwd, debug, args);
28
43
  }
29
44
  catch (error) {
30
- spinner.fail('Scanning failed.');
31
- throw error;
45
+ // Log errors in silent hook mode instead of exiting with 1
46
+ const logService = new LoggerService(cwd, this.fsService);
47
+ await logService.error(`Execution error: ${error.message || error}`, 'git-hook.log');
48
+ if (isSilent) {
49
+ console.error(`DevBrain Hook error logged: ${error.message || error}`);
50
+ process.exit(0); // Never interrupt Git commit!
51
+ }
52
+ else {
53
+ this.handleError(error, debug);
54
+ process.exit(1);
55
+ }
32
56
  }
33
57
  }
34
- async executeService(context, _args) {
35
- const spinner = ora('Analyzing code and generating memory files...').start();
58
+ async executeService(context, args) {
59
+ const isSilent = !!args.silent;
60
+ const forceFull = !!args.force;
61
+ // 1. Acquire Lock
62
+ const acquired = await this.lockManager.acquireLock();
63
+ if (!acquired) {
64
+ const msg = 'Another learning process is currently active or lock file exists.';
65
+ await this.loggerService.warn(msg, isSilent ? 'git-hook.log' : 'devbrain.log');
66
+ if (isSilent) {
67
+ return { skipped: true, reason: 'locked' };
68
+ }
69
+ throw new Error(msg + ' Ensure no other devbrain command is running, or remove .devbrain/.lock manually.');
70
+ }
71
+ const spinner = isSilent ? null : ora('DevBrain is updating project memory...').start();
36
72
  try {
37
- // Setup registry with default analyzers
38
- const registry = new AnalyzerRegistry();
39
- registry.register(new ReadmeAnalyzer(this.fsService));
40
- registry.register(new PackageJsonAnalyzer(this.fsService));
41
- registry.register(new TsconfigAnalyzer(this.fsService));
42
- registry.register(new GitAnalyzer(this.fsService));
43
- registry.register(new DockerfileAnalyzer(this.fsService));
44
- registry.register(new RequirementsTxtAnalyzer(this.fsService));
45
- registry.register(new PomXmlAnalyzer(this.fsService));
46
- registry.register(new BuildGradleAnalyzer(this.fsService));
47
- const projectContext = {
48
- cwd: context.cwd,
49
- files: this.filesList,
50
- config: context.config,
51
- };
52
- // Execute analyzers
53
- const analysis = await registry.runAll(projectContext);
54
- // Generate memory files
55
- const memoryService = new MemoryService(this.fsService);
73
+ // 2. Fetch Git Metadata
74
+ const isGitRepo = await this.gitService.isGitRepository();
75
+ const lastCommit = isGitRepo ? await this.historyManager.getLastProcessedCommit() : null;
76
+ const currentCommit = isGitRepo ? await this.gitService.getCurrentCommitHash() : null;
77
+ if (currentCommit) {
78
+ this.stats.commitHash = currentCommit;
79
+ this.stats.commitMessage = await this.gitService.getCommitMessage(currentCommit);
80
+ }
81
+ // Check if memory.json exists
82
+ const memoryExists = await this.fsService.exists(join(context.cwd, DEVBRAIN_DIR_NAME, 'memory.json'));
83
+ let analysis;
84
+ // 3. Determine Incremental vs Full
85
+ if (forceFull || !isGitRepo || !lastCommit || !memoryExists) {
86
+ // Run Full Scan
87
+ this.stats.strategy = 'full';
88
+ if (spinner)
89
+ spinner.text = 'Scanning entire project (full scan)...';
90
+ analysis = await this.memoryEngine.runFullScan();
91
+ }
92
+ else {
93
+ // Check if HEAD has actually changed
94
+ if (lastCommit === currentCommit) {
95
+ if (spinner) {
96
+ spinner.succeed('Project memory is already up to date with HEAD commit.');
97
+ }
98
+ await this.lockManager.releaseLock();
99
+ return { skipped: true, reason: 'up-to-date' };
100
+ }
101
+ // Run Incremental Scan
102
+ this.stats.strategy = 'incremental';
103
+ if (spinner)
104
+ spinner.text = 'Running incremental git-diff analysis...';
105
+ const changes = await this.gitService.getChangedFiles(lastCommit, currentCommit || 'HEAD');
106
+ // Scan directory files for context mapping
107
+ const scanner = new RepositoryScanner(this.fsService);
108
+ const allFiles = await scanner.scan(context.cwd, {
109
+ ignore: context.config?.scanner.ignore,
110
+ followSymlinks: context.config?.scanner.followSymlinks,
111
+ respectGitignore: args.respectGitignore ?? context.config?.scanner.respectGitignore,
112
+ });
113
+ const allChanged = [
114
+ ...changes.modified,
115
+ ...changes.deleted,
116
+ ...changes.renamed.map((r) => r.to),
117
+ ...changes.renamed.map((r) => r.from),
118
+ ];
119
+ // Resolve blast radius dependencies
120
+ const resolver = new DependencyResolver();
121
+ const { resolvedFiles, isFullScan } = resolver.resolveBlastRadius(allChanged, allFiles);
122
+ if (isFullScan) {
123
+ this.stats.strategy = 'full (config change)';
124
+ if (spinner)
125
+ spinner.text = 'Core config changed: scanning entire project...';
126
+ analysis = await this.memoryEngine.runFullScan();
127
+ }
128
+ else {
129
+ if (spinner)
130
+ spinner.text = `Analyzing ${resolvedFiles.length} files in blast radius...`;
131
+ analysis = await this.memoryEngine.incrementalLearn(resolvedFiles, changes.deleted, changes.renamed);
132
+ }
133
+ }
134
+ this.stats.filesCount = analysis.files.length;
135
+ // 4. Update documentation formats if enabled
56
136
  const memoryDir = join(context.cwd, context.config.memory.directory);
57
- await memoryService.generateMemory(analysis, memoryDir);
58
- spinner.succeed('Memory documentation generated successfully.');
137
+ if (context.config.documentation_generation !== false) {
138
+ if (spinner)
139
+ spinner.text = 'Regenerating project memory documentation...';
140
+ await this.memoryService.generateMemory(analysis, memoryDir);
141
+ }
142
+ if (context.config.context_generation !== false) {
143
+ if (spinner)
144
+ spinner.text = 'Regenerating AI context payload...';
145
+ await this.contextService.generateContext(analysis, memoryDir);
146
+ }
147
+ // 5. Append commit entry to history
148
+ if (isGitRepo && currentCommit) {
149
+ const historyEntry = {
150
+ commitHash: currentCommit,
151
+ timestamp: new Date().toISOString(),
152
+ status: 'success',
153
+ filesAnalyzed: this.stats.strategy.startsWith('full') ? ['*'] : [],
154
+ commitMessage: this.stats.commitMessage,
155
+ };
156
+ await this.historyManager.addCommitEntry(historyEntry);
157
+ }
158
+ if (spinner) {
159
+ spinner.succeed(`Successfully learned changes (${this.stats.strategy} scan, ${this.stats.filesCount} files).`);
160
+ }
161
+ await this.loggerService.success(`Completed learning session: ${this.stats.strategy} scan.`, isSilent ? 'git-hook.log' : 'devbrain.log');
59
162
  }
60
- catch (error) {
61
- spinner.fail('Analysis execution failed.');
62
- throw error;
163
+ finally {
164
+ // 6. Release Lock
165
+ await this.lockManager.releaseLock();
63
166
  }
64
167
  }
65
168
  async writeOutput(_context, _output, _args) {
66
- // No-op: files written in execution service
169
+ // Handled in services
67
170
  }
68
- async reportResult(context, _output, _args) {
69
- this.logger.success(`Repository documentation written to ${join(context.cwd, context.config.memory.directory)}`);
171
+ async reportResult(context, output, args) {
172
+ if (args.silent || (output && output.skipped))
173
+ return;
174
+ this.logger.success(`Documentation and context regenerated at ${join(context.cwd, DEVBRAIN_DIR_NAME)}`);
70
175
  }
71
176
  }
72
177
  //# sourceMappingURL=learn.command.js.map
@@ -0,0 +1,12 @@
1
+ import { CommandContext } from '@devbrain/shared';
2
+ import { CommandPipeline } from '../pipeline/command.pipeline.js';
3
+ export declare class StatusCommand extends CommandPipeline {
4
+ private gitService;
5
+ private lockManager;
6
+ private historyManager;
7
+ protected validate(context: CommandContext, _args: any): Promise<void>;
8
+ protected createContext(context: CommandContext, _args: any): Promise<void>;
9
+ protected executeService(context: CommandContext, _args: any): Promise<any>;
10
+ protected writeOutput(_context: CommandContext, _output: any, _args: any): Promise<void>;
11
+ protected reportResult(context: CommandContext, status: any, _args: any): Promise<void>;
12
+ }