Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d2e413d
Initial plan
Copilot May 14, 2026
9c608b5
Add aw manifest package resolution
Copilot May 14, 2026
0c37327
Fix aw manifest review follow-up
Copilot May 14, 2026
47cab9e
Polish aw manifest validation messages
Copilot May 14, 2026
bba9787
Add aw manifest schema and compile validation
Copilot May 14, 2026
eca2c37
Polish aw manifest schema follow-up
Copilot May 14, 2026
00b84e7
Refine aw manifest validation messages
Copilot May 14, 2026
1d45909
Address aw manifest review follow-up
Copilot May 14, 2026
8a24a70
Support aw manifests in add-wizard
Copilot May 14, 2026
8f85b10
Clarify add-wizard manifest package support
Copilot May 14, 2026
24f0ceb
Clarify add-wizard manifest help text
Copilot May 14, 2026
a2f8265
Polish add-wizard manifest tests
Copilot May 14, 2026
cfd9628
Tighten add-wizard manifest coverage
Copilot May 14, 2026
3b05795
Polish add-wizard manifest errors
Copilot May 14, 2026
5cfc6c1
Remove legacy repository manifest aliases
Copilot May 14, 2026
2d30d7d
Tighten legacy manifest alias coverage
Copilot May 14, 2026
b7c1a57
Clarify ignored legacy manifest behavior
Copilot May 14, 2026
989da58
Rename legacy manifest regression tests
Copilot May 14, 2026
69264c8
Refine aw manifest versioning and README validation
Copilot May 14, 2026
3af9726
Polish aw manifest validation cleanup
Copilot May 14, 2026
0be8b6e
Clarify aw manifest validation tests
Copilot May 14, 2026
a8704ec
Simplify repository package workflow resolution
Copilot May 14, 2026
b6788cd
Polish repository package cleanup
Copilot May 14, 2026
a27c1a5
Fix manifest package review regressions
Copilot May 14, 2026
0f7a5c3
Fix remaining manifest bot review feedback
Copilot May 14, 2026
b8070f4
Polish manifest review follow-up
Copilot May 14, 2026
c35e4fa
Log default branch fallback for manifests
Copilot May 14, 2026
6d18952
Polish manifest package error handling
Copilot May 14, 2026
ef22529
Tidy manifest package polish
Copilot May 14, 2026
529cb09
Simplify manifest branch resolution helper
Copilot May 14, 2026
9c6a815
fix: address Go lint follow-ups
Copilot May 14, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
title: aw.yml repository package manifest specification
description: Normative specification for the aw.yml repository package manifest format.
sidebar:
order: 321
---

# aw.yml Repository Package Manifest Specification

**Version**: 0.1.0
**Status**: Draft

## Abstract

This specification defines the `aw.yml` repository package manifest format used by `gh aw` to identify, validate, and install repository packages.

## 1. Introduction

The `aw.yml` manifest describes an installable Agentic Workflow package located either at a repository root or within a nested package folder.

Package references use one of these forms:

- `owner/repo`
- `owner/repo/path/to/package`

The package root is the directory containing `aw.yml`.

## 2. Conformance

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.

## 3. Manifest location and naming

The canonical manifest filename is `aw.yml`.

## 4. Manifest format

The manifest document MUST be a YAML mapping. Unknown top-level fields MUST be rejected.

### 4.1 Fields

| Field | Type | Required | Meaning |
| --- | --- | --- | --- |
| `manifest-version` | string | No | Manifest format version. Defaults to `"1"`. |
| `min-version` | string | No | Minimum supported `gh-aw` version. |
| `name` | string | Yes | Human-readable package name. |
| `description` | string | No | Human-readable package description. |
| `files` | array of strings | No | Explicit installable workflow file list. |

### 4.2 `manifest-version`

If omitted, `manifest-version` defaults to `"1"`.

For this version of the format, the only valid value is `"1"`.

### 4.3 `min-version`

If present, `min-version` MUST use the exact `vMAJOR.minor.patch` form, such as:

- `v1.2.3`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot vMAJOR.minor.patch is strictly supported

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 69264c8. min-version now only accepts the exact vMAJOR.minor.patch form in the schema, parser validation, and docs.


