chore(openspec): drop 9 superseded proposals + 11 stub archive files

Drop 9 batch proposals that are superseded by the boocode-lift-analysis
(boocontext-audit, conductor upgrades, self-healing/verify-gate skills):
add-3tier-memory, import-llm-evaluator, import-pregel-engine, plugin-platform,
conductor-evolution, code-intelligence-upgrade, dev-workflow, ui-overhaul,
agent-reliability.

Delete 11 stub archive files (49-66B each, 'Status: Shipped. Archived.' only)
that provide zero documentation value over the existing CHANGELOG.md + git tags.
This commit is contained in:
2026-06-07 22:15:38 +00:00
parent 0d6e9a2413
commit c935687725
119 changed files with 4897 additions and 45 deletions

View File

@@ -0,0 +1,76 @@
## ADDED Requirements
### Requirement: Workflow listing
The CLI SHALL provide a `list` command that displays all discovered workflows and their descriptions.
#### Scenario: List workflows
- **WHEN** `ion list` is run
- **THEN** all discovered workflows SHALL be listed with name, description, and source (bundled/project)
### Requirement: Workflow execution
The CLI SHALL provide a `run` command that executes a workflow by name with optional arguments.
#### Scenario: Run workflow with message
- **WHEN** `ion run analyze "analyze the codebase"` is run
- **THEN** the `analyze` workflow SHALL execute with the provided user message
#### Scenario: Run in specific directory
- **WHEN** `ion run build --cwd /path/to/project` is run
- **THEN** the workflow SHALL use the specified working directory
#### Scenario: Run with specific store
- **WHEN** `ion run deploy --store sqlite --db-path ./ion.db` is run
- **THEN** the specified store backend SHALL be used
### Requirement: Workflow approval commands
The CLI SHALL provide `approve` and `reject` commands for responding to approval gates.
#### Scenario: Approve a paused workflow
- **WHEN** `ion approve <run-id>` is run
- **THEN** the workflow SHALL resume from the paused approval node
#### Scenario: Approve with comment
- **WHEN** `ion approve <run-id> "looks good"` is run
- **THEN** the comment SHALL be recorded and available as `$nodeId.output`
#### Scenario: Reject with reason
- **WHEN** `ion reject <run-id> "needs changes"` is run
- **THEN** `$REJECTION_REASON` SHALL be set to "needs changes"
- **THEN** if `on_reject` is configured, the handler SHALL execute
### Requirement: Workflow run management
The CLI SHALL provide `status`, `runs`, `resume`, `abandon`, and `cleanup` commands.
#### Scenario: Show running workflows
- **WHEN** `ion status` is run
- **THEN** all active (running + paused) workflow runs SHALL be displayed
#### Scenario: List recent runs
- **WHEN** `ion runs` is run
- **THEN** recent workflow runs SHALL be listed with status and timestamps
#### Scenario: Resume failed run
- **WHEN** `ion resume <run-id>` is run
- **THEN** the failed run SHALL be resumed, skipping completed nodes
#### Scenario: Abandon run
- **WHEN** `ion abandon <run-id>` is run
- **THEN** the run SHALL be marked as cancelled
#### Scenario: Cleanup old runs
- **WHEN** `ion cleanup` is run (default 7 days)
- **THEN** runs older than the retention period SHALL have their artifacts removed
### Requirement: SOP-to-YAML conversion
The CLI SHALL provide a `convert` command to transpile `.sop.md` files to `.yaml`.
#### Scenario: Convert SOP to YAML
- **WHEN** `ion convert workflow.sop.md` is run
- **THEN** a `workflow.yaml` SHALL be written with the equivalent DAG representation
### Requirement: Machine-readable output
Workflow commands SHALL support `--json` flag for machine-readable output.
#### Scenario: JSON output for automation
- **WHEN** `ion list --json` is run
- **THEN** output SHALL be valid JSON array of workflow objects

View File

