agentwrangler 0.1.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 (157) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +116 -0
  3. package/dist/apply/jobs.js +429 -0
  4. package/dist/apply/open-terminal-child.mjs +98 -0
  5. package/dist/apply/open-terminal.js +221 -0
  6. package/dist/apply/settings-gen.js +35 -0
  7. package/dist/cli/agentwrangler.js +18 -0
  8. package/dist/daemon/config.js +51 -0
  9. package/dist/daemon/http.js +258 -0
  10. package/dist/daemon/index.js +372 -0
  11. package/dist/daemon/outcomes-pass.js +82 -0
  12. package/dist/daemon/readiness.js +15 -0
  13. package/dist/daemon/router.js +756 -0
  14. package/dist/daemon/static.js +146 -0
  15. package/dist/db/migrate.js +72 -0
  16. package/dist/db/migrations/001_observe.sql +196 -0
  17. package/dist/db/migrations/002_indexes.sql +6 -0
  18. package/dist/db/migrations/003_context_inventory_history.sql +20 -0
  19. package/dist/db/migrations/004_apply_jobs.sql +17 -0
  20. package/dist/db/migrations/005_tool_event_metadata.sql +17 -0
  21. package/dist/db/migrations/006_d7_query_indexes.sql +9 -0
  22. package/dist/db/migrations/007_work_item_branch_keys.sql +11 -0
  23. package/dist/db/migrations/008_thinking_tokens.sql +1 -0
  24. package/dist/db/migrations/009_user_turn_count.sql +1 -0
  25. package/dist/db/migrations/010_workspace_cwd.sql +1 -0
  26. package/dist/db/migrations/011_reports.sql +1 -0
  27. package/dist/db/migrations/012_reconcile_indexes.sql +2 -0
  28. package/dist/db/migrations/013_friction_fields.sql +5 -0
  29. package/dist/db/migrations/014_session_churn.sql +11 -0
  30. package/dist/db/migrations/015_gap_aggregates.sql +6 -0
  31. package/dist/db/open.js +30 -0
  32. package/dist/detector/benchmark-anchors.js +36 -0
  33. package/dist/detector/calibration.js +302 -0
  34. package/dist/detector/context-history-retention.js +312 -0
  35. package/dist/detector/context-probe.js +574 -0
  36. package/dist/detector/d1-source-identity.js +25 -0
  37. package/dist/detector/detectors/d10_catalog_footprint.js +146 -0
  38. package/dist/detector/detectors/d1_ctx_always_loaded.js +203 -0
  39. package/dist/detector/detectors/d2_session_long_full_context.js +119 -0
  40. package/dist/detector/detectors/d4_model_mismatch.js +258 -0
  41. package/dist/detector/detectors/d5_limit_burn_forecast.js +138 -0
  42. package/dist/detector/detectors/d6_tool_result_bloat.js +301 -0
  43. package/dist/detector/detectors/d7_loop_retry_waste.js +345 -0
  44. package/dist/detector/detectors/d8_cache_write_churn.js +201 -0
  45. package/dist/detector/detectors/d9_idle_background_session.js +101 -0
  46. package/dist/detector/engine.js +88 -0
  47. package/dist/detector/index.js +17 -0
  48. package/dist/detector/measurement.js +426 -0
  49. package/dist/detector/practice-registry.js +259 -0
  50. package/dist/detector/registry.js +32 -0
  51. package/dist/detector/savings.js +249 -0
  52. package/dist/detector/types.js +14 -0
  53. package/dist/evidence/common/approved-input.js +632 -0
  54. package/dist/evidence/common/boundary.js +84 -0
  55. package/dist/evidence/common/canonical.js +55 -0
  56. package/dist/evidence/common/redaction.js +321 -0
  57. package/dist/evidence/common/sqlite.js +25 -0
  58. package/dist/evidence/common/state.js +29 -0
  59. package/dist/evidence/cond1/cli.js +289 -0
  60. package/dist/evidence/cond1/packet.js +407 -0
  61. package/dist/evidence/cond1/prepare.js +295 -0
  62. package/dist/evidence/cond1/score.js +349 -0
  63. package/dist/evidence/cond1/types.js +1 -0
  64. package/dist/evidence/create-approval.js +365 -0
  65. package/dist/evidence/create-scratch.js +542 -0
  66. package/dist/evidence/d7/cli.js +113 -0
  67. package/dist/evidence/d7/measure.js +193 -0
  68. package/dist/evidence/d7/types.js +1 -0
  69. package/dist/evidence/discover-approval.js +492 -0
  70. package/dist/evidence/g2/adjudicate.js +20 -0
  71. package/dist/evidence/g2/cli.js +207 -0
  72. package/dist/evidence/g2/kappa.js +39 -0
  73. package/dist/evidence/g2/pipeline.js +92 -0
  74. package/dist/evidence/g2/store.js +14 -0
  75. package/dist/evidence/github/client.js +1 -0
  76. package/dist/evidence/github/gh-cli-client.js +301 -0
  77. package/dist/evidence/r3/cli.js +209 -0
  78. package/dist/evidence/r3/evaluate.js +417 -0
  79. package/dist/evidence/r3/packet.js +162 -0
  80. package/dist/evidence/r3/prepare.js +405 -0
  81. package/dist/evidence/r3/score.js +341 -0
  82. package/dist/evidence/r3/transcript.js +155 -0
  83. package/dist/evidence/r3/types.js +4 -0
  84. package/dist/hook/context-budget-hook.mjs +138 -0
  85. package/dist/hook/danger-guard-denylist.json +27 -0
  86. package/dist/hook/danger-guard-hook.mjs +167 -0
  87. package/dist/hook/install.js +0 -0
  88. package/dist/hook/limit-burn-hook.mjs +127 -0
  89. package/dist/hook/loop-guard-hook.mjs +104 -0
  90. package/dist/hook/precompact-checkpoint-hook.mjs +123 -0
  91. package/dist/ingest/churn-collector.js +122 -0
  92. package/dist/ingest/detector-hook.js +52 -0
  93. package/dist/ingest/discovery.js +207 -0
  94. package/dist/ingest/health.js +43 -0
  95. package/dist/ingest/index.js +28 -0
  96. package/dist/ingest/ingestor.js +509 -0
  97. package/dist/ingest/parser.js +344 -0
  98. package/dist/ingest/pricing.js +153 -0
  99. package/dist/ingest/reconcile.js +52 -0
  100. package/dist/ingest/tail.js +152 -0
  101. package/dist/ingest/types.js +24 -0
  102. package/dist/ingest/workspace-mapping.js +114 -0
  103. package/dist/oauth/anthropic-api-key.js +88 -0
  104. package/dist/oauth/count-tokens.js +86 -0
  105. package/dist/oauth/credentials.js +171 -0
  106. package/dist/oauth/judge-g2-client.js +154 -0
  107. package/dist/oauth/usage.js +167 -0
  108. package/dist/outcomes/branch-key.js +49 -0
  109. package/dist/outcomes/conclusions.js +45 -0
  110. package/dist/outcomes/derive.js +94 -0
  111. package/dist/outcomes/finding-extractors.js +131 -0
  112. package/dist/outcomes/findings.js +237 -0
  113. package/dist/outcomes/github/client.js +367 -0
  114. package/dist/outcomes/github/credential.js +195 -0
  115. package/dist/outcomes/github/gh-cli-client.js +340 -0
  116. package/dist/outcomes/linker.js +486 -0
  117. package/dist/outcomes/pool.js +24 -0
  118. package/dist/outcomes/sync.js +276 -0
  119. package/dist/query/api/agents-liveness.js +182 -0
  120. package/dist/query/api/burn-status.js +50 -0
  121. package/dist/query/api/context-budget.js +114 -0
  122. package/dist/query/api/context-composition.js +67 -0
  123. package/dist/query/api/cost-per-success.js +104 -0
  124. package/dist/query/api/delivery.js +92 -0
  125. package/dist/query/api/effectiveness.js +254 -0
  126. package/dist/query/api/efficiency-headroom.js +74 -0
  127. package/dist/query/api/headroom-trend.js +105 -0
  128. package/dist/query/api/hook-config.js +75 -0
  129. package/dist/query/api/hook-install.js +8 -0
  130. package/dist/query/api/hot-sessions.js +17 -0
  131. package/dist/query/api/idle-sessions.js +52 -0
  132. package/dist/query/api/index.js +40 -0
  133. package/dist/query/api/loop-guard.js +90 -0
  134. package/dist/query/api/offload-share.js +41 -0
  135. package/dist/query/api/outcomes.js +218 -0
  136. package/dist/query/api/overview.js +535 -0
  137. package/dist/query/api/rec-prompt.js +138 -0
  138. package/dist/query/api/recommendations-ledger.js +111 -0
  139. package/dist/query/api/recommendations.js +514 -0
  140. package/dist/query/api/reports.js +78 -0
  141. package/dist/query/api/self-churn.js +77 -0
  142. package/dist/query/api/self-percentiles.js +109 -0
  143. package/dist/query/api/session-drivers.js +153 -0
  144. package/dist/query/api/settings.js +85 -0
  145. package/dist/query/api/spend-flavor.js +234 -0
  146. package/dist/query/api/trends.js +155 -0
  147. package/dist/query/cap-weighted.js +119 -0
  148. package/dist/query/db-context.js +42 -0
  149. package/dist/query/envelope.js +71 -0
  150. package/dist/query/forecast.js +191 -0
  151. package/dist/query/settings-store.js +441 -0
  152. package/dist/query/spend.js +171 -0
  153. package/dist/query/trends.js +194 -0
  154. package/dist/ui/assets/index-DnRKgc21.css +1 -0
  155. package/dist/ui/assets/index-h1Q1wWq5.js +168 -0
  156. package/dist/ui/index.html +39 -0
  157. package/package.json +59 -0
