@osovv/vv-opencode 0.12.0 → 0.14.0

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 (46) hide show
  1. package/README.md +38 -34
  2. package/dist/commands/agent.js +23 -87
  3. package/dist/commands/agent.js.map +1 -1
  4. package/dist/commands/config-validate.d.ts +4 -24
  5. package/dist/commands/config-validate.js +52 -242
  6. package/dist/commands/config-validate.js.map +1 -1
  7. package/dist/commands/doctor.js +10 -9
  8. package/dist/commands/doctor.js.map +1 -1
  9. package/dist/commands/guardian.js +6 -13
  10. package/dist/commands/guardian.js.map +1 -1
  11. package/dist/commands/init.js +11 -15
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/install.d.ts +1 -16
  14. package/dist/commands/install.js +8 -44
  15. package/dist/commands/install.js.map +1 -1
  16. package/dist/commands/status.js +9 -9
  17. package/dist/commands/status.js.map +1 -1
  18. package/dist/commands/sync.d.ts +1 -1
  19. package/dist/commands/sync.js +8 -10
  20. package/dist/commands/sync.js.map +1 -1
  21. package/dist/lib/opencode.d.ts +19 -52
  22. package/dist/lib/opencode.js +86 -336
  23. package/dist/lib/opencode.js.map +1 -1
  24. package/dist/lib/package.d.ts +2 -0
  25. package/dist/lib/package.js +14 -10
  26. package/dist/lib/package.js.map +1 -1
  27. package/dist/lib/vvoc-config.d.ts +209 -0
  28. package/dist/lib/vvoc-config.js +642 -0
  29. package/dist/lib/vvoc-config.js.map +1 -0
  30. package/dist/lib/vvoc-paths.d.ts +2 -0
  31. package/dist/lib/vvoc-paths.js +9 -3
  32. package/dist/lib/vvoc-paths.js.map +1 -1
  33. package/dist/plugins/guardian/index.js +24 -206
  34. package/dist/plugins/guardian/index.js.map +1 -1
  35. package/dist/plugins/memory/index.js +1 -1
  36. package/dist/plugins/memory/index.js.map +1 -1
  37. package/dist/plugins/memory-store.d.ts +2 -9
  38. package/dist/plugins/memory-store.js +20 -127
  39. package/dist/plugins/memory-store.js.map +1 -1
  40. package/dist/plugins/secrets-redaction/config.d.ts +3 -20
  41. package/dist/plugins/secrets-redaction/config.js +33 -134
  42. package/dist/plugins/secrets-redaction/config.js.map +1 -1
  43. package/dist/plugins/secrets-redaction/engine.js +20 -60
  44. package/dist/plugins/secrets-redaction/engine.js.map +1 -1
  45. package/package.json +3 -1
  46. package/schemas/vvoc/v1.json +94 -0
