Feat/workflow contract - #43
Merged
Merged
Conversation
Extend FrontmatterParser.parse_yaml to accumulate nested key/value blocks inside object-list items (e.g. handoffs: under stages[]). Empty-value 4-space keys trigger accumulation of subsequent 6-space- indented lines, stored as a stripped raw string for callers to re-parse. A trailing nested block left open at EOF is correctly flushed. This is the prerequisite for reading workflow.stages[].handoffs from .vstack/config.yaml without a full YAML library.
…fact flag Move handoffs and artifacts from top-level agent config.yaml into a defaults: block. The generator extracts this block and re-exposes it at the expected paths, keeping generated agent.md output identical. Add baseline: true flag on output artifact entries. Flagged items are rendered into a dedicated '### baseline docs you maintain' table with an explicit instruction to keep them current — separating living docs from per-session deliverables. Add AGENT_ARTIFACTS_BASELINE template token and inject it into all six role agent templates. Refactor AgentGenerator: - _extract_defaults(): re-parses raw indented string from minimal parser - load_artifact_config(): merges defaults into config, resolves handoffs - _resolve_handoffs(): derives next agent from workflow stage order - _resolve_output_entries(): now an instance method; adds baseline field - _build_baseline_section(): renders the new baseline table - _build_handoffs(): compatibility shim kept for test call sites HANDOFF_ITEM_SCHEMA: agent field changed to required=False (target is now derived from workflow stage order, not hardcoded in config).
Add a workflow: block to the project config schema. It defines the full
pipeline as an ordered list of stages with gate, hitl, and handoffs fields.
Stage schema:
role: agent name (required)
gate: required | optional | skip
hitl: always | on-change | never
handoffs: single dict or list of dicts with prompt, agent?, label?
hitl semantics:
always — pipeline pauses for human approval before handoff
on-change — pause only when the stage made changes (default for optional)
never — pipeline continues without human approval (explicit opt-out)
CLI changes:
CommandLineInterface._read_workflow_stages(): reads and parses workflow
block from .vstack/config.yaml; preserves hitl field when present
CommandLineInterface._parse_stage_handoffs(): normalises handoffs to
[{prompt, agent, label}] list; supports dict, list, and legacy
handoff_prompt flat-key forms
CommandService: accepts workflow_stages kwarg, passes to AgentGenerator
InitCommand._warn_unknown_workflow_roles(): emits stderr warning for
stage roles with no matching agent template (non-fatal)
Seed .vstack/config.yaml and the project template with the default
six-stage pipeline (commented-out in the template; active in this repo).
ADR-023 (workflow contract): documents the workflow: block added to .vstack/config.yaml as the source of truth for pipeline stage order, gate policy, hitl policy, and handoff prompts. Covers the baseline: flag on output artifacts and the defaults: restructuring in agent config.yaml. Supersedes the handoff-in-agent-config approach. ADR-024 (subagent orchestration): documents the decision to use VS Code native subagents (runSubagent tool, agents: frontmatter) for the orchestrated pipeline. Defines the planner agent as coordinator. Rejects scripts/runner.py, MCP orchestrator, and planner-only alternatives. Supersedes ADR-004. ADR-004: status changed to 'superseded by ADR-024'; amendment added explaining that the original blocker (no platform support for subagents) was resolved when Microsoft shipped the runSubagent tool in May 2026. overview.md: ADR-004 row marked superseded; ADR-023 and ADR-024 rows added to the decision table; pipeline reference links updated. roadmap.md: workflow contract and orchestrated pipeline rows moved from candidate to in progress with ADR references.
…ormatting Regenerate all .github/ artifacts from updated templates: - Agent files reflect the new defaults: config structure, baseline artifact tables, and workflow-derived handoff targets - All skill/instruction/prompt VSTACK-META footers bumped to vstack_version 3.0.0 Update .vstack/vstack.json with new checksums and install timestamp. Fix CHANGELOG.md formatting: replace * bullets with - bullets and remove double blank lines between sections (mdformat compliance).
Introduce pyyaml>=6.0 as the sole runtime dependency and replace the
hand-rolled state-machine parser (~200 lines) in frontmatter/parser.py
with yaml.safe_load. Keeps the public API unchanged (parse, parse_yaml).
Pre-processes bare '- *' list items to '- ''*''' before passing to
yaml.safe_load, since VS Code uses '*' as a wildcard in agents: lists
and PyYAML treats bare '*' as an alias marker.
Also quote YAML-special leading characters ('*', '&', '!') in list items
emitted by the serializer, fix int version comparison in cli/init.py
(PyYAML parses 'version: 20260421027' as int), and remove dead-code
string-reparse fallbacks from agents/generator.py and cli/interface.py.
Add ADR-025 (pyyaml-runtime-dependency) and amend ADR-006 with a scope
note clarifying it covers skill template content only, not pip deps.
pyproject.toml: add pyyaml>=6.0 to [project] dependencies and to
[testenv] deps (tox uses package=skip so it does not auto-install).
Re-run 'vstack install' after serializer fix: all six role agent files now emit '- ''*''' instead of '- *' in the agents: list, which is valid YAML and safe for downstream yaml.safe_load parsing.
eschaar
force-pushed
the
feat/workflow-contract
branch
from
May 9, 2026 19:50
1c65872 to
4ce284c
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR centralizes workflow/pipeline configuration into a project-owned workflow: block (seeded in .vstack/config.yaml) and updates agent generation to derive handoffs and “baseline docs” sections from that contract. It also adds a new vstack migrate CLI command and a packaged _migrations/ record format to move docs/ artifacts across major-version path changes, while replacing the custom frontmatter YAML parser with PyYAML.
Changes:
- Introduce workflow-contract-driven agent generation (handoffs derived from
workflow.stages, plusbaseline: trueoutput grouping). - Add
vstack migratecommand with YAML migration records, CLI wiring, and tests. - Replace the hand-rolled YAML frontmatter parser with PyYAML and update docs/readmes accordingly.
Reviewed changes
Copilot reviewed 112 out of 113 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/vstack/skills/test_templates.py | Update frontmatter parser call style |
| tests/vstack/frontmatter/test_parser.py | Expand PyYAML-based parser coverage |
| tests/vstack/cli/test_registry.py | Move registry tests to CLI interface method |
| tests/vstack/cli/test_parser.py | Assert migrate subcommand presence |
| tests/vstack/cli/test_migrate.py | New tests for vstack migrate |
| tests/vstack/cli/test_interface.py | Add workflow stage parsing tests + wiring updates |
| tests/vstack/cli/test_init.py | Add unknown workflow role warning tests |
| tests/vstack/cli/test_helpers.py | Update normalize helper call site |
| tests/vstack/cli/test_catalog.py | Assert migrate command in catalog |
| tests/vstack/artifacts/test_generator.py | Tighten schema validation test input |
| tests/vstack/agents/test_role_wiring.py | Update wiring expectations for workflow-generated handoffs |
| tests/vstack/agents/test_generation.py | Seed workflow config for handoff generation test |
| src/vstack/utils.py | New hashing utilities module |
| src/vstack/manifest/store.py | Move hashing helpers + manifest preservation helpers into class |
| src/vstack/manifest/init.py | Adjust manifest package exports |
| src/vstack/frontmatter/serializer.py | Quote YAML-special list items |
| src/vstack/frontmatter/parser.py | Replace custom YAML parsing with PyYAML |
| src/vstack/constants.py | Add MIGRATIONS_ROOT package path |
| src/vstack/cli/uninstall.py | Use new manifest preservation helpers + base normalize |
| src/vstack/cli/service.py | Pass workflow stages into AgentGenerator |
| src/vstack/cli/registry.py | Remove standalone registry builder (deleted) |
| src/vstack/cli/parser.py | Add migrate subcommand args |
| src/vstack/cli/migrate.py | Implement docs migration command |
| src/vstack/cli/interface.py | Read workflow stages + build registry internally |
| src/vstack/cli/init.py | Warn on unknown workflow roles + update manifest helper calls |
| src/vstack/cli/helpers.py | Remove normalize helper module (deleted) |
| src/vstack/cli/catalog.py | Register migrate command |
| src/vstack/cli/base.py | Add _normalize_targeted_names helper |
| src/vstack/artifacts/generator.py | Reuse a shared FrontmatterParser instance |
| src/vstack/agents/generator.py | Generate handoffs/baseline sections from workflow + defaults blocks |
| src/vstack/agents/config.py | Make handoffs[].agent optional in schema |
| src/vstack/_templates/project/.vstack/config.yaml | Seed commented workflow contract template |
| src/vstack/_templates/agents/tester/template.md | Insert baseline section placeholder |
| src/vstack/_templates/agents/tester/config.yaml | Move artifacts/handoffs under defaults: + baseline paths |
| src/vstack/_templates/agents/release/template.md | Insert baseline section placeholder |
| src/vstack/_templates/agents/release/config.yaml | Move artifacts under defaults: |
| src/vstack/_templates/agents/product/template.md | Insert baseline section placeholder |
| src/vstack/_templates/agents/product/config.yaml | Move artifacts/handoffs under defaults: + baseline paths |
| src/vstack/_templates/agents/engineer/template.md | Insert baseline section placeholder |
| src/vstack/_templates/agents/engineer/config.yaml | Move artifacts/handoffs under defaults: |
| src/vstack/_templates/agents/designer/template.md | Insert baseline section placeholder |
| src/vstack/_templates/agents/designer/config.yaml | Move artifacts/handoffs under defaults: + baseline paths |
| src/vstack/_templates/agents/architect/template.md | Insert baseline section placeholder |
| src/vstack/_templates/agents/architect/config.yaml | Move artifacts/handoffs under defaults: + baseline paths |
| src/vstack/_migrations/v2_to_v3.yaml | Add v2→v3 docs move record |
| src/vstack/_migrations/README.md | Document migration record schema |
| README.md | Add install/upgrade/migrate guidance + PyYAML runtime note |
| README-pypi.md | Mirror install/upgrade/migrate guidance for PyPI |
| pyproject.toml | Add PyYAML runtime dep + include _migrations in package |
| poetry.lock | Move PyYAML to main deps + add types-PyYAML |
| docs/product/roadmap.md | Mark workflow contract shipped + update orchestration status |
| docs/product/requirements.md | Update runtime dependency statement |
| docs/architecture/overview.md | Update NFRs + ADR index entries |
| docs/architecture/adr/026-docs-artifact-migration-policy.md | New ADR for docs path stability + migration policy |
| docs/architecture/adr/025-pyyaml-runtime-dependency.md | New ADR for PyYAML dependency decision |
| docs/architecture/adr/024-subagent-orchestration.md | New ADR superseding ADR-004 |
| docs/architecture/adr/023-workflow-contract.md | New ADR defining workflow contract |
| docs/architecture/adr/006-no-runtime-dependency.md | Scope clarification + orchestration update |
| docs/architecture/adr/004-option-a-to-b-pipeline.md | Mark superseded + add amendment |
| CHANGELOG.md | Reformat existing entries (bulleted lists) |
| .vstack/vstack.json | Refresh manifest install timestamp/checksums |
| .vstack/config.yaml | Add workflow contract block to repo config |
| .github/agents/architect.agent.md | Regenerated agent output (handoffs/baseline + wildcard quoting) |
| .github/agents/designer.agent.md | Regenerated agent output (handoffs/baseline + wildcard quoting) |
| .github/agents/engineer.agent.md | Regenerated agent output (handoffs/baseline + wildcard quoting) |
| .github/agents/product.agent.md | Regenerated agent output (handoffs/baseline + wildcard quoting) |
| .github/agents/release.agent.md | Regenerated agent output (baseline placeholder + wildcard quoting) |
| .github/agents/tester.agent.md | Regenerated agent output (handoffs label change + wildcard quoting) |
| .github/skills/adr/SKILL.md | Regenerated skill output |
| .github/skills/analyse/SKILL.md | Regenerated skill output |
| .github/skills/architecture/SKILL.md | Regenerated skill output |
| .github/skills/aws-cli/SKILL.md | Regenerated skill output |
| .github/skills/cicd/SKILL.md | Regenerated skill output |
| .github/skills/cloudformation/SKILL.md | Regenerated skill output |
| .github/skills/code-review/SKILL.md | Regenerated skill output |
| .github/skills/codeql/SKILL.md | Regenerated skill output |
| .github/skills/concise/SKILL.md | Regenerated skill output |
| .github/skills/consult/SKILL.md | Regenerated skill output |
| .github/skills/container/SKILL.md | Regenerated skill output |
| .github/skills/conventional-commit/SKILL.md | Regenerated skill output |
| .github/skills/debug/SKILL.md | Regenerated skill output |
| .github/skills/dependabot/SKILL.md | Regenerated skill output |
| .github/skills/dependency/SKILL.md | Regenerated skill output |
| .github/skills/design/SKILL.md | Regenerated skill output |
| .github/skills/docs/SKILL.md | Regenerated skill output |
| .github/skills/explore/SKILL.md | Regenerated skill output |
| .github/skills/gdpr/SKILL.md | Regenerated skill output |
| .github/skills/gh-issues/SKILL.md | Regenerated skill output |
| .github/skills/gh-release/SKILL.md | Regenerated skill output |
| .github/skills/guardrails/SKILL.md | Regenerated skill output |
| .github/skills/helm/SKILL.md | Regenerated skill output |
| .github/skills/incident/SKILL.md | Regenerated skill output |
| .github/skills/inspect/SKILL.md | Regenerated skill output |
| .github/skills/k8s/SKILL.md | Regenerated skill output |
| .github/skills/migrate/SKILL.md | Regenerated skill output |
| .github/skills/onboard/SKILL.md | Regenerated skill output |
| .github/skills/openapi/SKILL.md | Regenerated skill output |
| .github/skills/performance/SKILL.md | Regenerated skill output |
| .github/skills/postmortem/SKILL.md | Regenerated skill output |
| .github/skills/pr/SKILL.md | Regenerated skill output |
| .github/skills/rancher/SKILL.md | Regenerated skill output |
| .github/skills/rca/SKILL.md | Regenerated skill output |
| .github/skills/refactor/SKILL.md | Regenerated skill output |
| .github/skills/release-notes/SKILL.md | Regenerated skill output |
| .github/skills/requirements/SKILL.md | Regenerated skill output |
| .github/skills/secret-scan/SKILL.md | Regenerated skill output |
| .github/skills/security/SKILL.md | Regenerated skill output |
| .github/skills/terraform/SKILL.md | Regenerated skill output |
| .github/skills/terragrunt/SKILL.md | Regenerated skill output |
| .github/skills/threat-model/SKILL.md | Regenerated skill output |
| .github/skills/verify/SKILL.md | Regenerated skill output |
| .github/skills/vision/SKILL.md | Regenerated skill output |
…t/list serialization
Restores the class-level static-method call form FrontmatterParser.parse(...)
and FrontmatterParser.parse_yaml(...) that was broken by the OOP-cleanup
commit. Updates every call site in artifacts/generator.py, cli/interface.py,
and cli/service.py.
Fixes FrontmatterSerializer._append_raw_field to use yaml.dump for dict and
list values instead of str(), which was emitting invalid Python-repr YAML
(e.g. {'owner': 'vstack', ...}) into generated agent frontmatter.
Adds tests for dict-value and list-value raw-field serialization.
…grate Implements the documented fallback in AgentGenerator._resolve_handoffs: when no workflow stages are configured but a handoff_prompt is present, returns a single generic handoff entry without an agent: key, preserving v3 behaviour. Previously the method returned [] in all no-workflow cases, discarding the agent's own prompt. Removes self._parser instance from AgentGenerator.__init__; all call sites now use FrontmatterParser class-level static methods directly. Adds a None guard in cli/migrate.py _detect_installed_major so it does not raise AttributeError when ManifestFile.read() returns None. Updates agent generator tests to cover the fallback case and the static call form.
preserve_existing_entry() and preserved_manifest_entries() were removed from vstack.manifest.__all__ as part of the OOP-cleanup refactor, but both are part of the public API and removing them is a backward-incompatible change for the current minor bump. Re-adds both as module-level wrapper functions in manifest/__init__.py that delegate to Manifest.preserve_existing_entry() and Manifest.preserved_entries() respectively and emit DeprecationWarning(stacklevel=2) so callers see the warning at their own call site. Both are listed in __all__ with a comment noting they will be dropped in the next minor release. Adds TestDeprecatedModuleLevelHelpers with three tests covering delegation, warning emission, and the None-manifest edge case.
…ons README ADR-023: corrects section 2 — agents use defaults.handoffs.prompt (nested block), not a flat handoff_prompt: key. Adds a real config.yaml example. Marks section 4 (project-level artifact overrides) as deferred / not implemented in this release rather than describing an unimplemented feature as if it were shipped. ADR-026: updates status to accepted and documents the shipped vstack migrate command with its CLI flags (--target, --from, --to, --dry-run). src/vstack/_migrations/README.md: removes 'not yet implemented' language; describes the actual migrate command behaviour and flags.
Regenerates all .github/skills/*/SKILL.md files and .vstack/vstack.json after the frontmatter serializer fix (dict/list metadata fields now emit valid block YAML instead of Python repr strings).
…signer, release product, architect, and designer agents lacked execute, preventing them from running terminal commands (find, git log, OpenAPI validators, doc aggregation). release lacked web, needed for CI-status, GitHub release, and PyPI checks. All six agents now have: read search edit execute web vscode todo agent. The agent tool is already present everywhere — subagent invocations are already wired correctly via the agents: ["*"] field.
…l configs
The agentskills.io spec defines allowed-tools as an experimental field that
pre-approves a space-separated set of tool names for a skill, preventing
scope creep when a skill is loaded into an agent that has broader permissions.
SKILL_SCHEMA gains a new FieldSpec("allowed-tools") entry, slotted between
the metadata and argument-hint fields.
Skill tool tiers:
- read-only / analysis (analyse, code-review, concise, consult, explore, inspect):
execute read search
- network-dependent (aws-cli, codeql, dependency, gh-issues, gh-release, pr,
secret-scan): execute read search edit web
- all others (31 skills): execute read search edit
…g changes Artifact changes driven by: - execute added to product, architect, designer agents - web added to release agent - allowed-tools field added to all 44 skill configs Generated via: python3 -m vstack install
…to candidate The workflow contract (workflow.stages, gate, hitl, handoffs) ships in this release, satisfying the prerequisite for ADR-024. However the planner agent and worker-agent wiring are not yet implemented; no planner template exists in src/vstack/_templates/agents/. Align the feature table (was 'in progress') with the detail section header (already 'candidate') and update the detail body to reference the shipped prerequisite and list concrete next steps.
… security.yml env Node.js 24 is now the supported runtime for GitHub Actions JavaScript runners. FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 was only set in publish.yml; add it to check, commit, verify, security, release, and automerge. Also: - security.yml was missing POETRY_VIRTUALENVS_IN_PROJECT=true despite running poetry install; without it the venv lands in the global cache dir, making the 'cache: poetry' step in setup-python point at the wrong location - publish.yml had a dead 'cache: poetry' / 'cache-dependency-path' on the setup-python step; publish only runs 'poetry build', never 'poetry install', so no venv is created and the cache is never populated or restored
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships the workflow contract source-of-truth feature (ADR-023) and the
vstack migratecommand (ADR-026).Workflow contract:
workflow:block seeded in.vstack/config.yamlbyvstack install, withstage,gate,hitl, andhandoffs.promptfor all six rolesconfig.yamldrops thehandoffs:block; each agent now carries ahandoff_prompt:string; the generator reads the workflow config to produce the full handoff section in.agent.mdbaseline:flag on output artifacts; agents explicitly distinguish baseline docs (living) from per-session deliverablesdefaults:block in agentconfig.yamlfor shared frontmatter fieldshandoff_prompt:)Docs migration:
vstack migratecommand moves docs artifact files between major versions with--dry-run,--from,--toflagssrc/vstack/_migrations/v2_to_v3.yamlmigration record (architecture.md, design.md, report files)Documentation:
vstack migrate)workflow contract source-of-truthmarked shippedWhy
Before this change, pipeline configuration was scattered across six agent configs. There was no central, machine-readable description of the pipeline order, gate policy, or handoff prompts. This blocked the future
plannerorchestrator (ADR-024) and made handoff prompts tightly coupled to stage names.vstack migrateis the upgrade path for projects on an older major version — without it, docs files would be silently left at stale paths after a major upgrade.Version impact