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
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -550,15 +550,15 @@ fmt-go:
.PHONY: fmt-cjs
fmt-cjs:
@echo "→ Formatting JavaScript files..."
@cd actions/setup/js && npm run format:cjs
@npx prettier --write 'scripts/**/*.js' --ignore-path .prettierignore
@cd actions/setup/js && npm run format:cjs --silent >/dev/null 2>&1
@npx prettier --write 'scripts/**/*.js' --ignore-path .prettierignore --log-level=error 2>&1
@echo "✓ JavaScript files formatted"

# Format JSON files in pkg directory (excluding actions/setup/js, which is handled by npm script)
.PHONY: fmt-json
fmt-json:
@echo "→ Formatting JSON files..."
@cd actions/setup/js && npm run format:pkg-json
@cd actions/setup/js && npm run format:pkg-json --silent >/dev/null 2>&1
@echo "✓ JSON files formatted"

# Check formatting
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/cache_memory_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ tools:
"# Cache memory file share configuration from frontmatter processed below",
"- name: Create cache-memory directory",
"- name: Cache cache-memory file share data",
"uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache@",
"key: memory-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}",
"path: /tmp/gh-aw/cache-memory",
"cat \"/opt/gh-aw/prompts/cache_memory_prompt.md\"",
Expand Down
32 changes: 24 additions & 8 deletions pkg/workflow/cache_memory_restore_only_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package workflow

