byterover-cli 1.0.4 → 1.0.5

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 (72) hide show
  1. package/README.md +13 -2
  2. package/dist/commands/curate.js +1 -1
  3. package/dist/commands/main.d.ts +13 -0
  4. package/dist/commands/main.js +53 -2
  5. package/dist/commands/query.js +1 -1
  6. package/dist/constants.d.ts +1 -1
  7. package/dist/constants.js +1 -1
  8. package/dist/core/domain/cipher/llm/registry.js +53 -2
  9. package/dist/core/domain/cipher/llm/types.d.ts +2 -0
  10. package/dist/core/domain/cipher/process/types.d.ts +7 -0
  11. package/dist/core/domain/cipher/session/session-metadata.d.ts +178 -0
  12. package/dist/core/domain/cipher/session/session-metadata.js +147 -0
  13. package/dist/core/domain/knowledge/markdown-writer.d.ts +15 -18
  14. package/dist/core/domain/knowledge/markdown-writer.js +232 -34
  15. package/dist/core/domain/knowledge/relation-parser.d.ts +25 -39
  16. package/dist/core/domain/knowledge/relation-parser.js +39 -61
  17. package/dist/core/domain/transport/schemas.d.ts +37 -2
  18. package/dist/core/domain/transport/schemas.js +23 -2
  19. package/dist/core/interfaces/cipher/i-session-persistence.d.ts +133 -0
  20. package/dist/core/interfaces/cipher/i-session-persistence.js +7 -0
  21. package/dist/core/interfaces/cipher/message-types.d.ts +6 -0
  22. package/dist/core/interfaces/executor/i-curate-executor.d.ts +2 -2
  23. package/dist/core/interfaces/i-context-file-reader.d.ts +3 -0
  24. package/dist/core/interfaces/usecase/{i-clear-use-case.d.ts → i-reset-use-case.d.ts} +1 -1
  25. package/dist/infra/cipher/agent/agent-schemas.d.ts +6 -6
  26. package/dist/infra/cipher/agent/service-initializer.js +4 -4
  27. package/dist/infra/cipher/file-system/context-tree-file-system-factory.js +3 -2
  28. package/dist/infra/cipher/file-system/file-system-service.js +1 -0
  29. package/dist/infra/cipher/http/internal-llm-http-service.js +3 -5
  30. package/dist/infra/cipher/interactive-loop.js +3 -1
  31. package/dist/infra/cipher/llm/context/context-manager.js +40 -16
  32. package/dist/infra/cipher/llm/formatters/gemini-formatter.d.ts +13 -0
  33. package/dist/infra/cipher/llm/formatters/gemini-formatter.js +98 -6
  34. package/dist/infra/cipher/llm/generators/byterover-content-generator.js +6 -2
  35. package/dist/infra/cipher/llm/thought-parser.d.ts +21 -0
  36. package/dist/infra/cipher/llm/thought-parser.js +27 -0
  37. package/dist/infra/cipher/llm/tool-output-processor.d.ts +10 -0
  38. package/dist/infra/cipher/llm/tool-output-processor.js +80 -7
  39. package/dist/infra/cipher/process/process-service.js +11 -3
  40. package/dist/infra/cipher/session/chat-session.d.ts +7 -2
  41. package/dist/infra/cipher/session/chat-session.js +90 -52
  42. package/dist/infra/cipher/session/session-metadata-store.d.ts +52 -0
  43. package/dist/infra/cipher/session/session-metadata-store.js +406 -0
  44. package/dist/infra/cipher/tools/implementations/curate-tool.js +113 -35
  45. package/dist/infra/cipher/tools/implementations/task-tool.js +1 -0
  46. package/dist/infra/context-tree/file-context-file-reader.js +4 -0
  47. package/dist/infra/core/task-processor.d.ts +2 -2
  48. package/dist/infra/process/process-manager.d.ts +10 -1
  49. package/dist/infra/process/process-manager.js +16 -6
  50. package/dist/infra/process/transport-handlers.js +31 -0
  51. package/dist/infra/repl/commands/index.js +5 -2
  52. package/dist/infra/repl/commands/new-command.d.ts +14 -0
  53. package/dist/infra/repl/commands/new-command.js +61 -0
  54. package/dist/infra/repl/commands/{clear-command.d.ts → reset-command.d.ts} +2 -2
  55. package/dist/infra/repl/commands/{clear-command.js → reset-command.js} +10 -10
  56. package/dist/infra/usecase/generate-rules-use-case.js +2 -2
  57. package/dist/infra/usecase/init-use-case.js +4 -4
  58. package/dist/infra/usecase/logout-use-case.js +1 -1
  59. package/dist/infra/usecase/push-use-case.js +1 -1
  60. package/dist/infra/usecase/{clear-use-case.d.ts → reset-use-case.d.ts} +5 -5
  61. package/dist/infra/usecase/{clear-use-case.js → reset-use-case.js} +5 -5
  62. package/dist/resources/prompts/curate.yml +68 -13
  63. package/dist/resources/tools/curate.txt +60 -15
  64. package/dist/tui/components/inline-prompts/inline-confirm.js +2 -2
  65. package/dist/tui/components/onboarding/onboarding-flow.js +1 -0
  66. package/dist/tui/views/command-view.js +15 -0
  67. package/dist/utils/file-validator.js +9 -7
  68. package/oclif.manifest.json +3 -3
  69. package/package.json +1 -1
  70. package/dist/config/context-tree-domains.d.ts +0 -29
  71. package/dist/config/context-tree-domains.js +0 -29
  72. /package/dist/core/interfaces/usecase/{i-clear-use-case.js → i-reset-use-case.js} +0 -0