@@ -0,0 +1,54 @@
## ADDED Requirements
### Requirement: DAG topological execution
The engine SHALL execute workflow nodes in topological order determined by `depends_on` edges using Kahn's algorithm.
#### Scenario: Independent nodes run concurrently
- **WHEN** a workflow has nodes A and B with no `depends_on`
- **THEN** both A and B SHALL execute in the same topological layer, concurrently
#### Scenario: Dependent nodes run sequentially
- **WHEN** node B lists `depends_on: [A]`
- **THEN** A SHALL complete before B begins
#### Scenario: Cycle detection
- **WHEN** nodes form a cycle (A → B → C → A)
- **THEN** the loader SHALL reject the workflow with a cycle detection error
### Requirement: Trigger rules
The engine SHALL support 4 trigger rules for join semantics.
#### Scenario: all_success (default)
- **WHEN** a node has multiple upstream dependencies and no explicit `trigger_rule`
- **THEN** it SHALL only run if ALL upstream nodes completed successfully
- **THEN** it SHALL be skipped if any upstream node failed
#### Scenario: one_success
- **WHEN** a node sets `trigger_rule: one_success`
- **THEN** it SHALL run if at least one upstream node completed successfully
#### Scenario: all_done
- **WHEN** a node sets `trigger_rule: all_done`
- **THEN** it SHALL run when all upstream nodes have finished (any status), regardless of success/failure
#### Scenario: none_failed_min_one_success
- **WHEN** a node sets `trigger_rule: none_failed_min_one_success`
- **THEN** it SHALL run only if no upstream node failed AND at least one succeeded
### Requirement: when conditions
Nodes SHALL support a `when:` string that evaluates to a boolean condition.
#### Scenario: when condition prevents execution
- **WHEN** a node has `when: "false"` or any expression that evaluates falsy
- **THEN** the node SHALL be skipped as if its trigger_rule prevented execution
### Requirement: Node retry with configurable policy
Nodes SHALL support a `retry` config with `max_attempts`, `delay_ms`, and `on_error` (transient|all).
#### Scenario: retry on transient error
- **WHEN** a node with `retry: { max_attempts: 3 }` fails with a transient error
- **THEN** it SHALL retry up to 3 times with configured delay between attempts
#### Scenario: retry exhausted
- **WHEN** all retry attempts fail
- **THEN** the node SHALL be marked as failed and trigger_rule evaluation proceeds

View File

@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: Append-only event log
Workflow runs SHALL produce append-only event records. Events SHALL NOT be modified after creation.
#### Scenario: Events are chronological
- **WHEN** a workflow executes
- **THEN** events SHALL be stored with monotonically increasing timestamps or sequence numbers
- **THEN** event order SHALL match execution order
#### Scenario: Events are immutable
- **WHEN** an event has been persisted
- **THEN** it SHALL NOT be updated or deleted
### Requirement: Event types
The event log SHALL support exactly 8 event types: `workflow_started`, `workflow_completed`, `workflow_failed`, `workflow_cancelled`, `node_started`, `node_completed`, `node_failed`, `node_skipped`.
#### Scenario: Workflow lifecycle events
- **WHEN** a workflow run begins
- **THEN** a `workflow_started` event SHALL be recorded
- **WHEN** a workflow run completes successfully
- **THEN** a `workflow_completed` event SHALL be recorded
- **WHEN** a workflow run fails
- **THEN** a `workflow_failed` event SHALL be recorded
#### Scenario: Node lifecycle events
- **WHEN** a node begins execution
- **THEN** a `node_started` event SHALL be recorded
- **WHEN** a node completes successfully
- **THEN** a `node_completed` event SHALL record the node's output
- **WHEN** a node fails
- **THEN** a `node_failed` event SHALL record the error
- **WHEN** a node is skipped (trigger_rule not met)
- **THEN** a `node_skipped` event SHALL be recorded
### Requirement: Deterministic replay for crash recovery
When a workflow run is resumed after an interruption, the engine SHALL load completed node outputs from the event log and skip re-execution of completed nodes.
#### Scenario: Resume skips completed nodes
- **WHEN** a workflow run is resumed after a crash
- **THEN** all nodes with a `node_completed` event SHALL be skipped
- **THEN** execution SHALL begin from the first node without a completed event
#### Scenario: Resume after partial execution
- **WHEN** a workflow had 5 nodes and the first 3 completed before the crash
- **THEN** nodes 1-3 SHALL be skipped (outputs loaded from event log)
- **THEN** node 4 SHALL be re-executed
### Requirement: Event storage via plugable backend
Events SHALL be persisted through the `IWorkflowStore` interface, with at least a filesystem backend.
#### Scenario: Filesystem event store
- **WHEN** using the filesystem backend
- **THEN** each run SHALL have a JSON file at `{runId}/events.jsonl`
- **THEN** events SHALL be appended as newline-delimited JSON
#### Scenario: SQLite event store
- **WHEN** using the SQLite backend
- **THEN** events SHALL be stored in a `workflow_events` table with columns for run_id, sequence, event_type, timestamp, and payload

