Skip to content

feat: artifact pipeline — typed artifact flow between workflow stages (v0.7.0) - #111

Merged
nutt-adam merged 6 commits into
mainfrom
feat/artifact-pipeline
Mar 22, 2026
Merged

feat: artifact pipeline — typed artifact flow between workflow stages (v0.7.0)#111
nutt-adam merged 6 commits into
mainfrom
feat/artifact-pipeline

Conversation

@nutt-adam

@nutt-adam nutt-adam commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Artifact Pipeline: Prompt steps can now capture and pass artifacts between workflow stages via artifact_glob and artifact_name fields
  • inject_files template expansion: {{output.step_id.path}} references allow artifacts from prior steps to be injected into downstream agents
  • Dashboard artifact labels: Flow connectors show artifact names during active runs
  • Dry-run validation: Validates gstack-slug availability when {slug} is used in artifact_glob
  • Variable interpolation: {slug}, {workspace}, {agent}, ~ expanded in glob patterns

Test Coverage

All new code paths have test coverage. 15 new tests added (326 → 341).

CODE PATH COVERAGE
===========================
[+] src/config/mod.rs — validation
    ├── [★★★ TESTED] artifact_glob + artifact_name parse
    ├── [★★★ TESTED] glob without name rejects
    ├── [★★★ TESTED] name without glob rejects  
    ├── [★★★ TESTED] requires step id
    ├── [★★★ TESTED] requires wait_for_idle
    └── [★★★ TESTED] invalid chars in name rejects

[+] src/automation/mod.rs — capture pipeline
    ├── [★★★ TESTED] expand_artifact_glob ({workspace}, {agent})
    ├── [★★★ TESTED] tilde expansion
    ├── [★★★ TESTED] snapshot_artifact_glob
    ├── [★★★ TESTED] capture picks newest new file
    ├── [★★★ TESTED] zero new files fails
    ├── [★★★ TESTED] multiple files picks newest
    ├── [★★★ TESTED] store_artifact_output
    ├── [★★★ TESTED] render_template with artifact path
    └── [★★★ TESTED] gstack-slug missing → actionable error

COVERAGE: 15/15 paths tested (100%)

Pre-Landing Review

