byterover-cli 1.0.2 → 1.0.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 (128) hide show
  1. package/README.md +62 -10
  2. package/dist/commands/curate.js +2 -2
  3. package/dist/commands/main.js +2 -2
  4. package/dist/commands/query.js +2 -2
  5. package/dist/commands/status.js +2 -2
  6. package/dist/config/context-tree-domains.d.ts +14 -2
  7. package/dist/config/context-tree-domains.js +22 -27
  8. package/dist/constants.d.ts +1 -0
  9. package/dist/constants.js +3 -0
  10. package/dist/core/domain/cipher/file-system/types.d.ts +2 -0
  11. package/dist/core/domain/entities/auth-token.js +6 -3
  12. package/dist/core/domain/entities/event.d.ts +1 -1
  13. package/dist/core/domain/entities/event.js +2 -1
  14. package/dist/core/domain/knowledge/relation-parser.d.ts +16 -1
  15. package/dist/core/domain/knowledge/relation-parser.js +19 -2
  16. package/dist/core/domain/transport/schemas.d.ts +17 -1
  17. package/dist/core/domain/transport/schemas.js +9 -1
  18. package/dist/core/interfaces/cipher/i-blob-storage.d.ts +6 -0
  19. package/dist/core/interfaces/cipher/index.d.ts +0 -1
  20. package/dist/core/interfaces/executor/i-curate-executor.d.ts +2 -0
  21. package/dist/infra/cipher/agent/cipher-agent.js +4 -0
  22. package/dist/infra/cipher/file-system/file-system-service.d.ts +4 -0
  23. package/dist/infra/cipher/file-system/file-system-service.js +5 -0
  24. package/dist/infra/cipher/system-prompt/contributors/context-tree-structure-contributor.js +4 -2
  25. package/dist/infra/cipher/tools/implementations/create-knowledge-topic-tool.js +24 -17
  26. package/dist/infra/cipher/tools/implementations/curate-tool.js +28 -33
  27. package/dist/infra/cipher/tools/implementations/read-file-tool.js +3 -12
  28. package/dist/infra/cipher/tools/implementations/spec-analyze-tool.js +18 -15
  29. package/dist/infra/cipher/tools/implementations/task-tool.js +53 -7
  30. package/dist/infra/context-tree/file-context-tree-service.js +4 -15
  31. package/dist/infra/core/executors/curate-executor.d.ts +2 -7
  32. package/dist/infra/core/executors/curate-executor.js +18 -53
  33. package/dist/infra/core/executors/query-executor.d.ts +1 -7
  34. package/dist/infra/core/executors/query-executor.js +10 -35
  35. package/dist/infra/core/task-processor.d.ts +2 -0
  36. package/dist/infra/core/task-processor.js +1 -0
  37. package/dist/infra/http/authenticated-http-client.js +5 -0
  38. package/dist/infra/process/agent-worker.js +113 -6
  39. package/dist/infra/process/constants.d.ts +1 -0
  40. package/dist/infra/process/constants.js +1 -0
  41. package/dist/infra/process/task-queue-manager.js +2 -1
  42. package/dist/infra/process/transport-handlers.js +4 -0
  43. package/dist/infra/process/transport-worker.js +89 -1
  44. package/dist/infra/repl/commands/curate-command.js +2 -2
  45. package/dist/infra/repl/commands/gen-rules-command.js +2 -2
  46. package/dist/infra/repl/commands/init-command.js +2 -2
  47. package/dist/infra/repl/commands/login-command.js +2 -2
  48. package/dist/infra/repl/commands/logout-command.js +2 -2
  49. package/dist/infra/repl/commands/pull-command.js +2 -2
  50. package/dist/infra/repl/commands/push-command.js +2 -2
  51. package/dist/infra/repl/commands/query-command.js +2 -2
  52. package/dist/infra/repl/commands/space/list-command.js +2 -2
  53. package/dist/infra/repl/commands/space/switch-command.js +2 -2
  54. package/dist/infra/repl/commands/status-command.js +2 -2
  55. package/dist/infra/repl/repl-startup.js +0 -2
  56. package/dist/infra/storage/file-token-store.d.ts +31 -0
  57. package/dist/infra/storage/file-token-store.js +98 -0
  58. package/dist/infra/storage/keychain-token-store.d.ts +4 -1
  59. package/dist/infra/storage/keychain-token-store.js +6 -4
  60. package/dist/infra/storage/token-store.d.ts +10 -0
  61. package/dist/infra/storage/token-store.js +14 -0
  62. package/dist/infra/usecase/curate-use-case.js +1 -1
  63. package/dist/infra/usecase/init-use-case.js +2 -4
  64. package/dist/infra/user/http-user-service.js +6 -11
  65. package/dist/resources/prompts/curate.yml +14 -5
  66. package/dist/resources/prompts/plan.yml +6 -0
  67. package/dist/tui/app.js +1 -1
  68. package/dist/tui/components/execution/log-item.js +2 -5
  69. package/dist/tui/components/header.d.ts +1 -1
  70. package/dist/tui/components/header.js +25 -4
  71. package/dist/tui/components/index.d.ts +5 -1
  72. package/dist/tui/components/index.js +3 -1
  73. package/dist/tui/components/init.d.ts +33 -0
  74. package/dist/tui/components/init.js +253 -0
  75. package/dist/tui/components/onboarding/index.d.ts +1 -0
  76. package/dist/tui/components/onboarding/index.js +1 -0
  77. package/dist/tui/components/onboarding/onboarding-flow.d.ts +2 -0
  78. package/dist/tui/components/onboarding/onboarding-flow.js +8 -229
  79. package/dist/tui/components/onboarding/onboarding-step.js +1 -1
  80. package/dist/tui/components/onboarding/welcome-box.d.ts +14 -0
  81. package/dist/tui/components/onboarding/welcome-box.js +23 -0
  82. package/dist/tui/components/status-badge.d.ts +22 -0
  83. package/dist/tui/components/status-badge.js +32 -0
  84. package/dist/tui/contexts/auth-context.js +2 -1
  85. package/dist/tui/contexts/index.d.ts +1 -0
  86. package/dist/tui/contexts/index.js +1 -0
  87. package/dist/tui/contexts/onboarding-context.d.ts +14 -0
  88. package/dist/tui/contexts/onboarding-context.js +17 -22
  89. package/dist/tui/contexts/status-context.d.ts +33 -0
  90. package/dist/tui/contexts/status-context.js +159 -0
  91. package/dist/tui/hooks/use-auth-polling.d.ts +4 -1
  92. package/dist/tui/hooks/use-auth-polling.js +21 -7
  93. package/dist/tui/hooks/use-tab-navigation.js +0 -2
  94. package/dist/tui/providers/app-providers.js +2 -2
  95. package/dist/tui/types/index.d.ts +2 -0
  96. package/dist/tui/types/index.js +2 -0
  97. package/dist/tui/types/status.d.ts +46 -0
  98. package/dist/tui/types/status.js +13 -0
  99. package/dist/tui/utils/index.d.ts +6 -0
  100. package/dist/tui/utils/index.js +6 -0
  101. package/dist/tui/utils/time.d.ts +10 -0
  102. package/dist/tui/utils/time.js +15 -0
  103. package/dist/tui/views/command-view.js +0 -2
  104. package/dist/tui/views/index.d.ts +1 -0
  105. package/dist/tui/views/index.js +1 -0
  106. package/dist/tui/views/init-view.d.ts +15 -0
  107. package/dist/tui/views/init-view.js +29 -0
  108. package/dist/tui/views/logs-view.js +22 -8
  109. package/dist/utils/environment-detector.d.ts +5 -0
  110. package/dist/utils/environment-detector.js +31 -0
  111. package/dist/utils/global-data-path.d.ts +11 -0
  112. package/dist/utils/global-data-path.js +32 -0
  113. package/oclif.manifest.json +1 -1
  114. package/package.json +1 -1
  115. package/dist/core/interfaces/cipher/i-agent-storage.d.ts +0 -152
  116. package/dist/core/interfaces/cipher/i-agent-storage.js +0 -1
  117. package/dist/infra/cipher/consumer/consumer-lock.d.ts +0 -20
  118. package/dist/infra/cipher/consumer/consumer-lock.js +0 -41
  119. package/dist/infra/cipher/consumer/consumer-service.d.ts +0 -99
  120. package/dist/infra/cipher/consumer/consumer-service.js +0 -166
  121. package/dist/infra/cipher/consumer/execution-consumer.d.ts +0 -126
  122. package/dist/infra/cipher/consumer/execution-consumer.js +0 -561
  123. package/dist/infra/cipher/consumer/index.d.ts +0 -33
  124. package/dist/infra/cipher/consumer/index.js +0 -34
  125. package/dist/infra/cipher/consumer/queue-polling-service.d.ts +0 -120
  126. package/dist/infra/cipher/consumer/queue-polling-service.js +0 -249
  127. package/dist/infra/cipher/storage/agent-storage.d.ts +0 -246
  128. package/dist/infra/cipher/storage/agent-storage.js +0 -956