View File

@@ -0,0 +1,37 @@
## ADDED Requirements
### Requirement: Approval gate pauses execution
An ApprovalNode SHALL pause workflow execution and send a message for human review. Execution SHALL only continue when the user approves or rejects.
#### Scenario: Approval pauses workflow
- **WHEN** an approval node executes
- **THEN** the workflow status SHALL transition to `paused`
- **THEN** a message SHALL be sent with the approval message text
#### Scenario: Approve resumes execution
- **WHEN** the user approves a paused workflow
- **THEN** the workflow SHALL resume with the next node in the DAG
#### Scenario: Reject fails the node
- **WHEN** the user rejects a paused workflow
- **THEN** the node SHALL be marked as failed
- **THEN** downstream nodes SHALL evaluate their trigger rules
### Requirement: Capture response from approval
An approval node MAY support `capture_response: true` to store the user's comment as `$nodeId.output`.
#### Scenario: Approval with captured response
- **WHEN** an approval node has `capture_response: true` and the user provides a comment during approval
- **THEN** the comment SHALL be stored as the node's output, available via `$nodeId.output`
### Requirement: On-reject retry
An approval node MAY specify `on_reject` with a `prompt` and optional `max_attempts` for re-presenting after rejection.
#### Scenario: Reject with retry prompt
- **WHEN** an approval node has `on_reject: { prompt: "..." }` and the user rejects
- **THEN** the on_reject prompt SHALL be executed (typically the AI revises based on feedback)
- **THEN** the approval gate SHALL be re-presented to the user
#### Scenario: Max attempts exceeded
- **WHEN** the number of rejections exceeds `on_reject.max_attempts`
- **THEN** the node SHALL fail permanently

View File

