busy-cli 0.1.2

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 +129 -0
  2. package/dist/builders/context.d.ts +50 -0
  3. package/dist/builders/context.d.ts.map +1 -0
  4. package/dist/builders/context.js +190 -0
  5. package/dist/cache/index.d.ts +100 -0
  6. package/dist/cache/index.d.ts.map +1 -0
  7. package/dist/cache/index.js +270 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +463 -0
  11. package/dist/commands/package.d.ts +96 -0
  12. package/dist/commands/package.d.ts.map +1 -0
  13. package/dist/commands/package.js +285 -0
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/loader.d.ts +6 -0
  18. package/dist/loader.d.ts.map +1 -0
  19. package/dist/loader.js +361 -0
  20. package/dist/merge.d.ts +16 -0
  21. package/dist/merge.d.ts.map +1 -0
  22. package/dist/merge.js +102 -0
  23. package/dist/package/manifest.d.ts +59 -0
  24. package/dist/package/manifest.d.ts.map +1 -0
  25. package/dist/package/manifest.js +265 -0
  26. package/dist/parser.d.ts +28 -0
  27. package/dist/parser.d.ts.map +1 -0
  28. package/dist/parser.js +220 -0
  29. package/dist/parsers/frontmatter.d.ts +14 -0
  30. package/dist/parsers/frontmatter.d.ts.map +1 -0
  31. package/dist/parsers/frontmatter.js +110 -0
  32. package/dist/parsers/imports.d.ts +48 -0
  33. package/dist/parsers/imports.d.ts.map +1 -0
  34. package/dist/parsers/imports.js +147 -0
  35. package/dist/parsers/links.d.ts +12 -0
  36. package/dist/parsers/links.d.ts.map +1 -0
  37. package/dist/parsers/links.js +79 -0
  38. package/dist/parsers/localdefs.d.ts +6 -0
  39. package/dist/parsers/localdefs.d.ts.map +1 -0
  40. package/dist/parsers/localdefs.js +132 -0
  41. package/dist/parsers/operations.d.ts +32 -0
  42. package/dist/parsers/operations.d.ts.map +1 -0
  43. package/dist/parsers/operations.js +313 -0
  44. package/dist/parsers/sections.d.ts +15 -0
  45. package/dist/parsers/sections.d.ts.map +1 -0
  46. package/dist/parsers/sections.js +173 -0
  47. package/dist/parsers/tools.d.ts +30 -0
  48. package/dist/parsers/tools.d.ts.map +1 -0
  49. package/dist/parsers/tools.js +178 -0
  50. package/dist/parsers/triggers.d.ts +35 -0
  51. package/dist/parsers/triggers.d.ts.map +1 -0
  52. package/dist/parsers/triggers.js +219 -0
  53. package/dist/providers/base.d.ts +60 -0
  54. package/dist/providers/base.d.ts.map +1 -0
  55. package/dist/providers/base.js +34 -0
  56. package/dist/providers/github.d.ts +18 -0
  57. package/dist/providers/github.d.ts.map +1 -0
  58. package/dist/providers/github.js +109 -0
  59. package/dist/providers/gitlab.d.ts +18 -0
  60. package/dist/providers/gitlab.d.ts.map +1 -0
  61. package/dist/providers/gitlab.js +101 -0
  62. package/dist/providers/index.d.ts +13 -0
  63. package/dist/providers/index.d.ts.map +1 -0
  64. package/dist/providers/index.js +17 -0
  65. package/dist/providers/local.d.ts +31 -0
  66. package/dist/providers/local.d.ts.map +1 -0
  67. package/dist/providers/local.js +116 -0
  68. package/dist/providers/url.d.ts +16 -0
  69. package/dist/providers/url.d.ts.map +1 -0
  70. package/dist/providers/url.js +45 -0
  71. package/dist/registry/index.d.ts +99 -0
  72. package/dist/registry/index.d.ts.map +1 -0
  73. package/dist/registry/index.js +320 -0
  74. package/dist/types/schema.d.ts +3259 -0
  75. package/dist/types/schema.d.ts.map +1 -0
  76. package/dist/types/schema.js +258 -0
  77. package/dist/utils/logger.d.ts +19 -0
  78. package/dist/utils/logger.d.ts.map +1 -0
  79. package/dist/utils/logger.js +23 -0
  80. package/dist/utils/slugify.d.ts +14 -0
  81. package/dist/utils/slugify.d.ts.map +1 -0
  82. package/dist/utils/slugify.js +28 -0
  83. package/package.json +61 -0
  84. package/src/__tests__/cache.test.ts +393 -0
  85. package/src/__tests__/cli-package.test.ts +667 -0
  86. package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
  87. package/src/__tests__/fixtures/concept.busy.md +30 -0
  88. package/src/__tests__/fixtures/document.busy.md +44 -0
  89. package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
  90. package/src/__tests__/fixtures/tool-document.busy.md +71 -0
  91. package/src/__tests__/fixtures/tool.busy.md +54 -0
  92. package/src/__tests__/imports.test.ts +244 -0
  93. package/src/__tests__/integration.test.ts +432 -0
  94. package/src/__tests__/operations.test.ts +408 -0
  95. package/src/__tests__/package-manifest.test.ts +455 -0
  96. package/src/__tests__/providers.test.ts +672 -0
  97. package/src/__tests__/registry.test.ts +402 -0
  98. package/src/__tests__/schema.test.ts +467 -0
  99. package/src/__tests__/tools.test.ts +376 -0
  100. package/src/__tests__/triggers.test.ts +312 -0
  101. package/src/builders/context.ts +294 -0
  102. package/src/cache/index.ts +312 -0
  103. package/src/cli/index.ts +514 -0
  104. package/src/commands/package.ts +392 -0
  105. package/src/index.ts +46 -0
  106. package/src/loader.ts +474 -0
  107. package/src/merge.ts +126 -0
  108. package/src/package/manifest.ts +349 -0
  109. package/src/parser.ts +278 -0
  110. package/src/parsers/frontmatter.ts +135 -0
  111. package/src/parsers/imports.ts +196 -0
  112. package/src/parsers/links.ts +108 -0
  113. package/src/parsers/localdefs.ts +166 -0
  114. package/src/parsers/operations.ts +404 -0
  115. package/src/parsers/sections.ts +230 -0
  116. package/src/parsers/tools.ts +215 -0
  117. package/src/parsers/triggers.ts +252 -0
  118. package/src/providers/base.ts +77 -0
  119. package/src/providers/github.ts +129 -0
  120. package/src/providers/gitlab.ts +121 -0
  121. package/src/providers/index.ts +25 -0
  122. package/src/providers/local.ts +129 -0
  123. package/src/providers/url.ts +56 -0
  124. package/src/registry/index.ts +408 -0
  125. package/src/types/schema.ts +369 -0
  126. package/src/utils/logger.ts +25 -0
  127. package/src/utils/slugify.ts +31 -0
  128. package/tsconfig.json +21 -0
@@ -0,0 +1,84 @@
1
+ ---
2
+ Name: AutomatedWorkflow
3
+ Type: [Document]
4
+ Description: A document with automated triggers for processing incoming data.
5
+ ---
6
+
7
+ # [Imports]
8
+
9
+ [Document]: ./document.busy.md
10
+ [ValidateInput]: ./operations.busy.md#validateinput
11
+ [ProcessData]: ./operations.busy.md#processdata
12
+
13
+ # [Local Definitions]
14
+
15
+ ## InputSource
16
+ External data sources that can trigger this workflow.
17
+
18
+ ## ProcessingRule
19
+ Rules for how to handle incoming data.
20
+
21
+ # [Setup]
22
+
23
+ Before using this workflow:
24
+ 1. Configure API credentials
25
+ 2. Set up notification channels
26
+ 3. Verify processing rules
27
+
28
+ # [Triggers]
29
+
30
+ - Set alarm for 6am each morning to run DailyReport
31
+ - When data.received from *@trusted.com, run ProcessIncoming
32
+ - When webhook.triggered, run HandleWebhook
33
+
34
+ # [Operations]
35
+
36
+ ## ProcessIncoming
37
+
38
+ Process incoming data from external sources.
39
+
40
+ ### [Inputs]
41
+ - source: The data source identifier
42
+ - payload: The incoming data payload
43
+
44
+ ### [Outputs]
45
+ - result: Processing result
46
+ - log_id: Processing log identifier
47
+
48
+ ### [Steps]
49
+ 1. Run [ValidateInput] on the payload
50
+ 2. Apply processing rules from [ProcessingRule]
51
+ 3. Execute [ProcessData] with validated input
52
+ 4. Log results and notify stakeholders
53
+
54
+ ### [Checklist]
55
+ - Input validated
56
+ - Rules applied
57
+ - Data processed
58
+ - Stakeholders notified
59
+
60
+ ## DailyReport
61
+
62
+ Generate and send daily activity report.
63
+
64
+ ### [Steps]
65
+ 1. Gather metrics from past 24 hours
66
+ 2. Generate summary report
67
+ 3. Send to configured recipients
68
+
69
+ ### [Checklist]
70
+ - Metrics collected
71
+ - Report generated
72
+ - Report sent
73
+
74
+ ## HandleWebhook
75
+
76
+ Handle incoming webhook requests.
77
+
78
+ ### [Inputs]
79
+ - webhook_data: The webhook payload
80
+
81
+ ### [Steps]
82
+ 1. Parse webhook payload
83
+ 2. Determine action based on event type
84
+ 3. Execute appropriate handler
@@ -0,0 +1,30 @@
1
+ ---
2
+ Name: Concept
3
+ Type: [Concept]
4
+ Description: Base concept type that all other types build upon.
5
+ ---
6
+ # [Imports]
7
+
8
+ # [Local Definitions]
9
+
10
+ ## Definition
11
+ A named unit of meaning that can be referenced and composed.
12
+
13
+ # [Setup]
14
+
15
+ When evaluating a concept, resolve all imports first, then process definitions.
16
+
17
+ # [Operations]
18
+
19
+ ## EvaluateConcept
20
+
21
+ Evaluate a concept and return its resolved definition.
22
+
23
+ ### [Steps]
24
+ 1. Load the concept document
25
+ 2. Resolve any import references
26
+ 3. Return the concept definition
27
+
28
+ ### [Checklist]
29
+ - Concept loaded successfully
30
+ - All imports resolved
@@ -0,0 +1,44 @@
1
+ ---
2
+ Name: Document
3
+ Type: [Document]
4
+ Description: Base document type for all BUSY documents.
5
+ ---
6
+ # [Imports]
7
+
8
+ [Concept]: ./concept.busy.md
9
+
10
+ # [Local Definitions]
11
+
12
+ ## Imports Section
13
+ Section where external document references are declared.
14
+
15
+ ## Local Definitions Section
16
+ Section where local concepts are defined.
17
+
18
+ ## Setup Section
19
+ Section containing prerequisites and initialization.
20
+
21
+ ## Operations Section
22
+ Section containing callable operations.
23
+
24
+ # [Setup]
25
+
26
+ A document must be evaluated before any operations can be executed.
27
+
28
+ # [Operations]
29
+
30
+ ## EvaluateDocument
31
+
32
+ Load and evaluate a BUSY document.
33
+
34
+ ### [Steps]
35
+ 1. Parse frontmatter for metadata
36
+ 2. Resolve all imports
37
+ 3. Load local definitions
38
+ 4. Execute setup section
39
+
40
+ ### [Checklist]
41
+ - Frontmatter parsed
42
+ - Imports resolved
43
+ - Definitions loaded
44
+ - Setup executed
@@ -0,0 +1,45 @@
1
+ ---
2
+ Name: SimpleOperation
3
+ Type: [Operation]
4
+ Description: A simple test operation for parsing validation.
5
+ ---
6
+ # [Imports]
7
+
8
+ [Concept]: ./concept.busy.md
9
+ [Document]: ./document.busy.md
10
+
11
+ # [Local Definitions]
12
+
13
+ ## InputParameter
14
+ The input data required for this operation to execute.
15
+
16
+ ## OutputResult
17
+ The result produced by executing this operation.
18
+
19
+ # [Setup]
20
+
21
+ Before executing this operation, ensure the context is properly initialized.
22
+
23
+ # [Operations]
24
+
25
+ ## ExecuteTask
26
+
27
+ Execute a simple task with inputs and outputs.
28
+
29
+ ### [Inputs]
30
+ - task_name: Name of the task to execute
31
+ - context: Execution context with required parameters
32
+
33
+ ### [Steps]
34
+ 1. Validate the input parameters are present
35
+ 2. Execute the main task logic referencing [Concept]
36
+ 3. Store the result in [OutputResult] format
37
+
38
+ ### [Outputs]
39
+ - result: The computed result
40
+ - status: Success or failure indicator
41
+
42
+ ### [Checklist]
43
+ - Input parameters validated
44
+ - Task executed successfully
45
+ - Output stored correctly
@@ -0,0 +1,71 @@
1
+ ---
2
+ Name: GmailTool
3
+ Type: [Tool]
4
+ Description: Tool for sending and receiving emails via Gmail API.
5
+ Provider: composio
6
+ ---
7
+ # [Imports]
8
+
9
+ [Tool]: ./tool.busy.md
10
+ [Operation]: ./operation.busy.md
11
+
12
+ # [Local Definitions]
13
+
14
+ ## Capability
15
+ Send and receive emails through the Gmail API with support for attachments and HTML content.
16
+
17
+ ## Invocation Contract
18
+ Uses Composio's GMAIL integration with OAuth2 authentication.
19
+
20
+ # [Setup]
21
+
22
+ Ensure GMAIL_OAUTH_TOKEN environment variable is set.
23
+
24
+ # [Tools]
25
+
26
+ ## send_email
27
+
28
+ Send an email to one or more recipients.
29
+
30
+ ### [Inputs]
31
+ - to: Recipient email address or comma-separated list
32
+ - subject: Email subject line
33
+ - body: Email body content (plain text or HTML)
34
+ - attachments: Optional list of file paths to attach
35
+
36
+ ### [Outputs]
37
+ - message_id: The ID of the sent message
38
+ - thread_id: The thread ID for conversation tracking
39
+
40
+ ### [Examples]
41
+ - send_email(to="user@example.com", subject="Hello", body="Test message")
42
+ - send_email(to="a@x.com,b@x.com", subject="Team Update", body="<h1>News</h1>")
43
+
44
+ #### composio
45
+ Action: GMAIL_SEND_EMAIL
46
+ Parameters:
47
+ to: to
48
+ subject: subject
49
+ body: body
50
+
51
+ ## read_inbox
52
+
53
+ Read messages from the inbox.
54
+
55
+ ### [Inputs]
56
+ - max_results: Maximum number of messages to return
57
+ - query: Optional Gmail search query
58
+
59
+ ### [Outputs]
60
+ - messages: List of message objects with id, subject, from, snippet
61
+
62
+ #### composio
63
+ Action: GMAIL_LIST_MESSAGES
64
+ Parameters:
65
+ maxResults: max_results
66
+ q: query
67
+
68
+ # [Triggers]
69
+
70
+ - When gmail.message.received from *@important.com, run ProcessImportantEmail
71
+ - Set alarm for 9am each morning to run InboxSummary
@@ -0,0 +1,54 @@
1
+ ---
2
+ Name: GmailTools
3
+ Type: [Tool]
4
+ Description: Gmail integration tools for email management.
5
+ Provider: composio
6
+ ---
7
+
8
+ # [Imports]
9
+
10
+ [Tool]: ../core/tool.busy.md
11
+
12
+ # [Tools]
13
+
14
+ ## send_email
15
+
16
+ Send an email to one or more recipients.
17
+
18
+ ### [Inputs]
19
+ - to: Recipient email address
20
+ - subject: Email subject line
21
+ - body: Email body content
22
+
23
+ ### [Outputs]
24
+ - message_id: The sent message ID
25
+ - thread_id: The thread ID
26
+
27
+ ### [Providers]
28
+
29
+ #### composio
30
+ Action: GMAIL_SEND_EMAIL
31
+ Parameters:
32
+ to: to
33
+ subject: subject
34
+ body: body
35
+
36
+ ## list_emails
37
+
38
+ List emails from the inbox.
39
+
40
+ ### [Inputs]
41
+ - limit: Maximum number of emails to return
42
+ - query: Search query string
43
+
44
+ ### [Outputs]
45
+ - emails: List of email objects
46
+ - next_page_token: Token for pagination
47
+
48
+ ### [Providers]
49
+
50
+ #### composio
51
+ Action: GMAIL_LIST_EMAILS
52
+ Parameters:
53
+ max_results: limit
54
+ q: query
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Import Parsing Tests - Match busy-python import format
3
+ *
4
+ * busy-python uses reference-style markdown links:
5
+ * [ConceptName]: path or [ConceptName]: path#anchor
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { parseImports, resolveImportTarget } from '../parsers/imports';
10
+ import type { Import } from '../types/schema';
11
+
12
+ describe('parseImports', () => {
13
+ it('should parse simple reference-style import', () => {
14
+ const content = `
15
+ # [Imports]
16
+
17
+ [Operation]: ./operation.busy.md
18
+ `;
19
+
20
+ const result = parseImports(content);
21
+ expect(result.imports).toHaveLength(1);
22
+ expect(result.imports[0]).toEqual({
23
+ conceptName: 'Operation',
24
+ path: './operation.busy.md',
25
+ anchor: undefined,
26
+ });
27
+ });
28
+
29
+ it('should parse import with anchor', () => {
30
+ const content = `
31
+ [RunChecklist]: ./checklist.busy.md#runchecklist
32
+ `;
33
+
34
+ const result = parseImports(content);
35
+ expect(result.imports).toHaveLength(1);
36
+ expect(result.imports[0]).toEqual({
37
+ conceptName: 'RunChecklist',
38
+ path: './checklist.busy.md',
39
+ anchor: 'runchecklist',
40
+ });
41
+ });
42
+
43
+ it('should parse multiple imports', () => {
44
+ const content = `
45
+ # [Imports]
46
+
47
+ [Concept]: ./concept.busy.md
48
+ [Document]: ./document.busy.md
49
+ [Operation]: ./operation.busy.md
50
+ [input]: ./operation.busy.md#input
51
+ [output]: ./operation.busy.md#output
52
+ `;
53
+
54
+ const result = parseImports(content);
55
+ expect(result.imports).toHaveLength(5);
56
+
57
+ // Check specific imports
58
+ const conceptImport = result.imports.find((i) => i.conceptName === 'Concept');
59
+ expect(conceptImport?.path).toBe('./concept.busy.md');
60
+ expect(conceptImport?.anchor).toBeUndefined();
61
+
62
+ const inputImport = result.imports.find((i) => i.conceptName === 'input');
63
+ expect(inputImport?.path).toBe('./operation.busy.md');
64
+ expect(inputImport?.anchor).toBe('input');
65
+ });
66
+
67
+ it('should handle imports with parent directory paths', () => {
68
+ const content = `
69
+ [Tool]: ../toolbox/tool.busy.md
70
+ [Utils]: ../../shared/utils.busy.md
71
+ `;
72
+
73
+ const result = parseImports(content);
74
+ expect(result.imports).toHaveLength(2);
75
+ expect(result.imports[0].path).toBe('../toolbox/tool.busy.md');
76
+ expect(result.imports[1].path).toBe('../../shared/utils.busy.md');
77
+ });
78
+
79
+ it('should build symbol table from imports', () => {
80
+ const content = `
81
+ [Operation]: ./operation.busy.md
82
+ [RunChecklist]: ./checklist.busy.md#runchecklist
83
+ `;
84
+
85
+ const result = parseImports(content);
86
+ expect(result.symbols).toHaveProperty('Operation');
87
+ expect(result.symbols).toHaveProperty('RunChecklist');
88
+ });
89
+
90
+ it('should handle imports anywhere in document (not just imports section)', () => {
91
+ const content = `
92
+ ---
93
+ Name: Test
94
+ Type: [Document]
95
+ Description: Test
96
+ ---
97
+
98
+ Some content here.
99
+
100
+ [Concept]: ./concept.busy.md
101
+
102
+ More content.
103
+
104
+ [Other]: ./other.busy.md#anchor
105
+ `;
106
+
107
+ const result = parseImports(content);
108
+ expect(result.imports).toHaveLength(2);
109
+ });
110
+
111
+ it('should not parse inline markdown links as imports', () => {
112
+ const content = `
113
+ This is [not an import](./file.md) in inline format.
114
+
115
+ [Actual Import]: ./import.busy.md
116
+ `;
117
+
118
+ const result = parseImports(content);
119
+ expect(result.imports).toHaveLength(1);
120
+ expect(result.imports[0].conceptName).toBe('Actual Import');
121
+ });
122
+
123
+ it('should handle concept names with spaces', () => {
124
+ const content = `
125
+ [Local Definitions]: ./document.busy.md#local-definitions
126
+ [Imports Section]: ./document.busy.md#imports-section
127
+ `;
128
+
129
+ const result = parseImports(content);
130
+ expect(result.imports).toHaveLength(2);
131
+ expect(result.imports[0].conceptName).toBe('Local Definitions');
132
+ expect(result.imports[1].conceptName).toBe('Imports Section');
133
+ });
134
+
135
+ it('should return empty array for document with no imports', () => {
136
+ const content = `
137
+ ---
138
+ Name: NoImports
139
+ Type: [Document]
140
+ Description: Document with no imports
141
+ ---
142
+
143
+ # [Setup]
144
+
145
+ Just some setup content.
146
+ `;
147
+
148
+ const result = parseImports(content);
149
+ expect(result.imports).toHaveLength(0);
150
+ expect(result.symbols).toEqual({});
151
+ });
152
+
153
+ it('should handle anchors with hyphens and numbers', () => {
154
+ const content = `
155
+ [Step1]: ./steps.busy.md#step-1
156
+ [RunStep2]: ./steps.busy.md#run-step-2-validation
157
+ `;
158
+
159
+ const result = parseImports(content);
160
+ expect(result.imports[0].anchor).toBe('step-1');
161
+ expect(result.imports[1].anchor).toBe('run-step-2-validation');
162
+ });
163
+ });
164
+
165
+ describe('resolveImportTarget', () => {
166
+ const fileMap = new Map([
167
+ ['operation.busy.md', { docId: 'operation', path: './operation.busy.md' }],
168
+ ['concept.busy.md', { docId: 'concept', path: './concept.busy.md' }],
169
+ ['document.busy.md', { docId: 'document', path: './document.busy.md' }],
170
+ ['checklist.busy.md', { docId: 'checklist', path: './checklist.busy.md' }],
171
+ ['operation', { docId: 'operation', path: './operation.busy.md' }],
172
+ ]);
173
+
174
+ it('should resolve simple path to docId', () => {
175
+ const result = resolveImportTarget('./operation.busy.md', fileMap);
176
+ expect(result.docId).toBe('operation');
177
+ expect(result.slug).toBeUndefined();
178
+ });
179
+
180
+ it('should resolve path with anchor to docId and slug', () => {
181
+ const result = resolveImportTarget('./checklist.busy.md#runchecklist', fileMap);
182
+ expect(result.docId).toBe('checklist');
183
+ expect(result.slug).toBe('runchecklist');
184
+ });
185
+
186
+ it('should resolve relative parent paths', () => {
187
+ // The resolver should handle path normalization
188
+ const result = resolveImportTarget('../core/operation.busy.md', fileMap);
189
+ // This test may need adjustment based on actual implementation
190
+ // The key is that the basename should match
191
+ });
192
+
193
+ it('should return empty object for unresolved paths', () => {
194
+ const result = resolveImportTarget('./nonexistent.busy.md', fileMap);
195
+ expect(result).toEqual({});
196
+ });
197
+
198
+ it('should handle paths without ./ prefix', () => {
199
+ const result = resolveImportTarget('operation.busy.md', fileMap);
200
+ expect(result.docId).toBe('operation');
201
+ });
202
+ });
203
+
204
+ describe('Import Format Validation', () => {
205
+ it('should match busy-python regex pattern', () => {
206
+ // busy-python pattern: r"\[([^\]]+)\]:\s*([^\s#]+)(?:#([^\s]+))?"
207
+ const pattern = /\[([^\]]+)\]:\s*([^\s#]+)(?:#([^\s]+))?/g;
208
+
209
+ const testCases = [
210
+ { input: '[Concept]: ./concept.busy.md', expected: ['Concept', './concept.busy.md', undefined] },
211
+ { input: '[Run]: ./file.md#anchor', expected: ['Run', './file.md', 'anchor'] },
212
+ { input: '[Multi Word]: ../path/file.md', expected: ['Multi Word', '../path/file.md', undefined] },
213
+ { input: '[Test]:./no-space.md', expected: ['Test', './no-space.md', undefined] },
214
+ { input: '[Anchor]:./file.md#multi-word-anchor', expected: ['Anchor', './file.md', 'multi-word-anchor'] },
215
+ ];
216
+
217
+ for (const { input, expected } of testCases) {
218
+ const match = pattern.exec(input);
219
+ pattern.lastIndex = 0; // Reset for next iteration
220
+
221
+ expect(match).not.toBeNull();
222
+ if (match) {
223
+ expect(match[1]).toBe(expected[0]); // concept name
224
+ expect(match[2]).toBe(expected[1]); // path
225
+ expect(match[3]).toBe(expected[2]); // anchor (may be undefined)
226
+ }
227
+ }
228
+ });
229
+
230
+ it('should not match inline link format', () => {
231
+ const pattern = /^\[([^\]]+)\]:\s*([^\s#]+)(?:#([^\s]+))?$/m;
232
+
233
+ const inlineLinks = [
234
+ '[text](./file.md)',
235
+ '[text](./file.md#anchor)',
236
+ 'Check [this](./link.md) out',
237
+ ];
238
+
239
+ for (const link of inlineLinks) {
240
+ const match = pattern.exec(link);
241
+ expect(match).toBeNull();
242
+ }
243
+ });
244
+ });