8 issues found and auto-fixed during /review:

  • [AUTO-FIXED] inject_files template expansion produced broken double-paths → rewrote with raw string + absolute path handling
  • [AUTO-FIXED] artifact_name not validated against [A-Za-z0-9_-] (Codex P3) → added character validation
  • [AUTO-FIXED] artifact steps without wait_for_idle could race (Codex P1) → added validation
  • [AUTO-FIXED] store_artifact_output returned .raw path, breaking resume (Codex P2) → returns canonical JSON path
  • [AUTO-FIXED] snapshot_artifact_glob swallowed errors (Claude [v0.2] Policy enforcement parity across runtimes #6) → returns Result
  • [AUTO-FIXED] resolve_gstack_slug ignored exit code (Claude [v0.2] Cost and budget guardrails #8) → checks status.success()
  • [AUTO-FIXED] file_name().unwrap_or_default() could produce empty filename (Claude [v0.2] Operator install and validation path #9) → returns error
  • [AUTO-FIXED] unsafe env mutation in test without #[serial] (Claude [v0.2] Event stream (SSE/WebSocket) #5) → added #[serial_test::serial]

Adversarial Review

Large-tier (840 lines): Claude structured + Codex structured + Claude adversarial + Codex adversarial.
All high-confidence findings addressed (Codex P1-P3 + Claude #2,5,6,8,9).

Test plan

  • All Rust tests pass (341 unit + 1 integration)
  • cargo clippy passes with no warnings
  • cargo fmt --check passes
  • Dry-run validates artifact pipeline workflow correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Steps can now capture generated artifacts (files) and pass them to downstream steps using glob patterns and template references.
    • Dashboard visualization now displays artifact names flowing between workflow stages during active runs.
    • Artifact glob patterns support variable interpolation: {slug}, {workspace}, {agent}, and ~ (home directory).
    • Dry-run mode validates artifact configurations before execution.
  • Documentation

    • Updated changelog and README with artifact capture capabilities and configuration details.

nutt-adam and others added 4 commits March 22, 2026 15:58
…or prompt steps

Add artifact_glob and artifact_name fields to Prompt workflow steps, enabling
typed artifact flow between pipeline stages. After a skill completes, tutti
globs for new files, captures the newest, and registers it as a step output
available to downstream steps via {{output.step_id.path}} templates.

Key capabilities:
- Pre-step snapshot prevents race conditions with concurrent runs
- inject_files supports {{output.step_id.path}} template expansion
- Variable interpolation: {slug}, {workspace}, {agent}, ~ in glob patterns
- Config validation: requires wait_for_idle, valid artifact_name chars, paired fields
- gstack-slug integration for {slug} resolution
- 15 new tests covering all codepaths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show artifact_glob and artifact_name in dry-run table and JSON output.
Validate gstack-slug availability at dry-run time when {slug} is used
in artifact_glob patterns.
Show artifact names as labels on pipeline flow connectors during active
workflow runs. Labels appear above the connector line between stages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 22, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@nutt-adam has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 10 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d4dbc867-cbd7-4e2e-bd42-98861949ef1e

📥 Commits

Reviewing files that changed from the base of the PR and between 4ecd945 and 189d81f.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • README.md
  • dashboard/app.js
  • dashboard/style.css
  • src/automation/mod.rs
📝 Walkthrough

Walkthrough

This PR introduces artifact-carrying behavior across workflow stages. Steps can now capture files matching a glob pattern (with variable expansion) and register them as outputs, which downstream steps can reference in file injection and templates. Changes include configuration schema additions, executor-level snapshotting and capture logic, dry-run validation, and dashboard visualization of artifacts between stages.

Changes

Cohort / File(s) Summary
Metadata & Documentation
CHANGELOG.md, Cargo.toml, README.md
Version bump to 0.7.0, added glob dependency, documented new artifact capture fields and template references.
Configuration Schema
src/config/mod.rs
Added artifact_glob and artifact_name optional fields to WorkflowStepConfig::Prompt with validation rules: fields must be paired, require step id and wait_for_idle: true, and artifact_name must be ASCII alphanumeric plus underscore/hyphen.
Core Automation Logic
src/automation/mod.rs
Implemented artifact snapshotting before step execution and capture after completion, supporting variable expansion (~, {workspace}, {agent}, {slug}). Enhanced inject_files template rendering and added validate_gstack_slug_available() for slug validation. Extended ResolvedStep::Prompt with inject_files_raw, artifact_glob, artifact_name.
CLI & Dry-Run
src/cli/run.rs
Added slug validation during dry-run for steps with {slug} in artifact glob. Extended DryRunStep::Prompt with optional artifact fields and enhanced step summary display.
Dashboard Visualization
dashboard/app.js, dashboard/style.css
Added artifactName tracking on step start events, introduced artifactBetweenStages() helper to detect artifacts flowing between stages, and conditionally render artifact labels on pipeline connectors with styling for absolute positioning and monospaced appearance.

Sequence Diagram

sequenceDiagram
    participant Executor
    participant FileSystem as File System
    participant OutputRegistry as Output Registry
    participant Dashboard

    Executor->>FileSystem: Snapshot files matching artifact_glob (pre-step)
    Note over Executor,FileSystem: Expand variables: ~, {workspace}, {agent}, {slug}
    
    Executor->>Executor: Execute prompt step
    
    Executor->>FileSystem: Scan for new files matching expanded glob (post-step)
    FileSystem-->>Executor: Return newest matching file
    
    Executor->>OutputRegistry: Register artifact as step output<br/>(JSON + .raw copy)
    OutputRegistry-->>Executor: Output stored
    
    Executor->>Dashboard: Emit step.started event with artifact_name
    Dashboard->>Dashboard: Query artifactBetweenStages(prevStage, stage)
    Dashboard->>Dashboard: Render artifact label on pipeline connector
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Artifacts now hop between stages,
Glob patterns capture files through ages,
Variables expand with a twitch,
Dashboard labels make flows rich,
Tutti workflows grow their wages!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is comprehensive and addresses most template requirements, but the Versioning section checklist items are not explicitly checked off and SemVer choice selection is not filled in. Explicitly check off the Versioning checklist items and fill in the SemVer choice (MINOR is appropriate for new capability) and version number field.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: introducing an artifact pipeline feature for typed artifact flow between workflow stages, with version bump to 0.7.0.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/artifact-pipeline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 6-12: The changelog uses inconsistent terminology and a mismatched
template key; update the Artifact Pipeline entry to consistently refer to
"skill" (not "prompt steps") and change the template example to the README's
placeholder style by using the `artifact_name` key (and keep `artifact_glob` and
`artifact_name` symbol names intact); also update the inject_files line to show
the supported template reference using `{{output.artifact_name.path}}` and
ensure both descriptions reference "skill" and the `artifact_name` placeholder
consistently so operators know which key to use.

In `@dashboard/app.js`:
- Around line 130-148: artifactBetweenStages currently inspects every run in
appState.runs and can return artifact labels from terminal runs; update the
function to skip any run whose status is not "running" by checking run.status
=== "running" (or !== "running" to continue) before iterating run.steps in
artifactBetweenStages so only active runs contribute labels.

In `@dashboard/style.css`:
- Around line 141-153: The .artifact-label rule uses an incorrect CSS variable
name; replace var(--mono) with the defined variable var(--font-mono) in the
.artifact-label font-family declaration so the label uses the JetBrains Mono
font; update the font-family in the .artifact-label selector accordingly.

In `@README.md`:
- Line 267: The README documents the artifact output interpolation as
{{output.artifact_name.path}} but the changelog uses {{output.step_id.path}},
which will confuse users; update the README text to use the canonical key format
used by the codebase (choose either {{output.artifact_name.path}} or
{{output.step_id.path}}) or explicitly state both supported forms and when to
use each, and ensure the example inject_files snippet and the surrounding
sentence reference the exact template key you choose so docs and changelog
match.

In `@src/automation/mod.rs`:
- Around line 2548-2584: store_artifact_output currently saves a JSON wrapper to
canonical_path and copies the raw artifact to raw_path but returns
StepOutputValue.path = canonical_path, so render_template/inject_files will pick
the JSON wrapper instead of the raw artifact; change the returned/exposed path
to the raw artifact by setting StepOutputValue.path to raw_path (while still
calling save_workflow_output and keeping canonical_path for the persisted JSON),
e.g. keep json_value and canonical_path as-is, perform the std::fs::copy to
produce raw_path, and then return StepOutputValue { path: raw_path, json:
json_value } so {{output.<name>.path}} refers to the raw file that downstream
inject_files should copy.
- Around line 1486-1546: The artifact capture/store logic (using
capture_artifact and store_artifact_output and updating outputs, output_files,
step_results, failed_steps, and success) must be extracted into a small helper
(e.g., capture_and_store_artifact) that accepts the run context (run_id,
step_index, started, artifact_pre_snapshot, artifact_name, project_root, etc.)
and returns a Result<saved_artifact_info, failure_marker>; implement the helper
to perform the sleep, call capture_artifact(expanded_pattern, pre_snap), call
store_artifact_output(self.project_root, &run_id, art_name, &artifact_path),
update outputs/output_files on success or push to failed_steps/step_results and
set success=false on error (constructing the same StepResult shape used
elsewhere); then call this helper from every early-success exit path (the
success continue at the prompt fallthrough and the implement_code
auto-finalization success branches) before returning/continuing so every
successful prompt path registers artifacts.
- Around line 6078-6097: The test
gstack_slug_missing_binary_returns_actionable_error mutates the process HOME and
introduces race conditions; change resolve_gstack_slug to accept an injectable
home directory (e.g., fn resolve_gstack_slug(home: impl AsRef<Path>) ->
Result<...>) or an Option<&Path> defaulting to env::var("HOME") when None,
update all call sites to pass either the real HOME or a test-supplied
non-existent path, and modify the test to call
resolve_gstack_slug("/nonexistent-path-for-test") instead of setting the HOME
env var; ensure signature and uses are updated consistently across the module
and propagate the new parameter through helper functions that locate the
gstack-slug binary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: aadb3d98-c5aa-4ab3-bf45-4b85da15cbec

📥 Commits

Reviewing files that changed from the base of the PR and between 150da11 and 4ecd945.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • dashboard/app.js
  • dashboard/style.css
  • src/automation/mod.rs
  • src/cli/run.rs
  • src/config/mod.rs

Comment thread CHANGELOG.md
Comment thread dashboard/app.js
Comment thread dashboard/style.css
Comment thread README.md Outdated
Comment thread src/automation/mod.rs
Comment thread src/automation/mod.rs
Comment thread src/automation/mod.rs Outdated
- Fix terminology in CHANGELOG (skill -> prompt step) and template key
  (step_id -> artifact_name)
- Skip non-running runs in dashboard artifact label derivation
- Fix CSS variable typo (--mono -> --font-mono)
- Align README artifact docs terminology with changelog
- Run artifact capture before every prompt success early exit
- Expose raw artifact path via output.<name>.path instead of JSON path
- Refactor resolve_gstack_slug() to accept injectable home dir param,
  removing unsafe env mutation from tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nutt-adam
nutt-adam merged commit f6bd37a into main Mar 22, 2026
11 checks passed
nutt-adam added a commit that referenced this pull request Mar 22, 2026
* feat: artifact pipeline — glob-based artifact capture and injection for prompt steps

Add artifact_glob and artifact_name fields to Prompt workflow steps, enabling
typed artifact flow between pipeline stages. After a skill completes, tutti
globs for new files, captures the newest, and registers it as a step output
available to downstream steps via {{output.step_id.path}} templates.

Key capabilities:
- Pre-step snapshot prevents race conditions with concurrent runs
- inject_files supports {{output.step_id.path}} template expansion
- Variable interpolation: {slug}, {workspace}, {agent}, ~ in glob patterns
- Config validation: requires wait_for_idle, valid artifact_name chars, paired fields
- gstack-slug integration for {slug} resolution
- 15 new tests covering all codepaths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: dry-run display and validation for artifact pipeline

Show artifact_glob and artifact_name in dry-run table and JSON output.
Validate gstack-slug availability at dry-run time when {slug} is used
in artifact_glob patterns.

* feat: dashboard artifact edge labels on flow connectors

Show artifact names as labels on pipeline flow connectors during active
workflow runs. Labels appear above the connector line between stages.

* chore: bump version and changelog (v0.7.0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: document artifact pipeline in README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address CodeRabbit review comments on PR #111

- Fix terminology in CHANGELOG (skill -> prompt step) and template key
  (step_id -> artifact_name)
- Skip non-running runs in dashboard artifact label derivation
- Fix CSS variable typo (--mono -> --font-mono)
- Align README artifact docs terminology with changelog
- Run artifact capture before every prompt success early exit
- Expose raw artifact path via output.<name>.path instead of JSON path
- Refactor resolve_gstack_slug() to accept injectable home dir param,
  removing unsafe env mutation from tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use startup_grace_secs for prompt activity timeout instead of hardcoded 20s

The prompt activity check used a hardcoded 20s timeout which was too short
for Claude Code + gstack skill startup. Now uses max(startup_grace_secs, 20s).

Also adds sdlc-gstack workflow to tutti.toml with artifact pipeline flow
and bumps /office-hours startup_grace to 120s for gstack preamble overhead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: artifact-polling mode, permissions fixes, dashboard race condition

- Artifact-polling mode: when artifact_glob is set without wait_for_idle,
  tutti polls for the artifact file instead of idle-detecting. Supports
  interactive gstack skills (e.g. /office-hours) where the agent waits
  for human input.
- Add Skill and AskUserQuestion to CLAUDE_TOOL_NAMES for dontAsk mode
- Fix dashboard focus sidebar race condition where fast poll overwrote
  usage stats with zeros from empty response
- Update sdlc-gstack workflow to use artifact-polling for design/eng_review

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: v0.8.0 template packs + role mapping

Add template pack format, `tt init --template <name>`, role-to-runtime
mapping, repo detection, and 3 starter templates (gstack-startup,
rust-cli, minimal). Template-tagged event stream for future telemetry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add sdlc-gstack workflow to gstack-startup template, use BTreeMap for roles

Addresses tester review feedback:
- gstack-startup template was missing the sdlc-gstack and sdlc-auto workflows
  that the design doc specified as "batteries included"
- Role display in `tt init` used HashMap with non-deterministic iteration order;
  switched to BTreeMap for consistent alphabetical output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: format automation/mod.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address 16 CodeRabbit review comments on PR #113

- Persist resumable JSON path (not raw artifact path) in output_files for
  checkpoint/resume compatibility
- Abort prompt step with a clear error when session dies without producing
  an artifact
- Surface a validation error instead of masking unresolved runtime as
  "unknown" in tt detect
- Register the generated workspace name (not directory basename) in global
  config during tt init
- Replace unwrap() calls with fallible error propagation in tt init fallback
  paths
- Make resolved_runtime crate-private (pub(crate))
- Add actionable remediation guidance to TemplateParse and TemplateNotFound
  error messages
- Make parse_template_tag return Result, propagating IO errors instead of
  collapsing to (None, None)
- Reject partial/empty template tags (missing version or empty id)
- Use temp-dir path in nonexistent-file test for cross-platform safety
- Make generate_config fallible, validating rendered TOML before returning
- Narrow gstack-startup template detect to Cargo.toml only (matches the
  cargo test validation step)
- Rename implement step id to implement_code for merge-gate enforcement
- Run validation step in implementer worktree (.tutti/worktrees/implementer)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant