From 8b0c0ac4051588f2c44f931cc380e68b7611997d Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:14:06 -0500 Subject: [PATCH 1/6] feat(workflows): add step type submission intake Add the canonical intake-only issue form for community workflow step types, document the current manual catalog path, and cover its exact labels and field contract. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../workflow_step_submission.yml | 237 ++++++++++++++++++ docs/community/overview.md | 12 +- docs/community/workflow-steps.md | 110 ++++++++ docs/reference/workflows.md | 3 + tests/test_github_workflows.py | 117 ++++++++- 5 files changed, 474 insertions(+), 5 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/workflow_step_submission.yml create mode 100644 docs/community/workflow-steps.md diff --git a/.github/ISSUE_TEMPLATE/workflow_step_submission.yml b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml new file mode 100644 index 0000000000..27d45c7eba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml @@ -0,0 +1,237 @@ +name: Workflow Step Type Submission +description: Submit a custom workflow step package for community catalog intake +title: "[Workflow Step]: Add " +labels: ["triage-must-have"] +body: + - type: markdown + attributes: + value: | + Thanks for contributing a workflow step type! This form captures the package, release, and catalog metadata needed for manual community catalog review. + + **Before submitting:** + - Review the [Community Workflow Step Types guide](https://github.com/github/spec-kit/blob/main/docs/community/workflow-steps.md) + - Publish a package containing root-level `step.yml` and `__init__.py` files + - Ensure `step.type_key`, the catalog ID, and the matching `StepBase.type_key` are identical + - Create an immutable, versioned release artifact and tag-pinned URLs for every catalog-downloaded file + - Test the release artifact with `specify workflow step add --from ` + + **After submitting:** GitHub automatically applies only the `triage-must-have` intake verdict. This phase is intake-only: no validation workflow or draft pull request is triggered. Maintainers currently review the metadata manually and, when accepted, update the community catalog through the normal reviewed pull request process. + + - type: input + id: step-id + attributes: + label: Step Type ID + description: Exact catalog key and `step.type_key`; it must be one safe path component and must not collide with a built-in step type + placeholder: "e.g., deploy" + validations: + required: true + + - type: input + id: step-name + attributes: + label: Step Type Name + description: Human-readable name declared in `step.yml` + placeholder: "e.g., Deploy" + validations: + required: true + + - type: input + id: version + attributes: + label: Version + description: PEP 440-compatible release version; it must match `step.version` in the submitted `step.yml` + placeholder: "e.g., 1.0.0" + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description + description: Concise description of the workflow behavior this step type implements + placeholder: Deploys the current project to a configured environment and returns the deployment URL + validations: + required: true + + - type: input + id: author + attributes: + label: Author + description: Name or organization declared for the package + placeholder: "e.g., Jane Doe or Acme Corp" + validations: + required: true + + - type: input + id: repository + attributes: + label: Repository URL + description: Public source repository containing the submitted package + placeholder: "https://github.com/your-org/spec-kit-step-deploy" + validations: + required: true + + - type: input + id: release-artifact-url + attributes: + label: Release Artifact URL + description: Exact immutable `.zip`, `.tar.gz`, or `.tgz` download URL for this version; do not use a latest-release or branch URL + placeholder: "https://github.com/your-org/spec-kit-step-deploy/releases/download/v1.0.0/deploy-1.0.0.zip" + validations: + required: true + + - type: input + id: step-yml-url + attributes: + label: step.yml URL + description: Exact tag-pinned HTTPS URL used as the catalog `step_yml_url` (or `url`) + placeholder: "https://raw.githubusercontent.com/your-org/spec-kit-step-deploy/v1.0.0/step.yml" + validations: + required: true + + - type: input + id: init-url + attributes: + label: __init__.py URL + description: Exact tag-pinned HTTPS URL used as the catalog `init_url` + placeholder: "https://raw.githubusercontent.com/your-org/spec-kit-step-deploy/v1.0.0/__init__.py" + validations: + required: true + + - type: textarea + id: extra-files + attributes: + label: Extra File URLs + description: JSON mapping of every additional package-relative file path to its exact tag-pinned HTTPS URL; enter `{}` when there are no extra files + render: json + placeholder: | + { + "helpers.py": "https://raw.githubusercontent.com/your-org/spec-kit-step-deploy/v1.0.0/helpers.py" + } + validations: + required: true + + - type: textarea + id: file-sha256 + attributes: + label: Per-file SHA-256 Digests + description: JSON mapping containing exactly `step.yml`, `__init__.py`, and every Extra File URL path, each with a 64-character hexadecimal SHA-256 digest + render: json + placeholder: | + { + "step.yml": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "__init__.py": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "helpers.py": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + } + validations: + required: true + + - type: input + id: license + attributes: + label: License + description: SPDX license identifier for the package; the repository must include the corresponding license text + placeholder: "e.g., MIT or Apache-2.0" + validations: + required: true + + - type: input + id: speckit-compatibility + attributes: + label: Spec Kit Compatibility + description: Spec Kit version constraint tested with this release; the current step catalog does not yet enforce this constraint + placeholder: "e.g., >=0.11.0" + validations: + required: true + + - type: textarea + id: runtime-dependencies + attributes: + label: Runtime and Tool Dependencies + description: List required Python packages, executables, services, environment variables, or platform constraints; enter `None` when the package has no external dependencies + placeholder: | + - Python package: requests>=2.32 + - Executable: kubectl>=1.30 + - Environment variable: KUBECONFIG + validations: + required: true + + - type: dropdown + id: step-type-count + attributes: + label: Number of Provided Step Types + description: One installed package currently registers exactly one `step.type_key` + options: + - "1" + validations: + required: true + + - type: textarea + id: step-types-provided + attributes: + label: Step Type Provided + description: Name the single `StepBase` subclass and its exact `type_key` + placeholder: "DeployStep — deploy" + validations: + required: true + + - type: input + id: documentation + attributes: + label: Documentation URL + description: Tag-pinned documentation covering configuration, outputs, failure behavior, side effects, and an example workflow + placeholder: "https://github.com/your-org/spec-kit-step-deploy/blob/v1.0.0/README.md" + validations: + required: true + + - type: input + id: changelog + attributes: + label: Changelog URL + description: Tag-pinned changelog for this release; leave empty only for an initial release with no prior versions + placeholder: "https://github.com/your-org/spec-kit-step-deploy/blob/v1.0.0/CHANGELOG.md" + + - type: textarea + id: testing-details + attributes: + label: Testing Details + description: Describe positive and negative configuration tests, execution/status/output tests, and any resume, nested, or concurrent behavior that applies + placeholder: | + **Tested with:** Spec Kit 0.11.0 on macOS and Ubuntu + + **Evidence:** + - Valid configuration completes with the documented outputs + - Invalid configuration returns a failed result with an actionable error + - Release artifact installs with `specify workflow step add deploy --from ...` + validations: + required: true + + - type: checkboxes + id: attestations + attributes: + label: Required Attestations + description: Confirm the submitted release satisfies the current external step-package contract + options: + - label: The release artifact contains regular, non-symlink `step.yml` and `__init__.py` files at the package root or under exactly one top-level directory + required: true + - label: The catalog ID, `step.type_key`, and the matching `StepBase.type_key` are identical and do not collide with a built-in step type + required: true + - label: The submitted version matches `step.version`, and every release and file URL is pinned to this immutable version + required: true + - label: The SHA-256 mapping covers exactly every file the catalog installer downloads + required: true + - label: The release artifact installs successfully with `specify workflow step add --from ` + required: true + - label: Runtime and tool dependencies, side effects, configuration, outputs, and failure behavior are documented + required: true + - label: The repository includes the submitted license and complete source for the released package + required: true + - label: I understand that custom workflow steps execute Python with the user's privileges and that a community catalog listing is not a code audit, endorsement, or support commitment + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Optional links or notes that help maintainers verify the submitted metadata + placeholder: Release notes, compatibility notes, or related issue links diff --git a/docs/community/overview.md b/docs/community/overview.md index 48b0d42d7b..3a03856ac6 100644 --- a/docs/community/overview.md +++ b/docs/community/overview.md @@ -24,6 +24,13 @@ Bundles compose extensions, presets, workflows, and steps into role or team stac [Browse community bundles →](bundles.md) +## Workflow step types + +Workflow step types add reusable executable behavior that workflows can select +with `type:`. + +[Submit a community workflow step type →](workflow-steps.md) + ## Walkthroughs Step-by-step guides that show Spec-Driven Development in action across different scenarios, languages, and frameworks. @@ -41,5 +48,6 @@ Community projects that extend, visualize, or build on Spec Kit — including VS To publish your own work, follow the [Extension Publishing Guide](https://github.com/github/spec-kit/blob/main/extensions/EXTENSION-PUBLISHING-GUIDE.md), the [Presets Publishing Guide](https://github.com/github/spec-kit/blob/main/presets/PUBLISHING.md), -or the [Community Bundles guide](bundles.md). For help choosing a component type, -see [Customize Spec Kit](../guides/customization.md). +the [Community Bundles guide](bundles.md), or the +[Community Workflow Step Types guide](workflow-steps.md). For help choosing a +component type, see [Customize Spec Kit](../guides/customization.md). diff --git a/docs/community/workflow-steps.md b/docs/community/workflow-steps.md new file mode 100644 index 0000000000..4311e00fc9 --- /dev/null +++ b/docs/community/workflow-steps.md @@ -0,0 +1,110 @@ +# Community Workflow Step Types + +> [!NOTE] +> Community workflow step types are independently created and maintained by +> their authors. Maintainers verify submission metadata and package shape; they +> do **not review, audit, endorse, or support the executable Python code**. +> Installing a custom step imports and runs that code with the user's +> privileges. Review the source before installing it. + +Custom workflow step packages add a `type:` that workflows can use beyond the +built-in step types. The canonical intake path is the +[Workflow Step Type Submission](https://github.com/github/spec-kit/issues/new?template=workflow_step_submission.yml) +issue form. + +This is currently an **intake-only** process. Opening the form applies only the +`triage-must-have` verdict. It does not trigger catalog validation, create a +branch, or open a draft pull request. Maintainers review submissions manually +and, when accepted, update +[`workflows/step-catalog.community.json`](https://github.com/github/spec-kit/blob/main/workflows/step-catalog.community.json) +through the normal reviewed pull request process. + +## Package Contract + +One installed package provides one workflow step type: + +```text +my-step/ +├── step.yml +├── __init__.py +└── helpers.py # optional; every catalog-downloaded extra file is declared +``` + +The package must satisfy the current installer and loader contract: + +- `step.yml` and `__init__.py` are regular, non-symlink files at the package + root. An archive may wrap the package in exactly one top-level directory. +- `step.type_key`, the catalog key/ID, and the matching + `StepBase.type_key` are identical. +- The ID is one safe path component. The installer rejects separators, leading + dots, trailing dots or spaces, reserved names, control characters, and + Windows-invalid filename characters. +- `__init__.py` defines a `StepBase` subclass matching that single type key. + Additional classes do not make the package provide additional registered + step types. +- The package stays within the current installer limits: 512 retained entries, + 32 directory levels, and 50 MiB of retained content. +- `.git`, `__pycache__`, and `.DS_Store` are excluded from installation. + +See [Custom step packages](../reference/workflows.md#custom-step-packages) for +the complete install, validation, and runtime behavior. + +## Submission Contract + +The issue form separates fields consumed by the current package/catalog code +from provenance and disclosure fields used during manual review. + +| Submission data | Current contract | +| --- | --- | +| Step type ID | Catalog key, `step.type_key`, and matching `StepBase.type_key` | +| Name, version, description, author | `step.yml` metadata and catalog/installed registry metadata | +| Release artifact URL | Immutable archive used to test the supported direct-URL installation path | +| `step.yml`, `__init__.py`, and extra-file URLs | Exact HTTPS file URLs consumed by catalog installation | +| Per-file SHA-256 mapping | Catalog `sha256`; keys must be exactly the files the installer downloads | +| Provided type name/count | Loader invariant: one matching type key per installed package | +| Repository, license, documentation, changelog | Source provenance and manual-review evidence; these are not `step.yml` fields | +| Spec Kit compatibility | Required intake disclosure; the step catalog accepts a `requires` mapping but does not yet define or enforce a Spec Kit compatibility schema | +| Runtime and tool dependencies | Required intake disclosure; the CLI does not resolve or install dependencies for custom steps | + +Use tag-pinned URLs for the release artifact, documentation, and every catalog +file. Do not submit branch URLs, `releases/latest` URLs, or other moving +targets. The current exact-release catalog path requires a valid release +version and a 64-character hexadecimal SHA-256 digest for `step.yml`, +`__init__.py`, and every `extra_files` path. + +## Prepare a Submission + +1. Create a public source repository with the complete package, license, + documentation, and changelog where applicable. +2. Test valid and invalid configuration, `StepStatus`, outputs, errors, and any + relevant resume, nested-step, or concurrent execution behavior. +3. Publish an immutable versioned archive (`.zip`, `.tar.gz`, or `.tgz`) and + test it: + + ```bash + specify workflow step add my-step \ + --from https://github.com/your-org/my-step/releases/download/v1.0.0/my-step-1.0.0.zip + ``` + +4. Publish tag-pinned HTTPS URLs for `step.yml`, `__init__.py`, and every extra + package file. Compute the SHA-256 digest of each downloaded file. +5. File the canonical submission issue with the exact release metadata and + test evidence. + +## Current Review Scope + +Maintainers currently check the form manually for: + +- complete identity, release, repository, license, and documentation metadata; +- consistency between the package ID, manifest type key, provided class, and + version; +- immutable release and file URLs; +- complete per-file checksum and extra-file mappings; +- disclosed compatibility and runtime dependencies; and +- evidence that the released archive follows the supported package shape and + installation path. + +There is no workflow-step submission validator or draft-PR generator in this +phase. A future automation phase will need a canonical step catalog entry +schema, including defined compatibility and dependency shapes, before all +submission fields can be machine-validated rather than reviewed as text. diff --git a/docs/reference/workflows.md b/docs/reference/workflows.md index b99fbea42c..83ed2e1884 100644 --- a/docs/reference/workflows.md +++ b/docs/reference/workflows.md @@ -587,6 +587,9 @@ specify workflow run speckit -i spec="Build a kanban board with drag-and-drop ta Custom step types are installed with `specify workflow step`. A step is a directory package containing metadata and executable Python: +To prepare a public package for community catalog intake, see +[Community Workflow Step Types](../community/workflow-steps.md). + ```text my-step/ ├── step.yml # required, at the package root diff --git a/tests/test_github_workflows.py b/tests/test_github_workflows.py index 59776b9c64..88a35fdecc 100644 --- a/tests/test_github_workflows.py +++ b/tests/test_github_workflows.py @@ -525,14 +525,125 @@ def test_bundle_submission_form_applies_only_automatic_intake_verdict(): assert "bundle-submission" not in bundle_form["labels"] +def test_workflow_step_submission_form_applies_only_automatic_intake_verdict(): + forms_dir = REPO_ROOT / ".github" / "ISSUE_TEMPLATE" + workflow_step_form = yaml.safe_load( + (forms_dir / "workflow_step_submission.yml").read_text(encoding="utf-8") + ) + + assert workflow_step_form["labels"] == ["triage-must-have"] + assert not { + "enhancement", + "needs-triage", + "workflow-step-submission", + "validation-passed", + "validation-failed", + } & set(workflow_step_form["labels"]) + + +def test_workflow_step_submission_form_has_valid_complete_field_contract(): + forms_dir = REPO_ROOT / ".github" / "ISSUE_TEMPLATE" + workflow_step_form = yaml.safe_load( + (forms_dir / "workflow_step_submission.yml").read_text(encoding="utf-8") + ) + fields = [item for item in workflow_step_form["body"] if "id" in item] + field_ids = [field["id"] for field in fields] + + assert len(field_ids) == len(set(field_ids)) + assert all(re.fullmatch(r"[A-Za-z0-9_-]+", field_id) for field_id in field_ids) + assert set(field_ids) == { + "step-id", + "step-name", + "version", + "description", + "author", + "repository", + "release-artifact-url", + "step-yml-url", + "init-url", + "extra-files", + "file-sha256", + "license", + "speckit-compatibility", + "runtime-dependencies", + "step-type-count", + "step-types-provided", + "documentation", + "changelog", + "testing-details", + "attestations", + "additional-context", + } + required_ids = { + "step-id", + "step-name", + "version", + "description", + "author", + "repository", + "release-artifact-url", + "step-yml-url", + "init-url", + "extra-files", + "file-sha256", + "license", + "speckit-compatibility", + "runtime-dependencies", + "step-type-count", + "step-types-provided", + "documentation", + "testing-details", + } + assert { + field["id"] + for field in fields + if field.get("validations", {}).get("required") is True + } == required_ids + field_by_id = {field["id"]: field for field in fields} + assert field_by_id["step-type-count"][ + "attributes" + ]["options"] == ["1"] + assert all( + option["required"] is True + for option in field_by_id["attestations"]["attributes"]["options"] + ) + + +def test_workflow_step_submission_form_documents_intake_only_phase(): + forms_dir = REPO_ROOT / ".github" / "ISSUE_TEMPLATE" + workflow_step_form = yaml.safe_load( + (forms_dir / "workflow_step_submission.yml").read_text(encoding="utf-8") + ) + introduction = workflow_step_form["body"][0]["attributes"]["value"] + + assert "This phase is intake-only" in introduction + assert "no validation workflow or draft pull request is triggered" in introduction + assert "update the community catalog through the normal reviewed pull request" in ( + introduction + ) + + def test_other_issue_forms_do_not_apply_automatic_intake_verdict(): forms_dir = REPO_ROOT / ".github" / "ISSUE_TEMPLATE" - for form_name in ( + automatic_intake_forms = { + "bundle_submission.yml", + "extension_submission.yml", + "preset_submission.yml", + "workflow_step_submission.yml", + } + other_forms = sorted( + path + for path in forms_dir.glob("*.yml") + if path.name != "config.yml" and path.name not in automatic_intake_forms + ) + + assert [path.name for path in other_forms] == [ "agent_request.yml", "bug_report.yml", "feature_request.yml", - ): - form = yaml.safe_load((forms_dir / form_name).read_text(encoding="utf-8")) + ] + for form_path in other_forms: + form = yaml.safe_load(form_path.read_text(encoding="utf-8")) assert "triage-must-have" not in form["labels"] From 2fd671e0ea7407209f54f36b32c6ec4baf2c3d72 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:37:03 -0500 Subject: [PATCH 2/6] docs: add workflow step guide to navigation Expose the new community workflow step type guide in the DocFX Community sidebar. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/toc.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/toc.yml b/docs/toc.yml index 4889ceeb5b..8761b8a491 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -102,6 +102,8 @@ href: community/presets.md - name: Bundles href: community/bundles.md + - name: Workflow Step Types + href: community/workflow-steps.md - name: Walkthroughs href: community/walkthroughs.md - name: Friends From 54f7ee884eba516409a4a34846c3877a445bb9c8 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:13:52 -0500 Subject: [PATCH 3/6] docs: clarify workflow step catalog trust Document that the built-in community step catalog is discovery-only and keep the package tree directly attached to its introduction. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/community/workflow-steps.md | 15 +++++++++++---- docs/reference/workflows.md | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/community/workflow-steps.md b/docs/community/workflow-steps.md index 4311e00fc9..9fba40a2cf 100644 --- a/docs/community/workflow-steps.md +++ b/docs/community/workflow-steps.md @@ -19,6 +19,13 @@ and, when accepted, update [`workflows/step-catalog.community.json`](https://github.com/github/spec-kit/blob/main/workflows/step-catalog.community.json) through the normal reviewed pull request process. +The built-in community catalog is **discovery-only**. Accepted entries can +appear in `specify workflow step search` and `info`, but +`specify workflow step add ` will not install executable code from that +source. After reviewing the source, install the submitted immutable archive +directly with `--from`, or use a separately configured step catalog that you +explicitly allow for installation. + ## Package Contract One installed package provides one workflow step type: @@ -59,7 +66,7 @@ from provenance and disclosure fields used during manual review. | Step type ID | Catalog key, `step.type_key`, and matching `StepBase.type_key` | | Name, version, description, author | `step.yml` metadata and catalog/installed registry metadata | | Release artifact URL | Immutable archive used to test the supported direct-URL installation path | -| `step.yml`, `__init__.py`, and extra-file URLs | Exact HTTPS file URLs consumed by catalog installation | +| `step.yml`, `__init__.py`, and extra-file URLs | Exact HTTPS file URLs consumed when installing from an explicitly install-allowed catalog | | Per-file SHA-256 mapping | Catalog `sha256`; keys must be exactly the files the installer downloads | | Provided type name/count | Loader invariant: one matching type key per installed package | | Repository, license, documentation, changelog | Source provenance and manual-review evidence; these are not `step.yml` fields | @@ -68,9 +75,9 @@ from provenance and disclosure fields used during manual review. Use tag-pinned URLs for the release artifact, documentation, and every catalog file. Do not submit branch URLs, `releases/latest` URLs, or other moving -targets. The current exact-release catalog path requires a valid release -version and a 64-character hexadecimal SHA-256 digest for `step.yml`, -`__init__.py`, and every `extra_files` path. +targets. Exact-release installation from an install-allowed catalog requires a +valid release version and a 64-character hexadecimal SHA-256 digest for +`step.yml`, `__init__.py`, and every `extra_files` path. ## Prepare a Submission diff --git a/docs/reference/workflows.md b/docs/reference/workflows.md index 83ed2e1884..9e524c1c85 100644 --- a/docs/reference/workflows.md +++ b/docs/reference/workflows.md @@ -587,9 +587,6 @@ specify workflow run speckit -i spec="Build a kanban board with drag-and-drop ta Custom step types are installed with `specify workflow step`. A step is a directory package containing metadata and executable Python: -To prepare a public package for community catalog intake, see -[Community Workflow Step Types](../community/workflow-steps.md). - ```text my-step/ ├── step.yml # required, at the package root @@ -597,6 +594,9 @@ my-step/ └── helpers.py # optional nested modules and data files ``` +To prepare a public package for community catalog intake, see +[Community Workflow Step Types](../community/workflow-steps.md). + `step.yml` declares the step's identity. `step.type_key` must exactly match the `` passed on the command line — the ID is never inferred from package content: From 1893dae0b1d4deb3de189a829d04730b7cbd3ee5 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:23:27 -0500 Subject: [PATCH 4/6] docs: align step download intake policy Match the established community forms by using a versioned download URL without claiming archive immutability, while documenting per-file catalog checksum protection. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../workflow_step_submission.yml | 15 ++++++----- docs/community/workflow-steps.md | 27 ++++++++++++------- tests/test_github_workflows.py | 17 ++++++++++-- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/workflow_step_submission.yml b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml index 27d45c7eba..c62042726f 100644 --- a/.github/ISSUE_TEMPLATE/workflow_step_submission.yml +++ b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml @@ -12,8 +12,9 @@ body: - Review the [Community Workflow Step Types guide](https://github.com/github/spec-kit/blob/main/docs/community/workflow-steps.md) - Publish a package containing root-level `step.yml` and `__init__.py` files - Ensure `step.type_key`, the catalog ID, and the matching `StepBase.type_key` are identical - - Create an immutable, versioned release artifact and tag-pinned URLs for every catalog-downloaded file - - Test the release artifact with `specify workflow step add --from ` + - Create a GitHub release with a versioned step package archive + - Provide version-pinned URLs and SHA-256 digests for every catalog-downloaded file + - Test the release artifact with `specify workflow step add --from ` **After submitting:** GitHub automatically applies only the `triage-must-have` intake verdict. This phase is intake-only: no validation workflow or draft pull request is triggered. Maintainers currently review the metadata manually and, when accepted, update the community catalog through the normal reviewed pull request process. @@ -72,10 +73,10 @@ body: required: true - type: input - id: release-artifact-url + id: download-url attributes: - label: Release Artifact URL - description: Exact immutable `.zip`, `.tar.gz`, or `.tgz` download URL for this version; do not use a latest-release or branch URL + label: Download URL + description: URL to the versioned `.zip`, `.tar.gz`, or `.tgz` step package archive for this release placeholder: "https://github.com/your-org/spec-kit-step-deploy/releases/download/v1.0.0/deploy-1.0.0.zip" validations: required: true @@ -216,11 +217,11 @@ body: required: true - label: The catalog ID, `step.type_key`, and the matching `StepBase.type_key` are identical and do not collide with a built-in step type required: true - - label: The submitted version matches `step.version`, and every release and file URL is pinned to this immutable version + - label: The submitted version matches `step.version`, the download URL identifies that versioned release, and every catalog file URL is version-pinned required: true - label: The SHA-256 mapping covers exactly every file the catalog installer downloads required: true - - label: The release artifact installs successfully with `specify workflow step add --from ` + - label: The release artifact installs successfully with `specify workflow step add --from ` required: true - label: Runtime and tool dependencies, side effects, configuration, outputs, and failure behavior are documented required: true diff --git a/docs/community/workflow-steps.md b/docs/community/workflow-steps.md index 9fba40a2cf..c8f371565b 100644 --- a/docs/community/workflow-steps.md +++ b/docs/community/workflow-steps.md @@ -22,7 +22,7 @@ through the normal reviewed pull request process. The built-in community catalog is **discovery-only**. Accepted entries can appear in `specify workflow step search` and `info`, but `specify workflow step add ` will not install executable code from that -source. After reviewing the source, install the submitted immutable archive +source. After reviewing the source, install the submitted versioned archive directly with `--from`, or use a separately configured step catalog that you explicitly allow for installation. @@ -65,7 +65,7 @@ from provenance and disclosure fields used during manual review. | --- | --- | | Step type ID | Catalog key, `step.type_key`, and matching `StepBase.type_key` | | Name, version, description, author | `step.yml` metadata and catalog/installed registry metadata | -| Release artifact URL | Immutable archive used to test the supported direct-URL installation path | +| Download URL | Versioned release archive used to test the supported direct-URL installation path | | `step.yml`, `__init__.py`, and extra-file URLs | Exact HTTPS file URLs consumed when installing from an explicitly install-allowed catalog | | Per-file SHA-256 mapping | Catalog `sha256`; keys must be exactly the files the installer downloads | | Provided type name/count | Loader invariant: one matching type key per installed package | @@ -73,11 +73,18 @@ from provenance and disclosure fields used during manual review. | Spec Kit compatibility | Required intake disclosure; the step catalog accepts a `requires` mapping but does not yet define or enforce a Spec Kit compatibility schema | | Runtime and tool dependencies | Required intake disclosure; the CLI does not resolve or install dependencies for custom steps | -Use tag-pinned URLs for the release artifact, documentation, and every catalog -file. Do not submit branch URLs, `releases/latest` URLs, or other moving -targets. Exact-release installation from an install-allowed catalog requires a -valid release version and a 64-character hexadecimal SHA-256 digest for -`step.yml`, `__init__.py`, and every `extra_files` path. +Use a versioned release URL for the archive, consistent with the other +community submission forms. Use version-pinned URLs for every catalog file; the +required per-file SHA-256 mapping detects changed bytes even if a referenced +tag is later moved. Do not submit branch URLs, `releases/latest` URLs, or other +floating targets. Exact-release installation from an install-allowed catalog +requires a valid release version and a 64-character hexadecimal SHA-256 digest +for `step.yml`, `__init__.py`, and every `extra_files` path. + +The direct `--from` archive installer does not currently accept or verify an +archive digest. The submitted Download URL is versioned release and test +evidence, not an immutable content guarantee; the per-file digests protect the +separate catalog-installation path. ## Prepare a Submission @@ -85,8 +92,8 @@ valid release version and a 64-character hexadecimal SHA-256 digest for documentation, and changelog where applicable. 2. Test valid and invalid configuration, `StepStatus`, outputs, errors, and any relevant resume, nested-step, or concurrent execution behavior. -3. Publish an immutable versioned archive (`.zip`, `.tar.gz`, or `.tgz`) and - test it: +3. Publish a versioned release archive (`.zip`, `.tar.gz`, or `.tgz`) and test + it: ```bash specify workflow step add my-step \ @@ -105,7 +112,7 @@ Maintainers currently check the form manually for: - complete identity, release, repository, license, and documentation metadata; - consistency between the package ID, manifest type key, provided class, and version; -- immutable release and file URLs; +- versioned release and catalog file URLs; - complete per-file checksum and extra-file mappings; - disclosed compatibility and runtime dependencies; and - evidence that the released archive follows the supported package shape and diff --git a/tests/test_github_workflows.py b/tests/test_github_workflows.py index 88a35fdecc..b42da26668 100644 --- a/tests/test_github_workflows.py +++ b/tests/test_github_workflows.py @@ -558,7 +558,7 @@ def test_workflow_step_submission_form_has_valid_complete_field_contract(): "description", "author", "repository", - "release-artifact-url", + "download-url", "step-yml-url", "init-url", "extra-files", @@ -581,7 +581,7 @@ def test_workflow_step_submission_form_has_valid_complete_field_contract(): "description", "author", "repository", - "release-artifact-url", + "download-url", "step-yml-url", "init-url", "extra-files", @@ -607,6 +607,19 @@ def test_workflow_step_submission_form_has_valid_complete_field_contract(): option["required"] is True for option in field_by_id["attestations"]["attributes"]["options"] ) + bundle_form = yaml.safe_load( + (forms_dir / "bundle_submission.yml").read_text(encoding="utf-8") + ) + bundle_fields = { + item["id"]: item for item in bundle_form["body"] if "id" in item + } + assert field_by_id["download-url"]["attributes"]["label"] == ( + bundle_fields["download-url"]["attributes"]["label"] + ) + download_description = field_by_id["download-url"]["attributes"]["description"] + assert "versioned" in download_description + assert "immutable" not in download_description + assert "immutable" not in yaml.safe_dump(workflow_step_form).lower() def test_workflow_step_submission_form_documents_intake_only_phase(): From 36e2547b7f1a38d236750a14c8d75d1cb9f2f610 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:16:15 -0500 Subject: [PATCH 5/6] docs: address workflow step intake review Add the standard required AI disclosure to the manual intake form and clarify how release and testing evidence relate to catalog digests. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/workflow_step_submission.yml | 13 +++++++++++++ docs/community/workflow-steps.md | 6 +++--- tests/test_github_workflows.py | 9 +++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/workflow_step_submission.yml b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml index c62042726f..583fc6be03 100644 --- a/.github/ISSUE_TEMPLATE/workflow_step_submission.yml +++ b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml @@ -236,3 +236,16 @@ body: label: Additional Context description: Optional links or notes that help maintainers verify the submitted metadata placeholder: Release notes, compatibility notes, or related issue links + + - type: textarea + id: ai-disclosure + attributes: + label: AI Disclosure + description: >- + Per our [Contributing guidelines](https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md#ai-contributions-in-spec-kit), + any AI assistance used to create this issue must be disclosed. If you used AI, name the agent/tool, model(s), + settings/mode (reasoning effort; autonomous vs. human-supervised), and extent. Otherwise leave this as "N/A". + This does not change how your issue is handled — it just gives maintainers visibility into model/agent usage. + value: "N/A" + validations: + required: true diff --git a/docs/community/workflow-steps.md b/docs/community/workflow-steps.md index c8f371565b..f71c9e0270 100644 --- a/docs/community/workflow-steps.md +++ b/docs/community/workflow-steps.md @@ -82,9 +82,9 @@ requires a valid release version and a 64-character hexadecimal SHA-256 digest for `step.yml`, `__init__.py`, and every `extra_files` path. The direct `--from` archive installer does not currently accept or verify an -archive digest. The submitted Download URL is versioned release and test -evidence, not an immutable content guarantee; the per-file digests protect the -separate catalog-installation path. +archive digest. The submitted Download URL and Testing Details fields provide +versioned-release and installation-test evidence, not an immutable content +guarantee; the per-file digests protect the separate catalog-installation path. ## Prepare a Submission diff --git a/tests/test_github_workflows.py b/tests/test_github_workflows.py index b42da26668..199fdb3158 100644 --- a/tests/test_github_workflows.py +++ b/tests/test_github_workflows.py @@ -573,6 +573,7 @@ def test_workflow_step_submission_form_has_valid_complete_field_contract(): "testing-details", "attestations", "additional-context", + "ai-disclosure", } required_ids = { "step-id", @@ -593,6 +594,7 @@ def test_workflow_step_submission_form_has_valid_complete_field_contract(): "step-types-provided", "documentation", "testing-details", + "ai-disclosure", } assert { field["id"] @@ -620,6 +622,13 @@ def test_workflow_step_submission_form_has_valid_complete_field_contract(): assert "versioned" in download_description assert "immutable" not in download_description assert "immutable" not in yaml.safe_dump(workflow_step_form).lower() + feature_form = yaml.safe_load( + (forms_dir / "feature_request.yml").read_text(encoding="utf-8") + ) + feature_fields = { + item["id"]: item for item in feature_form["body"] if "id" in item + } + assert field_by_id["ai-disclosure"] == feature_fields["ai-disclosure"] def test_workflow_step_submission_form_documents_intake_only_phase(): From e3bdf882c407a04fd330f7f172eb670cba7f6741 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:42:57 -0500 Subject: [PATCH 6/6] docs: clarify workflow step trust boundaries Document the exact catalog extra-file path constraints and distinguish package installation from later import-time execution. Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/workflow_step_submission.yml | 5 ++++- docs/community/workflow-steps.md | 11 ++++++++--- tests/test_github_workflows.py | 7 +++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/workflow_step_submission.yml b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml index 583fc6be03..70120d472c 100644 --- a/.github/ISSUE_TEMPLATE/workflow_step_submission.yml +++ b/.github/ISSUE_TEMPLATE/workflow_step_submission.yml @@ -103,7 +103,10 @@ body: id: extra-files attributes: label: Extra File URLs - description: JSON mapping of every additional package-relative file path to its exact tag-pinned HTTPS URL; enter `{}` when there are no extra files + description: >- + JSON mapping of every additional package-relative file path to its exact tag-pinned HTTPS URL. + Paths must use forward slashes, be relative and non-empty, contain no empty, `.` or `..` segments, + and must not case-insensitively alias `step.yml` or `__init__.py`. Enter `{}` when there are no extra files. render: json placeholder: | { diff --git a/docs/community/workflow-steps.md b/docs/community/workflow-steps.md index f71c9e0270..530fb514fc 100644 --- a/docs/community/workflow-steps.md +++ b/docs/community/workflow-steps.md @@ -4,8 +4,10 @@ > Community workflow step types are independently created and maintained by > their authors. Maintainers verify submission metadata and package shape; they > do **not review, audit, endorse, or support the executable Python code**. -> Installing a custom step imports and runs that code with the user's -> privileges. Review the source before installing it. +> Installation validates and copies the package without importing its Python +> code. Later `specify workflow add`, `run`, and `resume` commands load installed +> step packages, executing import-time code with the user's privileges. Review +> the source before using an installed step. Custom workflow step packages add a `type:` that workflows can use beyond the built-in step types. The canonical intake path is the @@ -49,6 +51,9 @@ The package must satisfy the current installer and loader contract: - `__init__.py` defines a `StepBase` subclass matching that single type key. Additional classes do not make the package provide additional registered step types. +- Extra-file paths use forward slashes, are relative and non-empty, contain no + empty, `.` or `..` segments, and do not case-insensitively alias `step.yml` + or `__init__.py`. - The package stays within the current installer limits: 512 retained entries, 32 directory levels, and 50 MiB of retained content. - `.git`, `__pycache__`, and `.DS_Store` are excluded from installation. @@ -66,7 +71,7 @@ from provenance and disclosure fields used during manual review. | Step type ID | Catalog key, `step.type_key`, and matching `StepBase.type_key` | | Name, version, description, author | `step.yml` metadata and catalog/installed registry metadata | | Download URL | Versioned release archive used to test the supported direct-URL installation path | -| `step.yml`, `__init__.py`, and extra-file URLs | Exact HTTPS file URLs consumed when installing from an explicitly install-allowed catalog | +| `step.yml`, `__init__.py`, and extra-file URLs | Exact HTTPS file URLs consumed when installing from an explicitly install-allowed catalog; extra-file keys follow the package-relative path restrictions above | | Per-file SHA-256 mapping | Catalog `sha256`; keys must be exactly the files the installer downloads | | Provided type name/count | Loader invariant: one matching type key per installed package | | Repository, license, documentation, changelog | Source provenance and manual-review evidence; these are not `step.yml` fields | diff --git a/tests/test_github_workflows.py b/tests/test_github_workflows.py index 199fdb3158..d6cf317296 100644 --- a/tests/test_github_workflows.py +++ b/tests/test_github_workflows.py @@ -622,6 +622,13 @@ def test_workflow_step_submission_form_has_valid_complete_field_contract(): assert "versioned" in download_description assert "immutable" not in download_description assert "immutable" not in yaml.safe_dump(workflow_step_form).lower() + extra_files_description = field_by_id["extra-files"]["attributes"]["description"] + assert "forward slashes" in extra_files_description + assert "relative and non-empty" in extra_files_description + assert "no empty, `.` or `..` segments" in extra_files_description + assert "case-insensitively alias `step.yml` or `__init__.py`" in ( + extra_files_description + ) feature_form = yaml.safe_load( (forms_dir / "feature_request.yml").read_text(encoding="utf-8") )