wicked-crew 0.6.0 → 0.7.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.
- package/README.md +103 -0
- package/dist/api/audit.d.ts +13 -0
- package/dist/api/audit.d.ts.map +1 -1
- package/dist/api/audit.js +18 -2
- package/dist/api/audit.js.map +1 -1
- package/dist/api/endpoint-manifest-live.d.ts +29 -0
- package/dist/api/endpoint-manifest-live.d.ts.map +1 -0
- package/dist/api/endpoint-manifest-live.js +72 -0
- package/dist/api/endpoint-manifest-live.js.map +1 -0
- package/dist/api/endpoint-manifest.d.ts +107 -0
- package/dist/api/endpoint-manifest.d.ts.map +1 -0
- package/dist/api/endpoint-manifest.js +108 -0
- package/dist/api/endpoint-manifest.js.map +1 -0
- package/dist/api/guidance-index.d.ts +39 -0
- package/dist/api/guidance-index.d.ts.map +1 -0
- package/dist/api/guidance-index.js +67 -0
- package/dist/api/guidance-index.js.map +1 -0
- package/dist/api/open-path.d.ts +16 -0
- package/dist/api/open-path.d.ts.map +1 -1
- package/dist/api/open-path.js +22 -0
- package/dist/api/open-path.js.map +1 -1
- package/dist/api/requirements.d.ts +7 -0
- package/dist/api/requirements.d.ts.map +1 -1
- package/dist/api/requirements.js +23 -2
- package/dist/api/requirements.js.map +1 -1
- package/dist/api/retry-index.d.ts +30 -0
- package/dist/api/retry-index.d.ts.map +1 -0
- package/dist/api/retry-index.js +45 -0
- package/dist/api/retry-index.js.map +1 -0
- package/dist/api/routes.d.ts +53 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +399 -30
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-files.d.ts +63 -0
- package/dist/api/run-files.d.ts.map +1 -0
- package/dist/api/run-files.js +271 -0
- package/dist/api/run-files.js.map +1 -0
- package/dist/api/server.d.ts +79 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +183 -8
- package/dist/api/server.js.map +1 -1
- package/dist/api/stall-watchdog.d.ts +62 -0
- package/dist/api/stall-watchdog.d.ts.map +1 -0
- package/dist/api/stall-watchdog.js +138 -0
- package/dist/api/stall-watchdog.js.map +1 -0
- package/dist/cli/index.js +89 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/core/adapter.d.ts +36 -10
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +245 -31
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/bridge-reaper.d.ts +134 -0
- package/dist/core/bridge-reaper.d.ts.map +1 -0
- package/dist/core/bridge-reaper.js +286 -0
- package/dist/core/bridge-reaper.js.map +1 -0
- package/dist/core/deliver.d.ts +118 -0
- package/dist/core/deliver.d.ts.map +1 -0
- package/dist/core/deliver.js +241 -0
- package/dist/core/deliver.js.map +1 -0
- package/dist/core/deliverable-floor.d.ts +155 -0
- package/dist/core/deliverable-floor.d.ts.map +1 -0
- package/dist/core/deliverable-floor.js +248 -0
- package/dist/core/deliverable-floor.js.map +1 -0
- package/dist/core/exec.d.ts +2 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js.map +1 -1
- package/dist/core/types.d.ts +79 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +52 -0
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +93 -12
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/chat-events.d.ts +207 -0
- package/dist/interactive/chat-events.d.ts.map +1 -0
- package/dist/interactive/chat-events.js +769 -0
- package/dist/interactive/chat-events.js.map +1 -0
- package/dist/interactive/demo-events.d.ts +283 -0
- package/dist/interactive/demo-events.d.ts.map +1 -0
- package/dist/interactive/demo-events.js +889 -0
- package/dist/interactive/demo-events.js.map +1 -0
- package/dist/interactive/draft-events.d.ts +87 -7
- package/dist/interactive/draft-events.d.ts.map +1 -1
- package/dist/interactive/draft-events.js +352 -49
- package/dist/interactive/draft-events.js.map +1 -1
- package/dist/interactive/edit-events.d.ts +25 -2
- package/dist/interactive/edit-events.d.ts.map +1 -1
- package/dist/interactive/edit-events.js +88 -9
- package/dist/interactive/edit-events.js.map +1 -1
- package/dist/interactive/repo-snapshot.d.ts +100 -0
- package/dist/interactive/repo-snapshot.d.ts.map +1 -0
- package/dist/interactive/repo-snapshot.js +289 -0
- package/dist/interactive/repo-snapshot.js.map +1 -0
- package/dist/projects/graph-paths.d.ts +122 -0
- package/dist/projects/graph-paths.d.ts.map +1 -0
- package/dist/projects/graph-paths.js +175 -0
- package/dist/projects/graph-paths.js.map +1 -0
- package/dist/projects/graph.d.ts +191 -0
- package/dist/projects/graph.d.ts.map +1 -0
- package/dist/projects/graph.js +829 -0
- package/dist/projects/graph.js.map +1 -0
- package/dist/projects/routes.d.ts +17 -0
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +139 -0
- package/dist/projects/routes.js.map +1 -1
- package/dist/qe/ledger.d.ts +3 -2
- package/dist/qe/ledger.d.ts.map +1 -1
- package/dist/qe/ledger.js +5 -4
- package/dist/qe/ledger.js.map +1 -1
- package/dist/studio/assets/index-D-BFUYnY.js +530 -0
- package/dist/studio/assets/index-D6S9zUtO.css +32 -0
- package/dist/studio/index.html +5 -3
- package/endpoint-manifest.json +624 -0
- package/package.json +8 -5
- package/dist/studio/assets/index-CCwXa1cn.js +0 -428
- package/dist/studio/assets/index-HWxo0h41.css +0 -32
|
@@ -0,0 +1,241 @@
|
|
|
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
|
+
/** The id of the appended phase — also the collision probe when a def already delivers. */
|
|
58
|
+
export const DELIVER_PHASE_ID = 'deliver';
|
|
59
|
+
/** How much of the run's intent rides in the commit subject before it is truncated. */
|
|
60
|
+
const INTENT_SUBJECT_CAP = 72;
|
|
61
|
+
/**
|
|
62
|
+
* The run's intent, reduced to something safe to embed in a single-quoted shell assignment and
|
|
63
|
+
* to use as a one-line commit subject.
|
|
64
|
+
*
|
|
65
|
+
* The intent is caller-supplied free text off `POST /runs`, and it is being spliced into a bash
|
|
66
|
+
* script — so this is a containment boundary, not cosmetics. Control characters (newlines
|
|
67
|
+
* included) and the single quote are REMOVED: with no `'` left, the value cannot escape the
|
|
68
|
+
* single-quoted assignment the script wraps it in, and with no control characters it cannot add
|
|
69
|
+
* a line. Everything else (unicode, `$`, backticks, backslashes) is inert inside single quotes
|
|
70
|
+
* and is kept, so the subject still reads like the intent it came from.
|
|
71
|
+
*/
|
|
72
|
+
function commitSubjectIntent(intent) {
|
|
73
|
+
return (intent ?? '')
|
|
74
|
+
.replace(/[\u0000-\u001f\u007f]+/g, ' ')
|
|
75
|
+
.replace(/'/g, '')
|
|
76
|
+
.replace(/\s+/g, ' ')
|
|
77
|
+
.trim()
|
|
78
|
+
.slice(0, INTENT_SUBJECT_CAP)
|
|
79
|
+
.trim();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The hardened deliver script, run as `bash -lc <script>` (login shell so the operator's PATH —
|
|
83
|
+
* where `gh` lives — is loaded, same as the field overlay).
|
|
84
|
+
*
|
|
85
|
+
* `set -euo pipefail` is load-bearing and verified in force for this executor (crew#317): the
|
|
86
|
+
* engine spawns `bash -lc` with this text verbatim, and `run_tool_cmd` turns a non-zero exit into
|
|
87
|
+
* `StepStatus::Failed`. It is no longer the ONLY thing standing between a failed `gh` and a green
|
|
88
|
+
* phase, though — the gh result is captured explicitly and success is re-derived from evidence.
|
|
89
|
+
*
|
|
90
|
+
* `intent` (the run's problem statement) rides in the commit subject; it is sanitised by
|
|
91
|
+
* {@link commitSubjectIntent} before it is spliced in.
|
|
92
|
+
*/
|
|
93
|
+
export function deliverPrScript(intent) {
|
|
94
|
+
return [
|
|
95
|
+
'set -euo pipefail',
|
|
96
|
+
// The engine concatenates the child's stdout and THEN its stderr, so anything git writes to
|
|
97
|
+
// stderr would land after the PR URL and break "(f) the URL is the last line". Folding stderr
|
|
98
|
+
// into stdout for the whole phase keeps the output in true chronological order and makes the
|
|
99
|
+
// final `echo "$URL"` genuinely last.
|
|
100
|
+
'exec 2>&1',
|
|
101
|
+
// Account guard — env-driven, never a name baked into crew code. Unset ⇒ whatever account
|
|
102
|
+
// gh already holds is used as-is.
|
|
103
|
+
'if [ -n "${GH_ACCOUNT:-}" ]; then L=$(gh api user -q .login); [ "$L" = "$GH_ACCOUNT" ] || gh auth switch --hostname github.com --user "$GH_ACCOUNT"; fi',
|
|
104
|
+
// (a) The run branch: wicked/<worktree-basename> (the engine names run worktrees by run id),
|
|
105
|
+
// falling back to the currently checked-out branch when that ref does not exist.
|
|
106
|
+
'R=$(basename "$PWD")',
|
|
107
|
+
'B="wicked/$R"',
|
|
108
|
+
'git rev-parse --verify "$B" >/dev/null 2>&1 || B=$(git branch --show-current)',
|
|
109
|
+
// Derive origin's default branch FIRST — the refusal below must cover a repo whose
|
|
110
|
+
// default is trunk/develop/anything, not just main/master (Copilot on #303).
|
|
111
|
+
'git fetch origin',
|
|
112
|
+
'D=$(git symbolic-ref -q --short refs/remotes/origin/HEAD || echo origin/main)',
|
|
113
|
+
'DEF="${D#origin/}"',
|
|
114
|
+
// (b) Refuse the repo's own default branch (by derived name), the classic names, and an
|
|
115
|
+
// empty name (detached HEAD), which would otherwise turn the push into a garbage ref.
|
|
116
|
+
'case "$B" in ""|main|master|"$DEF") echo "deliver: refusing to push branch \'$B\' — the deliver phase only pushes run branches, never the default branch"; exit 1;; esac',
|
|
117
|
+
// The commit below writes to whatever HEAD is, while the push sends $B. When those differ
|
|
118
|
+
// (a `wicked/<run-id>` ref exists but is NOT what this worktree has checked out) committing
|
|
119
|
+
// would put one branch's work on another and push a branch that never saw it. Refuse instead.
|
|
120
|
+
'C=$(git branch --show-current)',
|
|
121
|
+
'[ "$C" = "$B" ] || { echo "deliver: the worktree is on \'$C\' but the run branch is \'$B\' — refusing to commit one branch\'s work onto another; nothing was pushed"; exit 1; }',
|
|
122
|
+
// (c1) COMMIT THE RUN'S WORK (crew#317). Agents write files; they do not commit — which is
|
|
123
|
+
// the premise of core#291 and the reason `d1bc72c2` pushed a branch identical to origin/main.
|
|
124
|
+
// Author identity is deliberately NOT set here: the run worktree belongs to the operator's
|
|
125
|
+
// own clone, so `git commit` uses the repo/user config that already exists (and fails loudly,
|
|
126
|
+
// pushing nothing, if that config is missing). `git add -A` respects .gitignore.
|
|
127
|
+
`I='${commitSubjectIntent(intent)}'`,
|
|
128
|
+
'if [ -n "$I" ]; then M="wicked-crew run $R: $I"; else M="wicked-crew run $R"; fi',
|
|
129
|
+
'git add -A',
|
|
130
|
+
// Only commit when something is staged — a run that committed incrementally (core#280's
|
|
131
|
+
// liveness contract) leaves a clean tree and must not gain an empty commit here.
|
|
132
|
+
'git diff --cached --quiet || git commit -q -m "$M"',
|
|
133
|
+
// (c2) NOTHING TO DELIVER — no staged work AND no commits of its own. Fail LOUDLY before the
|
|
134
|
+
// remote is touched: an empty ref pushed under a run id is worse than a failed phase.
|
|
135
|
+
'A=$(git rev-list --count "$D..$B")',
|
|
136
|
+
'[ "$A" -ge 1 ] || { echo "deliver: nothing to deliver — the run produced no committed change ($B is not ahead of $D); nothing was pushed"; exit 1; }',
|
|
137
|
+
// (c3) Rebase onto origin's default branch so the PR opens mergeable. A conflict must fail
|
|
138
|
+
// the phase VISIBLY with nothing pushed — the abort leaves the worktree on the pre-rebase
|
|
139
|
+
// branch tip instead of mid-rebase.
|
|
140
|
+
'git rebase "$D" "$B" || { git rebase --abort >/dev/null 2>&1 || true; echo "deliver: rebase of $B onto $D failed (conflicts) — resolve on the branch and re-run; nothing was pushed"; exit 1; }',
|
|
141
|
+
// Re-derive after the rebase: it drops commits already upstream (patch-id equal), so a branch
|
|
142
|
+
// that WAS ahead can come out of a rebase carrying nothing of its own.
|
|
143
|
+
'A=$(git rev-list --count "$D..$B")',
|
|
144
|
+
'[ "$A" -ge 1 ] || { echo "deliver: nothing to deliver — the run produced no committed change (after rebasing onto $D, $B carries no commit of its own); nothing was pushed"; exit 1; }',
|
|
145
|
+
// (d) Push.
|
|
146
|
+
'git push -u origin "$B"',
|
|
147
|
+
// (e) Open the PR with gh's OUTPUT and EXIT STATUS captured separately (crew#317). The old
|
|
148
|
+
// `| tail -1` threw away everything gh said but one line and made the phase's verdict a
|
|
149
|
+
// property of a shell option; a gh failure now fails the phase carrying gh's own message.
|
|
150
|
+
'if ! OUT=$(gh pr create --head "$B" --fill 2>&1); then echo "$OUT"; echo "deliver: gh pr create failed for $B — no PR was opened"; exit 1; fi',
|
|
151
|
+
'echo "$OUT"',
|
|
152
|
+
// (f) DONE IS RE-DERIVED, NOT ASSERTED — twice, from two independent facts, before the phase
|
|
153
|
+
// is allowed to report a delivery:
|
|
154
|
+
// 1. gh actually produced a PR URL (an exit code alone is a claim, not evidence);
|
|
155
|
+
// 2. the ref ON THE REMOTE is ahead of origin's default branch by at least one commit.
|
|
156
|
+
"URL=$(printf '%s\\n' \"$OUT\" | grep -Eo 'https://[^[:space:]]+/pull/[0-9]+' | tail -1 || true)",
|
|
157
|
+
'[ -n "$URL" ] || { echo "deliver: gh pr create exited 0 but produced no PR URL for $B — refusing to report a delivery nothing can be pointed at"; exit 1; }',
|
|
158
|
+
'P=$(git rev-list --count "$D..origin/$B")',
|
|
159
|
+
'[ "$P" -ge 1 ] || { echo "deliver: $B is not ahead of $D on the remote after the push — refusing to report a delivery with no commits"; exit 1; }',
|
|
160
|
+
'echo "$URL"',
|
|
161
|
+
].join('\n');
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The deliver phase definition — the PhaseDef JSON shape core accepts, fully spelled out so the
|
|
165
|
+
* composed def round-trips through `registerWorkflow` (core's serde) and crew's own `WorkflowDef`
|
|
166
|
+
* type without casts. `gate: 'auto'` + `executes_code: false`: the phase is deterministic tooling,
|
|
167
|
+
* not governed agent work — its failure surface is the exit code + output, which core reports as
|
|
168
|
+
* a failed unit.
|
|
169
|
+
*
|
|
170
|
+
* ## Why `verified_evidence: true` and `validator_pin: null` (crew#317)
|
|
171
|
+
*
|
|
172
|
+
* The phase that touches the remote was the one phase nothing re-derived. The engine's phase model
|
|
173
|
+
* DOES let a Tool-executor phase carry a deterministic floor, and the mechanism is the flag rather
|
|
174
|
+
* than a pin crew mints itself:
|
|
175
|
+
*
|
|
176
|
+
* - a pin is a CONTENT ADDRESS into core's validator vault, and `attach_pinned_validators` is
|
|
177
|
+
* fail-closed on one that does not resolve — it BAILS the run at plan time. Authoring and
|
|
178
|
+
* approving a validator is `wicked-core provision-validator` + `approve-validator`, neither of
|
|
179
|
+
* which is exposed through the napi surface crew drives, so a pin invented here would fail
|
|
180
|
+
* every run of every deliver-composed workflow on a machine that had not been seeded by hand;
|
|
181
|
+
* - `verified_evidence: true` with no pin of its own is armed AT REGISTRATION by
|
|
182
|
+
* `enforce_verified_evidence` with the built-in evidence floor (`EVIDENCE_FLOOR_PIN`,
|
|
183
|
+
* criterion: "the run left a change in its worktree (done is re-derived from the diff, never
|
|
184
|
+
* asserted)"), which `pre_distribute` seeds on the plan path so it always resolves. Same
|
|
185
|
+
* mechanism `feature/test`, `bug/verify` and `migration/verify` reach it by.
|
|
186
|
+
*
|
|
187
|
+
* The floor then re-runs against the run's worktree at the gate, INDEPENDENTLY of anything this
|
|
188
|
+
* script printed, and denies the phase when the run left no change. Layer 2 (the agent judge)
|
|
189
|
+
* stays out of it: core hands the Tool path `agent_verdict: None`, so a tool phase's floor is
|
|
190
|
+
* deterministic and costs no LLM call. The PR-URL and branch-ahead assertions stay in the script
|
|
191
|
+
* because no vaulted floor can see the remote.
|
|
192
|
+
*/
|
|
193
|
+
export function deliverPrPhase(dependsOn = [], intent) {
|
|
194
|
+
return {
|
|
195
|
+
id: DELIVER_PHASE_ID,
|
|
196
|
+
kind: 'build',
|
|
197
|
+
executor: { type: 'tool', cmd: ['bash', '-lc', deliverPrScript(intent)] },
|
|
198
|
+
gate_type: null,
|
|
199
|
+
gate: 'auto',
|
|
200
|
+
executes_code: false,
|
|
201
|
+
verified_evidence: true,
|
|
202
|
+
required_deliverables: [],
|
|
203
|
+
depends_on: dependsOn,
|
|
204
|
+
role: 'neutral',
|
|
205
|
+
skill_ref: null,
|
|
206
|
+
allowed_skills: [],
|
|
207
|
+
validator_pin: null,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Compose a PER-RUN workflow def: `base`'s phases (untouched — the shared def is never mutated)
|
|
212
|
+
* plus the deliver phase appended last, under a run-scoped id. The caller registers the result
|
|
213
|
+
* with the engine for THIS run only; nothing is written to the overlay dir and the composed id
|
|
214
|
+
* never enters the user-workflow registry, so the catalog (`GET /workflows`) stays clean.
|
|
215
|
+
*
|
|
216
|
+
* `intent` is the run's problem statement — it names WHAT was delivered in the commit subject
|
|
217
|
+
* (`wicked-crew run <run-id>: <intent>`); omit it and the subject carries the run id alone.
|
|
218
|
+
*
|
|
219
|
+
* Throws when `base` already carries a `deliver` phase — appending a second phase with the same
|
|
220
|
+
* id would be ambiguous at best; the caller launches such a def as-is instead (see
|
|
221
|
+
* `CoreAdapter.launchRun`).
|
|
222
|
+
*/
|
|
223
|
+
export function composeDeliverWorkflow(base, runId, intent) {
|
|
224
|
+
if (base.phases.some((p) => p.id === DELIVER_PHASE_ID)) {
|
|
225
|
+
throw new Error(`workflow '${base.id}' already has a '${DELIVER_PHASE_ID}' phase — launch it without deliver: "pr"`);
|
|
226
|
+
}
|
|
227
|
+
const last = base.phases[base.phases.length - 1];
|
|
228
|
+
// Run ids are UUIDs from the route, but the CLI path accepts caller-supplied ids — keep the
|
|
229
|
+
// composed id inside the same safe charset `registerWorkflow` enforces for user defs.
|
|
230
|
+
const safeRunId = runId.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
231
|
+
// registerWorkflow enforces id.length <= 128; a caller-supplied CLI session id can be long.
|
|
232
|
+
// Truncate the run-id TAIL, keeping the base+marker prefix intact (Copilot on #303).
|
|
233
|
+
const composedId = `${base.id}-deliver-${safeRunId}`.slice(0, 128);
|
|
234
|
+
return {
|
|
235
|
+
// No `is_system` on purpose: core's overlay/register schema rejects unknown fields, and the
|
|
236
|
+
// composed def is engine-input, not catalog data.
|
|
237
|
+
id: composedId,
|
|
238
|
+
phases: [...base.phases, deliverPrPhase(last !== undefined ? [last.id] : [], intent)],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=deliver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.js","sourceRoot":"","sources":["../../src/core/deliver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE DELIVERABLE FLOOR (crew#311) — "done" is re-derived from the artifact, never asserted.
|
|
3
|
+
*
|
|
4
|
+
* ## The defect
|
|
5
|
+
*
|
|
6
|
+
* A governed unit that produced NOTHING still passed its execution gate on the strength of a
|
|
7
|
+
* prose reply. The engine's only universal floor is the SUBSTANCE floor
|
|
8
|
+
* (wicked-core `actor.rs`, "phase produced no reviewable substance"): a governed
|
|
9
|
+
* Creator/Neutral unit is rejected only when its output carries BOTH under 200 trimmed chars
|
|
10
|
+
* of prose AND no worktree change. So ~200 characters of narration — "I'll read the design
|
|
11
|
+
* spec, let me check the API types…" — clears it, with no file anywhere. Prose length stood in
|
|
12
|
+
* for evidence.
|
|
13
|
+
*
|
|
14
|
+
* The engine has two real evidence instruments, and neither reaches a run of this shape:
|
|
15
|
+
*
|
|
16
|
+
* 1. `validator_pin` → the built-in EVIDENCE floor (`builtin_floors::EVIDENCE_FLOOR_PIN`,
|
|
17
|
+
* criterion "the run left a change in its worktree (done is re-derived from the diff, never
|
|
18
|
+
* asserted)"). It re-verifies a git DIFF, and is fail-closed on a run with no worktree — so
|
|
19
|
+
* an UNBOUND run (`repoRef` omitted, `workdir: null`) can never satisfy it. Every crew
|
|
20
|
+
* interactive seam launches unbound on purpose (wicked-core#293/#294), and their deliverable
|
|
21
|
+
* is a FILE in a declared write root, invisible to a diff.
|
|
22
|
+
* 2. `required_deliverables` → enforced by wicked-core as of FINDING-101, but only in the
|
|
23
|
+
* WRAPPED-CLI runner (`execute_wrapped.rs`), only against the unit's own cwd, and any
|
|
24
|
+
* absolute or `..`-escaping path is counted MISSING by construction. The ACP runner — the
|
|
25
|
+
* path a real seat takes — never consults it. So for a run whose deliverable is an absolute
|
|
26
|
+
* path under `extraWriteRoots`, declaring it there would fail every run on one runner and
|
|
27
|
+
* check nothing on the other. That gap is wicked-core's to close
|
|
28
|
+
* (wicked-core#297, items 1 and 3); once it is, this phase can retire in favour of the
|
|
29
|
+
* engine field. Crew must not paper over it meanwhile by declaring something unverifiable.
|
|
30
|
+
*
|
|
31
|
+
* ## What this module does
|
|
32
|
+
*
|
|
33
|
+
* It gives crew the instrument it can honestly own: a deterministic TOOL phase, appended to a
|
|
34
|
+
* PER-RUN copy of the workflow def (the exact mechanism `core/deliver.ts` proved for
|
|
35
|
+
* `deliver: "pr"` — the shared def is never mutated, nothing lands in the overlay dir, the
|
|
36
|
+
* catalog never grows a per-run entry), which asserts that the artifacts the launcher declared
|
|
37
|
+
* EXIST, are non-empty, and were written by THIS run (crew#320 — existence alone re-derives
|
|
38
|
+
* nothing at a path a prior run already populated). A Tool phase's failure surface is its exit
|
|
39
|
+
* code: wicked-core's
|
|
40
|
+
* `run_tool_cmd` maps a non-zero exit to `StepStatus::Failed`, which fails the unit and the run
|
|
41
|
+
* through the unchanged completion path. No LLM, no worktree, no judgement about the content —
|
|
42
|
+
* only "the phase promised this file; is it there".
|
|
43
|
+
*
|
|
44
|
+
* That is deliberately a FLOOR and not a review, in exactly the sense `builtin_floors` documents:
|
|
45
|
+
* it proves an artifact exists. It says nothing about whether the artifact is correct, or even
|
|
46
|
+
* related to the task. What it removes is the failure mode crew#311 recorded — a unit whose
|
|
47
|
+
* `Write` was policy-denied, which therefore produced no file at all, reporting done.
|
|
48
|
+
*
|
|
49
|
+
* ## The report
|
|
50
|
+
*
|
|
51
|
+
* The script prints what was EXPECTED and what was FOUND before it exits, on both branches, so
|
|
52
|
+
* the failure is legible from the unit output alone: which paths were declared, which exist
|
|
53
|
+
* (with their size), and which are missing and why (absent / empty file / empty directory).
|
|
54
|
+
* An operator reading a failed run must never have to guess what the phase was supposed to
|
|
55
|
+
* produce.
|
|
56
|
+
*/
|
|
57
|
+
import type { PhaseDef, WorkflowDef } from './types.js';
|
|
58
|
+
/** The id of the appended floor phase — also the collision probe when a def already carries one. */
|
|
59
|
+
export declare const DELIVERABLE_FLOOR_PHASE_ID = "verify-deliverables";
|
|
60
|
+
/** The marker line the failing branch prints. Asserted by tests and greppable in a unit output. */
|
|
61
|
+
export declare const DELIVERABLE_FLOOR_FAILURE_MARKER = "[wicked-crew] DELIVERABLE FLOOR FAILED";
|
|
62
|
+
/**
|
|
63
|
+
* Slack allowed on the freshness comparison, in milliseconds (crew#320).
|
|
64
|
+
*
|
|
65
|
+
* The launch timestamp comes from the daemon's `Date.now()` (millisecond resolution); the
|
|
66
|
+
* artifact timestamp comes from the FILESYSTEM, and not every filesystem keeps milliseconds —
|
|
67
|
+
* HFS+ and ext3 truncate mtime to whole seconds, and some SMB/NFS mounts are coarser still. A
|
|
68
|
+
* file genuinely written 400 ms after a launch at `…:07.600Z` can therefore report an mtime of
|
|
69
|
+
* `…:07.000Z` and read as older than the launch that produced it.
|
|
70
|
+
*
|
|
71
|
+
* One second absorbs that truncation without weakening the check this exists to make: the stale
|
|
72
|
+
* artifact crew#320 describes is the PREVIOUS run's output, which predates this run's launch by
|
|
73
|
+
* that run's whole remaining lifetime plus the turnaround before the next one — orders of
|
|
74
|
+
* magnitude more than a timestamp-granularity rounding error.
|
|
75
|
+
*/
|
|
76
|
+
export declare const DELIVERABLE_FLOOR_MTIME_SLACK_MS = 1000;
|
|
77
|
+
/**
|
|
78
|
+
* The floor program, run as `node -e <script> <launchedAtMs> <path...>`.
|
|
79
|
+
*
|
|
80
|
+
* Node rather than a shell: this must work identically on macOS, Linux, and Windows, and the
|
|
81
|
+
* daemon already IS node, so `process.execPath` is a guaranteed-present absolute interpreter
|
|
82
|
+
* (see {@link deliverableFloorPhase}). `-e` runs CommonJS, so `require` is available; the launch
|
|
83
|
+
* timestamp and the declared paths arrive as `process.argv.slice(1)`, passed as argv rather than
|
|
84
|
+
* interpolated into a shell string so a path containing a space, a quote, or a `$` cannot be
|
|
85
|
+
* re-parsed.
|
|
86
|
+
*
|
|
87
|
+
* A path counts as PRODUCED when it exists, carries bytes, and was written by THIS RUN:
|
|
88
|
+
*
|
|
89
|
+
* - a non-empty file whose `mtimeMs` is at or after the launch timestamp;
|
|
90
|
+
* - a directory holding at least one direct entry whose `mtimeMs` is at or after it (a phase may
|
|
91
|
+
* declare a directory of outputs; the directory's own mtime is not enough, because a directory
|
|
92
|
+
* left over from a prior run keeps its entries and its mtime).
|
|
93
|
+
*
|
|
94
|
+
* A zero-byte file is NOT evidence — it is the same "nothing was produced" this exists to catch,
|
|
95
|
+
* and the draft/chat seams already treated an empty deliverable as a failure at their own
|
|
96
|
+
* post-hoc check.
|
|
97
|
+
*
|
|
98
|
+
* ## Why the timestamp (crew#320)
|
|
99
|
+
*
|
|
100
|
+
* Existence alone re-derives nothing when the path can be pre-populated. The interactive seams
|
|
101
|
+
* copy their deliverable to `outPath` and never remove it, and demo/draft/edit run directories
|
|
102
|
+
* are keyed by DOCUMENT ID (edit by `doc:version`) — so a second run over the same key starts
|
|
103
|
+
* with the previous run's file already sitting at the declared path. The floor would find it,
|
|
104
|
+
* find bytes, and pass: the artifact exists, but this run did not produce it. "Done" would be
|
|
105
|
+
* asserted by a leftover rather than derived from this run's work — the exact failure shape one
|
|
106
|
+
* level in from the one crew#311 closed.
|
|
107
|
+
*
|
|
108
|
+
* The comparison is the stronger of the two available fixes. Clearing `outPath` at launch would
|
|
109
|
+
* also work, but only for as long as every seam remembers to clear it; the mtime check survives
|
|
110
|
+
* a seam that forgets, and it survives a seam that has not been written yet.
|
|
111
|
+
*
|
|
112
|
+
* A launch timestamp that does not parse FAILS CLOSED. An armed floor that cannot judge freshness
|
|
113
|
+
* is not a floor, and passing on the grounds that the check itself is broken is precisely the
|
|
114
|
+
* "assert done" move this module exists to refuse.
|
|
115
|
+
*/
|
|
116
|
+
export declare function deliverableFloorScript(): string;
|
|
117
|
+
/**
|
|
118
|
+
* The floor PhaseDef for `paths`, judged against `launchedAtMs`.
|
|
119
|
+
*
|
|
120
|
+
* `launchedAtMs` is positional and required rather than defaulted (crew#320): a default would let
|
|
121
|
+
* a new caller arm a floor that checks existence only, and the resulting run would report done on
|
|
122
|
+
* a leftover with nothing in its output saying the freshness half was never applied. The compiler
|
|
123
|
+
* asking the question is the point.
|
|
124
|
+
*
|
|
125
|
+
* `cmd[0]` is `process.execPath` — the ABSOLUTE path of the node binary already running the
|
|
126
|
+
* daemon — not the bare name `node`. wicked-core preflights every Tool phase at launch
|
|
127
|
+
* (`workflow::preflight_tool_phases`) and REFUSES to start a run whose tool binary does not
|
|
128
|
+
* resolve; a bare `node` resolves only if the engine's PATH happens to carry one, which is
|
|
129
|
+
* exactly the kind of environment-dependent gate that fails in production and passes in tests.
|
|
130
|
+
* The composed def is per-run and hot-registered only, so baking a machine-local path into it
|
|
131
|
+
* costs nothing: it never reaches the catalog or the overlay dir.
|
|
132
|
+
*
|
|
133
|
+
* `role: 'neutral'` + `executes_code: false` + `governed`-free Tool dispatch: this phase is
|
|
134
|
+
* deterministic tooling, so the substance floor, the agent judge, and the input-governance fold
|
|
135
|
+
* do not apply to it — its verdict IS its exit code.
|
|
136
|
+
*/
|
|
137
|
+
export declare function deliverableFloorPhase(paths: readonly string[], launchedAtMs: number, dependsOn?: string[]): PhaseDef;
|
|
138
|
+
/**
|
|
139
|
+
* Compose a PER-RUN def: `base`'s phases (untouched — the shared def is never mutated) plus the
|
|
140
|
+
* deliverable floor appended last, under a run-scoped id. Mirrors `composeDeliverWorkflow`'s
|
|
141
|
+
* contract exactly, including the id charset/length rules `registerWorkflow` enforces and the
|
|
142
|
+
* deliberate absence of `is_system` (core's register schema rejects unknown fields on this path,
|
|
143
|
+
* and a composed def is engine input, not catalog data).
|
|
144
|
+
*
|
|
145
|
+
* Throws on an empty `paths` list — a floor over nothing would pass vacuously, which is the
|
|
146
|
+
* defect, not the fix. Throws on a def that already carries the floor phase, for the same
|
|
147
|
+
* ambiguity reason `composeDeliverWorkflow` does.
|
|
148
|
+
*
|
|
149
|
+
* `launchedAtMs` defaults to composition time, which is the correct instant on every path that
|
|
150
|
+
* exists: composition is the last thing that happens before `core.launchRun`, so nothing this run
|
|
151
|
+
* produces can predate it, and a nonsense value is rejected here rather than being discovered by
|
|
152
|
+
* the tool phase minutes later (crew#320).
|
|
153
|
+
*/
|
|
154
|
+
export declare function composeDeliverableFloor(base: WorkflowDef, runId: string, paths: readonly string[], launchedAtMs?: number): WorkflowDef;
|
|
155
|
+
//# sourceMappingURL=deliverable-floor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliverable-floor.d.ts","sourceRoot":"","sources":["../../src/core/deliverable-floor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD,oGAAoG;AACpG,eAAO,MAAM,0BAA0B,wBAAwB,CAAC;AAEhE,mGAAmG;AACnG,eAAO,MAAM,gCAAgC,2CACH,CAAC;AAE3C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gCAAgC,OAAQ,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAuC/C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,YAAY,EAAE,MAAM,EACpB,SAAS,GAAE,MAAM,EAAO,GACvB,QAAQ,CA4BV;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,YAAY,GAAE,MAAmB,GAChC,WAAW,CAiCb"}
|