chore: avance acumulado de sesiones previas (reorg dev/issues + ajustes)

Reorganizacion de dev/issues en subcarpetas (completed/, cpp/, gamedev/,
kanban/, trading/, imagegen/, matrix/) y cambios acumulados en cmd/fn/pyrunner,
.claude/commands y settings. Trabajo de otro LLM/sesion, commiteado a peticion
del usuario para desbloquear el working tree. Excluido logs/ardour_mcp_server.log (ruido).
This commit is contained in:
2026-06-30 14:43:51 +02:00
parent 5501507588
commit a3f75d61ec
125 changed files with 421 additions and 203 deletions
+50 -34
View File
@@ -18,6 +18,7 @@ type pyParam struct {
Default string // empty if required
IsKwargs bool // **kwargs
IsRegistry bool // type is a registry type (needs factory)
KwOnly bool // declared after a bare "*" or "*args" — must be passed by keyword
}
// pyFactory links a registry type to the function that creates it.
@@ -45,12 +46,21 @@ func parsePySignature(sig string) []pyParam {
// Split by comma, respecting nested brackets
parts := splitParams(raw)
var params []pyParam
kwOnly := false
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" || part == "self" || part == "cls" {
continue
}
// A bare "*" (PEP 3102) or "*args" var-positional marks the start of
// keyword-only params. Neither maps cleanly to positional CLI args, so
// skip the marker itself and flag every following param as keyword-only.
if part == "*" || (strings.HasPrefix(part, "*") && !strings.HasPrefix(part, "**")) {
kwOnly = true
continue
}
p := parseSingleParam(part)
p.KwOnly = kwOnly
params = append(params, p)
}
return params
@@ -189,11 +199,19 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
// Classify params
var factoryImports []string // import lines for factories
var factorySetup []string // code to create factory objects
var argLines []string // code to parse CLI args
var callArgs []string // arguments to pass to the function
var bodyLines []string // code that fills _call_args / _call_kwargs
cliArgIdx := 0
// emitCall appends one param to _call_args (positional) or _call_kwargs
// (keyword-only). indent prefixes the line (for params read inside an `if`).
emitCall := func(p pyParam, indent string) string {
if p.KwOnly {
return fmt.Sprintf("%s_call_kwargs[%q] = %s", indent, p.Name, p.Name)
}
return fmt.Sprintf("%s_call_args.append(%s)", indent, p.Name)
}
for _, p := range params {
if p.IsKwargs {
// Skip **kwargs for now — can't auto-resolve from CLI
@@ -235,27 +253,35 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
fmt.Sprintf("%s = %s(%s)", p.Name, factory.FuncName,
strings.Join(factoryArgs, ", ")))
callArgs = append(callArgs, p.Name)
// Factory objects are always present (required).
bodyLines = append(bodyLines, emitCall(p, ""))
} else {
// Primitive type — from CLI args
// Primitive type — from CLI args.
if p.Default != "" {
// Optional param with default
argLines = append(argLines,
fmt.Sprintf("%s = _args[%d] if len(_args) > %d else %s",
p.Name, cliArgIdx, cliArgIdx, convertDefault(p.Type, p.Default)))
argLines = append(argLines,
convertArg(p.Name, p.Type, true))
// Optional: only pass when the CLI arg is present. When absent we
// DON'T replicate the signature default (it may reference a module
// constant that doesn't exist in this runner) — we simply omit the
// argument so the function applies its own native default.
bodyLines = append(bodyLines,
fmt.Sprintf("if len(_args) > %d:", cliArgIdx))
bodyLines = append(bodyLines,
fmt.Sprintf(" %s = _args[%d]", p.Name, cliArgIdx))
if conv := convertArg(p.Name, p.Type, true); conv != "" {
bodyLines = append(bodyLines, " "+conv)
}
bodyLines = append(bodyLines, emitCall(p, " "))
} else {
// Required param
argLines = append(argLines,
// Required param.
bodyLines = append(bodyLines,
fmt.Sprintf("if len(_args) <= %d: sys.exit('error: missing required arg: %s (%s)')",
cliArgIdx, p.Name, p.Type))
argLines = append(argLines,
bodyLines = append(bodyLines,
fmt.Sprintf("%s = _args[%d]", p.Name, cliArgIdx))
argLines = append(argLines,
convertArg(p.Name, p.Type, false))
if conv := convertArg(p.Name, p.Type, false); conv != "" {
bodyLines = append(bodyLines, conv)
}
bodyLines = append(bodyLines, emitCall(p, ""))
}
callArgs = append(callArgs, p.Name)
cliArgIdx++
}
}
@@ -289,18 +315,18 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
sb.WriteString("\n")
}
// Arg parsing
if len(argLines) > 0 {
sb.WriteString("# --- parse CLI args ---\n")
for _, line := range argLines {
sb.WriteString(line + "\n")
}
sb.WriteString("\n")
// Arg parsing — build the positional/keyword argument collections.
sb.WriteString("# --- parse CLI args ---\n")
sb.WriteString("_call_args = []\n")
sb.WriteString("_call_kwargs = {}\n")
for _, line := range bodyLines {
sb.WriteString(line + "\n")
}
sb.WriteString("\n")
// Call
sb.WriteString("# --- execute ---\n")
sb.WriteString(fmt.Sprintf("_result = %s(%s)\n", fn.Name, strings.Join(callArgs, ", ")))
sb.WriteString(fmt.Sprintf("_result = %s(*_call_args, **_call_kwargs)\n", fn.Name))
sb.WriteString("\n")
// Output
@@ -365,16 +391,6 @@ func convertArg(name, typ string, _ bool) string {
}
}
// convertDefault ensures the default value is valid Python for the given type.
func convertDefault(_, def string) string {
// Most defaults from the signature are already valid Python
// Just handle the None case for Optional types
if def == "None" || def == "" {
return "None"
}
return def
}
// pythonList creates a Python list literal from strings: ["a", "b", "c"]
func pythonList(items []string) string {
quoted := make([]string, len(items))