Skip to content

Commit e761f8a

Browse files
author
Tom Brandenburg
committed
feat: harden flowsh harness generation
1 parent cb2654e commit e761f8a

11 files changed

Lines changed: 1739 additions & 1505 deletions

File tree

.github/workflows/qa.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: QA
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
qa:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Check out repository
14+
uses: actions/checkout@v4
15+
16+
- name: Install uv
17+
uses: astral-sh/setup-uv@v6
18+
with:
19+
enable-cache: true
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: "3.11"
25+
26+
- name: Install dependencies
27+
run: uv sync --locked
28+
29+
- name: Run quality gate
30+
run: make qa

.made/workflows.yml

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
workflows:
2-
- id: wf_mp8fbogh_azcga
3-
name: New workflow
4-
enabled: false
5-
schedule: null
6-
steps:
7-
- type: agent
8-
agent: build
9-
prompt: Write an empty test.txt
10-
shellScriptPath: .harness/new-workflow.sh
2+
- id: wf_example
3+
name: Example
4+
steps:
5+
- type: vars
6+
name: Capture date
7+
values:
8+
TODAY: date -u +%F
9+
- type: bash
10+
name: Print date
11+
run: |
12+
printf 'today=%s\n' "$TODAY"

Makefile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help install lint test qa clean
1+
.PHONY: help install lint test build qa clean hygiene
22

33
help:
44
@printf '%s\n' \
@@ -8,8 +8,10 @@ help:
88
' make install Install flowsh into the user PATH with uv tool' \
99
' make lint Run Ruff and compile-check Python files' \
1010
' make test Run tests' \
11-
' make qa Run lint and tests' \
12-
' make clean Remove local test/build artifacts'
11+
' make build Build source and wheel distributions' \
12+
' make qa Run lint, tests, and package build' \
13+
' make clean Remove local test/build artifacts' \
14+
' make hygiene Show tracked, untracked, and ignored files'
1315

1416
install:
1517
uv tool install --force .
@@ -22,8 +24,14 @@ lint:
2224
test:
2325
uv run pytest
2426

25-
qa: lint test
27+
build:
28+
uv build
29+
30+
qa: lint test build
2631
@printf '%s\n' 'QA passed'
2732

33+
hygiene:
34+
git status --short --ignored
35+
2836
clean:
2937
rm -rf .pytest_cache .ruff_cache .harness .flowsh src/flowsh/__pycache__ scripts/__pycache__ tests/__pycache__ dist build

README.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ workflows:
3434
3535
Supported step types are only `vars`, `bash`, and `agent`.
3636

37+
The input path must be a regular file no larger than 1 MiB. The input file must be valid UTF-8, non-empty YAML with a mapping root, no duplicate mapping keys, and no YAML aliases. Workflow and step names are single-line labels. Executable fields reject unsafe control bytes while allowing normal newlines and tabs. `vars` keys must be uppercase shell variable names, and `agent` names may contain only letters, digits, `_`, and `-`.
38+
3739
Harness paths are derived from workflow ids. `wf_example` writes `.harness/example.sh`.
3840

3941
## Commands
@@ -50,18 +52,66 @@ uv run flowsh .made/workflows.yml --dry-run
5052
5153
# Overwrite existing harness files
5254
uv run flowsh .made/workflows.yml --force
55+
56+
# Show version
57+
uv run flowsh --version
5358
```
5459

55-
Generated harness logs go to `.flowsh/logs` by default. Set `FLOWSH_LOG_DIR` when running a harness to use another local log directory.
60+
## CLI Contract
61+
62+
`flowsh` is non-interactive. It never prompts for missing information.
63+
64+
Current help output is plain text and deterministic across repeated runs:
65+
66+
```text
67+
Usage: flowsh [OPTIONS] WORKFLOW_YAML
68+
69+
Generate reproducible OpenCode Bash harness scripts from MADE workflow YAML.
70+
71+
Arguments:
72+
WORKFLOW_YAML Path to .made/workflows.yml \[required]
73+
74+
Options:
75+
--workflow TEXT Optional workflow id to generate. Defaults to all workflows.
76+
--dry-run Print planned output paths without writing scripts.
77+
--force Overwrite existing files. Without this, existing files cause a failure.
78+
--version Show the flowsh version and exit.
79+
--help Show this message and exit.
80+
```
81+
82+
The CLI pins its help formatter width so this contract does not vary with the
83+
caller terminal size or `COLUMNS` environment value.
84+
85+
Exit codes:
86+
87+
| Case | Exit | stdout | stderr |
88+
|---|---:|---|---|
89+
| `--help` | `0` | Help text | Empty |
90+
| `--version` | `0` | `flowsh <version>` | Empty |
91+
| Valid generation | `0` | One `Wrote <path>` line per harness | Empty |
92+
| Valid `--dry-run` | `0` | One `DRY-RUN would write <path>` line per selected workflow | Empty |
93+
| Missing required CLI argument | `2` | Empty | Typer usage error |
94+
| Malformed or unsupported workflow YAML | `1` | Empty | `ERROR: <reason>` |
95+
| Unknown `--workflow` id | `1` | Empty | `ERROR: No workflow id matched ...` with known workflow ids |
96+
| Existing harness without `--force` | `1` | Empty | `ERROR: Refusing to overwrite ...` |
97+
| Output directory/path safety failure | `1` | Empty | `ERROR: <path safety reason>` |
98+
99+
Generated harnesses are also non-interactive. `harness.sh --dry-run` exits `0` after logging planned steps to stderr and creating no log directory. A real harness run exits `0` only after every step succeeds. Failed `bash`, `vars`, or `agent` steps return the failing command status, log `Step failed: <step> (exit=<code>)` to stderr, and stop before later steps run. If an `agent` step runs without `opencode` on `PATH`, the harness exits `127` and prints `opencode CLI not found in PATH` to stderr.
100+
101+
Generated harnesses are written with owner-only executable permissions and refuse to overwrite existing paths unless `--force` is passed. Multi-workflow generation preflights overwrite conflicts before writing any harness. The `.harness` output directory must be a real directory, not a symlink or file. Harness dry runs do not create log files or directories. Real harness logs go to `.flowsh/logs` by default with owner-private directory and file permissions. Set `FLOWSH_LOG_DIR` when running a harness to use another local relative log directory; absolute paths, `..` path segments, symlinked path components, and non-directory log paths are refused. Logging setup and write failures fail the harness instead of being silently ignored.
102+
103+
Generated `bash` and `vars` bodies run with `bash -euo pipefail`, so command failures stop the workflow instead of being masked by later successful commands. Captured `vars` values are exported for later `bash` steps. `agent` steps invoke only `opencode run --format json` and fail with a clear error if `opencode` is not on `PATH`.
56104

57105
## Development
58106

59107
```bash
60108
uv sync
61109
make install
110+
make build
62111
make qa
63112
```
64113

65114
`make install` installs `flowsh` into the user PATH with `uv tool install --force .`.
115+
`make build` creates reproducible source and wheel distributions under ignored `dist/`.
66116

67-
`make qa` runs Ruff, Python compile checks, and pytest. There is no TypeScript compiler, template system, DSL explorer, or legacy node registry.
117+
`make qa` runs Ruff, Python compile checks, pytest, and package builds locally and in CI. There is no TypeScript compiler, template system, DSL explorer, legacy node registry, or archived legacy workflow spec in this repository.

0 commit comments

Comments
 (0)