@@ -1,16 +1,130 @@
1
1
  import { generateRelationsSection, parseRelations } from './relation-parser.js';
2
- /**
3
- * Extract snippets from context.md content.
4
- * Removes relations section and splits by separator.
5
- */
2
+ function generateRawConceptSection(rawConcept) {
3
+ if (!rawConcept) {
4
+ return '';
5
+ }
6
+ const parts = [];
7
+ if (rawConcept.task) {
8
+ parts.push(`**Task:**\n${rawConcept.task}`);
9
+ }
10
+ if (rawConcept.changes && rawConcept.changes.length > 0) {
11
+ parts.push(`**Changes:**\n${rawConcept.changes.map(c => `- ${c}`).join('\n')}`);
12
+ }
13
+ if (rawConcept.files && rawConcept.files.length > 0) {
14
+ parts.push(`**Files:**\n${rawConcept.files.map(f => `- ${f}`).join('\n')}`);
15
+ }
16
+ if (rawConcept.flow) {
17
+ parts.push(`**Flow:**\n${rawConcept.flow}`);
18
+ }
19
+ if (rawConcept.timestamp) {
20
+ parts.push(`**Timestamp:** ${rawConcept.timestamp}`);
21
+ }
22
+ if (parts.length === 0) {
23
+ return '';
24
+ }
25
+ return `\n## Raw Concept\n${parts.join('\n\n')}\n`;
26
+ }
27
+ function generateNarrativeSection(narrative) {
28
+ if (!narrative) {
29
+ return '';
30
+ }
31
+ const parts = [];
32
+ if (narrative.structure) {
33
+ parts.push(`### Structure\n${narrative.structure}`);
34
+ }
35
+ if (narrative.dependencies) {
36
+ parts.push(`### Dependencies\n${narrative.dependencies}`);
37
+ }
38
+ if (narrative.features) {
39
+ parts.push(`### Features\n${narrative.features}`);
40
+ }
41
+ if (parts.length === 0) {
42
+ return '';
43
+ }
44
+ return `\n## Narrative\n${parts.join('\n\n')}\n`;
45
+ }
46
+ function parseRawConceptSection(content) {
47
+ // Forgiving regex: allows optional whitespace after "## Raw Concept"
48
+ const rawConceptMatch = content.match(/##\s*Raw Concept\s*\n([\s\S]*?)(?=\n##\s|\n---\n|$)/i);
49
+ if (!rawConceptMatch) {
50
+ return undefined;
51
+ }
52
+ const sectionContent = rawConceptMatch[1];
53
+ const rawConcept = {};
54
+ // Forgiving: allows whitespace around "Task:" and after the newline
55
+ const taskMatch = sectionContent.match(/\*\*\s*Task\s*:\s*\*\*\s*\n([\s\S]*?)(?=\n\*\*|\n##|$)/i);
56
+ if (taskMatch) {
57
+ rawConcept.task = taskMatch[1].trim();
58
+ }
59
+ const changesMatch = sectionContent.match(/\*\*\s*Changes\s*:\s*\*\*\s*\n([\s\S]*?)(?=\n\*\*|\n##|$)/i);
60
+ if (changesMatch) {
61
+ rawConcept.changes = changesMatch[1]
62
+ .split('\n')
63
+ .filter(line => line.trim().startsWith('- '))
64
+ .map(line => line.trim().slice(2));
65
+ }
66
+ const filesMatch = sectionContent.match(/\*\*\s*Files\s*:\s*\*\*\s*\n([\s\S]*?)(?=\n\*\*|\n##|$)/i);
67
+ if (filesMatch) {
68
+ rawConcept.files = filesMatch[1]
69
+ .split('\n')
70
+ .filter(line => line.trim().startsWith('- '))
71
+ .map(line => line.trim().slice(2));
72
+ }
73
+ const flowMatch = sectionContent.match(/\*\*\s*Flow\s*:\s*\*\*\s*\n([\s\S]*?)(?=\n\*\*|\n##|$)/i);
74
+ if (flowMatch) {
75
+ rawConcept.flow = flowMatch[1].trim();
76
+ }
77
+ // Timestamp can be inline, so more flexible pattern
78
+ const timestampMatch = sectionContent.match(/\*\*\s*Timestamp\s*:\s*\*\*\s*(.+)/i);
79
+ if (timestampMatch) {
80
+ rawConcept.timestamp = timestampMatch[1].trim();
81
+ }
82
+ if (Object.keys(rawConcept).length === 0) {
83
+ return undefined;
84
+ }
85
+ return rawConcept;
86
+ }
87
+ function parseNarrativeSection(content) {
88
+ // Forgiving regex: allows optional whitespace after "## Narrative"
89
+ const narrativeMatch = content.match(/##\s*Narrative\s*\n([\s\S]*?)(?=\n##\s[^#]|\n---\n|$)/i);
90
+ if (!narrativeMatch) {
91
+ return undefined;
92
+ }
93
+ const sectionContent = narrativeMatch[1];
94
+ const narrative = {};
95
+ // Forgiving: allows whitespace after "### Structure"
96
+ const structureMatch = sectionContent.match(/###\s*Structure\s*\n([\s\S]*?)(?=\n###\s|\n##\s|$)/i);
97
+ if (structureMatch) {
98
+ narrative.structure = structureMatch[1].trim();
99
+ }
100
+ const dependenciesMatch = sectionContent.match(/###\s*Dependencies\s*\n([\s\S]*?)(?=\n###\s|\n##\s|$)/i);
101
+ if (dependenciesMatch) {
102
+ narrative.dependencies = dependenciesMatch[1].trim();
103
+ }
104
+ const featuresMatch = sectionContent.match(/###\s*Features\s*\n([\s\S]*?)(?=\n###\s|\n##\s|$)/i);
105
+ if (featuresMatch) {
106
+ narrative.features = featuresMatch[1].trim();
107
+ }
108
+ if (Object.keys(narrative).length === 0) {
109
+ return undefined;
110
+ }
111
+ return narrative;
112
+ }
6
113
  function extractSnippetsFromContent(content) {
7
- // Remove relations section if present
8
114
  let snippetContent = content;
9
- const relationsMatch = content.match(/## Relations[\s\S]*?(?=\n[^@\n]|$)/);
115
+ // Forgiving regex patterns for section removal
116
+ const relationsMatch = content.match(/##\s*Relations[\s\S]*?(?=\n[^@\n]|$)/i);
10
117
  if (relationsMatch) {
11
- snippetContent = content.replace(relationsMatch[0], '').trim();
118
+ snippetContent = snippetContent.replace(relationsMatch[0], '').trim();
119
+ }
120
+ const rawConceptMatch = snippetContent.match(/##\s*Raw Concept[\s\S]*?(?=\n##\s|\n---\n|$)/i);
121
+ if (rawConceptMatch) {
122
+ snippetContent = snippetContent.replace(rawConceptMatch[0], '').trim();
123
+ }
124
+ const narrativeMatch = snippetContent.match(/##\s*Narrative[\s\S]*?(?=\n##\s|\n---\n|$)/i);
125
+ if (narrativeMatch) {
126
+ snippetContent = snippetContent.replace(narrativeMatch[0], '').trim();
12
127
  }
13
- // Split by separator and filter empty
14
128
  const snippets = snippetContent
15
129
  .split(/\n---\n/)
16
130
  .map(s => s.trim())
@@ -18,39 +132,112 @@ function extractSnippetsFromContent(content) {
18
132
  return snippets;
19
133
  }
20
134
  /**
21
- * Generates Markdown files for knowledge context.
135
+ * Merges two RawConcept objects with the following strategy:
136
+ *
137
+ * **Scalars (task, flow, timestamp)**: Source wins (source.X || target.X)
138
+ * - Rationale: The source represents "new" or "incoming" data that should
139
+ * take precedence over existing target data for singular values.
140
+ *
141
+ * **Arrays (changes, files)**: Concatenated and deduplicated (target first, then source)
142
+ * - Rationale: For lists, we want to accumulate all entries rather than
143
+ * replacing them. Target entries are placed first to preserve order.
144
+ *
145
+ * @param source - The incoming/new RawConcept to merge (takes precedence for scalars)
146
+ * @param target - The existing/base RawConcept to merge into
147
+ * @returns Merged RawConcept or undefined if both inputs are empty
22
148
  */
149
+ function mergeRawConcepts(source, target) {
150
+ if (!source && !target) {
151
+ return undefined;
152
+ }
153
+ if (!source)
154
+ return target;
155
+ if (!target)
156
+ return source;
157
+ const merged = {};
158
+ // Scalars: source wins (newer data takes precedence)
159
+ merged.task = source.task || target.task;
160
+ merged.flow = source.flow || target.flow;
161
+ merged.timestamp = source.timestamp || target.timestamp;
162
+ // Arrays: concatenate and deduplicate (target first, then source)
163
+ const allChanges = [...(target.changes || []), ...(source.changes || [])];
164
+ if (allChanges.length > 0) {
165
+ merged.changes = [...new Set(allChanges)];
166
+ }
167
+ const allFiles = [...(target.files || []), ...(source.files || [])];
168
+ if (allFiles.length > 0) {
169
+ merged.files = [...new Set(allFiles)];
170
+ }
171
+ if (Object.keys(merged).length === 0) {
172
+ return undefined;
173
+ }
174
+ return merged;
175
+ }
176
+ function mergeNarratives(source, target) {
177
+ if (!source && !target) {
178
+ return undefined;
179
+ }
180
+ if (!source)
181
+ return target;
182
+ if (!target)
183
+ return source;
184
+ const merged = {};
185
+ if (source.structure || target.structure) {
186
+ const parts = [target.structure, source.structure].filter(Boolean);
187
+ merged.structure = parts.join('\n\n');
188
+ }
189
+ if (source.dependencies || target.dependencies) {
190
+ const parts = [target.dependencies, source.dependencies].filter(Boolean);
191
+ merged.dependencies = parts.join('\n\n');
192
+ }
193
+ if (source.features || target.features) {
194
+ const parts = [target.features, source.features].filter(Boolean);
195
+ merged.features = parts.join('\n\n');
196
+ }
197
+ if (Object.keys(merged).length === 0) {
198
+ return undefined;
199
+ }
200
+ return merged;
201
+ }
23
202
  export const MarkdownWriter = {
24
- /**
25
- * Generate context.md content with snippets and optional relations.
26
- * Used for both topics and subtopics in the knowledge hierarchy.
27
- */
28
203
  generateContext(data) {
29
- const snippets = data.snippets || [];
204
+ const snippets = (data.snippets || []).filter(s => s && s.trim());
30
205
  const relations = data.relations || [];
31
206
  const relationsSection = generateRelationsSection(relations);
32
- return `${relationsSection}
33
- ${snippets.length > 0 ? snippets.map(s => `${s}`).join('\n\n---\n\n') : 'No context available.'}
34
- `;
207
+ const rawConceptSection = generateRawConceptSection(data.rawConcept);
208
+ const narrativeSection = generateNarrativeSection(data.narrative);
209
+ const hasSnippets = snippets.length > 0;
210
+ // Build the content parts
211
+ const parts = [];
212
+ // Add sections (relations, rawConcept, narrative)
213
+ const sectionsContent = `${relationsSection}${rawConceptSection}${narrativeSection}`.trim();
214
+ if (sectionsContent) {
215
+ parts.push(sectionsContent);
216
+ }
217
+ // Add snippets if present
218
+ if (hasSnippets) {
219
+ const snippetsContent = snippets.join('\n\n---\n\n');
220
+ parts.push(snippetsContent);
221
+ }
222
+ // If nothing at all, return empty (should not happen in practice)
223
+ if (parts.length === 0) {
224
+ return '';
225
+ }
226
+ // Join parts with separator only if we have both sections and snippets
227
+ return parts.join('\n\n---\n\n') + '\n';
35
228
  },
36
- /**
37
- * Merge two context.md contents into one.
38
- * Combines snippets and relations, deduplicating where possible.
39
- *
40
- * @param sourceContent - Raw content from source context.md
41
- * @param targetContent - Raw content from target context.md
42
- * @returns Merged context.md content
43
- */
44
229
  mergeContexts(sourceContent, targetContent) {
45
- // Extract relations from both contents
46
230
  const sourceRelations = parseRelations(sourceContent);
47
231
  const targetRelations = parseRelations(targetContent);
48
- // Merge and deduplicate relations
49
232
  const mergedRelations = [...new Set([...sourceRelations, ...targetRelations])];
233
+ const sourceRawConcept = parseRawConceptSection(sourceContent);
234
+ const targetRawConcept = parseRawConceptSection(targetContent);
235
+ const mergedRawConcept = mergeRawConcepts(sourceRawConcept, targetRawConcept);
236
+ const sourceNarrative = parseNarrativeSection(sourceContent);
237
+ const targetNarrative = parseNarrativeSection(targetContent);
238
+ const mergedNarrative = mergeNarratives(sourceNarrative, targetNarrative);
50
239
  const sourceSnippets = extractSnippetsFromContent(sourceContent);
51
240
  const targetSnippets = extractSnippetsFromContent(targetContent);
52
- // Merge snippets (target first, then source)
53
- // Deduplicate by exact match
54
241
  const seenSnippets = new Set();
55
242
  const mergedSnippets = [];
56
243
  for (const snippet of [...targetSnippets, ...sourceSnippets]) {
@@ -59,10 +246,21 @@ ${snippets.length > 0 ? snippets.map(s => `${s}`).join('\n\n---\n\n') : 'No cont
59
246
  mergedSnippets.push(snippet);
60
247
  }
61
248
  }
62
- // Generate merged content
63
- const relationsSection = generateRelationsSection(mergedRelations);
64
- return `${relationsSection}
65
- ${mergedSnippets.length > 0 ? mergedSnippets.join('\n\n---\n\n') : 'No context available.'}
66
- `;
249
+ return MarkdownWriter.generateContext({
250
+ name: '',
251
+ narrative: mergedNarrative,
252
+ rawConcept: mergedRawConcept,
253
+ relations: mergedRelations,
254
+ snippets: mergedSnippets,
255
+ });
256
+ },
257
+ parseContent(content, name = '') {
258
+ return {
259
+ name,
260
+ narrative: parseNarrativeSection(content),
261
+ rawConcept: parseRawConceptSection(content),
262
+ relations: parseRelations(content),
263
+ snippets: extractSnippetsFromContent(content),
264
+ };
67
265
  },
68
266
  };
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Utilities for parsing and managing context relations.
3
- * Relations are expressed using @ notation: @domain/topic or @domain/topic/subtopic
3
+ * Relations are expressed using @ notation: @domain/topic/title.md or @domain/topic/subtopic/title.md
4
4
  */
5
5
  /**
6
- * Parse relations from context.md content.
7
- * Extracts all @domain/topic or @domain/topic/subtopic references.
6
+ * Parse relations from title.md content.
7
+ * Extracts all @domain/topic/title.md or @domain/topic/subtopic/title.md references.
8
8
  *
9
9
  * @param content - Markdown content to parse
10
10
  * @returns Array of unique relation paths (without @ prefix)
@@ -13,43 +13,27 @@
13
13
  * ```ts
14
14
  * const content = `
15
15
  * ## Relations
16
- * @code_style/error-handling
17
- * @structure/api-endpoints
16
+ * @code_style/error-handling/overview.md
17
+ * @structure/api/endpoints/rest.md
18
18
  * `
19
- * parseRelations(content) // ['code_style/error-handling', 'structure/api-endpoints']
19
+ * parseRelations(content) // ['code_style/error-handling/overview.md', 'structure/api/endpoints/rest.md']
20
20
  * ```
21
21
  */
22
22
  export declare function parseRelations(content: string): string[];
23
- /**
24
- * Validate a relation path format.
25
- * Valid formats: domain/topic or domain/topic/subtopic
26
- *
27
- * @param path - Relation path to validate (without @ prefix)
28
- * @returns True if path format is valid
29
- *
30
- * @example
31
- * ```ts
32
- * validateRelationPath('code_style/error-handling') // true
33
- * validateRelationPath('code_style/error-handling/try-catch') // true
34
- * validateRelationPath('invalid') // false
35
- * validateRelationPath('too/many/parts/here') // false
36
- * ```
37
- */
38
- export declare function validateRelationPath(path: string): boolean;
39
23
  /**
40
24
  * Resolve a relation path to an absolute file system path.
41
25
  *
42
26
  * @param basePath - Base path to context tree (e.g., '.brv/context-tree')
43
- * @param relation - Relation path (e.g., 'domain/topic' or 'domain/topic/subtopic')
44
- * @returns Absolute path to the context.md file
27
+ * @param relation - Relation path (e.g., 'domain/topic/title.md' or 'domain/topic/subtopic/title.md')
28
+ * @returns Absolute path to the title.md file
45
29
  *
46
30
  * @example
47
31
  * ```ts
48
- * resolveRelationPath('.brv/context-tree', 'code_style/error-handling')
49
- * // => '.brv/context-tree/code_style/error-handling/context.md'
32
+ * resolveRelationPath('.brv/context-tree', 'code_style/error-handling/overview.md')
33
+ * // => '.brv/context-tree/code_style/error-handling/overview.md'
50
34
  *
51
- * resolveRelationPath('.brv/context-tree', 'structure/api/endpoints')
52
- * // => '.brv/context-tree/structure/api/endpoints/context.md'
35
+ * resolveRelationPath('.brv/context-tree', 'structure/api/endpoints/rest.md')
36
+ * // => '.brv/context-tree/structure/api/endpoints/rest.md'
53
37
  * ```
54
38
  */
55
39
  export declare function resolveRelationPath(basePath: string, relation: string): string;
@@ -58,31 +42,33 @@ export declare function resolveRelationPath(basePath: string, relation: string):
58
42
  *
59
43
  * @param domain - Domain name
60
44
  * @param topic - Topic name
45
+ * @param title - Title (with .md extension)
61
46
  * @param subtopic - Optional subtopic name
62
47
  * @returns Formatted relation string with @ prefix
63
48
  *
64
49
  * @example
65
50
  * ```ts
66
- * formatRelation('code_style', 'error-handling')
67
- * // => '@code_style/error-handling'
51
+ * formatRelation('code_style', 'error-handling', 'overview.md')
52
+ * // => '@code_style/error-handling/overview.md'
68
53
  *
69
- * formatRelation('structure', 'api', 'endpoints')
70
- * // => '@structure/api/endpoints'
54
+ * formatRelation('structure', 'api', 'endpoints', 'rest.md')
55
+ * // => '@structure/api/endpoints/rest.md'
71
56
  * ```
72
57
  */
73
- export declare function formatRelation(domain: string, topic: string, subtopic?: string): string;
58
+ export declare function formatRelation(domain: string, topic: string, title: string, subtopic?: string): string;
74
59
  /**
75
60
  * Normalize a relation path by removing the @ prefix.
61
+ * Preserves file extensions (e.g., .md).
76
62
  *
77
63
  * @param relation - Relation path to normalize
78
- * @returns Normalized relation path
64
+ * @returns Normalized relation path without @ prefix (file extension preserved)
79
65
  *
80
66
  * @example
81
67
  * ```ts
82
- * normalizeRelation('code_style/error-handling') // 'code_style/error-handling'
83
- * normalizeRelation('@code_style/error-handling') // 'code_style/error-handling'
68
+ * normalizeRelation('code_style/error-handling.md') // 'code_style/error-handling.md'
69
+ * normalizeRelation('@code_style/error-handling.md') // 'code_style/error-handling.md'
84
70
  * normalizeRelation('code_style/error-handling/title.md') // 'code_style/error-handling/title.md'
85
- * normalizeRelation('@@@code_style/error-handling/title.md') // 'code_style/error-handling/title.md'
71
+ * normalizeRelation('code_style/error-handling/file.md') // 'code_style/error-handling/file.md'
86
72
  * ```
87
73
  */
88
74
  export declare function normalizeRelation(relation: string): string;
@@ -95,8 +81,8 @@ export declare function normalizeRelation(relation: string): string;
95
81
  *
96
82
  * @example
97
83
  * ```ts
98
- * generateRelationsSection(['code_style/error-handling', 'structure/api'])
99
- * // => '\n## Relations\n@code_style/error-handling\n@structure/api\n'
84
+ * generateRelationsSection(['code_style/error-handling/overview.md', 'structure/api/rest.md'])
85
+ * // => '\n## Relations\n@code_style/error-handling/overview.md\n@structure/api/rest.md\n'
100
86
  *
101
87
  * generateRelationsSection([])
102
88
  * // => ''
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Utilities for parsing and managing context relations.
3
- * Relations are expressed using @ notation: @domain/topic or @domain/topic/subtopic
3
+ * Relations are expressed using @ notation: @domain/topic/title.md or @domain/topic/subtopic/title.md
4
4
  */
5
5
  /**
6
6
  * Regular expression to match relation paths in markdown content.
7
- * Matches: @domain/topic or @domain/topic/subtopic
7
+ * Matches: @domain/topic/title.md or @domain/topic/subtopic/title.md
8
8
  */
9
- const RELATION_PATTERN = /@([\w-]+)\/([\w-]+)(?:\/([\w-]+))?/g;
9
+ const RELATION_PATTERN = /@([\w-]+\/[\w-]+(?:\/[\w-]+)?\/[\w-]+(?:\.[\w]+)?)(?![\w/-])/g;
10
10
  /**
11
- * Parse relations from context.md content.
12
- * Extracts all @domain/topic or @domain/topic/subtopic references.
11
+ * Parse relations from title.md content.
12
+ * Extracts all @domain/topic/title.md or @domain/topic/subtopic/title.md references.
13
13
  *
14
14
  * @param content - Markdown content to parse
15
15
  * @returns Array of unique relation paths (without @ prefix)
@@ -18,104 +18,77 @@ const RELATION_PATTERN = /@([\w-]+)\/([\w-]+)(?:\/([\w-]+))?/g;
18
18
  * ```ts
19
19
  * const content = `
20
20
  * ## Relations
21
- * @code_style/error-handling
22
- * @structure/api-endpoints
21
+ * @code_style/error-handling/overview.md
22
+ * @structure/api/endpoints/rest.md
23
23
  * `
24
- * parseRelations(content) // ['code_style/error-handling', 'structure/api-endpoints']
24
+ * parseRelations(content) // ['code_style/error-handling/overview.md', 'structure/api/endpoints/rest.md']
25
25
  * ```
26
26
  */
27
27
  export function parseRelations(content) {
28
28
  const relations = new Set();
29
- // Extract all @domain/topic or @domain/topic/subtopic patterns
29
+ // Extract all @domain/topic/title.md or @domain/topic/subtopic/title.md patterns
30
30
  const matches = content.matchAll(RELATION_PATTERN);
31
31
  for (const match of matches) {
32
- const [, domain, topic, subtopic] = match;
33
- const relation = subtopic
34
- ? `${domain}/${topic}/${subtopic}`
35
- : `${domain}/${topic}`;
36
- relations.add(relation);
32
+ const [, fullPath] = match;
33
+ relations.add(fullPath.trim());
37
34
  }
38
35
  return [...relations];
39
36
  }
40
- /**
41
- * Validate a relation path format.
42
- * Valid formats: domain/topic or domain/topic/subtopic
43
- *
44
- * @param path - Relation path to validate (without @ prefix)
45
- * @returns True if path format is valid
46
- *
47
- * @example
48
- * ```ts
49
- * validateRelationPath('code_style/error-handling') // true
50
- * validateRelationPath('code_style/error-handling/try-catch') // true
51
- * validateRelationPath('invalid') // false
52
- * validateRelationPath('too/many/parts/here') // false
53
- * ```
54
- */
55
- export function validateRelationPath(path) {
56
- const parts = path.split('/');
57
- // Must have 2 or 3 parts: domain/topic or domain/topic/subtopic
58
- if (parts.length < 2 || parts.length > 3) {
59
- return false;
60
- }
61
- // Each part must be non-empty and contain only valid characters
62
- const validPartPattern = /^[\w-]+$/;
63
- return parts.every(part => validPartPattern.test(part));
64
- }
65
37
  /**
66
38
  * Resolve a relation path to an absolute file system path.
67
39
  *
68
40
  * @param basePath - Base path to context tree (e.g., '.brv/context-tree')
69
- * @param relation - Relation path (e.g., 'domain/topic' or 'domain/topic/subtopic')
70
- * @returns Absolute path to the context.md file
41
+ * @param relation - Relation path (e.g., 'domain/topic/title.md' or 'domain/topic/subtopic/title.md')
42
+ * @returns Absolute path to the title.md file
71
43
  *
72
44
  * @example
73
45
  * ```ts
74
- * resolveRelationPath('.brv/context-tree', 'code_style/error-handling')
75
- * // => '.brv/context-tree/code_style/error-handling/context.md'
46
+ * resolveRelationPath('.brv/context-tree', 'code_style/error-handling/overview.md')
47
+ * // => '.brv/context-tree/code_style/error-handling/overview.md'
76
48
  *
77
- * resolveRelationPath('.brv/context-tree', 'structure/api/endpoints')
78
- * // => '.brv/context-tree/structure/api/endpoints/context.md'
49
+ * resolveRelationPath('.brv/context-tree', 'structure/api/endpoints/rest.md')
50
+ * // => '.brv/context-tree/structure/api/endpoints/rest.md'
79
51
  * ```
80
52
  */
81
53
  export function resolveRelationPath(basePath, relation) {
82
- const parts = relation.split('/');
83
- return `${basePath}/${parts.join('/')}/context.md`;
54
+ return `${basePath}/${relation}`;
84
55
  }
85
56
  /**
86
57
  * Format a relation path using @ notation.
87
58
  *
88
59
  * @param domain - Domain name
89
60
  * @param topic - Topic name
61
+ * @param title - Title (with .md extension)
90
62
  * @param subtopic - Optional subtopic name
91
63
  * @returns Formatted relation string with @ prefix
92
64
  *
93
65
  * @example
94
66
  * ```ts
95
- * formatRelation('code_style', 'error-handling')
96
- * // => '@code_style/error-handling'
67
+ * formatRelation('code_style', 'error-handling', 'overview.md')
68
+ * // => '@code_style/error-handling/overview.md'
97
69
  *
98
- * formatRelation('structure', 'api', 'endpoints')
99
- * // => '@structure/api/endpoints'
70
+ * formatRelation('structure', 'api', 'endpoints', 'rest.md')
71
+ * // => '@structure/api/endpoints/rest.md'
100
72
  * ```
101
73
  */
102
- export function formatRelation(domain, topic, subtopic) {
74
+ export function formatRelation(domain, topic, title, subtopic) {
103
75
  return subtopic
104
- ? `@${domain}/${topic}/${subtopic}`
105
- : `@${domain}/${topic}`;
76
+ ? `@${domain}/${topic}/${subtopic}/${title}`
77
+ : `@${domain}/${topic}/${title}`;
106
78
  }
107
79
  /**
108
80
  * Normalize a relation path by removing the @ prefix.
81
+ * Preserves file extensions (e.g., .md).
109
82
  *
110
83
  * @param relation - Relation path to normalize
111
- * @returns Normalized relation path
84
+ * @returns Normalized relation path without @ prefix (file extension preserved)
112
85
  *
113
86
  * @example
114
87
  * ```ts
115
- * normalizeRelation('code_style/error-handling') // 'code_style/error-handling'
116
- * normalizeRelation('@code_style/error-handling') // 'code_style/error-handling'
88
+ * normalizeRelation('code_style/error-handling.md') // 'code_style/error-handling.md'
89
+ * normalizeRelation('@code_style/error-handling.md') // 'code_style/error-handling.md'
117
90
  * normalizeRelation('code_style/error-handling/title.md') // 'code_style/error-handling/title.md'
118
- * normalizeRelation('@@@code_style/error-handling/title.md') // 'code_style/error-handling/title.md'
91
+ * normalizeRelation('code_style/error-handling/file.md') // 'code_style/error-handling/file.md'
119
92
  * ```
120
93
  */
121
94
  export function normalizeRelation(relation) {
@@ -130,8 +103,8 @@ export function normalizeRelation(relation) {
130
103
  *
131
104
  * @example
132
105
  * ```ts
133
- * generateRelationsSection(['code_style/error-handling', 'structure/api'])
134
- * // => '\n## Relations\n@code_style/error-handling\n@structure/api\n'
106
+ * generateRelationsSection(['code_style/error-handling/overview.md', 'structure/api/rest.md'])
107
+ * // => '\n## Relations\n@code_style/error-handling/overview.md\n@structure/api/rest.md\n'
135
108
  *
136
109
  * generateRelationsSection([])
137
110
  * // => ''
@@ -142,7 +115,12 @@ export function generateRelationsSection(relations) {
142
115
  return '';
143
116
  }
144
117
  const formattedRelations = relations
145
- .map(rel => `@${normalizeRelation(rel)}`)
118
+ .map(rel => {
119
+ const normalized = normalizeRelation(rel);
120
+ // Ensure .md extension is present
121
+ const withExtension = normalized.endsWith('.md') ? normalized : `${normalized}.md`;
122
+ return `@${withExtension}`;
123
+ })
146
124
  .join('\n');
147
125
  return `\n## Relations\n${formattedRelations}\n`;
148
126
  }
@@ -429,6 +429,8 @@ export declare const TransportLlmEventList: readonly ["llmservice:thinking", "ll
429
429
  export declare const TransportAgentEventNames: {
430
430
  readonly CONNECTED: "agent:connected";
431
431
  readonly DISCONNECTED: "agent:disconnected";
432
+ readonly NEW_SESSION: "agent:newSession";
433
+ readonly NEW_SESSION_CREATED: "agent:newSessionCreated";
432
434
  readonly REGISTER: "agent:register";
433
435
  readonly RESTART: "agent:restart";
434
436
  readonly RESTARTED: "agent:restarted";
@@ -449,10 +451,10 @@ export declare const TransportSessionEventNames: {
449
451
  * Internal message, not exposed to external clients
450
452
  */
451
453
  export declare const TaskExecuteSchema: z.ZodObject<{
452
- /** Client ID that created the task (for response routing) */
453
- clientId: z.ZodString;
454
454
  /** Client's working directory for file validation */
455
455
  clientCwd: z.ZodOptional<z.ZodString>;
456
+ /** Client ID that created the task (for response routing) */
457
+ clientId: z.ZodString;
456
458
  /** Task content/prompt */
457
459
  content: z.ZodString;
458
460
  /** Optional file paths for curate --files */
@@ -1137,6 +1139,37 @@ export declare const AgentRestartResponseSchema: z.ZodObject<{
1137
1139
  success: boolean;
1138
1140
  error?: string | undefined;
1139
1141
  }>;
1142
+ /**
1143
+ * Request to create a new session (end current, start fresh).
1144
+ * Used by /new command to start a fresh conversation.
1145
+ */
1146
+ export declare const AgentNewSessionRequestSchema: z.ZodObject<{
1147
+ /** Optional reason for new session (for logging) */
1148
+ reason: z.ZodOptional<z.ZodString>;
1149
+ }, "strip", z.ZodTypeAny, {
1150
+ reason?: string | undefined;
1151
+ }, {
1152
+ reason?: string | undefined;
1153
+ }>;
1154
+ /**
1155
+ * Response after new session is created.
1156
+ */
1157
+ export declare const AgentNewSessionResponseSchema: z.ZodObject<{
1158
+ /** Error message if session creation failed */
1159
+ error: z.ZodOptional<z.ZodString>;
1160
+ /** The new session ID */
1161
+ sessionId: z.ZodOptional<z.ZodString>;
1162
+ /** Whether the new session was created successfully */
1163
+ success: z.ZodBoolean;
1164
+ }, "strip", z.ZodTypeAny, {
1165
+ success: boolean;
1166
+ error?: string | undefined;
1167
+ sessionId?: string | undefined;
1168
+ }, {
1169
+ success: boolean;
1170
+ error?: string | undefined;
1171
+ sessionId?: string | undefined;
1172
+ }>;
1140
1173
  export type TodoItem = z.infer<typeof TodoItemSchema>;
1141
1174
  export type ChunkPayload = z.infer<typeof ChunkPayloadSchema>;
1142
1175
  export type ResponsePayload = z.infer<typeof ResponsePayloadSchema>;
@@ -1163,3 +1196,5 @@ export type SessionSwitchResponse = z.infer<typeof SessionSwitchResponseSchema>;
1163
1196
  export type SessionSwitchedBroadcast = z.infer<typeof SessionSwitchedBroadcastSchema>;
1164
1197
  export type AgentRestartRequest = z.infer<typeof AgentRestartRequestSchema>;
1165
1198
  export type AgentRestartResponse = z.infer<typeof AgentRestartResponseSchema>;
1199
+ export type AgentNewSessionRequest = z.infer<typeof AgentNewSessionRequestSchema>;
1200
+ export type AgentNewSessionResponse = z.infer<typeof AgentNewSessionResponseSchema>;