@boxxy/closeout 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/build-schema-parser.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/phases/verify.d.ts +1 -0
- package/package.json +4 -4
- package/src/build-schema-parser.ts +2 -2
- package/src/index.ts +1 -0
- package/src/phase-kinds.ts +2 -1
- package/src/phases/build.ts +30 -5
- package/src/phases/verify.ts +67 -0
package/README.md
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
</p>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
boxxy's phase kinds: triage, design, build and
|
|
15
|
-
checks the close-out the agent sends at the end of it, and settles the attempt from that
|
|
14
|
+
boxxy's phase kinds: triage, design, build, review and verify. Each one gives the input of an
|
|
15
|
+
attempt, checks the close-out the agent sends at the end of it, and settles the attempt from that
|
|
16
|
+
close-out.
|
|
16
17
|
|
|
17
18
|
The package does no I/O: the caller passes in the item, its attempts, the attempt's evidence rows
|
|
18
19
|
and what a close-out may name.
|
|
@@ -29,8 +30,8 @@ The package runs on [Bun](https://bun.sh) 1.3 or newer.
|
|
|
29
30
|
|
|
30
31
|
## Features
|
|
31
32
|
|
|
32
|
-
- `triage`, `design` and `
|
|
33
|
-
parser returns the stored close-out, or every problem with it in one pass.
|
|
33
|
+
- `triage`, `design`, `build` and `verify`, and `phaseKinds`, which lists them for the rules loader.
|
|
34
|
+
Each parser returns the stored close-out, or every problem with it in one pass.
|
|
34
35
|
- `createReviewPhase`, which builds the review phase kind on the review gate's `buildCommand`. The
|
|
35
36
|
server adds it to `phaseKinds`.
|
|
36
37
|
- `countUnrelatedLines`, the changed lines of a later review in files that had no open finding.
|
|
@@ -2,7 +2,7 @@ import type { CloseOutContext, CloseOutParser, CloseOutProblem } from '@boxxy/mo
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
interface SchemaParserInput<Raw, Body> {
|
|
4
4
|
readonly schema: z.ZodType<Raw>;
|
|
5
|
-
readonly toBody: (raw: Raw, frictionDropped: number) => Body;
|
|
5
|
+
readonly toBody: (raw: Raw, frictionDropped: number, ctx: CloseOutContext) => Body;
|
|
6
6
|
readonly check: (body: Body, ctx: CloseOutContext) => readonly CloseOutProblem[];
|
|
7
7
|
}
|
|
8
8
|
export declare function buildSchemaParser<Raw, Body>(input: SchemaParserInput<Raw, Body>): CloseOutParser<Body>;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export { build } from './phases/build.ts';
|
|
|
6
6
|
export { design } from './phases/design.ts';
|
|
7
7
|
export { createReviewPhase } from './phases/create-review-phase.ts';
|
|
8
8
|
export { triage } from './phases/triage.ts';
|
|
9
|
+
export { verify } from './phases/verify.ts';
|
|
9
10
|
export { countUnrelatedLines } from './review/count-unrelated-lines.ts';
|
|
10
11
|
export type { CloseOutLimits } from './types.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const verify: import("@boxxy/model").PhaseKind<unknown, import("@boxxy/content-hash").JsonValue, readonly string[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boxxy/closeout",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "boxxy's phase kinds: the input, the close-out parser and the settle rule of triage, design, build and review",
|
|
5
5
|
"homepage": "https://github.com/zgeoff/boxxy/tree/main/modules/closeout#readme",
|
|
6
6
|
"bugs": "https://github.com/zgeoff/boxxy/issues",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"typecheck": "tsrs --noEmit"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@boxxy/model": "0.2.
|
|
33
|
+
"@boxxy/model": "0.2.4",
|
|
34
34
|
"tiny-invariant": "1.3.3",
|
|
35
35
|
"ts-pattern": "5.9.2",
|
|
36
36
|
"zod": "4.6.7"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@boxxy/planning": "0.2.
|
|
40
|
-
"@boxxy/rules": "0.2.
|
|
39
|
+
"@boxxy/planning": "0.2.4",
|
|
40
|
+
"@boxxy/rules": "0.2.4",
|
|
41
41
|
"@faker-js/faker": "10.6.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
@@ -8,7 +8,7 @@ import { measureJsonBytes } from './measure-json-bytes.ts';
|
|
|
8
8
|
interface SchemaParserInput<Raw, Body> {
|
|
9
9
|
// the body as the agent sends it, with no `frictionDropped`: the parser counts that itself
|
|
10
10
|
readonly schema: z.ZodType<Raw>;
|
|
11
|
-
readonly toBody: (raw: Raw, frictionDropped: number) => Body;
|
|
11
|
+
readonly toBody: (raw: Raw, frictionDropped: number, ctx: CloseOutContext) => Body;
|
|
12
12
|
|
|
13
13
|
// the rules of the phase that the schema cannot hold; each problem comes back
|
|
14
14
|
readonly check: (body: Body, ctx: CloseOutContext) => readonly CloseOutProblem[];
|
|
@@ -41,7 +41,7 @@ function parseCloseOut<Raw, Body>(
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const body = input.toBody(parsed.data, split.dropped);
|
|
44
|
+
const body = input.toBody(parsed.data, split.dropped, ctx);
|
|
45
45
|
const problems = input.check(body, ctx);
|
|
46
46
|
|
|
47
47
|
if (problems.length > 0) {
|
package/src/index.ts
CHANGED
|
@@ -6,5 +6,6 @@ export { build } from './phases/build.ts';
|
|
|
6
6
|
export { design } from './phases/design.ts';
|
|
7
7
|
export { createReviewPhase } from './phases/create-review-phase.ts';
|
|
8
8
|
export { triage } from './phases/triage.ts';
|
|
9
|
+
export { verify } from './phases/verify.ts';
|
|
9
10
|
export { countUnrelatedLines } from './review/count-unrelated-lines.ts';
|
|
10
11
|
export type { CloseOutLimits } from './types.ts';
|
package/src/phase-kinds.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { PhaseKind } from '@boxxy/model';
|
|
|
2
2
|
import { build } from './phases/build.ts';
|
|
3
3
|
import { design } from './phases/design.ts';
|
|
4
4
|
import { triage } from './phases/triage.ts';
|
|
5
|
+
import { verify } from './phases/verify.ts';
|
|
5
6
|
|
|
6
7
|
// The phase kinds that need no port, in pipeline order. The server adds the review phase kind,
|
|
7
8
|
// which it builds on its review gate.
|
|
8
|
-
export const phaseKinds: readonly PhaseKind[] = [triage, design, build];
|
|
9
|
+
export const phaseKinds: readonly PhaseKind[] = [triage, design, build, verify];
|
package/src/phases/build.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AttemptRow,
|
|
2
3
|
BuildCloseOut,
|
|
3
4
|
BuildInput,
|
|
4
5
|
CloseOutContext,
|
|
@@ -15,6 +16,8 @@ import {
|
|
|
15
16
|
definePhase,
|
|
16
17
|
storedReviewCloseOutSchema,
|
|
17
18
|
toJSONValue,
|
|
19
|
+
verifyCloseOutSchema,
|
|
20
|
+
verifyInputSchema,
|
|
18
21
|
} from '@boxxy/model';
|
|
19
22
|
import { match } from 'ts-pattern';
|
|
20
23
|
import type { z } from 'zod';
|
|
@@ -64,21 +67,43 @@ function buildBuildInput(ctx: PhaseInputContext): BuildInput {
|
|
|
64
67
|
return {
|
|
65
68
|
acceptance: buildCriterionTexts(ctx.item),
|
|
66
69
|
design: findLatestDesign(ctx.history),
|
|
67
|
-
sentBack:
|
|
70
|
+
sentBack: findSentBack(ctx.history),
|
|
68
71
|
pullRequest: findPullRequestLink(ctx.pullRequests),
|
|
69
72
|
};
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
// The latest grading of the work decides the send-back: a review that asked for changes sends
|
|
76
|
+
// its findings, and a failed verify sends its features. A build after a passed review or a
|
|
77
|
+
// verified verify, sent back by the owner, gets none.
|
|
78
|
+
function findSentBack(history: readonly AttemptRow[]): SentBack | null {
|
|
79
|
+
const graded = history.findLast(
|
|
80
|
+
(row) => (row.phase === 'review' || row.phase === 'verify') && row.state === 'succeeded',
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (graded?.phase === 'verify') {
|
|
84
|
+
return findVerifySentBack(graded);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const reading = findLatestCloseOut(history, 'review', storedReviewCloseOutSchema)?.reading;
|
|
76
88
|
|
|
77
89
|
return reading?.status === 'blocked' && reading.why === 'open-findings'
|
|
78
90
|
? { from: 'review', head: reading.head, open: reading.open, answerHint: reading.answerHint }
|
|
79
91
|
: null;
|
|
80
92
|
}
|
|
81
93
|
|
|
94
|
+
function findVerifySentBack(verify: AttemptRow): SentBack | null {
|
|
95
|
+
const closeOut = verifyCloseOutSchema.safeParse(verify.closeOut);
|
|
96
|
+
const input = verifyInputSchema.safeParse(verify.input);
|
|
97
|
+
|
|
98
|
+
return closeOut.success && closeOut.data.result.kind === 'failed'
|
|
99
|
+
? {
|
|
100
|
+
from: 'verify',
|
|
101
|
+
head: input.success ? input.data.head : null,
|
|
102
|
+
features: closeOut.data.result.features,
|
|
103
|
+
}
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
|
|
82
107
|
function collectBuildProblems(closeOut: BuildCloseOut, ctx: CloseOutContext): CloseOutProblem[] {
|
|
83
108
|
const acceptance = new Set(ctx.item.acceptance.map((criterion) => criterion.id));
|
|
84
109
|
const own = new Set(ctx.evidence.map((row) => row.factId));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { PhaseInputContext, VerifyCloseOut, VerifyInput } from '@boxxy/model';
|
|
2
|
+
import {
|
|
3
|
+
brandSchemas,
|
|
4
|
+
definePhase,
|
|
5
|
+
storedReviewCloseOutSchema,
|
|
6
|
+
toJSONValue,
|
|
7
|
+
verifyCloseOutSchema,
|
|
8
|
+
verifyInputSchema,
|
|
9
|
+
} from '@boxxy/model';
|
|
10
|
+
import { match } from 'ts-pattern';
|
|
11
|
+
import type { z } from 'zod';
|
|
12
|
+
import { buildSchemaParser } from '../build-schema-parser.ts';
|
|
13
|
+
import { findLatestCloseOut } from '../utils/find-latest-close-out.ts';
|
|
14
|
+
import { findPullRequestLink } from '../utils/find-pull-request-link.ts';
|
|
15
|
+
|
|
16
|
+
// the parser copies `session` from the attempt's input, so the agent never sends it
|
|
17
|
+
const verifyBodySchema = verifyCloseOutSchema.omit({ session: true, frictionDropped: true });
|
|
18
|
+
|
|
19
|
+
export const verify = definePhase({
|
|
20
|
+
name: brandSchemas.phaseName.parse('verify'),
|
|
21
|
+
version: 1,
|
|
22
|
+
worker: brandSchemas.workerKindName.parse('atc.session'),
|
|
23
|
+
outcomes: ['verified', 'failed', 'blocked', 'abandoned'],
|
|
24
|
+
needsAcceptance: true,
|
|
25
|
+
input: (ctx) => toJSONValue(buildVerifyInput(ctx)),
|
|
26
|
+
closeOut: buildSchemaParser({
|
|
27
|
+
schema: verifyBodySchema,
|
|
28
|
+
toBody: (raw: z.output<typeof verifyBodySchema>, frictionDropped, ctx): VerifyCloseOut => ({
|
|
29
|
+
...raw,
|
|
30
|
+
session: verifyInputSchema.parse(ctx.input).session,
|
|
31
|
+
frictionDropped,
|
|
32
|
+
}),
|
|
33
|
+
check: () => [],
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
// A failed verify graded the work, so it succeeds, and a `session: builder` grader keeps its
|
|
37
|
+
// session for the rework. A blocked or abandoned verify graded nothing, and fails.
|
|
38
|
+
settle: (input) => ({
|
|
39
|
+
...match(input.closeOut.result)
|
|
40
|
+
.with({ kind: 'verified' }, { kind: 'failed' }, () => ({
|
|
41
|
+
state: 'succeeded' as const,
|
|
42
|
+
failure: null,
|
|
43
|
+
}))
|
|
44
|
+
.with({ kind: 'blocked' }, { kind: 'abandoned' }, (ended) => ({
|
|
45
|
+
state: 'failed' as const,
|
|
46
|
+
failure: ended.kind,
|
|
47
|
+
}))
|
|
48
|
+
.exhaustive(),
|
|
49
|
+
outcome: input.closeOut.result.kind,
|
|
50
|
+
effects: input.closeOut.friction.map((friction) => ({
|
|
51
|
+
kind: 'record-proposal' as const,
|
|
52
|
+
friction,
|
|
53
|
+
})),
|
|
54
|
+
frictionDropped: input.closeOut.frictionDropped,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// A recipe with no `session` field starts a fresh grader.
|
|
59
|
+
function buildVerifyInput(ctx: PhaseInputContext): VerifyInput {
|
|
60
|
+
const reading = findLatestCloseOut(ctx.history, 'review', storedReviewCloseOutSchema)?.reading;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
pullRequest: findPullRequestLink(ctx.pullRequests),
|
|
64
|
+
head: reading?.status === 'passed' ? reading.head : null,
|
|
65
|
+
session: ctx.session ?? 'fresh',
|
|
66
|
+
};
|
|
67
|
+
}
|