Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 149 additions & 79 deletions .github/workflows/architecture-guardian.lock.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/cli/add_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o
opts.WorkflowDir = filepath.Clean(opts.WorkflowDir)
githubWorkflowsDir = filepath.Join(gitRoot, opts.WorkflowDir)
} else {
githubWorkflowsDir = filepath.Join(gitRoot, ".github/workflows")
githubWorkflowsDir = filepath.Join(gitRoot, constants.GetWorkflowDir())
}

// Ensure the target directory exists
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/compile_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path/filepath"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/workflow"
)
Expand Down Expand Up @@ -50,7 +51,7 @@ func CompileWorkflows(ctx context.Context, config CompileConfig) ([]*workflow.Wo
// Set up workflow directory (using default if not specified)
workflowDir := config.WorkflowDir
if workflowDir == "" {
workflowDir = ".github/workflows"
workflowDir = constants.GetWorkflowDir()
compileOrchestratorLog.Printf("Using default workflow directory: %s", workflowDir)
} else {
workflowDir = filepath.Clean(workflowDir)
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/compile_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"

"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/sliceutil"
"github.com/github/gh-aw/pkg/stringutil"
Expand Down Expand Up @@ -183,7 +184,7 @@ func validateCompileConfig(config CompileConfig) error {
compileValidationLog.Print("Config validation failed: dependabot flag with specific files")
return errors.New("--dependabot flag cannot be used with specific workflow files")
}
if config.WorkflowDir != "" && config.WorkflowDir != ".github/workflows" {
if config.WorkflowDir != "" && config.WorkflowDir != constants.GetWorkflowDir() {
compileValidationLog.Printf("Config validation failed: dependabot with custom dir: %s", config.WorkflowDir)
return errors.New("--dependabot flag cannot be used with custom --dir")
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/cli/compile_watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/fsnotify/fsnotify"
"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/gitutil"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/workflow"
Expand All @@ -29,9 +30,9 @@ func watchAndCompileWorkflows(markdownFile string, compiler *workflow.Compiler,
return fmt.Errorf("watch mode requires being in a git repository: %w", err)
}

workflowsDir := filepath.Join(gitRoot, ".github/workflows")
workflowsDir := filepath.Join(gitRoot, constants.GetWorkflowDir())
if _, err := os.Stat(workflowsDir); os.IsNotExist(err) {
return fmt.Errorf("the .github/workflows directory does not exist in git root (%s)", gitRoot)
return fmt.Errorf("the %s directory does not exist in git root (%s)", constants.GetWorkflowDir(), gitRoot)
}
Comment on lines +33 to 36

// If a specific file is provided, watch only that file and its directory
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st
// Typically used to disable all workflows except the one being trialled
func DisableAllWorkflowsExcept(repoSlug string, exceptWorkflows []string, verbose bool) error {
enableLog.Printf("Disabling all workflows except: count=%d, repo=%s", len(exceptWorkflows), repoSlug)
workflowsDir := ".github/workflows"
workflowsDir := constants.GetWorkflowDir()

// Check if workflows directory exists
if _, err := os.Stat(workflowsDir); os.IsNotExist(err) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/fix_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func runFixCommand(workflowIDs []string, write bool, verbose bool, workflowDir s

// Set up workflow directory (using default if not specified)
if workflowDir == "" {
workflowDir = ".github/workflows"
workflowDir = constants.GetWorkflowDir()
fixLog.Printf("Using default workflow directory: %s", workflowDir)
} else {
workflowDir = filepath.Clean(workflowDir)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/lint_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Examples:
},
}

cmd.Flags().StringP("dir", "d", ".github/workflows", "Directory to scan for *.lock.yml files when no arguments are provided")
cmd.Flags().StringP("dir", "d", constants.GetWorkflowDir(), "Directory to scan for *.lock.yml files when no arguments are provided")
cmd.Flags().Bool("shellcheck", false, "Enable shellcheck integration in actionlint")
cmd.Flags().Bool("pyflakes", false, "Enable pyflakes integration in actionlint")

Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/list_workflows_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ Examples:
addRepoFlag(cmd)
addJSONFlag(cmd)
cmd.Flags().String("label", "", "Filter workflows by label")
cmd.Flags().String("path", ".github/workflows", "Path to workflows directory in the remote repository (used with --repo)")
cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows; ignored when --repo is set)")
cmd.Flags().String("path", constants.GetWorkflowDir(), "Path to workflows directory in the remote repository (used with --repo)")
cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: $GH_AW_WORKFLOWS_DIR or .github/workflows; ignored when --repo is set)")

Comment on lines +74 to 76
// Register completions for list command
cmd.ValidArgsFunction = CompleteWorkflowNames
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/logs_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func getAgenticWorkflowNames(verbose bool) ([]string, error) {
var workflowNames []string

// Look for .lock.yml files in .github/workflows directory
workflowsDir := ".github/workflows"
workflowsDir := constants.GetWorkflowDir()
if _, err := os.Stat(workflowsDir); os.IsNotExist(err) {
if verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("No .github/workflows directory found"))
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/mcp_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/parser"
"github.com/github/gh-aw/pkg/sliceutil"
Expand All @@ -21,7 +22,7 @@ var mcpListLog = logger.New("cli:mcp_list")
func ListWorkflowMCP(workflowFile string, verbose bool) error {
mcpListLog.Printf("Listing MCP servers: workflow=%s, verbose=%t", workflowFile, verbose)
// Determine the workflow directory and file
workflowsDir := ".github/workflows"
workflowsDir := constants.GetWorkflowDir()
var workflowPath string

if workflowFile != "" {
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/mcp_safe_update_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"

"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/workflow"
)

Expand All @@ -18,7 +19,7 @@ import (
// so that the returned manifests cannot be tampered with by the agent.
func CollectLockFileManifests(workflowsDir string) map[string]*workflow.GHAWManifest {
if workflowsDir == "" {
workflowsDir = ".github/workflows"
workflowsDir = constants.GetWorkflowDir()
}

result := make(map[string]*workflow.GHAWManifest)
Expand Down
8 changes: 4 additions & 4 deletions pkg/cli/remove_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,10 @@ func cleanupOrphanedIncludes(verbose bool) error {
}
}

// Find all include files in .github/workflows
// Find all include files in the workflows directory
// Only consider files in subdirectories (like shared/) as potential include files
// Root-level .md files are workflow files, not include files
workflowsDir := ".github/workflows"
workflowsDir := constants.GetWorkflowDir()
var allIncludes []string

err = filepath.Walk(workflowsDir, func(path string, info os.FileInfo, err error) error {
Expand Down Expand Up @@ -328,7 +328,7 @@ func previewOrphanedIncludes(filesToRemove []string, verbose bool) ([]string, er

// getAllIncludeFiles returns all include files in .github/workflows subdirectories
func getAllIncludeFiles() ([]string, error) {
workflowsDir := ".github/workflows"
workflowsDir := constants.GetWorkflowDir()
var allIncludes []string

err := filepath.Walk(workflowsDir, func(path string, info os.FileInfo, err error) error {
Expand Down Expand Up @@ -357,7 +357,7 @@ func getAllIncludeFiles() ([]string, error) {

// cleanupAllIncludes removes all include files when no workflows remain
func cleanupAllIncludes(verbose bool) error {
workflowsDir := ".github/workflows"
workflowsDir := constants.GetWorkflowDir()

err := filepath.Walk(workflowsDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var resolverLog = logger.New("cli:resolver")
// - Workflow name or subpath (e.g., "a.md" -> ".github/workflows/a.md", "shared/b.md" -> ".github/workflows/shared/b.md")
func ResolveWorkflowPath(workflowFile string) (string, error) {
resolverLog.Printf("Resolving workflow path: %s", workflowFile)
workflowsDir := ".github/workflows"
workflowsDir := constants.GetWorkflowDir()

// Add .md extension if not present
searchPath := workflowFile
Expand Down
8 changes: 4 additions & 4 deletions pkg/cli/run_workflow_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,19 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO

_, _, err := readWorkflowFile(normalizedID+".md", workflowsDir)
if err != nil {
return fmt.Errorf("failed to find workflow in local .github/workflows: %w", err)
return fmt.Errorf("failed to find workflow in local %s: %w", workflowsDir, err)
}

// Check if the lock file exists in .github/workflows
lockFilePath = filepath.Join(".github/workflows", lockFileName)
// Check if the lock file exists in the workflows directory
lockFilePath = filepath.Join(constants.GetWorkflowDir(), lockFileName)
if _, err := os.Stat(lockFilePath); os.IsNotExist(err) {
executionLog.Printf("Lock file not found: %s (workflow must be compiled first)", lockFilePath)
suggestions := []string{
fmt.Sprintf("Run '%s compile' to compile all workflows", string(constants.CLIExtensionPrefix)),
fmt.Sprintf("Run '%s compile %s' to compile this specific workflow", string(constants.CLIExtensionPrefix), normalizedID),
}
errMsg := console.FormatErrorWithSuggestions(
fmt.Sprintf("workflow lock file '%s' not found in .github/workflows", lockFileName),
fmt.Sprintf("workflow lock file '%s' not found in %s", lockFileName, constants.GetWorkflowDir()),
suggestions,
)
return errors.New(errMsg)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/trial_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func executeTrialRun(ctx context.Context, parsedSpecs []*WorkflowSpec, hostRepoS
}

// Display workflow description if present
workflowPath := filepath.Join(tempDir, ".github/workflows", parsedSpec.WorkflowName+".md")
workflowPath := filepath.Join(tempDir, constants.GetWorkflowDir(), parsedSpec.WorkflowName+".md")
if description := ExtractWorkflowDescriptionFromFile(workflowPath); description != "" {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(description))
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/update_container_pins.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/workflow"
)
Expand Down Expand Up @@ -168,7 +169,7 @@ func UpdateContainerPins(ctx context.Context, workflowDir string, verbose bool)
// "download_docker_images.sh" invocations.
func collectImagesFromLockFiles(workflowDir string) ([]string, error) {
if workflowDir == "" {
workflowDir = ".github/workflows"
workflowDir = constants.GetWorkflowDir()
}

entries, err := os.ReadDir(workflowDir)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/upgrade_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func runUpgradeCommand(ctx context.Context, verbose bool, workflowDir string, no
// Determine workflow directory
workflowsDir := workflowDir
if workflowsDir == "" {
workflowsDir = ".github/workflows"
workflowsDir = constants.GetWorkflowDir()
}

// Compile all workflow files
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/workflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var workflowTitleScannerBufferPool = sync.Pool{
}

func getWorkflowsDir() string {
return ".github/workflows"
return constants.GetWorkflowDir()
}

// readWorkflowFile reads a workflow file from either filesystem
Expand Down
9 changes: 8 additions & 1 deletion pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package constants

import (
"io/fs"
"os"
"path/filepath"
"time"
)
Expand Down Expand Up @@ -331,8 +332,14 @@ var SharedWorkflowForbiddenFields = []string{
"tracker-id", // Tracker ID
}

// GetWorkflowDir returns the workflows directory path.
// Always uses forward slashes, which are required for git/GitHub paths.
// GH_AW_WORKFLOWS_DIR overrides the default; any OS-specific separators are normalized.
func GetWorkflowDir() string {
return filepath.Join(".github", "workflows")
if dir := os.Getenv("GH_AW_WORKFLOWS_DIR"); dir != "" {
return filepath.ToSlash(dir)
}
return ".github/workflows"
}
Comment on lines 338 to 343

// MaxSymlinkDepth limits recursive symlink resolution when fetching remote files.
Expand Down
17 changes: 17 additions & 0 deletions pkg/constants/constants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ func TestGetWorkflowDir(t *testing.T) {
}
}

func TestGetWorkflowDirEnvOverride(t *testing.T) {
t.Setenv("GH_AW_WORKFLOWS_DIR", "/tmp/custom-workflows")
result := GetWorkflowDir()
if result != "/tmp/custom-workflows" {
t.Errorf("GetWorkflowDir() with GH_AW_WORKFLOWS_DIR set = %q, want %q", result, "/tmp/custom-workflows")
}
}

func TestGetWorkflowDirEnvEmpty(t *testing.T) {
t.Setenv("GH_AW_WORKFLOWS_DIR", "")
expected := filepath.Join(".github", "workflows")
result := GetWorkflowDir()
if result != expected {
t.Errorf("GetWorkflowDir() with empty GH_AW_WORKFLOWS_DIR = %q, want %q", result, expected)
}
}

func TestDefaultAllowedDomains(t *testing.T) {
if len(DefaultAllowedDomains) == 0 {
t.Error("DefaultAllowedDomains should not be empty")
Expand Down
3 changes: 2 additions & 1 deletion pkg/parser/import_bfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"

"github.com/goccy/go-yaml"
"github.com/github/gh-aw/pkg/constants"
)

// processImportsFromFrontmatterWithManifestAndSource is the internal implementation that includes source tracking.
Expand Down Expand Up @@ -399,7 +400,7 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a
// Use the parent's BasePath if available, otherwise default to .github/workflows
basePath := item.remoteOrigin.BasePath
if basePath == "" {
basePath = ".github/workflows"
basePath = constants.GetWorkflowDir()
}
// Clean the basePath to ensure it's normalized
basePath = path.Clean(basePath)
Expand Down
Loading