#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TaskRow {
pub task: &'static str,
pub command: &'static str,
pub note: Option<&'static str>,
pub probe: &'static [&'static str],
}
pub const TASK_MATRIX: &[TaskRow] = &[
TaskRow {
task: "delete an \"unused\" export or file",
command: "fallow dead-code --trace <file>:<export>",
note: None,
probe: &["dead-code", "--trace", "src/index.ts:foo"],
},
TaskRow {
task: "prove a TypeScript symbol's exact consumers before refactoring",
command: "fallow dead-code --type-aware --symbol-impact <file>:<export-or-class.method>",
note: None,
probe: &[
"dead-code",
"--type-aware",
"--symbol-impact",
"src/index.ts:foo",
],
},
TaskRow {
task: "delete an \"unused\" dependency",
command: "fallow dead-code --trace-dependency <name>",
note: None,
probe: &["dead-code", "--trace-dependency", "lodash"],
},
TaskRow {
task: "commit or open a PR",
command: "fallow audit --base <ref>",
note: None,
probe: &["audit", "--base", "main"],
},
TaskRow {
task: "prioritize refactoring",
command: "fallow health --hotspots --targets",
note: None,
probe: &["health", "--hotspots", "--targets"],
},
TaskRow {
task: "ask who owns code",
command: "fallow health --ownership",
note: None,
probe: &["health", "--ownership"],
},
TaskRow {
task: "check untested-but-reachable code",
command: "fallow health --coverage-gaps",
note: None,
probe: &["health", "--coverage-gaps"],
},
TaskRow {
task: "consolidate duplication",
command: "fallow dupes --trace dup:<fingerprint>",
note: None,
probe: &["dupes", "--trace", "dup:abc123"],
},
TaskRow {
task: "find feature flags",
command: "fallow flags",
note: None,
probe: &["flags"],
},
TaskRow {
task: "check which architecture rules apply to a file before changing it",
command: "fallow guard <files>",
note: None,
probe: &["guard", "src/index.ts"],
},
TaskRow {
task: "surface security candidates",
command: "fallow security",
note: None,
probe: &["security"],
},
TaskRow {
task: "understand a finding",
command: "fallow explain <issue-type>",
note: None,
probe: &["explain", "unused-export"],
},
TaskRow {
task: "scope a monorepo",
command: "--workspace <glob> / --changed-workspaces <ref>",
note: Some("global flags, prefix any command"),
probe: &[],
},
];
impl TaskRow {
#[must_use]
pub fn to_json(&self) -> serde_json::Value {
serde_json::json!({
"task": self.task,
"command": self.command,
"note": self.note,
})
}
}
pub const MUTATING_COMMANDS: &[&str] = &[
"agent",
"fix",
"init",
"hooks",
"migrate",
"setup-hooks",
"watch",
];
#[must_use]
pub fn leading_command_token(row: &TaskRow) -> &'static str {
let after_fallow = row.command.strip_prefix("fallow ").unwrap_or(row.command);
after_fallow.split_whitespace().next().unwrap_or("")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matrix_is_non_empty() {
assert!(!TASK_MATRIX.is_empty());
}
#[test]
fn matrix_excludes_mutating_commands() {
for row in TASK_MATRIX {
let first_token = leading_command_token(row);
assert!(
!MUTATING_COMMANDS.contains(&first_token),
"task matrix row '{}' names mutating command '{first_token}'",
row.task
);
}
}
#[test]
fn to_json_omits_probe_and_keeps_note_key() {
let row = TaskRow {
task: "t",
command: "fallow flags",
note: None,
probe: &["flags"],
};
let value = row.to_json();
assert_eq!(value["task"], "t");
assert_eq!(value["command"], "fallow flags");
assert!(value["note"].is_null());
assert!(value.get("probe").is_none());
}
#[test]
fn tasks_are_unique() {
let mut tasks: Vec<&str> = TASK_MATRIX.iter().map(|row| row.task).collect();
let total = tasks.len();
tasks.sort_unstable();
tasks.dedup();
assert_eq!(tasks.len(), total, "duplicate task in TASK_MATRIX");
}
#[test]
fn leading_token_skips_the_fallow_prefix() {
let row = TaskRow {
task: "t",
command: "fallow audit --base main",
note: None,
probe: &[],
};
assert_eq!(leading_command_token(&row), "audit");
let fragment = TaskRow {
task: "t",
command: "--workspace <glob>",
note: None,
probe: &[],
};
assert_eq!(leading_command_token(&fragment), "--workspace");
}
}