@@ -0,0 +1,44 @@
## ADDED Requirements
### Requirement: Programmatic execution
The engine SHALL export an `executeWorkflow()` function that accepts a workflow definition, store, and options.
#### Scenario: Execute workflow from code
- **WHEN** a host application calls `executeWorkflow(workflowDef, store, { userMessage: "..." })`
- **THEN** the workflow SHALL execute and return a `WorkflowExecutionResult`
### Requirement: Workflow parsing
The engine SHALL export `parseWorkflow(yaml: string): WorkflowDefinition` and `parseWorkflowFile(path: string): WorkflowDefinition` functions.
#### Scenario: Parse YAML string
- **WHEN** a host application calls `parseWorkflow(yamlString)`
- **THEN** it SHALL return a validated `WorkflowDefinition`
#### Scenario: Parse YAML file
- **WHEN** a host application calls `parseWorkflowFile("./workflows/my-workflow.yaml")`
- **THEN** it SHALL read and parse the file, returning a validated `WorkflowDefinition`
### Requirement: Workflow discovery
The engine SHALL export `discoverWorkflows(cwd: string): WorkflowLoadResult` for finding workflows in the filesystem.
#### Scenario: Discover workflows
- **WHEN** a host application calls `discoverWorkflows(cwd)`
- **THEN** it SHALL return all discovered workflows from the project's `.archon/workflows/` directory
### Requirement: Store constructors
The engine SHALL export store constructors for each backend: `createFsStore(path)`, `createSqliteStore(path)`, `createPostgresStore(connectionString)`.
#### Scenario: Create filesystem store
- **WHEN** a host application calls `createFsStore("./data")`
- **THEN** it SHALL return an initialized `IWorkflowStore` using the filesystem backend
#### Scenario: Create SQLite store
- **WHEN** a host application calls `createSqliteStore("./ion.db")`
- **THEN** it SHALL return an initialized `IWorkflowStore` using SQLite
### Requirement: TypeScript types
All public APIs SHALL export full TypeScript type definitions.
#### Scenario: Types available
- **WHEN** a host application imports from the package
- **THEN** `WorkflowDefinition`, `DagNode`, `NodeOutput`, `WorkflowRun`, `WorkflowExecutionResult`, `IWorkflowStore` types SHALL all be exported

View File

@@ -0,0 +1,34 @@
## ADDED Requirements
### Requirement: SOP markdown as secondary format
Workflows MAY be defined as `.sop.md` files in addition to YAML. The engine SHALL detect `.sop.md` files during discovery and transpile them to the DAG node representation.
#### Scenario: SOP file discovered alongside YAML
- **WHEN** a `.sop.md` file exists in the workflows directory alongside `.yaml` workflow files
- **THEN** both SHALL be discovered and listed as available workflows
#### Scenario: SOP transpiled to prompt nodes
- **WHEN** a `.sop.md` file is loaded
- **THEN** each `## Steps` section item SHALL become a `prompt:` node
- **THEN** `## Parameters` SHALL be extracted as node metadata
### Requirement: RFC 2119 constraint extraction
The transpiler SHALL extract RFC 2119 constraints from `**Constraints:**` blocks and embed them in the prompt text of the corresponding node.
#### Scenario: Constraints included in prompt
- **WHEN** a step has `**Constraints:** - You MUST do X`
- **THEN** the constraint text SHALL be appended to the node's prompt
### Requirement: Overview as workflow description
The `## Overview` section of a `.sop.md` file SHALL become the workflow's `description` field.
#### Scenario: Overview maps to description
- **WHEN** a `.sop.md` has `## Overview\nThis SOP does X`
- **THEN** the resulting workflow SHALL have `description: "This SOP does X"`
### Requirement: Parameter acquisition constraints
The transpiler SHALL validate that all required parameters from `## Parameters` are present before execution, using the constraint pattern from the SOP.
#### Scenario: Missing required parameter
- **WHEN** a required parameter has no value provided
- **THEN** the workflow SHALL prompt the user for the missing parameter before executing

View File