@@ -0,0 +1,365 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { EvidenceBoundaryError, assertScratchBoundary, resolveProspectivePath, } from "./common/boundary.js";
6
+ import { canonicalJson, sha256Bytes } from "./common/canonical.js";
7
+ import { SCRATCH_APPROVAL_VERSION, } from "./create-scratch.js";
8
+ const REPOSITORY_ROOT = path.resolve(fileURLToPath(new URL("../../", import.meta.url)));
9
+ const SAFE_REPORT_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/u;
10
+ const PROTOTYPE_NAMES = new Set(["__proto__", "constructor", "prototype"]);
11
+ function refuse(code) {
12
+ throw new EvidenceBoundaryError(code);
13
+ }
14
+ function refuseRetained(code, artifacts) {
15
+ refuse(`${code}_${artifacts}_retained`);
16
+ }
17
+ function isPlainRecord(value) {
18
+ if (value === null || typeof value !== "object" || Array.isArray(value))
19
+ return false;
20
+ const prototype = Object.getPrototypeOf(value);
21
+ return prototype === Object.prototype || prototype === null;
22
+ }
23
+ function assertExactKeys(value, expected, code) {
24
+ const actual = Object.keys(value).sort();
25
+ const wanted = [...expected].sort();
26
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
27
+ refuse(code);
28
+ }
29
+ }
30
+ function nonemptyString(value, code) {
31
+ if (typeof value !== "string" ||
32
+ value.length === 0 ||
33
+ [...value].some((character) => {
34
+ const codePoint = character.codePointAt(0);
35
+ return codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f);
36
+ })) {
37
+ refuse(code);
38
+ }
39
+ return value;
40
+ }
41
+ function reportAlias(value) {
42
+ const parsed = nonemptyString(value, "approval_report_alias_invalid");
43
+ if (!SAFE_REPORT_ALIAS.test(parsed) ||
44
+ parsed.endsWith(".") ||
45
+ parsed.includes("..") ||
46
+ PROTOTYPE_NAMES.has(parsed)) {
47
+ refuse("approval_report_alias_invalid");
48
+ }
49
+ return parsed;
50
+ }
51
+ function binaryCompare(left, right) {
52
+ return left < right ? -1 : left > right ? 1 : 0;
53
+ }
54
+ function parseRepositories(value) {
55
+ if (!Array.isArray(value) || value.length === 0) {
56
+ refuse("approval_repository_allowlist_invalid");
57
+ }
58
+ const workspaceIds = new Set();
59
+ const reportAliases = new Set();
60
+ const canonicalRepositories = new Set();
61
+ const repositories = value.map((entry) => {
62
+ if (!isPlainRecord(entry))
63
+ refuse("approval_repository_invalid");
64
+ assertExactKeys(entry, ["workspaceId", "owner", "repo", "reportAlias"], "approval_repository_unknown_or_missing_key");
65
+ const parsed = {
66
+ workspaceId: nonemptyString(entry.workspaceId, "approval_workspace_id_invalid"),
67
+ owner: nonemptyString(entry.owner, "approval_owner_invalid"),
68
+ repo: nonemptyString(entry.repo, "approval_repo_invalid"),
69
+ reportAlias: reportAlias(entry.reportAlias),
70
+ };
71
+ if (workspaceIds.has(parsed.workspaceId))
72
+ refuse("approval_workspace_duplicate");
73
+ if (reportAliases.has(parsed.reportAlias))
74
+ refuse("approval_report_alias_duplicate");
75
+ const canonicalRepository = `${parsed.owner.toLowerCase()}/${parsed.repo.toLowerCase()}`;
76
+ if (canonicalRepositories.has(canonicalRepository))
77
+ refuse("approval_repository_duplicate");
78
+ workspaceIds.add(parsed.workspaceId);
79
+ reportAliases.add(parsed.reportAlias);
80
+ canonicalRepositories.add(canonicalRepository);
81
+ return parsed;
82
+ });
83
+ return repositories.sort((left, right) => binaryCompare(left.workspaceId, right.workspaceId));
84
+ }
85
+ /** Build the exact v1 approval shape without filesystem, database, GitHub, or transcript access. */
86
+ export function buildScratchApprovalManifest(input) {
87
+ if (input.privateArtifactParentAcknowledged !== true) {
88
+ refuse("private_artifact_parent_not_acknowledged");
89
+ }
90
+ return {
91
+ version: SCRATCH_APPROVAL_VERSION,
92
+ sourceDbPath: nonemptyString(input.sourceDbPath, "approval_source_path_invalid"),
93
+ scratchDbPath: nonemptyString(input.scratchDbPath, "approval_scratch_path_invalid"),
94
+ scratchStatePath: nonemptyString(input.scratchStatePath, "approval_state_path_invalid"),
95
+ repositories: parseRepositories(input.repositories),
96
+ privateArtifactParentAcknowledged: true,
97
+ };
98
+ }
99
+ function isWithin(parent, child) {
100
+ const relative = path.relative(parent, child);
101
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== "..");
102
+ }
103
+ function comparisonPath(value) {
104
+ const normalized = path.normalize(value).replace(/[\\/]+$/u, "");
105
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
106
+ }
107
+ function parseRepositoriesJson(value) {
108
+ try {
109
+ return JSON.parse(value);
110
+ }
111
+ catch {
112
+ refuse("approval_repositories_json_invalid");
113
+ }
114
+ }
115
+ function resolveOutputPath(outputPath, repositoryRoot) {
116
+ if (outputPath.length === 0)
117
+ refuse("approval_output_path_invalid");
118
+ const resolved = resolveProspectivePath(outputPath);
119
+ if (isWithin(repositoryRoot, resolved))
120
+ refuse("approval_output_inside_repository");
121
+ if (fs.existsSync(resolved))
122
+ refuse("approval_output_already_exists");
123
+ const parent = path.dirname(resolved);
124
+ let parentStat;
125
+ try {
126
+ parentStat = fs.statSync(parent);
127
+ }
128
+ catch {
129
+ refuse("approval_output_parent_missing");
130
+ }
131
+ if (!parentStat.isDirectory())
132
+ refuse("approval_output_parent_not_directory");
133
+ return resolved;
134
+ }
135
+ async function publishNoReplace(outputPath, bytes, beforePublish, afterPublishedRead) {
136
+ const temporaryPath = path.join(path.dirname(outputPath), `.${path.basename(outputPath)}.${process.pid}.${randomUUID()}.candidate`);
137
+ const descriptor = fs.openSync(temporaryPath, "wx", 0o600);
138
+ try {
139
+ try {
140
+ fs.writeFileSync(descriptor, bytes, { encoding: "utf8" });
141
+ fs.fsyncSync(descriptor);
142
+ }
143
+ catch {
144
+ refuseRetained("approval_output_write_or_fsync_failed", "candidate");
145
+ }
146
+ const temporaryIdentity = fileDescriptorIdentity(descriptor, "approval_output_temporary_identity_failed", "candidate");
147
+ await beforePublish?.(outputPath, temporaryPath);
148
+ try {
149
+ fs.linkSync(temporaryPath, outputPath);
150
+ }
151
+ catch (error) {
152
+ if (error.code === "EEXIST") {
153
+ refuseRetained("approval_output_race_lost", "candidate");
154
+ }
155
+ refuseRetained("approval_output_publish_failed", "candidate");
156
+ }
157
+ await verifyPublishedOutput(outputPath, temporaryIdentity, bytes, afterPublishedRead);
158
+ }
159
+ catch (error) {
160
+ try {
161
+ fs.closeSync(descriptor);
162
+ }
163
+ catch {
164
+ const outputExists = fs.existsSync(outputPath);
165
+ refuseRetained("approval_output_close_failed", outputExists ? "output_and_candidate" : "candidate");
166
+ }
167
+ throw error;
168
+ }
169
+ try {
170
+ fs.closeSync(descriptor);
171
+ }
172
+ catch {
173
+ refuseRetained("approval_output_close_failed", "output_and_candidate");
174
+ }
175
+ try {
176
+ fs.unlinkSync(temporaryPath);
177
+ }
178
+ catch {
179
+ refuseRetained("approval_output_unlink_failed", "output_and_candidate");
180
+ }
181
+ return sha256Bytes(bytes);
182
+ }
183
+ function fileDescriptorIdentity(descriptor, failure, artifacts) {
184
+ let stat;
185
+ try {
186
+ stat = fs.fstatSync(descriptor, { bigint: true });
187
+ }
188
+ catch {
189
+ refuseRetained(failure, artifacts);
190
+ }
191
+ if (!stat.isFile() || stat.ino === 0n)
192
+ refuseRetained(failure, artifacts);
193
+ return { device: stat.dev, inode: stat.ino, size: stat.size };
194
+ }
195
+ function sameDescriptorIdentity(left, right) {
196
+ return left.device === right.device && left.inode === right.inode && left.size === right.size;
197
+ }
198
+ async function verifyPublishedOutput(outputPath, temporaryIdentity, bytes, afterPublishedRead) {
199
+ let pathStat;
200
+ try {
201
+ pathStat = fs.lstatSync(outputPath, { bigint: true });
202
+ }
203
+ catch {
204
+ refuseRetained("approval_output_lstat_failed", "output_and_candidate");
205
+ }
206
+ if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
207
+ refuseRetained("approval_output_not_regular_file", "output_and_candidate");
208
+ }
209
+ let publishedDescriptor;
210
+ try {
211
+ publishedDescriptor = fs.openSync(outputPath, "r");
212
+ }
213
+ catch {
214
+ refuseRetained("approval_output_open_failed", "output_and_candidate");
215
+ }
216
+ try {
217
+ const publishedIdentity = fileDescriptorIdentity(publishedDescriptor, "approval_output_identity_failed", "output_and_candidate");
218
+ const pathIdentity = {
219
+ device: pathStat.dev,
220
+ inode: pathStat.ino,
221
+ size: pathStat.size,
222
+ };
223
+ if (!sameDescriptorIdentity(temporaryIdentity, pathIdentity) ||
224
+ !sameDescriptorIdentity(temporaryIdentity, publishedIdentity)) {
225
+ refuseRetained("approval_output_identity_mismatch", "output_and_candidate");
226
+ }
227
+ const expected = Buffer.from(bytes, "utf8");
228
+ let published;
229
+ try {
230
+ published = fs.readFileSync(publishedDescriptor);
231
+ }
232
+ catch {
233
+ refuseRetained("approval_output_verify_failed", "output_and_candidate");
234
+ }
235
+ if (!published.equals(expected) || sha256Bytes(published) !== sha256Bytes(expected)) {
236
+ refuseRetained("approval_output_verify_mismatch", "output_and_candidate");
237
+ }
238
+ await afterPublishedRead?.(outputPath);
239
+ let finalPathStat;
240
+ try {
241
+ finalPathStat = fs.lstatSync(outputPath, { bigint: true });
242
+ }
243
+ catch {
244
+ refuseRetained("approval_output_final_lstat_failed", "output_and_candidate");
245
+ }
246
+ const finalPathIdentity = {
247
+ device: finalPathStat.dev,
248
+ inode: finalPathStat.ino,
249
+ size: finalPathStat.size,
250
+ };
251
+ if (!finalPathStat.isFile() ||
252
+ finalPathStat.isSymbolicLink() ||
253
+ !sameDescriptorIdentity(temporaryIdentity, finalPathIdentity) ||
254
+ !sameDescriptorIdentity(publishedIdentity, finalPathIdentity)) {
255
+ refuseRetained("approval_output_final_identity_mismatch", "output_and_candidate");
256
+ }
257
+ }
258
+ finally {
259
+ try {
260
+ fs.closeSync(publishedDescriptor);
261
+ }
262
+ catch {
263
+ refuseRetained("approval_output_close_failed", "output_and_candidate");
264
+ }
265
+ }
266
+ }
267
+ export function parseCreateScratchApprovalArgs(args) {
268
+ const valueFlags = new Set([
269
+ "--source-db",
270
+ "--scratch-db",
271
+ "--scratch-state",
272
+ "--repositories",
273
+ "--out",
274
+ ]);
275
+ const values = new Map();
276
+ let acknowledged = false;
277
+ for (let index = 0; index < args.length; index += 1) {
278
+ const flag = args[index];
279
+ if (flag === "--acknowledge-private-parent") {
280
+ if (acknowledged)
281
+ refuse("duplicate_cli_argument");
282
+ acknowledged = true;
283
+ continue;
284
+ }
285
+ if (flag === undefined || !valueFlags.has(flag))
286
+ refuse("unknown_cli_argument");
287
+ if (values.has(flag))
288
+ refuse("duplicate_cli_argument");
289
+ const value = args[index + 1];
290
+ if (value === undefined || value.length === 0 || value.startsWith("--")) {
291
+ refuse("missing_cli_value");
292
+ }
293
+ values.set(flag, value);
294
+ index += 1;
295
+ }
296
+ const sourceDbPath = values.get("--source-db");
297
+ const scratchDbPath = values.get("--scratch-db");
298
+ const scratchStatePath = values.get("--scratch-state");
299
+ const repositoriesJson = values.get("--repositories");
300
+ const outputPath = values.get("--out");
301
+ if (sourceDbPath === undefined ||
302
+ scratchDbPath === undefined ||
303
+ scratchStatePath === undefined ||
304
+ repositoriesJson === undefined ||
305
+ outputPath === undefined) {
306
+ refuse("missing_required_cli_argument");
307
+ }
308
+ if (!acknowledged)
309
+ refuse("private_artifact_parent_not_acknowledged");
310
+ return {
311
+ sourceDbPath,
312
+ scratchDbPath,
313
+ scratchStatePath,
314
+ repositoriesJson,
315
+ outputPath,
316
+ privateArtifactParentAcknowledged: true,
317
+ };
318
+ }
319
+ export async function createScratchApprovalCandidate(input, options = {}) {
320
+ const repositoryRoot = fs.realpathSync.native(options.repositoryRoot ?? REPOSITORY_ROOT);
321
+ const boundary = assertScratchBoundary({
322
+ sourceDbPath: input.sourceDbPath,
323
+ scratchDbPath: input.scratchDbPath,
324
+ scratchStatePath: input.scratchStatePath,
325
+ repositoryRoot,
326
+ });
327
+ const outputPath = resolveOutputPath(input.outputPath, repositoryRoot);
328
+ if (comparisonPath(outputPath) === comparisonPath(boundary.sourceDbPath) ||
329
+ comparisonPath(outputPath) === comparisonPath(boundary.scratchDbPath) ||
330
+ isWithin(boundary.scratchStatePath, outputPath) ||
331
+ isWithin(outputPath, boundary.scratchStatePath)) {
332
+ refuse("approval_output_overlaps_evidence_path");
333
+ }
334
+ const manifest = buildScratchApprovalManifest({
335
+ sourceDbPath: boundary.sourceDbPath,
336
+ scratchDbPath: boundary.scratchDbPath,
337
+ scratchStatePath: boundary.scratchStatePath,
338
+ repositories: parseRepositoriesJson(input.repositoriesJson),
339
+ privateArtifactParentAcknowledged: input.privateArtifactParentAcknowledged,
340
+ });
341
+ const bytes = `${canonicalJson(manifest)}\n`;
342
+ const sha256 = await publishNoReplace(outputPath, bytes, options.testHooks?.beforePublish, options.testHooks?.afterPublishedRead);
343
+ return { sha256, mappedWorkspaceN: manifest.repositories.length };
344
+ }
345
+ async function main() {
346
+ try {
347
+ const result = await createScratchApprovalCandidate(parseCreateScratchApprovalArgs(process.argv.slice(2)));
348
+ process.stdout.write(`${canonicalJson({
349
+ status: "CANDIDATE_CONFIRMATION_REQUIRED",
350
+ sha256: result.sha256,
351
+ mappedWorkspaceN: result.mappedWorkspaceN,
352
+ })}\n`);
353
+ }
354
+ catch (error) {
355
+ const failure = error instanceof EvidenceBoundaryError ? error.code : "unexpected_failure";
356
+ process.stderr.write(`${canonicalJson({ status: "REFUSED", failure })}\n`);
357
+ process.exitCode = 1;
358
+ }
359
+ }
360
+ const invokedPath = process.argv[1];
361
+ if (invokedPath !== undefined &&
362
+ pathToFileURL(path.resolve(invokedPath)).href ===
363
+ pathToFileURL(fileURLToPath(import.meta.url)).href) {
364
+ void main();
365
+ }