fix(agents): enforce permanent canonical agent ordering with regression tests
The canonical agent order is: sisyphus → hephaestus → prometheus → atlas Changes: - Export CANONICAL_CORE_AGENT_ORDER as single source of truth - Sort non-core agents alphabetically for deterministic ordering - Add 12 regression tests including 100-permutation stability test This permanently resolves the agent ordering saga that caused 15+ commits, 8+ PRs, and multiple reverts due to ZWSP prefixes, Object.entries() order dependencies, and inconsistent merge sequences. Closes #3281
This commit is contained in:
@@ -1,40 +1,63 @@
|
||||
import { getAgentDisplayName } from "../shared/agent-display-names";
|
||||
import { getAgentDisplayName } from "../shared/agent-display-names"
|
||||
|
||||
const CORE_AGENT_ORDER: ReadonlyArray<{ displayName: string; order: number }> = [
|
||||
{ displayName: getAgentDisplayName("sisyphus"), order: 1 },
|
||||
{ displayName: getAgentDisplayName("hephaestus"), order: 2 },
|
||||
{ displayName: getAgentDisplayName("prometheus"), order: 3 },
|
||||
{ displayName: getAgentDisplayName("atlas"), order: 4 },
|
||||
];
|
||||
/**
|
||||
* CRITICAL: This is the ONLY source of truth for core agent ordering.
|
||||
* The order is: sisyphus → hephaestus → prometheus → atlas
|
||||
*
|
||||
* DO NOT CHANGE THIS ORDER. Any PR attempting to modify this order
|
||||
* or introduce alternative ordering mechanisms (ZWSP prefixes, sort
|
||||
* shims, etc.) will be rejected.
|
||||
*
|
||||
* See: src/plugin-handlers/AGENTS.md for architectural context.
|
||||
*/
|
||||
export const CANONICAL_CORE_AGENT_ORDER = [
|
||||
"sisyphus",
|
||||
"hephaestus",
|
||||
"prometheus",
|
||||
"atlas",
|
||||
] as const
|
||||
|
||||
function injectOrderField(
|
||||
agentConfig: unknown,
|
||||
order: number,
|
||||
): unknown {
|
||||
type CoreAgentName = (typeof CANONICAL_CORE_AGENT_ORDER)[number]
|
||||
|
||||
const CORE_AGENT_ORDER: ReadonlyArray<{
|
||||
configKey: CoreAgentName
|
||||
displayName: string
|
||||
order: number
|
||||
}> = CANONICAL_CORE_AGENT_ORDER.map((configKey, index) => ({
|
||||
configKey,
|
||||
displayName: getAgentDisplayName(configKey),
|
||||
order: index + 1,
|
||||
}))
|
||||
|
||||
const CORE_DISPLAY_NAMES = new Set(CORE_AGENT_ORDER.map((a) => a.displayName))
|
||||
|
||||
function injectOrderField(agentConfig: unknown, order: number): unknown {
|
||||
if (typeof agentConfig === "object" && agentConfig !== null) {
|
||||
return { ...agentConfig, order };
|
||||
return { ...agentConfig, order }
|
||||
}
|
||||
return agentConfig;
|
||||
return agentConfig
|
||||
}
|
||||
|
||||
export function reorderAgentsByPriority(
|
||||
agents: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const ordered: Record<string, unknown> = {};
|
||||
const seen = new Set<string>();
|
||||
const ordered: Record<string, unknown> = {}
|
||||
const seen = new Set<string>()
|
||||
|
||||
for (const { displayName, order } of CORE_AGENT_ORDER) {
|
||||
if (Object.prototype.hasOwnProperty.call(agents, displayName)) {
|
||||
ordered[displayName] = injectOrderField(agents[displayName], order);
|
||||
seen.add(displayName);
|
||||
ordered[displayName] = injectOrderField(agents[displayName], order)
|
||||
seen.add(displayName)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(agents)) {
|
||||
if (!seen.has(key)) {
|
||||
ordered[key] = value;
|
||||
}
|
||||
const nonCoreKeys = Object.keys(agents)
|
||||
.filter((key) => !seen.has(key))
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
|
||||
for (const key of nonCoreKeys) {
|
||||
ordered[key] = agents[key]
|
||||
}
|
||||
|
||||
return ordered;
|
||||
return ordered
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user