import (
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -35,13 +36,13 @@ tools:
expectedInLock: []string{
"# Cache memory file share configuration from frontmatter processed below",
"- name: Restore cache-memory file share data",
"actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache/restore@", // SHA varies, just check action name
"key: memory-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}",
"path: /tmp/gh-aw/cache-memory",
},
notExpectedInLock: []string{
"- name: Upload cache-memory data as artifact",
"uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
// Note: We can't use "uses: actions/cache@" here because cache/restore also matches
},
},
{
Expand All @@ -65,10 +66,10 @@ tools:
expectedInLock: []string{
"# Cache memory file share configuration from frontmatter processed below",
"- name: Cache cache-memory file share data (default)",
"actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache@", // SHA varies
"key: memory-default-${{ github.run_id }}",
"- name: Restore cache-memory file share data (readonly)",
"actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache/restore@", // SHA varies
"key: memory-readonly-${{ github.run_id }}",
},
notExpectedInLock: []string{
Expand Down Expand Up @@ -103,9 +104,9 @@ tools:
---`,
expectedInLock: []string{
"- name: Cache cache-memory file share data (writeable)",
"actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache@", // SHA varies
"- name: Restore cache-memory file share data (readonly1)",
"actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache/restore@", // SHA varies
"- name: Restore cache-memory file share data (readonly2)",
},
notExpectedInLock: []string{
Expand Down Expand Up @@ -149,14 +150,29 @@ tools:
// Check expected strings are present
for _, expected := range tt.expectedInLock {
if !strings.Contains(lockStr, expected) {
t.Errorf("Expected to find '%s' in lock file but it was missing.\nLock file content:\n%s", expected, lockStr)
// Show a snippet of the lock file for context (first 100 lines)
lines := strings.Split(lockStr, "\n")
snippet := strings.Join(lines[:min(100, len(lines))], "\n")
t.Errorf("Expected to find '%s' in lock file but it was missing.\nFirst 100 lines of lock file:\n%s\n...(truncated)", expected, snippet)
}
}

// Check unexpected strings are NOT present
for _, notExpected := range tt.notExpectedInLock {
if strings.Contains(lockStr, notExpected) {
t.Errorf("Did not expect to find '%s' in lock file but it was present.\nLock file content:\n%s", notExpected, lockStr)
// Find the line containing the unexpected string for context
lines := strings.Split(lockStr, "\n")
var contextLines []string
for i, line := range lines {
if strings.Contains(line, notExpected) {
start := max(0, i-3)
end := min(len(lines), i+4)
contextLines = append(contextLines, fmt.Sprintf("Lines %d-%d:", start+1, end))
contextLines = append(contextLines, lines[start:end]...)
break
}
}
t.Errorf("Did not expect to find '%s' in lock file but it was present.\nContext:\n%s", notExpected, strings.Join(contextLines, "\n"))
}
}
})
Expand Down
32 changes: 25 additions & 7 deletions pkg/workflow/cache_memory_threat_detection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package workflow

import (
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -42,7 +43,7 @@ Test workflow with cache-memory and threat detection enabled.`,
expectedInLock: []string{
// In agent job, should use actions/cache/restore instead of actions/cache
"- name: Restore cache-memory file share data",
"uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache/restore@",
"key: memory-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}",
// Should upload artifact with if: always()
"- name: Upload cache-memory data as artifact",
Expand All @@ -55,7 +56,7 @@ Test workflow with cache-memory and threat detection enabled.`,
"if: always() && needs.agent.outputs.detection_success == 'true'",
"- name: Download cache-memory artifact (default)",
"- name: Save cache-memory to cache (default)",
"uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache/save@",
},
notExpectedInLock: []string{
// Should NOT use regular actions/cache in agent job
Expand All @@ -82,7 +83,7 @@ Test workflow with cache-memory but no threat detection.`,
expectedInLock: []string{
// Without threat detection, should use regular actions/cache
"- name: Cache cache-memory file share data",
"uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache@",
"key: memory-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}",
},
notExpectedInLock: []string{
Expand Down Expand Up @@ -121,7 +122,7 @@ Test workflow with multiple cache-memory and threat detection enabled.`,
expectedInLock: []string{
// Both caches should use restore
"- name: Restore cache-memory file share data (default)",
"uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache/restore@",
"key: memory-default-${{ github.run_id }}",
"- name: Restore cache-memory file share data (session)",
"key: memory-session-${{ github.run_id }}",
Expand Down Expand Up @@ -169,7 +170,7 @@ Test workflow with restore-only cache-memory and threat detection enabled.`,
expectedInLock: []string{
// Should use restore for restore-only cache (no ID suffix for single default cache)
"- name: Restore cache-memory file share data",
"uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache/restore@",
},
notExpectedInLock: []string{
// Should NOT upload artifact for restore-only
Expand Down Expand Up @@ -204,18 +205,35 @@ Test workflow with restore-only cache-memory and threat detection enabled.`,
t.Fatalf("Failed to read lock file: %v", err)
}
lockContent := string(lockYAML)
lines := strings.Split(lockContent, "\n")

// Check expected strings
for _, expected := range tt.expectedInLock {
if !strings.Contains(lockContent, expected) {
t.Errorf("Expected lock YAML to contain %q, but it didn't.\nGenerated YAML:\n%s", expected, lockContent)
// Show first 100 lines for context (not entire file)
preview := strings.Join(lines[:min(100, len(lines))], "\n")
if len(lines) > 100 {
preview += fmt.Sprintf("\n... (%d more lines)", len(lines)-100)
}
t.Errorf("Expected lock YAML to contain %q, but it didn't.\nFirst 100 lines:\n%s", expected, preview)
}
}

// Check not expected strings
for _, notExpected := range tt.notExpectedInLock {
if strings.Contains(lockContent, notExpected) {
t.Errorf("Expected lock YAML NOT to contain %q, but it did.\nGenerated YAML:\n%s", notExpected, lockContent)
// Find the matching line and show context
matchIdx := -1
for i, line := range lines {
if strings.Contains(line, notExpected) || strings.Contains(strings.Join(lines[max(0, i-1):min(len(lines), i+2)], "\n"), notExpected) {
matchIdx = i
break
}
}
start := max(0, matchIdx-3)
end := min(len(lines), matchIdx+4)
context := strings.Join(lines[start:end], "\n")
t.Errorf("Expected lock YAML NOT to contain %q, but it did.\nContext around match (lines %d-%d):\n%s", notExpected, start+1, end, context)
}
}
})
Expand Down
10 changes: 4 additions & 6 deletions pkg/workflow/compiler_activation_jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1026,12 +1026,10 @@ func (c *Compiler) generateCheckoutGitHubFolderForActivation(data *WorkflowData)
}
}

// Check if we have contents permission - without it, checkout is not possible
permParser := NewPermissionsParser(data.Permissions)
if !permParser.HasContentsReadAccess() {
compilerActivationJobsLog.Print("Skipping .github checkout in activation: no contents read access")
return nil
}
// Note: We don't check data.Permissions for contents read access here because
// the activation job ALWAYS gets contents:read added to its permissions (see buildActivationJob
// around line 720). The workflow's original permissions may not include contents:read,
// but the activation job will always have it for GitHub API access and runtime imports.

// For activation job, always add sparse checkout of .github and .agents folders
// This is needed for runtime imports during prompt generation
Expand Down
6 changes: 3 additions & 3 deletions pkg/workflow/compiler_artifacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ post-steps:
- name: First Post Step
run: echo "first"
- name: Second Post Step
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@v4 # SHA will be pinned
with:
name: test-artifact
path: test-file.txt
Expand Down Expand Up @@ -272,8 +272,8 @@ This workflow should generate a unified artifact upload step that includes the p
}

// Verify the upload step uses the correct action
if !strings.Contains(lockYAML, "uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f") {
t.Error("Expected 'actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f' action to be used")
if !strings.Contains(lockYAML, "uses: actions/upload-artifact@") { // SHA varies
t.Error("Expected 'actions/upload-artifact' action to be used")
}

// Verify the unified artifact name
Expand Down
45 changes: 38 additions & 7 deletions pkg/workflow/compiler_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package workflow

import (
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -46,7 +47,7 @@ tools:
"# Cache configuration from frontmatter was processed and added to the main job steps",
"# Cache configuration from frontmatter processed below",
"- name: Cache",
"uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache@", // SHA varies
"key: node-modules-${{ hashFiles('package-lock.json') }}",
"path: node_modules",
"restore-keys: node-modules-",
Expand Down Expand Up @@ -89,7 +90,7 @@ tools:
"# Cache configuration from frontmatter processed below",
"- name: Cache (node-modules-${{ hashFiles('package-lock.json') }})",
"- name: Cache (build-cache-${{ github.sha }})",
"uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache@", // SHA varies
"key: node-modules-${{ hashFiles('package-lock.json') }}",
"key: build-cache-${{ github.sha }}",
"path: node_modules",
Expand Down Expand Up @@ -131,7 +132,7 @@ tools:
expectedInLock: []string{
"# Cache configuration from frontmatter processed below",
"- name: Cache",
"uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830",
"uses: actions/cache@", // SHA varies
"key: full-cache-${{ github.sha }}",
"path: dist",
"restore-keys: |",
Expand Down Expand Up @@ -180,15 +181,30 @@ tools:
// Check that expected strings are present
for _, expected := range tt.expectedInLock {
if !strings.Contains(lockContent, expected) {
t.Errorf("Expected lock file to contain '%s' but it didn't.\nContent:\n%s", expected, lockContent)
// Show a snippet of the lock file for context (first 100 lines)
lines := strings.Split(lockContent, "\n")
snippet := strings.Join(lines[:min(100, len(lines))], "\n")
t.Errorf("Expected lock file to contain '%s' but it didn't.\nFirst 100 lines:\n%s\n...(truncated)", expected, snippet)
}
}

// Check that unexpected strings are NOT present in non-comment lines
// (frontmatter is embedded as comments, so we need to exclude comment lines)
for _, notExpected := range tt.notExpectedInLock {
if containsInNonCommentLines(lockContent, notExpected) {
t.Errorf("Lock file should NOT contain '%s' in non-comment lines but it did.\nContent:\n%s", notExpected, lockContent)
// Find the line containing the unexpected string for context
lines := strings.Split(lockContent, "\n")
var contextLines []string
for i, line := range lines {
if strings.Contains(line, strings.TrimSpace(notExpected)) {
start := max(0, i-3)
end := min(len(lines), i+4)
contextLines = append(contextLines, fmt.Sprintf("Lines %d-%d:", start+1, end))
contextLines = append(contextLines, lines[start:end]...)
break
}
}
t.Errorf("Lock file should NOT contain '%s' in non-comment lines but it did.\nContext:\n%s", notExpected, strings.Join(contextLines, "\n"))
}
}
})
Expand Down Expand Up @@ -247,7 +263,10 @@ This workflow should get default permissions applied automatically.

for _, expectedPerm := range expectedDefaultPermissions {
if !strings.Contains(lockContentStr, expectedPerm) {
t.Errorf("Expected default permission '%s' not found in generated workflow.\nGenerated content:\n%s", expectedPerm, lockContentStr)
// Show first 100 lines for context
lines := strings.Split(lockContentStr, "\n")
snippet := strings.Join(lines[:min(100, len(lines))], "\n")
t.Errorf("Expected default permission '%s' not found in generated workflow.\nFirst 100 lines:\n%s\n...(truncated)", expectedPerm, snippet)
}
}

Expand Down Expand Up @@ -430,7 +449,19 @@ This workflow has custom permissions that should override defaults.

for _, defaultPerm := range defaultOnlyPermissions {
if strings.Contains(lockContentStr, defaultPerm) {
t.Errorf("Default permission '%s' should not be present when custom permissions are specified.\nGenerated content:\n%s", defaultPerm, lockContentStr)
// Find the line containing the unexpected permission for context
lines := strings.Split(lockContentStr, "\n")
var contextLines []string
for i, line := range lines {
if strings.Contains(line, defaultPerm) {
start := max(0, i-3)
end := min(len(lines), i+4)
contextLines = append(contextLines, fmt.Sprintf("Lines %d-%d:", start+1, end))
contextLines = append(contextLines, lines[start:end]...)
break
}
}
t.Errorf("Default permission '%s' should not be present when custom permissions are specified.\nContext:\n%s", defaultPerm, strings.Join(contextLines, "\n"))
}
}
}
6 changes: 3 additions & 3 deletions pkg/workflow/compiler_customsteps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ func TestCustomStepsIndentation(t *testing.T) {
name: "standard_2_space_indentation",
stepsYAML: `steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true`,
Expand All @@ -38,7 +38,7 @@ func TestCustomStepsIndentation(t *testing.T) {
name: "odd_3_space_indentation",
stepsYAML: `steps:
- name: Odd indent
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
uses: actions/checkout@v5
with:
param: value`,
description: "3-space indentation should be normalized to standard format",
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/git_patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Please do the following tasks:
}

// Verify the upload step uses actions/upload-artifact
if !strings.Contains(lockContent, "uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f") {
if !strings.Contains(lockContent, "uses: actions/upload-artifact@") { // SHA varies
t.Error("Expected upload-artifact action to be used for unified artifact upload step")
}

Expand Down
Loading
Loading