diff --git a/docs/src/content/docs/reference/include-directives.md b/docs/src/content/docs/reference/include-directives.md index 003cc954874..bcf17c13a86 100644 --- a/docs/src/content/docs/reference/include-directives.md +++ b/docs/src/content/docs/reference/include-directives.md @@ -1,40 +1,44 @@ --- title: Include Directives -description: Learn how to modularize and reuse workflow components across multiple workflows using include directives for better organization and maintainability. +description: Learn how to modularize and reuse workflow components across multiple workflows using import directives for better organization and maintainability. sidebar: order: 4 --- -Include directives allow you to modularize and reuse workflow components across multiple workflows. +Import directives allow you to modularize and reuse workflow components across multiple workflows. -## Basic Include Syntax +## Basic Import Syntax ```aw wrap -@include relative/path/to/file.md +@import relative/path/to/file.md ``` -Includes files relative to the current markdown file's location. +Imports files relative to the current markdown file's location. -## Optional Include Syntax +:::note +`@import` and `@include` are aliases - you can use either keyword interchangeably. +::: + +## Optional Import Syntax ```aw wrap -@include? relative/path/to/file.md +@import? relative/path/to/file.md ``` -Includes files optionally - if the file doesn't exist, no error occurs and a friendly informational comment is added to the workflow. The optional file will be watched for changes in `gh aw compile --watch` mode, so creating the file later will automatically include it. +Imports files optionally - if the file doesn't exist, no error occurs and a friendly informational comment is added to the workflow. The optional file will be watched for changes in `gh aw compile --watch` mode, so creating the file later will automatically import it. -## Section-Specific Includes +## Section-Specific Imports ```aw wrap -@include filename.md#Section +@import filename.md#Section ``` -Includes only a specific section from a markdown file using the section header. +Imports only a specific section from a markdown file using the section header. ## Frontmatter Merging -- **Only `tools:` frontmatter** is allowed in included files, other entries give a warning. -- **Tool merging**: `allowed:` tools are merged across all included files +- **Only `tools:` frontmatter** is allowed in imported files, other entries give a warning. +- **Tool merging**: `allowed:` tools are merged across all imported files ### Example Tool Merging ```aw wrap @@ -45,7 +49,7 @@ tools: allowed: [get_issue] --- -@include shared/extra-tools.md # Adds more GitHub tools +@import shared/extra-tools.md # Adds more GitHub tools ``` ```aw wrap @@ -60,11 +64,11 @@ tools: **Result**: Final workflow has `github.allowed: [get_issue, add_issue_comment, update_issue]` and Claude Edit tool. -## Include Path Resolution +## Import Path Resolution -- **Relative paths**: Resolved relative to the including file -- **Nested includes**: Included files can include other files -- **Circular protection**: System prevents infinite include loops +- **Relative paths**: Resolved relative to the importing file +- **Nested imports**: Imported files can import other files +- **Circular protection**: System prevents infinite import loops ## Related Documentation diff --git a/docs/src/content/docs/reference/safe-jobs.md b/docs/src/content/docs/reference/safe-jobs.md index 1b31c5fbac4..1cc85225831 100644 --- a/docs/src/content/docs/reference/safe-jobs.md +++ b/docs/src/content/docs/reference/safe-jobs.md @@ -185,10 +185,10 @@ safe-outputs: run: echo "Deploying..." --- -@include shared/common-jobs.md +@import shared/common-jobs.md ``` -**Included file (shared/common-jobs.md):** +**Imported file (shared/common-jobs.md):** ```aw wrap --- safe-outputs: diff --git a/pkg/cli/remove_command.go b/pkg/cli/remove_command.go index 6a4930416fb..c81ee057659 100644 --- a/pkg/cli/remove_command.go +++ b/pkg/cli/remove_command.go @@ -367,12 +367,12 @@ func cleanupAllIncludes(verbose bool) error { return err } -// findIncludesInContent finds all @include references in content +// findIncludesInContent finds all @include and @import references in content func findIncludesInContent(content, baseDir string, verbose bool) ([]string, error) { _ = baseDir // unused parameter for now, keeping for potential future use _ = verbose // unused parameter for now, keeping for potential future use var includes []string - includePattern := regexp.MustCompile(`^@include(\?)?\s+(.+)$`) + includePattern := regexp.MustCompile(`^@(?:include|import)(\?)?\s+(.+)$`) scanner := bufio.NewScanner(strings.NewReader(content)) for scanner.Scan() { diff --git a/pkg/parser/frontmatter.go b/pkg/parser/frontmatter.go index c0277c6bd39..7517a3161b1 100644 --- a/pkg/parser/frontmatter.go +++ b/pkg/parser/frontmatter.go @@ -284,17 +284,17 @@ func ExtractMarkdown(filePath string) (string, error) { return ExtractMarkdownContent(string(content)) } -// ProcessIncludes processes @include directives in markdown content +// ProcessIncludes processes @include and @import directives in markdown content // This matches the bash process_includes function behavior func ProcessIncludes(content, baseDir string, extractTools bool) (string, error) { scanner := bufio.NewScanner(strings.NewReader(content)) var result bytes.Buffer - includePattern := regexp.MustCompile(`^@include(\?)?\s+(.+)$`) + includePattern := regexp.MustCompile(`^@(?:include|import)(\?)?\s+(.+)$`) for scanner.Scan() { line := scanner.Text() - // Check if this line is an @include directive + // Check if this line is an @include or @import directive if matches := includePattern.FindStringSubmatch(line); matches != nil { isOptional := matches[1] == "?" includePath := strings.TrimSpace(matches[2]) @@ -505,7 +505,7 @@ func extractEngineFromContent(content string) (string, error) { return strings.TrimSpace(string(engineJSON)), nil } -// ExpandIncludes recursively expands @include directives until no more remain +// ExpandIncludes recursively expands @include and @import directives until no more remain // This matches the bash expand_includes function behavior func ExpandIncludes(content, baseDir string, extractTools bool) (string, error) { const maxDepth = 10 @@ -518,9 +518,9 @@ func ExpandIncludes(content, baseDir string, extractTools bool) (string, error) return "", err } - // For tools mode, check if we still have @include directives + // For tools mode, check if we still have @include or @import directives if extractTools { - if !strings.Contains(processedContent, "@include") { + if !strings.Contains(processedContent, "@include") && !strings.Contains(processedContent, "@import") { // No more includes to process for tools mode currentContent = processedContent break @@ -544,7 +544,7 @@ func ExpandIncludes(content, baseDir string, extractTools bool) (string, error) return currentContent, nil } -// ExpandIncludesForEngines recursively expands @include directives to extract engine configurations +// ExpandIncludesForEngines recursively expands @include and @import directives to extract engine configurations func ExpandIncludesForEngines(content, baseDir string) ([]string, error) { const maxDepth = 10 var engines []string @@ -572,17 +572,17 @@ func ExpandIncludesForEngines(content, baseDir string) ([]string, error) { return engines, nil } -// ProcessIncludesForEngines processes @include directives to extract engine configurations +// ProcessIncludesForEngines processes @include and @import directives to extract engine configurations func ProcessIncludesForEngines(content, baseDir string) ([]string, string, error) { scanner := bufio.NewScanner(strings.NewReader(content)) var result bytes.Buffer var engines []string - includePattern := regexp.MustCompile(`^@include(\?)?\s+(.+)$`) + includePattern := regexp.MustCompile(`^@(?:include|import)(\?)?\s+(.+)$`) for scanner.Scan() { line := scanner.Text() - // Check if this line is an @include directive + // Check if this line is an @include or @import directive if matches := includePattern.FindStringSubmatch(line); matches != nil { isOptional := matches[1] == "?" includePath := strings.TrimSpace(matches[2]) diff --git a/pkg/parser/frontmatter_test.go b/pkg/parser/frontmatter_test.go index a44251995a1..2f0f3c88df9 100644 --- a/pkg/parser/frontmatter_test.go +++ b/pkg/parser/frontmatter_test.go @@ -497,6 +497,34 @@ Some content here. extractTools: false, expected: "# Content with Extra Newlines\nSome content here.\n# After include\n", }, + { + name: "simple import (alias for include)", + content: "@import test.md\n# After import", + baseDir: tempDir, + extractTools: false, + expected: "# Test Content\nThis is a test file content.\n# After import\n", + }, + { + name: "extract tools with import", + content: "@import test.md", + baseDir: tempDir, + extractTools: true, + expected: `{"bash":{"allowed":["ls","cat"]}}` + "\n", + }, + { + name: "import file not found", + content: "@import nonexistent.md", + baseDir: tempDir, + extractTools: false, + wantErr: true, + }, + { + name: "optional import missing file", + content: "@import? missing.md\n", + baseDir: tempDir, + extractTools: false, + expected: "", + }, } // Create test file with invalid frontmatter for testing validation @@ -1059,6 +1087,20 @@ This is test content. extractTools: true, wantContains: `"bash"`, }, + { + name: "expand markdown content with import", + content: "# Start\n@import test.md\n# End", + baseDir: tempDir, + extractTools: false, + wantContains: "# Test Content\nThis is test content.", + }, + { + name: "expand tools with import", + content: "@import test.md", + baseDir: tempDir, + extractTools: true, + wantContains: `"bash"`, + }, } for _, tt := range tests {