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,139 @@
1
+ import { StringDecoder } from "node:string_decoder";
2
+ import { Writable } from "node:stream";
3
+ import {
4
+ SAFE_OUTPUT_FALLBACK,
5
+ freeformSegment,
6
+ renderTerminalSegments,
7
+ } from "./output-policy.js";
8
+
9
+ const MAX_LINE_CODE_UNITS = 65_536;
10
+
11
+ export function createSanitizedLineWriter({ write } = {}) {
12
+ if (typeof write !== "function") throw new TypeError("line output write function is required");
13
+
14
+ let destinationQueue = Promise.resolve();
15
+ const enqueue = (stream, buffer) => {
16
+ destinationQueue = destinationQueue.then(() => write(stream, buffer));
17
+ // A caller may end both endpoints before awaiting finished(). Keep the
18
+ // serialized queue rejection observed during that interval.
19
+ destinationQueue.catch(() => {});
20
+ };
21
+
22
+ const stdout = createEndpoint("stdout", enqueue);
23
+ const stderr = createEndpoint("stderr", enqueue);
24
+ const stdoutFinished = endpointFinished(stdout);
25
+ const stderrFinished = endpointFinished(stderr);
26
+
27
+ return {
28
+ stdout,
29
+ stderr,
30
+ async finished() {
31
+ await Promise.all([stdoutFinished, stderrFinished]);
32
+ await destinationQueue;
33
+ },
34
+ };
35
+ }
36
+
37
+ function createEndpoint(stream, enqueue) {
38
+ const decoder = new StringDecoder("utf8");
39
+ const state = {
40
+ content: [],
41
+ contentLength: 0,
42
+ pendingCarriageReturn: false,
43
+ discardingOversizedLine: false,
44
+ };
45
+
46
+ return new Writable({
47
+ write(chunk, _encoding, callback) {
48
+ try {
49
+ consumeDecodedText(decoder.write(chunk), stream, state, enqueue);
50
+ callback();
51
+ } catch {
52
+ callback(new Error("Sanitized line processing failed safely."));
53
+ }
54
+ },
55
+ final(callback) {
56
+ try {
57
+ consumeDecodedText(decoder.end(), stream, state, enqueue);
58
+ flushFinalFragment(stream, state, enqueue);
59
+ callback();
60
+ } catch {
61
+ callback(new Error("Sanitized line processing failed safely."));
62
+ }
63
+ },
64
+ });
65
+ }
66
+
67
+ function consumeDecodedText(text, stream, state, enqueue) {
68
+ for (let index = 0; index < text.length; index += 1) {
69
+ const codeUnit = text[index];
70
+
71
+ if (codeUnit === "\n") {
72
+ if (!state.discardingOversizedLine) emitLine(stream, state.content.join(""), true, enqueue);
73
+ resetLine(state);
74
+ continue;
75
+ }
76
+
77
+ if (state.discardingOversizedLine) continue;
78
+
79
+ if (state.pendingCarriageReturn) {
80
+ state.pendingCarriageReturn = false;
81
+ appendCodeUnit("\r", stream, state, enqueue);
82
+ if (state.discardingOversizedLine) continue;
83
+ }
84
+
85
+ if (codeUnit === "\r") state.pendingCarriageReturn = true;
86
+ else appendCodeUnit(codeUnit, stream, state, enqueue);
87
+ }
88
+ }
89
+
90
+ function appendCodeUnit(codeUnit, stream, state, enqueue) {
91
+ if (state.contentLength >= MAX_LINE_CODE_UNITS) {
92
+ state.content = [];
93
+ state.contentLength = 0;
94
+ state.pendingCarriageReturn = false;
95
+ state.discardingOversizedLine = true;
96
+ enqueue(stream, Buffer.from(`[feature-factory] oversized ${stream} line redacted\n`, "utf8"));
97
+ return;
98
+ }
99
+ state.content.push(codeUnit);
100
+ state.contentLength += 1;
101
+ }
102
+
103
+ function flushFinalFragment(stream, state, enqueue) {
104
+ if (state.discardingOversizedLine) return;
105
+ if (state.pendingCarriageReturn) {
106
+ state.pendingCarriageReturn = false;
107
+ appendCodeUnit("\r", stream, state, enqueue);
108
+ }
109
+ if (!state.discardingOversizedLine && state.contentLength > 0) {
110
+ emitLine(stream, state.content.join(""), false, enqueue);
111
+ }
112
+ resetLine(state);
113
+ }
114
+
115
+ function emitLine(stream, content, delimited, enqueue) {
116
+ let rendered;
117
+ try {
118
+ rendered = renderTerminalSegments([freeformSegment(content)]);
119
+ } catch {
120
+ rendered = SAFE_OUTPUT_FALLBACK;
121
+ }
122
+ enqueue(stream, Buffer.from(delimited ? `${rendered}\n` : rendered, "utf8"));
123
+ }
124
+
125
+ function resetLine(state) {
126
+ state.content = [];
127
+ state.contentLength = 0;
128
+ state.pendingCarriageReturn = false;
129
+ state.discardingOversizedLine = false;
130
+ }
131
+
132
+ function endpointFinished(endpoint) {
133
+ const completion = new Promise((resolve, reject) => {
134
+ endpoint.once("finish", resolve);
135
+ endpoint.once("error", reject);
136
+ });
137
+ completion.catch(() => {});
138
+ return completion;
139
+ }
@@ -0,0 +1,365 @@
1
+ import {
2
+ REDACTED_VALUE,
3
+ SCRUB_MARKERS,
4
+ isSensitiveKey,
5
+ scrubSensitiveData,
6
+ scrubSensitiveString,
7
+ } from "./sensitive-data.js";
8
+ import { encodeTerminalText, serializeTerminalJson } from "./terminal-encoding.js";
9
+
10
+ export const SAFE_OUTPUT_FALLBACK = "Output rendering failed safely.";
11
+ export const SAFE_GENERIC_ERROR_DETAIL = "An unexpected error occurred.";
12
+ export const DIAGNOSTIC_FREEFORM_FIELDS = Object.freeze([
13
+ "error", "message", "reason", "detail", "summary", "action",
14
+ ]);
15
+
16
+ const OUTPUT_POLICY_ERROR_CODE = "ERR_OUTPUT_POLICY";
17
+ const segmentBrand = new WeakSet();
18
+ const structuredErrorBrand = new WeakSet();
19
+ const structuredErrorSegments = new WeakMap();
20
+ const TRUSTED_FRAMING_TEXT = Object.freeze({
21
+ EMPTY: "", SPACE: " ", COLON_SPACE: ": ", COMMA_SPACE: ", ",
22
+ DASH_SEPARATOR: " - ", SLASH: "/", EQUALS: "=", TAB: "\t",
23
+ LINE_FEED: "\n", OPEN_PAREN: "(", CLOSE_PAREN: ")",
24
+ OPEN_BRACKET: "[", CLOSE_BRACKET: "]", ERROR_PREFIX: "error: ",
25
+ WARNING_PREFIX: "warning: ",
26
+ });
27
+
28
+ export const TRUSTED_SEGMENTS = createTrustedSegments();
29
+
30
+ export class OutputPolicyError extends Error {
31
+ constructor(stage = "output") {
32
+ super(SAFE_OUTPUT_FALLBACK);
33
+ this.name = "OutputPolicyError";
34
+ this.code = OUTPUT_POLICY_ERROR_CODE;
35
+ this.stage = safeStage(stage);
36
+ }
37
+ }
38
+
39
+ export class StructuredOutputError extends Error {
40
+ constructor(message, segments) {
41
+ if (typeof message !== "string") fail("error");
42
+ const normalized = normalizeSegments(segments);
43
+ super(message);
44
+ Object.defineProperty(this, "name", {
45
+ value: "StructuredOutputError", enumerable: false, configurable: false, writable: false,
46
+ });
47
+ structuredErrorBrand.add(this);
48
+ structuredErrorSegments.set(this, normalized);
49
+ }
50
+
51
+ toString() {
52
+ return renderTerminalSegmentsOrFallback(structuredErrorSegments.get(this));
53
+ }
54
+ }
55
+
56
+ // Trusted framing is constructed only from the repository literals above. Callers
57
+ // select a named preconstructed segment; no runtime text construction API exists.
58
+ export function trustedSegment(name) {
59
+ try {
60
+ if (typeof name !== "string") fail("segment");
61
+ const descriptor = Reflect.getOwnPropertyDescriptor(TRUSTED_SEGMENTS, name);
62
+ if (!isDataDescriptor(descriptor) || !isOutputSegment(descriptor.value)) fail("segment");
63
+ return descriptor.value;
64
+ } catch (error) {
65
+ if (error instanceof OutputPolicyError) throw error;
66
+ fail("segment");
67
+ }
68
+ }
69
+
70
+ export function identitySegment(value) { return makeSegment("identity", value); }
71
+ export function freeformSegment(value) { return makeSegment("freeform", value); }
72
+
73
+ export function isOutputSegment(value) {
74
+ return (typeof value === "object" || typeof value === "function")
75
+ && value !== null && segmentBrand.has(value);
76
+ }
77
+
78
+ export function renderTerminalSegments(segments) {
79
+ try {
80
+ return normalizeSegments(segments).map(renderSegment).join("");
81
+ } catch (error) {
82
+ if (error instanceof OutputPolicyError) throw error;
83
+ fail("render");
84
+ }
85
+ }
86
+
87
+ export function renderTerminalSegmentsOrFallback(segments) {
88
+ try { return renderTerminalSegments(segments); } catch { return SAFE_OUTPUT_FALLBACK; }
89
+ }
90
+
91
+ export function projectFreeformData(value) {
92
+ try { return scrubSensitiveData(value, { mode: "baseline" }); } catch { fail("projection"); }
93
+ }
94
+
95
+ export function projectDiagnosticData(value, options = undefined) {
96
+ try {
97
+ const policy = readDiagnosticPolicy(options);
98
+ return projectDiagnosticValue(value, policy, [], 0, createDiagnosticState(policy)).value;
99
+ } catch (error) {
100
+ if (error instanceof OutputPolicyError) throw error;
101
+ fail("projection");
102
+ }
103
+ }
104
+
105
+ export function safeGenericErrorDetail(error) {
106
+ let detail = SAFE_GENERIC_ERROR_DETAIL;
107
+ try {
108
+ if (typeof error === "string") detail = error;
109
+ else if ((typeof error === "object" || typeof error === "function") && error !== null) {
110
+ const descriptor = Reflect.getOwnPropertyDescriptor(error, "message");
111
+ if (descriptor && Object.hasOwn(descriptor, "value") && typeof descriptor.value === "string") {
112
+ detail = descriptor.value;
113
+ }
114
+ }
115
+ } catch { detail = SAFE_GENERIC_ERROR_DETAIL; }
116
+ try { return scrubSensitiveString(detail, { mode: "baseline" }); }
117
+ catch { return SAFE_GENERIC_ERROR_DETAIL; }
118
+ }
119
+
120
+ export function errorOutputSegments(error) {
121
+ try {
122
+ if (structuredErrorBrand.has(error)) return structuredErrorSegments.get(error);
123
+ } catch { /* Treat malformed or hostile error-like values as unstructured freeform. */ }
124
+ return Object.freeze([freeformSegment(safeGenericErrorDetail(error))]);
125
+ }
126
+
127
+ export function renderErrorForTerminal(error) {
128
+ return renderTerminalSegmentsOrFallback(errorOutputSegments(error));
129
+ }
130
+
131
+ function makeSegment(kind, value) {
132
+ const segment = Object.freeze({ kind, value });
133
+ segmentBrand.add(segment);
134
+ return segment;
135
+ }
136
+
137
+ function createTrustedSegments() {
138
+ const output = Object.create(null);
139
+ for (const name of Object.keys(TRUSTED_FRAMING_TEXT)) {
140
+ defineData(output, name, makeSegment("trusted", TRUSTED_FRAMING_TEXT[name]));
141
+ }
142
+ return Object.freeze(output);
143
+ }
144
+
145
+ function renderSegment(segment) {
146
+ if (segment.kind === "trusted") return segment.value;
147
+ if (segment.kind === "identity") return encodeTerminalText(segment.value, { profile: "ascii-identity" });
148
+ const scrubbed = projectFreeformData(segment.value);
149
+ if (scrubbed !== null && typeof scrubbed === "object") return serializeTerminalJson(scrubbed);
150
+ return encodeTerminalText(scrubbed, { profile: "unicode-prose" });
151
+ }
152
+
153
+ function normalizeSegments(segments) {
154
+ try {
155
+ if (!Array.isArray(segments) || Object.getPrototypeOf(segments) !== Array.prototype) fail("segment");
156
+ const keys = Reflect.ownKeys(segments);
157
+ const lengthDescriptor = Reflect.getOwnPropertyDescriptor(segments, "length");
158
+ if (!isDataDescriptor(lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value)
159
+ || lengthDescriptor.value < 0 || keys.length !== lengthDescriptor.value + 1) fail("segment");
160
+ const normalized = [];
161
+ for (let index = 0; index < lengthDescriptor.value; index += 1) {
162
+ const descriptor = Reflect.getOwnPropertyDescriptor(segments, String(index));
163
+ if (!isDataDescriptor(descriptor) || descriptor.enumerable !== true
164
+ || !isOutputSegment(descriptor.value)) fail("segment");
165
+ normalized.push(descriptor.value);
166
+ }
167
+ return Object.freeze(normalized);
168
+ } catch (error) {
169
+ if (error instanceof OutputPolicyError) throw error;
170
+ fail("segment");
171
+ }
172
+ }
173
+
174
+ function readDiagnosticPolicy(options) {
175
+ if (options === undefined) return defaultDiagnosticPolicy();
176
+ if (options === null || (typeof options !== "object" && typeof options !== "function")) fail("projection");
177
+ const fields = options.freeformFields === undefined ? DIAGNOSTIC_FREEFORM_FIELDS : options.freeformFields;
178
+ if (!Array.isArray(fields) || fields.some((field) => typeof field !== "string")) fail("projection");
179
+ const paths = options.validatedIdentityPaths === undefined ? [] : options.validatedIdentityPaths;
180
+ if (!Array.isArray(paths)) fail("projection");
181
+ return {
182
+ freeformFields: new Set(fields),
183
+ validatedIdentityPaths: paths.map(readIdentityPath),
184
+ maxDepth: readDiagnosticBound(options, "maxDepth", 32),
185
+ maxNodes: readDiagnosticBound(options, "maxNodes", 10_000),
186
+ maxKeys: readDiagnosticBound(options, "maxKeys", 10_000),
187
+ maxArrayLength: readDiagnosticBound(options, "maxArrayLength", 10_000, 2 ** 32 - 1, 1),
188
+ maxStringLength: readDiagnosticBound(options, "maxStringLength", 65_536),
189
+ };
190
+ }
191
+
192
+ function defaultDiagnosticPolicy() {
193
+ return {
194
+ freeformFields: new Set(DIAGNOSTIC_FREEFORM_FIELDS), validatedIdentityPaths: [],
195
+ maxDepth: 32, maxNodes: 10_000, maxKeys: 10_000,
196
+ maxArrayLength: 10_000, maxStringLength: 65_536,
197
+ };
198
+ }
199
+
200
+ function readDiagnosticBound(options, name, fallback, maximum = Number.MAX_SAFE_INTEGER, minimum = 0) {
201
+ const value = options[name] === undefined ? fallback : options[name];
202
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) fail("projection");
203
+ return value;
204
+ }
205
+
206
+ function readIdentityPath(path) {
207
+ if (!Array.isArray(path) || path.length === 0) fail("projection");
208
+ const normalized = [];
209
+ for (let index = 0; index < path.length; index += 1) {
210
+ const descriptor = Reflect.getOwnPropertyDescriptor(path, String(index));
211
+ if (!isDataDescriptor(descriptor) || typeof descriptor.value !== "string"
212
+ || descriptor.value.length === 0) fail("projection");
213
+ normalized.push(descriptor.value);
214
+ }
215
+ if (Reflect.ownKeys(path).length !== path.length + 1) fail("projection");
216
+ return Object.freeze(normalized);
217
+ }
218
+
219
+ function createDiagnosticState(policy) {
220
+ return { nodes: 0, keys: 0, active: new WeakSet(), completed: new WeakSet(), ...policy };
221
+ }
222
+
223
+ function projectDiagnosticValue(value, policy, path, depth, state) {
224
+ if (state.nodes >= state.maxNodes) return { value: SCRUB_MARKERS.truncated, stop: true };
225
+ state.nodes += 1;
226
+ if (isValidatedIdentityPath(policy.validatedIdentityPaths, path)) {
227
+ return { value: validatedIdentityScalar(value), stop: false };
228
+ }
229
+ if (value === null || typeof value === "boolean") return { value, stop: false };
230
+ if (typeof value === "string") {
231
+ return { value: value.length > state.maxStringLength
232
+ ? REDACTED_VALUE : scrubSensitiveString(value, { mode: "baseline" }), stop: false };
233
+ }
234
+ if (typeof value === "number") {
235
+ return { value: Number.isFinite(value) && !Object.is(value, -0)
236
+ ? value : SCRUB_MARKERS.unsupported, stop: false };
237
+ }
238
+ if (typeof value !== "object") return { value: SCRUB_MARKERS.unsupported, stop: false };
239
+ let array;
240
+ let prototype;
241
+ try { array = Array.isArray(value); prototype = Object.getPrototypeOf(value); }
242
+ catch { return { value: SCRUB_MARKERS.unavailable, stop: false }; }
243
+ if ((array && prototype !== Array.prototype)
244
+ || (!array && prototype !== Object.prototype && prototype !== null)) {
245
+ return { value: SCRUB_MARKERS.unsupported, stop: false };
246
+ }
247
+ if (depth >= state.maxDepth) return { value: SCRUB_MARKERS.truncated, stop: false };
248
+ if (state.active.has(value)) return { value: SCRUB_MARKERS.circular, stop: false };
249
+ if (state.completed.has(value)) return { value: SCRUB_MARKERS.repeated, stop: false };
250
+ state.active.add(value);
251
+ let result;
252
+ try {
253
+ result = array
254
+ ? projectDiagnosticArray(value, policy, path, depth, state)
255
+ : projectDiagnosticObject(value, policy, path, depth, state);
256
+ } catch (error) {
257
+ if (error instanceof OutputPolicyError) throw error;
258
+ result = { value: SCRUB_MARKERS.unavailable, stop: false };
259
+ }
260
+ state.active.delete(value);
261
+ state.completed.add(value);
262
+ return result;
263
+ }
264
+
265
+ function projectDiagnosticArray(value, policy, path, depth, state) {
266
+ let keys;
267
+ let lengthDescriptor;
268
+ try { keys = Reflect.ownKeys(value); lengthDescriptor = Reflect.getOwnPropertyDescriptor(value, "length"); }
269
+ catch { return { value: SCRUB_MARKERS.unavailable, stop: false }; }
270
+ if (!isDataDescriptor(lengthDescriptor) || !Number.isInteger(lengthDescriptor.value)
271
+ || lengthDescriptor.value < 0 || lengthDescriptor.value > 2 ** 32 - 1
272
+ || keys.length !== lengthDescriptor.value + 1) fail("projection");
273
+ const sourceLength = lengthDescriptor.value;
274
+ const outputLength = Math.min(sourceLength, state.maxArrayLength);
275
+ const forcedTruncationIndex = sourceLength > state.maxArrayLength ? outputLength - 1 : -1;
276
+ const output = [];
277
+ for (let index = 0; index < outputLength; index += 1) {
278
+ if (index === forcedTruncationIndex) {
279
+ defineData(output, String(index), SCRUB_MARKERS.truncated);
280
+ return { value: output, stop: false };
281
+ }
282
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, String(index));
283
+ if (!isDataDescriptor(descriptor) || descriptor.enumerable !== true) fail("projection");
284
+ if (state.keys >= state.maxKeys) {
285
+ defineData(output, String(index), SCRUB_MARKERS.truncated);
286
+ return { value: output, stop: true };
287
+ }
288
+ state.keys += 1;
289
+ const projected = projectDiagnosticValue(
290
+ descriptor.value, policy, [...path, String(index)], depth + 1, state,
291
+ );
292
+ defineData(output, String(index), projected.value);
293
+ if (projected.stop) return { value: output, stop: true };
294
+ }
295
+ return { value: output, stop: false };
296
+ }
297
+
298
+ function projectDiagnosticObject(value, policy, path, depth, state) {
299
+ let keys;
300
+ try { keys = Reflect.ownKeys(value); }
301
+ catch { return { value: SCRUB_MARKERS.unavailable, stop: false }; }
302
+ const output = Object.create(null);
303
+ const allocated = new Set();
304
+ for (const key of keys) {
305
+ if (typeof key !== "string") continue;
306
+ const classified = key.length > state.maxStringLength
307
+ || isSensitiveKey(key, { mode: "baseline" });
308
+ let descriptor;
309
+ try { descriptor = Reflect.getOwnPropertyDescriptor(value, key); }
310
+ catch { descriptor = undefined; }
311
+ if (descriptor && descriptor.enumerable !== true) continue;
312
+ if (state.keys >= state.maxKeys) {
313
+ addDiagnosticTruncation(output, allocated);
314
+ return { value: output, stop: true };
315
+ }
316
+ state.keys += 1;
317
+ if (classified) continue;
318
+ if (key === "toJSON") fail("projection");
319
+ allocated.add(key);
320
+ if (!isDataDescriptor(descriptor)) {
321
+ defineData(output, key, SCRUB_MARKERS.unavailable);
322
+ continue;
323
+ }
324
+ const projected = projectDiagnosticValue(
325
+ descriptor.value, policy, [...path, key], depth + 1, state,
326
+ );
327
+ defineData(output, key, projected.value);
328
+ if (projected.stop) return { value: output, stop: true };
329
+ }
330
+ return { value: output, stop: false };
331
+ }
332
+
333
+ function addDiagnosticTruncation(output, allocated) {
334
+ let key = SCRUB_MARKERS.truncated;
335
+ for (let suffix = 2; allocated.has(key); suffix += 1) key = `${SCRUB_MARKERS.truncated}#${suffix}`;
336
+ defineData(output, key, SCRUB_MARKERS.truncated);
337
+ }
338
+
339
+ function validatedIdentityScalar(value) {
340
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
341
+ if (typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0)) return value;
342
+ fail("projection");
343
+ }
344
+
345
+ function isValidatedIdentityPath(patterns, path) {
346
+ return patterns.some((pattern) => pattern.length === path.length && pathMatches(pattern, path));
347
+ }
348
+
349
+ function pathMatches(pattern, path) {
350
+ return pattern.every((part, index) => part === "*" || part === path[index]);
351
+ }
352
+
353
+ function isDataDescriptor(descriptor) {
354
+ return descriptor !== undefined && Object.hasOwn(descriptor, "value");
355
+ }
356
+
357
+ function defineData(target, key, value) {
358
+ Object.defineProperty(target, key, { value, enumerable: true, configurable: true, writable: true });
359
+ }
360
+
361
+ function safeStage(stage) {
362
+ return ["segment", "render", "projection", "error", "output"].includes(stage) ? stage : "output";
363
+ }
364
+
365
+ function fail(stage) { throw new OutputPolicyError(stage); }