If the running compiler version is lower than `min-version`, validation MUST fail.

### 4.4 `name`

`name` MUST be present and MUST be a non-empty string after trimming surrounding whitespace.

### 4.5 `description`

If present, `description` MUST be a string.

Implementations SHOULD warn if `description` exceeds 255 characters.

### 4.6 `files`

If present, `files` MUST be an array of strings.

Each entry:

- MUST be resolved relative to the package root;
- MUST end in `.md`; and
- MUST begin with either `workflows/` or `.github/workflows/`.

Duplicate entries SHOULD be ignored after normalization.

## 5. Installable file resolution

Supported installable paths are:

- `workflows/<name>.md`
- `.github/workflows/<name>.md`

Nested descendants under those directories are also valid when referenced explicitly in `files`.

If `files` is present, valid entries are used as the installable workflow set. Invalid entries MUST be ignored with a warning.

If `files` is omitted, or if no valid entries remain after filtering, the implementation MUST attempt discovery under:

- `workflows/`
- `.github/workflows/`

If no installable workflow markdown files are resolved, package validation MUST fail.

## 6. Documentation

Package documentation is `README.md` in the package root.

Examples:

- Repository-root package: `README.md`
- Nested package: `path/to/package/README.md`

If `README.md` is absent, package validation MUST fail.

## 7. Validation and errors

Validation MUST fail for at least the following conditions:

- manifest file not found at the resolved package root;
- malformed YAML;
- top-level document is not a mapping;
- missing or empty `name`;
- unsupported `manifest-version`;
- invalid `min-version`;
- current compiler version is lower than `min-version`;
- unknown top-level fields, including `docs`; or
- missing required `README.md`; or
- no installable workflow files resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot fail if readme.md is missing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 69264c8, with compile-test coverage clarified in 0be8b6e. Package resolution and local compile validation now fail when README.md is missing.


Implementations SHOULD emit warnings for at least the following conditions:

- a `files` entry is ignored because it is not a supported installable path; or
- `description` exceeds 255 characters.

## 8. Compile validation

When `gh aw compile` encounters a repository-root `aw.yml`, it validates that manifest before compiling workflows.

A conforming compiler:

- MUST parse and validate the manifest according to this specification;
- MUST fail compilation on manifest errors;
- SHOULD surface warnings as `manifest_warning`; and
- SHOULD surface errors as `manifest_error`.

If JSON output is requested, manifest validation failure still causes an overall compilation failure result.

## 9. Examples

### 9.1 Repository-root package

```yaml
min-version: v0.38.0
name: Repo Assist
description: Friendly repository automation for review and issue triage
files:
- workflows/review.md
- .github/workflows/nightly-review.md
```

### 9.2 Nested package folder

Package reference:

```text
owner/repo/packages/repo-assist
```

Manifest location:

```text
packages/repo-assist/aw.yml
```

Manifest:

```yaml
name: Repo Assist
files:
- workflows/review.md
```

Documentation file:

```text
packages/repo-assist/README.md
```
58 changes: 58 additions & 0 deletions docs/src/content/docs/reference/repository-package-manifest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Repository package manifest
description: Reference for the aw.yml manifest used by gh aw add and gh aw compile.
sidebar:
order: 320
---

Use `aw.yml` to describe an installable workflow package for `gh aw add`.

- Repository root packages use `owner/repo`
- Nested packages use `owner/repo/path/to/package`
- `gh aw compile` validates a repository-root `aw.yml` before compiling workflows

For the normative file-format definition, see the [aw.yml repository package manifest specification](/gh-aw/reference/repository-package-manifest-specification/).

## Example

```yaml
min-version: v0.38.0
name: Repo Assist
description: Friendly repository automation for review and issue triage
files:
- workflows/review.md
- .github/workflows/nightly-review.md
```

## Quick reference

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `manifest-version` | string | No | Current supported value: `"1"`. Defaults to `"1"` when omitted. |
| `min-version` | string | No | Minimum compatible `gh aw` version. Must use the exact `vMAJOR.minor.patch` form, such as `v0.38.0`. |
| `name` | string | Yes | Human-readable package name. Must be non-empty after trimming whitespace. |
| `description` | string | No | Optional package description. `gh aw add` warns when it exceeds 255 characters. |
| `files` | array of strings | No | Package-root-relative markdown files under `workflows/` or `.github/workflows/`. |

