opencode-feature-factory 0.2.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +877 -0
  3. package/assets/agent/backend-builder.md +73 -0
  4. package/assets/agent/codebase-researcher.md +56 -0
  5. package/assets/agent/design-interpreter.md +37 -0
  6. package/assets/agent/frontend-builder.md +74 -0
  7. package/assets/agent/implementation-validator.md +73 -0
  8. package/assets/agent/security-reviewer.md +60 -0
  9. package/assets/agent/spec-writer.md +94 -0
  10. package/assets/agent/story-reader.md +38 -0
  11. package/assets/agent/story-writer.md +39 -0
  12. package/assets/agent/test-verifier.md +73 -0
  13. package/assets/agent/work-decomposer.md +102 -0
  14. package/assets/agent/work-reviewer.md +77 -0
  15. package/assets/command/feature.md +68 -0
  16. package/assets/skills/feature/SCHEMA.md +990 -0
  17. package/assets/skills/feature/SKILL.md +620 -0
  18. package/dist/tui.js +3641 -0
  19. package/package.json +65 -0
  20. package/src/cleanup-sweep-command.js +75 -0
  21. package/src/cleanup-sweep-eligibility.js +581 -0
  22. package/src/cleanup-sweep-output.js +139 -0
  23. package/src/cleanup-sweep-report.js +548 -0
  24. package/src/cleanup-sweep.js +546 -0
  25. package/src/cli-output.js +251 -0
  26. package/src/cli.js +1152 -0
  27. package/src/config.js +231 -0
  28. package/src/cost-attribution.js +327 -0
  29. package/src/cost-report.js +185 -0
  30. package/src/detached-log-supervisor.js +178 -0
  31. package/src/doctor.js +598 -0
  32. package/src/env-snapshot.js +211 -0
  33. package/src/factory-diagnostics.js +429 -0
  34. package/src/factory-paths.js +40 -0
  35. package/src/factory.js +4769 -0
  36. package/src/feature-command-payload.js +378 -0
  37. package/src/git.js +110 -0
  38. package/src/github.js +252 -0
  39. package/src/hardening/atomic-write.js +954 -0
  40. package/src/hardening/line-output.js +139 -0
  41. package/src/hardening/output-policy.js +365 -0
  42. package/src/hardening/process-verification.js +542 -0
  43. package/src/hardening/sensitive-data.js +341 -0
  44. package/src/hardening/terminal-encoding.js +224 -0
  45. package/src/plugin.js +246 -0
  46. package/src/post-pr-ci.js +754 -0
  47. package/src/process-evidence.js +1139 -0
  48. package/src/refs.js +144 -0
  49. package/src/run-state.js +2411 -0
  50. package/src/steering-conflicts.js +77 -0
  51. package/src/telemetry.js +419 -0
  52. package/src/tui-data.js +499 -0
  53. package/src/tui-rendering.js +148 -0
  54. package/src/utils.js +35 -0
  55. package/src/validate.js +1655 -0
  56. package/src/worktrees.js +56 -0