@@ -92,7 +92,7 @@ export class ContextTreeStructureContributor {
92
92
  if (truncatedCount.value > 0) {
93
93
  parts.push('', `[${truncatedCount.value} additional entries not shown]`);
94
94
  }
95
- parts.push('', '## Structure Guide', '- Each top-level folder is a **domain** (e.g., `code_style/`, `design/`, `structure/`)', '- Inside domains are **topics** as `.md` files or subfolders with `context.md`', '- `context.md` files contain the curated knowledge content', '', '## Usage', '- **Query commands**: Search ONLY within this context tree structure', '- **Curate commands**: Check existing content here before creating new topics', '</context-tree-structure>');
95
+ parts.push('', '## Structure Guide', '- Each top-level folder is a **domain** (dynamically created based on content)', '- Inside domains are **topics** as `.md` files or subfolders with `context.md`', '- `context.md` files contain the curated knowledge content', '', '## Dynamic Domains', '- Domains are created dynamically based on the semantics of curated content', '- Domain names should be descriptive, use snake_case (e.g., `authentication`, `api_design`)', '- Before creating a new domain, check if existing domains could accommodate the content', '', '## Usage', '- **Query commands**: Search ONLY within this context tree structure', '- **Curate commands**: Check existing domains/topics before creating new ones', '</context-tree-structure>');
96
96
  return parts.join('\n');
97
97
  }