@@ -0,0 +1,44 @@
## ADDED Requirements
### Requirement: Inline script execution
Script nodes SHALL execute inline TypeScript (runtime: `bun`) or Python (runtime: `uv`) code and capture stdout as the node output.
#### Scenario: Bun inline execution
- **WHEN** a script node has `runtime: bun` and `script: console.log("hello")`
- **THEN** the executor SHALL run the script via `bun -e`
- **THEN** stdout SHALL be captured as `$nodeId.output`
#### Scenario: Python inline execution
- **WHEN** a script node has `runtime: uv` and `script: print("hello")`
- **THEN** the executor SHALL run the script via `uv run python -c`
- **THEN** stdout SHALL be captured as `$nodeId.output`
### Requirement: Dependency installation
Script nodes SHALL support a `deps:` array that installs dependencies before execution.
#### Scenario: Bun with npm deps
- **WHEN** a script node has `runtime: bun` and `deps: ["lodash", "zod"]`
- **THEN** the executor SHALL run `bun install lodash zod` before executing
#### Scenario: Python with pip deps
- **WHEN** a script node has `runtime: uv` and `deps: ["requests", "click"]`
- **THEN** the executor SHALL run `uv pip install requests click` before executing
### Requirement: Named script files
Script nodes MAY reference named scripts from a `.archon/scripts/` directory by name instead of inline code.
#### Scenario: Named script discovery
- **WHEN** a script node has `script: analyze` and `scripts/analyze.ts` exists
- **THEN** the executor SHALL load and execute the file
#### Scenario: Runtime inferred from extension
- **WHEN** a script has `runtime: bun` and the named file has a `.ts` extension
- **THEN** the executor SHALL run it via `bun run`
### Requirement: Script timeout
Script nodes SHALL support a `timeout:` field in milliseconds. If execution exceeds the timeout, the process SHALL be killed and the node SHALL fail.
#### Scenario: Timeout exceeded
- **WHEN** a script node sets `timeout: 5000` and the script runs for 10 seconds
- **THEN** the process SHALL be killed after 5 seconds
- **THEN** the node SHALL be marked as failed with a timeout error

View File

@@ -0,0 +1,48 @@
## ADDED Requirements
### Requirement: IWorkflowStore interface
All storage backends SHALL implement the `IWorkflowStore` interface providing run lifecycle, event persistence, and node output retrieval.
#### Scenario: Store provides run CRUD
- **WHEN** a workflow run is created
- **THEN** `createWorkflowRun()` SHALL persist the run and return it
- **WHEN** a workflow run status is updated
- **THEN** `updateWorkflowRun()` SHALL persist the status change
#### Scenario: Store provides event persistence
- **WHEN** a workflow event is created
- **THEN** `createWorkflowEvent()` SHALL append it to the event log
#### Scenario: Store provides completed node outputs
- **WHEN** a workflow is resumed
- **THEN** `getCompletedDagNodeOutputs()` SHALL return all completed node outputs keyed by node ID
### Requirement: Filesystem backend
The filesystem backend SHALL store each workflow run as files in a directory: `{artifactsDir}/{runId}/`.
#### Scenario: Filesystem stores events as JSONL
- **WHEN** events are created using the filesystem backend
- **THEN** each run SHALL have `events.jsonl` with newline-delimited JSON
- **THEN** node outputs SHALL be stored as individual JSON files
#### Scenario: Filesystem stores run metadata
- **WHEN** a run is created using the filesystem backend
- **THEN** `run.json` SHALL contain the run metadata
### Requirement: SQLite backend
The SQLite backend SHALL store workflow data in a SQLite database with tables for runs, events, and node outputs.
#### Scenario: SQLite stores runs table
- **WHEN** using the SQLite backend
- **THEN** a `workflow_runs` table SHALL exist with columns for id, workflow_name, status, user_message, created_at, updated_at
#### Scenario: SQLite stores events table
- **WHEN** using the SQLite backend
- **THEN** a `workflow_events` table SHALL exist with columns for run_id, sequence, event_type, timestamp, payload
### Requirement: Postgres backend
The Postgres backend SHALL use a PostgreSQL database with the same schema as SQLite, accessed via the `IWorkflowStore` interface.
#### Scenario: Postgres uses same interface
- **WHEN** switching from SQLite to Postgres
- **THEN** no workflow engine code SHALL change — only the store implementation

View File