@@ -0,0 +1,341 @@
1
+ const BASELINE_SENSITIVE_KEY_PATTERN = /(?:secret|token|password|passwd|pwd|api[_-]?key|private[_-]?key|credential|authorization|auth[_-]?header|access[_-]?key|bearer|cookie)/iu;
2
+ const BASELINE_SENSITIVE_VALUE_PATTERN = /(?:secret|token|password|passwd|api[_-]?key|private[_-]?key)/iu;
3
+ const FLATTENED_BASIC_HEADER_PATTERN = /(^|[\r\n]|[^A-Za-z0-9_-])((?:Proxy-)?Authorization[ \t]*[:=][ \t]*Basic[ \t]+)([A-Za-z0-9._~+/-]+={0,})(?![A-Za-z0-9._~+/=-])/iu;
4
+ const FLATTENED_BASIC_HEADER_GLOBAL_PATTERN = /(^|[\r\n]|[^A-Za-z0-9_-])((?:Proxy-)?Authorization[ \t]*[:=][ \t]*Basic[ \t]+)([A-Za-z0-9._~+/-]+={0,})(?![A-Za-z0-9._~+/=-])/giu;
5
+ const TOKEN_SHAPED_VALUE_PATTERNS = Object.freeze([
6
+ /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/iu,
7
+ /\bgithub_pat_[A-Za-z0-9_]{20,}\b/iu,
8
+ /\bhc_[A-Za-z0-9][A-Za-z0-9_-]{10,}\b/iu,
9
+ /\bsk-proj[-_][A-Za-z0-9_-]{20,}\b/iu,
10
+ /\bsk-[A-Za-z0-9_-]{20,}\b/iu,
11
+ /\bxox[abp][_-][A-Za-z0-9-]{10,}(?:-[A-Za-z0-9-]{10,})*\b/iu,
12
+ /\bglpat-[A-Za-z0-9_-]{20,}\b/iu,
13
+ /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}\b/iu,
14
+ /\beyJ[A-Za-z0-9_-]{7,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/iu,
15
+ /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/iu,
16
+ /(?:^|[^A-Za-z0-9-])(?:[A-Fa-f0-9]{32,})(?=$|[^A-Za-z0-9-])/u,
17
+ /(?:https?|ssh|git|ftp):\/\/[^/\s:@]+:[^/\s@]+@/iu,
18
+ ]);
19
+ const ENDPOINT_PROVIDER_PREFIX_PATTERN = /(?:hc[a-z0-9_-]*|gh[pousr]|github_pat|sk(?:-proj)?|xox[abp]|glpat)[_-][A-Za-z0-9_-]{10,}/iu;
20
+ const ENDPOINT_BARE_HEX_PATTERN = /^[A-Fa-f0-9]{32,}$/u;
21
+ const ENDPOINT_SECRET_ASSIGNMENT_PATTERN = /(?:(?:key|token|secret|password|authorization|credential|access|team|api[_-]?key)\s*[=:]|[=:]\s*(?:key|token|secret|password|authorization|credential|access|team|api[_-]?key))/iu;
22
+ const ENDPOINT_LONG_TOKEN_PATTERN = /^[A-Za-z0-9._~+/-]{24,}$/u;
23
+ const HIGH_ENTROPY_SINGLE_TOKEN_MIN_LENGTH = 32;
24
+ const HIGH_ENTROPY_MIN_SHANNON = 3.5;
25
+ const SAFE_HIGH_ENTROPY_KEY_PATTERN = /^(?:OTEL_EXPORTER_OTLP(?:_(?:TRACES|METRICS|LOGS))?_(?:ENDPOINT|HEADERS|PROTOCOL|TIMEOUT|COMPRESSION|INSECURE|CERTIFICATE)|FEATURE_FACTORY_OTEL_ENABLED)$/u;
26
+ const ARRAY_INDEX_MAX = 2 ** 32 - 2;
27
+
28
+ export const REDACTED_VALUE = "[redacted]";
29
+ export const REDACTED_KEY = "[redacted-key]";
30
+ export const SCRUB_MARKERS = Object.freeze({
31
+ circular: "[circular]",
32
+ repeated: "[repeated]",
33
+ truncated: "[truncated]",
34
+ unsupported: "[unsupported]",
35
+ unavailable: "[unavailable]",
36
+ });
37
+
38
+ export function isSensitiveKey(value, { mode = "baseline" } = {}) {
39
+ assertMode(mode);
40
+ if (typeof value !== "string") return false;
41
+ return BASELINE_SENSITIVE_KEY_PATTERN.test(value) || isSecretShapedKey(value, { mode });
42
+ }
43
+
44
+ export function isSecretShapedKey(value, { mode = "baseline" } = {}) {
45
+ assertMode(mode);
46
+ if (typeof value !== "string") return false;
47
+ const trimmed = value.trim();
48
+ if (!trimmed || SAFE_HIGH_ENTROPY_KEY_PATTERN.test(trimmed)) return false;
49
+ return tokenShapedValue(trimmed)
50
+ || credentialBearingUrl(trimmed)
51
+ || highEntropySingleToken(trimmed)
52
+ || (mode === "endpoint" && endpointValueLooksSensitive(trimmed));
53
+ }
54
+
55
+ export function isSensitiveValue(value, { mode = "baseline" } = {}) {
56
+ assertMode(mode);
57
+ if (typeof value !== "string") return false;
58
+ const trimmed = value.trim();
59
+ if (!trimmed) return false;
60
+ return FLATTENED_BASIC_HEADER_PATTERN.test(value)
61
+ || BASELINE_SENSITIVE_VALUE_PATTERN.test(trimmed)
62
+ || tokenShapedValue(trimmed)
63
+ || credentialBearingUrl(trimmed)
64
+ || highEntropySingleToken(trimmed)
65
+ || (mode === "endpoint" && endpointValueLooksSensitive(trimmed));
66
+ }
67
+
68
+ export function scrubSensitiveString(value, { mode = "baseline" } = {}) {
69
+ assertMode(mode);
70
+ if (typeof value !== "string") return SCRUB_MARKERS.unsupported;
71
+ const flattenedCredentialsRedacted = value.replace(
72
+ FLATTENED_BASIC_HEADER_GLOBAL_PATTERN,
73
+ `$1$2${REDACTED_VALUE}`,
74
+ );
75
+ return isSensitiveValue(flattenedCredentialsRedacted, { mode })
76
+ ? REDACTED_VALUE
77
+ : flattenedCredentialsRedacted;
78
+ }
79
+
80
+ export function scrubSensitiveData(value, {
81
+ mode = "baseline",
82
+ keyMode = "omit",
83
+ maxDepth = 32,
84
+ maxNodes = 10_000,
85
+ maxKeys = 10_000,
86
+ maxArrayLength = 10_000,
87
+ maxStringLength = 65_536,
88
+ } = {}) {
89
+ assertMode(mode);
90
+ if (keyMode !== "omit" && keyMode !== "redact") throw new TypeError("invalid sensitive-data key mode");
91
+ assertBound("maxDepth", maxDepth);
92
+ assertBound("maxNodes", maxNodes);
93
+ assertBound("maxKeys", maxKeys);
94
+ assertBound("maxArrayLength", maxArrayLength, 2 ** 32 - 1, 1);
95
+ assertBound("maxStringLength", maxStringLength);
96
+
97
+ const state = {
98
+ mode,
99
+ keyMode,
100
+ maxDepth,
101
+ maxNodes,
102
+ maxKeys,
103
+ maxArrayLength,
104
+ maxStringLength,
105
+ nodes: 0,
106
+ keys: 0,
107
+ active: new WeakSet(),
108
+ completed: new WeakSet(),
109
+ };
110
+ return scrubValue(value, 0, state).value;
111
+ }
112
+
113
+ function scrubValue(value, depth, state) {
114
+ if (state.nodes >= state.maxNodes) return { value: SCRUB_MARKERS.truncated, stop: true };
115
+ state.nodes += 1;
116
+
117
+ if (value === null || typeof value === "boolean") return { value, stop: false };
118
+ if (typeof value === "number") {
119
+ return { value: Number.isFinite(value) && !Object.is(value, -0) ? value : SCRUB_MARKERS.unsupported, stop: false };
120
+ }
121
+ if (typeof value === "string") {
122
+ if (value.length > state.maxStringLength) return { value: REDACTED_VALUE, stop: false };
123
+ return { value: scrubSensitiveString(value, { mode: state.mode }), stop: false };
124
+ }
125
+ if (typeof value !== "object") return { value: SCRUB_MARKERS.unsupported, stop: false };
126
+
127
+ let array;
128
+ let prototype;
129
+ try {
130
+ array = Array.isArray(value);
131
+ prototype = Object.getPrototypeOf(value);
132
+ } catch {
133
+ return { value: SCRUB_MARKERS.unavailable, stop: false };
134
+ }
135
+ if ((array && prototype !== Array.prototype)
136
+ || (!array && prototype !== Object.prototype && prototype !== null)) {
137
+ return { value: SCRUB_MARKERS.unsupported, stop: false };
138
+ }
139
+ if (depth >= state.maxDepth) return { value: SCRUB_MARKERS.truncated, stop: false };
140
+ if (state.active.has(value)) return { value: SCRUB_MARKERS.circular, stop: false };
141
+ if (state.completed.has(value)) return { value: SCRUB_MARKERS.repeated, stop: false };
142
+
143
+ state.active.add(value);
144
+ let result;
145
+ try {
146
+ result = array ? scrubArray(value, depth, state) : scrubObject(value, depth, state);
147
+ } catch {
148
+ result = { value: SCRUB_MARKERS.unavailable, stop: false };
149
+ }
150
+ state.active.delete(value);
151
+ state.completed.add(value);
152
+ return result;
153
+ }
154
+
155
+ function scrubObject(source, depth, state) {
156
+ let keys;
157
+ try {
158
+ keys = Reflect.ownKeys(source);
159
+ } catch {
160
+ return { value: SCRUB_MARKERS.unavailable, stop: false };
161
+ }
162
+
163
+ const output = Object.create(null);
164
+ return projectKeys(source, output, keys, depth, state, () => true);
165
+ }
166
+
167
+ function scrubArray(source, depth, state) {
168
+ let lengthDescriptor;
169
+ let keys;
170
+ try {
171
+ lengthDescriptor = Reflect.getOwnPropertyDescriptor(source, "length");
172
+ keys = Reflect.ownKeys(source);
173
+ } catch {
174
+ return { value: SCRUB_MARKERS.unavailable, stop: false };
175
+ }
176
+ if (!lengthDescriptor || !("value" in lengthDescriptor)
177
+ || !Number.isInteger(lengthDescriptor.value)
178
+ || lengthDescriptor.value < 0
179
+ || lengthDescriptor.value > 2 ** 32 - 1) {
180
+ return { value: SCRUB_MARKERS.unavailable, stop: false };
181
+ }
182
+
183
+ const sourceLength = lengthDescriptor.value;
184
+ const outputLength = Math.min(sourceLength, state.maxArrayLength);
185
+ const output = new Array(outputLength);
186
+ const overCap = sourceLength > state.maxArrayLength;
187
+ const forcedTruncationIndex = overCap && outputLength > 0 ? outputLength - 1 : -1;
188
+ if (forcedTruncationIndex >= 0) defineData(output, String(forcedTruncationIndex), SCRUB_MARKERS.truncated);
189
+
190
+ return projectKeys(source, output, keys, depth, state, (key) => {
191
+ if (key === "length") return false;
192
+ const index = arrayIndex(key);
193
+ if (index === null) return true;
194
+ if (index >= outputLength) return false;
195
+ return index !== forcedTruncationIndex;
196
+ });
197
+ }
198
+
199
+ function projectKeys(source, output, keys, depth, state, include) {
200
+ const prepared = prepareKeys(source, keys, state, include);
201
+ const allocated = new Set(prepared.reserved);
202
+ for (const item of prepared.items) {
203
+ const { key, sensitive, descriptor } = item;
204
+ if (state.keys >= state.maxKeys) {
205
+ addTruncationProperty(output, allocated);
206
+ return { value: output, stop: true };
207
+ }
208
+ state.keys += 1;
209
+
210
+ if (item.unavailable) {
211
+ if (state.keyMode === "omit" && sensitive) continue;
212
+ const outputKey = sensitive ? allocateName(REDACTED_KEY, allocated) : key;
213
+ defineData(output, outputKey, SCRUB_MARKERS.unavailable);
214
+ continue;
215
+ }
216
+ if (sensitive) {
217
+ if (state.keyMode === "redact") defineData(output, allocateName(REDACTED_KEY, allocated), REDACTED_VALUE);
218
+ continue;
219
+ }
220
+ if (!("value" in descriptor)) {
221
+ defineData(output, key, SCRUB_MARKERS.unavailable);
222
+ continue;
223
+ }
224
+
225
+ const scrubbed = scrubValue(descriptor.value, depth + 1, state);
226
+ defineData(output, key, scrubbed.value);
227
+ if (scrubbed.stop) return { value: output, stop: true };
228
+ }
229
+ if (prepared.truncated) {
230
+ addTruncationProperty(output, allocated);
231
+ return { value: output, stop: true };
232
+ }
233
+ return { value: output, stop: false };
234
+ }
235
+
236
+ function prepareKeys(source, keys, state, include) {
237
+ const items = [];
238
+ const reserved = new Set();
239
+ const remaining = state.maxKeys - state.keys;
240
+ for (const key of keys) {
241
+ if (typeof key !== "string" || !include(key)) continue;
242
+ const sensitive = key.length > state.maxStringLength || isSensitiveKey(key, { mode: state.mode });
243
+
244
+ let descriptor;
245
+ try {
246
+ descriptor = Reflect.getOwnPropertyDescriptor(source, key);
247
+ } catch {
248
+ descriptor = null;
249
+ }
250
+ if (descriptor && descriptor.enumerable !== true) continue;
251
+ if (items.length >= remaining) return { items, reserved, truncated: true };
252
+ items.push({ key, sensitive, descriptor, unavailable: descriptor === null || descriptor === undefined });
253
+ if (!sensitive) reserved.add(key);
254
+ }
255
+ return { items, reserved, truncated: false };
256
+ }
257
+
258
+ function addTruncationProperty(output, allocated) {
259
+ defineData(output, allocateName(SCRUB_MARKERS.truncated, allocated), SCRUB_MARKERS.truncated);
260
+ }
261
+
262
+ function allocateName(base, allocated) {
263
+ if (!allocated.has(base)) {
264
+ allocated.add(base);
265
+ return base;
266
+ }
267
+ for (let suffix = 2; ; suffix += 1) {
268
+ const candidate = `${base}#${suffix}`;
269
+ if (!allocated.has(candidate)) {
270
+ allocated.add(candidate);
271
+ return candidate;
272
+ }
273
+ }
274
+ }
275
+
276
+ function defineData(target, key, value) {
277
+ Object.defineProperty(target, key, {
278
+ value,
279
+ enumerable: true,
280
+ configurable: true,
281
+ writable: true,
282
+ });
283
+ }
284
+
285
+ function arrayIndex(key) {
286
+ if (key === "") return null;
287
+ const number = Number(key);
288
+ if (!Number.isInteger(number) || number < 0 || number > ARRAY_INDEX_MAX || String(number) !== key) return null;
289
+ return number;
290
+ }
291
+
292
+ function endpointValueLooksSensitive(value) {
293
+ return ENDPOINT_PROVIDER_PREFIX_PATTERN.test(value)
294
+ || ENDPOINT_BARE_HEX_PATTERN.test(value)
295
+ || ENDPOINT_SECRET_ASSIGNMENT_PATTERN.test(value)
296
+ || (ENDPOINT_LONG_TOKEN_PATTERN.test(value) && mixedTokenChars(value));
297
+ }
298
+
299
+ function tokenShapedValue(value) {
300
+ return TOKEN_SHAPED_VALUE_PATTERNS.some((pattern) => pattern.test(value));
301
+ }
302
+
303
+ function credentialBearingUrl(value) {
304
+ try {
305
+ const parsed = new URL(value);
306
+ return Boolean(parsed.username || parsed.password);
307
+ } catch {
308
+ return false;
309
+ }
310
+ }
311
+
312
+ function highEntropySingleToken(value) {
313
+ if (value.length < HIGH_ENTROPY_SINGLE_TOKEN_MIN_LENGTH || /\s/u.test(value)) return false;
314
+ if (!/^[A-Za-z0-9._~+/=-]+$/u.test(value)) return false;
315
+ return shannonEntropy(value) >= HIGH_ENTROPY_MIN_SHANNON;
316
+ }
317
+
318
+ function shannonEntropy(value) {
319
+ const counts = new Map();
320
+ for (const character of value) counts.set(character, (counts.get(character) || 0) + 1);
321
+ let entropy = 0;
322
+ for (const count of counts.values()) {
323
+ const probability = count / value.length;
324
+ entropy -= probability * Math.log2(probability);
325
+ }
326
+ return entropy;
327
+ }
328
+
329
+ function mixedTokenChars(value) {
330
+ return /[A-Z]/u.test(value) && /[a-z]/u.test(value) && /[0-9]/u.test(value);
331
+ }
332
+
333
+ function assertMode(mode) {
334
+ if (mode !== "baseline" && mode !== "endpoint") throw new TypeError("invalid sensitive-data mode");
335
+ }
336
+
337
+ function assertBound(name, value, maximum = Number.MAX_SAFE_INTEGER, minimum = 0) {
338
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
339
+ throw new TypeError(`invalid sensitive-data ${name}`);
340
+ }
341
+ }
@@ -0,0 +1,224 @@
1
+ const ASCII_IDENTITY_PROFILE = "ascii-identity";
2
+ const UNICODE_PROSE_PROFILE = "unicode-prose";
3
+ const SAFE_JSON_MESSAGE = "Value is not valid safe JSON data.";
4
+
5
+ const UNICODE_PROSE_SCALAR = /^[\p{L}\p{M}\p{N}\p{P}\p{S}]$/u;
6
+ const DEFAULT_IGNORABLE_SCALAR = /^\p{Default_Ignorable_Code_Point}$/u;
7
+
8
+ export class SafeJsonError extends Error {
9
+ constructor() {
10
+ super(SAFE_JSON_MESSAGE);
11
+ this.name = "SafeJsonError";
12
+ }
13
+ }
14
+
15
+ export function encodeTerminalText(value, options = undefined) {
16
+ return encodeTerminalValue(value, readProfile(options, UNICODE_PROSE_PROFILE), false);
17
+ }
18
+
19
+ export function encodeTerminalLabel(value, options = undefined) {
20
+ const encoded = encodeTerminalValue(value, readProfile(options, ASCII_IDENTITY_PROFILE), true);
21
+ return `"${encoded}"`;
22
+ }
23
+
24
+ export function assertJsonDataModel(value) {
25
+ try {
26
+ return cloneJsonValue(value, new WeakSet());
27
+ } catch {
28
+ throw new SafeJsonError();
29
+ }
30
+ }
31
+
32
+ export function serializeTerminalJson(value, options = undefined) {
33
+ try {
34
+ const space = readJsonSpace(options);
35
+ const clone = cloneJsonValue(value, new WeakSet());
36
+ return escapeJsonTransport(JSON.stringify(clone, null, space));
37
+ } catch {
38
+ throw new SafeJsonError();
39
+ }
40
+ }
41
+
42
+ function readProfile(options, fallback) {
43
+ try {
44
+ if (options === undefined) return fallback;
45
+ if (options === null || (typeof options !== "object" && typeof options !== "function")) {
46
+ throw new TypeError();
47
+ }
48
+ const profile = options.profile === undefined ? fallback : options.profile;
49
+ if (profile !== ASCII_IDENTITY_PROFILE && profile !== UNICODE_PROSE_PROFILE) {
50
+ throw new TypeError();
51
+ }
52
+ return profile;
53
+ } catch {
54
+ throw new TypeError("Invalid terminal encoding options.");
55
+ }
56
+ }
57
+
58
+ function encodeTerminalValue(value, profile, quoteIsReserved) {
59
+ let text;
60
+ try {
61
+ text = String(value);
62
+ } catch {
63
+ throw new TypeError("Terminal text encoding failed.");
64
+ }
65
+
66
+ let encoded = "";
67
+ for (let index = 0; index < text.length;) {
68
+ const first = text.charCodeAt(index);
69
+
70
+ if (first === 0x5C) {
71
+ encoded += "\\\\";
72
+ index += 1;
73
+ continue;
74
+ }
75
+ if (quoteIsReserved && first === 0x22) {
76
+ encoded += "\\\"";
77
+ index += 1;
78
+ continue;
79
+ }
80
+
81
+ if (profile === ASCII_IDENTITY_PROFILE) {
82
+ encoded += first >= 0x20 && first <= 0x7E ? text[index] : unicodeEscape(first);
83
+ index += 1;
84
+ continue;
85
+ }
86
+
87
+ const second = text.charCodeAt(index + 1);
88
+ const isPair = first >= 0xD800 && first <= 0xDBFF && second >= 0xDC00 && second <= 0xDFFF;
89
+ if (isPair) {
90
+ const scalar = text.slice(index, index + 2);
91
+ encoded += isSafeUnicodeProseScalar(scalar)
92
+ ? scalar
93
+ : `${unicodeEscape(first)}${unicodeEscape(second)}`;
94
+ index += 2;
95
+ continue;
96
+ }
97
+
98
+ const scalar = text[index];
99
+ encoded += isSafeUnicodeProseScalar(scalar) ? scalar : unicodeEscape(first);
100
+ index += 1;
101
+ }
102
+ return encoded;
103
+ }
104
+
105
+ function isSafeUnicodeProseScalar(scalar) {
106
+ if (scalar === " ") return true;
107
+ return UNICODE_PROSE_SCALAR.test(scalar) && !DEFAULT_IGNORABLE_SCALAR.test(scalar);
108
+ }
109
+
110
+ function unicodeEscape(codeUnit) {
111
+ return `\\u${codeUnit.toString(16).toUpperCase().padStart(4, "0")}`;
112
+ }
113
+
114
+ function readJsonSpace(options) {
115
+ if (options === undefined) return 0;
116
+ if (options === null || (typeof options !== "object" && typeof options !== "function")) failJson();
117
+ const space = options.space === undefined ? 0 : options.space;
118
+ if (space !== 0 && space !== 2) failJson();
119
+ return space;
120
+ }
121
+
122
+ function cloneJsonValue(value, seen) {
123
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
124
+ if (typeof value === "number") {
125
+ if (!Number.isFinite(value) || Object.is(value, -0)) failJson();
126
+ return value;
127
+ }
128
+ if (typeof value !== "object") failJson();
129
+ if (seen.has(value)) failJson();
130
+ seen.add(value);
131
+
132
+ if (Array.isArray(value)) return cloneJsonArray(value, seen);
133
+ return cloneJsonObject(value, seen);
134
+ }
135
+
136
+ function cloneJsonArray(value, seen) {
137
+ if (Object.getPrototypeOf(value) !== Array.prototype) failJson();
138
+
139
+ const keys = Reflect.ownKeys(value);
140
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
141
+ if (!isDataDescriptor(lengthDescriptor)
142
+ || lengthDescriptor.enumerable
143
+ || !Number.isInteger(lengthDescriptor.value)
144
+ || lengthDescriptor.value < 0
145
+ || lengthDescriptor.value > 0xFFFFFFFF
146
+ || keys.length !== lengthDescriptor.value + 1) {
147
+ failJson();
148
+ }
149
+
150
+ const clone = [];
151
+ Object.setPrototypeOf(clone, null);
152
+ for (const key of keys) {
153
+ if (key === "length") continue;
154
+ if (typeof key !== "string" || !isArrayIndex(key, lengthDescriptor.value)) failJson();
155
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
156
+ if (!isDataDescriptor(descriptor) || !descriptor.enumerable) failJson();
157
+ defineJsonProperty(clone, key, cloneJsonValue(descriptor.value, seen));
158
+ }
159
+ return clone;
160
+ }
161
+
162
+ function cloneJsonObject(value, seen) {
163
+ const prototype = Object.getPrototypeOf(value);
164
+ if (prototype !== Object.prototype && prototype !== null) failJson();
165
+
166
+ const clone = Object.create(null);
167
+ for (const key of Reflect.ownKeys(value)) {
168
+ if (typeof key !== "string" || key === "toJSON") failJson();
169
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
170
+ if (!isDataDescriptor(descriptor) || !descriptor.enumerable) failJson();
171
+ defineJsonProperty(clone, key, cloneJsonValue(descriptor.value, seen));
172
+ }
173
+ return clone;
174
+ }
175
+
176
+ function isDataDescriptor(descriptor) {
177
+ return descriptor !== undefined && Object.hasOwn(descriptor, "value");
178
+ }
179
+
180
+ function isArrayIndex(key, length) {
181
+ if (key.length === 0) return false;
182
+ const index = Number(key);
183
+ return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
184
+ }
185
+
186
+ function defineJsonProperty(target, key, value) {
187
+ Object.defineProperty(target, key, {
188
+ value,
189
+ enumerable: true,
190
+ configurable: true,
191
+ writable: true,
192
+ });
193
+ }
194
+
195
+ function escapeJsonTransport(serialized) {
196
+ let safe = "";
197
+ for (let index = 0; index < serialized.length;) {
198
+ const first = serialized.charCodeAt(index);
199
+ if (first <= 0x7E) {
200
+ safe += serialized[index];
201
+ index += 1;
202
+ continue;
203
+ }
204
+
205
+ const second = serialized.charCodeAt(index + 1);
206
+ const isPair = first >= 0xD800 && first <= 0xDBFF && second >= 0xDC00 && second <= 0xDFFF;
207
+ if (isPair) {
208
+ const scalar = serialized.slice(index, index + 2);
209
+ safe += isSafeUnicodeProseScalar(scalar)
210
+ ? scalar
211
+ : `${unicodeEscape(first)}${unicodeEscape(second)}`;
212
+ index += 2;
213
+ continue;
214
+ }
215
+
216
+ safe += isSafeUnicodeProseScalar(serialized[index]) ? serialized[index] : unicodeEscape(first);
217
+ index += 1;
218
+ }
219
+ return safe;
220
+ }
221
+
222
+ function failJson() {
223
+ throw new Error();
224
+ }