@metaobjectsdev/render 0.7.0-rc.9 → 0.8.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/index.d.ts +8 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +8 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/prompt/output-format-renderer.d.ts +8 -0
  6. package/dist/prompt/output-format-renderer.d.ts.map +1 -0
  7. package/dist/prompt/output-format-renderer.js +164 -0
  8. package/dist/prompt/output-format-renderer.js.map +1 -0
  9. package/dist/prompt/output-format-spec.d.ts +18 -0
  10. package/dist/prompt/output-format-spec.d.ts.map +1 -0
  11. package/dist/prompt/output-format-spec.js +3 -0
  12. package/dist/prompt/output-format-spec.js.map +1 -0
  13. package/dist/prompt/prompt-field.d.ts +22 -0
  14. package/dist/prompt/prompt-field.d.ts.map +1 -0
  15. package/dist/prompt/prompt-field.js +3 -0
  16. package/dist/prompt/prompt-field.js.map +1 -0
  17. package/dist/prompt/prompt-overrides.d.ts +16 -0
  18. package/dist/prompt/prompt-overrides.d.ts.map +1 -0
  19. package/dist/prompt/prompt-overrides.js +13 -0
  20. package/dist/prompt/prompt-overrides.js.map +1 -0
  21. package/dist/prompt/prompt-style.d.ts +17 -0
  22. package/dist/prompt/prompt-style.d.ts.map +1 -0
  23. package/dist/prompt/prompt-style.js +34 -0
  24. package/dist/prompt/prompt-style.js.map +1 -0
  25. package/dist/recover/coerce.d.ts +5 -0
  26. package/dist/recover/coerce.d.ts.map +1 -0
  27. package/dist/recover/coerce.js +124 -0
  28. package/dist/recover/coerce.js.map +1 -0
  29. package/dist/recover/json-forgiving-reader.d.ts +5 -0
  30. package/dist/recover/json-forgiving-reader.d.ts.map +1 -0
  31. package/dist/recover/json-forgiving-reader.js +178 -0
  32. package/dist/recover/json-forgiving-reader.js.map +1 -0
  33. package/dist/recover/locate.d.ts +5 -0
  34. package/dist/recover/locate.d.ts.map +1 -0
  35. package/dist/recover/locate.js +75 -0
  36. package/dist/recover/locate.js.map +1 -0
  37. package/dist/recover/recover-map.d.ts +7 -0
  38. package/dist/recover/recover-map.d.ts.map +1 -0
  39. package/dist/recover/recover-map.js +36 -0
  40. package/dist/recover/recover-map.js.map +1 -0
  41. package/dist/recover/recover.d.ts +4 -0
  42. package/dist/recover/recover.d.ts.map +1 -0
  43. package/dist/recover/recover.js +115 -0
  44. package/dist/recover/recover.js.map +1 -0
  45. package/dist/recover/strip.d.ts +2 -0
  46. package/dist/recover/strip.d.ts.map +1 -0
  47. package/dist/recover/strip.js +17 -0
  48. package/dist/recover/strip.js.map +1 -0
  49. package/dist/recover/types.d.ts +117 -0
  50. package/dist/recover/types.d.ts.map +1 -0
  51. package/dist/recover/types.js +124 -0
  52. package/dist/recover/types.js.map +1 -0
  53. package/dist/recover/xml-forgiving-reader.d.ts +2 -0
  54. package/dist/recover/xml-forgiving-reader.d.ts.map +1 -0
  55. package/dist/recover/xml-forgiving-reader.js +79 -0
  56. package/dist/recover/xml-forgiving-reader.js.map +1 -0
  57. package/package.json +1 -1
  58. package/src/index.ts +42 -0
  59. package/src/prompt/output-format-renderer.ts +179 -0
  60. package/src/prompt/output-format-spec.ts +20 -0
  61. package/src/prompt/prompt-field.ts +24 -0
  62. package/src/prompt/prompt-overrides.ts +27 -0
  63. package/src/prompt/prompt-style.ts +36 -0
  64. package/src/recover/KNOWN_GAPS.md +35 -0
  65. package/src/recover/coerce.ts +141 -0
  66. package/src/recover/json-forgiving-reader.ts +167 -0
  67. package/src/recover/locate.ts +72 -0
  68. package/src/recover/recover-map.ts +39 -0
  69. package/src/recover/recover.ts +146 -0
  70. package/src/recover/strip.ts +17 -0
  71. package/src/recover/types.ts +217 -0
  72. package/src/recover/xml-forgiving-reader.ts +82 -0
@@ -0,0 +1,124 @@
1
+ // FR-010 recover engine — types & model (Tier-2 idiomatic TS port).
2
+ //
3
+ // Cross-port REFERENCE is the Java engine
4
+ // (server/java/render/.../recover/). This file ports the Java records/enums to
5
+ // idiomatic TS: enums become string-union `as const` objects (values match the
6
+ // corpus / Java enum names exactly), records become readonly interfaces +
7
+ // factory functions, and the mutable RecoveryReport is a class.
8
+ /** Output format the dirty text claims to be. Corpus schema.json uses "JSON"/"XML". */
9
+ export const Format = {
10
+ JSON: "JSON",
11
+ XML: "XML",
12
+ };
13
+ /** The coercion target kinds the engine understands. OBJECT = nested RecoverSchema. */
14
+ export const FieldKind = {
15
+ STRING: "STRING",
16
+ INT: "INT",
17
+ LONG: "LONG",
18
+ DOUBLE: "DOUBLE",
19
+ BOOLEAN: "BOOLEAN",
20
+ ENUM: "ENUM",
21
+ OBJECT: "OBJECT",
22
+ };
23
+ /**
24
+ * FROZEN cross-port per-field recovery classification. Do not reorder or add
25
+ * without an ADR. These string values are SERIALIZED in the conformance corpus.
26
+ */
27
+ export const FieldRecovery = {
28
+ RECOVERED: "RECOVERED",
29
+ // DEFAULTED is reserved (a future @default-backed value); the engine does not emit it yet.
30
+ DEFAULTED: "DEFAULTED",
31
+ LOST_OPTIONAL: "LOST_OPTIONAL",
32
+ LOST_REQUIRED: "LOST_REQUIRED",
33
+ MALFORMED: "MALFORMED",
34
+ };
35
+ /**
36
+ * STRICT: case-sensitive, minimal repair. NORMAL: case-insensitive keys/tags
37
+ * (default). LOOSE: maximal repair (currently identical to NORMAL — reserved).
38
+ */
39
+ export const Tolerance = {
40
+ STRICT: "STRICT",
41
+ NORMAL: "NORMAL",
42
+ LOOSE: "LOOSE",
43
+ };
44
+ export function scalar(name, kind, required) {
45
+ return { name, kind, required, array: false, enumValues: null, enumAlias: null, min: null, max: null, nested: null };
46
+ }
47
+ export function enumField(name, required, values, aliases) {
48
+ return {
49
+ name,
50
+ kind: FieldKind.ENUM,
51
+ required,
52
+ array: false,
53
+ enumValues: values == null ? null : [...values],
54
+ enumAlias: aliases == null ? {} : { ...aliases },
55
+ min: null,
56
+ max: null,
57
+ nested: null,
58
+ };
59
+ }
60
+ export function range(name, kind, required, min, max) {
61
+ return { name, kind, required, array: false, enumValues: null, enumAlias: null, min, max, nested: null };
62
+ }
63
+ export function object(name, required, array, nested) {
64
+ return { name, kind: FieldKind.OBJECT, required, array, enumValues: null, enumAlias: null, min: null, max: null, nested };
65
+ }
66
+ export function recoverSchema(format, rootName, fields) {
67
+ return { format, rootName, fields: fields == null ? [] : [...fields] };
68
+ }
69
+ export function defaults() {
70
+ return { tolerance: Tolerance.NORMAL, aliases: {}, normalizers: {}, onField: null };
71
+ }
72
+ /** Normalize a partial / undefined options bag into a complete RecoverOptions. */
73
+ export function normalizeOptions(opts) {
74
+ if (opts == null)
75
+ return defaults();
76
+ return {
77
+ tolerance: opts.tolerance ?? Tolerance.NORMAL,
78
+ aliases: opts.aliases == null ? {} : { ...opts.aliases },
79
+ normalizers: opts.normalizers == null ? {} : { ...opts.normalizers },
80
+ onField: opts.onField ?? null,
81
+ };
82
+ }
83
+ /** Mutable accumulator of per-field recovery classification, the degenerate-response flag, and coercion notes. */
84
+ export class RecoveryReport {
85
+ // Insertion-ordered (Map preserves insertion order, mirroring Java LinkedHashMap).
86
+ _states = new Map();
87
+ _coercions = [];
88
+ _empty = false;
89
+ set(fieldPath, state) {
90
+ this._states.set(fieldPath, state);
91
+ }
92
+ addCoercion(c) {
93
+ this._coercions.push(c);
94
+ }
95
+ markEmpty() {
96
+ this._empty = true;
97
+ }
98
+ isEmpty() {
99
+ return this._empty;
100
+ }
101
+ states() {
102
+ return new Map(this._states);
103
+ }
104
+ coercions() {
105
+ return [...this._coercions];
106
+ }
107
+ lostRequired() {
108
+ return this.byState(FieldRecovery.LOST_REQUIRED);
109
+ }
110
+ malformed() {
111
+ return this.byState(FieldRecovery.MALFORMED);
112
+ }
113
+ hasLostRequired() {
114
+ return this.lostRequired().length > 0;
115
+ }
116
+ byState(s) {
117
+ const out = [];
118
+ for (const [k, v] of this._states)
119
+ if (v === s)
120
+ out.push(k);
121
+ return out;
122
+ }
123
+ }
124
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/recover/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function readXml(span: string | null | undefined, caseInsensitive: boolean): Record<string, unknown>;
2
+ //# sourceMappingURL=xml-forgiving-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml-forgiving-reader.d.ts","sourceRoot":"","sources":["../../src/recover/xml-forgiving-reader.ts"],"names":[],"mappings":"AAgBA,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,eAAe,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAS1G"}
@@ -0,0 +1,79 @@
1
+ // Stage-4 tolerant XML reader for the bounded corpus malformation set. Never throws.
2
+ // Mirrors Java XmlForgivingReader. Must not index-out-of-range on a leading close tag.
3
+ function quote(s) {
4
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
6
+ /** Find first regex match at index >= `from` (emulates Java Matcher.find(int)). */
7
+ function matchFrom(source, flags, text, from) {
8
+ const g = new RegExp(source, flags.includes("g") ? flags : flags + "g");
9
+ g.lastIndex = from;
10
+ return g.exec(text);
11
+ }
12
+ const OPEN_TAG_SRC = "<([A-Za-z_][A-Za-z0-9_]*)(\\s[^>]*)?>";
13
+ export function readXml(span, caseInsensitive) {
14
+ const out = {};
15
+ if (span == null || span.trim().length === 0)
16
+ return out;
17
+ const gt = span.indexOf(">");
18
+ if (gt < 0)
19
+ return out;
20
+ const rootEnd = span.lastIndexOf("</");
21
+ const inner = span.substring(gt + 1, rootEnd < 0 || rootEnd <= gt ? span.length : rootEnd);
22
+ parseChildren(inner, caseInsensitive, out);
23
+ return out;
24
+ }
25
+ function parseChildren(inner, ci, out) {
26
+ const flags = ci ? "i" : "";
27
+ let pos = 0;
28
+ for (;;) {
29
+ const m = matchFrom(OPEN_TAG_SRC, flags, inner, pos);
30
+ if (m == null)
31
+ break;
32
+ const tag = m[1] ?? "";
33
+ const key = ci ? tag.toLowerCase() : tag;
34
+ const contentStart = m.index + m[0].length;
35
+ const closeRe = `</${quote(tag)}\\s*>`;
36
+ const close = matchFrom(closeRe, flags, inner, contentStart);
37
+ let contentEnd;
38
+ let next;
39
+ if (close != null) {
40
+ contentEnd = close.index;
41
+ next = close.index + close[0].length;
42
+ }
43
+ else {
44
+ // unclosed tag: recover text up to the next sibling open tag
45
+ const sib = matchFrom(OPEN_TAG_SRC, flags, inner, contentStart);
46
+ if (sib != null) {
47
+ contentEnd = sib.index;
48
+ next = contentEnd;
49
+ }
50
+ else {
51
+ contentEnd = inner.length;
52
+ next = inner.length;
53
+ }
54
+ }
55
+ const content = inner.substring(contentStart, contentEnd);
56
+ const value = content.includes("<") ? nestedOrText(content, ci) : content.trim();
57
+ accumulate(out, key, value);
58
+ pos = next;
59
+ }
60
+ }
61
+ function nestedOrText(content, ci) {
62
+ const nested = {};
63
+ parseChildren(content, ci, nested);
64
+ return Object.keys(nested).length === 0 ? content.trim() : nested;
65
+ }
66
+ function accumulate(out, key, value) {
67
+ if (!Object.prototype.hasOwnProperty.call(out, key)) {
68
+ out[key] = value;
69
+ return;
70
+ }
71
+ const existing = out[key];
72
+ if (Array.isArray(existing)) {
73
+ existing.push(value);
74
+ }
75
+ else {
76
+ out[key] = [existing, value];
77
+ }
78
+ }
79
+ //# sourceMappingURL=xml-forgiving-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml-forgiving-reader.js","sourceRoot":"","sources":["../../src/recover/xml-forgiving-reader.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/render",
3
- "version": "0.7.0-rc.9",
3
+ "version": "0.8.0-rc.1",
4
4
  "description": "Logic-less, deterministic text render engine (Mustache) for MetaObjects templates — provider-resolved partials, format-driven escaping, zero core dependency.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -11,3 +11,45 @@ export {
11
11
  type VerifyError,
12
12
  type VerifyOptions,
13
13
  } from "./verify.js";
