Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fd3b1ca
Initial plan
Copilot Dec 9, 2025
a908039
Initial exploration of safe outputs schema-driven generation
Copilot Dec 9, 2025
e9cb2f1
Implement schema-driven safe output action generation
Copilot Dec 9, 2025
a9b83f2
Add unit tests and fix linter errors for schema parser
Copilot Dec 9, 2025
43087a9
fix: Fix test pollution in compiler_custom_actions_test.go
Copilot Dec 9, 2025
9058728
Integrate schema-driven custom actions with script registry
Copilot Dec 9, 2025
ea114d4
Add generated actions to .gitignore
Copilot Dec 9, 2025
5a25e71
Move schema parser to workflow package and use schema-driven registra…
Copilot Dec 9, 2025
9b20f08
Commit generated actions and mark as auto-generated
Copilot Dec 10, 2025
6a7e64a
Add agent-mode frontmatter field to control action mode per workflow
Copilot Dec 10, 2025
36d4ee0
Add checkout and repository validation for dev mode in safe output jobs
Copilot Dec 10, 2025
168275d
Move dev mode validation to compile time and use sparse checkout
Copilot Dec 10, 2025
a4c8a68
Add bundled index.js files for generated actions
Copilot Dec 10, 2025
d187b0e
Fix bundling for Node.js environment with proper require() transforma…
Copilot Dec 10, 2025
45aee7a
Use GitHub Script bundling strategy instead of FILES pattern
Copilot Dec 10, 2025
42f4779
Use esbuild for action bundling with @actions/core and npm
Copilot Dec 10, 2025
b22d9fc
Keep package-lock.json, use npm ci, add more @actions deps, gitignore…
Copilot Dec 10, 2025
b53443f
Configure esbuild to minify JavaScript in release mode
Copilot Dec 10, 2025
669c4f6
Gitignore generated src/index.js files
Copilot Dec 10, 2025
6ff1d44
Update dev.md to create poem issue with staged safe outputs
Copilot Dec 10, 2025
9d0823b
Fix esbuild bundling to resolve local requires statically
Copilot Dec 10, 2025
ef210a4
Fix @actions/core bundling by making core globally available
Copilot Dec 10, 2025
d66a2ef
Auto-detect and import github-script globals (core, github, context)
Copilot Dec 10, 2025
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 .github/workflows/daily-team-status.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 11 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,18 @@ actions/**/.build/

# Generated action files (created by 'make generate-action-metadata')
# These are generated from pkg/workflow/js/*.cjs and should not be committed
# Using hyphenated names (kebab-case) following GitHub Actions convention
actions/noop/
actions/minimize_comment/
actions/close_issue/
actions/close_pull_request/
actions/close_discussion/
actions/minimize-comment/
actions/close-issue/
actions/close-pull-request/
actions/close-discussion/
actions/add-comment/
actions/create-issue/
actions/add-labels/
actions/create-discussion/
actions/update-issue/
actions/update-pull-request/

