wicked-crew 0.6.0 → 0.7.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 (99) hide show
  1. package/dist/api/audit.d.ts +13 -0
  2. package/dist/api/audit.d.ts.map +1 -1
  3. package/dist/api/audit.js +18 -2
  4. package/dist/api/audit.js.map +1 -1
  5. package/dist/api/guidance-index.d.ts +39 -0
  6. package/dist/api/guidance-index.d.ts.map +1 -0
  7. package/dist/api/guidance-index.js +67 -0
  8. package/dist/api/guidance-index.js.map +1 -0
  9. package/dist/api/open-path.d.ts +16 -0
  10. package/dist/api/open-path.d.ts.map +1 -1
  11. package/dist/api/open-path.js +22 -0
  12. package/dist/api/open-path.js.map +1 -1
  13. package/dist/api/retry-index.d.ts +30 -0
  14. package/dist/api/retry-index.d.ts.map +1 -0
  15. package/dist/api/retry-index.js +45 -0
  16. package/dist/api/retry-index.js.map +1 -0
  17. package/dist/api/routes.d.ts +53 -1
  18. package/dist/api/routes.d.ts.map +1 -1
  19. package/dist/api/routes.js +352 -23
  20. package/dist/api/routes.js.map +1 -1
  21. package/dist/api/run-files.d.ts +63 -0
  22. package/dist/api/run-files.d.ts.map +1 -0
  23. package/dist/api/run-files.js +271 -0
  24. package/dist/api/run-files.js.map +1 -0
  25. package/dist/api/server.d.ts +79 -0
  26. package/dist/api/server.d.ts.map +1 -1
  27. package/dist/api/server.js +135 -6
  28. package/dist/api/server.js.map +1 -1
  29. package/dist/api/stall-watchdog.d.ts +62 -0
  30. package/dist/api/stall-watchdog.d.ts.map +1 -0
  31. package/dist/api/stall-watchdog.js +138 -0
  32. package/dist/api/stall-watchdog.js.map +1 -0
  33. package/dist/cli/index.js +78 -13
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/core/adapter.d.ts +24 -10
  36. package/dist/core/adapter.d.ts.map +1 -1
  37. package/dist/core/adapter.js +191 -30
  38. package/dist/core/adapter.js.map +1 -1
  39. package/dist/core/bridge-reaper.d.ts +134 -0
  40. package/dist/core/bridge-reaper.d.ts.map +1 -0
  41. package/dist/core/bridge-reaper.js +286 -0
  42. package/dist/core/bridge-reaper.js.map +1 -0
  43. package/dist/core/deliver.d.ts +118 -0
  44. package/dist/core/deliver.d.ts.map +1 -0
  45. package/dist/core/deliver.js +241 -0
  46. package/dist/core/deliver.js.map +1 -0
  47. package/dist/core/deliverable-floor.d.ts +103 -0
  48. package/dist/core/deliverable-floor.d.ts.map +1 -0
  49. package/dist/core/deliverable-floor.js +173 -0
  50. package/dist/core/deliverable-floor.js.map +1 -0
  51. package/dist/core/exec.d.ts +2 -0
  52. package/dist/core/exec.d.ts.map +1 -1
  53. package/dist/core/exec.js.map +1 -1
  54. package/dist/core/types.d.ts +79 -1
  55. package/dist/core/types.d.ts.map +1 -1
  56. package/dist/core/types.js +3 -0
  57. package/dist/core/types.js.map +1 -1
  58. package/dist/interactive/bridge-pool.d.ts +28 -0
  59. package/dist/interactive/bridge-pool.d.ts.map +1 -1
  60. package/dist/interactive/bridge-pool.js +67 -10
  61. package/dist/interactive/bridge-pool.js.map +1 -1
  62. package/dist/interactive/chat-events.d.ts +207 -0
  63. package/dist/interactive/chat-events.d.ts.map +1 -0
  64. package/dist/interactive/chat-events.js +769 -0
  65. package/dist/interactive/chat-events.js.map +1 -0
  66. package/dist/interactive/demo-events.d.ts +283 -0
  67. package/dist/interactive/demo-events.d.ts.map +1 -0
  68. package/dist/interactive/demo-events.js +889 -0
  69. package/dist/interactive/demo-events.js.map +1 -0
  70. package/dist/interactive/draft-events.d.ts +87 -7
  71. package/dist/interactive/draft-events.d.ts.map +1 -1
  72. package/dist/interactive/draft-events.js +352 -49
  73. package/dist/interactive/draft-events.js.map +1 -1
  74. package/dist/interactive/edit-events.d.ts +22 -0
  75. package/dist/interactive/edit-events.d.ts.map +1 -1
  76. package/dist/interactive/edit-events.js +73 -2
  77. package/dist/interactive/edit-events.js.map +1 -1
  78. package/dist/interactive/repo-snapshot.d.ts +100 -0
  79. package/dist/interactive/repo-snapshot.d.ts.map +1 -0
  80. package/dist/interactive/repo-snapshot.js +289 -0
  81. package/dist/interactive/repo-snapshot.js.map +1 -0
  82. package/dist/projects/graph-paths.d.ts +92 -0
  83. package/dist/projects/graph-paths.d.ts.map +1 -0
  84. package/dist/projects/graph-paths.js +130 -0
  85. package/dist/projects/graph-paths.js.map +1 -0
  86. package/dist/projects/graph.d.ts +179 -0
  87. package/dist/projects/graph.d.ts.map +1 -0
  88. package/dist/projects/graph.js +775 -0
  89. package/dist/projects/graph.js.map +1 -0
  90. package/dist/projects/routes.d.ts +7 -0
  91. package/dist/projects/routes.d.ts.map +1 -1
  92. package/dist/projects/routes.js +122 -0
  93. package/dist/projects/routes.js.map +1 -1
  94. package/dist/studio/assets/index-8p8uwCxG.js +530 -0
  95. package/dist/studio/assets/index-D6S9zUtO.css +32 -0
  96. package/dist/studio/index.html +5 -3
  97. package/package.json +3 -3
  98. package/dist/studio/assets/index-CCwXa1cn.js +0 -428
  99. package/dist/studio/assets/index-HWxo0h41.css +0 -32
