Skip to content

Warnings

A malformed or incomplete recipe still compiles — the compiler and analyzer collect structured Warning objects instead of throwing, so callers can render a recipe and surface what’s wrong at the same time. WarningCode and friends are exported from @gram-lang/kitchen.

interface Warning {
  code: WarningCode;
  message: string;       // human-readable, ready to display as-is
  item?: string;
  loc?: { start: number; end: number }; // character offsets into the source, when available
  section?: string | null;
}

compile() returns them on CompilationResult.warnings; analyze() carries those through and may push additional ones onto the same array on AnalyzedCompilationResult.warnings. Never a bare string — .message is always present.

type WarningSeverity = "error" | "warning" | "info";
const warningSeverity: Record<WarningCode, WarningSeverity>;

Structural integrity issues — a reference to something that doesn’t exist, a naming collision, or an unresolvable module — are error. Recoverable gaps (estimation gaps, incomplete annotations) are warning, and contextual notifications (surplus quantities, batch scaling, resource contention) are info. This is exactly the distinction the CLI’s gram check --strict flag uses: without --strict, only error-severity codes fail the command; with it, every warning and info is promoted to error too. Build your own strict-mode logic on warningSeverity[code] the same way.

CodeSeverityMessage template
VARIABLE_NOT_FOUNDwarningCannot resolve relative quantity: target intermediate '&{targetName}' is not defined.
RELATIVE_QUANTITY_UNRESOLVEDwarningCannot resolve relative quantity: target ingredient '@{targetName}' was not found in the current section.
RELATIVE_QUANTITY_UNKNOWN_MASSwarningCannot compute relative quantity for '{item}': mass of target '{targetName}' is unknown.
CIRCULAR_REFERENCEerrorCircular reference detected: '{name}' depends on itself.
UNDEFINED_REFERENCEerrorUndefined reference '{prefix}{name}' — no prior step or section produces this item.
MISSING_UNITwarning{type} requires an explicit unit (e.g. min, s, °C).
INVALID_UNITwarningInvalid unit "{value}" for {type}.
SCOPE_CONFLICTerrorIntermediate variable '&{varName}' is redefined; variable names must be unique across the recipe.
MISSING_INGREDIENTwarningIngredient "{id}" not found in database — nutritional metrics and density conversions unavailable.
MISSING_MACROSinfoIngredient "{id}" has no macronutrient data in database — nutritional totals are partial.
UNKNOWN_MASSinfoCannot calculate mass for "{id}" — omitted from nutritional totals.
INVALID_MODIFIER_COMBINATIONwarningIncompatible modifiers on "{item}": {combination}.
COMPOSITE_PARENT_CONFLICTerrorComposite child "{childName}" was already linked to parent "{previousParent}" — using it with a different parent "{newParent}" here means both will share the same database entry, which is very likely wrong.
INVALID_BAKERS_REFERENCEwarning'{item}' cannot be used as the Baker's percentage reference (*).
NO_BAKERS_REFERENCEwarningBaker's percentages (%) are used but no base flour (*) was designated.
TIME_PARADOXwarningTimeline conflict: {cause} is pulled earlier than recipe start to satisfy {conflict}.
TRACK_CONTENTIONinfoResource contention on track '{trackName}': delayed by {delay} min for '{item}'.
MODULE_NOT_FOUNDerrorModule "{specifier}" could not be found or resolved.
MODULE_PARSE_ERRORerrorSyntax error in imported module "{specifier}": {parseMessage}
MODULE_CYCLEerrorCircular module import detected: {chain}.
MODULE_DEPTH_EXCEEDEDerrorImport depth limit exceeded ({depth}) while importing "{specifier}".
MODULE_EXPORT_NOT_FOUNDerrorModule "{specifier}" does not export '&{exported}' — it exists in the module but isn't re-exported. Add '-> &{exported}' to the section that produces it.
UNUSED_IMPORTwarningUnused import '&{local}' from "{specifier}".
UNRESOLVED_MODULE_YIELDerrorCannot compute yield for '&{binding}' from "{specifier}": missing physical mass data for one or more ingredients.
ESTIMATED_MODULE_YIELDwarningYield of '&{binding}' from "{specifier}" is estimated using standard ingredient densities or unit weights. Scale factor is approximate.
MODULE_UNIT_MISMATCHerrorUnit mismatch for '&{binding}' from "{specifier}": requested in '{requestedUnit}' but yields in '{yieldUnit}' without a conversion density.
MODULE_BATCH_INTERPRETATIONinfo'&{binding}' from "{specifier}" requested without unit — scaled as {batches} batch(es) of the module.
IMPORTED_BAKERS_REFERENCE_DROPPEDinfoBaker's percentage base (*) from "{specifier}" is scoped to its own module and was not imported.
DENSITY_OVERRIDE_SHADOWEDinfoDensity for "{ingredient}" in host recipe ({hostValue}) overrides module "{specifier}" ({moduleValue}).
MODULE_SURPLUSinfoScaling "{specifier}" for '&{binding}' yields a surplus: {surplus}.
MODULE_SPECIFIER_INVALIDerrorInvalid module path "{specifier}": {reason}
MODULE_SCHEME_UNSUPPORTEDerrorUnsupported URL scheme in module specifier: "{specifier}".
STOCKED_RETRO_PLANNING_IGNOREDwarningStocked module "{specifier}" has a retro-planning offset "~{...}", which is ignored because stocked items require no prep time.
RETRO_PLANNING_OVERRIDE_SHADOWEDinfoHost retro-planning offset on "@use {specifier}" overrides the module's internal offset.
MODULE_BINDING_SHADOWS_INGREDIENTwarningImported binding '&{binding}' from "{specifier}" shares name with a database ingredient.
STOCKED_DESTRUCTURED_NUTRITION_BLENDEDinfoStocked module "{specifier}" uses destructured imports — nutrition profile is averaged across the entire module.

A relative quantity references an intermediate variable (50% of &name) that hasn’t been declared as an intermediate output (>> name) anywhere in the recipe. Fix: declare the variable before referencing it, or check for a typo in the name.

A relative quantity references an ingredient (50% of @name) that hasn’t appeared earlier in the same section — relative-to-ingredient targets are section-scoped, unlike variables. Fix: move the referenced ingredient earlier in the same section, or reference a variable (&name) instead if it’s meant to be recipe-wide.

Pushed during analysis: the target of a relative quantity was found, but its own mass couldn’t be computed (no resolvable unit/density), so the percentage can’t be applied. Fix: give the target ingredient a standardizable unit, or a density/unit_weight entry in the ingredient database.

An ingredient’s relative quantity targets itself (@flour{50% of @flour}). Fix: remove the self-reference — a percentage-based quantity must target a different ingredient or variable.

A bare reference (&name) or a referenceable ingredient (@&name) points to something that was never registered earlier in the recipe. Fix: introduce the ingredient (without &) before referencing it, or check for a typo.

A Timer or Temperature was written without an explicit unit (e.g. ~{10} instead of ~{10 min}). Fix: add an explicit unit.

Either a Timer was given a non-numeric (text) quantity, or a Temperature was given a unit other than Celsius/Fahrenheit. Fix: use a numeric value + a recognized unit for timers; use °C or °F for temperatures.

Two sections declare the same intermediate/global variable name (>> name). Variable names must be unique across the whole recipe, not just within a section. Fix: rename one of the two declarations.

During nutrition estimation, an ingredient with a computable mass has no matching entry (by id or alias) in the ingredient database at all. Fix: add the ingredient (or an alias to an existing entry) to your database.

The ingredient exists in the database, but its entry has no nutrition block. Fix: add a nutrition block to that database entry.

Nutrition estimation couldn’t compute a mass for this ingredient at all (unresolvable unit, no density/unit_weight), so it’s excluded from the totals. Fix: same as RELATIVE_QUANTITY_UNKNOWN_MASS — give it a standardizable unit or physical data in the database.

Conflicting or duplicated modifiers on the same ingredient/cookware — e.g. optional (?) with important (*), hidden (-) with important (*), hidden (-) with referenceable (&), or the same modifier twice. The specific combination is named in .message. Fix: remove the conflicting modifier.

The ingredient marked as the baker’s-percentage base (via the * modifier or the bakersReference option) has a mass that was itself derived from another ingredient’s relative quantity — it can’t also serve as the 100% anchor, since that would be circular. Fix: mark a different ingredient with an absolute (non-relative) quantity as the reference.

Baker’s math was explicitly requested (enableBakersMath with a bare * search, or an explicit bakersReference id) but no ingredient matched. Fix: mark an ingredient with the * modifier, or correct the bakersReference id to match an existing ingredient.

v1.2.0

A short composite child name (e.g. @juice) is drawn from two different parent ingredients within the same recipe (e.g. <@lemon in one step and <@orange in another). Fix: use the full name for the child (e.g. @lemon juice and @orange juice) to prevent database identity collisions.

v1.2.0

An imported module specified in a @use directive could not be resolved or found on the filesystem. Fix: verify the module path, file extension (.gram), or configured path aliases in config.yaml.