feat(M018/S03): manifest generation (core + MCP + CLI)#212
Conversation
Adds `depends_on: Vec<String>` to `ChunkRef` in assay-types for Smelt manifest scheduling constraints. - Additive serde field with default/skip-serializing-if for backward compat - Updated struct-level doc; clarified order is cosmetic-only - JSON + TOML roundtrip tests, schema snapshots regenerated - All ChunkRef struct literals updated across 9 files Closes WOL-161 (M018/S02)
…_manifest New assay-core::manifest_gen module (gated behind orchestrate feature): - ManifestSource enum: Milestone(slug) and AllSpecs - generate_manifest() maps ChunkRef -> ManifestSession with depends_on - write_manifest() uses atomic tempfile-then-rename - 8 unit tests covering all paths (deps, no-deps, errors, round-trip)
New manifest_generate tool in AssayServer tool router: - ManifestGenerateParams: source (milestone/all-specs), slug, output - Delegates to manifest_gen::generate_manifest + write_manifest via spawn_blocking - Returns JSON with written path and session count - 3 handler tests: tool registration, milestone generation, missing slug error
New 'assay manifest generate' subcommand: - --from-milestone <slug>: generate from milestone chunks with depends_on - --from-specs: generate from all specs (fully parallel) - --output <path>: output file path (default: manifest.toml) - Mutually exclusive flag validation - Prints 'Written <path> (N sessions)' on success
There was a problem hiding this comment.
Pull request overview
Adds end-to-end “manifest generation” support so operators can emit a Smelt-ready manifest.toml from either a milestone’s chunks (with intra-milestone dependencies) or from all specs, exposed via both MCP and the CLI. This extends the milestone model to include chunk-level scheduling constraints (depends_on) that flow into generated run manifests.
Changes:
- Extend
assay-types::ChunkRefwithdepends_onand update JSON schema snapshots accordingly. - Add
assay-core::manifest_gen(generate + atomic write) and wire it into MCP (manifest_generate) and CLI (assay manifest generate). - Update assorted tests and milestone-creation helpers to populate the new
depends_onfield.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/assay-types/src/milestone.rs | Adds ChunkRef.depends_on and updates docs/tests for milestone chunk scheduling constraints. |
| crates/assay-types/tests/snapshots/schema_snapshots__milestone-schema.snap | Snapshot update to reflect new depends_on field and updated descriptions. |
| crates/assay-types/tests/snapshots/schema_snapshots__chunk-ref-schema.snap | Snapshot update to reflect new depends_on field and updated descriptions. |
| crates/assay-core/src/manifest_gen.rs | New core module to generate RunManifest from milestones or specs and write it atomically. |
| crates/assay-core/src/lib.rs | Exposes manifest_gen behind the orchestrate feature. |
| crates/assay-mcp/src/server.rs | Registers MCP tool manifest_generate and adds handler tests. |
| crates/assay-cli/src/main.rs | Adds top-level assay manifest command group. |
| crates/assay-cli/src/commands/mod.rs | Registers new manifest command module. |
| crates/assay-cli/src/commands/manifest.rs | New CLI subcommand implementation for assay manifest generate. |
| crates/assay-core/src/wizard.rs | Ensures wizard-created milestones initialize depends_on for chunks. |
| crates/assay-core/src/pr.rs | Updates milestone construction in tests for new depends_on field. |
| crates/assay-core/tests/pr.rs | Updates test milestones to include depends_on: vec![]. |
| crates/assay-core/tests/milestone_io.rs | Updates milestone I/O tests for new depends_on field. |
| crates/assay-core/tests/cycle.rs | Updates cycle tests for new depends_on field. |
| crates/assay-core/tests/analytics.rs | Updates analytics test milestone construction for new depends_on field. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// A reference to a `GatesSpec` chunk within a milestone, identified by slug and scheduling constraints. | ||
| /// | ||
| /// Each `ChunkRef` corresponds to a directory-based spec (a "chunk") that | ||
| /// contributes to the parent milestone. The `order` field controls the | ||
| /// canonical sequence for display and progress tracking. | ||
| /// contributes to the parent milestone. The `order` field is cosmetic display ordering only; | ||
| /// use `depends_on` to declare scheduling constraints between chunks. |
There was a problem hiding this comment.
The docs now describe ChunkRef.order as “cosmetic display ordering only”, but milestone cycle logic uses order to determine the active/next chunk (lowest-order incomplete). That makes order behaviorally significant. Please update this docstring/schema wording to reflect its role in cycle progression (or update the cycle algorithm if depends_on is intended to replace it).
| let specs_dir = config.assay_dir.join("specs"); | ||
| let scan_result = spec::scan(&specs_dir)?; | ||
|
|
||
| if scan_result.entries.is_empty() { | ||
| return Err(AssayError::Io { |
There was a problem hiding this comment.
ManifestSource::AllSpecs hard-codes the specs directory as <assay_dir>/specs, but .assay/config.toml allows overriding Config.specs_dir (and other CLI/MCP codepaths honor it). This will generate incorrect/empty manifests for projects with a non-default specs directory. Consider loading config to resolve the specs dir, or extending ManifestGenConfig to include the resolved specs path.
| prompt_layers: vec![], | ||
| file_scope: vec![], | ||
| shared_files: vec![], | ||
| depends_on: chunk.depends_on.clone(), | ||
| }) |
There was a problem hiding this comment.
Milestone generation copies ChunkRef.depends_on directly into ManifestSession.depends_on but does not validate unknown deps, self-deps, or cycles. Since the output is meant to be consumed as a DAG by Smelt/orchestrate, generate_manifest should validate the dependency graph (e.g., via orchestrate::dag::DependencyGraph::from_manifest) and return a clear error before writing an invalid manifest. Adding unit tests for unknown/cyclic deps would help prevent regressions.
- Use typed ManifestSourceParam enum in MCP params (proper JSON schema enum constraint) - Add manifest_gen imports to server.rs (style: avoid long module paths in handler) - Fix AssayError::Io message duplication in empty-chunks/no-specs error paths - Fix write_manifest doc: clarify crash-safety guarantee (no corruption, may leave temp file) - Fix ManifestSource doc: accurate assay_dir path reference - Add PartialEq + Eq to ManifestSource for testability - Remove unused dir.clone() in CLI handler - Fix CLI command description (drop 'manage' — only generate exists) - Fix manifest_gen module doc: clarify feature gate rationale - Fix depends_on field doc: remove (S03) slice ref, remove upward coupling to manifest_generate - Improve TOML spec comment in test helper - Update chunk-ref and milestone schema snapshots - Add tracing::warn on manifest_generate error path
* feat(M018/S02): ChunkRef dependency encoding — add depends_on field Adds `depends_on: Vec<String>` to `ChunkRef` in assay-types for Smelt manifest scheduling constraints. - Additive serde field with default/skip-serializing-if for backward compat - Updated struct-level doc; clarified order is cosmetic-only - JSON + TOML roundtrip tests, schema snapshots regenerated - All ChunkRef struct literals updated across 9 files Closes WOL-161 (M018/S02) * feat(S03/T01): add manifest_gen module with generate_manifest + write_manifest New assay-core::manifest_gen module (gated behind orchestrate feature): - ManifestSource enum: Milestone(slug) and AllSpecs - generate_manifest() maps ChunkRef -> ManifestSession with depends_on - write_manifest() uses atomic tempfile-then-rename - 8 unit tests covering all paths (deps, no-deps, errors, round-trip) * feat(S03/T02): register manifest_generate MCP tool + handler tests New manifest_generate tool in AssayServer tool router: - ManifestGenerateParams: source (milestone/all-specs), slug, output - Delegates to manifest_gen::generate_manifest + write_manifest via spawn_blocking - Returns JSON with written path and session count - 3 handler tests: tool registration, milestone generation, missing slug error * feat(S03/T03): add assay manifest generate CLI subcommand New 'assay manifest generate' subcommand: - --from-milestone <slug>: generate from milestone chunks with depends_on - --from-specs: generate from all specs (fully parallel) - --output <path>: output file path (default: manifest.toml) - Mutually exclusive flag validation - Prints 'Written <path> (N sessions)' on success * fix(S03): address PR review findings - Use typed ManifestSourceParam enum in MCP params (proper JSON schema enum constraint) - Add manifest_gen imports to server.rs (style: avoid long module paths in handler) - Fix AssayError::Io message duplication in empty-chunks/no-specs error paths - Fix write_manifest doc: clarify crash-safety guarantee (no corruption, may leave temp file) - Fix ManifestSource doc: accurate assay_dir path reference - Add PartialEq + Eq to ManifestSource for testability - Remove unused dir.clone() in CLI handler - Fix CLI command description (drop 'manage' — only generate exists) - Fix manifest_gen module doc: clarify feature gate rationale - Fix depends_on field doc: remove (S03) slice ref, remove upward coupling to manifest_generate - Improve TOML spec comment in test helper - Update chunk-ref and milestone schema snapshots - Add tracing::warn on manifest_generate error path --------- Co-authored-by: Test <test@test.com>
Summary
Closes M018/S03 — Manifest generation (core + MCP + CLI).
Operators can now generate a Smelt-ready
manifest.tomlfrom a planned milestone or all specs with a single command, instead of hand-writing it.What's in this PR
assay-core:
manifest_genmodule (T01)New
crates/assay-core/src/manifest_gen.rsmodule (gated behindorchestratefeature):ManifestSourceenum —Milestone(slug)andAllSpecsvariantsgenerate_manifest(source, config) -> Result<RunManifest>— reads milestone chunks viamilestone_load, maps eachChunkRef.depends_onthrough toManifestSession.depends_on; or scans all specs for fully-parallel sessionswrite_manifest(manifest, path)— atomic tempfile-then-rename writeassay-mcp:
manifest_generatetool (T02)ManifestGenerateParamsstruct withsource,slug,outputfieldsmanifest_generatetool registered inAssayServerrouter{ "written": "<path>", "sessions": N }on successassay-cli:
assay manifest generatesubcommand (T03)--from-milestone <slug>— generate from named milestone--from-specs— generate from all specs (fully parallel)--output <path>— defaults tomanifest.tomlWritten <path> (N sessions)on successVerification
cargo test -p assay-core -- manifest_gencargo test -p assay-mcp -- manifest_generatecargo run -p assay-cli -- manifest generate --helpjust readyRequirements