## Documentation

Package documentation is `README.md` in the package root.

- Repository-root package docs: `README.md`
- Nested package docs: `path/to/package/README.md`

The manifest does not support a `docs` field.
Missing `README.md` causes package validation to fail.

## Installable workflows

If `files` is present, valid entries are used as the install bundle.

If `files` is omitted or contains no valid installable paths, `gh aw add` scans:

- `workflows/`
- `.github/workflows/`

For nested packages, those paths are resolved relative to the package root.

The embedded JSON schema source of truth lives in `pkg/parser/schemas/aw_manifest_schema.json`.
4 changes: 4 additions & 0 deletions docs/src/content/docs/setup/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ gh aw add githubnext/agentics/ci-doctor --create-pull-request # Create PR

**Options:** `--dir/-d`, `--create-pull-request`, `--no-gitattributes`, `--append`, `--disable-security-scanner`, `--engine/-e`, `--force/-f`, `--name/-n`, `--no-stop-after`, `--stop-after`

Repository-level packages can declare an [`aw.yml` manifest](/gh-aw/reference/repository-package-manifest/) at the repository root or in a nested package folder to define installable files, package `README.md`, schema compatibility, and minimum supported CLI versions.

#### `new`

Create a workflow template in `.github/workflows/`. Opens for editing automatically.
Expand Down Expand Up @@ -263,6 +265,8 @@ gh aw compile --dependabot # Generate dependency manifests
gh aw compile --purge # Remove orphaned .lock.yml files
```

If the repository root contains an [`aw.yml` manifest](/gh-aw/reference/repository-package-manifest/), `gh aw compile` validates it before compiling workflows.

**Options:** `--action-mode`, `--action-tag`, `--actionlint`, `--actions-repo`, `--allow-action-refs`, `--approve`, `--dependabot`, `--dir/-d`, `--engine/-e`, `--fail-fast`, `--fix`, `--force`, `--force-refresh-action-pins`, `--json/-j`, `--logical-repo`, `--no-check-update`, `--no-emit`, `--poutine`, `--purge`, `--refresh-stop-time`, `--runner-guard`, `--schedule-seed`, `--stats`, `--strict`, `--trial`, `--validate`, `--validate-images`, `--watch/-w`, `--zizmor`

**`--approve` flag:** When compiling a workflow that already has a lock file, the compiler enforces *safe update mode* — any newly added secrets or custom actions not present in the previous manifest require explicit approval. Pass `--approve` to accept these changes and regenerate the manifest baseline. On first compile (no existing lock file), enforcement is skipped automatically and `--approve` is not needed.
Expand Down
8 changes: 8 additions & 0 deletions pkg/cli/add_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ for a guided setup that configures secrets, creates a pull request, and more.

Examples:
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/daily-repo-status # Add workflow directly
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/repo-assist # Add package from repository root aw.yml
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/packages/repo-assist # Add package from nested aw.yml
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor@v1.0.0 # Add with version
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/workflows/ci-doctor.md@main
` + string(constants.CLIExtensionPrefix) + ` add https://github.com/githubnext/agentics/blob/main/workflows/ci-doctor.md
Expand All @@ -66,6 +68,8 @@ Examples:
` + string(constants.CLIExtensionPrefix) + ` add githubnext/agentics/ci-doctor --dir .github/workflows/shared # Add to .github/workflows/shared/

Workflow specifications:
- Two parts: "owner/repo[@version]" (loads repository-root aw.yml package)
- Three+ parts without .md: "owner/repo/folder[@version]" (loads nested aw.yml package when present)
- Three parts: "owner/repo/workflow-name[@version]" (implicitly looks in workflows/ directory)
- Four+ parts: "owner/repo/workflows/workflow-name.md[@version]" (requires explicit .md extension)
- GitHub URL: "https://github.com/owner/repo/blob/branch/path/to/workflow.md"
Expand Down Expand Up @@ -195,6 +199,10 @@ func AddResolvedWorkflows(workflowStrings []string, resolved *ResolvedWorkflows,

result := &AddWorkflowsResult{}

for _, warning := range resolved.Warnings {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(warning))
}

// If creating a PR, check prerequisites
if opts.CreatePR {
// Check if GitHub CLI is available
Expand Down
6 changes: 3 additions & 3 deletions pkg/cli/add_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ func TestAddWorkflows(t *testing.T) {
errorContains: "at least one workflow",
},
{
name: "repo-only spec (requires workflow path)",
workflows: []string{"owner/repo"},
name: "invalid repo spec missing repo name",
workflows: []string{"owner"},
expectError: true,
errorContains: "workflow specification must be in format",
errorContains: "not a valid workflow path or repository package",
},
}

Expand Down
56 changes: 48 additions & 8 deletions pkg/cli/add_interactive_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"strings"

"charm.land/huh/v2"
"github.com/github/gh-aw/pkg/console"
Expand Down Expand Up @@ -195,14 +196,14 @@ func (c *AddInteractiveConfig) showWorkflowDescriptions() {
func (c *AddInteractiveConfig) determineFilesToAdd() (workflowFiles []string, initFiles []string, err error) {
addInteractiveLog.Print("Determining files to add")

// Parse the workflow specs to get the files that will be added
for _, spec := range c.WorkflowSpecs {
parsed, parseErr := parseWorkflowSpec(spec)
if parseErr != nil {
return nil, nil, fmt.Errorf("invalid workflow specification '%s': %w", spec, parseErr)
}
workflowFiles = append(workflowFiles, parsed.WorkflowName+".md")
workflowFiles = append(workflowFiles, parsed.WorkflowName+".lock.yml")
workflowNames, err := c.workflowNamesForInteractiveAdd()
if err != nil {
return nil, nil, err
}

for _, workflowName := range workflowNames {
workflowFiles = append(workflowFiles, workflowName+".md")
workflowFiles = append(workflowFiles, workflowName+".lock.yml")
}

fmt.Fprintln(os.Stderr, "")
Expand All @@ -214,6 +215,45 @@ func (c *AddInteractiveConfig) determineFilesToAdd() (workflowFiles []string, in
return workflowFiles, initFiles, nil
}

func (c *AddInteractiveConfig) workflowNamesForInteractiveAdd() ([]string, error) {
workflowSpecsForError := strings.Join(c.WorkflowSpecs, ", ")
if c.resolvedWorkflows != nil && len(c.resolvedWorkflows.Workflows) > 0 {
workflowNames := make([]string, 0, len(c.resolvedWorkflows.Workflows))
for i, resolvedWorkflow := range c.resolvedWorkflows.Workflows {
if resolvedWorkflow == nil {
return nil, fmt.Errorf("resolved manifest workflow at position %d from %q is nil", i+1, workflowSpecsForError)
}
if resolvedWorkflow.Spec == nil {
return nil, fmt.Errorf("resolved manifest workflow at position %d from %q is missing its specification", i+1, workflowSpecsForError)
}
workflowName := strings.TrimSpace(resolvedWorkflow.Spec.WorkflowName)
if workflowName == "" {
return nil, fmt.Errorf("resolved manifest workflow at position %d from %q is missing its workflow name", i+1, workflowSpecsForError)
}
workflowNames = append(workflowNames, workflowName)
}
return workflowNames, nil
}

workflowNames := make([]string, 0, len(c.WorkflowSpecs))
for _, spec := range c.WorkflowSpecs {
parsed, parseErr := parseWorkflowSpec(spec)
if parseErr != nil {
return nil, fmt.Errorf("invalid workflow specification '%s': %w", spec, parseErr)
}
workflowNames = append(workflowNames, parsed.WorkflowName)
}
return workflowNames, nil
}

func (c *AddInteractiveConfig) primaryWorkflowName() string {
workflowNames, err := c.workflowNamesForInteractiveAdd()
if err != nil || len(workflowNames) == 0 {
return ""
}
return workflowNames[0]
}

// confirmChanges asks the user to confirm the changes
// secretValue is empty if the secret already exists in the repository
func (c *AddInteractiveConfig) confirmChanges(workflowFiles, initFiles []string, secretName string, secretValue string) error {
Expand Down
Loading