feat: surface macros as first-class custom commands in the REPL

Implements the invocation and management surfaces from
plans/custom-commands-design.md §4 and §6:

- Top-level dispatch: an enabled macro <name> now runs as ".<name> [args]"
  from the command catch-all; runtime-disabled macros point at
  ".macro enable <name>", locked macros name the owning config, and
  unknown commands keep the existing error verbatim
- .macro enable|disable <name>: runtime toggles over the in-memory
  global-level enabled_macros list (disable with no list materializes
  all-active-minus-name); toggles error when a role/agent/session
  allowlist owns the field
- .set enabled_macros <csv|null> with workspace-then-global existence
  validation; .set key completion gains enabled_macros and the
  previously missing enabled_skills
- Dynamic completion: enabled macros (with descriptions) join built-ins
  on ".<TAB>" without touching the static command registry;
  ".macro <TAB>" lists invocable macros (incl. built-in-shadowed ones)
  plus the enable/disable subcommands; second-arg completion offers
  toggle-eligible names
- .list macros: enriched table (name, source, isolated, state,
  description) covering every resolver state incl. missing and
  shadowed rows; .help gains a custom-commands section
- Session info/render and sysinfo display enabled_macros; Macro::load
  resolves workspace-then-global; enable/disable rejected as macro
  names in the creator
This commit is contained in:
2026-08-21 11:55:13 -06:00
parent e8ddb61518
commit e94bd450cd
8 changed files with 537 additions and 53 deletions
-5
View File
@@ -474,11 +474,6 @@ pub fn list_macros() -> Vec<String> {
list_file_names(macros_dir(), ".yaml")
}
pub fn has_macro(name: &str) -> bool {
let names = list_macros();
names.contains(&name.to_string())
}
pub fn list_skills() -> Vec<String> {
let mut names = Vec::new();
let mut seen = HashSet::new();