toolroll 0.9.56 → 0.9.57
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 +8 -0
- package/dist/assignment-adapters.d.ts +5 -0
- package/dist/assignment.d.ts +24 -3
- package/dist/assignment.js +36 -10
- package/dist/browser-workspace.js +1 -1
- package/dist/chat-delivery.js +3 -3
- package/dist/chat-voice.js +1 -1
- package/dist/check-levels.d.ts +3 -0
- package/dist/check-levels.js +5 -0
- package/dist/dispose.js +0 -5
- package/dist/lead-status.d.ts +1 -1
- package/dist/lead-status.js +2 -2
- package/dist/operate.js +17 -8
- package/dist/proof.d.ts +9 -0
- package/dist/proof.js +12 -0
- package/dist/release-coverage.d.ts +16 -0
- package/dist/release-coverage.js +92 -0
- package/dist/server/chat.js +8 -6
- package/dist/server/render-results.d.ts +4 -1
- package/dist/server/render-results.js +23 -9
- package/dist/server/tasks.js +28 -25
- package/dist/store.d.ts +3 -5
- package/dist/store.js +17 -2
- package/dist/task-outcome-cli.js +3 -1
- package/dist/task-status.d.ts +59 -7
- package/dist/task-status.js +132 -16
- package/dist/team-runtime.js +1 -1
- package/dist/telegram-status.d.ts +11 -4
- package/dist/telegram-status.js +17 -10
- package/dist/telegram.js +4 -4
- package/dist/work-index.d.ts +6 -1
- package/dist/work-index.js +50 -8
- package/dist/workspace-ui.js +2 -1
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ import { type BuildReviewView } from "../review-switch.js";
|
|
|
12
12
|
import { type AcceptanceCriterion, type approvalOf } from "../scope.js";
|
|
13
13
|
import { type ReportItem } from "../scout-report.js";
|
|
14
14
|
import { type Artifact, type DiffComment, type Publication, type RepairChainRow, type ReviewRetryState, type Run, type Store, type TaskState } from "../store.js";
|
|
15
|
-
import { type PullRequestFact, type TaskStatus } from "../task-status.js";
|
|
15
|
+
import { type ReleaseState, type PullRequestFact, type TaskStatus } from "../task-status.js";
|
|
16
16
|
import { type DisplayStatus, type PublicationFacts, type ReviewFacts } from "../workspace-ui.js";
|
|
17
17
|
import { type Chrome, type Screen } from "./chrome.js";
|
|
18
18
|
import { type Html } from "../html.js";
|
|
@@ -78,6 +78,7 @@ export declare function criterionMatrixHtml(matrix: readonly CriterionMatrixRow[
|
|
|
78
78
|
links?: EvidenceLinkMap;
|
|
79
79
|
fileAnchors?: ReadonlyMap<string, string>;
|
|
80
80
|
verdict?: string | null;
|
|
81
|
+
release?: ReleaseState | undefined;
|
|
81
82
|
}): Html;
|
|
82
83
|
/** One policy summary beneath the requirements. Per-requirement concerns
|
|
83
84
|
* are already visible in the matrix; do not repeat their full text here. */
|
|
@@ -674,6 +675,8 @@ export declare const CHECK_EXCERPT_LINES = 12;
|
|
|
674
675
|
* change's first file, labelled as not cited), the screenshots it cites
|
|
675
676
|
* (none cited: every validated one), and the agent's own note. */
|
|
676
677
|
export declare function personCheckItems(proof: ProofBundleView, patchText: string | null, shots: readonly ResultScreenshot[], runId: number): BrowserCheckItem[];
|
|
678
|
+
/** The completion blocker no acceptance resolves (a failed check, an unresolved HIGH finding), in words, or null. */
|
|
679
|
+
export declare function hardBlockerOf(assignment: AssignmentSnapshot | null | undefined): string | null;
|
|
677
680
|
/** A Needs you result's one action (needs-you.ts): a link to the act that resolves it, or Confirm it
|
|
678
681
|
* stopped behind the password. Null under every other headline. */
|
|
679
682
|
export declare function needActionOf(assignment: AssignmentSnapshot | null, status: TaskStatus | null, csrf: string, returnTo: string, result?: {
|
|
@@ -21,7 +21,7 @@ import {} from "../scope.js";
|
|
|
21
21
|
import { parseReport } from "../scout-report.js";
|
|
22
22
|
import {} from "../store.js";
|
|
23
23
|
import { runCostWords } from "../summary.js";
|
|
24
|
-
import { demoChecksOf, pullRequestFactOf, requirementsOf, requirementWordOf, STATUS_MORE, statusDetailsHtml, statusIconSvg, statusWhyHtml } from "../task-status.js";
|
|
24
|
+
import { checkBackingOf, CHECKED_AT_RELEASE, demoChecksOf, pullRequestFactOf, requirementsOf, requirementWordOf, STATUS_MORE, statusDetailsHtml, statusIconSvg, statusWhyHtml } from "../task-status.js";
|
|
25
25
|
import { ACCEPT_NEEDS_REASON, acceptWordsOf, cantAcceptYetOf, evidenceProblemOf, MISMATCH_HEADLINE, receiptHeadingOf, receiptPublicationWords, reportMismatchesOf, RESULT_DECISION_SENTENCE, resultHeadlineOf, resultStatusOf, REVIEW_TOKENS } from "../workspace-ui.js";
|
|
26
26
|
import { createHash, randomBytes } from "node:crypto";
|
|
27
27
|
import { buildsViews, oneLineOf, projectChip, safePrUrl, screen, sentenceCase, strokeIcon, when, whenTime } from "./chrome.js";
|
|
@@ -173,13 +173,14 @@ export function criterionMatrixHtml(matrix, options = {}) {
|
|
|
173
173
|
const confirmed = row.assessment !== undefined && state === "pass";
|
|
174
174
|
// Plain words (task-status brief): the retired assessment step is not a state a person acts on.
|
|
175
175
|
// The card's Requirements row reads the same words from the same source (task-status.ts), so they agree.
|
|
176
|
-
const label = requirementWordOf(row, options.verdict);
|
|
176
|
+
const label = requirementWordOf(row, options.verdict, options.release);
|
|
177
177
|
const cls = label === "Met" ? "badge-done" : label === "You check" ? "badge-manual-review" : "badge-note";
|
|
178
178
|
const warnings = [];
|
|
179
179
|
// Only replace the known boilerplate. Other recorded failure details
|
|
180
180
|
// remain verbatim, so concision cannot hide a different problem.
|
|
181
181
|
const detail = row.detail.filter(line => !(row.assessment && row.review && line === `${row.review.author} ${row.review.judgement === "contradicts" ? "contradicts" : "needs more evidence for"} criterion "${row.id}": ${row.review.note}`) && !(state === "manual-review" && /^criterion "[^"\n]+" requires manual-review evidence — an operator must accept it before this can verify$/.test(line)));
|
|
182
|
-
|
|
182
|
+
// A requirement a release check covers, or that waits only on one, has nothing wrong to say.
|
|
183
|
+
if (!awaitingAssessment && state !== "pass" && label !== "Met" && label !== CHECKED_AT_RELEASE && detail.length > 0)
|
|
183
184
|
warnings.push(html `<ul class="requirement-issues">${detail.map(line => html `<li>${line}</li>`)}</ul>`);
|
|
184
185
|
const review = row.review;
|
|
185
186
|
if (review !== null && review.judgement !== "upholds") {
|
|
@@ -671,13 +672,16 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
671
672
|
action: false,
|
|
672
673
|
});
|
|
673
674
|
parts.push(html `<div id="verification" data-cockpit-section="result">${panel.html}</div>`);
|
|
674
|
-
|
|
675
|
+
// What no acceptance resolves (a failed check, a HIGH finding): Complete is refused, so no Accept is offered and
|
|
676
|
+
// the one line says the next step.
|
|
677
|
+
const hardStop = hardBlockerOf(assignment);
|
|
678
|
+
const blocked = hardStop ?? cantAcceptYetOf(proof?.verdict ?? null, proof?.reasons ?? [], accepted);
|
|
675
679
|
const youCheck = panel.panel.youCheck;
|
|
676
680
|
// Accept and finish posts the completion; when an acceptance is owed (the person's own checks, or a reason a
|
|
677
681
|
// report that doesn't match its changes asks for), the same request records it first.
|
|
678
682
|
const owed = accepted ? null : youCheck?.accept != null ? { note: null } : panel.panel.need?.accept != null ? { note: panel.panel.need.accept.note }
|
|
679
683
|
: blocked === ACCEPT_NEEDS_REASON ? { note: "Why is this safe to accept?" } : null;
|
|
680
|
-
const complete = assignment?.state === "ready-to-check" && assignment.receipt !== null && canRetryReview && csrf !== ""
|
|
684
|
+
const complete = hardStop === null && assignment?.state === "ready-to-check" && assignment.receipt !== null && canRetryReview && csrf !== ""
|
|
681
685
|
? { action: `${taskHref(view.taskId)}/complete`, receipt: assignment.receipt.digest, run: run.id, accept: owed } : null;
|
|
682
686
|
if (complete !== null)
|
|
683
687
|
parts.push(completionForm(view.taskId, run.id, complete.receipt, view.detail?.pullRequestTo ?? null, owed));
|
|
@@ -686,8 +690,10 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
686
690
|
const matrix = proof === null || proof.proofProblem !== null ? [] : proof.matrix;
|
|
687
691
|
const unanswered = accepted || youCheck == null ? [] : youCheck.items.length > 0 ? youCheck.items.map(one => one.statement === "" ? one.words : one.statement) : youCheck.lines;
|
|
688
692
|
const base = !acceptsHere ? null : acceptWordsOf({
|
|
689
|
-
|
|
690
|
-
|
|
693
|
+
// Checks Off that a release check already covers read as passed (release-coverage.ts).
|
|
694
|
+
checks: checks === undefined ? null : checks.running != null ? "running" : checks.level === "off" && checks.status !== "passed" ? checks.release != null ? "passed" : "off" : checks.status,
|
|
695
|
+
// Release-aware words (task-status.ts): a requirement waiting only on a release check, or covered by one, isn't unmet.
|
|
696
|
+
unmet: matrix.filter(row => !["Met", "You check", CHECKED_AT_RELEASE].includes(requirementWordOf(row, null, checks === undefined ? undefined : checkBackingOf(checks)))).length,
|
|
691
697
|
action: complete !== null ? "complete" : "accept",
|
|
692
698
|
publishing: view.detail?.publishing ?? "other",
|
|
693
699
|
proof: proof !== null && proof.proof !== null && proof.proofProblem === null,
|
|
@@ -700,7 +706,7 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
700
706
|
const followUps = view.detail.followUps ?? null;
|
|
701
707
|
const checksRunning = checks?.running != null || (followUps?.checks.some(one => one.state === "waiting" || one.state === "running") ?? false);
|
|
702
708
|
const runChecks = canRetryReview && csrf !== "" && followUps !== null && (followUps.full || followUps.quick) && !checksRunning &&
|
|
703
|
-
(checks === undefined || checks.status === "not-run" || checks.status === "unavailable")
|
|
709
|
+
(checks === undefined || ((checks.status === "not-run" || checks.status === "unavailable") && checks.release == null))
|
|
704
710
|
// Back on this result's Checks tab, where #follow-ups shows the run it started.
|
|
705
711
|
? { action: `/r/${run.id}/checks`, level: followUps.full ? "full" : "quick", returnTo: `${here}&run=${run.id}&tab=checks` } : null;
|
|
706
712
|
const need = panel.panel.need;
|
|
@@ -1774,7 +1780,8 @@ export function resultPanelParts(detail, o) {
|
|
|
1774
1780
|
checkParts.push(detail.signedCriteria > 0 ? html `<p class="meta">The approved scope has ${detail.signedCriteria} requirement${detail.signedCriteria === 1 ? "" : "s"}, but this build has no requirement-by-requirement verification.</p>` : html `<p class="meta">This scope signed no acceptance checks.</p>`);
|
|
1775
1781
|
}
|
|
1776
1782
|
else {
|
|
1777
|
-
checkParts.push(criterionMatrixHtml(proof.matrix, { runId, links: proof.matrixLinks, fileAnchors: detail.fileAnchors, verdict: proof.verdict
|
|
1783
|
+
checkParts.push(criterionMatrixHtml(proof.matrix, { runId, links: proof.matrixLinks, fileAnchors: detail.fileAnchors, verdict: proof.verdict,
|
|
1784
|
+
release: current?.receipt == null ? undefined : checkBackingOf(current.receipt.checks) }));
|
|
1778
1785
|
}
|
|
1779
1786
|
checkParts.push(semanticCoverageHtml(proof.matrix, proof.qualityMode));
|
|
1780
1787
|
if (reviewConflict(proof.matrix, proof.machineVerdict, proof.verdict))
|
|
@@ -1930,11 +1937,18 @@ export function personCheckItems(proof, patchText, shots, runId) {
|
|
|
1930
1937
|
};
|
|
1931
1938
|
});
|
|
1932
1939
|
}
|
|
1940
|
+
/** The completion blocker no acceptance resolves (a failed check, an unresolved HIGH finding), in words, or null. */
|
|
1941
|
+
export function hardBlockerOf(assignment) {
|
|
1942
|
+
return assignment?.readiness?.blockers.find(one => one.key === "check-failed" || one.key === "high")?.message ?? null;
|
|
1943
|
+
}
|
|
1933
1944
|
/** A Needs you result's one action (needs-you.ts): a link to the act that resolves it, or Confirm it
|
|
1934
1945
|
* stopped behind the password. Null under every other headline. */
|
|
1935
1946
|
export function needActionOf(assignment, status, csrf, returnTo, result) {
|
|
1936
1947
|
if (assignment === null || status === null || status.headline !== "Needs you" || status.need == null)
|
|
1937
1948
|
return null;
|
|
1949
|
+
// Accepting can't resolve it: Request changes is the way on, beside the reason.
|
|
1950
|
+
if (result !== undefined && hardBlockerOf(assignment) !== null)
|
|
1951
|
+
return null;
|
|
1938
1952
|
const action = assignment.primaryAction;
|
|
1939
1953
|
const rebuild = { href: null, confirm: null, rebuild: { action: `${taskHref(assignment.rootId)}/requeue` } };
|
|
1940
1954
|
// Built to an earlier plan: Build again, in place (the task page's requeue).
|
package/dist/server/tasks.js
CHANGED
|
@@ -165,8 +165,8 @@ export function createTasksHandlers(runtime) {
|
|
|
165
165
|
// Console v2: results ready to review and work running now, from the same admitted work index.
|
|
166
166
|
...(() => {
|
|
167
167
|
const row = (one) => ({ taskId: one.rootId, title: one.title, detail: one.status.detail, repo: one.repo });
|
|
168
|
-
const needsYou = workIndexPage(store, now, workAccess(), { view: "needs-you", limit: 40, project });
|
|
169
|
-
const runningNow = workIndexPage(store, now, workAccess(), { view: "running", limit: 40, project });
|
|
168
|
+
const needsYou = workIndexPage(store, now, workAccess(), { view: "needs-you", limit: 40, project, root: evidenceRoot });
|
|
169
|
+
const runningNow = workIndexPage(store, now, workAccess(), { view: "running", limit: 40, project, root: evidenceRoot });
|
|
170
170
|
return { ready: needsYou.items.filter(one => one.assignmentState === "ready-to-check").map(row), running: runningNow.items.map(row) };
|
|
171
171
|
})(),
|
|
172
172
|
};
|
|
@@ -195,7 +195,7 @@ export function createTasksHandlers(runtime) {
|
|
|
195
195
|
const rollup = project === null && !unscopedMode;
|
|
196
196
|
let work;
|
|
197
197
|
try {
|
|
198
|
-
work = workIndexPage(store, now, workAccess(), { view, limit: 40, cursor: url.searchParams.get('cursor'), project });
|
|
198
|
+
work = workIndexPage(store, now, workAccess(), { view, limit: 40, cursor: url.searchParams.get('cursor'), project, root: evidenceRoot });
|
|
199
199
|
const facts = requestContext.getStore();
|
|
200
200
|
if (facts !== undefined) {
|
|
201
201
|
// These counts cover the exact admitted project lens, including
|
|
@@ -1382,7 +1382,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1382
1382
|
// CI repair, suggestion-first (M8.18): a red episode never spawns an
|
|
1383
1383
|
// agent by itself — it EARNS a button, and the button creates one
|
|
1384
1384
|
// unapproved task through the same revision machinery as review
|
|
1385
|
-
// comments.
|
|
1385
|
+
// comments. One draft per task/PR, ever: the merge blocker records it.
|
|
1386
1386
|
const id = Number(draftRepair[1]);
|
|
1387
1387
|
const found = store.getRun(id);
|
|
1388
1388
|
if (found === null || !visible(taskRepoOf(found.taskRef))) {
|
|
@@ -1401,10 +1401,6 @@ export function createTasksHandlers(runtime) {
|
|
|
1401
1401
|
// failure was SEEN on and when (audit C-2) — a PR that advanced since
|
|
1402
1402
|
// is a different failure, and the click time is not an observation.
|
|
1403
1403
|
const episode = store.latestOpenCiEpisode(publication.githubRepo, publication.prNumber);
|
|
1404
|
-
// Suffixes survive truncation (audit C-7): the prefix gives way, the
|
|
1405
|
-
// identity-bearing tail never does.
|
|
1406
|
-
const suffix = `-ci-${publication.prNumber}`;
|
|
1407
|
-
const draftId = `${sourceTaskId.slice(0, 64 - suffix.length)}${suffix}`;
|
|
1408
1404
|
const brief = {
|
|
1409
1405
|
schema: 1,
|
|
1410
1406
|
kind: "ci-repair",
|
|
@@ -1425,25 +1421,32 @@ export function createTasksHandlers(runtime) {
|
|
|
1425
1421
|
// repair use (contract handoff task 2): the inherited terms come from
|
|
1426
1422
|
// the source rows inside the seal; the scope read above only names
|
|
1427
1423
|
// the digest this draft was composed against.
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1424
|
+
// One draft per task/PR, ever: the PR's merge blocker names it, lifted or not, and is written in the seal's transaction.
|
|
1425
|
+
const sealed = store.transact(() => {
|
|
1426
|
+
const prior = store.raw().prepare("SELECT task_id FROM merge_blocker WHERE publication = ? AND task_id IS NOT NULL ORDER BY rowid LIMIT 1").get(publication.id);
|
|
1427
|
+
if (prior !== undefined)
|
|
1428
|
+
return { ok: false, reason: "duplicate", detail: String(prior["task_id"]) };
|
|
1429
|
+
const result = store.sealRevision({
|
|
1430
|
+
source: { task: sourceTaskId, run: id, scopeDigest: sourceScope?.digest ?? null },
|
|
1431
|
+
brief: { evidenceRoot, key, sha256: createHash("sha256").update(briefBytes).digest("hex"), bytes: briefBytes.length, capture: "machine-authored ci-repair brief (exit 0)" },
|
|
1432
|
+
child: {
|
|
1433
|
+
title: `repair ${sourceTaskId}: CI failing on PR #${publication.prNumber}`,
|
|
1434
|
+
repair: `repair the failing CI on PR #${publication.prNumber} (failing head ${(episode?.headSha ?? publication.headSha).slice(0, 12)}). ` +
|
|
1435
|
+
`Read the failing checks on GitHub before approving; this draft carries no log content.`,
|
|
1436
|
+
},
|
|
1437
|
+
commentIds: null,
|
|
1438
|
+
requestedBy: who.name,
|
|
1439
|
+
}, now);
|
|
1440
|
+
// The merge blocker rides the SAME breath as the draft (merge grant,
|
|
1441
|
+
// findings 3/12/13): while this repair exists, the source PR merges
|
|
1442
|
+
// NOTHING — sticky until the operator's unblock act or the PR closes.
|
|
1443
|
+
if (result.ok)
|
|
1444
|
+
store.createMergeBlocker(publication.id, result.id, now);
|
|
1445
|
+
return result;
|
|
1446
|
+
});
|
|
1440
1447
|
if (!sealed.ok) {
|
|
1441
|
-
return refuse(response, who, sealed.reason === "duplicate" ? 409 : sealed.reason === "stale-source" || sealed.reason === "comments-taken" ? 409 : 400, sealed.reason === "duplicate" ? `already drafted as ${
|
|
1448
|
+
return refuse(response, who, sealed.reason === "duplicate" ? 409 : sealed.reason === "stale-source" || sealed.reason === "comments-taken" ? 409 : 400, sealed.reason === "duplicate" ? `already drafted as ${sealed.detail}` : `could not draft: ${sealed.detail}`, `/r/${id}`);
|
|
1442
1449
|
}
|
|
1443
|
-
// The merge blocker rides the SAME breath as the draft (merge grant,
|
|
1444
|
-
// findings 3/12/13): while this repair exists, the source PR merges
|
|
1445
|
-
// NOTHING — sticky until the operator's unblock act or the PR closes.
|
|
1446
|
-
store.createMergeBlocker(publication.id, sealed.id, now);
|
|
1447
1450
|
return redirect(response, taskHref(sealed.id));
|
|
1448
1451
|
}
|
|
1449
1452
|
async function resolveIncident(ctx) {
|
package/dist/store.d.ts
CHANGED
|
@@ -3018,7 +3018,7 @@ export declare class Store {
|
|
|
3018
3018
|
* transaction can never commit half a seal). A brief FILE whose seal
|
|
3019
3019
|
* failed is an orphan on disk, not authority — no artifact row points at
|
|
3020
3020
|
* it. Two concurrent seals race on the comment consumption count, the
|
|
3021
|
-
*
|
|
3021
|
+
* family's next version id, or the repair chain's `source_run UNIQUE`, and
|
|
3022
3022
|
* exactly one wins; the loser refuses in words with zero rows.
|
|
3023
3023
|
*
|
|
3024
3024
|
* The child is filed UNAPPROVED, always: this method never stamps an
|
|
@@ -3044,10 +3044,9 @@ export declare class Store {
|
|
|
3044
3044
|
bytes: number;
|
|
3045
3045
|
capture: string;
|
|
3046
3046
|
};
|
|
3047
|
-
/** The child's own words:
|
|
3048
|
-
*
|
|
3047
|
+
/** The child's own words: its title and the explicitly described repair
|
|
3048
|
+
* appended to the SOURCE goal. Its id is the family's next version. */
|
|
3049
3049
|
child: {
|
|
3050
|
-
id?: string;
|
|
3051
3050
|
title: string;
|
|
3052
3051
|
repair: string;
|
|
3053
3052
|
};
|
|
@@ -3169,7 +3168,6 @@ export declare class Store {
|
|
|
3169
3168
|
capture: string;
|
|
3170
3169
|
};
|
|
3171
3170
|
child: {
|
|
3172
|
-
id: string;
|
|
3173
3171
|
title: string;
|
|
3174
3172
|
repair: string;
|
|
3175
3173
|
};
|
package/dist/store.js
CHANGED
|
@@ -8756,7 +8756,7 @@ export class Store {
|
|
|
8756
8756
|
* transaction can never commit half a seal). A brief FILE whose seal
|
|
8757
8757
|
* failed is an orphan on disk, not authority — no artifact row points at
|
|
8758
8758
|
* it. Two concurrent seals race on the comment consumption count, the
|
|
8759
|
-
*
|
|
8759
|
+
* family's next version id, or the repair chain's `source_run UNIQUE`, and
|
|
8760
8760
|
* exactly one wins; the loser refuses in words with zero rows.
|
|
8761
8761
|
*
|
|
8762
8762
|
* The child is filed UNAPPROVED, always: this method never stamps an
|
|
@@ -8848,8 +8848,9 @@ export class Store {
|
|
|
8848
8848
|
const badText = validateTaskText({ title: args.child.title, goal: args.child.repair });
|
|
8849
8849
|
if (badText !== null)
|
|
8850
8850
|
return refuse(badText.reason, badText.message);
|
|
8851
|
+
// Every road names its child the same way: the family root and the next version, `<root>-v2`, `-v3`.
|
|
8851
8852
|
const made = this.#insertConsoleTask({
|
|
8852
|
-
|
|
8853
|
+
id: this.#nextRevisionId(ancestry.chain.at(-1) ?? args.source.task),
|
|
8853
8854
|
title: args.child.title,
|
|
8854
8855
|
...(sourceRef.repo === null ? {} : { repo: sourceRef.repo }),
|
|
8855
8856
|
goal,
|
|
@@ -8926,6 +8927,20 @@ export class Store {
|
|
|
8926
8927
|
}
|
|
8927
8928
|
return { chain, problem: null };
|
|
8928
8929
|
}
|
|
8930
|
+
/** The next free `<root>-vN` id inside the caller's transaction: N counts the family's versions (the root is v1),
|
|
8931
|
+
* skipping any id already taken. The suffix survives truncation to the 64-character id bound. */
|
|
8932
|
+
#nextRevisionId(root) {
|
|
8933
|
+
const family = this.db.prepare(`WITH RECURSIVE fam(id, depth) AS (
|
|
8934
|
+
SELECT external_id, 0 FROM task_ref WHERE backend = ? AND external_id = ?
|
|
8935
|
+
UNION SELECT r.external_id, fam.depth + 1 FROM task_ref r JOIN fam ON r.revision_of = fam.id WHERE r.backend = ? AND fam.depth < 64)
|
|
8936
|
+
SELECT COUNT(DISTINCT id) AS n FROM fam`).get(BUILT_IN, root, BUILT_IN);
|
|
8937
|
+
const taken = (id) => this.db.prepare("SELECT 1 AS hit FROM task WHERE id = ? UNION ALL SELECT 1 FROM task_ref WHERE backend = ? AND external_id = ?").get(id, BUILT_IN, id) !== undefined;
|
|
8938
|
+
const named = (n) => `${root.slice(0, 64 - `-v${n}`.length)}-v${n}`;
|
|
8939
|
+
let n = Math.max(2, Number(family?.["n"] ?? 1) + 1);
|
|
8940
|
+
while (taken(named(n)))
|
|
8941
|
+
n++;
|
|
8942
|
+
return named(n);
|
|
8943
|
+
}
|
|
8929
8944
|
revisionAncestryOf(taskId) {
|
|
8930
8945
|
return this.revisionAncestryStatus(taskId).chain;
|
|
8931
8946
|
}
|
package/dist/task-outcome-cli.js
CHANGED
|
@@ -82,7 +82,9 @@ export async function runTaskOutcomeCommand(action, positional, flags, context)
|
|
|
82
82
|
if (!changed.ok)
|
|
83
83
|
return emit(changed);
|
|
84
84
|
const assignment = 'body' in changed ? changed.body : changed.assignment;
|
|
85
|
-
|
|
85
|
+
// Checks deliberately Off: nothing to say about them here.
|
|
86
|
+
return emit({ ok: true, result: assignment }, [`${current.rootId} · Complete`, `Result: ${current.activeTaskId} · run ${current.receipt.runId} · ${current.receipt.head ?? 'no commit'}`,
|
|
87
|
+
...(current.readiness?.checksOff ? [] : [current.receipt.checks.detail])]);
|
|
86
88
|
}
|
|
87
89
|
const feedback = flags.get('feedback'), runFlag = flags.get('run'), sourceFlag = flags.get('source'), keyFlag = flags.get('key');
|
|
88
90
|
if (typeof feedback !== 'string' || !feedback.trim())
|
package/dist/task-status.d.ts
CHANGED
|
@@ -56,6 +56,24 @@ export type ChecksFact = {
|
|
|
56
56
|
running?: "quick" | "full" | null;
|
|
57
57
|
/** A batch check (batch-checks.ts): waiting for its batch, or how it was checked. */
|
|
58
58
|
batch?: ChecksBatch | null;
|
|
59
|
+
/** Off: the passing full release check whose commit contains this one (release-coverage.ts), read at read time. */
|
|
60
|
+
release?: {
|
|
61
|
+
run: number;
|
|
62
|
+
head: string;
|
|
63
|
+
} | null;
|
|
64
|
+
};
|
|
65
|
+
/** What checks Off leaves for a release: `pending` until a passing full release check contains the commit, then
|
|
66
|
+
* `covered`. `running`: a check is waiting for its batch or running on this commit. */
|
|
67
|
+
export type ReleaseState = "pending" | "covered" | "running";
|
|
68
|
+
export declare const CHECKED_AT_RELEASE = "Checked at release";
|
|
69
|
+
export declare function releaseStateOf(checks: Pick<ChecksFact, "status" | "level" | "release"> | null | undefined): ReleaseState | undefined;
|
|
70
|
+
/** How the project check stands for requirements that rest on it, read now: `covered` once a check passed on this
|
|
71
|
+
* commit (its own, a follow-up, or a release check containing it), `pending` while checks Off wait for a release. */
|
|
72
|
+
export declare function checkBackingOf(checks: Pick<ChecksFact, "status" | "level" | "release" | "running" | "batch"> | null | undefined): ReleaseState | undefined;
|
|
73
|
+
/** What stands between a finished result and Complete (`completionBlockersOf`): each with the exact next step. */
|
|
74
|
+
export type CompletionBlocker = {
|
|
75
|
+
key: "check-failed" | "check-missing" | "criteria" | "high";
|
|
76
|
+
message: string;
|
|
59
77
|
};
|
|
60
78
|
/** `waiting`: the result waits to be checked with others. Otherwise how its check ran: `together` on the
|
|
61
79
|
* temporary batch commit `tested` with `peers`; `split` (its batch failed), `conflict` or `alone` on its own commit. */
|
|
@@ -94,14 +112,22 @@ export type TaskStatusFacts = {
|
|
|
94
112
|
checks?: ChecksFact | null;
|
|
95
113
|
pullRequest?: PullRequestFact | null;
|
|
96
114
|
/** `unverified`: the report is refuted, so no requirement counts as met however it was marked. `missed`: the ones it
|
|
97
|
-
* failed or left unanswered.
|
|
115
|
+
* failed or left unanswered. `atRelease`: checks Off, waiting for a release check. `unshown`: a required screenshot
|
|
116
|
+
* is missing. `ask`: what a person is asked to look at first (a requirement they check, or a missing screenshot). */
|
|
98
117
|
requirements?: {
|
|
99
118
|
met: number;
|
|
100
119
|
total: number;
|
|
101
120
|
yours: number;
|
|
102
121
|
missed?: number;
|
|
103
122
|
unverified?: boolean;
|
|
123
|
+
atRelease?: number;
|
|
124
|
+
unshown?: number;
|
|
125
|
+
ask?: string | null;
|
|
104
126
|
} | null;
|
|
127
|
+
/** A person accepted this result's proof: their own check and a recorded exception need nothing more. */
|
|
128
|
+
accepted?: boolean;
|
|
129
|
+
/** What refuses Complete (`completionBlockersOf`); the first one is the sentence when it holds the result back. */
|
|
130
|
+
blockers?: readonly CompletionBlocker[];
|
|
105
131
|
evidence?: {
|
|
106
132
|
shortened: number;
|
|
107
133
|
missing: number;
|
|
@@ -124,11 +150,14 @@ export type TaskStatusFacts = {
|
|
|
124
150
|
/** What the person calls their lead (Settings → Lead); "Lead" when unnamed. */
|
|
125
151
|
leadName?: string;
|
|
126
152
|
};
|
|
127
|
-
export declare function headlineOf(facts: Pick<TaskStatusFacts, "stage" | "checks" | "report">): Headline;
|
|
153
|
+
export declare function headlineOf(facts: Pick<TaskStatusFacts, "stage" | "checks" | "report" | "requirements" | "accepted" | "blockers">): Headline;
|
|
128
154
|
/** What the lead's claim says, in place of "waits for you" (lead-voice.ts). */
|
|
129
155
|
export declare const leadOnIt: (name?: string) => string;
|
|
130
156
|
export declare const leadLapsed: (name?: string) => string;
|
|
131
|
-
|
|
157
|
+
/** The one status every surface shows (console list, task and result pages, CLI, chat, phone, `status`). */
|
|
158
|
+
export declare function statusOf(facts: TaskStatusFacts): TaskStatus;
|
|
159
|
+
/** The name most surfaces already import. */
|
|
160
|
+
export declare const taskStatusOf: typeof statusOf;
|
|
132
161
|
export declare function plainReasonOf(stage: TaskStage, code: string, detail: string | null | undefined): string | null;
|
|
133
162
|
export declare function stageOfCode(code: string, options?: {
|
|
134
163
|
needsPerson?: boolean;
|
|
@@ -165,18 +194,41 @@ export declare function assignmentStageOf(assignment: Pick<AssignmentSnapshot, "
|
|
|
165
194
|
wait?: WaitKey;
|
|
166
195
|
};
|
|
167
196
|
type RequirementRow = {
|
|
197
|
+
id?: string;
|
|
198
|
+
statement?: string;
|
|
168
199
|
state: string;
|
|
200
|
+
requiredEvidence?: readonly string[];
|
|
201
|
+
answered?: readonly {
|
|
202
|
+
kind: string;
|
|
203
|
+
}[];
|
|
204
|
+
detail?: readonly string[];
|
|
169
205
|
assessment?: {
|
|
170
206
|
evidenceState?: string;
|
|
207
|
+
detail?: readonly string[];
|
|
171
208
|
} | undefined;
|
|
172
209
|
review?: unknown;
|
|
173
210
|
};
|
|
174
211
|
/** One requirement's state in words: the card's Requirements row and the Checks tab read this one source, so they
|
|
175
|
-
* can't disagree. A refuted report verifies none of its requirements, so each reads Unverified, as the card does.
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
* can't disagree. A refuted report verifies none of its requirements, so each reads Unverified, as the card does.
|
|
213
|
+
* With checks Off, a requirement that waits only on the check reads Checked at release, then Met once a passing full
|
|
214
|
+
* release check contains the commit. */
|
|
215
|
+
export type RequirementWord = "Met" | "You check" | "Not shown yet" | "Checked at release" | "Waiting for checks" | "Not met" | "Unverified";
|
|
216
|
+
export declare function requirementWordOf(row: RequirementRow, verdict?: string | null, release?: ReleaseState): RequirementWord;
|
|
178
217
|
/** Requirements from the stored matrix: met, the ones only a person confirms, and the total. */
|
|
179
|
-
export declare function requirementsOf(matrix: readonly RequirementRow[] | null | undefined): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
218
|
+
export declare function requirementsOf(matrix: readonly RequirementRow[] | null | undefined, release?: ReleaseState): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
219
|
+
/** What refuses Complete, for `task complete`, the console and chat completion and deploy-candidate alike: a failed
|
|
220
|
+
* check; a required check that is missing (checks deliberately Off are not missing: they are checked at release);
|
|
221
|
+
* an unresolved requirement (a person's own check is theirs to give by completing; a recorded acceptance resolves
|
|
222
|
+
* the rest); an unresolved HIGH review finding. A release check that covers the commit counts as passed. */
|
|
223
|
+
export declare function completionBlockersOf(f: {
|
|
224
|
+
report: boolean;
|
|
225
|
+
checks: Pick<ChecksFact, "status" | "exitCode" | "level" | "release" | "running" | "batch"> | null;
|
|
226
|
+
checkRequired: boolean;
|
|
227
|
+
matrix: readonly RequirementRow[] | null | undefined;
|
|
228
|
+
verdict: string | null | undefined;
|
|
229
|
+
accepted: boolean;
|
|
230
|
+
high: number;
|
|
231
|
+
}): CompletionBlocker[];
|
|
180
232
|
/** A refuted report verifies none of its requirements, whatever it marked met (a person's acceptance doesn't change that). */
|
|
181
233
|
export declare function unverifiedWhenRefuted(requirements: NonNullable<TaskStatusFacts["requirements"]> | null, verdict: string | null | undefined): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
182
234
|
/** Saved evidence health from the receipt's artifact list and its caveats. */
|