@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Node output references
Prompts and commands SHALL support `$nodeId.output` to reference the output text of an upstream node, and `$nodeId.output.field` to reference a specific field from a structured output.
#### Scenario: Output reference substitution
- **WHEN** a prompt contains `$analysis.output`
- **THEN** it SHALL be replaced with the full output text of the node with id `analysis`
#### Scenario: Field reference with structured output
- **WHEN** a prompt contains `$analysis.output.summary` and the upstream node declared `output_format: { type: "object", properties: { summary: ... } }`
- **THEN** it SHALL be replaced with the value of the `summary` field from the parsed JSON output
#### Scenario: Missing node reference
- **WHEN** a prompt references `$nonexistent.output`
- **THEN** the reference SHALL resolve to an empty string with a warning
#### Scenario: Missing field on schemaless node
- **WHEN** a prompt references `$node.output.field` and the upstream node has no `output_format` and its output is not valid JSON
- **THEN** the consuming node SHALL fail with an error
#### Scenario: Strict field access for declared schemas
- **WHEN** a prompt references `$node.output.field` and the upstream node's `output_format` declares properties but `field` is not among them
- **THEN** the consuming node SHALL fail with a field-not-found error
### Requirement: Built-in variables
The engine SHALL support `$ARGUMENTS`, `$ARTIFACTS_DIR`, `$WORKFLOW_ID`, `$BASE_BRANCH`, `$DOCS_DIR`.
#### Scenario: $ARGUMENTS substitution
- **WHEN** a prompt contains `$ARGUMENTS`
- **THEN** it SHALL be replaced with the full user message/arguments string
#### Scenario: $ARTIFACTS_DIR substitution
- **WHEN** a prompt contains `$ARTIFACTS_DIR`
- **THEN** it SHALL be replaced with the path to the run's artifact directory
#### Scenario: $WORKFLOW_ID substitution
- **WHEN** a prompt contains `$WORKFLOW_ID`
- **THEN** it SHALL be replaced with the workflow run ID
### Requirement: Loop-specific variables
Loop nodes SHALL support `$LOOP_USER_INPUT` (from approve at interactive gates) and `$LOOP_PREV_OUTPUT` (output of the previous iteration).
#### Scenario: $LOOP_PREV_OUTPUT on first iteration
- **WHEN** a loop node is on its first iteration
- **THEN** `$LOOP_PREV_OUTPUT` SHALL resolve to an empty string
#### Scenario: $LOOP_PREV_OUTPUT on subsequent iterations
- **WHEN** a loop node is on iteration 2+
- **THEN** `$LOOP_PREV_OUTPUT` SHALL contain the cleaned output of the previous iteration
### Requirement: Approval-specific variables
Approval nodes SHALL support `$REJECTION_REASON`.
#### Scenario: $REJECTION_REASON in on_reject prompt
- **WHEN** an approval node is rejected with a reason
- **THEN** `$REJECTION_REASON` SHALL contain the reviewer's feedback text

View File

@@ -0,0 +1,51 @@
## ADDED Requirements
### Requirement: Run states
A workflow run SHALL transition through states: `pending → running → completed | failed | cancelled`. It MAY transition to `paused` for approval gates.
#### Scenario: Normal completion
- **WHEN** all DAG nodes complete successfully
- **THEN** the run status SHALL be `completed`
#### Scenario: Node failure
- **WHEN** a node fails and no retry succeeds
- **THEN** the run status SHALL be `failed`
#### Scenario: User cancellation
- **WHEN** a user cancels a running workflow
- **THEN** the run status SHALL be `cancelled`
#### Scenario: Approval pause
- **WHEN** an approval node is reached
- **THEN** the run status SHALL transition to `paused`
- **THEN** it SHALL transition back to `running` on approval
### Requirement: Resume from failure
A failed workflow SHALL support resumption, skipping already-completed nodes using stored outputs from the event log.
#### Scenario: Resume skips completed nodes
- **WHEN** a failed workflow has 2 completed nodes out of 5
- **THEN** resuming SHALL skip nodes 1-2 and re-execute from node 3
#### Scenario: Resume with always_run
- **WHEN** a node has `always_run: true` and the workflow is resumed
- **THEN** the node SHALL re-execute even if it completed previously
### Requirement: Event-based observability
All lifecycle transitions SHALL emit typed events through the event emitter for observability and external subscribers.
#### Scenario: Events for every state transition
- **WHEN** a workflow starts
- **THEN** a `workflow_started` event SHALL be emitted
- **WHEN** a workflow completes
- **THEN** a `workflow_completed` event SHALL be emitted
- **WHEN** a node starts/completes/fails/skips
- **THEN** corresponding node events SHALL be emitted
### Requirement: Cleanup
The engine SHALL support cleaning up old workflow runs and their artifacts.
#### Scenario: Cleanup by age
- **WHEN** cleanup is invoked with a retention period (default 7 days)
- **THEN** runs older than the retention period SHALL have their artifacts removed
- **THEN** run records MAY be pruned from the store

