Skip to content

feat: v0.8.0 template packs, role mapping, artifact-polling mode - #113

Merged
nutt-adam merged 13 commits into
mainfrom
feat/template-packs
Mar 22, 2026
Merged

feat: v0.8.0 template packs, role mapping, artifact-polling mode#113
nutt-adam merged 13 commits into
mainfrom
feat/template-packs

Conversation

@nutt-adam

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

Copy link
Copy Markdown
Contributor

Summary

  • Template packs: tt init --template <name> with 3 built-in templates (gstack-startup, rust-cli, minimal), repo auto-detection, and {{project_name}} variable substitution
  • Role mapping: [roles] table in tutti.toml maps logical roles to runtimes. Agents declare role = "planner" instead of hardcoding runtime = "claude-code". Resolution order: explicit runtime > role lookup > defaults
  • Artifact-polling mode: When artifact_glob is set without wait_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 input
  • Permissions fixes: Added Skill and AskUserQuestion to CLAUDE_TOOL_NAMES so gstack skills work in dontAsk mode
  • Dashboard fix: Fixed race condition where fast poll overwrote usage stats with zeros

Dogfood validation

Full sdlc-gstack workflow ran end-to-end during this session:

  1. /office-hours → design doc captured via artifact polling
  2. /plan-eng-review → test plan captured via artifact polling
  3. Implementer received both artifacts, produced 22-file commit (+976/-37)
  4. Tester reviewed: 0 critical issues, 1 medium (fixed), 2 low (fixed)

Test plan

  • cargo test — 361 tests pass
  • cargo clippy — clean
  • tt run sdlc-gstack --dry-run — validates config
  • Full dogfood run with interactive gstack skills
  • CI checks
  • CodeRabbit review

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Project templates (minimal, gstack-startup, rust-cli) with auto-detection and an optional --template init flag
    • Role-based agent→runtime mapping and template metadata captured in run history
  • Enhancements

    • Artifact polling for workflow steps to pass files between steps
    • Dashboard shows a new context usage stat with color-coded bar and smarter update logic for fast/slow polls
  • Bug Fixes

    • Prevents fast poll results from overwriting valid slow-poll usage display

nutt-adam and others added 11 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>
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>
@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 2 minutes and 50 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: 4c282547-6c41-4f55-b9e4-a086cf8cbb6a

📥 Commits

Reviewing files that changed from the base of the PR and between ba0237d and 04f5174.

📒 Files selected for processing (9)
  • src/automation/mod.rs
  • src/cli/detect.rs
  • src/cli/init.rs
  • src/config/mod.rs
  • src/error.rs
  • src/state/mod.rs
  • src/template/mod.rs
  • templates/gstack-startup.toml
  • tutti.toml
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Template system & CLI init
src/template/mod.rs, src/cli/init.rs, src/cli/mod.rs, src/main.rs
New template module (parse/generate/detect/load), init command now accepts --template and generates tutti.toml from templates with project-name substitution and template info printing; tests added for template flows.
Built-in templates
templates/minimal.toml, templates/rust-cli.toml, templates/gstack-startup.toml, tutti.toml
Added three built-in templates and updated tutti.toml with sdlc-gstack workflow using artifact globs/names and template-derived config samples.
Config: roles & runtime resolution
src/config/mod.rs, src/config/defaults.rs
Added TuttiConfig.roles and AgentConfig.role, changed AgentConfig::resolved_runtime to accept roles and implement resolution order (runtime → role lookup → defaults); kept DEFAULT_CONFIG but annotated for tests/backcompat.
Call-site updates for runtime resolution
src/automation/mod.rs, src/cli/...
src/cli/detect.rs, src/cli/doctor.rs, src/cli/down.rs, src/cli/send.rs, src/cli/snapshot.rs, src/cli/up.rs, src/health/mod.rs, src/permissions/mod.rs
Updated many resolved_runtime call sites to pass &config.roles alongside defaults; added two Claude tool names to permission list.
Automation executor: artifact polling & metadata
src/automation/mod.rs, src/config/mod.rs, src/state/mod.rs
Added artifact-polling control flow for prompt steps when artifact_glob+artifact_name and wait_for_idle=false (poll until timeout and persist newest matching file); adjusted idle/health wait timeout logic; persisted template_id/template_version in AutomationRunRecord; validation relaxed for artifact-glob modes.
Dashboard focus usage UI
dashboard/app.js
pollFocusUsage() now validates both workspace and agent before applying updates; added context usage stat with color buckets; renderFocusView() refactored to avoid fast-poll zero-value overwrites and to render context-only bars when appropriate.
State & errors
src/state/mod.rs, src/error.rs
Added parse_template_tag helper to extract template header from generated tutti.toml; AutomationRunRecord extended with optional template_id/template_version; new TuttiError variants TemplateParse and TemplateNotFound and mapped to config failure category.
Tests & fixtures
src/cli/..., src/budget/mod.rs, src/cli/handoff.rs, src/cli/watch.rs
Updated test fixtures to include role: None on AgentConfig and roles: None on TuttiConfig; added/adjusted tests for templates, validation, and artifact behaviors.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Dev as Developer/CLI
participant Init as Init Command
participant Templates as Template Loader
participant FS as Repo FS
participant Exec as Automation Executor
participant State as State Store

