toolroll 0.9.34 → 0.9.36
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/CHANGELOG.md +44 -0
- package/README.md +7 -0
- package/dist/assignment.d.ts +1 -0
- package/dist/assignment.js +2 -2
- package/dist/browser-workspace.d.ts +3 -3
- package/dist/builder.js +4 -7
- package/dist/chat-acceptance.d.ts +1 -1
- package/dist/contracts/contract.d.ts +33 -5
- package/dist/contracts/contract.js +143 -16
- package/dist/contracts/flow-propose.d.ts +325 -0
- package/dist/contracts/flow-propose.js +41 -0
- package/dist/contracts/flow.d.ts +19400 -0
- package/dist/contracts/flow.js +359 -0
- package/dist/contracts/handoff.d.ts +91 -0
- package/dist/contracts/handoff.js +82 -0
- package/dist/contracts/proof.d.ts +136 -0
- package/dist/contracts/proof.js +110 -0
- package/dist/contracts/verification-receipt.d.ts +148 -0
- package/dist/contracts/verification-receipt.js +87 -0
- package/dist/demo.js +28 -11
- package/dist/desktop-update.d.ts +2 -2
- package/dist/evidence.d.ts +3 -40
- package/dist/evidence.js +5 -1
- package/dist/flow-gallery.d.ts +3 -2
- package/dist/flow-share.d.ts +10 -32
- package/dist/flow-share.js +65 -83
- package/dist/flow-starters.d.ts +2 -1
- package/dist/flow-triggers.d.ts +15 -72
- package/dist/flow-triggers.js +94 -83
- package/dist/flows.d.ts +37 -218
- package/dist/flows.js +350 -385
- package/dist/mate-tools.js +14 -44
- package/dist/mcp-connect.d.ts +3 -0
- package/dist/mcp-connect.js +16 -4
- package/dist/operate.js +31 -2
- package/dist/plan.d.ts +5 -5
- package/dist/plan.js +5 -25
- package/dist/process-recovery-eligibility.d.ts +2 -2
- package/dist/process-recovery-eligibility.js +13 -8
- package/dist/proof.d.ts +15 -91
- package/dist/proof.js +90 -249
- package/dist/result-evidence-readers.d.ts +42 -0
- package/dist/result-evidence-readers.js +91 -0
- package/dist/serve.js +8 -99
- package/dist/setup-guide.d.ts +2 -2
- package/dist/surface.js +8 -1
- package/dist/task-review-brief.d.ts +75 -0
- package/dist/task-review-brief.js +179 -0
- package/dist/text-limits.d.ts +70 -0
- package/dist/text-limits.js +70 -0
- package/dist/verification-evidence.js +24 -12
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SCREENSHOT_CAPTURE, structuredHandoffView, terminalDiffView } from "./result-evidence-readers.js";
|
|
1
2
|
import { checkLeadIdentity, leadIdentityOf, leadNameOf, LEAD_NAME_MAX, LEAD_PERSONA_MAX } from "./lead-identity.js";
|
|
2
3
|
import { aboutYouOf, checkAboutYou, saveAboutYou, ABOUT_YOU_LINE_MAX, ABOUT_YOU_MAX_LINES } from "./lead-about.js";
|
|
3
4
|
import { withActor } from "./actor.js";
|
|
@@ -53,7 +54,7 @@ import { FORM_PATH, flowFormPage, receiveFlowForm, shareFlowButton, stopSharingF
|
|
|
53
54
|
import { addFlowTriggerTo, checkFlowTriggerNow, HOOK_PATH, pressFlowButton, receiveFlowHook, removeFlowTrigger, renewFlowHook, removeLinearKey, saveHooksBase, saveLinearKey, saveLinearSigningSecret } from "./flow-triggers.js";
|
|
54
55
|
import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decideFlowCard, FLOW_HREF, flowDefinitionOf, moveCardInFlow } from "./flow-engine.js";
|
|
55
56
|
import { chooseFlowCard } from "./flow-send.js";
|
|
56
|
-
import { FLOW_TEMPLATES, validateFlowDefinition } from "./flows.js";
|
|
57
|
+
import { FlowContractError, FLOW_TEMPLATES, validateFlowDefinition, withZoneNames } from "./flows.js";
|
|
57
58
|
import { TOOL_CATALOG, addToolTo, catalogTool, discoverTools, localAppOf, projectToolsOf, removeToolFrom, secretsSetFor, setToolSecret, splitCommandLine, testToolOf } from "./project-tools.js";
|
|
58
59
|
import { changeLearning, learningView } from "./project-learning.js";
|
|
59
60
|
import { applySavedKnowledge, changeKnowledge, knowledgeView, knowledgeVersion, readKnowledgeSnapshot } from "./project-knowledge.js";
|
|
@@ -1136,6 +1137,8 @@ export function createDecisionServer(options) {
|
|
|
1136
1137
|
response.end(`<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="refresh" content="0;url=${escape(to)}"><title>Toolroll</title>${HANDOFF_STYLE}<p>${escape(words)} <a href="${escape(to)}">Back to Toolroll</a></p>`);
|
|
1137
1138
|
};
|
|
1138
1139
|
const state = url.searchParams.get("state") ?? "";
|
|
1140
|
+
// Every arrival is logged (never the code or state): a sign-in that never came back is then visible as an absence.
|
|
1141
|
+
logEvent("info", "connect.callback", { error: url.searchParams.get("error"), known: connectVisits.has(state) });
|
|
1139
1142
|
if (!startedHere(request, state))
|
|
1140
1143
|
return done("/settings/tools", "problem", "That sign-in was started in another browser. Connect again from this one.");
|
|
1141
1144
|
const visit = connectVisits.get(state);
|
|
@@ -7179,12 +7182,13 @@ export function createDecisionServer(options) {
|
|
|
7179
7182
|
return redirect(response, "/flows");
|
|
7180
7183
|
}
|
|
7181
7184
|
if (action === "save") {
|
|
7182
|
-
let saved;
|
|
7185
|
+
let saved, drawn;
|
|
7183
7186
|
try {
|
|
7184
|
-
|
|
7187
|
+
drawn = JSON.parse(body.get("definition") ?? "null");
|
|
7188
|
+
saved = validateFlowDefinition(drawn);
|
|
7185
7189
|
}
|
|
7186
7190
|
catch (error) {
|
|
7187
|
-
return answer(400, { ok: false, said: error instanceof SyntaxError ? "That flow couldn't be read." : error instanceof Error ? error.message : "That flow isn't valid." });
|
|
7191
|
+
return answer(400, { ok: false, said: error instanceof SyntaxError ? "That flow couldn't be read." : error instanceof FlowContractError ? withZoneNames(error.lines, drawn) : error instanceof Error ? error.message : "That flow isn't valid." });
|
|
7188
7192
|
}
|
|
7189
7193
|
const name = (body.get("name") ?? flow.name).replace(/[\u0000-\u001f\u007f]/g, "").trim().slice(0, 80) || flow.name;
|
|
7190
7194
|
// The owner (v86) is whom "the owner decides" zones ask: someone who can approve on this project.
|
|
@@ -22504,32 +22508,6 @@ function runsPage(chrome, rows, liveIds, nextCursor, verdicts = new Map(), accep
|
|
|
22504
22508
|
const older = nextCursor === null ? "" : `<p><a href="/runs?before=${nextCursor}">older →</a></p>`;
|
|
22505
22509
|
return screen("builds", [`<h1>Builds <a class="badge" href="/peek">Peek at the live ones \u2192</a></h1>`, buildsViews("builds"), `<p class="hint">one build = one attempt by an agent to complete a task, on its own branch</p>`, list, older].join("\n"), { chrome });
|
|
22506
22510
|
}
|
|
22507
|
-
function structuredHandoffView(artifacts, root) {
|
|
22508
|
-
const artifact = [...artifacts].reverse().find(one => one.kind === "handoff");
|
|
22509
|
-
if (artifact === undefined)
|
|
22510
|
-
return null;
|
|
22511
|
-
const read = readVerifiedArtifact(root, artifact);
|
|
22512
|
-
if (!read.ok)
|
|
22513
|
-
return null;
|
|
22514
|
-
try {
|
|
22515
|
-
const parsed = JSON.parse(read.content.toString("utf8"));
|
|
22516
|
-
if (parsed === null || typeof parsed !== "object" || typeof parsed["conclusion"] !== "string")
|
|
22517
|
-
return null;
|
|
22518
|
-
const conclusion = oneLineOf(parsed["conclusion"], 600);
|
|
22519
|
-
if (conclusion === "" || hasForbiddenControls(conclusion))
|
|
22520
|
-
return null;
|
|
22521
|
-
const list = (name) => Array.isArray(parsed[name])
|
|
22522
|
-
? parsed[name]
|
|
22523
|
-
.filter((one) => typeof one === "string" && one.trim() !== "" && !hasForbiddenControls(one))
|
|
22524
|
-
.slice(0, 8)
|
|
22525
|
-
.map(one => oneLineOf(one, 240))
|
|
22526
|
-
: [];
|
|
22527
|
-
return { conclusion, changes: list("changes"), verification: list("verification"), followUps: list("followUps") };
|
|
22528
|
-
}
|
|
22529
|
-
catch {
|
|
22530
|
-
return null;
|
|
22531
|
-
}
|
|
22532
|
-
}
|
|
22533
22511
|
/** What a scout found, each with its link and picture, then any screenshot no item shows. Every link is the cited
|
|
22534
22512
|
* http(s) address the report parser admitted; every picture is served from verified evidence. */
|
|
22535
22513
|
function reportItemsHtml(items, shots, runId) {
|
|
@@ -22606,7 +22584,6 @@ function sharedResultFactsOf(run, proof, terminal, handoff, report, publication)
|
|
|
22606
22584
|
publicationWords: receiptPublicationWords(publication),
|
|
22607
22585
|
};
|
|
22608
22586
|
}
|
|
22609
|
-
const SCREENSHOT_CAPTURE = /^agent-claimed screenshot at (.+) \(validated (?:png|jpeg)\)/;
|
|
22610
22587
|
function proofBundleView(store, run, artifacts, root) {
|
|
22611
22588
|
const verdictRow = store.proofVerdictFor(run.id);
|
|
22612
22589
|
const acceptanceRow = store.proofAcceptance(run.id);
|
|
@@ -22722,74 +22699,6 @@ function completionReceiptView(store, run, artifacts, root, review = null) {
|
|
|
22722
22699
|
facts: sharedResultFactsOf(run, proof, terminal, handoff, report, publication),
|
|
22723
22700
|
};
|
|
22724
22701
|
}
|
|
22725
|
-
const CAPTURE_EXIT = /\(exit ([0-9]{1,4})\)\s*$/;
|
|
22726
|
-
/**
|
|
22727
|
-
* Assemble the terminal-diff card's facts. Every branch names its state:
|
|
22728
|
-
* a failed capture (nonzero exit recorded in the capture string) reads as
|
|
22729
|
-
* the failure it is, unverifiable bytes read as their problem, and absence
|
|
22730
|
-
* returns null so old runs simply show nothing rather than a broken card.
|
|
22731
|
-
*/
|
|
22732
|
-
function terminalDiffView(artifacts, root) {
|
|
22733
|
-
const patchArtifact = artifacts.find(one => one.kind === "terminal-diff");
|
|
22734
|
-
const statArtifact = artifacts.find(one => one.kind === "diff-stat");
|
|
22735
|
-
if (patchArtifact === undefined && statArtifact === undefined)
|
|
22736
|
-
return null;
|
|
22737
|
-
const view = { patch: null, stat: null };
|
|
22738
|
-
if (patchArtifact !== undefined) {
|
|
22739
|
-
const exit = CAPTURE_EXIT.exec(patchArtifact.capture);
|
|
22740
|
-
if (exit !== null && exit[1] !== "0") {
|
|
22741
|
-
view.patch = { problem: `capture failed — ${patchArtifact.capture}` };
|
|
22742
|
-
}
|
|
22743
|
-
else {
|
|
22744
|
-
const read = readVerifiedArtifact(root, patchArtifact);
|
|
22745
|
-
view.patch = read.ok
|
|
22746
|
-
? { text: read.content.toString("utf8"), truncated: patchArtifact.truncated, artifactId: patchArtifact.id }
|
|
22747
|
-
: { problem: `stored but unverifiable — ${read.problem}` };
|
|
22748
|
-
}
|
|
22749
|
-
}
|
|
22750
|
-
if (statArtifact !== undefined) {
|
|
22751
|
-
const exit = CAPTURE_EXIT.exec(statArtifact.capture);
|
|
22752
|
-
if (exit !== null && exit[1] !== "0") {
|
|
22753
|
-
view.stat = { problem: `capture failed — ${statArtifact.capture}` };
|
|
22754
|
-
}
|
|
22755
|
-
else {
|
|
22756
|
-
const read = readVerifiedArtifact(root, statArtifact);
|
|
22757
|
-
if (!read.ok) {
|
|
22758
|
-
view.stat = { problem: `stored but unverifiable — ${read.problem}` };
|
|
22759
|
-
}
|
|
22760
|
-
else {
|
|
22761
|
-
try {
|
|
22762
|
-
const parsed = JSON.parse(read.content.toString("utf8"));
|
|
22763
|
-
// Every field this page renders is type-proved (arc 6, finding 7):
|
|
22764
|
-
// "an object with a base key" was accepting any shape at all.
|
|
22765
|
-
const count = (value) => typeof value === "number" && Number.isFinite(value);
|
|
22766
|
-
const wellFormed = parsed !== null &&
|
|
22767
|
-
typeof parsed === "object" &&
|
|
22768
|
-
typeof parsed["base"] === "string" &&
|
|
22769
|
-
typeof parsed["head"] === "string" &&
|
|
22770
|
-
count(parsed["fileCount"]) &&
|
|
22771
|
-
count(parsed["additions"]) &&
|
|
22772
|
-
count(parsed["deletions"]) &&
|
|
22773
|
-
count(parsed["binaryCount"]) &&
|
|
22774
|
-
typeof parsed["filesTruncated"] === "boolean" &&
|
|
22775
|
-
Array.isArray(parsed["files"]) &&
|
|
22776
|
-
parsed["files"].every(one => one !== null &&
|
|
22777
|
-
typeof one === "object" &&
|
|
22778
|
-
typeof one["path"] === "string" &&
|
|
22779
|
-
(count(one["additions"]) || one["additions"] === null) &&
|
|
22780
|
-
(count(one["deletions"]) || one["deletions"] === null));
|
|
22781
|
-
view.stat = wellFormed
|
|
22782
|
-
? parsed
|
|
22783
|
-
: { problem: "stat is not the shape this page knows" };
|
|
22784
|
-
}
|
|
22785
|
-
catch {
|
|
22786
|
-
view.stat = { problem: "stat did not parse as JSON" };
|
|
22787
|
-
}
|
|
22788
|
-
}
|
|
22789
|
-
}
|
|
22790
|
-
}
|
|
22791
|
-
return view;
|
|
22792
|
-
}
|
|
22793
22702
|
const REVIEW_DIFF_LINE_CAP = 4_000;
|
|
22794
22703
|
/** Parse only the stable structure Git's unified patch format guarantees.
|
|
22795
22704
|
* Unknown metadata remains visible, and a patch that cannot be structured
|
package/dist/setup-guide.d.ts
CHANGED
|
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
|
|
|
15
15
|
} | null;
|
|
16
16
|
/** Browser installation may write only this known file inside its project. */
|
|
17
17
|
export declare function previewProjectInstructions(repo: string): {
|
|
18
|
+
content?: never;
|
|
18
19
|
ok: false;
|
|
19
20
|
message: string;
|
|
20
21
|
plan?: never;
|
|
21
|
-
content?: never;
|
|
22
22
|
fingerprint?: never;
|
|
23
23
|
installed?: never;
|
|
24
24
|
} | {
|
|
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
|
|
|
30
30
|
installed: boolean;
|
|
31
31
|
};
|
|
32
32
|
export declare function addProjectInstructions(repo: string, fingerprint: string): {
|
|
33
|
+
content?: never;
|
|
33
34
|
ok: false;
|
|
34
35
|
message: string;
|
|
35
36
|
plan?: never;
|
|
36
|
-
content?: never;
|
|
37
37
|
fingerprint?: never;
|
|
38
38
|
installed?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
package/dist/surface.js
CHANGED
|
@@ -278,7 +278,14 @@ export const COMMAND_GUIDE = [
|
|
|
278
278
|
operator("task approve", "the yes — nothing builds without one; binds to the scope digest"),
|
|
279
279
|
operator("task requeue", "exit a stall: incidents resolved, strikes cleared, queued again"),
|
|
280
280
|
operator("task regate", "explicitly rerun the approved check on the last exact commit — no agent or automatic retry"),
|
|
281
|
-
|
|
281
|
+
{ invocation: "task review", synopsis: "read a task's compact result packet with --brief, or a run's automatic findings without it", audience: "agent", agentMayInvoke: true, mutation: "none",
|
|
282
|
+
positionals: [{ name: "id", required: true, meaning: "task id with --brief; run id otherwise" }],
|
|
283
|
+
flags: [jsonFlag, dbFlag,
|
|
284
|
+
{ name: "brief", takesValue: false, meaning: "saved conclusion, changed files, checks, findings, screenshots, plan and next actions; missing fields say not recorded" },
|
|
285
|
+
{ name: "run", takesValue: true, meaning: "with --brief, select an exact finished attempt instead of the latest" },
|
|
286
|
+
{ name: "all", takesValue: false, meaning: "include LOW findings within the compact packet's limit; extra findings are counted" },
|
|
287
|
+
{ name: "token", takesValue: true, meaning: "the lead token (or TOOLROLL_LEAD_TOKEN)" }],
|
|
288
|
+
notableReasons: ["usage", "unauthenticated", "not-found"] },
|
|
282
289
|
operator("task accept", "record an explicit acceptance with limitations; does not change checks or prove deployment"),
|
|
283
290
|
operator("task repair", "inspect or approve an existing historical repair draft; use task revise for new feedback"),
|
|
284
291
|
operator("task route", "which configured agents plan, build and repair this task; phase and risk changes require renewed approval"),
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** A read-only packet of saved result facts. Never opens a worktree or emits logs, patches or saved shell commands. */
|
|
2
|
+
import type { Store } from './store.js';
|
|
3
|
+
import { type AssignmentAccess } from './assignment.js';
|
|
4
|
+
import { type FindingSeverity } from './review-switch.js';
|
|
5
|
+
declare const MISSING: 'not recorded';
|
|
6
|
+
type Missing = typeof MISSING;
|
|
7
|
+
type Finding = {
|
|
8
|
+
severity: FindingSeverity;
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
scenario: string;
|
|
12
|
+
};
|
|
13
|
+
export type ReviewBrief = {
|
|
14
|
+
header: {
|
|
15
|
+
task: string;
|
|
16
|
+
run: number | Missing;
|
|
17
|
+
provider: string;
|
|
18
|
+
model: string;
|
|
19
|
+
minutes: number | Missing;
|
|
20
|
+
outcome: string;
|
|
21
|
+
base: string;
|
|
22
|
+
head: string;
|
|
23
|
+
state: string;
|
|
24
|
+
};
|
|
25
|
+
conclusion: string;
|
|
26
|
+
changedFiles: {
|
|
27
|
+
files: {
|
|
28
|
+
path: string;
|
|
29
|
+
additions: number | Missing;
|
|
30
|
+
deletions: number | Missing;
|
|
31
|
+
}[];
|
|
32
|
+
total: number;
|
|
33
|
+
omitted: number;
|
|
34
|
+
} | Missing;
|
|
35
|
+
check: {
|
|
36
|
+
status: string;
|
|
37
|
+
exit: number | Missing;
|
|
38
|
+
failed: string[] | Missing;
|
|
39
|
+
};
|
|
40
|
+
review: {
|
|
41
|
+
state: string;
|
|
42
|
+
reason: string;
|
|
43
|
+
findings: Finding[];
|
|
44
|
+
counts: Record<FindingSeverity, number>;
|
|
45
|
+
omitted: Record<FindingSeverity, number>;
|
|
46
|
+
} | Missing;
|
|
47
|
+
screenshots: {
|
|
48
|
+
files: {
|
|
49
|
+
path: string;
|
|
50
|
+
bytes: number | Missing;
|
|
51
|
+
}[];
|
|
52
|
+
omitted: number;
|
|
53
|
+
} | Missing;
|
|
54
|
+
plan: {
|
|
55
|
+
titles: string[];
|
|
56
|
+
omitted: number;
|
|
57
|
+
} | Missing;
|
|
58
|
+
nextActions: string[];
|
|
59
|
+
};
|
|
60
|
+
/** Select only finished build/report attempts in the admitted family. An explicit run never crosses that boundary. */
|
|
61
|
+
export declare function taskReviewBrief(store: Store, task: string, root: string, now: Date, access: AssignmentAccess, options?: {
|
|
62
|
+
run?: number;
|
|
63
|
+
all?: boolean;
|
|
64
|
+
secrets?: readonly string[];
|
|
65
|
+
}): {
|
|
66
|
+
ok: true;
|
|
67
|
+
brief: ReviewBrief;
|
|
68
|
+
} | {
|
|
69
|
+
ok: false;
|
|
70
|
+
reason: string;
|
|
71
|
+
message: string;
|
|
72
|
+
};
|
|
73
|
+
/** Text and JSON share this bounded projection; --all includes LOW details within the same line budget. */
|
|
74
|
+
export declare function renderReviewBrief(brief: ReviewBrief): string[];
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { assignmentChecksForRun, assignmentOf } from './assignment.js';
|
|
2
|
+
import { assignmentPresentationOf } from './assignment-presentation.js';
|
|
3
|
+
import { readVerifiedArtifact, readVerifiedProofForRun, reportShotsOf, scanForSecrets } from './evidence.js';
|
|
4
|
+
import { SCREENSHOT_CAPTURE, structuredHandoffView, terminalDiffView } from './result-evidence-readers.js';
|
|
5
|
+
import { buildReviewOf } from './review-switch.js';
|
|
6
|
+
import { parseReport } from './scout-report.js';
|
|
7
|
+
import { sanitizeTranscriptLine } from './live.js';
|
|
8
|
+
import { redactSecretAssignments } from './builder.js';
|
|
9
|
+
import { parseExecutionPlanDocument } from './plan.js';
|
|
10
|
+
const MISSING = 'not recorded';
|
|
11
|
+
const SEVERITIES = ['HIGH', 'MEDIUM', 'LOW'];
|
|
12
|
+
/** Redact before shortening so a clipped credential cannot escape recognition. Apply to every data string in both formats. */
|
|
13
|
+
function safeText(value, secrets, cap = 300) {
|
|
14
|
+
if (typeof value !== 'string' || !value.trim())
|
|
15
|
+
return MISSING;
|
|
16
|
+
const flat = value.replace(/\s+/g, ' ').trim();
|
|
17
|
+
const cleaned = sanitizeTranscriptLine(flat);
|
|
18
|
+
if (secrets.some(secret => secret !== '' && value.includes(secret)) ||
|
|
19
|
+
/\blt_[a-f0-9]{12}_[A-Za-z0-9_-]{43}\b|\bBearer\s+\S+/i.test(flat) ||
|
|
20
|
+
scanForSecrets(flat).length || redactSecretAssignments(flat) !== flat || cleaned.startsWith('[a credential-shaped'))
|
|
21
|
+
return '[redacted]';
|
|
22
|
+
return cleaned.length <= cap ? cleaned : `${cleaned.slice(0, cap - 1)}…`;
|
|
23
|
+
}
|
|
24
|
+
function verifiedText(root, artifact) {
|
|
25
|
+
if (!artifact || artifact.truncated || artifact.captureStatus === 'failed')
|
|
26
|
+
return null;
|
|
27
|
+
const read = readVerifiedArtifact(root, artifact);
|
|
28
|
+
return read.ok ? read.content.toString('utf8') : null;
|
|
29
|
+
}
|
|
30
|
+
/** Select only finished build/report attempts in the admitted family. An explicit run never crosses that boundary. */
|
|
31
|
+
export function taskReviewBrief(store, task, root, now, access, options = {}) {
|
|
32
|
+
const family = store.taskFamilyOf(task, access.repos, access.principal === 'operator' && access.includeUnplaced === true);
|
|
33
|
+
if (family === null)
|
|
34
|
+
return { ok: false, reason: 'not-found', message: 'That task is unavailable in your projects.' };
|
|
35
|
+
const runs = family.versions.flatMap(version => store.runsFor(version.refId))
|
|
36
|
+
.filter(run => (run.role === 'builder' || run.role === 'scout') && run.finishedAt !== null && run.outcome !== null)
|
|
37
|
+
.sort((a, b) => b.id - a.id);
|
|
38
|
+
const run = options.run === undefined ? runs[0] ?? null : runs.find(one => one.id === options.run) ?? null;
|
|
39
|
+
if (options.run !== undefined && run === null)
|
|
40
|
+
return { ok: false, reason: 'not-found', message: 'That finished build or report is unavailable for this task.' };
|
|
41
|
+
const assignment = assignmentOf(store, task, now, access, root);
|
|
42
|
+
const safe = (value, cap) => safeText(value, options.secrets ?? [], cap);
|
|
43
|
+
const sha = (value) => /^[a-f0-9]{40,64}$/.test(value ?? '') ? value.slice(0, 12) : MISSING;
|
|
44
|
+
const artifacts = run === null ? [] : store.artifactsFor(run.id);
|
|
45
|
+
// Pass only the inventory: this reader does not need to open the patch for a brief.
|
|
46
|
+
const statView = terminalDiffView(artifacts.filter(one => one.kind === 'diff-stat' && one.captureStatus !== 'failed'), root)?.stat;
|
|
47
|
+
const stat = statView && !('problem' in statView) ? statView : null;
|
|
48
|
+
const handoff = structuredHandoffView(artifacts, root, value => safe(value, 600));
|
|
49
|
+
const reportText = run?.role === 'scout' ? verifiedText(root, artifacts.find(one => one.kind === 'report')) : null;
|
|
50
|
+
const report = reportText === null ? null : parseReport(reportText, { stored: true });
|
|
51
|
+
const conclusion = handoff?.conclusion ?? (report?.ok ? report.report.summary : run?.handoff);
|
|
52
|
+
const duration = run?.finishedAt ? (Date.parse(run.finishedAt) - Date.parse(run.startedAt)) / 60_000 : NaN;
|
|
53
|
+
const recordedCheck = run === null ? null : store.runCheckFor(run.id);
|
|
54
|
+
const receipt = assignment?.receipt?.runId === run?.id ? assignment?.receipt : null;
|
|
55
|
+
// The shared assignment reader includes later checks on the same candidate. Its result wins;
|
|
56
|
+
// old named failures must not ride a newer passing (or differently failed) check.
|
|
57
|
+
const checks = receipt?.checks ?? (run === null ? null : assignmentChecksForRun(store, root, run.id, now));
|
|
58
|
+
const status = checks && checks.status !== 'unavailable' && checks.status !== 'not-run' ? checks.status : recordedCheck?.status ?? checks?.status ?? MISSING;
|
|
59
|
+
const exit = checks && checks.status === status ? checks.exitCode : recordedCheck?.exitCode;
|
|
60
|
+
const sameCheck = recordedCheck?.status === status && recordedCheck.exitCode === exit &&
|
|
61
|
+
(!checks?.logArtifactId || artifacts.some(one => one.id === checks.logArtifactId && one.kind === 'check-log'));
|
|
62
|
+
const progress = run === null ? null : store.checkProgress(run.id);
|
|
63
|
+
const failed = sameCheck ? [...new Set([
|
|
64
|
+
...recordedCheck.suites.filter(one => one.status === 'failed').map(one => safe(one.name)),
|
|
65
|
+
...Object.entries(progress?.suites ?? {}).filter(([, suite]) => suite.state === 'failed').map(([name, suite]) => `${name}: ${suite.failed} failed`),
|
|
66
|
+
])] : [];
|
|
67
|
+
const review = run === null ? null : buildReviewOf(store, run.id);
|
|
68
|
+
const findings = review === null ? [] : [...review.high, ...review.followUps].filter(one => one && SEVERITIES.includes(one.severity));
|
|
69
|
+
const counts = Object.fromEntries(SEVERITIES.map(severity => [severity, findings.filter(one => one.severity === severity).length]));
|
|
70
|
+
const shown = SEVERITIES.flatMap(severity => severity === 'LOW' && !options.all ? [] : findings.filter(one => one.severity === severity))
|
|
71
|
+
.slice(0, 12).map(one => ({ severity: one.severity, file: safe(one.file), line: Number.isSafeInteger(one.line) ? one.line : 0, scenario: safe(one.scenario) }));
|
|
72
|
+
const omitted = Object.fromEntries(SEVERITIES.map(severity => [severity, counts[severity] - shown.filter(one => one.severity === severity).length]));
|
|
73
|
+
const proof = run === null ? null : readVerifiedProofForRun(store, root, run.id);
|
|
74
|
+
const shots = artifacts.filter(one => one.kind === 'screenshot');
|
|
75
|
+
const reportShots = report?.ok && run !== null ? reportShotsOf(artifacts, root, run.id, report.report.images) : [];
|
|
76
|
+
const pathOf = (artifact) => SCREENSHOT_CAPTURE.exec(artifact.capture)?.[1] ??
|
|
77
|
+
(reportShots.some(one => one.artifactId === artifact.id) ? `evidence/${artifact.key}` : null);
|
|
78
|
+
const declared = proof?.ok ? proof.proof.screenshots.map(one => one.path) : [];
|
|
79
|
+
const paths = [...new Set([...declared, ...shots.flatMap(one => pathOf(one) ?? [])])];
|
|
80
|
+
const screenshots = paths.map(path => {
|
|
81
|
+
const artifact = shots.find(one => pathOf(one) === path);
|
|
82
|
+
return { path: safe(path), bytes: artifact && artifact.captureStatus !== 'failed' && readVerifiedArtifact(root, artifact).ok ? artifact.bytesStored : MISSING };
|
|
83
|
+
});
|
|
84
|
+
const plan = run === null ? null : planTitles(store, root, run);
|
|
85
|
+
const current = run !== null && receipt !== null && family.current.refId === run.taskRef;
|
|
86
|
+
const nextActions = [];
|
|
87
|
+
if (!run)
|
|
88
|
+
nextActions.push('Open the task to inspect its progress.');
|
|
89
|
+
else if (!current)
|
|
90
|
+
nextActions.push('Open the current result before completing or requesting a revision.');
|
|
91
|
+
else {
|
|
92
|
+
if (assignment?.state === 'ready-to-check')
|
|
93
|
+
nextActions.push(`Complete: toolroll task complete ${safe(family.root.id)} --digest ${receipt.digest}`);
|
|
94
|
+
const diff = artifacts.find(one => one.kind === 'terminal-diff');
|
|
95
|
+
if (run.role === 'builder' && diff && readVerifiedArtifact(root, diff).ok) {
|
|
96
|
+
nextActions.push(`Revise: toolroll task revise ${safe(family.root.id)} --run ${run.id} --feedback "requested change"`);
|
|
97
|
+
}
|
|
98
|
+
else if (run.role === 'builder')
|
|
99
|
+
nextActions.push('Revision unavailable: saved changes are not recorded or cannot be read.');
|
|
100
|
+
if (assignment?.state === 'ready-to-check' || assignment?.state === 'complete') {
|
|
101
|
+
if (run.role === 'builder' && run.outcome === 'built')
|
|
102
|
+
nextActions.push(assignment.state === 'complete'
|
|
103
|
+
? 'Merge into a release through the normal release approval and check process.'
|
|
104
|
+
: 'After completion, merge into a release through the normal release approval and check process.');
|
|
105
|
+
}
|
|
106
|
+
else
|
|
107
|
+
nextActions.push(`Open the task: ${safe(assignment?.detail)}`);
|
|
108
|
+
}
|
|
109
|
+
return { ok: true, brief: {
|
|
110
|
+
header: { task: safe(run === null ? task : store.externalIdFor(run.taskRef)), run: run?.id ?? MISSING,
|
|
111
|
+
provider: safe(run?.provider), model: safe(run?.model), minutes: Number.isFinite(duration) && duration >= 0 ? Math.round(duration * 10) / 10 : MISSING,
|
|
112
|
+
outcome: safe(run?.outcome), base: sha(run?.baseRevision), head: sha(run?.headRevision), state: run !== null && !current ? 'Earlier result' : assignment === null ? MISSING : assignmentPresentationOf(assignment).status.label },
|
|
113
|
+
conclusion: safe(conclusion, 600),
|
|
114
|
+
changedFiles: stat === null ? MISSING : { files: [...stat.files].sort((a, b) => ((b.additions ?? 0) + (b.deletions ?? 0)) - ((a.additions ?? 0) + (a.deletions ?? 0)))
|
|
115
|
+
.slice(0, 15).map(one => ({ path: safe(one.path), additions: one.additions ?? MISSING, deletions: one.deletions ?? MISSING })), total: stat.fileCount, omitted: Math.max(0, stat.fileCount - Math.min(15, stat.files.length)) },
|
|
116
|
+
check: { status: status === 'unavailable' ? MISSING : status, exit: exit ?? MISSING, failed: status !== 'failed' ? [] : failed.length ? failed : MISSING },
|
|
117
|
+
review: review === null ? MISSING : { state: review.state, reason: safe(review.reason), findings: shown, counts, omitted },
|
|
118
|
+
screenshots: screenshots.length ? { files: screenshots.slice(0, 8), omitted: Math.max(0, screenshots.length - 8) } : MISSING,
|
|
119
|
+
plan: plan === null || plan.length === 0 ? MISSING : { titles: plan.slice(0, 5).map(one => safe(one)), omitted: Math.max(0, plan.length - 5) },
|
|
120
|
+
nextActions,
|
|
121
|
+
} };
|
|
122
|
+
}
|
|
123
|
+
function planTitles(store, root, run) {
|
|
124
|
+
const revision = run.planRevision == null ? null : store.getPlanRevision(run.planRevision);
|
|
125
|
+
// Historical runs cannot borrow a plan written after they started.
|
|
126
|
+
const artifact = revision === null ? store.runsFor(run.taskRef).filter(one => one.role === 'planner' && one.id < run.id)
|
|
127
|
+
.sort((a, b) => b.id - a.id).flatMap(one => store.artifactsFor(one.id)).find(one => one.kind === 'plan') : store.getArtifact(revision.artifact);
|
|
128
|
+
const text = verifiedText(root, artifact);
|
|
129
|
+
if (text === null)
|
|
130
|
+
return null;
|
|
131
|
+
const parsed = parseExecutionPlanDocument(text);
|
|
132
|
+
return parsed.ok ? parsed.document.milestones : text.split(/\r?\n/).filter(line => /^#{1,6}\s+\S/.test(line)).map(line => line.replace(/^#{1,6}\s+/, ''));
|
|
133
|
+
}
|
|
134
|
+
/** Text and JSON share this bounded projection; --all includes LOW details within the same line budget. */
|
|
135
|
+
export function renderReviewBrief(brief) {
|
|
136
|
+
const h = brief.header;
|
|
137
|
+
const lines = [`${h.task} · run ${h.run} · ${h.provider}/${h.model} · ${h.minutes} min · ${h.outcome} · ${h.base} → ${h.head} · ${h.state}`,
|
|
138
|
+
`Conclusion: ${brief.conclusion}`, 'Changed files:'];
|
|
139
|
+
if (brief.changedFiles === MISSING)
|
|
140
|
+
lines.push(` ${MISSING}`);
|
|
141
|
+
else {
|
|
142
|
+
lines.push(...brief.changedFiles.files.map(one => ` ${one.path} +${one.additions} / -${one.deletions}`));
|
|
143
|
+
if (brief.changedFiles.total === 0)
|
|
144
|
+
lines.push(' none');
|
|
145
|
+
if (brief.changedFiles.omitted)
|
|
146
|
+
lines.push(` and ${brief.changedFiles.omitted} more`);
|
|
147
|
+
}
|
|
148
|
+
const check = brief.check;
|
|
149
|
+
lines.push(`Check: ${check.status} · exit ${check.exit}${check.failed === MISSING ? ` · failed parts: ${MISSING}` : check.failed.length ? ` · failed: ${check.failed.join('; ')}` : ''}`);
|
|
150
|
+
if (brief.review === MISSING)
|
|
151
|
+
lines.push(`Automatic review: ${MISSING}`);
|
|
152
|
+
else {
|
|
153
|
+
lines.push(`Automatic review: ${brief.review.state}${brief.review.reason === MISSING ? '' : ` · ${brief.review.reason}`}`);
|
|
154
|
+
lines.push(...brief.review.findings.map(one => ` ${one.severity} ${one.file}:${one.line} — ${one.scenario}`));
|
|
155
|
+
for (const severity of SEVERITIES)
|
|
156
|
+
if (brief.review.omitted[severity])
|
|
157
|
+
lines.push(` ${severity}: ${brief.review.omitted[severity]} more (${brief.review.counts[severity]} total)`);
|
|
158
|
+
if (Object.values(brief.review.counts).every(count => count === 0) && brief.review.state === 'reviewed')
|
|
159
|
+
lines.push(' no findings');
|
|
160
|
+
}
|
|
161
|
+
lines.push('Screenshots:');
|
|
162
|
+
if (brief.screenshots === MISSING)
|
|
163
|
+
lines.push(` ${MISSING}`);
|
|
164
|
+
else {
|
|
165
|
+
lines.push(...brief.screenshots.files.map(one => ` ${one.path} (${one.bytes}${typeof one.bytes === 'number' ? ' bytes' : ''})`));
|
|
166
|
+
if (brief.screenshots.omitted)
|
|
167
|
+
lines.push(` and ${brief.screenshots.omitted} more`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('Plan:');
|
|
170
|
+
if (brief.plan === MISSING)
|
|
171
|
+
lines.push(` ${MISSING}`);
|
|
172
|
+
else {
|
|
173
|
+
lines.push(...brief.plan.titles.map(one => ` ${one}`));
|
|
174
|
+
if (brief.plan.omitted)
|
|
175
|
+
lines.push(` and ${brief.plan.omitted} more`);
|
|
176
|
+
}
|
|
177
|
+
lines.push('Next:', ...brief.nextActions.map(one => ` ${one}`));
|
|
178
|
+
return lines;
|
|
179
|
+
}
|
package/dist/text-limits.d.ts
CHANGED
|
@@ -16,6 +16,67 @@ export declare const TEXT_LIMITS: {
|
|
|
16
16
|
readonly noteBytes: 16000;
|
|
17
17
|
/** A flow work zone's instructions; nothing is held back for the card's details (they are attached when long). */
|
|
18
18
|
readonly flowInstructions: 8000;
|
|
19
|
+
/** A flow's zones and steps (src/contracts/flow.ts): a zone's name, a step's id as written, and a reference to another step. */
|
|
20
|
+
readonly flowTitle: 60;
|
|
21
|
+
readonly flowStepId: 32;
|
|
22
|
+
readonly flowRef: 60;
|
|
23
|
+
/** A Message zone's message, or the comment an Update zone leaves. */
|
|
24
|
+
readonly flowMessage: 1000;
|
|
25
|
+
/** Who decides a "Person decides" zone (a sign-in name), and the AI teammate who works a zone, as written. */
|
|
26
|
+
readonly flowDecider: 64;
|
|
27
|
+
readonly flowTeammate: 40;
|
|
28
|
+
/** A Build zone's other project: its path. */
|
|
29
|
+
readonly flowRepo: 1000;
|
|
30
|
+
/** A script's or teammate's answer, a choice's button, a Sort zone's answer and what it means, its question, and a score's level. */
|
|
31
|
+
readonly flowAnswer: 40;
|
|
32
|
+
readonly flowChoice: 40;
|
|
33
|
+
readonly flowSortMeans: 200;
|
|
34
|
+
readonly flowSortQuestion: 300;
|
|
35
|
+
readonly flowSortLevel: 120;
|
|
36
|
+
/** A Web request zone's address, one header's value and the body it sends (an email's body too). */
|
|
37
|
+
readonly flowUrl: 2000;
|
|
38
|
+
readonly flowHeader: 500;
|
|
39
|
+
readonly flowBody: 8000;
|
|
40
|
+
/** An email's recipients and subject. */
|
|
41
|
+
readonly flowEmailTo: 500;
|
|
42
|
+
readonly flowEmailSubject: 200;
|
|
43
|
+
/** A tool zone's tool (MCP server), its function, and the arguments as JSON. */
|
|
44
|
+
readonly flowToolServer: 64;
|
|
45
|
+
readonly flowToolName: 100;
|
|
46
|
+
readonly flowToolArgs: 4000;
|
|
47
|
+
/** A wait or reminder said in words ("3 days"), and a time zone's name. */
|
|
48
|
+
readonly flowDuration: 40;
|
|
49
|
+
readonly flowTimeZone: 60;
|
|
50
|
+
/** A flow's name, and a flow file's about line, needs, parameter questions and answers, and scripts. */
|
|
51
|
+
readonly flowName: 80;
|
|
52
|
+
readonly flowFileAbout: 600;
|
|
53
|
+
readonly flowFileNeed: 80;
|
|
54
|
+
readonly flowParameter: 200;
|
|
55
|
+
readonly flowScriptAbout: 160;
|
|
56
|
+
readonly flowScriptBody: 20000;
|
|
57
|
+
readonly flowScriptFile: 200;
|
|
58
|
+
/** A trigger's settings: a button's label and questions, a schedule and the card it makes, GitHub, Linear, webhook and email terms. */
|
|
59
|
+
readonly triggerButton: 40;
|
|
60
|
+
readonly triggerQuestion: 80;
|
|
61
|
+
readonly triggerSchedule: 80;
|
|
62
|
+
readonly triggerTitle: 200;
|
|
63
|
+
readonly triggerDescription: 2000;
|
|
64
|
+
readonly triggerGithubRepo: 140;
|
|
65
|
+
readonly triggerLabel: 50;
|
|
66
|
+
readonly triggerBranch: 100;
|
|
67
|
+
readonly triggerTeam: 12;
|
|
68
|
+
readonly triggerState: 40;
|
|
69
|
+
readonly triggerWebhookTitle: 120;
|
|
70
|
+
readonly triggerWebhookField: 80;
|
|
71
|
+
readonly triggerFolder: 100;
|
|
72
|
+
readonly triggerSender: 300;
|
|
73
|
+
readonly triggerSubject: 100;
|
|
74
|
+
/** A schedule's secrets written as one line ("API_KEY, CRM_TOKEN"). */
|
|
75
|
+
readonly triggerSecrets: 500;
|
|
76
|
+
/** A card the lead adds to a flow: its title and description; a script it saves inline. */
|
|
77
|
+
readonly flowCardTitle: 200;
|
|
78
|
+
readonly flowCardDescription: 4000;
|
|
79
|
+
readonly flowScriptInline: 1600;
|
|
19
80
|
/** A research report's summary, in UTF-8 bytes. */
|
|
20
81
|
readonly reportSummary: 2500;
|
|
21
82
|
/** What one flow step passes on to the steps after it. */
|
|
@@ -36,6 +97,15 @@ export declare const TEXT_LIMITS: {
|
|
|
36
97
|
readonly acceptanceIdBytes: 40;
|
|
37
98
|
readonly acceptanceStatementBytes: 1000;
|
|
38
99
|
readonly acceptanceHowBytes: 500;
|
|
100
|
+
/** A builder's whole proof file, in UTF-8 bytes. */
|
|
101
|
+
readonly proofPayloadBytes: number;
|
|
102
|
+
/** A proof criterion's id, in UTF-8 bytes. */
|
|
103
|
+
readonly proofCriterionIdBytes: 40;
|
|
104
|
+
/** How a proof criterion was met, in UTF-8 bytes. */
|
|
105
|
+
readonly proofHowBytes: 500;
|
|
106
|
+
/** One proof line — a criterion statement, evidence ref, check command or summary, changed path, caveat, or
|
|
107
|
+
* screenshot path or caption — in UTF-8 bytes. */
|
|
108
|
+
readonly proofLineBytes: 300;
|
|
39
109
|
};
|
|
40
110
|
export type TextLimitKey = keyof typeof TEXT_LIMITS;
|
|
41
111
|
/** One chat message on each platform. Longer text is split across messages, never cut. Teams: the text one Adaptive Card
|
package/dist/text-limits.js
CHANGED
|
@@ -16,6 +16,67 @@ export const TEXT_LIMITS = {
|
|
|
16
16
|
noteBytes: 16_000,
|
|
17
17
|
/** A flow work zone's instructions; nothing is held back for the card's details (they are attached when long). */
|
|
18
18
|
flowInstructions: 8_000,
|
|
19
|
+
/** A flow's zones and steps (src/contracts/flow.ts): a zone's name, a step's id as written, and a reference to another step. */
|
|
20
|
+
flowTitle: 60,
|
|
21
|
+
flowStepId: 32,
|
|
22
|
+
flowRef: 60,
|
|
23
|
+
/** A Message zone's message, or the comment an Update zone leaves. */
|
|
24
|
+
flowMessage: 1_000,
|
|
25
|
+
/** Who decides a "Person decides" zone (a sign-in name), and the AI teammate who works a zone, as written. */
|
|
26
|
+
flowDecider: 64,
|
|
27
|
+
flowTeammate: 40,
|
|
28
|
+
/** A Build zone's other project: its path. */
|
|
29
|
+
flowRepo: 1_000,
|
|
30
|
+
/** A script's or teammate's answer, a choice's button, a Sort zone's answer and what it means, its question, and a score's level. */
|
|
31
|
+
flowAnswer: 40,
|
|
32
|
+
flowChoice: 40,
|
|
33
|
+
flowSortMeans: 200,
|
|
34
|
+
flowSortQuestion: 300,
|
|
35
|
+
flowSortLevel: 120,
|
|
36
|
+
/** A Web request zone's address, one header's value and the body it sends (an email's body too). */
|
|
37
|
+
flowUrl: 2_000,
|
|
38
|
+
flowHeader: 500,
|
|
39
|
+
flowBody: 8_000,
|
|
40
|
+
/** An email's recipients and subject. */
|
|
41
|
+
flowEmailTo: 500,
|
|
42
|
+
flowEmailSubject: 200,
|
|
43
|
+
/** A tool zone's tool (MCP server), its function, and the arguments as JSON. */
|
|
44
|
+
flowToolServer: 64,
|
|
45
|
+
flowToolName: 100,
|
|
46
|
+
flowToolArgs: 4_000,
|
|
47
|
+
/** A wait or reminder said in words ("3 days"), and a time zone's name. */
|
|
48
|
+
flowDuration: 40,
|
|
49
|
+
flowTimeZone: 60,
|
|
50
|
+
/** A flow's name, and a flow file's about line, needs, parameter questions and answers, and scripts. */
|
|
51
|
+
flowName: 80,
|
|
52
|
+
flowFileAbout: 600,
|
|
53
|
+
flowFileNeed: 80,
|
|
54
|
+
flowParameter: 200,
|
|
55
|
+
flowScriptAbout: 160,
|
|
56
|
+
flowScriptBody: 20_000,
|
|
57
|
+
flowScriptFile: 200,
|
|
58
|
+
/** A trigger's settings: a button's label and questions, a schedule and the card it makes, GitHub, Linear, webhook and email terms. */
|
|
59
|
+
triggerButton: 40,
|
|
60
|
+
triggerQuestion: 80,
|
|
61
|
+
triggerSchedule: 80,
|
|
62
|
+
triggerTitle: 200,
|
|
63
|
+
triggerDescription: 2_000,
|
|
64
|
+
triggerGithubRepo: 140,
|
|
65
|
+
triggerLabel: 50,
|
|
66
|
+
triggerBranch: 100,
|
|
67
|
+
triggerTeam: 12,
|
|
68
|
+
triggerState: 40,
|
|
69
|
+
triggerWebhookTitle: 120,
|
|
70
|
+
triggerWebhookField: 80,
|
|
71
|
+
triggerFolder: 100,
|
|
72
|
+
triggerSender: 300,
|
|
73
|
+
triggerSubject: 100,
|
|
74
|
+
/** A schedule's secrets written as one line ("API_KEY, CRM_TOKEN"). */
|
|
75
|
+
triggerSecrets: 500,
|
|
76
|
+
/** A card the lead adds to a flow: its title and description; a script it saves inline. */
|
|
77
|
+
flowCardTitle: 200,
|
|
78
|
+
flowCardDescription: 4_000,
|
|
79
|
+
flowScriptInline: 1_600,
|
|
19
80
|
/** A research report's summary, in UTF-8 bytes. */
|
|
20
81
|
reportSummary: 2_500,
|
|
21
82
|
/** What one flow step passes on to the steps after it. */
|
|
@@ -36,6 +97,15 @@ export const TEXT_LIMITS = {
|
|
|
36
97
|
acceptanceIdBytes: 40,
|
|
37
98
|
acceptanceStatementBytes: 1_000,
|
|
38
99
|
acceptanceHowBytes: 500,
|
|
100
|
+
/** A builder's whole proof file, in UTF-8 bytes. */
|
|
101
|
+
proofPayloadBytes: 64 * 1024,
|
|
102
|
+
/** A proof criterion's id, in UTF-8 bytes. */
|
|
103
|
+
proofCriterionIdBytes: 40,
|
|
104
|
+
/** How a proof criterion was met, in UTF-8 bytes. */
|
|
105
|
+
proofHowBytes: 500,
|
|
106
|
+
/** One proof line — a criterion statement, evidence ref, check command or summary, changed path, caveat, or
|
|
107
|
+
* screenshot path or caption — in UTF-8 bytes. */
|
|
108
|
+
proofLineBytes: 300,
|
|
39
109
|
};
|
|
40
110
|
/** One chat message on each platform. Longer text is split across messages, never cut. Teams: the text one Adaptive Card
|
|
41
111
|
* holds, well inside its 28 KB message. */
|