View File

@@ -0,0 +1,77 @@
## ADDED Requirements
### Requirement: Workflow definition structure
A workflow YAML SHALL have a top-level `name`, `description`, and `nodes:` array. It MAY have `provider`, `model`, `interactive`, `mutates_checkout`, `tags`.
#### Scenario: Minimal valid workflow
- **WHEN** a YAML file contains a `name`, `description`, and at least one node
- **THEN** the loader SHALL parse it as a valid workflow definition
#### Scenario: Missing name
- **WHEN** a YAML file lacks a `name` field
- **THEN** the loader SHALL reject it with a validation error
### Requirement: Seven node types
The engine SHALL support exactly 7 node types, mutually exclusive per node: `command`, `prompt`, `bash`, `script`, `loop`, `approval`, `cancel`.
#### Scenario: Node with exactly one mode field
- **WHEN** a node has `prompt:` but no other mode field
- **THEN** it SHALL be classified as a PromptNode
#### Scenario: Node with multiple mode fields
- **WHEN** a node has both `prompt:` and `bash:`
- **THEN** the loader SHALL reject it with a mutual-exclusivity error
#### Scenario: Node with no mode field
- **WHEN** a node has none of the 7 mode fields
- **THEN** the loader SHALL reject it
### Requirement: Common node fields
All node types SHALL support `id`, `depends_on`, `when`, `trigger_rule`, `retry`, `timeout`, `output_type`, `always_run`.
#### Scenario: Node id must be unique
- **WHEN** two nodes in the same workflow share the same `id`
- **THEN** the loader SHALL reject the workflow
### Requirement: Prompt node
A PromptNode SHALL have a `prompt:` string field containing the AI prompt text.
#### Scenario: Empty prompt rejected
- **WHEN** a node has `prompt: ""`
- **THEN** the loader SHALL reject it
### Requirement: Bash node
A BashNode SHALL have a `bash:` string field and MAY have `timeout` (ms). AI-specific fields SHALL be ignored with a warning.
#### Scenario: Bash node with timeout
- **WHEN** a bash node includes `timeout: 30000`
- **THEN** the executor SHALL kill the subprocess after 30 seconds
### Requirement: Script node
A ScriptNode SHALL have `script:` (inline or named), `runtime:` (`bun` or `uv`), MAY have `deps:` and `timeout:`.
#### Scenario: Script with deps
- **WHEN** a script node has `runtime: bun` and `deps: ["lodash"]`
- **THEN** the executor SHALL install dependencies before running the script
#### Scenario: Named script from disk
- **WHEN** `script: analyze` and a file `scripts/analyze.ts` exists
- **THEN** the executor SHALL load and run it
### Requirement: Loop node
A LoopNode SHALL have `loop:` with `prompt`, `until`, `max_iterations`, and optional `fresh_context`, `interactive`, `gate_message`, `until_bash`.
#### Scenario: Loop with completion signal
- **WHEN** the AI response contains the `until` string
- **THEN** the loop SHALL stop and the node SHALL complete
#### Scenario: Loop exceeds max_iterations
- **WHEN** the loop reaches `max_iterations` without the completion signal
- **THEN** the node SHALL fail
### Requirement: Cancel node
A CancelNode SHALL have a `cancel:` string containing a reason. It SHALL terminate the workflow run.
#### Scenario: Cancel terminates workflow
- **WHEN** a cancel node executes
- **THEN** the workflow SHALL be marked as cancelled with the cancel reason