@clossys/writer 0.3.2
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.
- package/CHANGELOG.md +196 -0
- package/LICENSE +21 -0
- package/README.md +593 -0
- package/dist/addressability.d.ts +245 -0
- package/dist/addressability.d.ts.map +1 -0
- package/dist/addressability.js +567 -0
- package/dist/addressability.js.map +1 -0
- package/dist/checker.d.ts +159 -0
- package/dist/checker.d.ts.map +1 -0
- package/dist/checker.js +177 -0
- package/dist/checker.js.map +1 -0
- package/dist/cli.d.ts +149 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1049 -0
- package/dist/cli.js.map +1 -0
- package/dist/copy-gate.d.ts +107 -0
- package/dist/copy-gate.d.ts.map +1 -0
- package/dist/copy-gate.js +132 -0
- package/dist/copy-gate.js.map +1 -0
- package/dist/fingerprint.d.ts +61 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +64 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/locale-coverage.d.ts +227 -0
- package/dist/locale-coverage.d.ts.map +1 -0
- package/dist/locale-coverage.js +414 -0
- package/dist/locale-coverage.js.map +1 -0
- package/dist/passage.d.ts +294 -0
- package/dist/passage.d.ts.map +1 -0
- package/dist/passage.js +463 -0
- package/dist/passage.js.map +1 -0
- package/dist/path-exclusions.d.ts +114 -0
- package/dist/path-exclusions.d.ts.map +1 -0
- package/dist/path-exclusions.js +213 -0
- package/dist/path-exclusions.js.map +1 -0
- package/dist/registry.d.ts +61 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +64 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolve.d.ts +33 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +133 -0
- package/dist/resolve.js.map +1 -0
- package/dist/scan.d.ts +457 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +1698 -0
- package/dist/scan.js.map +1 -0
- package/dist/schema.d.ts +81 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +482 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +268 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +40 -0
- package/dist/types.js.map +1 -0
- package/dist/voice/checker.d.ts +186 -0
- package/dist/voice/checker.d.ts.map +1 -0
- package/dist/voice/checker.js +483 -0
- package/dist/voice/checker.js.map +1 -0
- package/dist/voice/derivation-coverage.d.ts +168 -0
- package/dist/voice/derivation-coverage.d.ts.map +1 -0
- package/dist/voice/derivation-coverage.js +181 -0
- package/dist/voice/derivation-coverage.js.map +1 -0
- package/dist/voice/fields.d.ts +107 -0
- package/dist/voice/fields.d.ts.map +1 -0
- package/dist/voice/fields.js +144 -0
- package/dist/voice/fields.js.map +1 -0
- package/dist/voice/index.d.ts +19 -0
- package/dist/voice/index.d.ts.map +1 -0
- package/dist/voice/index.js +24 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/internal/parse-template.d.ts +27 -0
- package/dist/voice/internal/parse-template.d.ts.map +1 -0
- package/dist/voice/internal/parse-template.js +111 -0
- package/dist/voice/internal/parse-template.js.map +1 -0
- package/dist/voice/internal/pattern-safety.d.ts +111 -0
- package/dist/voice/internal/pattern-safety.d.ts.map +1 -0
- package/dist/voice/internal/pattern-safety.js +278 -0
- package/dist/voice/internal/pattern-safety.js.map +1 -0
- package/dist/voice/schema.d.ts +65 -0
- package/dist/voice/schema.d.ts.map +1 -0
- package/dist/voice/schema.js +535 -0
- package/dist/voice/schema.js.map +1 -0
- package/dist/voice/types.d.ts +282 -0
- package/dist/voice/types.d.ts.map +1 -0
- package/dist/voice/types.js +37 -0
- package/dist/voice/types.js.map +1 -0
- package/package.json +72 -0
- package/src/addressability.ts +664 -0
- package/src/checker.ts +293 -0
- package/src/cli.ts +1166 -0
- package/src/copy-gate.ts +197 -0
- package/src/fingerprint.ts +66 -0
- package/src/index.ts +163 -0
- package/src/locale-coverage.ts +512 -0
- package/src/passage.ts +644 -0
- package/src/path-exclusions.ts +265 -0
- package/src/registry.ts +103 -0
- package/src/resolve.ts +172 -0
- package/src/scan.ts +1959 -0
- package/src/schema.ts +528 -0
- package/src/types.ts +301 -0
- package/src/voice/checker.ts +590 -0
- package/src/voice/derivation-coverage.ts +217 -0
- package/src/voice/fields.ts +160 -0
- package/src/voice/index.ts +55 -0
- package/src/voice/internal/parse-template.ts +114 -0
- package/src/voice/internal/pattern-safety.ts +336 -0
- package/src/voice/schema.ts +585 -0
- package/src/voice/types.ts +329 -0
- package/templates/voice-record.template.jsonc +135 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain TypeScript types for @clossys/writer's contract: one
|
|
3
|
+
* addressable, reviewable piece of copy (`CopyEntry`) and the record that
|
|
4
|
+
* groups a consumer's whole set of them (`CopyRecord`). Pure data — no
|
|
5
|
+
* validation logic lives here (see `schema.ts`) and no I/O (see
|
|
6
|
+
* `registry.ts`).
|
|
7
|
+
*
|
|
8
|
+
* This is the FROZEN CONTRACT this package and its sibling scanner/gate
|
|
9
|
+
* half (built in parallel, against the exact same shape) were both
|
|
10
|
+
* compiled against. Do not add, remove, rename, or reshape a field here
|
|
11
|
+
* without updating both halves in lockstep — a silent drift here breaks
|
|
12
|
+
* the other half's compile, not this package's own tests.
|
|
13
|
+
*
|
|
14
|
+
* No runtime schema library. That follows this repository's own
|
|
15
|
+
* precedent, restated once more because it matters more here than
|
|
16
|
+
* anywhere else: `@example/catalog`, `@example/policy`,
|
|
17
|
+
* `@example/ui/tokens`, and `@clossys/writer/voice` all ship zero
|
|
18
|
+
* runtime dependencies; only `@example/ui` carries any, and only
|
|
19
|
+
* because it wraps React primitives it genuinely cannot hand-roll. This
|
|
20
|
+
* package's entire job is dependency-free data shape validation — the
|
|
21
|
+
* same job `@example/strategy`'s `validation.ts` and
|
|
22
|
+
* `@clossys/writer/voice`'s `schema.ts` already do, in plain type
|
|
23
|
+
* guards, for their own shapes. `schema.ts` follows that pattern closely
|
|
24
|
+
* rather than pulling in a schema library (and its own major-version
|
|
25
|
+
* churn, a real cost for a *public* package's consumers) for what one
|
|
26
|
+
* file of type guards already covers.
|
|
27
|
+
*
|
|
28
|
+
* This file ships no example content of anyone's real copy. Every field
|
|
29
|
+
* here is either required-and-generic or a structural placeholder —
|
|
30
|
+
* filling in an actual product's actual sentences is a consumer's job.
|
|
31
|
+
* See "The single most important constraint" in the README: this package
|
|
32
|
+
* is to `@clossys/writer/voice` what `@example/ui` is to
|
|
33
|
+
* `@example/ui/tokens` — a vocabulary layer over a contract layer,
|
|
34
|
+
* never a source of real words. If a doc comment, a test fixture, or a
|
|
35
|
+
* README example in this package ever reads like something a product
|
|
36
|
+
* would actually show a user, that is a bug in this package, not a
|
|
37
|
+
* feature of it.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// CopyEntryId — the stable address of one entry
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A stable address for one `CopyEntry`, unique within its `CopyRecord`.
|
|
46
|
+
* Dot-separated, lowercase, kebab-case within each segment — e.g.
|
|
47
|
+
* `"pagination.no-results"`, `"onboarding.step-2.title"`. At least one
|
|
48
|
+
* dot is required: a bare, unnamespaced id like `"title"` is exactly the
|
|
49
|
+
* kind of id that collides the moment a second feature also needs a
|
|
50
|
+
* `"title"` entry, which is the whole reason this package asks for a
|
|
51
|
+
* namespaced, dot-separated address rather than an arbitrary free-text
|
|
52
|
+
* string. See `schema.ts`'s `COPY_ENTRY_ID_RE` for the exact pattern this
|
|
53
|
+
* shape is validated against.
|
|
54
|
+
*
|
|
55
|
+
* A plain `string` alias, not a branded/nominal type: this package has no
|
|
56
|
+
* runtime mechanism to keep a caller from constructing one by hand
|
|
57
|
+
* outside `validateCopyRecordShape`/`parseCopyRecord`, so pretending
|
|
58
|
+
* otherwise at the type level would be a false promise. The alias exists
|
|
59
|
+
* purely to make every signature below say what kind of string it means.
|
|
60
|
+
*/
|
|
61
|
+
export type CopyEntryId = string;
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// CopyRef and resolvable registry metadata
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A locale tag supplied by the consumer (normally a BCP 47 tag such as
|
|
69
|
+
* `en` or `fr-CA`). It remains a plain string because locale support is a
|
|
70
|
+
* consumer policy: copy must not bundle a second internationalisation stack.
|
|
71
|
+
*/
|
|
72
|
+
export type CopyLocale = string;
|
|
73
|
+
|
|
74
|
+
/** Values permitted when a `CopyRef` fills a registered `{placeholder}`. */
|
|
75
|
+
export type CopyValue = string | number | boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The stable, serialisable way another package asks copy for audience-facing
|
|
79
|
+
* text. Surface documents carry this value instead of a prose literal.
|
|
80
|
+
*
|
|
81
|
+
* `locale` is a request, not a fallback algorithm. The resolver owns the
|
|
82
|
+
* fallback policy and fails closed when no compatible registry is available.
|
|
83
|
+
*/
|
|
84
|
+
export interface CopyRef {
|
|
85
|
+
id: CopyEntryId;
|
|
86
|
+
locale?: CopyLocale;
|
|
87
|
+
values?: Readonly<Record<string, CopyValue>>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Lifecycle state of a registry entry. Only approved entries can resolve. */
|
|
91
|
+
export type CopyEntryStatus = "draft" | "approved" | "retired";
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Consumer-supplied provenance for a resolvable copy registry. `reference`
|
|
95
|
+
* is deliberately opaque: it may be a revision-control object, CMS revision,
|
|
96
|
+
* or editorial record, without Foundry importing that system.
|
|
97
|
+
*/
|
|
98
|
+
export interface CopySource {
|
|
99
|
+
kind: "consumer" | "generated" | "imported";
|
|
100
|
+
reference: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// CopyEntry — one addressable, reviewable piece of copy
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* One piece of copy a consumer's product shows a user, made addressable,
|
|
109
|
+
* reviewable, and checkable. This package never populates `text` with
|
|
110
|
+
* anything real — every `CopyEntry` a consumer's own repository registers
|
|
111
|
+
* is that repository's own words, the same way a `VoiceRecord`'s
|
|
112
|
+
* `glossary` and `claims` are never this package's words either (see
|
|
113
|
+
* `@clossys/writer/voice`'s README, "The single most important
|
|
114
|
+
* constraint").
|
|
115
|
+
*/
|
|
116
|
+
export interface CopyEntry {
|
|
117
|
+
/** Stable address. Unique within the `CopyRecord` it belongs to — see `CopyEntryId`. */
|
|
118
|
+
id: CopyEntryId;
|
|
119
|
+
/** The copy itself — a consumer's own words, never this package's. */
|
|
120
|
+
text: string;
|
|
121
|
+
/**
|
|
122
|
+
* Where this copy appears — a screen name, a component, a route, a
|
|
123
|
+
* notification type. Required, and deliberately not optional: an entry
|
|
124
|
+
* with no stated location cannot be found by a human reviewer skimming
|
|
125
|
+
* a report, which makes it unreviewable in practice even though it is
|
|
126
|
+
* structurally present. "Unlocatable copy is not reviewable copy" is
|
|
127
|
+
* the one-line version of why this field exists at all.
|
|
128
|
+
*/
|
|
129
|
+
context: string;
|
|
130
|
+
/**
|
|
131
|
+
* Named interpolations `text` must contain, e.g. `["start", "end",
|
|
132
|
+
* "total"]` for copy like `"Showing {start}–{end} of {total}
|
|
133
|
+
* results"`. Each name in this list is checked against `text` using the
|
|
134
|
+
* literal `{name}` convention — the same brace-delimited placeholder
|
|
135
|
+
* syntax already familiar from ICU MessageFormat and most i18n
|
|
136
|
+
* libraries — by `schema.ts`'s shape validation: a name declared here
|
|
137
|
+
* but not found as `{name}` in `text` is treated as a real bug (the
|
|
138
|
+
* copy was edited and the interpolation was dropped, or the
|
|
139
|
+
* placeholder list drifted from the actual sentence), not decoration.
|
|
140
|
+
* See `schema.ts` for exactly how this is checked, and this package's
|
|
141
|
+
* README for why this is presence-detection over an already-authored
|
|
142
|
+
* string, not a formatting engine: `Intl.PluralRules`,
|
|
143
|
+
* `Intl.ListFormat`, `Intl.RelativeTimeFormat`, and
|
|
144
|
+
* `Intl.NumberFormat.prototype.formatRange` already exist natively in
|
|
145
|
+
* Node 20+ for the actual formatting work, and this package does not
|
|
146
|
+
* re-wrap any of them.
|
|
147
|
+
*
|
|
148
|
+
* Optional — an entry with no interpolations at all (most copy) simply
|
|
149
|
+
* omits this field; `parseCopyRecord` defaults it to `[]`.
|
|
150
|
+
*/
|
|
151
|
+
placeholders?: string[];
|
|
152
|
+
/**
|
|
153
|
+
* A plain string into a consumer's own facts registry — never a typed
|
|
154
|
+
* import, never a runtime dependency on `@example/strategy`.
|
|
155
|
+
* This mirrors exactly how `@clossys/writer/voice`'s `Claim.factRef`
|
|
156
|
+
* refers to a `strategy` package's facts without importing it: the
|
|
157
|
+
* coupling is an opaque string convention, not a code import, so this
|
|
158
|
+
* package works today, before `strategy` (or a consumer's own facts
|
|
159
|
+
* registry) exists at all, and keeps working unchanged after it does.
|
|
160
|
+
* Resolving a `factRef` against a real facts registry is a later gate's
|
|
161
|
+
* job, one with visibility into both this package's entries and that
|
|
162
|
+
* registry — deliberately not this package's, and not
|
|
163
|
+
* `@clossys/writer/voice`'s either. See the README, "The `factRef`
|
|
164
|
+
* seam", for the fuller argument (borrowed near-verbatim from that
|
|
165
|
+
* package's own README section of the same name, because it is the
|
|
166
|
+
* same seam, one layer over).
|
|
167
|
+
*/
|
|
168
|
+
factRef?: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Content-derived provenance for one target-locale translation. Present only
|
|
173
|
+
* on a target-locale `CopyRegistryEntry` — a source-locale entry has nothing
|
|
174
|
+
* to be "translated from" and never carries this field. See
|
|
175
|
+
* `computeCopyFingerprint` below and `locale-coverage.ts`'s doc comment for
|
|
176
|
+
* why this is a fingerprint of the source text, never a human-maintained
|
|
177
|
+
* revision number: a hand-bumped counter requires someone to remember to
|
|
178
|
+
* bump it every time source copy changes, and nothing enforces that
|
|
179
|
+
* discipline, so it silently drifts out of sync. A content hash cannot drift
|
|
180
|
+
* the same way — it changes if and only if the text it was computed from
|
|
181
|
+
* changed.
|
|
182
|
+
*/
|
|
183
|
+
export interface CopyTranslationProvenance {
|
|
184
|
+
/**
|
|
185
|
+
* A fingerprint of the SOURCE entry's `text`, computed with
|
|
186
|
+
* `fingerprintAlgorithm`, at the moment this translation was produced.
|
|
187
|
+
* Compared against a fresh `computeCopyFingerprint(sourceEntry.text)` by
|
|
188
|
+
* `checkLocaleCoverage` to detect staleness — see that file's doc
|
|
189
|
+
* comment for the full comparison.
|
|
190
|
+
*/
|
|
191
|
+
sourceFingerprint: string;
|
|
192
|
+
/**
|
|
193
|
+
* The algorithm `sourceFingerprint` was computed with, e.g. `"sha256"` —
|
|
194
|
+
* matches `COPY_FINGERPRINT_ALGORITHM`, the constant `computeCopyFingerprint`
|
|
195
|
+
* (`fingerprint.ts`) is documented to use. Declared explicitly, per entry,
|
|
196
|
+
* so a future algorithm change is a detectable mismatch rather than a
|
|
197
|
+
* silent comparison between two fingerprints computed two different ways.
|
|
198
|
+
*/
|
|
199
|
+
fingerprintAlgorithm: string;
|
|
200
|
+
/** ISO-8601 timestamp of when this translation was produced. */
|
|
201
|
+
translatedAt: string;
|
|
202
|
+
/**
|
|
203
|
+
* Opaque translator/vendor-job identifier — never a personal name. This
|
|
204
|
+
* package ships no real identity data, per its own "no real product
|
|
205
|
+
* sentences" constraint (see this file's top-of-file doc comment).
|
|
206
|
+
*/
|
|
207
|
+
translatedBy?: string;
|
|
208
|
+
/** ISO-8601 timestamp of when this translation was last reviewed, if it has been. */
|
|
209
|
+
reviewedAt?: string;
|
|
210
|
+
/** Opaque reviewer identifier, mirroring `translatedBy` — never a personal name. */
|
|
211
|
+
reviewedBy?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** A `CopyEntry` that is eligible to participate in a rendered registry. */
|
|
215
|
+
export interface CopyRegistryEntry extends CopyEntry {
|
|
216
|
+
status: CopyEntryStatus;
|
|
217
|
+
/**
|
|
218
|
+
* Content-derived translation provenance, present only on a target-locale
|
|
219
|
+
* entry that has recorded it. Optional: absent on every source-locale
|
|
220
|
+
* entry, and absent on any target-locale entry authored before this field
|
|
221
|
+
* existed or by a host that has not adopted it yet — an existing registry
|
|
222
|
+
* does not become invalid overnight. See `locale-coverage.ts` for how
|
|
223
|
+
* presence/absence of this field changes what `checkLocaleCoverage` can
|
|
224
|
+
* report: an entry with `translation` gets a real stale/not-stale
|
|
225
|
+
* verdict; an entry without it gets `"locale-coverage:provenance-missing"`
|
|
226
|
+
* instead, which is a DIFFERENT outcome from "checked and not stale" —
|
|
227
|
+
* never collapsed into one signal.
|
|
228
|
+
*/
|
|
229
|
+
translation?: CopyTranslationProvenance;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// CopyRecord — one consumer's whole registered set of entries
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* One consumer's complete, registered set of copy entries. This is the
|
|
238
|
+
* "brand.css" of this package — foundry ships the schema this conforms
|
|
239
|
+
* to and the machinery to check it, never a real instance of it. See the
|
|
240
|
+
* README, "The single most important constraint".
|
|
241
|
+
*/
|
|
242
|
+
export interface CopyRecord {
|
|
243
|
+
id: string;
|
|
244
|
+
entries: CopyEntry[];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* A versioned, locale-specific `CopyRecord` that can safely resolve
|
|
249
|
+
* `CopyRef`s for an audience-facing surface. The older `CopyRecord` remains
|
|
250
|
+
* the deliberately minimal scanner/checker input; resolution requires this
|
|
251
|
+
* stronger contract so a manifest never pretends unversioned text is traced.
|
|
252
|
+
*/
|
|
253
|
+
export interface CopyRegistry extends CopyRecord {
|
|
254
|
+
locale: CopyLocale;
|
|
255
|
+
revision: string;
|
|
256
|
+
source: CopySource;
|
|
257
|
+
entries: CopyRegistryEntry[];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** A successful, fully traced resolution of one copy reference. */
|
|
261
|
+
export interface CopyResolution {
|
|
262
|
+
ref: CopyRef;
|
|
263
|
+
text: string;
|
|
264
|
+
recordId: string;
|
|
265
|
+
revision: string;
|
|
266
|
+
locale: CopyLocale;
|
|
267
|
+
source: CopySource;
|
|
268
|
+
entryId: CopyEntryId;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Resolver shape for callers that need rendered audience text. */
|
|
272
|
+
export type CopyResolver = (ref: CopyRef) => CopyResolution | undefined;
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// CopyFinding — shared shape, mirroring @clossys/writer/voice's own `VoiceFinding`
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* One thing `schema.ts`'s shape validation found wrong with a candidate
|
|
280
|
+
* `CopyRecord` or `CopyEntry`. Deliberately the same shape as
|
|
281
|
+
* `@clossys/writer/voice`'s `VoiceFinding` (itself deliberately the same
|
|
282
|
+
* shape as `@example/policy`'s `Finding`) — `rule` / `severity` /
|
|
283
|
+
* `message` / optional `path` — so a caller already handling one kind of
|
|
284
|
+
* finding in this repository's ecosystem does not need a second mental
|
|
285
|
+
* model for this package's. Defined fresh here, not imported: this
|
|
286
|
+
* file's own zero-runtime-dependency requirement applies to
|
|
287
|
+
* `@clossys/writer/voice` too — `types.ts` and `schema.ts` do not import
|
|
288
|
+
* from it. (`checker.ts` does depend on `@clossys/writer/voice`, for
|
|
289
|
+
* `checkCopy` itself — see that file's own doc comment for why that
|
|
290
|
+
* dependency is confined there.)
|
|
291
|
+
*/
|
|
292
|
+
export interface CopyFinding {
|
|
293
|
+
/** Stable identifier for the rule that produced this finding, e.g. `"id-shape"`, `"placeholder-missing-from-text"`. */
|
|
294
|
+
rule: string;
|
|
295
|
+
/** Always `"error"` from `schema.ts` — a malformed `CopyRecord` is never merely a warning. */
|
|
296
|
+
severity: "error" | "warning";
|
|
297
|
+
/** Human-readable description of the problem. */
|
|
298
|
+
message: string;
|
|
299
|
+
/** The specific field, entry index, or placeholder name this finding is about, when there is a single clear one. */
|
|
300
|
+
path?: string;
|
|
301
|
+
}
|