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.
- package/README.md +129 -0
- package/dist/builders/context.d.ts +50 -0
- package/dist/builders/context.d.ts.map +1 -0
- package/dist/builders/context.js +190 -0
- package/dist/cache/index.d.ts +100 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +270 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +463 -0
- package/dist/commands/package.d.ts +96 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +285 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/loader.d.ts +6 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +361 -0
- package/dist/merge.d.ts +16 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +102 -0
- package/dist/package/manifest.d.ts +59 -0
- package/dist/package/manifest.d.ts.map +1 -0
- package/dist/package/manifest.js +265 -0
- package/dist/parser.d.ts +28 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +220 -0
- package/dist/parsers/frontmatter.d.ts +14 -0
- package/dist/parsers/frontmatter.d.ts.map +1 -0
- package/dist/parsers/frontmatter.js +110 -0
- package/dist/parsers/imports.d.ts +48 -0
- package/dist/parsers/imports.d.ts.map +1 -0
- package/dist/parsers/imports.js +147 -0
- package/dist/parsers/links.d.ts +12 -0
- package/dist/parsers/links.d.ts.map +1 -0
- package/dist/parsers/links.js +79 -0
- package/dist/parsers/localdefs.d.ts +6 -0
- package/dist/parsers/localdefs.d.ts.map +1 -0
- package/dist/parsers/localdefs.js +132 -0
- package/dist/parsers/operations.d.ts +32 -0
- package/dist/parsers/operations.d.ts.map +1 -0
- package/dist/parsers/operations.js +313 -0
- package/dist/parsers/sections.d.ts +15 -0
- package/dist/parsers/sections.d.ts.map +1 -0
- package/dist/parsers/sections.js +173 -0
- package/dist/parsers/tools.d.ts +30 -0
- package/dist/parsers/tools.d.ts.map +1 -0
- package/dist/parsers/tools.js +178 -0
- package/dist/parsers/triggers.d.ts +35 -0
- package/dist/parsers/triggers.d.ts.map +1 -0
- package/dist/parsers/triggers.js +219 -0
- package/dist/providers/base.d.ts +60 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +34 -0
- package/dist/providers/github.d.ts +18 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +109 -0
- package/dist/providers/gitlab.d.ts +18 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +101 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/local.d.ts +31 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +116 -0
- package/dist/providers/url.d.ts +16 -0
- package/dist/providers/url.d.ts.map +1 -0
- package/dist/providers/url.js +45 -0
- package/dist/registry/index.d.ts +99 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +320 -0
- package/dist/types/schema.d.ts +3259 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +258 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +23 -0
- package/dist/utils/slugify.d.ts +14 -0
- package/dist/utils/slugify.d.ts.map +1 -0
- package/dist/utils/slugify.js +28 -0
- package/package.json +61 -0
- package/src/__tests__/cache.test.ts +393 -0
- package/src/__tests__/cli-package.test.ts +667 -0
- package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
- package/src/__tests__/fixtures/concept.busy.md +30 -0
- package/src/__tests__/fixtures/document.busy.md +44 -0
- package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
- package/src/__tests__/fixtures/tool-document.busy.md +71 -0
- package/src/__tests__/fixtures/tool.busy.md +54 -0
- package/src/__tests__/imports.test.ts +244 -0
- package/src/__tests__/integration.test.ts +432 -0
- package/src/__tests__/operations.test.ts +408 -0
- package/src/__tests__/package-manifest.test.ts +455 -0
- package/src/__tests__/providers.test.ts +672 -0
- package/src/__tests__/registry.test.ts +402 -0
- package/src/__tests__/schema.test.ts +467 -0
- package/src/__tests__/tools.test.ts +376 -0
- package/src/__tests__/triggers.test.ts +312 -0
- package/src/builders/context.ts +294 -0
- package/src/cache/index.ts +312 -0
- package/src/cli/index.ts +514 -0
- package/src/commands/package.ts +392 -0
- package/src/index.ts +46 -0
- package/src/loader.ts +474 -0
- package/src/merge.ts +126 -0
- package/src/package/manifest.ts +349 -0
- package/src/parser.ts +278 -0
- package/src/parsers/frontmatter.ts +135 -0
- package/src/parsers/imports.ts +196 -0
- package/src/parsers/links.ts +108 -0
- package/src/parsers/localdefs.ts +166 -0
- package/src/parsers/operations.ts +404 -0
- package/src/parsers/sections.ts +230 -0
- package/src/parsers/tools.ts +215 -0
- package/src/parsers/triggers.ts +252 -0
- package/src/providers/base.ts +77 -0
- package/src/providers/github.ts +129 -0
- package/src/providers/gitlab.ts +121 -0
- package/src/providers/index.ts +25 -0
- package/src/providers/local.ts +129 -0
- package/src/providers/url.ts +56 -0
- package/src/registry/index.ts +408 -0
- package/src/types/schema.ts +369 -0
- package/src/utils/logger.ts +25 -0
- package/src/utils/slugify.ts +31 -0
- 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
|
+
});
|