/*
RFC-1126: --site flag alias resolution for workspace-scoped kernel commands.
Agents habitually pass --site to mission.* and sternsystem.* commands;
before this resolver the flag was accepted as a universal flag and silently
ignored, so the command ran against the wrong target. The resolver rewrites
--site into the command's canonical targeting flag before schema resolution:
mission → open-mission lookup by system id, id → direct alias, system →
direct alias.
- Does not apply to site-scoped commands — for them --site is the canonical site selector consumed by ensureTargetSites.
- Does not apply when the command declares its own `site` flag (leitstand.*, sternsystem.extract) — there --site is canonical, not an alias.
- Does not invent new targeting flags — only rewrites into flags the command already declares.
- Canonical flag wins on identical targets; conflicting targets are a blocking KERNEL-FLAG-02 error, never a silent pick.
- Mission resolution scans open manifests under missions/-m* — zero or multiple matches are blocking errors.
- Resolution runs inside executeRegisteredCommand before resolveCommandFlags — the single choke point for pipeline steps and direct executeKernelCommand calls.
- RFC-1126: initial implementation — resolveSiteFlagAlias with mission/id/system target mapping and conflict diagnostics.
- RFC-1126: step 2 — kernel runtime wiring
Populate KernelRuntimeContext.workpieceEnv at all 5 context construction sites (execute-command + execute-pipeline); add resolveSiteFlagAlias rewriting --site into --mission/--id/--system for workspace-scoped commands with KERNEL-FLAG-02 conflict diagnostics.
*/
import { listMissionDirs, readMissionManifest } from "../../mission/mission-io.ts";
import type { Diagnostic, KernelCommandDefinition } from "@warpgogol/werkstatt-shared/kernel";
// @ai-invariant: --site on a workspace-scoped command is an alias, never a target — resolve it into the canonical targeting flag or fail.
interface SiteFlagOccurrence {
/** Index of the `--site` token (or the `--site=value` token). */
index: number;
/** True when the value was inline (`--site=x`); false when it is the next token. */
inline: boolean;
value: string;
}
function findSiteFlag(argv: string[]): SiteFlagOccurrence | undefined {
for (let i = 0; i < argv.length; i++) {
const entry = argv[i]!;
if (entry === "--") return undefined;
if (entry === "--site") {
const next = argv[i + 1];
if (typeof next === "string" && !next.startsWith("--")) {
return { index: i, inline: false, value: next };
}
return undefined;
}
if (entry.startsWith("--site=")) {
return { index: i, inline: true, value: entry.slice("--site=".length) };
}
}
return undefined;
}
function findCanonicalFlagValue(argv: string[], flag: string): string | undefined {
const inlinePrefix = `--${flag}=`;
for (let i = 0; i < argv.length; i++) {
const entry = argv[i]!;
if (entry === "--") return undefined;
if (entry === `--${flag}`) {
const next = argv[i + 1];
if (typeof next === "string" && !next.startsWith("--")) return next;
return undefined;
}
if (entry.startsWith(inlinePrefix)) {
return entry.slice(inlinePrefix.length);
}
}
return undefined;
}
/** Remove the `--site` occurrence (and its separate value token when not inline). */
function removeSiteFlag(argv: string[], occurrence: SiteFlagOccurrence): string[] {
const out = [...argv];
out.splice(occurrence.index, occurrence.inline ? 1 : 2);
return out;
}
function flagError(message: string, fixHint: string): Diagnostic {
return { ruleId: "KERNEL-FLAG-02", severity: "error", message, fixHint };
}
/**
* Resolve the open mission id for a system by scanning missions/-m*
* manifests. Returns the mission id, or a Diagnostic when zero or multiple
* open missions exist.
*/
async function resolveOpenMission(
workspaceRoot: string,
systemId: string,
): Promise<{ missionId: string } | { diagnostic: Diagnostic }> {
const dirs = await listMissionDirs(workspaceRoot, systemId);
const open: string[] = [];
for (const dir of dirs) {
try {
const manifest = await readMissionManifest(workspaceRoot, dir);
if (manifest.state === "open") open.push(manifest.missionId);
} catch {
// Unreadable manifest — not an open mission candidate.
}
}
if (open.length === 1) {
return { missionId: open[0]! };
}
if (open.length === 0) {
return {
diagnostic: flagError(
`--site ${systemId}: no open mission found for this Sternsystem.`,
`Open one first: mission.open --system ${systemId} --brief "", or pass --mission explicitly.`,
),
};
}
return {
diagnostic: flagError(
`--site ${systemId}: multiple open missions found (${open.join(", ")}).`,
`Pass --mission explicitly to disambiguate.`,
),
};
}
/**
* Rewrite `--site ` in argv into the command's canonical targeting
* flag. Returns the (possibly unchanged) argv plus an optional error
* diagnostic that the caller merges into the flag-resolution diagnostics.
*/
export async function resolveSiteFlagAlias(
argv: string[],
command: KernelCommandDefinition,
workspaceRoot: string,
): Promise<{ argv: string[]; diagnostic?: Diagnostic }> {
// Site-scoped commands use --site as the canonical site selector.
if (command.scope !== "workspace" || !command.flags) {
return { argv };
}
// Commands declaring their own `site` flag (leitstand.*, sternsystem.extract)
// treat --site as canonical — never an alias.
if ("site" in command.flags) {
return { argv };
}
const occurrence = findSiteFlag(argv);
if (!occurrence) {
return { argv };
}
const siteValue = occurrence.value;
// Priority: mission > id > system — the first declared flag wins the alias.
if ("mission" in command.flags && command.flags.mission!.kind === "string") {
const canonical = findCanonicalFlagValue(argv, "mission");
if (canonical !== undefined) {
// Both present: identical targets → canonical wins silently.
try {
const manifest = await readMissionManifest(workspaceRoot, canonical);
if (manifest.systemId === siteValue) {
return { argv: removeSiteFlag(argv, occurrence) };
}
} catch {
// Mission manifest unreadable — canonical flag stays authoritative.
return { argv: removeSiteFlag(argv, occurrence) };
}
return {
argv,
diagnostic: flagError(
`Conflicting targets: --mission ${canonical} does not belong to --site ${siteValue}.`,
`Pass either --mission ${canonical} or --site ${siteValue}, not both.`,
),
};
}
const resolved = await resolveOpenMission(workspaceRoot, siteValue);
if ("diagnostic" in resolved) {
return { argv, diagnostic: resolved.diagnostic };
}
const out = removeSiteFlag(argv, occurrence);
out.push("--mission", resolved.missionId);
return { argv: out };
}
const targetFlag =
"id" in command.flags && command.flags.id!.kind === "string"
? "id"
: "system" in command.flags && command.flags.system!.kind === "string"
? "system"
: undefined;
if (!targetFlag) {
return { argv };
}
const canonical = findCanonicalFlagValue(argv, targetFlag);
if (canonical !== undefined) {
if (canonical === siteValue) {
return { argv: removeSiteFlag(argv, occurrence) };
}
return {
argv,
diagnostic: flagError(
`Conflicting targets: --${targetFlag} ${canonical} vs --site ${siteValue}.`,
`Pass either --${targetFlag} ${canonical} or --site ${siteValue}, not both.`,
),
};
}
const out = removeSiteFlag(argv, occurrence);
out.push(`--${targetFlag}`, siteValue);
return { argv: out };
}