crewhaus 0.1.8 → 0.2.1

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 (138) hide show
  1. package/README.md +10 -3
  2. package/dist/advice-apply.d.ts +182 -0
  3. package/dist/advice-apply.js +286 -0
  4. package/dist/advise-rules.d.ts +348 -0
  5. package/dist/advise-rules.js +905 -0
  6. package/dist/alert-sink.d.ts +48 -0
  7. package/dist/alert-sink.js +86 -0
  8. package/dist/approval-gate.d.ts +127 -0
  9. package/dist/approval-gate.js +254 -0
  10. package/dist/audit-verify.d.ts +69 -0
  11. package/dist/audit-verify.js +97 -0
  12. package/dist/autodistill.d.ts +113 -0
  13. package/dist/autodistill.js +256 -0
  14. package/dist/channel-provision.d.ts +360 -0
  15. package/dist/channel-provision.js +881 -0
  16. package/dist/ci-scaffold.d.ts +31 -0
  17. package/dist/ci-scaffold.js +343 -0
  18. package/dist/compile-check.d.ts +90 -0
  19. package/dist/compile-check.js +285 -0
  20. package/dist/compliance-schedule.d.ts +35 -0
  21. package/dist/compliance-schedule.js +36 -0
  22. package/dist/context-pressure.d.ts +80 -0
  23. package/dist/context-pressure.js +166 -0
  24. package/dist/dataset-mine.d.ts +173 -0
  25. package/dist/dataset-mine.js +404 -0
  26. package/dist/datasets.d.ts +124 -0
  27. package/dist/datasets.js +260 -0
  28. package/dist/deploy-canary.d.ts +83 -0
  29. package/dist/deploy-canary.js +87 -0
  30. package/dist/doctor-checks.d.ts +33 -0
  31. package/dist/doctor-checks.js +92 -0
  32. package/dist/doctor-detect.d.ts +108 -0
  33. package/dist/doctor-detect.js +214 -0
  34. package/dist/doctor-fix.d.ts +81 -0
  35. package/dist/doctor-fix.js +164 -0
  36. package/dist/egress-triage.d.ts +121 -0
  37. package/dist/egress-triage.js +261 -0
  38. package/dist/eval-bridge.d.ts +114 -0
  39. package/dist/eval-bridge.js +158 -0
  40. package/dist/eval-coverage.d.ts +140 -0
  41. package/dist/eval-coverage.js +428 -0
  42. package/dist/eval-history.d.ts +48 -0
  43. package/dist/eval-history.js +157 -0
  44. package/dist/eval-matrix.d.ts +80 -0
  45. package/dist/eval-matrix.js +182 -0
  46. package/dist/eval-sentinel.d.ts +65 -0
  47. package/dist/eval-sentinel.js +132 -0
  48. package/dist/faq.d.ts +68 -0
  49. package/dist/faq.js +168 -0
  50. package/dist/feedback.d.ts +9 -2
  51. package/dist/feedback.js +17 -7
  52. package/dist/fewshot.d.ts +83 -0
  53. package/dist/fewshot.js +158 -0
  54. package/dist/fleet.d.ts +207 -0
  55. package/dist/fleet.js +488 -0
  56. package/dist/flywheel.d.ts +193 -0
  57. package/dist/flywheel.js +519 -0
  58. package/dist/graders-suggest.d.ts +186 -0
  59. package/dist/graders-suggest.js +658 -0
  60. package/dist/incident.d.ts +99 -0
  61. package/dist/incident.js +217 -0
  62. package/dist/index.d.ts +9 -1
  63. package/dist/index.js +11664 -963
  64. package/dist/init-interactive.d.ts +105 -0
  65. package/dist/init-interactive.js +208 -0
  66. package/dist/intents.d.ts +105 -0
  67. package/dist/intents.js +292 -0
  68. package/dist/judge-calibrate.d.ts +137 -0
  69. package/dist/judge-calibrate.js +247 -0
  70. package/dist/justification-calibrate.d.ts +150 -0
  71. package/dist/justification-calibrate.js +262 -0
  72. package/dist/justification-gate.d.ts +27 -6
  73. package/dist/justification-gate.js +30 -6
  74. package/dist/knowledge-sync.d.ts +179 -0
  75. package/dist/knowledge-sync.js +551 -0
  76. package/dist/lessons.d.ts +87 -0
  77. package/dist/lessons.js +207 -0
  78. package/dist/lint.d.ts +127 -0
  79. package/dist/lint.js +226 -0
  80. package/dist/loadtest.d.ts +114 -0
  81. package/dist/loadtest.js +196 -0
  82. package/dist/marketplace-cli.d.ts +110 -0
  83. package/dist/marketplace-cli.js +250 -0
  84. package/dist/mcp-doctor.d.ts +121 -0
  85. package/dist/mcp-doctor.js +249 -0
  86. package/dist/model-scan.d.ts +116 -0
  87. package/dist/model-scan.js +226 -0
  88. package/dist/onchain-tune.d.ts +164 -0
  89. package/dist/onchain-tune.js +346 -0
  90. package/dist/permissions-suggest.d.ts +126 -0
  91. package/dist/permissions-suggest.js +333 -0
  92. package/dist/pii-tune.d.ts +107 -0
  93. package/dist/pii-tune.js +122 -0
  94. package/dist/propose.d.ts +117 -0
  95. package/dist/propose.js +184 -0
  96. package/dist/refresh-goldens.d.ts +82 -0
  97. package/dist/refresh-goldens.js +221 -0
  98. package/dist/regression-pin.d.ts +160 -0
  99. package/dist/regression-pin.js +281 -0
  100. package/dist/retention.d.ts +193 -0
  101. package/dist/retention.js +607 -0
  102. package/dist/retire.d.ts +118 -0
  103. package/dist/retire.js +291 -0
  104. package/dist/right-size.d.ts +100 -0
  105. package/dist/right-size.js +123 -0
  106. package/dist/route.d.ts +19 -0
  107. package/dist/route.js +90 -0
  108. package/dist/scaffold-evals.d.ts +138 -0
  109. package/dist/scaffold-evals.js +410 -0
  110. package/dist/scope-audit-drift.d.ts +139 -0
  111. package/dist/scope-audit-drift.js +260 -0
  112. package/dist/security-corpus.d.ts +237 -0
  113. package/dist/security-corpus.js +516 -0
  114. package/dist/security-digest.d.ts +174 -0
  115. package/dist/security-digest.js +656 -0
  116. package/dist/sessions-index.d.ts +27 -0
  117. package/dist/sessions-index.js +51 -0
  118. package/dist/slo-doctor.d.ts +67 -0
  119. package/dist/slo-doctor.js +119 -0
  120. package/dist/slo-sink.d.ts +96 -0
  121. package/dist/slo-sink.js +107 -0
  122. package/dist/spec-changelog.d.ts +102 -0
  123. package/dist/spec-changelog.js +237 -0
  124. package/dist/state-backup.d.ts +223 -0
  125. package/dist/state-backup.js +648 -0
  126. package/dist/tools-cli.d.ts +170 -0
  127. package/dist/tools-cli.js +300 -0
  128. package/dist/triage.d.ts +202 -0
  129. package/dist/triage.js +403 -0
  130. package/dist/upgrade.d.ts +57 -0
  131. package/dist/upgrade.js +113 -0
  132. package/dist/version.d.ts +6 -0
  133. package/dist/version.js +27 -0
  134. package/dist/voice-eval.d.ts +138 -0
  135. package/dist/voice-eval.js +309 -0
  136. package/dist/watch.d.ts +58 -0
  137. package/dist/watch.js +97 -0
  138. package/package.json +90 -65
