dw-sdlc 0.21.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentic-config.d.ts +16 -0
- package/dist/agentic-config.js +28 -23
- package/dist/cannot-run.d.ts +27 -0
- package/dist/cannot-run.js +48 -0
- package/dist/catalogue.js +2 -1
- package/dist/checks/agents-map.js +7 -8
- package/dist/checks/check.d.ts +10 -8
- package/dist/checks/check.js +9 -7
- package/dist/checks/decision-records.js +3 -2
- package/dist/checks/file-size-ratchet.js +13 -17
- package/dist/checks/index.d.ts +6 -5
- package/dist/checks/index.js +10 -9
- package/dist/checks/markdown-refs.js +1 -1
- package/dist/checks/primitives.js +5 -5
- package/dist/checks/routed-docs.js +1 -3
- package/dist/checks/skill-sections.js +12 -20
- package/dist/checks/tree.js +7 -7
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +26 -5
- package/dist/commands/check.js +27 -42
- package/dist/commands/config.js +22 -27
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +11 -18
- package/dist/commands/gate.js +35 -40
- package/dist/commands/lead-start.js +28 -42
- package/dist/commands/pr-reviewed.js +41 -59
- package/dist/commands/pr-ship.js +71 -89
- package/dist/commands/primitives.js +38 -40
- package/dist/commands/recap.js +23 -74
- package/dist/commands/references-setup.js +28 -76
- package/dist/commands/references-sync.js +29 -53
- package/dist/commands/setup.d.ts +2 -7
- package/dist/commands/setup.js +38 -90
- package/dist/commands/sweep.js +46 -157
- package/dist/commands/worker-ls.js +15 -31
- package/dist/commands/worker-report.js +36 -52
- package/dist/commands/worker-start.js +43 -57
- package/dist/drift.js +2 -1
- package/dist/exec.d.ts +20 -0
- package/dist/exec.js +37 -4
- package/dist/gate-cache.js +2 -1
- package/dist/git.d.ts +32 -0
- package/dist/git.js +60 -0
- package/dist/json-schema.js +1 -3
- package/dist/landed.js +11 -22
- package/dist/paused-operation.d.ts +2 -0
- package/dist/paused-operation.js +17 -0
- package/dist/pins.js +2 -1
- package/dist/pr/decide.d.ts +4 -16
- package/dist/pr/decide.js +48 -84
- package/dist/pr/delta.d.ts +3 -4
- package/dist/pr/delta.js +10 -19
- package/dist/pr/read.d.ts +27 -30
- package/dist/pr/read.js +94 -182
- package/dist/pr/reply.js +3 -5
- package/dist/pr/route.d.ts +11 -3
- package/dist/pr/route.js +5 -5
- package/dist/pr/standing.d.ts +138 -0
- package/dist/pr/standing.js +181 -0
- package/dist/pr/state.d.ts +10 -69
- package/dist/pr/state.js +42 -72
- package/dist/pr/upstream.d.ts +1 -6
- package/dist/pr/upstream.js +2 -2
- package/dist/primitives-diff.js +1 -1
- package/dist/recap-next.js +34 -47
- package/dist/recap-primitives.js +1 -1
- package/dist/references-key.d.ts +0 -1
- package/dist/references-key.js +3 -4
- package/dist/run-command.d.ts +4 -1
- package/dist/run-command.js +32 -11
- package/dist/setup-gate.d.ts +1 -7
- package/dist/setup-rules.js +2 -6
- package/dist/setup-writer.d.ts +32 -0
- package/dist/setup-writer.js +60 -0
- package/dist/shell-line.d.ts +2 -0
- package/dist/shell-line.js +5 -0
- package/dist/sweep-removal.d.ts +31 -0
- package/dist/sweep-removal.js +133 -0
- package/dist/sweep-terminals.d.ts +1 -1
- package/dist/sweep-terminals.js +21 -57
- package/dist/sweep-trees.d.ts +19 -3
- package/dist/sweep-trees.js +67 -31
- package/dist/worker/herdr.d.ts +67 -23
- package/dist/worker/herdr.js +126 -77
- package/dist/worker/launch.js +32 -45
- package/dist/worker/origin.d.ts +1 -1
- package/dist/worker/origin.js +9 -11
- package/dist/worker/tabs.js +11 -7
- package/dist/working-tree.d.ts +0 -8
- package/dist/working-tree.js +6 -22
- package/dist/worktree-lock.js +1 -1
- package/package.json +1 -1
- package/dist/git-env.d.ts +0 -2
- package/dist/git-env.js +0 -10
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// The Review's standing at a pull request's head: what the Reviews before it read, from the review status
|
|
2
|
+
// each commit carries and the commits force pushes left behind; what the head carries of them; what the
|
|
3
|
+
// next Review reads, through a port onto GitHub's compares; and so the Review the head gets.
|
|
4
|
+
import { route } from "./route.js";
|
|
5
|
+
/**
|
|
6
|
+
* How many Reviews that each left a Finding open, with no success between them, the script lets run: the
|
|
7
|
+
* next call parks the pull request for the dev. A constant, like the depth rules, so no repository turns
|
|
8
|
+
* the loop back on.
|
|
9
|
+
*/
|
|
10
|
+
const REVIEW_LIMIT = 3;
|
|
11
|
+
/** A change as a key, equal exactly when the two changes are. */
|
|
12
|
+
function keyOf({ path, status, previous, lines }) {
|
|
13
|
+
return JSON.stringify([path, status, previous, lines]);
|
|
14
|
+
}
|
|
15
|
+
/** Whether the own changes at the head are those at the reviewed commit; unknown changes never are. */
|
|
16
|
+
function sameChanges(then, now) {
|
|
17
|
+
if (then === null || now === null) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return JSON.stringify(then.map(keyOf).toSorted()) === JSON.stringify(now.map(keyOf).toSorted());
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The files whose own change differs between `before` and `after`, a rename under both names; null when a
|
|
24
|
+
* compare cannot show either whole.
|
|
25
|
+
*/
|
|
26
|
+
function movedFiles(before, after) {
|
|
27
|
+
if (before === null || after === null) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const was = new Set(before.map(keyOf));
|
|
31
|
+
const is = new Set(after.map(keyOf));
|
|
32
|
+
const moved = [
|
|
33
|
+
...after.filter((change) => !was.has(keyOf(change))),
|
|
34
|
+
...before.filter((change) => !is.has(keyOf(change)))
|
|
35
|
+
];
|
|
36
|
+
const names = moved.flatMap(({ path, previous }) => (previous === null ? [path] : [path, previous]));
|
|
37
|
+
return [...new Set(names)].toSorted();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The last commit before the head a Review read, a success under off passed over, or `open`: a read mark
|
|
41
|
+
* on the head, or one with only merges after it. No fix answers its Findings, so no read stands in.
|
|
42
|
+
*/
|
|
43
|
+
function lastReadOf({ commits, head }) {
|
|
44
|
+
if (commits.some(({ oid, posted }) => oid === head && posted === "findings")) {
|
|
45
|
+
return { lastRead: null, open: head };
|
|
46
|
+
}
|
|
47
|
+
const index = commits.findLastIndex(({ oid, posted }) => oid !== head && (posted === "clean" || posted === "findings"));
|
|
48
|
+
const last = commits[index];
|
|
49
|
+
if (last === undefined) {
|
|
50
|
+
return { lastRead: null, open: null };
|
|
51
|
+
}
|
|
52
|
+
const after = commits.slice(index + 1);
|
|
53
|
+
const baseOnly = after.every(({ merge }) => merge);
|
|
54
|
+
if (last.posted === "findings" && baseOnly) {
|
|
55
|
+
return { lastRead: null, open: last.oid };
|
|
56
|
+
}
|
|
57
|
+
const baseMoved = after.some(({ merge }) => merge);
|
|
58
|
+
return { lastRead: { commit: last.oid, clean: last.posted === "clean", baseOnly, baseMoved }, open: null };
|
|
59
|
+
}
|
|
60
|
+
/** The newest commit force pushes left behind that a Review read: a rebase moved the base under it. */
|
|
61
|
+
function leftBehindRead({ leftBehind }) {
|
|
62
|
+
const read = leftBehind.find(({ posted }) => posted === "clean" || posted === "findings");
|
|
63
|
+
return read === undefined
|
|
64
|
+
? null
|
|
65
|
+
: { commit: read.oid, clean: read.posted === "clean", baseOnly: true, baseMoved: true };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A read mark a force push left behind and posted after every read of the history: the Review the head
|
|
69
|
+
* needs reads from it, not from the history's last read, whose clean status no longer stands.
|
|
70
|
+
*/
|
|
71
|
+
function leftBehindMark(history) {
|
|
72
|
+
const newest = inPostedOrder(history).findLast(({ posted }) => posted === "clean" || posted === "findings");
|
|
73
|
+
if (newest?.posted !== "findings" || history.commits.some(({ oid }) => oid === newest.oid)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return { commit: newest.oid, clean: false, baseOnly: true, baseMoved: true };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The read mark `read` left open, no fix showing after it (ADR 0023): no change since it after the branch's
|
|
80
|
+
* own commits alone, else the same own changes at both commits or a compare that cannot show them whole.
|
|
81
|
+
* Null for any other read.
|
|
82
|
+
*/
|
|
83
|
+
function openAt(read, { changed }, own, head) {
|
|
84
|
+
if (read === null || read.clean) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (!read.baseMoved) {
|
|
88
|
+
const since = changed(read.commit, head);
|
|
89
|
+
return since !== "unsized" && since.length === 0 ? read.commit : null;
|
|
90
|
+
}
|
|
91
|
+
const atRead = own(read.commit);
|
|
92
|
+
const atHead = own(head);
|
|
93
|
+
return atRead !== null && atHead !== null && !sameChanges(atRead, atHead) ? null : read.commit;
|
|
94
|
+
}
|
|
95
|
+
/** Whether a commit carries a review success of any kind. */
|
|
96
|
+
function isSuccess({ posted }) {
|
|
97
|
+
return posted === "clean" || posted === "off";
|
|
98
|
+
}
|
|
99
|
+
/** How many of `marked` carry a read mark. */
|
|
100
|
+
function findings(marked) {
|
|
101
|
+
return marked.filter(({ posted }) => posted === "findings").length;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The commits in the order their review statuses were posted: each line a force push left behind just
|
|
105
|
+
* after the commit of the history it was pushed on, older pushes first, and a line that holds none of
|
|
106
|
+
* the history before all of it.
|
|
107
|
+
*/
|
|
108
|
+
function inPostedOrder({ commits, leftBehind }) {
|
|
109
|
+
const oldestFirst = leftBehind.toReversed();
|
|
110
|
+
const pushedOn = (index) => oldestFirst.filter(({ forksAt }) => forksAt === index);
|
|
111
|
+
return [...pushedOn(-1), ...commits.flatMap((commit, index) => [commit, ...pushedOn(index)])];
|
|
112
|
+
}
|
|
113
|
+
/** The read marks posted after the last success, of the history and of the commits force pushes left behind. */
|
|
114
|
+
function readMarksOf(history) {
|
|
115
|
+
const posted = inPostedOrder(history);
|
|
116
|
+
const success = posted.findLastIndex(isSuccess);
|
|
117
|
+
return { count: findings(posted.slice(success + 1)), since: posted[success]?.oid ?? null };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The standing at the head of `history`. The compares run only when a Review `awaits` the head, and the
|
|
121
|
+
* files that Review would read are read only when `sized`, as `pr.review: auto` routes on them.
|
|
122
|
+
*/
|
|
123
|
+
export function standingOf(history, compares, { awaits, sized }) {
|
|
124
|
+
const marks = readMarksOf(history);
|
|
125
|
+
const inHistory = lastReadOf(history);
|
|
126
|
+
const read = inHistory.open === null ? (leftBehindMark(history) ?? inHistory.lastRead ?? leftBehindRead(history)) : null;
|
|
127
|
+
if (!awaits) {
|
|
128
|
+
return { lastRead: read, open: inHistory.open, marks, carried: false, changed: null, scope: null };
|
|
129
|
+
}
|
|
130
|
+
const { head, base } = history;
|
|
131
|
+
const own = (commit) => compares.ownChanges(base, commit);
|
|
132
|
+
const open = inHistory.open ?? openAt(read, compares, own, head);
|
|
133
|
+
const lastRead = open === null ? read : null;
|
|
134
|
+
const reads = { lastRead, open, marks };
|
|
135
|
+
// A clean read carries over the base's changes alone.
|
|
136
|
+
const carried = lastRead !== null && lastRead.clean && lastRead.baseOnly && sameChanges(own(lastRead.commit), own(head));
|
|
137
|
+
if (carried) {
|
|
138
|
+
return { ...reads, carried, changed: null, scope: null };
|
|
139
|
+
}
|
|
140
|
+
const scope = lastRead?.baseMoved === true ? movedFiles(own(lastRead.commit), own(head)) : null;
|
|
141
|
+
if (!sized) {
|
|
142
|
+
return { ...reads, carried, changed: null, scope };
|
|
143
|
+
}
|
|
144
|
+
const changed = compares.changed(lastRead?.commit ?? base, head);
|
|
145
|
+
if (scope === null || changed === "unsized") {
|
|
146
|
+
return { ...reads, carried, changed, scope };
|
|
147
|
+
}
|
|
148
|
+
const within = changed.filter(({ path, previous }) => scope.includes(path) || (previous !== null && scope.includes(previous)));
|
|
149
|
+
return { ...reads, carried, changed: within, scope };
|
|
150
|
+
}
|
|
151
|
+
/** Whether a Finding stands: a read mark since the last success, as the last read, or left open (ADR 0016). */
|
|
152
|
+
function findingsOpen({ marks, lastRead, open }) {
|
|
153
|
+
return marks.count > 0 || lastRead?.clean === false || open !== null;
|
|
154
|
+
}
|
|
155
|
+
/** The depth the head is read at: the dev's or the one `pr.review` sets, else the one the rules route. */
|
|
156
|
+
function depthOf({ depth, source }, standing) {
|
|
157
|
+
if (depth === "off") {
|
|
158
|
+
return { depth, source, why: source === "flag" ? "--review off" : "pr.review" };
|
|
159
|
+
}
|
|
160
|
+
if (depth !== "auto") {
|
|
161
|
+
return { depth, source };
|
|
162
|
+
}
|
|
163
|
+
return { ...route(standing.changed, findingsOpen(standing)), source: "auto" };
|
|
164
|
+
}
|
|
165
|
+
/** The Review the head gets at `standing` under `review`; the dev's word goes over a carry. */
|
|
166
|
+
export function reviewCall(standing, review) {
|
|
167
|
+
const { lastRead, open, marks, carried, scope } = standing;
|
|
168
|
+
const flagged = review.source === "flag";
|
|
169
|
+
if (carried && lastRead !== null && !flagged) {
|
|
170
|
+
return { kind: "carry", from: lastRead.commit };
|
|
171
|
+
}
|
|
172
|
+
const depth = depthOf(review, standing);
|
|
173
|
+
// The dev's own depth buys one Review past the limit, and only one: the flag rides on every
|
|
174
|
+
// later call. Their --review off posts past it: no one reads, by their word.
|
|
175
|
+
const limit = flagged ? REVIEW_LIMIT + 1 : REVIEW_LIMIT;
|
|
176
|
+
if (!(flagged && depth.depth === "off") && marks.count >= limit) {
|
|
177
|
+
return { ...marks, kind: "limit" };
|
|
178
|
+
}
|
|
179
|
+
// Over a carry the dev's depth reads the whole diff: the changes after the read are only the base's.
|
|
180
|
+
return { ...depth, kind: "review", since: carried ? null : (lastRead?.commit ?? null), paths: scope, open };
|
|
181
|
+
}
|
package/dist/pr/state.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { History, Standing } from "./standing.ts";
|
|
1
2
|
/** The commit status a clean review of the head leaves on it. */
|
|
2
3
|
export declare const REVIEW = "dw-sdlc/review";
|
|
3
4
|
/** How the description of a success the script posts under off begins: no one read that head. */
|
|
@@ -10,7 +11,7 @@ export declare const MARK_STATE = "failure";
|
|
|
10
11
|
export declare const READ_MARK = "read, findings open";
|
|
11
12
|
/** How the description of the success the dev's `--accept` posts reads: the head, taken with its Findings open. */
|
|
12
13
|
export declare const ACCEPTED = "accepted by the dev, findings open";
|
|
13
|
-
export declare const QUERY = "query($owner: String!, $name: String!, $number: Int!) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n url number state isDraft mergeable mergeStateStatus reviewDecision headRefOid headRefName headRef { name } isCrossRepository baseRefName\n baseRefOid baseRef { target { oid } } viewerDidAuthor mergeCommit { oid }\n ready: timelineItems(last: 1, itemTypes: [READY_FOR_REVIEW_EVENT]) { nodes { ... on ReadyForReviewEvent { createdAt } } }\n latestReviews(first: 100) { nodes { state author { login } commit { oid } } }\n reviewThreads(first: 100) { pageInfo { hasNextPage } nodes { id isResolved isOutdated path line\n comments(first: 1) { nodes { author { login } body } }\n latest: comments(last: 1) { nodes { viewerDidAuthor } } } }\n commits(last: 1) { nodes { commit { committedDate statusCheckRollup {\n contexts(first: 50) { pageInfo { hasNextPage } nodes {\n ... on CheckRun { name status conclusion detailsUrl startedAt isRequired(pullRequestNumber: $number) }\n ... on StatusContext { context state targetUrl createdAt isRequired(pullRequestNumber: $number) } } } } } } }\n history: commits(last: 100) { nodes { commit { oid parents { totalCount } status { context(name: \"dw-sdlc/review\") { state description } } } } }\n rewritten: timelineItems(last: 20, itemTypes: [HEAD_REF_FORCE_PUSHED_EVENT]) { nodes { ... on HeadRefForcePushedEvent {\n beforeCommit { oid history(first: 20) { nodes { oid parents(first: 1) { nodes { oid } }\n status { context(name: \"dw-sdlc/review\") { state description } } } } } } } }\n }\n }\n}";
|
|
14
|
+
export declare const QUERY = "query($owner: String!, $name: String!, $number: Int!) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n url number state isDraft mergeable mergeStateStatus reviewDecision headRefOid headRefName headRef { name } isCrossRepository baseRefName\n baseRefOid baseRef { target { oid } } viewerDidAuthor mergeCommit { oid }\n ready: timelineItems(last: 1, itemTypes: [READY_FOR_REVIEW_EVENT]) { nodes { ... on ReadyForReviewEvent { createdAt } } }\n latestReviews(first: 100) { nodes { state author { login } commit { oid } } }\n reviewThreads(first: 100) { pageInfo { hasNextPage } nodes { id isResolved isOutdated path line\n comments(first: 1) { nodes { author { login } body } }\n latest: comments(last: 1) { nodes { viewerDidAuthor } } } }\n commits(last: 1) { nodes { commit { committedDate statusCheckRollup {\n contexts(first: 50) { pageInfo { hasNextPage } nodes {\n ... on CheckRun { name status conclusion detailsUrl startedAt isRequired(pullRequestNumber: $number) }\n ... on StatusContext { context state targetUrl createdAt isRequired(pullRequestNumber: $number) } } } }\n status { context(name: \"dw-sdlc/review\") { context state targetUrl createdAt isRequired(pullRequestNumber: $number) } } } } }\n history: commits(last: 100) { nodes { commit { oid parents { totalCount } status { context(name: \"dw-sdlc/review\") { state description } } } } }\n rewritten: timelineItems(last: 20, itemTypes: [HEAD_REF_FORCE_PUSHED_EVENT]) { nodes { ... on HeadRefForcePushedEvent {\n beforeCommit { oid history(first: 20) { nodes { oid parents(first: 1) { nodes { oid } }\n status { context(name: \"dw-sdlc/review\") { state description } } } } } } } }\n }\n }\n}";
|
|
14
15
|
export type Bucket = "pass" | "fail" | "pending" | "skipping";
|
|
15
16
|
export type CiCheck = {
|
|
16
17
|
name: string;
|
|
@@ -38,7 +39,8 @@ export type ChangeRequest = {
|
|
|
38
39
|
reviewer: string;
|
|
39
40
|
answered: boolean;
|
|
40
41
|
};
|
|
41
|
-
|
|
42
|
+
/** The pull request as the reply holds it, before the Review's standing is read. */
|
|
43
|
+
export type PullRequest = {
|
|
42
44
|
url: string;
|
|
43
45
|
repo: string;
|
|
44
46
|
number: number;
|
|
@@ -74,67 +76,9 @@ export type PrState = {
|
|
|
74
76
|
cutOff: string[];
|
|
75
77
|
/** The oids of the pull request's last 100 commits, oldest first, the head last. */
|
|
76
78
|
commits: string[];
|
|
77
|
-
/**
|
|
78
|
-
* The last commit before the head a Review read, of the last 100: a review success not posted under
|
|
79
|
-
* off, or a read mark. With none there, a commit force pushes left behind that a Review read.
|
|
80
|
-
*/
|
|
81
|
-
lastRead: LastRead | null;
|
|
82
|
-
/**
|
|
83
|
-
* A read mark whose Findings no later commit answers: the head's own, or one a merge of the base or a
|
|
84
|
-
* rebase came after with no fix showing. Then `lastRead` is null, the Review reads the whole pull
|
|
85
|
-
* request, and nothing carries (ADR 0023).
|
|
86
|
-
*/
|
|
87
|
-
open: string | null;
|
|
88
|
-
/**
|
|
89
|
-
* The read marks since the last review success of any kind, of the last 100 commits, then, when they
|
|
90
|
-
* hold no success, of the commits force pushes left behind.
|
|
91
|
-
*/
|
|
92
|
-
marks: ReadMarks;
|
|
93
|
-
/** Whether the head carries the success of `lastRead`: the read decides it from two compares. */
|
|
94
|
-
carried: boolean;
|
|
95
|
-
/**
|
|
96
|
-
* The files a Review would read, from `lastRead` or the base to the head, read only for `pr.review:
|
|
97
|
-
* auto` to route on: `unsized` when the compare cannot list them all, null when not read.
|
|
98
|
-
*/
|
|
99
|
-
changed: ChangedFile[] | "unsized" | null;
|
|
100
|
-
/**
|
|
101
|
-
* The files whose own change moved since `lastRead`, when a merge of the base came after it: the files
|
|
102
|
-
* a fix touched, the base's own left out. Null with no such merge, or when a compare cannot show them.
|
|
103
|
-
*/
|
|
104
|
-
scope: string[] | null;
|
|
105
|
-
};
|
|
106
|
-
/**
|
|
107
|
-
* A file a Review would read: its name, its name before a rename, and the lines it adds and removes,
|
|
108
|
-
* null when the compare shows no patch for it, as for a binary file.
|
|
109
|
-
*/
|
|
110
|
-
export type ChangedFile = {
|
|
111
|
-
path: string;
|
|
112
|
-
previous: string | null;
|
|
113
|
-
lines: number | null;
|
|
114
|
-
};
|
|
115
|
-
/** A commit a Review read; `clean` when that Review left a success on it, not a read mark. */
|
|
116
|
-
export type Read = {
|
|
117
|
-
commit: string;
|
|
118
|
-
clean: boolean;
|
|
119
|
-
};
|
|
120
|
-
/**
|
|
121
|
-
* A commit a Review read; `baseOnly` when every change after it is the base's, the head included: only
|
|
122
|
-
* merges of the base, or a rebase past no other read; `baseMoved` when a merge of the base or a rebase
|
|
123
|
-
* came after it.
|
|
124
|
-
*/
|
|
125
|
-
export type LastRead = Read & {
|
|
126
|
-
baseOnly: boolean;
|
|
127
|
-
baseMoved: boolean;
|
|
128
|
-
};
|
|
129
|
-
/**
|
|
130
|
-
* The Reviews that left a Finding open since the last review success, a success under off and the dev's
|
|
131
|
-
* acceptance included: `count` their read marks, the head's own among them, and `since` that success's
|
|
132
|
-
* commit, null when none of the last 100 holds one.
|
|
133
|
-
*/
|
|
134
|
-
export type ReadMarks = {
|
|
135
|
-
count: number;
|
|
136
|
-
since: string | null;
|
|
137
79
|
};
|
|
80
|
+
/** The pull request and the Review's standing at its head. */
|
|
81
|
+
export type PrState = PullRequest & Standing;
|
|
138
82
|
/** The viewer's own pull request without a review success on its head: the one a Review reads. */
|
|
139
83
|
export declare function awaitsReview(pr: Pick<PrState, "viewerDidAuthor" | "checks">): boolean;
|
|
140
84
|
/** The repository and number of the pull request whose URL `gh pr view --json url` printed. */
|
|
@@ -144,15 +88,12 @@ export declare function parseView(reply: string): {
|
|
|
144
88
|
};
|
|
145
89
|
/** The default branch of the repository whose `gh repo view --json defaultBranchRef` printed `reply`. */
|
|
146
90
|
export declare function parseDefaultBranch(reply: string): string;
|
|
147
|
-
/** The state as the reply holds it, before the compares decide what the head carries and a Review reads. */
|
|
148
|
-
export type Parsed = Omit<PrState, "carried" | "changed" | "scope">;
|
|
149
91
|
/**
|
|
150
|
-
* The
|
|
151
|
-
*
|
|
152
|
-
* history still holds, where a Review's read stands once a rebase rewrote every commit it read.
|
|
92
|
+
* The pull request in `reply`, the text `gh api graphql` printed for QUERY on `repo`, and the history the
|
|
93
|
+
* Review's standing at its head is read from.
|
|
153
94
|
*/
|
|
154
95
|
export declare function parsePr(reply: string, repo: string): {
|
|
155
|
-
pr:
|
|
156
|
-
|
|
96
|
+
pr: PullRequest;
|
|
97
|
+
history: History;
|
|
157
98
|
};
|
|
158
99
|
export {};
|
package/dist/pr/state.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// One pull request as the Ship script reads it: the one GraphQL query, and its reply parsed into the
|
|
2
|
-
// state the decision reads
|
|
2
|
+
// state the decision reads, the review history the Review's standing is read from beside it. Every field
|
|
3
|
+
// the state needs is checked; a reply that fails names the field.
|
|
3
4
|
import { boolean, child, childOrNull, firstNode, hasMore, integer, integerOrNull, nodes, oneOf, ReplyError, rootOf, string, stringOrNull, time, timeOrNull } from "./reply.js";
|
|
4
5
|
/** The commit status a clean review of the head leaves on it. */
|
|
5
6
|
export const REVIEW = "dw-sdlc/review";
|
|
@@ -35,7 +36,8 @@ export const QUERY = `query($owner: String!, $name: String!, $number: Int!) {
|
|
|
35
36
|
commits(last: 1) { nodes { commit { committedDate statusCheckRollup {
|
|
36
37
|
contexts(first: ${CHECKS_READ}) { pageInfo { hasNextPage } nodes {
|
|
37
38
|
... on CheckRun { name status conclusion detailsUrl startedAt isRequired(pullRequestNumber: $number) }
|
|
38
|
-
... on StatusContext { context state targetUrl createdAt isRequired(pullRequestNumber: $number) } } } }
|
|
39
|
+
... on StatusContext { context state targetUrl createdAt isRequired(pullRequestNumber: $number) } } } }
|
|
40
|
+
status { context(name: "${REVIEW}") { context state targetUrl createdAt isRequired(pullRequestNumber: $number) } } } } }
|
|
39
41
|
history: commits(last: 100) { nodes { commit { oid parents { totalCount } status { context(name: "${REVIEW}") { state description } } } } }
|
|
40
42
|
rewritten: timelineItems(last: ${FORCE_PUSHES_READ}, itemTypes: [HEAD_REF_FORCE_PUSHED_EVENT]) { nodes { ... on HeadRefForcePushedEvent {
|
|
41
43
|
beforeCommit { oid history(first: ${LEFT_BEHIND_READ}) { nodes { oid parents(first: 1) { nodes { oid } }
|
|
@@ -89,6 +91,23 @@ function checkOf(context, repo) {
|
|
|
89
91
|
}
|
|
90
92
|
throw new ReplyError(`${context.at} is neither a CheckRun nor a StatusContext`);
|
|
91
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* The head's checks as the rollup lists them. With more past its first 50, the review status is read by
|
|
96
|
+
* its name too, so whether the head is reviewed never hangs on where the rollup lists it.
|
|
97
|
+
*/
|
|
98
|
+
function checksOf(commit, repo) {
|
|
99
|
+
const rollup = childOrNull(commit, "statusCheckRollup");
|
|
100
|
+
if (rollup === null) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
const listed = nodes(rollup, "contexts").map((context) => checkOf(context, repo));
|
|
104
|
+
if (!hasMore(rollup, "contexts") || listed.some(({ name }) => name === REVIEW)) {
|
|
105
|
+
return listed;
|
|
106
|
+
}
|
|
107
|
+
const status = childOrNull(commit, "status");
|
|
108
|
+
const review = status === null ? null : childOrNull(status, "context");
|
|
109
|
+
return review === null ? listed : [...listed, checkOf(review, repo)];
|
|
110
|
+
}
|
|
92
111
|
/** A thread by its opening comment; a deleted account's comment has no author and reads as GitHub's ghost. */
|
|
93
112
|
function threadOf(thread) {
|
|
94
113
|
const first = firstNode(thread, "comments");
|
|
@@ -134,34 +153,6 @@ function historyOf(pr) {
|
|
|
134
153
|
};
|
|
135
154
|
});
|
|
136
155
|
}
|
|
137
|
-
/**
|
|
138
|
-
* The last commit before `head` a Review read, the history oldest first, or `open`: a read mark on the
|
|
139
|
-
* head, or with only merges after it. No fix answers its Findings, so no read stands in (ADR 0023).
|
|
140
|
-
*/
|
|
141
|
-
function lastReadOf(history, head) {
|
|
142
|
-
if (history.some(({ oid, posted }) => oid === head && posted === "findings")) {
|
|
143
|
-
return { lastRead: null, open: head };
|
|
144
|
-
}
|
|
145
|
-
const index = history.findLastIndex(({ oid, posted }) => oid !== head && (posted === "clean" || posted === "findings"));
|
|
146
|
-
const last = history[index];
|
|
147
|
-
if (last === undefined) {
|
|
148
|
-
return { lastRead: null, open: null };
|
|
149
|
-
}
|
|
150
|
-
const after = history.slice(index + 1);
|
|
151
|
-
const baseOnly = after.every(({ merge }) => merge);
|
|
152
|
-
if (last.posted === "findings" && baseOnly) {
|
|
153
|
-
return { lastRead: null, open: last.oid };
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
lastRead: {
|
|
157
|
-
commit: last.oid,
|
|
158
|
-
clean: last.posted === "clean",
|
|
159
|
-
baseOnly,
|
|
160
|
-
baseMoved: after.some(({ merge }) => merge)
|
|
161
|
-
},
|
|
162
|
-
open: null
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
156
|
/**
|
|
166
157
|
* The commits a force push left behind: the head, then down its first parents within the history the
|
|
167
158
|
* read holds of it. A merge's first parent is the branch, so the walk stays off the base's side.
|
|
@@ -179,29 +170,27 @@ function lineOf(left) {
|
|
|
179
170
|
}
|
|
180
171
|
/**
|
|
181
172
|
* The commits the force pushes left behind, newest push first, each once and none the history still
|
|
182
|
-
* holds: a line stops where it meets the history, as under an amend, or a newer push's line
|
|
173
|
+
* holds: a line stops where it meets the history, as under an amend, or a newer push's line, and forks
|
|
174
|
+
* where the commit it meets does.
|
|
183
175
|
*/
|
|
184
|
-
function
|
|
185
|
-
const
|
|
176
|
+
function leftBehindOf(pr, history) {
|
|
177
|
+
const forks = new Map(history.map(({ oid }, index) => [oid, index]));
|
|
186
178
|
const left = [];
|
|
187
179
|
for (const event of nodes(pr, "rewritten").toReversed()) {
|
|
188
180
|
const head = childOrNull(event, "beforeCommit");
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
181
|
+
const line = head === null ? [] : lineOf(head);
|
|
182
|
+
const met = line.findIndex(({ oid }) => forks.has(oid));
|
|
183
|
+
const meets = line[met];
|
|
184
|
+
const forksAt = meets === undefined ? -1 : (forks.get(meets.oid) ?? -1);
|
|
185
|
+
for (const commit of meets === undefined ? line : line.slice(0, met)) {
|
|
186
|
+
forks.set(commit.oid, forksAt);
|
|
187
|
+
left.push({ ...commit, forksAt });
|
|
195
188
|
}
|
|
196
189
|
}
|
|
197
190
|
return left;
|
|
198
191
|
}
|
|
199
|
-
/** The commits of `forcePushed` a Review read; a success under off says no one read its commit. */
|
|
200
|
-
function rewrittenOf(forcePushed) {
|
|
201
|
-
return forcePushed.flatMap(({ oid, posted }) => posted === "clean" || posted === "findings" ? [{ commit: oid, clean: posted === "clean" }] : []);
|
|
202
|
-
}
|
|
203
192
|
/**
|
|
204
|
-
* Whether a non-merge commit follows `commit` in the history, oldest first, as
|
|
193
|
+
* Whether a non-merge commit follows `commit` in the history, oldest first, as the standing judges a read
|
|
205
194
|
* mark. A commit outside the history, after a force-push or past the last 100, is not answered.
|
|
206
195
|
*/
|
|
207
196
|
function answeredAfter(history, commit) {
|
|
@@ -221,23 +210,6 @@ function changeRequestsOf(pr, history) {
|
|
|
221
210
|
};
|
|
222
211
|
});
|
|
223
212
|
}
|
|
224
|
-
/**
|
|
225
|
-
* The read marks since the last success, the history's first; with no success there, the marks on the
|
|
226
|
-
* commits force pushes left behind go on the count, newest first, up to the first success among them.
|
|
227
|
-
*/
|
|
228
|
-
function readMarksOf(history, forcePushed) {
|
|
229
|
-
const isSuccess = ({ posted }) => posted === "clean" || posted === "off";
|
|
230
|
-
const findings = (commits) => commits.filter(({ posted }) => posted === "findings").length;
|
|
231
|
-
const success = history.findLastIndex(isSuccess);
|
|
232
|
-
const count = findings(history.slice(success + 1));
|
|
233
|
-
const lastSuccess = history[success];
|
|
234
|
-
if (lastSuccess !== undefined) {
|
|
235
|
-
return { count, since: lastSuccess.oid };
|
|
236
|
-
}
|
|
237
|
-
const pushedSuccess = forcePushed.findIndex(isSuccess);
|
|
238
|
-
const sincePushed = pushedSuccess < 0 ? forcePushed : forcePushed.slice(0, pushedSuccess);
|
|
239
|
-
return { count: count + findings(sincePushed), since: forcePushed[pushedSuccess]?.oid ?? null };
|
|
240
|
-
}
|
|
241
213
|
/** The repository and number of the pull request whose URL `gh pr view --json url` printed. */
|
|
242
214
|
export function parseView(reply) {
|
|
243
215
|
const url = string(rootOf(reply, "the reply of gh pr view"), "url");
|
|
@@ -252,9 +224,8 @@ export function parseDefaultBranch(reply) {
|
|
|
252
224
|
return string(child(rootOf(reply, "the reply of gh repo view"), "defaultBranchRef"), "name");
|
|
253
225
|
}
|
|
254
226
|
/**
|
|
255
|
-
* The
|
|
256
|
-
*
|
|
257
|
-
* history still holds, where a Review's read stands once a rebase rewrote every commit it read.
|
|
227
|
+
* The pull request in `reply`, the text `gh api graphql` printed for QUERY on `repo`, and the history the
|
|
228
|
+
* Review's standing at its head is read from.
|
|
258
229
|
*/
|
|
259
230
|
export function parsePr(reply, repo) {
|
|
260
231
|
const pr = child(child(child(rootOf(reply, "the reply"), "data"), "repository"), "pullRequest");
|
|
@@ -263,9 +234,9 @@ export function parsePr(reply, repo) {
|
|
|
263
234
|
const mergeCommit = childOrNull(pr, "mergeCommit");
|
|
264
235
|
const head = string(pr, "headRefOid");
|
|
265
236
|
const baseRef = childOrNull(pr, "baseRef");
|
|
266
|
-
const
|
|
237
|
+
const commits = historyOf(pr);
|
|
267
238
|
const [ready] = nodes(pr, "ready");
|
|
268
|
-
const
|
|
239
|
+
const parsed = {
|
|
269
240
|
url: string(pr, "url"),
|
|
270
241
|
repo,
|
|
271
242
|
number: integer(pr, "number"),
|
|
@@ -284,18 +255,17 @@ export function parsePr(reply, repo) {
|
|
|
284
255
|
base: string(pr, "baseRefName"),
|
|
285
256
|
baseOid: baseRef === null ? string(pr, "baseRefOid") : string(child(baseRef, "target"), "oid"),
|
|
286
257
|
mergeCommit: mergeCommit === null ? null : string(mergeCommit, "oid"),
|
|
287
|
-
checks:
|
|
258
|
+
checks: checksOf(commit, repo),
|
|
288
259
|
unresolvedThreads: nodes(pr, "reviewThreads")
|
|
289
260
|
.filter((thread) => !boolean(thread, "isResolved") && !boolean(thread, "isOutdated"))
|
|
290
261
|
.map(threadOf),
|
|
291
|
-
changeRequests: changeRequestsOf(pr,
|
|
262
|
+
changeRequests: changeRequestsOf(pr, commits),
|
|
292
263
|
cutOff: [
|
|
293
264
|
...(rollup !== null && hasMore(rollup, "contexts") ? [`the first ${CHECKS_READ} checks`] : []),
|
|
294
265
|
...(hasMore(pr, "reviewThreads") ? [`the first ${THREADS_READ} threads`] : [])
|
|
295
266
|
],
|
|
296
|
-
commits:
|
|
297
|
-
...lastReadOf(history, head)
|
|
267
|
+
commits: commits.map(({ oid }) => oid)
|
|
298
268
|
};
|
|
299
|
-
const
|
|
300
|
-
return { pr:
|
|
269
|
+
const history = { commits, leftBehind: leftBehindOf(pr, commits), head, base: parsed.baseOid };
|
|
270
|
+
return { pr: parsed, history };
|
|
301
271
|
}
|
package/dist/pr/upstream.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { PrState } from "./state.ts";
|
|
2
|
-
/**
|
|
3
|
-
* The remote branch name `branch` tracks, when it differs from `branch`. Read from the config, so it holds
|
|
4
|
-
* after the remote deleted the branch.
|
|
5
|
-
*/
|
|
6
|
-
export declare function upstreamName(root: string, branch: string): string | undefined;
|
|
7
2
|
/** Whether `pr`'s commits hold `head`, so the head is on GitHub whatever became of the upstream. */
|
|
8
|
-
export declare function holds(pr: PrState, head: string | null): boolean;
|
|
3
|
+
export declare function holds(pr: Pick<PrState, "commits">, head: string | null): boolean;
|
|
9
4
|
/** The checked-out branch at `root`, its head commit, and the other name its upstream gives. */
|
|
10
5
|
export type RenamedUpstream = {
|
|
11
6
|
branch: string;
|
package/dist/pr/upstream.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// A branch's upstream, read from git, for a branch pushed under another name: gh resolves
|
|
2
2
|
// its pull request neither through `@{push}` nor by the local name, so `pr ship` and recap ask by the name
|
|
3
3
|
// the upstream gives, and take that pull request only when its commits hold the local head.
|
|
4
|
-
import { asked } from "../
|
|
4
|
+
import { asked } from "../git.js";
|
|
5
5
|
/**
|
|
6
6
|
* The remote branch name `branch` tracks, when it differs from `branch`. Read from the config, so it holds
|
|
7
7
|
* after the remote deleted the branch.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
function upstreamName(root, branch) {
|
|
10
10
|
const remote = asked(root, ["config", "--get", `branch.${branch}.remote`]);
|
|
11
11
|
const merge = asked(root, ["config", "--get", `branch.${branch}.merge`]);
|
|
12
12
|
if (remote === undefined || remote === "." || merge === undefined || !merge.startsWith("refs/heads/")) {
|
package/dist/primitives-diff.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { resolveConfig } from "./agentic-config.js";
|
|
5
5
|
import { parseCatalogue, primitivesNaming } from "./catalogue.js";
|
|
6
6
|
import { catalogueFile } from "./checks/primitives.js";
|
|
7
|
-
import { asked, git } from "./
|
|
7
|
+
import { asked, git } from "./git.js";
|
|
8
8
|
const CLASSES = ["removes", "adds", "extends"];
|
|
9
9
|
const REMEDIES = {
|
|
10
10
|
missing: "create it, or point gate.checks.primitives.file at the catalogue",
|
package/dist/recap-next.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// The candidates for the session after this one, as `dw-sdlc recap` reads them: the open issues an agent may
|
|
2
2
|
// start, from one GraphQL request that also asks which issues the branch's pull request closes.
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { CannotRun } from "./cannot-run.js";
|
|
4
|
+
import { gh, said } from "./pr/read.js";
|
|
5
|
+
import { child, childOrNull, integer, nodes, ReplyError, rootOf, string } from "./pr/reply.js";
|
|
5
6
|
/** The label that marks an issue as ready for an agent to start. */
|
|
6
7
|
export const READY_LABEL = "ready-for-agent";
|
|
7
8
|
/** How many open issues the one read holds, oldest first, and how many issues the pull request closes. */
|
|
@@ -16,46 +17,33 @@ const QUERY = `query Backlog($owner: String!, $name: String!, $number: Int!, $cl
|
|
|
16
17
|
}
|
|
17
18
|
}`;
|
|
18
19
|
const REMEDY = "check gh auth status and the network, then call again";
|
|
19
|
-
|
|
20
|
-
return new CannotRun(`the backlog reply does not parse: ${field}`, "gh answered a shape dw-sdlc does not read; report it with the output of gh --version");
|
|
21
|
-
}
|
|
22
|
-
function nodesOf(value, field) {
|
|
23
|
-
if (!isObject(value) || !Array.isArray(value.nodes)) {
|
|
24
|
-
throw malformed(field);
|
|
25
|
-
}
|
|
26
|
-
return value.nodes;
|
|
27
|
-
}
|
|
28
|
-
function numberOf(value, field) {
|
|
29
|
-
if (!isObject(value) || typeof value.number !== "number") {
|
|
30
|
-
throw malformed(field);
|
|
31
|
-
}
|
|
32
|
-
return value.number;
|
|
33
|
-
}
|
|
20
|
+
const UNREAD_SHAPE = "gh answered a shape dw-sdlc does not read; report it with the output of gh --version";
|
|
34
21
|
/** An issue node as a candidate, or null when it has an assignee or an open blocker. */
|
|
35
22
|
function candidateOf(node) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
throw malformed("issues.nodes.assignees");
|
|
42
|
-
}
|
|
43
|
-
if (!isObject(dependencies) || typeof dependencies.blockedBy !== "number") {
|
|
44
|
-
throw malformed("issues.nodes.issueDependenciesSummary");
|
|
45
|
-
}
|
|
46
|
-
if (assignees.totalCount > 0 || dependencies.blockedBy > 0) {
|
|
23
|
+
const title = string(node, "title");
|
|
24
|
+
const url = string(node, "url");
|
|
25
|
+
const assignees = integer(child(node, "assignees"), "totalCount");
|
|
26
|
+
const blockedBy = integer(child(node, "issueDependenciesSummary"), "blockedBy");
|
|
27
|
+
if (assignees > 0 || blockedBy > 0) {
|
|
47
28
|
return null;
|
|
48
29
|
}
|
|
49
|
-
const
|
|
50
|
-
return { number,
|
|
30
|
+
const parent = childOrNull(node, "parent");
|
|
31
|
+
return { number: integer(node, "number"), title, url, parent: parent === null ? null : integer(parent, "number") };
|
|
51
32
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The candidates the backlog `reply` holds, the sub-issues of an issue the pull request closes first, each
|
|
35
|
+
* part in issue order. The issues it closes are read only when the query asked for them, `closing`.
|
|
36
|
+
*/
|
|
37
|
+
function candidatesIn(reply, closing) {
|
|
38
|
+
const repository = child(child(rootOf(reply, "the backlog reply"), "data"), "repository");
|
|
39
|
+
const closed = closing ? nodes(child(repository, "pullRequest"), "closingIssuesReferences") : [];
|
|
40
|
+
const closes = new Set(closed.map((node) => integer(node, "number")));
|
|
41
|
+
const candidates = nodes(repository, "issues")
|
|
42
|
+
.map(candidateOf)
|
|
43
|
+
.filter((candidate) => candidate !== null && !closes.has(candidate.number))
|
|
44
|
+
.toSorted((one, other) => one.number - other.number);
|
|
45
|
+
const underClosed = (candidate) => candidate.parent !== null && closes.has(candidate.parent);
|
|
46
|
+
return [...candidates.filter(underClosed), ...candidates.filter((candidate) => !underClosed(candidate))];
|
|
59
47
|
}
|
|
60
48
|
/**
|
|
61
49
|
* The open issues labelled ready-for-agent with no assignee and no open blocker, read from `repo`, or from the
|
|
@@ -81,16 +69,15 @@ function nextCandidates(pr) {
|
|
|
81
69
|
if (reply.status !== 0) {
|
|
82
70
|
throw new CannotRun(`gh could not read the backlog: ${said(reply.stderr)}`, REMEDY);
|
|
83
71
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return [...candidates.filter(underClosed), ...candidates.filter((candidate) => !underClosed(candidate))];
|
|
72
|
+
try {
|
|
73
|
+
return candidatesIn(reply.stdout, pr !== null);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error instanceof ReplyError) {
|
|
77
|
+
throw new CannotRun(`the backlog reply does not parse: ${error.message}`, UNREAD_SHAPE);
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
94
81
|
}
|
|
95
82
|
export function readNext(pr) {
|
|
96
83
|
try {
|
package/dist/recap-primitives.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// the default branch and a repository with no catalogue roll up to none.
|
|
4
4
|
import { resolveConfig } from "./agentic-config.js";
|
|
5
5
|
import { catalogueFile } from "./checks/primitives.js";
|
|
6
|
+
import { asked } from "./git.js";
|
|
6
7
|
import { primitivesDiff } from "./primitives-diff.js";
|
|
7
|
-
import { asked } from "./working-tree.js";
|
|
8
8
|
/** `defaultOf` names the default branch and is asked only once the repository has a catalogue and `branch` is no base. */
|
|
9
9
|
export function rollup(root, branch, defaultOf) {
|
|
10
10
|
const { guardrails, gate } = resolveConfig(root).config;
|