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
15 changes: 14 additions & 1 deletion pkg/cli/dependency_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,24 @@ func (g *DependencyGraph) addWorkflow(workflowPath string, compiler *workflow.Co

// extractImportsFromFile extracts imports directly from a workflow file
func (g *DependencyGraph) extractImportsFromFile(workflowPath string) ([]string, error) {
depGraphLog.Printf("Extracting imports from file: %s", workflowPath)
// Read the file
content, err := os.ReadFile(workflowPath)
if err != nil {
depGraphLog.Printf("Failed to read file %s: %v", workflowPath, err)
return nil, err
}

// Parse frontmatter
result, err := parser.ExtractFrontmatterFromContent(string(content))
if err != nil {
depGraphLog.Printf("Failed to parse frontmatter from %s: %v", workflowPath, err)
return nil, err
}

return g.extractImportsFromFrontmatter(workflowPath, result.Frontmatter), nil
imports := g.extractImportsFromFrontmatter(workflowPath, result.Frontmatter)
depGraphLog.Printf("Extracted %d imports from %s", len(imports), workflowPath)
return imports, nil
}

// extractImportsFromFrontmatter extracts the list of imported file paths from frontmatter
Expand All @@ -154,14 +159,18 @@ func (g *DependencyGraph) extractImportsFromFrontmatter(workflowPath string, fro

// Get frontmatter to extract imports
if frontmatter == nil {
depGraphLog.Printf("No frontmatter found in %s", workflowPath)
return imports
}

importsField, exists := frontmatter["imports"]
if !exists {
depGraphLog.Printf("No imports field in frontmatter for %s", workflowPath)
return imports
}

depGraphLog.Printf("Processing imports field from %s", workflowPath)

// Parse imports field - can be array of strings or objects with path
workflowDir := filepath.Dir(workflowPath)
switch v := importsField.(type) {
Expand Down Expand Up @@ -227,20 +236,24 @@ func (g *DependencyGraph) resolveImportPath(importPath string, baseDir string) s

// findGitRoot finds the git repository root
func (g *DependencyGraph) findGitRoot() string {
depGraphLog.Printf("Finding git root starting from: %s", g.workflowsDir)
// Start from workflows directory and walk up
dir := g.workflowsDir
for {
gitDir := filepath.Join(dir, ".git")
if _, err := os.Stat(gitDir); err == nil {
depGraphLog.Printf("Found git root at: %s", dir)
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
// Reached filesystem root
depGraphLog.Printf("Reached filesystem root, no .git directory found")
break
}
dir = parent
}
depGraphLog.Printf("Using fallback git root: %s", g.workflowsDir)
return g.workflowsDir // Fallback to workflows dir
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/cli/enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ var enableLog = logger.New("cli:enable")

// EnableWorkflowsByNames enables workflows by specific names, or all if no names provided
func EnableWorkflowsByNames(workflowNames []string, repoOverride string) error {
enableLog.Printf("EnableWorkflowsByNames called: workflow_count=%d, repo=%s", len(workflowNames), repoOverride)
return toggleWorkflowsByNames(workflowNames, true, repoOverride)
}

// DisableWorkflowsByNames disables workflows by specific names, or all if no names provided
func DisableWorkflowsByNames(workflowNames []string, repoOverride string) error {
enableLog.Printf("DisableWorkflowsByNames called: workflow_count=%d, repo=%s", len(workflowNames), repoOverride)
return toggleWorkflowsByNames(workflowNames, false, repoOverride)
}

Expand Down Expand Up @@ -94,11 +96,14 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st
}

// Get GitHub workflows status for comparison; warn but continue if unavailable
enableLog.Print("Fetching GitHub workflows status for comparison")
githubWorkflows, err := fetchGitHubWorkflows("", false)
if err != nil {
enableLog.Printf("Failed to fetch GitHub workflows: %v", err)
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Unable to fetch GitHub workflows (gh CLI may not be authenticated): %v", err)))
githubWorkflows = make(map[string]*GitHubWorkflow)
}
enableLog.Printf("Retrieved %d GitHub workflows from remote", len(githubWorkflows))

// Internal target model to support enabling by ID or lock filename
type workflowTarget struct {
Expand Down Expand Up @@ -178,6 +183,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st

// Report any workflows that weren't found
if len(notFoundNames) > 0 {
enableLog.Printf("Workflows not found: %v", notFoundNames)
suggestions := []string{
fmt.Sprintf("Run '%s status' to see all available workflows", string(constants.CLIExtensionPrefix)),
"Check for typos in the workflow names",
Expand All @@ -200,10 +206,12 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st

// If no targets after filtering, everything was already in the desired state
if len(targets) == 0 {
enableLog.Printf("No workflows need to be %sd - all already in desired state", action)
fmt.Fprintf(os.Stderr, "All specified workflows are already %sd\n", action)
return nil
}

enableLog.Printf("Proceeding to %s %d workflows", action, len(targets))
// Show what will be changed
fmt.Fprintf(os.Stderr, "The following workflows will be %sd:\n", action)
for _, t := range targets {
Expand Down
20 changes: 20 additions & 0 deletions pkg/cli/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,38 @@ func parseRepoSpec(repoSpec string) (*RepoSpec, error) {
var version string
if len(parts) == 2 {
version = parts[1]
specLog.Printf("Version specified: %s", version)
}

// Check if this is a GitHub URL
if strings.HasPrefix(repo, "https://github.com/") || strings.HasPrefix(repo, "http://github.com/") {
specLog.Print("Detected GitHub URL format")
// Parse GitHub URL: https://github.com/owner/repo
repoURL, err := url.Parse(repo)
if err != nil {
specLog.Printf("Failed to parse GitHub URL: %v", err)
return nil, fmt.Errorf("invalid GitHub URL: %w", err)
}

// Extract owner/repo from path
pathParts := strings.Split(strings.Trim(repoURL.Path, "/"), "/")
if len(pathParts) != 2 || pathParts[0] == "" || pathParts[1] == "" {
specLog.Printf("Invalid GitHub URL path parts: %v", pathParts)
return nil, fmt.Errorf("invalid GitHub URL: must be https://github.com/owner/repo. Example: https://github.com/githubnext/gh-aw")
}

repo = fmt.Sprintf("%s/%s", pathParts[0], pathParts[1])
specLog.Printf("Extracted repo from URL: %s", repo)
} else if repo == "." {
specLog.Print("Resolving current directory as repo")
// Handle current directory as repo (local workflow)
currentRepo, err := GetCurrentRepoSlug()
if err != nil {
specLog.Printf("Failed to get current repo: %v", err)
return nil, fmt.Errorf("failed to get current repository info: %w", err)
}
repo = currentRepo
specLog.Printf("Resolved current repo: %s", repo)
} else {
// Validate repository format (org/repo)
repoParts := strings.Split(repo, "/")
Expand All @@ -118,6 +126,7 @@ func parseRepoSpec(repoSpec string) (*RepoSpec, error) {
Version: version,
}

specLog.Printf("Parsed repo spec successfully: repo=%s, version=%s", repo, version)
return spec, nil
}

Expand All @@ -131,22 +140,28 @@ func parseRepoSpec(repoSpec string) (*RepoSpec, error) {
// - https://raw.githubusercontent.com/owner/repo/COMMIT_SHA/path/to/workflow.md
// - https://raw.githubusercontent.com/owner/repo/refs/tags/tag/path/to/workflow.md
func parseGitHubURL(spec string) (*WorkflowSpec, error) {
specLog.Printf("Parsing GitHub URL: %s", spec)
// First validate that this is a GitHub URL (github.com or raw.githubusercontent.com)
parsedURL, err := url.Parse(spec)
if err != nil {
specLog.Printf("Failed to parse URL: %v", err)
return nil, fmt.Errorf("invalid URL: %w", err)
}

// Must be a GitHub URL
if parsedURL.Host != "github.com" && parsedURL.Host != "raw.githubusercontent.com" {
specLog.Printf("Invalid host: %s", parsedURL.Host)
return nil, fmt.Errorf("URL must be from github.com or raw.githubusercontent.com")
}

owner, repo, ref, filePath, err := parser.ParseRepoFileURL(spec)
if err != nil {
specLog.Printf("Failed to parse repo file URL: %v", err)
return nil, err
}

specLog.Printf("Parsed GitHub URL: owner=%s, repo=%s, ref=%s, path=%s", owner, repo, ref, filePath)

// Ensure the file path ends with .md
if !strings.HasSuffix(filePath, ".md") {
return nil, fmt.Errorf("GitHub URL must point to a .md file")
Expand Down Expand Up @@ -271,17 +286,22 @@ func parseWorkflowSpec(spec string) (*WorkflowSpec, error) {

// parseLocalWorkflowSpec parses a local workflow specification starting with "./"
func parseLocalWorkflowSpec(spec string) (*WorkflowSpec, error) {
specLog.Printf("Parsing local workflow spec: %s", spec)
// Validate that it's a .md file
if !strings.HasSuffix(spec, ".md") {
specLog.Printf("Invalid extension for local workflow: %s", spec)
return nil, fmt.Errorf("local workflow specification must end with '.md' extension: %s", spec)
}

// Get current repository info
repoInfo, err := GetCurrentRepoSlug()
if err != nil {
specLog.Printf("Failed to get current repo slug: %v", err)
return nil, fmt.Errorf("failed to get current repository info for local workflow: %w", err)
}

specLog.Printf("Parsed local workflow: repo=%s, path=%s", repoInfo, spec)

return &WorkflowSpec{
RepoSpec: RepoSpec{
RepoSlug: repoInfo,
Expand Down
4 changes: 4 additions & 0 deletions pkg/workflow/claude_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var claudeToolsLog = logger.New("workflow:claude_tools")

// expandNeutralToolsToClaudeTools converts neutral tool names to Claude-specific tool configurations
func (e *ClaudeEngine) expandNeutralToolsToClaudeTools(tools map[string]any) map[string]any {
claudeToolsLog.Printf("Starting neutral tools expansion: input_tools=%d", len(tools))
result := make(map[string]any)

neutralToolCount := 0
Expand Down Expand Up @@ -110,6 +111,7 @@ func (e *ClaudeEngine) expandNeutralToolsToClaudeTools(tools map[string]any) map
claudeSection["allowed"] = claudeAllowed
result["claude"] = claudeSection

claudeToolsLog.Printf("Expansion complete: result_tools=%d, claude_allowed=%d", len(result), len(claudeAllowed))
return result
}

Expand All @@ -136,10 +138,12 @@ func (e *ClaudeEngine) computeAllowedClaudeToolsString(tools map[string]any, saf

// Enforce that only neutral tools are provided - fail if claude section is present
if _, hasClaudeSection := tools["claude"]; hasClaudeSection {
claudeToolsLog.Print("ERROR: Claude section found in input tools, should only contain neutral tools")
panic("computeAllowedClaudeToolsString should only receive neutral tools, not claude section tools")
}

// Convert neutral tools to Claude-specific tools
claudeToolsLog.Print("Converting neutral tools to Claude-specific format")
tools = e.expandNeutralToolsToClaudeTools(tools)

defaultClaudeTools := []string{
Expand Down
10 changes: 10 additions & 0 deletions pkg/workflow/script_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type ScriptRegistry struct {

// NewScriptRegistry creates a new empty script registry.
func NewScriptRegistry() *ScriptRegistry {
registryLog.Print("Creating new script registry")
return &ScriptRegistry{
scripts: make(map[string]*scriptEntry),
}
Expand Down Expand Up @@ -198,8 +199,14 @@ func (r *ScriptRegistry) GetActionPath(name string) string {

entry, exists := r.scripts[name]
if !exists {
if registryLog.Enabled() {
registryLog.Printf("GetActionPath: script not found: %s", name)
}
return ""
}
if registryLog.Enabled() && entry.actionPath != "" {
registryLog.Printf("GetActionPath: returning action path for %s: %s", name, entry.actionPath)
}
return entry.actionPath
}

Expand Down Expand Up @@ -353,6 +360,7 @@ func GetScriptWithMode(name string, mode RuntimeMode) string {
// This is used by the build system to discover which files need to be embedded in custom actions.
// The returned list includes all .cjs files found in pkg/workflow/js/, including dependencies.
func GetAllScriptFilenames() []string {
registryLog.Print("Getting all script filenames from JavaScript sources")
sources := GetJavaScriptSources()
filenames := make([]string, 0, len(sources))

Expand All @@ -363,6 +371,8 @@ func GetAllScriptFilenames() []string {
}
}

registryLog.Printf("Found %d .cjs files in JavaScript sources", len(filenames))

// Sort for consistency
sortedFilenames := make([]string, len(filenames))
copy(sortedFilenames, filenames)
Expand Down