32 KiB
Dynamic Step Manipulation & Workflow Export
Status: Draft
Author: GitHub Actions Team
Date: January 2026
Prerequisites: dap-debugging.md, dap-step-backwards.md (completed)
Progress Checklist
- Chunk 1: Command Parser & Infrastructure
- Chunk 2: Step Serializer (ActionStep → YAML)
- Chunk 3: Step Factory (Create new steps)
- Chunk 4: Step Manipulator (Queue operations)
- Chunk 5: REPL Commands (!step list, !step add run, !step edit, !step remove, !step move)
- Chunk 6: Action Download Integration (!step add uses)
- Chunk 7: Export Command (!step export)
- Chunk 8: JSON API for Browser Extension
- Chunk 9: Browser Extension UI
Overview
This plan extends the DAP debugger with the ability to dynamically manipulate job steps during a debug session: add new steps, edit pending steps, remove steps, and reorder them. At the end of a session, users can export the modified steps as YAML to paste into their workflow file.
This transforms the debugger from a "read-only inspection tool" into an interactive workflow editor — the key differentiator of this prototype.
Goals
- Primary: Enable add/edit/move/delete of job steps during debug session
- Primary: Support both
runandusesstep types - Primary: Export modified steps as valid YAML
- Secondary: Provide both REPL commands and JSON API for different clients
- Non-goal: Full workflow file reconstruction (steps section only)
- Non-goal: Production action restriction enforcement (noted for later)
Command API Specification
Grammar
!step <command> [target] [options]
Index Reference
- 1-based indexing for user-friendliness
- Completed steps are shown but read-only
- Cannot modify currently executing step (except via step-back)
Commands Summary
| Command | Purpose | Example |
|---|---|---|
!step list |
Show all steps | !step list --verbose |
!step add |
Add new step | !step add run "npm test" --after 3 |
!step edit |
Modify step | !step edit 4 --script "npm run test:ci" |
!step remove |
Delete step | !step remove 5 |
!step move |
Reorder step | !step move 5 --after 2 |
!step export |
Generate YAML | !step export --with-comments |
Position Modifiers
For !step add and !step move:
--at <index>— Insert at specific position--after <index>— Insert after step--before <index>— Insert before step--first— Insert at first pending position--last— Insert at end (default)
Full Command Reference
See "Command API Full Reference" section at end of document.
Implementation Chunks
Chunk 1: Command Parser & Infrastructure
Goal: Create the foundation for parsing and dispatching step commands.
Files to create:
src/Runner.Worker/Dap/StepCommands/StepCommandParser.cssrc/Runner.Worker/Dap/StepCommands/StepCommandResult.cs
Files to modify:
src/Runner.Worker/Dap/DapDebugSession.cs— Add command dispatch inHandleEvaluate()
Details:
-
StepCommandParser — Parse REPL command strings into structured commands:
public interface IStepCommandParser { StepCommand Parse(string input); // "!step add run \"echo hello\" --after 3" bool IsStepCommand(string input); // Starts with "!step" or is JSON with cmd:"step.*" } public abstract class StepCommand { } public class ListCommand : StepCommand { public bool Verbose; } public class AddRunCommand : StepCommand { public string Script; public string Name; public string Shell; public StepPosition Position; // ... } public class AddUsesCommand : StepCommand { /* ... */ } public class EditCommand : StepCommand { /* ... */ } public class RemoveCommand : StepCommand { public int Index; } public class MoveCommand : StepCommand { public int FromIndex; public StepPosition Position; } public class ExportCommand : StepCommand { public bool ChangesOnly; public bool WithComments; } -
StepPosition — Represent insertion position:
public class StepPosition { public PositionType Type { get; set; } // At, After, Before, First, Last public int? Index { get; set; } // For At, After, Before } -
StepCommandResult — Standardized response:
public class StepCommandResult { public bool Success { get; set; } public string Message { get; set; } public string Error { get; set; } // Error code public object Result { get; set; } // Command-specific data } -
Integration in DapDebugSession.HandleEvaluate():
// After checking for !debug command if (_stepCommandParser.IsStepCommand(expression)) { return await HandleStepCommandAsync(expression, executionContext); }
Testing:
- Unit tests for command parsing
- Test various edge cases (quoted strings, escapes, missing args)
Estimated effort: Small-medium
Chunk 2: Step Serializer (ActionStep → YAML)
Goal: Convert Pipelines.ActionStep objects to YAML string representation.
Files to create:
src/Runner.Worker/Dap/StepCommands/StepSerializer.cs
Details:
-
IStepSerializer interface:
public interface IStepSerializer { string ToYaml(Pipelines.ActionStep step); string ToYaml(IEnumerable<StepInfo> steps, bool withComments = false); } -
Handle both step types:
For
runsteps (ScriptReference):- name: Run Tests run: | npm ci npm test shell: bash working-directory: src env: NODE_ENV: test if: success() continue-on-error: false timeout-minutes: 10For
usessteps (RepositoryPathReference):- name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' cache: npm env: NODE_OPTIONS: --max-old-space-size=4096 if: success() -
Extract data from ActionStep:
Referencetype determines run vs usesInputsTemplateToken contains script (for run) or with values (for uses)EnvironmentTemplateToken contains env varsConditionstring contains if expressionDisplayNameorDisplayNameTokenfor name
-
Use existing YAML infrastructure:
- Consider using
YamlObjectWriterfrom WorkflowParser - Or use a simple string builder for more control
- Consider using
Testing:
- Round-trip tests: create step → serialize → verify YAML
- Test all step properties
- Test multi-line scripts
Estimated effort: Medium
Chunk 3: Step Factory (Create new steps)
Goal: Create Pipelines.ActionStep and IActionRunner objects at runtime.
Files to create:
src/Runner.Worker/Dap/StepCommands/StepFactory.cs
Details:
-
IStepFactory interface:
public interface IStepFactory : IRunnerService { Pipelines.ActionStep CreateRunStep( string script, string name = null, string shell = null, string workingDirectory = null, Dictionary<string, string> env = null, string condition = null, bool continueOnError = false, int? timeoutMinutes = null); Pipelines.ActionStep CreateUsesStep( string actionReference, // "owner/repo@ref" string name = null, Dictionary<string, string> with = null, Dictionary<string, string> env = null, string condition = null, bool continueOnError = false, int? timeoutMinutes = null); IActionRunner WrapInRunner( Pipelines.ActionStep step, IExecutionContext jobContext, ActionRunStage stage = ActionRunStage.Main); } -
CreateRunStep implementation:
public Pipelines.ActionStep CreateRunStep(...) { var step = new Pipelines.ActionStep { Id = Guid.NewGuid(), Name = $"_dynamic_{Guid.NewGuid():N}", DisplayName = name ?? "Run script", Reference = new ScriptReference(), Condition = condition ?? "success()", ContinueOnError = CreateBoolToken(continueOnError), TimeoutInMinutes = CreateIntToken(timeoutMinutes) }; // Build Inputs mapping with script, shell, working-directory step.Inputs = CreateRunInputs(script, shell, workingDirectory); // Build Environment mapping if (env?.Count > 0) step.Environment = CreateEnvToken(env); return step; } -
CreateUsesStep implementation:
public Pipelines.ActionStep CreateUsesStep(string actionReference, ...) { var (name, ref_, path) = ParseActionReference(actionReference); var step = new Pipelines.ActionStep { Id = Guid.NewGuid(), Name = $"_dynamic_{Guid.NewGuid():N}", DisplayName = displayName ?? actionReference, Reference = new RepositoryPathReference { Name = name, // "actions/checkout" Ref = ref_, // "v4" Path = path, // null or "subdir" RepositoryType = "GitHub" }, Condition = condition ?? "success()" }; // Build with inputs if (with?.Count > 0) step.Inputs = CreateWithInputs(with); return step; } -
ParseActionReference helper:
actions/checkout@v4→ name=actions/checkout, ref=v4, path=nullactions/setup-node@v4→ name=actions/setup-node, ref=v4owner/repo/subdir@ref→ name=owner/repo, ref=ref, path=subdirdocker://alpine:latest→ ContainerRegistryReference instead
-
WrapInRunner: Create IActionRunner from ActionStep (copy pattern from JobExtension.cs):
public IActionRunner WrapInRunner(Pipelines.ActionStep step, IExecutionContext jobContext, ActionRunStage stage) { var runner = HostContext.CreateService<IActionRunner>(); runner.Action = step; runner.Stage = stage; runner.Condition = step.Condition; runner.ExecutionContext = jobContext.CreateChild( Guid.NewGuid(), step.DisplayName, step.Name ); return runner; }
Testing:
- Create run step → verify all properties
- Create uses step → verify reference parsing
- Test edge cases (missing shell, local actions, docker actions)
Estimated effort: Medium
Chunk 4: Step Manipulator (Queue operations)
Goal: Manipulate the job step queue (add, remove, move, track changes).
Files to create:
src/Runner.Worker/Dap/StepCommands/StepManipulator.cssrc/Runner.Worker/Dap/StepCommands/StepInfo.cssrc/Runner.Worker/Dap/StepCommands/StepChange.cs
Details:
-
StepInfo — Unified step representation:
public class StepInfo { public int Index { get; set; } public string Name { get; set; } public string Type { get; set; } // "run" or "uses" public string TypeDetail { get; set; } // action ref or script preview public StepStatus Status { get; set; } // Completed, Current, Pending public ChangeType? Change { get; set; } // Added, Modified, null public Pipelines.ActionStep Action { get; set; } public IStep Step { get; set; } } public enum StepStatus { Completed, Current, Pending } public enum ChangeType { Added, Modified, Removed, Moved } -
StepChange — Track modifications:
public class StepChange { public ChangeType Type { get; set; } public int OriginalIndex { get; set; } public StepInfo OriginalStep { get; set; } public StepInfo ModifiedStep { get; set; } } -
IStepManipulator interface:
public interface IStepManipulator : IRunnerService { // Initialize with job context void Initialize(IExecutionContext jobContext, int currentStepIndex); void UpdateCurrentIndex(int index); // Query IReadOnlyList<StepInfo> GetAllSteps(); StepInfo GetStep(int index); int GetPendingCount(); int GetFirstPendingIndex(); // Mutate int InsertStep(IStep step, StepPosition position); void RemoveStep(int index); void MoveStep(int fromIndex, StepPosition position); void EditStep(int index, Action<Pipelines.ActionStep> edit); // Change tracking IReadOnlyList<StepChange> GetChanges(); void RecordOriginalState(); // Call at session start } -
Implementation details:
- Maintain list of completed steps (from checkpoints/history)
- Access
jobContext.JobStepsqueue for pending steps - Track all modifications for export diff
- Validate indices before operations
-
Queue manipulation:
public int InsertStep(IStep step, StepPosition position) { // Convert queue to list var pending = _jobContext.JobSteps.ToList(); _jobContext.JobSteps.Clear(); // Calculate insertion index int insertAt = CalculateInsertIndex(position, pending.Count); // Insert pending.Insert(insertAt, step); // Re-queue foreach (var s in pending) _jobContext.JobSteps.Enqueue(s); // Track change _changes.Add(new StepChange { Type = ChangeType.Added, ... }); return _currentIndex + insertAt + 1; // Return 1-based index }
Testing:
- Insert at various positions
- Remove and verify queue state
- Move operations
- Change tracking accuracy
Estimated effort: Medium
Chunk 5: REPL Commands (run steps)
Goal: Implement !step list, !step add run, !step edit, !step remove, !step move.
Files to modify:
src/Runner.Worker/Dap/DapDebugSession.cs— AddHandleStepCommandAsync()
Files to create:
src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs
Details:
-
IStepCommandHandler interface:
public interface IStepCommandHandler : IRunnerService { Task<StepCommandResult> HandleAsync(StepCommand command, IExecutionContext context); } -
Command implementations:
List:
case ListCommand list: var steps = _manipulator.GetAllSteps(); var output = FormatStepList(steps, list.Verbose); return new StepCommandResult { Success = true, Result = steps, Message = output };Add run:
case AddRunCommand add: var actionStep = _factory.CreateRunStep( add.Script, add.Name, add.Shell, add.WorkingDirectory, add.Env, add.Condition, add.ContinueOnError, add.Timeout); var runner = _factory.WrapInRunner(actionStep, context); var index = _manipulator.InsertStep(runner, add.Position); return new StepCommandResult { Success = true, Message = $"Step added at position {index}", Result = new { index, step = GetStepInfo(runner) } };Edit:
case EditCommand edit: ValidatePendingIndex(edit.Index); _manipulator.EditStep(edit.Index, step => { if (edit.Script != null) UpdateScript(step, edit.Script); if (edit.Name != null) step.DisplayName = edit.Name; if (edit.Condition != null) step.Condition = edit.Condition; // ... other fields }); return new StepCommandResult { Success = true, Message = $"Step {edit.Index} updated" };Remove:
case RemoveCommand remove: ValidatePendingIndex(remove.Index); _manipulator.RemoveStep(remove.Index); return new StepCommandResult { Success = true, Message = $"Step {remove.Index} removed" };Move:
case MoveCommand move: ValidatePendingIndex(move.FromIndex); var newIndex = _manipulator.MoveStep(move.FromIndex, move.Position); return new StepCommandResult { Success = true, Message = $"Step moved to position {newIndex}" }; -
Integration in DapDebugSession:
private async Task<Response> HandleStepCommandAsync(string expression, IExecutionContext context) { try { var command = _commandParser.Parse(expression); var result = await _commandHandler.HandleAsync(command, context); if (result.Success) return CreateSuccessResponse(new EvaluateResponseBody { Result = result.Message, Type = "string" }); else return CreateErrorResponse($"{result.Error}: {result.Message}"); } catch (StepCommandException ex) { return CreateErrorResponse(ex.Message); } }
Testing:
- End-to-end: add step → list → verify
- Edit various properties
- Remove and verify indices shift
- Move operations
Estimated effort: Medium-large
Chunk 6: Action Download Integration (!step add uses)
Goal: Support !step add uses with full action download.
Files to modify:
src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs
Details:
-
Add uses command handling:
case AddUsesCommand add: // Create the step var actionStep = _factory.CreateUsesStep( add.Action, add.Name, add.With, add.Env, add.Condition, add.ContinueOnError, add.Timeout); // Download the action (this is the key difference from run steps) var actionManager = HostContext.GetService<IActionManager>(); var prepareResult = await actionManager.PrepareActionsAsync( context, new[] { actionStep } ); // Check for pre-steps (some actions have setup steps) if (prepareResult.PreStepTracker.TryGetValue(actionStep.Id, out var preStep)) { // Insert pre-step before main step var preRunner = _factory.WrapInRunner(preStep.Action, context, ActionRunStage.Pre); _manipulator.InsertStep(preRunner, add.Position); } // Wrap and insert main step var runner = _factory.WrapInRunner(actionStep, context); var index = _manipulator.InsertStep(runner, CalculateMainPosition(add.Position, hasPreStep)); // Handle post-steps (cleanup) // These go to PostJobSteps stack, handled by existing infrastructure return new StepCommandResult { Success = true, Message = $"Action '{add.Action}' added at position {index}", Result = new { index, step = GetStepInfo(runner) } }; -
Error handling:
- Action not found → clear error message with action name
- Network failure → suggest retry
- Invalid action reference format → parse error
-
Production note (for future):
// TODO: Before production release, add action restriction checks: // - Verify action is in organization's allowed list // - Check verified creator requirements // - Enforce enterprise policies // For now, allow all actions in prototype
Testing:
- Add common actions (checkout, setup-node)
- Test actions with pre/post steps
- Test local actions (./.github/actions/...)
- Test docker actions (docker://...)
- Error cases: invalid action, network failure
Estimated effort: Medium
Chunk 7: Export Command (!step export)
Goal: Generate YAML output for modified steps.
Files to modify:
src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs
Details:
-
Export command handling:
case ExportCommand export: var steps = _manipulator.GetAllSteps(); var changes = _manipulator.GetChanges(); IEnumerable<StepInfo> toExport; if (export.ChangesOnly) { toExport = steps.Where(s => s.Change != null); } else { toExport = steps; } var yaml = _serializer.ToYaml(toExport, export.WithComments); return new StepCommandResult { Success = true, Message = yaml, Result = new { yaml, totalSteps = steps.Count, addedCount = changes.Count(c => c.Type == ChangeType.Added), modifiedCount = changes.Count(c => c.Type == ChangeType.Modified) } }; -
YAML output format:
steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node # ADDED uses: actions/setup-node@v4 with: node-version: '20' - name: Run Tests # MODIFIED run: | npm ci npm test shell: bash -
Change comments (when --with-comments):
# ADDEDfor new steps# MODIFIEDfor edited steps- Removed steps listed at bottom as comments (optional)
Testing:
- Export with no changes → valid YAML
- Export with additions → ADDED comments
- Export with modifications → MODIFIED comments
- Verify YAML is valid and can be pasted into workflow
Estimated effort: Small
Chunk 8: JSON API for Browser Extension
Goal: Add JSON command support for programmatic access.
Files to modify:
src/Runner.Worker/Dap/StepCommands/StepCommandParser.cssrc/Runner.Worker/Dap/DapDebugSession.cs
Details:
-
Detect JSON input:
public bool IsStepCommand(string input) { var trimmed = input.Trim(); return trimmed.StartsWith("!step") || (trimmed.StartsWith("{") && trimmed.Contains("\"cmd\"") && trimmed.Contains("\"step.")); } -
Parse JSON commands:
public StepCommand Parse(string input) { var trimmed = input.Trim(); if (trimmed.StartsWith("{")) return ParseJsonCommand(trimmed); else return ParseReplCommand(trimmed); } private StepCommand ParseJsonCommand(string json) { var obj = JObject.Parse(json); var cmd = obj["cmd"]?.ToString(); return cmd switch { "step.list" => new ListCommand { Verbose = obj["verbose"]?.Value<bool>() ?? false }, "step.add" => ParseJsonAddCommand(obj), "step.edit" => ParseJsonEditCommand(obj), "step.remove" => new RemoveCommand { Index = obj["index"].Value<int>() }, "step.move" => ParseJsonMoveCommand(obj), "step.export" => new ExportCommand { ChangesOnly = obj["changesOnly"]?.Value<bool>() ?? false, WithComments = obj["withComments"]?.Value<bool>() ?? false }, _ => throw new StepCommandException($"Unknown command: {cmd}") }; } -
JSON response format:
// For JSON input, return structured JSON response if (wasJsonInput) { return CreateSuccessResponse(new EvaluateResponseBody { Result = JsonConvert.SerializeObject(result), Type = "json" }); }
Testing:
- All commands via JSON
- Verify JSON responses are parseable
- Test error responses
Estimated effort: Small-medium
Chunk 9: Browser Extension UI
Goal: Add step manipulation UI to the browser extension.
Files to modify:
browser-ext/content/content.jsbrowser-ext/content/content.cssbrowser-ext/background/background.js
Details:
-
Steps Panel in Debugger Pane:
<div class="dap-steps-panel"> <div class="dap-steps-header"> <span>Steps</span> <button class="dap-add-step-btn">+ Add</button> </div> <div class="dap-steps-list"> <!-- Steps rendered here --> </div> <div class="dap-steps-footer"> <button class="dap-export-btn">Export Changes</button> </div> </div> -
Step List Rendering:
function renderSteps(steps) { return steps.map(step => ` <div class="dap-step ${step.status}" data-index="${step.index}"> <span class="dap-step-status">${getStatusIcon(step.status)}</span> <span class="dap-step-index">${step.index}.</span> <span class="dap-step-name">${step.name}</span> ${step.change ? `<span class="dap-step-badge">[${step.change}]</span>` : ''} <span class="dap-step-type">${step.type}</span> ${step.status === 'pending' ? renderStepActions(step) : ''} </div> `).join(''); } -
Add Step Dialog:
function showAddStepDialog() { // Modal with: // - Type selector: run / uses // - For run: script textarea, shell dropdown // - For uses: action input with autocomplete // - Common: name, if condition, env vars // - Position: dropdown (after current, at end, at position) } -
Step Context Menu:
function showStepContextMenu(stepIndex, event) { // Edit, Move Up, Move Down, Delete } -
Export Modal:
function showExportModal(yaml) { // Code view with syntax highlighting // Copy to clipboard button } -
Send commands via JSON API:
async function addStep(type, options) { const cmd = { cmd: 'step.add', type, ...options }; const response = await sendEvaluate(JSON.stringify(cmd)); refreshStepList(); }
Testing:
- Manual testing in Chrome
- All UI operations work correctly
- Responsive layout
Estimated effort: Medium-large
File Summary
New Files
| File | Chunk | Purpose |
|---|---|---|
StepCommands/StepCommandParser.cs |
1 | Parse REPL and JSON commands |
StepCommands/StepCommandResult.cs |
1 | Standardized command responses |
StepCommands/StepSerializer.cs |
2 | ActionStep → YAML conversion |
StepCommands/StepFactory.cs |
3 | Create new ActionStep objects |
StepCommands/StepManipulator.cs |
4 | Queue operations & change tracking |
StepCommands/StepInfo.cs |
4 | Step info data structure |
StepCommands/StepChange.cs |
4 | Change tracking data structure |
StepCommands/StepCommandHandler.cs |
5 | Command execution logic |
Modified Files
| File | Chunk | Changes |
|---|---|---|
DapDebugSession.cs |
1, 5 | Add command dispatch, wire up services |
content.js |
9 | Steps panel, dialogs, export modal |
content.css |
9 | Styling for new UI elements |
background.js |
9 | Helper functions if needed |
Command API Full Reference
!step list
!step list [--verbose]
Show all steps with their indices, status, and modification state.
Output format:
Steps:
✓ 1. Checkout uses actions/checkout@v4
✓ 2. Setup Node uses actions/setup-node@v4
▶ 3. Install deps run npm ci
4. Run tests [MODIFIED] run npm test
5. Build [ADDED] run npm run build
6. Deploy run ./deploy.sh
Legend: ✓ = completed, ▶ = current/paused, [ADDED] = new, [MODIFIED] = edited
JSON:
{"cmd": "step.list", "verbose": false}
!step add
Run step:
!step add run "<script>" [options]
Options:
--name "<name>" Display name
--shell <shell> Shell (bash, sh, pwsh, etc.)
--working-directory <path> Working directory
--if "<condition>" Condition expression
--env KEY=value Environment variable (repeatable)
--continue-on-error Don't fail job on step failure
--timeout <minutes> Step timeout
--at <index> Insert at position
--after <index> Insert after step
--before <index> Insert before step
--first Insert at first pending position
--last Insert at end (default)
Uses step:
!step add uses <action> [options]
Options:
--name "<name>" Display name
--with key=value Input parameter (repeatable)
--if "<condition>" Condition expression
--env KEY=value Environment variable (repeatable)
--continue-on-error Don't fail job on step failure
--timeout <minutes> Step timeout
[position options same as run]
JSON (run):
{
"cmd": "step.add",
"type": "run",
"script": "npm test",
"name": "Run Tests",
"shell": "bash",
"workingDirectory": "src",
"if": "success()",
"env": {"NODE_ENV": "test"},
"continueOnError": false,
"timeout": 10,
"position": {"after": 3}
}
JSON (uses):
{
"cmd": "step.add",
"type": "uses",
"action": "actions/setup-node@v4",
"name": "Setup Node",
"with": {"node-version": "20"},
"env": {},
"if": "success()",
"position": {"at": 2}
}
Position object options:
{"at": 3} // Insert at position 3
{"after": 2} // Insert after step 2
{"before": 4} // Insert before step 4
{"first": true} // Insert at first pending position
{"last": true} // Insert at end (default if omitted)
!step edit
!step edit <index> [modifications]
Modifications:
--name "<name>" Change display name
--script "<script>" Change script (run only)
--action "<action>" Change action (uses only)
--shell <shell> Change shell (run only)
--working-directory <path> Change working directory
--if "<condition>" Change condition
--with key=value Set/update input (uses only)
--env KEY=value Set/update env var
--remove-with <key> Remove input
--remove-env <KEY> Remove env var
--continue-on-error Enable continue-on-error
--no-continue-on-error Disable continue-on-error
--timeout <minutes> Change timeout
JSON:
{
"cmd": "step.edit",
"index": 4,
"script": "npm run test:ci",
"name": "CI Tests",
"with": {"node-version": "22"},
"removeWith": ["cache"],
"env": {"CI": "true"},
"removeEnv": ["DEBUG"]
}
!step remove
!step remove <index>
JSON:
{"cmd": "step.remove", "index": 5}
!step move
!step move <from> <position>
Position (one required):
--to <index> Move to position
--after <index> Move after step
--before <index> Move before step
--first Move to first pending position
--last Move to end
JSON:
{"cmd": "step.move", "from": 5, "position": {"after": 2}}
!step export
!step export [--changes-only] [--with-comments]
JSON:
{"cmd": "step.export", "changesOnly": false, "withComments": true}
Validation Rules
- Index bounds: Must be 1 ≤ index ≤ total_steps
- Completed steps: Cannot edit, remove, or move completed steps
- Current step: Cannot remove or move the currently executing step (can edit for next run if step-back)
- Position conflicts:
--at,--after,--before,--first,--lastare mutually exclusive - Type-specific options:
--script,--shell,--working-directoryonly for run;--action,--withonly for uses - Required values:
--at,--after,--before,--torequire an index value
Error Codes
| Code | Description |
|---|---|
INVALID_INDEX |
Index out of range or refers to completed/current step |
INVALID_COMMAND |
Unknown command |
INVALID_OPTION |
Unknown or conflicting options |
INVALID_TYPE |
Invalid step type (not "run" or "uses") |
ACTION_DOWNLOAD_FAILED |
Failed to download action for uses step |
PARSE_ERROR |
Failed to parse command/JSON |
Future Commands (Reserved)
These command names are reserved for future implementation:
!step duplicate <index> # Clone a step
!step enable <index> # Re-enable a disabled step
!step disable <index> # Skip step without removing
!step inspect <index> # Show detailed step info
!step reset <index> # Revert modifications
!step import # Add steps from YAML
Notes for Production
-
Action Restrictions: Before production, must integrate with organization/enterprise action policies (allowed lists, verified creators, etc.)
-
Security: Dynamic step execution has security implications - ensure proper sandboxing and audit logging
-
Persistence: Consider whether modifications should persist across step-back operations
-
Undo: Consider adding
!step undofor reverting last operation