Dev->>Init: run init (--template?)
Init->>Templates: load_template(name or path)
Templates-->>Init: ParsedTemplate (metadata + config_body)
Init->>FS: write generated tutti.toml (with template tag)
FS-->>State: (optional) later automation run reads tutti.toml
Exec->>State: parse_template_tag(config_path)
alt prompt step uses artifact_glob && wait_for_idle=false
Exec->>FS: snapshot pre-step, poll for artifact matches (interval)
FS-->>Exec: new artifact appears
Exec->>State: persist artifact capture + AutomationRunRecord (template metadata)
else timeout
Exec-->>State: step fails (timeout)
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I stitched templates in morning light,
Roles assigned to get things right,
Artifacts hopped from step to step,
Context bars now softly prep.
A small rabbit cheers the code tonight.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a clear summary of changes and validates dogfood testing, but is missing required versioning checklist items (Cargo.toml version, CHANGELOG.md, SemVer choice, version number). Complete the Versioning section by updating Cargo.toml version, adding CHANGELOG.md entry, selecting SemVer (likely MINOR for new features), and filling in the version number. Also confirm CI validation is complete.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main features added: template packs, role mapping, and artifact-polling mode, directly corresponding to the major changes in the changeset.

✏️ 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/template-packs

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.

Tip

You can disable poems in the walkthrough.

Disable the reviews.poem setting to disable the poems in the walkthrough.

Co-Authored-By: Claude Opus 4.6 (1M context) <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: 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 | 🟡 Minor

Validate 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 through agent.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 touch Cargo.toml/Cargo.lock, and test authoring often lands in tests/**. 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 keep role: None and resolve through the old explicit/default runtime path. A focused case where runtime is 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

📥 Commits

Reviewing files that changed from the base of the PR and between f6bd37a and 873b3f5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • dashboard/app.js
  • src/automation/mod.rs
  • src/budget/mod.rs
  • src/cli/detect.rs
  • src/cli/doctor.rs
  • src/cli/down.rs
  • src/cli/handoff.rs
  • src/cli/init.rs
  • src/cli/mod.rs
  • src/cli/send.rs
  • src/cli/snapshot.rs
  • src/cli/up.rs
  • src/cli/watch.rs
  • src/config/defaults.rs
  • src/config/mod.rs
  • src/error.rs
  • src/health/mod.rs
  • src/main.rs
  • src/permissions/mod.rs
  • src/state/mod.rs
  • src/template/mod.rs
  • templates/gstack-startup.toml
  • templates/minimal.toml
  • templates/rust-cli.toml
  • tutti.toml

Comment thread src/automation/mod.rs Outdated
Comment thread src/automation/mod.rs Outdated
Comment thread src/cli/detect.rs Outdated
Comment thread src/cli/init.rs
Comment thread src/cli/init.rs Outdated
Comment thread templates/gstack-startup.toml Outdated
Comment thread templates/gstack-startup.toml Outdated
Comment thread templates/gstack-startup.toml
Comment thread tutti.toml Outdated
Comment thread tutti.toml

@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: 1

♻️ Duplicate comments (2)
src/automation/mod.rs (2)

1115-1118: ⚠️ Potential issue | 🟠 Major

Don't checkpoint the raw artifact file as the resumable output.

These inserts persist saved.path, but store_artifact_output() makes that the copied *.raw file. load_resume_outputs() still serde_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 .raw in 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 | 🟠 Major

Abort the polling step after store_artifact_output() fails.

Both error branches already append a failed StepResult, but the footer still appends another outcome and continues. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 873b3f5 and ba0237d.

📒 Files selected for processing (1)
  • src/automation/mod.rs

Comment thread 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>
@nutt-adam
nutt-adam merged commit 11c9743 into main Mar 22, 2026
11 checks passed
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