98
98
  /**
@@ -105,7 +105,9 @@ export class ContextTreeStructureContributor {
105
105
  '',
106
106
  'The context tree at `.brv/context-tree/` exists but contains no curated content yet.',
107
107
  '',
108
- '**For curate commands**: You can create new knowledge topics in any domain.',
108
+ '**For curate commands**: Create new domains and topics dynamically based on content.',
109
+ '- Choose semantically meaningful domain names (e.g., `authentication`, `api_design`, `data_models`)',
110
+ '- Use snake_case format for domain names',
109
111
  '**For query commands**: No context is available to search.',
110
112
  '</context-tree-structure>',
111
113
  ].join('\n');
@@ -7,16 +7,16 @@ import { sanitizeFolderName } from '../../../../utils/file-helpers.js';
7
7
  const CreateKnowledgeTopicInputSchema = z.object({
8
8
  // Base path for knowledge storage
9
9
  basePath: z.string().default('.brv/context-tree'),
10
- domains: z.array(z.string()).describe('Array of domain names'),
10
+ domains: z.array(z.string()).describe('Array of domain names (dynamically created based on content)'),
11
11
  // Manual topics (optional)
12
12
  topics: z
13
13
  .array(z.object({
14
- domain: z.string().describe('Domain category name from predefined list'),
14
+ domain: z.string().describe('Domain category name (can be any semantically meaningful name)'),
15
15
  name: z.string().describe('Topic name'),
16
16
  relations: z
17
17
  .array(z.string())
18
18
  .optional()
19
- .describe('Related topics using @domain/topic or @domain/topic/subtopic notation'),
19
+ .describe('Related topics using domain/topic or domain/topic/subtopic notation'),
20
20
  snippets: z.array(z.string()).describe('Code/text snippets'),
21
21
  subtopics: z
22
22
  .array(z.object({
@@ -24,7 +24,7 @@ const CreateKnowledgeTopicInputSchema = z.object({
24
24
  relations: z
25
25
  .array(z.string())
26
26
  .optional()
27
- .describe('Related topics using @domain/topic or @domain/topic/subtopic notation'),
27
+ .describe('Related topics using domain/topic or domain/topic/subtopic notation'),
28
28
  snippets: z.array(z.string()).describe('Code/text snippets'),
29
29
  }))
30
30
  .describe('Array of subtopics'),
@@ -112,27 +112,34 @@ async function executeCreateKnowledgeTopic(input, _context) {
112
112
  */