@@ -0,0 +1,286 @@
1
+ /**
2
+ * ACP bridge child reaper — bridges must die with the daemon (crew#285).
3
+ *
4
+ * # The defect
5
+ *
6
+ * The engine spawns ACP bridge binaries as direct OS children of this daemon process,
7
+ * and the only kill handles for them live in the engine's in-memory registry. When the
8
+ * daemon dies — pkill during a restart, an operator's Ctrl-C, a plain `process.exit` —
9
+ * that registry dies with it and nothing reaps the bridges: they linger detached until
10
+ * their own session-idle logic (if any) gets around to exiting. Operators observed
11
+ * three `claude-agent-acp` processes coexisting while exactly one unit was executing.
12
+ *
13
+ * Same defect family as crew#277's cancel-orphans, but for daemon death rather than
14
+ * run cancellation.
15
+ *
16
+ * # The fix, in two halves
17
+ *
18
+ * 1. THIS MODULE (daemon side): a central registry of bridge child pids plus an OS
19
+ * process-table sweep, wired into the daemon's shutdown path. On SIGTERM/SIGINT the
20
+ * daemon SIGTERMs every bridge child, waits a short grace for them to exit, then
21
+ * SIGKILLs survivors. On plain `exit` (where no async work is possible) it fires a
22
+ * synchronous best-effort SIGTERM sweep.
23
+ *
24
+ * `register()` exists for any JS-side spawn site (and for tests); the engine-spawned
25
+ * bridges are found by `discoverBridgeChildren()` — a scan of the OS process table
26
+ * for DIRECT children of this daemon whose command line names a bridge binary. The
27
+ * direct-child restriction is the safety rail: another daemon's bridges have a
28
+ * different parent pid and are never touched.
29
+ *
30
+ * 2. `packages/agent-acp-bridges` (bridge side): the bridge treats stdin EOF as the
31
+ * portable parent-death signal and reaps its own CLI child instead of lingering
32
+ * until the CLI finishes. See `bridge.mjs`. That half covers our own bridges even
33
+ * when the daemon dies too hard (SIGKILL) for this module to run at all.
34
+ *
35
+ * # Why a process-table sweep rather than tracked pids alone
36
+ *
37
+ * The spawn happens inside the engine (the native actor thread), which reports no pid
38
+ * back to JS — there is nothing for the daemon to `register()`. The bridges ARE this
39
+ * process's direct children though, and their command lines name the bridge binaries
40
+ * (npm `.bin` shims exec `node .../<bridge-name>/...`), so a ppid-filtered scan
41
+ * recovers exactly the set the in-memory kill handles would have covered. The sweep
42
+ * fails OPEN (returns nothing) when the platform tooling is unavailable: a shutdown
43
+ * that cannot enumerate children must still shut down.
44
+ */
45
+ import { spawnSync } from 'node:child_process';
46
+ /**
47
+ * The bridge binaries the engine spawns by bare name on PATH. Mirrors the set audited
48
+ * by `bridge-names.test.ts` (declared dependencies of this package + the shims a real
49
+ * install produces); `bridge-reaper.test.ts` cross-checks this list against those same
50
+ * dependency manifests so a bridge added or dropped there cannot silently drift here.
51
+ */
52
+ export const BRIDGE_BINS = [
53
+ 'agy-acp',
54
+ 'claude-agent-acp',
55
+ 'codex-acp',
56
+ 'pi-acp',
57
+ ];
58
+ /** How long a SIGTERM'd bridge gets to exit before the SIGKILL escalation. */
59
+ export const BRIDGE_KILL_GRACE_MS = 2000;
60
+ /** How often the grace window re-checks survivor liveness. */
61
+ const POLL_INTERVAL_MS = 100;
62
+ /**
63
+ * One line of a `pid ppid command` process listing → the pids of DIRECT children of
64
+ * `parentPid` whose command line names a bridge binary. Pure, so the parsing is
65
+ * testable without a real process table.
66
+ */
67
+ /**
68
+ * Matches `bin` as a whole command token, never a substring: start/whitespace/path-sep/
69
+ * quote before; an optional Windows launcher extension (.cmd/.exe/.bat) and then
70
+ * end/whitespace/path-sep/quote after — so `pi-acp`, `/x/pi-acp`, `"C:\\x\\pi-acp.cmd"`
71
+ * all match while `api-acp` and `copy-of-pi-acp-backup` never do.
72
+ */
73
+ function bridgeTokenRe(bin) {
74
+ const esc = bin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
75
+ return new RegExp(`(?:^|[\\s/\\\\"'])${esc}(?:\\.(?:cmd|exe|bat))?(?:[\\s/\\\\"']|$)`, 'i');
76
+ }
77
+ /** Precompiled per-bin matchers — the scan loops run one test per line, no per-line RegExp churn. */
78
+ const BRIDGE_TOKEN_RES = BRIDGE_BINS.map((bin) => bridgeTokenRe(bin));
79
+ export function parseBridgeChildren(listing, parentPid) {
80
+ const pids = [];
81
+ for (const line of listing.split('\n')) {
82
+ const m = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
83
+ if (m === null)
84
+ continue;
85
+ const pid = Number(m[1]);
86
+ const ppid = Number(m[2]);
87
+ const command = m[3];
88
+ if (ppid !== parentPid || pid === parentPid)
89
+ continue;
90
+ // Token-boundary match: `pi-acp` must not match inside `api-acp` (Copilot review
91
+ // on #300 post-merge). A bridge binary appears as its own token — start-of-line,
92
+ // whitespace, or a path separator before it; end-of-token after.
93
+ if (BRIDGE_TOKEN_RES.some((re) => re.test(command)))
94
+ pids.push(pid);
95
+ }
96
+ return pids;
97
+ }
98
+ /** `pid ppid command` listing of every process, or null when the platform tooling fails. */
99
+ function listProcesses() {
100
+ try {
101
+ const out = process.platform === 'win32'
102
+ ? // wmic is removed on current Windows; CIM via PowerShell is the stable surface.
103
+ spawnSync('powershell', [
104
+ '-NoProfile',
105
+ '-Command',
106
+ "Get-CimInstance Win32_Process | ForEach-Object { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, $_.CommandLine }",
107
+ ], { encoding: 'utf8', windowsHide: true, maxBuffer: 16 * 1024 * 1024 })
108
+ : // POSIX keywords (`args`, not the BSD/procps-specific `command`): same spelling
109
+ // works on macOS and Linux.
110
+ spawnSync('ps', ['-A', '-o', 'pid=,ppid=,args='], {
111
+ encoding: 'utf8',
112
+ maxBuffer: 16 * 1024 * 1024,
113
+ });
114
+ if (out.error !== undefined || out.status !== 0 || typeof out.stdout !== 'string')
115
+ return null;
116
+ return out.stdout;
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
122
+ /**
123
+ * Pids of every live bridge process that is a direct child of `parentPid`. Fails open:
124
+ * an unreadable process table yields `[]`, never a throw — shutdown must proceed.
125
+ */
126
+ export function discoverBridgeChildren(parentPid = process.pid) {
127
+ const listing = listProcesses();
128
+ return listing === null ? [] : parseBridgeChildren(listing, parentPid);
129
+ }
130
+ /**
131
+ * Pids of bridge processes ORPHANED by a previous daemon generation: reparented to
132
+ * init (ppid 1). Deliberately conservative — a bridge owned by another LIVE daemon
133
+ * still has that daemon as its parent and is never matched, so a boot sweep cannot
134
+ * shoot a neighbour's workers (#285, Copilot review: shutdown-only reaping leaves
135
+ * pre-existing orphans alive forever).
136
+ */
137
+ export function parseOrphanedBridges(listing) {
138
+ const pids = [];
139
+ for (const line of listing.split('\n')) {
140
+ const m = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
141
+ if (m === null)
142
+ continue;
143
+ const pid = Number(m[1]);
144
+ const ppid = Number(m[2]);
145
+ const command = m[3];
146
+ if (ppid !== 1 || pid === process.pid)
147
+ continue;
148
+ if (BRIDGE_TOKEN_RES.some((re) => re.test(command)))
149
+ pids.push(pid);
150
+ }
151
+ return pids;
152
+ }
153
+ /**
154
+ * True when `pid`'s working directory sits inside an engine run worktree — the cwd
155
+ * contract of every engine-spawned bridge. A user's own nohup'd `*-acp` process also
156
+ * reparents to init but runs from an arbitrary cwd, so this is the discriminator that
157
+ * keeps the boot sweep from shooting it (Copilot review on #300). POSIX only (lsof);
158
+ * anywhere the cwd cannot be read the answer is false — never kill on uncertainty.
159
+ */
160
+ function pidRunsInRunWorktree(pid) {
161
+ if (process.platform === 'win32')
162
+ return false;
163
+ try {
164
+ const out = spawnSync('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], {
165
+ encoding: 'utf8',
166
+ maxBuffer: 1024 * 1024,
167
+ });
168
+ if (out.error !== undefined || out.status !== 0 || typeof out.stdout !== 'string')
169
+ return false;
170
+ const cwd = out.stdout.split('\n').find((l) => l.startsWith('n'))?.slice(1) ?? '';
171
+ return cwd.includes('wicked-worktrees');
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ }
177
+ /**
178
+ * Boot-time sweep: SIGTERM orphaned bridges from a prior daemon generation — ppid 1
179
+ * AND cwd inside a run worktree, so user-started bridges are never matched.
180
+ */
181
+ export function reapOrphansAtBoot(io = {}) {
182
+ const listing = listProcesses();
183
+ if (listing === null)
184
+ return [];
185
+ const orphans = parseOrphanedBridges(listing).filter((pid) => (io.cwdInWorktree ?? pidRunsInRunWorktree)(pid));
186
+ const reaped = [];
187
+ for (const pid of orphans) {
188
+ try {
189
+ (io.kill ?? process.kill)(pid, 'SIGTERM');
190
+ reaped.push(pid);
191
+ }
192
+ catch {
193
+ // ESRCH (already gone) / EPERM (not ours): skip silently — fail open.
194
+ }
195
+ }
196
+ return reaped;
197
+ }
198
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
199
+ /**
200
+ * The central bridge-child registry plus the shutdown path that empties it.
201
+ *
202
+ * A single instance (`bridgeReaper`) is wired into the daemon's shutdown handlers;
203
+ * tests construct their own with fake IO.
204
+ */
205
+ export class BridgeReaper {
206
+ tracked = new Set();
207
+ io;
208
+ constructor(io = {}) {
209
+ this.io = io;
210
+ }
211
+ /** Track a bridge child pid. Invalid pids (spawn failures yield `undefined`) are ignored. */
212
+ register(pid) {
213
+ if (typeof pid === 'number' && Number.isInteger(pid) && pid > 0)
214
+ this.tracked.add(pid);
215
+ }
216
+ /** Stop tracking a pid — call when the child's `close`/`exit` is observed. */
217
+ unregister(pid) {
218
+ this.tracked.delete(pid);
219
+ }
220
+ /** Currently tracked pids (registered only; discovery happens at kill time). */
221
+ pids() {
222
+ return [...this.tracked];
223
+ }
224
+ /**
225
+ * Deliver `signal`. For the signal-0 liveness probe, EPERM means "alive but not
226
+ * ours" and counts as existing; for real signals EPERM means the kill was NOT
227
+ * delivered and must not be reported as success (Copilot review on #300).
228
+ */
229
+ signal(pid, signal) {
230
+ try {
231
+ (this.io.kill ?? process.kill)(pid, signal);
232
+ return true;
233
+ }
234
+ catch (err) {
235
+ const eperm = err.code === 'EPERM';
236
+ return signal === 0 ? eperm : false;
237
+ }
238
+ }
239
+ /** Registered pids ∪ discovered direct-child bridges, deduplicated. */
240
+ targets() {
241
+ const all = new Set(this.tracked);
242
+ for (const pid of (this.io.discover ?? discoverBridgeChildren)(process.pid))
243
+ all.add(pid);
244
+ return [...all];
245
+ }
246
+ /**
247
+ * Graceful reap: SIGTERM every target, poll liveness for the grace window, SIGKILL
248
+ * survivors. Idempotent — dead pids are skipped, and the registry is cleared so a
249
+ * second invocation (the `exit` sweep after a signal-path shutdown) finds nothing
250
+ * registered and only re-discovers what actually still lives.
251
+ */
252
+ async shutdown() {
253
+ const graceMs = this.io.graceMs ?? BRIDGE_KILL_GRACE_MS;
254
+ const sleep = this.io.sleep ?? defaultSleep;
255
+ const targets = this.targets().filter((pid) => this.signal(pid, 0));
256
+ for (const pid of targets)
257
+ this.signal(pid, 'SIGTERM');
258
+ const deadline = Date.now() + graceMs;
259
+ let survivors = targets.filter((pid) => this.signal(pid, 0));
260
+ while (survivors.length > 0 && Date.now() < deadline) {
261
+ await sleep(Math.min(POLL_INTERVAL_MS, graceMs));
262
+ survivors = survivors.filter((pid) => this.signal(pid, 0));
263
+ }
264
+ const killed = survivors.filter((pid) => this.signal(pid, 'SIGKILL'));
265
+ this.tracked.clear();
266
+ return {
267
+ terminated: targets.filter((pid) => !survivors.includes(pid)),
268
+ killed,
269
+ undeliverable: survivors.filter((pid) => !killed.includes(pid)),
270
+ };
271
+ }
272
+ /**
273
+ * Synchronous best-effort sweep for the `exit` event, where no async work (and so no
274
+ * grace window) is possible. SIGTERM only — a synchronous SIGKILL would deny a bridge
275
+ * the chance to reap ITS child CLI, recreating the orphan problem one level down.
276
+ * The bridges' own stdin-EOF watchdog is the backstop for anything that ignores this.
277
+ */
278
+ sweepSync() {
279
+ for (const pid of this.targets())
280
+ this.signal(pid, 'SIGTERM');
281
+ this.tracked.clear();
282
+ }
283
+ }
284
+ /** The daemon-wide reaper the CLI wires into its shutdown handlers. */
285
+ export const bridgeReaper = new BridgeReaper();
286
+ //# sourceMappingURL=bridge-reaper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-reaper.js","sourceRoot":"","sources":["../../src/core/bridge-reaper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The first-class deliver phase (crew#293) — a run opens its own PR, opt-in.
3
+ *
4
+ * Productizes the operator-side `feature-pr` overlay proven during the DES-MERGE-001 campaign:
5
+ * a Tool phase appended after the workflow's last phase that COMMITS the run's work, pushes the
6
+ * run's branch and opens a PR via `gh`. What was data on one machine becomes a launch option
7
+ * (`deliver: "pr"` on `POST /runs`), composed PER RUN — the shared workflow def is never mutated.
8
+ *
9
+ * Field-proven hardening, replicated here:
10
+ * (a) the branch is derived from the run worktree's basename (`wicked/<run-id>`), falling back
11
+ * to the current branch when that ref does not exist;
12
+ * (b) the script REFUSES to push `main`/`master` (or an empty/detached branch name) — the
13
+ * deliver phase only ever pushes run branches;
14
+ * (c) it STAGES AND COMMITS the run's work, then rebases onto origin's default branch before
15
+ * pushing, and a conflicting rebase FAILS the phase visibly (aborting the rebase, pushing
16
+ * nothing) rather than pushing a conflicted tree;
17
+ * (d) `git push -u origin <branch>`;
18
+ * (e) `gh pr create --head <branch> --fill`, with gh's output and exit status captured
19
+ * SEPARATELY so a gh failure fails the phase carrying gh's own message;
20
+ * (f) the PR URL is the last line of the phase output.
21
+ *
22
+ * One deliberate change from the field version: NO gh account is baked into crew code (the
23
+ * overlay guarded a personal account). Instead, when the `GH_ACCOUNT` env var is set the script
24
+ * compares it against `gh api user -q .login` and runs
25
+ * `gh auth switch --hostname github.com --user "$GH_ACCOUNT"` only when they differ.
26
+ *
27
+ * Merge stays human: the phase opens the PR, never merges it.
28
+ *
29
+ * ## crew#317 — "pushed an empty branch and reported success"
30
+ *
31
+ * Run `d1bc72c2` (wicked-studio) delivered nothing while reporting `completed`. The persisted
32
+ * unit is the evidence, and it names the cause precisely. Its `tool_cmd` was NOT this script —
33
+ * it was the operator's hand-written `feature-pr` OVERLAY def, which begins `set -e` with **no
34
+ * `pipefail`**, so `gh pr create … | tail -1` reported `tail`'s status (0) and the phase passed
35
+ * with gh's error text where the PR URL belongs:
36
+ *
37
+ * ```text
38
+ * could not compute title or body defaults: could not find any commits between origin/main and
39
+ * wicked/d1bc72c2-…
40
+ * ```
41
+ *
42
+ * So the masking mechanism the issue hypothesised is real, but it belonged to the overlay, not
43
+ * here: `pipefail` IS in force for this executor (it is line 1 of this script, `bash -lc` runs it
44
+ * verbatim, and core's `run_tool_cmd` maps any non-zero exit to `StepStatus::Failed`). The three
45
+ * defects this script genuinely shared with the overlay are fixed below:
46
+ *
47
+ * 1. **No commit.** Agents write files and do not commit, so the pushed branch equalled the
48
+ * default branch. The script now stages and commits the run's work itself, and REFUSES to
49
+ * push when there is nothing to deliver.
50
+ * 2. **A masked result.** `| tail -1` discarded everything gh said except one line and made the
51
+ * phase's verdict depend on a pipe option. gh's output and status are now captured
52
+ * separately, and success is re-derived from a real PR URL rather than from an exit code.
53
+ * 3. **Ungoverned.** The phase shipped `verified_evidence: false` / `validator_pin: null`, so
54
+ * nothing re-derived what it claimed. It now declares `verified_evidence: true` — see
55
+ * {@link deliverPrPhase}.
56
+ */
57
+ import type { PhaseDef, WorkflowDef } from './types.js';
58
+ /** The id of the appended phase — also the collision probe when a def already delivers. */
59
+ export declare const DELIVER_PHASE_ID = "deliver";
60
+ /**
61
+ * The hardened deliver script, run as `bash -lc <script>` (login shell so the operator's PATH —
62
+ * where `gh` lives — is loaded, same as the field overlay).
63
+ *
64
+ * `set -euo pipefail` is load-bearing and verified in force for this executor (crew#317): the
65
+ * engine spawns `bash -lc` with this text verbatim, and `run_tool_cmd` turns a non-zero exit into
66
+ * `StepStatus::Failed`. It is no longer the ONLY thing standing between a failed `gh` and a green
67
+ * phase, though — the gh result is captured explicitly and success is re-derived from evidence.
68
+ *
69
+ * `intent` (the run's problem statement) rides in the commit subject; it is sanitised by
70
+ * {@link commitSubjectIntent} before it is spliced in.
71
+ */
72
+ export declare function deliverPrScript(intent?: string): string;
73
+ /**
74
+ * The deliver phase definition — the PhaseDef JSON shape core accepts, fully spelled out so the
75
+ * composed def round-trips through `registerWorkflow` (core's serde) and crew's own `WorkflowDef`
76
+ * type without casts. `gate: 'auto'` + `executes_code: false`: the phase is deterministic tooling,
77
+ * not governed agent work — its failure surface is the exit code + output, which core reports as
78
+ * a failed unit.
79
+ *
80
+ * ## Why `verified_evidence: true` and `validator_pin: null` (crew#317)
81
+ *
82
+ * The phase that touches the remote was the one phase nothing re-derived. The engine's phase model
83
+ * DOES let a Tool-executor phase carry a deterministic floor, and the mechanism is the flag rather
84
+ * than a pin crew mints itself:
85
+ *
86
+ * - a pin is a CONTENT ADDRESS into core's validator vault, and `attach_pinned_validators` is
87
+ * fail-closed on one that does not resolve — it BAILS the run at plan time. Authoring and
88
+ * approving a validator is `wicked-core provision-validator` + `approve-validator`, neither of
89
+ * which is exposed through the napi surface crew drives, so a pin invented here would fail
90
+ * every run of every deliver-composed workflow on a machine that had not been seeded by hand;
91
+ * - `verified_evidence: true` with no pin of its own is armed AT REGISTRATION by
92
+ * `enforce_verified_evidence` with the built-in evidence floor (`EVIDENCE_FLOOR_PIN`,
93
+ * criterion: "the run left a change in its worktree (done is re-derived from the diff, never
94
+ * asserted)"), which `pre_distribute` seeds on the plan path so it always resolves. Same
95
+ * mechanism `feature/test`, `bug/verify` and `migration/verify` reach it by.
96
+ *
97
+ * The floor then re-runs against the run's worktree at the gate, INDEPENDENTLY of anything this
98
+ * script printed, and denies the phase when the run left no change. Layer 2 (the agent judge)
99
+ * stays out of it: core hands the Tool path `agent_verdict: None`, so a tool phase's floor is
100
+ * deterministic and costs no LLM call. The PR-URL and branch-ahead assertions stay in the script
101
+ * because no vaulted floor can see the remote.
102
+ */
103
+ export declare function deliverPrPhase(dependsOn?: string[], intent?: string): PhaseDef;
104
+ /**
105
+ * Compose a PER-RUN workflow def: `base`'s phases (untouched — the shared def is never mutated)
106
+ * plus the deliver phase appended last, under a run-scoped id. The caller registers the result
107
+ * with the engine for THIS run only; nothing is written to the overlay dir and the composed id
108
+ * never enters the user-workflow registry, so the catalog (`GET /workflows`) stays clean.
109
+ *
110
+ * `intent` is the run's problem statement — it names WHAT was delivered in the commit subject
111
+ * (`wicked-crew run <run-id>: <intent>`); omit it and the subject carries the run id alone.
112
+ *
113
+ * Throws when `base` already carries a `deliver` phase — appending a second phase with the same
114
+ * id would be ambiguous at best; the caller launches such a def as-is instead (see
115
+ * `CoreAdapter.launchRun`).
116
+ */
117
+ export declare function composeDeliverWorkflow(base: WorkflowDef, runId: string, intent?: string): WorkflowDef;
118
+ //# sourceMappingURL=deliver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/core/deliver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,YAAY,CAAC;AA0B1C;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAqEvD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,SAAS,GAAE,MAAM,EAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAgBlF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,CAmBb"}