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.
- package/README.md +62 -10
- package/dist/commands/curate.js +2 -2
- package/dist/commands/main.js +2 -2
- package/dist/commands/query.js +2 -2
- package/dist/commands/status.js +2 -2
- package/dist/config/context-tree-domains.d.ts +14 -2
- package/dist/config/context-tree-domains.js +22 -27
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +3 -0
- package/dist/core/domain/cipher/file-system/types.d.ts +2 -0
- package/dist/core/domain/entities/auth-token.js +6 -3
- package/dist/core/domain/entities/event.d.ts +1 -1
- package/dist/core/domain/entities/event.js +2 -1
- package/dist/core/domain/knowledge/relation-parser.d.ts +16 -1
- package/dist/core/domain/knowledge/relation-parser.js +19 -2
- package/dist/core/domain/transport/schemas.d.ts +17 -1
- package/dist/core/domain/transport/schemas.js +9 -1
- package/dist/core/interfaces/cipher/i-blob-storage.d.ts +6 -0
- package/dist/core/interfaces/cipher/index.d.ts +0 -1
- package/dist/core/interfaces/executor/i-curate-executor.d.ts +2 -0
- package/dist/infra/cipher/agent/cipher-agent.js +4 -0
- package/dist/infra/cipher/file-system/file-system-service.d.ts +4 -0
- package/dist/infra/cipher/file-system/file-system-service.js +5 -0
- package/dist/infra/cipher/system-prompt/contributors/context-tree-structure-contributor.js +4 -2
- package/dist/infra/cipher/tools/implementations/create-knowledge-topic-tool.js +24 -17
- package/dist/infra/cipher/tools/implementations/curate-tool.js +28 -33
- package/dist/infra/cipher/tools/implementations/read-file-tool.js +3 -12
- package/dist/infra/cipher/tools/implementations/spec-analyze-tool.js +18 -15
- package/dist/infra/cipher/tools/implementations/task-tool.js +53 -7
- package/dist/infra/context-tree/file-context-tree-service.js +4 -15
- package/dist/infra/core/executors/curate-executor.d.ts +2 -7
- package/dist/infra/core/executors/curate-executor.js +18 -53
- package/dist/infra/core/executors/query-executor.d.ts +1 -7
- package/dist/infra/core/executors/query-executor.js +10 -35
- package/dist/infra/core/task-processor.d.ts +2 -0
- package/dist/infra/core/task-processor.js +1 -0
- package/dist/infra/http/authenticated-http-client.js +5 -0
- package/dist/infra/process/agent-worker.js +113 -6
- package/dist/infra/process/constants.d.ts +1 -0
- package/dist/infra/process/constants.js +1 -0
- package/dist/infra/process/task-queue-manager.js +2 -1
- package/dist/infra/process/transport-handlers.js +4 -0
- package/dist/infra/process/transport-worker.js +89 -1
- package/dist/infra/repl/commands/curate-command.js +2 -2
- package/dist/infra/repl/commands/gen-rules-command.js +2 -2
- package/dist/infra/repl/commands/init-command.js +2 -2
- package/dist/infra/repl/commands/login-command.js +2 -2
- package/dist/infra/repl/commands/logout-command.js +2 -2
- package/dist/infra/repl/commands/pull-command.js +2 -2
- package/dist/infra/repl/commands/push-command.js +2 -2
- package/dist/infra/repl/commands/query-command.js +2 -2
- package/dist/infra/repl/commands/space/list-command.js +2 -2
- package/dist/infra/repl/commands/space/switch-command.js +2 -2
- package/dist/infra/repl/commands/status-command.js +2 -2
- package/dist/infra/repl/repl-startup.js +0 -2
- package/dist/infra/storage/file-token-store.d.ts +31 -0
- package/dist/infra/storage/file-token-store.js +98 -0
- package/dist/infra/storage/keychain-token-store.d.ts +4 -1
- package/dist/infra/storage/keychain-token-store.js +6 -4
- package/dist/infra/storage/token-store.d.ts +10 -0
- package/dist/infra/storage/token-store.js +14 -0
- package/dist/infra/usecase/curate-use-case.js +1 -1
- package/dist/infra/usecase/init-use-case.js +2 -4
- package/dist/infra/user/http-user-service.js +6 -11
- package/dist/resources/prompts/curate.yml +14 -5
- package/dist/resources/prompts/plan.yml +6 -0
- package/dist/tui/app.js +1 -1
- package/dist/tui/components/execution/log-item.js +2 -5
- package/dist/tui/components/header.d.ts +1 -1
- package/dist/tui/components/header.js +25 -4
- package/dist/tui/components/index.d.ts +5 -1
- package/dist/tui/components/index.js +3 -1
- package/dist/tui/components/init.d.ts +33 -0
- package/dist/tui/components/init.js +253 -0
- package/dist/tui/components/onboarding/index.d.ts +1 -0
- package/dist/tui/components/onboarding/index.js +1 -0
- package/dist/tui/components/onboarding/onboarding-flow.d.ts +2 -0
- package/dist/tui/components/onboarding/onboarding-flow.js +8 -229
- package/dist/tui/components/onboarding/onboarding-step.js +1 -1
- package/dist/tui/components/onboarding/welcome-box.d.ts +14 -0
- package/dist/tui/components/onboarding/welcome-box.js +23 -0
- package/dist/tui/components/status-badge.d.ts +22 -0
- package/dist/tui/components/status-badge.js +32 -0
- package/dist/tui/contexts/auth-context.js +2 -1
- package/dist/tui/contexts/index.d.ts +1 -0
- package/dist/tui/contexts/index.js +1 -0
- package/dist/tui/contexts/onboarding-context.d.ts +14 -0
- package/dist/tui/contexts/onboarding-context.js +17 -22
- package/dist/tui/contexts/status-context.d.ts +33 -0
- package/dist/tui/contexts/status-context.js +159 -0
- package/dist/tui/hooks/use-auth-polling.d.ts +4 -1
- package/dist/tui/hooks/use-auth-polling.js +21 -7
- package/dist/tui/hooks/use-tab-navigation.js +0 -2
- package/dist/tui/providers/app-providers.js +2 -2
- package/dist/tui/types/index.d.ts +2 -0
- package/dist/tui/types/index.js +2 -0
- package/dist/tui/types/status.d.ts +46 -0
- package/dist/tui/types/status.js +13 -0
- package/dist/tui/utils/index.d.ts +6 -0
- package/dist/tui/utils/index.js +6 -0
- package/dist/tui/utils/time.d.ts +10 -0
- package/dist/tui/utils/time.js +15 -0
- package/dist/tui/views/command-view.js +0 -2
- package/dist/tui/views/index.d.ts +1 -0
- package/dist/tui/views/index.js +1 -0
- package/dist/tui/views/init-view.d.ts +15 -0
- package/dist/tui/views/init-view.js +29 -0
- package/dist/tui/views/logs-view.js +22 -8
- package/dist/utils/environment-detector.d.ts +5 -0
- package/dist/utils/environment-detector.js +31 -0
- package/dist/utils/global-data-path.d.ts +11 -0
- package/dist/utils/global-data-path.js +32 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/core/interfaces/cipher/i-agent-storage.d.ts +0 -152
- package/dist/core/interfaces/cipher/i-agent-storage.js +0 -1
- package/dist/infra/cipher/consumer/consumer-lock.d.ts +0 -20
- package/dist/infra/cipher/consumer/consumer-lock.js +0 -41
- package/dist/infra/cipher/consumer/consumer-service.d.ts +0 -99
- package/dist/infra/cipher/consumer/consumer-service.js +0 -166
- package/dist/infra/cipher/consumer/execution-consumer.d.ts +0 -126
- package/dist/infra/cipher/consumer/execution-consumer.js +0 -561
- package/dist/infra/cipher/consumer/index.d.ts +0 -33
- package/dist/infra/cipher/consumer/index.js +0 -34
- package/dist/infra/cipher/consumer/queue-polling-service.d.ts +0 -120
- package/dist/infra/cipher/consumer/queue-polling-service.js +0 -249
- package/dist/infra/cipher/storage/agent-storage.d.ts +0 -246
- 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** (
|
|
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**:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
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
|
-
|
|
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
|
|
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: ['
|
|
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
|
|
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
|
-
*
|
|
82
|
-
*
|
|
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
|
-
//
|
|
88
|
-
if (
|
|
89
|
-
return {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
//
|
|
96
|
-
|
|
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: `
|
|
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
|
-
//
|
|
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
|
|
488
|
-
-
|
|
489
|
-
-
|
|
490
|
-
-
|
|
491
|
-
-
|
|
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
|
-
|
|
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(
|
|
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('
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
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.
|
|
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
|
|
65
|
+
4. Only include domains that are actually present in the data
|
|
67
66
|
|
|
68
|
-
**
|
|
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
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
1
|
+
import { access, mkdir } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 */
|