feat: v0.8.0 template packs, role mapping, artifact-polling mode - #113
Conversation
…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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
…rdcoded 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>
- 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>
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>
…p 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>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughAdds a template system for generating configs, introduces role-based runtime resolution used across agents/CLIs, implements artifact-polling for prompt steps in the automation executor, persists template metadata in run records, and enhances dashboard focus usage with a context stat and safer fast/slow poll merging. Changes
Sequence Diagram(s)mermaid Dev->>Init: run init (--template?) Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment Tip You can disable poems in the walkthrough.Disable the |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/config/mod.rs (1)
644-677:⚠️ Potential issue | 🟡 MinorValidate the
[roles]table itself, not only the roles that are currently referenced.Right now a typo in an unused or predeclared role mapping still passes
validate()because runtime checking only happens throughagent.resolved_runtime(...). Please validate role names/runtime values directly when[roles]is present so bad mappings fail early.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/config/mod.rs` around lines 644 - 677, When self.roles is Some, validate the role mappings themselves before or alongside per-agent checks: iterate the map (the Some(roles) value) and for each (role_name, role_cfg) ensure the role_cfg has a runtime and that this runtime string is one of the known_runtimes (the same array used with agent.resolved_runtime). If a role mapping lacks a runtime or its runtime is not in known_runtimes, return TuttiError::ConfigValidation with a clear message referencing the role_name and invalid runtime; keep the existing agent.role checks intact.
🧹 Nitpick comments (3)
dashboard/app.js (1)
749-754: Context bar rendering is duplicated.The color-bucketing logic and bar rendering (lines 751-753) is repeated nearly verbatim in
renderFocusView()(lines 810-812 and 820-822). Consider extracting a small helper to reduce duplication and ensure consistency if thresholds change.♻️ Optional helper extraction
// Near the other helper functions function renderContextBar(ctxPct) { var ctxColor = ctxPct <= 70 ? "green" : (ctxPct <= 90 ? "amber" : "red"); var cssVar = ctxColor === "green" ? "working" : ctxColor === "amber" ? "auth-fail" : "blocked"; var html = statRow("context", ctxPct + "%", ctxColor); html += '<div class="focus-ctx-bar"><div class="focus-ctx-fill" style="width:' + ctxPct + '%;background:var(--' + cssVar + ')"></div></div>'; return html; }Then replace duplicated blocks with:
if (ctxPct != null) { statsHtml += renderContextBar(ctxPct); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dashboard/app.js` around lines 749 - 754, Extract the duplicated color-bucketing and bar HTML into a helper (e.g., renderContextBar) and use it from both the current block and renderFocusView to remove duplication; the helper should accept ctxPct, compute ctxColor with the same thresholds (ctxPct <= 70 ? "green" : (ctxPct <= 90 ? "amber" : "red")), map that to the CSS var ("working"/"auth-fail"/"blocked"), then return the combined statRow("context", ctxPct + "%", ctxColor) plus the focus-ctx-bar/focus-ctx-fill HTML; replace the three-line duplicated blocks that reference ctxPct in both renderFocusView and the current function with a call that checks ctxPct != null and appends renderContextBar(ctxPct).templates/rust-cli.toml (1)
32-43: Broaden the default Rust template scopes.Both generated agents are scoped to
src/**, which is pretty tight for Rust CLI work: dependency changes usually touchCargo.toml/Cargo.lock, and test authoring often lands intests/**. Expanding the starter scopes would make this pack fit common end-to-end changes much better.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@templates/rust-cli.toml` around lines 32 - 43, The agent scopes are too narrow—update the "implementer" and "tester" agent entries to broaden their scope beyond "src/**" so common Rust CLI changes are covered (e.g., Cargo.toml, Cargo.lock, tests, benches, examples, build.rs); modify the scope values on the [[agent]] blocks for implementer and tester (refer to the agent names "implementer" and "tester" in the template) to include those additional paths/globs so dependency, test, and example changes are allowed.src/cli/up.rs (1)
1764-1835: Add one role-backed runtime test in this module.The launch code now depends on
resolved_runtime(.., &config.roles), but these updated fixtures still keeprole: Noneand resolve through the old explicit/default runtime path. A focused case whereruntimeis absent and the runtime comes only from[roles]would cover the new behavior here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli/up.rs` around lines 1764 - 1835, Add a new unit test (e.g., agent_resolves_runtime_from_role) that mirrors agent_uses_profile_only_for_compatible_runtime but sets the agent.runtime to None and agent.role to Some("role-name") and adds a Roles mapping in TuttiConfig.roles that provides a runtime (e.g., "claude-code") for "role-name"; ensure DefaultsConfig.runtime is also None so the only source of runtime is the role, then call the same launch/path that invokes resolved_runtime(..., &config.roles) and assert the runtime was resolved from the role entry. This exercises the new role-backed resolution path referenced by resolved_runtime and uses the existing AgentConfig/make_agent/test scaffolding to locate where to add the case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/automation/mod.rs`:
- Around line 1117-1121: The persisted resume data currently stores saved.path
(the copied *.raw artifact) into output_files/outputs, which breaks
load_resume_outputs because it expects a JSON string to serde_json::from_str;
update the persistence so that for artifact outputs you save the JSON output
path (the metadata/JSON file) rather than saved.path, or alternatively modify
load_resume_outputs to detect artifact raw files and treat them as
Value::String(...) instead of attempting serde_json::from_str; change the insert
calls that currently store saved.path (references: output_files, outputs,
saved.path) to write the JSON path, and/or update load_resume_outputs to
special-case raw artifact paths when rehydrating resume outputs.
In `@src/cli/detect.rs`:
- Around line 24-26: Do not mask a missing runtime by assigning "unknown" to
runtime_name; instead propagate a targeted validation error when
agent.resolved_runtime(&resolved.config.defaults, &resolved.config.roles)
returns None. Replace the unwrap_or_else that sets runtime_name with logic that
returns or constructs a user-facing error (including actionable guidance)
explaining the runtime resolution failure and referencing the roles/defaults
used, so the tt detect command surfaces a clear validation message rather than a
synthetic "unknown" value; use the existing error-return path of the surrounding
function to surface this validation error.
In `@src/cli/init.rs`:
- Around line 47-68: Replace the panicking unwrap() calls in the tt init
fallback branches: where BuiltinTemplates::get("minimal").unwrap() is used, call
BuiltinTemplates::get("minimal").ok_or_else(|| TuttiError::user("missing builtin
template 'minimal' — internal registry inconsistent; reinstall or run `tt
doctor`"))? and propagate the error with ? so template::parse_template and
template::generate_config return a TuttiError instead of panicking; similarly,
in the loop over BuiltinTemplates::list(), avoid unwrap() on
BuiltinTemplates::get(name) — either skip invalid entries or convert them to a
user-facing TuttiError via ok_or_else(...) so callers of
template::parse_template receive recoverable errors, and ensure printed guidance
mentions running `tt init --template <name>` or `tt doctor` for remediation.
- Around line 16-19: The code currently sets project_name from the directory
basename (project_name = cwd.file_name()...), but you must register the actual
workspace name produced by the template/generator; read the generated tutti.toml
(or the in-memory template result) and extract the [workspace] name (e.g.,
workspace.name or similar field) and use that value when writing the global
registry entry instead of the directory basename; update both the initial
project_name usage and the other registration logic around the block referenced
(lines ~72-89) so they fallback to the directory basename only if the workspace
name is missing.
In `@src/config/mod.rs`:
- Around line 567-581: The method resolved_runtime is currently declared pub and
returns Option<String>, violating the repo rule that public functions under
src/**/*.rs must return Result<T, TuttiError>; either make it crate-private or
change its signature to return Result<Option<String>, TuttiError> (or
Result<String, TuttiError> if you want to treat missing runtime as an error).
Update the function declaration for resolved_runtime and its callers: if you
choose crate-private, change to pub(crate) and keep the Option return; if you
choose Result-based, return Result<Option<String>, TuttiError> (or
Result<String, TuttiError>) and propagate or construct a TuttiError where
appropriate, referencing DefaultsConfig and the roles lookup logic inside
resolved_runtime to ensure callers handle the Result.
In `@src/error.rs`:
- Around line 29-33: Update the user-facing error messages for the TemplateParse
and TemplateNotFound enum variants so they include actionable remediation
guidance: modify the #[error(...)] strings on TemplateParse(String) and
TemplateNotFound(String) in the error enum in src/error.rs to append a short
next-step suggestion (e.g., how to fix the template syntax or where to
add/locate the missing template), or alternatively ensure every caller of
TemplateParse and TemplateNotFound appends that guidance; reference the enum
variant names TemplateParse and TemplateNotFound when making the change.
In `@src/state/mod.rs`:
- Around line 10-15: Change parse_template_tag to return Result<(Option<String>,
Option<String>), TuttiError> instead of collapsing file-read failures; propagate
std::fs::read_to_string errors (use ? or map_err into TuttiError) so unreadable
config yields Err(TuttiError::Io/Parse) rather than (None,None), keep the same
semantics for empty file by returning Ok((None,None)) when there is no first
line, and update the function signature and internal early returns accordingly
(refer to parse_template_tag and use the crate's TuttiError type to construct
the error).
- Around line 1712-1717: Update the test parse_template_tag_nonexistent_file to
avoid a hardcoded Unix path: construct a missing-file path under
std::env::temp_dir() (or use the tempfile crate) and pass that Path to
parse_template_tag instead of "/nonexistent/tutti.toml"; ensure the chosen
filename is unlikely to exist (e.g., include a random/specific suffix) and keep
the same assertions on id and version. This changes only the test function
parse_template_tag_nonexistent_file and leaves the parse_template_tag
implementation unchanged.
- Around line 21-31: The parser currently returns (id, version) even when one
side is missing or id is empty; update the parsing logic around rest/parts so
that both id and version are present and non-empty before returning: after
splitn(2, ' ') ensure id.is_some() && version.is_some() and that
id.as_ref().unwrap().chars().any(...) (i.e. id is non-empty and passes the
ascii-alnum/-/_/- check) and version.as_ref().map(|v| !v.is_empty()) is true,
otherwise return (None, None); also add regression tests for inputs like "#
template: minimal" and "# template: 0.1.0" to assert they do not produce a
match.
In `@src/template/mod.rs`:
- Around line 74-84: The generate_config function should become fallible: change
its signature to return Result<String, TuttiError>, perform the same
substitution on template.config_body into a rendered string, then validate the
rendered TOML by attempting to parse it (e.g.,
toml::from_str::<toml::Value>(&rendered) or equivalent); if parsing fails return
an appropriate TuttiError (e.g., TuttiError::InvalidConfig or a new variant)
with the parse error details, otherwise return Ok(rendered). Update uses of
generate_config to handle the Result and import TuttiError where needed.
In `@templates/gstack-startup.toml`:
- Around line 115-122: The step id currently set as id = "implement" must be
changed to id = "implement_code" so the executor's commit/push enforcement
recognizes it; update the id value in the template (replace the existing
"implement" string with "implement_code") while leaving the other fields (type,
agent, text, wait_for_idle, wait_timeout_secs, startup_grace_secs, inject_files)
unchanged.
- Around line 1-5: The template "gstack-startup" currently lists detect =
["package.json", "Cargo.toml", "pyproject.toml"] but the generated workflow is
hard-coded to run Rust tests (e.g., "cargo test --quiet"); update the template
so repos that cannot run the workflow are not auto-detected: either remove
non-Rust detectors (keep only "Cargo.toml") if this template is Rust-only, or
modify the workflow generation logic referenced by the template to conditionally
choose test commands based on detected manifest (check presence of "Cargo.toml"
vs "package.json" vs "pyproject.toml") and only emit "cargo test --quiet" when
Cargo.toml is present; adjust the "detect" array or workflow generator
accordingly to ensure JS/Python repos do not get this Rust-only workflow.
- Around line 124-128: The "validate" workflow step (id "validate", run "cargo
test --quiet") is running in the main workspace instead of the implementer
worktree; update that step to run inside the implementer worktree by adding the
work directory setting (e.g., workdir or cwd) pointing to
".tutti/worktrees/implementer" for the step with id "validate" so the cargo
tests execute against the implementer's modified code.
In `@tutti.toml`:
- Around line 367-374: The step id is misnamed "implement" but the executor
expects the guarded implement step id "implement_code", causing premature
success; update the step definition so id = "implement_code" (the same id the
executor's branch-progress retry/finalize logic checks) or alternatively change
the executor to match "implement" — pick the former: rename the step id from
"implement" to "implement_code" in the manifest so the guarded implement step is
correctly enforced.
- Around line 376-380: The validate workflow step (id "validate", run "cargo
test --quiet") runs tests in the workspace root instead of the implementer's
worktree; update the step to run tests in the implementer checkout by changing
the command to change directory into the implementer worktree (e.g., cd
.tutti/worktrees/implementer) before invoking cargo test (keep --quiet and
fail_mode "closed" unchanged) so the approval gate runs against the
implementer's worktree content.
---
Outside diff comments:
In `@src/config/mod.rs`:
- Around line 644-677: When self.roles is Some, validate the role mappings
themselves before or alongside per-agent checks: iterate the map (the
Some(roles) value) and for each (role_name, role_cfg) ensure the role_cfg has a
runtime and that this runtime string is one of the known_runtimes (the same
array used with agent.resolved_runtime). If a role mapping lacks a runtime or
its runtime is not in known_runtimes, return TuttiError::ConfigValidation with a
clear message referencing the role_name and invalid runtime; keep the existing
agent.role checks intact.
---
Nitpick comments:
In `@dashboard/app.js`:
- Around line 749-754: Extract the duplicated color-bucketing and bar HTML into
a helper (e.g., renderContextBar) and use it from both the current block and
renderFocusView to remove duplication; the helper should accept ctxPct, compute
ctxColor with the same thresholds (ctxPct <= 70 ? "green" : (ctxPct <= 90 ?
"amber" : "red")), map that to the CSS var ("working"/"auth-fail"/"blocked"),
then return the combined statRow("context", ctxPct + "%", ctxColor) plus the
focus-ctx-bar/focus-ctx-fill HTML; replace the three-line duplicated blocks that
reference ctxPct in both renderFocusView and the current function with a call
that checks ctxPct != null and appends renderContextBar(ctxPct).
In `@src/cli/up.rs`:
- Around line 1764-1835: Add a new unit test (e.g.,
agent_resolves_runtime_from_role) that mirrors
agent_uses_profile_only_for_compatible_runtime but sets the agent.runtime to
None and agent.role to Some("role-name") and adds a Roles mapping in
TuttiConfig.roles that provides a runtime (e.g., "claude-code") for "role-name";
ensure DefaultsConfig.runtime is also None so the only source of runtime is the
role, then call the same launch/path that invokes resolved_runtime(...,
&config.roles) and assert the runtime was resolved from the role entry. This
exercises the new role-backed resolution path referenced by resolved_runtime and
uses the existing AgentConfig/make_agent/test scaffolding to locate where to add
the case.
In `@templates/rust-cli.toml`:
- Around line 32-43: The agent scopes are too narrow—update the "implementer"
and "tester" agent entries to broaden their scope beyond "src/**" so common Rust
CLI changes are covered (e.g., Cargo.toml, Cargo.lock, tests, benches, examples,
build.rs); modify the scope values on the [[agent]] blocks for implementer and
tester (refer to the agent names "implementer" and "tester" in the template) to
include those additional paths/globs so dependency, test, and example changes
are allowed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: bb18e471-39df-4878-9835-0358f345a42f
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (25)
dashboard/app.jssrc/automation/mod.rssrc/budget/mod.rssrc/cli/detect.rssrc/cli/doctor.rssrc/cli/down.rssrc/cli/handoff.rssrc/cli/init.rssrc/cli/mod.rssrc/cli/send.rssrc/cli/snapshot.rssrc/cli/up.rssrc/cli/watch.rssrc/config/defaults.rssrc/config/mod.rssrc/error.rssrc/health/mod.rssrc/main.rssrc/permissions/mod.rssrc/state/mod.rssrc/template/mod.rstemplates/gstack-startup.tomltemplates/minimal.tomltemplates/rust-cli.tomltutti.toml
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/automation/mod.rs (2)
1115-1118:⚠️ Potential issue | 🟠 MajorDon't checkpoint the raw artifact file as the resumable output.
These inserts persist
saved.path, butstore_artifact_output()makes that the copied*.rawfile.load_resume_outputs()stillserde_json::from_strs every persisted path, so any resumed run with an artifact output will fail before replay starts. Either save the canonical JSON output path here, or special-case.rawin resume loading.Also applies to: 1158-1161, 1555-1558, 1619-1622, 1707-1710
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/automation/mod.rs` around lines 1115 - 1118, The code is persisting the raw artifact file path (saved.path) into output_files via output_files.insert(art_name.to_string(), saved.path.display().to_string()), which causes resumed runs to try to deserialize a *.raw file; instead, persist the canonical JSON output path (the file produced by store_artifact_output() that contains the serializable result) or otherwise convert saved.path to the JSON output path before inserting; update the insert logic where used (the occurrances around output_files.insert in this file and the other noted locations) to store the JSON result path (or call the helper that returns the canonical JSON output path) so load_resume_outputs() will find valid JSON on resume.
1122-1140:⚠️ Potential issue | 🟠 MajorAbort the polling step after
store_artifact_output()fails.Both error branches already append a failed
StepResult, but the footer still appends another outcome andcontinues. That duplicates the result for the same step and lets later steps run after a fatal prompt failure.Also applies to: 1165-1185, 1209-1232
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/automation/mod.rs` around lines 1122 - 1140, The polling step currently records a failed StepResult when store_artifact_output() returns Err(e) but execution continues and a duplicate outcome/footer is later appended and subsequent steps run; update the error handling in the block handling store_artifact_output() (where failed_steps, step_results, and StepResult are pushed for step_index) to immediately abort the polling step flow—either by returning from the surrounding function or breaking out of the outer loop that processes steps—so no additional footer/result is appended and no later steps execute for this failed prompt; ensure the same fix is applied to the other identical error branches (the blocks around the other Err(e) handlers mentioned).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/automation/mod.rs`:
- Around line 205-206: start_and_wait_ready() currently polls agent adapters
using a.runtime.unwrap_or("claude-code") which bypasses role/default resolution;
change it to use the same resolver used elsewhere by calling
a.resolved_runtime(&self.config.defaults, &self.config.roles).unwrap_or_else(||
"unknown".to_string()) (or equivalent) instead of unwrap_or("claude-code") so
agents that derive runtime from role/defaults are waited on with the correct
adapter; apply the same replacement for the other occurrence that still uses
a.runtime.unwrap_or("claude-code").
---
Duplicate comments:
In `@src/automation/mod.rs`:
- Around line 1115-1118: The code is persisting the raw artifact file path
(saved.path) into output_files via output_files.insert(art_name.to_string(),
saved.path.display().to_string()), which causes resumed runs to try to
deserialize a *.raw file; instead, persist the canonical JSON output path (the
file produced by store_artifact_output() that contains the serializable result)
or otherwise convert saved.path to the JSON output path before inserting; update
the insert logic where used (the occurrances around output_files.insert in this
file and the other noted locations) to store the JSON result path (or call the
helper that returns the canonical JSON output path) so load_resume_outputs()
will find valid JSON on resume.
- Around line 1122-1140: The polling step currently records a failed StepResult
when store_artifact_output() returns Err(e) but execution continues and a
duplicate outcome/footer is later appended and subsequent steps run; update the
error handling in the block handling store_artifact_output() (where
failed_steps, step_results, and StepResult are pushed for step_index) to
immediately abort the polling step flow—either by returning from the surrounding
function or breaking out of the outer loop that processes steps—so no additional
footer/result is appended and no later steps execute for this failed prompt;
ensure the same fix is applied to the other identical error branches (the blocks
around the other Err(e) handlers mentioned).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: cee1c110-871b-45a3-a5bb-7fe186ab72f0
📒 Files selected for processing (1)
src/automation/mod.rs
- 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>
Summary
tt init --template <name>with 3 built-in templates (gstack-startup, rust-cli, minimal), repo auto-detection, and{{project_name}}variable substitution[roles]table in tutti.toml maps logical roles to runtimes. Agents declarerole = "planner"instead of hardcodingruntime = "claude-code". Resolution order: explicit runtime > role lookup > defaultsartifact_globis set withoutwait_for_idle, tutti polls for the artifact file instead of idle-detecting. Enables interactive gstack skills (/office-hours,/plan-eng-review) where the agent waits for human inputSkillandAskUserQuestiontoCLAUDE_TOOL_NAMESso gstack skills work indontAskmodeDogfood validation
Full
sdlc-gstackworkflow ran end-to-end during this session:/office-hours→ design doc captured via artifact polling/plan-eng-review→ test plan captured via artifact pollingTest plan
cargo test— 361 tests passcargo clippy— cleantt run sdlc-gstack --dry-run— validates config🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes