Context
packages/loopover-engine/src/config-lint.ts has two independent top-level-object parsers for the same manifest text, and they disagree on JSON/YAML fallback behavior:
parseTopLevelObject (line 111-126, used by unknownTopLevelWarnings, line 79): when the text looks like JSON (starts with {/[) but JSON.parse throws, it falls back to parseYaml(text) in a second try. The code comments why: "YAML flow mappings can start with { or [ while still being valid manifest syntax."
parseCanonicalTopLevelObject (line 99-109, used by recognizedFieldsFor, line 65): does the same looksLikeJson branch, but on a JSON.parse failure it does NOT fall back to YAML — the whole thing is one try/catch that returns null on any failure:
try {
return topLevelObjectOrNull(looksLikeJson ? JSON.parse(trimmed) : parseYaml(trimmed));
} catch {
return null;
}
Concretely: a manifest that is a valid YAML flow mapping starting with { (e.g. {settings: {reviewMode: strict}} without JSON-quoted keys) is real, parseable manifest syntax per unknownTopLevelWarnings's own comment. For that exact text, unknownTopLevelWarnings correctly falls back to YAML and evaluates unknown-field warnings against the real parsed object. But recognizedFieldsFor — called from buildConfigLintReport (line 44) to build the "Manifest parsed N recognized fields" report — hits the JSON.parse failure, catches it, and returns null → [], silently reporting zero recognized fields for a manifest that actually parses and is fully valid. The two functions produce internally inconsistent results (unknownTopLevelWarnings sees real fields and emits real warnings about them; recognizedFieldsFor sees nothing) for the identical input text within the same buildConfigLintReport call.
Requirements
parseCanonicalTopLevelObject must apply the same JSON→YAML fallback parseTopLevelObject already applies: on a JSON.parse failure when looksLikeJson is true, retry with parseYaml before giving up and returning null.
- After the fix,
recognizedFieldsFor and unknownTopLevelWarnings must agree on whether a given manifest text parses, for every input — de-duplicate the fallback logic into one shared helper used by both functions rather than hand-maintaining two copies, so this can't drift again.
Deliverables
Test Coverage Requirements
This repo's Codecov patch gate is 99%+ hard (branch-counted) on every changed line/branch in src/**/packages/**. The regression test above must reproduce the exact silent-empty-result failure mode described and assert it's fixed.
Expected Outcome
recognizedFieldsFor and unknownTopLevelWarnings parse the same manifest text consistently — a YAML-flow-mapping manifest starting with { is recognized by both, and buildConfigLintReport's "recognized N fields" summary no longer silently under-reports for valid manifests.
Links & Resources
packages/loopover-engine/src/config-lint.ts:65-71 (recognizedFieldsFor), :79-97 (unknownTopLevelWarnings, the correct fallback), :99-109 (parseCanonicalTopLevelObject, the gap), :111-126 (parseTopLevelObject, the sibling with the fallback already applied).
Context
packages/loopover-engine/src/config-lint.tshas two independent top-level-object parsers for the same manifest text, and they disagree on JSON/YAML fallback behavior:parseTopLevelObject(line 111-126, used byunknownTopLevelWarnings, line 79): when the text looks like JSON (starts with{/[) butJSON.parsethrows, it falls back toparseYaml(text)in a secondtry. The code comments why: "YAML flow mappings can start with{or[while still being valid manifest syntax."parseCanonicalTopLevelObject(line 99-109, used byrecognizedFieldsFor, line 65): does the samelooksLikeJsonbranch, but on aJSON.parsefailure it does NOT fall back to YAML — the whole thing is onetry/catchthat returnsnullon any failure:Concretely: a manifest that is a valid YAML flow mapping starting with
{(e.g.{settings: {reviewMode: strict}}without JSON-quoted keys) is real, parseable manifest syntax perunknownTopLevelWarnings's own comment. For that exact text,unknownTopLevelWarningscorrectly falls back to YAML and evaluates unknown-field warnings against the real parsed object. ButrecognizedFieldsFor— called frombuildConfigLintReport(line 44) to build the "Manifest parsed N recognized fields" report — hits theJSON.parsefailure, catches it, and returnsnull→[], silently reporting zero recognized fields for a manifest that actually parses and is fully valid. The two functions produce internally inconsistent results (unknownTopLevelWarningssees real fields and emits real warnings about them;recognizedFieldsForsees nothing) for the identical input text within the samebuildConfigLintReportcall.Requirements
parseCanonicalTopLevelObjectmust apply the same JSON→YAML fallbackparseTopLevelObjectalready applies: on aJSON.parsefailure whenlooksLikeJsonis true, retry withparseYamlbefore giving up and returningnull.recognizedFieldsForandunknownTopLevelWarningsmust agree on whether a given manifest text parses, for every input — de-duplicate the fallback logic into one shared helper used by both functions rather than hand-maintaining two copies, so this can't drift again.Deliverables
parseCanonicalTopLevelObject(or its replacement shared helper) applies the JSON→YAML fallback forlooksLikeJsontext, matchingparseTopLevelObject's existing behaviorrecognizedFieldsForandunknownTopLevelWarnings(eliminate the duplicatelooksLikeJson/try-catch logic betweenparseCanonicalTopLevelObjectandparseTopLevelObject){(invalid as strict JSON, e.g. unquoted keys) produces a non-emptyrecognizedFieldsForresult consistent with whatunknownTopLevelWarningsalready reports for the same textTest Coverage Requirements
This repo's Codecov patch gate is 99%+ hard (branch-counted) on every changed line/branch in
src/**/packages/**. The regression test above must reproduce the exact silent-empty-result failure mode described and assert it's fixed.Expected Outcome
recognizedFieldsForandunknownTopLevelWarningsparse the same manifest text consistently — a YAML-flow-mapping manifest starting with{is recognized by both, andbuildConfigLintReport's "recognized N fields" summary no longer silently under-reports for valid manifests.Links & Resources
packages/loopover-engine/src/config-lint.ts:65-71(recognizedFieldsFor),:79-97(unknownTopLevelWarnings, the correct fallback),:99-109(parseCanonicalTopLevelObject, the gap),:111-126(parseTopLevelObject, the sibling with the fallback already applied).