@@ -0,0 +1,99 @@
1
+ /** The trigger kinds that auto-assemble an incident bundle. */
2
+ export type IncidentKind = "run_abort" | "circuit_open" | "egress_blocked" | "justification_deny_storm" | "budget_exceeded";
3
+ /** A minimal structural view of a TraceEvent for trigger classification. */
4
+ export type TriggerEvent = {
5
+ readonly kind: string;
6
+ readonly [k: string]: unknown;
7
+ };
8
+ /**
9
+ * How many justification denials within the ring buffer constitute a "storm".
10
+ * A single deny is routine policy enforcement; a burst signals a prompt-
11
+ * injection probe or a misconfigured agent worth capturing.
12
+ */
13
+ export declare const JUSTIFICATION_DENY_STORM_THRESHOLD = 3;
14
+ export type AuditRecordLike = {
15
+ readonly ts: number;
16
+ readonly kind: string;
17
+ readonly payload: unknown;
18
+ readonly seq?: number;
19
+ readonly hash?: string;
20
+ };
21
+ export type CostSummary = {
22
+ readonly totalUsdMicros: number;
23
+ readonly byModel: Record<string, {
24
+ calls: number;
25
+ usdMicros: number;
26
+ }>;
27
+ };
28
+ export type SpecInfo = {
29
+ readonly name: string;
30
+ readonly version?: string;
31
+ readonly hash?: string;
32
+ };
33
+ /** The injected inputs assembled into a bundle. */
34
+ export type IncidentInputs = {
35
+ readonly kind: IncidentKind;
36
+ readonly sessionId: string;
37
+ /** ISO-8601 incident time (also seeds the bundle dir name). */
38
+ readonly incidentTs: string;
39
+ /** One-line human reason (e.g. "circuit anthropic → open: 5 consecutive 429s"). */
40
+ readonly reason: string;
41
+ /** bus.recent() ring-buffer events at incident time (any JSON-serializable). */
42
+ readonly ringEvents: ReadonlyArray<unknown>;
43
+ /** The session transcript slice (event-log entries) around the incident. */
44
+ readonly transcript: ReadonlyArray<unknown>;
45
+ /** Audit records matched to the session window (see file header). */
46
+ readonly auditRecords: ReadonlyArray<AuditRecordLike>;
47
+ readonly cost: CostSummary;
48
+ readonly spec: SpecInfo;
49
+ /** Captured doctor output, or a note if doctor could not run. */
50
+ readonly doctor: string;
51
+ /** The session window used for audit matching, for the manifest. */
52
+ readonly window: {
53
+ readonly startTs: number;
54
+ readonly endTs: number;
55
+ };
56
+ };
57
+ /** A file to write into the incident dir: relative name + string contents. */
58
+ export type IncidentFile = {
59
+ readonly name: string;
60
+ readonly contents: string;
61
+ };
62
+ export type AssembledIncident = {
63
+ readonly dirName: string;
64
+ readonly files: ReadonlyArray<IncidentFile>;
65
+ };
66
+ /** `<iso-ts-compacted>-<kind>` — a filesystem-safe, sortable dir name. */
67
+ export declare function incidentDirName(incidentTs: string, kind: IncidentKind): string;
68
+ /**
69
+ * Match audit records to a session by TIME WINDOW (the only signal every
70
+ * record kind shares — see the file header). `marginMs` widens the window on
71
+ * both ends for clock skew and late flushes.
72
+ */
73
+ export declare function matchAuditRecordsByWindow(records: ReadonlyArray<AuditRecordLike>, window: {
74
+ startTs: number;
75
+ endTs: number;
76
+ }, marginMs?: number): AuditRecordLike[];
77
+ /** Count justification denials in a set of ring-buffer events. */
78
+ export declare function countJustificationDenials(events: ReadonlyArray<unknown>): number;
79
+ /**
80
+ * Classify a live TraceEvent as an incident trigger, given the current ring
81
+ * buffer (for the deny-storm threshold). Returns the incident kind + reason,
82
+ * or undefined when the event is not a trigger.
83
+ */
84
+ export declare function classifyTrigger(event: TriggerEvent, ringEvents: ReadonlyArray<unknown>): {
85
+ kind: IncidentKind;
86
+ reason: string;
87
+ } | undefined;
88
+ /** Assemble the incident bundle files from injected inputs. */
89
+ export declare function assembleIncidentBundle(inputs: IncidentInputs): AssembledIncident;
90
+ /** Render the incident as an eval-report-styled HTML page. */
91
+ export declare function renderIncidentHtml(inputs: IncidentInputs): string;
92
+ /**
93
+ * Summarize cost_accrual events into a {@link CostSummary}. Handles BOTH
94
+ * shapes: a runtime bus TraceEvent (cost fields at the top level) AND a
95
+ * durable session-log entry (`{ kind, payload }` — cost fields nested under
96
+ * `payload`, the shape `crewhaus incident collect` reads). The FR-003 terminal
97
+ * summary aggregate is skipped so a run total never double-counts.
98
+ */
99
+ export declare function summarizeCost(ringEvents: ReadonlyArray<unknown>): CostSummary;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Item 32 — auto-assemble incident bundles from traces on failure events.
3
+ *
4
+ * On a trigger event — an unrecovered run abort, a circuit breaker going
5
+ * `open`, an egress-blocked verdict, a justification-deny storm, or a gateway
6
+ * `budget_exceeded` — the runtime (via an env-gated bus subscriber) or the
7
+ * `crewhaus incident collect --session <id>` command snapshots everything an
8
+ * on-call needs into `.crewhaus/incidents/<ts>-<kind>/`:
9
+ * - events.jsonl — the bus ring buffer (bus.recent()) at incident time
10
+ * - transcript.jsonl — the session event-log slice around the incident
11
+ * - audit.jsonl — the hash-chained audit records that fall in the session
12
+ * window (see AUDIT-SESSION LINKAGE below)
13
+ * - cost.json — the cost_accrual summary (total + per-model)
14
+ * - spec.json — the spec name, version, and hash
15
+ * - doctor.txt — captured `crewhaus doctor` output
16
+ * - bundle.json — the machine-readable manifest of all of the above
17
+ * - index.html — an eval-report-styled human view
18
+ *
19
+ * AUDIT-SESSION LINKAGE (design decision): audit records carry a `ts` (ms
20
+ * epoch) but NO first-class sessionId — the hash chain is per-tenant/day, not
21
+ * per-session. So "matching audit records" is defined by TIME WINDOW: every
22
+ * record whose `ts` lies within [sessionStart, incidentTs] (with a small
23
+ * margin for clock skew / a late flush). Where a payload DOES carry a
24
+ * correlating field (a justification record's toolName, a deployment_action's
25
+ * name), it is preserved verbatim in the bundle so a human can disambiguate
26
+ * concurrent sessions; the window is the primary join because it is the only
27
+ * signal every record kind shares.
28
+ *
29
+ * Side-effect-free: assembly takes injected inputs (ring-buffer events,
30
+ * transcript slice, audit records, cost summary, spec info, doctor text) and
31
+ * returns the files to write. The CLI/runtime perform the reads + the single
32
+ * mkdir/write pass, so the assembly + rendering are unit-testable.
33
+ */
34
+ import { escapeHtml, shell } from "@crewhaus/eval-report";
35
+ /**
36
+ * How many justification denials within the ring buffer constitute a "storm".
37
+ * A single deny is routine policy enforcement; a burst signals a prompt-
38
+ * injection probe or a misconfigured agent worth capturing.
39
+ */
40
+ export const JUSTIFICATION_DENY_STORM_THRESHOLD = 3;
41
+ /** `<iso-ts-compacted>-<kind>` — a filesystem-safe, sortable dir name. */
42
+ export function incidentDirName(incidentTs, kind) {
43
+ // 2026-07-02T18:07:03.412Z → 20260702T180703 (drop ms + punctuation).
44
+ const compact = incidentTs
45
+ .replace(/[-:]/g, "")
46
+ .replace(/\.\d+Z$/, "")
47
+ .replace(/Z$/, "");
48
+ return `${compact}-${kind}`;
49
+ }
50
+ /**
51
+ * Match audit records to a session by TIME WINDOW (the only signal every
52
+ * record kind shares — see the file header). `marginMs` widens the window on
53
+ * both ends for clock skew and late flushes.
54
+ */
55
+ export function matchAuditRecordsByWindow(records, window, marginMs = 2000) {
56
+ const lo = window.startTs - marginMs;
57
+ const hi = window.endTs + marginMs;
58
+ return records.filter((r) => r.ts >= lo && r.ts <= hi);
59
+ }
60
+ /** Count justification denials in a set of ring-buffer events. */
61
+ export function countJustificationDenials(events) {
62
+ let n = 0;
63
+ for (const ev of events) {
64
+ const e = ev;
65
+ if (e.kind === "permission_decision" && e.decision === "deny" && e.judgeModel !== undefined) {
66
+ n += 1;
67
+ }
68
+ }
69
+ return n;
70
+ }
71
+ /**
72
+ * Classify a live TraceEvent as an incident trigger, given the current ring
73
+ * buffer (for the deny-storm threshold). Returns the incident kind + reason,
74
+ * or undefined when the event is not a trigger.
75
+ */
76
+ export function classifyTrigger(event, ringEvents) {
77
+ switch (event.kind) {
78
+ case "circuit_state_changed":
79
+ if (event["toState"] === "open") {
80
+ return {
81
+ kind: "circuit_open",
82
+ reason: `circuit ${String(event["adapter"] ?? "?")} → open${event["reason"] !== undefined ? `: ${String(event["reason"])}` : ""}`,
83
+ };
84
+ }
85
+ return undefined;
86
+ case "permission_decision": {
87
+ if (event["outcome"] === "egress-blocked") {
88
+ return {
89
+ kind: "egress_blocked",
90
+ reason: `egress blocked on ${String(event["toolName"] ?? "?")}`,
91
+ };
92
+ }
93
+ // A deny-storm is measured over the ring buffer, INCLUDING this event.
94
+ if (event["decision"] === "deny" && event["judgeModel"] !== undefined) {
95
+ const denials = countJustificationDenials([...ringEvents, event]);
96
+ if (denials >= JUSTIFICATION_DENY_STORM_THRESHOLD) {
97
+ return {
98
+ kind: "justification_deny_storm",
99
+ reason: `justification-deny storm: ${denials} denials in the ring buffer`,
100
+ };
101
+ }
102
+ }
103
+ return undefined;
104
+ }
105
+ default:
106
+ return undefined;
107
+ }
108
+ }
109
+ /** Assemble the incident bundle files from injected inputs. */
110
+ export function assembleIncidentBundle(inputs) {
111
+ const dirName = incidentDirName(inputs.incidentTs, inputs.kind);
112
+ const manifest = {
113
+ kind: inputs.kind,
114
+ sessionId: inputs.sessionId,
115
+ incidentTs: inputs.incidentTs,
116
+ reason: inputs.reason,
117
+ window: inputs.window,
118
+ counts: {
119
+ ringEvents: inputs.ringEvents.length,
120
+ transcriptEntries: inputs.transcript.length,
121
+ auditRecords: inputs.auditRecords.length,
122
+ },
123
+ cost: inputs.cost,
124
+ spec: inputs.spec,
125
+ };
126
+ const files = [
127
+ { name: "bundle.json", contents: `${JSON.stringify(manifest, null, 2)}\n` },
128
+ { name: "events.jsonl", contents: toJsonl(inputs.ringEvents) },
129
+ { name: "transcript.jsonl", contents: toJsonl(inputs.transcript) },
130
+ { name: "audit.jsonl", contents: toJsonl(inputs.auditRecords) },
131
+ { name: "cost.json", contents: `${JSON.stringify(inputs.cost, null, 2)}\n` },
132
+ { name: "spec.json", contents: `${JSON.stringify(inputs.spec, null, 2)}\n` },
133
+ {
134
+ name: "doctor.txt",
135
+ contents: inputs.doctor.endsWith("\n") ? inputs.doctor : `${inputs.doctor}\n`,
136
+ },
137
+ { name: "index.html", contents: renderIncidentHtml(inputs) },
138
+ ];
139
+ return { dirName, files };
140
+ }
141
+ function toJsonl(items) {
142
+ if (items.length === 0)
143
+ return "";
144
+ return `${items.map((i) => JSON.stringify(i)).join("\n")}\n`;
145
+ }
146
+ /** Render the incident as an eval-report-styled HTML page. */
147
+ export function renderIncidentHtml(inputs) {
148
+ const usd = (micros) => `$${(micros / 1_000_000).toFixed(4)}`;
149
+ const cards = [
150
+ { label: "Kind", value: inputs.kind },
151
+ { label: "Session", value: inputs.sessionId },
152
+ {
153
+ label: "Spec",
154
+ value: `${inputs.spec.name}${inputs.spec.version ? `@${inputs.spec.version}` : ""}`,
155
+ },
156
+ { label: "Ring events", value: String(inputs.ringEvents.length) },
157
+ { label: "Transcript", value: String(inputs.transcript.length) },
158
+ { label: "Audit records", value: String(inputs.auditRecords.length) },
159
+ { label: "Total cost", value: usd(inputs.cost.totalUsdMicros) },
160
+ ];
161
+ const cardsHtml = cards
162
+ .map((c) => `<div class="card"><div class="label">${escapeHtml(c.label)}</div><div class="value">${escapeHtml(c.value)}</div></div>`)
163
+ .join("");
164
+ const eventRows = inputs.ringEvents
165
+ .slice(-100)
166
+ .map((ev) => {
167
+ const e = ev;
168
+ return `<tr><td>${escapeHtml(e.timestamp ?? "")}</td><td>${escapeHtml(e.kind ?? "")}</td><td><pre>${escapeHtml(JSON.stringify(ev))}</pre></td></tr>`;
169
+ })
170
+ .join("");
171
+ const auditRows = inputs.auditRecords
172
+ .map((r) => `<tr><td>${escapeHtml(new Date(r.ts).toISOString())}</td><td>${escapeHtml(r.kind)}</td><td><pre>${escapeHtml(JSON.stringify(r.payload))}</pre></td></tr>`)
173
+ .join("");
174
+ const costRows = Object.entries(inputs.cost.byModel)
175
+ .map(([model, c]) => `<tr><td>${escapeHtml(model)}</td><td>${c.calls}</td><td>${usd(c.usdMicros)}</td></tr>`)
176
+ .join("");
177
+ const body = `<h1>Incident — ${escapeHtml(inputs.kind)}</h1>
178
+ <div class="meta">${escapeHtml(inputs.incidentTs)} · ${escapeHtml(inputs.reason)}</div>
179
+ <div class="aggregate">${cardsHtml}</div>
180
+ <div class="diff-section"><h2>Cost by model</h2>
181
+ <table><thead><tr><th>Model</th><th>Calls</th><th>Cost</th></tr></thead><tbody>${costRows || '<tr><td colspan="3" class="na">no accruals</td></tr>'}</tbody></table></div>
182
+ <div class="diff-section"><h2>Ring buffer (last 100)</h2>
183
+ <table data-sortable><thead><tr><th>Time</th><th>Kind</th><th>Event</th></tr></thead><tbody>${eventRows || '<tr><td colspan="3" class="na">no events</td></tr>'}</tbody></table></div>
184
+ <div class="diff-section"><h2>Audit records (session window)</h2>
185
+ <table data-sortable><thead><tr><th>Time</th><th>Kind</th><th>Payload</th></tr></thead><tbody>${auditRows || '<tr><td colspan="3" class="na">no matching records</td></tr>'}</tbody></table></div>
186
+ <div class="diff-section"><h2>doctor</h2><pre>${escapeHtml(inputs.doctor)}</pre></div>`;
187
+ return shell(`Incident ${inputs.kind} — ${inputs.sessionId}`, body);
188
+ }
189
+ /**
190
+ * Summarize cost_accrual events into a {@link CostSummary}. Handles BOTH
191
+ * shapes: a runtime bus TraceEvent (cost fields at the top level) AND a
192
+ * durable session-log entry (`{ kind, payload }` — cost fields nested under
193
+ * `payload`, the shape `crewhaus incident collect` reads). The FR-003 terminal
194
+ * summary aggregate is skipped so a run total never double-counts.
195
+ */
196
+ export function summarizeCost(ringEvents) {
197
+ let totalUsdMicros = 0;
198
+ const byModel = {};
199
+ for (const ev of ringEvents) {
200
+ const top = ev;
201
+ if (top.kind !== "cost_accrual")
202
+ continue;
203
+ // Prefer the nested payload (session-log entry); fall back to the top level
204
+ // (trace bus event).
205
+ const fields = (typeof top.payload === "object" && top.payload !== null ? top.payload : ev);
206
+ if (fields.summary === true)
207
+ continue;
208
+ const micros = typeof fields.costUsdMicros === "number" ? fields.costUsdMicros : 0;
209
+ const model = fields.modelId ?? "unknown";
210
+ totalUsdMicros += micros;
211
+ const row = byModel[model] ?? { calls: 0, usdMicros: 0 };
212
+ row.calls += 1;
213
+ row.usdMicros += micros;
214
+ byModel[model] = row;
215
+ }
216
+ return { totalUsdMicros, byModel };
217
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env bun
2
- export {};
2
+ /**
3
+ * Section 18 — resolve `sandboxAvailable` for the `run` path from the
4
+ * `CREWHAUS_SANDBOX` env var, using the SAME grammar the compiled bundle
5
+ * emits (`packages/target-cli` renderRun): unset defaults to `"docker"`
6
+ * (available); any value whose lowercase is `"noop"` disables the sandbox
7
+ * floor (code-exec tools are then denied by permission-engine's
8
+ * `requiresSandbox` floor). Pure — reads only the passed env snapshot.
9
+ */
10
+ export declare function resolveSandboxAvailable(env?: NodeJS.ProcessEnv): boolean;