byterover-cli 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/commands/gen-rules.d.ts +42 -4
  2. package/dist/commands/gen-rules.js +235 -39
  3. package/dist/commands/init.d.ts +43 -3
  4. package/dist/commands/init.js +262 -24
  5. package/dist/core/domain/knowledge/directory-manager.js +2 -0
  6. package/dist/core/interfaces/i-file-service.d.ts +16 -0
  7. package/dist/core/interfaces/i-legacy-rule-detector.d.ts +56 -0
  8. package/dist/hooks/init/update-notifier.d.ts +39 -0
  9. package/dist/hooks/init/update-notifier.js +47 -0
  10. package/dist/infra/cipher/llm/generators/byterover-content-generator.js +6 -0
  11. package/dist/infra/cogit/context-tree-to-push-context-mapper.js +4 -4
  12. package/dist/infra/cogit/http-cogit-push-service.js +3 -3
  13. package/dist/infra/context-tree/file-context-tree-writer-service.d.ts +2 -0
  14. package/dist/infra/context-tree/file-context-tree-writer-service.js +2 -0
  15. package/dist/infra/file/fs-file-service.d.ts +2 -0
  16. package/dist/infra/file/fs-file-service.js +23 -1
  17. package/dist/infra/rule/constants.d.ts +9 -1
  18. package/dist/infra/rule/constants.js +9 -1
  19. package/dist/infra/rule/legacy-rule-detector.d.ts +21 -0
  20. package/dist/infra/rule/legacy-rule-detector.js +106 -0
  21. package/dist/infra/rule/rule-template-service.js +14 -6
  22. package/oclif.manifest.json +1 -1
  23. package/package.json +7 -2
  24. package/dist/core/domain/errors/rule-error.d.ts +0 -6
  25. package/dist/core/domain/errors/rule-error.js +0 -12
  26. package/dist/core/interfaces/i-rule-writer-service.d.ts +0 -13
  27. package/dist/infra/rule/rule-writer-service.d.ts +0 -19
  28. package/dist/infra/rule/rule-writer-service.js +0 -39
  29. /package/dist/core/interfaces/{i-rule-writer-service.js → i-legacy-rule-detector.js} +0 -0
