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
7 changes: 4 additions & 3 deletions cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ func runCompileCmd(cmd *cobra.Command, args []string) error {
}

type commandSet struct {
addCmd, addWizardCmd, updateCmd, deployCmd, trialCmd, initCmd, statusCmd, listCmd *cobra.Command
addCmd, addWizardCmd, editCmd, updateCmd, deployCmd, trialCmd, initCmd, statusCmd, listCmd *cobra.Command
mcpCmd, logsCmd, auditCmd, viewCmd, healthCmd, outcomesCmd, mcpServerCmd, prCmd, secretsCmd *cobra.Command
fixCmd, upgradeCmd, completionCmd, hashCmd, projectCmd, doctorCmd, checksCmd, validateCmd, lintCmd *cobra.Command
domainsCmd, experimentsCmd, forecastCmd, gradersCmd, modelsCmd, envCmd *cobra.Command
Expand Down Expand Up @@ -708,6 +708,7 @@ func createCommandSet() commandSet {
cmds := commandSet{
addCmd: cli.NewAddCommand(validateEngine),
addWizardCmd: cli.NewAddWizardCommand(validateEngine),
editCmd: cli.NewEditCommand(),
updateCmd: cli.NewUpdateCommand(validateEngine),
deployCmd: cli.NewDeployCommand(validateEngine),
trialCmd: cli.NewTrialCommand(validateEngine),
Expand Down Expand Up @@ -845,7 +846,7 @@ func configureOtherCommandFlags() {

func assignCommandGroups(cmds commandSet) {
cmds.initCmd.GroupID, newCmd.GroupID, cmds.addCmd.GroupID, cmds.addWizardCmd.GroupID = "setup", "setup", "setup", "setup"
removeCmd.GroupID, cmds.updateCmd.GroupID, cmds.deployCmd.GroupID, cmds.upgradeCmd.GroupID = "setup", "setup", "setup", "setup"
removeCmd.GroupID, cmds.editCmd.GroupID, cmds.updateCmd.GroupID, cmds.deployCmd.GroupID, cmds.upgradeCmd.GroupID = "setup", "setup", "setup", "setup", "setup"
cmds.secretsCmd.GroupID, cmds.envCmd.GroupID, cmds.doctorCmd.GroupID = "setup", "setup", "setup"
compileCmd.GroupID, cmds.validateCmd.GroupID, cmds.lintCmd.GroupID = "development", "development", "development"
cmds.mcpCmd.GroupID, cmds.fixCmd.GroupID, cmds.domainsCmd.GroupID = "development", "development", "development"
Expand All @@ -859,7 +860,7 @@ func assignCommandGroups(cmds commandSet) {

func addCommandsToRoot(cmds commandSet) {
rootCmd.AddCommand(
compileCmd, cmds.addCmd, cmds.addWizardCmd, cmds.updateCmd, cmds.deployCmd, cmds.upgradeCmd, cmds.trialCmd, newCmd, cmds.initCmd,
compileCmd, cmds.addCmd, cmds.addWizardCmd, cmds.editCmd, cmds.updateCmd, cmds.deployCmd, cmds.upgradeCmd, cmds.trialCmd, newCmd, cmds.initCmd,
runCmd, removeCmd, cmds.statusCmd, cmds.listCmd, enableCmd, disableCmd, cmds.logsCmd, cmds.auditCmd, cmds.viewCmd,
cmds.healthCmd, cmds.outcomesCmd, cmds.checksCmd, cmds.mcpCmd, cmds.mcpServerCmd, cmds.prCmd, versionCmd, cmds.secretsCmd,
cmds.fixCmd, cmds.validateCmd, cmds.lintCmd, cmds.completionCmd, cmds.hashCmd, cmds.projectCmd, cmds.doctorCmd,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ADR-55475: Schema-Validated Workflow Frontmatter Edit Command

**Date**: 2026-08-24
**Status**: Draft
**Deciders**: pelikhan, copilot-swe-agent

---

### Context

Workflow definitions in this project are Markdown files with YAML frontmatter that controls execution parameters (e.g., `max-turns`, `model`, `on.schedule`, `imports`). To change any of these parameters today, users must manually edit the raw Markdown file and then separately run `gh aw compile` to regenerate the corresponding `.lock.yml` file. Manual editing bypasses schema validation entirely, meaning invalid frontmatter values are only detected at compile time—after the file has already been written to disk. The need for an explicit, validated, and atomic path for mutating workflow frontmatter is the driving problem this PR addresses.

### Decision

We will add a new `gh aw edit` CLI command (`pkg/cli/edit_command.go`) that provides schema-validated, programmatic mutation of workflow frontmatter with automatic recompilation. The command accepts typed flag-based mutations (`--set`, `--unset`, `--add`, `--remove`, `--schedule`, `--add-import`, `--add-skill`) and a positional `path: value` shorthand. It validates the resulting frontmatter against the workflow schema before writing and immediately recompiles the `.lock.yml`; on compilation failure it rolls back both files to their prior state.

### Alternatives Considered

#### Alternative 1: Direct file editing + manual compile

Users continue to edit YAML frontmatter by hand and run `gh aw compile` separately. This requires no new code but provides no schema validation at edit time, allows invalid frontmatter to be committed before compile, and leaves the lock file in an inconsistent state when a user forgets to recompile. It was rejected because it does not address the safety problem.

#### Alternative 2: Extend `gh aw update` with frontmatter mutation flags

Add mutation flags to the existing `update` command. The `update` command is semantically about syncing a workflow from a `source:` declaration. Mixing configuration mutation into the same command would create a confusing API where the same command both fetches external content and edits local state. It was rejected to preserve the clarity of the existing command model.

### Consequences

#### Positive
- Schema validation runs before any bytes are written to disk, preventing invalid frontmatter values from ever reaching the repository.
- Automatic recompilation keeps `.lock.yml` atomically in sync with the edited workflow file.
- Transactional rollback: if recompilation fails, both the workflow source file and the lock file are restored to their pre-edit state.
- Fuzzy schedule expressions (`daily`, `every 6h`, `daily on weekdays`, etc.) are validated with the shared schedule parser at edit time, providing a user-friendly schedule API.

#### Negative
- Source-managed workflows (those with a `source:` key in their frontmatter) are explicitly rejected by the command; users must edit the upstream source or pin/unpin and then run `gh aw update` instead.
- YAML re-serialization via `go-yaml` may alter key ordering, indentation, comments, or whitespace in the frontmatter beyond the intended change, potentially producing noisy diffs. Edits that change nothing are detected and skip writing entirely, so no-op edits never rewrite a workflow.

#### Neutral
- The command is labelled "Experimental" in its `Short` and `Long` help text, signalling that its interface may change before stabilization.
- The command is registered in the `setup` group alongside `add`, `update`, and `remove`, maintaining consistency with the existing command taxonomy.

---

*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.*
Loading
Loading