wicked-crew 0.8.4 → 0.8.6
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/dist/api/ask-paths.d.ts +17 -0
- package/dist/api/ask-paths.d.ts.map +1 -1
- package/dist/api/ask-paths.js +13 -0
- package/dist/api/ask-paths.js.map +1 -1
- package/dist/api/ask-relay.d.ts +2 -0
- package/dist/api/ask-relay.d.ts.map +1 -1
- package/dist/api/ask-relay.js +35 -0
- package/dist/api/ask-relay.js.map +1 -1
- package/dist/api/chat-scope.d.ts +4 -4
- package/dist/api/chat-scope.d.ts.map +1 -1
- package/dist/api/chat-scope.js +1 -1
- package/dist/api/chat-turns.d.ts +12 -4
- package/dist/api/chat-turns.d.ts.map +1 -1
- package/dist/api/chat-turns.js +9 -4
- package/dist/api/chat-turns.js.map +1 -1
- package/dist/api/delivery-index.d.ts +53 -0
- package/dist/api/delivery-index.d.ts.map +1 -1
- package/dist/api/delivery-index.js +117 -0
- package/dist/api/delivery-index.js.map +1 -1
- package/dist/api/mcp.d.ts +4 -4
- package/dist/api/onboarding-capture.d.ts +58 -0
- package/dist/api/onboarding-capture.d.ts.map +1 -0
- package/dist/api/onboarding-capture.js +125 -0
- package/dist/api/onboarding-capture.js.map +1 -0
- package/dist/api/routes.d.ts +11 -3
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +84 -9
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-timing-index.d.ts +20 -0
- package/dist/api/run-timing-index.d.ts.map +1 -1
- package/dist/api/run-timing-index.js +46 -0
- package/dist/api/run-timing-index.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +52 -37
- package/dist/api/server.js.map +1 -1
- package/dist/api/skills.d.ts.map +1 -1
- package/dist/api/skills.js +3 -1
- package/dist/api/skills.js.map +1 -1
- package/dist/api/watch-routes.d.ts +3 -1
- package/dist/api/watch-routes.d.ts.map +1 -1
- package/dist/api/watch-routes.js +3 -0
- package/dist/api/watch-routes.js.map +1 -1
- package/dist/core/adapter.d.ts +10 -36
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +81 -57
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/deliver-exclusions.d.ts +15 -1
- package/dist/core/deliver-exclusions.d.ts.map +1 -1
- package/dist/core/deliver-exclusions.js +25 -3
- package/dist/core/deliver-exclusions.js.map +1 -1
- package/dist/core/deliver-text.d.ts +84 -10
- package/dist/core/deliver-text.d.ts.map +1 -1
- package/dist/core/deliver-text.js +279 -10
- package/dist/core/deliver-text.js.map +1 -1
- package/dist/core/deliver.d.ts +45 -0
- package/dist/core/deliver.d.ts.map +1 -1
- package/dist/core/deliver.js +136 -27
- package/dist/core/deliver.js.map +1 -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 +17 -0
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +69 -3
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/bridge-root.d.ts +7 -0
- package/dist/interactive/bridge-root.d.ts.map +1 -1
- package/dist/interactive/bridge-root.js +11 -0
- package/dist/interactive/bridge-root.js.map +1 -1
- package/dist/projects/routes.d.ts +6 -2
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +6 -0
- package/dist/projects/routes.js.map +1 -1
- package/dist/projects/settings.d.ts +2 -0
- package/dist/projects/settings.d.ts.map +1 -1
- package/dist/projects/settings.js +4 -0
- package/dist/projects/settings.js.map +1 -1
- package/dist/skills/refs.d.ts +6 -0
- package/dist/skills/refs.d.ts.map +1 -1
- package/dist/skills/refs.js +8 -0
- package/dist/skills/refs.js.map +1 -1
- package/dist/skills/runtime.d.ts +17 -2
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +30 -1
- package/dist/skills/runtime.js.map +1 -1
- package/dist/skills/store.d.ts.map +1 -1
- package/dist/skills/store.js +9 -1
- package/dist/skills/store.js.map +1 -1
- package/dist/standing-orders/parse.d.ts +9 -5
- package/dist/standing-orders/parse.d.ts.map +1 -1
- package/dist/standing-orders/parse.js +85 -37
- package/dist/standing-orders/parse.js.map +1 -1
- package/dist/standing-orders/routes.d.ts +21 -21
- package/dist/standing-orders/store.d.ts +9 -9
- package/dist/studio/assets/{index-B-ce66JW.js → index-CUNMURZ4.js} +122 -122
- package/dist/studio/assets/index-DPqNRzrT.css +32 -0
- package/dist/studio/editors/wicked-page/editor.json +1 -1
- package/dist/studio/editors/wicked-page/index.html +1 -1
- package/dist/studio/index.html +2 -2
- package/dist/studio/testid-inventory.json +141 -3
- package/dist/watch/checks/claim-vs-evidence.js +2 -2
- package/dist/watch/checks/claim-vs-evidence.js.map +1 -1
- package/dist/watch/checks/deliver-audit.js +2 -2
- package/dist/watch/checks/deliver-audit.js.map +1 -1
- package/dist/watch/checks/discovery.d.ts.map +1 -1
- package/dist/watch/checks/discovery.js +5 -3
- package/dist/watch/checks/discovery.js.map +1 -1
- package/dist/watch/checks/index.d.ts.map +1 -1
- package/dist/watch/checks/index.js +5 -0
- package/dist/watch/checks/index.js.map +1 -1
- package/dist/watch/checks/quiet-after-claim.js +2 -2
- package/dist/watch/checks/quiet-after-claim.js.map +1 -1
- package/dist/watch/checks/scope-drift.d.ts.map +1 -1
- package/dist/watch/checks/scope-drift.js +4 -3
- package/dist/watch/checks/scope-drift.js.map +1 -1
- package/dist/watch/checks/team-path.d.ts +38 -0
- package/dist/watch/checks/team-path.d.ts.map +1 -0
- package/dist/watch/checks/team-path.js +220 -0
- package/dist/watch/checks/team-path.js.map +1 -0
- package/dist/watch/checks/ungated.js +2 -2
- package/dist/watch/checks/ungated.js.map +1 -1
- package/dist/watch/checks/warned-rule.js +2 -2
- package/dist/watch/checks/warned-rule.js.map +1 -1
- package/dist/watch/registry.d.ts +18 -2
- package/dist/watch/registry.d.ts.map +1 -1
- package/dist/watch/registry.js +23 -4
- package/dist/watch/registry.js.map +1 -1
- package/dist/watch/types.d.ts +8 -2
- package/dist/watch/types.d.ts.map +1 -1
- package/endpoint-manifest.json +1 -1
- package/package.json +3 -3
- package/watch/entries/help-unanswered.json +16 -0
- package/watch/entries/path-repicked.json +13 -0
- package/watch/entries/reviewer-absent.json +13 -0
- package/dist/studio/assets/index-TvS52qj5.css +0 -32
|
@@ -27,13 +27,17 @@
|
|
|
27
27
|
* that committed incrementally keeps its own commits untouched — the PR title is composed from the
|
|
28
28
|
* run either way; only the deliver phase's own commit is guaranteed to match it.
|
|
29
29
|
*/
|
|
30
|
-
import type { GateSpec, PhaseDef, SessionView } from './types.js';
|
|
30
|
+
import type { GateSpec, PhaseDef, SessionView, WorkUnit } from './types.js';
|
|
31
31
|
/**
|
|
32
|
-
* The PR title cap
|
|
33
|
-
*
|
|
32
|
+
* The PR title cap (crew#860, S17b F15 / S17a F20). GitHub ACCEPTS 256 characters, and under that
|
|
33
|
+
* cap wicked-studio#586 opened as the intent's first 244 — a whole paragraph, cut mid-sentence,
|
|
34
|
+
* wider than the PR list renders. A title is a headline: the intent's FIRST SENTENCE, cut at a word
|
|
35
|
+
* boundary inside 100 characters; the full intent is the body's first section and the run id is
|
|
36
|
+
* named there. (crew#550 P-1 — a 72-character cap severed a quoted phrase — is still honoured: the
|
|
37
|
+
* cut is depth-aware, see {@link boundedTitle}.) The COMMIT subject keeps git's conventional width,
|
|
34
38
|
* {@link COMMIT_SUBJECT_MAX}; a longer title then opens the commit body in full.
|
|
35
39
|
*/
|
|
36
|
-
export declare const DELIVER_TITLE_MAX =
|
|
40
|
+
export declare const DELIVER_TITLE_MAX = 100;
|
|
37
41
|
/** The commit subject cap — git's conventional subject width (crew#550). */
|
|
38
42
|
export declare const COMMIT_SUBJECT_MAX = 72;
|
|
39
43
|
/** One phase of the run, with as much as is known about it at composition time. */
|
|
@@ -69,6 +73,27 @@ export interface DeliverVerdictFact {
|
|
|
69
73
|
verdict: string;
|
|
70
74
|
reason: string | null;
|
|
71
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* What a unit's seat REPORTED in its final output (crew#860): the governed-worker output contract's
|
|
78
|
+
* "What you did / Commands run (with exit codes) / Counts / Findings" block — for a creator, what
|
|
79
|
+
* changed and the recipe it ran; for an evaluator, its findings by severity. Read from the unit's
|
|
80
|
+
* captured output by {@link extractUnitReport}; `report` is bounded markdown, never empty.
|
|
81
|
+
*/
|
|
82
|
+
export interface DeliverReportFact {
|
|
83
|
+
phase: string;
|
|
84
|
+
seat: string | null;
|
|
85
|
+
role: 'creator' | 'evaluator';
|
|
86
|
+
report: string;
|
|
87
|
+
/** The commands the seat says it ran, with the exit code it pasted beside each
|
|
88
|
+
* ({@link extractRecipeExits}) — a creator's recipe, read for the done-when evidence table.
|
|
89
|
+
* Absent/empty = the output named no command with an exit code. */
|
|
90
|
+
exits?: DeliverRecipeExit[];
|
|
91
|
+
}
|
|
92
|
+
/** One command a seat reported running, with the exit code it pasted (crew#860). */
|
|
93
|
+
export interface DeliverRecipeExit {
|
|
94
|
+
command: string;
|
|
95
|
+
exit: number;
|
|
96
|
+
}
|
|
72
97
|
/** Everything the composer needs. Built by {@link factsFromRun} or {@link factsFromWorkflow}. */
|
|
73
98
|
export interface DeliverTextFacts {
|
|
74
99
|
runId: string;
|
|
@@ -102,6 +127,10 @@ export interface DeliverTextFacts {
|
|
|
102
127
|
* evaluator units' output by {@link extractFollowUps}). `[]` = read, none flagged; absent = not
|
|
103
128
|
* read (a definition-time composition), and the section is left out. */
|
|
104
129
|
followUps?: string[];
|
|
130
|
+
/** crew#860: the creator and evaluator seats' final reports, read from the unit outputs
|
|
131
|
+
* ({@link reportsFromOutputs}). `[]` = read, nothing reportable; absent = the outputs could not
|
|
132
|
+
* be read (a definition-time composition, or an adapter without transcripts) — said in the body. */
|
|
133
|
+
reports?: DeliverReportFact[];
|
|
105
134
|
}
|
|
106
135
|
export interface DeliverText {
|
|
107
136
|
/** The PR title: ≤ {@link DELIVER_TITLE_MAX} characters, one line, never cut mid-word. */
|
|
@@ -122,12 +151,21 @@ export interface IssueRefs {
|
|
|
122
151
|
*/
|
|
123
152
|
export declare function conventionalPrefix(workflowId: string | null | undefined): string;
|
|
124
153
|
/**
|
|
125
|
-
* The
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
|
|
154
|
+
* The first SENTENCE of a headline line (crew#860): the text up to the first `.`, `!` or `?` that
|
|
155
|
+
* is followed by whitespace or the end, at quote/bracket depth 0 (a period inside `(…)` or `"…"`
|
|
156
|
+
* does not end the sentence), and not the dot of an abbreviation. A closing `.` is dropped (a
|
|
157
|
+
* headline carries no full stop); `!` and `?` stay. A dot glued to the next word (`0.8.4`,
|
|
158
|
+
* `deliver.ts`, `wicked-studio#586.`) never splits. No terminator ⇒ the whole line.
|
|
159
|
+
*/
|
|
160
|
+
export declare function firstSentence(line: string): string;
|
|
161
|
+
/**
|
|
162
|
+
* The PR title / commit subject: the FIRST SENTENCE of the intent's first prose line (crew#860),
|
|
163
|
+
* whole when it fits, otherwise cut at the last word boundary that leaves room for a single `…` —
|
|
164
|
+
* so the result is ≤ {@link DELIVER_TITLE_MAX} characters and never ends mid-word (the F-3R2-014
|
|
165
|
+
* headline `…scenario CLN-2) aga`). Dangling punctuation before the ellipsis is dropped. A blank
|
|
166
|
+
* intent names the run instead — through the SAME bounded cut, so a long caller-supplied session id
|
|
167
|
+
* (the CLI passes `--session` through) is never cut mid-id either (Copilot on #525): the body names
|
|
168
|
+
* the run id in full.
|
|
131
169
|
*/
|
|
132
170
|
export declare function deliverTitle(intent: string, runId: string, workflowId?: string | null): string;
|
|
133
171
|
/**
|
|
@@ -182,6 +220,8 @@ export declare function composeEmbeddedDeliverText(f: DeliverTextFacts): Deliver
|
|
|
182
220
|
* references of the FULL intent while `f.intent` is the bounded copy ({@link composeEmbeddedDeliverText}).
|
|
183
221
|
*/
|
|
184
222
|
export declare function composeDeliverText(f: DeliverTextFacts, links?: IssueRefs): DeliverText;
|
|
223
|
+
/** At most this many pasted recipe commands per phase in the done-when table. */
|
|
224
|
+
export declare const RECIPE_EXITS_MAX = 12;
|
|
185
225
|
/** The one shape both carriers speak: title, blank line, body, trailing newline. */
|
|
186
226
|
export declare function framedDeliverText(text: DeliverText): string;
|
|
187
227
|
/**
|
|
@@ -215,6 +255,8 @@ export declare function factsFromRun(view: SessionView, runUrl: string | null, r
|
|
|
215
255
|
} | null;
|
|
216
256
|
/** crew#550: the evaluator follow-ups, when the caller read the evaluator units' output. */
|
|
217
257
|
followUps?: string[];
|
|
258
|
+
/** crew#860: the seats' final reports, when the caller read the unit outputs. */
|
|
259
|
+
reports?: DeliverReportFact[];
|
|
218
260
|
}): DeliverTextFacts;
|
|
219
261
|
/** What is known at LAUNCH, from the workflow definition — the script's embedded fallback. */
|
|
220
262
|
export declare function factsFromWorkflow(input: {
|
|
@@ -268,4 +310,36 @@ export declare const FOLLOW_UP_MAX_CHARS = 300;
|
|
|
268
310
|
* de-duplicated, capped at {@link FOLLOW_UPS_MAX}.
|
|
269
311
|
*/
|
|
270
312
|
export declare function extractFollowUps(text: string): string[];
|
|
313
|
+
/** How much of one seat's report rides a PR body (characters); the run record has the rest. */
|
|
314
|
+
export declare const REPORT_MAX_CHARS = 4000;
|
|
315
|
+
/** At most this many reports ride a PR body (one per creator / evaluator unit, by ord). */
|
|
316
|
+
export declare const REPORTS_MAX = 6;
|
|
317
|
+
/**
|
|
318
|
+
* A seat's final REPORT from its captured output (crew#860): the text from the first report heading
|
|
319
|
+
* ({@link REPORT_HEADING}) to the end — the output contract puts the report LAST, after the work —
|
|
320
|
+
* or, when no heading is found, the tail of the output (its last 40 lines), said so. `VERDICT:`
|
|
321
|
+
* lines are removed (the composer states the recorded verdict beside the phase). Headings are
|
|
322
|
+
* demoted below the body's own `##` / `###` so the report cannot restyle the PR; an unclosed code
|
|
323
|
+
* fence is closed so it cannot swallow the footer; control characters are dropped; the result is
|
|
324
|
+
* cut at a line boundary inside {@link REPORT_MAX_CHARS} and the cut is disclosed. `null` when the
|
|
325
|
+
* output carries nothing reportable (empty, or whitespace only).
|
|
326
|
+
*/
|
|
327
|
+
export declare function extractUnitReport(text: string | null | undefined): string | null;
|
|
328
|
+
/**
|
|
329
|
+
* The seats' reports for a run (crew#860), one per creator / evaluator unit in ord order, read
|
|
330
|
+
* through `readOutput` (the adapter's `workOutput`, injected so the composer stays pure). A unit
|
|
331
|
+
* whose output cannot be read or carries nothing reportable contributes nothing; the result is
|
|
332
|
+
* `[]` when nothing was reportable, never `undefined` — the caller says "could not read" only when
|
|
333
|
+
* it has no reader at all.
|
|
334
|
+
*/
|
|
335
|
+
export declare function reportsFromOutputs(view: SessionView, readOutput: (unit: WorkUnit) => Promise<string | null>): Promise<DeliverReportFact[]>;
|
|
336
|
+
/**
|
|
337
|
+
* The commands a seat's output says it ran, with the exit code it pasted beside each (crew#860) —
|
|
338
|
+
* the "Commands run (with exit codes)" half of the governed-worker output contract, in the shapes
|
|
339
|
+
* seats write it: `` - `npm run lint` → exit 0 ``, `` | `npx vitest run` | 0 | ``, `typecheck: exit 0`.
|
|
340
|
+
* A line counts only when it names BOTH a command (a code span, else the list item's text before
|
|
341
|
+
* the exit word) and an exit code; deduplicated by command (the LAST report wins — a re-run
|
|
342
|
+
* supersedes), at most {@link RECIPE_EXITS_MAX}. Never throws; `[]` when nothing parses.
|
|
343
|
+
*/
|
|
344
|
+
export declare function extractRecipeExits(text: string | null | undefined): DeliverRecipeExit[];
|
|
271
345
|
//# sourceMappingURL=deliver-text.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliver-text.d.ts","sourceRoot":"","sources":["../../src/core/deliver-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"deliver-text.d.ts","sourceRoot":"","sources":["../../src/core/deliver-text.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,11 +29,15 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { stripLinkedIssues } from './linked-issues.js';
|
|
31
31
|
/**
|
|
32
|
-
* The PR title cap
|
|
33
|
-
*
|
|
32
|
+
* The PR title cap (crew#860, S17b F15 / S17a F20). GitHub ACCEPTS 256 characters, and under that
|
|
33
|
+
* cap wicked-studio#586 opened as the intent's first 244 — a whole paragraph, cut mid-sentence,
|
|
34
|
+
* wider than the PR list renders. A title is a headline: the intent's FIRST SENTENCE, cut at a word
|
|
35
|
+
* boundary inside 100 characters; the full intent is the body's first section and the run id is
|
|
36
|
+
* named there. (crew#550 P-1 — a 72-character cap severed a quoted phrase — is still honoured: the
|
|
37
|
+
* cut is depth-aware, see {@link boundedTitle}.) The COMMIT subject keeps git's conventional width,
|
|
34
38
|
* {@link COMMIT_SUBJECT_MAX}; a longer title then opens the commit body in full.
|
|
35
39
|
*/
|
|
36
|
-
export const DELIVER_TITLE_MAX =
|
|
40
|
+
export const DELIVER_TITLE_MAX = 100;
|
|
37
41
|
/** The commit subject cap — git's conventional subject width (crew#550). */
|
|
38
42
|
export const COMMIT_SUBJECT_MAX = 72;
|
|
39
43
|
// ── the title ──────────────────────────────────────────────────────────────────────────────────
|
|
@@ -104,16 +108,64 @@ const HAS_CONVENTIONAL_PREFIX = /^(?:feat|fix|refactor|chore|docs|test|build|ci|
|
|
|
104
108
|
export function conventionalPrefix(workflowId) {
|
|
105
109
|
return CONVENTIONAL_TYPE_BY_WORKFLOW[workflowId ?? ''] ?? 'chore';
|
|
106
110
|
}
|
|
111
|
+
/** A `.` that ends one of these is an abbreviation, not a sentence (`e.g. the gate`, `vs. main`). */
|
|
112
|
+
const ABBREVIATION_BEFORE_DOT = /(?:^|[\s(])(?:e\.g|i\.e|etc|vs|cf|approx|incl|resp|fig|no|vol|ca)$/i;
|
|
107
113
|
/**
|
|
108
|
-
* The
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
+
* The first SENTENCE of a headline line (crew#860): the text up to the first `.`, `!` or `?` that
|
|
115
|
+
* is followed by whitespace or the end, at quote/bracket depth 0 (a period inside `(…)` or `"…"`
|
|
116
|
+
* does not end the sentence), and not the dot of an abbreviation. A closing `.` is dropped (a
|
|
117
|
+
* headline carries no full stop); `!` and `?` stay. A dot glued to the next word (`0.8.4`,
|
|
118
|
+
* `deliver.ts`, `wicked-studio#586.`) never splits. No terminator ⇒ the whole line.
|
|
119
|
+
*/
|
|
120
|
+
export function firstSentence(line) {
|
|
121
|
+
const stack = [];
|
|
122
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
123
|
+
const c = line[i];
|
|
124
|
+
const top = stack[stack.length - 1];
|
|
125
|
+
if (top !== undefined && c === top) {
|
|
126
|
+
stack.pop();
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (c === "'") {
|
|
130
|
+
if (i === 0 || line[i - 1] === ' ')
|
|
131
|
+
stack.push("'");
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const closer = OPENERS[c];
|
|
135
|
+
if (closer !== undefined) {
|
|
136
|
+
stack.push(closer);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (stack.length > 0 || (c !== '.' && c !== '!' && c !== '?'))
|
|
140
|
+
continue;
|
|
141
|
+
// A run of terminators (`?!`, `...`) ends together; the sentence ends only when whitespace or
|
|
142
|
+
// the end follows the run.
|
|
143
|
+
let j = i;
|
|
144
|
+
while (j + 1 < line.length && /[.!?]/.test(line[j + 1]))
|
|
145
|
+
j += 1;
|
|
146
|
+
if (j + 1 < line.length && !/\s/.test(line[j + 1]))
|
|
147
|
+
continue;
|
|
148
|
+
const head = line.slice(0, i);
|
|
149
|
+
if (c === '.' && j === i && ABBREVIATION_BEFORE_DOT.test(head))
|
|
150
|
+
continue;
|
|
151
|
+
const sentence = (c === '.' && j === i ? head : line.slice(0, j + 1)).trim();
|
|
152
|
+
if (sentence === '')
|
|
153
|
+
continue;
|
|
154
|
+
return sentence;
|
|
155
|
+
}
|
|
156
|
+
return line;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The PR title / commit subject: the FIRST SENTENCE of the intent's first prose line (crew#860),
|
|
160
|
+
* whole when it fits, otherwise cut at the last word boundary that leaves room for a single `…` —
|
|
161
|
+
* so the result is ≤ {@link DELIVER_TITLE_MAX} characters and never ends mid-word (the F-3R2-014
|
|
162
|
+
* headline `…scenario CLN-2) aga`). Dangling punctuation before the ellipsis is dropped. A blank
|
|
163
|
+
* intent names the run instead — through the SAME bounded cut, so a long caller-supplied session id
|
|
164
|
+
* (the CLI passes `--session` through) is never cut mid-id either (Copilot on #525): the body names
|
|
165
|
+
* the run id in full.
|
|
114
166
|
*/
|
|
115
167
|
export function deliverTitle(intent, runId, workflowId) {
|
|
116
|
-
const line = firstLine(intent);
|
|
168
|
+
const line = firstSentence(firstLine(intent));
|
|
117
169
|
// The run id is caller-supplied too (`LaunchSchema` only requires it non-empty): a newline in it
|
|
118
170
|
// must not turn the title into two lines and break the framing (Copilot on #525).
|
|
119
171
|
if (line === '')
|
|
@@ -402,6 +454,40 @@ export function composeDeliverText(f, links = issueRefs(f.intent, f.repoRef)) {
|
|
|
402
454
|
}
|
|
403
455
|
}
|
|
404
456
|
out.push('');
|
|
457
|
+
// crew#860 (S17b F15): the DONE-WHEN EVIDENCE, one row per phase — the deterministic floor's
|
|
458
|
+
// results for that phase (`typecheck 0 · lint 0`), the evaluator verdict recorded for it, and the
|
|
459
|
+
// exit codes the creator PASTED for its own recipe — so a reviewer reads "what proved this" in one
|
|
460
|
+
// table instead of reconstructing it from three sections and the run record.
|
|
461
|
+
if (f.source === 'run') {
|
|
462
|
+
out.push('## Done-when evidence', '');
|
|
463
|
+
out.push(...doneWhenTable(f), '');
|
|
464
|
+
}
|
|
465
|
+
// crew#860 (S17b F15): the FACTS the intent's own done-when asks the PR to state — the creator's
|
|
466
|
+
// "what I did / commands run with exit codes / done-when table" and each evaluator's findings —
|
|
467
|
+
// already exist in the unit outputs; the PR used to carry only "passed its gate" and the operator
|
|
468
|
+
// appended the table by hand. They ride here, bounded, under the seat that wrote them.
|
|
469
|
+
out.push('## Creator report', '');
|
|
470
|
+
const creators = (f.reports ?? []).filter((r) => r.role === 'creator');
|
|
471
|
+
if (f.source === 'workflow') {
|
|
472
|
+
out.push('_Not available at composition time — the run record has it._');
|
|
473
|
+
}
|
|
474
|
+
else if (f.reports === undefined) {
|
|
475
|
+
out.push("_The creator's output could not be read when this PR opened — the run record has it._");
|
|
476
|
+
}
|
|
477
|
+
else if (creators.length === 0) {
|
|
478
|
+
out.push('_No creator report was recorded (no creator phase, or its output carried no report)._');
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
for (const r of creators)
|
|
482
|
+
out.push(`### ${code(r.phase)} (${cell(r.seat ?? 'seat unknown')})`, '', r.report, '');
|
|
483
|
+
}
|
|
484
|
+
out.push('');
|
|
485
|
+
const evaluations = (f.reports ?? []).filter((r) => r.role === 'evaluator');
|
|
486
|
+
if (evaluations.length > 0) {
|
|
487
|
+
out.push('## Evaluator findings', '');
|
|
488
|
+
for (const r of evaluations)
|
|
489
|
+
out.push(`### ${code(r.phase)} (${cell(r.seat ?? 'seat unknown')})`, '', r.report, '');
|
|
490
|
+
}
|
|
405
491
|
// crew#550 P-7: what the evaluator said should happen NEXT lands on the PR, not only in its log.
|
|
406
492
|
if (f.followUps !== undefined) {
|
|
407
493
|
out.push('## Follow-ups', '');
|
|
@@ -418,6 +504,41 @@ export function composeDeliverText(f, links = issueRefs(f.intent, f.repoRef)) {
|
|
|
418
504
|
`Delivered-By: wicked-crew run ${oneLine(f.runId)}`, ...(f.trailers ?? []));
|
|
419
505
|
return { title, body: out.join('\n') };
|
|
420
506
|
}
|
|
507
|
+
/** At most this many pasted recipe commands per phase in the done-when table. */
|
|
508
|
+
export const RECIPE_EXITS_MAX = 12;
|
|
509
|
+
/**
|
|
510
|
+
* The done-when evidence table (crew#860): one row per phase that has ANY evidence — a floor
|
|
511
|
+
* result, a recorded verdict, or pasted recipe exit codes. Every cell says what was recorded, or
|
|
512
|
+
* `—` when nothing was; a phase with no evidence at all is left out (the Phases table lists it).
|
|
513
|
+
*/
|
|
514
|
+
function doneWhenTable(f) {
|
|
515
|
+
const rows = [];
|
|
516
|
+
const order = [];
|
|
517
|
+
for (const p of f.phases)
|
|
518
|
+
if (!order.includes(p.id))
|
|
519
|
+
order.push(p.id);
|
|
520
|
+
for (const x of [...(f.checks ?? []).map((c) => c.phase), ...f.verdicts.map((v) => v.phase), ...(f.reports ?? []).map((r) => r.phase)]) {
|
|
521
|
+
if (!order.includes(x))
|
|
522
|
+
order.push(x);
|
|
523
|
+
}
|
|
524
|
+
for (const id of order) {
|
|
525
|
+
const phase = f.phases.find((p) => p.id === id);
|
|
526
|
+
const checks = (f.checks ?? []).filter((c) => c.phase === id);
|
|
527
|
+
const floor = checks.map((c) => `${c.name} ${exitLabel(c)}${c.exitCode !== null && c.exitCode !== 0 && c.classification !== null ? ` (${c.classification})` : ''}`).join(' · ');
|
|
528
|
+
const verdict = f.verdicts.find((v) => v.phase === id);
|
|
529
|
+
const verdictCell = verdict === undefined ? '' : `${verdict.verdict}${verdict.reason !== null && verdict.reason.trim() !== '' ? ` — ${verdict.reason}` : ''}`;
|
|
530
|
+
const exits = (f.reports ?? []).filter((r) => r.phase === id).flatMap((r) => r.exits ?? []).slice(0, RECIPE_EXITS_MAX);
|
|
531
|
+
const recipe = exits.map((e) => `${code(e.command)} → ${e.exit}`).join('<br>');
|
|
532
|
+
if (floor === '' && verdictCell === '' && recipe === '')
|
|
533
|
+
continue;
|
|
534
|
+
const seat = phase?.seat ?? f.reports?.find((r) => r.phase === id)?.seat ?? verdict?.seat ?? null;
|
|
535
|
+
rows.push(`| ${code(id)} | ${cell(seat)} | ${floor === '' ? '—' : cell(floor, 400)} | ${verdictCell === '' ? '—' : cell(verdictCell, 300)} | ${recipe === '' ? '—' : recipe} |`);
|
|
536
|
+
}
|
|
537
|
+
if (rows.length === 0) {
|
|
538
|
+
return ['_No floor results, evaluator verdicts or pasted recipe exit codes were recorded for this run._'];
|
|
539
|
+
}
|
|
540
|
+
return ['| phase | seat | floor (exit codes) | evaluator verdict | recipe the seat ran (pasted exit codes) |', '|---|---|---|---|---|', ...rows];
|
|
541
|
+
}
|
|
421
542
|
/** The classification cell: the engine's word for a non-zero exit, `—` for a pass or none. */
|
|
422
543
|
function classificationLabel(c) {
|
|
423
544
|
if (c.classification !== null && c.classification.trim() !== '')
|
|
@@ -582,6 +703,7 @@ export function factsFromRun(view, runUrl, resolved = {}) {
|
|
|
582
703
|
revisesPr: resolved.revisesPr ?? null,
|
|
583
704
|
trailers: configuredTrailers(),
|
|
584
705
|
...(resolved.followUps !== undefined ? { followUps: resolved.followUps } : {}),
|
|
706
|
+
...(resolved.reports !== undefined ? { reports: resolved.reports } : {}),
|
|
585
707
|
};
|
|
586
708
|
}
|
|
587
709
|
/** What is known at LAUNCH, from the workflow definition — the script's embedded fallback. */
|
|
@@ -728,4 +850,151 @@ export function extractFollowUps(text) {
|
|
|
728
850
|
}
|
|
729
851
|
return out;
|
|
730
852
|
}
|
|
853
|
+
// ── seat reports (crew#860) ────────────────────────────────────────────────────────────────────
|
|
854
|
+
/** How much of one seat's report rides a PR body (characters); the run record has the rest. */
|
|
855
|
+
export const REPORT_MAX_CHARS = 4_000;
|
|
856
|
+
/** At most this many reports ride a PR body (one per creator / evaluator unit, by ord). */
|
|
857
|
+
export const REPORTS_MAX = 6;
|
|
858
|
+
/**
|
|
859
|
+
* The heading that OPENS a seat's final report — the governed-worker output contract's sections
|
|
860
|
+
* ("What you did", "Commands run", "Counts", "Findings", "Open questions") and the shapes seats
|
|
861
|
+
* actually write them in (`## What I did`, `**Commands run:**`, `Done-when`, `Evidence`, `Summary`).
|
|
862
|
+
*/
|
|
863
|
+
const REPORT_HEADING = /^\s*(?:#{1,6}\s*|\*\*|\d+[.)]\s*|[-*]\s*)*\s*(?:what (?:you|i|we) did|what (?:was|has been) done|what changed|commands? run|commands? executed|counts?|findings?|done[- ]when|evidence|open questions?|summary|report|verification|results?)\b/i;
|
|
864
|
+
/** The evaluator's contract line — stated separately by the composer, never repeated in the report. */
|
|
865
|
+
const VERDICT_LINE = /^\s*verdict:\s*(pass|fail)\s*$/i;
|
|
866
|
+
/**
|
|
867
|
+
* A seat's final REPORT from its captured output (crew#860): the text from the first report heading
|
|
868
|
+
* ({@link REPORT_HEADING}) to the end — the output contract puts the report LAST, after the work —
|
|
869
|
+
* or, when no heading is found, the tail of the output (its last 40 lines), said so. `VERDICT:`
|
|
870
|
+
* lines are removed (the composer states the recorded verdict beside the phase). Headings are
|
|
871
|
+
* demoted below the body's own `##` / `###` so the report cannot restyle the PR; an unclosed code
|
|
872
|
+
* fence is closed so it cannot swallow the footer; control characters are dropped; the result is
|
|
873
|
+
* cut at a line boundary inside {@link REPORT_MAX_CHARS} and the cut is disclosed. `null` when the
|
|
874
|
+
* output carries nothing reportable (empty, or whitespace only).
|
|
875
|
+
*/
|
|
876
|
+
export function extractUnitReport(text) {
|
|
877
|
+
if (text === null || text === undefined)
|
|
878
|
+
return null;
|
|
879
|
+
const lines = text
|
|
880
|
+
.replace(/\r\n?/g, '\n')
|
|
881
|
+
.replace(CONTROL_CHARS, '')
|
|
882
|
+
.split('\n')
|
|
883
|
+
.filter((l) => !VERDICT_LINE.test(l));
|
|
884
|
+
let start = lines.findIndex((l) => REPORT_HEADING.test(l));
|
|
885
|
+
let note = null;
|
|
886
|
+
if (start < 0) {
|
|
887
|
+
start = Math.max(0, lines.length - 40);
|
|
888
|
+
note = '_(no report headings found in the output — its last lines follow)_';
|
|
889
|
+
}
|
|
890
|
+
let block = lines.slice(start).join('\n').trim();
|
|
891
|
+
if (block === '')
|
|
892
|
+
return null;
|
|
893
|
+
// Below the body's `### <phase>` heading: `#`/`##`/`###` → `####`.
|
|
894
|
+
block = block.replace(/^#{1,3}(?=\s)/gm, '####');
|
|
895
|
+
let cut = false;
|
|
896
|
+
if (block.length > REPORT_MAX_CHARS) {
|
|
897
|
+
const head = block.slice(0, REPORT_MAX_CHARS);
|
|
898
|
+
const nl = head.lastIndexOf('\n');
|
|
899
|
+
block = (nl > REPORT_MAX_CHARS / 2 ? head.slice(0, nl) : head).trimEnd();
|
|
900
|
+
cut = true;
|
|
901
|
+
}
|
|
902
|
+
// An odd number of fences would leave the rest of the PR body inside a code block.
|
|
903
|
+
if ((block.match(/^\s*```/gm) ?? []).length % 2 === 1)
|
|
904
|
+
block += '\n```';
|
|
905
|
+
const parts = [block];
|
|
906
|
+
if (cut)
|
|
907
|
+
parts.push('', '_(cut here — the full report is on the run record)_');
|
|
908
|
+
if (note !== null)
|
|
909
|
+
parts.unshift(note, '');
|
|
910
|
+
return parts.join('\n');
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* The seats' reports for a run (crew#860), one per creator / evaluator unit in ord order, read
|
|
914
|
+
* through `readOutput` (the adapter's `workOutput`, injected so the composer stays pure). A unit
|
|
915
|
+
* whose output cannot be read or carries nothing reportable contributes nothing; the result is
|
|
916
|
+
* `[]` when nothing was reportable, never `undefined` — the caller says "could not read" only when
|
|
917
|
+
* it has no reader at all.
|
|
918
|
+
*/
|
|
919
|
+
export async function reportsFromOutputs(view, readOutput) {
|
|
920
|
+
const units = [...view.units]
|
|
921
|
+
.filter((u) => u.role === 'creator' || u.role === 'evaluator')
|
|
922
|
+
.sort((a, b) => a.ord - b.ord);
|
|
923
|
+
const out = [];
|
|
924
|
+
for (const u of units) {
|
|
925
|
+
if (out.length >= REPORTS_MAX)
|
|
926
|
+
break;
|
|
927
|
+
let text = null;
|
|
928
|
+
try {
|
|
929
|
+
text = await readOutput(u);
|
|
930
|
+
}
|
|
931
|
+
catch {
|
|
932
|
+
text = null;
|
|
933
|
+
}
|
|
934
|
+
const report = extractUnitReport(text);
|
|
935
|
+
if (report === null)
|
|
936
|
+
continue;
|
|
937
|
+
const exits = extractRecipeExits(text);
|
|
938
|
+
out.push({
|
|
939
|
+
phase: phaseIdOf(u),
|
|
940
|
+
seat: seatOf(u),
|
|
941
|
+
role: u.role,
|
|
942
|
+
report,
|
|
943
|
+
...(exits.length > 0 ? { exits } : {}),
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
return out;
|
|
947
|
+
}
|
|
948
|
+
/** `exit 0`, `exit code: 1`, `exit=2`, `exited 0`, `(exit 0)` — the exit code a seat pasted. */
|
|
949
|
+
const EXIT_WORD = /\bexit(?:ed)?(?:\s*code)?\s*[:=]?\s*(-?\d{1,3})\b/i;
|
|
950
|
+
/** `` `cmd` → 0 `` / `` `cmd` -> 0 `` / `` `cmd`: 0 `` — the arrow shapes after a code span. */
|
|
951
|
+
const ARROW_EXIT = /^\s*(?:→|->|=>|:|—|–)\s*(-?\d{1,3})\b/;
|
|
952
|
+
/** `` | `cmd` | 0 | `` — a table row whose next cell is a bare exit code. */
|
|
953
|
+
const TABLE_EXIT = /^\s*\|\s*(-?\d{1,3})\s*\|/;
|
|
954
|
+
const CODE_SPAN = /`([^`\n]{2,200})`/;
|
|
955
|
+
/**
|
|
956
|
+
* The commands a seat's output says it ran, with the exit code it pasted beside each (crew#860) —
|
|
957
|
+
* the "Commands run (with exit codes)" half of the governed-worker output contract, in the shapes
|
|
958
|
+
* seats write it: `` - `npm run lint` → exit 0 ``, `` | `npx vitest run` | 0 | ``, `typecheck: exit 0`.
|
|
959
|
+
* A line counts only when it names BOTH a command (a code span, else the list item's text before
|
|
960
|
+
* the exit word) and an exit code; deduplicated by command (the LAST report wins — a re-run
|
|
961
|
+
* supersedes), at most {@link RECIPE_EXITS_MAX}. Never throws; `[]` when nothing parses.
|
|
962
|
+
*/
|
|
963
|
+
export function extractRecipeExits(text) {
|
|
964
|
+
if (text === null || text === undefined)
|
|
965
|
+
return [];
|
|
966
|
+
const found = new Map();
|
|
967
|
+
for (const raw of text.replace(/\r\n?/g, '\n').replace(CONTROL_CHARS, '').split('\n')) {
|
|
968
|
+
const span = CODE_SPAN.exec(raw);
|
|
969
|
+
let command = null;
|
|
970
|
+
let exit = null;
|
|
971
|
+
if (span !== null) {
|
|
972
|
+
command = span[1].trim();
|
|
973
|
+
const after = raw.slice(span.index + span[0].length);
|
|
974
|
+
const m = EXIT_WORD.exec(after) ?? ARROW_EXIT.exec(after) ?? TABLE_EXIT.exec(after);
|
|
975
|
+
if (m !== null)
|
|
976
|
+
exit = Number(m[1]);
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
const m = EXIT_WORD.exec(raw);
|
|
980
|
+
if (m !== null) {
|
|
981
|
+
const head = raw
|
|
982
|
+
.slice(0, m.index)
|
|
983
|
+
.replace(/^\s*(?:[-*•]|\d+[.)])\s+/, '')
|
|
984
|
+
.replace(/[\s(:—–,-]+$/, '')
|
|
985
|
+
.trim();
|
|
986
|
+
if (head !== '' && head.length <= 120 && !/^(?:all|every|each)\b/i.test(head)) {
|
|
987
|
+
command = head;
|
|
988
|
+
exit = Number(m[1]);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (command === null || exit === null || command === '' || !Number.isFinite(exit))
|
|
993
|
+
continue;
|
|
994
|
+
const key = oneLine(command).slice(0, 120);
|
|
995
|
+
found.delete(key);
|
|
996
|
+
found.set(key, exit);
|
|
997
|
+
}
|
|
998
|
+
return [...found.entries()].slice(-RECIPE_EXITS_MAX).map(([command, exit]) => ({ command, exit }));
|
|
999
|
+
}
|
|
731
1000
|
//# sourceMappingURL=deliver-text.js.map
|