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:
Dark-Alex-17 committed 2026-08-21 11:55:13 -06:00
1 parent e8ddb61518
commit e94bd450cd
8 files changed
+537 -53

No files matched your search

-4
View File
@@ -75,7 +75,6 @@ pub enum MacroState {
}
impl MacroState {
#[allow(dead_code)]
pub fn is_invocable(&self) -> bool {
matches!(self, MacroState::Enabled | MacroState::ShadowedBuiltin)
}
@@ -95,7 +94,6 @@ pub struct DiscoveredMacro {
/// One row of the resolved macro set. Missing allowlist entries produce rows
/// with `source: None`.
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ResolvedMacro {
pub name: String,
pub source: Option<MacroSource>,
@@ -116,7 +114,6 @@ pub struct MacroPolicy {
}
impl MacroPolicy {
#[allow(dead_code)]
pub fn effective(
global: &AppConfig,
role: Option<&Role>,
@@ -199,7 +196,6 @@ impl MacroPolicy {
/// The invocation target for `name`: the workspace entry when one shadows
/// a global entry, otherwise the single discovered entry. Missing rows
/// are never returned.
#[allow(dead_code)]
pub fn find(&self, name: &str) -> Option<&ResolvedMacro> {
self.macros
.iter()