feat: solo-workflow-tighten — tighten solo dev loop #7

Merged
wollax merged 4 commits from feat/solo-workflow-tighten into main 2026-04-14 18:39:07 +00:00
Owner

Summary

  • Tighten solo developer workflow into explore → plan → review → execute → verify → ship loop
  • Add spec lifecycle status (Draft/Ready/Approved/Verified) with auto-promotion on gate pass
  • Add workflow state machine (next_action()) that reads project state and recommends next step
  • Add smart gate routing that dispatches criteria by type
  • Add session retention with configurable count/age limits and milestone protection
  • Add plan quick for transparent 1-chunk milestones
  • Add branch isolation heuristic with config-driven auto_isolate setting
  • Add surface-adapted gate evidence rendering
  • New plugin skills: /assay:explore, /assay:focus, /assay:check, /assay:ship

Test plan

  • just ready passes (2526 tests, clippy clean, fmt clean, cargo-deny clean)
  • Backward compatibility verified
  • E2E: plan quick → gate run → auto-promote → cycle_advance → PR
  • E2E: mixed-kind spec with Command + AgentReport criteria
## Summary - Tighten solo developer workflow into explore → plan → review → execute → verify → ship loop - Add spec lifecycle status (Draft/Ready/Approved/Verified) with auto-promotion on gate pass - Add workflow state machine (next_action()) that reads project state and recommends next step - Add smart gate routing that dispatches criteria by type - Add session retention with configurable count/age limits and milestone protection - Add plan quick for transparent 1-chunk milestones - Add branch isolation heuristic with config-driven auto_isolate setting - Add surface-adapted gate evidence rendering - New plugin skills: /assay:explore, /assay:focus, /assay:check, /assay:ship ## Test plan - [x] just ready passes (2526 tests, clippy clean, fmt clean, cargo-deny clean) - [x] Backward compatibility verified - [ ] E2E: plan quick → gate run → auto-promote → cycle_advance → PR - [ ] E2E: mixed-kind spec with Command + AgentReport criteria
Replace "Open Implementation Questions" in design.md with resolved
decisions (D7-D10) covering cycle_advance permissiveness, UAT config,
explore context loading, and skill alias mechanism. Update 4 spec files
with new scenarios (strict_status, dynamic branch detection, tiered
context loading, per-spec UAT override) and tasks.md with 3 new tasks
and clarified implementation details.
feat: implement solo-workflow-tighten change (sections 1-10)
Some checks failed
CI / Validate plugins (pull_request) Successful in 3s
CI / Check smelt (stable) (pull_request) Failing after 27s
CI / Check assay (stable) (pull_request) Failing after 29s
0cd7636cbc
Tightens the solo developer workflow into a 6-phase loop
(explore → plan → review → execute → verify → ship) with
autonomous transitions and gates as the machine-verifiable backbone.

Core library changes (assay-types + assay-core):
- GateSpecStatus enum (Draft/Ready/Approved/Verified) with status
  and uat fields on GatesSpec, auto-promotion on gate pass
- NextAction workflow state machine (8 variants) with strict_status
  check for gate evaluation gating
- Session retention: max_count/max_age_days eviction with milestone
  protection, lazy eviction on session create
- plan_quick: transparent 1-chunk milestone for flat specs
- WorkflowConfig: auto_isolate, protected_branches, uat_enabled,
  strict_status settings with should_isolate() heuristic
- evaluate_routed: smart gate routing by criterion kind with
  pipeline-only skip and agent_eval_mode config
- Gate evidence render module: terminal, markdown, PR body, and
  PR check renderers

CLI + MCP changes:
- assay plan quick subcommand with interactive criteria collection
- --status filter on assay spec list, status display in output
- spec_list MCP tool gains status parameter and response field

Plugin skills (claude-code, codex, opencode):
- New: /assay:explore, /assay:focus, /assay:check, /assay:ship
- Updated: /assay:plan with quick mode support
- Deprecated: /assay:status → focus, /assay:next-chunk → focus,
  /assay:gate-check → check (alias files with notices)

48/52 tasks complete. 2526 tests pass, just ready clean.
Author
Owner

Code review

Found 3 issues:

  1. lazy_evict always ignores user session configlazy_evict passes assay_dir.join("config.toml") to config::load(), but load() expects a project root and internally appends .assay/config.toml. The effective lookup becomes .assay/config.toml/.assay/config.toml, which never exists. The Err is silently swallowed and eviction always runs with SessionsConfig::default(), ignoring any configured max_count/max_age_days.