pkg/cli/workflows/*.yml
.github/workflows/test-update.md
Expand Down
105 changes: 72 additions & 33 deletions pkg/cli/generate_action_metadata_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,45 @@ type ActionOutput struct {
}

// GenerateActionMetadataCommand generates action.yml and README.md files for JavaScript modules
// Uses the agent-output schema to discover which safe output types should have custom actions
func GenerateActionMetadataCommand() error {
jsDir := "pkg/workflow/js"
actionsDir := "actions"
schemaPath := "schemas/agent-output.json"

generateActionMetadataLog.Print("Starting action metadata generation")
generateActionMetadataLog.Print("Starting schema-driven action metadata generation")

// Select target JavaScript files (simple ones for proof of concept)
targetFiles := []string{
"noop.cjs",
"minimize_comment.cjs",
"close_issue.cjs",
"close_pull_request.cjs",
"close_discussion.cjs",
// Load the safe output schema
schema, err := LoadSafeOutputSchema(schemaPath)
if err != nil {
return fmt.Errorf("failed to load schema: %w", err)
}

fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("🔍 Generating actions for %d JavaScript modules...", len(targetFiles))))
// Extract safe output types from the schema
safeOutputTypes := GetSafeOutputTypes(schema)

// Filter to only types that should have custom actions
var targetTypes []SafeOutputTypeSchema
for _, typeSchema := range safeOutputTypes {
if ShouldGenerateCustomAction(typeSchema.TypeName) {
targetTypes = append(targetTypes, typeSchema)
}
}

fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("🔍 Found %d safe output types in schema", len(safeOutputTypes))))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("🎯 Generating actions for %d types...", len(targetTypes))))

generatedCount := 0
for _, filename := range targetFiles {
for _, typeSchema := range targetTypes {
filename := GetJavaScriptFilename(typeSchema.TypeName)
jsPath := filepath.Join(jsDir, filename)

// Check if JavaScript file exists
if _, err := os.Stat(jsPath); os.IsNotExist(err) {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("⚠ Skipping %s: JavaScript file not found", typeSchema.TypeName)))
continue
}

// Read file content directly from filesystem
contentBytes, err := os.ReadFile(jsPath)
if err != nil {
Expand All @@ -69,17 +87,18 @@ func GenerateActionMetadataCommand() error {
}
content := string(contentBytes)

fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\n📦 Processing: %s", filename)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\n📦 Processing: %s (%s)", typeSchema.TypeName, typeSchema.Title)))

// Extract metadata
metadata, err := extractActionMetadata(filename, content)
// Extract metadata from both schema and JavaScript file
metadata, err := extractActionMetadataFromSchema(filename, content, typeSchema)
if err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("✗ Failed to extract metadata from %s: %s", filename, err.Error())))
continue
}

// Create action directory
actionDir := filepath.Join(actionsDir, metadata.ActionName)
// Create action directory (using hyphenated name for GitHub Actions convention)
actionDirName := GetActionDirectoryName(typeSchema.TypeName)
actionDir := filepath.Join(actionsDir, actionDirName)
if err := os.MkdirAll(actionDir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", actionDir, err)
}
Expand All @@ -95,21 +114,21 @@ func GenerateActionMetadataCommand() error {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("✗ Failed to generate action.yml: %s", err.Error())))
continue
}
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Generated action.yml")))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" ✓ Generated action.yml"))

// Generate README.md
if err := generateReadme(actionDir, metadata); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("✗ Failed to generate README.md: %s", err.Error())))
continue
}
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Generated README.md")))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" ✓ Generated README.md"))

// Copy source file
srcPath := filepath.Join(srcDir, "index.js")
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
return fmt.Errorf("failed to write source file: %w", err)
}
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ Copied source to src/index.js")))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" ✓ Copied source to src/index.js"))

generatedCount++
}
Expand All @@ -128,36 +147,56 @@ func GenerateActionMetadataCommand() error {
return nil
}

// extractActionMetadata extracts metadata from a JavaScript file
func extractActionMetadata(filename, content string) (*ActionMetadata, error) {
generateActionMetadataLog.Printf("Extracting metadata from %s", filename)

// Extract action name from filename (e.g., "noop.cjs" -> "noop")
actionName := strings.TrimSuffix(filename, ".cjs")
// extractActionMetadataFromSchema combines schema information with JavaScript analysis
func extractActionMetadataFromSchema(filename, content string, typeSchema SafeOutputTypeSchema) (*ActionMetadata, error) {
generateActionMetadataLog.Printf("Extracting metadata from schema for %s", typeSchema.TypeName)

// Extract description from JSDoc comment
description := extractDescription(content)
// Use schema description if available, otherwise fallback to JSDoc
description := typeSchema.Description
if description == "" {
description = fmt.Sprintf("Process %s safe output", actionName)
description = extractDescription(content)
}
if description == "" {
description = fmt.Sprintf("Process %s safe output", typeSchema.TypeName)
}

// Generate human-readable name from action name
name := generateHumanReadableName(actionName)
// Use schema title for action name
name := typeSchema.Title
if name == "" {
name = generateHumanReadableName(typeSchema.TypeName)
}

// Extract inputs
// Extract inputs from JavaScript (core.getInput calls)
inputs := extractInputs(content)

// Extract outputs
// Add standard token input if not already present
hasToken := false
for _, input := range inputs {
if input.Name == "token" {
hasToken = true
break
}
}
if !hasToken {
inputs = append([]ActionInput{{
Name: "token",
Description: "GitHub token for API authentication",
Required: true,
Default: "",
}}, inputs...)
}

// Extract outputs from JavaScript (core.setOutput calls)
outputs := extractOutputs(content)

// Extract dependencies
// Extract dependencies from require() calls
dependencies := extractDependencies(content)

metadata := &ActionMetadata{
Name: name,
Description: description,
Filename: filename,
ActionName: actionName,
ActionName: typeSchema.TypeName,
Inputs: inputs,
Outputs: outputs,
Dependencies: dependencies,
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/init_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestNewInitCommand(t *testing.T) {
if codespaceFlag.DefValue != "" {
t.Errorf("Expected codespaces flag default to be '', got %q", codespaceFlag.DefValue)
}

// Verify NoOptDefVal is set to a space (allows --codespaces without value)
if codespaceFlag.NoOptDefVal != " " {
t.Errorf("Expected codespaces flag NoOptDefVal to be ' ' (space), got %q", codespaceFlag.NoOptDefVal)
Expand Down
Loading