@@ -1,9 +1,21 @@
1
- import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
1
+ import { access, appendFile, copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import { dirname } from 'node:path';
3
3
  /**
4
4
  * File service implementation using Node.js fs module.
5
5
  */
6
6
  export class FsFileService {
7
+ async createBackup(filePath) {
8
+ try {
9
+ // Timestamp format: YYYY-MM-DD-HH-MM-SS
10
+ const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').split('T').join('-').slice(0, -5);
11
+ const backupPath = `${filePath}.backup-${timestamp}`;
12
+ await copyFile(filePath, backupPath);
13
+ return backupPath;
14
+ }
15
+ catch (error) {
16
+ throw new Error(`Failed to create backup file '${filePath}': ${error instanceof Error ? error.message : 'Unknown error'}`);
17
+ }
18
+ }
7
19
  /**
8
20
  * Checks if a file exists at the specified path.
9
21
  *
@@ -33,6 +45,16 @@ export class FsFileService {
33
45
  throw new Error(`Failed to read content from file '${filePath}': ${error instanceof Error ? error.message : String(error)}`);
34
46
  }
35
47
  }
48
+ async replaceContent(filePath, oldContent, newContent) {
49
+ try {
50
+ const currentContent = await this.read(filePath);
51
+ const updatedContent = currentContent.replace(oldContent, newContent);
52
+ await this.write(updatedContent, filePath, 'overwrite');
53
+ }
54
+ catch (error) {
55
+ throw new Error(`Failed to replace content in file '${filePath}': ${error instanceof Error ? error.message : 'Unknown error'}`);
56
+ }
57
+ }
36
58
  /**
37
59
  * Writes content to the specified file.
38
60
  * @param content The content to write.
@@ -1,4 +1,12 @@
1
1
  /**
2
2
  * ByteRover CLI generated rule template tag.
3
3
  */
4
- export declare const BR_RULE_TAG = "Generated by ByteRover CLI for";
4
+ export declare const BRV_RULE_TAG = "Generated by ByteRover CLI for";
5
+ /**
6
+ * Boundary markers for managed ByteRover rule sections.
7
+ * Used to identify and replace ByteRover-generated content in shared instruction files.
8
+ */
9
+ export declare const BRV_RULE_MARKERS: {
10
+ readonly END: "<!-- END BYTEROVER RULES -->";
11
+ readonly START: "<!-- BEGIN BYTEROVER RULES -->";
12
+ };
@@ -1,4 +1,12 @@
1
1
  /**
2
2
  * ByteRover CLI generated rule template tag.
3
3
  */
4
- export const BR_RULE_TAG = 'Generated by ByteRover CLI for';
4
+ export const BRV_RULE_TAG = 'Generated by ByteRover CLI for';
5
+ /**
6
+ * Boundary markers for managed ByteRover rule sections.
7
+ * Used to identify and replace ByteRover-generated content in shared instruction files.
8
+ */
9
+ export const BRV_RULE_MARKERS = {
10
+ END: '<!-- END BYTEROVER RULES -->',
11
+ START: '<!-- BEGIN BYTEROVER RULES -->',
12
+ };
@@ -0,0 +1,21 @@
1
+ import type { Agent } from '../../core/domain/entities/agent.js';
2
+ import type { ILegacyRuleDetector, LegacyRuleDetectionResult } from '../../core/interfaces/i-legacy-rule-detector.js';
3
+ export declare class LegacyRuleDetector implements ILegacyRuleDetector {
4
+ private static readonly SECTION_SEPARATOR_PATTERN;
5
+ private static readonly WORKFLOW_HEADER_PATTERN;
6
+ detectLegacyRules(content: string, agentName: Agent): LegacyRuleDetectionResult;
7
+ /**
8
+ * Attempts to find the start of a ByteRover rule section by working backwards from the footer.
9
+ *
10
+ * Strategy 3 (Conservative Multi-Pattern Match):
11
+ * 1. Look backwards for "# Workflow Instruction" header (most reliable)
12
+ * 2. If not found, look backwards for "---" separator before command reference
13
+ * 3. If still not found, return undefined (uncertain)
14
+ *
15
+ * @param lines All lines in the file.
16
+ * @param footerIndex Index of the line containing the footer tag (0-indexed).
17
+ * @param usedLineRanges Line ranges already claimed by detected sections.
18
+ * @returns Index of the start line (0-indexed), or undefined if uncertain.
19
+ */
20
+ private findSectionStart;
21
+ }
@@ -0,0 +1,106 @@
1
+ import { BRV_RULE_TAG } from './constants.js';
2
+ export class LegacyRuleDetector {
3
+ static SECTION_SEPARATOR_PATTERN = /^---\s*$/;
4
+ static WORKFLOW_HEADER_PATTERN = /^#\sWorkflow Instruction$/;
5
+ detectLegacyRules(content, agentName) {
6
+ // Normalize line endings to handle CRLF
7
+ const normalizedContent = content.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
8
+ const lines = normalizedContent.split('\n');
9
+ const footerTag = `${BRV_RULE_TAG} ${agentName}`;
10
+ const reliableMatches = [];
11
+ const uncertainMatches = [];
12
+ const usedLineRanges = [];
13
+ // Find all occurrences of the footer tag
14
+ for (const [index, line] of lines.entries()) {
15
+ // Use exact match to avoid matching partial agent names like "Github Copilot Extended"
16
+ if (line.trim() === footerTag) {
17
+ const footerIndex = index;
18
+ const footerLineNumber = footerIndex + 1;
19
+ // Try to find the start of this ByteRover section
20
+ const startIndex = this.findSectionStart(lines, footerIndex, usedLineRanges);
21
+ if (startIndex === undefined) {
22
+ // Uncertain match - couldn't reliably determine start
23
+ uncertainMatches.push({
24
+ footerLine: footerLineNumber,
25
+ reason: 'Could not reliably determine the start of the ByteRover rule section.',
26
+ });
27
+ }
28
+ else {
29
+ // Reliable match found
30
+ const startLineNumber = startIndex + 1;
31
+ const sectionContent = lines.slice(startIndex, footerIndex + 1).join('\n');
32
+ reliableMatches.push({
33
+ content: sectionContent,
34
+ endLine: footerLineNumber,
35
+ startLine: startLineNumber,
36
+ });
37
+ // Mark this range as used so it won't be reused by subsequent footers
38
+ usedLineRanges.push({ end: footerIndex, start: startIndex });
39
+ }
40
+ }
41
+ }
42
+ return { reliableMatches, uncertainMatches };
43
+ }
44
+ /**
45
+ * Attempts to find the start of a ByteRover rule section by working backwards from the footer.
46
+ *
47
+ * Strategy 3 (Conservative Multi-Pattern Match):
48
+ * 1. Look backwards for "# Workflow Instruction" header (most reliable)
49
+ * 2. If not found, look backwards for "---" separator before command reference
50
+ * 3. If still not found, return undefined (uncertain)
51
+ *
52
+ * @param lines All lines in the file.
53
+ * @param footerIndex Index of the line containing the footer tag (0-indexed).
54
+ * @param usedLineRanges Line ranges already claimed by detected sections.
55
+ * @returns Index of the start line (0-indexed), or undefined if uncertain.
56
+ */
57
+ findSectionStart(lines, footerIndex, usedLineRanges) {
58
+ // Strategy 1: Look for "# Workflow Instruction" header (most reliable)
59
+ for (let i = footerIndex - 1; i >= 0; i--) {
60
+ if (LegacyRuleDetector.WORKFLOW_HEADER_PATTERN.test(lines[i])) {
61
+ // Check if this header is within any already-detected section
62
+ const isWithinUsedRange = usedLineRanges.some((range) => i >= range.start && i <= range.end);
63
+ if (!isWithinUsedRange) {
64
+ return i;
65
+ }
66
+ }
67
+ }
68
+ // Strategy 2: Look for "---" separator
69
+ // We need to find the section separator that appears before the command reference
70
+ // and after the workflow content. This is less reliable but still useful.
71
+ const separatorIndices = [];
72
+ for (let i = footerIndex - 1; i >= 0; i--) {
73
+ if (LegacyRuleDetector.SECTION_SEPARATOR_PATTERN.test(lines[i])) {
74
+ separatorIndices.push(i);
75
+ }
76
+ }
77
+ // If we found at least one separator, use it as a fallback
78
+ // We want the one closest to the footer but before the "---" that precedes the footer
79
+ if (separatorIndices.length > 0) {
80
+ // The footer line should be preceded by "---", so we skip that one
81
+ // and look for the next separator going backwards
82
+ let candidateIndex;
83
+ for (const sepIndex of separatorIndices) {
84
+ const linesBetween = footerIndex - sepIndex;
85
+ if (linesBetween === 1) {
86
+ // This is the separator right before the footer, skip it
87
+ continue;
88
+ }
89
+ // Check if this separator is within any already-detected section
90
+ const isWithinUsedRange = usedLineRanges.some((range) => sepIndex >= range.start && sepIndex <= range.end);
91
+ if (isWithinUsedRange) {
92
+ continue;
93
+ }
94
+ // This could be a section separator within the ByteRover content
95
+ // Use the first one we find (closest to footer)
96
+ candidateIndex = sepIndex + 1;
97
+ break;
98
+ }
99
+ if (candidateIndex !== undefined && candidateIndex < footerIndex) {
100
+ return candidateIndex;
101
+ }
102
+ }
103
+ // Strategy 3: Could not reliably determine start
104
+ return undefined;
105
+ }
106
+ }
@@ -1,4 +1,16 @@
1
- import { BR_RULE_TAG } from './constants.js';
1
+ import { BRV_RULE_MARKERS, BRV_RULE_TAG } from './constants.js';
2
+ /**
3
+ * Wraps rule content with boundary markers for identification and replacement.
4
+ *
5
+ * @param content The rule content to wrap.
6
+ * @param agent The agent name for the footer tag.
7
+ * @param header Agent-specific header.
8
+ * @returns The wrapped content with boundary markers.
9
+ */
10
+ const wrapContentWithBoundaryMarkers = (content, agent, header) => {
11
+ const parts = [BRV_RULE_MARKERS.START, header, content, '---', `${BRV_RULE_TAG} ${agent}`, BRV_RULE_MARKERS.END];
12
+ return parts.join('\n');
13
+ };
2
14
  const guideHeaders = [
3
15
  {
4
16
  agent: 'Augment Code',
@@ -67,11 +79,7 @@ export class RuleTemplateService {
67
79
  const content = this.templateLoader.substituteVariables(baseTemplate, context);
68
80
  // Add agent-specific header if available (from develop branch)
69
81
  const header = guideHeaders.find((h) => h.agent === agent)?.value || '';
70
- return `${header}
71
- ${content}
72
- ---
73
- ${BR_RULE_TAG} ${agent}
74
- `;
82
+ return wrapContentWithBoundaryMarkers(content, agent, header);
75
83
  }
76
84
  catch (error) {
77
85
  throw new Error(`Failed to generate rule content for agent '${agent}': ${error instanceof Error ? error.message : String(error)}`);
@@ -672,5 +672,5 @@
672
672
  ]
673
673
  }
674
674
  },
675
- "version": "0.3.3"
675
+ "version": "0.3.4"
676
676
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "byterover-cli",
3
3
  "description": "ByteRover's CLI",
4
- "version": "0.3.3",
4
+ "version": "0.3.4",
5
5
  "author": "ByteRover",
6
6
  "bin": {
7
7
  "brv": "./bin/run.js"
@@ -16,6 +16,7 @@
16
16
  "@inquirer/prompts": "^7.9.0",
17
17
  "@oclif/core": "^4",
18
18
  "@oclif/plugin-help": "^6",
19
+ "@types/update-notifier": "^6.0.8",
19
20
  "axios": "^1.12.2",
20
21
  "better-sqlite3": "^11.5.0",
21
22
  "chalk": "^5.6.2",
@@ -30,6 +31,7 @@
30
31
  "nanoid": "^5.1.6",
31
32
  "open": "^10.2.0",
32
33
  "openai": "^6.9.1",
34
+ "update-notifier": "^7.3.1",
33
35
  "zod": "^3.25.76",
34
36
  "zod-to-json-schema": "^3.24.6"
35
37
  },
@@ -75,7 +77,10 @@
75
77
  "dirname": "brv",
76
78
  "commands": "./dist/commands",
77
79
  "hooks": {
78
- "init": "./dist/hooks/init/welcome",
80
+ "init": [
81
+ "./dist/hooks/init/welcome",
82
+ "./dist/hooks/init/update-notifier"
83
+ ],
79
84
  "command_not_found": "./dist/hooks/command_not_found/handle-invalid-commands",
80
85
  "error": "./dist/hooks/error/clean-errors",
81
86
  "prerun": "./dist/hooks/prerun/validate-brv-config-version"
@@ -1,6 +0,0 @@
1
- export declare class RuleError extends Error {
2
- constructor(message: string);
3
- }
4
- export declare class RuleExistsError extends RuleError {
5
- constructor(message?: string);
6
- }
@@ -1,12 +0,0 @@
1
- export class RuleError extends Error {
2
- constructor(message) {
3
- super(message);
4
- this.name = 'RuleError';
5
- }
6
- }
7
- export class RuleExistsError extends RuleError {
8
- constructor(message = 'Rule already exists') {
9
- super(message);
10
- this.name = 'RuleExistsError';
11
- }
12
- }
@@ -1,13 +0,0 @@
1
- import { Agent } from '../domain/entities/agent.js';
2
- /**
3
- * Interface for rule writer service operations.
4
- */
5
- export interface IRuleWriterService {
6
- /**
7
- * Writes a rule for the given agent.
8
- * @param agent The agent for which to write the rule.
9
- * @param force Whether to force the rule to be written even if it already exists.
10
- * @returns A promise that resolves when the rule has been written.
11
- */
12
- writeRule: (agent: Agent, force: boolean) => Promise<void>;
13
- }
@@ -1,19 +0,0 @@
1
- import { type Agent } from '../../core/domain/entities/agent.js';
2
- import { type IFileService } from '../../core/interfaces/i-file-service.js';
3
- import { IRuleTemplateService } from '../../core/interfaces/i-rule-template-service.js';
4
- import { type IRuleWriterService } from '../../core/interfaces/i-rule-writer-service.js';
5
- /**
6
- * Service for writing agent-specific rule files.
7
- * Uses IFileService to write files and RuleTemplateService to generate content.
8
- */
9
- export declare class RuleWriterService implements IRuleWriterService {
10
- private readonly fileService;
11
- private readonly templateService;
12
- /**
13
- * Creates a new RuleWriterService.
14
- * @param fileService The file service to use for writing files.
15
- * @param templateService The template service to use for generating rule content.
16
- */
17
- constructor(fileService: IFileService, templateService: IRuleTemplateService);
18
- writeRule(agent: Agent, force: boolean): Promise<void>;
19
- }
@@ -1,39 +0,0 @@
1
- import { RuleExistsError } from '../../core/domain/errors/rule-error.js';
2
- import { AGENT_RULE_CONFIGS } from './agent-rule-config.js';
3
- import { BR_RULE_TAG } from './constants.js';
4
- /**
5
- * Service for writing agent-specific rule files.
6
- * Uses IFileService to write files and RuleTemplateService to generate content.
7
- */
8
- export class RuleWriterService {
9
- fileService;
10
- templateService;
11
- /**
12
- * Creates a new RuleWriterService.
13
- * @param fileService The file service to use for writing files.
14
- * @param templateService The template service to use for generating rule content.
15
- */
16
- constructor(fileService, templateService) {
17
- this.fileService = fileService;
18
- this.templateService = templateService;
19
- }
20
- async writeRule(agent, force) {
21
- const config = AGENT_RULE_CONFIGS[agent];
22
- if (!config) {
23
- throw new Error(`No configuration found for agent: ${agent}`);
24
- }
25
- const { filePath, writeMode } = config;
26
- const fileExists = await this.fileService.exists(filePath);
27
- if (writeMode === 'overwrite' && fileExists && !force) {
28
- throw new RuleExistsError();
29
- }
30
- if (writeMode === 'append' && fileExists && !force) {
31
- const content = await this.fileService.read(filePath);
32
- if (content.includes(BR_RULE_TAG)) {
33
- throw new RuleExistsError();
34
- }
35
- }
36
- const ruleContent = await this.templateService.generateRuleContent(agent);
37
- await this.fileService.write(ruleContent, filePath, writeMode);
38
- }
39
- }