fn lazy_evict(assay_dir: &Path) {
let config_path = assay_dir.join("config.toml");
let sessions_config = if config_path.is_file() {
if let Ok(config) = crate::config::load(&config_path) {
config.sessions.unwrap_or_default()
} else {
assay_types::SessionsConfig::default()
}
} else {
assay_types::SessionsConfig::default()
};

Fix: pass the project root (parent of assay_dir) to config::load, or build the path manually like other call sites.

  1. AdvanceChunk.next_chunk points to the current chunk, not the next onespec_name is set from chunk.slug (the active chunk). When gates pass and remaining chunks exist, NextAction::AdvanceChunk { next_chunk: spec_name } returns the chunk that just completed rather than the next pending chunk. Any consumer acting on this will re-run the finished chunk. Same bug in the Verified status branch.

let spec_name = chunk.slug.clone();
let specs_dir = assay_dir.join("specs");
// Try to load the spec entry to check status
let status = match spec::load_spec_entry(&spec_name, &specs_dir) {
Ok(spec::SpecEntry::Directory { gates, .. }) => spec::effective_status(&gates),
Ok(spec::SpecEntry::Legacy { .. }) => GateSpecStatus::Draft,
Err(_) => GateSpecStatus::Draft,
};
// Check gate history for this spec
let run_ids = crate::history::list(assay_dir, &spec_name).unwrap_or_default();
if let Some(latest_run_id) = run_ids.last() {
// We have gate history — check the latest result
if let Ok(record) = crate::history::load(assay_dir, &spec_name, latest_run_id) {
if record.summary.enforcement.required_failed > 0 {
// Gates failed
let failed: Vec<String> = record
.summary
.results
.iter()
.filter(|r| {
r.enforcement == assay_types::Enforcement::Required
&& r.result.as_ref().is_some_and(|g| !g.passed)
})
.map(|r| r.criterion_name.clone())
.collect();
return Ok(NextAction::FixAndRecheck {
spec_name,
failed_criteria: failed,
});
}
// Gates passed — check if there are more chunks
let remaining = milestone
.chunks
.iter()
.filter(|c| !milestone.completed_chunks.contains(&c.slug) && c.slug != spec_name)
.count();
if remaining > 0 {
// More chunks remain after current
return Ok(NextAction::AdvanceChunk {
milestone_slug: milestone.slug.clone(),
next_chunk: spec_name,
});

  1. evaluate_routed "auto" mode for AgentReport is identical to "manual" — Both branches skip the criterion and push CriterionResult { result: None }. Required AgentReport criteria appear as "skipped" rather than pass/fail, meaning gates can appear to pass when agent criteria were never evaluated. The two branches (lines 317-335) are identical dead code.

Some(CriterionKind::AgentReport) => {
if agent_eval_mode == "manual" {
// Skip — user handles via gate_report flow
skipped += 1;
results.push(CriterionResult {
criterion_name: gc.name.clone(),
result: None,
enforcement,
source: None,
});
} else {
// Auto mode: skip in this function (evaluator subprocess
// is async and handled by the MCP layer's gate_evaluate)
skipped += 1;
results.push(CriterionResult {
criterion_name: gc.name.clone(),
result: None,
enforcement,
source: None,
});
}
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

### Code review Found 3 issues: 1. **`lazy_evict` always ignores user session config** — `lazy_evict` passes `assay_dir.join("config.toml")` to `config::load()`, but `load()` expects a project root and internally appends `.assay/config.toml`. The effective lookup becomes `.assay/config.toml/.assay/config.toml`, which never exists. The `Err` is silently swallowed and eviction always runs with `SessionsConfig::default()`, ignoring any configured `max_count`/`max_age_days`. https://forgejo.alexwollan.com/wollax/assay/src/commit/0cd7636cbc91858e2d8a9fda27baae680c434ffa/crates/assay-core/src/work_session.rs#L227-L237 Fix: pass the project root (parent of `assay_dir`) to `config::load`, or build the path manually like other call sites. 2. **`AdvanceChunk.next_chunk` points to the current chunk, not the next one** — `spec_name` is set from `chunk.slug` (the active chunk). When gates pass and remaining chunks exist, `NextAction::AdvanceChunk { next_chunk: spec_name }` returns the chunk that just completed rather than the next pending chunk. Any consumer acting on this will re-run the finished chunk. Same bug in the `Verified` status branch. https://forgejo.alexwollan.com/wollax/assay/src/commit/0cd7636cbc91858e2d8a9fda27baae680c434ffa/crates/assay-core/src/workflow/mod.rs#L89-L136 3. **`evaluate_routed` "auto" mode for AgentReport is identical to "manual"** — Both branches skip the criterion and push `CriterionResult { result: None }`. Required AgentReport criteria appear as "skipped" rather than pass/fail, meaning gates can appear to pass when agent criteria were never evaluated. The two branches (lines 317-335) are identical dead code. https://forgejo.alexwollan.com/wollax/assay/src/commit/0cd7636cbc91858e2d8a9fda27baae680c434ffa/crates/assay-core/src/gate/mod.rs#L316-L337 🤖 Generated with [Claude Code](https://claude.ai/code) <sub>- If this code review was useful, please react with 👍. Otherwise, react with 👎.</sub>
AGENTS.md is the canonical file with merged content from both files.
CLAUDE.md is now a symlink to AGENTS.md.
fix: resolve three pre-merge issues from code review
Some checks failed
CI / Validate plugins (pull_request) Successful in 3s
CI / Check assay (stable) (pull_request) Failing after 27s
CI / Check smelt (stable) (pull_request) Failing after 27s
6dd252f02c
- Fix eviction overcount: filter protected sessions from `remaining`
  before computing excess, preventing under-eviction when active
  milestone sessions are present
- Add lazy_evict call to list_sessions as docstring promised, with
  list_sessions_raw helper to avoid recursion from evict_sessions
  and recover_stale_sessions
- Remove dead NextAction variants (RunGates, PromptUat) that were
  never constructed; keep FixAndRecheck which is actively used
- Collapse duplicate AgentReport branches in evaluate_routed and
  update doc comment to reflect single-pass semantics
- Track 11 open issues from PR #7 code review
wollax merged commit a9d18b7bc3 into main 2026-04-14 18:39:07 +00:00
wollax deleted branch feat/solo-workflow-tighten 2026-04-14 18:39:07 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
wollax/assay!7
No description provided.