113
113
  export function createCreateKnowledgeTopicTool() {
114
114
  return {
115
- description: `Create organized knowledge topics within domain folders. This tool structures knowledge by creating topic and subtopic folders, each containing a context.md file with relevant snippets and optional relations.
115
+ description: `Create organized knowledge topics within dynamically-created domain folders. This tool structures knowledge by creating domain, topic, and subtopic folders, each containing a context.md file with relevant snippets and optional relations.
116
116
 
117
- Use this tool after detecting domains to organize extracted knowledge into a hierarchical structure:
118
- - Domain folders (e.g., .brv/context-tree/domain-name/)
119
- - Topic folders (e.g., .brv/context-tree/domain-name/topic-name/)
120
- - Topic context.md files (e.g., .brv/context-tree/domain-name/topic-name/context.md)
121
- - Subtopic folders (e.g., .brv/context-tree/domain-name/topic-name/subtopic-name/)
122
- - Subtopic context.md files (e.g., .brv/context-tree/domain-name/topic-name/subtopic-name/context.md)
117
+ **Dynamic Domain Creation:**
118
+ Domains are created dynamically based on the content being organized. Choose domain names that:
119
+ - Are semantically meaningful and descriptive (e.g., "authentication", "api_design", "data_models")
120
+ - Use snake_case format (1-3 words)
121
+ - Group related concepts together
122
+ - Avoid overly generic names (e.g., "misc", "other") or overly specific names
123
123
 
124
- Each topic should include:
124
+ **Hierarchical Structure:**
125
+ - Domain folders (e.g., .brv/context-tree/authentication/)
126
+ - Topic folders (e.g., .brv/context-tree/authentication/oauth_flow/)
127
+ - Topic context.md files (e.g., .brv/context-tree/authentication/oauth_flow/context.md)
128
+ - Subtopic folders (e.g., .brv/context-tree/authentication/oauth_flow/token_refresh/)
129
+ - Subtopic context.md files (e.g., .brv/context-tree/authentication/oauth_flow/token_refresh/context.md)
130
+
131
+ **Each topic should include:**
125
132
  1. A clear topic name
126
133
  2. Relevant code/text snippets that demonstrate the knowledge
127
- 3. Optional relations to other topics using @domain/topic or @domain/topic/subtopic notation
134
+ 3. Optional relations to other topics using domain/topic or domain/topic/subtopic notation
128
135
  4. Subtopics (optional) that break down the topic into smaller pieces
129
136
 
130
- Relations enhance knowledge discovery by linking related contexts. Example:
131
- - relations: ['code_style/error-handling', 'structure/api-endpoints/validation']
137
+ **Relations** enhance knowledge discovery by linking related contexts. Example:
138
+ - relations: ['authentication/session_management', 'api_design/endpoints/validation']
132
139
 
133
- The tool automatically:
140
+ **The tool automatically:**
134
141
  - Creates the base knowledge structure if it doesn't exist
135
- - Creates topic and subtopic folders as needed
142
+ - Creates domain, topic, and subtopic folders as needed
136
143
  - Generates context.md files with snippets and relations
137
144
  - Handles existing topics gracefully (updates instead of recreating)`,
138
145
  execute: executeCreateKnowledgeTopic,
@@ -1,20 +1,10 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { DEFAULT_CONTEXT_TREE_DOMAINS } from '../../../../config/context-tree-domains.js';
5
4
  import { ToolName } from '../../../../core/domain/cipher/tools/constants.js';
6
5
  import { DirectoryManager } from '../../../../core/domain/knowledge/directory-manager.js';
7
6
  import { MarkdownWriter } from '../../../../core/domain/knowledge/markdown-writer.js';
8
7
  import { toSnakeCase } from '../../../../utils/file-helpers.js';
9
- /**
10
- * Predefined domain names from configuration.
11
- */
12
- const PREDEFINED_DOMAIN_NAMES = DEFAULT_CONTEXT_TREE_DOMAINS.map((domain) => domain.name);
13
- /**
14
- * Maximum number of custom domains allowed beyond predefined domains.
15
- * This prevents excessive domain proliferation which degrades query quality.
16
- */
17
- const MAX_CUSTOM_DOMAINS = 3;
18
8
  /**
19
9
  * Operation types for curating knowledge topics.
20
10
  * Inspired by ACE Curator patterns.
@@ -27,7 +17,7 @@ const ContentSchema = z.object({
27
17
  relations: z
28
18
  .array(z.string())
29
19
  .optional()
30
- .describe('Related topics using @domain/topic or @domain/topic/subtopic notation'),
20
+ .describe('Related topics using domain/topic or domain/topic/subtopic notation'),
31
21
  snippets: z.array(z.string()).optional().describe('Code/text snippets'),
32
22
  });
33
23
  /**
@@ -78,33 +68,30 @@ async function getExistingDomains(basePath) {
78
68
  }
79
69
  }
80
70
  /**
81
- * Check if a domain is valid (either predefined or existing in context tree).
82
- * Also enforces the maximum custom domains limit.
71
+ * Validate domain name format.
72
+ * Dynamic domains are allowed - no predefined list or limits.
73
+ * The agent is responsible for creating semantically meaningful domains.
83
74
  */
84
75
  async function validateDomain(basePath, domainName) {
85
76
  const normalizedDomain = toSnakeCase(domainName);
86
77
  const existingDomains = await getExistingDomains(basePath);
87
- // Check if it's a predefined domain - always allowed
88
- if (PREDEFINED_DOMAIN_NAMES.includes(normalizedDomain)) {
89
- return { allowed: true, existingDomains };
90
- }
91
- // Check if it already exists in context tree - always allowed
92
- if (existingDomains.includes(normalizedDomain)) {
93
- return { allowed: true, existingDomains };
78
+ // Validate domain name format (must be non-empty and valid for filesystem)
79
+ if (!normalizedDomain || normalizedDomain.length === 0) {
80
+ return {
81
+ allowed: false,
82
+ existingDomains,
83
+ reason: 'Domain name cannot be empty.',
84
+ };
94
85
  }
95
- // Count how many custom domains already exist
96
- const customDomains = existingDomains.filter((domain) => !PREDEFINED_DOMAIN_NAMES.includes(domain));
97
- // Check if we've exceeded the custom domain limit
98
- if (customDomains.length >= MAX_CUSTOM_DOMAINS) {
86
+ // Check for invalid characters in domain name
87
+ if (!/^[\w-]+$/.test(normalizedDomain)) {
99
88
  return {
100
89
  allowed: false,
101
90
  existingDomains,
102
- reason: `Cannot create new domain "${normalizedDomain}". Maximum of ${MAX_CUSTOM_DOMAINS} custom domains allowed. ` +
103
- `Existing custom domains: ${customDomains.join(', ')}. ` +
104
- `Please use one of the predefined domains (${PREDEFINED_DOMAIN_NAMES.join(', ')}) or existing domains.`,
91
+ reason: `Domain name "${normalizedDomain}" contains invalid characters. Use only letters, numbers, underscores, and hyphens.`,
105
92
  };
106
93
  }
107
- // New custom domain within limit - allowed
94
+ // All valid domain names are allowed - dynamic domain creation enabled
108
95
  return { allowed: true, existingDomains };
109
96
  }
110
97
  /**
@@ -484,11 +471,19 @@ export function createCurateTool() {
484
471
  **Path format:** domain/topic or domain/topic/subtopic (uses snake_case automatically)
485
472
  **File naming:** Titles are converted to snake_case (e.g., "Best Practices" -> "best_practices.md")
486
473
 
487
- **Domain constraints:**
488
- - Predefined domains: code_style, design, structure, compliance, testing, bug_fixes
489
- - Up to 3 additional custom domains are allowed
490
- - Always prefer predefined domains when possible
491
- - Creating new domains beyond the limit will fail
474
+ **Dynamic Domain Creation:**
475
+ - Domains are created dynamically based on the context being curated
476
+ - Choose domain names that are semantically meaningful and descriptive
477
+ - Domain names should be concise (1-3 words), use snake_case format
478
+ - Examples of good domain names: authentication, api_design, data_models, error_handling, ui_components
479
+ - Before creating a new domain, check if existing domains could be reused
480
+ - Group related topics under the same domain for better organization
481
+
482
+ **Domain Naming Guidelines:**
483
+ - Use noun-based names that describe the category (e.g., "authentication" not "how_to_authenticate")
484
+ - Avoid overly generic names (e.g., "misc", "other", "general")
485
+ - Avoid overly specific names that only fit one topic
486
+ - Keep domain count reasonable by consolidating related concepts
492
487
 
493
488
  **Output:** Returns applied operations with status (success/failed), filePath (for created/modified files), and a summary of counts.`,
494
489
  execute: executeCurate,
@@ -6,18 +6,8 @@ import { ToolName } from '../../../../core/domain/cipher/tools/constants.js';
6
6
  const ReadFileInputSchema = z
7
7
  .object({
8
8
  filePath: z.string().describe('Path to the file to read (absolute or relative to working directory)'),
9
- limit: z
10
- .number()
11
- .int()
12
- .positive()
13
- .optional()
14
- .describe('Maximum number of lines to read (optional, default: 2000)'),
15
- offset: z
16
- .number()
17
- .int()
18
- .min(1)
19
- .optional()
20
- .describe('Starting line number (1-based, optional)'),
9
+ limit: z.number().int().positive().optional().describe('Maximum number of lines to read (optional, default: 2000)'),
10
+ offset: z.number().int().min(1).optional().describe('Starting line number (1-based, optional)'),
21
11
  })
22
12
  .strict();
23
13
  /**
@@ -56,6 +46,7 @@ export function createReadFileTool(fileSystemService) {
56
46
  size: result.size,
57
47
  totalLines: result.totalLines,
58
48
  truncated: result.truncated,
49
+ truncatedLineCount: result.truncatedLineCount,
59
50
  };
60
51
  },
61
52
  id: ToolName.READ_FILE,
@@ -1,17 +1,13 @@
1
1
  import { z } from 'zod';
2
- import { DEFAULT_CONTEXT_TREE_DOMAINS } from '../../../../config/context-tree-domains.js';
3
2
  import { ToolName } from '../../../../core/domain/cipher/tools/constants.js';
4
- /**
5
- * Predefined domain category names for validation reference
6
- */
7
- const PREDEFINED_DOMAIN_NAMES = DEFAULT_CONTEXT_TREE_DOMAINS.map((domain) => domain.name);
8
3
  /**
9
4
  * Domain category for knowledge classification.
5
+ * Domains are created dynamically based on content semantics.
10
6
  */
11
7
  const DomainCategory = z
12
8
  .string()
13
9
  .min(1)
14
- .describe(`Domain category name. Prefer using predefined domains: ${PREDEFINED_DOMAIN_NAMES.join(', ')}. Custom domains are allowed but limited to 3 maximum.`);
10
+ .describe('Domain category name. Create semantically meaningful domain names based on content (e.g., authentication, api_design, data_models). Use snake_case format.');
15
11
  /**
16
12
  * Text segment schema - represents a portion of the input data related to a domain
17
13
  */
@@ -27,7 +23,7 @@ const DetectDomainsInputSchema = z
27
23
  /** Detected domains with metadata and related text segments */
28
24
  domains: z
29
25
  .array(z.object({
30
- category: DomainCategory.describe('Domain category name from predefined list'),
26
+ category: DomainCategory.describe('Semantically meaningful domain category name (snake_case, e.g., authentication, api_design)'),
31
27
  textSegments: z
32
28
  .array(TextSegmentSchema)
33
29
  .min(1)
@@ -50,22 +46,29 @@ async function executeDetectDomains(input, _context) {
50
46
  * @returns Configured detect domains tool
51
47
  */
52
48
  export function createSpecAnalyzeTool() {
53
- const domainDescriptions = DEFAULT_CONTEXT_TREE_DOMAINS.map((domain) => ` - ${domain.name}: ${domain.description}`).join('\n');
54
49
  return {
55
- description: `Use this tool to analyze input data and detect which predefined knowledge domains are present. For each detected domain, you must also extract the specific text segments from the input data that relate to that domain.
50
+ description: `Use this tool to analyze input data and detect which knowledge domains are present. For each detected domain, you must also extract the specific text segments from the input data that relate to that domain.
56
51
 
57
52
  This tool should be the first tool to call when you want to understand new data, unless you already know what you are looking for.
58
53
 
59
- Predefined domain categories:
60
- ${domainDescriptions}
54
+ **Dynamic Domain Creation:**
55
+ Domains are created dynamically based on the semantics of the content. Choose domain names that:
56
+ - Are descriptive and semantically meaningful
57
+ - Use snake_case format (1-3 words)
58
+ - Group related concepts together
59
+ - Examples: \`authentication\`, \`api_design\`, \`data_models\`, \`error_handling\`, \`ui_components\`, \`testing_patterns\`
61
60
 
62
- For each domain you detect:
63
- 1. Identify the domain category from the predefined list above. Only create a custom domain if the content clearly does not fit any predefined domain (max 3 custom domains allowed)
61
+ **For each domain you detect:**
62
+ 1. Create a semantically meaningful domain category name based on the content
64
63
  2. Extract relevant text segments from the input data that demonstrate why this domain is relevant
65
64
  3. Each text segment should be a meaningful excerpt (not just keywords) that shows the connection to the domain
66
- 4. Only include domains that are actually present in the data - do not include domains just because they exist in the predefined list
65
+ 4. Only include domains that are actually present in the data
67
66
 
68
- **Important:** Always prefer predefined domains over custom ones to maintain query quality and ease of maintenance.
67
+ **Domain Naming Guidelines:**
68
+ - Use noun-based names that describe the category (e.g., \`authentication\` not \`how_to_authenticate\`)
69
+ - Avoid overly generic names (e.g., \`misc\`, \`other\`, \`general\`)
70
+ - Avoid overly specific names that only fit one topic
71
+ - Consolidate related concepts under the same domain
69
72
 
70
73
  The text segments will be used later to create organized knowledge topics, so they should be substantial enough to provide context.`,
71
74
  execute: executeDetectDomains,
@@ -1,6 +1,42 @@
1
+ import { load as loadYaml } from 'js-yaml';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
1
5
  import { z } from 'zod';
2
6
  import { getAgentRegistry } from '../../../../core/domain/cipher/agent/agent-registry.js';
3
7
  import { createContextTreeFileSystem } from '../../file-system/context-tree-file-system-factory.js';
8
+ /** Cache for loaded agent prompts. */
9
+ const agentPromptCache = new Map();
10
+ /** Type predicate to check if parsed YAML has a prompt field. */
11
+ function hasPromptField(value) {
12
+ return (typeof value === 'object' &&
13
+ value !== null &&
14
+ value !== undefined &&
15
+ 'prompt' in value &&
16
+ typeof value.prompt === 'string');
17
+ }
18
+ /** Load agent prompt from YAML file with caching. */
19
+ function loadAgentPrompt(promptFile) {
20
+ if (agentPromptCache.has(promptFile)) {
21
+ return agentPromptCache.get(promptFile);
22
+ }
23
+ try {
24
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
25
+ const promptsDir = path.join(currentDir, '../../../../resources/prompts');
26
+ const fullPath = path.join(promptsDir, promptFile);
27
+ if (!fs.existsSync(fullPath)) {
28
+ return '';
29
+ }
30
+ const yamlContent = fs.readFileSync(fullPath, 'utf8');
31
+ const parsed = loadYaml(yamlContent);
32
+ const prompt = hasPromptField(parsed) ? parsed.prompt : '';
33
+ agentPromptCache.set(promptFile, prompt);
34
+ return prompt;
35
+ }
36
+ catch {
37
+ return '';
38
+ }
39
+ }
4
40
  /**
5
41
  * Tool name constant for task tool.
6
42
  */
@@ -118,13 +154,23 @@ export function createTaskTool(dependencies) {
118
154
  session = await sessionManager.createSession(subagentSessionId);
119
155
  }
120
156
  // Build the system prompt for the subagent
121
- // The agent's promptFile will be used by the SystemPromptManager
122
- const agentPromptSection = agent.promptFile
123
- ? `\n\n[Agent: ${agent.name}]\nUsing prompt from: ${agent.promptFile}`
124
- : agent.prompt
125
- ? `\n\n[Agent: ${agent.name}]\n${agent.prompt}`
126
- : '';
127
- // Construct the full message for the subagent
157
+ // Load the agent's prompt from YAML file or use inline prompt
158
+ let agentPromptContent;
159
+ if (agent.promptFile) {
160
+ // Load the actual prompt content from the YAML file
161
+ agentPromptContent = loadAgentPrompt(agent.promptFile);
162
+ }
163
+ else if (agent.prompt) {
164
+ // Use inline prompt if no promptFile is specified
165
+ agentPromptContent = agent.prompt;
166
+ }
167
+ else {
168
+ agentPromptContent = '';
169
+ }
170
+ // Construct the full message for the subagent with agent instructions
171
+ const agentPromptSection = agentPromptContent
172
+ ? `\n\n## Agent Instructions (${agent.name})\n${agentPromptContent}`
173
+ : '';
128
174
  const fullPrompt = `${agentPromptSection}\n\n## Task\n${prompt}`;
129
175
  // Stream progress update
130
176
  if (context?.metadata) {
@@ -1,7 +1,6 @@
1
- import { access, mkdir, writeFile } from 'node:fs/promises';
1
+ import { access, mkdir } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- import { CONTEXT_TREE_DOMAINS } from '../../config/context-tree-domains.js';
4
- import { BRV_DIR, CONTEXT_FILE, CONTEXT_TREE_DIR } from '../../constants.js';
3
+ import { BRV_DIR, CONTEXT_TREE_DIR } from '../../constants.js';
5
4
  /**
6
5
  * File-based implementation of IContextTreeService.
7
6
  * Provides operations for managing the context tree structure.
@@ -27,19 +26,9 @@ export class FileContextTreeService {
27
26
  const baseDir = directory ?? this.config.baseDirectory ?? process.cwd();
28
27
  const brvDir = join(baseDir, BRV_DIR);
29
28
  const contextTreeDir = join(brvDir, CONTEXT_TREE_DIR);
30
- // Create .brv/context-tree/ directory
29
+ // Create .brv/context-tree/ directory only
30
+ // Domains are created dynamically by the agent based on curated content
31
31
  await mkdir(contextTreeDir, { recursive: true });
32
- // Create domain folders and context.md files in parallel
33
- await Promise.all(CONTEXT_TREE_DOMAINS.map(async (domain) => {
34
- const domainPath = join(contextTreeDir, domain.name);
35
- await mkdir(domainPath, { recursive: true });
36
- // Write context.md with domain description
37
- const contextMdPath = join(domainPath, CONTEXT_FILE);
38
- const contextContent = `# ${domain.name.replaceAll('_', ' ').replaceAll(/\b\w/g, (c) => c.toUpperCase())}\n\n${domain.description}\n`;
39
- await writeFile(contextMdPath, contextContent, 'utf8');
40
- }));
41
- // Note: index.json is no longer created as it's not actively used
42
- // Context tree uses filesystem-based discovery instead
43
32
  return contextTreeDir;
44
33
  }
45
34
  }
@@ -9,6 +9,7 @@ import type { CurateExecuteOptions, ICurateExecutor } from '../../../core/interf
9
9
  * Architecture:
10
10
  * - TaskProcessor injects the long-lived CipherAgent
11
11
  * - Event streaming is handled by agent-worker (subscribes to agentEventBus)
12
+ * - Transport handles task lifecycle (task:started, task:completed, task:error)
12
13
  * - Executor focuses solely on curate execution
13
14
  */
14
15
  export declare class CurateExecutor implements ICurateExecutor {
@@ -16,18 +17,12 @@ export declare class CurateExecutor implements ICurateExecutor {
16
17
  * Maximum number of files allowed in --files flag
17
18
  */
18
19
  private static readonly MAX_FILES;
19
- /**
20
- * Execute curate with an injected agent.
21
- *
22
- * @param agent - Long-lived CipherAgent (managed by caller)
23
- * @param options - Execution options (content, file references)
24
- * @returns Result string from agent execution
25
- */
26
20
  executeWithAgent(agent: ICipherAgent, options: CurateExecuteOptions): Promise<string>;
27
21
  /**
28
22
  * Process file paths from --files flag.
29
23
  *
30
24
  * @param filePaths - Array of file paths (relative or absolute)
25
+ * @param clientCwd - Client's working directory for file validation (optional, defaults to process.cwd())
31
26
  * @returns Formatted instructions for the agent to read the specified files,
32
27
  * or undefined if validation fails
33
28
  */
@@ -1,6 +1,5 @@
1
1
  import { FileValidationError } from '../../../core/domain/errors/task-error.js';
2
2
  import { validateFileForCurate } from '../../../utils/file-validator.js';
3
- import { getAgentStorage } from '../../cipher/storage/agent-storage.js';
4
3
  /**
5
4
  * CurateExecutor - Executes curate tasks with an injected CipherAgent.
6
5
  *
@@ -10,6 +9,7 @@ import { getAgentStorage } from '../../cipher/storage/agent-storage.js';
10
9
  * Architecture:
11
10
  * - TaskProcessor injects the long-lived CipherAgent
12
11
  * - Event streaming is handled by agent-worker (subscribes to agentEventBus)
12
+ * - Transport handles task lifecycle (task:started, task:completed, task:error)
13
13
  * - Executor focuses solely on curate execution
14
14
  */
15
15
  export class CurateExecutor {
@@ -17,66 +17,32 @@ export class CurateExecutor {
17
17
  * Maximum number of files allowed in --files flag
18
18
  */
19
19
  static MAX_FILES = 5;
20
- /**
21
- * Execute curate with an injected agent.
22
- *
23
- * @param agent - Long-lived CipherAgent (managed by caller)
24
- * @param options - Execution options (content, file references)
25
- * @returns Result string from agent execution
26
- */
27
20
  async executeWithAgent(agent, options) {
28
- const { content, files, taskId } = options;
29
- // Initialize storage for execution tracking
30
- const storage = await getAgentStorage();
31
- let executionId = null;
32
- try {
33
- // Process file references if provided (validation + instructions)
34
- const fileReferenceInstructions = this.processFileReferences(files ?? []);
35
- if (fileReferenceInstructions === undefined) {
36
- throw new FileValidationError();
37
- }
38
- // Create execution with status='running'
39
- // Save in JSON format with all fields for tracking:
40
- // - content: the context to curate
41
- // - files: original file paths (if provided)
42
- // - fileReferenceInstructions: generated instructions (if files provided and valid)
43
- const executionInput = JSON.stringify({
44
- content,
45
- ...(fileReferenceInstructions ? { fileReferenceInstructions } : {}),
46
- ...(files && files.length > 0 ? { files } : {}),
47
- });
48
- executionId = storage.createExecution('curate', executionInput);
49
- // Build prompt with optional file reference instructions
50
- const prompt = fileReferenceInstructions ? `${content}\n${fileReferenceInstructions}` : content;
51
- // Execute with curate commandType
52
- // Agent uses its default session (created during start())
53
- const response = await agent.execute(prompt, {
54
- executionContext: { commandType: 'curate' },
55
- taskId,
56
- });
57
- // Mark execution as completed
58
- storage.updateExecutionStatus(executionId, 'completed', response);
59
- // Cleanup old executions
60
- storage.cleanupOldExecutions(100);
61
- return response;
62
- }
63
- catch (error) {
64
- // Mark execution as failed
65
- if (executionId) {
66
- const errorMessage = error instanceof Error ? error.message : String(error);
67
- storage.updateExecutionStatus(executionId, 'failed', undefined, errorMessage);
68
- }
69
- throw error;
21
+ const { clientCwd, content, files, taskId } = options;
22
+ const fileReferenceInstructions = this.processFileReferences(files ?? [], clientCwd);
23
+ if (fileReferenceInstructions === undefined) {
24
+ throw new FileValidationError();
70
25
  }
26
+ // Build prompt with optional file reference instructions
27
+ const prompt = fileReferenceInstructions ? `${content}\n${fileReferenceInstructions}` : content;
28
+ // Execute with curate commandType
29
+ // Agent uses its default session (created during start())
30
+ // Task lifecycle is managed by Transport (task:started, task:completed, task:error)
31
+ const response = await agent.execute(prompt, {
32
+ executionContext: { commandType: 'curate' },
33
+ taskId,
34
+ });
35
+ return response;
71
36
  }
72
37
  /**
73
38
  * Process file paths from --files flag.
74
39
  *
75
40
  * @param filePaths - Array of file paths (relative or absolute)
41
+ * @param clientCwd - Client's working directory for file validation (optional, defaults to process.cwd())
76
42
  * @returns Formatted instructions for the agent to read the specified files,
77
43
  * or undefined if validation fails
78
44
  */
79
- processFileReferences(filePaths) {
45
+ processFileReferences(filePaths, clientCwd) {
80
46
  if (!filePaths || filePaths.length === 0) {
81
47
  return '';
82
48
  }
@@ -85,8 +51,7 @@ export class CurateExecutor {
85
51
  if (filePaths.length > CurateExecutor.MAX_FILES) {
86
52
  processedPaths = filePaths.slice(0, CurateExecutor.MAX_FILES);
87
53
  }
88
- // Get project root (current directory)
89
- const projectRoot = process.cwd();
54
+ const projectRoot = clientCwd ?? process.cwd();
90
55
  // Validate each file and collect errors
91
56
  const validPaths = [];
92
57
  const errors = [];
@@ -9,15 +9,9 @@ import type { IQueryExecutor, QueryExecuteOptions } from '../../../core/interfac
9
9
  * Architecture:
10
10
  * - TaskProcessor injects the long-lived CipherAgent
11
11
  * - Event streaming is handled by agent-worker (subscribes to agentEventBus)
12
+ * - Transport handles task lifecycle (task:started, task:completed, task:error)
12
13
  * - Executor focuses solely on query execution
13
14
  */
14
15
  export declare class QueryExecutor implements IQueryExecutor {
15
- /**
16
- * Execute query with an injected agent.
17
- *
18
- * @param agent - Long-lived CipherAgent (managed by caller)
19
- * @param options - Execution options (query)
20
- * @returns Result string from agent execution
21
- */
22
16
  executeWithAgent(agent: ICipherAgent, options: QueryExecuteOptions): Promise<string>;
23
17
  }
@@ -1,4 +1,3 @@
1
- import { getAgentStorage } from '../../cipher/storage/agent-storage.js';
2
1
  /**
3
2
  * QueryExecutor - Executes query tasks with an injected CipherAgent.
4
3
  *
@@ -8,44 +7,20 @@ import { getAgentStorage } from '../../cipher/storage/agent-storage.js';
8
7
  * Architecture:
9
8
  * - TaskProcessor injects the long-lived CipherAgent
10
9
  * - Event streaming is handled by agent-worker (subscribes to agentEventBus)
10
+ * - Transport handles task lifecycle (task:started, task:completed, task:error)
11
11
  * - Executor focuses solely on query execution
12
12
  */
13
13
  export class QueryExecutor {
14
- /**
15
- * Execute query with an injected agent.
16
- *
17
- * @param agent - Long-lived CipherAgent (managed by caller)
18
- * @param options - Execution options (query)
19
- * @returns Result string from agent execution
20
- */
21
14
  async executeWithAgent(agent, options) {
22
15
  const { query, taskId } = options;
23
- // Initialize storage for execution tracking
24
- const storage = await getAgentStorage();
25
- let executionId = null;
26
- try {
27
- // Create execution with status='running'
28
- executionId = storage.createExecution('query', query);
29
- // Execute with query commandType
30
- // Agent uses its default session (created during start())
31
- const prompt = `Search the context tree for: ${query}`;
32
- const response = await agent.execute(prompt, {
33
- executionContext: { commandType: 'query' },
34
- taskId,
35
- });
36
- // Mark execution as completed
37
- storage.updateExecutionStatus(executionId, 'completed', response);
38
- // Cleanup old executions
39
- storage.cleanupOldExecutions(100);
40
- return response;
41
- }
42
- catch (error) {
43
- // Mark execution as failed
44
- if (executionId) {
45
- const errorMessage = error instanceof Error ? error.message : String(error);
46
- storage.updateExecutionStatus(executionId, 'failed', undefined, errorMessage);
47
- }
48
- throw error;
49
- }
16
+ // Execute with query commandType
17
+ // Agent uses its default session (created during start())
18
+ // Task lifecycle is managed by Transport (task:started, task:completed, task:error)
19
+ const prompt = `Search the context tree for: ${query}`;
20
+ const response = await agent.execute(prompt, {
21
+ executionContext: { commandType: 'query' },
22
+ taskId,
23
+ });
24
+ return response;
50
25
  }
51
26
  }
@@ -9,6 +9,8 @@ import type { IQueryExecutor } from '../../core/interfaces/executor/i-query-exec
9
9
  export type TaskInput = {
10
10
  /** Task content/prompt */
11
11
  content: string;
12
+ /** Client's working directory for file validation */
13
+ clientCwd?: string;
12
14
  /** Optional file paths for curate --files */
13
15
  files?: string[];
14
16
  /** Task ID */
@@ -95,6 +95,7 @@ export class TaskProcessor {
95
95
  case 'curate': {
96
96
  // Agent uses its default session (Single-Session pattern)
97
97
  return this.curateExecutor.executeWithAgent(this.agent, {
98
+ clientCwd: input.clientCwd,
98
99
  content,
99
100
  files: input.files,
100
101
  taskId,