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
10 changes: 5 additions & 5 deletions pkg/cli/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,14 +313,14 @@ func getRepositorySlugFromRemoteForPath(path string) string {
func stageWorkflowChanges() {
// Find git root and add .github/workflows relative to it
if gitRoot, err := gitutil.FindGitRoot(); err == nil {
workflowsPath := filepath.Join(gitRoot, ".github/workflows/")
workflowsPath := filepath.Join(gitRoot, constants.WorkflowsDirSlash)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice — extracting .github/workflows/ into constants.WorkflowsDirSlash improves consistency.

_ = exec.Command("git", "-C", gitRoot, "add", workflowsPath).Run()

// Also stage .gitattributes if it was modified
_ = stageGitAttributesIfChanged()
} else {
// Fallback to relative path if git root can't be found
_ = exec.Command("git", "add", ".github/workflows/").Run()
_ = exec.Command("git", "add", constants.WorkflowsDirSlash).Run()
_ = exec.Command("git", "add", ".gitattributes").Run()
}
}
Expand All @@ -335,7 +335,7 @@ func ensureGitAttributes() (bool, error) {
}

gitAttributesPath := filepath.Join(gitRoot, ".gitattributes")
lockYmlEntry := ".github/workflows/*.lock.yml linguist-generated=true merge=ours"
lockYmlEntry := constants.WorkflowsLockYmlGitAttributesEntry

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of constants.WorkflowsLockYmlGitAttributesEntry for the gitattributes entry.

requiredEntries := []string{lockYmlEntry}

// Read existing .gitattributes file if it exists
Expand All @@ -357,7 +357,7 @@ func ensureGitAttributes() (bool, error) {
break
}
// Check for old format entries that need updating
if strings.HasPrefix(trimmedLine, ".github/workflows/*.lock.yml") && required == lockYmlEntry {
if strings.HasPrefix(trimmedLine, constants.WorkflowsLockYmlGlob) && required == lockYmlEntry {
gitLog.Print("Updating old .gitattributes entry format")
lines[i] = lockYmlEntry
found = true
Expand Down Expand Up @@ -414,7 +414,7 @@ func ensureLogsGitignore() error {

// Check if .gitignore already exists
if _, err := os.Stat(gitignorePath); err == nil {
gitLog.Print(".github/aw/logs/.gitignore already exists")
gitLog.Print(".github/aw/logs/.gitignore already exists") //nolint:hardcodedfilepath
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/includes.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func FetchIncludeFromSource(includePath string, baseSpec *WorkflowSpec, verbose
// If it's a relative path starting with shared/, it's relative to .github/
var fullPath string
if strings.HasPrefix(filePath, "shared/") {
fullPath = ".github/" + filePath
fullPath = constants.GithubDir + filePath
} else {
// Otherwise, resolve relative to the workflow path directory
baseDir := getParentDir(baseSpec.WorkflowPath)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/logs_run_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ func inferWorkflowPathFromDisplayName(displayName string) string {
if slug == "" {
return ""
}
return ".github/workflows/" + slug + ".lock.yml"
return constants.WorkflowsDirSlash + slug + ".lock.yml"
}

// runHasDifcFilteredItems checks if a run's gateway logs contain any DIFC_FILTERED events.
Expand Down
7 changes: 4 additions & 3 deletions pkg/cli/mcp_tools_privileged.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strconv"
"strings"

"github.com/github/gh-aw/pkg/constants"
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
Expand Down Expand Up @@ -133,7 +134,7 @@ from where the previous request stopped due to timeout.`,

// Build command arguments
// Force output directory to /tmp/gh-aw/aw-mcp/logs for MCP server
cmdArgs := []string{"logs", "-o", "/tmp/gh-aw/aw-mcp/logs"}
cmdArgs := []string{"logs", "-o", constants.TmpAwMcpLogsDir}
if args.WorkflowName != "" {
cmdArgs = append(cmdArgs, args.WorkflowName)
}
Expand Down Expand Up @@ -384,7 +385,7 @@ Multi-run diff returns JSON describing changes between the base and each compari
// Pass all run IDs/URLs directly - the audit command handles single vs. diff mode.
cmdArgs := []string{"audit"}
cmdArgs = append(cmdArgs, runItems...)
cmdArgs = append(cmdArgs, "-o", "/tmp/gh-aw/aw-mcp/logs", "--json")
cmdArgs = append(cmdArgs, "-o", constants.TmpAwMcpLogsDir, "--json")
if len(args.Artifacts) > 0 {
cmdArgs = append(cmdArgs, "--artifacts", strings.Join(args.Artifacts, ","))
}
Expand Down Expand Up @@ -525,7 +526,7 @@ Returns JSON describing the differences between the base run and each comparison
// Build: gh aw audit diff <base> <compare...> -o ... --json [--artifacts ...]
cmdArgs := []string{"audit", "diff", args.BaseRunID}
cmdArgs = append(cmdArgs, args.CompareRunIDs...)
cmdArgs = append(cmdArgs, "-o", "/tmp/gh-aw/aw-mcp/logs", "--json")
cmdArgs = append(cmdArgs, "-o", constants.TmpAwMcpLogsDir, "--json")
if len(args.Artifacts) > 0 {
cmdArgs = append(cmdArgs, "--artifacts", strings.Join(args.Artifacts, ","))
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/cli/shell_completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func installBashCompletion(verbose bool, cmd *cobra.Command) error {
brewPrefix := os.Getenv("HOMEBREW_PREFIX")
if brewPrefix == "" {
// Try common locations
for _, prefix := range []string{"/opt/homebrew", "/usr/local"} {
for _, prefix := range []string{constants.HomebrewPrefix, constants.UsrLocalPrefix} {
if _, err := os.Stat(filepath.Join(prefix, "etc", "bash_completion.d")); err == nil {
brewPrefix = prefix
break
Expand All @@ -159,8 +159,8 @@ func installBashCompletion(verbose bool, cmd *cobra.Command) error {
}
} else {
// Linux
if _, err := os.Stat("/etc/bash_completion.d"); err == nil {
completionPath = "/etc/bash_completion.d/gh-aw"
if _, err := os.Stat(constants.BashCompletionDir); err == nil {
completionPath = constants.BashCompletionGhAwPath
} else {
completionPath = filepath.Join(homeDir, ".bash_completion.d", "gh-aw")
}
Expand Down Expand Up @@ -426,7 +426,7 @@ func uninstallBashCompletion(verbose bool) error {
if runtime.GOOS == "darwin" {
brewPrefix := os.Getenv("HOMEBREW_PREFIX")
if brewPrefix == "" {
for _, prefix := range []string{"/opt/homebrew", "/usr/local"} {
for _, prefix := range []string{constants.HomebrewPrefix, constants.UsrLocalPrefix} {
if _, err := os.Stat(filepath.Join(prefix, "etc", "bash_completion.d")); err == nil {
possiblePaths = append(possiblePaths, filepath.Join(prefix, "etc", "bash_completion.d", "gh-aw"))
}
Expand All @@ -438,7 +438,7 @@ func uninstallBashCompletion(verbose bool) error {

// System-wide installations (Linux)
if runtime.GOOS == "linux" {
possiblePaths = append(possiblePaths, "/etc/bash_completion.d/gh-aw")
possiblePaths = append(possiblePaths, constants.BashCompletionGhAwPath)
}

removed := false
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/trial_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func installWorkflowInTrialMode(ctx context.Context, tempDir string, parsedSpec

// Compile the workflow with trial modifications
config := CompileConfig{
MarkdownFiles: []string{".github/workflows/" + parsedSpec.WorkflowName + ".md"},
MarkdownFiles: []string{constants.WorkflowsDirSlash + parsedSpec.WorkflowName + ".md"},
Verbose: opts.Verbose,
EngineOverride: opts.EngineOverride,
Validate: true,
Expand Down
165 changes: 164 additions & 1 deletion pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,177 @@ var SharedWorkflowForbiddenFields = []string{
"tracker-id", // Tracker ID
}

// Repository directory path constants
//
// These constants define the conventional repository-relative directory paths
// used by gh-aw for GitHub Actions workflows, agents, and related configuration.

// GithubDir is the root .github directory prefix (with trailing slash).
// Use this for path prefix comparisons against workspace-relative paths.
const GithubDir = ".github/"

// WorkflowsDir is the GitHub Actions workflow directory path (without trailing slash).
// This is the canonical location for workflow markdown and compiled lock YAML files.
const WorkflowsDir = ".github/workflows"

// WorkflowsDirSlash is WorkflowsDir with a trailing slash.
// Use this for path prefix matching (e.g. strings.HasPrefix or strings.Contains).
const WorkflowsDirSlash = WorkflowsDir + "/"

// AgentsDir is the custom GitHub Copilot agent definitions directory (with trailing slash).
const AgentsDir = ".github/agents/"

// WorkflowsLockYmlGlob is the glob pattern for compiled workflow lock YAML files.
const WorkflowsLockYmlGlob = WorkflowsDirSlash + "*.lock.yml"

// WorkflowsLockYmlGitAttributesEntry is the .gitattributes entry that marks lock YAML
// files as generated and sets the merge strategy.
const WorkflowsLockYmlGitAttributesEntry = WorkflowsLockYmlGlob + " linguist-generated=true merge=ours"

// Temporary runtime directory constants (/tmp/gh-aw tree)
//
// These constants define the /tmp/gh-aw directory layout used by the agent
// and engine harnesses during workflow execution. Paths here are always
// in the /tmp/gh-aw tree regardless of whether the runner uses RUNNER_TEMP.
// See also GhAwRootDir / GhAwRootDirShell for the host-side RUNNER_TEMP paths.

// TmpGhAwDir is the root /tmp/gh-aw directory (without trailing slash).
const TmpGhAwDir = "/tmp/gh-aw"

// TmpGhAwDirSlash is TmpGhAwDir with a trailing slash.
// Use for path prefix comparisons (e.g. strings.HasPrefix).
const TmpGhAwDirSlash = TmpGhAwDir + "/"

// TmpGhAwAgentDir is the agent working directory in the /tmp/gh-aw tree.
const TmpGhAwAgentDir = TmpGhAwDir + "/agent/"

// AgentStdioLogPath is the path for capturing agent standard I/O log output.
const AgentStdioLogPath = TmpGhAwDir + "/agent-stdio.log"

// AwPromptsFile is the runtime prompt file path populated by the setup action.
// Engine harnesses read this file to pass the compiled prompt to the AI engine.
const AwPromptsFile = TmpGhAwDir + "/aw-prompts/prompt.txt"

// TmpMcpConfigDir is the mcp-config directory in the /tmp/gh-aw tree.
// Engines that require a writable MCP config directory (e.g. Codex) use this path.
const TmpMcpConfigDir = TmpGhAwDir + "/mcp-config"

// TmpMcpServersJsonPath is the MCP servers JSON config file in the /tmp tree.
// Used by engines that resolve the config through the writable /tmp path.
const TmpMcpServersJsonPath = TmpMcpConfigDir + "/mcp-servers.json"

// TmpMcpConfigLogsDir is the MCP config server log directory.
const TmpMcpConfigLogsDir = TmpMcpConfigDir + "/logs/"

// TmpMcpLogsDir is the MCP server logs root directory (with trailing slash).
const TmpMcpLogsDir = TmpGhAwDir + "/mcp-logs/"

// TmpMcpLogsSafeOutputsDir is the safe-outputs MCP server log directory.
const TmpMcpLogsSafeOutputsDir = TmpGhAwDir + "/mcp-logs/safeoutputs"

// TmpMcpLogsPlaywrightDir is the Playwright MCP server log directory.
const TmpMcpLogsPlaywrightDir = TmpGhAwDir + "/mcp-logs/playwright"

// TmpMcpLogsMount is the Docker volume mount spec for the MCP logs directory.
const TmpMcpLogsMount = TmpGhAwDir + "/mcp-logs:" + TmpGhAwDir + "/mcp-logs"

// TmpMcpScriptsLogsDir is the mcp-scripts server log directory (with trailing slash).
const TmpMcpScriptsLogsDir = TmpGhAwDir + "/mcp-scripts/logs/"

// TmpRepoMemoryDir is the repo-memory data directory (with trailing slash).
const TmpRepoMemoryDir = TmpGhAwDir + "/repo-memory/"

// TmpCommentMemoryDir is the comment-memory data directory (with trailing slash).
const TmpCommentMemoryDir = TmpGhAwDir + "/comment-memory/"

// TmpAwBundleGlob is the glob pattern for bundle files produced by the agent.
const TmpAwBundleGlob = TmpGhAwDir + "/aw-*.bundle"

// TmpAwPatchGlob is the glob pattern for patch files produced by the agent.
const TmpAwPatchGlob = TmpGhAwDir + "/aw-*.patch"

// TmpGeminiClientErrorGlob is the glob for Gemini client error JSON diagnostic files.
const TmpGeminiClientErrorGlob = TmpGhAwDir + "/gemini-client-error-*.json"

// TmpAntigravityClientErrorGlob is the glob for Antigravity client error JSON diagnostic files.
const TmpAntigravityClientErrorGlob = TmpGhAwDir + "/antigravity-client-error-*.json"

// TmpPiAgentDir is the Pi engine agent working directory.
const TmpPiAgentDir = TmpGhAwDir + "/pi-agent-dir"

// ThreatDetectionLogPath is the threat detection engine log file path.
const ThreatDetectionLogPath = TmpGhAwDir + "/threat-detection/detection.log"

// TmpProxyLogsDir is the DIFC proxy logs directory (with trailing slash).
const TmpProxyLogsDir = TmpGhAwDir + "/proxy-logs/"

// TmpProxyTLSDir is the proxy TLS certificates sub-directory (with trailing slash).
const TmpProxyTLSDir = TmpGhAwDir + "/proxy-logs/proxy-tls/"

// TmpProxyTLSCACert is the proxy TLS CA certificate file path.
const TmpProxyTLSCACert = TmpGhAwDir + "/proxy-logs/proxy-tls/ca.crt"

// TmpDIFCProxyTLSCACert is the DIFC proxy TLS CA certificate file path.
const TmpDIFCProxyTLSCACert = TmpGhAwDir + "/difc-proxy-tls/ca.crt"

// TmpAwMcpLogsDir is the aw-mcp server logs directory.
const TmpAwMcpLogsDir = TmpGhAwDir + "/aw-mcp/logs"

// TmpSandboxAgentLogsDir is the sandbox agent logs directory (with trailing slash).
const TmpSandboxAgentLogsDir = TmpGhAwDir + "/sandbox/agent/logs/"

// Shell and Actions expression form path constants
//
// These complement GhAwRootDirShell and GhAwRootDir for sub-paths commonly
// referenced in both shell run: blocks and GitHub Actions expression contexts.

// GhAwRootDirShellSlash is GhAwRootDirShell with a trailing slash.
// Use for path prefix matching in shell expressions (e.g. ${RUNNER_TEMP}/gh-aw/).
const GhAwRootDirShellSlash = GhAwRootDirShell + "/"

// ShellMcpConfigDir is the mcp-config directory in shell environment variable form.
const ShellMcpConfigDir = GhAwRootDirShell + "/mcp-config"

// ShellMcpServersJsonPath is the MCP servers JSON config file path in shell form.
// Used by engines that resolve the config via the host RUNNER_TEMP path.
const ShellMcpServersJsonPath = GhAwRootDirShell + "/mcp-config/mcp-servers.json"

// GhAwRootDirSlash is GhAwRootDir with a trailing slash (Actions expression form).
const GhAwRootDirSlash = GhAwRootDir + "/"

// McpServersJsonPathExpr is the MCP servers JSON config path in Actions expression form.
const McpServersJsonPathExpr = GhAwRootDir + "/mcp-config/mcp-servers.json"

// CodexMcpConfigTomlPath is the Codex MCP config TOML file path in Actions expression form.
const CodexMcpConfigTomlPath = GhAwRootDir + "/mcp-config/config.toml"

// System path constants
//
// Well-known host system paths used by CLI tools and shell completion.

// CopilotBinaryPath is the path to the Copilot CLI binary inside AWF containers.
const CopilotBinaryPath = "/usr/local/bin/copilot"

// BashCompletionDir is the system-wide bash completion directory.
const BashCompletionDir = "/etc/bash_completion.d"

// BashCompletionGhAwPath is the gh-aw bash completion file path.
const BashCompletionGhAwPath = BashCompletionDir + "/gh-aw"

// HomebrewPrefix is the default Homebrew installation prefix on macOS.
const HomebrewPrefix = "/opt/homebrew"

// UsrLocalPrefix is the standard /usr/local installation prefix.
const UsrLocalPrefix = "/usr/local"

// 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 {
if dir := os.Getenv("GH_AW_WORKFLOWS_DIR"); dir != "" {
return filepath.ToSlash(dir)
}
return ".github/workflows"
return WorkflowsDir
}

// MaxSymlinkDepth limits recursive symlink resolution when fetching remote files.
Expand Down
6 changes: 3 additions & 3 deletions pkg/linters/hardcodedfilepath/hardcodedfilepath.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ func (r constRef) String() string {
// ".github", or "${RUNNER_TEMP}" alone are not flagged.
var pathPrefixes = []string{
"/tmp/",
"${RUNNER_TEMP}/",
"${{ runner.temp }}/",
".github/",
"${RUNNER_TEMP}/", //nolint:hardcodedfilepath
"${{ runner.temp }}/", //nolint:hardcodedfilepath
".github/", //nolint:hardcodedfilepath
"/opt/",
"/usr/",
"/var/",
Expand Down
3 changes: 2 additions & 1 deletion pkg/parser/import_field_extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"regexp"
"strings"

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

Expand Down Expand Up @@ -960,7 +961,7 @@ func computeImportRelPath(fullPath, importPath string) string {
if idx := strings.LastIndex(normalizedFullPath, "/.github/"); idx >= 0 {
return normalizedFullPath[idx+1:] // +1 to skip the leading slash
}
if strings.HasPrefix(normalizedFullPath, ".github/") {
if strings.HasPrefix(normalizedFullPath, constants.GithubDir) {
return normalizedFullPath
}
return importPath
Expand Down
3 changes: 2 additions & 1 deletion pkg/parser/include_expander.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"regexp"
"strings"

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

Expand Down Expand Up @@ -192,7 +193,7 @@ func ExtractBodyLevelImportPaths(content, baseDir string) []BodyLevelImport {
// Convert relative paths to workspace-root-relative.
// Paths already starting with ".github/" are workspace-root-relative.
// Absolute paths are used as-is.
if !strings.HasPrefix(importPath, ".github/") && !filepath.IsAbs(importPath) {
if !strings.HasPrefix(importPath, constants.GithubDir) && !filepath.IsAbs(importPath) {
if repoRoot != "" {
fullPath := filepath.Join(baseDir, importPath)
if rel, err := filepath.Rel(repoRoot, fullPath); err == nil && !strings.HasPrefix(rel, "..") {
Expand Down
2 changes: 1 addition & 1 deletion pkg/parser/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func buildPlaywrightBuiltinConfig(toolValue any) RegistryMCPServerConfig {
Command: "docker",
Args: []string{
"run", "-i", "--rm", "--shm-size=2gb", "--cap-add=SYS_ADMIN",
"-v", "/tmp/gh-aw/mcp-logs:/tmp/gh-aw/mcp-logs",
"-v", constants.TmpMcpLogsMount,
"mcr.microsoft.com/playwright:" + string(constants.DefaultPlaywrightBrowserVersion),
},
Env: make(map[string]string),
Expand Down
Loading
Loading