@@ -0,0 +1,642 @@
1
+ // FILE: src/lib/vvoc-config.ts
2
+ // VERSION: 1.1.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Define the canonical vvoc.json document shape, normalization rules, JSON Schema, and validation helpers.
5
+ // SCOPE: Versioned schema constants, default section generation, strict and lenient config parsing, section rendering/parsing helpers, and schema-based validation errors for vvoc-owned configuration.
6
+ // DEPENDS: [ajv/dist/2020, src/lib/package.ts]
7
+ // LINKS: [M-CLI-CONFIG, M-CLI-CONFIG-VALIDATE, M-PLUGIN-GUARDIAN, M-PLUGIN-MEMORY-STORE, M-PLUGIN-SECRETS-REDACTION-INTERNAL-CONFIG]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // VVOC_CONFIG_VERSION - Canonical vvoc config document version.
14
+ // VVOC_CONFIG_SCHEMA_URL - Hosted JSON Schema URL for the canonical vvoc config.
15
+ // VVOC_CONFIG_SCHEMA - JSON Schema document for vvoc.json.
16
+ // GuardianConfig - Fully seeded guardian section shape.
17
+ // GuardianConfigOverrides - Partial guardian section override shape.
18
+ // MemoryConfig - Fully seeded memory section shape.
19
+ // MemoryConfigOverrides - Partial memory section override shape.
20
+ // SecretsRedactionConfig - Fully seeded secrets-redaction section shape.
21
+ // VvocConfig - Fully seeded canonical vvoc config document shape.
22
+ // createGuardianConfig - Builds a fully seeded guardian section from optional overrides.
23
+ // createMemoryConfig - Builds a fully seeded memory section from optional overrides.
24
+ // createDefaultSecretsRedactionConfig - Builds the seeded secrets-redaction section.
25
+ // createDefaultVvocConfig - Builds the fully seeded canonical vvoc config document.
26
+ // parseGuardianConfigText - Strictly parses a guardian section JSON snippet.
27
+ // renderGuardianConfig - Renders a guardian section JSON snippet.
28
+ // parseMemoryConfigText - Strictly parses a memory section JSON snippet.
29
+ // renderMemoryConfig - Renders a memory section JSON snippet.
30
+ // parseVvocConfigText - Strictly parses the canonical vvoc config document.
31
+ // loadLenientVvocConfigText - Parses vvoc.json leniently for runtime fallback with warnings.
32
+ // renderVvocConfig - Renders canonical vvoc.json.
33
+ // validateVvocConfigDocument - Validates a parsed vvoc config object against the JSON Schema.
34
+ // END_MODULE_MAP
35
+ //
36
+ // START_CHANGE_SUMMARY
37
+ // LAST_CHANGE: [v1.1.0 - Switched canonical vvoc schema URLs to package-version-pinned jsDelivr URLs.]
38
+ // END_CHANGE_SUMMARY
39
+ import { Ajv2020 } from "ajv/dist/2020.js";
40
+ import { PACKAGE_NAME, PACKAGE_VERSION } from "./package.js";
41
+ const DEFAULT_GUARDIAN_TIMEOUT_MS = 90_000;
42
+ const DEFAULT_GUARDIAN_APPROVAL_RISK_THRESHOLD = 80;
43
+ const DEFAULT_MEMORY_SEARCH_LIMIT = 8;
44
+ const DEFAULT_SECRETS_REDACTION_TTL_MS = 3_600_000;
45
+ const DEFAULT_SECRETS_REDACTION_MAX_MAPPINGS = 10_000;
46
+ export const VVOC_CONFIG_VERSION = 1;
47
+ export const VVOC_CONFIG_SCHEMA_URL = `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@${PACKAGE_VERSION}/schemas/vvoc/v1.json`;
48
+ const JSON_SCHEMA_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
49
+ const BUILTIN_SECRETS_REDACTION_PATTERNS = [
50
+ "email",
51
+ "uuid",
52
+ "ipv4",
53
+ "mac",
54
+ "openai_key",
55
+ "anthropic_key",
56
+ "github_token",
57
+ "aws_access_key",
58
+ "stripe_key",
59
+ "bearer_token",
60
+ "bearer_dot",
61
+ "syn_key",
62
+ "hex_token",
63
+ ];
64
+ export const VVOC_CONFIG_SCHEMA = {
65
+ $schema: JSON_SCHEMA_DRAFT_2020_12,
66
+ $id: VVOC_CONFIG_SCHEMA_URL,
67
+ title: "vvoc config",
68
+ description: "Canonical vvoc configuration document.",
69
+ type: "object",
70
+ additionalProperties: false,
71
+ required: ["$schema", "version", "guardian", "memory", "secretsRedaction"],
72
+ properties: {
73
+ $schema: {
74
+ type: "string",
75
+ minLength: 1,
76
+ description: "Hosted JSON Schema URL for vvoc.json.",
77
+ },
78
+ version: {
79
+ type: "integer",
80
+ const: VVOC_CONFIG_VERSION,
81
+ },
82
+ guardian: {
83
+ type: "object",
84
+ additionalProperties: false,
85
+ required: ["timeoutMs", "approvalRiskThreshold", "reviewToastDurationMs"],
86
+ properties: {
87
+ model: { type: "string", minLength: 1 },
88
+ variant: { type: "string", minLength: 1 },
89
+ timeoutMs: { type: "integer", minimum: 1 },
90
+ approvalRiskThreshold: { type: "integer", minimum: 0, maximum: 100 },
91
+ reviewToastDurationMs: { type: "integer", minimum: 1 },
92
+ },
93
+ },
94
+ memory: {
95
+ type: "object",
96
+ additionalProperties: false,
97
+ required: ["enabled", "defaultSearchLimit"],
98
+ properties: {
99
+ enabled: { type: "boolean" },
100
+ defaultSearchLimit: { type: "integer", minimum: 1 },
101
+ reviewerModel: { type: "string", minLength: 1 },
102
+ reviewerVariant: { type: "string", minLength: 1 },
103
+ },
104
+ },
105
+ secretsRedaction: {
106
+ type: "object",
107
+ additionalProperties: false,
108
+ required: ["enabled", "secret", "ttlMs", "maxMappings", "patterns", "debug"],
109
+ properties: {
110
+ enabled: { type: "boolean" },
111
+ secret: { type: "string", minLength: 1 },
112
+ ttlMs: { type: "integer", minimum: 0 },
113
+ maxMappings: { type: "integer", minimum: 1 },
114
+ debug: { type: "boolean" },
115
+ patterns: {
116
+ type: "object",
117
+ additionalProperties: false,
118
+ required: ["keywords", "regex", "builtin", "exclude"],
119
+ properties: {
120
+ keywords: {
121
+ type: "array",
122
+ items: {
123
+ type: "object",
124
+ additionalProperties: false,
125
+ required: ["value"],
126
+ properties: {
127
+ value: { type: "string", minLength: 1 },
128
+ category: { type: "string", minLength: 1 },
129
+ },
130
+ },
131
+ },
132
+ regex: {
133
+ type: "array",
134
+ items: {
135
+ type: "object",
136
+ additionalProperties: false,
137
+ required: ["pattern", "category"],
138
+ properties: {
139
+ pattern: { type: "string", minLength: 1 },
140
+ category: { type: "string", minLength: 1 },
141
+ },
142
+ },
143
+ },
144
+ builtin: {
145
+ type: "array",
146
+ items: { type: "string", minLength: 1 },
147
+ },
148
+ exclude: {
149
+ type: "array",
150
+ items: { type: "string", minLength: 1 },
151
+ },
152
+ },
153
+ },
154
+ },
155
+ },
156
+ },
157
+ };
158
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
159
+ const validateWithSchema = ajv.compile(VVOC_CONFIG_SCHEMA);
160
+ // START_BLOCK_DEFAULT_CONFIG_BUILDERS
161
+ export function createGuardianConfig(overrides = {}) {
162
+ const timeoutMs = overrides.timeoutMs ?? DEFAULT_GUARDIAN_TIMEOUT_MS;
163
+ return compactObject({
164
+ model: normalizeOptionalString(overrides.model),
165
+ variant: normalizeOptionalString(overrides.variant),
166
+ timeoutMs,
167
+ approvalRiskThreshold: overrides.approvalRiskThreshold ?? DEFAULT_GUARDIAN_APPROVAL_RISK_THRESHOLD,
168
+ reviewToastDurationMs: overrides.reviewToastDurationMs ?? timeoutMs,
169
+ });
170
+ }
171
+ export function createMemoryConfig(overrides = {}) {
172
+ return compactObject({
173
+ enabled: overrides.enabled ?? true,
174
+ defaultSearchLimit: overrides.defaultSearchLimit ?? DEFAULT_MEMORY_SEARCH_LIMIT,
175
+ reviewerModel: normalizeOptionalString(overrides.reviewerModel),
176
+ reviewerVariant: normalizeOptionalString(overrides.reviewerVariant),
177
+ });
178
+ }
179
+ export function createDefaultSecretsRedactionConfig() {
180
+ return {
181
+ enabled: true,
182
+ secret: "${VVOC_SECRET}",
183
+ ttlMs: DEFAULT_SECRETS_REDACTION_TTL_MS,
184
+ maxMappings: DEFAULT_SECRETS_REDACTION_MAX_MAPPINGS,
185
+ patterns: {
186
+ keywords: [],
187
+ regex: [],
188
+ builtin: [...BUILTIN_SECRETS_REDACTION_PATTERNS],
189
+ exclude: [],
190
+ },
191
+ debug: false,
192
+ };
193
+ }
194
+ export function createDefaultVvocConfig() {
195
+ return {
196
+ $schema: VVOC_CONFIG_SCHEMA_URL,
197
+ version: VVOC_CONFIG_VERSION,
198
+ guardian: createGuardianConfig(),
199
+ memory: createMemoryConfig(),
200
+ secretsRedaction: createDefaultSecretsRedactionConfig(),
201
+ };
202
+ }
203
+ // END_BLOCK_DEFAULT_CONFIG_BUILDERS
204
+ // START_BLOCK_SECTION_PARSE_AND_RENDER
205
+ export function parseGuardianConfigText(text, label) {
206
+ const value = parseStrictJson(text, label);
207
+ if (!isPlainObject(value)) {
208
+ throw new Error(`${label}: expected a top-level object`);
209
+ }
210
+ assertAllowedKeys(value, ["model", "variant", "timeoutMs", "approvalRiskThreshold", "reviewToastDurationMs"], label);
211
+ const overrides = {};
212
+ if (Object.hasOwn(value, "model")) {
213
+ overrides.model = readNonEmptyString(value.model, `${label}: model`);
214
+ }
215
+ if (Object.hasOwn(value, "variant")) {
216
+ overrides.variant = readNonEmptyString(value.variant, `${label}: variant`);
217
+ }
218
+ if (Object.hasOwn(value, "timeoutMs")) {
219
+ overrides.timeoutMs = readPositiveInteger(value.timeoutMs, `${label}: timeoutMs`);
220
+ }
221
+ if (Object.hasOwn(value, "approvalRiskThreshold")) {
222
+ overrides.approvalRiskThreshold = readThreshold(value.approvalRiskThreshold, `${label}: approvalRiskThreshold`);
223
+ }
224
+ if (Object.hasOwn(value, "reviewToastDurationMs")) {
225
+ overrides.reviewToastDurationMs = readPositiveInteger(value.reviewToastDurationMs, `${label}: reviewToastDurationMs`);
226
+ }
227
+ return overrides;
228
+ }
229
+ export function renderGuardianConfig(overrides = {}) {
230
+ return renderJson(createGuardianConfig(overrides));
231
+ }
232
+ export function parseMemoryConfigText(text, label) {
233
+ const value = parseStrictJson(text, label);
234
+ if (!isPlainObject(value)) {
235
+ throw new Error(`${label}: expected a top-level object`);
236
+ }
237
+ assertAllowedKeys(value, ["enabled", "defaultSearchLimit", "reviewerModel", "reviewerVariant"], label);
238
+ const overrides = {};
239
+ if (Object.hasOwn(value, "enabled")) {
240
+ if (typeof value.enabled !== "boolean") {
241
+ throw new Error(`${label}: enabled: expected a boolean`);
242
+ }
243
+ overrides.enabled = value.enabled;
244
+ }
245
+ if (Object.hasOwn(value, "defaultSearchLimit")) {
246
+ overrides.defaultSearchLimit = readPositiveInteger(value.defaultSearchLimit, `${label}: defaultSearchLimit`);
247
+ }
248
+ if (Object.hasOwn(value, "reviewerModel")) {
249
+ overrides.reviewerModel = readNonEmptyString(value.reviewerModel, `${label}: reviewerModel`);
250
+ }
251
+ if (Object.hasOwn(value, "reviewerVariant")) {
252
+ overrides.reviewerVariant = readNonEmptyString(value.reviewerVariant, `${label}: reviewerVariant`);
253
+ }
254
+ return overrides;
255
+ }
256
+ export function renderMemoryConfig(overrides = {}) {
257
+ return renderJson(createMemoryConfig(overrides));
258
+ }
259
+ // END_BLOCK_SECTION_PARSE_AND_RENDER
260
+ // START_BLOCK_CANONICAL_CONFIG_PARSE_RENDER
261
+ export function parseVvocConfigText(text, label) {
262
+ const value = parseStrictJson(text, label);
263
+ const errors = validateVvocConfigDocument(value);
264
+ if (errors.length > 0) {
265
+ throw new Error(`${label}: ${errors.join("; ")}`);
266
+ }
267
+ return normalizeStrictVvocConfig(value);
268
+ }
269
+ export function loadLenientVvocConfigText(text, label, warnings) {
270
+ let value;
271
+ try {
272
+ value = parseStrictJson(text, label);
273
+ }
274
+ catch (error) {
275
+ warnings.push(error instanceof Error ? error.message : `${label}: invalid JSON`);
276
+ return createDefaultVvocConfig();
277
+ }
278
+ if (!isPlainObject(value)) {
279
+ warnings.push(`${label}: expected a top-level object`);
280
+ return createDefaultVvocConfig();
281
+ }
282
+ return {
283
+ $schema: readLenientSchemaUrl(value.$schema, `${label}: $schema`, warnings),
284
+ version: readLenientVersion(value.version, `${label}: version`, warnings),
285
+ guardian: loadLenientGuardianConfig(value.guardian, `${label}: guardian`, warnings),
286
+ memory: loadLenientMemoryConfig(value.memory, `${label}: memory`, warnings),
287
+ secretsRedaction: loadLenientSecretsRedactionConfig(value.secretsRedaction, `${label}: secretsRedaction`, warnings),
288
+ };
289
+ }
290
+ export function renderVvocConfig(config = createDefaultVvocConfig()) {
291
+ return renderJson({
292
+ $schema: VVOC_CONFIG_SCHEMA_URL,
293
+ version: VVOC_CONFIG_VERSION,
294
+ guardian: createGuardianConfig(config.guardian),
295
+ memory: createMemoryConfig(config.memory),
296
+ secretsRedaction: createSecretsRedactionConfig(config.secretsRedaction),
297
+ });
298
+ }
299
+ // END_BLOCK_CANONICAL_CONFIG_PARSE_RENDER
300
+ // START_BLOCK_SCHEMA_VALIDATION
301
+ export function validateVvocConfigDocument(document) {
302
+ if (validateWithSchema(document)) {
303
+ return [];
304
+ }
305
+ return (validateWithSchema.errors ?? []).map(formatSchemaError);
306
+ }
307
+ // END_BLOCK_SCHEMA_VALIDATION
308
+ function normalizeStrictVvocConfig(value) {
309
+ return {
310
+ $schema: readNonEmptyString(value.$schema, "$schema"),
311
+ version: readExactVersion(value.version, "version"),
312
+ guardian: createGuardianConfig(value.guardian),
313
+ memory: createMemoryConfig(value.memory),
314
+ secretsRedaction: createSecretsRedactionConfig(value.secretsRedaction),
315
+ };
316
+ }
317
+ function createSecretsRedactionConfig(overrides = {}) {
318
+ const defaults = createDefaultSecretsRedactionConfig();
319
+ const patterns = overrides.patterns ?? {};
320
+ return {
321
+ enabled: overrides.enabled ?? defaults.enabled,
322
+ secret: normalizeOptionalString(overrides.secret) ?? defaults.secret,
323
+ ttlMs: overrides.ttlMs ?? defaults.ttlMs,
324
+ maxMappings: overrides.maxMappings ?? defaults.maxMappings,
325
+ patterns: {
326
+ keywords: cloneKeywordRules(patterns.keywords ?? defaults.patterns.keywords),
327
+ regex: cloneRegexRules(patterns.regex ?? defaults.patterns.regex),
328
+ builtin: cloneStringArray(patterns.builtin ?? defaults.patterns.builtin),
329
+ exclude: cloneStringArray(patterns.exclude ?? defaults.patterns.exclude),
330
+ },
331
+ debug: overrides.debug ?? defaults.debug,
332
+ };
333
+ }
334
+ function cloneKeywordRules(rules) {
335
+ return rules.map((rule) => compactObject({ value: rule.value, category: rule.category }));
336
+ }
337
+ function cloneRegexRules(rules) {
338
+ return rules.map((rule) => ({ pattern: rule.pattern, category: rule.category }));
339
+ }
340
+ function cloneStringArray(values) {
341
+ return values.slice();
342
+ }
343
+ function loadLenientGuardianConfig(value, label, warnings) {
344
+ if (!isPlainObject(value)) {
345
+ warnings.push(`${label}: expected an object`);
346
+ return createGuardianConfig();
347
+ }
348
+ const overrides = {};
349
+ if (Object.hasOwn(value, "model")) {
350
+ const model = readLenientOptionalString(value.model, `${label}.model`, warnings);
351
+ if (model !== undefined) {
352
+ overrides.model = model;
353
+ }
354
+ }
355
+ if (Object.hasOwn(value, "variant")) {
356
+ const variant = readLenientOptionalString(value.variant, `${label}.variant`, warnings);
357
+ if (variant !== undefined) {
358
+ overrides.variant = variant;
359
+ }
360
+ }
361
+ if (Object.hasOwn(value, "timeoutMs")) {
362
+ const timeoutMs = readLenientPositiveInteger(value.timeoutMs, `${label}.timeoutMs`, warnings);
363
+ if (timeoutMs !== undefined) {
364
+ overrides.timeoutMs = timeoutMs;
365
+ }
366
+ }
367
+ if (Object.hasOwn(value, "approvalRiskThreshold")) {
368
+ const threshold = readLenientThreshold(value.approvalRiskThreshold, `${label}.approvalRiskThreshold`, warnings);
369
+ if (threshold !== undefined) {
370
+ overrides.approvalRiskThreshold = threshold;
371
+ }
372
+ }
373
+ if (Object.hasOwn(value, "reviewToastDurationMs")) {
374
+ const reviewToastDurationMs = readLenientPositiveInteger(value.reviewToastDurationMs, `${label}.reviewToastDurationMs`, warnings);
375
+ if (reviewToastDurationMs !== undefined) {
376
+ overrides.reviewToastDurationMs = reviewToastDurationMs;
377
+ }
378
+ }
379
+ return createGuardianConfig(overrides);
380
+ }
381
+ function loadLenientMemoryConfig(value, label, warnings) {
382
+ if (!isPlainObject(value)) {
383
+ warnings.push(`${label}: expected an object`);
384
+ return createMemoryConfig();
385
+ }
386
+ const overrides = {};
387
+ if (Object.hasOwn(value, "enabled")) {
388
+ if (typeof value.enabled === "boolean") {
389
+ overrides.enabled = value.enabled;
390
+ }
391
+ else {
392
+ warnings.push(`${label}.enabled: expected a boolean`);
393
+ }
394
+ }
395
+ if (Object.hasOwn(value, "defaultSearchLimit")) {
396
+ const limit = readLenientPositiveInteger(value.defaultSearchLimit, `${label}.defaultSearchLimit`, warnings);
397
+ if (limit !== undefined) {
398
+ overrides.defaultSearchLimit = limit;
399
+ }
400
+ }
401
+ if (Object.hasOwn(value, "reviewerModel")) {
402
+ const reviewerModel = readLenientOptionalString(value.reviewerModel, `${label}.reviewerModel`, warnings);
403
+ if (reviewerModel !== undefined) {
404
+ overrides.reviewerModel = reviewerModel;
405
+ }
406
+ }
407
+ if (Object.hasOwn(value, "reviewerVariant")) {
408
+ const reviewerVariant = readLenientOptionalString(value.reviewerVariant, `${label}.reviewerVariant`, warnings);
409
+ if (reviewerVariant !== undefined) {
410
+ overrides.reviewerVariant = reviewerVariant;
411
+ }
412
+ }
413
+ return createMemoryConfig(overrides);
414
+ }
415
+ function loadLenientSecretsRedactionConfig(value, label, warnings) {
416
+ if (!isPlainObject(value)) {
417
+ warnings.push(`${label}: expected an object`);
418
+ return createDefaultSecretsRedactionConfig();
419
+ }
420
+ const defaults = createDefaultSecretsRedactionConfig();
421
+ const config = createDefaultSecretsRedactionConfig();
422
+ if (Object.hasOwn(value, "enabled")) {
423
+ if (typeof value.enabled === "boolean") {
424
+ config.enabled = value.enabled;
425
+ }
426
+ else {
427
+ warnings.push(`${label}.enabled: expected a boolean`);
428
+ }
429
+ }
430
+ if (Object.hasOwn(value, "secret")) {
431
+ const secret = readLenientOptionalString(value.secret, `${label}.secret`, warnings);
432
+ if (secret !== undefined) {
433
+ config.secret = secret;
434
+ }
435
+ }
436
+ if (Object.hasOwn(value, "ttlMs")) {
437
+ const ttlMs = readLenientInteger(value.ttlMs, `${label}.ttlMs`, warnings, 0);
438
+ if (ttlMs !== undefined) {
439
+ config.ttlMs = ttlMs;
440
+ }
441
+ }
442
+ if (Object.hasOwn(value, "maxMappings")) {
443
+ const maxMappings = readLenientPositiveInteger(value.maxMappings, `${label}.maxMappings`, warnings);
444
+ if (maxMappings !== undefined) {
445
+ config.maxMappings = maxMappings;
446
+ }
447
+ }
448
+ if (Object.hasOwn(value, "debug")) {
449
+ if (typeof value.debug === "boolean") {
450
+ config.debug = value.debug;
451
+ }
452
+ else {
453
+ warnings.push(`${label}.debug: expected a boolean`);
454
+ }
455
+ }
456
+ const patternsValue = value.patterns;
457
+ if (patternsValue !== undefined) {
458
+ if (!isPlainObject(patternsValue)) {
459
+ warnings.push(`${label}.patterns: expected an object`);
460
+ config.patterns = defaults.patterns;
461
+ }
462
+ else {
463
+ config.patterns = {
464
+ keywords: loadLenientKeywordRules(patternsValue.keywords, `${label}.patterns.keywords`, warnings),
465
+ regex: loadLenientRegexRules(patternsValue.regex, `${label}.patterns.regex`, warnings),
466
+ builtin: loadLenientStringArray(patternsValue.builtin, `${label}.patterns.builtin`, warnings, defaults.patterns.builtin),
467
+ exclude: loadLenientStringArray(patternsValue.exclude, `${label}.patterns.exclude`, warnings, defaults.patterns.exclude),
468
+ };
469
+ }
470
+ }
471
+ return config;
472
+ }
473
+ function loadLenientKeywordRules(value, label, warnings) {
474
+ if (value === undefined) {
475
+ return [];
476
+ }
477
+ if (!Array.isArray(value)) {
478
+ warnings.push(`${label}: expected an array`);
479
+ return [];
480
+ }
481
+ const rules = [];
482
+ for (const [index, entry] of value.entries()) {
483
+ if (!isPlainObject(entry)) {
484
+ warnings.push(`${label}[${index}]: expected an object`);
485
+ continue;
486
+ }
487
+ const ruleValue = readLenientOptionalString(entry.value, `${label}[${index}].value`, warnings);
488
+ if (!ruleValue) {
489
+ continue;
490
+ }
491
+ const category = Object.hasOwn(entry, "category")
492
+ ? readLenientOptionalString(entry.category, `${label}[${index}].category`, warnings)
493
+ : undefined;
494
+ rules.push(compactObject({ value: ruleValue, category }));
495
+ }
496
+ return rules;
497
+ }
498
+ function loadLenientRegexRules(value, label, warnings) {
499
+ if (value === undefined) {
500
+ return [];
501
+ }
502
+ if (!Array.isArray(value)) {
503
+ warnings.push(`${label}: expected an array`);
504
+ return [];
505
+ }
506
+ const rules = [];
507
+ for (const [index, entry] of value.entries()) {
508
+ if (!isPlainObject(entry)) {
509
+ warnings.push(`${label}[${index}]: expected an object`);
510
+ continue;
511
+ }
512
+ const pattern = readLenientOptionalString(entry.pattern, `${label}[${index}].pattern`, warnings);
513
+ const category = readLenientOptionalString(entry.category, `${label}[${index}].category`, warnings);
514
+ if (!pattern || !category) {
515
+ continue;
516
+ }
517
+ rules.push({ pattern, category });
518
+ }
519
+ return rules;
520
+ }
521
+ function loadLenientStringArray(value, label, warnings, fallback) {
522
+ if (value === undefined) {
523
+ return cloneStringArray(fallback);
524
+ }
525
+ if (!Array.isArray(value)) {
526
+ warnings.push(`${label}: expected an array`);
527
+ return cloneStringArray(fallback);
528
+ }
529
+ const entries = value
530
+ .map((entry, index) => {
531
+ const normalized = readLenientOptionalString(entry, `${label}[${index}]`, warnings);
532
+ return normalized ?? "";
533
+ })
534
+ .filter(Boolean);
535
+ return entries;
536
+ }
537
+ function readLenientSchemaUrl(value, label, warnings) {
538
+ const normalized = readLenientOptionalString(value, label, warnings);
539
+ return normalized ?? VVOC_CONFIG_SCHEMA_URL;
540
+ }
541
+ function readLenientVersion(value, label, warnings) {
542
+ if (value === undefined) {
543
+ return VVOC_CONFIG_VERSION;
544
+ }
545
+ if (value === VVOC_CONFIG_VERSION) {
546
+ return VVOC_CONFIG_VERSION;
547
+ }
548
+ warnings.push(`${label}: expected ${VVOC_CONFIG_VERSION}`);
549
+ return VVOC_CONFIG_VERSION;
550
+ }
551
+ function readLenientOptionalString(value, label, warnings) {
552
+ if (typeof value !== "string" || !value.trim()) {
553
+ warnings.push(`${label}: expected a non-empty string`);
554
+ return undefined;
555
+ }
556
+ return value.trim();
557
+ }
558
+ function readLenientInteger(value, label, warnings, minimum) {
559
+ if (typeof value === "number" && Number.isInteger(value) && value >= minimum) {
560
+ return value;
561
+ }
562
+ warnings.push(`${label}: expected an integer >= ${minimum}`);
563
+ return undefined;
564
+ }
565
+ function readLenientPositiveInteger(value, label, warnings) {
566
+ return readLenientInteger(value, label, warnings, 1);
567
+ }
568
+ function readLenientThreshold(value, label, warnings) {
569
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 100) {
570
+ return value;
571
+ }
572
+ warnings.push(`${label}: expected an integer between 0 and 100`);
573
+ return undefined;
574
+ }
575
+ function parseStrictJson(text, label) {
576
+ try {
577
+ return JSON.parse(text);
578
+ }
579
+ catch (error) {
580
+ throw new Error(`${label}: failed to parse JSON (${error instanceof Error ? error.message : String(error)})`);
581
+ }
582
+ }
583
+ function readExactVersion(value, label) {
584
+ if (value !== VVOC_CONFIG_VERSION) {
585
+ throw new Error(`${label}: expected ${VVOC_CONFIG_VERSION}`);
586
+ }
587
+ return VVOC_CONFIG_VERSION;
588
+ }
589
+ function readNonEmptyString(value, label) {
590
+ if (typeof value !== "string" || !value.trim()) {
591
+ throw new Error(`${label}: expected a non-empty string`);
592
+ }
593
+ return value.trim();
594
+ }
595
+ function readPositiveInteger(value, label) {
596
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
597
+ return value;
598
+ }
599
+ throw new Error(`${label}: expected a positive integer`);
600
+ }
601
+ function readThreshold(value, label) {
602
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 100) {
603
+ return value;
604
+ }
605
+ throw new Error(`${label}: expected an integer between 0 and 100`);
606
+ }
607
+ function normalizeOptionalString(value) {
608
+ if (typeof value !== "string") {
609
+ return undefined;
610
+ }
611
+ const trimmed = value.trim();
612
+ return trimmed || undefined;
613
+ }
614
+ function isPlainObject(value) {
615
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
616
+ }
617
+ function assertAllowedKeys(record, allowedKeys, label) {
618
+ const allowed = new Set(allowedKeys);
619
+ for (const key of Object.keys(record)) {
620
+ if (!allowed.has(key)) {
621
+ throw new Error(`${label}: unsupported field "${key}"`);
622
+ }
623
+ }
624
+ }
625
+ function compactObject(value) {
626
+ const nextEntries = Object.entries(value).filter(([, entry]) => entry !== undefined);
627
+ return Object.fromEntries(nextEntries);
628
+ }
629
+ function renderJson(value) {
630
+ return `${JSON.stringify(value, null, 2)}\n`;
631
+ }
632
+ function formatSchemaError(error) {
633
+ const path = error.instancePath || "/";
634
+ if (error.keyword === "required") {
635
+ return `${path} missing required property "${String(error.params.missingProperty ?? "unknown")}"`;
636
+ }
637
+ if (error.keyword === "additionalProperties") {
638
+ return `${path} has unsupported property "${String(error.params.additionalProperty ?? "unknown")}"`;
639
+ }
640
+ return `${path} ${error.message ?? "is invalid"}`;
641
+ }
642
+ //# sourceMappingURL=vvoc-config.js.map