Problem Statement
Custom workflow step types are the only first-class Spec-Kit component that cannot be installed from a local path. Every other artifact supports local development, but steps are catalog/HTTPS-only:
| Component |
Local/dev install |
Custom URL |
| Extension |
specify extension add --dev <dir> |
--from <url> |
| Preset |
specify preset add --dev <dir> |
--from <url> |
| Workflow |
specify workflow add <path> --dev |
--from <url> |
| Bundle |
specify bundle install <path> |
— |
| Step |
none |
none |
specify workflow step add accepts only a catalog step id and fetches step.yml/__init__.py (and optional extra_files) over HTTPS (src/specify_cli/workflows/step/command_add.py, _safe_fetch rejects non-HTTPS). There is no --dev or --from.
This makes custom steps impossible to iterate on or test from a checkout:
- A developer must publish the step package to an HTTPS catalog (or run a localhost catalog server) before they can run it once.
- CI cannot install a step from the repository under test without a network round-trip to a hosted catalog.
- Bundles that declare
provides.steps cannot be developed or validated offline — bundle install delegates step installs to workflow_step_add(component.id) (bundler/services/primitives.py), which always resolves through the step catalog.
- Manually copying files into
.specify/workflows/steps/<id>/ works at runtime (load_custom_steps scans the directory) but is undocumented, unvalidated, and leaves the registry out of sync.
Net effect: the step type is second-class relative to extensions, presets, and workflows, and the only way to develop one is a throwaway localhost HTTPS/HTTP catalog.
Proposed Solution
Bring specify workflow step add to feature parity with the other components:
- Add
--dev <dir> to install a custom step from a local package directory containing step.yml, __init__.py, and any extra_files. It copies the package into .specify/workflows/steps/<id>/ and records it in the step registry with source: local, mirroring preset add --dev / extension add --dev.
- Add
--from <url> to install a single step package directly from a URL, mirroring the other components' --from.
- Apply the same safety guards the catalog path already uses: validate
step.type_key, reject ids that collide with built-in step types, reject duplicate installs (with a consistent --force/reinstall semantic), and keep the symlink/.. path-escape guards on the destination.
- Optionally support
bundle install <local dir> resolving provides.steps from the local bundle directory instead of the catalog stack, so step-bearing bundles work offline.
Alternatives Considered
- Manual copy into
.specify/workflows/steps/<id>/ — works because load_custom_steps scans the directory, but it's undocumented, skips validation, and doesn't register provenance, so workflow step list/remove/bundle tracking miss it.
- Localhost step catalog —
_validate_catalog_url already permits http://localhost, so a developer can run a local server and register it as a catalog. This is the current workaround, but it is heavy, undocumented, and not usable in CI without extra plumbing.
- Ship the step inside a preset/extension archive — not possible: the engine only loads custom steps from
.specify/workflows/steps/.
Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
Not applicable
Use Cases
- Iterating on a custom step type while developing a bundle that uses it, without publishing to a catalog on every change.
- Running a bundle's integration tests in CI against a local checkout of the step package.
- Authoring a step locally, then publishing it to a catalog only once it is stable.
- Offline development of step-bearing bundles.
Acceptance Criteria
Additional Context
- Command surface:
src/specify_cli/workflows/step/command_add.py (workflow_step_add(step_id), _safe_fetch HTTPS-only).
- Catalog/URL validation:
src/specify_cli/workflows/step/catalog/_domain.py (_validate_catalog_url).
- Loader:
src/specify_cli/workflows/__init__.py (load_custom_steps) — confirms local directory loading already works at runtime; only installation tooling is missing.
- Bundle delegation:
bundler/services/primitives.py (steps → workflow_step_add).
- Parity references:
install_from_directory in src/specify_cli/presets/__init__.py, and --dev handling for extensions and workflows.
- Motivating use case: a bundle that ships a custom step to consolidate run setup currently has to choose between a network-only component and a localhost catalog workaround.
AI Disclosure
Drafted with opencode (model deepseek-v4.1-flash), human-supervised and reviewed before submission.
Problem Statement
Custom workflow step types are the only first-class Spec-Kit component that cannot be installed from a local path. Every other artifact supports local development, but steps are catalog/HTTPS-only:
specify extension add --dev <dir>--from <url>specify preset add --dev <dir>--from <url>specify workflow add <path> --dev--from <url>specify bundle install <path>specify workflow step addaccepts only a catalog step id and fetchesstep.yml/__init__.py(and optionalextra_files) over HTTPS (src/specify_cli/workflows/step/command_add.py,_safe_fetchrejects non-HTTPS). There is no--devor--from.This makes custom steps impossible to iterate on or test from a checkout:
provides.stepscannot be developed or validated offline —bundle installdelegates step installs toworkflow_step_add(component.id)(bundler/services/primitives.py), which always resolves through the step catalog..specify/workflows/steps/<id>/works at runtime (load_custom_stepsscans the directory) but is undocumented, unvalidated, and leaves the registry out of sync.Net effect: the step type is second-class relative to extensions, presets, and workflows, and the only way to develop one is a throwaway localhost HTTPS/HTTP catalog.
Proposed Solution
Bring
specify workflow step addto feature parity with the other components:--dev <dir>to install a custom step from a local package directory containingstep.yml,__init__.py, and anyextra_files. It copies the package into.specify/workflows/steps/<id>/and records it in the step registry withsource: local, mirroringpreset add --dev/extension add --dev.--from <url>to install a single step package directly from a URL, mirroring the other components'--from.step.type_key, reject ids that collide with built-in step types, reject duplicate installs (with a consistent--force/reinstall semantic), and keep the symlink/..path-escape guards on the destination.bundle install <local dir>resolvingprovides.stepsfrom the local bundle directory instead of the catalog stack, so step-bearing bundles work offline.Alternatives Considered
.specify/workflows/steps/<id>/— works becauseload_custom_stepsscans the directory, but it's undocumented, skips validation, and doesn't register provenance, soworkflow step list/remove/bundle tracking miss it._validate_catalog_urlalready permitshttp://localhost, so a developer can run a local server and register it as a catalog. This is the current workaround, but it is heavy, undocumented, and not usable in CI without extra plumbing..specify/workflows/steps/.Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
Not applicable
Use Cases
Acceptance Criteria
specify workflow step add --dev <dir>installs a local step package into.specify/workflows/steps/<id>/.workflow run,workflow resume, andworkflow add, and appears inworkflow step list.--devvalidatesstep.yml/__init__.py, rejects collisions with built-in step types, and rejects duplicate installs with the same semantics as the catalog path.workflow step removecleans up correctly.--from <url>installs a single step package from a URL.bundle install <local dir>resolves and installsprovides.stepsfrom the local bundle source (or the limitation is explicitly documented).docs/reference/workflows.mdstep-type section) and tests cover the new paths.Additional Context
src/specify_cli/workflows/step/command_add.py(workflow_step_add(step_id),_safe_fetchHTTPS-only).src/specify_cli/workflows/step/catalog/_domain.py(_validate_catalog_url).src/specify_cli/workflows/__init__.py(load_custom_steps) — confirms local directory loading already works at runtime; only installation tooling is missing.bundler/services/primitives.py(steps→workflow_step_add).install_from_directoryinsrc/specify_cli/presets/__init__.py, and--devhandling for extensions and workflows.AI Disclosure
Drafted with opencode (model
deepseek-v4.1-flash), human-supervised and reviewed before submission.