I found a solution, enlisting my son (a developer in Amsterdam) to modify the solution in Post #2 to give me all macros, sorted by descending modification date, with human readable dates instead of the epoch format. If you're interested, here it is:

(() => {
"use strict";
const prettyDate = (t) => new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'short', timeZone: 'America/Denver'}).format(new Date(Number(t)));
const messy = (line) => { const p=line.split(' '); return prettyDate(p[0]) + " " + p.splice(1).join(' '); }
const main = () => {
const
km = Application("Keyboard Maestro"),
macros = km.macros;
return sortBy(
flip(compare)
)(
zipWith(
t => k => `${new Date(t).getTime()} -> ${k}`
)(
macros.modificationDate()
)(
macros.name()
)
)
.map(messy)
.join("\n");
};
// ------------------ GENERICS -------------------
// https://github.com/RobTrew/prelude-jxa
// ----------------- JS PRELUDE ------------------
// compare :: a -> a -> Ordering
const compare = a =>
b => a < b ? -1 : (a > b ? 1 : 0);
// flip :: (a -> b -> c) -> b -> a -> c
const flip = op =>
// The binary function op with
// its arguments reversed.
1 !== op.length
? (a, b) => op(b, a)
: (a => b => op(b)(a));
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
const sortBy = f =>
// A copy of xs sorted by the comparator function f.
xs => xs.slice()
.sort((a, b) => f(a)(b));
// zipWith :: (a -> a -> b) -> [a] -> [b]
const zipWith = f => {
// A list with the length of the shorter of
// xs and ys, defined by zipping with a
// custom function, rather than with the
// default tuple constructor.
const go = xs =>
ys => 0 < xs.length
? 0 < ys.length
? [f(xs[0])(ys[0])].concat(
go(xs.slice(1))(ys.slice(1))
)
: []
: [];
return go;
};
// MAIN --
return main();
})();

I've managed to tweak the JavaScript to create a zipWith4 that works if I want to add, say, the size of the macro:

(() => {
"use strict";
const main = () => {
const
km = Application("Keyboard Maestro"),
macros = km.macros;
return sortBy(
flip(compare)
)(
zipWith4(
t => k => u => v =>
`${new Date(t).getTime()} -> ${u} -> ${k} -> ${v}`
)(
macros.modificationDate()
)(
macros.name()
)(
macros.id()
)(
macros.size()
)
)
.join("\n");
};
// ------------------ GENERICS -------------------
// https://github.com/RobTrew/prelude-jxa
// ----------------- JS PRELUDE ------------------
// compare :: a -> a -> Ordering
const compare = a =>
b => a < b ? -1 : (a > b ? 1 : 0);
// flip :: (a -> b -> c) -> b -> a -> c
const flip = op =>
// The binary function op with
// its arguments reversed.
1 !== op.length
? (a, b) => op(b, a)
: (a => b => op(b)(a));
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
const sortBy = f =>
// A copy of xs sorted by the comparator function f.
xs => xs.slice()
.sort((a, b) => f(a)(b));
// zipWith4 :: (a -> b -> c -> d -> e) ->
// [a] -> [b] -> [c] -> [d] -> [e]
const zipWith4 = f =>
xs => ys => zs => zzs => Array.from({
length: Math.min(
...[xs, ys, zs, zzs].map(x => x.length)
)
}, (_, i) => f(xs[i])(ys[i])(zs[i])(zzs[i]));
// MAIN --
return main();
})();

That works perfectly. But what I'd really like is the UUID of the group containing the macro, not the size. I tried this for the fourth parameter:

macros.macrogroup().id()

But that failed ... I assume there's some way to get this, but it probably involves a mapping between the two data sets that I just don't know about ... all help appreciated!

I tried both ways, no joy—it doesn't seem to be, as the JavaScript dictionary in Script Editor shows it's the Macros object, but macros works in the script.

I've also tried macros.macrgroup().id() without luck. Clearly my lack of JS knowledge is the stumbling point here :).

Should be macroGroups for the app or macroGroup for a single macro (consult the SDEF in Script Editor, or Script Debugger), and

calling that method by adding () would lose the reference to a .MacroGroupscollection which does have has an id() method, trading it in for just a JS Array (of Macro Group values), which has no such method.

OMG that's exactly what I needed! Thank you so much—with some work now on my part, this could speed my MacroBackerUpper macro by an order of magnitude or two or ten!

PS there's already a pre-baked zipWith4 in my library at:

(tho these days for arbitrary scaling of zipping beyond zipWith3 I tend to use compositions of zipWith(x => x), which save having to write new functions)