use crate::config::Config;
use crate::diagnostic::Diagnostic;
use crate::load::load_project;
use serde::Serialize;
#[derive(Serialize)]
pub struct DescribeOutput {
pub version: String,
pub purpose: String,
pub philosophy: Vec<String>,
pub commands: Vec<CommandInfo>,
pub workflow: WorkflowInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_state: Option<ProjectState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggested_actions: Option<Vec<SuggestedAction>>,
}
#[derive(Serialize)]
pub struct CommandInfo {
pub name: String,
pub purpose: String,
pub when_to_use: String,
pub example: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub prerequisites: Vec<String>,
}
#[derive(Serialize)]
pub struct WorkflowInfo {
pub phases: Vec<String>,
pub typical_sequence: Vec<String>,
}
#[derive(Serialize)]
pub struct ProjectState {
pub rfcs: Vec<RfcState>,
pub adrs: Vec<AdrState>,
pub work_items: Vec<WorkItemState>,
}
#[derive(Serialize)]
pub struct RfcState {
pub id: String,
pub title: String,
pub status: String,
pub phase: String,
}
#[derive(Serialize)]
pub struct AdrState {
pub id: String,
pub title: String,
pub status: String,
}
#[derive(Serialize)]
pub struct WorkItemState {
pub id: String,
pub title: String,
pub status: String,
}
#[derive(Serialize)]
pub struct SuggestedAction {
pub command: String,
pub reason: String,
pub priority: String,
}
fn command_catalog() -> Vec<CommandInfo> {
vec![
CommandInfo {
name: "init".to_string(),
purpose: "Initialize govctl governance structure in the current directory".to_string(),
when_to_use: "Once per project, before any other govctl commands. Creates gov/ directory structure, config, and schemas.".to_string(),
example: "govctl init".to_string(),
prerequisites: vec![],
},
CommandInfo {
name: "init-skills".to_string(),
purpose: "Install agent skills and agents into the project".to_string(),
when_to_use: "After govctl init, if not using the govctl plugin. Installs skills and agents into the configured agent_dir.".to_string(),
example: "govctl init-skills".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "status".to_string(),
purpose: "Show summary counts of all artifacts".to_string(),
when_to_use: "To get an overview of project governance state. Run at start of session to understand current work.".to_string(),
example: "govctl status".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "check".to_string(),
purpose: "Validate all governed documents".to_string(),
when_to_use: "Before committing, after edits, to verify governance compliance. Run frequently during development.".to_string(),
example: "govctl check".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "verify".to_string(),
purpose: "Run reusable verification guards".to_string(),
when_to_use: "To execute project-level or work-item-specific completion gates before marking work done.".to_string(),
example: "govctl verify --work WI-2026-01-18-001".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "list rfc".to_string(),
purpose: "List all RFCs with their status and phase".to_string(),
when_to_use: "To see all specifications. Filter by status: 'govctl rfc list draft'.".to_string(),
example: "govctl rfc list".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "list adr".to_string(),
purpose: "List all ADRs (Architecture Decision Records)".to_string(),
when_to_use: "To see architectural decisions. Filter by status: 'govctl adr list accepted'.".to_string(),
example: "govctl adr list".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "list work".to_string(),
purpose: "List work items (defaults to pending: queue + active)".to_string(),
when_to_use: "To see current task queue. Use 'govctl work list all' for everything.".to_string(),
example: "govctl work list".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "new rfc".to_string(),
purpose: "Create a new RFC (specification document)".to_string(),
when_to_use: "Before implementing any new feature. RFCs define what must be built. No implementation without specification.".to_string(),
example: "govctl rfc new \"Add caching layer\"".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "new adr".to_string(),
purpose: "Create a new ADR (Architecture Decision Record)".to_string(),
when_to_use: "When making a significant design decision that should be documented. ADRs capture context, decision, and consequences.".to_string(),
example: "govctl adr new \"Use Redis for caching\"".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "new work".to_string(),
purpose: "Create a new work item".to_string(),
when_to_use: "When starting a task. Use --active to immediately activate it.".to_string(),
example: "govctl work new --active \"Implement describe command\"".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "new clause".to_string(),
purpose: "Create a new clause within an RFC".to_string(),
when_to_use: "When adding normative requirements to an RFC. Clauses are the atomic units of specification.".to_string(),
example: "govctl clause new RFC-0001:C-CACHE-TTL \"Cache TTL Policy\" -s Specification -k normative".to_string(),
prerequisites: vec!["RFC must exist".to_string()],
},
CommandInfo {
name: "finalize".to_string(),
purpose: "Transition RFC status to normative or deprecated".to_string(),
when_to_use: "When an RFC spec is complete and ready for implementation. 'normative' makes it binding law.".to_string(),
example: "govctl rfc finalize RFC-0001 normative".to_string(),
prerequisites: vec!["RFC must be in draft status".to_string()],
},
CommandInfo {
name: "advance".to_string(),
purpose: "Advance RFC phase (spec → impl → test → stable)".to_string(),
when_to_use: "After completing work for current phase. Phase discipline ensures proper workflow.".to_string(),
example: "govctl rfc advance RFC-0001 impl".to_string(),
prerequisites: vec!["RFC should be normative".to_string(), "Current phase work complete".to_string()],
},
CommandInfo {
name: "move".to_string(),
purpose: "Move work item to new status (queue/active/done/cancelled)".to_string(),
when_to_use: "To update task status. Use 'done' when complete, 'active' to start working.".to_string(),
example: "govctl work move WI-2026-01-18-001 done".to_string(),
prerequisites: vec!["Work item must exist".to_string(), "For 'done': acceptance criteria required".to_string()],
},
CommandInfo {
name: "accept".to_string(),
purpose: "Accept an ADR (proposed → accepted)".to_string(),
when_to_use: "When an architectural decision is approved.".to_string(),
example: "govctl adr accept ADR-0001".to_string(),
prerequisites: vec!["ADR must be in proposed status".to_string()],
},
CommandInfo {
name: "set".to_string(),
purpose: "Set a field value on an artifact".to_string(),
when_to_use: "To update artifact fields. Use --stdin for multi-line content.".to_string(),
example: "govctl rfc set RFC-0001 title \"New Title\"".to_string(),
prerequisites: vec!["Artifact must exist".to_string()],
},
CommandInfo {
name: "get".to_string(),
purpose: "Get a field value from an artifact".to_string(),
when_to_use: "To read artifact data. Omit field name to show entire artifact.".to_string(),
example: "govctl rfc get RFC-0001 status".to_string(),
prerequisites: vec!["Artifact must exist".to_string()],
},
CommandInfo {
name: "add".to_string(),
purpose: "Add a value to an array field".to_string(),
when_to_use: "To add items to refs, owners, acceptance_criteria, etc.".to_string(),
example: "govctl work add WI-2026-01-18-001 acceptance_criteria \"Tests pass\"".to_string(),
prerequisites: vec!["Artifact must exist".to_string()],
},
CommandInfo {
name: "remove".to_string(),
purpose: "Remove a value from an array field".to_string(),
when_to_use: "To remove items from array fields. Use --at for index, or pattern matching.".to_string(),
example: "govctl rfc remove RFC-0001 owners \"@oldowner\"".to_string(),
prerequisites: vec!["Artifact must exist".to_string()],
},
CommandInfo {
name: "tick".to_string(),
purpose: "Mark a checklist item as done/pending/cancelled".to_string(),
when_to_use: "To update acceptance criteria status on work items.".to_string(),
example: "govctl work tick WI-2026-01-18-001 acceptance_criteria \"Tests\" -s done".to_string(),
prerequisites: vec!["Work item or ADR must exist".to_string()],
},
CommandInfo {
name: "edit".to_string(),
purpose: "Edit clause text".to_string(),
when_to_use: "To update normative clause content. Use --stdin for multi-line text.".to_string(),
example: "govctl clause edit RFC-0001:C-SCOPE --stdin".to_string(),
prerequisites: vec!["Clause must exist".to_string()],
},
CommandInfo {
name: "render".to_string(),
purpose: "Render artifacts to markdown".to_string(),
when_to_use: "To generate human-readable documentation from SSOT. Run after RFC changes.".to_string(),
example: "govctl render rfc".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "migrate".to_string(),
purpose: "Convert legacy JSON governance storage to current TOML formats".to_string(),
when_to_use: "When a repository still stores RFCs or clauses as JSON, or when releases.toml needs schema metadata normalization.".to_string(),
example: "govctl migrate".to_string(),
prerequisites: vec!["govctl init".to_string()],
},
CommandInfo {
name: "bump".to_string(),
purpose: "Bump RFC version".to_string(),
when_to_use: "When making changes to a normative RFC. Follows semver.".to_string(),
example: "govctl rfc bump RFC-0001 --minor -m \"Add new clause\"".to_string(),
prerequisites: vec!["RFC must exist".to_string()],
},
CommandInfo {
name: "release".to_string(),
purpose: "Cut a release (collect unreleased work items)".to_string(),
when_to_use: "When releasing a new version. Collects done work items into changelog.".to_string(),
example: "govctl release 0.2.0".to_string(),
prerequisites: vec!["Done work items exist".to_string()],
},
CommandInfo {
name: "deprecate".to_string(),
purpose: "Deprecate an artifact".to_string(),
when_to_use: "When an RFC, clause, or ADR is no longer relevant but kept for history.".to_string(),
example: "govctl rfc deprecate RFC-0001".to_string(),
prerequisites: vec!["Artifact must exist".to_string()],
},
CommandInfo {
name: "supersede".to_string(),
purpose: "Supersede an artifact with a replacement".to_string(),
when_to_use: "When replacing an artifact with a newer version.".to_string(),
example: "govctl rfc supersede RFC-0001 --by RFC-0010".to_string(),
prerequisites: vec!["Both artifacts must exist".to_string()],
},
CommandInfo {
name: "show rfc".to_string(),
purpose: "Show RFC content to stdout (no file written)".to_string(),
when_to_use: "To read the full rendered RFC content. Use -o json for structured output.".to_string(),
example: "govctl rfc show RFC-0001".to_string(),
prerequisites: vec!["RFC must exist".to_string()],
},
CommandInfo {
name: "show adr".to_string(),
purpose: "Show ADR content to stdout (no file written)".to_string(),
when_to_use: "To read the full rendered ADR content. Use -o json for structured output.".to_string(),
example: "govctl adr show ADR-0001".to_string(),
prerequisites: vec!["ADR must exist".to_string()],
},
CommandInfo {
name: "show work".to_string(),
purpose: "Show work item content to stdout (no file written)".to_string(),
when_to_use: "To read the full rendered work item content. Use -o json for structured output.".to_string(),
example: "govctl work show WI-2026-01-18-001".to_string(),
prerequisites: vec!["Work item must exist".to_string()],
},
CommandInfo {
name: "show clause".to_string(),
purpose: "Show clause content to stdout (no file written)".to_string(),
when_to_use: "To read the clause text. Use -o json for structured output.".to_string(),
example: "govctl clause show RFC-0001:C-SUMMARY".to_string(),
prerequisites: vec!["Clause must exist".to_string()],
},
]
}
fn workflow_info() -> WorkflowInfo {
WorkflowInfo {
phases: vec![
"spec: RFC drafting and design discussion".to_string(),
"impl: Code writing per normative RFC".to_string(),
"test: Verification and test writing".to_string(),
"stable: Bug fixes only, no new features".to_string(),
],
typical_sequence: vec![
"govctl work new --active \"Feature Title\"".to_string(),
"govctl rfc new \"Feature Title\"".to_string(),
"govctl clause new RFC-NNNN:C-REQUIREMENT \"Requirement\" -k normative".to_string(),
"govctl rfc finalize RFC-NNNN normative".to_string(),
"govctl rfc advance RFC-NNNN impl".to_string(),
"# Implement the feature".to_string(),
"govctl rfc advance RFC-NNNN test".to_string(),
"# Write tests".to_string(),
"govctl rfc advance RFC-NNNN stable".to_string(),
"govctl work tick WI-xxx acceptance_criteria \"criterion\" -s done".to_string(),
"govctl work move WI-xxx done".to_string(),
],
}
}
fn generate_suggestions(
rfcs: &[RfcState],
adrs: &[AdrState],
work_items: &[WorkItemState],
) -> Vec<SuggestedAction> {
let mut suggestions = Vec::new();
for rfc in rfcs {
if rfc.status == "draft" {
suggestions.push(SuggestedAction {
command: format!("govctl rfc finalize {} normative", rfc.id),
reason: format!(
"{} is in draft status. If the spec is complete, finalize it to make it binding.",
rfc.id
),
priority: "medium".to_string(),
});
}
match (rfc.status.as_str(), rfc.phase.as_str()) {
("normative", "spec") => {
suggestions.push(SuggestedAction {
command: format!("govctl rfc advance {} impl", rfc.id),
reason: format!(
"{} is normative but still in spec phase. Advance to impl when ready to implement.",
rfc.id
),
priority: "high".to_string(),
});
}
("normative", "impl") => {
suggestions.push(SuggestedAction {
command: format!("govctl rfc advance {} test", rfc.id),
reason: format!(
"{} is in impl phase. Advance to test when implementation is complete.",
rfc.id
),
priority: "medium".to_string(),
});
}
("normative", "test") => {
suggestions.push(SuggestedAction {
command: format!("govctl rfc advance {} stable", rfc.id),
reason: format!(
"{} is in test phase. Advance to stable when tests pass.",
rfc.id
),
priority: "medium".to_string(),
});
}
_ => {}
}
}
for adr in adrs {
if adr.status == "proposed" {
suggestions.push(SuggestedAction {
command: format!("govctl adr accept {}", adr.id),
reason: format!(
"{} is proposed. Accept it if the decision is approved.",
adr.id
),
priority: "medium".to_string(),
});
}
}
let active_count = work_items.iter().filter(|w| w.status == "active").count();
let queue_count = work_items.iter().filter(|w| w.status == "queue").count();
if active_count == 0 && queue_count > 0 {
suggestions.push(SuggestedAction {
command: "govctl work list queue".to_string(),
reason: format!(
"No active work items but {} in queue. Consider activating one.",
queue_count
),
priority: "high".to_string(),
});
}
for work_item in work_items {
if work_item.status == "active" {
suggestions.push(SuggestedAction {
command: format!("govctl work move {} done", work_item.id),
reason: format!(
"{} is active. Mark it done when acceptance criteria are met.",
work_item.id
),
priority: "low".to_string(),
});
}
}
suggestions
}
pub fn describe(config: &Config, context: bool) -> anyhow::Result<Vec<Diagnostic>> {
let version = env!("CARGO_PKG_VERSION").to_string();
let mut output = DescribeOutput {
version,
purpose: "Enforces RFC-driven phase discipline for AI-assisted software development"
.to_string(),
philosophy: vec![
"RFC is the source of truth — No implementation without specification".to_string(),
"Phases are enforced — spec → impl → test → stable".to_string(),
"Governance is executable — Rules are checked, not suggested".to_string(),
],
commands: command_catalog(),
workflow: workflow_info(),
project_state: None,
suggested_actions: None,
};
if context && let Ok(index) = load_project(config) {
let rfcs: Vec<RfcState> = index
.rfcs
.iter()
.map(|r| RfcState {
id: r.rfc.rfc_id.clone(),
title: r.rfc.title.clone(),
status: r.rfc.status.as_ref().to_string(),
phase: r.rfc.phase.as_ref().to_string(),
})
.collect();
let adrs: Vec<AdrState> = index
.adrs
.iter()
.map(|a| AdrState {
id: a.meta().id.clone(),
title: a.meta().title.clone(),
status: a.meta().status.as_ref().to_string(),
})
.collect();
let work_items: Vec<WorkItemState> = index
.work_items
.iter()
.map(|w| WorkItemState {
id: w.meta().id.clone(),
title: w.meta().title.clone(),
status: w.meta().status.as_ref().to_string(),
})
.collect();
let suggestions = generate_suggestions(&rfcs, &adrs, &work_items);
output.project_state = Some(ProjectState {
rfcs,
adrs,
work_items,
});
output.suggested_actions = Some(suggestions);
}
let json = serde_json::to_string_pretty(&output)?;
println!("{}", json);
Ok(vec![])
}