14
+
15
+ // FR-010 tolerant recover engine (Tier-2 forgiving parser).
16
+ export { recover } from "./recover/recover.js";
17
+ export {
18
+ Format,
19
+ FieldKind,
20
+ FieldRecovery,
21
+ Tolerance,
22
+ RecoveryReport,
23
+ scalar,
24
+ enumField,
25
+ range,
26
+ object,
27
+ recoverSchema,
28
+ defaults,
29
+ type FieldSpec,
30
+ type RecoverSchema,
31
+ type RecoverOptions,
32
+ type RecoverOutcome,
33
+ type RecoveryResult,
34
+ type Coercion,
35
+ type OnField,
36
+ } from "./recover/types.js";
37
+ export {
38
+ asString,
39
+ asInt,
40
+ asLong,
41
+ asDouble,
42
+ asBool,
43
+ asStringList,
44
+ } from "./recover/recover-map.js";
45
+
46
+ // FR-010 artifact 1 — output-format prompt renderer ("produce your answer like this").
47
+ export { renderOutputFormat } from "./prompt/output-format-renderer.js";
48
+ export { PromptStyle, promptStyleFrom } from "./prompt/prompt-style.js";
49
+ export {
50
+ PROMPT_OVERRIDES_NONE,
51
+ noOverrides,
52
+ type PromptOverrides,
53
+ } from "./prompt/prompt-overrides.js";
54
+ export type { OutputFormatSpec } from "./prompt/output-format-spec.js";
55
+ export type { PromptField } from "./prompt/prompt-field.js";
@@ -0,0 +1,179 @@
1
+ // FR-010 artifact 1 — output-format prompt renderer ("produce your answer like this").
2
+ //
3
+ // Renders an OutputFormatSpec into a prompt fragment that teaches an LLM how to
4
+ // shape its answer. Three comment-free styles (guide / inline / exampleOnly) ×
5
+ // two formats (json / xml). Guidance is carried in prose / inline placeholders /
6
+ // a filled skeleton — NEVER in comments (models ignore them).
7
+ //
8
+ // Cross-port INVARIANT: the rendered text is byte-identical to the Java/C#/Kotlin
9
+ // reference (com.metaobjects.render.prompt.OutputFormatRenderer). Do not change
10
+ // the verbatim prose, skeleton shapes, or numeric-vs-quoted decision.
11
+
12
+ import { ESCAPERS } from "../escapers.js";
13
+ import { FieldKind, Format } from "../recover/types.js";
14
+ import type { OutputFormatSpec } from "./output-format-spec.js";
15
+ import type { PromptField } from "./prompt-field.js";
16
+ import type { PromptOverrides } from "./prompt-overrides.js";
17
+ import { PromptStyle } from "./prompt-style.js";
18
+
19
+ const NUMERIC_KINDS: ReadonlySet<FieldKind> = new Set<FieldKind>([
20
+ FieldKind.INT,
21
+ FieldKind.LONG,
22
+ FieldKind.DOUBLE,
23
+ FieldKind.BOOLEAN,
24
+ ]);
25
+
26
+ // The render engine OWNS format-keyed escaping; Format ("JSON"/"XML") maps to the
27
+ // lowercase ESCAPERS keys.
28
+ const escapeXml = (s: string): string => ESCAPERS.xml(s);
29
+ const escapeJson = (s: string): string => ESCAPERS.json(s);
30
+
31
+ /**
32
+ * Render an {@link OutputFormatSpec} into an output-format prompt fragment. The
33
+ * effective style is the override's style if present, otherwise the spec's.
34
+ */
35
+ export function renderOutputFormat(spec: OutputFormatSpec, overrides: PromptOverrides): string {
36
+ const effectiveStyle = overrides.style ?? spec.style;
37
+ switch (effectiveStyle) {
38
+ case PromptStyle.EXAMPLE_ONLY:
39
+ return renderExampleOnly(spec, overrides);
40
+ case PromptStyle.INLINE:
41
+ return renderInline(spec, overrides);
42
+ default:
43
+ return renderGuide(spec, overrides);
44
+ }
45
+ }
46
+
47
+ // ---- INLINE ----------------------------------------------------------------
48
+
49
+ function renderInline(spec: OutputFormatSpec, overrides: PromptOverrides): string {
50
+ return spec.format === Format.XML
51
+ ? renderXmlInline(spec, overrides)
52
+ : renderJsonInline(spec, overrides);
53
+ }
54
+
55
+ function renderXmlInline(spec: OutputFormatSpec, overrides: PromptOverrides): string {
56
+ const lines = spec.fields.map((field) => {
57
+ const escaped = escapeXml(inlineContent(field, overrides));
58
+ return ` <${field.name}>${escaped}</${field.name}>\n`;
59
+ });
60
+ return `<${spec.rootName}>\n${lines.join("")}</${spec.rootName}>`;
61
+ }
62
+
63
+ function renderJsonInline(spec: OutputFormatSpec, overrides: PromptOverrides): string {
64
+ const lines = spec.fields.map(
65
+ (field) => ` "${field.name}": "${escapeJson(inlineContent(field, overrides))}"`,
66
+ );
67
+ // Empty object is `{\n}` (Java/C# parity), not `{\n\n}` from join("") on no lines.
68
+ return spec.fields.length === 0 ? "{\n}" : `{\n${lines.join(",\n")}\n}`;
69
+ }
70
+
71
+ function inlineContent(field: PromptField, overrides: PromptOverrides): string {
72
+ if (field.kind === FieldKind.ENUM && field.enumValues != null && field.enumValues.length > 0) {
73
+ return field.enumValues.join(" | ");
74
+ }
75
+ if (field.kind === FieldKind.BOOLEAN) {
76
+ return "true | false";
77
+ }
78
+ const instruction = resolveInstruction(field, overrides);
79
+ return instruction != null ? `{${instruction}}` : `{${field.name}}`;
80
+ }
81
+
82
+ /** Effective instruction: override first, then the field default, else null. */
83
+ function resolveInstruction(field: PromptField, overrides: PromptOverrides): string | null {
84
+ const ov = overrides.instructions?.[field.name];
85
+ if (ov != null) return ov;
86
+ return field.instruction;
87
+ }
88
+
89
+ // ---- GUIDE -----------------------------------------------------------------
90
+
91
+ function renderGuide(spec: OutputFormatSpec, overrides: PromptOverrides): string {
92
+ let sb = "Fill in each field as described below:\n";
93
+ for (const field of spec.fields) {
94
+ const req = field.required ? "required" : "optional";
95
+ sb += `- ${field.name} (${req})`;
96
+ const instruction = resolveInstruction(field, overrides);
97
+ if (instruction != null) {
98
+ sb += `: ${instruction}`;
99
+ }
100
+ sb += "\n";
101
+ if (field.kind === FieldKind.ENUM && field.enumValues != null && field.enumValues.length > 0) {
102
+ sb += ` one of ${field.enumValues.join(", ")}\n`;
103
+ const enumDoc = field.enumDoc;
104
+ if (enumDoc != null) {
105
+ for (const val of field.enumValues) {
106
+ const doc = enumDoc[val];
107
+ if (doc != null) {
108
+ sb += ` ${val} = ${doc}\n`;
109
+ }
110
+ }
111
+ }
112
+ }
113
+ const eg = exampleValueIfDeclared(field, overrides);
114
+ if (eg != null) {
115
+ sb += ` e.g. ${eg}\n`;
116
+ }
117
+ }
118
+ sb += "\nRespond exactly like this:\n";
119
+ sb += renderExampleOnly(spec, overrides);
120
+ return sb;
121
+ }
122
+
123
+ // ---- EXAMPLE-ONLY (also the skeleton appended by GUIDE) ---------------------
124
+
125
+ function renderExampleOnly(spec: OutputFormatSpec, overrides: PromptOverrides): string {
126
+ return spec.format === Format.XML
127
+ ? renderXmlSkeleton(spec, overrides)
128
+ : renderJsonSkeleton(spec, overrides);
129
+ }
130
+
131
+ function renderXmlSkeleton(spec: OutputFormatSpec, overrides: PromptOverrides): string {
132
+ const lines = spec.fields.map((field) => {
133
+ const escaped = escapeXml(exampleValue(field, overrides));
134
+ return ` <${field.name}>${escaped}</${field.name}>\n`;
135
+ });
136
+ return `<${spec.rootName}>\n${lines.join("")}</${spec.rootName}>`;
137
+ }
138
+
139
+ function renderJsonSkeleton(spec: OutputFormatSpec, overrides: PromptOverrides): string {
140
+ // NOTE: FieldKind.OBJECT / nested fields are not expanded here — they render as
141
+ // a "{fieldName}" placeholder. Nested-object expansion is a bounded deferral
142
+ // (mirrors Java/C#).
143
+ const lines = spec.fields.map((field) => {
144
+ const value = exampleValue(field, overrides);
145
+ const rendered = isNumericOrBoolean(field.kind, value) ? value : `"${escapeJson(value)}"`;
146
+ return ` "${field.name}": ${rendered}`;
147
+ });
148
+ // Empty object is `{\n}` (Java/C# parity), not `{\n\n}` from join("") on no lines.
149
+ return spec.fields.length === 0 ? "{\n}" : `{\n${lines.join(",\n")}\n}`;
150
+ }
151
+
152
+ function exampleValueIfDeclared(field: PromptField, overrides: PromptOverrides): string | null {
153
+ const ov = overrides.examples?.[field.name];
154
+ if (ov != null) return ov;
155
+ if (field.example != null) return field.example;
156
+ return null;
157
+ }
158
+
159
+ function exampleValue(field: PromptField, overrides: PromptOverrides): string {
160
+ const ov = overrides.examples?.[field.name];
161
+ if (ov != null) return ov;
162
+ if (field.example != null) return field.example;
163
+ if (field.kind === FieldKind.ENUM && field.enumValues != null && field.enumValues.length > 0) {
164
+ return field.enumValues[0]!;
165
+ }
166
+ return `{${field.name}}`;
167
+ }
168
+
169
+ function isNumericOrBoolean(kind: FieldKind, value: string): boolean {
170
+ if (!NUMERIC_KINDS.has(kind)) return false;
171
+ if (value === "true" || value === "false") return true;
172
+ // Finite-only: NaN/Infinity fall through to a quoted string so the emitted JSON
173
+ // stays valid. Number("") is 0, so guard the empty/blank case explicitly. Reject
174
+ // JS-only radix literals (0x../0b../0o..) that Number() accepts but Java/C# don't —
175
+ // same guard as the recover engine's parseFiniteNumber (keeps the JSON valid + parity).
176
+ const t = value.trim();
177
+ if (t === "" || /^[+-]?0[xXbBoO]/.test(t)) return false;
178
+ return Number.isFinite(Number(t));
179
+ }
@@ -0,0 +1,20 @@
1
+ // FR-010 artifact 1 — a complete output-format descriptor.
2
+
3
+ import type { Format } from "../recover/types.js";
4
+ import type { PromptField } from "./prompt-field.js";
5
+ import type { PromptStyle } from "./prompt-style.js";
6
+
7
+ /**
8
+ * A complete output-format descriptor: the format, the root element/object name,
9
+ * the default presentation style, and the ordered fields. Drives
10
+ * {@link renderOutputFormat}.
11
+ *
12
+ * Precondition: `rootName` must be identifier-safe (valid XML element name / JSON
13
+ * key). The renderer does not escape it.
14
+ */
15
+ export interface OutputFormatSpec {
16
+ readonly format: Format;
17
+ readonly rootName: string;
18
+ readonly style: PromptStyle;
19
+ readonly fields: readonly PromptField[];
20
+ }
@@ -0,0 +1,24 @@
1
+ // FR-010 artifact 1 — one field of an output-format fragment.
2
+
3
+ import type { FieldKind } from "../recover/types.js";
4
+ import type { OutputFormatSpec } from "./output-format-spec.js";
5
+
6
+ /**
7
+ * One field of an output-format fragment. `enumValues`/`enumDoc` are non-null
8
+ * only for ENUM; `nested` non-null only for OBJECT; `example`/`instruction` are
9
+ * nullable.
10
+ *
11
+ * Precondition: `name` must be identifier-safe (valid XML element name / JSON
12
+ * key). The renderer does not escape field names.
13
+ */
14
+ export interface PromptField {
15
+ readonly name: string;
16
+ readonly kind: FieldKind;
17
+ readonly required: boolean;
18
+ readonly array: boolean;
19
+ readonly enumValues: readonly string[] | null;
20
+ readonly enumDoc: Readonly<Record<string, string>> | null;
21
+ readonly example: string | null;
22
+ readonly instruction: string | null;
23
+ readonly nested: OutputFormatSpec | null;
24
+ }
@@ -0,0 +1,27 @@
1
+ // FR-010 artifact 1 — render-time overrides of the metadata defaults.
2
+
3
+ import type { PromptStyle } from "./prompt-style.js";
4
+
5
+ /**
6
+ * Render-time overrides of the metadata defaults. `style` undefined keeps the
7
+ * spec's style; the maps override `PromptField.example`/`PromptField.instruction`
8
+ * per field name.
9
+ */
10
+ export interface PromptOverrides {
11
+ readonly style?: PromptStyle;
12
+ readonly examples?: Readonly<Record<string, string>>;
13
+ readonly instructions?: Readonly<Record<string, string>>;
14
+ }
15
+
16
+ /** No overrides — keep every metadata default. Mirrors Java `PromptOverrides.none()`. */
17
+ export const PROMPT_OVERRIDES_NONE: PromptOverrides = Object.freeze({
18
+ // `style` omitted (not `undefined`) under exactOptionalPropertyTypes: an absent
19
+ // style keeps the spec's own style, matching Java `PromptOverrides.none()`.
20
+ examples: {},
21
+ instructions: {},
22
+ });
23
+
24
+ /** No overrides — keep every metadata default. */
25
+ export function noOverrides(): PromptOverrides {
26
+ return PROMPT_OVERRIDES_NONE;
27
+ }
@@ -0,0 +1,36 @@
1
+ // FR-010 artifact 1 — output-format prompt renderer.
2
+ //
3
+ // How the output-format fragment teaches the model. Guidance is NEVER carried in
4
+ // comments (models ignore them) — it lives in prose / inline placeholders / a
5
+ // filled skeleton. Default is "guide".
6
+ //
7
+ // Tier-2 idiomatic TS: the Java SCREAMING_SNAKE enum (GUIDE/INLINE/EXAMPLE_ONLY)
8
+ // becomes a string-union whose members ARE the wire `@promptStyle` values
9
+ // ("guide" | "inline" | "exampleOnly") — no name<->wire mapping table needed.
10
+
11
+ /**
12
+ * - `"guide"`: prose field list ("Fill in each field…") followed by an example skeleton.
13
+ * - `"inline"`: a single skeleton whose field values are inline placeholders / enum choices.
14
+ * - `"exampleOnly"`: just a filled example skeleton, nothing else.
15
+ */
16
+ export const PromptStyle = {
17
+ GUIDE: "guide",
18
+ INLINE: "inline",
19
+ EXAMPLE_ONLY: "exampleOnly",
20
+ } as const;
21
+ export type PromptStyle = (typeof PromptStyle)[keyof typeof PromptStyle];
22
+
23
+ /**
24
+ * Maps the `@promptStyle` attribute string to a {@link PromptStyle}. Null or any
25
+ * unrecognized value falls back to `"guide"` (matches Java `PromptStyle.from`).
26
+ */
27
+ export function promptStyleFrom(s?: string | null): PromptStyle {
28
+ switch (s) {
29
+ case PromptStyle.INLINE:
30
+ return PromptStyle.INLINE;
31
+ case PromptStyle.EXAMPLE_ONLY:
32
+ return PromptStyle.EXAMPLE_ONLY;
33
+ default:
34
+ return PromptStyle.GUIDE;
35
+ }
36
+ }
@@ -0,0 +1,35 @@
1
+ # FR-010 TypeScript recover engine — known gaps & intentional cross-port divergences
2
+
3
+ Scope: the tolerant `recover` pipeline (`src/recover/`). The Java engine
4
+ (`server/java/render/.../recover/`) is the cross-port reference; `fixtures/recover-conformance/`
5
+ is the oracle. All 10 corpus cases pass.
6
+
7
+ ## Additive capability (TS + C#, beyond Java/Kotlin)
8
+
9
+ - **Nested-object recover is implemented.** A `FieldSpec` with a non-null `nested` schema
10
+ (built via the `object(...)` factory) is descended into and its sub-fields classified. The
11
+ Java/Kotlin ports defer this (their codegen emits a scalar-STRING placeholder). The C# port
12
+ also carries the OBJECT branch, so TS and C# agree. This is **dormant** under both the
13
+ conformance corpus (no nested fixture; the runner's schema parser never sets `nested`) and the
14
+ FR-010 codegen (Phase 3 emits the scalar placeholder for cross-port parity), so it changes no
15
+ shared-corpus result. If a future shared fixture adds a nested case, Java/Kotlin catch up.
16
+
17
+ ## Intentional, documented divergence (NOT a bug)
18
+
19
+ The cross-port contract pins *classification + canonical value* (numbers within ±1e-9), not
20
+ byte-identical native parsing.
21
+
22
+ - **Java-style numeric suffixes / hex-float literals.** Java's `Double.parseDouble` accepts
23
+ `"42d"` / `"42f"` and hex-float forms (→ RECOVERED); TS uses `Number(...)` + `Number.isFinite`,
24
+ which rejects them → **MALFORMED** (same accepted divergence the C# port records). The
25
+ load-bearing behavior — finite-only acceptance, `NaN`/`±Infinity` → MALFORMED — is identical.
26
+
27
+ - **JS-only radix-prefixed literals are GUARDED for parity.** `Number("0x10")` is `16` in JS, but
28
+ Java/C# reject `0x..`/`0b..`/`0o..` → MALFORMED. `parseFiniteNumber` rejects these prefixes
29
+ explicitly so TS matches Java/C# (→ MALFORMED) rather than over-accepting. (Not a divergence —
30
+ noted here because the guard exists precisely to prevent one.)
31
+
32
+ ## Bounded deferral (parity with all ports)
33
+
34
+ - Array-of-enum is not specialized (a scalar array recovers via `asStringList`).
35
+ - `asInt`/`asLong` both return `number | null` (JS has one number type) and truncate toward zero.